@ajosecortes/form-guard 1.0.2

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 ADDED
@@ -0,0 +1,417 @@
1
+ # Form Guard
2
+
3
+ A production-minded form validation library for plain HTML forms. Framework-agnostic, TypeScript-first.
4
+
5
+ Instance-based API, composable rules, clean error handling — with structured errors, first-class async support, debounce + cancellation, and accessible DOM output.
6
+
7
+ ## Why Form Guard
8
+
9
+ Most form validation libraries fall into two extremes: too low-level (requiring repetitive boilerplate) or too framework-coupled (useless for plain HTML projects). Form Guard keeps the simple, familiar instance-based approach while adding:
10
+
11
+ - **Structured errors** — every error has a `code`, `rule`, `field`, `value`, and `message`
12
+ - **Composable rules** — combine built-in rules with custom sync and async logic
13
+ - **First-class async** — async rules participate naturally in the validation pipeline
14
+ - **Accessible by default** — `aria-invalid`, `aria-describedby`, `role="alert"`, and focus management
15
+ - **Localization** — switch locales at runtime or provide your own messages
16
+ - **Group validation** — validate checkbox/radio groups with the same rule API
17
+ - **Debouncing & cancellation** — built-in debounce for onChange/blur modes, AbortController cancellation for stale async requests
18
+
19
+ ## Installation
20
+
21
+ ```bash
22
+ npm install form-guard
23
+ ```
24
+
25
+ ```html
26
+ <script type="module">
27
+ import { FormGuard, required, email, minLength } from 'form-guard';
28
+ </script>
29
+ ```
30
+
31
+ ## Quick Start
32
+
33
+ ```html
34
+ <form id="signup">
35
+ <input name="email" type="email" />
36
+ <div id="email-errors"></div>
37
+ <button type="submit">Sign up</button>
38
+ </form>
39
+ ```
40
+
41
+ ```ts
42
+ import { FormGuard, required, email } from 'form-guard';
43
+
44
+ const guard = new FormGuard('#signup');
45
+
46
+ guard.addField('email', [
47
+ required(),
48
+ email(),
49
+ ], {
50
+ errorContainer: document.getElementById('email-errors'),
51
+ });
52
+
53
+ guard.onSuccess(() => {
54
+ alert('Form is valid!');
55
+ });
56
+ ```
57
+
58
+ ## API Reference
59
+
60
+ ### Constructor
61
+
62
+ ```ts
63
+ new FormGuard(form, config?)
64
+ ```
65
+
66
+ - **form**: CSS selector string (`'#my-form'`) or `HTMLFormElement`
67
+ - **config**: Optional global configuration (see [Configuration](#configuration))
68
+
69
+ ### Methods
70
+
71
+ | Method | Returns | Description |
72
+ |--------|---------|-------------|
73
+ | `addField(selector, rules, config?)` | `this` | Register a field for validation |
74
+ | `removeField(selector)` | `this` | Remove a field from validation |
75
+ | `addGroup(selector, rules, config?)` | `this` | Register a checkbox/radio group |
76
+ | `removeGroup(selector)` | `this` | Remove a group from validation |
77
+ | `validate(force?)` | `Promise<boolean>` | Validate all registered fields and groups |
78
+ | `validateField(selector)` | `Promise<boolean>` | Validate a single field |
79
+ | `validateGroup(selector)` | `Promise<boolean>` | Validate a single group |
80
+ | `revalidate()` | `Promise<boolean>` | Reset all validated flags and re-validate |
81
+ | `revalidateField(selector)` | `Promise<boolean>` | Re-validate a specific field |
82
+ | `revalidateGroup(selector)` | `Promise<boolean>` | Re-validate a specific group |
83
+ | `clearErrors()` | `this` | Clear all errors and DOM state |
84
+ | `refresh()` | `this` | Re-scan the DOM for added/removed elements |
85
+ | `destroy()` | `void` | Remove all listeners and clean up |
86
+ | `setLocale(locale)` | `this` | Merge locale messages at runtime |
87
+ | `onSuccess(callback)` | `this` | Register a success hook |
88
+ | `onFail(callback)` | `this` | Register a failure hook |
89
+ | `onValidate(callback)` | `this` | Register a hook that fires on every validation |
90
+ | `getErrors()` | `ValidationError[]` | Get all current errors |
91
+ | `getFieldErrors(selector)` | `ValidationError[]` | Get errors for a specific field |
92
+ | `getGroupErrors(selector)` | `ValidationError[]` | Get errors for a specific group |
93
+ | `isFieldPending(selector)` | `boolean` | Whether a field has async validation in progress |
94
+ | `isPending()` | `boolean` | Whether any field has async validation in progress |
95
+
96
+ ## Rules
97
+
98
+ Every rule helper returns a `Rule` object. Rules execute in declared order. Validation stops at the first failing rule by default; set `collectAll: true` in global config to collect all failures.
99
+
100
+ ### Built-in Rules
101
+
102
+ | Rule | Usage | Description |
103
+ |------|-------|-------------|
104
+ | `required(msg?)` | `required('Name is required')` | Field must have a non-empty value |
105
+ | `email(msg?)` | `email()` | Value must match email format (skipped if empty) |
106
+ | `minLength(n, msg?)` | `minLength(8)` | String must be at least `n` characters (skipped if empty) |
107
+ | `maxLength(n, msg?)` | `maxLength(200)` | String must be at most `n` characters |
108
+ | `number(msg?)` | `number()` | Value must be a valid number (skipped if empty) |
109
+ | `integer(msg?)` | `integer()` | Value must be an integer (skipped if empty) |
110
+ | `min(n, msg?)` | `min(18)` | Number must be >= `n` (skipped if empty or NaN) |
111
+ | `max(n, msg?)` | `max(120)` | Number must be <= `n` (skipped if empty or NaN) |
112
+ | `pattern(regexp, msg?)` | `pattern(/^[A-Z]+$/)` | Value must match regex (skipped if empty) |
113
+ | `sameAs(fieldName, msg?)` | `sameAs('password')` | Value must match another field's value |
114
+ | `files(msg?)` | `files()` | File input must have at least one file |
115
+ | `minFiles(n, msg?)` | `minFiles(2)` | File input must have at least `n` files |
116
+ | `maxFiles(n, msg?)` | `maxFiles(5)` | File input must have at most `n` files |
117
+ | `strongPassword(msg?)` | `strongPassword()` | Must have uppercase, lowercase, digit, special char, and 8+ length |
118
+ | `custom(fn, name?)` | `custom(v => v > 0 \|\| 'Must be positive')` | Custom sync validation function |
119
+ | `asyncRule(fn, opts?)` | `asyncRule(checkUsername, { name: 'username' })` | Custom async validation function |
120
+
121
+ ### Custom Rules
122
+
123
+ ```ts
124
+ guard.addField('#age', [
125
+ required(),
126
+ custom((value, form) => {
127
+ const n = Number(value);
128
+ if (isNaN(n)) return 'Must be a number';
129
+ if (n < 0) return 'Age cannot be negative';
130
+ if (n > 150) return 'Age seems unrealistic';
131
+ return true;
132
+ }, 'valid-age'),
133
+ ]);
134
+ ```
135
+
136
+ ### Async Rules
137
+
138
+ Async rules run after sync rules pass. They receive the same `(value, form)` signature.
139
+
140
+ ```ts
141
+ guard.addField('#username', [
142
+ required(),
143
+ minLength(3),
144
+ asyncRule(async (value) => {
145
+ const res = await fetch(`/api/check-username?q=${value}`);
146
+ const data = await res.json();
147
+ return data.available || 'Username is taken';
148
+ }, { name: 'username-available' }),
149
+ ]);
150
+ ```
151
+
152
+ ## Configuration
153
+
154
+ ### Global Config
155
+
156
+ Passed as the second argument to the constructor:
157
+
158
+ ```ts
159
+ const guard = new FormGuard('#form', {
160
+ mode: 'onSubmit', // 'onSubmit' | 'onBlur' | 'onChange'
161
+ locale: {}, // Custom locale messages
162
+ focusOnError: true, // Focus first invalid field
163
+ lockForm: false, // Lock form during async validation
164
+ errorClass: 'form-guard-error', // CSS class for invalid fields
165
+ successClass: 'form-guard-success', // CSS class for valid fields
166
+ pendingClass: 'form-guard-pending', // CSS class during async validation
167
+ debug: false, // Enable debug warnings
168
+ verbose: false, // Verbose debug output
169
+ errorContainer: '.errors', // Default error container selector
170
+ collectAll: false, // Collect all errors per field instead of stopping at first
171
+ debounceMs: 0, // Debounce delay in ms for onChange/onBlur modes
172
+ });
173
+ ```
174
+
175
+ ### Validation Modes
176
+
177
+ - **`onSubmit`** (default): Validates when the form is submitted. Prevents submission on failure.
178
+ - **`onBlur`**: Validates each field when it loses focus.
179
+ - **`onChange`**: Validates each field as the user types/changes it.
180
+
181
+ ### Field Config
182
+
183
+ Passed as the third argument to `addField`:
184
+
185
+ ```ts
186
+ guard.addField('#email', [required(), email()], {
187
+ name: 'email', // Alternative field identifier
188
+ label: 'Email Address', // Human-readable label
189
+ errorContainer: el, // HTMLElement or CSS selector for error messages
190
+ successClass: 'my-success', // Override global success class
191
+ errorClass: 'my-error', // Override global error class
192
+ debounceMs: 500, // Per-field debounce override
193
+ messages: { // Per-field rule messages
194
+ required: 'Email cannot be empty',
195
+ email: 'Enter a valid email',
196
+ },
197
+ });
198
+ ```
199
+
200
+ ## Error Model
201
+
202
+ Every validation error is a structured object, not a plain string:
203
+
204
+ ```ts
205
+ interface ValidationError {
206
+ code: string; // Rule code: 'required', 'email', etc.
207
+ message: string; // Human-readable error message
208
+ field: string; // Field name or selector
209
+ rule: string; // Rule that triggered the error
210
+ value: unknown; // The value that failed
211
+ meta: Record<string, unknown>; // Additional metadata (reserved for future use)
212
+ }
213
+ ```
214
+
215
+ Access errors through hooks or the `getErrors()` method:
216
+
217
+ ```ts
218
+ guard.onFail((errors) => {
219
+ errors.forEach(e => {
220
+ console.log(`${e.field}: ${e.message} [${e.rule}]`);
221
+ });
222
+ });
223
+ ```
224
+
225
+ ## Lifecycle Hooks
226
+
227
+ Hooks are chainable and support multiple callbacks:
228
+
229
+ ```ts
230
+ guard
231
+ .onValidate(({ valid, errors, fields, groups }) => {
232
+ console.log('Validated:', valid, errors.length, 'errors');
233
+ })
234
+ .onSuccess(() => {
235
+ console.log('All checks passed');
236
+ })
237
+ .onFail((errors) => {
238
+ console.log(errors.length, 'validation errors');
239
+ });
240
+ ```
241
+
242
+ - **`onValidate`**: Fires after every validation, regardless of result. Receives the full validation result.
243
+ - **`onSuccess`**: Fires when all fields and groups pass validation.
244
+ - **`onFail`**: Fires when any field or group fails validation. Receives the array of all errors.
245
+
246
+ ## Group Validation
247
+
248
+ Validate checkbox/radio groups as a single unit:
249
+
250
+ ```ts
251
+ guard.addGroup('colors', [required('Select at least one color')], {
252
+ errorContainer: document.getElementById('colors-errors'),
253
+ });
254
+ ```
255
+
256
+ Group validation treats all elements with the same `name` attribute as a single logical field:
257
+ - **Checkbox groups**: At least one must be checked
258
+ - **Radio groups**: Exactly one must be selected
259
+
260
+ ## Debouncing & Cancellation
261
+
262
+ Async validation (e.g., checking username availability) benefits from debouncing: waiting until the user stops typing before firing an API call, and cancelling stale requests when a new keystroke arrives.
263
+
264
+ ### Configuration
265
+
266
+ ```ts
267
+ const guard = new FormGuard('#form', {
268
+ mode: 'onChange',
269
+ debounceMs: 300, // Wait 300ms after last keystroke before validating
270
+ });
271
+
272
+ // Per-field override
273
+ guard.addField('#username', [...rules], {
274
+ debounceMs: 500, // This field uses 500ms instead
275
+ });
276
+ ```
277
+
278
+ When `debounceMs` is `0` (default), validation fires immediately on each event.
279
+
280
+ ### Pending state
281
+
282
+ While async validation is in progress, the field gets a `form-guard-pending` CSS class (configurable via `pendingClass`). Check pending state programmatically:
283
+
284
+ ```ts
285
+ guard.isFieldPending('username'); // true while async validation runs
286
+ guard.isPending(); // true if any field is validating
287
+ ```
288
+
289
+ ### AbortController cancellation
290
+
291
+ Every async validation run creates a new `AbortController`. If a new validation starts before the previous one finishes (e.g., user keeps typing), the previous controller is aborted. Async rules receive the `AbortSignal` as the third argument:
292
+
293
+ ```ts
294
+ guard.addField('#username', [
295
+ asyncRule(async (value, form, signal) => {
296
+ const res = await fetch(`/api/check?q=${value}`, { signal });
297
+ if (!res.ok) throw new Error('Network error');
298
+ const data = await res.json();
299
+ return data.available || 'Username is taken';
300
+ }),
301
+ ]);
302
+ ```
303
+
304
+ - **Debounce** prevents firing on every keystroke
305
+ - **AbortController** cancels stale requests that haven't resolved yet
306
+ - **Combined**, they prevent race conditions and wasted API calls
307
+
308
+ ## Localization
309
+
310
+ Form Guard ships with English (`en`) built-in. Switch or extend locales at any time:
311
+
312
+ ```ts
313
+ import { FormGuard, en } from 'form-guard';
314
+
315
+ const es = {
316
+ required: 'Este campo es obligatorio',
317
+ email: 'Ingrese un email válido',
318
+ minLength: 'Debe tener al menos {0} caracteres',
319
+ };
320
+
321
+ const guard = new FormGuard('#form', { locale: es });
322
+
323
+ // Switch later
324
+ guard.setLocale({
325
+ required: 'Ce champ est obligatoire',
326
+ email: 'Veuillez entrer un email valide',
327
+ });
328
+ ```
329
+
330
+ Per-field messages take precedence over locale messages.
331
+
332
+ ## Accessibility
333
+
334
+ Form Guard sets DOM attributes for accessible error handling:
335
+
336
+ - **`aria-invalid="true"`** on fields with errors
337
+ - **`aria-describedby`** linking the field to its error message element
338
+ - **`role="alert"`** on error containers for screen reader announcements
339
+ - **Focus management**: When `focusOnError` is `true` (default), the first invalid field receives focus after validation
340
+
341
+ ### CSS Classes
342
+
343
+ - **`.form-guard-error`**: Added to fields with validation errors
344
+ - **`.form-guard-success`**: Added to fields that pass validation
345
+ - **`.form-guard-message`**: Error message span inside the error container
346
+
347
+ Customize these via global or per-field config.
348
+
349
+ ## Examples
350
+
351
+ The `examples/` directory contains runnable HTML files:
352
+
353
+ | Example | Demonstrates |
354
+ |---------|-------------|
355
+ | `signup.html` | Full signup form with multiple rules, password confirmation, checkbox |
356
+ | `async-username.html` | Async validation simulating a username availability API |
357
+ | `group-validation.html` | Radio and checkbox group validation |
358
+ | `custom-rules.html` | Custom sync validation rules |
359
+ | `localization.html` | Runtime locale switching (English, Spanish, French) |
360
+
361
+ To run an example:
362
+
363
+ ```bash
364
+ npm run dev
365
+ ```
366
+
367
+ Then open one of these in your browser:
368
+ - `http://localhost:5173/examples/signup.html`
369
+ - `http://localhost:5173/examples/async-username.html`
370
+ - `http://localhost:5173/examples/group-validation.html`
371
+ - `http://localhost:5173/examples/custom-rules.html`
372
+ - `http://localhost:5173/examples/localization.html`
373
+
374
+ Vite's dev server compiles TypeScript on the fly — no build step needed.
375
+
376
+ ## Testing
377
+
378
+ The test suite uses Vitest with jsdom. Tests cover observable behavior, not internals.
379
+
380
+ ```bash
381
+ npm test # Run once
382
+ npm run test:watch # Watch mode
383
+ ```
384
+
385
+ ### Coverage areas
386
+
387
+ - Constructor and initialization
388
+ - Field registration, removal, and validation
389
+ - All built-in rules (required, email, length, pattern, numeric, file, password, sameAs)
390
+ - Custom sync and async rules
391
+ - Group validation (checkbox, radio)
392
+ - Lifecycle hooks (onSuccess, onFail, onValidate)
393
+ - Locale support (default, constructor, runtime switching)
394
+ - DOM rendering (error/success classes, aria attributes, error containers)
395
+ - Clear/reset/destroy lifecycle
396
+ - collectAll mode
397
+
398
+ ## Design Decisions
399
+
400
+ ### Stop-at-first vs. collect-all
401
+
402
+ By default, validation stops at the first failing rule per field. This avoids overwhelming users with multiple messages for a single field. Set `collectAll: true` in global config to collect all failures — useful for inline validation displays that show every rule violation at once.
403
+
404
+ ### Error message precedence
405
+
406
+ 1. Rule's inline message (`required('Custom message')`)
407
+ 2. Field-level `messages` config
408
+ 3. Locale message
409
+ 4. Rule name fallback
410
+
411
+ ### No framework adapters in v1
412
+
413
+ Form Guard targets plain HTML forms directly. Framework wrappers (React, Vue, Angular) are out of scope for v1 but the headless `getErrors()` API and hook callbacks make integration straightforward.
414
+
415
+ ## License
416
+
417
+ MIT
@@ -0,0 +1,51 @@
1
+ import type { FormGuardInstance, FormGuardConfig, Rule, FieldConfig, GroupConfig, ValidationError, Locale, SuccessCallback, FailCallback, ValidateCallback } from './types';
2
+ export declare class FormGuard implements FormGuardInstance {
3
+ private form;
4
+ private config;
5
+ private fields;
6
+ private groups;
7
+ private locale;
8
+ private successCallbacks;
9
+ private failCallbacks;
10
+ private validateCallbacks;
11
+ private boundSubmit;
12
+ private boundBlur;
13
+ private boundChange;
14
+ private destroyed;
15
+ constructor(form: string | HTMLFormElement, config?: FormGuardConfig);
16
+ addField(fieldSelector: string, rules: Rule[], config?: FieldConfig): FormGuardInstance;
17
+ removeField(fieldSelector: string): FormGuardInstance;
18
+ addGroup(groupSelector: string, rules: Rule[], config?: GroupConfig): FormGuardInstance;
19
+ removeGroup(groupSelector: string): FormGuardInstance;
20
+ validate(forceRevalidation?: boolean): Promise<boolean>;
21
+ validateField(fieldSelector: string): Promise<boolean>;
22
+ validateGroup(groupSelector: string): Promise<boolean>;
23
+ revalidate(): Promise<boolean>;
24
+ revalidateField(fieldSelector: string): Promise<boolean>;
25
+ revalidateGroup(groupSelector: string): Promise<boolean>;
26
+ clearErrors(): FormGuardInstance;
27
+ refresh(): FormGuardInstance;
28
+ destroy(): void;
29
+ setLocale(locale: Locale): FormGuardInstance;
30
+ onSuccess(callback: SuccessCallback): FormGuardInstance;
31
+ onFail(callback: FailCallback): FormGuardInstance;
32
+ onValidate(callback: ValidateCallback): FormGuardInstance;
33
+ getErrors(): ValidationError[];
34
+ getFieldErrors(fieldSelector: string): ValidationError[];
35
+ getGroupErrors(groupSelector: string): ValidationError[];
36
+ isFieldPending(fieldSelector: string): boolean;
37
+ isPending(): boolean;
38
+ private validateFieldState;
39
+ private validateGroupState;
40
+ private cancelFieldValidation;
41
+ private getMessage;
42
+ private getAllErrors;
43
+ private fireHooks;
44
+ private handleSubmit;
45
+ private handleBlur;
46
+ private handleChange;
47
+ private handleGroupEvent;
48
+ private debounceValidate;
49
+ private bindEvents;
50
+ private unbindEvents;
51
+ }
package/dist/dom.d.ts ADDED
@@ -0,0 +1,11 @@
1
+ import type { ValidationError, FieldConfig, FormGuardConfig } from './types';
2
+ export declare function resolveErrorContainer(fieldConfig?: FieldConfig, globalConfig?: FormGuardConfig, element?: HTMLElement): HTMLElement | null;
3
+ export declare function clearFieldDOM(element: HTMLElement, fieldConfig?: FieldConfig, globalConfig?: FormGuardConfig): void;
4
+ export declare function renderFieldPending(element: HTMLElement, fieldConfig?: FieldConfig, globalConfig?: FormGuardConfig): void;
5
+ export declare function renderFieldError(element: HTMLElement, error: ValidationError, fieldConfig?: FieldConfig, globalConfig?: FormGuardConfig): void;
6
+ export declare function renderFieldSuccess(element: HTMLElement, fieldConfig?: FieldConfig, globalConfig?: FormGuardConfig): void;
7
+ export declare function focusFirstInvalid(fields: {
8
+ element: HTMLElement;
9
+ errors: ValidationError[];
10
+ }[]): void;
11
+ export declare function escapeHtml(text: string): string;
@@ -0,0 +1,2 @@
1
+ "use strict";var b=Object.defineProperty;var y=(r,e,t)=>e in r?b(r,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):r[e]=t;var u=(r,e,t)=>y(r,typeof e!="symbol"?e+"":e,t);Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const v={required:"This field is required",email:"Please enter a valid email address",minLength:"Must be at least {0} characters",maxLength:"Must be no more than {0} characters",number:"Must be a number",integer:"Must be an integer",min:"Must be at least {0}",max:"Must be no more than {0}",pattern:"Invalid format",sameAs:"Must match {0}",files:"Please select at least one file",minFiles:"Please select at least {0} files",maxFiles:"Please select at most {0} files",strongPassword:"Password must contain uppercase, lowercase, digit, and special character"};function d(r,e,t){const s=(r==null?void 0:r.errorContainer)??(e==null?void 0:e.errorContainer);if(!s)return null;if(s instanceof HTMLElement)return s;const i=s;if(t!=null&&t.closest){const n=t.closest(i);if(n)return n}return document.querySelector(i)}function h(r,e,t){const s=(e==null?void 0:e.errorClass)??(t==null?void 0:t.errorClass)??"form-guard-error",i=(e==null?void 0:e.successClass)??(t==null?void 0:t.successClass)??"form-guard-success",n=(t==null?void 0:t.pendingClass)??"form-guard-pending";r.classList.remove(s),r.classList.remove(i),r.classList.remove(n),r.removeAttribute("aria-invalid"),r.removeAttribute("aria-describedby");const a=d(e,t,r);a&&(a.innerHTML="",a.setAttribute("role","alert"))}function f(r,e,t){const s=(e==null?void 0:e.errorClass)??(t==null?void 0:t.errorClass)??"form-guard-error",i=(e==null?void 0:e.successClass)??(t==null?void 0:t.successClass)??"form-guard-success",n=(t==null?void 0:t.pendingClass)??"form-guard-pending";r.classList.remove(s),r.classList.remove(i),r.classList.add(n),r.removeAttribute("aria-invalid"),r.removeAttribute("aria-describedby");const a=d(e,t,r);a&&(a.innerHTML='<span class="form-guard-message form-guard-pending-text">Checking...</span>',a.setAttribute("role","status"),a.setAttribute("aria-live","polite"))}function E(r,e,t,s){const i=(t==null?void 0:t.errorClass)??(s==null?void 0:s.errorClass)??"form-guard-error",n=(t==null?void 0:t.successClass)??(s==null?void 0:s.successClass)??"form-guard-success",a=(s==null?void 0:s.pendingClass)??"form-guard-pending";r.classList.add(i),r.classList.remove(n),r.classList.remove(a),r.setAttribute("aria-invalid","true");const o=d(t,s,r);if(o){const c=`form-guard-error-${e.field}`;o.innerHTML=`<span id="${c}" class="form-guard-message">${A(e.message)}</span>`,o.setAttribute("role","alert"),r.setAttribute("aria-describedby",c)}}function L(r,e,t){const s=(e==null?void 0:e.errorClass)??(t==null?void 0:t.errorClass)??"form-guard-error",i=(e==null?void 0:e.successClass)??(t==null?void 0:t.successClass)??"form-guard-success",n=(t==null?void 0:t.pendingClass)??"form-guard-pending";r.classList.remove(s),r.classList.add(i),r.classList.remove(n),r.removeAttribute("aria-invalid"),r.removeAttribute("aria-describedby");const a=d(e,t,r);a&&(a.innerHTML="")}function g(r){for(const{element:e,errors:t}of r)if(t.length>0){e.focus();return}}function A(r){const e=document.createElement("div");return e.textContent=r,e.innerHTML}function m(r,e){return r.startsWith("#")||r.startsWith(".")||r.includes("[")?e.querySelector(r):e.querySelector(`[name="${r}"]`)}function p(r,e){return r.startsWith("#")||r.startsWith(".")||r.includes("[")?Array.from(e.querySelectorAll(r)):Array.from(e.querySelectorAll(`[name="${r}"]`))}function F(r){return r instanceof HTMLInputElement&&r.type==="file"?r.files:r instanceof HTMLInputElement&&(r.type==="checkbox"||r.type==="radio")?r.checked:r instanceof HTMLSelectElement&&r.multiple?Array.from(r.selectedOptions).map(t=>t.value):r.value}function k(r){const e=[];for(const t of r)t instanceof HTMLInputElement&&(t.type==="checkbox"||t.type==="radio")&&t.checked&&e.push(t.value);if(r.length>0){const t=r[0];if(t instanceof HTMLInputElement&&t.type==="radio")return e.length>0?e[0]:""}return e}function M(r,e){var t;return e!=null&&e.label?e.label:r.labels&&r.labels.length>0?((t=r.labels[0].textContent)==null?void 0:t.trim())??r.name:r.name||"field"}function l(r,e,t,s,i){return{code:r,message:e,field:t,rule:s,value:i,meta:{}}}class T{constructor(e,t){u(this,"form");u(this,"config");u(this,"fields",new Map);u(this,"groups",new Map);u(this,"locale");u(this,"successCallbacks",[]);u(this,"failCallbacks",[]);u(this,"validateCallbacks",[]);u(this,"boundSubmit",null);u(this,"boundBlur",null);u(this,"boundChange",null);u(this,"destroyed",!1);u(this,"handleSubmit",async e=>{if(this.config.mode!=="onSubmit")return;e.preventDefault(),await this.validate()&&this.form.submit()});if(this.config={mode:"onSubmit",focusOnError:!0,lockForm:!1,collectAll:!1,debounceMs:0,...t},this.locale={...v,...t==null?void 0:t.locale},typeof e=="string"){const s=document.querySelector(e);if(!s)throw new Error(`FormGuard: form not found for selector "${e}"`);this.form=s}else this.form=e;this.bindEvents()}addField(e,t,s){if(this.destroyed)return this;const i=e,n=m(e,this.form);if(!n)return this.config.debug&&console.warn(`FormGuard: field "${e}" not found`),this;if(this.fields.set(i,{field:n,rules:[...t],config:s??{},errors:[],validated:!1,pending:!1,abortController:null,debounceTimer:null}),this.config.mode==="onBlur"&&n.addEventListener("blur",this.handleBlur(i)),this.config.mode==="onChange"){const a=n instanceof HTMLInputElement&&(n.type==="checkbox"||n.type==="radio")?"change":"input";n.addEventListener(a,this.handleChange(i))}return this}removeField(e){const t=this.fields.get(e);return t&&(this.cancelFieldValidation(t),h(t.field,t.config,this.config),t.field.removeEventListener("blur",this.handleBlur(e)),t.field.removeEventListener("input",this.handleChange(e)),t.field.removeEventListener("change",this.handleChange(e))),this.fields.delete(e),this}addGroup(e,t,s){if(this.destroyed)return this;const i=p(e,this.form);if(i.length===0)return this.config.debug&&console.warn(`FormGuard: no elements found for group "${e}"`),this;if(this.groups.set(e,{elements:i,rules:[...t],config:s??{},errors:[],validated:!1,pending:!1,abortController:null,debounceTimer:null}),this.config.mode==="onBlur"||this.config.mode==="onChange")for(const n of i){n.addEventListener("blur",this.handleGroupEvent(e));const a=n instanceof HTMLInputElement&&(n.type==="checkbox"||n.type==="radio")?"change":"input";n.addEventListener(a,this.handleGroupEvent(e))}return this}removeGroup(e){const t=this.groups.get(e);if(t){this.cancelFieldValidation(t);for(const s of t.elements)s.removeEventListener("blur",this.handleGroupEvent(e)),s.removeEventListener("input",this.handleGroupEvent(e)),s.removeEventListener("change",this.handleGroupEvent(e))}return this.groups.delete(e),this}async validate(e=!1){if(this.destroyed)return!1;for(const[,i]of this.fields)!e&&i.validated||await this.validateFieldState(i);for(const[,i]of this.groups)!e&&i.validated||await this.validateGroupState(i);const t=this.getAllErrors(),s=t.length===0;if(this.fireHooks(s,t),!s&&this.config.focusOnError){const i=[];for(const[,n]of this.fields)n.errors.length>0&&i.push({element:n.field,errors:n.errors});g(i)}return s}async validateField(e){const t=this.fields.get(e);return t?(await this.validateFieldState(t),t.errors.length===0):!0}async validateGroup(e){const t=this.groups.get(e);return t?(await this.validateGroupState(t),t.errors.length===0):!0}async revalidate(){for(const[,e]of this.fields)e.validated=!1;for(const[,e]of this.groups)e.validated=!1;return this.validate(!0)}async revalidateField(e){const t=this.fields.get(e);return t?(t.validated=!1,await this.validateFieldState(t),t.errors.length===0):!0}async revalidateGroup(e){const t=this.groups.get(e);return t?(t.validated=!1,await this.validateGroupState(t),t.errors.length===0):!0}clearErrors(){for(const[,e]of this.fields)this.cancelFieldValidation(e),e.errors=[],e.validated=!1,e.pending=!1,h(e.field,e.config,this.config);for(const[,e]of this.groups)this.cancelFieldValidation(e),e.errors=[],e.validated=!1,e.pending=!1;return this}refresh(){const e=[...this.fields.entries()];for(const[s,i]of e){const n=m(s,this.form);n?i.field=n:this.fields.delete(s)}const t=[...this.groups.entries()];for(const[s,i]of t){const n=p(s,this.form);n.length>0?i.elements=n:this.groups.delete(s)}return this}destroy(){this.unbindEvents();for(const[e,t]of this.fields)this.cancelFieldValidation(t),h(t.field,t.config,this.config),t.field.removeEventListener("blur",this.handleBlur(e)),t.field.removeEventListener("input",this.handleChange(e)),t.field.removeEventListener("change",this.handleChange(e));for(const[e,t]of this.groups){this.cancelFieldValidation(t);for(const s of t.elements)s.removeEventListener("blur",this.handleGroupEvent(e)),s.removeEventListener("input",this.handleGroupEvent(e)),s.removeEventListener("change",this.handleGroupEvent(e))}this.fields.clear(),this.groups.clear(),this.successCallbacks.length=0,this.failCallbacks.length=0,this.validateCallbacks.length=0,this.destroyed=!0}setLocale(e){return this.locale={...this.locale,...e},this}onSuccess(e){return this.successCallbacks.push(e),this}onFail(e){return this.failCallbacks.push(e),this}onValidate(e){return this.validateCallbacks.push(e),this}getErrors(){return this.getAllErrors()}getFieldErrors(e){var t;return((t=this.fields.get(e))==null?void 0:t.errors)??[]}getGroupErrors(e){var t;return((t=this.groups.get(e))==null?void 0:t.errors)??[]}isFieldPending(e){var t;return((t=this.fields.get(e))==null?void 0:t.pending)??!1}isPending(){for(const[,e]of this.fields)if(e.pending)return!0;for(const[,e]of this.groups)if(e.pending)return!0;return!1}async validateFieldState(e){this.cancelFieldValidation(e);const t=new AbortController;e.abortController=t,e.pending=!0,e.errors=[],f(e.field,e.config,this.config);const s=F(e.field),i=M(e.field,e.config);try{for(const n of e.rules){if(t.signal.aborted)break;try{const a=n.validate(s,this.form,e.config,t.signal),o=a instanceof Promise?await a:a;if(t.signal.aborted)break;if(o===!0||o===void 0)continue;let c;if(typeof o=="string"?c=o:typeof o=="object"&&o!==null&&"message"in o?c=String(o.message):c=this.getMessage(n.name,e.config,i),e.errors.push(l(n.name,c,e.field.name||i,n.name,s)),!this.config.collectAll)break}catch(a){if(t.signal.aborted||(this.config.debug&&console.error(`FormGuard: rule "${n.name}" threw:`,a),e.errors.push(l(n.name,this.getMessage(n.name,e.config,i),e.field.name||i,n.name,s)),!this.config.collectAll))break}}}finally{e.abortController===t&&(e.pending=!1,e.validated=!0,e.abortController=null,t.signal.aborted||(e.errors.length>0?E(e.field,e.errors[0],e.config,this.config):L(e.field,e.config,this.config)))}}async validateGroupState(e){this.cancelFieldValidation(e);const t=new AbortController;e.abortController=t,e.pending=!0,e.errors=[];const s=k(e.elements),i=e.config.label??e.config.name??"group";try{for(const n of e.rules){if(t.signal.aborted)break;try{const a=n.validate(s,this.form,e.config,t.signal),o=a instanceof Promise?await a:a;if(t.signal.aborted)break;if(o===!0||o===void 0)continue;let c;if(typeof o=="string"?c=o:c=this.getMessage(n.name,e.config,i),e.errors.push(l(n.name,c,i,n.name,s)),!this.config.collectAll)break}catch(a){if(t.signal.aborted||(this.config.debug&&console.error(`FormGuard: rule "${n.name}" threw:`,a),e.errors.push(l(n.name,this.getMessage(n.name,e.config,i),i,n.name,s)),!this.config.collectAll))break}}}finally{e.abortController===t&&(e.pending=!1,e.validated=!0,e.abortController=null)}}cancelFieldValidation(e){e.debounceTimer!==null&&(clearTimeout(e.debounceTimer),e.debounceTimer=null),e.abortController&&(e.abortController.abort(),e.abortController=null,e.pending=!1)}getMessage(e,t,s){var n;const i=(n=t==null?void 0:t.messages)==null?void 0:n[e];return i||(this.locale[e]??e)}getAllErrors(){const e=[];for(const[,t]of this.fields)e.push(...t.errors);for(const[,t]of this.groups)e.push(...t.errors);return e}fireHooks(e,t){const s={};for(const[a,o]of this.fields)o.errors.length>0&&(s[a]=[...o.errors]);const i={};for(const[a,o]of this.groups)o.errors.length>0&&(i[a]=[...o.errors]);const n={valid:e,errors:t,fields:s,groups:i};for(const a of this.validateCallbacks)a(n);if(e)for(const a of this.successCallbacks)a();else for(const a of this.failCallbacks)a(t)}handleBlur(e){return()=>{const t=this.fields.get(e);t&&this.debounceValidate(()=>this.validateFieldState(t),t)}}handleChange(e){return()=>{const t=this.fields.get(e);t&&(t.validated=!1,this.debounceValidate(()=>this.validateFieldState(t),t))}}handleGroupEvent(e){return()=>{const t=this.groups.get(e);t&&(t.validated=!1,this.debounceValidate(()=>this.validateGroupState(t),t))}}debounceValidate(e,t){const s="config"in t&&"debounceMs"in t.config?t.config.debounceMs??this.config.debounceMs??0:this.config.debounceMs??0;s&&s>0?(t.debounceTimer!==null&&clearTimeout(t.debounceTimer),t.pending=!0,t instanceof Object&&"field"in t&&f(t.field,t.config,this.config),t.debounceTimer=setTimeout(()=>{t.debounceTimer=null,e()},s)):e()}bindEvents(){this.config.mode==="onSubmit"&&(this.boundSubmit=this.handleSubmit.bind(this),this.form.addEventListener("submit",this.boundSubmit),this.form.setAttribute("novalidate",""))}unbindEvents(){this.boundSubmit&&(this.form.removeEventListener("submit",this.boundSubmit),this.boundSubmit=null),this.boundBlur&&(this.form.removeEventListener("blur",this.boundBlur),this.boundBlur=null),this.boundChange&&(this.form.removeEventListener("input",this.boundChange),this.boundChange=null),this.form.removeAttribute("novalidate")}}const S=/^[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])?)*\.[a-zA-Z]{2,}$/;function G(r){return{name:"required",validate(e){return typeof e=="boolean"?e===!0||r||!1:typeof e=="string"?e.trim().length>0||r||!1:e instanceof FileList||Array.isArray(e)?e.length>0||r||!1:e!=null&&e!==""||r||!1}}}function w(r){return{name:"email",validate(e){return typeof e!="string"||e.trim()===""?!0:S.test(e.trim())||r||!1}}}function H(r,e){return{name:"minLength",validate(t){const s=typeof t=="string"?t.trim().length:String(t??"").length;return s===0?!0:s>=r||e||!1}}}function x(r,e){return{name:"maxLength",validate(t){return(typeof t=="string"?t.trim().length:String(t??"").length)<=r||e||!1}}}function P(r){return{name:"number",validate(e){return typeof e!="string"||e.trim()===""?!0:!isNaN(Number(e))||r||!1}}}function V(r){return{name:"integer",validate(e){if(typeof e!="string"||e.trim()==="")return!0;const t=Number(e);return!isNaN(t)&&Number.isInteger(t)||r||!1}}}function I(r,e){return{name:"min",validate(t){if(typeof t!="string"||t.trim()==="")return!0;const s=Number(t);return isNaN(s)?!0:s>=r||e||!1}}}function $(r,e){return{name:"max",validate(t){if(typeof t!="string"||t.trim()==="")return!0;const s=Number(t);return isNaN(s)?!0:s<=r||e||!1}}}function q(r,e){return{name:"pattern",validate(t){return typeof t!="string"||t.trim()===""?!0:r.test(t)||e||!1}}}function N(r,e){return{name:"sameAs",validate(t,s){const i=s.querySelector(`[name="${r}"]`);if(!i)return!0;const n=typeof t=="string"?t:"",a=i instanceof HTMLInputElement&&(i.type==="checkbox"||i.type==="radio")?i.checked:i.value;return n===String(a)||e||!1}}}function B(r){return{name:"files",validate(e){return e instanceof FileList?e.length>0||r||!1:!0}}}function _(r,e){return{name:"minFiles",validate(t){return!(t instanceof FileList)||t.length===0?!0:t.length>=r||e||!1}}}function z(r,e){return{name:"maxFiles",validate(t){return t instanceof FileList?t.length<=r||e||!1:!0}}}function Z(r,e){return{name:e??"custom",validate(t,s,i,n){return r(t,s,n)}}}function O(r,e){return{name:(e==null?void 0:e.name)??"async",validate(t,s,i,n){return r(t,s,n)}}}function W(r){const e=/[A-Z]/,t=/[a-z]/,s=/\d/,i=/[!@#$%^&*(),.?":{}|<>_\-+=\[\]\\;'/`~]/;return{name:"strongPassword",validate(n){return typeof n!="string"||n.trim()===""?!0:e.test(n)&&t.test(n)&&s.test(n)&&i.test(n)&&n.length>=8||r||!1}}}exports.FormGuard=T;exports.asyncRule=O;exports.custom=Z;exports.email=w;exports.en=v;exports.files=B;exports.integer=V;exports.max=$;exports.maxFiles=z;exports.maxLength=x;exports.min=I;exports.minFiles=_;exports.minLength=H;exports.number=P;exports.pattern=q;exports.required=G;exports.sameAs=N;exports.strongPassword=W;
2
+ //# sourceMappingURL=form-guard.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"form-guard.cjs","sources":["../src/locales/en.ts","../src/dom.ts","../src/FormGuard.ts","../src/rules.ts"],"sourcesContent":["import type { Locale } from '../types';\n\nconst en: Locale = {\n required: 'This field is required',\n email: 'Please enter a valid email address',\n minLength: 'Must be at least {0} characters',\n maxLength: 'Must be no more than {0} characters',\n number: 'Must be a number',\n integer: 'Must be an integer',\n min: 'Must be at least {0}',\n max: 'Must be no more than {0}',\n pattern: 'Invalid format',\n sameAs: 'Must match {0}',\n files: 'Please select at least one file',\n minFiles: 'Please select at least {0} files',\n maxFiles: 'Please select at most {0} files',\n strongPassword:\n 'Password must contain uppercase, lowercase, digit, and special character',\n};\n\nexport default en;\n","import type { ValidationError, FieldConfig, FormGuardConfig } from './types';\n\nexport function resolveErrorContainer(\n fieldConfig?: FieldConfig,\n globalConfig?: FormGuardConfig,\n element?: HTMLElement,\n): HTMLElement | null {\n const container =\n fieldConfig?.errorContainer ??\n globalConfig?.errorContainer;\n\n if (!container) return null;\n if (container instanceof HTMLElement) return container;\n\n const selector = container as string;\n if (element?.closest) {\n const found = element.closest(selector) as HTMLElement | null;\n if (found) return found;\n }\n return document.querySelector(selector);\n}\n\nexport function clearFieldDOM(\n element: HTMLElement,\n fieldConfig?: FieldConfig,\n globalConfig?: FormGuardConfig,\n): void {\n const errorClass = fieldConfig?.errorClass ?? globalConfig?.errorClass ?? 'form-guard-error';\n const successClass = fieldConfig?.successClass ?? globalConfig?.successClass ?? 'form-guard-success';\n const pendingClass = globalConfig?.pendingClass ?? 'form-guard-pending';\n\n element.classList.remove(errorClass);\n element.classList.remove(successClass);\n element.classList.remove(pendingClass);\n element.removeAttribute('aria-invalid');\n element.removeAttribute('aria-describedby');\n\n const container = resolveErrorContainer(fieldConfig, globalConfig, element);\n if (container) {\n container.innerHTML = '';\n container.setAttribute('role', 'alert');\n }\n}\n\nexport function renderFieldPending(\n element: HTMLElement,\n fieldConfig?: FieldConfig,\n globalConfig?: FormGuardConfig,\n): void {\n const errorClass = fieldConfig?.errorClass ?? globalConfig?.errorClass ?? 'form-guard-error';\n const successClass = fieldConfig?.successClass ?? globalConfig?.successClass ?? 'form-guard-success';\n const pendingClass = globalConfig?.pendingClass ?? 'form-guard-pending';\n\n element.classList.remove(errorClass);\n element.classList.remove(successClass);\n element.classList.add(pendingClass);\n element.removeAttribute('aria-invalid');\n element.removeAttribute('aria-describedby');\n\n const container = resolveErrorContainer(fieldConfig, globalConfig, element);\n if (container) {\n container.innerHTML = '<span class=\"form-guard-message form-guard-pending-text\">Checking...</span>';\n container.setAttribute('role', 'status');\n container.setAttribute('aria-live', 'polite');\n }\n}\n\nexport function renderFieldError(\n element: HTMLElement,\n error: ValidationError,\n fieldConfig?: FieldConfig,\n globalConfig?: FormGuardConfig,\n): void {\n const errorClass = fieldConfig?.errorClass ?? globalConfig?.errorClass ?? 'form-guard-error';\n const successClass = fieldConfig?.successClass ?? globalConfig?.successClass ?? 'form-guard-success';\n const pendingClass = globalConfig?.pendingClass ?? 'form-guard-pending';\n\n element.classList.add(errorClass);\n element.classList.remove(successClass);\n element.classList.remove(pendingClass);\n element.setAttribute('aria-invalid', 'true');\n\n const container = resolveErrorContainer(fieldConfig, globalConfig, element);\n if (container) {\n const errorId = `form-guard-error-${error.field}`;\n container.innerHTML = `<span id=\"${errorId}\" class=\"form-guard-message\">${escapeHtml(error.message)}</span>`;\n container.setAttribute('role', 'alert');\n element.setAttribute('aria-describedby', errorId);\n }\n}\n\nexport function renderFieldSuccess(\n element: HTMLElement,\n fieldConfig?: FieldConfig,\n globalConfig?: FormGuardConfig,\n): void {\n const errorClass = fieldConfig?.errorClass ?? globalConfig?.errorClass ?? 'form-guard-error';\n const successClass = fieldConfig?.successClass ?? globalConfig?.successClass ?? 'form-guard-success';\n const pendingClass = globalConfig?.pendingClass ?? 'form-guard-pending';\n\n element.classList.remove(errorClass);\n element.classList.add(successClass);\n element.classList.remove(pendingClass);\n element.removeAttribute('aria-invalid');\n element.removeAttribute('aria-describedby');\n\n const container = resolveErrorContainer(fieldConfig, globalConfig, element);\n if (container) {\n container.innerHTML = '';\n }\n}\n\nexport function focusFirstInvalid(\n fields: { element: HTMLElement; errors: ValidationError[] }[],\n): void {\n for (const { element, errors } of fields) {\n if (errors.length > 0) {\n element.focus();\n return;\n }\n }\n}\n\nexport function escapeHtml(text: string): string {\n const div = document.createElement('div');\n div.textContent = text;\n return div.innerHTML;\n}\n","import type {\n FormGuardInstance,\n FormGuardConfig,\n Rule,\n FieldConfig,\n GroupConfig,\n FieldState,\n GroupState,\n ValidationError,\n Locale,\n SuccessCallback,\n FailCallback,\n ValidateCallback,\n} from './types';\nimport { en } from './locales';\nimport {\n clearFieldDOM,\n renderFieldError,\n renderFieldSuccess,\n renderFieldPending,\n focusFirstInvalid,\n} from './dom';\n\nfunction resolveElement(\n selector: string,\n form: HTMLFormElement,\n): HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement | null {\n if (selector.startsWith('#') || selector.startsWith('.') || selector.includes('[')) {\n return form.querySelector(selector);\n }\n return form.querySelector(`[name=\"${selector}\"]`);\n}\n\nfunction resolveElements(\n selector: string,\n form: HTMLFormElement,\n): (HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement)[] {\n if (selector.startsWith('#') || selector.startsWith('.') || selector.includes('[')) {\n return Array.from(\n form.querySelectorAll<HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement>(selector),\n );\n }\n return Array.from(\n form.querySelectorAll<HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement>(\n `[name=\"${selector}\"]`,\n ),\n );\n}\n\nfunction getFieldValue(\n el: HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement,\n): unknown {\n if (el instanceof HTMLInputElement && el.type === 'file') {\n return (el as HTMLInputElement).files;\n }\n if (\n el instanceof HTMLInputElement &&\n (el.type === 'checkbox' || el.type === 'radio')\n ) {\n return el.checked;\n }\n if (el instanceof HTMLSelectElement && el.multiple) {\n const selected = Array.from(el.selectedOptions).map((opt) => opt.value);\n return selected;\n }\n return el.value;\n}\n\nfunction getGroupValue(\n elements: (HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement)[],\n): unknown {\n const checkedValues: string[] = [];\n for (const el of elements) {\n if (\n el instanceof HTMLInputElement &&\n (el.type === 'checkbox' || el.type === 'radio')\n ) {\n if (el.checked) {\n checkedValues.push(el.value);\n }\n }\n }\n if (elements.length > 0) {\n const first = elements[0]!;\n if (\n first instanceof HTMLInputElement &&\n first.type === 'radio'\n ) {\n return checkedValues.length > 0 ? checkedValues[0] : '';\n }\n }\n return checkedValues;\n}\n\nfunction getFieldLabel(\n el: HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement,\n fieldConfig?: FieldConfig,\n): string {\n if (fieldConfig?.label) return fieldConfig.label;\n if (el.labels && el.labels.length > 0) return el.labels[0]!.textContent?.trim() ?? el.name;\n return el.name || 'field';\n}\n\nfunction makeError(\n code: string,\n message: string,\n field: string,\n rule: string,\n value: unknown,\n): ValidationError {\n return { code, message, field, rule, value, meta: {} };\n}\n\nexport class FormGuard implements FormGuardInstance {\n private form: HTMLFormElement;\n private config: FormGuardConfig;\n private fields = new Map<string, FieldState>();\n private groups = new Map<string, GroupState>();\n private locale: Locale;\n private successCallbacks: SuccessCallback[] = [];\n private failCallbacks: FailCallback[] = [];\n private validateCallbacks: ValidateCallback[] = [];\n private boundSubmit: ((e: Event) => void) | null = null;\n private boundBlur: ((e: Event) => void) | null = null;\n private boundChange: ((e: Event) => void) | null = null;\n private destroyed = false;\n\n constructor(form: string | HTMLFormElement, config?: FormGuardConfig) {\n this.config = {\n mode: 'onSubmit',\n focusOnError: true,\n lockForm: false,\n collectAll: false,\n debounceMs: 0,\n ...config,\n };\n this.locale = { ...en, ...config?.locale };\n\n if (typeof form === 'string') {\n const el = document.querySelector<HTMLFormElement>(form);\n if (!el) {\n throw new Error(`FormGuard: form not found for selector \"${form}\"`);\n }\n this.form = el;\n } else {\n this.form = form;\n }\n\n this.bindEvents();\n }\n\n addField(\n fieldSelector: string,\n rules: Rule[],\n config?: FieldConfig,\n ): FormGuardInstance {\n if (this.destroyed) return this;\n const key = fieldSelector;\n const el = resolveElement(fieldSelector, this.form);\n if (!el) {\n if (this.config.debug) {\n console.warn(`FormGuard: field \"${fieldSelector}\" not found`);\n }\n return this;\n }\n\n this.fields.set(key, {\n field: el,\n rules: [...rules],\n config: config ?? {},\n errors: [],\n validated: false,\n pending: false,\n abortController: null,\n debounceTimer: null,\n });\n\n if (this.config.mode === 'onBlur') {\n el.addEventListener('blur', this.handleBlur(key));\n }\n if (this.config.mode === 'onChange') {\n const eventType =\n el instanceof HTMLInputElement &&\n (el.type === 'checkbox' || el.type === 'radio')\n ? 'change'\n : 'input';\n el.addEventListener(eventType, this.handleChange(key));\n }\n\n return this;\n }\n\n removeField(fieldSelector: string): FormGuardInstance {\n const state = this.fields.get(fieldSelector);\n if (state) {\n this.cancelFieldValidation(state);\n clearFieldDOM(state.field, state.config, this.config);\n state.field.removeEventListener('blur', this.handleBlur(fieldSelector));\n state.field.removeEventListener('input', this.handleChange(fieldSelector));\n state.field.removeEventListener('change', this.handleChange(fieldSelector));\n }\n this.fields.delete(fieldSelector);\n return this;\n }\n\n addGroup(\n groupSelector: string,\n rules: Rule[],\n config?: GroupConfig,\n ): FormGuardInstance {\n if (this.destroyed) return this;\n const elements = resolveElements(groupSelector, this.form);\n if (elements.length === 0) {\n if (this.config.debug) {\n console.warn(`FormGuard: no elements found for group \"${groupSelector}\"`);\n }\n return this;\n }\n\n this.groups.set(groupSelector, {\n elements,\n rules: [...rules],\n config: config ?? {},\n errors: [],\n validated: false,\n pending: false,\n abortController: null,\n debounceTimer: null,\n });\n\n if (this.config.mode === 'onBlur' || this.config.mode === 'onChange') {\n for (const el of elements) {\n el.addEventListener('blur', this.handleGroupEvent(groupSelector));\n const eventType =\n el instanceof HTMLInputElement &&\n (el.type === 'checkbox' || el.type === 'radio')\n ? 'change'\n : 'input';\n el.addEventListener(eventType, this.handleGroupEvent(groupSelector));\n }\n }\n\n return this;\n }\n\n removeGroup(groupSelector: string): FormGuardInstance {\n const state = this.groups.get(groupSelector);\n if (state) {\n this.cancelFieldValidation(state);\n for (const el of state.elements) {\n el.removeEventListener('blur', this.handleGroupEvent(groupSelector));\n el.removeEventListener('input', this.handleGroupEvent(groupSelector));\n el.removeEventListener('change', this.handleGroupEvent(groupSelector));\n }\n }\n this.groups.delete(groupSelector);\n return this;\n }\n\n async validate(forceRevalidation = false): Promise<boolean> {\n if (this.destroyed) return false;\n\n for (const [, state] of this.fields) {\n if (!forceRevalidation && state.validated) continue;\n await this.validateFieldState(state);\n }\n\n for (const [, state] of this.groups) {\n if (!forceRevalidation && state.validated) continue;\n await this.validateGroupState(state);\n }\n\n const allErrors = this.getAllErrors();\n const valid = allErrors.length === 0;\n\n this.fireHooks(valid, allErrors);\n\n if (!valid && this.config.focusOnError) {\n const fieldEntries: { element: HTMLElement; errors: ValidationError[] }[] = [];\n for (const [, state] of this.fields) {\n if (state.errors.length > 0) {\n fieldEntries.push({ element: state.field, errors: state.errors });\n }\n }\n focusFirstInvalid(fieldEntries);\n }\n\n return valid;\n }\n\n async validateField(fieldSelector: string): Promise<boolean> {\n const state = this.fields.get(fieldSelector);\n if (!state) return true;\n await this.validateFieldState(state);\n return state.errors.length === 0;\n }\n\n async validateGroup(groupSelector: string): Promise<boolean> {\n const state = this.groups.get(groupSelector);\n if (!state) return true;\n await this.validateGroupState(state);\n return state.errors.length === 0;\n }\n\n async revalidate(): Promise<boolean> {\n for (const [, state] of this.fields) {\n state.validated = false;\n }\n for (const [, state] of this.groups) {\n state.validated = false;\n }\n return this.validate(true);\n }\n\n async revalidateField(fieldSelector: string): Promise<boolean> {\n const state = this.fields.get(fieldSelector);\n if (!state) return true;\n state.validated = false;\n await this.validateFieldState(state);\n return state.errors.length === 0;\n }\n\n async revalidateGroup(groupSelector: string): Promise<boolean> {\n const state = this.groups.get(groupSelector);\n if (!state) return true;\n state.validated = false;\n await this.validateGroupState(state);\n return state.errors.length === 0;\n }\n\n clearErrors(): FormGuardInstance {\n for (const [, state] of this.fields) {\n this.cancelFieldValidation(state);\n state.errors = [];\n state.validated = false;\n state.pending = false;\n clearFieldDOM(state.field, state.config, this.config);\n }\n for (const [, state] of this.groups) {\n this.cancelFieldValidation(state);\n state.errors = [];\n state.validated = false;\n state.pending = false;\n }\n return this;\n }\n\n refresh(): FormGuardInstance {\n const fieldEntries = [...this.fields.entries()];\n for (const [key, state] of fieldEntries) {\n const el = resolveElement(key, this.form);\n if (el) {\n state.field = el;\n } else {\n this.fields.delete(key);\n }\n }\n\n const groupEntries = [...this.groups.entries()];\n for (const [key, state] of groupEntries) {\n const elements = resolveElements(key, this.form);\n if (elements.length > 0) {\n state.elements = elements;\n } else {\n this.groups.delete(key);\n }\n }\n\n return this;\n }\n\n destroy(): void {\n this.unbindEvents();\n for (const [key, state] of this.fields) {\n this.cancelFieldValidation(state);\n clearFieldDOM(state.field, state.config, this.config);\n state.field.removeEventListener('blur', this.handleBlur(key));\n state.field.removeEventListener('input', this.handleChange(key));\n state.field.removeEventListener('change', this.handleChange(key));\n }\n for (const [key, state] of this.groups) {\n this.cancelFieldValidation(state);\n for (const el of state.elements) {\n el.removeEventListener('blur', this.handleGroupEvent(key));\n el.removeEventListener('input', this.handleGroupEvent(key));\n el.removeEventListener('change', this.handleGroupEvent(key));\n }\n }\n this.fields.clear();\n this.groups.clear();\n this.successCallbacks.length = 0;\n this.failCallbacks.length = 0;\n this.validateCallbacks.length = 0;\n this.destroyed = true;\n }\n\n setLocale(locale: Locale): FormGuardInstance {\n this.locale = { ...this.locale, ...locale };\n return this;\n }\n\n onSuccess(callback: SuccessCallback): FormGuardInstance {\n this.successCallbacks.push(callback);\n return this;\n }\n\n onFail(callback: FailCallback): FormGuardInstance {\n this.failCallbacks.push(callback);\n return this;\n }\n\n onValidate(callback: ValidateCallback): FormGuardInstance {\n this.validateCallbacks.push(callback);\n return this;\n }\n\n getErrors(): ValidationError[] {\n return this.getAllErrors();\n }\n\n getFieldErrors(fieldSelector: string): ValidationError[] {\n return this.fields.get(fieldSelector)?.errors ?? [];\n }\n\n getGroupErrors(groupSelector: string): ValidationError[] {\n return this.groups.get(groupSelector)?.errors ?? [];\n }\n\n isFieldPending(fieldSelector: string): boolean {\n return this.fields.get(fieldSelector)?.pending ?? false;\n }\n\n isPending(): boolean {\n for (const [, state] of this.fields) {\n if (state.pending) return true;\n }\n for (const [, state] of this.groups) {\n if (state.pending) return true;\n }\n return false;\n }\n\n private async validateFieldState(state: FieldState): Promise<void> {\n // Cancel any previous async validation for this field\n this.cancelFieldValidation(state);\n\n const controller = new AbortController();\n state.abortController = controller;\n state.pending = true;\n state.errors = [];\n renderFieldPending(state.field, state.config, this.config);\n\n const value = getFieldValue(state.field);\n const label = getFieldLabel(state.field, state.config);\n\n try {\n for (const rule of state.rules) {\n if (controller.signal.aborted) break;\n\n try {\n const result = rule.validate(value, this.form, state.config, controller.signal);\n const outcome = result instanceof Promise ? await result : result;\n\n if (controller.signal.aborted) break;\n\n if (outcome === true || outcome === undefined) {\n continue;\n }\n\n let message: string;\n if (typeof outcome === 'string') {\n message = outcome;\n } else if (typeof outcome === 'object' && outcome !== null && 'message' in outcome) {\n message = String((outcome as ValidationError).message);\n } else {\n message = this.getMessage(rule.name, state.config, label);\n }\n\n state.errors.push(\n makeError(rule.name, message, state.field.name || label, rule.name, value),\n );\n\n if (!this.config.collectAll) break;\n } catch (err) {\n if (controller.signal.aborted) break;\n if (this.config.debug) {\n console.error(`FormGuard: rule \"${rule.name}\" threw:`, err);\n }\n state.errors.push(\n makeError(\n rule.name,\n this.getMessage(rule.name, state.config, label),\n state.field.name || label,\n rule.name,\n value,\n ),\n );\n if (!this.config.collectAll) break;\n }\n }\n } finally {\n // Only apply results if this controller hasn't been superseded\n if (state.abortController === controller) {\n state.pending = false;\n state.validated = true;\n state.abortController = null;\n\n if (!controller.signal.aborted) {\n if (state.errors.length > 0) {\n renderFieldError(state.field, state.errors[0]!, state.config, this.config);\n } else {\n renderFieldSuccess(state.field, state.config, this.config);\n }\n }\n }\n }\n }\n\n private async validateGroupState(state: GroupState): Promise<void> {\n this.cancelFieldValidation(state);\n\n const controller = new AbortController();\n state.abortController = controller;\n state.pending = true;\n state.errors = [];\n\n const value = getGroupValue(state.elements);\n const label = state.config.label ?? state.config.name ?? 'group';\n\n try {\n for (const rule of state.rules) {\n if (controller.signal.aborted) break;\n\n try {\n const result = rule.validate(value, this.form, state.config, controller.signal);\n const outcome = result instanceof Promise ? await result : result;\n\n if (controller.signal.aborted) break;\n\n if (outcome === true || outcome === undefined) {\n continue;\n }\n\n let message: string;\n if (typeof outcome === 'string') {\n message = outcome;\n } else {\n message = this.getMessage(rule.name, state.config, label);\n }\n\n state.errors.push(\n makeError(rule.name, message, label, rule.name, value),\n );\n\n if (!this.config.collectAll) break;\n } catch (err) {\n if (controller.signal.aborted) break;\n if (this.config.debug) {\n console.error(`FormGuard: rule \"${rule.name}\" threw:`, err);\n }\n state.errors.push(\n makeError(rule.name, this.getMessage(rule.name, state.config, label), label, rule.name, value),\n );\n if (!this.config.collectAll) break;\n }\n }\n } finally {\n if (state.abortController === controller) {\n state.pending = false;\n state.validated = true;\n state.abortController = null;\n }\n }\n }\n\n private cancelFieldValidation(state: FieldState | GroupState): void {\n if (state.debounceTimer !== null) {\n clearTimeout(state.debounceTimer);\n state.debounceTimer = null;\n }\n if (state.abortController) {\n state.abortController.abort();\n state.abortController = null;\n state.pending = false;\n }\n }\n\n private getMessage(\n ruleName: string,\n config?: FieldConfig | GroupConfig,\n _label?: string,\n ): string {\n const fieldMsg = config?.messages?.[ruleName];\n if (fieldMsg) return fieldMsg;\n return this.locale[ruleName] ?? ruleName;\n }\n\n private getAllErrors(): ValidationError[] {\n const all: ValidationError[] = [];\n for (const [, state] of this.fields) {\n all.push(...state.errors);\n }\n for (const [, state] of this.groups) {\n all.push(...state.errors);\n }\n return all;\n }\n\n private fireHooks(valid: boolean, errors: ValidationError[]): void {\n const fieldsErr: Record<string, ValidationError[]> = {};\n for (const [key, state] of this.fields) {\n if (state.errors.length > 0) {\n fieldsErr[key] = [...state.errors];\n }\n }\n const groupsErr: Record<string, ValidationError[]> = {};\n for (const [key, state] of this.groups) {\n if (state.errors.length > 0) {\n groupsErr[key] = [...state.errors];\n }\n }\n\n const result = { valid, errors, fields: fieldsErr, groups: groupsErr };\n\n for (const cb of this.validateCallbacks) {\n cb(result);\n }\n\n if (valid) {\n for (const cb of this.successCallbacks) {\n cb();\n }\n } else {\n for (const cb of this.failCallbacks) {\n cb(errors);\n }\n }\n }\n\n private handleSubmit = async (e: Event): Promise<void> => {\n if (this.config.mode !== 'onSubmit') return;\n e.preventDefault();\n const valid = await this.validate();\n if (valid) {\n this.form.submit();\n }\n };\n\n private handleBlur(key: string) {\n return () => {\n const state = this.fields.get(key);\n if (state) {\n this.debounceValidate(() => this.validateFieldState(state), state);\n }\n };\n }\n\n private handleChange(key: string) {\n return () => {\n const state = this.fields.get(key);\n if (state) {\n state.validated = false;\n this.debounceValidate(() => this.validateFieldState(state), state);\n }\n };\n }\n\n private handleGroupEvent(key: string) {\n return () => {\n const state = this.groups.get(key);\n if (state) {\n state.validated = false;\n this.debounceValidate(() => this.validateGroupState(state), state);\n }\n };\n }\n\n private debounceValidate(\n fn: () => Promise<void>,\n state: FieldState | GroupState,\n ): void {\n const debounceMs =\n 'config' in state && 'debounceMs' in state.config\n ? (state.config as FieldConfig).debounceMs ?? this.config.debounceMs ?? 0\n : this.config.debounceMs ?? 0;\n\n if (debounceMs && debounceMs > 0) {\n if (state.debounceTimer !== null) {\n clearTimeout(state.debounceTimer);\n }\n state.pending = true;\n if (state instanceof Object && 'field' in state) {\n renderFieldPending((state as FieldState).field, (state as FieldState).config, this.config);\n }\n state.debounceTimer = setTimeout(() => {\n state.debounceTimer = null;\n void fn();\n }, debounceMs);\n } else {\n void fn();\n }\n }\n\n private bindEvents(): void {\n if (this.config.mode === 'onSubmit') {\n this.boundSubmit = this.handleSubmit.bind(this);\n this.form.addEventListener('submit', this.boundSubmit);\n this.form.setAttribute('novalidate', '');\n }\n }\n\n private unbindEvents(): void {\n if (this.boundSubmit) {\n this.form.removeEventListener('submit', this.boundSubmit);\n this.boundSubmit = null;\n }\n if (this.boundBlur) {\n this.form.removeEventListener('blur', this.boundBlur);\n this.boundBlur = null;\n }\n if (this.boundChange) {\n this.form.removeEventListener('input', this.boundChange);\n this.boundChange = null;\n }\n this.form.removeAttribute('novalidate');\n }\n}\n","import type { Rule } from './types';\n\nconst EMAIL_RE =\n /^[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])?)*\\.[a-zA-Z]{2,}$/;\n\nexport function required(message?: string): Rule {\n return {\n name: 'required',\n validate(value: unknown): string | boolean {\n if (typeof value === 'boolean') {\n return value === true || message || false;\n }\n if (typeof value === 'string') {\n return value.trim().length > 0 || message || false;\n }\n if (value instanceof FileList) {\n return value.length > 0 || message || false;\n }\n if (Array.isArray(value)) {\n return value.length > 0 || message || false;\n }\n return (value != null && value !== '') || message || false;\n },\n };\n}\n\nexport function email(message?: string): Rule {\n return {\n name: 'email',\n validate(value: unknown): string | boolean {\n if (typeof value !== 'string' || value.trim() === '') return true;\n return EMAIL_RE.test(value.trim()) || message || false;\n },\n };\n}\n\nexport function minLength(min: number, message?: string): Rule {\n return {\n name: 'minLength',\n validate(value: unknown): string | boolean {\n const len =\n typeof value === 'string' ? value.trim().length : String(value ?? '').length;\n if (len === 0) return true;\n return len >= min || message || false;\n },\n };\n}\n\nexport function maxLength(max: number, message?: string): Rule {\n return {\n name: 'maxLength',\n validate(value: unknown): string | boolean {\n const len =\n typeof value === 'string' ? value.trim().length : String(value ?? '').length;\n return len <= max || message || false;\n },\n };\n}\n\nexport function number(message?: string): Rule {\n return {\n name: 'number',\n validate(value: unknown): string | boolean {\n if (typeof value !== 'string' || value.trim() === '') return true;\n return !isNaN(Number(value)) || message || false;\n },\n };\n}\n\nexport function integer(message?: string): Rule {\n return {\n name: 'integer',\n validate(value: unknown): string | boolean {\n if (typeof value !== 'string' || value.trim() === '') return true;\n const n = Number(value);\n return (!isNaN(n) && Number.isInteger(n)) || message || false;\n },\n };\n}\n\nexport function min(minVal: number, message?: string): Rule {\n return {\n name: 'min',\n validate(value: unknown): string | boolean {\n if (typeof value !== 'string' || value.trim() === '') return true;\n const n = Number(value);\n if (isNaN(n)) return true;\n return n >= minVal || message || false;\n },\n };\n}\n\nexport function max(maxVal: number, message?: string): Rule {\n return {\n name: 'max',\n validate(value: unknown): string | boolean {\n if (typeof value !== 'string' || value.trim() === '') return true;\n const n = Number(value);\n if (isNaN(n)) return true;\n return n <= maxVal || message || false;\n },\n };\n}\n\nexport function pattern(regexp: RegExp, message?: string): Rule {\n return {\n name: 'pattern',\n validate(value: unknown): string | boolean {\n if (typeof value !== 'string' || value.trim() === '') return true;\n return regexp.test(value) || message || false;\n },\n };\n}\n\nexport function sameAs(fieldName: string, message?: string): Rule {\n return {\n name: 'sameAs',\n validate(value: unknown, form: HTMLFormElement): string | boolean {\n const el = form.querySelector<HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement>(\n `[name=\"${fieldName}\"]`,\n );\n if (!el) return true;\n const val = typeof value === 'string' ? value : '';\n const other =\n el instanceof HTMLInputElement &&\n (el.type === 'checkbox' || el.type === 'radio')\n ? el.checked\n : el.value;\n return val === String(other) || message || false;\n },\n };\n}\n\nexport function files(message?: string): Rule {\n return {\n name: 'files',\n validate(value: unknown): string | boolean {\n if (!(value instanceof FileList)) return true;\n return value.length > 0 || message || false;\n },\n };\n}\n\nexport function minFiles(minCount: number, message?: string): Rule {\n return {\n name: 'minFiles',\n validate(value: unknown): string | boolean {\n if (!(value instanceof FileList)) return true;\n if (value.length === 0) return true;\n return value.length >= minCount || message || false;\n },\n };\n}\n\nexport function maxFiles(maxCount: number, message?: string): Rule {\n return {\n name: 'maxFiles',\n validate(value: unknown): string | boolean {\n if (!(value instanceof FileList)) return true;\n return value.length <= maxCount || message || false;\n },\n };\n}\n\nexport function custom(\n fn: (\n value: unknown,\n form: HTMLFormElement,\n signal?: AbortSignal,\n ) => string | boolean | Promise<string | boolean>,\n name?: string,\n): Rule {\n return {\n name: name ?? 'custom',\n validate(value: unknown, form: HTMLFormElement, _fieldConfig?: unknown, signal?: AbortSignal) {\n return fn(value, form, signal);\n },\n };\n}\n\nexport function asyncRule(\n fn: (\n value: unknown,\n form: HTMLFormElement,\n signal?: AbortSignal,\n ) => Promise<string | boolean>,\n options?: { name?: string },\n): Rule {\n return {\n name: options?.name ?? 'async',\n validate(value: unknown, form: HTMLFormElement, _fieldConfig?: unknown, signal?: AbortSignal) {\n return fn(value, form, signal);\n },\n };\n}\n\nexport function strongPassword(message?: string): Rule {\n const HAS_UPPER = /[A-Z]/;\n const HAS_LOWER = /[a-z]/;\n const HAS_DIGIT = /\\d/;\n const HAS_SPECIAL = /[!@#$%^&*(),.?\":{}|<>_\\-+=\\[\\]\\\\;'/`~]/;\n\n return {\n name: 'strongPassword',\n validate(value: unknown): string | boolean {\n if (typeof value !== 'string' || value.trim() === '') return true;\n const ok =\n HAS_UPPER.test(value) &&\n HAS_LOWER.test(value) &&\n HAS_DIGIT.test(value) &&\n HAS_SPECIAL.test(value) &&\n value.length >= 8;\n return ok || message || false;\n },\n };\n}\n"],"names":["en","resolveErrorContainer","fieldConfig","globalConfig","element","container","selector","found","clearFieldDOM","errorClass","successClass","pendingClass","renderFieldPending","renderFieldError","error","errorId","escapeHtml","renderFieldSuccess","focusFirstInvalid","fields","errors","text","div","resolveElement","form","resolveElements","getFieldValue","el","opt","getGroupValue","elements","checkedValues","first","getFieldLabel","_a","makeError","code","message","field","rule","value","FormGuard","config","__publicField","fieldSelector","rules","key","eventType","state","groupSelector","forceRevalidation","allErrors","valid","fieldEntries","groupEntries","locale","callback","controller","label","result","outcome","err","ruleName","_label","fieldMsg","all","fieldsErr","groupsErr","cb","fn","debounceMs","EMAIL_RE","required","email","minLength","min","len","maxLength","max","number","integer","n","minVal","maxVal","pattern","regexp","sameAs","fieldName","val","other","files","minFiles","minCount","maxFiles","maxCount","custom","name","_fieldConfig","signal","asyncRule","options","strongPassword","HAS_UPPER","HAS_LOWER","HAS_DIGIT","HAS_SPECIAL"],"mappings":"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"}