Skip to content

Commit 218e670

Browse files
authored
fix: all event target for shadow dom (#9551)
* fix: all event target for shadow dom * fix lint * fix lint once more * fix window.event.target access * simplify and add special cases
1 parent b66f676 commit 218e670

Some content is hidden

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

54 files changed

+688
-228
lines changed

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
"rsp-rules/shadow-safe-active-element": [ERROR],
254255
"rsp-rules/faster-node-contains": [ERROR],
255256
"rulesdir/imports": [ERROR],
@@ -432,6 +433,7 @@ export default [{
432433
"rsp-rules/act-events-test": ERROR,
433434
"rsp-rules/no-getByRole-toThrow": ERROR,
434435
"rsp-rules/no-non-shadow-contains": OFF,
436+
"rsp-rules/safe-event-target": OFF,
435437
"rsp-rules/shadow-safe-active-element": OFF,
436438
"rsp-rules/faster-node-contains": OFF,
437439
"rulesdir/imports": OFF,
@@ -474,6 +476,7 @@ export default [{
474476
rules: {
475477
"jsdoc/require-jsdoc": OFF,
476478
"jsdoc/require-description": OFF,
479+
"rsp-rules/safe-event-target": OFF,
477480
},
478481
}, {
479482
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, getActiveElement, getScrollParent, mergeProps, scrollIntoViewport, useDeepMemo, useDescription} from '@react-aria/utils';
16+
import {focusWithoutScrolling, getActiveElement, 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
@@ -343,17 +343,18 @@ export function useCalendarCell(props: AriaCalendarCellProps, state: CalendarSta
343343
state.highlightDate(date);
344344
}
345345
},
346-
onPointerDown(e) {
346+
onPointerDown(e: PointerEvent) {
347347
// This is necessary on touch devices to allow dragging
348348
// outside the original pressed element.
349349
// (JSDOM does not support this)
350-
if ('releasePointerCapture' in e.target) {
351-
if ('hasPointerCapture' in e.target) {
352-
if (e.target.hasPointerCapture(e.pointerId)) {
353-
e.target.releasePointerCapture(e.pointerId);
350+
let target = getEventTarget(e);
351+
if (target instanceof HTMLElement && 'releasePointerCapture' in target) {
352+
if ('hasPointerCapture' in target) {
353+
if (target.hasPointerCapture(e.pointerId)) {
354+
target.releasePointerCapture(e.pointerId);
354355
}
355356
} else {
356-
e.target.releasePointerCapture(e.pointerId);
357+
(target as HTMLElement).releasePointerCapture(e.pointerId);
357358
}
358359
}
359360
},

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: 5 additions & 5 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) {
@@ -68,7 +68,7 @@ export function useDatePickerGroup(state: DatePickerState | DateRangePickerState
6868
return;
6969
}
7070
// Try to find the segment prior to the element that was clicked on.
71-
let target = window.event?.target as FocusableElement;
71+
let target = window.event ? getEventTarget(window.event) as FocusableElement : null;
7272
let walker = getFocusableTreeWalker(ref.current, {tabbable: true});
7373
if (target) {
7474
walker.currentNode = target;

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

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
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';
16-
import {getActiveElement, isVirtualClick, isVirtualPointerEvent, nodeContains} from '@react-aria/utils';
16+
import {getActiveElement, getEventTarget, isVirtualClick, isVirtualPointerEvent, nodeContains} from '@react-aria/utils';
1717
import {getDragModality, getTypes} from './utils';
1818
import type {LocalizedStringFormatter} from '@internationalized/string';
1919
import {RefObject, useEffect, useState} from 'react';
@@ -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,26 @@ class DragSession {
257257

258258
onFocus(e: FocusEvent): void {
259259
let activateButton = this.getCurrentActivateButton();
260-
if (e.target === activateButton) {
260+
let eventTarget = getEventTarget(e);
261+
if (eventTarget === activateButton) {
261262
// TODO: canceling this breaks the focus ring. Revisit when we support tabbing.
262263
this.cancelEvent(e);
263264
return;
264265
}
265266

266267
// Prevent focus events, except to the original drag target.
267-
if (e.target !== this.dragTarget.element) {
268+
if (eventTarget !== this.dragTarget.element) {
268269
this.cancelEvent(e);
269270
}
270271

271272
// 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) {
273+
if (!(eventTarget instanceof HTMLElement) || eventTarget === this.dragTarget.element) {
273274
return;
274275
}
275276

276277
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));
278+
this.validDropTargets.find(target => target.element === eventTarget) ||
279+
this.validDropTargets.find(target => nodeContains(target.element, eventTarget));
279280

280281
if (!dropTarget) {
281282
// if (e.target === activateButton) {
@@ -289,7 +290,7 @@ class DragSession {
289290
return;
290291
}
291292

292-
let item = dropItems.get(e.target as HTMLElement);
293+
let item = dropItems.get(eventTarget);
293294
if (dropTarget) {
294295
this.setCurrentDropTarget(dropTarget, item);
295296
}
@@ -302,7 +303,7 @@ class DragSession {
302303
return;
303304
}
304305

305-
if (e.target !== this.dragTarget.element) {
306+
if (getEventTarget(e) !== this.dragTarget.element) {
306307
this.cancelEvent(e);
307308
}
308309

@@ -321,15 +322,16 @@ class DragSession {
321322
this.cancelEvent(e);
322323
if (isVirtualClick(e) || this.isVirtualClick) {
323324
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));
325+
let eventTarget = getEventTarget(e) as HTMLElement;
326+
let item = [...dropElements].find(item => item.element === eventTarget || nodeContains(item.activateButtonRef?.current, eventTarget));
327+
let dropTarget = this.validDropTargets.find(target => nodeContains(target.element, eventTarget));
326328
let activateButton = item?.activateButtonRef?.current ?? dropTarget?.activateButtonRef?.current;
327-
if (nodeContains(activateButton, e.target as HTMLElement) && dropTarget) {
329+
if (nodeContains(activateButton, eventTarget) && dropTarget) {
328330
this.activate(dropTarget, item);
329331
return;
330332
}
331333

332-
if (e.target === this.dragTarget.element) {
334+
if (getEventTarget(e) === this.dragTarget.element) {
333335
this.cancel();
334336
return;
335337
}
@@ -350,7 +352,8 @@ class DragSession {
350352

351353
cancelEvent(e: Event): void {
352354
// 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())) {
355+
let eventTarget = getEventTarget(e);
356+
if ((e.type === 'focusin' || e.type === 'focusout') && (eventTarget === this.dragTarget?.element || eventTarget === this.getCurrentActivateButton())) {
354357
return;
355358
}
356359

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

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,10 @@ import {DragEndEvent, DragItem, DragMoveEvent, DragPreviewRenderer, DragStartEve
1515
import {DragEvent, HTMLAttributes, version as ReactVersion, useEffect, useRef, useState} from 'react';
1616
import * as DragManager from './DragManager';
1717
import {DROP_EFFECT_TO_DROP_OPERATION, DROP_OPERATION, EFFECT_ALLOWED} from './constants';
18+
import {getEventTarget, isVirtualClick, isVirtualPointerEvent, useDescription, useGlobalListeners} from '@react-aria/utils';
1819
import {globalDropEffect, setGlobalAllowedDropOperations, setGlobalDropEffect, useDragModality, writeToDataTransfer} from './utils';
1920
// @ts-ignore
2021
import intlMessages from '../intl/*.json';
21-
import {isVirtualClick, isVirtualPointerEvent, useDescription, useGlobalListeners} from '@react-aria/utils';
2222
import {useLocalizedStringFormatter} from '@react-aria/i18n';
2323

2424
export interface DragOptions {
@@ -102,7 +102,7 @@ export function useDrag(options: DragOptions): DragResult {
102102
// If this drag was initiated by a mobile screen reader (e.g. VoiceOver or TalkBack), enter virtual dragging mode.
103103
if (modalityOnPointerDown.current === 'virtual') {
104104
e.preventDefault();
105-
startDragging(e.target as HTMLElement);
105+
startDragging(getEventTarget(e) as HTMLElement);
106106
modalityOnPointerDown.current = null;
107107
return;
108108
}
@@ -188,9 +188,9 @@ export function useDrag(options: DragOptions): DragResult {
188188

189189
// Wait a frame before we set dragging to true so that the browser has time to
190190
// render the preview image before we update the element that has been dragged.
191-
let target = e.target;
191+
let target = getEventTarget(e);
192192
requestAnimationFrame(() => {
193-
setDragging(target as Element);
193+
setDragging(target);
194194
});
195195
};
196196

@@ -340,24 +340,24 @@ export function useDrag(options: DragOptions): DragResult {
340340
}
341341
},
342342
onKeyDownCapture(e) {
343-
if (e.target === e.currentTarget && e.key === 'Enter') {
343+
if (getEventTarget(e) === e.currentTarget && e.key === 'Enter') {
344344
e.preventDefault();
345345
e.stopPropagation();
346346
}
347347
},
348348
onKeyUpCapture(e) {
349-
if (e.target === e.currentTarget && e.key === 'Enter') {
349+
if (getEventTarget(e) === e.currentTarget && e.key === 'Enter') {
350350
e.preventDefault();
351351
e.stopPropagation();
352-
startDragging(e.target as HTMLElement);
352+
startDragging(getEventTarget(e));
353353
}
354354
},
355355
onClick(e) {
356356
// Handle NVDA/JAWS in browse mode, and touch screen readers. In this case, no keyboard events are fired.
357357
if (isVirtualClick(e.nativeEvent) || modalityOnPointerDown.current === 'virtual') {
358358
e.preventDefault();
359359
e.stopPropagation();
360-
startDragging(e.target as HTMLElement);
360+
startDragging(getEventTarget(e));
361361
}
362362
}
363363
};

0 commit comments

Comments
 (0)