UNPKG

@adaptabletools/adaptable

Version:

Powerful AG Grid extension which provides advanced, cutting-edge functionality to meet all DataGrid requirements

532 lines (531 loc) 34.9 kB
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime"; import * as React from 'react'; import { Combobox, ComboboxChip, ComboboxChips, ComboboxChipsInput, ComboboxClear, ComboboxCollection, ComboboxContent, ComboboxEmpty, ComboboxGroup, ComboboxInput, ComboboxItem, ComboboxLabel, ComboboxList, ComboboxPrimitive, ComboboxSeparator, ComboboxTrigger, ComboboxValue, useComboboxAnchor, RESIZABLE_VIRTUALIZED_LIST_CLASS, } from '../ui/combobox'; import { cn } from '../../lib/utils'; import { ChevronDownIcon, Loader2Icon, PlusIcon } from 'lucide-react'; import { useMemo } from 'react'; import { InputGroupAddon, InputGroupButton } from '../ui/input-group'; import { getItemTextLabel, toStringValue, toStringValues, valueToItem, valueToItems, } from './comboboxUtils'; import { VirtualizedList } from './VirtualizedList'; import { CheckBox } from '../CheckBox'; import { Tooltip, TooltipContent, TooltipTrigger } from '../ui/tooltip'; const getContentStyle = ({ maxLabelLength }) => { const labelWidth = `${maxLabelLength + 6}ch`; return { width: `max(${labelWidth}, calc(var(--ab-base-space) * 25), var(--anchor-width))`, minWidth: `min(var(--anchor-width), var(--available-width), 80vw)`, }; }; const getEmptyText = (props) => { if (props.isLoading) { return 'Loading...'; } return props.emptyText ?? 'No items found.'; }; const AdaptableComboboxList = (props) => { const virtualizerRef = React.useRef(null); const loadingClassName = props.isLoading ? 'twa:opacity-50' : undefined; const renderItem = (item) => { const isUncreatedCreatable = item.creatable && !item.created; const label = isUncreatedCreatable ? `Create "${item.label}"` : item.label; const itemLabel = props.renderItemLabel ? props.renderItemLabel(label, item) : label; const itemContent = isUncreatedCreatable ? (_jsxs(_Fragment, { children: [_jsx(PlusIcon, {}), " ", itemLabel] })) : (itemLabel); const itemText = getItemTextLabel(item); const tooltipText = props.showItemTooltip && itemText != null ? isUncreatedCreatable ? `Create "${itemText}"` : itemText : undefined; return (_jsx(ComboboxItem, { value: item, "aria-label": itemText ?? undefined, className: 'ab-Combobox-Row', disabled: item.disabled, renderCheckboxIndicator: props.renderCheckboxIndicator, children: tooltipText != null ? (_jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { render: _jsx("span", { className: "twa:flex twa:min-w-0 twa:flex-1 twa:items-center twa:gap-2" }), children: itemContent }), _jsx(TooltipContent, { side: "right", children: tooltipText })] })) : (itemContent) }, item.value)); }; const list = props.groups ? (_jsx(ComboboxList, { className: loadingClassName, children: (group, index) => (_jsxs(React.Fragment, { children: [_jsxs(ComboboxGroup, { items: group.items, children: [group.label && _jsx(ComboboxLabel, { children: group.label }), _jsx(ComboboxCollection, { children: renderItem })] }), index < (props.groups?.length || 0) - 1 && _jsx(ComboboxSeparator, {})] }, typeof group.label === 'string' ? group.label : `${index}`)) })) : props.virtualized ? (_jsx(ComboboxPrimitive.List, { className: cn(loadingClassName, RESIZABLE_VIRTUALIZED_LIST_CLASS), children: _jsx(VirtualizedList, { open: props.open, renderItemLabel: props.renderItemLabel, virtualizerRef: virtualizerRef, showItemTooltip: props.showItemTooltip, renderCheckboxIndicator: props.renderCheckboxIndicator }) })) : (_jsx(ComboboxList, { className: loadingClassName, children: renderItem })); return (_jsxs(_Fragment, { children: [props.showSelectAllCheckbox && (_jsx(ComboboxValue, { children: (selectedItems) => { const allChecked = props.selectAllCheckboxValue !== undefined ? props.selectAllCheckboxValue : selectedItems.length === props.totalCount ? true : selectedItems.length >= 1 ? null : false; return (_jsx(CheckBox, { checked: allChecked, onChange: (checked) => { props.onCheckAllChange?.(checked); }, onKeyDown: (e) => { if (e.key === 'Enter') { props.onCheckAllChange?.(!allChecked); } }, onMouseDown: (e) => { e.preventDefault(); }, className: cn('twa:m-0 twa:px-3 twa:py-1 twa:text-sm twa:font-normal twa:text-current twa:w-full twa:bg-primary', { 'twa:mt-1': props.searchable === 'menulist', [loadingClassName]: props.isLoading, }), children: "Select All" })); } })), _jsx(ComboboxEmpty, { children: props.emptyText }), list] })); }; export const SingleCombobox = (props) => { const searchable = props.searchable === false ? false : props.searchable === 'menulist' ? 'menulist' : 'inline'; const isOpenControlled = props.open !== undefined; const [open, setOpen] = React.useState(props.open ?? props.defaultOpen ?? false); const resolvedOpen = isOpenControlled ? props.open : open; const onOpenChange = (nextOpen) => { if (!isOpenControlled) setOpen(nextOpen); props.onOpenChange?.(nextOpen); }; const showItemTooltip = props.showItemTooltip ?? !!props.virtualized; return searchable === 'menulist' ? (_jsx(SingleCombobox_MenuListSearchable, { ...props, multiple: false, open: resolvedOpen, onOpenChange: onOpenChange, searchable: searchable, showItemTooltip: showItemTooltip })) : (_jsx(SingleCombobox_SimpleSearchable, { ...props, multiple: false, open: resolvedOpen, onOpenChange: onOpenChange, searchable: searchable, showItemTooltip: showItemTooltip })); }; const isItemEqualToValue = (a, b) => a?.value === b?.value; const AdaptableCombobox_Root = (props) => { const allItems = props.allItems; const itemByValue = props.itemByValue; const resolvedValue = props.multiple ? valueToItems(props.value, itemByValue) : valueToItem(props.value, itemByValue); const internalValue = props.value !== undefined ? resolvedValue ?? null : resolvedValue; const internalDefaultValue = props.multiple ? valueToItems(props.defaultValue, itemByValue) : valueToItem(props.defaultValue, itemByValue); const handleValueChange = (next, eventDetails) => { if (!props.onValueChange) return; if (eventDetails?.reason === 'escape-key' && !props.clearOnEscape) { eventDetails.cancel?.(); return; } if (Array.isArray(next)) { props.onValueChange(toStringValues(next), next); } else { props.onValueChange(toStringValue(next), next); } }; const handleInputValueChange = (next, eventDetails) => { if (eventDetails?.reason === 'escape-key' && !props.clearOnEscape) { eventDetails.cancel?.(); return; } if (props.searchable === 'menulist' && eventDetails?.reason === 'input-clear' && (props.open || props.persistFilterOnReopen)) { eventDetails.cancel?.(); return; } props.onInputValueChange?.(next); }; let comboboxItems = props.groups ?? props.items; if (props.isCreatable && allItems[allItems.length - 1]?.creatable) { const creatableItems = []; for (let len = allItems.length - 1; len >= 0; len--) { const item = allItems[len]; if (item.creatable) { creatableItems.unshift(item); } else { break; } } comboboxItems = [...comboboxItems, ...creatableItems]; } return (_jsx(Combobox, { ...props, items: comboboxItems, isItemEqualToValue: isItemEqualToValue, itemToStringLabel: getItemTextLabel, value: internalValue, defaultValue: internalDefaultValue, onValueChange: handleValueChange, onInputValueChange: handleInputValueChange, "aria-label": props.ariaLabel || props['aria-label'], "data-name": props['data-name'], disabled: props.disabled, open: props.open, onOpenChange: props.onOpenChange, multiple: props.multiple, filter: props.filter, autoHighlight: true, children: props.children })); }; const useMenulistFilterInput = (props, creatableInput) => { const isMenulistFilter = props.searchable === 'menulist' && !props.isCreatable; const [filterValue, setFilterValue] = React.useState(props.inputValue ?? props.defaultInputValue ?? ''); if (!isMenulistFilter) { return creatableInput; } const inputValue = props.inputValue !== undefined ? props.inputValue : filterValue; const onInputValueChange = (value) => { if (props.inputValue === undefined) { setFilterValue(value); } props.onInputValueChange?.(value); }; return { inputValue, onInputValueChange }; }; const useGetAllItems = (props) => { const allItems = props.groups ? props.groups.flatMap((g) => g.items) : props.isCreatable ? [].concat(props.items) : props.items; const itemByValue = useMemo(() => { return new Map(allItems.map((item) => [item.value, item])); }, [allItems]); const creatableInput = useCreatable(props, allItems, itemByValue); const { inputValue, onInputValueChange } = useMenulistFilterInput(props, { inputValue: creatableInput.inputValue, onInputValueChange: creatableInput.onInputValueChange, }); const { onValueChange, value } = creatableInput; return { value, itemByValue, inputValue, onInputValueChange, onValueChange, allItems, maxLabelLength: allItems.reduce((max, item) => Math.max(max, typeof item.label === 'string' || typeof item.label === 'number' ? String(item.label).length : typeof item.textLabel === 'string' || typeof item.textLabel === 'number' ? String(item.textLabel).length : 0), 0), }; }; function useCreatable(props, allItems, itemByValue) { const [alreadySavedCreatableItems, setAlreadySavedCreatableItems] = React.useState([]); const itemByValueRef = React.useRef(itemByValue); itemByValueRef.current = itemByValue; if (props.isCreatable && alreadySavedCreatableItems.length > 0) { alreadySavedCreatableItems.forEach((item) => { itemByValue.set(item.value, item); allItems.push(item); }); } const handleValueChange = (value, item) => { const itemsToCreate = Array.isArray(item) ? item.filter((item) => item.creatable && !item.created) : item && item.creatable && !item.created ? [item] : []; let savingCreatableItem = false; let itemsAllNumeric = false; let newCreatedItems = []; if (props.isCreatable && itemsToCreate.length > 0) { savingCreatableItem = true; itemsAllNumeric = allItems.every((item) => !item.creatable ? typeof item.value === 'number' : true); newCreatedItems = itemsToCreate.map((item) => ({ ...item, creatable: true, created: true, value: itemsAllNumeric && !Number.isNaN(Number(item.value)) ? Number(item.value) : item.value, })); setAlreadySavedCreatableItems((current) => { const mergedItems = [...current, ...newCreatedItems]; const seenValues = new Set(); return mergedItems.filter((item) => { const key = `${item.value}`; if (seenValues.has(key)) { return false; } seenValues.add(key); return true; }); }); } if (savingCreatableItem) { if (props.multiple) { const createdByOriginalValue = new Map(); itemsToCreate.forEach((it, idx) => { createdByOriginalValue.set(it.value, newCreatedItems[idx]); }); const mergedItems = item.map((existing) => { return createdByOriginalValue.get(existing.value) ?? existing; }); const mergedValues = mergedItems.map((i) => i.value); props.onValueChange?.(mergedValues, mergedItems); } else { props.onValueChange?.(newCreatedItems[0].value, newCreatedItems[0]); } } else { props.onValueChange?.(value, item); } }; const [defaultControlledValue, setDefaultControlledValue] = React.useState(props.defaultValue ?? (props.multiple ? [] : null)); const value = props.value === undefined ? defaultControlledValue : props.value; let initialInputValue = props.inputValue ?? props.defaultInputValue ?? undefined; if (initialInputValue === undefined) { if (!props.multiple && value != undefined) { const item = itemByValue.get(value); if (item) { initialInputValue = getItemTextLabel(item); } else if (props.isCreatable) { initialInputValue = `${value}`; } } if (initialInputValue === undefined) { initialInputValue = ''; } } const [inputValue, setInputValue] = React.useState(initialInputValue ? `${initialInputValue}` : ''); React.useEffect(() => { if (!props.multiple) { const item = itemByValueRef.current.get(props.value); if (item) { setInputValue(getItemTextLabel(item)); } else if (props.isCreatable && props.value != undefined) { setInputValue(`${props.value}`); } } }, [props.value, props.multiple, props.isCreatable]); if (props.isCreatable) { if (inputValue) { const newValue = inputValue.trim(); const normalized = newValue.toLocaleLowerCase().replace(/\s+/g, '-'); const exactExists = allItems.some((item) => { const normalizedItemValue = `${item.value}`.trim().toLocaleLowerCase().replace(/\s+/g, '-'); return normalizedItemValue === normalized; }); if (newValue !== '' && !exactExists) { const creatableValue = !props.multiple && props.value != undefined && `${props.value}` === newValue ? props.value : newValue; const creatableItem = { creatable: true, created: false, value: creatableValue, label: newValue, }; itemByValue.set(creatableValue, creatableItem); allItems.push(creatableItem); } } } const onValueChange = (value, item) => { if (props.value === undefined) { setDefaultControlledValue(value); } if (!props.isCreatable) { props.onValueChange?.(value, item); } else { handleValueChange(value, item); } }; const handleInputValueChange = (value) => { setInputValue(value); props.onInputValueChange?.(value); }; const result = !props.isCreatable ? { inputValue: undefined, onInputValueChange: props.onInputValueChange, onValueChange, value, } : { inputValue, onInputValueChange: handleInputValueChange, onValueChange, value, }; return result; } const SingleCombobox_MenuListSearchable = (props) => { const emptyText = getEmptyText(props); const isDisabled = props.disabled; const showClear = props.showClear ?? true; const anchor = useComboboxAnchor(); const triggerClassName = cn('ab-ComboBox', 'twa:bg-input-background', props.className); const searchInputClassName = cn('ab-ComboBox', props.searchInputClassName ?? cn('twa:bg-input-background', props.className)); const triggerRef = React.useRef(null); const searchInputRef = React.useRef(null); const contentElRef = React.useRef(null); const contentRefCallback = React.useCallback((el) => { contentElRef.current = el; }, []); const hasFocusRef = React.useRef(false); const handleFocus = props.onFocus ? () => { if (!hasFocusRef.current) { hasFocusRef.current = true; props.onFocus(); } } : undefined; const handleBlur = props.onBlur ? (e) => { const relatedTarget = e.relatedTarget; const isInternal = triggerRef.current?.contains(relatedTarget) || relatedTarget === searchInputRef.current || contentElRef.current?.contains(relatedTarget); if (!isInternal) { hasFocusRef.current = false; props.onBlur(); } } : undefined; const { allItems, itemByValue, maxLabelLength, inputValue, onInputValueChange, onValueChange, value, } = useGetAllItems(props); return (_jsxs(AdaptableCombobox_Root, { ...props, value: value, allItems: allItems, itemByValue: itemByValue, inputValue: inputValue, onInputValueChange: onInputValueChange, onValueChange: onValueChange, children: [_jsx(ComboboxPrimitive.Trigger, { ref: triggerRef, render: _jsx("div", {}), nativeButton: false, "data-slot": "combobox-menulist-trigger", tabIndex: 0, "aria-label": props.ariaLabel || props['aria-label'], "data-name": props['data-name'], className: cn('twa:flex twa:w-full twa:cursor-default twa:items-center twa:justify-between twa:gap-1.5 twa:rounded-input twa:border twa:border-input twa:bg-transparent twa:py-2 twa:pr-2 twa:pl-2.5 twa:text-sm twa:whitespace-nowrap twa:shadow-xs twa:transition-[color,box-shadow] twa:outline-none twa:select-none twa:focus:border-ring twa:focus:ring-3 twa:focus:ring-ring/50 twa:disabled:cursor-not-allowed twa:disabled:opacity-50 twa:data-placeholder:text-muted-foreground twa:h-9', triggerClassName), disabled: isDisabled, onFocus: handleFocus, onBlur: handleBlur, children: _jsx(ComboboxValue, { placeholder: _jsx("span", { children: props.placeholder }), children: (value) => { return (_jsxs(React.Fragment, { children: [_jsx("span", { className: "twa:flex twa:flex-1 twa:truncate twa:text-left", "data-slot": "combobox-value", children: _jsx(ComboboxValue, { placeholder: _jsx("span", { children: props.placeholder }) }) }), _jsx("input", { type: "hidden", "data-slot": "combobox-value-input", readOnly: true, value: value ? getItemTextLabel(value) : '' }), props.isLoading && (_jsx(Loader2Icon, { className: "twa:size-4 twa:text-muted-foreground twa:animate-spin" })), value && showClear ? (_jsx(ComboboxClear, { disabled: isDisabled, onMouseDown: (e) => { e.preventBaseUIHandler(); } })) : props.showTrigger !== false ? (_jsx(InputGroupButton, { size: "icon-xs", variant: "ghost", className: 'twa:min-h-auto!', children: _jsx(ChevronDownIcon, { className: "twa:pointer-events-none twa:size-4 twa:shrink-0 twa:text-muted-foreground" }) })) : null] })); } }) }), _jsxs(ComboboxContent, { anchor: anchor, style: getContentStyle({ maxLabelLength }), resizable: props.resizable, contentRef: contentRefCallback, className: props.contentClassName, children: [_jsx(ComboboxInput, { className: searchInputClassName, placeholder: props.placeholder, showClear: false, showTrigger: false, disabled: isDisabled, ref: searchInputRef, onFocus: handleFocus, onBlur: handleBlur }), _jsx(AdaptableComboboxList, { renderItemLabel: props.renderItemLabel, searchable: props.searchable, emptyText: emptyText, groups: props.groups, virtualized: !!props.virtualized, showItemTooltip: !!props.showItemTooltip, open: props.open, isLoading: props.isLoading })] })] })); }; const SingleCombobox_SimpleSearchable = (props) => { const emptyText = getEmptyText(props); const anchor = useComboboxAnchor(); const className = cn('ab-ComboBox', 'twa:bg-input-background'); const showClear = props.showClear ?? true; const searchable = props.searchable === false ? false : 'inline'; const isDisabled = props.disabled; const { allItems, itemByValue, maxLabelLength, inputValue, onInputValueChange, onValueChange, value, } = useGetAllItems(props); return (_jsxs(AdaptableCombobox_Root, { ...props, value: value, itemByValue: itemByValue, inputValue: inputValue, onInputValueChange: onInputValueChange, onValueChange: onValueChange, allItems: allItems, children: [_jsx(ComboboxInput, { className: className, groupRef: anchor, placeholder: props.placeholder, showClear: showClear, showTrigger: props.showTrigger !== false, isLoading: props.isLoading, disabled: isDisabled, readOnly: searchable === false, "aria-label": props.ariaLabel || props['aria-label'], "data-name": props['data-name'], onFocus: props.onFocus, onBlur: props.onBlur }), _jsx(ComboboxContent, { anchor: anchor, style: getContentStyle({ maxLabelLength }), resizable: props.resizable, children: _jsx(AdaptableComboboxList, { searchable: props.searchable, renderItemLabel: props.renderItemLabel, emptyText: emptyText, groups: props.groups, virtualized: !!props.virtualized, showItemTooltip: !!props.showItemTooltip, open: props.open, isLoading: props.isLoading }) })] })); }; const MultiCombobox_SimpleSearchable = (props) => { const anchor = useComboboxAnchor(); const emptyText = getEmptyText(props); const showClear = props.showClear ?? true; const isDisabled = props.disabled; const { allItems, itemByValue, maxLabelLength, inputValue, onInputValueChange, onValueChange, value, } = useGetAllItems(props); const onCheckAllChange = props.onSelectAllCheckboxChange ?? ((checked) => { if (checked) { onValueChange?.(allItems.map((item) => item.value), allItems); } else { onValueChange?.([], []); } }); const [menuOpen, setMenuOpen] = React.useState(false); const onOpenChange = (open) => { setMenuOpen(open); props.onOpenChange(open); }; return (_jsxs(AdaptableCombobox_Root, { ...props, value: value, multiple: true, allItems: allItems, itemByValue: itemByValue, inputValue: inputValue, onInputValueChange: onInputValueChange, onValueChange: onValueChange, onOpenChange: onOpenChange, children: [_jsx(ComboboxChips, { "aria-label": props.ariaLabel || props['aria-label'], "data-name": props['data-name'], onClick: (e) => { if (e.target.closest('[data-slot="input-group"]')) { return; } if (!menuOpen) { onOpenChange(true); } }, ref: anchor, className: cn('twa:relative', props.className), children: _jsx(ComboboxValue, { children: (selectedItems) => { const shouldKeepTrigger = props.searchable === false && selectedItems.length > 0; return (_jsxs(React.Fragment, { children: [props.renderInputValues ? (props.renderInputValues(selectedItems)) : (_jsx(MultiComboboxChips, { selectedItems: selectedItems, showClear: showClear })), props.renderInput !== false ? (_jsxs(_Fragment, { children: [_jsx(ComboboxChipsInput, { placeholder: selectedItems.length > 0 ? '' : props.placeholder, readOnly: props.searchable === false, onFocus: props.onFocus, onBlur: props.onBlur, className: props.searchable === false && selectedItems.length > 0 ? 'twa:absolute! twa:inset-0 twa:min-h-auto! twa:max-h-auto!' : 'twa:flex-1' }), props.searchable === false && selectedItems.length > 0 ? (_jsx("div", { className: "twa:flex-1" })) : null, _jsx("div", { "data-slot": "input-group", role: "group", className: "twa:group/input-group twa:z-10", children: _jsxs(InputGroupAddon, { align: "inline-end", className: "twa:p-0 px twa:gap-0.5", children: [props.isLoading && (_jsx(Loader2Icon, { className: "twa:size-4 twa:text-muted-foreground twa:animate-spin" })), props.showTrigger !== false ? (_jsx(InputGroupButton, { size: "icon-xs", variant: "ghost", render: _jsx(ComboboxTrigger, { className: "twa:min-h-auto!" }), "data-slot": "combobox-toggle", className: cn(`twa:data-pressed:bg-transparent`, shouldKeepTrigger ? '' : 'twa:group-has-data-[slot=combobox-clear]/input-group:hidden'), disabled: isDisabled })) : null, showClear && _jsx(ComboboxClear, { disabled: isDisabled })] }) })] })) : null] })); } }) }), _jsx(ComboboxContent, { anchor: anchor, style: getContentStyle({ maxLabelLength }), resizable: props.resizable, children: _jsx(AdaptableComboboxList, { searchable: props.searchable, emptyText: emptyText, renderItemLabel: props.renderItemLabel, showSelectAllCheckbox: props.showSelectAllCheckbox, renderCheckboxIndicator: props.renderCheckboxIndicator ?? true, onCheckAllChange: onCheckAllChange, totalCount: allItems.length, groups: props.groups, virtualized: !!props.virtualized, showItemTooltip: !!props.showItemTooltip, open: props.open, isLoading: props.isLoading }) })] })); }; const MultiComboboxChips = (props) => { return (_jsx(_Fragment, { children: props.selectedItems.map((item) => (_jsx(ComboboxChip, { showRemove: props.showClear, onMouseDown: (e) => { e.preventBaseUIHandler?.(); }, className: 'twa:overflow-hidden', "aria-label": typeof item.label === 'string' ? item.label : undefined, children: item.label }, item.value))) })); }; const MultiCombobox_MenuListSearchable = (props) => { const anchor = useComboboxAnchor(); const emptyText = getEmptyText(props); const showClear = props.showClear ?? true; const isDisabled = props.disabled; const chipsInputRef = React.useRef(null); const chevronRef = React.useRef(null); const searchInputRef = React.useRef(null); const contentElRef = React.useRef(null); const contentRefCallback = React.useCallback((el) => { contentElRef.current = el; }, []); const { allItems, itemByValue, maxLabelLength, inputValue, onInputValueChange, onValueChange, value, } = useGetAllItems(props); const onCheckAllChange = props.onSelectAllCheckboxChange ?? ((checked) => { if (checked) { onValueChange?.(allItems.map((item) => item.value), allItems); } else { onValueChange?.([], []); } }); const hasFocusRef = React.useRef(false); const containerElRef = React.useRef(null); const isInternalFocusTarget = (target) => { return (containerElRef.current?.contains(target) || contentElRef.current?.contains(target)); }; const onOpenChange = (open) => { props.onOpenChange(open); if (!open && hasFocusRef.current) { requestAnimationFrame(() => { if (!isInternalFocusTarget(document.activeElement) && hasFocusRef.current) { hasFocusRef.current = false; props.onBlur?.(); } }); } }; const handleFocus = props.onFocus ? () => { if (!hasFocusRef.current) { hasFocusRef.current = true; props.onFocus(); } } : undefined; const handleBlur = props.onBlur ? (e) => { if (!isInternalFocusTarget(e.relatedTarget)) { hasFocusRef.current = false; props.onBlur(); } } : undefined; const renderChips = (_props) => { return (_jsx(ComboboxChips, { ref: anchor, "aria-label": props.ariaLabel || props['aria-label'], "data-name": props['data-name'], ..._props, onFocus: (e) => { containerElRef.current = e.currentTarget; _props?.onFocus?.(e); handleFocus?.(); }, onBlur: (event) => { _props?.onBlur?.(event); const newFocusedTarget = event.relatedTarget; const container = event.currentTarget; const isInternal = container.contains(newFocusedTarget) || contentElRef.current?.contains(newFocusedTarget); if (isInternal) { return; } onOpenChange(false); if (hasFocusRef.current) { hasFocusRef.current = false; props.onBlur?.(); } }, className: cn('twa:relative', props.className, _props?.className), children: _jsx(ComboboxValue, { children: (selectedItems) => { return (_jsxs(React.Fragment, { children: [props.renderInputValues ? (props.renderInputValues(selectedItems)) : (_jsx(MultiComboboxChips, { selectedItems: selectedItems, showClear: showClear })), props.renderInput !== false ? (_jsx(_Fragment, { children: _jsx(ComboboxChipsInput, { ref: chipsInputRef, placeholder: selectedItems.length > 0 ? '' : props.placeholder, readOnly: true, value: '', className: 'twa:flex-1' }) })) : (_jsx("div", { className: "twa:flex-1" })), _jsx("div", { "data-slot": "input-group", role: "group", className: "twa:group/input-group", children: _jsxs(InputGroupAddon, { align: "inline-end", className: "twa:p-0 px", children: [props.isLoading && (_jsx(Loader2Icon, { className: "twa:size-4 twa:text-muted-foreground twa:animate-spin" })), props.showTrigger !== false ? (_jsx(InputGroupButton, { size: "icon-xs", variant: "ghost", render: _jsx(ComboboxTrigger, { className: "twa:min-h-auto!", ref: chevronRef, "data-slot": "combobox-toggle", role: "widget" }), "data-slot": "combobox-toggle", className: cn(`twa:data-pressed:bg-transparent`, 'twa:group-has-data-[slot=combobox-clear]/input-group:hidden'), disabled: isDisabled })) : null, showClear && _jsx(ComboboxClear, { disabled: isDisabled })] }) })] })); } }) })); }; return (_jsxs(AdaptableCombobox_Root, { ...props, value: value, multiple: true, allItems: allItems, itemByValue: itemByValue, inputValue: inputValue, onInputValueChange: onInputValueChange, onValueChange: onValueChange, onOpenChange: onOpenChange, children: [props.renderInput === false ? (_jsx(ComboboxTrigger, { nativeButton: false, render: renderChips, icon: props.showTrigger !== false ? undefined : null })) : (renderChips(undefined)), _jsxs(ComboboxContent, { anchor: anchor, style: getContentStyle({ maxLabelLength }), resizable: props.resizable, contentRef: contentRefCallback, children: [_jsx(ComboboxInput, { placeholder: 'Filter...', showClear: false, ref: searchInputRef, showTrigger: false, disabled: isDisabled, onFocus: handleFocus, onBlur: handleBlur, role: "searchbox", className: 'twa:min-h-input', children: typeof props.renderSearchInputTrailing === 'function' ? props.renderSearchInputTrailing() : undefined }), _jsx(AdaptableComboboxList, { renderItemLabel: props.renderItemLabel, emptyText: emptyText, searchable: props.searchable, showSelectAllCheckbox: props.showSelectAllCheckbox, selectAllCheckboxValue: props.selectAllCheckboxValue, renderCheckboxIndicator: props.renderCheckboxIndicator ?? true, onCheckAllChange: onCheckAllChange, totalCount: allItems.length, groups: props.groups, virtualized: !!props.virtualized, showItemTooltip: !!props.showItemTooltip, open: props.open, isLoading: props.isLoading })] })] })); }; export const MultiCombobox = (props) => { const searchable = props.searchable === false ? false : props.searchable === 'menulist' ? 'menulist' : 'inline'; const [open, setOpen] = React.useState(props.open ?? props.defaultOpen ?? false); const onOpenChange = (open) => { setOpen(open); props.onOpenChange?.(open); }; const showItemTooltip = props.showItemTooltip ?? !!props.virtualized; return searchable === 'menulist' ? (_jsx(MultiCombobox_MenuListSearchable, { renderInput: false, ...props, multiple: true, open: open, onOpenChange: onOpenChange, searchable: searchable, showItemTooltip: showItemTooltip })) : (_jsx(MultiCombobox_SimpleSearchable, { ...props, open: open, multiple: true, onOpenChange: onOpenChange, searchable: searchable, showItemTooltip: showItemTooltip })); }; export const GRID_FILTER_COMBBOX_ADJUSTMENTS_CLASSNAME = cn('twa:[.ab-FloatingFilter_&]:border-none twa:[.ab-FloatingFilter_&]:inset-y-0.5 twa:[.ab-FloatingFilter_&]:inset-x-0 twa:[.ab-FloatingFilter_&]:absolute twa:[.ab-FloatingFilter_&]:shadow-none twa:[.ab-FloatingFilter_&]:min-h-auto!', 'twa:[.ab-FloatingFilter_&]:[line-height:1]', 'twa:[.ab-FloatingFilter_[data-slot=combobox-trigger]]:py-0', 'twa:[.ab-FloatingFilter_&]:[container-type:size]'); export const GridFilterCombobox = (props) => { const { showSelectedCount = false, placeholder = 'Select...', ...comboboxProps } = props; const sharedProps = { placeholder, showSelectAllCheckbox: true, searchable: 'menulist', renderInput: false, items: props.items ?? [], virtualized: true, resizable: true, showClear: false, showTrigger: false, renderInputValues: (items) => { let children = items.map((item) => getItemTextLabel(item)).join(', '); if (items.length === 0) { children = (_jsx("span", { "data-name": "placeholder", className: "twa:text-muted-foreground", children: placeholder })); } return (_jsx(_Fragment, { children: _jsxs("div", { className: "twa:text-ellipsis twa:overflow-hidden twa:whitespace-nowrap twa:flex-1000", "data-slot": "combobox-selected-values", children: [showSelectedCount && items.length > 0 && (_jsxs("span", { "data-name": "multiple-values-count", className: "twa:mr-0.5", children: ["(", items.length, ")"] })), children] }) })); }, }; const className = cn(GRID_FILTER_COMBBOX_ADJUSTMENTS_CLASSNAME, comboboxProps.className); const mergedProps = { ...sharedProps, ...comboboxProps, className, }; return _jsx(MultiCombobox, { ...mergedProps }); };