html-form-field 0.1.0 → 0.3.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/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2025 Yusuke Kawasaki
3
+ Copyright (c) 2025-2026 Yusuke Kawasaki
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -1,16 +1,16 @@
1
1
  # html-form-field
2
2
 
3
- Unified interface for HTML form fields with synchronized binding to object properties
3
+ Unified interface for HTML form fields with two-way binding to object properties.
4
4
 
5
- [![Node.js CI](https://github.com/kawanet/html-form-field/workflows/Node.js%20CI/badge.svg)](https://github.com/kawanet/html-form-field/actions/)
5
+ [![Node.js CI](https://github.com/kawanet/html-form-field/actions/workflows/nodejs.yml/badge.svg?branch=main)](https://github.com/kawanet/html-form-field/actions/)
6
6
  [![npm version](https://img.shields.io/npm/v/html-form-field)](https://www.npmjs.com/package/html-form-field)
7
7
  [![gzip size](https://img.badgesize.io/https://cdn.jsdelivr.net/npm/html-form-field/dist/html-form-field.min.js?compression=gzip)](https://cdn.jsdelivr.net/npm/html-form-field/dist/html-form-field.min.js)
8
8
 
9
- - Unified getter/setter for text inputs, checkboxes, radio buttons, and select elements
10
- - Two-way binding between form fields and object properties (synchronized updates in both directions)
11
- - Built-in change detection with `onChange` and `onWrite` callbacks
12
- - Small browser build: [html-form-field.min.js](https://cdn.jsdelivr.net/npm/html-form-field/dist/html-form-field.min.js) under 4KB minified, under 2KB gzipped
13
- - Full TypeScript support - [html-form-field.d.ts](https://github.com/kawanet/html-form-field/blob/main/types/html-form-field.d.ts) for detailed specifications
9
+ - A single getter/setter API for text inputs, checkboxes, radio buttons, and `<select>` elements.
10
+ - Two-way binding between a form field and an object property: assignments flow in either direction.
11
+ - Change-detection hooks via `onWrite` (any value write) and `onChange` (user-driven change events).
12
+ - Tiny browser build [html-form-field.min.js](https://cdn.jsdelivr.net/npm/html-form-field/dist/html-form-field.min.js) is under 4 KB minified and under 2 KB gzipped.
13
+ - First-class TypeScript types see [html-form-field.d.ts](https://github.com/kawanet/html-form-field/blob/main/types/html-form-field.d.ts) for the full surface.
14
14
 
15
15
  ## SYNOPSIS
16
16
 
@@ -29,15 +29,14 @@ const ctx = {} as Context
29
29
 
30
30
  formField({form, bindTo: ctx, name: "nickname"})
31
31
 
32
- console.log(ctx.nickname) // reads from form field
32
+ console.log(ctx.nickname) // reads from the form field
33
33
 
34
- ctx.nickname = "John" // updates form field
34
+ ctx.nickname = "John" // writes back to the form field
35
35
  ```
36
36
 
37
37
  #### HTML Example
38
38
 
39
39
  ```html
40
-
41
40
  <form>
42
41
  <ul>
43
42
  <li>Nickname: <input type="text" name="nickname" value="Alice"></li>
@@ -56,9 +55,9 @@ ctx.nickname = "John" // updates form field
56
55
  ```js
57
56
  const email = formField({form, name: "email"})
58
57
 
59
- console.log(email.value) // current value
58
+ console.log(email.value) // read the current value
60
59
 
61
- email.value = "john@example.com" // update value
60
+ email.value = "john@example.com" // assign a new value
62
61
  ```
63
62
 
64
63
  #### Multiple Selections
@@ -66,19 +65,17 @@ email.value = "john@example.com" // update value
66
65
  ```js
67
66
  const favo = formField({form, name: "favo", delim: ","})
68
67
 
69
- favo.toggle("tech") // toggle checkbox
70
-
71
- favo.toggle("travel", true)
68
+ favo.toggle("tech") // toggle one checkbox
69
+ favo.toggle("travel", true) // force-check
70
+ favo.toggle("trading", false) // force-uncheck
72
71
 
73
- favo.toggle("trading", false)
72
+ console.log(favo.has("travel")) // is this option currently selected?
74
73
 
75
- console.log(favo.has("travel")) // check if selected
76
-
77
- // Shortcut to item by index. Equivalent to items().at(index))
74
+ // Shortcut for items().at(index)
78
75
  const firstItem = favo.itemAt(0)
79
76
  console.log(firstItem.checked)
80
77
 
81
- // Shortcut to item by value. Equivalent to items().find(v => v.value === value)
78
+ // Shortcut for items().find(v => v.value === value)
82
79
  const travelItem = favo.itemOf("travel")
83
80
  console.log(travelItem.checked)
84
81
  ```
@@ -96,7 +93,29 @@ formField({
96
93
  })
97
94
  ```
98
95
 
99
- ## LINKS
96
+ ## SEE ALSO
100
97
 
101
98
  - https://www.npmjs.com/package/html-form-field
102
99
  - https://github.com/kawanet/html-form-field
100
+
101
+ ## MIT LICENSE
102
+
103
+ Copyright (c) 2025-2026 Yusuke Kawasaki
104
+
105
+ Permission is hereby granted, free of charge, to any person obtaining a copy
106
+ of this software and associated documentation files (the "Software"), to deal
107
+ in the Software without restriction, including without limitation the rights
108
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
109
+ copies of the Software, and to permit persons to whom the Software is
110
+ furnished to do so, subject to the following conditions:
111
+
112
+ The above copyright notice and this permission notice shall be included in all
113
+ copies or substantial portions of the Software.
114
+
115
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
116
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
117
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
118
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
119
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
120
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
121
+ SOFTWARE.
@@ -0,0 +1,2 @@
1
+ /* globals formField */
2
+ exports.formField = formField;
@@ -0,0 +1 @@
1
+ {"type":"commonjs"}
@@ -236,16 +236,12 @@ class FormBridgeImpl {
236
236
 
237
237
  const nodeList = getNodeList(options);
238
238
  updateEventListener(nodeList, _onChange);
239
- const items = this._items = formItemList(this, nodeList);
239
+ this._items = formItemList(this, nodeList);
240
240
 
241
241
  const defaults = options.defaults;
242
242
  if (defaults) {
243
- if (items[0].checkable) {
244
- for (const v of defaults) {
245
- if (this.setValue(v)) break
246
- }
247
- } else {
248
- this.setValue(defaults);
243
+ for (const v of defaults) {
244
+ if (this.setValue(v)) break
249
245
  }
250
246
  }
251
247
  }
@@ -1 +1 @@
1
- var formField=function(e){"use strict";const t=e=>{const t=e.options.onWrite;t&&t(e)},s={option:!0,radio:!0,checkbox:!0},n=(e,t)=>e.tagName===t,i=(e,t)=>{const s=[];for(const i of t)if(n(i,"SELECT"))for(const t of i.options)s.push(new c(e,t));else s.push(new r(e,i));return s};class o{constructor(e,t){this.field=e,this.node=t}get value(){return this.node.value}set value(e){this.setValue(e),t(this.field)}setValue(e){this.node.value=e}get checked(){return this.getChecked()}set checked(e){this.checked!==e&&(this.setChecked(e),t(this.field))}get disabled(){return this.node.disabled}set disabled(e){this.node.disabled=e}}class r extends o{constructor(e,t){super(e,t),this.checkable=s[t.type]}getChecked(){return this.node.checked}setChecked(e){this.node.checked=e}get label(){const e=this.node,t=e.closest("label"),s=t&&t.querySelectorAll(e.tagName);if(s&&1===s.length){const e=o(t);if(e)return e}const n=e.labels;for(const e of n){const t=o(e);if(t)return t}const i=o(e);if(i)return i;function o(e){const t=e.innerText||e.textContent;if(t)return t.trim()}}}class c extends o{constructor(e,t){super(e,t),this.checkable=!0}getChecked(){return this.node.selected}setChecked(e){this.node.selected=e}get label(){const e=this.node,t=e.label||e.textContent;if(t)return t.trim()}}const l=e=>"string"==typeof e,h=e=>null==e?"":l(e)?e:String(e),a=(e,t)=>null==e?[]:Array.isArray(e)?e.map(h):h(e).split(t),u=({form:e,name:t})=>{const s=JSON.stringify(t);if(!t)throw new Error(`Invalid name=${s}`);const n=`input[name=${s}], textarea[name=${s}], button[name=${s}], select[name=${s}]`,i=e.querySelectorAll(n);if(!i.length){if("function"==typeof e.matches&&e.matches(n))return[e];throw new Error(`Not found: name=${s}`)}return i},d=(e,t)=>{for(const s of e)s.removeEventListener("change",t),s.addEventListener("change",t)},f=e=>{const t=e.options.onWrite;t&&t(e)};class m{constructor(e={}){this.options=e;const t=this.name=e.name;((e,t,s)=>{if(!e)return;delete e[t],Object.defineProperty(e,t,{get:()=>s.value,set:e=>{s.value=e},enumerable:!0,configurable:!0})})(e.bindTo,t,this);const s=this._onChange=()=>{f(this),(e=>{const t=e.options.onChange;t&&t(e)})(this)},n=u(e);d(n,s);const o=this._items=i(this,n),r=e.defaults;if(r)if(o[0].checkable){for(const e of r)if(this.setValue(e))break}else this.setValue(r)}get value(){const e=this.current().map(e=>e.value);if(e.length>1){const t=this.options.delim||",";return e.join(t)}return e[0]}set value(e){this.setValue(e),f(this)}setValue(e){const t=this.items();1===t.length&&!t[0].checkable&&l(e)&&(e=[e]);const s=this.options.delim||",",n=a(e,s);let i=0,o=0;for(const e of t)if(e.checkable){const t=n.includes(e.value);t&&o++,e.checked!==t&&e.setChecked(t)}else{const t=n[i++],s=null==t;e.setValue(s?"":t),s||o++}return!!o}current(){return this.items().filter(e=>!e.disabled&&(!e.checkable||e.checked))}reload(){const e=u(this.options),t=this._onChange;d(e,t),this._items=i(this,e)}items(){return this._items}toggle(e,t){let s;const n=this.options.delim||",",i=a(e,n);for(const e of this.items())i.includes(e.value)&&(s=null!=t?t:!e.checked,e.checked=s);return s}has(e){return!!this.current().find(t=>t.value===e)}itemAt(e){return this.items().at(e)}itemOf(e){return this.items().find(t=>t.value===e)}}return e.formField=e=>new m(e),"undefined"!=typeof module&&(module.exports=e),e.formField}({});
1
+ var formField=function(e){"use strict";const t=e=>{const t=e.options.onWrite;t&&t(e)},s={option:!0,radio:!0,checkbox:!0},n=(e,t)=>e.tagName===t,i=(e,t)=>{const s=[];for(const i of t)if(n(i,"SELECT"))for(const t of i.options)s.push(new c(e,t));else s.push(new r(e,i));return s};class o{constructor(e,t){this.field=e,this.node=t}get value(){return this.node.value}set value(e){this.setValue(e),t(this.field)}setValue(e){this.node.value=e}get checked(){return this.getChecked()}set checked(e){this.checked!==e&&(this.setChecked(e),t(this.field))}get disabled(){return this.node.disabled}set disabled(e){this.node.disabled=e}}class r extends o{constructor(e,t){super(e,t),this.checkable=s[t.type]}getChecked(){return this.node.checked}setChecked(e){this.node.checked=e}get label(){const e=this.node,t=e.closest("label"),s=t&&t.querySelectorAll(e.tagName);if(s&&1===s.length){const e=o(t);if(e)return e}const n=e.labels;for(const e of n){const t=o(e);if(t)return t}const i=o(e);if(i)return i;function o(e){const t=e.innerText||e.textContent;if(t)return t.trim()}}}class c extends o{constructor(e,t){super(e,t),this.checkable=!0}getChecked(){return this.node.selected}setChecked(e){this.node.selected=e}get label(){const e=this.node,t=e.label||e.textContent;if(t)return t.trim()}}const l=e=>"string"==typeof e,h=e=>null==e?"":l(e)?e:String(e),a=(e,t)=>null==e?[]:Array.isArray(e)?e.map(h):h(e).split(t),u=({form:e,name:t})=>{const s=JSON.stringify(t);if(!t)throw new Error(`Invalid name=${s}`);const n=`input[name=${s}], textarea[name=${s}], button[name=${s}], select[name=${s}]`,i=e.querySelectorAll(n);if(!i.length){if("function"==typeof e.matches&&e.matches(n))return[e];throw new Error(`Not found: name=${s}`)}return i},d=(e,t)=>{for(const s of e)s.removeEventListener("change",t),s.addEventListener("change",t)},f=e=>{const t=e.options.onWrite;t&&t(e)};class m{constructor(e={}){this.options=e;const t=this.name=e.name;((e,t,s)=>{if(!e)return;delete e[t],Object.defineProperty(e,t,{get:()=>s.value,set:e=>{s.value=e},enumerable:!0,configurable:!0})})(e.bindTo,t,this);const s=this._onChange=()=>{f(this),(e=>{const t=e.options.onChange;t&&t(e)})(this)},n=u(e);d(n,s),this._items=i(this,n);const o=e.defaults;if(o)for(const e of o)if(this.setValue(e))break}get value(){const e=this.current().map(e=>e.value);if(e.length>1){const t=this.options.delim||",";return e.join(t)}return e[0]}set value(e){this.setValue(e),f(this)}setValue(e){const t=this.items();1===t.length&&!t[0].checkable&&l(e)&&(e=[e]);const s=this.options.delim||",",n=a(e,s);let i=0,o=0;for(const e of t)if(e.checkable){const t=n.includes(e.value);t&&o++,e.checked!==t&&e.setChecked(t)}else{const t=n[i++],s=null==t;e.setValue(s?"":t),s||o++}return!!o}current(){return this.items().filter(e=>!e.disabled&&(!e.checkable||e.checked))}reload(){const e=u(this.options),t=this._onChange;d(e,t),this._items=i(this,e)}items(){return this._items}toggle(e,t){let s;const n=this.options.delim||",",i=a(e,n);for(const e of this.items())i.includes(e.value)&&(s=null!=t?t:!e.checked,e.checked=s);return s}has(e){return!!this.current().find(t=>t.value===e)}itemAt(e){return this.items().at(e)}itemOf(e){return this.items().find(t=>t.value===e)}}return e.formField=e=>new m(e),"undefined"!=typeof module&&(module.exports=e),e.formField}({});
@@ -234,16 +234,12 @@ class FormBridgeImpl {
234
234
 
235
235
  const nodeList = getNodeList(options);
236
236
  updateEventListener(nodeList, _onChange);
237
- const items = this._items = formItemList(this, nodeList);
237
+ this._items = formItemList(this, nodeList);
238
238
 
239
239
  const defaults = options.defaults;
240
240
  if (defaults) {
241
- if (items[0].checkable) {
242
- for (const v of defaults) {
243
- if (this.setValue(v)) break
244
- }
245
- } else {
246
- this.setValue(defaults);
241
+ for (const v of defaults) {
242
+ if (this.setValue(v)) break
247
243
  }
248
244
  }
249
245
  }
package/package.json CHANGED
@@ -1,47 +1,52 @@
1
1
  {
2
2
  "name": "html-form-field",
3
3
  "description": "Unified interface for HTML form fields with synchronized binding to object properties",
4
- "version": "0.1.0",
5
- "author": "Kawanet",
6
- "c8": {
7
- "reporter": [
8
- "text",
9
- "lcov"
10
- ],
11
- "include": [
12
- "src/**/*.ts"
13
- ],
14
- "reportsDir": "coverage"
4
+ "version": "0.3.1",
5
+ "author": "@kawanet",
6
+ "bugs": {
7
+ "url": "https://github.com/kawanet/html-form-field/issues"
15
8
  },
9
+ "contributors": [
10
+ "kawanet <u-suke@kawa.net>"
11
+ ],
16
12
  "devDependencies": {
17
13
  "@rollup/plugin-alias": "^5.1.1",
18
- "@rollup/plugin-multi-entry": "^7.0.0",
14
+ "@rollup/plugin-commonjs": "^28.0.6",
15
+ "@rollup/plugin-multi-entry": "^7.1.0",
19
16
  "@rollup/plugin-node-resolve": "^16.0.3",
20
- "@rollup/plugin-sucrase": "^5.0.2",
17
+ "@rollup/plugin-sucrase": "^5.1.0",
21
18
  "@rollup/plugin-terser": "^0.4.4",
22
- "@types/jsdom": "^27.0.0",
23
- "@types/node": "^24.7.2",
24
- "c8": "^10.1.3",
25
- "chai": "^6.2.0",
26
- "html-ele": "^0.0.1",
27
- "jsdom": "^27.0.0",
28
- "mocha": "^11.7.4",
29
- "rollup": "^4.52.4",
30
- "terser": "^5.44.0",
19
+ "@types/jsdom": "^28.0.3",
20
+ "@types/node": "^24.9.1",
21
+ "html-ele": "^0.1.1",
22
+ "jsdom": "^29.1.1",
23
+ "mocha": "^11.7.6",
24
+ "rollup": "^4.60.4",
25
+ "terser": "^5.48.0",
31
26
  "typescript": "^5.9.3"
32
27
  },
28
+ "devEngines": {
29
+ "runtime": {
30
+ "name": "node",
31
+ "version": ">=22",
32
+ "onFail": "warn"
33
+ }
34
+ },
35
+ "engines": {
36
+ "node": ">=20"
37
+ },
33
38
  "exports": {
34
39
  ".": {
40
+ "types": "./types/html-form-field.d.ts",
35
41
  "require": "./dist/html-form-field.cjs",
36
- "import": {
37
- "types": "./types/html-form-field.d.ts",
38
- "default": "./dist/html-form-field.mjs"
39
- }
42
+ "import": "./dist/html-form-field.mjs"
40
43
  }
41
44
  },
42
45
  "files": [
43
46
  "LICENSE",
44
47
  "README.md",
48
+ "browser/import.js",
49
+ "browser/package.json",
45
50
  "dist/html-form-field.cjs",
46
51
  "dist/html-form-field.min.js",
47
52
  "dist/html-form-field.mjs",
@@ -49,16 +54,30 @@
49
54
  "src/*.ts",
50
55
  "types/*.d.ts"
51
56
  ],
57
+ "homepage": "https://github.com/kawanet/html-form-field",
58
+ "keywords": [
59
+ "checkbox",
60
+ "form",
61
+ "html",
62
+ "input",
63
+ "radio",
64
+ "select",
65
+ "textarea"
66
+ ],
52
67
  "license": "MIT",
53
- "main": "./src/index.ts",
68
+ "main": "./dist/html-form-field.cjs",
69
+ "repository": {
70
+ "type": "git",
71
+ "url": "git+https://github.com/kawanet/html-form-field.git"
72
+ },
54
73
  "scripts": {
55
74
  "build": "make -C builder",
56
75
  "fixpack": "fixpack",
57
- "prepare": "make -C builder clean all test",
58
- "test": "node --test",
59
- "test:coverage": "c8 node --test"
76
+ "prepack": "make -C builder is-buildable",
77
+ "prepare": "make -C builder is-not-buildable 2> /dev/null || make -C builder clean all test",
78
+ "test": "make -C builder test"
60
79
  },
61
80
  "sideEffects": false,
62
81
  "type": "module",
63
- "types": "types/html-form-field.d.ts"
82
+ "types": "./types/html-form-field.d.ts"
64
83
  }
package/src/form-field.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type {formField as NS, FormField, FormFieldOptions} from "../types/html-form-field.d.ts"
1
+ import type {formField as NS, FormField, FormFieldOptions} from "html-form-field"
2
2
  import {formItemList} from "./form-item.ts"
3
3
 
4
4
  type FieldEventHandler = (this: NS.FieldElement, ev: Event) => void
@@ -92,16 +92,12 @@ class FormBridgeImpl<T = any> implements FormField<T> {
92
92
 
93
93
  const nodeList = getNodeList(options)
94
94
  updateEventListener(nodeList, _onChange)
95
- const items = this._items = formItemList(this, nodeList)
95
+ this._items = formItemList(this, nodeList)
96
96
 
97
97
  const defaults = options.defaults
98
98
  if (defaults) {
99
- if (items[0].checkable) {
100
- for (const v of defaults) {
101
- if (this.setValue(v)) break
102
- }
103
- } else {
104
- this.setValue(defaults)
99
+ for (const v of defaults) {
100
+ if (this.setValue(v)) break
105
101
  }
106
102
  }
107
103
  }
package/src/form-item.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type {FormField, formField as NS} from "../types/html-form-field.d.ts"
1
+ import type {FormField, formField as NS} from "html-form-field"
2
2
 
3
3
  const triggerOnWrite = (field: FormField) => {
4
4
  const onWrite = field.options.onWrite
package/src/index.ts CHANGED
@@ -1,5 +1,9 @@
1
- export {formField} from "./form-field.ts"
2
- export type {FormField, FormFieldOptions} from "../types/html-form-field.d.ts"
3
- import type {formField as NS} from "../types/html-form-field.d.ts"
1
+ // Self-reference via the package name so `tsc --noEmit` resolves these
2
+ // types through `package.json` `exports` — the same path an external
3
+ // consumer would take. If the `exports.types` mapping ever breaks, the
4
+ // build fails here.
5
+ import type * as declared from "html-form-field"
4
6
 
5
- export type formField = typeof NS
7
+ export {formField} from "./form-field.ts"
8
+ export type {FormField, FormFieldOptions} from "html-form-field"
9
+ export type formField = typeof declared.formField
@@ -1,71 +1,83 @@
1
1
  /**
2
- * html-form-field - Unified interface for HTML form fields with synchronized binding to object properties
2
+ * html-form-field Unified interface for HTML form fields with two-way
3
+ * binding to object properties.
4
+ *
5
+ * @author kawanet
6
+ * @see https://github.com/kawanet/html-form-field
3
7
  */
8
+
4
9
  declare namespace formField {
10
+ /** Form field elements addressable by `name=`. */
5
11
  type FieldElement = HTMLInputElement | HTMLTextAreaElement | HTMLButtonElement | HTMLSelectElement
6
12
 
13
+ /** Individual item elements managed inside a field (a checkbox/radio in a group, or an `<option>` in a `<select>`). */
7
14
  type ItemElement = HTMLInputElement | HTMLTextAreaElement | HTMLButtonElement | HTMLOptionElement
8
15
 
9
16
  /**
10
- * Extract string keys of T
11
- * - If T is undefined: string (allow any name)
12
- * - If T is an object: union of keys K where T[K] extends string (strict)
17
+ * Extracts the string-valued keys of `T`.
18
+ *
19
+ * - `T = undefined`: any `string` is accepted (no bound object means no key constraint).
20
+ * - `T` is an object: the union of keys `K` where `T[K]` extends `string`.
13
21
  */
14
22
  type StringKeys<T> = [T] extends [undefined]
15
23
  ? string
16
24
  : { [K in keyof T]: K extends string ? (T[K] extends string ? K : never) : never }[keyof T];
17
25
 
26
+ /** Handler invoked when a field value is written. See `FormFieldOptions.onWrite`. */
18
27
  type OnWrite<T> = FormFieldOptions<T>["onWrite"]
19
28
 
29
+ /** Handler invoked on a user-driven `change` event. See `FormFieldOptions.onChange`. */
20
30
  type OnChange<T> = FormFieldOptions<T>["onChange"]
21
31
 
22
32
  interface FormFieldOptions<T = any> {
23
33
  /**
24
- * form element or container element which includes field elements
34
+ * The form element, or any container element that holds the field elements.
25
35
  */
26
36
  form: HTMLFormElement | FieldElement | ParentNode
27
37
 
28
38
  /**
29
- * name of the field element
30
- * - When `bindTo` is supplied, TypeScript infers `T` from that object and `name` is restricted
31
- * to keys of the bound object whose value type is `string`.
32
- * - When `bindTo` is not supplied (or `T` is not inferred), any string is allowed.
39
+ * `name=` attribute of the field to bind.
40
+ *
41
+ * When `bindTo` is supplied, TypeScript infers `T` from that object and `name` is
42
+ * restricted to keys of the bound object whose value type is `string`. Without
43
+ * `bindTo`, any string is accepted.
33
44
  */
34
45
  name: StringKeys<T>
35
46
 
36
47
  /**
37
- * Object to synchronize form values.
38
- * Only properties matching field names obtained via item() will have getters/setters defined.
39
- * The getter always references the value of the DOM field,
40
- * and the setter updates the DOM and triggers onWrite.
41
- * Other properties are not affected.
48
+ * Object to synchronize with the form field.
49
+ *
50
+ * A getter/setter is installed on the property matching `name`: the getter reads
51
+ * the live DOM value, the setter writes back to the DOM and fires `onWrite`. Other
52
+ * properties on the object are left untouched.
42
53
  */
43
54
  bindTo?: T
44
55
 
45
56
  /**
46
- * Called when the value is changed by user interaction,
47
- * or when assigning to a bound object's property.
48
- * Fires before onChange.
57
+ * Called whenever the field value is written — either by a setter
58
+ * (`field.value = ...` or assignment to a bound property) or by a user-driven
59
+ * `change` event. Fires before `onChange`.
49
60
  */
50
61
  onWrite?: (field: FormField<T>) => void
51
62
 
52
63
  /**
53
- * Called when the value is changed by user interaction (change event).
54
- * Not triggered by setter assignments. Fires after onWrite.
64
+ * Called only on a user-driven `change` event. Setter assignments do not trigger
65
+ * this handler. Fires after `onWrite`.
55
66
  */
56
67
  onChange?: (item: FormField<T>) => void
57
68
 
58
69
  /**
59
- * delimiter for multiple values (for checkboxes, multi-select)
60
- * Default: `,` (comma). Use Unit Separator `\x1F` instead,
61
- * if your values may include commas to reduce collisions.
70
+ * Delimiter joining multiple values for checkbox groups and multi-select fields.
71
+ * Defaults to `,` (comma). Use Unit Separator (`\x1F`) if your values may contain
72
+ * commas.
62
73
  */
63
74
  delim?: string
64
75
 
65
76
  /**
66
- * Default values to attempt at initialization (in order).
67
- * For checkbox/radio/select fields, tries the first value; if no matching option exists,
68
- * falls back to the next value, and so on until a match is found.
77
+ * Candidate initial values, tried in order.
78
+ *
79
+ * For checkbox / radio / select fields, the first value with a matching option is
80
+ * applied; if none matches, the next candidate is tried, and so on.
69
81
  */
70
82
  defaults?: string[]
71
83
  }
@@ -73,108 +85,90 @@ declare namespace formField {
73
85
  interface FormField<T = any> {
74
86
  readonly options: FormFieldOptions<T>
75
87
 
76
- /**
77
- * name of the field element (input, select, textarea)
78
- */
88
+ /** `name=` attribute of the bound field. */
79
89
  readonly name: StringKeys<T>
80
90
 
81
91
  /**
82
- * getter/setter of the value.
83
- * setter triggers onWrite handler.
84
- * Note: even for checkbox groups / multiple-select, the exposed type is string.
85
- * Multiple values are represented as a single string joined by options.delim (default ',').
92
+ * Getter/setter for the field value. Assignment fires the `onWrite` handler.
93
+ *
94
+ * For checkbox groups and multi-select fields the value is exposed as a single
95
+ * string formed by joining the selected values with `options.delim` (default `,`).
86
96
  */
87
97
  value: string
88
98
 
89
99
  /**
90
- * rebind the field element (input, select, textarea).
91
- * Call this when the set of option elements changes (for example when checkboxes,
92
- * radio buttons, or select <option> elements are added or removed) so that the
93
- * FormField re-scans and rebinds its items to reflect the current DOM.
100
+ * Re-scan the form for items matching `name` and rebind them. Call this after
101
+ * the underlying DOM changes for example when checkboxes, radio buttons, or
102
+ * `<option>` elements are added or removed so the `FormField` reflects the
103
+ * current state of the form.
94
104
  */
95
105
  reload(): void
96
106
 
97
107
  /**
98
- * list of all items (including disabled items)
99
- * - for checkbox, radio, select: returns all options
100
- * - for other types: returns all item(s)
108
+ * All items belonging to the field, including disabled ones.
109
+ *
110
+ * - checkbox / radio / select: every option in the group.
111
+ * - other field types: the single underlying item.
101
112
  */
102
113
  items(): FormItem[]
103
114
 
104
115
  /**
105
- * list of active items (excluding disabled items)
106
- * - for checkbox, radio: returns checked and not-disabled items
107
- * - for select: returns selected and not-disabled options
108
- * - for other types: returns all not-disabled item(s)
116
+ * Active items only disabled items are excluded.
117
+ *
118
+ * - checkbox / radio: items that are currently checked and enabled.
119
+ * - select: options that are currently selected and enabled.
120
+ * - other field types: the single underlying item, if enabled.
109
121
  */
110
122
  current(): FormItem[]
111
123
 
112
124
  /**
113
- * select/deselect an option (for checkbox, multi-select)
125
+ * Toggle the selection of an item (checkbox or multi-select). With `checked`
126
+ * supplied, sets the state explicitly; without it, flips the current state.
127
+ * Returns the new selection state.
114
128
  */
115
129
  toggle(value: string, checked?: boolean): boolean
116
130
 
117
- /**
118
- * check if an option is selected (for checkbox, multi-select)
119
- */
131
+ /** Returns `true` if the given value is currently selected. */
120
132
  has(value: string): boolean
121
133
 
122
134
  /**
123
- * Shortcut to access an item by index (operates on items()).
124
- * Equivalent to items().at(index). Returns undefined if out of range.
135
+ * Shortcut for `items().at(index)`. Returns `undefined` when out of range.
125
136
  */
126
137
  itemAt(index: number): FormItem | undefined
127
138
 
128
139
  /**
129
- * Shortcut to access an item by value (operates on items()).
130
- * Returns the first FormItem whose value equals the given value, or undefined if not found.
140
+ * Shortcut for `items().find(v => v.value === value)`. Returns the first match,
141
+ * or `undefined` if no item has the given value.
131
142
  */
132
143
  itemOf(value: string): FormItem | undefined
133
144
  }
134
145
 
135
146
  interface FormItem<E extends ItemElement = ItemElement> {
136
- /**
137
- * underlying HTML element (input, option, textarea)
138
- */
147
+ /** Underlying HTML element (`<input>`, `<option>`, `<textarea>`, ...). */
139
148
  readonly node: E
140
149
 
141
- /**
142
- * getter/setter of the `value` property
143
- * setter triggers onWrite handler
144
- */
150
+ /** Getter/setter for the item's `value` property. Assignment fires `onWrite`. */
145
151
  value: string
146
152
 
147
- /**
148
- * method to set the `value` property silently
149
- * does not trigger onWrite handler
150
- */
153
+ /** Set `value` without firing `onWrite`. */
151
154
  setValue(value: string): void
152
155
 
153
- /**
154
- * whether the option is checkable (radio, checkbox, select)
155
- */
156
+ /** Whether the item is a checkable type (radio, checkbox, or `<option>`). */
156
157
  readonly checkable: boolean
157
158
 
158
159
  /**
159
- * whether the option is selected (radio, checkbox, select)
160
- * setter triggers onWrite handler
160
+ * Selection state for radio buttons, checkboxes, and `<option>` elements.
161
+ * Assignment fires `onWrite`.
161
162
  */
162
163
  checked: boolean | undefined
163
164
 
164
- /**
165
- * method to set the `checked` property silently
166
- * does not trigger onWrite handler
167
- */
165
+ /** Set `checked` without firing `onWrite`. */
168
166
  setChecked(checked: boolean): void
169
167
 
170
- /**
171
- * getter/setter of the `disabled` property
172
- */
168
+ /** Getter/setter for the `disabled` property. */
173
169
  disabled: boolean
174
170
 
175
- /**
176
- * text content of the option (if applicable)
177
- */
171
+ /** Visible label text of the item, when the underlying element exposes one. */
178
172
  readonly label: string | undefined
179
173
  }
180
174
  }