@ic-reactor/candid 3.0.13-beta.0 → 3.0.14-beta.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.
@@ -1,24 +1,14 @@
1
1
  import type { BaseActor, FunctionName, FunctionType } from "@ic-reactor/core"
2
2
  import * as z from "zod"
3
+ import type { VisitorDataType, TextFormat, NumberFormat } from "../types"
4
+
5
+ export type { TextFormat, NumberFormat }
3
6
 
4
7
  // ════════════════════════════════════════════════════════════════════════════
5
8
  // Field Type Union
6
9
  // ════════════════════════════════════════════════════════════════════════════
7
10
 
8
- export type ArgumentFieldType =
9
- | "record"
10
- | "variant"
11
- | "tuple"
12
- | "optional"
13
- | "vector"
14
- | "blob"
15
- | "recursive"
16
- | "principal"
17
- | "number"
18
- | "text"
19
- | "boolean"
20
- | "null"
21
- | "unknown"
11
+ export type ArgumentFieldType = VisitorDataType
22
12
 
23
13
  // ════════════════════════════════════════════════════════════════════════════
24
14
  // Component Type Hints
@@ -27,18 +17,6 @@ export type ArgumentFieldType =
27
17
  /**
28
18
  * Suggested component type for rendering the field.
29
19
  * This eliminates the need for switch statements in the frontend.
30
- *
31
- * @example
32
- * ```tsx
33
- * const componentMap = {
34
- * 'text-input': TextField,
35
- * 'number-input': NumberField,
36
- * 'boolean-checkbox': BooleanField,
37
- * // ...
38
- * }
39
- * const Component = componentMap[field.component]
40
- * return <Component field={field} />
41
- * ```
42
20
  */
43
21
  export type FieldComponentType =
44
22
  | "record-container"
@@ -74,18 +52,6 @@ export type InputType =
74
52
  /**
75
53
  * Rendering hints for the UI.
76
54
  * Eliminates the need for frontend to maintain COMPLEX_TYPES arrays.
77
- *
78
- * @example
79
- * ```tsx
80
- * // Frontend no longer needs:
81
- * // const COMPLEX_TYPES = ["record", "tuple", "variant", "vector", "optional"]
82
- *
83
- * // Instead use:
84
- * if (field.renderHint.isCompound) {
85
- * return <CompoundFieldRenderer field={field} />
86
- * }
87
- * return <PrimitiveInput field={field} />
88
- * ```
89
55
  */
90
56
  export interface RenderHint {
91
57
  /** Whether this field has its own container/card styling (compound types) */
@@ -105,15 +71,10 @@ export interface RenderHint {
105
71
  /**
106
72
  * Pre-computed HTML input props for primitive fields.
107
73
  * Can be spread directly onto an input element.
108
- *
109
- * @example
110
- * ```tsx
111
- * <input {...field.inputProps} value={value} onChange={handleChange} />
112
- * ```
113
74
  */
114
75
  export interface PrimitiveInputProps {
115
- /** HTML input type */
116
- type?: "text" | "number" | "checkbox"
76
+ /** HTML input type - includes format-specific types */
77
+ type?: "text" | "number" | "checkbox" | "email" | "url" | "tel"
117
78
  /** Placeholder text */
118
79
  placeholder?: string
119
80
  /** Minimum value for number inputs */
@@ -122,10 +83,10 @@ export interface PrimitiveInputProps {
122
83
  max?: string | number
123
84
  /** Step value for number inputs */
124
85
  step?: string | number
125
- /** Pattern for text inputs */
86
+ /** Pattern for text inputs (e.g., phone numbers) */
126
87
  pattern?: string
127
88
  /** Input mode for virtual keyboards */
128
- inputMode?: "text" | "numeric" | "decimal"
89
+ inputMode?: "text" | "numeric" | "decimal" | "email" | "tel" | "url"
129
90
  /** Autocomplete hint */
130
91
  autoComplete?: string
131
92
  /** Whether to check spelling */
@@ -140,205 +101,29 @@ export interface PrimitiveInputProps {
140
101
  // Base Field Interface
141
102
  // ════════════════════════════════════════════════════════════════════════════
142
103
 
143
- export interface FieldBase<TValue = unknown> {
104
+ interface FieldBase<T extends VisitorDataType = VisitorDataType> {
144
105
  /** The field type */
145
- type: ArgumentFieldType
106
+ type: T
146
107
  /** Raw label from Candid: "__arg0", "_0_" */
147
108
  label: string
148
- /**
149
- * Pre-formatted display label for UI rendering.
150
- * Transforms raw labels into human-readable format.
151
- *
152
- * @example
153
- * "__arg0" => "Arg 0"
154
- * "_0_" => "Item 0"
155
- * "created_at_time" => "Created At Time"
156
- */
109
+ /** Pre-formatted display label for UI rendering */
157
110
  displayLabel: string
158
- /**
159
- * Form field name path for binding.
160
- * Uses bracket notation for array indices: `[0]`, `args[0].owner`, `tags[1]`
161
- * Compatible with TanStack Form's `form.Field` name prop.
162
- *
163
- * @example
164
- * ```tsx
165
- * <form.Field name={field.name}>
166
- * {(fieldApi) => <input {...} />}
167
- * </form.Field>
168
- * ```
169
- */
111
+ /** Form field name path for binding */
170
112
  name: string
171
- /**
172
- * Suggested component type for rendering this field.
173
- * Eliminates the need for switch statements in the frontend.
174
- */
113
+ /** Suggested component type for rendering this field */
175
114
  component: FieldComponentType
176
- /**
177
- * Rendering hints for UI strategy.
178
- * Use this to determine if the field needs a container or is a simple input.
179
- */
115
+ /** Rendering hints for UI strategy */
180
116
  renderHint: RenderHint
181
117
  /** Zod schema for field validation */
182
118
  schema: z.ZodTypeAny
183
- /** Default value for the field */
184
- defaultValue: TValue
185
119
  /** Original Candid type name for reference */
186
- candidType?: string
120
+ candidType: string
187
121
  }
188
122
 
189
123
  // ════════════════════════════════════════════════════════════════════════════
190
- // Compound Types
124
+ // Type-Specific Extras
191
125
  // ════════════════════════════════════════════════════════════════════════════
192
126
 
193
- export interface RecordField extends FieldBase<Record<string, unknown>> {
194
- type: "record"
195
- /** Child fields in the record */
196
- fields: Field[]
197
- /** Map of field label to its metadata for quick lookup */
198
- fieldMap: Map<string, Field>
199
- }
200
-
201
- export interface VariantField extends FieldBase<Record<string, unknown>> {
202
- type: "variant"
203
- /** All variant option fields */
204
- fields: Field[]
205
- /** List of variant option names */
206
- options: string[]
207
- /** Default selected option */
208
- defaultOption: string
209
- /** Map of option name to its field metadata */
210
- optionMap: Map<string, Field>
211
- /**
212
- * Get default value for a specific option.
213
- * Useful when switching between variant options.
214
- *
215
- * @example
216
- * ```tsx
217
- * const handleOptionChange = (newOption: string) => {
218
- * const newDefault = field.getOptionDefault(newOption)
219
- * fieldApi.handleChange(newDefault)
220
- * }
221
- * ```
222
- */
223
- getOptionDefault: (option: string) => Record<string, unknown>
224
- /**
225
- * Get the field for a specific option.
226
- *
227
- * @example
228
- * ```tsx
229
- * const transferField = field.getField("Transfer")
230
- * ```
231
- */
232
- getField: (option: string) => Field
233
- /**
234
- * Get the currently selected option from a value.
235
- * Returns the first valid key found, or the default option.
236
- *
237
- * @example
238
- * ```tsx
239
- * const selectedOption = field.getSelectedOption(currentValue)
240
- * // { Transfer: {...} } => "Transfer"
241
- * ```
242
- */
243
- getSelectedOption: (value: Record<string, unknown>) => string
244
- /**
245
- * Get the selected field from a value.
246
- * Combines getSelectedOption and getField for convenience.
247
- *
248
- * @example
249
- * ```tsx
250
- * // Current (verbose):
251
- * const validKeys = Object.keys(currentValue).filter(k => field.options.includes(k))
252
- * const selected = validKeys[0] ?? field.options[0]
253
- * const selectedIndex = Math.max(0, field.options.indexOf(selected))
254
- * const selectedField = field.fields[selectedIndex]
255
- *
256
- * // Proposed (simple):
257
- * const selectedField = field.getSelectedField(currentValue)
258
- * ```
259
- */
260
- getSelectedField: (value: Record<string, unknown>) => Field
261
- }
262
-
263
- export interface TupleField extends FieldBase<unknown[]> {
264
- type: "tuple"
265
- /** Tuple element fields in order */
266
- fields: Field[]
267
- }
268
-
269
- export interface OptionalField extends FieldBase<null> {
270
- type: "optional"
271
- /** The inner field when value is present */
272
- innerField: Field
273
- /**
274
- * Get default value when enabling the optional.
275
- * Returns the inner field's default value.
276
- *
277
- * @example
278
- * ```tsx
279
- * const handleToggle = (enabled: boolean) => {
280
- * if (enabled) {
281
- * fieldApi.handleChange(field.getInnerDefault())
282
- * } else {
283
- * fieldApi.handleChange(null)
284
- * }
285
- * }
286
- * ```
287
- */
288
- getInnerDefault: () => unknown
289
- /**
290
- * Check if a value represents an enabled optional.
291
- * Returns true if the value is not null or undefined.
292
- *
293
- * @example
294
- * ```tsx
295
- * // Current:
296
- * const enabled = fieldApi.state.value !== null && typeof fieldApi.state.value !== "undefined"
297
- *
298
- * // Proposed:
299
- * const enabled = field.isEnabled(fieldApi.state.value)
300
- * ```
301
- */
302
- isEnabled: (value: unknown) => boolean
303
- }
304
-
305
- export interface VectorField extends FieldBase<unknown[]> {
306
- type: "vector"
307
- /** Template field for vector items */
308
- itemField: Field
309
- /**
310
- * Get a new item with default values.
311
- * Used when adding items to the vector.
312
- *
313
- * @example
314
- * ```tsx
315
- * <button onClick={() => fieldApi.pushValue(field.getItemDefault())}>
316
- * Add Item
317
- * </button>
318
- * ```
319
- */
320
- getItemDefault: () => unknown
321
- /**
322
- * Create a properly configured item field for a specific index.
323
- * Handles name path and label generation.
324
- *
325
- * @example
326
- * ```tsx
327
- * // Current:
328
- * renderField({
329
- * ...field.itemField,
330
- * label: itemLabel,
331
- * name: itemFieldName
332
- * })
333
- *
334
- * // Proposed:
335
- * const itemField = field.createItemField(index, { label: itemLabel })
336
- * renderField(itemField)
337
- * ```
338
- */
339
- createItemField: (index: number, overrides?: { label?: string }) => Field
340
- }
341
-
342
127
  /**
343
128
  * Blob field size limits.
344
129
  */
@@ -361,151 +146,166 @@ export interface BlobValidationResult {
361
146
  error?: string
362
147
  }
363
148
 
364
- export interface BlobField extends FieldBase<string> {
365
- type: "blob"
366
- /** Item field for individual bytes (nat8) */
367
- itemField: Field
368
- /** Accepted input formats */
369
- acceptedFormats: ("hex" | "base64" | "file")[]
370
- /** Size limits for blob input */
371
- limits: BlobLimits
372
- /**
373
- * Normalize hex input (remove 0x prefix, lowercase, etc.)
374
- *
375
- * @example
376
- * ```tsx
377
- * const normalized = field.normalizeHex("0xDEADBEEF")
378
- * // => "deadbeef"
379
- * ```
380
- */
381
- normalizeHex: (input: string) => string
382
- /**
383
- * Validate blob input value.
384
- *
385
- * @example
386
- * ```tsx
387
- * const result = field.validateInput(value)
388
- * if (!result.valid) {
389
- * setError(result.error)
390
- * }
391
- * ```
392
- */
393
- validateInput: (value: string | Uint8Array) => BlobValidationResult
394
- }
395
-
396
- export interface RecursiveField extends FieldBase<undefined> {
397
- type: "recursive"
398
- /** Type name for the recursive type */
399
- typeName: string
400
- /** Lazily extract the inner field to prevent infinite loops */
401
- extract: () => Field
402
- /**
403
- * Get default value for the recursive type.
404
- * Evaluates the inner type on demand.
405
- */
406
- getInnerDefault: () => unknown
407
- }
408
-
409
- // ════════════════════════════════════════════════════════════════════════════
410
- // Primitive Types
411
- // ════════════════════════════════════════════════════════════════════════════
412
-
413
- export interface PrincipalField extends FieldBase<string> {
414
- type: "principal"
415
- maxLength: number
416
- minLength: number
417
- /**
418
- * Pre-computed HTML input props for direct spreading.
419
- * @example
420
- * ```tsx
421
- * <input {...field.inputProps} value={value} onChange={handleChange} />
422
- * ```
423
- */
424
- inputProps: PrimitiveInputProps
425
- }
426
-
427
- export interface NumberField extends FieldBase<string> {
428
- type: "number"
429
- /**
430
- * Original Candid type: nat, int, nat8, nat16, nat32, nat64, int8, int16, int32, int64, float32, float64
431
- */
432
- candidType: string
433
- /** Whether this is an unsigned type */
434
- unsigned: boolean
435
- /** Whether this is a floating point type */
436
- isFloat: boolean
437
- /** Bit width if applicable (8, 16, 32, 64, or undefined for unbounded) */
438
- bits?: number
439
- /** Minimum value constraint (for bounded types) */
440
- min?: string
441
- /** Maximum value constraint (for bounded types) */
442
- max?: string
443
- /**
444
- * Pre-computed HTML input props for direct spreading.
445
- * @example
446
- * ```tsx
447
- * <input {...field.inputProps} value={value} onChange={handleChange} />
448
- * ```
449
- */
450
- inputProps: PrimitiveInputProps
451
- }
452
-
453
- export interface TextField extends FieldBase<string> {
454
- type: "text"
455
- /** Minimum length constraint */
456
- minLength?: number
457
- /** Maximum length constraint */
458
- maxLength?: number
459
- /** Whether to render as multiline textarea */
460
- multiline?: boolean
461
- /**
462
- * Pre-computed HTML input props for direct spreading.
463
- * @example
464
- * ```tsx
465
- * <input {...field.inputProps} value={value} onChange={handleChange} />
466
- * ```
467
- */
468
- inputProps: PrimitiveInputProps
469
- }
470
-
471
- export interface BooleanField extends FieldBase<boolean> {
472
- type: "boolean"
473
- /**
474
- * Pre-computed HTML input props for direct spreading.
475
- * @example
476
- * ```tsx
477
- * <input {...field.inputProps} checked={value} onChange={handleChange} />
478
- * ```
479
- */
480
- inputProps: PrimitiveInputProps
481
- }
482
-
483
- export interface NullField extends FieldBase<null> {
484
- type: "null"
485
- }
486
-
487
- export interface UnknownField extends FieldBase<undefined> {
488
- type: "unknown"
489
- }
149
+ type FieldExtras<T extends VisitorDataType> = T extends "record"
150
+ ? {
151
+ /** Child fields in the record */
152
+ fields: FieldNode[]
153
+ /** Map of field label to its metadata for quick lookup */
154
+ fieldMap: Map<string, FieldNode>
155
+ defaultValue: Record<string, unknown>
156
+ }
157
+ : T extends "variant"
158
+ ? {
159
+ /** All variant option fields */
160
+ fields: FieldNode[]
161
+ /** List of variant option names */
162
+ options: string[]
163
+ /** Default selected option */
164
+ defaultOption: string
165
+ /** Map of option name to its field metadata */
166
+ optionMap: Map<string, FieldNode>
167
+ defaultValue: Record<string, unknown>
168
+ /** Get default value for a specific option */
169
+ getOptionDefault: (option: string) => Record<string, unknown>
170
+ /** Get the field for a specific option */
171
+ getField: (option: string) => FieldNode
172
+ /** Get the currently selected option from a value */
173
+ getSelectedOption: (value: Record<string, unknown>) => string
174
+ /** Get the selected field from a value */
175
+ getSelectedField: (value: Record<string, unknown>) => FieldNode
176
+ }
177
+ : T extends "tuple"
178
+ ? {
179
+ /** Tuple element fields in order */
180
+ fields: FieldNode[]
181
+ defaultValue: unknown[]
182
+ }
183
+ : T extends "optional"
184
+ ? {
185
+ /** The inner field when value is present */
186
+ innerField: FieldNode
187
+ defaultValue: null
188
+ /** Get default value when enabling the optional */
189
+ getInnerDefault: () => unknown
190
+ /** Check if a value represents an enabled optional */
191
+ isEnabled: (value: unknown) => boolean
192
+ }
193
+ : T extends "vector"
194
+ ? {
195
+ /** Template field for vector items */
196
+ itemField: FieldNode
197
+ defaultValue: unknown[]
198
+ /** Get a new item with default values */
199
+ getItemDefault: () => unknown
200
+ /** Create a properly configured item field for a specific index */
201
+ createItemField: (
202
+ index: number,
203
+ overrides?: { label?: string }
204
+ ) => FieldNode
205
+ }
206
+ : T extends "blob"
207
+ ? {
208
+ /** Item field for individual bytes (nat8) */
209
+ itemField: FieldNode
210
+ /** Accepted input formats */
211
+ acceptedFormats: ("hex" | "base64" | "file")[]
212
+ /** Size limits for blob input */
213
+ limits: BlobLimits
214
+ /** Normalize hex input */
215
+ normalizeHex: (input: string) => string
216
+ /** Validate blob input value */
217
+ validateInput: (
218
+ value: string | Uint8Array
219
+ ) => BlobValidationResult
220
+ defaultValue: string
221
+ }
222
+ : T extends "recursive"
223
+ ? {
224
+ /** Type name for the recursive type */
225
+ typeName: string
226
+ /** Lazily extract the inner field to prevent infinite loops */
227
+ extract: () => FieldNode
228
+ /** Get default value for the recursive type (evaluates lazily) */
229
+ getInnerDefault: () => unknown
230
+ defaultValue: undefined
231
+ }
232
+ : T extends "principal"
233
+ ? {
234
+ maxLength: number
235
+ minLength: number
236
+ /** Detected format based on label heuristics */
237
+ format: TextFormat
238
+ /** Pre-computed HTML input props */
239
+ inputProps: PrimitiveInputProps
240
+ defaultValue: string
241
+ }
242
+ : T extends "number"
243
+ ? {
244
+ /** Whether this is an unsigned type */
245
+ unsigned: boolean
246
+ /** Whether this is a floating point type */
247
+ isFloat: boolean
248
+ /** Bit width if applicable (8, 16, 32, 64, or undefined for unbounded) */
249
+ bits?: number
250
+ /** Minimum value constraint (for bounded types) */
251
+ min?: string
252
+ /** Maximum value constraint (for bounded types) */
253
+ max?: string
254
+ /** Detected format based on label heuristics */
255
+ format: NumberFormat
256
+ /** Pre-computed HTML input props */
257
+ inputProps: PrimitiveInputProps
258
+ defaultValue: string
259
+ }
260
+ : T extends "text"
261
+ ? {
262
+ /** Minimum length constraint */
263
+ minLength?: number
264
+ /** Maximum length constraint */
265
+ maxLength?: number
266
+ /** Whether to render as multiline textarea */
267
+ multiline?: boolean
268
+ /** Detected format based on label heuristics */
269
+ format: TextFormat
270
+ /** Pre-computed HTML input props */
271
+ inputProps: PrimitiveInputProps
272
+ defaultValue: string
273
+ }
274
+ : T extends "boolean"
275
+ ? {
276
+ /** Pre-computed HTML input props */
277
+ inputProps: PrimitiveInputProps
278
+ defaultValue: boolean
279
+ }
280
+ : T extends "null"
281
+ ? {
282
+ defaultValue: null
283
+ }
284
+ : T extends "unknown"
285
+ ? {
286
+ defaultValue: undefined
287
+ }
288
+ : {}
490
289
 
491
- // ════════════════════════════════════════════════════════════════════════════
492
- // Union Type
493
- // ════════════════════════════════════════════════════════════════════════════
494
-
495
- export type Field =
496
- | RecordField
497
- | VariantField
498
- | TupleField
499
- | OptionalField
500
- | VectorField
501
- | BlobField
502
- | RecursiveField
503
- | PrincipalField
504
- | NumberField
505
- | TextField
506
- | BooleanField
507
- | NullField
508
- | UnknownField
290
+ /**
291
+ * A unified field node that contains all metadata needed for rendering.
292
+ */
293
+ export type FieldNode<T extends VisitorDataType = VisitorDataType> =
294
+ FieldBase<T> & FieldExtras<T>
295
+
296
+ export type RecordField = FieldNode<"record">
297
+ export type VariantField = FieldNode<"variant">
298
+ export type TupleField = FieldNode<"tuple">
299
+ export type OptionalField = FieldNode<"optional">
300
+ export type VectorField = FieldNode<"vector">
301
+ export type BlobField = FieldNode<"blob">
302
+ export type RecursiveField = FieldNode<"recursive">
303
+ export type PrincipalField = FieldNode<"principal">
304
+ export type NumberField = FieldNode<"number">
305
+ export type TextField = FieldNode<"text">
306
+ export type BooleanField = FieldNode<"boolean">
307
+ export type NullField = FieldNode<"null">
308
+ export type UnknownField = FieldNode<"unknown">
509
309
 
510
310
  // ════════════════════════════════════════════════════════════════════════════
511
311
  // Form Metadata - TanStack Form Integration
@@ -514,31 +314,6 @@ export type Field =
514
314
  /**
515
315
  * Form metadata for a Candid method.
516
316
  * Contains all information needed to create a TanStack Form instance.
517
- *
518
- * @example
519
- * ```tsx
520
- * import { useForm } from '@tanstack/react-form'
521
- *
522
- * function MethodForm({ meta }: { meta: FormMeta }) {
523
- * const form = useForm({
524
- * ...meta.formOptions,
525
- * onSubmit: async ({ value }) => {
526
- * await actor[meta.functionName](...value)
527
- * }
528
- * })
529
- *
530
- * return (
531
- * <form onSubmit={(e) => { e.preventDefault(); form.handleSubmit() }}>
532
- * {meta.fields.map(field => (
533
- * <form.Field key={field.name} name={field.name}>
534
- * {(fieldApi) => <DynamicInput field={field} fieldApi={fieldApi} />}
535
- * </form.Field>
536
- * ))}
537
- * <button type="submit">Submit</button>
538
- * </form>
539
- * )
540
- * }
541
- * ```
542
317
  */
543
318
  export interface ArgumentsMeta<
544
319
  A = BaseActor,
@@ -549,7 +324,7 @@ export interface ArgumentsMeta<
549
324
  /** The function name */
550
325
  functionName: Name
551
326
  /** Argument field definitions for rendering */
552
- fields: Field[]
327
+ fields: FieldNode[]
553
328
  /** Default values for all arguments (as a tuple) */
554
329
  defaultValues: unknown[]
555
330
  /** Combined Zod schema for all arguments */
@@ -588,30 +363,17 @@ export type ArgumentsServiceMeta<A = BaseActor> = {
588
363
 
589
364
  /** Extract a specific field type */
590
365
  export type FieldByType<T extends ArgumentFieldType> = Extract<
591
- Field,
366
+ FieldNode,
592
367
  { type: T }
593
368
  >
594
369
 
595
370
  /**
596
371
  * Props type helper for field components.
597
372
  * Use this to type your field components for better DX.
598
- *
599
- * @example
600
- * ```tsx
601
- * const VariantField: React.FC<FieldProps<'variant'>> = ({ field, renderField }) => {
602
- * // field is properly typed as VariantField
603
- * return (
604
- * <div>
605
- * <select>{field.options.map(opt => ...)}</select>
606
- * {renderField?.(field.getSelectedField(currentValue))}
607
- * </div>
608
- * )
609
- * }
610
- * ```
611
373
  */
612
374
  export type FieldProps<T extends ArgumentFieldType> = {
613
375
  field: FieldByType<T>
614
- renderField?: (child: Field) => React.ReactNode
376
+ renderField?: (child: FieldNode) => React.ReactNode
615
377
  }
616
378
 
617
379
  /** Compound field types that contain other fields */
@@ -630,31 +392,3 @@ export type PrimitiveField =
630
392
  | TextField
631
393
  | BooleanField
632
394
  | NullField
633
-
634
- /**
635
- * A complete mapping of component types to React components.
636
- * Use this type when defining your component map.
637
- *
638
- * @example
639
- * ```tsx
640
- * const componentMap: ComponentMap<typeof MyTextInput, typeof MyNumberInput, ...> = {
641
- * 'text-input': MyTextInput,
642
- * 'number-input': MyNumberInput,
643
- * // ...
644
- * }
645
- * ```
646
- */
647
- export type ComponentMap<
648
- TComponents extends Record<FieldComponentType, unknown>,
649
- > = {
650
- [K in FieldComponentType]: TComponents[K]
651
- }
652
-
653
- /**
654
- * Get the component type for a given field component type.
655
- * Useful for typing dynamic component lookups.
656
- */
657
- export type GetComponentType<
658
- TMap extends Partial<Record<FieldComponentType, unknown>>,
659
- TKey extends FieldComponentType,
660
- > = TKey extends keyof TMap ? TMap[TKey] : never