@hitachivantara/uikit-react-core
Version:
UI Kit Core React components.
130 lines (129 loc) • 3.89 kB
JavaScript
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 };