a11y-form-validator 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/LICENSE +21 -0
  3. package/README.md +390 -0
  4. package/dist/A11yFormValidator-D0n6br-b.d.ts +335 -0
  5. package/dist/A11yFormValidator-D0n6br-b.d.ts.map +1 -0
  6. package/dist/addons/character-count.d.ts +38 -0
  7. package/dist/addons/character-count.d.ts.map +1 -0
  8. package/dist/addons/character-count.js +161 -0
  9. package/dist/addons/character-count.js.map +1 -0
  10. package/dist/addons/error-summary.d.ts +29 -0
  11. package/dist/addons/error-summary.d.ts.map +1 -0
  12. package/dist/addons/error-summary.js +4 -0
  13. package/dist/default-BHBWPNeK.d.ts +7 -0
  14. package/dist/default-BHBWPNeK.d.ts.map +1 -0
  15. package/dist/default-DFnPBuVC.js +18 -0
  16. package/dist/default-DFnPBuVC.js.map +1 -0
  17. package/dist/docs.d.ts +66 -0
  18. package/dist/docs.d.ts.map +1 -0
  19. package/dist/docs.js +204 -0
  20. package/dist/docs.js.map +1 -0
  21. package/dist/error-summary-FnZoUC72.js +99 -0
  22. package/dist/error-summary-FnZoUC72.js.map +1 -0
  23. package/dist/helpers-c_THOe-Q.js +150 -0
  24. package/dist/helpers-c_THOe-Q.js.map +1 -0
  25. package/dist/index.d.ts +37 -0
  26. package/dist/index.d.ts.map +1 -0
  27. package/dist/index.js +1197 -0
  28. package/dist/index.js.map +1 -0
  29. package/dist/index.min.js +2 -0
  30. package/dist/index.min.js.map +1 -0
  31. package/dist/locales/ar.json +13 -0
  32. package/dist/locales/en.json +13 -0
  33. package/dist/locales/es.json +13 -0
  34. package/dist/locales/fr.json +13 -0
  35. package/dist/locales/hi.json +13 -0
  36. package/dist/locales/it.json +13 -0
  37. package/dist/locales/ja.json +13 -0
  38. package/dist/locales/ko.json +13 -0
  39. package/dist/locales/nl.json +13 -0
  40. package/dist/locales/pl.json +13 -0
  41. package/dist/locales/pt-BR.json +13 -0
  42. package/dist/locales/ru.json +13 -0
  43. package/dist/locales/tr.json +13 -0
  44. package/dist/locales/uk.json +13 -0
  45. package/dist/locales/zh-CN.json +13 -0
  46. package/dist/minimal-40_EIW9U.d.ts +7 -0
  47. package/dist/minimal-40_EIW9U.d.ts.map +1 -0
  48. package/dist/minimal-Bm3pKoN7.js +15 -0
  49. package/dist/minimal-Bm3pKoN7.js.map +1 -0
  50. package/dist/no-summary-BI1pTld5.d.ts +7 -0
  51. package/dist/no-summary-BI1pTld5.d.ts.map +1 -0
  52. package/dist/no-summary-DQCNtzs6.js +16 -0
  53. package/dist/no-summary-DQCNtzs6.js.map +1 -0
  54. package/dist/presets/default.d.ts +3 -0
  55. package/dist/presets/default.js +5 -0
  56. package/dist/presets/minimal.d.ts +3 -0
  57. package/dist/presets/minimal.js +3 -0
  58. package/dist/presets/no-summary.d.ts +3 -0
  59. package/dist/presets/no-summary.js +3 -0
  60. package/dist/styles.css +91 -0
  61. package/package.json +95 -0
package/dist/index.js ADDED
@@ -0,0 +1,1197 @@
1
+ import { a as getPreferredScrollBehavior, c as normalizeToArray, d as toRuleOptions, f as toSafeInteger, i as escapeSelectorIdentifier, l as parseRuleList, n as camelToKebabCase, o as isEmptyValue, p as unique, r as ensureElementId, s as mergeOptions, t as applyPlaceholders, u as sanitizeId } from "./helpers-c_THOe-Q.js";
2
+ import "./error-summary-FnZoUC72.js";
3
+ import { t as createDefaultPreset } from "./default-DFnPBuVC.js";
4
+ import { t as createNoSummaryPreset } from "./no-summary-DQCNtzs6.js";
5
+ import { t as createMinimalPreset } from "./minimal-Bm3pKoN7.js";
6
+
7
+ //#region src/rules/checked.ts
8
+ function checkedRule({ value }) {
9
+ return {
10
+ valid: Array.isArray(value) ? value.length > 0 : Boolean(value),
11
+ messageKey: "checked"
12
+ };
13
+ }
14
+ var checked_default = checkedRule;
15
+
16
+ //#endregion
17
+ //#region src/rules/email.ts
18
+ const EMAIL_PATTERN = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+$/;
19
+ function emailRule({ value }) {
20
+ if (isEmptyValue(value)) return {
21
+ valid: true,
22
+ messageKey: "email"
23
+ };
24
+ return {
25
+ valid: EMAIL_PATTERN.test(String(value).trim()),
26
+ messageKey: "email"
27
+ };
28
+ }
29
+ var email_default = emailRule;
30
+
31
+ //#endregion
32
+ //#region src/rules/maxLength.ts
33
+ function maxLengthRule({ value, options }) {
34
+ const max = toSafeInteger(options.max, Number.POSITIVE_INFINITY, { min: 0 });
35
+ if (isEmptyValue(value)) return {
36
+ valid: true,
37
+ messageKey: "maxLength",
38
+ params: { max }
39
+ };
40
+ return {
41
+ valid: (Array.isArray(value) ? value.length : String(value).length) <= max,
42
+ messageKey: "maxLength",
43
+ params: { max }
44
+ };
45
+ }
46
+ var maxLength_default = maxLengthRule;
47
+
48
+ //#endregion
49
+ //#region src/rules/minLength.ts
50
+ function minLengthRule({ value, options }) {
51
+ const min = toSafeInteger(options.min, 0, { min: 0 });
52
+ if (isEmptyValue(value)) return {
53
+ valid: true,
54
+ messageKey: "minLength",
55
+ params: { min }
56
+ };
57
+ return {
58
+ valid: (Array.isArray(value) ? value.length : String(value).length) >= min,
59
+ messageKey: "minLength",
60
+ params: { min }
61
+ };
62
+ }
63
+ var minLength_default = minLengthRule;
64
+
65
+ //#endregion
66
+ //#region src/rules/pattern.ts
67
+ function patternRule({ value, options }) {
68
+ if (isEmptyValue(value)) return {
69
+ valid: true,
70
+ messageKey: "pattern"
71
+ };
72
+ try {
73
+ return {
74
+ valid: new RegExp(String(options.pattern || "")).test(String(value)),
75
+ messageKey: "pattern"
76
+ };
77
+ } catch {
78
+ return {
79
+ valid: true,
80
+ messageKey: "pattern",
81
+ metadata: { ignoredInvalidPattern: true }
82
+ };
83
+ }
84
+ }
85
+ var pattern_default = patternRule;
86
+
87
+ //#endregion
88
+ //#region src/rules/required.ts
89
+ function requiredRule({ value }) {
90
+ return {
91
+ valid: !isEmptyValue(value),
92
+ messageKey: "required"
93
+ };
94
+ }
95
+ var required_default = requiredRule;
96
+
97
+ //#endregion
98
+ //#region src/rules/same-as.ts
99
+ function sameAsRule({ field, form, value, options }) {
100
+ if (isEmptyValue(value)) return {
101
+ valid: true,
102
+ messageKey: "sameAs",
103
+ params: { targetLabel: "" }
104
+ };
105
+ const selector = String(options.selector || `[name="${options.field || ""}"]`);
106
+ let target = null;
107
+ try {
108
+ target = selector ? form.querySelector(selector) : null;
109
+ } catch {
110
+ target = null;
111
+ }
112
+ const targetValue = target instanceof HTMLInputElement || target instanceof HTMLSelectElement || target instanceof HTMLTextAreaElement ? target.value : "";
113
+ return {
114
+ valid: target ? String(value) === String(targetValue) : false,
115
+ messageKey: "sameAs",
116
+ params: { targetLabel: field.getTargetLabel(target) }
117
+ };
118
+ }
119
+ var same_as_default = sameAsRule;
120
+
121
+ //#endregion
122
+ //#region src/core/ErrorRenderer.ts
123
+ var ErrorRenderer = class {
124
+ validator;
125
+ constructor(validator) {
126
+ this.validator = validator;
127
+ }
128
+ getNode(field) {
129
+ return this.validator.form.querySelector(`#${field.errorId}`);
130
+ }
131
+ shouldRenderInline() {
132
+ return this.validator.options.errorMode !== "native";
133
+ }
134
+ syncAnnouncementAttributes(errorNode) {
135
+ if (this.validator.shouldAnnounceInlineErrors()) {
136
+ errorNode.setAttribute("role", "status");
137
+ errorNode.setAttribute("aria-live", "polite");
138
+ errorNode.setAttribute("aria-atomic", "true");
139
+ return;
140
+ }
141
+ errorNode.removeAttribute("role");
142
+ errorNode.removeAttribute("aria-live");
143
+ errorNode.removeAttribute("aria-atomic");
144
+ }
145
+ setNativeMessage(field, message) {
146
+ field.elements.forEach((element) => {
147
+ element.setCustomValidity?.(message || "");
148
+ });
149
+ }
150
+ getInsertionTarget(field) {
151
+ if (field.isGroupedChoice) {
152
+ const fieldset = field.primaryElement.closest("fieldset");
153
+ if (fieldset && this.validator.form.contains(fieldset)) return fieldset;
154
+ }
155
+ const label = field.primaryElement.closest("label");
156
+ if (label && this.validator.form.contains(label)) return label;
157
+ return field.primaryElement;
158
+ }
159
+ render(field, message) {
160
+ this.setNativeMessage(field, message);
161
+ const renderInline = this.shouldRenderInline();
162
+ if (renderInline) field.connectDescription(field.errorId);
163
+ else field.disconnectDescription(field.errorId);
164
+ field.setAriaInvalid(true, renderInline ? field.errorId : "");
165
+ field.setVisualState("invalid");
166
+ if (!renderInline) return;
167
+ let errorNode = this.getNode(field);
168
+ if (!errorNode) {
169
+ errorNode = this.validator.form.ownerDocument.createElement("div");
170
+ errorNode.id = field.errorId;
171
+ errorNode.className = "a11y-form-validator__error";
172
+ this.getInsertionTarget(field).insertAdjacentElement("afterend", errorNode);
173
+ }
174
+ this.syncAnnouncementAttributes(errorNode);
175
+ errorNode.textContent = message;
176
+ }
177
+ clear(field) {
178
+ const errorNode = this.getNode(field);
179
+ if (errorNode) errorNode.remove();
180
+ this.setNativeMessage(field, "");
181
+ field.disconnectDescription(field.errorId);
182
+ field.setAriaInvalid(false);
183
+ field.setVisualState("valid");
184
+ }
185
+ destroy() {
186
+ this.validator.fields.forEach((field) => this.clear(field));
187
+ }
188
+ };
189
+
190
+ //#endregion
191
+ //#region src/core/EventEmitter.ts
192
+ var EventEmitter = class {
193
+ target;
194
+ constructor(target) {
195
+ this.target = target;
196
+ }
197
+ emit(name, detail = {}) {
198
+ this.target.dispatchEvent(new CustomEvent(name, {
199
+ bubbles: true,
200
+ detail
201
+ }));
202
+ }
203
+ on(name, handler) {
204
+ this.target.addEventListener(name, handler);
205
+ return () => this.off(name, handler);
206
+ }
207
+ off(name, handler) {
208
+ this.target.removeEventListener(name, handler);
209
+ }
210
+ };
211
+
212
+ //#endregion
213
+ //#region src/core/FieldController.ts
214
+ function getControlLabels(element) {
215
+ if ("labels" in element && element.labels) return element.labels;
216
+ return [];
217
+ }
218
+ var FieldController = class {
219
+ validator;
220
+ name;
221
+ elements;
222
+ primaryElement;
223
+ type;
224
+ isGroupedChoice;
225
+ errorId;
226
+ primaryId;
227
+ initialDescribedBy;
228
+ generatedPrimaryId;
229
+ serverMessage;
230
+ lastError;
231
+ touched;
232
+ dirty;
233
+ constructor(validator, name, elements) {
234
+ this.validator = validator;
235
+ this.name = name;
236
+ this.elements = [...elements];
237
+ this.primaryElement = this.elements[0];
238
+ this.type = this.detectType();
239
+ this.isGroupedChoice = this.elements.length > 1 && ["radio", "checkbox"].includes(this.type);
240
+ this.errorId = `a11y-form-validator-error-${sanitizeId(this.validator.form.id || "form")}-${sanitizeId(this.name)}`;
241
+ this.generatedPrimaryId = !this.primaryElement.id;
242
+ this.primaryId = ensureElementId(this.primaryElement);
243
+ this.initialDescribedBy = new Map(this.elements.map((element) => [element, element.getAttribute("aria-describedby") || ""]));
244
+ this.serverMessage = "";
245
+ this.lastError = "";
246
+ this.touched = false;
247
+ this.dirty = false;
248
+ }
249
+ detectType() {
250
+ const tagName = this.primaryElement.tagName.toLowerCase();
251
+ if (tagName === "textarea") return "textarea";
252
+ if (tagName === "select") return this.primaryElement.multiple ? "select-multiple" : "select";
253
+ return (this.primaryElement.getAttribute("type") || "text").toLowerCase();
254
+ }
255
+ getActiveElements() {
256
+ return this.elements.filter((element) => !element.disabled);
257
+ }
258
+ isDisabled() {
259
+ return this.getActiveElements().length === 0;
260
+ }
261
+ isHidden() {
262
+ return this.elements.every((element) => {
263
+ if (element instanceof HTMLInputElement && element.type === "hidden" || element.hidden) return true;
264
+ return Boolean(element.closest("[hidden]"));
265
+ });
266
+ }
267
+ shouldIgnore() {
268
+ const { ignore, validateHidden } = this.validator.options;
269
+ if (ignore.disabled !== false && this.isDisabled()) return "disabled";
270
+ if (!validateHidden && ignore.hidden !== false && this.isHidden()) return "hidden";
271
+ if (ignore.selector && this.elements.some((element) => element.matches(ignore.selector))) return "selector";
272
+ return false;
273
+ }
274
+ getValue() {
275
+ const activeElements = this.getActiveElements();
276
+ if (this.type === "radio") {
277
+ const selected = activeElements.find((element) => element instanceof HTMLInputElement && element.checked);
278
+ return selected ? selected.value : "";
279
+ }
280
+ if (this.type === "checkbox" && this.elements.length > 1) return activeElements.filter((element) => element instanceof HTMLInputElement && element.checked).map((element) => element.value);
281
+ if (this.type === "checkbox") return this.primaryElement instanceof HTMLInputElement ? Boolean(this.primaryElement.checked) : false;
282
+ if (this.type === "file") return this.primaryElement instanceof HTMLInputElement ? Array.from(this.primaryElement.files || []) : [];
283
+ if (this.type === "select-multiple" && this.primaryElement instanceof HTMLSelectElement) return Array.from(this.primaryElement.selectedOptions).map((option) => option.value);
284
+ return this.primaryElement.value;
285
+ }
286
+ getFieldConfig() {
287
+ return this.validator.options.rules?.[this.name] || {};
288
+ }
289
+ getDataMessage(ruleName) {
290
+ return this.primaryElement.dataset[`message${ruleName.charAt(0).toUpperCase()}${ruleName.slice(1)}`] || this.primaryElement.getAttribute(`data-message-${camelToKebabCase(ruleName)}`) || "";
291
+ }
292
+ getLabel() {
293
+ const fieldset = this.primaryElement.closest("fieldset");
294
+ if (this.isGroupedChoice && fieldset) {
295
+ const legend = fieldset.querySelector("legend");
296
+ if (legend) return legend.textContent?.trim() || this.name;
297
+ }
298
+ const id = this.primaryElement.id;
299
+ if (id) {
300
+ const labelSelector = `label[for="${escapeSelectorIdentifier(id)}"]`;
301
+ const label = this.validator.form.querySelector(labelSelector) || this.validator.form.ownerDocument.querySelector(labelSelector);
302
+ if (label) return label.textContent?.trim() || this.name;
303
+ }
304
+ const wrapperLabel = this.primaryElement.closest("label");
305
+ if (wrapperLabel) return wrapperLabel.textContent?.trim() || this.name;
306
+ return this.name;
307
+ }
308
+ getTargetLabel(target) {
309
+ if (!target) return "";
310
+ const labels = getControlLabels(target);
311
+ if (labels.length) return labels[0].textContent?.trim() || "";
312
+ if (target.id) {
313
+ const label = this.validator.form.querySelector(`label[for="${escapeSelectorIdentifier(target.id)}"]`);
314
+ if (label) return label.textContent?.trim() || "";
315
+ }
316
+ if (target instanceof HTMLInputElement || target instanceof HTMLSelectElement || target instanceof HTMLTextAreaElement) return target.name || target.id || "";
317
+ return target.id || "";
318
+ }
319
+ getDescribedBy() {
320
+ return unique(this.elements.flatMap((element) => String(element.getAttribute("aria-describedby") || "").split(/\s+/)));
321
+ }
322
+ connectDescription(id) {
323
+ this.elements.forEach((element) => {
324
+ const base = String(this.initialDescribedBy.get(element) || "").split(/\s+/);
325
+ const current = String(element.getAttribute("aria-describedby") || "").split(/\s+/);
326
+ const next = unique([
327
+ ...base,
328
+ ...current,
329
+ id
330
+ ]);
331
+ if (next.length) element.setAttribute("aria-describedby", next.join(" "));
332
+ });
333
+ }
334
+ disconnectDescription(id) {
335
+ this.elements.forEach((element) => {
336
+ const base = String(this.initialDescribedBy.get(element) || "").split(/\s+/);
337
+ const current = String(element.getAttribute("aria-describedby") || "").split(/\s+/);
338
+ const next = unique([...base, ...current].filter((token) => token && token !== id));
339
+ if (next.length) element.setAttribute("aria-describedby", next.join(" "));
340
+ else element.removeAttribute("aria-describedby");
341
+ });
342
+ }
343
+ setAriaInvalid(isInvalid, errorId = this.errorId) {
344
+ this.elements.forEach((element) => {
345
+ if (isInvalid) {
346
+ element.setAttribute("aria-invalid", "true");
347
+ if (errorId) element.setAttribute("aria-errormessage", errorId);
348
+ else element.removeAttribute("aria-errormessage");
349
+ } else {
350
+ element.removeAttribute("aria-invalid");
351
+ element.removeAttribute("aria-errormessage");
352
+ }
353
+ });
354
+ }
355
+ setVisualState(state) {
356
+ this.elements.forEach((element) => {
357
+ element.dataset.validationState = state;
358
+ element.classList.toggle("is-valid", state === "valid");
359
+ element.classList.toggle("is-invalid", state === "invalid");
360
+ element.classList.toggle("is-pending", state === "pending");
361
+ element.classList.toggle("is-touched", this.touched);
362
+ element.classList.toggle("is-dirty", this.dirty);
363
+ element.classList.toggle("is-disabled", this.isDisabled());
364
+ });
365
+ }
366
+ clearVisualState() {
367
+ this.elements.forEach((element) => {
368
+ delete element.dataset.validationState;
369
+ element.classList.remove("is-valid", "is-invalid", "is-pending", "is-touched", "is-dirty", "is-disabled");
370
+ });
371
+ }
372
+ focus() {
373
+ this.primaryElement.focus({ preventScroll: true });
374
+ this.primaryElement.scrollIntoView?.({
375
+ block: "center",
376
+ behavior: getPreferredScrollBehavior(this.primaryElement)
377
+ });
378
+ }
379
+ markTouched() {
380
+ this.touched = true;
381
+ }
382
+ markDirty() {
383
+ this.dirty = true;
384
+ }
385
+ clearServerMessage() {
386
+ this.serverMessage = "";
387
+ }
388
+ setServerMessage(message) {
389
+ this.serverMessage = message;
390
+ this.lastError = message;
391
+ }
392
+ getRules() {
393
+ const nativeRules = this.getNativeRules();
394
+ const dataRules = this.getDataRules();
395
+ const configuredRules = parseRuleList(this.getFieldConfig());
396
+ return {
397
+ ...nativeRules,
398
+ ...dataRules,
399
+ ...configuredRules
400
+ };
401
+ }
402
+ getNativeRules() {
403
+ if (!this.validator.options.useNativeRules) return {};
404
+ const rules = {};
405
+ const requiredRuleName = ["checkbox", "radio"].includes(this.type) ? "checked" : "required";
406
+ if (this.elements.some((element) => element.hasAttribute("required"))) rules[requiredRuleName] = true;
407
+ if (this.type === "email") rules.email = true;
408
+ const minLength$1 = toSafeInteger(this.primaryElement.getAttribute("minlength") ?? void 0, NaN, { min: 0 });
409
+ if (Number.isFinite(minLength$1)) rules.minLength = { min: minLength$1 };
410
+ const maxLength$1 = toSafeInteger(this.primaryElement.getAttribute("maxlength") ?? void 0, NaN, { min: 0 });
411
+ if (Number.isFinite(maxLength$1)) rules.maxLength = { max: maxLength$1 };
412
+ const pattern$1 = this.primaryElement.getAttribute("pattern");
413
+ if (pattern$1) rules.pattern = { pattern: pattern$1 };
414
+ return rules;
415
+ }
416
+ getDataRules() {
417
+ const rules = parseRuleList(this.primaryElement.dataset.validate);
418
+ if (this.primaryElement.dataset.required === "true") rules.required = true;
419
+ if (this.primaryElement.dataset.checked === "true") rules.checked = true;
420
+ const dataMinLength = toSafeInteger(this.primaryElement.dataset.minLength, NaN, { min: 0 });
421
+ if (Number.isFinite(dataMinLength)) rules.minLength = { min: dataMinLength };
422
+ const dataMaxLength = toSafeInteger(this.primaryElement.dataset.maxLength, NaN, { min: 0 });
423
+ if (Number.isFinite(dataMaxLength)) rules.maxLength = { max: dataMaxLength };
424
+ if (this.primaryElement.dataset.pattern) rules.pattern = { pattern: this.primaryElement.dataset.pattern };
425
+ if (this.primaryElement.dataset.sameAs) rules.sameAs = { selector: this.primaryElement.dataset.sameAs };
426
+ return Object.fromEntries(Object.entries(rules).map(([key, value]) => [key, toRuleOptions(value, key)]));
427
+ }
428
+ getValidationContext(ruleName, ruleOptions, allValues) {
429
+ return {
430
+ field: this,
431
+ form: this.validator.form,
432
+ value: this.getValue(),
433
+ options: toRuleOptions(ruleOptions, ruleName),
434
+ allValues,
435
+ validator: this.validator
436
+ };
437
+ }
438
+ getNativeValidationMessage() {
439
+ if (typeof this.primaryElement.validationMessage === "string") return this.primaryElement.validationMessage;
440
+ return "";
441
+ }
442
+ destroy() {
443
+ this.clearServerMessage();
444
+ this.lastError = "";
445
+ this.setAriaInvalid(false);
446
+ this.clearVisualState();
447
+ this.elements.forEach((element) => {
448
+ const initial = this.initialDescribedBy.get(element);
449
+ if (initial) element.setAttribute("aria-describedby", initial);
450
+ else element.removeAttribute("aria-describedby");
451
+ });
452
+ if (this.generatedPrimaryId && this.primaryElement.id === this.primaryId) this.primaryElement.removeAttribute("id");
453
+ }
454
+ };
455
+
456
+ //#endregion
457
+ //#region src/locales/en.json
458
+ var en_default = {
459
+ required: "This field is required.",
460
+ email: "Enter a valid email address.",
461
+ minLength: "Enter at least {min} characters.",
462
+ maxLength: "Enter no more than {max} characters.",
463
+ pattern: "Enter a value in the correct format.",
464
+ checked: "Select this option to continue.",
465
+ sameAs: "This value must match {targetLabel}.",
466
+ summaryTitleOne: "There is 1 problem with your form.",
467
+ summaryTitleOther: "There are {count} problems with your form.",
468
+ summaryItem: "{fieldLabel}: {message}",
469
+ genericFallback: "Check this field."
470
+ };
471
+
472
+ //#endregion
473
+ //#region src/core/MessageResolver.ts
474
+ var MessageResolver = class {
475
+ validator;
476
+ defaultMessages;
477
+ constructor(validator) {
478
+ this.validator = validator;
479
+ this.defaultMessages = en_default;
480
+ }
481
+ detectLocale() {
482
+ const explicit = this.validator.options.locale;
483
+ const formLang = this.validator.form.getAttribute("lang");
484
+ const documentLang = this.validator.form.ownerDocument.documentElement.getAttribute("lang");
485
+ return explicit || formLang || documentLang || "en";
486
+ }
487
+ getLocaleCandidates(locale = this.detectLocale()) {
488
+ const candidates = [];
489
+ if (locale) {
490
+ candidates.push(locale);
491
+ const [base] = locale.split("-");
492
+ if (base && base !== locale) candidates.push(base);
493
+ }
494
+ candidates.push("en");
495
+ return [...new Set(candidates.filter(Boolean))];
496
+ }
497
+ getLocaleMessage(key) {
498
+ const candidates = this.getLocaleCandidates();
499
+ const configuredLocales = this.validator.options.locales || {};
500
+ for (const locale of candidates) {
501
+ const localeMessages = configuredLocales[locale];
502
+ if (localeMessages && localeMessages[key] != null) return localeMessages[key];
503
+ }
504
+ if (this.validator.options.messages?.[key] != null) return this.validator.options.messages[key];
505
+ return this.defaultMessages[key];
506
+ }
507
+ resolveValue(message, context) {
508
+ if (typeof message === "function") return message(context);
509
+ if (typeof message === "string") return applyPlaceholders(message, context.params);
510
+ return "";
511
+ }
512
+ getSummaryTitle(count) {
513
+ const key = count === 1 ? "summaryTitleOne" : "summaryTitleOther";
514
+ const message = this.getLocaleMessage(key) || this.getLocaleMessage("summaryTitle");
515
+ return this.resolveValue(message, {
516
+ field: null,
517
+ fieldName: "",
518
+ fieldLabel: "",
519
+ form: this.validator.form,
520
+ rule: key,
521
+ value: count,
522
+ params: { count },
523
+ locale: this.detectLocale()
524
+ }) || `There ${count === 1 ? "is" : "are"} ${count} problem${count === 1 ? "" : "s"} with your form.`;
525
+ }
526
+ getSummaryItem(field, message) {
527
+ const fieldLabel = field.getLabel();
528
+ const params = {
529
+ label: fieldLabel,
530
+ fieldLabel,
531
+ fieldName: field.name,
532
+ message
533
+ };
534
+ const summaryMessage = this.validator.options.messages?.summaryItem ?? this.getLocaleMessage("summaryItem");
535
+ const context = {
536
+ field,
537
+ fieldName: field.name,
538
+ fieldLabel,
539
+ form: this.validator.form,
540
+ rule: "summaryItem",
541
+ value: message,
542
+ params,
543
+ locale: this.detectLocale()
544
+ };
545
+ return this.resolveValue(summaryMessage, context) || `${fieldLabel}: ${message}`;
546
+ }
547
+ resolve(field, ruleName, result) {
548
+ const target = field.primaryElement;
549
+ const fieldLabel = field.getLabel();
550
+ const params = {
551
+ ...result.params,
552
+ label: fieldLabel,
553
+ fieldLabel,
554
+ targetLabel: result.params?.targetLabel || "",
555
+ value: field.getValue()
556
+ };
557
+ const context = {
558
+ field,
559
+ fieldName: field.name,
560
+ fieldLabel,
561
+ form: this.validator.form,
562
+ rule: ruleName,
563
+ value: field.getValue(),
564
+ params,
565
+ locale: this.detectLocale()
566
+ };
567
+ const fieldMessages = this.validator.options.messages?.fields;
568
+ const shouldUseNativeMessage = this.validator.options.errorMode === "native" || Boolean(result.nativeMessage);
569
+ const candidates = [
570
+ field.serverMessage,
571
+ field.getDataMessage(ruleName),
572
+ fieldMessages?.[field.name]?.[ruleName],
573
+ this.validator.options.messages?.[ruleName],
574
+ result.message,
575
+ shouldUseNativeMessage ? result.nativeMessage : "",
576
+ shouldUseNativeMessage && target.validity?.valid === false ? field.getNativeValidationMessage() : "",
577
+ this.getLocaleMessage(result.messageKey || ruleName),
578
+ this.defaultMessages[result.messageKey || ruleName],
579
+ this.defaultMessages.genericFallback,
580
+ "Check this field."
581
+ ];
582
+ for (const candidate of candidates) {
583
+ const value = this.resolveValue(candidate, context);
584
+ if (value) return value;
585
+ }
586
+ return "Check this field.";
587
+ }
588
+ };
589
+
590
+ //#endregion
591
+ //#region src/core/RuleRegistry.ts
592
+ var RuleRegistry = class {
593
+ rules;
594
+ constructor() {
595
+ this.rules = /* @__PURE__ */ new Map();
596
+ }
597
+ register(name, rule) {
598
+ this.rules.set(name, rule);
599
+ return this;
600
+ }
601
+ unregister(name) {
602
+ this.rules.delete(name);
603
+ return this;
604
+ }
605
+ has(name) {
606
+ return this.rules.has(name);
607
+ }
608
+ async run(name, context) {
609
+ const rule = this.rules.get(name);
610
+ if (!rule) return {
611
+ valid: true,
612
+ messageKey: name,
613
+ params: {}
614
+ };
615
+ const rawResult = await rule(context);
616
+ return this.normalize(name, rawResult);
617
+ }
618
+ normalize(name, result) {
619
+ if (result == null || result === true) return {
620
+ valid: true,
621
+ messageKey: name,
622
+ params: {}
623
+ };
624
+ if (result === false) return {
625
+ valid: false,
626
+ messageKey: name,
627
+ params: {}
628
+ };
629
+ if (typeof result === "string") return {
630
+ valid: false,
631
+ messageKey: name,
632
+ message: result,
633
+ params: {}
634
+ };
635
+ return {
636
+ valid: Boolean(result.valid),
637
+ messageKey: result.messageKey || name,
638
+ message: result.message,
639
+ params: result.params || {},
640
+ metadata: result.metadata || {},
641
+ nativeMessage: result.nativeMessage
642
+ };
643
+ }
644
+ };
645
+
646
+ //#endregion
647
+ //#region src/core/ValidationState.ts
648
+ var ValidationState = class {
649
+ formState;
650
+ fieldStates;
651
+ constructor() {
652
+ this.formState = "idle";
653
+ this.fieldStates = /* @__PURE__ */ new Map();
654
+ }
655
+ ensure(name) {
656
+ if (!this.fieldStates.has(name)) this.fieldStates.set(name, {
657
+ pristine: true,
658
+ dirty: false,
659
+ touched: false,
660
+ pending: false,
661
+ valid: false,
662
+ invalid: false,
663
+ disabled: false,
664
+ ignored: false
665
+ });
666
+ return this.fieldStates.get(name);
667
+ }
668
+ updateField(name, patch) {
669
+ const current = this.ensure(name);
670
+ this.fieldStates.set(name, {
671
+ ...current,
672
+ ...patch
673
+ });
674
+ return this.fieldStates.get(name);
675
+ }
676
+ setFormState(state) {
677
+ this.formState = state;
678
+ }
679
+ snapshot() {
680
+ return {
681
+ form: this.formState,
682
+ fields: Object.fromEntries(this.fieldStates.entries())
683
+ };
684
+ }
685
+ };
686
+
687
+ //#endregion
688
+ //#region src/core/A11yFormValidator.ts
689
+ const COMPONENT_NAME = "a11y-form-validator";
690
+ const EVENTS = Object.freeze({
691
+ init: `${COMPONENT_NAME}:init`,
692
+ beforeValidate: `${COMPONENT_NAME}:before-validate`,
693
+ afterValidate: `${COMPONENT_NAME}:after-validate`,
694
+ fieldValid: `${COMPONENT_NAME}:field-valid`,
695
+ fieldInvalid: `${COMPONENT_NAME}:field-invalid`,
696
+ formValid: `${COMPONENT_NAME}:form-valid`,
697
+ formInvalid: `${COMPONENT_NAME}:form-invalid`,
698
+ submitBlocked: `${COMPONENT_NAME}:submit-blocked`,
699
+ destroy: `${COMPONENT_NAME}:destroy`
700
+ });
701
+ const SELECTORS = Object.freeze({
702
+ fields: "input, select, textarea",
703
+ initAll: "[data-a11y-form-validator]"
704
+ });
705
+ const CLASSES = Object.freeze({
706
+ root: COMPONENT_NAME,
707
+ initialized: "is-initialized"
708
+ });
709
+ const ATTRIBUTES = Object.freeze({
710
+ describedBy: "aria-describedby",
711
+ errorMessage: "aria-errormessage",
712
+ hidden: "hidden",
713
+ invalid: "aria-invalid",
714
+ noValidate: "novalidate",
715
+ validationState: "data-validation-state"
716
+ });
717
+ const DEFAULT_OPTIONS = Object.freeze({
718
+ validateOn: ["submit"],
719
+ focusOnError: "summary",
720
+ errorMode: "inline",
721
+ useNativeRules: true,
722
+ disableNativeUI: true,
723
+ validateHidden: false,
724
+ ignore: Object.freeze({
725
+ disabled: true,
726
+ hidden: true,
727
+ selector: ""
728
+ }),
729
+ debounce: 150,
730
+ messages: Object.freeze({}),
731
+ locales: Object.freeze({}),
732
+ locale: "",
733
+ selectors: Object.freeze({ fields: SELECTORS.fields }),
734
+ addons: [],
735
+ renderer: null,
736
+ rules: Object.freeze({})
737
+ });
738
+ var A11yFormValidator = class A11yFormValidator {
739
+ static instances = /* @__PURE__ */ new WeakMap();
740
+ form;
741
+ options;
742
+ events;
743
+ state;
744
+ ruleRegistry;
745
+ messageResolver;
746
+ renderer;
747
+ fields;
748
+ fieldMap;
749
+ enabled;
750
+ hasSubmitted;
751
+ formErrors;
752
+ summaryAddon;
753
+ addons;
754
+ timers;
755
+ addedRootClass;
756
+ addedNoValidate;
757
+ abortController;
758
+ validationRuns;
759
+ destroyed;
760
+ inlineErrorAnnouncementsMuted;
761
+ onSubmit;
762
+ onFocusOut;
763
+ onInput;
764
+ onChange;
765
+ constructor(form, options = {}) {
766
+ if (!(form instanceof HTMLFormElement)) throw new TypeError("A11yFormValidator expects an HTMLFormElement.");
767
+ const existingInstance = A11yFormValidator.instances.get(form);
768
+ if (existingInstance) return existingInstance;
769
+ A11yFormValidator.instances.set(form, this);
770
+ this.form = form;
771
+ this.options = this.normalizeOptions(options);
772
+ this.events = new EventEmitter(this.form);
773
+ this.state = new ValidationState();
774
+ this.ruleRegistry = new RuleRegistry();
775
+ this.messageResolver = new MessageResolver(this);
776
+ this.renderer = this.options.renderer || new ErrorRenderer(this);
777
+ this.fields = [];
778
+ this.fieldMap = /* @__PURE__ */ new Map();
779
+ this.enabled = true;
780
+ this.hasSubmitted = false;
781
+ this.formErrors = [];
782
+ this.summaryAddon = null;
783
+ this.addons = [];
784
+ this.timers = /* @__PURE__ */ new Map();
785
+ this.addedRootClass = !this.form.classList.contains(CLASSES.root);
786
+ this.addedNoValidate = this.options.disableNativeUI && this.options.errorMode !== "native" && !this.form.hasAttribute("novalidate");
787
+ const AbortControllerConstructor = this.form.ownerDocument.defaultView?.AbortController || globalThis.AbortController;
788
+ this.abortController = AbortControllerConstructor ? new AbortControllerConstructor() : null;
789
+ this.validationRuns = /* @__PURE__ */ new Map();
790
+ this.destroyed = false;
791
+ this.inlineErrorAnnouncementsMuted = false;
792
+ this.onSubmit = this.handleSubmit.bind(this);
793
+ this.onFocusOut = this.handleFocusOut.bind(this);
794
+ this.onInput = this.handleInput.bind(this);
795
+ this.onChange = this.handleChange.bind(this);
796
+ this.registerDefaultRules();
797
+ this.form.classList.add(CLASSES.root, CLASSES.initialized);
798
+ if (this.addedNoValidate) this.form.setAttribute("novalidate", "novalidate");
799
+ this.refresh();
800
+ this.bindEvents();
801
+ this.installAddons();
802
+ this.emit(EVENTS.init);
803
+ }
804
+ static getInstance(form) {
805
+ return A11yFormValidator.instances.get(form);
806
+ }
807
+ normalizeOptions(options) {
808
+ const merged = mergeOptions(DEFAULT_OPTIONS, options);
809
+ return {
810
+ ...merged,
811
+ validateOn: normalizeToArray(merged.validateOn),
812
+ addons: normalizeToArray(merged.addons),
813
+ debounce: toSafeInteger(merged.debounce, DEFAULT_OPTIONS.debounce, { min: 0 }),
814
+ ignore: {
815
+ ...DEFAULT_OPTIONS.ignore,
816
+ ...options.ignore || {}
817
+ },
818
+ selectors: {
819
+ ...DEFAULT_OPTIONS.selectors,
820
+ ...options.selectors || {}
821
+ },
822
+ messages: options.messages || DEFAULT_OPTIONS.messages,
823
+ locales: options.locales || DEFAULT_OPTIONS.locales,
824
+ rules: options.rules || DEFAULT_OPTIONS.rules
825
+ };
826
+ }
827
+ emit(name, detail = {}) {
828
+ this.events.emit(name, {
829
+ instance: this,
830
+ validator: this,
831
+ ...detail
832
+ });
833
+ }
834
+ shouldAnnounceInlineErrors() {
835
+ return !this.inlineErrorAnnouncementsMuted;
836
+ }
837
+ shouldMuteInlineErrorsForBatch(reason) {
838
+ return reason === "submit" && this.options.focusOnError === "summary" && Boolean(this.summaryAddon);
839
+ }
840
+ registerDefaultRules() {
841
+ this.registerRule("required", required_default);
842
+ this.registerRule("email", email_default);
843
+ this.registerRule("minLength", minLength_default);
844
+ this.registerRule("maxLength", maxLength_default);
845
+ this.registerRule("pattern", pattern_default);
846
+ this.registerRule("checked", checked_default);
847
+ this.registerRule("sameAs", same_as_default);
848
+ }
849
+ registerRule(name, rule) {
850
+ this.ruleRegistry.register(name, rule);
851
+ return this;
852
+ }
853
+ unregisterRule(name) {
854
+ this.ruleRegistry.unregister(name);
855
+ return this;
856
+ }
857
+ installAddons() {
858
+ this.addons = normalizeToArray(this.options.addons).filter((addon) => {
859
+ return Boolean(addon) && typeof addon === "object" && typeof addon.install === "function";
860
+ });
861
+ this.addons.forEach((addon) => addon.install(this));
862
+ }
863
+ collectFields() {
864
+ const nodes = Array.from(this.form.querySelectorAll(this.options.selectors.fields)).filter((element) => {
865
+ return element instanceof HTMLInputElement || element instanceof HTMLSelectElement || element instanceof HTMLTextAreaElement;
866
+ }).filter((element) => element.name || element.id);
867
+ const nameCounts = nodes.reduce((counts, element) => {
868
+ const name = element.name || element.id;
869
+ counts.set(name, (counts.get(name) || 0) + 1);
870
+ return counts;
871
+ }, /* @__PURE__ */ new Map());
872
+ const grouped = /* @__PURE__ */ new Map();
873
+ nodes.forEach((element) => {
874
+ const name = element.name || element.id;
875
+ const type = (element.type || element.tagName).toLowerCase();
876
+ const shouldGroup = type === "radio" || type === "checkbox" && (nameCounts.get(name) || 0) > 1;
877
+ if (!grouped.has(name)) grouped.set(name, []);
878
+ const group = grouped.get(name);
879
+ if (shouldGroup) {
880
+ group.push(element);
881
+ return;
882
+ }
883
+ if (group.length === 0) group.push(element);
884
+ });
885
+ return [...grouped.entries()].map(([name, elements]) => new FieldController(this, name, elements));
886
+ }
887
+ refresh() {
888
+ this.fields = this.collectFields();
889
+ this.fieldMap = new Map(this.fields.map((field) => [field.name, field]));
890
+ this.fields.forEach((field) => this.state.ensure(field.name));
891
+ return this;
892
+ }
893
+ bindEvents() {
894
+ const listenerOptions = this.abortController ? { signal: this.abortController.signal } : void 0;
895
+ this.form.addEventListener("submit", this.onSubmit, listenerOptions);
896
+ this.form.addEventListener("focusout", this.onFocusOut, listenerOptions);
897
+ this.form.addEventListener("input", this.onInput, listenerOptions);
898
+ this.form.addEventListener("change", this.onChange, listenerOptions);
899
+ }
900
+ async handleSubmit(event) {
901
+ if (!this.enabled || !this.options.validateOn.includes("submit")) return;
902
+ this.hasSubmitted = true;
903
+ if (!await this.validate({ reason: "submit" })) {
904
+ event.preventDefault();
905
+ this.emit(EVENTS.submitBlocked, { errors: this.getErrors() });
906
+ this.focusOnError();
907
+ }
908
+ }
909
+ handleFocusOut(event) {
910
+ const field = this.findFieldByElement(event.target);
911
+ if (!field) return;
912
+ field.markTouched();
913
+ if (this.options.validateOn.includes("blur") || this.hasSubmitted) this.validateField(field, { reason: "blur" });
914
+ }
915
+ handleInput(event) {
916
+ const field = this.findFieldByElement(event.target);
917
+ if (!field) return;
918
+ field.markDirty();
919
+ field.clearServerMessage();
920
+ this.formErrors = [];
921
+ if (this.options.validateOn.includes("input")) this.queueValidation(field, "input");
922
+ else if (field.lastError && !field.serverMessage) {
923
+ this.renderer.clear(field);
924
+ field.lastError = "";
925
+ }
926
+ }
927
+ handleChange(event) {
928
+ const field = this.findFieldByElement(event.target);
929
+ if (!field) return;
930
+ field.markDirty();
931
+ field.clearServerMessage();
932
+ if (this.options.validateOn.includes("change")) this.validateField(field, { reason: "change" });
933
+ }
934
+ queueValidation(field, reason) {
935
+ clearTimeout(this.timers.get(field.name));
936
+ const timer = setTimeout(() => {
937
+ this.validateField(field, { reason });
938
+ this.timers.delete(field.name);
939
+ }, this.options.debounce);
940
+ this.timers.set(field.name, timer);
941
+ }
942
+ findFieldByElement(element) {
943
+ if (!(element instanceof HTMLElement)) return;
944
+ return this.fields.find((field) => field.elements.some((fieldElement) => fieldElement === element));
945
+ }
946
+ resolveField(input) {
947
+ if (input instanceof FieldController) return input;
948
+ if (typeof input === "string") return this.fieldMap.get(input) || null;
949
+ if (input instanceof HTMLElement) return this.findFieldByElement(input) || null;
950
+ return null;
951
+ }
952
+ getAllValues() {
953
+ return Object.fromEntries(this.fields.map((field) => [field.name, field.getValue()]));
954
+ }
955
+ async validate(options = {}) {
956
+ const reason = options.reason || "manual";
957
+ const previousInlineErrorAnnouncementsMuted = this.inlineErrorAnnouncementsMuted;
958
+ this.inlineErrorAnnouncementsMuted = previousInlineErrorAnnouncementsMuted || this.shouldMuteInlineErrorsForBatch(reason);
959
+ try {
960
+ this.emit(EVENTS.beforeValidate, { reason });
961
+ this.state.setFormState("validating");
962
+ const valid = (await Promise.all(this.fields.map((field) => this.validateField(field, options)))).every(Boolean) && this.formErrors.length === 0;
963
+ this.state.setFormState(valid ? "valid" : "invalid");
964
+ this.emit(EVENTS.afterValidate, {
965
+ valid,
966
+ errors: this.getErrors()
967
+ });
968
+ this.emit(valid ? EVENTS.formValid : EVENTS.formInvalid, { errors: this.getErrors() });
969
+ return valid;
970
+ } finally {
971
+ this.inlineErrorAnnouncementsMuted = previousInlineErrorAnnouncementsMuted;
972
+ }
973
+ }
974
+ async validateField(input, options = {}) {
975
+ const field = this.resolveField(input);
976
+ if (!field) return true;
977
+ const ignoredReason = field.shouldIgnore();
978
+ if (ignoredReason) {
979
+ field.lastError = "";
980
+ this.renderer.clear(field);
981
+ this.state.updateField(field.name, {
982
+ ignored: ignoredReason !== "disabled",
983
+ disabled: ignoredReason === "disabled",
984
+ valid: true,
985
+ invalid: false,
986
+ pending: false,
987
+ touched: field.touched,
988
+ dirty: field.dirty,
989
+ pristine: !field.dirty
990
+ });
991
+ return true;
992
+ }
993
+ if (field.serverMessage) {
994
+ this.renderer.render(field, field.serverMessage);
995
+ this.state.updateField(field.name, {
996
+ valid: false,
997
+ invalid: true,
998
+ pending: false,
999
+ touched: field.touched,
1000
+ dirty: field.dirty,
1001
+ pristine: !field.dirty
1002
+ });
1003
+ this.emit(EVENTS.fieldInvalid, {
1004
+ field,
1005
+ message: field.serverMessage,
1006
+ reason: options.reason || "manual"
1007
+ });
1008
+ return false;
1009
+ }
1010
+ const runId = (this.validationRuns.get(field.name) || 0) + 1;
1011
+ this.validationRuns.set(field.name, runId);
1012
+ field.setVisualState("pending");
1013
+ this.state.updateField(field.name, {
1014
+ pending: true,
1015
+ touched: field.touched,
1016
+ dirty: field.dirty,
1017
+ pristine: !field.dirty
1018
+ });
1019
+ const rules = field.getRules();
1020
+ const allValues = this.getAllValues();
1021
+ for (const [ruleName, ruleOptions] of Object.entries(rules)) {
1022
+ const result = await this.ruleRegistry.run(ruleName, field.getValidationContext(ruleName, ruleOptions, allValues));
1023
+ if (this.validationRuns.get(field.name) !== runId) return !field.lastError;
1024
+ if (!result.valid) {
1025
+ const message = this.messageResolver.resolve(field, ruleName, result);
1026
+ field.lastError = message;
1027
+ this.renderer.render(field, message);
1028
+ this.state.updateField(field.name, {
1029
+ valid: false,
1030
+ invalid: true,
1031
+ pending: false,
1032
+ touched: field.touched,
1033
+ dirty: field.dirty,
1034
+ pristine: !field.dirty,
1035
+ ignored: false,
1036
+ disabled: false
1037
+ });
1038
+ this.emit(EVENTS.fieldInvalid, {
1039
+ field,
1040
+ message,
1041
+ reason: options.reason || "manual"
1042
+ });
1043
+ return false;
1044
+ }
1045
+ }
1046
+ field.lastError = "";
1047
+ this.renderer.clear(field);
1048
+ this.state.updateField(field.name, {
1049
+ valid: true,
1050
+ invalid: false,
1051
+ pending: false,
1052
+ touched: field.touched,
1053
+ dirty: field.dirty,
1054
+ pristine: !field.dirty,
1055
+ ignored: false,
1056
+ disabled: false
1057
+ });
1058
+ this.emit(EVENTS.fieldValid, {
1059
+ field,
1060
+ reason: options.reason || "manual"
1061
+ });
1062
+ return true;
1063
+ }
1064
+ reset() {
1065
+ this.form.reset();
1066
+ this.hasSubmitted = false;
1067
+ this.clearErrors();
1068
+ this.fields.forEach((field) => {
1069
+ field.dirty = false;
1070
+ field.touched = false;
1071
+ field.setVisualState("valid");
1072
+ this.state.updateField(field.name, {
1073
+ pristine: true,
1074
+ dirty: false,
1075
+ touched: false,
1076
+ pending: false,
1077
+ valid: false,
1078
+ invalid: false,
1079
+ disabled: false,
1080
+ ignored: false
1081
+ });
1082
+ });
1083
+ this.state.setFormState("idle");
1084
+ return this;
1085
+ }
1086
+ clearErrors() {
1087
+ this.formErrors = [];
1088
+ this.fields.forEach((field) => {
1089
+ field.clearServerMessage();
1090
+ field.lastError = "";
1091
+ this.renderer.clear(field);
1092
+ });
1093
+ this.emit(EVENTS.afterValidate, {
1094
+ valid: true,
1095
+ errors: this.getErrors()
1096
+ });
1097
+ return this;
1098
+ }
1099
+ setErrors(errors = {}) {
1100
+ this.clearErrors();
1101
+ const fieldErrors = errors.fields || Object.fromEntries(Object.entries(errors).filter(([key]) => ![
1102
+ "form",
1103
+ "_form",
1104
+ "fields"
1105
+ ].includes(key)));
1106
+ const formErrors = errors.form || errors._form || [];
1107
+ const previousInlineErrorAnnouncementsMuted = this.inlineErrorAnnouncementsMuted;
1108
+ this.inlineErrorAnnouncementsMuted = previousInlineErrorAnnouncementsMuted || this.options.focusOnError === "summary" && Boolean(this.summaryAddon);
1109
+ try {
1110
+ this.formErrors = Array.isArray(formErrors) ? formErrors.map(String) : [formErrors].filter(Boolean).map(String);
1111
+ Object.entries(fieldErrors).forEach(([name, message]) => {
1112
+ const field = this.fieldMap.get(name);
1113
+ if (!field || !message) return;
1114
+ const text = Array.isArray(message) ? String(message[0]) : String(message);
1115
+ field.setServerMessage(text);
1116
+ this.renderer.render(field, text);
1117
+ this.state.updateField(field.name, {
1118
+ valid: false,
1119
+ invalid: true,
1120
+ pending: false,
1121
+ touched: field.touched,
1122
+ dirty: field.dirty,
1123
+ pristine: !field.dirty,
1124
+ ignored: false,
1125
+ disabled: false
1126
+ });
1127
+ });
1128
+ } finally {
1129
+ this.inlineErrorAnnouncementsMuted = previousInlineErrorAnnouncementsMuted;
1130
+ }
1131
+ this.state.setFormState("invalid");
1132
+ this.emit(EVENTS.afterValidate, {
1133
+ valid: false,
1134
+ errors: this.getErrors()
1135
+ });
1136
+ this.emit(EVENTS.formInvalid, { errors: this.getErrors() });
1137
+ return this;
1138
+ }
1139
+ getErrors() {
1140
+ return {
1141
+ fields: Object.fromEntries(this.fields.filter((field) => field.lastError).map((field) => [field.name, field.lastError])),
1142
+ form: [...this.formErrors]
1143
+ };
1144
+ }
1145
+ getState() {
1146
+ return this.state.snapshot();
1147
+ }
1148
+ enable() {
1149
+ this.enabled = true;
1150
+ return this;
1151
+ }
1152
+ disable() {
1153
+ this.enabled = false;
1154
+ return this;
1155
+ }
1156
+ focusOnError() {
1157
+ if (this.options.focusOnError === false) return;
1158
+ if (this.options.focusOnError === "summary" && this.summaryAddon?.hasErrors()) {
1159
+ this.summaryAddon.focus();
1160
+ return;
1161
+ }
1162
+ this.fields.find((field) => field.lastError)?.focus();
1163
+ }
1164
+ destroy() {
1165
+ if (this.destroyed) return;
1166
+ this.destroyed = true;
1167
+ if (this.abortController) this.abortController.abort();
1168
+ else {
1169
+ this.form.removeEventListener("submit", this.onSubmit);
1170
+ this.form.removeEventListener("focusout", this.onFocusOut);
1171
+ this.form.removeEventListener("input", this.onInput);
1172
+ this.form.removeEventListener("change", this.onChange);
1173
+ }
1174
+ this.timers.forEach((timer) => clearTimeout(timer));
1175
+ this.timers.clear();
1176
+ this.renderer.destroy?.();
1177
+ this.addons.forEach((addon) => addon.destroy?.());
1178
+ this.fields.forEach((field) => field.destroy());
1179
+ this.form.classList.remove(CLASSES.initialized);
1180
+ if (this.addedRootClass) this.form.classList.remove(CLASSES.root);
1181
+ if (this.addedNoValidate) this.form.removeAttribute("novalidate");
1182
+ this.emit(EVENTS.destroy);
1183
+ A11yFormValidator.instances.delete(this.form);
1184
+ }
1185
+ };
1186
+ function createFormValidator(form, options = {}) {
1187
+ return new A11yFormValidator(form, options);
1188
+ }
1189
+ function initFormValidators(options = {}, root) {
1190
+ const scope = root || globalThis.document;
1191
+ if (!scope) return [];
1192
+ return Array.from(scope.querySelectorAll(SELECTORS.initAll)).filter((element) => element instanceof HTMLFormElement).map((form) => createFormValidator(form, options));
1193
+ }
1194
+
1195
+ //#endregion
1196
+ export { A11yFormValidator, ATTRIBUTES, CLASSES, DEFAULT_OPTIONS, EVENTS, ErrorRenderer, EventEmitter, FieldController, MessageResolver, RuleRegistry, SELECTORS, ValidationState, createDefaultPreset, createFormValidator, createMinimalPreset, createNoSummaryPreset, en_default as enMessages, initFormValidators };
1197
+ //# sourceMappingURL=index.js.map