periplo-ui 4.3.0 → 4.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/dist/components/Combobox/Combobox.d.ts +0 -42
  2. package/dist/components/Combobox/Combobox.js +22 -60
  3. package/dist/components/Combobox/Combobox.js.map +1 -1
  4. package/dist/components/Combobox/ComboboxTrigger.d.ts +14 -0
  5. package/dist/components/Combobox/ComboboxTrigger.js +74 -0
  6. package/dist/components/Combobox/ComboboxTrigger.js.map +1 -0
  7. package/dist/components/Combobox/VirtualizedComboboxList.d.ts +1 -1
  8. package/dist/components/Combobox/VirtualizedComboboxList.js +2 -16
  9. package/dist/components/Combobox/VirtualizedComboboxList.js.map +1 -1
  10. package/dist/components/Combobox/hooks/index.d.ts +6 -0
  11. package/dist/components/Combobox/hooks/index.js +7 -0
  12. package/dist/components/Combobox/hooks/index.js.map +1 -0
  13. package/dist/components/Combobox/hooks/useComboboxOpenState.d.ts +7 -0
  14. package/dist/components/Combobox/hooks/useComboboxOpenState.js +18 -0
  15. package/dist/components/Combobox/hooks/useComboboxOpenState.js.map +1 -0
  16. package/dist/components/Combobox/hooks/useComboboxSelection.d.ts +7 -0
  17. package/dist/components/Combobox/hooks/useComboboxSelection.js +49 -0
  18. package/dist/components/Combobox/hooks/useComboboxSelection.js.map +1 -0
  19. package/dist/components/Combobox/hooks/useDebouncedState.d.ts +5 -0
  20. package/dist/components/Combobox/hooks/useDebouncedState.js +19 -0
  21. package/dist/components/Combobox/hooks/useDebouncedState.js.map +1 -0
  22. package/dist/components/Combobox/hooks/useDisplayValue.d.ts +11 -0
  23. package/dist/components/Combobox/hooks/useDisplayValue.js +32 -0
  24. package/dist/components/Combobox/hooks/useDisplayValue.js.map +1 -0
  25. package/dist/components/Combobox/hooks/usePaginatedOptions.d.ts +24 -0
  26. package/dist/components/Combobox/hooks/usePaginatedOptions.js +54 -0
  27. package/dist/components/Combobox/hooks/usePaginatedOptions.js.map +1 -0
  28. package/dist/components/Combobox/hooks/useSelectedCache.d.ts +4 -0
  29. package/dist/components/Combobox/hooks/useSelectedCache.js +17 -0
  30. package/dist/components/Combobox/hooks/useSelectedCache.js.map +1 -0
  31. package/dist/components/Combobox/types.d.ts +0 -1
  32. package/dist/components/Combobox/useCombobox.d.ts +3 -3
  33. package/dist/components/Combobox/useCombobox.js +25 -159
  34. package/dist/components/Combobox/useCombobox.js.map +1 -1
  35. package/dist/components/Tooltip/Tooltip.d.ts +7 -1
  36. package/dist/components/Tooltip/Tooltip.js +31 -24
  37. package/dist/components/Tooltip/Tooltip.js.map +1 -1
  38. package/dist/components/Tooltip/index.js +1 -1
  39. package/dist/index.js +1 -1
  40. package/package.json +1 -1
@@ -92,54 +92,12 @@ export type ComboboxProps<T> = ComboboxSingleProps<T> | ComboboxMultipleProps<T>
92
92
  *
93
93
  * @example Basic usage
94
94
  * ```tsx
95
- * interface User {
96
- * id: string
97
- * name: string
98
- * email: string
99
- * }
100
- *
101
- * <Combobox<User>
102
- * options={users}
103
- * value={selectedUserId}
104
- * onChange={setSelectedUserId}
105
- * getOptionValue={(user) => user.id}
106
- * getOptionLabel={(user) => user.name}
107
- * />
108
- * ```
109
- *
110
- * @example Custom filtering
111
- * ```tsx
112
95
  * <Combobox<User>
113
96
  * options={users}
114
97
  * value={selectedUserId}
115
98
  * onChange={setSelectedUserId}
116
99
  * getOptionValue={(user) => user.id}
117
100
  * getOptionLabel={(user) => user.name}
118
- * filterOptions={(options, searchTerm) =>
119
- * options.filter(user =>
120
- * user.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
121
- * user.email.toLowerCase().includes(searchTerm.toLowerCase())
122
- * )
123
- * }
124
- * />
125
- * ```
126
- *
127
- * @example Virtualized infinite scrolling with pagination
128
- * ```tsx
129
- * <Combobox<User>
130
- * options={[]} // Initial options can be empty
131
- * value={selectedUserId}
132
- * onChange={setSelectedUserId}
133
- * getOptionValue={(user) => user.id}
134
- * getOptionLabel={(user) => user.name}
135
- * fetchPage={async ({ page, search }) => {
136
- * const response = await api.getUsers({ page, search })
137
- * return {
138
- * items: response.users,
139
- * hasNextPage: response.page < response.totalPages,
140
- * nextPage: page + 1
141
- * }
142
- * }}
143
101
  * />
144
102
  * ```
145
103
  */
@@ -1,11 +1,9 @@
1
1
  import { jsxs, jsx } from 'react/jsx-runtime';
2
- import { CaretDown } from '@phosphor-icons/react/dist/ssr/CaretDown';
3
- import { X } from '@phosphor-icons/react/dist/ssr/X';
4
- import { useRef } from 'react';
2
+ import { useRef, useEffect } from 'react';
5
3
  import { cn } from '../../lib/utils.js';
6
- import { Button, buttonVariants } from '../Button/Button.js';
7
4
  import { Command, CommandInput } from '../Command/Command.js';
8
5
  import { PopoverRoot, PopoverTrigger, PopoverContent } from '../Popover/Popover.js';
6
+ import { ComboboxTrigger } from './ComboboxTrigger.js';
9
7
  import { StaticComboboxList } from './StaticComboboxList.js';
10
8
  import { useCombobox } from './useCombobox.js';
11
9
  import { VirtualizedComboboxList } from './VirtualizedComboboxList.js';
@@ -57,6 +55,12 @@ const Combobox = (props) => {
57
55
  handleClear,
58
56
  loadNextPage
59
57
  } = useCombobox(props);
58
+ useEffect(() => {
59
+ if (!open || !onOpenAutoFocus) return;
60
+ const timeoutId = globalThis.setTimeout(() => searchInputRef.current?.focus(), 0);
61
+ return () => globalThis.clearTimeout(timeoutId);
62
+ }, [open, onOpenAutoFocus]);
63
+ const hasMultipleRenderLabel = !!(multiple && "renderLabel" in props && props.renderLabel);
60
64
  return /* @__PURE__ */ jsxs("div", { className: "flex w-full flex-col gap-1", children: [
61
65
  /* @__PURE__ */ jsxs(PopoverRoot, { open, onOpenChange: setOpen, modal, children: [
62
66
  /* @__PURE__ */ jsx(
@@ -76,59 +80,21 @@ const Combobox = (props) => {
76
80
  event.preventDefault();
77
81
  event.stopPropagation();
78
82
  },
79
- children: /* @__PURE__ */ jsxs(
80
- Button,
83
+ children: /* @__PURE__ */ jsx(
84
+ ComboboxTrigger,
81
85
  {
82
86
  id,
83
- type: "button",
84
87
  disabled,
85
- variant: "text",
86
- className: cn(
87
- buttonVariants({ variant: "input", size: "lg" }),
88
- "relative flex justify-between rounded-lg",
89
- multiple && "renderLabel" in props && props.renderLabel ? "h-auto min-h-12" : "h-12",
90
- open && "border-neutral-950",
91
- disabled && "cursor-not-allowed",
92
- error && "border-error-400 focus-visible:border-error-700",
93
- className
94
- ),
95
- "aria-expanded": open,
96
- "aria-haspopup": "listbox",
97
- onMouseEnter: () => setIsHovered(true),
98
- onMouseLeave: () => setIsHovered(false),
99
- children: [
100
- /* @__PURE__ */ jsx(
101
- "span",
102
- {
103
- className: cn(
104
- "block",
105
- !hasValue && "text-neutral-300",
106
- !(multiple && "renderLabel" in props && props.renderLabel) && "truncate"
107
- ),
108
- children: displayValue
109
- }
110
- ),
111
- /* @__PURE__ */ jsx(
112
- CaretDown,
113
- {
114
- className: cn(
115
- "h-4 w-4 shrink-0 opacity-50 transition-opacity duration-150",
116
- showClearButton ? "opacity-0" : "opacity-50"
117
- )
118
- }
119
- ),
120
- onClear && hasValue && /* @__PURE__ */ jsx(
121
- X,
122
- {
123
- "data-testid": "clear-button",
124
- className: cn(
125
- "absolute right-4 z-10 h-4 w-4 shrink-0 cursor-pointer transition-opacity duration-150",
126
- showClearButton ? "opacity-100 hover:opacity-70" : "opacity-0"
127
- ),
128
- onClick: handleClear
129
- }
130
- )
131
- ]
88
+ open,
89
+ error,
90
+ hasValue,
91
+ showClearButton,
92
+ showClearIcon: !!onClear,
93
+ isMultilineLabel: hasMultipleRenderLabel,
94
+ displayValue,
95
+ className,
96
+ onHoverChange: setIsHovered,
97
+ onClear: handleClear
132
98
  }
133
99
  )
134
100
  }
@@ -140,10 +106,7 @@ const Combobox = (props) => {
140
106
  container,
141
107
  side: "bottom",
142
108
  align,
143
- onOpenAutoFocus: onOpenAutoFocus ? (event) => {
144
- event.preventDefault();
145
- searchInputRef.current?.focus();
146
- } : void 0,
109
+ onOpenAutoFocus: onOpenAutoFocus ? (event) => event.preventDefault() : void 0,
147
110
  children: /* @__PURE__ */ jsxs(Command, { shouldFilter: false, children: [
148
111
  /* @__PURE__ */ jsx(
149
112
  CommandInput,
@@ -170,8 +133,7 @@ const Combobox = (props) => {
170
133
  maxHeight: boundedListMaxHeight,
171
134
  hasNextPage,
172
135
  loadingMore,
173
- onLoadMore: loadNextPage,
174
- open
136
+ onLoadMore: loadNextPage
175
137
  }
176
138
  ) : /* @__PURE__ */ jsx(
177
139
  StaticComboboxList,
@@ -1 +1 @@
1
- {"version":3,"file":"Combobox.js","sources":["../../../src/components/Combobox/Combobox.tsx"],"sourcesContent":["import { CaretDown } from '@phosphor-icons/react/dist/ssr/CaretDown'\nimport { X } from '@phosphor-icons/react/dist/ssr/X'\nimport { useRef } from 'react'\n\nimport { cn } from '../../lib/utils'\nimport { Button, buttonVariants } from '../Button'\nimport { Command, CommandInput } from '../Command'\nimport { PopoverContent, PopoverRoot, PopoverTrigger } from '../Popover'\n\nimport { StaticComboboxList } from './StaticComboboxList'\nimport { useCombobox } from './useCombobox'\nimport { VirtualizedComboboxList } from './VirtualizedComboboxList'\n\ntype ComboboxBaseProps<T> = {\n /** Unique identifier for the combobox */\n id?: string\n /** Array of options to display in the combobox */\n options: Array<T>\n /** Function to get the unique identifier from an option. */\n getOptionValue: (option: T) => string\n /** Function to get the display text from an option. */\n getOptionLabel: (option: T) => string\n /** Custom render function for options. If not provided, defaults to showing a checkmark and label */\n renderOption?: (option: T, isSelected: boolean) => React.ReactNode\n /** Placeholder text shown when no option is selected */\n placeholder?: string\n /** Placeholder text for the search input field */\n searchPlaceholder?: string\n /** Message shown when no options match the search query */\n emptyMessage?: string\n /** Additional CSS classes to apply to the combobox trigger */\n className?: string\n /** Additional CSS classes to apply to the popover content */\n contentClassName?: string\n /** Whether the combobox is disabled */\n disabled?: boolean\n /** Maximum height of the options list. Can be any valid CSS height value */\n maxHeight?: string | number\n /** Whether to close the dropdown when an option is selected. Defaults to `true` for single select and `false` for multi-select. */\n closeOnSelect?: boolean\n /** Whether the combobox is in a loading state */\n loading?: boolean\n /** Message to show when in loading state */\n loadingPlaceholder?: string\n /** Whether the combobox has an error */\n error?: boolean | string\n /** Custom function to filter options based on search term */\n filterOptions?: (options: Array<T>, searchTerm: string) => Array<T>\n /** Callback function executed when the clear button is clicked. When provided, an X button will appear on hover to clear the selection. */\n onClear?: boolean | (() => void)\n /** Whether the selection can be cleared */\n clearable?: boolean\n /** Whether the combobox is inside a modal */\n modal?: boolean\n /** Placeholder text for the selected multiple options */\n selectedMultiplePlaceholder?: string\n /** Placeholder text for the multiple options */\n multipleOptionsPlaceholder?: string\n /** Special options that appear at the top with their own title. When selected, the combobox switches to single-select mode. */\n specialOptions?: Array<T>\n /** Title for the special options group */\n specialOptionsTitle?: string\n /** Container element to position the combobox relative to. */\n container?: HTMLElement\n /** Alignment of the dropdown relative to the trigger. Defaults to 'center'. */\n align?: 'start' | 'center' | 'end'\n /** Auto-focus the search input when the dropdown opens. */\n onOpenAutoFocus?: boolean\n}\n\nexport type ComboboxSingleProps<T> = ComboboxBaseProps<T> & {\n multiple?: false\n value?: string\n onChange: (value: string) => void\n /** Custom render function for the selected value display. */\n renderLabel?: (selectedOption: T) => React.ReactNode\n /**\n * Async pagination function.\n * Note: Multiple selection is not supported with virtualization.\n */\n fetchPage?: (params: {\n page: number\n search?: string\n }) => Promise<{ items: Array<T>; hasNextPage: boolean; nextPage: number }>\n /** Prefetch the first page on mount (virtualized only), so data is ready before opening. */\n prefetch?: boolean\n}\n\nexport type ComboboxMultipleProps<T> = ComboboxBaseProps<T> & {\n multiple: true\n value?: Array<string>\n onChange: (value: Array<string>) => void\n /** Custom render function for the selected value(s) display. */\n renderLabel?: (selectedOptions: Array<T>, onRemove: (value: string) => void) => React.ReactNode\n /** fetchPage is not allowed with multiple selection */\n fetchPage?: never\n /** prefetch is not allowed with multiple selection */\n prefetch?: never\n}\n\nexport type ComboboxProps<T> = ComboboxSingleProps<T> | ComboboxMultipleProps<T>\n\n/**\n * A searchable combobox component with support for custom rendering, keyboard navigation, and search filtering.\n *\n * @example Basic usage\n * ```tsx\n * interface User {\n * id: string\n * name: string\n * email: string\n * }\n *\n * <Combobox<User>\n * options={users}\n * value={selectedUserId}\n * onChange={setSelectedUserId}\n * getOptionValue={(user) => user.id}\n * getOptionLabel={(user) => user.name}\n * />\n * ```\n *\n * @example Custom filtering\n * ```tsx\n * <Combobox<User>\n * options={users}\n * value={selectedUserId}\n * onChange={setSelectedUserId}\n * getOptionValue={(user) => user.id}\n * getOptionLabel={(user) => user.name}\n * filterOptions={(options, searchTerm) =>\n * options.filter(user =>\n * user.name.toLowerCase().includes(searchTerm.toLowerCase()) ||\n * user.email.toLowerCase().includes(searchTerm.toLowerCase())\n * )\n * }\n * />\n * ```\n *\n * @example Virtualized infinite scrolling with pagination\n * ```tsx\n * <Combobox<User>\n * options={[]} // Initial options can be empty\n * value={selectedUserId}\n * onChange={setSelectedUserId}\n * getOptionValue={(user) => user.id}\n * getOptionLabel={(user) => user.name}\n * fetchPage={async ({ page, search }) => {\n * const response = await api.getUsers({ page, search })\n * return {\n * items: response.users,\n * hasNextPage: response.page < response.totalPages,\n * nextPage: page + 1\n * }\n * }}\n * />\n * ```\n */\nexport const Combobox = <T extends object>(props: ComboboxProps<T>) => {\n const {\n id,\n options,\n getOptionValue,\n getOptionLabel,\n searchPlaceholder = 'Search...',\n emptyMessage = 'No results found.',\n className = 'w-60',\n contentClassName,\n disabled = false,\n maxHeight = '300px',\n renderOption,\n loading = false,\n loadingPlaceholder = 'Cargando...',\n error = false,\n multiple,\n onClear,\n modal = false,\n selectedMultiplePlaceholder = 'Selected',\n multipleOptionsPlaceholder = 'Options',\n specialOptions,\n specialOptionsTitle,\n container,\n align,\n onOpenAutoFocus = false,\n } = props\n\n const searchInputRef = useRef<HTMLInputElement>(null)\n\n const normalizedMaxHeight = typeof maxHeight === 'number' ? `${maxHeight}px` : maxHeight\n const boundedListMaxHeight = `min(${normalizedMaxHeight}, max(120px, calc(var(--radix-popover-content-available-height, 100dvh) - 68px)))`\n\n const {\n open,\n setOpen,\n searchTerm,\n setSearchTerm,\n setIsHovered,\n hasNextPage,\n loadingMore,\n isVirtualized,\n filteredOptions,\n displayValue,\n hasValue,\n showClearButton,\n handleSelect,\n handleClear,\n loadNextPage,\n } = useCombobox(props)\n\n return (\n <div className=\"flex w-full flex-col gap-1\">\n <PopoverRoot open={open} onOpenChange={setOpen} modal={modal}>\n <PopoverTrigger\n asChild\n onPointerDown={(event) => {\n event.stopPropagation()\n if (open) {\n setOpen(false)\n event.preventDefault()\n } else {\n setOpen(true)\n }\n }}\n onClick={(event) => {\n event.preventDefault()\n event.stopPropagation()\n }}\n >\n <Button\n id={id}\n type=\"button\"\n disabled={disabled}\n variant=\"text\"\n className={cn(\n buttonVariants({ variant: 'input', size: 'lg' }),\n 'relative flex justify-between rounded-lg',\n multiple && 'renderLabel' in props && props.renderLabel ? 'h-auto min-h-12' : 'h-12',\n open && 'border-neutral-950',\n disabled && 'cursor-not-allowed',\n error && 'border-error-400 focus-visible:border-error-700',\n className,\n )}\n aria-expanded={open}\n aria-haspopup=\"listbox\"\n onMouseEnter={() => setIsHovered(true)}\n onMouseLeave={() => setIsHovered(false)}\n >\n <span\n className={cn(\n 'block',\n !hasValue && 'text-neutral-300',\n !(multiple && 'renderLabel' in props && props.renderLabel) && 'truncate',\n )}\n >\n {displayValue}\n </span>\n <CaretDown\n className={cn(\n 'h-4 w-4 shrink-0 opacity-50 transition-opacity duration-150',\n showClearButton ? 'opacity-0' : 'opacity-50',\n )}\n />\n {onClear && hasValue && (\n <X\n data-testid=\"clear-button\"\n className={cn(\n 'absolute right-4 z-10 h-4 w-4 shrink-0 cursor-pointer transition-opacity duration-150',\n showClearButton ? 'opacity-100 hover:opacity-70' : 'opacity-0',\n )}\n onClick={handleClear}\n />\n )}\n </Button>\n </PopoverTrigger>\n <PopoverContent\n className={cn('overflow-hidden p-0', contentClassName)}\n container={container}\n side=\"bottom\"\n align={align}\n onOpenAutoFocus={\n onOpenAutoFocus\n ? (event) => {\n event.preventDefault()\n searchInputRef.current?.focus()\n }\n : undefined\n }\n >\n <Command shouldFilter={false}>\n <CommandInput\n ref={searchInputRef}\n placeholder={searchPlaceholder}\n disabled={loading}\n value={searchTerm}\n onValueChange={setSearchTerm}\n />\n {isVirtualized ? (\n <VirtualizedComboboxList\n localOptions={filteredOptions}\n loading={loading}\n loadingPlaceholder={loadingPlaceholder}\n emptyMessage={emptyMessage}\n value={props.value as string}\n getOptionValue={getOptionValue}\n getOptionLabel={getOptionLabel}\n handleSelect={handleSelect}\n renderOption={renderOption}\n maxHeight={boundedListMaxHeight}\n hasNextPage={hasNextPage}\n loadingMore={loadingMore}\n onLoadMore={loadNextPage}\n open={open}\n />\n ) : (\n <StaticComboboxList\n filteredOptions={filteredOptions}\n loading={loading}\n loadingPlaceholder={loadingPlaceholder}\n emptyMessage={emptyMessage}\n multiple={!!multiple}\n value={props.value}\n getOptionValue={getOptionValue}\n getOptionLabel={getOptionLabel}\n handleSelect={handleSelect}\n renderOption={renderOption}\n maxHeight={boundedListMaxHeight}\n selectedMultiplePlaceholder={selectedMultiplePlaceholder}\n multipleOptionsPlaceholder={multipleOptionsPlaceholder}\n options={options}\n specialOptions={specialOptions}\n specialOptionsTitle={specialOptionsTitle}\n />\n )}\n </Command>\n </PopoverContent>\n </PopoverRoot>\n {typeof error === 'string' && <span className=\"text-error-500 text-sm\">{error}</span>}\n </div>\n )\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"Combobox.js","sources":["../../../src/components/Combobox/Combobox.tsx"],"sourcesContent":["import { useEffect, useRef } from 'react'\n\nimport { cn } from '../../lib/utils'\nimport { Command, CommandInput } from '../Command'\nimport { PopoverContent, PopoverRoot, PopoverTrigger } from '../Popover'\n\nimport { ComboboxTrigger } from './ComboboxTrigger'\nimport { StaticComboboxList } from './StaticComboboxList'\nimport { useCombobox } from './useCombobox'\nimport { VirtualizedComboboxList } from './VirtualizedComboboxList'\n\ntype ComboboxBaseProps<T> = {\n /** Unique identifier for the combobox */\n id?: string\n /** Array of options to display in the combobox */\n options: Array<T>\n /** Function to get the unique identifier from an option. */\n getOptionValue: (option: T) => string\n /** Function to get the display text from an option. */\n getOptionLabel: (option: T) => string\n /** Custom render function for options. If not provided, defaults to showing a checkmark and label */\n renderOption?: (option: T, isSelected: boolean) => React.ReactNode\n /** Placeholder text shown when no option is selected */\n placeholder?: string\n /** Placeholder text for the search input field */\n searchPlaceholder?: string\n /** Message shown when no options match the search query */\n emptyMessage?: string\n /** Additional CSS classes to apply to the combobox trigger */\n className?: string\n /** Additional CSS classes to apply to the popover content */\n contentClassName?: string\n /** Whether the combobox is disabled */\n disabled?: boolean\n /** Maximum height of the options list. Can be any valid CSS height value */\n maxHeight?: string | number\n /** Whether to close the dropdown when an option is selected. Defaults to `true` for single select and `false` for multi-select. */\n closeOnSelect?: boolean\n /** Whether the combobox is in a loading state */\n loading?: boolean\n /** Message to show when in loading state */\n loadingPlaceholder?: string\n /** Whether the combobox has an error */\n error?: boolean | string\n /** Custom function to filter options based on search term */\n filterOptions?: (options: Array<T>, searchTerm: string) => Array<T>\n /** Callback function executed when the clear button is clicked. When provided, an X button will appear on hover to clear the selection. */\n onClear?: boolean | (() => void)\n /** Whether the selection can be cleared */\n clearable?: boolean\n /** Whether the combobox is inside a modal */\n modal?: boolean\n /** Placeholder text for the selected multiple options */\n selectedMultiplePlaceholder?: string\n /** Placeholder text for the multiple options */\n multipleOptionsPlaceholder?: string\n /** Special options that appear at the top with their own title. When selected, the combobox switches to single-select mode. */\n specialOptions?: Array<T>\n /** Title for the special options group */\n specialOptionsTitle?: string\n /** Container element to position the combobox relative to. */\n container?: HTMLElement\n /** Alignment of the dropdown relative to the trigger. Defaults to 'center'. */\n align?: 'start' | 'center' | 'end'\n /** Auto-focus the search input when the dropdown opens. */\n onOpenAutoFocus?: boolean\n}\n\nexport type ComboboxSingleProps<T> = ComboboxBaseProps<T> & {\n multiple?: false\n value?: string\n onChange: (value: string) => void\n /** Custom render function for the selected value display. */\n renderLabel?: (selectedOption: T) => React.ReactNode\n /**\n * Async pagination function.\n * Note: Multiple selection is not supported with virtualization.\n */\n fetchPage?: (params: {\n page: number\n search?: string\n }) => Promise<{ items: Array<T>; hasNextPage: boolean; nextPage: number }>\n /** Prefetch the first page on mount (virtualized only), so data is ready before opening. */\n prefetch?: boolean\n}\n\nexport type ComboboxMultipleProps<T> = ComboboxBaseProps<T> & {\n multiple: true\n value?: Array<string>\n onChange: (value: Array<string>) => void\n /** Custom render function for the selected value(s) display. */\n renderLabel?: (selectedOptions: Array<T>, onRemove: (value: string) => void) => React.ReactNode\n /** fetchPage is not allowed with multiple selection */\n fetchPage?: never\n /** prefetch is not allowed with multiple selection */\n prefetch?: never\n}\n\nexport type ComboboxProps<T> = ComboboxSingleProps<T> | ComboboxMultipleProps<T>\n\n/**\n * A searchable combobox component with support for custom rendering, keyboard navigation, and search filtering.\n *\n * @example Basic usage\n * ```tsx\n * <Combobox<User>\n * options={users}\n * value={selectedUserId}\n * onChange={setSelectedUserId}\n * getOptionValue={(user) => user.id}\n * getOptionLabel={(user) => user.name}\n * />\n * ```\n */\nexport const Combobox = <T extends object>(props: ComboboxProps<T>) => {\n const {\n id,\n options,\n getOptionValue,\n getOptionLabel,\n searchPlaceholder = 'Search...',\n emptyMessage = 'No results found.',\n className = 'w-60',\n contentClassName,\n disabled = false,\n maxHeight = '300px',\n renderOption,\n loading = false,\n loadingPlaceholder = 'Cargando...',\n error = false,\n multiple,\n onClear,\n modal = false,\n selectedMultiplePlaceholder = 'Selected',\n multipleOptionsPlaceholder = 'Options',\n specialOptions,\n specialOptionsTitle,\n container,\n align,\n onOpenAutoFocus = false,\n } = props\n\n const searchInputRef = useRef<HTMLInputElement>(null)\n\n const normalizedMaxHeight = typeof maxHeight === 'number' ? `${maxHeight}px` : maxHeight\n const boundedListMaxHeight = `min(${normalizedMaxHeight}, max(120px, calc(var(--radix-popover-content-available-height, 100dvh) - 68px)))`\n\n const {\n open,\n setOpen,\n searchTerm,\n setSearchTerm,\n setIsHovered,\n hasNextPage,\n loadingMore,\n isVirtualized,\n filteredOptions,\n displayValue,\n hasValue,\n showClearButton,\n handleSelect,\n handleClear,\n loadNextPage,\n } = useCombobox(props)\n\n useEffect(() => {\n if (!open || !onOpenAutoFocus) return\n const timeoutId = globalThis.setTimeout(() => searchInputRef.current?.focus(), 0)\n return () => globalThis.clearTimeout(timeoutId)\n }, [open, onOpenAutoFocus])\n\n const hasMultipleRenderLabel = !!(multiple && 'renderLabel' in props && props.renderLabel)\n\n return (\n <div className=\"flex w-full flex-col gap-1\">\n <PopoverRoot open={open} onOpenChange={setOpen} modal={modal}>\n <PopoverTrigger\n asChild\n onPointerDown={(event) => {\n event.stopPropagation()\n if (open) {\n setOpen(false)\n event.preventDefault()\n } else {\n setOpen(true)\n }\n }}\n onClick={(event) => {\n event.preventDefault()\n event.stopPropagation()\n }}\n >\n <ComboboxTrigger\n id={id}\n disabled={disabled}\n open={open}\n error={error}\n hasValue={hasValue}\n showClearButton={showClearButton}\n showClearIcon={!!onClear}\n isMultilineLabel={hasMultipleRenderLabel}\n displayValue={displayValue}\n className={className}\n onHoverChange={setIsHovered}\n onClear={handleClear}\n />\n </PopoverTrigger>\n <PopoverContent\n className={cn('overflow-hidden p-0', contentClassName)}\n container={container}\n side=\"bottom\"\n align={align}\n onOpenAutoFocus={onOpenAutoFocus ? (event) => event.preventDefault() : undefined}\n >\n <Command shouldFilter={false}>\n <CommandInput\n ref={searchInputRef}\n placeholder={searchPlaceholder}\n disabled={loading}\n value={searchTerm}\n onValueChange={setSearchTerm}\n />\n {isVirtualized ? (\n <VirtualizedComboboxList\n localOptions={filteredOptions}\n loading={loading}\n loadingPlaceholder={loadingPlaceholder}\n emptyMessage={emptyMessage}\n value={props.value as string}\n getOptionValue={getOptionValue}\n getOptionLabel={getOptionLabel}\n handleSelect={handleSelect}\n renderOption={renderOption}\n maxHeight={boundedListMaxHeight}\n hasNextPage={hasNextPage}\n loadingMore={loadingMore}\n onLoadMore={loadNextPage}\n />\n ) : (\n <StaticComboboxList\n filteredOptions={filteredOptions}\n loading={loading}\n loadingPlaceholder={loadingPlaceholder}\n emptyMessage={emptyMessage}\n multiple={!!multiple}\n value={props.value}\n getOptionValue={getOptionValue}\n getOptionLabel={getOptionLabel}\n handleSelect={handleSelect}\n renderOption={renderOption}\n maxHeight={boundedListMaxHeight}\n selectedMultiplePlaceholder={selectedMultiplePlaceholder}\n multipleOptionsPlaceholder={multipleOptionsPlaceholder}\n options={options}\n specialOptions={specialOptions}\n specialOptionsTitle={specialOptionsTitle}\n />\n )}\n </Command>\n </PopoverContent>\n </PopoverRoot>\n {typeof error === 'string' && <span className=\"text-error-500 text-sm\">{error}</span>}\n </div>\n )\n}\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ export declare const ComboboxTrigger: import('react').ForwardRefExoticComponent<Readonly<{
2
+ id?: string;
3
+ disabled?: boolean;
4
+ open: boolean;
5
+ error?: boolean | string;
6
+ hasValue: boolean;
7
+ showClearButton: unknown;
8
+ showClearIcon: boolean;
9
+ isMultilineLabel: boolean;
10
+ displayValue: React.ReactNode;
11
+ className?: string;
12
+ onHoverChange: (hovered: boolean) => void;
13
+ onClear: (event: React.MouseEvent) => void;
14
+ }> & import('react').RefAttributes<HTMLButtonElement>>;
@@ -0,0 +1,74 @@
1
+ import { jsxs, jsx } from 'react/jsx-runtime';
2
+ import { CaretDown } from '@phosphor-icons/react/dist/ssr/CaretDown';
3
+ import { X } from '@phosphor-icons/react/dist/ssr/X';
4
+ import { forwardRef } from 'react';
5
+ import { cn } from '../../lib/utils.js';
6
+ import { Button, buttonVariants } from '../Button/Button.js';
7
+
8
+ const ComboboxTrigger = forwardRef(
9
+ ({
10
+ id,
11
+ disabled,
12
+ open,
13
+ error,
14
+ hasValue,
15
+ showClearButton,
16
+ showClearIcon,
17
+ isMultilineLabel,
18
+ displayValue,
19
+ className,
20
+ onHoverChange,
21
+ onClear,
22
+ ...rest
23
+ }, ref) => /* @__PURE__ */ jsxs(
24
+ Button,
25
+ {
26
+ ref,
27
+ id,
28
+ type: "button",
29
+ disabled,
30
+ variant: "text",
31
+ className: cn(
32
+ buttonVariants({ variant: "input", size: "lg" }),
33
+ "relative flex justify-between rounded-lg",
34
+ isMultilineLabel ? "h-auto min-h-12" : "h-12",
35
+ open && "border-neutral-950",
36
+ disabled && "cursor-not-allowed",
37
+ error && "border-error-400 focus-visible:border-error-700",
38
+ className
39
+ ),
40
+ "aria-expanded": open,
41
+ "aria-haspopup": "listbox",
42
+ onMouseEnter: () => onHoverChange(true),
43
+ onMouseLeave: () => onHoverChange(false),
44
+ ...rest,
45
+ children: [
46
+ /* @__PURE__ */ jsx("span", { className: cn("block", !hasValue && "text-neutral-300", !isMultilineLabel && "truncate"), children: displayValue }),
47
+ /* @__PURE__ */ jsx(
48
+ CaretDown,
49
+ {
50
+ className: cn(
51
+ "h-4 w-4 shrink-0 opacity-50 transition-opacity duration-150",
52
+ showClearButton ? "opacity-0" : "opacity-50"
53
+ )
54
+ }
55
+ ),
56
+ showClearIcon && hasValue && /* @__PURE__ */ jsx(
57
+ X,
58
+ {
59
+ "data-testid": "clear-button",
60
+ className: cn(
61
+ "absolute right-4 z-10 h-4 w-4 shrink-0 cursor-pointer transition-opacity duration-150",
62
+ showClearButton ? "opacity-100 hover:opacity-70" : "opacity-0"
63
+ ),
64
+ onClick: onClear
65
+ }
66
+ )
67
+ ]
68
+ }
69
+ )
70
+ );
71
+ ComboboxTrigger.displayName = "ComboboxTrigger";
72
+
73
+ export { ComboboxTrigger };
74
+ //# sourceMappingURL=ComboboxTrigger.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ComboboxTrigger.js","sources":["../../../src/components/Combobox/ComboboxTrigger.tsx"],"sourcesContent":["import { CaretDown } from '@phosphor-icons/react/dist/ssr/CaretDown'\nimport { X } from '@phosphor-icons/react/dist/ssr/X'\nimport { forwardRef } from 'react'\n\nimport { cn } from '../../lib/utils'\nimport { Button, buttonVariants } from '../Button'\n\ntype ComboboxTriggerProps = Readonly<{\n id?: string\n disabled?: boolean\n open: boolean\n error?: boolean | string\n hasValue: boolean\n showClearButton: unknown\n showClearIcon: boolean\n isMultilineLabel: boolean\n displayValue: React.ReactNode\n className?: string\n onHoverChange: (hovered: boolean) => void\n onClear: (event: React.MouseEvent) => void\n}>\n\nexport const ComboboxTrigger = forwardRef<HTMLButtonElement, ComboboxTriggerProps>(\n (\n {\n id,\n disabled,\n open,\n error,\n hasValue,\n showClearButton,\n showClearIcon,\n isMultilineLabel,\n displayValue,\n className,\n onHoverChange,\n onClear,\n ...rest\n },\n ref,\n ) => (\n <Button\n ref={ref}\n id={id}\n type=\"button\"\n disabled={disabled}\n variant=\"text\"\n className={cn(\n buttonVariants({ variant: 'input', size: 'lg' }),\n 'relative flex justify-between rounded-lg',\n isMultilineLabel ? 'h-auto min-h-12' : 'h-12',\n open && 'border-neutral-950',\n disabled && 'cursor-not-allowed',\n error && 'border-error-400 focus-visible:border-error-700',\n className,\n )}\n aria-expanded={open}\n aria-haspopup=\"listbox\"\n onMouseEnter={() => onHoverChange(true)}\n onMouseLeave={() => onHoverChange(false)}\n {...rest}\n >\n <span className={cn('block', !hasValue && 'text-neutral-300', !isMultilineLabel && 'truncate')}>\n {displayValue}\n </span>\n <CaretDown\n className={cn(\n 'h-4 w-4 shrink-0 opacity-50 transition-opacity duration-150',\n showClearButton ? 'opacity-0' : 'opacity-50',\n )}\n />\n {showClearIcon && hasValue && (\n <X\n data-testid=\"clear-button\"\n className={cn(\n 'absolute right-4 z-10 h-4 w-4 shrink-0 cursor-pointer transition-opacity duration-150',\n showClearButton ? 'opacity-100 hover:opacity-70' : 'opacity-0',\n )}\n onClick={onClear}\n />\n )}\n </Button>\n ),\n)\n\nComboboxTrigger.displayName = 'ComboboxTrigger'\n"],"names":[],"mappings":";;;;;;;AAsBO,MAAM,eAAA,GAAkB,UAAA;AAAA,EAC7B,CACE;AAAA,IACE,EAAA;AAAA,IACA,QAAA;AAAA,IACA,IAAA;AAAA,IACA,KAAA;AAAA,IACA,QAAA;AAAA,IACA,eAAA;AAAA,IACA,aAAA;AAAA,IACA,gBAAA;AAAA,IACA,YAAA;AAAA,IACA,SAAA;AAAA,IACA,aAAA;AAAA,IACA,OAAA;AAAA,IACA,GAAG;AAAA,KAEL,GAAA,qBAEA,IAAA;AAAA,IAAC,MAAA;AAAA,IAAA;AAAA,MACC,GAAA;AAAA,MACA,EAAA;AAAA,MACA,IAAA,EAAK,QAAA;AAAA,MACL,QAAA;AAAA,MACA,OAAA,EAAQ,MAAA;AAAA,MACR,SAAA,EAAW,EAAA;AAAA,QACT,eAAe,EAAE,OAAA,EAAS,OAAA,EAAS,IAAA,EAAM,MAAM,CAAA;AAAA,QAC/C,0CAAA;AAAA,QACA,mBAAmB,iBAAA,GAAoB,MAAA;AAAA,QACvC,IAAA,IAAQ,oBAAA;AAAA,QACR,QAAA,IAAY,oBAAA;AAAA,QACZ,KAAA,IAAS,iDAAA;AAAA,QACT;AAAA,OACF;AAAA,MACA,eAAA,EAAe,IAAA;AAAA,MACf,eAAA,EAAc,SAAA;AAAA,MACd,YAAA,EAAc,MAAM,aAAA,CAAc,IAAI,CAAA;AAAA,MACtC,YAAA,EAAc,MAAM,aAAA,CAAc,KAAK,CAAA;AAAA,MACtC,GAAG,IAAA;AAAA,MAEJ,QAAA,EAAA;AAAA,wBAAA,GAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAW,EAAA,CAAG,OAAA,EAAS,CAAC,QAAA,IAAY,kBAAA,EAAoB,CAAC,gBAAA,IAAoB,UAAU,CAAA,EAC1F,QAAA,EAAA,YAAA,EACH,CAAA;AAAA,wBACA,GAAA;AAAA,UAAC,SAAA;AAAA,UAAA;AAAA,YACC,SAAA,EAAW,EAAA;AAAA,cACT,6DAAA;AAAA,cACA,kBAAkB,WAAA,GAAc;AAAA;AAClC;AAAA,SACF;AAAA,QACC,iBAAiB,QAAA,oBAChB,GAAA;AAAA,UAAC,CAAA;AAAA,UAAA;AAAA,YACC,aAAA,EAAY,cAAA;AAAA,YACZ,SAAA,EAAW,EAAA;AAAA,cACT,uFAAA;AAAA,cACA,kBAAkB,8BAAA,GAAiC;AAAA,aACrD;AAAA,YACA,OAAA,EAAS;AAAA;AAAA;AACX;AAAA;AAAA;AAIR;AAEA,eAAA,CAAgB,WAAA,GAAc,iBAAA;;;;"}
@@ -1,2 +1,2 @@
1
1
  import { VirtualizedComboboxListProps } from './types';
2
- export declare const VirtualizedComboboxList: <T extends object>({ localOptions, loading, loadingPlaceholder, emptyMessage, value, getOptionValue, getOptionLabel, handleSelect, renderOption, maxHeight, hasNextPage, loadingMore, onLoadMore, open, }: VirtualizedComboboxListProps<T>) => import("react/jsx-runtime").JSX.Element;
2
+ export declare const VirtualizedComboboxList: <T extends object>({ localOptions, loading, loadingPlaceholder, emptyMessage, value, getOptionValue, getOptionLabel, handleSelect, renderOption, maxHeight, hasNextPage, loadingMore, onLoadMore, }: VirtualizedComboboxListProps<T>) => import("react/jsx-runtime").JSX.Element;
@@ -18,8 +18,7 @@ const VirtualizedComboboxList = ({
18
18
  maxHeight,
19
19
  hasNextPage,
20
20
  loadingMore,
21
- onLoadMore,
22
- open
21
+ onLoadMore
23
22
  }) => {
24
23
  const parentRef = useRef(null);
25
24
  const hasTriggeredRef = useRef(false);
@@ -33,20 +32,7 @@ const VirtualizedComboboxList = ({
33
32
  });
34
33
  useEffect(() => {
35
34
  hasTriggeredRef.current = false;
36
- }, [totalCount]);
37
- const hasScrolledToSelectedRef = useRef(false);
38
- useEffect(() => {
39
- if (!open) {
40
- hasScrolledToSelectedRef.current = false;
41
- return;
42
- }
43
- if (hasScrolledToSelectedRef.current || !value || totalCount === 0) return;
44
- const selectedIndex = localOptions.findIndex((option) => getOptionValue(option) === value);
45
- if (selectedIndex >= 0) {
46
- hasScrolledToSelectedRef.current = true;
47
- rowVirtualizer.scrollToIndex(selectedIndex, { align: "center" });
48
- }
49
- }, [open, value, totalCount, localOptions, getOptionValue, rowVirtualizer]);
35
+ }, [totalCount, loadingMore]);
50
36
  const virtualItems = rowVirtualizer.getVirtualItems();
51
37
  useEffect(() => {
52
38
  if (virtualItems.length === 0 || !hasNextPage || loadingMore || loading || hasTriggeredRef.current) {
@@ -1 +1 @@
1
- {"version":3,"file":"VirtualizedComboboxList.js","sources":["../../../src/components/Combobox/VirtualizedComboboxList.tsx"],"sourcesContent":["import { Check } from '@phosphor-icons/react/dist/ssr/Check'\nimport { useVirtualizer } from '@tanstack/react-virtual'\nimport { useRef, useEffect } from 'react'\n\nimport { CommandEmpty, CommandItem } from '../Command'\n\nimport { ComboboxOptionWithTooltip } from './ComboboxOptionWithTooltip'\nimport type { VirtualizedComboboxListProps } from './types'\n\nexport const VirtualizedComboboxList = <T extends object>({\n localOptions,\n loading,\n loadingPlaceholder,\n emptyMessage,\n value,\n getOptionValue,\n getOptionLabel,\n handleSelect,\n renderOption,\n maxHeight,\n hasNextPage,\n loadingMore,\n onLoadMore,\n open,\n}: VirtualizedComboboxListProps<T>) => {\n const parentRef = useRef<HTMLDivElement>(null)\n const hasTriggeredRef = useRef(false)\n\n const totalCount = localOptions.length\n const PREFETCH_THRESHOLD = 25\n\n const rowVirtualizer = useVirtualizer({\n count: totalCount,\n getScrollElement: () => parentRef.current,\n estimateSize: () => 40,\n overscan: 10,\n })\n\n useEffect(() => {\n hasTriggeredRef.current = false\n }, [totalCount])\n\n const hasScrolledToSelectedRef = useRef(false)\n\n useEffect(() => {\n if (!open) {\n hasScrolledToSelectedRef.current = false\n return\n }\n if (hasScrolledToSelectedRef.current || !value || totalCount === 0) return\n\n const selectedIndex = localOptions.findIndex((option) => getOptionValue(option) === value)\n if (selectedIndex >= 0) {\n hasScrolledToSelectedRef.current = true\n rowVirtualizer.scrollToIndex(selectedIndex, { align: 'center' })\n }\n }, [open, value, totalCount, localOptions, getOptionValue, rowVirtualizer])\n\n const virtualItems = rowVirtualizer.getVirtualItems()\n\n useEffect(() => {\n if (virtualItems.length === 0 || !hasNextPage || loadingMore || loading || hasTriggeredRef.current) {\n return\n }\n\n const lastVisibleItem = virtualItems.at(-1)\n const itemsFromEnd = totalCount - (lastVisibleItem?.index ?? 0)\n\n if (itemsFromEnd <= PREFETCH_THRESHOLD) {\n hasTriggeredRef.current = true\n onLoadMore()\n }\n }, [virtualItems, totalCount, hasNextPage, loadingMore, loading, onLoadMore])\n\n if (loading && localOptions.length === 0) {\n return (\n <div className=\"text-muted-foreground flex items-center justify-center py-6 text-sm\">{loadingPlaceholder}</div>\n )\n }\n\n if (localOptions.length === 0 && !loading && !loadingMore) {\n return <CommandEmpty>{emptyMessage}</CommandEmpty>\n }\n\n const getItemAtIndex = (index: number) => {\n const option = localOptions[index]\n const isSelected = value === getOptionValue(option)\n return { option, isSelected }\n }\n\n return (\n <div\n ref={parentRef}\n className=\"overflow-auto overscroll-contain\"\n style={{ maxHeight }}\n onWheel={(event) => event.stopPropagation()}\n >\n <div style={{ height: `${rowVirtualizer.getTotalSize()}px`, position: 'relative' }}>\n {rowVirtualizer.getVirtualItems().map((virtualRow) => {\n const { option, isSelected } = getItemAtIndex(virtualRow.index)\n const optionValue = getOptionValue(option)\n\n return (\n <div\n key={optionValue}\n data-index={virtualRow.index}\n ref={(el) => {\n if (el) rowVirtualizer.measureElement(el)\n }}\n style={{\n position: 'absolute',\n top: 0,\n left: 0,\n width: '100%',\n transform: `translateY(${virtualRow.start}px)`,\n }}\n >\n <CommandItem value={optionValue} onSelect={handleSelect}>\n {renderOption ? (\n renderOption(option, isSelected)\n ) : (\n <>\n <Check className={`mr-2 h-4 w-4 ${isSelected ? 'opacity-100' : 'opacity-0'}`} />\n <ComboboxOptionWithTooltip label={getOptionLabel(option)} />\n </>\n )}\n </CommandItem>\n </div>\n )\n })}\n </div>\n {loadingMore && (\n <div className=\"flex items-center justify-center py-2 text-sm text-neutral-500\">{loadingPlaceholder}</div>\n )}\n </div>\n )\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"VirtualizedComboboxList.js","sources":["../../../src/components/Combobox/VirtualizedComboboxList.tsx"],"sourcesContent":["import { Check } from '@phosphor-icons/react/dist/ssr/Check'\nimport { useVirtualizer } from '@tanstack/react-virtual'\nimport { useRef, useEffect } from 'react'\n\nimport { CommandEmpty, CommandItem } from '../Command'\n\nimport { ComboboxOptionWithTooltip } from './ComboboxOptionWithTooltip'\nimport type { VirtualizedComboboxListProps } from './types'\n\nexport const VirtualizedComboboxList = <T extends object>({\n localOptions,\n loading,\n loadingPlaceholder,\n emptyMessage,\n value,\n getOptionValue,\n getOptionLabel,\n handleSelect,\n renderOption,\n maxHeight,\n hasNextPage,\n loadingMore,\n onLoadMore,\n}: VirtualizedComboboxListProps<T>) => {\n const parentRef = useRef<HTMLDivElement>(null)\n const hasTriggeredRef = useRef(false)\n\n const totalCount = localOptions.length\n const PREFETCH_THRESHOLD = 25\n\n const rowVirtualizer = useVirtualizer({\n count: totalCount,\n getScrollElement: () => parentRef.current,\n estimateSize: () => 40,\n overscan: 10,\n })\n\n useEffect(() => {\n hasTriggeredRef.current = false\n }, [totalCount, loadingMore])\n\n const virtualItems = rowVirtualizer.getVirtualItems()\n\n useEffect(() => {\n if (virtualItems.length === 0 || !hasNextPage || loadingMore || loading || hasTriggeredRef.current) {\n return\n }\n\n const lastVisibleItem = virtualItems.at(-1)\n const itemsFromEnd = totalCount - (lastVisibleItem?.index ?? 0)\n\n if (itemsFromEnd <= PREFETCH_THRESHOLD) {\n hasTriggeredRef.current = true\n onLoadMore()\n }\n }, [virtualItems, totalCount, hasNextPage, loadingMore, loading, onLoadMore])\n\n if (loading && localOptions.length === 0) {\n return (\n <div className=\"text-muted-foreground flex items-center justify-center py-6 text-sm\">{loadingPlaceholder}</div>\n )\n }\n\n if (localOptions.length === 0 && !loading && !loadingMore) {\n return <CommandEmpty>{emptyMessage}</CommandEmpty>\n }\n\n const getItemAtIndex = (index: number) => {\n const option = localOptions[index]\n const isSelected = value === getOptionValue(option)\n return { option, isSelected }\n }\n\n return (\n <div\n ref={parentRef}\n className=\"overflow-auto overscroll-contain\"\n style={{ maxHeight }}\n onWheel={(event) => event.stopPropagation()}\n >\n <div style={{ height: `${rowVirtualizer.getTotalSize()}px`, position: 'relative' }}>\n {rowVirtualizer.getVirtualItems().map((virtualRow) => {\n const { option, isSelected } = getItemAtIndex(virtualRow.index)\n const optionValue = getOptionValue(option)\n\n return (\n <div\n key={optionValue}\n data-index={virtualRow.index}\n ref={(el) => {\n if (el) rowVirtualizer.measureElement(el)\n }}\n style={{\n position: 'absolute',\n top: 0,\n left: 0,\n width: '100%',\n transform: `translateY(${virtualRow.start}px)`,\n }}\n >\n <CommandItem value={optionValue} onSelect={handleSelect}>\n {renderOption ? (\n renderOption(option, isSelected)\n ) : (\n <>\n <Check className={`mr-2 h-4 w-4 ${isSelected ? 'opacity-100' : 'opacity-0'}`} />\n <ComboboxOptionWithTooltip label={getOptionLabel(option)} />\n </>\n )}\n </CommandItem>\n </div>\n )\n })}\n </div>\n {loadingMore && (\n <div className=\"flex items-center justify-center py-2 text-sm text-neutral-500\">{loadingPlaceholder}</div>\n )}\n </div>\n )\n}\n"],"names":[],"mappings":";;;;;;;AASO,MAAM,0BAA0B,CAAmB;AAAA,EACxD,YAAA;AAAA,EACA,OAAA;AAAA,EACA,kBAAA;AAAA,EACA,YAAA;AAAA,EACA,KAAA;AAAA,EACA,cAAA;AAAA,EACA,cAAA;AAAA,EACA,YAAA;AAAA,EACA,YAAA;AAAA,EACA,SAAA;AAAA,EACA,WAAA;AAAA,EACA,WAAA;AAAA,EACA;AACF,CAAA,KAAuC;AACrC,EAAA,MAAM,SAAA,GAAY,OAAuB,IAAI,CAAA;AAC7C,EAAA,MAAM,eAAA,GAAkB,OAAO,KAAK,CAAA;AAEpC,EAAA,MAAM,aAAa,YAAA,CAAa,MAAA;AAChC,EAAA,MAAM,kBAAA,GAAqB,EAAA;AAE3B,EAAA,MAAM,iBAAiB,cAAA,CAAe;AAAA,IACpC,KAAA,EAAO,UAAA;AAAA,IACP,gBAAA,EAAkB,MAAM,SAAA,CAAU,OAAA;AAAA,IAClC,cAAc,MAAM,EAAA;AAAA,IACpB,QAAA,EAAU;AAAA,GACX,CAAA;AAED,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,eAAA,CAAgB,OAAA,GAAU,KAAA;AAAA,EAC5B,CAAA,EAAG,CAAC,UAAA,EAAY,WAAW,CAAC,CAAA;AAE5B,EAAA,MAAM,YAAA,GAAe,eAAe,eAAA,EAAgB;AAEpD,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,IAAI,YAAA,CAAa,WAAW,CAAA,IAAK,CAAC,eAAe,WAAA,IAAe,OAAA,IAAW,gBAAgB,OAAA,EAAS;AAClG,MAAA;AAAA,IACF;AAEA,IAAA,MAAM,eAAA,GAAkB,YAAA,CAAa,EAAA,CAAG,EAAE,CAAA;AAC1C,IAAA,MAAM,YAAA,GAAe,UAAA,IAAc,eAAA,EAAiB,KAAA,IAAS,CAAA,CAAA;AAE7D,IAAA,IAAI,gBAAgB,kBAAA,EAAoB;AACtC,MAAA,eAAA,CAAgB,OAAA,GAAU,IAAA;AAC1B,MAAA,UAAA,EAAW;AAAA,IACb;AAAA,EACF,CAAA,EAAG,CAAC,YAAA,EAAc,UAAA,EAAY,aAAa,WAAA,EAAa,OAAA,EAAS,UAAU,CAAC,CAAA;AAE5E,EAAA,IAAI,OAAA,IAAW,YAAA,CAAa,MAAA,KAAW,CAAA,EAAG;AACxC,IAAA,uBACE,GAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,qEAAA,EAAuE,QAAA,EAAA,kBAAA,EAAmB,CAAA;AAAA,EAE7G;AAEA,EAAA,IAAI,aAAa,MAAA,KAAW,CAAA,IAAK,CAAC,OAAA,IAAW,CAAC,WAAA,EAAa;AACzD,IAAA,uBAAO,GAAA,CAAC,gBAAc,QAAA,EAAA,YAAA,EAAa,CAAA;AAAA,EACrC;AAEA,EAAA,MAAM,cAAA,GAAiB,CAAC,KAAA,KAAkB;AACxC,IAAA,MAAM,MAAA,GAAS,aAAa,KAAK,CAAA;AACjC,IAAA,MAAM,UAAA,GAAa,KAAA,KAAU,cAAA,CAAe,MAAM,CAAA;AAClD,IAAA,OAAO,EAAE,QAAQ,UAAA,EAAW;AAAA,EAC9B,CAAA;AAEA,EAAA,uBACE,IAAA;AAAA,IAAC,KAAA;AAAA,IAAA;AAAA,MACC,GAAA,EAAK,SAAA;AAAA,MACL,SAAA,EAAU,kCAAA;AAAA,MACV,KAAA,EAAO,EAAE,SAAA,EAAU;AAAA,MACnB,OAAA,EAAS,CAAC,KAAA,KAAU,KAAA,CAAM,eAAA,EAAgB;AAAA,MAE1C,QAAA,EAAA;AAAA,wBAAA,GAAA,CAAC,SAAI,KAAA,EAAO,EAAE,MAAA,EAAQ,CAAA,EAAG,eAAe,YAAA,EAAc,CAAA,EAAA,CAAA,EAAM,QAAA,EAAU,YAAW,EAC9E,QAAA,EAAA,cAAA,CAAe,iBAAgB,CAAE,GAAA,CAAI,CAAC,UAAA,KAAe;AACpD,UAAA,MAAM,EAAE,MAAA,EAAQ,UAAA,EAAW,GAAI,cAAA,CAAe,WAAW,KAAK,CAAA;AAC9D,UAAA,MAAM,WAAA,GAAc,eAAe,MAAM,CAAA;AAEzC,UAAA,uBACE,GAAA;AAAA,YAAC,KAAA;AAAA,YAAA;AAAA,cAEC,cAAY,UAAA,CAAW,KAAA;AAAA,cACvB,GAAA,EAAK,CAAC,EAAA,KAAO;AACX,gBAAA,IAAI,EAAA,EAAI,cAAA,CAAe,cAAA,CAAe,EAAE,CAAA;AAAA,cAC1C,CAAA;AAAA,cACA,KAAA,EAAO;AAAA,gBACL,QAAA,EAAU,UAAA;AAAA,gBACV,GAAA,EAAK,CAAA;AAAA,gBACL,IAAA,EAAM,CAAA;AAAA,gBACN,KAAA,EAAO,MAAA;AAAA,gBACP,SAAA,EAAW,CAAA,WAAA,EAAc,UAAA,CAAW,KAAK,CAAA,GAAA;AAAA,eAC3C;AAAA,cAEA,QAAA,kBAAA,GAAA,CAAC,WAAA,EAAA,EAAY,KAAA,EAAO,WAAA,EAAa,QAAA,EAAU,YAAA,EACxC,QAAA,EAAA,YAAA,GACC,YAAA,CAAa,MAAA,EAAQ,UAAU,CAAA,mBAE/B,IAAA,CAAA,QAAA,EAAA,EACE,QAAA,EAAA;AAAA,gCAAA,GAAA,CAAC,SAAM,SAAA,EAAW,CAAA,aAAA,EAAgB,UAAA,GAAa,aAAA,GAAgB,WAAW,CAAA,CAAA,EAAI,CAAA;AAAA,gCAC9E,GAAA,CAAC,yBAAA,EAAA,EAA0B,KAAA,EAAO,cAAA,CAAe,MAAM,CAAA,EAAG;AAAA,eAAA,EAC5D,CAAA,EAEJ;AAAA,aAAA;AAAA,YAtBK;AAAA,WAuBP;AAAA,QAEJ,CAAC,CAAA,EACH,CAAA;AAAA,QACC,WAAA,oBACC,GAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,kEAAkE,QAAA,EAAA,kBAAA,EAAmB;AAAA;AAAA;AAAA,GAExG;AAEJ;;;;"}
@@ -0,0 +1,6 @@
1
+ export { useComboboxOpenState } from './useComboboxOpenState';
2
+ export { useComboboxSelection } from './useComboboxSelection';
3
+ export { useDebouncedState } from './useDebouncedState';
4
+ export { useDisplayValue } from './useDisplayValue';
5
+ export { usePaginatedOptions } from './usePaginatedOptions';
6
+ export { useSelectedCache } from './useSelectedCache';
@@ -0,0 +1,7 @@
1
+ export { useComboboxOpenState } from './useComboboxOpenState.js';
2
+ export { useComboboxSelection } from './useComboboxSelection.js';
3
+ export { useDebouncedState } from './useDebouncedState.js';
4
+ export { useDisplayValue } from './useDisplayValue.js';
5
+ export { usePaginatedOptions } from './usePaginatedOptions.js';
6
+ export { useSelectedCache } from './useSelectedCache.js';
7
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;"}
@@ -0,0 +1,7 @@
1
+ export declare function useComboboxOpenState(): {
2
+ open: boolean;
3
+ setOpen: (next: boolean) => void;
4
+ searchTerm: string;
5
+ debouncedSearchTerm: string;
6
+ setSearchTerm: (next: string) => void;
7
+ };
@@ -0,0 +1,18 @@
1
+ import { useState, useCallback } from 'react';
2
+ import { useDebouncedState } from './useDebouncedState.js';
3
+
4
+ function useComboboxOpenState() {
5
+ const [open, setOpenRaw] = useState(false);
6
+ const { value: searchTerm, debouncedValue: debouncedSearchTerm, setValue: setSearchTerm } = useDebouncedState("");
7
+ const setOpen = useCallback(
8
+ (next) => {
9
+ setOpenRaw(next);
10
+ if (!next) setSearchTerm("");
11
+ },
12
+ [setSearchTerm]
13
+ );
14
+ return { open, setOpen, searchTerm, debouncedSearchTerm, setSearchTerm };
15
+ }
16
+
17
+ export { useComboboxOpenState };
18
+ //# sourceMappingURL=useComboboxOpenState.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useComboboxOpenState.js","sources":["../../../../src/components/Combobox/hooks/useComboboxOpenState.ts"],"sourcesContent":["import { useCallback, useState } from 'react'\n\nimport { useDebouncedState } from './useDebouncedState'\n\nexport function useComboboxOpenState() {\n const [open, setOpenRaw] = useState(false)\n const { value: searchTerm, debouncedValue: debouncedSearchTerm, setValue: setSearchTerm } = useDebouncedState('')\n\n const setOpen = useCallback(\n (next: boolean) => {\n setOpenRaw(next)\n if (!next) setSearchTerm('')\n },\n [setSearchTerm],\n )\n\n return { open, setOpen, searchTerm, debouncedSearchTerm, setSearchTerm }\n}\n"],"names":[],"mappings":";;;AAIO,SAAS,oBAAA,GAAuB;AACrC,EAAA,MAAM,CAAC,IAAA,EAAM,UAAU,CAAA,GAAI,SAAS,KAAK,CAAA;AACzC,EAAA,MAAM,EAAE,OAAO,UAAA,EAAY,cAAA,EAAgB,qBAAqB,QAAA,EAAU,aAAA,EAAc,GAAI,iBAAA,CAAkB,EAAE,CAAA;AAEhH,EAAA,MAAM,OAAA,GAAU,WAAA;AAAA,IACd,CAAC,IAAA,KAAkB;AACjB,MAAA,UAAA,CAAW,IAAI,CAAA;AACf,MAAA,IAAI,CAAC,IAAA,EAAM,aAAA,CAAc,EAAE,CAAA;AAAA,IAC7B,CAAA;AAAA,IACA,CAAC,aAAa;AAAA,GAChB;AAEA,EAAA,OAAO,EAAE,IAAA,EAAM,OAAA,EAAS,UAAA,EAAY,qBAAqB,aAAA,EAAc;AACzE;;;;"}
@@ -0,0 +1,7 @@
1
+ import { ComboboxProps } from '../Combobox';
2
+ export declare function useComboboxSelection<T extends object>(props: ComboboxProps<T>, setOpen: (open: boolean) => void): {
3
+ handleSelect: (currentValue: string) => void;
4
+ handleRemove: (valueToRemove: string) => void;
5
+ handleClear: (event: React.MouseEvent) => void;
6
+ hasValue: boolean;
7
+ };
@@ -0,0 +1,49 @@
1
+ import { useMemo } from 'react';
2
+
3
+ function useComboboxSelection(props, setOpen) {
4
+ const { multiple, clearable = true, onClear, specialOptions } = props;
5
+ const closeOnSelect = props.closeOnSelect ?? !props.multiple;
6
+ const handleSelect = (currentValue) => {
7
+ if (multiple) {
8
+ const { onChange } = props;
9
+ const value = Array.isArray(props.value) ? props.value : [];
10
+ const isRemoving = value.includes(currentValue);
11
+ if (specialOptions && specialOptions.length > 0) {
12
+ onChange(isRemoving ? [] : [currentValue]);
13
+ } else {
14
+ if (isRemoving && !clearable && value.length === 1) return;
15
+ onChange(isRemoving ? value.filter((val) => val !== currentValue) : [...value, currentValue]);
16
+ }
17
+ } else {
18
+ const { value, onChange } = props;
19
+ onChange(clearable && currentValue === value ? "" : currentValue);
20
+ }
21
+ if (closeOnSelect) setOpen(false);
22
+ };
23
+ const handleRemove = (valueToRemove) => {
24
+ if (props.multiple) {
25
+ const { onChange } = props;
26
+ const value = Array.isArray(props.value) ? props.value : [];
27
+ if (!clearable && value.length === 1) return;
28
+ onChange(value.filter((val) => val !== valueToRemove));
29
+ }
30
+ };
31
+ const handleClear = (event) => {
32
+ event.preventDefault();
33
+ event.stopPropagation();
34
+ if (multiple) {
35
+ if (clearable) props.onChange([]);
36
+ } else {
37
+ props.onChange("");
38
+ }
39
+ if (typeof onClear === "function") onClear();
40
+ };
41
+ const hasValue = useMemo(() => {
42
+ if (multiple) return Array.isArray(props.value) && props.value.length > 0;
43
+ return !!props.value;
44
+ }, [props.value, multiple]);
45
+ return { handleSelect, handleRemove, handleClear, hasValue };
46
+ }
47
+
48
+ export { useComboboxSelection };
49
+ //# sourceMappingURL=useComboboxSelection.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useComboboxSelection.js","sources":["../../../../src/components/Combobox/hooks/useComboboxSelection.ts"],"sourcesContent":["import { useMemo } from 'react'\n\nimport type { ComboboxProps } from '../Combobox'\n\nexport function useComboboxSelection<T extends object>(props: ComboboxProps<T>, setOpen: (open: boolean) => void) {\n const { multiple, clearable = true, onClear, specialOptions } = props\n const closeOnSelect = props.closeOnSelect ?? !props.multiple\n\n const handleSelect = (currentValue: string) => {\n if (multiple) {\n const { onChange } = props\n const value = Array.isArray(props.value) ? props.value : []\n const isRemoving = value.includes(currentValue)\n\n if (specialOptions && specialOptions.length > 0) {\n onChange(isRemoving ? [] : [currentValue])\n } else {\n if (isRemoving && !clearable && value.length === 1) return\n onChange(isRemoving ? value.filter((val) => val !== currentValue) : [...value, currentValue])\n }\n } else {\n const { value, onChange } = props\n onChange(clearable && currentValue === value ? '' : currentValue)\n }\n\n if (closeOnSelect) setOpen(false)\n }\n\n const handleRemove = (valueToRemove: string) => {\n if (props.multiple) {\n const { onChange } = props\n const value = Array.isArray(props.value) ? props.value : []\n if (!clearable && value.length === 1) return\n onChange(value.filter((val) => val !== valueToRemove))\n }\n }\n\n const handleClear = (event: React.MouseEvent) => {\n event.preventDefault()\n event.stopPropagation()\n\n if (multiple) {\n if (clearable) props.onChange([])\n } else {\n props.onChange('')\n }\n\n if (typeof onClear === 'function') onClear()\n }\n\n const hasValue = useMemo(() => {\n if (multiple) return Array.isArray(props.value) && props.value.length > 0\n return !!props.value\n }, [props.value, multiple])\n\n return { handleSelect, handleRemove, handleClear, hasValue }\n}\n"],"names":[],"mappings":";;AAIO,SAAS,oBAAA,CAAuC,OAAyB,OAAA,EAAkC;AAChH,EAAA,MAAM,EAAE,QAAA,EAAU,SAAA,GAAY,IAAA,EAAM,OAAA,EAAS,gBAAe,GAAI,KAAA;AAChE,EAAA,MAAM,aAAA,GAAgB,KAAA,CAAM,aAAA,IAAiB,CAAC,KAAA,CAAM,QAAA;AAEpD,EAAA,MAAM,YAAA,GAAe,CAAC,YAAA,KAAyB;AAC7C,IAAA,IAAI,QAAA,EAAU;AACZ,MAAA,MAAM,EAAE,UAAS,GAAI,KAAA;AACrB,MAAA,MAAM,KAAA,GAAQ,MAAM,OAAA,CAAQ,KAAA,CAAM,KAAK,CAAA,GAAI,KAAA,CAAM,QAAQ,EAAC;AAC1D,MAAA,MAAM,UAAA,GAAa,KAAA,CAAM,QAAA,CAAS,YAAY,CAAA;AAE9C,MAAA,IAAI,cAAA,IAAkB,cAAA,CAAe,MAAA,GAAS,CAAA,EAAG;AAC/C,QAAA,QAAA,CAAS,UAAA,GAAa,EAAC,GAAI,CAAC,YAAY,CAAC,CAAA;AAAA,MAC3C,CAAA,MAAO;AACL,QAAA,IAAI,UAAA,IAAc,CAAC,SAAA,IAAa,KAAA,CAAM,WAAW,CAAA,EAAG;AACpD,QAAA,QAAA,CAAS,UAAA,GAAa,KAAA,CAAM,MAAA,CAAO,CAAC,GAAA,KAAQ,GAAA,KAAQ,YAAY,CAAA,GAAI,CAAC,GAAG,KAAA,EAAO,YAAY,CAAC,CAAA;AAAA,MAC9F;AAAA,IACF,CAAA,MAAO;AACL,MAAA,MAAM,EAAE,KAAA,EAAO,QAAA,EAAS,GAAI,KAAA;AAC5B,MAAA,QAAA,CAAS,SAAA,IAAa,YAAA,KAAiB,KAAA,GAAQ,EAAA,GAAK,YAAY,CAAA;AAAA,IAClE;AAEA,IAAA,IAAI,aAAA,UAAuB,KAAK,CAAA;AAAA,EAClC,CAAA;AAEA,EAAA,MAAM,YAAA,GAAe,CAAC,aAAA,KAA0B;AAC9C,IAAA,IAAI,MAAM,QAAA,EAAU;AAClB,MAAA,MAAM,EAAE,UAAS,GAAI,KAAA;AACrB,MAAA,MAAM,KAAA,GAAQ,MAAM,OAAA,CAAQ,KAAA,CAAM,KAAK,CAAA,GAAI,KAAA,CAAM,QAAQ,EAAC;AAC1D,MAAA,IAAI,CAAC,SAAA,IAAa,KAAA,CAAM,MAAA,KAAW,CAAA,EAAG;AACtC,MAAA,QAAA,CAAS,MAAM,MAAA,CAAO,CAAC,GAAA,KAAQ,GAAA,KAAQ,aAAa,CAAC,CAAA;AAAA,IACvD;AAAA,EACF,CAAA;AAEA,EAAA,MAAM,WAAA,GAAc,CAAC,KAAA,KAA4B;AAC/C,IAAA,KAAA,CAAM,cAAA,EAAe;AACrB,IAAA,KAAA,CAAM,eAAA,EAAgB;AAEtB,IAAA,IAAI,QAAA,EAAU;AACZ,MAAA,IAAI,SAAA,EAAW,KAAA,CAAM,QAAA,CAAS,EAAE,CAAA;AAAA,IAClC,CAAA,MAAO;AACL,MAAA,KAAA,CAAM,SAAS,EAAE,CAAA;AAAA,IACnB;AAEA,IAAA,IAAI,OAAO,OAAA,KAAY,UAAA,EAAY,OAAA,EAAQ;AAAA,EAC7C,CAAA;AAEA,EAAA,MAAM,QAAA,GAAW,QAAQ,MAAM;AAC7B,IAAA,IAAI,QAAA,SAAiB,KAAA,CAAM,OAAA,CAAQ,MAAM,KAAK,CAAA,IAAK,KAAA,CAAM,KAAA,CAAM,MAAA,GAAS,CAAA;AACxE,IAAA,OAAO,CAAC,CAAC,KAAA,CAAM,KAAA;AAAA,EACjB,CAAA,EAAG,CAAC,KAAA,CAAM,KAAA,EAAO,QAAQ,CAAC,CAAA;AAE1B,EAAA,OAAO,EAAE,YAAA,EAAc,YAAA,EAAc,WAAA,EAAa,QAAA,EAAS;AAC7D;;;;"}
@@ -0,0 +1,5 @@
1
+ export declare function useDebouncedState<T>(initial: T, delay?: number): {
2
+ value: T;
3
+ debouncedValue: T;
4
+ setValue: (next: T) => void;
5
+ };
@@ -0,0 +1,19 @@
1
+ import { useState, useRef, useCallback } from 'react';
2
+
3
+ function useDebouncedState(initial, delay = 300) {
4
+ const [value, setImmediateValue] = useState(initial);
5
+ const [debouncedValue, setDebouncedValue] = useState(initial);
6
+ const timerRef = useRef(null);
7
+ const setValue = useCallback(
8
+ (next) => {
9
+ setImmediateValue(next);
10
+ if (timerRef.current) clearTimeout(timerRef.current);
11
+ timerRef.current = setTimeout(() => setDebouncedValue(next), delay);
12
+ },
13
+ [delay]
14
+ );
15
+ return { value, debouncedValue, setValue };
16
+ }
17
+
18
+ export { useDebouncedState };
19
+ //# sourceMappingURL=useDebouncedState.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useDebouncedState.js","sources":["../../../../src/components/Combobox/hooks/useDebouncedState.ts"],"sourcesContent":["import { useCallback, useRef, useState } from 'react'\n\nexport function useDebouncedState<T>(initial: T, delay: number = 300) {\n const [value, setImmediateValue] = useState<T>(initial)\n const [debouncedValue, setDebouncedValue] = useState<T>(initial)\n const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null)\n\n const setValue = useCallback(\n (next: T) => {\n setImmediateValue(next)\n if (timerRef.current) clearTimeout(timerRef.current)\n timerRef.current = setTimeout(() => setDebouncedValue(next), delay)\n },\n [delay],\n )\n\n return { value, debouncedValue, setValue }\n}\n"],"names":[],"mappings":";;AAEO,SAAS,iBAAA,CAAqB,OAAA,EAAY,KAAA,GAAgB,GAAA,EAAK;AACpE,EAAA,MAAM,CAAC,KAAA,EAAO,iBAAiB,CAAA,GAAI,SAAY,OAAO,CAAA;AACtD,EAAA,MAAM,CAAC,cAAA,EAAgB,iBAAiB,CAAA,GAAI,SAAY,OAAO,CAAA;AAC/D,EAAA,MAAM,QAAA,GAAW,OAA6C,IAAI,CAAA;AAElE,EAAA,MAAM,QAAA,GAAW,WAAA;AAAA,IACf,CAAC,IAAA,KAAY;AACX,MAAA,iBAAA,CAAkB,IAAI,CAAA;AACtB,MAAA,IAAI,QAAA,CAAS,OAAA,EAAS,YAAA,CAAa,QAAA,CAAS,OAAO,CAAA;AACnD,MAAA,QAAA,CAAS,UAAU,UAAA,CAAW,MAAM,iBAAA,CAAkB,IAAI,GAAG,KAAK,CAAA;AAAA,IACpE,CAAA;AAAA,IACA,CAAC,KAAK;AAAA,GACR;AAEA,EAAA,OAAO,EAAE,KAAA,EAAO,cAAA,EAAgB,QAAA,EAAS;AAC3C;;;;"}
@@ -0,0 +1,11 @@
1
+ import { ComboboxProps } from '../Combobox';
2
+ type UseDisplayValueParams<T extends object> = {
3
+ props: ComboboxProps<T>;
4
+ localOptions: Array<T>;
5
+ isVirtualized: boolean;
6
+ resolve: (value: string) => T | undefined;
7
+ recordOptions: (items: Iterable<T>) => void;
8
+ handleRemove: (value: string) => void;
9
+ };
10
+ export declare function useDisplayValue<T extends object>({ props, localOptions, isVirtualized, resolve, recordOptions, handleRemove, }: UseDisplayValueParams<T>): import('react').ReactNode;
11
+ export {};
@@ -0,0 +1,32 @@
1
+ import { useMemo } from 'react';
2
+
3
+ function useDisplayValue({
4
+ props,
5
+ localOptions,
6
+ isVirtualized,
7
+ resolve,
8
+ recordOptions,
9
+ handleRemove
10
+ }) {
11
+ const { options, getOptionValue, getOptionLabel, placeholder = "Select...", multiple, specialOptions } = props;
12
+ return useMemo(() => {
13
+ const available = isVirtualized ? localOptions : options;
14
+ const all = specialOptions ? [...specialOptions, ...available] : available;
15
+ recordOptions(all);
16
+ const lookup = (val) => all.find((option) => getOptionValue(option) === val) ?? resolve(val);
17
+ if (multiple) {
18
+ const { renderLabel: renderLabel2 } = props;
19
+ const value2 = Array.isArray(props.value) ? props.value : [];
20
+ if (value2.length === 0) return placeholder;
21
+ const selected2 = value2.map(lookup).filter((option) => option !== void 0);
22
+ return renderLabel2 ? renderLabel2(selected2, handleRemove) : selected2.map(getOptionLabel).join(", ");
23
+ }
24
+ const { value, renderLabel } = props;
25
+ const selected = value ? lookup(value) : void 0;
26
+ if (renderLabel && selected) return renderLabel(selected);
27
+ return selected ? getOptionLabel(selected) : placeholder;
28
+ }, [props, localOptions, isVirtualized, getOptionLabel, getOptionValue, placeholder, multiple, specialOptions]);
29
+ }
30
+
31
+ export { useDisplayValue };
32
+ //# sourceMappingURL=useDisplayValue.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useDisplayValue.js","sources":["../../../../src/components/Combobox/hooks/useDisplayValue.ts"],"sourcesContent":["import { useMemo } from 'react'\n\nimport type { ComboboxProps } from '../Combobox'\n\ntype UseDisplayValueParams<T extends object> = {\n props: ComboboxProps<T>\n localOptions: Array<T>\n isVirtualized: boolean\n resolve: (value: string) => T | undefined\n recordOptions: (items: Iterable<T>) => void\n handleRemove: (value: string) => void\n}\n\nexport function useDisplayValue<T extends object>({\n props,\n localOptions,\n isVirtualized,\n resolve,\n recordOptions,\n handleRemove,\n}: UseDisplayValueParams<T>) {\n const { options, getOptionValue, getOptionLabel, placeholder = 'Select...', multiple, specialOptions } = props\n\n return useMemo(() => {\n const available = isVirtualized ? localOptions : options\n const all = specialOptions ? [...specialOptions, ...available] : available\n recordOptions(all)\n\n const lookup = (val: string) => all.find((option) => getOptionValue(option) === val) ?? resolve(val)\n\n if (multiple) {\n const { renderLabel } = props\n const value = Array.isArray(props.value) ? props.value : []\n if (value.length === 0) return placeholder\n\n const selected = value.map(lookup).filter((option): option is T => option !== undefined)\n\n return renderLabel ? renderLabel(selected, handleRemove) : selected.map(getOptionLabel).join(', ')\n }\n\n const { value, renderLabel } = props\n const selected = value ? lookup(value) : undefined\n if (renderLabel && selected) return renderLabel(selected)\n return selected ? getOptionLabel(selected) : placeholder\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [props, localOptions, isVirtualized, getOptionLabel, getOptionValue, placeholder, multiple, specialOptions])\n}\n"],"names":["renderLabel","value","selected"],"mappings":";;AAaO,SAAS,eAAA,CAAkC;AAAA,EAChD,KAAA;AAAA,EACA,YAAA;AAAA,EACA,aAAA;AAAA,EACA,OAAA;AAAA,EACA,aAAA;AAAA,EACA;AACF,CAAA,EAA6B;AAC3B,EAAA,MAAM,EAAE,SAAS,cAAA,EAAgB,cAAA,EAAgB,cAAc,WAAA,EAAa,QAAA,EAAU,gBAAe,GAAI,KAAA;AAEzG,EAAA,OAAO,QAAQ,MAAM;AACnB,IAAA,MAAM,SAAA,GAAY,gBAAgB,YAAA,GAAe,OAAA;AACjD,IAAA,MAAM,MAAM,cAAA,GAAiB,CAAC,GAAG,cAAA,EAAgB,GAAG,SAAS,CAAA,GAAI,SAAA;AACjE,IAAA,aAAA,CAAc,GAAG,CAAA;AAEjB,IAAA,MAAM,MAAA,GAAS,CAAC,GAAA,KAAgB,GAAA,CAAI,IAAA,CAAK,CAAC,MAAA,KAAW,cAAA,CAAe,MAAM,CAAA,KAAM,GAAG,CAAA,IAAK,QAAQ,GAAG,CAAA;AAEnG,IAAA,IAAI,QAAA,EAAU;AACZ,MAAA,MAAM,EAAE,WAAA,EAAAA,YAAAA,EAAY,GAAI,KAAA;AACxB,MAAA,MAAMC,MAAAA,GAAQ,MAAM,OAAA,CAAQ,KAAA,CAAM,KAAK,CAAA,GAAI,KAAA,CAAM,QAAQ,EAAC;AAC1D,MAAA,IAAIA,MAAAA,CAAM,MAAA,KAAW,CAAA,EAAG,OAAO,WAAA;AAE/B,MAAA,MAAMC,SAAAA,GAAWD,OAAM,GAAA,CAAI,MAAM,EAAE,MAAA,CAAO,CAAC,MAAA,KAAwB,MAAA,KAAW,MAAS,CAAA;AAEvF,MAAA,OAAOD,YAAAA,GAAcA,YAAAA,CAAYE,SAAAA,EAAU,YAAY,CAAA,GAAIA,UAAS,GAAA,CAAI,cAAc,CAAA,CAAE,IAAA,CAAK,IAAI,CAAA;AAAA,IACnG;AAEA,IAAA,MAAM,EAAE,KAAA,EAAO,WAAA,EAAY,GAAI,KAAA;AAC/B,IAAA,MAAM,QAAA,GAAW,KAAA,GAAQ,MAAA,CAAO,KAAK,CAAA,GAAI,MAAA;AACzC,IAAA,IAAI,WAAA,IAAe,QAAA,EAAU,OAAO,WAAA,CAAY,QAAQ,CAAA;AACxD,IAAA,OAAO,QAAA,GAAW,cAAA,CAAe,QAAQ,CAAA,GAAI,WAAA;AAAA,EAE/C,CAAA,EAAG,CAAC,KAAA,EAAO,YAAA,EAAc,aAAA,EAAe,gBAAgB,cAAA,EAAgB,WAAA,EAAa,QAAA,EAAU,cAAc,CAAC,CAAA;AAChH;;;;"}
@@ -0,0 +1,24 @@
1
+ type FetchPageResult<T> = {
2
+ items: Array<T>;
3
+ hasNextPage: boolean;
4
+ nextPage: number;
5
+ };
6
+ type FetchPage<T> = (params: {
7
+ page: number;
8
+ search?: string;
9
+ }) => Promise<FetchPageResult<T>>;
10
+ type UsePaginatedOptionsParams<T> = {
11
+ fetchPage?: FetchPage<T>;
12
+ debouncedSearchTerm: string;
13
+ open: boolean;
14
+ prefetch?: boolean;
15
+ loading: boolean;
16
+ onItemsLoaded: (items: Array<T>) => void;
17
+ };
18
+ export declare function usePaginatedOptions<T>({ fetchPage, debouncedSearchTerm, open, prefetch, loading, onItemsLoaded, }: UsePaginatedOptionsParams<T>): {
19
+ localOptions: T[];
20
+ hasNextPage: boolean;
21
+ loadingMore: boolean;
22
+ loadNextPage: () => Promise<void>;
23
+ };
24
+ export {};
@@ -0,0 +1,54 @@
1
+ import { useState, useRef, useEffect } from 'react';
2
+
3
+ function usePaginatedOptions({
4
+ fetchPage,
5
+ debouncedSearchTerm,
6
+ open,
7
+ prefetch,
8
+ loading,
9
+ onItemsLoaded
10
+ }) {
11
+ const enabled = !!fetchPage;
12
+ const [page, setPage] = useState(1);
13
+ const [localOptions, setLocalOptions] = useState([]);
14
+ const [hasNextPage, setHasNextPage] = useState(true);
15
+ const [loadingMore, setLoadingMore] = useState(false);
16
+ const loadStateRef = useRef({ token: 0, inFlight: false });
17
+ useEffect(() => {
18
+ if (!enabled) return;
19
+ loadStateRef.current.token++;
20
+ loadStateRef.current.inFlight = false;
21
+ setPage(1);
22
+ setLocalOptions([]);
23
+ setHasNextPage(true);
24
+ setLoadingMore(false);
25
+ }, [debouncedSearchTerm, enabled]);
26
+ const loadNextPage = async () => {
27
+ if (!fetchPage || loadStateRef.current.inFlight || !hasNextPage || loading) return;
28
+ loadStateRef.current.inFlight = true;
29
+ const token = loadStateRef.current.token;
30
+ setLoadingMore(true);
31
+ try {
32
+ const result = await fetchPage({ page, search: debouncedSearchTerm || void 0 });
33
+ if (token !== loadStateRef.current.token) return;
34
+ onItemsLoaded(result.items);
35
+ setLocalOptions((prev) => [...prev, ...result.items]);
36
+ setPage(result.nextPage);
37
+ setHasNextPage(result.hasNextPage);
38
+ } finally {
39
+ if (token === loadStateRef.current.token) {
40
+ loadStateRef.current.inFlight = false;
41
+ setLoadingMore(false);
42
+ }
43
+ }
44
+ };
45
+ useEffect(() => {
46
+ if (enabled && (open || prefetch) && localOptions.length === 0 && !loading && !loadingMore) {
47
+ loadNextPage();
48
+ }
49
+ }, [enabled, open, prefetch, localOptions.length, loading, loadingMore, debouncedSearchTerm]);
50
+ return { localOptions, hasNextPage, loadingMore, loadNextPage };
51
+ }
52
+
53
+ export { usePaginatedOptions };
54
+ //# sourceMappingURL=usePaginatedOptions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"usePaginatedOptions.js","sources":["../../../../src/components/Combobox/hooks/usePaginatedOptions.ts"],"sourcesContent":["import { useEffect, useRef, useState } from 'react'\n\ntype FetchPageResult<T> = { items: Array<T>; hasNextPage: boolean; nextPage: number }\n\ntype FetchPage<T> = (params: { page: number; search?: string }) => Promise<FetchPageResult<T>>\n\ntype UsePaginatedOptionsParams<T> = {\n fetchPage?: FetchPage<T>\n debouncedSearchTerm: string\n open: boolean\n prefetch?: boolean\n loading: boolean\n onItemsLoaded: (items: Array<T>) => void\n}\n\nexport function usePaginatedOptions<T>({\n fetchPage,\n debouncedSearchTerm,\n open,\n prefetch,\n loading,\n onItemsLoaded,\n}: UsePaginatedOptionsParams<T>) {\n const enabled = !!fetchPage\n const [page, setPage] = useState(1)\n const [localOptions, setLocalOptions] = useState<Array<T>>([])\n const [hasNextPage, setHasNextPage] = useState(true)\n const [loadingMore, setLoadingMore] = useState(false)\n const loadStateRef = useRef({ token: 0, inFlight: false })\n\n useEffect(() => {\n if (!enabled) return\n loadStateRef.current.token++\n loadStateRef.current.inFlight = false\n setPage(1)\n setLocalOptions([])\n setHasNextPage(true)\n setLoadingMore(false)\n }, [debouncedSearchTerm, enabled])\n\n const loadNextPage = async () => {\n if (!fetchPage || loadStateRef.current.inFlight || !hasNextPage || loading) return\n\n loadStateRef.current.inFlight = true\n const token = loadStateRef.current.token\n setLoadingMore(true)\n try {\n const result = await fetchPage({ page, search: debouncedSearchTerm || undefined })\n if (token !== loadStateRef.current.token) return\n onItemsLoaded(result.items)\n setLocalOptions((prev) => [...prev, ...result.items])\n setPage(result.nextPage)\n setHasNextPage(result.hasNextPage)\n } finally {\n if (token === loadStateRef.current.token) {\n loadStateRef.current.inFlight = false\n setLoadingMore(false)\n }\n }\n }\n\n useEffect(() => {\n if (enabled && (open || prefetch) && localOptions.length === 0 && !loading && !loadingMore) {\n loadNextPage()\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [enabled, open, prefetch, localOptions.length, loading, loadingMore, debouncedSearchTerm])\n\n return { localOptions, hasNextPage, loadingMore, loadNextPage }\n}\n"],"names":[],"mappings":";;AAeO,SAAS,mBAAA,CAAuB;AAAA,EACrC,SAAA;AAAA,EACA,mBAAA;AAAA,EACA,IAAA;AAAA,EACA,QAAA;AAAA,EACA,OAAA;AAAA,EACA;AACF,CAAA,EAAiC;AAC/B,EAAA,MAAM,OAAA,GAAU,CAAC,CAAC,SAAA;AAClB,EAAA,MAAM,CAAC,IAAA,EAAM,OAAO,CAAA,GAAI,SAAS,CAAC,CAAA;AAClC,EAAA,MAAM,CAAC,YAAA,EAAc,eAAe,CAAA,GAAI,QAAA,CAAmB,EAAE,CAAA;AAC7D,EAAA,MAAM,CAAC,WAAA,EAAa,cAAc,CAAA,GAAI,SAAS,IAAI,CAAA;AACnD,EAAA,MAAM,CAAC,WAAA,EAAa,cAAc,CAAA,GAAI,SAAS,KAAK,CAAA;AACpD,EAAA,MAAM,eAAe,MAAA,CAAO,EAAE,OAAO,CAAA,EAAG,QAAA,EAAU,OAAO,CAAA;AAEzD,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,IAAI,CAAC,OAAA,EAAS;AACd,IAAA,YAAA,CAAa,OAAA,CAAQ,KAAA,EAAA;AACrB,IAAA,YAAA,CAAa,QAAQ,QAAA,GAAW,KAAA;AAChC,IAAA,OAAA,CAAQ,CAAC,CAAA;AACT,IAAA,eAAA,CAAgB,EAAE,CAAA;AAClB,IAAA,cAAA,CAAe,IAAI,CAAA;AACnB,IAAA,cAAA,CAAe,KAAK,CAAA;AAAA,EACtB,CAAA,EAAG,CAAC,mBAAA,EAAqB,OAAO,CAAC,CAAA;AAEjC,EAAA,MAAM,eAAe,YAAY;AAC/B,IAAA,IAAI,CAAC,SAAA,IAAa,YAAA,CAAa,QAAQ,QAAA,IAAY,CAAC,eAAe,OAAA,EAAS;AAE5E,IAAA,YAAA,CAAa,QAAQ,QAAA,GAAW,IAAA;AAChC,IAAA,MAAM,KAAA,GAAQ,aAAa,OAAA,CAAQ,KAAA;AACnC,IAAA,cAAA,CAAe,IAAI,CAAA;AACnB,IAAA,IAAI;AACF,MAAA,MAAM,MAAA,GAAS,MAAM,SAAA,CAAU,EAAE,MAAM,MAAA,EAAQ,mBAAA,IAAuB,QAAW,CAAA;AACjF,MAAA,IAAI,KAAA,KAAU,YAAA,CAAa,OAAA,CAAQ,KAAA,EAAO;AAC1C,MAAA,aAAA,CAAc,OAAO,KAAK,CAAA;AAC1B,MAAA,eAAA,CAAgB,CAAC,SAAS,CAAC,GAAG,MAAM,GAAG,MAAA,CAAO,KAAK,CAAC,CAAA;AACpD,MAAA,OAAA,CAAQ,OAAO,QAAQ,CAAA;AACvB,MAAA,cAAA,CAAe,OAAO,WAAW,CAAA;AAAA,IACnC,CAAA,SAAE;AACA,MAAA,IAAI,KAAA,KAAU,YAAA,CAAa,OAAA,CAAQ,KAAA,EAAO;AACxC,QAAA,YAAA,CAAa,QAAQ,QAAA,GAAW,KAAA;AAChC,QAAA,cAAA,CAAe,KAAK,CAAA;AAAA,MACtB;AAAA,IACF;AAAA,EACF,CAAA;AAEA,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,IAAI,OAAA,KAAY,QAAQ,QAAA,CAAA,IAAa,YAAA,CAAa,WAAW,CAAA,IAAK,CAAC,OAAA,IAAW,CAAC,WAAA,EAAa;AAC1F,MAAA,YAAA,EAAa;AAAA,IACf;AAAA,EAEF,CAAA,EAAG,CAAC,OAAA,EAAS,IAAA,EAAM,QAAA,EAAU,aAAa,MAAA,EAAQ,OAAA,EAAS,WAAA,EAAa,mBAAmB,CAAC,CAAA;AAE5F,EAAA,OAAO,EAAE,YAAA,EAAc,WAAA,EAAa,WAAA,EAAa,YAAA,EAAa;AAChE;;;;"}
@@ -0,0 +1,4 @@
1
+ export declare function useSelectedCache<T>(getOptionValue: (option: T) => string): {
2
+ record: (items: Iterable<T>) => void;
3
+ resolve: (value: string) => T | undefined;
4
+ };
@@ -0,0 +1,17 @@
1
+ import { useRef, useCallback } from 'react';
2
+
3
+ function useSelectedCache(getOptionValue) {
4
+ const cacheRef = useRef(/* @__PURE__ */ new Map());
5
+ const getValueRef = useRef(getOptionValue);
6
+ getValueRef.current = getOptionValue;
7
+ const record = useCallback((items) => {
8
+ for (const item of items) {
9
+ cacheRef.current.set(getValueRef.current(item), item);
10
+ }
11
+ }, []);
12
+ const resolve = useCallback((value) => cacheRef.current.get(value), []);
13
+ return { record, resolve };
14
+ }
15
+
16
+ export { useSelectedCache };
17
+ //# sourceMappingURL=useSelectedCache.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useSelectedCache.js","sources":["../../../../src/components/Combobox/hooks/useSelectedCache.ts"],"sourcesContent":["import { useCallback, useRef } from 'react'\n\nexport function useSelectedCache<T>(getOptionValue: (option: T) => string) {\n const cacheRef = useRef<Map<string, T>>(new Map())\n const getValueRef = useRef(getOptionValue)\n getValueRef.current = getOptionValue\n\n const record = useCallback((items: Iterable<T>) => {\n for (const item of items) {\n cacheRef.current.set(getValueRef.current(item), item)\n }\n }, [])\n\n const resolve = useCallback((value: string) => cacheRef.current.get(value), [])\n\n return { record, resolve }\n}\n"],"names":[],"mappings":";;AAEO,SAAS,iBAAoB,cAAA,EAAuC;AACzE,EAAA,MAAM,QAAA,GAAW,MAAA,iBAAuB,IAAI,GAAA,EAAK,CAAA;AACjD,EAAA,MAAM,WAAA,GAAc,OAAO,cAAc,CAAA;AACzC,EAAA,WAAA,CAAY,OAAA,GAAU,cAAA;AAEtB,EAAA,MAAM,MAAA,GAAS,WAAA,CAAY,CAAC,KAAA,KAAuB;AACjD,IAAA,KAAA,MAAW,QAAQ,KAAA,EAAO;AACxB,MAAA,QAAA,CAAS,QAAQ,GAAA,CAAI,WAAA,CAAY,OAAA,CAAQ,IAAI,GAAG,IAAI,CAAA;AAAA,IACtD;AAAA,EACF,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,MAAM,OAAA,GAAU,WAAA,CAAY,CAAC,KAAA,KAAkB,QAAA,CAAS,QAAQ,GAAA,CAAI,KAAK,CAAA,EAAG,EAAE,CAAA;AAE9E,EAAA,OAAO,EAAE,QAAQ,OAAA,EAAQ;AAC3B;;;;"}
@@ -14,7 +14,6 @@ export type VirtualizedComboboxListProps<T> = ComboboxListBaseProps<T> & {
14
14
  hasNextPage: boolean;
15
15
  loadingMore: boolean;
16
16
  onLoadMore: () => void;
17
- open: boolean;
18
17
  };
19
18
  export type StaticComboboxListProps<T> = ComboboxListBaseProps<T> & {
20
19
  filteredOptions: Array<T>;
@@ -1,9 +1,9 @@
1
1
  import { ComboboxProps } from './Combobox';
2
2
  export declare const useCombobox: <T extends object>(props: ComboboxProps<T>) => {
3
3
  open: boolean;
4
- setOpen: import('react').Dispatch<import('react').SetStateAction<boolean>>;
4
+ setOpen: (next: boolean) => void;
5
5
  searchTerm: string;
6
- setSearchTerm: import('react').Dispatch<import('react').SetStateAction<string>>;
6
+ setSearchTerm: (next: string) => void;
7
7
  isHovered: boolean;
8
8
  setIsHovered: import('react').Dispatch<import('react').SetStateAction<boolean>>;
9
9
  localOptions: T[];
@@ -12,7 +12,7 @@ export declare const useCombobox: <T extends object>(props: ComboboxProps<T>) =>
12
12
  isVirtualized: boolean;
13
13
  filteredOptions: T[];
14
14
  displayValue: import('react').ReactNode;
15
- hasValue: boolean | undefined;
15
+ hasValue: boolean;
16
16
  showClearButton: boolean | undefined;
17
17
  handleSelect: (currentValue: string) => void;
18
18
  handleRemove: (valueToRemove: string) => void;
@@ -1,174 +1,42 @@
1
- import { useState, useRef, useEffect, useCallback, useMemo } from 'react';
1
+ import { useMemo, useState } from 'react';
2
+ import { useComboboxOpenState } from './hooks/useComboboxOpenState.js';
3
+ import { useComboboxSelection } from './hooks/useComboboxSelection.js';
4
+ import { useDisplayValue } from './hooks/useDisplayValue.js';
5
+ import { usePaginatedOptions } from './hooks/usePaginatedOptions.js';
6
+ import { useSelectedCache } from './hooks/useSelectedCache.js';
2
7
 
3
8
  const useCombobox = (props) => {
4
- const {
5
- options,
6
- getOptionValue,
7
- getOptionLabel,
8
- placeholder = "Select...",
9
- loading = false,
10
- filterOptions,
11
- multiple,
12
- clearable = true,
13
- onClear,
14
- fetchPage,
15
- specialOptions,
16
- prefetch
17
- } = props;
18
- const [open, setOpen] = useState(false);
19
- const [searchTerm, setSearchTerm] = useState("");
20
- const [debouncedSearchTerm, setDebouncedSearchTerm] = useState("");
21
- const [isHovered, setIsHovered] = useState(false);
22
- const [page, setPage] = useState(1);
23
- const [localOptions, setLocalOptions] = useState(options);
24
- const [hasNextPage, setHasNextPage] = useState(true);
25
- const [loadingMore, setLoadingMore] = useState(false);
26
- const debounceTimerRef = useRef(void 0);
27
- const closeOnSelect = props.closeOnSelect ?? !props.multiple;
9
+ const { options, getOptionValue, getOptionLabel, loading = false, filterOptions, fetchPage, onClear, clearable = true } = props;
10
+ const { open, setOpen, searchTerm, debouncedSearchTerm, setSearchTerm } = useComboboxOpenState();
28
11
  const isVirtualized = !!fetchPage;
29
- useEffect(() => {
30
- if (!isVirtualized) return;
31
- clearTimeout(debounceTimerRef.current);
32
- debounceTimerRef.current = setTimeout(() => {
33
- setDebouncedSearchTerm(searchTerm);
34
- }, 300);
35
- return () => {
36
- clearTimeout(debounceTimerRef.current);
37
- };
38
- }, [searchTerm, isVirtualized]);
39
- useEffect(() => {
40
- if (isVirtualized) {
41
- setPage(1);
42
- setLocalOptions([]);
43
- setHasNextPage(true);
44
- setLoadingMore(false);
45
- }
46
- }, [debouncedSearchTerm, isVirtualized]);
47
- const loadNextPage = useCallback(async () => {
48
- if (!fetchPage || loadingMore || !hasNextPage || loading) return;
49
- setLoadingMore(true);
50
- try {
51
- const result = await fetchPage({ page, search: debouncedSearchTerm || void 0 });
52
- setLocalOptions((prev) => [...prev, ...result.items]);
53
- setPage(result.nextPage);
54
- setHasNextPage(result.hasNextPage);
55
- } finally {
56
- setLoadingMore(false);
57
- }
58
- }, [fetchPage, loadingMore, hasNextPage, loading, page, debouncedSearchTerm]);
59
- useEffect(() => {
60
- if (isVirtualized && (open || prefetch) && localOptions.length === 0 && !loading && !loadingMore) {
61
- loadNextPage();
62
- }
63
- }, [isVirtualized, open, prefetch, localOptions.length, loading, loadingMore, loadNextPage]);
12
+ const cache = useSelectedCache(getOptionValue);
13
+ const { localOptions, hasNextPage, loadingMore, loadNextPage } = usePaginatedOptions({
14
+ fetchPage,
15
+ debouncedSearchTerm,
16
+ open,
17
+ prefetch: props.prefetch,
18
+ loading,
19
+ onItemsLoaded: cache.record
20
+ });
64
21
  const filteredOptions = useMemo(() => {
65
- if (isVirtualized) {
66
- return localOptions;
67
- }
22
+ if (isVirtualized) return localOptions;
68
23
  if (!filterOptions) {
69
24
  return options.filter((option) => getOptionLabel(option).toLowerCase().includes(searchTerm.toLowerCase()));
70
25
  }
71
26
  return searchTerm ? filterOptions(options, searchTerm) : options;
72
27
  }, [isVirtualized, localOptions, filterOptions, options, searchTerm, getOptionLabel]);
73
- const handleSelect = useCallback(
74
- (currentValue) => {
75
- if (multiple) {
76
- const { value = [], onChange } = props;
77
- const isRemoving = value.includes(currentValue);
78
- if (specialOptions && specialOptions.length > 0) {
79
- if (isRemoving) {
80
- onChange([]);
81
- } else {
82
- onChange([currentValue]);
83
- }
84
- } else {
85
- if (isRemoving && !clearable && value.length === 1) {
86
- return;
87
- }
88
- const newValues = isRemoving ? value.filter((val) => val !== currentValue) : [...value, currentValue];
89
- onChange(newValues);
90
- }
91
- } else {
92
- const { value, onChange } = props;
93
- const newValue = clearable && currentValue === value ? "" : currentValue;
94
- onChange(newValue);
95
- }
96
- if (closeOnSelect) {
97
- setOpen(false);
98
- }
99
- },
100
- [multiple, clearable, closeOnSelect, props, specialOptions]
101
- );
102
- const handleRemove = useCallback(
103
- (valueToRemove) => {
104
- if (props.multiple) {
105
- const { value = [], onChange } = props;
106
- if (!clearable && value.length === 1) {
107
- return;
108
- }
109
- const newValues = value.filter((val) => val !== valueToRemove);
110
- onChange(newValues);
111
- }
112
- },
113
- [props, clearable]
114
- );
115
- const handleClear = useCallback(
116
- (event) => {
117
- event.preventDefault();
118
- event.stopPropagation();
119
- if (multiple) {
120
- const { onChange } = props;
121
- if (clearable) {
122
- onChange([]);
123
- }
124
- } else {
125
- const { onChange } = props;
126
- onChange("");
127
- }
128
- if (typeof onClear === "function") {
129
- onClear();
130
- }
131
- },
132
- [multiple, clearable, onClear, props]
133
- );
134
- const displayValue = useMemo(() => {
135
- const availableOptions = isVirtualized ? localOptions : options;
136
- const allAvailableOptions = specialOptions ? [...specialOptions, ...availableOptions] : availableOptions;
137
- if (multiple) {
138
- const { value: value2 = [], renderLabel: renderLabel2 } = props;
139
- if (value2.length === 0) return placeholder;
140
- const selectedOptions = allAvailableOptions.filter((option) => value2.includes(getOptionValue(option)));
141
- if (renderLabel2) {
142
- return renderLabel2(selectedOptions, handleRemove);
143
- }
144
- return selectedOptions.map(getOptionLabel).join(", ");
145
- }
146
- const { value, renderLabel } = props;
147
- const selectedOption = allAvailableOptions.find((option) => getOptionValue(option) === value);
148
- if (renderLabel && selectedOption) {
149
- return renderLabel(selectedOption);
150
- }
151
- return selectedOption ? getOptionLabel(selectedOption) : placeholder;
152
- }, [
28
+ const { handleSelect, handleRemove, handleClear, hasValue } = useComboboxSelection(props, setOpen);
29
+ const [isHovered, setIsHovered] = useState(false);
30
+ const displayValue = useDisplayValue({
153
31
  props,
154
- getOptionLabel,
155
- getOptionValue,
156
- options,
157
32
  localOptions,
158
33
  isVirtualized,
159
- placeholder,
160
- multiple,
161
- specialOptions
162
- ]);
163
- const hasValue = useMemo(() => {
164
- if (multiple) {
165
- return props.value && props.value.length > 0;
166
- }
167
- return !!props.value;
168
- }, [props.value, multiple]);
34
+ resolve: cache.resolve,
35
+ recordOptions: cache.record,
36
+ handleRemove
37
+ });
169
38
  const showClearButton = onClear && hasValue && isHovered && clearable;
170
39
  return {
171
- // State
172
40
  open,
173
41
  setOpen,
174
42
  searchTerm,
@@ -178,13 +46,11 @@ const useCombobox = (props) => {
178
46
  localOptions,
179
47
  hasNextPage,
180
48
  loadingMore,
181
- // Computed values
182
49
  isVirtualized,
183
50
  filteredOptions,
184
51
  displayValue,
185
52
  hasValue,
186
53
  showClearButton,
187
- // Handlers
188
54
  handleSelect,
189
55
  handleRemove,
190
56
  handleClear,
@@ -1 +1 @@
1
- {"version":3,"file":"useCombobox.js","sources":["../../../src/components/Combobox/useCombobox.ts"],"sourcesContent":["import { useCallback, useEffect, useMemo, useRef, useState } from 'react'\n\nimport type { ComboboxProps } from './Combobox'\n\nexport const useCombobox = <T extends object>(props: ComboboxProps<T>) => {\n const {\n options,\n getOptionValue,\n getOptionLabel,\n placeholder = 'Select...',\n loading = false,\n filterOptions,\n multiple,\n clearable = true,\n onClear,\n fetchPage,\n specialOptions,\n prefetch,\n } = props\n\n const [open, setOpen] = useState(false)\n const [searchTerm, setSearchTerm] = useState('')\n const [debouncedSearchTerm, setDebouncedSearchTerm] = useState('')\n const [isHovered, setIsHovered] = useState(false)\n\n const [page, setPage] = useState(1)\n const [localOptions, setLocalOptions] = useState<Array<T>>(options)\n const [hasNextPage, setHasNextPage] = useState(true)\n const [loadingMore, setLoadingMore] = useState(false)\n\n const debounceTimerRef = useRef<NodeJS.Timeout | undefined>(undefined)\n\n const closeOnSelect = props.closeOnSelect ?? !props.multiple\n const isVirtualized = !!fetchPage\n\n useEffect(() => {\n if (!isVirtualized) return\n\n clearTimeout(debounceTimerRef.current)\n\n debounceTimerRef.current = setTimeout(() => {\n setDebouncedSearchTerm(searchTerm)\n }, 300)\n\n return () => {\n clearTimeout(debounceTimerRef.current)\n }\n }, [searchTerm, isVirtualized])\n\n useEffect(() => {\n if (isVirtualized) {\n setPage(1)\n setLocalOptions([])\n setHasNextPage(true)\n setLoadingMore(false)\n }\n }, [debouncedSearchTerm, isVirtualized])\n\n const loadNextPage = useCallback(async () => {\n if (!fetchPage || loadingMore || !hasNextPage || loading) return\n\n setLoadingMore(true)\n try {\n const result = await fetchPage({ page, search: debouncedSearchTerm || undefined })\n setLocalOptions((prev) => [...prev, ...result.items])\n setPage(result.nextPage)\n setHasNextPage(result.hasNextPage)\n } finally {\n setLoadingMore(false)\n }\n }, [fetchPage, loadingMore, hasNextPage, loading, page, debouncedSearchTerm])\n\n // Load initial page when combobox opens (or on mount when prefetch enabled)\n useEffect(() => {\n if (isVirtualized && (open || prefetch) && localOptions.length === 0 && !loading && !loadingMore) {\n loadNextPage()\n }\n }, [isVirtualized, open, prefetch, localOptions.length, loading, loadingMore, loadNextPage])\n\n const filteredOptions = useMemo(() => {\n if (isVirtualized) {\n return localOptions\n }\n\n if (!filterOptions) {\n return options.filter((option) => getOptionLabel(option).toLowerCase().includes(searchTerm.toLowerCase()))\n }\n\n return searchTerm ? filterOptions(options, searchTerm) : options\n }, [isVirtualized, localOptions, filterOptions, options, searchTerm, getOptionLabel])\n\n const handleSelect = useCallback(\n (currentValue: string) => {\n if (multiple) {\n const { value = [], onChange } = props\n const isRemoving = value.includes(currentValue)\n\n if (specialOptions && specialOptions.length > 0) {\n if (isRemoving) {\n onChange([])\n } else {\n onChange([currentValue])\n }\n } else {\n if (isRemoving && !clearable && value.length === 1) {\n return\n }\n\n const newValues = isRemoving ? value.filter((val) => val !== currentValue) : [...value, currentValue]\n onChange(newValues)\n }\n } else {\n const { value, onChange } = props\n const newValue = clearable && currentValue === value ? '' : currentValue\n onChange(newValue)\n }\n\n if (closeOnSelect) {\n setOpen(false)\n }\n },\n [multiple, clearable, closeOnSelect, props, specialOptions],\n )\n\n const handleRemove = useCallback(\n (valueToRemove: string) => {\n if (props.multiple) {\n const { value = [], onChange } = props\n\n if (!clearable && value.length === 1) {\n return\n }\n\n const newValues = value.filter((val) => val !== valueToRemove)\n onChange(newValues)\n }\n },\n [props, clearable],\n )\n\n const handleClear = useCallback(\n (event: React.MouseEvent) => {\n event.preventDefault()\n event.stopPropagation()\n\n if (multiple) {\n const { onChange } = props\n\n if (clearable) {\n onChange([])\n }\n } else {\n const { onChange } = props\n onChange('')\n }\n\n if (typeof onClear === 'function') {\n onClear()\n }\n },\n [multiple, clearable, onClear, props],\n )\n\n const displayValue = useMemo(() => {\n const availableOptions = isVirtualized ? localOptions : options\n const allAvailableOptions = specialOptions ? [...specialOptions, ...availableOptions] : availableOptions\n\n if (multiple) {\n const { value = [], renderLabel } = props\n if (value.length === 0) return placeholder\n\n const selectedOptions = allAvailableOptions.filter((option) => value.includes(getOptionValue(option)))\n\n if (renderLabel) {\n return renderLabel(selectedOptions, handleRemove)\n }\n\n return selectedOptions.map(getOptionLabel).join(', ')\n }\n\n const { value, renderLabel } = props\n const selectedOption = allAvailableOptions.find((option) => getOptionValue(option) === value)\n if (renderLabel && selectedOption) {\n return renderLabel(selectedOption)\n }\n return selectedOption ? getOptionLabel(selectedOption) : placeholder\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [\n props,\n getOptionLabel,\n getOptionValue,\n options,\n localOptions,\n isVirtualized,\n placeholder,\n multiple,\n specialOptions,\n ])\n\n const hasValue = useMemo(() => {\n if (multiple) {\n return props.value && props.value.length > 0\n }\n return !!props.value\n }, [props.value, multiple])\n\n const showClearButton = onClear && hasValue && isHovered && clearable\n\n return {\n // State\n open,\n setOpen,\n searchTerm,\n setSearchTerm,\n isHovered,\n setIsHovered,\n localOptions,\n hasNextPage,\n loadingMore,\n\n // Computed values\n isVirtualized,\n filteredOptions,\n displayValue,\n hasValue,\n showClearButton,\n\n // Handlers\n handleSelect,\n handleRemove,\n handleClear,\n loadNextPage,\n }\n}\n"],"names":["value","renderLabel"],"mappings":"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"}
1
+ {"version":3,"file":"useCombobox.js","sources":["../../../src/components/Combobox/useCombobox.ts"],"sourcesContent":["import { useMemo, useState } from 'react'\n\nimport type { ComboboxProps } from './Combobox'\nimport {\n useComboboxOpenState,\n useComboboxSelection,\n useDisplayValue,\n usePaginatedOptions,\n useSelectedCache,\n} from './hooks'\n\nexport const useCombobox = <T extends object>(props: ComboboxProps<T>) => {\n const { options, getOptionValue, getOptionLabel, loading = false, filterOptions, fetchPage, onClear, clearable = true } = props\n\n const { open, setOpen, searchTerm, debouncedSearchTerm, setSearchTerm } = useComboboxOpenState()\n const isVirtualized = !!fetchPage\n\n const cache = useSelectedCache<T>(getOptionValue)\n\n const { localOptions, hasNextPage, loadingMore, loadNextPage } = usePaginatedOptions<T>({\n fetchPage,\n debouncedSearchTerm,\n open,\n prefetch: props.prefetch,\n loading,\n onItemsLoaded: cache.record,\n })\n\n const filteredOptions = useMemo(() => {\n if (isVirtualized) return localOptions\n\n if (!filterOptions) {\n return options.filter((option) => getOptionLabel(option).toLowerCase().includes(searchTerm.toLowerCase()))\n }\n\n return searchTerm ? filterOptions(options, searchTerm) : options\n }, [isVirtualized, localOptions, filterOptions, options, searchTerm, getOptionLabel])\n\n const { handleSelect, handleRemove, handleClear, hasValue } = useComboboxSelection(props, setOpen)\n\n const [isHovered, setIsHovered] = useState(false)\n\n const displayValue = useDisplayValue<T>({\n props,\n localOptions,\n isVirtualized,\n resolve: cache.resolve,\n recordOptions: cache.record,\n handleRemove,\n })\n\n const showClearButton = onClear && hasValue && isHovered && clearable\n\n return {\n open,\n setOpen,\n searchTerm,\n setSearchTerm,\n isHovered,\n setIsHovered,\n localOptions,\n hasNextPage,\n loadingMore,\n isVirtualized,\n filteredOptions,\n displayValue,\n hasValue,\n showClearButton,\n handleSelect,\n handleRemove,\n handleClear,\n loadNextPage,\n }\n}\n"],"names":[],"mappings":";;;;;;;AAWO,MAAM,WAAA,GAAc,CAAmB,KAAA,KAA4B;AACxE,EAAA,MAAM,EAAE,OAAA,EAAS,cAAA,EAAgB,cAAA,EAAgB,OAAA,GAAU,KAAA,EAAO,aAAA,EAAe,SAAA,EAAW,OAAA,EAAS,SAAA,GAAY,IAAA,EAAK,GAAI,KAAA;AAE1H,EAAA,MAAM,EAAE,IAAA,EAAM,OAAA,EAAS,YAAY,mBAAA,EAAqB,aAAA,KAAkB,oBAAA,EAAqB;AAC/F,EAAA,MAAM,aAAA,GAAgB,CAAC,CAAC,SAAA;AAExB,EAAA,MAAM,KAAA,GAAQ,iBAAoB,cAAc,CAAA;AAEhD,EAAA,MAAM,EAAE,YAAA,EAAc,WAAA,EAAa,WAAA,EAAa,YAAA,KAAiB,mBAAA,CAAuB;AAAA,IACtF,SAAA;AAAA,IACA,mBAAA;AAAA,IACA,IAAA;AAAA,IACA,UAAU,KAAA,CAAM,QAAA;AAAA,IAChB,OAAA;AAAA,IACA,eAAe,KAAA,CAAM;AAAA,GACtB,CAAA;AAED,EAAA,MAAM,eAAA,GAAkB,QAAQ,MAAM;AACpC,IAAA,IAAI,eAAe,OAAO,YAAA;AAE1B,IAAA,IAAI,CAAC,aAAA,EAAe;AAClB,MAAA,OAAO,OAAA,CAAQ,MAAA,CAAO,CAAC,MAAA,KAAW,cAAA,CAAe,MAAM,CAAA,CAAE,WAAA,EAAY,CAAE,QAAA,CAAS,UAAA,CAAW,WAAA,EAAa,CAAC,CAAA;AAAA,IAC3G;AAEA,IAAA,OAAO,UAAA,GAAa,aAAA,CAAc,OAAA,EAAS,UAAU,CAAA,GAAI,OAAA;AAAA,EAC3D,CAAA,EAAG,CAAC,aAAA,EAAe,YAAA,EAAc,eAAe,OAAA,EAAS,UAAA,EAAY,cAAc,CAAC,CAAA;AAEpF,EAAA,MAAM,EAAE,cAAc,YAAA,EAAc,WAAA,EAAa,UAAS,GAAI,oBAAA,CAAqB,OAAO,OAAO,CAAA;AAEjG,EAAA,MAAM,CAAC,SAAA,EAAW,YAAY,CAAA,GAAI,SAAS,KAAK,CAAA;AAEhD,EAAA,MAAM,eAAe,eAAA,CAAmB;AAAA,IACtC,KAAA;AAAA,IACA,YAAA;AAAA,IACA,aAAA;AAAA,IACA,SAAS,KAAA,CAAM,OAAA;AAAA,IACf,eAAe,KAAA,CAAM,MAAA;AAAA,IACrB;AAAA,GACD,CAAA;AAED,EAAA,MAAM,eAAA,GAAkB,OAAA,IAAW,QAAA,IAAY,SAAA,IAAa,SAAA;AAE5D,EAAA,OAAO;AAAA,IACL,IAAA;AAAA,IACA,OAAA;AAAA,IACA,UAAA;AAAA,IACA,aAAA;AAAA,IACA,SAAA;AAAA,IACA,YAAA;AAAA,IACA,YAAA;AAAA,IACA,WAAA;AAAA,IACA,WAAA;AAAA,IACA,aAAA;AAAA,IACA,eAAA;AAAA,IACA,YAAA;AAAA,IACA,QAAA;AAAA,IACA,eAAA;AAAA,IACA,YAAA;AAAA,IACA,YAAA;AAAA,IACA,WAAA;AAAA,IACA;AAAA,GACF;AACF;;;;"}
@@ -15,8 +15,14 @@ declare const TooltipRoot: React.FC<TooltipPrimitive.TooltipProps>;
15
15
  * Wrap with asChild to use custom components as triggers.
16
16
  */
17
17
  declare const TooltipTrigger: React.ForwardRefExoticComponent<TooltipPrimitive.TooltipTriggerProps & React.RefAttributes<HTMLButtonElement>>;
18
+ declare const TooltipPortal: React.FC<TooltipPrimitive.TooltipPortalProps>;
18
19
  declare const TooltipContent: React.ForwardRefExoticComponent<Omit<TooltipPrimitive.TooltipContentProps & React.RefAttributes<HTMLDivElement>, "ref"> & {
19
20
  arrow?: boolean;
21
+ /**
22
+ * Container for the portal. When provided, the tooltip is portaled into this element instead of `document.body`.
23
+ * Pass `null` to disable portaling and render inline (legacy behavior).
24
+ */
25
+ container?: React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Portal>["container"] | null;
20
26
  } & React.RefAttributes<HTMLDivElement>>;
21
27
  export type TooltipProps = {
22
28
  /**
@@ -99,4 +105,4 @@ export declare function Tooltip({ triggerElement, children, side, className, del
99
105
  * </TooltipRoot>
100
106
  * </TooltipProvider>
101
107
  */
102
- export { TooltipContent, TooltipProvider, TooltipRoot, TooltipTrigger };
108
+ export { TooltipContent, TooltipPortal, TooltipProvider, TooltipRoot, TooltipTrigger };
@@ -8,29 +8,36 @@ import { cn } from '../../lib/utils.js';
8
8
  const TooltipProvider = TooltipPrimitive.Provider;
9
9
  const TooltipRoot = TooltipPrimitive.Root;
10
10
  const TooltipTrigger = TooltipPrimitive.Trigger;
11
- const TooltipContent = React.forwardRef(({ className, sideOffset = 4, arrow, children, ...props }, ref) => /* @__PURE__ */ jsxs(
12
- TooltipPrimitive.Content,
13
- {
14
- ref,
15
- sideOffset,
16
- className: cn(
17
- "z-50 rounded-lg bg-white px-3 py-1.5 text-sm",
18
- "shadow-tooltip",
19
- "animate-in fade-in-0 zoom-in-95",
20
- "data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95",
21
- "data-[side=bottom]:slide-in-from-top-2",
22
- "data-[side=left]:slide-in-from-right-2",
23
- "data-[side=right]:slide-in-from-left-2",
24
- "data-[side=top]:slide-in-from-bottom-2",
25
- className
26
- ),
27
- ...props,
28
- children: [
29
- children,
30
- arrow && /* @__PURE__ */ jsx(TooltipPrimitive.Arrow, { fill: "white" })
31
- ]
11
+ const TooltipPortal = TooltipPrimitive.Portal;
12
+ const TooltipContent = React.forwardRef(
13
+ ({ className, sideOffset = 4, arrow, container, children, ...props }, ref) => {
14
+ const content = /* @__PURE__ */ jsxs(
15
+ TooltipPrimitive.Content,
16
+ {
17
+ ref,
18
+ sideOffset,
19
+ className: cn(
20
+ "z-50 rounded-lg bg-white px-3 py-1.5 text-sm",
21
+ "shadow-tooltip",
22
+ "animate-in fade-in-0 zoom-in-95",
23
+ "data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95",
24
+ "data-[side=bottom]:slide-in-from-top-2",
25
+ "data-[side=left]:slide-in-from-right-2",
26
+ "data-[side=right]:slide-in-from-left-2",
27
+ "data-[side=top]:slide-in-from-bottom-2",
28
+ className
29
+ ),
30
+ ...props,
31
+ children: [
32
+ children,
33
+ arrow && /* @__PURE__ */ jsx(TooltipPrimitive.Arrow, { fill: "white" })
34
+ ]
35
+ }
36
+ );
37
+ if (container === null) return content;
38
+ return /* @__PURE__ */ jsx(TooltipPrimitive.Portal, { container: container ?? void 0, children: content });
32
39
  }
33
- ));
40
+ );
34
41
  TooltipContent.displayName = TooltipPrimitive.Content.displayName;
35
42
  function Tooltip({
36
43
  triggerElement,
@@ -60,9 +67,9 @@ function Tooltip({
60
67
  }
61
68
  ) : /* @__PURE__ */ jsx(TooltipProvider, { children: /* @__PURE__ */ jsxs(TooltipRoot, { delayDuration, children: [
62
69
  /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: triggerElement }),
63
- /* @__PURE__ */ jsx(TooltipContent, { side, className, arrow, children })
70
+ /* @__PURE__ */ jsx(TooltipContent, { side, className, arrow, container, children })
64
71
  ] }) });
65
72
  }
66
73
 
67
- export { Tooltip, TooltipContent, TooltipProvider, TooltipRoot, TooltipTrigger };
74
+ export { Tooltip, TooltipContent, TooltipPortal, TooltipProvider, TooltipRoot, TooltipTrigger };
68
75
  //# sourceMappingURL=Tooltip.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"Tooltip.js","sources":["../../../src/components/Tooltip/Tooltip.tsx"],"sourcesContent":["import * as TooltipPrimitive from '@radix-ui/react-tooltip'\nimport * as React from 'react'\n\nimport { Popover } from '../Popover/Popover'\n\nimport { useIsMobile } from '@/lib/useMobile'\nimport { cn } from '@/lib/utils'\n\n/**\n * Provider component that wraps all tooltip instances.\n * Controls the delay duration and positioning of tooltips app-wide.\n */\nconst TooltipProvider = TooltipPrimitive.Provider\n\n/**\n * The root component that wraps the trigger and content.\n * Manages the open state and hover interactions.\n */\nconst TooltipRoot = TooltipPrimitive.Root\n\n/**\n * The element that triggers the tooltip when hovered or focused.\n * Wrap with asChild to use custom components as triggers.\n */\nconst TooltipTrigger = TooltipPrimitive.Trigger\n\nconst TooltipContent = React.forwardRef<\n React.ComponentRef<typeof TooltipPrimitive.Content>,\n React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content> & { arrow?: boolean }\n>(({ className, sideOffset = 4, arrow, children, ...props }, ref) => (\n <TooltipPrimitive.Content\n ref={ref}\n sideOffset={sideOffset}\n className={cn(\n 'z-50 rounded-lg bg-white px-3 py-1.5 text-sm',\n 'shadow-tooltip',\n 'animate-in fade-in-0 zoom-in-95',\n 'data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95',\n 'data-[side=bottom]:slide-in-from-top-2',\n 'data-[side=left]:slide-in-from-right-2',\n 'data-[side=right]:slide-in-from-left-2',\n 'data-[side=top]:slide-in-from-bottom-2',\n className,\n )}\n {...props}\n >\n {children}\n {arrow && <TooltipPrimitive.Arrow fill=\"white\" />}\n </TooltipPrimitive.Content>\n))\nTooltipContent.displayName = TooltipPrimitive.Content.displayName\n\nexport type TooltipProps = {\n /**\n * The content to be displayed inside the tooltip\n */\n children: React.ReactElement | string\n\n /**\n * The element that triggers the tooltip on hover\n */\n triggerElement: React.ReactElement | string\n\n /**\n * The preferred side of the trigger to render the tooltip\n */\n side?: 'top' | 'right' | 'bottom' | 'left'\n\n /**\n * Optional custom className for additional styling\n */\n className?: string\n\n /**\n * Delay duration for showing the tooltip in milliseconds\n * @default 700\n */\n delayDuration?: number\n\n /**\n * Whether to show an arrow pointer on the tooltip\n * @default false\n */\n arrow?: boolean\n\n /**\n * The preferred side of the trigger to render the mobile\n */\n mobileSide?: 'top' | 'right' | 'bottom' | 'left'\n\n /**\n * The preferred alignment of the mobile\n */\n mobileAlign?: 'start' | 'center' | 'end'\n\n /**\n * The preferred side offset of the mobile\n */\n mobileSideOffset?: number\n\n /**\n * The container to position the mobile\n */\n container?: HTMLElement\n}\n\n/**\n * A versatile tooltip component that shows additional information on hover.\n * Built on top of Radix UI's tooltip primitives with custom styling and animations.\n *\n * @example\n * // Basic usage\n * <Tooltip triggerElement={<button>Hover me</button>} side=\"top\">\n * Helpful information\n * </Tooltip>\n *\n * @example\n * // With custom delay and className\n * <Tooltip\n * triggerElement={<Icon />}\n * side=\"right\"\n * delayDuration={300}\n * className=\"custom-tooltip\"\n * >\n * Icon description\n * </Tooltip>\n */\nexport function Tooltip({\n triggerElement,\n children,\n side = 'top',\n className,\n delayDuration = 700,\n arrow = false,\n mobileSide,\n mobileAlign,\n mobileSideOffset,\n container,\n ...props\n}: Readonly<TooltipProps>) {\n const isMobile = useIsMobile()\n\n return isMobile ? (\n <Popover\n side={mobileSide}\n className={className}\n align={mobileAlign}\n sideOffset={mobileSideOffset}\n triggerElement={triggerElement}\n container={container}\n {...props}\n >\n {children}\n </Popover>\n ) : (\n <TooltipProvider>\n <TooltipRoot delayDuration={delayDuration}>\n <TooltipTrigger asChild>{triggerElement}</TooltipTrigger>\n <TooltipContent side={side} className={className} arrow={arrow}>\n {children}\n </TooltipContent>\n </TooltipRoot>\n </TooltipProvider>\n )\n}\n\n/**\n * Export primitive components for custom tooltip implementations.\n * Use these when you need more control over the tooltip behavior and styling.\n *\n * @example\n * // Custom implementation using primitives\n * <TooltipProvider>\n * <TooltipRoot>\n * <TooltipTrigger asChild>\n * <button>Hover</button>\n * </TooltipTrigger>\n * <TooltipContent>Custom tooltip</TooltipContent>\n * </TooltipRoot>\n * </TooltipProvider>\n */\nexport { TooltipContent, TooltipProvider, TooltipRoot, TooltipTrigger }\n"],"names":[],"mappings":";;;;;;;AAYA,MAAM,kBAAkB,gBAAA,CAAiB;AAMzC,MAAM,cAAc,gBAAA,CAAiB;AAMrC,MAAM,iBAAiB,gBAAA,CAAiB;AAExC,MAAM,cAAA,GAAiB,KAAA,CAAM,UAAA,CAG3B,CAAC,EAAE,SAAA,EAAW,UAAA,GAAa,CAAA,EAAG,KAAA,EAAO,QAAA,EAAU,GAAG,KAAA,IAAS,GAAA,qBAC3D,IAAA;AAAA,EAAC,gBAAA,CAAiB,OAAA;AAAA,EAAjB;AAAA,IACC,GAAA;AAAA,IACA,UAAA;AAAA,IACA,SAAA,EAAW,EAAA;AAAA,MACT,8CAAA;AAAA,MACA,gBAAA;AAAA,MACA,iCAAA;AAAA,MACA,gGAAA;AAAA,MACA,wCAAA;AAAA,MACA,wCAAA;AAAA,MACA,wCAAA;AAAA,MACA,wCAAA;AAAA,MACA;AAAA,KACF;AAAA,IACC,GAAG,KAAA;AAAA,IAEH,QAAA,EAAA;AAAA,MAAA,QAAA;AAAA,MACA,yBAAS,GAAA,CAAC,gBAAA,CAAiB,KAAA,EAAjB,EAAuB,MAAK,OAAA,EAAQ;AAAA;AAAA;AACjD,CACD;AACD,cAAA,CAAe,WAAA,GAAc,iBAAiB,OAAA,CAAQ,WAAA;AA6E/C,SAAS,OAAA,CAAQ;AAAA,EACtB,cAAA;AAAA,EACA,QAAA;AAAA,EACA,IAAA,GAAO,KAAA;AAAA,EACP,SAAA;AAAA,EACA,aAAA,GAAgB,GAAA;AAAA,EAChB,KAAA,GAAQ,KAAA;AAAA,EACR,UAAA;AAAA,EACA,WAAA;AAAA,EACA,gBAAA;AAAA,EACA,SAAA;AAAA,EACA,GAAG;AACL,CAAA,EAA2B;AACzB,EAAA,MAAM,WAAW,WAAA,EAAY;AAE7B,EAAA,OAAO,QAAA,mBACL,GAAA;AAAA,IAAC,OAAA;AAAA,IAAA;AAAA,MACC,IAAA,EAAM,UAAA;AAAA,MACN,SAAA;AAAA,MACA,KAAA,EAAO,WAAA;AAAA,MACP,UAAA,EAAY,gBAAA;AAAA,MACZ,cAAA;AAAA,MACA,SAAA;AAAA,MACC,GAAG,KAAA;AAAA,MAEH;AAAA;AAAA,GACH,mBAEA,GAAA,CAAC,eAAA,EAAA,EACC,QAAA,kBAAA,IAAA,CAAC,eAAY,aAAA,EACX,QAAA,EAAA;AAAA,oBAAA,GAAA,CAAC,cAAA,EAAA,EAAe,OAAA,EAAO,IAAA,EAAE,QAAA,EAAA,cAAA,EAAe,CAAA;AAAA,oBACxC,GAAA,CAAC,cAAA,EAAA,EAAe,IAAA,EAAY,SAAA,EAAsB,OAC/C,QAAA,EACH;AAAA,GAAA,EACF,CAAA,EACF,CAAA;AAEJ;;;;"}
1
+ {"version":3,"file":"Tooltip.js","sources":["../../../src/components/Tooltip/Tooltip.tsx"],"sourcesContent":["import * as TooltipPrimitive from '@radix-ui/react-tooltip'\nimport * as React from 'react'\n\nimport { Popover } from '../Popover/Popover'\n\nimport { useIsMobile } from '@/lib/useMobile'\nimport { cn } from '@/lib/utils'\n\n/**\n * Provider component that wraps all tooltip instances.\n * Controls the delay duration and positioning of tooltips app-wide.\n */\nconst TooltipProvider = TooltipPrimitive.Provider\n\n/**\n * The root component that wraps the trigger and content.\n * Manages the open state and hover interactions.\n */\nconst TooltipRoot = TooltipPrimitive.Root\n\n/**\n * The element that triggers the tooltip when hovered or focused.\n * Wrap with asChild to use custom components as triggers.\n */\nconst TooltipTrigger = TooltipPrimitive.Trigger\n\nconst TooltipPortal = TooltipPrimitive.Portal\n\ntype TooltipContentProps = React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content> & {\n arrow?: boolean\n /**\n * Container for the portal. When provided, the tooltip is portaled into this element instead of `document.body`.\n * Pass `null` to disable portaling and render inline (legacy behavior).\n */\n container?: React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Portal>['container'] | null\n}\n\nconst TooltipContent = React.forwardRef<React.ComponentRef<typeof TooltipPrimitive.Content>, TooltipContentProps>(\n ({ className, sideOffset = 4, arrow, container, children, ...props }, ref) => {\n const content = (\n <TooltipPrimitive.Content\n ref={ref}\n sideOffset={sideOffset}\n className={cn(\n 'z-50 rounded-lg bg-white px-3 py-1.5 text-sm',\n 'shadow-tooltip',\n 'animate-in fade-in-0 zoom-in-95',\n 'data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95',\n 'data-[side=bottom]:slide-in-from-top-2',\n 'data-[side=left]:slide-in-from-right-2',\n 'data-[side=right]:slide-in-from-left-2',\n 'data-[side=top]:slide-in-from-bottom-2',\n className,\n )}\n {...props}\n >\n {children}\n {arrow && <TooltipPrimitive.Arrow fill=\"white\" />}\n </TooltipPrimitive.Content>\n )\n\n if (container === null) return content\n return <TooltipPrimitive.Portal container={container ?? undefined}>{content}</TooltipPrimitive.Portal>\n },\n)\nTooltipContent.displayName = TooltipPrimitive.Content.displayName\n\nexport type TooltipProps = {\n /**\n * The content to be displayed inside the tooltip\n */\n children: React.ReactElement | string\n\n /**\n * The element that triggers the tooltip on hover\n */\n triggerElement: React.ReactElement | string\n\n /**\n * The preferred side of the trigger to render the tooltip\n */\n side?: 'top' | 'right' | 'bottom' | 'left'\n\n /**\n * Optional custom className for additional styling\n */\n className?: string\n\n /**\n * Delay duration for showing the tooltip in milliseconds\n * @default 700\n */\n delayDuration?: number\n\n /**\n * Whether to show an arrow pointer on the tooltip\n * @default false\n */\n arrow?: boolean\n\n /**\n * The preferred side of the trigger to render the mobile\n */\n mobileSide?: 'top' | 'right' | 'bottom' | 'left'\n\n /**\n * The preferred alignment of the mobile\n */\n mobileAlign?: 'start' | 'center' | 'end'\n\n /**\n * The preferred side offset of the mobile\n */\n mobileSideOffset?: number\n\n /**\n * The container to position the mobile\n */\n container?: HTMLElement\n}\n\n/**\n * A versatile tooltip component that shows additional information on hover.\n * Built on top of Radix UI's tooltip primitives with custom styling and animations.\n *\n * @example\n * // Basic usage\n * <Tooltip triggerElement={<button>Hover me</button>} side=\"top\">\n * Helpful information\n * </Tooltip>\n *\n * @example\n * // With custom delay and className\n * <Tooltip\n * triggerElement={<Icon />}\n * side=\"right\"\n * delayDuration={300}\n * className=\"custom-tooltip\"\n * >\n * Icon description\n * </Tooltip>\n */\nexport function Tooltip({\n triggerElement,\n children,\n side = 'top',\n className,\n delayDuration = 700,\n arrow = false,\n mobileSide,\n mobileAlign,\n mobileSideOffset,\n container,\n ...props\n}: Readonly<TooltipProps>) {\n const isMobile = useIsMobile()\n\n return isMobile ? (\n <Popover\n side={mobileSide}\n className={className}\n align={mobileAlign}\n sideOffset={mobileSideOffset}\n triggerElement={triggerElement}\n container={container}\n {...props}\n >\n {children}\n </Popover>\n ) : (\n <TooltipProvider>\n <TooltipRoot delayDuration={delayDuration}>\n <TooltipTrigger asChild>{triggerElement}</TooltipTrigger>\n <TooltipContent side={side} className={className} arrow={arrow} container={container}>\n {children}\n </TooltipContent>\n </TooltipRoot>\n </TooltipProvider>\n )\n}\n\n/**\n * Export primitive components for custom tooltip implementations.\n * Use these when you need more control over the tooltip behavior and styling.\n *\n * @example\n * // Custom implementation using primitives\n * <TooltipProvider>\n * <TooltipRoot>\n * <TooltipTrigger asChild>\n * <button>Hover</button>\n * </TooltipTrigger>\n * <TooltipContent>Custom tooltip</TooltipContent>\n * </TooltipRoot>\n * </TooltipProvider>\n */\nexport { TooltipContent, TooltipPortal, TooltipProvider, TooltipRoot, TooltipTrigger }\n"],"names":[],"mappings":";;;;;;;AAYA,MAAM,kBAAkB,gBAAA,CAAiB;AAMzC,MAAM,cAAc,gBAAA,CAAiB;AAMrC,MAAM,iBAAiB,gBAAA,CAAiB;AAExC,MAAM,gBAAgB,gBAAA,CAAiB;AAWvC,MAAM,iBAAiB,KAAA,CAAM,UAAA;AAAA,EAC3B,CAAC,EAAE,SAAA,EAAW,UAAA,GAAa,CAAA,EAAG,KAAA,EAAO,SAAA,EAAW,QAAA,EAAU,GAAG,KAAA,EAAM,EAAG,GAAA,KAAQ;AAC5E,IAAA,MAAM,OAAA,mBACJ,IAAA;AAAA,MAAC,gBAAA,CAAiB,OAAA;AAAA,MAAjB;AAAA,QACC,GAAA;AAAA,QACA,UAAA;AAAA,QACA,SAAA,EAAW,EAAA;AAAA,UACT,8CAAA;AAAA,UACA,gBAAA;AAAA,UACA,iCAAA;AAAA,UACA,gGAAA;AAAA,UACA,wCAAA;AAAA,UACA,wCAAA;AAAA,UACA,wCAAA;AAAA,UACA,wCAAA;AAAA,UACA;AAAA,SACF;AAAA,QACC,GAAG,KAAA;AAAA,QAEH,QAAA,EAAA;AAAA,UAAA,QAAA;AAAA,UACA,yBAAS,GAAA,CAAC,gBAAA,CAAiB,KAAA,EAAjB,EAAuB,MAAK,OAAA,EAAQ;AAAA;AAAA;AAAA,KACjD;AAGF,IAAA,IAAI,SAAA,KAAc,MAAM,OAAO,OAAA;AAC/B,IAAA,2BAAQ,gBAAA,CAAiB,MAAA,EAAjB,EAAwB,SAAA,EAAW,SAAA,IAAa,QAAY,QAAA,EAAA,OAAA,EAAQ,CAAA;AAAA,EAC9E;AACF;AACA,cAAA,CAAe,WAAA,GAAc,iBAAiB,OAAA,CAAQ,WAAA;AA6E/C,SAAS,OAAA,CAAQ;AAAA,EACtB,cAAA;AAAA,EACA,QAAA;AAAA,EACA,IAAA,GAAO,KAAA;AAAA,EACP,SAAA;AAAA,EACA,aAAA,GAAgB,GAAA;AAAA,EAChB,KAAA,GAAQ,KAAA;AAAA,EACR,UAAA;AAAA,EACA,WAAA;AAAA,EACA,gBAAA;AAAA,EACA,SAAA;AAAA,EACA,GAAG;AACL,CAAA,EAA2B;AACzB,EAAA,MAAM,WAAW,WAAA,EAAY;AAE7B,EAAA,OAAO,QAAA,mBACL,GAAA;AAAA,IAAC,OAAA;AAAA,IAAA;AAAA,MACC,IAAA,EAAM,UAAA;AAAA,MACN,SAAA;AAAA,MACA,KAAA,EAAO,WAAA;AAAA,MACP,UAAA,EAAY,gBAAA;AAAA,MACZ,cAAA;AAAA,MACA,SAAA;AAAA,MACC,GAAG,KAAA;AAAA,MAEH;AAAA;AAAA,GACH,mBAEA,GAAA,CAAC,eAAA,EAAA,EACC,QAAA,kBAAA,IAAA,CAAC,eAAY,aAAA,EACX,QAAA,EAAA;AAAA,oBAAA,GAAA,CAAC,cAAA,EAAA,EAAe,OAAA,EAAO,IAAA,EAAE,QAAA,EAAA,cAAA,EAAe,CAAA;AAAA,wBACvC,cAAA,EAAA,EAAe,IAAA,EAAY,SAAA,EAAsB,KAAA,EAAc,WAC7D,QAAA,EACH;AAAA,GAAA,EACF,CAAA,EACF,CAAA;AAEJ;;;;"}
@@ -1,2 +1,2 @@
1
- export { Tooltip, TooltipContent, TooltipProvider, TooltipRoot, TooltipTrigger } from './Tooltip.js';
1
+ export { Tooltip, TooltipContent, TooltipPortal, TooltipProvider, TooltipRoot, TooltipTrigger } from './Tooltip.js';
2
2
  //# sourceMappingURL=index.js.map
package/dist/index.js CHANGED
@@ -42,7 +42,7 @@ export { Textarea } from './components/Textarea/Textarea.js';
42
42
  export { Toaster, toast } from './components/Toaster/Toaster.js';
43
43
  export { Toggle, toggleVariants } from './components/Toggle/Toggle.js';
44
44
  export { ToggleGroup, ToggleGroupItem } from './components/ToggleGroup/ToggleGroup.js';
45
- export { Tooltip, TooltipContent, TooltipProvider, TooltipRoot, TooltipTrigger } from './components/Tooltip/Tooltip.js';
45
+ export { Tooltip, TooltipContent, TooltipPortal, TooltipProvider, TooltipRoot, TooltipTrigger } from './components/Tooltip/Tooltip.js';
46
46
  export { TruncatedTypographyWithTooltip } from './components/TruncatedTypographyWithTooltip/TruncatedTypographyWithTooltip.js';
47
47
  export { Typography, typographyVariants } from './components/Typography/Typography.js';
48
48
  export { InputDate } from './components/InputDate/InputDate.js';
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "periplo-ui",
3
3
  "description": "IATI UI library",
4
4
  "private": false,
5
- "version": "4.3.0",
5
+ "version": "4.3.1",
6
6
  "type": "module",
7
7
  "main": "dist/index.js",
8
8
  "types": "dist/index.d.ts",