@grafana/ui
Version:
Grafana Components Library
316 lines (309 loc) • 11.4 kB
JavaScript
;
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);
;
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