Skip to content

Commit 2500d6e

Browse files
committed
fix: all event target for shadow dom
1 parent 858d6cd commit 2500d6e

49 files changed

Lines changed: 612 additions & 210 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

eslint.config.mjs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,7 @@ export default [{
250250
"rsp-rules/no-react-key": [ERROR],
251251
"rsp-rules/sort-imports": [ERROR],
252252
"rsp-rules/no-non-shadow-contains": [ERROR],
253+
"rsp-rules/safe-event-target": [ERROR],
253254
"rulesdir/imports": [ERROR],
254255
"rulesdir/useLayoutEffectRule": [ERROR],
255256
"rulesdir/pure-render": [ERROR],
@@ -430,6 +431,7 @@ export default [{
430431
"rsp-rules/act-events-test": ERROR,
431432
"rsp-rules/no-getByRole-toThrow": ERROR,
432433
"rsp-rules/no-non-shadow-contains": OFF,
434+
"rsp-rules/safe-event-target": OFF,
433435
"rulesdir/imports": OFF,
434436
"monorepo/no-internal-import": OFF,
435437
"jsdoc/require-jsdoc": OFF
@@ -470,6 +472,7 @@ export default [{
470472
rules: {
471473
"jsdoc/require-jsdoc": OFF,
472474
"jsdoc/require-description": OFF,
475+
"rsp-rules/safe-event-target": OFF,
473476
},
474477
}, {
475478
files: [

packages/@react-aria/actiongroup/src/useActionGroup.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,10 @@
1313
import {AriaActionGroupProps} from '@react-types/actiongroup';
1414
import {createFocusManager} from '@react-aria/focus';
1515
import {DOMAttributes, FocusableElement, Orientation, RefObject} from '@react-types/shared';
16-
import {filterDOMProps, nodeContains, useLayoutEffect} from '@react-aria/utils';
16+
import {filterDOMProps, getEventTarget, nodeContains, useLayoutEffect} from '@react-aria/utils';
17+
import {KeyboardEventHandler, useState} from 'react';
1718
import {ListState} from '@react-stately/list';
1819
import {useLocale} from '@react-aria/i18n';
19-
import {useState} from 'react';
2020

2121
const BUTTON_GROUP_ROLES = {
2222
'none': 'toolbar',
@@ -47,8 +47,8 @@ export function useActionGroup<T>(props: AriaActionGroupProps<T>, state: ListSta
4747
let {direction} = useLocale();
4848
let focusManager = createFocusManager(ref);
4949
let flipDirection = direction === 'rtl' && orientation === 'horizontal';
50-
let onKeyDown = (e) => {
51-
if (!nodeContains(e.currentTarget, e.target)) {
50+
let onKeyDown: KeyboardEventHandler = (e) => {
51+
if (!nodeContains(e.currentTarget, getEventTarget(e))) {
5252
return;
5353
}
5454

packages/@react-aria/autocomplete/src/useAutocomplete.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
import {AriaLabelingProps, BaseEvent, DOMProps, FocusableElement, FocusEvents, KeyboardEvents, Node, RefObject, ValueBase} from '@react-types/shared';
1414
import {AriaTextFieldProps} from '@react-aria/textfield';
1515
import {AutocompleteProps, AutocompleteState} from '@react-stately/autocomplete';
16-
import {CLEAR_FOCUS_EVENT, FOCUS_EVENT, getActiveElement, getOwnerDocument, isAndroid, isCtrlKeyPressed, isIOS, mergeProps, mergeRefs, useEffectEvent, useEvent, useId, useLabels, useLayoutEffect, useObjectRef} from '@react-aria/utils';
16+
import {CLEAR_FOCUS_EVENT, FOCUS_EVENT, getActiveElement, getEventTarget, getOwnerDocument, isAndroid, isCtrlKeyPressed, isIOS, mergeProps, mergeRefs, useEffectEvent, useEvent, useId, useLabels, useLayoutEffect, useObjectRef} from '@react-aria/utils';
1717
import {dispatchVirtualBlur, dispatchVirtualFocus, getVirtuallyFocusedElement, moveVirtualFocus} from '@react-aria/focus';
1818
import {getInteractionModality, getPointerType} from '@react-aria/interactions';
1919
// @ts-ignore
@@ -112,7 +112,7 @@ export function useAutocomplete<T>(props: AriaAutocompleteOptions<T>, state: Aut
112112
inputRef.current.focus();
113113
}
114114

115-
let target = e.target as Element | null;
115+
let target = getEventTarget(e) as Element | null;
116116
if (e.isTrusted || !target || queuedActiveDescendant.current === target.id) {
117117
return;
118118
}
@@ -225,7 +225,7 @@ export function useAutocomplete<T>(props: AriaAutocompleteOptions<T>, state: Aut
225225
let keyDownTarget = useRef<Element | null>(null);
226226
// For textfield specific keydown operations
227227
let onKeyDown = (e: BaseEvent<ReactKeyboardEvent<any>>) => {
228-
keyDownTarget.current = e.target as Element;
228+
keyDownTarget.current = getEventTarget(e) as Element;
229229
if (e.nativeEvent.isComposing) {
230230
return;
231231
}
@@ -329,7 +329,7 @@ export function useAutocomplete<T>(props: AriaAutocompleteOptions<T>, state: Aut
329329
// Dispatch simulated key up events for things like triggering links in listbox
330330
// Make sure to stop the propagation of the input keyup event so that the simulated keyup/down pair
331331
// is detected by usePress instead of the original keyup originating from the input
332-
if (e.target === keyDownTarget.current) {
332+
if (getEventTarget(e) === keyDownTarget.current) {
333333
e.stopImmediatePropagation();
334334
let focusedNodeId = queuedActiveDescendant.current;
335335
if (focusedNodeId == null) {
@@ -386,7 +386,7 @@ export function useAutocomplete<T>(props: AriaAutocompleteOptions<T>, state: Aut
386386

387387
let curFocusedNode = queuedActiveDescendant.current ? document.getElementById(queuedActiveDescendant.current) : null;
388388
if (curFocusedNode) {
389-
let target = e.target;
389+
let target = getEventTarget(e);
390390
queueMicrotask(() => {
391391
// instead of focusing the last focused node, just focus the collection instead and have the collection handle what item to focus via useSelectableCollection/Item
392392
dispatchVirtualBlur(target, collectionRef.current);

packages/@react-aria/calendar/src/useCalendarCell.ts

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
import {CalendarDate, isEqualDay, isSameDay, isToday} from '@internationalized/date';
1414
import {CalendarState, RangeCalendarState} from '@react-stately/calendar';
1515
import {DOMAttributes, RefObject} from '@react-types/shared';
16-
import {focusWithoutScrolling, getScrollParent, mergeProps, scrollIntoViewport, useDeepMemo, useDescription} from '@react-aria/utils';
16+
import {focusWithoutScrolling, getEventTarget, getScrollParent, mergeProps, scrollIntoViewport, useDeepMemo, useDescription} from '@react-aria/utils';
1717
import {getEraFormat, hookData} from './utils';
1818
import {getInteractionModality, usePress} from '@react-aria/interactions';
1919
// @ts-ignore
@@ -333,17 +333,18 @@ export function useCalendarCell(props: AriaCalendarCellProps, state: CalendarSta
333333
state.highlightDate(date);
334334
}
335335
},
336-
onPointerDown(e) {
336+
onPointerDown(e: PointerEvent) {
337337
// This is necessary on touch devices to allow dragging
338338
// outside the original pressed element.
339339
// (JSDOM does not support this)
340-
if ('releasePointerCapture' in e.target) {
341-
if ('hasPointerCapture' in e.target) {
342-
if (e.target.hasPointerCapture(e.pointerId)) {
343-
e.target.releasePointerCapture(e.pointerId);
340+
let target = getEventTarget(e);
341+
if (target instanceof HTMLElement && 'releasePointerCapture' in target) {
342+
if ('hasPointerCapture' in target) {
343+
if (target.hasPointerCapture(e.pointerId)) {
344+
target.releasePointerCapture(e.pointerId);
344345
}
345346
} else {
346-
e.target.releasePointerCapture(e.pointerId);
347+
(target as HTMLElement).releasePointerCapture(e.pointerId);
347348
}
348349
}
349350
},

packages/@react-aria/color/src/useColorWheel.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
import {AriaColorWheelProps} from '@react-types/color';
1414
import {ColorWheelState} from '@react-stately/color';
1515
import {DOMAttributes, RefObject} from '@react-types/shared';
16-
import {focusWithoutScrolling, mergeProps, useFormReset, useGlobalListeners, useLabels} from '@react-aria/utils';
16+
import {focusWithoutScrolling, getEventTarget, mergeProps, useFormReset, useGlobalListeners, useLabels} from '@react-aria/utils';
1717
import React, {ChangeEvent, InputHTMLAttributes, useCallback, useRef} from 'react';
1818
import {useKeyboard, useMove} from '@react-aria/interactions';
1919
import {useLocale} from '@react-aria/i18n';
@@ -328,7 +328,7 @@ export function useColorWheel(props: AriaColorWheelOptions, state: ColorWheelSta
328328
name,
329329
form,
330330
onChange: (e: ChangeEvent<HTMLInputElement>) => {
331-
state.setHue(parseFloat(e.target.value));
331+
state.setHue(parseFloat(getEventTarget(e).value));
332332
},
333333
style: visuallyHiddenProps.style,
334334
'aria-errormessage': props['aria-errormessage'],

packages/@react-aria/combobox/src/useComboBox.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import {AriaComboBoxProps} from '@react-types/combobox';
1616
import {ariaHideOutside} from '@react-aria/overlays';
1717
import {AriaListBoxOptions, getItemId, listData} from '@react-aria/listbox';
1818
import {BaseEvent, DOMAttributes, KeyboardDelegate, LayoutDelegate, PressEvent, RefObject, RouterOptions, ValidationResult} from '@react-types/shared';
19-
import {chain, getActiveElement, getOwnerDocument, isAppleDevice, mergeProps, nodeContains, useEvent, useFormReset, useLabels, useRouter, useUpdateEffect} from '@react-aria/utils';
19+
import {chain, getActiveElement, getEventTarget, getOwnerDocument, isAppleDevice, mergeProps, nodeContains, useEvent, useFormReset, useLabels, useRouter, useUpdateEffect} from '@react-aria/utils';
2020
import {ComboBoxState} from '@react-stately/combobox';
2121
import {dispatchVirtualFocus} from '@react-aria/focus';
2222
import {FocusEvent, InputHTMLAttributes, KeyboardEvent, TouchEvent, useEffect, useMemo, useRef} from 'react';
@@ -264,7 +264,7 @@ export function useComboBox<T>(props: AriaComboBoxOptions<T>, state: ComboBoxSta
264264
return;
265265
}
266266

267-
let rect = (e.target as Element).getBoundingClientRect();
267+
let rect = (getEventTarget(e) as Element).getBoundingClientRect();
268268
let touch = e.changedTouches[0];
269269

270270
let centerX = Math.ceil(rect.left + .5 * rect.width);

packages/@react-aria/datepicker/src/useDatePickerGroup.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import {createFocusManager, getFocusableTreeWalker} from '@react-aria/focus';
22
import {DateFieldState, DatePickerState, DateRangePickerState} from '@react-stately/datepicker';
33
import {DOMAttributes, FocusableElement, KeyboardEvent, RefObject} from '@react-types/shared';
4-
import {mergeProps, nodeContains} from '@react-aria/utils';
4+
import {getEventTarget, mergeProps, nodeContains} from '@react-aria/utils';
55
import {useLocale} from '@react-aria/i18n';
66
import {useMemo} from 'react';
77
import {usePress} from '@react-aria/interactions';
@@ -12,7 +12,7 @@ export function useDatePickerGroup(state: DatePickerState | DateRangePickerState
1212

1313
// Open the popover on alt + arrow down
1414
let onKeyDown = (e: KeyboardEvent) => {
15-
if (!nodeContains(e.currentTarget, e.target as Element)) {
15+
if (!nodeContains(e.currentTarget, getEventTarget(e) as Element)) {
1616
return;
1717
}
1818

@@ -32,7 +32,7 @@ export function useDatePickerGroup(state: DatePickerState | DateRangePickerState
3232
e.stopPropagation();
3333
if (direction === 'rtl') {
3434
if (ref.current) {
35-
let target = e.target as FocusableElement;
35+
let target = getEventTarget(e) as FocusableElement;
3636
let prev = findNextSegment(ref.current, target.getBoundingClientRect().left, -1);
3737

3838
if (prev) {
@@ -48,7 +48,7 @@ export function useDatePickerGroup(state: DatePickerState | DateRangePickerState
4848
e.stopPropagation();
4949
if (direction === 'rtl') {
5050
if (ref.current) {
51-
let target = e.target as FocusableElement;
51+
let target = getEventTarget(e) as FocusableElement;
5252
let next = findNextSegment(ref.current, target.getBoundingClientRect().left, 1);
5353

5454
if (next) {

packages/@react-aria/dnd/src/DragManager.ts

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import {announce} from '@react-aria/live-announcer';
1414
import {ariaHideOutside} from '@react-aria/overlays';
1515
import {DragEndEvent, DragItem, DropActivateEvent, DropEnterEvent, DropEvent, DropExitEvent, DropItem, DropOperation, DropTarget as DroppableCollectionTarget, FocusableElement} from '@react-types/shared';
1616
import {getDragModality, getTypes} from './utils';
17-
import {isVirtualClick, isVirtualPointerEvent, nodeContains} from '@react-aria/utils';
17+
import {getEventTarget, isVirtualClick, isVirtualPointerEvent, nodeContains} from '@react-aria/utils';
1818
import type {LocalizedStringFormatter} from '@internationalized/string';
1919
import {RefObject, useEffect, useState} from 'react';
2020

@@ -243,7 +243,7 @@ class DragSession {
243243
this.cancelEvent(e);
244244

245245
if (e.key === 'Enter') {
246-
if (e.altKey || nodeContains(this.getCurrentActivateButton(), e.target as Node)) {
246+
if (e.altKey || nodeContains(this.getCurrentActivateButton(), getEventTarget(e) as Node)) {
247247
this.activate(this.currentDropTarget, this.currentDropItem);
248248
} else {
249249
this.drop();
@@ -257,25 +257,25 @@ class DragSession {
257257

258258
onFocus(e: FocusEvent): void {
259259
let activateButton = this.getCurrentActivateButton();
260-
if (e.target === activateButton) {
260+
if (getEventTarget(e) === activateButton) {
261261
// TODO: canceling this breaks the focus ring. Revisit when we support tabbing.
262262
this.cancelEvent(e);
263263
return;
264264
}
265265

266266
// Prevent focus events, except to the original drag target.
267-
if (e.target !== this.dragTarget.element) {
267+
if (getEventTarget(e) !== this.dragTarget.element) {
268268
this.cancelEvent(e);
269269
}
270270

271271
// Ignore focus events on the window/document (JSDOM). Will be handled in onBlur, below.
272-
if (!(e.target instanceof HTMLElement) || e.target === this.dragTarget.element) {
272+
if (!(getEventTarget(e) instanceof HTMLElement) || getEventTarget(e) === this.dragTarget.element) {
273273
return;
274274
}
275275

276276
let dropTarget =
277-
this.validDropTargets.find(target => target.element === e.target as HTMLElement) ||
278-
this.validDropTargets.find(target => nodeContains(target.element, e.target as HTMLElement));
277+
this.validDropTargets.find(target => target.element === getEventTarget(e) as HTMLElement) ||
278+
this.validDropTargets.find(target => nodeContains(target.element, getEventTarget(e) as HTMLElement));
279279

280280
if (!dropTarget) {
281281
// if (e.target === activateButton) {
@@ -289,7 +289,7 @@ class DragSession {
289289
return;
290290
}
291291

292-
let item = dropItems.get(e.target as HTMLElement);
292+
let item = dropItems.get(getEventTarget(e) as HTMLElement);
293293
if (dropTarget) {
294294
this.setCurrentDropTarget(dropTarget, item);
295295
}
@@ -302,7 +302,7 @@ class DragSession {
302302
return;
303303
}
304304

305-
if (e.target !== this.dragTarget.element) {
305+
if (getEventTarget(e) !== this.dragTarget.element) {
306306
this.cancelEvent(e);
307307
}
308308

@@ -321,15 +321,15 @@ class DragSession {
321321
this.cancelEvent(e);
322322
if (isVirtualClick(e) || this.isVirtualClick) {
323323
let dropElements = dropItems.values();
324-
let item = [...dropElements].find(item => item.element === e.target as HTMLElement || nodeContains(item.activateButtonRef?.current, e.target as HTMLElement));
325-
let dropTarget = this.validDropTargets.find(target => nodeContains(target.element, e.target as HTMLElement));
324+
let item = [...dropElements].find(item => item.element === getEventTarget(e) as HTMLElement || nodeContains(item.activateButtonRef?.current, getEventTarget(e) as HTMLElement));
325+
let dropTarget = this.validDropTargets.find(target => nodeContains(target.element, getEventTarget(e) as HTMLElement));
326326
let activateButton = item?.activateButtonRef?.current ?? dropTarget?.activateButtonRef?.current;
327-
if (nodeContains(activateButton, e.target as HTMLElement) && dropTarget) {
327+
if (nodeContains(activateButton, getEventTarget(e) as HTMLElement) && dropTarget) {
328328
this.activate(dropTarget, item);
329329
return;
330330
}
331331

332-
if (e.target === this.dragTarget.element) {
332+
if (getEventTarget(e) === this.dragTarget.element) {
333333
this.cancel();
334334
return;
335335
}
@@ -350,7 +350,7 @@ class DragSession {
350350

351351
cancelEvent(e: Event): void {
352352
// Allow focusin and focusout on the drag target so focus ring works properly.
353-
if ((e.type === 'focusin' || e.type === 'focusout') && (e.target === this.dragTarget?.element || e.target === this.getCurrentActivateButton())) {
353+
if ((e.type === 'focusin' || e.type === 'focusout') && (getEventTarget(e) === this.dragTarget?.element || getEventTarget(e) === this.getCurrentActivateButton())) {
354354
return;
355355
}
356356

packages/@react-aria/grid/src/useGrid.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,12 @@
1111
*/
1212

1313
import {AriaLabelingProps, DOMAttributes, DOMProps, Key, KeyboardDelegate, RefObject} from '@react-types/shared';
14-
import {filterDOMProps, mergeProps, nodeContains, useId} from '@react-aria/utils';
14+
import {filterDOMProps, getEventTarget, mergeProps, nodeContains, useId} from '@react-aria/utils';
15+
import {FocusEventHandler, useCallback, useMemo} from 'react';
1516
import {GridCollection} from '@react-types/grid';
1617
import {GridKeyboardDelegate} from './GridKeyboardDelegate';
1718
import {gridMap} from './utils';
1819
import {GridState} from '@react-stately/grid';
19-
import {useCallback, useMemo} from 'react';
2020
import {useCollator, useLocale} from '@react-aria/i18n';
2121
import {useGridSelectionAnnouncement} from './useGridSelectionAnnouncement';
2222
import {useHasTabbableChild} from '@react-aria/focus';
@@ -133,18 +133,18 @@ export function useGrid<T>(props: GridProps, state: GridState<T, GridCollection<
133133

134134
let domProps = filterDOMProps(props, {labelable: true});
135135

136-
let onFocus = useCallback((e) => {
136+
let onFocus: FocusEventHandler = useCallback((e) => {
137137
if (manager.isFocused) {
138138
// If a focus event bubbled through a portal, reset focus state.
139-
if (!nodeContains(e.currentTarget, e.target)) {
139+
if (!nodeContains(e.currentTarget, getEventTarget(e))) {
140140
manager.setFocused(false);
141141
}
142142

143143
return;
144144
}
145145

146146
// Focus events can bubble through portals. Ignore these events.
147-
if (!nodeContains(e.currentTarget, e.target)) {
147+
if (!nodeContains(e.currentTarget, getEventTarget(e))) {
148148
return;
149149
}
150150

packages/@react-aria/grid/src/useGridCell.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@
1212

1313
import {DOMAttributes, FocusableElement, Key, RefObject} from '@react-types/shared';
1414
import {focusSafely, isFocusVisible} from '@react-aria/interactions';
15+
import {getEventTarget, getScrollParent, mergeProps, nodeContains, scrollIntoViewport} from '@react-aria/utils';
1516
import {getFocusableTreeWalker} from '@react-aria/focus';
16-
import {getScrollParent, mergeProps, nodeContains, scrollIntoViewport} from '@react-aria/utils';
1717
import {GridCollection, GridNode} from '@react-types/grid';
1818
import {gridMap} from './utils';
1919
import {GridState} from '@react-stately/grid';
@@ -109,7 +109,7 @@ export function useGridCell<T, C extends GridCollection<T>>(props: GridCellProps
109109
});
110110

111111
let onKeyDownCapture = (e: ReactKeyboardEvent) => {
112-
if (!nodeContains(e.currentTarget, e.target as Element) || state.isKeyboardNavigationDisabled || !ref.current || !document.activeElement) {
112+
if (!nodeContains(e.currentTarget, getEventTarget(e) as Element) || state.isKeyboardNavigationDisabled || !ref.current || !document.activeElement) {
113113
return;
114114
}
115115

@@ -213,7 +213,7 @@ export function useGridCell<T, C extends GridCollection<T>>(props: GridCellProps
213213
// Prevent this event from reaching cell children, e.g. menu buttons. We want arrow keys to navigate
214214
// to the cell above/below instead. We need to re-dispatch the event from a higher parent so it still
215215
// bubbles and gets handled by useSelectableCollection.
216-
if (!e.altKey && nodeContains(ref.current, e.target as Element)) {
216+
if (!e.altKey && nodeContains(ref.current, getEventTarget(e) as Element)) {
217217
e.stopPropagation();
218218
e.preventDefault();
219219
ref.current.parentElement?.dispatchEvent(
@@ -228,7 +228,7 @@ export function useGridCell<T, C extends GridCollection<T>>(props: GridCellProps
228228
// be marshalled to that element rather than focusing the cell itself.
229229
let onFocus = (e) => {
230230
keyWhenFocused.current = node.key;
231-
if (e.target !== ref.current) {
231+
if (getEventTarget(e) !== ref.current) {
232232
// useSelectableItem only handles setting the focused key when
233233
// the focused element is the gridcell itself. We also want to
234234
// set the focused key when a child element receives focus.

0 commit comments

Comments
 (0)