@tabula/ui-multi-selector
Version:
A MultiSelector allows users to select one or more items from a list of choices, or suggest own item.
1,003 lines (963 loc) • 28.3 kB
JavaScript
// src/UiMultiSelector/UiMultiSelector.tsx
import { useMemo as useMemo6 } from "react";
import { FloatingPortal } from "@floating-ui/react";
import { clsx as clsx6 } from "clsx";
// src/UiMultiSelector/assets/chevron.svg?svgr
import * as React from "react";
import { memo } from "react";
import { jsx } from "react/jsx-runtime";
var SvgChevron = (props) => /* @__PURE__ */ jsx("svg", { width: 16, height: 16, viewBox: "0 0 16 16", fill: "none", xmlns: "http://www.w3.org/2000/svg", ...props, children: /* @__PURE__ */ jsx("path", { fillRule: "evenodd", clipRule: "evenodd", d: "M3.126 6.168a.5.5 0 0 1 .706-.042L8 9.831l4.168-3.705a.5.5 0 0 1 .664.748l-4.5 4a.5.5 0 0 1-.664 0l-4.5-4a.5.5 0 0 1-.042-.706Z", fill: "currentColor" }) });
var Memo = memo(SvgChevron);
// src/shared.css.ts
var hasChevron = "tabula_ui_multi_selector__1svmv054";
var sizes = { small: "tabula_ui_multi_selector__1svmv052", medium: "tabula_ui_multi_selector__1svmv053" };
var variants = { accent: "tabula_ui_multi_selector__1svmv050", contrast: "tabula_ui_multi_selector__1svmv051" };
// src/UiMultiSelector/UiMultiSelector.css.ts
var chevron = "tabula_ui_multi_selector__df9y05b";
var label = "tabula_ui_multi_selector__df9y059";
var root = "tabula_ui_multi_selector__df9y058";
var search = "tabula_ui_multi_selector__df9y05a";
var state = { isEmpty: "tabula_ui_multi_selector__df9y053", isEmptySearch: "tabula_ui_multi_selector__df9y054", isWarning: "tabula_ui_multi_selector__df9y055", isInvalid: "tabula_ui_multi_selector__df9y056", isDisabled: "tabula_ui_multi_selector__df9y057" };
// src/Dropdown/Dropdown.tsx
import { forwardRef as forwardRef2 } from "react";
import { clsx as clsx2 } from "clsx/lite";
// src/Dropdown/Dropdown.css.ts
var divider = "tabula_ui_multi_selector__1i1fwfx4";
var hasIcons = "tabula_ui_multi_selector__1i1fwfx1";
var highlight = "tabula_ui_multi_selector__1i1fwfx2";
var icon = "tabula_ui_multi_selector__1i1fwfx5";
var isCurrent = "tabula_ui_multi_selector__1i1fwfx8";
var item = "tabula_ui_multi_selector__1i1fwfx6";
var key = "tabula_ui_multi_selector__1i1fwfx9";
var label2 = "tabula_ui_multi_selector__1i1fwfx7";
var root2 = "tabula_ui_multi_selector__1i1fwfx0";
var search2 = "tabula_ui_multi_selector__1i1fwfx3";
// src/Dropdown/Dropdown.Item.tsx
import { forwardRef } from "react";
import { clsx } from "clsx/lite";
import { jsx as jsx2, jsxs } from "react/jsx-runtime";
var DropdownItem = forwardRef(
({ children, completeKey, icon: Icon, isCurrent: isCurrent2, onClick, title }, ref) => /* @__PURE__ */ jsxs(
"button",
{
className: clsx(item, isCurrent2 && isCurrent),
onClick,
ref,
tabIndex: -1,
title,
type: "button",
children: [
Icon != null && /* @__PURE__ */ jsx2(Icon, { className: icon }),
/* @__PURE__ */ jsx2("span", { className: label2, children }),
isCurrent2 && /* @__PURE__ */ jsx2("span", { className: key, children: completeKey })
]
}
)
);
if (import.meta.env.DEV) {
DropdownItem.displayName = "DropdownItem";
}
// src/Dropdown/hooks/useController.ts
import {
useCallback,
useEffect,
useImperativeHandle,
useRef,
useState
} from "react";
function useController(controllerRef, { items, search: search3, selected }) {
const rootRef = useRef(null);
const currentRef = useRef(null);
const isHoveredRef = useRef(false);
const handleMouseEnter = useCallback(() => {
isHoveredRef.current = true;
}, []);
const handleMouseLeave = useCallback(() => {
isHoveredRef.current = false;
}, []);
const [currentIndex, setCurrentIndex] = useState(0);
useEffect(() => {
setCurrentIndex(0);
}, [search3, selected.size]);
useEffect(() => {
const { current } = currentRef;
if (current == null) {
return;
}
current.scrollIntoView({
block: "nearest"
});
}, [currentIndex]);
useImperativeHandle(
controllerRef,
() => ({
goToPrevious: () => {
if (isHoveredRef.current || items.length === 0) {
return;
}
setCurrentIndex((current) => {
return current === 0 ? items.length - 1 : current - 1;
});
},
goToNext: () => {
if (isHoveredRef.current || items.length === 0) {
return;
}
setCurrentIndex((current) => {
return current === items.length - 1 ? 0 : current + 1;
});
},
selectCurrent: () => {
if (isHoveredRef.current) {
return;
}
const item2 = items.at(currentIndex);
if (item2 == null) {
return;
}
item2.onSelect();
}
}),
[currentIndex, items]
);
return {
currentIndex,
onMouseEnter: handleMouseEnter,
onMouseLeave: handleMouseLeave,
rootRef,
currentRef
};
}
// src/Dropdown/hooks/useHasIcons.ts
import { useMemo } from "react";
function hasIcon(item2) {
return typeof item2 !== "string" && item2.icon != null;
}
function useHasIcons({
addFound,
allowsCustomValue,
options,
selectAll,
selectFound
}) {
return useMemo(() => {
if (options.some((it) => hasIcon(it))) {
return true;
}
return !allowsCustomValue && hasIcon(selectAll) || hasIcon(addFound) || hasIcon(selectFound);
}, [addFound, allowsCustomValue, options, selectAll, selectFound]);
}
// src/Dropdown/hooks/useItems/useItems.ts
import { useMemo as useMemo2 } from "react";
// src/const.ts
var searchPlaceholder = "{search}";
// src/Dropdown/hooks/useItems/match.ts
function match(rawTarget, rawPattern) {
if (rawPattern.length === 0) {
return [
true,
[
{
isMatches: false,
substring: rawTarget
}
]
];
}
const target = rawTarget.toLowerCase();
const pattern = rawPattern.toLowerCase();
const parts = [];
let lastIndex = 0;
while (true) {
const index = target.indexOf(pattern, lastIndex);
if (index === -1) {
break;
}
if (index > lastIndex) {
parts.push({
isMatches: false,
substring: rawTarget.slice(lastIndex, index)
});
}
lastIndex = index + pattern.length;
parts.push({
isMatches: true,
substring: rawTarget.slice(index, lastIndex)
});
}
if (lastIndex < target.length) {
parts.push({ isMatches: false, substring: rawTarget.slice(lastIndex) });
}
if (parts.length === 1) {
return [parts[0].isMatches, parts];
}
return [true, parts];
}
// src/Dropdown/hooks/useItems/renderFound.tsx
import { jsx as jsx3 } from "react/jsx-runtime";
function renderFound(label3, values) {
const [isMatches, parts] = match(label3, searchPlaceholder);
if (!isMatches) {
return label3;
}
return parts.map((it, index) => {
if (!it.isMatches) {
return it.substring;
}
const found = values.reduce((acc, value, valueIndex) => {
if (acc.length > 0) {
acc.push(/* @__PURE__ */ jsx3("span", { children: ",\xA0" }, `separator-${valueIndex}`));
}
acc.push(
/* @__PURE__ */ jsx3("span", { className: search2, children: value }, `value-${valueIndex}`)
);
return acc;
}, []);
return /* @__PURE__ */ jsx3("span", { children: found }, index);
});
}
// src/Dropdown/hooks/useItems/buildBulkCustomValue.ts
function buildBulkCustomValue({ addFound, onUpdate, values }) {
const handleClick = () => {
onUpdate("add-custom", values);
};
const { icon: icon2, label: label3 } = typeof addFound === "string" ? { label: addFound } : addFound;
return {
key: "bulk-custom-value",
icon: icon2,
label: renderFound(label3, values),
onSelect: handleClick
};
}
// src/Dropdown/hooks/useItems/buildCustomValue.tsx
function buildCustomValue({ addFound, onUpdate, search: search3 }) {
const handleClick = () => {
onUpdate("add-custom", [search3]);
};
const { icon: icon2, label: label3 } = typeof addFound === "string" ? { label: addFound } : addFound;
return {
key: "custom-value",
icon: icon2,
label: renderFound(label3, [search3]),
onSelect: handleClick,
hasDividerAfter: true
};
}
// src/Dropdown/hooks/useItems/renderParts.tsx
import { jsx as jsx4 } from "react/jsx-runtime";
function renderParts(parts) {
if (parts.length === 0 && !parts[0].isMatches) {
return parts[0].substring;
}
return parts.map(
(it, index) => it.isMatches ? /* @__PURE__ */ jsx4("span", { className: highlight, children: it.substring }, index) : it.substring
);
}
// src/Dropdown/hooks/useItems/buildItems.ts
function buildItems({
allowsCustomValue,
onUpdate,
options,
search: search3,
selected
}) {
const values = [];
const items = [];
if (allowsCustomValue && search3.length === 0) {
return [values, items];
}
for (const option of options) {
const { icon: icon2, label: label3, value } = typeof option === "string" ? { label: option, value: option } : option;
if (selected.has(value)) {
continue;
}
const [isMatches, parts] = match(label3 ?? value, search3);
if (!isMatches) {
continue;
}
values.push(value);
items.push({
key: `item-${value}`,
icon: icon2,
label: renderParts(parts),
title: label3,
onSelect: () => {
onUpdate("add", [value]);
}
});
}
return [values, items];
}
// src/Dropdown/hooks/useItems/buildSelectAll.ts
function buildSelectAll({
hasDividerAfter,
onUpdate,
options,
selectAll,
selected
}) {
const handleClick = () => {
const ids = options.reduce((result, it) => {
const value = typeof it === "string" ? it : it.value;
if (!selected.has(value)) {
result.push(value);
}
return result;
}, []);
onUpdate("add-all", ids);
};
const { icon: icon2, label: label3 } = typeof selectAll === "string" ? { label: selectAll } : selectAll;
return {
key: "select-all",
icon: icon2,
label: label3,
onSelect: handleClick,
hasDividerAfter
};
}
// src/Dropdown/hooks/useItems/buildSelectFound.ts
function buildSelectFound({ onUpdate, selectFound, search: search3, values }) {
const handleClick = () => {
onUpdate("add-found", values);
};
const { icon: icon2, label: label3 } = typeof selectFound === "string" ? { label: selectFound } : selectFound;
return {
key: "select-found",
icon: icon2,
label: renderFound(label3, [search3]),
onSelect: handleClick,
hasDividerAfter: true
};
}
// src/Dropdown/hooks/useItems/useItems.ts
function useItems({
addFound,
allowsCustomValue,
maxSelectedLimit,
onUpdate,
options,
search: search3,
selectAll,
selectFound,
selected
}) {
return useMemo2(() => {
const [values, items] = buildItems({ allowsCustomValue, onUpdate, options, search: search3, selected });
const hasSearch = search3.length > 0;
if (allowsCustomValue) {
if (hasSearch) {
items.unshift(buildCustomValue({ addFound, onUpdate, search: search3 }));
const fromSearchValues = search3.split(/,\s|,/).filter((it) => it.trim().length > 0);
if (fromSearchValues.length > 1) {
items.unshift(
buildBulkCustomValue({
addFound,
onUpdate,
values: fromSearchValues
})
);
}
}
return items;
}
const isSatisfiesSelectedLimits = maxSelectedLimit == null || maxSelectedLimit > 1;
if (isSatisfiesSelectedLimits && items.length > 1) {
if (hasSearch) {
items.unshift(buildSelectFound({ onUpdate, search: search3, selectFound, values }));
}
items.unshift(
buildSelectAll({ hasDividerAfter: !hasSearch, onUpdate, options, selectAll, selected })
);
}
return items;
}, [
addFound,
allowsCustomValue,
maxSelectedLimit,
onUpdate,
options,
search3,
selected,
selectAll,
selectFound
]);
}
// src/Dropdown/Dropdown.tsx
import { jsx as jsx5 } from "react/jsx-runtime";
var Dropdown = forwardRef2(
({
addFound,
allowsCustomValue,
className,
completeKey,
maxSelectedLimit,
onUpdate,
options,
search: search3,
selectAll,
selectFound,
selected
}, ref) => {
const items = useItems({
addFound,
allowsCustomValue,
maxSelectedLimit,
onUpdate,
options,
search: search3,
selectAll,
selectFound,
selected
});
const { currentIndex, currentRef, onMouseEnter, onMouseLeave, rootRef } = useController(ref, {
items,
selected,
search: search3
});
const hasIcons2 = useHasIcons({ addFound, allowsCustomValue, options, selectAll, selectFound });
if (items.length === 0) {
return null;
}
const nodes = [];
for (const [idx, item2] of items.entries()) {
const { key: key2, icon: icon2, onSelect, label: label3, title, hasDividerAfter } = item2;
nodes.push(
/* @__PURE__ */ jsx5(
DropdownItem,
{
completeKey,
icon: icon2,
isCurrent: idx === currentIndex,
onClick: onSelect,
ref: idx === currentIndex ? currentRef : void 0,
title,
children: label3
},
key2
)
);
if (hasDividerAfter) {
nodes.push(/* @__PURE__ */ jsx5("div", { className: divider }, `${item2.key}-divider`));
}
}
return (
/* eslint-disable-next-line jsx-a11y/no-static-element-interactions */
/* @__PURE__ */ jsx5(
"div",
{
className: clsx2(root2, hasIcons2 && hasIcons, className),
onMouseEnter,
onMouseLeave,
ref: rootRef,
tabIndex: -1,
children: nodes
}
)
);
}
);
if (import.meta.env.DEV) {
Dropdown.displayName = "Dropdown";
}
// src/Search/Search.tsx
import { forwardRef as forwardRef3, useMemo as useMemo3 } from "react";
import { clsx as clsx3 } from "clsx/lite";
// src/Search/Search.css.ts
var root3 = "tabula_ui_multi_selector__1ih62u60";
// src/Search/Search.hooks.ts
import { useCallback as useCallback2 } from "react";
function useHandlers({
completeKey,
onArrowDown,
onArrowUp,
onBlurByTab,
onComplete,
onEscape,
onSearch
}) {
const handleChange = useCallback2(
(event) => {
onSearch(event.target.value);
},
[onSearch]
);
const handleKeyDown = useCallback2(
(event) => {
if (event.key === "Tab") {
if (completeKey === "Tab") {
event.preventDefault();
onComplete();
} else {
onBlurByTab();
}
return;
}
if (event.key === "Enter") {
if (completeKey === "Enter") {
event.preventDefault();
onComplete();
}
return;
}
if (event.key !== "ArrowDown" && event.key !== "ArrowUp" && event.key !== "Escape") {
return;
}
event.preventDefault();
switch (event.key) {
case "ArrowDown": {
onArrowDown();
break;
}
case "ArrowUp": {
onArrowUp();
break;
}
case "Escape": {
onEscape();
break;
}
}
},
[completeKey, onArrowDown, onArrowUp, onBlurByTab, onComplete, onEscape]
);
return { onChange: handleChange, onKeyDown: handleKeyDown };
}
// src/Search/Search.tsx
import { jsx as jsx6 } from "react/jsx-runtime";
var Search = forwardRef3(
({ className, id, isDisabled, onFocus, placeholder, value, ...handlers }, ref) => {
const { onChange, onKeyDown } = useHandlers(handlers);
const size2 = useMemo3(() => {
if (placeholder != null) {
return;
}
return Math.max(1, value.length);
}, [placeholder, value.length]);
return /* @__PURE__ */ jsx6(
"input",
{
className: clsx3(root3, className),
disabled: isDisabled,
id,
onChange,
onFocus,
onKeyDown,
placeholder,
ref,
size: size2,
value
}
);
}
);
if (import.meta.env.DEV) {
Search.displayName = "Search";
}
// src/Tags/Tags.tsx
import { clsx as clsx5 } from "clsx/lite";
// src/Tags/Tags.css.ts
var clear = "tabula_ui_multi_selector__1m6zcss3";
var list = "tabula_ui_multi_selector__1m6zcss4";
var root4 = "tabula_ui_multi_selector__1m6zcss2";
var state2 = { isEmpty: "tabula_ui_multi_selector__1m6zcss0", isDisabled: "tabula_ui_multi_selector__1m6zcss1" };
var tag = "tabula_ui_multi_selector__1m6zcss5";
// src/Clear/Clear.tsx
import { useCallback as useCallback3 } from "react";
import { clsx as clsx4 } from "clsx/lite";
// src/Clear/assets/clear.svg?svgr
import * as React2 from "react";
import { memo as memo2 } from "react";
import { jsx as jsx7 } from "react/jsx-runtime";
var SvgClear = (props) => /* @__PURE__ */ jsx7("svg", { width: 16, height: 16, viewBox: "0 0 16 16", fill: "none", xmlns: "http://www.w3.org/2000/svg", ...props, children: /* @__PURE__ */ jsx7("path", { fillRule: "evenodd", clipRule: "evenodd", d: "M4.146 4.146a.5.5 0 0 1 .708 0L8 7.293l3.146-3.147a.5.5 0 0 1 .708.708L8.707 8l3.147 3.146a.5.5 0 0 1-.708.708L8 8.707l-3.146 3.147a.5.5 0 0 1-.708-.708L7.293 8 4.146 4.854a.5.5 0 0 1 0-.708Z", fill: "currentColor" }) });
var Memo2 = memo2(SvgClear);
// src/Clear/Clear.css.ts
var root5 = "tabula_ui_multi_selector__1op479k0";
// src/Clear/Clear.tsx
import { jsx as jsx8 } from "react/jsx-runtime";
function Clear({ className, onUpdate }) {
const handleClick = useCallback3(() => {
onUpdate("remove-all", []);
}, [onUpdate]);
return /* @__PURE__ */ jsx8(
"button",
{
className: clsx4(root5, className),
onClick: handleClick,
tabIndex: -1,
type: "button",
children: /* @__PURE__ */ jsx8(Memo2, {})
}
);
}
// src/Tags/Tags.hooks.ts
import { useMemo as useMemo4 } from "react";
function useTags({ allowsCustomValue, options, selected }) {
return useMemo4(() => {
const optionsMap = /* @__PURE__ */ new Map();
for (const option of options) {
const value = typeof option === "string" ? option : option.value;
if (selected.has(value)) {
optionsMap.set(value, option);
}
}
const selectedOptions = [];
for (const value of selected) {
const option = optionsMap.get(value);
if (option == null) {
if (allowsCustomValue) {
selectedOptions.push(value);
}
} else {
selectedOptions.push(option);
}
}
return selectedOptions;
}, [selected, options, allowsCustomValue]);
}
// src/Tags/Tags.tsx
import { jsx as jsx9, jsxs as jsxs2 } from "react/jsx-runtime";
function Tags({
allowsCustomValue,
children,
isDisabled,
onUpdate,
options,
renderTag,
selected
}) {
const tags = useTags({ allowsCustomValue, options, selected });
return /* @__PURE__ */ jsxs2(
"div",
{
className: clsx5(
root4,
isDisabled && state2.isDisabled,
tags.length === 0 && state2.isEmpty
),
children: [
/* @__PURE__ */ jsx9(Clear, { className: clear, onUpdate }),
/* @__PURE__ */ jsxs2("div", { className: list, children: [
tags.map((it) => renderTag(tag, it)),
children
] })
]
}
);
}
// src/UiMultiSelector/hooks/useDropdown.ts
import { useCallback as useCallback4, useMemo as useMemo5, useRef as useRef2 } from "react";
import {
autoUpdate,
flip,
offset,
size,
useDismiss,
useFloating,
useInteractions,
useTransitionStyles
} from "@floating-ui/react";
import { useFlag } from "@tabula/use-flag";
function useDropdown() {
const dropdownRef = useRef2(null);
const [open, { on: onShowDropdown, off: onHideDropdown, change: onToggleDropdown }] = useFlag(false);
const handleHideDropdown = useCallback4(() => {
setTimeout(onHideDropdown, 0);
}, [onHideDropdown]);
const handleGoNext = useCallback4(() => {
dropdownRef.current?.goToNext();
}, []);
const handleGoPrevious = useCallback4(() => {
dropdownRef.current?.goToPrevious();
}, []);
const handleSelect = useCallback4(() => {
dropdownRef.current?.selectCurrent();
}, []);
const { refs, context, floatingStyles } = useFloating({
placement: "bottom-start",
strategy: "fixed",
open,
onOpenChange(state3) {
onToggleDropdown(state3);
},
middleware: [
offset({ mainAxis: 4 }),
flip(),
size({
apply({ rects, elements }) {
Object.assign(elements.floating.style, {
width: `${rects.reference.width}px`
});
}
})
],
whileElementsMounted(...args) {
return autoUpdate(...args, {
ancestorResize: true,
ancestorScroll: true,
elementResize: true
});
}
});
const dismiss = useDismiss(context, {
escapeKey: true,
outsidePress: true
});
const { getReferenceProps, getFloatingProps } = useInteractions([dismiss]);
const { isMounted: isOpen, styles: transitionStyles } = useTransitionStyles(context);
const style = useMemo5(
() => ({ ...floatingStyles, ...transitionStyles }),
[floatingStyles, transitionStyles]
);
return {
isOpen,
context,
dropdownRef,
floatingRef: refs.setFloating,
referenceRef: refs.setReference,
getReferenceProps,
getFloatingProps,
style,
onShowDropdown,
onHideDropdown: handleHideDropdown,
onGoNext: handleGoNext,
onGoPrevious: handleGoPrevious,
onSelectCurrent: handleSelect
};
}
// src/UiMultiSelector/hooks/useSearch.ts
import { useCallback as useCallback5, useEffect as useEffect2, useId, useRef as useRef3, useState as useState2 } from "react";
function useSearch(isDisabled) {
const searchId = useId();
const searchRef = useRef3(null);
const [search3, setSearch] = useState2("");
useEffect2(() => {
if (isDisabled) {
setSearch("");
}
}, [isDisabled]);
const onEscape = useCallback5(() => {
searchRef.current?.blur();
}, []);
return { onEscape, onSearch: setSearch, search: search3, searchId, searchRef };
}
// src/UiMultiSelector/hooks/useTagRenderer.tsx
import { useCallback as useCallback6 } from "react";
import { UiTag } from "@tabula/ui-tag";
import { jsx as jsx10 } from "react/jsx-runtime";
function useTagRenderer({ isDisabled, onUpdate, size: size2, variant }) {
return useCallback6(
(className, option) => {
const { icon: icon2, label: label3, value } = typeof option === "string" ? { value: option } : option;
const handleRemove = () => {
onUpdate("remove", [value]);
};
return /* @__PURE__ */ jsx10(
UiTag,
{
className,
icon: icon2,
isDisabled,
onRemove: handleRemove,
removeTabIndex: -1,
size: size2,
variant,
children: label3 ?? value
},
typeof option === "string" ? option : option.value
);
},
[onUpdate, isDisabled, size2, variant]
);
}
// src/UiMultiSelector/hooks/useUpdateHandler.ts
import { useCallback as useCallback7 } from "react";
function useUpdateHandler({
maxSelectedLimit,
onChange,
onSearch,
selected
}) {
return useCallback7(
(type, values) => {
const next = new Set(selected);
const difference = /* @__PURE__ */ new Set();
switch (type) {
case "add":
case "add-all":
case "add-found":
case "add-custom": {
for (const value of values) {
if (maxSelectedLimit != null && next.size >= maxSelectedLimit) {
break;
}
if (!next.has(value)) {
difference.add(value);
}
next.add(value);
}
break;
}
case "remove": {
for (const value of values) {
if (next.has(value)) {
difference.add(value);
}
next.delete(value);
}
break;
}
case "remove-all": {
for (const value of selected) {
difference.add(value);
}
next.clear();
break;
}
}
onChange(next, type, difference);
onSearch("");
},
[selected, onChange, onSearch, maxSelectedLimit]
);
}
// src/UiMultiSelector/UiMultiSelector.tsx
import { jsx as jsx11, jsxs as jsxs3 } from "react/jsx-runtime";
function UiMultiSelector({
addFound = "Add {search}",
allowsCustomValue,
className,
completeKey = "Enter",
defaultPlaceholder,
disabledPlaceholder,
isDisabled,
isInvalid,
isWarning,
maxSelectedLimit,
onChange,
options,
selectAll = "Select all",
selectFound = "Select all containing {search}",
selected,
size: size2,
variant,
withDropdownChevron
}) {
const { onEscape, onSearch, searchId, searchRef, search: search3 } = useSearch(isDisabled);
const onUpdate = useUpdateHandler({
maxSelectedLimit,
onChange,
onSearch,
selected
});
const {
dropdownRef,
floatingRef,
isOpen,
onGoNext,
onGoPrevious,
onSelectCurrent,
onShowDropdown,
onHideDropdown,
referenceRef,
style,
getFloatingProps,
getReferenceProps
} = useDropdown();
const renderTag = useTagRenderer({
isDisabled,
onUpdate,
size: size2,
variant
});
const isEmpty = selected.size === 0;
const isFilled = maxSelectedLimit != null && selected.size >= maxSelectedLimit || !allowsCustomValue && options.length > 0 && selected.size === options.length;
const isPopupVisible = !isDisabled && !isFilled;
const isSearchVisible = isPopupVisible || isDisabled && isEmpty;
const searchPlaceholder2 = useMemo6(() => {
if (isDisabled) {
return disabledPlaceholder;
}
if (!isEmpty) {
return;
}
return defaultPlaceholder;
}, [defaultPlaceholder, disabledPlaceholder, isDisabled, isEmpty]);
return /* @__PURE__ */ jsxs3(
"div",
{
className: clsx6(
root,
variants[variant],
sizes[size2],
withDropdownChevron && hasChevron,
isDisabled && state.isDisabled,
isEmpty && state.isEmpty,
isInvalid && state.isInvalid,
isWarning && state.isWarning,
className
),
ref: referenceRef,
...getReferenceProps(),
children: [
withDropdownChevron && !isDisabled && /* @__PURE__ */ jsx11(Memo, { className: chevron }),
isSearchVisible && /* @__PURE__ */ jsx11("label", { className: label, "aria-label": searchPlaceholder2, htmlFor: searchId }),
/* @__PURE__ */ jsx11(
Tags,
{
allowsCustomValue,
isDisabled,
onUpdate,
options,
renderTag,
selected,
children: isSearchVisible && /* @__PURE__ */ jsx11(
Search,
{
className: clsx6(search, search3 === "" && state.isEmptySearch),
completeKey,
id: searchId,
isDisabled,
onArrowDown: onGoNext,
onArrowUp: onGoPrevious,
onBlurByTab: onHideDropdown,
onComplete: onSelectCurrent,
onEscape,
onFocus: onShowDropdown,
onSearch,
placeholder: searchPlaceholder2,
ref: searchRef,
value: search3
}
)
}
),
isPopupVisible && /* @__PURE__ */ jsx11(FloatingPortal, { preserveTabOrder: false, children: /* @__PURE__ */ jsx11("div", { ref: floatingRef, style, ...getFloatingProps(), children: isOpen && /* @__PURE__ */ jsx11(
Dropdown,
{
addFound,
allowsCustomValue,
completeKey,
maxSelectedLimit,
onUpdate,
options,
ref: dropdownRef,
search: search3,
selectAll,
selectFound,
selected
}
) }) })
]
}
);
}
if (import.meta.env.DEV) {
UiMultiSelector.displayName = "ui-multi-selector(UiMultiSelector)";
}
export {
UiMultiSelector,
searchPlaceholder
};
// post-build: auto import bundled styles
import "./index.css";
//# sourceMappingURL=index.js.map