mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2026-03-13 10:22:08 +08:00
fix(inputs): keyboard focus improvements (#16838)
fixes #16815 fixes #16872 fixes #13978 fixes #16610
This commit is contained in:
46
core/src/utils/focus-visible.ts
Normal file
46
core/src/utils/focus-visible.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
|
||||
const ION_FOCUSED = 'ion-focused';
|
||||
const ION_FOCUSABLE = 'ion-focusable';
|
||||
const FOCUS_KEYS = ['Tab', 'ArrowDown', 'Space', 'Escape', ' ', 'Shift', 'Enter', 'ArrowLeft', 'ArrowRight', 'ArrowUp'];
|
||||
|
||||
export function startFocusVisible(doc: Document) {
|
||||
|
||||
let currentFocus: Element[] = [];
|
||||
let keyboardMode = true;
|
||||
|
||||
function setFocus(elements: Element[]) {
|
||||
currentFocus.forEach(el => el.classList.remove(ION_FOCUSED));
|
||||
elements.forEach(el => el.classList.add(ION_FOCUSED));
|
||||
currentFocus = elements;
|
||||
}
|
||||
|
||||
doc.addEventListener('keydown', ev => {
|
||||
keyboardMode = FOCUS_KEYS.includes(ev.key);
|
||||
if (!keyboardMode) {
|
||||
setFocus([]);
|
||||
}
|
||||
});
|
||||
|
||||
const pointerDown = () => {
|
||||
keyboardMode = false;
|
||||
setFocus([]);
|
||||
};
|
||||
doc.addEventListener('focusin', ev => {
|
||||
if (keyboardMode && ev.composedPath) {
|
||||
const toFocus = ev.composedPath().filter((el: any) => {
|
||||
if (el.classList) {
|
||||
return el.classList.contains(ION_FOCUSABLE);
|
||||
}
|
||||
return false;
|
||||
}) as Element[];
|
||||
setFocus(toFocus);
|
||||
}
|
||||
});
|
||||
doc.addEventListener('focusout', () => {
|
||||
if (doc.activeElement === doc.body) {
|
||||
setFocus([]);
|
||||
}
|
||||
});
|
||||
doc.addEventListener('touchstart', pointerDown);
|
||||
doc.addEventListener('mousedown', pointerDown);
|
||||
}
|
||||
@@ -25,6 +25,18 @@ export function createOverlay<T extends HTMLIonOverlayElement>(element: T, opts:
|
||||
export function connectListeners(doc: Document) {
|
||||
if (lastId === 0) {
|
||||
lastId = 1;
|
||||
// trap focus inside overlays
|
||||
doc.addEventListener('focusin', ev => {
|
||||
const lastOverlay = getOverlay(doc);
|
||||
if (lastOverlay && lastOverlay.backdropDismiss && !isDescendant(lastOverlay, ev.target as HTMLElement)) {
|
||||
const firstInput = lastOverlay.querySelector('input,button') as HTMLElement | null;
|
||||
if (firstInput) {
|
||||
firstInput.focus();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// handle back-button click
|
||||
doc.addEventListener('ionBackButton', ev => {
|
||||
const lastOverlay = getOverlay(doc);
|
||||
if (lastOverlay && lastOverlay.backdropDismiss) {
|
||||
@@ -34,6 +46,7 @@ export function connectListeners(doc: Document) {
|
||||
}
|
||||
});
|
||||
|
||||
// handle ESC to close overlay
|
||||
doc.addEventListener('keyup', ev => {
|
||||
if (ev.key === 'Escape') {
|
||||
const lastOverlay = getOverlay(doc);
|
||||
@@ -195,4 +208,14 @@ export function isCancel(role: string | undefined): boolean {
|
||||
return role === 'cancel' || role === BACKDROP;
|
||||
}
|
||||
|
||||
function isDescendant(parent: HTMLElement, child: HTMLElement | null) {
|
||||
while (child) {
|
||||
if (child === parent) {
|
||||
return true;
|
||||
}
|
||||
child = child.parentElement;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export const BACKDROP = 'backdrop';
|
||||
|
||||
@@ -19,6 +19,7 @@ export function startTapClick(doc: Document, config: Config) {
|
||||
if (cancelled || scrolling) {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
cancelled = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,6 +49,7 @@ export function startTapClick(doc: Document, config: Config) {
|
||||
}
|
||||
|
||||
function cancelActive() {
|
||||
console.log('cancelActive()');
|
||||
clearTimeout(activeDefer);
|
||||
activeDefer = undefined;
|
||||
if (activatableEle) {
|
||||
|
||||
Reference in New Issue
Block a user