@samline/forms 1.0.3 → 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 +87 -199
  2. package/dist/browser/global.d.ts +152 -0
  3. package/dist/browser/global.global.js +778 -474
  4. package/dist/browser/global.global.js.map +1 -1
  5. package/dist/index.cjs +753 -470
  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-PCKGH2WA.js +0 -11
  15. package/dist/chunk-PCKGH2WA.js.map +0 -1
  16. package/dist/chunk-WM2CL7IO.js +0 -576
  17. package/dist/chunk-WM2CL7IO.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 -734
  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 -764
  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-jYB7Wp7C.d.cts +0 -95
  37. package/dist/types-jYB7Wp7C.d.ts +0 -95
  38. package/dist/vanilla/index.cjs +0 -705
  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 -741
  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;
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;
193
502
  }
194
- if (firstField instanceof HTMLSelectElement || firstField instanceof HTMLTextAreaElement) {
195
- return firstField.value;
196
- }
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,26 +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
- clearManualErrorsOnChange: true,
267
- clearErrorsOnSubmit: true
750
+ clearErrorsOnSubmit: true,
751
+ clearManualErrorsOnChange: true
268
752
  };
269
- var mergeErrors = (left, right) => {
270
- const merged = { ...left };
271
- for (const [field, messages] of Object.entries(right)) {
272
- merged[field] = merged[field] ? [...merged[field], ...messages] : [...messages];
273
- }
274
- return merged;
275
- };
276
- var cloneErrors = (errors) => Object.fromEntries(
277
- Object.entries(errors).map(([field, messages]) => [field, [...messages]])
278
- );
279
753
  var createFormController = (target, options = {}) => {
280
754
  const element = resolveFormElement(target);
281
755
  const attributes = { ...DEFAULT_ATTRIBUTES, ...options.attributes };
@@ -284,47 +758,49 @@ var SamlineForms = (() => {
284
758
  ...options,
285
759
  attributes
286
760
  };
287
- const validators = normalizedOptions.validators ?? {};
288
- const watchedFields = /* @__PURE__ */ new Map();
289
- const listeners = [];
290
- const subscribers = /* @__PURE__ */ new Set();
291
- const submitHandlers = /* @__PURE__ */ new Set();
292
- const fieldCache = /* @__PURE__ */ new Map();
293
- let manualErrors = {};
294
- let validationErrors = {};
295
- let isValidated = Boolean(normalizedOptions.autoValidate);
296
- let autoSubmitEnabled = false;
297
- let autoSubmitDebounce = 0;
298
- let submitCount = 0;
299
- let autoSubmitTimer = null;
300
- let mutationObserver = null;
301
- let isDestroyed = false;
302
- const notifySubscribers = () => {
303
- const state = api.getState();
304
- 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
305
782
  };
306
- const addListener = (listenerTarget, type, handler) => {
307
- listenerTarget.addEventListener(type, handler);
308
- 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));
309
787
  };
310
- const clearFieldCache = () => {
311
- fieldCache.clear();
788
+ const addListener2 = (node, type, handler) => {
789
+ node.addEventListener(type, handler);
790
+ state2.listeners.push({ element: node, type, handler });
312
791
  };
792
+ const clearFieldCache = () => state2.fieldCache.clear();
313
793
  const getFieldsByName = (name) => {
314
- if (!element) {
315
- return [];
316
- }
317
- if (!fieldCache.has(name)) {
318
- fieldCache.set(name, queryNamedFields(element, name));
794
+ if (!state2.element) return [];
795
+ if (!state2.fieldCache.has(name)) {
796
+ state2.fieldCache.set(name, queryNamedFields(state2.element, name));
319
797
  }
320
- return fieldCache.get(name) ?? [];
798
+ return state2.fieldCache.get(name) ?? [];
321
799
  };
322
800
  const getTrackedFieldNames = () => {
323
- if (!element) {
324
- return Object.keys(validators);
325
- }
326
- const names = new Set(Object.keys(validators));
327
- 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)) {
328
804
  names.add(field.name);
329
805
  }
330
806
  return Array.from(names);
@@ -332,15 +808,13 @@ var SamlineForms = (() => {
332
808
  const getValues = () => {
333
809
  const values = {};
334
810
  for (const name of getTrackedFieldNames()) {
335
- values[name] = api.getValue(name);
811
+ values[name] = readFieldValue(getFieldsByName(name));
336
812
  }
337
813
  return values;
338
814
  };
339
- const getMergedErrors = () => mergeErrors(validationErrors, manualErrors);
815
+ const getMergedErrors = () => mergeErrors(state2.validationErrors, state2.manualErrors);
340
816
  const syncVisualState = (names) => {
341
- if (!element) {
342
- return;
343
- }
817
+ if (!state2.element) return;
344
818
  const targetNames = names ?? getTrackedFieldNames();
345
819
  const errors = getMergedErrors();
346
820
  for (const name of targetNames) {
@@ -352,24 +826,23 @@ var SamlineForms = (() => {
352
826
  }
353
827
  }
354
828
  };
829
+ void readFieldValue;
830
+ void writeFieldValue;
355
831
  const validateNames = (names) => {
356
- const targetNames = names ?? Object.keys(validators);
832
+ const targetNames = names ?? Object.keys(state2.validators);
357
833
  const values = getValues();
358
- const nextValidationErrors = names ? cloneErrors(validationErrors) : {};
834
+ const nextValidationErrors = names ? cloneErrors(state2.validationErrors) : {};
359
835
  for (const name of targetNames) {
360
- const rules = validators[name];
836
+ const rules = state2.validators[name];
361
837
  if (!rules) {
362
838
  delete nextValidationErrors[name];
363
839
  continue;
364
840
  }
365
- const errors = validateFieldValue(name, values[name], rules, values);
366
- if (errors.length > 0) {
367
- nextValidationErrors[name] = errors;
368
- } else {
369
- delete nextValidationErrors[name];
370
- }
841
+ const messages = validateFieldValue(name, values[name], rules, values);
842
+ if (messages.length > 0) nextValidationErrors[name] = messages;
843
+ else delete nextValidationErrors[name];
371
844
  }
372
- validationErrors = nextValidationErrors;
845
+ state2.validationErrors = nextValidationErrors;
373
846
  syncVisualState(names);
374
847
  return {
375
848
  isValid: Object.keys(getMergedErrors()).length === 0,
@@ -377,45 +850,49 @@ var SamlineForms = (() => {
377
850
  };
378
851
  };
379
852
  const emitFieldWatchers = (name) => {
380
- if (!element) {
381
- return;
382
- }
383
- const callbacks = watchedFields.get(name);
384
- if (!callbacks || callbacks.size === 0) {
385
- return;
386
- }
387
- const value = api.getValue(name);
388
- const state = api.getState();
389
- 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));
390
860
  };
391
861
  const scheduleAutoSubmit = () => {
392
- if (!element || !autoSubmitEnabled) {
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
+ );
393
869
  return;
394
870
  }
395
- if (autoSubmitTimer) {
396
- clearTimeout(autoSubmitTimer);
397
- }
398
- if (autoSubmitDebounce > 0) {
399
- autoSubmitTimer = setTimeout(() => submitForm(element), autoSubmitDebounce);
400
- return;
401
- }
402
- 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
403
884
  };
404
885
  const handleDelegatedEvent = (event) => {
405
886
  const target2 = event.target;
406
- if (!(target2 instanceof Element) || !isFieldElement(target2)) {
407
- return;
408
- }
887
+ if (!(target2 instanceof Element) || !isFieldElement(target2)) return;
409
888
  const name = target2.name;
410
- if (!name) {
411
- return;
412
- }
889
+ if (!name) return;
413
890
  clearFieldCache();
414
891
  if (normalizedOptions.clearManualErrorsOnChange) {
415
- delete manualErrors[name];
892
+ delete state2.manualErrors[name];
416
893
  }
417
894
  syncVisualState([name]);
418
- if (isValidated && validators[name]) {
895
+ if (state2.isValidated && state2.validators[name]) {
419
896
  validateNames([name]);
420
897
  }
421
898
  emitFieldWatchers(name);
@@ -423,305 +900,132 @@ var SamlineForms = (() => {
423
900
  scheduleAutoSubmit();
424
901
  };
425
902
  const handleSubmitEvent = (event) => {
426
- if (!element || isDestroyed) {
427
- return;
428
- }
429
- if (normalizedOptions.clearErrorsOnSubmit) {
430
- manualErrors = {};
431
- }
432
- const validation = api.validate();
433
- 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);
434
907
  const shouldPrevent = validation.isValid ? handlers.some((handler) => handler.preventDefault) : true;
435
- if (shouldPrevent) {
436
- event.preventDefault();
437
- }
438
- submitCount += 1;
908
+ if (shouldPrevent) event.preventDefault();
909
+ state2.submitCount += 1;
439
910
  notifySubscribers();
440
- if (!validation.isValid) {
441
- return;
442
- }
443
- const { data, formData } = api.getData();
444
- const state = api.getState();
445
- 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
+ );
446
917
  };
447
918
  const startMutationObserver = () => {
448
- if (!element || typeof MutationObserver === "undefined") {
449
- return;
450
- }
451
- mutationObserver = new MutationObserver(() => {
919
+ if (!state2.element || typeof MutationObserver === "undefined") return;
920
+ state2.mutationObserver = new MutationObserver(() => {
452
921
  clearFieldCache();
453
- if (isValidated) {
922
+ if (state2.isValidated) {
454
923
  syncVisualState();
455
924
  validateNames();
456
925
  }
457
926
  notifySubscribers();
458
927
  });
459
- mutationObserver.observe(element, {
928
+ state2.mutationObserver.observe(state2.element, {
460
929
  childList: true,
461
930
  subtree: true,
462
931
  attributes: true,
463
932
  attributeFilter: ["name", "type"]
464
933
  });
465
934
  };
466
- const isFieldElement = (value) => value instanceof HTMLInputElement || value instanceof HTMLSelectElement || value instanceof HTMLTextAreaElement;
467
935
  const api = {
468
- element,
469
- f: element,
470
- options: normalizedOptions,
471
- onSubmit(callback, preventDefault = true) {
472
- submitHandlers.add({ callback, preventDefault });
473
- return api;
474
- },
475
- watch(field, callback) {
476
- api.observe(field, callback);
477
- return api;
478
- },
479
- observe(field, callback) {
480
- const callbacks = watchedFields.get(field) ?? /* @__PURE__ */ new Set();
481
- callbacks.add(callback);
482
- watchedFields.set(field, callbacks);
483
- if (element) {
484
- callback(api.getValue(field), element, api.getState());
485
- }
486
- return () => {
487
- const currentCallbacks = watchedFields.get(field);
488
- currentCallbacks?.delete(callback);
489
- if (currentCallbacks && currentCallbacks.size === 0) {
490
- watchedFields.delete(field);
491
- }
492
- };
493
- },
494
- unwatch(field, callback) {
495
- if (!field) {
496
- watchedFields.clear();
497
- return api;
498
- }
499
- if (!callback) {
500
- watchedFields.delete(field);
501
- return api;
502
- }
503
- const callbacks = watchedFields.get(field);
504
- callbacks?.delete(callback);
505
- if (callbacks && callbacks.size === 0) {
506
- watchedFields.delete(field);
507
- }
508
- return api;
509
- },
510
- subscribe(listener) {
511
- subscribers.add(listener);
512
- listener(api.getState());
513
- return () => {
514
- subscribers.delete(listener);
515
- };
516
- },
517
- prefill(fieldName) {
518
- if (!element) {
519
- return api;
520
- }
521
- const queryParams = new URLSearchParams(window.location.search);
522
- queryParams.forEach((value, key) => {
523
- if (fieldName && fieldName !== key) {
524
- return;
525
- }
526
- api.setValue(key, value);
527
- });
528
- return api;
529
- },
530
- append({ tag, content, class: className, atStart = false }) {
531
- if (!element) {
532
- return null;
533
- }
534
- clearFieldCache();
535
- if (className) {
536
- const classSelector = className.trim().split(/\s+/)[0];
537
- element.querySelector(`.${classSelector}`)?.remove();
538
- }
539
- const node = document.createElement(tag);
540
- if (className) {
541
- node.className = className;
542
- }
543
- node.innerHTML = content;
544
- if (atStart && element.firstChild) {
545
- element.insertBefore(node, element.firstChild);
546
- } else {
547
- element.appendChild(node);
548
- }
549
- return node;
550
- },
551
- setErrors(fields) {
552
- if (Array.isArray(fields)) {
553
- manualErrors = {
554
- ...manualErrors,
555
- ...Object.fromEntries(fields.map((field) => [field, ["Invalid value."]]))
556
- };
557
- } else {
558
- manualErrors = {
559
- ...manualErrors,
560
- ...cloneErrors(fields)
561
- };
562
- }
563
- syncVisualState(Array.isArray(fields) ? fields : Object.keys(fields));
564
- notifySubscribers();
565
- return api;
936
+ get element() {
937
+ return state2.element;
566
938
  },
567
- clearErrors(fields) {
568
- if (!fields) {
569
- manualErrors = {};
570
- syncVisualState();
571
- notifySubscribers();
572
- return api;
573
- }
574
- for (const field of fields) {
575
- delete manualErrors[field];
576
- }
577
- syncVisualState(fields);
578
- notifySubscribers();
579
- return api;
580
- },
581
- setValue(name, value) {
582
- const fields = getFieldsByName(name);
583
- if (fields.length === 0) {
584
- return api;
585
- }
586
- writeFieldValue(fields, value);
587
- const firstField = fields[0];
588
- if (!firstField) {
589
- return api;
590
- }
591
- const eventType = firstField instanceof HTMLSelectElement || firstField instanceof HTMLInputElement && (firstField.type === "checkbox" || firstField.type === "radio") ? "change" : "input";
592
- firstField.dispatchEvent(new Event(eventType, { bubbles: true }));
593
- return api;
594
- },
595
- validate(fields) {
596
- isValidated = true;
597
- syncVisualState(fields);
598
- return validateNames(fields);
599
- },
600
- revalidate(fields) {
601
- return api.validate(fields);
939
+ get f() {
940
+ return state2.element;
602
941
  },
603
- reset() {
604
- if (!element) {
605
- return api;
606
- }
607
- element.reset();
608
- manualErrors = {};
609
- validationErrors = {};
610
- clearAttributes(element, [attributes.error, attributes.filled]);
611
- if (isValidated) {
612
- syncVisualState();
613
- }
614
- notifySubscribers();
615
- return api;
616
- },
617
- autoSubmit(nextOptions = true) {
618
- autoSubmitEnabled = nextOptions !== false;
619
- autoSubmitDebounce = typeof nextOptions === "object" && nextOptions ? nextOptions.debounce ?? 0 : 0;
620
- notifySubscribers();
621
- return api;
942
+ get options() {
943
+ return state2.options;
622
944
  },
623
- disableAutoSubmit() {
624
- autoSubmitEnabled = false;
625
- if (autoSubmitTimer) {
626
- clearTimeout(autoSubmitTimer);
627
- autoSubmitTimer = null;
628
- }
629
- notifySubscribers();
630
- return api;
631
- },
632
- getValue(name) {
633
- return readFieldValue(getFieldsByName(name));
634
- },
635
- getField(name) {
636
- const fields = getFieldsByName(name);
637
- if (fields.length === 0) {
638
- return null;
639
- }
640
- const [firstField] = fields;
641
- if (!firstField) {
642
- return null;
643
- }
644
- return fields.length === 1 ? firstField : fields;
645
- },
646
- getData() {
647
- if (!element) {
648
- return { data: {}, formData: new FormData() };
649
- }
650
- return parseFormData(element);
651
- },
652
- getState() {
653
- const values = getValues();
654
- const errors = cloneErrors(getMergedErrors());
655
- const filledFields = getTrackedFieldNames().filter((name) => {
656
- const value = values[name];
657
- return Array.isArray(value) ? value.length > 0 : typeof value === "string" ? value.trim().length > 0 : value !== void 0;
658
- });
659
- return {
660
- ...createEmptyFormState(),
661
- values,
662
- errors,
663
- filledFields,
664
- isValid: Object.keys(errors).length === 0,
665
- isValidated,
666
- autoSubmit: autoSubmitEnabled,
667
- submitCount
668
- };
669
- },
670
- destroy() {
671
- if (isDestroyed) {
672
- return;
673
- }
674
- isDestroyed = true;
675
- for (const listener of listeners) {
676
- listener.element.removeEventListener(listener.type, listener.handler);
677
- }
678
- listeners.length = 0;
679
- if (autoSubmitTimer) {
680
- clearTimeout(autoSubmitTimer);
681
- autoSubmitTimer = null;
682
- }
683
- mutationObserver?.disconnect();
684
- mutationObserver = null;
685
- watchedFields.clear();
686
- subscribers.clear();
687
- submitHandlers.clear();
688
- clearFieldCache();
689
- manualErrors = {};
690
- validationErrors = {};
691
- }
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)
692
967
  };
693
- if (element) {
694
- addListener(element, "input", handleDelegatedEvent);
695
- addListener(element, "change", handleDelegatedEvent);
696
- 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);
697
973
  startMutationObserver();
698
974
  }
699
975
  if (normalizedOptions.autoSubmit) {
700
- const autoSubmitOption = normalizedOptions.autoSubmit;
701
- 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
+ }
702
982
  }
703
- if (isValidated) {
983
+ if (state2.isValidated) {
704
984
  syncVisualState();
705
985
  validateNames();
706
986
  }
707
987
  return api;
708
988
  };
709
989
 
710
- // src/vanilla/index.ts
711
- var form = (target, options) => createFormController(target, options);
990
+ // src/api/form.ts
991
+ var form = (target, options = {}) => createFormController(target, options);
712
992
 
713
993
  // src/browser/global.ts
714
- var browserApi = {
715
- 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 = {
716
1019
  form,
717
- parseFormData,
718
- validateValues
1020
+ newForm,
1021
+ destroyForm,
1022
+ available
719
1023
  };
720
1024
  if (typeof globalThis !== "undefined") {
721
1025
  ;
722
- globalThis.forms = browserApi;
1026
+ globalThis.Forms = Forms;
723
1027
  }
724
- var global_default = browserApi;
1028
+ var global_default = Forms;
725
1029
  return __toCommonJS(global_exports);
726
1030
  })();
727
1031
  //# sourceMappingURL=global.global.js.map