diff --git a/packages/react-art/src/ReactARTHostConfig.js b/packages/react-art/src/ReactARTHostConfig.js
index 247086c64ba2d..4a525677bd214 100644
--- a/packages/react-art/src/ReactARTHostConfig.js
+++ b/packages/react-art/src/ReactARTHostConfig.js
@@ -461,3 +461,7 @@ export function updateFundamentalComponent(fundamentalInstance) {
export function unmountFundamentalComponent(fundamentalInstance) {
throw new Error('Not yet implemented.');
}
+
+export function getInstanceFromNode(node) {
+ throw new Error('Not yet implemented.');
+}
diff --git a/packages/react-dom/src/client/ReactDOMHostConfig.js b/packages/react-dom/src/client/ReactDOMHostConfig.js
index c9425be84026c..2e9224817acb9 100644
--- a/packages/react-dom/src/client/ReactDOMHostConfig.js
+++ b/packages/react-dom/src/client/ReactDOMHostConfig.js
@@ -7,7 +7,11 @@
* @flow
*/
-import {precacheFiberNode, updateFiberProps} from './ReactDOMComponentTree';
+import {
+ precacheFiberNode,
+ updateFiberProps,
+ getClosestInstanceFromNode,
+} from './ReactDOMComponentTree';
import {
createElement,
createTextNode,
@@ -976,3 +980,7 @@ export function unmountFundamentalComponent(
}
}
}
+
+export function getInstanceFromNode(node: HTMLElement): null | Object {
+ return getClosestInstanceFromNode(node) || null;
+}
diff --git a/packages/react-interactions/accessibility/README.md b/packages/react-interactions/accessibility/README.md
index 9e910b18778f3..582820eec122a 100644
--- a/packages/react-interactions/accessibility/README.md
+++ b/packages/react-interactions/accessibility/README.md
@@ -42,8 +42,8 @@ function MyComponent(props) {
);
}
-// Using the ref, we can get the host nodes via getScopedNodes()
-const divs = divOnlyScope.current.getScopedNodes();
+// Using the ref, we can get the host nodes via getAllNodes()
+const divs = divOnlyScope.current.getAllNodes();
// [
DIV 1
, DIV 2
, DIV 3
]
console.log(divs);
@@ -72,7 +72,17 @@ Returns the parent `ReactScopeInterface` of the scope node or `null` if none exi
Returns the current `props` object of the scope node.
-### getScopedNodes: () => null | Array
+### getAllNodes: () => null | Array
Returns an array of all child host nodes that successfully match when queried using the
-query function passed to the scope. Returns `null` if there are no matching host nodes.
\ No newline at end of file
+query function passed to the scope. Returns `null` if there are no matching host nodes.
+
+### getFirstNode: () => null | HTMLElement
+
+Returns the first child host node that successfully matches when queried using the
+query function passed to the scope. Returns `null` if there is no matching host node.
+
+### containsNode: (node: HTMLElement) => boolean
+
+Returns `true` or `false` depending on if the given `HTMLElement` is a descendant
+of the scope's sub-tree.
\ No newline at end of file
diff --git a/packages/react-interactions/accessibility/docs/TabbableScope.md b/packages/react-interactions/accessibility/docs/TabbableScope.md
index ff2209f6b5849..2bafcad00db21 100644
--- a/packages/react-interactions/accessibility/docs/TabbableScope.md
+++ b/packages/react-interactions/accessibility/docs/TabbableScope.md
@@ -15,7 +15,7 @@ function FocusableNodeCollector(props) {
const scope = scopeRef.current;
if (scope) {
- const tabFocusableNodes = scope.getScopedNodes();
+ const tabFocusableNodes = scope.getAllNodes();
if (tabFocusableNodes && props.onFocusableNodes) {
props.onFocusableNodes(tabFocusableNodes);
}
diff --git a/packages/react-interactions/accessibility/src/FocusContain.js b/packages/react-interactions/accessibility/src/FocusContain.js
index 100e747a57d38..60bd3cd2929fc 100644
--- a/packages/react-interactions/accessibility/src/FocusContain.js
+++ b/packages/react-interactions/accessibility/src/FocusContain.js
@@ -66,10 +66,14 @@ export default function FocusContain({
useLayoutEffect(
() => {
const scope = scopeRef.current;
- if (scope !== null && disabled !== true) {
- const elems = scope.getScopedNodes();
- if (elems && elems.indexOf(document.activeElement) === -1) {
- elems[0].focus();
+ if (
+ scope !== null &&
+ disabled !== true &&
+ !scope.containsNode(document.activeElement)
+ ) {
+ const fistElem = scope.getFirstNode();
+ if (fistElem !== null) {
+ fistElem.focus();
}
}
},
diff --git a/packages/react-interactions/accessibility/src/FocusGroup.js b/packages/react-interactions/accessibility/src/FocusGroup.js
index 898ee8a89eb07..c3d8e4004edf9 100644
--- a/packages/react-interactions/accessibility/src/FocusGroup.js
+++ b/packages/react-interactions/accessibility/src/FocusGroup.js
@@ -30,9 +30,9 @@ type FocusGroupProps = {|
const {useRef} = React;
function focusGroupItem(cell: ReactScopeMethods, event: KeyboardEvent): void {
- const tabbableNodes = cell.getScopedNodes();
- if (tabbableNodes !== null && tabbableNodes.length > 0) {
- tabbableNodes[0].focus();
+ const firstScopedNode = cell.getFirstNode();
+ if (firstScopedNode !== null) {
+ firstScopedNode.focus();
event.preventDefault();
}
}
@@ -135,7 +135,7 @@ export function createFocusGroup(
const tabScope = getGroupProps(currentItem).tabScopeRef.current;
if (tabScope) {
const activeNode = document.activeElement;
- const nodes = tabScope.getScopedNodes();
+ const nodes = tabScope.getAllNodes();
for (let i = 0; i < nodes.length; i++) {
const node = nodes[i];
if (node !== activeNode) {
diff --git a/packages/react-interactions/accessibility/src/FocusTable.js b/packages/react-interactions/accessibility/src/FocusTable.js
index ebe12652836bc..0f5970c1eb4fa 100644
--- a/packages/react-interactions/accessibility/src/FocusTable.js
+++ b/packages/react-interactions/accessibility/src/FocusTable.js
@@ -39,9 +39,9 @@ type FocusTableProps = {|
const {useRef} = React;
function focusScope(cell: ReactScopeMethods, event?: KeyboardEvent): void {
- const tabbableNodes = cell.getScopedNodes();
- if (tabbableNodes !== null && tabbableNodes.length > 0) {
- tabbableNodes[0].focus();
+ const firstScopedNode = cell.getFirstNode();
+ if (firstScopedNode !== null) {
+ firstScopedNode.focus();
if (event) {
event.preventDefault();
}
@@ -209,7 +209,7 @@ export function createFocusTable(
const tabScope = getTableProps(currentCell).tabScopeRef.current;
if (tabScope) {
const activeNode = document.activeElement;
- const nodes = tabScope.getScopedNodes();
+ const nodes = tabScope.getAllNodes();
for (let i = 0; i < nodes.length; i++) {
const node = nodes[i];
if (node !== activeNode) {
diff --git a/packages/react-interactions/accessibility/src/__tests__/TabbableScope-test.internal.js b/packages/react-interactions/accessibility/src/__tests__/TabbableScope-test.internal.js
index 3815a35209ab7..6926fa48ad4dc 100644
--- a/packages/react-interactions/accessibility/src/__tests__/TabbableScope-test.internal.js
+++ b/packages/react-interactions/accessibility/src/__tests__/TabbableScope-test.internal.js
@@ -35,7 +35,7 @@ describe('TabbableScope', () => {
container = null;
});
- it('getScopedNodes() works as intended', () => {
+ it('getAllNodes() works as intended', () => {
const scopeRef = React.createRef();
const nodeRefA = React.createRef();
const nodeRefB = React.createRef();
@@ -58,7 +58,7 @@ describe('TabbableScope', () => {
}
ReactDOM.render(, container);
- let nodes = scopeRef.current.getScopedNodes();
+ let nodes = scopeRef.current.getAllNodes();
expect(nodes).toEqual([
nodeRefA.current,
nodeRefB.current,
diff --git a/packages/react-interactions/accessibility/src/shared/getTabbableNodes.js b/packages/react-interactions/accessibility/src/shared/getTabbableNodes.js
index 1ee49390f2163..dd847c117d4b1 100644
--- a/packages/react-interactions/accessibility/src/shared/getTabbableNodes.js
+++ b/packages/react-interactions/accessibility/src/shared/getTabbableNodes.js
@@ -18,7 +18,7 @@ export default function getTabbableNodes(
number,
null | HTMLElement,
] {
- const tabbableNodes = scope.getScopedNodes();
+ const tabbableNodes = scope.getAllNodes();
if (tabbableNodes === null || tabbableNodes.length === 0) {
return [null, null, null, 0, null];
}
diff --git a/packages/react-native-renderer/src/ReactFabricHostConfig.js b/packages/react-native-renderer/src/ReactFabricHostConfig.js
index 4a66a4a216122..4b71f19f5557c 100644
--- a/packages/react-native-renderer/src/ReactFabricHostConfig.js
+++ b/packages/react-native-renderer/src/ReactFabricHostConfig.js
@@ -491,3 +491,7 @@ export function unmountFundamentalComponent(fundamentalInstance) {
export function cloneFundamentalInstance(fundamentalInstance) {
throw new Error('Not yet implemented.');
}
+
+export function getInstanceFromNode(node) {
+ throw new Error('Not yet implemented.');
+}
diff --git a/packages/react-native-renderer/src/ReactNativeHostConfig.js b/packages/react-native-renderer/src/ReactNativeHostConfig.js
index dd9e7e0e08a1c..4714fb3dd1b98 100644
--- a/packages/react-native-renderer/src/ReactNativeHostConfig.js
+++ b/packages/react-native-renderer/src/ReactNativeHostConfig.js
@@ -530,3 +530,7 @@ export function updateFundamentalComponent(fundamentalInstance) {
export function unmountFundamentalComponent(fundamentalInstance) {
throw new Error('Not yet implemented.');
}
+
+export function getInstanceFromNode(node) {
+ throw new Error('Not yet implemented.');
+}
diff --git a/packages/react-noop-renderer/src/createReactNoop.js b/packages/react-noop-renderer/src/createReactNoop.js
index a5040ee8d655a..9e3a2342bd152 100644
--- a/packages/react-noop-renderer/src/createReactNoop.js
+++ b/packages/react-noop-renderer/src/createReactNoop.js
@@ -432,6 +432,10 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
hidden: instance.hidden,
};
},
+
+ getInstanceFromNode() {
+ throw new Error('Not yet implemented.');
+ },
};
const hostConfig = useMutation
diff --git a/packages/react-reconciler/src/ReactFiberScope.js b/packages/react-reconciler/src/ReactFiberScope.js
index 54d6bff3d862a..fa8e849a3e323 100644
--- a/packages/react-reconciler/src/ReactFiberScope.js
+++ b/packages/react-reconciler/src/ReactFiberScope.js
@@ -14,7 +14,7 @@ import type {
ReactScopeMethods,
} from 'shared/ReactTypes';
-import {getPublicInstance} from './ReactFiberHostConfig';
+import {getPublicInstance, getInstanceFromNode} from './ReactFiberHostConfig';
import {
HostComponent,
@@ -54,6 +54,29 @@ function collectScopedNodes(
}
}
+function collectFirstScopedNode(
+ node: Fiber,
+ fn: (type: string | Object, props: Object) => boolean,
+): null | Object {
+ if (enableScopeAPI) {
+ if (node.tag === HostComponent) {
+ const {type, memoizedProps} = node;
+ if (fn(type, memoizedProps) === true) {
+ return getPublicInstance(node.stateNode);
+ }
+ }
+ let child = node.child;
+
+ if (isFiberSuspenseAndTimedOut(node)) {
+ child = getSuspenseFallbackChild(node);
+ }
+ if (child !== null) {
+ return collectFirstScopedNodeFromChildren(child, fn);
+ }
+ }
+ return null;
+}
+
function collectScopedNodesFromChildren(
startingChild: Fiber,
fn: (type: string | Object, props: Object) => boolean,
@@ -66,6 +89,21 @@ function collectScopedNodesFromChildren(
}
}
+function collectFirstScopedNodeFromChildren(
+ startingChild: Fiber,
+ fn: (type: string | Object, props: Object) => boolean,
+): Object | null {
+ let child = startingChild;
+ while (child !== null) {
+ const scopedNode = collectFirstScopedNode(child, fn);
+ if (scopedNode !== null) {
+ return scopedNode;
+ }
+ child = child.sibling;
+ }
+ return null;
+}
+
function collectNearestScopeMethods(
node: Fiber,
scope: ReactScope,
@@ -151,7 +189,7 @@ export function createScopeMethods(
const currentFiber = ((instance.fiber: any): Fiber);
return currentFiber.memoizedProps;
},
- getScopedNodes(): null | Array