@samline/forms 2.0.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/dist/browser/global.global.js +14 -4
- package/dist/browser/global.global.js.map +1 -1
- package/dist/index.cjs +14 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +14 -4
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -42,10 +42,10 @@ Requires Node 20+ when bundling. Runtime target is ES2020.
|
|
|
42
42
|
Use the browser build when you do not have a bundler and need to run the package directly in HTML, Shopify, WordPress, or any traditional template.
|
|
43
43
|
|
|
44
44
|
```html
|
|
45
|
-
<script src="https://unpkg.com/@samline/forms@2.
|
|
45
|
+
<script src="https://unpkg.com/@samline/forms@2.1.0/dist/browser/global.global.js"></script>
|
|
46
46
|
```
|
|
47
47
|
|
|
48
|
-
> Pin the version in production. Replace `2.
|
|
48
|
+
> Pin the version in production. Replace `2.1.0` with the version you ship.
|
|
49
49
|
|
|
50
50
|
The browser bundle exposes a single global: `window.Forms`.
|
|
51
51
|
|
|
@@ -55,7 +55,7 @@ The browser bundle exposes a single global: `window.Forms`.
|
|
|
55
55
|
<button type="submit">Send</button>
|
|
56
56
|
</form>
|
|
57
57
|
|
|
58
|
-
<script src="https://unpkg.com/@samline/forms@2.
|
|
58
|
+
<script src="https://unpkg.com/@samline/forms@2.1.0/dist/browser/global.global.js"></script>
|
|
59
59
|
<script>
|
|
60
60
|
const contactForm = window.Forms.newForm({ id: 'contact-form' })
|
|
61
61
|
|
|
@@ -101,6 +101,10 @@ var Forms = (() => {
|
|
|
101
101
|
`[name="${cssEscape(`${fieldName}_raw`)}"]`
|
|
102
102
|
);
|
|
103
103
|
if (byName) return byName;
|
|
104
|
+
const byNameOld = form2.querySelector(
|
|
105
|
+
`[name="${cssEscape(`${fieldName}Raw`)}"]`
|
|
106
|
+
);
|
|
107
|
+
if (byNameOld) return byNameOld;
|
|
104
108
|
return null;
|
|
105
109
|
};
|
|
106
110
|
var ensureRawMirror = (form2, fieldName) => {
|
|
@@ -108,7 +112,7 @@ var Forms = (() => {
|
|
|
108
112
|
if (existing) return existing;
|
|
109
113
|
const input = document.createElement("input");
|
|
110
114
|
input.type = "hidden";
|
|
111
|
-
input.name = `${fieldName}
|
|
115
|
+
input.name = `${fieldName}_raw`;
|
|
112
116
|
input.setAttribute("aria-hidden", "true");
|
|
113
117
|
input.tabIndex = -1;
|
|
114
118
|
input.setAttribute(FORMATTER_RAW_ATTRIBUTE, fieldName);
|
|
@@ -239,6 +243,7 @@ var Forms = (() => {
|
|
|
239
243
|
var buildHandler = (field, mirror, formatFn, formatType, formatOptions, mirrorFieldName) => {
|
|
240
244
|
const handler = (event) => {
|
|
241
245
|
if (!field.isConnected) return;
|
|
246
|
+
if (event.target !== field) return;
|
|
242
247
|
const rawInput = event.target.value;
|
|
243
248
|
const { formatted, raw } = formatFn(rawInput, formatType, formatOptions);
|
|
244
249
|
if (!formatted && !raw) {
|
|
@@ -268,8 +273,8 @@ var Forms = (() => {
|
|
|
268
273
|
if (writable.length === 0) continue;
|
|
269
274
|
for (const field of writable) {
|
|
270
275
|
const existing = bucket.get(fieldName);
|
|
271
|
-
const mirrorName = config.rawField ?? `${fieldName}
|
|
272
|
-
let mirror = findRawMirror(state2.element,
|
|
276
|
+
const mirrorName = config.rawField ?? `${fieldName}_raw`;
|
|
277
|
+
let mirror = findRawMirror(state2.element, fieldName) ?? null;
|
|
273
278
|
const mirrorIsOwned = !mirror;
|
|
274
279
|
if (!mirror) mirror = ensureRawMirror(state2.element, fieldName);
|
|
275
280
|
if (mirror && mirror.name !== mirrorName) mirror.name = mirrorName;
|
|
@@ -282,6 +287,11 @@ var Forms = (() => {
|
|
|
282
287
|
mirrorName: trackerMirrorName,
|
|
283
288
|
mirrorIsOwned
|
|
284
289
|
});
|
|
290
|
+
const current = field.value;
|
|
291
|
+
if (current !== "") {
|
|
292
|
+
const { formatted, raw } = formatter.format(current, formatType, formatOptions);
|
|
293
|
+
applyFormattedValue(field, mirror, formatted, raw);
|
|
294
|
+
}
|
|
285
295
|
continue;
|
|
286
296
|
}
|
|
287
297
|
const handler = buildHandler(
|
|
@@ -308,7 +318,7 @@ var Forms = (() => {
|
|
|
308
318
|
if (initial !== "") {
|
|
309
319
|
const { formatted, raw } = formatter.format(initial, formatType, formatOptions);
|
|
310
320
|
applyFormattedValue(field, mirror, formatted, raw);
|
|
311
|
-
} else if (mirror && mirror.value !== "") {
|
|
321
|
+
} else if (mirrorIsOwned && mirror && mirror.value !== "") {
|
|
312
322
|
mirror.value = "";
|
|
313
323
|
}
|
|
314
324
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/browser/global.ts","../../src/api/append.ts","../../src/api/auto-submit.ts","../../src/api/clear-errors.ts","../../src/core/format-helpers.ts","../../src/core/formatter-loader.ts","../../src/api/format.ts","../../src/api/destroy.ts","../../src/api/disable-auto-submit.ts","../../src/core/serialize.ts","../../src/api/get-data.ts","../../src/api/get-field.ts","../../src/core/state.ts","../../src/api/get-state.ts","../../src/core/dom.ts","../../src/api/get-value.ts","../../src/api/observe.ts","../../src/api/on-submit.ts","../../src/api/prefill.ts","../../src/api/reset.ts","../../src/api/validate.ts","../../src/api/revalidate.ts","../../src/api/set-errors.ts","../../src/api/set-value.ts","../../src/api/subscribe.ts","../../src/api/unwatch.ts","../../src/api/watch.ts","../../src/core/validation.ts","../../src/core/controller.ts","../../src/api/form.ts"],"sourcesContent":["// Browser entrypoint. Exposes a minimal surface as a single global\n// accessible via `window.Forms` (or `globalThis.Forms`).\n//\n// Public shape:\n// - Forms.form(target, options)\n// - Forms.newForm({ id, options })\n// - Forms.destroyForm(id)\n// - Forms.available (registry keyed by form id)\n\nimport { form } from '../api/form'\nimport type {\n FormController,\n FormControllerOptions,\n FormTarget\n} from '../core/types'\n\nexport interface NewFormInput {\n id: string\n options?: FormControllerOptions\n}\n\nexport interface FormsAvailable {\n [id: string]: FormController\n}\n\nexport interface FormsApi {\n form: (\n target: FormTarget,\n options?: FormControllerOptions\n ) => FormController\n newForm: (input: NewFormInput) => FormController | undefined\n destroyForm: (id: string) => void\n available: FormsAvailable\n}\n\nconst available: FormsAvailable = {}\n\nconst newForm = (input: NewFormInput): FormController | undefined => {\n const { id, options } = input\n if (!id) {\n console.error('Form ID is required')\n return\n }\n const controller = form(id, { ...options })\n available[id] = controller\n return controller\n}\n\nconst destroyForm = (id: string): void => {\n if (!id) {\n console.error('Form ID is required')\n return\n }\n const controller = available[id]\n if (controller) {\n controller.destroy()\n delete available[id]\n } else {\n console.warn(`Form with ID ${id} not found`)\n }\n}\n\nconst Forms: FormsApi = {\n form,\n newForm,\n destroyForm,\n available\n}\n\ndeclare global {\n interface Window {\n Forms: FormsApi\n }\n}\n\nif (typeof globalThis !== 'undefined') {\n ;(globalThis as typeof globalThis & { Forms: FormsApi }).Forms = Forms\n}\n\nexport default Forms","// api/append.ts\n// Appends DOM content to the bound form. If a class is provided, removes\n// any existing node with that class first to avoid duplicates.\n// Returns the created node or null when no element is bound.\n\nimport type { FormControllerHelpers, FormControllerState } from '../core/state'\nimport type { AppendContentOptions } from '../core/types'\n\nexport const createAppend =\n (state: FormControllerState, helpers: FormControllerHelpers) =>\n ({ tag, content, class: className, atStart = false }: AppendContentOptions) => {\n if (!state.element) return null\n\n helpers.clearFieldCache()\n\n if (className) {\n const classSelector = className.trim().split(/\\s+/)[0]\n const existing = classSelector\n ? state.element.querySelector(`.${classSelector}`)\n : null\n existing?.remove()\n }\n\n const node = document.createElement(tag)\n if (className) node.className = className\n node.innerHTML = content\n\n if (atStart && state.element.firstChild) {\n state.element.insertBefore(node, state.element.firstChild)\n } else {\n state.element.appendChild(node)\n }\n\n return node\n }\n","// api/auto-submit.ts\n// Enables native auto-submit. Pass `true` to enable, `false` to disable,\n// or an { debounce } object to delay.\n\nimport type { FormControllerHelpers, FormControllerState } from '../core/state'\n\nexport const createAutoSubmit =\n (state: FormControllerState, helpers: FormControllerHelpers) =>\n (next: boolean | { debounce?: number } = true) => {\n state.autoSubmitEnabled = next !== false\n state.autoSubmitDebounce =\n typeof next === 'object' && next ? next.debounce ?? 0 : 0\n helpers.notifySubscribers()\n return state.api!\n }\n","// api/clear-errors.ts\n// Clears manual errors. With no args, clears every manual error. With a\n// list, clears only those fields.\n\nimport type { FormControllerHelpers, FormControllerState } from '../core/state'\n\nexport const createClearErrors =\n (state: FormControllerState, helpers: FormControllerHelpers) =>\n (fields?: string[]) => {\n if (!fields) {\n state.manualErrors = {}\n helpers.syncVisualState()\n helpers.notifySubscribers()\n return state.api!\n }\n\n for (const field of fields) {\n delete state.manualErrors[field]\n }\n helpers.syncVisualState(fields)\n helpers.notifySubscribers()\n return state.api!\n }\n","// Low-level helpers used by `api/format.ts`:\n// - DOM cursor tracking equivalent to cleave.js' `getNextCursorPosition`\n// (so typing/backspacing inside formatted inputs keeps the caret\n// in a sensible place, even when delimiters are inserted or removed).\n// - Raw-mirror lookup & creation: each formatted input gets a hidden\n// sibling (`type=\"hidden\"`) that holds the backend-ready `raw`\n// value. The mirror is created on first `format()` call and reused\n// on subsequent calls so re-binding the controller does not duplicate\n// hidden inputs.\n//\n// The helpers here are pure DOM utilities — they do not know about the\n// controller state.\n\nimport type { FormFieldElement } from './types'\n\nexport const FORMATTER_RAW_ATTRIBUTE = 'data-formatter-raw-for'\n\n// Resolve a field by name (used to read the original input that owns the\n// raw mirror). Returns the first matching element or `null`.\nexport const getFormatterField = (\n form: HTMLFormElement,\n name: string\n): HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement | null => {\n const selector = `[name=\"${cssEscape(name)}\"]`\n const node = form.querySelector(selector)\n if (\n node instanceof HTMLInputElement ||\n node instanceof HTMLTextAreaElement ||\n node instanceof HTMLSelectElement\n ) {\n return node\n }\n return null\n}\n\n// Minimal CSS.escape polyfill for selectors that may contain special\n// characters (`[1].phone`, `email[primary]`, etc.).\nconst cssEscape = (value: string): string => {\n if (typeof CSS !== 'undefined' && typeof CSS.escape === 'function') {\n return CSS.escape(value)\n }\n return value.replace(/([!\"#$%&'()*+,./:;<=>?@\\[\\\\\\]^`{|}~])/g, '\\\\$1')\n}\n\n// Find the hidden input that mirrors `raw` for the given field name.\n// Returns the existing element or `null`.\nexport const findRawMirror = (\n form: HTMLFormElement,\n fieldName: string\n): HTMLInputElement | null => {\n const byAttr = form.querySelector<HTMLInputElement>(\n `[${FORMATTER_RAW_ATTRIBUTE}=\"${cssEscape(fieldName)}\"]`\n )\n if (byAttr) return byAttr\n\n // Backwards-compatible lookup: existing deployments may have used\n // `<input name=\"<field>_raw\">` to carry the raw value. Reuse it.\n const byName = form.querySelector<HTMLInputElement>(\n `[name=\"${cssEscape(`${fieldName}_raw`)}\"]`\n )\n if (byName) return byName\n\n return null\n}\n\n// Create a hidden input that will mirror the raw value of `fieldName`.\n// Idempotent: if a mirror already exists it is returned as-is.\nexport const ensureRawMirror = (\n form: HTMLFormElement,\n fieldName: string\n): HTMLInputElement => {\n const existing = findRawMirror(form, fieldName)\n if (existing) return existing\n\n const input = document.createElement('input')\n input.type = 'hidden'\n input.name = `${fieldName}Raw`\n input.setAttribute('aria-hidden', 'true')\n input.tabIndex = -1\n input.setAttribute(FORMATTER_RAW_ATTRIBUTE, fieldName)\n form.appendChild(input)\n return input\n}\n\n// Remove every raw mirror owned by `format()`. Mirrors created by this\n// controller carry the `data-formatter-raw-for` attribute; mirrors that\n// existed before are left untouched.\nexport const removeOwnedRawMirrors = (form: HTMLFormElement): void => {\n form\n .querySelectorAll<HTMLInputElement>(`[${FORMATTER_RAW_ATTRIBUTE}]`)\n .forEach(node => node.remove())\n}\n\n// Write `formatted` into the visible field and `raw` into the hidden\n// mirror, preserving the cursor as best as possible.\n//\n// Returns the new caret position so callers can debug or run additional\n// assertions. The function avoids triggering `MutationObserver` cascades\n// for the hidden mirror by setting `.value` directly (the controller's\n// own delegated handler ignores hidden inputs via `isFieldElement`).\nexport const applyFormattedValue = (\n visible: HTMLInputElement | HTMLTextAreaElement,\n mirror: HTMLInputElement | null,\n formatted: string,\n raw: string\n): number => {\n // Capture the caret in the **raw** input the user just typed — this\n // is what cleave's `getNextCursorPosition` does. Using the formatted\n // previous value would lose information when the user edits in the\n // middle of a masked field (deletion adjacent to delimiters, etc.).\n const rawInput = visible.value\n const caret = clamp(\n visible.selectionStart ?? rawInput.length,\n 0,\n rawInput.length\n )\n\n if (rawInput === formatted) {\n if (mirror && mirror.value !== raw) mirror.value = raw\n return caret\n }\n\n visible.value = formatted\n\n if (mirror) mirror.value = raw\n\n const newCaret = computeCursorPosition(caret, rawInput, formatted)\n restoreCursor(visible, newCaret)\n return newCaret\n}\n\nconst clamp = (value: number, min: number, max: number): number =>\n Math.max(min, Math.min(max, value))\n\n// `computeCursorPosition` is a cleave-style implementation:\n// - count \"non-format\" characters (digits / letters) between the caret\n// and the start of the **raw input the user just typed**, then\n// locate that same logical index in the new formatted value\n// (jumping over inserted delimiters).\n// - when the caret is at (or past) the end of the raw input, place\n// it at the end of the formatted value.\n//\n// The algorithm intentionally avoids any cleave-zen dependency so this\n// file is pure DOM arithmetic.\nexport const computeCursorPosition = (\n caretPos: number,\n rawInput: string,\n newValue: string\n): number => {\n if (newValue.length === 0) return 0\n\n // Caret at (or past) the end of the raw input — place at new end.\n if (caretPos >= rawInput.length) return newValue.length\n\n // Caret within the raw input: count non-format characters to its\n // LEFT (i.e. how many real characters are before the caret) and\n // find that same count in the new formatted value.\n const targetNonFormat = countNonFormat(rawInput.slice(0, caretPos))\n\n if (targetNonFormat === 0) {\n // Skip leading delimiters / prefix symbols (`$`, `+`, …) so the\n // caret lands at the first real character of the new value.\n let i = 0\n while (i < newValue.length && isFormatChar(newValue[i]!)) i += 1\n return i\n }\n\n let seen = 0\n for (let i = 0; i < newValue.length; i += 1) {\n if (!isFormatChar(newValue[i]!)) {\n seen += 1\n if (seen === targetNonFormat) {\n return Math.min(i + 1, newValue.length)\n }\n }\n }\n\n return newValue.length\n}\n\nconst countNonFormat = (value: string): number => {\n let count = 0\n for (const char of value) {\n if (!isFormatChar(char)) count += 1\n }\n return count\n}\n\n// \"Format characters\" are delimiters, prefix symbols, and other\n// non-alphanumeric separators that the formatter may insert or remove.\n// We deliberately keep this conservative — anything that is not a digit\n// or ASCII letter counts as a format character.\nexport const isFormatChar = (char: string): boolean => {\n if (!char) return false\n if (char >= '0' && char <= '9') return false\n if ((char >= 'a' && char <= 'z') || (char >= 'A' && char <= 'Z')) return false\n return true\n}\n\nconst restoreCursor = (\n field: HTMLInputElement | HTMLTextAreaElement,\n position: number\n): void => {\n const max = field.value.length\n const safe = clamp(position, 0, max)\n\n const apply = () => {\n try {\n field.setSelectionRange(safe, safe)\n } catch {\n // Some input types (number, email) throw on setSelectionRange.\n // Ignore — the caret just stays where the browser put it.\n }\n }\n\n // Android keyboard fix: defer one frame so the IME catches up before\n // we re-position the caret. Mirrors cleave.js' `isAndroid` branch.\n if (typeof navigator !== 'undefined' && /Android/i.test(navigator.userAgent)) {\n if (typeof requestAnimationFrame === 'function') {\n requestAnimationFrame(apply)\n } else {\n setTimeout(apply, 1)\n }\n return\n }\n\n apply()\n}\n\n// `getFormatterField` re-exported above is the canonical entry point,\n// but it is intentionally restricted to controller-owned fields. This\n// looser helper accepts a wider set of inputs for test fixtures.\nexport const isFormatterFieldElement = (node: Element): node is FormFieldElement =>\n node instanceof HTMLInputElement ||\n node instanceof HTMLTextAreaElement ||\n node instanceof HTMLSelectElement","// Lazy singleton loader for the optional `@samline/formatter` peer.\n// The formatter is imported dynamically so the package stays usable\n// even when the peer is not installed: callers receive `null` and a\n// single console.error explaining the missing dependency.\n//\n// The first failure is cached so the warning is printed exactly once\n// per process, not on every `format(...)` call.\n\nexport interface FormatterModule {\n format: (\n value: unknown,\n formatType: string,\n options?: Record<string, unknown>\n ) => { formatted: string; raw: string; type: string }\n FORMAT_TYPES?: readonly string[]\n isFormatType?: (value: unknown) => boolean\n regex?: Record<string, { pattern: RegExp; errorMessage: string }>\n}\n\ntype LoaderState =\n | { status: 'pending'; promise: Promise<FormatterModule | null> }\n | { status: 'resolved'; module: FormatterModule | null }\n\nlet state: LoaderState | null = null\n\nconst MISSING_MESSAGE =\n '[samline/forms] The `format()` and `formatAll()` methods require the ' +\n '@samline/formatter package. Install it with: npm i @samline/formatter ' +\n '(or pnpm/bun/yarn equivalent).'\n\nlet warned = false\n\nconst warnOnce = () => {\n if (warned) return\n warned = true\n console.error(MISSING_MESSAGE)\n}\n\nexport const loadFormatter = (): Promise<FormatterModule | null> => {\n if (state && state.status === 'resolved') {\n if (!state.module) warnOnce()\n return Promise.resolve(state.module)\n }\n\n if (state && state.status === 'pending') {\n return state.promise\n }\n\n const promise = (async () => {\n try {\n // Dynamic import keeps the optional peer out of the build graph.\n // The `/* @vite-ignore */` comment prevents bundlers (vite/rollup)\n // from trying to pre-bundle the optional peer.\n const mod = (await import(\n /* @vite-ignore */ '@samline/formatter'\n )) as FormatterModule\n state = { status: 'resolved', module: mod }\n return mod\n } catch {\n warnOnce()\n state = { status: 'resolved', module: null }\n return null\n }\n })()\n\n state = { status: 'pending', promise }\n return promise\n}\n\n// Test-only helper: reset the cached loader so unit tests can swap the\n// dynamic import between \"installed\" and \"missing\" scenarios.\nexport const __resetFormatterLoaderForTests = (): void => {\n state = null\n warned = false\n}\n\n// Test-only helper: inject a formatter implementation directly,\n// bypassing the dynamic import. Pass `null` to simulate the\n// \"peer not installed\" branch.\nexport const __setFormatterModuleForTests = (\n module: FormatterModule | null\n): void => {\n state = { status: 'resolved', module }\n}","// api/format.ts\n// Applies the optional `@samline/formatter` peer to one or many form\n// fields, with cleave-style cursor tracking and an auto-managed hidden\n// raw mirror.\n//\n// Public surface lives on `FormController` as `format()` and\n// `formatAll()`. Both are chainable and both behave identically — the\n// alias exists only to read naturally when the caller wants to apply\n// the same configuration to several inputs.\n//\n// When `@samline/formatter` is not installed the methods log a single\n// `console.error` (via `loadFormatter`) and return the controller\n// unchanged so the rest of the app keeps working.\n\nimport type { FieldFormatConfig, FormController, FormFieldElement } from '../core/types'\nimport {\n applyFormattedValue,\n ensureRawMirror,\n findRawMirror,\n FORMATTER_RAW_ATTRIBUTE\n} from '../core/format-helpers'\nimport { loadFormatter } from '../core/formatter-loader'\nimport type { FormControllerHelpers, FormControllerState } from '../core/state'\n\n// Listeners added on demand; tracked here so a future `unformat(field)`\n// (out of scope for v1) can detach them without trawling `state.listeners`.\ntype FormatEntry = {\n fields: Set<HTMLInputElement | HTMLTextAreaElement>\n mirrorName: string\n handler: (event: Event) => void\n mirrorIsOwned: boolean\n}\n\nconst registry = new WeakMap<FormControllerState, Map<string, FormatEntry>>()\n\nconst getRegistry = (state: FormControllerState): Map<string, FormatEntry> => {\n let bucket = registry.get(state)\n if (!bucket) {\n bucket = new Map<string, FormatEntry>()\n registry.set(state, bucket)\n }\n return bucket\n}\n\nconst resolveFieldNames = (config: FieldFormatConfig): string[] =>\n Array.isArray(config.field) ? config.field : [config.field]\n\nconst addListener = (\n state: FormControllerState,\n target: EventTarget,\n type: string,\n handler: EventListener\n): void => {\n target.addEventListener(type, handler)\n state.listeners.push({ element: target, type, handler })\n}\n\nconst buildHandler = (\n field: HTMLInputElement | HTMLTextAreaElement,\n mirror: HTMLInputElement | null,\n formatFn: NonNullable<Awaited<ReturnType<typeof loadFormatter>>>['format'],\n formatType: FieldFormatConfig['type'],\n formatOptions: Record<string, unknown> | undefined,\n mirrorFieldName: string\n): ((event: Event) => void) => {\n const handler = (event: Event) => {\n if (!field.isConnected) return\n const rawInput = (event.target as HTMLInputElement | HTMLTextAreaElement).value\n const { formatted, raw } = formatFn(rawInput, formatType, formatOptions)\n\n if (!formatted && !raw) {\n field.value = ''\n if (mirror && mirror.value !== '') mirror.value = ''\n return\n }\n\n applyFormattedValue(field, mirror, formatted, raw)\n // Keep the registry mirror pointer alive for in-place cleanup.\n void mirrorFieldName\n }\n return handler\n}\n\n// Internal: core apply routine shared by `format()` and `formatAll()`.\n// Returns the controller to preserve chainability even on the missing\n// peer path.\nconst applyFormat = async (\n state: FormControllerState,\n helpers: FormControllerHelpers,\n config: FieldFormatConfig\n): Promise<void> => {\n if (!state.element || !state.api) return\n\n const formatter = await loadFormatter()\n if (!formatter) return\n\n const formatType = config.type\n const formatOptions = config.options\n const fieldNames = resolveFieldNames(config)\n const bucket = getRegistry(state)\n\n for (const fieldName of fieldNames) {\n const fields = helpers.getFieldsByName(fieldName) as FormFieldElement[]\n if (fields.length === 0) continue\n\n // Filter to text-like inputs — the formatter does not work on\n // checkboxes, radios, files, etc.\n const writable = fields.filter(\n (f): f is HTMLInputElement | HTMLTextAreaElement =>\n (f instanceof HTMLInputElement &&\n f.type !== 'checkbox' &&\n f.type !== 'radio' &&\n f.type !== 'file' &&\n f.type !== 'submit' &&\n f.type !== 'button') ||\n f instanceof HTMLTextAreaElement\n )\n\n if (writable.length === 0) continue\n\n for (const field of writable) {\n // Idempotency: a second `format()` call for the same field\n // refreshes the configuration in `state.formattedFields` but\n // does not double-bind listeners or duplicate the hidden mirror.\n const existing = bucket.get(fieldName)\n const mirrorName = config.rawField ?? `${fieldName}Raw`\n let mirror = findRawMirror(state.element, mirrorName) ?? null\n const mirrorIsOwned = !mirror\n if (!mirror) mirror = ensureRawMirror(state.element, fieldName)\n // Sync the mirror's `name` attribute when the user overrides it\n // via `config.rawField` (default `<field>Raw`).\n if (mirror && mirror.name !== mirrorName) mirror.name = mirrorName\n const trackerMirrorName = mirror?.name ?? mirrorName\n\n if (existing) {\n // Refresh the stored config so `destroy()` knows the entry is\n // still active, but do not re-attach listeners.\n existing.mirrorName = trackerMirrorName\n existing.mirrorIsOwned = mirrorIsOwned\n state.formattedFields.set(fieldName, {\n config,\n mirrorName: trackerMirrorName,\n mirrorIsOwned\n })\n continue\n }\n\n const handler = buildHandler(\n field,\n mirror,\n formatter.format,\n formatType,\n formatOptions,\n trackerMirrorName\n )\n\n addListener(state, state.element, 'input', handler)\n bucket.set(fieldName, {\n fields: new Set([field]),\n mirrorName: trackerMirrorName,\n handler,\n mirrorIsOwned\n })\n state.formattedFields.set(fieldName, {\n config,\n mirrorName: trackerMirrorName,\n mirrorIsOwned\n })\n\n // Apply the formatter to the current value so pre-filled data\n // (e.g. coming from `setValue`, `prefill`, or server-rendered\n // HTML) is normalised immediately and the hidden mirror is\n // populated without waiting for a user keystroke.\n const initial = field.value\n if (initial !== '') {\n const { formatted, raw } = formatter.format(initial, formatType, formatOptions)\n applyFormattedValue(field, mirror, formatted, raw)\n } else if (mirror && mirror.value !== '') {\n mirror.value = ''\n }\n }\n }\n}\n\nexport const createFormat =\n (state: FormControllerState, helpers: FormControllerHelpers) =>\n (config: FieldFormatConfig): FormController => {\n if (state.isDestroyed) return state.api!\n if (!state.element) return state.api!\n\n // Fire and forget: the loader surfaces a single console.error if\n // the peer is missing, then resolves with `null`.\n void applyFormat(state, helpers, config)\n\n return state.api!\n }\n\nexport const createFormatAll = createFormat\n\n// Cleanup hook consumed by `api/destroy.ts`. Removes every listener\n// registered through this module and drops the owned raw mirrors that\n// were created during the controller's lifetime. Mirrors that already\n// existed in the DOM (i.e. `mirrorIsOwned === false`) are left alone.\nexport const cleanupFormatRegistry = (state: FormControllerState): void => {\n if (!state.element) {\n registry.delete(state)\n return\n }\n\n const bucket = registry.get(state)\n if (!bucket) {\n state.element\n .querySelectorAll<HTMLInputElement>(`[${FORMATTER_RAW_ATTRIBUTE}]`)\n .forEach(node => node.remove())\n return\n }\n\n for (const [fieldName, entry] of bucket) {\n state.element.removeEventListener('input', entry.handler)\n if (entry.mirrorIsOwned) {\n const mirror = findRawMirror(state.element, entry.mirrorName)\n mirror?.remove()\n }\n bucket.delete(fieldName)\n state.formattedFields.delete(fieldName)\n }\n\n // Catch any leftover owned mirrors (defensive — should not happen\n // because every `applyFormat` path registers in the bucket, but the\n // second pass guarantees no orphans escape `destroy()`).\n state.element\n .querySelectorAll<HTMLInputElement>(`[${FORMATTER_RAW_ATTRIBUTE}]`)\n .forEach(node => node.remove())\n}","// api/destroy.ts\n// Tears down the controller: removes DOM listeners, disconnects the\n// mutation observer, clears caches, and resets internal state.\n// Safe to call multiple times.\n\nimport { cleanupFormatRegistry } from './format'\nimport type { FormControllerHelpers, FormControllerState } from '../core/state'\n\nexport const createDestroy =\n (state: FormControllerState, _helpers: FormControllerHelpers) =>\n () => {\n if (state.isDestroyed) return\n\n state.isDestroyed = true\n for (const listener of state.listeners) {\n listener.element.removeEventListener(listener.type, listener.handler)\n }\n state.listeners.length = 0\n\n if (state.autoSubmitTimer) {\n clearTimeout(state.autoSubmitTimer)\n state.autoSubmitTimer = null\n }\n\n state.mutationObserver?.disconnect()\n state.mutationObserver = null\n state.watchedFields.clear()\n state.subscribers.clear()\n state.submitHandlers.clear()\n state.fieldCache.clear()\n state.manualErrors = {}\n state.validationErrors = {}\n\n // Detach format listeners and drop the hidden raw mirrors that\n // `format()` created for this controller. Mirrors that pre-existed\n // in the DOM (i.e. not owned by us) are preserved.\n cleanupFormatRegistry(state)\n }\n","// api/disable-auto-submit.ts\n// Disables autoSubmit and cancels any pending debounce timer.\n\nimport type { FormControllerHelpers, FormControllerState } from '../core/state'\n\nexport const createDisableAutoSubmit =\n (state: FormControllerState, helpers: FormControllerHelpers) =>\n () => {\n state.autoSubmitEnabled = false\n if (state.autoSubmitTimer) {\n clearTimeout(state.autoSubmitTimer)\n state.autoSubmitTimer = null\n }\n helpers.notifySubscribers()\n return state.api!\n }\n","// Pure serialization helpers for HTMLFormElement.\n// No DOM mutation, no controller state — safe to tree-shake into any bundle.\n\nimport type { SerializedFormResult, SerializedFormValue } from './types'\n\nconst isEmptyFile = (value: FormDataEntryValue): value is File =>\n value instanceof File && value.size === 0 && value.name === ''\n\nconst appendValue = (\n data: Record<string, SerializedFormValue>,\n key: string,\n value: FormDataEntryValue\n): void => {\n const current = data[key]\n if (current === undefined) {\n data[key] = value\n return\n }\n if (Array.isArray(current)) {\n current.push(value)\n return\n }\n data[key] = [current, value]\n}\n\nexport const parseFormData = (formElement: HTMLFormElement): SerializedFormResult => {\n const raw = new FormData(formElement)\n const formData = new FormData()\n const data: Record<string, SerializedFormValue> = {}\n\n raw.forEach((value, key) => {\n if (isEmptyFile(value)) return\n formData.append(key, value)\n appendValue(data, key, value)\n })\n\n return { data, formData }\n}\n","// api/get-data.ts\n// Returns the form serialized as both a plain object and a fresh\n// FormData instance. Empty file inputs are filtered out.\n\nimport type { FormControllerHelpers, FormControllerState } from '../core/state'\nimport type { SerializedFormResult } from '../core/types'\nimport { parseFormData } from '../core/serialize'\n\nexport const createGetData =\n (_state: FormControllerState, _helpers: FormControllerHelpers) =>\n (): SerializedFormResult => {\n if (!_state.element) return { data: {}, formData: new FormData() }\n return parseFormData(_state.element)\n }\n","// api/get-field.ts\n// Returns the underlying DOM field(s) for a given name:\n// - null when no field matches\n// - a single element when there's exactly one\n// - an array when there are multiple (radios, checkbox groups)\n\nimport type { FormControllerHelpers, FormControllerState } from '../core/state'\nimport type { FormFieldElement } from '../core/types'\n\nexport const createGetField =\n (state: FormControllerState, helpers: FormControllerHelpers) =>\n (name: string): FormFieldElement | FormFieldElement[] | null => {\n const fields = helpers.getFieldsByName(name)\n if (fields.length === 0) return null\n return fields.length === 1 ? fields[0]! : fields\n }\n","// Shared mutable state for the controller plus the small pure helpers used\n// by both core utilities and the api method factories.\n\nimport type {\n FieldFormatConfig,\n FormController,\n FormControllerOptions,\n FormErrors,\n FormFieldElement,\n FormFieldWatcher,\n FormStateListener,\n FormStateSnapshot,\n FormValues,\n ValidationResult,\n ValidationSchema,\n VisualAttributes\n} from './types'\n\nexport interface FormControllerState {\n element: HTMLFormElement | null\n options: FormControllerOptions\n attributes: Required<VisualAttributes>\n validators: ValidationSchema\n watchedFields: Map<string, Set<FormFieldWatcher>>\n subscribers: Set<FormStateListener>\n submitHandlers: Set<{\n callback: import('./types').FormSubmitHandler\n preventDefault: boolean\n }>\n fieldCache: Map<string, FormFieldElement[]>\n manualErrors: FormErrors\n validationErrors: FormErrors\n isValidated: boolean\n autoSubmitEnabled: boolean\n autoSubmitDebounce: number\n submitCount: number\n autoSubmitTimer: ReturnType<typeof setTimeout> | null\n isDestroyed: boolean\n listeners: Array<{\n element: EventTarget\n type: string\n handler: EventListenerOrEventListenerObject\n }>\n mutationObserver: MutationObserver | null\n // Tracks every field that `format()` is currently formatting, so\n // re-bindings are idempotent and `destroy()` can clean up the\n // associated listeners + hidden raw mirrors.\n formattedFields: Map<\n string,\n {\n config: FieldFormatConfig\n mirrorName: string\n mirrorIsOwned: boolean\n }\n >\n // Back-reference to the assembled public controller. Populated by the\n // controller orchestrator after the api method factories are wired.\n api: FormController | null\n}\n\n// Internal helpers bound by the controller orchestrator and consumed by\n// the api method factories. Kept here so api/*.ts only depends on the\n// shape, not on the orchestrator implementation.\nexport interface FormControllerHelpers {\n notifySubscribers: () => void\n clearFieldCache: () => void\n getFieldsByName: (name: string) => FormFieldElement[]\n getTrackedFieldNames: () => string[]\n getValues: () => FormValues\n getMergedErrors: () => FormErrors\n syncVisualState: (names?: string[]) => void\n validateNames: (names?: string[]) => ValidationResult\n emitFieldWatchers: (name: string) => void\n scheduleAutoSubmit: () => void\n}\n\nexport const createEmptyFormState = (): FormStateSnapshot => ({\n values: {},\n errors: {},\n filledFields: [],\n isValid: true,\n isValidated: false,\n autoSubmit: false,\n submitCount: 0\n})\n\nexport const cloneErrors = (errors: FormErrors): FormErrors =>\n Object.fromEntries(\n Object.entries(errors).map(([field, messages]) => [field, [...messages]])\n )\n\nexport const mergeErrors = (left: FormErrors, right: FormErrors): FormErrors => {\n const merged: FormErrors = { ...left }\n for (const [field, messages] of Object.entries(right)) {\n merged[field] = merged[field] ? [...merged[field], ...messages] : [...messages]\n }\n return merged\n}\n","// api/get-state.ts\n// Builds a snapshot of values, errors, filled fields and submission\n// metadata. Pure read — does not mutate controller state.\n\nimport type { FormControllerHelpers, FormControllerState } from '../core/state'\nimport type { FormStateSnapshot } from '../core/types'\nimport { cloneErrors, createEmptyFormState } from '../core/state'\n\nexport const createGetState =\n (state: FormControllerState, helpers: FormControllerHelpers) =>\n (): FormStateSnapshot => {\n const values = helpers.getValues()\n const errors = cloneErrors(helpers.getMergedErrors())\n const filledFields = helpers.getTrackedFieldNames().filter(name => {\n const value = values[name]\n if (Array.isArray(value)) return value.length > 0\n if (typeof value === 'string') return value.trim().length > 0\n return value !== undefined\n })\n\n return {\n ...createEmptyFormState(),\n values,\n errors,\n filledFields,\n isValid: Object.keys(errors).length === 0,\n isValidated: state.isValidated,\n autoSubmit: state.autoSubmitEnabled,\n submitCount: state.submitCount\n }\n }\n","// Low-level DOM helpers used by the controller orchestrator and by api\n// methods that need direct DOM access. Pure: no controller state.\n\nimport type { FormFieldElement, FormFieldValue, FormTarget } from './types'\n\nconst isFormFieldElement = (value: Element): value is FormFieldElement =>\n value instanceof HTMLInputElement ||\n value instanceof HTMLSelectElement ||\n value instanceof HTMLTextAreaElement\n\nexport const resolveFormElement = (target: FormTarget): HTMLFormElement | null => {\n if (!target) return null\n if (target instanceof HTMLFormElement) return target\n if (typeof target === 'string') {\n const node = document.getElementById(target)\n return node instanceof HTMLFormElement ? node : null\n }\n if ('current' in target) {\n return target.current instanceof HTMLFormElement ? target.current : null\n }\n return null\n}\n\nexport const getNamedFields = (form: HTMLFormElement): FormFieldElement[] =>\n Array.from(form.querySelectorAll('input[name], select[name], textarea[name]')).filter(\n isFormFieldElement\n )\n\nexport const queryNamedFields = (\n form: HTMLFormElement,\n name: string\n): FormFieldElement[] => getNamedFields(form).filter(field => field.name === name)\n\nexport const isFieldElement = (value: Element): value is FormFieldElement =>\n isFormFieldElement(value)\n\nexport const isFieldFilled = (field: FormFieldElement): boolean => {\n if (field instanceof HTMLInputElement) {\n if (field.type === 'checkbox' || field.type === 'radio') return field.checked\n if (field.type === 'file') return Boolean(field.files && field.files.length > 0)\n return field.value.trim() !== ''\n }\n return field.value !== ''\n}\n\nexport const applyBooleanAttribute = (\n field: FormFieldElement,\n attribute: string,\n enabled: boolean\n): void => {\n if (enabled) {\n field.setAttribute(attribute, '')\n } else {\n field.removeAttribute(attribute)\n }\n}\n\nexport const readFieldValue = (fields: FormFieldElement[]): FormFieldValue => {\n if (fields.length === 0) return undefined\n const first = fields[0]\n if (!first) return undefined\n\n if (first instanceof HTMLSelectElement || first instanceof HTMLTextAreaElement) {\n return first.value\n }\n\n if (first instanceof HTMLInputElement) {\n if (first.type === 'radio') {\n const checked = fields.find(\n (f): f is HTMLInputElement =>\n f instanceof HTMLInputElement && f.checked\n )\n return checked?.value ?? ''\n }\n\n if (first.type === 'checkbox') {\n const values = fields\n .filter(\n (f): f is HTMLInputElement =>\n f instanceof HTMLInputElement && f.checked\n )\n .map(f => f.value)\n return values.length > 1 ? values : (values[0] ?? '')\n }\n\n if (first.type === 'file') {\n return first.files ? Array.from(first.files) : []\n }\n }\n\n return first.value\n}\n\nexport const writeFieldValue = (fields: FormFieldElement[], value: unknown): void => {\n const normalizedArray = Array.isArray(value)\n ? value.map(item => String(item))\n : null\n const normalizedValue = value === undefined || value === null ? '' : String(value)\n\n for (const field of fields) {\n if (field instanceof HTMLInputElement) {\n if (field.type === 'checkbox') {\n field.checked = normalizedArray\n ? normalizedArray.includes(field.value)\n : normalizedValue === field.value\n continue\n }\n if (field.type === 'radio') {\n field.checked = field.value === normalizedValue\n continue\n }\n if (field.type === 'file') {\n if (Array.isArray(value) && value.length === 0) field.value = ''\n continue\n }\n field.value = normalizedValue\n continue\n }\n\n field.value = normalizedValue\n }\n}\n\nexport const clearAttributes = (form: HTMLFormElement, attributes: string[]): void => {\n for (const attribute of attributes) {\n form\n .querySelectorAll(`[${attribute}]`)\n .forEach(node => node.removeAttribute(attribute))\n }\n}\n\nexport const submitForm = (form: HTMLFormElement): void => {\n if (typeof form.requestSubmit === 'function') {\n form.requestSubmit()\n return\n }\n const submitButton = form.querySelector(\n 'button[type=\"submit\"], input[type=\"submit\"]'\n ) as HTMLButtonElement | HTMLInputElement | null\n if (submitButton) {\n submitButton.click()\n return\n }\n form.submit()\n}\n","// api/get-value.ts\n// Returns the current value of a field, with normalization for checkboxes,\n// radios, files, and multi-value inputs.\n\nimport type { FormControllerHelpers, FormControllerState } from '../core/state'\nimport type { FormFieldValue } from '../core/types'\nimport { readFieldValue } from '../core/dom'\n\nexport const createGetValue =\n (state: FormControllerState, helpers: FormControllerHelpers) =>\n (name: string): FormFieldValue => readFieldValue(helpers.getFieldsByName(name))\n","// api/observe.ts\n// Registers a callback that runs on mount and whenever the field changes.\n// Returns an unsubscribe function.\n\nimport type { FormControllerHelpers, FormControllerState } from '../core/state'\nimport type { FormFieldWatcher } from '../core/types'\n\nexport const createObserve =\n (state: FormControllerState, helpers: FormControllerHelpers) =>\n (field: string, callback: FormFieldWatcher) => {\n const callbacks = state.watchedFields.get(field) ?? new Set<FormFieldWatcher>()\n callbacks.add(callback)\n state.watchedFields.set(field, callbacks)\n\n if (state.element) {\n callback(state.api!.getValue(field), state.api!.getField(field), state.element, state.api!.getState())\n }\n\n return () => {\n const current = state.watchedFields.get(field)\n current?.delete(callback)\n if (current && current.size === 0) {\n state.watchedFields.delete(field)\n }\n }\n }\n","// api/on-submit.ts\n// Registers a submit handler. preventDefault=true intercepts valid submits,\n// which is the right choice for AJAX/fetch flows. Invalid submits are\n// always prevented regardless of the flag.\n\nimport type { FormControllerHelpers, FormControllerState } from '../core/state'\nimport type { FormSubmitHandler } from '../core/types'\n\nexport const createOnSubmit =\n (state: FormControllerState, _helpers: FormControllerHelpers) =>\n (callback: FormSubmitHandler, preventDefault = true) => {\n state.submitHandlers.add({ callback, preventDefault })\n return state.api!\n }\n","// api/prefill.ts\n// Reads window.location.search and writes matching values into the form.\n// Pass a field name to scope prefill to a single field.\n\nimport type { FormControllerHelpers, FormControllerState } from '../core/state'\n\nexport const createPrefill =\n (state: FormControllerState, _helpers: FormControllerHelpers) =>\n (fieldName?: string) => {\n if (!state.element) return state.api!\n\n const params = new URLSearchParams(window.location.search)\n params.forEach((value, key) => {\n if (fieldName && fieldName !== key) return\n state.api!.setValue(key, value)\n })\n return state.api!\n }\n","// api/reset.ts\n// Resets the native form, clears manual and validation errors, strips\n// css-filled/css-error attributes, and notifies subscribers.\n\nimport type { FormControllerHelpers, FormControllerState } from '../core/state'\nimport { clearAttributes } from '../core/dom'\n\nexport const createReset =\n (state: FormControllerState, helpers: FormControllerHelpers) =>\n () => {\n if (!state.element) return state.api!\n\n state.element.reset()\n state.manualErrors = {}\n state.validationErrors = {}\n clearAttributes(state.element, [state.attributes.error, state.attributes.filled])\n\n if (state.isValidated) helpers.syncVisualState()\n helpers.notifySubscribers()\n return state.api!\n }\n","// api/validate.ts\n// Runs validation. Marks the form as validated, syncs visual state, and\n// returns the validation result for callers that want it.\n\nimport type { FormControllerHelpers, FormControllerState } from '../core/state'\nimport type { ValidationResult } from '../core/types'\n\nexport const createValidate =\n (state: FormControllerState, helpers: FormControllerHelpers) =>\n (fields?: string[]): ValidationResult => {\n state.isValidated = true\n helpers.syncVisualState(fields)\n return helpers.validateNames(fields)\n }\n","// api/revalidate.ts\n// Alias of validate(); kept separate for readability at call sites that\n// want to express \"re-run validation now\".\n\nimport type { FormControllerHelpers, FormControllerState } from '../core/state'\nimport { createValidate } from './validate'\n\nexport const createRevalidate = createValidate\n","// api/set-errors.ts\n// Sets manual errors. Accepts an array of field names (each gets a\n// generic message) or a FormErrors map with per-field messages.\n\nimport type { FormControllerHelpers, FormControllerState } from '../core/state'\nimport type { FormErrors } from '../core/types'\nimport { cloneErrors } from '../core/state'\n\nexport const createSetErrors =\n (state: FormControllerState, helpers: FormControllerHelpers) =>\n (fields: string[] | FormErrors) => {\n if (Array.isArray(fields)) {\n state.manualErrors = {\n ...state.manualErrors,\n ...Object.fromEntries(fields.map(field => [field, ['Invalid value.']]))\n }\n } else {\n state.manualErrors = {\n ...state.manualErrors,\n ...cloneErrors(fields)\n }\n }\n\n helpers.syncVisualState(Array.isArray(fields) ? fields : Object.keys(fields))\n helpers.notifySubscribers()\n return state.api!\n }\n","// api/set-value.ts\n// Writes a value into a field and dispatches the correct DOM event so\n// listeners, validators, and visual state run as if a user typed.\n\nimport type { FormControllerHelpers, FormControllerState } from '../core/state'\nimport { writeFieldValue } from '../core/dom'\n\nexport const createSetValue =\n (state: FormControllerState, helpers: FormControllerHelpers) =>\n (name: string, value: unknown) => {\n const fields = helpers.getFieldsByName(name)\n if (fields.length === 0) return state.api!\n\n writeFieldValue(fields, value)\n const firstField = fields[0]\n if (!firstField) return state.api!\n\n const eventType =\n firstField instanceof HTMLSelectElement ||\n (firstField instanceof HTMLInputElement &&\n (firstField.type === 'checkbox' || firstField.type === 'radio'))\n ? 'change'\n : 'input'\n\n firstField.dispatchEvent(new Event(eventType, { bubbles: true }))\n return state.api!\n }\n","// api/subscribe.ts\n// Subscribes to global form state. Listener fires once immediately with\n// the current snapshot, then on every state mutation. Returns an\n// unsubscribe function.\n\nimport type { FormControllerHelpers, FormControllerState } from '../core/state'\nimport type { FormStateListener } from '../core/types'\n\nexport const createSubscribe =\n (state: FormControllerState, _helpers: FormControllerHelpers) =>\n (listener: FormStateListener) => {\n state.subscribers.add(listener)\n listener(state.api!.getState())\n return () => {\n state.subscribers.delete(listener)\n }\n }\n","// api/unwatch.ts\n// Removes watched callbacks. With no args, clears every watcher. With only\n// a field, removes all callbacks for that field. With field + callback,\n// removes just that callback.\n\nimport type { FormControllerHelpers, FormControllerState } from '../core/state'\nimport type { FormFieldWatcher } from '../core/types'\n\nexport const createUnwatch =\n (state: FormControllerState, _helpers: FormControllerHelpers) =>\n (field?: string, callback?: FormFieldWatcher) => {\n if (!field) {\n state.watchedFields.clear()\n return state.api!\n }\n\n if (!callback) {\n state.watchedFields.delete(field)\n return state.api!\n }\n\n const callbacks = state.watchedFields.get(field)\n callbacks?.delete(callback)\n if (callbacks && callbacks.size === 0) {\n state.watchedFields.delete(field)\n }\n return state.api!\n }\n","// api/watch.ts\n// Chainable alias over `observe`. Use this when you want a fluent setup\n// without holding the returned unsubscribe function.\n\nimport type { FormControllerHelpers, FormControllerState } from '../core/state'\nimport type { FormFieldWatcher } from '../core/types'\nimport { createObserve } from './observe'\n\nexport const createWatch = (state: FormControllerState, helpers: FormControllerHelpers) =>\n (field: string, callback: FormFieldWatcher) => {\n const observe = createObserve(state, helpers)\n observe(field, callback)\n return state.api!\n }\n","// Pure validation routines. No DOM, no controller state.\n\nimport type {\n FieldValidationContext,\n FieldValidationRules,\n FormErrors,\n FormFieldValue,\n FormValues,\n RuleConfig,\n ValidationResult,\n ValidationSchema\n} from './types'\n\nconst resolveRule = <T>(rule: RuleConfig<T> | undefined) => {\n if (rule === undefined) {\n return { value: undefined as T | undefined, message: undefined as string | undefined }\n }\n if (typeof rule === 'object' && rule !== null && 'value' in rule) {\n return { value: rule.value, message: rule.message }\n }\n return { value: rule, message: undefined as string | undefined }\n}\n\nconst hasValue = (value: FormFieldValue): boolean => {\n if (Array.isArray(value)) return value.length > 0\n if (typeof value === 'string') return value.trim().length > 0\n return value !== undefined\n}\n\nconst getValueLength = (value: FormFieldValue): number => {\n if (Array.isArray(value)) return value.length\n if (typeof value === 'string') return value.length\n return 0\n}\n\nconst toPatternTarget = (value: FormFieldValue): string => {\n if (typeof value === 'string') return value\n if (Array.isArray(value)) {\n return value\n .map(entry => (typeof entry === 'string' ? entry : entry.name))\n .join(',')\n }\n return ''\n}\n\nexport const validateFieldValue = (\n field: string,\n value: FormFieldValue,\n rules: FieldValidationRules,\n values: FormValues\n): string[] => {\n const errors: string[] = []\n const context: FieldValidationContext = { field, value, values }\n const required = resolveRule(rules.required)\n const minLength = resolveRule(rules.minLength)\n const maxLength = resolveRule(rules.maxLength)\n const pattern = resolveRule(rules.pattern)\n\n if (required.value && !hasValue(value)) {\n errors.push(required.message ?? 'This field is required.')\n }\n if (minLength.value !== undefined && getValueLength(value) < minLength.value) {\n errors.push(minLength.message ?? `Minimum length is ${minLength.value}.`)\n }\n if (maxLength.value !== undefined && getValueLength(value) > maxLength.value) {\n errors.push(maxLength.message ?? `Maximum length is ${maxLength.value}.`)\n }\n if (pattern.value && hasValue(value) && !pattern.value.test(toPatternTarget(value))) {\n errors.push(pattern.message ?? 'Value does not match the required pattern.')\n }\n\n const custom = rules.validate\n ? Array.isArray(rules.validate)\n ? rules.validate\n : [rules.validate]\n : []\n\n for (const validator of custom) {\n const result = validator(context)\n if (typeof result === 'string' && result.length > 0) errors.push(result)\n if (result === false) errors.push('Validation failed.')\n }\n\n return errors\n}\n\nexport const validateValues = (\n values: FormValues,\n schema: ValidationSchema\n): ValidationResult => {\n const errors: FormErrors = {}\n for (const [field, rules] of Object.entries(schema)) {\n const messages = validateFieldValue(field, values[field], rules, values)\n if (messages.length > 0) errors[field] = messages\n }\n return {\n isValid: Object.keys(errors).length === 0,\n errors\n }\n}\n","// Core orchestrator. Wires shared state, internal helpers and the public\n// api method factories into a single FormController instance.\n//\n// Internal event handlers and DOM wiring live here. Every public method\n// lives in `../api/<method>.ts` as an isolated factory.\n\nimport { createAppend } from '../api/append'\nimport { createAutoSubmit } from '../api/auto-submit'\nimport { createClearErrors } from '../api/clear-errors'\nimport { createDestroy } from '../api/destroy'\nimport { createDisableAutoSubmit } from '../api/disable-auto-submit'\nimport { createFormat, createFormatAll } from '../api/format'\nimport { createGetData } from '../api/get-data'\nimport { createGetField } from '../api/get-field'\nimport { createGetState } from '../api/get-state'\nimport { createGetValue } from '../api/get-value'\nimport { createObserve } from '../api/observe'\nimport { createOnSubmit } from '../api/on-submit'\nimport { createPrefill } from '../api/prefill'\nimport { createReset } from '../api/reset'\nimport { createRevalidate } from '../api/revalidate'\nimport { createSetErrors } from '../api/set-errors'\nimport { createSetValue } from '../api/set-value'\nimport { createSubscribe } from '../api/subscribe'\nimport { createUnwatch } from '../api/unwatch'\nimport { createValidate } from '../api/validate'\nimport { createWatch } from '../api/watch'\nimport { validateFieldValue } from '../core/validation'\nimport {\n applyBooleanAttribute,\n getNamedFields,\n isFieldElement,\n isFieldFilled,\n queryNamedFields,\n readFieldValue,\n resolveFormElement,\n submitForm,\n writeFieldValue\n} from './dom'\nimport { cloneErrors, mergeErrors } from './state'\nimport type {\n FormController,\n FormControllerOptions,\n FormFieldElement,\n FormFieldValue,\n FormTarget,\n ValidationResult,\n VisualAttributes\n} from './types'\nimport type { FormControllerHelpers, FormControllerState } from './state'\n\nconst DEFAULT_ATTRIBUTES: Required<VisualAttributes> = {\n filled: 'css-filled',\n error: 'css-error'\n}\n\nconst DEFAULT_OPTIONS = {\n autoValidate: true,\n clearErrorsOnSubmit: true,\n clearManualErrorsOnChange: true\n} satisfies Pick<\n FormControllerOptions,\n 'autoValidate' | 'clearErrorsOnSubmit' | 'clearManualErrorsOnChange'\n>\n\nexport const createFormController = (\n target: FormTarget,\n options: FormControllerOptions = {}\n): FormController => {\n const element = resolveFormElement(target)\n const attributes = { ...DEFAULT_ATTRIBUTES, ...options.attributes }\n const normalizedOptions: FormControllerOptions = {\n ...DEFAULT_OPTIONS,\n ...options,\n attributes\n }\n\n const state: FormControllerState = {\n element,\n options: normalizedOptions,\n attributes,\n validators: normalizedOptions.validators ?? {},\n watchedFields: new Map(),\n subscribers: new Set(),\n submitHandlers: new Set(),\n fieldCache: new Map(),\n manualErrors: {},\n validationErrors: {},\n isValidated: Boolean(normalizedOptions.autoValidate),\n autoSubmitEnabled: false,\n autoSubmitDebounce: 0,\n submitCount: 0,\n autoSubmitTimer: null,\n isDestroyed: false,\n listeners: [],\n mutationObserver: null,\n formattedFields: new Map(),\n api: null\n }\n\n // ------- Internal helpers (no DOM wiring of their own) -----------------\n\n const notifySubscribers = () => {\n if (!state.api) return\n const snapshot = state.api.getState()\n state.subscribers.forEach(listener => listener(snapshot))\n }\n\n const addListener = (\n node: EventTarget,\n type: string,\n handler: EventListenerOrEventListenerObject\n ) => {\n node.addEventListener(type, handler)\n state.listeners.push({ element: node, type, handler })\n }\n\n const clearFieldCache = () => state.fieldCache.clear()\n\n const getFieldsByName = (name: string): FormFieldElement[] => {\n if (!state.element) return []\n if (!state.fieldCache.has(name)) {\n state.fieldCache.set(name, queryNamedFields(state.element, name))\n }\n return state.fieldCache.get(name) ?? []\n }\n\n const getTrackedFieldNames = (): string[] => {\n if (!state.element) return Object.keys(state.validators)\n const names = new Set<string>(Object.keys(state.validators))\n for (const field of getNamedFields(state.element)) {\n names.add(field.name)\n }\n return Array.from(names)\n }\n\n const getValues = () => {\n const values: Record<string, FormFieldValue> = {}\n for (const name of getTrackedFieldNames()) {\n values[name] = readFieldValue(getFieldsByName(name))\n }\n return values\n }\n\n const getMergedErrors = () => mergeErrors(state.validationErrors, state.manualErrors)\n\n const syncVisualState = (names?: string[]) => {\n if (!state.element) return\n const targetNames = names ?? getTrackedFieldNames()\n const errors = getMergedErrors()\n for (const name of targetNames) {\n const fields = getFieldsByName(name)\n const hasError = Boolean(errors[name]?.length)\n for (const field of fields) {\n applyBooleanAttribute(field, attributes.filled, isFieldFilled(field))\n applyBooleanAttribute(field, attributes.error, hasError)\n }\n }\n }\n\n // readFieldValue/writeFieldValue are exported from core/dom for direct\n // callers; referenced here so the linter keeps them as part of the\n // controlled API surface.\n void readFieldValue\n void writeFieldValue\n\n const validateNames = (names?: string[]): ValidationResult => {\n const targetNames = names ?? Object.keys(state.validators)\n const values = getValues()\n const nextValidationErrors = names ? cloneErrors(state.validationErrors) : {}\n\n for (const name of targetNames) {\n const rules = state.validators[name]\n if (!rules) {\n delete nextValidationErrors[name]\n continue\n }\n const messages = validateFieldValue(name, values[name], rules, values)\n if (messages.length > 0) nextValidationErrors[name] = messages\n else delete nextValidationErrors[name]\n }\n\n state.validationErrors = nextValidationErrors\n syncVisualState(names)\n\n return {\n isValid: Object.keys(getMergedErrors()).length === 0,\n errors: cloneErrors(getMergedErrors())\n }\n }\n\n const emitFieldWatchers = (name: string) => {\n if (!state.element || !state.api) return\n const callbacks = state.watchedFields.get(name)\n if (!callbacks || callbacks.size === 0) return\n const value = state.api.getValue(name)\n const fieldElement = state.api.getField(name)\n const snapshot = state.api.getState()\n callbacks.forEach(callback => callback(value, fieldElement, state.element!, snapshot))\n }\n\n const scheduleAutoSubmit = () => {\n if (!state.element || !state.autoSubmitEnabled) return\n if (state.autoSubmitTimer) clearTimeout(state.autoSubmitTimer)\n if (state.autoSubmitDebounce > 0) {\n state.autoSubmitTimer = setTimeout(\n () => submitForm(state.element!),\n state.autoSubmitDebounce\n )\n return\n }\n submitForm(state.element)\n }\n\n const helpers: FormControllerHelpers = {\n notifySubscribers,\n clearFieldCache,\n getFieldsByName,\n getTrackedFieldNames,\n getValues,\n getMergedErrors,\n syncVisualState,\n validateNames,\n emitFieldWatchers,\n scheduleAutoSubmit\n }\n\n // ------- DOM event wiring ---------------------------------------------\n\n const handleDelegatedEvent = (event: Event) => {\n const target = event.target\n if (!(target instanceof Element) || !isFieldElement(target)) return\n const name = target.name\n if (!name) return\n\n clearFieldCache()\n\n if (normalizedOptions.clearManualErrorsOnChange) {\n delete state.manualErrors[name]\n }\n\n syncVisualState([name])\n\n if (state.isValidated && state.validators[name]) {\n validateNames([name])\n }\n\n emitFieldWatchers(name)\n notifySubscribers()\n scheduleAutoSubmit()\n }\n\n const handleSubmitEvent = (event: Event) => {\n if (!state.element || state.isDestroyed || !state.api) return\n\n if (normalizedOptions.clearErrorsOnSubmit) state.manualErrors = {}\n\n const validation = state.api.validate()\n const handlers = Array.from(state.submitHandlers)\n const shouldPrevent = validation.isValid\n ? handlers.some(handler => handler.preventDefault)\n : true\n\n if (shouldPrevent) event.preventDefault()\n\n state.submitCount += 1\n notifySubscribers()\n\n if (!validation.isValid) return\n\n const { data, formData } = state.api.getData()\n const snapshot = state.api.getState()\n handlers.forEach(handler =>\n handler.callback(state.element!, data, formData, snapshot)\n )\n }\n\n const startMutationObserver = () => {\n if (!state.element || typeof MutationObserver === 'undefined') return\n\n state.mutationObserver = new MutationObserver(() => {\n clearFieldCache()\n if (state.isValidated) {\n syncVisualState()\n validateNames()\n }\n notifySubscribers()\n })\n\n state.mutationObserver.observe(state.element, {\n childList: true,\n subtree: true,\n attributes: true,\n attributeFilter: ['name', 'type']\n })\n }\n\n // ------- Public api composition --------------------------------------\n\n const api: FormController = {\n get element() {\n return state.element\n },\n get f() {\n return state.element\n },\n get options() {\n return state.options\n },\n onSubmit: createOnSubmit(state, helpers),\n watch: createWatch(state, helpers),\n observe: createObserve(state, helpers),\n unwatch: createUnwatch(state, helpers),\n subscribe: createSubscribe(state, helpers),\n prefill: createPrefill(state, helpers),\n append: createAppend(state, helpers),\n setErrors: createSetErrors(state, helpers),\n clearErrors: createClearErrors(state, helpers),\n setValue: createSetValue(state, helpers),\n getValue: createGetValue(state, helpers),\n getField: createGetField(state, helpers),\n validate: createValidate(state, helpers),\n revalidate: createRevalidate(state, helpers),\n reset: createReset(state, helpers),\n autoSubmit: createAutoSubmit(state, helpers),\n disableAutoSubmit: createDisableAutoSubmit(state, helpers),\n getData: createGetData(state, helpers),\n getState: createGetState(state, helpers),\n format: createFormat(state, helpers),\n formatAll: createFormatAll(state, helpers),\n destroy: createDestroy(state, helpers)\n }\n\n state.api = api\n\n // ------- Lifecycle hooks ---------------------------------------------\n\n if (state.element) {\n addListener(state.element, 'input', handleDelegatedEvent)\n addListener(state.element, 'change', handleDelegatedEvent)\n addListener(state.element, 'submit', handleSubmitEvent)\n startMutationObserver()\n }\n\n if (normalizedOptions.autoSubmit) {\n api.autoSubmit(normalizedOptions.autoSubmit)\n }\n\n if (normalizedOptions.formats) {\n for (const config of Object.values(normalizedOptions.formats)) {\n api.format(config)\n }\n }\n\n if (state.isValidated) {\n syncVisualState()\n validateNames()\n }\n\n return api\n}\n","// api/form.ts\n// Public entry point: the `form(target, options)` factory.\n// Thin wrapper around the core orchestrator for ergonomic imports.\n\nimport { createFormController } from '../core/controller'\nimport type { FormControllerOptions, FormTarget } from '../core/types'\n\nexport const form = (\n target: FormTarget,\n options: FormControllerOptions = {}\n) => createFormController(target, options)\n"],"mappings":"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","names":["state","state","state","form","state","state","state","state","state","form","state","state","state","state","state","state","state","state","state","state","state","state","addListener","target"]}
|
|
1
|
+
{"version":3,"sources":["../../src/browser/global.ts","../../src/api/append.ts","../../src/api/auto-submit.ts","../../src/api/clear-errors.ts","../../src/core/format-helpers.ts","../../src/core/formatter-loader.ts","../../src/api/format.ts","../../src/api/destroy.ts","../../src/api/disable-auto-submit.ts","../../src/core/serialize.ts","../../src/api/get-data.ts","../../src/api/get-field.ts","../../src/core/state.ts","../../src/api/get-state.ts","../../src/core/dom.ts","../../src/api/get-value.ts","../../src/api/observe.ts","../../src/api/on-submit.ts","../../src/api/prefill.ts","../../src/api/reset.ts","../../src/api/validate.ts","../../src/api/revalidate.ts","../../src/api/set-errors.ts","../../src/api/set-value.ts","../../src/api/subscribe.ts","../../src/api/unwatch.ts","../../src/api/watch.ts","../../src/core/validation.ts","../../src/core/controller.ts","../../src/api/form.ts"],"sourcesContent":["// Browser entrypoint. Exposes a minimal surface as a single global\n// accessible via `window.Forms` (or `globalThis.Forms`).\n//\n// Public shape:\n// - Forms.form(target, options)\n// - Forms.newForm({ id, options })\n// - Forms.destroyForm(id)\n// - Forms.available (registry keyed by form id)\n\nimport { form } from '../api/form'\nimport type {\n FormController,\n FormControllerOptions,\n FormTarget\n} from '../core/types'\n\nexport interface NewFormInput {\n id: string\n options?: FormControllerOptions\n}\n\nexport interface FormsAvailable {\n [id: string]: FormController\n}\n\nexport interface FormsApi {\n form: (\n target: FormTarget,\n options?: FormControllerOptions\n ) => FormController\n newForm: (input: NewFormInput) => FormController | undefined\n destroyForm: (id: string) => void\n available: FormsAvailable\n}\n\nconst available: FormsAvailable = {}\n\nconst newForm = (input: NewFormInput): FormController | undefined => {\n const { id, options } = input\n if (!id) {\n console.error('Form ID is required')\n return\n }\n const controller = form(id, { ...options })\n available[id] = controller\n return controller\n}\n\nconst destroyForm = (id: string): void => {\n if (!id) {\n console.error('Form ID is required')\n return\n }\n const controller = available[id]\n if (controller) {\n controller.destroy()\n delete available[id]\n } else {\n console.warn(`Form with ID ${id} not found`)\n }\n}\n\nconst Forms: FormsApi = {\n form,\n newForm,\n destroyForm,\n available\n}\n\ndeclare global {\n interface Window {\n Forms: FormsApi\n }\n}\n\nif (typeof globalThis !== 'undefined') {\n ;(globalThis as typeof globalThis & { Forms: FormsApi }).Forms = Forms\n}\n\nexport default Forms","// api/append.ts\n// Appends DOM content to the bound form. If a class is provided, removes\n// any existing node with that class first to avoid duplicates.\n// Returns the created node or null when no element is bound.\n\nimport type { FormControllerHelpers, FormControllerState } from '../core/state'\nimport type { AppendContentOptions } from '../core/types'\n\nexport const createAppend =\n (state: FormControllerState, helpers: FormControllerHelpers) =>\n ({ tag, content, class: className, atStart = false }: AppendContentOptions) => {\n if (!state.element) return null\n\n helpers.clearFieldCache()\n\n if (className) {\n const classSelector = className.trim().split(/\\s+/)[0]\n const existing = classSelector\n ? state.element.querySelector(`.${classSelector}`)\n : null\n existing?.remove()\n }\n\n const node = document.createElement(tag)\n if (className) node.className = className\n node.innerHTML = content\n\n if (atStart && state.element.firstChild) {\n state.element.insertBefore(node, state.element.firstChild)\n } else {\n state.element.appendChild(node)\n }\n\n return node\n }\n","// api/auto-submit.ts\n// Enables native auto-submit. Pass `true` to enable, `false` to disable,\n// or an { debounce } object to delay.\n\nimport type { FormControllerHelpers, FormControllerState } from '../core/state'\n\nexport const createAutoSubmit =\n (state: FormControllerState, helpers: FormControllerHelpers) =>\n (next: boolean | { debounce?: number } = true) => {\n state.autoSubmitEnabled = next !== false\n state.autoSubmitDebounce =\n typeof next === 'object' && next ? next.debounce ?? 0 : 0\n helpers.notifySubscribers()\n return state.api!\n }\n","// api/clear-errors.ts\n// Clears manual errors. With no args, clears every manual error. With a\n// list, clears only those fields.\n\nimport type { FormControllerHelpers, FormControllerState } from '../core/state'\n\nexport const createClearErrors =\n (state: FormControllerState, helpers: FormControllerHelpers) =>\n (fields?: string[]) => {\n if (!fields) {\n state.manualErrors = {}\n helpers.syncVisualState()\n helpers.notifySubscribers()\n return state.api!\n }\n\n for (const field of fields) {\n delete state.manualErrors[field]\n }\n helpers.syncVisualState(fields)\n helpers.notifySubscribers()\n return state.api!\n }\n","// Low-level helpers used by `api/format.ts`:\n// - DOM cursor tracking equivalent to cleave.js' `getNextCursorPosition`\n// (so typing/backspacing inside formatted inputs keeps the caret\n// in a sensible place, even when delimiters are inserted or removed).\n// - Raw-mirror lookup & creation: each formatted input gets a hidden\n// sibling (`type=\"hidden\"`) that holds the backend-ready `raw`\n// value. The mirror is created on first `format()` call and reused\n// on subsequent calls so re-binding the controller does not duplicate\n// hidden inputs.\n//\n// The helpers here are pure DOM utilities — they do not know about the\n// controller state.\n\nimport type { FormFieldElement } from './types'\n\nexport const FORMATTER_RAW_ATTRIBUTE = 'data-formatter-raw-for'\n\n// Resolve a field by name (used to read the original input that owns the\n// raw mirror). Returns the first matching element or `null`.\nexport const getFormatterField = (\n form: HTMLFormElement,\n name: string\n): HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement | null => {\n const selector = `[name=\"${cssEscape(name)}\"]`\n const node = form.querySelector(selector)\n if (\n node instanceof HTMLInputElement ||\n node instanceof HTMLTextAreaElement ||\n node instanceof HTMLSelectElement\n ) {\n return node\n }\n return null\n}\n\n// Minimal CSS.escape polyfill for selectors that may contain special\n// characters (`[1].phone`, `email[primary]`, etc.).\nconst cssEscape = (value: string): string => {\n if (typeof CSS !== 'undefined' && typeof CSS.escape === 'function') {\n return CSS.escape(value)\n }\n return value.replace(/([!\"#$%&'()*+,./:;<=>?@\\[\\\\\\]^`{|}~])/g, '\\\\$1')\n}\n\n// Find the hidden input that mirrors `raw` for the given field name.\n// Returns the existing element or `null`.\nexport const findRawMirror = (\n form: HTMLFormElement,\n fieldName: string\n): HTMLInputElement | null => {\n const byAttr = form.querySelector<HTMLInputElement>(\n `[${FORMATTER_RAW_ATTRIBUTE}=\"${cssEscape(fieldName)}\"]`\n )\n if (byAttr) return byAttr\n\n // Backwards-compatible lookup: existing deployments may have used\n // `<input name=\"<field>_raw\">` or `<input name=\"<field>Raw\">` to carry the raw value. Reuse it.\n const byName = form.querySelector<HTMLInputElement>(\n `[name=\"${cssEscape(`${fieldName}_raw`)}\"]`\n )\n if (byName) return byName\n const byNameOld = form.querySelector<HTMLInputElement>(\n `[name=\"${cssEscape(`${fieldName}Raw`)}\"]`\n )\n if (byNameOld) return byNameOld\n\n return null\n}\n\n// Create a hidden input that will mirror the raw value of `fieldName`.\n// Idempotent: if a mirror already exists it is returned as-is.\nexport const ensureRawMirror = (\n form: HTMLFormElement,\n fieldName: string\n): HTMLInputElement => {\n const existing = findRawMirror(form, fieldName)\n if (existing) return existing\n\n const input = document.createElement('input')\n input.type = 'hidden'\n input.name = `${fieldName}_raw`\n input.setAttribute('aria-hidden', 'true')\n input.tabIndex = -1\n input.setAttribute(FORMATTER_RAW_ATTRIBUTE, fieldName)\n form.appendChild(input)\n return input\n}\n\n// Remove every raw mirror owned by `format()`. Mirrors created by this\n// controller carry the `data-formatter-raw-for` attribute; mirrors that\n// existed before are left untouched.\nexport const removeOwnedRawMirrors = (form: HTMLFormElement): void => {\n form\n .querySelectorAll<HTMLInputElement>(`[${FORMATTER_RAW_ATTRIBUTE}]`)\n .forEach(node => node.remove())\n}\n\n// Write `formatted` into the visible field and `raw` into the hidden\n// mirror, preserving the cursor as best as possible.\n//\n// Returns the new caret position so callers can debug or run additional\n// assertions. The function avoids triggering `MutationObserver` cascades\n// for the hidden mirror by setting `.value` directly (the controller's\n// own delegated handler ignores hidden inputs via `isFieldElement`).\nexport const applyFormattedValue = (\n visible: HTMLInputElement | HTMLTextAreaElement,\n mirror: HTMLInputElement | null,\n formatted: string,\n raw: string\n): number => {\n // Capture the caret in the **raw** input the user just typed — this\n // is what cleave's `getNextCursorPosition` does. Using the formatted\n // previous value would lose information when the user edits in the\n // middle of a masked field (deletion adjacent to delimiters, etc.).\n const rawInput = visible.value\n const caret = clamp(\n visible.selectionStart ?? rawInput.length,\n 0,\n rawInput.length\n )\n\n if (rawInput === formatted) {\n if (mirror && mirror.value !== raw) mirror.value = raw\n return caret\n }\n\n visible.value = formatted\n\n if (mirror) mirror.value = raw\n\n const newCaret = computeCursorPosition(caret, rawInput, formatted)\n restoreCursor(visible, newCaret)\n return newCaret\n}\n\nconst clamp = (value: number, min: number, max: number): number =>\n Math.max(min, Math.min(max, value))\n\n// `computeCursorPosition` is a cleave-style implementation:\n// - count \"non-format\" characters (digits / letters) between the caret\n// and the start of the **raw input the user just typed**, then\n// locate that same logical index in the new formatted value\n// (jumping over inserted delimiters).\n// - when the caret is at (or past) the end of the raw input, place\n// it at the end of the formatted value.\n//\n// The algorithm intentionally avoids any cleave-zen dependency so this\n// file is pure DOM arithmetic.\nexport const computeCursorPosition = (\n caretPos: number,\n rawInput: string,\n newValue: string\n): number => {\n if (newValue.length === 0) return 0\n\n // Caret at (or past) the end of the raw input — place at new end.\n if (caretPos >= rawInput.length) return newValue.length\n\n // Caret within the raw input: count non-format characters to its\n // LEFT (i.e. how many real characters are before the caret) and\n // find that same count in the new formatted value.\n const targetNonFormat = countNonFormat(rawInput.slice(0, caretPos))\n\n if (targetNonFormat === 0) {\n // Skip leading delimiters / prefix symbols (`$`, `+`, …) so the\n // caret lands at the first real character of the new value.\n let i = 0\n while (i < newValue.length && isFormatChar(newValue[i]!)) i += 1\n return i\n }\n\n let seen = 0\n for (let i = 0; i < newValue.length; i += 1) {\n if (!isFormatChar(newValue[i]!)) {\n seen += 1\n if (seen === targetNonFormat) {\n return Math.min(i + 1, newValue.length)\n }\n }\n }\n\n return newValue.length\n}\n\nconst countNonFormat = (value: string): number => {\n let count = 0\n for (const char of value) {\n if (!isFormatChar(char)) count += 1\n }\n return count\n}\n\n// \"Format characters\" are delimiters, prefix symbols, and other\n// non-alphanumeric separators that the formatter may insert or remove.\n// We deliberately keep this conservative — anything that is not a digit\n// or ASCII letter counts as a format character.\nexport const isFormatChar = (char: string): boolean => {\n if (!char) return false\n if (char >= '0' && char <= '9') return false\n if ((char >= 'a' && char <= 'z') || (char >= 'A' && char <= 'Z')) return false\n return true\n}\n\nconst restoreCursor = (\n field: HTMLInputElement | HTMLTextAreaElement,\n position: number\n): void => {\n const max = field.value.length\n const safe = clamp(position, 0, max)\n\n const apply = () => {\n try {\n field.setSelectionRange(safe, safe)\n } catch {\n // Some input types (number, email) throw on setSelectionRange.\n // Ignore — the caret just stays where the browser put it.\n }\n }\n\n // Android keyboard fix: defer one frame so the IME catches up before\n // we re-position the caret. Mirrors cleave.js' `isAndroid` branch.\n if (typeof navigator !== 'undefined' && /Android/i.test(navigator.userAgent)) {\n if (typeof requestAnimationFrame === 'function') {\n requestAnimationFrame(apply)\n } else {\n setTimeout(apply, 1)\n }\n return\n }\n\n apply()\n}\n\n// `getFormatterField` re-exported above is the canonical entry point,\n// but it is intentionally restricted to controller-owned fields. This\n// looser helper accepts a wider set of inputs for test fixtures.\nexport const isFormatterFieldElement = (node: Element): node is FormFieldElement =>\n node instanceof HTMLInputElement ||\n node instanceof HTMLTextAreaElement ||\n node instanceof HTMLSelectElement","// Lazy singleton loader for the optional `@samline/formatter` peer.\n// The formatter is imported dynamically so the package stays usable\n// even when the peer is not installed: callers receive `null` and a\n// single console.error explaining the missing dependency.\n//\n// The first failure is cached so the warning is printed exactly once\n// per process, not on every `format(...)` call.\n\nexport interface FormatterModule {\n format: (\n value: unknown,\n formatType: string,\n options?: Record<string, unknown>\n ) => { formatted: string; raw: string; type: string }\n FORMAT_TYPES?: readonly string[]\n isFormatType?: (value: unknown) => boolean\n regex?: Record<string, { pattern: RegExp; errorMessage: string }>\n}\n\ntype LoaderState =\n | { status: 'pending'; promise: Promise<FormatterModule | null> }\n | { status: 'resolved'; module: FormatterModule | null }\n\nlet state: LoaderState | null = null\n\nconst MISSING_MESSAGE =\n '[samline/forms] The `format()` and `formatAll()` methods require the ' +\n '@samline/formatter package. Install it with: npm i @samline/formatter ' +\n '(or pnpm/bun/yarn equivalent).'\n\nlet warned = false\n\nconst warnOnce = () => {\n if (warned) return\n warned = true\n console.error(MISSING_MESSAGE)\n}\n\nexport const loadFormatter = (): Promise<FormatterModule | null> => {\n if (state && state.status === 'resolved') {\n if (!state.module) warnOnce()\n return Promise.resolve(state.module)\n }\n\n if (state && state.status === 'pending') {\n return state.promise\n }\n\n const promise = (async () => {\n try {\n // Dynamic import keeps the optional peer out of the build graph.\n // The `/* @vite-ignore */` comment prevents bundlers (vite/rollup)\n // from trying to pre-bundle the optional peer.\n const mod = (await import(\n /* @vite-ignore */ '@samline/formatter'\n )) as FormatterModule\n state = { status: 'resolved', module: mod }\n return mod\n } catch {\n warnOnce()\n state = { status: 'resolved', module: null }\n return null\n }\n })()\n\n state = { status: 'pending', promise }\n return promise\n}\n\n// Test-only helper: reset the cached loader so unit tests can swap the\n// dynamic import between \"installed\" and \"missing\" scenarios.\nexport const __resetFormatterLoaderForTests = (): void => {\n state = null\n warned = false\n}\n\n// Test-only helper: inject a formatter implementation directly,\n// bypassing the dynamic import. Pass `null` to simulate the\n// \"peer not installed\" branch.\nexport const __setFormatterModuleForTests = (\n module: FormatterModule | null\n): void => {\n state = { status: 'resolved', module }\n}","// api/format.ts\n// Applies the optional `@samline/formatter` peer to one or many form\n// fields, with cleave-style cursor tracking and an auto-managed hidden\n// raw mirror.\n//\n// Public surface lives on `FormController` as `format()` and\n// `formatAll()`. Both are chainable and both behave identically — the\n// alias exists only to read naturally when the caller wants to apply\n// the same configuration to several inputs.\n//\n// When `@samline/formatter` is not installed the methods log a single\n// `console.error` (via `loadFormatter`) and return the controller\n// unchanged so the rest of the app keeps working.\n\nimport type { FieldFormatConfig, FormController, FormFieldElement } from '../core/types'\nimport {\n applyFormattedValue,\n ensureRawMirror,\n findRawMirror,\n FORMATTER_RAW_ATTRIBUTE\n} from '../core/format-helpers'\nimport { loadFormatter } from '../core/formatter-loader'\nimport type { FormControllerHelpers, FormControllerState } from '../core/state'\n\n// Listeners added on demand; tracked here so a future `unformat(field)`\n// (out of scope for v1) can detach them without trawling `state.listeners`.\ntype FormatEntry = {\n fields: Set<HTMLInputElement | HTMLTextAreaElement>\n mirrorName: string\n handler: (event: Event) => void\n mirrorIsOwned: boolean\n}\n\nconst registry = new WeakMap<FormControllerState, Map<string, FormatEntry>>()\n\nconst getRegistry = (state: FormControllerState): Map<string, FormatEntry> => {\n let bucket = registry.get(state)\n if (!bucket) {\n bucket = new Map<string, FormatEntry>()\n registry.set(state, bucket)\n }\n return bucket\n}\n\nconst resolveFieldNames = (config: FieldFormatConfig): string[] =>\n Array.isArray(config.field) ? config.field : [config.field]\n\nconst addListener = (\n state: FormControllerState,\n target: EventTarget,\n type: string,\n handler: EventListener\n): void => {\n target.addEventListener(type, handler)\n state.listeners.push({ element: target, type, handler })\n}\n\nconst buildHandler = (\n field: HTMLInputElement | HTMLTextAreaElement,\n mirror: HTMLInputElement | null,\n formatFn: NonNullable<Awaited<ReturnType<typeof loadFormatter>>>['format'],\n formatType: FieldFormatConfig['type'],\n formatOptions: Record<string, unknown> | undefined,\n mirrorFieldName: string\n): ((event: Event) => void) => {\n const handler = (event: Event) => {\n if (!field.isConnected) return\n // Bug #1 fix: ensure the event actually came from this field,\n // since the listener is delegated to the form.\n if (event.target !== field) return\n const rawInput = (event.target as HTMLInputElement | HTMLTextAreaElement).value\n const { formatted, raw } = formatFn(rawInput, formatType, formatOptions)\n\n if (!formatted && !raw) {\n field.value = ''\n if (mirror && mirror.value !== '') mirror.value = ''\n return\n }\n\n applyFormattedValue(field, mirror, formatted, raw)\n // Keep the registry mirror pointer alive for in-place cleanup.\n void mirrorFieldName\n }\n return handler\n}\n\n// Internal: core apply routine shared by `format()` and `formatAll()`.\n// Returns the controller to preserve chainability even on the missing\n// peer path.\nconst applyFormat = async (\n state: FormControllerState,\n helpers: FormControllerHelpers,\n config: FieldFormatConfig\n): Promise<void> => {\n if (!state.element || !state.api) return\n\n const formatter = await loadFormatter()\n if (!formatter) return\n\n const formatType = config.type\n const formatOptions = config.options\n const fieldNames = resolveFieldNames(config)\n const bucket = getRegistry(state)\n\n for (const fieldName of fieldNames) {\n const fields = helpers.getFieldsByName(fieldName) as FormFieldElement[]\n if (fields.length === 0) continue\n\n // Filter to text-like inputs — the formatter does not work on\n // checkboxes, radios, files, etc.\n const writable = fields.filter(\n (f): f is HTMLInputElement | HTMLTextAreaElement =>\n (f instanceof HTMLInputElement &&\n f.type !== 'checkbox' &&\n f.type !== 'radio' &&\n f.type !== 'file' &&\n f.type !== 'submit' &&\n f.type !== 'button') ||\n f instanceof HTMLTextAreaElement\n )\n\n if (writable.length === 0) continue\n\n for (const field of writable) {\n // Idempotency: a second `format()` call for the same field\n // refreshes the configuration in `state.formattedFields` but\n // does not double-bind listeners or duplicate the hidden mirror.\n const existing = bucket.get(fieldName)\n const mirrorName = config.rawField ?? `${fieldName}_raw`\n // Bug #2a fix: search by fieldName (the data-formatter-raw-for value),\n // not mirrorName (the input name). findRawMirror looks for\n // data-formatter-raw-for=\"<fieldName>\", not data-formatter-raw-for=\"<fieldName>Raw\".\n let mirror = findRawMirror(state.element, fieldName) ?? null\n const mirrorIsOwned = !mirror\n if (!mirror) mirror = ensureRawMirror(state.element, fieldName)\n // Sync the mirror's `name` attribute when the user overrides it\n // via `config.rawField` (default `<field>Raw`).\n if (mirror && mirror.name !== mirrorName) mirror.name = mirrorName\n const trackerMirrorName = mirror?.name ?? mirrorName\n\n if (existing) {\n // Refresh the stored config so `destroy()` knows the entry is\n // still active, but do not re-attach listeners.\n existing.mirrorName = trackerMirrorName\n existing.mirrorIsOwned = mirrorIsOwned\n state.formattedFields.set(fieldName, {\n config,\n mirrorName: trackerMirrorName,\n mirrorIsOwned\n })\n // Bug #2b fix: re-format the current value with the new options\n // (e.g. country_code change needs to re-format the phone).\n // Only re-format when the field has a value; when the field is\n // empty we leave the mirror untouched so pre-existing raw values\n // (set before format() was called) are preserved.\n const current = field.value\n if (current !== '') {\n const { formatted, raw } = formatter.format(current, formatType, formatOptions)\n applyFormattedValue(field, mirror, formatted, raw)\n }\n continue\n }\n\n const handler = buildHandler(\n field,\n mirror,\n formatter.format,\n formatType,\n formatOptions,\n trackerMirrorName\n )\n\n addListener(state, state.element, 'input', handler)\n bucket.set(fieldName, {\n fields: new Set([field]),\n mirrorName: trackerMirrorName,\n handler,\n mirrorIsOwned\n })\n state.formattedFields.set(fieldName, {\n config,\n mirrorName: trackerMirrorName,\n mirrorIsOwned\n })\n\n // Apply the formatter to the current value so pre-filled data\n // (e.g. coming from `setValue`, `prefill`, or server-rendered\n // HTML) is normalised immediately and the hidden mirror is\n // populated without waiting for a user keystroke.\n const initial = field.value\n if (initial !== '') {\n const { formatted, raw } = formatter.format(initial, formatType, formatOptions)\n applyFormattedValue(field, mirror, formatted, raw)\n } else if (mirrorIsOwned && mirror && mirror.value !== '') {\n // Only clear an owned mirror when the field is empty. Pre-existing\n // mirrors are left untouched so their values are preserved.\n mirror.value = ''\n }\n }\n }\n}\n\nexport const createFormat =\n (state: FormControllerState, helpers: FormControllerHelpers) =>\n (config: FieldFormatConfig): FormController => {\n if (state.isDestroyed) return state.api!\n if (!state.element) return state.api!\n\n // Fire and forget: the loader surfaces a single console.error if\n // the peer is missing, then resolves with `null`.\n void applyFormat(state, helpers, config)\n\n return state.api!\n }\n\nexport const createFormatAll = createFormat\n\n// Cleanup hook consumed by `api/destroy.ts`. Removes every listener\n// registered through this module and drops the owned raw mirrors that\n// were created during the controller's lifetime. Mirrors that already\n// existed in the DOM (i.e. `mirrorIsOwned === false`) are left alone.\nexport const cleanupFormatRegistry = (state: FormControllerState): void => {\n if (!state.element) {\n registry.delete(state)\n return\n }\n\n const bucket = registry.get(state)\n if (!bucket) {\n state.element\n .querySelectorAll<HTMLInputElement>(`[${FORMATTER_RAW_ATTRIBUTE}]`)\n .forEach(node => node.remove())\n return\n }\n\n for (const [fieldName, entry] of bucket) {\n state.element.removeEventListener('input', entry.handler)\n if (entry.mirrorIsOwned) {\n const mirror = findRawMirror(state.element, entry.mirrorName)\n mirror?.remove()\n }\n bucket.delete(fieldName)\n state.formattedFields.delete(fieldName)\n }\n\n // Catch any leftover owned mirrors (defensive — should not happen\n // because every `applyFormat` path registers in the bucket, but the\n // second pass guarantees no orphans escape `destroy()`).\n state.element\n .querySelectorAll<HTMLInputElement>(`[${FORMATTER_RAW_ATTRIBUTE}]`)\n .forEach(node => node.remove())\n}","// api/destroy.ts\n// Tears down the controller: removes DOM listeners, disconnects the\n// mutation observer, clears caches, and resets internal state.\n// Safe to call multiple times.\n\nimport { cleanupFormatRegistry } from './format'\nimport type { FormControllerHelpers, FormControllerState } from '../core/state'\n\nexport const createDestroy =\n (state: FormControllerState, _helpers: FormControllerHelpers) =>\n () => {\n if (state.isDestroyed) return\n\n state.isDestroyed = true\n for (const listener of state.listeners) {\n listener.element.removeEventListener(listener.type, listener.handler)\n }\n state.listeners.length = 0\n\n if (state.autoSubmitTimer) {\n clearTimeout(state.autoSubmitTimer)\n state.autoSubmitTimer = null\n }\n\n state.mutationObserver?.disconnect()\n state.mutationObserver = null\n state.watchedFields.clear()\n state.subscribers.clear()\n state.submitHandlers.clear()\n state.fieldCache.clear()\n state.manualErrors = {}\n state.validationErrors = {}\n\n // Detach format listeners and drop the hidden raw mirrors that\n // `format()` created for this controller. Mirrors that pre-existed\n // in the DOM (i.e. not owned by us) are preserved.\n cleanupFormatRegistry(state)\n }\n","// api/disable-auto-submit.ts\n// Disables autoSubmit and cancels any pending debounce timer.\n\nimport type { FormControllerHelpers, FormControllerState } from '../core/state'\n\nexport const createDisableAutoSubmit =\n (state: FormControllerState, helpers: FormControllerHelpers) =>\n () => {\n state.autoSubmitEnabled = false\n if (state.autoSubmitTimer) {\n clearTimeout(state.autoSubmitTimer)\n state.autoSubmitTimer = null\n }\n helpers.notifySubscribers()\n return state.api!\n }\n","// Pure serialization helpers for HTMLFormElement.\n// No DOM mutation, no controller state — safe to tree-shake into any bundle.\n\nimport type { SerializedFormResult, SerializedFormValue } from './types'\n\nconst isEmptyFile = (value: FormDataEntryValue): value is File =>\n value instanceof File && value.size === 0 && value.name === ''\n\nconst appendValue = (\n data: Record<string, SerializedFormValue>,\n key: string,\n value: FormDataEntryValue\n): void => {\n const current = data[key]\n if (current === undefined) {\n data[key] = value\n return\n }\n if (Array.isArray(current)) {\n current.push(value)\n return\n }\n data[key] = [current, value]\n}\n\nexport const parseFormData = (formElement: HTMLFormElement): SerializedFormResult => {\n const raw = new FormData(formElement)\n const formData = new FormData()\n const data: Record<string, SerializedFormValue> = {}\n\n raw.forEach((value, key) => {\n if (isEmptyFile(value)) return\n formData.append(key, value)\n appendValue(data, key, value)\n })\n\n return { data, formData }\n}\n","// api/get-data.ts\n// Returns the form serialized as both a plain object and a fresh\n// FormData instance. Empty file inputs are filtered out.\n\nimport type { FormControllerHelpers, FormControllerState } from '../core/state'\nimport type { SerializedFormResult } from '../core/types'\nimport { parseFormData } from '../core/serialize'\n\nexport const createGetData =\n (_state: FormControllerState, _helpers: FormControllerHelpers) =>\n (): SerializedFormResult => {\n if (!_state.element) return { data: {}, formData: new FormData() }\n return parseFormData(_state.element)\n }\n","// api/get-field.ts\n// Returns the underlying DOM field(s) for a given name:\n// - null when no field matches\n// - a single element when there's exactly one\n// - an array when there are multiple (radios, checkbox groups)\n\nimport type { FormControllerHelpers, FormControllerState } from '../core/state'\nimport type { FormFieldElement } from '../core/types'\n\nexport const createGetField =\n (state: FormControllerState, helpers: FormControllerHelpers) =>\n (name: string): FormFieldElement | FormFieldElement[] | null => {\n const fields = helpers.getFieldsByName(name)\n if (fields.length === 0) return null\n return fields.length === 1 ? fields[0]! : fields\n }\n","// Shared mutable state for the controller plus the small pure helpers used\n// by both core utilities and the api method factories.\n\nimport type {\n FieldFormatConfig,\n FormController,\n FormControllerOptions,\n FormErrors,\n FormFieldElement,\n FormFieldWatcher,\n FormStateListener,\n FormStateSnapshot,\n FormValues,\n ValidationResult,\n ValidationSchema,\n VisualAttributes\n} from './types'\n\nexport interface FormControllerState {\n element: HTMLFormElement | null\n options: FormControllerOptions\n attributes: Required<VisualAttributes>\n validators: ValidationSchema\n watchedFields: Map<string, Set<FormFieldWatcher>>\n subscribers: Set<FormStateListener>\n submitHandlers: Set<{\n callback: import('./types').FormSubmitHandler\n preventDefault: boolean\n }>\n fieldCache: Map<string, FormFieldElement[]>\n manualErrors: FormErrors\n validationErrors: FormErrors\n isValidated: boolean\n autoSubmitEnabled: boolean\n autoSubmitDebounce: number\n submitCount: number\n autoSubmitTimer: ReturnType<typeof setTimeout> | null\n isDestroyed: boolean\n listeners: Array<{\n element: EventTarget\n type: string\n handler: EventListenerOrEventListenerObject\n }>\n mutationObserver: MutationObserver | null\n // Tracks every field that `format()` is currently formatting, so\n // re-bindings are idempotent and `destroy()` can clean up the\n // associated listeners + hidden raw mirrors.\n formattedFields: Map<\n string,\n {\n config: FieldFormatConfig\n mirrorName: string\n mirrorIsOwned: boolean\n }\n >\n // Back-reference to the assembled public controller. Populated by the\n // controller orchestrator after the api method factories are wired.\n api: FormController | null\n}\n\n// Internal helpers bound by the controller orchestrator and consumed by\n// the api method factories. Kept here so api/*.ts only depends on the\n// shape, not on the orchestrator implementation.\nexport interface FormControllerHelpers {\n notifySubscribers: () => void\n clearFieldCache: () => void\n getFieldsByName: (name: string) => FormFieldElement[]\n getTrackedFieldNames: () => string[]\n getValues: () => FormValues\n getMergedErrors: () => FormErrors\n syncVisualState: (names?: string[]) => void\n validateNames: (names?: string[]) => ValidationResult\n emitFieldWatchers: (name: string) => void\n scheduleAutoSubmit: () => void\n}\n\nexport const createEmptyFormState = (): FormStateSnapshot => ({\n values: {},\n errors: {},\n filledFields: [],\n isValid: true,\n isValidated: false,\n autoSubmit: false,\n submitCount: 0\n})\n\nexport const cloneErrors = (errors: FormErrors): FormErrors =>\n Object.fromEntries(\n Object.entries(errors).map(([field, messages]) => [field, [...messages]])\n )\n\nexport const mergeErrors = (left: FormErrors, right: FormErrors): FormErrors => {\n const merged: FormErrors = { ...left }\n for (const [field, messages] of Object.entries(right)) {\n merged[field] = merged[field] ? [...merged[field], ...messages] : [...messages]\n }\n return merged\n}\n","// api/get-state.ts\n// Builds a snapshot of values, errors, filled fields and submission\n// metadata. Pure read — does not mutate controller state.\n\nimport type { FormControllerHelpers, FormControllerState } from '../core/state'\nimport type { FormStateSnapshot } from '../core/types'\nimport { cloneErrors, createEmptyFormState } from '../core/state'\n\nexport const createGetState =\n (state: FormControllerState, helpers: FormControllerHelpers) =>\n (): FormStateSnapshot => {\n const values = helpers.getValues()\n const errors = cloneErrors(helpers.getMergedErrors())\n const filledFields = helpers.getTrackedFieldNames().filter(name => {\n const value = values[name]\n if (Array.isArray(value)) return value.length > 0\n if (typeof value === 'string') return value.trim().length > 0\n return value !== undefined\n })\n\n return {\n ...createEmptyFormState(),\n values,\n errors,\n filledFields,\n isValid: Object.keys(errors).length === 0,\n isValidated: state.isValidated,\n autoSubmit: state.autoSubmitEnabled,\n submitCount: state.submitCount\n }\n }\n","// Low-level DOM helpers used by the controller orchestrator and by api\n// methods that need direct DOM access. Pure: no controller state.\n\nimport type { FormFieldElement, FormFieldValue, FormTarget } from './types'\n\nconst isFormFieldElement = (value: Element): value is FormFieldElement =>\n value instanceof HTMLInputElement ||\n value instanceof HTMLSelectElement ||\n value instanceof HTMLTextAreaElement\n\nexport const resolveFormElement = (target: FormTarget): HTMLFormElement | null => {\n if (!target) return null\n if (target instanceof HTMLFormElement) return target\n if (typeof target === 'string') {\n const node = document.getElementById(target)\n return node instanceof HTMLFormElement ? node : null\n }\n if ('current' in target) {\n return target.current instanceof HTMLFormElement ? target.current : null\n }\n return null\n}\n\nexport const getNamedFields = (form: HTMLFormElement): FormFieldElement[] =>\n Array.from(form.querySelectorAll('input[name], select[name], textarea[name]')).filter(\n isFormFieldElement\n )\n\nexport const queryNamedFields = (\n form: HTMLFormElement,\n name: string\n): FormFieldElement[] => getNamedFields(form).filter(field => field.name === name)\n\nexport const isFieldElement = (value: Element): value is FormFieldElement =>\n isFormFieldElement(value)\n\nexport const isFieldFilled = (field: FormFieldElement): boolean => {\n if (field instanceof HTMLInputElement) {\n if (field.type === 'checkbox' || field.type === 'radio') return field.checked\n if (field.type === 'file') return Boolean(field.files && field.files.length > 0)\n return field.value.trim() !== ''\n }\n return field.value !== ''\n}\n\nexport const applyBooleanAttribute = (\n field: FormFieldElement,\n attribute: string,\n enabled: boolean\n): void => {\n if (enabled) {\n field.setAttribute(attribute, '')\n } else {\n field.removeAttribute(attribute)\n }\n}\n\nexport const readFieldValue = (fields: FormFieldElement[]): FormFieldValue => {\n if (fields.length === 0) return undefined\n const first = fields[0]\n if (!first) return undefined\n\n if (first instanceof HTMLSelectElement || first instanceof HTMLTextAreaElement) {\n return first.value\n }\n\n if (first instanceof HTMLInputElement) {\n if (first.type === 'radio') {\n const checked = fields.find(\n (f): f is HTMLInputElement =>\n f instanceof HTMLInputElement && f.checked\n )\n return checked?.value ?? ''\n }\n\n if (first.type === 'checkbox') {\n const values = fields\n .filter(\n (f): f is HTMLInputElement =>\n f instanceof HTMLInputElement && f.checked\n )\n .map(f => f.value)\n return values.length > 1 ? values : (values[0] ?? '')\n }\n\n if (first.type === 'file') {\n return first.files ? Array.from(first.files) : []\n }\n }\n\n return first.value\n}\n\nexport const writeFieldValue = (fields: FormFieldElement[], value: unknown): void => {\n const normalizedArray = Array.isArray(value)\n ? value.map(item => String(item))\n : null\n const normalizedValue = value === undefined || value === null ? '' : String(value)\n\n for (const field of fields) {\n if (field instanceof HTMLInputElement) {\n if (field.type === 'checkbox') {\n field.checked = normalizedArray\n ? normalizedArray.includes(field.value)\n : normalizedValue === field.value\n continue\n }\n if (field.type === 'radio') {\n field.checked = field.value === normalizedValue\n continue\n }\n if (field.type === 'file') {\n if (Array.isArray(value) && value.length === 0) field.value = ''\n continue\n }\n field.value = normalizedValue\n continue\n }\n\n field.value = normalizedValue\n }\n}\n\nexport const clearAttributes = (form: HTMLFormElement, attributes: string[]): void => {\n for (const attribute of attributes) {\n form\n .querySelectorAll(`[${attribute}]`)\n .forEach(node => node.removeAttribute(attribute))\n }\n}\n\nexport const submitForm = (form: HTMLFormElement): void => {\n if (typeof form.requestSubmit === 'function') {\n form.requestSubmit()\n return\n }\n const submitButton = form.querySelector(\n 'button[type=\"submit\"], input[type=\"submit\"]'\n ) as HTMLButtonElement | HTMLInputElement | null\n if (submitButton) {\n submitButton.click()\n return\n }\n form.submit()\n}\n","// api/get-value.ts\n// Returns the current value of a field, with normalization for checkboxes,\n// radios, files, and multi-value inputs.\n\nimport type { FormControllerHelpers, FormControllerState } from '../core/state'\nimport type { FormFieldValue } from '../core/types'\nimport { readFieldValue } from '../core/dom'\n\nexport const createGetValue =\n (state: FormControllerState, helpers: FormControllerHelpers) =>\n (name: string): FormFieldValue => readFieldValue(helpers.getFieldsByName(name))\n","// api/observe.ts\n// Registers a callback that runs on mount and whenever the field changes.\n// Returns an unsubscribe function.\n\nimport type { FormControllerHelpers, FormControllerState } from '../core/state'\nimport type { FormFieldWatcher } from '../core/types'\n\nexport const createObserve =\n (state: FormControllerState, helpers: FormControllerHelpers) =>\n (field: string, callback: FormFieldWatcher) => {\n const callbacks = state.watchedFields.get(field) ?? new Set<FormFieldWatcher>()\n callbacks.add(callback)\n state.watchedFields.set(field, callbacks)\n\n if (state.element) {\n callback(state.api!.getValue(field), state.api!.getField(field), state.element, state.api!.getState())\n }\n\n return () => {\n const current = state.watchedFields.get(field)\n current?.delete(callback)\n if (current && current.size === 0) {\n state.watchedFields.delete(field)\n }\n }\n }\n","// api/on-submit.ts\n// Registers a submit handler. preventDefault=true intercepts valid submits,\n// which is the right choice for AJAX/fetch flows. Invalid submits are\n// always prevented regardless of the flag.\n\nimport type { FormControllerHelpers, FormControllerState } from '../core/state'\nimport type { FormSubmitHandler } from '../core/types'\n\nexport const createOnSubmit =\n (state: FormControllerState, _helpers: FormControllerHelpers) =>\n (callback: FormSubmitHandler, preventDefault = true) => {\n state.submitHandlers.add({ callback, preventDefault })\n return state.api!\n }\n","// api/prefill.ts\n// Reads window.location.search and writes matching values into the form.\n// Pass a field name to scope prefill to a single field.\n\nimport type { FormControllerHelpers, FormControllerState } from '../core/state'\n\nexport const createPrefill =\n (state: FormControllerState, _helpers: FormControllerHelpers) =>\n (fieldName?: string) => {\n if (!state.element) return state.api!\n\n const params = new URLSearchParams(window.location.search)\n params.forEach((value, key) => {\n if (fieldName && fieldName !== key) return\n state.api!.setValue(key, value)\n })\n return state.api!\n }\n","// api/reset.ts\n// Resets the native form, clears manual and validation errors, strips\n// css-filled/css-error attributes, and notifies subscribers.\n\nimport type { FormControllerHelpers, FormControllerState } from '../core/state'\nimport { clearAttributes } from '../core/dom'\n\nexport const createReset =\n (state: FormControllerState, helpers: FormControllerHelpers) =>\n () => {\n if (!state.element) return state.api!\n\n state.element.reset()\n state.manualErrors = {}\n state.validationErrors = {}\n clearAttributes(state.element, [state.attributes.error, state.attributes.filled])\n\n if (state.isValidated) helpers.syncVisualState()\n helpers.notifySubscribers()\n return state.api!\n }\n","// api/validate.ts\n// Runs validation. Marks the form as validated, syncs visual state, and\n// returns the validation result for callers that want it.\n\nimport type { FormControllerHelpers, FormControllerState } from '../core/state'\nimport type { ValidationResult } from '../core/types'\n\nexport const createValidate =\n (state: FormControllerState, helpers: FormControllerHelpers) =>\n (fields?: string[]): ValidationResult => {\n state.isValidated = true\n helpers.syncVisualState(fields)\n return helpers.validateNames(fields)\n }\n","// api/revalidate.ts\n// Alias of validate(); kept separate for readability at call sites that\n// want to express \"re-run validation now\".\n\nimport type { FormControllerHelpers, FormControllerState } from '../core/state'\nimport { createValidate } from './validate'\n\nexport const createRevalidate = createValidate\n","// api/set-errors.ts\n// Sets manual errors. Accepts an array of field names (each gets a\n// generic message) or a FormErrors map with per-field messages.\n\nimport type { FormControllerHelpers, FormControllerState } from '../core/state'\nimport type { FormErrors } from '../core/types'\nimport { cloneErrors } from '../core/state'\n\nexport const createSetErrors =\n (state: FormControllerState, helpers: FormControllerHelpers) =>\n (fields: string[] | FormErrors) => {\n if (Array.isArray(fields)) {\n state.manualErrors = {\n ...state.manualErrors,\n ...Object.fromEntries(fields.map(field => [field, ['Invalid value.']]))\n }\n } else {\n state.manualErrors = {\n ...state.manualErrors,\n ...cloneErrors(fields)\n }\n }\n\n helpers.syncVisualState(Array.isArray(fields) ? fields : Object.keys(fields))\n helpers.notifySubscribers()\n return state.api!\n }\n","// api/set-value.ts\n// Writes a value into a field and dispatches the correct DOM event so\n// listeners, validators, and visual state run as if a user typed.\n\nimport type { FormControllerHelpers, FormControllerState } from '../core/state'\nimport { writeFieldValue } from '../core/dom'\n\nexport const createSetValue =\n (state: FormControllerState, helpers: FormControllerHelpers) =>\n (name: string, value: unknown) => {\n const fields = helpers.getFieldsByName(name)\n if (fields.length === 0) return state.api!\n\n writeFieldValue(fields, value)\n const firstField = fields[0]\n if (!firstField) return state.api!\n\n const eventType =\n firstField instanceof HTMLSelectElement ||\n (firstField instanceof HTMLInputElement &&\n (firstField.type === 'checkbox' || firstField.type === 'radio'))\n ? 'change'\n : 'input'\n\n firstField.dispatchEvent(new Event(eventType, { bubbles: true }))\n return state.api!\n }\n","// api/subscribe.ts\n// Subscribes to global form state. Listener fires once immediately with\n// the current snapshot, then on every state mutation. Returns an\n// unsubscribe function.\n\nimport type { FormControllerHelpers, FormControllerState } from '../core/state'\nimport type { FormStateListener } from '../core/types'\n\nexport const createSubscribe =\n (state: FormControllerState, _helpers: FormControllerHelpers) =>\n (listener: FormStateListener) => {\n state.subscribers.add(listener)\n listener(state.api!.getState())\n return () => {\n state.subscribers.delete(listener)\n }\n }\n","// api/unwatch.ts\n// Removes watched callbacks. With no args, clears every watcher. With only\n// a field, removes all callbacks for that field. With field + callback,\n// removes just that callback.\n\nimport type { FormControllerHelpers, FormControllerState } from '../core/state'\nimport type { FormFieldWatcher } from '../core/types'\n\nexport const createUnwatch =\n (state: FormControllerState, _helpers: FormControllerHelpers) =>\n (field?: string, callback?: FormFieldWatcher) => {\n if (!field) {\n state.watchedFields.clear()\n return state.api!\n }\n\n if (!callback) {\n state.watchedFields.delete(field)\n return state.api!\n }\n\n const callbacks = state.watchedFields.get(field)\n callbacks?.delete(callback)\n if (callbacks && callbacks.size === 0) {\n state.watchedFields.delete(field)\n }\n return state.api!\n }\n","// api/watch.ts\n// Chainable alias over `observe`. Use this when you want a fluent setup\n// without holding the returned unsubscribe function.\n\nimport type { FormControllerHelpers, FormControllerState } from '../core/state'\nimport type { FormFieldWatcher } from '../core/types'\nimport { createObserve } from './observe'\n\nexport const createWatch = (state: FormControllerState, helpers: FormControllerHelpers) =>\n (field: string, callback: FormFieldWatcher) => {\n const observe = createObserve(state, helpers)\n observe(field, callback)\n return state.api!\n }\n","// Pure validation routines. No DOM, no controller state.\n\nimport type {\n FieldValidationContext,\n FieldValidationRules,\n FormErrors,\n FormFieldValue,\n FormValues,\n RuleConfig,\n ValidationResult,\n ValidationSchema\n} from './types'\n\nconst resolveRule = <T>(rule: RuleConfig<T> | undefined) => {\n if (rule === undefined) {\n return { value: undefined as T | undefined, message: undefined as string | undefined }\n }\n if (typeof rule === 'object' && rule !== null && 'value' in rule) {\n return { value: rule.value, message: rule.message }\n }\n return { value: rule, message: undefined as string | undefined }\n}\n\nconst hasValue = (value: FormFieldValue): boolean => {\n if (Array.isArray(value)) return value.length > 0\n if (typeof value === 'string') return value.trim().length > 0\n return value !== undefined\n}\n\nconst getValueLength = (value: FormFieldValue): number => {\n if (Array.isArray(value)) return value.length\n if (typeof value === 'string') return value.length\n return 0\n}\n\nconst toPatternTarget = (value: FormFieldValue): string => {\n if (typeof value === 'string') return value\n if (Array.isArray(value)) {\n return value\n .map(entry => (typeof entry === 'string' ? entry : entry.name))\n .join(',')\n }\n return ''\n}\n\nexport const validateFieldValue = (\n field: string,\n value: FormFieldValue,\n rules: FieldValidationRules,\n values: FormValues\n): string[] => {\n const errors: string[] = []\n const context: FieldValidationContext = { field, value, values }\n const required = resolveRule(rules.required)\n const minLength = resolveRule(rules.minLength)\n const maxLength = resolveRule(rules.maxLength)\n const pattern = resolveRule(rules.pattern)\n\n if (required.value && !hasValue(value)) {\n errors.push(required.message ?? 'This field is required.')\n }\n if (minLength.value !== undefined && getValueLength(value) < minLength.value) {\n errors.push(minLength.message ?? `Minimum length is ${minLength.value}.`)\n }\n if (maxLength.value !== undefined && getValueLength(value) > maxLength.value) {\n errors.push(maxLength.message ?? `Maximum length is ${maxLength.value}.`)\n }\n if (pattern.value && hasValue(value) && !pattern.value.test(toPatternTarget(value))) {\n errors.push(pattern.message ?? 'Value does not match the required pattern.')\n }\n\n const custom = rules.validate\n ? Array.isArray(rules.validate)\n ? rules.validate\n : [rules.validate]\n : []\n\n for (const validator of custom) {\n const result = validator(context)\n if (typeof result === 'string' && result.length > 0) errors.push(result)\n if (result === false) errors.push('Validation failed.')\n }\n\n return errors\n}\n\nexport const validateValues = (\n values: FormValues,\n schema: ValidationSchema\n): ValidationResult => {\n const errors: FormErrors = {}\n for (const [field, rules] of Object.entries(schema)) {\n const messages = validateFieldValue(field, values[field], rules, values)\n if (messages.length > 0) errors[field] = messages\n }\n return {\n isValid: Object.keys(errors).length === 0,\n errors\n }\n}\n","// Core orchestrator. Wires shared state, internal helpers and the public\n// api method factories into a single FormController instance.\n//\n// Internal event handlers and DOM wiring live here. Every public method\n// lives in `../api/<method>.ts` as an isolated factory.\n\nimport { createAppend } from '../api/append'\nimport { createAutoSubmit } from '../api/auto-submit'\nimport { createClearErrors } from '../api/clear-errors'\nimport { createDestroy } from '../api/destroy'\nimport { createDisableAutoSubmit } from '../api/disable-auto-submit'\nimport { createFormat, createFormatAll } from '../api/format'\nimport { createGetData } from '../api/get-data'\nimport { createGetField } from '../api/get-field'\nimport { createGetState } from '../api/get-state'\nimport { createGetValue } from '../api/get-value'\nimport { createObserve } from '../api/observe'\nimport { createOnSubmit } from '../api/on-submit'\nimport { createPrefill } from '../api/prefill'\nimport { createReset } from '../api/reset'\nimport { createRevalidate } from '../api/revalidate'\nimport { createSetErrors } from '../api/set-errors'\nimport { createSetValue } from '../api/set-value'\nimport { createSubscribe } from '../api/subscribe'\nimport { createUnwatch } from '../api/unwatch'\nimport { createValidate } from '../api/validate'\nimport { createWatch } from '../api/watch'\nimport { validateFieldValue } from '../core/validation'\nimport {\n applyBooleanAttribute,\n getNamedFields,\n isFieldElement,\n isFieldFilled,\n queryNamedFields,\n readFieldValue,\n resolveFormElement,\n submitForm,\n writeFieldValue\n} from './dom'\nimport { cloneErrors, mergeErrors } from './state'\nimport type {\n FormController,\n FormControllerOptions,\n FormFieldElement,\n FormFieldValue,\n FormTarget,\n ValidationResult,\n VisualAttributes\n} from './types'\nimport type { FormControllerHelpers, FormControllerState } from './state'\n\nconst DEFAULT_ATTRIBUTES: Required<VisualAttributes> = {\n filled: 'css-filled',\n error: 'css-error'\n}\n\nconst DEFAULT_OPTIONS = {\n autoValidate: true,\n clearErrorsOnSubmit: true,\n clearManualErrorsOnChange: true\n} satisfies Pick<\n FormControllerOptions,\n 'autoValidate' | 'clearErrorsOnSubmit' | 'clearManualErrorsOnChange'\n>\n\nexport const createFormController = (\n target: FormTarget,\n options: FormControllerOptions = {}\n): FormController => {\n const element = resolveFormElement(target)\n const attributes = { ...DEFAULT_ATTRIBUTES, ...options.attributes }\n const normalizedOptions: FormControllerOptions = {\n ...DEFAULT_OPTIONS,\n ...options,\n attributes\n }\n\n const state: FormControllerState = {\n element,\n options: normalizedOptions,\n attributes,\n validators: normalizedOptions.validators ?? {},\n watchedFields: new Map(),\n subscribers: new Set(),\n submitHandlers: new Set(),\n fieldCache: new Map(),\n manualErrors: {},\n validationErrors: {},\n isValidated: Boolean(normalizedOptions.autoValidate),\n autoSubmitEnabled: false,\n autoSubmitDebounce: 0,\n submitCount: 0,\n autoSubmitTimer: null,\n isDestroyed: false,\n listeners: [],\n mutationObserver: null,\n formattedFields: new Map(),\n api: null\n }\n\n // ------- Internal helpers (no DOM wiring of their own) -----------------\n\n const notifySubscribers = () => {\n if (!state.api) return\n const snapshot = state.api.getState()\n state.subscribers.forEach(listener => listener(snapshot))\n }\n\n const addListener = (\n node: EventTarget,\n type: string,\n handler: EventListenerOrEventListenerObject\n ) => {\n node.addEventListener(type, handler)\n state.listeners.push({ element: node, type, handler })\n }\n\n const clearFieldCache = () => state.fieldCache.clear()\n\n const getFieldsByName = (name: string): FormFieldElement[] => {\n if (!state.element) return []\n if (!state.fieldCache.has(name)) {\n state.fieldCache.set(name, queryNamedFields(state.element, name))\n }\n return state.fieldCache.get(name) ?? []\n }\n\n const getTrackedFieldNames = (): string[] => {\n if (!state.element) return Object.keys(state.validators)\n const names = new Set<string>(Object.keys(state.validators))\n for (const field of getNamedFields(state.element)) {\n names.add(field.name)\n }\n return Array.from(names)\n }\n\n const getValues = () => {\n const values: Record<string, FormFieldValue> = {}\n for (const name of getTrackedFieldNames()) {\n values[name] = readFieldValue(getFieldsByName(name))\n }\n return values\n }\n\n const getMergedErrors = () => mergeErrors(state.validationErrors, state.manualErrors)\n\n const syncVisualState = (names?: string[]) => {\n if (!state.element) return\n const targetNames = names ?? getTrackedFieldNames()\n const errors = getMergedErrors()\n for (const name of targetNames) {\n const fields = getFieldsByName(name)\n const hasError = Boolean(errors[name]?.length)\n for (const field of fields) {\n applyBooleanAttribute(field, attributes.filled, isFieldFilled(field))\n applyBooleanAttribute(field, attributes.error, hasError)\n }\n }\n }\n\n // readFieldValue/writeFieldValue are exported from core/dom for direct\n // callers; referenced here so the linter keeps them as part of the\n // controlled API surface.\n void readFieldValue\n void writeFieldValue\n\n const validateNames = (names?: string[]): ValidationResult => {\n const targetNames = names ?? Object.keys(state.validators)\n const values = getValues()\n const nextValidationErrors = names ? cloneErrors(state.validationErrors) : {}\n\n for (const name of targetNames) {\n const rules = state.validators[name]\n if (!rules) {\n delete nextValidationErrors[name]\n continue\n }\n const messages = validateFieldValue(name, values[name], rules, values)\n if (messages.length > 0) nextValidationErrors[name] = messages\n else delete nextValidationErrors[name]\n }\n\n state.validationErrors = nextValidationErrors\n syncVisualState(names)\n\n return {\n isValid: Object.keys(getMergedErrors()).length === 0,\n errors: cloneErrors(getMergedErrors())\n }\n }\n\n const emitFieldWatchers = (name: string) => {\n if (!state.element || !state.api) return\n const callbacks = state.watchedFields.get(name)\n if (!callbacks || callbacks.size === 0) return\n const value = state.api.getValue(name)\n const fieldElement = state.api.getField(name)\n const snapshot = state.api.getState()\n callbacks.forEach(callback => callback(value, fieldElement, state.element!, snapshot))\n }\n\n const scheduleAutoSubmit = () => {\n if (!state.element || !state.autoSubmitEnabled) return\n if (state.autoSubmitTimer) clearTimeout(state.autoSubmitTimer)\n if (state.autoSubmitDebounce > 0) {\n state.autoSubmitTimer = setTimeout(\n () => submitForm(state.element!),\n state.autoSubmitDebounce\n )\n return\n }\n submitForm(state.element)\n }\n\n const helpers: FormControllerHelpers = {\n notifySubscribers,\n clearFieldCache,\n getFieldsByName,\n getTrackedFieldNames,\n getValues,\n getMergedErrors,\n syncVisualState,\n validateNames,\n emitFieldWatchers,\n scheduleAutoSubmit\n }\n\n // ------- DOM event wiring ---------------------------------------------\n\n const handleDelegatedEvent = (event: Event) => {\n const target = event.target\n if (!(target instanceof Element) || !isFieldElement(target)) return\n const name = target.name\n if (!name) return\n\n clearFieldCache()\n\n if (normalizedOptions.clearManualErrorsOnChange) {\n delete state.manualErrors[name]\n }\n\n syncVisualState([name])\n\n if (state.isValidated && state.validators[name]) {\n validateNames([name])\n }\n\n emitFieldWatchers(name)\n notifySubscribers()\n scheduleAutoSubmit()\n }\n\n const handleSubmitEvent = (event: Event) => {\n if (!state.element || state.isDestroyed || !state.api) return\n\n if (normalizedOptions.clearErrorsOnSubmit) state.manualErrors = {}\n\n const validation = state.api.validate()\n const handlers = Array.from(state.submitHandlers)\n const shouldPrevent = validation.isValid\n ? handlers.some(handler => handler.preventDefault)\n : true\n\n if (shouldPrevent) event.preventDefault()\n\n state.submitCount += 1\n notifySubscribers()\n\n if (!validation.isValid) return\n\n const { data, formData } = state.api.getData()\n const snapshot = state.api.getState()\n handlers.forEach(handler =>\n handler.callback(state.element!, data, formData, snapshot)\n )\n }\n\n const startMutationObserver = () => {\n if (!state.element || typeof MutationObserver === 'undefined') return\n\n state.mutationObserver = new MutationObserver(() => {\n clearFieldCache()\n if (state.isValidated) {\n syncVisualState()\n validateNames()\n }\n notifySubscribers()\n })\n\n state.mutationObserver.observe(state.element, {\n childList: true,\n subtree: true,\n attributes: true,\n attributeFilter: ['name', 'type']\n })\n }\n\n // ------- Public api composition --------------------------------------\n\n const api: FormController = {\n get element() {\n return state.element\n },\n get f() {\n return state.element\n },\n get options() {\n return state.options\n },\n onSubmit: createOnSubmit(state, helpers),\n watch: createWatch(state, helpers),\n observe: createObserve(state, helpers),\n unwatch: createUnwatch(state, helpers),\n subscribe: createSubscribe(state, helpers),\n prefill: createPrefill(state, helpers),\n append: createAppend(state, helpers),\n setErrors: createSetErrors(state, helpers),\n clearErrors: createClearErrors(state, helpers),\n setValue: createSetValue(state, helpers),\n getValue: createGetValue(state, helpers),\n getField: createGetField(state, helpers),\n validate: createValidate(state, helpers),\n revalidate: createRevalidate(state, helpers),\n reset: createReset(state, helpers),\n autoSubmit: createAutoSubmit(state, helpers),\n disableAutoSubmit: createDisableAutoSubmit(state, helpers),\n getData: createGetData(state, helpers),\n getState: createGetState(state, helpers),\n format: createFormat(state, helpers),\n formatAll: createFormatAll(state, helpers),\n destroy: createDestroy(state, helpers)\n }\n\n state.api = api\n\n // ------- Lifecycle hooks ---------------------------------------------\n\n if (state.element) {\n addListener(state.element, 'input', handleDelegatedEvent)\n addListener(state.element, 'change', handleDelegatedEvent)\n addListener(state.element, 'submit', handleSubmitEvent)\n startMutationObserver()\n }\n\n if (normalizedOptions.autoSubmit) {\n api.autoSubmit(normalizedOptions.autoSubmit)\n }\n\n if (normalizedOptions.formats) {\n for (const config of Object.values(normalizedOptions.formats)) {\n api.format(config)\n }\n }\n\n if (state.isValidated) {\n syncVisualState()\n validateNames()\n }\n\n return api\n}\n","// api/form.ts\n// Public entry point: the `form(target, options)` factory.\n// Thin wrapper around the core orchestrator for ergonomic imports.\n\nimport { createFormController } from '../core/controller'\nimport type { FormControllerOptions, FormTarget } from '../core/types'\n\nexport const form = (\n target: FormTarget,\n options: FormControllerOptions = {}\n) => createFormController(target, options)\n"],"mappings":"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","names":["state","state","state","form","state","state","state","state","state","form","state","state","state","state","state","state","state","state","state","state","state","state","addListener","target"]}
|
package/dist/index.cjs
CHANGED
|
@@ -99,6 +99,10 @@ var findRawMirror = (form2, fieldName) => {
|
|
|
99
99
|
`[name="${cssEscape(`${fieldName}_raw`)}"]`
|
|
100
100
|
);
|
|
101
101
|
if (byName) return byName;
|
|
102
|
+
const byNameOld = form2.querySelector(
|
|
103
|
+
`[name="${cssEscape(`${fieldName}Raw`)}"]`
|
|
104
|
+
);
|
|
105
|
+
if (byNameOld) return byNameOld;
|
|
102
106
|
return null;
|
|
103
107
|
};
|
|
104
108
|
var ensureRawMirror = (form2, fieldName) => {
|
|
@@ -106,7 +110,7 @@ var ensureRawMirror = (form2, fieldName) => {
|
|
|
106
110
|
if (existing) return existing;
|
|
107
111
|
const input = document.createElement("input");
|
|
108
112
|
input.type = "hidden";
|
|
109
|
-
input.name = `${fieldName}
|
|
113
|
+
input.name = `${fieldName}_raw`;
|
|
110
114
|
input.setAttribute("aria-hidden", "true");
|
|
111
115
|
input.tabIndex = -1;
|
|
112
116
|
input.setAttribute(FORMATTER_RAW_ATTRIBUTE, fieldName);
|
|
@@ -237,6 +241,7 @@ var addListener = (state2, target, type, handler) => {
|
|
|
237
241
|
var buildHandler = (field, mirror, formatFn, formatType, formatOptions, mirrorFieldName) => {
|
|
238
242
|
const handler = (event) => {
|
|
239
243
|
if (!field.isConnected) return;
|
|
244
|
+
if (event.target !== field) return;
|
|
240
245
|
const rawInput = event.target.value;
|
|
241
246
|
const { formatted, raw } = formatFn(rawInput, formatType, formatOptions);
|
|
242
247
|
if (!formatted && !raw) {
|
|
@@ -266,8 +271,8 @@ var applyFormat = async (state2, helpers, config) => {
|
|
|
266
271
|
if (writable.length === 0) continue;
|
|
267
272
|
for (const field of writable) {
|
|
268
273
|
const existing = bucket.get(fieldName);
|
|
269
|
-
const mirrorName = config.rawField ?? `${fieldName}
|
|
270
|
-
let mirror = findRawMirror(state2.element,
|
|
274
|
+
const mirrorName = config.rawField ?? `${fieldName}_raw`;
|
|
275
|
+
let mirror = findRawMirror(state2.element, fieldName) ?? null;
|
|
271
276
|
const mirrorIsOwned = !mirror;
|
|
272
277
|
if (!mirror) mirror = ensureRawMirror(state2.element, fieldName);
|
|
273
278
|
if (mirror && mirror.name !== mirrorName) mirror.name = mirrorName;
|
|
@@ -280,6 +285,11 @@ var applyFormat = async (state2, helpers, config) => {
|
|
|
280
285
|
mirrorName: trackerMirrorName,
|
|
281
286
|
mirrorIsOwned
|
|
282
287
|
});
|
|
288
|
+
const current = field.value;
|
|
289
|
+
if (current !== "") {
|
|
290
|
+
const { formatted, raw } = formatter.format(current, formatType, formatOptions);
|
|
291
|
+
applyFormattedValue(field, mirror, formatted, raw);
|
|
292
|
+
}
|
|
283
293
|
continue;
|
|
284
294
|
}
|
|
285
295
|
const handler = buildHandler(
|
|
@@ -306,7 +316,7 @@ var applyFormat = async (state2, helpers, config) => {
|
|
|
306
316
|
if (initial !== "") {
|
|
307
317
|
const { formatted, raw } = formatter.format(initial, formatType, formatOptions);
|
|
308
318
|
applyFormattedValue(field, mirror, formatted, raw);
|
|
309
|
-
} else if (mirror && mirror.value !== "") {
|
|
319
|
+
} else if (mirrorIsOwned && mirror && mirror.value !== "") {
|
|
310
320
|
mirror.value = "";
|
|
311
321
|
}
|
|
312
322
|
}
|