@kopexa/filter 0.0.25 → 0.0.26

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/dist/chunk-6TRAIAKS.mjs +178 -0
  2. package/dist/chunk-I7WCYMXD.mjs +133 -0
  3. package/dist/{chunk-45QJL74L.mjs → chunk-JSRGUDCG.mjs} +49 -24
  4. package/dist/chunk-NUDUXOHP.mjs +243 -0
  5. package/dist/chunk-ON2UFJ3Y.mjs +32 -0
  6. package/dist/{chunk-RFCPJLIQ.mjs → chunk-SJXRD3RO.mjs} +6 -1
  7. package/dist/{chunk-EF4VI36D.mjs → chunk-UBTUCPOG.mjs} +1 -1
  8. package/dist/{chunk-3ZBNWXRA.mjs → chunk-WD7YU6IN.mjs} +17 -14
  9. package/dist/chunk-XCWKWXBW.mjs +602 -0
  10. package/dist/{chunk-URDCG5NI.mjs → chunk-YTYOFT33.mjs} +52 -0
  11. package/dist/filter-active.js +108 -28
  12. package/dist/filter-active.mjs +4 -4
  13. package/dist/filter-bar-internal.d.mts +25 -0
  14. package/dist/filter-bar-internal.d.ts +25 -0
  15. package/dist/filter-bar-internal.js +648 -0
  16. package/dist/filter-bar-internal.mjs +11 -0
  17. package/dist/filter-bar-messages.d.mts +124 -0
  18. package/dist/filter-bar-messages.d.ts +124 -0
  19. package/dist/filter-bar-messages.js +156 -0
  20. package/dist/filter-bar-messages.mjs +7 -0
  21. package/dist/filter-bar-types.d.mts +112 -0
  22. package/dist/filter-bar-types.d.ts +112 -0
  23. package/dist/filter-bar-types.js +56 -0
  24. package/dist/filter-bar-types.mjs +8 -0
  25. package/dist/filter-bar.d.mts +29 -0
  26. package/dist/filter-bar.d.ts +29 -0
  27. package/dist/filter-bar.js +942 -0
  28. package/dist/filter-bar.mjs +11 -0
  29. package/dist/filter-menu.js +161 -1
  30. package/dist/filter-menu.mjs +2 -1
  31. package/dist/filter-trigger.js +52 -0
  32. package/dist/filter-trigger.mjs +2 -2
  33. package/dist/filter-value-editor.js +65 -10
  34. package/dist/filter-value-editor.mjs +3 -3
  35. package/dist/filter.d.mts +6 -0
  36. package/dist/filter.d.ts +6 -0
  37. package/dist/filter.mjs +2 -2
  38. package/dist/index.d.mts +4 -0
  39. package/dist/index.d.ts +4 -0
  40. package/dist/index.js +1263 -31
  41. package/dist/index.mjs +28 -14
  42. package/dist/messages.d.mts +50 -0
  43. package/dist/messages.d.ts +50 -0
  44. package/dist/messages.js +52 -0
  45. package/dist/messages.mjs +1 -1
  46. package/dist/search-filter-bar.d.mts +44 -0
  47. package/dist/search-filter-bar.d.ts +44 -0
  48. package/dist/search-filter-bar.js +1007 -0
  49. package/dist/search-filter-bar.mjs +11 -0
  50. package/package.json +19 -16
  51. package/dist/{chunk-SH7DBK54.mjs → chunk-LWDVRMCI.mjs} +3 -3
@@ -0,0 +1,178 @@
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/filter-bar.tsx
11
+ import { useSafeIntl } from "@kopexa/i18n";
12
+ import { cn } from "@kopexa/shared-utils";
13
+ import { useControllableState } from "@kopexa/use-controllable-state";
14
+ import { useCallback, useId, useMemo, useState } from "react";
15
+ import { jsx, jsxs } from "react/jsx-runtime";
16
+ function FilterBar(props) {
17
+ const {
18
+ fields,
19
+ value: valueProp,
20
+ defaultValue = [],
21
+ onChange,
22
+ i18n: i18nProp,
23
+ className,
24
+ allowMultiple = true,
25
+ disabled = false
26
+ } = props;
27
+ const t = useSafeIntl();
28
+ const instanceId = useId();
29
+ const [value, setValue] = useControllableState({
30
+ value: valueProp,
31
+ defaultValue,
32
+ onChange
33
+ });
34
+ const [sessionFilterId, setSessionFilterId] = useState(null);
35
+ const i18n = useMemo(() => {
36
+ const defaultI18n = {
37
+ addFilter: t.formatMessage(filterBarMessages.add_filter),
38
+ clearAll: t.formatMessage(filterBarMessages.clear_all),
39
+ noResults: t.formatMessage(filterBarMessages.no_results),
40
+ searchPlaceholder: t.formatMessage(filterBarMessages.search_placeholder),
41
+ selectValue: t.formatMessage(filterBarMessages.select_value),
42
+ enterValue: t.formatMessage(filterBarMessages.enter_value),
43
+ operators: {
44
+ equals: t.formatMessage(filterBarMessages.op_equals),
45
+ not_equals: t.formatMessage(filterBarMessages.op_not_equals),
46
+ contains: t.formatMessage(filterBarMessages.op_contains),
47
+ not_contains: t.formatMessage(filterBarMessages.op_not_contains),
48
+ starts_with: t.formatMessage(filterBarMessages.op_starts_with),
49
+ ends_with: t.formatMessage(filterBarMessages.op_ends_with),
50
+ gt: t.formatMessage(filterBarMessages.op_gt),
51
+ lt: t.formatMessage(filterBarMessages.op_lt),
52
+ gte: t.formatMessage(filterBarMessages.op_gte),
53
+ lte: t.formatMessage(filterBarMessages.op_lte),
54
+ is_empty: t.formatMessage(filterBarMessages.op_is_empty),
55
+ is_not_empty: t.formatMessage(filterBarMessages.op_is_not_empty)
56
+ }
57
+ };
58
+ return {
59
+ ...defaultI18n,
60
+ ...i18nProp,
61
+ operators: {
62
+ ...defaultI18n.operators,
63
+ ...i18nProp == null ? void 0 : i18nProp.operators
64
+ }
65
+ };
66
+ }, [t, i18nProp]);
67
+ const fieldGroups = useMemo(() => {
68
+ var _a;
69
+ const groups = /* @__PURE__ */ new Map();
70
+ for (const field of fields) {
71
+ const group = field.group;
72
+ if (!groups.has(group)) {
73
+ groups.set(group, []);
74
+ }
75
+ (_a = groups.get(group)) == null ? void 0 : _a.push(field);
76
+ }
77
+ return groups;
78
+ }, [fields]);
79
+ const addFilter = useCallback(
80
+ (fieldId) => {
81
+ var _a;
82
+ const field = fields.find((f) => f.id === fieldId);
83
+ if (!field) return;
84
+ if (!allowMultiple && value.some((f) => f.fieldId === fieldId)) {
85
+ return;
86
+ }
87
+ const newFilter = {
88
+ id: `${instanceId}-${fieldId}-${Date.now()}`,
89
+ fieldId,
90
+ operator: (_a = field.defaultOperator) != null ? _a : "equals",
91
+ value: field.type === "multiselect" ? [] : null
92
+ };
93
+ setValue([...value, newFilter]);
94
+ setSessionFilterId(newFilter.id);
95
+ },
96
+ [fields, value, setValue, allowMultiple, instanceId]
97
+ );
98
+ const updateFilter = useCallback(
99
+ (filterId, updates) => {
100
+ setValue(
101
+ value.map((f) => f.id === filterId ? { ...f, ...updates } : f)
102
+ );
103
+ },
104
+ [value, setValue]
105
+ );
106
+ const removeFilter = useCallback(
107
+ (filterId) => {
108
+ setValue(value.filter((f) => f.id !== filterId));
109
+ if (sessionFilterId === filterId) {
110
+ setSessionFilterId(null);
111
+ }
112
+ },
113
+ [value, setValue, sessionFilterId]
114
+ );
115
+ const clearFilters = useCallback(() => {
116
+ setValue([]);
117
+ setSessionFilterId(null);
118
+ }, [setValue]);
119
+ const isFieldAvailable = useCallback(
120
+ (fieldId) => {
121
+ if (allowMultiple) return true;
122
+ return !value.some((f) => f.fieldId === fieldId);
123
+ },
124
+ [value, allowMultiple]
125
+ );
126
+ return /* @__PURE__ */ jsxs(
127
+ "div",
128
+ {
129
+ "data-slot": "filter-bar",
130
+ className: cn("flex flex-wrap items-center gap-2", className),
131
+ children: [
132
+ value.map((filter) => {
133
+ const field = fields.find((f) => f.id === filter.fieldId);
134
+ if (!field) return null;
135
+ return /* @__PURE__ */ jsx(
136
+ FilterPill,
137
+ {
138
+ filter,
139
+ field,
140
+ i18n,
141
+ onUpdate: (updates) => updateFilter(filter.id, updates),
142
+ onRemove: () => removeFilter(filter.id),
143
+ autoOpen: sessionFilterId === filter.id,
144
+ onAutoOpenComplete: () => setSessionFilterId(null),
145
+ disabled
146
+ },
147
+ filter.id
148
+ );
149
+ }),
150
+ /* @__PURE__ */ jsx(
151
+ AddFilterDropdown,
152
+ {
153
+ fieldGroups,
154
+ isFieldAvailable,
155
+ onAddFilter: addFilter,
156
+ i18n,
157
+ disabled
158
+ }
159
+ ),
160
+ value.length > 1 && /* @__PURE__ */ jsx(
161
+ "button",
162
+ {
163
+ type: "button",
164
+ onClick: clearFilters,
165
+ className: "text-xs text-muted-foreground hover:text-foreground cursor-pointer transition-colors underline-offset-2 hover:underline",
166
+ disabled,
167
+ children: i18n.clearAll
168
+ }
169
+ )
170
+ ]
171
+ }
172
+ );
173
+ }
174
+ FilterBar.displayName = "FilterBar";
175
+
176
+ export {
177
+ FilterBar
178
+ };
@@ -0,0 +1,133 @@
1
+ "use client";
2
+
3
+ // src/filter-bar-messages.ts
4
+ import { defineMessages } from "@kopexa/i18n";
5
+ var filterBarMessages = defineMessages({
6
+ add_filter: {
7
+ id: "filter-bar.add_filter",
8
+ defaultMessage: "Add Filter",
9
+ description: "Button text for adding a new filter"
10
+ },
11
+ clear_all: {
12
+ id: "filter-bar.clear_all",
13
+ defaultMessage: "Clear all",
14
+ description: "Button text for clearing all filters"
15
+ },
16
+ no_results: {
17
+ id: "filter-bar.no_results",
18
+ defaultMessage: "No results found",
19
+ description: "Message when search returns no results"
20
+ },
21
+ search_placeholder: {
22
+ id: "filter-bar.search_placeholder",
23
+ defaultMessage: "Search...",
24
+ description: "Placeholder for search input"
25
+ },
26
+ select_value: {
27
+ id: "filter-bar.select_value",
28
+ defaultMessage: "Select value...",
29
+ description: "Placeholder for value selection"
30
+ },
31
+ enter_value: {
32
+ id: "filter-bar.enter_value",
33
+ defaultMessage: "Enter value...",
34
+ description: "Placeholder for value input"
35
+ },
36
+ apply: {
37
+ id: "filter-bar.apply",
38
+ defaultMessage: "Apply",
39
+ description: "Button text for applying a filter value"
40
+ },
41
+ cancel: {
42
+ id: "filter-bar.cancel",
43
+ defaultMessage: "Cancel",
44
+ description: "Button text for canceling"
45
+ },
46
+ // Operators
47
+ op_equals: {
48
+ id: "filter-bar.operator.equals",
49
+ defaultMessage: "is",
50
+ description: "Operator: equals"
51
+ },
52
+ op_not_equals: {
53
+ id: "filter-bar.operator.not_equals",
54
+ defaultMessage: "is not",
55
+ description: "Operator: not equals"
56
+ },
57
+ op_contains: {
58
+ id: "filter-bar.operator.contains",
59
+ defaultMessage: "contains",
60
+ description: "Operator: contains"
61
+ },
62
+ op_not_contains: {
63
+ id: "filter-bar.operator.not_contains",
64
+ defaultMessage: "does not contain",
65
+ description: "Operator: does not contain"
66
+ },
67
+ op_starts_with: {
68
+ id: "filter-bar.operator.starts_with",
69
+ defaultMessage: "starts with",
70
+ description: "Operator: starts with"
71
+ },
72
+ op_ends_with: {
73
+ id: "filter-bar.operator.ends_with",
74
+ defaultMessage: "ends with",
75
+ description: "Operator: ends with"
76
+ },
77
+ op_gt: {
78
+ id: "filter-bar.operator.gt",
79
+ defaultMessage: "greater than",
80
+ description: "Operator: greater than"
81
+ },
82
+ op_lt: {
83
+ id: "filter-bar.operator.lt",
84
+ defaultMessage: "less than",
85
+ description: "Operator: less than"
86
+ },
87
+ op_gte: {
88
+ id: "filter-bar.operator.gte",
89
+ defaultMessage: "greater or equal",
90
+ description: "Operator: greater than or equal"
91
+ },
92
+ op_lte: {
93
+ id: "filter-bar.operator.lte",
94
+ defaultMessage: "less or equal",
95
+ description: "Operator: less than or equal"
96
+ },
97
+ op_is_empty: {
98
+ id: "filter-bar.operator.is_empty",
99
+ defaultMessage: "is empty",
100
+ description: "Operator: is empty"
101
+ },
102
+ op_is_not_empty: {
103
+ id: "filter-bar.operator.is_not_empty",
104
+ defaultMessage: "is not empty",
105
+ description: "Operator: is not empty"
106
+ },
107
+ // Accessibility
108
+ remove_filter: {
109
+ id: "filter-bar.remove_filter",
110
+ defaultMessage: "Remove {field} filter",
111
+ description: "Accessibility label for remove filter button"
112
+ },
113
+ operator_label: {
114
+ id: "filter-bar.operator_label",
115
+ defaultMessage: "{field} operator",
116
+ description: "Accessibility label for operator dropdown"
117
+ },
118
+ value_label: {
119
+ id: "filter-bar.value_label",
120
+ defaultMessage: "{field} value",
121
+ description: "Accessibility label for value editor"
122
+ },
123
+ // Multiselect
124
+ selected_count: {
125
+ id: "filter-bar.selected_count",
126
+ defaultMessage: "{count} selected",
127
+ description: "Display text for multiselect with multiple selections"
128
+ }
129
+ });
130
+
131
+ export {
132
+ filterBarMessages
133
+ };
@@ -1,16 +1,16 @@
1
1
  "use client";
2
2
  import {
3
3
  FilterValueEditor
4
- } from "./chunk-3ZBNWXRA.mjs";
4
+ } from "./chunk-WD7YU6IN.mjs";
5
+ import {
6
+ DEFAULT_OPERATORS
7
+ } from "./chunk-PHESMHTT.mjs";
5
8
  import {
6
9
  messages
7
- } from "./chunk-URDCG5NI.mjs";
10
+ } from "./chunk-YTYOFT33.mjs";
8
11
  import {
9
12
  useFilterContext
10
13
  } from "./chunk-I3Z2T4N2.mjs";
11
- import {
12
- DEFAULT_OPERATORS
13
- } from "./chunk-PHESMHTT.mjs";
14
14
 
15
15
  // src/filter-active.tsx
16
16
  import { useSafeIntl } from "@kopexa/i18n";
@@ -18,7 +18,7 @@ import { ChevronDownIcon, CloseIcon } from "@kopexa/icons";
18
18
  import { Popover } from "@kopexa/popover";
19
19
  import { cn } from "@kopexa/shared-utils";
20
20
  import { Switch } from "@kopexa/switch";
21
- import { jsx, jsxs } from "react/jsx-runtime";
21
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
22
22
  function FilterActive(props) {
23
23
  const { showClearAll = true, className, ...rest } = props;
24
24
  const { value, styles, clearFilters } = useFilterContext();
@@ -55,6 +55,7 @@ function FilterField({ filter }) {
55
55
  const field = fields.get(filter.fieldId);
56
56
  if (!field) return null;
57
57
  const availableOperators = (_a = field.operators) != null ? _a : DEFAULT_OPERATORS[field.type];
58
+ const hasSingleOperator = availableOperators.length === 1;
58
59
  const getOperatorLabel = (op) => {
59
60
  const opKey = `op_${op}`;
60
61
  return messages[opKey] ? t.formatMessage(messages[opKey]) : op;
@@ -79,20 +80,29 @@ function FilterField({ filter }) {
79
80
  field.icon && /* @__PURE__ */ jsx("span", { className: "size-4 flex-shrink-0", children: field.icon }),
80
81
  /* @__PURE__ */ jsx("span", { children: field.label })
81
82
  ] }),
82
- /* @__PURE__ */ jsxs("span", { className: styles.fieldOperator(), children: [
83
+ /* @__PURE__ */ jsx("span", { className: styles.fieldOperator(), children: hasSingleOperator ? /* @__PURE__ */ jsx(
84
+ "span",
85
+ {
86
+ className: styles.fieldOperatorSelect(),
87
+ style: { width: operatorWidth },
88
+ children: currentOperatorLabel
89
+ }
90
+ ) : /* @__PURE__ */ jsxs(Fragment, { children: [
83
91
  /* @__PURE__ */ jsx(
84
92
  "select",
85
93
  {
86
94
  className: styles.fieldOperatorSelect(),
87
95
  value: filter.operator,
88
96
  onChange: handleOperatorChange,
89
- title: currentOperatorLabel,
97
+ "aria-label": t.formatMessage(messages.operator_label, {
98
+ field: field.label
99
+ }),
90
100
  style: { width: operatorWidth },
91
101
  children: availableOperators.map((op) => /* @__PURE__ */ jsx("option", { value: op, children: getOperatorLabel(op) }, op))
92
102
  }
93
103
  ),
94
104
  /* @__PURE__ */ jsx(ChevronDownIcon, { className: styles.fieldOperatorIcon() })
95
- ] }),
105
+ ] }) }),
96
106
  useInlineInput && /* @__PURE__ */ jsx(InlineValueInput, { filter, field }),
97
107
  useInlineSwitch && /* @__PURE__ */ jsx("span", { className: styles.fieldValue(), children: /* @__PURE__ */ jsx(
98
108
  Switch,
@@ -112,7 +122,9 @@ function FilterField({ filter }) {
112
122
  type: "button",
113
123
  className: styles.fieldRemove(),
114
124
  onClick: () => removeFilter(filter.id),
115
- "aria-label": "Remove filter",
125
+ "aria-label": t.formatMessage(messages.remove_filter, {
126
+ field: field.label
127
+ }),
116
128
  children: /* @__PURE__ */ jsx(CloseIcon, { className: "size-3" })
117
129
  }
118
130
  )
@@ -141,6 +153,8 @@ function InlineValueInput({
141
153
  const placeholder = (_a = field.placeholder) != null ? _a : t.formatMessage(messages.enter_value);
142
154
  if (filter.operator === "between") {
143
155
  const [min, max] = Array.isArray(filter.value) ? filter.value : [void 0, void 0];
156
+ const minPlaceholder = t.formatMessage(messages.range_min);
157
+ const maxPlaceholder = t.formatMessage(messages.range_max);
144
158
  return /* @__PURE__ */ jsxs("span", { className: cn(styles.fieldValue(), "gap-1"), children: [
145
159
  /* @__PURE__ */ jsx(
146
160
  "input",
@@ -152,8 +166,11 @@ function InlineValueInput({
152
166
  const val = e.target.value ? Number(e.target.value) : void 0;
153
167
  updateFilter(filter.id, { value: [val, max] });
154
168
  },
155
- placeholder: "Min",
156
- style: { width: getInputWidth(min, "Min", 3) },
169
+ placeholder: minPlaceholder,
170
+ "aria-label": t.formatMessage(messages.value_min_label, {
171
+ field: field.label
172
+ }),
173
+ style: { width: getInputWidth(min, minPlaceholder, 3) },
157
174
  min: field.min,
158
175
  max: field.max,
159
176
  step: field.step
@@ -170,8 +187,11 @@ function InlineValueInput({
170
187
  const val = e.target.value ? Number(e.target.value) : void 0;
171
188
  updateFilter(filter.id, { value: [min, val] });
172
189
  },
173
- placeholder: "Max",
174
- style: { width: getInputWidth(max, "Max", 3) },
190
+ placeholder: maxPlaceholder,
191
+ "aria-label": t.formatMessage(messages.value_max_label, {
192
+ field: field.label
193
+ }),
194
+ style: { width: getInputWidth(max, maxPlaceholder, 3) },
175
195
  min: field.min,
176
196
  max: field.max,
177
197
  step: field.step
@@ -187,6 +207,9 @@ function InlineValueInput({
187
207
  value: filter.value !== void 0 && filter.value !== null ? String(filter.value) : "",
188
208
  onChange: handleChange,
189
209
  placeholder,
210
+ "aria-label": t.formatMessage(messages.value_label, {
211
+ field: field.label
212
+ }),
190
213
  style: { width: getInputWidth(filter.value, placeholder, 6) },
191
214
  min: field.min,
192
215
  max: field.max,
@@ -199,7 +222,8 @@ function PopoverValueEditor({
199
222
  field
200
223
  }) {
201
224
  const { styles, updateFilter } = useFilterContext();
202
- const valueDisplay = field.renderValue ? field.renderValue({ filter, field }) : getValueDisplay(filter, field);
225
+ const t = useSafeIntl();
226
+ const valueDisplay = field.renderValue ? field.renderValue({ filter, field }) : getValueDisplay(filter, field, t);
203
227
  const hasValue = filter.value !== void 0 && filter.value !== null && filter.value !== "" && !(Array.isArray(filter.value) && filter.value.length === 0);
204
228
  const handleCustomChange = (value) => {
205
229
  updateFilter(filter.id, { value });
@@ -212,7 +236,7 @@ function PopoverValueEditor({
212
236
  render: /* @__PURE__ */ jsx("div", {}),
213
237
  nativeButton: false,
214
238
  children: [
215
- /* @__PURE__ */ jsx("span", { className: styles.fieldValueText(), children: hasValue ? valueDisplay : "Select..." }),
239
+ /* @__PURE__ */ jsx("span", { className: styles.fieldValueText(), children: hasValue ? valueDisplay : t.formatMessage(messages.select_value) }),
216
240
  /* @__PURE__ */ jsx(ChevronDownIcon, { className: styles.fieldValueIcon() })
217
241
  ]
218
242
  }
@@ -239,7 +263,7 @@ function PopoverValueEditor({
239
263
  )
240
264
  ] });
241
265
  }
242
- function getValueDisplay(filter, field) {
266
+ function getValueDisplay(filter, field, t) {
243
267
  var _a, _b, _c, _d;
244
268
  const { value, operator } = filter;
245
269
  if (operator === "is_empty" || operator === "is_not_empty") {
@@ -249,19 +273,20 @@ function getValueDisplay(filter, field) {
249
273
  return null;
250
274
  }
251
275
  if (field.type === "boolean") {
252
- return value ? "Yes" : "No";
276
+ return value ? t.formatMessage(messages.boolean_true) : t.formatMessage(messages.boolean_false);
253
277
  }
254
278
  if (field.type === "select" && field.options) {
255
279
  const option = field.options.find((o) => o.value === value);
256
280
  return (_a = option == null ? void 0 : option.label) != null ? _a : String(value);
257
281
  }
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]);
282
+ if (field.type === "multiselect") {
283
+ const values = Array.isArray(value) ? value.filter((v) => typeof v === "string" && v.length > 1) : [];
284
+ if (values.length === 0) return null;
285
+ if (values.length === 1 && field.options) {
286
+ const option = field.options.find((o) => o.value === values[0]);
287
+ return (_b = option == null ? void 0 : option.label) != null ? _b : String(values[0]);
263
288
  }
264
- return `${value.length} selected`;
289
+ return `${values.length} selected`;
265
290
  }
266
291
  if (field.type === "date" && value instanceof Date) {
267
292
  return value.toLocaleDateString();