@holo-js/forms 0.3.12 → 0.3.15

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.
@@ -1,7 +1,26 @@
1
- import { g as FormSubmissionResult, S as SerializedFormSubmission, c as FormFailurePayload, i as FormSuccessPayload } from '../contracts-BOhBkI9c.js';
2
- import { WebFileLike, SchemaInputShape, FieldBuilderInput, InferFieldOutput, ValidationErrorBag } from '@holo-js/validation';
1
+ import { S as SerializedFormSubmission, c as FormFailurePayload, i as FormSuccessPayload, g as FormSubmissionResult } from '../contracts-BOhBkI9c.js';
2
+ import { ValidationErrorBag, WebFileLike, SchemaInputShape, FieldBuilderInput, InferFieldOutput } from '@holo-js/validation';
3
3
  import { FormSchema, InferFormData } from '../schema.js';
4
4
 
5
+ declare function collectFormDirtyPaths(current: unknown, initial: unknown, prefix?: string): readonly string[];
6
+ declare class FormClientState<TValues, TSuccess = unknown> {
7
+ #private;
8
+ values: TValues;
9
+ initialValues: TValues;
10
+ flattenedErrors: Record<string, readonly string[]>;
11
+ touched: Set<string>;
12
+ dirty: Set<string>;
13
+ lastSubmission?: SerializedFormSubmission<TValues> | FormFailurePayload<TValues> | FormSuccessPayload<TSuccess>;
14
+ readonly listeners: Set<() => void>;
15
+ validationSequence: number;
16
+ constructor(values: TValues, initialState?: SerializedFormSubmission<TValues> | FormFailurePayload<TValues>);
17
+ get submitting(): boolean;
18
+ get errors(): ValidationErrorBag<TValues>;
19
+ get dirtyPaths(): readonly string[];
20
+ replace(values: TValues, initialValues: TValues, errors: Record<string, readonly string[]>, touched: ReadonlySet<string>): void;
21
+ startSubmission(signal?: AbortSignal): () => void;
22
+ }
23
+
5
24
  type PrimitiveLike = string | number | boolean | bigint | symbol | null | undefined | Date | Blob | WebFileLike;
6
25
  type ValidateOnMode = 'submit' | 'blur' | 'change';
7
26
  interface ClientSubmitContext<TData> {
@@ -66,4 +85,4 @@ declare function runWithBrowserFormElement<TData, TSuccess>(formClient: Pick<Use
66
85
  */
67
86
  declare function createFormClient<TSchema extends FormSchema, TSuccess = unknown>(schemaDefinition: TSchema, options?: UseFormOptions<InferFormData<TSchema>, TSuccess>): UseFormResult<InferFormData<TSchema>, TSuccess, InferFormFieldTree<TSchema>>;
68
87
 
69
- export { type ClientSubmitContext, type ClientSubmitResult, type FormFieldState, type FormFieldTree, type InferFormFieldTree, type UseFormOptions, type UseFormResult, type ValidateOnMode, createFormClient, markClientSubmitControlFlowError, runWithBrowserFormElement };
88
+ export { type ClientSubmitContext, type ClientSubmitResult, FormClientState, type FormFieldState, type FormFieldTree, type InferFormFieldTree, type UseFormOptions, type UseFormResult, type ValidateOnMode, collectFormDirtyPaths, createFormClient, markClientSubmitControlFlowError, runWithBrowserFormElement };
@@ -16,7 +16,7 @@ import {
16
16
 
17
17
  // src/internal/client.ts
18
18
  import {
19
- createErrorBag,
19
+ createErrorBag as createErrorBag2,
20
20
  safeParse
21
21
  } from "@holo-js/validation";
22
22
 
@@ -72,9 +72,9 @@ async function getClientCsrfField() {
72
72
 
73
73
  // src/internal/validation-exception.ts
74
74
  import {
75
- isValidationException,
76
- validationInternals
75
+ isValidationException
77
76
  } from "@holo-js/validation";
77
+ import { parseValidationExceptionDigest } from "@holo-js/validation/internal/compiled";
78
78
  function isPlainObject(value) {
79
79
  return !!value && typeof value === "object" && !Array.isArray(value);
80
80
  }
@@ -104,7 +104,7 @@ function formFailurePayloadFromError(error) {
104
104
  return candidates.find((candidate) => isFormFailurePayload(candidate));
105
105
  }
106
106
  function validationExceptionToFailure(error, fallbackValues = {}) {
107
- const payload = isValidationException(error) ? error.toJSON() : validationInternals.parseValidationExceptionDigest(error);
107
+ const payload = isValidationException(error) ? error.toJSON() : parseValidationExceptionDigest(error);
108
108
  if (payload) {
109
109
  return {
110
110
  ok: false,
@@ -133,6 +133,9 @@ function validationExceptionToFailure(error, fallbackValues = {}) {
133
133
  return void 0;
134
134
  }
135
135
 
136
+ // src/internal/state.ts
137
+ import { createErrorBag } from "@holo-js/validation";
138
+
136
139
  // src/internal/formValues.ts
137
140
  function isPlainFormObject(value) {
138
141
  return !!value && typeof value === "object" && !Array.isArray(value) && !(value instanceof Date) && !(value instanceof Blob);
@@ -250,6 +253,69 @@ function buildFormData(value, path = "", formData = new FormData()) {
250
253
  return formData;
251
254
  }
252
255
 
256
+ // src/internal/state.ts
257
+ function collectFormDirtyPaths(current, initial, prefix = "") {
258
+ if (areFormValuesEqual(current, initial)) return [];
259
+ if (Array.isArray(current) && Array.isArray(initial)) {
260
+ const paths = Array.from(
261
+ { length: Math.max(current.length, initial.length) },
262
+ (_, index) => collectFormDirtyPaths(current[index], initial[index], prefix ? `${prefix}.${index}` : String(index))
263
+ ).flat();
264
+ return paths.length > 0 ? paths : [prefix];
265
+ }
266
+ if (isPlainFormObject(current) && isPlainFormObject(initial)) {
267
+ return [.../* @__PURE__ */ new Set([...Object.keys(current), ...Object.keys(initial)])].flatMap(
268
+ (key) => collectFormDirtyPaths(current[key], initial[key], prefix ? `${prefix}.${key}` : key)
269
+ );
270
+ }
271
+ return prefix ? [prefix] : [];
272
+ }
273
+ var FormClientState = class {
274
+ values;
275
+ initialValues;
276
+ flattenedErrors;
277
+ touched = /* @__PURE__ */ new Set();
278
+ dirty = /* @__PURE__ */ new Set();
279
+ lastSubmission;
280
+ listeners = /* @__PURE__ */ new Set();
281
+ validationSequence = 0;
282
+ #submissions = /* @__PURE__ */ new Set();
283
+ constructor(values, initialState) {
284
+ this.values = cloneFormValue(values);
285
+ this.initialValues = cloneFormValue(values);
286
+ this.flattenedErrors = { ...initialState?.errors };
287
+ this.lastSubmission = initialState;
288
+ }
289
+ get submitting() {
290
+ return this.#submissions.size > 0;
291
+ }
292
+ get errors() {
293
+ return createErrorBag(this.flattenedErrors);
294
+ }
295
+ get dirtyPaths() {
296
+ return collectFormDirtyPaths(this.values, this.initialValues);
297
+ }
298
+ replace(values, initialValues, errors, touched) {
299
+ this.values = values;
300
+ this.initialValues = initialValues;
301
+ this.flattenedErrors = errors;
302
+ this.touched = new Set(touched);
303
+ this.dirty = new Set(this.dirtyPaths);
304
+ }
305
+ startSubmission(signal) {
306
+ const id = /* @__PURE__ */ Symbol();
307
+ const finish = () => {
308
+ signal?.removeEventListener("abort", finish);
309
+ this.#submissions.delete(id);
310
+ };
311
+ if (!signal?.aborted) {
312
+ this.#submissions.add(id);
313
+ signal?.addEventListener("abort", finish, { once: true });
314
+ }
315
+ return finish;
316
+ }
317
+ };
318
+
253
319
  // src/internal/client.ts
254
320
  var browserFormSubmitSymbol = /* @__PURE__ */ Symbol("browserFormSubmit");
255
321
  var clientSubmitControlFlowErrors = /* @__PURE__ */ new WeakSet();
@@ -322,14 +388,14 @@ function createSubmission(valid, values, errors, failureStatus = 422, schemaDefi
322
388
  });
323
389
  }
324
390
  function createSuccessfulSubmission(schemaDefinition, data) {
325
- return createSubmission(true, data, createErrorBag(), 422, schemaDefinition);
391
+ return createSubmission(true, data, createErrorBag2(), 422, schemaDefinition);
326
392
  }
327
393
  function createFailedSubmission(schemaDefinition, values, flattenedErrors, status = 422) {
328
394
  void schemaDefinition;
329
395
  return createSubmission(
330
396
  false,
331
397
  values,
332
- createErrorBag(flattenedErrors),
398
+ createErrorBag2(flattenedErrors),
333
399
  normalizeStatus(status, 422),
334
400
  schemaDefinition
335
401
  );
@@ -380,9 +446,6 @@ async function runWithBrowserFormElement(formClient, form) {
380
446
  }
381
447
  return await formClient.submit();
382
448
  }
383
- function createTypedErrorBag(flattenedErrors) {
384
- return createErrorBag(flattenedErrors);
385
- }
386
449
  function notifyListeners(state) {
387
450
  for (const listener of state.listeners) {
388
451
  listener();
@@ -634,17 +697,7 @@ function createFormClient(schemaDefinition, options = {}) {
634
697
  normalizeFormObject(options.initialValues),
635
698
  normalizeFormObject(initialState?.values)
636
699
  );
637
- const state = {
638
- values: cloneFormValue(initialValues),
639
- initialValues: cloneFormValue(initialValues),
640
- flattenedErrors: { ...initialState?.errors ?? {} },
641
- touched: /* @__PURE__ */ new Set(),
642
- dirty: /* @__PURE__ */ new Set(),
643
- submitting: false,
644
- lastSubmission: initialState,
645
- listeners: /* @__PURE__ */ new Set(),
646
- validationSequence: 0
647
- };
700
+ const state = new FormClientState(initialValues, initialState);
648
701
  const validateOn = options.validateOn ?? "submit";
649
702
  const fieldPaths = flattenFormLeafPaths(schemaDefinition.fields);
650
703
  const fields = buildFieldsTree(
@@ -702,7 +755,7 @@ function createFormClient(schemaDefinition, options = {}) {
702
755
  return normalizedSubmission;
703
756
  }
704
757
  async function submit(browserForm) {
705
- state.submitting = true;
758
+ const finishSubmission = state.startSubmission();
706
759
  notifyListeners(state);
707
760
  try {
708
761
  const submitter = options.submitter ?? defaultSubmitter;
@@ -743,7 +796,7 @@ function createFormClient(schemaDefinition, options = {}) {
743
796
  data: void 0
744
797
  });
745
798
  } finally {
746
- state.submitting = false;
799
+ finishSubmission();
747
800
  notifyListeners(state);
748
801
  }
749
802
  }
@@ -753,7 +806,7 @@ function createFormClient(schemaDefinition, options = {}) {
753
806
  return state.values;
754
807
  },
755
808
  get errors() {
756
- return createTypedErrorBag(state.flattenedErrors);
809
+ return state.errors;
757
810
  },
758
811
  get submitting() {
759
812
  return state.submitting;
@@ -813,6 +866,8 @@ function createFormClient(schemaDefinition, options = {}) {
813
866
  });
814
867
  }
815
868
  export {
869
+ FormClientState,
870
+ collectFormDirtyPaths,
816
871
  createFormClient,
817
872
  markClientSubmitControlFlowError,
818
873
  runWithBrowserFormElement
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@holo-js/forms",
3
- "version": "0.3.12",
3
+ "version": "0.3.15",
4
4
  "description": "Holo-JS Framework - forms-first validation and submission contracts",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -33,10 +33,10 @@
33
33
  "test": "vitest --run"
34
34
  },
35
35
  "dependencies": {
36
- "@holo-js/validation": "^0.3.12"
36
+ "@holo-js/validation": "^0.3.15"
37
37
  },
38
38
  "peerDependencies": {
39
- "@holo-js/security": "^0.3.12"
39
+ "@holo-js/security": "^0.3.15"
40
40
  },
41
41
  "peerDependenciesMeta": {
42
42
  "@holo-js/security": {
@@ -48,5 +48,8 @@
48
48
  "tsup": "^8.3.5",
49
49
  "typescript": "^5.7.2",
50
50
  "vitest": "^4.1.5"
51
+ },
52
+ "engines": {
53
+ "node": ">=22.12.0"
51
54
  }
52
55
  }