UNPKG

@momentum-ui/react-collaboration

Version:

Cisco Momentum UI Framework for React Collaboration Applications

404 lines 22.2 kB
var __assign = (this && this.__assign) || function () { __assign = Object.assign || function(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; return __assign.apply(this, arguments); }; var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) { if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) { if (ar || !(i in from)) { if (!ar) ar = Array.prototype.slice.call(from, 0, i); ar[i] = from[i]; } } return to.concat(ar || Array.prototype.slice.call(from)); }; /* eslint-disable react-hooks/exhaustive-deps */ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { Item } from '@react-stately/collections'; import TextInput from '../TextInput'; import Icon from '../Icon'; import ButtonPill from '../ButtonPill'; import Menu from '../Menu'; import { KEYS, STYLE, DEFAULTS, ELEMENT, EVENT } from './ComboBox.constants'; import classnames from 'classnames'; import './ComboBox.style.scss'; import { handleFilter as handleFilterFunc, searchItem as searchItemFunc, getSumScrollTop as getSumScrollTopFunc, } from './ComboBox.utils'; /** * @deprecated Use the equivalent from momentum.design (NPM: `@momentum-design/components/dist/react`) */ var ComboBox = function (props) { var onArrowButtonPressCallback = props.onArrowButtonPress, onInputChangeCallback = props.onInputChange, onSelectionChangeCallback = props.onSelectionChange, openStateChangeCallBack = props.openStateChange, originComboBoxGroups = props.comboBoxGroups, _a = props.selectedKey, selectedKeyPayload = _a === void 0 ? DEFAULTS.SELECTEDKEY : _a, _b = props.disabledKeys, disabledKeysPayload = _b === void 0 ? DEFAULTS.DISABLEDKEYS : _b, _c = props.noResultText, noResultText = _c === void 0 ? DEFAULTS.NO_RESULT_TEXT : _c, _d = props.width, width = _d === void 0 ? DEFAULTS.WIDTH : _d, _e = props.placeholder, placeholder = _e === void 0 ? DEFAULTS.PLACEHOLDER : _e, _f = props.shouldFilterOnArrowButton, shouldFilterOnArrowButton = _f === void 0 ? DEFAULTS.SHOULD_FILTER_ON_ARROW_BUTTON : _f, _g = props.error, error = _g === void 0 ? DEFAULTS.ERROR : _g, inputRefProp = props.inputRef, className = props.className, id = props.id, style = props.style, label = props.label, description = props.description, children = props.children; var componentInputRef = useRef(null); var menuRef = useRef(null); var containerRef = useRef(null); var inputRef = inputRefProp || componentInputRef; var selectionPositionRef = useRef(null); var _h = useState(true), isInit = _h[0], setIsInit = _h[1]; var _j = useState(false), isOpen = _j[0], setIsOpen = _j[1]; var _k = useState(false), isFocused = _k[0], setIsFocused = _k[1]; var _l = useState(false), isPreFocused = _l[0], setIsPreFocused = _l[1]; var _m = useState(false), shouldFocusItem = _m[0], setShouldFocusItem = _m[1]; var _o = useState(), selectionContainerMaxHeight = _o[0], setSelectionContainerMaxHeight = _o[1]; var _p = useState(''), inputValue = _p[0], setInputValue = _p[1]; var _q = useState(selectedKeyPayload), selectedKey = _q[0], setSelectedKey = _q[1]; var _r = useState(originComboBoxGroups), groups = _r[0], setGroups = _r[1]; var isInputFocused = useRef(false); var wrapperProps = useMemo(function () { return ({ className: classnames(className, STYLE.wrapper), style: __assign({ '--local-width': width }, style), id: id, 'data-input-have-value': !!inputValue, 'data-error': error, }); }, [className, width, style, id, inputValue, error]); var disabledKeys = __spreadArray([KEYS.INPUT_SEARCH_NO_RESULT], disabledKeysPayload, true); // utils var searchItem = useCallback(function (key, groups) { return searchItemFunc(key, groups); }, []); var handleFocusBackToInput = useCallback(function () { var _a; (_a = inputRef === null || inputRef === void 0 ? void 0 : inputRef.current) === null || _a === void 0 ? void 0 : _a.focus(); }, [inputRef.current]); var handleFilter = useCallback(function (currentInputValue) { if (currentInputValue === void 0) { currentInputValue = ''; } if (currentInputValue) { var filterGroup = handleFilterFunc(originComboBoxGroups, currentInputValue); setGroups(filterGroup); } else { setGroups(originComboBoxGroups); } }, [originComboBoxGroups]); var handleItemFocus = useCallback(function () { var _a; var listItems = (_a = containerRef === null || containerRef === void 0 ? void 0 : containerRef.current) === null || _a === void 0 ? void 0 : _a.querySelectorAll('li[role="menuitemradio"][aria-disabled="false"]'); if (listItems === null || listItems === void 0 ? void 0 : listItems.length) { var selectedItem = Array.from(listItems).find(function (item) { return (item === null || item === void 0 ? void 0 : item.ariaChecked) === 'true'; }); if (selectedItem) { // prioritize focusing on the selected item. selectedItem.focus(); } else { listItems[0].focus(); } } setShouldFocusItem(false); }, [containerRef.current]); // Used to prevent the bottom of the list from exceeding the window boundary. var handleSelectionContainerMaxHeight = useCallback(function () { var _a; var windowHeight = window.innerHeight; var bottom = ((_a = inputRef === null || inputRef === void 0 ? void 0 : inputRef.current) === null || _a === void 0 ? void 0 : _a.getBoundingClientRect()).bottom; var inputDistanceFromViewportBottom = windowHeight - bottom; if (inputDistanceFromViewportBottom < ELEMENT.PROPS.SELECTION_CONTAINER_MAX_HEIGHT + 8) { setSelectionContainerMaxHeight(inputDistanceFromViewportBottom - 8); } else { setSelectionContainerMaxHeight(ELEMENT.PROPS.SELECTION_CONTAINER_MAX_HEIGHT); } }, [inputRef.current]); // Since the positioning of the list uses ‘fix’, it is necessary to calculate the height of all scroll bars of the list’s ancestor elements, // and then set the translate to prevent the positioning of the list from being affected by the scroll bars of the ancestor elements. var handleSelectionTranslate = useCallback(function () { if (isOpen && (inputRef === null || inputRef === void 0 ? void 0 : inputRef.current) && selectionPositionRef.current) { var scrollTop = getSumScrollTopFunc(inputRef.current); selectionPositionRef.current.style.transform = "translateY(".concat(-scrollTop, "px)"); } }, [inputRef.current, isOpen]); // event var handlerStopPropagation = useCallback(function (event) { if (event.code === EVENT.KEY.KEYCODE.ESCAPE) { event.stopPropagation(); } }, []); var handleTriggerOutsideForList = useCallback(function (event) { var _a; if (isOpen) { if ((containerRef === null || containerRef === void 0 ? void 0 : containerRef.current) && !((_a = containerRef === null || containerRef === void 0 ? void 0 : containerRef.current) === null || _a === void 0 ? void 0 : _a.contains(event.target))) { setIsOpen(false); } } }, [containerRef.current, isOpen]); var handleTriggerOutsideForInput = useCallback(function (event) { var _a, _b; if ((containerRef === null || containerRef === void 0 ? void 0 : containerRef.current) && !((_a = containerRef === null || containerRef === void 0 ? void 0 : containerRef.current) === null || _a === void 0 ? void 0 : _a.contains(event.target))) { setIsOpen(false); var currentItem = searchItem(selectedKey, originComboBoxGroups); handleFilter(currentItem.label); setInputValue((_b = currentItem.label) !== null && _b !== void 0 ? _b : ''); } }, [containerRef.current, selectedKey, originComboBoxGroups, handleFilter, searchItem]); var handleItemFocusChange = useCallback(function (event) { var _a; var item = event.target; var itemRect = item.getBoundingClientRect(); var ulRect = (_a = menuRef === null || menuRef === void 0 ? void 0 : menuRef.current) === null || _a === void 0 ? void 0 : _a.getBoundingClientRect(); if (itemRect.top < ulRect.top || itemRect.bottom > ulRect.bottom) { item.scrollIntoView(); } }, [menuRef.current]); var handlePreventScroll = useCallback(function (event) { var _a; if (isOpen && (selectionPositionRef === null || selectionPositionRef === void 0 ? void 0 : selectionPositionRef.current)) { if (!((_a = selectionPositionRef === null || selectionPositionRef === void 0 ? void 0 : selectionPositionRef.current) === null || _a === void 0 ? void 0 : _a.contains(event.target))) { event.preventDefault(); } } }, [selectionPositionRef.current, isOpen]); var handleInputKeyDown = useCallback(function (event) { if (event.code === EVENT.KEY.KEYCODE.ESCAPE) { if (isOpen) { setIsOpen(false); } else { setInputValue(''); handleFilter(); } } if ((!isOpen && event.code === EVENT.KEY.KEYCODE.ENTER) || event.code === EVENT.KEY.KEYCODE.ARROW_DOWN || event.code === EVENT.KEY.KEYCODE.ARROW_UP) { setShouldFocusItem(true); setIsOpen(true); } }, [isOpen, handleFilter]); var handleMenuKeyDown = useCallback(function (event) { if (event.code === EVENT.KEY.KEYCODE.ESCAPE) { setIsOpen(false); handleFocusBackToInput(); } else if (event.code === EVENT.KEY.KEYCODE.TAB) { // when the focus is on the listItem, prevent focus escape event.preventDefault(); } }, [handleFocusBackToInput]); var handleGetFocusEle = useCallback(function (event) { var _a; setIsFocused((_a = containerRef === null || containerRef === void 0 ? void 0 : containerRef.current) === null || _a === void 0 ? void 0 : _a.contains(event.target)); }, [containerRef.current]); var handleGetPreFocusEle = useCallback(function (event) { var _a; setIsPreFocused((_a = containerRef === null || containerRef === void 0 ? void 0 : containerRef.current) === null || _a === void 0 ? void 0 : _a.contains(event.target)); }, [containerRef.current]); var handleGetInputFocus = useCallback(function (event) { var _a; isInputFocused.current = (_a = inputRef === null || inputRef === void 0 ? void 0 : inputRef.current) === null || _a === void 0 ? void 0 : _a.contains(event.target); }, [inputRef.current]); var handleInputFocus = useCallback(function () { if (!isOpen) { handleFilter(inputValue); } }, [handleFilter, inputValue, isOpen]); // effect useEffect(function () { if (openStateChangeCallBack) { openStateChangeCallBack(isOpen); } }, [openStateChangeCallBack, isOpen]); useEffect(function () { var _a; if (isInit) { // isInit is used to solve the case where the input is focused by default during initialization. // Since isInputFocused is used in the logic as the basis for whether to setInputValue, // special handling is needed if the focus is on the input during initialization. if (selectedKey) { // If ‘selected’ exists, a matching item must be found to complete the initialization. var currentItem = searchItem(selectedKey, originComboBoxGroups); if (currentItem.label) { handleFilter(currentItem.label); setInputValue(currentItem.label); setIsInit(false); } } else { setIsInit(false); } } else { if (isInputFocused.current) { // If the focus is on the input, it indicates that the user may be operating the input, // so here we do not want the inputValue to change spontaneously. handleFilter(inputValue); } else { var currentItem = searchItem(selectedKey, originComboBoxGroups); handleFilter(currentItem.label); setInputValue((_a = currentItem.label) !== null && _a !== void 0 ? _a : ''); } } }, [originComboBoxGroups, selectedKey, inputValue, isInit, handleFilter, searchItem]); useEffect(function () { handleSelectionTranslate(); handleSelectionContainerMaxHeight(); }, [handleSelectionTranslate, handleSelectionContainerMaxHeight]); useEffect(function () { var _a; document.addEventListener('focusin', handleGetFocusEle); (_a = containerRef === null || containerRef === void 0 ? void 0 : containerRef.current) === null || _a === void 0 ? void 0 : _a.addEventListener('focusout', handleGetPreFocusEle); return function () { var _a; document.removeEventListener('focusin', handleGetFocusEle); (_a = containerRef === null || containerRef === void 0 ? void 0 : containerRef.current) === null || _a === void 0 ? void 0 : _a.removeEventListener('focusout', handleGetPreFocusEle); }; }, [containerRef.current, handleGetFocusEle, handleGetPreFocusEle]); useEffect(function () { var _a; // Fix the issue where focusing on the input in certain situations does not correctly filter the expanded items (_a = inputRef === null || inputRef === void 0 ? void 0 : inputRef.current) === null || _a === void 0 ? void 0 : _a.addEventListener('focus', handleInputFocus); return function () { var _a; (_a = inputRef === null || inputRef === void 0 ? void 0 : inputRef.current) === null || _a === void 0 ? void 0 : _a.removeEventListener('focus', handleInputFocus); }; }, [inputRef.current, handleInputFocus]); useEffect(function () { document.addEventListener('mousedown', handleTriggerOutsideForList); return function () { document.removeEventListener('mousedown', handleTriggerOutsideForList); }; }, [handleTriggerOutsideForList]); useEffect(function () { document.addEventListener('mousedown', handleTriggerOutsideForInput); return function () { document.removeEventListener('mousedown', handleTriggerOutsideForInput); }; }, [handleTriggerOutsideForInput]); useEffect(function () { window.addEventListener('mousewheel', handlePreventScroll, { passive: false }); return function () { window.removeEventListener('mousewheel', handlePreventScroll); }; }, [handlePreventScroll]); useEffect(function () { var _a; (_a = containerRef === null || containerRef === void 0 ? void 0 : containerRef.current) === null || _a === void 0 ? void 0 : _a.addEventListener('keydown', handlerStopPropagation); return function () { var _a; (_a = containerRef === null || containerRef === void 0 ? void 0 : containerRef.current) === null || _a === void 0 ? void 0 : _a.removeEventListener('keydown', handlerStopPropagation); }; }, [containerRef.current, handlerStopPropagation]); useEffect(function () { document.addEventListener('focusin', handleGetInputFocus); return function () { document.removeEventListener('focusin', handleGetInputFocus); }; }, [handleGetInputFocus]); useEffect(function () { var _a; (_a = menuRef === null || menuRef === void 0 ? void 0 : menuRef.current) === null || _a === void 0 ? void 0 : _a.addEventListener('focusin', handleItemFocusChange); return function () { var _a; (_a = menuRef === null || menuRef === void 0 ? void 0 : menuRef.current) === null || _a === void 0 ? void 0 : _a.removeEventListener('focusin', handleItemFocusChange); }; }, [menuRef.current, handleItemFocusChange]); useEffect(function () { var _a; (_a = menuRef === null || menuRef === void 0 ? void 0 : menuRef.current) === null || _a === void 0 ? void 0 : _a.addEventListener('keydown', handleMenuKeyDown); return function () { var _a; (_a = menuRef === null || menuRef === void 0 ? void 0 : menuRef.current) === null || _a === void 0 ? void 0 : _a.removeEventListener('keydown', handleMenuKeyDown); }; }, [menuRef.current, handleMenuKeyDown]); useEffect(function () { var _a; (_a = inputRef === null || inputRef === void 0 ? void 0 : inputRef.current) === null || _a === void 0 ? void 0 : _a.addEventListener('keydown', handleInputKeyDown); return function () { var _a; (_a = inputRef === null || inputRef === void 0 ? void 0 : inputRef.current) === null || _a === void 0 ? void 0 : _a.removeEventListener('keydown', handleInputKeyDown); }; }, [inputRef === null || inputRef === void 0 ? void 0 : inputRef.current, handleInputKeyDown]); useEffect(function () { if (shouldFocusItem && isOpen) { handleItemFocus(); } }, [shouldFocusItem, isOpen, handleItemFocus]); // Used to handle the logic of inputValue when the focus switches from inside the component to outside. useEffect(function () { if (containerRef === null || containerRef === void 0 ? void 0 : containerRef.current) { if (!isFocused && isPreFocused) { if (selectedKey) { var currentItem = searchItem(selectedKey, originComboBoxGroups); setInputValue(currentItem.label); handleFilter(currentItem.label); } else { setInputValue(''); handleFilter(); } setIsOpen(false); } } }, [ containerRef.current, isPreFocused, isFocused, selectedKey, originComboBoxGroups, searchItem, handleFilter, ]); // subcomponent event var onInputChange = useCallback(function (event) { var currentInputValue = event.target.value; if (!isOpen) { setIsOpen(true); } setInputValue(currentInputValue); handleFilter(currentInputValue); if (onInputChangeCallback) { onInputChangeCallback(event); } }, [isOpen, onInputChangeCallback, handleFilter]); var onAction = useCallback(function (key) { setSelectedKey(key); var currentItem = searchItem(key, originComboBoxGroups); setInputValue(currentItem.label); handleFilter(currentItem.label); if (onSelectionChangeCallback) { onSelectionChangeCallback(currentItem); } setIsOpen(false); handleFocusBackToInput(); }, [ originComboBoxGroups, handleFocusBackToInput, onSelectionChangeCallback, searchItem, handleFilter, ]); var onArrowButtonPress = useCallback(function (event) { if (!isOpen) { setShouldFocusItem(true); if (!shouldFilterOnArrowButton) { handleFilter(); } else { handleFilter(inputValue); } } if (onArrowButtonPressCallback) { onArrowButtonPressCallback(event); } setIsOpen(!isOpen); }, [isOpen, inputValue, shouldFilterOnArrowButton, handleFilter, onArrowButtonPressCallback]); return (React.createElement(React.Fragment, null, label ? React.createElement("div", { className: STYLE.label }, label) : null, React.createElement("div", __assign({}, wrapperProps, { ref: containerRef }), React.createElement("div", { className: STYLE.inputSection }, React.createElement(TextInput, { ref: inputRef, "aria-label": STYLE.input, // ComboBox hides the clear button from the input therefore set the clear aria label to empty string clearAriaLabel: '', placeholder: placeholder, value: inputValue, className: STYLE.input, onInput: onInputChange, autoComplete: "off" }), React.createElement("div", { className: STYLE.divider }), React.createElement(ButtonPill, { variant: "tertiary", onClick: onArrowButtonPress, className: STYLE.button, stopPropagation: false }, React.createElement(Icon, { className: STYLE.arrowIcon, name: isOpen ? 'arrow-up' : 'arrow-down', scale: 12, weight: "filled" }))), isOpen ? (React.createElement("div", { className: STYLE.selectionPosition, ref: selectionPositionRef }, React.createElement("div", { className: STYLE.selectionContainer, ref: menuRef, style: { maxHeight: "".concat(selectionContainerMaxHeight, "px") } }, React.createElement(Menu, { selectionMode: "single", "aria-label": STYLE.selection, items: groups, onAction: onAction, className: STYLE.selection, selectedKeys: [selectedKey], disabledKeys: disabledKeys }, groups.length ? (children) : (React.createElement(Item, { key: KEYS.INPUT_SEARCH_NO_RESULT, textValue: noResultText }, React.createElement("div", { "aria-label": STYLE.noResultText, className: STYLE.noResultText }, noResultText))))))) : null), description && React.createElement("div", { className: STYLE.description }, description))); }; export default ComboBox; //# sourceMappingURL=ComboBox.js.map