From 2e8c74612aeb57f594c3990a99ef079fe44582fc Mon Sep 17 00:00:00 2001 From: voideanvalue Date: Wed, 26 Jun 2019 20:09:24 +0100 Subject: [PATCH 1/2] Add option to includeNegativeTabIndex in ReactDOMResponderContext#getFocusableElementsInScope --- .../src/events/DOMEventResponderSystem.js | 49 +++++++++++++++---- .../DOMEventResponderSystem-test.internal.js | 46 +++++++++++++++++ packages/react-events/src/dom/FocusScope.js | 4 +- packages/shared/ReactDOMTypes.js | 4 +- 4 files changed, 91 insertions(+), 12 deletions(-) diff --git a/packages/react-dom/src/events/DOMEventResponderSystem.js b/packages/react-dom/src/events/DOMEventResponderSystem.js index a918ebc9c9f..a4fac16b2b9 100644 --- a/packages/react-dom/src/events/DOMEventResponderSystem.js +++ b/packages/react-dom/src/events/DOMEventResponderSystem.js @@ -389,14 +389,20 @@ const eventResponderContext: ReactDOMResponderContext = { } } }, - getFocusableElementsInScope(): Array { + getFocusableElementsInScope( + includeNegativeTabIndex: boolean, + ): Array { validateResponderContext(); const focusableElements = []; const eventComponentInstance = ((currentInstance: any): ReactDOMEventComponentInstance); const child = ((eventComponentInstance.currentFiber: any): Fiber).child; if (child !== null) { - collectFocusableElements(child, focusableElements); + collectFocusableElements( + child, + focusableElements, + includeNegativeTabIndex, + ); } return focusableElements; }, @@ -467,27 +473,40 @@ const eventResponderContext: ReactDOMResponderContext = { function collectFocusableElements( node: Fiber, focusableElements: Array, + includeNegativeTabIndex: boolean, ): void { if (isFiberSuspenseAndTimedOut(node)) { const fallbackChild = getSuspenseFallbackChild(node); if (fallbackChild !== null) { - collectFocusableElements(fallbackChild, focusableElements); + collectFocusableElements( + fallbackChild, + focusableElements, + includeNegativeTabIndex, + ); } } else { - if (isFiberHostComponentFocusable(node)) { + if (isFiberHostComponentFocusable(node, includeNegativeTabIndex)) { focusableElements.push(node.stateNode); } else { const child = node.child; if (child !== null) { - collectFocusableElements(child, focusableElements); + collectFocusableElements( + child, + focusableElements, + includeNegativeTabIndex, + ); } } } const sibling = node.sibling; if (sibling !== null) { - collectFocusableElements(sibling, focusableElements); + collectFocusableElements( + sibling, + focusableElements, + includeNegativeTabIndex, + ); } } @@ -506,15 +525,27 @@ function releaseOwnershipForEventComponentInstance( return false; } -function isFiberHostComponentFocusable(fiber: Fiber): boolean { +function isInteger(value: any): boolean { + return ( + typeof value === 'number' && isFinite(value) && Math.floor(value) === value + ); +} + +function isFiberHostComponentFocusable( + fiber: Fiber, + includeNegativeTabIndex: boolean, +): boolean { if (fiber.tag !== HostComponent) { return false; } const {type, memoizedProps} = fiber; - if (memoizedProps.tabIndex === -1 || memoizedProps.disabled) { + if (memoizedProps.disabled) { return false; } - if (memoizedProps.tabIndex === 0 || memoizedProps.contentEditable === true) { + if (isInteger(memoizedProps.tabIndex)) { + return includeNegativeTabIndex ? true : memoizedProps.tabIndex >= 0; + } + if (memoizedProps.contentEditable === true) { return true; } if (type === 'a' || type === 'area') { diff --git a/packages/react-dom/src/events/__tests__/DOMEventResponderSystem-test.internal.js b/packages/react-dom/src/events/__tests__/DOMEventResponderSystem-test.internal.js index 72706477808..9e74f74b48a 100644 --- a/packages/react-dom/src/events/__tests__/DOMEventResponderSystem-test.internal.js +++ b/packages/react-dom/src/events/__tests__/DOMEventResponderSystem-test.internal.js @@ -1128,4 +1128,50 @@ describe('DOMEventResponderSystem', () => { 'hook 2b', ]); }); + + it('getFocusableElementsInScope works', () => { + let includeNegativeTabIndex = false; + let focusableElementsInScope = []; + + const EventComponent = createReactEventComponent({ + targetEventTypes: ['keydown'], + onEvent(event, context) { + focusableElementsInScope = context.getFocusableElementsInScope( + includeNegativeTabIndex, + ); + }, + allowMultipleHostChildren: true, + }); + + const buttonProps = [ + {id: 'button1', ref: React.createRef(), tabIndex: 0}, + {id: 'button2', ref: React.createRef(), tabIndex: -1}, + {id: 'button3', ref: React.createRef(), tabIndex: 2}, + {id: 'button4', ref: React.createRef(), disabled: true}, + ]; + + const Test = () => ( + + {buttonProps.map((props, index) =>