@akinon/akifilter 1.1.1-rc.0 → 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;AA2B1B,OAAO,KAAK,EAAkB,eAAe,EAAE,MAAM,SAAS,CAAC;AAY/D,KAAK,oBAAoB,GAAG,WAAW,CAAC;AA2BxC,MAAM,MAAM,cAAc,CACxB,YAAY,SAAS,oBAAoB,GAAG,oBAAoB,IAC9D;IACF;;OAEG;IACH,YAAY,CAAC,EAAE,eAAe,CAAC,YAAY,CAAC,CAAC;IAC7C;;OAEG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B;;OAEG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC,YAAY,CAAC,CAAC;IACtC;;OAEG;IACH,cAAc,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,CAAC,YAAY,CAAC,KAAK,IAAI,CAAC;IACzD;;OAEG;IACH,qBAAqB,CAAC,EAAE,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,KAAK,IAAI,CAAC;IAClE;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,IAAI,CAAC;IACzB;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,IAAI,CAAC;IACzB;;OAEG;IACH,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;IACxB;;OAEG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B;;OAEG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B,CAAC;AAirBF,eAAO,MAAM,SAAS;KACpB,YAAY,SAAS,oBAAoB,uBAElC,cAAc,CAAC,YAAY,CAAC;;CAmBpC,CAAC"}
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,54 @@ 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
+ };
66
+ /**
67
+ * Checks if a field should be disabled based on config.disabled property.
68
+ */
69
+ const checkIsDisabled = (field, formValues) => {
70
+ var _a;
71
+ const configDisabled = (_a = field.config) === null || _a === void 0 ? void 0 : _a.disabled;
72
+ if (typeof configDisabled === 'function') {
73
+ return configDisabled(formValues);
74
+ }
75
+ return Boolean(configDisabled);
76
+ };
77
+ /**
78
+ * Checks if a field should be visible based on config.visible property.
79
+ */
80
+ const checkIsVisible = (field, formValues) => {
81
+ var _a;
82
+ const configVisible = (_a = field.config) === null || _a === void 0 ? void 0 : _a.visible;
83
+ if (typeof configVisible === 'function') {
84
+ return configVisible(formValues);
85
+ }
86
+ return configVisible !== false;
87
+ };
40
88
  const resolveClearedFieldValue = (field, defaultValue) => {
41
89
  if (defaultValue !== undefined) {
42
90
  return defaultValue;
@@ -64,9 +112,15 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
64
112
  const schemaDefaults = react_1.default.useMemo(() => (0, schema_1.normaliseValuesBySchema)(flattenedSchema, (0, schema_1.extractDefaultValues)(flattenedSchema)), [flattenedSchema]);
65
113
  const externalDefaults = react_1.default.useMemo(() => (0, schema_1.normaliseValuesBySchema)(flattenedSchema, defaultValues), [flattenedSchema, defaultValues]);
66
114
  const baseDefaultValues = react_1.default.useMemo(() => (Object.assign(Object.assign({}, schemaDefaults), externalDefaults)), [schemaDefaults, externalDefaults]);
67
- 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(() => {
68
117
  var _a;
69
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));
70
124
  }, [flattenedSchema, regularFields, storageKey]);
71
125
  const mergedDefaultValues = react_1.default.useMemo(() => (Object.assign(Object.assign({}, baseDefaultValues), persistedDefaults)), [baseDefaultValues, persistedDefaults]);
72
126
  const formMethods = (0, akiform_1.useForm)({
@@ -109,12 +163,17 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
109
163
  if (field.type === 'select') {
110
164
  return resolveSelectLabel();
111
165
  }
166
+ // For custom fields with options (like custom selects), try to find the label
167
+ if (field.type === 'custom' && 'options' in field && field.options) {
168
+ return resolveSelectLabel();
169
+ }
112
170
  if (field.type === 'date') {
113
171
  const iso = akidate_1.akidate.toIsoDate(currentValue);
114
172
  if (iso) {
115
173
  // Use localized format with time if showTime is enabled
116
174
  const hasShowTime = 'showTime' in field && field.showTime;
117
- const format = hasShowTime ? 'L LTS' : 'YYYY-MM-DD';
175
+ // L = localized date, LTS = localized time with seconds
176
+ const format = hasShowTime ? 'L LTS' : 'L';
118
177
  return akidate_1.akidate.formatIsoDate(iso, format);
119
178
  }
120
179
  }
@@ -130,7 +189,8 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
130
189
  }
131
190
  const iso = akidate_1.akidate.toIsoDate(currentValue);
132
191
  if (iso) {
133
- return akidate_1.akidate.formatIsoDate(iso, constants_1.DATE_FORMAT);
192
+ // Use localized date format for fallback
193
+ return akidate_1.akidate.formatIsoDate(iso, 'L');
134
194
  }
135
195
  return String(currentValue);
136
196
  };
@@ -159,7 +219,20 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
159
219
  (0, storage_1.writeVisibleKeys)(storageKey, visibleKeys);
160
220
  onVisibleFieldsChange === null || onVisibleFieldsChange === void 0 ? void 0 : onVisibleFieldsChange(visibleKeys);
161
221
  }, [visibleKeys, onVisibleFieldsChange, storageKey]);
222
+ // Track previous mergedDefaultValues to avoid unnecessary resets
223
+ const previousMergedDefaultValuesRef = react_1.default.useRef(null);
162
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;
163
236
  formMethods.reset(mergedDefaultValues);
164
237
  }, [formMethods, mergedDefaultValues]);
165
238
  const normalisedValues = react_1.default.useMemo(() => (0, values_1.normaliseOutputValues)(flattenedSchema, formValues), [flattenedSchema, formValues]);
@@ -217,11 +290,43 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
217
290
  react_1.default.useEffect(() => {
218
291
  lastPersistedValuesRef.current = null;
219
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]);
220
325
  const handleClearAll = react_1.default.useCallback(() => {
221
- const clearedDefaults = Object.assign({}, baseDefaultValues);
326
+ const clearedDefaults = Object.assign({}, schemaDefaults);
222
327
  flattenedSchema.forEach(field => {
223
328
  const key = String(field.key);
224
- const defaultValue = baseDefaultValues[key];
329
+ const defaultValue = schemaDefaults[key];
225
330
  const resolved = resolveClearedFieldValue(field, defaultValue);
226
331
  if (resolved === undefined) {
227
332
  delete clearedDefaults[key];
@@ -242,7 +347,7 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
242
347
  lastPersistedValuesRef.current = nextSerialised !== null && nextSerialised !== void 0 ? nextSerialised : null;
243
348
  onValuesChange === null || onValuesChange === void 0 ? void 0 : onValuesChange(nextValues);
244
349
  }, [
245
- baseDefaultValues,
350
+ schemaDefaults,
246
351
  flattenedSchema,
247
352
  formMethods,
248
353
  onClearAll,
@@ -274,31 +379,33 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
274
379
  if (!schemaField) {
275
380
  return;
276
381
  }
277
- const defaultValue = baseDefaultValues[String(schemaField.key)];
278
- const fieldPath = schemaField.key;
382
+ const defaultValue = schemaDefaults[String(schemaField.key)];
279
383
  const nextValue = resolveClearedFieldValue(schemaField, defaultValue);
280
- // Update the form value
281
- formMethods.setValue(fieldPath, nextValue, {
282
- shouldDirty: false,
283
- shouldTouch: false,
284
- shouldValidate: false
285
- });
286
- // Compute the updated values immediately (don't wait for debounce)
287
- const currentValues = formMethods.getValues() || {};
288
- 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)
289
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
+ });
290
396
  const nextSerialised = JSON.stringify(nextValues);
291
397
  // Persist immediately (bypass debounce for remove action)
292
398
  persistValues(nextValues);
293
399
  // Update refs to mark this as the latest persisted state
294
400
  lastPersistedValuesRef.current = nextSerialised;
295
401
  currentSerialisedValuesRef.current = nextSerialised;
402
+ previousSerialisedValuesRef.current = nextSerialised; // Prevent immediate effect from re-emitting
296
403
  hasEmittedValuesRef.current = true;
297
404
  hasInitialValuesRef.current = Object.keys(nextValues).length > 0;
298
405
  // Emit the change to parent
299
406
  onValuesChange === null || onValuesChange === void 0 ? void 0 : onValuesChange(nextValues);
300
407
  }, [
301
- baseDefaultValues,
408
+ schemaDefaults,
302
409
  flattenedSchema,
303
410
  formMethods,
304
411
  onValuesChange,
@@ -320,22 +427,25 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
320
427
  const start = (modalPage - 1) * modalPageSize;
321
428
  return filteredFields.slice(start, start + modalPageSize);
322
429
  }, [filteredFields, modalPage, modalPageSize]);
323
- const visibleFields = react_1.default.useMemo(() => regularFields.filter(field => visibleKeys.includes(String(field.key))), [regularFields, visibleKeys]);
430
+ const visibleFields = react_1.default.useMemo(() => regularFields
431
+ .filter(field => visibleKeys.includes(String(field.key)))
432
+ .filter(field => checkIsVisible(field, formValues !== null && formValues !== void 0 ? formValues : {})), [regularFields, visibleKeys, formValues]);
324
433
  const renderFieldComponent = (field) => {
325
434
  const ariaLabel = (0, schema_1.getFieldAriaLabel)(field);
435
+ const isDisabled = checkIsDisabled(field, formValues !== null && formValues !== void 0 ? formValues : {});
326
436
  switch (field.type) {
327
437
  case 'text':
328
- return (react_1.default.createElement(ui_input_1.Input, { placeholder: field.placeholder, size: "large", allowClear: true, "aria-label": ariaLabel }));
438
+ return (react_1.default.createElement(ui_input_1.Input, { placeholder: field.placeholder, size: "large", allowClear: true, "aria-label": ariaLabel, disabled: isDisabled }));
329
439
  case 'number':
330
- return (react_1.default.createElement(ui_input_number_1.InputNumber, { placeholder: field.placeholder, size: "large", className: "akinon-filter__field--number", "aria-label": ariaLabel }));
440
+ 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 }));
331
441
  case 'select':
332
- return (react_1.default.createElement(ui_select_1.Select, { placeholder: field.placeholder, size: "large", options: field.options, showSearch: true, optionFilterProp: "label", "aria-label": ariaLabel }));
442
+ 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 }));
333
443
  case 'checkbox':
334
- return react_1.default.createElement(ui_checkbox_1.Checkbox, null, field.label);
444
+ return react_1.default.createElement(ui_checkbox_1.Checkbox, { disabled: isDisabled }, field.label);
335
445
  case 'date':
336
- return (react_1.default.createElement(ui_date_picker_1.DatePicker, { placeholder: field.placeholder, showTime: field.showTime, suffixIcon: "calendar", suffixIconSize: "16px", "aria-label": ariaLabel }));
446
+ 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 }));
337
447
  case 'textarea':
338
- return (react_1.default.createElement(ui_input_1.InputTextArea, { placeholder: field.placeholder, autoSize: { minRows: 3, maxRows: 6 }, "aria-label": ariaLabel }));
448
+ return (react_1.default.createElement(ui_input_1.InputTextArea, { placeholder: field.placeholder, autoSize: { minRows: 3, maxRows: 6 }, "aria-label": ariaLabel, disabled: isDisabled }));
339
449
  case 'custom':
340
450
  if (typeof field.render === 'function') {
341
451
  return field.render({
@@ -356,7 +466,7 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
356
466
  }
357
467
  };
358
468
  const renderFormField = (field) => {
359
- return (react_1.default.createElement(akiform_1.FormItem, { key: String(field.key), control: formMethods.control, name: field.key, tooltip: field.tooltip, help: field.help, labelDescription: field.labelDescription, required: Boolean(field.validation), className: "mb-0", valuePropName: field.type === 'checkbox' ? 'checked' : undefined }, renderFieldComponent(field)));
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)));
360
470
  };
361
471
  return (react_1.default.createElement(ui_card_1.Card, { size: "small", className: "akinon-filter shadow", "data-testid": "akifilter-root" },
362
472
  react_1.default.createElement(antd_1.ConfigProvider, { theme: theme_overrides_1.themeOverrides },
@@ -1,10 +1,26 @@
1
1
  import type { FieldValues, Path } from '@akinon/akiform';
2
2
  import type { FormField } from '@akinon/akiform-builder';
3
+ export interface AkifilterFieldConfig<TFieldValues extends FieldValues = FieldValues> {
4
+ /**
5
+ * Controls whether the field is disabled.
6
+ * Can be a boolean or a function that receives form values and returns a boolean.
7
+ */
8
+ disabled?: boolean | ((formValues: TFieldValues) => boolean);
9
+ /**
10
+ * Controls whether the field is visible.
11
+ * Can be a boolean or a function that receives form values and returns a boolean.
12
+ */
13
+ visible?: boolean | ((formValues: TFieldValues) => boolean);
14
+ }
3
15
  export type AkifilterField<TFieldValues extends FieldValues = FieldValues> = FormField<TFieldValues> & {
4
16
  /**
5
17
  * Controls whether the field is shown in the main filter form by default.
6
18
  */
7
19
  isVisible?: boolean;
20
+ /**
21
+ * Configuration options for field behavior (disabled, visible).
22
+ */
23
+ config?: AkifilterFieldConfig<TFieldValues>;
8
24
  };
9
25
  export type AkifilterFieldKey<TFieldValues extends FieldValues = FieldValues> = Path<TFieldValues>;
10
26
  export type AkifilterSchema<TFieldValues extends FieldValues = FieldValues> = AkifilterField<TFieldValues>[];
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AACzD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AAEzD,MAAM,MAAM,cAAc,CAAC,YAAY,SAAS,WAAW,GAAG,WAAW,IACvE,SAAS,CAAC,YAAY,CAAC,GAAG;IACxB;;OAEG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB,CAAC;AAEJ,MAAM,MAAM,iBAAiB,CAAC,YAAY,SAAS,WAAW,GAAG,WAAW,IAC1E,IAAI,CAAC,YAAY,CAAC,CAAC;AAErB,MAAM,MAAM,eAAe,CAAC,YAAY,SAAS,WAAW,GAAG,WAAW,IACxE,cAAc,CAAC,YAAY,CAAC,EAAE,CAAC"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AACzD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AAEzD,MAAM,WAAW,oBAAoB,CACnC,YAAY,SAAS,WAAW,GAAG,WAAW;IAE9C;;;OAGG;IACH,QAAQ,CAAC,EAAE,OAAO,GAAG,CAAC,CAAC,UAAU,EAAE,YAAY,KAAK,OAAO,CAAC,CAAC;IAC7D;;;OAGG;IACH,OAAO,CAAC,EAAE,OAAO,GAAG,CAAC,CAAC,UAAU,EAAE,YAAY,KAAK,OAAO,CAAC,CAAC;CAC7D;AAED,MAAM,MAAM,cAAc,CAAC,YAAY,SAAS,WAAW,GAAG,WAAW,IACvE,SAAS,CAAC,YAAY,CAAC,GAAG;IACxB;;OAEG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB;;OAEG;IACH,MAAM,CAAC,EAAE,oBAAoB,CAAC,YAAY,CAAC,CAAC;CAC7C,CAAC;AAEJ,MAAM,MAAM,iBAAiB,CAAC,YAAY,SAAS,WAAW,GAAG,WAAW,IAC1E,IAAI,CAAC,YAAY,CAAC,CAAC;AAErB,MAAM,MAAM,eAAe,CAAC,YAAY,SAAS,WAAW,GAAG,WAAW,IACxE,cAAc,CAAC,YAAY,CAAC,EAAE,CAAC"}
@@ -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;AA2B1B,OAAO,KAAK,EAAkB,eAAe,EAAE,MAAM,SAAS,CAAC;AAY/D,KAAK,oBAAoB,GAAG,WAAW,CAAC;AA2BxC,MAAM,MAAM,cAAc,CACxB,YAAY,SAAS,oBAAoB,GAAG,oBAAoB,IAC9D;IACF;;OAEG;IACH,YAAY,CAAC,EAAE,eAAe,CAAC,YAAY,CAAC,CAAC;IAC7C;;OAEG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B;;OAEG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC,YAAY,CAAC,CAAC;IACtC;;OAEG;IACH,cAAc,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,CAAC,YAAY,CAAC,KAAK,IAAI,CAAC;IACzD;;OAEG;IACH,qBAAqB,CAAC,EAAE,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,KAAK,IAAI,CAAC;IAClE;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,IAAI,CAAC;IACzB;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,IAAI,CAAC;IACzB;;OAEG;IACH,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;IACxB;;OAEG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B;;OAEG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B,CAAC;AAirBF,eAAO,MAAM,SAAS;KACpB,YAAY,SAAS,oBAAoB,uBAElC,cAAc,CAAC,YAAY,CAAC;;CAmBpC,CAAC"}
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';
@@ -29,11 +29,59 @@ import { themeOverrides } from './common/theme-overrides';
29
29
  import { AppliedFilters } from './components/applied-filters';
30
30
  import { FilterToolbar } from './components/filter-toolbar';
31
31
  import { VisibilityModal } from './components/visibility-modal';
32
- import { BOOLEAN_STRING, DATE_FORMAT, DEFAULT_MODAL_PAGE_SIZE, FILTER_DEBOUNCE_DELAY } from './constants';
32
+ import { BOOLEAN_STRING, DEFAULT_MODAL_PAGE_SIZE, FILTER_DEBOUNCE_DELAY } from './constants';
33
33
  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
+ };
63
+ /**
64
+ * Checks if a field should be disabled based on config.disabled property.
65
+ */
66
+ const checkIsDisabled = (field, formValues) => {
67
+ var _a;
68
+ const configDisabled = (_a = field.config) === null || _a === void 0 ? void 0 : _a.disabled;
69
+ if (typeof configDisabled === 'function') {
70
+ return configDisabled(formValues);
71
+ }
72
+ return Boolean(configDisabled);
73
+ };
74
+ /**
75
+ * Checks if a field should be visible based on config.visible property.
76
+ */
77
+ const checkIsVisible = (field, formValues) => {
78
+ var _a;
79
+ const configVisible = (_a = field.config) === null || _a === void 0 ? void 0 : _a.visible;
80
+ if (typeof configVisible === 'function') {
81
+ return configVisible(formValues);
82
+ }
83
+ return configVisible !== false;
84
+ };
37
85
  const resolveClearedFieldValue = (field, defaultValue) => {
38
86
  if (defaultValue !== undefined) {
39
87
  return defaultValue;
@@ -61,9 +109,15 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
61
109
  const schemaDefaults = React.useMemo(() => normaliseValuesBySchema(flattenedSchema, extractDefaultValues(flattenedSchema)), [flattenedSchema]);
62
110
  const externalDefaults = React.useMemo(() => normaliseValuesBySchema(flattenedSchema, defaultValues), [flattenedSchema, defaultValues]);
63
111
  const baseDefaultValues = React.useMemo(() => (Object.assign(Object.assign({}, schemaDefaults), externalDefaults)), [schemaDefaults, externalDefaults]);
64
- const persistedDefaults = React.useMemo(() => {
112
+ // Use state for persistedDefaults to make it reactive to localStorage changes
113
+ const [persistedDefaults, setPersistedDefaults] = React.useState(() => {
65
114
  var _a;
66
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));
67
121
  }, [flattenedSchema, regularFields, storageKey]);
68
122
  const mergedDefaultValues = React.useMemo(() => (Object.assign(Object.assign({}, baseDefaultValues), persistedDefaults)), [baseDefaultValues, persistedDefaults]);
69
123
  const formMethods = useForm({
@@ -106,12 +160,17 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
106
160
  if (field.type === 'select') {
107
161
  return resolveSelectLabel();
108
162
  }
163
+ // For custom fields with options (like custom selects), try to find the label
164
+ if (field.type === 'custom' && 'options' in field && field.options) {
165
+ return resolveSelectLabel();
166
+ }
109
167
  if (field.type === 'date') {
110
168
  const iso = akidate.toIsoDate(currentValue);
111
169
  if (iso) {
112
170
  // Use localized format with time if showTime is enabled
113
171
  const hasShowTime = 'showTime' in field && field.showTime;
114
- const format = hasShowTime ? 'L LTS' : 'YYYY-MM-DD';
172
+ // L = localized date, LTS = localized time with seconds
173
+ const format = hasShowTime ? 'L LTS' : 'L';
115
174
  return akidate.formatIsoDate(iso, format);
116
175
  }
117
176
  }
@@ -127,7 +186,8 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
127
186
  }
128
187
  const iso = akidate.toIsoDate(currentValue);
129
188
  if (iso) {
130
- return akidate.formatIsoDate(iso, DATE_FORMAT);
189
+ // Use localized date format for fallback
190
+ return akidate.formatIsoDate(iso, 'L');
131
191
  }
132
192
  return String(currentValue);
133
193
  };
@@ -156,7 +216,20 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
156
216
  writeVisibleKeys(storageKey, visibleKeys);
157
217
  onVisibleFieldsChange === null || onVisibleFieldsChange === void 0 ? void 0 : onVisibleFieldsChange(visibleKeys);
158
218
  }, [visibleKeys, onVisibleFieldsChange, storageKey]);
219
+ // Track previous mergedDefaultValues to avoid unnecessary resets
220
+ const previousMergedDefaultValuesRef = React.useRef(null);
159
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;
160
233
  formMethods.reset(mergedDefaultValues);
161
234
  }, [formMethods, mergedDefaultValues]);
162
235
  const normalisedValues = React.useMemo(() => normaliseOutputValues(flattenedSchema, formValues), [flattenedSchema, formValues]);
@@ -214,11 +287,43 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
214
287
  React.useEffect(() => {
215
288
  lastPersistedValuesRef.current = null;
216
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]);
217
322
  const handleClearAll = React.useCallback(() => {
218
- const clearedDefaults = Object.assign({}, baseDefaultValues);
323
+ const clearedDefaults = Object.assign({}, schemaDefaults);
219
324
  flattenedSchema.forEach(field => {
220
325
  const key = String(field.key);
221
- const defaultValue = baseDefaultValues[key];
326
+ const defaultValue = schemaDefaults[key];
222
327
  const resolved = resolveClearedFieldValue(field, defaultValue);
223
328
  if (resolved === undefined) {
224
329
  delete clearedDefaults[key];
@@ -239,7 +344,7 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
239
344
  lastPersistedValuesRef.current = nextSerialised !== null && nextSerialised !== void 0 ? nextSerialised : null;
240
345
  onValuesChange === null || onValuesChange === void 0 ? void 0 : onValuesChange(nextValues);
241
346
  }, [
242
- baseDefaultValues,
347
+ schemaDefaults,
243
348
  flattenedSchema,
244
349
  formMethods,
245
350
  onClearAll,
@@ -271,31 +376,33 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
271
376
  if (!schemaField) {
272
377
  return;
273
378
  }
274
- const defaultValue = baseDefaultValues[String(schemaField.key)];
275
- const fieldPath = schemaField.key;
379
+ const defaultValue = schemaDefaults[String(schemaField.key)];
276
380
  const nextValue = resolveClearedFieldValue(schemaField, defaultValue);
277
- // Update the form value
278
- formMethods.setValue(fieldPath, nextValue, {
279
- shouldDirty: false,
280
- shouldTouch: false,
281
- shouldValidate: false
282
- });
283
- // Compute the updated values immediately (don't wait for debounce)
284
- const currentValues = formMethods.getValues() || {};
285
- 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)
286
386
  const nextValues = normaliseOutputValues(flattenedSchema, updatedFormValues);
387
+ // Update form state
388
+ formMethods.reset(updatedFormValues, {
389
+ keepDirty: false,
390
+ keepTouched: false,
391
+ keepValues: false
392
+ });
287
393
  const nextSerialised = JSON.stringify(nextValues);
288
394
  // Persist immediately (bypass debounce for remove action)
289
395
  persistValues(nextValues);
290
396
  // Update refs to mark this as the latest persisted state
291
397
  lastPersistedValuesRef.current = nextSerialised;
292
398
  currentSerialisedValuesRef.current = nextSerialised;
399
+ previousSerialisedValuesRef.current = nextSerialised; // Prevent immediate effect from re-emitting
293
400
  hasEmittedValuesRef.current = true;
294
401
  hasInitialValuesRef.current = Object.keys(nextValues).length > 0;
295
402
  // Emit the change to parent
296
403
  onValuesChange === null || onValuesChange === void 0 ? void 0 : onValuesChange(nextValues);
297
404
  }, [
298
- baseDefaultValues,
405
+ schemaDefaults,
299
406
  flattenedSchema,
300
407
  formMethods,
301
408
  onValuesChange,
@@ -317,22 +424,25 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
317
424
  const start = (modalPage - 1) * modalPageSize;
318
425
  return filteredFields.slice(start, start + modalPageSize);
319
426
  }, [filteredFields, modalPage, modalPageSize]);
320
- const visibleFields = React.useMemo(() => regularFields.filter(field => visibleKeys.includes(String(field.key))), [regularFields, visibleKeys]);
427
+ const visibleFields = React.useMemo(() => regularFields
428
+ .filter(field => visibleKeys.includes(String(field.key)))
429
+ .filter(field => checkIsVisible(field, formValues !== null && formValues !== void 0 ? formValues : {})), [regularFields, visibleKeys, formValues]);
321
430
  const renderFieldComponent = (field) => {
322
431
  const ariaLabel = getFieldAriaLabel(field);
432
+ const isDisabled = checkIsDisabled(field, formValues !== null && formValues !== void 0 ? formValues : {});
323
433
  switch (field.type) {
324
434
  case 'text':
325
- return (React.createElement(Input, { placeholder: field.placeholder, size: "large", allowClear: true, "aria-label": ariaLabel }));
435
+ return (React.createElement(Input, { placeholder: field.placeholder, size: "large", allowClear: true, "aria-label": ariaLabel, disabled: isDisabled }));
326
436
  case 'number':
327
- return (React.createElement(InputNumber, { placeholder: field.placeholder, size: "large", className: "akinon-filter__field--number", "aria-label": ariaLabel }));
437
+ return (React.createElement(InputNumber, { placeholder: field.placeholder, size: "large", className: "akinon-filter__field--number", "aria-label": ariaLabel, disabled: isDisabled }));
328
438
  case 'select':
329
- return (React.createElement(Select, { placeholder: field.placeholder, size: "large", options: field.options, showSearch: true, optionFilterProp: "label", "aria-label": ariaLabel }));
439
+ return (React.createElement(Select, { placeholder: field.placeholder, size: "large", options: field.options, showSearch: true, optionFilterProp: "label", "aria-label": ariaLabel, disabled: isDisabled }));
330
440
  case 'checkbox':
331
- return React.createElement(Checkbox, null, field.label);
441
+ return React.createElement(Checkbox, { disabled: isDisabled }, field.label);
332
442
  case 'date':
333
- return (React.createElement(DatePicker, { placeholder: field.placeholder, showTime: field.showTime, suffixIcon: "calendar", suffixIconSize: "16px", "aria-label": ariaLabel }));
443
+ return (React.createElement(DatePicker, { placeholder: field.placeholder, showTime: field.showTime, suffixIcon: "calendar", suffixIconSize: "16px", "aria-label": ariaLabel, disabled: isDisabled }));
334
444
  case 'textarea':
335
- return (React.createElement(InputTextArea, { placeholder: field.placeholder, autoSize: { minRows: 3, maxRows: 6 }, "aria-label": ariaLabel }));
445
+ return (React.createElement(InputTextArea, { placeholder: field.placeholder, autoSize: { minRows: 3, maxRows: 6 }, "aria-label": ariaLabel, disabled: isDisabled }));
336
446
  case 'custom':
337
447
  if (typeof field.render === 'function') {
338
448
  return field.render({
@@ -353,7 +463,7 @@ const AkifilterContent = ({ filterSchema, storageNamespace, defaultValues, onVal
353
463
  }
354
464
  };
355
465
  const renderFormField = (field) => {
356
- return (React.createElement(FormItem, { key: String(field.key), control: formMethods.control, name: field.key, tooltip: field.tooltip, help: field.help, labelDescription: field.labelDescription, required: Boolean(field.validation), className: "mb-0", valuePropName: field.type === 'checkbox' ? 'checked' : undefined }, renderFieldComponent(field)));
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)));
357
467
  };
358
468
  return (React.createElement(Card, { size: "small", className: "akinon-filter shadow", "data-testid": "akifilter-root" },
359
469
  React.createElement(ConfigProvider, { theme: themeOverrides },
@@ -1,10 +1,26 @@
1
1
  import type { FieldValues, Path } from '@akinon/akiform';
2
2
  import type { FormField } from '@akinon/akiform-builder';
3
+ export interface AkifilterFieldConfig<TFieldValues extends FieldValues = FieldValues> {
4
+ /**
5
+ * Controls whether the field is disabled.
6
+ * Can be a boolean or a function that receives form values and returns a boolean.
7
+ */
8
+ disabled?: boolean | ((formValues: TFieldValues) => boolean);
9
+ /**
10
+ * Controls whether the field is visible.
11
+ * Can be a boolean or a function that receives form values and returns a boolean.
12
+ */
13
+ visible?: boolean | ((formValues: TFieldValues) => boolean);
14
+ }
3
15
  export type AkifilterField<TFieldValues extends FieldValues = FieldValues> = FormField<TFieldValues> & {
4
16
  /**
5
17
  * Controls whether the field is shown in the main filter form by default.
6
18
  */
7
19
  isVisible?: boolean;
20
+ /**
21
+ * Configuration options for field behavior (disabled, visible).
22
+ */
23
+ config?: AkifilterFieldConfig<TFieldValues>;
8
24
  };
9
25
  export type AkifilterFieldKey<TFieldValues extends FieldValues = FieldValues> = Path<TFieldValues>;
10
26
  export type AkifilterSchema<TFieldValues extends FieldValues = FieldValues> = AkifilterField<TFieldValues>[];
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AACzD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AAEzD,MAAM,MAAM,cAAc,CAAC,YAAY,SAAS,WAAW,GAAG,WAAW,IACvE,SAAS,CAAC,YAAY,CAAC,GAAG;IACxB;;OAEG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB,CAAC;AAEJ,MAAM,MAAM,iBAAiB,CAAC,YAAY,SAAS,WAAW,GAAG,WAAW,IAC1E,IAAI,CAAC,YAAY,CAAC,CAAC;AAErB,MAAM,MAAM,eAAe,CAAC,YAAY,SAAS,WAAW,GAAG,WAAW,IACxE,cAAc,CAAC,YAAY,CAAC,EAAE,CAAC"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AACzD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AAEzD,MAAM,WAAW,oBAAoB,CACnC,YAAY,SAAS,WAAW,GAAG,WAAW;IAE9C;;;OAGG;IACH,QAAQ,CAAC,EAAE,OAAO,GAAG,CAAC,CAAC,UAAU,EAAE,YAAY,KAAK,OAAO,CAAC,CAAC;IAC7D;;;OAGG;IACH,OAAO,CAAC,EAAE,OAAO,GAAG,CAAC,CAAC,UAAU,EAAE,YAAY,KAAK,OAAO,CAAC,CAAC;CAC7D;AAED,MAAM,MAAM,cAAc,CAAC,YAAY,SAAS,WAAW,GAAG,WAAW,IACvE,SAAS,CAAC,YAAY,CAAC,GAAG;IACxB;;OAEG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB;;OAEG;IACH,MAAM,CAAC,EAAE,oBAAoB,CAAC,YAAY,CAAC,CAAC;CAC7C,CAAC;AAEJ,MAAM,MAAM,iBAAiB,CAAC,YAAY,SAAS,WAAW,GAAG,WAAW,IAC1E,IAAI,CAAC,YAAY,CAAC,CAAC;AAErB,MAAM,MAAM,eAAe,CAAC,YAAY,SAAS,WAAW,GAAG,WAAW,IACxE,cAAc,CAAC,YAAY,CAAC,EAAE,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@akinon/akifilter",
3
- "version": "1.1.1-rc.0",
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,32 +13,32 @@
13
13
  "antd": "^5.27.0",
14
14
  "react-error-boundary": "^6.0.0",
15
15
  "@akinon/akiform": "1.1.2",
16
- "@akinon/akidate": "1.1.2",
17
- "@akinon/icons": "1.1.2-rc.0",
16
+ "@akinon/akidate": "1.1.3-next.0",
17
+ "@akinon/ui-button": "1.4.0-next.1",
18
+ "@akinon/icons": "1.1.2-next.1",
19
+ "@akinon/ui-card": "1.1.3-next.1",
20
+ "@akinon/ui-checkbox": "1.3.3-next.1",
18
21
  "@akinon/akilocale": "1.2.1",
19
- "@akinon/ui-button": "1.4.0-rc.0",
20
- "@akinon/ui-card": "1.1.3-rc.0",
21
- "@akinon/ui-checkbox": "1.3.3-rc.0",
22
- "@akinon/ui-date-picker": "1.3.3-rc.0",
23
- "@akinon/ui-collapse": "1.3.2-rc.0",
24
- "@akinon/ui-input": "1.1.3-rc.0",
25
- "@akinon/ui-typography": "1.1.1",
26
- "@akinon/ui-input-number": "1.3.3-rc.0",
27
- "@akinon/ui-select": "1.3.4-rc.0",
28
- "@akinon/ui-pagination": "1.3.4-rc.0",
29
- "@akinon/ui-modal": "1.1.3-rc.0",
30
- "@akinon/ui-space": "1.3.3-rc.0"
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",
25
+ "@akinon/ui-date-picker": "1.3.3-next.1",
26
+ "@akinon/ui-input-number": "1.3.3-next.1",
27
+ "@akinon/ui-select": "1.3.4-next.1",
28
+ "@akinon/ui-pagination": "1.3.4-next.1",
29
+ "@akinon/ui-typography": "1.1.2-next.0",
30
+ "@akinon/ui-space": "1.3.3-next.1"
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/akiform-builder": "1.3.5-rc.0",
38
- "@akinon/utils": "1.1.4-rc.0",
39
- "@akinon/ui-theme": "1.1.3-rc.0",
37
+ "@akinon/akiform-builder": "1.3.5-next.1",
38
+ "@akinon/ui-theme": "1.1.3-next.1",
39
+ "@akinon/typescript-config": "1.1.1",
40
40
  "@akinon/vitest-config": "1.1.1",
41
- "@akinon/typescript-config": "1.1.1"
41
+ "@akinon/utils": "1.1.4-next.1"
42
42
  },
43
43
  "peerDependencies": {
44
44
  "react": "^18 || ^19",