@samline/forms 1.0.3 → 2.1.0

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