{
this.scrollEl = el;
}}
- /**
- * When an element has an overlay scroll style and
- * a fixed height, Firefox will focus the scrollable
- * container if the content exceeds the container's
- * dimensions.
- *
- * This causes keyboard navigation to focus to this
- * element instead of going to the next element in
- * the tab order.
- *
- * The desired behavior is for the user to be able to
- * focus the assistive focusable element and tab to
- * the next element in the tab order. Instead of tabbing
- * to this element.
- *
- * To prevent this, we set the tabIndex to -1. This
- * will match the behavior of the other browsers.
- */
- tabIndex={-1}
+ role="slider"
+ tabindex={this.disabled ? undefined : 0}
+ aria-label={this.ariaLabel}
+ aria-valuemin={0}
+ aria-valuemax={0}
+ aria-valuenow={0}
+ aria-valuetext={this.getOptionValueText(this.activeItem)}
+ aria-orientation="vertical"
+ onKeyDown={(ev) => this.onKeyDown(ev)}
>
diff --git a/core/src/components/picker-column/test/picker-column.spec.tsx b/core/src/components/picker-column/test/picker-column.spec.tsx
index 55a9c1c88da..479daba5352 100644
--- a/core/src/components/picker-column/test/picker-column.spec.tsx
+++ b/core/src/components/picker-column/test/picker-column.spec.tsx
@@ -1,10 +1,10 @@
import { h } from '@stencil/core';
import { newSpecPage } from '@stencil/core/testing';
-import { PickerColumn } from '../picker-column';
import { PickerColumnOption } from '../../picker-column-option/picker-column-option';
+import { PickerColumn } from '../picker-column';
-describe('picker-column: assistive element', () => {
+describe('picker-column', () => {
beforeEach(() => {
const mockIntersectionObserver = jest.fn();
mockIntersectionObserver.mockReturnValue({
@@ -22,9 +22,9 @@ describe('picker-column: assistive element', () => {
});
const pickerCol = page.body.querySelector('ion-picker-column')!;
- const assistiveFocusable = pickerCol.shadowRoot!.querySelector('.assistive-focusable')!;
+ const pickerOpts = pickerCol.shadowRoot!.querySelector('.picker-opts')!;
- expect(assistiveFocusable.getAttribute('aria-label')).not.toBe(null);
+ expect(pickerOpts.getAttribute('aria-label')).not.toBe(null);
});
it('should have a custom label', async () => {
@@ -34,9 +34,9 @@ describe('picker-column: assistive element', () => {
});
const pickerCol = page.body.querySelector('ion-picker-column')!;
- const assistiveFocusable = pickerCol.shadowRoot!.querySelector('.assistive-focusable')!;
+ const pickerOpts = pickerCol.shadowRoot!.querySelector('.picker-opts')!;
- expect(assistiveFocusable.getAttribute('aria-label')).toBe('my label');
+ expect(pickerOpts.getAttribute('aria-label')).toBe('my label');
});
it('should update a custom label', async () => {
@@ -46,12 +46,12 @@ describe('picker-column: assistive element', () => {
});
const pickerCol = page.body.querySelector('ion-picker-column')!;
- const assistiveFocusable = pickerCol.shadowRoot!.querySelector('.assistive-focusable')!;
+ const pickerOpts = pickerCol.shadowRoot!.querySelector('.picker-opts')!;
pickerCol.setAttribute('aria-label', 'my label');
await page.waitForChanges();
- expect(assistiveFocusable.getAttribute('aria-label')).toBe('my label');
+ expect(pickerOpts.getAttribute('aria-label')).toBe('my label');
});
it('should receive keyboard focus when enabled', async () => {
@@ -61,9 +61,9 @@ describe('picker-column: assistive element', () => {
});
const pickerCol = page.body.querySelector('ion-picker-column')!;
- const assistiveFocusable = pickerCol.shadowRoot!.querySelector
('.assistive-focusable')!;
+ const pickerOpts = pickerCol.shadowRoot!.querySelector('.picker-opts')!;
- expect(assistiveFocusable.tabIndex).toBe(0);
+ expect(pickerOpts.tabIndex).toBe(0);
});
it('should not receive keyboard focus when disabled', async () => {
@@ -73,9 +73,9 @@ describe('picker-column: assistive element', () => {
});
const pickerCol = page.body.querySelector('ion-picker-column')!;
- const assistiveFocusable = pickerCol.shadowRoot!.querySelector('.assistive-focusable')!;
+ const pickerOpts = pickerCol.shadowRoot!.querySelector('.picker-opts')!;
- expect(assistiveFocusable.tabIndex).toBe(-1);
+ expect(pickerOpts.tabIndex).toBe(-1);
});
it('should use option aria-label as assistive element aria-valuetext', async () => {
@@ -91,9 +91,9 @@ describe('picker-column: assistive element', () => {
});
const pickerCol = page.body.querySelector('ion-picker-column')!;
- const assistiveFocusable = pickerCol.shadowRoot!.querySelector('.assistive-focusable')!;
+ const pickerOpts = pickerCol.shadowRoot!.querySelector('.picker-opts')!;
- expect(assistiveFocusable.getAttribute('aria-valuetext')).toBe('My Label');
+ expect(pickerOpts.getAttribute('aria-valuetext')).toBe('My Label');
});
it('should use option text as assistive element aria-valuetext when no label provided', async () => {
@@ -107,8 +107,8 @@ describe('picker-column: assistive element', () => {
});
const pickerCol = page.body.querySelector('ion-picker-column')!;
- const assistiveFocusable = pickerCol.shadowRoot!.querySelector('.assistive-focusable')!;
+ const pickerOpts = pickerCol.shadowRoot!.querySelector('.picker-opts')!;
- expect(assistiveFocusable.getAttribute('aria-valuetext')).toBe('My Text');
+ expect(pickerOpts.getAttribute('aria-valuetext')).toBe('My Text');
});
});
diff --git a/core/src/components/picker/test/a11y/picker.e2e.ts b/core/src/components/picker/test/a11y/picker.e2e.ts
index 4c5d206cdd2..5ad5989adf2 100644
--- a/core/src/components/picker/test/a11y/picker.e2e.ts
+++ b/core/src/components/picker/test/a11y/picker.e2e.ts
@@ -9,7 +9,14 @@ configs().forEach(({ title, config }) => {
const results = await new AxeBuilder({ page }).analyze();
- expect(results.violations).toEqual([]);
+ const hasKnownViolations = results.violations.filter((violation) => violation.id === 'color-contrast');
+ const violations = results.violations.filter((violation) => !hasKnownViolations.includes(violation));
+
+ if (hasKnownViolations.length > 0) {
+ console.warn('A11Y: Known violation - contrast color.', hasKnownViolations);
+ }
+
+ expect(violations).toEqual([]);
});
});
});
diff --git a/core/src/components/picker/test/basic/index.html b/core/src/components/picker/test/basic/index.html
index b5c7c58f1d1..279b32c38c2 100644
--- a/core/src/components/picker/test/basic/index.html
+++ b/core/src/components/picker/test/basic/index.html
@@ -177,6 +177,10 @@ Modal
'onion'
);
+ const columnDualNumericFirst = document.querySelector('ion-picker-column#dual-numeric-first');
+ columnDualNumericFirst.addEventListener('ionChange', (ev) => {
+ console.log('Column change', ev.detail);
+ });
setPickerColumn(
'#dual-numeric-first',
[
@@ -195,6 +199,10 @@ Modal
],
3
);
+ const columnDualNumericSecond = document.querySelector('ion-picker-column#dual-numeric-second');
+ columnDualNumericSecond.addEventListener('ionChange', (ev) => {
+ console.log('Column change', ev.detail);
+ });
setPickerColumn('#dual-numeric-second', minutes, 3);
setPickerColumn(
diff --git a/core/src/components/picker/test/basic/picker.e2e.ts b/core/src/components/picker/test/basic/picker.e2e.ts
index 5c4a65caf7b..19908fe1106 100644
--- a/core/src/components/picker/test/basic/picker.e2e.ts
+++ b/core/src/components/picker/test/basic/picker.e2e.ts
@@ -106,32 +106,43 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
});
test('tabbing should correctly move focus between columns', async ({ page }) => {
- const firstColumn = page.locator('ion-picker-column#first');
- const secondColumn = page.locator('ion-picker-column#second');
+ const firstColumn = await page.evaluate(() => document.querySelector('ion-picker-column#first'));
+ const secondColumn = await page.evaluate(() => document.querySelector('ion-picker-column#second'));
// Focus first column
await page.keyboard.press('Tab');
- await expect(firstColumn).toBeFocused();
+
+ let activeElement = await page.evaluate(() => document.activeElement);
+ expect(activeElement).toEqual(firstColumn);
await page.waitForChanges();
// Focus second column
await page.keyboard.press('Tab');
- await expect(secondColumn).toBeFocused();
+
+ activeElement = await page.evaluate(() => document.activeElement);
+ expect(activeElement).toEqual(secondColumn);
});
test('tabbing should correctly move focus back', async ({ page }) => {
- const firstColumn = page.locator('ion-picker-column#first');
- const secondColumn = page.locator('ion-picker-column#second');
+ const firstColumn = await page.evaluate(() => document.querySelector('ion-picker-column#first'));
+ const secondColumn = await page.evaluate(() => document.querySelector('ion-picker-column#second'));
- await secondColumn.evaluate((el: HTMLIonPickerColumnElement) => el.setFocus());
- await expect(secondColumn).toBeFocused();
+ await page.evaluate((selector) => {
+ const el = document.querySelector(selector) as HTMLElement | null;
+ el?.focus();
+ }, 'ion-picker-column#second');
+
+ let activeElement = await page.evaluate(() => document.activeElement);
+ expect(activeElement).toEqual(secondColumn);
await page.waitForChanges();
// Focus first column
await page.keyboard.press('Shift+Tab');
- await expect(firstColumn).toBeFocused();
+
+ activeElement = await page.evaluate(() => document.activeElement);
+ expect(activeElement).toEqual(firstColumn);
});
});
});
diff --git a/core/src/components/picker/test/basic/picker.e2e.ts-snapshots/picker-modal-diff-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/picker/test/basic/picker.e2e.ts-snapshots/picker-modal-diff-ios-ltr-Mobile-Chrome-linux.png
index c2dbaa4aedb..4656b449060 100644
Binary files a/core/src/components/picker/test/basic/picker.e2e.ts-snapshots/picker-modal-diff-ios-ltr-Mobile-Chrome-linux.png and b/core/src/components/picker/test/basic/picker.e2e.ts-snapshots/picker-modal-diff-ios-ltr-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/picker/test/basic/picker.e2e.ts-snapshots/picker-modal-diff-md-ltr-Mobile-Chrome-linux.png b/core/src/components/picker/test/basic/picker.e2e.ts-snapshots/picker-modal-diff-md-ltr-Mobile-Chrome-linux.png
index 9483724b697..7d23340c2e1 100644
Binary files a/core/src/components/picker/test/basic/picker.e2e.ts-snapshots/picker-modal-diff-md-ltr-Mobile-Chrome-linux.png and b/core/src/components/picker/test/basic/picker.e2e.ts-snapshots/picker-modal-diff-md-ltr-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/picker/test/keyboard-entry/picker.e2e.ts b/core/src/components/picker/test/keyboard-entry/picker.e2e.ts
index b54cd03585a..35a66caa918 100644
--- a/core/src/components/picker/test/keyboard-entry/picker.e2e.ts
+++ b/core/src/components/picker/test/keyboard-entry/picker.e2e.ts
@@ -38,8 +38,12 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
);
const column = page.locator('ion-picker-column');
+
+ const colShadowRoot = await column.evaluateHandle((el) => el.shadowRoot);
+ const columnPickerOpts = await colShadowRoot.evaluateHandle((root) => root?.querySelector('.picker-opts'));
+
const ionChange = await page.spyOnEvent('ionChange');
- await column.evaluate((el: HTMLIonPickerColumnElement) => el.setFocus());
+ await columnPickerOpts.evaluate((el) => el && (el as HTMLElement).focus());
await page.keyboard.press('Digit2');
@@ -99,23 +103,25 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
);
const firstColumn = page.locator('ion-picker-column#first');
const secondColumn = page.locator('ion-picker-column#second');
- const highlight = page.locator('ion-picker .picker-highlight');
const firstIonChange = await (firstColumn as E2ELocator).spyOnEvent('ionChange');
const secondIonChange = await (secondColumn as E2ELocator).spyOnEvent('ionChange');
- const box = await highlight.boundingBox();
- if (box !== null) {
- await page.mouse.click(box.x + box.width / 2, box.y + box.height / 2);
- }
+ const firstColShadowRoot = await firstColumn.evaluateHandle((el) => el.shadowRoot);
+ const columnPickerOpts = await firstColShadowRoot.evaluateHandle((root) => root?.querySelector('.picker-opts'));
- await expect(firstColumn).toHaveClass(/picker-column-active/);
- await expect(secondColumn).toHaveClass(/picker-column-active/);
+ // Focus first column
+ await columnPickerOpts.evaluate((el) => el && (el as HTMLElement).focus());
await page.keyboard.press('Digit2');
await expect(firstIonChange).toHaveReceivedEventDetail({ value: 2 });
await expect(firstColumn).toHaveJSProperty('value', 2);
+ // Focus second column
+ await page.keyboard.press('Tab');
+
+ await page.waitForChanges();
+
await page.keyboard.press('Digit2+Digit4');
await expect(secondIonChange).toHaveReceivedEventDetail({ value: 24 });
@@ -155,8 +161,12 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
);
const column = page.locator('ion-picker-column');
+
+ const colShadowRoot = await column.evaluateHandle((el) => el.shadowRoot);
+ const columnPickerOpts = await colShadowRoot.evaluateHandle((root) => root?.querySelector('.picker-opts'));
+
const ionChange = await page.spyOnEvent('ionChange');
- await column.evaluate((el: HTMLIonPickerColumnElement) => el.setFocus());
+ await columnPickerOpts.evaluate((el) => el && (el as HTMLElement).focus());
await page.keyboard.press('Digit0');
diff --git a/core/src/utils/overlays.ts b/core/src/utils/overlays.ts
index 9dfc0b63edd..bb677761edf 100644
--- a/core/src/utils/overlays.ts
+++ b/core/src/utils/overlays.ts
@@ -32,7 +32,6 @@ import {
getElementRoot,
removeEventListener,
} from './helpers';
-import { isPlatform } from './platform';
let lastOverlayIndex = 0;
let lastId = 0;
@@ -523,9 +522,6 @@ export const present = async (
document.body.classList.add(BACKDROP_NO_SCROLL);
}
- hideUnderlyingOverlaysFromScreenReaders(overlay.el);
- hideAnimatingOverlayFromScreenReaders(overlay.el);
-
overlay.presented = true;
overlay.willPresent.emit();
overlay.willPresentShorthand?.emit();
@@ -674,13 +670,6 @@ export const dismiss = async (
overlay.presented = false;
try {
- /**
- * There is no need to show the overlay to screen readers during
- * the dismiss animation. This is because the overlay will be removed
- * from the DOM after the animation is complete.
- */
- hideAnimatingOverlayFromScreenReaders(overlay.el);
-
// Overlay contents should not be clickable during dismiss
overlay.el.style.setProperty('pointer-events', 'none');
overlay.willDismiss.emit({ data, role });
@@ -728,8 +717,6 @@ export const dismiss = async (
overlay.el.remove();
- revealOverlaysToScreenReaders();
-
return true;
};
@@ -966,98 +953,4 @@ export const createTriggerController = () => {
};
};
-/**
- * The overlay that is being animated also needs to hide from screen
- * readers during its animation. This ensures that assistive technologies
- * like TalkBack do not announce or interact with the content until the
- * animation is complete, avoiding confusion for users.
- *
- * When the overlay is presented on an Android device, TalkBack's focus rings
- * may appear in the wrong position due to the transition (specifically
- * `transform` styles). This occurs because the focus rings are initially
- * displayed at the starting position of the elements before the transition
- * begins. This workaround ensures the focus rings do not appear in the
- * incorrect location.
- *
- * If this solution is applied to iOS devices, then it leads to a bug where
- * the overlays cannot be accessed by screen readers. This is due to
- * VoiceOver not being able to update the accessibility tree when the
- * `aria-hidden` is removed.
- *
- * @param overlay - The overlay that is being animated.
- */
-const hideAnimatingOverlayFromScreenReaders = (overlay: HTMLIonOverlayElement) => {
- if (doc === undefined) return;
-
- if (isPlatform('android')) {
- /**
- * Once the animation is complete, this attribute will be removed.
- * This is done at the end of the `present` method.
- */
- overlay.setAttribute('aria-hidden', 'true');
- }
-};
-
-/**
- * Ensure that underlying overlays have aria-hidden if necessary so that screen readers
- * cannot move focus to these elements. Note that we cannot rely on focus/focusin/focusout
- * events here because those events do not fire when the screen readers moves to a non-focusable
- * element such as text.
- * Without this logic screen readers would be able to move focus outside of the top focus-trapped overlay.
- *
- * @param newTopMostOverlay - The overlay that is being presented. Since the overlay has not been
- * fully presented yet at the time this function is called it will not be included in the getPresentedOverlays result.
- */
-const hideUnderlyingOverlaysFromScreenReaders = (newTopMostOverlay: HTMLIonOverlayElement) => {
- if (doc === undefined) return;
-
- const overlays = getPresentedOverlays(doc);
-
- for (let i = overlays.length - 1; i >= 0; i--) {
- const presentedOverlay = overlays[i];
- const nextPresentedOverlay = overlays[i + 1] ?? newTopMostOverlay;
-
- /**
- * If next overlay has aria-hidden then all remaining overlays will have it too.
- * Or, if the next overlay is a Toast that does not have aria-hidden then current overlay
- * should not have aria-hidden either so focus can remain in the current overlay.
- */
- if (nextPresentedOverlay.hasAttribute('aria-hidden') || nextPresentedOverlay.tagName !== 'ION-TOAST') {
- presentedOverlay.setAttribute('aria-hidden', 'true');
- }
- }
-};
-
-/**
- * When dismissing an overlay we need to reveal the new top-most overlay to screen readers.
- * If the top-most overlay is a Toast we potentially need to reveal more overlays since
- * focus is never automatically moved to the Toast.
- */
-const revealOverlaysToScreenReaders = () => {
- if (doc === undefined) return;
-
- const overlays = getPresentedOverlays(doc);
-
- for (let i = overlays.length - 1; i >= 0; i--) {
- const currentOverlay = overlays[i];
-
- /**
- * If the current we are looking at is a Toast then we can remove aria-hidden.
- * However, we potentially need to keep looking at the overlay stack because there
- * could be more Toasts underneath. Additionally, we need to unhide the closest non-Toast
- * overlay too so focus can move there since focus is never automatically moved to the Toast.
- */
- currentOverlay.removeAttribute('aria-hidden');
-
- /**
- * If we found a non-Toast element then we can just remove aria-hidden and stop searching entirely
- * since this overlay should always receive focus. As a result, all underlying overlays should still
- * be hidden from screen readers.
- */
- if (currentOverlay.tagName !== 'ION-TOAST') {
- break;
- }
- }
-};
-
export const FOCUS_TRAP_DISABLE_CLASS = 'ion-disable-focus-trap';
diff --git a/core/src/utils/test/overlays/overlays.spec.ts b/core/src/utils/test/overlays/overlays.spec.ts
deleted file mode 100644
index 29a77c3c268..00000000000
--- a/core/src/utils/test/overlays/overlays.spec.ts
+++ /dev/null
@@ -1,263 +0,0 @@
-import { newSpecPage } from '@stencil/core/testing';
-
-import { Modal } from '../../../components/modal/modal';
-import { Toast } from '../../../components/toast/toast';
-import { Nav } from '../../../components/nav/nav';
-import { RouterOutlet } from '../../../components/router-outlet/router-outlet';
-import { setRootAriaHidden } from '../../overlays';
-
-describe('setRootAriaHidden()', () => {
- it('should correctly remove and re-add router outlet from accessibility tree', async () => {
- const page = await newSpecPage({
- components: [RouterOutlet],
- html: `
-
- `,
- });
-
- const routerOutlet = page.body.querySelector('ion-router-outlet')!;
-
- expect(routerOutlet.hasAttribute('aria-hidden')).toEqual(false);
-
- setRootAriaHidden(true);
- expect(routerOutlet.hasAttribute('aria-hidden')).toEqual(true);
-
- setRootAriaHidden(false);
- expect(routerOutlet.hasAttribute('aria-hidden')).toEqual(false);
- });
-
- it('should correctly remove and re-add nav from accessibility tree', async () => {
- const page = await newSpecPage({
- components: [Nav],
- html: `
-
- `,
- });
-
- const nav = page.body.querySelector('ion-nav')!;
-
- expect(nav.hasAttribute('aria-hidden')).toEqual(false);
-
- setRootAriaHidden(true);
- expect(nav.hasAttribute('aria-hidden')).toEqual(true);
-
- setRootAriaHidden(false);
- expect(nav.hasAttribute('aria-hidden')).toEqual(false);
- });
-
- it('should correctly remove and re-add custom container from accessibility tree', async () => {
- const page = await newSpecPage({
- components: [],
- html: `
-
-
- `,
- });
-
- const containerRoot = page.body.querySelector('#ion-view-container-root')!;
- const notContainerRoot = page.body.querySelector('#not-container-root')!;
-
- expect(containerRoot.hasAttribute('aria-hidden')).toEqual(false);
- expect(notContainerRoot.hasAttribute('aria-hidden')).toEqual(false);
-
- setRootAriaHidden(true);
- expect(containerRoot.hasAttribute('aria-hidden')).toEqual(true);
- expect(notContainerRoot.hasAttribute('aria-hidden')).toEqual(false);
-
- setRootAriaHidden(false);
- expect(containerRoot.hasAttribute('aria-hidden')).toEqual(false);
- expect(notContainerRoot.hasAttribute('aria-hidden')).toEqual(false);
- });
-
- it('should not error if router outlet was not found', async () => {
- await newSpecPage({
- components: [],
- html: `
-
- `,
- });
-
- setRootAriaHidden(true);
- });
-
- it('should remove router-outlet from accessibility tree when overlay is presented', async () => {
- const page = await newSpecPage({
- components: [RouterOutlet, Modal],
- html: `
-
-
-
- `,
- });
-
- const routerOutlet = page.body.querySelector('ion-router-outlet')!;
- const modal = page.body.querySelector('ion-modal')!;
-
- await modal.present();
-
- expect(routerOutlet.hasAttribute('aria-hidden')).toEqual(true);
- });
-
- it('should add router-outlet from accessibility tree when then final overlay is dismissed', async () => {
- const page = await newSpecPage({
- components: [RouterOutlet, Modal],
- html: `
-
-
-
-
- `,
- });
-
- const routerOutlet = page.body.querySelector('ion-router-outlet')!;
- const modalOne = page.body.querySelector('ion-modal#one')!;
- const modalTwo = page.body.querySelector('ion-modal#two')!;
-
- await modalOne.present();
-
- expect(routerOutlet.hasAttribute('aria-hidden')).toEqual(true);
-
- await modalTwo.present();
-
- expect(routerOutlet.hasAttribute('aria-hidden')).toEqual(true);
-
- await modalOne.dismiss();
-
- expect(routerOutlet.hasAttribute('aria-hidden')).toEqual(true);
-
- await modalTwo.dismiss();
-
- expect(routerOutlet.hasAttribute('aria-hidden')).toEqual(false);
- });
-});
-
-describe('aria-hidden on individual overlays', () => {
- it('should hide non-topmost overlays from screen readers', async () => {
- const page = await newSpecPage({
- components: [Modal],
- html: `
-
-
- `,
- });
-
- const modalOne = page.body.querySelector('ion-modal#one')!;
- const modalTwo = page.body.querySelector('ion-modal#two')!;
-
- await modalOne.present();
- await modalTwo.present();
-
- expect(modalOne.hasAttribute('aria-hidden')).toEqual(true);
- expect(modalTwo.hasAttribute('aria-hidden')).toEqual(false);
- });
-
- it('should unhide new topmost overlay from screen readers when topmost is dismissed', async () => {
- const page = await newSpecPage({
- components: [Modal],
- html: `
-
-
- `,
- });
-
- const modalOne = page.body.querySelector('ion-modal#one')!;
- const modalTwo = page.body.querySelector('ion-modal#two')!;
-
- await modalOne.present();
- await modalTwo.present();
-
- // dismiss modalTwo so that modalOne becomes the new topmost overlay
- await modalTwo.dismiss();
-
- expect(modalOne.hasAttribute('aria-hidden')).toEqual(false);
- });
-
- it('should not keep overlays hidden from screen readers if presented after being dismissed while non-topmost', async () => {
- const page = await newSpecPage({
- components: [Modal],
- html: `
-
-
- `,
- });
-
- const modalOne = page.body.querySelector('ion-modal#one')!;
- const modalTwo = page.body.querySelector('ion-modal#two')!;
-
- await modalOne.present();
- await modalTwo.present();
-
- // modalOne is not the topmost overlay at this point and is hidden from screen readers
- await modalOne.dismiss();
-
- // modalOne will become the topmost overlay; ensure it isn't still hidden from screen readers
- await modalOne.present();
- expect(modalOne.hasAttribute('aria-hidden')).toEqual(false);
- });
-
- it('should not hide previous overlay if top-most overlay is toast', async () => {
- const page = await newSpecPage({
- components: [Modal, Toast],
- html: `
-
-
-
-
- `,
- });
-
- const modalOne = page.body.querySelector('ion-modal#m-one')!;
- const modalTwo = page.body.querySelector('ion-modal#m-two')!;
- const toastOne = page.body.querySelector('ion-toast#t-one')!;
- const toastTwo = page.body.querySelector('ion-toast#t-two')!;
-
- await modalOne.present();
- await modalTwo.present();
- await toastOne.present();
- await toastTwo.present();
-
- expect(modalOne.hasAttribute('aria-hidden')).toEqual(true);
- expect(modalTwo.hasAttribute('aria-hidden')).toEqual(false);
- expect(toastOne.hasAttribute('aria-hidden')).toEqual(false);
- expect(toastTwo.hasAttribute('aria-hidden')).toEqual(false);
-
- await toastTwo.dismiss();
-
- expect(modalOne.hasAttribute('aria-hidden')).toEqual(true);
- expect(modalTwo.hasAttribute('aria-hidden')).toEqual(false);
- expect(toastOne.hasAttribute('aria-hidden')).toEqual(false);
-
- await toastOne.dismiss();
-
- expect(modalOne.hasAttribute('aria-hidden')).toEqual(true);
- expect(modalTwo.hasAttribute('aria-hidden')).toEqual(false);
- });
-
- it('should hide previous overlay even with a toast that is not the top-most overlay', async () => {
- const page = await newSpecPage({
- components: [Modal, Toast],
- html: `
-
-
-
- `,
- });
-
- const modalOne = page.body.querySelector('ion-modal#m-one')!;
- const modalTwo = page.body.querySelector('ion-modal#m-two')!;
- const toastOne = page.body.querySelector('ion-toast#t-one')!;
-
- await modalOne.present();
- await toastOne.present();
- await modalTwo.present();
-
- expect(modalOne.hasAttribute('aria-hidden')).toEqual(true);
- expect(toastOne.hasAttribute('aria-hidden')).toEqual(true);
- expect(modalTwo.hasAttribute('aria-hidden')).toEqual(false);
-
- await modalTwo.dismiss();
-
- expect(modalOne.hasAttribute('aria-hidden')).toEqual(false);
- expect(toastOne.hasAttribute('aria-hidden')).toEqual(false);
- });
-});