sveltekit-discriminated-fields 0.3.0 → 0.4.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/README.md +63 -75
- package/dist/discriminated.d.ts +23 -23
- package/dist/discriminated.js +28 -20
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -69,21 +69,21 @@ Use `FieldVariants` to render variant-specific fields:
|
|
|
69
69
|
<p {...props}>Please select a shape type above.</p>
|
|
70
70
|
{/snippet}
|
|
71
71
|
|
|
72
|
-
{#snippet circle(
|
|
73
|
-
<label {...
|
|
74
|
-
Radius: <input {...
|
|
72
|
+
{#snippet circle(shape)}
|
|
73
|
+
<label {...shape}>
|
|
74
|
+
Radius: <input {...shape.fields.radius.as('number')} />
|
|
75
75
|
</label>
|
|
76
76
|
{/snippet}
|
|
77
77
|
|
|
78
|
-
{#snippet rectangle(
|
|
79
|
-
<div {...
|
|
80
|
-
<input {...
|
|
81
|
-
<input {...
|
|
78
|
+
{#snippet rectangle(shape)}
|
|
79
|
+
<div {...shape}>
|
|
80
|
+
<input {...shape.fields.width.as('number')} placeholder="Width" />
|
|
81
|
+
<input {...shape.fields.height.as('number')} placeholder="Height" />
|
|
82
82
|
</div>
|
|
83
83
|
{/snippet}
|
|
84
84
|
|
|
85
|
-
{#snippet point(
|
|
86
|
-
<p {...
|
|
85
|
+
{#snippet point(shape)}
|
|
86
|
+
<p {...shape}>Point has no additional fields.</p>
|
|
87
87
|
{/snippet}
|
|
88
88
|
</FieldVariants>
|
|
89
89
|
|
|
@@ -95,14 +95,14 @@ Use `FieldVariants` to render variant-specific fields:
|
|
|
95
95
|
|
|
96
96
|
Each variant snippet receives a single argument that mirrors how forms work in SvelteKit:
|
|
97
97
|
|
|
98
|
-
- **Spread for CSS targeting**: `{...
|
|
99
|
-
- **Access fields**: `
|
|
98
|
+
- **Spread for CSS targeting**: `{...shape}` - Adds the `data-fv` attribute for CSS visibility
|
|
99
|
+
- **Access fields**: `shape.fields.radius` - Provides the variant-specific form fields
|
|
100
100
|
|
|
101
101
|
This pattern is consistent with how you use forms: `<form {...form}>` + `form.fields.x`.
|
|
102
102
|
|
|
103
103
|
### Snippets and Fields
|
|
104
104
|
|
|
105
|
-
Each snippet receives correctly narrowed fields for that variant - TypeScript knows `
|
|
105
|
+
Each snippet receives correctly narrowed fields for that variant - TypeScript knows `shape.fields.radius` exists in the `circle` snippet but not in `rectangle`. Only valid discriminator values are accepted.
|
|
106
106
|
|
|
107
107
|
Snippets only receive fields **specific to that variant**. Fields common to all variants (same name and type) should be rendered outside `FieldVariants` to prevent accidental duplicate inputs. Fields shared by some but not all variants, or with differing types across variants, produce compile-time errors.
|
|
108
108
|
|
|
@@ -115,24 +115,24 @@ For radio button discriminators, you must use the `discriminated()` wrapper. The
|
|
|
115
115
|
import { shapeForm } from './data.remote';
|
|
116
116
|
import { discriminated, FieldVariants } from 'sveltekit-discriminated-fields';
|
|
117
117
|
|
|
118
|
-
const shape = $derived(discriminated('kind'
|
|
118
|
+
const shape = $derived(discriminated(shapeForm.fields, 'kind'));
|
|
119
119
|
</script>
|
|
120
120
|
|
|
121
121
|
<form {...shapeForm}>
|
|
122
122
|
<fieldset>
|
|
123
|
-
<label><input {...shape.kind.as("radio", "circle")} /> Circle</label>
|
|
124
|
-
<label><input {...shape.kind.as("radio", "rectangle")} /> Rectangle</label>
|
|
125
|
-
<label><input {...shape.kind.as("radio", "point")} /> Point</label>
|
|
123
|
+
<label><input {...shape.fields.kind.as("radio", "circle")} /> Circle</label>
|
|
124
|
+
<label><input {...shape.fields.kind.as("radio", "rectangle")} /> Rectangle</label>
|
|
125
|
+
<label><input {...shape.fields.kind.as("radio", "point")} /> Point</label>
|
|
126
126
|
</fieldset>
|
|
127
127
|
|
|
128
|
-
<FieldVariants fields={
|
|
128
|
+
<FieldVariants fields={shapeForm.fields} key="kind">
|
|
129
129
|
{#snippet fallback(props)}
|
|
130
130
|
<p {...props}>Select a shape type</p>
|
|
131
131
|
{/snippet}
|
|
132
132
|
|
|
133
|
-
{#snippet circle(
|
|
134
|
-
<label {...
|
|
135
|
-
Radius: <input {...
|
|
133
|
+
{#snippet circle(shape)}
|
|
134
|
+
<label {...shape}>
|
|
135
|
+
Radius: <input {...shape.fields.radius.as('number')} />
|
|
136
136
|
</label>
|
|
137
137
|
{/snippet}
|
|
138
138
|
|
|
@@ -148,11 +148,11 @@ See the [radio-form example](./test/src/routes/radio-form) for a complete workin
|
|
|
148
148
|
For select elements, you can use `.as("option", value)` for type-safe option values. This is optional - you can still use `value="..."` directly if you prefer:
|
|
149
149
|
|
|
150
150
|
```svelte
|
|
151
|
-
<select {...shape.kind.as("select")}>
|
|
151
|
+
<select {...shape.fields.kind.as("select")}>
|
|
152
152
|
<!-- Type-safe: typos caught at compile time -->
|
|
153
|
-
<option {...shape.kind.as("option")}>Select a shape...</option>
|
|
154
|
-
<option {...shape.kind.as("option", "circle")}>Circle</option>
|
|
155
|
-
<option {...shape.kind.as("option", "rectangle")}>Rectangle</option>
|
|
153
|
+
<option {...shape.fields.kind.as("option")}>Select a shape...</option>
|
|
154
|
+
<option {...shape.fields.kind.as("option", "circle")}>Circle</option>
|
|
155
|
+
<option {...shape.fields.kind.as("option", "rectangle")}>Rectangle</option>
|
|
156
156
|
|
|
157
157
|
<!-- Also works: standard HTML (no type checking) -->
|
|
158
158
|
<option value="point">Point</option>
|
|
@@ -203,16 +203,16 @@ const orderSchema = z.object({
|
|
|
203
203
|
<script lang="ts">
|
|
204
204
|
import { discriminated, FieldVariants } from 'sveltekit-discriminated-fields';
|
|
205
205
|
|
|
206
|
-
const shipping = $derived(discriminated(
|
|
206
|
+
const shipping = $derived(discriminated(orderForm.fields.shipping, 'method'));
|
|
207
207
|
</script>
|
|
208
208
|
|
|
209
|
-
<FieldVariants fields={shipping} key="method">
|
|
210
|
-
{#snippet pickup(
|
|
211
|
-
<input {...
|
|
209
|
+
<FieldVariants fields={orderForm.fields.shipping} key="method">
|
|
210
|
+
{#snippet pickup(shipping)}
|
|
211
|
+
<input {...shipping} {...shipping.fields.store.as('text')} />
|
|
212
212
|
{/snippet}
|
|
213
213
|
|
|
214
|
-
{#snippet delivery(
|
|
215
|
-
<input {...
|
|
214
|
+
{#snippet delivery(shipping)}
|
|
215
|
+
<input {...shipping} {...shipping.fields.address.as('text')} />
|
|
216
216
|
{/snippet}
|
|
217
217
|
</FieldVariants>
|
|
218
218
|
```
|
|
@@ -224,13 +224,13 @@ You can also have multiple discriminated unions in the same form, or even a disc
|
|
|
224
224
|
By default, `FieldVariants` requires a snippet for every variant - a compile error appears if one is missing, helping you avoid omissions. When you intentionally want to handle only some variants, use `partial={true}`:
|
|
225
225
|
|
|
226
226
|
```svelte
|
|
227
|
-
<FieldVariants fields={
|
|
228
|
-
{#snippet circle(
|
|
229
|
-
<input {...
|
|
227
|
+
<FieldVariants fields={shapeForm.fields} key="kind" partial={true}>
|
|
228
|
+
{#snippet circle(shape)}
|
|
229
|
+
<input {...shape} {...shape.fields.radius.as('number')} />
|
|
230
230
|
{/snippet}
|
|
231
231
|
|
|
232
|
-
{#snippet rectangle(
|
|
233
|
-
<input {...
|
|
232
|
+
{#snippet rectangle(shape)}
|
|
233
|
+
<input {...shape} {...shape.fields.width.as('number')} />
|
|
234
234
|
{/snippet}
|
|
235
235
|
|
|
236
236
|
<!-- point snippet omitted - nothing shown when point selected -->
|
|
@@ -247,11 +247,11 @@ By default, `FieldVariants` requires a snippet for every variant - a compile err
|
|
|
247
247
|
This means forms work without JavaScript, but once JS loads, you get full Svelte features:
|
|
248
248
|
|
|
249
249
|
```svelte
|
|
250
|
-
<FieldVariants fields={
|
|
251
|
-
{#snippet circle(
|
|
250
|
+
<FieldVariants fields={shapeForm.fields} key="kind">
|
|
251
|
+
{#snippet circle(shape)}
|
|
252
252
|
<!-- Svelte transitions work after hydration -->
|
|
253
|
-
<div {...
|
|
254
|
-
<input {...
|
|
253
|
+
<div {...shape} transition:slide>
|
|
254
|
+
<input {...shape.fields.radius.as('number')} />
|
|
255
255
|
</div>
|
|
256
256
|
{/snippet}
|
|
257
257
|
</FieldVariants>
|
|
@@ -262,7 +262,7 @@ This means forms work without JavaScript, but once JS loads, you get full Svelte
|
|
|
262
262
|
If you want to handle visibility yourself, disable CSS generation:
|
|
263
263
|
|
|
264
264
|
```svelte
|
|
265
|
-
<FieldVariants fields={
|
|
265
|
+
<FieldVariants fields={shapeForm.fields} key="kind" css={false}>
|
|
266
266
|
<!-- snippets -->
|
|
267
267
|
</FieldVariants>
|
|
268
268
|
```
|
|
@@ -273,9 +273,9 @@ When using SvelteKit's remote function `form()` with discriminated union schemas
|
|
|
273
273
|
|
|
274
274
|
The `discriminated()` function wraps your form fields to:
|
|
275
275
|
|
|
276
|
-
1.
|
|
277
|
-
2.
|
|
278
|
-
3. Provide a type-safe
|
|
276
|
+
1. Provide `.type` - the current discriminator value for TypeScript narrowing
|
|
277
|
+
2. Provide `.fields` - all variant fields accessible with proper typing
|
|
278
|
+
3. Provide a type-safe `.set()` method for programmatic updates
|
|
279
279
|
4. Fix `.as("radio", value)` to accept only valid discriminator values
|
|
280
280
|
|
|
281
281
|
The following example demonstrates conditionally rendering variant-specific fields with type-safe narrowing, without using `FieldVariants`. This approach requires JavaScript (unlike `FieldVariants` which works without JS):
|
|
@@ -285,15 +285,15 @@ The following example demonstrates conditionally rendering variant-specific fiel
|
|
|
285
285
|
import { shapeForm } from './data.remote';
|
|
286
286
|
import { discriminated } from 'sveltekit-discriminated-fields';
|
|
287
287
|
|
|
288
|
-
const shape = $derived(discriminated('kind'
|
|
288
|
+
const shape = $derived(discriminated(shapeForm.fields, 'kind'));
|
|
289
289
|
</script>
|
|
290
290
|
|
|
291
|
-
<!-- Use
|
|
292
|
-
{#if shape.
|
|
293
|
-
<input {...shape.radius.as('number')} /> <!-- TypeScript knows radius exists -->
|
|
294
|
-
{:else if shape.
|
|
295
|
-
<input {...shape.width.as('number')} /> <!-- TypeScript knows width exists -->
|
|
296
|
-
<input {...shape.height.as('number')} />
|
|
291
|
+
<!-- Use .type for narrowing, .fields for field access -->
|
|
292
|
+
{#if shape.type === 'circle'}
|
|
293
|
+
<input {...shape.fields.radius.as('number')} /> <!-- TypeScript knows radius exists -->
|
|
294
|
+
{:else if shape.type === 'rectangle'}
|
|
295
|
+
<input {...shape.fields.width.as('number')} /> <!-- TypeScript knows width exists -->
|
|
296
|
+
<input {...shape.fields.height.as('number')} />
|
|
297
297
|
{/if}
|
|
298
298
|
```
|
|
299
299
|
|
|
@@ -307,31 +307,31 @@ A component for rendering variant-specific form sections with CSS-only visibilit
|
|
|
307
307
|
|
|
308
308
|
**Props:**
|
|
309
309
|
|
|
310
|
-
| Prop | Type
|
|
311
|
-
| --------- |
|
|
312
|
-
| `fields` | `
|
|
313
|
-
| `key` | `string`
|
|
314
|
-
| `partial` | `boolean` (optional)
|
|
315
|
-
| `css` | `boolean` (optional)
|
|
310
|
+
| Prop | Type | Description |
|
|
311
|
+
| --------- | -------------------- | --------------------------------------------------------- |
|
|
312
|
+
| `fields` | `RemoteFormFields` | Raw form fields from `form.fields` (not wrapped) |
|
|
313
|
+
| `key` | `string` | The discriminator key (must match a field in the schema) |
|
|
314
|
+
| `partial` | `boolean` (optional) | Allow missing snippets for some variants (default: false) |
|
|
315
|
+
| `css` | `boolean` (optional) | Enable CSS visibility generation (default: true) |
|
|
316
316
|
|
|
317
317
|
**Snippets:**
|
|
318
318
|
|
|
319
319
|
- `fallback(props)` - Rendered when no variant is selected. Spread `props` onto your element.
|
|
320
|
-
- `{variantName}(
|
|
320
|
+
- `{variantName}(variant)` - One snippet per variant. Spread `variant` onto your container, access fields via `variant.fields`.
|
|
321
321
|
|
|
322
|
-
### `discriminated(
|
|
322
|
+
### `discriminated(fields, key)`
|
|
323
323
|
|
|
324
324
|
Wraps discriminated union form fields for type-safe narrowing.
|
|
325
325
|
|
|
326
326
|
**Parameters:**
|
|
327
327
|
|
|
328
|
-
- `key` - The discriminator key (must exist as a field in all variants)
|
|
329
328
|
- `fields` - Form fields from a discriminated union schema
|
|
329
|
+
- `key` - The discriminator key (must exist as a field in all variants)
|
|
330
330
|
|
|
331
331
|
**Returns:** A proxy object with:
|
|
332
332
|
|
|
333
|
-
-
|
|
334
|
-
-
|
|
333
|
+
- `type` - The current discriminator value (for narrowing)
|
|
334
|
+
- `fields` - All form fields with proper variant typing
|
|
335
335
|
- `set(data)` - Type-safe setter that infers variant from discriminator
|
|
336
336
|
- `allIssues()` - All validation issues for the discriminated fields
|
|
337
337
|
|
|
@@ -340,7 +340,7 @@ Wraps discriminated union form fields for type-safe narrowing.
|
|
|
340
340
|
Type helper that extracts the underlying data type from wrapped fields:
|
|
341
341
|
|
|
342
342
|
```typescript
|
|
343
|
-
const payment = discriminated("type"
|
|
343
|
+
const payment = discriminated(form.fields, "type");
|
|
344
344
|
type Payment = DiscriminatedData<typeof payment>;
|
|
345
345
|
// { type: 'card'; cardNumber: string; cvv: string } | { type: 'bank'; ... }
|
|
346
346
|
```
|
|
@@ -350,22 +350,10 @@ type Payment = DiscriminatedData<typeof payment>;
|
|
|
350
350
|
Type for the argument passed to variant snippets:
|
|
351
351
|
|
|
352
352
|
```typescript
|
|
353
|
-
//
|
|
353
|
+
// variant can be spread onto elements and has a .fields property
|
|
354
354
|
type VariantSnippetArg<T> = VariantProps & { readonly fields: T };
|
|
355
355
|
```
|
|
356
356
|
|
|
357
|
-
## Migration from v0.1
|
|
358
|
-
|
|
359
|
-
If upgrading from v0.1:
|
|
360
|
-
|
|
361
|
-
1. Rename `UnionVariants` to `FieldVariants`
|
|
362
|
-
2. Rename `discriminatedFields()` to `discriminated()`
|
|
363
|
-
3. Remove the `selector` prop (no longer needed - `form:has()` handles all layouts)
|
|
364
|
-
4. Update snippet signatures:
|
|
365
|
-
- Old: `{#snippet circle(fields)} <input {...fields.radius.as('number')} />`
|
|
366
|
-
- New: `{#snippet circle(v)} <div {...v}><input {...v.fields.radius.as('number')} /></div>`
|
|
367
|
-
5. Add `props` parameter to fallback and spread it: `{#snippet fallback(props)} <p {...props}>...</p>`
|
|
368
|
-
|
|
369
357
|
## License
|
|
370
358
|
|
|
371
359
|
MIT
|
package/dist/discriminated.d.ts
CHANGED
|
@@ -16,19 +16,22 @@ type VariantDiscriminatorField<V extends string, AllV extends string> = Omit<Rem
|
|
|
16
16
|
as(type: Exclude<RemoteFormFieldType<V>, 'radio'>, ...args: unknown[]): ReturnType<RemoteFormField<V>['as']>;
|
|
17
17
|
};
|
|
18
18
|
type NestedField<V> = [V] extends [object] ? RemoteFormFields<V> : RemoteFormField<V & RemoteFormFieldValue>;
|
|
19
|
-
type
|
|
20
|
-
readonly [P in
|
|
21
|
-
} & {
|
|
22
|
-
readonly [P in K]: VariantDiscriminatorField<V & string, AllV>;
|
|
19
|
+
type FieldsObject<K extends string, D, V extends string, AllV extends string> = {
|
|
20
|
+
readonly [P in K]: VariantDiscriminatorField<V, AllV>;
|
|
23
21
|
} & {
|
|
24
22
|
readonly [P in Exclude<keyof D, K>]: NestedField<D[P & keyof D]>;
|
|
23
|
+
};
|
|
24
|
+
type VariantFields<K extends string, D, AllV extends string> = D extends Record<K, infer V> ? {
|
|
25
|
+
readonly type: V;
|
|
26
|
+
readonly fields: FieldsObject<K, D, V & string, AllV>;
|
|
25
27
|
} : never;
|
|
26
28
|
type UndefinedVariant<K extends string, D, AllV extends string> = {
|
|
27
|
-
readonly
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
} & {
|
|
31
|
-
|
|
29
|
+
readonly type: undefined;
|
|
30
|
+
readonly fields: {
|
|
31
|
+
readonly [P in K]: VariantDiscriminatorField<AllV, AllV>;
|
|
32
|
+
} & {
|
|
33
|
+
readonly [P in Exclude<keyof D, K>]: NestedField<D[P & keyof D]>;
|
|
34
|
+
};
|
|
32
35
|
};
|
|
33
36
|
type SetMethod<K extends string, D> = {
|
|
34
37
|
set: <V extends DiscriminatorValues<K, D>>(data: Extract<D, Record<K, V>>) => void;
|
|
@@ -40,35 +43,32 @@ type CommonMethods = RemoteFormFields<unknown> extends {
|
|
|
40
43
|
} : never;
|
|
41
44
|
type DiscriminatedFields<K extends string, D, AllV extends string = DiscriminatorValues<K, D> & string> = (VariantFields<K, D, AllV> | UndefinedVariant<K, D, AllV>) & SetMethod<K, D> & CommonMethods;
|
|
42
45
|
/**
|
|
43
|
-
*
|
|
44
|
-
* - All original fields pass through unchanged (type, issues, allIssues, etc.)
|
|
45
|
-
* - `set` is overridden with type-safe version
|
|
46
|
-
* - `${key}Value` is added for discriminator value (e.g., `reward.typeValue`)
|
|
47
|
-
* - Discriminator field `.as("radio", value)` is type-safe (only valid values allowed)
|
|
48
|
-
* - Discriminator field `.as("option", value?)` is type-safe for select options
|
|
46
|
+
* Wraps discriminated union form fields for type-safe narrowing.
|
|
49
47
|
*
|
|
50
48
|
* @example
|
|
51
49
|
* ```svelte
|
|
52
50
|
* <script>
|
|
53
|
-
* const priority = $derived(discriminated("level"
|
|
51
|
+
* const priority = $derived(discriminated(priorityForm.fields, "level"));
|
|
54
52
|
* </script>
|
|
55
53
|
*
|
|
56
|
-
*
|
|
54
|
+
* {#if priority.type === "high"}
|
|
55
|
+
* <input {...priority.fields.urgency.as("number")} />
|
|
56
|
+
* {/if}
|
|
57
57
|
*
|
|
58
|
-
* <select {...priority.level.as("select")}>
|
|
59
|
-
* <option {...priority.level.as("option")}>Select...</option>
|
|
60
|
-
* <option {...priority.level.as("option", "high")}>High</option>
|
|
58
|
+
* <select {...priority.fields.level.as("select")}>
|
|
59
|
+
* <option {...priority.fields.level.as("option")}>Select...</option>
|
|
60
|
+
* <option {...priority.fields.level.as("option", "high")}>High</option>
|
|
61
61
|
* </select>
|
|
62
62
|
* ```
|
|
63
63
|
*
|
|
64
|
-
* @param key - Discriminator key (e.g. 'type')
|
|
65
64
|
* @param fields - Form fields from a discriminated union schema
|
|
66
|
-
* @
|
|
65
|
+
* @param key - Discriminator key (e.g. 'type', 'kind')
|
|
66
|
+
* @returns Object with `.type` (discriminator value), `.fields` (all form fields), `.set()`, `.allIssues()`
|
|
67
67
|
*/
|
|
68
68
|
export declare function discriminated<K extends string, T extends {
|
|
69
69
|
set: (v: never) => unknown;
|
|
70
70
|
} & Record<K, {
|
|
71
71
|
value(): unknown;
|
|
72
72
|
as(type: 'radio', value: string): object;
|
|
73
|
-
}>>(
|
|
73
|
+
}>>(fields: T, key: K): DiscriminatedFields<K, DiscriminatedData<T>>;
|
|
74
74
|
export {};
|
package/dist/discriminated.js
CHANGED
|
@@ -2,32 +2,29 @@
|
|
|
2
2
|
// Main function
|
|
3
3
|
// =============================================================================
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
6
|
-
* - All original fields pass through unchanged (type, issues, allIssues, etc.)
|
|
7
|
-
* - `set` is overridden with type-safe version
|
|
8
|
-
* - `${key}Value` is added for discriminator value (e.g., `reward.typeValue`)
|
|
9
|
-
* - Discriminator field `.as("radio", value)` is type-safe (only valid values allowed)
|
|
10
|
-
* - Discriminator field `.as("option", value?)` is type-safe for select options
|
|
5
|
+
* Wraps discriminated union form fields for type-safe narrowing.
|
|
11
6
|
*
|
|
12
7
|
* @example
|
|
13
8
|
* ```svelte
|
|
14
9
|
* <script>
|
|
15
|
-
* const priority = $derived(discriminated("level"
|
|
10
|
+
* const priority = $derived(discriminated(priorityForm.fields, "level"));
|
|
16
11
|
* </script>
|
|
17
12
|
*
|
|
18
|
-
*
|
|
13
|
+
* {#if priority.type === "high"}
|
|
14
|
+
* <input {...priority.fields.urgency.as("number")} />
|
|
15
|
+
* {/if}
|
|
19
16
|
*
|
|
20
|
-
* <select {...priority.level.as("select")}>
|
|
21
|
-
* <option {...priority.level.as("option")}>Select...</option>
|
|
22
|
-
* <option {...priority.level.as("option", "high")}>High</option>
|
|
17
|
+
* <select {...priority.fields.level.as("select")}>
|
|
18
|
+
* <option {...priority.fields.level.as("option")}>Select...</option>
|
|
19
|
+
* <option {...priority.fields.level.as("option", "high")}>High</option>
|
|
23
20
|
* </select>
|
|
24
21
|
* ```
|
|
25
22
|
*
|
|
26
|
-
* @param key - Discriminator key (e.g. 'type')
|
|
27
23
|
* @param fields - Form fields from a discriminated union schema
|
|
28
|
-
* @
|
|
24
|
+
* @param key - Discriminator key (e.g. 'type', 'kind')
|
|
25
|
+
* @returns Object with `.type` (discriminator value), `.fields` (all form fields), `.set()`, `.allIssues()`
|
|
29
26
|
*/
|
|
30
|
-
export function discriminated(
|
|
27
|
+
export function discriminated(fields, key) {
|
|
31
28
|
// Wrap the discriminator field to intercept as("option", value?) calls
|
|
32
29
|
const wrapDiscriminatorField = (field) => {
|
|
33
30
|
return new Proxy(field, {
|
|
@@ -44,18 +41,29 @@ export function discriminated(key, fields) {
|
|
|
44
41
|
}
|
|
45
42
|
});
|
|
46
43
|
};
|
|
44
|
+
// Create the fields proxy that wraps the discriminator field
|
|
45
|
+
const fieldsProxy = new Proxy(fields, {
|
|
46
|
+
get(target, prop) {
|
|
47
|
+
if (prop === key)
|
|
48
|
+
return wrapDiscriminatorField(target[key]);
|
|
49
|
+
return Reflect.get(target, prop);
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
// Create the main proxy with .type, .fields, .set, .allIssues
|
|
47
53
|
const proxy = new Proxy(fields, {
|
|
48
54
|
get(target, prop) {
|
|
49
|
-
if (prop ===
|
|
55
|
+
if (prop === 'type')
|
|
50
56
|
return target[key].value();
|
|
57
|
+
if (prop === 'fields')
|
|
58
|
+
return fieldsProxy;
|
|
51
59
|
if (prop === 'set')
|
|
52
60
|
return (data) => target.set(data);
|
|
53
|
-
if (prop ===
|
|
54
|
-
return
|
|
55
|
-
return
|
|
61
|
+
if (prop === 'allIssues')
|
|
62
|
+
return () => target.allIssues?.();
|
|
63
|
+
return undefined;
|
|
56
64
|
},
|
|
57
|
-
has(
|
|
58
|
-
return prop ===
|
|
65
|
+
has(_target, prop) {
|
|
66
|
+
return prop === 'type' || prop === 'fields' || prop === 'set' || prop === 'allIssues';
|
|
59
67
|
}
|
|
60
68
|
});
|
|
61
69
|
return proxy;
|