@kopexa/filter 0.0.24 → 0.0.26

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 (51) hide show
  1. package/dist/chunk-6TRAIAKS.mjs +178 -0
  2. package/dist/chunk-I7WCYMXD.mjs +133 -0
  3. package/dist/{chunk-45QJL74L.mjs → chunk-JSRGUDCG.mjs} +49 -24
  4. package/dist/chunk-NUDUXOHP.mjs +243 -0
  5. package/dist/chunk-ON2UFJ3Y.mjs +32 -0
  6. package/dist/{chunk-RFCPJLIQ.mjs → chunk-SJXRD3RO.mjs} +6 -1
  7. package/dist/{chunk-EF4VI36D.mjs → chunk-UBTUCPOG.mjs} +1 -1
  8. package/dist/{chunk-3ZBNWXRA.mjs → chunk-WD7YU6IN.mjs} +17 -14
  9. package/dist/chunk-XCWKWXBW.mjs +602 -0
  10. package/dist/{chunk-URDCG5NI.mjs → chunk-YTYOFT33.mjs} +52 -0
  11. package/dist/filter-active.js +108 -28
  12. package/dist/filter-active.mjs +4 -4
  13. package/dist/filter-bar-internal.d.mts +25 -0
  14. package/dist/filter-bar-internal.d.ts +25 -0
  15. package/dist/filter-bar-internal.js +648 -0
  16. package/dist/filter-bar-internal.mjs +11 -0
  17. package/dist/filter-bar-messages.d.mts +124 -0
  18. package/dist/filter-bar-messages.d.ts +124 -0
  19. package/dist/filter-bar-messages.js +156 -0
  20. package/dist/filter-bar-messages.mjs +7 -0
  21. package/dist/filter-bar-types.d.mts +112 -0
  22. package/dist/filter-bar-types.d.ts +112 -0
  23. package/dist/filter-bar-types.js +56 -0
  24. package/dist/filter-bar-types.mjs +8 -0
  25. package/dist/filter-bar.d.mts +29 -0
  26. package/dist/filter-bar.d.ts +29 -0
  27. package/dist/filter-bar.js +942 -0
  28. package/dist/filter-bar.mjs +11 -0
  29. package/dist/filter-menu.js +161 -1
  30. package/dist/filter-menu.mjs +2 -1
  31. package/dist/filter-trigger.js +52 -0
  32. package/dist/filter-trigger.mjs +2 -2
  33. package/dist/filter-value-editor.js +65 -10
  34. package/dist/filter-value-editor.mjs +3 -3
  35. package/dist/filter.d.mts +6 -0
  36. package/dist/filter.d.ts +6 -0
  37. package/dist/filter.mjs +2 -2
  38. package/dist/index.d.mts +4 -0
  39. package/dist/index.d.ts +4 -0
  40. package/dist/index.js +1263 -31
  41. package/dist/index.mjs +28 -14
  42. package/dist/messages.d.mts +50 -0
  43. package/dist/messages.d.ts +50 -0
  44. package/dist/messages.js +52 -0
  45. package/dist/messages.mjs +1 -1
  46. package/dist/search-filter-bar.d.mts +44 -0
  47. package/dist/search-filter-bar.d.ts +44 -0
  48. package/dist/search-filter-bar.js +1007 -0
  49. package/dist/search-filter-bar.mjs +11 -0
  50. package/package.json +19 -16
  51. package/dist/{chunk-SH7DBK54.mjs → chunk-LWDVRMCI.mjs} +3 -3
@@ -0,0 +1,602 @@
1
+ "use client";
2
+ import {
3
+ DEFAULT_FILTER_BAR_OPERATORS
4
+ } from "./chunk-ON2UFJ3Y.mjs";
5
+
6
+ // src/filter-bar-internal.tsx
7
+ import { Button } from "@kopexa/button";
8
+ import { Checkbox } from "@kopexa/checkbox";
9
+ import { Command } from "@kopexa/command";
10
+ import { DropdownMenu } from "@kopexa/dropdown-menu";
11
+ import { CheckIcon, ChevronDownIcon, CloseIcon, PlusIcon } from "@kopexa/icons";
12
+ import { Input } from "@kopexa/input";
13
+ import { Popover } from "@kopexa/popover";
14
+ import { cn } from "@kopexa/shared-utils";
15
+ import {
16
+ useCallback,
17
+ useMemo,
18
+ useRef,
19
+ useState
20
+ } from "react";
21
+ import { jsx, jsxs } from "react/jsx-runtime";
22
+ function AddFilterDropdown(props) {
23
+ const { fieldGroups, isFieldAvailable, onAddFilter, i18n, disabled } = props;
24
+ return /* @__PURE__ */ jsxs(DropdownMenu.Root, { children: [
25
+ /* @__PURE__ */ jsx(DropdownMenu.Trigger, { asChild: true, children: /* @__PURE__ */ jsx(
26
+ Button,
27
+ {
28
+ variant: "outline",
29
+ size: "sm",
30
+ className: "rounded-full border-dashed",
31
+ disabled,
32
+ startContent: /* @__PURE__ */ jsx(PlusIcon, { className: "size-4" }),
33
+ children: i18n.addFilter
34
+ }
35
+ ) }),
36
+ /* @__PURE__ */ jsx(DropdownMenu.Content, { align: "start", className: "min-w-[200px]", children: Array.from(fieldGroups.entries()).map(([groupLabel, groupFields]) => /* @__PURE__ */ jsxs(DropdownMenu.Group, { children: [
37
+ groupLabel && /* @__PURE__ */ jsx(DropdownMenu.Label, { children: groupLabel }),
38
+ groupFields.map((field) => {
39
+ const available = isFieldAvailable(field.id);
40
+ return /* @__PURE__ */ jsxs(
41
+ DropdownMenu.Item,
42
+ {
43
+ disabled: field.disabled || !available,
44
+ onSelect: () => onAddFilter(field.id),
45
+ children: [
46
+ field.icon && /* @__PURE__ */ jsx("span", { className: "size-4 text-muted-foreground mr-2", children: field.icon }),
47
+ /* @__PURE__ */ jsx("span", { children: field.label })
48
+ ]
49
+ },
50
+ field.id
51
+ );
52
+ })
53
+ ] }, groupLabel != null ? groupLabel : "ungrouped")) })
54
+ ] });
55
+ }
56
+ function FilterPill(props) {
57
+ var _a;
58
+ const {
59
+ filter,
60
+ field,
61
+ i18n,
62
+ onUpdate,
63
+ onRemove,
64
+ autoOpen,
65
+ onAutoOpenComplete,
66
+ disabled
67
+ } = props;
68
+ const [open, setOpen] = useState(autoOpen != null ? autoOpen : false);
69
+ const operators = (_a = field.operators) != null ? _a : DEFAULT_FILTER_BAR_OPERATORS[field.type];
70
+ const isEmptyOperator = filter.operator === "is_empty" || filter.operator === "is_not_empty";
71
+ const hasCustomRenderer = !!field.customRenderer;
72
+ const useInlineInput = !hasCustomRenderer && (field.type === "text" || field.type === "number");
73
+ const usePopover = !useInlineInput && !isEmptyOperator;
74
+ const handleOpenChange = useCallback(
75
+ (isOpen) => {
76
+ setOpen(isOpen);
77
+ if (!isOpen && autoOpen) {
78
+ onAutoOpenComplete == null ? void 0 : onAutoOpenComplete();
79
+ }
80
+ },
81
+ [autoOpen, onAutoOpenComplete]
82
+ );
83
+ const valueDisplay = useMemo(() => {
84
+ var _a2, _b;
85
+ if (isEmptyOperator) return null;
86
+ if (filter.value === null || filter.value === "") return null;
87
+ if (field.type === "select" && field.options) {
88
+ const option = field.options.find((o) => o.value === filter.value);
89
+ return (_a2 = option == null ? void 0 : option.label) != null ? _a2 : String(filter.value);
90
+ }
91
+ if (field.type === "multiselect") {
92
+ const values = Array.isArray(filter.value) ? filter.value.filter(
93
+ (v) => typeof v === "string" && v.length > 1
94
+ ) : [];
95
+ if (values.length === 0) return null;
96
+ if (values.length === 1 && field.options) {
97
+ const option = field.options.find((o) => o.value === values[0]);
98
+ return (_b = option == null ? void 0 : option.label) != null ? _b : values[0];
99
+ }
100
+ return `${values.length} selected`;
101
+ }
102
+ return String(filter.value);
103
+ }, [filter.value, field, isEmptyOperator]);
104
+ return /* @__PURE__ */ jsxs(
105
+ "div",
106
+ {
107
+ "data-slot": "filter-pill",
108
+ className: cn(
109
+ "group inline-flex items-center rounded-full overflow-hidden",
110
+ "border border-border bg-background",
111
+ "transition-all hover:shadow-sm focus-within:ring-2 focus-within:ring-ring focus-within:ring-offset-1"
112
+ ),
113
+ children: [
114
+ /* @__PURE__ */ jsxs(
115
+ "span",
116
+ {
117
+ className: cn(
118
+ "bg-muted text-foreground font-medium text-xs",
119
+ "flex items-center gap-1.5 flex-shrink-0",
120
+ "rounded-l-full px-3 py-1.5"
121
+ ),
122
+ children: [
123
+ field.icon && /* @__PURE__ */ jsx("span", { className: "size-4 flex-shrink-0", children: field.icon }),
124
+ /* @__PURE__ */ jsx("span", { children: field.label })
125
+ ]
126
+ }
127
+ ),
128
+ /* @__PURE__ */ jsx(
129
+ OperatorDropdown,
130
+ {
131
+ operator: filter.operator,
132
+ operators,
133
+ i18n,
134
+ onChange: (op) => {
135
+ const newUpdates = { operator: op };
136
+ if (op === "is_empty" || op === "is_not_empty") {
137
+ newUpdates.value = null;
138
+ }
139
+ onUpdate(newUpdates);
140
+ },
141
+ disabled
142
+ }
143
+ ),
144
+ useInlineInput && !isEmptyOperator && /* @__PURE__ */ jsx(
145
+ InlineValueInput,
146
+ {
147
+ filter,
148
+ field,
149
+ i18n,
150
+ onUpdate,
151
+ disabled
152
+ }
153
+ ),
154
+ usePopover && /* @__PURE__ */ jsxs(Popover.Root, { open, onOpenChange: handleOpenChange, children: [
155
+ /* @__PURE__ */ jsxs(
156
+ Popover.Trigger,
157
+ {
158
+ className: cn(
159
+ "bg-background text-foreground text-xs",
160
+ "flex items-center gap-1 px-2.5 py-1.5",
161
+ "cursor-pointer hover:bg-muted/30"
162
+ ),
163
+ nativeButton: false,
164
+ render: /* @__PURE__ */ jsx("div", {}),
165
+ children: [
166
+ /* @__PURE__ */ jsx("span", { className: "truncate max-w-[150px]", children: valueDisplay != null ? valueDisplay : i18n.selectValue }),
167
+ /* @__PURE__ */ jsx(ChevronDownIcon, { className: "size-3.5 opacity-60 flex-shrink-0" })
168
+ ]
169
+ }
170
+ ),
171
+ /* @__PURE__ */ jsx(
172
+ Popover.Content,
173
+ {
174
+ "data-slot": "filter-editor",
175
+ className: "p-0 w-[280px]",
176
+ align: "start",
177
+ showArrow: false,
178
+ children: /* @__PURE__ */ jsx(
179
+ ValueEditor,
180
+ {
181
+ filter,
182
+ field,
183
+ i18n,
184
+ onUpdate,
185
+ onClose: () => handleOpenChange(false)
186
+ }
187
+ )
188
+ }
189
+ )
190
+ ] }),
191
+ /* @__PURE__ */ jsx(
192
+ "button",
193
+ {
194
+ type: "button",
195
+ onClick: onRemove,
196
+ className: cn(
197
+ "size-5 rounded-full flex items-center justify-center flex-shrink-0",
198
+ "hover:bg-destructive/10 hover:text-destructive",
199
+ "transition-colors cursor-pointer mr-1"
200
+ ),
201
+ disabled,
202
+ "aria-label": `Remove ${field.label} filter`,
203
+ children: /* @__PURE__ */ jsx(CloseIcon, { className: "size-3" })
204
+ }
205
+ )
206
+ ]
207
+ }
208
+ );
209
+ }
210
+ function OperatorDropdown(props) {
211
+ const { operator, operators, i18n, onChange, disabled } = props;
212
+ const hasSingleOperator = operators.length === 1;
213
+ const currentLabel = i18n.operators[operator];
214
+ if (hasSingleOperator) {
215
+ return /* @__PURE__ */ jsx(
216
+ "span",
217
+ {
218
+ className: cn(
219
+ "bg-primary text-primary-foreground",
220
+ "flex items-center px-3 py-0.5",
221
+ "rounded-full shadow-sm mx-1 my-0.5",
222
+ "text-xs font-normal"
223
+ ),
224
+ children: currentLabel
225
+ }
226
+ );
227
+ }
228
+ return /* @__PURE__ */ jsxs(DropdownMenu.Root, { children: [
229
+ /* @__PURE__ */ jsx(DropdownMenu.Trigger, { asChild: true, disabled, children: /* @__PURE__ */ jsxs(
230
+ "button",
231
+ {
232
+ type: "button",
233
+ className: cn(
234
+ "bg-primary text-primary-foreground",
235
+ "flex items-center gap-1 px-3 py-0.5",
236
+ "rounded-full shadow-sm mx-1 my-0.5",
237
+ "text-xs font-normal cursor-pointer",
238
+ "hover:bg-primary/90 transition-colors"
239
+ ),
240
+ children: [
241
+ currentLabel,
242
+ /* @__PURE__ */ jsx(ChevronDownIcon, { className: "size-3 opacity-70" })
243
+ ]
244
+ }
245
+ ) }),
246
+ /* @__PURE__ */ jsx(DropdownMenu.Content, { align: "start", className: "min-w-[140px]", children: operators.map((op) => /* @__PURE__ */ jsxs(
247
+ DropdownMenu.Item,
248
+ {
249
+ onSelect: () => onChange(op),
250
+ className: "gap-2",
251
+ children: [
252
+ op === operator && /* @__PURE__ */ jsx(CheckIcon, { className: "size-4" }),
253
+ /* @__PURE__ */ jsx("span", { className: op !== operator ? "pl-6" : "", children: i18n.operators[op] })
254
+ ]
255
+ },
256
+ op
257
+ )) })
258
+ ] });
259
+ }
260
+ function getInputWidth(value, placeholder, minWidth = 4) {
261
+ const displayValue = value !== void 0 && value !== null && value !== "" ? String(value) : placeholder;
262
+ const width = Math.max(displayValue.length + 1, minWidth);
263
+ return `${width}ch`;
264
+ }
265
+ function InlineValueInput(props) {
266
+ var _a;
267
+ const { filter, field, i18n, onUpdate, disabled } = props;
268
+ const placeholder = (_a = field.placeholder) != null ? _a : i18n.enterValue;
269
+ const handleChange = (e) => {
270
+ const val = e.target.value;
271
+ if (field.type === "number") {
272
+ onUpdate({ value: val ? Number(val) : null });
273
+ } else {
274
+ onUpdate({ value: val });
275
+ }
276
+ };
277
+ return /* @__PURE__ */ jsx(
278
+ "span",
279
+ {
280
+ className: cn(
281
+ "bg-background text-foreground text-xs",
282
+ "flex items-center gap-1 px-2.5 py-1.5"
283
+ ),
284
+ children: /* @__PURE__ */ jsx(
285
+ "input",
286
+ {
287
+ type: field.type === "number" ? "number" : "text",
288
+ className: cn(
289
+ "bg-transparent border-none outline-none",
290
+ "placeholder:text-muted-foreground",
291
+ "text-xs"
292
+ ),
293
+ value: filter.value !== void 0 && filter.value !== null ? String(filter.value) : "",
294
+ onChange: handleChange,
295
+ placeholder,
296
+ "aria-label": `${field.label} value`,
297
+ style: { width: getInputWidth(filter.value, placeholder, 6) },
298
+ min: field.min,
299
+ max: field.max,
300
+ step: field.step,
301
+ disabled
302
+ }
303
+ )
304
+ }
305
+ );
306
+ }
307
+ function ValueEditor(props) {
308
+ var _a, _b, _c, _d;
309
+ const { filter, field, i18n, onUpdate, onClose } = props;
310
+ if (field.customRenderer) {
311
+ return /* @__PURE__ */ jsx("div", { className: "p-3", children: field.customRenderer({
312
+ field,
313
+ value: filter.value,
314
+ onChange: (value) => onUpdate({ value })
315
+ }) });
316
+ }
317
+ if (field.type === "select" && field.options) {
318
+ return /* @__PURE__ */ jsx(
319
+ SelectEditor,
320
+ {
321
+ options: field.options,
322
+ value: filter.value,
323
+ onChange: (value) => {
324
+ onUpdate({ value });
325
+ onClose();
326
+ },
327
+ i18n
328
+ }
329
+ );
330
+ }
331
+ if (field.type === "multiselect" && field.options) {
332
+ const currentValue = Array.isArray(filter.value) ? filter.value.filter(
333
+ (v) => typeof v === "string" && v.length > 1
334
+ ) : [];
335
+ return /* @__PURE__ */ jsx(
336
+ MultiselectEditor,
337
+ {
338
+ options: field.options,
339
+ value: currentValue,
340
+ onChange: (value) => onUpdate({ value }),
341
+ i18n
342
+ }
343
+ );
344
+ }
345
+ if (field.type === "text") {
346
+ return /* @__PURE__ */ jsx(
347
+ TextEditor,
348
+ {
349
+ value: (_a = filter.value) != null ? _a : "",
350
+ onChange: (value) => onUpdate({ value }),
351
+ placeholder: (_b = field.placeholder) != null ? _b : i18n.enterValue,
352
+ validation: field.validation,
353
+ onClose
354
+ }
355
+ );
356
+ }
357
+ if (field.type === "number") {
358
+ return /* @__PURE__ */ jsx(
359
+ NumberEditor,
360
+ {
361
+ value: (_c = filter.value) != null ? _c : null,
362
+ onChange: (value) => onUpdate({ value }),
363
+ placeholder: (_d = field.placeholder) != null ? _d : i18n.enterValue,
364
+ min: field.min,
365
+ max: field.max,
366
+ step: field.step,
367
+ onClose
368
+ }
369
+ );
370
+ }
371
+ return null;
372
+ }
373
+ function SelectEditor(props) {
374
+ const { options = [], value, onChange, i18n } = props;
375
+ const [search, setSearch] = useState("");
376
+ const filteredOptions = useMemo(() => {
377
+ if (!search) return options;
378
+ const lower = search.toLowerCase();
379
+ return options.filter(
380
+ (o) => o.label.toLowerCase().includes(lower) || o.value.toLowerCase().includes(lower)
381
+ );
382
+ }, [options, search]);
383
+ return /* @__PURE__ */ jsxs(Command.Root, { shouldFilter: false, children: [
384
+ /* @__PURE__ */ jsx(
385
+ Command.Input,
386
+ {
387
+ placeholder: i18n.searchPlaceholder,
388
+ value: search,
389
+ onValueChange: setSearch
390
+ }
391
+ ),
392
+ /* @__PURE__ */ jsxs(Command.List, { className: "max-h-[300px]", children: [
393
+ /* @__PURE__ */ jsx(Command.Empty, { children: i18n.noResults }),
394
+ filteredOptions.map((option) => /* @__PURE__ */ jsxs(
395
+ Command.Item,
396
+ {
397
+ value: option.value,
398
+ onSelect: () => onChange(option.value),
399
+ className: "flex items-center gap-2 min-w-0",
400
+ children: [
401
+ option.icon && /* @__PURE__ */ jsx("span", { className: "flex-shrink-0 [&>*]:size-5", children: option.icon }),
402
+ /* @__PURE__ */ jsx("span", { className: "truncate flex-1 min-w-0", children: option.label }),
403
+ value === option.value && /* @__PURE__ */ jsx(CheckIcon, { className: "size-4 flex-shrink-0" })
404
+ ]
405
+ },
406
+ option.value
407
+ ))
408
+ ] })
409
+ ] });
410
+ }
411
+ function MultiselectEditor(props) {
412
+ const { options = [], value, onChange, i18n } = props;
413
+ const [search, setSearch] = useState("");
414
+ const filteredOptions = useMemo(() => {
415
+ if (!search) return options;
416
+ const lower = search.toLowerCase();
417
+ return options.filter(
418
+ (o) => o.label.toLowerCase().includes(lower) || o.value.toLowerCase().includes(lower)
419
+ );
420
+ }, [options, search]);
421
+ const toggleOption = useCallback(
422
+ (optionValue) => {
423
+ if (value.includes(optionValue)) {
424
+ onChange(value.filter((v) => v !== optionValue));
425
+ } else {
426
+ onChange([...value, optionValue]);
427
+ }
428
+ },
429
+ [value, onChange]
430
+ );
431
+ return /* @__PURE__ */ jsxs(Command.Root, { shouldFilter: false, children: [
432
+ /* @__PURE__ */ jsx(
433
+ Command.Input,
434
+ {
435
+ placeholder: i18n.searchPlaceholder,
436
+ value: search,
437
+ onValueChange: setSearch
438
+ }
439
+ ),
440
+ /* @__PURE__ */ jsxs(Command.List, { className: "max-h-[300px]", children: [
441
+ /* @__PURE__ */ jsx(Command.Empty, { children: i18n.noResults }),
442
+ filteredOptions.map((option) => {
443
+ const isSelected = value.includes(option.value);
444
+ return /* @__PURE__ */ jsxs(
445
+ Command.Item,
446
+ {
447
+ value: option.value,
448
+ onSelect: () => toggleOption(option.value),
449
+ className: "flex items-center gap-2 min-w-0",
450
+ children: [
451
+ /* @__PURE__ */ jsx(
452
+ Checkbox,
453
+ {
454
+ checked: isSelected,
455
+ className: "flex-shrink-0 border-border bg-background data-[state=checked]:bg-primary data-[state=checked]:border-primary [&_svg]:text-primary-foreground"
456
+ }
457
+ ),
458
+ option.icon && /* @__PURE__ */ jsx("span", { className: "flex-shrink-0 [&>*]:size-5", children: option.icon }),
459
+ /* @__PURE__ */ jsx("span", { className: "truncate flex-1 min-w-0", children: option.label })
460
+ ]
461
+ },
462
+ option.value
463
+ );
464
+ })
465
+ ] })
466
+ ] });
467
+ }
468
+ function TextEditor(props) {
469
+ const {
470
+ value: initialValue,
471
+ onChange,
472
+ placeholder,
473
+ validation,
474
+ onClose
475
+ } = props;
476
+ const [localValue, setLocalValue] = useState(initialValue);
477
+ const [error, setError] = useState(null);
478
+ const inputRef = useRef(null);
479
+ const validate = useCallback(
480
+ (val) => {
481
+ var _a;
482
+ if (!validation) return true;
483
+ if (validation.pattern) {
484
+ const regex = new RegExp(validation.pattern);
485
+ if (!regex.test(val)) {
486
+ setError((_a = validation.message) != null ? _a : "Invalid format");
487
+ return false;
488
+ }
489
+ }
490
+ if (validation.validate) {
491
+ const result = validation.validate(val);
492
+ if (result !== true) {
493
+ setError(typeof result === "string" ? result : "Invalid value");
494
+ return false;
495
+ }
496
+ }
497
+ setError(null);
498
+ return true;
499
+ },
500
+ [validation]
501
+ );
502
+ const handleSubmit = useCallback(() => {
503
+ if (validate(localValue)) {
504
+ onChange(localValue);
505
+ onClose();
506
+ }
507
+ }, [localValue, validate, onChange, onClose]);
508
+ const handleKeyDown = useCallback(
509
+ (e) => {
510
+ if (e.key === "Enter") {
511
+ e.preventDefault();
512
+ handleSubmit();
513
+ } else if (e.key === "Escape") {
514
+ e.preventDefault();
515
+ onClose();
516
+ }
517
+ },
518
+ [handleSubmit, onClose]
519
+ );
520
+ return /* @__PURE__ */ jsxs("div", { className: "p-3 space-y-2", children: [
521
+ /* @__PURE__ */ jsx(
522
+ Input,
523
+ {
524
+ ref: inputRef,
525
+ type: "text",
526
+ value: localValue,
527
+ onChange: (e) => {
528
+ setLocalValue(e.target.value);
529
+ setError(null);
530
+ },
531
+ onKeyDown: handleKeyDown,
532
+ placeholder,
533
+ className: "w-full",
534
+ autoFocus: true
535
+ }
536
+ ),
537
+ error && /* @__PURE__ */ jsx("p", { className: "text-xs text-destructive", children: error }),
538
+ /* @__PURE__ */ jsxs("div", { className: "flex justify-end gap-2", children: [
539
+ /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", onClick: onClose, children: "Cancel" }),
540
+ /* @__PURE__ */ jsx(Button, { size: "sm", onClick: handleSubmit, children: "Apply" })
541
+ ] })
542
+ ] });
543
+ }
544
+ function NumberEditor(props) {
545
+ const {
546
+ value: initialValue,
547
+ onChange,
548
+ placeholder,
549
+ min,
550
+ max,
551
+ step,
552
+ onClose
553
+ } = props;
554
+ const [localValue, setLocalValue] = useState(
555
+ initialValue !== null ? String(initialValue) : ""
556
+ );
557
+ const inputRef = useRef(null);
558
+ const handleSubmit = useCallback(() => {
559
+ const numValue = localValue === "" ? null : Number(localValue);
560
+ onChange(numValue);
561
+ onClose();
562
+ }, [localValue, onChange, onClose]);
563
+ const handleKeyDown = useCallback(
564
+ (e) => {
565
+ if (e.key === "Enter") {
566
+ e.preventDefault();
567
+ handleSubmit();
568
+ } else if (e.key === "Escape") {
569
+ e.preventDefault();
570
+ onClose();
571
+ }
572
+ },
573
+ [handleSubmit, onClose]
574
+ );
575
+ return /* @__PURE__ */ jsxs("div", { className: "p-3 space-y-2", children: [
576
+ /* @__PURE__ */ jsx(
577
+ Input,
578
+ {
579
+ ref: inputRef,
580
+ type: "number",
581
+ value: localValue,
582
+ onChange: (e) => setLocalValue(e.target.value),
583
+ onKeyDown: handleKeyDown,
584
+ placeholder,
585
+ min,
586
+ max,
587
+ step,
588
+ className: "w-full",
589
+ autoFocus: true
590
+ }
591
+ ),
592
+ /* @__PURE__ */ jsxs("div", { className: "flex justify-end gap-2", children: [
593
+ /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", onClick: onClose, children: "Cancel" }),
594
+ /* @__PURE__ */ jsx(Button, { size: "sm", onClick: handleSubmit, children: "Apply" })
595
+ ] })
596
+ ] });
597
+ }
598
+
599
+ export {
600
+ AddFilterDropdown,
601
+ FilterPill
602
+ };
@@ -103,6 +103,58 @@ var messages = defineMessages({
103
103
  id: "filter.operator.is_not_empty",
104
104
  defaultMessage: "is not empty",
105
105
  description: "Operator: is not empty"
106
+ },
107
+ // Additional UI strings
108
+ loading: {
109
+ id: "filter.loading",
110
+ defaultMessage: "Loading...",
111
+ description: "Loading state text"
112
+ },
113
+ boolean_true: {
114
+ id: "filter.boolean_true",
115
+ defaultMessage: "Yes",
116
+ description: "Boolean true value display"
117
+ },
118
+ boolean_false: {
119
+ id: "filter.boolean_false",
120
+ defaultMessage: "No",
121
+ description: "Boolean false value display"
122
+ },
123
+ range_min: {
124
+ id: "filter.range_min",
125
+ defaultMessage: "Min",
126
+ description: "Minimum value placeholder for range inputs"
127
+ },
128
+ range_max: {
129
+ id: "filter.range_max",
130
+ defaultMessage: "Max",
131
+ description: "Maximum value placeholder for range inputs"
132
+ },
133
+ // Accessibility labels
134
+ remove_filter: {
135
+ id: "filter.remove_filter",
136
+ defaultMessage: "Remove {field} filter",
137
+ description: "Accessibility label for remove filter button"
138
+ },
139
+ operator_label: {
140
+ id: "filter.operator_label",
141
+ defaultMessage: "{field} operator",
142
+ description: "Accessibility label for operator select"
143
+ },
144
+ value_label: {
145
+ id: "filter.value_label",
146
+ defaultMessage: "{field} value",
147
+ description: "Accessibility label for value input"
148
+ },
149
+ value_min_label: {
150
+ id: "filter.value_min_label",
151
+ defaultMessage: "{field} minimum value",
152
+ description: "Accessibility label for minimum value input"
153
+ },
154
+ value_max_label: {
155
+ id: "filter.value_max_label",
156
+ defaultMessage: "{field} maximum value",
157
+ description: "Accessibility label for maximum value input"
106
158
  }
107
159
  });
108
160