@tabula/ui-multi-selector
Version:
A MultiSelector allows users to select one or more items from a list of choices, or suggest own item.
5 lines • 66.6 kB
Source Map (JSON)
{
"version": 3,
"sources": ["../src/UiMultiSelector/UiMultiSelector.tsx", "../src/UiMultiSelector/assets/chevron.svg", "../src/UiMultiSelector/assets/running.svg", "../src/shared.css.ts", "../src/UiMultiSelector/UiMultiSelector.css.ts", "../src/Dropdown/Dropdown.tsx", "../src/Dropdown/Dropdown.css.ts", "../src/Dropdown/Dropdown.Item.tsx", "../src/Dropdown/hooks/useController.ts", "../src/Dropdown/hooks/useHasIcons.ts", "../src/Dropdown/hooks/useItems/useItems.ts", "../src/const.ts", "../src/Dropdown/hooks/useItems/match.ts", "../src/Dropdown/hooks/useItems/renderFound.tsx", "../src/Dropdown/hooks/useItems/buildBulkCustomValue.ts", "../src/Dropdown/hooks/useItems/buildCustomValue.tsx", "../src/Dropdown/hooks/useItems/renderParts.tsx", "../src/Dropdown/hooks/useItems/buildItems.ts", "../src/Dropdown/hooks/useItems/buildSelectAll.ts", "../src/Dropdown/hooks/useItems/buildSelectFound.ts", "../src/Search/Search.tsx", "../src/Search/Search.css.ts", "../src/Search/Search.hooks.ts", "../src/Tags/Tags.tsx", "../src/Tags/Tags.css.ts", "../src/Clear/Clear.tsx", "../src/Clear/assets/clear.svg", "../src/Clear/Clear.css.ts", "../src/Tags/Tags.hooks.ts", "../src/UiMultiSelector/hooks/useDropdown.ts", "../src/UiMultiSelector/hooks/useSearch.ts", "../src/UiMultiSelector/hooks/useTagRenderer.tsx", "../src/UiMultiSelector/hooks/useUpdateHandler.ts"],
"sourcesContent": ["import { ReactNode, useMemo } from 'react';\n\nimport { FloatingPortal } from '@floating-ui/react';\nimport { clsx } from 'clsx';\n\nimport { ReactComponent as ChevronIcon } from './assets/chevron.svg';\nimport { ReactComponent as RunningIcon } from './assets/running.svg';\n\nimport * as shared from '../shared.css';\nimport * as styles from './UiMultiSelector.css';\n\nimport { Dropdown } from '../Dropdown';\nimport { Search } from '../Search';\nimport { Tags } from '../Tags';\nimport {\n BatchAction,\n ChangeHandler,\n CompleteKey,\n Option,\n SearchHandler,\n Selected,\n Size,\n Variant,\n} from '../types';\n\nimport { useDropdown, useSearch, useTagRenderer, useUpdateHandler } from './hooks';\n\nexport type Props = {\n addFound?: BatchAction;\n allowsCustomValue?: boolean;\n className?: string;\n completeKey?: CompleteKey;\n defaultPlaceholder?: string;\n disabledPlaceholder?: string;\n isDisabled?: boolean;\n isInvalid?: boolean;\n isPending?: boolean;\n isWarning?: boolean;\n maxSelectedLimit?: number;\n onAutocomplete?: SearchHandler;\n onChange: ChangeHandler;\n options: Option[];\n selectAll?: BatchAction;\n selectFound?: BatchAction;\n selected: Selected;\n size: Size;\n variant: Variant;\n withDropdownChevron?: boolean;\n};\n\nexport function UiMultiSelector({\n addFound = 'Add {search}',\n allowsCustomValue,\n className,\n completeKey = 'Enter',\n defaultPlaceholder,\n disabledPlaceholder,\n isDisabled,\n isInvalid,\n isPending = false,\n isWarning,\n maxSelectedLimit,\n onAutocomplete,\n onChange,\n options,\n selectAll = 'Select all',\n selectFound = 'Select all containing {search}',\n selected,\n size,\n variant,\n withDropdownChevron,\n}: Props): ReactNode {\n const { onEscape, onSearch, searchId, searchRef, search } = useSearch(isDisabled, onAutocomplete);\n\n const onUpdate = useUpdateHandler({\n maxSelectedLimit,\n onChange,\n onSearch,\n selected,\n });\n\n const {\n dropdownRef,\n floatingRef,\n isOpen,\n onGoNext,\n onGoPrevious,\n onSelectCurrent,\n onShowDropdown,\n onHideDropdown,\n referenceRef,\n style,\n getFloatingProps,\n getReferenceProps,\n } = useDropdown();\n\n const renderTag = useTagRenderer({\n isDisabled,\n onUpdate,\n size,\n variant,\n });\n\n const isEmpty = selected.size === 0;\n\n const definedValueOnly = !allowsCustomValue && onAutocomplete == null;\n const isFilled =\n (maxSelectedLimit != null && selected.size >= maxSelectedLimit) ||\n (definedValueOnly && options.length > 0 && selected.size === options.length);\n\n const isPopupVisible = !isDisabled && !isFilled;\n const isSearchVisible = isPopupVisible || (isDisabled && isEmpty);\n\n const DropdownChevron = isPending ? RunningIcon : ChevronIcon;\n\n const searchPlaceholder = useMemo(() => {\n if (isDisabled) {\n return disabledPlaceholder;\n }\n\n if (!isEmpty) {\n return;\n }\n\n return defaultPlaceholder;\n }, [defaultPlaceholder, disabledPlaceholder, isDisabled, isEmpty]);\n\n return (\n <div\n className={clsx(\n styles.root,\n shared.variants[variant],\n shared.sizes[size],\n withDropdownChevron && shared.hasChevron,\n isDisabled && styles.state.isDisabled,\n isEmpty && styles.state.isEmpty,\n isInvalid && styles.state.isInvalid,\n isWarning && styles.state.isWarning,\n className,\n )}\n ref={referenceRef}\n {...getReferenceProps()}\n >\n {withDropdownChevron && !isDisabled && <DropdownChevron className={styles.chevron} />}\n\n {/* NOTE: Allows to focus on search input when click on space between tags/clear button. */}\n {isSearchVisible && (\n <label className={styles.label} aria-label={searchPlaceholder} htmlFor={searchId} />\n )}\n <Tags\n allowsCustomValue={allowsCustomValue}\n isDisabled={isDisabled}\n onUpdate={onUpdate}\n options={options}\n renderTag={renderTag}\n selected={selected}\n >\n {isSearchVisible && (\n <Search\n className={clsx(styles.search, search === '' && styles.state.isEmptySearch)}\n completeKey={completeKey}\n id={searchId}\n isDisabled={isDisabled}\n onArrowDown={onGoNext}\n onArrowUp={onGoPrevious}\n onBlurByTab={onHideDropdown}\n onComplete={onSelectCurrent}\n onEscape={onEscape}\n onFocus={onShowDropdown}\n onSearch={onSearch}\n placeholder={searchPlaceholder}\n ref={searchRef}\n value={search}\n />\n )}\n </Tags>\n {isPopupVisible && (\n <FloatingPortal preserveTabOrder={false}>\n <div ref={floatingRef} style={style} {...getFloatingProps()}>\n {isOpen && (\n <Dropdown\n addFound={addFound}\n allowsCustomValue={allowsCustomValue}\n completeKey={completeKey}\n isPending={isPending}\n maxSelectedLimit={maxSelectedLimit}\n onUpdate={onUpdate}\n options={options}\n ref={dropdownRef}\n search={search}\n selectAll={selectAll}\n selectFound={selectFound}\n selected={selected}\n />\n )}\n </div>\n </FloatingPortal>\n )}\n </div>\n );\n}\n\nif (import.meta.env.DEV) {\n UiMultiSelector.displayName = 'ui-multi-selector(UiMultiSelector)';\n}\n", "import svgUrl from \"ni:svgr;6m6FpM7qnscNstz9p6rlutUIT9jJmtMvVN_tmRJmyAA\";\nexport default svgUrl;\nimport * as React from \"react\";\nimport { memo } from \"react\";\nconst SvgChevron = props => <svg width={16} height={16} viewBox=\"0 0 16 16\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" {...props}><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\" /></svg>;\nconst Memo = memo(SvgChevron);\nexport { Memo as ReactComponent };", "import svgUrl from \"ni:svgr;MTyHTxApS1FJxmWwXFE15DqbwiDJmQ0OajOhP1mC0EQ\";\nexport default svgUrl;\nimport * as React from \"react\";\nimport { memo } from \"react\";\nconst SvgRunning = props => <svg width={16} height={16} viewBox=\"0 0 16 16\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" {...props}><path fillRule=\"evenodd\" clipRule=\"evenodd\" d=\"M4.667 12.989a6 6 0 0 0 3.326 1.01L8 14a.5.5 0 0 0 0-1v-.005A4.995 4.995 0 1 1 12.995 8H13a.5.5 0 0 0 1 0 6 6 0 1 0-9.333 4.989Z\" fill=\"currentColor\"><animateTransform attributeName=\"transform\" attributeType=\"XML\" type=\"rotate\" from=\"0 8 8\" to=\"360 8 8\" dur=\"700ms\" repeatCount=\"indefinite\" /></path></svg>;\nconst Memo = memo(SvgRunning);\nexport { Memo as ReactComponent };", "import 'src/shared.css.ts.vanilla.css?source=';\nexport var hasChevron = 'tabula_ui_multi_selector__1svmv054';\nexport var sizes = {small:'tabula_ui_multi_selector__1svmv052',medium:'tabula_ui_multi_selector__1svmv053'};\nexport var variants = {accent:'tabula_ui_multi_selector__1svmv050',contrast:'tabula_ui_multi_selector__1svmv051'};", "import 'src/shared.css.ts.vanilla.css?source=';\nimport 'src/UiMultiSelector/UiMultiSelector.css.ts.vanilla.css?source=#H4sIAAAAAAAAA71Wy24iMRC85yt8WYkcejUzeTuX7Jcgz9iAFeMetdsEdpV_XzGvEAQZI0huTGNVVZerbb84tTEkuHQAFS5r9MZzeL56OVgX_66E-M2qjE5No50uo2M7DcaZipGmUz172mR3j80yIQDGVmZSrBRNADoehxQASiRtCCr0TOgAtJmp6Pj6ORE1P4jqTWRSDuBPkSVDFSNQtz1UjcGyRS8FGafYrkxb73ohpW0MUtzX6926FHm9FgGd1T3PmGMdXxUpIElRo_VsqC3iytDM4ZsUC6u18W2VSfle3GCtQ9rtTEdS2xUAMxUYIL_e_deoYP0cILDyWpEGqAl1rLZtNnrev45FHlbLVXaXJSenVNXrnDB63Uo9HJOPRVCTXSraNJkxnk8QlZ8hqnG2VmQ8JxB2aHKx3aYec2c_ZGIE8pTuBjIVJtbXkeUMqxiuz-AtuuhhZGe9kaLYz-5XQ9zQQ5Gu_eGyaRlmdhihgx4cHvOHvanrTqRL9jIei6-kXUyFR56MLrv5HCMpPPr-vLvIkNz9-B3zpshbPz_zjjmC2ob_zEtnHDvJ2vsft9YQIV3Y2AbzW2zdR04y9amz7OMVoMqALnI3FYy1FFn725kZDx9_wXpt1sP3m9W8kCLPsl9tYWHsfMG7ldEJ-zim2KzTZ051PWgbaqc2Uli_PeahdFi9JqPcpBPt9ZoGP36hF-kKenOLon2WXUrCCSYM-3t7goT0Y3pcx-iK24bs8_PhWM6pbSbrL1hmXB7NOtaqsrzpvpMaL0f4j1-Y3cMQgA2xVbQ54YmYkKheWDPowyO_syNta9Oz84msD87A9nhKlk8ke9jjyofBef8PsVi4_0kOAAA';\nexport var chevron = 'tabula_ui_multi_selector__df9y05b';\nexport var label = 'tabula_ui_multi_selector__df9y059';\nexport var root = 'tabula_ui_multi_selector__df9y058';\nexport var search = 'tabula_ui_multi_selector__df9y05a';\nexport 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'};", "import { ReactNode, forwardRef } from 'react';\n\nimport { clsx } from 'clsx/lite';\n\nimport * as styles from './Dropdown.css';\n\nimport {\n BatchAction,\n CompleteKey,\n DropdownController,\n Option,\n Selected,\n UpdateHandler,\n} from '../types';\n\nimport { DropdownItem } from './Dropdown.Item';\nimport { useController, useHasIcons, useItems } from './hooks';\n\ntype Props = {\n addFound: BatchAction;\n allowsCustomValue?: boolean;\n className?: string;\n completeKey: CompleteKey;\n isPending: boolean;\n maxSelectedLimit?: number;\n onUpdate: UpdateHandler;\n options: Option[];\n search: string;\n selectAll: BatchAction;\n selectFound: BatchAction;\n selected: Selected;\n};\n\nexport const Dropdown = forwardRef<DropdownController, Props>(\n (\n {\n addFound,\n allowsCustomValue,\n className,\n completeKey,\n isPending,\n maxSelectedLimit,\n onUpdate,\n options,\n search,\n selectAll,\n selectFound,\n selected,\n },\n ref,\n ) => {\n const items = useItems({\n addFound,\n allowsCustomValue,\n isPending,\n maxSelectedLimit,\n onUpdate,\n options,\n search,\n selectAll,\n selectFound,\n selected,\n });\n\n const { currentIndex, currentRef, onMouseEnter, onMouseLeave, rootRef } = useController(ref, {\n items,\n selected,\n search,\n });\n\n const hasIcons = useHasIcons({ addFound, allowsCustomValue, options, selectAll, selectFound });\n\n if (items.length === 0) {\n return null;\n }\n\n const nodes: ReactNode[] = [];\n\n for (const [idx, item] of items.entries()) {\n if ('skeleton' in item) {\n nodes.push(<div key={item.key} className={styles.skeleton} />);\n continue;\n }\n\n const { key, icon, onSelect, label, title, hasDividerAfter } = item;\n\n nodes.push(\n <DropdownItem\n completeKey={completeKey}\n icon={icon}\n isCurrent={idx === currentIndex}\n key={key}\n onClick={onSelect}\n ref={idx === currentIndex ? currentRef : undefined}\n title={title}\n >\n {label}\n </DropdownItem>,\n );\n\n // NOTE: We don't append divider as item to list, to avoid complicated navigation logic.\n if (hasDividerAfter) {\n nodes.push(<div className={styles.divider} key={`${item.key}-divider`} />);\n }\n }\n\n return (\n /* eslint-disable-next-line jsx-a11y/no-static-element-interactions */\n <div\n className={clsx(styles.root, hasIcons && styles.hasIcons, className)}\n onMouseEnter={onMouseEnter}\n onMouseLeave={onMouseLeave}\n ref={rootRef}\n tabIndex={-1}\n >\n {nodes}\n </div>\n );\n },\n);\n\nif (import.meta.env.DEV) {\n Dropdown.displayName = 'Dropdown';\n}\n", "import 'src/Dropdown/Dropdown.css.ts.vanilla.css?source=#H4sIAAAAAAAAA91XYW-bMBD93l9hVZoUtnnCJM0690v3SyIHH8GrsZF9NERT__sEOCnJSAttqkn7Fh2-53e-d8_OvRY7cATXmtLUFqU1YNDfXd0PxsnvK0K-oVhXWqwqtSoqjWrlQUOK1q1WTLFsm9Vxu46QQtQ0B7XJkZNU6HTGYvKZzJOyJl_Isqyju3ZZKaRUZsObEIm72No6CY46IVXl2y8hLtKHjbOVkTS12jpOHoWbURqIaus8pb1FtHSqEG5HU2sQDEZ7-Jr6XEi75aQNEBKTRVkT1nIYAu2WU7qMvh4yWFm3WS8msOQkI3ktYxG16zuq9hFcpu2W1pzkSkowJ_EdJ6JC20SfxrWHc7qF9YNC6lNntV4LFzomlS-12HFirIHxiElIz6zBo440gaYwYTz14FRG6dpqSVnSfQvt0IAIjvpSpK0QRiIcpwUshBopOmF8Zl0xGus4LRpf-_y_rL0nBWW0MkDX2qYPJwObdPofHNnFpJE1UKETmtKfLJ5w-Itw-FslMeeExfGnbtO97bA9i0K4jTK8G9c3EbuZwOtmLwoNNScxiQ8j-sz14GkHriEybovl6cg2e3WAQquNoQqh8JykYBDcSeNiwpLT7efJpO05FxnC3jiCvXJyfT1c1L_Rx5IbzGmaKy1nLDqmHPqw7CQzHS8Zxrt9K958GG9xMwHv-wQzKkCqqnifHfUwLmBIPbSXLelZ6s0vKpWDFJU1nDi77T78qjyq7HDvdznUo3D4ypBsRNl7cJx1lvlhgAanqlP780U6pPO2vlI4MIHTef2HMg7vmf75tpVwoiHDCUrhefOCCHr5QCtkE2QbTpI2pUw1pFH78Fz42Qj7jgYJsUmEbo_uAEZY_w7IFUI7K9BIZOtE2Wvo_mnHCWitSq_88ZOv_xAcx-XHx_c5DoIaQYYbi7NueXSW2MlwjCMhJvifg02lhXufAfZBLuCAfbjLW2Df5c7a398X9wv-NnCbB99r_uh4q5UcdZuPsj4Eh2rvfZcWpTj_D-jpD-oujO4lDwAA';\nexport var divider = 'tabula_ui_multi_selector__1i1fwfx4';\nexport var hasIcons = 'tabula_ui_multi_selector__1i1fwfx1';\nexport var highlight = 'tabula_ui_multi_selector__1i1fwfx2';\nexport var icon = 'tabula_ui_multi_selector__1i1fwfx5';\nexport var isCurrent = 'tabula_ui_multi_selector__1i1fwfx9';\nexport var item = 'tabula_ui_multi_selector__1i1fwfx7';\nexport var key = 'tabula_ui_multi_selector__1i1fwfxa';\nexport var label = 'tabula_ui_multi_selector__1i1fwfx8';\nexport var root = 'tabula_ui_multi_selector__1i1fwfx0';\nexport var search = 'tabula_ui_multi_selector__1i1fwfx3';\nexport var skeleton = 'tabula_ui_multi_selector__1i1fwfx6';", "import { PropsWithChildren, forwardRef } from 'react';\n\nimport { clsx } from 'clsx/lite';\n\nimport * as styles from './Dropdown.css';\n\nimport { CompleteKey, IconComponent } from '../types';\n\ntype Props = PropsWithChildren<{\n completeKey: CompleteKey;\n\n icon?: IconComponent;\n title?: string;\n\n isCurrent?: boolean;\n\n onClick: () => void;\n}>;\n\nexport const DropdownItem = forwardRef<HTMLButtonElement, Props>(\n ({ children, completeKey, icon: Icon, isCurrent, onClick, title }, ref) => (\n <button\n className={clsx(styles.item, isCurrent && styles.isCurrent)}\n onClick={onClick}\n ref={ref}\n tabIndex={-1}\n title={title}\n type=\"button\"\n >\n {Icon != null && <Icon className={styles.icon} />}\n\n <span className={styles.label}>{children}</span>\n\n {isCurrent && <span className={styles.key}>{completeKey}</span>}\n </button>\n ),\n);\n\nif (import.meta.env.DEV) {\n DropdownItem.displayName = 'DropdownItem';\n}\n", "import {\n Ref,\n RefObject,\n useCallback,\n useEffect,\n useImperativeHandle,\n useRef,\n useState,\n} from 'react';\n\nimport { DropdownController, Selected } from '../../types';\n\nimport { Item } from '../Dropdown.types';\n\ntype Options = {\n items: Item[];\n search: string;\n selected: Selected;\n};\n\ntype Result = {\n currentIndex: number;\n\n onMouseEnter: () => void;\n onMouseLeave: () => void;\n\n rootRef: RefObject<HTMLDivElement | null>;\n currentRef: RefObject<HTMLButtonElement | null>;\n};\n\nexport function useController(\n controllerRef: Ref<DropdownController>,\n { items, search, selected }: Options,\n): Result {\n const rootRef = useRef<HTMLDivElement>(null);\n\n // NOTE: Used to scroll dropdown to current item when user navigate through keyboard.\n const currentRef = useRef<HTMLButtonElement>(null);\n\n // NOTE: We detect hover on the dropdown to disable keyboard interactions when item has mouse hover.\n //\n // It needed to avoid conflicts between keyboard navigation state and mouse navigation.\n const isHoveredRef = useRef(false);\n\n const handleMouseEnter = useCallback(() => {\n isHoveredRef.current = true;\n }, []);\n\n const handleMouseLeave = useCallback(() => {\n isHoveredRef.current = false;\n }, []);\n\n // NOTE: Index of current selected item when user uses keyboard navigation.\n const [currentIndex, setCurrentIndex] = useState(0);\n\n // NOTE: Reset current index when search has been changed or changed list of selected items (usually it happened when\n // a user selected any item.\n //\n // We detect selected count instead of selected list itself, because in general way, items will be added/removed,\n // but selected itself can be changed without adding/removing items and can be provided from outside.\n useEffect(() => {\n setCurrentIndex(0);\n }, [search, selected.size]);\n\n // NOTE: When current index has been changed, we should scroll to the new current item.\n useEffect(() => {\n const { current } = currentRef;\n\n if (current == null) {\n return;\n }\n\n current.scrollIntoView({\n block: 'nearest',\n });\n }, [currentIndex]);\n\n useImperativeHandle(\n controllerRef,\n () => ({\n goToPrevious: () => {\n if (isHoveredRef.current || items.length === 0) {\n return;\n }\n\n setCurrentIndex((current) => {\n return current === 0 ? items.length - 1 : current - 1;\n });\n },\n\n goToNext: () => {\n if (isHoveredRef.current || items.length === 0) {\n return;\n }\n\n setCurrentIndex((current) => {\n return current === items.length - 1 ? 0 : current + 1;\n });\n },\n\n selectCurrent: () => {\n if (isHoveredRef.current) {\n return;\n }\n\n const item = items.at(currentIndex);\n\n // NOTE: If `items` array is empty, then item will be `undefined` even for index equals to `0`.\n if (item == null || 'skeleton' in item) {\n return;\n }\n\n item.onSelect();\n },\n }),\n [currentIndex, items],\n );\n\n return {\n currentIndex,\n onMouseEnter: handleMouseEnter,\n onMouseLeave: handleMouseLeave,\n rootRef,\n currentRef,\n };\n}\n", "import { useMemo } from 'react';\n\nimport { BatchAction, IconComponent, Option } from '../../types';\n\ntype Options = {\n addFound: BatchAction;\n allowsCustomValue?: boolean;\n options: Option[];\n selectAll: BatchAction;\n selectFound: BatchAction;\n};\n\nfunction hasIcon<Item extends string | { icon?: IconComponent }>(item: Item): boolean {\n return typeof item !== 'string' && item.icon != null;\n}\n\nexport function useHasIcons({\n addFound,\n allowsCustomValue,\n options,\n selectAll,\n selectFound,\n}: Options): boolean {\n return useMemo(() => {\n if (options.some((it) => hasIcon(it))) {\n return true;\n }\n\n return (!allowsCustomValue && hasIcon(selectAll)) || hasIcon(addFound) || hasIcon(selectFound);\n }, [addFound, allowsCustomValue, options, selectAll, selectFound]);\n}\n", "import { useMemo } from 'react';\n\nimport { BatchAction, Option, Selected, UpdateHandler } from '../../../types';\n\nimport { Item } from '../../Dropdown.types';\n\nimport { buildBulkCustomValue } from './buildBulkCustomValue';\nimport { buildCustomValue } from './buildCustomValue';\nimport { buildItems } from './buildItems';\nimport { buildSelectAll } from './buildSelectAll';\nimport { buildSelectFound } from './buildSelectFound';\n\nconst PENDING_ITEMS: Item[] = [\n { key: 'pending-1', skeleton: true },\n { key: 'pending-2', skeleton: true },\n { key: 'pending-3', skeleton: true },\n];\n\ntype Options = {\n addFound: BatchAction;\n allowsCustomValue?: boolean;\n isPending: boolean;\n maxSelectedLimit?: number;\n onUpdate: UpdateHandler;\n options: Option[];\n search: string;\n selectAll: BatchAction;\n selectFound: BatchAction;\n selected: Selected;\n};\n\nexport function useItems({\n addFound,\n allowsCustomValue,\n isPending,\n maxSelectedLimit,\n onUpdate,\n options,\n search,\n selectAll,\n selectFound,\n selected,\n}: Options): Item[] {\n return useMemo(() => {\n if (isPending) {\n return PENDING_ITEMS;\n }\n\n const [values, items] = buildItems({ allowsCustomValue, onUpdate, options, search, selected });\n\n const hasSearch = search.length > 0;\n\n // NOTE: When option `allowsCustomValue` is used, we should build items list in different way.\n //\n // When option is on, we suggest an option to apply current input as a new item.\n //\n // Otherwise, we suggest select all and found options and append divider between select options and regular\n // items. We should suggest that options only if any items has been available to use.\n if (allowsCustomValue) {\n // NOTE: Suggest apply custom value only if input isn't empty.\n if (hasSearch) {\n items.unshift(buildCustomValue({ addFound, onUpdate, search }));\n\n const fromSearchValues = search.split(/,\\s|,/).filter((it) => it.trim().length > 0);\n // NOTE: Suggest apply custom values batch of search split by comma\n if (fromSearchValues.length > 1) {\n items.unshift(\n buildBulkCustomValue({\n addFound,\n onUpdate,\n values: fromSearchValues,\n }),\n );\n }\n }\n return items;\n }\n\n const isSatisfiesSelectedLimits = maxSelectedLimit == null || maxSelectedLimit > 1;\n if (isSatisfiesSelectedLimits && items.length > 1) {\n // NOTE: Suggest `Select found` option only when search isn't empty.\n if (hasSearch) {\n items.unshift(buildSelectFound({ onUpdate, search, selectFound, values }));\n }\n\n items.unshift(\n buildSelectAll({ hasDividerAfter: !hasSearch, onUpdate, options, selectAll, selected }),\n );\n }\n\n return items;\n }, [\n addFound,\n allowsCustomValue,\n isPending,\n maxSelectedLimit,\n onUpdate,\n options,\n search,\n selected,\n selectAll,\n selectFound,\n ]);\n}\n", "export const searchPlaceholder = '{search}';\n", "import { Part } from '../../Dropdown.types';\n\nexport function match(rawTarget: string, rawPattern: string): [boolean, Part[]] {\n if (rawPattern.length === 0) {\n return [\n true,\n [\n {\n isMatches: false,\n substring: rawTarget,\n },\n ],\n ];\n }\n\n const target = rawTarget.toLowerCase();\n const pattern = rawPattern.toLowerCase();\n\n const parts: Part[] = [];\n\n let lastIndex = 0;\n\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n while (true) {\n const index = target.indexOf(pattern, lastIndex);\n\n if (index === -1) {\n break;\n }\n\n if (index > lastIndex) {\n parts.push({\n isMatches: false,\n substring: rawTarget.slice(lastIndex, index),\n });\n }\n\n lastIndex = index + pattern.length;\n\n parts.push({\n isMatches: true,\n substring: rawTarget.slice(index, lastIndex),\n });\n }\n\n if (lastIndex < target.length) {\n parts.push({ isMatches: false, substring: rawTarget.slice(lastIndex) });\n }\n\n if (parts.length === 1) {\n return [parts[0].isMatches, parts];\n }\n\n return [true, parts];\n}\n", "import { ReactNode } from 'react';\n\nimport * as styles from '../../Dropdown.css';\n\nimport { searchPlaceholder } from '../../../const';\n\nimport { Part } from '../../Dropdown.types';\n\nimport { match } from './match';\n\n/* eslint-disable react/no-array-index-key */\nexport function renderFound(label: string, values: string[]): ReactNode {\n const [isMatches, parts]: [boolean, Part[]] = match(label, searchPlaceholder);\n\n if (!isMatches) {\n return label;\n }\n\n // NOTE: Replace search input placeholder and highlight it if placeholder is available.\n return parts.map((it, index) => {\n if (!it.isMatches) {\n return it.substring;\n }\n\n const found = values.reduce<ReactNode[]>((acc, value, valueIndex) => {\n if (acc.length > 0) {\n acc.push(<span key={`separator-${valueIndex}`}>, </span>);\n }\n\n acc.push(\n <span key={`value-${valueIndex}`} className={styles.search}>\n {value}\n </span>,\n );\n return acc;\n }, []);\n return <span key={index}>{found}</span>;\n });\n}\n/* eslint-enable */\n", "import { BatchAction, UpdateHandler } from '../../../types';\n\nimport { Item } from '../../Dropdown.types';\n\nimport { renderFound } from './renderFound';\n\ntype Options = {\n addFound: BatchAction;\n onUpdate: UpdateHandler;\n values: string[];\n};\n\nexport function buildBulkCustomValue({ addFound, onUpdate, values }: Options): Item {\n const handleClick = () => {\n onUpdate('add-custom', values);\n };\n\n const { icon, label } = typeof addFound === 'string' ? { label: addFound } : addFound;\n\n return {\n key: 'bulk-custom-value',\n\n icon,\n label: renderFound(label, values),\n\n onSelect: handleClick,\n };\n}\n", "import { BatchAction, UpdateHandler } from '../../../types';\n\nimport { Item } from '../../Dropdown.types';\n\nimport { renderFound } from './renderFound';\n\ntype Options = {\n addFound: BatchAction;\n onUpdate: UpdateHandler;\n search: string;\n};\n\nexport function buildCustomValue({ addFound, onUpdate, search }: Options): Item {\n const handleClick = () => {\n onUpdate('add-custom', [search]);\n };\n\n const { icon, label } = typeof addFound === 'string' ? { label: addFound } : addFound;\n\n return {\n key: 'custom-value',\n\n icon,\n label: renderFound(label, [search]),\n\n onSelect: handleClick,\n\n hasDividerAfter: true,\n };\n}\n", "import { ReactNode } from 'react';\n\nimport * as styles from '../../Dropdown.css';\n\nimport { Part } from '../../Dropdown.types';\n\n/* eslint-disable react/no-array-index-key */\nexport function renderParts(parts: Part[]): ReactNode {\n if (parts.length === 0 && !parts[0].isMatches) {\n return parts[0].substring;\n }\n\n // NOTE: Render search highlights in option label.\n return parts.map((it, index) =>\n it.isMatches ? (\n <span className={styles.highlight} key={index}>\n {it.substring}\n </span>\n ) : (\n it.substring\n ),\n );\n}\n/* eslint-enable */\n", "import { Option, Selected, UpdateHandler } from '../../../types';\n\nimport { Item, Part } from '../../Dropdown.types';\n\nimport { match } from './match';\nimport { renderParts } from './renderParts';\n\ntype Options = {\n allowsCustomValue?: boolean;\n onUpdate: UpdateHandler;\n options: Option[];\n search: string;\n selected: Selected;\n};\n\nexport function buildItems({\n allowsCustomValue,\n onUpdate,\n options,\n search,\n selected,\n}: Options): [string[], Item[]] {\n const values: string[] = [];\n const items: Item[] = [];\n\n // NOTE: If custom values are allowed and search is empty, then just don't\n // suggest any items.\n if (allowsCustomValue && search.length === 0) {\n return [values, items];\n }\n\n for (const option of options) {\n const { icon, label, value } =\n typeof option === 'string' ? { label: option, value: option } : option;\n\n if (selected.has(value)) {\n continue;\n }\n\n const [isMatches, parts]: [boolean, Part[]] = match(label ?? value, search);\n\n if (!isMatches) {\n continue;\n }\n\n values.push(value);\n\n items.push({\n key: `item-${value}`,\n\n icon,\n label: renderParts(parts),\n title: label,\n\n onSelect: () => {\n onUpdate('add', [value]);\n },\n });\n }\n\n return [values, items];\n}\n", "import { BatchAction, Option, Selected, UpdateHandler } from '../../../types';\n\nimport { Item } from '../../Dropdown.types';\n\ntype Options = {\n hasDividerAfter?: boolean;\n onUpdate: UpdateHandler;\n options: Option[];\n selectAll: BatchAction;\n selected: Selected;\n};\n\nexport function buildSelectAll({\n hasDividerAfter,\n onUpdate,\n options,\n selectAll,\n selected,\n}: Options): Item {\n const handleClick = () => {\n const ids = options.reduce<string[]>((result, it) => {\n const value = typeof it === 'string' ? it : it.value;\n\n if (!selected.has(value)) {\n result.push(value);\n }\n\n return result;\n }, []);\n\n onUpdate('add-all', ids);\n };\n\n const { icon, label } = typeof selectAll === 'string' ? { label: selectAll } : selectAll;\n\n return {\n key: 'select-all',\n\n icon,\n label,\n\n onSelect: handleClick,\n\n hasDividerAfter,\n };\n}\n", "import { BatchAction, UpdateHandler } from '../../../types';\n\nimport { Item } from '../../Dropdown.types';\n\nimport { renderFound } from './renderFound';\n\ntype Options = {\n onUpdate: UpdateHandler;\n search: string;\n selectFound: BatchAction;\n values: string[];\n};\n\nexport function buildSelectFound({ onUpdate, selectFound, search, values }: Options): Item {\n const handleClick = () => {\n onUpdate('add-found', values);\n };\n\n const { icon, label } = typeof selectFound === 'string' ? { label: selectFound } : selectFound;\n\n return {\n key: 'select-found',\n\n icon,\n label: renderFound(label, [search]),\n\n onSelect: handleClick,\n\n hasDividerAfter: true,\n };\n}\n", "import { forwardRef, useMemo } from 'react';\n\nimport { clsx } from 'clsx/lite';\n\nimport * as styles from './Search.css';\n\nimport { CompleteKey, SearchHandler } from '../types';\n\nimport { useHandlers } from './Search.hooks';\n\ntype Props = {\n className?: string;\n completeKey: CompleteKey;\n id: string;\n isDisabled?: boolean;\n onArrowDown: () => void;\n onArrowUp: () => void;\n onBlurByTab: () => void;\n onComplete: () => void;\n onEscape: () => void;\n onFocus: () => void;\n onSearch: SearchHandler;\n placeholder?: string;\n value: string;\n};\n\nexport const Search = forwardRef<HTMLInputElement, Props>(\n ({ className, id, isDisabled, onFocus, placeholder, value, ...handlers }, ref) => {\n const { onChange, onKeyDown } = useHandlers(handlers);\n\n const size = useMemo(() => {\n if (placeholder != null) {\n return;\n }\n\n return Math.max(1, value.length);\n }, [placeholder, value.length]);\n\n return (\n <input\n className={clsx(styles.root, className)}\n disabled={isDisabled}\n id={id}\n onChange={onChange}\n onFocus={onFocus}\n onKeyDown={onKeyDown}\n placeholder={placeholder}\n ref={ref}\n size={size}\n value={value}\n />\n );\n },\n);\n\nif (import.meta.env.DEV) {\n Search.displayName = 'Search';\n}\n", "import 'src/shared.css.ts.vanilla.css?source=';\nimport 'src/Search/Search.css.ts.vanilla.css?source=#H4sIAAAAAAAAA61UTW_bMAy991foMqA5cLCDtRjUS_-JwVh0LEwWDZFyEhT974M_2jjDhjnDToIexcdHPUqvAS-UjB4CQM1dz5GiysvD629x8_ZgzFfFQw5YZV91OaivhALVyqmqSt8-7_NzMZ0zpuGo1gyYHgEmphEQAMEoIJR8A9CR87mDcj9Hdy9TZiBVSiA91j4e7-C4TVzYlM4KmjBKw6m7g-02cWHr0blJVGG-9ecZa8kfW7Vm__0D6fAMJ--0taYsii8zeODkKFkTOdKCYP3jmDhHZ81UqMdEUedgzYHTjdwJkXGNSlEB-uQ7TJdJ2vs2c2zDdZbFIs4afKSrpL-RyNANxVOxqZDzgodAbql17RX-3Nnq0NRl4iDgSNEH2dTlLLD8PwJ_sWTrDds-YE0tB0dp4d7i5TXpY3DH8l49RzsTrPNdTjiGABoUBSh36yih-HgEEMXoMLlxVNjlWv1Ad03LShVIy6e4NDS9DR4oNYFP1lAIvhcv91BH1sdPD3bzdhrOnW1H5n-9x9WbMIbHr0Av1pSztPefMp8_9_MEAAA';\nexport var root = 'tabula_ui_multi_selector__1ih62u60';", "import { ChangeEventHandler, KeyboardEventHandler, useCallback } from 'react';\n\nimport { CompleteKey } from '../types';\n\n// region Types\n\ntype KeyboardHandler = KeyboardEventHandler<HTMLInputElement>;\n\ntype ChangeHandler = ChangeEventHandler<HTMLInputElement>;\n\n// endregion Types\n\ntype Options = {\n completeKey: CompleteKey;\n onArrowDown: () => void;\n onArrowUp: () => void;\n onBlurByTab: () => void;\n onComplete: () => void;\n onEscape: () => void;\n onSearch: (value: string) => void;\n};\n\ntype Result = {\n onChange: ChangeHandler;\n onKeyDown: KeyboardHandler;\n};\n\nexport function useHandlers({\n completeKey,\n onArrowDown,\n onArrowUp,\n onBlurByTab,\n onComplete,\n onEscape,\n onSearch,\n}: Options): Result {\n const handleChange = useCallback<ChangeEventHandler<HTMLInputElement>>(\n (event) => {\n onSearch(event.target.value);\n },\n [onSearch],\n );\n\n // NOTE: Handle special keys: arrow navigation, blur or completion.\n const handleKeyDown = useCallback<KeyboardEventHandler<HTMLInputElement>>(\n (event) => {\n // NOTE: We can handle the `Tab` in two ways depends on the `completeKey` option:\n // - if `completeKey` is `Tab`, then we activate completion and cancel default behavior;\n // - otherwise, we don't block default behavior, but close the dropdown (we close dropdown on focus loose\n // ONLY when move to the previous/next taggable element, not by click on dropdown/tags/etc.).\n if (event.key === 'Tab') {\n if (completeKey === 'Tab') {\n event.preventDefault();\n\n onComplete();\n } else {\n onBlurByTab();\n }\n\n return;\n }\n\n // NOTE: Handle then `Enter` key only if `completeKey` is `Enter`.\n if (event.key === 'Enter') {\n if (completeKey === 'Enter') {\n event.preventDefault();\n\n onComplete();\n }\n\n return;\n }\n\n // NOTE: Skip any non-special keys.\n if (event.key !== 'ArrowDown' && event.key !== 'ArrowUp' && event.key !== 'Escape') {\n return;\n }\n\n event.preventDefault();\n\n switch (event.key) {\n case 'ArrowDown': {\n onArrowDown();\n\n break;\n }\n case 'ArrowUp': {\n onArrowUp();\n\n break;\n }\n case 'Escape': {\n onEscape();\n\n break;\n }\n }\n },\n [completeKey, onArrowDown, onArrowUp, onBlurByTab, onComplete, onEscape],\n );\n\n return { onChange: handleChange, onKeyDown: handleKeyDown };\n}\n", "import { PropsWithChildren, ReactNode } from 'react';\n\nimport { clsx } from 'clsx/lite';\n\nimport * as styles from './Tags.css';\n\nimport { Clear } from '../Clear';\nimport { Option, Selected, TagRenderer, UpdateHandler } from '../types';\n\nimport { useTags } from './Tags.hooks';\n\ntype Props = PropsWithChildren<{\n allowsCustomValue?: boolean;\n isDisabled?: boolean;\n onUpdate: UpdateHandler;\n options: Option[];\n renderTag: TagRenderer;\n selected: Selected;\n}>;\n\nexport function Tags({\n allowsCustomValue,\n children,\n isDisabled,\n onUpdate,\n options,\n renderTag,\n selected,\n}: Props): ReactNode {\n const tags = useTags({ allowsCustomValue, options, selected });\n\n const isNotEmpty = tags.length > 0;\n\n return (\n <div className={clsx(styles.root, isDisabled && isNotEmpty && styles.state.noPaddings)}>\n {isNotEmpty && !isDisabled && <Clear className={styles.clear} onUpdate={onUpdate} />}\n\n <div className={styles.list}>\n {tags.map((it): ReactNode => renderTag(styles.tag, it))}\n\n {children}\n </div>\n </div>\n );\n}\n", "import 'src/shared.css.ts.vanilla.css?source=';\nimport 'src/Tags/Tags.css.ts.vanilla.css?source=#H4sIAAAAAAAAA71WXWvCMBR991fcl8HGyEg_lBFf_CclttFeSJPQpLZu-N9HbZ3oBBuJeynlJjfn3HNvDllJvhc1uLUkJNeV0UooZ5ez1c04fM8APhxfN5JnDWZVIx1mVkiRO11nWVQtvnJro-M-gIp3pMXClQwiSl-Wx6BEJUgpcFs6BrSPHe4canfVjs5jD2DDiwLVlkFquukIySMIn9MQhmz6AMAkiYbc-JSrLTrUigFfWy0bJ8LKfMJx2vxKDFAPLfWSPL6_JfUgNFKIg7f9ouKIXpU8cQZGuKeUnMQ-c5iMyQVaI_mewUaKsaT-j7Q1Nwz6b9jBOeFuuXnC3bw43asngcYw-et7OZf5a29-QCCmpnsLfRPfA_BK_XiFGuAA1FM_6oHuXlg9p_lPALF6j_h_sYJQny8mUh9OS6_9DdXx2XG2uZtPEyc6R_RO1BupWwZCSjQW7bB4jpdYFEIN0bZEJ4g1PBcMlD5b5uEHFkSay1oJAAA';\nexport var clear = 'tabula_ui_multi_selector__1m6zcss2';\nexport var list = 'tabula_ui_multi_selector__1m6zcss3';\nexport var root = 'tabula_ui_multi_selector__1m6zcss1';\nexport var state = {noPaddings:'tabula_ui_multi_selector__1m6zcss0'};\nexport var tag = 'tabula_ui_multi_selector__1m6zcss4';", "import { ReactNode, useCallback } from 'react';\n\nimport { clsx } from 'clsx/lite';\n\nimport { ReactComponent as ClearIcon } from './assets/clear.svg';\n\nimport * as styles from './Clear.css';\n\nimport { UpdateHandler } from '../types';\n\ntype Props = {\n className?: string;\n\n onUpdate: UpdateHandler;\n};\n\nexport function Clear({ className, onUpdate }: Props): ReactNode {\n const handleClick = useCallback(() => {\n onUpdate('remove-all', []);\n }, [onUpdate]);\n\n return (\n <button\n className={clsx(styles.root, className)}\n onClick={handleClick}\n tabIndex={-1}\n type=\"button\"\n >\n <ClearIcon />\n </button>\n );\n}\n", "import svgUrl from \"ni:svgr;i1pRadfLp1Nwlp2wYdQ7-ORMsH5YXajt3NcenQzJdWA\";\nexport default svgUrl;\nimport * as React from \"react\";\nimport { memo } from \"react\";\nconst SvgClear = props => <svg width={16} height={16} viewBox=\"0 0 16 16\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" {...props}><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\" /></svg>;\nconst Memo = memo(SvgClear);\nexport { Memo as ReactComponent };", "import 'src/shared.css.ts.vanilla.css?source=';\nimport 'src/Clear/Clear.css.ts.vanilla.css?source=#H4sIAAAAAAAAA72VwW7bMAyG73kKXQYsBw52lqCYeumexGAsxtEqSwZFuQ2KvHsR28FSzMHswujNJmX9309S1pPDE7GSvQMoQ90ET17i4-ppNK7eVkr9ENwnh0WyRZ2c2CKSo1ICF0Uemu3Dr-esW6eUsbFxeNLq4Oj1sQtdnsBYplJs8FpxeOkTf1IUezhBGbyQF61K8kLcJ9HZyoMVquPHRIPGWF9plfXv-8CGGBiNTVGrXfatj5fBBdaqRf4OMJhygSNc9QCEWCzyaX354vwfn7Gt22y3mVGLF2vkqNUma4ZKHMlWR_kbmSb58xOS238kt3MksxmSff218sHT0BEsnysOyRu43wRPSRgdwO_djPJPAtPH0BIvhfewOB6WYltaii_PZgDmn2hs3ryqGJw1YxzD6bscKQ4OwNABk5P1PSvC6GODTF4Wph7t-iz2boO75IsNyTQ3o0PyVXYmztSV9RDKFAfUkMRZT1pt5rB2O8Bmluptv7uxsv39gs7dSprEeEkAHDAKQL6-zRJG6yuAKOgNsgFoOJjUFX89-SJp2NZT75HRBs_U-Gj4-us4r87vHclDedwHAAA';\nexport var root = 'tabula_ui_multi_selector__1op479k0';", "import { useMemo } from 'react';\n\nimport { Option, Selected } from '../types';\n\ntype Options = {\n allowsCustomValue?: boolean;\n options: Option[];\n selected: Selected;\n};\n\nexport function useTags({ allowsCustomValue, options, selected }: Options): Option[] {\n return useMemo(() => {\n const optionsMap = new Map<string, Option>();\n\n for (const option of options) {\n const value = typeof option === 'string' ? option : option.value;\n\n if (selected.has(value)) {\n optionsMap.set(value, option);\n }\n }\n\n const selectedOptions: Option[] = [];\n\n for (const value of selected) {\n const option = optionsMap.get(value);\n\n if (option == null) {\n if (allowsCustomValue) {\n selectedOptions.push(value);\n }\n } else {\n selectedOptions.push(option);\n }\n }\n\n return selectedOptions;\n }, [selected, options, allowsCustomValue]);\n}\n", "import { CSSProperties, Ref, RefObject, useCallback, useMemo, useRef } from 'react';\n\nimport {\n FloatingContext,\n autoUpdate,\n flip,\n offset,\n size,\n useDismiss,\n useFloating,\n useInteractions,\n useTransitionStyles,\n} from '@floating-ui/react';\n\nimport { useFlag } from '@tabula/use-flag';\n\nimport { DropdownController } from '../../types';\n\ntype Result = {\n isOpen: boolean;\n\n context: FloatingContext;\n\n dropdownRef: RefObject<DropdownController | null>;\n floatingRef: Ref<HTMLDivElement>;\n referenceRef: Ref<HTMLDivElement>;\n\n getFloatingProps: () => Record<string, unknown>;\n getReferenceProps: () => Record<string, unknown>;\n\n style: CSSProperties;\n\n onShowDropdown: () => void;\n onHideDropdown: () => void;\n\n onGoNext: () => void;\n onGoPrevious: () => void;\n\n onSelectCurrent: () => void;\n};\n\nexport function useDropdown(): Result {\n const dropdownRef = useRef<DropdownController>(null);\n\n const [open, { on: onShowDropdown, off: onHideDropdown, change: onToggleDropdown }] =\n useFlag(false);\n\n // NOTE: Hide dropdown with timeout.\n //\n // We hide dropdown when search input lose its focus. In that case, `blur` event will be fired early, dropdown\n // will be hidden, and click by option will not work.\n const handleHideDropdown = useCallback(() => {\n setTimeout(onHideDropdown, 0);\n }, [onHideDropdown]);\n\n const handleGoNext = useCallback(() => {\n dropdownRef.current?.goToNext();\n }, []);\n\n const handleGoPrevious = useCallback(() => {\n dropdownRef.current?.goToPrevious();\n }, []);\n\n const handleSelect = useCallback(() => {\n dropdownRef.current?.selectCurrent();\n }, []);\n\n const { refs, context, floatingStyles } = useFloating<HTMLDivElement>({\n placement: 'bottom-start',\n\n strategy: 'fixed',\n\n open,\n\n onOpenChange(state: boolean) {\n onToggleDropdown(state);\n },\n\n middleware: [\n offset({ mainAxis: 4 }),\n flip(),\n size({\n apply({ rects, elements }) {\n Object.assign(elements.floating.style, {\n width: `${rects.reference.width}px`,\n });\n },\n }),\n ],\n\n whileElementsMounted(...args) {\n return autoUpdate(...args, {\n ancestorResize: true,\n ancestorScroll: true,\n elementResize: true,\n });\n },\n });\n\n const dismiss = useDismiss(context, {\n escapeKey: true,\n outsidePress: true,\n });\n\n const { getReferenceProps, getFloatingProps } = useInteractions([dismiss]);\n\n const { isMounted: isOpen, styles: transitionStyles } = useTransitionStyles(context);\n\n const style = useMemo(\n () => ({ ...floatingStyles, ...transitionStyles }),\n [floatingStyles, transitionStyles],\n );\n\n return {\n isOpen,\n\n context,\n\n dropdownRef,\n floatingRef: refs.setFloating,\n referenceRef: refs.setReference,\n\n getReferenceProps,\n getFloatingProps,\n\n style,\n\n onShowDropdown,\n onHideDropdown: handleHideDropdown,\n\n onGoNext: handleGoNext,\n onGoPrevious: handleGoPrevious,\n onSelectCurrent: handleSelect,\n };\n}\n", "import { RefObject, useCallback, useEffect, useId, useRef, useState } from 'react';\n\nimport { SearchHandler } from '../../types';\n\ntype Result = {\n onEscape: () => void;\n onSearch: SearchHandler;\n search: string;\n searchId: string;\n searchRef: RefObject<HTMLInputElement | null>;\n};\n\nexport function useSearch(isDisabled?: boolean, onAutocomplete?: SearchHandler): Result {\n const searchId = useId();\n const searchRef = useRef<HTMLInputElement>(null);\n\n const [search, setSearch] = useState('');\n\n const handleSearch = useCallback<SearchHandler>(\n (value) => {\n setSearch(value);\n\n if (onAutocomplete != null) {\n onAutocomplete(value);\n }\n },\n [onAutocomplete],\n );\n\n // NOTE: Reset search input when control is disabled.\n useEffect(() => {\n if (isDisabled) {\n handleSearch('');\n }\n }, [handleSearch, isDisabled]);\n\n // NOTE: Remove focus from search input when `Escape` has been pressed.\n const onEscape = useCallback(() => {\n searchRef.current?.blur();\n }, []);\n\n return { onEscape, onSearch: handleSearch, search, searchId, searchRef };\n}\n", "import { useCallback } from 'react';\n\nimport { UiTag } from '@tabula/ui-tag';\n\nimport { Size, TagRenderer, UpdateHandler, Variant } from '../../types';\n\ntype Options = {\n isDisabled?: boolean;\n onUpdate: UpdateHandler;\n size: Size;\n variant: Variant;\n};\n\nexport function useTagRenderer({ isDisabled, onUpdate, size, variant }: Options): TagRenderer {\n return useCallback<TagRenderer>(\n (className, option) => {\n const { icon, label, value } = typeof option === 'string' ? { value: option } : option;\n\n const handleRemove = () => {\n onUpdate('remove', [value]);\n };\n\n return (\n <UiTag\n className={className}\n icon={icon}\n isDisabled={isDisabled}\n key={typeof option === 'string' ? option : option.value}\n onRemove={handleRemove}\n removeTabIndex={-1}\n size={size}\n variant={variant}\n >\n {label ?? value}\n </UiTag>\n );\n },\n [onUpdate, isDisabled, size, variant],\n );\n}\n", "import { useCallback } from 'react';\n\nimport { ChangeHandler, SearchHandler, Selected, UpdateHandler } from '../../types';\n\ntype Options = {\n maxSelectedLimit?: number;\n onChange: ChangeHandler;\n onSearch: SearchHandler;\n selected: Selected;\n};\n\nexport function useUpdateHandler({\n maxSelectedLimit,\n onChange,\n onSearch,\n selected,\n}: Options): UpdateHandler {\n return useCallback<UpdateHandler>(\n (type, values) => {\n const next: Selected = new Set(selected);\n const difference: Selected = new Set();\n\n switch (type) {\n case 'add':\n case 'add-all':\n case 'add-found':\n case 'add-custom': {\n for (const value of values) {\n if (maxSelectedLimit != null && next.size >= maxSelectedLimit) {\n break;\n }\n\n if (!next.has(value)) {\n difference.add(value);\n }\n\n next.add(value);\n }\n\n break;\n }\n case 'remove': {\n for (const value of values) {\n if (next.has(value)) {\n difference.add(value);\n }\n\n next.delete(value);\n }\n\n break;\n }\n case 'remove-all': {\n for (const value of selected) {\n difference.add(value);\n }\n\n next.clear();\n\n break;\n }\n }\n\n onChange(next, type, difference);\n\n onSearch('');\n },\n [selected, onChange, onSearch, maxSelectedLimit],\n );\n}\n"],
"mappings": "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