UNPKG

@hitachivantara/uikit-react-core

Version:
130 lines (129 loc) 3.89 kB
import * as React from "react"; import { jsx } from "react/jsx-runtime"; import { unstable_useEnhancedEffect } from "@mui/material/utils"; //#region src/TreeView/internals/DescendantProvider.tsx /** Credit: https://github.com/reach/reach-ui/blob/86a046f54d53b6420e392b3fa56dd991d9d4e458/packages/descendants/README.md * Modified slightly to suit our purposes. */ function binaryFindElement(array, element) { let start = 0; let end = array.length - 1; while (start <= end) { const middle = Math.floor((start + end) / 2); if (array[middle].element === element) return middle; if (array[middle].element.compareDocumentPosition(element) & Node.DOCUMENT_POSITION_PRECEDING) end = middle - 1; else start = middle + 1; } return start; } var DescendantContext = React.createContext({ level: 0 }); function usePrevious(value) { const ref = React.useRef(null); React.useEffect(() => { ref.current = value; }, [value]); return ref.current; } var noop = () => {}; /** * This hook registers our descendant by passing it into an array. We can then * search that array by to find its index when registering it in the component. * We use this for focus management, keyboard navigation, and typeahead * functionality for some components. * * The hook accepts the element node * * Our main goals with this are: * 1) maximum composability, * 2) minimal API friction * 3) SSR compatibility* * 4) concurrent safe * 5) index always up-to-date with the tree despite changes * 6) works with memoization of any component in the tree (hopefully) * * * As for SSR, the good news is that we don't actually need the index on the * server for most use-cases, as we are only using it to determine the order of * composed descendants for keyboard navigation. */ function useDescendant(descendant) { const [, forceUpdate] = React.useState(); const { registerDescendant = noop, unregisterDescendant = noop, descendants = [], parentId = null, level = 0 } = React.useContext(DescendantContext); const index = descendants.findIndex((item) => item.element === descendant.element); const previousDescendants = usePrevious(descendants); unstable_useEnhancedEffect(() => { if (descendant.element) { registerDescendant({ ...descendant, index }); return () => { unregisterDescendant(descendant.element); }; } forceUpdate({}); }, [ registerDescendant, unregisterDescendant, index, descendants.some((newDescendant, position) => { return previousDescendants?.[position] && previousDescendants[position].element !== newDescendant.element; }), descendant ]); return { parentId, index, level }; } var DescendantProvider = (props) => { const { children, id, level } = props; const [items, set] = React.useState([]); const registerDescendant = React.useCallback(({ element, ...other }) => { set((oldItems) => { if (oldItems.length === 0) return [{ ...other, element, index: 0 }]; const index = binaryFindElement(oldItems, element); let newItems; if (oldItems[index] && oldItems[index].element === element) newItems = oldItems; else { const newItem = { ...other, element, index }; newItems = oldItems.slice(); newItems.splice(index, 0, newItem); } newItems.forEach((item, position) => { item.index = position; }); return newItems; }); }, []); const unregisterDescendant = React.useCallback((element) => { set((oldItems) => oldItems.filter((item) => element !== item.element)); }, []); const value = React.useMemo(() => ({ descendants: items, registerDescendant, unregisterDescendant, parentId: id, level }), [ items, registerDescendant, unregisterDescendant, id, level ]); return /* @__PURE__ */ jsx(DescendantContext.Provider, { value, children }); }; //#endregion export { DescendantContext, DescendantProvider, useDescendant };