mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-11-08 23:58:13 +08:00
fix(toggle): ensure proper visual selection when navigating via VoiceOver in Safari (#30349)
Issue number: resolves internal --------- <!-- Please do not submit updates to dependencies unless it fixes an issue. --> <!-- Please try to limit your pull request to one type (bugfix, feature, etc). Submit multiple pull requests if needed. --> ## What is the current behavior? <!-- Please describe the current behavior that you are modifying. --> Currently, MacOS voice over on Safari does not recognize ion-toggle correctly and fails to highlight the element properly ## What is the new behavior? <!-- Please describe the behavior or changes that are being added by this PR. --> By adding the role property to the host element, we're correctly identifying the toggle so Safari knows how to handle it. ## Does this introduce a breaking change? - [ ] Yes - [X] No <!-- If this introduces a breaking change: 1. Describe the impact and migration path for existing applications below. 2. Update the BREAKING.md file with the breaking change. 3. Add "BREAKING CHANGE: [...]" to the commit description when merging. See https://github.com/ionic-team/ionic-framework/blob/main/docs/CONTRIBUTING.md#footer for more information. --> ## Other information <!-- Any other information that is important to this PR such as screenshots of how the component looks before and after the change. -->
This commit is contained in:
@ -31,8 +31,6 @@
|
|||||||
|
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
|
|
||||||
outline: none;
|
|
||||||
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
z-index: $z-index-item-input;
|
z-index: $z-index-item-input;
|
||||||
@ -69,8 +67,12 @@
|
|||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The native input must be hidden with display instead of visibility or
|
||||||
|
* aria-hidden to avoid accessibility issues with nested interactive elements.
|
||||||
|
*/
|
||||||
input {
|
input {
|
||||||
@include visually-hidden();
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Toggle Wrapper
|
// Toggle Wrapper
|
||||||
|
|||||||
@ -35,6 +35,7 @@ import type { ToggleChangeEventDetail } from './toggle-interface';
|
|||||||
})
|
})
|
||||||
export class Toggle implements ComponentInterface {
|
export class Toggle implements ComponentInterface {
|
||||||
private inputId = `ion-tg-${toggleIds++}`;
|
private inputId = `ion-tg-${toggleIds++}`;
|
||||||
|
private inputLabelId = `${this.inputId}-lbl`;
|
||||||
private helperTextId = `${this.inputId}-helper-text`;
|
private helperTextId = `${this.inputId}-helper-text`;
|
||||||
private errorTextId = `${this.inputId}-error-text`;
|
private errorTextId = `${this.inputId}-error-text`;
|
||||||
private gesture?: Gesture;
|
private gesture?: Gesture;
|
||||||
@ -246,6 +247,15 @@ export class Toggle implements ComponentInterface {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private onKeyDown = (ev: KeyboardEvent) => {
|
||||||
|
if (ev.key === ' ') {
|
||||||
|
ev.preventDefault();
|
||||||
|
if (!this.disabled) {
|
||||||
|
this.toggleChecked();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
private onClick = (ev: MouseEvent) => {
|
private onClick = (ev: MouseEvent) => {
|
||||||
if (this.disabled) {
|
if (this.disabled) {
|
||||||
return;
|
return;
|
||||||
@ -355,8 +365,23 @@ export class Toggle implements ComponentInterface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { activated, color, checked, disabled, el, justify, labelPlacement, inputId, name, alignment, required } =
|
const {
|
||||||
this;
|
activated,
|
||||||
|
alignment,
|
||||||
|
checked,
|
||||||
|
color,
|
||||||
|
disabled,
|
||||||
|
el,
|
||||||
|
errorTextId,
|
||||||
|
hasLabel,
|
||||||
|
inheritedAttributes,
|
||||||
|
inputId,
|
||||||
|
inputLabelId,
|
||||||
|
justify,
|
||||||
|
labelPlacement,
|
||||||
|
name,
|
||||||
|
required,
|
||||||
|
} = this;
|
||||||
|
|
||||||
const mode = getIonMode(this);
|
const mode = getIonMode(this);
|
||||||
const value = this.getValue();
|
const value = this.getValue();
|
||||||
@ -365,9 +390,16 @@ export class Toggle implements ComponentInterface {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Host
|
<Host
|
||||||
|
role="switch"
|
||||||
|
aria-checked={`${checked}`}
|
||||||
aria-describedby={this.getHintTextID()}
|
aria-describedby={this.getHintTextID()}
|
||||||
aria-invalid={this.getHintTextID() === this.errorTextId}
|
aria-invalid={this.getHintTextID() === errorTextId}
|
||||||
onClick={this.onClick}
|
onClick={this.onClick}
|
||||||
|
aria-labelledby={hasLabel ? inputLabelId : null}
|
||||||
|
aria-label={inheritedAttributes['aria-label'] || null}
|
||||||
|
aria-disabled={disabled ? 'true' : null}
|
||||||
|
tabindex={disabled ? undefined : 0}
|
||||||
|
onKeyDown={this.onKeyDown}
|
||||||
class={createColorClasses(color, {
|
class={createColorClasses(color, {
|
||||||
[mode]: true,
|
[mode]: true,
|
||||||
'in-item': hostContext('ion-item', el),
|
'in-item': hostContext('ion-item', el),
|
||||||
@ -380,7 +412,7 @@ export class Toggle implements ComponentInterface {
|
|||||||
[`toggle-${rtl}`]: true,
|
[`toggle-${rtl}`]: true,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<label class="toggle-wrapper">
|
<label class="toggle-wrapper" htmlFor={inputId}>
|
||||||
{/*
|
{/*
|
||||||
The native control must be rendered
|
The native control must be rendered
|
||||||
before the visible label text due to https://bugs.webkit.org/show_bug.cgi?id=251951
|
before the visible label text due to https://bugs.webkit.org/show_bug.cgi?id=251951
|
||||||
@ -396,14 +428,15 @@ export class Toggle implements ComponentInterface {
|
|||||||
onBlur={() => this.onBlur()}
|
onBlur={() => this.onBlur()}
|
||||||
ref={(focusEl) => (this.focusEl = focusEl)}
|
ref={(focusEl) => (this.focusEl = focusEl)}
|
||||||
required={required}
|
required={required}
|
||||||
{...this.inheritedAttributes}
|
{...inheritedAttributes}
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
class={{
|
class={{
|
||||||
'label-text-wrapper': true,
|
'label-text-wrapper': true,
|
||||||
'label-text-wrapper-hidden': !this.hasLabel,
|
'label-text-wrapper-hidden': !hasLabel,
|
||||||
}}
|
}}
|
||||||
part="label"
|
part="label"
|
||||||
|
id={inputLabelId}
|
||||||
>
|
>
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
{this.renderHintText()}
|
{this.renderHintText()}
|
||||||
|
|||||||
Reference in New Issue
Block a user