@kopexa/filter 0.0.17 → 0.0.19

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.
@@ -15,7 +15,11 @@ import { useSafeIntl } from "@kopexa/i18n";
15
15
  import { Input } from "@kopexa/input";
16
16
  import { Select } from "@kopexa/select";
17
17
  import { jsx, jsxs } from "react/jsx-runtime";
18
- function FilterValueEditor({ filter, field }) {
18
+ function FilterValueEditor({
19
+ filter,
20
+ field,
21
+ hideOperator
22
+ }) {
19
23
  var _a;
20
24
  const { styles, updateFilter, setEditingFilterId } = useFilterContext();
21
25
  const t = useSafeIntl();
@@ -40,7 +44,7 @@ function FilterValueEditor({ filter, field }) {
40
44
  const needsValue = filter.operator !== "is_empty" && filter.operator !== "is_not_empty";
41
45
  return /* @__PURE__ */ jsxs("div", { "data-slot": "filter-value-editor", children: [
42
46
  /* @__PURE__ */ jsx("div", { className: styles.editorHeader(), children: /* @__PURE__ */ jsx("span", { className: styles.editorTitle(), children: field.label }) }),
43
- operators.length > 1 && /* @__PURE__ */ jsx("div", { className: styles.editorOperator(), children: /* @__PURE__ */ jsxs(
47
+ !hideOperator && operators.length > 1 && /* @__PURE__ */ jsx("div", { className: styles.editorOperator(), children: /* @__PURE__ */ jsxs(
44
48
  Select,
45
49
  {
46
50
  value: filter.operator,
@@ -0,0 +1,283 @@
1
+ "use client";
2
+ import {
3
+ FilterValueEditor
4
+ } from "./chunk-3ZBNWXRA.mjs";
5
+ import {
6
+ messages
7
+ } from "./chunk-URDCG5NI.mjs";
8
+ import {
9
+ useFilterContext
10
+ } from "./chunk-I3Z2T4N2.mjs";
11
+ import {
12
+ DEFAULT_OPERATORS
13
+ } from "./chunk-PHESMHTT.mjs";
14
+
15
+ // src/filter-active.tsx
16
+ import { useSafeIntl } from "@kopexa/i18n";
17
+ import { ChevronDownIcon, CloseIcon } from "@kopexa/icons";
18
+ import { Popover } from "@kopexa/popover";
19
+ import { cn } from "@kopexa/shared-utils";
20
+ import { Switch } from "@kopexa/switch";
21
+ import { jsx, jsxs } from "react/jsx-runtime";
22
+ function FilterActive(props) {
23
+ const { showClearAll = true, className, ...rest } = props;
24
+ const { value, styles, clearFilters } = useFilterContext();
25
+ const t = useSafeIntl();
26
+ if (value.length === 0) {
27
+ return null;
28
+ }
29
+ return /* @__PURE__ */ jsxs(
30
+ "div",
31
+ {
32
+ "data-slot": "filter-active",
33
+ className: cn(styles.active(), className),
34
+ ...rest,
35
+ children: [
36
+ value.map((filter) => /* @__PURE__ */ jsx(FilterField, { filter }, filter.id)),
37
+ showClearAll && value.length > 1 && /* @__PURE__ */ jsx(
38
+ "button",
39
+ {
40
+ type: "button",
41
+ className: styles.clearAll(),
42
+ onClick: clearFilters,
43
+ children: t.formatMessage(messages.clear_all)
44
+ }
45
+ )
46
+ ]
47
+ }
48
+ );
49
+ }
50
+ FilterActive.displayName = "FilterActive";
51
+ function FilterField({ filter }) {
52
+ var _a;
53
+ const { fields, styles, removeFilter, updateFilter } = useFilterContext();
54
+ const t = useSafeIntl();
55
+ const field = fields.get(filter.fieldId);
56
+ if (!field) return null;
57
+ const availableOperators = (_a = field.operators) != null ? _a : DEFAULT_OPERATORS[field.type];
58
+ const getOperatorLabel = (op) => {
59
+ const opKey = `op_${op}`;
60
+ return messages[opKey] ? t.formatMessage(messages[opKey]) : op;
61
+ };
62
+ const handleOperatorChange = (e) => {
63
+ const newOperator = e.target.value;
64
+ if (newOperator === "is_empty" || newOperator === "is_not_empty") {
65
+ updateFilter(filter.id, { operator: newOperator, value: void 0 });
66
+ } else {
67
+ updateFilter(filter.id, { operator: newOperator });
68
+ }
69
+ };
70
+ const isEmptyOperator = filter.operator === "is_empty" || filter.operator === "is_not_empty";
71
+ const hasCustomEditor = !!field.renderEditor;
72
+ const useInlineInput = !hasCustomEditor && (field.type === "text" || field.type === "number");
73
+ const useInlineSwitch = !hasCustomEditor && field.type === "boolean" && !isEmptyOperator;
74
+ const usePopover = !useInlineInput && !useInlineSwitch && !isEmptyOperator;
75
+ const currentOperatorLabel = getOperatorLabel(filter.operator);
76
+ const operatorWidth = `${currentOperatorLabel.length + 2}ch`;
77
+ return /* @__PURE__ */ jsxs("div", { "data-slot": "filter-field", className: styles.field(), children: [
78
+ /* @__PURE__ */ jsxs("span", { className: styles.fieldLabel(), children: [
79
+ field.icon && /* @__PURE__ */ jsx("span", { className: "size-4 flex-shrink-0", children: field.icon }),
80
+ /* @__PURE__ */ jsx("span", { children: field.label })
81
+ ] }),
82
+ /* @__PURE__ */ jsxs("span", { className: styles.fieldOperator(), children: [
83
+ /* @__PURE__ */ jsx(
84
+ "select",
85
+ {
86
+ className: styles.fieldOperatorSelect(),
87
+ value: filter.operator,
88
+ onChange: handleOperatorChange,
89
+ title: currentOperatorLabel,
90
+ style: { width: operatorWidth },
91
+ children: availableOperators.map((op) => /* @__PURE__ */ jsx("option", { value: op, children: getOperatorLabel(op) }, op))
92
+ }
93
+ ),
94
+ /* @__PURE__ */ jsx(ChevronDownIcon, { className: styles.fieldOperatorIcon() })
95
+ ] }),
96
+ useInlineInput && /* @__PURE__ */ jsx(InlineValueInput, { filter, field }),
97
+ useInlineSwitch && /* @__PURE__ */ jsx("span", { className: styles.fieldValue(), children: /* @__PURE__ */ jsx(
98
+ Switch,
99
+ {
100
+ size: "sm",
101
+ checked: Boolean(filter.value),
102
+ onCheckedChange: (checked) => {
103
+ updateFilter(filter.id, { value: checked });
104
+ },
105
+ "aria-label": field.label
106
+ }
107
+ ) }),
108
+ usePopover && /* @__PURE__ */ jsx(PopoverValueEditor, { filter, field }),
109
+ /* @__PURE__ */ jsx(
110
+ "button",
111
+ {
112
+ type: "button",
113
+ className: styles.fieldRemove(),
114
+ onClick: () => removeFilter(filter.id),
115
+ "aria-label": "Remove filter",
116
+ children: /* @__PURE__ */ jsx(CloseIcon, { className: "size-3" })
117
+ }
118
+ )
119
+ ] });
120
+ }
121
+ function getInputWidth(value, placeholder, minWidth = 4) {
122
+ const displayValue = value !== void 0 && value !== null && value !== "" ? String(value) : placeholder;
123
+ const width = Math.max(displayValue.length + 1, minWidth);
124
+ return `${width}ch`;
125
+ }
126
+ function InlineValueInput({
127
+ filter,
128
+ field
129
+ }) {
130
+ var _a;
131
+ const { styles, updateFilter } = useFilterContext();
132
+ const t = useSafeIntl();
133
+ const handleChange = (e) => {
134
+ const val = e.target.value;
135
+ if (field.type === "number") {
136
+ updateFilter(filter.id, { value: val ? Number(val) : void 0 });
137
+ } else {
138
+ updateFilter(filter.id, { value: val });
139
+ }
140
+ };
141
+ const placeholder = (_a = field.placeholder) != null ? _a : t.formatMessage(messages.enter_value);
142
+ if (filter.operator === "between") {
143
+ const [min, max] = Array.isArray(filter.value) ? filter.value : [void 0, void 0];
144
+ return /* @__PURE__ */ jsxs("span", { className: cn(styles.fieldValue(), "gap-1"), children: [
145
+ /* @__PURE__ */ jsx(
146
+ "input",
147
+ {
148
+ type: "number",
149
+ className: styles.fieldValueInput(),
150
+ value: min != null ? min : "",
151
+ onChange: (e) => {
152
+ const val = e.target.value ? Number(e.target.value) : void 0;
153
+ updateFilter(filter.id, { value: [val, max] });
154
+ },
155
+ placeholder: "Min",
156
+ style: { width: getInputWidth(min, "Min", 3) },
157
+ min: field.min,
158
+ max: field.max,
159
+ step: field.step
160
+ }
161
+ ),
162
+ /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "\u2013" }),
163
+ /* @__PURE__ */ jsx(
164
+ "input",
165
+ {
166
+ type: "number",
167
+ className: styles.fieldValueInput(),
168
+ value: max != null ? max : "",
169
+ onChange: (e) => {
170
+ const val = e.target.value ? Number(e.target.value) : void 0;
171
+ updateFilter(filter.id, { value: [min, val] });
172
+ },
173
+ placeholder: "Max",
174
+ style: { width: getInputWidth(max, "Max", 3) },
175
+ min: field.min,
176
+ max: field.max,
177
+ step: field.step
178
+ }
179
+ )
180
+ ] });
181
+ }
182
+ return /* @__PURE__ */ jsx("span", { className: styles.fieldValue(), children: /* @__PURE__ */ jsx(
183
+ "input",
184
+ {
185
+ type: field.type === "number" ? "number" : "text",
186
+ className: styles.fieldValueInput(),
187
+ value: filter.value !== void 0 && filter.value !== null ? String(filter.value) : "",
188
+ onChange: handleChange,
189
+ placeholder,
190
+ style: { width: getInputWidth(filter.value, placeholder, 6) },
191
+ min: field.min,
192
+ max: field.max,
193
+ step: field.step
194
+ }
195
+ ) });
196
+ }
197
+ function PopoverValueEditor({
198
+ filter,
199
+ field
200
+ }) {
201
+ const { styles, updateFilter } = useFilterContext();
202
+ const valueDisplay = field.renderValue ? field.renderValue({ filter, field }) : getValueDisplay(filter, field);
203
+ const hasValue = filter.value !== void 0 && filter.value !== null && filter.value !== "" && !(Array.isArray(filter.value) && filter.value.length === 0);
204
+ const handleCustomChange = (value) => {
205
+ updateFilter(filter.id, { value });
206
+ };
207
+ return /* @__PURE__ */ jsxs(Popover.Root, { children: [
208
+ /* @__PURE__ */ jsxs(
209
+ Popover.Trigger,
210
+ {
211
+ className: cn(styles.fieldValue(), "cursor-pointer hover:bg-muted/30"),
212
+ render: /* @__PURE__ */ jsx("div", {}),
213
+ nativeButton: false,
214
+ children: [
215
+ /* @__PURE__ */ jsx("span", { className: styles.fieldValueText(), children: hasValue ? valueDisplay : "Select..." }),
216
+ /* @__PURE__ */ jsx(ChevronDownIcon, { className: styles.fieldValueIcon() })
217
+ ]
218
+ }
219
+ ),
220
+ /* @__PURE__ */ jsx(
221
+ Popover.Content,
222
+ {
223
+ "data-slot": "filter-editor",
224
+ className: styles.editor(),
225
+ align: "start",
226
+ showArrow: false,
227
+ children: field.renderEditor ? (
228
+ // Use custom editor if provided
229
+ field.renderEditor({
230
+ filter,
231
+ field,
232
+ onChange: handleCustomChange
233
+ })
234
+ ) : (
235
+ // Default editor
236
+ /* @__PURE__ */ jsx(FilterValueEditor, { filter, field, hideOperator: true })
237
+ )
238
+ }
239
+ )
240
+ ] });
241
+ }
242
+ function getValueDisplay(filter, field) {
243
+ var _a, _b, _c, _d;
244
+ const { value, operator } = filter;
245
+ if (operator === "is_empty" || operator === "is_not_empty") {
246
+ return null;
247
+ }
248
+ if (value === void 0 || value === null || value === "") {
249
+ return null;
250
+ }
251
+ if (field.type === "boolean") {
252
+ return value ? "Yes" : "No";
253
+ }
254
+ if (field.type === "select" && field.options) {
255
+ const option = field.options.find((o) => o.value === value);
256
+ return (_a = option == null ? void 0 : option.label) != null ? _a : String(value);
257
+ }
258
+ if (field.type === "multiselect" && Array.isArray(value)) {
259
+ if (value.length === 0) return null;
260
+ if (value.length === 1 && field.options) {
261
+ const option = field.options.find((o) => o.value === value[0]);
262
+ return (_b = option == null ? void 0 : option.label) != null ? _b : String(value[0]);
263
+ }
264
+ return `${value.length} selected`;
265
+ }
266
+ if (field.type === "date" && value instanceof Date) {
267
+ return value.toLocaleDateString();
268
+ }
269
+ if (field.type === "daterange" && Array.isArray(value)) {
270
+ const [start, end] = value;
271
+ const startStr = start instanceof Date ? start.toLocaleDateString() : "...";
272
+ const endStr = end instanceof Date ? end.toLocaleDateString() : "...";
273
+ return `${startStr} \u2013 ${endStr}`;
274
+ }
275
+ if (operator === "between" && Array.isArray(value)) {
276
+ return `${(_c = value[0]) != null ? _c : "..."} \u2013 ${(_d = value[1]) != null ? _d : "..."}`;
277
+ }
278
+ return String(value);
279
+ }
280
+
281
+ export {
282
+ FilterActive
283
+ };
@@ -85,7 +85,9 @@ function FilterItem(props) {
85
85
  max,
86
86
  step,
87
87
  searchable,
88
- disabled
88
+ disabled,
89
+ renderEditor,
90
+ renderValue
89
91
  } = props;
90
92
  const { registerField, unregisterField } = useFilterContext();
91
93
  const group = useFilterGroup();
@@ -103,7 +105,9 @@ function FilterItem(props) {
103
105
  step,
104
106
  searchable,
105
107
  group,
106
- disabled
108
+ disabled,
109
+ renderEditor,
110
+ renderValue
107
111
  });
108
112
  return () => unregisterField(id);
109
113
  }, [
@@ -120,6 +124,8 @@ function FilterItem(props) {
120
124
  searchable,
121
125
  group,
122
126
  disabled,
127
+ renderEditor,
128
+ renderValue,
123
129
  registerField,
124
130
  unregisterField
125
131
  ]);
@@ -39,12 +39,6 @@ var [FilterProvider, useFilterContext] = (0, import_react_utils.createContext)({
39
39
  errorMessage: "useFilterContext must be used within a Filter component. Make sure to wrap your FilterMenu, FilterTrigger, etc. inside a <Filter> component."
40
40
  });
41
41
 
42
- // src/filter-value-editor.tsx
43
- var import_checkbox = require("@kopexa/checkbox");
44
- var import_i18n2 = require("@kopexa/i18n");
45
- var import_input = require("@kopexa/input");
46
- var import_select = require("@kopexa/select");
47
-
48
42
  // src/filter-types.ts
49
43
  var DEFAULT_OPERATORS = {
50
44
  text: [
@@ -65,6 +59,12 @@ var DEFAULT_OPERATORS = {
65
59
  boolean: ["equals", "not_equals", "is_empty", "is_not_empty"]
66
60
  };
67
61
 
62
+ // src/filter-value-editor.tsx
63
+ var import_checkbox = require("@kopexa/checkbox");
64
+ var import_i18n2 = require("@kopexa/i18n");
65
+ var import_input = require("@kopexa/input");
66
+ var import_select = require("@kopexa/select");
67
+
68
68
  // src/messages.ts
69
69
  var import_i18n = require("@kopexa/i18n");
70
70
  var messages = (0, import_i18n.defineMessages)({
@@ -173,7 +173,11 @@ var messages = (0, import_i18n.defineMessages)({
173
173
 
174
174
  // src/filter-value-editor.tsx
175
175
  var import_jsx_runtime = require("react/jsx-runtime");
176
- function FilterValueEditor({ filter, field }) {
176
+ function FilterValueEditor({
177
+ filter,
178
+ field,
179
+ hideOperator
180
+ }) {
177
181
  var _a;
178
182
  const { styles, updateFilter, setEditingFilterId } = useFilterContext();
179
183
  const t = (0, import_i18n2.useSafeIntl)();
@@ -198,7 +202,7 @@ function FilterValueEditor({ filter, field }) {
198
202
  const needsValue = filter.operator !== "is_empty" && filter.operator !== "is_not_empty";
199
203
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { "data-slot": "filter-value-editor", children: [
200
204
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: styles.editorHeader(), children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: styles.editorTitle(), children: field.label }) }),
201
- operators.length > 1 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: styles.editorOperator(), children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
205
+ !hideOperator && operators.length > 1 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: styles.editorOperator(), children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
202
206
  import_select.Select,
203
207
  {
204
208
  value: filter.operator,
@@ -448,81 +452,195 @@ function FilterActive(props) {
448
452
  }
449
453
  FilterActive.displayName = "FilterActive";
450
454
  function FilterField({ filter }) {
451
- const {
452
- fields,
453
- styles,
454
- removeFilter,
455
- editingFilterId,
456
- setEditingFilterId,
457
- updateFilter
458
- } = useFilterContext();
455
+ var _a;
456
+ const { fields, styles, removeFilter, updateFilter } = useFilterContext();
459
457
  const t = (0, import_i18n3.useSafeIntl)();
460
458
  const field = fields.get(filter.fieldId);
461
459
  if (!field) return null;
462
- const isEditing = editingFilterId === filter.id;
463
- const operatorKey = `op_${filter.operator}`;
464
- const operatorLabel = messages[operatorKey] ? t.formatMessage(messages[operatorKey]) : filter.operator;
465
- const isBooleanWithValue = field.type === "boolean" && filter.operator !== "is_empty" && filter.operator !== "is_not_empty";
466
- const valueDisplay = isBooleanWithValue ? null : getValueDisplay(filter, field);
467
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
468
- import_popover.Popover.Root,
460
+ const availableOperators = (_a = field.operators) != null ? _a : DEFAULT_OPERATORS[field.type];
461
+ const getOperatorLabel = (op) => {
462
+ const opKey = `op_${op}`;
463
+ return messages[opKey] ? t.formatMessage(messages[opKey]) : op;
464
+ };
465
+ const handleOperatorChange = (e) => {
466
+ const newOperator = e.target.value;
467
+ if (newOperator === "is_empty" || newOperator === "is_not_empty") {
468
+ updateFilter(filter.id, { operator: newOperator, value: void 0 });
469
+ } else {
470
+ updateFilter(filter.id, { operator: newOperator });
471
+ }
472
+ };
473
+ const isEmptyOperator = filter.operator === "is_empty" || filter.operator === "is_not_empty";
474
+ const hasCustomEditor = !!field.renderEditor;
475
+ const useInlineInput = !hasCustomEditor && (field.type === "text" || field.type === "number");
476
+ const useInlineSwitch = !hasCustomEditor && field.type === "boolean" && !isEmptyOperator;
477
+ const usePopover = !useInlineInput && !useInlineSwitch && !isEmptyOperator;
478
+ const currentOperatorLabel = getOperatorLabel(filter.operator);
479
+ const operatorWidth = `${currentOperatorLabel.length + 2}ch`;
480
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { "data-slot": "filter-field", className: styles.field(), children: [
481
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: styles.fieldLabel(), children: [
482
+ field.icon && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "size-4 flex-shrink-0", children: field.icon }),
483
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: field.label })
484
+ ] }),
485
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: styles.fieldOperator(), children: [
486
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
487
+ "select",
488
+ {
489
+ className: styles.fieldOperatorSelect(),
490
+ value: filter.operator,
491
+ onChange: handleOperatorChange,
492
+ title: currentOperatorLabel,
493
+ style: { width: operatorWidth },
494
+ children: availableOperators.map((op) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("option", { value: op, children: getOperatorLabel(op) }, op))
495
+ }
496
+ ),
497
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_icons.ChevronDownIcon, { className: styles.fieldOperatorIcon() })
498
+ ] }),
499
+ useInlineInput && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(InlineValueInput, { filter, field }),
500
+ useInlineSwitch && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: styles.fieldValue(), children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
501
+ import_switch.Switch,
502
+ {
503
+ size: "sm",
504
+ checked: Boolean(filter.value),
505
+ onCheckedChange: (checked) => {
506
+ updateFilter(filter.id, { value: checked });
507
+ },
508
+ "aria-label": field.label
509
+ }
510
+ ) }),
511
+ usePopover && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(PopoverValueEditor, { filter, field }),
512
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
513
+ "button",
514
+ {
515
+ type: "button",
516
+ className: styles.fieldRemove(),
517
+ onClick: () => removeFilter(filter.id),
518
+ "aria-label": "Remove filter",
519
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_icons.CloseIcon, { className: "size-3" })
520
+ }
521
+ )
522
+ ] });
523
+ }
524
+ function getInputWidth(value, placeholder, minWidth = 4) {
525
+ const displayValue = value !== void 0 && value !== null && value !== "" ? String(value) : placeholder;
526
+ const width = Math.max(displayValue.length + 1, minWidth);
527
+ return `${width}ch`;
528
+ }
529
+ function InlineValueInput({
530
+ filter,
531
+ field
532
+ }) {
533
+ var _a;
534
+ const { styles, updateFilter } = useFilterContext();
535
+ const t = (0, import_i18n3.useSafeIntl)();
536
+ const handleChange = (e) => {
537
+ const val = e.target.value;
538
+ if (field.type === "number") {
539
+ updateFilter(filter.id, { value: val ? Number(val) : void 0 });
540
+ } else {
541
+ updateFilter(filter.id, { value: val });
542
+ }
543
+ };
544
+ const placeholder = (_a = field.placeholder) != null ? _a : t.formatMessage(messages.enter_value);
545
+ if (filter.operator === "between") {
546
+ const [min, max] = Array.isArray(filter.value) ? filter.value : [void 0, void 0];
547
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: (0, import_shared_utils.cn)(styles.fieldValue(), "gap-1"), children: [
548
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
549
+ "input",
550
+ {
551
+ type: "number",
552
+ className: styles.fieldValueInput(),
553
+ value: min != null ? min : "",
554
+ onChange: (e) => {
555
+ const val = e.target.value ? Number(e.target.value) : void 0;
556
+ updateFilter(filter.id, { value: [val, max] });
557
+ },
558
+ placeholder: "Min",
559
+ style: { width: getInputWidth(min, "Min", 3) },
560
+ min: field.min,
561
+ max: field.max,
562
+ step: field.step
563
+ }
564
+ ),
565
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "text-muted-foreground", children: "\u2013" }),
566
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
567
+ "input",
568
+ {
569
+ type: "number",
570
+ className: styles.fieldValueInput(),
571
+ value: max != null ? max : "",
572
+ onChange: (e) => {
573
+ const val = e.target.value ? Number(e.target.value) : void 0;
574
+ updateFilter(filter.id, { value: [min, val] });
575
+ },
576
+ placeholder: "Max",
577
+ style: { width: getInputWidth(max, "Max", 3) },
578
+ min: field.min,
579
+ max: field.max,
580
+ step: field.step
581
+ }
582
+ )
583
+ ] });
584
+ }
585
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: styles.fieldValue(), children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
586
+ "input",
469
587
  {
470
- open: isEditing,
471
- onOpenChange: (open) => setEditingFilterId(open ? filter.id : null),
472
- children: [
473
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
474
- import_popover.Popover.Trigger,
475
- {
476
- "data-slot": "filter-field",
477
- className: styles.field(),
478
- render: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", {}),
479
- nativeButton: false,
480
- children: [
481
- field.icon && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: styles.menuItemIcon(), children: field.icon }),
482
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: styles.fieldLabel(), children: field.label }),
483
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: styles.fieldOperator(), children: operatorLabel }),
484
- isBooleanWithValue && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
485
- import_switch.Switch,
486
- {
487
- size: "sm",
488
- checked: Boolean(filter.value),
489
- onCheckedChange: (checked) => {
490
- updateFilter(filter.id, { value: checked });
491
- },
492
- onClick: (e) => e.stopPropagation(),
493
- "aria-label": field.label
494
- }
495
- ),
496
- valueDisplay && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: styles.fieldValue(), children: valueDisplay }),
497
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
498
- "button",
499
- {
500
- type: "button",
501
- className: styles.fieldRemove(),
502
- onClick: (e) => {
503
- e.stopPropagation();
504
- removeFilter(filter.id);
505
- },
506
- "aria-label": "Remove filter",
507
- children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_icons.CloseIcon, { className: "size-3" })
508
- }
509
- )
510
- ]
511
- }
512
- ),
513
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
514
- import_popover.Popover.Content,
515
- {
516
- "data-slot": "filter-editor",
517
- className: styles.editor(),
518
- align: "start",
519
- showArrow: false,
520
- children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(FilterValueEditor, { filter, field })
521
- }
522
- )
523
- ]
588
+ type: field.type === "number" ? "number" : "text",
589
+ className: styles.fieldValueInput(),
590
+ value: filter.value !== void 0 && filter.value !== null ? String(filter.value) : "",
591
+ onChange: handleChange,
592
+ placeholder,
593
+ style: { width: getInputWidth(filter.value, placeholder, 6) },
594
+ min: field.min,
595
+ max: field.max,
596
+ step: field.step
524
597
  }
525
- );
598
+ ) });
599
+ }
600
+ function PopoverValueEditor({
601
+ filter,
602
+ field
603
+ }) {
604
+ const { styles, updateFilter } = useFilterContext();
605
+ const valueDisplay = field.renderValue ? field.renderValue({ filter, field }) : getValueDisplay(filter, field);
606
+ const hasValue = filter.value !== void 0 && filter.value !== null && filter.value !== "" && !(Array.isArray(filter.value) && filter.value.length === 0);
607
+ const handleCustomChange = (value) => {
608
+ updateFilter(filter.id, { value });
609
+ };
610
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_popover.Popover.Root, { children: [
611
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
612
+ import_popover.Popover.Trigger,
613
+ {
614
+ className: (0, import_shared_utils.cn)(styles.fieldValue(), "cursor-pointer hover:bg-muted/30"),
615
+ render: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", {}),
616
+ nativeButton: false,
617
+ children: [
618
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: styles.fieldValueText(), children: hasValue ? valueDisplay : "Select..." }),
619
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_icons.ChevronDownIcon, { className: styles.fieldValueIcon() })
620
+ ]
621
+ }
622
+ ),
623
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
624
+ import_popover.Popover.Content,
625
+ {
626
+ "data-slot": "filter-editor",
627
+ className: styles.editor(),
628
+ align: "start",
629
+ showArrow: false,
630
+ children: field.renderEditor ? (
631
+ // Use custom editor if provided
632
+ field.renderEditor({
633
+ filter,
634
+ field,
635
+ onChange: handleCustomChange
636
+ })
637
+ ) : (
638
+ // Default editor
639
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(FilterValueEditor, { filter, field, hideOperator: true })
640
+ )
641
+ }
642
+ )
643
+ ] });
526
644
  }
527
645
  function getValueDisplay(filter, field) {
528
646
  var _a, _b, _c, _d;
@@ -531,7 +649,7 @@ function getValueDisplay(filter, field) {
531
649
  return null;
532
650
  }
533
651
  if (value === void 0 || value === null || value === "") {
534
- return "...";
652
+ return null;
535
653
  }
536
654
  if (field.type === "boolean") {
537
655
  return value ? "Yes" : "No";
@@ -541,7 +659,7 @@ function getValueDisplay(filter, field) {
541
659
  return (_a = option == null ? void 0 : option.label) != null ? _a : String(value);
542
660
  }
543
661
  if (field.type === "multiselect" && Array.isArray(value)) {
544
- if (value.length === 0) return "...";
662
+ if (value.length === 0) return null;
545
663
  if (value.length === 1 && field.options) {
546
664
  const option = field.options.find((o) => o.value === value[0]);
547
665
  return (_b = option == null ? void 0 : option.label) != null ? _b : String(value[0]);
@@ -555,10 +673,10 @@ function getValueDisplay(filter, field) {
555
673
  const [start, end] = value;
556
674
  const startStr = start instanceof Date ? start.toLocaleDateString() : "...";
557
675
  const endStr = end instanceof Date ? end.toLocaleDateString() : "...";
558
- return `${startStr} - ${endStr}`;
676
+ return `${startStr} \u2013 ${endStr}`;
559
677
  }
560
678
  if (operator === "between" && Array.isArray(value)) {
561
- return `${(_c = value[0]) != null ? _c : "..."} - ${(_d = value[1]) != null ? _d : "..."}`;
679
+ return `${(_c = value[0]) != null ? _c : "..."} \u2013 ${(_d = value[1]) != null ? _d : "..."}`;
562
680
  }
563
681
  return String(value);
564
682
  }