Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 17 additions & 5 deletions core/api.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1000,15 +1000,15 @@ ion-menu,prop,menuId,string | undefined,undefined,false,true
ion-menu,prop,side,"end" | "start",'start',false,true
ion-menu,prop,swipeGesture,boolean,true,false,false
ion-menu,prop,type,"overlay" | "push" | "reveal" | undefined,undefined,false,false
ion-menu,method,close,close(animated?: boolean) => Promise<boolean>
ion-menu,method,close,close(animated?: boolean, role?: string) => Promise<boolean>
ion-menu,method,isActive,isActive() => Promise<boolean>
ion-menu,method,isOpen,isOpen() => Promise<boolean>
ion-menu,method,open,open(animated?: boolean) => Promise<boolean>
ion-menu,method,setOpen,setOpen(shouldOpen: boolean, animated?: boolean) => Promise<boolean>
ion-menu,method,setOpen,setOpen(shouldOpen: boolean, animated?: boolean, role?: string) => Promise<boolean>
ion-menu,method,toggle,toggle(animated?: boolean) => Promise<boolean>
ion-menu,event,ionDidClose,void,true
ion-menu,event,ionDidClose,MenuCloseEventDetail,true
ion-menu,event,ionDidOpen,void,true
ion-menu,event,ionWillClose,void,true
ion-menu,event,ionWillClose,MenuCloseEventDetail,true
ion-menu,event,ionWillOpen,void,true
ion-menu,css-prop,--background,ios
ion-menu,css-prop,--background,md
Expand Down Expand Up @@ -1542,6 +1542,7 @@ ion-segment,css-prop,--background,ios
ion-segment,css-prop,--background,md

ion-segment-button,shadow
ion-segment-button,prop,contentId,string | undefined,undefined,false,true
ion-segment-button,prop,disabled,boolean,false,false,false
ion-segment-button,prop,layout,"icon-bottom" | "icon-end" | "icon-hide" | "icon-start" | "icon-top" | "label-hide" | undefined,'icon-top',false,false
ion-segment-button,prop,mode,"ios" | "md",undefined,false,false
Expand Down Expand Up @@ -1607,14 +1608,20 @@ ion-segment-button,part,indicator
ion-segment-button,part,indicator-background
ion-segment-button,part,native

ion-segment-content,shadow

ion-segment-view,shadow
ion-segment-view,prop,disabled,boolean,false,false,false
ion-segment-view,event,ionSegmentViewScroll,SegmentViewScrollEvent,true

ion-select,shadow
ion-select,prop,cancelText,string,'Cancel',false,false
ion-select,prop,color,"danger" | "dark" | "light" | "medium" | "primary" | "secondary" | "success" | "tertiary" | "warning" | string & Record<never, never> | undefined,undefined,false,true
ion-select,prop,compareWith,((currentValue: any, compareValue: any) => boolean) | null | string | undefined,undefined,false,false
ion-select,prop,disabled,boolean,false,false,false
ion-select,prop,expandedIcon,string | undefined,undefined,false,false
ion-select,prop,fill,"outline" | "solid" | undefined,undefined,false,false
ion-select,prop,interface,"action-sheet" | "alert" | "popover",'alert',false,false
ion-select,prop,interface,"action-sheet" | "alert" | "modal" | "popover",'alert',false,false
ion-select,prop,interfaceOptions,any,{},false,false
ion-select,prop,justify,"end" | "space-between" | "start" | undefined,undefined,false,false
ion-select,prop,label,string | undefined,undefined,false,false
Expand Down Expand Up @@ -1672,6 +1679,11 @@ ion-select,part,label
ion-select,part,placeholder
ion-select,part,text

ion-select-modal,scoped
ion-select-modal,prop,header,string | undefined,undefined,false,false
ion-select-modal,prop,multiple,boolean | undefined,undefined,false,false
ion-select-modal,prop,options,SelectModalOption[],[],false,false

ion-select-option,shadow
ion-select-option,prop,disabled,boolean,false,false,false
ion-select-option,prop,value,any,undefined,false,false
Expand Down
116 changes: 103 additions & 13 deletions core/src/components.d.ts

Large diffs are not rendered by default.

17 changes: 13 additions & 4 deletions core/src/components/alert/alert.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -730,10 +730,12 @@ export class Alert implements ComponentInterface, OverlayInterface {
const role = this.inputs.length > 0 || this.buttons.length > 0 ? 'alertdialog' : 'alert';

/**
* If the header is defined, use that. Otherwise, fall back to the subHeader.
* If neither is defined, don't set aria-labelledby.
* Use both the header and subHeader ids if they are defined.
* If only the header is defined, use the header id.
* If only the subHeader is defined, use the subHeader id.
* If neither are defined, do not set aria-labelledby.
*/
const ariaLabelledBy = header ? hdrId : subHeader ? subHdrId : null;
const ariaLabelledBy = header && subHeader ? `${hdrId} ${subHdrId}` : header ? hdrId : subHeader ? subHdrId : null;

return (
<Host
Expand Down Expand Up @@ -766,11 +768,18 @@ export class Alert implements ComponentInterface, OverlayInterface {
{header}
</h2>
)}
{subHeader && (
{/* If no header exists, subHeader should be the highest heading level, h2 */}
{subHeader && !header && (
<h2 id={subHdrId} class="alert-sub-title">
{subHeader}
</h2>
)}
{/* If a header exists, subHeader should be one level below it, h3 */}
{subHeader && header && (
<h3 id={subHdrId} class="alert-sub-title">
{subHeader}
</h3>
)}
</div>

{this.renderAlertMessage(msgId)}
Expand Down
37 changes: 33 additions & 4 deletions core/src/components/alert/test/a11y/alert.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,27 @@ const testAria = async (

const alert = page.locator('ion-alert');

const header = alert.locator('.alert-title');
const subHeader = alert.locator('.alert-sub-title');

// If a header exists, it should be an h2 element
if ((await header.count()) > 0) {
const headerTagName = await header.evaluate((el) => el.tagName);
expect(headerTagName).toBe('H2');
}

// If a header and subHeader exist, the subHeader should be an h3 element
if ((await header.count()) > 0 && (await subHeader.count()) > 0) {
const subHeaderTagName = await subHeader.evaluate((el) => el.tagName);
expect(subHeaderTagName).toBe('H3');
}

// If a subHeader exists without a header, the subHeader should be an h2 element
if ((await header.count()) === 0 && (await subHeader.count()) > 0) {
const subHeaderTagName = await subHeader.evaluate((el) => el.tagName);
expect(subHeaderTagName).toBe('H2');
}

/**
* expect().toHaveAttribute() can't check for a null value, so grab and check
* the values manually instead.
Expand Down Expand Up @@ -124,16 +145,24 @@ configs({ directions: ['ltr'] }).forEach(({ config, title }) => {
await page.goto(`/src/components/alert/test/a11y`, config);
});

test('should have aria-labelledby when header is set', async ({ page }) => {
await testAria(page, 'noMessage', 'alert-1-hdr', null);
test('should have aria-labelledby set to both when header and subHeader are set', async ({ page }) => {
await testAria(page, 'bothHeadersOnly', 'alert-1-hdr alert-1-sub-hdr', null);
});

test('should have aria-labelledby set when only header is set', async ({ page }) => {
await testAria(page, 'headerOnly', 'alert-1-hdr', null);
});

test('should fall back to subHeader for aria-labelledby if header is not defined', async ({ page }) => {
await testAria(page, 'subHeaderOnly', 'alert-1-sub-hdr', null);
});

test('should have aria-describedby when message is set', async ({ page }) => {
await testAria(page, 'noHeaders', null, 'alert-1-msg');
});

test('should fall back to subHeader for aria-labelledby if header is not defined', async ({ page }) => {
await testAria(page, 'subHeaderOnly', 'alert-1-sub-hdr', 'alert-1-msg');
test('should have aria-labelledby and aria-describedby when headers and message are set', async ({ page }) => {
await testAria(page, 'headersAndMessage', 'alert-1-hdr alert-1-sub-hdr', 'alert-1-msg');
});

test('should allow for manually specifying aria attributes', async ({ page }) => {
Expand Down
19 changes: 13 additions & 6 deletions core/src/components/alert/test/a11y/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,11 @@
<main class="ion-padding">
<h1>Alert - A11y</h1>

<button class="expand" id="bothHeaders" onclick="presentBothHeaders()">Both Headers</button>
<button class="expand" id="bothHeadersOnly" onclick="presentBothHeadersOnly()">Both Headers Only</button>
<button class="expand" id="headerOnly" onclick="presentHeaderOnly()">Header Only</button>
<button class="expand" id="subHeaderOnly" onclick="presentSubHeaderOnly()">Subheader Only</button>
<button class="expand" id="noHeaders" onclick="presentNoHeaders()">No Headers</button>
<button class="expand" id="noMessage" onclick="presentNoMessage()">No Message</button>
<button class="expand" id="headersAndMessage" onclick="presentHeadersAndMessage()">Headers and Message</button>
<button class="expand" id="customAria" onclick="presentCustomAria()">Custom Aria</button>
<button class="expand" id="ariaLabelButton" onclick="presentAriaLabelButton()">Aria Label Button</button>
<button class="expand" id="checkbox" onclick="presentAlertCheckbox()">Checkbox</button>
Expand All @@ -34,19 +35,24 @@ <h1>Alert - A11y</h1>
await alert.present();
}

function presentBothHeaders() {
function presentBothHeadersOnly() {
openAlert({
header: 'Header',
subHeader: 'Subtitle',
message: 'This is an alert message.',
buttons: ['OK'],
});
}

function presentHeaderOnly() {
openAlert({
header: 'Header',
buttons: ['OK'],
});
}

function presentSubHeaderOnly() {
openAlert({
subHeader: 'Subtitle',
message: 'This is an alert message.',
buttons: ['OK'],
});
}
Expand All @@ -58,10 +64,11 @@ <h1>Alert - A11y</h1>
});
}

function presentNoMessage() {
function presentHeadersAndMessage() {
openAlert({
header: 'Header',
subHeader: 'Subtitle',
message: 'This is an alert message.',
buttons: ['OK'],
});
}
Expand Down
6 changes: 4 additions & 2 deletions core/src/components/checkbox/checkbox.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { ComponentInterface, EventEmitter } from '@stencil/core';
import { Component, Element, Event, Host, Prop, h } from '@stencil/core';
import { Component, Element, Event, Host, Method, Prop, h } from '@stencil/core';
import type { Attributes } from '@utils/helpers';
import { inheritAriaAttributes, renderHiddenInput } from '@utils/helpers';
import { createColorClasses, hostContext } from '@utils/theme';
Expand Down Expand Up @@ -121,7 +121,9 @@ export class Checkbox implements ComponentInterface {
};
}

private setFocus() {
/** @internal */
@Method()
async setFocus() {
if (this.focusEl) {
this.focusEl.focus();
}
Expand Down
8 changes: 6 additions & 2 deletions core/src/components/menu/menu-interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export interface MenuI {
close(animated?: boolean): Promise<boolean>;
toggle(animated?: boolean): Promise<boolean>;
setOpen(shouldOpen: boolean, animated?: boolean): Promise<boolean>;
_setOpen(shouldOpen: boolean, animated?: boolean): Promise<boolean>;
_setOpen(shouldOpen: boolean, animated?: boolean, role?: string): Promise<boolean>;
}

export interface MenuControllerI {
Expand All @@ -42,14 +42,18 @@ export interface MenuControllerI {
_createAnimation(type: string, menuCmp: MenuI): Promise<Animation>;
_register(menu: MenuI): void;
_unregister(menu: MenuI): void;
_setOpen(menu: MenuI, shouldOpen: boolean, animated: boolean): Promise<boolean>;
_setOpen(menu: MenuI, shouldOpen: boolean, animated: boolean, role?: string): Promise<boolean>;
}

export interface MenuChangeEventDetail {
disabled: boolean;
open: boolean;
}

export interface MenuCloseEventDetail {
role?: string;
}

export interface MenuCustomEvent<T = any> extends CustomEvent {
detail: T;
target: HTMLIonMenuElement;
Expand Down
Loading
Loading