UNPKG

rsuite

Version:

A suite of react components

226 lines (218 loc) 7.58 kB
'use client'; "use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); exports.__esModule = true; exports.default = void 0; var _react = require("react"); var _isFunction = _interopRequireDefault(require("lodash/isFunction")); var _isUndefined = _interopRequireDefault(require("lodash/isUndefined")); var _find = _interopRequireDefault(require("lodash/find")); var _domLib = require("dom-lib"); var _hooks = require("../../hooks"); var _utils = require("../../utils"); var _utils2 = require("../../Tree/utils"); var _utils3 = require("../utils"); /** * A hook that manages the focus state of the option. * @param defaultFocusItemValue * @param props */ const useFocusItemValue = (defaultFocusItemValue, props) => { const { valueKey = 'value', focusableQueryKey = '[data-key][aria-disabled="false"]', defaultLayer = 0, focusToOption = true, data, target, rtl, callback, // TODO-Doma This legacy behavior of using `.parent` property should be deprecated // Always explicitly pass `getParent` when there's need to traverse upwards getParent = item => item?.parent } = props; const [focusItemValue, setFocusItemValue] = (0, _react.useState)(defaultFocusItemValue); const [layer, setLayer] = (0, _react.useState)(defaultLayer); const [keys, setKeys] = (0, _react.useState)([]); const focusCallback = (0, _hooks.useEventCallback)((value, event) => { if (focusToOption) { const menu = (0, _isFunction.default)(target) ? target() : target; const focusElement = menu?.querySelector(`[data-key="${value}"]`); focusElement?.focus(); } callback?.(value, event); }); const getScrollContainer = (0, _hooks.useEventCallback)(() => { const menu = (0, _isFunction.default)(target) ? target() : target; // For Cascader and MutiCascader const subMenu = menu?.querySelector(`[data-layer="${layer}"]`); if (subMenu) { return subMenu; } // For SelectPicker、CheckPicker、Autocomplete、InputPicker、TagPicker return menu?.querySelector('[role="listbox"]'); }); /** * Get the elements visible in all options. */ const getFocusableMenuItems = () => { if (!target) { return []; } let currentKeys = keys; if (layer < 1) { const popup = (0, _isFunction.default)(target) ? target() : target; const rootMenu = popup?.querySelector('[data-layer="0"]'); if (rootMenu) { currentKeys = Array.from(rootMenu.querySelectorAll(focusableQueryKey) ?? []).map(item => item.dataset?.key); } else { currentKeys = Array.from(popup?.querySelectorAll(focusableQueryKey) ?? []).map(item => item.dataset?.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(key => (0, _find.default)(data, i => `${i[valueKey]}` === key)); }; /** * Get the index of the focus item. */ const findFocusItemIndex = (0, _hooks.useEventCallback)(callback => { const items = getFocusableMenuItems(); for (let i = 0; i < items.length; i += 1) { if ((0, _utils.shallowEqual)(focusItemValue, items[i]?.[valueKey])) { callback(items, i); return; } } callback(items, -1); }); const scrollListItem = (0, _hooks.useEventCallback)((direction, itemValue, willOverflow) => { const container = getScrollContainer(); const item = container?.querySelector(`[data-key="${itemValue}"]`); if (willOverflow && container) { const { scrollHeight, clientHeight } = container; container.scrollTop = direction === 'top' ? scrollHeight - clientHeight : 0; return; } if (item && container) { if (!isVisible(item, container, direction)) { const height = (0, _domLib.getHeight)(item); scrollTo(container, direction, height); } } }); const focusNextMenuItem = (0, _hooks.useEventCallback)(event => { findFocusItemIndex((items, index) => { const willOverflow = index + 2 > items.length; const nextIndex = willOverflow ? 0 : index + 1; const focusItem = items[nextIndex]; if (!(0, _isUndefined.default)(focusItem)) { setFocusItemValue(focusItem[valueKey]); focusCallback(focusItem[valueKey], event); scrollListItem('bottom', focusItem[valueKey], willOverflow); } }); }); const focusPrevMenuItem = (0, _hooks.useEventCallback)(event => { findFocusItemIndex((items, index) => { const willOverflow = index === 0; const nextIndex = willOverflow ? items.length - 1 : index - 1; const focusItem = items[nextIndex]; if (!(0, _isUndefined.default)(focusItem)) { setFocusItemValue(focusItem[valueKey]); focusCallback(focusItem[valueKey], event); scrollListItem('top', focusItem[valueKey], willOverflow); } }); }); const getSubMenuKeys = nextLayer => { const menu = (0, _isFunction.default)(target) ? target() : target; const subMenu = menu?.querySelector(`[data-layer="${nextLayer}"]`); if (subMenu) { return Array.from(subMenu.querySelectorAll(focusableQueryKey))?.map(item => item.dataset?.key); } return null; }; const focusNextLevelMenu = (0, _hooks.useEventCallback)(event => { const nextLayer = layer + 1; const nextKeys = getSubMenuKeys(nextLayer); if (nextKeys) { setKeys(nextKeys); setLayer(nextLayer); setFocusItemValue(nextKeys[0]); focusCallback(nextKeys[0], event); } }); const focusPrevLevelMenu = (0, _hooks.useEventCallback)(event => { const nextLayer = layer - 1; const nextKeys = getSubMenuKeys(nextLayer); if (nextKeys) { setKeys(nextKeys); setLayer(nextLayer); const focusItem = (0, _utils2.findNodeOfTree)(data, item => item[valueKey] === focusItemValue); const parentItemValue = getParent(focusItem)?.[valueKey]; if (parentItemValue) { setFocusItemValue(parentItemValue); focusCallback(parentItemValue, event); } } }); const handleKeyDown = (0, _hooks.useEventCallback)(event => { (0, _utils3.onMenuKeyDown)(event, { down: focusNextMenuItem, up: focusPrevMenuItem, [rtl ? 'left' : 'right']: focusNextLevelMenu, [rtl ? 'right' : 'left']: focusPrevLevelMenu }); }); return { focusItemValue, setFocusItemValue, layer, setLayer, keys, setKeys, onKeyDown: handleKeyDown }; }; function scrollTo(container, direction, step) { const { scrollTop } = container; container.scrollTop = direction === 'top' ? scrollTop - step : scrollTop + step; } /** * Checks if the element has a vertical scrollbar. */ function hasVerticalScroll(element) { const { scrollHeight, clientHeight } = element; 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; } const { top, bottom, height } = element.getBoundingClientRect(); const { top: containerTop, bottom: containerBottom } = container.getBoundingClientRect(); if (direction === 'top') { return top + height > containerTop; } return bottom - height < containerBottom; } var _default = exports.default = useFocusItemValue;