@samline/forms 2.6.0 → 2.7.1
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 +6 -4
- package/dist/browser/global.d.ts +19 -3
- package/dist/browser/global.global.js +176 -30
- package/dist/browser/global.global.js.map +1 -1
- package/dist/browser/index.cjs +176 -30
- package/dist/browser/index.cjs.map +1 -1
- package/dist/browser/index.d.cts +1 -1
- package/dist/browser/index.d.ts +1 -1
- package/dist/browser/index.js +1 -1
- package/dist/{chunk-L4GRKSQ5.js → chunk-MMHX34WB.js} +181 -31
- package/dist/chunk-MMHX34WB.js.map +1 -0
- package/dist/index.cjs +180 -30
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/{registry-BpqJetAt.d.cts → registry-GzopYPCR.d.cts} +20 -4
- package/dist/{registry-BpqJetAt.d.ts → registry-GzopYPCR.d.ts} +20 -4
- package/docs/api/add-cleanup.md +52 -0
- package/docs/api/destroy.md +16 -1
- package/docs/api/form.md +4 -2
- package/docs/api/format.md +3 -3
- package/docs/api/get-state.md +17 -1
- package/docs/api/index.md +1 -0
- package/docs/api/on-submit.md +7 -3
- package/docs/api/reset.md +3 -2
- package/docs/api/revalidate.md +3 -1
- package/docs/api/subscribe.md +16 -0
- package/docs/api/validate-field-value.md +39 -3
- package/docs/api/validate-values.md +4 -1
- package/docs/api/validate.md +5 -3
- package/docs/browser.md +5 -5
- package/docs/css-styling.md +5 -1
- package/docs/getting-started.md +13 -8
- package/docs/options.md +69 -2
- package/docs/recipes.md +65 -11
- package/docs/typescript.md +47 -8
- package/package.json +1 -1
- package/dist/chunk-L4GRKSQ5.js.map +0 -1
package/dist/index.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { F as FormTarget, a as FormControllerOptions, b as FormController, S as SerializedFormResult, c as FormFieldValue, d as FieldValidationRules, e as FormValues, V as ValidationSchema, f as ValidationResult } from './registry-
|
|
2
|
-
export { A as AppendContentOptions, g as AutoSubmitOptions, h as FieldFormatConfig, i as FieldFormatConfigMap, j as FieldValidationContext, k as FieldValidator, l as
|
|
1
|
+
import { F as FormTarget, a as FormControllerOptions, b as FormController, S as SerializedFormResult, c as FormFieldValue, d as FieldValidationRules, e as FormValues, V as ValidationSchema, f as ValidationResult } from './registry-GzopYPCR.cjs';
|
|
2
|
+
export { A as AppendContentOptions, g as AutoSubmitOptions, h as FieldFormatConfig, i as FieldFormatConfigMap, j as FieldValidationContext, k as FieldValidator, l as FormCleanup, m as FormDataPrimitive, n as FormErrors, o as FormFieldElement, p as FormFieldWatcher, q as FormStateListener, r as FormStateSnapshot, s as FormSubmitHandler, t as FormatType, u as FormsApi, v as FormsAvailable, N as NewFormInput, R as RuleConfig, w as SerializedFormValue, x as ValueValidationRules, y as VisualAttributes, z as browser } from './registry-GzopYPCR.cjs';
|
|
3
3
|
|
|
4
4
|
declare const form: (target: FormTarget, options?: FormControllerOptions) => FormController;
|
|
5
5
|
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { F as FormTarget, a as FormControllerOptions, b as FormController, S as SerializedFormResult, c as FormFieldValue, d as FieldValidationRules, e as FormValues, V as ValidationSchema, f as ValidationResult } from './registry-
|
|
2
|
-
export { A as AppendContentOptions, g as AutoSubmitOptions, h as FieldFormatConfig, i as FieldFormatConfigMap, j as FieldValidationContext, k as FieldValidator, l as
|
|
1
|
+
import { F as FormTarget, a as FormControllerOptions, b as FormController, S as SerializedFormResult, c as FormFieldValue, d as FieldValidationRules, e as FormValues, V as ValidationSchema, f as ValidationResult } from './registry-GzopYPCR.js';
|
|
2
|
+
export { A as AppendContentOptions, g as AutoSubmitOptions, h as FieldFormatConfig, i as FieldFormatConfigMap, j as FieldValidationContext, k as FieldValidator, l as FormCleanup, m as FormDataPrimitive, n as FormErrors, o as FormFieldElement, p as FormFieldWatcher, q as FormStateListener, r as FormStateSnapshot, s as FormSubmitHandler, t as FormatType, u as FormsApi, v as FormsAvailable, N as NewFormInput, R as RuleConfig, w as SerializedFormValue, x as ValueValidationRules, y as VisualAttributes, z as browser } from './registry-GzopYPCR.js';
|
|
3
3
|
|
|
4
4
|
declare const form: (target: FormTarget, options?: FormControllerOptions) => FormController;
|
|
5
5
|
|
package/dist/index.js
CHANGED
|
@@ -30,16 +30,29 @@ interface FieldValidationContext {
|
|
|
30
30
|
field: string;
|
|
31
31
|
value: FormFieldValue;
|
|
32
32
|
values: FormValues;
|
|
33
|
+
/** Concrete control while validating one member through `each`. */
|
|
34
|
+
element?: FormFieldElement;
|
|
35
|
+
/** Zero-based member position while validating through `each`. */
|
|
36
|
+
index?: number;
|
|
33
37
|
}
|
|
34
38
|
type FieldValidator = (context: FieldValidationContext) => string | undefined | null | false | true;
|
|
35
|
-
interface
|
|
39
|
+
interface ValueValidationRules {
|
|
36
40
|
required?: RuleConfig<boolean>;
|
|
37
41
|
minLength?: RuleConfig<number>;
|
|
38
42
|
maxLength?: RuleConfig<number>;
|
|
39
43
|
pattern?: RuleConfig<RegExp>;
|
|
44
|
+
numeric?: RuleConfig<boolean>;
|
|
45
|
+
min?: RuleConfig<number>;
|
|
46
|
+
max?: RuleConfig<number>;
|
|
47
|
+
validate?: FieldValidator | FieldValidator[];
|
|
48
|
+
}
|
|
49
|
+
interface FieldValidationRules extends ValueValidationRules {
|
|
40
50
|
/** Field name whose non-empty value must equal this field's value. */
|
|
41
51
|
sameAs?: RuleConfig<string>;
|
|
42
|
-
|
|
52
|
+
/** Fields whose changes should revalidate this field. */
|
|
53
|
+
dependsOn?: string | string[];
|
|
54
|
+
/** Rules applied independently to each collection member. */
|
|
55
|
+
each?: ValueValidationRules;
|
|
43
56
|
}
|
|
44
57
|
type ValidationSchema = Record<string, FieldValidationRules>;
|
|
45
58
|
interface VisualAttributes {
|
|
@@ -79,6 +92,7 @@ interface FormStateSnapshot {
|
|
|
79
92
|
isValid: boolean;
|
|
80
93
|
isValidated: boolean;
|
|
81
94
|
autoSubmit: boolean;
|
|
95
|
+
isSubmitting: boolean;
|
|
82
96
|
submitCount: number;
|
|
83
97
|
}
|
|
84
98
|
type FormStateListener = (state: FormStateSnapshot) => void;
|
|
@@ -93,12 +107,14 @@ interface AppendContentOptions {
|
|
|
93
107
|
atStart?: boolean;
|
|
94
108
|
}
|
|
95
109
|
type FormFieldWatcher = (value: FormFieldValue, field: FormFieldElement | FormFieldElement[] | null, form: HTMLFormElement, state: FormStateSnapshot) => void;
|
|
96
|
-
type FormSubmitHandler = (form: HTMLFormElement, data: Record<string, SerializedFormValue>, formData: FormData, state: FormStateSnapshot) => void
|
|
110
|
+
type FormSubmitHandler = (form: HTMLFormElement, data: Record<string, SerializedFormValue>, formData: FormData, state: FormStateSnapshot) => void | Promise<void>;
|
|
111
|
+
type FormCleanup = () => void;
|
|
97
112
|
interface FormController {
|
|
98
113
|
readonly element: HTMLFormElement | null;
|
|
99
114
|
readonly f: HTMLFormElement | null;
|
|
100
115
|
readonly options: FormControllerOptions;
|
|
101
116
|
onSubmit: (callback: FormSubmitHandler, preventDefault?: boolean) => FormController;
|
|
117
|
+
addCleanup: (cleanup: FormCleanup) => () => void;
|
|
102
118
|
watch: (field: string, callback: FormFieldWatcher) => FormController;
|
|
103
119
|
observe: (field: string, callback: FormFieldWatcher) => () => void;
|
|
104
120
|
unwatch: (field?: string, callback?: FormFieldWatcher) => FormController;
|
|
@@ -145,4 +161,4 @@ interface FormsApi {
|
|
|
145
161
|
}
|
|
146
162
|
declare const browser: FormsApi;
|
|
147
163
|
|
|
148
|
-
export { type AppendContentOptions as A, type FormTarget as F, type NewFormInput as N, type RuleConfig as R, type SerializedFormResult as S, type ValidationSchema as V, type FormControllerOptions as a, type FormController as b, type FormFieldValue as c, type FieldValidationRules as d, type FormValues as e, type ValidationResult as f, type AutoSubmitOptions as g, type FieldFormatConfig as h, type FieldFormatConfigMap as i, type FieldValidationContext as j, type FieldValidator as k, type
|
|
164
|
+
export { type AppendContentOptions as A, type FormTarget as F, type NewFormInput as N, type RuleConfig as R, type SerializedFormResult as S, type ValidationSchema as V, type FormControllerOptions as a, type FormController as b, type FormFieldValue as c, type FieldValidationRules as d, type FormValues as e, type ValidationResult as f, type AutoSubmitOptions as g, type FieldFormatConfig as h, type FieldFormatConfigMap as i, type FieldValidationContext as j, type FieldValidator as k, type FormCleanup as l, type FormDataPrimitive as m, type FormErrors as n, type FormFieldElement as o, type FormFieldWatcher as p, type FormStateListener as q, type FormStateSnapshot as r, type FormSubmitHandler as s, type FormatType as t, type FormsApi as u, type FormsAvailable as v, type SerializedFormValue as w, type ValueValidationRules as x, type VisualAttributes as y, browser as z };
|
|
@@ -30,16 +30,29 @@ interface FieldValidationContext {
|
|
|
30
30
|
field: string;
|
|
31
31
|
value: FormFieldValue;
|
|
32
32
|
values: FormValues;
|
|
33
|
+
/** Concrete control while validating one member through `each`. */
|
|
34
|
+
element?: FormFieldElement;
|
|
35
|
+
/** Zero-based member position while validating through `each`. */
|
|
36
|
+
index?: number;
|
|
33
37
|
}
|
|
34
38
|
type FieldValidator = (context: FieldValidationContext) => string | undefined | null | false | true;
|
|
35
|
-
interface
|
|
39
|
+
interface ValueValidationRules {
|
|
36
40
|
required?: RuleConfig<boolean>;
|
|
37
41
|
minLength?: RuleConfig<number>;
|
|
38
42
|
maxLength?: RuleConfig<number>;
|
|
39
43
|
pattern?: RuleConfig<RegExp>;
|
|
44
|
+
numeric?: RuleConfig<boolean>;
|
|
45
|
+
min?: RuleConfig<number>;
|
|
46
|
+
max?: RuleConfig<number>;
|
|
47
|
+
validate?: FieldValidator | FieldValidator[];
|
|
48
|
+
}
|
|
49
|
+
interface FieldValidationRules extends ValueValidationRules {
|
|
40
50
|
/** Field name whose non-empty value must equal this field's value. */
|
|
41
51
|
sameAs?: RuleConfig<string>;
|
|
42
|
-
|
|
52
|
+
/** Fields whose changes should revalidate this field. */
|
|
53
|
+
dependsOn?: string | string[];
|
|
54
|
+
/** Rules applied independently to each collection member. */
|
|
55
|
+
each?: ValueValidationRules;
|
|
43
56
|
}
|
|
44
57
|
type ValidationSchema = Record<string, FieldValidationRules>;
|
|
45
58
|
interface VisualAttributes {
|
|
@@ -79,6 +92,7 @@ interface FormStateSnapshot {
|
|
|
79
92
|
isValid: boolean;
|
|
80
93
|
isValidated: boolean;
|
|
81
94
|
autoSubmit: boolean;
|
|
95
|
+
isSubmitting: boolean;
|
|
82
96
|
submitCount: number;
|
|
83
97
|
}
|
|
84
98
|
type FormStateListener = (state: FormStateSnapshot) => void;
|
|
@@ -93,12 +107,14 @@ interface AppendContentOptions {
|
|
|
93
107
|
atStart?: boolean;
|
|
94
108
|
}
|
|
95
109
|
type FormFieldWatcher = (value: FormFieldValue, field: FormFieldElement | FormFieldElement[] | null, form: HTMLFormElement, state: FormStateSnapshot) => void;
|
|
96
|
-
type FormSubmitHandler = (form: HTMLFormElement, data: Record<string, SerializedFormValue>, formData: FormData, state: FormStateSnapshot) => void
|
|
110
|
+
type FormSubmitHandler = (form: HTMLFormElement, data: Record<string, SerializedFormValue>, formData: FormData, state: FormStateSnapshot) => void | Promise<void>;
|
|
111
|
+
type FormCleanup = () => void;
|
|
97
112
|
interface FormController {
|
|
98
113
|
readonly element: HTMLFormElement | null;
|
|
99
114
|
readonly f: HTMLFormElement | null;
|
|
100
115
|
readonly options: FormControllerOptions;
|
|
101
116
|
onSubmit: (callback: FormSubmitHandler, preventDefault?: boolean) => FormController;
|
|
117
|
+
addCleanup: (cleanup: FormCleanup) => () => void;
|
|
102
118
|
watch: (field: string, callback: FormFieldWatcher) => FormController;
|
|
103
119
|
observe: (field: string, callback: FormFieldWatcher) => () => void;
|
|
104
120
|
unwatch: (field?: string, callback?: FormFieldWatcher) => FormController;
|
|
@@ -145,4 +161,4 @@ interface FormsApi {
|
|
|
145
161
|
}
|
|
146
162
|
declare const browser: FormsApi;
|
|
147
163
|
|
|
148
|
-
export { type AppendContentOptions as A, type FormTarget as F, type NewFormInput as N, type RuleConfig as R, type SerializedFormResult as S, type ValidationSchema as V, type FormControllerOptions as a, type FormController as b, type FormFieldValue as c, type FieldValidationRules as d, type FormValues as e, type ValidationResult as f, type AutoSubmitOptions as g, type FieldFormatConfig as h, type FieldFormatConfigMap as i, type FieldValidationContext as j, type FieldValidator as k, type
|
|
164
|
+
export { type AppendContentOptions as A, type FormTarget as F, type NewFormInput as N, type RuleConfig as R, type SerializedFormResult as S, type ValidationSchema as V, type FormControllerOptions as a, type FormController as b, type FormFieldValue as c, type FieldValidationRules as d, type FormValues as e, type ValidationResult as f, type AutoSubmitOptions as g, type FieldFormatConfig as h, type FieldFormatConfigMap as i, type FieldValidationContext as j, type FieldValidator as k, type FormCleanup as l, type FormDataPrimitive as m, type FormErrors as n, type FormFieldElement as o, type FormFieldWatcher as p, type FormStateListener as q, type FormStateSnapshot as r, type FormSubmitHandler as s, type FormatType as t, type FormsApi as u, type FormsAvailable as v, type SerializedFormValue as w, type ValueValidationRules as x, type VisualAttributes as y, browser as z };
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# `addCleanup(cleanup)`
|
|
2
|
+
|
|
3
|
+
Registers teardown work owned by the controller. The callback runs when [`destroy()`](destroy.md) tears the controller down, unless that registration is removed first.
|
|
4
|
+
|
|
5
|
+
## Signature
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
addCleanup(cleanup: FormCleanup): () => void
|
|
9
|
+
|
|
10
|
+
type FormCleanup = () => void
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Parameters
|
|
14
|
+
|
|
15
|
+
| Name | Type | Required | Description |
|
|
16
|
+
| --- | --- | --- | --- |
|
|
17
|
+
| `cleanup` | [`FormCleanup`](../typescript.md#formcleanup) | yes | Synchronous callback to run during controller destruction. |
|
|
18
|
+
|
|
19
|
+
## Returns
|
|
20
|
+
|
|
21
|
+
An unregister function. Calling it removes only that registration without running the cleanup. It is idempotent.
|
|
22
|
+
|
|
23
|
+
## Behaviour
|
|
24
|
+
|
|
25
|
+
- Active cleanups run once, in reverse registration order, when `destroy()` is first called.
|
|
26
|
+
- Registering the same callback more than once creates independent registrations. Each active registration runs once.
|
|
27
|
+
- A cleanup added after the controller is already destroyed runs immediately. Its returned unregister function is a no-op.
|
|
28
|
+
- If a cleanup throws, `destroy()` logs `[forms] cleanup failed`, continues with the remaining callbacks, and completes teardown.
|
|
29
|
+
- Calling `destroy()` again does not rerun cleanups.
|
|
30
|
+
|
|
31
|
+
## Example
|
|
32
|
+
|
|
33
|
+
```ts
|
|
34
|
+
import { form } from '@samline/forms'
|
|
35
|
+
|
|
36
|
+
const profile = form('profile-form')
|
|
37
|
+
const abortController = new AbortController()
|
|
38
|
+
|
|
39
|
+
const unregister = profile.addCleanup(() => abortController.abort())
|
|
40
|
+
|
|
41
|
+
// Optional: keep the resource alive beyond this controller.
|
|
42
|
+
unregister()
|
|
43
|
+
|
|
44
|
+
profile.destroy()
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
This hook is useful for integration-owned event listeners, observers, subscriptions, abort controllers, and other resources that should share the form controller's lifetime.
|
|
48
|
+
|
|
49
|
+
## Related
|
|
50
|
+
|
|
51
|
+
- [`destroy`](destroy.md) — runs all active cleanup registrations.
|
|
52
|
+
- [`observe`](observe.md) and [`subscribe`](subscribe.md) — return their own unsubscribe functions.
|
package/docs/api/destroy.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# `destroy()`
|
|
2
2
|
|
|
3
|
-
Tears the controller down. Removes all DOM listeners, disconnects the `MutationObserver`, clears caches, drops subscribers and submit handlers, and resets internal state.
|
|
3
|
+
Tears the controller down. Removes all DOM listeners, disconnects the `MutationObserver`, runs registered cleanups, clears caches, drops subscribers and submit handlers, and resets internal state.
|
|
4
4
|
|
|
5
5
|
## Signature
|
|
6
6
|
|
|
@@ -19,8 +19,10 @@ When invoked, the controller:
|
|
|
19
19
|
- Removes every listener it added (`input`, `submit`, formatter listeners, and document delegation for external controls).
|
|
20
20
|
- Cancels any pending auto-submit debounce timer.
|
|
21
21
|
- Disconnects the form `MutationObserver`.
|
|
22
|
+
- Runs active callbacks registered with [`addCleanup()`](add-cleanup.md) once in reverse registration order. A thrown cleanup is logged and does not stop the others.
|
|
22
23
|
- Clears `watchedFields`, `subscribers`, `submitHandlers`, and `fieldCache`.
|
|
23
24
|
- Resets manual and validation error maps.
|
|
25
|
+
- Clears tracked async-submit state. Pending handler promises continue independently, but no completion notification is emitted after destruction.
|
|
24
26
|
|
|
25
27
|
After `destroy()` the controller object still exists and its methods remain callable. Direct reads and writes can still inspect or mutate the bound DOM, but the removed controller listeners no longer react to resulting events.
|
|
26
28
|
|
|
@@ -53,13 +55,26 @@ window.addEventListener('beforeunload', () => {
|
|
|
53
55
|
})
|
|
54
56
|
```
|
|
55
57
|
|
|
58
|
+
### Own integration resources
|
|
59
|
+
|
|
60
|
+
```ts
|
|
61
|
+
const profile = form('profile-form')
|
|
62
|
+
const observer = new ResizeObserver(() => updateLayout())
|
|
63
|
+
|
|
64
|
+
if (profile.element) observer.observe(profile.element)
|
|
65
|
+
profile.addCleanup(() => observer.disconnect())
|
|
66
|
+
```
|
|
67
|
+
|
|
56
68
|
## Edge cases
|
|
57
69
|
|
|
58
70
|
- **`destroy()` does not remove `css-filled` / `css-error` attributes** from fields. If you need that, call [`reset()`](reset.md) before destroy, or strip the attributes yourself.
|
|
59
71
|
- **Subscribers and watchers stop receiving notifications immediately**, but their closures remain until you release them.
|
|
72
|
+
- **Cleanup registrations are independent.** Registering the same callback twice runs it twice unless one registration's unregister function is called.
|
|
73
|
+
- **Late cleanup registration runs immediately.** Calling `addCleanup()` after destruction executes the callback at once and returns a no-op unregister function.
|
|
60
74
|
- **Reusing the controller reference after destroy is possible but usually misleading.** Reads still inspect the bound form and methods such as `setValue()` can still write fields, but watchers, subscribers, validation-on-input, and auto-submit no longer run.
|
|
61
75
|
|
|
62
76
|
## Related
|
|
63
77
|
|
|
64
78
|
- [`reset`](reset.md) — restore form values without removing listeners.
|
|
65
79
|
- [`form`](form.md) — bind a new controller.
|
|
80
|
+
- [`addCleanup`](add-cleanup.md) — register controller-owned teardown work.
|
package/docs/api/form.md
CHANGED
|
@@ -69,8 +69,8 @@ On creation the controller:
|
|
|
69
69
|
3. Wires delegated `input` and `submit` listeners, including controls associated through `form="id"`.
|
|
70
70
|
4. Starts a `MutationObserver` on the form subtree to track dynamic fields.
|
|
71
71
|
5. Optionally enables [`autoSubmit`](../api/auto-submit.md) when `options.autoSubmit` is truthy.
|
|
72
|
-
6.
|
|
73
|
-
7.
|
|
72
|
+
6. Synchronizes initial `css-filled` state for every tracked control, including when `autoValidate` is disabled.
|
|
73
|
+
7. Runs an initial validation pass only if `autoValidate` is enabled.
|
|
74
74
|
|
|
75
75
|
## Examples
|
|
76
76
|
|
|
@@ -134,6 +134,7 @@ form(profileRef)
|
|
|
134
134
|
- **The id is resolved once.** If the DOM element with that id is replaced, the controller keeps listening to the original element. Recreate the controller if you re-render the form.
|
|
135
135
|
- **`null` / `undefined` targets** are accepted and produce an inert controller. The chainable methods still return the controller, so they are safe to call as no-ops.
|
|
136
136
|
- **Reusing a target across multiple controllers** is supported but they each install their own listeners — call [`destroy()`](destroy.md) on the previous one to avoid duplicate work.
|
|
137
|
+
- **`autoValidate: false` disables the initial rule pass, not visual initialization.** Prefilled fields still receive `css-filled`; no validation errors are produced and `isValidated` remains `false`.
|
|
137
138
|
- **`createFormController` is also exported** as the lower-level factory; prefer `form` for ergonomic imports.
|
|
138
139
|
|
|
139
140
|
## `createFormController`
|
|
@@ -151,4 +152,5 @@ This is the factory used by `form()` and accepts the same arguments. It is expor
|
|
|
151
152
|
|
|
152
153
|
- [`FormController`](../typescript.md#formcontroller) — the returned controller type.
|
|
153
154
|
- [`destroy`](destroy.md) — tear the controller down.
|
|
155
|
+
- [`addCleanup`](add-cleanup.md) — attach integration cleanup to the controller lifecycle.
|
|
154
156
|
- [docs/options.md](../options.md) — every option in detail.
|
package/docs/api/format.md
CHANGED
|
@@ -81,11 +81,11 @@ When the visible field already carries a value at the moment `format()` first ru
|
|
|
81
81
|
To bridge the two layers, the controller:
|
|
82
82
|
|
|
83
83
|
- For the **initial pass** (and for input events whose source is the hidden mirror, e.g. a `setValue('field', raw)` that wrote the canonical raw), overrides the formatter call to use `interpretInputAs: 'auto'`. This handles the realistic `old()` re-render case where the server hands the visible the canonical raw.
|
|
84
|
-
- For **live
|
|
85
|
-
- For the **re-bind** path (a second `format()` call with new options for the same field), the
|
|
84
|
+
- For **live input events** on the visible field, the controller injects `interpretInputAs: 'display'` when the caller did not choose a mode. This keeps typing and full-length delimiter-less pastes in display order instead of leaving them ambiguous under the formatter's automatic heuristic.
|
|
85
|
+
- For the **re-bind** path (a second `format()` call with new options for the same field), the controller also injects `'display'` by default because the current visible value is already in display order.
|
|
86
86
|
- An **explicit `interpretInputAs`** in `options` is always respected, never overridden. Use `'display'` if you pre-render the visible in display order from the server, or `'raw'` if you want the legacy pre-1.2.0 round-trip semantics for the initial pass.
|
|
87
87
|
|
|
88
|
-
This is transparent for the common case: a Blade re-render with `value="{{ old('birthday') }}"` carrying `"19901212"` repopulates the visible as `"12/12/1990"`
|
|
88
|
+
This is transparent for the common case: a Blade re-render with `value="{{ old('birthday') }}"` carrying `"19901212"` repopulates the visible as `"12/12/1990"`, while pasting `"12121990"` into the visible field remains 12 December 1990. Set `interpretInputAs` explicitly only when your application needs a different convention.
|
|
89
89
|
|
|
90
90
|
## Pre-authoring the visible
|
|
91
91
|
|
package/docs/api/get-state.md
CHANGED
|
@@ -16,6 +16,7 @@ interface FormStateSnapshot {
|
|
|
16
16
|
isValid: boolean
|
|
17
17
|
isValidated: boolean
|
|
18
18
|
autoSubmit: boolean
|
|
19
|
+
isSubmitting: boolean
|
|
19
20
|
submitCount: number
|
|
20
21
|
}
|
|
21
22
|
```
|
|
@@ -36,8 +37,9 @@ A [`FormStateSnapshot`](../typescript.md#formstatesnapshot). Every call returns
|
|
|
36
37
|
| `errors` | Merged validation errors (from rules) and manual errors (from [`setErrors`](set-errors.md)). |
|
|
37
38
|
| `filledFields` | Names of fields that have a non-empty value. |
|
|
38
39
|
| `isValid` | `true` when `errors` has no entries. |
|
|
39
|
-
| `isValidated` | `true` after [`validate`](validate.md) has run
|
|
40
|
+
| `isValidated` | `true` after the initial auto-validation pass or once [`validate`](validate.md) has run. |
|
|
40
41
|
| `autoSubmit` | `true` while auto-submit is enabled. |
|
|
42
|
+
| `isSubmitting` | `true` while at least one valid submission has asynchronous `onSubmit` work that has not settled. |
|
|
41
43
|
| `submitCount` | Number of submit attempts (valid or invalid). |
|
|
42
44
|
|
|
43
45
|
## Examples
|
|
@@ -54,6 +56,7 @@ const state = profile.getState()
|
|
|
54
56
|
console.log(state.values.email)
|
|
55
57
|
console.log(state.errors)
|
|
56
58
|
console.log(state.isValid)
|
|
59
|
+
console.log(state.isSubmitting)
|
|
57
60
|
```
|
|
58
61
|
|
|
59
62
|
### Drive an accessible error message
|
|
@@ -82,12 +85,25 @@ profile.onSubmit((_form, _data, _formData, state) => {
|
|
|
82
85
|
})
|
|
83
86
|
```
|
|
84
87
|
|
|
88
|
+
### Disable a button during async submissions
|
|
89
|
+
|
|
90
|
+
```ts
|
|
91
|
+
profile.subscribe(state => {
|
|
92
|
+
submitButton.disabled = state.isSubmitting
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
profile.onSubmit(async (_form, _data, formData) => {
|
|
96
|
+
await fetch('/profile', { method: 'POST', body: formData })
|
|
97
|
+
})
|
|
98
|
+
```
|
|
99
|
+
|
|
85
100
|
## Edge cases
|
|
86
101
|
|
|
87
102
|
- **`getState()` does not notify subscribers.** If you need a notification-driven view, use [`subscribe`](subscribe.md).
|
|
88
103
|
- **`errors` is the merged map** (validation + manual). Use [`validate`](validate.md) if you need just the validation errors.
|
|
89
104
|
- **`filledFields` reflects the value snapshot at the time of the call** — it is not stored between calls.
|
|
90
105
|
- **`submitCount` increments on every submit**, valid or invalid. It does not reset on [`reset`](reset.md); it tracks the lifetime of the controller.
|
|
106
|
+
- **`isSubmitting` tracks overlapping work.** It remains `true` until every currently pending async submission batch settles, including rejected promises. Synchronous handlers do not toggle it.
|
|
91
107
|
|
|
92
108
|
## Related
|
|
93
109
|
|
package/docs/api/index.md
CHANGED
|
@@ -8,6 +8,7 @@ Every public method and helper gets its own page. Use this index to navigate to
|
|
|
8
8
|
- [`createFormController(target, options?)`](form.md#createformcontroller) — underlying controller factory.
|
|
9
9
|
- [`browser`](../browser.md) — registry-oriented browser API and global build.
|
|
10
10
|
- [`element`](element.md) — the bound form (`f` is an alias).
|
|
11
|
+
- [`addCleanup(cleanup)`](add-cleanup.md) — register controller-owned teardown work.
|
|
11
12
|
- [`destroy()`](destroy.md) — tear down listeners, observer, and caches.
|
|
12
13
|
- [`reset()`](reset.md) — restore native form values and clear errors.
|
|
13
14
|
|
package/docs/api/on-submit.md
CHANGED
|
@@ -28,12 +28,13 @@ The submit pipeline runs in this order:
|
|
|
28
28
|
|
|
29
29
|
1. If `clearErrorsOnSubmit` is `true` (default), manual errors are cleared.
|
|
30
30
|
2. Validation runs for every field with rules. `isValidated` becomes `true`.
|
|
31
|
-
3. If validation fails, the browser’s native submit is prevented, submit handlers are **not** invoked, `aria-invalid="true"` is synchronized, the first connected, enabled, non-hidden invalid
|
|
31
|
+
3. If validation fails, the browser’s native submit is prevented, submit handlers are **not** invoked, `aria-invalid="true"` is synchronized, the first connected, enabled, non-hidden invalid control receives focus (including the first failing `each` member), `submitCount` is incremented, and subscribers are notified.
|
|
32
32
|
4. If validation passes:
|
|
33
33
|
- `submitCount` is incremented.
|
|
34
34
|
- Subscribers are notified.
|
|
35
35
|
- If at least one handler was registered with `preventDefault: true`, the native submit is prevented. Otherwise (all handlers `preventDefault: false`), the native submit proceeds.
|
|
36
|
-
|
|
36
|
+
- Every registered handler is invoked in registration order. When the event supplies a successful named submit button, its name/value is included in `data` and `formData`.
|
|
37
|
+
- Promise-returning handlers are tracked concurrently. Subscribers receive `isSubmitting: true` while the batch is pending and another update when all promises in it have fulfilled or rejected.
|
|
37
38
|
|
|
38
39
|
## Examples
|
|
39
40
|
|
|
@@ -81,7 +82,10 @@ profile
|
|
|
81
82
|
- **`data` is a plain object mirror of `FormData`.** Repeated names become arrays (e.g. `interests: ['design', 'code']`).
|
|
82
83
|
- **Handlers do not receive errors as an argument.** If you need to inspect the validation result, read [`getState()`](get-state.md) or call [`validate()`](validate.md) from inside the handler.
|
|
83
84
|
- **`onSubmit` cannot be removed** — there is no `offSubmit` API. If you need conditional submission, guard inside the handler or recreate the controller.
|
|
84
|
-
- **
|
|
85
|
+
- **Handlers may return `void` or `Promise<void>`.** Invocation still starts synchronously and native submit prevention is decided before promises settle; async resolution does not gate the browser.
|
|
86
|
+
- **Handlers in one submission run concurrently.** The controller invokes them in registration order without awaiting between calls, then tracks all returned promises together.
|
|
87
|
+
- **Rejected promises settle submission state.** They do not become form errors and are consumed by the controller's all-settled tracking, so `isSubmitting` returns to `false` when no other submission work remains.
|
|
88
|
+
- **Overlapping submissions are tracked independently.** `isSubmitting` remains `true` until every pending submission batch has settled.
|
|
85
89
|
|
|
86
90
|
## Related
|
|
87
91
|
|
package/docs/api/reset.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# `reset()`
|
|
2
2
|
|
|
3
|
-
Restores the form to its initial state: native field values reset, manual and validation errors cleared, visual attributes
|
|
3
|
+
Restores the form to its initial state: native field values reset, manual and validation errors cleared, visual attributes synchronized, subscribers notified.
|
|
4
4
|
|
|
5
5
|
## Signature
|
|
6
6
|
|
|
@@ -20,7 +20,7 @@ When invoked, the controller:
|
|
|
20
20
|
2. Clears `state.manualErrors` and `state.validationErrors`.
|
|
21
21
|
3. Restores matching visible/raw defaults for formatted fields.
|
|
22
22
|
4. Removes every `css-filled`, `css-error`, and `aria-invalid` attribute from associated fields.
|
|
23
|
-
5. Re-syncs visual state
|
|
23
|
+
5. Re-syncs visual state. Controls whose native default value is non-empty regain `css-filled`, even when `autoValidate` is `false`; errors and `aria-invalid` stay cleared.
|
|
24
24
|
6. Notifies subscribers.
|
|
25
25
|
|
|
26
26
|
## Examples
|
|
@@ -53,6 +53,7 @@ document.querySelector('#cancel')?.addEventListener('click', () => {
|
|
|
53
53
|
- **Native `reset()` does not dispatch `input` events** for fields whose value changes because of the reset. The controller restores formatted mirrors and visual/ARIA state explicitly.
|
|
54
54
|
- **Submit handlers are not invoked.** `reset()` is purely a UI / state operation.
|
|
55
55
|
- **The `submitCount` from [`getState()`](get-state.md) is not reset.** It tracks the lifetime of the controller, not the lifetime of a single form session.
|
|
56
|
+
- **Pending async submit work is not cancelled.** `isSubmitting` remains true until the outstanding `onSubmit` promises settle.
|
|
56
57
|
|
|
57
58
|
## Related
|
|
58
59
|
|
package/docs/api/revalidate.md
CHANGED
|
@@ -21,6 +21,8 @@ Same as [`validate`](validate.md) — a [`ValidationResult`](../typescript.md#va
|
|
|
21
21
|
|
|
22
22
|
Identical to [`validate`](validate.md). The two methods share the same internal implementation.
|
|
23
23
|
|
|
24
|
+
This includes strict decimal `numeric` / inclusive `min` / `max` rules, per-member `each` validation and element-level error styling. Explicit field lists are not expanded through `sameAs` or `dependsOn`; those relationships drive revalidation after input events.
|
|
25
|
+
|
|
24
26
|
## When to use which
|
|
25
27
|
|
|
26
28
|
Use `validate` when you are running validation as part of an initial check. Use `revalidate` when you are explicitly asking the controller to recompute validation after a state change (e.g. after `setErrors`, after async data loads, after resetting partial form state).
|
|
@@ -47,4 +49,4 @@ profile.revalidate(['email'])
|
|
|
47
49
|
|
|
48
50
|
## Related
|
|
49
51
|
|
|
50
|
-
- [`validate`](validate.md) — same implementation, different name.
|
|
52
|
+
- [`validate`](validate.md) — same implementation, different name.
|
package/docs/api/subscribe.md
CHANGED
|
@@ -32,6 +32,7 @@ State mutations that fire subscribers include:
|
|
|
32
32
|
- [`autoSubmit`](auto-submit.md) and [`disableAutoSubmit`](disable-auto-submit.md).
|
|
33
33
|
- [`reset`](reset.md).
|
|
34
34
|
- Submit attempts, after their validation pass and `submitCount` update.
|
|
35
|
+
- Async submit state transitions: once when promise-returning handlers make `isSubmitting` true, and again after their submission batch settles. Overlapping batches keep it true until all settle.
|
|
35
36
|
- `MutationObserver`-detected DOM changes.
|
|
36
37
|
|
|
37
38
|
Direct [`validate`](validate.md) and [`revalidate`](revalidate.md) calls update validation state and visual attributes but do not independently notify subscribers. Use their returned result when synchronous rendering is required.
|
|
@@ -78,6 +79,21 @@ profile.subscribe(state => {
|
|
|
78
79
|
})
|
|
79
80
|
```
|
|
80
81
|
|
|
82
|
+
### Track asynchronous submission
|
|
83
|
+
|
|
84
|
+
```ts
|
|
85
|
+
const profile = form('profile-form')
|
|
86
|
+
|
|
87
|
+
profile.subscribe(state => {
|
|
88
|
+
submitButton.disabled = state.isSubmitting
|
|
89
|
+
submitButton.textContent = state.isSubmitting ? 'Saving...' : 'Save'
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
profile.onSubmit(async (_form, _data, formData) => {
|
|
93
|
+
await fetch('/profile', { method: 'POST', body: formData })
|
|
94
|
+
})
|
|
95
|
+
```
|
|
96
|
+
|
|
81
97
|
## Edge cases
|
|
82
98
|
|
|
83
99
|
- **The listener is invoked with a fresh snapshot** on every fire — it is safe to store the reference for the duration of one call.
|
|
@@ -34,8 +34,10 @@ Runs each rule in order:
|
|
|
34
34
|
2. `minLength` — string length (or array length) must be ≥ the threshold.
|
|
35
35
|
3. `maxLength` — string length (or array length) must be ≤ the threshold.
|
|
36
36
|
4. `pattern` — the string form of the value must match the regex (skipped when the value is empty).
|
|
37
|
-
5. `
|
|
38
|
-
6. `validate` — each custom validator runs in registration order. Returning a string pushes an error; returning `false` pushes `"Validation failed."`; returning `null`, `undefined`, or `true` is treated as a pass.
|
|
37
|
+
5. `numeric`, `min`, and `max` — non-empty values must be strict decimals and satisfy the inclusive bounds.
|
|
38
|
+
6. `validate` — each field-level custom validator runs in registration order. Returning a string pushes an error; returning `false` pushes `"Validation failed."`; returning `null`, `undefined`, or `true` is treated as a pass.
|
|
39
|
+
7. `sameAs` — the aggregate value must equal the named value (skipped until both values are non-empty).
|
|
40
|
+
8. `each` — the value-level rules run independently for every collection member, including any member-level custom validators.
|
|
39
41
|
|
|
40
42
|
Default error messages are produced when the rule was configured without an explicit `message`. Custom messages come from the `{ value, message }` form of `RuleConfig`.
|
|
41
43
|
|
|
@@ -93,14 +95,48 @@ const errors = validateFieldValue(
|
|
|
93
95
|
console.log(errors) // ['Use at least 8 characters.']
|
|
94
96
|
```
|
|
95
97
|
|
|
98
|
+
### Validate strict decimal values and bounds
|
|
99
|
+
|
|
100
|
+
```ts
|
|
101
|
+
const errors = validateFieldValue(
|
|
102
|
+
'amount',
|
|
103
|
+
'100.25',
|
|
104
|
+
{ numeric: true, min: 0, max: 500 },
|
|
105
|
+
{ amount: '100.25' }
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
console.log(errors) // []
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### Validate every collection member
|
|
112
|
+
|
|
113
|
+
```ts
|
|
114
|
+
const errors = validateFieldValue(
|
|
115
|
+
'emails',
|
|
116
|
+
['valid@example.com', 'invalid'],
|
|
117
|
+
{
|
|
118
|
+
each: {
|
|
119
|
+
required: true,
|
|
120
|
+
pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/
|
|
121
|
+
}
|
|
122
|
+
},
|
|
123
|
+
{ emails: ['valid@example.com', 'invalid'] }
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
console.log(errors) // ['Value does not match the required pattern.']
|
|
127
|
+
```
|
|
128
|
+
|
|
96
129
|
## Edge cases
|
|
97
130
|
|
|
98
131
|
- **The pattern rule is skipped for empty values.** This is the standard “required + pattern” pattern: required runs first, then pattern runs only when there is a value.
|
|
132
|
+
- **Numeric rules are skipped for empty values.** Use `required` as well when the field is mandatory.
|
|
133
|
+
- **Numeric parsing is strict.** Surrounding whitespace is trimmed, and ordinary signed decimals such as `-2`, `+3.5`, `.5`, and `10.` are accepted. Exponents, hexadecimal, `Infinity`, `NaN`, and formatted separators are rejected. `min` and `max` are inclusive and also enforce numeric input when `numeric` is omitted.
|
|
99
134
|
- **The `sameAs` rule is skipped until both values are non-empty.** Add `required` to each mandatory field; `sameAs` does not imply requiredness.
|
|
100
|
-
- **Pure validation does not track dependencies.** `
|
|
135
|
+
- **Pure validation does not track dependencies.** `dependsOn` is controller metadata and has no effect here; `validateFieldValue` evaluates the supplied values once.
|
|
101
136
|
- **`validateFieldValue` does not read from any DOM.** Pass everything in as arguments.
|
|
102
137
|
- **`validate` runs after the built-in rules.** It receives the full context, including the current value and the other field values.
|
|
103
138
|
- **Multiple custom validators** can be passed as an array — they all run, and any error from any of them is collected.
|
|
139
|
+
- **`each` keeps the public return type flat.** Member errors are appended to the returned `string[]` in member order. Member custom validators receive a zero-based `index`; `element` is unavailable in this DOM-free helper.
|
|
104
140
|
|
|
105
141
|
## Related
|
|
106
142
|
|
|
@@ -38,7 +38,7 @@ A [`ValidationResult`](../typescript.md#validationresult):
|
|
|
38
38
|
|
|
39
39
|
## Behaviour
|
|
40
40
|
|
|
41
|
-
For each `[field, rules]` entry in the schema, runs [`validateFieldValue`](validate-field-value.md) against `values[field]`. The other values are available to `sameAs` and custom validators.
|
|
41
|
+
For each `[field, rules]` entry in the schema, runs [`validateFieldValue`](validate-field-value.md) against `values[field]`. The other values are available to `sameAs` and custom validators. Aggregate rules and `each` member rules are collected into the same `string[]` for that field.
|
|
42
42
|
|
|
43
43
|
## Examples
|
|
44
44
|
|
|
@@ -92,6 +92,9 @@ if (!result.isValid) {
|
|
|
92
92
|
- **`validateValues` does not know which fields are “tracked”.** It validates every entry in the schema, even fields that are not present in `values`. An undefined value fails `required` but passes the rest.
|
|
93
93
|
- **Errors are always a fresh object.** Safe to mutate.
|
|
94
94
|
- **`sameAs` compares the provided snapshot only.** Dependency tracking and automatic revalidation belong to a form controller, not this pure helper.
|
|
95
|
+
- **`dependsOn` has no effect in this helper.** It describes controller revalidation triggers, not a validation rule.
|
|
96
|
+
- **`each` errors stay flat.** Member messages are appended under the field's existing `FormErrors` key in member order; no nested error shape is introduced.
|
|
97
|
+
- **Numeric rules use strict decimal syntax and inclusive bounds.** Empty values skip `numeric`, `min`, and `max`; use `required` to reject emptiness.
|
|
95
98
|
- **Custom validators receive the full values map**, so cross-field rules work the same as inside a controller.
|
|
96
99
|
|
|
97
100
|
## Related
|
package/docs/api/validate.md
CHANGED
|
@@ -33,7 +33,8 @@ A [`ValidationResult`](../typescript.md#validationresult):
|
|
|
33
33
|
|
|
34
34
|
- Marks the form as validated (`state.isValidated = true`). This means future field changes will run validation again, even when the controller was created with `autoValidate: false`.
|
|
35
35
|
- Re-syncs visual attributes (`css-filled` / `css-error`) for the affected fields.
|
|
36
|
-
- After the form is validated, an input event also revalidates fields whose `sameAs`
|
|
36
|
+
- After the form is validated, an input event also revalidates fields whose `sameAs` or `dependsOn` metadata references the changed field. Transitive chains and cycles are deduplicated and each affected field runs once.
|
|
37
|
+
- `each` validates every collection member independently. Item-only failures apply `css-error` and `aria-invalid="true"` only to the failing DOM controls, while the public `errors[field]` remains a flat `string[]`.
|
|
37
38
|
- Subscribers are not notified — this method is a pure read of the rule engine. To run validation and notify subscribers, use [`subscribe`](subscribe.md) and read [`getState()`](get-state.md).
|
|
38
39
|
|
|
39
40
|
## Examples
|
|
@@ -88,8 +89,9 @@ function nextStep(step: 1 | 2) {
|
|
|
88
89
|
|
|
89
90
|
- **`validate` ignores manual errors** during computation, but the returned `errors` includes them (merged with validation errors). The `isValid` flag reflects the merged map.
|
|
90
91
|
- **Validation rules must be configured** via `options.validators`. Fields without rules are always considered valid.
|
|
91
|
-
- **An explicit partial call validates exactly the requested fields.** `validate(['password'])` does not expand `sameAs`
|
|
92
|
-
- **Custom validators receive `{ field, value, values }
|
|
92
|
+
- **An explicit partial call validates exactly the requested fields.** `validate(['password'])` does not expand `sameAs` / `dependsOn` relationships; dependency expansion happens for input events, including those dispatched by `setValue()`.
|
|
93
|
+
- **Custom validators receive `{ field, value, values }`.** During `each`, the context also includes the concrete `element` and zero-based `index` — see [`FieldValidationContext`](../typescript.md#fieldvalidationcontext).
|
|
94
|
+
- **Numeric rules are strict and bounds are inclusive.** Empty values skip `numeric`, `min`, and `max`; add `required` when needed.
|
|
93
95
|
- **A custom validator that returns `false` pushes the generic message `"Validation failed."`.**
|
|
94
96
|
- **`validate()` is the same as calling [`revalidate()`](revalidate.md)`** — they are aliases for readability.
|
|
95
97
|
|
package/docs/browser.md
CHANGED
|
@@ -11,10 +11,10 @@ For every other case (modern apps, bundlers, TypeScript projects), use the main
|
|
|
11
11
|
## Script tag
|
|
12
12
|
|
|
13
13
|
```html
|
|
14
|
-
<script src="https://unpkg.com/@samline/forms@2.
|
|
14
|
+
<script src="https://unpkg.com/@samline/forms@2.7.1/dist/browser/global.global.js"></script>
|
|
15
15
|
```
|
|
16
16
|
|
|
17
|
-
> Pin the version in production. Replace `2.
|
|
17
|
+
> Pin the version in production. Replace `2.7.1` with the version you ship.
|
|
18
18
|
|
|
19
19
|
The bundle is a single IIFE that registers a global object.
|
|
20
20
|
|
|
@@ -49,7 +49,7 @@ The factory returns a `FormController` with the same signatures, semantics, and
|
|
|
49
49
|
<button type="submit">Send</button>
|
|
50
50
|
</form>
|
|
51
51
|
|
|
52
|
-
<script src="https://unpkg.com/@samline/forms@2.
|
|
52
|
+
<script src="https://unpkg.com/@samline/forms@2.7.1/dist/browser/global.global.js"></script>
|
|
53
53
|
<script>
|
|
54
54
|
const contactForm = window.Forms.newForm({
|
|
55
55
|
id: 'contact-form',
|
|
@@ -102,7 +102,7 @@ The browser bundle ships only the registry helpers plus the `form` factory. Ever
|
|
|
102
102
|
|
|
103
103
|
The controller returned by `form` / `newForm` exposes the methods documented under [docs/api/](api/index.md):
|
|
104
104
|
|
|
105
|
-
- Lifecycle: [`element`](api/element.md), [`reset`](api/reset.md), [`destroy`](api/destroy.md).
|
|
105
|
+
- Lifecycle: [`element`](api/element.md), [`addCleanup`](api/add-cleanup.md), [`reset`](api/reset.md), [`destroy`](api/destroy.md).
|
|
106
106
|
- Submission: [`onSubmit`](api/on-submit.md), [`autoSubmit`](api/auto-submit.md), [`disableAutoSubmit`](api/disable-auto-submit.md).
|
|
107
107
|
- Field observation: [`watch`](api/watch.md), [`observe`](api/observe.md), [`unwatch`](api/unwatch.md), [`subscribe`](api/subscribe.md).
|
|
108
108
|
- Field values: [`setValue`](api/set-value.md), [`getValue`](api/get-value.md), [`getField`](api/get-field.md), [`prefill`](api/prefill.md).
|
|
@@ -126,7 +126,7 @@ window.Forms.destroyForm('contact-form')
|
|
|
126
126
|
|
|
127
127
|
## Common pitfalls
|
|
128
128
|
|
|
129
|
-
- **Pin the version.** The CDN URL above is `2.
|
|
129
|
+
- **Pin the version.** The CDN URL above is `2.7.1`. Replace it whenever you upgrade.
|
|
130
130
|
- **The script must be loaded before any code that uses `window.Forms`.** Place the `<script>` tag in `<head>` with `defer`, or before the user script in `<body>`.
|
|
131
131
|
- **No bundler means no tree-shaking.** The global browser bundle includes the controller and formatter peer. Inspect the published artifact when bundle size is a constraint.
|
|
132
132
|
- **CSP:** if your site uses a strict Content Security Policy, allow `unpkg.com` in `script-src` (or self-host the file).
|