@connect-soft/form-generator 1.1.0-alpha3 → 1.1.0-alpha4

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/README.md CHANGED
@@ -266,6 +266,113 @@ const fields = [
266
266
 
267
267
  ---
268
268
 
269
+ ## Custom Layouts
270
+
271
+ For full control over form layout, pass a render function as `children`:
272
+
273
+ ```typescript
274
+ <FormGenerator
275
+ fields={[
276
+ { type: 'text', name: 'email', label: 'Email' },
277
+ { type: 'password', name: 'password', label: 'Password' },
278
+ ] as const}
279
+ title="Login"
280
+ onSubmit={handleSubmit}
281
+ >
282
+ {({ fields, buttons, title }) => (
283
+ <div className="login-form">
284
+ <h1>{title}</h1>
285
+ <div className="field-row">{fields.email}</div>
286
+ <div className="field-row">{fields.password}</div>
287
+ <div className="actions">{buttons.submit}</div>
288
+ </div>
289
+ )}
290
+ </FormGenerator>
291
+ ```
292
+
293
+ ### Render Props API
294
+
295
+ The render function receives:
296
+
297
+ | Property | Type | Description |
298
+ |----------|------|-------------|
299
+ | `fields` | `TemplateFields` | Pre-rendered fields (see below) |
300
+ | `layouts` | `TemplateLayouts` | Pre-rendered named layouts |
301
+ | `buttons` | `{ submit, reset? }` | Pre-rendered buttons |
302
+ | `title` | `string` | Form title prop |
303
+ | `description` | `string` | Form description prop |
304
+ | `form` | `UseFormReturn` | react-hook-form instance |
305
+ | `isSubmitting` | `boolean` | Form submission state |
306
+ | `isValid` | `boolean` | Form validity state |
307
+ | `isDirty` | `boolean` | Form dirty state |
308
+ | `renderField` | `function` | Manual field renderer |
309
+ | `renderLayout` | `function` | Manual layout renderer |
310
+
311
+ ### Fields Object
312
+
313
+ Access fields by name or use helper methods:
314
+
315
+ ```typescript
316
+ {({ fields }) => (
317
+ <div>
318
+ {/* Access individual fields */}
319
+ {fields.email}
320
+ {fields.password}
321
+
322
+ {/* Render all fields */}
323
+ {fields.all}
324
+
325
+ {/* Render only fields not yet accessed */}
326
+ {fields.remaining}
327
+
328
+ {/* Check if field exists */}
329
+ {fields.has('email') && fields.email}
330
+
331
+ {/* Get all field names */}
332
+ {fields.names.map(name => <div key={name}>{fields[name]}</div>)}
333
+
334
+ {/* Render specific fields */}
335
+ {fields.render('email', 'password')}
336
+ </div>
337
+ )}
338
+ ```
339
+
340
+ ### Mixed Layout Example
341
+
342
+ Highlight specific fields while rendering the rest normally:
343
+
344
+ ```typescript
345
+ <FormGenerator fields={fieldDefinitions} onSubmit={handleSubmit}>
346
+ {({ fields, buttons }) => (
347
+ <div>
348
+ <div className="highlighted">{fields.email}</div>
349
+ <div className="other-fields">{fields.remaining}</div>
350
+ {buttons.submit}
351
+ </div>
352
+ )}
353
+ </FormGenerator>
354
+ ```
355
+
356
+ ### Form State Access
357
+
358
+ Use form state for conditional rendering:
359
+
360
+ ```typescript
361
+ <FormGenerator fields={fieldDefinitions} onSubmit={handleSubmit}>
362
+ {({ fields, buttons, isSubmitting, isValid, isDirty }) => (
363
+ <div>
364
+ {fields.all}
365
+ <button type="submit" disabled={isSubmitting || !isValid}>
366
+ {isSubmitting ? 'Saving...' : 'Submit'}
367
+ </button>
368
+ {isDirty && <span>You have unsaved changes</span>}
369
+ </div>
370
+ )}
371
+ </FormGenerator>
372
+ ```
373
+
374
+ ---
375
+
269
376
  ## TypeScript Type Inference
270
377
 
271
378
  Get full type inference from field definitions:
@@ -378,6 +485,11 @@ function MyForm() {
378
485
  | `submitText` | `string` | `'Submit'` | Submit button text |
379
486
  | `disabled` | `boolean` | `false` | Disable entire form |
380
487
  | `mode` | `'onChange' \| 'onBlur' \| 'onSubmit' \| 'onTouched' \| 'all'` | `'onChange'` | Validation trigger mode |
488
+ | `children` | `TemplateRenderFn` | - | Render function for custom layout |
489
+ | `title` | `string` | - | Form title (available in render props) |
490
+ | `description` | `string` | - | Form description (available in render props) |
491
+ | `showReset` | `boolean` | `false` | Include reset button in `buttons.reset` |
492
+ | `resetText` | `string` | `'Reset'` | Reset button text |
381
493
 
382
494
  ### Field Base Properties
383
495
 
package/dist/index.js CHANGED
@@ -3,8 +3,8 @@
3
3
  var jsxRuntime = require('react/jsx-runtime');
4
4
  var React = require('react');
5
5
  var n$1 = require('zod/v4/core');
6
- var zod = require('zod');
7
6
  var compilerRuntime = require('react/compiler-runtime');
7
+ var zod = require('zod');
8
8
 
9
9
  function _interopNamespaceDefault(e) {
10
10
  var n = Object.create(null);
@@ -3130,6 +3130,105 @@ function clearAllRegistries() {
3130
3130
  clearLayoutRegistry();
3131
3131
  }
3132
3132
 
3133
+ const RESERVED_PROPS = new Set(['all', 'remaining', 'names', 'has', 'render']);
3134
+ function createTemplateFields(fieldEntries) {
3135
+ const accessedFields = new Set();
3136
+ const handler = {
3137
+ get(_, prop) {
3138
+ if (typeof prop === 'symbol') {
3139
+ return undefined;
3140
+ }
3141
+ if (prop === 'all') {
3142
+ return Array.from(fieldEntries.values()).map(entry => entry.element);
3143
+ }
3144
+ if (prop === 'remaining') {
3145
+ return Array.from(fieldEntries.entries()).filter(([name]) => !accessedFields.has(name)).map(([, entry]) => entry.element);
3146
+ }
3147
+ if (prop === 'names') {
3148
+ return Array.from(fieldEntries.keys());
3149
+ }
3150
+ if (prop === 'has') {
3151
+ return name => fieldEntries.has(name);
3152
+ }
3153
+ if (prop === 'render') {
3154
+ return (...names) => {
3155
+ return names.filter(name => fieldEntries.has(name)).map(name => {
3156
+ accessedFields.add(name);
3157
+ return fieldEntries.get(name).element;
3158
+ });
3159
+ };
3160
+ }
3161
+ if (fieldEntries.has(prop)) {
3162
+ accessedFields.add(prop);
3163
+ return fieldEntries.get(prop).element;
3164
+ }
3165
+ return undefined;
3166
+ },
3167
+ has(_, prop) {
3168
+ if (typeof prop === 'symbol') {
3169
+ return false;
3170
+ }
3171
+ return RESERVED_PROPS.has(prop) || fieldEntries.has(prop);
3172
+ },
3173
+ ownKeys() {
3174
+ return [...RESERVED_PROPS, ...fieldEntries.keys()];
3175
+ },
3176
+ getOwnPropertyDescriptor(_, prop) {
3177
+ if (typeof prop === 'symbol') {
3178
+ return undefined;
3179
+ }
3180
+ if (RESERVED_PROPS.has(prop) || fieldEntries.has(prop)) {
3181
+ return {
3182
+ configurable: true,
3183
+ enumerable: true,
3184
+ value: this.get(_, prop, {})
3185
+ };
3186
+ }
3187
+ return undefined;
3188
+ }
3189
+ };
3190
+ return new Proxy({}, handler);
3191
+ }
3192
+ function createTemplateLayouts(layoutEntries) {
3193
+ const handler = {
3194
+ get(_, prop) {
3195
+ if (typeof prop === 'symbol') {
3196
+ return undefined;
3197
+ }
3198
+ if (prop === 'all') {
3199
+ return Array.from(layoutEntries.values()).map(entry => entry.element);
3200
+ }
3201
+ if (layoutEntries.has(prop)) {
3202
+ return layoutEntries.get(prop).element;
3203
+ }
3204
+ return undefined;
3205
+ },
3206
+ has(_, prop) {
3207
+ if (typeof prop === 'symbol') {
3208
+ return false;
3209
+ }
3210
+ return prop === 'all' || layoutEntries.has(prop);
3211
+ },
3212
+ ownKeys() {
3213
+ return ['all', ...layoutEntries.keys()];
3214
+ },
3215
+ getOwnPropertyDescriptor(_, prop) {
3216
+ if (typeof prop === 'symbol') {
3217
+ return undefined;
3218
+ }
3219
+ if (prop === 'all' || layoutEntries.has(prop)) {
3220
+ return {
3221
+ configurable: true,
3222
+ enumerable: true,
3223
+ value: this.get(_, prop, {})
3224
+ };
3225
+ }
3226
+ return undefined;
3227
+ }
3228
+ };
3229
+ return new Proxy({}, handler);
3230
+ }
3231
+
3133
3232
  const FieldRenderer = /*#__PURE__*/React.memo(t0 => {
3134
3233
  const $ = compilerRuntime.c(5);
3135
3234
  const {
@@ -3375,8 +3474,9 @@ const defaultValidators = {
3375
3474
  tel: stringValidator,
3376
3475
  number: field => {
3377
3476
  let schema = zod.z.coerce.number();
3378
- if (field.min !== undefined) schema = schema.min(field.min);
3379
- if (field.max !== undefined) schema = schema.max(field.max);
3477
+ const numberField = field;
3478
+ if (numberField.min !== undefined) schema = schema.min(numberField.min);
3479
+ if (numberField.max !== undefined) schema = schema.max(numberField.max);
3380
3480
  return schema;
3381
3481
  },
3382
3482
  checkbox: () => zod.z.coerce.boolean(),
@@ -3462,6 +3562,38 @@ function getItemKey(item, index) {
3462
3562
  }
3463
3563
  return item.name;
3464
3564
  }
3565
+ function extractFields(items) {
3566
+ const fields = [];
3567
+ for (const item of items) {
3568
+ if (isLayoutBlock(item)) {
3569
+ let childItems = [];
3570
+ if (item.type === 'columns') {
3571
+ for (const column of item.columns) {
3572
+ childItems.push(...column.children);
3573
+ }
3574
+ } else if (item.type === 'section') {
3575
+ childItems = item.children;
3576
+ }
3577
+ fields.push(...extractFields(childItems));
3578
+ } else {
3579
+ fields.push(item);
3580
+ }
3581
+ }
3582
+ return fields;
3583
+ }
3584
+ function extractLayouts(items) {
3585
+ const layouts = [];
3586
+ items.forEach((item, index) => {
3587
+ if (isLayoutBlock(item)) {
3588
+ layouts.push({
3589
+ layout: item,
3590
+ index
3591
+ });
3592
+ }
3593
+ });
3594
+ return layouts;
3595
+ }
3596
+
3465
3597
  function hasSchema(props) {
3466
3598
  return props.schema !== undefined;
3467
3599
  }
@@ -3473,6 +3605,11 @@ function FormGeneratorImpl(props) {
3473
3605
  submitText = 'Submit',
3474
3606
  disabled,
3475
3607
  mode = 'onChange',
3608
+ title,
3609
+ description,
3610
+ showReset = false,
3611
+ resetText = 'Reset',
3612
+ children,
3476
3613
  ref
3477
3614
  } = props;
3478
3615
  const userSchema = hasSchema(props) ? props.schema : undefined;
@@ -3496,6 +3633,9 @@ function FormGeneratorImpl(props) {
3496
3633
  const handleSubmit = form.handleSubmit(async data => {
3497
3634
  await onSubmit(data);
3498
3635
  });
3636
+ const handleReset = React.useCallback(() => {
3637
+ form.reset(mergedDefaultValues);
3638
+ }, [form, mergedDefaultValues]);
3499
3639
  React.useImperativeHandle(ref, () => ({
3500
3640
  setValues: values => {
3501
3641
  Object.entries(values).forEach(([key, value]) => {
@@ -3520,26 +3660,124 @@ function FormGeneratorImpl(props) {
3520
3660
  setError: (name, error) => form.setError(name, error),
3521
3661
  isValid: () => form.formState.isValid,
3522
3662
  isDirty: () => form.formState.isDirty,
3523
- form
3663
+ form: form
3524
3664
  }), [form, handleSubmit, mergedDefaultValues]);
3525
3665
  const SubmitButton = getFormComponent('SubmitButton');
3666
+ const fieldEntries = React.useMemo(() => {
3667
+ const entries = new Map();
3668
+ const allFields = extractFields(fields);
3669
+ allFields.forEach((field, index) => {
3670
+ if (!field.hidden) {
3671
+ const element = jsxRuntime.jsx(FieldRenderer, {
3672
+ field: field
3673
+ }, field.name || `field-${index}`);
3674
+ entries.set(field.name, {
3675
+ field,
3676
+ element,
3677
+ accessed: false
3678
+ });
3679
+ }
3680
+ });
3681
+ return entries;
3682
+ }, [fields]);
3683
+ const layoutEntries = React.useMemo(() => {
3684
+ const entries_0 = new Map();
3685
+ const allLayouts = extractLayouts(fields);
3686
+ allLayouts.forEach(({
3687
+ layout,
3688
+ index: index_0
3689
+ }) => {
3690
+ if (!layout.hidden) {
3691
+ const key_0 = layout.name || `layout-${layout.type}-${index_0}`;
3692
+ const element_0 = jsxRuntime.jsx(FormItemRenderer, {
3693
+ item: layout
3694
+ }, key_0);
3695
+ if (layout.name) {
3696
+ entries_0.set(layout.name, {
3697
+ layout,
3698
+ element: element_0
3699
+ });
3700
+ }
3701
+ }
3702
+ });
3703
+ return entries_0;
3704
+ }, [fields]);
3705
+ const templateFields = React.useMemo(() => createTemplateFields(fieldEntries), [fieldEntries]);
3706
+ const templateLayouts = React.useMemo(() => createTemplateLayouts(layoutEntries), [layoutEntries]);
3707
+ const buttons = React.useMemo(() => {
3708
+ const result = {
3709
+ submit: jsxRuntime.jsx(SubmitButton, {
3710
+ disabled: disabled || form.formState.isSubmitting,
3711
+ isSubmitting: form.formState.isSubmitting,
3712
+ children: submitText
3713
+ }, "submit")
3714
+ };
3715
+ if (showReset) {
3716
+ result.reset = jsxRuntime.jsx("button", {
3717
+ type: "button",
3718
+ onClick: handleReset,
3719
+ disabled: disabled,
3720
+ children: resetText
3721
+ }, "reset");
3722
+ }
3723
+ return result;
3724
+ }, [SubmitButton, disabled, form.formState.isSubmitting, submitText, showReset, resetText, handleReset]);
3725
+ const renderField = React.useCallback((field_0, options) => {
3726
+ return jsxRuntime.jsx(FieldRenderer, {
3727
+ field: field_0,
3728
+ namePrefix: options === null || options === void 0 ? void 0 : options.namePrefix
3729
+ });
3730
+ }, []);
3731
+ const renderLayout = React.useCallback((layout_0, options_0) => {
3732
+ return jsxRuntime.jsx(FormItemRenderer, {
3733
+ item: layout_0,
3734
+ namePrefix: options_0 === null || options_0 === void 0 ? void 0 : options_0.namePrefix
3735
+ });
3736
+ }, []);
3737
+ if (!children) {
3738
+ return jsxRuntime.jsx(FormProvider, {
3739
+ ...form,
3740
+ children: jsxRuntime.jsxs("form", {
3741
+ ref: formRef,
3742
+ onSubmit: handleSubmit,
3743
+ className: className,
3744
+ children: [fields.map((item, index_1) => jsxRuntime.jsx(FormItemRenderer, {
3745
+ item: item
3746
+ }, getItemKey(item, index_1))), jsxRuntime.jsx(SubmitButton, {
3747
+ disabled: disabled || form.formState.isSubmitting,
3748
+ isSubmitting: form.formState.isSubmitting,
3749
+ children: submitText
3750
+ })]
3751
+ })
3752
+ });
3753
+ }
3754
+ const renderProps = {
3755
+ fields: templateFields,
3756
+ layouts: templateLayouts,
3757
+ buttons,
3758
+ title,
3759
+ description,
3760
+ form: form,
3761
+ isSubmitting: form.formState.isSubmitting,
3762
+ isValid: form.formState.isValid,
3763
+ isDirty: form.formState.isDirty,
3764
+ renderField,
3765
+ renderLayout
3766
+ };
3767
+ const renderFn = children;
3526
3768
  return jsxRuntime.jsx(FormProvider, {
3527
3769
  ...form,
3528
- children: jsxRuntime.jsxs("form", {
3770
+ children: jsxRuntime.jsx("form", {
3529
3771
  ref: formRef,
3530
3772
  onSubmit: handleSubmit,
3531
3773
  className: className,
3532
- children: [fields.map((item, index) => jsxRuntime.jsx(FormItemRenderer, {
3533
- item: item
3534
- }, getItemKey(item, index))), jsxRuntime.jsx(SubmitButton, {
3535
- disabled: disabled || form.formState.isSubmitting,
3536
- isSubmitting: form.formState.isSubmitting,
3537
- children: submitText
3538
- })]
3774
+ children: renderFn(renderProps)
3539
3775
  })
3540
3776
  });
3541
3777
  }
3542
- const FormGenerator = FormGeneratorImpl;
3778
+ function FormGenerator(props) {
3779
+ return FormGeneratorImpl(props);
3780
+ }
3543
3781
 
3544
3782
  Object.defineProperty(exports, "z", {
3545
3783
  enumerable: true,