@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.
- package/dist/cjs/akifilter.d.ts.map +1 -1
- package/dist/cjs/akifilter.js +91 -12
- package/dist/esm/akifilter.d.ts.map +1 -1
- package/dist/esm/akifilter.js +92 -13
- package/package.json +8 -8
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"akifilter.d.ts","sourceRoot":"","sources":["../../src/akifilter.tsx"],"names":[],"mappings":"AAAA,OAAO,cAAc,CAAC;AAGtB,OAAO,EAIL,WAAW,
|
|
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"}
|
package/dist/cjs/akifilter.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
//
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
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(
|
|
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,
|
|
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"}
|
package/dist/esm/akifilter.js
CHANGED
|
@@ -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,
|
|
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
|
-
|
|
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
|
-
//
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
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(
|
|
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.
|
|
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/
|
|
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/
|
|
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/
|
|
41
|
+
"@akinon/utils": "1.1.4-next.1"
|
|
42
42
|
},
|
|
43
43
|
"peerDependencies": {
|
|
44
44
|
"react": "^18 || ^19",
|