@samline/forms 1.0.2 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/README.md +88 -186
  2. package/dist/browser/global.d.ts +152 -0
  3. package/dist/browser/global.global.js +780 -472
  4. package/dist/browser/global.global.js.map +1 -1
  5. package/dist/index.cjs +755 -468
  6. package/dist/index.cjs.map +1 -1
  7. package/dist/index.d.cts +141 -3
  8. package/dist/index.d.ts +141 -3
  9. package/dist/index.js +960 -13
  10. package/dist/index.js.map +1 -1
  11. package/package.json +8 -43
  12. package/dist/chunk-4NEGCRY2.js +0 -125
  13. package/dist/chunk-4NEGCRY2.js.map +0 -1
  14. package/dist/chunk-KIJEFIHP.js +0 -572
  15. package/dist/chunk-KIJEFIHP.js.map +0 -1
  16. package/dist/chunk-KKQD25KI.js +0 -11
  17. package/dist/chunk-KKQD25KI.js.map +0 -1
  18. package/dist/core/index.cjs +0 -154
  19. package/dist/core/index.cjs.map +0 -1
  20. package/dist/core/index.d.cts +0 -11
  21. package/dist/core/index.d.ts +0 -11
  22. package/dist/core/index.js +0 -13
  23. package/dist/core/index.js.map +0 -1
  24. package/dist/react/index.cjs +0 -730
  25. package/dist/react/index.cjs.map +0 -1
  26. package/dist/react/index.d.cts +0 -10
  27. package/dist/react/index.d.ts +0 -10
  28. package/dist/react/index.js +0 -43
  29. package/dist/react/index.js.map +0 -1
  30. package/dist/svelte/index.cjs +0 -760
  31. package/dist/svelte/index.cjs.map +0 -1
  32. package/dist/svelte/index.d.cts +0 -20
  33. package/dist/svelte/index.d.ts +0 -20
  34. package/dist/svelte/index.js +0 -72
  35. package/dist/svelte/index.js.map +0 -1
  36. package/dist/types-C0_1rYlm.d.cts +0 -94
  37. package/dist/types-C0_1rYlm.d.ts +0 -94
  38. package/dist/vanilla/index.cjs +0 -701
  39. package/dist/vanilla/index.cjs.map +0 -1
  40. package/dist/vanilla/index.d.cts +0 -7
  41. package/dist/vanilla/index.d.ts +0 -7
  42. package/dist/vanilla/index.js +0 -12
  43. package/dist/vanilla/index.js.map +0 -1
  44. package/dist/vue/index.cjs +0 -737
  45. package/dist/vue/index.cjs.map +0 -1
  46. package/dist/vue/index.d.cts +0 -13
  47. package/dist/vue/index.d.ts +0 -13
  48. package/dist/vue/index.js +0 -56
  49. package/dist/vue/index.js.map +0 -1
@@ -1,9 +1,17 @@
1
1
  "use strict";
2
- var SamlineForms = (() => {
2
+ var Forms = (() => {
3
+ var __create = Object.create;
3
4
  var __defProp = Object.defineProperty;
4
5
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
6
  var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
6
8
  var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
10
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
11
+ }) : x)(function(x) {
12
+ if (typeof require !== "undefined") return require.apply(this, arguments);
13
+ throw Error('Dynamic require of "' + x + '" is not supported');
14
+ });
7
15
  var __export = (target, all) => {
8
16
  for (var name in all)
9
17
  __defProp(target, name, { get: all[name], enumerable: true });
@@ -16,6 +24,14 @@ var SamlineForms = (() => {
16
24
  }
17
25
  return to;
18
26
  };
27
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
28
+ // If the importer is in node compatibility mode or this is not an ESM
29
+ // file that has been converted to a CommonJS file using a Babel-
30
+ // compatible transform (i.e. "__esModule" has not been set), then set
31
+ // "default" to the CommonJS "module.exports" for node compatibility.
32
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
33
+ mod
34
+ ));
19
35
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
20
36
 
21
37
  // src/browser/global.ts
@@ -24,6 +40,343 @@ var SamlineForms = (() => {
24
40
  default: () => global_default
25
41
  });
26
42
 
43
+ // src/api/append.ts
44
+ var createAppend = (state2, helpers) => ({ tag, content, class: className, atStart = false }) => {
45
+ if (!state2.element) return null;
46
+ helpers.clearFieldCache();
47
+ if (className) {
48
+ const classSelector = className.trim().split(/\s+/)[0];
49
+ const existing = classSelector ? state2.element.querySelector(`.${classSelector}`) : null;
50
+ existing?.remove();
51
+ }
52
+ const node = document.createElement(tag);
53
+ if (className) node.className = className;
54
+ node.innerHTML = content;
55
+ if (atStart && state2.element.firstChild) {
56
+ state2.element.insertBefore(node, state2.element.firstChild);
57
+ } else {
58
+ state2.element.appendChild(node);
59
+ }
60
+ return node;
61
+ };
62
+
63
+ // src/api/auto-submit.ts
64
+ var createAutoSubmit = (state2, helpers) => (next = true) => {
65
+ state2.autoSubmitEnabled = next !== false;
66
+ state2.autoSubmitDebounce = typeof next === "object" && next ? next.debounce ?? 0 : 0;
67
+ helpers.notifySubscribers();
68
+ return state2.api;
69
+ };
70
+
71
+ // src/api/clear-errors.ts
72
+ var createClearErrors = (state2, helpers) => (fields) => {
73
+ if (!fields) {
74
+ state2.manualErrors = {};
75
+ helpers.syncVisualState();
76
+ helpers.notifySubscribers();
77
+ return state2.api;
78
+ }
79
+ for (const field of fields) {
80
+ delete state2.manualErrors[field];
81
+ }
82
+ helpers.syncVisualState(fields);
83
+ helpers.notifySubscribers();
84
+ return state2.api;
85
+ };
86
+
87
+ // src/core/format-helpers.ts
88
+ var FORMATTER_RAW_ATTRIBUTE = "data-formatter-raw-for";
89
+ var cssEscape = (value) => {
90
+ if (typeof CSS !== "undefined" && typeof CSS.escape === "function") {
91
+ return CSS.escape(value);
92
+ }
93
+ return value.replace(/([!"#$%&'()*+,./:;<=>?@\[\\\]^`{|}~])/g, "\\$1");
94
+ };
95
+ var findRawMirror = (form2, fieldName) => {
96
+ const byAttr = form2.querySelector(
97
+ `[${FORMATTER_RAW_ATTRIBUTE}="${cssEscape(fieldName)}"]`
98
+ );
99
+ if (byAttr) return byAttr;
100
+ const byName = form2.querySelector(
101
+ `[name="${cssEscape(`${fieldName}_raw`)}"]`
102
+ );
103
+ if (byName) return byName;
104
+ return null;
105
+ };
106
+ var ensureRawMirror = (form2, fieldName) => {
107
+ const existing = findRawMirror(form2, fieldName);
108
+ if (existing) return existing;
109
+ const input = document.createElement("input");
110
+ input.type = "hidden";
111
+ input.name = `${fieldName}Raw`;
112
+ input.setAttribute("aria-hidden", "true");
113
+ input.tabIndex = -1;
114
+ input.setAttribute(FORMATTER_RAW_ATTRIBUTE, fieldName);
115
+ form2.appendChild(input);
116
+ return input;
117
+ };
118
+ var applyFormattedValue = (visible, mirror, formatted, raw) => {
119
+ const rawInput = visible.value;
120
+ const caret = clamp(
121
+ visible.selectionStart ?? rawInput.length,
122
+ 0,
123
+ rawInput.length
124
+ );
125
+ if (rawInput === formatted) {
126
+ if (mirror && mirror.value !== raw) mirror.value = raw;
127
+ return caret;
128
+ }
129
+ visible.value = formatted;
130
+ if (mirror) mirror.value = raw;
131
+ const newCaret = computeCursorPosition(caret, rawInput, formatted);
132
+ restoreCursor(visible, newCaret);
133
+ return newCaret;
134
+ };
135
+ var clamp = (value, min, max) => Math.max(min, Math.min(max, value));
136
+ var computeCursorPosition = (caretPos, rawInput, newValue) => {
137
+ if (newValue.length === 0) return 0;
138
+ if (caretPos >= rawInput.length) return newValue.length;
139
+ const targetNonFormat = countNonFormat(rawInput.slice(0, caretPos));
140
+ if (targetNonFormat === 0) {
141
+ let i = 0;
142
+ while (i < newValue.length && isFormatChar(newValue[i])) i += 1;
143
+ return i;
144
+ }
145
+ let seen = 0;
146
+ for (let i = 0; i < newValue.length; i += 1) {
147
+ if (!isFormatChar(newValue[i])) {
148
+ seen += 1;
149
+ if (seen === targetNonFormat) {
150
+ return Math.min(i + 1, newValue.length);
151
+ }
152
+ }
153
+ }
154
+ return newValue.length;
155
+ };
156
+ var countNonFormat = (value) => {
157
+ let count = 0;
158
+ for (const char of value) {
159
+ if (!isFormatChar(char)) count += 1;
160
+ }
161
+ return count;
162
+ };
163
+ var isFormatChar = (char) => {
164
+ if (!char) return false;
165
+ if (char >= "0" && char <= "9") return false;
166
+ if (char >= "a" && char <= "z" || char >= "A" && char <= "Z") return false;
167
+ return true;
168
+ };
169
+ var restoreCursor = (field, position) => {
170
+ const max = field.value.length;
171
+ const safe = clamp(position, 0, max);
172
+ const apply = () => {
173
+ try {
174
+ field.setSelectionRange(safe, safe);
175
+ } catch {
176
+ }
177
+ };
178
+ if (typeof navigator !== "undefined" && /Android/i.test(navigator.userAgent)) {
179
+ if (typeof requestAnimationFrame === "function") {
180
+ requestAnimationFrame(apply);
181
+ } else {
182
+ setTimeout(apply, 1);
183
+ }
184
+ return;
185
+ }
186
+ apply();
187
+ };
188
+
189
+ // src/core/formatter-loader.ts
190
+ var state = null;
191
+ var MISSING_MESSAGE = "[samline/forms] The `format()` and `formatAll()` methods require the @samline/formatter package. Install it with: npm i @samline/formatter (or pnpm/bun/yarn equivalent).";
192
+ var warned = false;
193
+ var warnOnce = () => {
194
+ if (warned) return;
195
+ warned = true;
196
+ console.error(MISSING_MESSAGE);
197
+ };
198
+ var loadFormatter = () => {
199
+ if (state && state.status === "resolved") {
200
+ if (!state.module) warnOnce();
201
+ return Promise.resolve(state.module);
202
+ }
203
+ if (state && state.status === "pending") {
204
+ return state.promise;
205
+ }
206
+ const promise = (async () => {
207
+ try {
208
+ const mod = await import(
209
+ /* @vite-ignore */
210
+ "@samline/formatter"
211
+ );
212
+ state = { status: "resolved", module: mod };
213
+ return mod;
214
+ } catch {
215
+ warnOnce();
216
+ state = { status: "resolved", module: null };
217
+ return null;
218
+ }
219
+ })();
220
+ state = { status: "pending", promise };
221
+ return promise;
222
+ };
223
+
224
+ // src/api/format.ts
225
+ var registry = /* @__PURE__ */ new WeakMap();
226
+ var getRegistry = (state2) => {
227
+ let bucket = registry.get(state2);
228
+ if (!bucket) {
229
+ bucket = /* @__PURE__ */ new Map();
230
+ registry.set(state2, bucket);
231
+ }
232
+ return bucket;
233
+ };
234
+ var resolveFieldNames = (config) => Array.isArray(config.field) ? config.field : [config.field];
235
+ var addListener = (state2, target, type, handler) => {
236
+ target.addEventListener(type, handler);
237
+ state2.listeners.push({ element: target, type, handler });
238
+ };
239
+ var buildHandler = (field, mirror, formatFn, formatType, formatOptions, mirrorFieldName) => {
240
+ const handler = (event) => {
241
+ if (!field.isConnected) return;
242
+ const rawInput = event.target.value;
243
+ const { formatted, raw } = formatFn(rawInput, formatType, formatOptions);
244
+ if (!formatted && !raw) {
245
+ field.value = "";
246
+ if (mirror && mirror.value !== "") mirror.value = "";
247
+ return;
248
+ }
249
+ applyFormattedValue(field, mirror, formatted, raw);
250
+ void mirrorFieldName;
251
+ };
252
+ return handler;
253
+ };
254
+ var applyFormat = async (state2, helpers, config) => {
255
+ if (!state2.element || !state2.api) return;
256
+ const formatter = await loadFormatter();
257
+ if (!formatter) return;
258
+ const formatType = config.type;
259
+ const formatOptions = config.options;
260
+ const fieldNames = resolveFieldNames(config);
261
+ const bucket = getRegistry(state2);
262
+ for (const fieldName of fieldNames) {
263
+ const fields = helpers.getFieldsByName(fieldName);
264
+ if (fields.length === 0) continue;
265
+ const writable = fields.filter(
266
+ (f) => f instanceof HTMLInputElement && f.type !== "checkbox" && f.type !== "radio" && f.type !== "file" && f.type !== "submit" && f.type !== "button" || f instanceof HTMLTextAreaElement
267
+ );
268
+ if (writable.length === 0) continue;
269
+ for (const field of writable) {
270
+ const existing = bucket.get(fieldName);
271
+ const mirrorName = config.rawField ?? `${fieldName}Raw`;
272
+ let mirror = findRawMirror(state2.element, mirrorName) ?? null;
273
+ const mirrorIsOwned = !mirror;
274
+ if (!mirror) mirror = ensureRawMirror(state2.element, fieldName);
275
+ if (mirror && mirror.name !== mirrorName) mirror.name = mirrorName;
276
+ const trackerMirrorName = mirror?.name ?? mirrorName;
277
+ if (existing) {
278
+ existing.mirrorName = trackerMirrorName;
279
+ existing.mirrorIsOwned = mirrorIsOwned;
280
+ state2.formattedFields.set(fieldName, {
281
+ config,
282
+ mirrorName: trackerMirrorName,
283
+ mirrorIsOwned
284
+ });
285
+ continue;
286
+ }
287
+ const handler = buildHandler(
288
+ field,
289
+ mirror,
290
+ formatter.format,
291
+ formatType,
292
+ formatOptions,
293
+ trackerMirrorName
294
+ );
295
+ addListener(state2, state2.element, "input", handler);
296
+ bucket.set(fieldName, {
297
+ fields: /* @__PURE__ */ new Set([field]),
298
+ mirrorName: trackerMirrorName,
299
+ handler,
300
+ mirrorIsOwned
301
+ });
302
+ state2.formattedFields.set(fieldName, {
303
+ config,
304
+ mirrorName: trackerMirrorName,
305
+ mirrorIsOwned
306
+ });
307
+ const initial = field.value;
308
+ if (initial !== "") {
309
+ const { formatted, raw } = formatter.format(initial, formatType, formatOptions);
310
+ applyFormattedValue(field, mirror, formatted, raw);
311
+ } else if (mirror && mirror.value !== "") {
312
+ mirror.value = "";
313
+ }
314
+ }
315
+ }
316
+ };
317
+ var createFormat = (state2, helpers) => (config) => {
318
+ if (state2.isDestroyed) return state2.api;
319
+ if (!state2.element) return state2.api;
320
+ void applyFormat(state2, helpers, config);
321
+ return state2.api;
322
+ };
323
+ var createFormatAll = createFormat;
324
+ var cleanupFormatRegistry = (state2) => {
325
+ if (!state2.element) {
326
+ registry.delete(state2);
327
+ return;
328
+ }
329
+ const bucket = registry.get(state2);
330
+ if (!bucket) {
331
+ state2.element.querySelectorAll(`[${FORMATTER_RAW_ATTRIBUTE}]`).forEach((node) => node.remove());
332
+ return;
333
+ }
334
+ for (const [fieldName, entry] of bucket) {
335
+ state2.element.removeEventListener("input", entry.handler);
336
+ if (entry.mirrorIsOwned) {
337
+ const mirror = findRawMirror(state2.element, entry.mirrorName);
338
+ mirror?.remove();
339
+ }
340
+ bucket.delete(fieldName);
341
+ state2.formattedFields.delete(fieldName);
342
+ }
343
+ state2.element.querySelectorAll(`[${FORMATTER_RAW_ATTRIBUTE}]`).forEach((node) => node.remove());
344
+ };
345
+
346
+ // src/api/destroy.ts
347
+ var createDestroy = (state2, _helpers) => () => {
348
+ if (state2.isDestroyed) return;
349
+ state2.isDestroyed = true;
350
+ for (const listener of state2.listeners) {
351
+ listener.element.removeEventListener(listener.type, listener.handler);
352
+ }
353
+ state2.listeners.length = 0;
354
+ if (state2.autoSubmitTimer) {
355
+ clearTimeout(state2.autoSubmitTimer);
356
+ state2.autoSubmitTimer = null;
357
+ }
358
+ state2.mutationObserver?.disconnect();
359
+ state2.mutationObserver = null;
360
+ state2.watchedFields.clear();
361
+ state2.subscribers.clear();
362
+ state2.submitHandlers.clear();
363
+ state2.fieldCache.clear();
364
+ state2.manualErrors = {};
365
+ state2.validationErrors = {};
366
+ cleanupFormatRegistry(state2);
367
+ };
368
+
369
+ // src/api/disable-auto-submit.ts
370
+ var createDisableAutoSubmit = (state2, helpers) => () => {
371
+ state2.autoSubmitEnabled = false;
372
+ if (state2.autoSubmitTimer) {
373
+ clearTimeout(state2.autoSubmitTimer);
374
+ state2.autoSubmitTimer = null;
375
+ }
376
+ helpers.notifySubscribers();
377
+ return state2.api;
378
+ };
379
+
27
380
  // src/core/serialize.ts
28
381
  var isEmptyFile = (value) => value instanceof File && value.size === 0 && value.name === "";
29
382
  var appendValue = (data, key, value) => {
@@ -39,19 +392,30 @@ var SamlineForms = (() => {
39
392
  data[key] = [current, value];
40
393
  };
41
394
  var parseFormData = (formElement) => {
42
- const rawFormData = new FormData(formElement);
395
+ const raw = new FormData(formElement);
43
396
  const formData = new FormData();
44
397
  const data = {};
45
- rawFormData.forEach((value, key) => {
46
- if (isEmptyFile(value)) {
47
- return;
48
- }
398
+ raw.forEach((value, key) => {
399
+ if (isEmptyFile(value)) return;
49
400
  formData.append(key, value);
50
401
  appendValue(data, key, value);
51
402
  });
52
403
  return { data, formData };
53
404
  };
54
405
 
406
+ // src/api/get-data.ts
407
+ var createGetData = (_state, _helpers) => () => {
408
+ if (!_state.element) return { data: {}, formData: new FormData() };
409
+ return parseFormData(_state.element);
410
+ };
411
+
412
+ // src/api/get-field.ts
413
+ var createGetField = (state2, helpers) => (name) => {
414
+ const fields = helpers.getFieldsByName(name);
415
+ if (fields.length === 0) return null;
416
+ return fields.length === 1 ? fields[0] : fields;
417
+ };
418
+
55
419
  // src/core/state.ts
56
420
  var createEmptyFormState = () => ({
57
421
  values: {},
@@ -62,98 +426,47 @@ var SamlineForms = (() => {
62
426
  autoSubmit: false,
63
427
  submitCount: 0
64
428
  });
65
-
66
- // src/core/validation.ts
67
- var resolveRule = (rule) => {
68
- if (rule === void 0) {
69
- return { value: void 0, message: void 0 };
70
- }
71
- if (typeof rule === "object" && rule !== null && "value" in rule) {
72
- return { value: rule.value, message: rule.message };
73
- }
74
- return { value: rule, message: void 0 };
75
- };
76
- var hasValue = (value) => {
77
- if (Array.isArray(value)) {
78
- return value.length > 0;
79
- }
80
- return typeof value === "string" ? value.trim().length > 0 : value !== void 0;
81
- };
82
- var getValueLength = (value) => {
83
- if (Array.isArray(value)) {
84
- return value.length;
85
- }
86
- if (typeof value === "string") {
87
- return value.length;
88
- }
89
- return 0;
90
- };
91
- var toPatternTarget = (value) => {
92
- if (typeof value === "string") {
93
- return value;
94
- }
95
- if (Array.isArray(value)) {
96
- return value.map((entry) => typeof entry === "string" ? entry : entry.name).join(",");
97
- }
98
- return "";
99
- };
100
- var validateFieldValue = (field, value, rules, values) => {
101
- const errors = [];
102
- const context = { field, value, values };
103
- const required = resolveRule(rules.required);
104
- const minLength = resolveRule(rules.minLength);
105
- const maxLength = resolveRule(rules.maxLength);
106
- const pattern = resolveRule(rules.pattern);
107
- if (required.value && !hasValue(value)) {
108
- errors.push(required.message ?? "This field is required.");
109
- }
110
- if (minLength.value !== void 0 && getValueLength(value) < minLength.value) {
111
- errors.push(minLength.message ?? `Minimum length is ${minLength.value}.`);
112
- }
113
- if (maxLength.value !== void 0 && getValueLength(value) > maxLength.value) {
114
- errors.push(maxLength.message ?? `Maximum length is ${maxLength.value}.`);
115
- }
116
- if (pattern.value && hasValue(value) && !pattern.value.test(toPatternTarget(value))) {
117
- errors.push(pattern.message ?? "Value does not match the required pattern.");
118
- }
119
- const customValidators = rules.validate ? Array.isArray(rules.validate) ? rules.validate : [rules.validate] : [];
120
- for (const validator of customValidators) {
121
- const result = validator(context);
122
- if (typeof result === "string" && result.length > 0) {
123
- errors.push(result);
124
- }
125
- if (result === false) {
126
- errors.push("Validation failed.");
127
- }
429
+ var cloneErrors = (errors) => Object.fromEntries(
430
+ Object.entries(errors).map(([field, messages]) => [field, [...messages]])
431
+ );
432
+ var mergeErrors = (left, right) => {
433
+ const merged = { ...left };
434
+ for (const [field, messages] of Object.entries(right)) {
435
+ merged[field] = merged[field] ? [...merged[field], ...messages] : [...messages];
128
436
  }
129
- return errors;
437
+ return merged;
130
438
  };
131
- var validateValues = (values, schema) => {
132
- const errors = {};
133
- for (const [field, rules] of Object.entries(schema)) {
134
- const messages = validateFieldValue(field, values[field], rules, values);
135
- if (messages.length > 0) {
136
- errors[field] = messages;
137
- }
138
- }
439
+
440
+ // src/api/get-state.ts
441
+ var createGetState = (state2, helpers) => () => {
442
+ const values = helpers.getValues();
443
+ const errors = cloneErrors(helpers.getMergedErrors());
444
+ const filledFields = helpers.getTrackedFieldNames().filter((name) => {
445
+ const value = values[name];
446
+ if (Array.isArray(value)) return value.length > 0;
447
+ if (typeof value === "string") return value.trim().length > 0;
448
+ return value !== void 0;
449
+ });
139
450
  return {
451
+ ...createEmptyFormState(),
452
+ values,
453
+ errors,
454
+ filledFields,
140
455
  isValid: Object.keys(errors).length === 0,
141
- errors
456
+ isValidated: state2.isValidated,
457
+ autoSubmit: state2.autoSubmitEnabled,
458
+ submitCount: state2.submitCount
142
459
  };
143
460
  };
144
461
 
145
- // src/internal/dom.ts
462
+ // src/core/dom.ts
146
463
  var isFormFieldElement = (value) => value instanceof HTMLInputElement || value instanceof HTMLSelectElement || value instanceof HTMLTextAreaElement;
147
464
  var resolveFormElement = (target) => {
148
- if (!target) {
149
- return null;
150
- }
151
- if (target instanceof HTMLFormElement) {
152
- return target;
153
- }
465
+ if (!target) return null;
466
+ if (target instanceof HTMLFormElement) return target;
154
467
  if (typeof target === "string") {
155
- const element = document.getElementById(target);
156
- return element instanceof HTMLFormElement ? element : null;
468
+ const node = document.getElementById(target);
469
+ return node instanceof HTMLFormElement ? node : null;
157
470
  }
158
471
  if ("current" in target) {
159
472
  return target.current instanceof HTMLFormElement ? target.current : null;
@@ -164,14 +477,11 @@ var SamlineForms = (() => {
164
477
  isFormFieldElement
165
478
  );
166
479
  var queryNamedFields = (form2, name) => getNamedFields(form2).filter((field) => field.name === name);
480
+ var isFieldElement = (value) => isFormFieldElement(value);
167
481
  var isFieldFilled = (field) => {
168
482
  if (field instanceof HTMLInputElement) {
169
- if (field.type === "checkbox" || field.type === "radio") {
170
- return field.checked;
171
- }
172
- if (field.type === "file") {
173
- return Boolean(field.files && field.files.length > 0);
174
- }
483
+ if (field.type === "checkbox" || field.type === "radio") return field.checked;
484
+ if (field.type === "file") return Boolean(field.files && field.files.length > 0);
175
485
  return field.value.trim() !== "";
176
486
  }
177
487
  return field.value !== "";
@@ -179,35 +489,35 @@ var SamlineForms = (() => {
179
489
  var applyBooleanAttribute = (field, attribute, enabled) => {
180
490
  if (enabled) {
181
491
  field.setAttribute(attribute, "");
182
- return;
492
+ } else {
493
+ field.removeAttribute(attribute);
183
494
  }
184
- field.removeAttribute(attribute);
185
495
  };
186
496
  var readFieldValue = (fields) => {
187
- if (fields.length === 0) {
188
- return void 0;
189
- }
190
- const firstField = fields[0];
191
- if (!firstField) {
192
- return void 0;
193
- }
194
- if (firstField instanceof HTMLSelectElement || firstField instanceof HTMLTextAreaElement) {
195
- return firstField.value;
497
+ if (fields.length === 0) return void 0;
498
+ const first = fields[0];
499
+ if (!first) return void 0;
500
+ if (first instanceof HTMLSelectElement || first instanceof HTMLTextAreaElement) {
501
+ return first.value;
196
502
  }
197
- if (firstField.type === "radio") {
198
- const checked = fields.find(
199
- (field) => field instanceof HTMLInputElement && field.checked
200
- );
201
- return checked?.value ?? "";
202
- }
203
- if (firstField.type === "checkbox") {
204
- const values = fields.filter((field) => field instanceof HTMLInputElement && field.checked).map((field) => field.value);
205
- return values.length > 1 ? values : values[0] ?? "";
206
- }
207
- if (firstField.type === "file") {
208
- return firstField.files ? Array.from(firstField.files) : [];
503
+ if (first instanceof HTMLInputElement) {
504
+ if (first.type === "radio") {
505
+ const checked = fields.find(
506
+ (f) => f instanceof HTMLInputElement && f.checked
507
+ );
508
+ return checked?.value ?? "";
509
+ }
510
+ if (first.type === "checkbox") {
511
+ const values = fields.filter(
512
+ (f) => f instanceof HTMLInputElement && f.checked
513
+ ).map((f) => f.value);
514
+ return values.length > 1 ? values : values[0] ?? "";
515
+ }
516
+ if (first.type === "file") {
517
+ return first.files ? Array.from(first.files) : [];
518
+ }
209
519
  }
210
- return firstField.value;
520
+ return first.value;
211
521
  };
212
522
  var writeFieldValue = (fields, value) => {
213
523
  const normalizedArray = Array.isArray(value) ? value.map((item) => String(item)) : null;
@@ -223,9 +533,7 @@ var SamlineForms = (() => {
223
533
  continue;
224
534
  }
225
535
  if (field.type === "file") {
226
- if (Array.isArray(value) && value.length === 0) {
227
- field.value = "";
228
- }
536
+ if (Array.isArray(value) && value.length === 0) field.value = "";
229
537
  continue;
230
538
  }
231
539
  field.value = normalizedValue;
@@ -236,9 +544,7 @@ var SamlineForms = (() => {
236
544
  };
237
545
  var clearAttributes = (form2, attributes) => {
238
546
  for (const attribute of attributes) {
239
- form2.querySelectorAll(`[${attribute}]`).forEach((element) => {
240
- element.removeAttribute(attribute);
241
- });
547
+ form2.querySelectorAll(`[${attribute}]`).forEach((node) => node.removeAttribute(attribute));
242
548
  }
243
549
  };
244
550
  var submitForm = (form2) => {
@@ -256,25 +562,194 @@ var SamlineForms = (() => {
256
562
  form2.submit();
257
563
  };
258
564
 
259
- // src/internal/controller.ts
565
+ // src/api/get-value.ts
566
+ var createGetValue = (state2, helpers) => (name) => readFieldValue(helpers.getFieldsByName(name));
567
+
568
+ // src/api/observe.ts
569
+ var createObserve = (state2, helpers) => (field, callback) => {
570
+ const callbacks = state2.watchedFields.get(field) ?? /* @__PURE__ */ new Set();
571
+ callbacks.add(callback);
572
+ state2.watchedFields.set(field, callbacks);
573
+ if (state2.element) {
574
+ callback(state2.api.getValue(field), state2.api.getField(field), state2.element, state2.api.getState());
575
+ }
576
+ return () => {
577
+ const current = state2.watchedFields.get(field);
578
+ current?.delete(callback);
579
+ if (current && current.size === 0) {
580
+ state2.watchedFields.delete(field);
581
+ }
582
+ };
583
+ };
584
+
585
+ // src/api/on-submit.ts
586
+ var createOnSubmit = (state2, _helpers) => (callback, preventDefault = true) => {
587
+ state2.submitHandlers.add({ callback, preventDefault });
588
+ return state2.api;
589
+ };
590
+
591
+ // src/api/prefill.ts
592
+ var createPrefill = (state2, _helpers) => (fieldName) => {
593
+ if (!state2.element) return state2.api;
594
+ const params = new URLSearchParams(window.location.search);
595
+ params.forEach((value, key) => {
596
+ if (fieldName && fieldName !== key) return;
597
+ state2.api.setValue(key, value);
598
+ });
599
+ return state2.api;
600
+ };
601
+
602
+ // src/api/reset.ts
603
+ var createReset = (state2, helpers) => () => {
604
+ if (!state2.element) return state2.api;
605
+ state2.element.reset();
606
+ state2.manualErrors = {};
607
+ state2.validationErrors = {};
608
+ clearAttributes(state2.element, [state2.attributes.error, state2.attributes.filled]);
609
+ if (state2.isValidated) helpers.syncVisualState();
610
+ helpers.notifySubscribers();
611
+ return state2.api;
612
+ };
613
+
614
+ // src/api/validate.ts
615
+ var createValidate = (state2, helpers) => (fields) => {
616
+ state2.isValidated = true;
617
+ helpers.syncVisualState(fields);
618
+ return helpers.validateNames(fields);
619
+ };
620
+
621
+ // src/api/revalidate.ts
622
+ var createRevalidate = createValidate;
623
+
624
+ // src/api/set-errors.ts
625
+ var createSetErrors = (state2, helpers) => (fields) => {
626
+ if (Array.isArray(fields)) {
627
+ state2.manualErrors = {
628
+ ...state2.manualErrors,
629
+ ...Object.fromEntries(fields.map((field) => [field, ["Invalid value."]]))
630
+ };
631
+ } else {
632
+ state2.manualErrors = {
633
+ ...state2.manualErrors,
634
+ ...cloneErrors(fields)
635
+ };
636
+ }
637
+ helpers.syncVisualState(Array.isArray(fields) ? fields : Object.keys(fields));
638
+ helpers.notifySubscribers();
639
+ return state2.api;
640
+ };
641
+
642
+ // src/api/set-value.ts
643
+ var createSetValue = (state2, helpers) => (name, value) => {
644
+ const fields = helpers.getFieldsByName(name);
645
+ if (fields.length === 0) return state2.api;
646
+ writeFieldValue(fields, value);
647
+ const firstField = fields[0];
648
+ if (!firstField) return state2.api;
649
+ const eventType = firstField instanceof HTMLSelectElement || firstField instanceof HTMLInputElement && (firstField.type === "checkbox" || firstField.type === "radio") ? "change" : "input";
650
+ firstField.dispatchEvent(new Event(eventType, { bubbles: true }));
651
+ return state2.api;
652
+ };
653
+
654
+ // src/api/subscribe.ts
655
+ var createSubscribe = (state2, _helpers) => (listener) => {
656
+ state2.subscribers.add(listener);
657
+ listener(state2.api.getState());
658
+ return () => {
659
+ state2.subscribers.delete(listener);
660
+ };
661
+ };
662
+
663
+ // src/api/unwatch.ts
664
+ var createUnwatch = (state2, _helpers) => (field, callback) => {
665
+ if (!field) {
666
+ state2.watchedFields.clear();
667
+ return state2.api;
668
+ }
669
+ if (!callback) {
670
+ state2.watchedFields.delete(field);
671
+ return state2.api;
672
+ }
673
+ const callbacks = state2.watchedFields.get(field);
674
+ callbacks?.delete(callback);
675
+ if (callbacks && callbacks.size === 0) {
676
+ state2.watchedFields.delete(field);
677
+ }
678
+ return state2.api;
679
+ };
680
+
681
+ // src/api/watch.ts
682
+ var createWatch = (state2, helpers) => (field, callback) => {
683
+ const observe = createObserve(state2, helpers);
684
+ observe(field, callback);
685
+ return state2.api;
686
+ };
687
+
688
+ // src/core/validation.ts
689
+ var resolveRule = (rule) => {
690
+ if (rule === void 0) {
691
+ return { value: void 0, message: void 0 };
692
+ }
693
+ if (typeof rule === "object" && rule !== null && "value" in rule) {
694
+ return { value: rule.value, message: rule.message };
695
+ }
696
+ return { value: rule, message: void 0 };
697
+ };
698
+ var hasValue = (value) => {
699
+ if (Array.isArray(value)) return value.length > 0;
700
+ if (typeof value === "string") return value.trim().length > 0;
701
+ return value !== void 0;
702
+ };
703
+ var getValueLength = (value) => {
704
+ if (Array.isArray(value)) return value.length;
705
+ if (typeof value === "string") return value.length;
706
+ return 0;
707
+ };
708
+ var toPatternTarget = (value) => {
709
+ if (typeof value === "string") return value;
710
+ if (Array.isArray(value)) {
711
+ return value.map((entry) => typeof entry === "string" ? entry : entry.name).join(",");
712
+ }
713
+ return "";
714
+ };
715
+ var validateFieldValue = (field, value, rules, values) => {
716
+ const errors = [];
717
+ const context = { field, value, values };
718
+ const required = resolveRule(rules.required);
719
+ const minLength = resolveRule(rules.minLength);
720
+ const maxLength = resolveRule(rules.maxLength);
721
+ const pattern = resolveRule(rules.pattern);
722
+ if (required.value && !hasValue(value)) {
723
+ errors.push(required.message ?? "This field is required.");
724
+ }
725
+ if (minLength.value !== void 0 && getValueLength(value) < minLength.value) {
726
+ errors.push(minLength.message ?? `Minimum length is ${minLength.value}.`);
727
+ }
728
+ if (maxLength.value !== void 0 && getValueLength(value) > maxLength.value) {
729
+ errors.push(maxLength.message ?? `Maximum length is ${maxLength.value}.`);
730
+ }
731
+ if (pattern.value && hasValue(value) && !pattern.value.test(toPatternTarget(value))) {
732
+ errors.push(pattern.message ?? "Value does not match the required pattern.");
733
+ }
734
+ const custom = rules.validate ? Array.isArray(rules.validate) ? rules.validate : [rules.validate] : [];
735
+ for (const validator of custom) {
736
+ const result = validator(context);
737
+ if (typeof result === "string" && result.length > 0) errors.push(result);
738
+ if (result === false) errors.push("Validation failed.");
739
+ }
740
+ return errors;
741
+ };
742
+
743
+ // src/core/controller.ts
260
744
  var DEFAULT_ATTRIBUTES = {
261
745
  filled: "css-filled",
262
746
  error: "css-error"
263
747
  };
264
748
  var DEFAULT_OPTIONS = {
265
749
  autoValidate: true,
266
- clearErrorsOnSubmit: true
750
+ clearErrorsOnSubmit: true,
751
+ clearManualErrorsOnChange: true
267
752
  };
268
- var mergeErrors = (left, right) => {
269
- const merged = { ...left };
270
- for (const [field, messages] of Object.entries(right)) {
271
- merged[field] = merged[field] ? [...merged[field], ...messages] : [...messages];
272
- }
273
- return merged;
274
- };
275
- var cloneErrors = (errors) => Object.fromEntries(
276
- Object.entries(errors).map(([field, messages]) => [field, [...messages]])
277
- );
278
753
  var createFormController = (target, options = {}) => {
279
754
  const element = resolveFormElement(target);
280
755
  const attributes = { ...DEFAULT_ATTRIBUTES, ...options.attributes };
@@ -283,47 +758,49 @@ var SamlineForms = (() => {
283
758
  ...options,
284
759
  attributes
285
760
  };
286
- const validators = normalizedOptions.validators ?? {};
287
- const watchedFields = /* @__PURE__ */ new Map();
288
- const listeners = [];
289
- const subscribers = /* @__PURE__ */ new Set();
290
- const submitHandlers = /* @__PURE__ */ new Set();
291
- const fieldCache = /* @__PURE__ */ new Map();
292
- let manualErrors = {};
293
- let validationErrors = {};
294
- let isValidated = Boolean(normalizedOptions.autoValidate);
295
- let autoSubmitEnabled = false;
296
- let autoSubmitDebounce = 0;
297
- let submitCount = 0;
298
- let autoSubmitTimer = null;
299
- let mutationObserver = null;
300
- let isDestroyed = false;
301
- const notifySubscribers = () => {
302
- const state = api.getState();
303
- subscribers.forEach((listener) => listener(state));
761
+ const state2 = {
762
+ element,
763
+ options: normalizedOptions,
764
+ attributes,
765
+ validators: normalizedOptions.validators ?? {},
766
+ watchedFields: /* @__PURE__ */ new Map(),
767
+ subscribers: /* @__PURE__ */ new Set(),
768
+ submitHandlers: /* @__PURE__ */ new Set(),
769
+ fieldCache: /* @__PURE__ */ new Map(),
770
+ manualErrors: {},
771
+ validationErrors: {},
772
+ isValidated: Boolean(normalizedOptions.autoValidate),
773
+ autoSubmitEnabled: false,
774
+ autoSubmitDebounce: 0,
775
+ submitCount: 0,
776
+ autoSubmitTimer: null,
777
+ isDestroyed: false,
778
+ listeners: [],
779
+ mutationObserver: null,
780
+ formattedFields: /* @__PURE__ */ new Map(),
781
+ api: null
304
782
  };
305
- const addListener = (listenerTarget, type, handler) => {
306
- listenerTarget.addEventListener(type, handler);
307
- listeners.push({ element: listenerTarget, type, handler });
783
+ const notifySubscribers = () => {
784
+ if (!state2.api) return;
785
+ const snapshot = state2.api.getState();
786
+ state2.subscribers.forEach((listener) => listener(snapshot));
308
787
  };
309
- const clearFieldCache = () => {
310
- fieldCache.clear();
788
+ const addListener2 = (node, type, handler) => {
789
+ node.addEventListener(type, handler);
790
+ state2.listeners.push({ element: node, type, handler });
311
791
  };
792
+ const clearFieldCache = () => state2.fieldCache.clear();
312
793
  const getFieldsByName = (name) => {
313
- if (!element) {
314
- return [];
794
+ if (!state2.element) return [];
795
+ if (!state2.fieldCache.has(name)) {
796
+ state2.fieldCache.set(name, queryNamedFields(state2.element, name));
315
797
  }
316
- if (!fieldCache.has(name)) {
317
- fieldCache.set(name, queryNamedFields(element, name));
318
- }
319
- return fieldCache.get(name) ?? [];
798
+ return state2.fieldCache.get(name) ?? [];
320
799
  };
321
800
  const getTrackedFieldNames = () => {
322
- if (!element) {
323
- return Object.keys(validators);
324
- }
325
- const names = new Set(Object.keys(validators));
326
- for (const field of getNamedFields(element)) {
801
+ if (!state2.element) return Object.keys(state2.validators);
802
+ const names = new Set(Object.keys(state2.validators));
803
+ for (const field of getNamedFields(state2.element)) {
327
804
  names.add(field.name);
328
805
  }
329
806
  return Array.from(names);
@@ -331,15 +808,13 @@ var SamlineForms = (() => {
331
808
  const getValues = () => {
332
809
  const values = {};
333
810
  for (const name of getTrackedFieldNames()) {
334
- values[name] = api.getValue(name);
811
+ values[name] = readFieldValue(getFieldsByName(name));
335
812
  }
336
813
  return values;
337
814
  };
338
- const getMergedErrors = () => mergeErrors(validationErrors, manualErrors);
815
+ const getMergedErrors = () => mergeErrors(state2.validationErrors, state2.manualErrors);
339
816
  const syncVisualState = (names) => {
340
- if (!element) {
341
- return;
342
- }
817
+ if (!state2.element) return;
343
818
  const targetNames = names ?? getTrackedFieldNames();
344
819
  const errors = getMergedErrors();
345
820
  for (const name of targetNames) {
@@ -351,24 +826,23 @@ var SamlineForms = (() => {
351
826
  }
352
827
  }
353
828
  };
829
+ void readFieldValue;
830
+ void writeFieldValue;
354
831
  const validateNames = (names) => {
355
- const targetNames = names ?? Object.keys(validators);
832
+ const targetNames = names ?? Object.keys(state2.validators);
356
833
  const values = getValues();
357
- const nextValidationErrors = names ? cloneErrors(validationErrors) : {};
834
+ const nextValidationErrors = names ? cloneErrors(state2.validationErrors) : {};
358
835
  for (const name of targetNames) {
359
- const rules = validators[name];
836
+ const rules = state2.validators[name];
360
837
  if (!rules) {
361
838
  delete nextValidationErrors[name];
362
839
  continue;
363
840
  }
364
- const errors = validateFieldValue(name, values[name], rules, values);
365
- if (errors.length > 0) {
366
- nextValidationErrors[name] = errors;
367
- } else {
368
- delete nextValidationErrors[name];
369
- }
841
+ const messages = validateFieldValue(name, values[name], rules, values);
842
+ if (messages.length > 0) nextValidationErrors[name] = messages;
843
+ else delete nextValidationErrors[name];
370
844
  }
371
- validationErrors = nextValidationErrors;
845
+ state2.validationErrors = nextValidationErrors;
372
846
  syncVisualState(names);
373
847
  return {
374
848
  isValid: Object.keys(getMergedErrors()).length === 0,
@@ -376,42 +850,49 @@ var SamlineForms = (() => {
376
850
  };
377
851
  };
378
852
  const emitFieldWatchers = (name) => {
379
- if (!element) {
380
- return;
381
- }
382
- const callbacks = watchedFields.get(name);
383
- if (!callbacks || callbacks.size === 0) {
384
- return;
385
- }
386
- const value = api.getValue(name);
387
- const state = api.getState();
388
- callbacks.forEach((callback) => callback(value, element, state));
853
+ if (!state2.element || !state2.api) return;
854
+ const callbacks = state2.watchedFields.get(name);
855
+ if (!callbacks || callbacks.size === 0) return;
856
+ const value = state2.api.getValue(name);
857
+ const fieldElement = state2.api.getField(name);
858
+ const snapshot = state2.api.getState();
859
+ callbacks.forEach((callback) => callback(value, fieldElement, state2.element, snapshot));
389
860
  };
390
861
  const scheduleAutoSubmit = () => {
391
- if (!element || !autoSubmitEnabled) {
392
- return;
393
- }
394
- if (autoSubmitTimer) {
395
- clearTimeout(autoSubmitTimer);
396
- }
397
- if (autoSubmitDebounce > 0) {
398
- autoSubmitTimer = setTimeout(() => submitForm(element), autoSubmitDebounce);
862
+ if (!state2.element || !state2.autoSubmitEnabled) return;
863
+ if (state2.autoSubmitTimer) clearTimeout(state2.autoSubmitTimer);
864
+ if (state2.autoSubmitDebounce > 0) {
865
+ state2.autoSubmitTimer = setTimeout(
866
+ () => submitForm(state2.element),
867
+ state2.autoSubmitDebounce
868
+ );
399
869
  return;
400
870
  }
401
- submitForm(element);
871
+ submitForm(state2.element);
872
+ };
873
+ const helpers = {
874
+ notifySubscribers,
875
+ clearFieldCache,
876
+ getFieldsByName,
877
+ getTrackedFieldNames,
878
+ getValues,
879
+ getMergedErrors,
880
+ syncVisualState,
881
+ validateNames,
882
+ emitFieldWatchers,
883
+ scheduleAutoSubmit
402
884
  };
403
885
  const handleDelegatedEvent = (event) => {
404
886
  const target2 = event.target;
405
- if (!(target2 instanceof Element) || !isFieldElement(target2)) {
406
- return;
407
- }
887
+ if (!(target2 instanceof Element) || !isFieldElement(target2)) return;
408
888
  const name = target2.name;
409
- if (!name) {
410
- return;
411
- }
889
+ if (!name) return;
412
890
  clearFieldCache();
891
+ if (normalizedOptions.clearManualErrorsOnChange) {
892
+ delete state2.manualErrors[name];
893
+ }
413
894
  syncVisualState([name]);
414
- if (isValidated && validators[name]) {
895
+ if (state2.isValidated && state2.validators[name]) {
415
896
  validateNames([name]);
416
897
  }
417
898
  emitFieldWatchers(name);
@@ -419,305 +900,132 @@ var SamlineForms = (() => {
419
900
  scheduleAutoSubmit();
420
901
  };
421
902
  const handleSubmitEvent = (event) => {
422
- if (!element || isDestroyed) {
423
- return;
424
- }
425
- if (normalizedOptions.clearErrorsOnSubmit) {
426
- manualErrors = {};
427
- }
428
- const validation = api.validate();
429
- const handlers = Array.from(submitHandlers);
903
+ if (!state2.element || state2.isDestroyed || !state2.api) return;
904
+ if (normalizedOptions.clearErrorsOnSubmit) state2.manualErrors = {};
905
+ const validation = state2.api.validate();
906
+ const handlers = Array.from(state2.submitHandlers);
430
907
  const shouldPrevent = validation.isValid ? handlers.some((handler) => handler.preventDefault) : true;
431
- if (shouldPrevent) {
432
- event.preventDefault();
433
- }
434
- submitCount += 1;
908
+ if (shouldPrevent) event.preventDefault();
909
+ state2.submitCount += 1;
435
910
  notifySubscribers();
436
- if (!validation.isValid) {
437
- return;
438
- }
439
- const { data, formData } = api.getData();
440
- const state = api.getState();
441
- handlers.forEach((handler) => handler.callback(element, data, formData, state));
911
+ if (!validation.isValid) return;
912
+ const { data, formData } = state2.api.getData();
913
+ const snapshot = state2.api.getState();
914
+ handlers.forEach(
915
+ (handler) => handler.callback(state2.element, data, formData, snapshot)
916
+ );
442
917
  };
443
918
  const startMutationObserver = () => {
444
- if (!element || typeof MutationObserver === "undefined") {
445
- return;
446
- }
447
- mutationObserver = new MutationObserver(() => {
919
+ if (!state2.element || typeof MutationObserver === "undefined") return;
920
+ state2.mutationObserver = new MutationObserver(() => {
448
921
  clearFieldCache();
449
- if (isValidated) {
922
+ if (state2.isValidated) {
450
923
  syncVisualState();
451
924
  validateNames();
452
925
  }
453
926
  notifySubscribers();
454
927
  });
455
- mutationObserver.observe(element, {
928
+ state2.mutationObserver.observe(state2.element, {
456
929
  childList: true,
457
930
  subtree: true,
458
931
  attributes: true,
459
932
  attributeFilter: ["name", "type"]
460
933
  });
461
934
  };
462
- const isFieldElement = (value) => value instanceof HTMLInputElement || value instanceof HTMLSelectElement || value instanceof HTMLTextAreaElement;
463
935
  const api = {
464
- element,
465
- f: element,
466
- options: normalizedOptions,
467
- onSubmit(callback, preventDefault = true) {
468
- submitHandlers.add({ callback, preventDefault });
469
- return api;
470
- },
471
- watch(field, callback) {
472
- api.observe(field, callback);
473
- return api;
474
- },
475
- observe(field, callback) {
476
- const callbacks = watchedFields.get(field) ?? /* @__PURE__ */ new Set();
477
- callbacks.add(callback);
478
- watchedFields.set(field, callbacks);
479
- if (element) {
480
- callback(api.getValue(field), element, api.getState());
481
- }
482
- return () => {
483
- const currentCallbacks = watchedFields.get(field);
484
- currentCallbacks?.delete(callback);
485
- if (currentCallbacks && currentCallbacks.size === 0) {
486
- watchedFields.delete(field);
487
- }
488
- };
489
- },
490
- unwatch(field, callback) {
491
- if (!field) {
492
- watchedFields.clear();
493
- return api;
494
- }
495
- if (!callback) {
496
- watchedFields.delete(field);
497
- return api;
498
- }
499
- const callbacks = watchedFields.get(field);
500
- callbacks?.delete(callback);
501
- if (callbacks && callbacks.size === 0) {
502
- watchedFields.delete(field);
503
- }
504
- return api;
505
- },
506
- subscribe(listener) {
507
- subscribers.add(listener);
508
- listener(api.getState());
509
- return () => {
510
- subscribers.delete(listener);
511
- };
512
- },
513
- prefill(fieldName) {
514
- if (!element) {
515
- return api;
516
- }
517
- const queryParams = new URLSearchParams(window.location.search);
518
- queryParams.forEach((value, key) => {
519
- if (fieldName && fieldName !== key) {
520
- return;
521
- }
522
- api.setValue(key, value);
523
- });
524
- return api;
525
- },
526
- append({ tag, content, class: className, atStart = false }) {
527
- if (!element) {
528
- return null;
529
- }
530
- clearFieldCache();
531
- if (className) {
532
- const classSelector = className.trim().split(/\s+/)[0];
533
- element.querySelector(`.${classSelector}`)?.remove();
534
- }
535
- const node = document.createElement(tag);
536
- if (className) {
537
- node.className = className;
538
- }
539
- node.innerHTML = content;
540
- if (atStart && element.firstChild) {
541
- element.insertBefore(node, element.firstChild);
542
- } else {
543
- element.appendChild(node);
544
- }
545
- return node;
546
- },
547
- setErrors(fields) {
548
- if (Array.isArray(fields)) {
549
- manualErrors = {
550
- ...manualErrors,
551
- ...Object.fromEntries(fields.map((field) => [field, ["Invalid value."]]))
552
- };
553
- } else {
554
- manualErrors = {
555
- ...manualErrors,
556
- ...cloneErrors(fields)
557
- };
558
- }
559
- syncVisualState(Array.isArray(fields) ? fields : Object.keys(fields));
560
- notifySubscribers();
561
- return api;
936
+ get element() {
937
+ return state2.element;
562
938
  },
563
- clearErrors(fields) {
564
- if (!fields) {
565
- manualErrors = {};
566
- syncVisualState();
567
- notifySubscribers();
568
- return api;
569
- }
570
- for (const field of fields) {
571
- delete manualErrors[field];
572
- }
573
- syncVisualState(fields);
574
- notifySubscribers();
575
- return api;
576
- },
577
- setValue(name, value) {
578
- const fields = getFieldsByName(name);
579
- if (fields.length === 0) {
580
- return api;
581
- }
582
- writeFieldValue(fields, value);
583
- const firstField = fields[0];
584
- if (!firstField) {
585
- return api;
586
- }
587
- const eventType = firstField instanceof HTMLSelectElement || firstField instanceof HTMLInputElement && (firstField.type === "checkbox" || firstField.type === "radio") ? "change" : "input";
588
- firstField.dispatchEvent(new Event(eventType, { bubbles: true }));
589
- return api;
590
- },
591
- validate(fields) {
592
- isValidated = true;
593
- syncVisualState(fields);
594
- return validateNames(fields);
595
- },
596
- revalidate(fields) {
597
- return api.validate(fields);
939
+ get f() {
940
+ return state2.element;
598
941
  },
599
- reset() {
600
- if (!element) {
601
- return api;
602
- }
603
- element.reset();
604
- manualErrors = {};
605
- validationErrors = {};
606
- clearAttributes(element, [attributes.error, attributes.filled]);
607
- if (isValidated) {
608
- syncVisualState();
609
- }
610
- notifySubscribers();
611
- return api;
612
- },
613
- autoSubmit(nextOptions = true) {
614
- autoSubmitEnabled = nextOptions !== false;
615
- autoSubmitDebounce = typeof nextOptions === "object" && nextOptions ? nextOptions.debounce ?? 0 : 0;
616
- notifySubscribers();
617
- return api;
942
+ get options() {
943
+ return state2.options;
618
944
  },
619
- disableAutoSubmit() {
620
- autoSubmitEnabled = false;
621
- if (autoSubmitTimer) {
622
- clearTimeout(autoSubmitTimer);
623
- autoSubmitTimer = null;
624
- }
625
- notifySubscribers();
626
- return api;
627
- },
628
- getValue(name) {
629
- return readFieldValue(getFieldsByName(name));
630
- },
631
- getField(name) {
632
- const fields = getFieldsByName(name);
633
- if (fields.length === 0) {
634
- return null;
635
- }
636
- const [firstField] = fields;
637
- if (!firstField) {
638
- return null;
639
- }
640
- return fields.length === 1 ? firstField : fields;
641
- },
642
- getData() {
643
- if (!element) {
644
- return { data: {}, formData: new FormData() };
645
- }
646
- return parseFormData(element);
647
- },
648
- getState() {
649
- const values = getValues();
650
- const errors = cloneErrors(getMergedErrors());
651
- const filledFields = getTrackedFieldNames().filter((name) => {
652
- const value = values[name];
653
- return Array.isArray(value) ? value.length > 0 : typeof value === "string" ? value.trim().length > 0 : value !== void 0;
654
- });
655
- return {
656
- ...createEmptyFormState(),
657
- values,
658
- errors,
659
- filledFields,
660
- isValid: Object.keys(errors).length === 0,
661
- isValidated,
662
- autoSubmit: autoSubmitEnabled,
663
- submitCount
664
- };
665
- },
666
- destroy() {
667
- if (isDestroyed) {
668
- return;
669
- }
670
- isDestroyed = true;
671
- for (const listener of listeners) {
672
- listener.element.removeEventListener(listener.type, listener.handler);
673
- }
674
- listeners.length = 0;
675
- if (autoSubmitTimer) {
676
- clearTimeout(autoSubmitTimer);
677
- autoSubmitTimer = null;
678
- }
679
- mutationObserver?.disconnect();
680
- mutationObserver = null;
681
- watchedFields.clear();
682
- subscribers.clear();
683
- submitHandlers.clear();
684
- clearFieldCache();
685
- manualErrors = {};
686
- validationErrors = {};
687
- }
945
+ onSubmit: createOnSubmit(state2, helpers),
946
+ watch: createWatch(state2, helpers),
947
+ observe: createObserve(state2, helpers),
948
+ unwatch: createUnwatch(state2, helpers),
949
+ subscribe: createSubscribe(state2, helpers),
950
+ prefill: createPrefill(state2, helpers),
951
+ append: createAppend(state2, helpers),
952
+ setErrors: createSetErrors(state2, helpers),
953
+ clearErrors: createClearErrors(state2, helpers),
954
+ setValue: createSetValue(state2, helpers),
955
+ getValue: createGetValue(state2, helpers),
956
+ getField: createGetField(state2, helpers),
957
+ validate: createValidate(state2, helpers),
958
+ revalidate: createRevalidate(state2, helpers),
959
+ reset: createReset(state2, helpers),
960
+ autoSubmit: createAutoSubmit(state2, helpers),
961
+ disableAutoSubmit: createDisableAutoSubmit(state2, helpers),
962
+ getData: createGetData(state2, helpers),
963
+ getState: createGetState(state2, helpers),
964
+ format: createFormat(state2, helpers),
965
+ formatAll: createFormatAll(state2, helpers),
966
+ destroy: createDestroy(state2, helpers)
688
967
  };
689
- if (element) {
690
- addListener(element, "input", handleDelegatedEvent);
691
- addListener(element, "change", handleDelegatedEvent);
692
- addListener(element, "submit", handleSubmitEvent);
968
+ state2.api = api;
969
+ if (state2.element) {
970
+ addListener2(state2.element, "input", handleDelegatedEvent);
971
+ addListener2(state2.element, "change", handleDelegatedEvent);
972
+ addListener2(state2.element, "submit", handleSubmitEvent);
693
973
  startMutationObserver();
694
974
  }
695
975
  if (normalizedOptions.autoSubmit) {
696
- const autoSubmitOption = normalizedOptions.autoSubmit;
697
- api.autoSubmit(autoSubmitOption);
976
+ api.autoSubmit(normalizedOptions.autoSubmit);
977
+ }
978
+ if (normalizedOptions.formats) {
979
+ for (const config of Object.values(normalizedOptions.formats)) {
980
+ api.format(config);
981
+ }
698
982
  }
699
- if (isValidated) {
983
+ if (state2.isValidated) {
700
984
  syncVisualState();
701
985
  validateNames();
702
986
  }
703
987
  return api;
704
988
  };
705
989
 
706
- // src/vanilla/index.ts
707
- var form = (target, options) => createFormController(target, options);
990
+ // src/api/form.ts
991
+ var form = (target, options = {}) => createFormController(target, options);
708
992
 
709
993
  // src/browser/global.ts
710
- var browserApi = {
711
- createFormController,
994
+ var available = {};
995
+ var newForm = (input) => {
996
+ const { id, options } = input;
997
+ if (!id) {
998
+ console.error("Form ID is required");
999
+ return;
1000
+ }
1001
+ const controller = form(id, { ...options });
1002
+ available[id] = controller;
1003
+ return controller;
1004
+ };
1005
+ var destroyForm = (id) => {
1006
+ if (!id) {
1007
+ console.error("Form ID is required");
1008
+ return;
1009
+ }
1010
+ const controller = available[id];
1011
+ if (controller) {
1012
+ controller.destroy();
1013
+ delete available[id];
1014
+ } else {
1015
+ console.warn(`Form with ID ${id} not found`);
1016
+ }
1017
+ };
1018
+ var Forms = {
712
1019
  form,
713
- parseFormData,
714
- validateValues
1020
+ newForm,
1021
+ destroyForm,
1022
+ available
715
1023
  };
716
1024
  if (typeof globalThis !== "undefined") {
717
1025
  ;
718
- globalThis.forms = browserApi;
1026
+ globalThis.Forms = Forms;
719
1027
  }
720
- var global_default = browserApi;
1028
+ var global_default = Forms;
721
1029
  return __toCommonJS(global_exports);
722
1030
  })();
723
1031
  //# sourceMappingURL=global.global.js.map