@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.
Files changed (54) hide show
  1. package/dist/kit/builder/form/components/FormBuilder.d.ts +20 -0
  2. package/dist/kit/builder/form/components/FormBuilder.d.ts.map +1 -1
  3. package/dist/kit/builder/form/components/FormBuilderField.d.ts +5 -5
  4. package/dist/kit/builder/form/components/FormBuilderField.d.ts.map +1 -1
  5. package/dist/kit/builder/form/components/fields/ArrayField.d.ts +3 -0
  6. package/dist/kit/builder/form/components/fields/ArrayField.d.ts.map +1 -0
  7. package/dist/kit/builder/form/components/fields/AutocompleteField.d.ts +3 -0
  8. package/dist/kit/builder/form/components/fields/AutocompleteField.d.ts.map +1 -0
  9. package/dist/kit/builder/form/components/fields/CheckboxField.d.ts +3 -0
  10. package/dist/kit/builder/form/components/fields/CheckboxField.d.ts.map +1 -0
  11. package/dist/kit/builder/form/components/fields/DateField.d.ts +3 -0
  12. package/dist/kit/builder/form/components/fields/DateField.d.ts.map +1 -0
  13. package/dist/kit/builder/form/components/fields/FileField.d.ts +3 -0
  14. package/dist/kit/builder/form/components/fields/FileField.d.ts.map +1 -0
  15. package/dist/kit/builder/form/components/fields/NumberField.d.ts +3 -0
  16. package/dist/kit/builder/form/components/fields/NumberField.d.ts.map +1 -0
  17. package/dist/kit/builder/form/components/fields/ObjectField.d.ts +3 -0
  18. package/dist/kit/builder/form/components/fields/ObjectField.d.ts.map +1 -0
  19. package/dist/kit/builder/form/components/fields/RadioField.d.ts +3 -0
  20. package/dist/kit/builder/form/components/fields/RadioField.d.ts.map +1 -0
  21. package/dist/kit/builder/form/components/fields/SelectField.d.ts +3 -0
  22. package/dist/kit/builder/form/components/fields/SelectField.d.ts.map +1 -0
  23. package/dist/kit/builder/form/components/fields/SwitchField.d.ts +3 -0
  24. package/dist/kit/builder/form/components/fields/SwitchField.d.ts.map +1 -0
  25. package/dist/kit/builder/form/components/fields/TextField.d.ts +3 -0
  26. package/dist/kit/builder/form/components/fields/TextField.d.ts.map +1 -0
  27. package/dist/kit/builder/form/components/fields/TextareaField.d.ts +3 -0
  28. package/dist/kit/builder/form/components/fields/TextareaField.d.ts.map +1 -0
  29. package/dist/kit/builder/form/components/fields/index.d.ts +14 -0
  30. package/dist/kit/builder/form/components/fields/index.d.ts.map +1 -0
  31. package/dist/kit/builder/form/components/fields/types.d.ts +14 -0
  32. package/dist/kit/builder/form/components/fields/types.d.ts.map +1 -0
  33. package/dist/kit/themes/clean-slate.css +1 -1
  34. package/dist/kit/themes/default.css +1 -1
  35. package/dist/kit/themes/minimal-modern.css +1 -1
  36. package/dist/kit/themes/spotify.css +1 -1
  37. package/package.json +1 -1
  38. package/src/kit/builder/form/components/FormBuilder.tsx +66 -16
  39. package/src/kit/builder/form/components/FormBuilderField.tsx +139 -387
  40. package/src/kit/builder/form/components/fields/ArrayField.tsx +222 -0
  41. package/src/kit/builder/form/components/fields/AutocompleteField.tsx +25 -0
  42. package/src/kit/builder/form/components/fields/CheckboxField.tsx +56 -0
  43. package/src/kit/builder/form/components/fields/DateField.tsx +15 -0
  44. package/src/kit/builder/form/components/fields/FileField.tsx +14 -0
  45. package/src/kit/builder/form/components/fields/NumberField.tsx +15 -0
  46. package/src/kit/builder/form/components/fields/ObjectField.tsx +30 -0
  47. package/src/kit/builder/form/components/fields/RadioField.tsx +29 -0
  48. package/src/kit/builder/form/components/fields/SelectField.tsx +31 -0
  49. package/src/kit/builder/form/components/fields/SwitchField.tsx +56 -0
  50. package/src/kit/builder/form/components/fields/TextField.tsx +18 -0
  51. package/src/kit/builder/form/components/fields/TextareaField.tsx +15 -0
  52. package/src/kit/builder/form/components/fields/index.ts +13 -0
  53. package/src/kit/builder/form/components/fields/types.ts +14 -0
  54. 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, watch } = form;
340
+ const { control, handleSubmit, reset, setValue, getValues } = form;
321
341
 
322
- // Determine if any field dependencies are declared
323
- const hasDependencies = useMemo(() => {
324
- return sections.some((section) =>
325
- section.fields?.some((f) => Array.isArray(f.dependencies) && f.dependencies.length > 0),
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
- // Only watch values when there are dependencies to respond to
330
- // This prevents unnecessary re-renders that can cause focus loss
331
- const emptyWatchedValues = useMemo(() => ({} as Record<string, any>), []);
332
- const watchedValues = hasDependencies ? watch() : emptyWatchedValues;
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 = (watchedValues as Record<string, any>)[dep.field];
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
- setValue(field.name, dep.value);
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, setValue, getValues],
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) => {