diff options
Diffstat (limited to 'polygerrit-ui/app/utils/focusable.ts')
-rw-r--r-- | polygerrit-ui/app/utils/focusable.ts | 67 |
1 files changed, 67 insertions, 0 deletions
diff --git a/polygerrit-ui/app/utils/focusable.ts b/polygerrit-ui/app/utils/focusable.ts new file mode 100644 index 0000000000..d5bed09c07 --- /dev/null +++ b/polygerrit-ui/app/utils/focusable.ts @@ -0,0 +1,67 @@ +/** + * @license + * Copyright 2022 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +const FOCUSABLE_QUERY = + 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'; + +/** + * Gets an ordered list of focusable elements nested within a containing + * element that may contain shadow DOMs. + * + * This goes depth-first, so that the order of elements follows the a11y tree. + */ +export function* getFocusableElements( + el: HTMLElement | SVGElement +): Generator<HTMLElement | SVGElement> { + const style = window.getComputedStyle(el); + if (style.display === 'none' || style.visibility === 'hidden') return; + if (el.matches(FOCUSABLE_QUERY)) { + yield el; + } + + let children = []; + if (el.localName === 'slot') { + children = (el as HTMLSlotElement).assignedNodes({flatten: true}); + } else { + children = [...(el.shadowRoot || el).children]; + } + + for (const node of children.filter( + node => node instanceof HTMLElement || node instanceof SVGElement + )) { + yield* getFocusableElements(node as HTMLElement | SVGElement); + } +} + +/** + * Gets an ordered list of focusable elements nested within a containing + * element that may contain shadow DOMs. + * + * This returns in reverse a11 order. + */ +export function* getFocusableElementsReverse( + el: HTMLElement | SVGElement +): Generator<HTMLElement | SVGElement> { + const style = window.getComputedStyle(el); + if (style.display === 'none' || style.visibility === 'hidden') return; + + let children = []; + if (el.localName === 'slot') { + children = (el as HTMLSlotElement).assignedNodes({flatten: true}); + } else { + children = [...(el.shadowRoot || el).children]; + } + + for (const node of children + .filter(node => node instanceof HTMLElement || node instanceof SVGElement) + .reverse()) { + yield* getFocusableElementsReverse(node as HTMLElement | SVGElement); + } + + if (el.matches(FOCUSABLE_QUERY)) { + yield el; + } +} |