@samline/forms 2.2.3 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/dist/browser/global.d.ts +16 -5
- package/dist/browser/global.global.js +160 -87
- package/dist/browser/global.global.js.map +1 -1
- package/dist/index.cjs +160 -87
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +16 -5
- package/dist/index.d.ts +16 -5
- package/dist/index.js +160 -87
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -50,33 +50,45 @@ var cssEscape = (value) => {
|
|
|
50
50
|
}
|
|
51
51
|
return value.replace(/([!"#$%&'()*+,./:;<=>?@\[\\\]^`{|}~])/g, "\\$1");
|
|
52
52
|
};
|
|
53
|
-
var
|
|
53
|
+
var findVisibleField = (form2, displayName) => {
|
|
54
|
+
const node = form2.querySelector(
|
|
55
|
+
`[name="${cssEscape(displayName)}"]:not([type="hidden"])`
|
|
56
|
+
);
|
|
57
|
+
if (node instanceof HTMLInputElement || node instanceof HTMLTextAreaElement) {
|
|
58
|
+
return node;
|
|
59
|
+
}
|
|
60
|
+
return null;
|
|
61
|
+
};
|
|
62
|
+
var findHiddenMirror = (form2, fieldName) => {
|
|
54
63
|
const byAttr = form2.querySelector(
|
|
55
64
|
`[${FORMATTER_RAW_ATTRIBUTE}="${cssEscape(fieldName)}"]`
|
|
56
65
|
);
|
|
57
66
|
if (byAttr) return byAttr;
|
|
58
67
|
const byName = form2.querySelector(
|
|
59
|
-
`[name="${cssEscape(
|
|
68
|
+
`input[type="hidden"][name="${cssEscape(fieldName)}"]`
|
|
60
69
|
);
|
|
61
70
|
if (byName) return byName;
|
|
62
|
-
const byNameOld = form2.querySelector(
|
|
63
|
-
`[name="${cssEscape(`${fieldName}Raw`)}"]`
|
|
64
|
-
);
|
|
65
|
-
if (byNameOld) return byNameOld;
|
|
66
71
|
return null;
|
|
67
72
|
};
|
|
68
|
-
var
|
|
69
|
-
const existing =
|
|
73
|
+
var ensureHiddenMirror = (form2, fieldName) => {
|
|
74
|
+
const existing = findHiddenMirror(form2, fieldName);
|
|
70
75
|
if (existing) return existing;
|
|
71
76
|
const input = document.createElement("input");
|
|
72
77
|
input.type = "hidden";
|
|
73
|
-
input.name =
|
|
78
|
+
input.name = fieldName;
|
|
74
79
|
input.setAttribute("aria-hidden", "true");
|
|
75
80
|
input.tabIndex = -1;
|
|
76
81
|
input.setAttribute(FORMATTER_RAW_ATTRIBUTE, fieldName);
|
|
77
82
|
form2.appendChild(input);
|
|
78
83
|
return input;
|
|
79
84
|
};
|
|
85
|
+
var renameVisibleField = (visible, displayName) => {
|
|
86
|
+
if (visible.name === displayName) return;
|
|
87
|
+
visible.name = displayName;
|
|
88
|
+
};
|
|
89
|
+
var restoreVisibleName = (visible, originalName) => {
|
|
90
|
+
visible.name = originalName;
|
|
91
|
+
};
|
|
80
92
|
var applyFormattedValue = (visible, mirror, formatted, raw, inputType) => {
|
|
81
93
|
const rawInput = visible.value;
|
|
82
94
|
const caret = clamp(
|
|
@@ -194,6 +206,7 @@ var loadFormatter = () => {
|
|
|
194
206
|
};
|
|
195
207
|
|
|
196
208
|
// src/api/format.ts
|
|
209
|
+
var UNBOUND = null;
|
|
197
210
|
var registry = /* @__PURE__ */ new WeakMap();
|
|
198
211
|
var getRegistry = (state2) => {
|
|
199
212
|
let bucket = registry.get(state2);
|
|
@@ -204,97 +217,126 @@ var getRegistry = (state2) => {
|
|
|
204
217
|
return bucket;
|
|
205
218
|
};
|
|
206
219
|
var resolveFieldNames = (config) => Array.isArray(config.field) ? config.field : [config.field];
|
|
207
|
-
var addListener = (state2, target, type, handler) => {
|
|
208
|
-
|
|
209
|
-
|
|
220
|
+
var addListener = (state2, target, type, handler, useCapture) => {
|
|
221
|
+
if (useCapture) {
|
|
222
|
+
target.addEventListener(type, handler, { capture: true });
|
|
223
|
+
} else {
|
|
224
|
+
target.addEventListener(type, handler);
|
|
225
|
+
}
|
|
226
|
+
const entry = { element: target, type, handler };
|
|
227
|
+
if (useCapture !== void 0) entry.capture = useCapture;
|
|
228
|
+
state2.listeners.push(entry);
|
|
210
229
|
};
|
|
211
|
-
var
|
|
230
|
+
var readInputType = (event) => {
|
|
231
|
+
const candidate = event;
|
|
232
|
+
return typeof candidate.inputType === "string" ? candidate.inputType : void 0;
|
|
233
|
+
};
|
|
234
|
+
var buildHandler = (visible, mirror, formatFn, formatType, formatOptions) => {
|
|
212
235
|
const handler = (event) => {
|
|
213
|
-
if (!
|
|
214
|
-
|
|
215
|
-
const
|
|
236
|
+
if (!visible.isConnected || !mirror.isConnected) return;
|
|
237
|
+
const isVisible = event.target === visible;
|
|
238
|
+
const isMirror = event.target === mirror;
|
|
239
|
+
if (!isVisible && !isMirror) return;
|
|
240
|
+
const rawInput = isMirror ? mirror.value : visible.value;
|
|
216
241
|
const inputType = readInputType(event);
|
|
217
242
|
const { formatted, raw } = formatFn(rawInput, formatType, formatOptions);
|
|
218
243
|
if (!formatted && !raw) {
|
|
219
|
-
|
|
220
|
-
if (mirror
|
|
244
|
+
visible.value = "";
|
|
245
|
+
if (mirror.value !== "") mirror.value = "";
|
|
221
246
|
return;
|
|
222
247
|
}
|
|
223
|
-
applyFormattedValue(
|
|
224
|
-
void mirrorFieldName;
|
|
248
|
+
applyFormattedValue(visible, mirror, formatted, raw, inputType);
|
|
225
249
|
};
|
|
226
250
|
return handler;
|
|
227
251
|
};
|
|
228
|
-
var readInputType = (event) => {
|
|
229
|
-
const candidate = event;
|
|
230
|
-
return typeof candidate.inputType === "string" ? candidate.inputType : void 0;
|
|
231
|
-
};
|
|
232
252
|
var applyFormat = async (state2, helpers, config) => {
|
|
233
253
|
if (!state2.element || !state2.api) return;
|
|
234
|
-
const formatter = await loadFormatter();
|
|
235
|
-
if (!formatter) return;
|
|
236
254
|
const formatType = config.type;
|
|
237
255
|
const formatOptions = config.options;
|
|
238
256
|
const fieldNames = resolveFieldNames(config);
|
|
239
257
|
const bucket = getRegistry(state2);
|
|
240
258
|
for (const fieldName of fieldNames) {
|
|
241
|
-
const
|
|
242
|
-
|
|
243
|
-
const
|
|
244
|
-
(f) => f instanceof HTMLInputElement && f.type !== "
|
|
259
|
+
const displayName = config.displayField ?? `${fieldName}_displayed`;
|
|
260
|
+
const canonicalFields = helpers.getFieldsByName(fieldName);
|
|
261
|
+
const candidates = canonicalFields.filter(
|
|
262
|
+
(f) => f instanceof HTMLInputElement && f.type !== "hidden" || f instanceof HTMLTextAreaElement
|
|
245
263
|
);
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
});
|
|
263
|
-
const current = field.value;
|
|
264
|
-
if (current !== "") {
|
|
265
|
-
const { formatted, raw } = formatter.format(current, formatType, formatOptions);
|
|
266
|
-
applyFormattedValue(field, mirror, formatted, raw);
|
|
267
|
-
}
|
|
268
|
-
continue;
|
|
269
|
-
}
|
|
270
|
-
const handler = buildHandler(
|
|
271
|
-
field,
|
|
272
|
-
mirror,
|
|
273
|
-
formatter.format,
|
|
274
|
-
formatType,
|
|
275
|
-
formatOptions,
|
|
276
|
-
trackerMirrorName
|
|
277
|
-
);
|
|
278
|
-
addListener(state2, state2.element, "input", handler);
|
|
279
|
-
bucket.set(fieldName, {
|
|
280
|
-
fields: /* @__PURE__ */ new Set([field]),
|
|
281
|
-
mirrorName: trackerMirrorName,
|
|
282
|
-
handler,
|
|
264
|
+
let visible = candidates[0] ?? null;
|
|
265
|
+
if (!visible) {
|
|
266
|
+
visible = findVisibleField(state2.element, displayName);
|
|
267
|
+
}
|
|
268
|
+
if (!visible) continue;
|
|
269
|
+
const existing = bucket.get(fieldName);
|
|
270
|
+
let mirror = findHiddenMirror(state2.element, fieldName);
|
|
271
|
+
const mirrorIsOwned = !mirror;
|
|
272
|
+
if (!mirror) mirror = ensureHiddenMirror(state2.element, fieldName);
|
|
273
|
+
renameVisibleField(visible, displayName);
|
|
274
|
+
if (existing) {
|
|
275
|
+
existing.displayName = displayName;
|
|
276
|
+
existing.mirrorIsOwned = mirrorIsOwned;
|
|
277
|
+
state2.formattedFields.set(fieldName, {
|
|
278
|
+
config,
|
|
279
|
+
displayName,
|
|
283
280
|
mirrorIsOwned
|
|
284
281
|
});
|
|
282
|
+
} else {
|
|
283
|
+
bucket.set(fieldName, {
|
|
284
|
+
canonicalName: fieldName,
|
|
285
|
+
displayName,
|
|
286
|
+
visible,
|
|
287
|
+
mirror,
|
|
288
|
+
mirrorIsOwned,
|
|
289
|
+
handler: UNBOUND
|
|
290
|
+
});
|
|
285
291
|
state2.formattedFields.set(fieldName, {
|
|
286
292
|
config,
|
|
287
|
-
|
|
293
|
+
displayName,
|
|
288
294
|
mirrorIsOwned
|
|
289
295
|
});
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
const formatter = await loadFormatter();
|
|
299
|
+
if (!formatter) {
|
|
300
|
+
rollbackPhase1(bucket, state2.formattedFields);
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
for (const [, entry] of bucket) {
|
|
304
|
+
if (entry.handler !== UNBOUND) {
|
|
305
|
+
const current = entry.visible.value;
|
|
306
|
+
if (current !== "") {
|
|
307
|
+
const { formatted, raw } = formatter.format(current, formatType, formatOptions);
|
|
308
|
+
applyFormattedValue(entry.visible, entry.mirror, formatted, raw);
|
|
296
309
|
}
|
|
310
|
+
continue;
|
|
311
|
+
}
|
|
312
|
+
const handler = buildHandler(
|
|
313
|
+
entry.visible,
|
|
314
|
+
entry.mirror,
|
|
315
|
+
formatter.format,
|
|
316
|
+
formatType,
|
|
317
|
+
formatOptions
|
|
318
|
+
);
|
|
319
|
+
addListener(state2, state2.element, "input", handler, true);
|
|
320
|
+
entry.handler = handler;
|
|
321
|
+
const initial = entry.visible.value;
|
|
322
|
+
if (initial !== "") {
|
|
323
|
+
const { formatted, raw } = formatter.format(initial, formatType, formatOptions);
|
|
324
|
+
applyFormattedValue(entry.visible, entry.mirror, formatted, raw);
|
|
325
|
+
} else if (entry.mirrorIsOwned && entry.mirror.value !== "") {
|
|
326
|
+
entry.mirror.value = "";
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
};
|
|
330
|
+
var rollbackPhase1 = (bucket, registry2) => {
|
|
331
|
+
for (const [fieldName, entry] of bucket) {
|
|
332
|
+
if (entry.visible.isConnected) {
|
|
333
|
+
restoreVisibleName(entry.visible, fieldName);
|
|
297
334
|
}
|
|
335
|
+
if (entry.mirrorIsOwned && entry.mirror.isConnected) {
|
|
336
|
+
entry.mirror.remove();
|
|
337
|
+
}
|
|
338
|
+
bucket.delete(fieldName);
|
|
339
|
+
registry2.delete(fieldName);
|
|
298
340
|
}
|
|
299
341
|
};
|
|
300
342
|
var createFormat = (state2, helpers) => (config) => {
|
|
@@ -304,6 +346,14 @@ var createFormat = (state2, helpers) => (config) => {
|
|
|
304
346
|
return state2.api;
|
|
305
347
|
};
|
|
306
348
|
var createFormatAll = createFormat;
|
|
349
|
+
var resolveCanonicalForName = (state2, name) => {
|
|
350
|
+
const bucket = registry.get(state2);
|
|
351
|
+
if (!bucket) return null;
|
|
352
|
+
for (const [canonical, entry] of bucket) {
|
|
353
|
+
if (canonical === name || entry.displayName === name) return canonical;
|
|
354
|
+
}
|
|
355
|
+
return null;
|
|
356
|
+
};
|
|
307
357
|
var cleanupFormatRegistry = (state2) => {
|
|
308
358
|
if (!state2.element) {
|
|
309
359
|
registry.delete(state2);
|
|
@@ -315,10 +365,14 @@ var cleanupFormatRegistry = (state2) => {
|
|
|
315
365
|
return;
|
|
316
366
|
}
|
|
317
367
|
for (const [fieldName, entry] of bucket) {
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
368
|
+
if (entry.handler !== UNBOUND) {
|
|
369
|
+
state2.element.removeEventListener("input", entry.handler, true);
|
|
370
|
+
}
|
|
371
|
+
if (entry.visible.isConnected) {
|
|
372
|
+
restoreVisibleName(entry.visible, fieldName);
|
|
373
|
+
}
|
|
374
|
+
if (entry.mirrorIsOwned && entry.mirror.isConnected) {
|
|
375
|
+
entry.mirror.remove();
|
|
322
376
|
}
|
|
323
377
|
bucket.delete(fieldName);
|
|
324
378
|
state2.formattedFields.delete(fieldName);
|
|
@@ -331,7 +385,12 @@ var createDestroy = (state2, _helpers) => () => {
|
|
|
331
385
|
if (state2.isDestroyed) return;
|
|
332
386
|
state2.isDestroyed = true;
|
|
333
387
|
for (const listener of state2.listeners) {
|
|
334
|
-
listener.
|
|
388
|
+
const options = listener.capture ? { capture: true } : void 0;
|
|
389
|
+
if (options) {
|
|
390
|
+
listener.element.removeEventListener(listener.type, listener.handler, options);
|
|
391
|
+
} else {
|
|
392
|
+
listener.element.removeEventListener(listener.type, listener.handler);
|
|
393
|
+
}
|
|
335
394
|
}
|
|
336
395
|
state2.listeners.length = 0;
|
|
337
396
|
if (state2.autoSubmitTimer) {
|
|
@@ -779,9 +838,15 @@ var createFormController = (target, options = {}) => {
|
|
|
779
838
|
const snapshot = state2.api.getState();
|
|
780
839
|
state2.subscribers.forEach((listener) => listener(snapshot));
|
|
781
840
|
};
|
|
782
|
-
const addListener2 = (node, type, handler) => {
|
|
783
|
-
|
|
784
|
-
|
|
841
|
+
const addListener2 = (node, type, handler, capture) => {
|
|
842
|
+
if (capture) {
|
|
843
|
+
node.addEventListener(type, handler, { capture: true });
|
|
844
|
+
} else {
|
|
845
|
+
node.addEventListener(type, handler);
|
|
846
|
+
}
|
|
847
|
+
const entry = { element: node, type, handler };
|
|
848
|
+
if (capture !== void 0) entry.capture = capture;
|
|
849
|
+
state2.listeners.push(entry);
|
|
785
850
|
};
|
|
786
851
|
const clearFieldCache = () => state2.fieldCache.clear();
|
|
787
852
|
const getFieldsByName = (name) => {
|
|
@@ -797,6 +862,12 @@ var createFormController = (target, options = {}) => {
|
|
|
797
862
|
for (const field of getNamedFields(state2.element)) {
|
|
798
863
|
names.add(field.name);
|
|
799
864
|
}
|
|
865
|
+
for (const [canonical, entry] of state2.formattedFields) {
|
|
866
|
+
names.add(canonical);
|
|
867
|
+
if (entry.displayName && entry.displayName !== canonical) {
|
|
868
|
+
names.add(entry.displayName);
|
|
869
|
+
}
|
|
870
|
+
}
|
|
800
871
|
return Array.from(names);
|
|
801
872
|
};
|
|
802
873
|
const getValues = () => {
|
|
@@ -882,14 +953,16 @@ var createFormController = (target, options = {}) => {
|
|
|
882
953
|
const name = target2.name;
|
|
883
954
|
if (!name) return;
|
|
884
955
|
clearFieldCache();
|
|
956
|
+
const canonical = resolveCanonicalForName(state2, name) ?? name;
|
|
885
957
|
if (normalizedOptions.clearManualErrorsOnChange) {
|
|
886
|
-
delete state2.manualErrors[
|
|
958
|
+
delete state2.manualErrors[canonical];
|
|
887
959
|
}
|
|
888
|
-
syncVisualState([
|
|
889
|
-
if (state2.isValidated && state2.validators[
|
|
890
|
-
validateNames([
|
|
960
|
+
syncVisualState([canonical]);
|
|
961
|
+
if (state2.isValidated && state2.validators[canonical]) {
|
|
962
|
+
validateNames([canonical]);
|
|
891
963
|
}
|
|
892
|
-
emitFieldWatchers(
|
|
964
|
+
emitFieldWatchers(canonical);
|
|
965
|
+
if (canonical !== name) emitFieldWatchers(name);
|
|
893
966
|
notifySubscribers();
|
|
894
967
|
scheduleAutoSubmit();
|
|
895
968
|
};
|