@yamada-ui/react
Version:
React UI components of the Yamada, by the Yamada, for the Yamada built with React and Emotion
206 lines (202 loc) • 6.77 kB
JavaScript
"use client";
import { createContext as createContext$1 } from "../../utils/context.js";
import { useSafeLayoutEffect } from "../../utils/effect.js";
import { mergeRefs } from "../../utils/ref.js";
import { utils_exports } from "../../utils/index.js";
import { useRef } from "react";
//#region src/hooks/use-descendants/index.ts
const sortNodes = (nodes) => nodes.sort((a, b) => {
const compare = a.compareDocumentPosition(b);
if (compare & Node.DOCUMENT_POSITION_FOLLOWING || compare & Node.DOCUMENT_POSITION_CONTAINED_BY) return -1;
if (compare & Node.DOCUMENT_POSITION_PRECEDING || compare & Node.DOCUMENT_POSITION_CONTAINS) return 1;
if (compare & Node.DOCUMENT_POSITION_DISCONNECTED || compare & Node.DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC) {
console.warn("Cannot sort the given nodes.");
return 0;
} else return 0;
});
const getNextIndex = (current, max, loop) => {
let next = current + 1;
if (loop && next >= max) next = 0;
return next;
};
const getPrevIndex = (current, max, loop) => {
let next = current - 1;
if (loop && next < 0) next = max;
return next;
};
const descendantManager = () => {
const descendants = /* @__PURE__ */ new Map();
const setIndexes = (next) => {
descendants.forEach((descendant) => {
descendant.index = next.indexOf(descendant.node);
descendant.node.dataset.index = descendant.index.toString();
});
};
const set = (node, props) => {
if (!node || descendants.has(node)) return;
const sorted = sortNodes(Array.from(descendants.keys()).concat(node));
const descendant = {
...props,
index: -1,
node
};
descendants.set(node, descendant);
setIndexes(sorted);
};
const destroy = () => descendants.clear();
const register = (props) => {
return (node) => set(node, props);
};
const unregister = (node) => {
if (node == null) return;
descendants.delete(node);
setIndexes(sortNodes(Array.from(descendants.keys())));
};
const count = () => values().length;
const enabledCount = () => enabledValues().length;
const active = (target, options) => {
if (!target) return;
if (!(target instanceof Node)) target = target.node;
if ((0, utils_exports.isTruthyDataAttr)(target.dataset.activedescendant)) return;
values().forEach(({ node }) => {
delete node.dataset.activedescendant;
});
target.dataset.activedescendant = "";
if (options) target.focus(options);
};
const indexOf = (target) => {
if (!target) return -1;
if (target instanceof Node) return descendants.get(target)?.index ?? -1;
else return descendants.get(target.node)?.index ?? -1;
};
const enabledIndexOf = (target) => {
if (!target) return -1;
if (target instanceof Node) return enabledValues().findIndex(({ node }) => node.isSameNode(target));
else return enabledValues().findIndex(({ node }) => node.isSameNode(target.node));
};
const values = () => Array.from(descendants.values()).sort((a, b) => a.index - b.index);
const enabledValues = () => values().filter(({ disabled, node }) => !(0, utils_exports.runIfFn)(disabled, node));
const value = (indexOrNode) => {
if (!count() || indexOrNode == null) return void 0;
return (0, utils_exports.isNumber)(indexOrNode) ? values()[indexOrNode] : descendants.get(indexOrNode);
};
const enabledValue = (index) => {
if (!enabledCount()) return void 0;
return enabledValues()[index];
};
const firstValue = () => value(0);
const enabledFirstValue = () => enabledValue(0);
const lastValue = () => value(count() - 1);
const enabledLastValue = () => enabledValue(enabledCount() - 1);
const prevValue = (indexOrNode, loop = true) => {
if (!count()) return void 0;
const currentIndex = (0, utils_exports.isNumber)(indexOrNode) ? indexOrNode : indexOf(indexOrNode);
if (currentIndex === -1) return void 0;
return value(getPrevIndex(currentIndex, count() - 1, loop));
};
const enabledPrevValue = (indexOrNode, loop = true) => {
if (!enabledCount()) return void 0;
let index = (0, utils_exports.isNumber)(indexOrNode) ? indexOrNode : indexOf(indexOrNode);
let enabledValue$1 = null;
let recurred = false;
while (enabledValue$1 == null) {
index--;
if (index < 0) {
if (!loop) return;
index = count() - 1;
recurred = true;
}
const descendant = value(index);
enabledValue$1 = descendant && !(0, utils_exports.runIfFn)(descendant.disabled, descendant.node) ? descendant : null;
}
if (recurred) enabledValue$1.recurred = recurred;
return enabledValue$1;
};
const nextValue = (indexOrNode, loop = true) => {
if (!count()) return void 0;
const currentIndex = (0, utils_exports.isNumber)(indexOrNode) ? indexOrNode : indexOf(indexOrNode);
if (currentIndex === -1) return void 0;
return value(getNextIndex(currentIndex, count(), loop));
};
const enabledNextValue = (indexOrNode, loop = true) => {
if (!enabledCount()) return void 0;
let index = (0, utils_exports.isNumber)(indexOrNode) ? indexOrNode : indexOf(indexOrNode);
let enabledValue$1 = null;
let recurred = false;
while (enabledValue$1 == null) {
index++;
if (index >= count()) {
if (!loop) return;
index = 0;
recurred = true;
}
const descendant = value(index);
enabledValue$1 = descendant && !(0, utils_exports.runIfFn)(descendant.disabled, descendant.node) ? descendant : null;
}
if (recurred) enabledValue$1.recurred = recurred;
return enabledValue$1;
};
return {
active,
count,
destroy,
enabledCount,
enabledFirstValue,
enabledIndexOf,
enabledLastValue,
enabledNextValue,
enabledPrevValue,
enabledValue,
enabledValues,
firstValue,
indexOf,
lastValue,
nextValue,
prevValue,
register,
unregister,
value,
values
};
};
/**
* `useDescendants` is a custom hook that manages descendants.
*
* @see https://yamada-ui.com/docs/hooks/use-descendants
*/
const createDescendants = () => {
const [DescendantsContext, useDescendantsContext] = createContext$1({ name: "DescendantsContext" });
const useDescendantRegister = (descendants) => {
const ref = useRef(null);
useSafeLayoutEffect(() => {
return () => {
if (ref.current) descendants?.unregister(ref.current);
};
}, []);
return (props) => mergeRefs(ref, descendants?.register(props));
};
const useDescendants = () => {
const descendants = useRef(descendantManager());
useSafeLayoutEffect(() => {
return () => descendants.current.destroy();
});
return descendants.current;
};
const useDescendant = (props) => {
const descendants = useDescendantsContext();
return {
descendants,
register: useDescendantRegister(descendants)(props)
};
};
return {
DescendantsContext,
useDescendant,
useDescendantRegister,
useDescendants,
useDescendantsContext
};
};
//#endregion
export { createDescendants };
//# sourceMappingURL=index.js.map