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