@samline/forms 2.7.2 → 2.8.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 (52) hide show
  1. package/CHANGELOG.md +302 -0
  2. package/README.md +110 -122
  3. package/dist/browser/global.d.ts +1 -13
  4. package/dist/browser/global.global.js +12 -6188
  5. package/dist/browser/global.global.js.map +1 -1
  6. package/dist/browser/index.cjs +125 -56
  7. package/dist/browser/index.cjs.map +1 -1
  8. package/dist/browser/index.js +1 -1
  9. package/dist/{chunk-MMHX34WB.js → chunk-23VZAYA3.js} +126 -57
  10. package/dist/chunk-23VZAYA3.js.map +1 -0
  11. package/dist/index.cjs +125 -56
  12. package/dist/index.cjs.map +1 -1
  13. package/dist/index.js +1 -1
  14. package/package.json +35 -7
  15. package/dist/chunk-MMHX34WB.js.map +0 -1
  16. package/docs/api/add-cleanup.md +0 -52
  17. package/docs/api/append.md +0 -96
  18. package/docs/api/auto-submit.md +0 -91
  19. package/docs/api/clear-errors.md +0 -69
  20. package/docs/api/destroy.md +0 -80
  21. package/docs/api/disable-auto-submit.md +0 -65
  22. package/docs/api/element.md +0 -78
  23. package/docs/api/form.md +0 -156
  24. package/docs/api/format.md +0 -270
  25. package/docs/api/get-data.md +0 -90
  26. package/docs/api/get-field.md +0 -87
  27. package/docs/api/get-state.md +0 -111
  28. package/docs/api/get-value.md +0 -114
  29. package/docs/api/index.md +0 -55
  30. package/docs/api/observe.md +0 -126
  31. package/docs/api/on-submit.md +0 -94
  32. package/docs/api/parse-form-data.md +0 -94
  33. package/docs/api/prefill.md +0 -89
  34. package/docs/api/regex.md +0 -155
  35. package/docs/api/reset.md +0 -61
  36. package/docs/api/revalidate.md +0 -52
  37. package/docs/api/set-errors.md +0 -97
  38. package/docs/api/set-value.md +0 -120
  39. package/docs/api/subscribe.md +0 -107
  40. package/docs/api/unwatch.md +0 -74
  41. package/docs/api/validate-field-value.md +0 -144
  42. package/docs/api/validate-values.md +0 -103
  43. package/docs/api/validate.md +0 -102
  44. package/docs/api/watch.md +0 -117
  45. package/docs/browser.md +0 -156
  46. package/docs/css-styling.md +0 -191
  47. package/docs/getting-started.md +0 -222
  48. package/docs/options.md +0 -326
  49. package/docs/recipes.md +0 -531
  50. package/docs/typescript.md +0 -615
  51. package/docs/vanilla.md +0 -12
  52. /package/{LICENSE → LICENSE.md} +0 -0
package/docs/api/index.md DELETED
@@ -1,55 +0,0 @@
1
- # API Reference
2
-
3
- Every public method and helper gets its own page. Use this index to navigate to the right one.
4
-
5
- ## Lifecycle
6
-
7
- - [`form(target, options?)`](form.md) — bind a controller to an `HTMLFormElement`.
8
- - [`createFormController(target, options?)`](form.md#createformcontroller) — underlying controller factory.
9
- - [`browser`](../browser.md) — registry-oriented browser API and global build.
10
- - [`element`](element.md) — the bound form (`f` is an alias).
11
- - [`addCleanup(cleanup)`](add-cleanup.md) — register controller-owned teardown work.
12
- - [`destroy()`](destroy.md) — tear down listeners, observer, and caches.
13
- - [`reset()`](reset.md) — restore native form values and clear errors.
14
-
15
- ## Submission
16
-
17
- - [`onSubmit(callback, preventDefault?)`](on-submit.md) — register a submit handler.
18
- - [`autoSubmit(options?)`](auto-submit.md) — enable submit-on-change with optional debounce.
19
- - [`disableAutoSubmit()`](disable-auto-submit.md) — turn auto-submit off and cancel pending debounce.
20
-
21
- ## Field observation
22
-
23
- - [`watch(field, callback)`](watch.md) — chainable fire-and-forget reaction to a field.
24
- - [`observe(field, callback)`](observe.md) — like `watch`, but returns an unsubscribe function and fires immediately.
25
- - [`unwatch(field?, callback?)`](unwatch.md) — remove watched callbacks.
26
- - [`subscribe(listener)`](subscribe.md) — react to the entire form state.
27
-
28
- ## Field values
29
-
30
- - [`setValue(name, value)`](set-value.md) — write a value into a field.
31
- - [`getValue(name)`](get-value.md) — read the current value of a field.
32
- - [`getField(name)`](get-field.md) — read the underlying DOM element(s).
33
- - [`prefill(fieldName?)`](prefill.md) — populate the form from `window.location.search`.
34
- - [`format(config)`](format.md) — apply `@samline/formatter` to a field, with auto-managed raw mirror and cursor tracking. The visible is renamed to `<field>_displayed` and a hidden `<field>` raw mirror is created. Both names are first-class in the controller's API.
35
- - [`formatAll(config)`](format.md) — alias of `format()` for `field: string[]` use cases.
36
-
37
- ## Validation
38
-
39
- - [`validate(fields?)`](validate.md) — run validation, return the result.
40
- - [`revalidate(fields?)`](revalidate.md) — alias of `validate` with explicit intent.
41
- - [`setErrors(fields)`](set-errors.md) — push manual errors.
42
- - [`clearErrors(fields?)`](clear-errors.md) — clear manual errors.
43
-
44
- ## State and data
45
-
46
- - [`getData()`](get-data.md) — return plain object + `FormData` for the form.
47
- - [`getState()`](get-state.md) — return a snapshot of values, errors, and metadata.
48
- - [`append(options)`](append.md) — inject a DOM node into the form.
49
-
50
- ## Pure helpers
51
-
52
- - [`parseFormData(formElement, submitter?)`](parse-form-data.md) — same serializer used internally, no controller needed.
53
- - [`validateValues(values, schema)`](validate-values.md) — run a schema against a values map.
54
- - [`validateFieldValue(field, value, rules, values)`](validate-field-value.md) — run a rule set against a single value.
55
- - [`regex`](regex.md) — named regular expressions and default error messages exported by the optional peer [`@samline/formatter`](https://github.com/samline/formatter), not by `@samline/forms`.
@@ -1,126 +0,0 @@
1
- # `observe(field, callback)`
2
-
3
- Registers a callback that fires immediately with the current value and on every subsequent change. Returns an unsubscribe function.
4
-
5
- ## Signature
6
-
7
- ```ts
8
- observe(
9
- field: string,
10
- callback: FormFieldWatcher
11
- ): () => void
12
- ```
13
-
14
- ## Parameters
15
-
16
- | Name | Type | Required | Description |
17
- | --- | --- | --- | --- |
18
- | `field` | `string` | yes | The `name` attribute of the field to observe. |
19
- | `callback` | [`FormFieldWatcher`](../typescript.md#formfieldwatcher) | yes | Invoked with `(value, field, form, state)`. Fires immediately with the current value, then on every change. `field` is the observed DOM element(s) (same shape as [`getField`](../typescript.md#formfieldelement--formfieldelement--null) returns). |
20
-
21
- ## Returns
22
-
23
- A function. Calling it removes the observer for this field.
24
-
25
- ## Behaviour
26
-
27
- - On registration, the callback is invoked once synchronously with the current value, the bound form, and a state snapshot.
28
- - Subsequent user `input` events and programmatic [`setValue`](set-value.md) calls invoke the callback again. DOM mutations alone do not fire field watchers.
29
- - A field can have multiple observers.
30
- - The unsubscribe function is idempotent — calling it more than once is safe.
31
-
32
- ## Examples
33
-
34
- ### Observe and unsubscribe later
35
-
36
- ```ts
37
- import { form } from '@samline/forms'
38
-
39
- const profile = form('profile-form')
40
-
41
- const stop = profile.observe('email', value => {
42
- console.log('email is now:', value)
43
- })
44
-
45
- // Later, when the form is unmounted:
46
- stop()
47
- ```
48
-
49
- ### Sync a derived model
50
-
51
- ```ts
52
- import { form } from '@samline/forms'
53
-
54
- const checkout = form('checkout-form')
55
-
56
- const unsubscribe = checkout.observe('shippingMethod', (value, field, _form, state) => {
57
- const total = computeTotal(state.values)
58
- document.querySelector('#total')!.textContent = String(total)
59
- if (field && 'name' in field) {
60
- console.log(`updated ${field.name} ->`, value)
61
- }
62
- })
63
-
64
- window.addEventListener('beforeunload', unsubscribe)
65
- ```
66
-
67
- ### Share a handler across fields
68
-
69
- ```ts
70
- import { form } from '@samline/forms'
71
-
72
- const signup = form('signup-form')
73
-
74
- const track = (value: unknown, field: HTMLInputElement | HTMLInputElement[] | null) => {
75
- const name = field && 'name' in field ? field.name : null
76
- analytics.track('signup_field_changed', { field: name, value })
77
- }
78
-
79
- const offEmail = signup.observe('email', track)
80
- const offPassword = signup.observe('password', track)
81
-
82
- // Later, on teardown:
83
- offEmail()
84
- offPassword()
85
- ```
86
-
87
- ### Manipulate the watched element directly
88
-
89
- ```ts
90
- import { form } from '@samline/forms'
91
-
92
- const profile = form('profile-form')
93
-
94
- const stop = profile.observe('avatar', (_value, field) => {
95
- if (field instanceof HTMLInputElement) {
96
- field.setAttribute('data-touched', 'true')
97
- }
98
- })
99
- ```
100
-
101
- ### Multiple observers on the same field
102
-
103
- ```ts
104
- import { form } from '@samline/forms'
105
-
106
- const signup = form('signup-form')
107
-
108
- const offA = signup.observe('password', value => updateStrength(value))
109
- const offB = signup.observe('password', value => updateCapsLockWarning(value))
110
-
111
- // Remove only the strength meter.
112
- offA()
113
- ```
114
-
115
- ## Edge cases
116
-
117
- - **The initial fire happens synchronously inside `observe`**. If you do not want this, use [`watch`](watch.md) instead.
118
- - **Observers persist across calls to [`reset`](reset.md) and [`setValue`](set-value.md)** — they are not affected by state changes.
119
- - **The unsubscribe function only removes this specific callback.** Other observers on the same field continue to fire.
120
-
121
- ## Related
122
-
123
- - [`watch`](watch.md) — chainable, no unsubscribe.
124
- - [`unwatch`](unwatch.md) — bulk removal of watchers.
125
- - [`subscribe`](subscribe.md) — global state observer.
126
- - [`format`](format.md) — when a field is formatted, `observe('<field>', cb)` fires with the raw value and `observe('<field>_displayed', cb)` fires with the formatted value. Both fire on the same keystroke. See [the mirror convention](format.md#the-mirror-convention).
@@ -1,94 +0,0 @@
1
- # `onSubmit(callback, preventDefault?)`
2
-
3
- Registers a handler that runs when the form is submitted and validation passes. Multiple handlers can be registered; each runs in registration order.
4
-
5
- ## Signature
6
-
7
- ```ts
8
- onSubmit(
9
- callback: FormSubmitHandler,
10
- preventDefault?: boolean
11
- ): FormController
12
- ```
13
-
14
- ## Parameters
15
-
16
- | Name | Type | Required | Default | Description |
17
- | --- | --- | --- | --- | --- |
18
- | `callback` | [`FormSubmitHandler`](../typescript.md#formsubmithandler) | yes | — | Handler invoked when the form is valid. Receives `(form, data, formData, state)`. |
19
- | `preventDefault` | `boolean` | no | `true` | Whether to intercept valid submissions. Invalid submissions are always intercepted. |
20
-
21
- ## Returns
22
-
23
- The same [`FormController`](../typescript.md#formcontroller) — chainable.
24
-
25
- ## Behaviour
26
-
27
- The submit pipeline runs in this order:
28
-
29
- 1. If `clearErrorsOnSubmit` is `true` (default), manual errors are cleared.
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 control receives focus (including the first failing `each` member), `submitCount` is incremented, and subscribers are notified.
32
- 4. If validation passes:
33
- - `submitCount` is incremented.
34
- - Subscribers are notified.
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
- - 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.
38
-
39
- ## Examples
40
-
41
- ### Default: intercept and send with `fetch`
42
-
43
- ```ts
44
- import { form } from '@samline/forms'
45
-
46
- const profile = form('profile-form')
47
-
48
- profile.onSubmit(async (_element, _data, formData) => {
49
- await fetch('/profile', { method: 'POST', body: formData })
50
- })
51
- ```
52
-
53
- ### Pass `false` to allow native submit
54
-
55
- ```ts
56
- import { form } from '@samline/forms'
57
-
58
- const bladeForm = form('blade-form', {
59
- validators: { email: { required: true } }
60
- })
61
-
62
- bladeForm.onSubmit(() => {
63
- console.log('client-side hooks ran')
64
- }, false)
65
- ```
66
-
67
- Use this when you want the browser to perform the native submit (Blade, ERB, classic Rails, etc.) but still run a client-side hook first.
68
-
69
- ### Multiple handlers
70
-
71
- ```ts
72
- profile
73
- .onSubmit((_form, data) => analytics.track('signup', data))
74
- .onSubmit(async (_form, _data, formData) => {
75
- await fetch('/signup', { method: 'POST', body: formData })
76
- })
77
- ```
78
-
79
- ## Edge cases
80
-
81
- - **Handlers receive a fresh `FormData`** built from the live form on each invocation. Do not cache it.
82
- - **`data` is a plain object mirror of `FormData`.** Repeated names become arrays (e.g. `interests: ['design', 'code']`).
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.
84
- - **`onSubmit` cannot be removed** — there is no `offSubmit` API. If you need conditional submission, guard inside the handler or recreate the controller.
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.
89
-
90
- ## Related
91
-
92
- - [`autoSubmit`](auto-submit.md) — submit on every change.
93
- - [`validate`](validate.md) — run validation without submitting.
94
- - [`getData`](get-data.md) — what the handler receives.
@@ -1,94 +0,0 @@
1
- # `parseFormData(formElement, submitter?)`
2
-
3
- Pure serializer. Returns a plain object mirror of the form plus a fresh `FormData` instance. Does not require a controller — useful for one-off serialization (e.g. server-side hydration, scripts that read forms outside of a binding).
4
-
5
- ## Signature
6
-
7
- ```ts
8
- function parseFormData(
9
- formElement: HTMLFormElement,
10
- submitter?: HTMLElement | null
11
- ): SerializedFormResult
12
- ```
13
-
14
- ```ts
15
- interface SerializedFormResult {
16
- data: Record<string, SerializedFormValue>
17
- formData: FormData
18
- }
19
-
20
- type SerializedFormValue = FormDataPrimitive | FormDataPrimitive[]
21
- type FormDataPrimitive = FormDataEntryValue // string | File
22
- ```
23
-
24
- ## Parameters
25
-
26
- | Name | Type | Required | Description |
27
- | --- | --- | --- | --- |
28
- | `formElement` | `HTMLFormElement` | yes | The form to serialize. |
29
- | `submitter` | `HTMLElement \| null` | no | Successful named submit button whose contribution should be included. |
30
-
31
- ## Returns
32
-
33
- A [`SerializedFormResult`](../typescript.md#serializedformresult) with two fresh values:
34
-
35
- - `data` — plain object mirror. Repeated names become arrays.
36
- - `formData` — a fresh `FormData` instance.
37
-
38
- ## Behaviour
39
-
40
- - Reads directly from the live DOM via `new FormData(formElement, submitter)` when a submitter is supplied.
41
- - Preserves fields named `constructor` or `__proto__` as ordinary own properties in `data`.
42
- - Drops entries where the value is an empty `File` (`size === 0` and `name === ''`).
43
- - Builds the `data` mirror while iterating, grouping repeated names into arrays.
44
- - Does not mutate the form or any global state.
45
-
46
- ## Examples
47
-
48
- ### One-off serialization
49
-
50
- ```ts
51
- import { parseFormData } from '@samline/forms'
52
-
53
- const formElement = document.querySelector<HTMLFormElement>('#contact-form')
54
- if (formElement) {
55
- const { data, formData } = parseFormData(formElement)
56
- console.log(data, formData)
57
- }
58
- ```
59
-
60
- ### Hydrate from a snapshot
61
-
62
- ```ts
63
- import { parseFormData } from '@samline/forms'
64
-
65
- const element = document.querySelector<HTMLFormElement>('#profile-form')
66
-
67
- // Snapshot on every input:
68
- element?.addEventListener('input', () => {
69
- if (!element) return
70
- const { data } = parseFormData(element)
71
- localStorage.setItem('draft', JSON.stringify(data))
72
- })
73
-
74
- // Restore on load:
75
- const saved = localStorage.getItem('draft')
76
- if (saved && element) {
77
- const data = JSON.parse(saved) as Record<string, string>
78
- for (const [key, value] of Object.entries(data)) {
79
- const field = element.querySelector<HTMLInputElement>(`[name="${key}"]`)
80
- if (field) field.value = value
81
- }
82
- }
83
- ```
84
-
85
- ## Edge cases
86
-
87
- - **Empty file inputs are filtered out.** This matches what [`getData()`](get-data.md) does inside a controller.
88
- - **`parseFormData` does not validate** — pass the result through [`validateValues`](validate-values.md) if you need validation.
89
- - **No type coercion.** All values come from `FormData` directly.
90
-
91
- ## Related
92
-
93
- - [`getData`](get-data.md) — controller-bound equivalent.
94
- - [`validateValues`](validate-values.md) — run validation against a `FormValues` map.
@@ -1,89 +0,0 @@
1
- # `prefill(fieldName?)`
2
-
3
- Populates the form (or a single field) from the current URL query string. Useful for sharing prefilled links, marketing tracking params, or restoring form state from `location.search`.
4
-
5
- ## Signature
6
-
7
- ```ts
8
- prefill(): FormController
9
- prefill(fieldName: string): FormController
10
- ```
11
-
12
- ## Parameters
13
-
14
- | Name | Type | Required | Description |
15
- | --- | --- | --- | --- |
16
- | `fieldName` | `string` | no | When provided, only this field is prefilled. When omitted, every query parameter is matched. |
17
-
18
- ## Returns
19
-
20
- The same [`FormController`](../typescript.md#formcontroller) — chainable.
21
-
22
- ## Behaviour
23
-
24
- - Reads `window.location.search` once with `URLSearchParams`.
25
- - For each entry, calls [`setValue`](set-value.md) with the key/value pair.
26
- - When `fieldName` is provided, only the matching key is written.
27
- - Values are always strings. If a field expects a different shape (e.g. `number`), the consumer must convert.
28
-
29
- Because the method delegates to [`setValue`](set-value.md), each prefilled field:
30
-
31
- - Receives a synthetic `input` event, so watchers and subscribers fire.
32
- - Triggers validation if it has rules and `autoValidate` is enabled.
33
- - Triggers autoSubmit if it is enabled.
34
- - Updates the `css-filled` / `css-error` attributes.
35
-
36
- ## Examples
37
-
38
- ### Prefill the whole form
39
-
40
- ```ts
41
- import { form } from '@samline/forms'
42
-
43
- // URL: https://example.com/contact?utm_source=newsletter&email=hi@example.com
44
- const contact = form('contact-form')
45
- contact.prefill()
46
-
47
- contact.getValue('email') // 'hi@example.com'
48
- contact.getValue('utm_source') // 'newsletter'
49
- ```
50
-
51
- ### Prefill a single field
52
-
53
- ```ts
54
- const contact = form('contact-form')
55
- contact.prefill('email')
56
- ```
57
-
58
- ### Round-trip a draft through the URL
59
-
60
- ```ts
61
- import { form } from '@samline/forms'
62
-
63
- const draft = form('draft-form')
64
-
65
- // User types into fields, watchers debounce-serialize and push to the URL:
66
- draft.subscribe(state => {
67
- const params = new URLSearchParams()
68
- for (const [key, value] of Object.entries(state.values)) {
69
- if (typeof value === 'string') params.set(key, value)
70
- }
71
- history.replaceState(null, '', `?${params.toString()}`)
72
- })
73
-
74
- // On next mount:
75
- draft.prefill()
76
- ```
77
-
78
- ## Edge cases
79
-
80
- - **`window` must exist.** The browser bundle uses `window.location`; in non-DOM environments `prefill` is a no-op.
81
- - **Values are always strings.** Prefilling a `<select>` requires the query value to match one of the option values exactly.
82
- - **Repeated query keys** (`?tag=a&tag=b`) are processed in iteration order. Only the last value wins because `URLSearchParams.forEach` visits both entries; the second `setValue` call overwrites the first.
83
- - **Prefill runs synchronously.** Call it after the form is in the DOM.
84
-
85
- ## Related
86
-
87
- - [`setValue`](set-value.md) — underlying primitive.
88
- - [`getValue`](get-value.md) — read the prefilled values.
89
- - [`format`](format.md) — when a field is formatted, prefilling the canonical name writes the raw and reformats the visible; prefilling the display name writes the formatted and recomputes the raw. See [the mirror convention](format.md#the-mirror-convention).
package/docs/api/regex.md DELETED
@@ -1,155 +0,0 @@
1
- # `regex`
2
-
3
- > **Heads up — this is a peer dependency.** `regex` is exported by [`@samline/formatter`](https://github.com/samline/formatter), **not** by `@samline/forms`. It only exists when `@samline/formatter` is installed in your project. The forms package does not bundle it, ship it, or declare it as a hard dependency — it is an optional peer. Importing it without installing the peer will throw a module-not-found error at build / runtime, and the page below is only meaningful when the peer is on disk.
4
-
5
- A named dictionary of common regular expressions and their default error messages. Use it to feed the [`pattern`](../options.md#validators) rule of any field validator without hand-rolling a `RegExp` and a string for every form.
6
-
7
- ## Import
8
-
9
- ```ts
10
- import { regex } from '@samline/formatter'
11
- ```
12
-
13
- `@samline/formatter` is listed as an optional peer in `package.json`. If you already have it installed for the `format()` / `formatAll()` masks, the import works out of the box. If you do not, install it with:
14
-
15
- ```bash
16
- npm install @samline/formatter
17
- ```
18
-
19
- ```bash
20
- pnpm add @samline/formatter
21
- ```
22
-
23
- ```bash
24
- bun add @samline/formatter
25
- ```
26
-
27
- ## Signature
28
-
29
- ```ts
30
- const regex: Record<string, { pattern: RegExp; errorMessage: string }>
31
- ```
32
-
33
- Each entry is keyed by a short name (`'email'`, `'url'`, `'phone'`, …) and exposes:
34
-
35
- | Field | Type | Description |
36
- | --- | --- | --- |
37
- | `pattern` | `RegExp` | The expression to test the value against. |
38
- | `errorMessage` | `string` | A human-readable message describing the failure. Use it as the default `message` for the `pattern` rule. |
39
-
40
- The exact key set lives in the `@samline/formatter` package and can grow over time — refer to its [options reference](https://github.com/samline/formatter/blob/main/docs/options.md) for the full list. Common entries include `email`, `url`, `phone`, and `slug`.
41
-
42
- ## Parameters
43
-
44
- None — `regex` is a plain object, not a function.
45
-
46
- ## Returns
47
-
48
- A plain `Record<string, { pattern: RegExp; errorMessage: string }>` that you can index by name.
49
-
50
- ## Behaviour
51
-
52
- `regex` is purely declarative data. The forms package does not call it for you — you opt in by passing `regex.<key>.pattern` (and optionally `regex.<key>.errorMessage`) into a field's `validators` config. The controller's built-in `pattern` rule then runs the expression and pushes the message when the value does not match.
53
-
54
- `regex` works the same whether you read it directly, destructure the entries you need, or merge it into a project-wide constant. The patterns are owned by `@samline/formatter`; treat the object as read-only.
55
-
56
- ## Examples
57
-
58
- ### Validate an email with the built-in pattern
59
-
60
- ```ts
61
- import { form } from '@samline/forms'
62
- import { regex } from '@samline/formatter'
63
-
64
- const contact = form('contact-form', {
65
- validators: {
66
- email: {
67
- required: true,
68
- pattern: regex.email.pattern
69
- }
70
- }
71
- })
72
- ```
73
-
74
- This is the same `pattern` you'd write by hand (`/^[^\s@]+@[^\s@]+\.[^\s@]+$/`); the only difference is that it ships with the formatter peer.
75
-
76
- ### Pair the pattern with the default error message
77
-
78
- ```ts
79
- import { form } from '@samline/forms'
80
- import { regex } from '@samline/formatter'
81
-
82
- const signup = form('signup-form', {
83
- validators: {
84
- email: {
85
- required: true,
86
- pattern: {
87
- value: regex.email.pattern,
88
- message: regex.email.errorMessage
89
- }
90
- }
91
- }
92
- })
93
- ```
94
-
95
- When the value fails the pattern, the controller pushes `regex.email.errorMessage` into `state.errors.email` and applies the `css-error` attribute to the field — see [Validation](../options.md#validators) and [CSS styling](../css-styling.md).
96
-
97
- ### Use it without a controller
98
-
99
- The patterns and messages are plain values, so they work the same in any validation pipeline:
100
-
101
- ```ts
102
- import { regex } from '@samline/formatter'
103
- import { validateFieldValue } from '@samline/forms'
104
-
105
- const errors = validateFieldValue(
106
- 'email',
107
- 'not-an-email',
108
- { pattern: regex.email.pattern },
109
- {}
110
- )
111
-
112
- // errors = ['<default pattern message, sourced from @samline/formatter>']
113
- ```
114
-
115
- See [`validateFieldValue`](validate-field-value.md) for the pure helper signature.
116
-
117
- ### Spread `regex` into a browser global alongside `browser`
118
-
119
- The vanilla entrypoint exposes a `browser` singleton with `form`, `newForm`, `destroyForm`, and `available`. The most common pattern is to spread it together with `regex` into a single project global — see [Browser registry helpers](../getting-started.md#browser-registry-helpers) in the getting-started guide:
120
-
121
- ```ts
122
- import { browser } from '@samline/forms'
123
- import { regex } from '@samline/formatter' // optional, from your project
124
-
125
- window.Form = { ...browser, regex }
126
-
127
- window.Form.newForm({
128
- id: 'contact-form',
129
- options: {
130
- validators: {
131
- email: {
132
- required: true,
133
- pattern: regex.email.pattern
134
- }
135
- }
136
- }
137
- })
138
- ```
139
-
140
- The same shape is available from the IIFE bundle under `window.Forms`; the spread pattern works the same way when the project loads `@samline/forms/browser` instead.
141
-
142
- ## Edge cases
143
-
144
- - **Missing peer.** `import { regex } from '@samline/formatter'` throws a module-not-found error when the peer is not installed. `@samline/forms` does not catch this — install the peer before you import the symbol.
145
- - **No types are shipped by `@samline/forms` for `regex`.** The shim in [`src/types/formatter.d.ts`](../../src/types/formatter.d.ts) declares the type as `Record<string, { pattern: RegExp; errorMessage: string }>` for projects that do not yet have the peer on disk, but the real types come from the installed peer at compile time.
146
- - **`regex` is not enumerated at runtime.** The key set lives in `@samline/formatter`; iterate with `Object.keys(regex)` if you need a dynamic listing.
147
- - **The controller never reads `regex` for you.** You opt in per field via the `pattern` rule. There is no global switch that turns on `regex.email` for every `email` field.
148
- - **Browser global key collision.** Spreading `{ ...browser, regex }` into `window.Form` is the documented pattern; assigning `window.Forms.regex` directly is fine too — the registry is shared across spreads.
149
-
150
- ## Related
151
-
152
- - [`validators`](../options.md#validators) — the option that consumes `regex.<key>.pattern`.
153
- - [`validateFieldValue`](validate-field-value.md) — the pure helper that runs the `pattern` rule.
154
- - [`format`](format.md) — also depends on `@samline/formatter` being installed.
155
- - [`@samline/formatter` options reference](https://github.com/samline/formatter/blob/main/docs/options.md) — the canonical list of `regex` keys.
package/docs/api/reset.md DELETED
@@ -1,61 +0,0 @@
1
- # `reset()`
2
-
3
- Restores the form to its initial state: native field values reset, manual and validation errors cleared, visual attributes synchronized, subscribers notified.
4
-
5
- ## Signature
6
-
7
- ```ts
8
- reset(): FormController
9
- ```
10
-
11
- ## Returns
12
-
13
- The same [`FormController`](../typescript.md#formcontroller) — chainable.
14
-
15
- ## Behaviour
16
-
17
- When invoked, the controller:
18
-
19
- 1. Calls `state.element.reset()` (the native form reset).
20
- 2. Clears `state.manualErrors` and `state.validationErrors`.
21
- 3. Restores matching visible/raw defaults for formatted fields.
22
- 4. Removes every `css-filled`, `css-error`, and `aria-invalid` attribute from associated fields.
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
- 6. Notifies subscribers.
25
-
26
- ## Examples
27
-
28
- ### Reset after a successful submit
29
-
30
- ```ts
31
- import { form } from '@samline/forms'
32
-
33
- const profile = form('profile-form')
34
-
35
- profile.onSubmit(async (_element, _data, formData) => {
36
- await fetch('/profile', { method: 'POST', body: formData })
37
- profile.reset()
38
- })
39
- ```
40
-
41
- ### Reset on cancel
42
-
43
- ```ts
44
- const signup = form('signup-form')
45
-
46
- document.querySelector('#cancel')?.addEventListener('click', () => {
47
- signup.reset()
48
- })
49
- ```
50
-
51
- ## Edge cases
52
-
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
- - **Submit handlers are not invoked.** `reset()` is purely a UI / state operation.
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.
57
-
58
- ## Related
59
-
60
- - [`destroy`](destroy.md) — full teardown.
61
- - [`clearErrors`](clear-errors.md) — clear errors without resetting field values.