@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
JavaScript
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 });
};