Skip to content

Commit d03d579

Browse files
committed
fix window.event.target access
1 parent 972ee84 commit d03d579

4 files changed

Lines changed: 56 additions & 7 deletions

File tree

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -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/form/src/useFormValidation.ts

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

1313
import {FormValidationState} from '@react-stately/form';
14+
import {getEventTarget, useEffectEvent, useLayoutEffect} from '@react-aria/utils';
1415
import {RefObject, Validation, ValidationResult} from '@react-types/shared';
1516
import {setInteractionModality} from '@react-aria/interactions';
1617
import {useEffect, useRef} from 'react';
17-
import {useEffectEvent, useLayoutEffect} from '@react-aria/utils';
1818

1919
type ValidatableElement = HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement;
2020

@@ -94,7 +94,7 @@ export function useFormValidation<T>(props: FormValidationProps<T>, state: FormV
9494
// This is best-effort. There may be false positives, e.g. setTimeout.
9595
form.reset = () => {
9696
// React uses MessageChannel for scheduling, so ignore 'message' events.
97-
isIgnoredReset.current = !window.event || (window.event.type === 'message' && window.event.target instanceof MessagePort);
97+
isIgnoredReset.current = !window.event || (window.event.type === 'message' && getEventTarget(window.event) instanceof MessagePort);
9898
reset?.call(form);
9999
isIgnoredReset.current = false;
100100
};

packages/dev/eslint-plugin-rsp-rules/rules/safe-event-target.js

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -107,14 +107,34 @@ const plugin = {
107107
// Only match common event parameter names
108108
const commonEventNames = /^(e|event|evt)$/i;
109109
let isEventTarget = false;
110+
let eventExpression = null;
110111

111112
if (node.object.type === 'Identifier') {
112113
// Check if the identifier matches common event names (e.target, event.target, evt.target)
113114
isEventTarget = commonEventNames.test(node.object.name);
115+
if (isEventTarget) {
116+
eventExpression = node.object;
117+
}
118+
} else if (node.object.type === 'MemberExpression' || node.object.type === 'ChainExpression') {
119+
// Handle *.event.target or *.event?.target patterns (e.g., window.event, getOwnerWindow(foo).event)
120+
let objectToCheck = node.object;
121+
122+
// Unwrap ChainExpression to get the MemberExpression
123+
if (objectToCheck.type === 'ChainExpression') {
124+
objectToCheck = objectToCheck.expression;
125+
}
126+
127+
// Check for *.event pattern (any expression followed by .event)
128+
if (objectToCheck.type === 'MemberExpression' &&
129+
objectToCheck.property.type === 'Identifier' &&
130+
objectToCheck.property.name === 'event') {
131+
isEventTarget = true;
132+
eventExpression = node.object;
133+
}
114134
}
115135

116136
// Skip if this doesn't look like an event target access
117-
if (!isEventTarget) {
137+
if (!isEventTarget || !eventExpression) {
118138
return;
119139
}
120140

@@ -159,8 +179,13 @@ const plugin = {
159179
const fixes = [];
160180
const sourceCode = context.sourceCode;
161181

162-
// Get the event object (e.g., 'event' from 'event.target')
163-
const eventText = sourceCode.getText(node.object);
182+
// Get the event expression text
183+
let eventText = sourceCode.getText(eventExpression);
184+
185+
// If it's a ChainExpression (window.event?), unwrap it
186+
if (eventExpression.type === 'ChainExpression') {
187+
eventText = sourceCode.getText(eventExpression.expression);
188+
}
164189

165190
// Replace event.target with getEventTarget(event)
166191
fixes.push(fixer.replaceText(node, `${getEventTargetLocalName}(${eventText})`));

packages/dev/eslint-plugin-rsp-rules/test/safe-event-target.test-lint.js

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import safeEventTargetRule from '../rules/safe-event-target.js';
1515

1616
const ruleTester = new RuleTester({
1717
languageOptions: {
18-
ecmaVersion: 2015,
18+
ecmaVersion: 2020,
1919
sourceType: 'module'
2020
}
2121
});
@@ -167,6 +167,30 @@ getEventTarget(event.nativeEvent)`,
167167
output: `import {getEventTarget} from '@react-aria/utils';
168168
getEventTarget(event)`,
169169
errors: 1
170+
},
171+
{
172+
code: 'window.event.target;',
173+
output: `import {getEventTarget} from '@react-aria/utils';
174+
getEventTarget(window.event);`,
175+
errors: 1
176+
},
177+
{
178+
code: 'window.event?.target;',
179+
output: `import {getEventTarget} from '@react-aria/utils';
180+
getEventTarget(window.event);`,
181+
errors: 1
182+
},
183+
{
184+
code: 'getOwnerWindow(foo).event.target;',
185+
output: `import {getEventTarget} from '@react-aria/utils';
186+
getEventTarget(getOwnerWindow(foo).event);`,
187+
errors: 1
188+
},
189+
{
190+
code: 'getOwnerWindow(foo).event?.target;',
191+
output: `import {getEventTarget} from '@react-aria/utils';
192+
getEventTarget(getOwnerWindow(foo).event);`,
193+
errors: 1
170194
}
171195
]
172196
}

0 commit comments

Comments
 (0)