rsuite
Version:
A suite of react components
658 lines (531 loc) • 24.7 kB
JavaScript
"use strict";
var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard");
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
exports.__esModule = true;
exports.createConcatChildrenFunction = createConcatChildrenFunction;
exports.shouldDisplay = shouldDisplay;
exports.usePickerClassName = usePickerClassName;
exports.onMenuKeyDown = onMenuKeyDown;
exports.useSearch = useSearch;
exports.usePublicMethods = usePublicMethods;
exports.useToggleKeyDownEvent = exports.useFocusItemValue = void 0;
var _objectWithoutPropertiesLoose2 = _interopRequireDefault(require("@babel/runtime/helpers/objectWithoutPropertiesLoose"));
var _react = _interopRequireWildcard(require("react"));
var _kebabCase = _interopRequireDefault(require("lodash/kebabCase"));
var _trim = _interopRequireDefault(require("lodash/trim"));
var _isFunction = _interopRequireDefault(require("lodash/isFunction"));
var _isUndefined = _interopRequireDefault(require("lodash/isUndefined"));
var _omit = _interopRequireDefault(require("lodash/omit"));
var _find = _interopRequireDefault(require("lodash/find"));
var _treeUtils = require("../utils/treeUtils");
var _utils = require("../utils");
var _domLib = require("dom-lib");
var defaultNodeKeys = {
valueKey: 'value',
childrenKey: 'children'
};
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 = (0, _treeUtils.findNodeOfTree)(data, function (item) {
return nodeValue === item[valueKey];
});
}
node[childrenKey] = children;
return data.concat([]);
};
}
function shouldDisplay(label, searchKeyword) {
if (!(0, _trim.default)(searchKeyword)) {
return true;
}
var keyword = searchKeyword.toLocaleLowerCase();
if (typeof label === 'string' || typeof label === 'number') {
return ("" + label).toLocaleLowerCase().indexOf(keyword) >= 0;
} else if ( /*#__PURE__*/_react.default.isValidElement(label)) {
var nodes = (0, _utils.reactToString)(label);
return nodes.join('').toLocaleLowerCase().indexOf(keyword) >= 0;
}
return false;
}
/**
* The className of the assembled Toggle is on the Picker.
*/
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 = (0, _objectWithoutPropertiesLoose2.default)(props, ["name", "classPrefix", "className", "placement", "appearance", "cleanable", "block", "disabled", "countable", "readOnly", "plaintext", "hasValue"]);
var _useClassNames = (0, _utils.useClassNames)(classPrefix),
withClassPrefix = _useClassNames.withClassPrefix,
merge = _useClassNames.merge;
var classes = merge(className, withClassPrefix(name, appearance, 'toggle-wrapper', (_withClassPrefix = {}, _withClassPrefix["placement-" + (0, _kebabCase.default)((0, _utils.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((0, _omit.default)(props, [].concat(Object.keys(rest || {}), ['disabled', 'readOnly', 'plaintext'])));
return [classes, usedClassNamePropKeys];
}
/**
* Handling keyboard events...
* @param event Keyboard event object
* @param events Event callback functions
*/
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 _utils.KEY_VALUES.DOWN:
down === null || down === void 0 ? void 0 : down(event);
event.preventDefault();
break;
// up
case _utils.KEY_VALUES.UP:
up === null || up === void 0 ? void 0 : up(event);
event.preventDefault();
break;
// enter
case _utils.KEY_VALUES.ENTER:
enter === null || enter === void 0 ? void 0 : enter(event);
event.preventDefault();
break;
// delete
case _utils.KEY_VALUES.BACKSPACE:
del === null || del === void 0 ? void 0 : del(event);
break;
// esc | tab
case _utils.KEY_VALUES.ESC:
case _utils.KEY_VALUES.TAB:
esc === null || esc === void 0 ? void 0 : esc(event);
break;
// left arrow
case _utils.KEY_VALUES.LEFT:
left === null || left === void 0 ? void 0 : left(event);
break;
// right arrow
case _utils.KEY_VALUES.RIGHT:
right === null || right === void 0 ? void 0 : right(event);
break;
default:
}
}
/**
* Checks if the element has a vertical scrollbar.
*/
function hasVerticalScroll(element) {
var scrollHeight = element.scrollHeight,
clientHeight = element.clientHeight;
return scrollHeight > clientHeight;
}
/**
* Checks if the element is within the visible area of the container
*/
function isVisible(element, container, direction) {
if (!hasVerticalScroll(container)) {
return true;
}
var _element$getBoundingC = element.getBoundingClientRect(),
top = _element$getBoundingC.top,
bottom = _element$getBoundingC.bottom,
height = _element$getBoundingC.height;
var _container$getBoundin = container.getBoundingClientRect(),
containerTop = _container$getBoundin.top,
containerBottom = _container$getBoundin.bottom;
if (direction === 'top') {
return top + height > containerTop;
}
return bottom - height < containerBottom;
}
function scrollTo(container, direction, step) {
var scrollTop = container.scrollTop;
container.scrollTop = direction === 'top' ? scrollTop - step : scrollTop + step;
}
/**
* A hook that manages the focus state of the option.
* @param defaultFocusItemValue
* @param props
*/
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 = (0, _react.useState)(defaultFocusItemValue),
focusItemValue = _useState[0],
setFocusItemValue = _useState[1];
var _useState2 = (0, _react.useState)(defaultLayer),
layer = _useState2[0],
setLayer = _useState2[1];
var _useState3 = (0, _react.useState)([]),
keys = _useState3[0],
setKeys = _useState3[1];
var getScrollContainer = (0, _react.useCallback)(function () {
var menu = (0, _isFunction.default)(target) ? target() : target; // For Cascader and MutiCascader
var subMenu = menu === null || menu === void 0 ? void 0 : menu.querySelector("[data-layer=\"" + layer + "\"]");
if (subMenu) {
return subMenu;
} // For SelectPicker、CheckPicker、Autocomplete、InputPicker、TagPicker
return menu === null || menu === void 0 ? void 0 : menu.querySelector('[role="listbox"]');
}, [layer, target]);
/**
* Get the elements visible in all options.
*/
var getFocusableMenuItems = (0, _react.useCallback)(function () {
if (!target) {
return [];
}
var currentKeys = keys;
if (layer < 1) {
var popup = (0, _isFunction.default)(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 (0, _find.default)(data, function (i) {
return "" + i[valueKey] === key;
});
});
}, [data, focusableQueryKey, keys, target, valueKey, layer]);
/**
* Get the index of the focus item.
*/
var findFocusItemIndex = (0, _react.useCallback)(function (callback) {
var items = getFocusableMenuItems();
for (var i = 0; i < items.length; i += 1) {
var _items$i;
if ((0, _utils.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 scrollListItem = (0, _react.useCallback)(function (direction, itemValue, willOverflow) {
var container = getScrollContainer();
var item = container === null || container === void 0 ? void 0 : container.querySelector("[data-key=\"" + itemValue + "\"]");
if (willOverflow && container) {
var scrollHeight = container.scrollHeight,
clientHeight = container.clientHeight;
container.scrollTop = direction === 'top' ? scrollHeight - clientHeight : 0;
return;
}
if (item && container) {
if (!isVisible(item, container, direction)) {
var height = (0, _domLib.getHeight)(item);
scrollTo(container, direction, height);
}
}
}, [getScrollContainer]);
var focusNextMenuItem = (0, _react.useCallback)(function (event) {
findFocusItemIndex(function (items, index) {
var willOverflow = index + 2 > items.length;
var nextIndex = willOverflow ? 0 : index + 1;
var focusItem = items[nextIndex];
if (!(0, _isUndefined.default)(focusItem)) {
setFocusItemValue(focusItem[valueKey]);
callback === null || callback === void 0 ? void 0 : callback(focusItem[valueKey], event);
scrollListItem('bottom', focusItem[valueKey], willOverflow);
}
});
}, [callback, findFocusItemIndex, scrollListItem, valueKey]);
var focusPrevMenuItem = (0, _react.useCallback)(function (event) {
findFocusItemIndex(function (items, index) {
var willOverflow = index === 0;
var nextIndex = willOverflow ? items.length - 1 : index - 1;
var focusItem = items[nextIndex];
if (!(0, _isUndefined.default)(focusItem)) {
setFocusItemValue(focusItem[valueKey]);
callback === null || callback === void 0 ? void 0 : callback(focusItem[valueKey], event);
scrollListItem('top', focusItem[valueKey], willOverflow);
}
});
}, [callback, findFocusItemIndex, scrollListItem, valueKey]);
var getSubMenuKeys = (0, _react.useCallback)(function (nextLayer) {
var menu = (0, _isFunction.default)(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 = (0, _react.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 = (0, _react.useCallback)(function (event) {
var nextLayer = layer - 1;
var nextKeys = getSubMenuKeys(nextLayer);
if (nextKeys) {
var _focusItem$parent;
setKeys(nextKeys);
setLayer(nextLayer);
var focusItem = (0, _treeUtils.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 = (0, _react.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
};
};
exports.useFocusItemValue = useFocusItemValue;
/**
* A hook to control the toggle keyboard operation
* @param props
*/
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 = (0, _react.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 = (0, _react.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 = (0, _react.useCallback)(function () {
if (active) {
handleClose();
return;
}
handleOpen();
}, [active, handleOpen, handleClose]);
var onToggle = (0, _react.useCallback)(function (event) {
if (event.target === (targetRef === null || targetRef === void 0 ? void 0 : targetRef.current)) {
// enter
if (toggle && event.key === _utils.KEY_VALUES.ENTER) {
handleToggleDropdown();
} // delete
if (event.key === _utils.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 === _utils.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 === _utils.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 === _utils.KEY_VALUES.ESC || event.key === _utils.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;
};
exports.useToggleKeyDownEvent = useToggleKeyDownEvent;
/**
* A hook that handles search filter options
* @param props
*/
function useSearch(props) {
var labelKey = props.labelKey,
data = props.data,
searchBy = props.searchBy,
callback = props.callback; // Use search keywords to filter options.
var _useState4 = (0, _react.useState)(''),
searchKeyword = _useState4[0],
setSearchKeyword = _useState4[1];
/**
* Index of keyword in `label`
* @param {node} label
*/
var checkShouldDisplay = (0, _react.useCallback)(function (item, keyword) {
var label = item === null || item === void 0 ? void 0 : item[labelKey];
var _keyword = (0, _isUndefined.default)(keyword) ? searchKeyword : keyword;
if (typeof searchBy === 'function') {
return searchBy(_keyword, label, item);
}
return shouldDisplay(label, _keyword);
}, [labelKey, searchBy, searchKeyword]);
var updateFilteredData = (0, _react.useCallback)(function (nextData) {
setFilteredData((0, _treeUtils.filterNodesOfTree)(nextData, function (item) {
return checkShouldDisplay(item);
}));
}, [checkShouldDisplay]);
var _useState5 = (0, _react.useState)((0, _treeUtils.filterNodesOfTree)(data, function (item) {
return checkShouldDisplay(item);
})),
filteredData = _useState5[0],
setFilteredData = _useState5[1];
var handleSearch = function handleSearch(searchKeyword, event) {
var filteredData = (0, _treeUtils.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
*/
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 = (0, _react.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 = (0, _react.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 = (0, _react.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]);
(0, _react.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;
if (!(overlayRef !== null && overlayRef !== void 0 && overlayRef.current)) {
throw new Error('The overlay is not found. Please confirm whether the picker is open.');
}
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
};
});
}