rsuite
Version:
A suite of react components
547 lines (455 loc) • 20.5 kB
JavaScript
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
};
});
}