UNPKG

@mantine/core

Version:

React components library focused on usability, accessibility and developer experience

373 lines (372 loc) 13.6 kB
"use client"; import { useProps } from "../../core/MantineProvider/use-props/use-props.mjs"; import { useResolvedStylesApi } from "../../core/styles-api/use-resolved-styles-api/use-resolved-styles-api.mjs"; import { useStyles } from "../../core/styles-api/use-styles/use-styles.mjs"; import { extractStyleProps } from "../../core/Box/style-props/extract-style-props/extract-style-props.mjs"; import { factory } from "../../core/factory/factory.mjs"; import { ScrollArea } from "../ScrollArea/ScrollArea.mjs"; import { InputBase } from "../InputBase/InputBase.mjs"; import { useCombobox } from "../Combobox/use-combobox/use-combobox.mjs"; import { Combobox } from "../Combobox/Combobox.mjs"; import { CheckIcon } from "../Checkbox/CheckIcon.mjs"; import { CascaderColumns } from "./CascaderColumns.mjs"; import { flattenCascaderPaths } from "./flatten-cascader-paths.mjs"; import { getCascaderPathOptions } from "./get-cascader-path-options.mjs"; import { useCascader } from "./use-cascader.mjs"; import Cascader_module_default from "./Cascader.module.mjs"; import { Fragment, useEffect, useMemo } from "react"; import { useId as useId$1, useUncontrolled } from "@mantine/hooks"; import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime"; //#region packages/@mantine/core/src/components/Cascader/Cascader.tsx const defaultProps = { expandTrigger: "click", safeAreaPolygon: true, changeOnSelect: false, allowDeselect: true, withCheckIcon: true, checkIconPosition: "right", withColumns: true, searchable: false, separator: "/", maxDisplayedLevels: 3, previousLevelsControlLabel: "Show previous levels", nextLevelsControlLabel: "Show next levels", maxDropdownHeight: 260, openOnFocus: true, size: "sm" }; function optionLabelToString(option) { return typeof option.label === "string" || typeof option.label === "number" ? String(option.label) : option.value; } function firstEnabledOptionIndex(options) { return options.findIndex((option) => !option.disabled); } function lastEnabledOptionIndex(options) { for (let index = options.length - 1; index >= 0; index -= 1) if (!options[index].disabled) return index; return -1; } function joinPathLabels(options, separator) { return options.map(optionLabelToString).join(` ${separator} `); } function defaultCascaderFilter(query, options, separator) { const trimmed = query.trim().toLowerCase(); if (trimmed.length === 0) return true; return joinPathLabels(options, separator).toLowerCase().includes(trimmed); } function defaultRenderSearchOption(options, separator) { return options.map((option, index) => /* @__PURE__ */ jsxs(Fragment, { children: [index > 0 && /* @__PURE__ */ jsxs("span", { "data-cascader-separator": true, children: [ " ", separator, " " ] }), option.label ?? option.value] }, option.value)); } const Cascader = factory((_props) => { const props = useProps([ "Input", "InputWrapper", "Cascader" ], defaultProps, _props); const { classNames, className, style, styles, unstyled, vars, size, data, value, defaultValue, onChange, changeOnSelect, closeOnSelect, allowDeselect, withCheckIcon, checkIconPosition, withColumns, expandTrigger, safeAreaPolygon, searchable, searchValue, defaultSearchValue, onSearchChange, filter, renderSearchOption, formatValue, renderOption, separator, columnWidth, maxDisplayedLevels, previousLevelsControlLabel, nextLevelsControlLabel, maxDropdownHeight, nothingFoundMessage, clearable, clearSectionMode, clearButtonProps, onClear, dropdownOpened, defaultDropdownOpened, onDropdownOpen, onDropdownClose, comboboxProps, scrollAreaProps, chevronColor, hiddenInputProps, openOnFocus, variant, onKeyDown, onFocus, onBlur, onClick, readOnly, disabled, radius, rightSection, rightSectionWidth, rightSectionPointerEvents, rightSectionProps, leftSection, leftSectionWidth, leftSectionPointerEvents, leftSectionProps, inputContainer, inputWrapperOrder, withAsterisk, labelProps, descriptionProps, errorProps, successProps, wrapperProps, description, label, error, success, withErrorStyles, withSuccessStyles, name, form, id, placeholder, required, mod, attributes, ...others } = props; const _id = useId$1(id); const separatorString = typeof separator === "string" || typeof separator === "number" ? String(separator) : "/"; const combobox = useCombobox({ opened: dropdownOpened, defaultOpened: defaultDropdownOpened, onDropdownOpen: () => { onDropdownOpen?.(); cascader.resetActivePath(); }, onDropdownClose: () => { onDropdownClose?.(); if (searchable) setSearchValue(cascader.value ? displayString : ""); } }); const shouldCloseOnSelect = closeOnSelect ?? !allowDeselect; const cascader = useCascader({ data, value, defaultValue, onChange, changeOnSelect, allowDeselect, expandTrigger, onLeafSelect: () => { if (shouldCloseOnSelect) combobox.closeDropdown(); } }); const getStyles = useStyles({ name: "Cascader", classes: Cascader_module_default, props, classNames, styles, unstyled, attributes }); const { resolvedClassNames, resolvedStyles } = useResolvedStylesApi({ props, styles, classNames }); const { styleProps, rest: { type, autoComplete, ...rest } } = extractStyleProps(others); const displayString = useMemo(() => { if (cascader.pathOptions.length === 0 || !cascader.value) return ""; if (formatValue) { const rendered = formatValue({ value: cascader.value, options: cascader.pathOptions }); if (typeof rendered === "string" || typeof rendered === "number") return String(rendered); } return joinPathLabels(cascader.pathOptions, separatorString); }, [ cascader.pathOptions, cascader.value, formatValue, separatorString ]); const [_searchValue, setSearchValue] = useUncontrolled({ value: searchValue, defaultValue: defaultSearchValue, finalValue: useMemo(() => { if (!searchable || !defaultValue) return ""; return joinPathLabels(getCascaderPathOptions(data, defaultValue), separatorString); }, []), onChange: onSearchChange }); const isSearching = !!searchable && _searchValue.trim().length > 0 && _searchValue !== displayString; useEffect(() => { if (searchable) setSearchValue(cascader.value ? displayString : ""); }, [cascader.value]); const showFlatList = isSearching || !withColumns; const flatPaths = useMemo(() => flattenCascaderPaths(data), [data]); const flatListItems = useMemo(() => { if (!showFlatList) return []; const base = flatPaths.filter((flatPath) => changeOnSelect ? true : flatPath.leaf); if (!isSearching) return base; return base.filter((flatPath) => filter ? filter(_searchValue, flatPath.options) : defaultCascaderFilter(_searchValue, flatPath.options, separatorString)); }, [ flatPaths, showFlatList, isSearching, _searchValue, changeOnSelect, filter, separatorString ]); const canInteract = !readOnly && !disabled; const handleSearchChange = (nextValue) => { setSearchValue(nextValue); if (canInteract) combobox.openDropdown(); }; const handleKeyDown = (event) => { onKeyDown?.(event); if (event.key === "Escape") { combobox.closeDropdown(); return; } if (showFlatList || !canInteract) return; if (!combobox.dropdownOpened) { if (event.key === "ArrowDown" || event.key === "ArrowUp" || event.key === "ArrowRight" || event.key === "Enter" || !searchable && event.key === " ") { event.preventDefault(); cascader.setKeyboardNav(true); combobox.openDropdown(); if (!cascader.value && (event.key === "ArrowDown" || event.key === "ArrowUp")) { const rootIndex = event.key === "ArrowDown" ? firstEnabledOptionIndex(data) : lastEnabledOptionIndex(data); if (rootIndex >= 0) cascader.setActivePath([data[rootIndex].value]); } } return; } if (cascader.handleColumnsKeyDown(event)) event.preventDefault(); else if (!searchable && event.key === " ") event.preventDefault(); }; const clearButton = /* @__PURE__ */ jsx(Combobox.ClearButton, { ...clearButtonProps, onClear: () => { onClear?.(); cascader.setValue(null); cascader.setActivePath([]); setSearchValue(""); combobox.focusTarget(); } }); const hasValue = Array.isArray(cascader.value) && cascader.value.length > 0; const _clearable = clearable && hasValue && !disabled && !readOnly; const listId = _id ? `${_id}-cascader-list` : void 0; const valueKey = cascader.value ? JSON.stringify(cascader.value) : null; const dropdown = /* @__PURE__ */ jsx(Combobox.Dropdown, { hidden: readOnly || disabled, style: showFlatList ? void 0 : { padding: 0 }, children: showFlatList ? /* @__PURE__ */ jsxs(Combobox.Options, { "aria-label": typeof label === "string" ? label : void 0, children: [/* @__PURE__ */ jsx(ScrollArea.Autosize, { mah: maxDropdownHeight ?? 260, type: "scroll", scrollbarSize: "var(--combobox-padding)", offsetScrollbars: "y", ...scrollAreaProps, children: flatListItems.map((flatPath, index) => { const pathKey = JSON.stringify(flatPath.path); const active = valueKey !== null && pathKey === valueKey; return /* @__PURE__ */ jsx(Combobox.Option, { value: `${index}`, active, disabled: flatPath.disabled, children: /* @__PURE__ */ jsxs("span", { ...getStyles("flatOption"), children: [ active && withCheckIcon && checkIconPosition === "left" && /* @__PURE__ */ jsx(CheckIcon, { ...getStyles("columnOptionCheck") }), /* @__PURE__ */ jsx("span", { ...getStyles("columnOptionLabel"), children: renderSearchOption ? renderSearchOption(_searchValue, flatPath.options) : defaultRenderSearchOption(flatPath.options, separator) }), active && withCheckIcon && checkIconPosition !== "left" && /* @__PURE__ */ jsx(CheckIcon, { ...getStyles("columnOptionCheck") }) ] }) }, pathKey); }) }), flatListItems.length === 0 && nothingFoundMessage && /* @__PURE__ */ jsx(Combobox.Empty, { children: nothingFoundMessage })] }) : /* @__PURE__ */ jsx(CascaderColumns, { data, activePath: cascader.activePath, value: cascader.value, keyboardNav: cascader.keyboardNav, withCheckIcon, checkIconPosition, renderOption, columnWidth, maxDisplayedLevels, previousLevelsControlLabel, nextLevelsControlLabel, maxDropdownHeight, nothingFoundMessage, getStyles, unstyled, scrollAreaProps, onOptionClick: cascader.handleOptionClick, onOptionMouseEnter: cascader.handleOptionMouseEnter, onColumnsMouseLeave: () => { if (expandTrigger === "hover") cascader.setActivePath(cascader.value ?? []); }, onPointerActivity: () => cascader.setKeyboardNav(false), listId, safeAreaPolygon: expandTrigger === "hover" ? safeAreaPolygon : false }) }); return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs(Combobox, { store: combobox, __staticSelector: "Cascader", classNames: resolvedClassNames, styles: resolvedStyles, unstyled, readOnly, size, attributes, width: showFlatList ? "target" : "max-content", position: "bottom-start", onOptionSubmit: (val) => { const index = Number(val); const flatPath = flatListItems[index]; if (!flatPath || flatPath.disabled) return; cascader.selectPath(flatPath.path); cascader.setActivePath(flatPath.path); if (shouldCloseOnSelect) combobox.closeDropdown(); }, ...comboboxProps, children: [/* @__PURE__ */ jsx(Combobox.Target, { targetType: searchable ? "input" : "button", withKeyboardNavigation: showFlatList, autoComplete, children: /* @__PURE__ */ jsx(InputBase, { id: _id, __defaultRightSection: /* @__PURE__ */ jsx(Combobox.Chevron, { size, error, unstyled, color: chevronColor }), __clearSection: clearButton, __clearable: _clearable, __clearSectionMode: clearSectionMode, rightSection, rightSectionPointerEvents: rightSectionPointerEvents || "none", ...rest, ...styleProps, size, __staticSelector: "Cascader", disabled, readOnly: readOnly || !searchable, value: searchable ? _searchValue : displayString, onChange: (event) => handleSearchChange(event.currentTarget.value), onFocus: (event) => { if (openOnFocus && searchable && canInteract) combobox.openDropdown(); onFocus?.(event); }, onBlur: (event) => { combobox.closeDropdown(); onBlur?.(event); }, onClick: (event) => { if (canInteract) { cascader.setKeyboardNav(false); if (searchable) combobox.openDropdown(); else combobox.toggleDropdown(); } onClick?.(event); }, onKeyDown: handleKeyDown, classNames: resolvedClassNames, styles: resolvedStyles, unstyled, pointer: !searchable, error, success, attributes, className, style, variant, radius, leftSection, leftSectionWidth, leftSectionPointerEvents, leftSectionProps, rightSectionWidth, rightSectionProps, inputContainer, inputWrapperOrder, withAsterisk, labelProps, descriptionProps, errorProps, successProps, wrapperProps, description, label, withErrorStyles, withSuccessStyles, placeholder, required, mod }) }), dropdown] }), /* @__PURE__ */ jsx(Combobox.HiddenInput, { value: cascader.value, name, form, disabled, ...hiddenInputProps })] }); }); Cascader.classes = { ...InputBase.classes, ...Combobox.classes, ...Cascader_module_default }; Cascader.displayName = "@mantine/core/Cascader"; //#endregion export { Cascader }; //# sourceMappingURL=Cascader.mjs.map