effect-atom-svelte 0.2.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.
Files changed (56) hide show
  1. package/README.md +5 -2
  2. package/dist/Form.d.ts +605 -0
  3. package/dist/Form.js +67 -0
  4. package/dist/Hooks.svelte.d.ts +70 -10
  5. package/dist/Hooks.svelte.js +489 -163
  6. package/dist/HydrationBoundary.svelte +134 -17
  7. package/dist/HydrationBoundary.svelte.d.ts +13 -4
  8. package/dist/Inspector.d.ts +7 -3
  9. package/dist/Inspector.js +113 -29
  10. package/dist/RegistryContext.d.ts +2 -0
  11. package/dist/RegistryContext.js +13 -5
  12. package/dist/ScopedAtom.d.ts +5 -4
  13. package/dist/ScopedAtom.js +20 -11
  14. package/dist/SvelteKit.d.ts +71 -12
  15. package/dist/SvelteKit.js +133 -54
  16. package/dist/index.d.ts +10 -1
  17. package/dist/index.js +10 -1
  18. package/dist/internal/awaitWrite.d.ts +11 -0
  19. package/dist/internal/awaitWrite.js +27 -0
  20. package/dist/internal/boundaries.d.ts +9 -0
  21. package/dist/internal/boundaries.js +10 -0
  22. package/dist/internal/disposal.d.ts +13 -0
  23. package/dist/internal/disposal.js +43 -0
  24. package/dist/internal/form/array.d.ts +21 -0
  25. package/dist/internal/form/array.js +57 -0
  26. package/dist/internal/form/constraints.d.ts +13 -0
  27. package/dist/internal/form/constraints.js +166 -0
  28. package/dist/internal/form/engine.d.ts +56 -0
  29. package/dist/internal/form/engine.js +842 -0
  30. package/dist/internal/form/engineOf.d.ts +26 -0
  31. package/dist/internal/form/engineOf.js +43 -0
  32. package/dist/internal/form/field.d.ts +30 -0
  33. package/dist/internal/form/field.js +68 -0
  34. package/dist/internal/form/group.d.ts +13 -0
  35. package/dist/internal/form/group.js +20 -0
  36. package/dist/internal/form/instance.d.ts +41 -0
  37. package/dist/internal/form/instance.js +135 -0
  38. package/dist/internal/form/issues.d.ts +23 -0
  39. package/dist/internal/form/issues.js +144 -0
  40. package/dist/internal/form/paths.d.ts +47 -0
  41. package/dist/internal/form/paths.js +175 -0
  42. package/dist/internal/form/types.d.ts +80 -0
  43. package/dist/internal/form/types.js +1 -0
  44. package/dist/internal/form/validationError.d.ts +13 -0
  45. package/dist/internal/form/validationError.js +6 -0
  46. package/dist/internal/hydration.d.ts +11 -2
  47. package/dist/internal/hydration.js +26 -4
  48. package/dist/internal/nodeInternals.d.ts +6 -0
  49. package/dist/internal/nodeInternals.js +64 -7
  50. package/dist/internal/renderEnd.d.ts +13 -0
  51. package/dist/internal/renderEnd.js +34 -0
  52. package/dist/internal/teardown.svelte.d.ts +17 -0
  53. package/dist/internal/teardown.svelte.js +44 -0
  54. package/dist/internal/troubleshooting.d.ts +2 -0
  55. package/dist/internal/troubleshooting.js +3 -0
  56. package/package.json +12 -10
package/README.md CHANGED
@@ -10,7 +10,7 @@ This is a community project by Jarred Norris. It is not part of Effect, and the
10
10
 
11
11
  - `effect` 4.0.x. The peer range is `~4.0.0`, not `^4.0.0`: the bindings use parts of the atom registry that aren't public API, which a minor release of `effect` can change.
12
12
  - Svelte 5.57.2 or later. `experimental.async` is needed for `useAtomSuspense`, `useAtomResult` and server rendering.
13
- - SvelteKit is optional. The docs site and its tests run on SvelteKit 3. On SvelteKit 2, the error hooks in `effect-atom-svelte/sveltekit` work with less detail.
13
+ - SvelteKit is optional. The docs site and its tests run on SvelteKit 3. On SvelteKit 2, the error hooks in `effect-atom-svelte/sveltekit` work with less detail, and reach `<svelte:boundary>` only with `kit.experimental.handleRenderingErrors` turned on.
14
14
 
15
15
  ## Installation
16
16
 
@@ -64,7 +64,9 @@ On the server, the provider creates a registry for each request, so visitors nev
64
64
  const greeting = useAtomSuspense(greetingAtom);
65
65
  </script>
66
66
 
67
- <button onclick={() => (count.current += 1)}>{count.current} × 2 = {doubled.current}</button>
67
+ <button onclick={() => (count.current += 1)}
68
+ >{count.current} × 2 = {doubled.current}</button
69
+ >
68
70
 
69
71
  <svelte:boundary>
70
72
  <p>{await greeting.current}</p>
@@ -80,6 +82,7 @@ Hooks return an object whose `current` you read, assign or `bind:` to. Don't des
80
82
  - [Your first atom](https://atom.jarrednorris.dev/first-atom), [Reading and writing](https://atom.jarrednorris.dev/reading-and-writing) and [Async atoms](https://atom.jarrednorris.dev/async-atoms)
81
83
  - [Server rendering](https://atom.jarrednorris.dev/server-rendering) and [Hydration](https://atom.jarrednorris.dev/hydration)
82
84
  - [Effect RPC](https://atom.jarrednorris.dev/rpc) and [HttpApi](https://atom.jarrednorris.dev/http)
85
+ - [Forms](https://atom.jarrednorris.dev/forms), built from one Effect Schema
83
86
  - [API reference](https://atom.jarrednorris.dev/reference) and [Troubleshooting](https://atom.jarrednorris.dev/troubleshooting)
84
87
  - [Every export at a glance, and more recipes](https://github.com/jarrednorrisdev/effect-atom-svelte#api-at-a-glance), in the repository's README
85
88
 
package/dist/Form.d.ts ADDED
@@ -0,0 +1,605 @@
1
+ /**
2
+ * Forms driven by one Effect Schema. `Form.make` defines a form from a Schema whose encoded side is
3
+ * what inputs hold and whose decoded side is what a submit sends; `useForm` creates an instance in
4
+ * a component, and `Form.instance` creates one without Svelte. The module is headless: it gives
5
+ * values, errors and element attributes, and no components.
6
+ *
7
+ * @since 0.4.0
8
+ */
9
+ import { Duration } from "effect";
10
+ import type { Exit, Option, Schema, SchemaIssue } from "effect";
11
+ import { AtomRegistry } from "effect/reactivity";
12
+ import type { AsyncResult, Atom } from "effect/reactivity";
13
+ import type { AtomInput } from "./Hooks.svelte.ts";
14
+ import type { Constraints as InternalConstraints } from "./internal/form/constraints.ts";
15
+ import type { Definition } from "./internal/form/types.ts";
16
+ import { FormValidationError } from "./internal/form/validationError.ts";
17
+ /**
18
+ * Marks a value as a form definition: every definition has this key, set to this same string.
19
+ *
20
+ * @stability unstable
21
+ * @since 0.4.0
22
+ * @category type ids
23
+ */
24
+ export declare const TypeId: "~effect-atom-svelte/Form";
25
+ /**
26
+ * The type of `TypeId`.
27
+ *
28
+ * @stability unstable
29
+ * @since 0.4.0
30
+ * @category type ids
31
+ */
32
+ export type TypeId = typeof TypeId;
33
+ /**
34
+ * When a field's error shows: as the user types (`"input"`), when they leave it (`"blur"`), or only
35
+ * when the form is submitted (`"submit"`). The Schema checks every change whatever the timing, so
36
+ * `valid` and `field.issue` are always current; the timing only decides when `field.error` shows.
37
+ *
38
+ * @stability unstable
39
+ * @since 0.4.0
40
+ * @category models
41
+ */
42
+ export type Timing = "submit" | "blur" | "input";
43
+ /**
44
+ * A form definition: a Schema and the settings every instance shares. It holds no state, so it can
45
+ * live in a module beside the atoms.
46
+ *
47
+ * @stability unstable
48
+ * @since 0.4.0
49
+ * @category models
50
+ */
51
+ export interface Form<S extends Schema.Top> {
52
+ readonly [TypeId]: TypeId;
53
+ readonly schema: S;
54
+ /** @internal */
55
+ readonly definition: Definition;
56
+ }
57
+ /**
58
+ * The values a form's inputs hold: its Schema's encoded side.
59
+ *
60
+ * @stability unstable
61
+ * @since 0.4.0
62
+ * @category models
63
+ */
64
+ export type Encoded<F extends Form<Schema.Top>> = F["schema"]["Encoded"];
65
+ /**
66
+ * What a form's values decode to, and a submit sends: its Schema's decoded side.
67
+ *
68
+ * @stability unstable
69
+ * @since 0.4.0
70
+ * @category models
71
+ */
72
+ export type Decoded<F extends Form<Schema.Top>> = F["schema"]["Type"];
73
+ /**
74
+ * Options for `Form.make`: the settings every instance of the form shares.
75
+ *
76
+ * @stability unstable
77
+ * @since 0.4.0
78
+ * @category models
79
+ */
80
+ export interface MakeOptions {
81
+ /**
82
+ * A name for the form, used in its atoms' labels, such as `SignUp.values`, which the devtools
83
+ * show. The devtools' Vite plugin fills it in from the variable a form is declared as.
84
+ */
85
+ readonly name?: string | undefined;
86
+ /** When a field's error first shows. Defaults to `"blur"`. */
87
+ readonly validateOn?: Timing | undefined;
88
+ /**
89
+ * When it updates after the field has shown an error, or once the form has been submitted.
90
+ * Defaults to `"input"`.
91
+ */
92
+ readonly revalidateOn?: Timing | undefined;
93
+ /** How long to wait after a change before validating. Defaults to none. */
94
+ readonly debounce?: Duration.Input | undefined;
95
+ /** A message for an issue that has no `message` annotation, or `undefined` for Effect's own. */
96
+ readonly leafHook?: ((issue: SchemaIssue.Leaf) => string | undefined) | undefined;
97
+ /** A message for a failed check that has no `message` annotation, or `undefined` for Effect's. */
98
+ readonly checkHook?: ((issue: SchemaIssue.Filter) => string | undefined) | undefined;
99
+ }
100
+ /**
101
+ * Options for `Form.make` with a runtime, for a Schema whose checks need services.
102
+ *
103
+ * @stability unstable
104
+ * @since 0.4.0
105
+ * @category models
106
+ */
107
+ export interface MakeOptionsWithRuntime<R, ER> extends MakeOptions {
108
+ /** The runtime validation runs on, built with `Atom.runtime(layer)`. */
109
+ readonly runtime: Atom.AtomRuntime<R, ER>;
110
+ }
111
+ /**
112
+ * Defines a form from a Schema. Decoding always runs as an Effect, with every issue reported, so
113
+ * checks that call services work when a runtime provides them. A message comes from the Schema's
114
+ * `message` annotation first, then from `leafHook` or `checkHook`, then from Effect's default.
115
+ *
116
+ * **Example** (Defining a login form)
117
+ *
118
+ * ```ts
119
+ * import { Schema } from "effect";
120
+ * import { Form } from "effect-atom-svelte";
121
+ *
122
+ * export const Login = Form.make(
123
+ * Schema.Struct({
124
+ * email: Schema.String.check(
125
+ * Schema.isPattern(/^\S+@\S+\.\S+$/u, {
126
+ * message: "Enter an email address",
127
+ * })
128
+ * ),
129
+ * password: Schema.String.check(
130
+ * Schema.isMinLength(8, {
131
+ * message: "At least 8 characters",
132
+ * })
133
+ * ),
134
+ * })
135
+ * );
136
+ * ```
137
+ *
138
+ * @see {@link https://atom.jarrednorris.dev/forms | Forms}
139
+ * @stability unstable
140
+ * @since 0.4.0
141
+ * @category constructors
142
+ */
143
+ export declare function make<S extends Schema.Top & {
144
+ readonly DecodingServices: never;
145
+ }>(schema: S, options?: MakeOptions): Form<S>;
146
+ export declare function make<S extends Schema.Top, R, ER>(schema: S & ([S["DecodingServices"]] extends [R] ? unknown : never), options: MakeOptionsWithRuntime<R, ER>): Form<S>;
147
+ /**
148
+ * Whether a value is a form definition from `Form.make`.
149
+ *
150
+ * @stability unstable
151
+ * @since 0.4.0
152
+ * @category guards
153
+ */
154
+ export declare const isForm: (u: unknown) => u is Form<Schema.Top>;
155
+ type Depth = [never, 0, 1, 2, 3, 4, 5, 6, 7, 8];
156
+ type Scalar = string | number | boolean | bigint | symbol | null | undefined | Date | ((...args: readonly never[]) => unknown);
157
+ /**
158
+ * Every path into `T`, as a tuple of object keys and array indexes:
159
+ * `["email"]`, `["items", 0, "street"]`. For a union, the paths of every member.
160
+ *
161
+ * @stability unstable
162
+ * @since 0.4.0
163
+ * @category models
164
+ */
165
+ export type Path<T, D extends number = 8> = [D] extends [never] ? never : T extends Scalar ? never : T extends readonly (infer Item)[] ? readonly [number] | readonly [number, ...Path<NonNullable<Item>, Depth[D]>] : T extends object ? {
166
+ [K in keyof T & string]-?: readonly [K] | readonly [K, ...Path<NonNullable<T[K]>, Depth[D]>];
167
+ }[keyof T & string] : never;
168
+ /**
169
+ * The value at path `P` in `T`. For a union, the value in each member that has the path. Below an
170
+ * optional parent it may also be `undefined`, as it is while the parent is.
171
+ *
172
+ * @stability unstable
173
+ * @since 0.4.0
174
+ * @category models
175
+ */
176
+ export type At<T, P> = P extends readonly [] ? T : P extends readonly [infer Head, ...infer Rest] ? T extends unknown ? T extends readonly (infer Item)[] ? Head extends number ? At<Rest extends readonly [] ? Item : NonNullable<Item>, Rest> : never : Head extends keyof T ? Rest extends readonly [] ? T[Head] : At<NonNullable<T[Head]>, Rest> | (undefined extends T[Head] ? undefined : never) : never : never : never;
177
+ /**
178
+ * The top-level keys of `T`, of every member for a union: the short form of a path.
179
+ *
180
+ * @stability unstable
181
+ * @since 0.4.0
182
+ * @category models
183
+ */
184
+ export type Key<T> = T extends unknown ? T extends readonly unknown[] | Scalar ? never : T extends object ? keyof T & string : never : never;
185
+ type ItemOf<V> = NonNullable<V> extends readonly (infer Item)[] ? Item : never;
186
+ /**
187
+ * A path as the form reports it, such as in `errors`: object keys and array indexes.
188
+ *
189
+ * @stability unstable
190
+ * @since 0.4.0
191
+ * @category models
192
+ */
193
+ export type AnyPath = readonly (string | number)[];
194
+ /**
195
+ * HTML input constraints read from a field's Schema checks: `minlength` from `isMinLength` and
196
+ * `isNonEmpty`, `maxlength` from `isMaxLength`, both from `isBetweenLength`, `min` and `max` from
197
+ * `isGreaterThanOrEqualTo`, `isLessThanOrEqualTo` and `isBetween`, `pattern` from an `isPattern`
198
+ * anchored with `^` and `$`, with no flags but `u` or `v` (an HTML pattern must match the whole value, a
199
+ * Schema pattern anywhere in it), and `required`
200
+ * where the Schema rejects an empty string. Other checks set nothing.
201
+ *
202
+ * @stability unstable
203
+ * @since 0.4.0
204
+ * @category models
205
+ */
206
+ export type Constraints = InternalConstraints;
207
+ /**
208
+ * The attributes `field.attrs` spreads on a native input.
209
+ *
210
+ * @stability unstable
211
+ * @since 0.4.0
212
+ * @category models
213
+ */
214
+ export interface FieldAttrs {
215
+ readonly name: string;
216
+ readonly id: string;
217
+ readonly "aria-invalid": true | undefined;
218
+ readonly "aria-describedby": string | undefined;
219
+ readonly onblur: () => void;
220
+ }
221
+ /**
222
+ * One field of a form instance. Every property is reactive in a component. `value` can be
223
+ * assigned, so `bind:value={field.value}` works; an assignment counts as input for validation
224
+ * timing, and a change clears a server or manual error on the field and the form-level error.
225
+ *
226
+ * @stability unstable
227
+ * @since 0.4.0
228
+ * @category models
229
+ */
230
+ export interface Field<V> {
231
+ value: V;
232
+ /** The error to show: a schema error once the field's timing reveals it, or a server one. */
233
+ readonly error: string | undefined;
234
+ /**
235
+ * The latest check's message for the field, shown or not: the Schema checks every change, and
236
+ * `error` waits for the field's timing. For a live checklist, or to see what the timing hides.
237
+ */
238
+ readonly issue: string | undefined;
239
+ /** Whether the user has left the field. `useForm`'s `attrs.onblur` sets it. */
240
+ touched: boolean;
241
+ /** Whether the value differs from the initial value. */
242
+ readonly dirty: boolean;
243
+ /**
244
+ * Whether the form's validation is running or waiting for its debounce: the whole form's flag,
245
+ * the same on every field, as one run checks every field.
246
+ */
247
+ readonly validating: boolean;
248
+ /** The field's element id, from the instance's `id`. */
249
+ readonly id: string;
250
+ /** The id for the element that shows `error`, which `attrs` points `aria-describedby` at. */
251
+ readonly errorId: string;
252
+ /** The `name` attribute: the path joined with dots, such as `items.0.qty`. */
253
+ readonly name: string;
254
+ /** Where the field is now. An array item's field follows the item when it moves. */
255
+ readonly path: AnyPath;
256
+ /**
257
+ * Attributes to spread on a native input: `name`, `id`, `aria-invalid` while `error` is set,
258
+ * `aria-describedby` pointing at `errorId` while it is, and an `onblur` that marks the field
259
+ * touched.
260
+ */
261
+ readonly attrs: FieldAttrs;
262
+ readonly constraints: Constraints;
263
+ /** What leaving the field does, for a custom control that has no blur event. */
264
+ readonly blur: () => void;
265
+ }
266
+ /**
267
+ * One item of an array field, with a key that stays with it when items move.
268
+ *
269
+ * @stability unstable
270
+ * @since 0.4.0
271
+ * @category models
272
+ */
273
+ export interface ArrayItem<I> {
274
+ /** Stays with the item: use it to key an `{#each}` block. */
275
+ readonly key: number;
276
+ readonly index: number;
277
+ /** The item's own fields, for an item that is an object. */
278
+ readonly fields: Group<I>;
279
+ /** The item itself as a field, for an item that is a single value. */
280
+ readonly field: Field<I>;
281
+ }
282
+ /**
283
+ * An array in a form. Its operations move each item's errors and touched state with the item.
284
+ *
285
+ * @stability unstable
286
+ * @since 0.4.0
287
+ * @category models
288
+ */
289
+ export interface ArrayField<I> {
290
+ readonly items: readonly ArrayItem<I>[];
291
+ /** An error on the array itself, such as from a check on its length. */
292
+ readonly error: string | undefined;
293
+ readonly path: AnyPath;
294
+ /** Adds an item at the end, with a new key and no errors or touched state. */
295
+ readonly append: (item: I) => void;
296
+ /** Removes the item at `index`, and its errors and touched state. Out of range does nothing. */
297
+ readonly remove: (index: number) => void;
298
+ /**
299
+ * Moves the item at index `from` to index `to`, shifting the items between; its errors and
300
+ * touched state go with it. Either index out of range does nothing.
301
+ */
302
+ readonly move: (from: number, to: number) => void;
303
+ /** Puts a new item at `index`: the old item's errors and touched state go with it. */
304
+ readonly replace: (index: number, item: I) => void;
305
+ }
306
+ /**
307
+ * Fields under one path. A component that edits a part of a form takes a `Group` of that part's
308
+ * shape, and any form with that shape somewhere passes `form.at(path)`.
309
+ *
310
+ * @stability unstable
311
+ * @since 0.4.0
312
+ * @category models
313
+ */
314
+ export interface Group<T> {
315
+ readonly path: AnyPath;
316
+ /** A field by top-level key, or by path: `field("email")`, `field(["items", 0, "qty"])`. */
317
+ readonly field: {
318
+ <K extends Key<T>>(key: K): Field<At<T, readonly [K]>>;
319
+ <const P extends Path<T>>(path: P): Field<At<T, P>>;
320
+ };
321
+ /** An array field, by top-level key or by path. */
322
+ readonly array: {
323
+ <K extends Key<T>>(key: K): ArrayField<ItemOf<At<T, readonly [K]>>>;
324
+ <const P extends Path<T>>(path: P): ArrayField<ItemOf<At<T, P>>>;
325
+ };
326
+ /** The fields under a key or path, as a group to hand to a component. */
327
+ readonly at: {
328
+ <K extends Key<T>>(key: K): Group<NonNullable<At<T, readonly [K]>>>;
329
+ <const P extends Path<T>>(path: P): Group<NonNullable<At<T, P>>>;
330
+ };
331
+ }
332
+ /**
333
+ * Errors to put on a form, from `mapError` or `setErrors`: by top-level field, by path, and
334
+ * form-level.
335
+ *
336
+ * @stability unstable
337
+ * @since 0.4.0
338
+ * @category models
339
+ */
340
+ export interface Errors<T> {
341
+ readonly fields?: Readonly<Partial<Record<Key<T>, string | undefined>>> | undefined;
342
+ readonly paths?: readonly (readonly [Path<T>, string])[] | undefined;
343
+ readonly form?: string | undefined;
344
+ }
345
+ /**
346
+ * One error as `form.errors` lists it.
347
+ *
348
+ * @stability unstable
349
+ * @since 0.4.0
350
+ * @category models
351
+ */
352
+ export interface ErrorEntry {
353
+ /** Empty for a form-level error. */
354
+ readonly path: AnyPath;
355
+ readonly message: string;
356
+ }
357
+ /**
358
+ * Why `submit` didn't call the submit atom: the values didn't decode. `errors` lists what showed.
359
+ * Its `_tag` is `"FormValidationError"`, for `Effect.catchTag`.
360
+ *
361
+ * @stability unstable
362
+ * @since 0.4.0
363
+ * @category errors
364
+ */
365
+ export declare const ValidationError: typeof FormValidationError;
366
+ /**
367
+ * The type of `ValidationError`.
368
+ *
369
+ * @stability unstable
370
+ * @since 0.4.0
371
+ * @category errors
372
+ */
373
+ export type ValidationError = FormValidationError;
374
+ /**
375
+ * The attributes `form.attrs` spreads on a `<form>`: `novalidate`, so the browser's own bubbles
376
+ * never compete with the form's errors, and an `onsubmit` that prevents the browser's submit,
377
+ * submits the form, and moves focus to the first field with an error when validation fails.
378
+ *
379
+ * @stability unstable
380
+ * @since 0.4.0
381
+ * @category models
382
+ */
383
+ export interface FormAttrs {
384
+ readonly novalidate: true;
385
+ readonly onsubmit: (event: Event) => void;
386
+ }
387
+ /**
388
+ * A form instance: its values, state and actions. Every property is reactive in a component.
389
+ * `submitting` and `submitResult` belong to this instance, not to the submit atom, which other
390
+ * instances may share.
391
+ *
392
+ * @stability unstable
393
+ * @since 0.4.0
394
+ * @category models
395
+ */
396
+ export interface Instance<T, D, A, E, Args> extends Group<T> {
397
+ /** The current values, encoded, as the inputs hold them. */
398
+ readonly values: T;
399
+ /**
400
+ * What `dirty` compares the values with: `initialValues`, or the values of the last successful
401
+ * submit, which become the initial values.
402
+ */
403
+ readonly initialValues: T;
404
+ /** Whether the values differ from `initialValues`. */
405
+ readonly dirty: boolean;
406
+ /**
407
+ * Whether the form can be submitted as it stands: the latest values have been checked and
408
+ * decode, and no external error (from `mapError` or `setErrors`) is set. It is false while the
409
+ * Schema fails, while a check is running or waiting for its debounce, and while any external
410
+ * error is set, whether or not an error shows yet.
411
+ */
412
+ readonly valid: boolean;
413
+ /** Whether validation is running or waiting for its debounce. */
414
+ readonly validating: boolean;
415
+ /** Whether a submit is waiting for the submit atom's result. */
416
+ readonly submitting: boolean;
417
+ /**
418
+ * The last submit's result, holding only failures `mapError` didn't claim. A validation that
419
+ * couldn't run at all, such as when the form's runtime failed to build, ends here as a defect.
420
+ */
421
+ readonly submitResult: AsyncResult.AsyncResult<A, E>;
422
+ /**
423
+ * A form-level error, on no field: `mapError`'s or `setErrors`' `form`, which shows at once and
424
+ * clears on any change to the values, at the next submit or on reset; or an error from a check on the whole Struct
425
+ * that gives no `path`, which shows only after `submit` or a `validate()` of the whole form.
426
+ */
427
+ readonly rootError: string | undefined;
428
+ /**
429
+ * Every error on show, for an error summary: the form-level one first, then the fields' in the
430
+ * Schema's order, which may not be the page's.
431
+ */
432
+ readonly errors: readonly ErrorEntry[];
433
+ /** How many times `submit` has been called since the last reset, whatever the outcome. */
434
+ readonly submitCount: number;
435
+ /** How many times `validate` has been called since the last reset. */
436
+ readonly validationCount: number;
437
+ /** The values of the last successful submit, encoded and decoded. */
438
+ readonly lastSubmitted: Option.Option<{
439
+ readonly encoded: T;
440
+ readonly decoded: D;
441
+ }>;
442
+ /** Whether the values differ from the last successful submit's. */
443
+ readonly dirtySinceSubmit: boolean;
444
+ /** Attributes to spread on the `<form>` element: see `FormAttrs`. */
445
+ readonly attrs: FormAttrs;
446
+ /**
447
+ * Decodes the values and, when they decode, writes the submit atom and waits for its result. A
448
+ * call while a submit runs gets that submit's result. A submit that settles after a reset doesn't
449
+ * touch the fresh form. Instances that submit to the same `Atom.fn` share its one result: if
450
+ * another instance writes it while this submit runs, this submit resolves with the other's
451
+ * result, so give each instance its own atom.
452
+ */
453
+ readonly submit: (...args: [Args] extends [undefined] ? [] : [args: Args]) => Promise<Exit.Exit<A, E | ValidationError>>;
454
+ /**
455
+ * Shows the Schema's errors at and under these paths (every path by default) and resolves
456
+ * whether they passed: no Schema error and no external error (from `mapError` or `setErrors`) at
457
+ * or under any of them. A whole-form `validate()` also counts the form-level error; a call with
458
+ * paths doesn't.
459
+ */
460
+ readonly validate: (paths?: readonly (Key<T> | Path<T>)[]) => Promise<boolean>;
461
+ /**
462
+ * Sets several values at once. Not counted as input on any field, so an untouched field's error
463
+ * waits for its own timing; a touched field whose check it breaks shows its error at once, as
464
+ * after another field's change. Like any change, it clears external errors where values changed.
465
+ */
466
+ readonly setValues: (values: T | ((previous: T) => T)) => void;
467
+ /**
468
+ * Back to `initialValues`, or to `values`, which become the initial values. Clears
469
+ * `lastSubmitted`, `submitResult`, touched state, every error and both counts. After a successful
470
+ * submit, `initialValues` are the submitted values, unless `resetOnSuccess` is set.
471
+ */
472
+ readonly reset: (values?: T) => void;
473
+ /**
474
+ * Puts the values back to the last successful submit's, or to `initialValues` when there has
475
+ * been none. Touched state stays. As with `setValues`, a touched field whose check it breaks shows
476
+ * its error at once, and it clears external errors where values change.
477
+ */
478
+ readonly revertToLastSubmit: () => void;
479
+ /**
480
+ * Puts errors on fields or on the form; they show at once, and keep `valid` false while set. A
481
+ * field's error clears when the value at its path changes, including from a change to a parent or
482
+ * a child path. The form-level error (`form`) clears on any change to the values, and at the
483
+ * next submit. Both clear on reset; blur, `validate` and a write of the same values leave them.
484
+ * They don't stop a submit: it checks only the Schema, and the server checks the values again.
485
+ */
486
+ readonly setErrors: (errors: Errors<T>) => void;
487
+ /**
488
+ * Focuses the field with an error that comes first on the page, finding each by the id in
489
+ * `field.attrs`. Fields whose element isn't on the page are skipped. `errors` keeps the Schema's
490
+ * order, which may differ from the page's.
491
+ */
492
+ readonly focusFirstError: () => void;
493
+ }
494
+ /**
495
+ * Where `submit` sends the decoded values: a write atom from `runtime.fn`, an `AtomRpc` mutation
496
+ * or `Atom.fn`, or a getter that picks one. `payload` builds what to write from the decoded values
497
+ * and the arguments `submit` was called with; without it, the atom gets the decoded values.
498
+ *
499
+ * @stability unstable
500
+ * @since 0.4.0
501
+ * @category models
502
+ */
503
+ export type SubmitOptions<D, A, E, W, Args> = {
504
+ readonly atom: AtomInput<Atom.Writable<AsyncResult.AsyncResult<A, E>, W>>;
505
+ readonly payload: (decoded: D, args: Args) => W;
506
+ } | {
507
+ readonly atom: AtomInput<Atom.Writable<AsyncResult.AsyncResult<A, E>, D>>;
508
+ readonly payload?: undefined;
509
+ };
510
+ /**
511
+ * Options for an instance, shared by `useForm` and `Form.instance`.
512
+ *
513
+ * @stability unstable
514
+ * @since 0.4.0
515
+ * @category models
516
+ */
517
+ export interface BaseInstanceOptions<T> {
518
+ /**
519
+ * The base of every element id, which must match between server and browser: pass `$props.id()`.
520
+ */
521
+ readonly id: string;
522
+ /**
523
+ * The values to start from, or a getter. `useForm` follows a getter: a clean form takes new
524
+ * values, a dirty one keeps its edits.
525
+ */
526
+ readonly initialValues: T | (() => T);
527
+ /**
528
+ * Storage for the values that you own, such as an `Atom.kvs` draft that survives a reload. A
529
+ * write to it from code counts as a change made in code, not as typing. So does a field's write
530
+ * that the atom hands back changed (trimmed, say): the field loses its own timing for that change.
531
+ */
532
+ readonly values?: Atom.Writable<T> | undefined;
533
+ /**
534
+ * After a successful submit, reset to `initialValues` rather than adopting the submitted values
535
+ * as the new initial values. The reset keeps `lastSubmitted` and `submitResult`.
536
+ */
537
+ readonly resetOnSuccess?: boolean | undefined;
538
+ }
539
+ /**
540
+ * Options for an instance that submits to an atom.
541
+ *
542
+ * @stability unstable
543
+ * @since 0.4.0
544
+ * @category models
545
+ */
546
+ export interface InstanceOptions<T, D, A, E, W, Args> extends BaseInstanceOptions<T> {
547
+ readonly submit: SubmitOptions<D, A, E, W, Args>;
548
+ /**
549
+ * Claims a typed failure: return errors to put on fields or the form, or `undefined` to leave
550
+ * the failure in `submitResult`. Defects and interruptions are never passed here. The errors
551
+ * show at once and keep `valid` false while set. A field's error clears when the value at its
552
+ * path changes; the form-level one (`form`) on any change to the values. All of them clear at the
553
+ * next submit and on reset.
554
+ */
555
+ readonly mapError?: ((error: E) => Errors<T> | undefined) | undefined;
556
+ /** Submit after each change that validates. Only for a submit that takes no arguments. */
557
+ readonly autoSubmit?: ([Args] extends [undefined] ? boolean : never) | undefined;
558
+ }
559
+ /**
560
+ * Options for an instance with no submit atom: `submit` validates and resolves with the decoded
561
+ * values.
562
+ *
563
+ * @stability unstable
564
+ * @since 0.4.0
565
+ * @category models
566
+ */
567
+ export interface ValidateOnlyOptions<T> extends BaseInstanceOptions<T> {
568
+ readonly submit?: undefined;
569
+ readonly autoSubmit?: boolean | undefined;
570
+ }
571
+ /**
572
+ * Creates a form instance without Svelte, for tests and code outside components. It lives in
573
+ * `registry` (a new one by default) until `dispose` is called; a submit in flight keeps it until
574
+ * the submit settles. A getter for `initialValues` is read once.
575
+ *
576
+ * **Example** (Submitting a form in a test)
577
+ *
578
+ * ```ts
579
+ * import { Form } from "effect-atom-svelte";
580
+ * import { Login } from "./forms.ts";
581
+ *
582
+ * const form = Form.instance(Login, {
583
+ * id: "test",
584
+ * initialValues: { email: "", password: "" },
585
+ * });
586
+ * form.field("email").value = "ada@example.com";
587
+ * const exit = await form.submit();
588
+ * form.dispose();
589
+ * ```
590
+ *
591
+ * @stability unstable
592
+ * @since 0.4.0
593
+ * @category constructors
594
+ */
595
+ export declare function instance<F extends Form<Schema.Top>, A, E, W, Args = undefined>(form: F, options: InstanceOptions<Encoded<F>, Decoded<F>, A, E, W, Args> & {
596
+ readonly registry?: AtomRegistry.AtomRegistry | undefined;
597
+ }): Instance<Encoded<F>, Decoded<F>, A, E, Args> & {
598
+ readonly dispose: () => void;
599
+ };
600
+ export declare function instance<F extends Form<Schema.Top>>(form: F, options: ValidateOnlyOptions<Encoded<F>> & {
601
+ readonly registry?: AtomRegistry.AtomRegistry | undefined;
602
+ }): Instance<Encoded<F>, Decoded<F>, Decoded<F>, never, undefined> & {
603
+ readonly dispose: () => void;
604
+ };
605
+ export {};