@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.cjs
CHANGED
|
@@ -91,33 +91,45 @@ var cssEscape = (value) => {
|
|
|
91
91
|
}
|
|
92
92
|
return value.replace(/([!"#$%&'()*+,./:;<=>?@\[\\\]^`{|}~])/g, "\\$1");
|
|
93
93
|
};
|
|
94
|
-
var
|
|
94
|
+
var findVisibleField = (form2, displayName) => {
|
|
95
|
+
const node = form2.querySelector(
|
|
96
|
+
`[name="${cssEscape(displayName)}"]:not([type="hidden"])`
|
|
97
|
+
);
|
|
98
|
+
if (node instanceof HTMLInputElement || node instanceof HTMLTextAreaElement) {
|
|
99
|
+
return node;
|
|
100
|
+
}
|
|
101
|
+
return null;
|
|
102
|
+
};
|
|
103
|
+
var findHiddenMirror = (form2, fieldName) => {
|
|
95
104
|
const byAttr = form2.querySelector(
|
|
96
105
|
`[${FORMATTER_RAW_ATTRIBUTE}="${cssEscape(fieldName)}"]`
|
|
97
106
|
);
|
|
98
107
|
if (byAttr) return byAttr;
|
|
99
108
|
const byName = form2.querySelector(
|
|
100
|
-
`[name="${cssEscape(
|
|
109
|
+
`input[type="hidden"][name="${cssEscape(fieldName)}"]`
|
|
101
110
|
);
|
|
102
111
|
if (byName) return byName;
|
|
103
|
-
const byNameOld = form2.querySelector(
|
|
104
|
-
`[name="${cssEscape(`${fieldName}Raw`)}"]`
|
|
105
|
-
);
|
|
106
|
-
if (byNameOld) return byNameOld;
|
|
107
112
|
return null;
|
|
108
113
|
};
|
|
109
|
-
var
|
|
110
|
-
const existing =
|
|
114
|
+
var ensureHiddenMirror = (form2, fieldName) => {
|
|
115
|
+
const existing = findHiddenMirror(form2, fieldName);
|
|
111
116
|
if (existing) return existing;
|
|
112
117
|
const input = document.createElement("input");
|
|
113
118
|
input.type = "hidden";
|
|
114
|
-
input.name =
|
|
119
|
+
input.name = fieldName;
|
|
115
120
|
input.setAttribute("aria-hidden", "true");
|
|
116
121
|
input.tabIndex = -1;
|
|
117
122
|
input.setAttribute(FORMATTER_RAW_ATTRIBUTE, fieldName);
|
|
118
123
|
form2.appendChild(input);
|
|
119
124
|
return input;
|
|
120
125
|
};
|
|
126
|
+
var renameVisibleField = (visible, displayName) => {
|
|
127
|
+
if (visible.name === displayName) return;
|
|
128
|
+
visible.name = displayName;
|
|
129
|
+
};
|
|
130
|
+
var restoreVisibleName = (visible, originalName) => {
|
|
131
|
+
visible.name = originalName;
|
|
132
|
+
};
|
|
121
133
|
var applyFormattedValue = (visible, mirror, formatted, raw, inputType) => {
|
|
122
134
|
const rawInput = visible.value;
|
|
123
135
|
const caret = clamp(
|
|
@@ -235,6 +247,7 @@ var loadFormatter = () => {
|
|
|
235
247
|
};
|
|
236
248
|
|
|
237
249
|
// src/api/format.ts
|
|
250
|
+
var UNBOUND = null;
|
|
238
251
|
var registry = /* @__PURE__ */ new WeakMap();
|
|
239
252
|
var getRegistry = (state2) => {
|
|
240
253
|
let bucket = registry.get(state2);
|
|
@@ -245,97 +258,126 @@ var getRegistry = (state2) => {
|
|
|
245
258
|
return bucket;
|
|
246
259
|
};
|
|
247
260
|
var resolveFieldNames = (config) => Array.isArray(config.field) ? config.field : [config.field];
|
|
248
|
-
var addListener = (state2, target, type, handler) => {
|
|
249
|
-
|
|
250
|
-
|
|
261
|
+
var addListener = (state2, target, type, handler, useCapture) => {
|
|
262
|
+
if (useCapture) {
|
|
263
|
+
target.addEventListener(type, handler, { capture: true });
|
|
264
|
+
} else {
|
|
265
|
+
target.addEventListener(type, handler);
|
|
266
|
+
}
|
|
267
|
+
const entry = { element: target, type, handler };
|
|
268
|
+
if (useCapture !== void 0) entry.capture = useCapture;
|
|
269
|
+
state2.listeners.push(entry);
|
|
251
270
|
};
|
|
252
|
-
var
|
|
271
|
+
var readInputType = (event) => {
|
|
272
|
+
const candidate = event;
|
|
273
|
+
return typeof candidate.inputType === "string" ? candidate.inputType : void 0;
|
|
274
|
+
};
|
|
275
|
+
var buildHandler = (visible, mirror, formatFn, formatType, formatOptions) => {
|
|
253
276
|
const handler = (event) => {
|
|
254
|
-
if (!
|
|
255
|
-
|
|
256
|
-
const
|
|
277
|
+
if (!visible.isConnected || !mirror.isConnected) return;
|
|
278
|
+
const isVisible = event.target === visible;
|
|
279
|
+
const isMirror = event.target === mirror;
|
|
280
|
+
if (!isVisible && !isMirror) return;
|
|
281
|
+
const rawInput = isMirror ? mirror.value : visible.value;
|
|
257
282
|
const inputType = readInputType(event);
|
|
258
283
|
const { formatted, raw } = formatFn(rawInput, formatType, formatOptions);
|
|
259
284
|
if (!formatted && !raw) {
|
|
260
|
-
|
|
261
|
-
if (mirror
|
|
285
|
+
visible.value = "";
|
|
286
|
+
if (mirror.value !== "") mirror.value = "";
|
|
262
287
|
return;
|
|
263
288
|
}
|
|
264
|
-
applyFormattedValue(
|
|
265
|
-
void mirrorFieldName;
|
|
289
|
+
applyFormattedValue(visible, mirror, formatted, raw, inputType);
|
|
266
290
|
};
|
|
267
291
|
return handler;
|
|
268
292
|
};
|
|
269
|
-
var readInputType = (event) => {
|
|
270
|
-
const candidate = event;
|
|
271
|
-
return typeof candidate.inputType === "string" ? candidate.inputType : void 0;
|
|
272
|
-
};
|
|
273
293
|
var applyFormat = async (state2, helpers, config) => {
|
|
274
294
|
if (!state2.element || !state2.api) return;
|
|
275
|
-
const formatter = await loadFormatter();
|
|
276
|
-
if (!formatter) return;
|
|
277
295
|
const formatType = config.type;
|
|
278
296
|
const formatOptions = config.options;
|
|
279
297
|
const fieldNames = resolveFieldNames(config);
|
|
280
298
|
const bucket = getRegistry(state2);
|
|
281
299
|
for (const fieldName of fieldNames) {
|
|
282
|
-
const
|
|
283
|
-
|
|
284
|
-
const
|
|
285
|
-
(f) => f instanceof HTMLInputElement && f.type !== "
|
|
300
|
+
const displayName = config.displayField ?? `${fieldName}_displayed`;
|
|
301
|
+
const canonicalFields = helpers.getFieldsByName(fieldName);
|
|
302
|
+
const candidates = canonicalFields.filter(
|
|
303
|
+
(f) => f instanceof HTMLInputElement && f.type !== "hidden" || f instanceof HTMLTextAreaElement
|
|
286
304
|
);
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
});
|
|
304
|
-
const current = field.value;
|
|
305
|
-
if (current !== "") {
|
|
306
|
-
const { formatted, raw } = formatter.format(current, formatType, formatOptions);
|
|
307
|
-
applyFormattedValue(field, mirror, formatted, raw);
|
|
308
|
-
}
|
|
309
|
-
continue;
|
|
310
|
-
}
|
|
311
|
-
const handler = buildHandler(
|
|
312
|
-
field,
|
|
313
|
-
mirror,
|
|
314
|
-
formatter.format,
|
|
315
|
-
formatType,
|
|
316
|
-
formatOptions,
|
|
317
|
-
trackerMirrorName
|
|
318
|
-
);
|
|
319
|
-
addListener(state2, state2.element, "input", handler);
|
|
320
|
-
bucket.set(fieldName, {
|
|
321
|
-
fields: /* @__PURE__ */ new Set([field]),
|
|
322
|
-
mirrorName: trackerMirrorName,
|
|
323
|
-
handler,
|
|
305
|
+
let visible = candidates[0] ?? null;
|
|
306
|
+
if (!visible) {
|
|
307
|
+
visible = findVisibleField(state2.element, displayName);
|
|
308
|
+
}
|
|
309
|
+
if (!visible) continue;
|
|
310
|
+
const existing = bucket.get(fieldName);
|
|
311
|
+
let mirror = findHiddenMirror(state2.element, fieldName);
|
|
312
|
+
const mirrorIsOwned = !mirror;
|
|
313
|
+
if (!mirror) mirror = ensureHiddenMirror(state2.element, fieldName);
|
|
314
|
+
renameVisibleField(visible, displayName);
|
|
315
|
+
if (existing) {
|
|
316
|
+
existing.displayName = displayName;
|
|
317
|
+
existing.mirrorIsOwned = mirrorIsOwned;
|
|
318
|
+
state2.formattedFields.set(fieldName, {
|
|
319
|
+
config,
|
|
320
|
+
displayName,
|
|
324
321
|
mirrorIsOwned
|
|
325
322
|
});
|
|
323
|
+
} else {
|
|
324
|
+
bucket.set(fieldName, {
|
|
325
|
+
canonicalName: fieldName,
|
|
326
|
+
displayName,
|
|
327
|
+
visible,
|
|
328
|
+
mirror,
|
|
329
|
+
mirrorIsOwned,
|
|
330
|
+
handler: UNBOUND
|
|
331
|
+
});
|
|
326
332
|
state2.formattedFields.set(fieldName, {
|
|
327
333
|
config,
|
|
328
|
-
|
|
334
|
+
displayName,
|
|
329
335
|
mirrorIsOwned
|
|
330
336
|
});
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
const formatter = await loadFormatter();
|
|
340
|
+
if (!formatter) {
|
|
341
|
+
rollbackPhase1(bucket, state2.formattedFields);
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
for (const [, entry] of bucket) {
|
|
345
|
+
if (entry.handler !== UNBOUND) {
|
|
346
|
+
const current = entry.visible.value;
|
|
347
|
+
if (current !== "") {
|
|
348
|
+
const { formatted, raw } = formatter.format(current, formatType, formatOptions);
|
|
349
|
+
applyFormattedValue(entry.visible, entry.mirror, formatted, raw);
|
|
337
350
|
}
|
|
351
|
+
continue;
|
|
352
|
+
}
|
|
353
|
+
const handler = buildHandler(
|
|
354
|
+
entry.visible,
|
|
355
|
+
entry.mirror,
|
|
356
|
+
formatter.format,
|
|
357
|
+
formatType,
|
|
358
|
+
formatOptions
|
|
359
|
+
);
|
|
360
|
+
addListener(state2, state2.element, "input", handler, true);
|
|
361
|
+
entry.handler = handler;
|
|
362
|
+
const initial = entry.visible.value;
|
|
363
|
+
if (initial !== "") {
|
|
364
|
+
const { formatted, raw } = formatter.format(initial, formatType, formatOptions);
|
|
365
|
+
applyFormattedValue(entry.visible, entry.mirror, formatted, raw);
|
|
366
|
+
} else if (entry.mirrorIsOwned && entry.mirror.value !== "") {
|
|
367
|
+
entry.mirror.value = "";
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
};
|
|
371
|
+
var rollbackPhase1 = (bucket, registry2) => {
|
|
372
|
+
for (const [fieldName, entry] of bucket) {
|
|
373
|
+
if (entry.visible.isConnected) {
|
|
374
|
+
restoreVisibleName(entry.visible, fieldName);
|
|
338
375
|
}
|
|
376
|
+
if (entry.mirrorIsOwned && entry.mirror.isConnected) {
|
|
377
|
+
entry.mirror.remove();
|
|
378
|
+
}
|
|
379
|
+
bucket.delete(fieldName);
|
|
380
|
+
registry2.delete(fieldName);
|
|
339
381
|
}
|
|
340
382
|
};
|
|
341
383
|
var createFormat = (state2, helpers) => (config) => {
|
|
@@ -345,6 +387,14 @@ var createFormat = (state2, helpers) => (config) => {
|
|
|
345
387
|
return state2.api;
|
|
346
388
|
};
|
|
347
389
|
var createFormatAll = createFormat;
|
|
390
|
+
var resolveCanonicalForName = (state2, name) => {
|
|
391
|
+
const bucket = registry.get(state2);
|
|
392
|
+
if (!bucket) return null;
|
|
393
|
+
for (const [canonical, entry] of bucket) {
|
|
394
|
+
if (canonical === name || entry.displayName === name) return canonical;
|
|
395
|
+
}
|
|
396
|
+
return null;
|
|
397
|
+
};
|
|
348
398
|
var cleanupFormatRegistry = (state2) => {
|
|
349
399
|
if (!state2.element) {
|
|
350
400
|
registry.delete(state2);
|
|
@@ -356,10 +406,14 @@ var cleanupFormatRegistry = (state2) => {
|
|
|
356
406
|
return;
|
|
357
407
|
}
|
|
358
408
|
for (const [fieldName, entry] of bucket) {
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
409
|
+
if (entry.handler !== UNBOUND) {
|
|
410
|
+
state2.element.removeEventListener("input", entry.handler, true);
|
|
411
|
+
}
|
|
412
|
+
if (entry.visible.isConnected) {
|
|
413
|
+
restoreVisibleName(entry.visible, fieldName);
|
|
414
|
+
}
|
|
415
|
+
if (entry.mirrorIsOwned && entry.mirror.isConnected) {
|
|
416
|
+
entry.mirror.remove();
|
|
363
417
|
}
|
|
364
418
|
bucket.delete(fieldName);
|
|
365
419
|
state2.formattedFields.delete(fieldName);
|
|
@@ -372,7 +426,12 @@ var createDestroy = (state2, _helpers) => () => {
|
|
|
372
426
|
if (state2.isDestroyed) return;
|
|
373
427
|
state2.isDestroyed = true;
|
|
374
428
|
for (const listener of state2.listeners) {
|
|
375
|
-
listener.
|
|
429
|
+
const options = listener.capture ? { capture: true } : void 0;
|
|
430
|
+
if (options) {
|
|
431
|
+
listener.element.removeEventListener(listener.type, listener.handler, options);
|
|
432
|
+
} else {
|
|
433
|
+
listener.element.removeEventListener(listener.type, listener.handler);
|
|
434
|
+
}
|
|
376
435
|
}
|
|
377
436
|
state2.listeners.length = 0;
|
|
378
437
|
if (state2.autoSubmitTimer) {
|
|
@@ -820,9 +879,15 @@ var createFormController = (target, options = {}) => {
|
|
|
820
879
|
const snapshot = state2.api.getState();
|
|
821
880
|
state2.subscribers.forEach((listener) => listener(snapshot));
|
|
822
881
|
};
|
|
823
|
-
const addListener2 = (node, type, handler) => {
|
|
824
|
-
|
|
825
|
-
|
|
882
|
+
const addListener2 = (node, type, handler, capture) => {
|
|
883
|
+
if (capture) {
|
|
884
|
+
node.addEventListener(type, handler, { capture: true });
|
|
885
|
+
} else {
|
|
886
|
+
node.addEventListener(type, handler);
|
|
887
|
+
}
|
|
888
|
+
const entry = { element: node, type, handler };
|
|
889
|
+
if (capture !== void 0) entry.capture = capture;
|
|
890
|
+
state2.listeners.push(entry);
|
|
826
891
|
};
|
|
827
892
|
const clearFieldCache = () => state2.fieldCache.clear();
|
|
828
893
|
const getFieldsByName = (name) => {
|
|
@@ -838,6 +903,12 @@ var createFormController = (target, options = {}) => {
|
|
|
838
903
|
for (const field of getNamedFields(state2.element)) {
|
|
839
904
|
names.add(field.name);
|
|
840
905
|
}
|
|
906
|
+
for (const [canonical, entry] of state2.formattedFields) {
|
|
907
|
+
names.add(canonical);
|
|
908
|
+
if (entry.displayName && entry.displayName !== canonical) {
|
|
909
|
+
names.add(entry.displayName);
|
|
910
|
+
}
|
|
911
|
+
}
|
|
841
912
|
return Array.from(names);
|
|
842
913
|
};
|
|
843
914
|
const getValues = () => {
|
|
@@ -923,14 +994,16 @@ var createFormController = (target, options = {}) => {
|
|
|
923
994
|
const name = target2.name;
|
|
924
995
|
if (!name) return;
|
|
925
996
|
clearFieldCache();
|
|
997
|
+
const canonical = resolveCanonicalForName(state2, name) ?? name;
|
|
926
998
|
if (normalizedOptions.clearManualErrorsOnChange) {
|
|
927
|
-
delete state2.manualErrors[
|
|
999
|
+
delete state2.manualErrors[canonical];
|
|
928
1000
|
}
|
|
929
|
-
syncVisualState([
|
|
930
|
-
if (state2.isValidated && state2.validators[
|
|
931
|
-
validateNames([
|
|
1001
|
+
syncVisualState([canonical]);
|
|
1002
|
+
if (state2.isValidated && state2.validators[canonical]) {
|
|
1003
|
+
validateNames([canonical]);
|
|
932
1004
|
}
|
|
933
|
-
emitFieldWatchers(
|
|
1005
|
+
emitFieldWatchers(canonical);
|
|
1006
|
+
if (canonical !== name) emitFieldWatchers(name);
|
|
934
1007
|
notifySubscribers();
|
|
935
1008
|
scheduleAutoSubmit();
|
|
936
1009
|
};
|