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

194 lines
7.6 KiB
JavaScript

"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.DescendantProvider = DescendantProvider;
exports.useDescendant = useDescendant;
var _objectWithoutPropertiesLoose2 = _interopRequireDefault(require("@babel/runtime/helpers/objectWithoutPropertiesLoose"));
var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends"));
var React = _interopRequireWildcard(require("react"));
var _propTypes = _interopRequireDefault(require("prop-types"));
var _useEnhancedEffect = _interopRequireDefault(require("@mui/utils/useEnhancedEffect"));
var _jsxRuntime = require("react/jsx-runtime");
const _excluded = ["element"];
/** 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.
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
function findIndex(array, comp) {
for (let i = 0; i < array.length; i += 1) {
if (comp(array[i])) {
return i;
}
}
return -1;
}
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;
}
// 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;
}
const DescendantContext = /*#__PURE__*/React.createContext({});
if (process.env.NODE_ENV !== 'production') {
DescendantContext.displayName = 'DescendantContext';
}
function usePrevious(value) {
const ref = React.useRef(null);
React.useEffect(() => {
ref.current = value;
}, [value]);
return ref.current;
}
const 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
} = React.useContext(DescendantContext);
// 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.
const index = findIndex(descendants, item => item.element === descendant.element);
const 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.
const someDescendantsHaveChanged = descendants.some((newDescendant, position) => {
return previousDescendants && previousDescendants[position] && previousDescendants[position].element !== newDescendant.element;
});
// Prevent any flashing
(0, _useEnhancedEffect.default)(() => {
if (descendant.element) {
registerDescendant((0, _extends2.default)({}, descendant, {
index
}));
return () => {
unregisterDescendant(descendant.element);
};
}
forceUpdate({});
return undefined;
}, [registerDescendant, unregisterDescendant, index, someDescendantsHaveChanged, descendant]);
return {
parentId,
index
};
}
function DescendantProvider(props) {
const {
children,
id
} = props;
const [items, set] = React.useState([]);
const registerDescendant = React.useCallback(_ref => {
let {
element
} = _ref,
other = (0, _objectWithoutPropertiesLoose2.default)(_ref, _excluded);
set(oldItems => {
if (oldItems.length === 0) {
// If there are no items, register at index 0 and bail.
return [(0, _extends2.default)({}, other, {
element,
index: 0
})];
}
const index = binaryFindElement(oldItems, element);
let 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.
const newItem = (0, _extends2.default)({}, other, {
element,
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((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
}), [items, registerDescendant, unregisterDescendant, id]);
return /*#__PURE__*/(0, _jsxRuntime.jsx)(DescendantContext.Provider, {
value: value,
children: children
});
}
process.env.NODE_ENV !== "production" ? DescendantProvider.propTypes = {
children: _propTypes.default.node,
id: _propTypes.default.string
} : void 0;