@momentum-ui/react-collaboration
Version:
Cisco Momentum UI Framework for React Collaboration Applications
404 lines • 22.2 kB
JavaScript
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