rsuite
Version:
A suite of react components
401 lines (359 loc) • 15.6 kB
JavaScript
import _taggedTemplateLiteralLoose from "@babel/runtime/helpers/esm/taggedTemplateLiteralLoose";
import _extends from "@babel/runtime/helpers/esm/extends";
import _objectWithoutPropertiesLoose from "@babel/runtime/helpers/esm/objectWithoutPropertiesLoose";
var _templateObject;
import React, { useRef, useState, useCallback } from 'react';
import PropTypes from 'prop-types';
import clone from 'lodash/clone';
import isUndefined from 'lodash/isUndefined';
import isFunction from 'lodash/isFunction';
import remove from 'lodash/remove';
import omit from 'lodash/omit';
import pick from 'lodash/pick';
import isNil from 'lodash/isNil';
import { filterNodesOfTree } from '../utils/treeUtils';
import { createChainedFunction, getDataGroupBy, useClassNames, shallowEqual, useCustom, useControlled, mergeRefs } from '../utils';
import { DropdownMenu, DropdownMenuCheckItem as DropdownMenuItem, PickerToggle, PickerOverlay, SearchBar, SelectedElement, PickerToggleTrigger, useFocusItemValue, usePickerClassName, useSearch, usePublicMethods, useToggleKeyDownEvent, pickTriggerPropKeys, omitTriggerPropKeys, listPickerPropTypes } from '../Picker';
var emptyArray = [];
var CheckPicker = /*#__PURE__*/React.forwardRef(function (props, ref) {
var _props$as = props.as,
Component = _props$as === void 0 ? 'div' : _props$as,
_props$appearance = props.appearance,
appearance = _props$appearance === void 0 ? 'default' : _props$appearance,
_props$classPrefix = props.classPrefix,
classPrefix = _props$classPrefix === void 0 ? 'picker' : _props$classPrefix,
_props$countable = props.countable,
countable = _props$countable === void 0 ? true : _props$countable,
_props$data = props.data,
data = _props$data === void 0 ? emptyArray : _props$data,
_props$disabledItemVa = props.disabledItemValues,
disabledItemValues = _props$disabledItemVa === void 0 ? emptyArray : _props$disabledItemVa,
_props$valueKey = props.valueKey,
valueKey = _props$valueKey === void 0 ? 'value' : _props$valueKey,
_props$labelKey = props.labelKey,
labelKey = _props$labelKey === void 0 ? 'label' : _props$labelKey,
_props$searchable = props.searchable,
searchable = _props$searchable === void 0 ? true : _props$searchable,
virtualized = props.virtualized,
_props$cleanable = props.cleanable,
cleanable = _props$cleanable === void 0 ? true : _props$cleanable,
_props$placement = props.placement,
placement = _props$placement === void 0 ? 'bottomStart' : _props$placement,
_props$menuAutoWidth = props.menuAutoWidth,
menuAutoWidth = _props$menuAutoWidth === void 0 ? true : _props$menuAutoWidth,
_props$menuMaxHeight = props.menuMaxHeight,
menuMaxHeight = _props$menuMaxHeight === void 0 ? 320 : _props$menuMaxHeight,
menuClassName = props.menuClassName,
menuStyle = props.menuStyle,
overrideLocale = props.locale,
placeholder = props.placeholder,
disabled = props.disabled,
toggleAs = props.toggleAs,
style = props.style,
sticky = props.sticky,
valueProp = props.value,
defaultValue = props.defaultValue,
groupBy = props.groupBy,
listProps = props.listProps,
id = props.id,
sort = props.sort,
searchBy = props.searchBy,
renderMenuItem = props.renderMenuItem,
renderMenuGroup = props.renderMenuGroup,
renderValue = props.renderValue,
renderExtraFooter = props.renderExtraFooter,
renderMenu = props.renderMenu,
onGroupTitleClick = props.onGroupTitleClick,
onSearch = props.onSearch,
onEnter = props.onEnter,
onEntered = props.onEntered,
onExited = props.onExited,
onClean = props.onClean,
onChange = props.onChange,
onSelect = props.onSelect,
onClose = props.onClose,
onOpen = props.onOpen,
rest = _objectWithoutPropertiesLoose(props, ["as", "appearance", "classPrefix", "countable", "data", "disabledItemValues", "valueKey", "labelKey", "searchable", "virtualized", "cleanable", "placement", "menuAutoWidth", "menuMaxHeight", "menuClassName", "menuStyle", "locale", "placeholder", "disabled", "toggleAs", "style", "sticky", "value", "defaultValue", "groupBy", "listProps", "id", "sort", "searchBy", "renderMenuItem", "renderMenuGroup", "renderValue", "renderExtraFooter", "renderMenu", "onGroupTitleClick", "onSearch", "onEnter", "onEntered", "onExited", "onClean", "onChange", "onSelect", "onClose", "onOpen"]);
var triggerRef = useRef(null);
var targetRef = useRef(null);
var overlayRef = useRef(null);
var searchInputRef = useRef(null);
var _useCustom = useCustom('Picker', overrideLocale),
locale = _useCustom.locale;
var _useControlled = useControlled(valueProp, defaultValue || []),
value = _useControlled[0],
setValue = _useControlled[1]; // Used to hover the focuse item when trigger `onKeydown`
var _useFocusItemValue = useFocusItemValue(value === null || value === void 0 ? void 0 : value[0], {
data: data,
valueKey: valueKey,
target: function target() {
return overlayRef.current;
}
}),
focusItemValue = _useFocusItemValue.focusItemValue,
setFocusItemValue = _useFocusItemValue.setFocusItemValue,
onFocusItem = _useFocusItemValue.onKeyDown;
var handleSearchCallback = useCallback(function (searchKeyword, filteredData, event) {
var _filteredData$;
// The first option after filtering is the focus.
setFocusItemValue(filteredData === null || filteredData === void 0 ? void 0 : (_filteredData$ = filteredData[0]) === null || _filteredData$ === void 0 ? void 0 : _filteredData$[valueKey]);
onSearch === null || onSearch === void 0 ? void 0 : onSearch(searchKeyword, event);
}, [setFocusItemValue, onSearch, valueKey]); // Use search keywords to filter options.
var _useSearch = useSearch({
labelKey: labelKey,
data: data,
searchBy: searchBy,
callback: handleSearchCallback
}),
searchKeyword = _useSearch.searchKeyword,
filteredData = _useSearch.filteredData,
setSearchKeyword = _useSearch.setSearchKeyword,
handleSearch = _useSearch.handleSearch,
checkShouldDisplay = _useSearch.checkShouldDisplay; // Use component active state to support keyboard events.
var _useState = useState(false),
active = _useState[0],
setActive = _useState[1]; // A list of shortcut options.
// when opened again, the selected options are displayed at the top.
var _useState2 = useState([]),
stickyItems = _useState2[0],
setStickyItems = _useState2[1];
var initStickyItems = function initStickyItems() {
if (!sticky) {
return;
}
var nextStickyItems = [];
if (data && value.length) {
nextStickyItems = data.filter(function (item) {
return value.some(function (v) {
return v === item[valueKey];
});
});
}
setStickyItems(nextStickyItems);
};
var handleChangeValue = useCallback(function (value, event) {
onChange === null || onChange === void 0 ? void 0 : onChange(value, event);
}, [onChange]);
var handleClean = useCallback(function (event) {
if (disabled || !cleanable) {
return;
}
setValue([]);
onClean === null || onClean === void 0 ? void 0 : onClean(event);
handleChangeValue([], event);
}, [disabled, cleanable, setValue, onClean, handleChangeValue]);
var handleMenuPressEnter = function handleMenuPressEnter(event) {
var nextValue = clone(value);
if (!focusItemValue) {
return;
}
if (!nextValue.some(function (v) {
return shallowEqual(v, focusItemValue);
})) {
nextValue.push(focusItemValue);
} else {
remove(nextValue, function (itemVal) {
return shallowEqual(itemVal, focusItemValue);
});
}
var focusItem = data.find(function (item) {
return shallowEqual(item === null || item === void 0 ? void 0 : item[valueKey], focusItemValue);
});
setValue(nextValue);
handleSelect(nextValue, focusItem, event);
handleChangeValue(nextValue, event);
};
var onPickerKeyDown = useToggleKeyDownEvent(_extends({
toggle: !focusItemValue || !active,
triggerRef: triggerRef,
targetRef: targetRef,
overlayRef: overlayRef,
searchInputRef: searchInputRef,
active: active,
onExit: handleClean,
onMenuKeyDown: onFocusItem,
onMenuPressEnter: handleMenuPressEnter,
onMenuPressBackspace: handleClean,
onClose: function onClose() {
setFocusItemValue(null);
}
}, rest));
var handleSelect = useCallback(function (nextItemValue, item, event) {
onSelect === null || onSelect === void 0 ? void 0 : onSelect(nextItemValue, item, event);
}, [onSelect]);
var handleItemSelect = useCallback(function (nextItemValue, item, event, checked) {
var nextValue = clone(value);
if (checked) {
nextValue.push(nextItemValue);
} else {
remove(nextValue, function (itemVal) {
return shallowEqual(itemVal, nextItemValue);
});
}
setValue(nextValue);
setFocusItemValue(nextItemValue);
handleSelect(nextValue, item, event);
handleChangeValue(nextValue, event);
}, [value, setValue, handleSelect, handleChangeValue, setFocusItemValue]);
var handleEntered = useCallback(function () {
setActive(true);
onOpen === null || onOpen === void 0 ? void 0 : onOpen();
}, [onOpen]);
var handleExited = useCallback(function () {
setSearchKeyword('');
setFocusItemValue(null);
setActive(false);
onClose === null || onClose === void 0 ? void 0 : onClose();
}, [onClose, setFocusItemValue, setSearchKeyword]);
usePublicMethods(ref, {
triggerRef: triggerRef,
overlayRef: overlayRef,
targetRef: targetRef
});
var selectedItems = data.filter(function (item) {
return value === null || value === void 0 ? void 0 : value.some(function (val) {
return shallowEqual(item[valueKey], val);
});
}) || [];
/**
* 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 = selectedItems.length > 0 || (value === null || value === void 0 ? void 0 : value.length) > 0 && isFunction(renderValue);
var _useClassNames = useClassNames(classPrefix),
prefix = _useClassNames.prefix,
merge = _useClassNames.merge;
var selectedElement = placeholder;
if (selectedItems.length > 0) {
selectedElement = /*#__PURE__*/React.createElement(SelectedElement, {
selectedItems: selectedItems,
countable: countable,
valueKey: valueKey,
labelKey: labelKey,
prefix: prefix
});
}
if ((value === null || value === void 0 ? void 0 : value.length) > 0 && isFunction(renderValue)) {
selectedElement = renderValue(value, selectedItems, selectedElement); // If renderValue returns null or undefined, hasValue is false.
if (isNil(selectedElement)) {
hasValue = false;
}
}
var renderDropdownMenu = function renderDropdownMenu(positionProps, speakerRef) {
var left = positionProps.left,
top = positionProps.top,
className = positionProps.className;
var classes = merge(className, menuClassName, prefix('check-menu'));
var styles = _extends({}, menuStyle, {
left: left,
top: top
});
var items = filteredData;
var filteredStickyItems = [];
if (stickyItems) {
filteredStickyItems = filterNodesOfTree(stickyItems, function (item) {
return checkShouldDisplay(item);
});
items = filterNodesOfTree(data, function (item) {
return checkShouldDisplay(item) && !stickyItems.some(function (v) {
return v[valueKey] === item[valueKey];
});
});
} // Create a tree structure data when set `groupBy`
if (groupBy) {
items = getDataGroupBy(items, groupBy, sort);
} else if (typeof sort === 'function') {
items = items.sort(sort(false));
}
var menu = items.length || filteredStickyItems.length ? /*#__PURE__*/React.createElement(DropdownMenu, {
id: id ? id + "-listbox" : undefined,
listProps: listProps,
disabledItemValues: disabledItemValues,
valueKey: valueKey,
labelKey: labelKey,
renderMenuGroup: renderMenuGroup,
renderMenuItem: renderMenuItem,
maxHeight: menuMaxHeight,
classPrefix: 'picker-check-menu',
dropdownMenuItemAs: DropdownMenuItem,
activeItemValues: value,
focusItemValue: focusItemValue,
data: [].concat(filteredStickyItems, items),
group: !isUndefined(groupBy),
onSelect: handleItemSelect,
onGroupTitleClick: onGroupTitleClick,
virtualized: virtualized
}) : /*#__PURE__*/React.createElement("div", {
className: prefix(_templateObject || (_templateObject = _taggedTemplateLiteralLoose(["none"])))
}, locale === null || locale === void 0 ? void 0 : locale.noResultsText);
return /*#__PURE__*/React.createElement(PickerOverlay, {
ref: mergeRefs(overlayRef, speakerRef),
autoWidth: menuAutoWidth,
className: classes,
style: styles,
onKeyDown: onPickerKeyDown,
target: triggerRef
}, searchable && /*#__PURE__*/React.createElement(SearchBar, {
placeholder: locale === null || locale === void 0 ? void 0 : locale.searchPlaceholder,
onChange: handleSearch,
value: searchKeyword,
inputRef: searchInputRef
}), renderMenu ? renderMenu(menu) : menu, renderExtraFooter === null || renderExtraFooter === void 0 ? void 0 : renderExtraFooter());
};
var _usePickerClassName = usePickerClassName(_extends({}, props, {
appearance: appearance,
classPrefix: classPrefix,
cleanable: cleanable,
countable: countable,
hasValue: hasValue,
name: 'check'
})),
classes = _usePickerClassName[0],
usedClassNamePropKeys = _usePickerClassName[1];
return /*#__PURE__*/React.createElement(PickerToggleTrigger, {
pickerProps: pick(props, pickTriggerPropKeys),
ref: triggerRef,
placement: placement,
onEnter: createChainedFunction(initStickyItems, onEnter),
onEntered: createChainedFunction(handleEntered, onEntered),
onExited: createChainedFunction(handleExited, onExited),
speaker: renderDropdownMenu
}, /*#__PURE__*/React.createElement(Component, {
className: classes,
style: style
}, /*#__PURE__*/React.createElement(PickerToggle, _extends({}, omit(rest, [].concat(omitTriggerPropKeys, usedClassNamePropKeys)), {
id: id,
ref: targetRef,
appearance: appearance,
disabled: disabled,
onClean: handleClean,
onKeyDown: onPickerKeyDown,
as: toggleAs,
cleanable: cleanable && !disabled,
hasValue: hasValue,
active: active,
placement: placement,
inputValue: value
}), selectedElement || (locale === null || locale === void 0 ? void 0 : locale.placeholder))));
});
CheckPicker.displayName = 'CheckPicker';
CheckPicker.propTypes = _extends({}, listPickerPropTypes, {
locale: PropTypes.any,
appearance: PropTypes.oneOf(['default', 'subtle']),
menuAutoWidth: PropTypes.bool,
menuMaxHeight: PropTypes.number,
renderMenu: PropTypes.func,
renderMenuItem: PropTypes.func,
renderMenuGroup: PropTypes.func,
onSelect: PropTypes.func,
onGroupTitleClick: PropTypes.func,
onSearch: PropTypes.func,
groupBy: PropTypes.any,
sort: PropTypes.func,
searchable: PropTypes.bool,
countable: PropTypes.bool,
sticky: PropTypes.bool,
virtualized: PropTypes.bool,
searchBy: PropTypes.func
});
export default CheckPicker;