UNPKG

bonree-cascader

Version:

cascade select ui component for react

301 lines (265 loc) 11.8 kB
import _extends from "@babel/runtime/helpers/esm/extends"; import _objectSpread from "@babel/runtime/helpers/esm/objectSpread2"; import _slicedToArray from "@babel/runtime/helpers/esm/slicedToArray"; import _objectWithoutProperties from "@babel/runtime/helpers/esm/objectWithoutProperties"; var _excluded = ["checkable", "changeOnSelect", "children", "options", "onChange", "value", "defaultValue", "popupVisible", "open", "dropdownClassName", "popupClassName", "onDropdownVisibleChange", "onPopupVisibleChange", "popupPlacement", "placement", "searchValue", "onSearch", "showSearch", "expandTrigger", "expandIcon", "loadingIcon", "displayRender", "loadData", "dropdownMenuColumnStyle", "dropdownPrefixCls", "dropdownMatchSelectWidth", "requestFailureIcon", "requestFailureText", "refreshText", "empty"]; import * as React from 'react'; import warning from "rc-util/es/warning"; import useMergedState from "rc-util/es/hooks/useMergedState"; import generate from "bonree-tree-select/es/generate"; import OptionList from './OptionList'; import CascaderContext from './context'; import { connectValue, convertOptions, fillFieldNames, restoreCompatibleValue, splitValue } from './util'; import useUpdateEffect from './hooks/useUpdateEffect'; import useSearchConfig from './hooks/useSearchConfig'; var INTERNAL_VALUE_FIELD = '__rc_cascader_value__'; /** * `rc-cascader` is much like `bonree-tree-select` but API is very different. * It's caused that component developer is not same person * and we do not rice the API naming standard at that time. * * To avoid breaking change, wrap the `bonree-tree-select` to compatible with `rc-cascader` API. * This should be better to merge to same API like `bonree-tree-select` or `bonree-select` in next major version. * * Update: * - dropdown class change to `rc-cascader-dropdown` * - direction rtl keyboard * * Deprecated: * - popupVisible * - hidePopupOnSelect * * Removed: * - builtinPlacements: Handle by select */ var RefCascader = generate({ prefixCls: 'rc-cascader', optionList: OptionList }); function defaultDisplayRender(labels) { return labels.join(' / '); } export var LOAD_STATUS; (function (LOAD_STATUS) { LOAD_STATUS["LOADING"] = "RC_LOADING_RC"; LOAD_STATUS["EMPTY"] = "RC_EMPTY_RC"; LOAD_STATUS["FAILED"] = "RC_LOADED_FAILED_RC"; })(LOAD_STATUS || (LOAD_STATUS = {})); var Cascader = /*#__PURE__*/React.forwardRef(function (props, ref) { var checkable = props.checkable, changeOnSelect = props.changeOnSelect, children = props.children, options = props.options, onChange = props.onChange, value = props.value, defaultValue = props.defaultValue, popupVisible = props.popupVisible, open = props.open, dropdownClassName = props.dropdownClassName, popupClassName = props.popupClassName, onDropdownVisibleChange = props.onDropdownVisibleChange, onPopupVisibleChange = props.onPopupVisibleChange, popupPlacement = props.popupPlacement, placement = props.placement, searchValue = props.searchValue, onSearch = props.onSearch, showSearch = props.showSearch, expandTrigger = props.expandTrigger, _props$expandIcon = props.expandIcon, expandIcon = _props$expandIcon === void 0 ? '>' : _props$expandIcon, loadingIcon = props.loadingIcon, _props$displayRender = props.displayRender, displayRender = _props$displayRender === void 0 ? defaultDisplayRender : _props$displayRender, loadData = props.loadData, dropdownMenuColumnStyle = props.dropdownMenuColumnStyle, dropdownPrefixCls = props.dropdownPrefixCls, _props$dropdownMatchS = props.dropdownMatchSelectWidth, dropdownMatchSelectWidth = _props$dropdownMatchS === void 0 ? false : _props$dropdownMatchS, _props$requestFailure = props.requestFailureIcon, requestFailureIcon = _props$requestFailure === void 0 ? null : _props$requestFailure, requestFailureText = props.requestFailureText, refreshText = props.refreshText, empty = props.empty, restProps = _objectWithoutProperties(props, _excluded); var fieldNames = restProps.fieldNames; // ============================ Ref ============================= var cascaderRef = React.useRef(); React.useImperativeHandle(ref, function () { return { focus: function focus() { cascaderRef.current.focus(); }, blur: function blur() { cascaderRef.current.blur(); } }; }); var getEntityByValue = function getEntityByValue(val) { return cascaderRef.current.getEntityByValue(val); }; // =========================== Search =========================== var _useMergedState = useMergedState(undefined, { value: searchValue, onChange: onSearch }), _useMergedState2 = _slicedToArray(_useMergedState, 2), mergedSearch = _useMergedState2[0], setMergedSearch = _useMergedState2[1]; var _useSearchConfig = useSearchConfig(showSearch), _useSearchConfig2 = _slicedToArray(_useSearchConfig, 2), mergedShowSearch = _useSearchConfig2[0], searchConfig = _useSearchConfig2[1]; // ========================== Options =========================== var outerFieldNames = React.useMemo(function () { return fillFieldNames(fieldNames); }, [fieldNames]); var mergedFieldNames = React.useMemo(function () { return _objectSpread(_objectSpread({}, outerFieldNames), {}, { value: INTERNAL_VALUE_FIELD }); }, [outerFieldNames]); var mergedOptions = React.useMemo(function () { return convertOptions(options, outerFieldNames, INTERNAL_VALUE_FIELD); }, [options, outerFieldNames]); // =========================== Value ============================ /** * Always pass props value to last value unit: * - single: ['light', 'little'] => ['light__little'] * - multiple: [['light', 'little'], ['bamboo']] => ['light__little', 'bamboo'] */ var parseToInternalValue = function parseToInternalValue(propValue) { var propValueList = []; if (propValue) { propValueList = checkable ? propValue : [propValue]; } return propValueList.map(connectValue); }; var _React$useState = React.useState(function () { return parseToInternalValue(value || defaultValue); }), _React$useState2 = _slicedToArray(_React$useState, 2), internalValue = _React$useState2[0], setInternalValue = _React$useState2[1]; useUpdateEffect(function () { setInternalValue(parseToInternalValue(value)); }, [value]); // =========================== Label ============================ var labelRender = function labelRender(entity, val) { var fieldLabel = mergedFieldNames.label; if (!entity) { var valPath = splitValue(val); return displayRender(valPath, []); } if (checkable) { return entity.data.node[fieldLabel]; } var _restoreCompatibleVal = restoreCompatibleValue(entity, mergedFieldNames), selectedOptions = _restoreCompatibleVal.options; var rawOptions = selectedOptions.map(function (opt) { return opt.node; }); var labelList = rawOptions.map(function (opt) { return opt[fieldLabel]; }); return displayRender(labelList, rawOptions); }; // =========================== Change =========================== var onInternalChange = function onInternalChange(newValue /** Not care current type */ ) { // TODO: Need improve motion experience setMergedSearch(''); var valueList = checkable ? newValue : [newValue]; var pathList = []; var optionsList = []; var valueEntities = valueList.map(getEntityByValue).filter(function (entity) { return entity; }); valueEntities.forEach(function (entity) { var _restoreCompatibleVal2 = restoreCompatibleValue(entity, mergedFieldNames), valueOptions = _restoreCompatibleVal2.options; var originOptions = valueOptions.map(function (option) { return option.node; }); pathList.push(originOptions.map(function (opt) { return (// Here we should use original FieldNames value mapping opt[outerFieldNames.value] ); })); optionsList.push(originOptions); }); // Fill state if (value === undefined) { setInternalValue(valueList); } if (onChange) { if (checkable) { onChange(pathList, optionsList); } else { // TODO: This should return null as other component. // But its a breaking change and we should keep the logic. onChange(pathList[0] || [], optionsList[0] || []); } } }; // ============================ Open ============================ if (process.env.NODE_ENV !== 'production') { warning(!onPopupVisibleChange, '`onPopupVisibleChange` is deprecated. Please use `onDropdownVisibleChange` instead.'); warning(popupVisible === undefined, '`popupVisible` is deprecated. Please use `open` instead.'); warning(popupClassName === undefined, '`popupClassName` is deprecated. Please use `dropdownClassName` instead.'); warning(popupPlacement === undefined, '`popupPlacement` is deprecated. Please use `placement` instead.'); } var mergedOpen = open !== undefined ? open : popupVisible; var mergedDropdownClassName = dropdownClassName || popupClassName; var mergedPlacement = placement || popupPlacement; var onInternalDropdownVisibleChange = function onInternalDropdownVisibleChange(nextVisible) { onDropdownVisibleChange === null || onDropdownVisibleChange === void 0 ? void 0 : onDropdownVisibleChange(nextVisible); onPopupVisibleChange === null || onPopupVisibleChange === void 0 ? void 0 : onPopupVisibleChange(nextVisible); }; // ========================== Context =========================== var context = React.useMemo(function () { return { changeOnSelect: changeOnSelect, expandTrigger: expandTrigger, fieldNames: mergedFieldNames, expandIcon: expandIcon, loadingIcon: loadingIcon, loadData: loadData, dropdownMenuColumnStyle: dropdownMenuColumnStyle, search: searchConfig, dropdownPrefixCls: dropdownPrefixCls, requestFailureIcon: requestFailureIcon, requestFailureText: requestFailureText, refreshText: refreshText, empty: empty }; }, [changeOnSelect, expandTrigger, mergedFieldNames, expandIcon, loadingIcon, loadData, dropdownMenuColumnStyle, searchConfig, dropdownPrefixCls]); // =========================== Render =========================== var dropdownStyle = // Search to match width mergedSearch && searchConfig.matchInputWidth || // Empty keep the width !mergedOptions.length ? {} : { minWidth: 'auto' }; return /*#__PURE__*/React.createElement(CascaderContext.Provider, { value: context }, /*#__PURE__*/React.createElement(RefCascader, _extends({ ref: cascaderRef }, restProps, { fieldNames: mergedFieldNames, value: checkable ? internalValue : internalValue[0], placement: mergedPlacement, dropdownMatchSelectWidth: dropdownMatchSelectWidth, dropdownStyle: dropdownStyle, dropdownClassName: mergedDropdownClassName, treeData: mergedOptions, treeCheckable: checkable, treeNodeFilterProp: "label", onChange: onInternalChange, showCheckedStrategy: RefCascader.SHOW_PARENT, open: mergedOpen, onDropdownVisibleChange: onInternalDropdownVisibleChange, searchValue: mergedSearch // Customize filter logic in OptionList , filterTreeNode: function filterTreeNode() { return true; }, showSearch: mergedShowSearch, onSearch: setMergedSearch, labelRender: labelRender, getRawInputElement: function getRawInputElement() { return children; } }))); }); Cascader.displayName = 'Cascader'; export default Cascader;