@formwright/core 0.1.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/dist/index.cjs ADDED
@@ -0,0 +1,764 @@
1
+ 'use strict';
2
+
3
+ var schema = require('@formwright/schema');
4
+
5
+ // 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
+
164
+ // src/conditions.ts
165
+ function isOp(cond, key) {
166
+ return typeof cond === "object" && cond !== null && key in cond;
167
+ }
168
+ function toNumber(v) {
169
+ return typeof v === "number" ? v : Number(v);
170
+ }
171
+ function evalNode(cond, get) {
172
+ if (cond === null || typeof cond !== "object") return cond;
173
+ if (Array.isArray(cond)) return cond;
174
+ if (isOp(cond, "var")) return get(String(cond.var));
175
+ if (isOp(cond, "==")) {
176
+ const [a, b] = cond["=="];
177
+ return evalNode(a, get) === evalNode(b, get);
178
+ }
179
+ if (isOp(cond, "!=")) {
180
+ const [a, b] = cond["!="];
181
+ return evalNode(a, get) !== evalNode(b, get);
182
+ }
183
+ if (isOp(cond, ">")) {
184
+ const [a, b] = cond[">"];
185
+ return toNumber(evalNode(a, get)) > toNumber(evalNode(b, get));
186
+ }
187
+ if (isOp(cond, ">=")) {
188
+ const [a, b] = cond[">="];
189
+ return toNumber(evalNode(a, get)) >= toNumber(evalNode(b, get));
190
+ }
191
+ if (isOp(cond, "<")) {
192
+ const [a, b] = cond["<"];
193
+ return toNumber(evalNode(a, get)) < toNumber(evalNode(b, get));
194
+ }
195
+ if (isOp(cond, "<=")) {
196
+ const [a, b] = cond["<="];
197
+ return toNumber(evalNode(a, get)) <= toNumber(evalNode(b, get));
198
+ }
199
+ if (isOp(cond, "in")) {
200
+ const [a, b] = cond["in"];
201
+ const needle = evalNode(a, get);
202
+ const haystack = evalNode(b, get);
203
+ if (Array.isArray(haystack)) return haystack.includes(needle);
204
+ if (typeof haystack === "string") return haystack.includes(String(needle));
205
+ return false;
206
+ }
207
+ if (isOp(cond, "not")) {
208
+ return !truthy(evalNode(cond.not, get));
209
+ }
210
+ if (isOp(cond, "and")) {
211
+ return cond.and.every((c) => truthy(evalNode(c, get)));
212
+ }
213
+ if (isOp(cond, "or")) {
214
+ return cond.or.some((c) => truthy(evalNode(c, get)));
215
+ }
216
+ return false;
217
+ }
218
+ function truthy(v) {
219
+ return Boolean(v);
220
+ }
221
+ function evaluateCondition(cond, get, fallback = true) {
222
+ if (cond === void 0) return fallback;
223
+ return truthy(evalNode(cond, get));
224
+ }
225
+ function referencedFields(cond) {
226
+ const ids = /* @__PURE__ */ new Set();
227
+ const walk = (c) => {
228
+ if (c === null || typeof c !== "object") return;
229
+ if (Array.isArray(c)) {
230
+ c.forEach(walk);
231
+ return;
232
+ }
233
+ const rec = c;
234
+ if ("var" in rec) {
235
+ ids.add(String(rec["var"]));
236
+ return;
237
+ }
238
+ for (const v of Object.values(rec)) walk(v);
239
+ };
240
+ if (cond !== void 0) walk(cond);
241
+ return [...ids];
242
+ }
243
+
244
+ // src/validation.ts
245
+ var EMAIL = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
246
+ var URL = /^https?:\/\/[^\s]+$/;
247
+ var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
248
+ function isEmpty(value) {
249
+ return value === void 0 || value === null || value === "";
250
+ }
251
+ function compileValidator(schema) {
252
+ return (value) => {
253
+ const msg = (fallback) => typeof schema.message === "string" ? schema.message : fallback;
254
+ if (isEmpty(value)) {
255
+ return schema.required ? msg("This field is required") : null;
256
+ }
257
+ if (schema.kind === "string" || schema.format) {
258
+ const str = String(value);
259
+ if (schema.minLength !== void 0 && str.length < schema.minLength) {
260
+ return msg(`Must be at least ${schema.minLength} characters`);
261
+ }
262
+ if (schema.maxLength !== void 0 && str.length > schema.maxLength) {
263
+ return msg(`Must be at most ${schema.maxLength} characters`);
264
+ }
265
+ if (schema.pattern !== void 0 && !new RegExp(schema.pattern).test(str)) {
266
+ return msg("Invalid format");
267
+ }
268
+ if (schema.format === "email" && !EMAIL.test(str)) return msg("Enter a valid email");
269
+ if (schema.format === "url" && !URL.test(str)) return msg("Enter a valid URL");
270
+ if (schema.format === "uuid" && !UUID.test(str)) return msg("Enter a valid UUID");
271
+ }
272
+ if (schema.kind === "number") {
273
+ const num = Number(value);
274
+ if (Number.isNaN(num)) return msg("Must be a number");
275
+ if (schema.min !== void 0 && num < schema.min) return msg(`Must be \u2265 ${schema.min}`);
276
+ if (schema.max !== void 0 && num > schema.max) return msg(`Must be \u2264 ${schema.max}`);
277
+ }
278
+ return null;
279
+ };
280
+ }
281
+
282
+ // src/providers.ts
283
+ function isProviderRef(value) {
284
+ return typeof value === "object" && value !== null && ("$t" in value || "$query" in value || "$theme" in value);
285
+ }
286
+ function resolve(value, providers) {
287
+ if (value === void 0) return void 0;
288
+ if (!isProviderRef(value)) return value;
289
+ if ("$t" in value) {
290
+ const i18n = providers?.i18n;
291
+ return i18n ? i18n.t(value.$t, value.args) : value.$t;
292
+ }
293
+ if ("$theme" in value) {
294
+ const theme = providers?.theme;
295
+ return theme ? theme.token(value.$theme) : value.$theme;
296
+ }
297
+ return void 0;
298
+ }
299
+ function resolveQuery(value, providers) {
300
+ if (!isProviderRef(value) || !("$query" in value)) return null;
301
+ const provider = providers?.query;
302
+ if (!provider) return null;
303
+ const spec = value.$query;
304
+ if (typeof spec === "string") return provider.query(spec);
305
+ const [key, params] = spec;
306
+ return provider.query(key, params);
307
+ }
308
+
309
+ // src/model.ts
310
+ function defaultValueFor(type) {
311
+ switch (type) {
312
+ case "checkbox":
313
+ return false;
314
+ case "number":
315
+ return void 0;
316
+ default:
317
+ return "";
318
+ }
319
+ }
320
+ var FieldState = class {
321
+ /** Discriminant for the {@link FieldNode} union (leaf vs group/collection). */
322
+ kind = "field";
323
+ id;
324
+ schema;
325
+ value;
326
+ error;
327
+ touched;
328
+ visible;
329
+ enabled;
330
+ required;
331
+ validator;
332
+ constructor(schema, initial, getValue) {
333
+ this.id = schema.id;
334
+ this.schema = schema;
335
+ this.value = signal(initial);
336
+ this.error = signal(null);
337
+ this.touched = signal(false);
338
+ this.validator = schema.validation ? compileValidator(schema.validation) : null;
339
+ this.visible = computed(() => evaluateCondition(schema.visibleWhen, getValue, true));
340
+ this.enabled = computed(() => evaluateCondition(schema.enabledWhen, getValue, true));
341
+ this.required = computed(() => {
342
+ if (schema.requiredWhen !== void 0) {
343
+ return evaluateCondition(schema.requiredWhen, getValue, false);
344
+ }
345
+ return schema.validation?.required ?? false;
346
+ });
347
+ }
348
+ /** Run validation, store and return the error (or null). Hidden fields never error. */
349
+ validate() {
350
+ if (!this.visible.peek()) {
351
+ this.error.set(null);
352
+ return null;
353
+ }
354
+ let result = null;
355
+ if (this.required.peek() && isEmpty2(this.value.peek())) {
356
+ result = "This field is required";
357
+ } else if (this.validator) {
358
+ result = this.validator(this.value.peek());
359
+ }
360
+ this.error.set(result);
361
+ return result;
362
+ }
363
+ reset(value) {
364
+ this.value.set(value);
365
+ this.error.set(null);
366
+ this.touched.set(false);
367
+ }
368
+ };
369
+ function isEmpty2(value) {
370
+ return value === void 0 || value === null || value === "";
371
+ }
372
+
373
+ // src/nodes.ts
374
+ function nodeValue(node) {
375
+ return node.value.get();
376
+ }
377
+ function collectValues(nodes) {
378
+ const out = {};
379
+ for (const node of nodes) out[node.id] = nodeValue(node);
380
+ return out;
381
+ }
382
+ function buildNodes(schemas, scope, initial) {
383
+ const nodes = [];
384
+ const byName = /* @__PURE__ */ new Map();
385
+ for (const schema of schemas) {
386
+ let node;
387
+ if (schema.type === "group") {
388
+ node = new GroupNode(schema, scope, asDict(initial[schema.id]));
389
+ } else if (schema.type === "collection") {
390
+ node = new CollectionNode(schema, scope, asArray(initial[schema.id]));
391
+ } else {
392
+ const init = initial[schema.id] ?? schema.defaultValue ?? defaultValueFor(schema.type);
393
+ node = new FieldState(schema, init, scope);
394
+ }
395
+ nodes.push(node);
396
+ byName.set(schema.id, node);
397
+ }
398
+ return { nodes, byName };
399
+ }
400
+ function asDict(v) {
401
+ return v !== null && typeof v === "object" && !Array.isArray(v) ? v : {};
402
+ }
403
+ function asArray(v) {
404
+ return Array.isArray(v) ? v : [];
405
+ }
406
+ function resetNodes(nodes, initial) {
407
+ for (const node of nodes) {
408
+ if (node.kind === "field") {
409
+ const init = initial[node.id] ?? node.schema.defaultValue ?? defaultValueFor(node.schema.type);
410
+ node.reset(init);
411
+ } else if (node.kind === "group") {
412
+ node.reset(asDict(initial[node.id]));
413
+ } else {
414
+ node.reset(asArray(initial[node.id]));
415
+ }
416
+ }
417
+ }
418
+ var GroupNode = class {
419
+ kind = "group";
420
+ id;
421
+ schema;
422
+ children;
423
+ byName;
424
+ value;
425
+ visible;
426
+ enabled;
427
+ /** The scope a child uses: resolve a name among siblings, else delegate upward. */
428
+ scope;
429
+ constructor(schema, parentScope, initial) {
430
+ this.id = schema.id;
431
+ this.schema = schema;
432
+ this.scope = (name) => {
433
+ const child = this.byName.get(name);
434
+ return child ? nodeValue(child) : parentScope(name);
435
+ };
436
+ const built = buildNodes(schema.fields ?? [], this.scope, initial);
437
+ this.children = built.nodes;
438
+ 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));
442
+ }
443
+ reset(initial) {
444
+ resetNodes(this.children, initial);
445
+ }
446
+ };
447
+ var CollectionNode = class {
448
+ kind = "collection";
449
+ id;
450
+ schema;
451
+ value;
452
+ visible;
453
+ enabled;
454
+ rows;
455
+ parentScope;
456
+ itemSchema;
457
+ seq = 0;
458
+ constructor(schema, parentScope, initial) {
459
+ this.id = schema.id;
460
+ this.schema = schema;
461
+ this.parentScope = parentScope;
462
+ this.itemSchema = { id: schema.id, type: "group", fields: schema.fields ?? [] };
463
+ 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));
468
+ }
469
+ /** Reactive list of rows (subscribes the caller to add/remove). */
470
+ get items() {
471
+ return this.rows;
472
+ }
473
+ makeItem(initial) {
474
+ return { key: this.seq++, group: new GroupNode(this.itemSchema, this.parentScope, initial) };
475
+ }
476
+ seedRows(initial) {
477
+ const rows = initial.map((row) => this.makeItem(row));
478
+ const min = this.schema.minItems ?? 0;
479
+ while (rows.length < min) rows.push(this.makeItem({}));
480
+ return rows;
481
+ }
482
+ /** Append a new empty row, unless `maxItems` is reached. */
483
+ add() {
484
+ const max = this.schema.maxItems;
485
+ if (max !== void 0 && this.rows.peek().length >= max) return;
486
+ this.rows.update((rows) => [...rows, this.makeItem({})]);
487
+ }
488
+ /** Remove the row at `index`, unless `minItems` would be violated. */
489
+ removeAt(index) {
490
+ const min = this.schema.minItems ?? 0;
491
+ if (this.rows.peek().length <= min) return;
492
+ this.rows.update((rows) => rows.filter((_, i) => i !== index));
493
+ }
494
+ reset(initial) {
495
+ this.seq = 0;
496
+ this.rows.set(this.seedRows(initial));
497
+ }
498
+ };
499
+ function buildTree(schemas, initial) {
500
+ let byName;
501
+ const scope = (name) => {
502
+ const node = byName.get(name);
503
+ return node ? nodeValue(node) : void 0;
504
+ };
505
+ const built = buildNodes(schemas, scope, initial);
506
+ byName = built.byName;
507
+ return { nodes: built.nodes, byName, scope };
508
+ }
509
+ function eachLeaf(nodes, visit) {
510
+ for (const node of nodes) {
511
+ if (node.kind === "field") visit(node);
512
+ else if (node.kind === "group") eachLeaf(node.children, visit);
513
+ else for (const row of node.items.peek()) eachLeaf(row.group.children, visit);
514
+ }
515
+ }
516
+ var defaultRenderer = null;
517
+ function setDefaultRenderer(renderer) {
518
+ defaultRenderer = renderer;
519
+ }
520
+ var Form = class {
521
+ schema;
522
+ options;
523
+ /** Top-level field tree (leaf fields, groups, and collections, in order). */
524
+ tree;
525
+ order;
526
+ /** Reactive snapshot of all field values (nested for groups/collections). */
527
+ values;
528
+ /** True when the current values differ from the initial values. */
529
+ isDirty;
530
+ /** True when no visible field currently has an error. */
531
+ isValid;
532
+ submitting = signal(false);
533
+ initialValues;
534
+ initialSnapshot;
535
+ rootByName;
536
+ listeners = /* @__PURE__ */ new Map();
537
+ disposeRenderer = null;
538
+ constructor(schema$1, initialValues = {}, options = {}) {
539
+ this.schema = schema.parseSchema(schema$1);
540
+ this.options = options;
541
+ this.initialValues = initialValues;
542
+ const tree = buildTree(this.schema.fields, initialValues);
543
+ this.tree = tree.nodes;
544
+ this.rootByName = tree.byName;
545
+ this.order = this.schema.fields.map((f) => f.id);
546
+ this.values = computed(() => collectTree(this.tree));
547
+ this.initialSnapshot = JSON.stringify(untrack(() => this.values.peek()));
548
+ this.isDirty = computed(() => JSON.stringify(this.values.get()) !== this.initialSnapshot);
549
+ this.isValid = computed(() => {
550
+ let valid = true;
551
+ eachLeaf(this.tree, (leaf) => {
552
+ if (leaf.visible.get() && leaf.error.get() !== null) valid = false;
553
+ });
554
+ return valid;
555
+ });
556
+ }
557
+ // ---- value access -------------------------------------------------------
558
+ /** All leaf fields keyed by dotted path (e.g. `items.name`, `contacts.0.email`). */
559
+ get fields() {
560
+ return collectLeaves(this.tree);
561
+ }
562
+ /** Resolve a leaf field by dotted path. Top-level ids work directly. */
563
+ field(path) {
564
+ return resolveLeaf(this.tree, this.rootByName, path);
565
+ }
566
+ getValue(path) {
567
+ return this.field(path)?.value.peek();
568
+ }
569
+ setValue(path, value) {
570
+ const field = this.field(path);
571
+ if (field) this.setFieldValue(field, value);
572
+ }
573
+ /** Apply a value to a specific leaf node (used by the renderer). */
574
+ setFieldValue(field, value) {
575
+ field.value.set(value);
576
+ field.touched.set(true);
577
+ if (field.error.peek() !== null) field.validate();
578
+ this.emit("change", { id: field.id, value });
579
+ }
580
+ setError(id, error) {
581
+ this.field(id)?.error.set(error);
582
+ }
583
+ setErrors(errors) {
584
+ batch(() => {
585
+ for (const [id, error] of Object.entries(errors)) this.setError(id, error);
586
+ });
587
+ }
588
+ get isSubmitting() {
589
+ return this.submitting;
590
+ }
591
+ // ---- lifecycle ----------------------------------------------------------
592
+ /** Validate every (visible) leaf field; returns true when the whole form is valid. */
593
+ validate() {
594
+ return untrack(() => {
595
+ let ok = true;
596
+ batch(() => {
597
+ eachLeaf(this.tree, (leaf) => {
598
+ if (leaf.validate() !== null) ok = false;
599
+ });
600
+ });
601
+ return ok;
602
+ });
603
+ }
604
+ /** Run the submission pipeline: validate → transform → send → onSuccess/onError. */
605
+ async submit() {
606
+ if (!this.validate()) {
607
+ const error = new FormValidationError(this.collectErrors());
608
+ this.runErrorHandler(error);
609
+ this.emit("error", error);
610
+ throw error;
611
+ }
612
+ this.submitting.set(true);
613
+ const values = untrack(() => this.values.peek());
614
+ const payload = this.applyTransform(values);
615
+ this.emit("submit", payload);
616
+ try {
617
+ const result = await this.send(payload);
618
+ this.runSuccessHandler(result);
619
+ this.emit("success", result);
620
+ return result;
621
+ } catch (error) {
622
+ this.runErrorHandler(error);
623
+ this.emit("error", error);
624
+ throw error;
625
+ } finally {
626
+ this.submitting.set(false);
627
+ }
628
+ }
629
+ reset(values = this.initialValues) {
630
+ batch(() => {
631
+ resetNodes(this.tree, values);
632
+ });
633
+ }
634
+ /** Mount into a host element using the given renderer (or the registered default). */
635
+ mount(host, renderer = defaultRenderer) {
636
+ if (!renderer) {
637
+ throw new Error(
638
+ "No renderer available. Import '@formwright/dom' or pass a renderer to Form.mount()."
639
+ );
640
+ }
641
+ this.disposeRenderer?.();
642
+ this.disposeRenderer = renderer.mount(this, host);
643
+ return this.disposeRenderer;
644
+ }
645
+ destroy() {
646
+ this.disposeRenderer?.();
647
+ this.disposeRenderer = null;
648
+ this.listeners.clear();
649
+ }
650
+ // ---- events -------------------------------------------------------------
651
+ on(event, listener) {
652
+ let set = this.listeners.get(event);
653
+ if (!set) {
654
+ set = /* @__PURE__ */ new Set();
655
+ this.listeners.set(event, set);
656
+ }
657
+ set.add(listener);
658
+ return () => set.delete(listener);
659
+ }
660
+ emit(event, payload) {
661
+ const set = this.listeners.get(event);
662
+ if (set) for (const listener of [...set]) listener(payload);
663
+ }
664
+ // ---- internals ----------------------------------------------------------
665
+ collectErrors() {
666
+ const errors = {};
667
+ for (const [path, field] of this.fields) errors[path] = field.error.peek();
668
+ return errors;
669
+ }
670
+ applyTransform(values) {
671
+ const name = this.schema.submit?.transform;
672
+ const transform = name ? this.options.transforms?.[name] : void 0;
673
+ return transform ? transform(values, this) : values;
674
+ }
675
+ async send(payload) {
676
+ if (this.options.send) return this.options.send(payload, this);
677
+ const endpoint = this.schema.submit?.endpoint;
678
+ if (!endpoint) return payload;
679
+ const init = {
680
+ method: endpoint.method,
681
+ headers: { "content-type": "application/json" }
682
+ };
683
+ if (endpoint.method !== "GET") init.body = JSON.stringify(payload);
684
+ const response = await fetch(endpoint.url, init);
685
+ if (!response.ok) throw new Error(`Request failed: ${response.status}`);
686
+ const text = await response.text();
687
+ return text ? JSON.parse(text) : void 0;
688
+ }
689
+ runSuccessHandler(result) {
690
+ const name = this.schema.submit?.onSuccess;
691
+ const handler = name ? this.options.handlers?.[name] : void 0;
692
+ handler?.(result, this);
693
+ }
694
+ runErrorHandler(error) {
695
+ const name = this.schema.submit?.onError;
696
+ const handler = name ? this.options.handlers?.[name] : void 0;
697
+ handler?.(error, this);
698
+ }
699
+ };
700
+ var FormValidationError = class extends Error {
701
+ errors;
702
+ constructor(errors) {
703
+ super("Form validation failed");
704
+ this.name = "FormValidationError";
705
+ this.errors = errors;
706
+ }
707
+ };
708
+ function collectTree(tree) {
709
+ return collectValues(tree);
710
+ }
711
+ function collectLeaves(tree) {
712
+ const out = /* @__PURE__ */ new Map();
713
+ const walk = (nodes, prefix) => {
714
+ for (const node of nodes) {
715
+ const path = prefix ? `${prefix}.${node.id}` : node.id;
716
+ if (node.kind === "field") out.set(path, node);
717
+ else if (node.kind === "group") walk(node.children, path);
718
+ else {
719
+ node.items.peek().forEach((row, i) => walk(row.group.children, `${path}.${i}`));
720
+ }
721
+ }
722
+ };
723
+ walk(tree, "");
724
+ return out;
725
+ }
726
+ function resolveLeaf(tree, rootByName, path) {
727
+ const parts = path.split(".");
728
+ let node = rootByName.get(parts[0]);
729
+ for (let i = 1; i < parts.length && node; i++) {
730
+ const part = parts[i];
731
+ if (node instanceof GroupNode) {
732
+ node = node.byName.get(part);
733
+ } else if (node instanceof CollectionNode) {
734
+ node = node.items.peek()[Number(part)]?.group;
735
+ } else {
736
+ return void 0;
737
+ }
738
+ }
739
+ return node && node.kind === "field" ? node : void 0;
740
+ }
741
+
742
+ exports.CollectionNode = CollectionNode;
743
+ exports.FieldState = FieldState;
744
+ exports.Form = Form;
745
+ exports.FormValidationError = FormValidationError;
746
+ exports.GroupNode = GroupNode;
747
+ exports.batch = batch;
748
+ exports.buildTree = buildTree;
749
+ exports.compileValidator = compileValidator;
750
+ exports.computed = computed;
751
+ exports.defaultValueFor = defaultValueFor;
752
+ exports.eachLeaf = eachLeaf;
753
+ exports.effect = effect;
754
+ exports.evaluateCondition = evaluateCondition;
755
+ exports.isProviderRef = isProviderRef;
756
+ exports.isTracking = isTracking;
757
+ exports.referencedFields = referencedFields;
758
+ exports.resolve = resolve;
759
+ exports.resolveQuery = resolveQuery;
760
+ exports.setDefaultRenderer = setDefaultRenderer;
761
+ exports.signal = signal;
762
+ exports.untrack = untrack;
763
+ //# sourceMappingURL=index.cjs.map
764
+ //# sourceMappingURL=index.cjs.map