@k3-universe/react-kit 0.0.3 → 0.0.5
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/kit/builder/form/components/FormBuilder.d.ts +20 -0
- package/dist/kit/builder/form/components/FormBuilder.d.ts.map +1 -1
- package/dist/kit/builder/form/components/FormBuilderField.d.ts +5 -5
- package/dist/kit/builder/form/components/FormBuilderField.d.ts.map +1 -1
- package/dist/kit/builder/form/components/fields/ArrayField.d.ts +3 -0
- package/dist/kit/builder/form/components/fields/ArrayField.d.ts.map +1 -0
- package/dist/kit/builder/form/components/fields/AutocompleteField.d.ts +3 -0
- package/dist/kit/builder/form/components/fields/AutocompleteField.d.ts.map +1 -0
- package/dist/kit/builder/form/components/fields/CheckboxField.d.ts +3 -0
- package/dist/kit/builder/form/components/fields/CheckboxField.d.ts.map +1 -0
- package/dist/kit/builder/form/components/fields/DateField.d.ts +3 -0
- package/dist/kit/builder/form/components/fields/DateField.d.ts.map +1 -0
- package/dist/kit/builder/form/components/fields/FileField.d.ts +3 -0
- package/dist/kit/builder/form/components/fields/FileField.d.ts.map +1 -0
- package/dist/kit/builder/form/components/fields/NumberField.d.ts +3 -0
- package/dist/kit/builder/form/components/fields/NumberField.d.ts.map +1 -0
- package/dist/kit/builder/form/components/fields/ObjectField.d.ts +3 -0
- package/dist/kit/builder/form/components/fields/ObjectField.d.ts.map +1 -0
- package/dist/kit/builder/form/components/fields/RadioField.d.ts +3 -0
- package/dist/kit/builder/form/components/fields/RadioField.d.ts.map +1 -0
- package/dist/kit/builder/form/components/fields/SelectField.d.ts +3 -0
- package/dist/kit/builder/form/components/fields/SelectField.d.ts.map +1 -0
- package/dist/kit/builder/form/components/fields/SwitchField.d.ts +3 -0
- package/dist/kit/builder/form/components/fields/SwitchField.d.ts.map +1 -0
- package/dist/kit/builder/form/components/fields/TextField.d.ts +3 -0
- package/dist/kit/builder/form/components/fields/TextField.d.ts.map +1 -0
- package/dist/kit/builder/form/components/fields/TextareaField.d.ts +3 -0
- package/dist/kit/builder/form/components/fields/TextareaField.d.ts.map +1 -0
- package/dist/kit/builder/form/components/fields/index.d.ts +14 -0
- package/dist/kit/builder/form/components/fields/index.d.ts.map +1 -0
- package/dist/kit/builder/form/components/fields/types.d.ts +14 -0
- package/dist/kit/builder/form/components/fields/types.d.ts.map +1 -0
- package/dist/kit/themes/clean-slate.css +1 -1
- package/dist/kit/themes/default.css +1 -1
- package/dist/kit/themes/minimal-modern.css +1 -1
- package/dist/kit/themes/spotify.css +1 -1
- package/package.json +1 -1
- package/src/kit/builder/form/components/FormBuilder.tsx +66 -16
- package/src/kit/builder/form/components/FormBuilderField.tsx +139 -387
- package/src/kit/builder/form/components/fields/ArrayField.tsx +222 -0
- package/src/kit/builder/form/components/fields/AutocompleteField.tsx +25 -0
- package/src/kit/builder/form/components/fields/CheckboxField.tsx +56 -0
- package/src/kit/builder/form/components/fields/DateField.tsx +15 -0
- package/src/kit/builder/form/components/fields/FileField.tsx +14 -0
- package/src/kit/builder/form/components/fields/NumberField.tsx +15 -0
- package/src/kit/builder/form/components/fields/ObjectField.tsx +30 -0
- package/src/kit/builder/form/components/fields/RadioField.tsx +29 -0
- package/src/kit/builder/form/components/fields/SelectField.tsx +31 -0
- package/src/kit/builder/form/components/fields/SwitchField.tsx +56 -0
- package/src/kit/builder/form/components/fields/TextField.tsx +18 -0
- package/src/kit/builder/form/components/fields/TextareaField.tsx +15 -0
- package/src/kit/builder/form/components/fields/index.ts +13 -0
- package/src/kit/builder/form/components/fields/types.ts +14 -0
- package/src/stories/kit/builder/Form.ArrayLayouts.stories.tsx +153 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import React, { useCallback, useMemo } from 'react';
|
|
2
|
-
import { useForm } from 'react-hook-form';
|
|
1
|
+
import React, { useCallback, useEffect, useMemo, useRef } from 'react';
|
|
2
|
+
import { useForm, useWatch, type Control, type FieldValues } from 'react-hook-form';
|
|
3
3
|
import { zodResolver } from '@hookform/resolvers/zod';
|
|
4
4
|
import { z } from 'zod';
|
|
5
5
|
import { cn } from '../../../../shadcn/lib/utils';
|
|
@@ -66,6 +66,26 @@ export interface FormBuilderFieldConfig {
|
|
|
66
66
|
gridCols?: number;
|
|
67
67
|
rows?: number; // For textarea fields
|
|
68
68
|
itemType?: string; // For array fields
|
|
69
|
+
// Array field layout: default 'card'
|
|
70
|
+
arrayLayout?: 'card' | 'table' | 'custom';
|
|
71
|
+
// Custom renderer for array fields when arrayLayout === 'custom'
|
|
72
|
+
arrayRender?: (params: {
|
|
73
|
+
field: FormBuilderFieldConfig;
|
|
74
|
+
control: Control<FieldValues>;
|
|
75
|
+
fieldPath: string;
|
|
76
|
+
value: any;
|
|
77
|
+
onChange: (value: any) => void;
|
|
78
|
+
addItem: () => void;
|
|
79
|
+
removeItem: (index: number) => void;
|
|
80
|
+
disabled?: boolean;
|
|
81
|
+
rows?: { id: string }[]; // useFieldArray rows for stable rendering
|
|
82
|
+
}) => React.ReactNode;
|
|
83
|
+
// Optional styling for array layouts (used mainly for 'table')
|
|
84
|
+
arrayColors?: {
|
|
85
|
+
headerBgClass?: string; // e.g. 'bg-teal-700'
|
|
86
|
+
headerTextClass?: string; // e.g. 'text-white'
|
|
87
|
+
rowAltBgClass?: string; // e.g. 'bg-teal-50'
|
|
88
|
+
};
|
|
69
89
|
conditional?: {
|
|
70
90
|
field: string;
|
|
71
91
|
value: any;
|
|
@@ -317,21 +337,36 @@ export function FormBuilder({
|
|
|
317
337
|
defaultValues: generatedDefaultValues,
|
|
318
338
|
});
|
|
319
339
|
|
|
320
|
-
const { control, handleSubmit, reset, setValue, getValues
|
|
340
|
+
const { control, handleSubmit, reset, setValue, getValues } = form;
|
|
321
341
|
|
|
322
|
-
// Determine
|
|
323
|
-
const
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
342
|
+
// Determine dependency fields to watch
|
|
343
|
+
const dependencyFields = useMemo(() => {
|
|
344
|
+
const set = new Set<string>();
|
|
345
|
+
sections.forEach((section) => {
|
|
346
|
+
section.fields?.forEach((f) => {
|
|
347
|
+
f.dependencies?.forEach((d) => set.add(d.field));
|
|
348
|
+
});
|
|
349
|
+
});
|
|
350
|
+
return Array.from(set);
|
|
327
351
|
}, [sections]);
|
|
328
352
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
353
|
+
const hasDependencies = dependencyFields.length > 0;
|
|
354
|
+
|
|
355
|
+
// Watch only dependency fields via useWatch; create a stable object map
|
|
356
|
+
// Always call useWatch to satisfy hooks rules. Passing an empty array is safe and returns an empty array.
|
|
357
|
+
const depValuesArr = useWatch({ control, name: dependencyFields });
|
|
358
|
+
const watchedValues = useMemo(() => {
|
|
359
|
+
if (!hasDependencies) return {} as Record<string, any>;
|
|
360
|
+
const obj: Record<string, any> = {};
|
|
361
|
+
dependencyFields.forEach((n, i) => { obj[n] = (depValuesArr as any[])[i]; });
|
|
362
|
+
return obj;
|
|
363
|
+
// dependencyFields is stable from sections; depValuesArr changes only when values change
|
|
364
|
+
}, [hasDependencies, dependencyFields, depValuesArr]);
|
|
333
365
|
|
|
334
366
|
// Handle field dependencies
|
|
367
|
+
// Queue dependency-driven value updates to avoid calling setValue during render
|
|
368
|
+
const pendingValueUpdatesRef = useRef<Array<{ name: string; value: any }>>([]);
|
|
369
|
+
|
|
335
370
|
const handleFieldDependencies = useCallback(
|
|
336
371
|
(field: FormBuilderFieldConfig) => {
|
|
337
372
|
if (!hasDependencies || !field.dependencies) return {};
|
|
@@ -339,7 +374,7 @@ export function FormBuilder({
|
|
|
339
374
|
const result: { disabled?: boolean; hidden?: boolean } = {};
|
|
340
375
|
|
|
341
376
|
field.dependencies.forEach((dep) => {
|
|
342
|
-
const dependentValue =
|
|
377
|
+
const dependentValue = watchedValues[dep.field];
|
|
343
378
|
const conditionMet = dep.condition(dependentValue);
|
|
344
379
|
|
|
345
380
|
switch (dep.action) {
|
|
@@ -358,9 +393,9 @@ export function FormBuilder({
|
|
|
358
393
|
case 'setValue':
|
|
359
394
|
if (conditionMet && dep.value !== undefined) {
|
|
360
395
|
const currentValue = getValues(field.name);
|
|
361
|
-
// Only setValue if the value is actually different to prevent infinite loops
|
|
362
396
|
if (currentValue !== dep.value) {
|
|
363
|
-
|
|
397
|
+
// Defer the update to an effect to prevent state changes during render
|
|
398
|
+
pendingValueUpdatesRef.current.push({ name: field.name, value: dep.value });
|
|
364
399
|
}
|
|
365
400
|
}
|
|
366
401
|
break;
|
|
@@ -369,9 +404,24 @@ export function FormBuilder({
|
|
|
369
404
|
|
|
370
405
|
return result;
|
|
371
406
|
},
|
|
372
|
-
[hasDependencies, watchedValues,
|
|
407
|
+
[hasDependencies, watchedValues, getValues],
|
|
373
408
|
);
|
|
374
409
|
|
|
410
|
+
// Flush any pending setValue updates after watchedValues change
|
|
411
|
+
useEffect(() => {
|
|
412
|
+
if (pendingValueUpdatesRef.current.length === 0) return;
|
|
413
|
+
const updatesMap = new Map<string, any>();
|
|
414
|
+
// last write wins per field
|
|
415
|
+
pendingValueUpdatesRef.current.forEach(({ name, value }) => updatesMap.set(name, value));
|
|
416
|
+
pendingValueUpdatesRef.current = [];
|
|
417
|
+
updatesMap.forEach((value, name) => {
|
|
418
|
+
const current = getValues(name);
|
|
419
|
+
if (current !== value) {
|
|
420
|
+
setValue(name, value, { shouldDirty: false, shouldTouch: false, shouldValidate: false });
|
|
421
|
+
}
|
|
422
|
+
});
|
|
423
|
+
}, [watchedValues, setValue, getValues]);
|
|
424
|
+
|
|
375
425
|
// Handle field change with custom onChange
|
|
376
426
|
const handleFieldChange = useCallback(
|
|
377
427
|
(field: FormBuilderFieldConfig, value: any) => {
|