@akinon/akifilter 1.10.2-261002-0 → 1.11.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 CHANGED
@@ -45,6 +45,54 @@
45
45
 
46
46
  ---
47
47
 
48
+ ## Value Persistence (`persistValues`)
49
+
50
+ By default, field values and active filter buttons are written to local storage
51
+ and restored on the next mount. Set `persistValues={false}` when the filter has
52
+ to start from values the host supplies (for example filters loaded from an API):
53
+
54
+ ```tsx
55
+ <Akifilter
56
+ filterSchema={filterSchema}
57
+ storageNamespace="saved-filters"
58
+ persistValues={false}
59
+ defaultValues={savedFilters}
60
+ onValuesChange={setFilters}
61
+ />
62
+ ```
63
+
64
+ - Values and button state are never read or written; the form starts from
65
+ schema defaults + `defaultValues`, and buttons start inactive.
66
+ - Values stored before the prop was set are ignored, not deleted.
67
+ - Visible-field (and per-section visibility) persistence is unaffected.
68
+ - Leaving the prop out keeps today's behaviour (`true`).
69
+ - `persistValues` and `defaultValues` are read on mount; changing them later
70
+ does not reset the form. When the values come from an API, render
71
+ `Akifilter` only after they have loaded. Turning `persistValues` off after
72
+ mount stops further writes but keeps what was already restored.
73
+
74
+ ---
75
+
76
+ ## Footer
77
+
78
+ `footer` renders host content inside the filter card, below every field and
79
+ section. Pass a node, or a function that receives `{ values }` (the
80
+ `onValuesChange` shape, not debounced) to render conditionally. A falsy result
81
+ (`null`, `undefined`, `false`, `''`, `0`) renders nothing:
82
+
83
+ ```tsx
84
+ <Akifilter
85
+ filterSchema={filterSchema}
86
+ footer={({ values }) =>
87
+ Object.keys(values).length > 0 ? <Button>Save as rule</Button> : null
88
+ }
89
+ />
90
+ ```
91
+
92
+ The footer sits outside the form, so a button there never submits it.
93
+
94
+ ---
95
+
48
96
  ## Section Fields
49
97
 
50
98
  Fields declared with `type: 'section'` render as collapsible panels below the
@@ -4,6 +4,16 @@ import React from 'react';
4
4
  import { type AppliedFilter } from './components/applied-filters';
5
5
  import type { AkifilterActionsRef, AkifilterButton, AkifilterSchema } from './types';
6
6
  type AkifilterFieldValues = FieldValues;
7
+ /**
8
+ * What a `footer` render function receives.
9
+ */
10
+ export type AkifilterFooterContext<TFieldValues extends AkifilterFieldValues = AkifilterFieldValues> = {
11
+ /**
12
+ * Current filter values in the `onValuesChange` shape (normalised form values
13
+ * merged with active filter-button values), without the debounce.
14
+ */
15
+ values: Partial<TFieldValues>;
16
+ };
7
17
  export type AkifilterProps<TFieldValues extends AkifilterFieldValues = AkifilterFieldValues> = {
8
18
  /**
9
19
  * Declarative description of the filter fields.
@@ -13,6 +23,18 @@ export type AkifilterProps<TFieldValues extends AkifilterFieldValues = Akifilter
13
23
  * Optional namespace for local storage persistence.
14
24
  */
15
25
  storageNamespace?: string;
26
+ /**
27
+ * When false, field values and filter-button state are never read from or
28
+ * written to localStorage; the form starts from schema defaults +
29
+ * `defaultValues`. Use it when the filter must start from values supplied by
30
+ * the host (e.g. filters loaded from an API). Values stored before the prop
31
+ * was set are ignored, not deleted. Visible-field persistence is unaffected.
32
+ * Read on mount, like `defaultValues`: render the filter once the values are
33
+ * ready instead of passing them in later, and changing this prop afterwards
34
+ * does not clear state that was already restored.
35
+ * @default true
36
+ */
37
+ persistValues?: boolean;
16
38
  /**
17
39
  * Default values supplied by the host application.
18
40
  */
@@ -101,6 +123,14 @@ export type AkifilterProps<TFieldValues extends AkifilterFieldValues = Akifilter
101
123
  * function on every render re-notifies with an unchanged list.
102
124
  */
103
125
  onSectionVisibleFieldsChange?: (sectionKey: string, keys: Array<Path<TFieldValues>>) => void;
126
+ /**
127
+ * Host content rendered inside the filter card, below every field and
128
+ * section (e.g. an action button). Pass a node, or a function that receives
129
+ * the current values to render conditionally. Rendered outside the form, so a
130
+ * button here never submits it. Nothing is rendered when it returns a
131
+ * falsy value (`null`, `undefined`, `false`, `''`, `0`).
132
+ */
133
+ footer?: React.ReactNode | ((context: AkifilterFooterContext<TFieldValues>) => React.ReactNode);
104
134
  };
105
135
  export declare const Akifilter: {
106
136
  <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;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;IAC9D;;;;;;;;;OASG;IACH,4BAA4B,CAAC,EAAE,CAC7B,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,KAC5B,IAAI,CAAC;CACX,CAAC;AAu9BF,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;;GAEG;AACH,MAAM,MAAM,sBAAsB,CAChC,YAAY,SAAS,oBAAoB,GAAG,oBAAoB,IAC9D;IACF;;;OAGG;IACH,MAAM,EAAE,OAAO,CAAC,YAAY,CAAC,CAAC;CAC/B,CAAC;AAEF,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;;;;;;;;;;OAUG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB;;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;IAC9D;;;;;;;;;OASG;IACH,4BAA4B,CAAC,EAAE,CAC7B,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,KAC5B,IAAI,CAAC;IACV;;;;;;OAMG;IACH,MAAM,CAAC,EACH,KAAK,CAAC,SAAS,GACf,CAAC,CAAC,OAAO,EAAE,sBAAsB,CAAC,YAAY,CAAC,KAAK,KAAK,CAAC,SAAS,CAAC,CAAC;CAC1E,CAAC;AAsgCF,eAAO,MAAM,SAAS;KACpB,YAAY,SAAS,oBAAoB,uBAElC,cAAc,CAAC,YAAY,CAAC;;CAoBpC,CAAC"}
@@ -134,7 +134,7 @@ const resolveClearedFieldValue = (field, defaultValue) => {
134
134
  return undefined;
135
135
  }
136
136
  };
137
- const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onValuesChange, onVisibleFieldsChange, onImportCsv, onImportXls, onClearAll, enableImportCsv, enableImportXls, filterActionsRef, externalAppliedFilters, onRemoveExternalFilter, filterButtons, onFilterButtonsChange, onSectionImportCsv, onSectionImportXls, onSectionVisibleFieldsChange }) => {
137
+ const AkifilterContent = ({ filterSchema, storageNamespace, persistValues: shouldPersistValues = true, defaultValues, onValuesChange, onVisibleFieldsChange, onImportCsv, onImportXls, onClearAll, enableImportCsv, enableImportXls, filterActionsRef, externalAppliedFilters, onRemoveExternalFilter, filterButtons, onFilterButtonsChange, onSectionImportCsv, onSectionImportXls, onSectionVisibleFieldsChange, footer }) => {
138
138
  // Separate regular fields from section fields
139
139
  const { regularFields, sectionFields } = react_1.default.useMemo(() => (0, schema_1.partitionSchema)(filterSchema), [filterSchema]);
140
140
  const excludeSectionKeys = react_1.default.useMemo(() => (0, schema_1.deriveExcludeSectionKeys)(sectionFields), [sectionFields]);
@@ -148,15 +148,22 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
148
148
  // Read against the *flattened* schema: `readStoredValues` drops keys that are
149
149
  // not in the schema it is given, so passing only the top-level fields would
150
150
  // silently discard every section field's persisted value on reload.
151
+ // With `persistValues={false}` storage is never read, so the form starts from
152
+ // `baseDefaultValues` alone.
151
153
  const [persistedDefaults, setPersistedDefaults] = react_1.default.useState(() => {
152
154
  var _a;
153
- return (0, schema_1.normaliseValuesBySchema)(flattenedSchema, (_a = (0, storage_1.readStoredValues)(flattenedSchema, storageKey)) !== null && _a !== void 0 ? _a : undefined);
155
+ return shouldPersistValues
156
+ ? (0, schema_1.normaliseValuesBySchema)(flattenedSchema, (_a = (0, storage_1.readStoredValues)(flattenedSchema, storageKey)) !== null && _a !== void 0 ? _a : undefined)
157
+ : {};
154
158
  });
155
159
  // Re-read from localStorage when storageKey changes
156
160
  react_1.default.useEffect(() => {
157
161
  var _a;
162
+ if (!shouldPersistValues) {
163
+ return;
164
+ }
158
165
  setPersistedDefaults((0, schema_1.normaliseValuesBySchema)(flattenedSchema, (_a = (0, storage_1.readStoredValues)(flattenedSchema, storageKey)) !== null && _a !== void 0 ? _a : undefined));
159
- }, [flattenedSchema, storageKey]);
166
+ }, [flattenedSchema, storageKey, shouldPersistValues]);
160
167
  const mergedDefaultValues = react_1.default.useMemo(() => (Object.assign(Object.assign({}, baseDefaultValues), persistedDefaults)), [baseDefaultValues, persistedDefaults]);
161
168
  const formMethods = (0, akiform_1.useForm)({
162
169
  defaultValues: mergedDefaultValues
@@ -165,12 +172,15 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
165
172
  // --- Filter buttons (quick-filter toggles) ---------------------------------
166
173
  // A button is identified by its `value`, which must be unique across the
167
174
  // whole `filterButtons` array.
168
- const [activeButtonValues, setActiveButtonValues] = react_1.default.useState(() => { var _a; return (_a = (0, storage_1.readStoredButtonValues)(storageKey)) !== null && _a !== void 0 ? _a : []; });
175
+ const [activeButtonValues, setActiveButtonValues] = react_1.default.useState(() => { var _a; return shouldPersistValues ? ((_a = (0, storage_1.readStoredButtonValues)(storageKey)) !== null && _a !== void 0 ? _a : []) : []; });
169
176
  // Re-read persisted button state when the storage key changes.
170
177
  react_1.default.useEffect(() => {
171
178
  var _a;
179
+ if (!shouldPersistValues) {
180
+ return;
181
+ }
172
182
  setActiveButtonValues((_a = (0, storage_1.readStoredButtonValues)(storageKey)) !== null && _a !== void 0 ? _a : []);
173
- }, [storageKey]);
183
+ }, [storageKey, shouldPersistValues]);
174
184
  // Build the `{ key: value[] }` object contributed by the active buttons.
175
185
  // Buttons that share a key are merged into an array (legacy behaviour).
176
186
  const computeButtonValues = react_1.default.useCallback((values) => {
@@ -327,13 +337,16 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
327
337
  const persistValues = react_1.default.useCallback((values, serialised) => {
328
338
  const nextSerialised = serialised !== null && serialised !== void 0 ? serialised : JSON.stringify(values);
329
339
  lastPersistedValuesRef.current = nextSerialised;
340
+ if (!shouldPersistValues) {
341
+ return nextSerialised;
342
+ }
330
343
  if (Object.keys(values).length === 0) {
331
344
  (0, storage_1.clearStoredValues)(storageKey);
332
345
  return nextSerialised;
333
346
  }
334
347
  (0, storage_1.writeStoredValues)(storageKey, values);
335
348
  return nextSerialised;
336
- }, [storageKey]);
349
+ }, [storageKey, shouldPersistValues]);
337
350
  // Emit the normalised form values merged with the active filter-button
338
351
  // values. Buttons live outside the form state, so every emission point routes
339
352
  // through here to keep the two slices combined in the payload.
@@ -405,10 +418,12 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
405
418
  lastPersistedValuesRef.current = nextSerialised;
406
419
  currentSerialisedValuesRef.current = nextSerialised;
407
420
  // Update persistedDefaults to prevent stale mergedDefaultValues on re-render
408
- setPersistedDefaults(currParsed);
421
+ if (shouldPersistValues) {
422
+ setPersistedDefaults(currParsed);
423
+ }
409
424
  emitValues(currParsed);
410
425
  }
411
- }, [serialisedValues, emitValues, persistValues]);
426
+ }, [serialisedValues, emitValues, persistValues, shouldPersistValues]);
412
427
  const handleClearAll = react_1.default.useCallback(() => {
413
428
  const clearedDefaults = Object.assign({}, schemaDefaults);
414
429
  flattenedSchema.forEach(field => {
@@ -521,7 +536,9 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
521
536
  // before the state-update effect runs.
522
537
  buttonValuesRef.current = computeButtonValues(nextValues);
523
538
  setActiveButtonValues(nextValues);
524
- (0, storage_1.writeStoredButtonValues)(storageKey, nextValues);
539
+ if (shouldPersistValues) {
540
+ (0, storage_1.writeStoredButtonValues)(storageKey, nextValues);
541
+ }
525
542
  const formValuesNow = (0, values_1.normaliseOutputValues)(flattenedSchema, formMethods.getValues());
526
543
  hasEmittedValuesRef.current = true;
527
544
  emitValues(formValuesNow);
@@ -530,6 +547,7 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
530
547
  activeButtonValues,
531
548
  computeButtonValues,
532
549
  storageKey,
550
+ shouldPersistValues,
533
551
  flattenedSchema,
534
552
  formMethods,
535
553
  emitValues,
@@ -589,7 +607,9 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
589
607
  if (activeButtonValues.length > 0) {
590
608
  buttonValuesRef.current = {};
591
609
  setActiveButtonValues([]);
592
- (0, storage_1.clearStoredButtonValues)(storageKey);
610
+ if (shouldPersistValues) {
611
+ (0, storage_1.clearStoredButtonValues)(storageKey);
612
+ }
593
613
  onFilterButtonsChange === null || onFilterButtonsChange === void 0 ? void 0 : onFilterButtonsChange([]);
594
614
  }
595
615
  handleClearAll();
@@ -600,6 +620,7 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
600
620
  onRemoveExternalFilter,
601
621
  activeButtonValues,
602
622
  storageKey,
623
+ shouldPersistValues,
603
624
  onFilterButtonsChange
604
625
  ]);
605
626
  const renderFieldComponent = (field) => {
@@ -640,6 +661,11 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
640
661
  return (react_1.default.createElement(ui_typography_1.Text, { type: "secondary", className: "akinon-filter__unsupported" }, i18n_1.i18n.t('form.unsupportedField', { field: String(field.type) })));
641
662
  }
642
663
  };
664
+ const footerContent = typeof footer === 'function'
665
+ ? footer({
666
+ values: Object.assign(Object.assign({}, normalisedValues), computeButtonValues(activeButtonValues))
667
+ })
668
+ : footer;
643
669
  const renderFormField = (field) => {
644
670
  return (react_1.default.createElement(FilterFormItem, { key: String(field.key), control: formMethods.control, name: field.key, tooltip: field.tooltip, help: field.help, labelDescription: field.labelDescription, required: Boolean(field.validation), className: "mb-0", valuePropName: field.type === constants_1.FIELD_TYPES.CHECKBOX ? 'checked' : undefined }, renderFieldComponent(field)));
645
671
  };
@@ -658,7 +684,8 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
658
684
  visibleFields.length === 0 && regularFields.length > 0 ? (react_1.default.createElement("div", { className: "akinon-filter__empty" }, i18n_1.i18n.t('form.noVisibleFields'))) : null),
659
685
  sectionFields.length > 0 && (react_1.default.createElement("div", { className: "akinon-filter__section-fields" }, sectionFields.map(section => (react_1.default.createElement(section_fieldset_1.SectionFieldset, { key: String(section.key), section: section, storageKey: storageKey, renderFormField: renderFormField, onRemoveField: handleRemoveFilter, formValues: normalisedValues, onImportCsv: onSectionImportCsv &&
660
686
  (file => onSectionImportCsv(String(section.key), file)), onImportXls: onSectionImportXls &&
661
- (file => onSectionImportXls(String(section.key), file)), onVisibleFieldsChange: onSectionVisibleFieldsChange }))))))),
687
+ (file => onSectionImportXls(String(section.key), file)), onVisibleFieldsChange: onSectionVisibleFieldsChange })))))),
688
+ footerContent ? (react_1.default.createElement("div", { className: "akinon-filter__footer", "data-testid": "akifilter-footer" }, footerContent)) : null),
662
689
  react_1.default.createElement(visibility_modal_1.VisibilityModal, Object.assign({}, modalProps)))));
663
690
  };
664
691
  const AkifilterEmptyState = () => {
@@ -184,6 +184,10 @@
184
184
  color: var(--color-gray-500) !important;
185
185
  }
186
186
 
187
+ .akinon-filter__footer {
188
+ margin-top: 1rem;
189
+ }
190
+
187
191
  @media (max-width: 1024px) {
188
192
  .akinon-filter__form-grid {
189
193
  grid-template-columns: repeat(2, minmax(0, 1fr));
@@ -4,6 +4,16 @@ import React from 'react';
4
4
  import { type AppliedFilter } from './components/applied-filters';
5
5
  import type { AkifilterActionsRef, AkifilterButton, AkifilterSchema } from './types';
6
6
  type AkifilterFieldValues = FieldValues;
7
+ /**
8
+ * What a `footer` render function receives.
9
+ */
10
+ export type AkifilterFooterContext<TFieldValues extends AkifilterFieldValues = AkifilterFieldValues> = {
11
+ /**
12
+ * Current filter values in the `onValuesChange` shape (normalised form values
13
+ * merged with active filter-button values), without the debounce.
14
+ */
15
+ values: Partial<TFieldValues>;
16
+ };
7
17
  export type AkifilterProps<TFieldValues extends AkifilterFieldValues = AkifilterFieldValues> = {
8
18
  /**
9
19
  * Declarative description of the filter fields.
@@ -13,6 +23,18 @@ export type AkifilterProps<TFieldValues extends AkifilterFieldValues = Akifilter
13
23
  * Optional namespace for local storage persistence.
14
24
  */
15
25
  storageNamespace?: string;
26
+ /**
27
+ * When false, field values and filter-button state are never read from or
28
+ * written to localStorage; the form starts from schema defaults +
29
+ * `defaultValues`. Use it when the filter must start from values supplied by
30
+ * the host (e.g. filters loaded from an API). Values stored before the prop
31
+ * was set are ignored, not deleted. Visible-field persistence is unaffected.
32
+ * Read on mount, like `defaultValues`: render the filter once the values are
33
+ * ready instead of passing them in later, and changing this prop afterwards
34
+ * does not clear state that was already restored.
35
+ * @default true
36
+ */
37
+ persistValues?: boolean;
16
38
  /**
17
39
  * Default values supplied by the host application.
18
40
  */
@@ -101,6 +123,14 @@ export type AkifilterProps<TFieldValues extends AkifilterFieldValues = Akifilter
101
123
  * function on every render re-notifies with an unchanged list.
102
124
  */
103
125
  onSectionVisibleFieldsChange?: (sectionKey: string, keys: Array<Path<TFieldValues>>) => void;
126
+ /**
127
+ * Host content rendered inside the filter card, below every field and
128
+ * section (e.g. an action button). Pass a node, or a function that receives
129
+ * the current values to render conditionally. Rendered outside the form, so a
130
+ * button here never submits it. Nothing is rendered when it returns a
131
+ * falsy value (`null`, `undefined`, `false`, `''`, `0`).
132
+ */
133
+ footer?: React.ReactNode | ((context: AkifilterFooterContext<TFieldValues>) => React.ReactNode);
104
134
  };
105
135
  export declare const Akifilter: {
106
136
  <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;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;IAC9D;;;;;;;;;OASG;IACH,4BAA4B,CAAC,EAAE,CAC7B,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,KAC5B,IAAI,CAAC;CACX,CAAC;AAu9BF,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;;GAEG;AACH,MAAM,MAAM,sBAAsB,CAChC,YAAY,SAAS,oBAAoB,GAAG,oBAAoB,IAC9D;IACF;;;OAGG;IACH,MAAM,EAAE,OAAO,CAAC,YAAY,CAAC,CAAC;CAC/B,CAAC;AAEF,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;;;;;;;;;;OAUG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB;;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;IAC9D;;;;;;;;;OASG;IACH,4BAA4B,CAAC,EAAE,CAC7B,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,KAC5B,IAAI,CAAC;IACV;;;;;;OAMG;IACH,MAAM,CAAC,EACH,KAAK,CAAC,SAAS,GACf,CAAC,CAAC,OAAO,EAAE,sBAAsB,CAAC,YAAY,CAAC,KAAK,KAAK,CAAC,SAAS,CAAC,CAAC;CAC1E,CAAC;AAsgCF,eAAO,MAAM,SAAS;KACpB,YAAY,SAAS,oBAAoB,uBAElC,cAAc,CAAC,YAAY,CAAC;;CAoBpC,CAAC"}
@@ -131,7 +131,7 @@ const resolveClearedFieldValue = (field, defaultValue) => {
131
131
  return undefined;
132
132
  }
133
133
  };
134
- const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onValuesChange, onVisibleFieldsChange, onImportCsv, onImportXls, onClearAll, enableImportCsv, enableImportXls, filterActionsRef, externalAppliedFilters, onRemoveExternalFilter, filterButtons, onFilterButtonsChange, onSectionImportCsv, onSectionImportXls, onSectionVisibleFieldsChange }) => {
134
+ const AkifilterContent = ({ filterSchema, storageNamespace, persistValues: shouldPersistValues = true, defaultValues, onValuesChange, onVisibleFieldsChange, onImportCsv, onImportXls, onClearAll, enableImportCsv, enableImportXls, filterActionsRef, externalAppliedFilters, onRemoveExternalFilter, filterButtons, onFilterButtonsChange, onSectionImportCsv, onSectionImportXls, onSectionVisibleFieldsChange, footer }) => {
135
135
  // Separate regular fields from section fields
136
136
  const { regularFields, sectionFields } = React.useMemo(() => partitionSchema(filterSchema), [filterSchema]);
137
137
  const excludeSectionKeys = React.useMemo(() => deriveExcludeSectionKeys(sectionFields), [sectionFields]);
@@ -145,15 +145,22 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
145
145
  // Read against the *flattened* schema: `readStoredValues` drops keys that are
146
146
  // not in the schema it is given, so passing only the top-level fields would
147
147
  // silently discard every section field's persisted value on reload.
148
+ // With `persistValues={false}` storage is never read, so the form starts from
149
+ // `baseDefaultValues` alone.
148
150
  const [persistedDefaults, setPersistedDefaults] = React.useState(() => {
149
151
  var _a;
150
- return normaliseValuesBySchema(flattenedSchema, (_a = readStoredValues(flattenedSchema, storageKey)) !== null && _a !== void 0 ? _a : undefined);
152
+ return shouldPersistValues
153
+ ? normaliseValuesBySchema(flattenedSchema, (_a = readStoredValues(flattenedSchema, storageKey)) !== null && _a !== void 0 ? _a : undefined)
154
+ : {};
151
155
  });
152
156
  // Re-read from localStorage when storageKey changes
153
157
  React.useEffect(() => {
154
158
  var _a;
159
+ if (!shouldPersistValues) {
160
+ return;
161
+ }
155
162
  setPersistedDefaults(normaliseValuesBySchema(flattenedSchema, (_a = readStoredValues(flattenedSchema, storageKey)) !== null && _a !== void 0 ? _a : undefined));
156
- }, [flattenedSchema, storageKey]);
163
+ }, [flattenedSchema, storageKey, shouldPersistValues]);
157
164
  const mergedDefaultValues = React.useMemo(() => (Object.assign(Object.assign({}, baseDefaultValues), persistedDefaults)), [baseDefaultValues, persistedDefaults]);
158
165
  const formMethods = useForm({
159
166
  defaultValues: mergedDefaultValues
@@ -162,12 +169,15 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
162
169
  // --- Filter buttons (quick-filter toggles) ---------------------------------
163
170
  // A button is identified by its `value`, which must be unique across the
164
171
  // whole `filterButtons` array.
165
- const [activeButtonValues, setActiveButtonValues] = React.useState(() => { var _a; return (_a = readStoredButtonValues(storageKey)) !== null && _a !== void 0 ? _a : []; });
172
+ const [activeButtonValues, setActiveButtonValues] = React.useState(() => { var _a; return shouldPersistValues ? ((_a = readStoredButtonValues(storageKey)) !== null && _a !== void 0 ? _a : []) : []; });
166
173
  // Re-read persisted button state when the storage key changes.
167
174
  React.useEffect(() => {
168
175
  var _a;
176
+ if (!shouldPersistValues) {
177
+ return;
178
+ }
169
179
  setActiveButtonValues((_a = readStoredButtonValues(storageKey)) !== null && _a !== void 0 ? _a : []);
170
- }, [storageKey]);
180
+ }, [storageKey, shouldPersistValues]);
171
181
  // Build the `{ key: value[] }` object contributed by the active buttons.
172
182
  // Buttons that share a key are merged into an array (legacy behaviour).
173
183
  const computeButtonValues = React.useCallback((values) => {
@@ -324,13 +334,16 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
324
334
  const persistValues = React.useCallback((values, serialised) => {
325
335
  const nextSerialised = serialised !== null && serialised !== void 0 ? serialised : JSON.stringify(values);
326
336
  lastPersistedValuesRef.current = nextSerialised;
337
+ if (!shouldPersistValues) {
338
+ return nextSerialised;
339
+ }
327
340
  if (Object.keys(values).length === 0) {
328
341
  clearStoredValues(storageKey);
329
342
  return nextSerialised;
330
343
  }
331
344
  writeStoredValues(storageKey, values);
332
345
  return nextSerialised;
333
- }, [storageKey]);
346
+ }, [storageKey, shouldPersistValues]);
334
347
  // Emit the normalised form values merged with the active filter-button
335
348
  // values. Buttons live outside the form state, so every emission point routes
336
349
  // through here to keep the two slices combined in the payload.
@@ -402,10 +415,12 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
402
415
  lastPersistedValuesRef.current = nextSerialised;
403
416
  currentSerialisedValuesRef.current = nextSerialised;
404
417
  // Update persistedDefaults to prevent stale mergedDefaultValues on re-render
405
- setPersistedDefaults(currParsed);
418
+ if (shouldPersistValues) {
419
+ setPersistedDefaults(currParsed);
420
+ }
406
421
  emitValues(currParsed);
407
422
  }
408
- }, [serialisedValues, emitValues, persistValues]);
423
+ }, [serialisedValues, emitValues, persistValues, shouldPersistValues]);
409
424
  const handleClearAll = React.useCallback(() => {
410
425
  const clearedDefaults = Object.assign({}, schemaDefaults);
411
426
  flattenedSchema.forEach(field => {
@@ -518,7 +533,9 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
518
533
  // before the state-update effect runs.
519
534
  buttonValuesRef.current = computeButtonValues(nextValues);
520
535
  setActiveButtonValues(nextValues);
521
- writeStoredButtonValues(storageKey, nextValues);
536
+ if (shouldPersistValues) {
537
+ writeStoredButtonValues(storageKey, nextValues);
538
+ }
522
539
  const formValuesNow = normaliseOutputValues(flattenedSchema, formMethods.getValues());
523
540
  hasEmittedValuesRef.current = true;
524
541
  emitValues(formValuesNow);
@@ -527,6 +544,7 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
527
544
  activeButtonValues,
528
545
  computeButtonValues,
529
546
  storageKey,
547
+ shouldPersistValues,
530
548
  flattenedSchema,
531
549
  formMethods,
532
550
  emitValues,
@@ -586,7 +604,9 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
586
604
  if (activeButtonValues.length > 0) {
587
605
  buttonValuesRef.current = {};
588
606
  setActiveButtonValues([]);
589
- clearStoredButtonValues(storageKey);
607
+ if (shouldPersistValues) {
608
+ clearStoredButtonValues(storageKey);
609
+ }
590
610
  onFilterButtonsChange === null || onFilterButtonsChange === void 0 ? void 0 : onFilterButtonsChange([]);
591
611
  }
592
612
  handleClearAll();
@@ -597,6 +617,7 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
597
617
  onRemoveExternalFilter,
598
618
  activeButtonValues,
599
619
  storageKey,
620
+ shouldPersistValues,
600
621
  onFilterButtonsChange
601
622
  ]);
602
623
  const renderFieldComponent = (field) => {
@@ -637,6 +658,11 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
637
658
  return (React.createElement(Text, { type: "secondary", className: "akinon-filter__unsupported" }, i18n.t('form.unsupportedField', { field: String(field.type) })));
638
659
  }
639
660
  };
661
+ const footerContent = typeof footer === 'function'
662
+ ? footer({
663
+ values: Object.assign(Object.assign({}, normalisedValues), computeButtonValues(activeButtonValues))
664
+ })
665
+ : footer;
640
666
  const renderFormField = (field) => {
641
667
  return (React.createElement(FilterFormItem, { key: String(field.key), control: formMethods.control, name: field.key, tooltip: field.tooltip, help: field.help, labelDescription: field.labelDescription, required: Boolean(field.validation), className: "mb-0", valuePropName: field.type === FIELD_TYPES.CHECKBOX ? 'checked' : undefined }, renderFieldComponent(field)));
642
668
  };
@@ -655,7 +681,8 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
655
681
  visibleFields.length === 0 && regularFields.length > 0 ? (React.createElement("div", { className: "akinon-filter__empty" }, i18n.t('form.noVisibleFields'))) : null),
656
682
  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 &&
657
683
  (file => onSectionImportCsv(String(section.key), file)), onImportXls: onSectionImportXls &&
658
- (file => onSectionImportXls(String(section.key), file)), onVisibleFieldsChange: onSectionVisibleFieldsChange }))))))),
684
+ (file => onSectionImportXls(String(section.key), file)), onVisibleFieldsChange: onSectionVisibleFieldsChange })))))),
685
+ footerContent ? (React.createElement("div", { className: "akinon-filter__footer", "data-testid": "akifilter-footer" }, footerContent)) : null),
659
686
  React.createElement(VisibilityModal, Object.assign({}, modalProps)))));
660
687
  };
661
688
  const AkifilterEmptyState = () => {
@@ -184,6 +184,10 @@
184
184
  color: var(--color-gray-500) !important;
185
185
  }
186
186
 
187
+ .akinon-filter__footer {
188
+ margin-top: 1rem;
189
+ }
190
+
187
191
  @media (max-width: 1024px) {
188
192
  .akinon-filter__form-grid {
189
193
  grid-template-columns: repeat(2, minmax(0, 1fr));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@akinon/akifilter",
3
- "version": "1.10.2-261002-0",
3
+ "version": "1.11.0",
4
4
  "private": false,
5
5
  "description": "Akifilter is a filtering library for Akinon frontend applications.",
6
6
  "type": "module",
@@ -16,20 +16,20 @@
16
16
  "@akinon/akiform": "1.1.9",
17
17
  "@akinon/akidate": "1.2.7",
18
18
  "@akinon/akilocale": "1.2.8",
19
- "@akinon/icons": "1.6.0-261002-0",
20
- "@akinon/ui-button": "1.5.8-261002-0",
19
+ "@akinon/icons": "1.6.0",
20
+ "@akinon/ui-button": "1.5.8",
21
21
  "@akinon/ui-card": "1.2.12",
22
22
  "@akinon/ui-checkbox": "1.4.12",
23
23
  "@akinon/ui-collapse": "1.4.8",
24
- "@akinon/ui-date-picker": "1.5.8-261002-0",
24
+ "@akinon/ui-date-picker": "1.5.8",
25
25
  "@akinon/ui-input": "1.2.12",
26
26
  "@akinon/ui-input-number": "1.4.12",
27
27
  "@akinon/ui-modal": "1.2.13",
28
- "@akinon/ui-pagination": "1.4.19-261002-0",
29
- "@akinon/ui-select": "1.4.19-261002-0",
28
+ "@akinon/ui-pagination": "1.4.19",
29
+ "@akinon/ui-select": "1.4.19",
30
30
  "@akinon/ui-space": "1.4.12",
31
31
  "@akinon/ui-typography": "1.2.7",
32
- "@akinon/ui-upload": "1.6.5-261002-0"
32
+ "@akinon/ui-upload": "1.6.5"
33
33
  },
34
34
  "devDependencies": {
35
35
  "@types/lodash-es": "^4.17.12",
@@ -37,7 +37,7 @@
37
37
  "copyfiles": "^2.4.1",
38
38
  "rimraf": "^5.0.5",
39
39
  "typescript": "*",
40
- "@akinon/akiform-builder": "1.8.4-261002-0",
40
+ "@akinon/akiform-builder": "1.8.4",
41
41
  "@akinon/typescript-config": "1.1.8",
42
42
  "@akinon/utils": "1.2.13",
43
43
  "@akinon/ui-theme": "1.3.3",