@kopexa/filter 0.0.2

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/LICENSE +201 -0
  2. package/dist/chunk-3WNAREG6.mjs +29 -0
  3. package/dist/chunk-63P4ITVP.mjs +263 -0
  4. package/dist/chunk-7KY2PDNL.mjs +136 -0
  5. package/dist/chunk-7QP7FRID.mjs +47 -0
  6. package/dist/chunk-EF4VI36D.mjs +36 -0
  7. package/dist/chunk-I3Z2T4N2.mjs +14 -0
  8. package/dist/chunk-PTJ7HZPA.mjs +164 -0
  9. package/dist/chunk-TBHYZZSX.mjs +139 -0
  10. package/dist/chunk-URDCG5NI.mjs +111 -0
  11. package/dist/filter-active.d.mts +13 -0
  12. package/dist/filter-active.d.ts +13 -0
  13. package/dist/filter-active.js +538 -0
  14. package/dist/filter-active.mjs +12 -0
  15. package/dist/filter-context.d.mts +28 -0
  16. package/dist/filter-context.d.ts +28 -0
  17. package/dist/filter-context.js +39 -0
  18. package/dist/filter-context.mjs +10 -0
  19. package/dist/filter-i18n.d.mts +20 -0
  20. package/dist/filter-i18n.d.ts +20 -0
  21. package/dist/filter-i18n.js +52 -0
  22. package/dist/filter-i18n.mjs +7 -0
  23. package/dist/filter-menu.d.mts +65 -0
  24. package/dist/filter-menu.d.ts +65 -0
  25. package/dist/filter-menu.js +169 -0
  26. package/dist/filter-menu.mjs +15 -0
  27. package/dist/filter-trigger.d.mts +14 -0
  28. package/dist/filter-trigger.d.ts +14 -0
  29. package/dist/filter-trigger.js +170 -0
  30. package/dist/filter-trigger.mjs +10 -0
  31. package/dist/filter-types.d.mts +60 -0
  32. package/dist/filter-types.d.ts +60 -0
  33. package/dist/filter-types.js +72 -0
  34. package/dist/filter-types.mjs +11 -0
  35. package/dist/filter-value-editor.d.mts +11 -0
  36. package/dist/filter-value-editor.d.ts +11 -0
  37. package/dist/filter-value-editor.js +414 -0
  38. package/dist/filter-value-editor.mjs +11 -0
  39. package/dist/filter.d.mts +24 -0
  40. package/dist/filter.d.ts +24 -0
  41. package/dist/filter.js +242 -0
  42. package/dist/filter.mjs +11 -0
  43. package/dist/index.d.mts +13 -0
  44. package/dist/index.d.ts +13 -0
  45. package/dist/index.js +909 -0
  46. package/dist/index.mjs +39 -0
  47. package/dist/messages.d.mts +104 -0
  48. package/dist/messages.d.ts +104 -0
  49. package/dist/messages.js +134 -0
  50. package/dist/messages.mjs +7 -0
  51. package/package.json +69 -0
package/dist/index.js ADDED
@@ -0,0 +1,909 @@
1
+ "use client";
2
+ "use strict";
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __export = (target, all) => {
10
+ for (var name in all)
11
+ __defProp(target, name, { get: all[name], enumerable: true });
12
+ };
13
+ var __copyProps = (to, from, except, desc) => {
14
+ if (from && typeof from === "object" || typeof from === "function") {
15
+ for (let key of __getOwnPropNames(from))
16
+ if (!__hasOwnProp.call(to, key) && key !== except)
17
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
18
+ }
19
+ return to;
20
+ };
21
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
22
+ // If the importer is in node compatibility mode or this is not an ESM
23
+ // file that has been converted to a CommonJS file using a Babel-
24
+ // compatible transform (i.e. "__esModule" has not been set), then set
25
+ // "default" to the CommonJS "module.exports" for node compatibility.
26
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
27
+ mod
28
+ ));
29
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
30
+
31
+ // src/index.ts
32
+ var index_exports = {};
33
+ __export(index_exports, {
34
+ Filter: () => Filter,
35
+ FilterActive: () => FilterActive,
36
+ FilterGroup: () => FilterGroup,
37
+ FilterItem: () => FilterItem,
38
+ FilterMenu: () => FilterMenu,
39
+ FilterMenuSeparator: () => FilterMenuSeparator,
40
+ FilterTrigger: () => FilterTrigger,
41
+ FilterValueEditor: () => FilterValueEditor,
42
+ filterMessages: () => messages,
43
+ useFilterContext: () => useFilterContext
44
+ });
45
+ module.exports = __toCommonJS(index_exports);
46
+
47
+ // src/filter.tsx
48
+ var import_dropdown_menu = require("@kopexa/dropdown-menu");
49
+ var import_shared_utils = require("@kopexa/shared-utils");
50
+ var import_theme = require("@kopexa/theme");
51
+ var React = __toESM(require("react"));
52
+
53
+ // src/filter-context.tsx
54
+ var import_react_utils = require("@kopexa/react-utils");
55
+ var [FilterProvider, useFilterContext] = (0, import_react_utils.createContext)({
56
+ name: "FilterContext",
57
+ strict: true,
58
+ errorMessage: "useFilterContext must be used within a Filter component. Make sure to wrap your FilterMenu, FilterTrigger, etc. inside a <Filter> component."
59
+ });
60
+
61
+ // src/filter-i18n.ts
62
+ var DEFAULT_I18N = {
63
+ addFilter: "Add Filter",
64
+ clearAll: "Clear all",
65
+ noFields: "No filter options available",
66
+ selectValue: "Select value...",
67
+ enterValue: "Enter value...",
68
+ operators: {
69
+ equals: "is",
70
+ not_equals: "is not",
71
+ contains: "contains",
72
+ not_contains: "does not contain",
73
+ starts_with: "starts with",
74
+ ends_with: "ends with",
75
+ gt: "greater than",
76
+ lt: "less than",
77
+ gte: "greater or equal",
78
+ lte: "less or equal",
79
+ between: "between",
80
+ is_empty: "is empty",
81
+ is_not_empty: "is not empty"
82
+ }
83
+ };
84
+
85
+ // src/filter-types.ts
86
+ var DEFAULT_OPERATORS = {
87
+ text: [
88
+ "equals",
89
+ "not_equals",
90
+ "contains",
91
+ "not_contains",
92
+ "starts_with",
93
+ "ends_with",
94
+ "is_empty",
95
+ "is_not_empty"
96
+ ],
97
+ number: ["equals", "not_equals", "gt", "lt", "gte", "lte", "between"],
98
+ select: ["equals", "not_equals", "is_empty", "is_not_empty"],
99
+ multiselect: ["equals", "not_equals", "is_empty", "is_not_empty"],
100
+ date: ["equals", "not_equals", "gt", "lt", "gte", "lte", "between"],
101
+ daterange: ["between"],
102
+ boolean: ["equals"]
103
+ };
104
+ function getDefaultOperator(type) {
105
+ switch (type) {
106
+ case "text":
107
+ return "contains";
108
+ case "number":
109
+ case "date":
110
+ case "select":
111
+ case "multiselect":
112
+ return "equals";
113
+ case "daterange":
114
+ return "between";
115
+ case "boolean":
116
+ return "equals";
117
+ default:
118
+ return "equals";
119
+ }
120
+ }
121
+
122
+ // src/filter.tsx
123
+ var import_jsx_runtime = require("react/jsx-runtime");
124
+ function Filter(props) {
125
+ const {
126
+ value = [],
127
+ onChange,
128
+ allowMultiple = true,
129
+ size = "md",
130
+ variant = "outline",
131
+ i18n,
132
+ className,
133
+ children,
134
+ ...rest
135
+ } = props;
136
+ const mergedI18n = React.useMemo(
137
+ () => ({
138
+ ...DEFAULT_I18N,
139
+ ...i18n,
140
+ operators: {
141
+ ...DEFAULT_I18N.operators,
142
+ ...i18n == null ? void 0 : i18n.operators
143
+ }
144
+ }),
145
+ [i18n]
146
+ );
147
+ const styles = React.useMemo(
148
+ () => (0, import_theme.filter)({ size, variant }),
149
+ [size, variant]
150
+ );
151
+ const [fields, setFields] = React.useState(
152
+ /* @__PURE__ */ new Map()
153
+ );
154
+ const [menuOpen, setMenuOpen] = React.useState(false);
155
+ const [editingFilterId, setEditingFilterId] = React.useState(
156
+ null
157
+ );
158
+ const registerField = React.useCallback((field) => {
159
+ setFields((prev) => {
160
+ const next = new Map(prev);
161
+ next.set(field.id, field);
162
+ return next;
163
+ });
164
+ }, []);
165
+ const unregisterField = React.useCallback((fieldId) => {
166
+ setFields((prev) => {
167
+ const next = new Map(prev);
168
+ next.delete(fieldId);
169
+ return next;
170
+ });
171
+ }, []);
172
+ const updateFilter = React.useCallback(
173
+ (filterId, updates) => {
174
+ const newFilters = value.map((filter) => {
175
+ if (filter.id === filterId) {
176
+ const updated = { ...filter, ...updates };
177
+ if (updates.operator === "is_empty" || updates.operator === "is_not_empty") {
178
+ updated.value = void 0;
179
+ }
180
+ return updated;
181
+ }
182
+ return filter;
183
+ });
184
+ onChange == null ? void 0 : onChange(newFilters);
185
+ },
186
+ [value, onChange]
187
+ );
188
+ const removeFilter = React.useCallback(
189
+ (filterId) => {
190
+ onChange == null ? void 0 : onChange(value.filter((f) => f.id !== filterId));
191
+ },
192
+ [value, onChange]
193
+ );
194
+ const clearFilters = React.useCallback(() => {
195
+ onChange == null ? void 0 : onChange([]);
196
+ }, [onChange]);
197
+ const addFilter = React.useCallback(
198
+ (fieldKey) => {
199
+ const field = fields.get(fieldKey);
200
+ if (!field) return;
201
+ const defaultOperator = getDefaultOperator(field.type);
202
+ const newFilter = {
203
+ id: `${Date.now()}-${Math.random().toString(36).slice(2, 11)}`,
204
+ fieldId: fieldKey,
205
+ operator: defaultOperator,
206
+ value: void 0
207
+ };
208
+ onChange == null ? void 0 : onChange([...value, newFilter]);
209
+ },
210
+ [fields, value, onChange]
211
+ );
212
+ const contextValue = React.useMemo(
213
+ () => ({
214
+ // State
215
+ value,
216
+ fields,
217
+ allowMultiple,
218
+ // Style
219
+ styles,
220
+ size: size != null ? size : "md",
221
+ variant: variant != null ? variant : "outline",
222
+ i18n: mergedI18n,
223
+ // Actions
224
+ addFilter,
225
+ updateFilter,
226
+ removeFilter,
227
+ clearFilters,
228
+ // Field registration
229
+ registerField,
230
+ unregisterField,
231
+ // Menu state
232
+ menuOpen,
233
+ setMenuOpen,
234
+ // Editor state
235
+ editingFilterId,
236
+ setEditingFilterId
237
+ }),
238
+ [
239
+ value,
240
+ fields,
241
+ allowMultiple,
242
+ styles,
243
+ size,
244
+ variant,
245
+ mergedI18n,
246
+ addFilter,
247
+ updateFilter,
248
+ removeFilter,
249
+ clearFilters,
250
+ registerField,
251
+ unregisterField,
252
+ menuOpen,
253
+ editingFilterId
254
+ ]
255
+ );
256
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FilterProvider, { value: contextValue, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dropdown_menu.DropdownMenu.Root, { open: menuOpen, onOpenChange: setMenuOpen, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
257
+ "div",
258
+ {
259
+ "data-slot": "filter",
260
+ className: (0, import_shared_utils.cn)(styles.root(), className),
261
+ ...rest,
262
+ children
263
+ }
264
+ ) }) });
265
+ }
266
+ Filter.displayName = "Filter";
267
+
268
+ // src/filter-active.tsx
269
+ var import_i18n3 = require("@kopexa/i18n");
270
+ var import_icons = require("@kopexa/icons");
271
+ var import_popover = require("@kopexa/popover");
272
+ var import_shared_utils2 = require("@kopexa/shared-utils");
273
+
274
+ // src/filter-value-editor.tsx
275
+ var import_checkbox = require("@kopexa/checkbox");
276
+ var import_i18n2 = require("@kopexa/i18n");
277
+ var import_input = require("@kopexa/input");
278
+ var import_select = require("@kopexa/select");
279
+
280
+ // src/messages.ts
281
+ var import_i18n = require("@kopexa/i18n");
282
+ var messages = (0, import_i18n.defineMessages)({
283
+ add_filter: {
284
+ id: "filter.add_filter",
285
+ defaultMessage: "Add Filter",
286
+ description: "Button text for adding a new filter"
287
+ },
288
+ clear_all: {
289
+ id: "filter.clear_all",
290
+ defaultMessage: "Clear all",
291
+ description: "Button text for clearing all filters"
292
+ },
293
+ no_fields: {
294
+ id: "filter.no_fields",
295
+ defaultMessage: "No filter options available",
296
+ description: "Message when no filter fields are configured"
297
+ },
298
+ apply: {
299
+ id: "filter.apply",
300
+ defaultMessage: "Apply",
301
+ description: "Button text for applying a filter value"
302
+ },
303
+ cancel: {
304
+ id: "filter.cancel",
305
+ defaultMessage: "Cancel",
306
+ description: "Button text for canceling filter editing"
307
+ },
308
+ select_value: {
309
+ id: "filter.select_value",
310
+ defaultMessage: "Select value...",
311
+ description: "Placeholder for value selection"
312
+ },
313
+ enter_value: {
314
+ id: "filter.enter_value",
315
+ defaultMessage: "Enter value...",
316
+ description: "Placeholder for value input"
317
+ },
318
+ // Operators
319
+ op_equals: {
320
+ id: "filter.operator.equals",
321
+ defaultMessage: "is",
322
+ description: "Operator: equals"
323
+ },
324
+ op_not_equals: {
325
+ id: "filter.operator.not_equals",
326
+ defaultMessage: "is not",
327
+ description: "Operator: not equals"
328
+ },
329
+ op_contains: {
330
+ id: "filter.operator.contains",
331
+ defaultMessage: "contains",
332
+ description: "Operator: contains"
333
+ },
334
+ op_not_contains: {
335
+ id: "filter.operator.not_contains",
336
+ defaultMessage: "does not contain",
337
+ description: "Operator: does not contain"
338
+ },
339
+ op_starts_with: {
340
+ id: "filter.operator.starts_with",
341
+ defaultMessage: "starts with",
342
+ description: "Operator: starts with"
343
+ },
344
+ op_ends_with: {
345
+ id: "filter.operator.ends_with",
346
+ defaultMessage: "ends with",
347
+ description: "Operator: ends with"
348
+ },
349
+ op_gt: {
350
+ id: "filter.operator.gt",
351
+ defaultMessage: "greater than",
352
+ description: "Operator: greater than"
353
+ },
354
+ op_lt: {
355
+ id: "filter.operator.lt",
356
+ defaultMessage: "less than",
357
+ description: "Operator: less than"
358
+ },
359
+ op_gte: {
360
+ id: "filter.operator.gte",
361
+ defaultMessage: "greater or equal",
362
+ description: "Operator: greater than or equal"
363
+ },
364
+ op_lte: {
365
+ id: "filter.operator.lte",
366
+ defaultMessage: "less or equal",
367
+ description: "Operator: less than or equal"
368
+ },
369
+ op_between: {
370
+ id: "filter.operator.between",
371
+ defaultMessage: "between",
372
+ description: "Operator: between"
373
+ },
374
+ op_is_empty: {
375
+ id: "filter.operator.is_empty",
376
+ defaultMessage: "is empty",
377
+ description: "Operator: is empty"
378
+ },
379
+ op_is_not_empty: {
380
+ id: "filter.operator.is_not_empty",
381
+ defaultMessage: "is not empty",
382
+ description: "Operator: is not empty"
383
+ }
384
+ });
385
+
386
+ // src/filter-value-editor.tsx
387
+ var import_jsx_runtime2 = require("react/jsx-runtime");
388
+ function FilterValueEditor({ filter, field }) {
389
+ var _a;
390
+ const { styles, updateFilter, setEditingFilterId } = useFilterContext();
391
+ const t = (0, import_i18n2.useSafeIntl)();
392
+ const operators = (_a = field.operators) != null ? _a : DEFAULT_OPERATORS[field.type];
393
+ const handleOperatorChange = (newOperator) => {
394
+ updateFilter(filter.id, { operator: newOperator });
395
+ if (newOperator === "is_empty" || newOperator === "is_not_empty") {
396
+ setEditingFilterId(null);
397
+ }
398
+ };
399
+ const handleValueChange = (newValue) => {
400
+ updateFilter(filter.id, { value: newValue });
401
+ };
402
+ const handleSelectValue = (newValue) => {
403
+ updateFilter(filter.id, { value: newValue });
404
+ setEditingFilterId(null);
405
+ };
406
+ const getOperatorLabel = (op) => {
407
+ const key = `op_${op}`;
408
+ return messages[key] ? t.formatMessage(messages[key]) : op;
409
+ };
410
+ const needsValue = filter.operator !== "is_empty" && filter.operator !== "is_not_empty";
411
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { "data-slot": "filter-value-editor", children: [
412
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: styles.editorHeader(), children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: styles.editorTitle(), children: field.label }) }),
413
+ operators.length > 1 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: styles.editorOperator(), children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
414
+ import_select.Select,
415
+ {
416
+ value: filter.operator,
417
+ onValueChange: (val) => handleOperatorChange(val),
418
+ size: "sm",
419
+ children: [
420
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_select.Select.Trigger, { className: "w-full", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_select.Select.Value, {}) }),
421
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_select.Select.Content, { children: operators.map((op) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_select.Select.Item, { value: op, children: getOperatorLabel(op) }, op)) })
422
+ ]
423
+ }
424
+ ) }),
425
+ needsValue && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: styles.editorInput(), children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
426
+ ValueInput,
427
+ {
428
+ field,
429
+ operator: filter.operator,
430
+ value: filter.value,
431
+ onChange: handleValueChange,
432
+ onSelect: handleSelectValue
433
+ }
434
+ ) })
435
+ ] });
436
+ }
437
+ function ValueInput({
438
+ field,
439
+ operator,
440
+ value,
441
+ onChange,
442
+ onSelect
443
+ }) {
444
+ var _a, _b, _c, _d, _e, _f;
445
+ const t = (0, import_i18n2.useSafeIntl)();
446
+ switch (field.type) {
447
+ case "text":
448
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
449
+ import_input.Input,
450
+ {
451
+ value: String(value != null ? value : ""),
452
+ onChange: (e) => onChange(e.target.value),
453
+ placeholder: (_a = field.placeholder) != null ? _a : t.formatMessage(messages.enter_value),
454
+ size: "sm",
455
+ autoFocus: true
456
+ }
457
+ );
458
+ case "number":
459
+ if (operator === "between") {
460
+ const [min, max] = Array.isArray(value) ? value : [void 0, void 0];
461
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex items-center gap-2", children: [
462
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
463
+ import_input.Input,
464
+ {
465
+ type: "number",
466
+ value: min != null ? min : "",
467
+ onChange: (e) => onChange([
468
+ e.target.value ? Number(e.target.value) : void 0,
469
+ max
470
+ ]),
471
+ placeholder: "Min",
472
+ min: field.min,
473
+ max: field.max,
474
+ step: field.step,
475
+ size: "sm",
476
+ autoFocus: true
477
+ }
478
+ ),
479
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "text-muted-foreground", children: "-" }),
480
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
481
+ import_input.Input,
482
+ {
483
+ type: "number",
484
+ value: max != null ? max : "",
485
+ onChange: (e) => onChange([
486
+ min,
487
+ e.target.value ? Number(e.target.value) : void 0
488
+ ]),
489
+ placeholder: "Max",
490
+ min: field.min,
491
+ max: field.max,
492
+ step: field.step,
493
+ size: "sm"
494
+ }
495
+ )
496
+ ] });
497
+ }
498
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
499
+ import_input.Input,
500
+ {
501
+ type: "number",
502
+ value: value !== void 0 && value !== null ? String(value) : "",
503
+ onChange: (e) => onChange(e.target.value ? Number(e.target.value) : void 0),
504
+ placeholder: (_b = field.placeholder) != null ? _b : t.formatMessage(messages.enter_value),
505
+ min: field.min,
506
+ max: field.max,
507
+ step: field.step,
508
+ size: "sm",
509
+ autoFocus: true
510
+ }
511
+ );
512
+ case "select":
513
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_select.Select, { value: String(value != null ? value : ""), onValueChange: onSelect, size: "sm", children: [
514
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_select.Select.Trigger, { className: "w-full", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
515
+ import_select.Select.Value,
516
+ {
517
+ placeholder: (_c = field.placeholder) != null ? _c : t.formatMessage(messages.select_value)
518
+ }
519
+ ) }),
520
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_select.Select.Content, { children: (_d = field.options) == null ? void 0 : _d.map((option) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
521
+ import_select.Select.Item,
522
+ {
523
+ value: option.value,
524
+ disabled: option.disabled,
525
+ children: [
526
+ option.icon && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "mr-2 size-4", children: option.icon }),
527
+ option.label
528
+ ]
529
+ },
530
+ option.value
531
+ )) })
532
+ ] });
533
+ case "multiselect": {
534
+ const selectedValues = Array.isArray(value) ? value : [];
535
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "space-y-2 max-h-[200px] overflow-auto", children: (_e = field.options) == null ? void 0 : _e.map((option) => (
536
+ // biome-ignore lint/a11y/noLabelWithoutControl: Checkbox is a custom form control inside the label
537
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
538
+ "label",
539
+ {
540
+ className: "flex items-center gap-2 cursor-pointer",
541
+ children: [
542
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
543
+ import_checkbox.Checkbox,
544
+ {
545
+ checked: selectedValues.includes(option.value),
546
+ onCheckedChange: (checked) => {
547
+ if (checked) {
548
+ onChange([...selectedValues, option.value]);
549
+ } else {
550
+ onChange(selectedValues.filter((v) => v !== option.value));
551
+ }
552
+ },
553
+ disabled: option.disabled
554
+ }
555
+ ),
556
+ option.icon && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "size-4", children: option.icon }),
557
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "text-sm", children: option.label })
558
+ ]
559
+ },
560
+ option.value
561
+ )
562
+ )) });
563
+ }
564
+ case "boolean":
565
+ return (
566
+ // biome-ignore lint/a11y/noLabelWithoutControl: Checkbox is a custom form control inside the label
567
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("label", { className: "flex items-center gap-2 cursor-pointer", children: [
568
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
569
+ import_checkbox.Checkbox,
570
+ {
571
+ checked: Boolean(value),
572
+ onCheckedChange: (checked) => onSelect(Boolean(checked))
573
+ }
574
+ ),
575
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "text-sm", children: field.label })
576
+ ] })
577
+ );
578
+ case "date":
579
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
580
+ import_input.Input,
581
+ {
582
+ type: "date",
583
+ value: value instanceof Date ? value.toISOString().split("T")[0] : String(value != null ? value : ""),
584
+ onChange: (e) => onChange(e.target.value ? new Date(e.target.value) : void 0),
585
+ size: "sm"
586
+ }
587
+ );
588
+ case "daterange": {
589
+ const [start, end] = Array.isArray(value) ? value : [void 0, void 0];
590
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex items-center gap-2", children: [
591
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
592
+ import_input.Input,
593
+ {
594
+ type: "date",
595
+ value: start instanceof Date ? start.toISOString().split("T")[0] : String(start != null ? start : ""),
596
+ onChange: (e) => onChange([
597
+ e.target.value ? new Date(e.target.value) : void 0,
598
+ end
599
+ ]),
600
+ size: "sm"
601
+ }
602
+ ),
603
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "text-muted-foreground", children: "-" }),
604
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
605
+ import_input.Input,
606
+ {
607
+ type: "date",
608
+ value: end instanceof Date ? end.toISOString().split("T")[0] : String(end != null ? end : ""),
609
+ onChange: (e) => onChange([
610
+ start,
611
+ e.target.value ? new Date(e.target.value) : void 0
612
+ ]),
613
+ size: "sm"
614
+ }
615
+ )
616
+ ] });
617
+ }
618
+ default:
619
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
620
+ import_input.Input,
621
+ {
622
+ value: String(value != null ? value : ""),
623
+ onChange: (e) => onChange(e.target.value),
624
+ placeholder: (_f = field.placeholder) != null ? _f : t.formatMessage(messages.enter_value),
625
+ size: "sm"
626
+ }
627
+ );
628
+ }
629
+ }
630
+
631
+ // src/filter-active.tsx
632
+ var import_jsx_runtime3 = require("react/jsx-runtime");
633
+ function FilterActive(props) {
634
+ const { showClearAll = true, className, ...rest } = props;
635
+ const { value, styles, clearFilters } = useFilterContext();
636
+ const t = (0, import_i18n3.useSafeIntl)();
637
+ if (value.length === 0) {
638
+ return null;
639
+ }
640
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
641
+ "div",
642
+ {
643
+ "data-slot": "filter-active",
644
+ className: (0, import_shared_utils2.cn)(styles.active(), className),
645
+ ...rest,
646
+ children: [
647
+ value.map((filter) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(FilterField, { filter }, filter.id)),
648
+ showClearAll && value.length > 1 && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
649
+ "button",
650
+ {
651
+ type: "button",
652
+ className: styles.clearAll(),
653
+ onClick: clearFilters,
654
+ children: t.formatMessage(messages.clear_all)
655
+ }
656
+ )
657
+ ]
658
+ }
659
+ );
660
+ }
661
+ FilterActive.displayName = "FilterActive";
662
+ function FilterField({ filter }) {
663
+ const { fields, styles, removeFilter, editingFilterId, setEditingFilterId } = useFilterContext();
664
+ const t = (0, import_i18n3.useSafeIntl)();
665
+ const field = fields.get(filter.fieldId);
666
+ if (!field) return null;
667
+ const isEditing = editingFilterId === filter.id;
668
+ const operatorKey = `op_${filter.operator}`;
669
+ const operatorLabel = messages[operatorKey] ? t.formatMessage(messages[operatorKey]) : filter.operator;
670
+ const valueDisplay = getValueDisplay(filter, field);
671
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
672
+ import_popover.Popover.Root,
673
+ {
674
+ open: isEditing,
675
+ onOpenChange: (open) => setEditingFilterId(open ? filter.id : null),
676
+ children: [
677
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_popover.Popover.Trigger, { "data-slot": "filter-field", className: styles.field(), children: [
678
+ field.icon && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: styles.menuItemIcon(), children: field.icon }),
679
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: styles.fieldLabel(), children: field.label }),
680
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: styles.fieldOperator(), children: operatorLabel }),
681
+ valueDisplay && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: styles.fieldValue(), children: valueDisplay }),
682
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
683
+ "button",
684
+ {
685
+ type: "button",
686
+ className: styles.fieldRemove(),
687
+ onClick: (e) => {
688
+ e.stopPropagation();
689
+ removeFilter(filter.id);
690
+ },
691
+ "aria-label": "Remove filter",
692
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_icons.CloseIcon, { className: "size-3" })
693
+ }
694
+ )
695
+ ] }),
696
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
697
+ import_popover.Popover.Content,
698
+ {
699
+ "data-slot": "filter-editor",
700
+ className: styles.editor(),
701
+ align: "start",
702
+ showArrow: false,
703
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(FilterValueEditor, { filter, field })
704
+ }
705
+ )
706
+ ]
707
+ }
708
+ );
709
+ }
710
+ function getValueDisplay(filter, field) {
711
+ var _a, _b, _c, _d;
712
+ const { value, operator } = filter;
713
+ if (operator === "is_empty" || operator === "is_not_empty") {
714
+ return null;
715
+ }
716
+ if (value === void 0 || value === null || value === "") {
717
+ return "...";
718
+ }
719
+ if (field.type === "boolean") {
720
+ return value ? "Yes" : "No";
721
+ }
722
+ if (field.type === "select" && field.options) {
723
+ const option = field.options.find((o) => o.value === value);
724
+ return (_a = option == null ? void 0 : option.label) != null ? _a : String(value);
725
+ }
726
+ if (field.type === "multiselect" && Array.isArray(value)) {
727
+ if (value.length === 0) return "...";
728
+ if (value.length === 1 && field.options) {
729
+ const option = field.options.find((o) => o.value === value[0]);
730
+ return (_b = option == null ? void 0 : option.label) != null ? _b : String(value[0]);
731
+ }
732
+ return `${value.length} selected`;
733
+ }
734
+ if (field.type === "date" && value instanceof Date) {
735
+ return value.toLocaleDateString();
736
+ }
737
+ if (field.type === "daterange" && Array.isArray(value)) {
738
+ const [start, end] = value;
739
+ const startStr = start instanceof Date ? start.toLocaleDateString() : "...";
740
+ const endStr = end instanceof Date ? end.toLocaleDateString() : "...";
741
+ return `${startStr} - ${endStr}`;
742
+ }
743
+ if (operator === "between" && Array.isArray(value)) {
744
+ return `${(_c = value[0]) != null ? _c : "..."} - ${(_d = value[1]) != null ? _d : "..."}`;
745
+ }
746
+ return String(value);
747
+ }
748
+
749
+ // src/filter-menu.tsx
750
+ var import_dropdown_menu2 = require("@kopexa/dropdown-menu");
751
+ var import_shared_utils3 = require("@kopexa/shared-utils");
752
+ var import_react = require("react");
753
+ var import_jsx_runtime4 = require("react/jsx-runtime");
754
+ function FilterMenu(props) {
755
+ var _a;
756
+ const { children, className } = props;
757
+ const { fields, styles, addFilter, allowMultiple, value } = useFilterContext();
758
+ const fieldGroups = /* @__PURE__ */ new Map();
759
+ for (const [id, field] of fields) {
760
+ const group = field.group;
761
+ if (!fieldGroups.has(group)) {
762
+ fieldGroups.set(group, /* @__PURE__ */ new Map());
763
+ }
764
+ (_a = fieldGroups.get(group)) == null ? void 0 : _a.set(id, field);
765
+ }
766
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
767
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: { display: "none" }, "aria-hidden": "true", children }),
768
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
769
+ import_dropdown_menu2.DropdownMenu.Content,
770
+ {
771
+ "data-slot": "filter-menu",
772
+ className: (0, import_shared_utils3.cn)("min-w-[220px]", className),
773
+ align: "start",
774
+ children: fields.size === 0 ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "px-2 py-3 text-sm text-muted-foreground text-center", children: "Loading..." }) : Array.from(fieldGroups.entries()).map(([groupLabel, groupFields]) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_dropdown_menu2.DropdownMenu.Group, { children: [
775
+ groupLabel && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_dropdown_menu2.DropdownMenu.Label, { children: groupLabel }),
776
+ Array.from(groupFields.values()).map((field) => {
777
+ const hasFilter = value.some((f) => f.fieldId === field.id);
778
+ const isDisabled = field.disabled || !allowMultiple && hasFilter;
779
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
780
+ import_dropdown_menu2.DropdownMenu.Item,
781
+ {
782
+ "data-slot": "filter-item",
783
+ disabled: isDisabled,
784
+ onSelect: () => addFilter(field.id),
785
+ children: [
786
+ field.icon && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: styles.menuItemIcon(), children: field.icon }),
787
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: field.label })
788
+ ]
789
+ },
790
+ field.id
791
+ );
792
+ })
793
+ ] }, groupLabel != null ? groupLabel : "ungrouped"))
794
+ }
795
+ )
796
+ ] });
797
+ }
798
+ FilterMenu.displayName = "FilterMenu";
799
+ function FilterGroup(props) {
800
+ const { label, children } = props;
801
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(FilterGroupProvider, { label, children });
802
+ }
803
+ FilterGroup.displayName = "FilterGroup";
804
+ var FilterGroupContext = (0, import_react.createContext)(void 0);
805
+ function FilterGroupProvider({
806
+ label,
807
+ children
808
+ }) {
809
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(FilterGroupContext.Provider, { value: label, children });
810
+ }
811
+ function useFilterGroup() {
812
+ return (0, import_react.useContext)(FilterGroupContext);
813
+ }
814
+ function FilterItem(props) {
815
+ const {
816
+ id,
817
+ label,
818
+ icon,
819
+ type,
820
+ options,
821
+ operators,
822
+ placeholder,
823
+ min,
824
+ max,
825
+ step,
826
+ searchable,
827
+ disabled
828
+ } = props;
829
+ const { registerField, unregisterField } = useFilterContext();
830
+ const group = useFilterGroup();
831
+ (0, import_react.useLayoutEffect)(() => {
832
+ registerField({
833
+ id,
834
+ label,
835
+ icon,
836
+ type,
837
+ options,
838
+ operators,
839
+ placeholder,
840
+ min,
841
+ max,
842
+ step,
843
+ searchable,
844
+ group,
845
+ disabled
846
+ });
847
+ return () => unregisterField(id);
848
+ }, [
849
+ id,
850
+ label,
851
+ icon,
852
+ type,
853
+ options,
854
+ operators,
855
+ placeholder,
856
+ min,
857
+ max,
858
+ step,
859
+ searchable,
860
+ group,
861
+ disabled,
862
+ registerField,
863
+ unregisterField
864
+ ]);
865
+ return null;
866
+ }
867
+ FilterItem.displayName = "FilterItem";
868
+ function FilterMenuSeparator() {
869
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_dropdown_menu2.DropdownMenu.Separator, { "data-slot": "filter-menu-separator" });
870
+ }
871
+ FilterMenuSeparator.displayName = "FilterMenuSeparator";
872
+
873
+ // src/filter-trigger.tsx
874
+ var import_dropdown_menu3 = require("@kopexa/dropdown-menu");
875
+ var import_i18n4 = require("@kopexa/i18n");
876
+ var import_icons2 = require("@kopexa/icons");
877
+ var import_shared_utils4 = require("@kopexa/shared-utils");
878
+ var import_jsx_runtime5 = require("react/jsx-runtime");
879
+ function FilterTrigger(props) {
880
+ const { className, children, icon, ...rest } = props;
881
+ const { styles } = useFilterContext();
882
+ const t = (0, import_i18n4.useSafeIntl)();
883
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
884
+ import_dropdown_menu3.DropdownMenu.Trigger,
885
+ {
886
+ "data-slot": "filter-trigger",
887
+ className: (0, import_shared_utils4.cn)(styles.trigger(), className),
888
+ ...rest,
889
+ children: [
890
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: styles.triggerIcon(), children: icon != null ? icon : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_icons2.PlusIcon, { className: "size-4" }) }),
891
+ children != null ? children : t.formatMessage(messages.add_filter)
892
+ ]
893
+ }
894
+ );
895
+ }
896
+ FilterTrigger.displayName = "FilterTrigger";
897
+ // Annotate the CommonJS export names for ESM import in node:
898
+ 0 && (module.exports = {
899
+ Filter,
900
+ FilterActive,
901
+ FilterGroup,
902
+ FilterItem,
903
+ FilterMenu,
904
+ FilterMenuSeparator,
905
+ FilterTrigger,
906
+ FilterValueEditor,
907
+ filterMessages,
908
+ useFilterContext
909
+ });