From 8c2d2a618908cc255ee804d5055e167586b47767 Mon Sep 17 00:00:00 2001 From: Blake Vandercar Date: Mon, 9 Dec 2024 13:59:09 -0700 Subject: [PATCH 1/4] export focusable element selector --- packages/core/src/common/utils/domUtils.ts | 18 ++++++++++++++++++ packages/core/src/common/utils/index.ts | 1 + .../src/components/overlay/overlayUtils.ts | 16 +++------------- .../src/components/date-input/dateInput.tsx | 6 +----- .../src/components/date-input3/dateInput3.tsx | 6 +----- 5 files changed, 24 insertions(+), 23 deletions(-) diff --git a/packages/core/src/common/utils/domUtils.ts b/packages/core/src/common/utils/domUtils.ts index e153317f45..7aa178ba8a 100644 --- a/packages/core/src/common/utils/domUtils.ts +++ b/packages/core/src/common/utils/domUtils.ts @@ -158,3 +158,21 @@ export function clickElementOnKeyPress(keys: string[]) { } }; } + +/** + * Selector for all possible focusable items. + * + * @example `a[href]:not([tabindex="-1"])`, `[tabindex]:not([tabindex="-1"])`, etc. + * + * Derived from this SO question: + * https://stackoverflow.com/questions/1599660/which-html-elements-can-receive-focus + */ +export const SELECTOR_FOCUSABLE = [ + 'a[href]:not([tabindex="-1"])', + 'button:not([disabled]):not([tabindex="-1"])', + 'details:not([tabindex="-1"])', + 'input:not([disabled]):not([tabindex="-1"])', + 'select:not([disabled]):not([tabindex="-1"])', + 'textarea:not([disabled]):not([tabindex="-1"])', + '[tabindex]:not([tabindex="-1"])', +].join(","); diff --git a/packages/core/src/common/utils/index.ts b/packages/core/src/common/utils/index.ts index b936abd470..7e7ceb66bd 100644 --- a/packages/core/src/common/utils/index.ts +++ b/packages/core/src/common/utils/index.ts @@ -23,6 +23,7 @@ export { throttle, throttleEvent, throttleReactEventCallback, + SELECTOR_FOCUSABLE, } from "./domUtils"; export { isFunction } from "./functionUtils"; export * from "./jsUtils"; diff --git a/packages/core/src/components/overlay/overlayUtils.ts b/packages/core/src/components/overlay/overlayUtils.ts index 9d239137fa..e0ace1255a 100644 --- a/packages/core/src/components/overlay/overlayUtils.ts +++ b/packages/core/src/components/overlay/overlayUtils.ts @@ -16,6 +16,7 @@ import { OVERLAY_END_FOCUS_TRAP, OVERLAY_START_FOCUS_TRAP } from "../../common/classes"; import { getRef } from "../../common/refs"; +import { SELECTOR_FOCUSABLE } from "../../common/utils/domUtils"; /** * Returns the keyboard-focusable elements inside a given container element, ignoring focus traps @@ -27,19 +28,8 @@ export function getKeyboardFocusableElements(container: HTMLElement | React.RefO containerElement != null ? Array.from( // Order may not be correct if children elements use tabindex values > 0. - // Selectors derived from this SO question: - // https://stackoverflow.com/questions/1599660/which-html-elements-can-receive-focus - containerElement.querySelectorAll( - [ - 'a[href]:not([tabindex="-1"])', - 'button:not([disabled]):not([tabindex="-1"])', - 'details:not([tabindex="-1"])', - 'input:not([disabled]):not([tabindex="-1"])', - 'select:not([disabled]):not([tabindex="-1"])', - 'textarea:not([disabled]):not([tabindex="-1"])', - '[tabindex]:not([tabindex="-1"])', - ].join(","), - ), + + containerElement.querySelectorAll(SELECTOR_FOCUSABLE), ) : []; diff --git a/packages/datetime/src/components/date-input/dateInput.tsx b/packages/datetime/src/components/date-input/dateInput.tsx index e70616c948..f60a857b4d 100644 --- a/packages/datetime/src/components/date-input/dateInput.tsx +++ b/packages/datetime/src/components/date-input/dateInput.tsx @@ -732,11 +732,7 @@ function getKeyboardFocusableElements(popoverContentRef: React.MutableRefObject< return []; } - const elements = Array.from( - popoverContentRef.current.querySelectorAll( - "button:not([disabled]),input,[tabindex]:not([tabindex='-1'])", - ), - ); + const elements = Array.from(popoverContentRef.current.querySelectorAll(Utils.SELECTOR_FOCUSABLE)); // Remove focus boundary div elements elements.pop(); elements.shift(); diff --git a/packages/datetime2/src/components/date-input3/dateInput3.tsx b/packages/datetime2/src/components/date-input3/dateInput3.tsx index f8d1cde790..712c58dfa5 100644 --- a/packages/datetime2/src/components/date-input3/dateInput3.tsx +++ b/packages/datetime2/src/components/date-input3/dateInput3.tsx @@ -610,11 +610,7 @@ function getKeyboardFocusableElements(popoverContentRef: React.MutableRefObject< return []; } - const elements = Array.from( - popoverContentRef.current.querySelectorAll( - "button:not([disabled]),input,[tabindex]:not([tabindex='-1'])", - ), - ); + const elements = Array.from(popoverContentRef.current.querySelectorAll(Utils.SELECTOR_FOCUSABLE)); // Remove focus boundary div elements elements.pop(); elements.shift(); From 106e880a7fb3f2f31e0a8611b11a0bf8c98be694 Mon Sep 17 00:00:00 2001 From: Blake Vandercar Date: Mon, 9 Dec 2024 14:11:30 -0700 Subject: [PATCH 2/4] update jsdoc --- packages/core/src/common/utils/domUtils.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/core/src/common/utils/domUtils.ts b/packages/core/src/common/utils/domUtils.ts index 7aa178ba8a..7eb86340a6 100644 --- a/packages/core/src/common/utils/domUtils.ts +++ b/packages/core/src/common/utils/domUtils.ts @@ -162,10 +162,10 @@ export function clickElementOnKeyPress(keys: string[]) { /** * Selector for all possible focusable items. * - * @example `a[href]:not([tabindex="-1"])`, `[tabindex]:not([tabindex="-1"])`, etc. + * Derived from this SO question: {@link https://stackoverflow.com/questions/1599660/which-html-elements-can-receive-focus} * - * Derived from this SO question: - * https://stackoverflow.com/questions/1599660/which-html-elements-can-receive-focus + * @example `a[href]:not([tabindex="-1"])`, `[tabindex]:not([tabindex="-1"])`, etc. + */ export const SELECTOR_FOCUSABLE = [ 'a[href]:not([tabindex="-1"])', From 6a0a2b4de896869bcadc22fec7a1e3b29a95b6b9 Mon Sep 17 00:00:00 2001 From: Blake Vandercar Date: Mon, 9 Dec 2024 14:32:17 -0700 Subject: [PATCH 3/4] format fix --- packages/core/src/common/utils/domUtils.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/core/src/common/utils/domUtils.ts b/packages/core/src/common/utils/domUtils.ts index 7eb86340a6..02aeb9a922 100644 --- a/packages/core/src/common/utils/domUtils.ts +++ b/packages/core/src/common/utils/domUtils.ts @@ -165,7 +165,6 @@ export function clickElementOnKeyPress(keys: string[]) { * Derived from this SO question: {@link https://stackoverflow.com/questions/1599660/which-html-elements-can-receive-focus} * * @example `a[href]:not([tabindex="-1"])`, `[tabindex]:not([tabindex="-1"])`, etc. - */ export const SELECTOR_FOCUSABLE = [ 'a[href]:not([tabindex="-1"])', From 7c0402592614df7cb2070a1c4641b816b4f8f40e Mon Sep 17 00:00:00 2001 From: Blake Vandercar Date: Tue, 7 Jan 2025 09:46:15 -0700 Subject: [PATCH 4/4] refactor: export function not constant --- packages/core/src/common/utils/domUtils.ts | 18 ++++++++++++++++-- packages/core/src/common/utils/index.ts | 2 +- .../src/components/overlay/overlayUtils.ts | 10 ++++------ .../src/components/date-input/dateInput.tsx | 2 +- .../src/components/date-input3/dateInput3.tsx | 2 +- 5 files changed, 23 insertions(+), 11 deletions(-) diff --git a/packages/core/src/common/utils/domUtils.ts b/packages/core/src/common/utils/domUtils.ts index 02aeb9a922..6cdfae3113 100644 --- a/packages/core/src/common/utils/domUtils.ts +++ b/packages/core/src/common/utils/domUtils.ts @@ -164,9 +164,9 @@ export function clickElementOnKeyPress(keys: string[]) { * * Derived from this SO question: {@link https://stackoverflow.com/questions/1599660/which-html-elements-can-receive-focus} * - * @example `a[href]:not([tabindex="-1"])`, `[tabindex]:not([tabindex="-1"])`, etc. + * Note: Order may not be correct if children elements use tabindex values > 0. */ -export const SELECTOR_FOCUSABLE = [ +const SELECTOR_FOCUSABLE = [ 'a[href]:not([tabindex="-1"])', 'button:not([disabled]):not([tabindex="-1"])', 'details:not([tabindex="-1"])', @@ -175,3 +175,17 @@ export const SELECTOR_FOCUSABLE = [ 'textarea:not([disabled]):not([tabindex="-1"])', '[tabindex]:not([tabindex="-1"])', ].join(","); + +/** + * Gets all focusable elements within the given element. + * + * Selector derived from this SO question: {@link https://stackoverflow.com/questions/1599660/which-html-elements-can-receive-focus} + * + * Note: Order may not be correct if children elements use tabindex values > 0. + * + * @param {HTMLElement} element - The element to search within. + * @returns {HTMLElement[]} An array of focusable elements. + */ +export function getFocusableElements(element: HTMLElement): HTMLElement[] { + return Array.from(element.querySelectorAll(SELECTOR_FOCUSABLE)); +} diff --git a/packages/core/src/common/utils/index.ts b/packages/core/src/common/utils/index.ts index 7e7ceb66bd..52bac16aca 100644 --- a/packages/core/src/common/utils/index.ts +++ b/packages/core/src/common/utils/index.ts @@ -20,10 +20,10 @@ export { elementIsOrContains, elementIsTextInput, getActiveElement, + getFocusableElements, throttle, throttleEvent, throttleReactEventCallback, - SELECTOR_FOCUSABLE, } from "./domUtils"; export { isFunction } from "./functionUtils"; export * from "./jsUtils"; diff --git a/packages/core/src/components/overlay/overlayUtils.ts b/packages/core/src/components/overlay/overlayUtils.ts index e0ace1255a..f519119af4 100644 --- a/packages/core/src/components/overlay/overlayUtils.ts +++ b/packages/core/src/components/overlay/overlayUtils.ts @@ -16,7 +16,7 @@ import { OVERLAY_END_FOCUS_TRAP, OVERLAY_START_FOCUS_TRAP } from "../../common/classes"; import { getRef } from "../../common/refs"; -import { SELECTOR_FOCUSABLE } from "../../common/utils/domUtils"; +import { getFocusableElements } from "../../common/utils/domUtils"; /** * Returns the keyboard-focusable elements inside a given container element, ignoring focus traps @@ -24,13 +24,11 @@ import { SELECTOR_FOCUSABLE } from "../../common/utils/domUtils"; */ export function getKeyboardFocusableElements(container: HTMLElement | React.RefObject): HTMLElement[] { const containerElement = getRef(container); + const focusableElements = containerElement != null - ? Array.from( - // Order may not be correct if children elements use tabindex values > 0. - - containerElement.querySelectorAll(SELECTOR_FOCUSABLE), - ) + ? // Note: Order may not be correct if children elements use tabindex values > 0. + getFocusableElements(containerElement) : []; return focusableElements.filter( diff --git a/packages/datetime/src/components/date-input/dateInput.tsx b/packages/datetime/src/components/date-input/dateInput.tsx index f60a857b4d..bfc198d57a 100644 --- a/packages/datetime/src/components/date-input/dateInput.tsx +++ b/packages/datetime/src/components/date-input/dateInput.tsx @@ -732,7 +732,7 @@ function getKeyboardFocusableElements(popoverContentRef: React.MutableRefObject< return []; } - const elements = Array.from(popoverContentRef.current.querySelectorAll(Utils.SELECTOR_FOCUSABLE)); + const elements = Utils.getFocusableElements(popoverContentRef.current); // Remove focus boundary div elements elements.pop(); elements.shift(); diff --git a/packages/datetime2/src/components/date-input3/dateInput3.tsx b/packages/datetime2/src/components/date-input3/dateInput3.tsx index 054bfd9533..e370e7d7ab 100644 --- a/packages/datetime2/src/components/date-input3/dateInput3.tsx +++ b/packages/datetime2/src/components/date-input3/dateInput3.tsx @@ -611,7 +611,7 @@ function getKeyboardFocusableElements(popoverContentRef: React.MutableRefObject< return []; } - const elements = Array.from(popoverContentRef.current.querySelectorAll(Utils.SELECTOR_FOCUSABLE)); + const elements = Utils.getFocusableElements(popoverContentRef.current); // Remove focus boundary div elements elements.pop(); elements.shift();