@uniform-ts/core 0.0.0 → 0.0.2

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
@@ -468,7 +468,26 @@ const schema = z.object({
468
468
 
469
469
  Zod still validates the array (`.min(1)` etc.) — only the _render_ is taken over by your component.
470
470
 
471
- #### Option 2 — Named key in the registry
471
+ #### Option 2 — String field as select
472
+
473
+ A `z.string()` field can be rendered as a select by setting `meta.component: 'select'` together with `meta.options`. UniForm treats it as type `"select"` during introspection:
474
+
475
+ ```ts
476
+ const schema = z.object({
477
+ role: z.string().meta({
478
+ component: 'select',
479
+ options: [
480
+ { label: 'User', value: 'user' },
481
+ { label: 'Admin', value: 'admin' },
482
+ { label: 'Editor', value: 'editor' },
483
+ ],
484
+ }),
485
+ })
486
+ ```
487
+
488
+ This is an alternative to `z.enum(...)` — useful when the option list is dynamic or when you need a plain `string` output type rather than a union literal.
489
+
490
+ #### Option 3 — Named key in the registry
472
491
 
473
492
  Register a component under a custom string key — either in `createAutoForm` or the `components` prop — then reference it with `meta.component: 'yourKey'`:
474
493
 
package/dist/index.d.mts CHANGED
@@ -181,6 +181,26 @@ type FieldConfigBase = {
181
181
  required: boolean;
182
182
  /** Merged UI metadata for the field. */
183
183
  meta: FieldMeta;
184
+ /**
185
+ * The original Zod schema for this field, after transparent wrappers
186
+ * (`optional`, `nullable`, `default`, `pipe`) have been stripped.
187
+ *
188
+ * This is a general escape hatch for custom components that need to inspect
189
+ * the raw schema — for example, to read union variants, access custom Zod
190
+ * metadata not captured by introspection, or build schema-aware validation UI.
191
+ *
192
+ * @example
193
+ * function MyUnionInput({ field }: FieldProps) {
194
+ * // Inspect the original schema for any edge case
195
+ * const schema = field.schema
196
+ * if (schema._zod.def.type === 'union') {
197
+ * const variants = (schema._zod.def as z.$ZodUnionDef).options
198
+ * // render a type-switcher using the raw Zod variants
199
+ * }
200
+ * return <input ... />
201
+ * }
202
+ */
203
+ schema: z.$ZodType;
184
204
  };
185
205
  /**
186
206
  * The fully resolved configuration for a single form field, produced by
@@ -256,6 +276,12 @@ type FieldProps = {
256
276
  options?: SelectOption[];
257
277
  /** Full field metadata, including any custom keys. */
258
278
  meta: FieldMeta;
279
+ /**
280
+ * The original Zod schema for this field (after transparent wrappers are stripped).
281
+ * Use this as an escape hatch when you need capabilities beyond what `FieldConfig`
282
+ * exposes — e.g. inspecting union variants, accessing custom Zod refinements, etc.
283
+ */
284
+ schema: z.$ZodType;
259
285
  };
260
286
  /**
261
287
  * A map of field type keys to React components used to render them.
package/dist/index.d.ts CHANGED
@@ -181,6 +181,26 @@ type FieldConfigBase = {
181
181
  required: boolean;
182
182
  /** Merged UI metadata for the field. */
183
183
  meta: FieldMeta;
184
+ /**
185
+ * The original Zod schema for this field, after transparent wrappers
186
+ * (`optional`, `nullable`, `default`, `pipe`) have been stripped.
187
+ *
188
+ * This is a general escape hatch for custom components that need to inspect
189
+ * the raw schema — for example, to read union variants, access custom Zod
190
+ * metadata not captured by introspection, or build schema-aware validation UI.
191
+ *
192
+ * @example
193
+ * function MyUnionInput({ field }: FieldProps) {
194
+ * // Inspect the original schema for any edge case
195
+ * const schema = field.schema
196
+ * if (schema._zod.def.type === 'union') {
197
+ * const variants = (schema._zod.def as z.$ZodUnionDef).options
198
+ * // render a type-switcher using the raw Zod variants
199
+ * }
200
+ * return <input ... />
201
+ * }
202
+ */
203
+ schema: z.$ZodType;
184
204
  };
185
205
  /**
186
206
  * The fully resolved configuration for a single form field, produced by
@@ -256,6 +276,12 @@ type FieldProps = {
256
276
  options?: SelectOption[];
257
277
  /** Full field metadata, including any custom keys. */
258
278
  meta: FieldMeta;
279
+ /**
280
+ * The original Zod schema for this field (after transparent wrappers are stripped).
281
+ * Use this as an escape hatch when you need capabilities beyond what `FieldConfig`
282
+ * exposes — e.g. inspecting union variants, accessing custom Zod refinements, etc.
283
+ */
284
+ schema: z.$ZodType;
259
285
  };
260
286
  /**
261
287
  * A map of field type keys to React components used to render them.
package/dist/index.js CHANGED
@@ -83,25 +83,30 @@ function introspectSchema(schema, name = "", parentPath = "") {
83
83
  let maxItems;
84
84
  try {
85
85
  if (kind === "string") {
86
- type = "string";
87
- const defFormat = def.format;
88
- if (defFormat === "email") {
89
- mergedMeta["inputType"] = "email";
90
- } else if (defFormat === "url") {
91
- mergedMeta["inputType"] = "url";
92
- } else if (defFormat === "uuid") {
93
- mergedMeta["inputType"] = "uuid";
86
+ if (mergedMeta.component === "select" && Array.isArray(mergedMeta.options) && mergedMeta.options.length > 0) {
87
+ type = "select";
88
+ options = mergedMeta.options;
94
89
  } else {
95
- const checks = def.checks ?? [];
96
- const hasFormat = (fmt) => checks.some(
97
- (c) => c._zod.def.check === "string_format" && c._zod.def.format === fmt
98
- );
99
- if (hasFormat("email")) {
90
+ type = "string";
91
+ const defFormat = def.format;
92
+ if (defFormat === "email") {
100
93
  mergedMeta["inputType"] = "email";
101
- } else if (hasFormat("url")) {
94
+ } else if (defFormat === "url") {
102
95
  mergedMeta["inputType"] = "url";
103
- } else if (hasFormat("uuid")) {
96
+ } else if (defFormat === "uuid") {
104
97
  mergedMeta["inputType"] = "uuid";
98
+ } else {
99
+ const checks = def.checks ?? [];
100
+ const hasFormat = (fmt) => checks.some(
101
+ (c) => c._zod.def.check === "string_format" && c._zod.def.format === fmt
102
+ );
103
+ if (hasFormat("email")) {
104
+ mergedMeta["inputType"] = "email";
105
+ } else if (hasFormat("url")) {
106
+ mergedMeta["inputType"] = "url";
107
+ } else if (hasFormat("uuid")) {
108
+ mergedMeta["inputType"] = "uuid";
109
+ }
105
110
  }
106
111
  }
107
112
  } else if (kind === "number") {
@@ -138,15 +143,24 @@ function introspectSchema(schema, name = "", parentPath = "") {
138
143
  }
139
144
  }
140
145
  } else if (def.type === "union") {
141
- type = "union";
142
146
  const unionDef = def;
147
+ const variants = unionDef.options;
143
148
  if ("discriminator" in unionDef) {
149
+ type = "union";
144
150
  discriminatorKey = unionDef.discriminator;
151
+ unionVariants = variants.map(
152
+ (variant, i) => introspectSchema(variant, String(i), path)
153
+ );
154
+ } else {
155
+ const collapsed = introspectSchema(variants[0], name, parentPath);
156
+ return {
157
+ ...collapsed,
158
+ name: path,
159
+ label,
160
+ meta: { ...collapsed.meta, ...mergedMeta },
161
+ schema: inner
162
+ };
145
163
  }
146
- const variants = unionDef.options;
147
- unionVariants = variants.map(
148
- (variant, i) => introspectSchema(variant, String(i), path)
149
- );
150
164
  }
151
165
  } catch {
152
166
  type = "unknown";
@@ -157,6 +171,7 @@ function introspectSchema(schema, name = "", parentPath = "") {
157
171
  label,
158
172
  required,
159
173
  meta: mergedMeta,
174
+ schema: inner,
160
175
  ...options !== void 0 && { options },
161
176
  ...children !== void 0 && { children },
162
177
  ...itemConfig !== void 0 && { itemConfig },
@@ -195,6 +210,7 @@ function parseDiscriminatedUnionMeta(schema) {
195
210
  label: deriveLabel(discriminatorKey),
196
211
  required: true,
197
212
  meta: {},
213
+ schema,
198
214
  options: discriminatorOptions
199
215
  };
200
216
  return {
@@ -495,7 +511,7 @@ function ScalarField({
495
511
  onChange: (value) => {
496
512
  const coerced = coerceValue(field.type, value, coercions);
497
513
  rhfField.onChange(coerced);
498
- field.meta.onChange?.(coerced, formMethods);
514
+ void field.meta.onChange?.(coerced, formMethods);
499
515
  },
500
516
  onBlur: rhfField.onBlur,
501
517
  ref: rhfField.ref,
@@ -505,7 +521,9 @@ function ScalarField({
505
521
  error: fieldState.error?.message,
506
522
  required: field.required,
507
523
  disabled: field.meta.disabled || contextDisabled,
508
- meta: field.meta
524
+ options: field.meta.options,
525
+ meta: field.meta,
526
+ schema: field.schema
509
527
  }
510
528
  )
511
529
  }
@@ -537,7 +555,7 @@ function BooleanField({
537
555
  value: rhfField.value ?? false,
538
556
  onChange: (value) => {
539
557
  rhfField.onChange(value);
540
- field.meta.onChange?.(value, formMethods);
558
+ void field.meta.onChange?.(value, formMethods);
541
559
  },
542
560
  onBlur: rhfField.onBlur,
543
561
  ref: rhfField.ref,
@@ -547,7 +565,8 @@ function BooleanField({
547
565
  error: fieldState.error?.message,
548
566
  required: field.required,
549
567
  disabled: field.meta.disabled || rhfField.disabled || contextDisabled,
550
- meta: field.meta
568
+ meta: field.meta,
569
+ schema: field.schema
551
570
  }
552
571
  )
553
572
  }
@@ -579,7 +598,7 @@ function SelectField({
579
598
  value: rhfField.value ?? "",
580
599
  onChange: (value) => {
581
600
  rhfField.onChange(value);
582
- field.meta.onChange?.(value, formMethods);
601
+ void field.meta.onChange?.(value, formMethods);
583
602
  },
584
603
  onBlur: rhfField.onBlur,
585
604
  ref: rhfField.ref,
@@ -590,7 +609,8 @@ function SelectField({
590
609
  required: field.required,
591
610
  disabled: field.meta.disabled || contextDisabled,
592
611
  options: field.options,
593
- meta: field.meta
612
+ meta: field.meta,
613
+ schema: field.schema
594
614
  }
595
615
  )
596
616
  }