@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.
- package/README.md +306 -0
- package/dist/cjs/akifilter.d.ts +19 -0
- package/dist/cjs/akifilter.d.ts.map +1 -1
- package/dist/cjs/akifilter.js +74 -17
- package/dist/cjs/common/storage.d.ts.map +1 -1
- package/dist/cjs/common/storage.js +47 -2
- package/dist/cjs/components/file-import-button.d.ts +33 -0
- package/dist/cjs/components/file-import-button.d.ts.map +1 -0
- package/dist/cjs/components/file-import-button.js +37 -0
- package/dist/cjs/components/filter-toolbar.d.ts.map +1 -1
- package/dist/cjs/components/filter-toolbar.js +4 -18
- package/dist/cjs/components/section-fieldset.d.ts +16 -2
- package/dist/cjs/components/section-fieldset.d.ts.map +1 -1
- package/dist/cjs/components/section-fieldset.js +104 -32
- package/dist/cjs/components/visibility-modal.js +1 -1
- package/dist/cjs/constants.d.ts +6 -0
- package/dist/cjs/constants.d.ts.map +1 -1
- package/dist/cjs/constants.js +7 -1
- package/dist/cjs/hooks/use-field-visibility.d.ts.map +1 -1
- package/dist/cjs/hooks/use-field-visibility.js +10 -1
- package/dist/cjs/i18n/translations/en.d.ts +8 -0
- package/dist/cjs/i18n/translations/en.d.ts.map +1 -1
- package/dist/cjs/i18n/translations/en.js +8 -0
- package/dist/cjs/i18n/translations/tr.d.ts +8 -0
- package/dist/cjs/i18n/translations/tr.d.ts.map +1 -1
- package/dist/cjs/i18n/translations/tr.js +8 -0
- package/dist/cjs/styles.css +36 -11
- package/dist/cjs/types.d.ts +146 -3
- package/dist/cjs/types.d.ts.map +1 -1
- package/dist/cjs/utils/schema.d.ts +10 -0
- package/dist/cjs/utils/schema.d.ts.map +1 -1
- package/dist/cjs/utils/schema.js +13 -1
- package/dist/esm/akifilter.d.ts +19 -0
- package/dist/esm/akifilter.d.ts.map +1 -1
- package/dist/esm/akifilter.js +75 -18
- package/dist/esm/common/storage.d.ts.map +1 -1
- package/dist/esm/common/storage.js +47 -2
- package/dist/esm/components/file-import-button.d.ts +33 -0
- package/dist/esm/components/file-import-button.d.ts.map +1 -0
- package/dist/esm/components/file-import-button.js +33 -0
- package/dist/esm/components/filter-toolbar.d.ts.map +1 -1
- package/dist/esm/components/filter-toolbar.js +4 -18
- package/dist/esm/components/section-fieldset.d.ts +16 -2
- package/dist/esm/components/section-fieldset.d.ts.map +1 -1
- package/dist/esm/components/section-fieldset.js +104 -32
- package/dist/esm/components/visibility-modal.js +2 -2
- package/dist/esm/constants.d.ts +6 -0
- package/dist/esm/constants.d.ts.map +1 -1
- package/dist/esm/constants.js +6 -0
- package/dist/esm/hooks/use-field-visibility.d.ts.map +1 -1
- package/dist/esm/hooks/use-field-visibility.js +10 -1
- package/dist/esm/i18n/translations/en.d.ts +8 -0
- package/dist/esm/i18n/translations/en.d.ts.map +1 -1
- package/dist/esm/i18n/translations/en.js +8 -0
- package/dist/esm/i18n/translations/tr.d.ts +8 -0
- package/dist/esm/i18n/translations/tr.d.ts.map +1 -1
- package/dist/esm/i18n/translations/tr.js +8 -0
- package/dist/esm/styles.css +36 -11
- package/dist/esm/types.d.ts +146 -3
- package/dist/esm/types.d.ts.map +1 -1
- package/dist/esm/utils/schema.d.ts +10 -0
- package/dist/esm/utils/schema.d.ts.map +1 -1
- package/dist/esm/utils/schema.js +10 -0
- package/package.json +7 -7
package/dist/cjs/types.d.ts
CHANGED
|
@@ -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
|
-
|
|
17
|
-
|
|
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
|
package/dist/cjs/types.d.ts.map
CHANGED
|
@@ -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,
|
|
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"}
|
package/dist/cjs/utils/schema.js
CHANGED
|
@@ -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
|
};
|
package/dist/esm/akifilter.d.ts
CHANGED
|
@@ -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;
|
|
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"}
|
package/dist/esm/akifilter.js
CHANGED
|
@@ -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(
|
|
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(
|
|
147
|
-
}, [flattenedSchema,
|
|
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
|
|
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
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
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), { [
|
|
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
|
-
}, [
|
|
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;
|
|
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
|
|
124
|
+
const fieldsByKey = new Map(schema.map(field => [String(field.key), field]));
|
|
84
125
|
return Object.entries(parsed).reduce((acc, [key, value]) => {
|
|
85
|
-
|
|
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;
|
|
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"}
|