Skip to content

refactor: rewrite wrapFocus utils in typescript #18913

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Apr 29, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions packages/react/src/components/ComposedModal/ComposedModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,9 @@ import mergeRefs from '../../tools/mergeRefs';
import cx from 'classnames';
import { toggleClass } from '../../tools/toggleClass';
import requiredIfGivenPropIsTruthy from '../../prop-types/requiredIfGivenPropIsTruthy';
import wrapFocus, {
import {
elementOrParentIsFloatingMenu,
wrapFocus,
wrapFocusWithoutSentinels,
} from '../../internal/wrapFocus';
import { usePrefix } from '../../internal/usePrefix';
Expand Down Expand Up @@ -330,11 +331,12 @@ const ComposedModal = React.forwardRef<HTMLDivElement, ComposedModalProps>(
}

function handleOnClick(evt: React.MouseEvent<HTMLDivElement>) {
const target = evt.target as Node;
const mouseDownTarget = onMouseDownTarget.current as Node;
const { target } = evt;
const mouseDownTarget = onMouseDownTarget.current;
evt.stopPropagation();
if (
!preventCloseOnClickOutside &&
target instanceof Node &&
!elementOrParentIsFloatingMenu(target, selectorsFloatingMenus) &&
innerModal.current &&
!innerModal.current.contains(target) &&
Expand All @@ -358,9 +360,7 @@ const ComposedModal = React.forwardRef<HTMLDivElement, ComposedModalProps>(
endTrapNode: endSentinelNode,
currentActiveNode,
oldActiveNode,
selectorsFloatingMenus: selectorsFloatingMenus?.filter(
Boolean
) as string[],
selectorsFloatingMenus: selectorsFloatingMenus?.filter(Boolean),
});
}
}
Expand Down
21 changes: 12 additions & 9 deletions packages/react/src/components/Modal/Modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,9 @@ import ButtonSet from '../ButtonSet';
import InlineLoading from '../InlineLoading';
import { Layer } from '../Layer';
import requiredIfGivenPropIsTruthy from '../../prop-types/requiredIfGivenPropIsTruthy';
import wrapFocus, {
import {
elementOrParentIsFloatingMenu,
wrapFocus,
wrapFocusWithoutSentinels,
} from '../../internal/wrapFocus';
import { debounce } from 'es-toolkit/compat';
Expand Down Expand Up @@ -313,15 +314,14 @@ const Modal = React.forwardRef(function Modal(

evt.stopPropagation();

if (open) {
if (open && target instanceof HTMLElement) {
if (match(evt, keys.Escape)) {
onRequestClose(evt);
}

if (
match(evt, keys.Enter) &&
shouldSubmitOnEnter &&
target instanceof Element &&
!isCloseButton(target) &&
document.activeElement !== button.current
) {
Expand All @@ -336,20 +336,19 @@ const Modal = React.forwardRef(function Modal(
) {
wrapFocusWithoutSentinels({
containerNode: innerModal.current,
currentActiveNode: evt.target,
// TODO: Delete type assertion following util rewrite.
// https://github.com/carbon-design-system/carbon/pull/18913
event: evt as any,
currentActiveNode: target,
event: evt,
});
}
}
}

function handleOnClick(evt: React.MouseEvent<HTMLDivElement>) {
const target = evt.target as Node;
const { target } = evt;
evt.stopPropagation();
if (
!preventCloseOnClickOutside &&
target instanceof Node &&
!elementOrParentIsFloatingMenu(target, selectorsFloatingMenus) &&
innerModal.current &&
!innerModal.current.contains(target)
Expand All @@ -362,7 +361,11 @@ const Modal = React.forwardRef(function Modal(
target: oldActiveNode,
relatedTarget: currentActiveNode,
}: React.FocusEvent<HTMLDivElement>) {
if (open && currentActiveNode && oldActiveNode) {
if (
open &&
oldActiveNode instanceof HTMLElement &&
currentActiveNode instanceof HTMLElement
) {
const { current: bodyNode } = innerModal;
const { current: startTrapNode } = startTrap;
const { current: endTrapNode } = endTrap;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ import { keys, matches, match } from '../../internal/keyboard';
import { usePrefix } from '../../internal/usePrefix';
import { useId } from '../../internal/useId';
import { noopFn } from '../../internal/noopFn';
import wrapFocus, { wrapFocusWithoutSentinels } from '../../internal/wrapFocus';
import { wrapFocus, wrapFocusWithoutSentinels } from '../../internal/wrapFocus';
import { useFeatureFlag } from '../FeatureFlags';
import { warning } from '../../internal/warning';
import deprecateValuesWithin from '../../prop-types/deprecateValuesWithin';
Expand Down
20 changes: 14 additions & 6 deletions packages/react/src/internal/FloatingMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
import { selectorFocusable, selectorTabbable } from './keyboard/navigation';
import { PrefixContext } from './usePrefix';
import { warning } from './warning';
import wrapFocus, { wrapFocusWithoutSentinels } from './wrapFocus';
import { wrapFocus, wrapFocusWithoutSentinels } from './wrapFocus';

export const DIRECTION_LEFT = 'left';
export const DIRECTION_TOP = 'top';
Expand Down Expand Up @@ -456,18 +456,22 @@
/**
* Blur handler used when focus trapping is enabled.
*/
const handleBlur = (event: FocusEvent<HTMLDivElement>) => {
const handleBlur = (event: React.FocusEvent<HTMLDivElement>) => {
const { target, relatedTarget } = event;

if (
menuBodyRef.current &&
startSentinelRef.current &&
endSentinelRef.current
endSentinelRef.current &&
target instanceof HTMLElement &&
relatedTarget instanceof HTMLElement
) {
wrapFocus({
bodyNode: menuBodyRef.current,
startTrapNode: startSentinelRef.current,
endTrapNode: endSentinelRef.current,
currentActiveNode: event.relatedTarget as HTMLElement,
oldActiveNode: event.target,
currentActiveNode: relatedTarget,
oldActiveNode: target,
});
}
};
Expand All @@ -476,7 +480,11 @@
* Keydown handler for focus wrapping when experimental focus trap is enabled.
*/
const handleKeyDown = (event: KeyboardEvent<HTMLDivElement>) => {
if (match(event, keys.Tab) && menuBodyRef.current) {
if (
match(event, keys.Tab) &&
menuBodyRef.current &&
event.target instanceof HTMLElement

Check warning on line 486 in packages/react/src/internal/FloatingMenu.tsx

View check run for this annotation

Codecov / codecov/patch

packages/react/src/internal/FloatingMenu.tsx#L485-L486

Added lines #L485 - L486 were not covered by tests
) {
wrapFocusWithoutSentinels({
containerNode: menuBodyRef.current,
currentActiveNode: event.target,
Expand Down
11 changes: 2 additions & 9 deletions packages/react/src/internal/__tests__/wrapFocus-test.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,11 @@
/**
* Copyright IBM Corp. 2020, 2024
* Copyright IBM Corp. 2020, 2025
*
* This source code is licensed under the Apache-2.0 license found in the
* LICENSE file in the root directory of this source tree.
*/

import wrapFocus from '../wrapFocus';

/**
* Copyright IBM Corp. 2020
*
* This source code is licensed under the Apache-2.0 license found in the
* LICENSE file in the root directory of this source tree.
*/
import { wrapFocus } from '../wrapFocus';

describe('wrapFocus', () => {
let node;
Expand Down
18 changes: 0 additions & 18 deletions packages/react/src/internal/keyboard/navigation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,24 +36,6 @@ export const getNextIndex = (
return;
};

/**
* A flag `node.compareDocumentPosition(target)` returns that indicates
* `target` is located earlier than `node` in the document or `target` contains `node`.
*/
export const DOCUMENT_POSITION_BROAD_PRECEDING =
typeof Node !== 'undefined'
? Node.DOCUMENT_POSITION_PRECEDING | Node.DOCUMENT_POSITION_CONTAINS
: 0;

/**
* A flag `node.compareDocumentPosition(target)` returns that indicates
* `target` is located later than `node` in the document or `node` contains `target`.
*/
export const DOCUMENT_POSITION_BROAD_FOLLOWING =
typeof Node !== 'undefined'
? Node.DOCUMENT_POSITION_FOLLOWING | Node.DOCUMENT_POSITION_CONTAINED_BY
: 0;

/**
* CSS selector that selects major nodes that are sequentially focusable.
*/
Expand Down
146 changes: 0 additions & 146 deletions packages/react/src/internal/wrapFocus.js

This file was deleted.

Loading
Loading