Skip to content

Commit 2745248

Browse files
committed
test(overlays): add a focus trap modal to the manual test page
1 parent da37775 commit 2745248

2 files changed

Lines changed: 56 additions & 2 deletions

File tree

‎core/src/utils/focus-visible.ts‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -136,14 +136,18 @@ export const startFocusVisible = (rootEl?: HTMLElement): FocusVisibleUtility =>
136136
}
137137
};
138138

139-
ref.addEventListener('keydown', onKeydown);
139+
/*
140+
* Capture phase, so the mode is current for the overlay focus trap, which
141+
* intercepts Tab in its own capture listener.
142+
*/
143+
ref.addEventListener('keydown', onKeydown, true);
140144
ref.addEventListener('focusin', onFocusin);
141145
ref.addEventListener('focusout', onFocusout);
142146
ref.addEventListener('touchstart', pointerDown, { passive: true });
143147
ref.addEventListener('mousedown', pointerDown);
144148

145149
const destroy = () => {
146-
ref.removeEventListener('keydown', onKeydown);
150+
ref.removeEventListener('keydown', onKeydown, true);
147151
ref.removeEventListener('focusin', onFocusin);
148152
ref.removeEventListener('focusout', onFocusout);
149153
ref.removeEventListener('touchstart', pointerDown);

‎core/src/utils/test/overlays/index.html‎

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,9 @@
4545
<ion-button id="create-and-present-toast" onclick="createAndPresentToast()"
4646
>Create and Present Toast</ion-button
4747
>
48+
<ion-button id="create-and-present-focus-trap" onclick="createAndPresentFocusTrapModal()"
49+
>Create and Present Focus Trap Modal</ion-button
50+
>
4851
</ion-content>
4952
</div>
5053
</ion-app>
@@ -128,6 +131,53 @@
128131
await toast.present();
129132
};
130133

134+
/*
135+
Presents a modal for checking the focus trap's redirect by hand. An item
136+
holding a single checkbox is focusable without being clickable, so the
137+
trap resolves to the item itself and draws the indicator on it.
138+
139+
Moving focus to an element outside the modal makes the trap redirect
140+
focus back in. The indicator should only follow that redirect when the
141+
interaction was keyboard driven.
142+
*/
143+
const createAndPresentFocusTrapModal = async () => {
144+
const div = document.createElement('div');
145+
div.innerHTML = `
146+
<ion-content class="ion-padding">
147+
<ion-list>
148+
<ion-item>
149+
<ion-checkbox>Dark Mode</ion-checkbox>
150+
</ion-item>
151+
</ion-list>
152+
153+
<ion-button id="modal-move-focus">Move focus outside</ion-button>
154+
155+
<p>
156+
Pointer case: click the button above. Keyboard case: press Tab twice to
157+
reach it, then Enter. A third Tab escapes the modal, so the count
158+
matters.
159+
</p>
160+
161+
<p>
162+
Only the keyboard case should leave the indicator on the Dark Mode row.
163+
</p>
164+
</ion-content>
165+
`;
166+
167+
const moveFocusButton = div.querySelector('ion-button#modal-move-focus');
168+
moveFocusButton.onclick = () => {
169+
document.querySelector('#root-input').setFocus();
170+
};
171+
172+
const modal = await modalController.create({
173+
component: div,
174+
});
175+
176+
await modal.present();
177+
178+
return modal;
179+
};
180+
131181
const createNestedOverlayModal = async () => {
132182
const div = document.createElement('div');
133183
div.innerHTML = `

0 commit comments

Comments
 (0)