@samline/forms 2.4.0 → 2.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +11 -8
- package/dist/browser/global.d.ts +28 -18
- package/dist/browser/global.global.js +477 -66
- package/dist/browser/global.global.js.map +1 -1
- package/dist/browser/index.cjs +1450 -0
- package/dist/browser/index.cjs.map +1 -0
- package/dist/browser/index.d.cts +14 -0
- package/dist/browser/index.d.ts +14 -0
- package/dist/browser/index.js +23 -0
- package/dist/browser/index.js.map +1 -0
- package/dist/chunk-WGPAVJTK.js +1415 -0
- package/dist/chunk-WGPAVJTK.js.map +1 -0
- package/dist/index.cjs +296 -40
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -158
- package/dist/index.d.ts +4 -158
- package/dist/index.js +7 -1148
- package/dist/index.js.map +1 -1
- package/dist/registry-GzopYPCR.d.cts +164 -0
- package/dist/registry-GzopYPCR.d.ts +164 -0
- package/docs/api/add-cleanup.md +52 -0
- package/docs/api/append.md +96 -0
- package/docs/api/auto-submit.md +91 -0
- package/docs/api/clear-errors.md +69 -0
- package/docs/api/destroy.md +80 -0
- package/docs/api/disable-auto-submit.md +65 -0
- package/docs/api/element.md +78 -0
- package/docs/api/form.md +156 -0
- package/docs/api/format.md +270 -0
- package/docs/api/get-data.md +90 -0
- package/docs/api/get-field.md +87 -0
- package/docs/api/get-state.md +111 -0
- package/docs/api/get-value.md +114 -0
- package/docs/api/index.md +55 -0
- package/docs/api/observe.md +126 -0
- package/docs/api/on-submit.md +94 -0
- package/docs/api/parse-form-data.md +94 -0
- package/docs/api/prefill.md +89 -0
- package/docs/api/regex.md +155 -0
- package/docs/api/reset.md +61 -0
- package/docs/api/revalidate.md +52 -0
- package/docs/api/set-errors.md +97 -0
- package/docs/api/set-value.md +120 -0
- package/docs/api/subscribe.md +107 -0
- package/docs/api/unwatch.md +74 -0
- package/docs/api/validate-field-value.md +144 -0
- package/docs/api/validate-values.md +103 -0
- package/docs/api/validate.md +102 -0
- package/docs/api/watch.md +117 -0
- package/docs/browser.md +156 -0
- package/docs/css-styling.md +191 -0
- package/docs/getting-started.md +222 -0
- package/docs/options.md +326 -0
- package/docs/recipes.md +530 -0
- package/docs/typescript.md +615 -0
- package/docs/vanilla.md +12 -0
- package/package.json +58 -14
|
@@ -0,0 +1,1415 @@
|
|
|
1
|
+
// src/core/serialize.ts
|
|
2
|
+
var isEmptyFile = (value) => value instanceof File && value.size === 0 && value.name === "";
|
|
3
|
+
var appendValue = (data, key, value) => {
|
|
4
|
+
if (!Object.prototype.hasOwnProperty.call(data, key)) {
|
|
5
|
+
Object.defineProperty(data, key, {
|
|
6
|
+
value,
|
|
7
|
+
writable: true,
|
|
8
|
+
enumerable: true,
|
|
9
|
+
configurable: true
|
|
10
|
+
});
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
const current = data[key];
|
|
14
|
+
if (Array.isArray(current)) {
|
|
15
|
+
current.push(value);
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
data[key] = [current, value];
|
|
19
|
+
};
|
|
20
|
+
var parseFormData = (formElement, submitter) => {
|
|
21
|
+
const raw = submitter ? new FormData(formElement, submitter) : new FormData(formElement);
|
|
22
|
+
const formData = new FormData();
|
|
23
|
+
const data = {};
|
|
24
|
+
raw.forEach((value, key) => {
|
|
25
|
+
if (isEmptyFile(value)) return;
|
|
26
|
+
formData.append(key, value);
|
|
27
|
+
appendValue(data, key, value);
|
|
28
|
+
});
|
|
29
|
+
return { data, formData };
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
// src/core/validation.ts
|
|
33
|
+
var resolveRule = (rule) => {
|
|
34
|
+
if (rule === void 0) {
|
|
35
|
+
return { value: void 0, message: void 0 };
|
|
36
|
+
}
|
|
37
|
+
if (typeof rule === "object" && rule !== null && "value" in rule) {
|
|
38
|
+
return { value: rule.value, message: rule.message };
|
|
39
|
+
}
|
|
40
|
+
return { value: rule, message: void 0 };
|
|
41
|
+
};
|
|
42
|
+
var hasValue = (value) => {
|
|
43
|
+
if (Array.isArray(value)) return value.length > 0;
|
|
44
|
+
if (typeof value === "string") return value.trim().length > 0;
|
|
45
|
+
return value !== void 0;
|
|
46
|
+
};
|
|
47
|
+
var getValueLength = (value) => {
|
|
48
|
+
if (Array.isArray(value)) return value.length;
|
|
49
|
+
if (typeof value === "string") return value.length;
|
|
50
|
+
return 0;
|
|
51
|
+
};
|
|
52
|
+
var toPatternTarget = (value) => {
|
|
53
|
+
if (typeof value === "string") return value;
|
|
54
|
+
if (Array.isArray(value)) {
|
|
55
|
+
return value.map((entry) => typeof entry === "string" ? entry : entry.name).join(",");
|
|
56
|
+
}
|
|
57
|
+
return "";
|
|
58
|
+
};
|
|
59
|
+
var valuesAreEqual = (left, right) => {
|
|
60
|
+
if (Array.isArray(left) && Array.isArray(right)) {
|
|
61
|
+
return left.length === right.length && left.every((entry, index) => entry === right[index]);
|
|
62
|
+
}
|
|
63
|
+
return left === right;
|
|
64
|
+
};
|
|
65
|
+
var DECIMAL_PATTERN = /^[+-]?(?:\d+(?:\.\d*)?|\.\d+)$/;
|
|
66
|
+
var parseStrictNumber = (value) => {
|
|
67
|
+
if (typeof value !== "string") return void 0;
|
|
68
|
+
const candidate = value.trim();
|
|
69
|
+
if (!DECIMAL_PATTERN.test(candidate)) return void 0;
|
|
70
|
+
const number = Number(candidate);
|
|
71
|
+
return Number.isFinite(number) ? number : void 0;
|
|
72
|
+
};
|
|
73
|
+
var validateValue = (field, value, rules, values, contextOverrides = {}) => {
|
|
74
|
+
const errors = [];
|
|
75
|
+
const context = {
|
|
76
|
+
field,
|
|
77
|
+
value,
|
|
78
|
+
values,
|
|
79
|
+
...contextOverrides
|
|
80
|
+
};
|
|
81
|
+
const required = resolveRule(rules.required);
|
|
82
|
+
const minLength = resolveRule(rules.minLength);
|
|
83
|
+
const maxLength = resolveRule(rules.maxLength);
|
|
84
|
+
const pattern = resolveRule(rules.pattern);
|
|
85
|
+
const numeric = resolveRule(rules.numeric);
|
|
86
|
+
const min = resolveRule(rules.min);
|
|
87
|
+
const max = resolveRule(rules.max);
|
|
88
|
+
if (required.value && !hasValue(value)) {
|
|
89
|
+
errors.push(required.message ?? "This field is required.");
|
|
90
|
+
}
|
|
91
|
+
if (minLength.value !== void 0 && getValueLength(value) < minLength.value) {
|
|
92
|
+
errors.push(minLength.message ?? `Minimum length is ${minLength.value}.`);
|
|
93
|
+
}
|
|
94
|
+
if (maxLength.value !== void 0 && getValueLength(value) > maxLength.value) {
|
|
95
|
+
errors.push(maxLength.message ?? `Maximum length is ${maxLength.value}.`);
|
|
96
|
+
}
|
|
97
|
+
if (pattern.value && hasValue(value)) {
|
|
98
|
+
pattern.value.lastIndex = 0;
|
|
99
|
+
const matches = pattern.value.test(toPatternTarget(value));
|
|
100
|
+
pattern.value.lastIndex = 0;
|
|
101
|
+
if (!matches) {
|
|
102
|
+
errors.push(pattern.message ?? "Value does not match the required pattern.");
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
if (hasValue(value) && (numeric.value || min.value !== void 0 || max.value !== void 0)) {
|
|
106
|
+
const number = parseStrictNumber(value);
|
|
107
|
+
if (number === void 0) {
|
|
108
|
+
errors.push(numeric.message ?? "Value must be a number.");
|
|
109
|
+
} else {
|
|
110
|
+
if (min.value !== void 0 && number < min.value) {
|
|
111
|
+
errors.push(min.message ?? `Minimum value is ${min.value}.`);
|
|
112
|
+
}
|
|
113
|
+
if (max.value !== void 0 && number > max.value) {
|
|
114
|
+
errors.push(max.message ?? `Maximum value is ${max.value}.`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
const custom = rules.validate ? Array.isArray(rules.validate) ? rules.validate : [rules.validate] : [];
|
|
119
|
+
for (const validator of custom) {
|
|
120
|
+
const result = validator(context);
|
|
121
|
+
if (typeof result === "string" && result.length > 0) errors.push(result);
|
|
122
|
+
if (result === false) errors.push("Validation failed.");
|
|
123
|
+
}
|
|
124
|
+
return errors;
|
|
125
|
+
};
|
|
126
|
+
var validateFieldValueDetailed = (field, value, rules, values, items) => {
|
|
127
|
+
const groupErrors = validateValue(field, value, rules, values);
|
|
128
|
+
const sameAs = resolveRule(rules.sameAs);
|
|
129
|
+
if (sameAs.value !== void 0) {
|
|
130
|
+
const otherValue = values[sameAs.value];
|
|
131
|
+
if (hasValue(value) && hasValue(otherValue) && !valuesAreEqual(value, otherValue)) {
|
|
132
|
+
groupErrors.push(sameAs.message ?? `Value must match ${sameAs.value}.`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
const itemErrors = [];
|
|
136
|
+
if (rules.each) {
|
|
137
|
+
const sourceItems = items ?? (Array.isArray(value) ? value.map((entry) => ({
|
|
138
|
+
value: typeof entry === "string" ? entry : [entry]
|
|
139
|
+
})) : [{ value }]);
|
|
140
|
+
sourceItems.forEach((item, index) => {
|
|
141
|
+
const overrides = {
|
|
142
|
+
index
|
|
143
|
+
};
|
|
144
|
+
if (item.element) overrides.element = item.element;
|
|
145
|
+
const messages = validateValue(field, item.value, rules.each, values, overrides);
|
|
146
|
+
if (messages.length === 0) return;
|
|
147
|
+
const error = {
|
|
148
|
+
index,
|
|
149
|
+
messages
|
|
150
|
+
};
|
|
151
|
+
if (item.element) error.element = item.element;
|
|
152
|
+
itemErrors.push(error);
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
return { groupErrors, itemErrors };
|
|
156
|
+
};
|
|
157
|
+
var validateFieldValue = (field, value, rules, values) => {
|
|
158
|
+
const result = validateFieldValueDetailed(field, value, rules, values);
|
|
159
|
+
return [...result.groupErrors, ...result.itemErrors.flatMap((item) => item.messages)];
|
|
160
|
+
};
|
|
161
|
+
var validateValues = (values, schema) => {
|
|
162
|
+
const errors = {};
|
|
163
|
+
for (const [field, rules] of Object.entries(schema)) {
|
|
164
|
+
const messages = validateFieldValue(field, values[field], rules, values);
|
|
165
|
+
if (messages.length > 0) errors[field] = messages;
|
|
166
|
+
}
|
|
167
|
+
return {
|
|
168
|
+
isValid: Object.keys(errors).length === 0,
|
|
169
|
+
errors
|
|
170
|
+
};
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
// src/api/append.ts
|
|
174
|
+
var createAppend = (state2, helpers) => ({ tag, content, class: className, atStart = false }) => {
|
|
175
|
+
if (!state2.element) return null;
|
|
176
|
+
helpers.clearFieldCache();
|
|
177
|
+
if (className) {
|
|
178
|
+
const classSelector = className.trim().split(/\s+/)[0];
|
|
179
|
+
const existing = classSelector ? state2.element.getElementsByClassName(classSelector)[0] : null;
|
|
180
|
+
existing?.remove();
|
|
181
|
+
}
|
|
182
|
+
const node = document.createElement(tag);
|
|
183
|
+
if (className) node.className = className;
|
|
184
|
+
node.innerHTML = content;
|
|
185
|
+
if (atStart && state2.element.firstChild) {
|
|
186
|
+
state2.element.insertBefore(node, state2.element.firstChild);
|
|
187
|
+
} else {
|
|
188
|
+
state2.element.appendChild(node);
|
|
189
|
+
}
|
|
190
|
+
return node;
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
// src/api/add-cleanup.ts
|
|
194
|
+
var createAddCleanup = (state2, _helpers) => (cleanup) => {
|
|
195
|
+
if (state2.isDestroyed) {
|
|
196
|
+
cleanup();
|
|
197
|
+
return () => void 0;
|
|
198
|
+
}
|
|
199
|
+
const registration = () => cleanup();
|
|
200
|
+
state2.cleanups.add(registration);
|
|
201
|
+
let active = true;
|
|
202
|
+
return () => {
|
|
203
|
+
if (!active) return;
|
|
204
|
+
active = false;
|
|
205
|
+
state2.cleanups.delete(registration);
|
|
206
|
+
};
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
// src/api/auto-submit.ts
|
|
210
|
+
var createAutoSubmit = (state2, helpers) => (next = true) => {
|
|
211
|
+
state2.autoSubmitEnabled = next !== false;
|
|
212
|
+
if (!state2.autoSubmitEnabled && state2.autoSubmitTimer) {
|
|
213
|
+
clearTimeout(state2.autoSubmitTimer);
|
|
214
|
+
state2.autoSubmitTimer = null;
|
|
215
|
+
}
|
|
216
|
+
state2.autoSubmitDebounce = typeof next === "object" && next ? next.debounce ?? 0 : 0;
|
|
217
|
+
helpers.notifySubscribers();
|
|
218
|
+
return state2.api;
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
// src/api/clear-errors.ts
|
|
222
|
+
var createClearErrors = (state2, helpers) => (fields) => {
|
|
223
|
+
if (!fields) {
|
|
224
|
+
state2.manualErrors = {};
|
|
225
|
+
helpers.syncVisualState();
|
|
226
|
+
helpers.notifySubscribers();
|
|
227
|
+
return state2.api;
|
|
228
|
+
}
|
|
229
|
+
for (const field of fields) {
|
|
230
|
+
delete state2.manualErrors[field];
|
|
231
|
+
}
|
|
232
|
+
helpers.syncVisualState(fields);
|
|
233
|
+
helpers.notifySubscribers();
|
|
234
|
+
return state2.api;
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
// src/core/format-helpers.ts
|
|
238
|
+
var FORMATTER_RAW_ATTRIBUTE = "data-formatter-raw-for";
|
|
239
|
+
var cssEscape = (value) => {
|
|
240
|
+
if (typeof CSS !== "undefined" && typeof CSS.escape === "function") {
|
|
241
|
+
return CSS.escape(value);
|
|
242
|
+
}
|
|
243
|
+
return value.replace(/([!"#$%&'()*+,./:;<=>?@\[\\\]^`{|}~])/g, "\\$1");
|
|
244
|
+
};
|
|
245
|
+
var findVisibleField = (form2, displayName) => {
|
|
246
|
+
const node = form2.querySelector(
|
|
247
|
+
`[name="${cssEscape(displayName)}"]:not([type="hidden"])`
|
|
248
|
+
);
|
|
249
|
+
if (node instanceof HTMLInputElement || node instanceof HTMLTextAreaElement) {
|
|
250
|
+
return node;
|
|
251
|
+
}
|
|
252
|
+
return null;
|
|
253
|
+
};
|
|
254
|
+
var findHiddenMirror = (form2, fieldName) => {
|
|
255
|
+
const byAttr = form2.querySelector(
|
|
256
|
+
`[${FORMATTER_RAW_ATTRIBUTE}="${cssEscape(fieldName)}"]`
|
|
257
|
+
);
|
|
258
|
+
if (byAttr) return byAttr;
|
|
259
|
+
const byName = form2.querySelector(
|
|
260
|
+
`input[type="hidden"][name="${cssEscape(fieldName)}"]`
|
|
261
|
+
);
|
|
262
|
+
if (byName) return byName;
|
|
263
|
+
return null;
|
|
264
|
+
};
|
|
265
|
+
var ensureHiddenMirror = (form2, fieldName) => {
|
|
266
|
+
const existing = findHiddenMirror(form2, fieldName);
|
|
267
|
+
if (existing) return existing;
|
|
268
|
+
const input = document.createElement("input");
|
|
269
|
+
input.type = "hidden";
|
|
270
|
+
input.name = fieldName;
|
|
271
|
+
input.setAttribute("aria-hidden", "true");
|
|
272
|
+
input.tabIndex = -1;
|
|
273
|
+
input.setAttribute(FORMATTER_RAW_ATTRIBUTE, fieldName);
|
|
274
|
+
form2.appendChild(input);
|
|
275
|
+
return input;
|
|
276
|
+
};
|
|
277
|
+
var renameVisibleField = (visible, displayName) => {
|
|
278
|
+
if (visible.name === displayName) return;
|
|
279
|
+
visible.name = displayName;
|
|
280
|
+
};
|
|
281
|
+
var restoreVisibleName = (visible, originalName) => {
|
|
282
|
+
visible.name = originalName;
|
|
283
|
+
};
|
|
284
|
+
var applyFormattedValue = (visible, mirror, formatted, raw, inputType) => {
|
|
285
|
+
const rawInput = visible.value;
|
|
286
|
+
const caret = clamp(
|
|
287
|
+
visible.selectionStart ?? rawInput.length,
|
|
288
|
+
0,
|
|
289
|
+
rawInput.length
|
|
290
|
+
);
|
|
291
|
+
if (mirror && mirror.value !== raw) mirror.value = raw;
|
|
292
|
+
if (rawInput === formatted) {
|
|
293
|
+
const newCaret2 = pullCaretPastLeadingDelimiter(caret, formatted, inputType);
|
|
294
|
+
if (newCaret2 !== caret) restoreCursor(visible, newCaret2);
|
|
295
|
+
return newCaret2;
|
|
296
|
+
}
|
|
297
|
+
visible.value = formatted;
|
|
298
|
+
const newCaret = pullCaretPastLeadingDelimiter(
|
|
299
|
+
computeCursorPosition(caret, rawInput, formatted),
|
|
300
|
+
formatted,
|
|
301
|
+
inputType
|
|
302
|
+
);
|
|
303
|
+
restoreCursor(visible, newCaret);
|
|
304
|
+
return newCaret;
|
|
305
|
+
};
|
|
306
|
+
var pullCaretPastLeadingDelimiter = (caret, value, inputType) => {
|
|
307
|
+
if (!inputType || !inputType.startsWith("delete")) return caret;
|
|
308
|
+
let cursor = caret;
|
|
309
|
+
while (cursor > 0 && isFormatChar(value[cursor - 1])) {
|
|
310
|
+
cursor -= 1;
|
|
311
|
+
}
|
|
312
|
+
return cursor;
|
|
313
|
+
};
|
|
314
|
+
var clamp = (value, min, max) => Math.max(min, Math.min(max, value));
|
|
315
|
+
var computeCursorPosition = (caretPos, rawInput, newValue) => {
|
|
316
|
+
if (newValue.length === 0) return 0;
|
|
317
|
+
if (caretPos >= rawInput.length) return newValue.length;
|
|
318
|
+
const targetNonFormat = countNonFormat(rawInput.slice(0, caretPos));
|
|
319
|
+
if (targetNonFormat === 0) {
|
|
320
|
+
let i = 0;
|
|
321
|
+
while (i < newValue.length && isFormatChar(newValue[i])) i += 1;
|
|
322
|
+
return i;
|
|
323
|
+
}
|
|
324
|
+
let seen = 0;
|
|
325
|
+
for (let i = 0; i < newValue.length; i += 1) {
|
|
326
|
+
if (!isFormatChar(newValue[i])) {
|
|
327
|
+
seen += 1;
|
|
328
|
+
if (seen === targetNonFormat) {
|
|
329
|
+
return Math.min(i + 1, newValue.length);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
return newValue.length;
|
|
334
|
+
};
|
|
335
|
+
var countNonFormat = (value) => {
|
|
336
|
+
let count = 0;
|
|
337
|
+
for (const char of value) {
|
|
338
|
+
if (!isFormatChar(char)) count += 1;
|
|
339
|
+
}
|
|
340
|
+
return count;
|
|
341
|
+
};
|
|
342
|
+
var isFormatChar = (char) => {
|
|
343
|
+
if (!char) return false;
|
|
344
|
+
if (char >= "0" && char <= "9") return false;
|
|
345
|
+
if (char >= "a" && char <= "z" || char >= "A" && char <= "Z") return false;
|
|
346
|
+
return true;
|
|
347
|
+
};
|
|
348
|
+
var restoreCursor = (field, position) => {
|
|
349
|
+
const max = field.value.length;
|
|
350
|
+
const safe = clamp(position, 0, max);
|
|
351
|
+
const apply = () => {
|
|
352
|
+
try {
|
|
353
|
+
field.setSelectionRange(safe, safe);
|
|
354
|
+
} catch {
|
|
355
|
+
}
|
|
356
|
+
};
|
|
357
|
+
if (typeof navigator !== "undefined" && /Android/i.test(navigator.userAgent)) {
|
|
358
|
+
if (typeof requestAnimationFrame === "function") {
|
|
359
|
+
requestAnimationFrame(apply);
|
|
360
|
+
} else {
|
|
361
|
+
setTimeout(apply, 1);
|
|
362
|
+
}
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
apply();
|
|
366
|
+
};
|
|
367
|
+
|
|
368
|
+
// src/core/formatter-loader.ts
|
|
369
|
+
var state = null;
|
|
370
|
+
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).";
|
|
371
|
+
var warned = false;
|
|
372
|
+
var warnOnce = () => {
|
|
373
|
+
if (warned) return;
|
|
374
|
+
warned = true;
|
|
375
|
+
console.error(MISSING_MESSAGE);
|
|
376
|
+
};
|
|
377
|
+
var loadFormatter = () => {
|
|
378
|
+
if (state && state.status === "resolved") {
|
|
379
|
+
if (!state.module) warnOnce();
|
|
380
|
+
return Promise.resolve(state.module);
|
|
381
|
+
}
|
|
382
|
+
if (state && state.status === "pending") {
|
|
383
|
+
return state.promise;
|
|
384
|
+
}
|
|
385
|
+
const promise = (async () => {
|
|
386
|
+
try {
|
|
387
|
+
const mod = await import("@samline/formatter");
|
|
388
|
+
state = { status: "resolved", module: mod };
|
|
389
|
+
return mod;
|
|
390
|
+
} catch {
|
|
391
|
+
warnOnce();
|
|
392
|
+
state = { status: "resolved", module: null };
|
|
393
|
+
return null;
|
|
394
|
+
}
|
|
395
|
+
})();
|
|
396
|
+
state = { status: "pending", promise };
|
|
397
|
+
return promise;
|
|
398
|
+
};
|
|
399
|
+
|
|
400
|
+
// src/api/format.ts
|
|
401
|
+
var UNBOUND = null;
|
|
402
|
+
var registry = /* @__PURE__ */ new WeakMap();
|
|
403
|
+
var getRegistry = (state2) => {
|
|
404
|
+
let bucket = registry.get(state2);
|
|
405
|
+
if (!bucket) {
|
|
406
|
+
bucket = /* @__PURE__ */ new Map();
|
|
407
|
+
registry.set(state2, bucket);
|
|
408
|
+
}
|
|
409
|
+
return bucket;
|
|
410
|
+
};
|
|
411
|
+
var resolveFieldNames = (config) => Array.isArray(config.field) ? config.field : [config.field];
|
|
412
|
+
var addListener = (state2, target, type, handler, useCapture) => {
|
|
413
|
+
if (useCapture) {
|
|
414
|
+
target.addEventListener(type, handler, { capture: true });
|
|
415
|
+
} else {
|
|
416
|
+
target.addEventListener(type, handler);
|
|
417
|
+
}
|
|
418
|
+
const entry = { element: target, type, handler };
|
|
419
|
+
if (useCapture !== void 0) entry.capture = useCapture;
|
|
420
|
+
state2.listeners.push(entry);
|
|
421
|
+
};
|
|
422
|
+
var readInputType = (event) => {
|
|
423
|
+
const candidate = event;
|
|
424
|
+
return typeof candidate.inputType === "string" ? candidate.inputType : void 0;
|
|
425
|
+
};
|
|
426
|
+
var buildAutoInterpretedOptions = (formatOptions) => ({
|
|
427
|
+
...formatOptions ?? {},
|
|
428
|
+
interpretInputAs: "auto"
|
|
429
|
+
});
|
|
430
|
+
var shouldForceAutoInterpretation = (formatOptions) => formatOptions?.interpretInputAs === void 0;
|
|
431
|
+
var buildHandler = (visible, mirror, formatFn, formatType, formatOptions) => {
|
|
432
|
+
const handler = (event) => {
|
|
433
|
+
if (!visible.isConnected || !mirror.isConnected) return;
|
|
434
|
+
const isVisible = event.target === visible;
|
|
435
|
+
const isMirror = event.target === mirror;
|
|
436
|
+
if (!isVisible && !isMirror) return;
|
|
437
|
+
const rawInput = isMirror ? mirror.value : visible.value;
|
|
438
|
+
const inputType = readInputType(event);
|
|
439
|
+
const sourceOptions = isMirror && shouldForceAutoInterpretation(formatOptions) ? buildAutoInterpretedOptions(formatOptions) : formatOptions;
|
|
440
|
+
const { formatted, raw } = formatFn(rawInput, formatType, sourceOptions);
|
|
441
|
+
if (!formatted && !raw) {
|
|
442
|
+
visible.value = "";
|
|
443
|
+
if (mirror.value !== "") mirror.value = "";
|
|
444
|
+
return;
|
|
445
|
+
}
|
|
446
|
+
applyFormattedValue(visible, mirror, formatted, raw, inputType);
|
|
447
|
+
};
|
|
448
|
+
return handler;
|
|
449
|
+
};
|
|
450
|
+
var applyFormat = async (state2, helpers, config) => {
|
|
451
|
+
if (!state2.element || !state2.api) return;
|
|
452
|
+
if (Array.isArray(config.field) && config.displayField) {
|
|
453
|
+
console.error(
|
|
454
|
+
"[samline/forms] `displayField` can only be used when `field` is a string."
|
|
455
|
+
);
|
|
456
|
+
return;
|
|
457
|
+
}
|
|
458
|
+
const formatType = config.type;
|
|
459
|
+
const formatOptions = config.options;
|
|
460
|
+
const fieldNames = resolveFieldNames(config);
|
|
461
|
+
const bucket = getRegistry(state2);
|
|
462
|
+
const phase1Entries = [];
|
|
463
|
+
for (const fieldName of fieldNames) {
|
|
464
|
+
const displayName = config.displayField ?? `${fieldName}_displayed`;
|
|
465
|
+
const canonicalFields = helpers.getFieldsByName(fieldName);
|
|
466
|
+
const candidates = canonicalFields.filter(
|
|
467
|
+
(f) => f instanceof HTMLInputElement && f.type !== "hidden" || f instanceof HTMLTextAreaElement
|
|
468
|
+
);
|
|
469
|
+
let visible = candidates[0] ?? null;
|
|
470
|
+
if (!visible) {
|
|
471
|
+
visible = findVisibleField(state2.element, displayName);
|
|
472
|
+
}
|
|
473
|
+
if (!visible) continue;
|
|
474
|
+
const existing = bucket.get(fieldName);
|
|
475
|
+
let mirror = findHiddenMirror(state2.element, fieldName);
|
|
476
|
+
const mirrorIsOwned = !mirror;
|
|
477
|
+
if (!mirror) mirror = ensureHiddenMirror(state2.element, fieldName);
|
|
478
|
+
renameVisibleField(visible, displayName);
|
|
479
|
+
if (existing) {
|
|
480
|
+
existing.displayName = displayName;
|
|
481
|
+
existing.mirrorIsOwned = mirrorIsOwned;
|
|
482
|
+
state2.formattedFields.set(fieldName, {
|
|
483
|
+
config,
|
|
484
|
+
displayName,
|
|
485
|
+
mirrorIsOwned
|
|
486
|
+
});
|
|
487
|
+
phase1Entries.push(existing);
|
|
488
|
+
} else {
|
|
489
|
+
const entry = {
|
|
490
|
+
canonicalName: fieldName,
|
|
491
|
+
displayName,
|
|
492
|
+
visible,
|
|
493
|
+
mirror,
|
|
494
|
+
mirrorIsOwned,
|
|
495
|
+
defaultVisible: null,
|
|
496
|
+
defaultRaw: null,
|
|
497
|
+
handler: UNBOUND
|
|
498
|
+
};
|
|
499
|
+
bucket.set(fieldName, entry);
|
|
500
|
+
state2.formattedFields.set(fieldName, {
|
|
501
|
+
config,
|
|
502
|
+
displayName,
|
|
503
|
+
mirrorIsOwned
|
|
504
|
+
});
|
|
505
|
+
phase1Entries.push(entry);
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
const formatter = await loadFormatter();
|
|
509
|
+
if (state2.isDestroyed) {
|
|
510
|
+
rollbackPhase1(phase1Entries, bucket, state2.formattedFields);
|
|
511
|
+
return;
|
|
512
|
+
}
|
|
513
|
+
if (!formatter) {
|
|
514
|
+
rollbackPhase1(phase1Entries, bucket, state2.formattedFields);
|
|
515
|
+
return;
|
|
516
|
+
}
|
|
517
|
+
for (const entry of phase1Entries) {
|
|
518
|
+
if (entry.handler !== UNBOUND) {
|
|
519
|
+
const current = entry.visible.value;
|
|
520
|
+
if (current !== "") {
|
|
521
|
+
const { formatted, raw } = formatter.format(current, formatType, formatOptions);
|
|
522
|
+
applyFormattedValue(entry.visible, entry.mirror, formatted, raw);
|
|
523
|
+
}
|
|
524
|
+
continue;
|
|
525
|
+
}
|
|
526
|
+
const handler = buildHandler(
|
|
527
|
+
entry.visible,
|
|
528
|
+
entry.mirror,
|
|
529
|
+
formatter.format,
|
|
530
|
+
formatType,
|
|
531
|
+
formatOptions
|
|
532
|
+
);
|
|
533
|
+
addListener(state2, state2.element, "input", handler, true);
|
|
534
|
+
entry.handler = handler;
|
|
535
|
+
const initial = entry.visible.value;
|
|
536
|
+
if (initial !== "") {
|
|
537
|
+
const initialOptions = shouldForceAutoInterpretation(formatOptions) ? buildAutoInterpretedOptions(formatOptions) : formatOptions;
|
|
538
|
+
const { formatted, raw } = formatter.format(initial, formatType, initialOptions);
|
|
539
|
+
applyFormattedValue(entry.visible, entry.mirror, formatted, raw);
|
|
540
|
+
} else if (entry.mirrorIsOwned && entry.mirror.value !== "") {
|
|
541
|
+
entry.mirror.value = "";
|
|
542
|
+
}
|
|
543
|
+
entry.defaultVisible = entry.visible.value;
|
|
544
|
+
entry.defaultRaw = entry.mirror.value;
|
|
545
|
+
}
|
|
546
|
+
};
|
|
547
|
+
var resetFormattedFields = (state2) => {
|
|
548
|
+
const bucket = registry.get(state2);
|
|
549
|
+
if (!bucket) return;
|
|
550
|
+
for (const entry of bucket.values()) {
|
|
551
|
+
if (entry.defaultVisible !== null) entry.visible.value = entry.defaultVisible;
|
|
552
|
+
if (entry.defaultRaw !== null) entry.mirror.value = entry.defaultRaw;
|
|
553
|
+
}
|
|
554
|
+
};
|
|
555
|
+
var rollbackPhase1 = (entries, bucket, registry2) => {
|
|
556
|
+
for (const entry of entries) {
|
|
557
|
+
if (entry.visible.isConnected) {
|
|
558
|
+
restoreVisibleName(entry.visible, entry.canonicalName);
|
|
559
|
+
}
|
|
560
|
+
if (entry.mirrorIsOwned && entry.mirror.isConnected) {
|
|
561
|
+
entry.mirror.remove();
|
|
562
|
+
}
|
|
563
|
+
bucket.delete(entry.canonicalName);
|
|
564
|
+
registry2.delete(entry.canonicalName);
|
|
565
|
+
}
|
|
566
|
+
};
|
|
567
|
+
var createFormat = (state2, helpers) => (config) => {
|
|
568
|
+
if (state2.isDestroyed) return state2.api;
|
|
569
|
+
if (!state2.element) return state2.api;
|
|
570
|
+
void applyFormat(state2, helpers, config);
|
|
571
|
+
return state2.api;
|
|
572
|
+
};
|
|
573
|
+
var createFormatAll = createFormat;
|
|
574
|
+
var resolveCanonicalForName = (state2, name) => {
|
|
575
|
+
const bucket = registry.get(state2);
|
|
576
|
+
if (!bucket) return null;
|
|
577
|
+
for (const [canonical, entry] of bucket) {
|
|
578
|
+
if (canonical === name || entry.displayName === name) return canonical;
|
|
579
|
+
}
|
|
580
|
+
return null;
|
|
581
|
+
};
|
|
582
|
+
var resolveDisplayNameForName = (state2, name) => {
|
|
583
|
+
const bucket = registry.get(state2);
|
|
584
|
+
if (!bucket) return name;
|
|
585
|
+
for (const [canonical, entry] of bucket) {
|
|
586
|
+
if (canonical === name) return entry.displayName;
|
|
587
|
+
if (entry.displayName === name) return entry.displayName;
|
|
588
|
+
}
|
|
589
|
+
return name;
|
|
590
|
+
};
|
|
591
|
+
var cleanupFormatRegistry = (state2) => {
|
|
592
|
+
if (!state2.element) {
|
|
593
|
+
registry.delete(state2);
|
|
594
|
+
return;
|
|
595
|
+
}
|
|
596
|
+
const bucket = registry.get(state2);
|
|
597
|
+
if (!bucket) {
|
|
598
|
+
state2.element.querySelectorAll(`[${FORMATTER_RAW_ATTRIBUTE}]`).forEach((node) => node.remove());
|
|
599
|
+
return;
|
|
600
|
+
}
|
|
601
|
+
for (const [fieldName, entry] of bucket) {
|
|
602
|
+
if (entry.handler !== UNBOUND) {
|
|
603
|
+
state2.element.removeEventListener("input", entry.handler, true);
|
|
604
|
+
}
|
|
605
|
+
if (entry.visible.isConnected) {
|
|
606
|
+
restoreVisibleName(entry.visible, fieldName);
|
|
607
|
+
}
|
|
608
|
+
if (entry.mirrorIsOwned && entry.mirror.isConnected) {
|
|
609
|
+
entry.mirror.remove();
|
|
610
|
+
}
|
|
611
|
+
bucket.delete(fieldName);
|
|
612
|
+
state2.formattedFields.delete(fieldName);
|
|
613
|
+
}
|
|
614
|
+
state2.element.querySelectorAll(`[${FORMATTER_RAW_ATTRIBUTE}]`).forEach((node) => node.remove());
|
|
615
|
+
};
|
|
616
|
+
|
|
617
|
+
// src/api/destroy.ts
|
|
618
|
+
var createDestroy = (state2, _helpers) => () => {
|
|
619
|
+
if (state2.isDestroyed) return;
|
|
620
|
+
state2.isDestroyed = true;
|
|
621
|
+
for (const listener of state2.listeners) {
|
|
622
|
+
const options = listener.capture ? { capture: true } : void 0;
|
|
623
|
+
if (options) {
|
|
624
|
+
listener.element.removeEventListener(listener.type, listener.handler, options);
|
|
625
|
+
} else {
|
|
626
|
+
listener.element.removeEventListener(listener.type, listener.handler);
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
state2.listeners.length = 0;
|
|
630
|
+
if (state2.autoSubmitTimer) {
|
|
631
|
+
clearTimeout(state2.autoSubmitTimer);
|
|
632
|
+
state2.autoSubmitTimer = null;
|
|
633
|
+
}
|
|
634
|
+
state2.mutationObserver?.disconnect();
|
|
635
|
+
state2.mutationObserver = null;
|
|
636
|
+
const cleanups = Array.from(state2.cleanups).reverse();
|
|
637
|
+
state2.cleanups.clear();
|
|
638
|
+
for (const cleanup of cleanups) {
|
|
639
|
+
try {
|
|
640
|
+
cleanup();
|
|
641
|
+
} catch (error) {
|
|
642
|
+
console.error("[forms] cleanup failed", error);
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
state2.watchedFields.clear();
|
|
646
|
+
state2.subscribers.clear();
|
|
647
|
+
state2.submitHandlers.clear();
|
|
648
|
+
state2.fieldCache.clear();
|
|
649
|
+
state2.manualErrors = {};
|
|
650
|
+
state2.validationErrors = {};
|
|
651
|
+
state2.validationGroupErrors = {};
|
|
652
|
+
state2.validationElementErrors.clear();
|
|
653
|
+
state2.pendingSubmitCount = 0;
|
|
654
|
+
cleanupFormatRegistry(state2);
|
|
655
|
+
};
|
|
656
|
+
|
|
657
|
+
// src/api/disable-auto-submit.ts
|
|
658
|
+
var createDisableAutoSubmit = (state2, helpers) => () => {
|
|
659
|
+
state2.autoSubmitEnabled = false;
|
|
660
|
+
if (state2.autoSubmitTimer) {
|
|
661
|
+
clearTimeout(state2.autoSubmitTimer);
|
|
662
|
+
state2.autoSubmitTimer = null;
|
|
663
|
+
}
|
|
664
|
+
helpers.notifySubscribers();
|
|
665
|
+
return state2.api;
|
|
666
|
+
};
|
|
667
|
+
|
|
668
|
+
// src/api/get-data.ts
|
|
669
|
+
var createGetData = (_state, _helpers) => () => {
|
|
670
|
+
if (!_state.element) return { data: {}, formData: new FormData() };
|
|
671
|
+
return parseFormData(_state.element);
|
|
672
|
+
};
|
|
673
|
+
|
|
674
|
+
// src/api/get-field.ts
|
|
675
|
+
var createGetField = (state2, helpers) => (name) => {
|
|
676
|
+
const fields = helpers.getFieldsByName(name);
|
|
677
|
+
if (fields.length === 0) return null;
|
|
678
|
+
return fields.length === 1 ? fields[0] : fields;
|
|
679
|
+
};
|
|
680
|
+
|
|
681
|
+
// src/core/state.ts
|
|
682
|
+
var createEmptyFormState = () => ({
|
|
683
|
+
values: {},
|
|
684
|
+
errors: {},
|
|
685
|
+
filledFields: [],
|
|
686
|
+
isValid: true,
|
|
687
|
+
isValidated: false,
|
|
688
|
+
autoSubmit: false,
|
|
689
|
+
isSubmitting: false,
|
|
690
|
+
submitCount: 0
|
|
691
|
+
});
|
|
692
|
+
var cloneErrors = (errors) => Object.fromEntries(
|
|
693
|
+
Object.entries(errors).map(([field, messages]) => [field, [...messages]])
|
|
694
|
+
);
|
|
695
|
+
var mergeErrors = (left, right) => {
|
|
696
|
+
const merged = { ...left };
|
|
697
|
+
for (const [field, messages] of Object.entries(right)) {
|
|
698
|
+
merged[field] = merged[field] ? [...merged[field], ...messages] : [...messages];
|
|
699
|
+
}
|
|
700
|
+
return merged;
|
|
701
|
+
};
|
|
702
|
+
|
|
703
|
+
// src/api/get-state.ts
|
|
704
|
+
var createGetState = (state2, helpers) => () => {
|
|
705
|
+
const values = helpers.getValues();
|
|
706
|
+
const errors = cloneErrors(helpers.getMergedErrors());
|
|
707
|
+
const filledFields = helpers.getTrackedFieldNames().filter((name) => {
|
|
708
|
+
const value = values[name];
|
|
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
|
+
return {
|
|
714
|
+
...createEmptyFormState(),
|
|
715
|
+
values,
|
|
716
|
+
errors,
|
|
717
|
+
filledFields,
|
|
718
|
+
isValid: Object.keys(errors).length === 0,
|
|
719
|
+
isValidated: state2.isValidated,
|
|
720
|
+
autoSubmit: state2.autoSubmitEnabled,
|
|
721
|
+
isSubmitting: state2.pendingSubmitCount > 0,
|
|
722
|
+
submitCount: state2.submitCount
|
|
723
|
+
};
|
|
724
|
+
};
|
|
725
|
+
|
|
726
|
+
// src/core/dom.ts
|
|
727
|
+
var isFormFieldElement = (value) => value instanceof HTMLInputElement || value instanceof HTMLSelectElement || value instanceof HTMLTextAreaElement;
|
|
728
|
+
var isArraySyntax = (field) => typeof field.name === "string" && field.name.endsWith("[]");
|
|
729
|
+
var resolveFormElement = (target) => {
|
|
730
|
+
if (!target) return null;
|
|
731
|
+
if (target instanceof HTMLFormElement) return target;
|
|
732
|
+
if (typeof target === "string") {
|
|
733
|
+
const node = document.getElementById(target);
|
|
734
|
+
return node instanceof HTMLFormElement ? node : null;
|
|
735
|
+
}
|
|
736
|
+
if ("current" in target) {
|
|
737
|
+
return target.current instanceof HTMLFormElement ? target.current : null;
|
|
738
|
+
}
|
|
739
|
+
return null;
|
|
740
|
+
};
|
|
741
|
+
var getNamedFields = (form2) => Array.from(form2.elements).filter(
|
|
742
|
+
(element) => isFormFieldElement(element) && Boolean(element.name)
|
|
743
|
+
);
|
|
744
|
+
var queryNamedFields = (form2, name) => getNamedFields(form2).filter((field) => field.name === name);
|
|
745
|
+
var isFieldElement = (value) => isFormFieldElement(value);
|
|
746
|
+
var isFieldFilled = (field) => {
|
|
747
|
+
if (field instanceof HTMLInputElement) {
|
|
748
|
+
if (field.type === "checkbox" || field.type === "radio") return field.checked;
|
|
749
|
+
if (field.type === "file") return Boolean(field.files && field.files.length > 0);
|
|
750
|
+
return field.value.trim() !== "";
|
|
751
|
+
}
|
|
752
|
+
return field.value !== "";
|
|
753
|
+
};
|
|
754
|
+
var applyBooleanAttribute = (field, attribute, enabled) => {
|
|
755
|
+
if (enabled) {
|
|
756
|
+
field.setAttribute(attribute, "");
|
|
757
|
+
} else {
|
|
758
|
+
field.removeAttribute(attribute);
|
|
759
|
+
}
|
|
760
|
+
};
|
|
761
|
+
var readFieldValue = (fields) => {
|
|
762
|
+
if (fields.length === 0) return void 0;
|
|
763
|
+
const first = fields[0];
|
|
764
|
+
if (!first) return void 0;
|
|
765
|
+
if (isArraySyntax(first) && fields.length > 1) {
|
|
766
|
+
if (first instanceof HTMLSelectElement || first instanceof HTMLTextAreaElement) {
|
|
767
|
+
return fields.map((f) => f.value);
|
|
768
|
+
}
|
|
769
|
+
if (first instanceof HTMLInputElement) {
|
|
770
|
+
if (first.type === "radio") {
|
|
771
|
+
const checked = fields.find(
|
|
772
|
+
(f) => f instanceof HTMLInputElement && f.checked
|
|
773
|
+
);
|
|
774
|
+
return checked?.value ?? "";
|
|
775
|
+
}
|
|
776
|
+
if (first.type === "checkbox") {
|
|
777
|
+
return fields.filter(
|
|
778
|
+
(f) => f instanceof HTMLInputElement && f.checked
|
|
779
|
+
).map((f) => f.value);
|
|
780
|
+
}
|
|
781
|
+
if (first.type === "file") {
|
|
782
|
+
return fields.flatMap(
|
|
783
|
+
(f) => f instanceof HTMLInputElement && f.files ? Array.from(f.files) : []
|
|
784
|
+
);
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
return fields.map((f) => f.value);
|
|
788
|
+
}
|
|
789
|
+
if (first instanceof HTMLSelectElement && first.multiple) {
|
|
790
|
+
return Array.from(first.selectedOptions, (option) => option.value);
|
|
791
|
+
}
|
|
792
|
+
if (first instanceof HTMLSelectElement || first instanceof HTMLTextAreaElement) {
|
|
793
|
+
return first.value;
|
|
794
|
+
}
|
|
795
|
+
if (first instanceof HTMLInputElement) {
|
|
796
|
+
if (first.type === "radio") {
|
|
797
|
+
const checked = fields.find(
|
|
798
|
+
(f) => f instanceof HTMLInputElement && f.checked
|
|
799
|
+
);
|
|
800
|
+
return checked?.value ?? "";
|
|
801
|
+
}
|
|
802
|
+
if (first.type === "checkbox") {
|
|
803
|
+
const values = fields.filter(
|
|
804
|
+
(f) => f instanceof HTMLInputElement && f.checked
|
|
805
|
+
).map((f) => f.value);
|
|
806
|
+
return values.length > 1 ? values : values[0] ?? "";
|
|
807
|
+
}
|
|
808
|
+
if (first.type === "file") {
|
|
809
|
+
return first.files ? Array.from(first.files) : [];
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
return first.value;
|
|
813
|
+
};
|
|
814
|
+
var writeFieldValue = (fields, value) => {
|
|
815
|
+
const normalizedArray = Array.isArray(value) ? value.map((item) => String(item)) : null;
|
|
816
|
+
const normalizedValue = value === void 0 || value === null ? "" : String(value);
|
|
817
|
+
const distributeArray = fields.length > 0 && fields[0] !== void 0 && isArraySyntax(fields[0]) && normalizedArray !== null;
|
|
818
|
+
for (let index = 0; index < fields.length; index++) {
|
|
819
|
+
const field = fields[index];
|
|
820
|
+
if (!field) continue;
|
|
821
|
+
if (field instanceof HTMLInputElement) {
|
|
822
|
+
if (field.type === "checkbox") {
|
|
823
|
+
field.checked = normalizedArray ? normalizedArray.includes(field.value) : normalizedValue === field.value;
|
|
824
|
+
continue;
|
|
825
|
+
}
|
|
826
|
+
if (field.type === "radio") {
|
|
827
|
+
field.checked = field.value === normalizedValue;
|
|
828
|
+
continue;
|
|
829
|
+
}
|
|
830
|
+
if (field.type === "file") {
|
|
831
|
+
if (Array.isArray(value) && value.length === 0) field.value = "";
|
|
832
|
+
continue;
|
|
833
|
+
}
|
|
834
|
+
if (distributeArray && normalizedArray) {
|
|
835
|
+
field.value = normalizedArray[index] ?? "";
|
|
836
|
+
continue;
|
|
837
|
+
}
|
|
838
|
+
field.value = normalizedValue;
|
|
839
|
+
continue;
|
|
840
|
+
}
|
|
841
|
+
if (field instanceof HTMLSelectElement && field.multiple) {
|
|
842
|
+
const selectedValues = normalizedArray ?? [normalizedValue];
|
|
843
|
+
for (const option of field.options) {
|
|
844
|
+
option.selected = selectedValues.includes(option.value);
|
|
845
|
+
}
|
|
846
|
+
continue;
|
|
847
|
+
}
|
|
848
|
+
if (distributeArray && normalizedArray) {
|
|
849
|
+
field.value = normalizedArray[index] ?? "";
|
|
850
|
+
continue;
|
|
851
|
+
}
|
|
852
|
+
field.value = normalizedValue;
|
|
853
|
+
}
|
|
854
|
+
};
|
|
855
|
+
var clearAttributes = (form2, attributes) => {
|
|
856
|
+
for (const attribute of attributes) {
|
|
857
|
+
getNamedFields(form2).forEach((field) => field.removeAttribute(attribute));
|
|
858
|
+
}
|
|
859
|
+
};
|
|
860
|
+
var submitForm = (form2) => {
|
|
861
|
+
if (typeof form2.requestSubmit === "function") {
|
|
862
|
+
form2.requestSubmit();
|
|
863
|
+
return;
|
|
864
|
+
}
|
|
865
|
+
const submitButton = form2.querySelector(
|
|
866
|
+
'button[type="submit"], input[type="submit"]'
|
|
867
|
+
);
|
|
868
|
+
if (submitButton) {
|
|
869
|
+
submitButton.click();
|
|
870
|
+
return;
|
|
871
|
+
}
|
|
872
|
+
form2.submit();
|
|
873
|
+
};
|
|
874
|
+
|
|
875
|
+
// src/api/get-value.ts
|
|
876
|
+
var createGetValue = (state2, helpers) => (name) => readFieldValue(helpers.getFieldsByName(name));
|
|
877
|
+
|
|
878
|
+
// src/api/observe.ts
|
|
879
|
+
var createObserve = (state2, helpers) => (field, callback) => {
|
|
880
|
+
const callbacks = state2.watchedFields.get(field) ?? /* @__PURE__ */ new Set();
|
|
881
|
+
callbacks.add(callback);
|
|
882
|
+
state2.watchedFields.set(field, callbacks);
|
|
883
|
+
if (state2.element) {
|
|
884
|
+
callback(state2.api.getValue(field), state2.api.getField(field), state2.element, state2.api.getState());
|
|
885
|
+
}
|
|
886
|
+
return () => {
|
|
887
|
+
const current = state2.watchedFields.get(field);
|
|
888
|
+
current?.delete(callback);
|
|
889
|
+
if (current && current.size === 0) {
|
|
890
|
+
state2.watchedFields.delete(field);
|
|
891
|
+
}
|
|
892
|
+
};
|
|
893
|
+
};
|
|
894
|
+
|
|
895
|
+
// src/api/on-submit.ts
|
|
896
|
+
var createOnSubmit = (state2, _helpers) => (callback, preventDefault = true) => {
|
|
897
|
+
state2.submitHandlers.add({ callback, preventDefault });
|
|
898
|
+
return state2.api;
|
|
899
|
+
};
|
|
900
|
+
|
|
901
|
+
// src/api/prefill.ts
|
|
902
|
+
var createPrefill = (state2, _helpers) => (fieldName) => {
|
|
903
|
+
if (!state2.element) return state2.api;
|
|
904
|
+
const params = new URLSearchParams(window.location.search);
|
|
905
|
+
params.forEach((value, key) => {
|
|
906
|
+
if (fieldName && fieldName !== key) return;
|
|
907
|
+
state2.api.setValue(key, value);
|
|
908
|
+
});
|
|
909
|
+
return state2.api;
|
|
910
|
+
};
|
|
911
|
+
|
|
912
|
+
// src/api/reset.ts
|
|
913
|
+
var createReset = (state2, helpers) => () => {
|
|
914
|
+
if (!state2.element) return state2.api;
|
|
915
|
+
state2.element.reset();
|
|
916
|
+
resetFormattedFields(state2);
|
|
917
|
+
state2.manualErrors = {};
|
|
918
|
+
state2.validationErrors = {};
|
|
919
|
+
state2.validationGroupErrors = {};
|
|
920
|
+
state2.validationElementErrors.clear();
|
|
921
|
+
clearAttributes(state2.element, [
|
|
922
|
+
state2.attributes.error,
|
|
923
|
+
state2.attributes.filled,
|
|
924
|
+
"aria-invalid"
|
|
925
|
+
]);
|
|
926
|
+
helpers.syncVisualState();
|
|
927
|
+
helpers.notifySubscribers();
|
|
928
|
+
return state2.api;
|
|
929
|
+
};
|
|
930
|
+
|
|
931
|
+
// src/api/validate.ts
|
|
932
|
+
var createValidate = (state2, helpers) => (fields) => {
|
|
933
|
+
state2.isValidated = true;
|
|
934
|
+
helpers.syncVisualState(fields);
|
|
935
|
+
return helpers.validateNames(fields);
|
|
936
|
+
};
|
|
937
|
+
|
|
938
|
+
// src/api/revalidate.ts
|
|
939
|
+
var createRevalidate = createValidate;
|
|
940
|
+
|
|
941
|
+
// src/api/set-errors.ts
|
|
942
|
+
var createSetErrors = (state2, helpers) => (fields) => {
|
|
943
|
+
if (Array.isArray(fields)) {
|
|
944
|
+
state2.manualErrors = {
|
|
945
|
+
...state2.manualErrors,
|
|
946
|
+
...Object.fromEntries(fields.map((field) => [field, ["Invalid value."]]))
|
|
947
|
+
};
|
|
948
|
+
} else {
|
|
949
|
+
state2.manualErrors = {
|
|
950
|
+
...state2.manualErrors,
|
|
951
|
+
...cloneErrors(fields)
|
|
952
|
+
};
|
|
953
|
+
}
|
|
954
|
+
helpers.syncVisualState(Array.isArray(fields) ? fields : Object.keys(fields));
|
|
955
|
+
helpers.notifySubscribers();
|
|
956
|
+
return state2.api;
|
|
957
|
+
};
|
|
958
|
+
|
|
959
|
+
// src/api/set-value.ts
|
|
960
|
+
var createSetValue = (state2, helpers) => (name, value) => {
|
|
961
|
+
const fields = helpers.getFieldsByName(name);
|
|
962
|
+
if (fields.length === 0) return state2.api;
|
|
963
|
+
writeFieldValue(fields, value);
|
|
964
|
+
const firstField = fields[0];
|
|
965
|
+
if (!firstField) return state2.api;
|
|
966
|
+
firstField.dispatchEvent(new Event("input", { bubbles: true }));
|
|
967
|
+
return state2.api;
|
|
968
|
+
};
|
|
969
|
+
|
|
970
|
+
// src/api/subscribe.ts
|
|
971
|
+
var createSubscribe = (state2, _helpers) => (listener) => {
|
|
972
|
+
state2.subscribers.add(listener);
|
|
973
|
+
listener(state2.api.getState());
|
|
974
|
+
return () => {
|
|
975
|
+
state2.subscribers.delete(listener);
|
|
976
|
+
};
|
|
977
|
+
};
|
|
978
|
+
|
|
979
|
+
// src/api/unwatch.ts
|
|
980
|
+
var createUnwatch = (state2, _helpers) => (field, callback) => {
|
|
981
|
+
if (!field) {
|
|
982
|
+
state2.watchedFields.clear();
|
|
983
|
+
return state2.api;
|
|
984
|
+
}
|
|
985
|
+
if (!callback) {
|
|
986
|
+
state2.watchedFields.delete(field);
|
|
987
|
+
return state2.api;
|
|
988
|
+
}
|
|
989
|
+
const callbacks = state2.watchedFields.get(field);
|
|
990
|
+
callbacks?.delete(callback);
|
|
991
|
+
if (callbacks && callbacks.size === 0) {
|
|
992
|
+
state2.watchedFields.delete(field);
|
|
993
|
+
}
|
|
994
|
+
return state2.api;
|
|
995
|
+
};
|
|
996
|
+
|
|
997
|
+
// src/api/watch.ts
|
|
998
|
+
var createWatch = (state2, helpers) => (field, callback) => {
|
|
999
|
+
const observe = createObserve(state2, helpers);
|
|
1000
|
+
observe(field, callback);
|
|
1001
|
+
return state2.api;
|
|
1002
|
+
};
|
|
1003
|
+
|
|
1004
|
+
// src/core/controller.ts
|
|
1005
|
+
var DEFAULT_ATTRIBUTES = {
|
|
1006
|
+
filled: "css-filled",
|
|
1007
|
+
error: "css-error"
|
|
1008
|
+
};
|
|
1009
|
+
var DEFAULT_OPTIONS = {
|
|
1010
|
+
autoValidate: true,
|
|
1011
|
+
clearErrorsOnSubmit: true,
|
|
1012
|
+
clearManualErrorsOnChange: true
|
|
1013
|
+
};
|
|
1014
|
+
var createFormController = (target, options = {}) => {
|
|
1015
|
+
const element = resolveFormElement(target);
|
|
1016
|
+
const attributes = { ...DEFAULT_ATTRIBUTES, ...options.attributes };
|
|
1017
|
+
const normalizedOptions = {
|
|
1018
|
+
...DEFAULT_OPTIONS,
|
|
1019
|
+
...options,
|
|
1020
|
+
attributes
|
|
1021
|
+
};
|
|
1022
|
+
const state2 = {
|
|
1023
|
+
element,
|
|
1024
|
+
options: normalizedOptions,
|
|
1025
|
+
attributes,
|
|
1026
|
+
validators: normalizedOptions.validators ?? {},
|
|
1027
|
+
watchedFields: /* @__PURE__ */ new Map(),
|
|
1028
|
+
subscribers: /* @__PURE__ */ new Set(),
|
|
1029
|
+
submitHandlers: /* @__PURE__ */ new Set(),
|
|
1030
|
+
fieldCache: /* @__PURE__ */ new Map(),
|
|
1031
|
+
manualErrors: {},
|
|
1032
|
+
validationErrors: {},
|
|
1033
|
+
validationGroupErrors: {},
|
|
1034
|
+
validationElementErrors: /* @__PURE__ */ new Map(),
|
|
1035
|
+
isValidated: Boolean(normalizedOptions.autoValidate),
|
|
1036
|
+
autoSubmitEnabled: false,
|
|
1037
|
+
autoSubmitDebounce: 0,
|
|
1038
|
+
submitCount: 0,
|
|
1039
|
+
pendingSubmitCount: 0,
|
|
1040
|
+
autoSubmitTimer: null,
|
|
1041
|
+
isDestroyed: false,
|
|
1042
|
+
listeners: [],
|
|
1043
|
+
mutationObserver: null,
|
|
1044
|
+
cleanups: /* @__PURE__ */ new Set(),
|
|
1045
|
+
formattedFields: /* @__PURE__ */ new Map(),
|
|
1046
|
+
api: null
|
|
1047
|
+
};
|
|
1048
|
+
const validationDependents = /* @__PURE__ */ new Map();
|
|
1049
|
+
for (const [field, rules] of Object.entries(state2.validators)) {
|
|
1050
|
+
const sources = /* @__PURE__ */ new Set();
|
|
1051
|
+
const sameAs = rules.sameAs;
|
|
1052
|
+
if (sameAs !== void 0) {
|
|
1053
|
+
sources.add(typeof sameAs === "string" ? sameAs : sameAs.value);
|
|
1054
|
+
}
|
|
1055
|
+
if (typeof rules.dependsOn === "string") sources.add(rules.dependsOn);
|
|
1056
|
+
else rules.dependsOn?.forEach((source) => sources.add(source));
|
|
1057
|
+
for (const source of sources) {
|
|
1058
|
+
const dependents = validationDependents.get(source) ?? /* @__PURE__ */ new Set();
|
|
1059
|
+
dependents.add(field);
|
|
1060
|
+
validationDependents.set(source, dependents);
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
const notifySubscribers = () => {
|
|
1064
|
+
if (!state2.api) return;
|
|
1065
|
+
const snapshot = state2.api.getState();
|
|
1066
|
+
state2.subscribers.forEach((listener) => listener(snapshot));
|
|
1067
|
+
};
|
|
1068
|
+
const addListener2 = (node, type, handler, capture) => {
|
|
1069
|
+
if (capture) {
|
|
1070
|
+
node.addEventListener(type, handler, { capture: true });
|
|
1071
|
+
} else {
|
|
1072
|
+
node.addEventListener(type, handler);
|
|
1073
|
+
}
|
|
1074
|
+
const entry = { element: node, type, handler };
|
|
1075
|
+
if (capture !== void 0) entry.capture = capture;
|
|
1076
|
+
state2.listeners.push(entry);
|
|
1077
|
+
};
|
|
1078
|
+
const clearFieldCache = () => state2.fieldCache.clear();
|
|
1079
|
+
const getFieldsByName = (name) => {
|
|
1080
|
+
if (!state2.element) return [];
|
|
1081
|
+
const fields = queryNamedFields(state2.element, name);
|
|
1082
|
+
state2.fieldCache.set(name, fields);
|
|
1083
|
+
return fields;
|
|
1084
|
+
};
|
|
1085
|
+
const getTrackedFieldNames = () => {
|
|
1086
|
+
if (!state2.element) return Object.keys(state2.validators);
|
|
1087
|
+
const names = new Set(Object.keys(state2.validators));
|
|
1088
|
+
for (const field of getNamedFields(state2.element)) {
|
|
1089
|
+
names.add(field.name);
|
|
1090
|
+
}
|
|
1091
|
+
for (const [canonical, entry] of state2.formattedFields) {
|
|
1092
|
+
names.add(canonical);
|
|
1093
|
+
if (entry.displayName && entry.displayName !== canonical) {
|
|
1094
|
+
names.add(entry.displayName);
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
return Array.from(names);
|
|
1098
|
+
};
|
|
1099
|
+
const getValues = () => {
|
|
1100
|
+
const values = {};
|
|
1101
|
+
for (const name of getTrackedFieldNames()) {
|
|
1102
|
+
values[name] = readFieldValue(getFieldsByName(name));
|
|
1103
|
+
}
|
|
1104
|
+
return values;
|
|
1105
|
+
};
|
|
1106
|
+
const getMergedErrors = () => mergeErrors(state2.validationErrors, state2.manualErrors);
|
|
1107
|
+
const syncVisualState = (names) => {
|
|
1108
|
+
if (!state2.element) return;
|
|
1109
|
+
const sourceNames = names ?? getTrackedFieldNames();
|
|
1110
|
+
for (const name of sourceNames) {
|
|
1111
|
+
const displayName = resolveDisplayNameForName(state2, name);
|
|
1112
|
+
const canonical = resolveCanonicalForName(state2, name) ?? name;
|
|
1113
|
+
const fields = getFieldsByName(displayName);
|
|
1114
|
+
const hasGroupError = Boolean(
|
|
1115
|
+
state2.validationGroupErrors[canonical]?.length || state2.manualErrors[canonical]?.length
|
|
1116
|
+
);
|
|
1117
|
+
const elementErrors = state2.validationElementErrors.get(canonical);
|
|
1118
|
+
fields.forEach((field) => {
|
|
1119
|
+
const hasError = hasGroupError || Boolean(elementErrors?.get(field)?.length);
|
|
1120
|
+
applyBooleanAttribute(field, attributes.filled, isFieldFilled(field));
|
|
1121
|
+
applyBooleanAttribute(field, attributes.error, hasError);
|
|
1122
|
+
if (hasError) field.setAttribute("aria-invalid", "true");
|
|
1123
|
+
else field.removeAttribute("aria-invalid");
|
|
1124
|
+
});
|
|
1125
|
+
}
|
|
1126
|
+
};
|
|
1127
|
+
void readFieldValue;
|
|
1128
|
+
void writeFieldValue;
|
|
1129
|
+
const validateNames = (names) => {
|
|
1130
|
+
const targetNames = names ?? Object.keys(state2.validators);
|
|
1131
|
+
const values = getValues();
|
|
1132
|
+
const nextValidationErrors = names ? cloneErrors(state2.validationErrors) : {};
|
|
1133
|
+
const nextGroupErrors = names ? cloneErrors(state2.validationGroupErrors) : {};
|
|
1134
|
+
const nextElementErrors = names ? new Map(state2.validationElementErrors) : /* @__PURE__ */ new Map();
|
|
1135
|
+
for (const name of targetNames) {
|
|
1136
|
+
const rules = state2.validators[name];
|
|
1137
|
+
if (!rules) {
|
|
1138
|
+
delete nextValidationErrors[name];
|
|
1139
|
+
delete nextGroupErrors[name];
|
|
1140
|
+
nextElementErrors.delete(name);
|
|
1141
|
+
continue;
|
|
1142
|
+
}
|
|
1143
|
+
const fields = getFieldsByName(name);
|
|
1144
|
+
const displayFields = getFieldsByName(resolveDisplayNameForName(state2, name));
|
|
1145
|
+
const items = fields.map((field, index) => ({
|
|
1146
|
+
value: readFieldValue([field]),
|
|
1147
|
+
element: displayFields[index] ?? field
|
|
1148
|
+
}));
|
|
1149
|
+
const result = validateFieldValueDetailed(name, values[name], rules, values, items);
|
|
1150
|
+
const messages = [...result.groupErrors, ...result.itemErrors.flatMap((item) => item.messages)];
|
|
1151
|
+
if (messages.length > 0) nextValidationErrors[name] = messages;
|
|
1152
|
+
else delete nextValidationErrors[name];
|
|
1153
|
+
if (result.groupErrors.length > 0) nextGroupErrors[name] = result.groupErrors;
|
|
1154
|
+
else delete nextGroupErrors[name];
|
|
1155
|
+
const perElement = /* @__PURE__ */ new Map();
|
|
1156
|
+
result.itemErrors.forEach((item) => {
|
|
1157
|
+
if (item.element) perElement.set(item.element, item.messages);
|
|
1158
|
+
});
|
|
1159
|
+
if (perElement.size > 0) nextElementErrors.set(name, perElement);
|
|
1160
|
+
else nextElementErrors.delete(name);
|
|
1161
|
+
}
|
|
1162
|
+
state2.validationErrors = nextValidationErrors;
|
|
1163
|
+
state2.validationGroupErrors = nextGroupErrors;
|
|
1164
|
+
state2.validationElementErrors = nextElementErrors;
|
|
1165
|
+
syncVisualState(names);
|
|
1166
|
+
return {
|
|
1167
|
+
isValid: Object.keys(getMergedErrors()).length === 0,
|
|
1168
|
+
errors: cloneErrors(getMergedErrors())
|
|
1169
|
+
};
|
|
1170
|
+
};
|
|
1171
|
+
const getAffectedValidationNames = (name) => {
|
|
1172
|
+
const affected = [];
|
|
1173
|
+
const pending = [name];
|
|
1174
|
+
const visited = /* @__PURE__ */ new Set();
|
|
1175
|
+
for (let index = 0; index < pending.length; index += 1) {
|
|
1176
|
+
const current = pending[index];
|
|
1177
|
+
if (visited.has(current)) continue;
|
|
1178
|
+
visited.add(current);
|
|
1179
|
+
affected.push(current);
|
|
1180
|
+
for (const dependent of validationDependents.get(current) ?? []) {
|
|
1181
|
+
if (!visited.has(dependent)) pending.push(dependent);
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
return affected;
|
|
1185
|
+
};
|
|
1186
|
+
const emitFieldWatchers = (name) => {
|
|
1187
|
+
if (!state2.element || !state2.api) return;
|
|
1188
|
+
const callbacks = state2.watchedFields.get(name);
|
|
1189
|
+
if (!callbacks || callbacks.size === 0) return;
|
|
1190
|
+
const value = state2.api.getValue(name);
|
|
1191
|
+
const fieldElement = state2.api.getField(name);
|
|
1192
|
+
const snapshot = state2.api.getState();
|
|
1193
|
+
callbacks.forEach((callback) => callback(value, fieldElement, state2.element, snapshot));
|
|
1194
|
+
};
|
|
1195
|
+
const scheduleAutoSubmit = () => {
|
|
1196
|
+
if (!state2.element || !state2.autoSubmitEnabled) return;
|
|
1197
|
+
if (state2.autoSubmitTimer) clearTimeout(state2.autoSubmitTimer);
|
|
1198
|
+
if (state2.autoSubmitDebounce > 0) {
|
|
1199
|
+
state2.autoSubmitTimer = setTimeout(
|
|
1200
|
+
() => submitForm(state2.element),
|
|
1201
|
+
state2.autoSubmitDebounce
|
|
1202
|
+
);
|
|
1203
|
+
return;
|
|
1204
|
+
}
|
|
1205
|
+
submitForm(state2.element);
|
|
1206
|
+
};
|
|
1207
|
+
const helpers = {
|
|
1208
|
+
notifySubscribers,
|
|
1209
|
+
clearFieldCache,
|
|
1210
|
+
getFieldsByName,
|
|
1211
|
+
getTrackedFieldNames,
|
|
1212
|
+
getValues,
|
|
1213
|
+
getMergedErrors,
|
|
1214
|
+
syncVisualState,
|
|
1215
|
+
validateNames,
|
|
1216
|
+
emitFieldWatchers,
|
|
1217
|
+
scheduleAutoSubmit
|
|
1218
|
+
};
|
|
1219
|
+
const handleDelegatedEvent = (event) => {
|
|
1220
|
+
const target2 = event.target;
|
|
1221
|
+
if (!(target2 instanceof Element) || !isFieldElement(target2)) return;
|
|
1222
|
+
const name = target2.name;
|
|
1223
|
+
if (!name) return;
|
|
1224
|
+
clearFieldCache();
|
|
1225
|
+
const canonical = resolveCanonicalForName(state2, name) ?? name;
|
|
1226
|
+
const displayName = resolveDisplayNameForName(state2, name);
|
|
1227
|
+
if (normalizedOptions.clearManualErrorsOnChange) {
|
|
1228
|
+
delete state2.manualErrors[canonical];
|
|
1229
|
+
}
|
|
1230
|
+
syncVisualState([displayName]);
|
|
1231
|
+
const affectedValidationNames = getAffectedValidationNames(canonical);
|
|
1232
|
+
if (state2.isValidated && affectedValidationNames.some((field) => state2.validators[field] !== void 0)) {
|
|
1233
|
+
validateNames(affectedValidationNames);
|
|
1234
|
+
}
|
|
1235
|
+
emitFieldWatchers(canonical);
|
|
1236
|
+
if (canonical !== name) emitFieldWatchers(name);
|
|
1237
|
+
notifySubscribers();
|
|
1238
|
+
scheduleAutoSubmit();
|
|
1239
|
+
};
|
|
1240
|
+
const handleExternalDelegatedEvent = (event) => {
|
|
1241
|
+
const target2 = event.target;
|
|
1242
|
+
if (!(target2 instanceof Element) || !isFieldElement(target2)) return;
|
|
1243
|
+
if (target2.form !== state2.element || state2.element?.contains(target2)) return;
|
|
1244
|
+
handleDelegatedEvent(event);
|
|
1245
|
+
};
|
|
1246
|
+
const handleSubmitEvent = (event) => {
|
|
1247
|
+
if (!state2.element || state2.isDestroyed || !state2.api) return;
|
|
1248
|
+
if (normalizedOptions.clearErrorsOnSubmit) state2.manualErrors = {};
|
|
1249
|
+
const validation = state2.api.validate();
|
|
1250
|
+
const handlers = Array.from(state2.submitHandlers);
|
|
1251
|
+
const shouldPrevent = validation.isValid ? handlers.some((handler) => handler.preventDefault) : true;
|
|
1252
|
+
if (shouldPrevent) event.preventDefault();
|
|
1253
|
+
state2.submitCount += 1;
|
|
1254
|
+
notifySubscribers();
|
|
1255
|
+
if (!validation.isValid) {
|
|
1256
|
+
for (const name of Object.keys(validation.errors)) {
|
|
1257
|
+
const displayName = resolveDisplayNameForName(state2, name);
|
|
1258
|
+
const fields = getFieldsByName(displayName);
|
|
1259
|
+
const elementErrors = state2.validationElementErrors.get(name);
|
|
1260
|
+
const field = fields.find((candidate2) => {
|
|
1261
|
+
if (!candidate2.isConnected || candidate2.disabled || candidate2.hidden) return false;
|
|
1262
|
+
if (candidate2 instanceof HTMLInputElement && candidate2.type === "hidden") return false;
|
|
1263
|
+
return elementErrors?.has(candidate2);
|
|
1264
|
+
}) ?? fields.find((candidate2) => {
|
|
1265
|
+
if (!candidate2.isConnected || candidate2.disabled || candidate2.hidden) return false;
|
|
1266
|
+
return !(candidate2 instanceof HTMLInputElement && candidate2.type === "hidden");
|
|
1267
|
+
});
|
|
1268
|
+
if (field) {
|
|
1269
|
+
field.focus();
|
|
1270
|
+
break;
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
1273
|
+
return;
|
|
1274
|
+
}
|
|
1275
|
+
const candidate = event;
|
|
1276
|
+
const submitter = candidate.submitter instanceof HTMLElement ? candidate.submitter : null;
|
|
1277
|
+
const { data, formData } = parseFormData(state2.element, submitter);
|
|
1278
|
+
const snapshot = state2.api.getState();
|
|
1279
|
+
const pending = [];
|
|
1280
|
+
let synchronousError;
|
|
1281
|
+
let hasSynchronousError = false;
|
|
1282
|
+
try {
|
|
1283
|
+
for (const handler of handlers) {
|
|
1284
|
+
const result = handler.callback(state2.element, data, formData, snapshot);
|
|
1285
|
+
if (result && typeof result.then === "function") pending.push(Promise.resolve(result));
|
|
1286
|
+
}
|
|
1287
|
+
} catch (error) {
|
|
1288
|
+
synchronousError = error;
|
|
1289
|
+
hasSynchronousError = true;
|
|
1290
|
+
}
|
|
1291
|
+
if (pending.length > 0) {
|
|
1292
|
+
state2.pendingSubmitCount += 1;
|
|
1293
|
+
notifySubscribers();
|
|
1294
|
+
void Promise.allSettled(pending).then(() => {
|
|
1295
|
+
state2.pendingSubmitCount = Math.max(0, state2.pendingSubmitCount - 1);
|
|
1296
|
+
if (!state2.isDestroyed) notifySubscribers();
|
|
1297
|
+
});
|
|
1298
|
+
}
|
|
1299
|
+
if (hasSynchronousError) throw synchronousError;
|
|
1300
|
+
};
|
|
1301
|
+
const startMutationObserver = () => {
|
|
1302
|
+
if (!state2.element || typeof MutationObserver === "undefined") return;
|
|
1303
|
+
state2.mutationObserver = new MutationObserver(() => {
|
|
1304
|
+
clearFieldCache();
|
|
1305
|
+
if (state2.isValidated) {
|
|
1306
|
+
validateNames();
|
|
1307
|
+
} else syncVisualState();
|
|
1308
|
+
notifySubscribers();
|
|
1309
|
+
});
|
|
1310
|
+
state2.mutationObserver.observe(state2.element, {
|
|
1311
|
+
childList: true,
|
|
1312
|
+
subtree: true,
|
|
1313
|
+
attributes: true,
|
|
1314
|
+
attributeFilter: ["name", "type"]
|
|
1315
|
+
});
|
|
1316
|
+
};
|
|
1317
|
+
const api = {
|
|
1318
|
+
get element() {
|
|
1319
|
+
return state2.element;
|
|
1320
|
+
},
|
|
1321
|
+
get f() {
|
|
1322
|
+
return state2.element;
|
|
1323
|
+
},
|
|
1324
|
+
get options() {
|
|
1325
|
+
return state2.options;
|
|
1326
|
+
},
|
|
1327
|
+
onSubmit: createOnSubmit(state2, helpers),
|
|
1328
|
+
addCleanup: createAddCleanup(state2, helpers),
|
|
1329
|
+
watch: createWatch(state2, helpers),
|
|
1330
|
+
observe: createObserve(state2, helpers),
|
|
1331
|
+
unwatch: createUnwatch(state2, helpers),
|
|
1332
|
+
subscribe: createSubscribe(state2, helpers),
|
|
1333
|
+
prefill: createPrefill(state2, helpers),
|
|
1334
|
+
append: createAppend(state2, helpers),
|
|
1335
|
+
setErrors: createSetErrors(state2, helpers),
|
|
1336
|
+
clearErrors: createClearErrors(state2, helpers),
|
|
1337
|
+
setValue: createSetValue(state2, helpers),
|
|
1338
|
+
getValue: createGetValue(state2, helpers),
|
|
1339
|
+
getField: createGetField(state2, helpers),
|
|
1340
|
+
validate: createValidate(state2, helpers),
|
|
1341
|
+
revalidate: createRevalidate(state2, helpers),
|
|
1342
|
+
reset: createReset(state2, helpers),
|
|
1343
|
+
autoSubmit: createAutoSubmit(state2, helpers),
|
|
1344
|
+
disableAutoSubmit: createDisableAutoSubmit(state2, helpers),
|
|
1345
|
+
getData: createGetData(state2, helpers),
|
|
1346
|
+
getState: createGetState(state2, helpers),
|
|
1347
|
+
format: createFormat(state2, helpers),
|
|
1348
|
+
formatAll: createFormatAll(state2, helpers),
|
|
1349
|
+
destroy: createDestroy(state2, helpers)
|
|
1350
|
+
};
|
|
1351
|
+
state2.api = api;
|
|
1352
|
+
if (state2.element) {
|
|
1353
|
+
addListener2(state2.element, "input", handleDelegatedEvent);
|
|
1354
|
+
addListener2(document, "input", handleExternalDelegatedEvent);
|
|
1355
|
+
addListener2(state2.element, "submit", handleSubmitEvent);
|
|
1356
|
+
startMutationObserver();
|
|
1357
|
+
}
|
|
1358
|
+
if (normalizedOptions.autoSubmit) {
|
|
1359
|
+
api.autoSubmit(normalizedOptions.autoSubmit);
|
|
1360
|
+
}
|
|
1361
|
+
if (normalizedOptions.formats) {
|
|
1362
|
+
for (const config of Object.values(normalizedOptions.formats)) {
|
|
1363
|
+
api.format(config);
|
|
1364
|
+
}
|
|
1365
|
+
}
|
|
1366
|
+
if (state2.isValidated) validateNames();
|
|
1367
|
+
else syncVisualState();
|
|
1368
|
+
return api;
|
|
1369
|
+
};
|
|
1370
|
+
|
|
1371
|
+
// src/api/form.ts
|
|
1372
|
+
var form = (target, options = {}) => createFormController(target, options);
|
|
1373
|
+
|
|
1374
|
+
// src/browser/registry.ts
|
|
1375
|
+
var available = {};
|
|
1376
|
+
var newForm = (input) => {
|
|
1377
|
+
const { id, options } = input;
|
|
1378
|
+
if (!id) {
|
|
1379
|
+
console.error("Form ID is required");
|
|
1380
|
+
return;
|
|
1381
|
+
}
|
|
1382
|
+
available[id]?.destroy();
|
|
1383
|
+
const controller = form(id, { ...options });
|
|
1384
|
+
available[id] = controller;
|
|
1385
|
+
return controller;
|
|
1386
|
+
};
|
|
1387
|
+
var destroyForm = (id) => {
|
|
1388
|
+
if (!id) {
|
|
1389
|
+
console.error("Form ID is required");
|
|
1390
|
+
return;
|
|
1391
|
+
}
|
|
1392
|
+
const controller = available[id];
|
|
1393
|
+
if (controller) {
|
|
1394
|
+
controller.destroy();
|
|
1395
|
+
delete available[id];
|
|
1396
|
+
} else {
|
|
1397
|
+
console.warn(`Form with ID ${id} not found`);
|
|
1398
|
+
}
|
|
1399
|
+
};
|
|
1400
|
+
var browser = {
|
|
1401
|
+
form,
|
|
1402
|
+
newForm,
|
|
1403
|
+
destroyForm,
|
|
1404
|
+
available
|
|
1405
|
+
};
|
|
1406
|
+
|
|
1407
|
+
export {
|
|
1408
|
+
parseFormData,
|
|
1409
|
+
validateFieldValue,
|
|
1410
|
+
validateValues,
|
|
1411
|
+
createFormController,
|
|
1412
|
+
form,
|
|
1413
|
+
browser
|
|
1414
|
+
};
|
|
1415
|
+
//# sourceMappingURL=chunk-WGPAVJTK.js.map
|