@formwright/core 0.2.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/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
- import { signal, computed, effect, untrack, batch } from './chunk-O4DUMDBU.js';
2
- export { batch, computed, effect, isTracking, signal, untrack } from './chunk-O4DUMDBU.js';
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
@@ -184,9 +184,11 @@ var FieldState = class {
184
184
  revision;
185
185
  validator;
186
186
  rev = signal(0);
187
- constructor(schema, initial, getValue) {
187
+ stepActive;
188
+ constructor(schema, initial, getValue, stepActive) {
188
189
  this.id = schema.id;
189
190
  this.schema = schema;
191
+ this.stepActive = stepActive;
190
192
  this.value = signal(initial);
191
193
  this.error = signal(null);
192
194
  this.touched = signal(false);
@@ -214,12 +216,16 @@ var FieldState = class {
214
216
  this.validator = this.schema.validation ? compileValidator(this.schema.validation) : null;
215
217
  this.rev.update((n) => n + 1);
216
218
  }
217
- /** Run validation, store and return the error (or null). Hidden fields never error. */
218
- validate() {
219
+ /** Run validation, store and return the error (or null). Hidden / inactive-step fields never error. */
220
+ validate(options) {
219
221
  if (!this.visible.peek()) {
220
222
  this.error.set(null);
221
223
  return null;
222
224
  }
225
+ if (!options?.allSteps && this.stepActive && !this.stepActive.peek()) {
226
+ this.error.set(null);
227
+ return null;
228
+ }
223
229
  let result = null;
224
230
  if (this.required.peek() && isEmpty2(this.value.peek())) {
225
231
  result = requiredMessage(this.schema.validation);
@@ -256,18 +262,24 @@ function collectValues(nodes) {
256
262
  }
257
263
  return out;
258
264
  }
259
- function buildNodes(schemas, scope, initial) {
265
+ function buildNodes(schemas, scope, initial, stepActive) {
260
266
  const nodes = [];
261
267
  const byName = /* @__PURE__ */ new Map();
262
268
  for (const schema of schemas) {
263
269
  let node;
264
270
  if (schema.type === "group") {
265
- 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);
266
276
  } else if (schema.type === "collection") {
267
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]));
268
280
  } else {
269
281
  const init = initial[schema.id] ?? schema.defaultValue ?? defaultValueFor(schema.type);
270
- node = new FieldState(schema, init, scope);
282
+ node = new FieldState(schema, init, scope, stepActive);
271
283
  }
272
284
  nodes.push(node);
273
285
  byName.set(schema.id, node);
@@ -285,7 +297,9 @@ function resetNodes(nodes, initial) {
285
297
  if (node.kind === "field") {
286
298
  const init = initial[node.id] ?? node.schema.defaultValue ?? defaultValueFor(node.schema.type);
287
299
  node.reset(init);
288
- } 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") {
289
303
  node.reset(asDict(initial[node.id]));
290
304
  } else {
291
305
  node.reset(asArray(initial[node.id]));
@@ -303,14 +317,14 @@ var GroupNode = class {
303
317
  enabled;
304
318
  /** The scope a child uses: resolve a name among siblings, else delegate upward. */
305
319
  scope;
306
- constructor(schema, parentScope, initial) {
320
+ constructor(schema, parentScope, initial, stepActive) {
307
321
  this.id = schema.id;
308
322
  this.schema = schema;
309
323
  this.scope = (name) => {
310
324
  const child = this.byName.get(name);
311
325
  return child ? nodeValue(child) : parentScope(name);
312
326
  };
313
- const built = buildNodes(schema.fields ?? [], this.scope, initial);
327
+ const built = buildNodes(schema.fields ?? [], this.scope, initial, stepActive);
314
328
  this.children = built.nodes;
315
329
  this.byName = built.byName;
316
330
  this.value = computed(() => collectValues(this.children));
@@ -373,6 +387,109 @@ var CollectionNode = class {
373
387
  this.rows.set(this.seedRows(initial));
374
388
  }
375
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
+ };
376
493
  function buildTree(schemas, initial) {
377
494
  let byName;
378
495
  const scope = (name) => {
@@ -386,8 +503,10 @@ function buildTree(schemas, initial) {
386
503
  function eachLeaf(nodes, visit) {
387
504
  for (const node of nodes) {
388
505
  if (node.kind === "field") visit(node);
389
- else if (node.kind === "group") eachLeaf(node.children, visit);
390
- else for (const row of node.items.peek()) eachLeaf(row.group.children, visit);
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);
391
510
  }
392
511
  }
393
512
  var defaultRenderer = null;
@@ -490,17 +609,22 @@ var Form = class {
490
609
  }
491
610
  // ---- lifecycle ----------------------------------------------------------
492
611
  /** Validate every (visible) leaf field; returns true when the whole form is valid. */
493
- validate() {
612
+ validate(options) {
613
+ const allSteps = options?.allSteps ?? false;
494
614
  return untrack(() => {
495
615
  let ok = true;
496
616
  batch(() => {
497
617
  eachLeaf(this.tree, (leaf) => {
498
- if (leaf.validate() !== null) ok = false;
618
+ if (leaf.validate({ allSteps }) !== null) ok = false;
499
619
  });
500
620
  });
501
621
  return ok;
502
622
  });
503
623
  }
624
+ /** Find the first `steps` container in the field tree (if any). */
625
+ findSteps() {
626
+ return findSteps(this.tree);
627
+ }
504
628
  /**
505
629
  * Run the submission pipeline: validate → transform → send → onSuccess/onError.
506
630
  * Pass an inline `transform` to shape the final payload, e.g.
@@ -511,7 +635,7 @@ var Form = class {
511
635
  * `const res = await form.submit(); res.ok ? res.data : res.error`.
512
636
  */
513
637
  async submit(transform) {
514
- if (!this.validate()) {
638
+ if (!this.validate({ allSteps: true })) {
515
639
  const errors = this.collectErrors();
516
640
  const error = new FormValidationError(errors);
517
641
  this.runErrorHandler(error);
@@ -691,8 +815,10 @@ function collectLeaves(tree) {
691
815
  for (const node of nodes) {
692
816
  const path = prefix ? `${prefix}.${node.id}` : node.id;
693
817
  if (node.kind === "field") out.set(path, node);
694
- else if (node.kind === "group") walk(node.children, path);
695
- 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 {
696
822
  node.items.peek().forEach((row, i) => walk(row.group.children, `${path}.${i}`));
697
823
  }
698
824
  }
@@ -705,7 +831,9 @@ function resolveLeaf(tree, rootByName, path) {
705
831
  let node = rootByName.get(parts[0]);
706
832
  for (let i = 1; i < parts.length && node; i++) {
707
833
  const part = parts[i];
708
- if (node instanceof GroupNode) {
834
+ if (node instanceof GroupNode || node instanceof StepNode) {
835
+ node = node.byName.get(part);
836
+ } else if (node instanceof StepsNode) {
709
837
  node = node.byName.get(part);
710
838
  } else if (node instanceof CollectionNode) {
711
839
  node = node.items.peek()[Number(part)]?.group;
@@ -715,7 +843,22 @@ function resolveLeaf(tree, rootByName, path) {
715
843
  }
716
844
  return node && node.kind === "field" ? node : void 0;
717
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
+ }
718
861
 
719
- export { CollectionNode, FieldState, Form, FormValidationError, GroupNode, buildTree, compileValidator, defaultValueFor, eachLeaf, evaluateCondition, isPresentational, 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 };
720
863
  //# sourceMappingURL=index.js.map
721
864
  //# sourceMappingURL=index.js.map