@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 +417 -0
- package/dist/FormGuard.d.ts +51 -0
- package/dist/dom.d.ts +11 -0
- package/dist/form-guard.cjs +2 -0
- package/dist/form-guard.cjs.map +1 -0
- package/dist/form-guard.js +580 -0
- package/dist/form-guard.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/locales/en.d.ts +3 -0
- package/dist/locales/index.d.ts +2 -0
- package/dist/rules.d.ts +19 -0
- package/dist/types.d.ts +102 -0
- package/package.json +34 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"form-guard.js","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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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { FormGuard } from './FormGuard';
|
|
2
|
+
export { required, email, minLength, maxLength, number, integer, min, max, pattern, sameAs, files, minFiles, maxFiles, custom, asyncRule, strongPassword, } from './rules';
|
|
3
|
+
export { en } from './locales';
|
|
4
|
+
export type { FormGuardInstance, FormGuardConfig, Rule, RuleValidator, FieldConfig, GroupConfig, ValidationError, ValidationResult, Locale, FieldState, GroupState, ValidationMode, SuccessCallback, FailCallback, ValidateCallback, } from './types';
|
package/dist/rules.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Rule } from './types';
|
|
2
|
+
export declare function required(message?: string): Rule;
|
|
3
|
+
export declare function email(message?: string): Rule;
|
|
4
|
+
export declare function minLength(min: number, message?: string): Rule;
|
|
5
|
+
export declare function maxLength(max: number, message?: string): Rule;
|
|
6
|
+
export declare function number(message?: string): Rule;
|
|
7
|
+
export declare function integer(message?: string): Rule;
|
|
8
|
+
export declare function min(minVal: number, message?: string): Rule;
|
|
9
|
+
export declare function max(maxVal: number, message?: string): Rule;
|
|
10
|
+
export declare function pattern(regexp: RegExp, message?: string): Rule;
|
|
11
|
+
export declare function sameAs(fieldName: string, message?: string): Rule;
|
|
12
|
+
export declare function files(message?: string): Rule;
|
|
13
|
+
export declare function minFiles(minCount: number, message?: string): Rule;
|
|
14
|
+
export declare function maxFiles(maxCount: number, message?: string): Rule;
|
|
15
|
+
export declare function custom(fn: (value: unknown, form: HTMLFormElement, signal?: AbortSignal) => string | boolean | Promise<string | boolean>, name?: string): Rule;
|
|
16
|
+
export declare function asyncRule(fn: (value: unknown, form: HTMLFormElement, signal?: AbortSignal) => Promise<string | boolean>, options?: {
|
|
17
|
+
name?: string;
|
|
18
|
+
}): Rule;
|
|
19
|
+
export declare function strongPassword(message?: string): Rule;
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
export interface ValidationError {
|
|
2
|
+
code: string;
|
|
3
|
+
message: string;
|
|
4
|
+
field: string;
|
|
5
|
+
rule: string;
|
|
6
|
+
value: unknown;
|
|
7
|
+
meta: Record<string, unknown>;
|
|
8
|
+
}
|
|
9
|
+
export type ValidationResult = boolean | string | ValidationError | Promise<boolean | string | ValidationError>;
|
|
10
|
+
export type RuleValidator = (value: unknown, form: HTMLFormElement, fieldConfig?: FieldConfig, signal?: AbortSignal) => ValidationResult;
|
|
11
|
+
export interface Rule {
|
|
12
|
+
name: string;
|
|
13
|
+
validate: RuleValidator;
|
|
14
|
+
priority?: number;
|
|
15
|
+
}
|
|
16
|
+
export interface FieldConfig {
|
|
17
|
+
name?: string;
|
|
18
|
+
label?: string;
|
|
19
|
+
errorContainer?: HTMLElement | string;
|
|
20
|
+
successClass?: string;
|
|
21
|
+
errorClass?: string;
|
|
22
|
+
messages?: Record<string, string>;
|
|
23
|
+
debounceMs?: number;
|
|
24
|
+
}
|
|
25
|
+
export interface GroupConfig {
|
|
26
|
+
name?: string;
|
|
27
|
+
label?: string;
|
|
28
|
+
errorContainer?: HTMLElement | string;
|
|
29
|
+
successClass?: string;
|
|
30
|
+
errorClass?: string;
|
|
31
|
+
messages?: Record<string, string>;
|
|
32
|
+
}
|
|
33
|
+
export type ValidationMode = 'onSubmit' | 'onBlur' | 'onChange';
|
|
34
|
+
export interface Locale {
|
|
35
|
+
[key: string]: string;
|
|
36
|
+
}
|
|
37
|
+
export interface FormGuardConfig {
|
|
38
|
+
mode?: ValidationMode;
|
|
39
|
+
locale?: Locale;
|
|
40
|
+
focusOnError?: boolean;
|
|
41
|
+
lockForm?: boolean;
|
|
42
|
+
errorClass?: string;
|
|
43
|
+
successClass?: string;
|
|
44
|
+
pendingClass?: string;
|
|
45
|
+
debug?: boolean;
|
|
46
|
+
verbose?: boolean;
|
|
47
|
+
errorContainer?: HTMLElement | string;
|
|
48
|
+
collectAll?: boolean;
|
|
49
|
+
debounceMs?: number;
|
|
50
|
+
}
|
|
51
|
+
export interface FieldState {
|
|
52
|
+
field: HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement;
|
|
53
|
+
rules: Rule[];
|
|
54
|
+
config: FieldConfig;
|
|
55
|
+
errors: ValidationError[];
|
|
56
|
+
validated: boolean;
|
|
57
|
+
pending: boolean;
|
|
58
|
+
abortController: AbortController | null;
|
|
59
|
+
debounceTimer: ReturnType<typeof setTimeout> | null;
|
|
60
|
+
}
|
|
61
|
+
export interface GroupState {
|
|
62
|
+
elements: (HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement)[];
|
|
63
|
+
rules: Rule[];
|
|
64
|
+
config: GroupConfig;
|
|
65
|
+
errors: ValidationError[];
|
|
66
|
+
validated: boolean;
|
|
67
|
+
pending: boolean;
|
|
68
|
+
abortController: AbortController | null;
|
|
69
|
+
debounceTimer: ReturnType<typeof setTimeout> | null;
|
|
70
|
+
}
|
|
71
|
+
export type SuccessCallback = () => void;
|
|
72
|
+
export type FailCallback = (errors: ValidationError[]) => void;
|
|
73
|
+
export type ValidateCallback = (result: {
|
|
74
|
+
valid: boolean;
|
|
75
|
+
errors: ValidationError[];
|
|
76
|
+
fields: Record<string, ValidationError[]>;
|
|
77
|
+
groups: Record<string, ValidationError[]>;
|
|
78
|
+
}) => void;
|
|
79
|
+
export interface FormGuardInstance {
|
|
80
|
+
addField(fieldSelector: string, rules: Rule[], config?: FieldConfig): FormGuardInstance;
|
|
81
|
+
removeField(fieldSelector: string): FormGuardInstance;
|
|
82
|
+
addGroup(groupSelector: string, rules: Rule[], config?: GroupConfig): FormGuardInstance;
|
|
83
|
+
removeGroup(groupSelector: string): FormGuardInstance;
|
|
84
|
+
validate(forceRevalidation?: boolean): Promise<boolean>;
|
|
85
|
+
validateField(fieldSelector: string): Promise<boolean>;
|
|
86
|
+
validateGroup(groupSelector: string): Promise<boolean>;
|
|
87
|
+
revalidate(): Promise<boolean>;
|
|
88
|
+
revalidateField(fieldSelector: string): Promise<boolean>;
|
|
89
|
+
revalidateGroup(groupSelector: string): Promise<boolean>;
|
|
90
|
+
clearErrors(): FormGuardInstance;
|
|
91
|
+
refresh(): FormGuardInstance;
|
|
92
|
+
destroy(): void;
|
|
93
|
+
setLocale(locale: Locale): FormGuardInstance;
|
|
94
|
+
onSuccess(callback: SuccessCallback): FormGuardInstance;
|
|
95
|
+
onFail(callback: FailCallback): FormGuardInstance;
|
|
96
|
+
onValidate(callback: ValidateCallback): FormGuardInstance;
|
|
97
|
+
getErrors(): ValidationError[];
|
|
98
|
+
getFieldErrors(fieldSelector: string): ValidationError[];
|
|
99
|
+
getGroupErrors(groupSelector: string): ValidationError[];
|
|
100
|
+
isFieldPending(fieldSelector: string): boolean;
|
|
101
|
+
isPending(): boolean;
|
|
102
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ajosecortes/form-guard",
|
|
3
|
+
"version": "1.0.2",
|
|
4
|
+
"description": "Production-minded form validation library — framework-agnostic, TypeScript-first",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/form-guard.cjs",
|
|
7
|
+
"module": "./dist/form-guard.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/form-guard.js",
|
|
13
|
+
"require": "./dist/form-guard.cjs"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist"
|
|
18
|
+
],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"dev": "vite --open",
|
|
21
|
+
"build": "vite build && tsc --emitDeclarationOnly",
|
|
22
|
+
"test": "vitest run",
|
|
23
|
+
"test:watch": "vitest",
|
|
24
|
+
"lint": "tsc --noEmit"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"@types/node": "^22.0.0",
|
|
28
|
+
"jsdom": "^25.0.0",
|
|
29
|
+
"typescript": "^5.6.0",
|
|
30
|
+
"vite": "^6.0.0",
|
|
31
|
+
"vitest": "^3.0.0"
|
|
32
|
+
},
|
|
33
|
+
"license": "MIT"
|
|
34
|
+
}
|