bonree-cascader
Version:
cascade select ui component for react
301 lines (265 loc) • 11.8 kB
JavaScript
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;