@formwright/core 0.1.0 → 0.2.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 +96 -0
- package/dist/chunk-O4DUMDBU.js +3 -0
- package/dist/chunk-O4DUMDBU.js.map +1 -0
- package/dist/index.cjs +214 -215
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +51 -9
- package/dist/index.d.ts +51 -9
- package/dist/index.js +170 -34
- package/dist/index.js.map +1 -1
- package/dist/reactive.cjs +27 -164
- package/dist/reactive.cjs.map +1 -1
- package/dist/reactive.d.cts +1 -45
- package/dist/reactive.d.ts +1 -45
- package/dist/reactive.js +1 -1
- package/package.json +3 -2
- package/dist/chunk-EZUHEI5F.js +0 -162
- package/dist/chunk-EZUHEI5F.js.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -1,165 +1,9 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
var reactive = require('@wright/reactive');
|
|
3
4
|
var schema = require('@formwright/schema');
|
|
4
5
|
|
|
5
6
|
// src/reactive.ts
|
|
6
|
-
var activeObserver = null;
|
|
7
|
-
var batchDepth = 0;
|
|
8
|
-
var pendingEffects = /* @__PURE__ */ new Set();
|
|
9
|
-
var flushing = false;
|
|
10
|
-
function link(source) {
|
|
11
|
-
const obs = activeObserver;
|
|
12
|
-
if (obs === null) return;
|
|
13
|
-
if (!source.observers.has(obs)) {
|
|
14
|
-
source.observers.add(obs);
|
|
15
|
-
obs.sources.add(source);
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
function clearSources(obs) {
|
|
19
|
-
for (const src of obs.sources) src.observers.delete(obs);
|
|
20
|
-
obs.sources.clear();
|
|
21
|
-
}
|
|
22
|
-
function flush() {
|
|
23
|
-
if (flushing) return;
|
|
24
|
-
flushing = true;
|
|
25
|
-
try {
|
|
26
|
-
while (pendingEffects.size > 0) {
|
|
27
|
-
const next = pendingEffects.values().next().value;
|
|
28
|
-
pendingEffects.delete(next);
|
|
29
|
-
next.run();
|
|
30
|
-
}
|
|
31
|
-
} finally {
|
|
32
|
-
flushing = false;
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
var SignalNode = class {
|
|
36
|
-
constructor(value) {
|
|
37
|
-
this.value = value;
|
|
38
|
-
}
|
|
39
|
-
value;
|
|
40
|
-
observers = /* @__PURE__ */ new Set();
|
|
41
|
-
get() {
|
|
42
|
-
link(this);
|
|
43
|
-
return this.value;
|
|
44
|
-
}
|
|
45
|
-
peek() {
|
|
46
|
-
return this.value;
|
|
47
|
-
}
|
|
48
|
-
set(next) {
|
|
49
|
-
if (Object.is(next, this.value)) return;
|
|
50
|
-
this.value = next;
|
|
51
|
-
for (const obs of [...this.observers]) obs.notify();
|
|
52
|
-
if (batchDepth === 0) flush();
|
|
53
|
-
}
|
|
54
|
-
update(fn) {
|
|
55
|
-
this.set(fn(this.value));
|
|
56
|
-
}
|
|
57
|
-
};
|
|
58
|
-
var ComputedNode = class {
|
|
59
|
-
constructor(fn) {
|
|
60
|
-
this.fn = fn;
|
|
61
|
-
}
|
|
62
|
-
fn;
|
|
63
|
-
observers = /* @__PURE__ */ new Set();
|
|
64
|
-
sources = /* @__PURE__ */ new Set();
|
|
65
|
-
value;
|
|
66
|
-
dirty = true;
|
|
67
|
-
notify() {
|
|
68
|
-
if (this.dirty) return;
|
|
69
|
-
this.dirty = true;
|
|
70
|
-
for (const obs of [...this.observers]) obs.notify();
|
|
71
|
-
}
|
|
72
|
-
get() {
|
|
73
|
-
link(this);
|
|
74
|
-
if (this.dirty) this.recompute();
|
|
75
|
-
return this.value;
|
|
76
|
-
}
|
|
77
|
-
peek() {
|
|
78
|
-
if (this.dirty) this.recompute();
|
|
79
|
-
return this.value;
|
|
80
|
-
}
|
|
81
|
-
recompute() {
|
|
82
|
-
clearSources(this);
|
|
83
|
-
const prev = activeObserver;
|
|
84
|
-
activeObserver = this;
|
|
85
|
-
try {
|
|
86
|
-
this.value = this.fn();
|
|
87
|
-
this.dirty = false;
|
|
88
|
-
} finally {
|
|
89
|
-
activeObserver = prev;
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
};
|
|
93
|
-
var EffectNode = class {
|
|
94
|
-
constructor(fn) {
|
|
95
|
-
this.fn = fn;
|
|
96
|
-
this.run();
|
|
97
|
-
}
|
|
98
|
-
fn;
|
|
99
|
-
sources = /* @__PURE__ */ new Set();
|
|
100
|
-
cleanup = void 0;
|
|
101
|
-
disposed = false;
|
|
102
|
-
notify() {
|
|
103
|
-
if (this.disposed) return;
|
|
104
|
-
pendingEffects.add(this);
|
|
105
|
-
}
|
|
106
|
-
run() {
|
|
107
|
-
if (this.disposed) return;
|
|
108
|
-
this.runCleanup();
|
|
109
|
-
clearSources(this);
|
|
110
|
-
const prev = activeObserver;
|
|
111
|
-
activeObserver = this;
|
|
112
|
-
try {
|
|
113
|
-
this.cleanup = this.fn();
|
|
114
|
-
} finally {
|
|
115
|
-
activeObserver = prev;
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
runCleanup() {
|
|
119
|
-
if (typeof this.cleanup === "function") {
|
|
120
|
-
this.cleanup();
|
|
121
|
-
this.cleanup = void 0;
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
dispose() {
|
|
125
|
-
if (this.disposed) return;
|
|
126
|
-
this.disposed = true;
|
|
127
|
-
this.runCleanup();
|
|
128
|
-
clearSources(this);
|
|
129
|
-
pendingEffects.delete(this);
|
|
130
|
-
}
|
|
131
|
-
};
|
|
132
|
-
function signal(initial) {
|
|
133
|
-
return new SignalNode(initial);
|
|
134
|
-
}
|
|
135
|
-
function computed(fn) {
|
|
136
|
-
return new ComputedNode(fn);
|
|
137
|
-
}
|
|
138
|
-
function effect(fn) {
|
|
139
|
-
const node = new EffectNode(fn);
|
|
140
|
-
return () => node.dispose();
|
|
141
|
-
}
|
|
142
|
-
function untrack(fn) {
|
|
143
|
-
const prev = activeObserver;
|
|
144
|
-
activeObserver = null;
|
|
145
|
-
try {
|
|
146
|
-
return fn();
|
|
147
|
-
} finally {
|
|
148
|
-
activeObserver = prev;
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
function batch(fn) {
|
|
152
|
-
batchDepth++;
|
|
153
|
-
try {
|
|
154
|
-
return fn();
|
|
155
|
-
} finally {
|
|
156
|
-
batchDepth--;
|
|
157
|
-
if (batchDepth === 0) flush();
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
function isTracking() {
|
|
161
|
-
return activeObserver !== null;
|
|
162
|
-
}
|
|
163
7
|
|
|
164
8
|
// src/conditions.ts
|
|
165
9
|
function isOp(cond, key) {
|
|
@@ -249,35 +93,42 @@ function isEmpty(value) {
|
|
|
249
93
|
return value === void 0 || value === null || value === "";
|
|
250
94
|
}
|
|
251
95
|
function compileValidator(schema) {
|
|
96
|
+
const overrides = schema.messages ?? {};
|
|
97
|
+
const msg = (rule, fallback) => overrides[rule] ?? (typeof schema.message === "string" ? schema.message : fallback);
|
|
252
98
|
return (value) => {
|
|
253
|
-
const msg = (fallback) => typeof schema.message === "string" ? schema.message : fallback;
|
|
254
99
|
if (isEmpty(value)) {
|
|
255
|
-
return schema.required ? msg("This field is required") : null;
|
|
100
|
+
return schema.required ? msg("required", "This field is required") : null;
|
|
256
101
|
}
|
|
257
102
|
if (schema.kind === "string" || schema.format) {
|
|
258
103
|
const str = String(value);
|
|
259
104
|
if (schema.minLength !== void 0 && str.length < schema.minLength) {
|
|
260
|
-
return msg(`Must be at least ${schema.minLength} characters`);
|
|
105
|
+
return msg("minLength", `Must be at least ${schema.minLength} characters`);
|
|
261
106
|
}
|
|
262
107
|
if (schema.maxLength !== void 0 && str.length > schema.maxLength) {
|
|
263
|
-
return msg(`Must be at most ${schema.maxLength} characters`);
|
|
108
|
+
return msg("maxLength", `Must be at most ${schema.maxLength} characters`);
|
|
264
109
|
}
|
|
265
110
|
if (schema.pattern !== void 0 && !new RegExp(schema.pattern).test(str)) {
|
|
266
|
-
return msg("Invalid format");
|
|
111
|
+
return msg("pattern", "Invalid format");
|
|
267
112
|
}
|
|
268
|
-
if (schema.format === "email" && !EMAIL.test(str))
|
|
269
|
-
|
|
270
|
-
if (schema.format === "
|
|
113
|
+
if (schema.format === "email" && !EMAIL.test(str))
|
|
114
|
+
return msg("format", "Enter a valid email");
|
|
115
|
+
if (schema.format === "url" && !URL.test(str)) return msg("format", "Enter a valid URL");
|
|
116
|
+
if (schema.format === "uuid" && !UUID.test(str)) return msg("format", "Enter a valid UUID");
|
|
271
117
|
}
|
|
272
118
|
if (schema.kind === "number") {
|
|
273
119
|
const num = Number(value);
|
|
274
|
-
if (Number.isNaN(num)) return msg("Must be a number");
|
|
275
|
-
if (schema.min !== void 0 && num < schema.min)
|
|
276
|
-
|
|
120
|
+
if (Number.isNaN(num)) return msg("type", "Must be a number");
|
|
121
|
+
if (schema.min !== void 0 && num < schema.min)
|
|
122
|
+
return msg("min", `Must be \u2265 ${schema.min}`);
|
|
123
|
+
if (schema.max !== void 0 && num > schema.max)
|
|
124
|
+
return msg("max", `Must be \u2264 ${schema.max}`);
|
|
277
125
|
}
|
|
278
126
|
return null;
|
|
279
127
|
};
|
|
280
128
|
}
|
|
129
|
+
function requiredMessage(schema) {
|
|
130
|
+
return schema?.messages?.required ?? (typeof schema?.message === "string" ? schema.message : "This field is required");
|
|
131
|
+
}
|
|
281
132
|
|
|
282
133
|
// src/providers.ts
|
|
283
134
|
function isProviderRef(value) {
|
|
@@ -317,10 +168,14 @@ function defaultValueFor(type) {
|
|
|
317
168
|
return "";
|
|
318
169
|
}
|
|
319
170
|
}
|
|
171
|
+
var uidSeq = 0;
|
|
320
172
|
var FieldState = class {
|
|
321
173
|
/** Discriminant for the {@link FieldNode} union (leaf vs group/collection). */
|
|
322
174
|
kind = "field";
|
|
323
175
|
id;
|
|
176
|
+
/** A globally-unique DOM id for the control (collection rows reuse `id`, so this must be unique). */
|
|
177
|
+
domId = `fw-${(uidSeq++).toString(36)}`;
|
|
178
|
+
/** The field's schema — mutable at runtime via {@link patchSchema}. */
|
|
324
179
|
schema;
|
|
325
180
|
value;
|
|
326
181
|
error;
|
|
@@ -328,23 +183,40 @@ var FieldState = class {
|
|
|
328
183
|
visible;
|
|
329
184
|
enabled;
|
|
330
185
|
required;
|
|
186
|
+
/** Bumps whenever the schema is patched — renderers re-render the field on change. */
|
|
187
|
+
revision;
|
|
331
188
|
validator;
|
|
189
|
+
rev = reactive.signal(0);
|
|
332
190
|
constructor(schema, initial, getValue) {
|
|
333
191
|
this.id = schema.id;
|
|
334
192
|
this.schema = schema;
|
|
335
|
-
this.value = signal(initial);
|
|
336
|
-
this.error = signal(null);
|
|
337
|
-
this.touched = signal(false);
|
|
193
|
+
this.value = reactive.signal(initial);
|
|
194
|
+
this.error = reactive.signal(null);
|
|
195
|
+
this.touched = reactive.signal(false);
|
|
338
196
|
this.validator = schema.validation ? compileValidator(schema.validation) : null;
|
|
339
|
-
this.
|
|
340
|
-
this.
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
197
|
+
this.revision = this.rev;
|
|
198
|
+
this.visible = reactive.computed(() => {
|
|
199
|
+
this.rev.get();
|
|
200
|
+
return evaluateCondition(this.schema.visibleWhen, getValue, true);
|
|
201
|
+
});
|
|
202
|
+
this.enabled = reactive.computed(() => {
|
|
203
|
+
this.rev.get();
|
|
204
|
+
return evaluateCondition(this.schema.enabledWhen, getValue, true);
|
|
205
|
+
});
|
|
206
|
+
this.required = reactive.computed(() => {
|
|
207
|
+
this.rev.get();
|
|
208
|
+
if (this.schema.requiredWhen !== void 0) {
|
|
209
|
+
return evaluateCondition(this.schema.requiredWhen, getValue, false);
|
|
344
210
|
}
|
|
345
|
-
return schema.validation?.required ?? false;
|
|
211
|
+
return this.schema.validation?.required ?? false;
|
|
346
212
|
});
|
|
347
213
|
}
|
|
214
|
+
/** Merge a partial schema in at runtime (change type, label, options, validation, …). */
|
|
215
|
+
patchSchema(partial) {
|
|
216
|
+
this.schema = { ...this.schema, ...partial };
|
|
217
|
+
this.validator = this.schema.validation ? compileValidator(this.schema.validation) : null;
|
|
218
|
+
this.rev.update((n) => n + 1);
|
|
219
|
+
}
|
|
348
220
|
/** Run validation, store and return the error (or null). Hidden fields never error. */
|
|
349
221
|
validate() {
|
|
350
222
|
if (!this.visible.peek()) {
|
|
@@ -353,7 +225,7 @@ var FieldState = class {
|
|
|
353
225
|
}
|
|
354
226
|
let result = null;
|
|
355
227
|
if (this.required.peek() && isEmpty2(this.value.peek())) {
|
|
356
|
-
result =
|
|
228
|
+
result = requiredMessage(this.schema.validation);
|
|
357
229
|
} else if (this.validator) {
|
|
358
230
|
result = this.validator(this.value.peek());
|
|
359
231
|
}
|
|
@@ -371,12 +243,20 @@ function isEmpty2(value) {
|
|
|
371
243
|
}
|
|
372
244
|
|
|
373
245
|
// src/nodes.ts
|
|
246
|
+
var PRESENTATIONAL = /* @__PURE__ */ new Set(["heading", "separator", "paragraph"]);
|
|
247
|
+
function isPresentational(type) {
|
|
248
|
+
return PRESENTATIONAL.has(type);
|
|
249
|
+
}
|
|
374
250
|
function nodeValue(node) {
|
|
375
251
|
return node.value.get();
|
|
376
252
|
}
|
|
377
253
|
function collectValues(nodes) {
|
|
378
254
|
const out = {};
|
|
379
|
-
for (const node of nodes)
|
|
255
|
+
for (const node of nodes) {
|
|
256
|
+
if (node.schema.omit || isPresentational(node.schema.type)) continue;
|
|
257
|
+
if (!node.visible.get()) continue;
|
|
258
|
+
out[node.id] = nodeValue(node);
|
|
259
|
+
}
|
|
380
260
|
return out;
|
|
381
261
|
}
|
|
382
262
|
function buildNodes(schemas, scope, initial) {
|
|
@@ -436,9 +316,9 @@ var GroupNode = class {
|
|
|
436
316
|
const built = buildNodes(schema.fields ?? [], this.scope, initial);
|
|
437
317
|
this.children = built.nodes;
|
|
438
318
|
this.byName = built.byName;
|
|
439
|
-
this.value = computed(() => collectValues(this.children));
|
|
440
|
-
this.visible = computed(() => evaluateCondition(schema.visibleWhen, parentScope, true));
|
|
441
|
-
this.enabled = computed(() => evaluateCondition(schema.enabledWhen, parentScope, true));
|
|
319
|
+
this.value = reactive.computed(() => collectValues(this.children));
|
|
320
|
+
this.visible = reactive.computed(() => evaluateCondition(schema.visibleWhen, parentScope, true));
|
|
321
|
+
this.enabled = reactive.computed(() => evaluateCondition(schema.enabledWhen, parentScope, true));
|
|
442
322
|
}
|
|
443
323
|
reset(initial) {
|
|
444
324
|
resetNodes(this.children, initial);
|
|
@@ -461,10 +341,10 @@ var CollectionNode = class {
|
|
|
461
341
|
this.parentScope = parentScope;
|
|
462
342
|
this.itemSchema = { id: schema.id, type: "group", fields: schema.fields ?? [] };
|
|
463
343
|
const seed = this.seedRows(initial);
|
|
464
|
-
this.rows = signal(seed);
|
|
465
|
-
this.value = computed(() => this.rows.get().map((row) => row.group.value.get()));
|
|
466
|
-
this.visible = computed(() => evaluateCondition(schema.visibleWhen, parentScope, true));
|
|
467
|
-
this.enabled = computed(() => evaluateCondition(schema.enabledWhen, parentScope, true));
|
|
344
|
+
this.rows = reactive.signal(seed);
|
|
345
|
+
this.value = reactive.computed(() => this.rows.get().map((row) => row.group.value.get()));
|
|
346
|
+
this.visible = reactive.computed(() => evaluateCondition(schema.visibleWhen, parentScope, true));
|
|
347
|
+
this.enabled = reactive.computed(() => evaluateCondition(schema.enabledWhen, parentScope, true));
|
|
468
348
|
}
|
|
469
349
|
/** Reactive list of rows (subscribes the caller to add/remove). */
|
|
470
350
|
get items() {
|
|
@@ -529,24 +409,37 @@ var Form = class {
|
|
|
529
409
|
isDirty;
|
|
530
410
|
/** True when no visible field currently has an error. */
|
|
531
411
|
isValid;
|
|
532
|
-
submitting = signal(false);
|
|
412
|
+
submitting = reactive.signal(false);
|
|
533
413
|
initialValues;
|
|
534
414
|
initialSnapshot;
|
|
535
415
|
rootByName;
|
|
536
416
|
listeners = /* @__PURE__ */ new Map();
|
|
537
417
|
disposeRenderer = null;
|
|
418
|
+
disposePersist = null;
|
|
538
419
|
constructor(schema$1, initialValues = {}, options = {}) {
|
|
539
420
|
this.schema = schema.parseSchema(schema$1);
|
|
540
421
|
this.options = options;
|
|
541
422
|
this.initialValues = initialValues;
|
|
542
|
-
const
|
|
423
|
+
const fields = this.schema.locales?.length ? expandLocalized(this.schema.fields, this.schema.locales) : this.schema.fields;
|
|
424
|
+
const seed = loadPersisted(options.persistKey, initialValues);
|
|
425
|
+
const tree = buildTree(fields, seed);
|
|
543
426
|
this.tree = tree.nodes;
|
|
544
427
|
this.rootByName = tree.byName;
|
|
545
|
-
this.order =
|
|
546
|
-
this.values = computed(() => collectTree(this.tree));
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
428
|
+
this.order = fields.map((f) => f.id);
|
|
429
|
+
this.values = reactive.computed(() => collectTree(this.tree));
|
|
430
|
+
if (options.persistKey) {
|
|
431
|
+
const key = options.persistKey;
|
|
432
|
+
this.disposePersist = reactive.effect(() => {
|
|
433
|
+
const v = this.values.get();
|
|
434
|
+
try {
|
|
435
|
+
localStorage.setItem(key, JSON.stringify(v));
|
|
436
|
+
} catch {
|
|
437
|
+
}
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
this.initialSnapshot = JSON.stringify(reactive.untrack(() => this.values.peek()));
|
|
441
|
+
this.isDirty = reactive.computed(() => JSON.stringify(this.values.get()) !== this.initialSnapshot);
|
|
442
|
+
this.isValid = reactive.computed(() => {
|
|
550
443
|
let valid = true;
|
|
551
444
|
eachLeaf(this.tree, (leaf) => {
|
|
552
445
|
if (leaf.visible.get() && leaf.error.get() !== null) valid = false;
|
|
@@ -574,14 +467,24 @@ var Form = class {
|
|
|
574
467
|
setFieldValue(field, value) {
|
|
575
468
|
field.value.set(value);
|
|
576
469
|
field.touched.set(true);
|
|
577
|
-
|
|
470
|
+
field.validate();
|
|
578
471
|
this.emit("change", { id: field.id, value });
|
|
579
472
|
}
|
|
580
473
|
setError(id, error) {
|
|
581
474
|
this.field(id)?.error.set(error);
|
|
582
475
|
}
|
|
476
|
+
/** Patch one field's schema at runtime (change type, label, options, validation, …). */
|
|
477
|
+
setFieldSchema(path, partial) {
|
|
478
|
+
this.field(path)?.patchSchema(partial);
|
|
479
|
+
}
|
|
480
|
+
/** Patch many fields' schemas at once: `form.patch({ state: { type: "text" }, … })`. */
|
|
481
|
+
patch(updates) {
|
|
482
|
+
reactive.batch(() => {
|
|
483
|
+
for (const [path, partial] of Object.entries(updates)) this.setFieldSchema(path, partial);
|
|
484
|
+
});
|
|
485
|
+
}
|
|
583
486
|
setErrors(errors) {
|
|
584
|
-
batch(() => {
|
|
487
|
+
reactive.batch(() => {
|
|
585
488
|
for (const [id, error] of Object.entries(errors)) this.setError(id, error);
|
|
586
489
|
});
|
|
587
490
|
}
|
|
@@ -591,9 +494,9 @@ var Form = class {
|
|
|
591
494
|
// ---- lifecycle ----------------------------------------------------------
|
|
592
495
|
/** Validate every (visible) leaf field; returns true when the whole form is valid. */
|
|
593
496
|
validate() {
|
|
594
|
-
return untrack(() => {
|
|
497
|
+
return reactive.untrack(() => {
|
|
595
498
|
let ok = true;
|
|
596
|
-
batch(() => {
|
|
499
|
+
reactive.batch(() => {
|
|
597
500
|
eachLeaf(this.tree, (leaf) => {
|
|
598
501
|
if (leaf.validate() !== null) ok = false;
|
|
599
502
|
});
|
|
@@ -601,36 +504,54 @@ var Form = class {
|
|
|
601
504
|
return ok;
|
|
602
505
|
});
|
|
603
506
|
}
|
|
604
|
-
/**
|
|
605
|
-
|
|
507
|
+
/**
|
|
508
|
+
* Run the submission pipeline: validate → transform → send → onSuccess/onError.
|
|
509
|
+
* Pass an inline `transform` to shape the final payload, e.g.
|
|
510
|
+
* `form.submit((values) => ({ ...values, source: "web" }))`.
|
|
511
|
+
*
|
|
512
|
+
* Always **resolves** with a {@link SubmitResult} — never throws — so you can
|
|
513
|
+
* handle both outcomes from the API in one place:
|
|
514
|
+
* `const res = await form.submit(); res.ok ? res.data : res.error`.
|
|
515
|
+
*/
|
|
516
|
+
async submit(transform) {
|
|
606
517
|
if (!this.validate()) {
|
|
607
|
-
const
|
|
518
|
+
const errors = this.collectErrors();
|
|
519
|
+
const error = new FormValidationError(errors);
|
|
608
520
|
this.runErrorHandler(error);
|
|
609
521
|
this.emit("error", error);
|
|
610
|
-
|
|
522
|
+
return { ok: false, error, errors };
|
|
611
523
|
}
|
|
612
524
|
this.submitting.set(true);
|
|
613
|
-
const values = untrack(() => this.values.peek());
|
|
614
|
-
const
|
|
525
|
+
const values = reactive.untrack(() => this.values.peek());
|
|
526
|
+
const named = this.applyTransform(values);
|
|
527
|
+
const payload = transform ? transform(named, this) : named;
|
|
615
528
|
this.emit("submit", payload);
|
|
616
529
|
try {
|
|
617
|
-
const
|
|
618
|
-
this.
|
|
619
|
-
this.
|
|
620
|
-
|
|
530
|
+
const data = await this.send(payload);
|
|
531
|
+
this.clearPersisted();
|
|
532
|
+
this.runSuccessHandler(data);
|
|
533
|
+
this.emit("success", data);
|
|
534
|
+
return { ok: true, data };
|
|
621
535
|
} catch (error) {
|
|
622
536
|
this.runErrorHandler(error);
|
|
623
537
|
this.emit("error", error);
|
|
624
|
-
|
|
538
|
+
return { ok: false, error };
|
|
625
539
|
} finally {
|
|
626
540
|
this.submitting.set(false);
|
|
627
541
|
}
|
|
628
542
|
}
|
|
629
543
|
reset(values = this.initialValues) {
|
|
630
|
-
batch(() => {
|
|
544
|
+
reactive.batch(() => {
|
|
631
545
|
resetNodes(this.tree, values);
|
|
632
546
|
});
|
|
633
547
|
}
|
|
548
|
+
/** Trigger a named form action: runs its handler (from options) and emits "action". */
|
|
549
|
+
action(name) {
|
|
550
|
+
const def = this.schema.actions?.find((a) => a.name === name);
|
|
551
|
+
const handler = def?.handler ? this.options.handlers?.[def.handler] : void 0;
|
|
552
|
+
handler?.(this);
|
|
553
|
+
this.emit("action", { name });
|
|
554
|
+
}
|
|
634
555
|
/** Mount into a host element using the given renderer (or the registered default). */
|
|
635
556
|
mount(host, renderer = defaultRenderer) {
|
|
636
557
|
if (!renderer) {
|
|
@@ -645,8 +566,19 @@ var Form = class {
|
|
|
645
566
|
destroy() {
|
|
646
567
|
this.disposeRenderer?.();
|
|
647
568
|
this.disposeRenderer = null;
|
|
569
|
+
this.disposePersist?.();
|
|
570
|
+
this.disposePersist = null;
|
|
648
571
|
this.listeners.clear();
|
|
649
572
|
}
|
|
573
|
+
/** Remove the cached draft from `localStorage` (called on a successful submit). */
|
|
574
|
+
clearPersisted() {
|
|
575
|
+
const key = this.options.persistKey;
|
|
576
|
+
if (!key || typeof localStorage === "undefined") return;
|
|
577
|
+
try {
|
|
578
|
+
localStorage.removeItem(key);
|
|
579
|
+
} catch {
|
|
580
|
+
}
|
|
581
|
+
}
|
|
650
582
|
// ---- events -------------------------------------------------------------
|
|
651
583
|
on(event, listener) {
|
|
652
584
|
let set = this.listeners.get(event);
|
|
@@ -708,6 +640,54 @@ var FormValidationError = class extends Error {
|
|
|
708
640
|
function collectTree(tree) {
|
|
709
641
|
return collectValues(tree);
|
|
710
642
|
}
|
|
643
|
+
function loadPersisted(key, initial) {
|
|
644
|
+
if (!key || typeof localStorage === "undefined") return initial;
|
|
645
|
+
try {
|
|
646
|
+
const saved = localStorage.getItem(key);
|
|
647
|
+
if (saved) return { ...initial, ...JSON.parse(saved) };
|
|
648
|
+
} catch {
|
|
649
|
+
}
|
|
650
|
+
return initial;
|
|
651
|
+
}
|
|
652
|
+
function expandLocalized(fields, locales) {
|
|
653
|
+
return fields.map((f) => {
|
|
654
|
+
if (f.localized) {
|
|
655
|
+
const leafType = f.type === "group" || f.type === "collection" ? "text" : f.type;
|
|
656
|
+
const child = (loc) => {
|
|
657
|
+
const c = { id: loc, type: leafType, label: loc };
|
|
658
|
+
if (f.placeholder !== void 0) c["placeholder"] = f.placeholder;
|
|
659
|
+
if (f.validation !== void 0) c["validation"] = f.validation;
|
|
660
|
+
if (f.options !== void 0) c["options"] = f.options;
|
|
661
|
+
if (f.widget !== void 0) c["widget"] = f.widget;
|
|
662
|
+
if (f.tooltip !== void 0) c["tooltip"] = f.tooltip;
|
|
663
|
+
return c;
|
|
664
|
+
};
|
|
665
|
+
const group = {
|
|
666
|
+
id: f.id,
|
|
667
|
+
type: "group",
|
|
668
|
+
// Keep the `localized` flag so the renderer shows ONE input + a language
|
|
669
|
+
// switcher (instead of one input per locale). Value stays `{ en, ar }`.
|
|
670
|
+
localized: true,
|
|
671
|
+
fields: locales.map(child)
|
|
672
|
+
};
|
|
673
|
+
if (f.defaultLocale !== void 0) group["defaultLocale"] = f.defaultLocale;
|
|
674
|
+
for (const key of [
|
|
675
|
+
"label",
|
|
676
|
+
"visibleWhen",
|
|
677
|
+
"enabledWhen",
|
|
678
|
+
"class",
|
|
679
|
+
"classes",
|
|
680
|
+
"help",
|
|
681
|
+
"tooltip"
|
|
682
|
+
]) {
|
|
683
|
+
if (f[key] !== void 0) group[key] = f[key];
|
|
684
|
+
}
|
|
685
|
+
return group;
|
|
686
|
+
}
|
|
687
|
+
if (f.fields) return { ...f, fields: expandLocalized(f.fields, locales) };
|
|
688
|
+
return f;
|
|
689
|
+
});
|
|
690
|
+
}
|
|
711
691
|
function collectLeaves(tree) {
|
|
712
692
|
const out = /* @__PURE__ */ new Map();
|
|
713
693
|
const walk = (nodes, prefix) => {
|
|
@@ -739,26 +719,45 @@ function resolveLeaf(tree, rootByName, path) {
|
|
|
739
719
|
return node && node.kind === "field" ? node : void 0;
|
|
740
720
|
}
|
|
741
721
|
|
|
722
|
+
Object.defineProperty(exports, "batch", {
|
|
723
|
+
enumerable: true,
|
|
724
|
+
get: function () { return reactive.batch; }
|
|
725
|
+
});
|
|
726
|
+
Object.defineProperty(exports, "computed", {
|
|
727
|
+
enumerable: true,
|
|
728
|
+
get: function () { return reactive.computed; }
|
|
729
|
+
});
|
|
730
|
+
Object.defineProperty(exports, "effect", {
|
|
731
|
+
enumerable: true,
|
|
732
|
+
get: function () { return reactive.effect; }
|
|
733
|
+
});
|
|
734
|
+
Object.defineProperty(exports, "isTracking", {
|
|
735
|
+
enumerable: true,
|
|
736
|
+
get: function () { return reactive.isTracking; }
|
|
737
|
+
});
|
|
738
|
+
Object.defineProperty(exports, "signal", {
|
|
739
|
+
enumerable: true,
|
|
740
|
+
get: function () { return reactive.signal; }
|
|
741
|
+
});
|
|
742
|
+
Object.defineProperty(exports, "untrack", {
|
|
743
|
+
enumerable: true,
|
|
744
|
+
get: function () { return reactive.untrack; }
|
|
745
|
+
});
|
|
742
746
|
exports.CollectionNode = CollectionNode;
|
|
743
747
|
exports.FieldState = FieldState;
|
|
744
748
|
exports.Form = Form;
|
|
745
749
|
exports.FormValidationError = FormValidationError;
|
|
746
750
|
exports.GroupNode = GroupNode;
|
|
747
|
-
exports.batch = batch;
|
|
748
751
|
exports.buildTree = buildTree;
|
|
749
752
|
exports.compileValidator = compileValidator;
|
|
750
|
-
exports.computed = computed;
|
|
751
753
|
exports.defaultValueFor = defaultValueFor;
|
|
752
754
|
exports.eachLeaf = eachLeaf;
|
|
753
|
-
exports.effect = effect;
|
|
754
755
|
exports.evaluateCondition = evaluateCondition;
|
|
756
|
+
exports.isPresentational = isPresentational;
|
|
755
757
|
exports.isProviderRef = isProviderRef;
|
|
756
|
-
exports.isTracking = isTracking;
|
|
757
758
|
exports.referencedFields = referencedFields;
|
|
758
759
|
exports.resolve = resolve;
|
|
759
760
|
exports.resolveQuery = resolveQuery;
|
|
760
761
|
exports.setDefaultRenderer = setDefaultRenderer;
|
|
761
|
-
exports.signal = signal;
|
|
762
|
-
exports.untrack = untrack;
|
|
763
762
|
//# sourceMappingURL=index.cjs.map
|
|
764
763
|
//# sourceMappingURL=index.cjs.map
|