@formwright/core 0.1.0 → 0.2.2
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-OWEG6VGP.js +3 -0
- package/dist/chunk-OWEG6VGP.js.map +1 -0
- package/dist/index.cjs +376 -232
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +105 -17
- package/dist/index.d.ts +105 -17
- package/dist/index.js +330 -51
- 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.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { signal, computed, untrack, batch } from './chunk-
|
|
2
|
-
export { batch, computed, effect, isTracking, signal, untrack } from './chunk-
|
|
1
|
+
import { signal, computed, effect, untrack, batch } from './chunk-OWEG6VGP.js';
|
|
2
|
+
export { batch, computed, effect, isTracking, signal, untrack } from './chunk-OWEG6VGP.js';
|
|
3
3
|
import { parseSchema } from '@formwright/schema';
|
|
4
4
|
|
|
5
5
|
// src/conditions.ts
|
|
@@ -90,35 +90,42 @@ function isEmpty(value) {
|
|
|
90
90
|
return value === void 0 || value === null || value === "";
|
|
91
91
|
}
|
|
92
92
|
function compileValidator(schema) {
|
|
93
|
+
const overrides = schema.messages ?? {};
|
|
94
|
+
const msg = (rule, fallback) => overrides[rule] ?? (typeof schema.message === "string" ? schema.message : fallback);
|
|
93
95
|
return (value) => {
|
|
94
|
-
const msg = (fallback) => typeof schema.message === "string" ? schema.message : fallback;
|
|
95
96
|
if (isEmpty(value)) {
|
|
96
|
-
return schema.required ? msg("This field is required") : null;
|
|
97
|
+
return schema.required ? msg("required", "This field is required") : null;
|
|
97
98
|
}
|
|
98
99
|
if (schema.kind === "string" || schema.format) {
|
|
99
100
|
const str = String(value);
|
|
100
101
|
if (schema.minLength !== void 0 && str.length < schema.minLength) {
|
|
101
|
-
return msg(`Must be at least ${schema.minLength} characters`);
|
|
102
|
+
return msg("minLength", `Must be at least ${schema.minLength} characters`);
|
|
102
103
|
}
|
|
103
104
|
if (schema.maxLength !== void 0 && str.length > schema.maxLength) {
|
|
104
|
-
return msg(`Must be at most ${schema.maxLength} characters`);
|
|
105
|
+
return msg("maxLength", `Must be at most ${schema.maxLength} characters`);
|
|
105
106
|
}
|
|
106
107
|
if (schema.pattern !== void 0 && !new RegExp(schema.pattern).test(str)) {
|
|
107
|
-
return msg("Invalid format");
|
|
108
|
+
return msg("pattern", "Invalid format");
|
|
108
109
|
}
|
|
109
|
-
if (schema.format === "email" && !EMAIL.test(str))
|
|
110
|
-
|
|
111
|
-
if (schema.format === "
|
|
110
|
+
if (schema.format === "email" && !EMAIL.test(str))
|
|
111
|
+
return msg("format", "Enter a valid email");
|
|
112
|
+
if (schema.format === "url" && !URL.test(str)) return msg("format", "Enter a valid URL");
|
|
113
|
+
if (schema.format === "uuid" && !UUID.test(str)) return msg("format", "Enter a valid UUID");
|
|
112
114
|
}
|
|
113
115
|
if (schema.kind === "number") {
|
|
114
116
|
const num = Number(value);
|
|
115
|
-
if (Number.isNaN(num)) return msg("Must be a number");
|
|
116
|
-
if (schema.min !== void 0 && num < schema.min)
|
|
117
|
-
|
|
117
|
+
if (Number.isNaN(num)) return msg("type", "Must be a number");
|
|
118
|
+
if (schema.min !== void 0 && num < schema.min)
|
|
119
|
+
return msg("min", `Must be \u2265 ${schema.min}`);
|
|
120
|
+
if (schema.max !== void 0 && num > schema.max)
|
|
121
|
+
return msg("max", `Must be \u2264 ${schema.max}`);
|
|
118
122
|
}
|
|
119
123
|
return null;
|
|
120
124
|
};
|
|
121
125
|
}
|
|
126
|
+
function requiredMessage(schema) {
|
|
127
|
+
return schema?.messages?.required ?? (typeof schema?.message === "string" ? schema.message : "This field is required");
|
|
128
|
+
}
|
|
122
129
|
|
|
123
130
|
// src/providers.ts
|
|
124
131
|
function isProviderRef(value) {
|
|
@@ -158,10 +165,14 @@ function defaultValueFor(type) {
|
|
|
158
165
|
return "";
|
|
159
166
|
}
|
|
160
167
|
}
|
|
168
|
+
var uidSeq = 0;
|
|
161
169
|
var FieldState = class {
|
|
162
170
|
/** Discriminant for the {@link FieldNode} union (leaf vs group/collection). */
|
|
163
171
|
kind = "field";
|
|
164
172
|
id;
|
|
173
|
+
/** A globally-unique DOM id for the control (collection rows reuse `id`, so this must be unique). */
|
|
174
|
+
domId = `fw-${(uidSeq++).toString(36)}`;
|
|
175
|
+
/** The field's schema — mutable at runtime via {@link patchSchema}. */
|
|
165
176
|
schema;
|
|
166
177
|
value;
|
|
167
178
|
error;
|
|
@@ -169,32 +180,55 @@ var FieldState = class {
|
|
|
169
180
|
visible;
|
|
170
181
|
enabled;
|
|
171
182
|
required;
|
|
183
|
+
/** Bumps whenever the schema is patched — renderers re-render the field on change. */
|
|
184
|
+
revision;
|
|
172
185
|
validator;
|
|
173
|
-
|
|
186
|
+
rev = signal(0);
|
|
187
|
+
stepActive;
|
|
188
|
+
constructor(schema, initial, getValue, stepActive) {
|
|
174
189
|
this.id = schema.id;
|
|
175
190
|
this.schema = schema;
|
|
191
|
+
this.stepActive = stepActive;
|
|
176
192
|
this.value = signal(initial);
|
|
177
193
|
this.error = signal(null);
|
|
178
194
|
this.touched = signal(false);
|
|
179
195
|
this.validator = schema.validation ? compileValidator(schema.validation) : null;
|
|
180
|
-
this.
|
|
181
|
-
this.
|
|
196
|
+
this.revision = this.rev;
|
|
197
|
+
this.visible = computed(() => {
|
|
198
|
+
this.rev.get();
|
|
199
|
+
return evaluateCondition(this.schema.visibleWhen, getValue, true);
|
|
200
|
+
});
|
|
201
|
+
this.enabled = computed(() => {
|
|
202
|
+
this.rev.get();
|
|
203
|
+
return evaluateCondition(this.schema.enabledWhen, getValue, true);
|
|
204
|
+
});
|
|
182
205
|
this.required = computed(() => {
|
|
183
|
-
|
|
184
|
-
|
|
206
|
+
this.rev.get();
|
|
207
|
+
if (this.schema.requiredWhen !== void 0) {
|
|
208
|
+
return evaluateCondition(this.schema.requiredWhen, getValue, false);
|
|
185
209
|
}
|
|
186
|
-
return schema.validation?.required ?? false;
|
|
210
|
+
return this.schema.validation?.required ?? false;
|
|
187
211
|
});
|
|
188
212
|
}
|
|
189
|
-
/**
|
|
190
|
-
|
|
213
|
+
/** Merge a partial schema in at runtime (change type, label, options, validation, …). */
|
|
214
|
+
patchSchema(partial) {
|
|
215
|
+
this.schema = { ...this.schema, ...partial };
|
|
216
|
+
this.validator = this.schema.validation ? compileValidator(this.schema.validation) : null;
|
|
217
|
+
this.rev.update((n) => n + 1);
|
|
218
|
+
}
|
|
219
|
+
/** Run validation, store and return the error (or null). Hidden / inactive-step fields never error. */
|
|
220
|
+
validate(options) {
|
|
191
221
|
if (!this.visible.peek()) {
|
|
192
222
|
this.error.set(null);
|
|
193
223
|
return null;
|
|
194
224
|
}
|
|
225
|
+
if (!options?.allSteps && this.stepActive && !this.stepActive.peek()) {
|
|
226
|
+
this.error.set(null);
|
|
227
|
+
return null;
|
|
228
|
+
}
|
|
195
229
|
let result = null;
|
|
196
230
|
if (this.required.peek() && isEmpty2(this.value.peek())) {
|
|
197
|
-
result =
|
|
231
|
+
result = requiredMessage(this.schema.validation);
|
|
198
232
|
} else if (this.validator) {
|
|
199
233
|
result = this.validator(this.value.peek());
|
|
200
234
|
}
|
|
@@ -212,26 +246,40 @@ function isEmpty2(value) {
|
|
|
212
246
|
}
|
|
213
247
|
|
|
214
248
|
// src/nodes.ts
|
|
249
|
+
var PRESENTATIONAL = /* @__PURE__ */ new Set(["heading", "separator", "paragraph"]);
|
|
250
|
+
function isPresentational(type) {
|
|
251
|
+
return PRESENTATIONAL.has(type);
|
|
252
|
+
}
|
|
215
253
|
function nodeValue(node) {
|
|
216
254
|
return node.value.get();
|
|
217
255
|
}
|
|
218
256
|
function collectValues(nodes) {
|
|
219
257
|
const out = {};
|
|
220
|
-
for (const node of nodes)
|
|
258
|
+
for (const node of nodes) {
|
|
259
|
+
if (node.schema.omit || isPresentational(node.schema.type)) continue;
|
|
260
|
+
if (!node.visible.get()) continue;
|
|
261
|
+
out[node.id] = nodeValue(node);
|
|
262
|
+
}
|
|
221
263
|
return out;
|
|
222
264
|
}
|
|
223
|
-
function buildNodes(schemas, scope, initial) {
|
|
265
|
+
function buildNodes(schemas, scope, initial, stepActive) {
|
|
224
266
|
const nodes = [];
|
|
225
267
|
const byName = /* @__PURE__ */ new Map();
|
|
226
268
|
for (const schema of schemas) {
|
|
227
269
|
let node;
|
|
228
270
|
if (schema.type === "group") {
|
|
229
|
-
node = new GroupNode(schema, scope, asDict(initial[schema.id]));
|
|
271
|
+
node = new GroupNode(schema, scope, asDict(initial[schema.id]), stepActive);
|
|
272
|
+
} else if (schema.type === "step") {
|
|
273
|
+
if (!stepActive)
|
|
274
|
+
throw new Error('Field type "step" must be nested inside a "steps" container');
|
|
275
|
+
node = new StepNode(schema, scope, asDict(initial[schema.id]), stepActive);
|
|
230
276
|
} else if (schema.type === "collection") {
|
|
231
277
|
node = new CollectionNode(schema, scope, asArray(initial[schema.id]));
|
|
278
|
+
} else if (schema.type === "steps") {
|
|
279
|
+
node = new StepsNode(schema, scope, asDict(initial[schema.id]));
|
|
232
280
|
} else {
|
|
233
281
|
const init = initial[schema.id] ?? schema.defaultValue ?? defaultValueFor(schema.type);
|
|
234
|
-
node = new FieldState(schema, init, scope);
|
|
282
|
+
node = new FieldState(schema, init, scope, stepActive);
|
|
235
283
|
}
|
|
236
284
|
nodes.push(node);
|
|
237
285
|
byName.set(schema.id, node);
|
|
@@ -249,7 +297,9 @@ function resetNodes(nodes, initial) {
|
|
|
249
297
|
if (node.kind === "field") {
|
|
250
298
|
const init = initial[node.id] ?? node.schema.defaultValue ?? defaultValueFor(node.schema.type);
|
|
251
299
|
node.reset(init);
|
|
252
|
-
} else if (node.kind === "group") {
|
|
300
|
+
} else if (node.kind === "group" || node.kind === "step") {
|
|
301
|
+
node.reset(asDict(initial[node.id]));
|
|
302
|
+
} else if (node.kind === "steps") {
|
|
253
303
|
node.reset(asDict(initial[node.id]));
|
|
254
304
|
} else {
|
|
255
305
|
node.reset(asArray(initial[node.id]));
|
|
@@ -267,14 +317,14 @@ var GroupNode = class {
|
|
|
267
317
|
enabled;
|
|
268
318
|
/** The scope a child uses: resolve a name among siblings, else delegate upward. */
|
|
269
319
|
scope;
|
|
270
|
-
constructor(schema, parentScope, initial) {
|
|
320
|
+
constructor(schema, parentScope, initial, stepActive) {
|
|
271
321
|
this.id = schema.id;
|
|
272
322
|
this.schema = schema;
|
|
273
323
|
this.scope = (name) => {
|
|
274
324
|
const child = this.byName.get(name);
|
|
275
325
|
return child ? nodeValue(child) : parentScope(name);
|
|
276
326
|
};
|
|
277
|
-
const built = buildNodes(schema.fields ?? [], this.scope, initial);
|
|
327
|
+
const built = buildNodes(schema.fields ?? [], this.scope, initial, stepActive);
|
|
278
328
|
this.children = built.nodes;
|
|
279
329
|
this.byName = built.byName;
|
|
280
330
|
this.value = computed(() => collectValues(this.children));
|
|
@@ -337,6 +387,109 @@ var CollectionNode = class {
|
|
|
337
387
|
this.rows.set(this.seedRows(initial));
|
|
338
388
|
}
|
|
339
389
|
};
|
|
390
|
+
var StepNode = class {
|
|
391
|
+
kind = "step";
|
|
392
|
+
id;
|
|
393
|
+
schema;
|
|
394
|
+
children;
|
|
395
|
+
byName;
|
|
396
|
+
value;
|
|
397
|
+
visible;
|
|
398
|
+
enabled;
|
|
399
|
+
scope;
|
|
400
|
+
/** True when this step is the active step in its parent wizard. */
|
|
401
|
+
active;
|
|
402
|
+
constructor(schema, parentScope, initial, active) {
|
|
403
|
+
this.id = schema.id;
|
|
404
|
+
this.schema = schema;
|
|
405
|
+
this.active = active;
|
|
406
|
+
this.scope = (name) => {
|
|
407
|
+
const child = this.byName.get(name);
|
|
408
|
+
return child ? nodeValue(child) : parentScope(name);
|
|
409
|
+
};
|
|
410
|
+
const built = buildNodes(schema.fields ?? [], this.scope, initial, active);
|
|
411
|
+
this.children = built.nodes;
|
|
412
|
+
this.byName = built.byName;
|
|
413
|
+
this.value = computed(() => collectValues(this.children));
|
|
414
|
+
this.visible = computed(() => evaluateCondition(schema.visibleWhen, parentScope, true));
|
|
415
|
+
this.enabled = computed(() => evaluateCondition(schema.enabledWhen, parentScope, true));
|
|
416
|
+
}
|
|
417
|
+
reset(initial) {
|
|
418
|
+
resetNodes(this.children, initial);
|
|
419
|
+
}
|
|
420
|
+
};
|
|
421
|
+
var StepsNode = class {
|
|
422
|
+
kind = "steps";
|
|
423
|
+
id;
|
|
424
|
+
schema;
|
|
425
|
+
steps;
|
|
426
|
+
byName;
|
|
427
|
+
value;
|
|
428
|
+
visible;
|
|
429
|
+
enabled;
|
|
430
|
+
currentStep;
|
|
431
|
+
scope;
|
|
432
|
+
constructor(schema, parentScope, initial) {
|
|
433
|
+
this.id = schema.id;
|
|
434
|
+
this.schema = schema;
|
|
435
|
+
this.currentStep = signal(0);
|
|
436
|
+
this.byName = /* @__PURE__ */ new Map();
|
|
437
|
+
this.scope = (name) => {
|
|
438
|
+
const step = this.byName.get(name);
|
|
439
|
+
return step ? step.value.get() : parentScope(name);
|
|
440
|
+
};
|
|
441
|
+
const stepSchemas = schema.fields ?? [];
|
|
442
|
+
const steps = stepSchemas.map((stepSchema, index) => {
|
|
443
|
+
const active = computed(() => this.currentStep.get() === index);
|
|
444
|
+
const stepInitial = asDict(initial[stepSchema.id]);
|
|
445
|
+
const step = new StepNode(stepSchema, this.scope, stepInitial, active);
|
|
446
|
+
this.byName.set(stepSchema.id, step);
|
|
447
|
+
return step;
|
|
448
|
+
});
|
|
449
|
+
this.steps = steps;
|
|
450
|
+
this.value = computed(() => {
|
|
451
|
+
const out = {};
|
|
452
|
+
for (const step of this.steps) out[step.id] = step.value.get();
|
|
453
|
+
return out;
|
|
454
|
+
});
|
|
455
|
+
this.visible = computed(() => evaluateCondition(schema.visibleWhen, parentScope, true));
|
|
456
|
+
this.enabled = computed(() => evaluateCondition(schema.enabledWhen, parentScope, true));
|
|
457
|
+
}
|
|
458
|
+
/** Validate every leaf in the step at `index` (defaults to the current step). */
|
|
459
|
+
validateStep(index) {
|
|
460
|
+
const i = index ?? this.currentStep.peek();
|
|
461
|
+
const step = this.steps[i];
|
|
462
|
+
if (!step) return true;
|
|
463
|
+
let ok = true;
|
|
464
|
+
eachLeaf([step], (leaf) => {
|
|
465
|
+
if (leaf.validate() !== null) ok = false;
|
|
466
|
+
});
|
|
467
|
+
return ok;
|
|
468
|
+
}
|
|
469
|
+
/** Advance to the next step after optionally validating the current one. Returns false if blocked. */
|
|
470
|
+
next() {
|
|
471
|
+
const validate = this.schema.validateOnNext !== false;
|
|
472
|
+
if (validate && !this.validateStep()) return false;
|
|
473
|
+
const cur = this.currentStep.peek();
|
|
474
|
+
if (cur < this.steps.length - 1) {
|
|
475
|
+
this.currentStep.set(cur + 1);
|
|
476
|
+
return true;
|
|
477
|
+
}
|
|
478
|
+
return false;
|
|
479
|
+
}
|
|
480
|
+
/** Go back one step (no validation). */
|
|
481
|
+
prev() {
|
|
482
|
+
this.currentStep.update((i) => Math.max(0, i - 1));
|
|
483
|
+
}
|
|
484
|
+
/** Jump to a step by index (does not validate). */
|
|
485
|
+
goTo(index) {
|
|
486
|
+
if (index >= 0 && index < this.steps.length) this.currentStep.set(index);
|
|
487
|
+
}
|
|
488
|
+
reset(initial) {
|
|
489
|
+
this.currentStep.set(0);
|
|
490
|
+
for (const step of this.steps) step.reset(asDict(initial[step.id]));
|
|
491
|
+
}
|
|
492
|
+
};
|
|
340
493
|
function buildTree(schemas, initial) {
|
|
341
494
|
let byName;
|
|
342
495
|
const scope = (name) => {
|
|
@@ -350,8 +503,10 @@ function buildTree(schemas, initial) {
|
|
|
350
503
|
function eachLeaf(nodes, visit) {
|
|
351
504
|
for (const node of nodes) {
|
|
352
505
|
if (node.kind === "field") visit(node);
|
|
353
|
-
else if (node.kind === "group") eachLeaf(node.children, visit);
|
|
354
|
-
else
|
|
506
|
+
else if (node.kind === "group" || node.kind === "step") eachLeaf(node.children, visit);
|
|
507
|
+
else if (node.kind === "steps") {
|
|
508
|
+
for (const step of node.steps) eachLeaf(step.children, visit);
|
|
509
|
+
} else for (const row of node.items.peek()) eachLeaf(row.group.children, visit);
|
|
355
510
|
}
|
|
356
511
|
}
|
|
357
512
|
var defaultRenderer = null;
|
|
@@ -376,15 +531,28 @@ var Form = class {
|
|
|
376
531
|
rootByName;
|
|
377
532
|
listeners = /* @__PURE__ */ new Map();
|
|
378
533
|
disposeRenderer = null;
|
|
534
|
+
disposePersist = null;
|
|
379
535
|
constructor(schema, initialValues = {}, options = {}) {
|
|
380
536
|
this.schema = parseSchema(schema);
|
|
381
537
|
this.options = options;
|
|
382
538
|
this.initialValues = initialValues;
|
|
383
|
-
const
|
|
539
|
+
const fields = this.schema.locales?.length ? expandLocalized(this.schema.fields, this.schema.locales) : this.schema.fields;
|
|
540
|
+
const seed = loadPersisted(options.persistKey, initialValues);
|
|
541
|
+
const tree = buildTree(fields, seed);
|
|
384
542
|
this.tree = tree.nodes;
|
|
385
543
|
this.rootByName = tree.byName;
|
|
386
|
-
this.order =
|
|
544
|
+
this.order = fields.map((f) => f.id);
|
|
387
545
|
this.values = computed(() => collectTree(this.tree));
|
|
546
|
+
if (options.persistKey) {
|
|
547
|
+
const key = options.persistKey;
|
|
548
|
+
this.disposePersist = effect(() => {
|
|
549
|
+
const v = this.values.get();
|
|
550
|
+
try {
|
|
551
|
+
localStorage.setItem(key, JSON.stringify(v));
|
|
552
|
+
} catch {
|
|
553
|
+
}
|
|
554
|
+
});
|
|
555
|
+
}
|
|
388
556
|
this.initialSnapshot = JSON.stringify(untrack(() => this.values.peek()));
|
|
389
557
|
this.isDirty = computed(() => JSON.stringify(this.values.get()) !== this.initialSnapshot);
|
|
390
558
|
this.isValid = computed(() => {
|
|
@@ -415,12 +583,22 @@ var Form = class {
|
|
|
415
583
|
setFieldValue(field, value) {
|
|
416
584
|
field.value.set(value);
|
|
417
585
|
field.touched.set(true);
|
|
418
|
-
|
|
586
|
+
field.validate();
|
|
419
587
|
this.emit("change", { id: field.id, value });
|
|
420
588
|
}
|
|
421
589
|
setError(id, error) {
|
|
422
590
|
this.field(id)?.error.set(error);
|
|
423
591
|
}
|
|
592
|
+
/** Patch one field's schema at runtime (change type, label, options, validation, …). */
|
|
593
|
+
setFieldSchema(path, partial) {
|
|
594
|
+
this.field(path)?.patchSchema(partial);
|
|
595
|
+
}
|
|
596
|
+
/** Patch many fields' schemas at once: `form.patch({ state: { type: "text" }, … })`. */
|
|
597
|
+
patch(updates) {
|
|
598
|
+
batch(() => {
|
|
599
|
+
for (const [path, partial] of Object.entries(updates)) this.setFieldSchema(path, partial);
|
|
600
|
+
});
|
|
601
|
+
}
|
|
424
602
|
setErrors(errors) {
|
|
425
603
|
batch(() => {
|
|
426
604
|
for (const [id, error] of Object.entries(errors)) this.setError(id, error);
|
|
@@ -431,38 +609,54 @@ var Form = class {
|
|
|
431
609
|
}
|
|
432
610
|
// ---- lifecycle ----------------------------------------------------------
|
|
433
611
|
/** Validate every (visible) leaf field; returns true when the whole form is valid. */
|
|
434
|
-
validate() {
|
|
612
|
+
validate(options) {
|
|
613
|
+
const allSteps = options?.allSteps ?? false;
|
|
435
614
|
return untrack(() => {
|
|
436
615
|
let ok = true;
|
|
437
616
|
batch(() => {
|
|
438
617
|
eachLeaf(this.tree, (leaf) => {
|
|
439
|
-
if (leaf.validate() !== null) ok = false;
|
|
618
|
+
if (leaf.validate({ allSteps }) !== null) ok = false;
|
|
440
619
|
});
|
|
441
620
|
});
|
|
442
621
|
return ok;
|
|
443
622
|
});
|
|
444
623
|
}
|
|
445
|
-
/**
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
624
|
+
/** Find the first `steps` container in the field tree (if any). */
|
|
625
|
+
findSteps() {
|
|
626
|
+
return findSteps(this.tree);
|
|
627
|
+
}
|
|
628
|
+
/**
|
|
629
|
+
* Run the submission pipeline: validate → transform → send → onSuccess/onError.
|
|
630
|
+
* Pass an inline `transform` to shape the final payload, e.g.
|
|
631
|
+
* `form.submit((values) => ({ ...values, source: "web" }))`.
|
|
632
|
+
*
|
|
633
|
+
* Always **resolves** with a {@link SubmitResult} — never throws — so you can
|
|
634
|
+
* handle both outcomes from the API in one place:
|
|
635
|
+
* `const res = await form.submit(); res.ok ? res.data : res.error`.
|
|
636
|
+
*/
|
|
637
|
+
async submit(transform) {
|
|
638
|
+
if (!this.validate({ allSteps: true })) {
|
|
639
|
+
const errors = this.collectErrors();
|
|
640
|
+
const error = new FormValidationError(errors);
|
|
449
641
|
this.runErrorHandler(error);
|
|
450
642
|
this.emit("error", error);
|
|
451
|
-
|
|
643
|
+
return { ok: false, error, errors };
|
|
452
644
|
}
|
|
453
645
|
this.submitting.set(true);
|
|
454
646
|
const values = untrack(() => this.values.peek());
|
|
455
|
-
const
|
|
647
|
+
const named = this.applyTransform(values);
|
|
648
|
+
const payload = transform ? transform(named, this) : named;
|
|
456
649
|
this.emit("submit", payload);
|
|
457
650
|
try {
|
|
458
|
-
const
|
|
459
|
-
this.
|
|
460
|
-
this.
|
|
461
|
-
|
|
651
|
+
const data = await this.send(payload);
|
|
652
|
+
this.clearPersisted();
|
|
653
|
+
this.runSuccessHandler(data);
|
|
654
|
+
this.emit("success", data);
|
|
655
|
+
return { ok: true, data };
|
|
462
656
|
} catch (error) {
|
|
463
657
|
this.runErrorHandler(error);
|
|
464
658
|
this.emit("error", error);
|
|
465
|
-
|
|
659
|
+
return { ok: false, error };
|
|
466
660
|
} finally {
|
|
467
661
|
this.submitting.set(false);
|
|
468
662
|
}
|
|
@@ -472,6 +666,13 @@ var Form = class {
|
|
|
472
666
|
resetNodes(this.tree, values);
|
|
473
667
|
});
|
|
474
668
|
}
|
|
669
|
+
/** Trigger a named form action: runs its handler (from options) and emits "action". */
|
|
670
|
+
action(name) {
|
|
671
|
+
const def = this.schema.actions?.find((a) => a.name === name);
|
|
672
|
+
const handler = def?.handler ? this.options.handlers?.[def.handler] : void 0;
|
|
673
|
+
handler?.(this);
|
|
674
|
+
this.emit("action", { name });
|
|
675
|
+
}
|
|
475
676
|
/** Mount into a host element using the given renderer (or the registered default). */
|
|
476
677
|
mount(host, renderer = defaultRenderer) {
|
|
477
678
|
if (!renderer) {
|
|
@@ -486,8 +687,19 @@ var Form = class {
|
|
|
486
687
|
destroy() {
|
|
487
688
|
this.disposeRenderer?.();
|
|
488
689
|
this.disposeRenderer = null;
|
|
690
|
+
this.disposePersist?.();
|
|
691
|
+
this.disposePersist = null;
|
|
489
692
|
this.listeners.clear();
|
|
490
693
|
}
|
|
694
|
+
/** Remove the cached draft from `localStorage` (called on a successful submit). */
|
|
695
|
+
clearPersisted() {
|
|
696
|
+
const key = this.options.persistKey;
|
|
697
|
+
if (!key || typeof localStorage === "undefined") return;
|
|
698
|
+
try {
|
|
699
|
+
localStorage.removeItem(key);
|
|
700
|
+
} catch {
|
|
701
|
+
}
|
|
702
|
+
}
|
|
491
703
|
// ---- events -------------------------------------------------------------
|
|
492
704
|
on(event, listener) {
|
|
493
705
|
let set = this.listeners.get(event);
|
|
@@ -549,14 +761,64 @@ var FormValidationError = class extends Error {
|
|
|
549
761
|
function collectTree(tree) {
|
|
550
762
|
return collectValues(tree);
|
|
551
763
|
}
|
|
764
|
+
function loadPersisted(key, initial) {
|
|
765
|
+
if (!key || typeof localStorage === "undefined") return initial;
|
|
766
|
+
try {
|
|
767
|
+
const saved = localStorage.getItem(key);
|
|
768
|
+
if (saved) return { ...initial, ...JSON.parse(saved) };
|
|
769
|
+
} catch {
|
|
770
|
+
}
|
|
771
|
+
return initial;
|
|
772
|
+
}
|
|
773
|
+
function expandLocalized(fields, locales) {
|
|
774
|
+
return fields.map((f) => {
|
|
775
|
+
if (f.localized) {
|
|
776
|
+
const leafType = f.type === "group" || f.type === "collection" ? "text" : f.type;
|
|
777
|
+
const child = (loc) => {
|
|
778
|
+
const c = { id: loc, type: leafType, label: loc };
|
|
779
|
+
if (f.placeholder !== void 0) c["placeholder"] = f.placeholder;
|
|
780
|
+
if (f.validation !== void 0) c["validation"] = f.validation;
|
|
781
|
+
if (f.options !== void 0) c["options"] = f.options;
|
|
782
|
+
if (f.widget !== void 0) c["widget"] = f.widget;
|
|
783
|
+
if (f.tooltip !== void 0) c["tooltip"] = f.tooltip;
|
|
784
|
+
return c;
|
|
785
|
+
};
|
|
786
|
+
const group = {
|
|
787
|
+
id: f.id,
|
|
788
|
+
type: "group",
|
|
789
|
+
// Keep the `localized` flag so the renderer shows ONE input + a language
|
|
790
|
+
// switcher (instead of one input per locale). Value stays `{ en, ar }`.
|
|
791
|
+
localized: true,
|
|
792
|
+
fields: locales.map(child)
|
|
793
|
+
};
|
|
794
|
+
if (f.defaultLocale !== void 0) group["defaultLocale"] = f.defaultLocale;
|
|
795
|
+
for (const key of [
|
|
796
|
+
"label",
|
|
797
|
+
"visibleWhen",
|
|
798
|
+
"enabledWhen",
|
|
799
|
+
"class",
|
|
800
|
+
"classes",
|
|
801
|
+
"help",
|
|
802
|
+
"tooltip"
|
|
803
|
+
]) {
|
|
804
|
+
if (f[key] !== void 0) group[key] = f[key];
|
|
805
|
+
}
|
|
806
|
+
return group;
|
|
807
|
+
}
|
|
808
|
+
if (f.fields) return { ...f, fields: expandLocalized(f.fields, locales) };
|
|
809
|
+
return f;
|
|
810
|
+
});
|
|
811
|
+
}
|
|
552
812
|
function collectLeaves(tree) {
|
|
553
813
|
const out = /* @__PURE__ */ new Map();
|
|
554
814
|
const walk = (nodes, prefix) => {
|
|
555
815
|
for (const node of nodes) {
|
|
556
816
|
const path = prefix ? `${prefix}.${node.id}` : node.id;
|
|
557
817
|
if (node.kind === "field") out.set(path, node);
|
|
558
|
-
else if (node.kind === "group") walk(node.children, path);
|
|
559
|
-
else {
|
|
818
|
+
else if (node.kind === "group" || node.kind === "step") walk(node.children, path);
|
|
819
|
+
else if (node.kind === "steps") {
|
|
820
|
+
for (const step of node.steps) walk(step.children, `${path}.${step.id}`);
|
|
821
|
+
} else {
|
|
560
822
|
node.items.peek().forEach((row, i) => walk(row.group.children, `${path}.${i}`));
|
|
561
823
|
}
|
|
562
824
|
}
|
|
@@ -569,7 +831,9 @@ function resolveLeaf(tree, rootByName, path) {
|
|
|
569
831
|
let node = rootByName.get(parts[0]);
|
|
570
832
|
for (let i = 1; i < parts.length && node; i++) {
|
|
571
833
|
const part = parts[i];
|
|
572
|
-
if (node instanceof GroupNode) {
|
|
834
|
+
if (node instanceof GroupNode || node instanceof StepNode) {
|
|
835
|
+
node = node.byName.get(part);
|
|
836
|
+
} else if (node instanceof StepsNode) {
|
|
573
837
|
node = node.byName.get(part);
|
|
574
838
|
} else if (node instanceof CollectionNode) {
|
|
575
839
|
node = node.items.peek()[Number(part)]?.group;
|
|
@@ -579,7 +843,22 @@ function resolveLeaf(tree, rootByName, path) {
|
|
|
579
843
|
}
|
|
580
844
|
return node && node.kind === "field" ? node : void 0;
|
|
581
845
|
}
|
|
846
|
+
function findSteps(tree) {
|
|
847
|
+
for (const node of tree) {
|
|
848
|
+
if (node.kind === "steps") return node;
|
|
849
|
+
if (node.kind === "group" || node.kind === "step") {
|
|
850
|
+
const found = findSteps(node.children);
|
|
851
|
+
if (found) return found;
|
|
852
|
+
} else if (node.kind === "collection") {
|
|
853
|
+
for (const row of node.items.peek()) {
|
|
854
|
+
const found = findSteps(row.group.children);
|
|
855
|
+
if (found) return found;
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
return void 0;
|
|
860
|
+
}
|
|
582
861
|
|
|
583
|
-
export { CollectionNode, FieldState, Form, FormValidationError, GroupNode, buildTree, compileValidator, defaultValueFor, eachLeaf, evaluateCondition, isProviderRef, referencedFields, resolve, resolveQuery, setDefaultRenderer };
|
|
862
|
+
export { CollectionNode, FieldState, Form, FormValidationError, GroupNode, StepNode, StepsNode, buildTree, compileValidator, defaultValueFor, eachLeaf, evaluateCondition, isPresentational, isProviderRef, referencedFields, resolve, resolveQuery, setDefaultRenderer };
|
|
584
863
|
//# sourceMappingURL=index.js.map
|
|
585
864
|
//# sourceMappingURL=index.js.map
|