@holo-js/forms 0.1.4 → 0.1.5

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,833 @@
1
+ import {
2
+ clearSensitiveInputValues,
3
+ normalizeFailureErrors,
4
+ normalizeFailureInput,
5
+ normalizeStatus,
6
+ sanitizeFlashedInput
7
+ } from "../chunk-WJOAU4V2.mjs";
8
+ import {
9
+ createMissingSecurityPackageError,
10
+ isMissingOptionalPackageError,
11
+ parseCookieHeader
12
+ } from "../chunk-GH4IN2OV.mjs";
13
+ import {
14
+ FormContractError
15
+ } from "../chunk-OFPH5LBY.mjs";
16
+
17
+ // src/internal/client.ts
18
+ import {
19
+ createErrorBag,
20
+ safeParse
21
+ } from "@holo-js/validation";
22
+
23
+ // src/client-security.ts
24
+ var securityClientModulePromise;
25
+ async function importSecurityClientModule() {
26
+ if (typeof process !== "undefined" && process.env && process.env.VITEST) {
27
+ return await import(
28
+ /* @vite-ignore */
29
+ "@holo-js/security/client"
30
+ );
31
+ }
32
+ return await import("@holo-js/security/client");
33
+ }
34
+ async function loadSecurityClientModule() {
35
+ const runtime = globalThis;
36
+ if (runtime.__holoFormsSecurityClientModule__) {
37
+ return runtime.__holoFormsSecurityClientModule__;
38
+ }
39
+ securityClientModulePromise ??= (runtime.__holoFormsSecurityClientImport__ ? runtime.__holoFormsSecurityClientImport__() : importSecurityClientModule()).then((module) => module).catch(async (error) => {
40
+ securityClientModulePromise = void 0;
41
+ if (isMissingOptionalPackageError(error)) {
42
+ throw createMissingSecurityPackageError();
43
+ }
44
+ throw error;
45
+ });
46
+ return await securityClientModulePromise;
47
+ }
48
+ async function getClientCsrfField() {
49
+ const runtime = globalThis;
50
+ if (!runtime.document || typeof runtime.document.cookie !== "string") {
51
+ return void 0;
52
+ }
53
+ let security;
54
+ try {
55
+ security = await loadSecurityClientModule();
56
+ } catch (error) {
57
+ if (error instanceof FormContractError) {
58
+ return void 0;
59
+ }
60
+ throw error;
61
+ }
62
+ const config = security.getSecurityClientConfig().csrf;
63
+ const value = parseCookieHeader(runtime.document.cookie)[config.cookie];
64
+ if (!value) {
65
+ return void 0;
66
+ }
67
+ return Object.freeze({
68
+ name: config.field,
69
+ value
70
+ });
71
+ }
72
+
73
+ // src/internal/validation-exception.ts
74
+ import {
75
+ isValidationException,
76
+ validationInternals
77
+ } from "@holo-js/validation";
78
+ function isPlainObject(value) {
79
+ return !!value && typeof value === "object" && !Array.isArray(value);
80
+ }
81
+ function isErrorRecord(value) {
82
+ return isPlainObject(value) && Object.values(value).every((messages) => Array.isArray(messages) && messages.every((message) => typeof message === "string"));
83
+ }
84
+ function isFormFailurePayload(value) {
85
+ return isPlainObject(value) && value.ok === false && typeof value.status === "number" && value.valid === false && isPlainObject(value.values) && isErrorRecord(value.errors);
86
+ }
87
+ function readNestedPayload(value, path) {
88
+ let cursor = value;
89
+ for (const key of path) {
90
+ if (!isPlainObject(cursor)) {
91
+ return void 0;
92
+ }
93
+ cursor = cursor[key];
94
+ }
95
+ return cursor;
96
+ }
97
+ function formFailurePayloadFromError(error) {
98
+ const candidates = [
99
+ error,
100
+ readNestedPayload(error, ["data"]),
101
+ readNestedPayload(error, ["response", "_data"]),
102
+ readNestedPayload(error, ["response", "data"])
103
+ ];
104
+ return candidates.find((candidate) => isFormFailurePayload(candidate));
105
+ }
106
+ function validationExceptionToFailure(error, fallbackValues = {}) {
107
+ const payload = isValidationException(error) ? error.toJSON() : validationInternals.parseValidationExceptionDigest(error);
108
+ if (payload) {
109
+ return {
110
+ ok: false,
111
+ status: payload.status,
112
+ submitted: true,
113
+ valid: false,
114
+ values: payload.values && Object.keys(payload.values).length > 0 ? payload.values : fallbackValues,
115
+ errors: payload.errors,
116
+ ...typeof payload.retryAfterSeconds === "number" ? { retryAfterSeconds: payload.retryAfterSeconds } : {},
117
+ ...typeof payload.retryAt === "string" ? { retryAt: payload.retryAt } : {}
118
+ };
119
+ }
120
+ const formFailurePayload = formFailurePayloadFromError(error);
121
+ if (formFailurePayload) {
122
+ return {
123
+ ok: false,
124
+ status: formFailurePayload.status,
125
+ submitted: true,
126
+ valid: false,
127
+ values: Object.keys(formFailurePayload.values).length > 0 ? formFailurePayload.values : fallbackValues,
128
+ errors: formFailurePayload.errors,
129
+ ...typeof formFailurePayload.retryAfterSeconds === "number" ? { retryAfterSeconds: formFailurePayload.retryAfterSeconds } : {},
130
+ ...typeof formFailurePayload.retryAt === "string" ? { retryAt: formFailurePayload.retryAt } : {}
131
+ };
132
+ }
133
+ return void 0;
134
+ }
135
+
136
+ // src/internal/client.ts
137
+ var browserFormSubmitSymbol = /* @__PURE__ */ Symbol("browserFormSubmit");
138
+ function isBrowserFormSubmitter(value) {
139
+ return browserFormSubmitSymbol in value;
140
+ }
141
+ var transportFailureMessage = "Unable to submit the form right now. Please try again.";
142
+ function serializeSubmissionState(valid, values, errors, schemaDefinition) {
143
+ return Object.freeze({
144
+ valid,
145
+ submitted: true,
146
+ values: sanitizeFlashedInput(values, schemaDefinition),
147
+ errors: errors.flatten()
148
+ });
149
+ }
150
+ function createSubmission(valid, values, errors, failureStatus = 422, schemaDefinition) {
151
+ const normalizedFailureStatus = normalizeStatus(failureStatus, 422);
152
+ const serialize = () => serializeSubmissionState(valid, values, errors, schemaDefinition);
153
+ const failure = (input) => {
154
+ const normalized = normalizeFailureInput(input, normalizedFailureStatus);
155
+ return {
156
+ ok: false,
157
+ status: normalized.status,
158
+ valid: false,
159
+ values: sanitizeFlashedInput(values, schemaDefinition),
160
+ errors: normalizeFailureErrors(errors.flatten(), normalized.errors)
161
+ };
162
+ };
163
+ const success = (payload, status) => ({
164
+ ok: true,
165
+ status: normalizeStatus(status, 200),
166
+ data: payload
167
+ });
168
+ if (valid) {
169
+ const data = values;
170
+ return Object.freeze({
171
+ valid: true,
172
+ submitted: true,
173
+ data,
174
+ values: data,
175
+ errors,
176
+ serialize,
177
+ success,
178
+ fail(input) {
179
+ return failure(input);
180
+ }
181
+ });
182
+ }
183
+ return Object.freeze({
184
+ valid: false,
185
+ submitted: true,
186
+ data: void 0,
187
+ values,
188
+ errors,
189
+ serialize,
190
+ success,
191
+ fail(input) {
192
+ return failure(input);
193
+ }
194
+ });
195
+ }
196
+ function createSuccessfulSubmission(schemaDefinition, data) {
197
+ return createSubmission(true, data, createErrorBag(), 422, schemaDefinition);
198
+ }
199
+ function createFailedSubmission(schemaDefinition, values, flattenedErrors, status = 422) {
200
+ void schemaDefinition;
201
+ return createSubmission(
202
+ false,
203
+ values,
204
+ createErrorBag(flattenedErrors),
205
+ normalizeStatus(status, 422),
206
+ schemaDefinition
207
+ );
208
+ }
209
+ function createTransportFailure(values, status = 500) {
210
+ return {
211
+ ok: false,
212
+ status: normalizeStatus(status, 500),
213
+ submitted: true,
214
+ valid: false,
215
+ values,
216
+ errors: {
217
+ _root: [transportFailureMessage]
218
+ }
219
+ };
220
+ }
221
+ async function validateClientValues(values, schemaDefinition) {
222
+ const result = await safeParse(
223
+ values,
224
+ schemaDefinition
225
+ );
226
+ if (result.valid) {
227
+ return createSuccessfulSubmission(schemaDefinition, result.data);
228
+ }
229
+ return createFailedSubmission(
230
+ schemaDefinition,
231
+ result.values,
232
+ result.errors.flatten()
233
+ );
234
+ }
235
+ function isPlainObject2(value) {
236
+ return !!value && typeof value === "object" && !Array.isArray(value) && !(value instanceof Date) && !(value instanceof Blob);
237
+ }
238
+ function isSchemaFieldLike(value) {
239
+ return !!value && typeof value === "object" && value.kind === "field" && !!value.definition && typeof value.definition === "object";
240
+ }
241
+ function areEqual(left, right) {
242
+ if (Object.is(left, right)) {
243
+ return true;
244
+ }
245
+ if (left instanceof Date && right instanceof Date) {
246
+ return left.getTime() === right.getTime();
247
+ }
248
+ if (left instanceof Blob || right instanceof Blob) {
249
+ return false;
250
+ }
251
+ if (Array.isArray(left) && Array.isArray(right)) {
252
+ return left.length === right.length && left.every((value, index) => areEqual(value, right[index]));
253
+ }
254
+ if (isPlainObject2(left) && isPlainObject2(right)) {
255
+ const leftKeys = Object.keys(left);
256
+ const rightKeys = Object.keys(right);
257
+ return leftKeys.length === rightKeys.length && leftKeys.every((key) => key in right && areEqual(left[key], right[key]));
258
+ }
259
+ return false;
260
+ }
261
+ function cloneValue(value) {
262
+ if (Array.isArray(value)) {
263
+ return value.map((item) => cloneValue(item));
264
+ }
265
+ if (value instanceof Date) {
266
+ return new Date(value.getTime());
267
+ }
268
+ if (isPlainObject2(value)) {
269
+ return Object.fromEntries(
270
+ Object.entries(value).map(([key, entry]) => [key, cloneValue(entry)])
271
+ );
272
+ }
273
+ return value;
274
+ }
275
+ function isLeafValue(value) {
276
+ return Array.isArray(value) || value instanceof Date || value instanceof Blob || isSchemaFieldLike(value) || !isPlainObject2(value);
277
+ }
278
+ function normalizeObject(value) {
279
+ return isPlainObject2(value) ? cloneValue(value) : {};
280
+ }
281
+ function mergeValues(base, override) {
282
+ if (!override || !isPlainObject2(override)) {
283
+ return cloneValue(base);
284
+ }
285
+ const output = cloneValue(base);
286
+ for (const [key, value] of Object.entries(override)) {
287
+ if (typeof value === "undefined" && key in output) {
288
+ continue;
289
+ }
290
+ if (isPlainObject2(value) && isPlainObject2(output[key])) {
291
+ output[key] = mergeValues(output[key], value);
292
+ continue;
293
+ }
294
+ output[key] = cloneValue(value);
295
+ }
296
+ return output;
297
+ }
298
+ function splitPath(path) {
299
+ return path.split(".").map((part) => part.trim()).filter(Boolean);
300
+ }
301
+ function getValueAtPath(root, path) {
302
+ let cursor = root;
303
+ for (const part of splitPath(path)) {
304
+ if (!isPlainObject2(cursor) && !Array.isArray(cursor)) {
305
+ return void 0;
306
+ }
307
+ cursor = cursor[part];
308
+ }
309
+ return cursor;
310
+ }
311
+ function setValueAtPath(root, path, value) {
312
+ const parts = splitPath(path);
313
+ let cursor = root;
314
+ for (const [index, part] of parts.entries()) {
315
+ const last = index === parts.length - 1;
316
+ const nextPart = parts[index + 1];
317
+ if (Array.isArray(cursor)) {
318
+ const offset = Number(part);
319
+ if (!Number.isInteger(offset) || offset < 0) {
320
+ return;
321
+ }
322
+ if (last) {
323
+ cursor[offset] = value;
324
+ return;
325
+ }
326
+ const existing2 = cursor[offset];
327
+ if (!isPlainObject2(existing2) && !Array.isArray(existing2)) {
328
+ cursor[offset] = /^\d+$/.test(nextPart) ? [] : {};
329
+ }
330
+ cursor = cursor[offset];
331
+ continue;
332
+ }
333
+ if (last) {
334
+ cursor[part] = value;
335
+ return;
336
+ }
337
+ const existing = cursor[part];
338
+ if (!isPlainObject2(existing) && !Array.isArray(existing)) {
339
+ cursor[part] = /^\d+$/.test(nextPart) ? [] : {};
340
+ }
341
+ cursor = cursor[part];
342
+ }
343
+ }
344
+ function flattenLeafPaths(value, prefix = "") {
345
+ if (isLeafValue(value)) {
346
+ return [prefix].filter(Boolean);
347
+ }
348
+ return Object.entries(value).flatMap(([key, nested]) => {
349
+ const next = prefix ? `${prefix}.${key}` : key;
350
+ return flattenLeafPaths(nested, next);
351
+ });
352
+ }
353
+ function buildFormData(value, path = "", formData = new FormData()) {
354
+ if (typeof value === "undefined") {
355
+ return formData;
356
+ }
357
+ if (value instanceof Date) {
358
+ formData.append(path, value.toISOString());
359
+ return formData;
360
+ }
361
+ if (value instanceof Blob) {
362
+ formData.append(path, value);
363
+ return formData;
364
+ }
365
+ if (Array.isArray(value)) {
366
+ for (const item of value) {
367
+ buildFormData(item, `${path}[]`, formData);
368
+ }
369
+ return formData;
370
+ }
371
+ if (isPlainObject2(value)) {
372
+ for (const [key, nested] of Object.entries(value)) {
373
+ const next = path ? `${path}.${key}` : key;
374
+ buildFormData(nested, next, formData);
375
+ }
376
+ return formData;
377
+ }
378
+ formData.append(path, String(value));
379
+ return formData;
380
+ }
381
+ function getActiveElementBrowserForm() {
382
+ const activeElement = globalThis.document?.activeElement;
383
+ return activeElement?.form ?? void 0;
384
+ }
385
+ function getActiveBrowserFormData(form) {
386
+ if (!form) {
387
+ return void 0;
388
+ }
389
+ try {
390
+ return Reflect.construct(FormData, [form]);
391
+ } catch {
392
+ return void 0;
393
+ }
394
+ }
395
+ async function runWithBrowserFormElement(formClient, form) {
396
+ if (isBrowserFormSubmitter(formClient)) {
397
+ return await formClient[browserFormSubmitSymbol](form);
398
+ }
399
+ return await formClient.submit();
400
+ }
401
+ function createTypedErrorBag(flattenedErrors) {
402
+ return createErrorBag(flattenedErrors);
403
+ }
404
+ function notifyListeners(state) {
405
+ for (const listener of state.listeners) {
406
+ listener();
407
+ }
408
+ }
409
+ function nextValidationSequence(state) {
410
+ state.validationSequence += 1;
411
+ return state.validationSequence;
412
+ }
413
+ function isLatestValidation(state, sequence) {
414
+ return sequence === state.validationSequence;
415
+ }
416
+ function collectErrorsForPath(flattenedErrors, path) {
417
+ const direct = flattenedErrors[path] ?? [];
418
+ const prefix = `${path}.`;
419
+ const nested = Object.entries(flattenedErrors).filter(([key]) => key.startsWith(prefix)).flatMap(([, messages]) => messages);
420
+ return direct.length > 0 || nested.length > 0 ? [...direct, ...nested] : [];
421
+ }
422
+ function replaceErrorsForPath(flattenedErrors, path, nextErrors) {
423
+ const prefix = `${path}.`;
424
+ const merged = Object.fromEntries(
425
+ Object.entries(flattenedErrors).filter(([key]) => key !== path && !key.startsWith(prefix))
426
+ );
427
+ for (const [key, messages] of Object.entries(nextErrors)) {
428
+ if (key === path || key.startsWith(prefix)) {
429
+ merged[key] = messages;
430
+ }
431
+ }
432
+ return merged;
433
+ }
434
+ function buildFieldsTree(state, schemaDefinition, source, validateOn, prefix = "") {
435
+ if (isLeafValue(source)) {
436
+ const path = prefix;
437
+ return Object.freeze({
438
+ get value() {
439
+ return getValueAtPath(state.values, path);
440
+ },
441
+ set value(next) {
442
+ void this.set(next);
443
+ },
444
+ get errors() {
445
+ return collectErrorsForPath(state.flattenedErrors, path);
446
+ },
447
+ get touched() {
448
+ return state.touched.has(path);
449
+ },
450
+ get dirty() {
451
+ return state.dirty.has(path);
452
+ },
453
+ async set(value) {
454
+ setValueAtPath(state.values, path, value);
455
+ state.touched.add(path);
456
+ if (!areEqual(getValueAtPath(state.initialValues, path), value)) {
457
+ state.dirty.add(path);
458
+ } else {
459
+ state.dirty.delete(path);
460
+ }
461
+ if (validateOn === "change") {
462
+ const sequence = nextValidationSequence(state);
463
+ const submission = await validateClientValues(
464
+ cloneValue(state.values),
465
+ schemaDefinition
466
+ );
467
+ if (isLatestValidation(state, sequence)) {
468
+ state.flattenedErrors = submission.errors.flatten();
469
+ }
470
+ }
471
+ notifyListeners(state);
472
+ },
473
+ async onInput(value) {
474
+ await this.set(value);
475
+ },
476
+ async onBlur() {
477
+ state.touched.add(path);
478
+ if (validateOn === "blur") {
479
+ const sequence = nextValidationSequence(state);
480
+ const submission = await validateClientValues(
481
+ cloneValue(state.values),
482
+ schemaDefinition
483
+ );
484
+ if (isLatestValidation(state, sequence)) {
485
+ state.flattenedErrors = replaceErrorsForPath(
486
+ state.flattenedErrors,
487
+ path,
488
+ submission.errors.flatten()
489
+ );
490
+ }
491
+ }
492
+ notifyListeners(state);
493
+ },
494
+ async validate() {
495
+ const sequence = nextValidationSequence(state);
496
+ const submission = await validateClientValues(cloneValue(state.values), schemaDefinition);
497
+ if (isLatestValidation(state, sequence)) {
498
+ state.flattenedErrors = submission.errors.flatten();
499
+ notifyListeners(state);
500
+ }
501
+ return state.flattenedErrors[path] ?? [];
502
+ }
503
+ });
504
+ }
505
+ const entries = Object.entries(source).map(([key, value]) => {
506
+ const next = prefix ? `${prefix}.${key}` : key;
507
+ return [key, buildFieldsTree(state, schemaDefinition, value, validateOn, next)];
508
+ });
509
+ return Object.freeze(Object.fromEntries(entries));
510
+ }
511
+ function isSubmissionResult(value) {
512
+ return "valid" in value && "errors" in value && "values" in value && !!value.errors && typeof value.errors === "object" && "flatten" in value.errors && typeof value.errors.flatten === "function";
513
+ }
514
+ function isSerializedSubmission(value) {
515
+ return "submitted" in value && "errors" in value && "values" in value && !("ok" in value);
516
+ }
517
+ function isFormFailurePayload2(value) {
518
+ return isPlainObject2(value) && value.ok === false && typeof value.status === "number" && value.valid === false && isPlainObject2(value.values) && isPlainObject2(value.errors);
519
+ }
520
+ function isSvelteKitActionFailureResult(value) {
521
+ return isPlainObject2(value) && value.type === "failure" && typeof value.status === "number" && typeof value.data === "string";
522
+ }
523
+ function isSvelteKitActionErrorResult(value) {
524
+ return isPlainObject2(value) && value.type === "error" && isPlainObject2(value.error);
525
+ }
526
+ function parseJsonObject(value) {
527
+ try {
528
+ return JSON.parse(value);
529
+ } catch {
530
+ return void 0;
531
+ }
532
+ }
533
+ function normalizeSvelteKitActionResult(result) {
534
+ const payload = isSvelteKitActionFailureResult(result) ? parseJsonObject(result.data) : isSvelteKitActionErrorResult(result) ? result.error : void 0;
535
+ if (!isFormFailurePayload2(payload)) {
536
+ return void 0;
537
+ }
538
+ return {
539
+ ...payload,
540
+ submitted: true
541
+ };
542
+ }
543
+ function normalizeSubmissionLike(schemaDefinition, values, result) {
544
+ const svelteKitActionResult = normalizeSvelteKitActionResult(result);
545
+ if (svelteKitActionResult) {
546
+ return svelteKitActionResult;
547
+ }
548
+ if (isSubmissionResult(result)) {
549
+ return result;
550
+ }
551
+ if ("ok" in result && result.ok === false && "status" in result && "errors" in result && "values" in result) {
552
+ return {
553
+ ok: false,
554
+ status: result.status,
555
+ submitted: true,
556
+ valid: false,
557
+ values: result.values,
558
+ errors: result.errors,
559
+ ...result.status === 429 && "retryAfter" in result ? { retryAfter: result.retryAfter } : {},
560
+ ...result.status === 429 && "retryAt" in result ? { retryAt: result.retryAt } : {}
561
+ };
562
+ }
563
+ if (isSerializedSubmission(result)) {
564
+ if (result.valid) {
565
+ return createSuccessfulSubmission(schemaDefinition, result.values);
566
+ }
567
+ return createFailedSubmission(schemaDefinition, result.values, result.errors);
568
+ }
569
+ if ("ok" in result && result.ok === true) {
570
+ return {
571
+ ok: true,
572
+ status: result.status,
573
+ data: result.data
574
+ };
575
+ }
576
+ return createSuccessfulSubmission(schemaDefinition, values);
577
+ }
578
+ function appendQueryString(action, formData) {
579
+ const [rawPath = "", hash = ""] = action.split("#", 2);
580
+ const [pathname = "", search = ""] = rawPath.split("?", 2);
581
+ const params = new URLSearchParams(search);
582
+ for (const [key, value] of formData.entries()) {
583
+ params.append(key, typeof value === "string" ? value : value.name);
584
+ }
585
+ const query = params.toString();
586
+ const suffix = hash ? `#${hash}` : "";
587
+ return query ? `${pathname}?${query}${suffix}` : `${pathname}${suffix}`;
588
+ }
589
+ function isJsonResponse(response) {
590
+ const contentType = response.headers?.get("content-type")?.split(";", 1)[0]?.trim().toLowerCase();
591
+ return contentType === "application/json" || contentType?.endsWith("+json") === true;
592
+ }
593
+ async function normalizeFetchResponse(response, fallbackValues) {
594
+ if (response.status === 204 || response.status === 205) {
595
+ return {
596
+ ok: true,
597
+ status: response.status,
598
+ data: void 0
599
+ };
600
+ }
601
+ if (isJsonResponse(response)) {
602
+ return await response.json();
603
+ }
604
+ if (response.ok) {
605
+ return {
606
+ ok: true,
607
+ status: response.status,
608
+ data: void 0
609
+ };
610
+ }
611
+ try {
612
+ return await response.json();
613
+ } catch {
614
+ return createTransportFailure(fallbackValues, response.status);
615
+ }
616
+ }
617
+ async function defaultSubmitter(context) {
618
+ if (typeof fetch !== "function" || !context.action) {
619
+ return {
620
+ ok: true,
621
+ status: 200,
622
+ data: context.values
623
+ };
624
+ }
625
+ const method = context.method.toUpperCase();
626
+ if (method === "GET" || method === "HEAD") {
627
+ const response2 = await fetch(appendQueryString(context.action, context.formData), {
628
+ method
629
+ });
630
+ if (method === "HEAD" || response2.status === 204 || response2.status === 205) {
631
+ return {
632
+ ok: true,
633
+ status: response2.status,
634
+ data: void 0
635
+ };
636
+ }
637
+ return await normalizeFetchResponse(response2, context.values);
638
+ }
639
+ const response = await fetch(context.action, {
640
+ method,
641
+ body: context.formData
642
+ });
643
+ return await normalizeFetchResponse(response, context.values);
644
+ }
645
+ function isSafeMethod(method) {
646
+ const normalized = method.trim().toUpperCase();
647
+ return normalized === "GET" || normalized === "HEAD" || normalized === "OPTIONS" || normalized === "TRACE";
648
+ }
649
+ function createFormClient(schemaDefinition, options = {}) {
650
+ const initialState = options.initialState ?? void 0;
651
+ const initialValues = mergeValues(
652
+ normalizeObject(options.initialValues),
653
+ normalizeObject(initialState?.values)
654
+ );
655
+ const state = {
656
+ values: cloneValue(initialValues),
657
+ initialValues: cloneValue(initialValues),
658
+ flattenedErrors: { ...initialState?.errors ?? {} },
659
+ touched: /* @__PURE__ */ new Set(),
660
+ dirty: /* @__PURE__ */ new Set(),
661
+ submitting: false,
662
+ lastSubmission: initialState,
663
+ listeners: /* @__PURE__ */ new Set(),
664
+ validationSequence: 0
665
+ };
666
+ const validateOn = options.validateOn ?? "submit";
667
+ const fieldPaths = flattenLeafPaths(schemaDefinition.fields);
668
+ const fields = buildFieldsTree(
669
+ state,
670
+ schemaDefinition,
671
+ schemaDefinition.fields,
672
+ validateOn
673
+ );
674
+ async function runValidation() {
675
+ return await runValidationForInput(cloneValue(state.values));
676
+ }
677
+ async function runValidationForInput(input) {
678
+ const sequence = nextValidationSequence(state);
679
+ const submission = await validateClientValues(input, schemaDefinition);
680
+ if (isLatestValidation(state, sequence)) {
681
+ state.values = mergeValues(
682
+ state.values,
683
+ submission.valid ? submission.data : submission.values
684
+ );
685
+ state.flattenedErrors = submission.errors.flatten();
686
+ notifyListeners(state);
687
+ }
688
+ return submission;
689
+ }
690
+ function applyServerState(result) {
691
+ const normalized = normalizeSubmissionLike(
692
+ schemaDefinition,
693
+ state.values,
694
+ result
695
+ );
696
+ if ("ok" in normalized && normalized.ok === true) {
697
+ state.lastSubmission = normalized;
698
+ state.flattenedErrors = {};
699
+ notifyListeners(state);
700
+ return normalized;
701
+ }
702
+ if ("ok" in normalized && normalized.ok === false) {
703
+ const sanitized = {
704
+ ...normalized,
705
+ values: sanitizeFlashedInput(normalized.values, schemaDefinition)
706
+ };
707
+ state.values = mergeValues(state.values, sanitized.values);
708
+ clearSensitiveInputValues(state.values, schemaDefinition);
709
+ state.flattenedErrors = sanitized.errors;
710
+ state.lastSubmission = sanitized;
711
+ notifyListeners(state);
712
+ return sanitized;
713
+ }
714
+ const normalizedSubmission = normalized;
715
+ state.values = mergeValues(state.values, normalizedSubmission.values);
716
+ clearSensitiveInputValues(state.values, schemaDefinition);
717
+ state.flattenedErrors = normalizedSubmission.errors.flatten();
718
+ state.lastSubmission = normalizedSubmission.valid ? void 0 : normalizedSubmission.fail();
719
+ notifyListeners(state);
720
+ return normalizedSubmission;
721
+ }
722
+ async function submit(browserForm) {
723
+ state.submitting = true;
724
+ notifyListeners(state);
725
+ try {
726
+ const submitter = options.submitter ?? defaultSubmitter;
727
+ const liveForm = browserForm ?? getActiveElementBrowserForm();
728
+ const method = options.method ?? liveForm?.method ?? "POST";
729
+ const action = options.action ?? liveForm?.action;
730
+ const liveFormData = getActiveBrowserFormData(liveForm);
731
+ const formData = liveFormData ?? buildFormData(state.values);
732
+ const localSubmission = liveFormData ? await runValidationForInput(liveFormData) : await runValidation();
733
+ if (!localSubmission.valid) {
734
+ return localSubmission;
735
+ }
736
+ if (!isSafeMethod(method)) {
737
+ const csrfField = await getClientCsrfField();
738
+ if (csrfField) {
739
+ formData.set(csrfField.name, csrfField.value);
740
+ }
741
+ }
742
+ let response;
743
+ try {
744
+ response = await submitter({
745
+ action,
746
+ method,
747
+ values: localSubmission.data,
748
+ formData
749
+ });
750
+ } catch (error) {
751
+ return applyServerState(
752
+ validationExceptionToFailure(error, state.values) ?? createTransportFailure(state.values)
753
+ );
754
+ }
755
+ return applyServerState(response ?? {
756
+ ok: true,
757
+ status: 204,
758
+ data: void 0
759
+ });
760
+ } finally {
761
+ state.submitting = false;
762
+ notifyListeners(state);
763
+ }
764
+ }
765
+ return Object.freeze({
766
+ fields,
767
+ get values() {
768
+ return state.values;
769
+ },
770
+ get errors() {
771
+ return createTypedErrorBag(state.flattenedErrors);
772
+ },
773
+ get submitting() {
774
+ return state.submitting;
775
+ },
776
+ get valid() {
777
+ return Object.keys(state.flattenedErrors).length === 0;
778
+ },
779
+ get lastSubmission() {
780
+ return state.lastSubmission;
781
+ },
782
+ subscribe(listener) {
783
+ state.listeners.add(listener);
784
+ return () => {
785
+ state.listeners.delete(listener);
786
+ };
787
+ },
788
+ async validate() {
789
+ return runValidation();
790
+ },
791
+ async validateField(path) {
792
+ const submission = await runValidation();
793
+ return submission.errors.get(path);
794
+ },
795
+ submit() {
796
+ return submit();
797
+ },
798
+ [browserFormSubmitSymbol](form) {
799
+ return submit(form);
800
+ },
801
+ reset(values) {
802
+ const next = mergeValues(state.initialValues, values);
803
+ state.values = cloneValue(next);
804
+ state.initialValues = cloneValue(next);
805
+ state.flattenedErrors = {};
806
+ state.touched.clear();
807
+ state.dirty.clear();
808
+ state.lastSubmission = void 0;
809
+ notifyListeners(state);
810
+ },
811
+ async setValue(path, value) {
812
+ setValueAtPath(state.values, path, value);
813
+ state.touched.add(path);
814
+ if (!areEqual(getValueAtPath(state.initialValues, path), value)) {
815
+ state.dirty.add(path);
816
+ } else {
817
+ state.dirty.delete(path);
818
+ }
819
+ if (validateOn === "change" && fieldPaths.includes(path)) {
820
+ await runValidation();
821
+ return;
822
+ }
823
+ notifyListeners(state);
824
+ },
825
+ applyServerState(result) {
826
+ return applyServerState(result);
827
+ }
828
+ });
829
+ }
830
+ export {
831
+ createFormClient,
832
+ runWithBrowserFormElement
833
+ };