UNPKG

rsuite

Version:

A suite of react components

321 lines (319 loc) 9.7 kB
'use client'; import _extends from "@babel/runtime/helpers/esm/extends"; import React, { useMemo } from 'react'; import classNames from 'classnames'; import CheckTreeView from "../CheckTree/CheckTreeView.js"; import useTreeValue from "../CheckTree/hooks/useTreeValue.js"; import useFlattenTree from "../Tree/hooks/useFlattenTree.js"; import useTreeWithChildren from "../Tree/hooks/useTreeWithChildren.js"; import useExpandTree from "../Tree/hooks/useExpandTree.js"; import useFocusState from "./hooks/useFocusState.js"; import isNil from 'lodash/isNil'; import pick from 'lodash/pick'; import isFunction from 'lodash/isFunction'; import { useStyles, useCustom, useEventCallback } from "../internals/hooks/index.js"; import { forwardRef, createChainedFunction, mergeRefs } from "../internals/utils/index.js"; import { PickerToggle, onMenuKeyDown, PickerPopup, SelectedElement, PickerToggleTrigger, useToggleKeyDownEvent, usePickerRef, triggerPropKeys } from "../internals/Picker/index.js"; import { getSelectedItems } from "../CheckTree/utils.js"; import { TreeProvider, useTreeImperativeHandle } from "../internals/Tree/TreeProvider.js"; /** * The `CheckTreePicker` component is used for selecting multiple options which are organized in a tree structure. * * @see https://rsuitejs.com/components/check-tree-picker */ const CheckTreePicker = forwardRef((props, ref) => { const { propsWithDefaults } = useCustom('CheckTreePicker', props); const { as, id, appearance = 'default', block, cleanable = true, countable = true, cascade = true, className, classPrefix = 'picker', childrenKey = 'children', disabled, data = [], defaultValue = [], defaultExpandAll = false, disabledItemValues = [], expandItemValues: controlledExpandItemValues, defaultExpandItemValues = [], placeholder, popupClassName, popupStyle, popupAutoWidth = true, placement = 'bottomStart', treeHeight = 320, toggleAs, searchBy, searchKeyword, showIndentLine, searchable = true, style, size, valueKey = 'value', value: controlledValue, virtualized = false, uncheckableItemValues = [], locale, labelKey = 'label', listProps, getChildren, renderExtraFooter, onEnter, onChange, onClean, onExit, onSearch, onSelect, onSelectItem, onScroll, onExpand, renderValue, renderTree, renderTreeIcon, renderTreeNode, onCascadeChange, ...rest } = propsWithDefaults; const { trigger, root, target, overlay, list, searchInput, treeView } = usePickerRef(ref); const { prefix } = useStyles(classPrefix); const [value, setValue] = useTreeValue(controlledValue, { defaultValue, uncheckableItemValues }); const itemDataKeys = { childrenKey, labelKey, valueKey }; const { treeData, loadingNodeValues, appendChild } = useTreeWithChildren(data, itemDataKeys); const { expandItemValues, handleExpandTreeNode } = useExpandTree(data, { ...itemDataKeys, defaultExpandAll, defaultExpandItemValues, controlledExpandItemValues, onExpand, getChildren, appendChild }); const flattenedNodes = useFlattenTree(treeData, { ...itemDataKeys, uncheckableItemValues, disabledItemValues, multiple: true, cascade, value }); const selectedNodes = getSelectedItems(flattenedNodes, value); const { register, focusFirstNode } = useTreeImperativeHandle(); const { focusItemValue, setFocusItemValue, active, triggerProps } = useFocusState({ target, onEnter, onExit }); const handleClean = useEventCallback(event => { const target = event.target; // exclude searchbox if (target.matches('input[role="searchbox"]') || disabled || !cleanable) { return; } setFocusItemValue(null); setValue([]); onChange === null || onChange === void 0 || onChange([], event); onCascadeChange === null || onCascadeChange === void 0 || onCascadeChange([], event); }); const handleTreeKeyDown = useEventCallback(event => { onMenuKeyDown(event, { del: handleClean, down: () => focusFirstNode() }); }); const onPickerKeydown = useToggleKeyDownEvent({ toggle: !focusItemValue || !active, trigger, target, overlay, searchInput, active, onExit: handleClean, onMenuKeyDown: handleTreeKeyDown, ...rest }); // transform the parent node value to the leaf node value const handleTransValue2Children = useEventCallback(nextSelectedNodes => { return nextSelectedNodes.map(node => { const currentNode = node.refKey ? flattenedNodes[node.refKey] : null; if (currentNode && currentNode[childrenKey] && currentNode[childrenKey].length) { const childNodes = currentNode[childrenKey].filter(child => { const childValue = child[valueKey]; return !disabledItemValues.includes(childValue) && !uncheckableItemValues.includes(childValue); }); return handleTransValue2Children(childNodes); } return node; }).flat(); }); const handleChangeCascade = useEventCallback((nextValue, event) => { if (!cascade) { onCascadeChange === null || onCascadeChange === void 0 || onCascadeChange(nextValue, event); } else { const nextSelectedNodes = getSelectedItems(flattenedNodes, nextValue); const childrenNodes = handleTransValue2Children(nextSelectedNodes); const childrenValue = childrenNodes.map(node => node[valueKey]); onCascadeChange === null || onCascadeChange === void 0 || onCascadeChange(childrenValue, event); } }); const handleChange = useEventCallback((nextValue, event) => { setValue(nextValue); onChange === null || onChange === void 0 || onChange(nextValue, event); handleChangeCascade(nextValue, event); }); const treeContext = useMemo(() => ({ register, props: { labelKey, valueKey, childrenKey, virtualized, renderTreeIcon, renderTreeNode } }), [childrenKey, labelKey, valueKey, virtualized, register, renderTreeIcon, renderTreeNode]); const checkTreeView = /*#__PURE__*/React.createElement(TreeProvider, { value: treeContext }, /*#__PURE__*/React.createElement(CheckTreeView, { ref: treeView, disabledItemValues: disabledItemValues, expandItemValues: expandItemValues, uncheckableItemValues: uncheckableItemValues, cascade: cascade, data: treeData, height: treeHeight, showIndentLine: showIndentLine, listProps: listProps, listRef: list, locale: locale, searchBy: searchBy, searchable: searchable, searchKeyword: searchKeyword, searchInputRef: searchInput, onScroll: onScroll, onSelect: onSelect, onSelectItem: onSelectItem, onExpand: handleExpandTreeNode, onSearch: onSearch, onChange: handleChange, onFocusItem: setFocusItemValue, value: value, loadingNodeValues: loadingNodeValues, flattenedNodes: flattenedNodes })); const renderTreeView = (positionProps, speakerRef) => { const { className } = positionProps; const classes = classNames(className, popupClassName, prefix('check-tree-menu')); return /*#__PURE__*/React.createElement(PickerPopup, { ref: mergeRefs(overlay, speakerRef), autoWidth: popupAutoWidth, className: classes, style: popupStyle, onKeyDown: onPickerKeydown, target: trigger }, renderTree ? renderTree(checkTreeView) : checkTreeView, renderExtraFooter === null || renderExtraFooter === void 0 ? void 0 : renderExtraFooter()); }; /** * 1.Have a value and the value is valid. * 2.Regardless of whether the value is valid, as long as renderValue is set, it is judged to have a value. */ let hasValidValue = selectedNodes.length > 0 || value.length > 0 && isFunction(renderValue); let selectedElement = placeholder; if (hasValidValue) { selectedElement = /*#__PURE__*/React.createElement(SelectedElement, { selectedItems: selectedNodes, countable: countable, valueKey: valueKey, labelKey: labelKey, childrenKey: childrenKey, prefix: prefix, cascade: cascade, locale: locale, badgeSize: size }); if (isFunction(renderValue)) { selectedElement = renderValue(value, selectedNodes, selectedElement); if (isNil(selectedElement)) { hasValidValue = false; } } } return /*#__PURE__*/React.createElement(PickerToggleTrigger, { as: as, id: id, pickerType: "check-tree", block: block, disabled: disabled, appearance: appearance, popupType: "tree", multiple: true, triggerProps: { ...pick(props, triggerPropKeys), ...triggerProps }, ref: trigger, placement: placement, speaker: renderTreeView, rootRef: root, style: style, classPrefix: classPrefix, className: className }, /*#__PURE__*/React.createElement(PickerToggle, _extends({ ref: target, appearance: appearance, onKeyDown: onPickerKeydown, onClean: createChainedFunction(handleClean, onClean), cleanable: cleanable && !disabled, countable: countable, disabled: disabled, as: toggleAs, hasValue: hasValidValue, active: active, placement: placement, inputValue: value, focusItemValue: focusItemValue, size: size }, rest), selectedElement || (locale === null || locale === void 0 ? void 0 : locale.placeholder))); }); CheckTreePicker.displayName = 'CheckTreePicker'; export default CheckTreePicker;