@orkestrel/form 0.0.10 → 0.0.11

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 CHANGED
@@ -4,8 +4,8 @@
4
4
  > the answers given against it, declarative `FieldRule` data stating what those answers must
5
5
  > satisfy, acknowledged editing, and a terminal submit that settles the form exactly once.
6
6
 
7
- A terminal prompt and a browser form ask the same thing in different places, so this package ships
8
- what they share and neither renders nor reads input itself. Its `answer` promise is the parking seam
7
+ A terminal prompt and a browser form ask the same thing in different places, so the core ships
8
+ what they share. The browser entry binds native controls and renders neutral feedback. Its `answer` promise is the parking seam
9
9
  a server needs: hand the document out, wait, receive the answers back. A live form can take a field
10
10
  out and put it back with `disable` and `enable`, and exported budgets bound what one schema and its
11
11
  answers may retain, so a document that arrives from a wire costs a known maximum before anything
@@ -67,8 +67,10 @@ concept inventory of what this package leaves to the layer above.
67
67
 
68
68
  ## Package
69
69
 
70
- Published as one entry point per the `exports` field in `package.json`: `.`, the host-independent
71
- core. It ships dual ESM+CJS builds with declarations for both.
70
+ The `.` entry ships the host-independent core in ESM and CJS. The `./browser` entry ships the native
71
+ binding in ESM. Both include declarations. The binding supports password reveal, rule messages
72
+ without duplicate native feedback, presented-control write notifications and touch, and optional
73
+ blur deferral through footer presses. See [Native binding](./guides/form.md#native-binding).
72
74
 
73
75
  ## Development
74
76
 
@@ -259,6 +259,8 @@ export declare type FormBindingErrorCode = 'CONTROL' | 'PENDING' | 'DOM';
259
259
  /** Lists the native binding's notifications after their associated DOM effects. */
260
260
  export declare type FormBindingEventMap = {
261
261
  readonly render: readonly [];
262
+ /** Reports a control after its native value changes during document projection. */
263
+ readonly write: readonly [control: FormControl];
262
264
  readonly submit: readonly [submission: FormSubmission, submitter: HTMLElement | undefined];
263
265
  readonly error: readonly [error: unknown];
264
266
  };
@@ -281,6 +283,10 @@ export declare interface FormBindingInterface {
281
283
  export declare interface FormBindingOptions {
282
284
  readonly feedback?: ReadonlyMap<string, HTMLElement>;
283
285
  readonly summary?: HTMLElement;
286
+ /** Maps native controls to focus targets whose departure touches the represented field. */
287
+ readonly presentations?: ReadonlyMap<FormControl, HTMLElement>;
288
+ /** Defers blur touch until a same-form button's primary pointer press finishes. Default: false. */
289
+ readonly defer?: boolean;
284
290
  readonly on?: EmitterHooks<FormBindingEventMap>;
285
291
  readonly error?: EmitterErrorHandler;
286
292
  }
@@ -696,6 +702,24 @@ declare interface PasswordField extends FieldBase {
696
702
  */
697
703
  export declare function readFormControls(field: FormField, controls: readonly FormControl[]): FieldValue | undefined;
698
704
 
705
+ /**
706
+ * Reads native constraint messages not already reported by document rules.
707
+ * @param control - The native control with binding-owned custom validity removed.
708
+ * @param errors - The document's reported failures.
709
+ * @returns Uncovered constraint messages, preserving authored custom validity.
710
+ * @remarks
711
+ * A lone native failure retains platform copy. Competing failures use constraint-specific
712
+ * copy because the platform exposes only one message for the whole control.
713
+ * @example
714
+ * ```ts
715
+ * const input = document.createElement('input')
716
+ * input.name = 'name'
717
+ * input.required = true
718
+ * readNativeMessages(input, [{ field: 'name', rule: 'required', message: 'Enter a name' }]) // []
719
+ * ```
720
+ */
721
+ export declare function readNativeMessages(control: FormControl, errors: readonly FieldError[]): readonly string[];
722
+
699
723
  /**
700
724
  * Represents one choice out of a list.
701
725
  *
@@ -101,7 +101,8 @@ Object.freeze([
101
101
  "settled",
102
102
  "abandoned"
103
103
  ]);
104
- Object.freeze({
104
+ /** Holds the default failure copy for every named field rule. */
105
+ var RULE_MESSAGES = Object.freeze({
105
106
  required: "This field is required",
106
107
  minimum: "Must be at least {limit}",
107
108
  maximum: "Must be at most {limit}",
@@ -189,6 +190,22 @@ function matchesValue(a, b) {
189
190
  if (isArray(a) || isArray(b)) return isArray(a) && isArray(b) && a.length === b.length && a.every((entry, index) => entry === b[index]);
190
191
  return a === b;
191
192
  }
193
+ /**
194
+ * Resolves and interpolates one rule message.
195
+ *
196
+ * @remarks
197
+ * A replacement in `messages` is read first and {@link RULE_MESSAGES} supplies the copy otherwise,
198
+ * and `{limit}` in whichever text wins is replaced with the rule's operand.
199
+ *
200
+ * @param rule - The rule whose message to resolve.
201
+ * @param limit - The optional operand substituted for `{limit}`.
202
+ * @param messages - Optional rule-specific message replacements.
203
+ * @returns The resolved failure text.
204
+ */
205
+ function formatMessage(rule, limit, messages) {
206
+ const message = messages?.[rule] ?? RULE_MESSAGES[rule];
207
+ return limit === void 0 ? message : message.replaceAll("{limit}", String(limit));
208
+ }
192
209
  //#endregion
193
210
  //#region src/browser/helpers.ts
194
211
  /**
@@ -216,7 +233,7 @@ function matchesFormControls(field, controls) {
216
233
  "tel",
217
234
  "hidden"
218
235
  ].includes(control.type);
219
- case "password": return control.type === "password";
236
+ case "password": return control.type === "password" || control.type === "text";
220
237
  case "number": return control.type === "number" || control.type === "range";
221
238
  case "datetime": return control.type === "datetime-local";
222
239
  case "confirm": return control.type === "checkbox";
@@ -228,6 +245,44 @@ function matchesFormControls(field, controls) {
228
245
  });
229
246
  }
230
247
  /**
248
+ * Reads native constraint messages not already reported by document rules.
249
+ * @param control - The native control with binding-owned custom validity removed.
250
+ * @param errors - The document's reported failures.
251
+ * @returns Uncovered constraint messages, preserving authored custom validity.
252
+ * @remarks
253
+ * A lone native failure retains platform copy. Competing failures use constraint-specific
254
+ * copy because the platform exposes only one message for the whole control.
255
+ * @example
256
+ * ```ts
257
+ * const input = document.createElement('input')
258
+ * input.name = 'name'
259
+ * input.required = true
260
+ * readNativeMessages(input, [{ field: 'name', rule: 'required', message: 'Enter a name' }]) // []
261
+ * ```
262
+ */
263
+ function readNativeMessages(control, errors) {
264
+ const rules = new Set(errors.filter((error) => error.field === control.name).map((error) => error.rule));
265
+ const validity = control.validity;
266
+ const failures = /* @__PURE__ */ new Map();
267
+ if (validity.valueMissing) failures.set("required", formatMessage("required"));
268
+ if (validity.rangeUnderflow) failures.set("minimum", formatMessage("minimum", control.getAttribute("min") ?? ""));
269
+ if (validity.rangeOverflow) failures.set("maximum", formatMessage("maximum", control.getAttribute("max") ?? ""));
270
+ if (validity.tooShort) failures.set("minimum", formatMessage("minimum", control.getAttribute("minlength") ?? ""));
271
+ if (validity.tooLong) failures.set("maximum", formatMessage("maximum", control.getAttribute("maxlength") ?? ""));
272
+ if (validity.stepMismatch) failures.set("step", "Use a permitted value for this step");
273
+ if (validity.patternMismatch) failures.set("pattern", formatMessage("pattern"));
274
+ if (validity.typeMismatch && control instanceof HTMLInputElement) {
275
+ if (control.type === "email") failures.set("email", formatMessage("email"));
276
+ else if (control.type === "url") failures.set("url", formatMessage("url"));
277
+ }
278
+ const messages = [];
279
+ if (validity.customError) messages.push(control.validationMessage);
280
+ const lone = failures.size + Number(validity.customError) + Number(validity.badInput) === 1;
281
+ if (validity.badInput) messages.push(lone ? control.validationMessage : "Enter a valid value");
282
+ for (const [rule, message] of failures) if (!rules.has(rule)) messages.push(lone ? control.validationMessage : message);
283
+ return messages;
284
+ }
285
+ /**
231
286
  * Reads one compatible native population using the document's blank-to-absence semantics.
232
287
  * @param field - The field declaring the answer shape.
233
288
  * @param controls - Controls sharing the field's name, in native order.
@@ -288,6 +343,8 @@ var FormBinding = class {
288
343
  #logical = /* @__PURE__ */ new Set();
289
344
  #unreadable = /* @__PURE__ */ new Set();
290
345
  #names = /* @__PURE__ */ new Map();
346
+ #readings = /* @__PURE__ */ new Map();
347
+ #deferred = /* @__PURE__ */ new Set();
291
348
  #refresh = this.#onState.bind(this);
292
349
  #fill = this.#onFill.bind(this);
293
350
  #disable = this.#onDisable.bind(this);
@@ -299,13 +356,15 @@ var FormBinding = class {
299
356
  #summaryText = "";
300
357
  #destroyed = false;
301
358
  #running = false;
302
- #queued = false;
359
+ #preserve;
303
360
  #reading = false;
304
361
  #excluding;
305
362
  #changed = false;
306
363
  #notified = false;
307
364
  #scheduled = false;
308
365
  #submitting = false;
366
+ #pointer;
367
+ #release;
309
368
  /**
310
369
  * Binds existing controls to the supplied document's answers.
311
370
  * @param element - The native form, including its externally associated controls.
@@ -318,7 +377,7 @@ var FormBinding = class {
318
377
  this.#document = document;
319
378
  this.#options = options;
320
379
  this.#emitter = new Emitter(options);
321
- this.#observer = new MutationObserver(() => this.#onDocument());
380
+ this.#observer = new MutationObserver(() => this.#onDocument(true));
322
381
  for (const name of document.disabled) this.#logical.add(name);
323
382
  try {
324
383
  this.#controls = this.#discover();
@@ -329,6 +388,21 @@ var FormBinding = class {
329
388
  element.ownerDocument.addEventListener("change", (event) => this.#onInput(event), { signal });
330
389
  element.ownerDocument.addEventListener("focusout", (event) => this.#onBlur(event), { signal });
331
390
  element.ownerDocument.addEventListener("click", (event) => this.#onClick(event), { signal });
391
+ if (options?.defer) {
392
+ element.ownerDocument.addEventListener("pointerdown", (event) => this.#onPress(event), {
393
+ signal,
394
+ capture: true
395
+ });
396
+ element.ownerDocument.addEventListener("pointerup", (event) => this.#onRelease(event), {
397
+ signal,
398
+ capture: true
399
+ });
400
+ element.ownerDocument.addEventListener("pointercancel", (event) => this.#onRelease(event), {
401
+ signal,
402
+ capture: true
403
+ });
404
+ element.ownerDocument.defaultView?.addEventListener("blur", () => this.#finishPress(), { signal });
405
+ }
332
406
  document.emitter.on("fill", this.#fill);
333
407
  document.emitter.on("disable", this.#disable);
334
408
  document.emitter.on("enable", this.#enable);
@@ -433,6 +507,7 @@ var FormBinding = class {
433
507
  this.#destroyed = true;
434
508
  this.#observer.disconnect();
435
509
  this.#listeners.abort();
510
+ this.#clearPress();
436
511
  this.#document.emitter.off("fill", this.#fill);
437
512
  this.#document.emitter.off("disable", this.#disable);
438
513
  this.#document.emitter.off("enable", this.#enable);
@@ -460,27 +535,29 @@ var FormBinding = class {
460
535
  this.#excluded.clear();
461
536
  this.#feedback.clear();
462
537
  this.#names.clear();
538
+ this.#readings.clear();
463
539
  this.#unreadable.clear();
464
540
  this.#logical.clear();
465
541
  this.#controls = /* @__PURE__ */ new Map();
466
542
  this.#summary = void 0;
467
543
  this.#emitter.destroy();
468
544
  }
469
- #drain() {
545
+ #drain(preserve = false) {
470
546
  if (this.#destroyed) return;
471
- this.#queued = true;
547
+ this.#preserve = this.#preserve === false ? false : preserve;
472
548
  if (this.#running || this.#reading) return;
473
549
  this.#running = true;
474
550
  try {
475
551
  let turns = 0;
476
- while (this.#queued && !this.#destroyed && turns < 2) {
477
- this.#queued = false;
552
+ while (this.#preserve !== void 0 && !this.#destroyed && turns < 2) {
553
+ const retaining = this.#preserve;
554
+ this.#preserve = void 0;
478
555
  this.#changed = false;
479
556
  this.#controls = this.#discover();
480
557
  this.#releaseRemoved();
481
558
  this.#exclusions();
482
559
  if (this.#destroyed) break;
483
- this.#paint();
560
+ this.#paint(retaining);
484
561
  if (!this.#destroyed && (this.#changed || !this.#notified)) {
485
562
  this.#notified = true;
486
563
  this.#emitter.emit("render");
@@ -488,15 +565,15 @@ var FormBinding = class {
488
565
  turns += 1;
489
566
  }
490
567
  } catch (error) {
491
- this.#queued = false;
568
+ this.#preserve = void 0;
492
569
  throw isFormBindingError(error) ? error : new FormBindingError("DOM", "Native form projection failed", { cause: error });
493
570
  } finally {
494
571
  this.#running = false;
495
- if (this.#queued && !this.#destroyed && !this.#scheduled) {
572
+ if (this.#preserve !== void 0 && !this.#destroyed && !this.#scheduled) {
496
573
  this.#scheduled = true;
497
574
  queueMicrotask(() => {
498
575
  this.#scheduled = false;
499
- if (!this.#destroyed) this.#onDocument();
576
+ if (!this.#destroyed) this.#onDocument(this.#preserve ?? false);
500
577
  });
501
578
  }
502
579
  }
@@ -560,7 +637,7 @@ var FormBinding = class {
560
637
  }
561
638
  }
562
639
  }
563
- #paint() {
640
+ #paint(preserve) {
564
641
  this.#attribute(this.#element, "novalidate", "");
565
642
  this.#attribute(this.#element, "aria-busy", this.#document.pending === void 0 ? "false" : "true");
566
643
  const errors = [...this.#document.errors];
@@ -570,11 +647,12 @@ var FormBinding = class {
570
647
  const group = this.#controls.get(field.name) ?? [];
571
648
  for (const control of group) {
572
649
  this.#metadata(field, control);
573
- if (!this.#unreadable.has(field.name)) this.#value(field, control, Object.hasOwn(values, field.name) ? values[field.name] : void 0);
650
+ if (!this.#unreadable.has(field.name)) this.#value(field, control, Object.hasOwn(values, field.name) ? values[field.name] : void 0, preserve);
651
+ if (this.#destroyed) return;
574
652
  this.#restoreValidity(control);
575
- if (!this.#document.disabled.has(field.name) && !control.validity.valid && control.willValidate) errors.push({
653
+ if (!this.#document.disabled.has(field.name) && !control.validity.valid && control.willValidate) for (const message of readNativeMessages(control, this.#document.errors)) errors.push({
576
654
  field: field.name,
577
- message: control.validationMessage
655
+ message
578
656
  });
579
657
  }
580
658
  }
@@ -639,12 +717,22 @@ var FormBinding = class {
639
717
  if (field.control === "number" && field.rule?.step !== void 0 && !control.hasAttribute("step")) this.#attribute(control, "step", String(field.rule.step));
640
718
  }
641
719
  }
642
- #value(field, control, value) {
720
+ #value(field, control, value, preserve) {
721
+ if (preserve && !field.locked && control.ownerDocument.activeElement === control && this.#readings.has(control) && (control instanceof HTMLTextAreaElement || control instanceof HTMLInputElement && [
722
+ "text",
723
+ "search",
724
+ "email",
725
+ "url",
726
+ "tel",
727
+ "password"
728
+ ].includes(control.type)) && control.value !== this.#readings.get(control)) return;
729
+ let changed = false;
643
730
  if (control instanceof HTMLInputElement && control.type === "file") {
644
731
  const names = Array.from(control.files ?? [], (file) => file.name);
645
732
  if (names.length > 0 && (value === void 0 || !matchesValue(names, value))) {
646
733
  control.value = "";
647
734
  this.#changed = true;
735
+ this.#emitter.emit("write", control);
648
736
  }
649
737
  return;
650
738
  }
@@ -652,13 +740,13 @@ var FormBinding = class {
652
740
  const checked = field.control === "confirm" ? value === true : Array.isArray(value) ? value.includes(control.value) : value === control.value;
653
741
  if (control.checked !== checked) {
654
742
  control.checked = checked;
655
- this.#changed = true;
743
+ changed = true;
656
744
  }
657
745
  } else if (control instanceof HTMLSelectElement && control.multiple) for (const option of Array.from(control.options)) {
658
746
  const selected = Array.isArray(value) && value.includes(option.value);
659
747
  if (option.selected !== selected) {
660
748
  option.selected = selected;
661
- this.#changed = true;
749
+ changed = true;
662
750
  }
663
751
  }
664
752
  else {
@@ -667,9 +755,14 @@ var FormBinding = class {
667
755
  const before = control.value;
668
756
  const invalid = control.validity.badInput;
669
757
  control.value = text;
670
- if (control.value !== before || invalid) this.#changed = true;
758
+ if (control.value !== before || invalid) changed = true;
671
759
  }
672
760
  }
761
+ this.#readings.set(control, control.value);
762
+ if (changed) {
763
+ this.#changed = true;
764
+ this.#emitter.emit("write", control);
765
+ }
673
766
  }
674
767
  #attribute(element, name, value) {
675
768
  const attributes = this.#attributes.get(element) ?? /* @__PURE__ */ new Map();
@@ -725,6 +818,7 @@ var FormBinding = class {
725
818
  this.#restoreValidity(control);
726
819
  this.#restore(control);
727
820
  this.#names.delete(control);
821
+ this.#readings.delete(control);
728
822
  }
729
823
  for (const control of live) this.#names.set(control, control.name);
730
824
  for (const element of this.#attributes.keys()) if ((isFormControl(element) || element instanceof HTMLButtonElement) && element.form !== this.#element) this.#restore(element);
@@ -768,10 +862,10 @@ var FormBinding = class {
768
862
  #focusable(name) {
769
863
  return this.#controls.get(name)?.find((control) => !control.matches(":disabled") && control.closest("[inert]") === null && control.getClientRects().length > 0 && !["hidden", "collapse"].includes(getComputedStyle(control).visibility) && !(control instanceof HTMLInputElement && control.type === "hidden"));
770
864
  }
771
- #onDocument() {
865
+ #onDocument(preserve = false) {
772
866
  if (this.#destroyed) return;
773
867
  try {
774
- this.#drain();
868
+ this.#drain(preserve);
775
869
  } catch (error) {
776
870
  this.#emitter.emit("error", error);
777
871
  }
@@ -796,6 +890,7 @@ var FormBinding = class {
796
890
  this.#onState();
797
891
  }
798
892
  #onClear() {
893
+ this.#clearPress();
799
894
  this.#logical.clear();
800
895
  this.#excluded.clear();
801
896
  this.#unreadable.clear();
@@ -828,8 +923,17 @@ var FormBinding = class {
828
923
  ].includes(control.type)) event.preventDefault();
829
924
  }
830
925
  #onBlur(event) {
831
- const control = event.target;
926
+ let control = event.target;
927
+ for (const [native, presentation] of this.#options?.presentations ?? []) if (control === presentation) {
928
+ control = native;
929
+ break;
930
+ }
832
931
  if (!isFormControl(control) || control.form !== this.#element || !this.#document.field(control.name) || this.#destroyed || this.#document.status !== "editing") return;
932
+ if (event.relatedTarget === this.#options?.presentations?.get(control)) return;
933
+ if (this.#pointer !== void 0) {
934
+ this.#deferred.add(control.name);
935
+ return;
936
+ }
833
937
  try {
834
938
  this.#document.touch(control.name);
835
939
  this.#drain();
@@ -837,6 +941,42 @@ var FormBinding = class {
837
941
  this.#emitter.emit("error", error);
838
942
  }
839
943
  }
944
+ #onPress(event) {
945
+ if (!event.isPrimary || event.button !== 0 || !(event.target instanceof Element)) return;
946
+ const button = event.target.closest("button,input");
947
+ if (!((button instanceof HTMLButtonElement || button instanceof HTMLInputElement && [
948
+ "button",
949
+ "submit",
950
+ "reset",
951
+ "image"
952
+ ].includes(button.type)) && button.form === this.#element && !button.matches(":disabled"))) return;
953
+ this.#finishPress();
954
+ this.#pointer = event.pointerId;
955
+ }
956
+ #onRelease(event) {
957
+ if (event.pointerId !== this.#pointer) return;
958
+ if (this.#release !== void 0) clearTimeout(this.#release);
959
+ this.#release = setTimeout(() => this.#finishPress(), 0);
960
+ }
961
+ #finishPress() {
962
+ const names = [...this.#deferred];
963
+ this.#clearPress();
964
+ if (this.#destroyed || this.#document.status !== "editing") return;
965
+ try {
966
+ for (const name of names) {
967
+ if (this.#destroyed) break;
968
+ this.#document.touch(name);
969
+ }
970
+ } catch (error) {
971
+ this.#emitter.emit("error", error);
972
+ }
973
+ }
974
+ #clearPress() {
975
+ if (this.#release !== void 0) clearTimeout(this.#release);
976
+ this.#release = void 0;
977
+ this.#pointer = void 0;
978
+ this.#deferred.clear();
979
+ }
840
980
  #onSubmit(event) {
841
981
  if (event.defaultPrevented || this.#destroyed) return;
842
982
  event.preventDefault();
@@ -911,6 +1051,6 @@ function createFormBinding(element, document, options) {
911
1051
  return new FormBinding(element, document, options);
912
1052
  }
913
1053
  //#endregion
914
- export { FormBinding, FormBindingError, createFormBinding, isFormBindingError, isFormControl, matchesFormControls, readFormControls };
1054
+ export { FormBinding, FormBindingError, createFormBinding, isFormBindingError, isFormControl, matchesFormControls, readFormControls, readNativeMessages };
915
1055
 
916
1056
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../../src/browser/errors.ts","../../../src/core/constants.ts","../../../src/core/helpers.ts","../../../src/browser/helpers.ts","../../../src/browser/validators.ts","../../../src/browser/FormBinding.ts","../../../src/browser/factories.ts"],"sourcesContent":["import type { FormBindingErrorCode } from './types.js'\nimport { isInstance } from '@orkestrel/contract'\n\n/** Reports an incompatible control, overlapping submission, or native DOM failure. */\nexport class FormBindingError extends Error {\n\treadonly code: FormBindingErrorCode\n\treadonly context: Readonly<Record<string, unknown>> | undefined\n\n\t/**\n\t * Creates a native binding refusal.\n\t * @param code - The refusal category.\n\t * @param message - The explanation.\n\t * @param context - The associated field or cause.\n\t */\n\tconstructor(\n\t\tcode: FormBindingErrorCode,\n\t\tmessage: string,\n\t\tcontext?: Readonly<Record<string, unknown>>,\n\t) {\n\t\tsuper(message)\n\t\tthis.name = 'FormBindingError'\n\t\tthis.code = code\n\t\tthis.context = context === undefined ? undefined : Object.freeze({ ...context })\n\t}\n}\n\n/**\n * Checks whether a value is a native binding refusal.\n * @param value - The value to inspect.\n * @returns True if the value is a native binding refusal; false otherwise.\n * @example\n * ```ts\n * isFormBindingError(new FormBindingError('CONTROL', 'Duplicate text controls')) // true\n * ```\n */\nexport function isFormBindingError(value: unknown): value is FormBindingError {\n\treturn isInstance(value, FormBindingError)\n}\n","import type { FieldControl, FieldRuleName, FormStatus } from './types.js'\n\n/** Lists every field control, in the order declared by the public contract. */\nexport const FIELD_CONTROLS: readonly FieldControl[] = Object.freeze([\n\t'text',\n\t'editor',\n\t'password',\n\t'number',\n\t'date',\n\t'time',\n\t'datetime',\n\t'color',\n\t'confirm',\n\t'select',\n\t'checkbox',\n\t'file',\n])\n\n/** Lists the members every field declares, whatever its control. */\nexport const FIELD_BASE_KEYS: readonly string[] = Object.freeze([\n\t'control',\n\t'name',\n\t'label',\n\t'help',\n\t'group',\n\t'hidden',\n\t'disabled',\n\t'locked',\n\t'rule',\n\t'meta',\n])\n\n/**\n * Lists every member one field control permits, composed from {@link FIELD_BASE_KEYS} and the\n * members the control's own interface adds.\n */\nexport const FIELD_KEYS: Readonly<Record<FieldControl, readonly string[]>> = Object.freeze({\n\ttext: Object.freeze([...FIELD_BASE_KEYS, 'default', 'placeholder']),\n\teditor: Object.freeze([...FIELD_BASE_KEYS, 'default', 'placeholder']),\n\tpassword: Object.freeze([...FIELD_BASE_KEYS, 'mask']),\n\tnumber: Object.freeze([...FIELD_BASE_KEYS, 'default', 'placeholder']),\n\tdate: Object.freeze([...FIELD_BASE_KEYS, 'default']),\n\ttime: Object.freeze([...FIELD_BASE_KEYS, 'default']),\n\tdatetime: Object.freeze([...FIELD_BASE_KEYS, 'default']),\n\tcolor: Object.freeze([...FIELD_BASE_KEYS, 'default']),\n\tconfirm: Object.freeze([...FIELD_BASE_KEYS, 'default']),\n\tselect: Object.freeze([...FIELD_BASE_KEYS, 'choices', 'default', 'open']),\n\tcheckbox: Object.freeze([...FIELD_BASE_KEYS, 'choices', 'default']),\n\tfile: Object.freeze([...FIELD_BASE_KEYS, 'accept', 'multiple']),\n})\n\n/** Lists every form lifecycle status. */\nexport const FORM_STATUSES: readonly FormStatus[] = Object.freeze([\n\t'editing',\n\t'settled',\n\t'abandoned',\n])\n\n/** Holds the default failure copy for every named field rule. */\nexport const RULE_MESSAGES: Readonly<Record<FieldRuleName, string>> = Object.freeze({\n\trequired: 'This field is required',\n\tminimum: 'Must be at least {limit}',\n\tmaximum: 'Must be at most {limit}',\n\tstep: 'Must be a multiple of {limit}',\n\tpattern: 'Must match the required format',\n\temail: 'Must be a valid email address',\n\turl: 'Must be a valid URL',\n\tinteger: 'Must be an integer',\n\talphanumeric: 'Must contain only letters and numbers',\n})\n\n/** Matches a practical whole-address email shape. */\nexport const EMAIL_PATTERN = Object.freeze(/^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/)\n\n/** Matches an absolute HTTP or HTTPS URL shape. */\nexport const URL_PATTERN = Object.freeze(/^https?:\\/\\/[^\\s]+$/)\n\n/** Matches one or more ASCII letters or digits. */\nexport const ALPHANUMERIC_PATTERN = Object.freeze(/^[A-Za-z0-9]+$/)\n\n/** Matches a signed or unsigned base-ten integer string. */\nexport const INTEGER_PATTERN = Object.freeze(/^[+-]?\\d+$/)\n\n/** Matches a six-digit hexadecimal color string. */\nexport const COLOR_PATTERN = Object.freeze(/^#[0-9A-Fa-f]{6}$/)\n\n/** Matches an ISO calendar date string in `YYYY-MM-DD` form. */\nexport const DATE_PATTERN = Object.freeze(/^\\d{4}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[12]\\d|3[01])$/)\n\n/** Matches a 24-hour time string with optional seconds. */\nexport const TIME_PATTERN = Object.freeze(/^(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d)?$/)\n\n/** Matches an ISO local date and time string with optional seconds. */\nexport const DATETIME_PATTERN = Object.freeze(\n\t/^\\d{4}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[12]\\d|3[01])T(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d)?$/,\n)\n\n/** Caps the accepted source length for an authored regular expression, at 256. */\nexport const PATTERN_LIMIT = 256\n\n/** Caps the number of fields one schema may declare, at 512. */\nexport const FIELD_LIMIT = 512\n\n/** Caps the number of groups one schema may declare, at 64. */\nexport const GROUP_LIMIT = 64\n\n/** Caps the number of choices one `select` or `checkbox` field may offer, at 1024. */\nexport const CHOICE_LIMIT = 1024\n\n/** Caps the number of entries one list-valued answer may hold, at 1024. */\nexport const LIST_LIMIT = 1024\n\n/** Caps the length, in UTF-16 code units, of a schema, group, or field name, at 128. */\nexport const NAME_LIMIT = 128\n\n/** Caps the length, in UTF-16 code units, of any single retained string, at 65536. */\nexport const STRING_LIMIT = 65536\n\n/** Caps the total length, in UTF-16 code units, of every string one schema retains, at 1048576. */\nexport const TEXT_LIMIT = 1048576\n\n/** Caps the total number of records, arrays, and leaves one schema retains, at 16384. */\nexport const NODE_LIMIT = 16384\n","import type { JSONRecord, JSONValue } from '@orkestrel/contract'\nimport type {\n\tEvaluationOptions,\n\tFieldError,\n\tFieldControl,\n\tFieldRuleName,\n\tFieldValue,\n\tFormField,\n\tFormGroup,\n\tFormSchema,\n\tFormValues,\n} from './types.js'\nimport {\n\tattempt,\n\tcloneJSONRecord,\n\tisArray,\n\tisBoolean,\n\tisFiniteNumber,\n\tisInteger,\n\tisContractError,\n\tisRecord,\n\tisString,\n\treadArrayEntries,\n} from '@orkestrel/contract'\nimport { cloneValue } from './cloners.js'\nimport {\n\tALPHANUMERIC_PATTERN,\n\tCHOICE_LIMIT,\n\tCOLOR_PATTERN,\n\tDATE_PATTERN,\n\tDATETIME_PATTERN,\n\tEMAIL_PATTERN,\n\tFIELD_CONTROLS,\n\tFIELD_LIMIT,\n\tGROUP_LIMIT,\n\tINTEGER_PATTERN,\n\tLIST_LIMIT,\n\tNAME_LIMIT,\n\tNODE_LIMIT,\n\tPATTERN_LIMIT,\n\tRULE_MESSAGES,\n\tSTRING_LIMIT,\n\tTEXT_LIMIT,\n\tTIME_PATTERN,\n\tURL_PATTERN,\n} from './constants.js'\nimport { FormError } from './errors.js'\n\n/**\n * Writes one own enumerable data property onto a record.\n *\n * @param target - The record to write into.\n * @param name - The property name to write.\n * @param value - The value to store.\n *\n * @remarks\n * Plain assignment runs an inherited setter, so writing a `__proto__` key that way reaches\n * `Object.prototype` and leaves the record without the entry. Defining the property writes the\n * record itself, whatever the prototype chain declares. The entry stays writable and configurable.\n *\n * @example\n * ```ts\n * const values: Record<string, number> = {}\n * defineEntry(values, '__proto__', 1)\n * Object.hasOwn(values, '__proto__') // true\n * ```\n */\nexport function defineEntry<T>(target: Record<string, T>, name: string, value: T): void {\n\tObject.defineProperty(target, name, {\n\t\tvalue,\n\t\tenumerable: true,\n\t\tconfigurable: true,\n\t\twritable: true,\n\t})\n}\n\n/**\n * Writes one own enumerable data property that cannot be rewritten or removed.\n *\n * @param target - The record to write into.\n * @param name - The property name to write.\n * @param value - The value to store.\n *\n * @remarks\n * The prototype-safe write of {@link defineEntry}, frozen: the entry is neither writable nor\n * configurable, so the record a parser hands back cannot be edited through the key it filled.\n *\n * @example\n * ```ts\n * const values: Record<string, number> = {}\n * freezeEntry(values, '__proto__', 1)\n * Object.getOwnPropertyDescriptor(values, '__proto__')?.writable // false\n * ```\n */\nexport function freezeEntry<T>(target: Record<string, T>, name: string, value: T): void {\n\tObject.defineProperty(target, name, {\n\t\tvalue,\n\t\tenumerable: true,\n\t\tconfigurable: false,\n\t\twritable: false,\n\t})\n}\n\n/**\n * Checks whether a value has the shape required by one field control.\n *\n * @remarks\n * Every write and every seeded value passes through this gate, and it reads `STRING_LIMIT` and\n * `LIST_LIMIT` before it consults the control, so no regular expression sees an over-long value.\n *\n * @param field - The field that owns the value.\n * @param value - The unknown value to inspect.\n * @returns True if the control can hold the value; false otherwise.\n */\nexport function matchesField(field: FormField, value: unknown): value is FieldValue {\n\tif (isString(value) && value.length > STRING_LIMIT) return false\n\n\tlet entries: readonly unknown[] | undefined\n\tif (isArray(value)) {\n\t\tconst length = attempt(() => value.length)\n\t\tif (!length.success || length.value > LIST_LIMIT) return false\n\n\t\tconst read = readArrayEntries(value)\n\t\tif (!read.success || !read.value.dense) return false\n\n\t\tentries = read.value.entries\n\t\tif (entries.some((entry) => !isString(entry) || entry.length > STRING_LIMIT)) return false\n\t}\n\n\tswitch (field.control) {\n\t\tcase 'text':\n\t\tcase 'editor':\n\t\tcase 'password':\n\t\t\treturn isString(value)\n\t\tcase 'number':\n\t\t\treturn isFiniteNumber(value)\n\t\tcase 'date':\n\t\t\treturn isString(value) && DATE_PATTERN.test(value)\n\t\tcase 'time':\n\t\t\treturn isString(value) && TIME_PATTERN.test(value)\n\t\tcase 'datetime':\n\t\t\treturn isString(value) && DATETIME_PATTERN.test(value)\n\t\tcase 'color':\n\t\t\treturn isString(value) && COLOR_PATTERN.test(value)\n\t\tcase 'confirm':\n\t\t\treturn isBoolean(value)\n\t\tcase 'select':\n\t\t\treturn (\n\t\t\t\tisString(value) &&\n\t\t\t\t!field.choices.some((choice) => choice.value === value && choice.disabled === true) &&\n\t\t\t\t(field.open === true ||\n\t\t\t\t\tfield.choices.some((choice) => choice.value === value && choice.disabled !== true))\n\t\t\t)\n\t\tcase 'checkbox':\n\t\t\treturn (\n\t\t\t\tentries !== undefined &&\n\t\t\t\tentries.every(\n\t\t\t\t\t(entry) =>\n\t\t\t\t\t\tisString(entry) &&\n\t\t\t\t\t\tfield.choices.some((choice) => choice.value === entry && choice.disabled !== true),\n\t\t\t\t) &&\n\t\t\t\tnew Set(entries).size === entries.length\n\t\t\t)\n\t\tcase 'file':\n\t\t\treturn entries !== undefined && (field.multiple === true || entries.length <= 1)\n\t}\n}\n\n/**\n * Decides whether a raw binding value projects to an answered field.\n *\n * @remarks\n * Bind with `fill(name, matchesAnswer(raw) ? raw : undefined)`. This projection treats an absent\n * value and a string containing only whitespace as unanswered. Every other field value is an\n * answer, including an empty list, `false`, and zero. Core evaluation does not use this projection:\n * its `required` rule remains presence-only.\n *\n * @param value - The raw field value, or absence.\n * @returns True if the binding preserves the value as an answer; false otherwise.\n */\nexport function matchesAnswer(value: FieldValue | undefined): boolean {\n\treturn value !== undefined && (!isString(value) || value.trim().length > 0)\n}\n\n/**\n * Checks whether a named rule applies to one field control.\n *\n * @remarks\n * The runtime control-membership check keeps this boundary total for JavaScript callers that\n * bypass the declared {@link FieldControl} contract.\n *\n * @param control - The field control to inspect.\n * @param rule - The named rule to inspect.\n * @returns True if the control evaluates that rule; false otherwise.\n */\nexport function appliesRule(control: FieldControl, rule: FieldRuleName): boolean {\n\tif (!FIELD_CONTROLS.some((candidate) => candidate === control)) return false\n\n\tswitch (rule) {\n\t\tcase 'required':\n\t\t\treturn true\n\t\tcase 'minimum':\n\t\tcase 'maximum':\n\t\t\treturn control !== 'color' && control !== 'confirm' && control !== 'select'\n\t\tcase 'step':\n\t\t\treturn control === 'number'\n\t\tcase 'pattern':\n\t\tcase 'email':\n\t\tcase 'url':\n\t\tcase 'alphanumeric':\n\t\t\treturn (\n\t\t\t\tcontrol !== 'number' &&\n\t\t\t\tcontrol !== 'confirm' &&\n\t\t\t\tcontrol !== 'checkbox' &&\n\t\t\t\tcontrol !== 'file'\n\t\t\t)\n\t\tcase 'integer':\n\t\t\treturn control !== 'confirm' && control !== 'checkbox' && control !== 'file'\n\t}\n\n\treturn false\n}\n\n/**\n * Evaluates one field rule against its current value.\n *\n * @param field - The field and rule to evaluate.\n * @param value - The current value, or absence.\n * @param values - Every value available to a custom rule.\n * @param messages - Optional rule-specific message replacements.\n * @returns Every failure in rule order.\n * @throws Thrown when a {@link FieldValidator} supplied through {@link FieldRule.custom} throws:\n * its own value escapes unchanged, because this helper adds no boundary around it.\n */\nexport function evaluateField(\n\tfield: FormField,\n\tvalue: FieldValue | undefined,\n\tvalues: FormValues,\n\tmessages?: Readonly<Partial<Record<FieldRuleName, string>>>,\n): readonly FieldError[] {\n\tconst errors: FieldError[] = []\n\tconst rule = field.rule\n\n\tif (value === undefined) {\n\t\tif (rule?.required === true && appliesRule(field.control, 'required')) {\n\t\t\terrors.push(createFieldError(field, 'required', undefined, messages))\n\t\t}\n\t}\n\n\tif (rule === undefined) return Object.freeze(errors)\n\n\tif (rule.minimum !== undefined && appliesRule(field.control, 'minimum')) {\n\t\tlet failed = false\n\n\t\tswitch (field.control) {\n\t\t\tcase 'text':\n\t\t\tcase 'editor':\n\t\t\tcase 'password':\n\t\t\t\tfailed = isString(value) && isFiniteNumber(rule.minimum) && value.length < rule.minimum\n\t\t\t\tbreak\n\t\t\tcase 'number':\n\t\t\t\tfailed = isFiniteNumber(value) && isFiniteNumber(rule.minimum) && value < rule.minimum\n\t\t\t\tbreak\n\t\t\tcase 'date':\n\t\t\tcase 'time':\n\t\t\tcase 'datetime':\n\t\t\t\tfailed = isString(value) && isString(rule.minimum) && value < rule.minimum\n\t\t\t\tbreak\n\t\t\tcase 'checkbox':\n\t\t\tcase 'file':\n\t\t\t\tfailed = isArray(value) && isFiniteNumber(rule.minimum) && value.length < rule.minimum\n\t\t\t\tbreak\n\t\t\tcase 'color':\n\t\t\tcase 'confirm':\n\t\t\tcase 'select':\n\t\t\t\tbreak\n\t\t}\n\n\t\tif (failed) {\n\t\t\terrors.push(createFieldError(field, 'minimum', rule.minimum, messages))\n\t\t}\n\t}\n\n\tif (rule.maximum !== undefined && appliesRule(field.control, 'maximum')) {\n\t\tlet failed = false\n\n\t\tswitch (field.control) {\n\t\t\tcase 'text':\n\t\t\tcase 'editor':\n\t\t\tcase 'password':\n\t\t\t\tfailed = isString(value) && isFiniteNumber(rule.maximum) && value.length > rule.maximum\n\t\t\t\tbreak\n\t\t\tcase 'number':\n\t\t\t\tfailed = isFiniteNumber(value) && isFiniteNumber(rule.maximum) && value > rule.maximum\n\t\t\t\tbreak\n\t\t\tcase 'date':\n\t\t\tcase 'time':\n\t\t\tcase 'datetime':\n\t\t\t\tfailed = isString(value) && isString(rule.maximum) && value > rule.maximum\n\t\t\t\tbreak\n\t\t\tcase 'checkbox':\n\t\t\tcase 'file':\n\t\t\t\tfailed = isArray(value) && isFiniteNumber(rule.maximum) && value.length > rule.maximum\n\t\t\t\tbreak\n\t\t\tcase 'color':\n\t\t\tcase 'confirm':\n\t\t\tcase 'select':\n\t\t\t\tbreak\n\t\t}\n\n\t\tif (failed) {\n\t\t\terrors.push(createFieldError(field, 'maximum', rule.maximum, messages))\n\t\t}\n\t}\n\n\tif (rule.step !== undefined && appliesRule(field.control, 'step') && isFiniteNumber(value)) {\n\t\tconst base = isFiniteNumber(rule.minimum) ? rule.minimum : 0\n\t\tconst multiple = (value - base) / rule.step\n\n\t\tif (\n\t\t\t!isFiniteNumber(rule.step) ||\n\t\t\trule.step === 0 ||\n\t\t\t!isFiniteNumber(multiple) ||\n\t\t\tMath.abs(multiple - Math.round(multiple)) > 1e-9\n\t\t) {\n\t\t\terrors.push(createFieldError(field, 'step', rule.step, messages))\n\t\t}\n\t}\n\n\tif (isString(value)) {\n\t\tif (rule.pattern !== undefined && appliesRule(field.control, 'pattern')) {\n\t\t\tconst pattern = rule.pattern\n\n\t\t\tif (pattern.length > PATTERN_LIMIT) {\n\t\t\t\terrors.push(createFieldError(field, 'pattern', undefined, messages))\n\t\t\t} else {\n\t\t\t\tconst outcome = attempt(() => new RegExp(pattern).test(value))\n\n\t\t\t\tif (!outcome.success || !outcome.value) {\n\t\t\t\t\terrors.push(createFieldError(field, 'pattern', undefined, messages))\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (rule.email === true && appliesRule(field.control, 'email') && !EMAIL_PATTERN.test(value)) {\n\t\t\terrors.push(createFieldError(field, 'email', undefined, messages))\n\t\t}\n\n\t\tif (rule.url === true && appliesRule(field.control, 'url') && !URL_PATTERN.test(value)) {\n\t\t\terrors.push(createFieldError(field, 'url', undefined, messages))\n\t\t}\n\n\t\tif (\n\t\t\trule.alphanumeric === true &&\n\t\t\tappliesRule(field.control, 'alphanumeric') &&\n\t\t\t!ALPHANUMERIC_PATTERN.test(value)\n\t\t) {\n\t\t\terrors.push(createFieldError(field, 'alphanumeric', undefined, messages))\n\t\t}\n\n\t\tif (\n\t\t\trule.integer === true &&\n\t\t\tappliesRule(field.control, 'integer') &&\n\t\t\t!INTEGER_PATTERN.test(value)\n\t\t) {\n\t\t\terrors.push(createFieldError(field, 'integer', undefined, messages))\n\t\t}\n\t}\n\n\tif (\n\t\tvalue !== undefined &&\n\t\tfield.control === 'number' &&\n\t\trule.integer === true &&\n\t\tappliesRule(field.control, 'integer') &&\n\t\t!isInteger(value)\n\t) {\n\t\terrors.push(createFieldError(field, 'integer', undefined, messages))\n\t}\n\n\tif (rule.custom !== undefined) {\n\t\tconst result = rule.custom(value, values)\n\n\t\tif (isString(result)) errors.push(Object.freeze({ field: field.name, message: result }))\n\t}\n\n\treturn Object.freeze(errors)\n}\n\n/**\n * Evaluates every active field in schema order.\n *\n * @param schema - The form schema to evaluate.\n * @param values - The values keyed by field name.\n * @param options - Optional message replacements and the effective disabled field set.\n * @returns Every field failure in schema and rule order.\n * @throws Thrown when a {@link FieldValidator} supplied through {@link FieldRule.custom} throws:\n * its own value escapes unchanged, because this helper adds no boundary around it.\n */\nexport function evaluateForm(\n\tschema: FormSchema,\n\tvalues: FormValues,\n\toptions?: EvaluationOptions,\n): readonly FieldError[] {\n\tconst errors: FieldError[] = []\n\n\tfor (const field of schema.fields) {\n\t\tconst active =\n\t\t\toptions?.disabled === undefined ? field.disabled !== true : !options.disabled.has(field.name)\n\n\t\tif (active) {\n\t\t\tconst value = Object.hasOwn(values, field.name) ? values[field.name] : undefined\n\t\t\terrors.push(...evaluateField(field, value, values, options?.messages))\n\t\t}\n\t}\n\n\treturn Object.freeze(errors)\n}\n\n/**\n * Computes the values explicitly seeded by a schema.\n *\n * @remarks\n * `password` and `file` declare no default, so a field of either control never appears in the\n * result.\n *\n * @param schema - The schema whose defaults to collect.\n * @returns A value record containing only fields with defaults.\n */\nexport function computeDefaults(schema: FormSchema): FormValues {\n\tconst defaults: Record<string, FieldValue> = {}\n\n\tfor (const field of schema.fields) {\n\t\tswitch (field.control) {\n\t\t\tcase 'checkbox':\n\t\t\t\tif (field.default !== undefined) {\n\t\t\t\t\tdefineEntry(defaults, field.name, cloneValue(field.default))\n\t\t\t\t}\n\t\t\t\tbreak\n\t\t\tcase 'password':\n\t\t\tcase 'file':\n\t\t\t\tbreak\n\t\t\tcase 'text':\n\t\t\tcase 'editor':\n\t\t\tcase 'number':\n\t\t\tcase 'date':\n\t\t\tcase 'time':\n\t\t\tcase 'datetime':\n\t\t\tcase 'color':\n\t\t\tcase 'confirm':\n\t\t\tcase 'select':\n\t\t\t\tif (field.default !== undefined) {\n\t\t\t\t\tdefineEntry(defaults, field.name, field.default)\n\t\t\t\t}\n\t\t\t\tbreak\n\t\t}\n\t}\n\n\treturn Object.freeze(defaults)\n}\n\n/**\n * Compares two field values by scalar identity or ordered list content.\n *\n * @param a - The first field value.\n * @param b - The second field value.\n * @returns True if both values contain the same answer; false otherwise.\n */\nexport function matchesValue(a: FieldValue, b: FieldValue): boolean {\n\tif (isArray(a) || isArray(b)) {\n\t\treturn (\n\t\t\tisArray(a) &&\n\t\t\tisArray(b) &&\n\t\t\ta.length === b.length &&\n\t\t\ta.every((entry, index) => entry === b[index])\n\t\t)\n\t}\n\n\treturn a === b\n}\n\n/**\n * Extracts the names whose answers differ between two form value records.\n *\n * @remarks\n * Presence is compared in both directions before present values are compared through\n * {@link matchesValue}. The returned set is a new snapshot, exposed as readonly because later\n * changes to either input never alter its membership.\n *\n * @param current - The values the form holds.\n * @param opened - The values held when the form opened.\n * @returns A readonly snapshot of changed field names.\n */\nexport function extractChanges(current: FormValues, opened: FormValues): ReadonlySet<string> {\n\tconst names = new Set([...Object.keys(current), ...Object.keys(opened)])\n\tconst changed = new Set<string>()\n\n\tfor (const name of names) {\n\t\tconst hasCurrent = Object.hasOwn(current, name)\n\t\tconst hasOpened = Object.hasOwn(opened, name)\n\n\t\tif (hasCurrent !== hasOpened) {\n\t\t\tchanged.add(name)\n\t\t\tcontinue\n\t\t}\n\n\t\tconst now = current[name]\n\t\tconst before = opened[name]\n\t\tif (now === undefined || before === undefined || !matchesValue(now, before)) changed.add(name)\n\t}\n\n\treturn changed\n}\n\n/**\n * Compares two form value records by keys and value content.\n *\n * @param a - The first value record.\n * @param b - The second value record.\n * @returns True if both records contain the same answers; false otherwise.\n */\nexport function matchesValues(a: FormValues, b: FormValues): boolean {\n\treturn extractChanges(a, b).size === 0\n}\n\n/**\n * Resolves and interpolates one rule message.\n *\n * @remarks\n * A replacement in `messages` is read first and {@link RULE_MESSAGES} supplies the copy otherwise,\n * and `{limit}` in whichever text wins is replaced with the rule's operand.\n *\n * @param rule - The rule whose message to resolve.\n * @param limit - The optional operand substituted for `{limit}`.\n * @param messages - Optional rule-specific message replacements.\n * @returns The resolved failure text.\n */\nexport function formatMessage(\n\trule: FieldRuleName,\n\tlimit?: number | string,\n\tmessages?: Readonly<Partial<Record<FieldRuleName, string>>>,\n): string {\n\tconst message = messages?.[rule] ?? RULE_MESSAGES[rule]\n\treturn limit === undefined ? message : message.replaceAll('{limit}', String(limit))\n}\n\n/**\n * Creates one named-rule failure against a field.\n *\n * @param field - The field the rule failed on.\n * @param rule - The named rule that failed.\n * @param limit - The rule's operand, substituted for `{limit}`, or absence when it carries none.\n * @param messages - Optional rule-specific message replacements.\n * @returns A frozen {@link FieldError} carrying the field's name, the resolved text, and the rule.\n *\n * @remarks\n * A `custom` validator and the form's `invalidate` method both report a message of their own\n * under no rule name, so neither builds its failure here.\n *\n * @example\n * ```ts\n * const field: FormField = { control: 'text', name: 'nickname', rule: { minimum: 3 } }\n * const error = createFieldError(field, 'minimum', 3)\n * error.message // 'Must be at least 3'\n * ```\n */\nexport function createFieldError(\n\tfield: FormField,\n\trule: FieldRuleName,\n\tlimit: number | string | undefined,\n\tmessages?: Readonly<Partial<Record<FieldRuleName, string>>>,\n): FieldError {\n\treturn Object.freeze({ field: field.name, message: formatMessage(rule, limit, messages), rule })\n}\n\n/**\n * Projects a schema into JSON while removing custom validators and absent values.\n *\n * @param schema - The schema to project.\n * @returns A deep JSON copy of the serializable schema.\n * @throws A {@link FormError} coded `SCHEMA` when accessor-bearing metadata cannot be owned. A\n * non-contract throw while reading metadata escapes unchanged.\n */\nexport function serializeForm(schema: FormSchema): JSONRecord {\n\tconst output: Record<string, JSONValue> = {}\n\n\tif (schema.name !== undefined) output.name = schema.name\n\tif (schema.label !== undefined) output.label = schema.label\n\tif (schema.help !== undefined) output.help = schema.help\n\tif (schema.groups !== undefined) {\n\t\toutput.groups = schema.groups.map((group): JSONRecord => {\n\t\t\tconst entry: Record<string, JSONValue> = { name: group.name, label: group.label }\n\t\t\tif (group.help !== undefined) entry.help = group.help\n\t\t\treturn entry\n\t\t})\n\t}\n\n\toutput.fields = schema.fields.map((field): JSONRecord => {\n\t\tconst entry: Record<string, JSONValue> = {\n\t\t\tcontrol: field.control,\n\t\t\tname: field.name,\n\t\t}\n\n\t\tif (field.label !== undefined) entry.label = field.label\n\t\tif (field.help !== undefined) entry.help = field.help\n\t\tif (field.group !== undefined) entry.group = field.group\n\t\tif (field.hidden !== undefined) entry.hidden = field.hidden\n\t\tif (field.disabled !== undefined) entry.disabled = field.disabled\n\t\tif (field.locked !== undefined) entry.locked = field.locked\n\t\tconst meta = field.meta\n\t\tif (meta !== undefined) {\n\t\t\ttry {\n\t\t\t\tentry.meta = cloneJSONRecord(meta)\n\t\t\t} catch (error) {\n\t\t\t\tif (!isContractError(error)) throw error\n\t\t\t\tthrow new FormError('SCHEMA', `Field \"${field.name}\" has metadata that cannot be owned`, {\n\t\t\t\t\tfield: field.name,\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\n\t\tswitch (field.control) {\n\t\t\tcase 'text':\n\t\t\tcase 'editor':\n\t\t\tcase 'number':\n\t\t\t\tif (field.default !== undefined) entry.default = field.default\n\t\t\t\tif (field.placeholder !== undefined) entry.placeholder = field.placeholder\n\t\t\t\tbreak\n\t\t\tcase 'password':\n\t\t\t\tif (field.mask !== undefined) entry.mask = field.mask\n\t\t\t\tbreak\n\t\t\tcase 'date':\n\t\t\tcase 'time':\n\t\t\tcase 'datetime':\n\t\t\tcase 'color':\n\t\t\tcase 'confirm':\n\t\t\t\tif (field.default !== undefined) entry.default = field.default\n\t\t\t\tbreak\n\t\t\tcase 'select':\n\t\t\t\tentry.choices = field.choices.map((choice): JSONRecord => {\n\t\t\t\t\tconst option: Record<string, JSONValue> = {\n\t\t\t\t\t\tvalue: choice.value,\n\t\t\t\t\t\tlabel: choice.label,\n\t\t\t\t\t}\n\t\t\t\t\tif (choice.help !== undefined) option.help = choice.help\n\t\t\t\t\tif (choice.disabled !== undefined) option.disabled = choice.disabled\n\t\t\t\t\treturn option\n\t\t\t\t})\n\t\t\t\tif (field.default !== undefined) entry.default = field.default\n\t\t\t\tif (field.open !== undefined) entry.open = field.open\n\t\t\t\tbreak\n\t\t\tcase 'checkbox':\n\t\t\t\tentry.choices = field.choices.map((choice): JSONRecord => {\n\t\t\t\t\tconst option: Record<string, JSONValue> = {\n\t\t\t\t\t\tvalue: choice.value,\n\t\t\t\t\t\tlabel: choice.label,\n\t\t\t\t\t}\n\t\t\t\t\tif (choice.help !== undefined) option.help = choice.help\n\t\t\t\t\tif (choice.disabled !== undefined) option.disabled = choice.disabled\n\t\t\t\t\treturn option\n\t\t\t\t})\n\t\t\t\tif (field.default !== undefined) entry.default = field.default\n\t\t\t\tbreak\n\t\t\tcase 'file':\n\t\t\t\tif (field.accept !== undefined) entry.accept = field.accept\n\t\t\t\tif (field.multiple !== undefined) entry.multiple = field.multiple\n\t\t\t\tbreak\n\t\t}\n\n\t\tif (field.rule !== undefined) {\n\t\t\tconst rule: Record<string, JSONValue> = {}\n\n\t\t\tif (field.rule.required !== undefined) rule.required = field.rule.required\n\t\t\tif (field.rule.minimum !== undefined) rule.minimum = field.rule.minimum\n\t\t\tif (field.rule.maximum !== undefined) rule.maximum = field.rule.maximum\n\t\t\tif (field.rule.step !== undefined) rule.step = field.rule.step\n\t\t\tif (field.rule.pattern !== undefined) rule.pattern = field.rule.pattern\n\t\t\tif (field.rule.email !== undefined) rule.email = field.rule.email\n\t\t\tif (field.rule.url !== undefined) rule.url = field.rule.url\n\t\t\tif (field.rule.integer !== undefined) rule.integer = field.rule.integer\n\t\t\tif (field.rule.alphanumeric !== undefined) rule.alphanumeric = field.rule.alphanumeric\n\t\t\tif (Object.keys(rule).length > 0) entry.rule = rule\n\t\t}\n\n\t\treturn entry\n\t})\n\n\treturn cloneJSONRecord(output)\n}\n\n/**\n * Selects referenced groups in first-reference field order.\n *\n * @param schema - The schema whose group references to resolve.\n * @returns The referenced schema groups without duplicates.\n */\nexport function extractGroups(schema: FormSchema): readonly FormGroup[] {\n\tconst groups: FormGroup[] = []\n\n\tif (schema.groups === undefined) return Object.freeze(groups)\n\n\tfor (const field of schema.fields) {\n\t\tif (field.group !== undefined && !groups.some((group) => group.name === field.group)) {\n\t\t\tconst group = schema.groups.find((entry) => entry.name === field.group)\n\t\t\tif (group !== undefined) groups.push(group)\n\t\t}\n\t}\n\n\treturn Object.freeze(groups)\n}\n\n/**\n * Audits a structurally valid schema for domain invariants.\n *\n * @param schema - The form schema to audit.\n * @returns Human-readable invariant violations, or an empty list when the schema is sound.\n */\nexport function auditSchema(schema: FormSchema): readonly string[] {\n\tconst faults: string[] = []\n\tconst fields = new Set<string>()\n\tconst groups = new Set<string>()\n\tlet choiceExceeded: string | undefined\n\tlet nameExceeded = schema.name !== undefined && schema.name.length > NAME_LIMIT\n\n\tif (schema.fields.length > FIELD_LIMIT) {\n\t\tfaults.push(`Schema declares more than ${FIELD_LIMIT} fields`)\n\t}\n\tif (schema.groups !== undefined && schema.groups.length > GROUP_LIMIT) {\n\t\tfaults.push(`Schema declares more than ${GROUP_LIMIT} groups`)\n\t}\n\n\tif (schema.groups !== undefined) {\n\t\tconst count = Math.min(schema.groups.length, GROUP_LIMIT + 1)\n\t\tfor (let index = 0; index < count; index += 1) {\n\t\t\tconst group = schema.groups[index]\n\t\t\tif (group !== undefined && group.name.length > NAME_LIMIT) nameExceeded = true\n\t\t}\n\t}\n\n\tconst fieldCount = Math.min(schema.fields.length, FIELD_LIMIT + 1)\n\tfor (let index = 0; index < fieldCount; index += 1) {\n\t\tconst field = schema.fields[index]\n\t\tif (field === undefined) continue\n\n\t\tif (\n\t\t\tfield.name.length > NAME_LIMIT ||\n\t\t\t(field.group !== undefined && field.group.length > NAME_LIMIT)\n\t\t) {\n\t\t\tnameExceeded = true\n\t\t}\n\t\tif (\n\t\t\tchoiceExceeded === undefined &&\n\t\t\t(field.control === 'select' || field.control === 'checkbox') &&\n\t\t\tfield.choices.length > CHOICE_LIMIT\n\t\t) {\n\t\t\tchoiceExceeded = field.name\n\t\t}\n\t}\n\n\tif (choiceExceeded !== undefined) {\n\t\tfaults.push(`Field \"${choiceExceeded}\" offers more than ${CHOICE_LIMIT} choices`)\n\t}\n\tif (nameExceeded) faults.push(`Schema contains a name longer than ${NAME_LIMIT}`)\n\n\tconst pending: unknown[] = [schema]\n\tconst metadata: boolean[] = [false]\n\tlet position = 0\n\tlet stringExceeded = false\n\tlet textExceeded = false\n\tlet nodeExceeded = false\n\tlet text = 0\n\n\twhile (position < pending.length) {\n\t\tconst node = pending[position]\n\t\tconst inMeta = metadata[position] === true\n\t\tposition += 1\n\n\t\tif (isString(node)) {\n\t\t\tif (node.length > STRING_LIMIT) stringExceeded = true\n\t\t\ttext = Math.min(TEXT_LIMIT + 1, text + node.length)\n\t\t\tif (text > TEXT_LIMIT) textExceeded = true\n\t\t\tcontinue\n\t\t}\n\n\t\tif (isArray(node)) {\n\t\t\tconst length = attempt(() => node.length)\n\t\t\tif (!length.success || length.value > NODE_LIMIT - pending.length) {\n\t\t\t\tnodeExceeded = true\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tconst read = readArrayEntries(node)\n\t\t\tif (!read.success || !read.value.dense) continue\n\n\t\t\tfor (let index = 0; index < read.value.entries.length; index += 1) {\n\t\t\t\tconst entry = read.value.entries[index]\n\t\t\t\tif (entry !== undefined) {\n\t\t\t\t\tpending.push(entry)\n\t\t\t\t\tmetadata.push(inMeta)\n\t\t\t\t}\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\n\t\tif (!isRecord(node)) continue\n\n\t\tconst keys = attempt(() => Object.keys(node))\n\t\tif (!keys.success) continue\n\n\t\tfor (const key of keys.value) {\n\t\t\tif (inMeta) {\n\t\t\t\tif (key.length > STRING_LIMIT) stringExceeded = true\n\t\t\t\ttext = Math.min(TEXT_LIMIT + 1, text + key.length)\n\t\t\t\tif (text > TEXT_LIMIT) textExceeded = true\n\t\t\t}\n\n\t\t\tconst value = attempt(() => node[key])\n\t\t\tif (!value.success || value.value === undefined) continue\n\t\t\tif (pending.length >= NODE_LIMIT) {\n\t\t\t\tnodeExceeded = true\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tpending.push(value.value)\n\t\t\tmetadata.push(inMeta || key === 'meta')\n\t\t}\n\t}\n\n\tif (stringExceeded) faults.push(`Schema contains a string longer than ${STRING_LIMIT}`)\n\tif (textExceeded) faults.push(`Schema retains more than ${TEXT_LIMIT} string code units`)\n\tif (nodeExceeded) faults.push(`Schema retains more than ${NODE_LIMIT} nodes`)\n\n\tif (schema.groups !== undefined) {\n\t\tconst count = Math.min(schema.groups.length, GROUP_LIMIT + 1)\n\t\tfor (let index = 0; index < count; index += 1) {\n\t\t\tconst group = schema.groups[index]\n\t\t\tif (group === undefined) continue\n\t\t\tif (groups.has(group.name)) faults.push(`Group \"${group.name}\" is declared more than once`)\n\t\t\tgroups.add(group.name)\n\t\t}\n\t}\n\n\tfor (let fieldIndex = 0; fieldIndex < fieldCount; fieldIndex += 1) {\n\t\tconst field = schema.fields[fieldIndex]\n\t\tif (field === undefined) continue\n\t\tif (field.name.length === 0) faults.push('Field \"\" has an empty name')\n\t\tif (field.name === '__proto__') faults.push('Field \"__proto__\" has a refused name')\n\t\tif (fields.has(field.name)) faults.push(`Field \"${field.name}\" is declared more than once`)\n\t\tfields.add(field.name)\n\n\t\tif (field.group !== undefined && !groups.has(field.group)) {\n\t\t\tfaults.push(`Field \"${field.name}\" references missing group \"${field.group}\"`)\n\t\t}\n\n\t\tswitch (field.control) {\n\t\t\tcase 'password':\n\t\t\tcase 'file':\n\t\t\t\tbreak\n\t\t\tcase 'text':\n\t\t\tcase 'editor':\n\t\t\tcase 'number':\n\t\t\tcase 'date':\n\t\t\tcase 'time':\n\t\t\tcase 'datetime':\n\t\t\tcase 'color':\n\t\t\tcase 'confirm':\n\t\t\tcase 'select':\n\t\t\tcase 'checkbox':\n\t\t\t\tif (field.default !== undefined && !matchesField(field, field.default)) {\n\t\t\t\t\tfaults.push(`Field \"${field.name}\" has an invalid default`)\n\t\t\t\t}\n\t\t\t\tbreak\n\t\t}\n\n\t\tif (field.control === 'select' || field.control === 'checkbox') {\n\t\t\tconst choices = new Set<string>()\n\t\t\tconst count = Math.min(field.choices.length, CHOICE_LIMIT + 1)\n\t\t\tlet enabled = 0\n\n\t\t\tfor (let index = 0; index < count; index += 1) {\n\t\t\t\tconst choice = field.choices[index]\n\t\t\t\tif (choice === undefined) continue\n\t\t\t\tif (choice.disabled !== true) enabled += 1\n\t\t\t\tif (choices.has(choice.value)) {\n\t\t\t\t\tfaults.push(`Field \"${field.name}\" offers choice \"${choice.value}\" more than once`)\n\t\t\t\t}\n\t\t\t\tchoices.add(choice.value)\n\t\t\t}\n\n\t\t\tif (\n\t\t\t\tfield.control === 'select' &&\n\t\t\t\tfield.rule?.required === true &&\n\t\t\t\tfield.open !== true &&\n\t\t\t\tenabled === 0\n\t\t\t) {\n\t\t\t\tfaults.push(`Field \"${field.name}\" is required but offers no enabled choice`)\n\t\t\t}\n\n\t\t\tconst minimum = field.rule?.minimum\n\t\t\tif (\n\t\t\t\tfield.control === 'checkbox' &&\n\t\t\t\tisFiniteNumber(minimum) &&\n\t\t\t\tminimum > 0 &&\n\t\t\t\tminimum > enabled\n\t\t\t) {\n\t\t\t\tfaults.push(\n\t\t\t\t\t`Field \"${field.name}\" has minimum ${minimum} but offers only ${enabled} enabled ${enabled === 1 ? 'choice' : 'choices'}`,\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\n\t\tconst rule = field.rule\n\t\tif (rule === undefined) continue\n\n\t\tconst temporal =\n\t\t\tfield.control === 'date' || field.control === 'time' || field.control === 'datetime'\n\t\tif (rule.minimum !== undefined && !appliesRule(field.control, 'minimum')) {\n\t\t\tfaults.push(`Field \"${field.name}\" has minimum on ${field.control}`)\n\t\t} else if (isString(rule.minimum) && !temporal) {\n\t\t\tfaults.push(`Field \"${field.name}\" has a string minimum on ${field.control}`)\n\t\t}\n\t\tif (rule.maximum !== undefined && !appliesRule(field.control, 'maximum')) {\n\t\t\tfaults.push(`Field \"${field.name}\" has maximum on ${field.control}`)\n\t\t} else if (isString(rule.maximum) && !temporal) {\n\t\t\tfaults.push(`Field \"${field.name}\" has a string maximum on ${field.control}`)\n\t\t}\n\t\tif (isFiniteNumber(rule.minimum) && temporal) {\n\t\t\tfaults.push(`Field \"${field.name}\" has a numeric minimum on ${field.control}`)\n\t\t}\n\t\tif (isFiniteNumber(rule.maximum) && temporal) {\n\t\t\tfaults.push(`Field \"${field.name}\" has a numeric maximum on ${field.control}`)\n\t\t}\n\t\tif (\n\t\t\tisFiniteNumber(rule.maximum) &&\n\t\t\trule.maximum < 0 &&\n\t\t\t(field.control === 'text' ||\n\t\t\t\tfield.control === 'editor' ||\n\t\t\t\tfield.control === 'password' ||\n\t\t\t\tfield.control === 'checkbox' ||\n\t\t\t\tfield.control === 'file')\n\t\t) {\n\t\t\tfaults.push(`Field \"${field.name}\" has a negative maximum on ${field.control}`)\n\t\t}\n\n\t\tif (rule.step !== undefined && !appliesRule(field.control, 'step')) {\n\t\t\tfaults.push(`Field \"${field.name}\" has step on ${field.control}`)\n\t\t}\n\t\tif (rule.step !== undefined && rule.step <= 0) {\n\t\t\tfaults.push(`Field \"${field.name}\" has a non-positive step`)\n\t\t}\n\n\t\tif (rule.pattern !== undefined && !appliesRule(field.control, 'pattern')) {\n\t\t\tfaults.push(`Field \"${field.name}\" has pattern on ${field.control}`)\n\t\t}\n\t\tif (rule.email === true && !appliesRule(field.control, 'email')) {\n\t\t\tfaults.push(`Field \"${field.name}\" has email on ${field.control}`)\n\t\t}\n\t\tif (rule.url === true && !appliesRule(field.control, 'url')) {\n\t\t\tfaults.push(`Field \"${field.name}\" has url on ${field.control}`)\n\t\t}\n\t\tif (rule.alphanumeric === true && !appliesRule(field.control, 'alphanumeric')) {\n\t\t\tfaults.push(`Field \"${field.name}\" has alphanumeric on ${field.control}`)\n\t\t}\n\t\tif (rule.integer === true && !appliesRule(field.control, 'integer')) {\n\t\t\tfaults.push(`Field \"${field.name}\" has integer on ${field.control}`)\n\t\t}\n\n\t\tif (rule.pattern !== undefined) {\n\t\t\tif (rule.pattern.length > PATTERN_LIMIT) {\n\t\t\t\tfaults.push(`Field \"${field.name}\" has a pattern longer than ${PATTERN_LIMIT}`)\n\t\t\t} else {\n\t\t\t\ttry {\n\t\t\t\t\tRegExp(rule.pattern)\n\t\t\t\t} catch {\n\t\t\t\t\tfaults.push(`Field \"${field.name}\" has an invalid pattern`)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (\n\t\t\t(isFiniteNumber(rule.minimum) &&\n\t\t\t\tisFiniteNumber(rule.maximum) &&\n\t\t\t\trule.minimum > rule.maximum) ||\n\t\t\t(isString(rule.minimum) && isString(rule.maximum) && rule.minimum > rule.maximum)\n\t\t) {\n\t\t\tfaults.push(`Field \"${field.name}\" has minimum greater than maximum`)\n\t\t}\n\n\t\tif (field.control === 'date') {\n\t\t\tif (isString(rule.minimum) && !DATE_PATTERN.test(rule.minimum)) {\n\t\t\t\tfaults.push(`Field \"${field.name}\" has an invalid date minimum`)\n\t\t\t}\n\t\t\tif (isString(rule.maximum) && !DATE_PATTERN.test(rule.maximum)) {\n\t\t\t\tfaults.push(`Field \"${field.name}\" has an invalid date maximum`)\n\t\t\t}\n\t\t}\n\n\t\tif (field.control === 'time') {\n\t\t\tif (isString(rule.minimum) && !TIME_PATTERN.test(rule.minimum)) {\n\t\t\t\tfaults.push(`Field \"${field.name}\" has an invalid time minimum`)\n\t\t\t}\n\t\t\tif (isString(rule.maximum) && !TIME_PATTERN.test(rule.maximum)) {\n\t\t\t\tfaults.push(`Field \"${field.name}\" has an invalid time maximum`)\n\t\t\t}\n\t\t}\n\n\t\tif (field.control === 'datetime') {\n\t\t\tif (isString(rule.minimum) && !DATETIME_PATTERN.test(rule.minimum)) {\n\t\t\t\tfaults.push(`Field \"${field.name}\" has an invalid datetime minimum`)\n\t\t\t}\n\t\t\tif (isString(rule.maximum) && !DATETIME_PATTERN.test(rule.maximum)) {\n\t\t\t\tfaults.push(`Field \"${field.name}\" has an invalid datetime maximum`)\n\t\t\t}\n\t\t}\n\t}\n\n\treturn Object.freeze(faults)\n}\n","import type { FieldValue, FormField } from '../core/types.js'\nimport type { FormControl } from './types.js'\nimport { matchesAnswer, matchesField } from '../core/helpers.js'\nimport { FormBindingError } from './errors.js'\n\n/**\n * Checks a native control population against a field's declared value shape.\n * @param field - The declared field.\n * @param controls - Associated controls sharing its name.\n * @returns True if the population has an unambiguous compatible shape; false otherwise.\n * @example\n * ```ts\n * matchesFormControls({ control: 'text', name: 'name' }, [document.createElement('input')]) // true\n * ```\n */\nexport function matchesFormControls(field: FormField, controls: readonly FormControl[]): boolean {\n\tif (controls.length === 0) return true\n\tif (controls.length > 1 && field.control !== 'checkbox' && field.control !== 'select')\n\t\treturn false\n\treturn controls.every((control) => {\n\t\tif (control instanceof HTMLTextAreaElement)\n\t\t\treturn field.control === 'editor' && controls.length === 1\n\t\tif (control instanceof HTMLSelectElement) {\n\t\t\treturn (\n\t\t\t\tcontrols.length === 1 &&\n\t\t\t\t(control.multiple ? field.control === 'checkbox' : field.control === 'select')\n\t\t\t)\n\t\t}\n\t\tswitch (field.control) {\n\t\t\tcase 'text':\n\t\t\t\treturn ['text', 'search', 'email', 'url', 'tel', 'hidden'].includes(control.type)\n\t\t\tcase 'password':\n\t\t\t\treturn control.type === 'password'\n\t\t\tcase 'number':\n\t\t\t\treturn control.type === 'number' || control.type === 'range'\n\t\t\tcase 'datetime':\n\t\t\t\treturn control.type === 'datetime-local'\n\t\t\tcase 'confirm':\n\t\t\t\treturn control.type === 'checkbox'\n\t\t\tcase 'checkbox':\n\t\t\t\treturn (\n\t\t\t\t\tcontrol.type === 'checkbox' &&\n\t\t\t\t\tcontrols.every((entry) => entry instanceof HTMLInputElement)\n\t\t\t\t)\n\t\t\tcase 'select':\n\t\t\t\treturn (\n\t\t\t\t\tcontrol.type === 'radio' &&\n\t\t\t\t\tcontrols.every((entry) => entry instanceof HTMLInputElement && entry.type === 'radio')\n\t\t\t\t)\n\t\t\tcase 'editor':\n\t\t\t\treturn false\n\t\t\tdefault:\n\t\t\t\treturn control.type === field.control\n\t\t}\n\t})\n}\n\n/**\n * Reads one compatible native population using the document's blank-to-absence semantics.\n * @param field - The field declaring the answer shape.\n * @param controls - Controls sharing the field's name, in native order.\n * @returns The native answer, or undefined for an absent answer or empty population.\n * @throws Thrown when the population or answer is incompatible, including numeric bad input.\n * @example\n * ```ts\n * const input = document.createElement('input')\n * input.value = 'Ada'\n * readFormControls({ control: 'text', name: 'name' }, [input]) // 'Ada'\n * ```\n */\nexport function readFormControls(\n\tfield: FormField,\n\tcontrols: readonly FormControl[],\n): FieldValue | undefined {\n\tif (!matchesFormControls(field, controls)) {\n\t\tthrow new FormBindingError('CONTROL', `Incompatible controls for \"${field.name}\"`, {\n\t\t\tfield: field.name,\n\t\t})\n\t}\n\tconst first = controls[0]\n\tif (first === undefined) return undefined\n\tlet value: FieldValue | undefined\n\tif (field.control === 'checkbox') {\n\t\tvalue =\n\t\t\tfirst instanceof HTMLSelectElement\n\t\t\t\t? Array.from(first.selectedOptions, (option) => option.value)\n\t\t\t\t: controls\n\t\t\t\t\t\t.filter((control) => control instanceof HTMLInputElement && control.checked)\n\t\t\t\t\t\t.map((control) => control.value)\n\t} else if (field.control === 'select' && first instanceof HTMLInputElement) {\n\t\tvalue = controls.find(\n\t\t\t(control) => control instanceof HTMLInputElement && control.checked,\n\t\t)?.value\n\t} else if (field.control === 'confirm' && first instanceof HTMLInputElement) {\n\t\tvalue = first.checked\n\t} else if (field.control === 'number' && first instanceof HTMLInputElement) {\n\t\tif (first.validity.badInput)\n\t\t\tthrow new FormBindingError('CONTROL', 'Enter a number', { field: field.name })\n\t\tvalue = first.value === '' ? undefined : first.valueAsNumber\n\t} else if (field.control === 'file' && first instanceof HTMLInputElement) {\n\t\tvalue = Array.from(first.files ?? [], (file) => file.name)\n\t} else {\n\t\tvalue = first.value\n\t}\n\tif (!matchesAnswer(value)) return undefined\n\tif (value !== undefined && !matchesField(field, value)) {\n\t\tthrow new FormBindingError('CONTROL', `Incompatible native answer for \"${field.name}\"`, {\n\t\t\tfield: field.name,\n\t\t})\n\t}\n\treturn value\n}\n","import type { FormControl } from './types.js'\nimport { isInstance } from '@orkestrel/contract'\n\n/**\n * Checks whether a value is a native answer control.\n * @param value - The value to inspect.\n * @returns True if the value is an input, select, or textarea; false otherwise.\n * @example\n * ```ts\n * isFormControl(document.createElement('input')) // true\n * ```\n */\nexport function isFormControl(value: unknown): value is FormControl {\n\treturn (\n\t\t(typeof HTMLInputElement !== 'undefined' && isInstance(value, HTMLInputElement)) ||\n\t\t(typeof HTMLSelectElement !== 'undefined' && isInstance(value, HTMLSelectElement)) ||\n\t\t(typeof HTMLTextAreaElement !== 'undefined' && isInstance(value, HTMLTextAreaElement))\n\t)\n}\n","import type { EmitterInterface } from '@orkestrel/emitter'\nimport type { FieldError, FieldValue, FormField, FormInterface } from '../core/types.js'\nimport type {\n\tFormBindingAttribute,\n\tFormBindingEventMap,\n\tFormBindingInterface,\n\tFormBindingOptions,\n\tFormBindingValidity,\n\tFormControl,\n} from './types.js'\nimport { Emitter } from '@orkestrel/emitter'\nimport { matchesValue } from '../core/helpers.js'\nimport { FormBindingError, isFormBindingError } from './errors.js'\nimport { matchesFormControls, readFormControls } from './helpers.js'\nimport { isFormControl } from './validators.js'\n\n/** Implements a controlled native form over a borrowed document. */\nexport class FormBinding implements FormBindingInterface {\n\treadonly #element: HTMLFormElement\n\treadonly #document: FormInterface\n\treadonly #options: FormBindingOptions | undefined\n\treadonly #emitter: Emitter<FormBindingEventMap>\n\treadonly #observer: MutationObserver\n\treadonly #listeners = new AbortController()\n\treadonly #attributes = new Map<Element, Map<string, FormBindingAttribute>>()\n\treadonly #validity = new Map<FormControl, FormBindingValidity>()\n\treadonly #feedback = new Map<string, HTMLElement>()\n\treadonly #excluded = new Set<string>()\n\treadonly #logical = new Set<string>()\n\treadonly #unreadable = new Set<string>()\n\treadonly #names = new Map<FormControl, string>()\n\treadonly #refresh = this.#onState.bind(this)\n\treadonly #fill = this.#onFill.bind(this)\n\treadonly #disable = this.#onDisable.bind(this)\n\treadonly #enable = this.#onEnable.bind(this)\n\treadonly #clear = this.#onClear.bind(this)\n\treadonly #abandon = this.destroy.bind(this)\n\t#controls: ReadonlyMap<string, readonly FormControl[]> = new Map()\n\t#summary: HTMLUListElement | undefined\n\t#summaryText = ''\n\t#destroyed = false\n\t#running = false\n\t#queued = false\n\t#reading = false\n\t#excluding: string | undefined\n\t#changed = false\n\t#notified = false\n\t#scheduled = false\n\t#submitting = false\n\n\t/**\n\t * Binds existing controls to the supplied document's answers.\n\t * @param element - The native form, including its externally associated controls.\n\t * @param document - The borrowed answer document.\n\t * @param options - Feedback destinations and listeners.\n\t * @throws Thrown when controls contradict the schema or native initialization fails.\n\t */\n\tconstructor(element: HTMLFormElement, document: FormInterface, options?: FormBindingOptions) {\n\t\tthis.#element = element\n\t\tthis.#document = document\n\t\tthis.#options = options\n\t\tthis.#emitter = new Emitter<FormBindingEventMap>(options)\n\t\tthis.#observer = new MutationObserver(() => this.#onDocument())\n\t\tfor (const name of document.disabled) this.#logical.add(name)\n\t\ttry {\n\t\t\tthis.#controls = this.#discover()\n\t\t\tconst signal = this.#listeners.signal\n\t\t\telement.addEventListener('submit', (event) => this.#onSubmit(event), { signal })\n\t\t\telement.addEventListener('reset', (event) => this.#onReset(event), { signal })\n\t\t\telement.ownerDocument.addEventListener('input', (event) => this.#onInput(event), { signal })\n\t\t\telement.ownerDocument.addEventListener('change', (event) => this.#onInput(event), { signal })\n\t\t\telement.ownerDocument.addEventListener('focusout', (event) => this.#onBlur(event), { signal })\n\t\t\telement.ownerDocument.addEventListener('click', (event) => this.#onClick(event), { signal })\n\t\t\tdocument.emitter.on('fill', this.#fill)\n\t\t\tdocument.emitter.on('disable', this.#disable)\n\t\t\tdocument.emitter.on('enable', this.#enable)\n\t\t\tdocument.emitter.on('clear', this.#clear)\n\t\t\tdocument.emitter.on('abandon', this.#abandon)\n\t\t\tfor (const event of ['touch', 'validate', 'prepare', 'accept', 'abort', 'submit'] as const) {\n\t\t\t\tdocument.emitter.on(event, this.#refresh)\n\t\t\t}\n\t\t\tthis.refresh()\n\t\t\tif (!this.#destroyed) {\n\t\t\t\tthis.#observer.observe(element.ownerDocument, {\n\t\t\t\t\tsubtree: true,\n\t\t\t\t\tchildList: true,\n\t\t\t\t\tattributes: true,\n\t\t\t\t\tattributeFilter: [\n\t\t\t\t\t\t'name',\n\t\t\t\t\t\t'form',\n\t\t\t\t\t\t'id',\n\t\t\t\t\t\t'type',\n\t\t\t\t\t\t'multiple',\n\t\t\t\t\t\t'disabled',\n\t\t\t\t\t\t'required',\n\t\t\t\t\t\t'min',\n\t\t\t\t\t\t'max',\n\t\t\t\t\t\t'step',\n\t\t\t\t\t\t'pattern',\n\t\t\t\t\t\t'minlength',\n\t\t\t\t\t\t'maxlength',\n\t\t\t\t\t\t'value',\n\t\t\t\t\t\t'hidden',\n\t\t\t\t\t\t'inert',\n\t\t\t\t\t\t'style',\n\t\t\t\t\t\t'class',\n\t\t\t\t\t],\n\t\t\t\t})\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tthis.destroy()\n\t\t\tthrow error\n\t\t}\n\t}\n\n\t/** Holds the native form. */\n\tget element(): HTMLFormElement {\n\t\treturn this.#element\n\t}\n\t/** Holds the borrowed answer document. */\n\tget document(): FormInterface {\n\t\treturn this.#document\n\t}\n\t/** Reports whether the binding released its resources. */\n\tget destroyed(): boolean {\n\t\treturn this.#destroyed\n\t}\n\t/** Holds the binding's isolated event emitter. */\n\tget emitter(): EmitterInterface<FormBindingEventMap> {\n\t\treturn this.#emitter\n\t}\n\n\t/** Imports mounted enabled controls without acknowledging their answers. */\n\tread(): void {\n\t\tif (this.#destroyed || this.#reading || this.#document.status !== 'editing') return\n\t\tconst controls = this.#discover()\n\t\tconst answers = new Map<string, FieldValue | undefined>()\n\t\tfor (const field of this.#document.schema.fields) {\n\t\t\tconst group = controls.get(field.name)\n\t\t\tif (group === undefined || field.locked || this.#logical.has(field.name)) continue\n\t\t\tconst enabled = group.filter((control) => !this.#nativeDisabled(control))\n\t\t\tif (enabled.length === 0) continue\n\t\t\tif (enabled.some((control) => control.validity.badInput)) {\n\t\t\t\tthis.#unreadable.add(field.name)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tthis.#unreadable.delete(field.name)\n\t\t\tanswers.set(field.name, readFormControls(field, enabled))\n\t\t}\n\t\tthis.#controls = controls\n\t\tthis.#reading = true\n\t\ttry {\n\t\t\tthis.#exclusions()\n\t\t\tfor (const [name, value] of answers) {\n\t\t\t\tif (this.#destroyed) break\n\t\t\t\tconst values = this.#document.values\n\t\t\t\tconst previous = Object.hasOwn(values, name) ? values[name] : undefined\n\t\t\t\tif (\n\t\t\t\t\tprevious === undefined || value === undefined\n\t\t\t\t\t\t? previous !== value\n\t\t\t\t\t\t: !matchesValue(previous, value)\n\t\t\t\t)\n\t\t\t\t\tthis.#document.fill(name, value)\n\t\t\t}\n\t\t} finally {\n\t\t\tthis.#reading = false\n\t\t\tif (!this.#destroyed) this.#drain()\n\t\t}\n\t}\n\n\t/** Rediscovers controls and projects the document into the native form. */\n\trefresh(): void {\n\t\tif (this.#destroyed) return\n\t\tthis.#unreadable.clear()\n\t\tthis.#drain()\n\t}\n\n\t/** Releases owned DOM effects and listeners without destroying the document. */\n\tdestroy(): void {\n\t\tif (this.#destroyed) return\n\t\tthis.#destroyed = true\n\t\tthis.#observer.disconnect()\n\t\tthis.#listeners.abort()\n\t\tthis.#document.emitter.off('fill', this.#fill)\n\t\tthis.#document.emitter.off('disable', this.#disable)\n\t\tthis.#document.emitter.off('enable', this.#enable)\n\t\tthis.#document.emitter.off('clear', this.#clear)\n\t\tthis.#document.emitter.off('abandon', this.#abandon)\n\t\tfor (const event of ['touch', 'validate', 'prepare', 'accept', 'abort', 'submit'] as const) {\n\t\t\tthis.#document.emitter.off(event, this.#refresh)\n\t\t}\n\t\tfor (const element of this.#attributes.keys()) this.#restore(element)\n\t\tfor (const control of this.#validity.keys()) this.#restoreValidity(control)\n\t\tfor (const node of this.#feedback.values()) node.remove()\n\t\tthis.#summary?.remove()\n\t\tif (this.#document.status === 'editing') {\n\t\t\tfor (const name of this.#excluded) {\n\t\t\t\tif (!this.#logical.has(name)) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tthis.#document.enable(name)\n\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\tthis.#emitter.emit('error', error)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tthis.#excluded.clear()\n\t\tthis.#feedback.clear()\n\t\tthis.#names.clear()\n\t\tthis.#unreadable.clear()\n\t\tthis.#logical.clear()\n\t\tthis.#controls = new Map()\n\t\tthis.#summary = undefined\n\t\tthis.#emitter.destroy()\n\t}\n\n\t#drain(): void {\n\t\tif (this.#destroyed) return\n\t\tthis.#queued = true\n\t\tif (this.#running || this.#reading) return\n\t\tthis.#running = true\n\t\ttry {\n\t\t\tlet turns = 0\n\t\t\twhile (this.#queued && !this.#destroyed && turns < 2) {\n\t\t\t\tthis.#queued = false\n\t\t\t\tthis.#changed = false\n\t\t\t\tthis.#controls = this.#discover()\n\t\t\t\tthis.#releaseRemoved()\n\t\t\t\tthis.#exclusions()\n\t\t\t\tif (this.#destroyed) break\n\t\t\t\tthis.#paint()\n\t\t\t\tif (!this.#destroyed && (this.#changed || !this.#notified)) {\n\t\t\t\t\tthis.#notified = true\n\t\t\t\t\tthis.#emitter.emit('render')\n\t\t\t\t}\n\t\t\t\tturns += 1\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tthis.#queued = false\n\t\t\tthrow isFormBindingError(error)\n\t\t\t\t? error\n\t\t\t\t: new FormBindingError('DOM', 'Native form projection failed', { cause: error })\n\t\t} finally {\n\t\t\tthis.#running = false\n\t\t\tif (this.#queued && !this.#destroyed && !this.#scheduled) {\n\t\t\t\tthis.#scheduled = true\n\t\t\t\tqueueMicrotask(() => {\n\t\t\t\t\tthis.#scheduled = false\n\t\t\t\t\tif (!this.#destroyed) this.#onDocument()\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\t}\n\n\t#discover(): ReadonlyMap<string, readonly FormControl[]> {\n\t\tconst groups = new Map<string, FormControl[]>()\n\t\tfor (const control of Array.from(this.#element.elements)) {\n\t\t\tif (\n\t\t\t\t!isFormControl(control) ||\n\t\t\t\tcontrol.form !== this.#element ||\n\t\t\t\t!this.#document.field(control.name)\n\t\t\t)\n\t\t\t\tcontinue\n\t\t\tif (\n\t\t\t\tcontrol instanceof HTMLInputElement &&\n\t\t\t\t['submit', 'reset', 'button', 'image'].includes(control.type)\n\t\t\t)\n\t\t\t\tcontinue\n\t\t\tconst group = groups.get(control.name) ?? []\n\t\t\tgroup.push(control)\n\t\t\tgroups.set(control.name, group)\n\t\t}\n\t\tfor (const field of this.#document.schema.fields) {\n\t\t\tconst group = groups.get(field.name)\n\t\t\tif (group !== undefined && !matchesFormControls(field, group)) {\n\t\t\t\tthrow new FormBindingError('CONTROL', `Incompatible controls for \"${field.name}\"`, {\n\t\t\t\t\tfield: field.name,\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\t\treturn groups\n\t}\n\n\t#nativeDisabled(control: FormControl): boolean {\n\t\tconst owned = this.#attributes.get(control)?.get('disabled')\n\t\tif (\n\t\t\towned !== undefined &&\n\t\t\tcontrol.getAttribute('disabled') === owned.value &&\n\t\t\towned.original === null\n\t\t) {\n\t\t\t// A model-owned disabled attribute cannot become a native exclusion on the next scan.\n\t\t\tlet ancestor = control.parentElement\n\t\t\twhile (ancestor !== null) {\n\t\t\t\tif (ancestor instanceof HTMLFieldSetElement && ancestor.disabled) {\n\t\t\t\t\tconst legend = Array.from(ancestor.children).find(\n\t\t\t\t\t\t(child) => child instanceof HTMLLegendElement,\n\t\t\t\t\t)\n\t\t\t\t\tif (legend === undefined || !legend.contains(control)) return true\n\t\t\t\t}\n\t\t\t\tancestor = ancestor.parentElement\n\t\t\t}\n\t\t\treturn false\n\t\t}\n\t\treturn control.matches(':disabled')\n\t}\n\n\t#exclusions(): void {\n\t\tif (this.#document.status !== 'editing') return\n\t\tfor (const field of this.#document.schema.fields) {\n\t\t\tif (this.#destroyed) return\n\t\t\tconst group = this.#controls.get(field.name)\n\t\t\tconst excluded =\n\t\t\t\tgroup !== undefined && group.every((control) => this.#nativeDisabled(control))\n\t\t\tif (excluded && !this.#logical.has(field.name) && !this.#excluded.has(field.name)) {\n\t\t\t\tthis.#excluded.add(field.name)\n\t\t\t\tthis.#excluding = field.name\n\t\t\t\ttry {\n\t\t\t\t\tthis.#document.disable(field.name)\n\t\t\t\t} finally {\n\t\t\t\t\tthis.#excluding = undefined\n\t\t\t\t}\n\t\t\t} else if (!excluded && this.#excluded.delete(field.name) && !this.#logical.has(field.name)) {\n\t\t\t\tthis.#excluding = field.name\n\t\t\t\ttry {\n\t\t\t\t\tthis.#document.enable(field.name)\n\t\t\t\t} finally {\n\t\t\t\t\tthis.#excluding = undefined\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t#paint(): void {\n\t\tthis.#attribute(this.#element, 'novalidate', '')\n\t\tthis.#attribute(\n\t\t\tthis.#element,\n\t\t\t'aria-busy',\n\t\t\tthis.#document.pending === undefined ? 'false' : 'true',\n\t\t)\n\t\tconst errors: FieldError[] = [...this.#document.errors]\n\t\tconst values = this.#document.values\n\t\tfor (const field of this.#document.schema.fields) {\n\t\t\tif (this.#destroyed) return\n\t\t\tconst group = this.#controls.get(field.name) ?? []\n\t\t\tfor (const control of group) {\n\t\t\t\tthis.#metadata(field, control)\n\t\t\t\tif (!this.#unreadable.has(field.name))\n\t\t\t\t\tthis.#value(\n\t\t\t\t\t\tfield,\n\t\t\t\t\t\tcontrol,\n\t\t\t\t\t\tObject.hasOwn(values, field.name) ? values[field.name] : undefined,\n\t\t\t\t\t)\n\t\t\t\tthis.#restoreValidity(control)\n\t\t\t\tif (\n\t\t\t\t\t!this.#document.disabled.has(field.name) &&\n\t\t\t\t\t!control.validity.valid &&\n\t\t\t\t\tcontrol.willValidate\n\t\t\t\t) {\n\t\t\t\t\terrors.push({ field: field.name, message: control.validationMessage })\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tfor (const field of this.#document.schema.fields) {\n\t\t\tif (this.#destroyed) return\n\t\t\tconst messages = [\n\t\t\t\t...new Set(\n\t\t\t\t\terrors.filter((error) => error.field === field.name).map((error) => error.message),\n\t\t\t\t),\n\t\t\t]\n\t\t\tconst message = messages.join('\\n')\n\t\t\tconst visible =\n\t\t\t\tthis.#document.touched.has(field.name) && !this.#document.disabled.has(field.name)\n\t\t\tconst group = this.#controls.get(field.name) ?? []\n\t\t\tlet feedback = this.#feedback.get(field.name)\n\t\t\tif (\n\t\t\t\tfeedback === undefined &&\n\t\t\t\t(group.length > 0 || this.#options?.feedback?.has(field.name))\n\t\t\t) {\n\t\t\t\tfeedback = this.#element.ownerDocument.createElement('span')\n\t\t\t\tfeedback.id = `form-feedback-${crypto.randomUUID()}`\n\t\t\t\tfeedback.dataset.formFeedback = field.name\n\t\t\t\tconst destination = this.#options?.feedback?.get(field.name)\n\t\t\t\tif (destination !== undefined) destination.append(feedback)\n\t\t\t\telse (group.at(-1) ?? this.#element).insertAdjacentElement('afterend', feedback)\n\t\t\t\tthis.#feedback.set(field.name, feedback)\n\t\t\t}\n\t\t\tif (feedback !== undefined) {\n\t\t\t\tif (feedback.textContent !== message) {\n\t\t\t\t\tfeedback.textContent = message\n\t\t\t\t\tthis.#changed = true\n\t\t\t\t}\n\t\t\t\tif (feedback.hidden !== (!visible || message === '')) {\n\t\t\t\t\tfeedback.hidden = !visible || message === ''\n\t\t\t\t\tthis.#changed = true\n\t\t\t\t}\n\t\t\t}\n\t\t\tfor (const control of group) {\n\t\t\t\tif (message !== '' && !control.validity.customError) {\n\t\t\t\t\tthis.#validity.set(control, { original: '', value: message })\n\t\t\t\t\tcontrol.setCustomValidity(message)\n\t\t\t\t}\n\t\t\t\tthis.#attribute(control, 'aria-invalid', visible && message !== '' ? 'true' : null)\n\t\t\t\tif (feedback !== undefined)\n\t\t\t\t\tthis.#description(control, feedback.id, visible && message !== '')\n\t\t\t}\n\t\t}\n\t\tthis.#paintSummary(\n\t\t\terrors.filter(\n\t\t\t\t(error) =>\n\t\t\t\t\tthis.#document.touched.has(error.field) && !this.#document.disabled.has(error.field),\n\t\t\t),\n\t\t)\n\t\tfor (const control of Array.from(this.#element.elements)) {\n\t\t\tif (\n\t\t\t\t(control instanceof HTMLButtonElement && control.type === 'submit') ||\n\t\t\t\t(control instanceof HTMLInputElement && ['submit', 'image'].includes(control.type))\n\t\t\t) {\n\t\t\t\tthis.#attribute(\n\t\t\t\t\tcontrol,\n\t\t\t\t\t'aria-busy',\n\t\t\t\t\tthis.#document.pending === undefined ? 'false' : 'true',\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\t}\n\n\t#metadata(field: FormField, control: FormControl): void {\n\t\tif (this.#logical.has(field.name)) this.#attribute(control, 'disabled', '')\n\t\telse this.#releaseAttribute(control, 'disabled')\n\t\tif (\n\t\t\tfield.locked &&\n\t\t\t!(control instanceof HTMLSelectElement) &&\n\t\t\t!(\n\t\t\t\tcontrol instanceof HTMLInputElement &&\n\t\t\t\t['radio', 'checkbox', 'file', 'range', 'color'].includes(control.type)\n\t\t\t)\n\t\t)\n\t\t\tthis.#attribute(control, 'readonly', '')\n\t\tif (field.rule?.required && !['confirm', 'checkbox', 'file'].includes(field.control))\n\t\t\tthis.#attribute(control, 'required', '')\n\t\tif (\n\t\t\tfield.control === 'number' ||\n\t\t\tfield.control === 'date' ||\n\t\t\tfield.control === 'time' ||\n\t\t\tfield.control === 'datetime'\n\t\t) {\n\t\t\tif (field.rule?.minimum !== undefined && !control.hasAttribute('min'))\n\t\t\t\tthis.#attribute(control, 'min', String(field.rule.minimum))\n\t\t\tif (field.rule?.maximum !== undefined && !control.hasAttribute('max'))\n\t\t\t\tthis.#attribute(control, 'max', String(field.rule.maximum))\n\t\t\tif (\n\t\t\t\tfield.control === 'number' &&\n\t\t\t\tfield.rule?.step !== undefined &&\n\t\t\t\t!control.hasAttribute('step')\n\t\t\t)\n\t\t\t\tthis.#attribute(control, 'step', String(field.rule.step))\n\t\t}\n\t}\n\n\t#value(field: FormField, control: FormControl, value: FieldValue | undefined): void {\n\t\tif (control instanceof HTMLInputElement && control.type === 'file') {\n\t\t\t// The browser alone owns bytes. A differing saved name cannot create a FileList.\n\t\t\tconst names = Array.from(control.files ?? [], (file) => file.name)\n\t\t\tif (names.length > 0 && (value === undefined || !matchesValue(names, value))) {\n\t\t\t\tcontrol.value = ''\n\t\t\t\tthis.#changed = true\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t\tif (\n\t\t\tcontrol instanceof HTMLInputElement &&\n\t\t\t(control.type === 'checkbox' || control.type === 'radio')\n\t\t) {\n\t\t\tconst checked =\n\t\t\t\tfield.control === 'confirm'\n\t\t\t\t\t? value === true\n\t\t\t\t\t: Array.isArray(value)\n\t\t\t\t\t\t? value.includes(control.value)\n\t\t\t\t\t\t: value === control.value\n\t\t\tif (control.checked !== checked) {\n\t\t\t\tcontrol.checked = checked\n\t\t\t\tthis.#changed = true\n\t\t\t}\n\t\t} else if (control instanceof HTMLSelectElement && control.multiple) {\n\t\t\tfor (const option of Array.from(control.options)) {\n\t\t\t\tconst selected = Array.isArray(value) && value.includes(option.value)\n\t\t\t\tif (option.selected !== selected) {\n\t\t\t\t\toption.selected = selected\n\t\t\t\t\tthis.#changed = true\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tconst text = value === undefined ? '' : String(value)\n\t\t\tif (\n\t\t\t\tcontrol.value !== text ||\n\t\t\t\t(control instanceof HTMLInputElement && control.validity.badInput)\n\t\t\t) {\n\t\t\t\tconst before = control.value\n\t\t\t\tconst invalid = control.validity.badInput\n\t\t\t\tcontrol.value = text\n\t\t\t\tif (control.value !== before || invalid) this.#changed = true\n\t\t\t}\n\t\t}\n\t}\n\n\t#attribute(element: Element, name: string, value: string | null): void {\n\t\tconst attributes = this.#attributes.get(element) ?? new Map<string, FormBindingAttribute>()\n\t\tconst previous = attributes.get(name)\n\t\tconst current = element.getAttribute(name)\n\t\tconst original =\n\t\t\tprevious === undefined || previous.value !== current ? current : previous.original\n\t\tif (current !== value) {\n\t\t\tif (value === null) element.removeAttribute(name)\n\t\t\telse element.setAttribute(name, value)\n\t\t\tthis.#changed = true\n\t\t}\n\t\tattributes.set(name, { original, value })\n\t\tthis.#attributes.set(element, attributes)\n\t}\n\n\t#description(control: FormControl, id: string, shown: boolean): void {\n\t\tconst tokens = (control.getAttribute('aria-describedby') ?? '')\n\t\t\t.split(/\\s+/)\n\t\t\t.filter((token) => token !== '' && token !== id)\n\t\tif (shown) tokens.push(id)\n\t\tthis.#attribute(control, 'aria-describedby', tokens.length === 0 ? null : tokens.join(' '))\n\t}\n\n\t#releaseAttribute(element: Element, name: string): void {\n\t\tconst attributes = this.#attributes.get(element)\n\t\tconst owned = attributes?.get(name)\n\t\tif (owned === undefined) return\n\t\tif (element.getAttribute(name) === owned.value) {\n\t\t\tif (owned.original === null) element.removeAttribute(name)\n\t\t\telse element.setAttribute(name, owned.original)\n\t\t\tthis.#changed = true\n\t\t}\n\t\tattributes?.delete(name)\n\t}\n\n\t#restore(element: Element): void {\n\t\tfor (const name of this.#attributes.get(element)?.keys() ?? [])\n\t\t\tthis.#releaseAttribute(element, name)\n\t\tconst ids = new Set([...this.#feedback.values()].map((feedback) => feedback.id))\n\t\tconst description = element.getAttribute('aria-describedby')\n\t\tif (description !== null) {\n\t\t\tconst tokens = description.split(/\\s+/).filter((token) => token !== '' && !ids.has(token))\n\t\t\tif (tokens.length === 0) element.removeAttribute('aria-describedby')\n\t\t\telse if (tokens.join(' ') !== description)\n\t\t\t\telement.setAttribute('aria-describedby', tokens.join(' '))\n\t\t}\n\t\tthis.#attributes.delete(element)\n\t}\n\n\t#restoreValidity(control: FormControl): void {\n\t\tconst owned = this.#validity.get(control)\n\t\tif (\n\t\t\towned !== undefined &&\n\t\t\tcontrol.validity.customError &&\n\t\t\tcontrol.validationMessage === owned.value\n\t\t)\n\t\t\tcontrol.setCustomValidity(owned.original)\n\t\tthis.#validity.delete(control)\n\t}\n\n\t#releaseRemoved(): void {\n\t\tconst live = new Set([...this.#controls.values()].flat())\n\t\tfor (const [control, name] of this.#names) {\n\t\t\tif (!live.has(control) || control.name !== name) {\n\t\t\t\tthis.#restoreValidity(control)\n\t\t\t\tthis.#restore(control)\n\t\t\t\tthis.#names.delete(control)\n\t\t\t}\n\t\t}\n\t\tfor (const control of live) this.#names.set(control, control.name)\n\t\tfor (const element of this.#attributes.keys()) {\n\t\t\tif (\n\t\t\t\t(isFormControl(element) || element instanceof HTMLButtonElement) &&\n\t\t\t\telement.form !== this.#element\n\t\t\t)\n\t\t\t\tthis.#restore(element)\n\t\t}\n\t\tfor (const [name, feedback] of this.#feedback) {\n\t\t\tif (!this.#controls.has(name) && !this.#options?.feedback?.has(name)) {\n\t\t\t\tfeedback.remove()\n\t\t\t\tthis.#feedback.delete(name)\n\t\t\t}\n\t\t}\n\t}\n\n\t#paintSummary(errors: readonly FieldError[]): void {\n\t\tconst destination = this.#options?.summary\n\t\tif (destination === undefined) return\n\t\tconst text = JSON.stringify(\n\t\t\terrors.map((error) => [error.field, error.message, this.#focusable(error.field)?.id]),\n\t\t)\n\t\tif (text === this.#summaryText) return\n\t\tthis.#summaryText = text\n\t\tthis.#changed = true\n\t\tthis.#summary?.remove()\n\t\tthis.#summary = this.#element.ownerDocument.createElement('ul')\n\t\tthis.#summary.dataset.formSummary = ''\n\t\tthis.#summary.hidden = errors.length === 0\n\t\tfor (const error of errors) {\n\t\t\tconst entry = this.#element.ownerDocument.createElement('li')\n\t\t\tconst target = this.#focusable(error.field)\n\t\t\tconst label = this.#document.field(error.field)?.label ?? error.field\n\t\t\tif (target === undefined) entry.textContent = `${label}: ${error.message}`\n\t\t\telse {\n\t\t\t\tif (target.id === '') this.#attribute(target, 'id', `form-control-${crypto.randomUUID()}`)\n\t\t\t\tconst link = this.#element.ownerDocument.createElement('a')\n\t\t\t\tlink.href = `#${target.id}`\n\t\t\t\tlink.textContent = `${label}: ${error.message}`\n\t\t\t\tlink.dataset.formField = error.field\n\t\t\t\tentry.append(link)\n\t\t\t}\n\t\t\tthis.#summary.append(entry)\n\t\t}\n\t\tdestination.append(this.#summary)\n\t}\n\n\t#focusable(name: string): FormControl | undefined {\n\t\treturn this.#controls\n\t\t\t.get(name)\n\t\t\t?.find(\n\t\t\t\t(control) =>\n\t\t\t\t\t!control.matches(':disabled') &&\n\t\t\t\t\tcontrol.closest('[inert]') === null &&\n\t\t\t\t\tcontrol.getClientRects().length > 0 &&\n\t\t\t\t\t!['hidden', 'collapse'].includes(getComputedStyle(control).visibility) &&\n\t\t\t\t\t!(control instanceof HTMLInputElement && control.type === 'hidden'),\n\t\t\t)\n\t}\n\n\t#onDocument(): void {\n\t\tif (this.#destroyed) return\n\t\ttry {\n\t\t\tthis.#drain()\n\t\t} catch (error) {\n\t\t\tthis.#emitter.emit('error', error)\n\t\t}\n\t}\n\n\t#onFill(name: string): void {\n\t\tthis.#unreadable.delete(name)\n\t\tthis.#onState()\n\t}\n\n\t#onState(): void {\n\t\tthis.#notified = false\n\t\tthis.#onDocument()\n\t}\n\n\t#onDisable(name: string): void {\n\t\tif (this.#excluding !== name) this.#logical.add(name)\n\t\tthis.#onState()\n\t}\n\n\t#onEnable(name: string): void {\n\t\tif (this.#excluding !== name) {\n\t\t\tthis.#logical.delete(name)\n\t\t\tthis.#excluded.delete(name)\n\t\t}\n\t\tthis.#onState()\n\t}\n\n\t#onClear(): void {\n\t\tthis.#logical.clear()\n\t\tthis.#excluded.clear()\n\t\tthis.#unreadable.clear()\n\t\tfor (const name of this.#document.disabled) this.#logical.add(name)\n\t\tthis.#onState()\n\t}\n\n\t#onInput(event: Event): void {\n\t\tconst control = event.target\n\t\tif (\n\t\t\t!isFormControl(control) ||\n\t\t\tcontrol.form !== this.#element ||\n\t\t\t!this.#document.field(control.name) ||\n\t\t\tthis.#destroyed\n\t\t)\n\t\t\treturn\n\t\ttry {\n\t\t\tthis.read()\n\t\t} catch (error) {\n\t\t\tthis.#emitter.emit('error', error)\n\t\t}\n\t}\n\n\t#onClick(event: MouseEvent): void {\n\t\tconst control = event.target\n\t\tif (control instanceof Element) {\n\t\t\tconst link = control.closest('a[data-form-field]')\n\t\t\tif (\n\t\t\t\tlink instanceof HTMLAnchorElement &&\n\t\t\t\tthis.#summary?.contains(link) &&\n\t\t\t\tlink.dataset.formField !== undefined\n\t\t\t) {\n\t\t\t\tevent.preventDefault()\n\t\t\t\tthis.#focusable(link.dataset.formField)?.focus()\n\t\t\t}\n\t\t}\n\t\tif (\n\t\t\tcontrol instanceof HTMLInputElement &&\n\t\t\tcontrol.form === this.#element &&\n\t\t\tthis.#document.field(control.name)?.locked &&\n\t\t\t['file', 'checkbox', 'radio', 'color'].includes(control.type)\n\t\t)\n\t\t\tevent.preventDefault()\n\t}\n\n\t#onBlur(event: FocusEvent): void {\n\t\tconst control = event.target\n\t\tif (\n\t\t\t!isFormControl(control) ||\n\t\t\tcontrol.form !== this.#element ||\n\t\t\t!this.#document.field(control.name) ||\n\t\t\tthis.#destroyed ||\n\t\t\tthis.#document.status !== 'editing'\n\t\t)\n\t\t\treturn\n\t\ttry {\n\t\t\tthis.#document.touch(control.name)\n\t\t\tthis.#drain()\n\t\t} catch (error) {\n\t\t\tthis.#emitter.emit('error', error)\n\t\t}\n\t}\n\n\t#onSubmit(event: SubmitEvent): void {\n\t\tif (event.defaultPrevented || this.#destroyed) return\n\t\tevent.preventDefault()\n\t\tif (this.#submitting || this.#document.pending !== undefined) {\n\t\t\tthis.#emitter.emit(\n\t\t\t\t'error',\n\t\t\t\tnew FormBindingError('PENDING', 'A submission is already pending'),\n\t\t\t)\n\t\t\treturn\n\t\t}\n\t\tthis.#submitting = true\n\t\ttry {\n\t\t\tthis.read()\n\t\t\tif (this.#destroyed) return\n\t\t\tfor (const field of this.#document.schema.fields) {\n\t\t\t\tif (!this.#document.disabled.has(field.name)) this.#document.touch(field.name)\n\t\t\t\tif (this.#destroyed) return\n\t\t\t}\n\t\t\tthis.#drain()\n\t\t\tconst native = [...this.#controls].filter(\n\t\t\t\t([name, controls]) =>\n\t\t\t\t\t!this.#document.disabled.has(name) &&\n\t\t\t\t\tcontrols.some((control) => control.willValidate && !control.validity.valid),\n\t\t\t)\n\t\t\tif (native.length > 0) {\n\t\t\t\tfor (const [name] of native) {\n\t\t\t\t\tconst target = this.#focusable(name)\n\t\t\t\t\tif (target !== undefined) {\n\t\t\t\t\t\ttarget.focus()\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst result = this.#document.prepare()\n\t\t\tif (this.#destroyed) return\n\t\t\tthis.#drain()\n\t\t\tif (result.success) {\n\t\t\t\tif (!result.value.signal.aborted && this.#document.pending === result.value)\n\t\t\t\t\tthis.#emitter.emit('submit', result.value, event.submitter ?? undefined)\n\t\t\t} else {\n\t\t\t\tfor (const error of result.error) {\n\t\t\t\t\tconst target = this.#focusable(error.field)\n\t\t\t\t\tif (target !== undefined) {\n\t\t\t\t\t\ttarget.focus()\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tthis.#emitter.emit('error', error)\n\t\t} finally {\n\t\t\tthis.#submitting = false\n\t\t}\n\t}\n\n\t#onReset(event: Event): void {\n\t\tif (event.defaultPrevented || this.#destroyed) return\n\t\tevent.preventDefault()\n\t\ttry {\n\t\t\tthis.#document.clear()\n\t\t\tthis.refresh()\n\t\t} catch (error) {\n\t\t\tthis.#emitter.emit('error', error)\n\t\t}\n\t}\n}\n","import type { FormInterface } from '../core/types.js'\nimport type { FormBindingInterface, FormBindingOptions } from './types.js'\nimport { FormBinding } from './FormBinding.js'\n\n/**\n * Creates a native form binding exposed through its lifecycle interface.\n * @param element - The native form.\n * @param document - The borrowed answer document.\n * @param options - Feedback destinations and listeners.\n * @returns The initialized native binding.\n * @example\n * ```ts\n * const binding = createFormBinding(element, document)\n * binding.refresh()\n * ```\n */\nexport function createFormBinding(\n\telement: HTMLFormElement,\n\tdocument: FormInterface,\n\toptions?: FormBindingOptions,\n): FormBindingInterface {\n\treturn new FormBinding(element, document, options)\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../../src/browser/errors.ts","../../../src/core/constants.ts","../../../src/core/helpers.ts","../../../src/browser/helpers.ts","../../../src/browser/validators.ts","../../../src/browser/FormBinding.ts","../../../src/browser/factories.ts"],"sourcesContent":["import type { FormBindingErrorCode } from './types.js'\nimport { isInstance } from '@orkestrel/contract'\n\n/** Reports an incompatible control, overlapping submission, or native DOM failure. */\nexport class FormBindingError extends Error {\n\treadonly code: FormBindingErrorCode\n\treadonly context: Readonly<Record<string, unknown>> | undefined\n\n\t/**\n\t * Creates a native binding refusal.\n\t * @param code - The refusal category.\n\t * @param message - The explanation.\n\t * @param context - The associated field or cause.\n\t */\n\tconstructor(\n\t\tcode: FormBindingErrorCode,\n\t\tmessage: string,\n\t\tcontext?: Readonly<Record<string, unknown>>,\n\t) {\n\t\tsuper(message)\n\t\tthis.name = 'FormBindingError'\n\t\tthis.code = code\n\t\tthis.context = context === undefined ? undefined : Object.freeze({ ...context })\n\t}\n}\n\n/**\n * Checks whether a value is a native binding refusal.\n * @param value - The value to inspect.\n * @returns True if the value is a native binding refusal; false otherwise.\n * @example\n * ```ts\n * isFormBindingError(new FormBindingError('CONTROL', 'Duplicate text controls')) // true\n * ```\n */\nexport function isFormBindingError(value: unknown): value is FormBindingError {\n\treturn isInstance(value, FormBindingError)\n}\n","import type { FieldControl, FieldRuleName, FormStatus } from './types.js'\n\n/** Lists every field control, in the order declared by the public contract. */\nexport const FIELD_CONTROLS: readonly FieldControl[] = Object.freeze([\n\t'text',\n\t'editor',\n\t'password',\n\t'number',\n\t'date',\n\t'time',\n\t'datetime',\n\t'color',\n\t'confirm',\n\t'select',\n\t'checkbox',\n\t'file',\n])\n\n/** Lists the members every field declares, whatever its control. */\nexport const FIELD_BASE_KEYS: readonly string[] = Object.freeze([\n\t'control',\n\t'name',\n\t'label',\n\t'help',\n\t'group',\n\t'hidden',\n\t'disabled',\n\t'locked',\n\t'rule',\n\t'meta',\n])\n\n/**\n * Lists every member one field control permits, composed from {@link FIELD_BASE_KEYS} and the\n * members the control's own interface adds.\n */\nexport const FIELD_KEYS: Readonly<Record<FieldControl, readonly string[]>> = Object.freeze({\n\ttext: Object.freeze([...FIELD_BASE_KEYS, 'default', 'placeholder']),\n\teditor: Object.freeze([...FIELD_BASE_KEYS, 'default', 'placeholder']),\n\tpassword: Object.freeze([...FIELD_BASE_KEYS, 'mask']),\n\tnumber: Object.freeze([...FIELD_BASE_KEYS, 'default', 'placeholder']),\n\tdate: Object.freeze([...FIELD_BASE_KEYS, 'default']),\n\ttime: Object.freeze([...FIELD_BASE_KEYS, 'default']),\n\tdatetime: Object.freeze([...FIELD_BASE_KEYS, 'default']),\n\tcolor: Object.freeze([...FIELD_BASE_KEYS, 'default']),\n\tconfirm: Object.freeze([...FIELD_BASE_KEYS, 'default']),\n\tselect: Object.freeze([...FIELD_BASE_KEYS, 'choices', 'default', 'open']),\n\tcheckbox: Object.freeze([...FIELD_BASE_KEYS, 'choices', 'default']),\n\tfile: Object.freeze([...FIELD_BASE_KEYS, 'accept', 'multiple']),\n})\n\n/** Lists every form lifecycle status. */\nexport const FORM_STATUSES: readonly FormStatus[] = Object.freeze([\n\t'editing',\n\t'settled',\n\t'abandoned',\n])\n\n/** Holds the default failure copy for every named field rule. */\nexport const RULE_MESSAGES: Readonly<Record<FieldRuleName, string>> = Object.freeze({\n\trequired: 'This field is required',\n\tminimum: 'Must be at least {limit}',\n\tmaximum: 'Must be at most {limit}',\n\tstep: 'Must be a multiple of {limit}',\n\tpattern: 'Must match the required format',\n\temail: 'Must be a valid email address',\n\turl: 'Must be a valid URL',\n\tinteger: 'Must be an integer',\n\talphanumeric: 'Must contain only letters and numbers',\n})\n\n/** Matches a practical whole-address email shape. */\nexport const EMAIL_PATTERN = Object.freeze(/^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/)\n\n/** Matches an absolute HTTP or HTTPS URL shape. */\nexport const URL_PATTERN = Object.freeze(/^https?:\\/\\/[^\\s]+$/)\n\n/** Matches one or more ASCII letters or digits. */\nexport const ALPHANUMERIC_PATTERN = Object.freeze(/^[A-Za-z0-9]+$/)\n\n/** Matches a signed or unsigned base-ten integer string. */\nexport const INTEGER_PATTERN = Object.freeze(/^[+-]?\\d+$/)\n\n/** Matches a six-digit hexadecimal color string. */\nexport const COLOR_PATTERN = Object.freeze(/^#[0-9A-Fa-f]{6}$/)\n\n/** Matches an ISO calendar date string in `YYYY-MM-DD` form. */\nexport const DATE_PATTERN = Object.freeze(/^\\d{4}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[12]\\d|3[01])$/)\n\n/** Matches a 24-hour time string with optional seconds. */\nexport const TIME_PATTERN = Object.freeze(/^(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d)?$/)\n\n/** Matches an ISO local date and time string with optional seconds. */\nexport const DATETIME_PATTERN = Object.freeze(\n\t/^\\d{4}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[12]\\d|3[01])T(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d)?$/,\n)\n\n/** Caps the accepted source length for an authored regular expression, at 256. */\nexport const PATTERN_LIMIT = 256\n\n/** Caps the number of fields one schema may declare, at 512. */\nexport const FIELD_LIMIT = 512\n\n/** Caps the number of groups one schema may declare, at 64. */\nexport const GROUP_LIMIT = 64\n\n/** Caps the number of choices one `select` or `checkbox` field may offer, at 1024. */\nexport const CHOICE_LIMIT = 1024\n\n/** Caps the number of entries one list-valued answer may hold, at 1024. */\nexport const LIST_LIMIT = 1024\n\n/** Caps the length, in UTF-16 code units, of a schema, group, or field name, at 128. */\nexport const NAME_LIMIT = 128\n\n/** Caps the length, in UTF-16 code units, of any single retained string, at 65536. */\nexport const STRING_LIMIT = 65536\n\n/** Caps the total length, in UTF-16 code units, of every string one schema retains, at 1048576. */\nexport const TEXT_LIMIT = 1048576\n\n/** Caps the total number of records, arrays, and leaves one schema retains, at 16384. */\nexport const NODE_LIMIT = 16384\n","import type { JSONRecord, JSONValue } from '@orkestrel/contract'\nimport type {\n\tEvaluationOptions,\n\tFieldError,\n\tFieldControl,\n\tFieldRuleName,\n\tFieldValue,\n\tFormField,\n\tFormGroup,\n\tFormSchema,\n\tFormValues,\n} from './types.js'\nimport {\n\tattempt,\n\tcloneJSONRecord,\n\tisArray,\n\tisBoolean,\n\tisFiniteNumber,\n\tisInteger,\n\tisContractError,\n\tisRecord,\n\tisString,\n\treadArrayEntries,\n} from '@orkestrel/contract'\nimport { cloneValue } from './cloners.js'\nimport {\n\tALPHANUMERIC_PATTERN,\n\tCHOICE_LIMIT,\n\tCOLOR_PATTERN,\n\tDATE_PATTERN,\n\tDATETIME_PATTERN,\n\tEMAIL_PATTERN,\n\tFIELD_CONTROLS,\n\tFIELD_LIMIT,\n\tGROUP_LIMIT,\n\tINTEGER_PATTERN,\n\tLIST_LIMIT,\n\tNAME_LIMIT,\n\tNODE_LIMIT,\n\tPATTERN_LIMIT,\n\tRULE_MESSAGES,\n\tSTRING_LIMIT,\n\tTEXT_LIMIT,\n\tTIME_PATTERN,\n\tURL_PATTERN,\n} from './constants.js'\nimport { FormError } from './errors.js'\n\n/**\n * Writes one own enumerable data property onto a record.\n *\n * @param target - The record to write into.\n * @param name - The property name to write.\n * @param value - The value to store.\n *\n * @remarks\n * Plain assignment runs an inherited setter, so writing a `__proto__` key that way reaches\n * `Object.prototype` and leaves the record without the entry. Defining the property writes the\n * record itself, whatever the prototype chain declares. The entry stays writable and configurable.\n *\n * @example\n * ```ts\n * const values: Record<string, number> = {}\n * defineEntry(values, '__proto__', 1)\n * Object.hasOwn(values, '__proto__') // true\n * ```\n */\nexport function defineEntry<T>(target: Record<string, T>, name: string, value: T): void {\n\tObject.defineProperty(target, name, {\n\t\tvalue,\n\t\tenumerable: true,\n\t\tconfigurable: true,\n\t\twritable: true,\n\t})\n}\n\n/**\n * Writes one own enumerable data property that cannot be rewritten or removed.\n *\n * @param target - The record to write into.\n * @param name - The property name to write.\n * @param value - The value to store.\n *\n * @remarks\n * The prototype-safe write of {@link defineEntry}, frozen: the entry is neither writable nor\n * configurable, so the record a parser hands back cannot be edited through the key it filled.\n *\n * @example\n * ```ts\n * const values: Record<string, number> = {}\n * freezeEntry(values, '__proto__', 1)\n * Object.getOwnPropertyDescriptor(values, '__proto__')?.writable // false\n * ```\n */\nexport function freezeEntry<T>(target: Record<string, T>, name: string, value: T): void {\n\tObject.defineProperty(target, name, {\n\t\tvalue,\n\t\tenumerable: true,\n\t\tconfigurable: false,\n\t\twritable: false,\n\t})\n}\n\n/**\n * Checks whether a value has the shape required by one field control.\n *\n * @remarks\n * Every write and every seeded value passes through this gate, and it reads `STRING_LIMIT` and\n * `LIST_LIMIT` before it consults the control, so no regular expression sees an over-long value.\n *\n * @param field - The field that owns the value.\n * @param value - The unknown value to inspect.\n * @returns True if the control can hold the value; false otherwise.\n */\nexport function matchesField(field: FormField, value: unknown): value is FieldValue {\n\tif (isString(value) && value.length > STRING_LIMIT) return false\n\n\tlet entries: readonly unknown[] | undefined\n\tif (isArray(value)) {\n\t\tconst length = attempt(() => value.length)\n\t\tif (!length.success || length.value > LIST_LIMIT) return false\n\n\t\tconst read = readArrayEntries(value)\n\t\tif (!read.success || !read.value.dense) return false\n\n\t\tentries = read.value.entries\n\t\tif (entries.some((entry) => !isString(entry) || entry.length > STRING_LIMIT)) return false\n\t}\n\n\tswitch (field.control) {\n\t\tcase 'text':\n\t\tcase 'editor':\n\t\tcase 'password':\n\t\t\treturn isString(value)\n\t\tcase 'number':\n\t\t\treturn isFiniteNumber(value)\n\t\tcase 'date':\n\t\t\treturn isString(value) && DATE_PATTERN.test(value)\n\t\tcase 'time':\n\t\t\treturn isString(value) && TIME_PATTERN.test(value)\n\t\tcase 'datetime':\n\t\t\treturn isString(value) && DATETIME_PATTERN.test(value)\n\t\tcase 'color':\n\t\t\treturn isString(value) && COLOR_PATTERN.test(value)\n\t\tcase 'confirm':\n\t\t\treturn isBoolean(value)\n\t\tcase 'select':\n\t\t\treturn (\n\t\t\t\tisString(value) &&\n\t\t\t\t!field.choices.some((choice) => choice.value === value && choice.disabled === true) &&\n\t\t\t\t(field.open === true ||\n\t\t\t\t\tfield.choices.some((choice) => choice.value === value && choice.disabled !== true))\n\t\t\t)\n\t\tcase 'checkbox':\n\t\t\treturn (\n\t\t\t\tentries !== undefined &&\n\t\t\t\tentries.every(\n\t\t\t\t\t(entry) =>\n\t\t\t\t\t\tisString(entry) &&\n\t\t\t\t\t\tfield.choices.some((choice) => choice.value === entry && choice.disabled !== true),\n\t\t\t\t) &&\n\t\t\t\tnew Set(entries).size === entries.length\n\t\t\t)\n\t\tcase 'file':\n\t\t\treturn entries !== undefined && (field.multiple === true || entries.length <= 1)\n\t}\n}\n\n/**\n * Decides whether a raw binding value projects to an answered field.\n *\n * @remarks\n * Bind with `fill(name, matchesAnswer(raw) ? raw : undefined)`. This projection treats an absent\n * value and a string containing only whitespace as unanswered. Every other field value is an\n * answer, including an empty list, `false`, and zero. Core evaluation does not use this projection:\n * its `required` rule remains presence-only.\n *\n * @param value - The raw field value, or absence.\n * @returns True if the binding preserves the value as an answer; false otherwise.\n */\nexport function matchesAnswer(value: FieldValue | undefined): boolean {\n\treturn value !== undefined && (!isString(value) || value.trim().length > 0)\n}\n\n/**\n * Checks whether a named rule applies to one field control.\n *\n * @remarks\n * The runtime control-membership check keeps this boundary total for JavaScript callers that\n * bypass the declared {@link FieldControl} contract.\n *\n * @param control - The field control to inspect.\n * @param rule - The named rule to inspect.\n * @returns True if the control evaluates that rule; false otherwise.\n */\nexport function appliesRule(control: FieldControl, rule: FieldRuleName): boolean {\n\tif (!FIELD_CONTROLS.some((candidate) => candidate === control)) return false\n\n\tswitch (rule) {\n\t\tcase 'required':\n\t\t\treturn true\n\t\tcase 'minimum':\n\t\tcase 'maximum':\n\t\t\treturn control !== 'color' && control !== 'confirm' && control !== 'select'\n\t\tcase 'step':\n\t\t\treturn control === 'number'\n\t\tcase 'pattern':\n\t\tcase 'email':\n\t\tcase 'url':\n\t\tcase 'alphanumeric':\n\t\t\treturn (\n\t\t\t\tcontrol !== 'number' &&\n\t\t\t\tcontrol !== 'confirm' &&\n\t\t\t\tcontrol !== 'checkbox' &&\n\t\t\t\tcontrol !== 'file'\n\t\t\t)\n\t\tcase 'integer':\n\t\t\treturn control !== 'confirm' && control !== 'checkbox' && control !== 'file'\n\t}\n\n\treturn false\n}\n\n/**\n * Evaluates one field rule against its current value.\n *\n * @param field - The field and rule to evaluate.\n * @param value - The current value, or absence.\n * @param values - Every value available to a custom rule.\n * @param messages - Optional rule-specific message replacements.\n * @returns Every failure in rule order.\n * @throws Thrown when a {@link FieldValidator} supplied through {@link FieldRule.custom} throws:\n * its own value escapes unchanged, because this helper adds no boundary around it.\n */\nexport function evaluateField(\n\tfield: FormField,\n\tvalue: FieldValue | undefined,\n\tvalues: FormValues,\n\tmessages?: Readonly<Partial<Record<FieldRuleName, string>>>,\n): readonly FieldError[] {\n\tconst errors: FieldError[] = []\n\tconst rule = field.rule\n\n\tif (value === undefined) {\n\t\tif (rule?.required === true && appliesRule(field.control, 'required')) {\n\t\t\terrors.push(createFieldError(field, 'required', undefined, messages))\n\t\t}\n\t}\n\n\tif (rule === undefined) return Object.freeze(errors)\n\n\tif (rule.minimum !== undefined && appliesRule(field.control, 'minimum')) {\n\t\tlet failed = false\n\n\t\tswitch (field.control) {\n\t\t\tcase 'text':\n\t\t\tcase 'editor':\n\t\t\tcase 'password':\n\t\t\t\tfailed = isString(value) && isFiniteNumber(rule.minimum) && value.length < rule.minimum\n\t\t\t\tbreak\n\t\t\tcase 'number':\n\t\t\t\tfailed = isFiniteNumber(value) && isFiniteNumber(rule.minimum) && value < rule.minimum\n\t\t\t\tbreak\n\t\t\tcase 'date':\n\t\t\tcase 'time':\n\t\t\tcase 'datetime':\n\t\t\t\tfailed = isString(value) && isString(rule.minimum) && value < rule.minimum\n\t\t\t\tbreak\n\t\t\tcase 'checkbox':\n\t\t\tcase 'file':\n\t\t\t\tfailed = isArray(value) && isFiniteNumber(rule.minimum) && value.length < rule.minimum\n\t\t\t\tbreak\n\t\t\tcase 'color':\n\t\t\tcase 'confirm':\n\t\t\tcase 'select':\n\t\t\t\tbreak\n\t\t}\n\n\t\tif (failed) {\n\t\t\terrors.push(createFieldError(field, 'minimum', rule.minimum, messages))\n\t\t}\n\t}\n\n\tif (rule.maximum !== undefined && appliesRule(field.control, 'maximum')) {\n\t\tlet failed = false\n\n\t\tswitch (field.control) {\n\t\t\tcase 'text':\n\t\t\tcase 'editor':\n\t\t\tcase 'password':\n\t\t\t\tfailed = isString(value) && isFiniteNumber(rule.maximum) && value.length > rule.maximum\n\t\t\t\tbreak\n\t\t\tcase 'number':\n\t\t\t\tfailed = isFiniteNumber(value) && isFiniteNumber(rule.maximum) && value > rule.maximum\n\t\t\t\tbreak\n\t\t\tcase 'date':\n\t\t\tcase 'time':\n\t\t\tcase 'datetime':\n\t\t\t\tfailed = isString(value) && isString(rule.maximum) && value > rule.maximum\n\t\t\t\tbreak\n\t\t\tcase 'checkbox':\n\t\t\tcase 'file':\n\t\t\t\tfailed = isArray(value) && isFiniteNumber(rule.maximum) && value.length > rule.maximum\n\t\t\t\tbreak\n\t\t\tcase 'color':\n\t\t\tcase 'confirm':\n\t\t\tcase 'select':\n\t\t\t\tbreak\n\t\t}\n\n\t\tif (failed) {\n\t\t\terrors.push(createFieldError(field, 'maximum', rule.maximum, messages))\n\t\t}\n\t}\n\n\tif (rule.step !== undefined && appliesRule(field.control, 'step') && isFiniteNumber(value)) {\n\t\tconst base = isFiniteNumber(rule.minimum) ? rule.minimum : 0\n\t\tconst multiple = (value - base) / rule.step\n\n\t\tif (\n\t\t\t!isFiniteNumber(rule.step) ||\n\t\t\trule.step === 0 ||\n\t\t\t!isFiniteNumber(multiple) ||\n\t\t\tMath.abs(multiple - Math.round(multiple)) > 1e-9\n\t\t) {\n\t\t\terrors.push(createFieldError(field, 'step', rule.step, messages))\n\t\t}\n\t}\n\n\tif (isString(value)) {\n\t\tif (rule.pattern !== undefined && appliesRule(field.control, 'pattern')) {\n\t\t\tconst pattern = rule.pattern\n\n\t\t\tif (pattern.length > PATTERN_LIMIT) {\n\t\t\t\terrors.push(createFieldError(field, 'pattern', undefined, messages))\n\t\t\t} else {\n\t\t\t\tconst outcome = attempt(() => new RegExp(pattern).test(value))\n\n\t\t\t\tif (!outcome.success || !outcome.value) {\n\t\t\t\t\terrors.push(createFieldError(field, 'pattern', undefined, messages))\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (rule.email === true && appliesRule(field.control, 'email') && !EMAIL_PATTERN.test(value)) {\n\t\t\terrors.push(createFieldError(field, 'email', undefined, messages))\n\t\t}\n\n\t\tif (rule.url === true && appliesRule(field.control, 'url') && !URL_PATTERN.test(value)) {\n\t\t\terrors.push(createFieldError(field, 'url', undefined, messages))\n\t\t}\n\n\t\tif (\n\t\t\trule.alphanumeric === true &&\n\t\t\tappliesRule(field.control, 'alphanumeric') &&\n\t\t\t!ALPHANUMERIC_PATTERN.test(value)\n\t\t) {\n\t\t\terrors.push(createFieldError(field, 'alphanumeric', undefined, messages))\n\t\t}\n\n\t\tif (\n\t\t\trule.integer === true &&\n\t\t\tappliesRule(field.control, 'integer') &&\n\t\t\t!INTEGER_PATTERN.test(value)\n\t\t) {\n\t\t\terrors.push(createFieldError(field, 'integer', undefined, messages))\n\t\t}\n\t}\n\n\tif (\n\t\tvalue !== undefined &&\n\t\tfield.control === 'number' &&\n\t\trule.integer === true &&\n\t\tappliesRule(field.control, 'integer') &&\n\t\t!isInteger(value)\n\t) {\n\t\terrors.push(createFieldError(field, 'integer', undefined, messages))\n\t}\n\n\tif (rule.custom !== undefined) {\n\t\tconst result = rule.custom(value, values)\n\n\t\tif (isString(result)) errors.push(Object.freeze({ field: field.name, message: result }))\n\t}\n\n\treturn Object.freeze(errors)\n}\n\n/**\n * Evaluates every active field in schema order.\n *\n * @param schema - The form schema to evaluate.\n * @param values - The values keyed by field name.\n * @param options - Optional message replacements and the effective disabled field set.\n * @returns Every field failure in schema and rule order.\n * @throws Thrown when a {@link FieldValidator} supplied through {@link FieldRule.custom} throws:\n * its own value escapes unchanged, because this helper adds no boundary around it.\n */\nexport function evaluateForm(\n\tschema: FormSchema,\n\tvalues: FormValues,\n\toptions?: EvaluationOptions,\n): readonly FieldError[] {\n\tconst errors: FieldError[] = []\n\n\tfor (const field of schema.fields) {\n\t\tconst active =\n\t\t\toptions?.disabled === undefined ? field.disabled !== true : !options.disabled.has(field.name)\n\n\t\tif (active) {\n\t\t\tconst value = Object.hasOwn(values, field.name) ? values[field.name] : undefined\n\t\t\terrors.push(...evaluateField(field, value, values, options?.messages))\n\t\t}\n\t}\n\n\treturn Object.freeze(errors)\n}\n\n/**\n * Computes the values explicitly seeded by a schema.\n *\n * @remarks\n * `password` and `file` declare no default, so a field of either control never appears in the\n * result.\n *\n * @param schema - The schema whose defaults to collect.\n * @returns A value record containing only fields with defaults.\n */\nexport function computeDefaults(schema: FormSchema): FormValues {\n\tconst defaults: Record<string, FieldValue> = {}\n\n\tfor (const field of schema.fields) {\n\t\tswitch (field.control) {\n\t\t\tcase 'checkbox':\n\t\t\t\tif (field.default !== undefined) {\n\t\t\t\t\tdefineEntry(defaults, field.name, cloneValue(field.default))\n\t\t\t\t}\n\t\t\t\tbreak\n\t\t\tcase 'password':\n\t\t\tcase 'file':\n\t\t\t\tbreak\n\t\t\tcase 'text':\n\t\t\tcase 'editor':\n\t\t\tcase 'number':\n\t\t\tcase 'date':\n\t\t\tcase 'time':\n\t\t\tcase 'datetime':\n\t\t\tcase 'color':\n\t\t\tcase 'confirm':\n\t\t\tcase 'select':\n\t\t\t\tif (field.default !== undefined) {\n\t\t\t\t\tdefineEntry(defaults, field.name, field.default)\n\t\t\t\t}\n\t\t\t\tbreak\n\t\t}\n\t}\n\n\treturn Object.freeze(defaults)\n}\n\n/**\n * Compares two field values by scalar identity or ordered list content.\n *\n * @param a - The first field value.\n * @param b - The second field value.\n * @returns True if both values contain the same answer; false otherwise.\n */\nexport function matchesValue(a: FieldValue, b: FieldValue): boolean {\n\tif (isArray(a) || isArray(b)) {\n\t\treturn (\n\t\t\tisArray(a) &&\n\t\t\tisArray(b) &&\n\t\t\ta.length === b.length &&\n\t\t\ta.every((entry, index) => entry === b[index])\n\t\t)\n\t}\n\n\treturn a === b\n}\n\n/**\n * Extracts the names whose answers differ between two form value records.\n *\n * @remarks\n * Presence is compared in both directions before present values are compared through\n * {@link matchesValue}. The returned set is a new snapshot, exposed as readonly because later\n * changes to either input never alter its membership.\n *\n * @param current - The values the form holds.\n * @param opened - The values held when the form opened.\n * @returns A readonly snapshot of changed field names.\n */\nexport function extractChanges(current: FormValues, opened: FormValues): ReadonlySet<string> {\n\tconst names = new Set([...Object.keys(current), ...Object.keys(opened)])\n\tconst changed = new Set<string>()\n\n\tfor (const name of names) {\n\t\tconst hasCurrent = Object.hasOwn(current, name)\n\t\tconst hasOpened = Object.hasOwn(opened, name)\n\n\t\tif (hasCurrent !== hasOpened) {\n\t\t\tchanged.add(name)\n\t\t\tcontinue\n\t\t}\n\n\t\tconst now = current[name]\n\t\tconst before = opened[name]\n\t\tif (now === undefined || before === undefined || !matchesValue(now, before)) changed.add(name)\n\t}\n\n\treturn changed\n}\n\n/**\n * Compares two form value records by keys and value content.\n *\n * @param a - The first value record.\n * @param b - The second value record.\n * @returns True if both records contain the same answers; false otherwise.\n */\nexport function matchesValues(a: FormValues, b: FormValues): boolean {\n\treturn extractChanges(a, b).size === 0\n}\n\n/**\n * Resolves and interpolates one rule message.\n *\n * @remarks\n * A replacement in `messages` is read first and {@link RULE_MESSAGES} supplies the copy otherwise,\n * and `{limit}` in whichever text wins is replaced with the rule's operand.\n *\n * @param rule - The rule whose message to resolve.\n * @param limit - The optional operand substituted for `{limit}`.\n * @param messages - Optional rule-specific message replacements.\n * @returns The resolved failure text.\n */\nexport function formatMessage(\n\trule: FieldRuleName,\n\tlimit?: number | string,\n\tmessages?: Readonly<Partial<Record<FieldRuleName, string>>>,\n): string {\n\tconst message = messages?.[rule] ?? RULE_MESSAGES[rule]\n\treturn limit === undefined ? message : message.replaceAll('{limit}', String(limit))\n}\n\n/**\n * Creates one named-rule failure against a field.\n *\n * @param field - The field the rule failed on.\n * @param rule - The named rule that failed.\n * @param limit - The rule's operand, substituted for `{limit}`, or absence when it carries none.\n * @param messages - Optional rule-specific message replacements.\n * @returns A frozen {@link FieldError} carrying the field's name, the resolved text, and the rule.\n *\n * @remarks\n * A `custom` validator and the form's `invalidate` method both report a message of their own\n * under no rule name, so neither builds its failure here.\n *\n * @example\n * ```ts\n * const field: FormField = { control: 'text', name: 'nickname', rule: { minimum: 3 } }\n * const error = createFieldError(field, 'minimum', 3)\n * error.message // 'Must be at least 3'\n * ```\n */\nexport function createFieldError(\n\tfield: FormField,\n\trule: FieldRuleName,\n\tlimit: number | string | undefined,\n\tmessages?: Readonly<Partial<Record<FieldRuleName, string>>>,\n): FieldError {\n\treturn Object.freeze({ field: field.name, message: formatMessage(rule, limit, messages), rule })\n}\n\n/**\n * Projects a schema into JSON while removing custom validators and absent values.\n *\n * @param schema - The schema to project.\n * @returns A deep JSON copy of the serializable schema.\n * @throws A {@link FormError} coded `SCHEMA` when accessor-bearing metadata cannot be owned. A\n * non-contract throw while reading metadata escapes unchanged.\n */\nexport function serializeForm(schema: FormSchema): JSONRecord {\n\tconst output: Record<string, JSONValue> = {}\n\n\tif (schema.name !== undefined) output.name = schema.name\n\tif (schema.label !== undefined) output.label = schema.label\n\tif (schema.help !== undefined) output.help = schema.help\n\tif (schema.groups !== undefined) {\n\t\toutput.groups = schema.groups.map((group): JSONRecord => {\n\t\t\tconst entry: Record<string, JSONValue> = { name: group.name, label: group.label }\n\t\t\tif (group.help !== undefined) entry.help = group.help\n\t\t\treturn entry\n\t\t})\n\t}\n\n\toutput.fields = schema.fields.map((field): JSONRecord => {\n\t\tconst entry: Record<string, JSONValue> = {\n\t\t\tcontrol: field.control,\n\t\t\tname: field.name,\n\t\t}\n\n\t\tif (field.label !== undefined) entry.label = field.label\n\t\tif (field.help !== undefined) entry.help = field.help\n\t\tif (field.group !== undefined) entry.group = field.group\n\t\tif (field.hidden !== undefined) entry.hidden = field.hidden\n\t\tif (field.disabled !== undefined) entry.disabled = field.disabled\n\t\tif (field.locked !== undefined) entry.locked = field.locked\n\t\tconst meta = field.meta\n\t\tif (meta !== undefined) {\n\t\t\ttry {\n\t\t\t\tentry.meta = cloneJSONRecord(meta)\n\t\t\t} catch (error) {\n\t\t\t\tif (!isContractError(error)) throw error\n\t\t\t\tthrow new FormError('SCHEMA', `Field \"${field.name}\" has metadata that cannot be owned`, {\n\t\t\t\t\tfield: field.name,\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\n\t\tswitch (field.control) {\n\t\t\tcase 'text':\n\t\t\tcase 'editor':\n\t\t\tcase 'number':\n\t\t\t\tif (field.default !== undefined) entry.default = field.default\n\t\t\t\tif (field.placeholder !== undefined) entry.placeholder = field.placeholder\n\t\t\t\tbreak\n\t\t\tcase 'password':\n\t\t\t\tif (field.mask !== undefined) entry.mask = field.mask\n\t\t\t\tbreak\n\t\t\tcase 'date':\n\t\t\tcase 'time':\n\t\t\tcase 'datetime':\n\t\t\tcase 'color':\n\t\t\tcase 'confirm':\n\t\t\t\tif (field.default !== undefined) entry.default = field.default\n\t\t\t\tbreak\n\t\t\tcase 'select':\n\t\t\t\tentry.choices = field.choices.map((choice): JSONRecord => {\n\t\t\t\t\tconst option: Record<string, JSONValue> = {\n\t\t\t\t\t\tvalue: choice.value,\n\t\t\t\t\t\tlabel: choice.label,\n\t\t\t\t\t}\n\t\t\t\t\tif (choice.help !== undefined) option.help = choice.help\n\t\t\t\t\tif (choice.disabled !== undefined) option.disabled = choice.disabled\n\t\t\t\t\treturn option\n\t\t\t\t})\n\t\t\t\tif (field.default !== undefined) entry.default = field.default\n\t\t\t\tif (field.open !== undefined) entry.open = field.open\n\t\t\t\tbreak\n\t\t\tcase 'checkbox':\n\t\t\t\tentry.choices = field.choices.map((choice): JSONRecord => {\n\t\t\t\t\tconst option: Record<string, JSONValue> = {\n\t\t\t\t\t\tvalue: choice.value,\n\t\t\t\t\t\tlabel: choice.label,\n\t\t\t\t\t}\n\t\t\t\t\tif (choice.help !== undefined) option.help = choice.help\n\t\t\t\t\tif (choice.disabled !== undefined) option.disabled = choice.disabled\n\t\t\t\t\treturn option\n\t\t\t\t})\n\t\t\t\tif (field.default !== undefined) entry.default = field.default\n\t\t\t\tbreak\n\t\t\tcase 'file':\n\t\t\t\tif (field.accept !== undefined) entry.accept = field.accept\n\t\t\t\tif (field.multiple !== undefined) entry.multiple = field.multiple\n\t\t\t\tbreak\n\t\t}\n\n\t\tif (field.rule !== undefined) {\n\t\t\tconst rule: Record<string, JSONValue> = {}\n\n\t\t\tif (field.rule.required !== undefined) rule.required = field.rule.required\n\t\t\tif (field.rule.minimum !== undefined) rule.minimum = field.rule.minimum\n\t\t\tif (field.rule.maximum !== undefined) rule.maximum = field.rule.maximum\n\t\t\tif (field.rule.step !== undefined) rule.step = field.rule.step\n\t\t\tif (field.rule.pattern !== undefined) rule.pattern = field.rule.pattern\n\t\t\tif (field.rule.email !== undefined) rule.email = field.rule.email\n\t\t\tif (field.rule.url !== undefined) rule.url = field.rule.url\n\t\t\tif (field.rule.integer !== undefined) rule.integer = field.rule.integer\n\t\t\tif (field.rule.alphanumeric !== undefined) rule.alphanumeric = field.rule.alphanumeric\n\t\t\tif (Object.keys(rule).length > 0) entry.rule = rule\n\t\t}\n\n\t\treturn entry\n\t})\n\n\treturn cloneJSONRecord(output)\n}\n\n/**\n * Selects referenced groups in first-reference field order.\n *\n * @param schema - The schema whose group references to resolve.\n * @returns The referenced schema groups without duplicates.\n */\nexport function extractGroups(schema: FormSchema): readonly FormGroup[] {\n\tconst groups: FormGroup[] = []\n\n\tif (schema.groups === undefined) return Object.freeze(groups)\n\n\tfor (const field of schema.fields) {\n\t\tif (field.group !== undefined && !groups.some((group) => group.name === field.group)) {\n\t\t\tconst group = schema.groups.find((entry) => entry.name === field.group)\n\t\t\tif (group !== undefined) groups.push(group)\n\t\t}\n\t}\n\n\treturn Object.freeze(groups)\n}\n\n/**\n * Audits a structurally valid schema for domain invariants.\n *\n * @param schema - The form schema to audit.\n * @returns Human-readable invariant violations, or an empty list when the schema is sound.\n */\nexport function auditSchema(schema: FormSchema): readonly string[] {\n\tconst faults: string[] = []\n\tconst fields = new Set<string>()\n\tconst groups = new Set<string>()\n\tlet choiceExceeded: string | undefined\n\tlet nameExceeded = schema.name !== undefined && schema.name.length > NAME_LIMIT\n\n\tif (schema.fields.length > FIELD_LIMIT) {\n\t\tfaults.push(`Schema declares more than ${FIELD_LIMIT} fields`)\n\t}\n\tif (schema.groups !== undefined && schema.groups.length > GROUP_LIMIT) {\n\t\tfaults.push(`Schema declares more than ${GROUP_LIMIT} groups`)\n\t}\n\n\tif (schema.groups !== undefined) {\n\t\tconst count = Math.min(schema.groups.length, GROUP_LIMIT + 1)\n\t\tfor (let index = 0; index < count; index += 1) {\n\t\t\tconst group = schema.groups[index]\n\t\t\tif (group !== undefined && group.name.length > NAME_LIMIT) nameExceeded = true\n\t\t}\n\t}\n\n\tconst fieldCount = Math.min(schema.fields.length, FIELD_LIMIT + 1)\n\tfor (let index = 0; index < fieldCount; index += 1) {\n\t\tconst field = schema.fields[index]\n\t\tif (field === undefined) continue\n\n\t\tif (\n\t\t\tfield.name.length > NAME_LIMIT ||\n\t\t\t(field.group !== undefined && field.group.length > NAME_LIMIT)\n\t\t) {\n\t\t\tnameExceeded = true\n\t\t}\n\t\tif (\n\t\t\tchoiceExceeded === undefined &&\n\t\t\t(field.control === 'select' || field.control === 'checkbox') &&\n\t\t\tfield.choices.length > CHOICE_LIMIT\n\t\t) {\n\t\t\tchoiceExceeded = field.name\n\t\t}\n\t}\n\n\tif (choiceExceeded !== undefined) {\n\t\tfaults.push(`Field \"${choiceExceeded}\" offers more than ${CHOICE_LIMIT} choices`)\n\t}\n\tif (nameExceeded) faults.push(`Schema contains a name longer than ${NAME_LIMIT}`)\n\n\tconst pending: unknown[] = [schema]\n\tconst metadata: boolean[] = [false]\n\tlet position = 0\n\tlet stringExceeded = false\n\tlet textExceeded = false\n\tlet nodeExceeded = false\n\tlet text = 0\n\n\twhile (position < pending.length) {\n\t\tconst node = pending[position]\n\t\tconst inMeta = metadata[position] === true\n\t\tposition += 1\n\n\t\tif (isString(node)) {\n\t\t\tif (node.length > STRING_LIMIT) stringExceeded = true\n\t\t\ttext = Math.min(TEXT_LIMIT + 1, text + node.length)\n\t\t\tif (text > TEXT_LIMIT) textExceeded = true\n\t\t\tcontinue\n\t\t}\n\n\t\tif (isArray(node)) {\n\t\t\tconst length = attempt(() => node.length)\n\t\t\tif (!length.success || length.value > NODE_LIMIT - pending.length) {\n\t\t\t\tnodeExceeded = true\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tconst read = readArrayEntries(node)\n\t\t\tif (!read.success || !read.value.dense) continue\n\n\t\t\tfor (let index = 0; index < read.value.entries.length; index += 1) {\n\t\t\t\tconst entry = read.value.entries[index]\n\t\t\t\tif (entry !== undefined) {\n\t\t\t\t\tpending.push(entry)\n\t\t\t\t\tmetadata.push(inMeta)\n\t\t\t\t}\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\n\t\tif (!isRecord(node)) continue\n\n\t\tconst keys = attempt(() => Object.keys(node))\n\t\tif (!keys.success) continue\n\n\t\tfor (const key of keys.value) {\n\t\t\tif (inMeta) {\n\t\t\t\tif (key.length > STRING_LIMIT) stringExceeded = true\n\t\t\t\ttext = Math.min(TEXT_LIMIT + 1, text + key.length)\n\t\t\t\tif (text > TEXT_LIMIT) textExceeded = true\n\t\t\t}\n\n\t\t\tconst value = attempt(() => node[key])\n\t\t\tif (!value.success || value.value === undefined) continue\n\t\t\tif (pending.length >= NODE_LIMIT) {\n\t\t\t\tnodeExceeded = true\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tpending.push(value.value)\n\t\t\tmetadata.push(inMeta || key === 'meta')\n\t\t}\n\t}\n\n\tif (stringExceeded) faults.push(`Schema contains a string longer than ${STRING_LIMIT}`)\n\tif (textExceeded) faults.push(`Schema retains more than ${TEXT_LIMIT} string code units`)\n\tif (nodeExceeded) faults.push(`Schema retains more than ${NODE_LIMIT} nodes`)\n\n\tif (schema.groups !== undefined) {\n\t\tconst count = Math.min(schema.groups.length, GROUP_LIMIT + 1)\n\t\tfor (let index = 0; index < count; index += 1) {\n\t\t\tconst group = schema.groups[index]\n\t\t\tif (group === undefined) continue\n\t\t\tif (groups.has(group.name)) faults.push(`Group \"${group.name}\" is declared more than once`)\n\t\t\tgroups.add(group.name)\n\t\t}\n\t}\n\n\tfor (let fieldIndex = 0; fieldIndex < fieldCount; fieldIndex += 1) {\n\t\tconst field = schema.fields[fieldIndex]\n\t\tif (field === undefined) continue\n\t\tif (field.name.length === 0) faults.push('Field \"\" has an empty name')\n\t\tif (field.name === '__proto__') faults.push('Field \"__proto__\" has a refused name')\n\t\tif (fields.has(field.name)) faults.push(`Field \"${field.name}\" is declared more than once`)\n\t\tfields.add(field.name)\n\n\t\tif (field.group !== undefined && !groups.has(field.group)) {\n\t\t\tfaults.push(`Field \"${field.name}\" references missing group \"${field.group}\"`)\n\t\t}\n\n\t\tswitch (field.control) {\n\t\t\tcase 'password':\n\t\t\tcase 'file':\n\t\t\t\tbreak\n\t\t\tcase 'text':\n\t\t\tcase 'editor':\n\t\t\tcase 'number':\n\t\t\tcase 'date':\n\t\t\tcase 'time':\n\t\t\tcase 'datetime':\n\t\t\tcase 'color':\n\t\t\tcase 'confirm':\n\t\t\tcase 'select':\n\t\t\tcase 'checkbox':\n\t\t\t\tif (field.default !== undefined && !matchesField(field, field.default)) {\n\t\t\t\t\tfaults.push(`Field \"${field.name}\" has an invalid default`)\n\t\t\t\t}\n\t\t\t\tbreak\n\t\t}\n\n\t\tif (field.control === 'select' || field.control === 'checkbox') {\n\t\t\tconst choices = new Set<string>()\n\t\t\tconst count = Math.min(field.choices.length, CHOICE_LIMIT + 1)\n\t\t\tlet enabled = 0\n\n\t\t\tfor (let index = 0; index < count; index += 1) {\n\t\t\t\tconst choice = field.choices[index]\n\t\t\t\tif (choice === undefined) continue\n\t\t\t\tif (choice.disabled !== true) enabled += 1\n\t\t\t\tif (choices.has(choice.value)) {\n\t\t\t\t\tfaults.push(`Field \"${field.name}\" offers choice \"${choice.value}\" more than once`)\n\t\t\t\t}\n\t\t\t\tchoices.add(choice.value)\n\t\t\t}\n\n\t\t\tif (\n\t\t\t\tfield.control === 'select' &&\n\t\t\t\tfield.rule?.required === true &&\n\t\t\t\tfield.open !== true &&\n\t\t\t\tenabled === 0\n\t\t\t) {\n\t\t\t\tfaults.push(`Field \"${field.name}\" is required but offers no enabled choice`)\n\t\t\t}\n\n\t\t\tconst minimum = field.rule?.minimum\n\t\t\tif (\n\t\t\t\tfield.control === 'checkbox' &&\n\t\t\t\tisFiniteNumber(minimum) &&\n\t\t\t\tminimum > 0 &&\n\t\t\t\tminimum > enabled\n\t\t\t) {\n\t\t\t\tfaults.push(\n\t\t\t\t\t`Field \"${field.name}\" has minimum ${minimum} but offers only ${enabled} enabled ${enabled === 1 ? 'choice' : 'choices'}`,\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\n\t\tconst rule = field.rule\n\t\tif (rule === undefined) continue\n\n\t\tconst temporal =\n\t\t\tfield.control === 'date' || field.control === 'time' || field.control === 'datetime'\n\t\tif (rule.minimum !== undefined && !appliesRule(field.control, 'minimum')) {\n\t\t\tfaults.push(`Field \"${field.name}\" has minimum on ${field.control}`)\n\t\t} else if (isString(rule.minimum) && !temporal) {\n\t\t\tfaults.push(`Field \"${field.name}\" has a string minimum on ${field.control}`)\n\t\t}\n\t\tif (rule.maximum !== undefined && !appliesRule(field.control, 'maximum')) {\n\t\t\tfaults.push(`Field \"${field.name}\" has maximum on ${field.control}`)\n\t\t} else if (isString(rule.maximum) && !temporal) {\n\t\t\tfaults.push(`Field \"${field.name}\" has a string maximum on ${field.control}`)\n\t\t}\n\t\tif (isFiniteNumber(rule.minimum) && temporal) {\n\t\t\tfaults.push(`Field \"${field.name}\" has a numeric minimum on ${field.control}`)\n\t\t}\n\t\tif (isFiniteNumber(rule.maximum) && temporal) {\n\t\t\tfaults.push(`Field \"${field.name}\" has a numeric maximum on ${field.control}`)\n\t\t}\n\t\tif (\n\t\t\tisFiniteNumber(rule.maximum) &&\n\t\t\trule.maximum < 0 &&\n\t\t\t(field.control === 'text' ||\n\t\t\t\tfield.control === 'editor' ||\n\t\t\t\tfield.control === 'password' ||\n\t\t\t\tfield.control === 'checkbox' ||\n\t\t\t\tfield.control === 'file')\n\t\t) {\n\t\t\tfaults.push(`Field \"${field.name}\" has a negative maximum on ${field.control}`)\n\t\t}\n\n\t\tif (rule.step !== undefined && !appliesRule(field.control, 'step')) {\n\t\t\tfaults.push(`Field \"${field.name}\" has step on ${field.control}`)\n\t\t}\n\t\tif (rule.step !== undefined && rule.step <= 0) {\n\t\t\tfaults.push(`Field \"${field.name}\" has a non-positive step`)\n\t\t}\n\n\t\tif (rule.pattern !== undefined && !appliesRule(field.control, 'pattern')) {\n\t\t\tfaults.push(`Field \"${field.name}\" has pattern on ${field.control}`)\n\t\t}\n\t\tif (rule.email === true && !appliesRule(field.control, 'email')) {\n\t\t\tfaults.push(`Field \"${field.name}\" has email on ${field.control}`)\n\t\t}\n\t\tif (rule.url === true && !appliesRule(field.control, 'url')) {\n\t\t\tfaults.push(`Field \"${field.name}\" has url on ${field.control}`)\n\t\t}\n\t\tif (rule.alphanumeric === true && !appliesRule(field.control, 'alphanumeric')) {\n\t\t\tfaults.push(`Field \"${field.name}\" has alphanumeric on ${field.control}`)\n\t\t}\n\t\tif (rule.integer === true && !appliesRule(field.control, 'integer')) {\n\t\t\tfaults.push(`Field \"${field.name}\" has integer on ${field.control}`)\n\t\t}\n\n\t\tif (rule.pattern !== undefined) {\n\t\t\tif (rule.pattern.length > PATTERN_LIMIT) {\n\t\t\t\tfaults.push(`Field \"${field.name}\" has a pattern longer than ${PATTERN_LIMIT}`)\n\t\t\t} else {\n\t\t\t\ttry {\n\t\t\t\t\tRegExp(rule.pattern)\n\t\t\t\t} catch {\n\t\t\t\t\tfaults.push(`Field \"${field.name}\" has an invalid pattern`)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (\n\t\t\t(isFiniteNumber(rule.minimum) &&\n\t\t\t\tisFiniteNumber(rule.maximum) &&\n\t\t\t\trule.minimum > rule.maximum) ||\n\t\t\t(isString(rule.minimum) && isString(rule.maximum) && rule.minimum > rule.maximum)\n\t\t) {\n\t\t\tfaults.push(`Field \"${field.name}\" has minimum greater than maximum`)\n\t\t}\n\n\t\tif (field.control === 'date') {\n\t\t\tif (isString(rule.minimum) && !DATE_PATTERN.test(rule.minimum)) {\n\t\t\t\tfaults.push(`Field \"${field.name}\" has an invalid date minimum`)\n\t\t\t}\n\t\t\tif (isString(rule.maximum) && !DATE_PATTERN.test(rule.maximum)) {\n\t\t\t\tfaults.push(`Field \"${field.name}\" has an invalid date maximum`)\n\t\t\t}\n\t\t}\n\n\t\tif (field.control === 'time') {\n\t\t\tif (isString(rule.minimum) && !TIME_PATTERN.test(rule.minimum)) {\n\t\t\t\tfaults.push(`Field \"${field.name}\" has an invalid time minimum`)\n\t\t\t}\n\t\t\tif (isString(rule.maximum) && !TIME_PATTERN.test(rule.maximum)) {\n\t\t\t\tfaults.push(`Field \"${field.name}\" has an invalid time maximum`)\n\t\t\t}\n\t\t}\n\n\t\tif (field.control === 'datetime') {\n\t\t\tif (isString(rule.minimum) && !DATETIME_PATTERN.test(rule.minimum)) {\n\t\t\t\tfaults.push(`Field \"${field.name}\" has an invalid datetime minimum`)\n\t\t\t}\n\t\t\tif (isString(rule.maximum) && !DATETIME_PATTERN.test(rule.maximum)) {\n\t\t\t\tfaults.push(`Field \"${field.name}\" has an invalid datetime maximum`)\n\t\t\t}\n\t\t}\n\t}\n\n\treturn Object.freeze(faults)\n}\n","import type { FieldError, FieldRuleName, FieldValue, FormField } from '../core/types.js'\nimport type { FormControl } from './types.js'\nimport { formatMessage, matchesAnswer, matchesField } from '../core/helpers.js'\nimport { FormBindingError } from './errors.js'\n\n/**\n * Checks a native control population against a field's declared value shape.\n * @param field - The declared field.\n * @param controls - Associated controls sharing its name.\n * @returns True if the population has an unambiguous compatible shape; false otherwise.\n * @example\n * ```ts\n * matchesFormControls({ control: 'text', name: 'name' }, [document.createElement('input')]) // true\n * ```\n */\nexport function matchesFormControls(field: FormField, controls: readonly FormControl[]): boolean {\n\tif (controls.length === 0) return true\n\tif (controls.length > 1 && field.control !== 'checkbox' && field.control !== 'select')\n\t\treturn false\n\treturn controls.every((control) => {\n\t\tif (control instanceof HTMLTextAreaElement)\n\t\t\treturn field.control === 'editor' && controls.length === 1\n\t\tif (control instanceof HTMLSelectElement) {\n\t\t\treturn (\n\t\t\t\tcontrols.length === 1 &&\n\t\t\t\t(control.multiple ? field.control === 'checkbox' : field.control === 'select')\n\t\t\t)\n\t\t}\n\t\tswitch (field.control) {\n\t\t\tcase 'text':\n\t\t\t\treturn ['text', 'search', 'email', 'url', 'tel', 'hidden'].includes(control.type)\n\t\t\tcase 'password':\n\t\t\t\treturn control.type === 'password' || control.type === 'text'\n\t\t\tcase 'number':\n\t\t\t\treturn control.type === 'number' || control.type === 'range'\n\t\t\tcase 'datetime':\n\t\t\t\treturn control.type === 'datetime-local'\n\t\t\tcase 'confirm':\n\t\t\t\treturn control.type === 'checkbox'\n\t\t\tcase 'checkbox':\n\t\t\t\treturn (\n\t\t\t\t\tcontrol.type === 'checkbox' &&\n\t\t\t\t\tcontrols.every((entry) => entry instanceof HTMLInputElement)\n\t\t\t\t)\n\t\t\tcase 'select':\n\t\t\t\treturn (\n\t\t\t\t\tcontrol.type === 'radio' &&\n\t\t\t\t\tcontrols.every((entry) => entry instanceof HTMLInputElement && entry.type === 'radio')\n\t\t\t\t)\n\t\t\tcase 'editor':\n\t\t\t\treturn false\n\t\t\tdefault:\n\t\t\t\treturn control.type === field.control\n\t\t}\n\t})\n}\n\n/**\n * Reads native constraint messages not already reported by document rules.\n * @param control - The native control with binding-owned custom validity removed.\n * @param errors - The document's reported failures.\n * @returns Uncovered constraint messages, preserving authored custom validity.\n * @remarks\n * A lone native failure retains platform copy. Competing failures use constraint-specific\n * copy because the platform exposes only one message for the whole control.\n * @example\n * ```ts\n * const input = document.createElement('input')\n * input.name = 'name'\n * input.required = true\n * readNativeMessages(input, [{ field: 'name', rule: 'required', message: 'Enter a name' }]) // []\n * ```\n */\nexport function readNativeMessages(\n\tcontrol: FormControl,\n\terrors: readonly FieldError[],\n): readonly string[] {\n\tconst rules = new Set(\n\t\terrors.filter((error) => error.field === control.name).map((error) => error.rule),\n\t)\n\tconst validity = control.validity\n\tconst failures = new Map<FieldRuleName, string>()\n\tif (validity.valueMissing) failures.set('required', formatMessage('required'))\n\tif (validity.rangeUnderflow)\n\t\tfailures.set('minimum', formatMessage('minimum', control.getAttribute('min') ?? ''))\n\tif (validity.rangeOverflow)\n\t\tfailures.set('maximum', formatMessage('maximum', control.getAttribute('max') ?? ''))\n\tif (validity.tooShort)\n\t\tfailures.set('minimum', formatMessage('minimum', control.getAttribute('minlength') ?? ''))\n\tif (validity.tooLong)\n\t\tfailures.set('maximum', formatMessage('maximum', control.getAttribute('maxlength') ?? ''))\n\tif (validity.stepMismatch) failures.set('step', 'Use a permitted value for this step')\n\tif (validity.patternMismatch) failures.set('pattern', formatMessage('pattern'))\n\tif (validity.typeMismatch && control instanceof HTMLInputElement) {\n\t\tif (control.type === 'email') failures.set('email', formatMessage('email'))\n\t\telse if (control.type === 'url') failures.set('url', formatMessage('url'))\n\t}\n\tconst messages: string[] = []\n\tif (validity.customError) messages.push(control.validationMessage)\n\tconst lone = failures.size + Number(validity.customError) + Number(validity.badInput) === 1\n\tif (validity.badInput) messages.push(lone ? control.validationMessage : 'Enter a valid value')\n\tfor (const [rule, message] of failures) {\n\t\tif (!rules.has(rule)) messages.push(lone ? control.validationMessage : message)\n\t}\n\treturn messages\n}\n\n/**\n * Reads one compatible native population using the document's blank-to-absence semantics.\n * @param field - The field declaring the answer shape.\n * @param controls - Controls sharing the field's name, in native order.\n * @returns The native answer, or undefined for an absent answer or empty population.\n * @throws Thrown when the population or answer is incompatible, including numeric bad input.\n * @example\n * ```ts\n * const input = document.createElement('input')\n * input.value = 'Ada'\n * readFormControls({ control: 'text', name: 'name' }, [input]) // 'Ada'\n * ```\n */\nexport function readFormControls(\n\tfield: FormField,\n\tcontrols: readonly FormControl[],\n): FieldValue | undefined {\n\tif (!matchesFormControls(field, controls)) {\n\t\tthrow new FormBindingError('CONTROL', `Incompatible controls for \"${field.name}\"`, {\n\t\t\tfield: field.name,\n\t\t})\n\t}\n\tconst first = controls[0]\n\tif (first === undefined) return undefined\n\tlet value: FieldValue | undefined\n\tif (field.control === 'checkbox') {\n\t\tvalue =\n\t\t\tfirst instanceof HTMLSelectElement\n\t\t\t\t? Array.from(first.selectedOptions, (option) => option.value)\n\t\t\t\t: controls\n\t\t\t\t\t\t.filter((control) => control instanceof HTMLInputElement && control.checked)\n\t\t\t\t\t\t.map((control) => control.value)\n\t} else if (field.control === 'select' && first instanceof HTMLInputElement) {\n\t\tvalue = controls.find(\n\t\t\t(control) => control instanceof HTMLInputElement && control.checked,\n\t\t)?.value\n\t} else if (field.control === 'confirm' && first instanceof HTMLInputElement) {\n\t\tvalue = first.checked\n\t} else if (field.control === 'number' && first instanceof HTMLInputElement) {\n\t\tif (first.validity.badInput)\n\t\t\tthrow new FormBindingError('CONTROL', 'Enter a number', { field: field.name })\n\t\tvalue = first.value === '' ? undefined : first.valueAsNumber\n\t} else if (field.control === 'file' && first instanceof HTMLInputElement) {\n\t\tvalue = Array.from(first.files ?? [], (file) => file.name)\n\t} else {\n\t\tvalue = first.value\n\t}\n\tif (!matchesAnswer(value)) return undefined\n\tif (value !== undefined && !matchesField(field, value)) {\n\t\tthrow new FormBindingError('CONTROL', `Incompatible native answer for \"${field.name}\"`, {\n\t\t\tfield: field.name,\n\t\t})\n\t}\n\treturn value\n}\n","import type { FormControl } from './types.js'\nimport { isInstance } from '@orkestrel/contract'\n\n/**\n * Checks whether a value is a native answer control.\n * @param value - The value to inspect.\n * @returns True if the value is an input, select, or textarea; false otherwise.\n * @example\n * ```ts\n * isFormControl(document.createElement('input')) // true\n * ```\n */\nexport function isFormControl(value: unknown): value is FormControl {\n\treturn (\n\t\t(typeof HTMLInputElement !== 'undefined' && isInstance(value, HTMLInputElement)) ||\n\t\t(typeof HTMLSelectElement !== 'undefined' && isInstance(value, HTMLSelectElement)) ||\n\t\t(typeof HTMLTextAreaElement !== 'undefined' && isInstance(value, HTMLTextAreaElement))\n\t)\n}\n","import type { EmitterInterface } from '@orkestrel/emitter'\nimport type { FieldError, FieldValue, FormField, FormInterface } from '../core/types.js'\nimport type {\n\tFormBindingAttribute,\n\tFormBindingEventMap,\n\tFormBindingInterface,\n\tFormBindingOptions,\n\tFormBindingValidity,\n\tFormControl,\n} from './types.js'\nimport { Emitter } from '@orkestrel/emitter'\nimport { matchesValue } from '../core/helpers.js'\nimport { FormBindingError, isFormBindingError } from './errors.js'\nimport { matchesFormControls, readFormControls, readNativeMessages } from './helpers.js'\nimport { isFormControl } from './validators.js'\n\n/** Implements a controlled native form over a borrowed document. */\nexport class FormBinding implements FormBindingInterface {\n\treadonly #element: HTMLFormElement\n\treadonly #document: FormInterface\n\treadonly #options: FormBindingOptions | undefined\n\treadonly #emitter: Emitter<FormBindingEventMap>\n\treadonly #observer: MutationObserver\n\treadonly #listeners = new AbortController()\n\treadonly #attributes = new Map<Element, Map<string, FormBindingAttribute>>()\n\treadonly #validity = new Map<FormControl, FormBindingValidity>()\n\treadonly #feedback = new Map<string, HTMLElement>()\n\treadonly #excluded = new Set<string>()\n\treadonly #logical = new Set<string>()\n\treadonly #unreadable = new Set<string>()\n\treadonly #names = new Map<FormControl, string>()\n\treadonly #readings = new Map<FormControl, string>()\n\treadonly #deferred = new Set<string>()\n\treadonly #refresh = this.#onState.bind(this)\n\treadonly #fill = this.#onFill.bind(this)\n\treadonly #disable = this.#onDisable.bind(this)\n\treadonly #enable = this.#onEnable.bind(this)\n\treadonly #clear = this.#onClear.bind(this)\n\treadonly #abandon = this.destroy.bind(this)\n\t#controls: ReadonlyMap<string, readonly FormControl[]> = new Map()\n\t#summary: HTMLUListElement | undefined\n\t#summaryText = ''\n\t#destroyed = false\n\t#running = false\n\t#preserve: boolean | undefined\n\t#reading = false\n\t#excluding: string | undefined\n\t#changed = false\n\t#notified = false\n\t#scheduled = false\n\t#submitting = false\n\t#pointer: number | undefined\n\t#release: ReturnType<typeof setTimeout> | undefined\n\n\t/**\n\t * Binds existing controls to the supplied document's answers.\n\t * @param element - The native form, including its externally associated controls.\n\t * @param document - The borrowed answer document.\n\t * @param options - Feedback destinations and listeners.\n\t * @throws Thrown when controls contradict the schema or native initialization fails.\n\t */\n\tconstructor(element: HTMLFormElement, document: FormInterface, options?: FormBindingOptions) {\n\t\tthis.#element = element\n\t\tthis.#document = document\n\t\tthis.#options = options\n\t\tthis.#emitter = new Emitter<FormBindingEventMap>(options)\n\t\tthis.#observer = new MutationObserver(() => this.#onDocument(true))\n\t\tfor (const name of document.disabled) this.#logical.add(name)\n\t\ttry {\n\t\t\tthis.#controls = this.#discover()\n\t\t\tconst signal = this.#listeners.signal\n\t\t\telement.addEventListener('submit', (event) => this.#onSubmit(event), { signal })\n\t\t\telement.addEventListener('reset', (event) => this.#onReset(event), { signal })\n\t\t\telement.ownerDocument.addEventListener('input', (event) => this.#onInput(event), { signal })\n\t\t\telement.ownerDocument.addEventListener('change', (event) => this.#onInput(event), { signal })\n\t\t\telement.ownerDocument.addEventListener('focusout', (event) => this.#onBlur(event), { signal })\n\t\t\telement.ownerDocument.addEventListener('click', (event) => this.#onClick(event), { signal })\n\t\t\tif (options?.defer) {\n\t\t\t\telement.ownerDocument.addEventListener('pointerdown', (event) => this.#onPress(event), {\n\t\t\t\t\tsignal,\n\t\t\t\t\tcapture: true,\n\t\t\t\t})\n\t\t\t\telement.ownerDocument.addEventListener('pointerup', (event) => this.#onRelease(event), {\n\t\t\t\t\tsignal,\n\t\t\t\t\tcapture: true,\n\t\t\t\t})\n\t\t\t\telement.ownerDocument.addEventListener('pointercancel', (event) => this.#onRelease(event), {\n\t\t\t\t\tsignal,\n\t\t\t\t\tcapture: true,\n\t\t\t\t})\n\t\t\t\telement.ownerDocument.defaultView?.addEventListener('blur', () => this.#finishPress(), {\n\t\t\t\t\tsignal,\n\t\t\t\t})\n\t\t\t}\n\t\t\tdocument.emitter.on('fill', this.#fill)\n\t\t\tdocument.emitter.on('disable', this.#disable)\n\t\t\tdocument.emitter.on('enable', this.#enable)\n\t\t\tdocument.emitter.on('clear', this.#clear)\n\t\t\tdocument.emitter.on('abandon', this.#abandon)\n\t\t\tfor (const event of ['touch', 'validate', 'prepare', 'accept', 'abort', 'submit'] as const) {\n\t\t\t\tdocument.emitter.on(event, this.#refresh)\n\t\t\t}\n\t\t\tthis.refresh()\n\t\t\tif (!this.#destroyed) {\n\t\t\t\tthis.#observer.observe(element.ownerDocument, {\n\t\t\t\t\tsubtree: true,\n\t\t\t\t\tchildList: true,\n\t\t\t\t\tattributes: true,\n\t\t\t\t\tattributeFilter: [\n\t\t\t\t\t\t'name',\n\t\t\t\t\t\t'form',\n\t\t\t\t\t\t'id',\n\t\t\t\t\t\t'type',\n\t\t\t\t\t\t'multiple',\n\t\t\t\t\t\t'disabled',\n\t\t\t\t\t\t'required',\n\t\t\t\t\t\t'min',\n\t\t\t\t\t\t'max',\n\t\t\t\t\t\t'step',\n\t\t\t\t\t\t'pattern',\n\t\t\t\t\t\t'minlength',\n\t\t\t\t\t\t'maxlength',\n\t\t\t\t\t\t'value',\n\t\t\t\t\t\t'hidden',\n\t\t\t\t\t\t'inert',\n\t\t\t\t\t\t'style',\n\t\t\t\t\t\t'class',\n\t\t\t\t\t],\n\t\t\t\t})\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tthis.destroy()\n\t\t\tthrow error\n\t\t}\n\t}\n\n\t/** Holds the native form. */\n\tget element(): HTMLFormElement {\n\t\treturn this.#element\n\t}\n\t/** Holds the borrowed answer document. */\n\tget document(): FormInterface {\n\t\treturn this.#document\n\t}\n\t/** Reports whether the binding released its resources. */\n\tget destroyed(): boolean {\n\t\treturn this.#destroyed\n\t}\n\t/** Holds the binding's isolated event emitter. */\n\tget emitter(): EmitterInterface<FormBindingEventMap> {\n\t\treturn this.#emitter\n\t}\n\n\t/** Imports mounted enabled controls without acknowledging their answers. */\n\tread(): void {\n\t\tif (this.#destroyed || this.#reading || this.#document.status !== 'editing') return\n\t\tconst controls = this.#discover()\n\t\tconst answers = new Map<string, FieldValue | undefined>()\n\t\tfor (const field of this.#document.schema.fields) {\n\t\t\tconst group = controls.get(field.name)\n\t\t\tif (group === undefined || field.locked || this.#logical.has(field.name)) continue\n\t\t\tconst enabled = group.filter((control) => !this.#nativeDisabled(control))\n\t\t\tif (enabled.length === 0) continue\n\t\t\tif (enabled.some((control) => control.validity.badInput)) {\n\t\t\t\tthis.#unreadable.add(field.name)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tthis.#unreadable.delete(field.name)\n\t\t\tanswers.set(field.name, readFormControls(field, enabled))\n\t\t}\n\t\tthis.#controls = controls\n\t\tthis.#reading = true\n\t\ttry {\n\t\t\tthis.#exclusions()\n\t\t\tfor (const [name, value] of answers) {\n\t\t\t\tif (this.#destroyed) break\n\t\t\t\tconst values = this.#document.values\n\t\t\t\tconst previous = Object.hasOwn(values, name) ? values[name] : undefined\n\t\t\t\tif (\n\t\t\t\t\tprevious === undefined || value === undefined\n\t\t\t\t\t\t? previous !== value\n\t\t\t\t\t\t: !matchesValue(previous, value)\n\t\t\t\t)\n\t\t\t\t\tthis.#document.fill(name, value)\n\t\t\t}\n\t\t} finally {\n\t\t\tthis.#reading = false\n\t\t\tif (!this.#destroyed) this.#drain()\n\t\t}\n\t}\n\n\t/** Rediscovers controls and projects the document into the native form. */\n\trefresh(): void {\n\t\tif (this.#destroyed) return\n\t\tthis.#unreadable.clear()\n\t\tthis.#drain()\n\t}\n\n\t/** Releases owned DOM effects and listeners without destroying the document. */\n\tdestroy(): void {\n\t\tif (this.#destroyed) return\n\t\tthis.#destroyed = true\n\t\tthis.#observer.disconnect()\n\t\tthis.#listeners.abort()\n\t\tthis.#clearPress()\n\t\tthis.#document.emitter.off('fill', this.#fill)\n\t\tthis.#document.emitter.off('disable', this.#disable)\n\t\tthis.#document.emitter.off('enable', this.#enable)\n\t\tthis.#document.emitter.off('clear', this.#clear)\n\t\tthis.#document.emitter.off('abandon', this.#abandon)\n\t\tfor (const event of ['touch', 'validate', 'prepare', 'accept', 'abort', 'submit'] as const) {\n\t\t\tthis.#document.emitter.off(event, this.#refresh)\n\t\t}\n\t\tfor (const element of this.#attributes.keys()) this.#restore(element)\n\t\tfor (const control of this.#validity.keys()) this.#restoreValidity(control)\n\t\tfor (const node of this.#feedback.values()) node.remove()\n\t\tthis.#summary?.remove()\n\t\tif (this.#document.status === 'editing') {\n\t\t\tfor (const name of this.#excluded) {\n\t\t\t\tif (!this.#logical.has(name)) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tthis.#document.enable(name)\n\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\tthis.#emitter.emit('error', error)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tthis.#excluded.clear()\n\t\tthis.#feedback.clear()\n\t\tthis.#names.clear()\n\t\tthis.#readings.clear()\n\t\tthis.#unreadable.clear()\n\t\tthis.#logical.clear()\n\t\tthis.#controls = new Map()\n\t\tthis.#summary = undefined\n\t\tthis.#emitter.destroy()\n\t}\n\n\t#drain(preserve = false): void {\n\t\tif (this.#destroyed) return\n\t\tthis.#preserve = this.#preserve === false ? false : preserve\n\t\tif (this.#running || this.#reading) return\n\t\tthis.#running = true\n\t\ttry {\n\t\t\tlet turns = 0\n\t\t\twhile (this.#preserve !== undefined && !this.#destroyed && turns < 2) {\n\t\t\t\tconst retaining = this.#preserve\n\t\t\t\tthis.#preserve = undefined\n\t\t\t\tthis.#changed = false\n\t\t\t\tthis.#controls = this.#discover()\n\t\t\t\tthis.#releaseRemoved()\n\t\t\t\tthis.#exclusions()\n\t\t\t\tif (this.#destroyed) break\n\t\t\t\tthis.#paint(retaining)\n\t\t\t\tif (!this.#destroyed && (this.#changed || !this.#notified)) {\n\t\t\t\t\tthis.#notified = true\n\t\t\t\t\tthis.#emitter.emit('render')\n\t\t\t\t}\n\t\t\t\tturns += 1\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tthis.#preserve = undefined\n\t\t\tthrow isFormBindingError(error)\n\t\t\t\t? error\n\t\t\t\t: new FormBindingError('DOM', 'Native form projection failed', { cause: error })\n\t\t} finally {\n\t\t\tthis.#running = false\n\t\t\tif (this.#preserve !== undefined && !this.#destroyed && !this.#scheduled) {\n\t\t\t\tthis.#scheduled = true\n\t\t\t\tqueueMicrotask(() => {\n\t\t\t\t\tthis.#scheduled = false\n\t\t\t\t\tif (!this.#destroyed) this.#onDocument(this.#preserve ?? false)\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\t}\n\n\t#discover(): ReadonlyMap<string, readonly FormControl[]> {\n\t\tconst groups = new Map<string, FormControl[]>()\n\t\tfor (const control of Array.from(this.#element.elements)) {\n\t\t\tif (\n\t\t\t\t!isFormControl(control) ||\n\t\t\t\tcontrol.form !== this.#element ||\n\t\t\t\t!this.#document.field(control.name)\n\t\t\t)\n\t\t\t\tcontinue\n\t\t\tif (\n\t\t\t\tcontrol instanceof HTMLInputElement &&\n\t\t\t\t['submit', 'reset', 'button', 'image'].includes(control.type)\n\t\t\t)\n\t\t\t\tcontinue\n\t\t\tconst group = groups.get(control.name) ?? []\n\t\t\tgroup.push(control)\n\t\t\tgroups.set(control.name, group)\n\t\t}\n\t\tfor (const field of this.#document.schema.fields) {\n\t\t\tconst group = groups.get(field.name)\n\t\t\tif (group !== undefined && !matchesFormControls(field, group)) {\n\t\t\t\tthrow new FormBindingError('CONTROL', `Incompatible controls for \"${field.name}\"`, {\n\t\t\t\t\tfield: field.name,\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\t\treturn groups\n\t}\n\n\t#nativeDisabled(control: FormControl): boolean {\n\t\tconst owned = this.#attributes.get(control)?.get('disabled')\n\t\tif (\n\t\t\towned !== undefined &&\n\t\t\tcontrol.getAttribute('disabled') === owned.value &&\n\t\t\towned.original === null\n\t\t) {\n\t\t\t// A model-owned disabled attribute cannot become a native exclusion on the next scan.\n\t\t\tlet ancestor = control.parentElement\n\t\t\twhile (ancestor !== null) {\n\t\t\t\tif (ancestor instanceof HTMLFieldSetElement && ancestor.disabled) {\n\t\t\t\t\tconst legend = Array.from(ancestor.children).find(\n\t\t\t\t\t\t(child) => child instanceof HTMLLegendElement,\n\t\t\t\t\t)\n\t\t\t\t\tif (legend === undefined || !legend.contains(control)) return true\n\t\t\t\t}\n\t\t\t\tancestor = ancestor.parentElement\n\t\t\t}\n\t\t\treturn false\n\t\t}\n\t\treturn control.matches(':disabled')\n\t}\n\n\t#exclusions(): void {\n\t\tif (this.#document.status !== 'editing') return\n\t\tfor (const field of this.#document.schema.fields) {\n\t\t\tif (this.#destroyed) return\n\t\t\tconst group = this.#controls.get(field.name)\n\t\t\tconst excluded =\n\t\t\t\tgroup !== undefined && group.every((control) => this.#nativeDisabled(control))\n\t\t\tif (excluded && !this.#logical.has(field.name) && !this.#excluded.has(field.name)) {\n\t\t\t\tthis.#excluded.add(field.name)\n\t\t\t\tthis.#excluding = field.name\n\t\t\t\ttry {\n\t\t\t\t\tthis.#document.disable(field.name)\n\t\t\t\t} finally {\n\t\t\t\t\tthis.#excluding = undefined\n\t\t\t\t}\n\t\t\t} else if (!excluded && this.#excluded.delete(field.name) && !this.#logical.has(field.name)) {\n\t\t\t\tthis.#excluding = field.name\n\t\t\t\ttry {\n\t\t\t\t\tthis.#document.enable(field.name)\n\t\t\t\t} finally {\n\t\t\t\t\tthis.#excluding = undefined\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t#paint(preserve: boolean): void {\n\t\tthis.#attribute(this.#element, 'novalidate', '')\n\t\tthis.#attribute(\n\t\t\tthis.#element,\n\t\t\t'aria-busy',\n\t\t\tthis.#document.pending === undefined ? 'false' : 'true',\n\t\t)\n\t\tconst errors: FieldError[] = [...this.#document.errors]\n\t\tconst values = this.#document.values\n\t\tfor (const field of this.#document.schema.fields) {\n\t\t\tif (this.#destroyed) return\n\t\t\tconst group = this.#controls.get(field.name) ?? []\n\t\t\tfor (const control of group) {\n\t\t\t\tthis.#metadata(field, control)\n\t\t\t\tif (!this.#unreadable.has(field.name))\n\t\t\t\t\tthis.#value(\n\t\t\t\t\t\tfield,\n\t\t\t\t\t\tcontrol,\n\t\t\t\t\t\tObject.hasOwn(values, field.name) ? values[field.name] : undefined,\n\t\t\t\t\t\tpreserve,\n\t\t\t\t\t)\n\t\t\t\tif (this.#destroyed) return\n\t\t\t\tthis.#restoreValidity(control)\n\t\t\t\tif (\n\t\t\t\t\t!this.#document.disabled.has(field.name) &&\n\t\t\t\t\t!control.validity.valid &&\n\t\t\t\t\tcontrol.willValidate\n\t\t\t\t) {\n\t\t\t\t\tfor (const message of readNativeMessages(control, this.#document.errors))\n\t\t\t\t\t\terrors.push({ field: field.name, message })\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tfor (const field of this.#document.schema.fields) {\n\t\t\tif (this.#destroyed) return\n\t\t\tconst messages = [\n\t\t\t\t...new Set(\n\t\t\t\t\terrors.filter((error) => error.field === field.name).map((error) => error.message),\n\t\t\t\t),\n\t\t\t]\n\t\t\tconst message = messages.join('\\n')\n\t\t\tconst visible =\n\t\t\t\tthis.#document.touched.has(field.name) && !this.#document.disabled.has(field.name)\n\t\t\tconst group = this.#controls.get(field.name) ?? []\n\t\t\tlet feedback = this.#feedback.get(field.name)\n\t\t\tif (\n\t\t\t\tfeedback === undefined &&\n\t\t\t\t(group.length > 0 || this.#options?.feedback?.has(field.name))\n\t\t\t) {\n\t\t\t\tfeedback = this.#element.ownerDocument.createElement('span')\n\t\t\t\tfeedback.id = `form-feedback-${crypto.randomUUID()}`\n\t\t\t\tfeedback.dataset.formFeedback = field.name\n\t\t\t\tconst destination = this.#options?.feedback?.get(field.name)\n\t\t\t\tif (destination !== undefined) destination.append(feedback)\n\t\t\t\telse (group.at(-1) ?? this.#element).insertAdjacentElement('afterend', feedback)\n\t\t\t\tthis.#feedback.set(field.name, feedback)\n\t\t\t}\n\t\t\tif (feedback !== undefined) {\n\t\t\t\tif (feedback.textContent !== message) {\n\t\t\t\t\tfeedback.textContent = message\n\t\t\t\t\tthis.#changed = true\n\t\t\t\t}\n\t\t\t\tif (feedback.hidden !== (!visible || message === '')) {\n\t\t\t\t\tfeedback.hidden = !visible || message === ''\n\t\t\t\t\tthis.#changed = true\n\t\t\t\t}\n\t\t\t}\n\t\t\tfor (const control of group) {\n\t\t\t\tif (message !== '' && !control.validity.customError) {\n\t\t\t\t\tthis.#validity.set(control, { original: '', value: message })\n\t\t\t\t\tcontrol.setCustomValidity(message)\n\t\t\t\t}\n\t\t\t\tthis.#attribute(control, 'aria-invalid', visible && message !== '' ? 'true' : null)\n\t\t\t\tif (feedback !== undefined)\n\t\t\t\t\tthis.#description(control, feedback.id, visible && message !== '')\n\t\t\t}\n\t\t}\n\t\tthis.#paintSummary(\n\t\t\terrors.filter(\n\t\t\t\t(error) =>\n\t\t\t\t\tthis.#document.touched.has(error.field) && !this.#document.disabled.has(error.field),\n\t\t\t),\n\t\t)\n\t\tfor (const control of Array.from(this.#element.elements)) {\n\t\t\tif (\n\t\t\t\t(control instanceof HTMLButtonElement && control.type === 'submit') ||\n\t\t\t\t(control instanceof HTMLInputElement && ['submit', 'image'].includes(control.type))\n\t\t\t) {\n\t\t\t\tthis.#attribute(\n\t\t\t\t\tcontrol,\n\t\t\t\t\t'aria-busy',\n\t\t\t\t\tthis.#document.pending === undefined ? 'false' : 'true',\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\t}\n\n\t#metadata(field: FormField, control: FormControl): void {\n\t\tif (this.#logical.has(field.name)) this.#attribute(control, 'disabled', '')\n\t\telse this.#releaseAttribute(control, 'disabled')\n\t\tif (\n\t\t\tfield.locked &&\n\t\t\t!(control instanceof HTMLSelectElement) &&\n\t\t\t!(\n\t\t\t\tcontrol instanceof HTMLInputElement &&\n\t\t\t\t['radio', 'checkbox', 'file', 'range', 'color'].includes(control.type)\n\t\t\t)\n\t\t)\n\t\t\tthis.#attribute(control, 'readonly', '')\n\t\tif (field.rule?.required && !['confirm', 'checkbox', 'file'].includes(field.control))\n\t\t\tthis.#attribute(control, 'required', '')\n\t\tif (\n\t\t\tfield.control === 'number' ||\n\t\t\tfield.control === 'date' ||\n\t\t\tfield.control === 'time' ||\n\t\t\tfield.control === 'datetime'\n\t\t) {\n\t\t\tif (field.rule?.minimum !== undefined && !control.hasAttribute('min'))\n\t\t\t\tthis.#attribute(control, 'min', String(field.rule.minimum))\n\t\t\tif (field.rule?.maximum !== undefined && !control.hasAttribute('max'))\n\t\t\t\tthis.#attribute(control, 'max', String(field.rule.maximum))\n\t\t\tif (\n\t\t\t\tfield.control === 'number' &&\n\t\t\t\tfield.rule?.step !== undefined &&\n\t\t\t\t!control.hasAttribute('step')\n\t\t\t)\n\t\t\t\tthis.#attribute(control, 'step', String(field.rule.step))\n\t\t}\n\t}\n\n\t#value(\n\t\tfield: FormField,\n\t\tcontrol: FormControl,\n\t\tvalue: FieldValue | undefined,\n\t\tpreserve: boolean,\n\t): void {\n\t\tif (\n\t\t\tpreserve &&\n\t\t\t!field.locked &&\n\t\t\tcontrol.ownerDocument.activeElement === control &&\n\t\t\tthis.#readings.has(control) &&\n\t\t\t(control instanceof HTMLTextAreaElement ||\n\t\t\t\t(control instanceof HTMLInputElement &&\n\t\t\t\t\t['text', 'search', 'email', 'url', 'tel', 'password'].includes(control.type))) &&\n\t\t\tcontrol.value !== this.#readings.get(control)\n\t\t)\n\t\t\treturn\n\t\tlet changed = false\n\t\tif (control instanceof HTMLInputElement && control.type === 'file') {\n\t\t\t// The browser alone owns bytes. A differing saved name cannot create a FileList.\n\t\t\tconst names = Array.from(control.files ?? [], (file) => file.name)\n\t\t\tif (names.length > 0 && (value === undefined || !matchesValue(names, value))) {\n\t\t\t\tcontrol.value = ''\n\t\t\t\tthis.#changed = true\n\t\t\t\tthis.#emitter.emit('write', control)\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t\tif (\n\t\t\tcontrol instanceof HTMLInputElement &&\n\t\t\t(control.type === 'checkbox' || control.type === 'radio')\n\t\t) {\n\t\t\tconst checked =\n\t\t\t\tfield.control === 'confirm'\n\t\t\t\t\t? value === true\n\t\t\t\t\t: Array.isArray(value)\n\t\t\t\t\t\t? value.includes(control.value)\n\t\t\t\t\t\t: value === control.value\n\t\t\tif (control.checked !== checked) {\n\t\t\t\tcontrol.checked = checked\n\t\t\t\tchanged = true\n\t\t\t}\n\t\t} else if (control instanceof HTMLSelectElement && control.multiple) {\n\t\t\tfor (const option of Array.from(control.options)) {\n\t\t\t\tconst selected = Array.isArray(value) && value.includes(option.value)\n\t\t\t\tif (option.selected !== selected) {\n\t\t\t\t\toption.selected = selected\n\t\t\t\t\tchanged = true\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tconst text = value === undefined ? '' : String(value)\n\t\t\tif (\n\t\t\t\tcontrol.value !== text ||\n\t\t\t\t(control instanceof HTMLInputElement && control.validity.badInput)\n\t\t\t) {\n\t\t\t\tconst before = control.value\n\t\t\t\tconst invalid = control.validity.badInput\n\t\t\t\tcontrol.value = text\n\t\t\t\tif (control.value !== before || invalid) changed = true\n\t\t\t}\n\t\t}\n\t\tthis.#readings.set(control, control.value)\n\t\tif (changed) {\n\t\t\tthis.#changed = true\n\t\t\tthis.#emitter.emit('write', control)\n\t\t}\n\t}\n\n\t#attribute(element: Element, name: string, value: string | null): void {\n\t\tconst attributes = this.#attributes.get(element) ?? new Map<string, FormBindingAttribute>()\n\t\tconst previous = attributes.get(name)\n\t\tconst current = element.getAttribute(name)\n\t\tconst original =\n\t\t\tprevious === undefined || previous.value !== current ? current : previous.original\n\t\tif (current !== value) {\n\t\t\tif (value === null) element.removeAttribute(name)\n\t\t\telse element.setAttribute(name, value)\n\t\t\tthis.#changed = true\n\t\t}\n\t\tattributes.set(name, { original, value })\n\t\tthis.#attributes.set(element, attributes)\n\t}\n\n\t#description(control: FormControl, id: string, shown: boolean): void {\n\t\tconst tokens = (control.getAttribute('aria-describedby') ?? '')\n\t\t\t.split(/\\s+/)\n\t\t\t.filter((token) => token !== '' && token !== id)\n\t\tif (shown) tokens.push(id)\n\t\tthis.#attribute(control, 'aria-describedby', tokens.length === 0 ? null : tokens.join(' '))\n\t}\n\n\t#releaseAttribute(element: Element, name: string): void {\n\t\tconst attributes = this.#attributes.get(element)\n\t\tconst owned = attributes?.get(name)\n\t\tif (owned === undefined) return\n\t\tif (element.getAttribute(name) === owned.value) {\n\t\t\tif (owned.original === null) element.removeAttribute(name)\n\t\t\telse element.setAttribute(name, owned.original)\n\t\t\tthis.#changed = true\n\t\t}\n\t\tattributes?.delete(name)\n\t}\n\n\t#restore(element: Element): void {\n\t\tfor (const name of this.#attributes.get(element)?.keys() ?? [])\n\t\t\tthis.#releaseAttribute(element, name)\n\t\tconst ids = new Set([...this.#feedback.values()].map((feedback) => feedback.id))\n\t\tconst description = element.getAttribute('aria-describedby')\n\t\tif (description !== null) {\n\t\t\tconst tokens = description.split(/\\s+/).filter((token) => token !== '' && !ids.has(token))\n\t\t\tif (tokens.length === 0) element.removeAttribute('aria-describedby')\n\t\t\telse if (tokens.join(' ') !== description)\n\t\t\t\telement.setAttribute('aria-describedby', tokens.join(' '))\n\t\t}\n\t\tthis.#attributes.delete(element)\n\t}\n\n\t#restoreValidity(control: FormControl): void {\n\t\tconst owned = this.#validity.get(control)\n\t\tif (\n\t\t\towned !== undefined &&\n\t\t\tcontrol.validity.customError &&\n\t\t\tcontrol.validationMessage === owned.value\n\t\t)\n\t\t\tcontrol.setCustomValidity(owned.original)\n\t\tthis.#validity.delete(control)\n\t}\n\n\t#releaseRemoved(): void {\n\t\tconst live = new Set([...this.#controls.values()].flat())\n\t\tfor (const [control, name] of this.#names) {\n\t\t\tif (!live.has(control) || control.name !== name) {\n\t\t\t\tthis.#restoreValidity(control)\n\t\t\t\tthis.#restore(control)\n\t\t\t\tthis.#names.delete(control)\n\t\t\t\tthis.#readings.delete(control)\n\t\t\t}\n\t\t}\n\t\tfor (const control of live) this.#names.set(control, control.name)\n\t\tfor (const element of this.#attributes.keys()) {\n\t\t\tif (\n\t\t\t\t(isFormControl(element) || element instanceof HTMLButtonElement) &&\n\t\t\t\telement.form !== this.#element\n\t\t\t)\n\t\t\t\tthis.#restore(element)\n\t\t}\n\t\tfor (const [name, feedback] of this.#feedback) {\n\t\t\tif (!this.#controls.has(name) && !this.#options?.feedback?.has(name)) {\n\t\t\t\tfeedback.remove()\n\t\t\t\tthis.#feedback.delete(name)\n\t\t\t}\n\t\t}\n\t}\n\n\t#paintSummary(errors: readonly FieldError[]): void {\n\t\tconst destination = this.#options?.summary\n\t\tif (destination === undefined) return\n\t\tconst text = JSON.stringify(\n\t\t\terrors.map((error) => [error.field, error.message, this.#focusable(error.field)?.id]),\n\t\t)\n\t\tif (text === this.#summaryText) return\n\t\tthis.#summaryText = text\n\t\tthis.#changed = true\n\t\tthis.#summary?.remove()\n\t\tthis.#summary = this.#element.ownerDocument.createElement('ul')\n\t\tthis.#summary.dataset.formSummary = ''\n\t\tthis.#summary.hidden = errors.length === 0\n\t\tfor (const error of errors) {\n\t\t\tconst entry = this.#element.ownerDocument.createElement('li')\n\t\t\tconst target = this.#focusable(error.field)\n\t\t\tconst label = this.#document.field(error.field)?.label ?? error.field\n\t\t\tif (target === undefined) entry.textContent = `${label}: ${error.message}`\n\t\t\telse {\n\t\t\t\tif (target.id === '') this.#attribute(target, 'id', `form-control-${crypto.randomUUID()}`)\n\t\t\t\tconst link = this.#element.ownerDocument.createElement('a')\n\t\t\t\tlink.href = `#${target.id}`\n\t\t\t\tlink.textContent = `${label}: ${error.message}`\n\t\t\t\tlink.dataset.formField = error.field\n\t\t\t\tentry.append(link)\n\t\t\t}\n\t\t\tthis.#summary.append(entry)\n\t\t}\n\t\tdestination.append(this.#summary)\n\t}\n\n\t#focusable(name: string): FormControl | undefined {\n\t\treturn this.#controls\n\t\t\t.get(name)\n\t\t\t?.find(\n\t\t\t\t(control) =>\n\t\t\t\t\t!control.matches(':disabled') &&\n\t\t\t\t\tcontrol.closest('[inert]') === null &&\n\t\t\t\t\tcontrol.getClientRects().length > 0 &&\n\t\t\t\t\t!['hidden', 'collapse'].includes(getComputedStyle(control).visibility) &&\n\t\t\t\t\t!(control instanceof HTMLInputElement && control.type === 'hidden'),\n\t\t\t)\n\t}\n\n\t#onDocument(preserve = false): void {\n\t\tif (this.#destroyed) return\n\t\ttry {\n\t\t\tthis.#drain(preserve)\n\t\t} catch (error) {\n\t\t\tthis.#emitter.emit('error', error)\n\t\t}\n\t}\n\n\t#onFill(name: string): void {\n\t\tthis.#unreadable.delete(name)\n\t\tthis.#onState()\n\t}\n\n\t#onState(): void {\n\t\tthis.#notified = false\n\t\tthis.#onDocument()\n\t}\n\n\t#onDisable(name: string): void {\n\t\tif (this.#excluding !== name) this.#logical.add(name)\n\t\tthis.#onState()\n\t}\n\n\t#onEnable(name: string): void {\n\t\tif (this.#excluding !== name) {\n\t\t\tthis.#logical.delete(name)\n\t\t\tthis.#excluded.delete(name)\n\t\t}\n\t\tthis.#onState()\n\t}\n\n\t#onClear(): void {\n\t\tthis.#clearPress()\n\t\tthis.#logical.clear()\n\t\tthis.#excluded.clear()\n\t\tthis.#unreadable.clear()\n\t\tfor (const name of this.#document.disabled) this.#logical.add(name)\n\t\tthis.#onState()\n\t}\n\n\t#onInput(event: Event): void {\n\t\tconst control = event.target\n\t\tif (\n\t\t\t!isFormControl(control) ||\n\t\t\tcontrol.form !== this.#element ||\n\t\t\t!this.#document.field(control.name) ||\n\t\t\tthis.#destroyed\n\t\t)\n\t\t\treturn\n\t\ttry {\n\t\t\tthis.read()\n\t\t} catch (error) {\n\t\t\tthis.#emitter.emit('error', error)\n\t\t}\n\t}\n\n\t#onClick(event: MouseEvent): void {\n\t\tconst control = event.target\n\t\tif (control instanceof Element) {\n\t\t\tconst link = control.closest('a[data-form-field]')\n\t\t\tif (\n\t\t\t\tlink instanceof HTMLAnchorElement &&\n\t\t\t\tthis.#summary?.contains(link) &&\n\t\t\t\tlink.dataset.formField !== undefined\n\t\t\t) {\n\t\t\t\tevent.preventDefault()\n\t\t\t\tthis.#focusable(link.dataset.formField)?.focus()\n\t\t\t}\n\t\t}\n\t\tif (\n\t\t\tcontrol instanceof HTMLInputElement &&\n\t\t\tcontrol.form === this.#element &&\n\t\t\tthis.#document.field(control.name)?.locked &&\n\t\t\t['file', 'checkbox', 'radio', 'color'].includes(control.type)\n\t\t)\n\t\t\tevent.preventDefault()\n\t}\n\n\t#onBlur(event: FocusEvent): void {\n\t\tlet control = event.target\n\t\tfor (const [native, presentation] of this.#options?.presentations ?? []) {\n\t\t\tif (control === presentation) {\n\t\t\t\tcontrol = native\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tif (\n\t\t\t!isFormControl(control) ||\n\t\t\tcontrol.form !== this.#element ||\n\t\t\t!this.#document.field(control.name) ||\n\t\t\tthis.#destroyed ||\n\t\t\tthis.#document.status !== 'editing'\n\t\t)\n\t\t\treturn\n\t\tif (event.relatedTarget === this.#options?.presentations?.get(control)) return\n\t\tif (this.#pointer !== undefined) {\n\t\t\tthis.#deferred.add(control.name)\n\t\t\treturn\n\t\t}\n\t\ttry {\n\t\t\tthis.#document.touch(control.name)\n\t\t\tthis.#drain()\n\t\t} catch (error) {\n\t\t\tthis.#emitter.emit('error', error)\n\t\t}\n\t}\n\n\t#onPress(event: PointerEvent): void {\n\t\tif (!event.isPrimary || event.button !== 0 || !(event.target instanceof Element)) return\n\t\tconst button = event.target.closest('button,input')\n\t\tif (\n\t\t\t!(\n\t\t\t\t(button instanceof HTMLButtonElement ||\n\t\t\t\t\t(button instanceof HTMLInputElement &&\n\t\t\t\t\t\t['button', 'submit', 'reset', 'image'].includes(button.type))) &&\n\t\t\t\tbutton.form === this.#element &&\n\t\t\t\t!button.matches(':disabled')\n\t\t\t)\n\t\t)\n\t\t\treturn\n\t\tthis.#finishPress()\n\t\tthis.#pointer = event.pointerId\n\t}\n\n\t#onRelease(event: PointerEvent): void {\n\t\tif (event.pointerId !== this.#pointer) return\n\t\t// A task keeps the pointerup-to-click sequence free of layout-changing feedback.\n\t\tif (this.#release !== undefined) clearTimeout(this.#release)\n\t\tthis.#release = setTimeout(() => this.#finishPress(), 0)\n\t}\n\n\t#finishPress(): void {\n\t\tconst names = [...this.#deferred]\n\t\tthis.#clearPress()\n\t\tif (this.#destroyed || this.#document.status !== 'editing') return\n\t\ttry {\n\t\t\tfor (const name of names) {\n\t\t\t\tif (this.#destroyed) break\n\t\t\t\tthis.#document.touch(name)\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tthis.#emitter.emit('error', error)\n\t\t}\n\t}\n\n\t#clearPress(): void {\n\t\tif (this.#release !== undefined) clearTimeout(this.#release)\n\t\tthis.#release = undefined\n\t\tthis.#pointer = undefined\n\t\tthis.#deferred.clear()\n\t}\n\n\t#onSubmit(event: SubmitEvent): void {\n\t\tif (event.defaultPrevented || this.#destroyed) return\n\t\tevent.preventDefault()\n\t\tif (this.#submitting || this.#document.pending !== undefined) {\n\t\t\tthis.#emitter.emit(\n\t\t\t\t'error',\n\t\t\t\tnew FormBindingError('PENDING', 'A submission is already pending'),\n\t\t\t)\n\t\t\treturn\n\t\t}\n\t\tthis.#submitting = true\n\t\ttry {\n\t\t\tthis.read()\n\t\t\tif (this.#destroyed) return\n\t\t\tfor (const field of this.#document.schema.fields) {\n\t\t\t\tif (!this.#document.disabled.has(field.name)) this.#document.touch(field.name)\n\t\t\t\tif (this.#destroyed) return\n\t\t\t}\n\t\t\tthis.#drain()\n\t\t\tconst native = [...this.#controls].filter(\n\t\t\t\t([name, controls]) =>\n\t\t\t\t\t!this.#document.disabled.has(name) &&\n\t\t\t\t\tcontrols.some((control) => control.willValidate && !control.validity.valid),\n\t\t\t)\n\t\t\tif (native.length > 0) {\n\t\t\t\tfor (const [name] of native) {\n\t\t\t\t\tconst target = this.#focusable(name)\n\t\t\t\t\tif (target !== undefined) {\n\t\t\t\t\t\ttarget.focus()\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst result = this.#document.prepare()\n\t\t\tif (this.#destroyed) return\n\t\t\tthis.#drain()\n\t\t\tif (result.success) {\n\t\t\t\tif (!result.value.signal.aborted && this.#document.pending === result.value)\n\t\t\t\t\tthis.#emitter.emit('submit', result.value, event.submitter ?? undefined)\n\t\t\t} else {\n\t\t\t\tfor (const error of result.error) {\n\t\t\t\t\tconst target = this.#focusable(error.field)\n\t\t\t\t\tif (target !== undefined) {\n\t\t\t\t\t\ttarget.focus()\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tthis.#emitter.emit('error', error)\n\t\t} finally {\n\t\t\tthis.#submitting = false\n\t\t}\n\t}\n\n\t#onReset(event: Event): void {\n\t\tif (event.defaultPrevented || this.#destroyed) return\n\t\tevent.preventDefault()\n\t\ttry {\n\t\t\tthis.#document.clear()\n\t\t\tthis.refresh()\n\t\t} catch (error) {\n\t\t\tthis.#emitter.emit('error', error)\n\t\t}\n\t}\n}\n","import type { FormInterface } from '../core/types.js'\nimport type { FormBindingInterface, FormBindingOptions } from './types.js'\nimport { FormBinding } from './FormBinding.js'\n\n/**\n * Creates a native form binding exposed through its lifecycle interface.\n * @param element - The native form.\n * @param document - The borrowed answer document.\n * @param options - Feedback destinations and listeners.\n * @returns The initialized native binding.\n * @example\n * ```ts\n * const binding = createFormBinding(element, document)\n * binding.refresh()\n * ```\n */\nexport function createFormBinding(\n\telement: HTMLFormElement,\n\tdocument: FormInterface,\n\toptions?: FormBindingOptions,\n): FormBindingInterface {\n\treturn new FormBinding(element, document, options)\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@orkestrel/form",
3
- "version": "0.0.10",
3
+ "version": "0.0.11",
4
4
  "description": "The @orkestrel/form package.",
5
5
  "keywords": [],
6
6
  "homepage": "https://github.com/orkestrel/form#readme",
@@ -80,7 +80,7 @@
80
80
  "@orkestrel/browser": "^0.0.28",
81
81
  "@orkestrel/guide": "^0.0.24",
82
82
  "@orkestrel/probe": "^0.0.23",
83
- "@orkestrel/scaffold": "^0.0.100",
83
+ "@orkestrel/scaffold": "^0.0.101",
84
84
  "@orkestrel/test": "^0.0.25",
85
85
  "@types/node": "^26.6.5",
86
86
  "@vitest/browser-playwright": "^4.1.11",