@kopexa/filter 0.0.25 → 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,942 @@
1
+ "use client";
2
+ "use strict";
3
+ "use client";
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
21
+
22
+ // src/filter-bar.tsx
23
+ var filter_bar_exports = {};
24
+ __export(filter_bar_exports, {
25
+ FilterBar: () => FilterBar
26
+ });
27
+ module.exports = __toCommonJS(filter_bar_exports);
28
+ var import_i18n2 = require("@kopexa/i18n");
29
+ var import_shared_utils2 = require("@kopexa/shared-utils");
30
+ var import_use_controllable_state = require("@kopexa/use-controllable-state");
31
+ var import_react2 = require("react");
32
+
33
+ // src/filter-bar-internal.tsx
34
+ var import_button = require("@kopexa/button");
35
+ var import_checkbox = require("@kopexa/checkbox");
36
+ var import_command = require("@kopexa/command");
37
+ var import_dropdown_menu = require("@kopexa/dropdown-menu");
38
+ var import_icons = require("@kopexa/icons");
39
+ var import_input = require("@kopexa/input");
40
+ var import_popover = require("@kopexa/popover");
41
+ var import_shared_utils = require("@kopexa/shared-utils");
42
+ var import_react = require("react");
43
+
44
+ // src/filter-bar-types.ts
45
+ var DEFAULT_FILTER_BAR_OPERATORS = {
46
+ select: ["equals", "not_equals", "is_empty", "is_not_empty"],
47
+ multiselect: ["equals", "not_equals", "is_empty", "is_not_empty"],
48
+ text: [
49
+ "equals",
50
+ "not_equals",
51
+ "contains",
52
+ "not_contains",
53
+ "starts_with",
54
+ "ends_with",
55
+ "is_empty",
56
+ "is_not_empty"
57
+ ],
58
+ number: [
59
+ "equals",
60
+ "not_equals",
61
+ "gt",
62
+ "lt",
63
+ "gte",
64
+ "lte",
65
+ "is_empty",
66
+ "is_not_empty"
67
+ ],
68
+ custom: ["equals", "not_equals"]
69
+ };
70
+
71
+ // src/filter-bar-internal.tsx
72
+ var import_jsx_runtime = require("react/jsx-runtime");
73
+ function AddFilterDropdown(props) {
74
+ const { fieldGroups, isFieldAvailable, onAddFilter, i18n, disabled } = props;
75
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_dropdown_menu.DropdownMenu.Root, { children: [
76
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dropdown_menu.DropdownMenu.Trigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
77
+ import_button.Button,
78
+ {
79
+ variant: "outline",
80
+ size: "sm",
81
+ className: "rounded-full border-dashed",
82
+ disabled,
83
+ startContent: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_icons.PlusIcon, { className: "size-4" }),
84
+ children: i18n.addFilter
85
+ }
86
+ ) }),
87
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dropdown_menu.DropdownMenu.Content, { align: "start", className: "min-w-[200px]", children: Array.from(fieldGroups.entries()).map(([groupLabel, groupFields]) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_dropdown_menu.DropdownMenu.Group, { children: [
88
+ groupLabel && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dropdown_menu.DropdownMenu.Label, { children: groupLabel }),
89
+ groupFields.map((field) => {
90
+ const available = isFieldAvailable(field.id);
91
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
92
+ import_dropdown_menu.DropdownMenu.Item,
93
+ {
94
+ disabled: field.disabled || !available,
95
+ onSelect: () => onAddFilter(field.id),
96
+ children: [
97
+ field.icon && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "size-4 text-muted-foreground mr-2", children: field.icon }),
98
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: field.label })
99
+ ]
100
+ },
101
+ field.id
102
+ );
103
+ })
104
+ ] }, groupLabel != null ? groupLabel : "ungrouped")) })
105
+ ] });
106
+ }
107
+ function FilterPill(props) {
108
+ var _a;
109
+ const {
110
+ filter,
111
+ field,
112
+ i18n,
113
+ onUpdate,
114
+ onRemove,
115
+ autoOpen,
116
+ onAutoOpenComplete,
117
+ disabled
118
+ } = props;
119
+ const [open, setOpen] = (0, import_react.useState)(autoOpen != null ? autoOpen : false);
120
+ const operators = (_a = field.operators) != null ? _a : DEFAULT_FILTER_BAR_OPERATORS[field.type];
121
+ const isEmptyOperator = filter.operator === "is_empty" || filter.operator === "is_not_empty";
122
+ const hasCustomRenderer = !!field.customRenderer;
123
+ const useInlineInput = !hasCustomRenderer && (field.type === "text" || field.type === "number");
124
+ const usePopover = !useInlineInput && !isEmptyOperator;
125
+ const handleOpenChange = (0, import_react.useCallback)(
126
+ (isOpen) => {
127
+ setOpen(isOpen);
128
+ if (!isOpen && autoOpen) {
129
+ onAutoOpenComplete == null ? void 0 : onAutoOpenComplete();
130
+ }
131
+ },
132
+ [autoOpen, onAutoOpenComplete]
133
+ );
134
+ const valueDisplay = (0, import_react.useMemo)(() => {
135
+ var _a2, _b;
136
+ if (isEmptyOperator) return null;
137
+ if (filter.value === null || filter.value === "") return null;
138
+ if (field.type === "select" && field.options) {
139
+ const option = field.options.find((o) => o.value === filter.value);
140
+ return (_a2 = option == null ? void 0 : option.label) != null ? _a2 : String(filter.value);
141
+ }
142
+ if (field.type === "multiselect") {
143
+ const values = Array.isArray(filter.value) ? filter.value.filter(
144
+ (v) => typeof v === "string" && v.length > 1
145
+ ) : [];
146
+ if (values.length === 0) return null;
147
+ if (values.length === 1 && field.options) {
148
+ const option = field.options.find((o) => o.value === values[0]);
149
+ return (_b = option == null ? void 0 : option.label) != null ? _b : values[0];
150
+ }
151
+ return `${values.length} selected`;
152
+ }
153
+ return String(filter.value);
154
+ }, [filter.value, field, isEmptyOperator]);
155
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
156
+ "div",
157
+ {
158
+ "data-slot": "filter-pill",
159
+ className: (0, import_shared_utils.cn)(
160
+ "group inline-flex items-center rounded-full overflow-hidden",
161
+ "border border-border bg-background",
162
+ "transition-all hover:shadow-sm focus-within:ring-2 focus-within:ring-ring focus-within:ring-offset-1"
163
+ ),
164
+ children: [
165
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
166
+ "span",
167
+ {
168
+ className: (0, import_shared_utils.cn)(
169
+ "bg-muted text-foreground font-medium text-xs",
170
+ "flex items-center gap-1.5 flex-shrink-0",
171
+ "rounded-l-full px-3 py-1.5"
172
+ ),
173
+ children: [
174
+ field.icon && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "size-4 flex-shrink-0", children: field.icon }),
175
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: field.label })
176
+ ]
177
+ }
178
+ ),
179
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
180
+ OperatorDropdown,
181
+ {
182
+ operator: filter.operator,
183
+ operators,
184
+ i18n,
185
+ onChange: (op) => {
186
+ const newUpdates = { operator: op };
187
+ if (op === "is_empty" || op === "is_not_empty") {
188
+ newUpdates.value = null;
189
+ }
190
+ onUpdate(newUpdates);
191
+ },
192
+ disabled
193
+ }
194
+ ),
195
+ useInlineInput && !isEmptyOperator && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
196
+ InlineValueInput,
197
+ {
198
+ filter,
199
+ field,
200
+ i18n,
201
+ onUpdate,
202
+ disabled
203
+ }
204
+ ),
205
+ usePopover && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_popover.Popover.Root, { open, onOpenChange: handleOpenChange, children: [
206
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
207
+ import_popover.Popover.Trigger,
208
+ {
209
+ className: (0, import_shared_utils.cn)(
210
+ "bg-background text-foreground text-xs",
211
+ "flex items-center gap-1 px-2.5 py-1.5",
212
+ "cursor-pointer hover:bg-muted/30"
213
+ ),
214
+ nativeButton: false,
215
+ render: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {}),
216
+ children: [
217
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "truncate max-w-[150px]", children: valueDisplay != null ? valueDisplay : i18n.selectValue }),
218
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_icons.ChevronDownIcon, { className: "size-3.5 opacity-60 flex-shrink-0" })
219
+ ]
220
+ }
221
+ ),
222
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
223
+ import_popover.Popover.Content,
224
+ {
225
+ "data-slot": "filter-editor",
226
+ className: "p-0 w-[280px]",
227
+ align: "start",
228
+ showArrow: false,
229
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
230
+ ValueEditor,
231
+ {
232
+ filter,
233
+ field,
234
+ i18n,
235
+ onUpdate,
236
+ onClose: () => handleOpenChange(false)
237
+ }
238
+ )
239
+ }
240
+ )
241
+ ] }),
242
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
243
+ "button",
244
+ {
245
+ type: "button",
246
+ onClick: onRemove,
247
+ className: (0, import_shared_utils.cn)(
248
+ "size-5 rounded-full flex items-center justify-center flex-shrink-0",
249
+ "hover:bg-destructive/10 hover:text-destructive",
250
+ "transition-colors cursor-pointer mr-1"
251
+ ),
252
+ disabled,
253
+ "aria-label": `Remove ${field.label} filter`,
254
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_icons.CloseIcon, { className: "size-3" })
255
+ }
256
+ )
257
+ ]
258
+ }
259
+ );
260
+ }
261
+ function OperatorDropdown(props) {
262
+ const { operator, operators, i18n, onChange, disabled } = props;
263
+ const hasSingleOperator = operators.length === 1;
264
+ const currentLabel = i18n.operators[operator];
265
+ if (hasSingleOperator) {
266
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
267
+ "span",
268
+ {
269
+ className: (0, import_shared_utils.cn)(
270
+ "bg-primary text-primary-foreground",
271
+ "flex items-center px-3 py-0.5",
272
+ "rounded-full shadow-sm mx-1 my-0.5",
273
+ "text-xs font-normal"
274
+ ),
275
+ children: currentLabel
276
+ }
277
+ );
278
+ }
279
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_dropdown_menu.DropdownMenu.Root, { children: [
280
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dropdown_menu.DropdownMenu.Trigger, { asChild: true, disabled, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
281
+ "button",
282
+ {
283
+ type: "button",
284
+ className: (0, import_shared_utils.cn)(
285
+ "bg-primary text-primary-foreground",
286
+ "flex items-center gap-1 px-3 py-0.5",
287
+ "rounded-full shadow-sm mx-1 my-0.5",
288
+ "text-xs font-normal cursor-pointer",
289
+ "hover:bg-primary/90 transition-colors"
290
+ ),
291
+ children: [
292
+ currentLabel,
293
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_icons.ChevronDownIcon, { className: "size-3 opacity-70" })
294
+ ]
295
+ }
296
+ ) }),
297
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dropdown_menu.DropdownMenu.Content, { align: "start", className: "min-w-[140px]", children: operators.map((op) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
298
+ import_dropdown_menu.DropdownMenu.Item,
299
+ {
300
+ onSelect: () => onChange(op),
301
+ className: "gap-2",
302
+ children: [
303
+ op === operator && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_icons.CheckIcon, { className: "size-4" }),
304
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: op !== operator ? "pl-6" : "", children: i18n.operators[op] })
305
+ ]
306
+ },
307
+ op
308
+ )) })
309
+ ] });
310
+ }
311
+ function getInputWidth(value, placeholder, minWidth = 4) {
312
+ const displayValue = value !== void 0 && value !== null && value !== "" ? String(value) : placeholder;
313
+ const width = Math.max(displayValue.length + 1, minWidth);
314
+ return `${width}ch`;
315
+ }
316
+ function InlineValueInput(props) {
317
+ var _a;
318
+ const { filter, field, i18n, onUpdate, disabled } = props;
319
+ const placeholder = (_a = field.placeholder) != null ? _a : i18n.enterValue;
320
+ const handleChange = (e) => {
321
+ const val = e.target.value;
322
+ if (field.type === "number") {
323
+ onUpdate({ value: val ? Number(val) : null });
324
+ } else {
325
+ onUpdate({ value: val });
326
+ }
327
+ };
328
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
329
+ "span",
330
+ {
331
+ className: (0, import_shared_utils.cn)(
332
+ "bg-background text-foreground text-xs",
333
+ "flex items-center gap-1 px-2.5 py-1.5"
334
+ ),
335
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
336
+ "input",
337
+ {
338
+ type: field.type === "number" ? "number" : "text",
339
+ className: (0, import_shared_utils.cn)(
340
+ "bg-transparent border-none outline-none",
341
+ "placeholder:text-muted-foreground",
342
+ "text-xs"
343
+ ),
344
+ value: filter.value !== void 0 && filter.value !== null ? String(filter.value) : "",
345
+ onChange: handleChange,
346
+ placeholder,
347
+ "aria-label": `${field.label} value`,
348
+ style: { width: getInputWidth(filter.value, placeholder, 6) },
349
+ min: field.min,
350
+ max: field.max,
351
+ step: field.step,
352
+ disabled
353
+ }
354
+ )
355
+ }
356
+ );
357
+ }
358
+ function ValueEditor(props) {
359
+ var _a, _b, _c, _d;
360
+ const { filter, field, i18n, onUpdate, onClose } = props;
361
+ if (field.customRenderer) {
362
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "p-3", children: field.customRenderer({
363
+ field,
364
+ value: filter.value,
365
+ onChange: (value) => onUpdate({ value })
366
+ }) });
367
+ }
368
+ if (field.type === "select" && field.options) {
369
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
370
+ SelectEditor,
371
+ {
372
+ options: field.options,
373
+ value: filter.value,
374
+ onChange: (value) => {
375
+ onUpdate({ value });
376
+ onClose();
377
+ },
378
+ i18n
379
+ }
380
+ );
381
+ }
382
+ if (field.type === "multiselect" && field.options) {
383
+ const currentValue = Array.isArray(filter.value) ? filter.value.filter(
384
+ (v) => typeof v === "string" && v.length > 1
385
+ ) : [];
386
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
387
+ MultiselectEditor,
388
+ {
389
+ options: field.options,
390
+ value: currentValue,
391
+ onChange: (value) => onUpdate({ value }),
392
+ i18n
393
+ }
394
+ );
395
+ }
396
+ if (field.type === "text") {
397
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
398
+ TextEditor,
399
+ {
400
+ value: (_a = filter.value) != null ? _a : "",
401
+ onChange: (value) => onUpdate({ value }),
402
+ placeholder: (_b = field.placeholder) != null ? _b : i18n.enterValue,
403
+ validation: field.validation,
404
+ onClose
405
+ }
406
+ );
407
+ }
408
+ if (field.type === "number") {
409
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
410
+ NumberEditor,
411
+ {
412
+ value: (_c = filter.value) != null ? _c : null,
413
+ onChange: (value) => onUpdate({ value }),
414
+ placeholder: (_d = field.placeholder) != null ? _d : i18n.enterValue,
415
+ min: field.min,
416
+ max: field.max,
417
+ step: field.step,
418
+ onClose
419
+ }
420
+ );
421
+ }
422
+ return null;
423
+ }
424
+ function SelectEditor(props) {
425
+ const { options = [], value, onChange, i18n } = props;
426
+ const [search, setSearch] = (0, import_react.useState)("");
427
+ const filteredOptions = (0, import_react.useMemo)(() => {
428
+ if (!search) return options;
429
+ const lower = search.toLowerCase();
430
+ return options.filter(
431
+ (o) => o.label.toLowerCase().includes(lower) || o.value.toLowerCase().includes(lower)
432
+ );
433
+ }, [options, search]);
434
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_command.Command.Root, { shouldFilter: false, children: [
435
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
436
+ import_command.Command.Input,
437
+ {
438
+ placeholder: i18n.searchPlaceholder,
439
+ value: search,
440
+ onValueChange: setSearch
441
+ }
442
+ ),
443
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_command.Command.List, { className: "max-h-[300px]", children: [
444
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_command.Command.Empty, { children: i18n.noResults }),
445
+ filteredOptions.map((option) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
446
+ import_command.Command.Item,
447
+ {
448
+ value: option.value,
449
+ onSelect: () => onChange(option.value),
450
+ className: "flex items-center gap-2 min-w-0",
451
+ children: [
452
+ option.icon && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "flex-shrink-0 [&>*]:size-5", children: option.icon }),
453
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "truncate flex-1 min-w-0", children: option.label }),
454
+ value === option.value && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_icons.CheckIcon, { className: "size-4 flex-shrink-0" })
455
+ ]
456
+ },
457
+ option.value
458
+ ))
459
+ ] })
460
+ ] });
461
+ }
462
+ function MultiselectEditor(props) {
463
+ const { options = [], value, onChange, i18n } = props;
464
+ const [search, setSearch] = (0, import_react.useState)("");
465
+ const filteredOptions = (0, import_react.useMemo)(() => {
466
+ if (!search) return options;
467
+ const lower = search.toLowerCase();
468
+ return options.filter(
469
+ (o) => o.label.toLowerCase().includes(lower) || o.value.toLowerCase().includes(lower)
470
+ );
471
+ }, [options, search]);
472
+ const toggleOption = (0, import_react.useCallback)(
473
+ (optionValue) => {
474
+ if (value.includes(optionValue)) {
475
+ onChange(value.filter((v) => v !== optionValue));
476
+ } else {
477
+ onChange([...value, optionValue]);
478
+ }
479
+ },
480
+ [value, onChange]
481
+ );
482
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_command.Command.Root, { shouldFilter: false, children: [
483
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
484
+ import_command.Command.Input,
485
+ {
486
+ placeholder: i18n.searchPlaceholder,
487
+ value: search,
488
+ onValueChange: setSearch
489
+ }
490
+ ),
491
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_command.Command.List, { className: "max-h-[300px]", children: [
492
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_command.Command.Empty, { children: i18n.noResults }),
493
+ filteredOptions.map((option) => {
494
+ const isSelected = value.includes(option.value);
495
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
496
+ import_command.Command.Item,
497
+ {
498
+ value: option.value,
499
+ onSelect: () => toggleOption(option.value),
500
+ className: "flex items-center gap-2 min-w-0",
501
+ children: [
502
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
503
+ import_checkbox.Checkbox,
504
+ {
505
+ checked: isSelected,
506
+ className: "flex-shrink-0 border-border bg-background data-[state=checked]:bg-primary data-[state=checked]:border-primary [&_svg]:text-primary-foreground"
507
+ }
508
+ ),
509
+ option.icon && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "flex-shrink-0 [&>*]:size-5", children: option.icon }),
510
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "truncate flex-1 min-w-0", children: option.label })
511
+ ]
512
+ },
513
+ option.value
514
+ );
515
+ })
516
+ ] })
517
+ ] });
518
+ }
519
+ function TextEditor(props) {
520
+ const {
521
+ value: initialValue,
522
+ onChange,
523
+ placeholder,
524
+ validation,
525
+ onClose
526
+ } = props;
527
+ const [localValue, setLocalValue] = (0, import_react.useState)(initialValue);
528
+ const [error, setError] = (0, import_react.useState)(null);
529
+ const inputRef = (0, import_react.useRef)(null);
530
+ const validate = (0, import_react.useCallback)(
531
+ (val) => {
532
+ var _a;
533
+ if (!validation) return true;
534
+ if (validation.pattern) {
535
+ const regex = new RegExp(validation.pattern);
536
+ if (!regex.test(val)) {
537
+ setError((_a = validation.message) != null ? _a : "Invalid format");
538
+ return false;
539
+ }
540
+ }
541
+ if (validation.validate) {
542
+ const result = validation.validate(val);
543
+ if (result !== true) {
544
+ setError(typeof result === "string" ? result : "Invalid value");
545
+ return false;
546
+ }
547
+ }
548
+ setError(null);
549
+ return true;
550
+ },
551
+ [validation]
552
+ );
553
+ const handleSubmit = (0, import_react.useCallback)(() => {
554
+ if (validate(localValue)) {
555
+ onChange(localValue);
556
+ onClose();
557
+ }
558
+ }, [localValue, validate, onChange, onClose]);
559
+ const handleKeyDown = (0, import_react.useCallback)(
560
+ (e) => {
561
+ if (e.key === "Enter") {
562
+ e.preventDefault();
563
+ handleSubmit();
564
+ } else if (e.key === "Escape") {
565
+ e.preventDefault();
566
+ onClose();
567
+ }
568
+ },
569
+ [handleSubmit, onClose]
570
+ );
571
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "p-3 space-y-2", children: [
572
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
573
+ import_input.Input,
574
+ {
575
+ ref: inputRef,
576
+ type: "text",
577
+ value: localValue,
578
+ onChange: (e) => {
579
+ setLocalValue(e.target.value);
580
+ setError(null);
581
+ },
582
+ onKeyDown: handleKeyDown,
583
+ placeholder,
584
+ className: "w-full",
585
+ autoFocus: true
586
+ }
587
+ ),
588
+ error && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "text-xs text-destructive", children: error }),
589
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex justify-end gap-2", children: [
590
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_button.Button, { variant: "ghost", size: "sm", onClick: onClose, children: "Cancel" }),
591
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_button.Button, { size: "sm", onClick: handleSubmit, children: "Apply" })
592
+ ] })
593
+ ] });
594
+ }
595
+ function NumberEditor(props) {
596
+ const {
597
+ value: initialValue,
598
+ onChange,
599
+ placeholder,
600
+ min,
601
+ max,
602
+ step,
603
+ onClose
604
+ } = props;
605
+ const [localValue, setLocalValue] = (0, import_react.useState)(
606
+ initialValue !== null ? String(initialValue) : ""
607
+ );
608
+ const inputRef = (0, import_react.useRef)(null);
609
+ const handleSubmit = (0, import_react.useCallback)(() => {
610
+ const numValue = localValue === "" ? null : Number(localValue);
611
+ onChange(numValue);
612
+ onClose();
613
+ }, [localValue, onChange, onClose]);
614
+ const handleKeyDown = (0, import_react.useCallback)(
615
+ (e) => {
616
+ if (e.key === "Enter") {
617
+ e.preventDefault();
618
+ handleSubmit();
619
+ } else if (e.key === "Escape") {
620
+ e.preventDefault();
621
+ onClose();
622
+ }
623
+ },
624
+ [handleSubmit, onClose]
625
+ );
626
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "p-3 space-y-2", children: [
627
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
628
+ import_input.Input,
629
+ {
630
+ ref: inputRef,
631
+ type: "number",
632
+ value: localValue,
633
+ onChange: (e) => setLocalValue(e.target.value),
634
+ onKeyDown: handleKeyDown,
635
+ placeholder,
636
+ min,
637
+ max,
638
+ step,
639
+ className: "w-full",
640
+ autoFocus: true
641
+ }
642
+ ),
643
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex justify-end gap-2", children: [
644
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_button.Button, { variant: "ghost", size: "sm", onClick: onClose, children: "Cancel" }),
645
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_button.Button, { size: "sm", onClick: handleSubmit, children: "Apply" })
646
+ ] })
647
+ ] });
648
+ }
649
+
650
+ // src/filter-bar-messages.ts
651
+ var import_i18n = require("@kopexa/i18n");
652
+ var filterBarMessages = (0, import_i18n.defineMessages)({
653
+ add_filter: {
654
+ id: "filter-bar.add_filter",
655
+ defaultMessage: "Add Filter",
656
+ description: "Button text for adding a new filter"
657
+ },
658
+ clear_all: {
659
+ id: "filter-bar.clear_all",
660
+ defaultMessage: "Clear all",
661
+ description: "Button text for clearing all filters"
662
+ },
663
+ no_results: {
664
+ id: "filter-bar.no_results",
665
+ defaultMessage: "No results found",
666
+ description: "Message when search returns no results"
667
+ },
668
+ search_placeholder: {
669
+ id: "filter-bar.search_placeholder",
670
+ defaultMessage: "Search...",
671
+ description: "Placeholder for search input"
672
+ },
673
+ select_value: {
674
+ id: "filter-bar.select_value",
675
+ defaultMessage: "Select value...",
676
+ description: "Placeholder for value selection"
677
+ },
678
+ enter_value: {
679
+ id: "filter-bar.enter_value",
680
+ defaultMessage: "Enter value...",
681
+ description: "Placeholder for value input"
682
+ },
683
+ apply: {
684
+ id: "filter-bar.apply",
685
+ defaultMessage: "Apply",
686
+ description: "Button text for applying a filter value"
687
+ },
688
+ cancel: {
689
+ id: "filter-bar.cancel",
690
+ defaultMessage: "Cancel",
691
+ description: "Button text for canceling"
692
+ },
693
+ // Operators
694
+ op_equals: {
695
+ id: "filter-bar.operator.equals",
696
+ defaultMessage: "is",
697
+ description: "Operator: equals"
698
+ },
699
+ op_not_equals: {
700
+ id: "filter-bar.operator.not_equals",
701
+ defaultMessage: "is not",
702
+ description: "Operator: not equals"
703
+ },
704
+ op_contains: {
705
+ id: "filter-bar.operator.contains",
706
+ defaultMessage: "contains",
707
+ description: "Operator: contains"
708
+ },
709
+ op_not_contains: {
710
+ id: "filter-bar.operator.not_contains",
711
+ defaultMessage: "does not contain",
712
+ description: "Operator: does not contain"
713
+ },
714
+ op_starts_with: {
715
+ id: "filter-bar.operator.starts_with",
716
+ defaultMessage: "starts with",
717
+ description: "Operator: starts with"
718
+ },
719
+ op_ends_with: {
720
+ id: "filter-bar.operator.ends_with",
721
+ defaultMessage: "ends with",
722
+ description: "Operator: ends with"
723
+ },
724
+ op_gt: {
725
+ id: "filter-bar.operator.gt",
726
+ defaultMessage: "greater than",
727
+ description: "Operator: greater than"
728
+ },
729
+ op_lt: {
730
+ id: "filter-bar.operator.lt",
731
+ defaultMessage: "less than",
732
+ description: "Operator: less than"
733
+ },
734
+ op_gte: {
735
+ id: "filter-bar.operator.gte",
736
+ defaultMessage: "greater or equal",
737
+ description: "Operator: greater than or equal"
738
+ },
739
+ op_lte: {
740
+ id: "filter-bar.operator.lte",
741
+ defaultMessage: "less or equal",
742
+ description: "Operator: less than or equal"
743
+ },
744
+ op_is_empty: {
745
+ id: "filter-bar.operator.is_empty",
746
+ defaultMessage: "is empty",
747
+ description: "Operator: is empty"
748
+ },
749
+ op_is_not_empty: {
750
+ id: "filter-bar.operator.is_not_empty",
751
+ defaultMessage: "is not empty",
752
+ description: "Operator: is not empty"
753
+ },
754
+ // Accessibility
755
+ remove_filter: {
756
+ id: "filter-bar.remove_filter",
757
+ defaultMessage: "Remove {field} filter",
758
+ description: "Accessibility label for remove filter button"
759
+ },
760
+ operator_label: {
761
+ id: "filter-bar.operator_label",
762
+ defaultMessage: "{field} operator",
763
+ description: "Accessibility label for operator dropdown"
764
+ },
765
+ value_label: {
766
+ id: "filter-bar.value_label",
767
+ defaultMessage: "{field} value",
768
+ description: "Accessibility label for value editor"
769
+ },
770
+ // Multiselect
771
+ selected_count: {
772
+ id: "filter-bar.selected_count",
773
+ defaultMessage: "{count} selected",
774
+ description: "Display text for multiselect with multiple selections"
775
+ }
776
+ });
777
+
778
+ // src/filter-bar.tsx
779
+ var import_jsx_runtime2 = require("react/jsx-runtime");
780
+ function FilterBar(props) {
781
+ const {
782
+ fields,
783
+ value: valueProp,
784
+ defaultValue = [],
785
+ onChange,
786
+ i18n: i18nProp,
787
+ className,
788
+ allowMultiple = true,
789
+ disabled = false
790
+ } = props;
791
+ const t = (0, import_i18n2.useSafeIntl)();
792
+ const instanceId = (0, import_react2.useId)();
793
+ const [value, setValue] = (0, import_use_controllable_state.useControllableState)({
794
+ value: valueProp,
795
+ defaultValue,
796
+ onChange
797
+ });
798
+ const [sessionFilterId, setSessionFilterId] = (0, import_react2.useState)(null);
799
+ const i18n = (0, import_react2.useMemo)(() => {
800
+ const defaultI18n = {
801
+ addFilter: t.formatMessage(filterBarMessages.add_filter),
802
+ clearAll: t.formatMessage(filterBarMessages.clear_all),
803
+ noResults: t.formatMessage(filterBarMessages.no_results),
804
+ searchPlaceholder: t.formatMessage(filterBarMessages.search_placeholder),
805
+ selectValue: t.formatMessage(filterBarMessages.select_value),
806
+ enterValue: t.formatMessage(filterBarMessages.enter_value),
807
+ operators: {
808
+ equals: t.formatMessage(filterBarMessages.op_equals),
809
+ not_equals: t.formatMessage(filterBarMessages.op_not_equals),
810
+ contains: t.formatMessage(filterBarMessages.op_contains),
811
+ not_contains: t.formatMessage(filterBarMessages.op_not_contains),
812
+ starts_with: t.formatMessage(filterBarMessages.op_starts_with),
813
+ ends_with: t.formatMessage(filterBarMessages.op_ends_with),
814
+ gt: t.formatMessage(filterBarMessages.op_gt),
815
+ lt: t.formatMessage(filterBarMessages.op_lt),
816
+ gte: t.formatMessage(filterBarMessages.op_gte),
817
+ lte: t.formatMessage(filterBarMessages.op_lte),
818
+ is_empty: t.formatMessage(filterBarMessages.op_is_empty),
819
+ is_not_empty: t.formatMessage(filterBarMessages.op_is_not_empty)
820
+ }
821
+ };
822
+ return {
823
+ ...defaultI18n,
824
+ ...i18nProp,
825
+ operators: {
826
+ ...defaultI18n.operators,
827
+ ...i18nProp == null ? void 0 : i18nProp.operators
828
+ }
829
+ };
830
+ }, [t, i18nProp]);
831
+ const fieldGroups = (0, import_react2.useMemo)(() => {
832
+ var _a;
833
+ const groups = /* @__PURE__ */ new Map();
834
+ for (const field of fields) {
835
+ const group = field.group;
836
+ if (!groups.has(group)) {
837
+ groups.set(group, []);
838
+ }
839
+ (_a = groups.get(group)) == null ? void 0 : _a.push(field);
840
+ }
841
+ return groups;
842
+ }, [fields]);
843
+ const addFilter = (0, import_react2.useCallback)(
844
+ (fieldId) => {
845
+ var _a;
846
+ const field = fields.find((f) => f.id === fieldId);
847
+ if (!field) return;
848
+ if (!allowMultiple && value.some((f) => f.fieldId === fieldId)) {
849
+ return;
850
+ }
851
+ const newFilter = {
852
+ id: `${instanceId}-${fieldId}-${Date.now()}`,
853
+ fieldId,
854
+ operator: (_a = field.defaultOperator) != null ? _a : "equals",
855
+ value: field.type === "multiselect" ? [] : null
856
+ };
857
+ setValue([...value, newFilter]);
858
+ setSessionFilterId(newFilter.id);
859
+ },
860
+ [fields, value, setValue, allowMultiple, instanceId]
861
+ );
862
+ const updateFilter = (0, import_react2.useCallback)(
863
+ (filterId, updates) => {
864
+ setValue(
865
+ value.map((f) => f.id === filterId ? { ...f, ...updates } : f)
866
+ );
867
+ },
868
+ [value, setValue]
869
+ );
870
+ const removeFilter = (0, import_react2.useCallback)(
871
+ (filterId) => {
872
+ setValue(value.filter((f) => f.id !== filterId));
873
+ if (sessionFilterId === filterId) {
874
+ setSessionFilterId(null);
875
+ }
876
+ },
877
+ [value, setValue, sessionFilterId]
878
+ );
879
+ const clearFilters = (0, import_react2.useCallback)(() => {
880
+ setValue([]);
881
+ setSessionFilterId(null);
882
+ }, [setValue]);
883
+ const isFieldAvailable = (0, import_react2.useCallback)(
884
+ (fieldId) => {
885
+ if (allowMultiple) return true;
886
+ return !value.some((f) => f.fieldId === fieldId);
887
+ },
888
+ [value, allowMultiple]
889
+ );
890
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
891
+ "div",
892
+ {
893
+ "data-slot": "filter-bar",
894
+ className: (0, import_shared_utils2.cn)("flex flex-wrap items-center gap-2", className),
895
+ children: [
896
+ value.map((filter) => {
897
+ const field = fields.find((f) => f.id === filter.fieldId);
898
+ if (!field) return null;
899
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
900
+ FilterPill,
901
+ {
902
+ filter,
903
+ field,
904
+ i18n,
905
+ onUpdate: (updates) => updateFilter(filter.id, updates),
906
+ onRemove: () => removeFilter(filter.id),
907
+ autoOpen: sessionFilterId === filter.id,
908
+ onAutoOpenComplete: () => setSessionFilterId(null),
909
+ disabled
910
+ },
911
+ filter.id
912
+ );
913
+ }),
914
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
915
+ AddFilterDropdown,
916
+ {
917
+ fieldGroups,
918
+ isFieldAvailable,
919
+ onAddFilter: addFilter,
920
+ i18n,
921
+ disabled
922
+ }
923
+ ),
924
+ value.length > 1 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
925
+ "button",
926
+ {
927
+ type: "button",
928
+ onClick: clearFilters,
929
+ className: "text-xs text-muted-foreground hover:text-foreground cursor-pointer transition-colors underline-offset-2 hover:underline",
930
+ disabled,
931
+ children: i18n.clearAll
932
+ }
933
+ )
934
+ ]
935
+ }
936
+ );
937
+ }
938
+ FilterBar.displayName = "FilterBar";
939
+ // Annotate the CommonJS export names for ESM import in node:
940
+ 0 && (module.exports = {
941
+ FilterBar
942
+ });