UNPKG

@grafana/ui

Version:
316 lines (309 loc) • 11.4 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); var jsxRuntime = require('react/jsx-runtime'); var css = require('@emotion/css'); var reactVirtual = require('@tanstack/react-virtual'); var downshift = require('downshift'); var React = require('react'); var i18n = require('@grafana/i18n'); var ThemeContext = require('../../themes/ThemeContext.cjs'); var FieldContext = require('../Forms/FieldContext.cjs'); var Icon = require('../Icon/Icon.cjs'); var AutoSizeInput = require('../Input/AutoSizeInput.cjs'); var Input = require('../Input/Input.cjs'); var Portal = require('../Portal/Portal.cjs'); var ComboboxList = require('./ComboboxList.cjs'); var SuffixIcon = require('./SuffixIcon.cjs'); var filter = require('./filter.cjs'); var getComboboxStyles = require('./getComboboxStyles.cjs'); var useComboboxFloat = require('./useComboboxFloat.cjs'); var useOptions = require('./useOptions.cjs'); var utils = require('./utils.cjs'); function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; } var React__default = /*#__PURE__*/_interopDefaultCompat(React); "use strict"; const noop = () => { }; const VIRTUAL_OVERSCAN_ITEMS = 4; const Combobox = (props) => { var _a; const { options: allOptions, onChange, value: valueProp, placeholder: placeholderProp, isClearable, // this should be default false, but TS can't infer the conditional type if you do createCustomValue = false, customValueDescription, id: idProp, width, minWidth, maxWidth, "aria-labelledby": ariaLabelledBy, "data-testid": dataTestId, autoFocus, onBlur, disabled: disabledProp, invalid: invalidProp, prefixIcon, noOptionsMessage, isOpen: isOpenProp, onIsOpenChange: onIsOpenChangeProp, loading: loadingProp } = props; const fieldContext = FieldContext.useFieldContext(); const id = idProp != null ? idProp : fieldContext.id; const disabled = disabledProp != null ? disabledProp : fieldContext.disabled; const invalid = invalidProp != null ? invalidProp : fieldContext.invalid; const value = typeof valueProp === "object" ? valueProp == null ? void 0 : valueProp.value : valueProp; const baseId = React.useId().replace(/:/g, "--"); const { options: filteredOptions, groupStartIndices, updateOptions, asyncLoading, asyncError, resetSearch } = useOptions.useOptions(allOptions, createCustomValue, customValueDescription); const isAsync = typeof allOptions === "function"; const selectedItemIndex = React.useMemo(() => { if (isAsync) { return null; } if (valueProp === void 0 || valueProp === null) { return null; } const index = allOptions.findIndex((option) => option.value === value); if (index === -1) { return null; } return index; }, [valueProp, allOptions, value, isAsync]); const selectedItem = React.useMemo(() => { if (valueProp === void 0 || valueProp === null) { return null; } if (selectedItemIndex !== null && !isAsync) { return allOptions[selectedItemIndex]; } return typeof valueProp === "object" ? valueProp : { value: valueProp, label: valueProp.toString() }; }, [selectedItemIndex, isAsync, valueProp, allOptions]); const menuId = `${baseId}-downshift-menu`; const labelId = `${baseId}-downshift-label`; const styles = ThemeContext.useStyles2(getComboboxStyles.getComboboxStyles); const onIsOpenChangeHandler = React.useCallback( (changes) => { var _a2; onIsOpenChangeProp == null ? void 0 : onIsOpenChangeProp(changes.isOpen); if (changes.isOpen && ((_a2 = changes.inputValue) != null ? _a2 : "") === "") { updateOptions(""); } if (!changes.isOpen) { resetSearch(); } }, [onIsOpenChangeProp, updateOptions, resetSearch] ); const rangeExtractor = React.useCallback( (range) => { const startIndex = Math.max(0, range.startIndex - range.overscan); const endIndex = Math.min(filteredOptions.length - 1, range.endIndex + range.overscan); const rangeToReturn = Array.from({ length: endIndex - startIndex + 1 }, (_, i) => startIndex + i); const firstDisplayedOption = filteredOptions[rangeToReturn[0]]; if (firstDisplayedOption == null ? void 0 : firstDisplayedOption.group) { const groupStartIndex = groupStartIndices.get(firstDisplayedOption.group); if (groupStartIndex !== void 0 && groupStartIndex < rangeToReturn[0]) { rangeToReturn.unshift(groupStartIndex); } } return rangeToReturn; }, [filteredOptions, groupStartIndices] ); const rowVirtualizer = reactVirtual.useVirtualizer({ count: filteredOptions.length, getScrollElement: () => scrollRef.current, estimateSize: (index) => { const firstGroupItem = utils.isNewGroup(filteredOptions[index], index > 0 ? filteredOptions[index - 1] : void 0); const hasDescription = "description" in filteredOptions[index]; const hasGroup = "group" in filteredOptions[index]; let itemHeight = getComboboxStyles.MENU_OPTION_HEIGHT; if (hasDescription) { itemHeight = getComboboxStyles.MENU_OPTION_HEIGHT_DESCRIPTION; } if (firstGroupItem && hasGroup) { itemHeight += getComboboxStyles.MENU_OPTION_HEIGHT; } return itemHeight; }, getItemKey: (index) => { var _a2, _b; return (_b = (_a2 = filteredOptions[index]) == null ? void 0 : _a2.value) != null ? _b : index; }, overscan: VIRTUAL_OVERSCAN_ITEMS, rangeExtractor }); const { isOpen, highlightedIndex, getInputProps, getMenuProps, getItemProps, selectItem } = downshift.useCombobox({ menuId, labelId, inputId: id, items: filteredOptions, itemToString: filter.itemToString, selectedItem, isItemDisabled: (item) => !!(item == null ? void 0 : item.infoOption), // Don't change downshift state in the onBlahChange handlers. Instead, use the stateReducer to make changes. // Downshift calls change handlers on the render after so you can get sync/flickering issues if you change its state // in them. // Instead, stateReducer is called in the same tick as state changes, before that state is committed and rendered. onSelectedItemChange: ({ selectedItem: selectedItem2 }) => { if (isClearable) { onChange(selectedItem2); } else if (selectedItem2 !== null) { onChange(selectedItem2); } }, defaultHighlightedIndex: selectedItemIndex != null ? selectedItemIndex : 0, scrollIntoView: () => { }, ...isOpenProp !== void 0 ? { isOpen: isOpenProp } : {}, onIsOpenChange: onIsOpenChangeHandler, onHighlightedIndexChange: ({ highlightedIndex: highlightedIndex2, type }) => { if (type !== downshift.useCombobox.stateChangeTypes.MenuMouseLeave) { rowVirtualizer.scrollToIndex(highlightedIndex2); } }, onStateChange: ({ inputValue: newInputValue, type, selectedItem: newSelectedItem }) => { switch (type) { case downshift.useCombobox.stateChangeTypes.InputChange: updateOptions(newInputValue != null ? newInputValue : ""); break; default: break; } }, stateReducer(state, actionAndChanges) { let { changes } = actionAndChanges; const menuBeingOpened = state.isOpen === false && changes.isOpen === true; const menuBeingClosed = state.isOpen === true && changes.isOpen === false; if (menuBeingOpened && changes.inputValue === state.inputValue) { changes = { ...changes, inputValue: "" }; } if (menuBeingClosed) { if (changes.selectedItem) { changes = { ...changes, inputValue: filter.itemToString(changes.selectedItem) }; } else if (changes.inputValue !== "") { changes = { ...changes, inputValue: "" }; } } return changes; } }); const { inputRef, floatingRef, floatStyles, scrollRef } = useComboboxFloat.useComboboxFloat(filteredOptions, isOpen); const isAutoSize = width === "auto"; const InputComponent = isAutoSize ? AutoSizeInput.AutoSizeInput : Input.Input; const placeholder = (isOpen ? filter.itemToString(selectedItem) : null) || placeholderProp; const loading = loadingProp || fieldContext.loading || asyncLoading; const inputSuffix = /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [ value !== void 0 && value === (selectedItem == null ? void 0 : selectedItem.value) && isClearable && /* @__PURE__ */ jsxRuntime.jsx( Icon.Icon, { name: "times", className: styles.clear, title: i18n.t("combobox.clear.title", "Clear value"), tabIndex: 0, role: "button", onClick: () => { selectItem(null); }, onKeyDown: (e) => { if (e.key === "Enter" || e.key === " ") { selectItem(null); } } } ), /* @__PURE__ */ jsxRuntime.jsx(SuffixIcon.SuffixIcon, { isLoading: loading || false, isOpen }) ] }); const { Wrapper, wrapperProps } = isAutoSize ? { Wrapper: "div", wrapperProps: { className: styles.adaptToParent } } : { Wrapper: React__default.default.Fragment }; const icon = (_a = selectedItem == null ? void 0 : selectedItem.icon) != null ? _a : prefixIcon; return /* @__PURE__ */ jsxRuntime.jsxs(Wrapper, { ...wrapperProps, children: [ /* @__PURE__ */ jsxRuntime.jsx( InputComponent, { width: isAutoSize ? void 0 : width, ...isAutoSize ? { minWidth, maxWidth } : {}, autoFocus, prefix: icon && /* @__PURE__ */ jsxRuntime.jsx(Icon.Icon, { name: icon }), disabled, invalid, className: styles.input, suffix: inputSuffix, ...getInputProps({ ref: inputRef, onChange: noop, // Empty onCall to avoid TS error https://github.com/downshift-js/downshift/issues/718 "aria-labelledby": ariaLabelledBy, // Label should be handled with the Field component placeholder, "data-testid": dataTestId, onKeyDown: (event) => { if (event.key === "Escape" && isOpen) { event.stopPropagation(); } }, onBlur }) } ), /* @__PURE__ */ jsxRuntime.jsx(Portal.Portal, { children: /* @__PURE__ */ jsxRuntime.jsx( "div", { className: css.cx(styles.menu, !isOpen && styles.menuClosed), style: { ...floatStyles, pointerEvents: "auto" // Override container's pointer-events: none }, ...getMenuProps({ ref: floatingRef, "aria-labelledby": ariaLabelledBy }), children: isOpen && /* @__PURE__ */ jsxRuntime.jsx( ComboboxList.ComboboxList, { loading, options: filteredOptions, highlightedIndex, selectedItems: selectedItem ? [selectedItem] : [], scrollRef, getItemProps, error: asyncError, noOptionsMessage } ) } ) }) ] }); }; exports.Combobox = Combobox; //# sourceMappingURL=Combobox.cjs.map