UNPKG

@mantine/core

Version:

React components library focused on usability, accessibility and developer experience

135 lines (134 loc) 4.74 kB
"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