@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.
- package/dist/chunk-6TRAIAKS.mjs +178 -0
- package/dist/chunk-I7WCYMXD.mjs +133 -0
- package/dist/{chunk-45QJL74L.mjs → chunk-JSRGUDCG.mjs} +49 -24
- package/dist/chunk-NUDUXOHP.mjs +243 -0
- package/dist/chunk-ON2UFJ3Y.mjs +32 -0
- package/dist/{chunk-RFCPJLIQ.mjs → chunk-SJXRD3RO.mjs} +6 -1
- package/dist/{chunk-EF4VI36D.mjs → chunk-UBTUCPOG.mjs} +1 -1
- package/dist/{chunk-3ZBNWXRA.mjs → chunk-WD7YU6IN.mjs} +17 -14
- package/dist/chunk-XCWKWXBW.mjs +602 -0
- package/dist/{chunk-URDCG5NI.mjs → chunk-YTYOFT33.mjs} +52 -0
- package/dist/filter-active.js +108 -28
- package/dist/filter-active.mjs +4 -4
- package/dist/filter-bar-internal.d.mts +25 -0
- package/dist/filter-bar-internal.d.ts +25 -0
- package/dist/filter-bar-internal.js +648 -0
- package/dist/filter-bar-internal.mjs +11 -0
- package/dist/filter-bar-messages.d.mts +124 -0
- package/dist/filter-bar-messages.d.ts +124 -0
- package/dist/filter-bar-messages.js +156 -0
- package/dist/filter-bar-messages.mjs +7 -0
- package/dist/filter-bar-types.d.mts +112 -0
- package/dist/filter-bar-types.d.ts +112 -0
- package/dist/filter-bar-types.js +56 -0
- package/dist/filter-bar-types.mjs +8 -0
- package/dist/filter-bar.d.mts +29 -0
- package/dist/filter-bar.d.ts +29 -0
- package/dist/filter-bar.js +942 -0
- package/dist/filter-bar.mjs +11 -0
- package/dist/filter-menu.js +161 -1
- package/dist/filter-menu.mjs +2 -1
- package/dist/filter-trigger.js +52 -0
- package/dist/filter-trigger.mjs +2 -2
- package/dist/filter-value-editor.js +65 -10
- package/dist/filter-value-editor.mjs +3 -3
- package/dist/filter.d.mts +6 -0
- package/dist/filter.d.ts +6 -0
- package/dist/filter.mjs +2 -2
- package/dist/index.d.mts +4 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +1263 -31
- package/dist/index.mjs +28 -14
- package/dist/messages.d.mts +50 -0
- package/dist/messages.d.ts +50 -0
- package/dist/messages.js +52 -0
- package/dist/messages.mjs +1 -1
- package/dist/search-filter-bar.d.mts +44 -0
- package/dist/search-filter-bar.d.ts +44 -0
- package/dist/search-filter-bar.js +1007 -0
- package/dist/search-filter-bar.mjs +11 -0
- package/package.json +19 -16
- package/dist/{chunk-SH7DBK54.mjs → chunk-LWDVRMCI.mjs} +3 -3
package/dist/index.js
CHANGED
|
@@ -33,12 +33,15 @@ var index_exports = {};
|
|
|
33
33
|
__export(index_exports, {
|
|
34
34
|
Filter: () => Filter,
|
|
35
35
|
FilterActive: () => FilterActive,
|
|
36
|
+
FilterBar: () => FilterBar,
|
|
36
37
|
FilterGroup: () => FilterGroup,
|
|
37
38
|
FilterItem: () => FilterItem,
|
|
38
39
|
FilterMenu: () => FilterMenu,
|
|
39
40
|
FilterMenuSeparator: () => FilterMenuSeparator,
|
|
40
41
|
FilterTrigger: () => FilterTrigger,
|
|
41
42
|
FilterValueEditor: () => FilterValueEditor,
|
|
43
|
+
SearchFilterBar: () => SearchFilterBar,
|
|
44
|
+
filterBarMessages: () => filterBarMessages,
|
|
42
45
|
filterMessages: () => messages,
|
|
43
46
|
useFilterContext: () => useFilterContext
|
|
44
47
|
});
|
|
@@ -381,6 +384,58 @@ var messages = (0, import_i18n.defineMessages)({
|
|
|
381
384
|
id: "filter.operator.is_not_empty",
|
|
382
385
|
defaultMessage: "is not empty",
|
|
383
386
|
description: "Operator: is not empty"
|
|
387
|
+
},
|
|
388
|
+
// Additional UI strings
|
|
389
|
+
loading: {
|
|
390
|
+
id: "filter.loading",
|
|
391
|
+
defaultMessage: "Loading...",
|
|
392
|
+
description: "Loading state text"
|
|
393
|
+
},
|
|
394
|
+
boolean_true: {
|
|
395
|
+
id: "filter.boolean_true",
|
|
396
|
+
defaultMessage: "Yes",
|
|
397
|
+
description: "Boolean true value display"
|
|
398
|
+
},
|
|
399
|
+
boolean_false: {
|
|
400
|
+
id: "filter.boolean_false",
|
|
401
|
+
defaultMessage: "No",
|
|
402
|
+
description: "Boolean false value display"
|
|
403
|
+
},
|
|
404
|
+
range_min: {
|
|
405
|
+
id: "filter.range_min",
|
|
406
|
+
defaultMessage: "Min",
|
|
407
|
+
description: "Minimum value placeholder for range inputs"
|
|
408
|
+
},
|
|
409
|
+
range_max: {
|
|
410
|
+
id: "filter.range_max",
|
|
411
|
+
defaultMessage: "Max",
|
|
412
|
+
description: "Maximum value placeholder for range inputs"
|
|
413
|
+
},
|
|
414
|
+
// Accessibility labels
|
|
415
|
+
remove_filter: {
|
|
416
|
+
id: "filter.remove_filter",
|
|
417
|
+
defaultMessage: "Remove {field} filter",
|
|
418
|
+
description: "Accessibility label for remove filter button"
|
|
419
|
+
},
|
|
420
|
+
operator_label: {
|
|
421
|
+
id: "filter.operator_label",
|
|
422
|
+
defaultMessage: "{field} operator",
|
|
423
|
+
description: "Accessibility label for operator select"
|
|
424
|
+
},
|
|
425
|
+
value_label: {
|
|
426
|
+
id: "filter.value_label",
|
|
427
|
+
defaultMessage: "{field} value",
|
|
428
|
+
description: "Accessibility label for value input"
|
|
429
|
+
},
|
|
430
|
+
value_min_label: {
|
|
431
|
+
id: "filter.value_min_label",
|
|
432
|
+
defaultMessage: "{field} minimum value",
|
|
433
|
+
description: "Accessibility label for minimum value input"
|
|
434
|
+
},
|
|
435
|
+
value_max_label: {
|
|
436
|
+
id: "filter.value_max_label",
|
|
437
|
+
defaultMessage: "{field} maximum value",
|
|
438
|
+
description: "Accessibility label for maximum value input"
|
|
384
439
|
}
|
|
385
440
|
});
|
|
386
441
|
|
|
@@ -522,27 +577,29 @@ function ValueInput({
|
|
|
522
577
|
placeholder: (_c = field.placeholder) != null ? _c : t.formatMessage(messages.select_value)
|
|
523
578
|
}
|
|
524
579
|
) }),
|
|
525
|
-
/* @__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.
|
|
580
|
+
/* @__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.jsx)(
|
|
526
581
|
import_select.Select.Item,
|
|
527
582
|
{
|
|
528
583
|
value: option.value,
|
|
529
584
|
disabled: option.disabled,
|
|
530
|
-
children: [
|
|
531
|
-
option.icon && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "
|
|
585
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "flex items-center gap-2", children: [
|
|
586
|
+
option.icon && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "size-4 flex-shrink-0", children: option.icon }),
|
|
532
587
|
option.label
|
|
533
|
-
]
|
|
588
|
+
] })
|
|
534
589
|
},
|
|
535
590
|
option.value
|
|
536
591
|
)) })
|
|
537
592
|
] });
|
|
538
593
|
case "multiselect": {
|
|
539
|
-
const selectedValues = Array.isArray(value) ? value
|
|
540
|
-
|
|
594
|
+
const selectedValues = Array.isArray(value) ? value.filter(
|
|
595
|
+
(v) => typeof v === "string" && v.length > 1
|
|
596
|
+
) : [];
|
|
597
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "space-y-1 max-h-[200px] overflow-auto p-1", children: (_e = field.options) == null ? void 0 : _e.map((option) => (
|
|
541
598
|
// biome-ignore lint/a11y/noLabelWithoutControl: Checkbox is a custom form control inside the label
|
|
542
599
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
543
600
|
"label",
|
|
544
601
|
{
|
|
545
|
-
className: "flex items-center gap-2 cursor-pointer",
|
|
602
|
+
className: "flex items-center gap-2 cursor-pointer hover:bg-muted/50 rounded-md px-2 py-1.5",
|
|
546
603
|
children: [
|
|
547
604
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
548
605
|
import_checkbox.Checkbox,
|
|
@@ -555,11 +612,12 @@ function ValueInput({
|
|
|
555
612
|
onChange(selectedValues.filter((v) => v !== option.value));
|
|
556
613
|
}
|
|
557
614
|
},
|
|
558
|
-
disabled: option.disabled
|
|
615
|
+
disabled: option.disabled,
|
|
616
|
+
className: "flex-shrink-0 border-border bg-background data-[state=checked]:bg-primary data-[state=checked]:border-primary [&_svg]:text-primary-foreground"
|
|
559
617
|
}
|
|
560
618
|
),
|
|
561
|
-
option.icon && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "size-
|
|
562
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "text-sm", children: option.label })
|
|
619
|
+
option.icon && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "flex-shrink-0 [&>*]:size-5", children: option.icon }),
|
|
620
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "text-sm truncate", children: option.label })
|
|
563
621
|
]
|
|
564
622
|
},
|
|
565
623
|
option.value
|
|
@@ -671,6 +729,7 @@ function FilterField({ filter }) {
|
|
|
671
729
|
const field = fields.get(filter.fieldId);
|
|
672
730
|
if (!field) return null;
|
|
673
731
|
const availableOperators = (_a = field.operators) != null ? _a : DEFAULT_OPERATORS[field.type];
|
|
732
|
+
const hasSingleOperator = availableOperators.length === 1;
|
|
674
733
|
const getOperatorLabel = (op) => {
|
|
675
734
|
const opKey = `op_${op}`;
|
|
676
735
|
return messages[opKey] ? t.formatMessage(messages[opKey]) : op;
|
|
@@ -695,20 +754,29 @@ function FilterField({ filter }) {
|
|
|
695
754
|
field.icon && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "size-4 flex-shrink-0", children: field.icon }),
|
|
696
755
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: field.label })
|
|
697
756
|
] }),
|
|
698
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.
|
|
757
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: styles.fieldOperator(), children: hasSingleOperator ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
758
|
+
"span",
|
|
759
|
+
{
|
|
760
|
+
className: styles.fieldOperatorSelect(),
|
|
761
|
+
style: { width: operatorWidth },
|
|
762
|
+
children: currentOperatorLabel
|
|
763
|
+
}
|
|
764
|
+
) : /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
|
|
699
765
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
700
766
|
"select",
|
|
701
767
|
{
|
|
702
768
|
className: styles.fieldOperatorSelect(),
|
|
703
769
|
value: filter.operator,
|
|
704
770
|
onChange: handleOperatorChange,
|
|
705
|
-
|
|
771
|
+
"aria-label": t.formatMessage(messages.operator_label, {
|
|
772
|
+
field: field.label
|
|
773
|
+
}),
|
|
706
774
|
style: { width: operatorWidth },
|
|
707
775
|
children: availableOperators.map((op) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("option", { value: op, children: getOperatorLabel(op) }, op))
|
|
708
776
|
}
|
|
709
777
|
),
|
|
710
778
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_icons.ChevronDownIcon, { className: styles.fieldOperatorIcon() })
|
|
711
|
-
] }),
|
|
779
|
+
] }) }),
|
|
712
780
|
useInlineInput && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(InlineValueInput, { filter, field }),
|
|
713
781
|
useInlineSwitch && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: styles.fieldValue(), children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
714
782
|
import_switch.Switch,
|
|
@@ -728,7 +796,9 @@ function FilterField({ filter }) {
|
|
|
728
796
|
type: "button",
|
|
729
797
|
className: styles.fieldRemove(),
|
|
730
798
|
onClick: () => removeFilter(filter.id),
|
|
731
|
-
"aria-label":
|
|
799
|
+
"aria-label": t.formatMessage(messages.remove_filter, {
|
|
800
|
+
field: field.label
|
|
801
|
+
}),
|
|
732
802
|
children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_icons.CloseIcon, { className: "size-3" })
|
|
733
803
|
}
|
|
734
804
|
)
|
|
@@ -757,6 +827,8 @@ function InlineValueInput({
|
|
|
757
827
|
const placeholder = (_a = field.placeholder) != null ? _a : t.formatMessage(messages.enter_value);
|
|
758
828
|
if (filter.operator === "between") {
|
|
759
829
|
const [min, max] = Array.isArray(filter.value) ? filter.value : [void 0, void 0];
|
|
830
|
+
const minPlaceholder = t.formatMessage(messages.range_min);
|
|
831
|
+
const maxPlaceholder = t.formatMessage(messages.range_max);
|
|
760
832
|
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { className: (0, import_shared_utils2.cn)(styles.fieldValue(), "gap-1"), children: [
|
|
761
833
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
762
834
|
"input",
|
|
@@ -768,8 +840,11 @@ function InlineValueInput({
|
|
|
768
840
|
const val = e.target.value ? Number(e.target.value) : void 0;
|
|
769
841
|
updateFilter(filter.id, { value: [val, max] });
|
|
770
842
|
},
|
|
771
|
-
placeholder:
|
|
772
|
-
|
|
843
|
+
placeholder: minPlaceholder,
|
|
844
|
+
"aria-label": t.formatMessage(messages.value_min_label, {
|
|
845
|
+
field: field.label
|
|
846
|
+
}),
|
|
847
|
+
style: { width: getInputWidth(min, minPlaceholder, 3) },
|
|
773
848
|
min: field.min,
|
|
774
849
|
max: field.max,
|
|
775
850
|
step: field.step
|
|
@@ -786,8 +861,11 @@ function InlineValueInput({
|
|
|
786
861
|
const val = e.target.value ? Number(e.target.value) : void 0;
|
|
787
862
|
updateFilter(filter.id, { value: [min, val] });
|
|
788
863
|
},
|
|
789
|
-
placeholder:
|
|
790
|
-
|
|
864
|
+
placeholder: maxPlaceholder,
|
|
865
|
+
"aria-label": t.formatMessage(messages.value_max_label, {
|
|
866
|
+
field: field.label
|
|
867
|
+
}),
|
|
868
|
+
style: { width: getInputWidth(max, maxPlaceholder, 3) },
|
|
791
869
|
min: field.min,
|
|
792
870
|
max: field.max,
|
|
793
871
|
step: field.step
|
|
@@ -803,6 +881,9 @@ function InlineValueInput({
|
|
|
803
881
|
value: filter.value !== void 0 && filter.value !== null ? String(filter.value) : "",
|
|
804
882
|
onChange: handleChange,
|
|
805
883
|
placeholder,
|
|
884
|
+
"aria-label": t.formatMessage(messages.value_label, {
|
|
885
|
+
field: field.label
|
|
886
|
+
}),
|
|
806
887
|
style: { width: getInputWidth(filter.value, placeholder, 6) },
|
|
807
888
|
min: field.min,
|
|
808
889
|
max: field.max,
|
|
@@ -815,7 +896,8 @@ function PopoverValueEditor({
|
|
|
815
896
|
field
|
|
816
897
|
}) {
|
|
817
898
|
const { styles, updateFilter } = useFilterContext();
|
|
818
|
-
const
|
|
899
|
+
const t = (0, import_i18n3.useSafeIntl)();
|
|
900
|
+
const valueDisplay = field.renderValue ? field.renderValue({ filter, field }) : getValueDisplay(filter, field, t);
|
|
819
901
|
const hasValue = filter.value !== void 0 && filter.value !== null && filter.value !== "" && !(Array.isArray(filter.value) && filter.value.length === 0);
|
|
820
902
|
const handleCustomChange = (value) => {
|
|
821
903
|
updateFilter(filter.id, { value });
|
|
@@ -828,7 +910,7 @@ function PopoverValueEditor({
|
|
|
828
910
|
render: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", {}),
|
|
829
911
|
nativeButton: false,
|
|
830
912
|
children: [
|
|
831
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: styles.fieldValueText(), children: hasValue ? valueDisplay :
|
|
913
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: styles.fieldValueText(), children: hasValue ? valueDisplay : t.formatMessage(messages.select_value) }),
|
|
832
914
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_icons.ChevronDownIcon, { className: styles.fieldValueIcon() })
|
|
833
915
|
]
|
|
834
916
|
}
|
|
@@ -855,7 +937,7 @@ function PopoverValueEditor({
|
|
|
855
937
|
)
|
|
856
938
|
] });
|
|
857
939
|
}
|
|
858
|
-
function getValueDisplay(filter, field) {
|
|
940
|
+
function getValueDisplay(filter, field, t) {
|
|
859
941
|
var _a, _b, _c, _d;
|
|
860
942
|
const { value, operator } = filter;
|
|
861
943
|
if (operator === "is_empty" || operator === "is_not_empty") {
|
|
@@ -865,19 +947,20 @@ function getValueDisplay(filter, field) {
|
|
|
865
947
|
return null;
|
|
866
948
|
}
|
|
867
949
|
if (field.type === "boolean") {
|
|
868
|
-
return value ?
|
|
950
|
+
return value ? t.formatMessage(messages.boolean_true) : t.formatMessage(messages.boolean_false);
|
|
869
951
|
}
|
|
870
952
|
if (field.type === "select" && field.options) {
|
|
871
953
|
const option = field.options.find((o) => o.value === value);
|
|
872
954
|
return (_a = option == null ? void 0 : option.label) != null ? _a : String(value);
|
|
873
955
|
}
|
|
874
|
-
if (field.type === "multiselect"
|
|
875
|
-
|
|
876
|
-
if (
|
|
877
|
-
|
|
878
|
-
|
|
956
|
+
if (field.type === "multiselect") {
|
|
957
|
+
const values = Array.isArray(value) ? value.filter((v) => typeof v === "string" && v.length > 1) : [];
|
|
958
|
+
if (values.length === 0) return null;
|
|
959
|
+
if (values.length === 1 && field.options) {
|
|
960
|
+
const option = field.options.find((o) => o.value === values[0]);
|
|
961
|
+
return (_b = option == null ? void 0 : option.label) != null ? _b : String(values[0]);
|
|
879
962
|
}
|
|
880
|
-
return `${
|
|
963
|
+
return `${values.length} selected`;
|
|
881
964
|
}
|
|
882
965
|
if (field.type === "date" && value instanceof Date) {
|
|
883
966
|
return value.toLocaleDateString();
|
|
@@ -896,6 +979,7 @@ function getValueDisplay(filter, field) {
|
|
|
896
979
|
|
|
897
980
|
// src/filter-menu.tsx
|
|
898
981
|
var import_dropdown_menu2 = require("@kopexa/dropdown-menu");
|
|
982
|
+
var import_i18n4 = require("@kopexa/i18n");
|
|
899
983
|
var import_shared_utils3 = require("@kopexa/shared-utils");
|
|
900
984
|
var import_react = require("react");
|
|
901
985
|
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
@@ -903,6 +987,7 @@ function FilterMenu(props) {
|
|
|
903
987
|
var _a;
|
|
904
988
|
const { children, className } = props;
|
|
905
989
|
const { fields, styles, addFilter, allowMultiple, value } = useFilterContext();
|
|
990
|
+
const t = (0, import_i18n4.useSafeIntl)();
|
|
906
991
|
const fieldGroups = /* @__PURE__ */ new Map();
|
|
907
992
|
for (const [id, field] of fields) {
|
|
908
993
|
const group = field.group;
|
|
@@ -919,7 +1004,7 @@ function FilterMenu(props) {
|
|
|
919
1004
|
"data-slot": "filter-menu",
|
|
920
1005
|
className: (0, import_shared_utils3.cn)("min-w-[220px]", className),
|
|
921
1006
|
align: "start",
|
|
922
|
-
children: fields.size === 0 ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "px-2 py-3 text-sm text-muted-foreground text-center", children:
|
|
1007
|
+
children: fields.size === 0 ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "px-2 py-3 text-sm text-muted-foreground text-center", children: t.formatMessage(messages.loading) }) : Array.from(fieldGroups.entries()).map(([groupLabel, groupFields]) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_dropdown_menu2.DropdownMenu.Group, { children: [
|
|
923
1008
|
groupLabel && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_dropdown_menu2.DropdownMenu.Label, { children: groupLabel }),
|
|
924
1009
|
Array.from(groupFields.values()).map((field) => {
|
|
925
1010
|
const hasFilter = value.some((f) => f.fieldId === field.id);
|
|
@@ -1026,14 +1111,14 @@ FilterMenuSeparator.displayName = "FilterMenuSeparator";
|
|
|
1026
1111
|
|
|
1027
1112
|
// src/filter-trigger.tsx
|
|
1028
1113
|
var import_dropdown_menu3 = require("@kopexa/dropdown-menu");
|
|
1029
|
-
var
|
|
1114
|
+
var import_i18n5 = require("@kopexa/i18n");
|
|
1030
1115
|
var import_icons2 = require("@kopexa/icons");
|
|
1031
1116
|
var import_shared_utils4 = require("@kopexa/shared-utils");
|
|
1032
1117
|
var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
1033
1118
|
function FilterTrigger(props) {
|
|
1034
1119
|
const { className, children, icon, ...rest } = props;
|
|
1035
1120
|
const { styles } = useFilterContext();
|
|
1036
|
-
const t = (0,
|
|
1121
|
+
const t = (0, import_i18n5.useSafeIntl)();
|
|
1037
1122
|
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
|
|
1038
1123
|
import_dropdown_menu3.DropdownMenu.Trigger,
|
|
1039
1124
|
{
|
|
@@ -1048,16 +1133,1163 @@ function FilterTrigger(props) {
|
|
|
1048
1133
|
);
|
|
1049
1134
|
}
|
|
1050
1135
|
FilterTrigger.displayName = "FilterTrigger";
|
|
1136
|
+
|
|
1137
|
+
// src/filter-bar.tsx
|
|
1138
|
+
var import_i18n7 = require("@kopexa/i18n");
|
|
1139
|
+
var import_shared_utils6 = require("@kopexa/shared-utils");
|
|
1140
|
+
var import_use_controllable_state = require("@kopexa/use-controllable-state");
|
|
1141
|
+
var import_react3 = require("react");
|
|
1142
|
+
|
|
1143
|
+
// src/filter-bar-internal.tsx
|
|
1144
|
+
var import_button = require("@kopexa/button");
|
|
1145
|
+
var import_checkbox2 = require("@kopexa/checkbox");
|
|
1146
|
+
var import_command = require("@kopexa/command");
|
|
1147
|
+
var import_dropdown_menu4 = require("@kopexa/dropdown-menu");
|
|
1148
|
+
var import_icons3 = require("@kopexa/icons");
|
|
1149
|
+
var import_input2 = require("@kopexa/input");
|
|
1150
|
+
var import_popover2 = require("@kopexa/popover");
|
|
1151
|
+
var import_shared_utils5 = require("@kopexa/shared-utils");
|
|
1152
|
+
var import_react2 = require("react");
|
|
1153
|
+
|
|
1154
|
+
// src/filter-bar-types.ts
|
|
1155
|
+
var DEFAULT_FILTER_BAR_OPERATORS = {
|
|
1156
|
+
select: ["equals", "not_equals", "is_empty", "is_not_empty"],
|
|
1157
|
+
multiselect: ["equals", "not_equals", "is_empty", "is_not_empty"],
|
|
1158
|
+
text: [
|
|
1159
|
+
"equals",
|
|
1160
|
+
"not_equals",
|
|
1161
|
+
"contains",
|
|
1162
|
+
"not_contains",
|
|
1163
|
+
"starts_with",
|
|
1164
|
+
"ends_with",
|
|
1165
|
+
"is_empty",
|
|
1166
|
+
"is_not_empty"
|
|
1167
|
+
],
|
|
1168
|
+
number: [
|
|
1169
|
+
"equals",
|
|
1170
|
+
"not_equals",
|
|
1171
|
+
"gt",
|
|
1172
|
+
"lt",
|
|
1173
|
+
"gte",
|
|
1174
|
+
"lte",
|
|
1175
|
+
"is_empty",
|
|
1176
|
+
"is_not_empty"
|
|
1177
|
+
],
|
|
1178
|
+
custom: ["equals", "not_equals"]
|
|
1179
|
+
};
|
|
1180
|
+
|
|
1181
|
+
// src/filter-bar-internal.tsx
|
|
1182
|
+
var import_jsx_runtime6 = require("react/jsx-runtime");
|
|
1183
|
+
function AddFilterDropdown(props) {
|
|
1184
|
+
const { fieldGroups, isFieldAvailable, onAddFilter, i18n, disabled } = props;
|
|
1185
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_dropdown_menu4.DropdownMenu.Root, { children: [
|
|
1186
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_dropdown_menu4.DropdownMenu.Trigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
1187
|
+
import_button.Button,
|
|
1188
|
+
{
|
|
1189
|
+
variant: "outline",
|
|
1190
|
+
size: "sm",
|
|
1191
|
+
className: "rounded-full border-dashed",
|
|
1192
|
+
disabled,
|
|
1193
|
+
startContent: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_icons3.PlusIcon, { className: "size-4" }),
|
|
1194
|
+
children: i18n.addFilter
|
|
1195
|
+
}
|
|
1196
|
+
) }),
|
|
1197
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_dropdown_menu4.DropdownMenu.Content, { align: "start", className: "min-w-[200px]", children: Array.from(fieldGroups.entries()).map(([groupLabel, groupFields]) => /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_dropdown_menu4.DropdownMenu.Group, { children: [
|
|
1198
|
+
groupLabel && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_dropdown_menu4.DropdownMenu.Label, { children: groupLabel }),
|
|
1199
|
+
groupFields.map((field) => {
|
|
1200
|
+
const available = isFieldAvailable(field.id);
|
|
1201
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
|
|
1202
|
+
import_dropdown_menu4.DropdownMenu.Item,
|
|
1203
|
+
{
|
|
1204
|
+
disabled: field.disabled || !available,
|
|
1205
|
+
onSelect: () => onAddFilter(field.id),
|
|
1206
|
+
children: [
|
|
1207
|
+
field.icon && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "size-4 text-muted-foreground mr-2", children: field.icon }),
|
|
1208
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { children: field.label })
|
|
1209
|
+
]
|
|
1210
|
+
},
|
|
1211
|
+
field.id
|
|
1212
|
+
);
|
|
1213
|
+
})
|
|
1214
|
+
] }, groupLabel != null ? groupLabel : "ungrouped")) })
|
|
1215
|
+
] });
|
|
1216
|
+
}
|
|
1217
|
+
function FilterPill(props) {
|
|
1218
|
+
var _a;
|
|
1219
|
+
const {
|
|
1220
|
+
filter,
|
|
1221
|
+
field,
|
|
1222
|
+
i18n,
|
|
1223
|
+
onUpdate,
|
|
1224
|
+
onRemove,
|
|
1225
|
+
autoOpen,
|
|
1226
|
+
onAutoOpenComplete,
|
|
1227
|
+
disabled
|
|
1228
|
+
} = props;
|
|
1229
|
+
const [open, setOpen] = (0, import_react2.useState)(autoOpen != null ? autoOpen : false);
|
|
1230
|
+
const operators = (_a = field.operators) != null ? _a : DEFAULT_FILTER_BAR_OPERATORS[field.type];
|
|
1231
|
+
const isEmptyOperator = filter.operator === "is_empty" || filter.operator === "is_not_empty";
|
|
1232
|
+
const hasCustomRenderer = !!field.customRenderer;
|
|
1233
|
+
const useInlineInput = !hasCustomRenderer && (field.type === "text" || field.type === "number");
|
|
1234
|
+
const usePopover = !useInlineInput && !isEmptyOperator;
|
|
1235
|
+
const handleOpenChange = (0, import_react2.useCallback)(
|
|
1236
|
+
(isOpen) => {
|
|
1237
|
+
setOpen(isOpen);
|
|
1238
|
+
if (!isOpen && autoOpen) {
|
|
1239
|
+
onAutoOpenComplete == null ? void 0 : onAutoOpenComplete();
|
|
1240
|
+
}
|
|
1241
|
+
},
|
|
1242
|
+
[autoOpen, onAutoOpenComplete]
|
|
1243
|
+
);
|
|
1244
|
+
const valueDisplay = (0, import_react2.useMemo)(() => {
|
|
1245
|
+
var _a2, _b;
|
|
1246
|
+
if (isEmptyOperator) return null;
|
|
1247
|
+
if (filter.value === null || filter.value === "") return null;
|
|
1248
|
+
if (field.type === "select" && field.options) {
|
|
1249
|
+
const option = field.options.find((o) => o.value === filter.value);
|
|
1250
|
+
return (_a2 = option == null ? void 0 : option.label) != null ? _a2 : String(filter.value);
|
|
1251
|
+
}
|
|
1252
|
+
if (field.type === "multiselect") {
|
|
1253
|
+
const values = Array.isArray(filter.value) ? filter.value.filter(
|
|
1254
|
+
(v) => typeof v === "string" && v.length > 1
|
|
1255
|
+
) : [];
|
|
1256
|
+
if (values.length === 0) return null;
|
|
1257
|
+
if (values.length === 1 && field.options) {
|
|
1258
|
+
const option = field.options.find((o) => o.value === values[0]);
|
|
1259
|
+
return (_b = option == null ? void 0 : option.label) != null ? _b : values[0];
|
|
1260
|
+
}
|
|
1261
|
+
return `${values.length} selected`;
|
|
1262
|
+
}
|
|
1263
|
+
return String(filter.value);
|
|
1264
|
+
}, [filter.value, field, isEmptyOperator]);
|
|
1265
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
|
|
1266
|
+
"div",
|
|
1267
|
+
{
|
|
1268
|
+
"data-slot": "filter-pill",
|
|
1269
|
+
className: (0, import_shared_utils5.cn)(
|
|
1270
|
+
"group inline-flex items-center rounded-full overflow-hidden",
|
|
1271
|
+
"border border-border bg-background",
|
|
1272
|
+
"transition-all hover:shadow-sm focus-within:ring-2 focus-within:ring-ring focus-within:ring-offset-1"
|
|
1273
|
+
),
|
|
1274
|
+
children: [
|
|
1275
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
|
|
1276
|
+
"span",
|
|
1277
|
+
{
|
|
1278
|
+
className: (0, import_shared_utils5.cn)(
|
|
1279
|
+
"bg-muted text-foreground font-medium text-xs",
|
|
1280
|
+
"flex items-center gap-1.5 flex-shrink-0",
|
|
1281
|
+
"rounded-l-full px-3 py-1.5"
|
|
1282
|
+
),
|
|
1283
|
+
children: [
|
|
1284
|
+
field.icon && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "size-4 flex-shrink-0", children: field.icon }),
|
|
1285
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { children: field.label })
|
|
1286
|
+
]
|
|
1287
|
+
}
|
|
1288
|
+
),
|
|
1289
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
1290
|
+
OperatorDropdown,
|
|
1291
|
+
{
|
|
1292
|
+
operator: filter.operator,
|
|
1293
|
+
operators,
|
|
1294
|
+
i18n,
|
|
1295
|
+
onChange: (op) => {
|
|
1296
|
+
const newUpdates = { operator: op };
|
|
1297
|
+
if (op === "is_empty" || op === "is_not_empty") {
|
|
1298
|
+
newUpdates.value = null;
|
|
1299
|
+
}
|
|
1300
|
+
onUpdate(newUpdates);
|
|
1301
|
+
},
|
|
1302
|
+
disabled
|
|
1303
|
+
}
|
|
1304
|
+
),
|
|
1305
|
+
useInlineInput && !isEmptyOperator && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
1306
|
+
InlineValueInput2,
|
|
1307
|
+
{
|
|
1308
|
+
filter,
|
|
1309
|
+
field,
|
|
1310
|
+
i18n,
|
|
1311
|
+
onUpdate,
|
|
1312
|
+
disabled
|
|
1313
|
+
}
|
|
1314
|
+
),
|
|
1315
|
+
usePopover && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_popover2.Popover.Root, { open, onOpenChange: handleOpenChange, children: [
|
|
1316
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
|
|
1317
|
+
import_popover2.Popover.Trigger,
|
|
1318
|
+
{
|
|
1319
|
+
className: (0, import_shared_utils5.cn)(
|
|
1320
|
+
"bg-background text-foreground text-xs",
|
|
1321
|
+
"flex items-center gap-1 px-2.5 py-1.5",
|
|
1322
|
+
"cursor-pointer hover:bg-muted/30"
|
|
1323
|
+
),
|
|
1324
|
+
nativeButton: false,
|
|
1325
|
+
render: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", {}),
|
|
1326
|
+
children: [
|
|
1327
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "truncate max-w-[150px]", children: valueDisplay != null ? valueDisplay : i18n.selectValue }),
|
|
1328
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_icons3.ChevronDownIcon, { className: "size-3.5 opacity-60 flex-shrink-0" })
|
|
1329
|
+
]
|
|
1330
|
+
}
|
|
1331
|
+
),
|
|
1332
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
1333
|
+
import_popover2.Popover.Content,
|
|
1334
|
+
{
|
|
1335
|
+
"data-slot": "filter-editor",
|
|
1336
|
+
className: "p-0 w-[280px]",
|
|
1337
|
+
align: "start",
|
|
1338
|
+
showArrow: false,
|
|
1339
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
1340
|
+
ValueEditor,
|
|
1341
|
+
{
|
|
1342
|
+
filter,
|
|
1343
|
+
field,
|
|
1344
|
+
i18n,
|
|
1345
|
+
onUpdate,
|
|
1346
|
+
onClose: () => handleOpenChange(false)
|
|
1347
|
+
}
|
|
1348
|
+
)
|
|
1349
|
+
}
|
|
1350
|
+
)
|
|
1351
|
+
] }),
|
|
1352
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
1353
|
+
"button",
|
|
1354
|
+
{
|
|
1355
|
+
type: "button",
|
|
1356
|
+
onClick: onRemove,
|
|
1357
|
+
className: (0, import_shared_utils5.cn)(
|
|
1358
|
+
"size-5 rounded-full flex items-center justify-center flex-shrink-0",
|
|
1359
|
+
"hover:bg-destructive/10 hover:text-destructive",
|
|
1360
|
+
"transition-colors cursor-pointer mr-1"
|
|
1361
|
+
),
|
|
1362
|
+
disabled,
|
|
1363
|
+
"aria-label": `Remove ${field.label} filter`,
|
|
1364
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_icons3.CloseIcon, { className: "size-3" })
|
|
1365
|
+
}
|
|
1366
|
+
)
|
|
1367
|
+
]
|
|
1368
|
+
}
|
|
1369
|
+
);
|
|
1370
|
+
}
|
|
1371
|
+
function OperatorDropdown(props) {
|
|
1372
|
+
const { operator, operators, i18n, onChange, disabled } = props;
|
|
1373
|
+
const hasSingleOperator = operators.length === 1;
|
|
1374
|
+
const currentLabel = i18n.operators[operator];
|
|
1375
|
+
if (hasSingleOperator) {
|
|
1376
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
1377
|
+
"span",
|
|
1378
|
+
{
|
|
1379
|
+
className: (0, import_shared_utils5.cn)(
|
|
1380
|
+
"bg-primary text-primary-foreground",
|
|
1381
|
+
"flex items-center px-3 py-0.5",
|
|
1382
|
+
"rounded-full shadow-sm mx-1 my-0.5",
|
|
1383
|
+
"text-xs font-normal"
|
|
1384
|
+
),
|
|
1385
|
+
children: currentLabel
|
|
1386
|
+
}
|
|
1387
|
+
);
|
|
1388
|
+
}
|
|
1389
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_dropdown_menu4.DropdownMenu.Root, { children: [
|
|
1390
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_dropdown_menu4.DropdownMenu.Trigger, { asChild: true, disabled, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
|
|
1391
|
+
"button",
|
|
1392
|
+
{
|
|
1393
|
+
type: "button",
|
|
1394
|
+
className: (0, import_shared_utils5.cn)(
|
|
1395
|
+
"bg-primary text-primary-foreground",
|
|
1396
|
+
"flex items-center gap-1 px-3 py-0.5",
|
|
1397
|
+
"rounded-full shadow-sm mx-1 my-0.5",
|
|
1398
|
+
"text-xs font-normal cursor-pointer",
|
|
1399
|
+
"hover:bg-primary/90 transition-colors"
|
|
1400
|
+
),
|
|
1401
|
+
children: [
|
|
1402
|
+
currentLabel,
|
|
1403
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_icons3.ChevronDownIcon, { className: "size-3 opacity-70" })
|
|
1404
|
+
]
|
|
1405
|
+
}
|
|
1406
|
+
) }),
|
|
1407
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_dropdown_menu4.DropdownMenu.Content, { align: "start", className: "min-w-[140px]", children: operators.map((op) => /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
|
|
1408
|
+
import_dropdown_menu4.DropdownMenu.Item,
|
|
1409
|
+
{
|
|
1410
|
+
onSelect: () => onChange(op),
|
|
1411
|
+
className: "gap-2",
|
|
1412
|
+
children: [
|
|
1413
|
+
op === operator && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_icons3.CheckIcon, { className: "size-4" }),
|
|
1414
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: op !== operator ? "pl-6" : "", children: i18n.operators[op] })
|
|
1415
|
+
]
|
|
1416
|
+
},
|
|
1417
|
+
op
|
|
1418
|
+
)) })
|
|
1419
|
+
] });
|
|
1420
|
+
}
|
|
1421
|
+
function getInputWidth2(value, placeholder, minWidth = 4) {
|
|
1422
|
+
const displayValue = value !== void 0 && value !== null && value !== "" ? String(value) : placeholder;
|
|
1423
|
+
const width = Math.max(displayValue.length + 1, minWidth);
|
|
1424
|
+
return `${width}ch`;
|
|
1425
|
+
}
|
|
1426
|
+
function InlineValueInput2(props) {
|
|
1427
|
+
var _a;
|
|
1428
|
+
const { filter, field, i18n, onUpdate, disabled } = props;
|
|
1429
|
+
const placeholder = (_a = field.placeholder) != null ? _a : i18n.enterValue;
|
|
1430
|
+
const handleChange = (e) => {
|
|
1431
|
+
const val = e.target.value;
|
|
1432
|
+
if (field.type === "number") {
|
|
1433
|
+
onUpdate({ value: val ? Number(val) : null });
|
|
1434
|
+
} else {
|
|
1435
|
+
onUpdate({ value: val });
|
|
1436
|
+
}
|
|
1437
|
+
};
|
|
1438
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
1439
|
+
"span",
|
|
1440
|
+
{
|
|
1441
|
+
className: (0, import_shared_utils5.cn)(
|
|
1442
|
+
"bg-background text-foreground text-xs",
|
|
1443
|
+
"flex items-center gap-1 px-2.5 py-1.5"
|
|
1444
|
+
),
|
|
1445
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
1446
|
+
"input",
|
|
1447
|
+
{
|
|
1448
|
+
type: field.type === "number" ? "number" : "text",
|
|
1449
|
+
className: (0, import_shared_utils5.cn)(
|
|
1450
|
+
"bg-transparent border-none outline-none",
|
|
1451
|
+
"placeholder:text-muted-foreground",
|
|
1452
|
+
"text-xs"
|
|
1453
|
+
),
|
|
1454
|
+
value: filter.value !== void 0 && filter.value !== null ? String(filter.value) : "",
|
|
1455
|
+
onChange: handleChange,
|
|
1456
|
+
placeholder,
|
|
1457
|
+
"aria-label": `${field.label} value`,
|
|
1458
|
+
style: { width: getInputWidth2(filter.value, placeholder, 6) },
|
|
1459
|
+
min: field.min,
|
|
1460
|
+
max: field.max,
|
|
1461
|
+
step: field.step,
|
|
1462
|
+
disabled
|
|
1463
|
+
}
|
|
1464
|
+
)
|
|
1465
|
+
}
|
|
1466
|
+
);
|
|
1467
|
+
}
|
|
1468
|
+
function ValueEditor(props) {
|
|
1469
|
+
var _a, _b, _c, _d;
|
|
1470
|
+
const { filter, field, i18n, onUpdate, onClose } = props;
|
|
1471
|
+
if (field.customRenderer) {
|
|
1472
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "p-3", children: field.customRenderer({
|
|
1473
|
+
field,
|
|
1474
|
+
value: filter.value,
|
|
1475
|
+
onChange: (value) => onUpdate({ value })
|
|
1476
|
+
}) });
|
|
1477
|
+
}
|
|
1478
|
+
if (field.type === "select" && field.options) {
|
|
1479
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
1480
|
+
SelectEditor,
|
|
1481
|
+
{
|
|
1482
|
+
options: field.options,
|
|
1483
|
+
value: filter.value,
|
|
1484
|
+
onChange: (value) => {
|
|
1485
|
+
onUpdate({ value });
|
|
1486
|
+
onClose();
|
|
1487
|
+
},
|
|
1488
|
+
i18n
|
|
1489
|
+
}
|
|
1490
|
+
);
|
|
1491
|
+
}
|
|
1492
|
+
if (field.type === "multiselect" && field.options) {
|
|
1493
|
+
const currentValue = Array.isArray(filter.value) ? filter.value.filter(
|
|
1494
|
+
(v) => typeof v === "string" && v.length > 1
|
|
1495
|
+
) : [];
|
|
1496
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
1497
|
+
MultiselectEditor,
|
|
1498
|
+
{
|
|
1499
|
+
options: field.options,
|
|
1500
|
+
value: currentValue,
|
|
1501
|
+
onChange: (value) => onUpdate({ value }),
|
|
1502
|
+
i18n
|
|
1503
|
+
}
|
|
1504
|
+
);
|
|
1505
|
+
}
|
|
1506
|
+
if (field.type === "text") {
|
|
1507
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
1508
|
+
TextEditor,
|
|
1509
|
+
{
|
|
1510
|
+
value: (_a = filter.value) != null ? _a : "",
|
|
1511
|
+
onChange: (value) => onUpdate({ value }),
|
|
1512
|
+
placeholder: (_b = field.placeholder) != null ? _b : i18n.enterValue,
|
|
1513
|
+
validation: field.validation,
|
|
1514
|
+
onClose
|
|
1515
|
+
}
|
|
1516
|
+
);
|
|
1517
|
+
}
|
|
1518
|
+
if (field.type === "number") {
|
|
1519
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
1520
|
+
NumberEditor,
|
|
1521
|
+
{
|
|
1522
|
+
value: (_c = filter.value) != null ? _c : null,
|
|
1523
|
+
onChange: (value) => onUpdate({ value }),
|
|
1524
|
+
placeholder: (_d = field.placeholder) != null ? _d : i18n.enterValue,
|
|
1525
|
+
min: field.min,
|
|
1526
|
+
max: field.max,
|
|
1527
|
+
step: field.step,
|
|
1528
|
+
onClose
|
|
1529
|
+
}
|
|
1530
|
+
);
|
|
1531
|
+
}
|
|
1532
|
+
return null;
|
|
1533
|
+
}
|
|
1534
|
+
function SelectEditor(props) {
|
|
1535
|
+
const { options = [], value, onChange, i18n } = props;
|
|
1536
|
+
const [search, setSearch] = (0, import_react2.useState)("");
|
|
1537
|
+
const filteredOptions = (0, import_react2.useMemo)(() => {
|
|
1538
|
+
if (!search) return options;
|
|
1539
|
+
const lower = search.toLowerCase();
|
|
1540
|
+
return options.filter(
|
|
1541
|
+
(o) => o.label.toLowerCase().includes(lower) || o.value.toLowerCase().includes(lower)
|
|
1542
|
+
);
|
|
1543
|
+
}, [options, search]);
|
|
1544
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_command.Command.Root, { shouldFilter: false, children: [
|
|
1545
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
1546
|
+
import_command.Command.Input,
|
|
1547
|
+
{
|
|
1548
|
+
placeholder: i18n.searchPlaceholder,
|
|
1549
|
+
value: search,
|
|
1550
|
+
onValueChange: setSearch
|
|
1551
|
+
}
|
|
1552
|
+
),
|
|
1553
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_command.Command.List, { className: "max-h-[300px]", children: [
|
|
1554
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_command.Command.Empty, { children: i18n.noResults }),
|
|
1555
|
+
filteredOptions.map((option) => /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
|
|
1556
|
+
import_command.Command.Item,
|
|
1557
|
+
{
|
|
1558
|
+
value: option.value,
|
|
1559
|
+
onSelect: () => onChange(option.value),
|
|
1560
|
+
className: "flex items-center gap-2 min-w-0",
|
|
1561
|
+
children: [
|
|
1562
|
+
option.icon && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "flex-shrink-0 [&>*]:size-5", children: option.icon }),
|
|
1563
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "truncate flex-1 min-w-0", children: option.label }),
|
|
1564
|
+
value === option.value && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_icons3.CheckIcon, { className: "size-4 flex-shrink-0" })
|
|
1565
|
+
]
|
|
1566
|
+
},
|
|
1567
|
+
option.value
|
|
1568
|
+
))
|
|
1569
|
+
] })
|
|
1570
|
+
] });
|
|
1571
|
+
}
|
|
1572
|
+
function MultiselectEditor(props) {
|
|
1573
|
+
const { options = [], value, onChange, i18n } = props;
|
|
1574
|
+
const [search, setSearch] = (0, import_react2.useState)("");
|
|
1575
|
+
const filteredOptions = (0, import_react2.useMemo)(() => {
|
|
1576
|
+
if (!search) return options;
|
|
1577
|
+
const lower = search.toLowerCase();
|
|
1578
|
+
return options.filter(
|
|
1579
|
+
(o) => o.label.toLowerCase().includes(lower) || o.value.toLowerCase().includes(lower)
|
|
1580
|
+
);
|
|
1581
|
+
}, [options, search]);
|
|
1582
|
+
const toggleOption = (0, import_react2.useCallback)(
|
|
1583
|
+
(optionValue) => {
|
|
1584
|
+
if (value.includes(optionValue)) {
|
|
1585
|
+
onChange(value.filter((v) => v !== optionValue));
|
|
1586
|
+
} else {
|
|
1587
|
+
onChange([...value, optionValue]);
|
|
1588
|
+
}
|
|
1589
|
+
},
|
|
1590
|
+
[value, onChange]
|
|
1591
|
+
);
|
|
1592
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_command.Command.Root, { shouldFilter: false, children: [
|
|
1593
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
1594
|
+
import_command.Command.Input,
|
|
1595
|
+
{
|
|
1596
|
+
placeholder: i18n.searchPlaceholder,
|
|
1597
|
+
value: search,
|
|
1598
|
+
onValueChange: setSearch
|
|
1599
|
+
}
|
|
1600
|
+
),
|
|
1601
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_command.Command.List, { className: "max-h-[300px]", children: [
|
|
1602
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_command.Command.Empty, { children: i18n.noResults }),
|
|
1603
|
+
filteredOptions.map((option) => {
|
|
1604
|
+
const isSelected = value.includes(option.value);
|
|
1605
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
|
|
1606
|
+
import_command.Command.Item,
|
|
1607
|
+
{
|
|
1608
|
+
value: option.value,
|
|
1609
|
+
onSelect: () => toggleOption(option.value),
|
|
1610
|
+
className: "flex items-center gap-2 min-w-0",
|
|
1611
|
+
children: [
|
|
1612
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
1613
|
+
import_checkbox2.Checkbox,
|
|
1614
|
+
{
|
|
1615
|
+
checked: isSelected,
|
|
1616
|
+
className: "flex-shrink-0 border-border bg-background data-[state=checked]:bg-primary data-[state=checked]:border-primary [&_svg]:text-primary-foreground"
|
|
1617
|
+
}
|
|
1618
|
+
),
|
|
1619
|
+
option.icon && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "flex-shrink-0 [&>*]:size-5", children: option.icon }),
|
|
1620
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "truncate flex-1 min-w-0", children: option.label })
|
|
1621
|
+
]
|
|
1622
|
+
},
|
|
1623
|
+
option.value
|
|
1624
|
+
);
|
|
1625
|
+
})
|
|
1626
|
+
] })
|
|
1627
|
+
] });
|
|
1628
|
+
}
|
|
1629
|
+
function TextEditor(props) {
|
|
1630
|
+
const {
|
|
1631
|
+
value: initialValue,
|
|
1632
|
+
onChange,
|
|
1633
|
+
placeholder,
|
|
1634
|
+
validation,
|
|
1635
|
+
onClose
|
|
1636
|
+
} = props;
|
|
1637
|
+
const [localValue, setLocalValue] = (0, import_react2.useState)(initialValue);
|
|
1638
|
+
const [error, setError] = (0, import_react2.useState)(null);
|
|
1639
|
+
const inputRef = (0, import_react2.useRef)(null);
|
|
1640
|
+
const validate = (0, import_react2.useCallback)(
|
|
1641
|
+
(val) => {
|
|
1642
|
+
var _a;
|
|
1643
|
+
if (!validation) return true;
|
|
1644
|
+
if (validation.pattern) {
|
|
1645
|
+
const regex = new RegExp(validation.pattern);
|
|
1646
|
+
if (!regex.test(val)) {
|
|
1647
|
+
setError((_a = validation.message) != null ? _a : "Invalid format");
|
|
1648
|
+
return false;
|
|
1649
|
+
}
|
|
1650
|
+
}
|
|
1651
|
+
if (validation.validate) {
|
|
1652
|
+
const result = validation.validate(val);
|
|
1653
|
+
if (result !== true) {
|
|
1654
|
+
setError(typeof result === "string" ? result : "Invalid value");
|
|
1655
|
+
return false;
|
|
1656
|
+
}
|
|
1657
|
+
}
|
|
1658
|
+
setError(null);
|
|
1659
|
+
return true;
|
|
1660
|
+
},
|
|
1661
|
+
[validation]
|
|
1662
|
+
);
|
|
1663
|
+
const handleSubmit = (0, import_react2.useCallback)(() => {
|
|
1664
|
+
if (validate(localValue)) {
|
|
1665
|
+
onChange(localValue);
|
|
1666
|
+
onClose();
|
|
1667
|
+
}
|
|
1668
|
+
}, [localValue, validate, onChange, onClose]);
|
|
1669
|
+
const handleKeyDown = (0, import_react2.useCallback)(
|
|
1670
|
+
(e) => {
|
|
1671
|
+
if (e.key === "Enter") {
|
|
1672
|
+
e.preventDefault();
|
|
1673
|
+
handleSubmit();
|
|
1674
|
+
} else if (e.key === "Escape") {
|
|
1675
|
+
e.preventDefault();
|
|
1676
|
+
onClose();
|
|
1677
|
+
}
|
|
1678
|
+
},
|
|
1679
|
+
[handleSubmit, onClose]
|
|
1680
|
+
);
|
|
1681
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "p-3 space-y-2", children: [
|
|
1682
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
1683
|
+
import_input2.Input,
|
|
1684
|
+
{
|
|
1685
|
+
ref: inputRef,
|
|
1686
|
+
type: "text",
|
|
1687
|
+
value: localValue,
|
|
1688
|
+
onChange: (e) => {
|
|
1689
|
+
setLocalValue(e.target.value);
|
|
1690
|
+
setError(null);
|
|
1691
|
+
},
|
|
1692
|
+
onKeyDown: handleKeyDown,
|
|
1693
|
+
placeholder,
|
|
1694
|
+
className: "w-full",
|
|
1695
|
+
autoFocus: true
|
|
1696
|
+
}
|
|
1697
|
+
),
|
|
1698
|
+
error && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "text-xs text-destructive", children: error }),
|
|
1699
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "flex justify-end gap-2", children: [
|
|
1700
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_button.Button, { variant: "ghost", size: "sm", onClick: onClose, children: "Cancel" }),
|
|
1701
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_button.Button, { size: "sm", onClick: handleSubmit, children: "Apply" })
|
|
1702
|
+
] })
|
|
1703
|
+
] });
|
|
1704
|
+
}
|
|
1705
|
+
function NumberEditor(props) {
|
|
1706
|
+
const {
|
|
1707
|
+
value: initialValue,
|
|
1708
|
+
onChange,
|
|
1709
|
+
placeholder,
|
|
1710
|
+
min,
|
|
1711
|
+
max,
|
|
1712
|
+
step,
|
|
1713
|
+
onClose
|
|
1714
|
+
} = props;
|
|
1715
|
+
const [localValue, setLocalValue] = (0, import_react2.useState)(
|
|
1716
|
+
initialValue !== null ? String(initialValue) : ""
|
|
1717
|
+
);
|
|
1718
|
+
const inputRef = (0, import_react2.useRef)(null);
|
|
1719
|
+
const handleSubmit = (0, import_react2.useCallback)(() => {
|
|
1720
|
+
const numValue = localValue === "" ? null : Number(localValue);
|
|
1721
|
+
onChange(numValue);
|
|
1722
|
+
onClose();
|
|
1723
|
+
}, [localValue, onChange, onClose]);
|
|
1724
|
+
const handleKeyDown = (0, import_react2.useCallback)(
|
|
1725
|
+
(e) => {
|
|
1726
|
+
if (e.key === "Enter") {
|
|
1727
|
+
e.preventDefault();
|
|
1728
|
+
handleSubmit();
|
|
1729
|
+
} else if (e.key === "Escape") {
|
|
1730
|
+
e.preventDefault();
|
|
1731
|
+
onClose();
|
|
1732
|
+
}
|
|
1733
|
+
},
|
|
1734
|
+
[handleSubmit, onClose]
|
|
1735
|
+
);
|
|
1736
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "p-3 space-y-2", children: [
|
|
1737
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
1738
|
+
import_input2.Input,
|
|
1739
|
+
{
|
|
1740
|
+
ref: inputRef,
|
|
1741
|
+
type: "number",
|
|
1742
|
+
value: localValue,
|
|
1743
|
+
onChange: (e) => setLocalValue(e.target.value),
|
|
1744
|
+
onKeyDown: handleKeyDown,
|
|
1745
|
+
placeholder,
|
|
1746
|
+
min,
|
|
1747
|
+
max,
|
|
1748
|
+
step,
|
|
1749
|
+
className: "w-full",
|
|
1750
|
+
autoFocus: true
|
|
1751
|
+
}
|
|
1752
|
+
),
|
|
1753
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "flex justify-end gap-2", children: [
|
|
1754
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_button.Button, { variant: "ghost", size: "sm", onClick: onClose, children: "Cancel" }),
|
|
1755
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_button.Button, { size: "sm", onClick: handleSubmit, children: "Apply" })
|
|
1756
|
+
] })
|
|
1757
|
+
] });
|
|
1758
|
+
}
|
|
1759
|
+
|
|
1760
|
+
// src/filter-bar-messages.ts
|
|
1761
|
+
var import_i18n6 = require("@kopexa/i18n");
|
|
1762
|
+
var filterBarMessages = (0, import_i18n6.defineMessages)({
|
|
1763
|
+
add_filter: {
|
|
1764
|
+
id: "filter-bar.add_filter",
|
|
1765
|
+
defaultMessage: "Add Filter",
|
|
1766
|
+
description: "Button text for adding a new filter"
|
|
1767
|
+
},
|
|
1768
|
+
clear_all: {
|
|
1769
|
+
id: "filter-bar.clear_all",
|
|
1770
|
+
defaultMessage: "Clear all",
|
|
1771
|
+
description: "Button text for clearing all filters"
|
|
1772
|
+
},
|
|
1773
|
+
no_results: {
|
|
1774
|
+
id: "filter-bar.no_results",
|
|
1775
|
+
defaultMessage: "No results found",
|
|
1776
|
+
description: "Message when search returns no results"
|
|
1777
|
+
},
|
|
1778
|
+
search_placeholder: {
|
|
1779
|
+
id: "filter-bar.search_placeholder",
|
|
1780
|
+
defaultMessage: "Search...",
|
|
1781
|
+
description: "Placeholder for search input"
|
|
1782
|
+
},
|
|
1783
|
+
select_value: {
|
|
1784
|
+
id: "filter-bar.select_value",
|
|
1785
|
+
defaultMessage: "Select value...",
|
|
1786
|
+
description: "Placeholder for value selection"
|
|
1787
|
+
},
|
|
1788
|
+
enter_value: {
|
|
1789
|
+
id: "filter-bar.enter_value",
|
|
1790
|
+
defaultMessage: "Enter value...",
|
|
1791
|
+
description: "Placeholder for value input"
|
|
1792
|
+
},
|
|
1793
|
+
apply: {
|
|
1794
|
+
id: "filter-bar.apply",
|
|
1795
|
+
defaultMessage: "Apply",
|
|
1796
|
+
description: "Button text for applying a filter value"
|
|
1797
|
+
},
|
|
1798
|
+
cancel: {
|
|
1799
|
+
id: "filter-bar.cancel",
|
|
1800
|
+
defaultMessage: "Cancel",
|
|
1801
|
+
description: "Button text for canceling"
|
|
1802
|
+
},
|
|
1803
|
+
// Operators
|
|
1804
|
+
op_equals: {
|
|
1805
|
+
id: "filter-bar.operator.equals",
|
|
1806
|
+
defaultMessage: "is",
|
|
1807
|
+
description: "Operator: equals"
|
|
1808
|
+
},
|
|
1809
|
+
op_not_equals: {
|
|
1810
|
+
id: "filter-bar.operator.not_equals",
|
|
1811
|
+
defaultMessage: "is not",
|
|
1812
|
+
description: "Operator: not equals"
|
|
1813
|
+
},
|
|
1814
|
+
op_contains: {
|
|
1815
|
+
id: "filter-bar.operator.contains",
|
|
1816
|
+
defaultMessage: "contains",
|
|
1817
|
+
description: "Operator: contains"
|
|
1818
|
+
},
|
|
1819
|
+
op_not_contains: {
|
|
1820
|
+
id: "filter-bar.operator.not_contains",
|
|
1821
|
+
defaultMessage: "does not contain",
|
|
1822
|
+
description: "Operator: does not contain"
|
|
1823
|
+
},
|
|
1824
|
+
op_starts_with: {
|
|
1825
|
+
id: "filter-bar.operator.starts_with",
|
|
1826
|
+
defaultMessage: "starts with",
|
|
1827
|
+
description: "Operator: starts with"
|
|
1828
|
+
},
|
|
1829
|
+
op_ends_with: {
|
|
1830
|
+
id: "filter-bar.operator.ends_with",
|
|
1831
|
+
defaultMessage: "ends with",
|
|
1832
|
+
description: "Operator: ends with"
|
|
1833
|
+
},
|
|
1834
|
+
op_gt: {
|
|
1835
|
+
id: "filter-bar.operator.gt",
|
|
1836
|
+
defaultMessage: "greater than",
|
|
1837
|
+
description: "Operator: greater than"
|
|
1838
|
+
},
|
|
1839
|
+
op_lt: {
|
|
1840
|
+
id: "filter-bar.operator.lt",
|
|
1841
|
+
defaultMessage: "less than",
|
|
1842
|
+
description: "Operator: less than"
|
|
1843
|
+
},
|
|
1844
|
+
op_gte: {
|
|
1845
|
+
id: "filter-bar.operator.gte",
|
|
1846
|
+
defaultMessage: "greater or equal",
|
|
1847
|
+
description: "Operator: greater than or equal"
|
|
1848
|
+
},
|
|
1849
|
+
op_lte: {
|
|
1850
|
+
id: "filter-bar.operator.lte",
|
|
1851
|
+
defaultMessage: "less or equal",
|
|
1852
|
+
description: "Operator: less than or equal"
|
|
1853
|
+
},
|
|
1854
|
+
op_is_empty: {
|
|
1855
|
+
id: "filter-bar.operator.is_empty",
|
|
1856
|
+
defaultMessage: "is empty",
|
|
1857
|
+
description: "Operator: is empty"
|
|
1858
|
+
},
|
|
1859
|
+
op_is_not_empty: {
|
|
1860
|
+
id: "filter-bar.operator.is_not_empty",
|
|
1861
|
+
defaultMessage: "is not empty",
|
|
1862
|
+
description: "Operator: is not empty"
|
|
1863
|
+
},
|
|
1864
|
+
// Accessibility
|
|
1865
|
+
remove_filter: {
|
|
1866
|
+
id: "filter-bar.remove_filter",
|
|
1867
|
+
defaultMessage: "Remove {field} filter",
|
|
1868
|
+
description: "Accessibility label for remove filter button"
|
|
1869
|
+
},
|
|
1870
|
+
operator_label: {
|
|
1871
|
+
id: "filter-bar.operator_label",
|
|
1872
|
+
defaultMessage: "{field} operator",
|
|
1873
|
+
description: "Accessibility label for operator dropdown"
|
|
1874
|
+
},
|
|
1875
|
+
value_label: {
|
|
1876
|
+
id: "filter-bar.value_label",
|
|
1877
|
+
defaultMessage: "{field} value",
|
|
1878
|
+
description: "Accessibility label for value editor"
|
|
1879
|
+
},
|
|
1880
|
+
// Multiselect
|
|
1881
|
+
selected_count: {
|
|
1882
|
+
id: "filter-bar.selected_count",
|
|
1883
|
+
defaultMessage: "{count} selected",
|
|
1884
|
+
description: "Display text for multiselect with multiple selections"
|
|
1885
|
+
}
|
|
1886
|
+
});
|
|
1887
|
+
|
|
1888
|
+
// src/filter-bar.tsx
|
|
1889
|
+
var import_jsx_runtime7 = require("react/jsx-runtime");
|
|
1890
|
+
function FilterBar(props) {
|
|
1891
|
+
const {
|
|
1892
|
+
fields,
|
|
1893
|
+
value: valueProp,
|
|
1894
|
+
defaultValue = [],
|
|
1895
|
+
onChange,
|
|
1896
|
+
i18n: i18nProp,
|
|
1897
|
+
className,
|
|
1898
|
+
allowMultiple = true,
|
|
1899
|
+
disabled = false
|
|
1900
|
+
} = props;
|
|
1901
|
+
const t = (0, import_i18n7.useSafeIntl)();
|
|
1902
|
+
const instanceId = (0, import_react3.useId)();
|
|
1903
|
+
const [value, setValue] = (0, import_use_controllable_state.useControllableState)({
|
|
1904
|
+
value: valueProp,
|
|
1905
|
+
defaultValue,
|
|
1906
|
+
onChange
|
|
1907
|
+
});
|
|
1908
|
+
const [sessionFilterId, setSessionFilterId] = (0, import_react3.useState)(null);
|
|
1909
|
+
const i18n = (0, import_react3.useMemo)(() => {
|
|
1910
|
+
const defaultI18n = {
|
|
1911
|
+
addFilter: t.formatMessage(filterBarMessages.add_filter),
|
|
1912
|
+
clearAll: t.formatMessage(filterBarMessages.clear_all),
|
|
1913
|
+
noResults: t.formatMessage(filterBarMessages.no_results),
|
|
1914
|
+
searchPlaceholder: t.formatMessage(filterBarMessages.search_placeholder),
|
|
1915
|
+
selectValue: t.formatMessage(filterBarMessages.select_value),
|
|
1916
|
+
enterValue: t.formatMessage(filterBarMessages.enter_value),
|
|
1917
|
+
operators: {
|
|
1918
|
+
equals: t.formatMessage(filterBarMessages.op_equals),
|
|
1919
|
+
not_equals: t.formatMessage(filterBarMessages.op_not_equals),
|
|
1920
|
+
contains: t.formatMessage(filterBarMessages.op_contains),
|
|
1921
|
+
not_contains: t.formatMessage(filterBarMessages.op_not_contains),
|
|
1922
|
+
starts_with: t.formatMessage(filterBarMessages.op_starts_with),
|
|
1923
|
+
ends_with: t.formatMessage(filterBarMessages.op_ends_with),
|
|
1924
|
+
gt: t.formatMessage(filterBarMessages.op_gt),
|
|
1925
|
+
lt: t.formatMessage(filterBarMessages.op_lt),
|
|
1926
|
+
gte: t.formatMessage(filterBarMessages.op_gte),
|
|
1927
|
+
lte: t.formatMessage(filterBarMessages.op_lte),
|
|
1928
|
+
is_empty: t.formatMessage(filterBarMessages.op_is_empty),
|
|
1929
|
+
is_not_empty: t.formatMessage(filterBarMessages.op_is_not_empty)
|
|
1930
|
+
}
|
|
1931
|
+
};
|
|
1932
|
+
return {
|
|
1933
|
+
...defaultI18n,
|
|
1934
|
+
...i18nProp,
|
|
1935
|
+
operators: {
|
|
1936
|
+
...defaultI18n.operators,
|
|
1937
|
+
...i18nProp == null ? void 0 : i18nProp.operators
|
|
1938
|
+
}
|
|
1939
|
+
};
|
|
1940
|
+
}, [t, i18nProp]);
|
|
1941
|
+
const fieldGroups = (0, import_react3.useMemo)(() => {
|
|
1942
|
+
var _a;
|
|
1943
|
+
const groups = /* @__PURE__ */ new Map();
|
|
1944
|
+
for (const field of fields) {
|
|
1945
|
+
const group = field.group;
|
|
1946
|
+
if (!groups.has(group)) {
|
|
1947
|
+
groups.set(group, []);
|
|
1948
|
+
}
|
|
1949
|
+
(_a = groups.get(group)) == null ? void 0 : _a.push(field);
|
|
1950
|
+
}
|
|
1951
|
+
return groups;
|
|
1952
|
+
}, [fields]);
|
|
1953
|
+
const addFilter = (0, import_react3.useCallback)(
|
|
1954
|
+
(fieldId) => {
|
|
1955
|
+
var _a;
|
|
1956
|
+
const field = fields.find((f) => f.id === fieldId);
|
|
1957
|
+
if (!field) return;
|
|
1958
|
+
if (!allowMultiple && value.some((f) => f.fieldId === fieldId)) {
|
|
1959
|
+
return;
|
|
1960
|
+
}
|
|
1961
|
+
const newFilter = {
|
|
1962
|
+
id: `${instanceId}-${fieldId}-${Date.now()}`,
|
|
1963
|
+
fieldId,
|
|
1964
|
+
operator: (_a = field.defaultOperator) != null ? _a : "equals",
|
|
1965
|
+
value: field.type === "multiselect" ? [] : null
|
|
1966
|
+
};
|
|
1967
|
+
setValue([...value, newFilter]);
|
|
1968
|
+
setSessionFilterId(newFilter.id);
|
|
1969
|
+
},
|
|
1970
|
+
[fields, value, setValue, allowMultiple, instanceId]
|
|
1971
|
+
);
|
|
1972
|
+
const updateFilter = (0, import_react3.useCallback)(
|
|
1973
|
+
(filterId, updates) => {
|
|
1974
|
+
setValue(
|
|
1975
|
+
value.map((f) => f.id === filterId ? { ...f, ...updates } : f)
|
|
1976
|
+
);
|
|
1977
|
+
},
|
|
1978
|
+
[value, setValue]
|
|
1979
|
+
);
|
|
1980
|
+
const removeFilter = (0, import_react3.useCallback)(
|
|
1981
|
+
(filterId) => {
|
|
1982
|
+
setValue(value.filter((f) => f.id !== filterId));
|
|
1983
|
+
if (sessionFilterId === filterId) {
|
|
1984
|
+
setSessionFilterId(null);
|
|
1985
|
+
}
|
|
1986
|
+
},
|
|
1987
|
+
[value, setValue, sessionFilterId]
|
|
1988
|
+
);
|
|
1989
|
+
const clearFilters = (0, import_react3.useCallback)(() => {
|
|
1990
|
+
setValue([]);
|
|
1991
|
+
setSessionFilterId(null);
|
|
1992
|
+
}, [setValue]);
|
|
1993
|
+
const isFieldAvailable = (0, import_react3.useCallback)(
|
|
1994
|
+
(fieldId) => {
|
|
1995
|
+
if (allowMultiple) return true;
|
|
1996
|
+
return !value.some((f) => f.fieldId === fieldId);
|
|
1997
|
+
},
|
|
1998
|
+
[value, allowMultiple]
|
|
1999
|
+
);
|
|
2000
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
|
|
2001
|
+
"div",
|
|
2002
|
+
{
|
|
2003
|
+
"data-slot": "filter-bar",
|
|
2004
|
+
className: (0, import_shared_utils6.cn)("flex flex-wrap items-center gap-2", className),
|
|
2005
|
+
children: [
|
|
2006
|
+
value.map((filter) => {
|
|
2007
|
+
const field = fields.find((f) => f.id === filter.fieldId);
|
|
2008
|
+
if (!field) return null;
|
|
2009
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
2010
|
+
FilterPill,
|
|
2011
|
+
{
|
|
2012
|
+
filter,
|
|
2013
|
+
field,
|
|
2014
|
+
i18n,
|
|
2015
|
+
onUpdate: (updates) => updateFilter(filter.id, updates),
|
|
2016
|
+
onRemove: () => removeFilter(filter.id),
|
|
2017
|
+
autoOpen: sessionFilterId === filter.id,
|
|
2018
|
+
onAutoOpenComplete: () => setSessionFilterId(null),
|
|
2019
|
+
disabled
|
|
2020
|
+
},
|
|
2021
|
+
filter.id
|
|
2022
|
+
);
|
|
2023
|
+
}),
|
|
2024
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
2025
|
+
AddFilterDropdown,
|
|
2026
|
+
{
|
|
2027
|
+
fieldGroups,
|
|
2028
|
+
isFieldAvailable,
|
|
2029
|
+
onAddFilter: addFilter,
|
|
2030
|
+
i18n,
|
|
2031
|
+
disabled
|
|
2032
|
+
}
|
|
2033
|
+
),
|
|
2034
|
+
value.length > 1 && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
2035
|
+
"button",
|
|
2036
|
+
{
|
|
2037
|
+
type: "button",
|
|
2038
|
+
onClick: clearFilters,
|
|
2039
|
+
className: "text-xs text-muted-foreground hover:text-foreground cursor-pointer transition-colors underline-offset-2 hover:underline",
|
|
2040
|
+
disabled,
|
|
2041
|
+
children: i18n.clearAll
|
|
2042
|
+
}
|
|
2043
|
+
)
|
|
2044
|
+
]
|
|
2045
|
+
}
|
|
2046
|
+
);
|
|
2047
|
+
}
|
|
2048
|
+
FilterBar.displayName = "FilterBar";
|
|
2049
|
+
|
|
2050
|
+
// src/search-filter-bar.tsx
|
|
2051
|
+
var import_i18n8 = require("@kopexa/i18n");
|
|
2052
|
+
var import_icons4 = require("@kopexa/icons");
|
|
2053
|
+
var import_shared_utils7 = require("@kopexa/shared-utils");
|
|
2054
|
+
var import_spinner = require("@kopexa/spinner");
|
|
2055
|
+
var import_use_controllable_state2 = require("@kopexa/use-controllable-state");
|
|
2056
|
+
var import_use_debounced_callback = require("@kopexa/use-debounced-callback");
|
|
2057
|
+
var import_react4 = require("react");
|
|
2058
|
+
var import_jsx_runtime8 = require("react/jsx-runtime");
|
|
2059
|
+
function SearchFilterBar(props) {
|
|
2060
|
+
const {
|
|
2061
|
+
fields,
|
|
2062
|
+
filters: filtersProp,
|
|
2063
|
+
defaultFilters = [],
|
|
2064
|
+
onFiltersChange,
|
|
2065
|
+
search: searchProp,
|
|
2066
|
+
defaultSearch = "",
|
|
2067
|
+
onSearchChange,
|
|
2068
|
+
onSearchValueChange,
|
|
2069
|
+
searchDebounce = 300,
|
|
2070
|
+
searchPlaceholder,
|
|
2071
|
+
searchLoading = false,
|
|
2072
|
+
i18n: i18nProp,
|
|
2073
|
+
className,
|
|
2074
|
+
allowMultiple = true,
|
|
2075
|
+
disabled = false,
|
|
2076
|
+
endContent
|
|
2077
|
+
} = props;
|
|
2078
|
+
const t = (0, import_i18n8.useSafeIntl)();
|
|
2079
|
+
const instanceId = (0, import_react4.useId)();
|
|
2080
|
+
const [filters, setFilters] = (0, import_use_controllable_state2.useControllableState)({
|
|
2081
|
+
value: filtersProp,
|
|
2082
|
+
defaultValue: defaultFilters,
|
|
2083
|
+
onChange: onFiltersChange
|
|
2084
|
+
});
|
|
2085
|
+
const [search, setSearch] = (0, import_use_controllable_state2.useControllableState)({
|
|
2086
|
+
value: searchProp,
|
|
2087
|
+
defaultValue: defaultSearch,
|
|
2088
|
+
onChange: onSearchChange
|
|
2089
|
+
});
|
|
2090
|
+
const [sessionFilterId, setSessionFilterId] = (0, import_react4.useState)(null);
|
|
2091
|
+
const i18n = (0, import_react4.useMemo)(() => {
|
|
2092
|
+
const defaultI18n = {
|
|
2093
|
+
addFilter: t.formatMessage(filterBarMessages.add_filter),
|
|
2094
|
+
clearAll: t.formatMessage(filterBarMessages.clear_all),
|
|
2095
|
+
noResults: t.formatMessage(filterBarMessages.no_results),
|
|
2096
|
+
searchPlaceholder: t.formatMessage(filterBarMessages.search_placeholder),
|
|
2097
|
+
selectValue: t.formatMessage(filterBarMessages.select_value),
|
|
2098
|
+
enterValue: t.formatMessage(filterBarMessages.enter_value),
|
|
2099
|
+
operators: {
|
|
2100
|
+
equals: t.formatMessage(filterBarMessages.op_equals),
|
|
2101
|
+
not_equals: t.formatMessage(filterBarMessages.op_not_equals),
|
|
2102
|
+
contains: t.formatMessage(filterBarMessages.op_contains),
|
|
2103
|
+
not_contains: t.formatMessage(filterBarMessages.op_not_contains),
|
|
2104
|
+
starts_with: t.formatMessage(filterBarMessages.op_starts_with),
|
|
2105
|
+
ends_with: t.formatMessage(filterBarMessages.op_ends_with),
|
|
2106
|
+
gt: t.formatMessage(filterBarMessages.op_gt),
|
|
2107
|
+
lt: t.formatMessage(filterBarMessages.op_lt),
|
|
2108
|
+
gte: t.formatMessage(filterBarMessages.op_gte),
|
|
2109
|
+
lte: t.formatMessage(filterBarMessages.op_lte),
|
|
2110
|
+
is_empty: t.formatMessage(filterBarMessages.op_is_empty),
|
|
2111
|
+
is_not_empty: t.formatMessage(filterBarMessages.op_is_not_empty)
|
|
2112
|
+
}
|
|
2113
|
+
};
|
|
2114
|
+
return {
|
|
2115
|
+
...defaultI18n,
|
|
2116
|
+
...i18nProp,
|
|
2117
|
+
operators: {
|
|
2118
|
+
...defaultI18n.operators,
|
|
2119
|
+
...i18nProp == null ? void 0 : i18nProp.operators
|
|
2120
|
+
}
|
|
2121
|
+
};
|
|
2122
|
+
}, [t, i18nProp]);
|
|
2123
|
+
const fieldGroups = (0, import_react4.useMemo)(() => {
|
|
2124
|
+
var _a;
|
|
2125
|
+
const groups = /* @__PURE__ */ new Map();
|
|
2126
|
+
for (const field of fields) {
|
|
2127
|
+
const group = field.group;
|
|
2128
|
+
if (!groups.has(group)) {
|
|
2129
|
+
groups.set(group, []);
|
|
2130
|
+
}
|
|
2131
|
+
(_a = groups.get(group)) == null ? void 0 : _a.push(field);
|
|
2132
|
+
}
|
|
2133
|
+
return groups;
|
|
2134
|
+
}, [fields]);
|
|
2135
|
+
const addFilter = (0, import_react4.useCallback)(
|
|
2136
|
+
(fieldId) => {
|
|
2137
|
+
var _a;
|
|
2138
|
+
const field = fields.find((f) => f.id === fieldId);
|
|
2139
|
+
if (!field) return;
|
|
2140
|
+
if (!allowMultiple && filters.some((f) => f.fieldId === fieldId)) {
|
|
2141
|
+
return;
|
|
2142
|
+
}
|
|
2143
|
+
const newFilter = {
|
|
2144
|
+
id: `${instanceId}-${fieldId}-${Date.now()}`,
|
|
2145
|
+
fieldId,
|
|
2146
|
+
operator: (_a = field.defaultOperator) != null ? _a : "equals",
|
|
2147
|
+
value: field.type === "multiselect" ? [] : null
|
|
2148
|
+
};
|
|
2149
|
+
setFilters([...filters, newFilter]);
|
|
2150
|
+
setSessionFilterId(newFilter.id);
|
|
2151
|
+
},
|
|
2152
|
+
[fields, filters, setFilters, allowMultiple, instanceId]
|
|
2153
|
+
);
|
|
2154
|
+
const updateFilter = (0, import_react4.useCallback)(
|
|
2155
|
+
(filterId, updates) => {
|
|
2156
|
+
setFilters(
|
|
2157
|
+
filters.map((f) => f.id === filterId ? { ...f, ...updates } : f)
|
|
2158
|
+
);
|
|
2159
|
+
},
|
|
2160
|
+
[filters, setFilters]
|
|
2161
|
+
);
|
|
2162
|
+
const removeFilter = (0, import_react4.useCallback)(
|
|
2163
|
+
(filterId) => {
|
|
2164
|
+
setFilters(filters.filter((f) => f.id !== filterId));
|
|
2165
|
+
if (sessionFilterId === filterId) {
|
|
2166
|
+
setSessionFilterId(null);
|
|
2167
|
+
}
|
|
2168
|
+
},
|
|
2169
|
+
[filters, setFilters, sessionFilterId]
|
|
2170
|
+
);
|
|
2171
|
+
const clearFilters = (0, import_react4.useCallback)(() => {
|
|
2172
|
+
setFilters([]);
|
|
2173
|
+
setSessionFilterId(null);
|
|
2174
|
+
}, [setFilters]);
|
|
2175
|
+
const isFieldAvailable = (0, import_react4.useCallback)(
|
|
2176
|
+
(fieldId) => {
|
|
2177
|
+
if (allowMultiple) return true;
|
|
2178
|
+
return !filters.some((f) => f.fieldId === fieldId);
|
|
2179
|
+
},
|
|
2180
|
+
[filters, allowMultiple]
|
|
2181
|
+
);
|
|
2182
|
+
const debouncedSearchCallback = (0, import_use_debounced_callback.useDebounceCallback)((value) => {
|
|
2183
|
+
onSearchValueChange == null ? void 0 : onSearchValueChange(value);
|
|
2184
|
+
}, searchDebounce);
|
|
2185
|
+
const handleSearchChange = (0, import_react4.useCallback)(
|
|
2186
|
+
(e) => {
|
|
2187
|
+
const value = e.target.value;
|
|
2188
|
+
setSearch(value);
|
|
2189
|
+
if (onSearchValueChange) {
|
|
2190
|
+
debouncedSearchCallback(value);
|
|
2191
|
+
}
|
|
2192
|
+
},
|
|
2193
|
+
[setSearch, onSearchValueChange, debouncedSearchCallback]
|
|
2194
|
+
);
|
|
2195
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
|
|
2196
|
+
"div",
|
|
2197
|
+
{
|
|
2198
|
+
"data-slot": "search-filter-bar",
|
|
2199
|
+
className: (0, import_shared_utils7.cn)("flex items-center gap-2 w-full", className),
|
|
2200
|
+
children: [
|
|
2201
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
|
|
2202
|
+
"div",
|
|
2203
|
+
{
|
|
2204
|
+
className: (0, import_shared_utils7.cn)(
|
|
2205
|
+
"flex flex-1 flex-wrap items-center gap-2",
|
|
2206
|
+
"border border-input rounded-lg",
|
|
2207
|
+
"bg-background",
|
|
2208
|
+
"px-3 py-2",
|
|
2209
|
+
"focus-within:ring-2 focus-within:ring-ring focus-within:ring-offset-1",
|
|
2210
|
+
"transition-shadow",
|
|
2211
|
+
disabled && "opacity-50 pointer-events-none"
|
|
2212
|
+
),
|
|
2213
|
+
children: [
|
|
2214
|
+
filters.map((filter) => {
|
|
2215
|
+
const field = fields.find((f) => f.id === filter.fieldId);
|
|
2216
|
+
if (!field) return null;
|
|
2217
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
2218
|
+
FilterPill,
|
|
2219
|
+
{
|
|
2220
|
+
filter,
|
|
2221
|
+
field,
|
|
2222
|
+
i18n,
|
|
2223
|
+
onUpdate: (updates) => updateFilter(filter.id, updates),
|
|
2224
|
+
onRemove: () => removeFilter(filter.id),
|
|
2225
|
+
autoOpen: sessionFilterId === filter.id,
|
|
2226
|
+
onAutoOpenComplete: () => setSessionFilterId(null),
|
|
2227
|
+
disabled
|
|
2228
|
+
},
|
|
2229
|
+
filter.id
|
|
2230
|
+
);
|
|
2231
|
+
}),
|
|
2232
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
2233
|
+
AddFilterDropdown,
|
|
2234
|
+
{
|
|
2235
|
+
fieldGroups,
|
|
2236
|
+
isFieldAvailable,
|
|
2237
|
+
onAddFilter: addFilter,
|
|
2238
|
+
i18n,
|
|
2239
|
+
disabled
|
|
2240
|
+
}
|
|
2241
|
+
),
|
|
2242
|
+
filters.length > 1 && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
2243
|
+
"button",
|
|
2244
|
+
{
|
|
2245
|
+
type: "button",
|
|
2246
|
+
onClick: clearFilters,
|
|
2247
|
+
className: "text-xs text-muted-foreground hover:text-foreground cursor-pointer transition-colors underline-offset-2 hover:underline",
|
|
2248
|
+
disabled,
|
|
2249
|
+
children: i18n.clearAll
|
|
2250
|
+
}
|
|
2251
|
+
),
|
|
2252
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "flex flex-1 items-center gap-2 min-w-[120px]", children: [
|
|
2253
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_icons4.SearchIcon, { className: "size-4 text-muted-foreground flex-shrink-0" }),
|
|
2254
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
2255
|
+
"input",
|
|
2256
|
+
{
|
|
2257
|
+
type: "text",
|
|
2258
|
+
value: search,
|
|
2259
|
+
onChange: handleSearchChange,
|
|
2260
|
+
placeholder: searchPlaceholder != null ? searchPlaceholder : i18n.searchPlaceholder,
|
|
2261
|
+
disabled,
|
|
2262
|
+
className: (0, import_shared_utils7.cn)(
|
|
2263
|
+
"flex-1 bg-transparent border-none outline-none",
|
|
2264
|
+
"text-sm placeholder:text-muted-foreground",
|
|
2265
|
+
"min-w-0"
|
|
2266
|
+
)
|
|
2267
|
+
}
|
|
2268
|
+
),
|
|
2269
|
+
searchLoading && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_spinner.Spinner, { size: "xs", className: "flex-shrink-0" })
|
|
2270
|
+
] })
|
|
2271
|
+
]
|
|
2272
|
+
}
|
|
2273
|
+
),
|
|
2274
|
+
endContent
|
|
2275
|
+
]
|
|
2276
|
+
}
|
|
2277
|
+
);
|
|
2278
|
+
}
|
|
2279
|
+
SearchFilterBar.displayName = "SearchFilterBar";
|
|
1051
2280
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1052
2281
|
0 && (module.exports = {
|
|
1053
2282
|
Filter,
|
|
1054
2283
|
FilterActive,
|
|
2284
|
+
FilterBar,
|
|
1055
2285
|
FilterGroup,
|
|
1056
2286
|
FilterItem,
|
|
1057
2287
|
FilterMenu,
|
|
1058
2288
|
FilterMenuSeparator,
|
|
1059
2289
|
FilterTrigger,
|
|
1060
2290
|
FilterValueEditor,
|
|
2291
|
+
SearchFilterBar,
|
|
2292
|
+
filterBarMessages,
|
|
1061
2293
|
filterMessages,
|
|
1062
2294
|
useFilterContext
|
|
1063
2295
|
});
|