@mantine/core
Version:
React components library focused on usability, accessibility and developer experience
373 lines (372 loc) • 13.6 kB
JavaScript
"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