@akinon/akifilter 1.8.0 → 1.9.0

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 (64) hide show
  1. package/README.md +306 -0
  2. package/dist/cjs/akifilter.d.ts +19 -0
  3. package/dist/cjs/akifilter.d.ts.map +1 -1
  4. package/dist/cjs/akifilter.js +74 -17
  5. package/dist/cjs/common/storage.d.ts.map +1 -1
  6. package/dist/cjs/common/storage.js +47 -2
  7. package/dist/cjs/components/file-import-button.d.ts +33 -0
  8. package/dist/cjs/components/file-import-button.d.ts.map +1 -0
  9. package/dist/cjs/components/file-import-button.js +37 -0
  10. package/dist/cjs/components/filter-toolbar.d.ts.map +1 -1
  11. package/dist/cjs/components/filter-toolbar.js +4 -18
  12. package/dist/cjs/components/section-fieldset.d.ts +16 -2
  13. package/dist/cjs/components/section-fieldset.d.ts.map +1 -1
  14. package/dist/cjs/components/section-fieldset.js +104 -32
  15. package/dist/cjs/components/visibility-modal.js +1 -1
  16. package/dist/cjs/constants.d.ts +6 -0
  17. package/dist/cjs/constants.d.ts.map +1 -1
  18. package/dist/cjs/constants.js +7 -1
  19. package/dist/cjs/hooks/use-field-visibility.d.ts.map +1 -1
  20. package/dist/cjs/hooks/use-field-visibility.js +10 -1
  21. package/dist/cjs/i18n/translations/en.d.ts +8 -0
  22. package/dist/cjs/i18n/translations/en.d.ts.map +1 -1
  23. package/dist/cjs/i18n/translations/en.js +8 -0
  24. package/dist/cjs/i18n/translations/tr.d.ts +8 -0
  25. package/dist/cjs/i18n/translations/tr.d.ts.map +1 -1
  26. package/dist/cjs/i18n/translations/tr.js +8 -0
  27. package/dist/cjs/styles.css +36 -11
  28. package/dist/cjs/types.d.ts +146 -3
  29. package/dist/cjs/types.d.ts.map +1 -1
  30. package/dist/cjs/utils/schema.d.ts +10 -0
  31. package/dist/cjs/utils/schema.d.ts.map +1 -1
  32. package/dist/cjs/utils/schema.js +13 -1
  33. package/dist/esm/akifilter.d.ts +19 -0
  34. package/dist/esm/akifilter.d.ts.map +1 -1
  35. package/dist/esm/akifilter.js +75 -18
  36. package/dist/esm/common/storage.d.ts.map +1 -1
  37. package/dist/esm/common/storage.js +47 -2
  38. package/dist/esm/components/file-import-button.d.ts +33 -0
  39. package/dist/esm/components/file-import-button.d.ts.map +1 -0
  40. package/dist/esm/components/file-import-button.js +33 -0
  41. package/dist/esm/components/filter-toolbar.d.ts.map +1 -1
  42. package/dist/esm/components/filter-toolbar.js +4 -18
  43. package/dist/esm/components/section-fieldset.d.ts +16 -2
  44. package/dist/esm/components/section-fieldset.d.ts.map +1 -1
  45. package/dist/esm/components/section-fieldset.js +104 -32
  46. package/dist/esm/components/visibility-modal.js +2 -2
  47. package/dist/esm/constants.d.ts +6 -0
  48. package/dist/esm/constants.d.ts.map +1 -1
  49. package/dist/esm/constants.js +6 -0
  50. package/dist/esm/hooks/use-field-visibility.d.ts.map +1 -1
  51. package/dist/esm/hooks/use-field-visibility.js +10 -1
  52. package/dist/esm/i18n/translations/en.d.ts +8 -0
  53. package/dist/esm/i18n/translations/en.d.ts.map +1 -1
  54. package/dist/esm/i18n/translations/en.js +8 -0
  55. package/dist/esm/i18n/translations/tr.d.ts +8 -0
  56. package/dist/esm/i18n/translations/tr.d.ts.map +1 -1
  57. package/dist/esm/i18n/translations/tr.js +8 -0
  58. package/dist/esm/styles.css +36 -11
  59. package/dist/esm/types.d.ts +146 -3
  60. package/dist/esm/types.d.ts.map +1 -1
  61. package/dist/esm/utils/schema.d.ts +10 -0
  62. package/dist/esm/utils/schema.d.ts.map +1 -1
  63. package/dist/esm/utils/schema.js +10 -0
  64. package/package.json +7 -7
@@ -13,8 +13,40 @@ export interface AkifilterFieldConfig<TFieldValues extends FieldValues = FieldVa
13
13
  */
14
14
  visible?: boolean | ((formValues: TFieldValues, currentVisible?: boolean) => boolean);
15
15
  }
16
- type DistributiveOmit<T, K extends PropertyKey> = T extends unknown ? Omit<T, K> : never;
17
- export type AkifilterField<TFieldValues extends FieldValues = FieldValues> = DistributiveOmit<FormField<TFieldValues>, 'config'> & {
16
+ /**
17
+ * One applied-filter chip produced by a field that owns several filters at
18
+ * once (see `appliedChips`).
19
+ */
20
+ export type AkifilterFieldChip = {
21
+ /**
22
+ * Identifies the chip inside its field. Passed back to
23
+ * `onRemoveAppliedChip` when the chip is removed, so it has to be stable for
24
+ * the same logical entry (e.g. the filter type it represents) and unique
25
+ * within the field — two chips sharing an id cannot be told apart on
26
+ * removal.
27
+ */
28
+ id: string;
29
+ /**
30
+ * Text before the colon. Defaults to the field's applied label
31
+ * (`appliedLabel`, else `label`).
32
+ */
33
+ label?: string;
34
+ /**
35
+ * Text after the colon — already formatted, since only the field knows how
36
+ * to render its own value.
37
+ */
38
+ value: string;
39
+ /**
40
+ * Renders the chip with the exclude styling. Defaults to whether the field
41
+ * belongs to an exclude section.
42
+ */
43
+ isExclude?: boolean;
44
+ };
45
+ /**
46
+ * Props Akifilter adds on top of the shared `FormField`. Kept separate so the
47
+ * mapping below can apply them to nested fields as well as top-level ones.
48
+ */
49
+ type AkifilterFieldExtras<TFieldValues extends FieldValues = FieldValues> = {
18
50
  /**
19
51
  * Configuration options for field behavior (disabled, visible).
20
52
  */
@@ -24,14 +56,109 @@ export type AkifilterField<TFieldValues extends FieldValues = FieldValues> = Dis
24
56
  * Only applicable when `type` is `'file'`.
25
57
  */
26
58
  fileAccept?: string;
59
+ /**
60
+ * Expands this field's value into one chip per entry, instead of the single
61
+ * chip Akifilter derives from the value itself.
62
+ *
63
+ * This is what lets one field own several filters at once — a value that
64
+ * holds a bucket per filter type, a range that reads better as two chips,
65
+ * and so on. Because the buckets stay inside the field's form value,
66
+ * Akifilter persists and rehydrates them like any other value; nothing has
67
+ * to be mirrored outside the component.
68
+ *
69
+ * Pair it with `onRemoveAppliedChip` to make the chips removable. Return an
70
+ * empty array for "no chips right now".
71
+ *
72
+ * Only called while the field's own value is non-empty: the emptiness rules
73
+ * that suppress a plain chip (`null`/`undefined`, a blank string, an empty
74
+ * array) suppress these too. `formValues` is there to *format* the chips —
75
+ * a unit or a mode held by a sibling field — not to derive them from a
76
+ * sibling while this field holds nothing.
77
+ */
78
+ appliedChips?: (context: {
79
+ field: AkifilterField<TFieldValues>;
80
+ value: unknown;
81
+ formValues: Partial<TFieldValues>;
82
+ }) => AkifilterFieldChip[];
83
+ /**
84
+ * Maps the removal of one `appliedChips` chip back onto a new value for the
85
+ * field, which Akifilter then commits, persists and emits.
86
+ *
87
+ * Return the value the field should hold after the removal — `null` to clear
88
+ * the field entirely. Without this callback the chips render but cannot be
89
+ * removed.
90
+ */
91
+ onRemoveAppliedChip?: (context: {
92
+ field: AkifilterField<TFieldValues>;
93
+ value: unknown;
94
+ id: string;
95
+ }) => unknown;
96
+ /**
97
+ * Renders a CSV import button in the section header, paired with the
98
+ * `onSectionImportCsv` callback. Section fields only.
99
+ */
100
+ enableImportCsv?: boolean;
101
+ /**
102
+ * Renders an XLS/XLSX import button in the section header, paired with the
103
+ * `onSectionImportXls` callback. Section fields only.
104
+ */
105
+ enableImportXls?: boolean;
106
+ /**
107
+ * Overrides `label` for this field's applied-filter chip only.
108
+ *
109
+ * The form input and the "select visible filters" modal keep using `label`.
110
+ * Use this when the chip needs a shorter or more explicit name than the form
111
+ * does (e.g. `label: 'Created At'` next to a date range, `appliedLabel:
112
+ * 'Order date'` in the chip).
113
+ */
114
+ appliedLabel?: string;
115
+ /**
116
+ * Overrides `label` for this field's row in the "select visible filters"
117
+ * modal only. Also what the modal's search box matches against.
118
+ *
119
+ * The form input and the applied-filter chip keep using `label`. Use this
120
+ * when the modal lists fields out of context and needs a fully qualified
121
+ * name (e.g. `label: 'Status'`, `visibilityLabel: 'Order status'`).
122
+ */
123
+ visibilityLabel?: string;
27
124
  };
125
+ /**
126
+ * Applies `AkifilterFieldExtras` to one member of the `FormField` union.
127
+ *
128
+ * Container fields (`section`, `field-array`, `column`) get their `fields`
129
+ * re-typed recursively: without that, the shared `FormField[]` child type
130
+ * would reject every Akifilter-only prop — a field inside a section could not
131
+ * declare `config.visible`, `fileAccept`, or a label override at all.
132
+ */
133
+ type Akifiltered<TField, TFieldValues extends FieldValues> = TField extends {
134
+ fields: unknown[];
135
+ } ? Omit<TField, 'config' | 'fields'> & AkifilterFieldExtras<TFieldValues> & {
136
+ fields: AkifilterField<TFieldValues>[];
137
+ } : Omit<TField, 'config'> & AkifilterFieldExtras<TFieldValues>;
138
+ /**
139
+ * A single Akifilter schema entry. `TField` below is a naked type parameter,
140
+ * so the conditional distributes over every member of the `FormField` union
141
+ * and each variant keeps its own discriminating `type` literal.
142
+ */
143
+ export type AkifilterField<TFieldValues extends FieldValues = FieldValues> = Akifiltered<FormField<TFieldValues>, TFieldValues>;
28
144
  /**
29
145
  * Section field as consumed by Akifilter. Derived from the shared
30
146
  * `SectionField` so flags like `selectableFields`/`isExcludeSection` stay
31
147
  * single-sourced, but omits `collapsible` — Akifilter always renders sections
32
148
  * as collapsible panels and does not honor that prop.
33
149
  */
34
- export type AkifilterSectionField<TFieldValues extends FieldValues = FieldValues> = Omit<SectionField<TFieldValues>, 'collapsible'>;
150
+ export type AkifilterSectionField<TFieldValues extends FieldValues = FieldValues> = Omit<SectionField<TFieldValues>, 'collapsible' | 'fields'> & {
151
+ /**
152
+ * Child fields, typed as Akifilter fields rather than the shared
153
+ * `FormField` so section children accept the same Akifilter-only props as
154
+ * top-level ones (`config.visible`, `fileAccept`, the label overrides).
155
+ */
156
+ fields: AkifilterField<TFieldValues>[];
157
+ /** Renders a CSV import button in the section header. */
158
+ enableImportCsv?: boolean;
159
+ /** Renders an XLS/XLSX import button in the section header. */
160
+ enableImportXls?: boolean;
161
+ };
35
162
  export type AkifilterFieldKey<TFieldValues extends FieldValues = FieldValues> = Path<TFieldValues>;
36
163
  export type AkifilterSchema<TFieldValues extends FieldValues = FieldValues> = AkifilterField<TFieldValues>[];
37
164
  /**
@@ -88,5 +215,21 @@ export interface AkifilterActionsRef {
88
215
  */
89
216
  clearValue: (keys: string | string[]) => void;
90
217
  }
218
+ declare module '@akinon/akiform-builder' {
219
+ interface FieldExtensions {
220
+ /** @see AkifilterField.appliedLabel */
221
+ appliedLabel?: AkifilterField['appliedLabel'];
222
+ /** @see AkifilterField.visibilityLabel */
223
+ visibilityLabel?: AkifilterField['visibilityLabel'];
224
+ /** @see AkifilterField.appliedChips */
225
+ appliedChips?: AkifilterField['appliedChips'];
226
+ /** @see AkifilterField.onRemoveAppliedChip */
227
+ onRemoveAppliedChip?: AkifilterField['onRemoveAppliedChip'];
228
+ /** @see AkifilterField.enableImportCsv */
229
+ enableImportCsv?: boolean;
230
+ /** @see AkifilterField.enableImportXls */
231
+ enableImportXls?: boolean;
232
+ }
233
+ }
91
234
  export {};
92
235
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AACzD,OAAO,KAAK,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AACvE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAE9C,MAAM,WAAW,oBAAoB,CACnC,YAAY,SAAS,WAAW,GAAG,WAAW;IAE9C;;;OAGG;IACH,QAAQ,CAAC,EAAE,OAAO,GAAG,CAAC,CAAC,UAAU,EAAE,YAAY,KAAK,OAAO,CAAC,CAAC;IAC7D;;;OAGG;IACH,OAAO,CAAC,EACJ,OAAO,GACP,CAAC,CAAC,UAAU,EAAE,YAAY,EAAE,cAAc,CAAC,EAAE,OAAO,KAAK,OAAO,CAAC,CAAC;CACvE;AAED,KAAK,gBAAgB,CAAC,CAAC,EAAE,CAAC,SAAS,WAAW,IAAI,CAAC,SAAS,OAAO,GAC/D,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,GACV,KAAK,CAAC;AAEV,MAAM,MAAM,cAAc,CAAC,YAAY,SAAS,WAAW,GAAG,WAAW,IACvE,gBAAgB,CAAC,SAAS,CAAC,YAAY,CAAC,EAAE,QAAQ,CAAC,GAAG;IACpD;;OAEG;IACH,MAAM,CAAC,EAAE,oBAAoB,CAAC,YAAY,CAAC,CAAC;IAC5C;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AAEJ;;;;;GAKG;AACH,MAAM,MAAM,qBAAqB,CAC/B,YAAY,SAAS,WAAW,GAAG,WAAW,IAC5C,IAAI,CAAC,YAAY,CAAC,YAAY,CAAC,EAAE,aAAa,CAAC,CAAC;AAEpD,MAAM,MAAM,iBAAiB,CAAC,YAAY,SAAS,WAAW,GAAG,WAAW,IAC1E,IAAI,CAAC,YAAY,CAAC,CAAC;AAErB,MAAM,MAAM,eAAe,CAAC,YAAY,SAAS,WAAW,GAAG,WAAW,IACxE,cAAc,CAAC,YAAY,CAAC,EAAE,CAAC;AAEjC;;;;;;;GAOG;AACH,MAAM,WAAW,eAAe;IAC9B;;;;;;OAMG;IACH,GAAG,EAAE,MAAM,CAAC;IACZ;;;;;OAKG;IACH,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC;IACvB;;;OAGG;IACH,KAAK,EAAE,MAAM,CAAC;IACd;;;OAGG;IACH,aAAa,EAAE,MAAM,CAAC;IACtB;;;OAGG;IACH,IAAI,EAAE,QAAQ,CAAC;IACf;;OAEG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC;;;OAGG;IACH,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,KAAK,IAAI,CAAC;CAC/C"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"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"}
@@ -14,6 +14,16 @@ export declare const partitionSchema: <TFieldValues extends FieldValues = FieldV
14
14
  };
15
15
  export declare const deriveExcludeSectionKeys: <TFieldValues extends FieldValues = FieldValues>(sectionFields: Array<AkifilterSectionField<TFieldValues>>) => Set<string>;
16
16
  export declare const getDisplayLabel: <TFieldValues extends FieldValues = FieldValues>(field: AkifilterField<TFieldValues>) => string;
17
+ /**
18
+ * Label for the field's applied-filter chip. Falls back to the shared display
19
+ * label, so a schema that sets no override behaves exactly as before.
20
+ */
21
+ export declare const getAppliedLabel: <TFieldValues extends FieldValues = FieldValues>(field: AkifilterField<TFieldValues>) => string;
22
+ /**
23
+ * Label for the field's row in the visibility modal. Falls back to the shared
24
+ * display label, so a schema that sets no override behaves exactly as before.
25
+ */
26
+ export declare const getVisibilityLabel: <TFieldValues extends FieldValues = FieldValues>(field: AkifilterField<TFieldValues>) => string;
17
27
  export declare const getFieldAriaLabel: <TFieldValues extends FieldValues = FieldValues>(field: AkifilterField<TFieldValues>) => string;
18
28
  export declare const ensureSchemaOrder: <TFieldValues extends FieldValues = FieldValues>(schema: AkifilterSchema<TFieldValues>, keys: string[]) => string[];
19
29
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../../src/utils/schema.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAInD,OAAO,KAAK,EACV,cAAc,EACd,eAAe,EACf,qBAAqB,EACtB,MAAM,UAAU,CAAC;AAElB;;;GAGG;AACH,eAAO,MAAM,aAAa,GAAI,YAAY,SAAS,WAAW,GAAG,WAAW,EAC1E,QAAQ,eAAe,CAAC,YAAY,CAAC,KACpC,eAAe,CAAC,YAAY,CAW9B,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,eAAe,GAAI,YAAY,SAAS,WAAW,GAAG,WAAW,EAC5E,QAAQ,eAAe,CAAC,YAAY,CAAC,KACpC;IACD,aAAa,EAAE,eAAe,CAAC,YAAY,CAAC,CAAC;IAC7C,aAAa,EAAE,KAAK,CAAC,qBAAqB,CAAC,YAAY,CAAC,CAAC,CAAC;CAc3D,CAAC;AAEF,eAAO,MAAM,wBAAwB,GACnC,YAAY,SAAS,WAAW,GAAG,WAAW,EAE9C,eAAe,KAAK,CAAC,qBAAqB,CAAC,YAAY,CAAC,CAAC,KACxD,GAAG,CAAC,MAAM,CAKV,CAAC;AAEJ,eAAO,MAAM,eAAe,GAAI,YAAY,SAAS,WAAW,GAAG,WAAW,EAC5E,OAAO,cAAc,CAAC,YAAY,CAAC,KAClC,MAEF,CAAC;AAEF,eAAO,MAAM,iBAAiB,GAC5B,YAAY,SAAS,WAAW,GAAG,WAAW,EAE9C,OAAO,cAAc,CAAC,YAAY,CAAC,KAClC,MAEF,CAAC;AAEF,eAAO,MAAM,iBAAiB,GAC5B,YAAY,SAAS,WAAW,GAAG,WAAW,EAE9C,QAAQ,eAAe,CAAC,YAAY,CAAC,EACrC,MAAM,MAAM,EAAE,KACb,MAAM,EAGR,CAAC;AAkBF;;GAEG;AACH,eAAO,MAAM,sBAAsB,GACjC,YAAY,SAAS,WAAW,GAAG,WAAW,EAE9C,OAAO,cAAc,CAAC,YAAY,CAAC,EACnC,aAAa,YAAY,EACzB,iBAAiB,OAAO,KACvB,OAAO,GAAG,SAC8D,CAAC;AAE5E,eAAO,MAAM,wBAAwB,GACnC,YAAY,SAAS,WAAW,GAAG,WAAW,EAE9C,QAAQ,eAAe,CAAC,YAAY,CAAC,KACpC,MAAM,EAUR,CAAC;AAEF,eAAO,MAAM,oBAAoB,GAC/B,YAAY,SAAS,WAAW,GAAG,WAAW,EAE9C,QAAQ,eAAe,CAAC,YAAY,CAAC,KACpC,OAAO,CAAC,YAAY,CAQtB,CAAC;AAEF,eAAO,MAAM,oBAAoB,GAAI,YAAY,SAAS,WAAW,EACnE,OAAO,cAAc,CAAC,YAAY,CAAC,KAClC,OAA4C,CAAC;AAEhD,eAAO,MAAM,uBAAuB,GAClC,YAAY,SAAS,WAAW,GAAG,WAAW,EAE9C,QAAQ,eAAe,CAAC,YAAY,CAAC,EACrC,SAAS,OAAO,CAAC,YAAY,CAAC,KAC7B,OAAO,CAAC,YAAY,CA0CtB,CAAC"}
1
+ {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../../src/utils/schema.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAInD,OAAO,KAAK,EACV,cAAc,EACd,eAAe,EACf,qBAAqB,EACtB,MAAM,UAAU,CAAC;AAElB;;;GAGG;AACH,eAAO,MAAM,aAAa,GAAI,YAAY,SAAS,WAAW,GAAG,WAAW,EAC1E,QAAQ,eAAe,CAAC,YAAY,CAAC,KACpC,eAAe,CAAC,YAAY,CAW9B,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,eAAe,GAAI,YAAY,SAAS,WAAW,GAAG,WAAW,EAC5E,QAAQ,eAAe,CAAC,YAAY,CAAC,KACpC;IACD,aAAa,EAAE,eAAe,CAAC,YAAY,CAAC,CAAC;IAC7C,aAAa,EAAE,KAAK,CAAC,qBAAqB,CAAC,YAAY,CAAC,CAAC,CAAC;CAc3D,CAAC;AAEF,eAAO,MAAM,wBAAwB,GACnC,YAAY,SAAS,WAAW,GAAG,WAAW,EAE9C,eAAe,KAAK,CAAC,qBAAqB,CAAC,YAAY,CAAC,CAAC,KACxD,GAAG,CAAC,MAAM,CAKV,CAAC;AAEJ,eAAO,MAAM,eAAe,GAAI,YAAY,SAAS,WAAW,GAAG,WAAW,EAC5E,OAAO,cAAc,CAAC,YAAY,CAAC,KAClC,MAEF,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,eAAe,GAAI,YAAY,SAAS,WAAW,GAAG,WAAW,EAC5E,OAAO,cAAc,CAAC,YAAY,CAAC,KAClC,MAAsD,CAAC;AAE1D;;;GAGG;AACH,eAAO,MAAM,kBAAkB,GAC7B,YAAY,SAAS,WAAW,GAAG,WAAW,EAE9C,OAAO,cAAc,CAAC,YAAY,CAAC,KAClC,MAAyD,CAAC;AAE7D,eAAO,MAAM,iBAAiB,GAC5B,YAAY,SAAS,WAAW,GAAG,WAAW,EAE9C,OAAO,cAAc,CAAC,YAAY,CAAC,KAClC,MAEF,CAAC;AAEF,eAAO,MAAM,iBAAiB,GAC5B,YAAY,SAAS,WAAW,GAAG,WAAW,EAE9C,QAAQ,eAAe,CAAC,YAAY,CAAC,EACrC,MAAM,MAAM,EAAE,KACb,MAAM,EAGR,CAAC;AAkBF;;GAEG;AACH,eAAO,MAAM,sBAAsB,GACjC,YAAY,SAAS,WAAW,GAAG,WAAW,EAE9C,OAAO,cAAc,CAAC,YAAY,CAAC,EACnC,aAAa,YAAY,EACzB,iBAAiB,OAAO,KACvB,OAAO,GAAG,SAC8D,CAAC;AAE5E,eAAO,MAAM,wBAAwB,GACnC,YAAY,SAAS,WAAW,GAAG,WAAW,EAE9C,QAAQ,eAAe,CAAC,YAAY,CAAC,KACpC,MAAM,EAUR,CAAC;AAEF,eAAO,MAAM,oBAAoB,GAC/B,YAAY,SAAS,WAAW,GAAG,WAAW,EAE9C,QAAQ,eAAe,CAAC,YAAY,CAAC,KACpC,OAAO,CAAC,YAAY,CAQtB,CAAC;AAEF,eAAO,MAAM,oBAAoB,GAAI,YAAY,SAAS,WAAW,EACnE,OAAO,cAAc,CAAC,YAAY,CAAC,KAClC,OAA4C,CAAC;AAEhD,eAAO,MAAM,uBAAuB,GAClC,YAAY,SAAS,WAAW,GAAG,WAAW,EAE9C,QAAQ,eAAe,CAAC,YAAY,CAAC,EACrC,SAAS,OAAO,CAAC,YAAY,CAAC,KAC7B,OAAO,CAAC,YAAY,CA0CtB,CAAC"}
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.normaliseValuesBySchema = exports.hasDynamicVisibility = exports.extractDefaultValues = exports.deriveDefaultVisibleKeys = exports.resolveFieldVisibility = exports.ensureSchemaOrder = exports.getFieldAriaLabel = exports.getDisplayLabel = exports.deriveExcludeSectionKeys = exports.partitionSchema = exports.flattenSchema = void 0;
3
+ exports.normaliseValuesBySchema = exports.hasDynamicVisibility = exports.extractDefaultValues = exports.deriveDefaultVisibleKeys = exports.resolveFieldVisibility = exports.ensureSchemaOrder = exports.getFieldAriaLabel = exports.getVisibilityLabel = exports.getAppliedLabel = exports.getDisplayLabel = exports.deriveExcludeSectionKeys = exports.partitionSchema = exports.flattenSchema = void 0;
4
4
  const akidate_1 = require("@akinon/akidate");
5
5
  const lodash_es_1 = require("lodash-es");
6
6
  const constants_1 = require("../constants");
@@ -46,6 +46,18 @@ const getDisplayLabel = (field) => {
46
46
  return field.label || field.placeholder || String(field.key);
47
47
  };
48
48
  exports.getDisplayLabel = getDisplayLabel;
49
+ /**
50
+ * Label for the field's applied-filter chip. Falls back to the shared display
51
+ * label, so a schema that sets no override behaves exactly as before.
52
+ */
53
+ const getAppliedLabel = (field) => field.appliedLabel || (0, exports.getDisplayLabel)(field);
54
+ exports.getAppliedLabel = getAppliedLabel;
55
+ /**
56
+ * Label for the field's row in the visibility modal. Falls back to the shared
57
+ * display label, so a schema that sets no override behaves exactly as before.
58
+ */
59
+ const getVisibilityLabel = (field) => field.visibilityLabel || (0, exports.getDisplayLabel)(field);
60
+ exports.getVisibilityLabel = getVisibilityLabel;
49
61
  const getFieldAriaLabel = (field) => {
50
62
  return field.label || field.placeholder || String(field.key);
51
63
  };
@@ -71,6 +71,25 @@ export type AkifilterProps<TFieldValues extends AkifilterFieldValues = Akifilter
71
71
  * Notified whenever the set of active filter buttons changes.
72
72
  */
73
73
  onFilterButtonsChange?: (activeButtons: AkifilterButton[]) => void;
74
+ /**
75
+ * Triggered when a user picks a CSV file from a section header's import
76
+ * button. Receives the section's `key` and the selected file, so one handler
77
+ * can serve every section.
78
+ *
79
+ * The button only renders for sections that set `enableImportCsv: true` —
80
+ * mirroring how the top-level toolbar pairs `enableImportCsv` with
81
+ * `onImportCsv`. Akifilter never parses the file; the host application
82
+ * decides what to do with it (e.g. call an exclude-by-file endpoint and
83
+ * surface a chip through `externalAppliedFilters`).
84
+ */
85
+ onSectionImportCsv?: (sectionKey: string, file: File) => void;
86
+ /**
87
+ * Triggered when a user picks an XLS/XLSX file from a section header's import
88
+ * button. Receives the section's `key` and the selected file.
89
+ *
90
+ * The button only renders for sections that set `enableImportXls: true`.
91
+ */
92
+ onSectionImportXls?: (sectionKey: string, file: File) => void;
74
93
  };
75
94
  export declare const Akifilter: {
76
95
  <TFieldValues extends AkifilterFieldValues = FieldValues>(props: AkifilterProps<TFieldValues>): React.JSX.Element;
@@ -1 +1 @@
1
- {"version":3,"file":"akifilter.d.ts","sourceRoot":"","sources":["../../src/akifilter.tsx"],"names":[],"mappings":"AAAA,OAAO,cAAc,CAAC;AAGtB,OAAO,EAIL,WAAW,EACX,IAAI,EAIL,MAAM,iBAAiB,CAAC;AAYzB,OAAO,KAAK,MAAM,OAAO,CAAC;AAc1B,OAAO,EACL,KAAK,aAAa,EAEnB,MAAM,8BAA8B,CAAC;AAgBtC,OAAO,KAAK,EACV,mBAAmB,EACnB,eAAe,EAEf,eAAe,EAChB,MAAM,SAAS,CAAC;AAWjB,KAAK,oBAAoB,GAAG,WAAW,CAAC;AA2KxC,MAAM,MAAM,cAAc,CACxB,YAAY,SAAS,oBAAoB,GAAG,oBAAoB,IAC9D;IACF;;OAEG;IACH,YAAY,CAAC,EAAE,eAAe,CAAC,YAAY,CAAC,CAAC;IAC7C;;OAEG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B;;OAEG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC,YAAY,CAAC,CAAC;IACtC;;OAEG;IACH,cAAc,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,CAAC,YAAY,CAAC,KAAK,IAAI,CAAC;IACzD;;OAEG;IACH,qBAAqB,CAAC,EAAE,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,KAAK,IAAI,CAAC;IAClE;;;OAGG;IACH,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,IAAI,CAAC;IACnC;;;OAGG;IACH,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,IAAI,CAAC;IACnC;;OAEG;IACH,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;IACxB;;OAEG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B;;OAEG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B;;OAEG;IACH,gBAAgB,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,mBAAmB,CAAC,CAAC;IAClD;;;OAGG;IACH,sBAAsB,CAAC,EAAE,aAAa,EAAE,CAAC;IACzC;;OAEG;IACH,sBAAsB,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/C;;;;;OAKG;IACH,aAAa,CAAC,EAAE,eAAe,EAAE,CAAC;IAClC;;OAEG;IACH,qBAAqB,CAAC,EAAE,CAAC,aAAa,EAAE,eAAe,EAAE,KAAK,IAAI,CAAC;CACpE,CAAC;AA+3BF,eAAO,MAAM,SAAS;KACpB,YAAY,SAAS,oBAAoB,uBAElC,cAAc,CAAC,YAAY,CAAC;;CAoBpC,CAAC"}
1
+ {"version":3,"file":"akifilter.d.ts","sourceRoot":"","sources":["../../src/akifilter.tsx"],"names":[],"mappings":"AAAA,OAAO,cAAc,CAAC;AAGtB,OAAO,EAIL,WAAW,EACX,IAAI,EAIL,MAAM,iBAAiB,CAAC;AAYzB,OAAO,KAAK,MAAM,OAAO,CAAC;AAc1B,OAAO,EACL,KAAK,aAAa,EAEnB,MAAM,8BAA8B,CAAC;AAgBtC,OAAO,KAAK,EACV,mBAAmB,EACnB,eAAe,EAEf,eAAe,EAChB,MAAM,SAAS,CAAC;AAYjB,KAAK,oBAAoB,GAAG,WAAW,CAAC;AAiLxC,MAAM,MAAM,cAAc,CACxB,YAAY,SAAS,oBAAoB,GAAG,oBAAoB,IAC9D;IACF;;OAEG;IACH,YAAY,CAAC,EAAE,eAAe,CAAC,YAAY,CAAC,CAAC;IAC7C;;OAEG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B;;OAEG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC,YAAY,CAAC,CAAC;IACtC;;OAEG;IACH,cAAc,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,CAAC,YAAY,CAAC,KAAK,IAAI,CAAC;IACzD;;OAEG;IACH,qBAAqB,CAAC,EAAE,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,KAAK,IAAI,CAAC;IAClE;;;OAGG;IACH,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,IAAI,CAAC;IACnC;;;OAGG;IACH,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,IAAI,CAAC;IACnC;;OAEG;IACH,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;IACxB;;OAEG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B;;OAEG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B;;OAEG;IACH,gBAAgB,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,mBAAmB,CAAC,CAAC;IAClD;;;OAGG;IACH,sBAAsB,CAAC,EAAE,aAAa,EAAE,CAAC;IACzC;;OAEG;IACH,sBAAsB,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/C;;;;;OAKG;IACH,aAAa,CAAC,EAAE,eAAe,EAAE,CAAC;IAClC;;OAEG;IACH,qBAAqB,CAAC,EAAE,CAAC,aAAa,EAAE,eAAe,EAAE,KAAK,IAAI,CAAC;IACnE;;;;;;;;;;OAUG;IACH,kBAAkB,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,KAAK,IAAI,CAAC;IAC9D;;;;;OAKG;IACH,kBAAkB,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,KAAK,IAAI,CAAC;CAC/D,CAAC;AAk9BF,eAAO,MAAM,SAAS;KACpB,YAAY,SAAS,oBAAoB,uBAElC,cAAc,CAAC,YAAY,CAAC;;CAoBpC,CAAC"}
@@ -37,7 +37,7 @@ import { useDynamicVisibility } from './hooks/use-dynamic-visibility';
37
37
  import { useFieldVisibility } from './hooks/use-field-visibility';
38
38
  import { useVisibilityCleanup } from './hooks/use-visibility-cleanup';
39
39
  import { i18n } from './i18n';
40
- import { deriveExcludeSectionKeys, extractDefaultValues, flattenSchema, getFieldAriaLabel, normaliseValuesBySchema, partitionSchema } from './utils/schema';
40
+ import { deriveExcludeSectionKeys, extractDefaultValues, flattenSchema, getAppliedLabel, getFieldAriaLabel, normaliseValuesBySchema, partitionSchema } from './utils/schema';
41
41
  import { normaliseOutputValues } from './utils/values';
42
42
  /**
43
43
  * Prefix for applied-filter chip keys that originate from filter buttons.
@@ -45,6 +45,12 @@ import { normaliseOutputValues } from './utils/values';
45
45
  * routed back to the owning button.
46
46
  */
47
47
  const BUTTON_CHIP_PREFIX = 'akifilter-button:';
48
+ // Chips a field produced through `appliedChips`. The key carries both halves —
49
+ // `<field key>::<chip id>` — so a removal can be routed back to the field that
50
+ // owns it. Split at the *first* separator, so a chip id may contain one; a
51
+ // field key may not, which holds for form paths.
52
+ const FIELD_CHIP_PREFIX = 'akifilter-field:';
53
+ const FIELD_CHIP_SEPARATOR = '::';
48
54
  const FilterFormItem = (_a) => {
49
55
  var _b, _c;
50
56
  var { children, control, name, valuePropName } = _a, props = __rest(_a, ["children", "control", "name", "valuePropName"]);
@@ -125,7 +131,7 @@ const resolveClearedFieldValue = (field, defaultValue) => {
125
131
  return undefined;
126
132
  }
127
133
  };
128
- const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onValuesChange, onVisibleFieldsChange, onImportCsv, onImportXls, onClearAll, enableImportCsv, enableImportXls, filterActionsRef, externalAppliedFilters, onRemoveExternalFilter, filterButtons, onFilterButtonsChange }) => {
134
+ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onValuesChange, onVisibleFieldsChange, onImportCsv, onImportXls, onClearAll, enableImportCsv, enableImportXls, filterActionsRef, externalAppliedFilters, onRemoveExternalFilter, filterButtons, onFilterButtonsChange, onSectionImportCsv, onSectionImportXls }) => {
129
135
  // Separate regular fields from section fields
130
136
  const { regularFields, sectionFields } = React.useMemo(() => partitionSchema(filterSchema), [filterSchema]);
131
137
  const excludeSectionKeys = React.useMemo(() => deriveExcludeSectionKeys(sectionFields), [sectionFields]);
@@ -135,16 +141,19 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
135
141
  const schemaDefaults = React.useMemo(() => normaliseValuesBySchema(flattenedSchema, extractDefaultValues(flattenedSchema)), [flattenedSchema]);
136
142
  const externalDefaults = React.useMemo(() => normaliseValuesBySchema(flattenedSchema, defaultValues), [flattenedSchema, defaultValues]);
137
143
  const baseDefaultValues = React.useMemo(() => (Object.assign(Object.assign({}, schemaDefaults), externalDefaults)), [schemaDefaults, externalDefaults]);
138
- // Use state for persistedDefaults to make it reactive to localStorage changes
144
+ // Use state for persistedDefaults to make it reactive to localStorage changes.
145
+ // Read against the *flattened* schema: `readStoredValues` drops keys that are
146
+ // not in the schema it is given, so passing only the top-level fields would
147
+ // silently discard every section field's persisted value on reload.
139
148
  const [persistedDefaults, setPersistedDefaults] = React.useState(() => {
140
149
  var _a;
141
- return normaliseValuesBySchema(flattenedSchema, (_a = readStoredValues(regularFields, storageKey)) !== null && _a !== void 0 ? _a : undefined);
150
+ return normaliseValuesBySchema(flattenedSchema, (_a = readStoredValues(flattenedSchema, storageKey)) !== null && _a !== void 0 ? _a : undefined);
142
151
  });
143
152
  // Re-read from localStorage when storageKey changes
144
153
  React.useEffect(() => {
145
154
  var _a;
146
- setPersistedDefaults(normaliseValuesBySchema(flattenedSchema, (_a = readStoredValues(regularFields, storageKey)) !== null && _a !== void 0 ? _a : undefined));
147
- }, [flattenedSchema, regularFields, storageKey]);
155
+ setPersistedDefaults(normaliseValuesBySchema(flattenedSchema, (_a = readStoredValues(flattenedSchema, storageKey)) !== null && _a !== void 0 ? _a : undefined));
156
+ }, [flattenedSchema, storageKey]);
148
157
  const mergedDefaultValues = React.useMemo(() => (Object.assign(Object.assign({}, baseDefaultValues), persistedDefaults)), [baseDefaultValues, persistedDefaults]);
149
158
  const formMethods = useForm({
150
159
  defaultValues: mergedDefaultValues
@@ -197,7 +206,23 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
197
206
  if (field.type === FIELD_TYPES.CHECKBOX && currentValue !== true) {
198
207
  return acc;
199
208
  }
200
- const label = field.label || field.placeholder || key;
209
+ const label = getAppliedLabel(field);
210
+ // A field that owns several filters renders its own chips: only it knows
211
+ // how to split its value and how to format each part.
212
+ if (typeof field.appliedChips === 'function') {
213
+ field
214
+ .appliedChips({ field, value: currentValue, formValues })
215
+ .forEach(chip => {
216
+ var _a, _b;
217
+ acc.push({
218
+ key: `${FIELD_CHIP_PREFIX}${key}${FIELD_CHIP_SEPARATOR}${chip.id}`,
219
+ label: (_a = chip.label) !== null && _a !== void 0 ? _a : label,
220
+ value: chip.value,
221
+ isExclude: (_b = chip.isExclude) !== null && _b !== void 0 ? _b : excludeSectionKeys.has(key)
222
+ });
223
+ });
224
+ return acc;
225
+ }
201
226
  const fieldOptions = 'options' in field && field.options ? field.options : undefined;
202
227
  const resolveLabel = (val) => {
203
228
  var _a;
@@ -415,17 +440,14 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
415
440
  emitValues,
416
441
  persistValues
417
442
  ]);
418
- const handleRemoveFilter = React.useCallback((key) => {
419
- const schemaField = flattenedSchema.find(field => String(field.key) === key);
420
- if (!schemaField) {
421
- return;
422
- }
423
- const defaultValue = schemaDefaults[String(schemaField.key)];
424
- const nextValue = resolveClearedFieldValue(schemaField, defaultValue);
443
+ // Writes one field's value and pushes the result through the whole
444
+ // persist/emit path immediately, bypassing the debounce the way a removal
445
+ // has to. Shared by chip removals — whole-field and per-chip alike.
446
+ const commitFieldValue = React.useCallback((fieldKey, nextValue) => {
425
447
  // Manually construct the updated form values instead of relying on setValue + getValues
426
448
  // because setValue is batched and getValues() wouldn't reflect the change immediately
427
449
  const currentFormValues = formMethods.getValues();
428
- const updatedFormValues = Object.assign(Object.assign({}, currentFormValues), { [String(schemaField.key)]: nextValue });
450
+ const updatedFormValues = Object.assign(Object.assign({}, currentFormValues), { [fieldKey]: nextValue });
429
451
  // Normalize the values (removes empty/undefined entries)
430
452
  const nextValues = normaliseOutputValues(flattenedSchema, updatedFormValues);
431
453
  // updatedFormValues holds an explicit cleared value (e.g. `null` for custom/file
@@ -447,7 +469,36 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
447
469
  hasInitialValuesRef.current = Object.keys(nextValues).length > 0;
448
470
  // Emit the change to parent
449
471
  emitValues(nextValues);
450
- }, [schemaDefaults, flattenedSchema, formMethods, emitValues, persistValues]);
472
+ }, [flattenedSchema, formMethods, emitValues, persistValues]);
473
+ const handleRemoveFilter = React.useCallback((key) => {
474
+ const schemaField = flattenedSchema.find(field => String(field.key) === key);
475
+ if (!schemaField) {
476
+ return;
477
+ }
478
+ const defaultValue = schemaDefaults[String(schemaField.key)];
479
+ commitFieldValue(String(schemaField.key), resolveClearedFieldValue(schemaField, defaultValue));
480
+ }, [schemaDefaults, flattenedSchema, commitFieldValue]);
481
+ // Removal of a single chip produced by a field's own `appliedChips`. The
482
+ // field decides what its value becomes; Akifilter just commits it.
483
+ const handleFieldChipRemove = React.useCallback((chipKey) => {
484
+ const separatorIndex = chipKey.indexOf(FIELD_CHIP_SEPARATOR);
485
+ if (separatorIndex === -1) {
486
+ return;
487
+ }
488
+ const fieldKey = chipKey.slice(0, separatorIndex);
489
+ const chipId = chipKey.slice(separatorIndex + FIELD_CHIP_SEPARATOR.length);
490
+ const schemaField = flattenedSchema.find(field => String(field.key) === fieldKey);
491
+ if (!schemaField ||
492
+ typeof schemaField.onRemoveAppliedChip !== 'function') {
493
+ return;
494
+ }
495
+ const currentValue = formMethods.getValues()[fieldKey];
496
+ commitFieldValue(fieldKey, schemaField.onRemoveAppliedChip({
497
+ field: schemaField,
498
+ value: currentValue,
499
+ id: chipId
500
+ }));
501
+ }, [flattenedSchema, formMethods, commitFieldValue]);
451
502
  // Toggle a filter button. Button state lives outside the form, so this syncs
452
503
  // the value ref, persists, and emits immediately (bypassing the form debounce).
453
504
  const handleButtonToggle = React.useCallback((button) => {
@@ -511,6 +562,9 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
511
562
  if (key.startsWith(BUTTON_CHIP_PREFIX)) {
512
563
  handleButtonRemove(key.slice(BUTTON_CHIP_PREFIX.length));
513
564
  }
565
+ else if (key.startsWith(FIELD_CHIP_PREFIX)) {
566
+ handleFieldChipRemove(key.slice(FIELD_CHIP_PREFIX.length));
567
+ }
514
568
  else if (externalAppliedFilters === null || externalAppliedFilters === void 0 ? void 0 : externalAppliedFilters.some(f => f.key === key)) {
515
569
  onRemoveExternalFilter === null || onRemoveExternalFilter === void 0 ? void 0 : onRemoveExternalFilter(key);
516
570
  }
@@ -521,7 +575,8 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
521
575
  externalAppliedFilters,
522
576
  onRemoveExternalFilter,
523
577
  handleRemoveFilter,
524
- handleButtonRemove
578
+ handleButtonRemove,
579
+ handleFieldChipRemove
525
580
  ]);
526
581
  const handleClearAllApplied = React.useCallback(() => {
527
582
  // Reset filter buttons before clearing the form so the merged emission in
@@ -596,7 +651,9 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
596
651
  React.createElement("div", { className: "akinon-filter__form-grid", "data-testid": "akifilter-form-grid" },
597
652
  visibleFields.map(renderFormField),
598
653
  visibleFields.length === 0 && regularFields.length > 0 ? (React.createElement("div", { className: "akinon-filter__empty" }, i18n.t('form.noVisibleFields'))) : null),
599
- sectionFields.length > 0 && (React.createElement("div", { className: "akinon-filter__section-fields" }, sectionFields.map(section => (React.createElement(SectionFieldset, { key: String(section.key), section: section, storageKey: storageKey, renderFormField: renderFormField, onRemoveField: handleRemoveFilter, formValues: normalisedValues }))))))),
654
+ sectionFields.length > 0 && (React.createElement("div", { className: "akinon-filter__section-fields" }, sectionFields.map(section => (React.createElement(SectionFieldset, { key: String(section.key), section: section, storageKey: storageKey, renderFormField: renderFormField, onRemoveField: handleRemoveFilter, formValues: normalisedValues, onImportCsv: onSectionImportCsv &&
655
+ (file => onSectionImportCsv(String(section.key), file)), onImportXls: onSectionImportXls &&
656
+ (file => onSectionImportXls(String(section.key), file)) }))))))),
600
657
  React.createElement(VisibilityModal, Object.assign({}, modalProps)))));
601
658
  };
602
659
  const AkifilterEmptyState = () => {
@@ -1 +1 @@
1
- {"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../../../src/common/storage.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAGnD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AA4B/C,eAAO,MAAM,eAAe,GAAI,YAAY,SAAS,WAAW,EAC9D,QAAQ,cAAc,CAAC,YAAY,CAAC,EAAE,EACtC,YAAY,MAAM,KACjB,MAKF,CAAC;AAIF,eAAO,MAAM,eAAe,GAAI,YAAY,SAAS,WAAW,EAC9D,QAAQ,cAAc,CAAC,YAAY,CAAC,EAAE,EACtC,YAAY,MAAM,KACjB,MAAM,EAAE,GAAG,IAoBb,CAAC;AAEF,eAAO,MAAM,gBAAgB,GAAI,YAAY,MAAM,EAAE,MAAM,MAAM,EAAE,KAAG,IAQrE,CAAC;AAEF,eAAO,MAAM,yBAAyB,GACpC,YAAY,MAAM,EAClB,YAAY,MAAM,GAAG,MAAM,KAC1B,MAAuD,CAAC;AAE3D,eAAO,MAAM,gBAAgB,GAAI,YAAY,MAAM,KAAG,IAQrD,CAAC;AAKF,eAAO,MAAM,gBAAgB,GAAI,YAAY,SAAS,WAAW,EAC/D,QAAQ,cAAc,CAAC,YAAY,CAAC,EAAE,EACtC,YAAY,MAAM,KACjB,OAAO,CAAC,YAAY,CAAC,GAAG,IA+B1B,CAAC;AAEF,eAAO,MAAM,iBAAiB,GAAI,YAAY,SAAS,WAAW,EAChE,YAAY,MAAM,EAClB,QAAQ,OAAO,CAAC,YAAY,CAAC,KAC5B,IAeF,CAAC;AAEF,eAAO,MAAM,iBAAiB,GAAI,YAAY,MAAM,KAAG,IAQtD,CAAC;AAKF,eAAO,MAAM,sBAAsB,GACjC,YAAY,MAAM,KACjB,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC,GAAG,IAwB3B,CAAC;AAEF,eAAO,MAAM,uBAAuB,GAClC,YAAY,MAAM,EAClB,QAAQ,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC,KAC7B,IAcF,CAAC;AAEF,eAAO,MAAM,uBAAuB,GAAI,YAAY,MAAM,KAAG,IAQ5D,CAAC"}
1
+ {"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../../../src/common/storage.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAInD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AA4B/C,eAAO,MAAM,eAAe,GAAI,YAAY,SAAS,WAAW,EAC9D,QAAQ,cAAc,CAAC,YAAY,CAAC,EAAE,EACtC,YAAY,MAAM,KACjB,MAKF,CAAC;AAIF,eAAO,MAAM,eAAe,GAAI,YAAY,SAAS,WAAW,EAC9D,QAAQ,cAAc,CAAC,YAAY,CAAC,EAAE,EACtC,YAAY,MAAM,KACjB,MAAM,EAAE,GAAG,IAoBb,CAAC;AAEF,eAAO,MAAM,gBAAgB,GAAI,YAAY,MAAM,EAAE,MAAM,MAAM,EAAE,KAAG,IAQrE,CAAC;AAEF,eAAO,MAAM,yBAAyB,GACpC,YAAY,MAAM,EAClB,YAAY,MAAM,GAAG,MAAM,KAC1B,MAAuD,CAAC;AAE3D,eAAO,MAAM,gBAAgB,GAAI,YAAY,MAAM,KAAG,IAQrD,CAAC;AA0DF,eAAO,MAAM,gBAAgB,GAAI,YAAY,SAAS,WAAW,EAC/D,QAAQ,cAAc,CAAC,YAAY,CAAC,EAAE,EACtC,YAAY,MAAM,KACjB,OAAO,CAAC,YAAY,CAAC,GAAG,IAqC1B,CAAC;AAEF,eAAO,MAAM,iBAAiB,GAAI,YAAY,SAAS,WAAW,EAChE,YAAY,MAAM,EAClB,QAAQ,OAAO,CAAC,YAAY,CAAC,KAC5B,IAeF,CAAC;AAEF,eAAO,MAAM,iBAAiB,GAAI,YAAY,MAAM,KAAG,IAQtD,CAAC;AAKF,eAAO,MAAM,sBAAsB,GACjC,YAAY,MAAM,KACjB,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC,GAAG,IAwB3B,CAAC;AAEF,eAAO,MAAM,uBAAuB,GAClC,YAAY,MAAM,EAClB,QAAQ,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC,KAC7B,IAcF,CAAC;AAEF,eAAO,MAAM,uBAAuB,GAAI,YAAY,MAAM,KAAG,IAQ5D,CAAC"}
@@ -1,3 +1,4 @@
1
+ import { FIELD_MODES, FIELD_TYPES } from '../constants';
1
2
  import { i18n } from '../i18n';
2
3
  const STORAGE_PREFIX = 'akifilter:v1';
3
4
  const VALUES_SUFFIX = ':values';
@@ -68,6 +69,46 @@ export const clearVisibleKeys = (storageKey) => {
68
69
  }
69
70
  };
70
71
  const buildValuesStorageKey = (storageKey) => `${storageKey}${VALUES_SUFFIX}`;
72
+ /**
73
+ * Guards a persisted value against schema drift.
74
+ *
75
+ * The storage key is hashed from the top-level field signature only, so a
76
+ * section field whose `type`/`mode` changed between releases keeps the same key
77
+ * and its stale value would otherwise be restored into an incompatible widget
78
+ * (e.g. a multi-select's array landing in a text input, which then renders
79
+ * `a,b` and is re-emitted as an array to a host expecting a string).
80
+ *
81
+ * Deliberately conservative: it only rejects shapes that no widget of that type
82
+ * can hold, and never polices string-vs-number.
83
+ */
84
+ const isStoredValueCompatible = (field, value) => {
85
+ // `File` objects stringify to `{}`, so a persisted file value is always junk.
86
+ if (field.type === FIELD_TYPES.FILE) {
87
+ return false;
88
+ }
89
+ // Custom fields own their value shape; the package cannot judge it. So does
90
+ // any field that renders its own chips: `appliedChips` means the value is a
91
+ // composite only that field interprets, whatever the field's type is.
92
+ if (field.type === FIELD_TYPES.CUSTOM ||
93
+ typeof field.appliedChips === 'function') {
94
+ return true;
95
+ }
96
+ const isMultiSelect = field.type === FIELD_TYPES.SELECT &&
97
+ (field.mode === FIELD_MODES.MULTIPLE || field.mode === FIELD_MODES.TAGS);
98
+ if (Array.isArray(value)) {
99
+ // Only multi-selects hold arrays. A bare scalar is still fine for them —
100
+ // `normaliseValuesBySchema` wraps it.
101
+ return isMultiSelect;
102
+ }
103
+ // Nothing else round-trips through a filter widget as a plain object.
104
+ if (value !== null && typeof value === 'object') {
105
+ return false;
106
+ }
107
+ if (field.type === FIELD_TYPES.CHECKBOX) {
108
+ return typeof value === 'boolean';
109
+ }
110
+ return true;
111
+ };
71
112
  export const readStoredValues = (schema, storageKey) => {
72
113
  if (!isBrowser)
73
114
  return null;
@@ -80,9 +121,13 @@ export const readStoredValues = (schema, storageKey) => {
80
121
  if (!parsed || typeof parsed !== 'object') {
81
122
  return null;
82
123
  }
83
- const schemaKeys = new Set(schema.map(field => String(field.key)));
124
+ const fieldsByKey = new Map(schema.map(field => [String(field.key), field]));
84
125
  return Object.entries(parsed).reduce((acc, [key, value]) => {
85
- if (schemaKeys.has(String(key))) {
126
+ const field = fieldsByKey.get(String(key));
127
+ // Unknown keys belong to a different schema version; values whose shape no
128
+ // longer fits their field are stale leftovers of one (see
129
+ // `isStoredValueCompatible`).
130
+ if (field && isStoredValueCompatible(field, value)) {
86
131
  acc[key] =
87
132
  value;
88
133
  }
@@ -0,0 +1,33 @@
1
+ import React from 'react';
2
+ export type FileImportButtonProps = {
3
+ /**
4
+ * Accepted file extensions, forwarded to the hidden file input
5
+ * (e.g. `'.csv'`, `'.xls,.xlsx'`).
6
+ */
7
+ accept: string;
8
+ /**
9
+ * Text rendered inside the button (e.g. `'CSV'`).
10
+ */
11
+ label: string;
12
+ /**
13
+ * Tooltip / accessible name. Falls back to `label` when omitted, which is
14
+ * what the top-level toolbar relies on.
15
+ */
16
+ title?: string;
17
+ /**
18
+ * Called with the picked file. The component never uploads anything itself;
19
+ * the host application decides what to do with the file.
20
+ */
21
+ onImport: (file: File) => void;
22
+ /**
23
+ * Extra class names appended to the button.
24
+ */
25
+ className?: string;
26
+ };
27
+ /**
28
+ * Button that opens the native file picker and hands the selected file to
29
+ * `onImport`. Shared by the top-level filter toolbar and the section headers so
30
+ * both surfaces behave identically.
31
+ */
32
+ export declare const FileImportButton: ({ accept, label, title, onImport, className }: FileImportButtonProps) => React.JSX.Element;
33
+ //# sourceMappingURL=file-import-button.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-import-button.d.ts","sourceRoot":"","sources":["../../../src/components/file-import-button.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,MAAM,MAAM,qBAAqB,GAAG;IAClC;;;OAGG;IACH,MAAM,EAAE,MAAM,CAAC;IACf;;OAEG;IACH,KAAK,EAAE,MAAM,CAAC;IACd;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;OAGG;IACH,QAAQ,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,IAAI,CAAC;IAC/B;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,GAAI,+CAM9B,qBAAqB,sBA6CvB,CAAC"}
@@ -0,0 +1,33 @@
1
+ import { Button } from '@akinon/ui-button';
2
+ import React from 'react';
3
+ /**
4
+ * Button that opens the native file picker and hands the selected file to
5
+ * `onImport`. Shared by the top-level filter toolbar and the section headers so
6
+ * both surfaces behave identically.
7
+ */
8
+ export const FileImportButton = ({ accept, label, title, onImport, className }) => {
9
+ const inputRef = React.useRef(null);
10
+ const resolvedTitle = title !== null && title !== void 0 ? title : label;
11
+ const handleClick = () => { var _a; return (_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.click(); };
12
+ const handleChange = (event) => {
13
+ var _a;
14
+ const file = (_a = event.target.files) === null || _a === void 0 ? void 0 : _a[0];
15
+ if (file) {
16
+ onImport(file);
17
+ }
18
+ // Reset so same file can be re-selected
19
+ event.target.value = '';
20
+ };
21
+ return (React.createElement(React.Fragment, null,
22
+ React.createElement("input", { ref: inputRef, type: "file", accept: accept, style: { display: 'none' }, onChange: handleChange }),
23
+ React.createElement(Button, { className: [
24
+ 'akinon-filter__toolbar-item',
25
+ 'akinon-filter__toolbar-item--file',
26
+ className
27
+ ]
28
+ .filter(Boolean)
29
+ .join(' '), icon: "filter",
30
+ // Smaller than the icon-only toolbar buttons: the icon shares its 36px
31
+ // square with the label underneath it.
32
+ iconSize: 12, onClick: handleClick, type: "primary", title: resolvedTitle, "aria-label": resolvedTitle }, label)));
33
+ };
@@ -1 +1 @@
1
- {"version":3,"file":"filter-toolbar.d.ts","sourceRoot":"","sources":["../../../src/components/filter-toolbar.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,MAAM,OAAO,CAAC;AAI1B,KAAK,kBAAkB,GAAG;IACxB,WAAW,EAAE,MAAM,IAAI,CAAC;IACxB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,IAAI,CAAC;IACnC,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,IAAI,CAAC;CACpC,CAAC;AAgDF,eAAO,MAAM,aAAa,GAAI,6EAM3B,kBAAkB,sBAoCpB,CAAC"}
1
+ {"version":3,"file":"filter-toolbar.d.ts","sourceRoot":"","sources":["../../../src/components/filter-toolbar.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,MAAM,OAAO,CAAC;AAM1B,KAAK,kBAAkB,GAAG;IACxB,WAAW,EAAE,MAAM,IAAI,CAAC;IACxB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,IAAI,CAAC;IACnC,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,IAAI,CAAC;CACpC,CAAC;AAEF,eAAO,MAAM,aAAa,GAAI,6EAM3B,kBAAkB,sBAoCpB,CAAC"}