UNPKG

2.92 kBJavaScriptView Raw
1'use client';
2
3import * as React from 'react';
4export const CompoundComponentContext = /*#__PURE__*/React.createContext(null);
5if (process.env.NODE_ENV !== 'production') {
6 CompoundComponentContext.displayName = 'CompoundComponentContext';
7}
8/**
9 * Sorts the subitems by their position in the DOM.
10 */
11function sortSubitems(subitems) {
12 const subitemsArray = Array.from(subitems.keys()).map(key => {
13 const subitem = subitems.get(key);
14 return {
15 key,
16 subitem
17 };
18 });
19 subitemsArray.sort((a, b) => {
20 const aNode = a.subitem.ref.current;
21 const bNode = b.subitem.ref.current;
22 if (aNode === null || bNode === null || aNode === bNode) {
23 return 0;
24 }
25
26 // eslint-disable-next-line no-bitwise
27 return aNode.compareDocumentPosition(bNode) & Node.DOCUMENT_POSITION_PRECEDING ? 1 : -1;
28 });
29 return new Map(subitemsArray.map(item => [item.key, item.subitem]));
30}
31
32/**
33 * Provides a way for a component to know about its children.
34 *
35 * Child components register themselves with the `useCompoundItem` hook, passing in arbitrary metadata to the parent.
36 *
37 * This is a more powerful altervantive to `children` traversal, as child components don't have to be placed
38 * directly inside the parent component. They can be anywhere in the tree (and even rendered by other components).
39 *
40 * The downside is that this doesn't work with SSR as it relies on the useEffect hook.
41 *
42 * @ignore - internal hook.
43 */
44export function useCompoundParent() {
45 const [subitems, setSubitems] = React.useState(new Map());
46 const subitemKeys = React.useRef(new Set());
47 const deregisterItem = React.useCallback(function deregisterItem(id) {
48 subitemKeys.current.delete(id);
49 setSubitems(previousState => {
50 const newState = new Map(previousState);
51 newState.delete(id);
52 return newState;
53 });
54 }, []);
55 const registerItem = React.useCallback(function registerItem(id, item) {
56 let providedOrGeneratedId;
57 if (typeof id === 'function') {
58 providedOrGeneratedId = id(subitemKeys.current);
59 } else {
60 providedOrGeneratedId = id;
61 }
62 subitemKeys.current.add(providedOrGeneratedId);
63 setSubitems(previousState => {
64 const newState = new Map(previousState);
65 newState.set(providedOrGeneratedId, item);
66 return newState;
67 });
68 return {
69 id: providedOrGeneratedId,
70 deregister: () => deregisterItem(providedOrGeneratedId)
71 };
72 }, [deregisterItem]);
73 const sortedSubitems = React.useMemo(() => sortSubitems(subitems), [subitems]);
74 const getItemIndex = React.useCallback(function getItemIndex(id) {
75 return Array.from(sortedSubitems.keys()).indexOf(id);
76 }, [sortedSubitems]);
77 const contextValue = React.useMemo(() => ({
78 getItemIndex,
79 registerItem,
80 totalSubitemCount: subitems.size
81 }), [getItemIndex, registerItem, subitems.size]);
82 return {
83 contextValue,
84 subitems: sortedSubitems
85 };
86}
\No newline at end of file