@samline/forms 2.2.2 → 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 +179 -88
- package/dist/browser/global.global.js.map +1 -1
- package/dist/index.cjs +179 -88
- 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 +179 -88
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -50,50 +50,75 @@ 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
|
};
|
|
80
|
-
var
|
|
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
|
+
};
|
|
92
|
+
var applyFormattedValue = (visible, mirror, formatted, raw, inputType) => {
|
|
81
93
|
const rawInput = visible.value;
|
|
82
94
|
const caret = clamp(
|
|
83
95
|
visible.selectionStart ?? rawInput.length,
|
|
84
96
|
0,
|
|
85
97
|
rawInput.length
|
|
86
98
|
);
|
|
99
|
+
if (mirror && mirror.value !== raw) mirror.value = raw;
|
|
87
100
|
if (rawInput === formatted) {
|
|
88
|
-
|
|
89
|
-
|
|
101
|
+
const newCaret2 = pullCaretPastLeadingDelimiter(caret, formatted, inputType);
|
|
102
|
+
if (newCaret2 !== caret) restoreCursor(visible, newCaret2);
|
|
103
|
+
return newCaret2;
|
|
90
104
|
}
|
|
91
105
|
visible.value = formatted;
|
|
92
|
-
|
|
93
|
-
|
|
106
|
+
const newCaret = pullCaretPastLeadingDelimiter(
|
|
107
|
+
computeCursorPosition(caret, rawInput, formatted),
|
|
108
|
+
formatted,
|
|
109
|
+
inputType
|
|
110
|
+
);
|
|
94
111
|
restoreCursor(visible, newCaret);
|
|
95
112
|
return newCaret;
|
|
96
113
|
};
|
|
114
|
+
var pullCaretPastLeadingDelimiter = (caret, value, inputType) => {
|
|
115
|
+
if (!inputType || !inputType.startsWith("delete")) return caret;
|
|
116
|
+
let cursor = caret;
|
|
117
|
+
while (cursor > 0 && isFormatChar(value[cursor - 1])) {
|
|
118
|
+
cursor -= 1;
|
|
119
|
+
}
|
|
120
|
+
return cursor;
|
|
121
|
+
};
|
|
97
122
|
var clamp = (value, min, max) => Math.max(min, Math.min(max, value));
|
|
98
123
|
var computeCursorPosition = (caretPos, rawInput, newValue) => {
|
|
99
124
|
if (newValue.length === 0) return 0;
|
|
@@ -181,6 +206,7 @@ var loadFormatter = () => {
|
|
|
181
206
|
};
|
|
182
207
|
|
|
183
208
|
// src/api/format.ts
|
|
209
|
+
var UNBOUND = null;
|
|
184
210
|
var registry = /* @__PURE__ */ new WeakMap();
|
|
185
211
|
var getRegistry = (state2) => {
|
|
186
212
|
let bucket = registry.get(state2);
|
|
@@ -191,92 +217,126 @@ var getRegistry = (state2) => {
|
|
|
191
217
|
return bucket;
|
|
192
218
|
};
|
|
193
219
|
var resolveFieldNames = (config) => Array.isArray(config.field) ? config.field : [config.field];
|
|
194
|
-
var addListener = (state2, target, type, handler) => {
|
|
195
|
-
|
|
196
|
-
|
|
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);
|
|
229
|
+
};
|
|
230
|
+
var readInputType = (event) => {
|
|
231
|
+
const candidate = event;
|
|
232
|
+
return typeof candidate.inputType === "string" ? candidate.inputType : void 0;
|
|
197
233
|
};
|
|
198
|
-
var buildHandler = (
|
|
234
|
+
var buildHandler = (visible, mirror, formatFn, formatType, formatOptions) => {
|
|
199
235
|
const handler = (event) => {
|
|
200
|
-
if (!
|
|
201
|
-
|
|
202
|
-
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;
|
|
241
|
+
const inputType = readInputType(event);
|
|
203
242
|
const { formatted, raw } = formatFn(rawInput, formatType, formatOptions);
|
|
204
243
|
if (!formatted && !raw) {
|
|
205
|
-
|
|
206
|
-
if (mirror
|
|
244
|
+
visible.value = "";
|
|
245
|
+
if (mirror.value !== "") mirror.value = "";
|
|
207
246
|
return;
|
|
208
247
|
}
|
|
209
|
-
applyFormattedValue(
|
|
210
|
-
void mirrorFieldName;
|
|
248
|
+
applyFormattedValue(visible, mirror, formatted, raw, inputType);
|
|
211
249
|
};
|
|
212
250
|
return handler;
|
|
213
251
|
};
|
|
214
252
|
var applyFormat = async (state2, helpers, config) => {
|
|
215
253
|
if (!state2.element || !state2.api) return;
|
|
216
|
-
const formatter = await loadFormatter();
|
|
217
|
-
if (!formatter) return;
|
|
218
254
|
const formatType = config.type;
|
|
219
255
|
const formatOptions = config.options;
|
|
220
256
|
const fieldNames = resolveFieldNames(config);
|
|
221
257
|
const bucket = getRegistry(state2);
|
|
222
258
|
for (const fieldName of fieldNames) {
|
|
223
|
-
const
|
|
224
|
-
|
|
225
|
-
const
|
|
226
|
-
(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
|
|
227
263
|
);
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
});
|
|
245
|
-
const current = field.value;
|
|
246
|
-
if (current !== "") {
|
|
247
|
-
const { formatted, raw } = formatter.format(current, formatType, formatOptions);
|
|
248
|
-
applyFormattedValue(field, mirror, formatted, raw);
|
|
249
|
-
}
|
|
250
|
-
continue;
|
|
251
|
-
}
|
|
252
|
-
const handler = buildHandler(
|
|
253
|
-
field,
|
|
254
|
-
mirror,
|
|
255
|
-
formatter.format,
|
|
256
|
-
formatType,
|
|
257
|
-
formatOptions,
|
|
258
|
-
trackerMirrorName
|
|
259
|
-
);
|
|
260
|
-
addListener(state2, state2.element, "input", handler);
|
|
261
|
-
bucket.set(fieldName, {
|
|
262
|
-
fields: /* @__PURE__ */ new Set([field]),
|
|
263
|
-
mirrorName: trackerMirrorName,
|
|
264
|
-
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,
|
|
265
280
|
mirrorIsOwned
|
|
266
281
|
});
|
|
282
|
+
} else {
|
|
283
|
+
bucket.set(fieldName, {
|
|
284
|
+
canonicalName: fieldName,
|
|
285
|
+
displayName,
|
|
286
|
+
visible,
|
|
287
|
+
mirror,
|
|
288
|
+
mirrorIsOwned,
|
|
289
|
+
handler: UNBOUND
|
|
290
|
+
});
|
|
267
291
|
state2.formattedFields.set(fieldName, {
|
|
268
292
|
config,
|
|
269
|
-
|
|
293
|
+
displayName,
|
|
270
294
|
mirrorIsOwned
|
|
271
295
|
});
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
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);
|
|
278
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);
|
|
334
|
+
}
|
|
335
|
+
if (entry.mirrorIsOwned && entry.mirror.isConnected) {
|
|
336
|
+
entry.mirror.remove();
|
|
279
337
|
}
|
|
338
|
+
bucket.delete(fieldName);
|
|
339
|
+
registry2.delete(fieldName);
|
|
280
340
|
}
|
|
281
341
|
};
|
|
282
342
|
var createFormat = (state2, helpers) => (config) => {
|
|
@@ -286,6 +346,14 @@ var createFormat = (state2, helpers) => (config) => {
|
|
|
286
346
|
return state2.api;
|
|
287
347
|
};
|
|
288
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
|
+
};
|
|
289
357
|
var cleanupFormatRegistry = (state2) => {
|
|
290
358
|
if (!state2.element) {
|
|
291
359
|
registry.delete(state2);
|
|
@@ -297,10 +365,14 @@ var cleanupFormatRegistry = (state2) => {
|
|
|
297
365
|
return;
|
|
298
366
|
}
|
|
299
367
|
for (const [fieldName, entry] of bucket) {
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
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();
|
|
304
376
|
}
|
|
305
377
|
bucket.delete(fieldName);
|
|
306
378
|
state2.formattedFields.delete(fieldName);
|
|
@@ -313,7 +385,12 @@ var createDestroy = (state2, _helpers) => () => {
|
|
|
313
385
|
if (state2.isDestroyed) return;
|
|
314
386
|
state2.isDestroyed = true;
|
|
315
387
|
for (const listener of state2.listeners) {
|
|
316
|
-
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
|
+
}
|
|
317
394
|
}
|
|
318
395
|
state2.listeners.length = 0;
|
|
319
396
|
if (state2.autoSubmitTimer) {
|
|
@@ -761,9 +838,15 @@ var createFormController = (target, options = {}) => {
|
|
|
761
838
|
const snapshot = state2.api.getState();
|
|
762
839
|
state2.subscribers.forEach((listener) => listener(snapshot));
|
|
763
840
|
};
|
|
764
|
-
const addListener2 = (node, type, handler) => {
|
|
765
|
-
|
|
766
|
-
|
|
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);
|
|
767
850
|
};
|
|
768
851
|
const clearFieldCache = () => state2.fieldCache.clear();
|
|
769
852
|
const getFieldsByName = (name) => {
|
|
@@ -779,6 +862,12 @@ var createFormController = (target, options = {}) => {
|
|
|
779
862
|
for (const field of getNamedFields(state2.element)) {
|
|
780
863
|
names.add(field.name);
|
|
781
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
|
+
}
|
|
782
871
|
return Array.from(names);
|
|
783
872
|
};
|
|
784
873
|
const getValues = () => {
|
|
@@ -864,14 +953,16 @@ var createFormController = (target, options = {}) => {
|
|
|
864
953
|
const name = target2.name;
|
|
865
954
|
if (!name) return;
|
|
866
955
|
clearFieldCache();
|
|
956
|
+
const canonical = resolveCanonicalForName(state2, name) ?? name;
|
|
867
957
|
if (normalizedOptions.clearManualErrorsOnChange) {
|
|
868
|
-
delete state2.manualErrors[
|
|
958
|
+
delete state2.manualErrors[canonical];
|
|
869
959
|
}
|
|
870
|
-
syncVisualState([
|
|
871
|
-
if (state2.isValidated && state2.validators[
|
|
872
|
-
validateNames([
|
|
960
|
+
syncVisualState([canonical]);
|
|
961
|
+
if (state2.isValidated && state2.validators[canonical]) {
|
|
962
|
+
validateNames([canonical]);
|
|
873
963
|
}
|
|
874
|
-
emitFieldWatchers(
|
|
964
|
+
emitFieldWatchers(canonical);
|
|
965
|
+
if (canonical !== name) emitFieldWatchers(name);
|
|
875
966
|
notifySubscribers();
|
|
876
967
|
scheduleAutoSubmit();
|
|
877
968
|
};
|