@akinon/akifilter 1.2.0-next.1 → 1.2.0-next.10

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;AAWzB,OAAO,KAAK,MAAM,OAAO,CAAC;AA0B1B,OAAO,KAAK,EAAkB,eAAe,EAAE,MAAM,SAAS,CAAC;AAY/D,KAAK,oBAAoB,GAAG,WAAW,CAAC;AA2DxC,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;AAgsBF,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;AAWzB,OAAO,KAAK,MAAM,OAAO,CAAC;AA0B1B,OAAO,KAAK,EAAkB,eAAe,EAAE,MAAM,SAAS,CAAC;AAY/D,KAAK,oBAAoB,GAAG,WAAW,CAAC;AA0HxC,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;AAswBF,eAAO,MAAM,SAAS;KACpB,YAAY,SAAS,oBAAoB,uBAElC,cAAc,CAAC,YAAY,CAAC;;CAmBpC,CAAC"}
@@ -37,6 +37,32 @@ const use_debounced_value_1 = require("./hooks/use-debounced-value");
37
37
  const i18n_1 = require("./i18n");
38
38
  const schema_1 = require("./utils/schema");
39
39
  const values_1 = require("./utils/values");
40
+ const FilterFormItem = (_a) => {
41
+ var _b, _c;
42
+ var { children, control, name, valuePropName } = _a, props = __rest(_a, ["children", "control", "name", "valuePropName"]);
43
+ const { field, fieldState } = (0, akiform_1.useController)({ name, control });
44
+ const childrenWithProps = react_1.default.Children.map(children, child => {
45
+ if (react_1.default.isValidElement(child)) {
46
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
47
+ const typedChild = child;
48
+ return react_1.default.cloneElement(typedChild, Object.assign(Object.assign(Object.assign({}, field), {
49
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
50
+ onChange: (...params) => {
51
+ var _a, _b;
52
+ (_b = (_a = typedChild.props).onChange) === null || _b === void 0 ? void 0 : _b.call(_a, params);
53
+ field.onChange(...params);
54
+ }, onBlur: () => {
55
+ var _a, _b;
56
+ (_b = (_a = typedChild.props).onBlur) === null || _b === void 0 ? void 0 : _b.call(_a);
57
+ field.onBlur();
58
+ }, ref: undefined }), (valuePropName && {
59
+ [valuePropName]: field.value
60
+ })));
61
+ }
62
+ return child;
63
+ });
64
+ return (react_1.default.createElement(akiform_1.Akiform.Item, Object.assign({}, props, { validateStatus: fieldState.invalid ? 'error' : undefined, help: (_c = (_b = fieldState.error) === null || _b === void 0 ? void 0 : _b.message) !== null && _c !== void 0 ? _c : props.help, className: props.className }), childrenWithProps));
65
+ };
40
66
  /**
41
67
  * Checks if a field should be disabled based on config.disabled property.
42
68
  */
@@ -86,9 +112,15 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
86
112
  const schemaDefaults = react_1.default.useMemo(() => (0, schema_1.normaliseValuesBySchema)(flattenedSchema, (0, schema_1.extractDefaultValues)(flattenedSchema)), [flattenedSchema]);
87
113
  const externalDefaults = react_1.default.useMemo(() => (0, schema_1.normaliseValuesBySchema)(flattenedSchema, defaultValues), [flattenedSchema, defaultValues]);
88
114
  const baseDefaultValues = react_1.default.useMemo(() => (Object.assign(Object.assign({}, schemaDefaults), externalDefaults)), [schemaDefaults, externalDefaults]);
89
- const persistedDefaults = react_1.default.useMemo(() => {
115
+ // Use state for persistedDefaults to make it reactive to localStorage changes
116
+ const [persistedDefaults, setPersistedDefaults] = react_1.default.useState(() => {
90
117
  var _a;
91
118
  return (0, schema_1.normaliseValuesBySchema)(flattenedSchema, (_a = (0, storage_1.readStoredValues)(regularFields, storageKey)) !== null && _a !== void 0 ? _a : undefined);
119
+ });
120
+ // Re-read from localStorage when storageKey changes
121
+ react_1.default.useEffect(() => {
122
+ var _a;
123
+ setPersistedDefaults((0, schema_1.normaliseValuesBySchema)(flattenedSchema, (_a = (0, storage_1.readStoredValues)(regularFields, storageKey)) !== null && _a !== void 0 ? _a : undefined));
92
124
  }, [flattenedSchema, regularFields, storageKey]);
93
125
  const mergedDefaultValues = react_1.default.useMemo(() => (Object.assign(Object.assign({}, baseDefaultValues), persistedDefaults)), [baseDefaultValues, persistedDefaults]);
94
126
  const formMethods = (0, akiform_1.useForm)({
@@ -187,7 +219,20 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
187
219
  (0, storage_1.writeVisibleKeys)(storageKey, visibleKeys);
188
220
  onVisibleFieldsChange === null || onVisibleFieldsChange === void 0 ? void 0 : onVisibleFieldsChange(visibleKeys);
189
221
  }, [visibleKeys, onVisibleFieldsChange, storageKey]);
222
+ // Track previous mergedDefaultValues to avoid unnecessary resets
223
+ const previousMergedDefaultValuesRef = react_1.default.useRef(null);
190
224
  react_1.default.useEffect(() => {
225
+ const serialised = JSON.stringify(mergedDefaultValues);
226
+ // Skip if the values haven't actually changed (deep comparison)
227
+ if (previousMergedDefaultValuesRef.current === serialised) {
228
+ return;
229
+ }
230
+ // Skip reset on initial mount - useForm already uses defaultValues
231
+ if (previousMergedDefaultValuesRef.current === null) {
232
+ previousMergedDefaultValuesRef.current = serialised;
233
+ return;
234
+ }
235
+ previousMergedDefaultValuesRef.current = serialised;
191
236
  formMethods.reset(mergedDefaultValues);
192
237
  }, [formMethods, mergedDefaultValues]);
193
238
  const normalisedValues = react_1.default.useMemo(() => (0, values_1.normaliseOutputValues)(flattenedSchema, formValues), [flattenedSchema, formValues]);
@@ -245,6 +290,38 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
245
290
  react_1.default.useEffect(() => {
246
291
  lastPersistedValuesRef.current = null;
247
292
  }, [storageKey]);
293
+ // Track previous serialised values to detect field removals
294
+ const previousSerialisedValuesRef = react_1.default.useRef(serialisedValues);
295
+ // Immediate effect for field removals (bypasses debounce)
296
+ react_1.default.useEffect(() => {
297
+ const previous = previousSerialisedValuesRef.current;
298
+ const current = serialisedValues;
299
+ previousSerialisedValuesRef.current = current;
300
+ // Skip if this is the initial render
301
+ if (!hasEmittedValuesRef.current) {
302
+ return;
303
+ }
304
+ // Skip if values haven't actually changed
305
+ if (previous === current) {
306
+ return;
307
+ }
308
+ const prevParsed = JSON.parse(previous);
309
+ const currParsed = JSON.parse(current);
310
+ // Detect if a field was removed (existed in prev but not in current)
311
+ const prevKeys = Object.keys(prevParsed);
312
+ const currKeys = Object.keys(currParsed);
313
+ const removedKeys = prevKeys.filter(key => !currKeys.includes(key));
314
+ // If a field was removed, immediately persist and emit (bypass debounce)
315
+ if (removedKeys.length > 0) {
316
+ const nextSerialised = current;
317
+ persistValues(currParsed, nextSerialised);
318
+ lastPersistedValuesRef.current = nextSerialised;
319
+ currentSerialisedValuesRef.current = nextSerialised;
320
+ // Update persistedDefaults to prevent stale mergedDefaultValues on re-render
321
+ setPersistedDefaults(currParsed);
322
+ onValuesChange === null || onValuesChange === void 0 ? void 0 : onValuesChange(currParsed);
323
+ }
324
+ }, [serialisedValues, onValuesChange, persistValues]);
248
325
  const handleClearAll = react_1.default.useCallback(() => {
249
326
  const clearedDefaults = Object.assign({}, schemaDefaults);
250
327
  flattenedSchema.forEach(field => {
@@ -303,24 +380,26 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
303
380
  return;
304
381
  }
305
382
  const defaultValue = schemaDefaults[String(schemaField.key)];
306
- const fieldPath = schemaField.key;
307
383
  const nextValue = resolveClearedFieldValue(schemaField, defaultValue);
308
- // Update the form value
309
- formMethods.setValue(fieldPath, nextValue, {
310
- shouldDirty: false,
311
- shouldTouch: false,
312
- shouldValidate: false
313
- });
314
- // Compute the updated values immediately (don't wait for debounce)
315
- const currentValues = formMethods.getValues() || {};
316
- const updatedFormValues = Object.assign(Object.assign({}, currentValues), { [fieldPath]: nextValue });
384
+ // Manually construct the updated form values instead of relying on setValue + getValues
385
+ // because setValue is batched and getValues() wouldn't reflect the change immediately
386
+ const currentFormValues = formMethods.getValues();
387
+ const updatedFormValues = Object.assign(Object.assign({}, currentFormValues), { [String(schemaField.key)]: nextValue });
388
+ // Normalize the values (removes empty/undefined entries)
317
389
  const nextValues = (0, values_1.normaliseOutputValues)(flattenedSchema, updatedFormValues);
390
+ // Update form state
391
+ formMethods.reset(updatedFormValues, {
392
+ keepDirty: false,
393
+ keepTouched: false,
394
+ keepValues: false
395
+ });
318
396
  const nextSerialised = JSON.stringify(nextValues);
319
397
  // Persist immediately (bypass debounce for remove action)
320
398
  persistValues(nextValues);
321
399
  // Update refs to mark this as the latest persisted state
322
400
  lastPersistedValuesRef.current = nextSerialised;
323
401
  currentSerialisedValuesRef.current = nextSerialised;
402
+ previousSerialisedValuesRef.current = nextSerialised; // Prevent immediate effect from re-emitting
324
403
  hasEmittedValuesRef.current = true;
325
404
  hasInitialValuesRef.current = Object.keys(nextValues).length > 0;
326
405
  // Emit the change to parent
@@ -387,7 +466,7 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
387
466
  }
388
467
  };
389
468
  const renderFormField = (field) => {
390
- 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)));
469
+ 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)));
391
470
  };
392
471
  return (react_1.default.createElement(ui_card_1.Card, { size: "small", className: "akinon-filter shadow", "data-testid": "akifilter-root" },
393
472
  react_1.default.createElement(antd_1.ConfigProvider, { theme: theme_overrides_1.themeOverrides },
@@ -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;AAWzB,OAAO,KAAK,MAAM,OAAO,CAAC;AA0B1B,OAAO,KAAK,EAAkB,eAAe,EAAE,MAAM,SAAS,CAAC;AAY/D,KAAK,oBAAoB,GAAG,WAAW,CAAC;AA2DxC,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;AAgsBF,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;AAWzB,OAAO,KAAK,MAAM,OAAO,CAAC;AA0B1B,OAAO,KAAK,EAAkB,eAAe,EAAE,MAAM,SAAS,CAAC;AAY/D,KAAK,oBAAoB,GAAG,WAAW,CAAC;AA0HxC,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;AAswBF,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
  };
12
12
  import './styles.css';
13
13
  import { akidate } from '@akinon/akidate';
14
- import { Akiform, FormItem, useForm, useWatch } from '@akinon/akiform';
14
+ import { Akiform, useController, 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';
@@ -34,6 +34,32 @@ import { useDebouncedValue } from './hooks/use-debounced-value';
34
34
  import { i18n } from './i18n';
35
35
  import { deriveDefaultVisibleKeys, ensureSchemaOrder, extractDefaultValues, flattenSchema, getFieldAriaLabel, normaliseValuesBySchema, partitionSchema } from './utils/schema';
36
36
  import { normaliseOutputValues } from './utils/values';
37
+ const FilterFormItem = (_a) => {
38
+ var _b, _c;
39
+ var { children, control, name, valuePropName } = _a, props = __rest(_a, ["children", "control", "name", "valuePropName"]);
40
+ const { field, fieldState } = useController({ name, control });
41
+ const childrenWithProps = React.Children.map(children, child => {
42
+ if (React.isValidElement(child)) {
43
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
44
+ const typedChild = child;
45
+ return React.cloneElement(typedChild, Object.assign(Object.assign(Object.assign({}, field), {
46
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
47
+ onChange: (...params) => {
48
+ var _a, _b;
49
+ (_b = (_a = typedChild.props).onChange) === null || _b === void 0 ? void 0 : _b.call(_a, params);
50
+ field.onChange(...params);
51
+ }, onBlur: () => {
52
+ var _a, _b;
53
+ (_b = (_a = typedChild.props).onBlur) === null || _b === void 0 ? void 0 : _b.call(_a);
54
+ field.onBlur();
55
+ }, ref: undefined }), (valuePropName && {
56
+ [valuePropName]: field.value
57
+ })));
58
+ }
59
+ return child;
60
+ });
61
+ return (React.createElement(Akiform.Item, Object.assign({}, props, { validateStatus: fieldState.invalid ? 'error' : undefined, help: (_c = (_b = fieldState.error) === null || _b === void 0 ? void 0 : _b.message) !== null && _c !== void 0 ? _c : props.help, className: props.className }), childrenWithProps));
62
+ };
37
63
  /**
38
64
  * Checks if a field should be disabled based on config.disabled property.
39
65
  */
@@ -83,9 +109,15 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
83
109
  const schemaDefaults = React.useMemo(() => normaliseValuesBySchema(flattenedSchema, extractDefaultValues(flattenedSchema)), [flattenedSchema]);
84
110
  const externalDefaults = React.useMemo(() => normaliseValuesBySchema(flattenedSchema, defaultValues), [flattenedSchema, defaultValues]);
85
111
  const baseDefaultValues = React.useMemo(() => (Object.assign(Object.assign({}, schemaDefaults), externalDefaults)), [schemaDefaults, externalDefaults]);
86
- const persistedDefaults = React.useMemo(() => {
112
+ // Use state for persistedDefaults to make it reactive to localStorage changes
113
+ const [persistedDefaults, setPersistedDefaults] = React.useState(() => {
87
114
  var _a;
88
115
  return normaliseValuesBySchema(flattenedSchema, (_a = readStoredValues(regularFields, storageKey)) !== null && _a !== void 0 ? _a : undefined);
116
+ });
117
+ // Re-read from localStorage when storageKey changes
118
+ React.useEffect(() => {
119
+ var _a;
120
+ setPersistedDefaults(normaliseValuesBySchema(flattenedSchema, (_a = readStoredValues(regularFields, storageKey)) !== null && _a !== void 0 ? _a : undefined));
89
121
  }, [flattenedSchema, regularFields, storageKey]);
90
122
  const mergedDefaultValues = React.useMemo(() => (Object.assign(Object.assign({}, baseDefaultValues), persistedDefaults)), [baseDefaultValues, persistedDefaults]);
91
123
  const formMethods = useForm({
@@ -184,7 +216,20 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
184
216
  writeVisibleKeys(storageKey, visibleKeys);
185
217
  onVisibleFieldsChange === null || onVisibleFieldsChange === void 0 ? void 0 : onVisibleFieldsChange(visibleKeys);
186
218
  }, [visibleKeys, onVisibleFieldsChange, storageKey]);
219
+ // Track previous mergedDefaultValues to avoid unnecessary resets
220
+ const previousMergedDefaultValuesRef = React.useRef(null);
187
221
  React.useEffect(() => {
222
+ const serialised = JSON.stringify(mergedDefaultValues);
223
+ // Skip if the values haven't actually changed (deep comparison)
224
+ if (previousMergedDefaultValuesRef.current === serialised) {
225
+ return;
226
+ }
227
+ // Skip reset on initial mount - useForm already uses defaultValues
228
+ if (previousMergedDefaultValuesRef.current === null) {
229
+ previousMergedDefaultValuesRef.current = serialised;
230
+ return;
231
+ }
232
+ previousMergedDefaultValuesRef.current = serialised;
188
233
  formMethods.reset(mergedDefaultValues);
189
234
  }, [formMethods, mergedDefaultValues]);
190
235
  const normalisedValues = React.useMemo(() => normaliseOutputValues(flattenedSchema, formValues), [flattenedSchema, formValues]);
@@ -242,6 +287,38 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
242
287
  React.useEffect(() => {
243
288
  lastPersistedValuesRef.current = null;
244
289
  }, [storageKey]);
290
+ // Track previous serialised values to detect field removals
291
+ const previousSerialisedValuesRef = React.useRef(serialisedValues);
292
+ // Immediate effect for field removals (bypasses debounce)
293
+ React.useEffect(() => {
294
+ const previous = previousSerialisedValuesRef.current;
295
+ const current = serialisedValues;
296
+ previousSerialisedValuesRef.current = current;
297
+ // Skip if this is the initial render
298
+ if (!hasEmittedValuesRef.current) {
299
+ return;
300
+ }
301
+ // Skip if values haven't actually changed
302
+ if (previous === current) {
303
+ return;
304
+ }
305
+ const prevParsed = JSON.parse(previous);
306
+ const currParsed = JSON.parse(current);
307
+ // Detect if a field was removed (existed in prev but not in current)
308
+ const prevKeys = Object.keys(prevParsed);
309
+ const currKeys = Object.keys(currParsed);
310
+ const removedKeys = prevKeys.filter(key => !currKeys.includes(key));
311
+ // If a field was removed, immediately persist and emit (bypass debounce)
312
+ if (removedKeys.length > 0) {
313
+ const nextSerialised = current;
314
+ persistValues(currParsed, nextSerialised);
315
+ lastPersistedValuesRef.current = nextSerialised;
316
+ currentSerialisedValuesRef.current = nextSerialised;
317
+ // Update persistedDefaults to prevent stale mergedDefaultValues on re-render
318
+ setPersistedDefaults(currParsed);
319
+ onValuesChange === null || onValuesChange === void 0 ? void 0 : onValuesChange(currParsed);
320
+ }
321
+ }, [serialisedValues, onValuesChange, persistValues]);
245
322
  const handleClearAll = React.useCallback(() => {
246
323
  const clearedDefaults = Object.assign({}, schemaDefaults);
247
324
  flattenedSchema.forEach(field => {
@@ -300,24 +377,26 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
300
377
  return;
301
378
  }
302
379
  const defaultValue = schemaDefaults[String(schemaField.key)];
303
- const fieldPath = schemaField.key;
304
380
  const nextValue = resolveClearedFieldValue(schemaField, defaultValue);
305
- // Update the form value
306
- formMethods.setValue(fieldPath, nextValue, {
307
- shouldDirty: false,
308
- shouldTouch: false,
309
- shouldValidate: false
310
- });
311
- // Compute the updated values immediately (don't wait for debounce)
312
- const currentValues = formMethods.getValues() || {};
313
- const updatedFormValues = Object.assign(Object.assign({}, currentValues), { [fieldPath]: nextValue });
381
+ // Manually construct the updated form values instead of relying on setValue + getValues
382
+ // because setValue is batched and getValues() wouldn't reflect the change immediately
383
+ const currentFormValues = formMethods.getValues();
384
+ const updatedFormValues = Object.assign(Object.assign({}, currentFormValues), { [String(schemaField.key)]: nextValue });
385
+ // Normalize the values (removes empty/undefined entries)
314
386
  const nextValues = normaliseOutputValues(flattenedSchema, updatedFormValues);
387
+ // Update form state
388
+ formMethods.reset(updatedFormValues, {
389
+ keepDirty: false,
390
+ keepTouched: false,
391
+ keepValues: false
392
+ });
315
393
  const nextSerialised = JSON.stringify(nextValues);
316
394
  // Persist immediately (bypass debounce for remove action)
317
395
  persistValues(nextValues);
318
396
  // Update refs to mark this as the latest persisted state
319
397
  lastPersistedValuesRef.current = nextSerialised;
320
398
  currentSerialisedValuesRef.current = nextSerialised;
399
+ previousSerialisedValuesRef.current = nextSerialised; // Prevent immediate effect from re-emitting
321
400
  hasEmittedValuesRef.current = true;
322
401
  hasInitialValuesRef.current = Object.keys(nextValues).length > 0;
323
402
  // Emit the change to parent
@@ -384,7 +463,7 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
384
463
  }
385
464
  };
386
465
  const renderFormField = (field) => {
387
- 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)));
466
+ 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)));
388
467
  };
389
468
  return (React.createElement(Card, { size: "small", className: "akinon-filter shadow", "data-testid": "akifilter-root" },
390
469
  React.createElement(ConfigProvider, { theme: themeOverrides },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@akinon/akifilter",
3
- "version": "1.2.0-next.1",
3
+ "version": "1.2.0-next.10",
4
4
  "private": false,
5
5
  "description": "Akifilter is a filtering library for Akinon frontend applications.",
6
6
  "type": "module",
@@ -13,20 +13,20 @@
13
13
  "antd": "^5.27.0",
14
14
  "react-error-boundary": "^6.0.0",
15
15
  "@akinon/akiform": "1.1.2",
16
- "@akinon/akilocale": "1.2.1",
16
+ "@akinon/akidate": "1.1.3-next.0",
17
17
  "@akinon/ui-button": "1.4.0-next.1",
18
18
  "@akinon/icons": "1.1.2-next.1",
19
19
  "@akinon/ui-card": "1.1.3-next.1",
20
20
  "@akinon/ui-checkbox": "1.3.3-next.1",
21
+ "@akinon/akilocale": "1.2.1",
21
22
  "@akinon/ui-collapse": "1.3.2-next.1",
23
+ "@akinon/ui-input": "1.1.3-next.1",
24
+ "@akinon/ui-modal": "1.1.3-next.1",
22
25
  "@akinon/ui-date-picker": "1.3.3-next.1",
23
26
  "@akinon/ui-input-number": "1.3.3-next.1",
24
- "@akinon/ui-input": "1.1.3-next.1",
25
27
  "@akinon/ui-select": "1.3.4-next.1",
26
- "@akinon/ui-typography": "1.1.2-next.0",
27
- "@akinon/ui-modal": "1.1.3-next.1",
28
28
  "@akinon/ui-pagination": "1.3.4-next.1",
29
- "@akinon/akidate": "1.1.3-next.0",
29
+ "@akinon/ui-typography": "1.1.2-next.0",
30
30
  "@akinon/ui-space": "1.3.3-next.1"
31
31
  },
32
32
  "devDependencies": {
@@ -34,11 +34,11 @@
34
34
  "copyfiles": "^2.4.1",
35
35
  "rimraf": "^5.0.5",
36
36
  "typescript": "*",
37
- "@akinon/utils": "1.1.4-next.1",
38
37
  "@akinon/akiform-builder": "1.3.5-next.1",
38
+ "@akinon/ui-theme": "1.1.3-next.1",
39
39
  "@akinon/typescript-config": "1.1.1",
40
40
  "@akinon/vitest-config": "1.1.1",
41
- "@akinon/ui-theme": "1.1.3-next.1"
41
+ "@akinon/utils": "1.1.4-next.1"
42
42
  },
43
43
  "peerDependencies": {
44
44
  "react": "^18 || ^19",