@akinon/akiform-builder 0.7.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/dist/cjs/__tests__/akiform-builder.test.d.ts +2 -0
  2. package/dist/cjs/__tests__/akiform-builder.test.d.ts.map +1 -0
  3. package/dist/cjs/__tests__/akiform-builder.test.js +1258 -0
  4. package/dist/cjs/__tests__/field-builder.test.d.ts +2 -0
  5. package/dist/cjs/__tests__/field-builder.test.d.ts.map +1 -0
  6. package/dist/cjs/__tests__/field-builder.test.js +251 -0
  7. package/dist/cjs/index.css +7 -0
  8. package/dist/cjs/src/akiform-builder.d.ts +7 -0
  9. package/dist/cjs/src/akiform-builder.d.ts.map +1 -0
  10. package/dist/cjs/src/akiform-builder.js +291 -0
  11. package/dist/cjs/src/field-builder.d.ts +37 -0
  12. package/dist/cjs/src/field-builder.d.ts.map +1 -0
  13. package/dist/cjs/src/field-builder.js +94 -0
  14. package/dist/cjs/src/i18n/index.d.ts +5 -0
  15. package/dist/cjs/src/i18n/index.d.ts.map +1 -0
  16. package/dist/cjs/src/i18n/index.js +14 -0
  17. package/dist/cjs/src/i18n/translations/en.d.ts +7 -0
  18. package/dist/cjs/src/i18n/translations/en.d.ts.map +1 -0
  19. package/dist/cjs/src/i18n/translations/en.js +8 -0
  20. package/dist/cjs/src/i18n/translations/tr.d.ts +7 -0
  21. package/dist/cjs/src/i18n/translations/tr.d.ts.map +1 -0
  22. package/dist/cjs/src/i18n/translations/tr.js +8 -0
  23. package/dist/cjs/src/index.d.ts +4 -0
  24. package/dist/cjs/src/index.d.ts.map +1 -0
  25. package/dist/cjs/src/index.js +21 -0
  26. package/dist/cjs/src/types.d.ts +84 -0
  27. package/dist/cjs/src/types.d.ts.map +1 -0
  28. package/dist/cjs/src/types.js +2 -0
  29. package/dist/esm/__tests__/akiform-builder.test.d.ts +2 -0
  30. package/dist/esm/__tests__/akiform-builder.test.d.ts.map +1 -0
  31. package/dist/esm/__tests__/akiform-builder.test.js +1256 -0
  32. package/dist/esm/__tests__/field-builder.test.d.ts +2 -0
  33. package/dist/esm/__tests__/field-builder.test.d.ts.map +1 -0
  34. package/dist/esm/__tests__/field-builder.test.js +249 -0
  35. package/dist/esm/index.css +7 -0
  36. package/dist/esm/src/akiform-builder.d.ts +7 -0
  37. package/dist/esm/src/akiform-builder.d.ts.map +1 -0
  38. package/dist/esm/src/akiform-builder.js +288 -0
  39. package/dist/esm/src/field-builder.d.ts +37 -0
  40. package/dist/esm/src/field-builder.d.ts.map +1 -0
  41. package/dist/esm/src/field-builder.js +91 -0
  42. package/dist/esm/src/i18n/index.d.ts +5 -0
  43. package/dist/esm/src/i18n/index.d.ts.map +1 -0
  44. package/dist/esm/src/i18n/index.js +11 -0
  45. package/dist/esm/src/i18n/translations/en.d.ts +7 -0
  46. package/dist/esm/src/i18n/translations/en.d.ts.map +1 -0
  47. package/dist/esm/src/i18n/translations/en.js +6 -0
  48. package/dist/esm/src/i18n/translations/tr.d.ts +7 -0
  49. package/dist/esm/src/i18n/translations/tr.d.ts.map +1 -0
  50. package/dist/esm/src/i18n/translations/tr.js +6 -0
  51. package/dist/esm/src/index.d.ts +4 -0
  52. package/dist/esm/src/index.d.ts.map +1 -0
  53. package/dist/esm/src/index.js +3 -0
  54. package/dist/esm/src/types.d.ts +84 -0
  55. package/dist/esm/src/types.d.ts.map +1 -0
  56. package/dist/esm/src/types.js +1 -0
  57. package/package.json +18 -16
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=field-builder.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"field-builder.test.d.ts","sourceRoot":"","sources":["../../../__tests__/field-builder.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,249 @@
1
+ import { akival } from '@akinon/akival';
2
+ import { field } from '../src/field-builder';
3
+ describe('FieldBuilder', () => {
4
+ it('should build a text field with all properties', () => {
5
+ const textField = field()
6
+ .key('name')
7
+ .label('Name')
8
+ .type('text')
9
+ .placeholder('Enter your name')
10
+ .defaultValue('John Doe')
11
+ .validation(akival.string().required())
12
+ .config({ disabled: false, visible: true })
13
+ .build();
14
+ expect(textField).toEqual({
15
+ key: 'name',
16
+ label: 'Name',
17
+ type: 'text',
18
+ placeholder: 'Enter your name',
19
+ defaultValue: 'John Doe',
20
+ validation: expect.any(Object),
21
+ config: { disabled: false, visible: true }
22
+ });
23
+ });
24
+ it('should build a number field', () => {
25
+ const numberField = field()
26
+ .key('age')
27
+ .label('Age')
28
+ .type('number')
29
+ .placeholder('Enter your age')
30
+ .defaultValue(18)
31
+ .validation(akival.number().min(18))
32
+ .build();
33
+ expect(numberField).toEqual({
34
+ key: 'age',
35
+ label: 'Age',
36
+ type: 'number',
37
+ placeholder: 'Enter your age',
38
+ defaultValue: 18,
39
+ validation: expect.any(Object)
40
+ });
41
+ });
42
+ it('should build a select field', () => {
43
+ const selectField = field()
44
+ .key('country')
45
+ .label('Country')
46
+ .type('select')
47
+ .options([
48
+ { value: 'us', label: 'United States' },
49
+ { value: 'ca', label: 'Canada' }
50
+ ])
51
+ .defaultValue('us')
52
+ .build();
53
+ expect(selectField).toEqual({
54
+ key: 'country',
55
+ label: 'Country',
56
+ type: 'select',
57
+ options: [
58
+ { value: 'us', label: 'United States' },
59
+ { value: 'ca', label: 'Canada' }
60
+ ],
61
+ defaultValue: 'us'
62
+ });
63
+ });
64
+ it('should build a checkbox field', () => {
65
+ const checkboxField = field()
66
+ .key('subscribe')
67
+ .label('Subscribe to newsletter')
68
+ .type('checkbox')
69
+ .defaultValue(false)
70
+ .build();
71
+ expect(checkboxField).toEqual({
72
+ key: 'subscribe',
73
+ label: 'Subscribe to newsletter',
74
+ type: 'checkbox',
75
+ defaultValue: false
76
+ });
77
+ });
78
+ it('should build a date field', () => {
79
+ const dateField = field()
80
+ .key('birthdate')
81
+ .label('Birth Date')
82
+ .type('date')
83
+ .placeholder('Select your birth date')
84
+ .build();
85
+ expect(dateField).toEqual({
86
+ key: 'birthdate',
87
+ label: 'Birth Date',
88
+ type: 'date',
89
+ placeholder: 'Select your birth date'
90
+ });
91
+ });
92
+ it('should build a textarea field', () => {
93
+ const textareaField = field()
94
+ .key('description')
95
+ .label('Description')
96
+ .type('textarea')
97
+ .placeholder('Enter a description')
98
+ .build();
99
+ expect(textareaField).toEqual({
100
+ key: 'description',
101
+ label: 'Description',
102
+ type: 'textarea',
103
+ placeholder: 'Enter a description'
104
+ });
105
+ });
106
+ it('should build a custom field', () => {
107
+ const renderFn = vi.fn();
108
+ const customField = field()
109
+ .key('custom')
110
+ .label('Custom Field')
111
+ .type('custom')
112
+ .render(renderFn)
113
+ .build();
114
+ expect(customField).toEqual({
115
+ key: 'custom',
116
+ label: 'Custom Field',
117
+ type: 'custom',
118
+ render: renderFn
119
+ });
120
+ });
121
+ it('should throw an error if required properties are missing', () => {
122
+ expect(() => field().build()).toThrow('Field must have at least a key, label, and type');
123
+ expect(() => field().key('test').build()).toThrow('Field must have at least a key, label, and type');
124
+ expect(() => field().key('test').label('Test').build()).toThrow('Field must have at least a key, label, and type');
125
+ });
126
+ it('should only allow options for select fields', () => {
127
+ const selectField = field()
128
+ .key('country')
129
+ .label('Country')
130
+ .type('select')
131
+ .options([
132
+ { value: 'us', label: 'United States' },
133
+ { value: 'ca', label: 'Canada' }
134
+ ])
135
+ .build();
136
+ expect(selectField).toEqual({
137
+ key: 'country',
138
+ label: 'Country',
139
+ type: 'select',
140
+ options: [
141
+ { value: 'us', label: 'United States' },
142
+ { value: 'ca', label: 'Canada' }
143
+ ]
144
+ });
145
+ // @ts-expect-error : We are explicitly waiting .options to throw an error.
146
+ field().key('name').label('Name').type('text').options([]);
147
+ });
148
+ it('should only allow render for custom fields', () => {
149
+ const renderFn = vi.fn();
150
+ const customField = field()
151
+ .key('custom')
152
+ .label('Custom Field')
153
+ .type('custom')
154
+ .render(renderFn)
155
+ .build();
156
+ expect(customField).toEqual({
157
+ key: 'custom',
158
+ label: 'Custom Field',
159
+ type: 'custom',
160
+ render: renderFn
161
+ });
162
+ // @ts-expect-error : We are explicitly waiting .render to throw an error.
163
+ field().key('name').label('Name').type('text').render(vi.fn());
164
+ });
165
+ it('should allow chaining methods in any order', () => {
166
+ const textField = field()
167
+ .type('text')
168
+ .key('name')
169
+ .label('Name')
170
+ .placeholder('Enter your name')
171
+ .build();
172
+ expect(textField).toEqual({
173
+ key: 'name',
174
+ label: 'Name',
175
+ type: 'text',
176
+ placeholder: 'Enter your name'
177
+ });
178
+ });
179
+ it('should allow building fields with minimal properties', () => {
180
+ const minimalField = field()
181
+ .key('minimal')
182
+ .label('Minimal')
183
+ .type('text')
184
+ .build();
185
+ expect(minimalField).toEqual({
186
+ key: 'minimal',
187
+ label: 'Minimal',
188
+ type: 'text'
189
+ });
190
+ });
191
+ it('should build a field array with fields', () => {
192
+ const fieldArray = field()
193
+ .key('addresses')
194
+ .label('Addresses')
195
+ .type('fieldArray')
196
+ .fields([
197
+ field().key('street').label('Street').type('text').build(),
198
+ field().key('city').label('City').type('text').build()
199
+ ])
200
+ .build();
201
+ expect(fieldArray).toEqual({
202
+ key: 'addresses',
203
+ label: 'Addresses',
204
+ type: 'fieldArray',
205
+ fields: [
206
+ { key: 'street', label: 'Street', type: 'text' },
207
+ { key: 'city', label: 'City', type: 'text' }
208
+ ]
209
+ });
210
+ });
211
+ it('should build a section field with fields', () => {
212
+ const sectionField = field()
213
+ .key('personalInfo')
214
+ .label('Personal Information')
215
+ .type('section')
216
+ .fields([
217
+ field().key('name').label('Name').type('text').build(),
218
+ field().key('age').label('Age').type('number').build()
219
+ ])
220
+ .defaultExpanded(true)
221
+ .build();
222
+ expect(sectionField).toEqual({
223
+ key: 'personalInfo',
224
+ label: 'Personal Information',
225
+ type: 'section',
226
+ fields: [
227
+ { key: 'name', label: 'Name', type: 'text' },
228
+ { key: 'age', label: 'Age', type: 'number' }
229
+ ],
230
+ defaultExpanded: true
231
+ });
232
+ });
233
+ it('should not set fields for non-fieldArray and non-section types', () => {
234
+ const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => { });
235
+ const textField = field()
236
+ .key('name')
237
+ .label('Name')
238
+ .type('text')
239
+ .fields([field().key('invalid').label('Invalid').type('text').build()])
240
+ .build();
241
+ expect(textField).toEqual({
242
+ key: 'name',
243
+ label: 'Name',
244
+ type: 'text'
245
+ });
246
+ expect(consoleSpy).toHaveBeenCalledWith("Fields can only be set for 'fieldArray' or 'section' types. Current type: text");
247
+ consoleSpy.mockRestore();
248
+ });
249
+ });
@@ -0,0 +1,7 @@
1
+ .akiform-builder .sr-only {
2
+ display: none;
3
+ }
4
+
5
+ .akiform-builder-field-array {
6
+ padding-bottom: 1rem;
7
+ }
@@ -0,0 +1,7 @@
1
+ import './index.css';
2
+ import { FieldValues } from '@akinon/akiform';
3
+ import React from 'react';
4
+ import { AkiformBuilderProps, AkiformBuilderRef } from './types';
5
+ export declare const THROTTLE_DELAY = 300;
6
+ export declare const AkiformBuilder: React.ForwardRefExoticComponent<AkiformBuilderProps<FieldValues> & React.RefAttributes<AkiformBuilderRef<FieldValues>>>;
7
+ //# sourceMappingURL=akiform-builder.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"akiform-builder.d.ts","sourceRoot":"","sources":["../../../src/akiform-builder.tsx"],"names":[],"mappings":"AAAA,OAAO,aAAa,CAAC;AAErB,OAAO,EAKL,WAAW,EAMZ,MAAM,iBAAiB,CAAC;AAWzB,OAAO,KAWN,MAAM,OAAO,CAAC;AAGf,OAAO,EACL,mBAAmB,EACnB,iBAAiB,EAMlB,MAAM,SAAS,CAAC;AAEjB,eAAO,MAAM,cAAc,MAAM,CAAC;AAgJlC,eAAO,MAAM,cAAc,yHAwS1B,CAAC"}
@@ -0,0 +1,288 @@
1
+ var __rest = (this && this.__rest) || function (s, e) {
2
+ var t = {};
3
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
4
+ t[p] = s[p];
5
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
6
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
7
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
8
+ t[p[i]] = s[p[i]];
9
+ }
10
+ return t;
11
+ };
12
+ import './index.css';
13
+ import { Akiform, akivalResolver, FormItem, useFieldArray, useForm, useWatch } from '@akinon/akiform';
14
+ import { akival } from '@akinon/akival';
15
+ import { Button } from '@akinon/ui-button';
16
+ import { Checkbox } from '@akinon/ui-checkbox';
17
+ import { Collapse } from '@akinon/ui-collapse';
18
+ import { DatePicker } from '@akinon/ui-date-picker';
19
+ import { Input, InputTextArea } from '@akinon/ui-input';
20
+ import { InputNumber } from '@akinon/ui-input-number';
21
+ import { Select } from '@akinon/ui-select';
22
+ import { Space } from '@akinon/ui-space';
23
+ import { Title } from '@akinon/ui-typography';
24
+ import React, { forwardRef, Fragment, useCallback, useEffect, useImperativeHandle, useMemo, useRef } from 'react';
25
+ import { i18n } from './i18n';
26
+ export const THROTTLE_DELAY = 300; // ms
27
+ const SectionComponent = ({ field, control, formValues, formState, layout, layoutOptions }) => {
28
+ return (React.createElement(Collapse, { defaultActiveKey: field.defaultExpanded ? [field.key] : [], items: [
29
+ {
30
+ label: field.label,
31
+ key: field.key,
32
+ children: field.fields.map(nestedField => {
33
+ var _a, _b;
34
+ const isVisible = typeof ((_a = nestedField.config) === null || _a === void 0 ? void 0 : _a.visible) === 'function'
35
+ ? nestedField.config.visible(formValues)
36
+ : ((_b = nestedField.config) === null || _b === void 0 ? void 0 : _b.visible) !== false;
37
+ if (!isVisible) {
38
+ return null;
39
+ }
40
+ return (React.createElement(FormItem, { key: nestedField.key, control: control, name: nestedField.key, label: nestedField.label }, renderField(nestedField, control, formValues, formState, layout, layoutOptions)));
41
+ })
42
+ }
43
+ ] }));
44
+ };
45
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
46
+ const renderField = (field, control, formValues,
47
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
48
+ formState, layout, layoutOptions) => {
49
+ const commonProps = {
50
+ 'aria-required': field.validation ? true : false,
51
+ 'aria-invalid': formState.errors[field.key] ? true : false
52
+ };
53
+ switch (field.type) {
54
+ case 'text':
55
+ return (React.createElement(Input, Object.assign({ placeholder: field.placeholder, size: "large" }, commonProps)));
56
+ case 'number':
57
+ return (React.createElement(InputNumber, Object.assign({ placeholder: field.placeholder, size: "large" }, commonProps)));
58
+ case 'select':
59
+ return (React.createElement(Select, Object.assign({ placeholder: field.placeholder, options: field.options }, commonProps)));
60
+ case 'checkbox':
61
+ return (React.createElement(Checkbox, Object.assign({ checked: formValues[field.key] }, commonProps), field.label));
62
+ case 'date':
63
+ return (React.createElement(DatePicker, Object.assign({ placeholder: field.placeholder, size: "large" }, commonProps)));
64
+ case 'textarea':
65
+ return React.createElement(InputTextArea, Object.assign({ placeholder: field.placeholder }, commonProps));
66
+ case 'fieldArray':
67
+ return (React.createElement(FieldArrayComponent, { field: field, control: control, formValues: formValues, formState: formState, layout: layout, layoutOptions: layoutOptions }));
68
+ case 'custom':
69
+ if (typeof field.render === 'function') {
70
+ return field.render({ field, formValues, control, formState });
71
+ }
72
+ console.warn(`Custom field "${field.key}" has no render function`);
73
+ return React.createElement(Fragment, null);
74
+ case 'section':
75
+ return (React.createElement(SectionComponent, { field: field, control: control, formValues: formValues, formState: formState, layout: layout, layoutOptions: layoutOptions }));
76
+ default:
77
+ return React.createElement(Fragment, null);
78
+ }
79
+ };
80
+ export const AkiformBuilder = forwardRef((_a, ref) => {
81
+ var _b, _c;
82
+ var { fields, onSubmit, layout = 'vertical', layoutOptions, showResetButton = false, onReset, controlled = false, values, onValueChange } = _a, rest = __rest(_a, ["fields", "onSubmit", "layout", "layoutOptions", "showResetButton", "onReset", "controlled", "values", "onValueChange"]);
83
+ const validationSchema = useMemo(() => {
84
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
85
+ const schema = {};
86
+ fields.forEach(field => {
87
+ if (field.validation) {
88
+ schema[field.key] = field.validation;
89
+ }
90
+ if (field.type === 'fieldArray') {
91
+ schema[field.key] = akival.array().of(akival.object().shape(field.fields.reduce((acc, nestedField) => {
92
+ if (nestedField.validation) {
93
+ acc[nestedField.key] = nestedField.validation;
94
+ }
95
+ return acc;
96
+ },
97
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
98
+ {})));
99
+ }
100
+ });
101
+ return akival.object().shape(schema);
102
+ }, [fields]);
103
+ const { control, handleSubmit, reset, setValue, formState } = useForm(Object.assign({
104
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
105
+ resolver: akivalResolver(validationSchema),
106
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
107
+ defaultValues: rest.initialValues }, (controlled && { values: values })));
108
+ const formValues = useWatch({ control });
109
+ const prevFormValuesRef = useRef(null);
110
+ const isInitialRenderRef = useRef(true);
111
+ const throttleTimeoutRef = useRef(null);
112
+ const handleValueChange = useCallback((values) => {
113
+ if (!controlled) {
114
+ if (throttleTimeoutRef.current) {
115
+ clearTimeout(throttleTimeoutRef.current);
116
+ }
117
+ throttleTimeoutRef.current = setTimeout(() => {
118
+ onValueChange === null || onValueChange === void 0 ? void 0 : onValueChange(values);
119
+ }, THROTTLE_DELAY);
120
+ }
121
+ else {
122
+ onValueChange === null || onValueChange === void 0 ? void 0 : onValueChange(values);
123
+ }
124
+ }, [controlled, onValueChange]);
125
+ useEffect(() => {
126
+ if (isInitialRenderRef.current) {
127
+ isInitialRenderRef.current = false;
128
+ prevFormValuesRef.current = Object.assign({}, formValues);
129
+ return;
130
+ }
131
+ if (formState.isDirty ||
132
+ Object.keys(formState.touchedFields).length > 0) {
133
+ const hasChanged = JSON.stringify(formValues) !==
134
+ JSON.stringify(prevFormValuesRef.current);
135
+ if (hasChanged) {
136
+ handleValueChange(formValues);
137
+ prevFormValuesRef.current = Object.assign({}, formValues);
138
+ }
139
+ }
140
+ }, [
141
+ formValues,
142
+ formState.isDirty,
143
+ formState.touchedFields,
144
+ handleValueChange
145
+ ]);
146
+ useEffect(() => {
147
+ return () => {
148
+ if (throttleTimeoutRef.current) {
149
+ clearTimeout(throttleTimeoutRef.current);
150
+ }
151
+ };
152
+ }, []);
153
+ useEffect(() => {
154
+ if (controlled && onValueChange) {
155
+ onValueChange(formValues);
156
+ }
157
+ }, [controlled, onValueChange, formValues]);
158
+ const formItemLayout = useMemo(() => {
159
+ const defaultHorizontalLayout = {
160
+ labelCol: { span: 6 },
161
+ wrapperCol: { span: 18 }
162
+ };
163
+ const defaultInlineLayout = {
164
+ wrapperCol: { span: 24 }
165
+ };
166
+ switch (layout) {
167
+ case 'horizontal':
168
+ return {
169
+ labelCol: (layoutOptions === null || layoutOptions === void 0 ? void 0 : layoutOptions.labelCol) || defaultHorizontalLayout.labelCol,
170
+ wrapperCol: (layoutOptions === null || layoutOptions === void 0 ? void 0 : layoutOptions.wrapperCol) || defaultHorizontalLayout.wrapperCol
171
+ };
172
+ case 'inline':
173
+ return {
174
+ wrapperCol: (layoutOptions === null || layoutOptions === void 0 ? void 0 : layoutOptions.wrapperCol) || defaultInlineLayout.wrapperCol
175
+ };
176
+ case 'vertical':
177
+ default:
178
+ return {};
179
+ }
180
+ }, [layout, layoutOptions]);
181
+ const handleReset = (event) => {
182
+ event.preventDefault();
183
+ reset();
184
+ if (onReset) {
185
+ // Create a synthetic FormEvent if the event is a MouseEvent
186
+ const formEvent = event.type === 'click'
187
+ ? {
188
+ preventDefault: () => { },
189
+ target: event.target.closest('form')
190
+ }
191
+ : event;
192
+ onReset(formEvent);
193
+ }
194
+ };
195
+ useImperativeHandle(ref, () => ({
196
+ reset: (values) => {
197
+ if (values) {
198
+ // Partial reset: only reset specified fields
199
+ Object.keys(values).forEach(key => {
200
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
201
+ setValue(key, values[key]);
202
+ });
203
+ }
204
+ else {
205
+ // Full reset
206
+ reset();
207
+ }
208
+ if (onReset) {
209
+ const syntheticEvent = {
210
+ preventDefault: () => { },
211
+ target: { reset: () => { } }
212
+ };
213
+ onReset(syntheticEvent);
214
+ }
215
+ }
216
+ }));
217
+ return (React.createElement("div", { className: "akiform-builder" },
218
+ React.createElement(Akiform, Object.assign({ onFinish: handleSubmit(onSubmit), onReset: handleReset, layout: layout }, formItemLayout, rest, { "data-testid": "akiform-builder", role: "form", "aria-label": i18n.t('formLabel'), requiredMark: true }),
219
+ fields.map(field => {
220
+ var _a, _b, _c, _d;
221
+ const isDisabled = typeof ((_a = field.config) === null || _a === void 0 ? void 0 : _a.disabled) === 'function'
222
+ ? field.config.disabled(formValues)
223
+ : (_b = field.config) === null || _b === void 0 ? void 0 : _b.disabled;
224
+ const isVisible = typeof ((_c = field.config) === null || _c === void 0 ? void 0 : _c.visible) === 'function'
225
+ ? field.config.visible(formValues)
226
+ : ((_d = field.config) === null || _d === void 0 ? void 0 : _d.visible) !== false;
227
+ if (!isVisible) {
228
+ return null;
229
+ }
230
+ if (field.type === 'section') {
231
+ return (React.createElement(SectionComponent, { key: field.key, field: field, control: control, formValues: formValues, formState: formState, layout: layout, layoutOptions: layoutOptions }));
232
+ }
233
+ if (field.type === 'fieldArray') {
234
+ return (React.createElement("div", { className: "akiform-builder-field-array", key: field.key },
235
+ React.createElement(Title, { level: 5 }, field.label),
236
+ renderField(field, control, formValues, formState, layout, layoutOptions)));
237
+ }
238
+ return (React.createElement(FormItem, { key: field.key, control: control, name: field.key, label: field.label, disabled: isDisabled, required: field.validation ? true : false, tooltip: field.tooltip }, renderField(field, control, formValues, formState, layout, layoutOptions)));
239
+ }),
240
+ React.createElement(FormItem, { control: control, name: 'form_actions', wrapperCol: layout === 'horizontal'
241
+ ? {
242
+ offset: (_b = formItemLayout.labelCol) === null || _b === void 0 ? void 0 : _b.span,
243
+ span: (_c = formItemLayout.wrapperCol) === null || _c === void 0 ? void 0 : _c.span
244
+ }
245
+ : undefined },
246
+ React.createElement(Space, null,
247
+ React.createElement(Button, { type: "primary", htmlType: "submit" }, i18n.t('submit')),
248
+ showResetButton && (React.createElement(Button
249
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
250
+ , {
251
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
252
+ onClick: handleReset, type: "default", htmlType: "reset" }, i18n.t('reset'))))))));
253
+ });
254
+ AkiformBuilder.displayName = 'AkiformBuilder';
255
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
256
+ const FieldArrayComponent = ({ field, control, formValues, formState, layout, layoutOptions }) => {
257
+ const { fields, append, remove } = useFieldArray({
258
+ control,
259
+ name: field.key
260
+ });
261
+ const createInitialValue = () => {
262
+ return field.fields.reduce((acc, nestedField) => {
263
+ acc[nestedField.key] = nestedField.defaultValue || '';
264
+ return acc;
265
+ },
266
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
267
+ {});
268
+ };
269
+ return (React.createElement("div", { role: "group", "aria-labelledby": `${field.key}-label` },
270
+ React.createElement("div", { id: `${field.key}-label`, className: "sr-only", "aria-hidden": true }, field.label),
271
+ fields.map((item, index) => (React.createElement("div", { key: item.id },
272
+ field.fields.map(nestedField => {
273
+ var _a, _b;
274
+ const isVisible = typeof ((_a = nestedField.config) === null || _a === void 0 ? void 0 : _a.visible) === 'function'
275
+ ? nestedField.config.visible(((_b = formValues[field.key]) === null || _b === void 0 ? void 0 : _b[index]) || {})
276
+ : true;
277
+ if (!isVisible) {
278
+ return null;
279
+ }
280
+ return (React.createElement(FormItem, { key: `${field.key}.${index}.${nestedField.key}`, control: control, name: `${field.key}.${index}.${nestedField.key}`, label: nestedField.label, required: nestedField.validation ? true : false }, renderField(Object.assign(Object.assign({}, nestedField), {
281
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
282
+ key: `${field.key}.${index}.${nestedField.key}` }), control, formValues, formState, layout, layoutOptions)));
283
+ }),
284
+ React.createElement(Button, { icon: "eksi", size: "small", danger: true, onClick: () => remove(index), "aria-label": `Remove ${field.label} ${index + 1}` }, "Remove")))),
285
+ React.createElement(Button, { icon: "arti", size: "small", type: "text",
286
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
287
+ onClick: () => append(createInitialValue()), "aria-label": `Add ${field.label}` }, "Add")));
288
+ };
@@ -0,0 +1,37 @@
1
+ import { FieldPath, FieldValues } from '@akinon/akiform';
2
+ import { AnySchema } from '@akinon/akival';
3
+ import { TooltipProps } from '@akinon/ui-tooltip';
4
+ import { FieldConfig, FieldType, FormField } from './types';
5
+ type FieldTypeToBuilder<T extends FieldType, TFieldValues extends FieldValues = FieldValues> = T extends 'select' ? SelectFieldBuilder<TFieldValues> : T extends 'custom' ? CustomFieldBuilder<TFieldValues> : T extends 'section' ? SectionFieldBuilder<TFieldValues> : BaseFieldBuilder<TFieldValues>;
6
+ declare class BaseFieldBuilder<TFieldValues extends FieldValues = FieldValues> {
7
+ protected field: Partial<FormField<TFieldValues>>;
8
+ key(key: FieldPath<TFieldValues>): this;
9
+ label(label: string): this;
10
+ type<T extends FieldType>(type: T): FieldTypeToBuilder<T, TFieldValues>;
11
+ placeholder(placeholder: string): this;
12
+ defaultValue(value: any): this;
13
+ validation(schema: AnySchema): this;
14
+ config(config: FieldConfig<TFieldValues>): this;
15
+ fields(fields: FormField<TFieldValues>[]): this;
16
+ tooltip(tooltipProps: TooltipProps | string): this;
17
+ build(): FormField<TFieldValues>;
18
+ }
19
+ declare class SelectFieldBuilder<TFieldValues extends FieldValues = FieldValues> extends BaseFieldBuilder<TFieldValues> {
20
+ options(options: Array<{
21
+ value: string | number;
22
+ label: string;
23
+ }>): this;
24
+ }
25
+ declare class CustomFieldBuilder<TFieldValues extends FieldValues = FieldValues> extends BaseFieldBuilder<TFieldValues> {
26
+ render(renderFn: (props: {
27
+ field: FormField<TFieldValues>;
28
+ formValues: TFieldValues;
29
+ control: any;
30
+ }) => React.ReactElement): this;
31
+ }
32
+ declare class SectionFieldBuilder<TFieldValues extends FieldValues = FieldValues> extends BaseFieldBuilder<TFieldValues> {
33
+ defaultExpanded(expanded: boolean): this;
34
+ }
35
+ export declare function field<TFieldValues extends FieldValues = FieldValues>(): BaseFieldBuilder<TFieldValues> & SelectFieldBuilder<TFieldValues> & CustomFieldBuilder<TFieldValues> & SectionFieldBuilder<TFieldValues>;
36
+ export {};
37
+ //# sourceMappingURL=field-builder.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"field-builder.d.ts","sourceRoot":"","sources":["../../../src/field-builder.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AACzD,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAElD,OAAO,EAEL,WAAW,EACX,SAAS,EACT,SAAS,EAEV,MAAM,SAAS,CAAC;AAEjB,KAAK,kBAAkB,CACrB,CAAC,SAAS,SAAS,EACnB,YAAY,SAAS,WAAW,GAAG,WAAW,IAC5C,CAAC,SAAS,QAAQ,GAClB,kBAAkB,CAAC,YAAY,CAAC,GAChC,CAAC,SAAS,QAAQ,GAChB,kBAAkB,CAAC,YAAY,CAAC,GAChC,CAAC,SAAS,SAAS,GACjB,mBAAmB,CAAC,YAAY,CAAC,GACjC,gBAAgB,CAAC,YAAY,CAAC,CAAC;AAEvC,cAAM,gBAAgB,CAAC,YAAY,SAAS,WAAW,GAAG,WAAW;IACnE,SAAS,CAAC,KAAK,EAAE,OAAO,CAAC,SAAS,CAAC,YAAY,CAAC,CAAC,CAAM;IAEvD,GAAG,CAAC,GAAG,EAAE,SAAS,CAAC,YAAY,CAAC,GAAG,IAAI;IAKvC,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAK1B,IAAI,CAAC,CAAC,SAAS,SAAS,EAAE,IAAI,EAAE,CAAC,GAAG,kBAAkB,CAAC,CAAC,EAAE,YAAY,CAAC;IAKvE,WAAW,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI;IAMtC,YAAY,CAAC,KAAK,EAAE,GAAG,GAAG,IAAI;IAK9B,UAAU,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI;IAKnC,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,YAAY,CAAC,GAAG,IAAI;IAM/C,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,YAAY,CAAC,EAAE,GAAG,IAAI;IAa/C,OAAO,CAAC,YAAY,EAAE,YAAY,GAAG,MAAM,GAAG,IAAI;IAKlD,KAAK,IAAI,SAAS,CAAC,YAAY,CAAC;CAMjC;AAED,cAAM,kBAAkB,CACtB,YAAY,SAAS,WAAW,GAAG,WAAW,CAC9C,SAAQ,gBAAgB,CAAC,YAAY,CAAC;IACtC,OAAO,CAAC,OAAO,EAAE,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,GAAG,IAAI;CAKzE;AAED,cAAM,kBAAkB,CACtB,YAAY,SAAS,WAAW,GAAG,WAAW,CAC9C,SAAQ,gBAAgB,CAAC,YAAY,CAAC;IACtC,MAAM,CACJ,QAAQ,EAAE,CAAC,KAAK,EAAE;QAChB,KAAK,EAAE,SAAS,CAAC,YAAY,CAAC,CAAC;QAC/B,UAAU,EAAE,YAAY,CAAC;QAEzB,OAAO,EAAE,GAAG,CAAC;KACd,KAAK,KAAK,CAAC,YAAY,GACvB,IAAI;CAKR;AAED,cAAM,mBAAmB,CACvB,YAAY,SAAS,WAAW,GAAG,WAAW,CAC9C,SAAQ,gBAAgB,CAAC,YAAY,CAAC;IACtC,eAAe,CAAC,QAAQ,EAAE,OAAO,GAAG,IAAI;CAIzC;AAED,wBAAgB,KAAK,CACnB,YAAY,SAAS,WAAW,GAAG,WAAW,KAC3C,gBAAgB,CAAC,YAAY,CAAC,GACjC,kBAAkB,CAAC,YAAY,CAAC,GAChC,kBAAkB,CAAC,YAAY,CAAC,GAChC,mBAAmB,CAAC,YAAY,CAAC,CAoBlC"}