@ic-reactor/candid 3.0.11-beta.2 → 3.0.13-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.
- package/dist/display-reactor.d.ts +1 -2
- package/dist/display-reactor.d.ts.map +1 -1
- package/dist/display-reactor.js +1 -1
- package/dist/display-reactor.js.map +1 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/metadata-display-reactor.d.ts +3 -3
- package/dist/metadata-display-reactor.d.ts.map +1 -1
- package/dist/metadata-display-reactor.js +2 -2
- package/dist/metadata-display-reactor.js.map +1 -1
- package/dist/visitor/arguments/helpers.d.ts +40 -0
- package/dist/visitor/arguments/helpers.d.ts.map +1 -0
- package/dist/visitor/arguments/helpers.js +81 -0
- package/dist/visitor/arguments/helpers.js.map +1 -0
- package/dist/visitor/arguments/index.d.ts +64 -42
- package/dist/visitor/arguments/index.d.ts.map +1 -1
- package/dist/visitor/arguments/index.js +472 -85
- package/dist/visitor/arguments/index.js.map +1 -1
- package/dist/visitor/arguments/types.d.ts +481 -46
- package/dist/visitor/arguments/types.d.ts.map +1 -1
- package/dist/visitor/helpers.d.ts +1 -1
- package/dist/visitor/helpers.d.ts.map +1 -1
- package/dist/visitor/returns/index.d.ts +1 -2
- package/dist/visitor/returns/index.d.ts.map +1 -1
- package/dist/visitor/returns/index.js +2 -3
- package/dist/visitor/returns/index.js.map +1 -1
- package/dist/visitor/types.d.ts +2 -3
- package/dist/visitor/types.d.ts.map +1 -1
- package/dist/visitor/types.js +1 -2
- package/dist/visitor/types.js.map +1 -1
- package/package.json +6 -3
- package/src/display-reactor.ts +4 -6
- package/src/index.ts +0 -1
- package/src/metadata-display-reactor.ts +8 -8
- package/src/visitor/arguments/helpers.ts +104 -0
- package/src/visitor/arguments/index.test.ts +544 -52
- package/src/visitor/arguments/index.ts +590 -151
- package/src/visitor/arguments/schema.test.ts +215 -0
- package/src/visitor/arguments/types.ts +554 -62
- package/src/visitor/helpers.ts +1 -1
- package/src/visitor/returns/index.test.ts +1 -1
- package/src/visitor/returns/index.ts +2 -3
- package/src/visitor/types.ts +2 -3
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { BaseActor, FunctionName, FunctionType } from "@ic-reactor/core"
|
|
2
|
+
import * as z from "zod"
|
|
2
3
|
|
|
3
4
|
// ════════════════════════════════════════════════════════════════════════════
|
|
4
5
|
// Field Type Union
|
|
@@ -19,150 +20,641 @@ export type ArgumentFieldType =
|
|
|
19
20
|
| "null"
|
|
20
21
|
| "unknown"
|
|
21
22
|
|
|
23
|
+
// ════════════════════════════════════════════════════════════════════════════
|
|
24
|
+
// Component Type Hints
|
|
25
|
+
// ════════════════════════════════════════════════════════════════════════════
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Suggested component type for rendering the field.
|
|
29
|
+
* 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
|
+
*/
|
|
43
|
+
export type FieldComponentType =
|
|
44
|
+
| "record-container"
|
|
45
|
+
| "tuple-container"
|
|
46
|
+
| "variant-select"
|
|
47
|
+
| "optional-toggle"
|
|
48
|
+
| "vector-list"
|
|
49
|
+
| "blob-upload"
|
|
50
|
+
| "principal-input"
|
|
51
|
+
| "text-input"
|
|
52
|
+
| "number-input"
|
|
53
|
+
| "boolean-checkbox"
|
|
54
|
+
| "null-hidden"
|
|
55
|
+
| "recursive-lazy"
|
|
56
|
+
| "unknown-fallback"
|
|
57
|
+
|
|
58
|
+
// ════════════════════════════════════════════════════════════════════════════
|
|
59
|
+
// Render Hints for UI Rendering Strategy
|
|
60
|
+
// ════════════════════════════════════════════════════════════════════════════
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Input type hints for HTML input elements.
|
|
64
|
+
* Used by primitive fields to suggest the appropriate input type.
|
|
65
|
+
*/
|
|
66
|
+
export type InputType =
|
|
67
|
+
| "text"
|
|
68
|
+
| "number"
|
|
69
|
+
| "checkbox"
|
|
70
|
+
| "select"
|
|
71
|
+
| "file"
|
|
72
|
+
| "textarea"
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Rendering hints for the UI.
|
|
76
|
+
* 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
|
+
*/
|
|
90
|
+
export interface RenderHint {
|
|
91
|
+
/** Whether this field has its own container/card styling (compound types) */
|
|
92
|
+
isCompound: boolean
|
|
93
|
+
/** Whether this is a leaf input (primitive types) */
|
|
94
|
+
isPrimitive: boolean
|
|
95
|
+
/** Suggested input type for HTML input elements */
|
|
96
|
+
inputType?: InputType
|
|
97
|
+
/** Description or help text for the field (derived from Candid) */
|
|
98
|
+
description?: string
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// ════════════════════════════════════════════════════════════════════════════
|
|
102
|
+
// Primitive Input Props
|
|
103
|
+
// ════════════════════════════════════════════════════════════════════════════
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Pre-computed HTML input props for primitive fields.
|
|
107
|
+
* Can be spread directly onto an input element.
|
|
108
|
+
*
|
|
109
|
+
* @example
|
|
110
|
+
* ```tsx
|
|
111
|
+
* <input {...field.inputProps} value={value} onChange={handleChange} />
|
|
112
|
+
* ```
|
|
113
|
+
*/
|
|
114
|
+
export interface PrimitiveInputProps {
|
|
115
|
+
/** HTML input type */
|
|
116
|
+
type?: "text" | "number" | "checkbox"
|
|
117
|
+
/** Placeholder text */
|
|
118
|
+
placeholder?: string
|
|
119
|
+
/** Minimum value for number inputs */
|
|
120
|
+
min?: string | number
|
|
121
|
+
/** Maximum value for number inputs */
|
|
122
|
+
max?: string | number
|
|
123
|
+
/** Step value for number inputs */
|
|
124
|
+
step?: string | number
|
|
125
|
+
/** Pattern for text inputs */
|
|
126
|
+
pattern?: string
|
|
127
|
+
/** Input mode for virtual keyboards */
|
|
128
|
+
inputMode?: "text" | "numeric" | "decimal"
|
|
129
|
+
/** Autocomplete hint */
|
|
130
|
+
autoComplete?: string
|
|
131
|
+
/** Whether to check spelling */
|
|
132
|
+
spellCheck?: boolean
|
|
133
|
+
/** Minimum length for text inputs */
|
|
134
|
+
minLength?: number
|
|
135
|
+
/** Maximum length for text inputs */
|
|
136
|
+
maxLength?: number
|
|
137
|
+
}
|
|
138
|
+
|
|
22
139
|
// ════════════════════════════════════════════════════════════════════════════
|
|
23
140
|
// Base Field Interface
|
|
24
141
|
// ════════════════════════════════════════════════════════════════════════════
|
|
25
142
|
|
|
26
|
-
export interface
|
|
143
|
+
export interface FieldBase<TValue = unknown> {
|
|
27
144
|
/** The field type */
|
|
28
145
|
type: ArgumentFieldType
|
|
29
|
-
/**
|
|
146
|
+
/** Raw label from Candid: "__arg0", "_0_" */
|
|
30
147
|
label: string
|
|
31
|
-
/**
|
|
32
|
-
|
|
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
|
+
*/
|
|
157
|
+
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
|
+
*/
|
|
170
|
+
name: string
|
|
171
|
+
/**
|
|
172
|
+
* Suggested component type for rendering this field.
|
|
173
|
+
* Eliminates the need for switch statements in the frontend.
|
|
174
|
+
*/
|
|
175
|
+
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
|
+
*/
|
|
180
|
+
renderHint: RenderHint
|
|
181
|
+
/** Zod schema for field validation */
|
|
182
|
+
schema: z.ZodTypeAny
|
|
183
|
+
/** Default value for the field */
|
|
184
|
+
defaultValue: TValue
|
|
185
|
+
/** Original Candid type name for reference */
|
|
186
|
+
candidType?: string
|
|
33
187
|
}
|
|
34
188
|
|
|
35
189
|
// ════════════════════════════════════════════════════════════════════════════
|
|
36
190
|
// Compound Types
|
|
37
191
|
// ════════════════════════════════════════════════════════════════════════════
|
|
38
192
|
|
|
39
|
-
export interface
|
|
193
|
+
export interface RecordField extends FieldBase<Record<string, unknown>> {
|
|
40
194
|
type: "record"
|
|
41
|
-
fields
|
|
42
|
-
|
|
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>
|
|
43
199
|
}
|
|
44
200
|
|
|
45
|
-
export interface
|
|
201
|
+
export interface VariantField extends FieldBase<Record<string, unknown>> {
|
|
46
202
|
type: "variant"
|
|
47
|
-
fields
|
|
203
|
+
/** All variant option fields */
|
|
204
|
+
fields: Field[]
|
|
205
|
+
/** List of variant option names */
|
|
48
206
|
options: string[]
|
|
207
|
+
/** Default selected option */
|
|
49
208
|
defaultOption: string
|
|
50
|
-
|
|
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
|
|
51
261
|
}
|
|
52
262
|
|
|
53
|
-
export interface
|
|
263
|
+
export interface TupleField extends FieldBase<unknown[]> {
|
|
54
264
|
type: "tuple"
|
|
55
|
-
fields
|
|
56
|
-
|
|
265
|
+
/** Tuple element fields in order */
|
|
266
|
+
fields: Field[]
|
|
57
267
|
}
|
|
58
268
|
|
|
59
|
-
export interface
|
|
269
|
+
export interface OptionalField extends FieldBase<null> {
|
|
60
270
|
type: "optional"
|
|
61
|
-
|
|
62
|
-
|
|
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
|
|
63
303
|
}
|
|
64
304
|
|
|
65
|
-
export interface
|
|
305
|
+
export interface VectorField extends FieldBase<unknown[]> {
|
|
66
306
|
type: "vector"
|
|
67
|
-
|
|
68
|
-
|
|
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
|
+
/**
|
|
343
|
+
* Blob field size limits.
|
|
344
|
+
*/
|
|
345
|
+
export interface BlobLimits {
|
|
346
|
+
/** Maximum bytes when entering as hex (e.g., 512 bytes) */
|
|
347
|
+
maxHexBytes: number
|
|
348
|
+
/** Maximum file size in bytes (e.g., 2MB ICP limit) */
|
|
349
|
+
maxFileBytes: number
|
|
350
|
+
/** Maximum hex display length before truncation */
|
|
351
|
+
maxHexDisplayLength: number
|
|
69
352
|
}
|
|
70
353
|
|
|
71
|
-
|
|
354
|
+
/**
|
|
355
|
+
* Validation result for blob input.
|
|
356
|
+
*/
|
|
357
|
+
export interface BlobValidationResult {
|
|
358
|
+
/** Whether the input is valid */
|
|
359
|
+
valid: boolean
|
|
360
|
+
/** Error message if invalid */
|
|
361
|
+
error?: string
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
export interface BlobField extends FieldBase<string> {
|
|
72
365
|
type: "blob"
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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
|
|
76
394
|
}
|
|
77
395
|
|
|
78
|
-
export interface
|
|
396
|
+
export interface RecursiveField extends FieldBase<undefined> {
|
|
79
397
|
type: "recursive"
|
|
398
|
+
/** Type name for the recursive type */
|
|
399
|
+
typeName: string
|
|
80
400
|
/** Lazily extract the inner field to prevent infinite loops */
|
|
81
|
-
extract: () =>
|
|
401
|
+
extract: () => Field
|
|
402
|
+
/**
|
|
403
|
+
* Get default value for the recursive type.
|
|
404
|
+
* Evaluates the inner type on demand.
|
|
405
|
+
*/
|
|
406
|
+
getInnerDefault: () => unknown
|
|
82
407
|
}
|
|
83
408
|
|
|
84
409
|
// ════════════════════════════════════════════════════════════════════════════
|
|
85
410
|
// Primitive Types
|
|
86
411
|
// ════════════════════════════════════════════════════════════════════════════
|
|
87
412
|
|
|
88
|
-
export interface
|
|
413
|
+
export interface PrincipalField extends FieldBase<string> {
|
|
89
414
|
type: "principal"
|
|
90
|
-
/** Display format: string */
|
|
91
|
-
defaultValue: string
|
|
92
415
|
maxLength: number
|
|
93
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
|
|
94
425
|
}
|
|
95
426
|
|
|
96
|
-
export interface
|
|
427
|
+
export interface NumberField extends FieldBase<string> {
|
|
97
428
|
type: "number"
|
|
98
|
-
/**
|
|
99
|
-
|
|
100
|
-
|
|
429
|
+
/**
|
|
430
|
+
* Original Candid type: nat, int, nat8, nat16, nat32, nat64, int8, int16, int32, int64, float32, float64
|
|
431
|
+
*/
|
|
101
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
|
|
102
451
|
}
|
|
103
452
|
|
|
104
|
-
export interface
|
|
453
|
+
export interface TextField extends FieldBase<string> {
|
|
105
454
|
type: "text"
|
|
106
|
-
|
|
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
|
|
107
469
|
}
|
|
108
470
|
|
|
109
|
-
export interface
|
|
471
|
+
export interface BooleanField extends FieldBase<boolean> {
|
|
110
472
|
type: "boolean"
|
|
111
|
-
|
|
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
|
|
112
481
|
}
|
|
113
482
|
|
|
114
|
-
export interface
|
|
483
|
+
export interface NullField extends FieldBase<null> {
|
|
115
484
|
type: "null"
|
|
116
|
-
defaultValue: null
|
|
117
485
|
}
|
|
118
486
|
|
|
119
|
-
export interface
|
|
487
|
+
export interface UnknownField extends FieldBase<undefined> {
|
|
120
488
|
type: "unknown"
|
|
121
|
-
defaultValue: undefined
|
|
122
489
|
}
|
|
123
490
|
|
|
124
491
|
// ════════════════════════════════════════════════════════════════════════════
|
|
125
492
|
// Union Type
|
|
126
493
|
// ════════════════════════════════════════════════════════════════════════════
|
|
127
494
|
|
|
128
|
-
export type
|
|
129
|
-
|
|
|
130
|
-
|
|
|
131
|
-
|
|
|
132
|
-
|
|
|
133
|
-
|
|
|
134
|
-
|
|
|
135
|
-
|
|
|
136
|
-
|
|
|
137
|
-
|
|
|
138
|
-
|
|
|
139
|
-
|
|
|
140
|
-
|
|
|
141
|
-
|
|
|
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
|
|
142
509
|
|
|
143
510
|
// ════════════════════════════════════════════════════════════════════════════
|
|
144
|
-
//
|
|
511
|
+
// Form Metadata - TanStack Form Integration
|
|
145
512
|
// ════════════════════════════════════════════════════════════════════════════
|
|
146
513
|
|
|
147
|
-
|
|
514
|
+
/**
|
|
515
|
+
* Form metadata for a Candid method.
|
|
516
|
+
* 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
|
+
*/
|
|
543
|
+
export interface ArgumentsMeta<
|
|
148
544
|
A = BaseActor,
|
|
149
545
|
Name extends FunctionName<A> = FunctionName<A>,
|
|
150
546
|
> {
|
|
547
|
+
/** Whether this is a "query" or "update" function */
|
|
151
548
|
functionType: FunctionType
|
|
549
|
+
/** The function name */
|
|
152
550
|
functionName: Name
|
|
153
|
-
|
|
551
|
+
/** Argument field definitions for rendering */
|
|
552
|
+
fields: Field[]
|
|
553
|
+
/** Default values for all arguments (as a tuple) */
|
|
554
|
+
defaultValues: unknown[]
|
|
555
|
+
/** Combined Zod schema for all arguments */
|
|
556
|
+
schema: z.ZodTuple<[z.ZodTypeAny, ...z.ZodTypeAny[]]>
|
|
557
|
+
/** Number of arguments */
|
|
558
|
+
argCount: number
|
|
559
|
+
/** Whether the function takes no arguments */
|
|
560
|
+
isNoArgs: boolean
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
/**
|
|
564
|
+
* Options that can be spread into useForm().
|
|
565
|
+
* Pre-configured with defaultValues and validators.
|
|
566
|
+
*/
|
|
567
|
+
export interface FormOptions {
|
|
568
|
+
/** Initial form values */
|
|
154
569
|
defaultValues: unknown[]
|
|
570
|
+
/** Validators using the Zod schema */
|
|
571
|
+
validators: {
|
|
572
|
+
onChange: z.ZodTypeAny
|
|
573
|
+
onBlur: z.ZodTypeAny
|
|
574
|
+
}
|
|
155
575
|
}
|
|
156
576
|
|
|
157
|
-
|
|
158
|
-
|
|
577
|
+
/**
|
|
578
|
+
* Service-level form metadata.
|
|
579
|
+
* Maps each method name to its FormMeta.
|
|
580
|
+
*/
|
|
581
|
+
export type ArgumentsServiceMeta<A = BaseActor> = {
|
|
582
|
+
[K in FunctionName<A>]: ArgumentsMeta<A, K>
|
|
159
583
|
}
|
|
160
584
|
|
|
161
585
|
// ════════════════════════════════════════════════════════════════════════════
|
|
162
|
-
// Type Utilities
|
|
586
|
+
// Type Utilities & Guards
|
|
163
587
|
// ════════════════════════════════════════════════════════════════════════════
|
|
164
588
|
|
|
165
|
-
|
|
166
|
-
|
|
589
|
+
/** Extract a specific field type */
|
|
590
|
+
export type FieldByType<T extends ArgumentFieldType> = Extract<
|
|
591
|
+
Field,
|
|
167
592
|
{ type: T }
|
|
168
593
|
>
|
|
594
|
+
|
|
595
|
+
/**
|
|
596
|
+
* Props type helper for field components.
|
|
597
|
+
* 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
|
+
*/
|
|
612
|
+
export type FieldProps<T extends ArgumentFieldType> = {
|
|
613
|
+
field: FieldByType<T>
|
|
614
|
+
renderField?: (child: Field) => React.ReactNode
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
/** Compound field types that contain other fields */
|
|
618
|
+
export type CompoundField =
|
|
619
|
+
| RecordField
|
|
620
|
+
| VariantField
|
|
621
|
+
| TupleField
|
|
622
|
+
| OptionalField
|
|
623
|
+
| VectorField
|
|
624
|
+
| RecursiveField
|
|
625
|
+
|
|
626
|
+
/** Primitive field types */
|
|
627
|
+
export type PrimitiveField =
|
|
628
|
+
| PrincipalField
|
|
629
|
+
| NumberField
|
|
630
|
+
| TextField
|
|
631
|
+
| BooleanField
|
|
632
|
+
| 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
|