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

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;AAqwBF,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,8 +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(() => {
191
- formMethods.reset(mergedDefaultValues);
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;
192
236
  }, [formMethods, mergedDefaultValues]);
193
237
  const normalisedValues = react_1.default.useMemo(() => (0, values_1.normaliseOutputValues)(flattenedSchema, formValues), [flattenedSchema, formValues]);
194
238
  const hasInitialValuesRef = react_1.default.useRef(false);
@@ -245,6 +289,38 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
245
289
  react_1.default.useEffect(() => {
246
290
  lastPersistedValuesRef.current = null;
247
291
  }, [storageKey]);
292
+ // Track previous serialised values to detect field removals
293
+ const previousSerialisedValuesRef = react_1.default.useRef(serialisedValues);
294
+ // Immediate effect for field removals (bypasses debounce)
295
+ react_1.default.useEffect(() => {
296
+ const previous = previousSerialisedValuesRef.current;
297
+ const current = serialisedValues;
298
+ previousSerialisedValuesRef.current = current;
299
+ // Skip if this is the initial render
300
+ if (!hasEmittedValuesRef.current) {
301
+ return;
302
+ }
303
+ // Skip if values haven't actually changed
304
+ if (previous === current) {
305
+ return;
306
+ }
307
+ const prevParsed = JSON.parse(previous);
308
+ const currParsed = JSON.parse(current);
309
+ // Detect if a field was removed (existed in prev but not in current)
310
+ const prevKeys = Object.keys(prevParsed);
311
+ const currKeys = Object.keys(currParsed);
312
+ const removedKeys = prevKeys.filter(key => !currKeys.includes(key));
313
+ // If a field was removed, immediately persist and emit (bypass debounce)
314
+ if (removedKeys.length > 0) {
315
+ const nextSerialised = current;
316
+ persistValues(currParsed, nextSerialised);
317
+ lastPersistedValuesRef.current = nextSerialised;
318
+ currentSerialisedValuesRef.current = nextSerialised;
319
+ // Update persistedDefaults to prevent stale mergedDefaultValues on re-render
320
+ setPersistedDefaults(currParsed);
321
+ onValuesChange === null || onValuesChange === void 0 ? void 0 : onValuesChange(currParsed);
322
+ }
323
+ }, [serialisedValues, onValuesChange, persistValues]);
248
324
  const handleClearAll = react_1.default.useCallback(() => {
249
325
  const clearedDefaults = Object.assign({}, schemaDefaults);
250
326
  flattenedSchema.forEach(field => {
@@ -303,24 +379,26 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
303
379
  return;
304
380
  }
305
381
  const defaultValue = schemaDefaults[String(schemaField.key)];
306
- const fieldPath = schemaField.key;
307
382
  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 });
383
+ // Manually construct the updated form values instead of relying on setValue + getValues
384
+ // because setValue is batched and getValues() wouldn't reflect the change immediately
385
+ const currentFormValues = formMethods.getValues();
386
+ const updatedFormValues = Object.assign(Object.assign({}, currentFormValues), { [String(schemaField.key)]: nextValue });
387
+ // Normalize the values (removes empty/undefined entries)
317
388
  const nextValues = (0, values_1.normaliseOutputValues)(flattenedSchema, updatedFormValues);
389
+ // Update form state
390
+ formMethods.reset(updatedFormValues, {
391
+ keepDirty: false,
392
+ keepTouched: false,
393
+ keepValues: false
394
+ });
318
395
  const nextSerialised = JSON.stringify(nextValues);
319
396
  // Persist immediately (bypass debounce for remove action)
320
397
  persistValues(nextValues);
321
398
  // Update refs to mark this as the latest persisted state
322
399
  lastPersistedValuesRef.current = nextSerialised;
323
400
  currentSerialisedValuesRef.current = nextSerialised;
401
+ previousSerialisedValuesRef.current = nextSerialised; // Prevent immediate effect from re-emitting
324
402
  hasEmittedValuesRef.current = true;
325
403
  hasInitialValuesRef.current = Object.keys(nextValues).length > 0;
326
404
  // Emit the change to parent
@@ -387,7 +465,7 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
387
465
  }
388
466
  };
389
467
  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)));
468
+ 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
469
  };
392
470
  return (react_1.default.createElement(ui_card_1.Card, { size: "small", className: "akinon-filter shadow", "data-testid": "akifilter-root" },
393
471
  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;AAqwBF,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,8 +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(() => {
188
- formMethods.reset(mergedDefaultValues);
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;
189
233
  }, [formMethods, mergedDefaultValues]);
190
234
  const normalisedValues = React.useMemo(() => normaliseOutputValues(flattenedSchema, formValues), [flattenedSchema, formValues]);
191
235
  const hasInitialValuesRef = React.useRef(false);
@@ -242,6 +286,38 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
242
286
  React.useEffect(() => {
243
287
  lastPersistedValuesRef.current = null;
244
288
  }, [storageKey]);
289
+ // Track previous serialised values to detect field removals
290
+ const previousSerialisedValuesRef = React.useRef(serialisedValues);
291
+ // Immediate effect for field removals (bypasses debounce)
292
+ React.useEffect(() => {
293
+ const previous = previousSerialisedValuesRef.current;
294
+ const current = serialisedValues;
295
+ previousSerialisedValuesRef.current = current;
296
+ // Skip if this is the initial render
297
+ if (!hasEmittedValuesRef.current) {
298
+ return;
299
+ }
300
+ // Skip if values haven't actually changed
301
+ if (previous === current) {
302
+ return;
303
+ }
304
+ const prevParsed = JSON.parse(previous);
305
+ const currParsed = JSON.parse(current);
306
+ // Detect if a field was removed (existed in prev but not in current)
307
+ const prevKeys = Object.keys(prevParsed);
308
+ const currKeys = Object.keys(currParsed);
309
+ const removedKeys = prevKeys.filter(key => !currKeys.includes(key));
310
+ // If a field was removed, immediately persist and emit (bypass debounce)
311
+ if (removedKeys.length > 0) {
312
+ const nextSerialised = current;
313
+ persistValues(currParsed, nextSerialised);
314
+ lastPersistedValuesRef.current = nextSerialised;
315
+ currentSerialisedValuesRef.current = nextSerialised;
316
+ // Update persistedDefaults to prevent stale mergedDefaultValues on re-render
317
+ setPersistedDefaults(currParsed);
318
+ onValuesChange === null || onValuesChange === void 0 ? void 0 : onValuesChange(currParsed);
319
+ }
320
+ }, [serialisedValues, onValuesChange, persistValues]);
245
321
  const handleClearAll = React.useCallback(() => {
246
322
  const clearedDefaults = Object.assign({}, schemaDefaults);
247
323
  flattenedSchema.forEach(field => {
@@ -300,24 +376,26 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
300
376
  return;
301
377
  }
302
378
  const defaultValue = schemaDefaults[String(schemaField.key)];
303
- const fieldPath = schemaField.key;
304
379
  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 });
380
+ // Manually construct the updated form values instead of relying on setValue + getValues
381
+ // because setValue is batched and getValues() wouldn't reflect the change immediately
382
+ const currentFormValues = formMethods.getValues();
383
+ const updatedFormValues = Object.assign(Object.assign({}, currentFormValues), { [String(schemaField.key)]: nextValue });
384
+ // Normalize the values (removes empty/undefined entries)
314
385
  const nextValues = normaliseOutputValues(flattenedSchema, updatedFormValues);
386
+ // Update form state
387
+ formMethods.reset(updatedFormValues, {
388
+ keepDirty: false,
389
+ keepTouched: false,
390
+ keepValues: false
391
+ });
315
392
  const nextSerialised = JSON.stringify(nextValues);
316
393
  // Persist immediately (bypass debounce for remove action)
317
394
  persistValues(nextValues);
318
395
  // Update refs to mark this as the latest persisted state
319
396
  lastPersistedValuesRef.current = nextSerialised;
320
397
  currentSerialisedValuesRef.current = nextSerialised;
398
+ previousSerialisedValuesRef.current = nextSerialised; // Prevent immediate effect from re-emitting
321
399
  hasEmittedValuesRef.current = true;
322
400
  hasInitialValuesRef.current = Object.keys(nextValues).length > 0;
323
401
  // Emit the change to parent
@@ -384,7 +462,7 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
384
462
  }
385
463
  };
386
464
  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)));
465
+ 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
466
  };
389
467
  return (React.createElement(Card, { size: "small", className: "akinon-filter shadow", "data-testid": "akifilter-root" },
390
468
  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.11",
4
4
  "private": false,
5
5
  "description": "Akifilter is a filtering library for Akinon frontend applications.",
6
6
  "type": "module",
@@ -14,31 +14,31 @@
14
14
  "react-error-boundary": "^6.0.0",
15
15
  "@akinon/akiform": "1.1.2",
16
16
  "@akinon/akilocale": "1.2.1",
17
- "@akinon/ui-button": "1.4.0-next.1",
18
17
  "@akinon/icons": "1.1.2-next.1",
18
+ "@akinon/ui-button": "1.4.0-next.1",
19
19
  "@akinon/ui-card": "1.1.3-next.1",
20
20
  "@akinon/ui-checkbox": "1.3.3-next.1",
21
21
  "@akinon/ui-collapse": "1.3.2-next.1",
22
22
  "@akinon/ui-date-picker": "1.3.3-next.1",
23
- "@akinon/ui-input-number": "1.3.3-next.1",
23
+ "@akinon/akidate": "1.1.3-next.0",
24
24
  "@akinon/ui-input": "1.1.3-next.1",
25
- "@akinon/ui-select": "1.3.4-next.1",
26
- "@akinon/ui-typography": "1.1.2-next.0",
25
+ "@akinon/ui-input-number": "1.3.3-next.1",
27
26
  "@akinon/ui-modal": "1.1.3-next.1",
27
+ "@akinon/ui-space": "1.3.3-next.1",
28
+ "@akinon/ui-select": "1.3.4-next.1",
28
29
  "@akinon/ui-pagination": "1.3.4-next.1",
29
- "@akinon/akidate": "1.1.3-next.0",
30
- "@akinon/ui-space": "1.3.3-next.1"
30
+ "@akinon/ui-typography": "1.1.2-next.0"
31
31
  },
32
32
  "devDependencies": {
33
33
  "clean-package": "2.2.0",
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",
39
38
  "@akinon/typescript-config": "1.1.1",
40
- "@akinon/vitest-config": "1.1.1",
41
- "@akinon/ui-theme": "1.1.3-next.1"
39
+ "@akinon/ui-theme": "1.1.3-next.1",
40
+ "@akinon/utils": "1.1.4-next.1",
41
+ "@akinon/vitest-config": "1.1.1"
42
42
  },
43
43
  "peerDependencies": {
44
44
  "react": "^18 || ^19",