@vritti/quantum-ui 0.2.8 → 0.2.10

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 (188) hide show
  1. package/README.md +7 -7
  2. package/dist/Alert.js +115 -0
  3. package/dist/Alert.js.map +1 -0
  4. package/dist/Avatar.js +6 -6
  5. package/dist/Badge.js +2 -28
  6. package/dist/Badge.js.map +1 -1
  7. package/dist/Badge2.js +30 -0
  8. package/dist/Badge2.js.map +1 -0
  9. package/dist/Button.js +1 -1
  10. package/dist/Button2.js +35 -16
  11. package/dist/Button2.js.map +1 -1
  12. package/dist/Card.js +1 -1
  13. package/dist/Chart.js +2 -2
  14. package/dist/Checkbox.js +2 -289
  15. package/dist/Checkbox.js.map +1 -1
  16. package/dist/Checkbox2.js +292 -0
  17. package/dist/Checkbox2.js.map +1 -0
  18. package/dist/Combination.js +6 -18
  19. package/dist/Combination.js.map +1 -1
  20. package/dist/DatePicker.js +4 -335
  21. package/dist/DatePicker.js.map +1 -1
  22. package/dist/DropdownMenu.js +8 -7
  23. package/dist/DropdownMenu.js.map +1 -1
  24. package/dist/Form.js +52 -27
  25. package/dist/Form.js.map +1 -1
  26. package/dist/Label.js +2 -2
  27. package/dist/OTPField.js +1 -1
  28. package/dist/PasswordField.js +1 -1
  29. package/dist/PhoneField.js +6 -6
  30. package/dist/PhoneField.js.map +1 -1
  31. package/dist/Progress.js +3 -3
  32. package/dist/Skeleton.js +1 -1
  33. package/dist/Sonner.js +7 -39
  34. package/dist/Sonner.js.map +1 -1
  35. package/dist/Spinner.js +1 -1
  36. package/dist/Switch.js +5 -5
  37. package/dist/TextArea.js +1 -1
  38. package/dist/TextField.js +4 -3
  39. package/dist/TextField.js.map +1 -1
  40. package/dist/ThemeContext.js +2 -8
  41. package/dist/ThemeContext.js.map +1 -1
  42. package/dist/Toggle.js +3 -3
  43. package/dist/Typography.js +1 -1
  44. package/dist/assets/quantum-ui.css +31 -31
  45. package/dist/chevron-left.js +15 -0
  46. package/dist/chevron-left.js.map +1 -0
  47. package/dist/chevron-right.js +15 -0
  48. package/dist/chevron-right.js.map +1 -0
  49. package/dist/components/Alert.js +2 -0
  50. package/dist/components/Alert.js.map +1 -0
  51. package/dist/components/Badge.js +2 -1
  52. package/dist/components/Badge.js.map +1 -1
  53. package/dist/components/DataTable.js +2 -0
  54. package/dist/components/DataTable.js.map +1 -0
  55. package/dist/components/Select.js +1379 -0
  56. package/dist/components/Select.js.map +1 -0
  57. package/dist/field.js +2 -2
  58. package/dist/hooks/index.js +1 -0
  59. package/dist/hooks/index.js.map +1 -1
  60. package/dist/index.js +10 -4
  61. package/dist/index.js.map +1 -1
  62. package/dist/index10.js +10 -99
  63. package/dist/index10.js.map +1 -1
  64. package/dist/index11.js +35 -95
  65. package/dist/index11.js.map +1 -1
  66. package/dist/index12.js +86 -190
  67. package/dist/index12.js.map +1 -1
  68. package/dist/index13.js +196 -190
  69. package/dist/index13.js.map +1 -1
  70. package/dist/index14.js +199 -0
  71. package/dist/index14.js.map +1 -0
  72. package/dist/index2.js +65 -58
  73. package/dist/index2.js.map +1 -1
  74. package/dist/index3.js +60 -8
  75. package/dist/index3.js.map +1 -1
  76. package/dist/index4.js +80 -106
  77. package/dist/index4.js.map +1 -1
  78. package/dist/index5.js +42 -37
  79. package/dist/index5.js.map +1 -1
  80. package/dist/index6.js +9 -4
  81. package/dist/index6.js.map +1 -1
  82. package/dist/index7.js +125 -10
  83. package/dist/index7.js.map +1 -1
  84. package/dist/index8.js +37 -42
  85. package/dist/index8.js.map +1 -1
  86. package/dist/index9.js +4 -37
  87. package/dist/index9.js.map +1 -1
  88. package/dist/lib/components/Alert/Alert.d.ts +11 -0
  89. package/dist/lib/components/Alert/Alert.d.ts.map +1 -0
  90. package/dist/lib/components/Alert/index.d.ts +2 -0
  91. package/dist/lib/components/Alert/index.d.ts.map +1 -0
  92. package/dist/lib/components/DataTable/DataTable.d.ts +15 -0
  93. package/dist/lib/components/DataTable/DataTable.d.ts.map +1 -0
  94. package/dist/lib/components/DataTable/components/DataTableColumnHeader.d.ts +12 -0
  95. package/dist/lib/components/DataTable/components/DataTableColumnHeader.d.ts.map +1 -0
  96. package/dist/lib/components/DataTable/components/DataTableEmpty.d.ts +13 -0
  97. package/dist/lib/components/DataTable/components/DataTableEmpty.d.ts.map +1 -0
  98. package/dist/lib/components/DataTable/components/DataTablePagination.d.ts +13 -0
  99. package/dist/lib/components/DataTable/components/DataTablePagination.d.ts.map +1 -0
  100. package/dist/lib/components/DataTable/components/DataTableToolbar.d.ts +15 -0
  101. package/dist/lib/components/DataTable/components/DataTableToolbar.d.ts.map +1 -0
  102. package/dist/lib/components/DataTable/components/DataTableViewOptions.d.ts +11 -0
  103. package/dist/lib/components/DataTable/components/DataTableViewOptions.d.ts.map +1 -0
  104. package/dist/lib/components/DataTable/hooks/useDataTable.d.ts +3 -0
  105. package/dist/lib/components/DataTable/hooks/useDataTable.d.ts.map +1 -0
  106. package/dist/lib/components/DataTable/index.d.ts +10 -0
  107. package/dist/lib/components/DataTable/index.d.ts.map +1 -0
  108. package/dist/lib/components/DataTable/types.d.ts +67 -0
  109. package/dist/lib/components/DataTable/types.d.ts.map +1 -0
  110. package/dist/lib/components/DataTable/utils.d.ts +3 -0
  111. package/dist/lib/components/DataTable/utils.d.ts.map +1 -0
  112. package/dist/lib/components/Form/Form.d.ts +3 -2
  113. package/dist/lib/components/Form/Form.d.ts.map +1 -1
  114. package/dist/lib/components/PhoneField/PhoneField.d.ts +1 -0
  115. package/dist/lib/components/PhoneField/PhoneField.d.ts.map +1 -1
  116. package/dist/lib/components/Select/Select.d.ts +23 -0
  117. package/dist/lib/components/Select/Select.d.ts.map +1 -0
  118. package/dist/lib/components/Select/components/MultiSelect/MultiSelect.d.ts +25 -0
  119. package/dist/lib/components/Select/components/MultiSelect/MultiSelect.d.ts.map +1 -0
  120. package/dist/lib/components/Select/components/MultiSelect/MultiSelectFilter.d.ts +21 -0
  121. package/dist/lib/components/Select/components/MultiSelect/MultiSelectFilter.d.ts.map +1 -0
  122. package/dist/lib/components/Select/components/SingleSelect/SingleSelect.d.ts +25 -0
  123. package/dist/lib/components/Select/components/SingleSelect/SingleSelect.d.ts.map +1 -0
  124. package/dist/lib/components/Select/components/SingleSelect/SingleSelectFilter.d.ts +21 -0
  125. package/dist/lib/components/Select/components/SingleSelect/SingleSelectFilter.d.ts.map +1 -0
  126. package/dist/lib/components/Select/hooks/useMultiSelect.d.ts +32 -0
  127. package/dist/lib/components/Select/hooks/useMultiSelect.d.ts.map +1 -0
  128. package/dist/lib/components/Select/hooks/useSelect.d.ts +23 -0
  129. package/dist/lib/components/Select/hooks/useSelect.d.ts.map +1 -0
  130. package/dist/lib/components/Select/hooks/useSingleSelect.d.ts +30 -0
  131. package/dist/lib/components/Select/hooks/useSingleSelect.d.ts.map +1 -0
  132. package/dist/lib/components/Select/index.d.ts +38 -0
  133. package/dist/lib/components/Select/index.d.ts.map +1 -0
  134. package/dist/lib/components/Select/types.d.ts +31 -0
  135. package/dist/lib/components/Select/types.d.ts.map +1 -0
  136. package/dist/lib/components/TextField/TextField.d.ts.map +1 -1
  137. package/dist/lib/components/index.d.ts +3 -0
  138. package/dist/lib/components/index.d.ts.map +1 -1
  139. package/dist/lib/context/ThemeContext.d.ts +1 -1
  140. package/dist/lib/context/ThemeContext.d.ts.map +1 -1
  141. package/dist/lib/context/index.d.ts +1 -1
  142. package/dist/lib/context/index.d.ts.map +1 -1
  143. package/dist/lib/hooks/index.d.ts +2 -1
  144. package/dist/lib/hooks/index.d.ts.map +1 -1
  145. package/dist/lib/hooks/useSSE.d.ts +21 -0
  146. package/dist/lib/hooks/useSSE.d.ts.map +1 -0
  147. package/dist/lib/index.d.ts +2 -2
  148. package/dist/lib/index.d.ts.map +1 -1
  149. package/dist/lib/utils/axios.d.ts.map +1 -1
  150. package/dist/lib/utils/formHelpers.d.ts +7 -3
  151. package/dist/lib/utils/formHelpers.d.ts.map +1 -1
  152. package/dist/popover.js +329 -0
  153. package/dist/popover.js.map +1 -0
  154. package/dist/separator2.js +2 -2
  155. package/dist/shadcn/shadcnAlert/alert.d.ts +11 -0
  156. package/dist/shadcn/shadcnAlert/alert.d.ts.map +1 -0
  157. package/dist/shadcn/shadcnAlert/index.d.ts +2 -0
  158. package/dist/shadcn/shadcnAlert/index.d.ts.map +1 -0
  159. package/dist/shadcn/shadcnBadge/Badge.d.ts +1 -1
  160. package/dist/shadcn/shadcnButton/Button.d.ts +2 -2
  161. package/dist/shadcn/shadcnButton/Button.d.ts.map +1 -1
  162. package/dist/shadcn/shadcnInputOTP/InputOTP.d.ts +2 -2
  163. package/dist/shadcn/shadcnMultiSelect/index.d.ts +3 -0
  164. package/dist/shadcn/shadcnMultiSelect/index.d.ts.map +1 -0
  165. package/dist/shadcn/shadcnMultiSelect/multi-select.d.ts +61 -0
  166. package/dist/shadcn/shadcnMultiSelect/multi-select.d.ts.map +1 -0
  167. package/dist/shadcn/shadcnSingleSelect/index.d.ts +3 -0
  168. package/dist/shadcn/shadcnSingleSelect/index.d.ts.map +1 -0
  169. package/dist/shadcn/shadcnSingleSelect/single-select.d.ts +60 -0
  170. package/dist/shadcn/shadcnSingleSelect/single-select.d.ts.map +1 -0
  171. package/dist/shadcn/shadcnSonner/sonner.d.ts +1 -1
  172. package/dist/shadcn/shadcnSonner/sonner.d.ts.map +1 -1
  173. package/dist/triangle-alert.js +40 -0
  174. package/dist/triangle-alert.js.map +1 -0
  175. package/dist/useSSE.js +82 -0
  176. package/dist/useSSE.js.map +1 -0
  177. package/dist/useTheme.js.map +1 -1
  178. package/dist/utils/axios.js +4167 -2
  179. package/dist/utils/axios.js.map +1 -1
  180. package/dist/utils.js +3748 -2970
  181. package/dist/utils.js.map +1 -1
  182. package/dist/utils2.js +3042 -0
  183. package/dist/utils2.js.map +1 -0
  184. package/dist/x.js +18 -0
  185. package/dist/x.js.map +1 -0
  186. package/package.json +22 -7
  187. package/dist/axios.js +0 -4228
  188. package/dist/axios.js.map +0 -1
@@ -0,0 +1,1379 @@
1
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
2
+ import * as React from 'react';
3
+ import { useState, useMemo, useRef, useId, useCallback, useEffect } from 'react';
4
+ import { F as Field, e as FieldLabel, b as FieldDescription, c as FieldError } from '../field.js';
5
+ import { c as cn } from '../utils2.js';
6
+ import { P as Popover, a as PopoverTrigger, C as ChevronDown, b as PopoverContent } from '../popover.js';
7
+ import { c as createLucideIcon } from '../createLucideIcon.js';
8
+ import { C as Checkbox } from '../Checkbox2.js';
9
+ import { L as LoaderCircle } from '../loader-circle.js';
10
+ import { B as Badge } from '../Badge2.js';
11
+ import { X } from '../x.js';
12
+ import { C as Check } from '../check.js';
13
+ import { useQuery, keepPreviousData, useInfiniteQuery } from '@tanstack/react-query';
14
+ import { axios } from '../utils/axios.js';
15
+
16
+ /**
17
+ * @license lucide-react v0.562.0 - ISC
18
+ *
19
+ * This source code is licensed under the ISC license.
20
+ * See the LICENSE file in the root directory of this source tree.
21
+ */
22
+
23
+
24
+ const __iconNode = [
25
+ ["path", { d: "m21 21-4.34-4.34", key: "14j7rj" }],
26
+ ["circle", { cx: "11", cy: "11", r: "8", key: "4ej97u" }]
27
+ ];
28
+ const Search = createLucideIcon("search", __iconNode);
29
+
30
+ function MultiSelectRoot$1({ open, onOpenChange, disabled, children }) {
31
+ return /* @__PURE__ */ jsx(Popover, { "data-slot": "multi-select", open: disabled ? false : open, onOpenChange: disabled ? void 0 : onOpenChange, children });
32
+ }
33
+ const MultiSelectTrigger$1 = React.forwardRef(
34
+ ({ className, children, disabled, open, listboxId, ...props }, ref) => {
35
+ return /* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(
36
+ "button",
37
+ {
38
+ ref,
39
+ type: "button",
40
+ "data-slot": "multi-select-trigger",
41
+ role: "combobox",
42
+ "aria-expanded": open ?? false,
43
+ "aria-haspopup": "listbox",
44
+ "aria-controls": listboxId,
45
+ disabled,
46
+ className: cn(
47
+ "border-input data-[placeholder]:text-muted-foreground [&_svg:not([class*='text-'])]:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 dark:hover:bg-input/50 flex w-full items-center justify-between gap-2 rounded-md border bg-transparent px-3 py-2 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
48
+ "min-h-9",
49
+ className
50
+ ),
51
+ ...props,
52
+ children: [
53
+ children,
54
+ /* @__PURE__ */ jsx(ChevronDown, { className: "size-4 shrink-0 opacity-50" })
55
+ ]
56
+ }
57
+ ) });
58
+ }
59
+ );
60
+ MultiSelectTrigger$1.displayName = "MultiSelectTrigger";
61
+ function MultiSelectContent$1({ className, children, align = "start", ...props }) {
62
+ return /* @__PURE__ */ jsx(
63
+ PopoverContent,
64
+ {
65
+ "data-slot": "multi-select-content",
66
+ align,
67
+ className: cn("w-[var(--radix-popover-trigger-width)] p-0", className),
68
+ ...props,
69
+ children
70
+ }
71
+ );
72
+ }
73
+ function MultiSelectSearch$1({ value, onValueChange, placeholder = "Search...", className }) {
74
+ return /* @__PURE__ */ jsxs("div", { "data-slot": "multi-select-search", className: cn("flex items-center gap-2 border-b px-3", className), children: [
75
+ /* @__PURE__ */ jsx(Search, { className: "size-4 shrink-0 text-muted-foreground" }),
76
+ /* @__PURE__ */ jsx(
77
+ "input",
78
+ {
79
+ type: "text",
80
+ "aria-label": "Search options",
81
+ value,
82
+ onChange: (e) => onValueChange(e.target.value),
83
+ placeholder,
84
+ className: "flex h-9 w-full bg-transparent py-2 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50"
85
+ }
86
+ )
87
+ ] });
88
+ }
89
+ function MultiSelectActions$1({ onSelectAll, onClear, disabled, className }) {
90
+ return /* @__PURE__ */ jsxs(
91
+ "div",
92
+ {
93
+ "data-slot": "multi-select-actions",
94
+ className: cn("flex items-center justify-between border-t px-3 py-1.5", className),
95
+ children: [
96
+ /* @__PURE__ */ jsx(
97
+ "button",
98
+ {
99
+ type: "button",
100
+ disabled,
101
+ className: "text-xs font-medium text-primary hover:text-primary/80 disabled:pointer-events-none disabled:opacity-50",
102
+ onClick: onSelectAll,
103
+ children: "Select All"
104
+ }
105
+ ),
106
+ /* @__PURE__ */ jsx(
107
+ "button",
108
+ {
109
+ type: "button",
110
+ disabled,
111
+ className: "text-xs font-medium text-muted-foreground hover:text-foreground disabled:pointer-events-none disabled:opacity-50",
112
+ onClick: onClear,
113
+ children: "Clear"
114
+ }
115
+ )
116
+ ]
117
+ }
118
+ );
119
+ }
120
+ function MultiSelectList$1({ className, children, ...props }) {
121
+ return /* @__PURE__ */ jsx(
122
+ "div",
123
+ {
124
+ "data-slot": "multi-select-list",
125
+ role: "listbox",
126
+ "aria-multiselectable": "true",
127
+ className: cn("max-h-60 overflow-y-auto p-1", className),
128
+ ...props,
129
+ children
130
+ }
131
+ );
132
+ }
133
+ const MultiSelectRow$1 = React.memo(function MultiSelectRow2({
134
+ name,
135
+ checked,
136
+ onToggle,
137
+ disabled,
138
+ className
139
+ }) {
140
+ function handleClick() {
141
+ if (!disabled) {
142
+ onToggle();
143
+ }
144
+ }
145
+ function handleKeyDown(e) {
146
+ if (disabled) return;
147
+ if (e.key === "Enter" || e.key === " ") {
148
+ e.preventDefault();
149
+ onToggle();
150
+ }
151
+ }
152
+ return /* @__PURE__ */ jsxs(
153
+ "div",
154
+ {
155
+ "data-slot": "multi-select-row",
156
+ role: "option",
157
+ "aria-selected": checked,
158
+ "aria-disabled": disabled,
159
+ tabIndex: disabled ? void 0 : 0,
160
+ className: cn(
161
+ "relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none",
162
+ "hover:bg-accent hover:text-accent-foreground",
163
+ "data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
164
+ className
165
+ ),
166
+ "data-disabled": disabled ? "" : void 0,
167
+ onClick: handleClick,
168
+ onKeyDown: handleKeyDown,
169
+ children: [
170
+ /* @__PURE__ */ jsx(
171
+ Checkbox,
172
+ {
173
+ checked,
174
+ onCheckedChange: () => onToggle(),
175
+ disabled,
176
+ tabIndex: -1,
177
+ "aria-hidden": "true"
178
+ }
179
+ ),
180
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: name })
181
+ ]
182
+ }
183
+ );
184
+ });
185
+ function MultiSelectGroup$1({ className, children, ...props }) {
186
+ return /* @__PURE__ */ jsx("div", { "data-slot": "multi-select-group", role: "group", className: cn(className), ...props, children });
187
+ }
188
+ function MultiSelectGroupLabel$1({ className, children, ...props }) {
189
+ return /* @__PURE__ */ jsx(
190
+ "div",
191
+ {
192
+ "data-slot": "multi-select-group-label",
193
+ className: cn("text-muted-foreground px-2 py-1.5 text-xs", className),
194
+ ...props,
195
+ children
196
+ }
197
+ );
198
+ }
199
+ function MultiSelectEmpty$1({ children, className }) {
200
+ return /* @__PURE__ */ jsx(
201
+ "div",
202
+ {
203
+ "data-slot": "multi-select-empty",
204
+ className: cn("flex items-center justify-center py-6 text-sm text-muted-foreground", className),
205
+ children: children ?? "No options found."
206
+ }
207
+ );
208
+ }
209
+
210
+ function useMultiSelect({
211
+ options,
212
+ groups,
213
+ value: controlledValue,
214
+ onChange,
215
+ defaultValue,
216
+ remoteSearch
217
+ }) {
218
+ const isControlled = controlledValue !== void 0;
219
+ const [internalValue, setInternalValue] = useState(defaultValue ?? []);
220
+ const selectedValues = isControlled ? controlledValue : internalValue;
221
+ const selectedSet = useMemo(() => new Set(selectedValues), [selectedValues]);
222
+ const selectedValuesRef = useRef(selectedValues);
223
+ selectedValuesRef.current = selectedValues;
224
+ const [open, setOpen] = useState(false);
225
+ const [searchQuery, setSearchQuery] = useState("");
226
+ const listboxId = useId();
227
+ const optionMap = useMemo(() => {
228
+ const map = /* @__PURE__ */ new Map();
229
+ for (const option of options) {
230
+ map.set(option.value, option);
231
+ }
232
+ return map;
233
+ }, [options]);
234
+ const filteredOptions = useMemo(() => {
235
+ if (remoteSearch || !searchQuery) return options;
236
+ const lower = searchQuery.toLowerCase();
237
+ return options.filter((option) => option.label.toLowerCase().includes(lower));
238
+ }, [options, searchQuery, remoteSearch]);
239
+ const updateSelection = useCallback(
240
+ (nextValues) => {
241
+ if (!isControlled) {
242
+ setInternalValue(nextValues);
243
+ }
244
+ onChange?.(nextValues);
245
+ },
246
+ [isControlled, onChange]
247
+ );
248
+ const toggleOption = useCallback(
249
+ (optionValue) => {
250
+ const option = optionMap.get(optionValue);
251
+ if (option?.disabled) return;
252
+ const current = selectedValuesRef.current;
253
+ const nextValues = current.includes(optionValue) ? current.filter((v) => v !== optionValue) : [...current, optionValue];
254
+ updateSelection(nextValues);
255
+ },
256
+ [optionMap, updateSelection]
257
+ );
258
+ const selectAll = useCallback(() => {
259
+ const enabledValues = options.filter((o) => !o.disabled).map((o) => o.value);
260
+ const currentDisabledValues = selectedValuesRef.current.filter((v) => optionMap.get(v)?.disabled);
261
+ updateSelection([.../* @__PURE__ */ new Set([...currentDisabledValues, ...enabledValues])]);
262
+ }, [options, optionMap, updateSelection]);
263
+ const clearAll = useCallback(() => {
264
+ const currentDisabledValues = selectedValuesRef.current.filter((v) => optionMap.get(v)?.disabled);
265
+ updateSelection(currentDisabledValues);
266
+ }, [optionMap, updateSelection]);
267
+ useEffect(() => {
268
+ if (!open) {
269
+ setSearchQuery("");
270
+ }
271
+ }, [open]);
272
+ const selectedOptions = useMemo(
273
+ () => selectedValues.map((v) => optionMap.get(v)).filter(Boolean),
274
+ [selectedValues, optionMap]
275
+ );
276
+ const grouped = useMemo(() => {
277
+ if (!groups || groups.length === 0) return null;
278
+ const buckets = /* @__PURE__ */ new Map();
279
+ for (const g of groups) {
280
+ buckets.set(g.id, []);
281
+ }
282
+ const ungrouped = [];
283
+ for (const option of filteredOptions) {
284
+ const bucket = option.groupId != null ? buckets.get(option.groupId) : void 0;
285
+ if (bucket) {
286
+ bucket.push(option);
287
+ } else {
288
+ ungrouped.push(option);
289
+ }
290
+ }
291
+ const entries = [];
292
+ for (const g of groups) {
293
+ const bucket = buckets.get(g.id);
294
+ if (!bucket || bucket.length === 0) continue;
295
+ entries.push({ name: g.name, options: bucket });
296
+ }
297
+ return { ungrouped, entries };
298
+ }, [filteredOptions, groups]);
299
+ return {
300
+ selectedValues,
301
+ selectedSet,
302
+ selectedOptions,
303
+ open,
304
+ setOpen,
305
+ searchQuery,
306
+ setSearchQuery,
307
+ listboxId,
308
+ filteredOptions,
309
+ grouped,
310
+ toggleOption,
311
+ selectAll,
312
+ clearAll
313
+ };
314
+ }
315
+
316
+ const MultiSelect = React.forwardRef(
317
+ ({
318
+ label,
319
+ description,
320
+ error,
321
+ placeholder = "Select options",
322
+ options,
323
+ groups,
324
+ value: controlledValue,
325
+ onChange,
326
+ onBlur,
327
+ name,
328
+ disabled = false,
329
+ required,
330
+ className,
331
+ id,
332
+ defaultValue,
333
+ maxDisplayedItems = 3,
334
+ searchable = false,
335
+ searchPlaceholder = "Search...",
336
+ asyncState
337
+ }, ref) => {
338
+ const state = useMultiSelect({
339
+ options: options ?? [],
340
+ groups,
341
+ value: controlledValue,
342
+ onChange,
343
+ defaultValue,
344
+ remoteSearch: !!asyncState
345
+ });
346
+ const searchValue = asyncState ? asyncState.searchQuery : state.searchQuery;
347
+ const setSearchValue = asyncState ? asyncState.setSearchQuery : state.setSearchQuery;
348
+ const showSearch = !!asyncState || searchable;
349
+ function renderRow(option) {
350
+ return /* @__PURE__ */ jsx(
351
+ MultiSelectRow$1,
352
+ {
353
+ name: option.label,
354
+ checked: state.selectedSet.has(option.value),
355
+ onToggle: () => {
356
+ state.toggleOption(option.value);
357
+ },
358
+ disabled: option.disabled
359
+ },
360
+ String(option.value)
361
+ );
362
+ }
363
+ function renderOptions() {
364
+ if (asyncState?.loading) {
365
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-center gap-2 py-6", children: [
366
+ /* @__PURE__ */ jsx(LoaderCircle, { className: "size-4 animate-spin text-muted-foreground" }),
367
+ /* @__PURE__ */ jsx("span", { className: "text-sm text-muted-foreground", children: "Loading..." })
368
+ ] });
369
+ }
370
+ if (state.filteredOptions.length === 0) {
371
+ return /* @__PURE__ */ jsx(MultiSelectEmpty$1, {});
372
+ }
373
+ if (!state.grouped) {
374
+ return state.filteredOptions.map(renderRow);
375
+ }
376
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
377
+ state.grouped.ungrouped.map(renderRow),
378
+ state.grouped.entries.map((entry) => /* @__PURE__ */ jsxs(MultiSelectGroup$1, { children: [
379
+ /* @__PURE__ */ jsx(MultiSelectGroupLabel$1, { children: entry.name }),
380
+ entry.options.map(renderRow)
381
+ ] }, entry.name))
382
+ ] });
383
+ }
384
+ function renderTriggerContent() {
385
+ if (state.selectedOptions.length === 0) {
386
+ return /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: placeholder });
387
+ }
388
+ if (state.selectedOptions.length > maxDisplayedItems) {
389
+ return /* @__PURE__ */ jsxs("span", { className: "text-sm", children: [
390
+ state.selectedOptions.length,
391
+ " items selected"
392
+ ] });
393
+ }
394
+ return /* @__PURE__ */ jsx("span", { className: "flex flex-wrap items-center gap-1", children: state.selectedOptions.map((option) => /* @__PURE__ */ jsxs(Badge, { variant: "secondary", className: "gap-1 pr-1", children: [
395
+ option.label,
396
+ /* @__PURE__ */ jsx(
397
+ "button",
398
+ {
399
+ type: "button",
400
+ "aria-label": `Remove ${option.label}`,
401
+ className: "rounded-full outline-none ring-offset-background focus:ring-2 focus:ring-ring focus:ring-offset-2",
402
+ onMouseDown: (e) => e.preventDefault(),
403
+ onClick: (e) => {
404
+ e.stopPropagation();
405
+ state.toggleOption(option.value);
406
+ },
407
+ children: /* @__PURE__ */ jsx(X, { className: "size-3 text-muted-foreground hover:text-foreground" })
408
+ }
409
+ )
410
+ ] }, String(option.value))) });
411
+ }
412
+ return /* @__PURE__ */ jsxs(Field, { children: [
413
+ label && /* @__PURE__ */ jsx(FieldLabel, { children: label }),
414
+ /* @__PURE__ */ jsxs(MultiSelectRoot$1, { open: state.open, onOpenChange: state.setOpen, disabled, children: [
415
+ /* @__PURE__ */ jsx(
416
+ MultiSelectTrigger$1,
417
+ {
418
+ ref,
419
+ id,
420
+ open: state.open,
421
+ listboxId: state.listboxId,
422
+ "aria-invalid": !!error,
423
+ "aria-required": required,
424
+ disabled,
425
+ onBlur,
426
+ className,
427
+ children: /* @__PURE__ */ jsx("span", { className: "flex flex-1 flex-wrap items-center gap-1 overflow-hidden", children: renderTriggerContent() })
428
+ }
429
+ ),
430
+ /* @__PURE__ */ jsxs(MultiSelectContent$1, { children: [
431
+ showSearch && /* @__PURE__ */ jsx(MultiSelectSearch$1, { value: searchValue, onValueChange: setSearchValue, placeholder: searchPlaceholder }),
432
+ /* @__PURE__ */ jsxs(MultiSelectList$1, { id: state.listboxId, children: [
433
+ renderOptions(),
434
+ asyncState?.hasMore && /* @__PURE__ */ jsx("div", { ref: asyncState.sentinelRef, className: "h-1" }),
435
+ asyncState?.loadingMore && /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-center gap-2 py-2", children: [
436
+ /* @__PURE__ */ jsx(LoaderCircle, { className: "size-3 animate-spin text-muted-foreground" }),
437
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: "Loading more..." })
438
+ ] })
439
+ ] }),
440
+ /* @__PURE__ */ jsx(MultiSelectActions$1, { onSelectAll: state.selectAll, onClear: state.clearAll, disabled })
441
+ ] })
442
+ ] }),
443
+ name && state.selectedValues.map((v) => /* @__PURE__ */ jsx("input", { type: "hidden", name, value: String(v) }, String(v))),
444
+ description && !error && /* @__PURE__ */ jsx(FieldDescription, { children: description }),
445
+ error && /* @__PURE__ */ jsx(FieldError, { children: error })
446
+ ] });
447
+ }
448
+ );
449
+ MultiSelect.displayName = "MultiSelect";
450
+
451
+ const MultiSelectFilter = React.forwardRef(
452
+ ({
453
+ label,
454
+ placeholder,
455
+ options,
456
+ groups,
457
+ value: controlledValue,
458
+ onChange,
459
+ onBlur,
460
+ name,
461
+ disabled = false,
462
+ required,
463
+ className,
464
+ id,
465
+ defaultValue,
466
+ searchPlaceholder = "Search...",
467
+ asyncState
468
+ }, ref) => {
469
+ const state = useMultiSelect({
470
+ options: options ?? [],
471
+ groups,
472
+ value: controlledValue,
473
+ onChange,
474
+ defaultValue,
475
+ remoteSearch: !!asyncState
476
+ });
477
+ const searchValue = asyncState ? asyncState.searchQuery : state.searchQuery;
478
+ const setSearchValue = asyncState ? asyncState.setSearchQuery : state.setSearchQuery;
479
+ function renderRow(option) {
480
+ return /* @__PURE__ */ jsx(
481
+ MultiSelectRow$1,
482
+ {
483
+ name: option.label,
484
+ checked: state.selectedSet.has(option.value),
485
+ onToggle: () => {
486
+ state.toggleOption(option.value);
487
+ },
488
+ disabled: option.disabled
489
+ },
490
+ String(option.value)
491
+ );
492
+ }
493
+ function renderOptions() {
494
+ if (asyncState?.loading) {
495
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-center gap-2 py-6", children: [
496
+ /* @__PURE__ */ jsx(LoaderCircle, { className: "size-4 animate-spin text-muted-foreground" }),
497
+ /* @__PURE__ */ jsx("span", { className: "text-sm text-muted-foreground", children: "Loading..." })
498
+ ] });
499
+ }
500
+ if (state.filteredOptions.length === 0) {
501
+ return /* @__PURE__ */ jsx(MultiSelectEmpty$1, {});
502
+ }
503
+ if (!state.grouped) {
504
+ return state.filteredOptions.map(renderRow);
505
+ }
506
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
507
+ state.grouped.ungrouped.map(renderRow),
508
+ state.grouped.entries.map((entry) => /* @__PURE__ */ jsxs(MultiSelectGroup$1, { children: [
509
+ /* @__PURE__ */ jsx(MultiSelectGroupLabel$1, { children: entry.name }),
510
+ entry.options.map(renderRow)
511
+ ] }, entry.name))
512
+ ] });
513
+ }
514
+ const hasValues = state.selectedValues.length > 0;
515
+ const triggerText = hasValues ? `${label} = ${state.selectedValues.length} selected` : label ?? placeholder;
516
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
517
+ /* @__PURE__ */ jsxs(MultiSelectRoot$1, { open: state.open, onOpenChange: state.setOpen, disabled, children: [
518
+ /* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(
519
+ "button",
520
+ {
521
+ ref,
522
+ id,
523
+ type: "button",
524
+ role: "combobox",
525
+ "aria-expanded": state.open,
526
+ "aria-haspopup": "listbox",
527
+ "aria-controls": state.listboxId,
528
+ "aria-required": required,
529
+ disabled,
530
+ onBlur,
531
+ className: cn(
532
+ "inline-flex items-center gap-2 rounded-md border px-3 py-2 text-sm whitespace-nowrap shadow-xs outline-none transition-colors disabled:cursor-not-allowed disabled:opacity-50",
533
+ hasValues ? "bg-primary text-primary-foreground border-primary hover:bg-primary/90" : "border-input bg-transparent hover:bg-accent hover:text-accent-foreground",
534
+ className
535
+ ),
536
+ children: [
537
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: triggerText }),
538
+ /* @__PURE__ */ jsx(ChevronDown, { className: "size-4 shrink-0 opacity-50" })
539
+ ]
540
+ }
541
+ ) }),
542
+ /* @__PURE__ */ jsxs(MultiSelectContent$1, { className: "w-[250px]", children: [
543
+ /* @__PURE__ */ jsx(MultiSelectSearch$1, { value: searchValue, onValueChange: setSearchValue, placeholder: searchPlaceholder }),
544
+ /* @__PURE__ */ jsxs(MultiSelectList$1, { id: state.listboxId, children: [
545
+ renderOptions(),
546
+ asyncState?.hasMore && /* @__PURE__ */ jsx("div", { ref: asyncState.sentinelRef, className: "h-1" }),
547
+ asyncState?.loadingMore && /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-center gap-2 py-2", children: [
548
+ /* @__PURE__ */ jsx(LoaderCircle, { className: "size-3 animate-spin text-muted-foreground" }),
549
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: "Loading more..." })
550
+ ] })
551
+ ] }),
552
+ /* @__PURE__ */ jsx(MultiSelectActions$1, { onSelectAll: state.selectAll, onClear: state.clearAll, disabled })
553
+ ] })
554
+ ] }),
555
+ name && state.selectedValues.map((v) => /* @__PURE__ */ jsx("input", { type: "hidden", name, value: String(v) }, String(v)))
556
+ ] });
557
+ }
558
+ );
559
+ MultiSelectFilter.displayName = "MultiSelectFilter";
560
+
561
+ function SingleSelectRoot$1({ open, onOpenChange, disabled, children }) {
562
+ return /* @__PURE__ */ jsx(
563
+ Popover,
564
+ {
565
+ "data-slot": "single-select",
566
+ open: disabled ? false : open,
567
+ onOpenChange: disabled ? void 0 : onOpenChange,
568
+ children
569
+ }
570
+ );
571
+ }
572
+ const SingleSelectTrigger$1 = React.forwardRef(
573
+ ({ className, children, disabled, open, listboxId, ...props }, ref) => {
574
+ return /* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(
575
+ "button",
576
+ {
577
+ ref,
578
+ type: "button",
579
+ "data-slot": "single-select-trigger",
580
+ role: "combobox",
581
+ "aria-expanded": open ?? false,
582
+ "aria-haspopup": "listbox",
583
+ "aria-controls": listboxId,
584
+ disabled,
585
+ className: cn(
586
+ "border-input data-[placeholder]:text-muted-foreground [&_svg:not([class*='text-'])]:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 dark:hover:bg-input/50 flex w-full items-center justify-between gap-2 rounded-md border bg-transparent px-3 py-2 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
587
+ "min-h-9",
588
+ className
589
+ ),
590
+ ...props,
591
+ children: [
592
+ children,
593
+ /* @__PURE__ */ jsx(ChevronDown, { className: "size-4 shrink-0 opacity-50" })
594
+ ]
595
+ }
596
+ ) });
597
+ }
598
+ );
599
+ SingleSelectTrigger$1.displayName = "SingleSelectTrigger";
600
+ function SingleSelectContent$1({ className, children, align = "start", ...props }) {
601
+ return /* @__PURE__ */ jsx(
602
+ PopoverContent,
603
+ {
604
+ "data-slot": "single-select-content",
605
+ align,
606
+ className: cn("w-[var(--radix-popover-trigger-width)] p-0", className),
607
+ ...props,
608
+ children
609
+ }
610
+ );
611
+ }
612
+ function SingleSelectSearch$1({ value, onValueChange, placeholder = "Search...", className }) {
613
+ return /* @__PURE__ */ jsxs("div", { "data-slot": "single-select-search", className: cn("flex items-center gap-2 border-b px-3", className), children: [
614
+ /* @__PURE__ */ jsx(Search, { className: "size-4 shrink-0 text-muted-foreground" }),
615
+ /* @__PURE__ */ jsx(
616
+ "input",
617
+ {
618
+ type: "text",
619
+ "aria-label": "Search options",
620
+ value,
621
+ onChange: (e) => onValueChange(e.target.value),
622
+ placeholder,
623
+ className: "flex h-9 w-full bg-transparent py-2 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50"
624
+ }
625
+ )
626
+ ] });
627
+ }
628
+ function SingleSelectClear$1({ onClear, disabled, className }) {
629
+ return /* @__PURE__ */ jsx(
630
+ "div",
631
+ {
632
+ "data-slot": "single-select-clear",
633
+ className: cn("flex items-center justify-end border-t px-3 py-1.5", className),
634
+ children: /* @__PURE__ */ jsx(
635
+ "button",
636
+ {
637
+ type: "button",
638
+ disabled,
639
+ className: "text-xs font-medium text-muted-foreground hover:text-foreground disabled:pointer-events-none disabled:opacity-50",
640
+ onClick: onClear,
641
+ children: "Clear"
642
+ }
643
+ )
644
+ }
645
+ );
646
+ }
647
+ function SingleSelectList$1({ className, children, ...props }) {
648
+ return /* @__PURE__ */ jsx(
649
+ "div",
650
+ {
651
+ "data-slot": "single-select-list",
652
+ role: "listbox",
653
+ className: cn("max-h-60 overflow-y-auto p-1", className),
654
+ ...props,
655
+ children
656
+ }
657
+ );
658
+ }
659
+ const SingleSelectRow$1 = React.memo(function SingleSelectRow2({
660
+ name,
661
+ selected,
662
+ onSelect,
663
+ disabled,
664
+ className
665
+ }) {
666
+ function handleClick() {
667
+ if (!disabled) {
668
+ onSelect();
669
+ }
670
+ }
671
+ function handleKeyDown(e) {
672
+ if (disabled) return;
673
+ if (e.key === "Enter" || e.key === " ") {
674
+ e.preventDefault();
675
+ onSelect();
676
+ }
677
+ }
678
+ return /* @__PURE__ */ jsxs(
679
+ "div",
680
+ {
681
+ "data-slot": "single-select-row",
682
+ role: "option",
683
+ "aria-selected": selected,
684
+ "aria-disabled": disabled,
685
+ tabIndex: disabled ? void 0 : 0,
686
+ className: cn(
687
+ "relative flex w-full cursor-default items-center rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none",
688
+ "hover:bg-accent hover:text-accent-foreground",
689
+ "data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
690
+ className
691
+ ),
692
+ "data-disabled": disabled ? "" : void 0,
693
+ onClick: handleClick,
694
+ onKeyDown: handleKeyDown,
695
+ children: [
696
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: name }),
697
+ /* @__PURE__ */ jsx("span", { className: "absolute right-2 flex size-3.5 items-center justify-center", children: selected && /* @__PURE__ */ jsx(Check, { className: "size-4" }) })
698
+ ]
699
+ }
700
+ );
701
+ });
702
+ function SingleSelectGroup$1({ className, children, ...props }) {
703
+ return /* @__PURE__ */ jsx("div", { "data-slot": "single-select-group", role: "group", className: cn(className), ...props, children });
704
+ }
705
+ function SingleSelectGroupLabel$1({ className, children, ...props }) {
706
+ return /* @__PURE__ */ jsx(
707
+ "div",
708
+ {
709
+ "data-slot": "single-select-group-label",
710
+ className: cn("text-muted-foreground px-2 py-1.5 text-xs", className),
711
+ ...props,
712
+ children
713
+ }
714
+ );
715
+ }
716
+ function SingleSelectEmpty$1({ children, className }) {
717
+ return /* @__PURE__ */ jsx(
718
+ "div",
719
+ {
720
+ "data-slot": "single-select-empty",
721
+ className: cn("flex items-center justify-center py-6 text-sm text-muted-foreground", className),
722
+ children: children ?? "No options found."
723
+ }
724
+ );
725
+ }
726
+
727
+ function useSingleSelect({
728
+ options,
729
+ groups,
730
+ value: controlledValue,
731
+ onChange,
732
+ defaultValue,
733
+ remoteSearch
734
+ }) {
735
+ const isControlled = controlledValue !== void 0;
736
+ const [internalValue, setInternalValue] = useState(defaultValue ?? "");
737
+ const selectedValue = isControlled ? controlledValue : internalValue;
738
+ const [open, setOpen] = useState(false);
739
+ const [searchQuery, setSearchQuery] = useState("");
740
+ const listboxId = useId();
741
+ const optionMap = useMemo(() => {
742
+ const map = /* @__PURE__ */ new Map();
743
+ for (const option of options) {
744
+ map.set(option.value, option);
745
+ }
746
+ return map;
747
+ }, [options]);
748
+ const filteredOptions = useMemo(() => {
749
+ if (remoteSearch || !searchQuery) return options;
750
+ const lower = searchQuery.toLowerCase();
751
+ return options.filter((o) => o.label.toLowerCase().includes(lower));
752
+ }, [options, searchQuery, remoteSearch]);
753
+ const updateSelection = useCallback(
754
+ (nextValue) => {
755
+ if (!isControlled) {
756
+ setInternalValue(nextValue);
757
+ }
758
+ onChange?.(nextValue);
759
+ },
760
+ [isControlled, onChange]
761
+ );
762
+ const selectOption = useCallback(
763
+ (optionValue) => {
764
+ const option = optionMap.get(optionValue);
765
+ if (option?.disabled) return;
766
+ updateSelection(optionValue);
767
+ setOpen(false);
768
+ },
769
+ [optionMap, updateSelection]
770
+ );
771
+ const clearSelection = useCallback(() => {
772
+ updateSelection("");
773
+ setOpen(false);
774
+ }, [updateSelection]);
775
+ useEffect(() => {
776
+ if (!open) {
777
+ setSearchQuery("");
778
+ }
779
+ }, [open]);
780
+ const selectedOption = selectedValue ? optionMap.get(selectedValue) : void 0;
781
+ const grouped = useMemo(() => {
782
+ if (!groups || groups.length === 0) return null;
783
+ const buckets = /* @__PURE__ */ new Map();
784
+ for (const g of groups) {
785
+ buckets.set(g.id, []);
786
+ }
787
+ const ungrouped = [];
788
+ for (const option of filteredOptions) {
789
+ const bucket = option.groupId != null ? buckets.get(option.groupId) : void 0;
790
+ if (bucket) {
791
+ bucket.push(option);
792
+ } else {
793
+ ungrouped.push(option);
794
+ }
795
+ }
796
+ const entries = [];
797
+ for (const g of groups) {
798
+ const bucket = buckets.get(g.id);
799
+ if (!bucket || bucket.length === 0) continue;
800
+ entries.push({ name: g.name, options: bucket });
801
+ }
802
+ return { ungrouped, entries };
803
+ }, [filteredOptions, groups]);
804
+ return {
805
+ selectedValue,
806
+ selectedOption,
807
+ open,
808
+ setOpen,
809
+ searchQuery,
810
+ setSearchQuery,
811
+ listboxId,
812
+ filteredOptions,
813
+ grouped,
814
+ selectOption,
815
+ clearSelection
816
+ };
817
+ }
818
+
819
+ const SingleSelect = React.forwardRef(
820
+ ({
821
+ label,
822
+ description,
823
+ error,
824
+ placeholder = "Select an option",
825
+ options,
826
+ groups,
827
+ value: controlledValue,
828
+ onChange,
829
+ onBlur,
830
+ name,
831
+ disabled = false,
832
+ required,
833
+ className,
834
+ id,
835
+ defaultValue,
836
+ searchable = false,
837
+ searchPlaceholder = "Search...",
838
+ clearable = false,
839
+ asyncState
840
+ }, ref) => {
841
+ const state = useSingleSelect({
842
+ options: options ?? [],
843
+ groups,
844
+ value: controlledValue,
845
+ onChange,
846
+ defaultValue,
847
+ remoteSearch: !!asyncState
848
+ });
849
+ const searchValue = asyncState ? asyncState.searchQuery : state.searchQuery;
850
+ const setSearchValue = asyncState ? asyncState.setSearchQuery : state.setSearchQuery;
851
+ const showSearch = !!asyncState || searchable;
852
+ function renderRow(option) {
853
+ return /* @__PURE__ */ jsx(
854
+ SingleSelectRow$1,
855
+ {
856
+ name: option.label,
857
+ selected: state.selectedValue === option.value,
858
+ onSelect: () => {
859
+ state.selectOption(option.value);
860
+ },
861
+ disabled: option.disabled
862
+ },
863
+ String(option.value)
864
+ );
865
+ }
866
+ function renderOptions() {
867
+ if (asyncState?.loading) {
868
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-center gap-2 py-6", children: [
869
+ /* @__PURE__ */ jsx(LoaderCircle, { className: "size-4 animate-spin text-muted-foreground" }),
870
+ /* @__PURE__ */ jsx("span", { className: "text-sm text-muted-foreground", children: "Loading..." })
871
+ ] });
872
+ }
873
+ if (state.filteredOptions.length === 0) {
874
+ return /* @__PURE__ */ jsx(SingleSelectEmpty$1, {});
875
+ }
876
+ if (!state.grouped) {
877
+ return state.filteredOptions.map(renderRow);
878
+ }
879
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
880
+ state.grouped.ungrouped.map(renderRow),
881
+ state.grouped.entries.map((entry) => /* @__PURE__ */ jsxs(SingleSelectGroup$1, { children: [
882
+ /* @__PURE__ */ jsx(SingleSelectGroupLabel$1, { children: entry.name }),
883
+ entry.options.map(renderRow)
884
+ ] }, entry.name))
885
+ ] });
886
+ }
887
+ return /* @__PURE__ */ jsxs(Field, { children: [
888
+ label && /* @__PURE__ */ jsx(FieldLabel, { children: label }),
889
+ /* @__PURE__ */ jsxs(SingleSelectRoot$1, { open: state.open, onOpenChange: state.setOpen, disabled, children: [
890
+ /* @__PURE__ */ jsx(
891
+ SingleSelectTrigger$1,
892
+ {
893
+ ref,
894
+ id,
895
+ open: state.open,
896
+ listboxId: state.listboxId,
897
+ "aria-invalid": !!error,
898
+ "aria-required": required,
899
+ disabled,
900
+ onBlur,
901
+ className: cn("w-full", className),
902
+ children: /* @__PURE__ */ jsx("span", { className: "flex flex-1 items-center overflow-hidden", children: state.selectedOption ? /* @__PURE__ */ jsx("span", { className: "truncate", children: state.selectedOption.label }) : /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: placeholder }) })
903
+ }
904
+ ),
905
+ /* @__PURE__ */ jsxs(SingleSelectContent$1, { children: [
906
+ showSearch && /* @__PURE__ */ jsx(SingleSelectSearch$1, { value: searchValue, onValueChange: setSearchValue, placeholder: searchPlaceholder }),
907
+ /* @__PURE__ */ jsxs(SingleSelectList$1, { id: state.listboxId, children: [
908
+ renderOptions(),
909
+ asyncState?.hasMore && /* @__PURE__ */ jsx("div", { ref: asyncState.sentinelRef, className: "h-1" }),
910
+ asyncState?.loadingMore && /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-center gap-2 py-2", children: [
911
+ /* @__PURE__ */ jsx(LoaderCircle, { className: "size-3 animate-spin text-muted-foreground" }),
912
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: "Loading more..." })
913
+ ] })
914
+ ] }),
915
+ clearable && /* @__PURE__ */ jsx(SingleSelectClear$1, { onClear: state.clearSelection, disabled: !state.selectedValue })
916
+ ] })
917
+ ] }),
918
+ name && state.selectedValue && /* @__PURE__ */ jsx("input", { type: "hidden", name, value: String(state.selectedValue) }),
919
+ description && !error && /* @__PURE__ */ jsx(FieldDescription, { children: description }),
920
+ error && /* @__PURE__ */ jsx(FieldError, { children: error })
921
+ ] });
922
+ }
923
+ );
924
+ SingleSelect.displayName = "SingleSelect";
925
+
926
+ const SingleSelectFilter = React.forwardRef(
927
+ ({
928
+ label,
929
+ placeholder,
930
+ options,
931
+ groups,
932
+ value: controlledValue,
933
+ onChange,
934
+ onBlur,
935
+ name,
936
+ disabled = false,
937
+ required,
938
+ className,
939
+ id,
940
+ defaultValue,
941
+ searchPlaceholder = "Search...",
942
+ asyncState
943
+ }, ref) => {
944
+ const state = useSingleSelect({
945
+ options: options ?? [],
946
+ groups,
947
+ value: controlledValue,
948
+ onChange,
949
+ defaultValue,
950
+ remoteSearch: !!asyncState
951
+ });
952
+ const searchValue = asyncState ? asyncState.searchQuery : state.searchQuery;
953
+ const setSearchValue = asyncState ? asyncState.setSearchQuery : state.setSearchQuery;
954
+ function renderRow(option) {
955
+ return /* @__PURE__ */ jsx(
956
+ SingleSelectRow$1,
957
+ {
958
+ name: option.label,
959
+ selected: state.selectedValue === option.value,
960
+ onSelect: () => {
961
+ state.selectOption(option.value);
962
+ },
963
+ disabled: option.disabled
964
+ },
965
+ String(option.value)
966
+ );
967
+ }
968
+ function renderOptions() {
969
+ if (asyncState?.loading) {
970
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-center gap-2 py-6", children: [
971
+ /* @__PURE__ */ jsx(LoaderCircle, { className: "size-4 animate-spin text-muted-foreground" }),
972
+ /* @__PURE__ */ jsx("span", { className: "text-sm text-muted-foreground", children: "Loading..." })
973
+ ] });
974
+ }
975
+ if (state.filteredOptions.length === 0) {
976
+ return /* @__PURE__ */ jsx(SingleSelectEmpty$1, {});
977
+ }
978
+ if (!state.grouped) {
979
+ return state.filteredOptions.map(renderRow);
980
+ }
981
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
982
+ state.grouped.ungrouped.map(renderRow),
983
+ state.grouped.entries.map((entry) => /* @__PURE__ */ jsxs(SingleSelectGroup$1, { children: [
984
+ /* @__PURE__ */ jsx(SingleSelectGroupLabel$1, { children: entry.name }),
985
+ entry.options.map(renderRow)
986
+ ] }, entry.name))
987
+ ] });
988
+ }
989
+ const triggerText = state.selectedOption ? `${label} = ${state.selectedOption.label}` : label ?? placeholder;
990
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
991
+ /* @__PURE__ */ jsxs(SingleSelectRoot$1, { open: state.open, onOpenChange: state.setOpen, disabled, children: [
992
+ /* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(
993
+ "button",
994
+ {
995
+ ref,
996
+ id,
997
+ type: "button",
998
+ role: "combobox",
999
+ "aria-expanded": state.open,
1000
+ "aria-haspopup": "listbox",
1001
+ "aria-controls": state.listboxId,
1002
+ "aria-required": required,
1003
+ disabled,
1004
+ onBlur,
1005
+ className: cn(
1006
+ "inline-flex items-center gap-2 rounded-md border px-3 py-2 text-sm whitespace-nowrap shadow-xs outline-none transition-colors disabled:cursor-not-allowed disabled:opacity-50",
1007
+ state.selectedOption ? "bg-primary text-primary-foreground border-primary hover:bg-primary/90" : "border-input bg-transparent hover:bg-accent hover:text-accent-foreground",
1008
+ className
1009
+ ),
1010
+ children: [
1011
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: triggerText }),
1012
+ /* @__PURE__ */ jsx(ChevronDown, { className: "size-4 shrink-0 opacity-50" })
1013
+ ]
1014
+ }
1015
+ ) }),
1016
+ /* @__PURE__ */ jsxs(SingleSelectContent$1, { className: "w-[250px]", children: [
1017
+ /* @__PURE__ */ jsx(SingleSelectSearch$1, { value: searchValue, onValueChange: setSearchValue, placeholder: searchPlaceholder }),
1018
+ /* @__PURE__ */ jsxs(SingleSelectList$1, { id: state.listboxId, children: [
1019
+ renderOptions(),
1020
+ asyncState?.hasMore && /* @__PURE__ */ jsx("div", { ref: asyncState.sentinelRef, className: "h-1" }),
1021
+ asyncState?.loadingMore && /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-center gap-2 py-2", children: [
1022
+ /* @__PURE__ */ jsx(LoaderCircle, { className: "size-3 animate-spin text-muted-foreground" }),
1023
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: "Loading more..." })
1024
+ ] })
1025
+ ] }),
1026
+ /* @__PURE__ */ jsx(SingleSelectClear$1, { onClear: state.clearSelection, disabled: !state.selectedValue })
1027
+ ] })
1028
+ ] }),
1029
+ name && state.selectedValue && /* @__PURE__ */ jsx("input", { type: "hidden", name, value: String(state.selectedValue) })
1030
+ ] });
1031
+ }
1032
+ );
1033
+ SingleSelectFilter.displayName = "SingleSelectFilter";
1034
+
1035
+ // src/observe.ts
1036
+ var observerMap = /* @__PURE__ */ new Map();
1037
+ var RootIds = /* @__PURE__ */ new WeakMap();
1038
+ var rootId = 0;
1039
+ var unsupportedValue;
1040
+ function getRootId(root) {
1041
+ if (!root) return "0";
1042
+ if (RootIds.has(root)) return RootIds.get(root);
1043
+ rootId += 1;
1044
+ RootIds.set(root, rootId.toString());
1045
+ return RootIds.get(root);
1046
+ }
1047
+ function optionsToId(options) {
1048
+ return Object.keys(options).sort().filter(
1049
+ (key) => options[key] !== void 0
1050
+ ).map((key) => {
1051
+ return `${key}_${key === "root" ? getRootId(options.root) : options[key]}`;
1052
+ }).toString();
1053
+ }
1054
+ function createObserver(options) {
1055
+ const id = optionsToId(options);
1056
+ let instance = observerMap.get(id);
1057
+ if (!instance) {
1058
+ const elements = /* @__PURE__ */ new Map();
1059
+ let thresholds;
1060
+ const observer = new IntersectionObserver((entries) => {
1061
+ entries.forEach((entry) => {
1062
+ var _a2;
1063
+ const inView = entry.isIntersecting && thresholds.some((threshold) => entry.intersectionRatio >= threshold);
1064
+ if (options.trackVisibility && typeof entry.isVisible === "undefined") {
1065
+ entry.isVisible = inView;
1066
+ }
1067
+ (_a2 = elements.get(entry.target)) == null ? void 0 : _a2.forEach((callback) => {
1068
+ callback(inView, entry);
1069
+ });
1070
+ });
1071
+ }, options);
1072
+ thresholds = observer.thresholds || (Array.isArray(options.threshold) ? options.threshold : [options.threshold || 0]);
1073
+ instance = {
1074
+ id,
1075
+ observer,
1076
+ elements
1077
+ };
1078
+ observerMap.set(id, instance);
1079
+ }
1080
+ return instance;
1081
+ }
1082
+ function observe(element, callback, options = {}, fallbackInView = unsupportedValue) {
1083
+ if (typeof window.IntersectionObserver === "undefined" && fallbackInView !== void 0) {
1084
+ const bounds = element.getBoundingClientRect();
1085
+ callback(fallbackInView, {
1086
+ isIntersecting: fallbackInView,
1087
+ target: element,
1088
+ intersectionRatio: typeof options.threshold === "number" ? options.threshold : 0,
1089
+ time: 0,
1090
+ boundingClientRect: bounds,
1091
+ intersectionRect: bounds,
1092
+ rootBounds: bounds
1093
+ });
1094
+ return () => {
1095
+ };
1096
+ }
1097
+ const { id, observer, elements } = createObserver(options);
1098
+ const callbacks = elements.get(element) || [];
1099
+ if (!elements.has(element)) {
1100
+ elements.set(element, callbacks);
1101
+ }
1102
+ callbacks.push(callback);
1103
+ observer.observe(element);
1104
+ return function unobserve() {
1105
+ callbacks.splice(callbacks.indexOf(callback), 1);
1106
+ if (callbacks.length === 0) {
1107
+ elements.delete(element);
1108
+ observer.unobserve(element);
1109
+ }
1110
+ if (elements.size === 0) {
1111
+ observer.disconnect();
1112
+ observerMap.delete(id);
1113
+ }
1114
+ };
1115
+ }
1116
+ function useInView({
1117
+ threshold,
1118
+ delay,
1119
+ trackVisibility,
1120
+ rootMargin,
1121
+ root,
1122
+ triggerOnce,
1123
+ skip,
1124
+ initialInView,
1125
+ fallbackInView,
1126
+ onChange
1127
+ } = {}) {
1128
+ var _a2;
1129
+ const [ref, setRef] = React.useState(null);
1130
+ const callback = React.useRef(onChange);
1131
+ const lastInViewRef = React.useRef(initialInView);
1132
+ const [state, setState] = React.useState({
1133
+ inView: !!initialInView,
1134
+ entry: void 0
1135
+ });
1136
+ callback.current = onChange;
1137
+ React.useEffect(
1138
+ () => {
1139
+ if (lastInViewRef.current === void 0) {
1140
+ lastInViewRef.current = initialInView;
1141
+ }
1142
+ if (skip || !ref) return;
1143
+ let unobserve;
1144
+ unobserve = observe(
1145
+ ref,
1146
+ (inView, entry) => {
1147
+ const previousInView = lastInViewRef.current;
1148
+ lastInViewRef.current = inView;
1149
+ if (previousInView === void 0 && !inView) {
1150
+ return;
1151
+ }
1152
+ setState({
1153
+ inView,
1154
+ entry
1155
+ });
1156
+ if (callback.current) callback.current(inView, entry);
1157
+ if (entry.isIntersecting && triggerOnce && unobserve) {
1158
+ unobserve();
1159
+ unobserve = void 0;
1160
+ }
1161
+ },
1162
+ {
1163
+ root,
1164
+ rootMargin,
1165
+ threshold,
1166
+ // @ts-expect-error
1167
+ trackVisibility,
1168
+ delay
1169
+ },
1170
+ fallbackInView
1171
+ );
1172
+ return () => {
1173
+ if (unobserve) {
1174
+ unobserve();
1175
+ }
1176
+ };
1177
+ },
1178
+ // We break the rule here, because we aren't including the actual `threshold` variable
1179
+ // eslint-disable-next-line react-hooks/exhaustive-deps
1180
+ [
1181
+ // If the threshold is an array, convert it to a string, so it won't change between renders.
1182
+ Array.isArray(threshold) ? threshold.toString() : threshold,
1183
+ ref,
1184
+ root,
1185
+ rootMargin,
1186
+ triggerOnce,
1187
+ skip,
1188
+ trackVisibility,
1189
+ fallbackInView,
1190
+ delay
1191
+ ]
1192
+ );
1193
+ const entryTarget = (_a2 = state.entry) == null ? void 0 : _a2.target;
1194
+ const previousEntryTarget = React.useRef(void 0);
1195
+ if (!ref && entryTarget && !triggerOnce && !skip && previousEntryTarget.current !== entryTarget) {
1196
+ previousEntryTarget.current = entryTarget;
1197
+ setState({
1198
+ inView: !!initialInView,
1199
+ entry: void 0
1200
+ });
1201
+ lastInViewRef.current = initialInView;
1202
+ }
1203
+ const result = [setRef, state.inView, state.entry];
1204
+ result.ref = result[0];
1205
+ result.inView = result[1];
1206
+ result.entry = result[2];
1207
+ return result;
1208
+ }
1209
+
1210
+ const NOOP_REF = () => {
1211
+ };
1212
+ function stableStringify(obj) {
1213
+ if (!obj) return "";
1214
+ return JSON.stringify(obj, Object.keys(obj).sort());
1215
+ }
1216
+ function useSelect({
1217
+ options: staticOptions,
1218
+ groups: staticGroups,
1219
+ optionsEndpoint,
1220
+ searchDebounceMs = 300,
1221
+ limit = 20,
1222
+ fieldKeys,
1223
+ params,
1224
+ selectedValues
1225
+ }) {
1226
+ const isAsync = !!optionsEndpoint;
1227
+ const instanceId = useId();
1228
+ const [searchQuery, setSearchQuery] = useState("");
1229
+ const [debouncedSearch, setDebouncedSearch] = useState("");
1230
+ const debounceRef = useRef(null);
1231
+ useEffect(() => {
1232
+ if (!isAsync) return;
1233
+ if (debounceRef.current) clearTimeout(debounceRef.current);
1234
+ debounceRef.current = setTimeout(() => {
1235
+ setDebouncedSearch(searchQuery);
1236
+ }, searchDebounceMs);
1237
+ return () => {
1238
+ if (debounceRef.current) clearTimeout(debounceRef.current);
1239
+ };
1240
+ }, [searchQuery, searchDebounceMs, isAsync]);
1241
+ const serializedValues = useMemo(
1242
+ () => selectedValues && selectedValues.length > 0 ? selectedValues.filter((v) => typeof v !== "boolean").join(",") : void 0,
1243
+ [selectedValues]
1244
+ );
1245
+ const { data: resolvedSelected } = useQuery({
1246
+ queryKey: ["select-resolve", instanceId, optionsEndpoint, JSON.stringify(selectedValues)],
1247
+ queryFn: () => axios.get(optionsEndpoint ?? "", {
1248
+ params: { values: serializedValues, ...fieldKeys, ...params },
1249
+ showSuccessToast: false,
1250
+ showErrorToast: false
1251
+ }).then((r) => r.data),
1252
+ enabled: isAsync && (selectedValues?.length ?? 0) > 0,
1253
+ staleTime: 5 * 6e4,
1254
+ placeholderData: keepPreviousData
1255
+ });
1256
+ const { data, isFetching, isFetchingNextPage, fetchNextPage, hasNextPage } = useInfiniteQuery({
1257
+ queryKey: [
1258
+ "select-search",
1259
+ instanceId,
1260
+ optionsEndpoint,
1261
+ debouncedSearch,
1262
+ stableStringify(fieldKeys),
1263
+ stableStringify(params)
1264
+ ],
1265
+ queryFn: ({ pageParam = 0 }) => axios.get(optionsEndpoint ?? "", {
1266
+ params: {
1267
+ search: debouncedSearch || void 0,
1268
+ limit,
1269
+ offset: pageParam,
1270
+ excludeIds: serializedValues,
1271
+ ...fieldKeys,
1272
+ ...params
1273
+ },
1274
+ showSuccessToast: false,
1275
+ showErrorToast: false
1276
+ }).then((r) => r.data),
1277
+ getNextPageParam: (lastPage, _allPages, lastPageParam) => lastPage.hasMore ? lastPageParam + limit : void 0,
1278
+ initialPageParam: 0,
1279
+ enabled: isAsync,
1280
+ placeholderData: keepPreviousData
1281
+ });
1282
+ const fetchedOptions = useMemo(() => {
1283
+ const searchResults = data?.pages.flatMap((p) => p.options) ?? [];
1284
+ const selected = resolvedSelected?.options ?? [];
1285
+ if (selected.length === 0) return searchResults;
1286
+ const selectedIdSet = new Set(selected.map((o) => o.value));
1287
+ const dedupedSearch = searchResults.filter((o) => !selectedIdSet.has(o.value));
1288
+ return [...selected, ...dedupedSearch];
1289
+ }, [resolvedSelected, data]);
1290
+ const fetchedGroups = data?.pages[0]?.groups ?? staticGroups ?? [];
1291
+ const { ref: sentinelRef, inView } = useInView();
1292
+ useEffect(() => {
1293
+ if (inView && hasNextPage && !isFetchingNextPage) {
1294
+ fetchNextPage();
1295
+ }
1296
+ }, [inView, hasNextPage, isFetchingNextPage, fetchNextPage]);
1297
+ if (!isAsync) {
1298
+ return {
1299
+ options: staticOptions ?? [],
1300
+ groups: staticGroups ?? [],
1301
+ loading: false,
1302
+ loadingMore: false,
1303
+ hasMore: false,
1304
+ searchQuery,
1305
+ setSearchQuery,
1306
+ sentinelRef: NOOP_REF
1307
+ };
1308
+ }
1309
+ return {
1310
+ options: fetchedOptions,
1311
+ groups: fetchedGroups,
1312
+ loading: isFetching && !isFetchingNextPage,
1313
+ loadingMore: isFetchingNextPage,
1314
+ hasMore: hasNextPage ?? false,
1315
+ searchQuery,
1316
+ setSearchQuery,
1317
+ sentinelRef
1318
+ };
1319
+ }
1320
+
1321
+ const Select = React.forwardRef((props, ref) => {
1322
+ const { multiple, type = "default", optionsEndpoint, searchDebounceMs, limit, fieldKeys, params, ...rest } = props;
1323
+ const selectData = useSelect({
1324
+ options: rest.options,
1325
+ groups: rest.groups,
1326
+ optionsEndpoint,
1327
+ searchDebounceMs,
1328
+ limit,
1329
+ fieldKeys,
1330
+ params,
1331
+ selectedValues: rest.value != null ? Array.isArray(rest.value) ? rest.value : [rest.value] : void 0
1332
+ });
1333
+ const isAsync = !!optionsEndpoint;
1334
+ const asyncState = isAsync ? {
1335
+ loading: selectData.loading,
1336
+ loadingMore: selectData.loadingMore,
1337
+ hasMore: selectData.hasMore,
1338
+ searchQuery: selectData.searchQuery,
1339
+ setSearchQuery: selectData.setSearchQuery,
1340
+ sentinelRef: selectData.sentinelRef
1341
+ } : void 0;
1342
+ const childProps = {
1343
+ ...rest,
1344
+ options: selectData.options,
1345
+ groups: selectData.groups,
1346
+ asyncState
1347
+ };
1348
+ if (multiple) {
1349
+ if (type === "filter") return /* @__PURE__ */ jsx(MultiSelectFilter, { ref, ...childProps });
1350
+ return /* @__PURE__ */ jsx(MultiSelect, { ref, ...childProps });
1351
+ }
1352
+ if (type === "filter") return /* @__PURE__ */ jsx(SingleSelectFilter, { ref, ...childProps });
1353
+ return /* @__PURE__ */ jsx(SingleSelect, { ref, ...childProps });
1354
+ });
1355
+ Select.displayName = "Select";
1356
+
1357
+ const MultiSelectRoot = MultiSelectRoot$1;
1358
+ const MultiSelectTrigger = MultiSelectTrigger$1;
1359
+ const MultiSelectContent = MultiSelectContent$1;
1360
+ const MultiSelectSearch = MultiSelectSearch$1;
1361
+ const MultiSelectActions = MultiSelectActions$1;
1362
+ const MultiSelectGroup = MultiSelectGroup$1;
1363
+ const MultiSelectGroupLabel = MultiSelectGroupLabel$1;
1364
+ const MultiSelectList = MultiSelectList$1;
1365
+ const MultiSelectRow = MultiSelectRow$1;
1366
+ const MultiSelectEmpty = MultiSelectEmpty$1;
1367
+ const SingleSelectRoot = SingleSelectRoot$1;
1368
+ const SingleSelectTrigger = SingleSelectTrigger$1;
1369
+ const SingleSelectContent = SingleSelectContent$1;
1370
+ const SingleSelectSearch = SingleSelectSearch$1;
1371
+ const SingleSelectClear = SingleSelectClear$1;
1372
+ const SingleSelectGroup = SingleSelectGroup$1;
1373
+ const SingleSelectGroupLabel = SingleSelectGroupLabel$1;
1374
+ const SingleSelectList = SingleSelectList$1;
1375
+ const SingleSelectRow = SingleSelectRow$1;
1376
+ const SingleSelectEmpty = SingleSelectEmpty$1;
1377
+
1378
+ export { MultiSelect, MultiSelectActions, MultiSelectContent, MultiSelectEmpty, MultiSelectFilter, MultiSelectGroup, MultiSelectGroupLabel, MultiSelectList, MultiSelectRoot, MultiSelectRow, MultiSelectSearch, MultiSelectTrigger, Select, SingleSelect, SingleSelectClear, SingleSelectContent, SingleSelectEmpty, SingleSelectFilter, SingleSelectGroup, SingleSelectGroupLabel, SingleSelectList, SingleSelectRoot, SingleSelectRow, SingleSelectSearch, SingleSelectTrigger, useSelect };
1379
+ //# sourceMappingURL=Select.js.map