Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[react-interactions] Adds more experimental Scope API methods #17042

Merged
merged 3 commits into from
Oct 8, 2019
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 getAllScopedNodes()
const divs = divOnlyScope.current.getAllScopedNodes();

// [<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>
### getAllScopedNodes: () => 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.

### getFirstScopedNode: () => 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.getAllScopedNodes();
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.getFirstScopedNode();
if (fistElem !== null) {
fistElem.focus();
}
}
},
Expand Down
14 changes: 8 additions & 6 deletions packages/react-interactions/accessibility/src/FocusGroup.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,13 @@ 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();
event.preventDefault();
function focusGroupItem(cell: ReactScopeMethods, event?: KeyboardEvent): void {
trueadm marked this conversation as resolved.
Show resolved Hide resolved
const firstScopedNode = cell.getFirstScopedNode();
if (firstScopedNode !== null) {
firstScopedNode.focus();
if (event) {
event.preventDefault();
}
}
}

Expand Down Expand Up @@ -135,7 +137,7 @@ export function createFocusGroup(
const tabScope = getGroupProps(currentItem).tabScopeRef.current;
if (tabScope) {
const activeNode = document.activeElement;
const nodes = tabScope.getScopedNodes();
const nodes = tabScope.getAllScopedNodes();
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.getFirstScopedNode();
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.getAllScopedNodes();
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('getAllScopedNodes() 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.getAllScopedNodes();
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.getAllScopedNodes();
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> {
getAllScopedNodes(): 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;
},
getFirstScopedNode(): 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