@akinon/akifilter 1.0.5 → 1.1.1-rc.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.
@@ -1 +1 @@
1
- {"version":3,"file":"akifilter.d.ts","sourceRoot":"","sources":["../../src/akifilter.tsx"],"names":[],"mappings":"AAAA,OAAO,cAAc,CAAC;AAGtB,OAAO,EAIL,WAAW,EAEX,IAAI,EAGL,MAAM,iBAAiB,CAAC;AAUzB,OAAO,KAAK,MAAM,OAAO,CAAC;AA2B1B,OAAO,KAAK,EAAkB,eAAe,EAAE,MAAM,SAAS,CAAC;AAU/D,KAAK,oBAAoB,GAAG,WAAW,CAAC;AA2BxC,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;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,IAAI,CAAC;IACzB;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,IAAI,CAAC;IACzB;;OAEG;IACH,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;IACxB;;OAEG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B;;OAEG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B,CAAC;AAqnBF,eAAO,MAAM,SAAS;KACpB,YAAY,SAAS,oBAAoB,uBAElC,cAAc,CAAC,YAAY,CAAC;;CAmBpC,CAAC"}
1
+ {"version":3,"file":"akifilter.d.ts","sourceRoot":"","sources":["../../src/akifilter.tsx"],"names":[],"mappings":"AAAA,OAAO,cAAc,CAAC;AAGtB,OAAO,EAIL,WAAW,EAEX,IAAI,EAGL,MAAM,iBAAiB,CAAC;AAWzB,OAAO,KAAK,MAAM,OAAO,CAAC;AA2B1B,OAAO,KAAK,EAAkB,eAAe,EAAE,MAAM,SAAS,CAAC;AAY/D,KAAK,oBAAoB,GAAG,WAAW,CAAC;AA2BxC,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;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,IAAI,CAAC;IACzB;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,IAAI,CAAC;IACzB;;OAEG;IACH,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;IACxB;;OAEG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B;;OAEG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B,CAAC;AAirBF,eAAO,MAAM,SAAS;KACpB,YAAY,SAAS,oBAAoB,uBAElC,cAAc,CAAC,YAAY,CAAC;;CAmBpC,CAAC"}
@@ -18,6 +18,7 @@ const akiform_1 = require("@akinon/akiform");
18
18
  const ui_button_1 = require("@akinon/ui-button");
19
19
  const ui_card_1 = require("@akinon/ui-card");
20
20
  const ui_checkbox_1 = require("@akinon/ui-checkbox");
21
+ const ui_collapse_1 = require("@akinon/ui-collapse");
21
22
  const ui_date_picker_1 = require("@akinon/ui-date-picker");
22
23
  const ui_input_1 = require("@akinon/ui-input");
23
24
  const ui_input_number_1 = require("@akinon/ui-input-number");
@@ -55,14 +56,18 @@ const resolveClearedFieldValue = (field, defaultValue) => {
55
56
  }
56
57
  };
57
58
  const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onValuesChange, onVisibleFieldsChange, onImportCsv, onImportXls, onClearAll, enableImportCsv, enableImportXls }) => {
58
- const storageKey = react_1.default.useMemo(() => (0, storage_1.buildStorageKey)(filterSchema, storageNamespace), [filterSchema, storageNamespace]);
59
- const schemaDefaults = react_1.default.useMemo(() => (0, schema_1.normaliseValuesBySchema)(filterSchema, (0, schema_1.extractDefaultValues)(filterSchema)), [filterSchema]);
60
- const externalDefaults = react_1.default.useMemo(() => (0, schema_1.normaliseValuesBySchema)(filterSchema, defaultValues), [filterSchema, defaultValues]);
59
+ // Separate regular fields from section fields
60
+ const { regularFields, sectionFields } = react_1.default.useMemo(() => (0, schema_1.partitionSchema)(filterSchema), [filterSchema]);
61
+ // Flatten schema for storage and form value operations
62
+ const flattenedSchema = react_1.default.useMemo(() => (0, schema_1.flattenSchema)(filterSchema), [filterSchema]);
63
+ const storageKey = react_1.default.useMemo(() => (0, storage_1.buildStorageKey)(regularFields, storageNamespace), [regularFields, storageNamespace]);
64
+ const schemaDefaults = react_1.default.useMemo(() => (0, schema_1.normaliseValuesBySchema)(flattenedSchema, (0, schema_1.extractDefaultValues)(flattenedSchema)), [flattenedSchema]);
65
+ const externalDefaults = react_1.default.useMemo(() => (0, schema_1.normaliseValuesBySchema)(flattenedSchema, defaultValues), [flattenedSchema, defaultValues]);
61
66
  const baseDefaultValues = react_1.default.useMemo(() => (Object.assign(Object.assign({}, schemaDefaults), externalDefaults)), [schemaDefaults, externalDefaults]);
62
67
  const persistedDefaults = react_1.default.useMemo(() => {
63
68
  var _a;
64
- return (0, schema_1.normaliseValuesBySchema)(filterSchema, (_a = (0, storage_1.readStoredValues)(filterSchema, storageKey)) !== null && _a !== void 0 ? _a : undefined);
65
- }, [filterSchema, storageKey]);
69
+ return (0, schema_1.normaliseValuesBySchema)(flattenedSchema, (_a = (0, storage_1.readStoredValues)(regularFields, storageKey)) !== null && _a !== void 0 ? _a : undefined);
70
+ }, [flattenedSchema, regularFields, storageKey]);
66
71
  const mergedDefaultValues = react_1.default.useMemo(() => (Object.assign(Object.assign({}, baseDefaultValues), persistedDefaults)), [baseDefaultValues, persistedDefaults]);
67
72
  const formMethods = (0, akiform_1.useForm)({
68
73
  defaultValues: mergedDefaultValues
@@ -76,7 +81,7 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
76
81
  if (!formValues) {
77
82
  return [];
78
83
  }
79
- return filterSchema.reduce((acc, field) => {
84
+ return flattenedSchema.reduce((acc, field) => {
80
85
  const key = String(field.key);
81
86
  const currentValue = formValues[key];
82
87
  if (currentValue === undefined || currentValue === null) {
@@ -132,24 +137,24 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
132
137
  acc.push({ key, label, value: resolveValue() });
133
138
  return acc;
134
139
  }, []);
135
- }, [filterSchema, formValues]);
140
+ }, [flattenedSchema, formValues]);
136
141
  const resolveInitialVisibleKeys = react_1.default.useCallback(() => {
137
- const storedKeys = (0, storage_1.readVisibleKeys)(filterSchema, storageKey);
142
+ const storedKeys = (0, storage_1.readVisibleKeys)(regularFields, storageKey);
138
143
  if (storedKeys && storedKeys.length > 0) {
139
- return (0, schema_1.ensureSchemaOrder)(filterSchema, storedKeys);
144
+ return (0, schema_1.ensureSchemaOrder)(regularFields, storedKeys);
140
145
  }
141
- return (0, schema_1.deriveDefaultVisibleKeys)(filterSchema);
142
- }, [filterSchema, storageKey]);
146
+ return (0, schema_1.deriveDefaultVisibleKeys)(regularFields);
147
+ }, [regularFields, storageKey]);
143
148
  const [visibleKeys, setVisibleKeys] = react_1.default.useState(resolveInitialVisibleKeys);
144
149
  react_1.default.useEffect(() => {
145
150
  setVisibleKeys(previous => {
146
- const ordered = (0, schema_1.ensureSchemaOrder)(filterSchema, previous);
151
+ const ordered = (0, schema_1.ensureSchemaOrder)(regularFields, previous);
147
152
  if (ordered.length) {
148
153
  return ordered;
149
154
  }
150
155
  return resolveInitialVisibleKeys();
151
156
  });
152
- }, [filterSchema, resolveInitialVisibleKeys]);
157
+ }, [regularFields, resolveInitialVisibleKeys]);
153
158
  react_1.default.useEffect(() => {
154
159
  (0, storage_1.writeVisibleKeys)(storageKey, visibleKeys);
155
160
  onVisibleFieldsChange === null || onVisibleFieldsChange === void 0 ? void 0 : onVisibleFieldsChange(visibleKeys);
@@ -157,12 +162,12 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
157
162
  react_1.default.useEffect(() => {
158
163
  formMethods.reset(mergedDefaultValues);
159
164
  }, [formMethods, mergedDefaultValues]);
160
- const normalisedValues = react_1.default.useMemo(() => (0, values_1.normaliseOutputValues)(filterSchema, formValues), [filterSchema, formValues]);
165
+ const normalisedValues = react_1.default.useMemo(() => (0, values_1.normaliseOutputValues)(flattenedSchema, formValues), [flattenedSchema, formValues]);
161
166
  const hasInitialValuesRef = react_1.default.useRef(false);
162
167
  react_1.default.useEffect(() => {
163
- const initialValues = (0, values_1.normaliseOutputValues)(filterSchema, mergedDefaultValues);
168
+ const initialValues = (0, values_1.normaliseOutputValues)(flattenedSchema, mergedDefaultValues);
164
169
  hasInitialValuesRef.current = Object.keys(initialValues).length > 0;
165
- }, [filterSchema, mergedDefaultValues]);
170
+ }, [flattenedSchema, mergedDefaultValues]);
166
171
  const serialisedValues = react_1.default.useMemo(() => JSON.stringify(normalisedValues), [normalisedValues]);
167
172
  const debouncedSerialisedValues = (0, use_debounced_value_1.useDebouncedValue)(serialisedValues, constants_1.FILTER_DEBOUNCE_DELAY);
168
173
  const lastPersistedValuesRef = react_1.default.useRef(null);
@@ -214,7 +219,7 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
214
219
  }, [storageKey]);
215
220
  const handleClearAll = react_1.default.useCallback(() => {
216
221
  const clearedDefaults = Object.assign({}, baseDefaultValues);
217
- filterSchema.forEach(field => {
222
+ flattenedSchema.forEach(field => {
218
223
  const key = String(field.key);
219
224
  const defaultValue = baseDefaultValues[key];
220
225
  const resolved = resolveClearedFieldValue(field, defaultValue);
@@ -230,13 +235,20 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
230
235
  keepTouched: false
231
236
  });
232
237
  onClearAll === null || onClearAll === void 0 ? void 0 : onClearAll();
233
- const nextValues = (0, values_1.normaliseOutputValues)(filterSchema, clearedDefaults);
238
+ const nextValues = (0, values_1.normaliseOutputValues)(flattenedSchema, clearedDefaults);
234
239
  hasInitialValuesRef.current = Object.keys(nextValues).length > 0;
235
240
  const nextSerialised = persistValues(nextValues);
236
241
  hasEmittedValuesRef.current = true;
237
242
  lastPersistedValuesRef.current = nextSerialised !== null && nextSerialised !== void 0 ? nextSerialised : null;
238
243
  onValuesChange === null || onValuesChange === void 0 ? void 0 : onValuesChange(nextValues);
239
- }, [baseDefaultValues, filterSchema, formMethods, onClearAll, persistValues]);
244
+ }, [
245
+ baseDefaultValues,
246
+ flattenedSchema,
247
+ formMethods,
248
+ onClearAll,
249
+ onValuesChange,
250
+ persistValues
251
+ ]);
240
252
  const handleOpenModal = react_1.default.useCallback(() => {
241
253
  setIsModalOpen(true);
242
254
  setSearchTerm('');
@@ -254,11 +266,11 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
254
266
  const nextKeys = exists
255
267
  ? prev.filter(item => item !== key)
256
268
  : [...prev, key];
257
- return (0, schema_1.ensureSchemaOrder)(filterSchema, nextKeys);
269
+ return (0, schema_1.ensureSchemaOrder)(regularFields, nextKeys);
258
270
  });
259
- }, [filterSchema]);
271
+ }, [regularFields]);
260
272
  const handleRemoveFilter = react_1.default.useCallback((key) => {
261
- const schemaField = filterSchema.find(field => String(field.key) === key);
273
+ const schemaField = flattenedSchema.find(field => String(field.key) === key);
262
274
  if (!schemaField) {
263
275
  return;
264
276
  }
@@ -274,7 +286,7 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
274
286
  // Compute the updated values immediately (don't wait for debounce)
275
287
  const currentValues = formMethods.getValues() || {};
276
288
  const updatedFormValues = Object.assign(Object.assign({}, currentValues), { [fieldPath]: nextValue });
277
- const nextValues = (0, values_1.normaliseOutputValues)(filterSchema, updatedFormValues);
289
+ const nextValues = (0, values_1.normaliseOutputValues)(flattenedSchema, updatedFormValues);
278
290
  const nextSerialised = JSON.stringify(nextValues);
279
291
  // Persist immediately (bypass debounce for remove action)
280
292
  persistValues(nextValues);
@@ -287,7 +299,7 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
287
299
  onValuesChange === null || onValuesChange === void 0 ? void 0 : onValuesChange(nextValues);
288
300
  }, [
289
301
  baseDefaultValues,
290
- filterSchema,
302
+ flattenedSchema,
291
303
  formMethods,
292
304
  onValuesChange,
293
305
  persistValues
@@ -295,20 +307,20 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
295
307
  const filteredFields = react_1.default.useMemo(() => {
296
308
  const normalisedTerm = searchTerm.trim().toLowerCase();
297
309
  if (!normalisedTerm) {
298
- return filterSchema;
310
+ return regularFields;
299
311
  }
300
- return filterSchema.filter(field => {
312
+ return regularFields.filter(field => {
301
313
  const searchable = [field.label, field.placeholder, String(field.key)]
302
314
  .filter(Boolean)
303
315
  .map(value => String(value).toLowerCase());
304
316
  return searchable.some(text => text.includes(normalisedTerm));
305
317
  });
306
- }, [filterSchema, searchTerm]);
318
+ }, [regularFields, searchTerm]);
307
319
  const paginatedFields = react_1.default.useMemo(() => {
308
320
  const start = (modalPage - 1) * modalPageSize;
309
321
  return filteredFields.slice(start, start + modalPageSize);
310
322
  }, [filteredFields, modalPage, modalPageSize]);
311
- const visibleFields = react_1.default.useMemo(() => filterSchema.filter(field => visibleKeys.includes(String(field.key))), [filterSchema, visibleKeys]);
323
+ const visibleFields = react_1.default.useMemo(() => regularFields.filter(field => visibleKeys.includes(String(field.key))), [regularFields, visibleKeys]);
312
324
  const renderFieldComponent = (field) => {
313
325
  const ariaLabel = (0, schema_1.getFieldAriaLabel)(field);
314
326
  switch (field.type) {
@@ -335,10 +347,17 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
335
347
  });
336
348
  }
337
349
  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) })));
350
+ case 'section':
351
+ // Section fields should not be rendered inline
352
+ // They are rendered separately after the main grid
353
+ return null;
338
354
  default:
339
355
  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) })));
340
356
  }
341
357
  };
358
+ const renderFormField = (field) => {
359
+ return (react_1.default.createElement(akiform_1.FormItem, { 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 === 'checkbox' ? 'checked' : undefined }, renderFieldComponent(field)));
360
+ };
342
361
  return (react_1.default.createElement(ui_card_1.Card, { size: "small", className: "akinon-filter shadow", "data-testid": "akifilter-root" },
343
362
  react_1.default.createElement(antd_1.ConfigProvider, { theme: theme_overrides_1.themeOverrides },
344
363
  react_1.default.createElement(filter_toolbar_1.FilterToolbar, { onOpenModal: handleOpenModal, enableImportCsv: enableImportCsv, enableImportXls: enableImportXls, onImportCsv: onImportCsv, onImportXls: onImportXls }),
@@ -346,8 +365,17 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
346
365
  react_1.default.createElement("div", { className: "akinon-filter__body" },
347
366
  react_1.default.createElement(akiform_1.Akiform, { layout: "vertical", className: "akinon-filter__form" },
348
367
  react_1.default.createElement("div", { className: "akinon-filter__form-grid", "data-testid": "akifilter-form-grid" },
349
- visibleFields.map(field => (react_1.default.createElement(akiform_1.FormItem, { 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 === 'checkbox' ? 'checked' : undefined }, renderFieldComponent(field)))),
350
- visibleFields.length === 0 ? (react_1.default.createElement("div", { className: "akinon-filter__empty" }, i18n_1.i18n.t('form.noVisibleFields'))) : null))),
368
+ visibleFields.map(renderFormField),
369
+ visibleFields.length === 0 ? (react_1.default.createElement("div", { className: "akinon-filter__empty" }, i18n_1.i18n.t('form.noVisibleFields'))) : null),
370
+ sectionFields.length > 0 && (react_1.default.createElement("div", { className: "akinon-filter__section-fields" }, sectionFields.map(section => {
371
+ return (react_1.default.createElement(ui_collapse_1.Collapse, { key: section.key, defaultActiveKey: section.key, ghost: true, items: [
372
+ {
373
+ key: section.key,
374
+ label: section.label,
375
+ children: (react_1.default.createElement("div", { className: "akinon-filter__form-grid" }, section.fields.map(renderFormField)))
376
+ }
377
+ ] }));
378
+ }))))),
351
379
  react_1.default.createElement(visibility_modal_1.VisibilityModal, { open: isModalOpen, onClose: handleCloseModal, searchTerm: searchTerm, paginatedFields: paginatedFields, filteredCount: filteredFields.length, visibleKeys: visibleKeys, onSearchTermChange: value => {
352
380
  setSearchTerm(value);
353
381
  setModalPage(1);
@@ -51,6 +51,15 @@
51
51
  top: 1px;
52
52
  }
53
53
 
54
+ .akinon-filter__common-filters {
55
+ margin-top: 2rem;
56
+ }
57
+
58
+ .akinon-filter__common-filters-title {
59
+ color: var(--color-gray-500);
60
+ margin-bottom: 1rem;
61
+ }
62
+
54
63
  .akinon-filter__form-grid {
55
64
  display: grid;
56
65
  grid-template-columns: repeat(4, minmax(0, 1fr));
@@ -65,6 +74,27 @@
65
74
  width: 100%;
66
75
  }
67
76
 
77
+ .akinon-filter__section-fields {
78
+ margin-top: 2rem;
79
+ display: flex;
80
+ flex-direction: column;
81
+ gap: 1rem;
82
+ }
83
+
84
+ .akinon-filter__section-fields .akinon-collapse-header {
85
+ padding-bottom: 0 !important;
86
+ }
87
+
88
+ .akinon-filter__section-fields .akinon-collapse-header-text {
89
+ color: var(--color-gray-500);
90
+ }
91
+
92
+ .akinon-filter__section-fields .akinon-collapse-expand-icon .akinon-collapse-arrow {
93
+ font-size: 1rem !important;
94
+ color: var(--color-gray-500) !important;
95
+ }
96
+
97
+
68
98
  @media (max-width: 1024px) {
69
99
  .akinon-filter__form-grid {
70
100
  grid-template-columns: repeat(2, minmax(0, 1fr));
@@ -1,5 +1,19 @@
1
1
  import type { FieldValues } from '@akinon/akiform';
2
2
  import type { AkifilterField, AkifilterSchema } from '../types';
3
+ /**
4
+ * Flattens schema by extracting all nested fields from section fields.
5
+ * This is used for storage operations and form value management.
6
+ */
7
+ export declare const flattenSchema: <TFieldValues extends FieldValues = FieldValues>(schema: AkifilterSchema<TFieldValues>) => AkifilterSchema<TFieldValues>;
8
+ /**
9
+ * Separates section fields from regular fields.
10
+ */
11
+ export declare const partitionSchema: <TFieldValues extends FieldValues = FieldValues>(schema: AkifilterSchema<TFieldValues>) => {
12
+ regularFields: AkifilterSchema<TFieldValues>;
13
+ sectionFields: Array<AkifilterField<TFieldValues> & {
14
+ fields: AkifilterSchema<TFieldValues>;
15
+ }>;
16
+ };
3
17
  export declare const getDisplayLabel: <TFieldValues extends FieldValues = FieldValues>(field: AkifilterField<TFieldValues>) => string;
4
18
  export declare const getFieldAriaLabel: <TFieldValues extends FieldValues = FieldValues>(field: AkifilterField<TFieldValues>) => string;
5
19
  export declare const ensureSchemaOrder: <TFieldValues extends FieldValues = FieldValues>(schema: AkifilterSchema<TFieldValues>, keys: string[]) => string[];
@@ -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;AAGnD,OAAO,KAAK,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAEhE,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;AAEF,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,uBAAuB,GAClC,YAAY,SAAS,WAAW,GAAG,WAAW,EAE9C,QAAQ,eAAe,CAAC,YAAY,CAAC,EACrC,SAAS,OAAO,CAAC,YAAY,CAAC,KAC7B,OAAO,CAAC,YAAY,CA+BtB,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;AAGnD,OAAO,KAAK,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAEhE;;;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,CAClB,cAAc,CAAC,YAAY,CAAC,GAAG;QAC7B,MAAM,EAAE,eAAe,CAAC,YAAY,CAAC,CAAC;KACvC,CACF,CAAC;CAwBH,CAAC;AAEF,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;AAEF,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,uBAAuB,GAClC,YAAY,SAAS,WAAW,GAAG,WAAW,EAE9C,QAAQ,eAAe,CAAC,YAAY,CAAC,EACrC,SAAS,OAAO,CAAC,YAAY,CAAC,KAC7B,OAAO,CAAC,YAAY,CA+BtB,CAAC"}
@@ -1,8 +1,42 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.normaliseValuesBySchema = exports.extractDefaultValues = exports.deriveDefaultVisibleKeys = exports.ensureSchemaOrder = exports.getFieldAriaLabel = exports.getDisplayLabel = void 0;
3
+ exports.normaliseValuesBySchema = exports.extractDefaultValues = exports.deriveDefaultVisibleKeys = exports.ensureSchemaOrder = exports.getFieldAriaLabel = exports.getDisplayLabel = exports.partitionSchema = exports.flattenSchema = void 0;
4
4
  const akidate_1 = require("@akinon/akidate");
5
5
  const constants_1 = require("../constants");
6
+ /**
7
+ * Flattens schema by extracting all nested fields from section fields.
8
+ * This is used for storage operations and form value management.
9
+ */
10
+ const flattenSchema = (schema) => {
11
+ return schema.reduce((acc, field) => {
12
+ if (field.type === 'section' && 'fields' in field) {
13
+ // Recursively flatten nested section fields
14
+ return [
15
+ ...acc,
16
+ ...(0, exports.flattenSchema)(field.fields)
17
+ ];
18
+ }
19
+ return [...acc, field];
20
+ }, []);
21
+ };
22
+ exports.flattenSchema = flattenSchema;
23
+ /**
24
+ * Separates section fields from regular fields.
25
+ */
26
+ const partitionSchema = (schema) => {
27
+ const regularFields = [];
28
+ const sectionFields = [];
29
+ schema.forEach(field => {
30
+ if (field.type === 'section') {
31
+ sectionFields.push(field);
32
+ }
33
+ else {
34
+ regularFields.push(field);
35
+ }
36
+ });
37
+ return { regularFields, sectionFields };
38
+ };
39
+ exports.partitionSchema = partitionSchema;
6
40
  const getDisplayLabel = (field) => {
7
41
  return field.label || field.placeholder || String(field.key);
8
42
  };
@@ -1 +1 @@
1
- {"version":3,"file":"akifilter.d.ts","sourceRoot":"","sources":["../../src/akifilter.tsx"],"names":[],"mappings":"AAAA,OAAO,cAAc,CAAC;AAGtB,OAAO,EAIL,WAAW,EAEX,IAAI,EAGL,MAAM,iBAAiB,CAAC;AAUzB,OAAO,KAAK,MAAM,OAAO,CAAC;AA2B1B,OAAO,KAAK,EAAkB,eAAe,EAAE,MAAM,SAAS,CAAC;AAU/D,KAAK,oBAAoB,GAAG,WAAW,CAAC;AA2BxC,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;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,IAAI,CAAC;IACzB;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,IAAI,CAAC;IACzB;;OAEG;IACH,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;IACxB;;OAEG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B;;OAEG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B,CAAC;AAqnBF,eAAO,MAAM,SAAS;KACpB,YAAY,SAAS,oBAAoB,uBAElC,cAAc,CAAC,YAAY,CAAC;;CAmBpC,CAAC"}
1
+ {"version":3,"file":"akifilter.d.ts","sourceRoot":"","sources":["../../src/akifilter.tsx"],"names":[],"mappings":"AAAA,OAAO,cAAc,CAAC;AAGtB,OAAO,EAIL,WAAW,EAEX,IAAI,EAGL,MAAM,iBAAiB,CAAC;AAWzB,OAAO,KAAK,MAAM,OAAO,CAAC;AA2B1B,OAAO,KAAK,EAAkB,eAAe,EAAE,MAAM,SAAS,CAAC;AAY/D,KAAK,oBAAoB,GAAG,WAAW,CAAC;AA2BxC,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;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,IAAI,CAAC;IACzB;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,IAAI,CAAC;IACzB;;OAEG;IACH,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;IACxB;;OAEG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B;;OAEG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B,CAAC;AAirBF,eAAO,MAAM,SAAS;KACpB,YAAY,SAAS,oBAAoB,uBAElC,cAAc,CAAC,YAAY,CAAC;;CAmBpC,CAAC"}
@@ -15,6 +15,7 @@ import { Akiform, FormItem, useForm, useWatch } from '@akinon/akiform';
15
15
  import { Button } from '@akinon/ui-button';
16
16
  import { Card } from '@akinon/ui-card';
17
17
  import { Checkbox } from '@akinon/ui-checkbox';
18
+ import { Collapse } from '@akinon/ui-collapse';
18
19
  import { DatePicker } from '@akinon/ui-date-picker';
19
20
  import { Input, InputTextArea } from '@akinon/ui-input';
20
21
  import { InputNumber } from '@akinon/ui-input-number';
@@ -31,7 +32,7 @@ import { VisibilityModal } from './components/visibility-modal';
31
32
  import { BOOLEAN_STRING, DATE_FORMAT, DEFAULT_MODAL_PAGE_SIZE, FILTER_DEBOUNCE_DELAY } from './constants';
32
33
  import { useDebouncedValue } from './hooks/use-debounced-value';
33
34
  import { i18n } from './i18n';
34
- import { deriveDefaultVisibleKeys, ensureSchemaOrder, extractDefaultValues, getFieldAriaLabel, normaliseValuesBySchema } from './utils/schema';
35
+ import { deriveDefaultVisibleKeys, ensureSchemaOrder, extractDefaultValues, flattenSchema, getFieldAriaLabel, normaliseValuesBySchema, partitionSchema } from './utils/schema';
35
36
  import { normaliseOutputValues } from './utils/values';
36
37
  const resolveClearedFieldValue = (field, defaultValue) => {
37
38
  if (defaultValue !== undefined) {
@@ -52,14 +53,18 @@ const resolveClearedFieldValue = (field, defaultValue) => {
52
53
  }
53
54
  };
54
55
  const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onValuesChange, onVisibleFieldsChange, onImportCsv, onImportXls, onClearAll, enableImportCsv, enableImportXls }) => {
55
- const storageKey = React.useMemo(() => buildStorageKey(filterSchema, storageNamespace), [filterSchema, storageNamespace]);
56
- const schemaDefaults = React.useMemo(() => normaliseValuesBySchema(filterSchema, extractDefaultValues(filterSchema)), [filterSchema]);
57
- const externalDefaults = React.useMemo(() => normaliseValuesBySchema(filterSchema, defaultValues), [filterSchema, defaultValues]);
56
+ // Separate regular fields from section fields
57
+ const { regularFields, sectionFields } = React.useMemo(() => partitionSchema(filterSchema), [filterSchema]);
58
+ // Flatten schema for storage and form value operations
59
+ const flattenedSchema = React.useMemo(() => flattenSchema(filterSchema), [filterSchema]);
60
+ const storageKey = React.useMemo(() => buildStorageKey(regularFields, storageNamespace), [regularFields, storageNamespace]);
61
+ const schemaDefaults = React.useMemo(() => normaliseValuesBySchema(flattenedSchema, extractDefaultValues(flattenedSchema)), [flattenedSchema]);
62
+ const externalDefaults = React.useMemo(() => normaliseValuesBySchema(flattenedSchema, defaultValues), [flattenedSchema, defaultValues]);
58
63
  const baseDefaultValues = React.useMemo(() => (Object.assign(Object.assign({}, schemaDefaults), externalDefaults)), [schemaDefaults, externalDefaults]);
59
64
  const persistedDefaults = React.useMemo(() => {
60
65
  var _a;
61
- return normaliseValuesBySchema(filterSchema, (_a = readStoredValues(filterSchema, storageKey)) !== null && _a !== void 0 ? _a : undefined);
62
- }, [filterSchema, storageKey]);
66
+ return normaliseValuesBySchema(flattenedSchema, (_a = readStoredValues(regularFields, storageKey)) !== null && _a !== void 0 ? _a : undefined);
67
+ }, [flattenedSchema, regularFields, storageKey]);
63
68
  const mergedDefaultValues = React.useMemo(() => (Object.assign(Object.assign({}, baseDefaultValues), persistedDefaults)), [baseDefaultValues, persistedDefaults]);
64
69
  const formMethods = useForm({
65
70
  defaultValues: mergedDefaultValues
@@ -73,7 +78,7 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
73
78
  if (!formValues) {
74
79
  return [];
75
80
  }
76
- return filterSchema.reduce((acc, field) => {
81
+ return flattenedSchema.reduce((acc, field) => {
77
82
  const key = String(field.key);
78
83
  const currentValue = formValues[key];
79
84
  if (currentValue === undefined || currentValue === null) {
@@ -129,24 +134,24 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
129
134
  acc.push({ key, label, value: resolveValue() });
130
135
  return acc;
131
136
  }, []);
132
- }, [filterSchema, formValues]);
137
+ }, [flattenedSchema, formValues]);
133
138
  const resolveInitialVisibleKeys = React.useCallback(() => {
134
- const storedKeys = readVisibleKeys(filterSchema, storageKey);
139
+ const storedKeys = readVisibleKeys(regularFields, storageKey);
135
140
  if (storedKeys && storedKeys.length > 0) {
136
- return ensureSchemaOrder(filterSchema, storedKeys);
141
+ return ensureSchemaOrder(regularFields, storedKeys);
137
142
  }
138
- return deriveDefaultVisibleKeys(filterSchema);
139
- }, [filterSchema, storageKey]);
143
+ return deriveDefaultVisibleKeys(regularFields);
144
+ }, [regularFields, storageKey]);
140
145
  const [visibleKeys, setVisibleKeys] = React.useState(resolveInitialVisibleKeys);
141
146
  React.useEffect(() => {
142
147
  setVisibleKeys(previous => {
143
- const ordered = ensureSchemaOrder(filterSchema, previous);
148
+ const ordered = ensureSchemaOrder(regularFields, previous);
144
149
  if (ordered.length) {
145
150
  return ordered;
146
151
  }
147
152
  return resolveInitialVisibleKeys();
148
153
  });
149
- }, [filterSchema, resolveInitialVisibleKeys]);
154
+ }, [regularFields, resolveInitialVisibleKeys]);
150
155
  React.useEffect(() => {
151
156
  writeVisibleKeys(storageKey, visibleKeys);
152
157
  onVisibleFieldsChange === null || onVisibleFieldsChange === void 0 ? void 0 : onVisibleFieldsChange(visibleKeys);
@@ -154,12 +159,12 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
154
159
  React.useEffect(() => {
155
160
  formMethods.reset(mergedDefaultValues);
156
161
  }, [formMethods, mergedDefaultValues]);
157
- const normalisedValues = React.useMemo(() => normaliseOutputValues(filterSchema, formValues), [filterSchema, formValues]);
162
+ const normalisedValues = React.useMemo(() => normaliseOutputValues(flattenedSchema, formValues), [flattenedSchema, formValues]);
158
163
  const hasInitialValuesRef = React.useRef(false);
159
164
  React.useEffect(() => {
160
- const initialValues = normaliseOutputValues(filterSchema, mergedDefaultValues);
165
+ const initialValues = normaliseOutputValues(flattenedSchema, mergedDefaultValues);
161
166
  hasInitialValuesRef.current = Object.keys(initialValues).length > 0;
162
- }, [filterSchema, mergedDefaultValues]);
167
+ }, [flattenedSchema, mergedDefaultValues]);
163
168
  const serialisedValues = React.useMemo(() => JSON.stringify(normalisedValues), [normalisedValues]);
164
169
  const debouncedSerialisedValues = useDebouncedValue(serialisedValues, FILTER_DEBOUNCE_DELAY);
165
170
  const lastPersistedValuesRef = React.useRef(null);
@@ -211,7 +216,7 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
211
216
  }, [storageKey]);
212
217
  const handleClearAll = React.useCallback(() => {
213
218
  const clearedDefaults = Object.assign({}, baseDefaultValues);
214
- filterSchema.forEach(field => {
219
+ flattenedSchema.forEach(field => {
215
220
  const key = String(field.key);
216
221
  const defaultValue = baseDefaultValues[key];
217
222
  const resolved = resolveClearedFieldValue(field, defaultValue);
@@ -227,13 +232,20 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
227
232
  keepTouched: false
228
233
  });
229
234
  onClearAll === null || onClearAll === void 0 ? void 0 : onClearAll();
230
- const nextValues = normaliseOutputValues(filterSchema, clearedDefaults);
235
+ const nextValues = normaliseOutputValues(flattenedSchema, clearedDefaults);
231
236
  hasInitialValuesRef.current = Object.keys(nextValues).length > 0;
232
237
  const nextSerialised = persistValues(nextValues);
233
238
  hasEmittedValuesRef.current = true;
234
239
  lastPersistedValuesRef.current = nextSerialised !== null && nextSerialised !== void 0 ? nextSerialised : null;
235
240
  onValuesChange === null || onValuesChange === void 0 ? void 0 : onValuesChange(nextValues);
236
- }, [baseDefaultValues, filterSchema, formMethods, onClearAll, persistValues]);
241
+ }, [
242
+ baseDefaultValues,
243
+ flattenedSchema,
244
+ formMethods,
245
+ onClearAll,
246
+ onValuesChange,
247
+ persistValues
248
+ ]);
237
249
  const handleOpenModal = React.useCallback(() => {
238
250
  setIsModalOpen(true);
239
251
  setSearchTerm('');
@@ -251,11 +263,11 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
251
263
  const nextKeys = exists
252
264
  ? prev.filter(item => item !== key)
253
265
  : [...prev, key];
254
- return ensureSchemaOrder(filterSchema, nextKeys);
266
+ return ensureSchemaOrder(regularFields, nextKeys);
255
267
  });
256
- }, [filterSchema]);
268
+ }, [regularFields]);
257
269
  const handleRemoveFilter = React.useCallback((key) => {
258
- const schemaField = filterSchema.find(field => String(field.key) === key);
270
+ const schemaField = flattenedSchema.find(field => String(field.key) === key);
259
271
  if (!schemaField) {
260
272
  return;
261
273
  }
@@ -271,7 +283,7 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
271
283
  // Compute the updated values immediately (don't wait for debounce)
272
284
  const currentValues = formMethods.getValues() || {};
273
285
  const updatedFormValues = Object.assign(Object.assign({}, currentValues), { [fieldPath]: nextValue });
274
- const nextValues = normaliseOutputValues(filterSchema, updatedFormValues);
286
+ const nextValues = normaliseOutputValues(flattenedSchema, updatedFormValues);
275
287
  const nextSerialised = JSON.stringify(nextValues);
276
288
  // Persist immediately (bypass debounce for remove action)
277
289
  persistValues(nextValues);
@@ -284,7 +296,7 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
284
296
  onValuesChange === null || onValuesChange === void 0 ? void 0 : onValuesChange(nextValues);
285
297
  }, [
286
298
  baseDefaultValues,
287
- filterSchema,
299
+ flattenedSchema,
288
300
  formMethods,
289
301
  onValuesChange,
290
302
  persistValues
@@ -292,20 +304,20 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
292
304
  const filteredFields = React.useMemo(() => {
293
305
  const normalisedTerm = searchTerm.trim().toLowerCase();
294
306
  if (!normalisedTerm) {
295
- return filterSchema;
307
+ return regularFields;
296
308
  }
297
- return filterSchema.filter(field => {
309
+ return regularFields.filter(field => {
298
310
  const searchable = [field.label, field.placeholder, String(field.key)]
299
311
  .filter(Boolean)
300
312
  .map(value => String(value).toLowerCase());
301
313
  return searchable.some(text => text.includes(normalisedTerm));
302
314
  });
303
- }, [filterSchema, searchTerm]);
315
+ }, [regularFields, searchTerm]);
304
316
  const paginatedFields = React.useMemo(() => {
305
317
  const start = (modalPage - 1) * modalPageSize;
306
318
  return filteredFields.slice(start, start + modalPageSize);
307
319
  }, [filteredFields, modalPage, modalPageSize]);
308
- const visibleFields = React.useMemo(() => filterSchema.filter(field => visibleKeys.includes(String(field.key))), [filterSchema, visibleKeys]);
320
+ const visibleFields = React.useMemo(() => regularFields.filter(field => visibleKeys.includes(String(field.key))), [regularFields, visibleKeys]);
309
321
  const renderFieldComponent = (field) => {
310
322
  const ariaLabel = getFieldAriaLabel(field);
311
323
  switch (field.type) {
@@ -332,10 +344,17 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
332
344
  });
333
345
  }
334
346
  return (React.createElement(Text, { type: "secondary", className: "akinon-filter__unsupported" }, i18n.t('form.unsupportedField', { field: String(field.type) })));
347
+ case 'section':
348
+ // Section fields should not be rendered inline
349
+ // They are rendered separately after the main grid
350
+ return null;
335
351
  default:
336
352
  return (React.createElement(Text, { type: "secondary", className: "akinon-filter__unsupported" }, i18n.t('form.unsupportedField', { field: String(field.type) })));
337
353
  }
338
354
  };
355
+ const renderFormField = (field) => {
356
+ return (React.createElement(FormItem, { 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 === 'checkbox' ? 'checked' : undefined }, renderFieldComponent(field)));
357
+ };
339
358
  return (React.createElement(Card, { size: "small", className: "akinon-filter shadow", "data-testid": "akifilter-root" },
340
359
  React.createElement(ConfigProvider, { theme: themeOverrides },
341
360
  React.createElement(FilterToolbar, { onOpenModal: handleOpenModal, enableImportCsv: enableImportCsv, enableImportXls: enableImportXls, onImportCsv: onImportCsv, onImportXls: onImportXls }),
@@ -343,8 +362,17 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
343
362
  React.createElement("div", { className: "akinon-filter__body" },
344
363
  React.createElement(Akiform, { layout: "vertical", className: "akinon-filter__form" },
345
364
  React.createElement("div", { className: "akinon-filter__form-grid", "data-testid": "akifilter-form-grid" },
346
- visibleFields.map(field => (React.createElement(FormItem, { 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 === 'checkbox' ? 'checked' : undefined }, renderFieldComponent(field)))),
347
- visibleFields.length === 0 ? (React.createElement("div", { className: "akinon-filter__empty" }, i18n.t('form.noVisibleFields'))) : null))),
365
+ visibleFields.map(renderFormField),
366
+ visibleFields.length === 0 ? (React.createElement("div", { className: "akinon-filter__empty" }, i18n.t('form.noVisibleFields'))) : null),
367
+ sectionFields.length > 0 && (React.createElement("div", { className: "akinon-filter__section-fields" }, sectionFields.map(section => {
368
+ return (React.createElement(Collapse, { key: section.key, defaultActiveKey: section.key, ghost: true, items: [
369
+ {
370
+ key: section.key,
371
+ label: section.label,
372
+ children: (React.createElement("div", { className: "akinon-filter__form-grid" }, section.fields.map(renderFormField)))
373
+ }
374
+ ] }));
375
+ }))))),
348
376
  React.createElement(VisibilityModal, { open: isModalOpen, onClose: handleCloseModal, searchTerm: searchTerm, paginatedFields: paginatedFields, filteredCount: filteredFields.length, visibleKeys: visibleKeys, onSearchTermChange: value => {
349
377
  setSearchTerm(value);
350
378
  setModalPage(1);
@@ -51,6 +51,15 @@
51
51
  top: 1px;
52
52
  }
53
53
 
54
+ .akinon-filter__common-filters {
55
+ margin-top: 2rem;
56
+ }
57
+
58
+ .akinon-filter__common-filters-title {
59
+ color: var(--color-gray-500);
60
+ margin-bottom: 1rem;
61
+ }
62
+
54
63
  .akinon-filter__form-grid {
55
64
  display: grid;
56
65
  grid-template-columns: repeat(4, minmax(0, 1fr));
@@ -65,6 +74,27 @@
65
74
  width: 100%;
66
75
  }
67
76
 
77
+ .akinon-filter__section-fields {
78
+ margin-top: 2rem;
79
+ display: flex;
80
+ flex-direction: column;
81
+ gap: 1rem;
82
+ }
83
+
84
+ .akinon-filter__section-fields .akinon-collapse-header {
85
+ padding-bottom: 0 !important;
86
+ }
87
+
88
+ .akinon-filter__section-fields .akinon-collapse-header-text {
89
+ color: var(--color-gray-500);
90
+ }
91
+
92
+ .akinon-filter__section-fields .akinon-collapse-expand-icon .akinon-collapse-arrow {
93
+ font-size: 1rem !important;
94
+ color: var(--color-gray-500) !important;
95
+ }
96
+
97
+
68
98
  @media (max-width: 1024px) {
69
99
  .akinon-filter__form-grid {
70
100
  grid-template-columns: repeat(2, minmax(0, 1fr));
@@ -1,5 +1,19 @@
1
1
  import type { FieldValues } from '@akinon/akiform';
2
2
  import type { AkifilterField, AkifilterSchema } from '../types';
3
+ /**
4
+ * Flattens schema by extracting all nested fields from section fields.
5
+ * This is used for storage operations and form value management.
6
+ */
7
+ export declare const flattenSchema: <TFieldValues extends FieldValues = FieldValues>(schema: AkifilterSchema<TFieldValues>) => AkifilterSchema<TFieldValues>;
8
+ /**
9
+ * Separates section fields from regular fields.
10
+ */
11
+ export declare const partitionSchema: <TFieldValues extends FieldValues = FieldValues>(schema: AkifilterSchema<TFieldValues>) => {
12
+ regularFields: AkifilterSchema<TFieldValues>;
13
+ sectionFields: Array<AkifilterField<TFieldValues> & {
14
+ fields: AkifilterSchema<TFieldValues>;
15
+ }>;
16
+ };
3
17
  export declare const getDisplayLabel: <TFieldValues extends FieldValues = FieldValues>(field: AkifilterField<TFieldValues>) => string;
4
18
  export declare const getFieldAriaLabel: <TFieldValues extends FieldValues = FieldValues>(field: AkifilterField<TFieldValues>) => string;
5
19
  export declare const ensureSchemaOrder: <TFieldValues extends FieldValues = FieldValues>(schema: AkifilterSchema<TFieldValues>, keys: string[]) => string[];
@@ -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;AAGnD,OAAO,KAAK,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAEhE,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;AAEF,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,uBAAuB,GAClC,YAAY,SAAS,WAAW,GAAG,WAAW,EAE9C,QAAQ,eAAe,CAAC,YAAY,CAAC,EACrC,SAAS,OAAO,CAAC,YAAY,CAAC,KAC7B,OAAO,CAAC,YAAY,CA+BtB,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;AAGnD,OAAO,KAAK,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAEhE;;;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,CAClB,cAAc,CAAC,YAAY,CAAC,GAAG;QAC7B,MAAM,EAAE,eAAe,CAAC,YAAY,CAAC,CAAC;KACvC,CACF,CAAC;CAwBH,CAAC;AAEF,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;AAEF,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,uBAAuB,GAClC,YAAY,SAAS,WAAW,GAAG,WAAW,EAE9C,QAAQ,eAAe,CAAC,YAAY,CAAC,EACrC,SAAS,OAAO,CAAC,YAAY,CAAC,KAC7B,OAAO,CAAC,YAAY,CA+BtB,CAAC"}
@@ -1,5 +1,37 @@
1
1
  import { akidate } from '@akinon/akidate';
2
2
  import { DEFAULT_VISIBLE_COUNT } from '../constants';
3
+ /**
4
+ * Flattens schema by extracting all nested fields from section fields.
5
+ * This is used for storage operations and form value management.
6
+ */
7
+ export const flattenSchema = (schema) => {
8
+ return schema.reduce((acc, field) => {
9
+ if (field.type === 'section' && 'fields' in field) {
10
+ // Recursively flatten nested section fields
11
+ return [
12
+ ...acc,
13
+ ...flattenSchema(field.fields)
14
+ ];
15
+ }
16
+ return [...acc, field];
17
+ }, []);
18
+ };
19
+ /**
20
+ * Separates section fields from regular fields.
21
+ */
22
+ export const partitionSchema = (schema) => {
23
+ const regularFields = [];
24
+ const sectionFields = [];
25
+ schema.forEach(field => {
26
+ if (field.type === 'section') {
27
+ sectionFields.push(field);
28
+ }
29
+ else {
30
+ regularFields.push(field);
31
+ }
32
+ });
33
+ return { regularFields, sectionFields };
34
+ };
3
35
  export const getDisplayLabel = (field) => {
4
36
  return field.label || field.placeholder || String(field.key);
5
37
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@akinon/akifilter",
3
- "version": "1.0.5",
3
+ "version": "1.1.1-rc.0",
4
4
  "private": false,
5
5
  "description": "Akifilter is a filtering library for Akinon frontend applications.",
6
6
  "type": "module",
@@ -14,30 +14,31 @@
14
14
  "react-error-boundary": "^6.0.0",
15
15
  "@akinon/akiform": "1.1.2",
16
16
  "@akinon/akidate": "1.1.2",
17
+ "@akinon/icons": "1.1.2-rc.0",
17
18
  "@akinon/akilocale": "1.2.1",
18
- "@akinon/icons": "1.1.1",
19
- "@akinon/ui-button": "1.3.2",
20
- "@akinon/ui-checkbox": "1.3.2",
21
- "@akinon/ui-card": "1.1.2",
22
- "@akinon/ui-input": "1.1.2",
23
- "@akinon/ui-input-number": "1.3.2",
24
- "@akinon/ui-modal": "1.1.2",
25
- "@akinon/ui-select": "1.3.3",
26
- "@akinon/ui-pagination": "1.3.3",
27
- "@akinon/ui-space": "1.3.2",
19
+ "@akinon/ui-button": "1.4.0-rc.0",
20
+ "@akinon/ui-card": "1.1.3-rc.0",
21
+ "@akinon/ui-checkbox": "1.3.3-rc.0",
22
+ "@akinon/ui-date-picker": "1.3.3-rc.0",
23
+ "@akinon/ui-collapse": "1.3.2-rc.0",
24
+ "@akinon/ui-input": "1.1.3-rc.0",
28
25
  "@akinon/ui-typography": "1.1.1",
29
- "@akinon/ui-date-picker": "1.3.2"
26
+ "@akinon/ui-input-number": "1.3.3-rc.0",
27
+ "@akinon/ui-select": "1.3.4-rc.0",
28
+ "@akinon/ui-pagination": "1.3.4-rc.0",
29
+ "@akinon/ui-modal": "1.1.3-rc.0",
30
+ "@akinon/ui-space": "1.3.3-rc.0"
30
31
  },
31
32
  "devDependencies": {
32
33
  "clean-package": "2.2.0",
33
34
  "copyfiles": "^2.4.1",
34
35
  "rimraf": "^5.0.5",
35
36
  "typescript": "*",
36
- "@akinon/akiform-builder": "1.3.4",
37
- "@akinon/typescript-config": "1.1.1",
38
- "@akinon/ui-theme": "1.1.2",
37
+ "@akinon/akiform-builder": "1.3.5-rc.0",
38
+ "@akinon/utils": "1.1.4-rc.0",
39
+ "@akinon/ui-theme": "1.1.3-rc.0",
39
40
  "@akinon/vitest-config": "1.1.1",
40
- "@akinon/utils": "1.1.3"
41
+ "@akinon/typescript-config": "1.1.1"
41
42
  },
42
43
  "peerDependencies": {
43
44
  "react": "^18 || ^19",