@kopexa/filter 0.0.25 → 0.0.27

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/dist/chunk-6TRAIAKS.mjs +178 -0
  2. package/dist/chunk-I7WCYMXD.mjs +133 -0
  3. package/dist/{chunk-45QJL74L.mjs → chunk-JSRGUDCG.mjs} +49 -24
  4. package/dist/chunk-NUDUXOHP.mjs +243 -0
  5. package/dist/chunk-ON2UFJ3Y.mjs +32 -0
  6. package/dist/{chunk-RFCPJLIQ.mjs → chunk-SJXRD3RO.mjs} +6 -1
  7. package/dist/{chunk-EF4VI36D.mjs → chunk-UBTUCPOG.mjs} +1 -1
  8. package/dist/{chunk-3ZBNWXRA.mjs → chunk-WD7YU6IN.mjs} +17 -14
  9. package/dist/chunk-XCWKWXBW.mjs +602 -0
  10. package/dist/{chunk-URDCG5NI.mjs → chunk-YTYOFT33.mjs} +52 -0
  11. package/dist/filter-active.js +108 -28
  12. package/dist/filter-active.mjs +4 -4
  13. package/dist/filter-bar-internal.d.mts +25 -0
  14. package/dist/filter-bar-internal.d.ts +25 -0
  15. package/dist/filter-bar-internal.js +648 -0
  16. package/dist/filter-bar-internal.mjs +11 -0
  17. package/dist/filter-bar-messages.d.mts +124 -0
  18. package/dist/filter-bar-messages.d.ts +124 -0
  19. package/dist/filter-bar-messages.js +156 -0
  20. package/dist/filter-bar-messages.mjs +7 -0
  21. package/dist/filter-bar-types.d.mts +112 -0
  22. package/dist/filter-bar-types.d.ts +112 -0
  23. package/dist/filter-bar-types.js +56 -0
  24. package/dist/filter-bar-types.mjs +8 -0
  25. package/dist/filter-bar.d.mts +29 -0
  26. package/dist/filter-bar.d.ts +29 -0
  27. package/dist/filter-bar.js +942 -0
  28. package/dist/filter-bar.mjs +11 -0
  29. package/dist/filter-menu.js +161 -1
  30. package/dist/filter-menu.mjs +2 -1
  31. package/dist/filter-trigger.js +52 -0
  32. package/dist/filter-trigger.mjs +2 -2
  33. package/dist/filter-value-editor.js +65 -10
  34. package/dist/filter-value-editor.mjs +3 -3
  35. package/dist/filter.d.mts +6 -0
  36. package/dist/filter.d.ts +6 -0
  37. package/dist/filter.mjs +2 -2
  38. package/dist/index.d.mts +4 -0
  39. package/dist/index.d.ts +4 -0
  40. package/dist/index.js +1263 -31
  41. package/dist/index.mjs +28 -14
  42. package/dist/messages.d.mts +50 -0
  43. package/dist/messages.d.ts +50 -0
  44. package/dist/messages.js +52 -0
  45. package/dist/messages.mjs +1 -1
  46. package/dist/search-filter-bar.d.mts +44 -0
  47. package/dist/search-filter-bar.d.ts +44 -0
  48. package/dist/search-filter-bar.js +1007 -0
  49. package/dist/search-filter-bar.mjs +11 -0
  50. package/package.json +19 -16
  51. package/dist/{chunk-SH7DBK54.mjs → chunk-LWDVRMCI.mjs} +3 -3
@@ -0,0 +1,243 @@
1
+ "use client";
2
+ import {
3
+ AddFilterDropdown,
4
+ FilterPill
5
+ } from "./chunk-XCWKWXBW.mjs";
6
+ import {
7
+ filterBarMessages
8
+ } from "./chunk-I7WCYMXD.mjs";
9
+
10
+ // src/search-filter-bar.tsx
11
+ import { useSafeIntl } from "@kopexa/i18n";
12
+ import { SearchIcon } from "@kopexa/icons";
13
+ import { cn } from "@kopexa/shared-utils";
14
+ import { Spinner } from "@kopexa/spinner";
15
+ import { useControllableState } from "@kopexa/use-controllable-state";
16
+ import { useDebounceCallback } from "@kopexa/use-debounced-callback";
17
+ import { useCallback, useId, useMemo, useState } from "react";
18
+ import { jsx, jsxs } from "react/jsx-runtime";
19
+ function SearchFilterBar(props) {
20
+ const {
21
+ fields,
22
+ filters: filtersProp,
23
+ defaultFilters = [],
24
+ onFiltersChange,
25
+ search: searchProp,
26
+ defaultSearch = "",
27
+ onSearchChange,
28
+ onSearchValueChange,
29
+ searchDebounce = 300,
30
+ searchPlaceholder,
31
+ searchLoading = false,
32
+ i18n: i18nProp,
33
+ className,
34
+ allowMultiple = true,
35
+ disabled = false,
36
+ endContent
37
+ } = props;
38
+ const t = useSafeIntl();
39
+ const instanceId = useId();
40
+ const [filters, setFilters] = useControllableState({
41
+ value: filtersProp,
42
+ defaultValue: defaultFilters,
43
+ onChange: onFiltersChange
44
+ });
45
+ const [search, setSearch] = useControllableState({
46
+ value: searchProp,
47
+ defaultValue: defaultSearch,
48
+ onChange: onSearchChange
49
+ });
50
+ const [sessionFilterId, setSessionFilterId] = useState(null);
51
+ const i18n = useMemo(() => {
52
+ const defaultI18n = {
53
+ addFilter: t.formatMessage(filterBarMessages.add_filter),
54
+ clearAll: t.formatMessage(filterBarMessages.clear_all),
55
+ noResults: t.formatMessage(filterBarMessages.no_results),
56
+ searchPlaceholder: t.formatMessage(filterBarMessages.search_placeholder),
57
+ selectValue: t.formatMessage(filterBarMessages.select_value),
58
+ enterValue: t.formatMessage(filterBarMessages.enter_value),
59
+ operators: {
60
+ equals: t.formatMessage(filterBarMessages.op_equals),
61
+ not_equals: t.formatMessage(filterBarMessages.op_not_equals),
62
+ contains: t.formatMessage(filterBarMessages.op_contains),
63
+ not_contains: t.formatMessage(filterBarMessages.op_not_contains),
64
+ starts_with: t.formatMessage(filterBarMessages.op_starts_with),
65
+ ends_with: t.formatMessage(filterBarMessages.op_ends_with),
66
+ gt: t.formatMessage(filterBarMessages.op_gt),
67
+ lt: t.formatMessage(filterBarMessages.op_lt),
68
+ gte: t.formatMessage(filterBarMessages.op_gte),
69
+ lte: t.formatMessage(filterBarMessages.op_lte),
70
+ is_empty: t.formatMessage(filterBarMessages.op_is_empty),
71
+ is_not_empty: t.formatMessage(filterBarMessages.op_is_not_empty)
72
+ }
73
+ };
74
+ return {
75
+ ...defaultI18n,
76
+ ...i18nProp,
77
+ operators: {
78
+ ...defaultI18n.operators,
79
+ ...i18nProp == null ? void 0 : i18nProp.operators
80
+ }
81
+ };
82
+ }, [t, i18nProp]);
83
+ const fieldGroups = useMemo(() => {
84
+ var _a;
85
+ const groups = /* @__PURE__ */ new Map();
86
+ for (const field of fields) {
87
+ const group = field.group;
88
+ if (!groups.has(group)) {
89
+ groups.set(group, []);
90
+ }
91
+ (_a = groups.get(group)) == null ? void 0 : _a.push(field);
92
+ }
93
+ return groups;
94
+ }, [fields]);
95
+ const addFilter = useCallback(
96
+ (fieldId) => {
97
+ var _a;
98
+ const field = fields.find((f) => f.id === fieldId);
99
+ if (!field) return;
100
+ if (!allowMultiple && filters.some((f) => f.fieldId === fieldId)) {
101
+ return;
102
+ }
103
+ const newFilter = {
104
+ id: `${instanceId}-${fieldId}-${Date.now()}`,
105
+ fieldId,
106
+ operator: (_a = field.defaultOperator) != null ? _a : "equals",
107
+ value: field.type === "multiselect" ? [] : null
108
+ };
109
+ setFilters([...filters, newFilter]);
110
+ setSessionFilterId(newFilter.id);
111
+ },
112
+ [fields, filters, setFilters, allowMultiple, instanceId]
113
+ );
114
+ const updateFilter = useCallback(
115
+ (filterId, updates) => {
116
+ setFilters(
117
+ filters.map((f) => f.id === filterId ? { ...f, ...updates } : f)
118
+ );
119
+ },
120
+ [filters, setFilters]
121
+ );
122
+ const removeFilter = useCallback(
123
+ (filterId) => {
124
+ setFilters(filters.filter((f) => f.id !== filterId));
125
+ if (sessionFilterId === filterId) {
126
+ setSessionFilterId(null);
127
+ }
128
+ },
129
+ [filters, setFilters, sessionFilterId]
130
+ );
131
+ const clearFilters = useCallback(() => {
132
+ setFilters([]);
133
+ setSessionFilterId(null);
134
+ }, [setFilters]);
135
+ const isFieldAvailable = useCallback(
136
+ (fieldId) => {
137
+ if (allowMultiple) return true;
138
+ return !filters.some((f) => f.fieldId === fieldId);
139
+ },
140
+ [filters, allowMultiple]
141
+ );
142
+ const debouncedSearchCallback = useDebounceCallback((value) => {
143
+ onSearchValueChange == null ? void 0 : onSearchValueChange(value);
144
+ }, searchDebounce);
145
+ const handleSearchChange = useCallback(
146
+ (e) => {
147
+ const value = e.target.value;
148
+ setSearch(value);
149
+ if (onSearchValueChange) {
150
+ debouncedSearchCallback(value);
151
+ }
152
+ },
153
+ [setSearch, onSearchValueChange, debouncedSearchCallback]
154
+ );
155
+ return /* @__PURE__ */ jsxs(
156
+ "div",
157
+ {
158
+ "data-slot": "search-filter-bar",
159
+ className: cn("flex items-center gap-2 w-full", className),
160
+ children: [
161
+ /* @__PURE__ */ jsxs(
162
+ "div",
163
+ {
164
+ className: cn(
165
+ "flex flex-1 flex-wrap items-center gap-2",
166
+ "border border-input rounded-lg",
167
+ "bg-background",
168
+ "px-3 py-2",
169
+ "focus-within:ring-2 focus-within:ring-ring focus-within:ring-offset-1",
170
+ "transition-shadow",
171
+ disabled && "opacity-50 pointer-events-none"
172
+ ),
173
+ children: [
174
+ filters.map((filter) => {
175
+ const field = fields.find((f) => f.id === filter.fieldId);
176
+ if (!field) return null;
177
+ return /* @__PURE__ */ jsx(
178
+ FilterPill,
179
+ {
180
+ filter,
181
+ field,
182
+ i18n,
183
+ onUpdate: (updates) => updateFilter(filter.id, updates),
184
+ onRemove: () => removeFilter(filter.id),
185
+ autoOpen: sessionFilterId === filter.id,
186
+ onAutoOpenComplete: () => setSessionFilterId(null),
187
+ disabled
188
+ },
189
+ filter.id
190
+ );
191
+ }),
192
+ /* @__PURE__ */ jsx(
193
+ AddFilterDropdown,
194
+ {
195
+ fieldGroups,
196
+ isFieldAvailable,
197
+ onAddFilter: addFilter,
198
+ i18n,
199
+ disabled
200
+ }
201
+ ),
202
+ filters.length > 1 && /* @__PURE__ */ jsx(
203
+ "button",
204
+ {
205
+ type: "button",
206
+ onClick: clearFilters,
207
+ className: "text-xs text-muted-foreground hover:text-foreground cursor-pointer transition-colors underline-offset-2 hover:underline",
208
+ disabled,
209
+ children: i18n.clearAll
210
+ }
211
+ ),
212
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-1 items-center gap-2 min-w-[120px]", children: [
213
+ /* @__PURE__ */ jsx(SearchIcon, { className: "size-4 text-muted-foreground flex-shrink-0" }),
214
+ /* @__PURE__ */ jsx(
215
+ "input",
216
+ {
217
+ type: "text",
218
+ value: search,
219
+ onChange: handleSearchChange,
220
+ placeholder: searchPlaceholder != null ? searchPlaceholder : i18n.searchPlaceholder,
221
+ disabled,
222
+ className: cn(
223
+ "flex-1 bg-transparent border-none outline-none",
224
+ "text-sm placeholder:text-muted-foreground",
225
+ "min-w-0"
226
+ )
227
+ }
228
+ ),
229
+ searchLoading && /* @__PURE__ */ jsx(Spinner, { size: "xs", className: "flex-shrink-0" })
230
+ ] })
231
+ ]
232
+ }
233
+ ),
234
+ endContent
235
+ ]
236
+ }
237
+ );
238
+ }
239
+ SearchFilterBar.displayName = "SearchFilterBar";
240
+
241
+ export {
242
+ SearchFilterBar
243
+ };
@@ -0,0 +1,32 @@
1
+ "use client";
2
+
3
+ // src/filter-bar-types.ts
4
+ var DEFAULT_FILTER_BAR_OPERATORS = {
5
+ select: ["equals", "not_equals", "is_empty", "is_not_empty"],
6
+ multiselect: ["equals", "not_equals", "is_empty", "is_not_empty"],
7
+ text: [
8
+ "equals",
9
+ "not_equals",
10
+ "contains",
11
+ "not_contains",
12
+ "starts_with",
13
+ "ends_with",
14
+ "is_empty",
15
+ "is_not_empty"
16
+ ],
17
+ number: [
18
+ "equals",
19
+ "not_equals",
20
+ "gt",
21
+ "lt",
22
+ "gte",
23
+ "lte",
24
+ "is_empty",
25
+ "is_not_empty"
26
+ ],
27
+ custom: ["equals", "not_equals"]
28
+ };
29
+
30
+ export {
31
+ DEFAULT_FILTER_BAR_OPERATORS
32
+ };
@@ -1,10 +1,14 @@
1
1
  "use client";
2
+ import {
3
+ messages
4
+ } from "./chunk-YTYOFT33.mjs";
2
5
  import {
3
6
  useFilterContext
4
7
  } from "./chunk-I3Z2T4N2.mjs";
5
8
 
6
9
  // src/filter-menu.tsx
7
10
  import { DropdownMenu } from "@kopexa/dropdown-menu";
11
+ import { useSafeIntl } from "@kopexa/i18n";
8
12
  import { cn } from "@kopexa/shared-utils";
9
13
  import {
10
14
  createContext,
@@ -16,6 +20,7 @@ function FilterMenu(props) {
16
20
  var _a;
17
21
  const { children, className } = props;
18
22
  const { fields, styles, addFilter, allowMultiple, value } = useFilterContext();
23
+ const t = useSafeIntl();
19
24
  const fieldGroups = /* @__PURE__ */ new Map();
20
25
  for (const [id, field] of fields) {
21
26
  const group = field.group;
@@ -32,7 +37,7 @@ function FilterMenu(props) {
32
37
  "data-slot": "filter-menu",
33
38
  className: cn("min-w-[220px]", className),
34
39
  align: "start",
35
- children: fields.size === 0 ? /* @__PURE__ */ jsx("div", { className: "px-2 py-3 text-sm text-muted-foreground text-center", children: "Loading..." }) : Array.from(fieldGroups.entries()).map(([groupLabel, groupFields]) => /* @__PURE__ */ jsxs(DropdownMenu.Group, { children: [
40
+ children: fields.size === 0 ? /* @__PURE__ */ 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__ */ jsxs(DropdownMenu.Group, { children: [
36
41
  groupLabel && /* @__PURE__ */ jsx(DropdownMenu.Label, { children: groupLabel }),
37
42
  Array.from(groupFields.values()).map((field) => {
38
43
  const hasFilter = value.some((f) => f.fieldId === field.id);
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import {
3
3
  messages
4
- } from "./chunk-URDCG5NI.mjs";
4
+ } from "./chunk-YTYOFT33.mjs";
5
5
  import {
6
6
  useFilterContext
7
7
  } from "./chunk-I3Z2T4N2.mjs";
@@ -1,13 +1,13 @@
1
1
  "use client";
2
+ import {
3
+ DEFAULT_OPERATORS
4
+ } from "./chunk-PHESMHTT.mjs";
2
5
  import {
3
6
  messages
4
- } from "./chunk-URDCG5NI.mjs";
7
+ } from "./chunk-YTYOFT33.mjs";
5
8
  import {
6
9
  useFilterContext
7
10
  } from "./chunk-I3Z2T4N2.mjs";
8
- import {
9
- DEFAULT_OPERATORS
10
- } from "./chunk-PHESMHTT.mjs";
11
11
 
12
12
  // src/filter-value-editor.tsx
13
13
  import { Checkbox } from "@kopexa/checkbox";
@@ -151,27 +151,29 @@ function ValueInput({
151
151
  placeholder: (_c = field.placeholder) != null ? _c : t.formatMessage(messages.select_value)
152
152
  }
153
153
  ) }),
154
- /* @__PURE__ */ jsx(Select.Content, { children: (_d = field.options) == null ? void 0 : _d.map((option) => /* @__PURE__ */ jsxs(
154
+ /* @__PURE__ */ jsx(Select.Content, { children: (_d = field.options) == null ? void 0 : _d.map((option) => /* @__PURE__ */ jsx(
155
155
  Select.Item,
156
156
  {
157
157
  value: option.value,
158
158
  disabled: option.disabled,
159
- children: [
160
- option.icon && /* @__PURE__ */ jsx("span", { className: "mr-2 size-4", children: option.icon }),
159
+ children: /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-2", children: [
160
+ option.icon && /* @__PURE__ */ jsx("span", { className: "size-4 flex-shrink-0", children: option.icon }),
161
161
  option.label
162
- ]
162
+ ] })
163
163
  },
164
164
  option.value
165
165
  )) })
166
166
  ] });
167
167
  case "multiselect": {
168
- const selectedValues = Array.isArray(value) ? value : [];
169
- return /* @__PURE__ */ jsx("div", { className: "space-y-2 max-h-[200px] overflow-auto", children: (_e = field.options) == null ? void 0 : _e.map((option) => (
168
+ const selectedValues = Array.isArray(value) ? value.filter(
169
+ (v) => typeof v === "string" && v.length > 1
170
+ ) : [];
171
+ return /* @__PURE__ */ jsx("div", { className: "space-y-1 max-h-[200px] overflow-auto p-1", children: (_e = field.options) == null ? void 0 : _e.map((option) => (
170
172
  // biome-ignore lint/a11y/noLabelWithoutControl: Checkbox is a custom form control inside the label
171
173
  /* @__PURE__ */ jsxs(
172
174
  "label",
173
175
  {
174
- className: "flex items-center gap-2 cursor-pointer",
176
+ className: "flex items-center gap-2 cursor-pointer hover:bg-muted/50 rounded-md px-2 py-1.5",
175
177
  children: [
176
178
  /* @__PURE__ */ jsx(
177
179
  Checkbox,
@@ -184,11 +186,12 @@ function ValueInput({
184
186
  onChange(selectedValues.filter((v) => v !== option.value));
185
187
  }
186
188
  },
187
- disabled: option.disabled
189
+ disabled: option.disabled,
190
+ className: "flex-shrink-0 border-border bg-background data-[state=checked]:bg-primary data-[state=checked]:border-primary [&_svg]:text-primary-foreground"
188
191
  }
189
192
  ),
190
- option.icon && /* @__PURE__ */ jsx("span", { className: "size-4", children: option.icon }),
191
- /* @__PURE__ */ jsx("span", { className: "text-sm", children: option.label })
193
+ option.icon && /* @__PURE__ */ jsx("span", { className: "flex-shrink-0 [&>*]:size-5", children: option.icon }),
194
+ /* @__PURE__ */ jsx("span", { className: "text-sm truncate", children: option.label })
192
195
  ]
193
196
  },
194
197
  option.value