2024-06-21 19:49:13 +03:00

199 lines
7.4 KiB
JavaScript

import _objectWithoutProperties from "@babel/runtime/helpers/esm/objectWithoutProperties";
import _extends from "@babel/runtime/helpers/esm/extends";
import _slicedToArray from "@babel/runtime/helpers/esm/slicedToArray";
var _excluded = ["element"];
import * as React from 'react';
import PropTypes from 'prop-types';
import useEnhancedEffect from '@mui/utils/useEnhancedEffect';
/** Credit: https://github.com/reach/reach-ui/blob/86a046f54d53b6420e392b3fa56dd991d9d4e458/packages/descendants/README.md
* Modified slightly to suit our purposes.
*/
// To replace with .findIndex() once we stop IE11 support.
import { jsx as _jsx } from "react/jsx-runtime";
function findIndex(array, comp) {
for (var i = 0; i < array.length; i += 1) {
if (comp(array[i])) {
return i;
}
}
return -1;
}
function binaryFindElement(array, element) {
var start = 0;
var end = array.length - 1;
while (start <= end) {
var middle = Math.floor((start + end) / 2);
if (array[middle].element === element) {
return middle;
}
// eslint-disable-next-line no-bitwise
if (array[middle].element.compareDocumentPosition(element) & Node.DOCUMENT_POSITION_PRECEDING) {
end = middle - 1;
} else {
start = middle + 1;
}
}
return start;
}
var DescendantContext = /*#__PURE__*/React.createContext({});
if (process.env.NODE_ENV !== 'production') {
DescendantContext.displayName = 'DescendantContext';
}
function usePrevious(value) {
var ref = React.useRef(null);
React.useEffect(function () {
ref.current = value;
}, [value]);
return ref.current;
}
var noop = function 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.
*/
export function useDescendant(descendant) {
var _React$useState = React.useState(),
_React$useState2 = _slicedToArray(_React$useState, 2),
forceUpdate = _React$useState2[1];
var _React$useContext = React.useContext(DescendantContext),
_React$useContext$reg = _React$useContext.registerDescendant,
registerDescendant = _React$useContext$reg === void 0 ? noop : _React$useContext$reg,
_React$useContext$unr = _React$useContext.unregisterDescendant,
unregisterDescendant = _React$useContext$unr === void 0 ? noop : _React$useContext$unr,
_React$useContext$des = _React$useContext.descendants,
descendants = _React$useContext$des === void 0 ? [] : _React$useContext$des,
_React$useContext$par = _React$useContext.parentId,
parentId = _React$useContext$par === void 0 ? null : _React$useContext$par;
// This will initially return -1 because we haven't registered the descendant
// on the first render. After we register, this will then return the correct
// index on the following render, and we will re-register descendants
// so that everything is up-to-date before the user interacts with a
// collection.
var index = findIndex(descendants, function (item) {
return item.element === descendant.element;
});
var previousDescendants = usePrevious(descendants);
// We also need to re-register descendants any time ANY of the other
// descendants have changed. My brain was melting when I wrote this and it
// feels a little off, but checking in render and using the result in the
// effect's dependency array works well enough.
var someDescendantsHaveChanged = descendants.some(function (newDescendant, position) {
return previousDescendants && previousDescendants[position] && previousDescendants[position].element !== newDescendant.element;
});
// Prevent any flashing
useEnhancedEffect(function () {
if (descendant.element) {
registerDescendant(_extends({}, descendant, {
index: index
}));
return function () {
unregisterDescendant(descendant.element);
};
}
forceUpdate({});
return undefined;
}, [registerDescendant, unregisterDescendant, index, someDescendantsHaveChanged, descendant]);
return {
parentId: parentId,
index: index
};
}
export function DescendantProvider(props) {
var children = props.children,
id = props.id;
var _React$useState3 = React.useState([]),
_React$useState4 = _slicedToArray(_React$useState3, 2),
items = _React$useState4[0],
set = _React$useState4[1];
var registerDescendant = React.useCallback(function (_ref) {
var element = _ref.element,
other = _objectWithoutProperties(_ref, _excluded);
set(function (oldItems) {
if (oldItems.length === 0) {
// If there are no items, register at index 0 and bail.
return [_extends({}, other, {
element: element,
index: 0
})];
}
var index = binaryFindElement(oldItems, element);
var newItems;
if (oldItems[index] && oldItems[index].element === element) {
// If the element is already registered, just use the same array
newItems = oldItems;
} else {
// When registering a descendant, we need to make sure we insert in
// into the array in the same order that it appears in the DOM. So as
// new descendants are added or maybe some are removed, we always know
// that the array is up-to-date and correct.
//
// So here we look at our registered descendants and see if the new
// element we are adding appears earlier than an existing descendant's
// DOM node via `node.compareDocumentPosition`. If it does, we insert
// the new element at this index. Because `registerDescendant` will be
// called in an effect every time the descendants state value changes,
// we should be sure that this index is accurate when descendent
// elements come or go from our component.
var newItem = _extends({}, other, {
element: element,
index: index
});
// If an index is not found we will push the element to the end.
newItems = oldItems.slice();
newItems.splice(index, 0, newItem);
}
newItems.forEach(function (item, position) {
item.index = position;
});
return newItems;
});
}, []);
var unregisterDescendant = React.useCallback(function (element) {
set(function (oldItems) {
return oldItems.filter(function (item) {
return element !== item.element;
});
});
}, []);
var value = React.useMemo(function () {
return {
descendants: items,
registerDescendant: registerDescendant,
unregisterDescendant: unregisterDescendant,
parentId: id
};
}, [items, registerDescendant, unregisterDescendant, id]);
return /*#__PURE__*/_jsx(DescendantContext.Provider, {
value: value,
children: children
});
}
process.env.NODE_ENV !== "production" ? DescendantProvider.propTypes = {
children: PropTypes.node,
id: PropTypes.string
} : void 0;