UNPKG

rsuite

Version:

A suite of react components

552 lines (470 loc) 20.7 kB
"use strict"; var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard"); var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); exports.__esModule = true; exports.default = void 0; var _extends3 = _interopRequireDefault(require("@babel/runtime/helpers/extends")); var _objectWithoutPropertiesLoose2 = _interopRequireDefault(require("@babel/runtime/helpers/objectWithoutPropertiesLoose")); var _react = _interopRequireWildcard(require("react")); var _propTypes = _interopRequireDefault(require("prop-types")); var _omit = _interopRequireDefault(require("lodash/omit")); var _pick = _interopRequireDefault(require("lodash/pick")); var _isNil = _interopRequireDefault(require("lodash/isNil")); var _isFunction = _interopRequireDefault(require("lodash/isFunction")); var _shallowEqual = _interopRequireDefault(require("../utils/shallowEqual")); var _DropdownMenu = _interopRequireDefault(require("./DropdownMenu")); var _treeUtils = require("../utils/treeUtils"); var _utils = require("./utils"); var _utils2 = require("../utils"); var _Picker = require("../Picker"); var emptyArray = []; var Cascader = /*#__PURE__*/_react.default.forwardRef(function (props, ref) { var _props$as = props.as, Component = _props$as === void 0 ? 'div' : _props$as, _props$data = props.data, data = _props$data === void 0 ? emptyArray : _props$data, _props$classPrefix = props.classPrefix, classPrefix = _props$classPrefix === void 0 ? 'picker' : _props$classPrefix, _props$childrenKey = props.childrenKey, childrenKey = _props$childrenKey === void 0 ? 'children' : _props$childrenKey, _props$valueKey = props.valueKey, valueKey = _props$valueKey === void 0 ? 'value' : _props$valueKey, _props$labelKey = props.labelKey, labelKey = _props$labelKey === void 0 ? 'label' : _props$labelKey, defaultValue = props.defaultValue, placeholder = props.placeholder, disabled = props.disabled, _props$disabledItemVa = props.disabledItemValues, disabledItemValues = _props$disabledItemVa === void 0 ? emptyArray : _props$disabledItemVa, _props$appearance = props.appearance, appearance = _props$appearance === void 0 ? 'default' : _props$appearance, _props$cleanable = props.cleanable, cleanable = _props$cleanable === void 0 ? true : _props$cleanable, overrideLocale = props.locale, toggleAs = props.toggleAs, style = props.style, valueProp = props.value, inline = props.inline, menuClassName = props.menuClassName, menuStyle = props.menuStyle, menuWidth = props.menuWidth, menuHeight = props.menuHeight, _props$searchable = props.searchable, searchable = _props$searchable === void 0 ? true : _props$searchable, parentSelectable = props.parentSelectable, _props$placement = props.placement, placement = _props$placement === void 0 ? 'bottomStart' : _props$placement, id = props.id, renderMenuItem = props.renderMenuItem, renderSearchItem = props.renderSearchItem, renderValue = props.renderValue, renderMenu = props.renderMenu, renderExtraFooter = props.renderExtraFooter, onEnter = props.onEnter, onExited = props.onExited, onClean = props.onClean, onChange = props.onChange, onSelect = props.onSelect, onSearch = props.onSearch, onClose = props.onClose, onOpen = props.onOpen, getChildren = props.getChildren, rest = (0, _objectWithoutPropertiesLoose2.default)(props, ["as", "data", "classPrefix", "childrenKey", "valueKey", "labelKey", "defaultValue", "placeholder", "disabled", "disabledItemValues", "appearance", "cleanable", "locale", "toggleAs", "style", "value", "inline", "menuClassName", "menuStyle", "menuWidth", "menuHeight", "searchable", "parentSelectable", "placement", "id", "renderMenuItem", "renderSearchItem", "renderValue", "renderMenu", "renderExtraFooter", "onEnter", "onExited", "onClean", "onChange", "onSelect", "onSearch", "onClose", "onOpen", "getChildren"]); // Use component active state to support keyboard events. var _useState = (0, _react.useState)(false), active = _useState[0], setActive = _useState[1]; var _useState2 = (0, _react.useState)((0, _treeUtils.flattenTree)(data, childrenKey)), flattenData = _useState2[0], setFlattenData = _useState2[1]; var triggerRef = (0, _react.useRef)(null); var overlayRef = (0, _react.useRef)(null); var targetRef = (0, _react.useRef)(null); var searchInputRef = (0, _react.useRef)(null); var _ref = (0, _utils2.useControlled)(valueProp, defaultValue), value = _ref[0], setValue = _ref[1]; var _usePaths = (0, _utils.usePaths)({ data: data, valueKey: valueKey, childrenKey: childrenKey, value: value }), selectedPaths = _usePaths.selectedPaths, valueToPaths = _usePaths.valueToPaths, columnData = _usePaths.columnData, addColumn = _usePaths.addColumn, romoveColumnByIndex = _usePaths.romoveColumnByIndex, setValueToPaths = _usePaths.setValueToPaths, setColumnData = _usePaths.setColumnData, setSelectedPaths = _usePaths.setSelectedPaths, enforceUpdate = _usePaths.enforceUpdate; (0, _react.useEffect)(function () { setFlattenData((0, _treeUtils.flattenTree)(data, childrenKey)); }, [data, childrenKey]); (0, _Picker.usePublicMethods)(ref, { triggerRef: triggerRef, overlayRef: overlayRef, targetRef: targetRef }); var _useCustom = (0, _utils2.useCustom)('Picker', overrideLocale), locale = _useCustom.locale, rtl = _useCustom.rtl; /** * 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. */ var hasValue = valueToPaths.length > 0 || !(0, _isNil.default)(value) && (0, _isFunction.default)(renderValue); var _useClassNames = (0, _utils2.useClassNames)(classPrefix), prefix = _useClassNames.prefix, merge = _useClassNames.merge; var _useState3 = (0, _react.useState)(''), searchKeyword = _useState3[0], setSearchKeyword = _useState3[1]; var someKeyword = (0, _react.useCallback)(function (item, keyword) { if (item[labelKey].match(new RegExp((0, _utils2.getSafeRegExpString)(keyword || searchKeyword), 'i'))) { return true; } if (item.parent && someKeyword(item.parent)) { return true; } return false; }, [labelKey, searchKeyword]); var getSearchResult = (0, _react.useCallback)(function (keyword) { var items = []; var result = flattenData.filter(function (item) { if (!parentSelectable && item[childrenKey]) { return false; } return someKeyword(item, keyword); }); for (var i = 0; i < result.length; i++) { items.push(result[i]); // A maximum of 100 search results are returned. if (i === 99) { return items; } } return items; }, [childrenKey, flattenData, someKeyword, parentSelectable]); // Used to hover the focuse item when trigger `onKeydown` var _useFocusItemValue = (0, _Picker.useFocusItemValue)(value, { rtl: rtl, data: flattenData, valueKey: valueKey, defaultLayer: valueToPaths !== null && valueToPaths !== void 0 && valueToPaths.length ? valueToPaths.length - 1 : 0, target: function target() { return overlayRef.current; }, callback: (0, _react.useCallback)(function (value) { enforceUpdate(value, true); }, [enforceUpdate]) }), focusItemValue = _useFocusItemValue.focusItemValue, setFocusItemValue = _useFocusItemValue.setFocusItemValue, setLayer = _useFocusItemValue.setLayer, setKeys = _useFocusItemValue.setKeys, onFocusItem = _useFocusItemValue.onKeyDown; var handleSearch = (0, _react.useCallback)(function (value, event) { var items = getSearchResult(value); setSearchKeyword(value); onSearch === null || onSearch === void 0 ? void 0 : onSearch(value, event); if (items.length > 0) { setFocusItemValue(items[0][valueKey]); setLayer(0); setKeys([]); } }, [getSearchResult, onSearch, setFocusItemValue, setKeys, setLayer, valueKey]); var handleEntered = (0, _react.useCallback)(function () { if (!targetRef.current) { return; } onOpen === null || onOpen === void 0 ? void 0 : onOpen(); setActive(true); }, [onOpen]); var handleExited = (0, _react.useCallback)(function () { if (!targetRef.current) { return; } onClose === null || onClose === void 0 ? void 0 : onClose(); setActive(false); setSearchKeyword(''); }, [onClose]); var handleClose = (0, _react.useCallback)(function () { var _triggerRef$current; (_triggerRef$current = triggerRef.current) === null || _triggerRef$current === void 0 ? void 0 : _triggerRef$current.close(); }, [triggerRef]); var handleClean = (0, _react.useCallback)(function (event) { if (disabled || !targetRef.current) { return; } setColumnData([data]); setValue(null); setSelectedPaths([]); setValueToPaths([]); onChange === null || onChange === void 0 ? void 0 : onChange(null, event); }, [data, disabled, onChange, setSelectedPaths, setColumnData, setValueToPaths, setValue]); var handleMenuPressEnter = (0, _react.useCallback)(function (event) { var focusItem = (0, _treeUtils.findNodeOfTree)(data, function (item) { return item[valueKey] === focusItemValue; }); var isLeafNode = focusItem && !focusItem[childrenKey]; if (isLeafNode) { setValue(focusItemValue); setValueToPaths(selectedPaths); if (selectedPaths.length) { setLayer(selectedPaths.length - 1); } if (!(0, _shallowEqual.default)(value, focusItemValue)) { onChange === null || onChange === void 0 ? void 0 : onChange(focusItemValue !== null && focusItemValue !== void 0 ? focusItemValue : null, event); } handleClose(); } }, [childrenKey, data, focusItemValue, handleClose, onChange, selectedPaths, setLayer, setValue, setValueToPaths, value, valueKey]); var onPickerKeyDown = (0, _Picker.useToggleKeyDownEvent)((0, _extends3.default)({ toggle: !focusItemValue || !active, triggerRef: triggerRef, targetRef: targetRef, overlayRef: overlayRef, searchInputRef: searchInputRef, active: active, onExit: handleClean, onMenuKeyDown: onFocusItem, onMenuPressEnter: handleMenuPressEnter }, rest)); var handleSelect = function handleSelect(node, cascadePaths, isLeafNode, event) { var _node$childrenKey, _node$childrenKey2, _triggerRef$current2; onSelect === null || onSelect === void 0 ? void 0 : onSelect(node, cascadePaths, event); setSelectedPaths(cascadePaths); var nextValue = node[valueKey]; var columnIndex = cascadePaths.length; // Lazy load node's children if (typeof getChildren === 'function' && ((_node$childrenKey = node[childrenKey]) === null || _node$childrenKey === void 0 ? void 0 : _node$childrenKey.length) === 0) { node.loading = true; var children = getChildren(node); if (children instanceof Promise) { children.then(function (data) { node.loading = false; node[childrenKey] = data; if (targetRef.current) { addColumn(data, columnIndex); } }); } else { node.loading = false; node[childrenKey] = children; addColumn(children, columnIndex); } } else if ((_node$childrenKey2 = node[childrenKey]) !== null && _node$childrenKey2 !== void 0 && _node$childrenKey2.length) { addColumn(node[childrenKey], columnIndex); } else { // Removes subsequent columns of the current column when the clicked node is a leaf node. romoveColumnByIndex(columnIndex); } if (isLeafNode) { // Determines whether the option is a leaf node, and if so, closes the picker. handleClose(); // Update the selected path to the value path. // That is, the selected path will be displayed on the button after clicking the child node. setValueToPaths(cascadePaths); setValue(nextValue); if (!(0, _shallowEqual.default)(value, nextValue)) { onChange === null || onChange === void 0 ? void 0 : onChange(nextValue, event); } return; } /** When the parent is optional, the value and the displayed path are updated. */ if (parentSelectable && !(0, _shallowEqual.default)(value, nextValue)) { setValue(nextValue); onChange === null || onChange === void 0 ? void 0 : onChange(nextValue, event); setValueToPaths(cascadePaths); } // Update menu position (_triggerRef$current2 = triggerRef.current) === null || _triggerRef$current2 === void 0 ? void 0 : _triggerRef$current2.updatePosition(); }; /** * The search structure option is processed after being selected. */ var handleSearchRowSelect = function handleSearchRowSelect(node, nodes, event) { var nextValue = node[valueKey]; handleClose(); setSearchKeyword(''); setValue(nextValue); setValueToPaths(nodes); enforceUpdate(nextValue); onSelect === null || onSelect === void 0 ? void 0 : onSelect(node, nodes, event); onChange === null || onChange === void 0 ? void 0 : onChange(nextValue, event); }; var renderSearchRow = function renderSearchRow(item, key) { var regx = new RegExp((0, _utils2.getSafeRegExpString)(searchKeyword), 'ig'); var nodes = (0, _treeUtils.getNodeParents)(item); nodes.push(item); var formattedNodes = nodes.map(function (node) { var _extends2; var labelElements = []; var a = node[labelKey].split(regx); var b = node[labelKey].match(regx); for (var i = 0; i < a.length; i++) { labelElements.push(a[i]); if (b && b[i]) { labelElements.push( /*#__PURE__*/_react.default.createElement("span", { key: i, className: prefix('cascader-search-match') }, b[i])); } } return (0, _extends3.default)({}, node, (_extends2 = {}, _extends2[labelKey] = labelElements, _extends2)); }); var disabled = disabledItemValues.some(function (value) { return formattedNodes.some(function (node) { return node[valueKey] === value; }); }); var itemClasses = prefix('cascader-row', { 'cascader-row-disabled': disabled, 'cascader-row-focus': item[valueKey] === focusItemValue }); var label = formattedNodes.map(function (node, index) { return /*#__PURE__*/_react.default.createElement("span", { key: "col-" + index, className: prefix('cascader-col') }, node[labelKey]); }); return /*#__PURE__*/_react.default.createElement("div", { key: key, "aria-disabled": disabled, "data-key": item[valueKey], className: itemClasses, onClick: function onClick(event) { if (!disabled) { handleSearchRowSelect(item, nodes, event); } } }, renderSearchItem ? renderSearchItem(label, nodes) : label); }; var renderSearchResultPanel = function renderSearchResultPanel() { if (searchKeyword === '') { return null; } var items = getSearchResult(); return /*#__PURE__*/_react.default.createElement("div", { className: prefix('cascader-search-panel'), "data-layer": 0 }, items.length ? items.map(renderSearchRow) : /*#__PURE__*/_react.default.createElement("div", { className: prefix('none') }, locale.noResultsText)); }; var renderDropdownMenu = function renderDropdownMenu(positionProps, speakerRef) { var _ref2 = positionProps || {}, left = _ref2.left, top = _ref2.top, className = _ref2.className; var styles = (0, _extends3.default)({}, menuStyle, { left: left, top: top }); var classes = merge(className, menuClassName, prefix('cascader-menu', { inline: inline })); return /*#__PURE__*/_react.default.createElement(_Picker.PickerOverlay, { ref: (0, _utils2.mergeRefs)(overlayRef, speakerRef), className: classes, style: styles, target: triggerRef, onKeyDown: onPickerKeyDown }, searchable && /*#__PURE__*/_react.default.createElement(_Picker.SearchBar, { placeholder: locale === null || locale === void 0 ? void 0 : locale.searchPlaceholder, onChange: handleSearch, value: searchKeyword, inputRef: searchInputRef }), renderSearchResultPanel(), searchKeyword === '' && /*#__PURE__*/_react.default.createElement(_DropdownMenu.default, { id: id ? id + "-listbox" : undefined, menuWidth: menuWidth, menuHeight: menuHeight, disabledItemValues: disabledItemValues, valueKey: valueKey, labelKey: labelKey, childrenKey: childrenKey, classPrefix: 'picker-cascader-menu', cascadeData: columnData, cascadePaths: selectedPaths, activeItemValue: value // FIXME make onSelect generic , onSelect: handleSelect, renderMenu: renderMenu, renderMenuItem: renderMenuItem }), renderExtraFooter === null || renderExtraFooter === void 0 ? void 0 : renderExtraFooter()); }; var selectedElement = placeholder; if (valueToPaths.length > 0) { selectedElement = []; valueToPaths.forEach(function (item, index) { var key = item[valueKey] || item[labelKey]; selectedElement.push( /*#__PURE__*/_react.default.createElement("span", { key: key }, item[labelKey])); if (index < valueToPaths.length - 1) { selectedElement.push( /*#__PURE__*/_react.default.createElement("span", { className: "separator", key: key + "-separator" }, ' / ')); } }); } if (!(0, _isNil.default)(value) && (0, _isFunction.default)(renderValue)) { selectedElement = renderValue(value, valueToPaths, selectedElement); // If renderValue returns null or undefined, hasValue is false. if ((0, _isNil.default)(selectedElement)) { hasValue = false; } } var _usePickerClassName = (0, _Picker.usePickerClassName)((0, _extends3.default)({}, props, { classPrefix: classPrefix, hasValue: hasValue, name: 'cascader', appearance: appearance, cleanable: cleanable })), classes = _usePickerClassName[0], usedClassNamePropKeys = _usePickerClassName[1]; // TODO: bad api design // consider an isolated Menu component if (inline) { return renderDropdownMenu(); } return /*#__PURE__*/_react.default.createElement(_Picker.PickerToggleTrigger, { pickerProps: (0, _pick.default)(props, _Picker.pickTriggerPropKeys), ref: triggerRef, placement: placement, onEntered: (0, _utils2.createChainedFunction)(handleEntered, onEnter), onExited: (0, _utils2.createChainedFunction)(handleExited, onExited), speaker: renderDropdownMenu }, /*#__PURE__*/_react.default.createElement(Component, { className: classes, style: style }, /*#__PURE__*/_react.default.createElement(_Picker.PickerToggle, (0, _extends3.default)({}, (0, _omit.default)(rest, [].concat(_Picker.omitTriggerPropKeys, usedClassNamePropKeys)), { id: id, ref: targetRef, as: toggleAs, appearance: appearance, disabled: disabled, onClean: (0, _utils2.createChainedFunction)(handleClean, onClean), onKeyDown: onPickerKeyDown, cleanable: cleanable && !disabled, hasValue: hasValue, active: active, placement: placement, inputValue: value !== null && value !== void 0 ? value : '' }), selectedElement || (locale === null || locale === void 0 ? void 0 : locale.placeholder)))); }); Cascader.displayName = 'Cascader'; Cascader.propTypes = (0, _extends3.default)({}, _Picker.listPickerPropTypes, { disabledItemValues: _propTypes.default.array, locale: _propTypes.default.any, appearance: _propTypes.default.oneOf(['default', 'subtle']), renderMenu: _propTypes.default.func, onSelect: _propTypes.default.func, onSearch: _propTypes.default.func, cleanable: _propTypes.default.bool, renderMenuItem: _propTypes.default.func, renderSearchItem: _propTypes.default.func, menuWidth: _propTypes.default.number, menuHeight: _propTypes.default.oneOfType([_propTypes.default.number, _propTypes.default.string]), searchable: _propTypes.default.bool, inline: _propTypes.default.bool, parentSelectable: _propTypes.default.bool }); var _default = Cascader; exports.default = _default;