mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-18 19:21:34 +08:00
fix(select): improvements for announcing placeholder and value on screenreaders (#22556)
- Hides select text from screen readers so it isn't announced twice (Android talkback needs this) - Adds the placeholder text to be announced if there is no value - Don't add a comma if there is no value/placeholder (NVDA speech viewer) - Don't announce alert label twice
This commit is contained in:
@ -373,22 +373,24 @@ export class Alert implements ComponentInterface, OverlayInterface {
|
|||||||
return values;
|
return values;
|
||||||
}
|
}
|
||||||
|
|
||||||
private renderAlertInputs(labelledBy: string | undefined) {
|
private renderAlertInputs() {
|
||||||
switch (this.inputType) {
|
switch (this.inputType) {
|
||||||
case 'checkbox': return this.renderCheckbox(labelledBy);
|
case 'checkbox': return this.renderCheckbox();
|
||||||
case 'radio': return this.renderRadio(labelledBy);
|
case 'radio': return this.renderRadio();
|
||||||
default: return this.renderInput(labelledBy);
|
default: return this.renderInput();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private renderCheckbox(labelledby: string | undefined) {
|
private renderCheckbox() {
|
||||||
const inputs = this.processedInputs;
|
const inputs = this.processedInputs;
|
||||||
const mode = getIonMode(this);
|
const mode = getIonMode(this);
|
||||||
|
|
||||||
if (inputs.length === 0) {
|
if (inputs.length === 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class="alert-checkbox-group" aria-labelledby={labelledby}>
|
<div class="alert-checkbox-group">
|
||||||
{ inputs.map(i => (
|
{ inputs.map(i => (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@ -422,13 +424,15 @@ export class Alert implements ComponentInterface, OverlayInterface {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private renderRadio(labelledby: string | undefined) {
|
private renderRadio() {
|
||||||
const inputs = this.processedInputs;
|
const inputs = this.processedInputs;
|
||||||
|
|
||||||
if (inputs.length === 0) {
|
if (inputs.length === 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class="alert-radio-group" role="radiogroup" aria-labelledby={labelledby} aria-activedescendant={this.activeId}>
|
<div class="alert-radio-group" role="radiogroup" aria-activedescendant={this.activeId}>
|
||||||
{ inputs.map(i => (
|
{ inputs.map(i => (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@ -459,13 +463,13 @@ export class Alert implements ComponentInterface, OverlayInterface {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private renderInput(labelledby: string | undefined) {
|
private renderInput() {
|
||||||
const inputs = this.processedInputs;
|
const inputs = this.processedInputs;
|
||||||
if (inputs.length === 0) {
|
if (inputs.length === 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<div class="alert-input-group" aria-labelledby={labelledby}>
|
<div class="alert-input-group">
|
||||||
{ inputs.map(i => {
|
{ inputs.map(i => {
|
||||||
if (i.type === 'textarea') {
|
if (i.type === 'textarea') {
|
||||||
return (
|
return (
|
||||||
@ -552,13 +556,6 @@ export class Alert implements ComponentInterface, OverlayInterface {
|
|||||||
const subHdrId = `alert-${overlayIndex}-sub-hdr`;
|
const subHdrId = `alert-${overlayIndex}-sub-hdr`;
|
||||||
const msgId = `alert-${overlayIndex}-msg`;
|
const msgId = `alert-${overlayIndex}-msg`;
|
||||||
|
|
||||||
let labelledById: string | undefined;
|
|
||||||
if (header !== undefined) {
|
|
||||||
labelledById = hdrId;
|
|
||||||
} else if (subHeader !== undefined) {
|
|
||||||
labelledById = subHdrId;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Host
|
<Host
|
||||||
role="dialog"
|
role="dialog"
|
||||||
@ -589,7 +586,7 @@ export class Alert implements ComponentInterface, OverlayInterface {
|
|||||||
|
|
||||||
<div id={msgId} class="alert-message" innerHTML={sanitizeDOMString(this.message)}></div>
|
<div id={msgId} class="alert-message" innerHTML={sanitizeDOMString(this.message)}></div>
|
||||||
|
|
||||||
{this.renderAlertInputs(labelledById)}
|
{this.renderAlertInputs()}
|
||||||
{this.renderAlertButtons()}
|
{this.renderAlertButtons()}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
@ -458,11 +458,12 @@ export class Select implements ComponentInterface {
|
|||||||
const textPart = addPlaceholderClass ? 'placeholder' : 'text';
|
const textPart = addPlaceholderClass ? 'placeholder' : 'text';
|
||||||
|
|
||||||
// If there is a label then we need to concatenate it with the
|
// If there is a label then we need to concatenate it with the
|
||||||
// current value and a comma so it separates nicely when the screen reader
|
// current value (or placeholder) and a comma so it separates
|
||||||
// announces it, otherwise just announce the value
|
// nicely when the screen reader announces it, otherwise just
|
||||||
|
// announce the value / placeholder
|
||||||
const displayLabel = labelText !== undefined
|
const displayLabel = labelText !== undefined
|
||||||
? `${displayValue}, ${labelText}`
|
? (selectText !== '' ? `${selectText}, ${labelText}` : labelText)
|
||||||
: displayValue;
|
: selectText;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Host
|
<Host
|
||||||
@ -477,7 +478,7 @@ export class Select implements ComponentInterface {
|
|||||||
'select-disabled': disabled,
|
'select-disabled': disabled,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div class={selectTextClasses} part={textPart}>
|
<div aria-hidden="true" class={selectTextClasses} part={textPart}>
|
||||||
{selectText}
|
{selectText}
|
||||||
</div>
|
</div>
|
||||||
<div class="select-icon" role="presentation" part="icon">
|
<div class="select-icon" role="presentation" part="icon">
|
||||||
|
@ -31,7 +31,7 @@
|
|||||||
</ion-list-header>
|
</ion-list-header>
|
||||||
|
|
||||||
<div class="native-select-wrapper">
|
<div class="native-select-wrapper">
|
||||||
<label for="pet-select">Choose a Pet</label>
|
<label for="pet-select">Favorite Pet</label>
|
||||||
|
|
||||||
<select name="pets" id="pet-select">
|
<select name="pets" id="pet-select">
|
||||||
<option value="dog">Dog</option>
|
<option value="dog">Dog</option>
|
||||||
@ -52,9 +52,35 @@
|
|||||||
</ion-list-header>
|
</ion-list-header>
|
||||||
|
|
||||||
<ion-item>
|
<ion-item>
|
||||||
<label for="ionic-select">Choose a Pet Custom</label>
|
<ion-label>Favorite Pet</ion-label>
|
||||||
|
|
||||||
<ion-select id="ionic-select">
|
<ion-select>
|
||||||
|
<ion-select-option value="dog">Dog</ion-select-option>
|
||||||
|
<ion-select-option value="cat">Cat</ion-select-option>
|
||||||
|
<ion-select-option value="hamster">Hamster</ion-select-option>
|
||||||
|
<ion-select-option value="parrot">Parrot</ion-select-option>
|
||||||
|
<ion-select-option value="spider">Spider</ion-select-option>
|
||||||
|
<ion-select-option value="goldfish">Goldfish</ion-select-option>
|
||||||
|
</ion-select>
|
||||||
|
</ion-item>
|
||||||
|
|
||||||
|
<ion-item>
|
||||||
|
<ion-label>Favorite Pet</ion-label>
|
||||||
|
|
||||||
|
<ion-select placeholder="Select a Pet">
|
||||||
|
<ion-select-option value="dog">Dog</ion-select-option>
|
||||||
|
<ion-select-option value="cat">Cat</ion-select-option>
|
||||||
|
<ion-select-option value="hamster">Hamster</ion-select-option>
|
||||||
|
<ion-select-option value="parrot">Parrot</ion-select-option>
|
||||||
|
<ion-select-option value="spider">Spider</ion-select-option>
|
||||||
|
<ion-select-option value="goldfish">Goldfish</ion-select-option>
|
||||||
|
</ion-select>
|
||||||
|
</ion-item>
|
||||||
|
|
||||||
|
<ion-item>
|
||||||
|
<ion-label>Favorite Pet</ion-label>
|
||||||
|
|
||||||
|
<ion-select value="spider">
|
||||||
<ion-select-option value="dog">Dog</ion-select-option>
|
<ion-select-option value="dog">Dog</ion-select-option>
|
||||||
<ion-select-option value="cat">Cat</ion-select-option>
|
<ion-select-option value="cat">Cat</ion-select-option>
|
||||||
<ion-select-option value="hamster">Hamster</ion-select-option>
|
<ion-select-option value="hamster">Hamster</ion-select-option>
|
||||||
@ -73,9 +99,9 @@
|
|||||||
</ion-list-header>
|
</ion-list-header>
|
||||||
|
|
||||||
<ion-item>
|
<ion-item>
|
||||||
<ion-label>Choose a Pet</ion-label>
|
<label for="ionic-select">Favorite Pet</label>
|
||||||
|
|
||||||
<ion-select>
|
<ion-select id="ionic-select">
|
||||||
<ion-select-option value="dog">Dog</ion-select-option>
|
<ion-select-option value="dog">Dog</ion-select-option>
|
||||||
<ion-select-option value="cat">Cat</ion-select-option>
|
<ion-select-option value="cat">Cat</ion-select-option>
|
||||||
<ion-select-option value="hamster">Hamster</ion-select-option>
|
<ion-select-option value="hamster">Hamster</ion-select-option>
|
||||||
@ -94,7 +120,7 @@
|
|||||||
</ion-list-header>
|
</ion-list-header>
|
||||||
|
|
||||||
<ion-item>
|
<ion-item>
|
||||||
<ion-label>Choose a Pet</ion-label>
|
<ion-label>Favorite Pet</ion-label>
|
||||||
|
|
||||||
<ion-select interface="popover">
|
<ion-select interface="popover">
|
||||||
<ion-select-option value="dog">Dog</ion-select-option>
|
<ion-select-option value="dog">Dog</ion-select-option>
|
||||||
@ -115,7 +141,7 @@
|
|||||||
</ion-list-header>
|
</ion-list-header>
|
||||||
|
|
||||||
<ion-item>
|
<ion-item>
|
||||||
<ion-label>Choose a Pet</ion-label>
|
<ion-label>Favorite Pet</ion-label>
|
||||||
|
|
||||||
<ion-select interface="action-sheet">
|
<ion-select interface="action-sheet">
|
||||||
<ion-select-option value="dog">Dog</ion-select-option>
|
<ion-select-option value="dog">Dog</ion-select-option>
|
||||||
|
Reference in New Issue
Block a user