Skip to content

Commit 64e3da2

Browse files
authored
Event API: Add FocusScope surface (#15487)
1 parent 3f058de commit 64e3da2

24 files changed

+500
-347
lines changed

packages/react-dom/src/events/DOMEventResponderSystem.js

Lines changed: 63 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,8 @@ const eventListeners:
8484
($Shape<PartialEventObject>) => void,
8585
> = new PossiblyWeakMap();
8686

87+
let alreadyDispatching = false;
88+
8789
let currentTimers = new Map();
8890
let currentOwner = null;
8991
let currentInstance: null | ReactEventComponentInstance = null;
@@ -322,61 +324,65 @@ const eventResponderContext: ReactResponderContext = {
322324
}
323325
}
324326
},
325-
getEventTargetsFromTarget(
326-
target: Element | Document,
327-
queryType?: Symbol | number,
328-
queryKey?: string,
329-
): Array<{
330-
node: Element,
331-
props: null | Object,
332-
}> {
333-
validateResponderContext();
334-
const eventTargetHostComponents = [];
335-
let node = getClosestInstanceFromNode(target);
336-
// We traverse up the fiber tree from the target fiber, to the
337-
// current event component fiber. Along the way, we check if
338-
// the fiber has any children that are event targets. If there
339-
// are, we query them (optionally) to ensure they match the
340-
// specified type and key. We then push the event target props
341-
// along with the associated parent host component of that event
342-
// target.
327+
getFocusableElementsInScope(): Array<HTMLElement> {
328+
const focusableElements = [];
329+
const eventComponentInstance = ((currentInstance: any): ReactEventComponentInstance);
330+
let node = ((eventComponentInstance.currentFiber: any): Fiber).child;
331+
343332
while (node !== null) {
344333
if (node.stateNode === currentInstance) {
345334
break;
346335
}
347-
let child = node.child;
348-
349-
while (child !== null) {
350-
if (
351-
child.tag === EventTargetWorkTag &&
352-
queryEventTarget(child, queryType, queryKey)
353-
) {
354-
const props = child.stateNode.props;
355-
let parent = child.return;
356-
357-
if (parent !== null) {
358-
if (parent.stateNode === currentInstance) {
359-
break;
360-
}
361-
if (parent.tag === HostComponent) {
362-
eventTargetHostComponents.push({
363-
node: parent.stateNode,
364-
props,
365-
});
366-
break;
367-
}
368-
parent = parent.return;
369-
}
370-
break;
336+
if (isFiberHostComponentFocusable(node)) {
337+
focusableElements.push(node.stateNode);
338+
} else {
339+
const child = node.child;
340+
341+
if (child !== null) {
342+
node = child;
343+
continue;
371344
}
372-
child = child.sibling;
373345
}
374-
node = node.return;
346+
const sibling = node.sibling;
347+
348+
if (sibling !== null) {
349+
node = sibling;
350+
continue;
351+
}
352+
const parent = node.return;
353+
if (parent === null) {
354+
break;
355+
}
356+
node = parent.sibling;
375357
}
376-
return eventTargetHostComponents;
358+
359+
return focusableElements;
377360
},
378361
};
379362

363+
function isFiberHostComponentFocusable(fiber: Fiber): boolean {
364+
if (fiber.tag !== HostComponent) {
365+
return false;
366+
}
367+
const {type, memoizedProps} = fiber;
368+
if (memoizedProps.tabIndex === -1 || memoizedProps.disabled) {
369+
return false;
370+
}
371+
if (memoizedProps.tabIndex === 0) {
372+
return true;
373+
}
374+
if (type === 'a' || type === 'area') {
375+
return !!memoizedProps.href;
376+
}
377+
return (
378+
type === 'button' ||
379+
type === 'textarea' ||
380+
type === 'input' ||
381+
type === 'object' ||
382+
type === 'select'
383+
);
384+
}
385+
380386
function processTimers(timers: Map<Symbol, ResponderTimer>): void {
381387
const timersArr = Array.from(timers.values());
382388
currentEventQueue = createEventQueue();
@@ -398,20 +404,6 @@ function processTimers(timers: Map<Symbol, ResponderTimer>): void {
398404
}
399405
}
400406

401-
function queryEventTarget(
402-
child: Fiber,
403-
queryType: void | Symbol | number,
404-
queryKey: void | string,
405-
): boolean {
406-
if (queryType !== undefined && child.type.type !== queryType) {
407-
return false;
408-
}
409-
if (queryKey !== undefined && child.key !== queryKey) {
410-
return false;
411-
}
412-
return true;
413-
}
414-
415407
function createResponderEvent(
416408
topLevelType: string,
417409
nativeEvent: AnyNativeEvent,
@@ -467,7 +459,7 @@ export function processEventQueue(): void {
467459
}
468460
}
469461

470-
function getTargetEventTypes(
462+
function getTargetEventTypesSet(
471463
eventTypes: Array<ReactEventResponderEventType>,
472464
): Set<DOMTopLevelEventType> {
473465
let cachedSet = targetEventTypeCached.get(eventTypes);
@@ -497,12 +489,13 @@ function getTargetEventResponderInstances(
497489
const eventComponentInstance = node.stateNode;
498490
if (currentOwner === null || currentOwner === eventComponentInstance) {
499491
const responder = eventComponentInstance.responder;
492+
const targetEventTypes = responder.targetEventTypes;
500493
// Validate the target event type exists on the responder
501-
const targetEventTypes = getTargetEventTypes(
502-
responder.targetEventTypes,
503-
);
504-
if (targetEventTypes.has(topLevelType)) {
505-
eventResponderInstances.push(eventComponentInstance);
494+
if (targetEventTypes !== undefined) {
495+
const targetEventTypesSet = getTargetEventTypesSet(targetEventTypes);
496+
if (targetEventTypesSet.has(topLevelType)) {
497+
eventResponderInstances.push(eventComponentInstance);
498+
}
506499
}
507500
}
508501
}
@@ -716,6 +709,10 @@ export function dispatchEventForResponderEventSystem(
716709
eventSystemFlags: EventSystemFlags,
717710
): void {
718711
if (enableEventAPI) {
712+
if (alreadyDispatching) {
713+
return;
714+
}
715+
alreadyDispatching = true;
719716
currentEventQueue = createEventQueue();
720717
try {
721718
traverseAndHandleEventResponderInstances(
@@ -730,6 +727,7 @@ export function dispatchEventForResponderEventSystem(
730727
currentTimers = null;
731728
currentInstance = null;
732729
currentEventQueue = null;
730+
alreadyDispatching = false;
733731
}
734732
}
735733
}

0 commit comments

Comments
 (0)