@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.
@@ -0,0 +1,3 @@
1
+ export { batch, computed, effect, isTracking, signal, untrack } from '@formwright/reactive';
2
+ //# sourceMappingURL=chunk-OWEG6VGP.js.map
3
+ //# sourceMappingURL=chunk-OWEG6VGP.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":[],"names":[],"mappings":"","file":"chunk-O4DUMDBU.js","sourcesContent":[]}
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"chunk-OWEG6VGP.js","sourcesContent":[]}
package/dist/index.cjs CHANGED
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var reactive = require('@wright/reactive');
3
+ var reactive = require('@formwright/reactive');
4
4
  var schema = require('@formwright/schema');
5
5
 
6
6
  // src/reactive.ts
@@ -187,9 +187,11 @@ var FieldState = class {
187
187
  revision;
188
188
  validator;
189
189
  rev = reactive.signal(0);
190
- constructor(schema, initial, getValue) {
190
+ stepActive;
191
+ constructor(schema, initial, getValue, stepActive) {
191
192
  this.id = schema.id;
192
193
  this.schema = schema;
194
+ this.stepActive = stepActive;
193
195
  this.value = reactive.signal(initial);
194
196
  this.error = reactive.signal(null);
195
197
  this.touched = reactive.signal(false);
@@ -217,12 +219,16 @@ var FieldState = class {
217
219
  this.validator = this.schema.validation ? compileValidator(this.schema.validation) : null;
218
220
  this.rev.update((n) => n + 1);
219
221
  }
220
- /** Run validation, store and return the error (or null). Hidden fields never error. */
221
- validate() {
222
+ /** Run validation, store and return the error (or null). Hidden / inactive-step fields never error. */
223
+ validate(options) {
222
224
  if (!this.visible.peek()) {
223
225
  this.error.set(null);
224
226
  return null;
225
227
  }
228
+ if (!options?.allSteps && this.stepActive && !this.stepActive.peek()) {
229
+ this.error.set(null);
230
+ return null;
231
+ }
226
232
  let result = null;
227
233
  if (this.required.peek() && isEmpty2(this.value.peek())) {
228
234
  result = requiredMessage(this.schema.validation);
@@ -259,18 +265,24 @@ function collectValues(nodes) {
259
265
  }
260
266
  return out;
261
267
  }
262
- function buildNodes(schemas, scope, initial) {
268
+ function buildNodes(schemas, scope, initial, stepActive) {
263
269
  const nodes = [];
264
270
  const byName = /* @__PURE__ */ new Map();
265
271
  for (const schema of schemas) {
266
272
  let node;
267
273
  if (schema.type === "group") {
268
- node = new GroupNode(schema, scope, asDict(initial[schema.id]));
274
+ node = new GroupNode(schema, scope, asDict(initial[schema.id]), stepActive);
275
+ } else if (schema.type === "step") {
276
+ if (!stepActive)
277
+ throw new Error('Field type "step" must be nested inside a "steps" container');
278
+ node = new StepNode(schema, scope, asDict(initial[schema.id]), stepActive);
269
279
  } else if (schema.type === "collection") {
270
280
  node = new CollectionNode(schema, scope, asArray(initial[schema.id]));
281
+ } else if (schema.type === "steps") {
282
+ node = new StepsNode(schema, scope, asDict(initial[schema.id]));
271
283
  } else {
272
284
  const init = initial[schema.id] ?? schema.defaultValue ?? defaultValueFor(schema.type);
273
- node = new FieldState(schema, init, scope);
285
+ node = new FieldState(schema, init, scope, stepActive);
274
286
  }
275
287
  nodes.push(node);
276
288
  byName.set(schema.id, node);
@@ -288,7 +300,9 @@ function resetNodes(nodes, initial) {
288
300
  if (node.kind === "field") {
289
301
  const init = initial[node.id] ?? node.schema.defaultValue ?? defaultValueFor(node.schema.type);
290
302
  node.reset(init);
291
- } else if (node.kind === "group") {
303
+ } else if (node.kind === "group" || node.kind === "step") {
304
+ node.reset(asDict(initial[node.id]));
305
+ } else if (node.kind === "steps") {
292
306
  node.reset(asDict(initial[node.id]));
293
307
  } else {
294
308
  node.reset(asArray(initial[node.id]));
@@ -306,14 +320,14 @@ var GroupNode = class {
306
320
  enabled;
307
321
  /** The scope a child uses: resolve a name among siblings, else delegate upward. */
308
322
  scope;
309
- constructor(schema, parentScope, initial) {
323
+ constructor(schema, parentScope, initial, stepActive) {
310
324
  this.id = schema.id;
311
325
  this.schema = schema;
312
326
  this.scope = (name) => {
313
327
  const child = this.byName.get(name);
314
328
  return child ? nodeValue(child) : parentScope(name);
315
329
  };
316
- const built = buildNodes(schema.fields ?? [], this.scope, initial);
330
+ const built = buildNodes(schema.fields ?? [], this.scope, initial, stepActive);
317
331
  this.children = built.nodes;
318
332
  this.byName = built.byName;
319
333
  this.value = reactive.computed(() => collectValues(this.children));
@@ -376,6 +390,109 @@ var CollectionNode = class {
376
390
  this.rows.set(this.seedRows(initial));
377
391
  }
378
392
  };
393
+ var StepNode = class {
394
+ kind = "step";
395
+ id;
396
+ schema;
397
+ children;
398
+ byName;
399
+ value;
400
+ visible;
401
+ enabled;
402
+ scope;
403
+ /** True when this step is the active step in its parent wizard. */
404
+ active;
405
+ constructor(schema, parentScope, initial, active) {
406
+ this.id = schema.id;
407
+ this.schema = schema;
408
+ this.active = active;
409
+ this.scope = (name) => {
410
+ const child = this.byName.get(name);
411
+ return child ? nodeValue(child) : parentScope(name);
412
+ };
413
+ const built = buildNodes(schema.fields ?? [], this.scope, initial, active);
414
+ this.children = built.nodes;
415
+ this.byName = built.byName;
416
+ this.value = reactive.computed(() => collectValues(this.children));
417
+ this.visible = reactive.computed(() => evaluateCondition(schema.visibleWhen, parentScope, true));
418
+ this.enabled = reactive.computed(() => evaluateCondition(schema.enabledWhen, parentScope, true));
419
+ }
420
+ reset(initial) {
421
+ resetNodes(this.children, initial);
422
+ }
423
+ };
424
+ var StepsNode = class {
425
+ kind = "steps";
426
+ id;
427
+ schema;
428
+ steps;
429
+ byName;
430
+ value;
431
+ visible;
432
+ enabled;
433
+ currentStep;
434
+ scope;
435
+ constructor(schema, parentScope, initial) {
436
+ this.id = schema.id;
437
+ this.schema = schema;
438
+ this.currentStep = reactive.signal(0);
439
+ this.byName = /* @__PURE__ */ new Map();
440
+ this.scope = (name) => {
441
+ const step = this.byName.get(name);
442
+ return step ? step.value.get() : parentScope(name);
443
+ };
444
+ const stepSchemas = schema.fields ?? [];
445
+ const steps = stepSchemas.map((stepSchema, index) => {
446
+ const active = reactive.computed(() => this.currentStep.get() === index);
447
+ const stepInitial = asDict(initial[stepSchema.id]);
448
+ const step = new StepNode(stepSchema, this.scope, stepInitial, active);
449
+ this.byName.set(stepSchema.id, step);
450
+ return step;
451
+ });
452
+ this.steps = steps;
453
+ this.value = reactive.computed(() => {
454
+ const out = {};
455
+ for (const step of this.steps) out[step.id] = step.value.get();
456
+ return out;
457
+ });
458
+ this.visible = reactive.computed(() => evaluateCondition(schema.visibleWhen, parentScope, true));
459
+ this.enabled = reactive.computed(() => evaluateCondition(schema.enabledWhen, parentScope, true));
460
+ }
461
+ /** Validate every leaf in the step at `index` (defaults to the current step). */
462
+ validateStep(index) {
463
+ const i = index ?? this.currentStep.peek();
464
+ const step = this.steps[i];
465
+ if (!step) return true;
466
+ let ok = true;
467
+ eachLeaf([step], (leaf) => {
468
+ if (leaf.validate() !== null) ok = false;
469
+ });
470
+ return ok;
471
+ }
472
+ /** Advance to the next step after optionally validating the current one. Returns false if blocked. */
473
+ next() {
474
+ const validate = this.schema.validateOnNext !== false;
475
+ if (validate && !this.validateStep()) return false;
476
+ const cur = this.currentStep.peek();
477
+ if (cur < this.steps.length - 1) {
478
+ this.currentStep.set(cur + 1);
479
+ return true;
480
+ }
481
+ return false;
482
+ }
483
+ /** Go back one step (no validation). */
484
+ prev() {
485
+ this.currentStep.update((i) => Math.max(0, i - 1));
486
+ }
487
+ /** Jump to a step by index (does not validate). */
488
+ goTo(index) {
489
+ if (index >= 0 && index < this.steps.length) this.currentStep.set(index);
490
+ }
491
+ reset(initial) {
492
+ this.currentStep.set(0);
493
+ for (const step of this.steps) step.reset(asDict(initial[step.id]));
494
+ }
495
+ };
379
496
  function buildTree(schemas, initial) {
380
497
  let byName;
381
498
  const scope = (name) => {
@@ -389,8 +506,10 @@ function buildTree(schemas, initial) {
389
506
  function eachLeaf(nodes, visit) {
390
507
  for (const node of nodes) {
391
508
  if (node.kind === "field") visit(node);
392
- else if (node.kind === "group") eachLeaf(node.children, visit);
393
- else for (const row of node.items.peek()) eachLeaf(row.group.children, visit);
509
+ else if (node.kind === "group" || node.kind === "step") eachLeaf(node.children, visit);
510
+ else if (node.kind === "steps") {
511
+ for (const step of node.steps) eachLeaf(step.children, visit);
512
+ } else for (const row of node.items.peek()) eachLeaf(row.group.children, visit);
394
513
  }
395
514
  }
396
515
  var defaultRenderer = null;
@@ -493,17 +612,22 @@ var Form = class {
493
612
  }
494
613
  // ---- lifecycle ----------------------------------------------------------
495
614
  /** Validate every (visible) leaf field; returns true when the whole form is valid. */
496
- validate() {
615
+ validate(options) {
616
+ const allSteps = options?.allSteps ?? false;
497
617
  return reactive.untrack(() => {
498
618
  let ok = true;
499
619
  reactive.batch(() => {
500
620
  eachLeaf(this.tree, (leaf) => {
501
- if (leaf.validate() !== null) ok = false;
621
+ if (leaf.validate({ allSteps }) !== null) ok = false;
502
622
  });
503
623
  });
504
624
  return ok;
505
625
  });
506
626
  }
627
+ /** Find the first `steps` container in the field tree (if any). */
628
+ findSteps() {
629
+ return findSteps(this.tree);
630
+ }
507
631
  /**
508
632
  * Run the submission pipeline: validate → transform → send → onSuccess/onError.
509
633
  * Pass an inline `transform` to shape the final payload, e.g.
@@ -514,7 +638,7 @@ var Form = class {
514
638
  * `const res = await form.submit(); res.ok ? res.data : res.error`.
515
639
  */
516
640
  async submit(transform) {
517
- if (!this.validate()) {
641
+ if (!this.validate({ allSteps: true })) {
518
642
  const errors = this.collectErrors();
519
643
  const error = new FormValidationError(errors);
520
644
  this.runErrorHandler(error);
@@ -694,8 +818,10 @@ function collectLeaves(tree) {
694
818
  for (const node of nodes) {
695
819
  const path = prefix ? `${prefix}.${node.id}` : node.id;
696
820
  if (node.kind === "field") out.set(path, node);
697
- else if (node.kind === "group") walk(node.children, path);
698
- else {
821
+ else if (node.kind === "group" || node.kind === "step") walk(node.children, path);
822
+ else if (node.kind === "steps") {
823
+ for (const step of node.steps) walk(step.children, `${path}.${step.id}`);
824
+ } else {
699
825
  node.items.peek().forEach((row, i) => walk(row.group.children, `${path}.${i}`));
700
826
  }
701
827
  }
@@ -708,7 +834,9 @@ function resolveLeaf(tree, rootByName, path) {
708
834
  let node = rootByName.get(parts[0]);
709
835
  for (let i = 1; i < parts.length && node; i++) {
710
836
  const part = parts[i];
711
- if (node instanceof GroupNode) {
837
+ if (node instanceof GroupNode || node instanceof StepNode) {
838
+ node = node.byName.get(part);
839
+ } else if (node instanceof StepsNode) {
712
840
  node = node.byName.get(part);
713
841
  } else if (node instanceof CollectionNode) {
714
842
  node = node.items.peek()[Number(part)]?.group;
@@ -718,6 +846,21 @@ function resolveLeaf(tree, rootByName, path) {
718
846
  }
719
847
  return node && node.kind === "field" ? node : void 0;
720
848
  }
849
+ function findSteps(tree) {
850
+ for (const node of tree) {
851
+ if (node.kind === "steps") return node;
852
+ if (node.kind === "group" || node.kind === "step") {
853
+ const found = findSteps(node.children);
854
+ if (found) return found;
855
+ } else if (node.kind === "collection") {
856
+ for (const row of node.items.peek()) {
857
+ const found = findSteps(row.group.children);
858
+ if (found) return found;
859
+ }
860
+ }
861
+ }
862
+ return void 0;
863
+ }
721
864
 
722
865
  Object.defineProperty(exports, "batch", {
723
866
  enumerable: true,
@@ -748,6 +891,8 @@ exports.FieldState = FieldState;
748
891
  exports.Form = Form;
749
892
  exports.FormValidationError = FormValidationError;
750
893
  exports.GroupNode = GroupNode;
894
+ exports.StepNode = StepNode;
895
+ exports.StepsNode = StepsNode;
751
896
  exports.buildTree = buildTree;
752
897
  exports.compileValidator = compileValidator;
753
898
  exports.defaultValueFor = defaultValueFor;