@akinon/akifilter 1.4.4-260521-3 → 1.6.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,5 +1,6 @@
1
1
  import './styles.css';
2
2
  import { FieldValues, Path } from '@akinon/akiform';
3
+ import { type PanelProps } from '@akinon/ui-collapse';
3
4
  import React from 'react';
4
5
  import { type AppliedFilter } from './components/applied-filters';
5
6
  import type { AkifilterActionsRef, AkifilterSchema } from './types';
@@ -61,6 +62,15 @@ export type AkifilterProps<TFieldValues extends AkifilterFieldValues = Akifilter
61
62
  */
62
63
  onRemoveExternalFilter?: (key: string) => void;
63
64
  };
65
+ export declare const ExpandIcon: ({ isActive }: PanelProps) => {
66
+ name: "chevron_down";
67
+ size: number;
68
+ style: {
69
+ transform: string;
70
+ transition: string;
71
+ marginTop: number;
72
+ };
73
+ };
64
74
  export declare const Akifilter: {
65
75
  <TFieldValues extends AkifilterFieldValues = FieldValues>(props: AkifilterProps<TFieldValues>): React.JSX.Element;
66
76
  displayName: string;
@@ -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;AAazB,OAAO,KAAK,MAAM,OAAO,CAAC;AAa1B,OAAO,EACL,KAAK,aAAa,EAEnB,MAAM,8BAA8B,CAAC;AAYtC,OAAO,KAAK,EACV,mBAAmB,EAEnB,eAAe,EAChB,MAAM,SAAS,CAAC;AAajB,KAAK,oBAAoB,GAAG,WAAW,CAAC;AA8JxC,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;CAChD,CAAC;AAu1BF,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,EACX,IAAI,EAIL,MAAM,iBAAiB,CAAC;AAIzB,OAAO,EAAY,KAAK,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAShE,OAAO,KAAK,MAAM,OAAO,CAAC;AAa1B,OAAO,EACL,KAAK,aAAa,EAEnB,MAAM,8BAA8B,CAAC;AActC,OAAO,KAAK,EACV,mBAAmB,EAEnB,eAAe,EAChB,MAAM,SAAS,CAAC;AAcjB,KAAK,oBAAoB,GAAG,WAAW,CAAC;AAoKxC,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;CAChD,CAAC;AA4zBF,eAAO,MAAM,UAAU,GAAI,cAAc,UAAU;;;;;;;;CAQjD,CAAC;AA8CH,eAAO,MAAM,SAAS;KACpB,YAAY,SAAS,oBAAoB,uBAElC,cAAc,CAAC,YAAY,CAAC;;CAmBpC,CAAC"}
@@ -11,7 +11,7 @@ var __rest = (this && this.__rest) || function (s, e) {
11
11
  return t;
12
12
  };
13
13
  Object.defineProperty(exports, "__esModule", { value: true });
14
- exports.Akifilter = void 0;
14
+ exports.Akifilter = exports.ExpandIcon = void 0;
15
15
  require("./styles.css");
16
16
  const akidate_1 = require("@akinon/akidate");
17
17
  const akiform_1 = require("@akinon/akiform");
@@ -97,20 +97,24 @@ const resolveClearedFieldValue = (field, defaultValue) => {
97
97
  return defaultValue;
98
98
  }
99
99
  switch (field.type) {
100
- case 'text':
101
- case 'textarea':
100
+ case constants_1.FIELD_TYPES.TEXT:
101
+ case constants_1.FIELD_TYPES.TEXTAREA:
102
102
  return '';
103
- case 'checkbox':
103
+ case constants_1.FIELD_TYPES.CHECKBOX:
104
104
  return false;
105
- case 'number':
106
- case 'date':
105
+ case constants_1.FIELD_TYPES.NUMBER:
106
+ case constants_1.FIELD_TYPES.DATE:
107
107
  return null;
108
- case 'select':
109
- if (field.mode === 'multiple' || field.mode === 'tags') {
108
+ case constants_1.FIELD_TYPES.SELECT:
109
+ if (field.mode === constants_1.FIELD_MODES.MULTIPLE ||
110
+ field.mode === constants_1.FIELD_MODES.TAGS) {
110
111
  return [];
111
112
  }
112
113
  return null;
113
- case 'file':
114
+ // Custom fields must clear to `null`, not `undefined` (PUF-5666): otherwise
115
+ // react-hook-form rewrites the cleared value as a "zombie" and resurrects the filter.
116
+ case constants_1.FIELD_TYPES.FILE:
117
+ case constants_1.FIELD_TYPES.CUSTOM:
114
118
  return null;
115
119
  default:
116
120
  return undefined;
@@ -119,6 +123,7 @@ const resolveClearedFieldValue = (field, defaultValue) => {
119
123
  const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onValuesChange, onVisibleFieldsChange, onImportCsv, onImportXls, onClearAll, enableImportCsv, enableImportXls, filterActionsRef, externalAppliedFilters, onRemoveExternalFilter }) => {
120
124
  // Separate regular fields from section fields
121
125
  const { regularFields, sectionFields } = react_1.default.useMemo(() => (0, schema_1.partitionSchema)(filterSchema), [filterSchema]);
126
+ const excludeSectionKeys = react_1.default.useMemo(() => (0, schema_1.deriveExcludeSectionKeys)(sectionFields), [sectionFields]);
122
127
  // Flatten schema for storage and form value operations
123
128
  const flattenedSchema = react_1.default.useMemo(() => (0, schema_1.flattenSchema)(filterSchema), [filterSchema]);
124
129
  const storageKey = react_1.default.useMemo(() => (0, storage_1.buildStorageKey)(regularFields, storageNamespace), [regularFields, storageNamespace]);
@@ -160,7 +165,7 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
160
165
  if (Array.isArray(currentValue) && currentValue.length === 0) {
161
166
  return acc;
162
167
  }
163
- if (field.type === 'checkbox' && currentValue !== true) {
168
+ if (field.type === constants_1.FIELD_TYPES.CHECKBOX && currentValue !== true) {
164
169
  return acc;
165
170
  }
166
171
  const label = field.label || field.placeholder || key;
@@ -183,18 +188,20 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
183
188
  return resolveLabel(currentValue);
184
189
  };
185
190
  const resolveValue = () => {
186
- if (field.type === 'file') {
191
+ if (field.type === constants_1.FIELD_TYPES.FILE) {
187
192
  const fileValue = currentValue;
188
193
  return fileValue instanceof File ? fileValue.name : String(fileValue);
189
194
  }
190
- if (field.type === 'select') {
195
+ if (field.type === constants_1.FIELD_TYPES.SELECT) {
191
196
  return resolveSelectLabel();
192
197
  }
193
198
  // For custom fields with options (like custom selects), try to find the label
194
- if (field.type === 'custom' && 'options' in field && field.options) {
199
+ if (field.type === constants_1.FIELD_TYPES.CUSTOM &&
200
+ 'options' in field &&
201
+ field.options) {
195
202
  return resolveSelectLabel();
196
203
  }
197
- if (field.type === 'date') {
204
+ if (field.type === constants_1.FIELD_TYPES.DATE) {
198
205
  const iso = akidate_1.akidate.toIsoDate(currentValue);
199
206
  if (iso) {
200
207
  // Use localized format with time if showTime is enabled
@@ -221,10 +228,15 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
221
228
  }
222
229
  return String(currentValue);
223
230
  };
224
- acc.push({ key, label, value: resolveValue() });
231
+ acc.push({
232
+ key,
233
+ label,
234
+ value: resolveValue(),
235
+ isExclude: excludeSectionKeys.has(key)
236
+ });
225
237
  return acc;
226
238
  }, []);
227
- }, [flattenedSchema, formValues]);
239
+ }, [flattenedSchema, formValues, excludeSectionKeys]);
228
240
  const resolveInitialVisibleKeys = react_1.default.useCallback(() => {
229
241
  const storedKeys = (0, storage_1.readVisibleKeys)(regularFields, storageKey);
230
242
  const defaultKeys = (0, schema_1.deriveDefaultVisibleKeys)(regularFields);
@@ -260,16 +272,6 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
260
272
  (0, storage_1.writeVisibleKeys)(storageKey, visibleKeys);
261
273
  onVisibleFieldsChange === null || onVisibleFieldsChange === void 0 ? void 0 : onVisibleFieldsChange(visibleKeys);
262
274
  }, [visibleKeys, onVisibleFieldsChange, storageKey]);
263
- // Track previous mergedDefaultValues to avoid unnecessary resets
264
- const previousMergedDefaultValuesRef = react_1.default.useRef(null);
265
- react_1.default.useEffect(() => {
266
- const serialised = JSON.stringify(mergedDefaultValues);
267
- // Skip if the values haven't actually changed (deep comparison)
268
- if (previousMergedDefaultValuesRef.current === serialised) {
269
- return;
270
- }
271
- previousMergedDefaultValuesRef.current = serialised;
272
- }, [mergedDefaultValues]);
273
275
  const normalisedValues = react_1.default.useMemo(() => (0, values_1.normaliseOutputValues)(flattenedSchema, formValues), [flattenedSchema, formValues]);
274
276
  const hasInitialValuesRef = react_1.default.useRef(false);
275
277
  react_1.default.useEffect(() => {
@@ -370,6 +372,9 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
370
372
  clearedDefaults[key] = resolved;
371
373
  }
372
374
  });
375
+ // clearedDefaults carries an explicit cleared value (e.g. `null` for custom/file
376
+ // fields) for every key, so reset overwrites stale values instead of leaving the
377
+ // field untouched and resurrecting the filter (PUF-5666).
373
378
  formMethods.reset(clearedDefaults, {
374
379
  keepDirty: false,
375
380
  keepTouched: false
@@ -422,7 +427,9 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
422
427
  const updatedFormValues = Object.assign(Object.assign({}, currentFormValues), { [String(schemaField.key)]: nextValue });
423
428
  // Normalize the values (removes empty/undefined entries)
424
429
  const nextValues = (0, values_1.normaliseOutputValues)(flattenedSchema, updatedFormValues);
425
- // Update form state
430
+ // updatedFormValues holds an explicit cleared value (e.g. `null` for custom/file
431
+ // fields) for the removed key, so reset overwrites the stale value rather than
432
+ // leaving it in place and resurrecting the filter (PUF-5666).
426
433
  formMethods.reset(updatedFormValues, {
427
434
  keepDirty: false,
428
435
  keepTouched: false,
@@ -498,21 +505,21 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
498
505
  const ariaLabel = (0, schema_1.getFieldAriaLabel)(field);
499
506
  const isDisabled = checkIsDisabled(field, formValues !== null && formValues !== void 0 ? formValues : {});
500
507
  switch (field.type) {
501
- case 'text':
508
+ case constants_1.FIELD_TYPES.TEXT:
502
509
  return (react_1.default.createElement(ui_input_1.Input, { placeholder: field.placeholder, size: "large", allowClear: true, "aria-label": ariaLabel, disabled: isDisabled }));
503
- case 'number':
510
+ case constants_1.FIELD_TYPES.NUMBER:
504
511
  return (react_1.default.createElement(ui_input_number_1.InputNumber, { placeholder: field.placeholder, size: "large", className: "akinon-filter__field--number", "aria-label": ariaLabel, disabled: isDisabled }));
505
- case 'select':
512
+ case constants_1.FIELD_TYPES.SELECT:
506
513
  return (react_1.default.createElement(ui_select_1.Select, { placeholder: field.placeholder, size: "large", options: field.options, showSearch: true, optionFilterProp: "label", "aria-label": ariaLabel, disabled: isDisabled, mode: field.mode }));
507
- case 'checkbox':
514
+ case constants_1.FIELD_TYPES.CHECKBOX:
508
515
  return react_1.default.createElement(ui_checkbox_1.Checkbox, { disabled: isDisabled }, field.label);
509
- case 'date':
516
+ case constants_1.FIELD_TYPES.DATE:
510
517
  return (react_1.default.createElement(ui_date_picker_1.DatePicker, { placeholder: field.placeholder, showTime: field.showTime, suffixIcon: "calendar", suffixIconSize: "16px", "aria-label": ariaLabel, disabled: isDisabled }));
511
- case 'textarea':
518
+ case constants_1.FIELD_TYPES.TEXTAREA:
512
519
  return (react_1.default.createElement(ui_input_1.InputTextArea, { placeholder: field.placeholder, autoSize: { minRows: 3, maxRows: 6 }, "aria-label": ariaLabel, disabled: isDisabled }));
513
- case 'file':
520
+ case constants_1.FIELD_TYPES.FILE:
514
521
  return (react_1.default.createElement(FileFilterInput, { accept: field.fileAccept, "aria-label": ariaLabel, disabled: isDisabled }));
515
- case 'custom':
522
+ case constants_1.FIELD_TYPES.CUSTOM:
516
523
  if (typeof field.render === 'function') {
517
524
  return field.render({
518
525
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -524,7 +531,7 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
524
531
  });
525
532
  }
526
533
  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) })));
527
- case 'section':
534
+ case constants_1.FIELD_TYPES.SECTION:
528
535
  // Section fields should not be rendered inline
529
536
  // They are rendered separately after the main grid
530
537
  return null;
@@ -533,7 +540,7 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
533
540
  }
534
541
  };
535
542
  const renderFormField = (field) => {
536
- 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 === 'checkbox' ? 'checked' : undefined }, renderFieldComponent(field)));
543
+ 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)));
537
544
  };
538
545
  return (react_1.default.createElement(ui_card_1.Card, { size: "small", className: "akinon-filter shadow", "data-testid": "akifilter-root" },
539
546
  react_1.default.createElement(antd_1.ConfigProvider, { theme: theme_overrides_1.themeOverrides },
@@ -545,10 +552,11 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
545
552
  visibleFields.map(renderFormField),
546
553
  visibleFields.length === 0 ? (react_1.default.createElement("div", { className: "akinon-filter__empty" }, i18n_1.i18n.t('form.noVisibleFields'))) : null),
547
554
  sectionFields.length > 0 && (react_1.default.createElement("div", { className: "akinon-filter__section-fields" }, sectionFields.map(section => {
548
- return (react_1.default.createElement(ui_collapse_1.Collapse, { key: section.key, defaultActiveKey: section.key, ghost: true, items: [
555
+ return (react_1.default.createElement(ui_collapse_1.Collapse, { expandIconPosition: "end", key: section.key, expandIcon: exports.ExpandIcon, defaultActiveKey: section.defaultExpanded === false ? [] : [section.key], ghost: true, items: [
549
556
  {
550
557
  key: section.key,
551
- label: section.label,
558
+ label: (react_1.default.createElement(ui_typography_1.Title, { className: "akinon-filter__title", level: 4 }, section.label)),
559
+ headerClass: 'akinon-filter__section-header',
552
560
  children: (react_1.default.createElement("div", { className: "akinon-filter__form-grid" }, section.fields.map(renderFormField)))
553
561
  }
554
562
  ] }));
@@ -563,6 +571,16 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
563
571
  }
564
572
  }, page: modalPage, pageSize: modalPageSize }))));
565
573
  };
574
+ const ExpandIcon = ({ isActive }) => ({
575
+ name: 'chevron_down',
576
+ size: 10,
577
+ style: {
578
+ transform: isActive ? 'rotate(-180deg)' : 'rotate(0deg)',
579
+ transition: 'transform 0.2s ease-in-out',
580
+ marginTop: 8
581
+ }
582
+ });
583
+ exports.ExpandIcon = ExpandIcon;
566
584
  const AkifilterEmptyState = () => {
567
585
  const { t } = i18n_1.i18n;
568
586
  return (react_1.default.createElement(ui_card_1.Card, { size: "small", className: "akinon-filter shadow", "data-testid": "akifilter-empty" },
@@ -3,6 +3,7 @@ type AppliedFilter = {
3
3
  key: string;
4
4
  label: string;
5
5
  value: string;
6
+ isExclude?: boolean;
6
7
  };
7
8
  type AppliedFiltersProps = {
8
9
  filters: AppliedFilter[];
@@ -1 +1 @@
1
- {"version":3,"file":"applied-filters.d.ts","sourceRoot":"","sources":["../../../src/components/applied-filters.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,MAAM,OAAO,CAAC;AAI1B,KAAK,aAAa,GAAG;IACnB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,KAAK,mBAAmB,GAAG;IACzB,OAAO,EAAE,aAAa,EAAE,CAAC;IACzB,QAAQ,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAChC,UAAU,EAAE,MAAM,IAAI,CAAC;CACxB,CAAC;AAEF,eAAO,MAAM,cAAc,GAAI,mCAI5B,mBAAmB,sBAwDrB,CAAC;AAEF,YAAY,EAAE,aAAa,EAAE,CAAC"}
1
+ {"version":3,"file":"applied-filters.d.ts","sourceRoot":"","sources":["../../../src/components/applied-filters.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,MAAM,OAAO,CAAC;AAI1B,KAAK,aAAa,GAAG;IACnB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB,CAAC;AAEF,KAAK,mBAAmB,GAAG;IACzB,OAAO,EAAE,aAAa,EAAE,CAAC;IACzB,QAAQ,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAChC,UAAU,EAAE,MAAM,IAAI,CAAC;CACxB,CAAC;AAEF,eAAO,MAAM,cAAc,GAAI,mCAI5B,mBAAmB,sBA+DrB,CAAC;AAEF,YAAY,EAAE,aAAa,EAAE,CAAC"}
@@ -12,7 +12,8 @@ const AppliedFilters = ({ filters, onRemove, onClearAll }) => {
12
12
  return (react_1.default.createElement("div", { className: "akinon-filter__applied", "data-testid": "akifilter-applied" },
13
13
  react_1.default.createElement(ui_space_1.Space, { className: "akinon-filter__applied-summary", size: 8 },
14
14
  react_1.default.createElement(ui_typography_1.Text, { className: "akinon-filter__applied-label" }, i18n_1.i18n.t('applied.label')),
15
- hasFilters ? (react_1.default.createElement(ui_space_1.Space, { className: "akinon-filter__applied-items", size: 8, wrap: true }, filters.map(item => (react_1.default.createElement("div", { key: `${item.key}-${item.value}`, className: "akinon-filter__chip" },
15
+ hasFilters ? (react_1.default.createElement(ui_space_1.Space, { className: "akinon-filter__applied-items", size: 8, wrap: true }, filters.map(item => (react_1.default.createElement("div", { key: `${item.key}-${item.value}`, className: `akinon-filter__chip${item.isExclude ? ' akinon-filter__chip--exclude' : ''}` },
16
+ item.isExclude ? (react_1.default.createElement(icons_1.Icon, { icon: "exclude", size: 12, className: "akinon-filter__chip-exclude-icon" })) : null,
16
17
  item.label ? (react_1.default.createElement("span", { className: "akinon-filter__chip-label" },
17
18
  item.label,
18
19
  ":")) : null,
@@ -7,4 +7,19 @@ export declare const BOOLEAN_STRING: {
7
7
  readonly TRUE: "true";
8
8
  readonly FALSE: "false";
9
9
  };
10
+ export declare const FIELD_TYPES: {
11
+ readonly TEXT: "text";
12
+ readonly NUMBER: "number";
13
+ readonly SELECT: "select";
14
+ readonly CHECKBOX: "checkbox";
15
+ readonly DATE: "date";
16
+ readonly TEXTAREA: "textarea";
17
+ readonly SECTION: "section";
18
+ readonly CUSTOM: "custom";
19
+ readonly FILE: "file";
20
+ };
21
+ export declare const FIELD_MODES: {
22
+ readonly TAGS: "tags";
23
+ readonly MULTIPLE: "multiple";
24
+ };
10
25
  //# sourceMappingURL=constants.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../src/constants.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,qBAAqB,IAAI,CAAC;AACvC,eAAO,MAAM,uBAAuB,KAAK,CAAC;AAC1C,eAAO,MAAM,qBAAqB,MAAM,CAAC;AAEzC,eAAO,MAAM,WAAW,eAAe,CAAC;AACxC,eAAO,MAAM,eAAe,wBAAwB,CAAC;AAErD,eAAO,MAAM,cAAc;;;CAGjB,CAAC"}
1
+ {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../src/constants.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,qBAAqB,IAAI,CAAC;AACvC,eAAO,MAAM,uBAAuB,KAAK,CAAC;AAC1C,eAAO,MAAM,qBAAqB,MAAM,CAAC;AAEzC,eAAO,MAAM,WAAW,eAAe,CAAC;AACxC,eAAO,MAAM,eAAe,wBAAwB,CAAC;AAErD,eAAO,MAAM,cAAc;;;CAGjB,CAAC;AAEX,eAAO,MAAM,WAAW;;;;;;;;;;CAUd,CAAC;AAEX,eAAO,MAAM,WAAW;;;CAGd,CAAC"}
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.BOOLEAN_STRING = exports.DATETIME_FORMAT = exports.DATE_FORMAT = exports.FILTER_DEBOUNCE_DELAY = exports.DEFAULT_MODAL_PAGE_SIZE = exports.DEFAULT_VISIBLE_COUNT = void 0;
3
+ exports.FIELD_MODES = exports.FIELD_TYPES = exports.BOOLEAN_STRING = exports.DATETIME_FORMAT = exports.DATE_FORMAT = exports.FILTER_DEBOUNCE_DELAY = exports.DEFAULT_MODAL_PAGE_SIZE = exports.DEFAULT_VISIBLE_COUNT = void 0;
4
4
  exports.DEFAULT_VISIBLE_COUNT = 8;
5
5
  exports.DEFAULT_MODAL_PAGE_SIZE = 40;
6
6
  exports.FILTER_DEBOUNCE_DELAY = 300;
@@ -10,3 +10,18 @@ exports.BOOLEAN_STRING = {
10
10
  TRUE: 'true',
11
11
  FALSE: 'false'
12
12
  };
13
+ exports.FIELD_TYPES = {
14
+ TEXT: 'text',
15
+ NUMBER: 'number',
16
+ SELECT: 'select',
17
+ CHECKBOX: 'checkbox',
18
+ DATE: 'date',
19
+ TEXTAREA: 'textarea',
20
+ SECTION: 'section',
21
+ CUSTOM: 'custom',
22
+ FILE: 'file'
23
+ };
24
+ exports.FIELD_MODES = {
25
+ TAGS: 'tags',
26
+ MULTIPLE: 'multiple'
27
+ };
@@ -47,11 +47,11 @@
47
47
  background-color: var(--color-ebonyClay-900);
48
48
  flex: 1;
49
49
  display: flex;
50
- align-items: center;
50
+ align-items: flex-start;
51
51
  border-radius: 5px;
52
52
  padding: 6px 8px;
53
53
  margin-right: 6px;
54
- height: 36px;
54
+ min-height: 36px;
55
55
  }
56
56
 
57
57
  .akinon-filter__applied-label {
@@ -59,16 +59,13 @@
59
59
  font-weight: 600;
60
60
  font-size: 13px;
61
61
  position: relative;
62
- top: 1px;
62
+ white-space: nowrap;
63
+ top: 7px;
63
64
  }
64
65
 
65
- .akinon-filter__common-filters {
66
- margin-top: 2rem;
67
- }
68
-
69
- .akinon-filter__common-filters-title {
70
- color: var(--color-gray-500);
71
- margin-bottom: 1rem;
66
+ .akinon-filter__section-header span {
67
+ margin-inline-end: unset !important;
68
+ flex: unset !important;
72
69
  }
73
70
 
74
71
  .akinon-filter__form-grid {
@@ -121,8 +118,10 @@
121
118
 
122
119
  .akinon-filter__applied-items {
123
120
  display: flex;
121
+ flex: 1;
124
122
  flex-wrap: wrap;
125
123
  gap: 8px;
124
+ min-width: 0;
126
125
  }
127
126
 
128
127
  .akinon-filter__chip {
@@ -136,6 +135,16 @@
136
135
  height: 24px;
137
136
  }
138
137
 
138
+ .akinon-filter__chip--exclude {
139
+ background-color: #ffe0e4;
140
+ border: 1px solid #ff354e;
141
+ height: 23px;
142
+ }
143
+
144
+ .akinon-filter__chip-exclude-icon {
145
+ margin-right: 5px;
146
+ }
147
+
139
148
  .akinon-filter__chip-label {
140
149
  font-weight: 600;
141
150
  color: var(--color-ebonyClay-300);
@@ -162,7 +171,7 @@
162
171
  color: color(--color-ebonyClay-500);
163
172
  font-size: 12px;
164
173
  position: relative;
165
- top: 1px;
174
+ top: 8px;
166
175
  }
167
176
 
168
177
  .akinon-filter__modal-list {
@@ -187,13 +196,13 @@
187
196
  column-gap: 8px; /* replaces Ant's label padding, zeroed out below */
188
197
  line-height: 1.4;
189
198
  margin-inline-start: 0; /* override Ant's adjacent-sibling margin */
199
+ color: var(--color-white);
190
200
  }
191
201
 
192
202
  .akinon-filter__modal-list
193
203
  .akinon-filter__modal-checkbox.ant-checkbox-wrapper
194
204
  > .ant-checkbox {
195
205
  align-self: start;
196
- margin-block-start: 0.15em; /* em-relative baseline nudge */
197
206
  }
198
207
 
199
208
  .akinon-filter__modal-list
@@ -253,6 +262,11 @@
253
262
  width: auto;
254
263
  }
255
264
 
265
+ .akinon-filter .akinon-select-multiple .akinon-select-selector {
266
+ height: 40px;
267
+ overflow: hidden;
268
+ }
269
+
256
270
  /* Modal window. */
257
271
 
258
272
  .akinon-filter__modal-toolbar {
@@ -1,4 +1,5 @@
1
1
  import type { FieldValues } from '@akinon/akiform';
2
+ import { FIELD_TYPES } from '../constants';
2
3
  import type { AkifilterField, AkifilterSchema } from '../types';
3
4
  /**
4
5
  * Flattens schema by extracting all nested fields from section fields.
@@ -11,9 +12,17 @@ export declare const flattenSchema: <TFieldValues extends FieldValues = FieldVal
11
12
  export declare const partitionSchema: <TFieldValues extends FieldValues = FieldValues>(schema: AkifilterSchema<TFieldValues>) => {
12
13
  regularFields: AkifilterSchema<TFieldValues>;
13
14
  sectionFields: Array<AkifilterField<TFieldValues> & {
15
+ type: typeof FIELD_TYPES.SECTION;
14
16
  fields: AkifilterSchema<TFieldValues>;
17
+ defaultExpanded?: boolean;
18
+ isExcludeSection?: boolean;
15
19
  }>;
16
20
  };
21
+ export declare const deriveExcludeSectionKeys: <TFieldValues extends FieldValues = FieldValues>(sectionFields: Array<AkifilterField<TFieldValues> & {
22
+ type: typeof FIELD_TYPES.SECTION;
23
+ fields: AkifilterSchema<TFieldValues>;
24
+ isExcludeSection?: boolean;
25
+ }>) => Set<string>;
17
26
  export declare const getDisplayLabel: <TFieldValues extends FieldValues = FieldValues>(field: AkifilterField<TFieldValues>) => string;
18
27
  export declare const getFieldAriaLabel: <TFieldValues extends FieldValues = FieldValues>(field: AkifilterField<TFieldValues>) => string;
19
28
  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;AAInD,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;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,CAyCtB,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,EAAsC,WAAW,EAAE,MAAM,cAAc,CAAC;AAC/E,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,IAAI,EAAE,OAAO,WAAW,CAAC,OAAO,CAAC;QACjC,MAAM,EAAE,eAAe,CAAC,YAAY,CAAC,CAAC;QACtC,eAAe,CAAC,EAAE,OAAO,CAAC;QAC1B,gBAAgB,CAAC,EAAE,OAAO,CAAC;KAC5B,CACF,CAAC;CAwBH,CAAC;AAEF,eAAO,MAAM,wBAAwB,GACnC,YAAY,SAAS,WAAW,GAAG,WAAW,EAE9C,eAAe,KAAK,CAClB,cAAc,CAAC,YAAY,CAAC,GAAG;IAC7B,IAAI,EAAE,OAAO,WAAW,CAAC,OAAO,CAAC;IACjC,MAAM,EAAE,eAAe,CAAC,YAAY,CAAC,CAAC;IACtC,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B,CACF,KACA,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,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.partitionSchema = exports.flattenSchema = void 0;
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;
4
4
  const akidate_1 = require("@akinon/akidate");
5
5
  const lodash_es_1 = require("lodash-es");
6
6
  const constants_1 = require("../constants");
@@ -10,7 +10,7 @@ const constants_1 = require("../constants");
10
10
  */
11
11
  const flattenSchema = (schema) => {
12
12
  return schema.reduce((acc, field) => {
13
- if (field.type === 'section' && 'fields' in field) {
13
+ if (field.type === constants_1.FIELD_TYPES.SECTION && 'fields' in field) {
14
14
  // Recursively flatten nested section fields
15
15
  return [
16
16
  ...acc,
@@ -28,7 +28,7 @@ const partitionSchema = (schema) => {
28
28
  const regularFields = [];
29
29
  const sectionFields = [];
30
30
  schema.forEach(field => {
31
- if (field.type === 'section') {
31
+ if (field.type === constants_1.FIELD_TYPES.SECTION) {
32
32
  sectionFields.push(field);
33
33
  }
34
34
  else {
@@ -38,6 +38,10 @@ const partitionSchema = (schema) => {
38
38
  return { regularFields, sectionFields };
39
39
  };
40
40
  exports.partitionSchema = partitionSchema;
41
+ const deriveExcludeSectionKeys = (sectionFields) => new Set(sectionFields
42
+ .filter(s => s.isExcludeSection)
43
+ .flatMap(s => s.fields.map(f => String(f.key))));
44
+ exports.deriveExcludeSectionKeys = deriveExcludeSectionKeys;
41
45
  const getDisplayLabel = (field) => {
42
46
  return field.label || field.placeholder || String(field.key);
43
47
  };
@@ -96,15 +100,16 @@ const normaliseValuesBySchema = (schema, values) => {
96
100
  return acc;
97
101
  }
98
102
  const currentValue = values[key];
99
- if (field.type === 'date') {
103
+ if (field.type === constants_1.FIELD_TYPES.DATE) {
100
104
  const parsed = akidate_1.akidate.parse(currentValue);
101
105
  if (parsed) {
102
106
  acc[key] = parsed;
103
107
  return acc;
104
108
  }
105
109
  }
106
- if (field.type === 'select' &&
107
- (field.mode === 'multiple' || field.mode === 'tags') &&
110
+ if (field.type === constants_1.FIELD_TYPES.SELECT &&
111
+ (field.mode === constants_1.FIELD_MODES.MULTIPLE ||
112
+ field.mode === constants_1.FIELD_MODES.TAGS) &&
108
113
  currentValue != null &&
109
114
  !Array.isArray(currentValue)) {
110
115
  acc[key] = [currentValue];
@@ -13,10 +13,10 @@ const shouldPersistValue = (field, value) => {
13
13
  if (Array.isArray(value) && value.length === 0) {
14
14
  return false;
15
15
  }
16
- if ((field === null || field === void 0 ? void 0 : field.type) === 'checkbox' && value !== true) {
16
+ if ((field === null || field === void 0 ? void 0 : field.type) === constants_1.FIELD_TYPES.CHECKBOX && value !== true) {
17
17
  return false;
18
18
  }
19
- if ((field === null || field === void 0 ? void 0 : field.type) === 'file') {
19
+ if ((field === null || field === void 0 ? void 0 : field.type) === constants_1.FIELD_TYPES.FILE) {
20
20
  return value instanceof File;
21
21
  }
22
22
  return true;
@@ -33,7 +33,7 @@ const normaliseOutputValues = (schema, values) => {
33
33
  return acc;
34
34
  }
35
35
  let normalisedValue = rawValue;
36
- if ((field === null || field === void 0 ? void 0 : field.type) === 'date') {
36
+ if ((field === null || field === void 0 ? void 0 : field.type) === constants_1.FIELD_TYPES.DATE) {
37
37
  const normalised = akidate_1.akidate.toIsoDate(rawValue);
38
38
  if (!normalised) {
39
39
  return acc;
@@ -1,5 +1,6 @@
1
1
  import './styles.css';
2
2
  import { FieldValues, Path } from '@akinon/akiform';
3
+ import { type PanelProps } from '@akinon/ui-collapse';
3
4
  import React from 'react';
4
5
  import { type AppliedFilter } from './components/applied-filters';
5
6
  import type { AkifilterActionsRef, AkifilterSchema } from './types';
@@ -61,6 +62,15 @@ export type AkifilterProps<TFieldValues extends AkifilterFieldValues = Akifilter
61
62
  */
62
63
  onRemoveExternalFilter?: (key: string) => void;
63
64
  };
65
+ export declare const ExpandIcon: ({ isActive }: PanelProps) => {
66
+ name: "chevron_down";
67
+ size: number;
68
+ style: {
69
+ transform: string;
70
+ transition: string;
71
+ marginTop: number;
72
+ };
73
+ };
64
74
  export declare const Akifilter: {
65
75
  <TFieldValues extends AkifilterFieldValues = FieldValues>(props: AkifilterProps<TFieldValues>): React.JSX.Element;
66
76
  displayName: string;
@@ -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;AAazB,OAAO,KAAK,MAAM,OAAO,CAAC;AAa1B,OAAO,EACL,KAAK,aAAa,EAEnB,MAAM,8BAA8B,CAAC;AAYtC,OAAO,KAAK,EACV,mBAAmB,EAEnB,eAAe,EAChB,MAAM,SAAS,CAAC;AAajB,KAAK,oBAAoB,GAAG,WAAW,CAAC;AA8JxC,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;CAChD,CAAC;AAu1BF,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,EACX,IAAI,EAIL,MAAM,iBAAiB,CAAC;AAIzB,OAAO,EAAY,KAAK,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAShE,OAAO,KAAK,MAAM,OAAO,CAAC;AAa1B,OAAO,EACL,KAAK,aAAa,EAEnB,MAAM,8BAA8B,CAAC;AActC,OAAO,KAAK,EACV,mBAAmB,EAEnB,eAAe,EAChB,MAAM,SAAS,CAAC;AAcjB,KAAK,oBAAoB,GAAG,WAAW,CAAC;AAoKxC,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;CAChD,CAAC;AA4zBF,eAAO,MAAM,UAAU,GAAI,cAAc,UAAU;;;;;;;;CAQjD,CAAC;AA8CH,eAAO,MAAM,SAAS;KACpB,YAAY,SAAS,oBAAoB,uBAElC,cAAc,CAAC,YAAY,CAAC;;CAmBpC,CAAC"}
@@ -30,12 +30,12 @@ import { themeOverrides } from './common/theme-overrides';
30
30
  import { AppliedFilters } from './components/applied-filters';
31
31
  import { FilterToolbar } from './components/filter-toolbar';
32
32
  import { VisibilityModal } from './components/visibility-modal';
33
- import { BOOLEAN_STRING, DEFAULT_MODAL_PAGE_SIZE, FILTER_DEBOUNCE_DELAY } from './constants';
33
+ import { BOOLEAN_STRING, DEFAULT_MODAL_PAGE_SIZE, FIELD_MODES, FIELD_TYPES, FILTER_DEBOUNCE_DELAY } from './constants';
34
34
  import { useDebouncedValue } from './hooks/use-debounced-value';
35
35
  import { useDynamicVisibility } from './hooks/use-dynamic-visibility';
36
36
  import { useVisibilityCleanup } from './hooks/use-visibility-cleanup';
37
37
  import { i18n } from './i18n';
38
- import { deriveDefaultVisibleKeys, ensureSchemaOrder, extractDefaultValues, flattenSchema, getFieldAriaLabel, normaliseValuesBySchema, partitionSchema, resolveFieldVisibility } from './utils/schema';
38
+ import { deriveDefaultVisibleKeys, deriveExcludeSectionKeys, ensureSchemaOrder, extractDefaultValues, flattenSchema, getFieldAriaLabel, normaliseValuesBySchema, partitionSchema, resolveFieldVisibility } from './utils/schema';
39
39
  import { normaliseOutputValues } from './utils/values';
40
40
  const FilterFormItem = (_a) => {
41
41
  var _b, _c;
@@ -94,20 +94,24 @@ const resolveClearedFieldValue = (field, defaultValue) => {
94
94
  return defaultValue;
95
95
  }
96
96
  switch (field.type) {
97
- case 'text':
98
- case 'textarea':
97
+ case FIELD_TYPES.TEXT:
98
+ case FIELD_TYPES.TEXTAREA:
99
99
  return '';
100
- case 'checkbox':
100
+ case FIELD_TYPES.CHECKBOX:
101
101
  return false;
102
- case 'number':
103
- case 'date':
102
+ case FIELD_TYPES.NUMBER:
103
+ case FIELD_TYPES.DATE:
104
104
  return null;
105
- case 'select':
106
- if (field.mode === 'multiple' || field.mode === 'tags') {
105
+ case FIELD_TYPES.SELECT:
106
+ if (field.mode === FIELD_MODES.MULTIPLE ||
107
+ field.mode === FIELD_MODES.TAGS) {
107
108
  return [];
108
109
  }
109
110
  return null;
110
- case 'file':
111
+ // Custom fields must clear to `null`, not `undefined` (PUF-5666): otherwise
112
+ // react-hook-form rewrites the cleared value as a "zombie" and resurrects the filter.
113
+ case FIELD_TYPES.FILE:
114
+ case FIELD_TYPES.CUSTOM:
111
115
  return null;
112
116
  default:
113
117
  return undefined;
@@ -116,6 +120,7 @@ const resolveClearedFieldValue = (field, defaultValue) => {
116
120
  const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onValuesChange, onVisibleFieldsChange, onImportCsv, onImportXls, onClearAll, enableImportCsv, enableImportXls, filterActionsRef, externalAppliedFilters, onRemoveExternalFilter }) => {
117
121
  // Separate regular fields from section fields
118
122
  const { regularFields, sectionFields } = React.useMemo(() => partitionSchema(filterSchema), [filterSchema]);
123
+ const excludeSectionKeys = React.useMemo(() => deriveExcludeSectionKeys(sectionFields), [sectionFields]);
119
124
  // Flatten schema for storage and form value operations
120
125
  const flattenedSchema = React.useMemo(() => flattenSchema(filterSchema), [filterSchema]);
121
126
  const storageKey = React.useMemo(() => buildStorageKey(regularFields, storageNamespace), [regularFields, storageNamespace]);
@@ -157,7 +162,7 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
157
162
  if (Array.isArray(currentValue) && currentValue.length === 0) {
158
163
  return acc;
159
164
  }
160
- if (field.type === 'checkbox' && currentValue !== true) {
165
+ if (field.type === FIELD_TYPES.CHECKBOX && currentValue !== true) {
161
166
  return acc;
162
167
  }
163
168
  const label = field.label || field.placeholder || key;
@@ -180,18 +185,20 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
180
185
  return resolveLabel(currentValue);
181
186
  };
182
187
  const resolveValue = () => {
183
- if (field.type === 'file') {
188
+ if (field.type === FIELD_TYPES.FILE) {
184
189
  const fileValue = currentValue;
185
190
  return fileValue instanceof File ? fileValue.name : String(fileValue);
186
191
  }
187
- if (field.type === 'select') {
192
+ if (field.type === FIELD_TYPES.SELECT) {
188
193
  return resolveSelectLabel();
189
194
  }
190
195
  // For custom fields with options (like custom selects), try to find the label
191
- if (field.type === 'custom' && 'options' in field && field.options) {
196
+ if (field.type === FIELD_TYPES.CUSTOM &&
197
+ 'options' in field &&
198
+ field.options) {
192
199
  return resolveSelectLabel();
193
200
  }
194
- if (field.type === 'date') {
201
+ if (field.type === FIELD_TYPES.DATE) {
195
202
  const iso = akidate.toIsoDate(currentValue);
196
203
  if (iso) {
197
204
  // Use localized format with time if showTime is enabled
@@ -218,10 +225,15 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
218
225
  }
219
226
  return String(currentValue);
220
227
  };
221
- acc.push({ key, label, value: resolveValue() });
228
+ acc.push({
229
+ key,
230
+ label,
231
+ value: resolveValue(),
232
+ isExclude: excludeSectionKeys.has(key)
233
+ });
222
234
  return acc;
223
235
  }, []);
224
- }, [flattenedSchema, formValues]);
236
+ }, [flattenedSchema, formValues, excludeSectionKeys]);
225
237
  const resolveInitialVisibleKeys = React.useCallback(() => {
226
238
  const storedKeys = readVisibleKeys(regularFields, storageKey);
227
239
  const defaultKeys = deriveDefaultVisibleKeys(regularFields);
@@ -257,16 +269,6 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
257
269
  writeVisibleKeys(storageKey, visibleKeys);
258
270
  onVisibleFieldsChange === null || onVisibleFieldsChange === void 0 ? void 0 : onVisibleFieldsChange(visibleKeys);
259
271
  }, [visibleKeys, onVisibleFieldsChange, storageKey]);
260
- // Track previous mergedDefaultValues to avoid unnecessary resets
261
- const previousMergedDefaultValuesRef = React.useRef(null);
262
- React.useEffect(() => {
263
- const serialised = JSON.stringify(mergedDefaultValues);
264
- // Skip if the values haven't actually changed (deep comparison)
265
- if (previousMergedDefaultValuesRef.current === serialised) {
266
- return;
267
- }
268
- previousMergedDefaultValuesRef.current = serialised;
269
- }, [mergedDefaultValues]);
270
272
  const normalisedValues = React.useMemo(() => normaliseOutputValues(flattenedSchema, formValues), [flattenedSchema, formValues]);
271
273
  const hasInitialValuesRef = React.useRef(false);
272
274
  React.useEffect(() => {
@@ -367,6 +369,9 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
367
369
  clearedDefaults[key] = resolved;
368
370
  }
369
371
  });
372
+ // clearedDefaults carries an explicit cleared value (e.g. `null` for custom/file
373
+ // fields) for every key, so reset overwrites stale values instead of leaving the
374
+ // field untouched and resurrecting the filter (PUF-5666).
370
375
  formMethods.reset(clearedDefaults, {
371
376
  keepDirty: false,
372
377
  keepTouched: false
@@ -419,7 +424,9 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
419
424
  const updatedFormValues = Object.assign(Object.assign({}, currentFormValues), { [String(schemaField.key)]: nextValue });
420
425
  // Normalize the values (removes empty/undefined entries)
421
426
  const nextValues = normaliseOutputValues(flattenedSchema, updatedFormValues);
422
- // Update form state
427
+ // updatedFormValues holds an explicit cleared value (e.g. `null` for custom/file
428
+ // fields) for the removed key, so reset overwrites the stale value rather than
429
+ // leaving it in place and resurrecting the filter (PUF-5666).
423
430
  formMethods.reset(updatedFormValues, {
424
431
  keepDirty: false,
425
432
  keepTouched: false,
@@ -495,21 +502,21 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
495
502
  const ariaLabel = getFieldAriaLabel(field);
496
503
  const isDisabled = checkIsDisabled(field, formValues !== null && formValues !== void 0 ? formValues : {});
497
504
  switch (field.type) {
498
- case 'text':
505
+ case FIELD_TYPES.TEXT:
499
506
  return (React.createElement(Input, { placeholder: field.placeholder, size: "large", allowClear: true, "aria-label": ariaLabel, disabled: isDisabled }));
500
- case 'number':
507
+ case FIELD_TYPES.NUMBER:
501
508
  return (React.createElement(InputNumber, { placeholder: field.placeholder, size: "large", className: "akinon-filter__field--number", "aria-label": ariaLabel, disabled: isDisabled }));
502
- case 'select':
509
+ case FIELD_TYPES.SELECT:
503
510
  return (React.createElement(Select, { placeholder: field.placeholder, size: "large", options: field.options, showSearch: true, optionFilterProp: "label", "aria-label": ariaLabel, disabled: isDisabled, mode: field.mode }));
504
- case 'checkbox':
511
+ case FIELD_TYPES.CHECKBOX:
505
512
  return React.createElement(Checkbox, { disabled: isDisabled }, field.label);
506
- case 'date':
513
+ case FIELD_TYPES.DATE:
507
514
  return (React.createElement(DatePicker, { placeholder: field.placeholder, showTime: field.showTime, suffixIcon: "calendar", suffixIconSize: "16px", "aria-label": ariaLabel, disabled: isDisabled }));
508
- case 'textarea':
515
+ case FIELD_TYPES.TEXTAREA:
509
516
  return (React.createElement(InputTextArea, { placeholder: field.placeholder, autoSize: { minRows: 3, maxRows: 6 }, "aria-label": ariaLabel, disabled: isDisabled }));
510
- case 'file':
517
+ case FIELD_TYPES.FILE:
511
518
  return (React.createElement(FileFilterInput, { accept: field.fileAccept, "aria-label": ariaLabel, disabled: isDisabled }));
512
- case 'custom':
519
+ case FIELD_TYPES.CUSTOM:
513
520
  if (typeof field.render === 'function') {
514
521
  return field.render({
515
522
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -521,7 +528,7 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
521
528
  });
522
529
  }
523
530
  return (React.createElement(Text, { type: "secondary", className: "akinon-filter__unsupported" }, i18n.t('form.unsupportedField', { field: String(field.type) })));
524
- case 'section':
531
+ case FIELD_TYPES.SECTION:
525
532
  // Section fields should not be rendered inline
526
533
  // They are rendered separately after the main grid
527
534
  return null;
@@ -530,7 +537,7 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
530
537
  }
531
538
  };
532
539
  const renderFormField = (field) => {
533
- 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 === 'checkbox' ? 'checked' : undefined }, renderFieldComponent(field)));
540
+ 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)));
534
541
  };
535
542
  return (React.createElement(Card, { size: "small", className: "akinon-filter shadow", "data-testid": "akifilter-root" },
536
543
  React.createElement(ConfigProvider, { theme: themeOverrides },
@@ -542,10 +549,11 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
542
549
  visibleFields.map(renderFormField),
543
550
  visibleFields.length === 0 ? (React.createElement("div", { className: "akinon-filter__empty" }, i18n.t('form.noVisibleFields'))) : null),
544
551
  sectionFields.length > 0 && (React.createElement("div", { className: "akinon-filter__section-fields" }, sectionFields.map(section => {
545
- return (React.createElement(Collapse, { key: section.key, defaultActiveKey: section.key, ghost: true, items: [
552
+ return (React.createElement(Collapse, { expandIconPosition: "end", key: section.key, expandIcon: ExpandIcon, defaultActiveKey: section.defaultExpanded === false ? [] : [section.key], ghost: true, items: [
546
553
  {
547
554
  key: section.key,
548
- label: section.label,
555
+ label: (React.createElement(Title, { className: "akinon-filter__title", level: 4 }, section.label)),
556
+ headerClass: 'akinon-filter__section-header',
549
557
  children: (React.createElement("div", { className: "akinon-filter__form-grid" }, section.fields.map(renderFormField)))
550
558
  }
551
559
  ] }));
@@ -560,6 +568,15 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
560
568
  }
561
569
  }, page: modalPage, pageSize: modalPageSize }))));
562
570
  };
571
+ export const ExpandIcon = ({ isActive }) => ({
572
+ name: 'chevron_down',
573
+ size: 10,
574
+ style: {
575
+ transform: isActive ? 'rotate(-180deg)' : 'rotate(0deg)',
576
+ transition: 'transform 0.2s ease-in-out',
577
+ marginTop: 8
578
+ }
579
+ });
563
580
  const AkifilterEmptyState = () => {
564
581
  const { t } = i18n;
565
582
  return (React.createElement(Card, { size: "small", className: "akinon-filter shadow", "data-testid": "akifilter-empty" },
@@ -3,6 +3,7 @@ type AppliedFilter = {
3
3
  key: string;
4
4
  label: string;
5
5
  value: string;
6
+ isExclude?: boolean;
6
7
  };
7
8
  type AppliedFiltersProps = {
8
9
  filters: AppliedFilter[];
@@ -1 +1 @@
1
- {"version":3,"file":"applied-filters.d.ts","sourceRoot":"","sources":["../../../src/components/applied-filters.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,MAAM,OAAO,CAAC;AAI1B,KAAK,aAAa,GAAG;IACnB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,KAAK,mBAAmB,GAAG;IACzB,OAAO,EAAE,aAAa,EAAE,CAAC;IACzB,QAAQ,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAChC,UAAU,EAAE,MAAM,IAAI,CAAC;CACxB,CAAC;AAEF,eAAO,MAAM,cAAc,GAAI,mCAI5B,mBAAmB,sBAwDrB,CAAC;AAEF,YAAY,EAAE,aAAa,EAAE,CAAC"}
1
+ {"version":3,"file":"applied-filters.d.ts","sourceRoot":"","sources":["../../../src/components/applied-filters.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,MAAM,OAAO,CAAC;AAI1B,KAAK,aAAa,GAAG;IACnB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB,CAAC;AAEF,KAAK,mBAAmB,GAAG;IACzB,OAAO,EAAE,aAAa,EAAE,CAAC;IACzB,QAAQ,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAChC,UAAU,EAAE,MAAM,IAAI,CAAC;CACxB,CAAC;AAEF,eAAO,MAAM,cAAc,GAAI,mCAI5B,mBAAmB,sBA+DrB,CAAC;AAEF,YAAY,EAAE,aAAa,EAAE,CAAC"}
@@ -9,7 +9,8 @@ export const AppliedFilters = ({ filters, onRemove, onClearAll }) => {
9
9
  return (React.createElement("div", { className: "akinon-filter__applied", "data-testid": "akifilter-applied" },
10
10
  React.createElement(Space, { className: "akinon-filter__applied-summary", size: 8 },
11
11
  React.createElement(Text, { className: "akinon-filter__applied-label" }, i18n.t('applied.label')),
12
- hasFilters ? (React.createElement(Space, { className: "akinon-filter__applied-items", size: 8, wrap: true }, filters.map(item => (React.createElement("div", { key: `${item.key}-${item.value}`, className: "akinon-filter__chip" },
12
+ hasFilters ? (React.createElement(Space, { className: "akinon-filter__applied-items", size: 8, wrap: true }, filters.map(item => (React.createElement("div", { key: `${item.key}-${item.value}`, className: `akinon-filter__chip${item.isExclude ? ' akinon-filter__chip--exclude' : ''}` },
13
+ item.isExclude ? (React.createElement(Icon, { icon: "exclude", size: 12, className: "akinon-filter__chip-exclude-icon" })) : null,
13
14
  item.label ? (React.createElement("span", { className: "akinon-filter__chip-label" },
14
15
  item.label,
15
16
  ":")) : null,
@@ -7,4 +7,19 @@ export declare const BOOLEAN_STRING: {
7
7
  readonly TRUE: "true";
8
8
  readonly FALSE: "false";
9
9
  };
10
+ export declare const FIELD_TYPES: {
11
+ readonly TEXT: "text";
12
+ readonly NUMBER: "number";
13
+ readonly SELECT: "select";
14
+ readonly CHECKBOX: "checkbox";
15
+ readonly DATE: "date";
16
+ readonly TEXTAREA: "textarea";
17
+ readonly SECTION: "section";
18
+ readonly CUSTOM: "custom";
19
+ readonly FILE: "file";
20
+ };
21
+ export declare const FIELD_MODES: {
22
+ readonly TAGS: "tags";
23
+ readonly MULTIPLE: "multiple";
24
+ };
10
25
  //# sourceMappingURL=constants.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../src/constants.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,qBAAqB,IAAI,CAAC;AACvC,eAAO,MAAM,uBAAuB,KAAK,CAAC;AAC1C,eAAO,MAAM,qBAAqB,MAAM,CAAC;AAEzC,eAAO,MAAM,WAAW,eAAe,CAAC;AACxC,eAAO,MAAM,eAAe,wBAAwB,CAAC;AAErD,eAAO,MAAM,cAAc;;;CAGjB,CAAC"}
1
+ {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../src/constants.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,qBAAqB,IAAI,CAAC;AACvC,eAAO,MAAM,uBAAuB,KAAK,CAAC;AAC1C,eAAO,MAAM,qBAAqB,MAAM,CAAC;AAEzC,eAAO,MAAM,WAAW,eAAe,CAAC;AACxC,eAAO,MAAM,eAAe,wBAAwB,CAAC;AAErD,eAAO,MAAM,cAAc;;;CAGjB,CAAC;AAEX,eAAO,MAAM,WAAW;;;;;;;;;;CAUd,CAAC;AAEX,eAAO,MAAM,WAAW;;;CAGd,CAAC"}
@@ -7,3 +7,18 @@ export const BOOLEAN_STRING = {
7
7
  TRUE: 'true',
8
8
  FALSE: 'false'
9
9
  };
10
+ export const FIELD_TYPES = {
11
+ TEXT: 'text',
12
+ NUMBER: 'number',
13
+ SELECT: 'select',
14
+ CHECKBOX: 'checkbox',
15
+ DATE: 'date',
16
+ TEXTAREA: 'textarea',
17
+ SECTION: 'section',
18
+ CUSTOM: 'custom',
19
+ FILE: 'file'
20
+ };
21
+ export const FIELD_MODES = {
22
+ TAGS: 'tags',
23
+ MULTIPLE: 'multiple'
24
+ };
@@ -47,11 +47,11 @@
47
47
  background-color: var(--color-ebonyClay-900);
48
48
  flex: 1;
49
49
  display: flex;
50
- align-items: center;
50
+ align-items: flex-start;
51
51
  border-radius: 5px;
52
52
  padding: 6px 8px;
53
53
  margin-right: 6px;
54
- height: 36px;
54
+ min-height: 36px;
55
55
  }
56
56
 
57
57
  .akinon-filter__applied-label {
@@ -59,16 +59,13 @@
59
59
  font-weight: 600;
60
60
  font-size: 13px;
61
61
  position: relative;
62
- top: 1px;
62
+ white-space: nowrap;
63
+ top: 7px;
63
64
  }
64
65
 
65
- .akinon-filter__common-filters {
66
- margin-top: 2rem;
67
- }
68
-
69
- .akinon-filter__common-filters-title {
70
- color: var(--color-gray-500);
71
- margin-bottom: 1rem;
66
+ .akinon-filter__section-header span {
67
+ margin-inline-end: unset !important;
68
+ flex: unset !important;
72
69
  }
73
70
 
74
71
  .akinon-filter__form-grid {
@@ -121,8 +118,10 @@
121
118
 
122
119
  .akinon-filter__applied-items {
123
120
  display: flex;
121
+ flex: 1;
124
122
  flex-wrap: wrap;
125
123
  gap: 8px;
124
+ min-width: 0;
126
125
  }
127
126
 
128
127
  .akinon-filter__chip {
@@ -136,6 +135,16 @@
136
135
  height: 24px;
137
136
  }
138
137
 
138
+ .akinon-filter__chip--exclude {
139
+ background-color: #ffe0e4;
140
+ border: 1px solid #ff354e;
141
+ height: 23px;
142
+ }
143
+
144
+ .akinon-filter__chip-exclude-icon {
145
+ margin-right: 5px;
146
+ }
147
+
139
148
  .akinon-filter__chip-label {
140
149
  font-weight: 600;
141
150
  color: var(--color-ebonyClay-300);
@@ -162,7 +171,7 @@
162
171
  color: color(--color-ebonyClay-500);
163
172
  font-size: 12px;
164
173
  position: relative;
165
- top: 1px;
174
+ top: 8px;
166
175
  }
167
176
 
168
177
  .akinon-filter__modal-list {
@@ -187,13 +196,13 @@
187
196
  column-gap: 8px; /* replaces Ant's label padding, zeroed out below */
188
197
  line-height: 1.4;
189
198
  margin-inline-start: 0; /* override Ant's adjacent-sibling margin */
199
+ color: var(--color-white);
190
200
  }
191
201
 
192
202
  .akinon-filter__modal-list
193
203
  .akinon-filter__modal-checkbox.ant-checkbox-wrapper
194
204
  > .ant-checkbox {
195
205
  align-self: start;
196
- margin-block-start: 0.15em; /* em-relative baseline nudge */
197
206
  }
198
207
 
199
208
  .akinon-filter__modal-list
@@ -253,6 +262,11 @@
253
262
  width: auto;
254
263
  }
255
264
 
265
+ .akinon-filter .akinon-select-multiple .akinon-select-selector {
266
+ height: 40px;
267
+ overflow: hidden;
268
+ }
269
+
256
270
  /* Modal window. */
257
271
 
258
272
  .akinon-filter__modal-toolbar {
@@ -1,4 +1,5 @@
1
1
  import type { FieldValues } from '@akinon/akiform';
2
+ import { FIELD_TYPES } from '../constants';
2
3
  import type { AkifilterField, AkifilterSchema } from '../types';
3
4
  /**
4
5
  * Flattens schema by extracting all nested fields from section fields.
@@ -11,9 +12,17 @@ export declare const flattenSchema: <TFieldValues extends FieldValues = FieldVal
11
12
  export declare const partitionSchema: <TFieldValues extends FieldValues = FieldValues>(schema: AkifilterSchema<TFieldValues>) => {
12
13
  regularFields: AkifilterSchema<TFieldValues>;
13
14
  sectionFields: Array<AkifilterField<TFieldValues> & {
15
+ type: typeof FIELD_TYPES.SECTION;
14
16
  fields: AkifilterSchema<TFieldValues>;
17
+ defaultExpanded?: boolean;
18
+ isExcludeSection?: boolean;
15
19
  }>;
16
20
  };
21
+ export declare const deriveExcludeSectionKeys: <TFieldValues extends FieldValues = FieldValues>(sectionFields: Array<AkifilterField<TFieldValues> & {
22
+ type: typeof FIELD_TYPES.SECTION;
23
+ fields: AkifilterSchema<TFieldValues>;
24
+ isExcludeSection?: boolean;
25
+ }>) => Set<string>;
17
26
  export declare const getDisplayLabel: <TFieldValues extends FieldValues = FieldValues>(field: AkifilterField<TFieldValues>) => string;
18
27
  export declare const getFieldAriaLabel: <TFieldValues extends FieldValues = FieldValues>(field: AkifilterField<TFieldValues>) => string;
19
28
  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;AAInD,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;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,CAyCtB,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,EAAsC,WAAW,EAAE,MAAM,cAAc,CAAC;AAC/E,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,IAAI,EAAE,OAAO,WAAW,CAAC,OAAO,CAAC;QACjC,MAAM,EAAE,eAAe,CAAC,YAAY,CAAC,CAAC;QACtC,eAAe,CAAC,EAAE,OAAO,CAAC;QAC1B,gBAAgB,CAAC,EAAE,OAAO,CAAC;KAC5B,CACF,CAAC;CAwBH,CAAC;AAEF,eAAO,MAAM,wBAAwB,GACnC,YAAY,SAAS,WAAW,GAAG,WAAW,EAE9C,eAAe,KAAK,CAClB,cAAc,CAAC,YAAY,CAAC,GAAG;IAC7B,IAAI,EAAE,OAAO,WAAW,CAAC,OAAO,CAAC;IACjC,MAAM,EAAE,eAAe,CAAC,YAAY,CAAC,CAAC;IACtC,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B,CACF,KACA,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,13 +1,13 @@
1
1
  import { akidate } from '@akinon/akidate';
2
2
  import { isBoolean, isFunction } from 'lodash-es';
3
- import { DEFAULT_VISIBLE_COUNT } from '../constants';
3
+ import { DEFAULT_VISIBLE_COUNT, FIELD_MODES, FIELD_TYPES } from '../constants';
4
4
  /**
5
5
  * Flattens schema by extracting all nested fields from section fields.
6
6
  * This is used for storage operations and form value management.
7
7
  */
8
8
  export const flattenSchema = (schema) => {
9
9
  return schema.reduce((acc, field) => {
10
- if (field.type === 'section' && 'fields' in field) {
10
+ if (field.type === FIELD_TYPES.SECTION && 'fields' in field) {
11
11
  // Recursively flatten nested section fields
12
12
  return [
13
13
  ...acc,
@@ -24,7 +24,7 @@ export const partitionSchema = (schema) => {
24
24
  const regularFields = [];
25
25
  const sectionFields = [];
26
26
  schema.forEach(field => {
27
- if (field.type === 'section') {
27
+ if (field.type === FIELD_TYPES.SECTION) {
28
28
  sectionFields.push(field);
29
29
  }
30
30
  else {
@@ -33,6 +33,9 @@ export const partitionSchema = (schema) => {
33
33
  });
34
34
  return { regularFields, sectionFields };
35
35
  };
36
+ export const deriveExcludeSectionKeys = (sectionFields) => new Set(sectionFields
37
+ .filter(s => s.isExcludeSection)
38
+ .flatMap(s => s.fields.map(f => String(f.key))));
36
39
  export const getDisplayLabel = (field) => {
37
40
  return field.label || field.placeholder || String(field.key);
38
41
  };
@@ -84,15 +87,16 @@ export const normaliseValuesBySchema = (schema, values) => {
84
87
  return acc;
85
88
  }
86
89
  const currentValue = values[key];
87
- if (field.type === 'date') {
90
+ if (field.type === FIELD_TYPES.DATE) {
88
91
  const parsed = akidate.parse(currentValue);
89
92
  if (parsed) {
90
93
  acc[key] = parsed;
91
94
  return acc;
92
95
  }
93
96
  }
94
- if (field.type === 'select' &&
95
- (field.mode === 'multiple' || field.mode === 'tags') &&
97
+ if (field.type === FIELD_TYPES.SELECT &&
98
+ (field.mode === FIELD_MODES.MULTIPLE ||
99
+ field.mode === FIELD_MODES.TAGS) &&
96
100
  currentValue != null &&
97
101
  !Array.isArray(currentValue)) {
98
102
  acc[key] = [currentValue];
@@ -1,5 +1,5 @@
1
1
  import { akidate } from '@akinon/akidate';
2
- import { DATE_FORMAT, DATETIME_FORMAT } from '../constants';
2
+ import { DATE_FORMAT, DATETIME_FORMAT, FIELD_TYPES } from '../constants';
3
3
  export const shouldPersistValue = (field, value) => {
4
4
  if (value === undefined || value === null) {
5
5
  return false;
@@ -10,10 +10,10 @@ export const shouldPersistValue = (field, value) => {
10
10
  if (Array.isArray(value) && value.length === 0) {
11
11
  return false;
12
12
  }
13
- if ((field === null || field === void 0 ? void 0 : field.type) === 'checkbox' && value !== true) {
13
+ if ((field === null || field === void 0 ? void 0 : field.type) === FIELD_TYPES.CHECKBOX && value !== true) {
14
14
  return false;
15
15
  }
16
- if ((field === null || field === void 0 ? void 0 : field.type) === 'file') {
16
+ if ((field === null || field === void 0 ? void 0 : field.type) === FIELD_TYPES.FILE) {
17
17
  return value instanceof File;
18
18
  }
19
19
  return true;
@@ -29,7 +29,7 @@ export const normaliseOutputValues = (schema, values) => {
29
29
  return acc;
30
30
  }
31
31
  let normalisedValue = rawValue;
32
- if ((field === null || field === void 0 ? void 0 : field.type) === 'date') {
32
+ if ((field === null || field === void 0 ? void 0 : field.type) === FIELD_TYPES.DATE) {
33
33
  const normalised = akidate.toIsoDate(rawValue);
34
34
  if (!normalised) {
35
35
  return acc;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@akinon/akifilter",
3
- "version": "1.4.4-260521-3",
3
+ "version": "1.6.0",
4
4
  "private": false,
5
5
  "description": "Akifilter is a filtering library for Akinon frontend applications.",
6
6
  "type": "module",
@@ -15,8 +15,8 @@
15
15
  "lodash-es": "^4.17.21",
16
16
  "@akinon/akiform": "1.1.9",
17
17
  "@akinon/akidate": "1.2.7",
18
- "@akinon/icons": "1.3.2",
19
18
  "@akinon/akilocale": "1.2.8",
19
+ "@akinon/icons": "1.3.2",
20
20
  "@akinon/ui-button": "1.5.3",
21
21
  "@akinon/ui-card": "1.2.10",
22
22
  "@akinon/ui-checkbox": "1.4.10",
@@ -29,7 +29,7 @@
29
29
  "@akinon/ui-select": "1.4.13",
30
30
  "@akinon/ui-space": "1.4.10",
31
31
  "@akinon/ui-typography": "1.2.7",
32
- "@akinon/ui-upload": "1.5.3"
32
+ "@akinon/ui-upload": "1.6.0"
33
33
  },
34
34
  "devDependencies": {
35
35
  "@types/lodash-es": "^4.17.12",
@@ -37,8 +37,8 @@
37
37
  "copyfiles": "^2.4.1",
38
38
  "rimraf": "^5.0.5",
39
39
  "typescript": "*",
40
+ "@akinon/akiform-builder": "1.6.0",
40
41
  "@akinon/typescript-config": "1.1.8",
41
- "@akinon/akiform-builder": "1.5.11",
42
42
  "@akinon/utils": "1.2.11",
43
43
  "@akinon/ui-theme": "1.3.1",
44
44
  "@akinon/vitest-config": "1.1.8"