Skip to content

Commit

Permalink
[react-interactions] Adds more experimental Scope API methods (#17042)
Browse files Browse the repository at this point in the history
  • Loading branch information
trueadm authored Oct 8, 2019
1 parent 5a71cbe commit b71ab61
Show file tree
Hide file tree
Showing 17 changed files with 261 additions and 37 deletions.
4 changes: 4 additions & 0 deletions packages/react-art/src/ReactARTHostConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.');
}
10 changes: 9 additions & 1 deletion packages/react-dom/src/client/ReactDOMHostConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@
* @flow
*/

import {precacheFiberNode, updateFiberProps} from './ReactDOMComponentTree';
import {
precacheFiberNode,
updateFiberProps,
getClosestInstanceFromNode,
} from './ReactDOMComponentTree';
import {
createElement,
createTextNode,
Expand Down Expand Up @@ -976,3 +980,7 @@ export function unmountFundamentalComponent(
}
}
}

export function getInstanceFromNode(node: HTMLElement): null | Object {
return getClosestInstanceFromNode(node) || null;
}
18 changes: 14 additions & 4 deletions packages/react-interactions/accessibility/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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>DIV 1</div>, <div>DIV 2</div>, <div>DIV 3</div>]
console.log(divs);
Expand Down Expand Up @@ -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<HTMLElement>
### getAllNodes: () => null | Array<HTMLElement>

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.
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.
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
12 changes: 8 additions & 4 deletions packages/react-interactions/accessibility/src/FocusContain.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
}
},
Expand Down
8 changes: 4 additions & 4 deletions packages/react-interactions/accessibility/src/FocusGroup.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
}
Expand Down Expand Up @@ -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) {
Expand Down
8 changes: 4 additions & 4 deletions packages/react-interactions/accessibility/src/FocusTable.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
Expand Down Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -58,7 +58,7 @@ describe('TabbableScope', () => {
}

ReactDOM.render(<Test />, container);
let nodes = scopeRef.current.getScopedNodes();
let nodes = scopeRef.current.getAllNodes();
expect(nodes).toEqual([
nodeRefA.current,
nodeRefB.current,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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];
}
Expand Down
4 changes: 4 additions & 0 deletions packages/react-native-renderer/src/ReactFabricHostConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.');
}
4 changes: 4 additions & 0 deletions packages/react-native-renderer/src/ReactNativeHostConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.');
}
4 changes: 4 additions & 0 deletions packages/react-noop-renderer/src/createReactNoop.js
Original file line number Diff line number Diff line change
Expand Up @@ -432,6 +432,10 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
hidden: instance.hidden,
};
},

getInstanceFromNode() {
throw new Error('Not yet implemented.');
},
};

const hostConfig = useMutation
Expand Down
64 changes: 62 additions & 2 deletions packages/react-reconciler/src/ReactFiberScope.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import type {
ReactScopeMethods,
} from 'shared/ReactTypes';

import {getPublicInstance} from './ReactFiberHostConfig';
import {getPublicInstance, getInstanceFromNode} from './ReactFiberHostConfig';

import {
HostComponent,
Expand Down Expand Up @@ -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,
Expand All @@ -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,
Expand Down Expand Up @@ -151,7 +189,7 @@ export function createScopeMethods(
const currentFiber = ((instance.fiber: any): Fiber);
return currentFiber.memoizedProps;
},
getScopedNodes(): null | Array<Object> {
getAllNodes(): null | Array<Object> {
const currentFiber = ((instance.fiber: any): Fiber);
const child = currentFiber.child;
const scopedNodes = [];
Expand All @@ -160,5 +198,27 @@ export function createScopeMethods(
}
return scopedNodes.length === 0 ? null : scopedNodes;
},
getFirstNode(): null | Object {
const currentFiber = ((instance.fiber: any): Fiber);
const child = currentFiber.child;
if (child !== null) {
return collectFirstScopedNodeFromChildren(child, fn);
}
return null;
},
containsNode(node: Object): boolean {
let fiber = getInstanceFromNode(node);
while (fiber !== null) {
if (
fiber.tag === ScopeComponent &&
fiber.type === scope &&
fiber.stateNode === instance
) {
return true;
}
fiber = fiber.return;
}
return false;
},
};
}
Loading

0 comments on commit b71ab61

Please sign in to comment.