@samline/forms 2.8.0 → 2.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,32 @@ All notable changes to `@samline/forms` are documented in this file. The format
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [2.10.0] - 2026-11-12
8
+
9
+ No breaking API changes.
10
+
11
+ ### Added
12
+
13
+ - Support for `@samline/formatter` 3.0.0. The optional peer now accepts `^2.1.0 || ^3.0.0`. Formatter 3 makes phones opt-in, so forms loads `@samline/formatter/phone` and calls `registerPhone()` the first time a field is formatted with `type: 'phone'`. Forms without phone fields never load libphonenumber-js, and bundlers put the phone subpath in its own lazy chunk. If the subpath cannot be loaded, forms logs one `console.error` and leaves the field unformatted.
14
+ - The standalone script bundles formatter 3.0.0, with phone support included.
15
+
16
+ ### Fixed
17
+
18
+ - A submit the user starts while a debounced auto-submit is pending cancels the pending one. Before, the form was submitted twice.
19
+
20
+ ## [2.9.0] - 2026-11-12
21
+
22
+ No breaking API changes.
23
+
24
+ ### Added
25
+
26
+ - `setOptions(name, options, config?)` replaces the options of a `<select>` with strings, numbers, `{ value, label, disabled, selected }` objects or `{ label, options }` groups. Labels are written as text. The current value is kept when it is still listed (`keepValue: true` by default), and the change pipeline runs once, only when the selected value changes ([#13](https://github.com/samline/forms/issues/13)). New exported types: `SelectOption`, `SelectOptionGroup`, `SelectOptionInput` and `SetOptionsConfig`.
27
+ - `watch()` and `observe()` accept an array of field names. The callback receives `(values, changed, form, state)`: one value per listed field and the name of the field that changed (`null` on registration and after `reset()`). It fires once per change of any listed field. `unwatch(fields, callback?)` removes these watchers. New exported type: `FormFieldsWatcher` ([#10](https://github.com/samline/forms/issues/10)).
28
+
29
+ ### Fixed
30
+
31
+ - Adding or removing `<option>` elements outside the controller is no longer ignored. When it changes a select's value, the controller revalidates, updates `css-filled` / `css-error`, fires watchers and notifies subscribers.
32
+
7
33
  ## [2.8.0] - 2026-11-06
8
34
 
9
35
  No breaking API changes. The behavior changes below fix incorrect or surprising results; review them if your code relied on the old behavior.
@@ -277,7 +303,9 @@ No breaking API changes. The behavior changes below fix incorrect or surprising
277
303
 
278
304
  - Initial release of `@samline/forms`. Vanilla JS form controller with framework-specific variants (React, Vue, Svelte) initially included; bundled `.local/` agent docs at the repo root.
279
305
 
280
- [Unreleased]: https://github.com/samline/forms/compare/v2.8.0...HEAD
306
+ [Unreleased]: https://github.com/samline/forms/compare/v2.10.0...HEAD
307
+ [2.10.0]: https://github.com/samline/forms/compare/v2.9.0...v2.10.0
308
+ [2.9.0]: https://github.com/samline/forms/compare/v2.8.0...v2.9.0
281
309
  [2.8.0]: https://github.com/samline/forms/compare/v2.7.2...v2.8.0
282
310
  [2.7.2]: https://github.com/samline/forms/compare/v2.7.1...v2.7.2
283
311
  [2.7.1]: https://github.com/samline/forms/compare/v2.7.0...v2.7.1
package/README.md CHANGED
@@ -30,7 +30,7 @@ pnpm add @samline/forms
30
30
  bun add @samline/forms
31
31
  ```
32
32
 
33
- Requires Node 20+ when bundling; the runtime targets ES2020 browsers. Formatting needs the optional peer `@samline/formatter` (`npm install @samline/formatter`); validation, state and serialization do not.
33
+ Requires Node 20+ when bundling; the runtime targets ES2020 browsers. Formatting needs the optional peer `@samline/formatter` (`npm install @samline/formatter`); validation, state and serialization do not. No runtime stylesheet is required; the application owns form styling.
34
34
 
35
35
  ## Quick start
36
36
 
@@ -78,7 +78,7 @@ Invalid submits are always prevented and focus moves to the first invalid field.
78
78
  The standalone build defines `window.Forms` and already includes `@samline/formatter`. Pin the version.
79
79
 
80
80
  ```html
81
- <script src="https://unpkg.com/@samline/forms@2.8.0/dist/browser/global.global.js"></script>
81
+ <script src="https://unpkg.com/@samline/forms@2.10.0/dist/browser/global.global.js"></script>
82
82
  <script>
83
83
  const signup = window.Forms.form('signup', {
84
84
  validators: { email: { required: true, pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/ } },
@@ -104,27 +104,35 @@ One controller owns one form's listeners, `MutationObserver` and auto-submit tim
104
104
 
105
105
  ## API at a glance
106
106
 
107
- | Export | Purpose |
108
- | ------------------------------------------ | ------------------------------------------------------------------- |
109
- | `form(target, options?)` | Create a controller for an id, `HTMLFormElement` or `{ current }`. |
110
- | `createFormController(target, options?)` | The same factory under its descriptive name. |
111
- | `browser` | `{ form, newForm, destroyForm, available }` registry singleton. |
112
- | `parseFormData(form, submitter?)` | Serialize a form into `{ data, formData }`. |
113
- | `validateValues(values, schema)` | Validate a plain values object against a `ValidationSchema`. |
114
- | `validateFieldValue(field, value, rules, values)` | Validate one value. |
115
- | `@samline/forms/browser` | The registry as a module namespace; also sets `globalThis.Forms`. |
116
- | `@samline/forms/browser/global` | The standalone IIFE that defines `window.Forms`. |
107
+ | API | Purpose |
108
+ | ------------------------------------------------- | ------------------------------------------------------------------ |
109
+ | `form(target, options?)` | Create a controller for an id, `HTMLFormElement` or `{ current }`. |
110
+ | `createFormController(target, options?)` | The same factory under its descriptive name. |
111
+ | `browser` | `{ form, newForm, destroyForm, available }` registry singleton. |
112
+ | `parseFormData(form, submitter?)` | Serialize a form into `{ data, formData }`. |
113
+ | `validateValues(values, schema)` | Validate a plain values object against a `ValidationSchema`. |
114
+ | `validateFieldValue(field, value, rules, values)` | Validate one value. |
115
+ | `@samline/forms/browser` | The registry as a module namespace; also sets `globalThis.Forms`. |
116
+ | `@samline/forms/browser/global` | The standalone IIFE that defines `window.Forms`. |
117
+
118
+ ### Entrypoints
119
+
120
+ | Entrypoint | When to use |
121
+ | ------------------------------- | ---------------------------------------------------------------------------------------- |
122
+ | `@samline/forms` | Controller factory, pure validation/serialization helpers and types for ESM or CommonJS. |
123
+ | `@samline/forms/browser` | Module registry that also installs `globalThis.Forms`. |
124
+ | `@samline/forms/browser/global` | Standalone script with `window.Forms` and the bundled formatter. |
117
125
 
118
126
  ### Controller methods
119
127
 
120
- | Group | Methods |
121
- | ------------- | ---------------------------------------------------------------------------------- |
122
- | Submission | `onSubmit`, `autoSubmit`, `disableAutoSubmit` |
123
- | Validation | `validate`, `revalidate`, `setErrors`, `clearErrors` |
124
- | State | `subscribe`, `observe`, `watch`, `unwatch`, `getState` |
125
- | Values | `setValue`, `getValue`, `getField`, `getData`, `prefill`, `reset` |
126
- | Formatting | `format`, `formatAll` |
127
- | Lifecycle | `element`, `f`, `options`, `append`, `addCleanup`, `destroy` |
128
+ | Group | Methods |
129
+ | ---------- | ------------------------------------------------------------------------------- |
130
+ | Submission | `onSubmit`, `autoSubmit`, `disableAutoSubmit` |
131
+ | Validation | `validate`, `revalidate`, `setErrors`, `clearErrors` |
132
+ | State | `subscribe`, `observe`, `watch`, `unwatch`, `getState` |
133
+ | Values | `setValue`, `setOptions`, `getValue`, `getField`, `getData`, `prefill`, `reset` |
134
+ | Formatting | `format`, `formatAll` |
135
+ | Lifecycle | `element`, `f`, `options`, `append`, `addCleanup`, `destroy` |
128
136
 
129
137
  ### Options
130
138
 
@@ -132,19 +140,21 @@ One controller owns one form's listeners, `MutationObserver` and auto-submit tim
132
140
 
133
141
  ## Documentation
134
142
 
135
- | Guide | What it covers |
136
- | ------------------------------------------------------------------------------------------ | -------------------------------------------------------------------- |
137
- | [Get started](https://samline.github.io/forms/docs/getting-started/) | Installation, the first form, CDN use and cleanup. |
138
- | [Examples](https://samline.github.io/forms/docs/guides/examples/) | Twelve live forms with their code. |
139
- | [Validation and errors](https://samline.github.io/forms/docs/guides/validation/) | Every rule, cross-field checks and accessible messages. |
140
- | [State and submission](https://samline.github.io/forms/docs/guides/state-and-submission/) | Subscribers, async submits, auto-submit, prefill and reset. |
141
- | [Formatting fields](https://samline.github.io/forms/docs/guides/formatting/) | The optional formatter and the hidden raw mirror. |
142
- | [Framework integrations](https://samline.github.io/forms/docs/guides/frameworks/) | React, Vue, Svelte, Astro, Shopify and WordPress. |
143
- | [API reference](https://samline.github.io/forms/docs/reference/api/) | Every method, grouped by task, with one page per method. |
143
+ Read the [documentation](https://samline.github.io/forms/docs/) for the full guides and API contracts, and explore [working examples](https://samline.github.io/forms/examples/) with complete and separate source files for both loading modes.
144
+
145
+ | Guide | What it covers |
146
+ | ----------------------------------------------------------------------------------------- | ----------------------------------------------------------- |
147
+ | [Get started](https://samline.github.io/forms/docs/getting-started/) | Installation, the first form, CDN use and cleanup. |
148
+ | [Examples](https://samline.github.io/forms/docs/guides/examples/) | Twelve live forms with their code. |
149
+ | [Validation and errors](https://samline.github.io/forms/docs/guides/validation/) | Every rule, cross-field checks and accessible messages. |
150
+ | [State and submission](https://samline.github.io/forms/docs/guides/state-and-submission/) | Subscribers, async submits, auto-submit, prefill and reset. |
151
+ | [Formatting fields](https://samline.github.io/forms/docs/guides/formatting/) | The optional formatter and the hidden raw mirror. |
152
+ | [Framework integrations](https://samline.github.io/forms/docs/guides/frameworks/) | React, Vue, Svelte, Astro, Shopify and WordPress. |
153
+ | [API reference](https://samline.github.io/forms/docs/reference/api/) | Every method, grouped by task, with one page per method. |
144
154
 
145
155
  ## Changelog
146
156
 
147
- Read [CHANGELOG.md](CHANGELOG.md) or the [published changelog](https://samline.github.io/forms/docs/changelog/) for release history and explicitly marked behavior changes.
157
+ Read [CHANGELOG.md](CHANGELOG.md) or the [published changelog](https://samline.github.io/forms/docs/changelog/) for release history and explicitly marked breaking and behavior changes. Review the notes before upgrading.
148
158
 
149
159
  ### Upgrading to 2.8
150
160
 
@@ -156,6 +166,8 @@ Read [CHANGELOG.md](CHANGELOG.md) or the [published changelog](https://samline.g
156
166
 
157
167
  The [Forms skill](https://samline.github.io/forms/skills/forms/SKILL.md) describes 2.8 integration: loading modes, validation rules and accessible messages, state and submission, formatting and host ownership. Pass it to an agent or install it using a skills-compatible tool. Verify the installed package version before applying its guidance.
158
168
 
169
+ The maintained [repository source](skills/forms/SKILL.md) and website download are the same file.
170
+
159
171
  Download [SKILL.md](https://samline.github.io/forms/skills/forms/SKILL.md) into a `forms/` folder in your agent's supported skill directory. For agents that discover project skills from `.agents/skills/`:
160
172
 
161
173
  ```sh
@@ -168,4 +180,4 @@ When working from this repository, copy `skills/forms/SKILL.md` instead. Keep th
168
180
 
169
181
  ## License
170
182
 
171
- MIT — [LICENSE.md](LICENSE.md)
183
+ MIT — [LICENSE.md](LICENSE.md).
@@ -106,7 +106,38 @@ interface AppendContentOptions {
106
106
  class?: string;
107
107
  atStart?: boolean;
108
108
  }
109
+ /** One `<option>` written by `setOptions()`. */
110
+ interface SelectOption {
111
+ value: string | number;
112
+ /** Visible text. Defaults to `String(value)`. Written as text, never HTML. */
113
+ label?: string;
114
+ disabled?: boolean;
115
+ /** Selects the option and makes it the default restored by `reset()`. */
116
+ selected?: boolean;
117
+ }
118
+ /** One `<optgroup>` written by `setOptions()`. */
119
+ interface SelectOptionGroup {
120
+ label: string;
121
+ options: Array<string | number | SelectOption>;
122
+ disabled?: boolean;
123
+ }
124
+ /** A plain string or number is shorthand for `{ value, label: String(value) }`. */
125
+ type SelectOptionInput = string | number | SelectOption | SelectOptionGroup;
126
+ interface SetOptionsConfig {
127
+ /**
128
+ * Keep the current selection when its value is still in the new list.
129
+ * Ignored for a select when any new option has `selected: true`.
130
+ * Defaults to `true`.
131
+ */
132
+ keepValue?: boolean;
133
+ }
109
134
  type FormFieldWatcher = (value: FormFieldValue, field: FormFieldElement | FormFieldElement[] | null, form: HTMLFormElement, state: FormStateSnapshot) => void;
135
+ /**
136
+ * Callback for `watch()` / `observe()` with a list of fields. `values` has
137
+ * one entry per watched field. `changed` is the field that triggered the
138
+ * call, or `null` on registration and after `reset()`.
139
+ */
140
+ type FormFieldsWatcher = (values: FormValues, changed: string | null, form: HTMLFormElement, state: FormStateSnapshot) => void;
110
141
  type FormSubmitHandler = (form: HTMLFormElement, data: Record<string, SerializedFormValue>, formData: FormData, state: FormStateSnapshot) => void | Promise<void>;
111
142
  type FormCleanup = () => void;
112
143
  interface FormController {
@@ -115,15 +146,30 @@ interface FormController {
115
146
  readonly options: FormControllerOptions;
116
147
  onSubmit: (callback: FormSubmitHandler, preventDefault?: boolean) => FormController;
117
148
  addCleanup: (cleanup: FormCleanup) => () => void;
118
- watch: (field: string, callback: FormFieldWatcher) => FormController;
119
- observe: (field: string, callback: FormFieldWatcher) => () => void;
120
- unwatch: (field?: string, callback?: FormFieldWatcher) => FormController;
149
+ watch: {
150
+ (field: string, callback: FormFieldWatcher): FormController;
151
+ (fields: string[], callback: FormFieldsWatcher): FormController;
152
+ };
153
+ observe: {
154
+ (field: string, callback: FormFieldWatcher): () => void;
155
+ (fields: string[], callback: FormFieldsWatcher): () => void;
156
+ };
157
+ unwatch: {
158
+ (field?: string, callback?: FormFieldWatcher): FormController;
159
+ (fields: string[], callback?: FormFieldsWatcher): FormController;
160
+ };
121
161
  subscribe: (listener: FormStateListener) => () => void;
122
162
  prefill: (fieldName?: string) => FormController;
123
163
  append: (options: AppendContentOptions) => HTMLElement | null;
124
164
  setErrors: (fields: string[] | FormErrors) => FormController;
125
165
  clearErrors: (fields?: string[]) => FormController;
126
166
  setValue: (name: string, value: unknown) => FormController;
167
+ /**
168
+ * Replace the `<option>` / `<optgroup>` children of every `<select>`
169
+ * named `name`. Fires the normal change pipeline only when the
170
+ * selected value changes. Chainable.
171
+ */
172
+ setOptions: (name: string, options: SelectOptionInput[], config?: SetOptionsConfig) => FormController;
127
173
  validate: (fields?: string[]) => ValidationResult;
128
174
  revalidate: (fields?: string[]) => ValidationResult;
129
175
  /**