UNPKG

@spark-ui/components

Version:

Spark (Leboncoin design system) components.

1,270 lines (1,240 loc) 41 kB
import { Popover } from "../chunk-QS2FHLSL.mjs"; import { IconButton } from "../chunk-QLOIAU3C.mjs"; import "../chunk-USSL4UZ5.mjs"; import { Spinner } from "../chunk-MUNDKRAE.mjs"; import { Icon } from "../chunk-AESXFMCC.mjs"; import { VisuallyHidden } from "../chunk-NBZKMCHF.mjs"; import "../chunk-4F5DOL57.mjs"; // src/combobox/ComboboxContext.tsx import { useFormFieldControl } from "@spark-ui/components/form-field"; import { useCombinedState } from "@spark-ui/hooks/use-combined-state"; import { useCombobox as useCombobox3, useMultipleSelection } from "downshift"; import { createContext, Fragment, useContext, useEffect, useId, useRef, useState } from "react"; // src/combobox/useCombobox/multipleSelectionReducer.ts import { useCombobox } from "downshift"; // src/combobox/utils/index.ts import { isValidElement, Children } from "react"; function getIndexByKey(map, targetKey) { let index = 0; for (const [key] of map.entries()) { if (key === targetKey) { return index; } index++; } return -1; } var getKeyAtIndex = (map, index) => { let i = 0; for (const key of map.keys()) { if (i === index) return key; i++; } return void 0; }; var getElementByIndex = (map, index) => { const key = getKeyAtIndex(map, index); return key !== void 0 ? map.get(key) : void 0; }; var getElementDisplayName = (element) => { return element ? element.type.displayName : ""; }; var getOrderedItems = (children, result = []) => { Children.forEach(children, (child) => { if (!isValidElement(child)) return; if (getElementDisplayName(child) === "Combobox.Item") { const childProps = child.props; result.push({ value: childProps.value, disabled: !!childProps.disabled, text: getItemText(childProps.children) }); } if (child.props.children) { getOrderedItems(child.props.children, result); } }); return result; }; var findNestedItemText = (children) => { if (!children) return ""; for (const child of Children.toArray(children)) { if (isValidElement(child)) { const childElement = child; if (getElementDisplayName(childElement) === "Combobox.ItemText") { return childElement.props.children; } const foundText = findNestedItemText(childElement.props.children); if (foundText) return foundText; } } return ""; }; var getItemText = (children) => { return typeof children === "string" ? children : findNestedItemText(children); }; var getItemsFromChildren = (children) => { const newMap = /* @__PURE__ */ new Map(); getOrderedItems(children).forEach((itemData) => { newMap.set(itemData.value, itemData); }); return newMap; }; var hasChildComponent = (children, displayName) => { return Children.toArray(children).some((child) => { if (!isValidElement(child)) return false; if (getElementDisplayName(child) === displayName) { return true; } else if (child.props.children) { return hasChildComponent(child.props.children, displayName); } return false; }); }; var findElement = (children, value) => { return Children.toArray(children).filter(isValidElement).find((child) => value === getElementDisplayName(child) || ""); }; // src/combobox/useCombobox/multipleSelectionReducer.ts var multipleSelectionReducer = ({ multiselect, selectedItems, allowCustomValue = false, setSelectedItems, triggerAreaRef, items }) => { const reducer = (_, { changes, type }) => { const isFocusInsideTriggerArea = triggerAreaRef.current?.contains?.(document.activeElement); switch (type) { case useCombobox.stateChangeTypes.InputClick: return { ...changes, isOpen: true // keep menu opened }; case useCombobox.stateChangeTypes.InputKeyDownEnter: case useCombobox.stateChangeTypes.ItemClick: { const newState = { ...changes }; if (changes.selectedItem != null) { newState.inputValue = ""; newState.isOpen = true; const highlightedIndex = getIndexByKey(items, changes.selectedItem.value); newState.highlightedIndex = highlightedIndex; const isAlreadySelected = multiselect.selectedItems.some( (selectedItem) => selectedItem.value === changes.selectedItem?.value ); const updatedItems = isAlreadySelected ? selectedItems.filter((item) => item.value !== changes.selectedItem?.value) : [...selectedItems, changes.selectedItem]; setSelectedItems(updatedItems); } return newState; } case useCombobox.stateChangeTypes.ToggleButtonClick: return { ...changes, inputValue: allowCustomValue ? changes.inputValue : "" }; case useCombobox.stateChangeTypes.InputChange: return { ...changes, selectedItem: changes.highlightedIndex === -1 ? null : changes.selectedItem }; case useCombobox.stateChangeTypes.InputBlur: return { ...changes, inputValue: allowCustomValue ? changes.inputValue : "", isOpen: isFocusInsideTriggerArea }; default: return changes; } }; return reducer; }; // src/combobox/useCombobox/singleSelectionReducer.ts import { useCombobox as useCombobox2 } from "downshift"; var singleSelectionReducer = ({ filteredItems, allowCustomValue = false, setSelectedItem }) => { const reducer = (state, { changes, type }) => { const exactMatch = filteredItems.find( (item) => item.text.toLowerCase() === state.inputValue.toLowerCase() ); switch (type) { case useCombobox2.stateChangeTypes.InputKeyDownEscape: if (!changes.selectedItem) { setSelectedItem(null); } return changes; case useCombobox2.stateChangeTypes.ItemClick: case useCombobox2.stateChangeTypes.InputKeyDownEnter: if (changes.selectedItem) { setSelectedItem(changes.selectedItem); } return changes; case useCombobox2.stateChangeTypes.InputClick: return { ...changes, isOpen: true }; case useCombobox2.stateChangeTypes.ToggleButtonClick: case useCombobox2.stateChangeTypes.InputBlur: if (allowCustomValue) return changes; if (state.inputValue === "") { setSelectedItem(null); return { ...changes, selectedItem: null }; } if (exactMatch) { setSelectedItem(exactMatch); return { ...changes, selectedItem: exactMatch, inputValue: exactMatch.text }; } if (state.selectedItem) { return { ...changes, inputValue: state.selectedItem.text }; } return { ...changes, inputValue: "" }; default: return changes; } }; return reducer; }; // src/combobox/ComboboxContext.tsx import { jsx } from "react/jsx-runtime"; var ComboboxContext = createContext(null); var getFilteredItemsMap = (map, inputValue) => { if (!inputValue) return map; return new Map( Array.from(map).filter(([_, { text }]) => text.toLowerCase().includes(inputValue.toLowerCase())) ); }; var ID_PREFIX = ":combobox"; var ComboboxProvider = ({ children, state: stateProp, allowCustomValue = false, filtering = "auto", disabled: disabledProp = false, multiple = false, readOnly: readOnlyProp = false, wrap = true, // Value value: controlledValue, defaultValue, onValueChange, // Open open: controlledOpen, defaultOpen, onOpenChange, isLoading }) => { const isMounted = useRef(false); const [inputValue, setInputValue] = useState(""); const [isTyping, setIsTyping] = useState(filtering === "strict"); const triggerAreaRef = useRef(null); const innerInputRef = useRef(null); const [onInputValueChange, setOnInputValueChange] = useState(null); const [comboboxValue] = useCombinedState(controlledValue, defaultValue); const shouldFilterItems = filtering === "strict" || filtering === "auto" && isTyping; const [itemsMap, setItemsMap] = useState(getItemsFromChildren(children)); const [filteredItemsMap, setFilteredItems] = useState( shouldFilterItems ? getFilteredItemsMap(itemsMap, inputValue) : itemsMap ); const [selectedItem, setSelectedItem] = useState( itemsMap.get(comboboxValue) || null ); const [selectedItems, setSelectedItems] = useState( comboboxValue ? [...itemsMap.values()].filter((item) => comboboxValue.includes(item.value)) : [] ); const onInternalSelectedItemChange = (item) => { setIsTyping(false); if (item?.value !== selectedItem?.value) { setSelectedItem(item); setTimeout(() => { onValueChange?.(item?.value); }, 0); } }; const onInternalSelectedItemsChange = (items) => { setSelectedItems(items); setTimeout(() => { onValueChange?.(items.map((i) => i.value)); }, 0); }; useEffect(() => { if (!isMounted.current) { isMounted.current = true; return; } if (multiple) { const newSelectedItems = comboboxValue.reduce( (accum, value) => { const match = itemsMap.get(value); return match ? [...accum, match] : accum; }, [] ); setSelectedItems(comboboxValue ? newSelectedItems : []); } else { setSelectedItem(itemsMap.get(comboboxValue) || null); } }, [multiple ? JSON.stringify(comboboxValue) : comboboxValue]); const field = useFormFieldControl(); const internalFieldLabelID = `${ID_PREFIX}-label-${useId()}`; const internalFieldID = `${ID_PREFIX}-field-${useId()}`; const id = field.id || internalFieldID; const labelId = field.labelId || internalFieldLabelID; const state = field.state || stateProp; const disabled = field.disabled ?? disabledProp; const readOnly = field.readOnly ?? readOnlyProp; const [hasPopover, setHasPopover] = useState( hasChildComponent(children, "Combobox.Popover") ); const [lastInteractionType, setLastInteractionType] = useState("mouse"); useEffect(() => { setFilteredItems(shouldFilterItems ? getFilteredItemsMap(itemsMap, inputValue) : itemsMap); }, [inputValue, itemsMap]); const multiselect = useMultipleSelection({ selectedItems, stateReducer: (state2, { type, changes }) => { const types = useMultipleSelection.stateChangeTypes; switch (type) { case types.SelectedItemKeyDownBackspace: case types.SelectedItemKeyDownDelete: { onInternalSelectedItemsChange(changes.selectedItems || []); let activeIndex; if (type === types.SelectedItemKeyDownDelete) { const isLastItem = state2?.activeIndex === changes.selectedItems?.length; activeIndex = isLastItem ? -1 : state2.activeIndex; } else { const hasItemBefore = (changes?.activeIndex || 0) - 1 >= 0; activeIndex = hasItemBefore ? state2.activeIndex - 1 : changes?.activeIndex; } return { ...changes, activeIndex }; } case types.SelectedItemClick: if (innerInputRef.current) { innerInputRef.current.focus(); } return { ...changes, activeIndex: -1 // the focus will remain on the input }; case types.FunctionRemoveSelectedItem: return { ...changes, activeIndex: -1 // the focus will remain on the input }; case types.DropdownKeyDownNavigationPrevious: downshift.closeMenu(); return changes; default: return changes; } } }); const filteredItems = Array.from(filteredItemsMap.values()); useEffect(() => { onInputValueChange?.(inputValue || ""); }, [inputValue]); const downshift = useCombobox3({ inputId: id, items: filteredItems, selectedItem: multiple ? void 0 : selectedItem, id, labelId, // Input inputValue, onInputValueChange: ({ inputValue: newInputValue }) => { setInputValue(newInputValue); if (shouldFilterItems) { const filtered = getFilteredItemsMap(itemsMap, newInputValue || ""); setFilteredItems(filtered); } }, // Open initialIsOpen: defaultOpen, ...controlledOpen != null && { isOpen: controlledOpen }, onIsOpenChange: (changes) => { if (changes.isOpen != null) { onOpenChange?.(changes.isOpen); } }, // Custom Spark item object parsing itemToString: (item) => { return item?.text; }, isItemDisabled: (item) => { const isFilteredOut = !!inputValue && !filteredItems.some((filteredItem) => { return item.value === filteredItem.value; }); return item.disabled || isFilteredOut; }, // Main reducer stateReducer: multiple ? multipleSelectionReducer({ multiselect, selectedItems, allowCustomValue, setSelectedItems: onInternalSelectedItemsChange, triggerAreaRef, items: itemsMap }) : singleSelectionReducer({ allowCustomValue, setSelectedItem: onInternalSelectedItemChange, filteredItems: [...filteredItemsMap.values()] }), /** * Downshift default behaviour is to scroll into view the highlighted item when the dropdown opens. This behaviour is not stable and scrolls the dropdown to the bottom of the screen. */ scrollIntoView: (node) => { if (node) { node.scrollIntoView({ block: "nearest" }); } return void 0; } }); useEffect(() => { const newMap = getItemsFromChildren(children); const previousItems = [...itemsMap.values()]; const newItems = [...newMap.values()]; const hasItemsChanges = previousItems.length !== newItems.length || previousItems.some((item, index) => { const hasUpdatedValue = item.value !== newItems[index]?.value; const hasUpdatedText = item.text !== newItems[index]?.text; return hasUpdatedValue || hasUpdatedText; }); if (hasItemsChanges) { setItemsMap(newMap); } }, [children]); const [WrapperComponent, wrapperProps] = hasPopover ? [Popover, { open: true }] : [Fragment, {}]; return /* @__PURE__ */ jsx( ComboboxContext.Provider, { value: { // Data itemsMap, filteredItemsMap, highlightedItem: getElementByIndex(filteredItemsMap, downshift.highlightedIndex), // State multiple, disabled, readOnly, hasPopover, setHasPopover, state, lastInteractionType, setLastInteractionType, wrap, // Refs innerInputRef, triggerAreaRef, // Downshift state ...downshift, ...multiselect, setInputValue, selectItem: onInternalSelectedItemChange, setSelectedItems: onInternalSelectedItemsChange, isLoading, setOnInputValueChange, isTyping, setIsTyping }, children: /* @__PURE__ */ jsx(WrapperComponent, { ...wrapperProps, children }) } ); }; var useComboboxContext = () => { const context = useContext(ComboboxContext); if (!context) { throw Error("useComboboxContext must be used within a Combobox provider"); } return context; }; // src/combobox/Combobox.tsx import { jsx as jsx2 } from "react/jsx-runtime"; var Combobox = ({ children, ...props }) => { return /* @__PURE__ */ jsx2(ComboboxProvider, { ...props, children }); }; Combobox.displayName = "Combobox"; // src/combobox/ComboboxClearButton.tsx import { DeleteOutline } from "@spark-ui/icons/DeleteOutline"; import { cx } from "class-variance-authority"; import { jsx as jsx3 } from "react/jsx-runtime"; var ClearButton = ({ className, tabIndex = -1, onClick, ref, ...others }) => { const ctx = useComboboxContext(); const handleClick = (event) => { event.stopPropagation(); if (ctx.multiple) { ctx.setSelectedItems([]); } else { ctx.selectItem(null); } ctx.setInputValue(""); if (ctx.innerInputRef.current) { ctx.innerInputRef.current.focus(); } if (onClick) { onClick(event); } }; return /* @__PURE__ */ jsx3( "button", { ref, className: cx(className, "h-sz-44 text-neutral hover:text-neutral-hovered"), tabIndex, onClick: handleClick, type: "button", ...others, children: /* @__PURE__ */ jsx3(Icon, { size: "sm", children: /* @__PURE__ */ jsx3(DeleteOutline, {}) }) } ); }; ClearButton.displayName = "Combobox.ClearButton"; // src/combobox/ComboboxDisclosure.tsx import { useMergeRefs } from "@spark-ui/hooks/use-merge-refs"; import { ArrowHorizontalDown } from "@spark-ui/icons/ArrowHorizontalDown"; import { cx as cx2 } from "class-variance-authority"; import { jsx as jsx4 } from "react/jsx-runtime"; var Disclosure = ({ className, closedLabel, openedLabel, intent = "neutral", design = "ghost", size = "sm", ref: forwardedRef, ...props }) => { const ctx = useComboboxContext(); const { ref: downshiftRef, ...downshiftDisclosureProps } = ctx.getToggleButtonProps({ disabled: ctx.disabled || ctx.readOnly, onClick: (event) => { event.stopPropagation(); } }); const isExpanded = downshiftDisclosureProps["aria-expanded"]; const ref = useMergeRefs(forwardedRef, downshiftRef); return /* @__PURE__ */ jsx4( IconButton, { ref, className: cx2(className, "mt-[calc((44px-32px)/2)]"), intent, design, size, ...downshiftDisclosureProps, ...props, "aria-label": isExpanded ? openedLabel : closedLabel, disabled: ctx.disabled, children: /* @__PURE__ */ jsx4( Icon, { className: cx2("shrink-0", "rotate-0 transition duration-100 ease-in", { "rotate-180": isExpanded }), size: "sm", children: /* @__PURE__ */ jsx4(ArrowHorizontalDown, {}) } ) } ); }; Disclosure.displayName = "Combobox.Disclosure"; // src/combobox/ComboboxEmpty.tsx import { cx as cx3 } from "class-variance-authority"; import { jsx as jsx5 } from "react/jsx-runtime"; var Empty = ({ className, children, ref: forwardedRef }) => { const ctx = useComboboxContext(); const hasNoItemVisible = ctx.filteredItemsMap.size === 0; return hasNoItemVisible ? /* @__PURE__ */ jsx5( "div", { ref: forwardedRef, className: cx3("px-lg py-md text-body-1 text-on-surface/dim-1", className), children } ) : null; }; Empty.displayName = "Combobox.Empty"; // src/combobox/ComboboxGroup.tsx import { cx as cx4 } from "class-variance-authority"; import { Children as Children2, isValidElement as isValidElement2 } from "react"; // src/combobox/ComboboxItemsGroupContext.tsx import { createContext as createContext2, useContext as useContext2, useId as useId2 } from "react"; import { jsx as jsx6 } from "react/jsx-runtime"; var ComboboxGroupContext = createContext2(null); var ComboboxGroupProvider = ({ children }) => { const groupLabelId = `${ID_PREFIX}-group-label-${useId2()}`; return /* @__PURE__ */ jsx6(ComboboxGroupContext.Provider, { value: { groupLabelId }, children }); }; var useComboboxGroupContext = () => { const context = useContext2(ComboboxGroupContext); if (!context) { throw Error("useComboboxGroupContext must be used within a ComboboxGroup provider"); } return context; }; // src/combobox/ComboboxGroup.tsx import { jsx as jsx7 } from "react/jsx-runtime"; var Group = ({ children, ref: forwardedRef, ...props }) => { return /* @__PURE__ */ jsx7(ComboboxGroupProvider, { children: /* @__PURE__ */ jsx7(GroupContent, { ref: forwardedRef, ...props, children }) }); }; var GroupContent = ({ children, className, ref: forwardedRef }) => { const ctx = useComboboxContext(); const groupCtx = useComboboxGroupContext(); const hasVisibleOptions = Children2.toArray(children).some((child) => { return isValidElement2(child) && ctx.filteredItemsMap.get(child.props.value); }); return hasVisibleOptions ? /* @__PURE__ */ jsx7( "div", { ref: forwardedRef, role: "group", "aria-labelledby": groupCtx.groupLabelId, className: cx4(className), children } ) : null; }; Group.displayName = "Combobox.Group"; // src/combobox/ComboboxInput.tsx import { useFormFieldControl as useFormFieldControl2 } from "@spark-ui/components/form-field"; import { useCombinedState as useCombinedState2 } from "@spark-ui/hooks/use-combined-state"; import { useMergeRefs as useMergeRefs2 } from "@spark-ui/hooks/use-merge-refs"; import { cx as cx5 } from "class-variance-authority"; import { Fragment as Fragment2, useEffect as useEffect2 } from "react"; import { Fragment as Fragment3, jsx as jsx8, jsxs } from "react/jsx-runtime"; var Input = ({ "aria-label": ariaLabel, className, placeholder, value, defaultValue, onValueChange, ref: forwardedRef, ...props }) => { const ctx = useComboboxContext(); const field = useFormFieldControl2(); const [inputValue] = useCombinedState2(value, defaultValue); const { isInvalid, description } = field; useEffect2(() => { if (inputValue != null) { ctx.setInputValue(inputValue); } }, [inputValue]); useEffect2(() => { if (onValueChange) { ctx.setOnInputValueChange(() => onValueChange); } if (!ctx.multiple && ctx.selectedItem) { ctx.setInputValue(ctx.selectedItem.text); } }, []); const [PopoverTrigger, popoverTriggerProps] = ctx.hasPopover ? [Popover.Trigger, { asChild: true, type: void 0 }] : [Fragment2, {}]; const multiselectInputProps = ctx.getDropdownProps(); const inputRef = useMergeRefs2(forwardedRef, ctx.innerInputRef, multiselectInputProps.ref); const downshiftInputProps = ctx.getInputProps({ disabled: ctx.disabled || ctx.readOnly, ...multiselectInputProps, onKeyDown: (event) => { multiselectInputProps.onKeyDown?.(event); ctx.setLastInteractionType("keyboard"); ctx.setIsTyping(true); }, /** * * Important: * - without this, the input cursor is moved to the end after every change. * @see https://github.com/downshift-js/downshift/issues/1108#issuecomment-674180157 */ onChange: (e) => { ctx.setInputValue(e.target.value); }, ref: inputRef }); const hasPlaceholder = ctx.multiple ? ctx.selectedItems.length === 0 : ctx.selectedItem === null; function mergeHandlers(handlerA, handlerB) { return (event) => { handlerA?.(event); handlerB?.(event); }; } const mergedEventProps = { onBlur: mergeHandlers(props.onBlur, downshiftInputProps.onBlur), onChange: mergeHandlers(props.onChange, downshiftInputProps.onChange), onClick: mergeHandlers(props.onClick, downshiftInputProps.onClick), onKeyDown: mergeHandlers(props.onKeyDown, downshiftInputProps.onKeyDown) }; return /* @__PURE__ */ jsxs(Fragment3, { children: [ ariaLabel && /* @__PURE__ */ jsx8(VisuallyHidden, { children: /* @__PURE__ */ jsx8("label", { ...ctx.getLabelProps(), children: ariaLabel }) }), /* @__PURE__ */ jsx8(PopoverTrigger, { ...popoverTriggerProps, children: /* @__PURE__ */ jsx8( "input", { "data-spark-component": "combobox-input", type: "text", ...hasPlaceholder && { placeholder }, className: cx5( "max-w-full shrink-0 grow basis-[80px]", "h-sz-28 bg-surface px-sm text-body-1 text-ellipsis outline-hidden", "disabled:text-on-surface/dim-3 disabled:cursor-not-allowed disabled:bg-transparent", "read-only:text-on-surface read-only:cursor-default read-only:bg-transparent", className ), ...props, ...downshiftInputProps, ...mergedEventProps, value: ctx.inputValue, "aria-label": ariaLabel, disabled: ctx.disabled, readOnly: ctx.readOnly, "aria-invalid": isInvalid, "aria-describedby": description } ) }) ] }); }; Input.displayName = "Combobox.Input"; // src/combobox/ComboboxItem.tsx import { useMergeRefs as useMergeRefs3 } from "@spark-ui/hooks/use-merge-refs"; import { cva, cx as cx6 } from "class-variance-authority"; // src/combobox/ComboboxItemContext.tsx import { createContext as createContext3, useContext as useContext3, useState as useState2 } from "react"; import { jsx as jsx9 } from "react/jsx-runtime"; var ComboboxItemContext = createContext3(null); var ComboboxItemProvider = ({ value, disabled = false, children }) => { const ctx = useComboboxContext(); const [textId, setTextId] = useState2(void 0); const index = getIndexByKey(ctx.filteredItemsMap, value); const itemData = { disabled, value, text: getItemText(children) }; const isSelected = ctx.multiple ? ctx.selectedItems.some((selectedItem) => selectedItem.value === value) : ctx.selectedItem?.value === value; return /* @__PURE__ */ jsx9( ComboboxItemContext.Provider, { value: { textId, setTextId, isSelected, itemData, index, disabled }, children } ); }; var useComboboxItemContext = () => { const context = useContext3(ComboboxItemContext); if (!context) { throw Error("useComboboxItemContext must be used within a ComboboxItem provider"); } return context; }; // src/combobox/ComboboxItem.tsx import { jsx as jsx10 } from "react/jsx-runtime"; var Item = ({ children, ref: forwardedRef, ...props }) => { const { value, disabled } = props; return /* @__PURE__ */ jsx10(ComboboxItemProvider, { value, disabled, children: /* @__PURE__ */ jsx10(ItemContent, { ref: forwardedRef, ...props, children }) }); }; var styles = cva("px-lg py-md text-body-1", { variants: { selected: { true: "font-bold" }, disabled: { true: "opacity-dim-3 cursor-not-allowed", false: "cursor-pointer" }, highlighted: { true: "" }, interactionType: { mouse: "", keyboard: "" } }, compoundVariants: [ { highlighted: true, interactionType: "mouse", class: "bg-surface-hovered" }, { highlighted: true, interactionType: "keyboard", class: "u-outline" } ] }); var ItemContent = ({ className, disabled = false, value, children, ref: forwardedRef }) => { const ctx = useComboboxContext(); const itemCtx = useComboboxItemContext(); const isVisible = !!ctx.filteredItemsMap.get(value); const { ref: downshiftRef, ...downshiftItemProps } = ctx.getItemProps({ item: itemCtx.itemData, index: itemCtx.index }); const ref = useMergeRefs3(forwardedRef, downshiftRef); if (!isVisible) return null; return /* @__PURE__ */ jsx10( "li", { ref, className: cx6( styles({ selected: itemCtx.isSelected, disabled, highlighted: ctx.highlightedItem?.value === value, interactionType: ctx.lastInteractionType, className }) ), ...downshiftItemProps, "aria-selected": itemCtx.isSelected, "aria-labelledby": itemCtx.textId, children }, value ); }; Item.displayName = "Combobox.Item"; // src/combobox/ComboboxItemIndicator.tsx import { Check } from "@spark-ui/icons/Check"; import { cx as cx7 } from "class-variance-authority"; import { jsx as jsx11 } from "react/jsx-runtime"; var ItemIndicator = ({ className, children, label, ref: forwardedRef }) => { const { disabled, isSelected } = useComboboxItemContext(); const childElement = children || /* @__PURE__ */ jsx11(Icon, { size: "sm", children: /* @__PURE__ */ jsx11(Check, { "aria-label": label }) }); return /* @__PURE__ */ jsx11( "span", { ref: forwardedRef, className: cx7("min-h-sz-16 min-w-sz-16 flex", disabled && "opacity-dim-3", className), children: isSelected && childElement } ); }; ItemIndicator.displayName = "Combobox.ItemIndicator"; // src/combobox/ComboboxItems.tsx import { useMergeRefs as useMergeRefs4 } from "@spark-ui/hooks/use-merge-refs"; import { cx as cx8 } from "class-variance-authority"; import { useLayoutEffect, useRef as useRef2 } from "react"; import { jsx as jsx12 } from "react/jsx-runtime"; var Items = ({ children, className, ref: forwardedRef, ...props }) => { const ctx = useComboboxContext(); const { ref: downshiftRef, ...downshiftMenuProps } = ctx.getMenuProps({ onMouseMove: () => { ctx.setLastInteractionType("mouse"); } }); const innerRef = useRef2(null); const ref = useMergeRefs4(forwardedRef, downshiftRef, innerRef); const isOpen = ctx.hasPopover ? ctx.isOpen : true; const isPointerEventsDisabled = ctx.hasPopover && !isOpen; useLayoutEffect(() => { if (innerRef.current?.parentElement) { innerRef.current.parentElement.style.pointerEvents = isPointerEventsDisabled ? "none" : ""; innerRef.current.style.pointerEvents = isPointerEventsDisabled ? "none" : ""; } }, [isPointerEventsDisabled]); return /* @__PURE__ */ jsx12( "ul", { ref, className: cx8( className, "flex flex-col", isOpen ? "block" : "pointer-events-none invisible opacity-0", ctx.hasPopover && "p-lg", ctx.isLoading && "items-center overflow-y-auto" ), ...props, ...downshiftMenuProps, "aria-busy": ctx.isLoading, "data-spark-component": "combobox-items", children: ctx.isLoading ? /* @__PURE__ */ jsx12(Spinner, { size: "sm" }) : children } ); }; Items.displayName = "Combobox.Items"; // src/combobox/ComboboxItemText.tsx import { cx as cx9 } from "class-variance-authority"; import { useEffect as useEffect3, useId as useId3 } from "react"; import { jsx as jsx13 } from "react/jsx-runtime"; var ItemText = ({ children, className, ref: forwardedRef }) => { const id = `${ID_PREFIX}-item-text-${useId3()}`; const { setTextId } = useComboboxItemContext(); useEffect3(() => { setTextId(id); return () => setTextId(void 0); }); return /* @__PURE__ */ jsx13("span", { id, className: cx9("inline", className), ref: forwardedRef, children }); }; ItemText.displayName = "Combobox.ItemText"; // src/combobox/ComboboxLabel.tsx import { cx as cx10 } from "class-variance-authority"; import { jsx as jsx14 } from "react/jsx-runtime"; var Label = ({ children, className, ref: forwardedRef }) => { const groupCtx = useComboboxGroupContext(); return /* @__PURE__ */ jsx14( "div", { ref: forwardedRef, id: groupCtx.groupLabelId, className: cx10("px-md py-sm text-body-2 text-neutral italic", className), children } ); }; Label.displayName = "Combobox.Label"; // src/combobox/ComboboxLeadingIcon.tsx import { jsx as jsx15 } from "react/jsx-runtime"; var LeadingIcon = ({ children }) => { return /* @__PURE__ */ jsx15(Icon, { size: "sm", className: "h-sz-44 shrink-0", children }); }; LeadingIcon.displayName = "Combobox.LeadingIcon"; // src/combobox/ComboboxPopover.tsx import { cx as cx11 } from "class-variance-authority"; import { useEffect as useEffect4 } from "react"; import { jsx as jsx16 } from "react/jsx-runtime"; var Popover2 = ({ children, matchTriggerWidth = true, sideOffset = 4, className, ref: forwardedRef, ...props }) => { const ctx = useComboboxContext(); useEffect4(() => { ctx.setHasPopover(true); return () => ctx.setHasPopover(false); }, []); return /* @__PURE__ */ jsx16( Popover.Content, { ref: forwardedRef, inset: true, asChild: true, matchTriggerWidth, className: cx11("z-dropdown! relative", className), sideOffset, onOpenAutoFocus: (e) => { e.preventDefault(); }, ...props, "data-spark-component": "combobox-popover", children } ); }; Popover2.displayName = "Combobox.Popover"; // src/combobox/ComboboxPortal.tsx import { jsx as jsx17 } from "react/jsx-runtime"; var Portal = ({ children, ...rest }) => /* @__PURE__ */ jsx17(Popover.Portal, { ...rest, children }); Portal.displayName = "Combobox.Portal"; // src/combobox/ComboboxSelectedItems.tsx import { DeleteOutline as DeleteOutline2 } from "@spark-ui/icons/DeleteOutline"; import { cx as cx12 } from "class-variance-authority"; import { Fragment as Fragment4, jsx as jsx18, jsxs as jsxs2 } from "react/jsx-runtime"; var SelectedItem = ({ item: selectedItem, index }) => { const ctx = useComboboxContext(); const isCleanable = !ctx.disabled && !ctx.readOnly; const handleFocus = (e) => { const element = e.target; if (ctx.lastInteractionType === "keyboard") { element.scrollIntoView({ behavior: "smooth", block: "nearest", inline: "nearest" }); } }; const { disabled, ...selectedItemProps } = ctx.getSelectedItemProps({ disabled: ctx.disabled || ctx.readOnly, selectedItem, index }); const Element = disabled ? "button" : "span"; return /* @__PURE__ */ jsxs2( Element, { role: "presentation", "data-spark-component": "combobox-selected-item", className: cx12( "h-sz-28 bg-neutral-container flex items-center rounded-md align-middle", "text-body-2 text-on-neutral-container", "disabled:opacity-dim-3 disabled:cursor-not-allowed", "focus-visible:u-outline-inset outline-hidden", { "px-md": !isCleanable, "pl-md": isCleanable } ), ...selectedItemProps, tabIndex: -1, ...disabled && { disabled: true }, onFocus: handleFocus, children: [ /* @__PURE__ */ jsx18( "span", { className: cx12("line-clamp-1 overflow-x-hidden leading-normal break-all text-ellipsis", { "w-max": !ctx.wrap }), children: selectedItem.text } ), ctx.disabled, isCleanable && /* @__PURE__ */ jsx18( "button", { type: "button", tabIndex: -1, "aria-hidden": true, className: "px-md h-full cursor-pointer", onClick: (e) => { e.stopPropagation(); const updatedSelectedItems = ctx.selectedItems.filter( (item) => item.value !== selectedItem.value ); ctx.setSelectedItems(updatedSelectedItems); if (ctx.innerInputRef.current) { ctx.innerInputRef.current.focus({ preventScroll: true }); } }, children: /* @__PURE__ */ jsx18(Icon, { size: "sm", children: /* @__PURE__ */ jsx18(DeleteOutline2, {}) }) } ) ] }, `selected-item-${index}` ); }; var SelectedItems = () => { const ctx = useComboboxContext(); return ctx.multiple && ctx.selectedItems.length ? /* @__PURE__ */ jsx18(Fragment4, { children: ctx.selectedItems.map((item, index) => /* @__PURE__ */ jsx18(SelectedItem, { item, index }, item.value)) }) : null; }; SelectedItems.displayName = "Combobox.SelectedItems"; // src/combobox/ComboboxTrigger.tsx import { useFormFieldControl as useFormFieldControl3 } from "@spark-ui/components/form-field"; import { useMergeRefs as useMergeRefs5 } from "@spark-ui/hooks/use-merge-refs"; import { cx as cx13 } from "class-variance-authority"; import { Fragment as Fragment5, useEffect as useEffect6, useRef as useRef4 } from "react"; // src/combobox/ComboboxTrigger.styles.tsx import { cva as cva2 } from "class-variance-authority"; var styles2 = cva2( [ "flex items-start gap-md min-h-sz-44 text-body-1", "h-fit rounded-lg px-lg", // outline styles "ring-1 outline-hidden ring-inset focus-within:ring-2" ], { variants: { allowWrap: { true: "", false: "h-sz-44" }, state: { undefined: "ring-outline focus-within:ring-outline-high", error: "ring-error", alert: "ring-alert", success: "ring-success" }, disabled: { true: "cursor-not-allowed border-outline bg-on-surface/dim-5 text-on-surface/dim-3" }, readOnly: { true: "cursor-default bg-on-surface/dim-5 text-on-surface" } }, compoundVariants: [ { disabled: false, state: void 0, class: "hover:ring-outline-high" }, { disabled: false, readOnly: false, class: "bg-surface text-on-surface cursor-text" } ], defaultVariants: { state: void 0, disabled: false, readOnly: false } } ); // src/combobox/utils/useWidthIncreaseCallback.ts import { useEffect as useEffect5, useRef as useRef3 } from "react"; var useWidthIncreaseCallback = (elementRef, callback) => { const prevWidthRef = useRef3(null); useEffect5(() => { const checkWidthIncrease = () => { const currentWidth = elementRef.current?.scrollWidth || null; if (prevWidthRef.current && currentWidth && currentWidth > prevWidthRef.current) { callback(); } prevWidthRef.current = currentWidth; requestAnimationFrame(checkWidthIncrease); }; const interval = requestAnimationFrame(checkWidthIncrease); return () => cancelAnimationFrame(interval); }, [elementRef]); }; // src/combobox/ComboboxTrigger.tsx import { Fragment as Fragment6, jsx as jsx19, jsxs as jsxs3 } from "react/jsx-runtime"; var Trigger = ({ className, children, ref: forwardedRef }) => { const ctx = useComboboxContext(); const field = useFormFieldControl3(); const leadingIcon = findElement(children, "Combobox.LeadingIcon"); const selectedItems = findElement(children, "Combobox.SelectedItems"); const input = findElement(children, "Combobox.Input"); const clearButton = findElement(children, "Combobox.ClearButton"); const disclosure = findElement(children, "Combobox.Disclosure"); const [PopoverAnchor, popoverAnchorProps] = ctx.hasPopover ? [Popover.Anchor, { asChild: true, type: void 0 }] : [Fragment5, {}]; const ref = useMergeRefs5(forwardedRef, ctx.triggerAreaRef); const scrollableAreaRef = useRef4(null); const disabled = field.disabled || ctx.disabled; const readOnly = field.readOnly || ctx.readOnly; const hasClearButton = !!clearButton && !disabled && !readOnly; const scrollToRight = () => { if (scrollableAreaRef.current && !ctx.wrap) { const { scrollWidth, clientWidth } = scrollableAreaRef.current; scrollableAreaRef.current.scrollLeft = scrollWidth - clientWidth; } }; useWidthIncreaseCallback(scrollableAreaRef, scrollToRight); useEffect6(() => { const resizeObserver = new ResizeObserver(scrollToRight); if (scrollableAreaRef.current) { resizeObserver.observe(scrollableAreaRef.current); } return () => { resizeObserver.disconnect(); }; }, []); return /* @__PURE__ */ jsx19(Fragment6, { children: /* @__PURE__ */ jsx19(PopoverAnchor, { ...popoverAnchorProps, children: /* @__PURE__ */ jsxs3( "div", { ref, className: styles2({ className, state: ctx.state, disabled, readOnly, allowWrap: ctx.wrap }), onClick: () => { if (!ctx.isOpen && !disabled && !readOnly) { ctx.openMenu(); if (ctx.innerInputRef.current) { ctx.innerInputRef.current.focus(); } } }, children: [ leadingIcon, /* @__PURE__ */ jsxs3( "div", { ref: scrollableAreaRef, className: cx13( "min-w-none gap-sm py-md inline-flex grow items-start", ctx.wrap ? "flex-wrap" : "u-no-scrollbar overflow-x-auto p-[2px]" ), children: [ selectedItems, input ] } ), hasClearButton && clearButton, disclosure ] } ) }) }); }; Trigger.displayName = "Combobox.Trigger"; // src/combobox/index.ts var Combobox2 = Object.assign(Combobox, { Group, Item, Items, ItemText, ItemIndicator, Label, Popover: Popover2, Trigger, LeadingIcon, Empty, Input, Disclosure, SelectedItems, ClearButton, Portal }); Combobox2.displayName = "Combobox"; Group.displayName = "Combobox.Group"; Items.displayName = "Combobox.Items"; Item.displayName = "Combobox.Item"; ItemText.displayName = "Combobox.ItemText"; ItemIndicator.displayName = "Combobox.ItemIndicator"; Label.displayName = "Combobox.Label"; Popover2.displayName = "Combobox.Popover"; Trigger.displayName = "Combobox.Trigger"; LeadingIcon.displayName = "Combobox.LeadingIcon"; Empty.displayName = "Combobox.Empty"; Input.displayName = "Combobox.Input"; Disclosure.displayName = "Combobox.Disclosure"; SelectedItems.displayName = "Combobox.SelectedItems"; ClearButton.displayName = "Combobox.ClearButton"; Portal.displayName = "Combobox.Portal"; export { Combobox2 as Combobox, ComboboxProvider, useComboboxContext }; //# sourceMappingURL=index.mjs.map