UNPKG

rsuite

Version:

A suite of react components

547 lines (455 loc) 20.5 kB
import _objectWithoutPropertiesLoose from "@babel/runtime/helpers/esm/objectWithoutPropertiesLoose"; import React, { useState, useImperativeHandle, useCallback } from 'react'; import kebabCase from 'lodash/kebabCase'; import trim from 'lodash/trim'; import isFunction from 'lodash/isFunction'; import isUndefined from 'lodash/isUndefined'; import omit from 'lodash/omit'; import find from 'lodash/find'; import { findNodeOfTree, filterNodesOfTree } from '../utils/treeUtils'; import { KEY_VALUES, useClassNames, shallowEqual, reactToString, placementPolyfill } from '../utils'; var defaultNodeKeys = { valueKey: 'value', childrenKey: 'children' }; export function createConcatChildrenFunction(node, nodeValue, nodeKeys) { if (nodeKeys === void 0) { nodeKeys = defaultNodeKeys; } var _nodeKeys = nodeKeys, valueKey = _nodeKeys.valueKey, childrenKey = _nodeKeys.childrenKey; return function (data, children) { if (nodeValue) { node = findNodeOfTree(data, function (item) { return nodeValue === item[valueKey]; }); } node[childrenKey] = children; return data.concat([]); }; } export function shouldDisplay(label, searchKeyword) { if (!trim(searchKeyword)) { return true; } var keyword = searchKeyword.toLocaleLowerCase(); if (typeof label === 'string' || typeof label === 'number') { return ("" + label).toLocaleLowerCase().indexOf(keyword) >= 0; } else if ( /*#__PURE__*/React.isValidElement(label)) { var nodes = reactToString(label); return nodes.join('').toLocaleLowerCase().indexOf(keyword) >= 0; } return false; } /** * The className of the assembled Toggle is on the Picker. */ export function usePickerClassName(props) { var _withClassPrefix; var name = props.name, classPrefix = props.classPrefix, className = props.className, placement = props.placement, appearance = props.appearance, cleanable = props.cleanable, block = props.block, disabled = props.disabled, countable = props.countable, readOnly = props.readOnly, plaintext = props.plaintext, hasValue = props.hasValue, rest = _objectWithoutPropertiesLoose(props, ["name", "classPrefix", "className", "placement", "appearance", "cleanable", "block", "disabled", "countable", "readOnly", "plaintext", "hasValue"]); var _useClassNames = useClassNames(classPrefix), withClassPrefix = _useClassNames.withClassPrefix, merge = _useClassNames.merge; var classes = merge(className, withClassPrefix(name, appearance, 'toggle-wrapper', (_withClassPrefix = {}, _withClassPrefix["placement-" + kebabCase(placementPolyfill(placement))] = placement, _withClassPrefix['read-only'] = readOnly, _withClassPrefix['has-value'] = hasValue, _withClassPrefix.cleanable = cleanable, _withClassPrefix.block = block, _withClassPrefix.disabled = disabled, _withClassPrefix.countable = countable, _withClassPrefix.plaintext = plaintext, _withClassPrefix))); var usedClassNamePropKeys = Object.keys(omit(props, [].concat(Object.keys(rest || {}), ['disabled', 'readOnly', 'plaintext']))); return [classes, usedClassNamePropKeys]; } /** * Handling keyboard events... * @param event Keyboard event object * @param events Event callback functions */ export function onMenuKeyDown(event, events) { var down = events.down, up = events.up, enter = events.enter, del = events.del, esc = events.esc, right = events.right, left = events.left; switch (event.key) { // down case KEY_VALUES.DOWN: down === null || down === void 0 ? void 0 : down(event); event.preventDefault(); break; // up case KEY_VALUES.UP: up === null || up === void 0 ? void 0 : up(event); event.preventDefault(); break; // enter case KEY_VALUES.ENTER: enter === null || enter === void 0 ? void 0 : enter(event); event.preventDefault(); break; // delete case KEY_VALUES.BACKSPACE: del === null || del === void 0 ? void 0 : del(event); break; // esc | tab case KEY_VALUES.ESC: case KEY_VALUES.TAB: esc === null || esc === void 0 ? void 0 : esc(event); break; // left arrow case KEY_VALUES.LEFT: left === null || left === void 0 ? void 0 : left(event); break; // right arrow case KEY_VALUES.RIGHT: right === null || right === void 0 ? void 0 : right(event); break; default: } } /** * A hook that manages the focus state of the option. * @param defaultFocusItemValue * @param props */ export var useFocusItemValue = function useFocusItemValue(defaultFocusItemValue, props) { var _props$valueKey = props.valueKey, valueKey = _props$valueKey === void 0 ? 'value' : _props$valueKey, _props$focusableQuery = props.focusableQueryKey, focusableQueryKey = _props$focusableQuery === void 0 ? '[data-key][aria-disabled="false"]' : _props$focusableQuery, _props$defaultLayer = props.defaultLayer, defaultLayer = _props$defaultLayer === void 0 ? 0 : _props$defaultLayer, data = props.data, target = props.target, rtl = props.rtl, callback = props.callback; var _useState = useState(defaultFocusItemValue), focusItemValue = _useState[0], setFocusItemValue = _useState[1]; var _useState2 = useState(defaultLayer), layer = _useState2[0], setLayer = _useState2[1]; var _useState3 = useState([]), keys = _useState3[0], setKeys = _useState3[1]; /** * Get the elements visible in all options. */ var getFocusableMenuItems = useCallback(function () { if (!target) { return []; } var currentKeys = keys; if (layer < 1) { var popup = isFunction(target) ? target() : target; var rootMenu = popup === null || popup === void 0 ? void 0 : popup.querySelector('[data-layer="0"]'); if (rootMenu) { var _rootMenu$querySelect; currentKeys = Array.from((_rootMenu$querySelect = rootMenu.querySelectorAll(focusableQueryKey)) !== null && _rootMenu$querySelect !== void 0 ? _rootMenu$querySelect : []).map(function (item) { var _item$dataset; return (_item$dataset = item.dataset) === null || _item$dataset === void 0 ? void 0 : _item$dataset.key; }); } else { var _popup$querySelectorA; currentKeys = Array.from((_popup$querySelectorA = popup === null || popup === void 0 ? void 0 : popup.querySelectorAll(focusableQueryKey)) !== null && _popup$querySelectorA !== void 0 ? _popup$querySelectorA : []).map(function (item) { var _item$dataset2; return (_item$dataset2 = item.dataset) === null || _item$dataset2 === void 0 ? void 0 : _item$dataset2.key; }); } } // 1. It is necessary to traverse the `keys` instead of `data` here to preserve the order of the array. // 2. The values ​​in `keys` are all string, so the corresponding value of `data` should also be converted to string return currentKeys.map(function (key) { return find(data, function (i) { return "" + i[valueKey] === key; }); }); }, [data, focusableQueryKey, keys, target, valueKey, layer]); /** * Get the index of the focus item. */ var findFocusItemIndex = useCallback(function (callback) { var items = getFocusableMenuItems(); for (var i = 0; i < items.length; i += 1) { var _items$i; if (shallowEqual(focusItemValue, (_items$i = items[i]) === null || _items$i === void 0 ? void 0 : _items$i[valueKey])) { callback(items, i); return; } } callback(items, -1); }, [focusItemValue, getFocusableMenuItems, valueKey]); var focusNextMenuItem = useCallback(function (event) { findFocusItemIndex(function (items, index) { var nextIndex = index + 2 > items.length ? 0 : index + 1; var focusItem = items[nextIndex]; if (!isUndefined(focusItem)) { setFocusItemValue(focusItem[valueKey]); callback === null || callback === void 0 ? void 0 : callback(focusItem[valueKey], event); } }); }, [callback, findFocusItemIndex, valueKey]); var focusPrevMenuItem = useCallback(function (event) { findFocusItemIndex(function (items, index) { var nextIndex = index === 0 ? items.length - 1 : index - 1; var focusItem = items[nextIndex]; if (!isUndefined(focusItem)) { setFocusItemValue(focusItem[valueKey]); callback === null || callback === void 0 ? void 0 : callback(focusItem[valueKey], event); } }); }, [callback, findFocusItemIndex, valueKey]); var getSubMenuKeys = useCallback(function (nextLayer) { var menu = isFunction(target) ? target() : target; var subMenu = menu === null || menu === void 0 ? void 0 : menu.querySelector("[data-layer=\"" + nextLayer + "\"]"); if (subMenu) { var _Array$from; return (_Array$from = Array.from(subMenu.querySelectorAll(focusableQueryKey))) === null || _Array$from === void 0 ? void 0 : _Array$from.map(function (item) { var _item$dataset3; return (_item$dataset3 = item.dataset) === null || _item$dataset3 === void 0 ? void 0 : _item$dataset3.key; }); } return null; }, [focusableQueryKey, target]); var focusNextLevelMenu = useCallback(function (event) { var nextLayer = layer + 1; var nextKeys = getSubMenuKeys(nextLayer); if (nextKeys) { setKeys(nextKeys); setLayer(nextLayer); setFocusItemValue(nextKeys[0]); callback === null || callback === void 0 ? void 0 : callback(nextKeys[0], event); } }, [callback, getSubMenuKeys, layer]); var focusPrevLevelMenu = useCallback(function (event) { var nextLayer = layer - 1; var nextKeys = getSubMenuKeys(nextLayer); if (nextKeys) { var _focusItem$parent; setKeys(nextKeys); setLayer(nextLayer); var focusItem = findNodeOfTree(data, function (item) { return item[valueKey] === focusItemValue; }); var parentItemValue = focusItem === null || focusItem === void 0 ? void 0 : (_focusItem$parent = focusItem.parent) === null || _focusItem$parent === void 0 ? void 0 : _focusItem$parent[valueKey]; if (parentItemValue) { setFocusItemValue(parentItemValue); callback === null || callback === void 0 ? void 0 : callback(parentItemValue, event); } } }, [callback, data, focusItemValue, getSubMenuKeys, layer, valueKey]); var handleKeyDown = useCallback(function (event) { var _onMenuKeyDown; onMenuKeyDown(event, (_onMenuKeyDown = { down: focusNextMenuItem, up: focusPrevMenuItem }, _onMenuKeyDown[rtl ? 'left' : 'right'] = focusNextLevelMenu, _onMenuKeyDown[rtl ? 'right' : 'left'] = focusPrevLevelMenu, _onMenuKeyDown)); }, [focusNextLevelMenu, focusNextMenuItem, focusPrevLevelMenu, focusPrevMenuItem, rtl]); return { focusItemValue: focusItemValue, setFocusItemValue: setFocusItemValue, layer: layer, setLayer: setLayer, keys: keys, setKeys: setKeys, onKeyDown: handleKeyDown }; }; /** * A hook to control the toggle keyboard operation * @param props */ export var useToggleKeyDownEvent = function useToggleKeyDownEvent(props) { var _props$toggle = props.toggle, toggle = _props$toggle === void 0 ? true : _props$toggle, triggerRef = props.triggerRef, targetRef = props.targetRef, overlayRef = props.overlayRef, searchInputRef = props.searchInputRef, active = props.active, onExit = props.onExit, onOpen = props.onOpen, onClose = props.onClose, onKeyDown = props.onKeyDown, onMenuKeyDown = props.onMenuKeyDown, onMenuPressEnter = props.onMenuPressEnter, onMenuPressBackspace = props.onMenuPressBackspace; var handleClose = useCallback(function () { var _triggerRef$current, _triggerRef$current$c; (_triggerRef$current = triggerRef.current) === null || _triggerRef$current === void 0 ? void 0 : (_triggerRef$current$c = _triggerRef$current.close) === null || _triggerRef$current$c === void 0 ? void 0 : _triggerRef$current$c.call(_triggerRef$current); onClose === null || onClose === void 0 ? void 0 : onClose(); }, [onClose, triggerRef]); var handleOpen = useCallback(function () { var _triggerRef$current2, _triggerRef$current2$; (_triggerRef$current2 = triggerRef.current) === null || _triggerRef$current2 === void 0 ? void 0 : (_triggerRef$current2$ = _triggerRef$current2.open) === null || _triggerRef$current2$ === void 0 ? void 0 : _triggerRef$current2$.call(_triggerRef$current2); onOpen === null || onOpen === void 0 ? void 0 : onOpen(); }, [onOpen, triggerRef]); var handleToggleDropdown = useCallback(function () { if (active) { handleClose(); return; } handleOpen(); }, [active, handleOpen, handleClose]); var onToggle = useCallback(function (event) { if (event.target === (targetRef === null || targetRef === void 0 ? void 0 : targetRef.current)) { // enter if (toggle && event.key === KEY_VALUES.ENTER) { handleToggleDropdown(); } // delete if (event.key === KEY_VALUES.BACKSPACE) { onExit === null || onExit === void 0 ? void 0 : onExit(event); } } if (overlayRef !== null && overlayRef !== void 0 && overlayRef.current) { // The keyboard operation callback on the menu. onMenuKeyDown === null || onMenuKeyDown === void 0 ? void 0 : onMenuKeyDown(event); if (event.key === KEY_VALUES.ENTER) { onMenuPressEnter === null || onMenuPressEnter === void 0 ? void 0 : onMenuPressEnter(event); } /** * There is no callback when typing the Backspace key in the search box. * The default is to remove search keywords */ if (event.key === KEY_VALUES.BACKSPACE && event.target !== (searchInputRef === null || searchInputRef === void 0 ? void 0 : searchInputRef.current)) { onMenuPressBackspace === null || onMenuPressBackspace === void 0 ? void 0 : onMenuPressBackspace(event); } // The search box gets focus when typing characters and numbers. if (event.key.length === 1 && /\w/.test(event.key)) { var _event$target; // Exclude Input // eg: <SelectPicker renderExtraFooter={() => <Input />} /> if (((_event$target = event.target) === null || _event$target === void 0 ? void 0 : _event$target.tagName) !== 'INPUT') { var _searchInputRef$curre; searchInputRef === null || searchInputRef === void 0 ? void 0 : (_searchInputRef$curre = searchInputRef.current) === null || _searchInputRef$curre === void 0 ? void 0 : _searchInputRef$curre.focus(); } } } if (event.key === KEY_VALUES.ESC || event.key === KEY_VALUES.TAB) { handleClose(); } // Native event callback onKeyDown === null || onKeyDown === void 0 ? void 0 : onKeyDown(event); }, [handleClose, handleToggleDropdown, overlayRef, onExit, onKeyDown, onMenuKeyDown, onMenuPressBackspace, onMenuPressEnter, toggle, targetRef, searchInputRef]); return onToggle; }; /** * A hook that handles search filter options * @param props */ export function useSearch(props) { var labelKey = props.labelKey, data = props.data, searchBy = props.searchBy, callback = props.callback; // Use search keywords to filter options. var _useState4 = useState(''), searchKeyword = _useState4[0], setSearchKeyword = _useState4[1]; /** * Index of keyword in `label` * @param {node} label */ var checkShouldDisplay = useCallback(function (item, keyword) { var label = item === null || item === void 0 ? void 0 : item[labelKey]; var _keyword = isUndefined(keyword) ? searchKeyword : keyword; if (typeof searchBy === 'function') { return searchBy(_keyword, label, item); } return shouldDisplay(label, _keyword); }, [labelKey, searchBy, searchKeyword]); var updateFilteredData = useCallback(function (nextData) { setFilteredData(filterNodesOfTree(nextData, function (item) { return checkShouldDisplay(item); })); }, [checkShouldDisplay]); var _useState5 = useState(filterNodesOfTree(data, function (item) { return checkShouldDisplay(item); })), filteredData = _useState5[0], setFilteredData = _useState5[1]; var handleSearch = function handleSearch(searchKeyword, event) { var filteredData = filterNodesOfTree(data, function (item) { return checkShouldDisplay(item, searchKeyword); }); setFilteredData(filteredData); setSearchKeyword(searchKeyword); callback === null || callback === void 0 ? void 0 : callback(searchKeyword, filteredData, event); }; return { searchKeyword: searchKeyword, filteredData: filteredData, updateFilteredData: updateFilteredData, setSearchKeyword: setSearchKeyword, checkShouldDisplay: checkShouldDisplay, handleSearch: handleSearch }; } /** * A hook of the exposed method of Picker */ export function usePublicMethods(ref, parmas) { var triggerRef = parmas.triggerRef, overlayRef = parmas.overlayRef, targetRef = parmas.targetRef, rootRef = parmas.rootRef, listRef = parmas.listRef, inline = parmas.inline; var handleOpen = useCallback(function () { var _triggerRef$current3; triggerRef === null || triggerRef === void 0 ? void 0 : (_triggerRef$current3 = triggerRef.current) === null || _triggerRef$current3 === void 0 ? void 0 : _triggerRef$current3.open(); }, [triggerRef]); var handleClose = useCallback(function () { var _triggerRef$current4; triggerRef === null || triggerRef === void 0 ? void 0 : (_triggerRef$current4 = triggerRef.current) === null || _triggerRef$current4 === void 0 ? void 0 : _triggerRef$current4.close(); }, [triggerRef]); var handleUpdatePosition = useCallback(function () { var _triggerRef$current5; triggerRef === null || triggerRef === void 0 ? void 0 : (_triggerRef$current5 = triggerRef.current) === null || _triggerRef$current5 === void 0 ? void 0 : _triggerRef$current5.updatePosition(); }, [triggerRef]); useImperativeHandle(ref, function () { // Tree and CheckTree if (inline) { return { get root() { var _triggerRef$current$r, _triggerRef$current6; return rootRef !== null && rootRef !== void 0 && rootRef.current ? rootRef === null || rootRef === void 0 ? void 0 : rootRef.current : (_triggerRef$current$r = triggerRef === null || triggerRef === void 0 ? void 0 : (_triggerRef$current6 = triggerRef.current) === null || _triggerRef$current6 === void 0 ? void 0 : _triggerRef$current6.root) !== null && _triggerRef$current$r !== void 0 ? _triggerRef$current$r : null; }, get list() { if (!(listRef !== null && listRef !== void 0 && listRef.current)) { throw new Error('The list is not found, please set `virtualized` for the component.'); } return listRef === null || listRef === void 0 ? void 0 : listRef.current; } }; } return { get root() { var _ref, _triggerRef$current7; return (_ref = (rootRef === null || rootRef === void 0 ? void 0 : rootRef.current) || (triggerRef === null || triggerRef === void 0 ? void 0 : (_triggerRef$current7 = triggerRef.current) === null || _triggerRef$current7 === void 0 ? void 0 : _triggerRef$current7.root)) !== null && _ref !== void 0 ? _ref : null; }, get overlay() { var _overlayRef$current; return (_overlayRef$current = overlayRef === null || overlayRef === void 0 ? void 0 : overlayRef.current) !== null && _overlayRef$current !== void 0 ? _overlayRef$current : null; }, get target() { var _targetRef$current; return (_targetRef$current = targetRef === null || targetRef === void 0 ? void 0 : targetRef.current) !== null && _targetRef$current !== void 0 ? _targetRef$current : null; }, get list() { if (!(listRef !== null && listRef !== void 0 && listRef.current)) { throw new Error("\n The list is not found.\n 1.Please set virtualized for the component.\n 2.Please confirm whether the picker is open.\n "); } return listRef === null || listRef === void 0 ? void 0 : listRef.current; }, updatePosition: handleUpdatePosition, open: handleOpen, close: handleClose }; }); }