diff --git a/src/date-picker/index.tsx b/src/date-picker/index.tsx index 98df59bc2c..5d810363d8 100644 --- a/src/date-picker/index.tsx +++ b/src/date-picker/index.tsx @@ -77,7 +77,7 @@ const DatePicker = React.forwardRef( const calendarDescriptionId = useUniqueId('calendar-description-'); const mergedRef = useMergeRefs(rootRef, __internalRootRef); - useFocusTracker({ rootRef, onBlur, onFocus, viewportId: expandToViewport ? dropdownId : '' }); + useFocusTracker({ rootRef, onBlur, onFocus }); const onDropdownCloseHandler = useCallback(() => setIsDropDownOpen(false), [setIsDropDownOpen]); diff --git a/src/date-range-picker/index.tsx b/src/date-range-picker/index.tsx index e4ef7426b6..e8f535a5d6 100644 --- a/src/date-range-picker/index.tsx +++ b/src/date-range-picker/index.tsx @@ -129,7 +129,7 @@ const DateRangePicker = React.forwardRef( const rootRef = useRef(null); const dropdownId = useUniqueId('date-range-picker-dropdown'); - useFocusTracker({ rootRef, onBlur, onFocus, viewportId: expandToViewport ? dropdownId : '' }); + useFocusTracker({ rootRef, onBlur, onFocus }); const [isDropDownOpen, setIsDropDownOpen] = useState(false); diff --git a/src/internal/focus-tracker.ts b/src/internal/focus-tracker.ts index b664762145..845538b67b 100644 --- a/src/internal/focus-tracker.ts +++ b/src/internal/focus-tracker.ts @@ -1,6 +1,6 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -import { containsOrEqual } from './utils/dom'; +import { nodeBelongs } from './utils/node-belongs'; interface FocusTrackerOptions { onFocusEnter: () => void; @@ -10,18 +10,16 @@ interface FocusTrackerOptions { export default class FocusTracker { private readonly onFocusLeave: () => void; private readonly onFocusEnter: () => void; - private readonly viewportId: string; private currentlyFocused = false; - constructor(private node: HTMLElement, { onFocusEnter, onFocusLeave }: FocusTrackerOptions, viewportId = '') { + constructor(private node: HTMLElement, { onFocusEnter, onFocusLeave }: FocusTrackerOptions) { this.onFocusEnter = onFocusEnter; this.onFocusLeave = onFocusLeave; - this.viewportId = viewportId; } initialize() { - this.currentlyFocused = containsOrEqual(this.node, document.activeElement as any); + this.currentlyFocused = nodeBelongs(this.node, document.activeElement); document.addEventListener('focusin', this.focusInListener); document.addEventListener('focusout', this.focusOutListener); } @@ -32,20 +30,15 @@ export default class FocusTracker { } private focusInListener = (event: FocusEvent) => { - const focusIsInside = containsOrEqual(this.node, event.target as Node); + const focusIsInside = nodeBelongs(this.node, event.target); if (!this.currentlyFocused && focusIsInside) { this.triggerFocus(); } }; private focusOutListener = (event: FocusEvent) => { - const nextFocused = event.relatedTarget as Node; - let isNextFocusedInParent = !containsOrEqual(this.node, nextFocused); - - if (this.viewportId) { - const viewport = document.getElementById(this.viewportId); - isNextFocusedInParent = isNextFocusedInParent && !containsOrEqual(viewport, nextFocused); - } + const nextFocused = event.relatedTarget; + const isNextFocusedInParent = !nodeBelongs(this.node, nextFocused); if (this.currentlyFocused && (nextFocused === null || isNextFocusedInParent)) { this.triggerBlur(); } diff --git a/src/internal/hooks/use-focus-tracker.ts b/src/internal/hooks/use-focus-tracker.ts index b82a370237..566a2a4da0 100644 --- a/src/internal/hooks/use-focus-tracker.ts +++ b/src/internal/hooks/use-focus-tracker.ts @@ -9,32 +9,27 @@ interface UseFocusTracker { onBlur?: NonCancelableEventHandler; onFocus?: NonCancelableEventHandler; rootRef: MutableRefObject; - viewportId?: string; }): void; } -export const useFocusTracker: UseFocusTracker = ({ rootRef, onBlur, onFocus, viewportId }) => { +export const useFocusTracker: UseFocusTracker = ({ rootRef, onBlur, onFocus }) => { const focusTracker = useRef(null); useEffect(() => { if (!rootRef.current) { return; } - focusTracker.current = new FocusTracker( - rootRef.current, - { - onFocusLeave: () => { - fireNonCancelableEvent(onBlur); - }, - onFocusEnter: () => { - fireNonCancelableEvent(onFocus); - }, + focusTracker.current = new FocusTracker(rootRef.current, { + onFocusLeave: () => { + fireNonCancelableEvent(onBlur); }, - viewportId - ); + onFocusEnter: () => { + fireNonCancelableEvent(onFocus); + }, + }); focusTracker.current.initialize(); return () => { focusTracker.current?.destroy(); }; - }, [rootRef, onBlur, onFocus, viewportId]); + }, [rootRef, onBlur, onFocus]); }; diff --git a/src/internal/utils/node-belongs.ts b/src/internal/utils/node-belongs.ts index f1b23b6811..ac8473c118 100644 --- a/src/internal/utils/node-belongs.ts +++ b/src/internal/utils/node-belongs.ts @@ -10,11 +10,11 @@ import { containsOrEqual, findUpUntil } from './dom'; * @param container Container node * @param target Node that is checked to be a descendant of the container */ -export function nodeBelongs(container: Node | null, target: Node): boolean { - const portal = findUpUntil( - target as HTMLElement, - node => node instanceof HTMLElement && !!node.dataset.awsuiReferrerId - ); +export function nodeBelongs(container: Node | null, target: Node | EventTarget | null): boolean { + if (!(target instanceof Node)) { + return false; + } + const portal = findUpUntil(target as HTMLElement, node => !!node.dataset.awsuiReferrerId); const referrer = portal instanceof HTMLElement ? document.getElementById(portal.dataset.awsuiReferrerId ?? '') : null; return referrer ? containsOrEqual(container, referrer) : containsOrEqual(container, target); }