Skip to content

Commit

Permalink
feat(toggle): add ios 18 haptic feedback
Browse files Browse the repository at this point in the history
  • Loading branch information
jedlikowski committed Oct 17, 2024
1 parent cdb4456 commit b29e007
Showing 1 changed file with 35 additions and 3 deletions.
38 changes: 35 additions & 3 deletions core/src/components/toggle/toggle.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { ComponentInterface, EventEmitter } from '@stencil/core';
import { Component, Element, Event, Host, Prop, State, Watch, h } from '@stencil/core';
import { renderHiddenInput, inheritAriaAttributes } from '@utils/helpers';
import type { Attributes } from '@utils/helpers';
import { hapticSelection } from '@utils/native/haptic';
import { hapticAvailable, hapticSelection } from '@utils/native/haptic';
import { isRTL } from '@utils/rtl';
import { createColorClasses, hostContext } from '@utils/theme';
import { checkmarkOutline, removeOutline, ellipseOutline } from 'ionicons/icons';
Expand Down Expand Up @@ -34,6 +34,7 @@ export class Toggle implements ComponentInterface {
private inputId = `ion-tg-${toggleIds++}`;
private gesture?: Gesture;
private focusEl?: HTMLElement;
private hapticEl?: HTMLElement;
private lastDrag = 0;
private inheritedAttributes: Attributes = {};
private toggleTrack?: HTMLElement;
Expand Down Expand Up @@ -138,6 +139,10 @@ export class Toggle implements ComponentInterface {
const isNowChecked = !checked;
this.checked = isNowChecked;

if (this.hapticEl) {
this.hapticEl.click();
}

this.ionChange.emit({
checked: isNowChecked,
value,
Expand Down Expand Up @@ -285,6 +290,33 @@ export class Toggle implements ComponentInterface {
);
}

/**
* On Safari (iOS 18+) we can trigger haptic feedback programatically
* by rendering <input type="checkbox" switch> element
* with an associated <label> and triggering click()
* on the <label> element.
*/
private renderFallbackHapticElements() {
const { inputId } = this;
const mode = getIonMode(this);

if (hapticAvailable() || mode !== 'ios') {
return;
}

return (
<label aria-hidden="true" ref={(hapticEl) => (this.hapticEl = hapticEl)} style={{ display: 'none' }}>
<input
id={inputId + '-haptic'}
type="checkbox"
// @ts-expect-error safari-only custom attrrbute required for haptic feedback
switch
style={{ display: 'none' }}
/>
</label>
);
}

private get hasLabel() {
return this.el.textContent !== '';
}
Expand All @@ -299,7 +331,6 @@ export class Toggle implements ComponentInterface {

return (
<Host
onClick={this.onClick}
class={createColorClasses(color, {
[mode]: true,
'in-item': hostContext('ion-item', el),
Expand All @@ -312,7 +343,8 @@ export class Toggle implements ComponentInterface {
[`toggle-${rtl}`]: true,
})}
>
<label class="toggle-wrapper">
{this.renderFallbackHapticElements()}
<label class="toggle-wrapper" onClick={this.onClick}>
{/*
The native control must be rendered
before the visible label text due to https://bugs.webkit.org/show_bug.cgi?id=251951
Expand Down

0 comments on commit b29e007

Please sign in to comment.