@mantine/core
Version:
React components library focused on usability, accessibility and developer experience
135 lines (134 loc) • 4.74 kB
JavaScript
"use client";
import { cascaderOptionHasChildren, getCascaderColumns } from "./get-cascader-columns.mjs";
import { getCascaderPathOptions } from "./get-cascader-path-options.mjs";
import { useCallback, useMemo, useRef, useState } from "react";
import { useUncontrolled } from "@mantine/hooks";
//#region packages/@mantine/core/src/components/Cascader/use-cascader.ts
function pathsEqual(a, b) {
return !!a && a.length === b.length && a.every((item, index) => item === b[index]);
}
function findEnabledIndex(options, from, direction) {
let index = from + direction;
while (index >= 0 && index < options.length) {
if (!options[index].disabled) return index;
index += direction;
}
return -1;
}
function useCascader({ data, value, defaultValue, onChange, changeOnSelect, allowDeselect, expandTrigger, onLeafSelect }) {
const [keyboardNav, setKeyboardNav] = useState(false);
const [_value, setValue] = useUncontrolled({
value,
defaultValue,
finalValue: null,
onChange: (val) => onChange?.(val, getCascaderPathOptions(data, val))
});
const [activePath, setActivePath] = useState(() => _value ?? []);
const activePathRef = useRef(activePath);
activePathRef.current = activePath;
const resetActivePath = useCallback(() => {
setActivePath(_value ?? []);
}, [_value]);
const selectPath = useCallback((path) => {
setValue(allowDeselect && pathsEqual(_value, path) ? null : path);
}, [
allowDeselect,
_value,
setValue
]);
const expandOption = useCallback((level, option) => {
const optionPath = [...activePathRef.current.slice(0, level), option.value];
const children = option.children;
if (Array.isArray(children) && children.length > 0) {
const first = findEnabledIndex(children, -1, 1);
setActivePath(first >= 0 ? [...optionPath, children[first].value] : optionPath);
} else setActivePath(optionPath);
}, []);
return {
value: _value,
setValue,
selectPath,
activePath,
setActivePath,
resetActivePath,
keyboardNav,
setKeyboardNav,
handleOptionClick: useCallback((level, option) => {
setKeyboardNav(false);
const path = [...activePath.slice(0, level), option.value];
if (cascaderOptionHasChildren(option)) {
if (changeOnSelect) selectPath(path);
expandOption(level, option);
if (changeOnSelect) onLeafSelect?.();
} else {
selectPath(path);
onLeafSelect?.();
}
}, [
activePath,
changeOnSelect,
selectPath,
expandOption,
onLeafSelect
]),
handleOptionMouseEnter: useCallback((level, option) => {
setKeyboardNav(false);
if (expandTrigger !== "hover") return;
if (cascaderOptionHasChildren(option)) expandOption(level, option);
else setActivePath([...activePath.slice(0, level), option.value]);
}, [
expandTrigger,
activePath,
expandOption
]),
handleColumnsKeyDown: useCallback((event) => {
setKeyboardNav(true);
const columns = getCascaderColumns(data, activePath);
const focusedLevel = Math.max(0, activePath.length - 1);
const currentColumn = columns[focusedLevel] ?? columns[0] ?? [];
const focusedIndex = currentColumn.findIndex((item) => item.value === activePath[focusedLevel]);
const focusedOption = focusedIndex >= 0 ? currentColumn[focusedIndex] : void 0;
switch (event.key) {
case "ArrowDown": {
const next = findEnabledIndex(currentColumn, focusedIndex, 1);
if (next >= 0) setActivePath([...activePath.slice(0, focusedLevel), currentColumn[next].value]);
return true;
}
case "ArrowUp": {
const prev = findEnabledIndex(currentColumn, focusedIndex < 0 ? currentColumn.length : focusedIndex, -1);
if (prev >= 0) setActivePath([...activePath.slice(0, focusedLevel), currentColumn[prev].value]);
return true;
}
case "ArrowRight":
if (focusedOption && cascaderOptionHasChildren(focusedOption)) expandOption(focusedLevel, focusedOption);
return true;
case "ArrowLeft":
if (activePath.length > 1) setActivePath(activePath.slice(0, -1));
return true;
case "Enter":
if (!focusedOption) return false;
if (cascaderOptionHasChildren(focusedOption)) {
if (changeOnSelect) selectPath([...activePath.slice(0, focusedLevel), focusedOption.value]);
expandOption(focusedLevel, focusedOption);
if (changeOnSelect) onLeafSelect?.();
} else {
selectPath([...activePath.slice(0, focusedLevel), focusedOption.value]);
onLeafSelect?.();
}
return true;
default: return false;
}
}, [
data,
activePath,
changeOnSelect,
selectPath,
expandOption,
onLeafSelect
]),
pathOptions: useMemo(() => getCascaderPathOptions(data, _value), [data, _value])
};
}
//#endregion
export { useCascader };
//# sourceMappingURL=use-cascader.mjs.map