@conform-to/dom 1.14.0 → 1.15.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/README.md CHANGED
@@ -7,7 +7,7 @@
7
7
  ╚══════╝ ╚═════╝ ╚═╝ ╚══╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝
8
8
  ```
9
9
 
10
- Version 1.14.0 / License MIT / Copyright (c) 2025 Edmund Hung
10
+ Version 1.15.0 / License MIT / Copyright (c) 2025 Edmund Hung
11
11
 
12
12
  Progressively enhance HTML forms with React. Build resilient, type-safe forms with no hassle using web standards.
13
13
 
@@ -1,4 +1,4 @@
1
- import type { FormValue, JsonPrimitive, Serialize, SerializedValue, Submission, SubmissionResult } from './types';
1
+ import type { FormValue, FieldName, JsonPrimitive, Serialize, SerializedValue, Submission, SubmissionResult, UnknownObject } from './types';
2
2
  import type { StandardSchemaIssue } from './standard-schema';
3
3
  export declare const DEFAULT_INTENT_NAME = "__INTENT__";
4
4
  /**
@@ -285,4 +285,81 @@ formData: FormData | URLSearchParams | FormValue | null, options?: {
285
285
  * - anything else -> undefined
286
286
  */
287
287
  export declare function serialize(value: unknown): SerializedValue | null | undefined;
288
+ /**
289
+ * Retrieve a field value from FormData with optional type guards.
290
+ *
291
+ * @example
292
+ * // Basic field access: return `unknown`
293
+ * const email = getFieldValue(formData, 'email');
294
+ * // String type: returns `string`
295
+ * const name = getFieldValue(formData, 'name', { type: 'string' });
296
+ * // File type: returns `File`
297
+ * const avatar = getFieldValue(formData, 'avatar', { type: 'file' });
298
+ * // Object type: returns { city: unknown, ... }
299
+ * const address = getFieldValue<Address>(formData, 'address', { type: 'object' });
300
+ * // Array: returns `unknown[]`
301
+ * const tags = getFieldValue(formData, 'tags', { array: true });
302
+ * // Array with object type: returns `Array<{ name: unknown, ... }>`
303
+ * const items = getFieldValue<Item[]>(formData, 'items', { type: 'object', array: true });
304
+ * // Optional string type: returns `string | undefined`
305
+ * const bio = getFieldValue(formData, 'bio', { type: 'string', optional: true });
306
+ */
307
+ export declare function getFieldValue<FieldShape extends Array<Record<string, unknown>>>(formData: FormData | URLSearchParams, name: FieldName<FieldShape>, options: {
308
+ type: 'object';
309
+ array: true;
310
+ optional: true;
311
+ }): FieldShape extends Array<infer Item extends Record<string, unknown>> ? Array<UnknownObject<Item>> | undefined : never;
312
+ export declare function getFieldValue<FieldShape extends Array<Record<string, unknown>>>(formData: FormData | URLSearchParams, name: FieldName<FieldShape>, options: {
313
+ type: 'object';
314
+ array: true;
315
+ }): FieldShape extends Array<infer Item extends Record<string, unknown>> ? Array<UnknownObject<Item>> : never;
316
+ export declare function getFieldValue<FieldShape extends Record<string, unknown> = Record<string, unknown>>(formData: FormData | URLSearchParams, name: FieldName<FieldShape>, options: {
317
+ type: 'object';
318
+ optional: true;
319
+ }): UnknownObject<FieldShape> | undefined;
320
+ export declare function getFieldValue<FieldShape extends Record<string, unknown> = Record<string, unknown>>(formData: FormData | URLSearchParams, name: FieldName<FieldShape>, options: {
321
+ type: 'object';
322
+ }): UnknownObject<FieldShape>;
323
+ export declare function getFieldValue<FieldShape>(formData: FormData | URLSearchParams, name: FieldName<FieldShape>, options: {
324
+ type: 'string';
325
+ array: true;
326
+ optional: true;
327
+ }): string[] | undefined;
328
+ export declare function getFieldValue<FieldShape>(formData: FormData | URLSearchParams, name: FieldName<FieldShape>, options: {
329
+ type: 'string';
330
+ array: true;
331
+ }): string[];
332
+ export declare function getFieldValue<FieldShape>(formData: FormData | URLSearchParams, name: FieldName<FieldShape>, options: {
333
+ type: 'string';
334
+ optional: true;
335
+ }): string | undefined;
336
+ export declare function getFieldValue<FieldShape>(formData: FormData | URLSearchParams, name: FieldName<FieldShape>, options: {
337
+ type: 'string';
338
+ }): string;
339
+ export declare function getFieldValue<FieldShape>(formData: FormData | URLSearchParams, name: FieldName<FieldShape>, options: {
340
+ type: 'file';
341
+ array: true;
342
+ optional: true;
343
+ }): File[] | undefined;
344
+ export declare function getFieldValue<FieldShape>(formData: FormData | URLSearchParams, name: FieldName<FieldShape>, options: {
345
+ type: 'file';
346
+ array: true;
347
+ }): File[];
348
+ export declare function getFieldValue<FieldShape>(formData: FormData | URLSearchParams, name: FieldName<FieldShape>, options: {
349
+ type: 'file';
350
+ optional: true;
351
+ }): File | undefined;
352
+ export declare function getFieldValue<FieldShape>(formData: FormData | URLSearchParams, name: FieldName<FieldShape>, options: {
353
+ type: 'file';
354
+ }): File;
355
+ export declare function getFieldValue<FieldShape>(formData: FormData | URLSearchParams, name: FieldName<FieldShape>, options: {
356
+ array: true;
357
+ optional: true;
358
+ }): Array<unknown> | undefined;
359
+ export declare function getFieldValue<FieldShape>(formData: FormData | URLSearchParams, name: FieldName<FieldShape>, options: {
360
+ array: true;
361
+ }): Array<unknown>;
362
+ export declare function getFieldValue<FieldShape>(formData: FormData | URLSearchParams, name: FieldName<FieldShape>, options?: {
363
+ optional?: boolean;
364
+ }): unknown;
288
365
  //# sourceMappingURL=formdata.d.ts.map
package/dist/formdata.js CHANGED
@@ -576,9 +576,81 @@ function serialize(value) {
576
576
  return serializePrimitive(value);
577
577
  }
578
578
 
579
+ /**
580
+ * Retrieve a field value from FormData with optional type guards.
581
+ *
582
+ * @example
583
+ * // Basic field access: return `unknown`
584
+ * const email = getFieldValue(formData, 'email');
585
+ * // String type: returns `string`
586
+ * const name = getFieldValue(formData, 'name', { type: 'string' });
587
+ * // File type: returns `File`
588
+ * const avatar = getFieldValue(formData, 'avatar', { type: 'file' });
589
+ * // Object type: returns { city: unknown, ... }
590
+ * const address = getFieldValue<Address>(formData, 'address', { type: 'object' });
591
+ * // Array: returns `unknown[]`
592
+ * const tags = getFieldValue(formData, 'tags', { array: true });
593
+ * // Array with object type: returns `Array<{ name: unknown, ... }>`
594
+ * const items = getFieldValue<Item[]>(formData, 'items', { type: 'object', array: true });
595
+ * // Optional string type: returns `string | undefined`
596
+ * const bio = getFieldValue(formData, 'bio', { type: 'string', optional: true });
597
+ */
598
+
599
+ function getFieldValue(formData, name, options) {
600
+ var {
601
+ type,
602
+ array,
603
+ optional
604
+ } = options !== null && options !== void 0 ? options : {};
605
+ var value;
606
+
607
+ // Check if formData has a direct entry
608
+ if (formData.has(name)) {
609
+ // Get value based on array option
610
+ value = array ? formData.getAll(name) : formData.get(name);
611
+ } else {
612
+ // Parse formData and use getValueAtPath
613
+ var _submission = parseSubmission(formData, {
614
+ stripEmptyValues: false
615
+ });
616
+ value = getValueAtPath(_submission.payload, name);
617
+ }
618
+
619
+ // If optional and value is undefined, skip validation and return early
620
+ if (optional && value === undefined) {
621
+ return;
622
+ }
623
+
624
+ // Type guards - validate the value matches the expected type
625
+ if (array && !Array.isArray(value)) {
626
+ throw new Error("Expected field \"".concat(name, "\" to be an array, but got ").concat(util.getTypeName(value)));
627
+ }
628
+ if (type) {
629
+ var items = array ? value : [value];
630
+ var predicate = {
631
+ string: v => typeof v === 'string',
632
+ file: v => v instanceof File,
633
+ object: util.isPlainObject
634
+ }[type];
635
+ var typeName = {
636
+ string: 'a string',
637
+ file: 'a File',
638
+ object: 'an object'
639
+ }[type];
640
+ for (var i = 0; i < items.length; i++) {
641
+ if (!predicate(items[i])) {
642
+ var field = array ? "".concat(name, "[").concat(i, "]") : name;
643
+ throw new Error("Expected field \"".concat(field, "\" to be ").concat(typeName, ", but got ").concat(util.getTypeName(items[i])));
644
+ }
645
+ }
646
+ }
647
+ return value;
648
+ }
649
+
579
650
  exports.DEFAULT_INTENT_NAME = DEFAULT_INTENT_NAME;
580
651
  exports.appendPathSegment = appendPathSegment;
581
652
  exports.formatPathSegments = formatPathSegments;
653
+ exports.getFieldValue = getFieldValue;
582
654
  exports.getFormData = getFormData;
583
655
  exports.getPathSegments = getPathSegments;
584
656
  exports.getRelativePath = getRelativePath;
package/dist/formdata.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  import { objectSpread2 as _objectSpread2 } from './_virtual/_rollupPluginBabelHelpers.mjs';
2
2
  import { isSubmitter, isGlobalInstance } from './dom.mjs';
3
- import { isPlainObject, stripFiles, deepEqual } from './util.mjs';
3
+ import { isPlainObject, stripFiles, deepEqual, getTypeName } from './util.mjs';
4
4
  import { formatIssues } from './standard-schema.mjs';
5
5
 
6
6
  var DEFAULT_INTENT_NAME = '__INTENT__';
@@ -572,4 +572,75 @@ function serialize(value) {
572
572
  return serializePrimitive(value);
573
573
  }
574
574
 
575
- export { DEFAULT_INTENT_NAME, appendPathSegment, formatPathSegments, getFormData, getPathSegments, getRelativePath, getValueAtPath, isDirty, isPrefix, parseSubmission, report, serialize, setValueAtPath };
575
+ /**
576
+ * Retrieve a field value from FormData with optional type guards.
577
+ *
578
+ * @example
579
+ * // Basic field access: return `unknown`
580
+ * const email = getFieldValue(formData, 'email');
581
+ * // String type: returns `string`
582
+ * const name = getFieldValue(formData, 'name', { type: 'string' });
583
+ * // File type: returns `File`
584
+ * const avatar = getFieldValue(formData, 'avatar', { type: 'file' });
585
+ * // Object type: returns { city: unknown, ... }
586
+ * const address = getFieldValue<Address>(formData, 'address', { type: 'object' });
587
+ * // Array: returns `unknown[]`
588
+ * const tags = getFieldValue(formData, 'tags', { array: true });
589
+ * // Array with object type: returns `Array<{ name: unknown, ... }>`
590
+ * const items = getFieldValue<Item[]>(formData, 'items', { type: 'object', array: true });
591
+ * // Optional string type: returns `string | undefined`
592
+ * const bio = getFieldValue(formData, 'bio', { type: 'string', optional: true });
593
+ */
594
+
595
+ function getFieldValue(formData, name, options) {
596
+ var {
597
+ type,
598
+ array,
599
+ optional
600
+ } = options !== null && options !== void 0 ? options : {};
601
+ var value;
602
+
603
+ // Check if formData has a direct entry
604
+ if (formData.has(name)) {
605
+ // Get value based on array option
606
+ value = array ? formData.getAll(name) : formData.get(name);
607
+ } else {
608
+ // Parse formData and use getValueAtPath
609
+ var _submission = parseSubmission(formData, {
610
+ stripEmptyValues: false
611
+ });
612
+ value = getValueAtPath(_submission.payload, name);
613
+ }
614
+
615
+ // If optional and value is undefined, skip validation and return early
616
+ if (optional && value === undefined) {
617
+ return;
618
+ }
619
+
620
+ // Type guards - validate the value matches the expected type
621
+ if (array && !Array.isArray(value)) {
622
+ throw new Error("Expected field \"".concat(name, "\" to be an array, but got ").concat(getTypeName(value)));
623
+ }
624
+ if (type) {
625
+ var items = array ? value : [value];
626
+ var predicate = {
627
+ string: v => typeof v === 'string',
628
+ file: v => v instanceof File,
629
+ object: isPlainObject
630
+ }[type];
631
+ var typeName = {
632
+ string: 'a string',
633
+ file: 'a File',
634
+ object: 'an object'
635
+ }[type];
636
+ for (var i = 0; i < items.length; i++) {
637
+ if (!predicate(items[i])) {
638
+ var field = array ? "".concat(name, "[").concat(i, "]") : name;
639
+ throw new Error("Expected field \"".concat(field, "\" to be ").concat(typeName, ", but got ").concat(getTypeName(items[i])));
640
+ }
641
+ }
642
+ }
643
+ return value;
644
+ }
645
+
646
+ export { DEFAULT_INTENT_NAME, appendPathSegment, formatPathSegments, getFieldValue, getFormData, getPathSegments, getRelativePath, getValueAtPath, isDirty, isPrefix, parseSubmission, report, serialize, setValueAtPath };
@@ -1,5 +1,5 @@
1
- export type { Serialize, FormValue, FormError, Submission, SubmissionResult, ValidationAttributes, } from '../types';
2
- export { DEFAULT_INTENT_NAME, getFormData, isDirty, parseSubmission, getPathSegments, formatPathSegments, appendPathSegment, getRelativePath, getValueAtPath, setValueAtPath, report, serialize, } from '../formdata';
1
+ export type { Serialize, FieldName, FormValue, FormError, Submission, SubmissionResult, ValidationAttributes, } from '../types';
2
+ export { DEFAULT_INTENT_NAME, getFormData, isDirty, parseSubmission, getPathSegments, formatPathSegments, appendPathSegment, getRelativePath, getValueAtPath, setValueAtPath, report, serialize, getFieldValue, } from '../formdata';
3
3
  export { isPlainObject, deepEqual } from '../util';
4
4
  export { isFieldElement, isGlobalInstance, updateField, createFileList, createSubmitEvent, createGlobalFormsObserver, focus, change, blur, getFormAction, getFormEncType, getFormMethod, requestSubmit, requestIntent, } from '../dom';
5
5
  export { formatIssues } from '../standard-schema';
@@ -12,6 +12,7 @@ var standardSchema = require('../standard-schema.js');
12
12
  exports.DEFAULT_INTENT_NAME = formdata.DEFAULT_INTENT_NAME;
13
13
  exports.appendPathSegment = formdata.appendPathSegment;
14
14
  exports.formatPathSegments = formdata.formatPathSegments;
15
+ exports.getFieldValue = formdata.getFieldValue;
15
16
  exports.getFormData = formdata.getFormData;
16
17
  exports.getPathSegments = formdata.getPathSegments;
17
18
  exports.getRelativePath = formdata.getRelativePath;
@@ -1,4 +1,4 @@
1
- export { DEFAULT_INTENT_NAME, appendPathSegment, formatPathSegments, getFormData, getPathSegments, getRelativePath, getValueAtPath, isDirty, parseSubmission, report, serialize, setValueAtPath } from '../formdata.mjs';
1
+ export { DEFAULT_INTENT_NAME, appendPathSegment, formatPathSegments, getFieldValue, getFormData, getPathSegments, getRelativePath, getValueAtPath, isDirty, parseSubmission, report, serialize, setValueAtPath } from '../formdata.mjs';
2
2
  export { deepEqual, isPlainObject } from '../util.mjs';
3
3
  export { blur, change, createFileList, createGlobalFormsObserver, createSubmitEvent, focus, getFormAction, getFormEncType, getFormMethod, isFieldElement, isGlobalInstance, requestIntent, requestSubmit, updateField } from '../dom.mjs';
4
4
  export { formatIssues } from '../standard-schema.mjs';
package/dist/types.d.ts CHANGED
@@ -76,6 +76,10 @@ export type SubmissionResult<ErrorShape = string, ValueType extends JsonPrimitiv
76
76
  */
77
77
  reset?: boolean;
78
78
  };
79
+ /** The name of an input field with type information for TypeScript inference. */
80
+ export type FieldName<FieldShape> = string & {
81
+ '~shape'?: FieldShape;
82
+ };
79
83
  /**
80
84
  * The input attributes with related to the Constraint Validation API
81
85
  * @see https://developer.mozilla.org/en-US/docs/Web/HTML/Constraint_validation
@@ -115,4 +119,14 @@ export type Serialize = (value: unknown) => SerializedValue | null | undefined;
115
119
  * - Arrays allow representing multi-value fields.
116
120
  */
117
121
  export type SerializedValue = string | string[] | File | File[];
122
+ /**
123
+ * Flatten a discriminated union into a single type with all properties.
124
+ */
125
+ export type Combine<T, K extends PropertyKey = T extends unknown ? keyof T : never> = T extends unknown ? T & Partial<Record<Exclude<K, keyof T>, never>> : never;
126
+ /**
127
+ * Maps all keys of T (including all keys from discriminated unions) to unknown.
128
+ */
129
+ export type UnknownObject<T> = [T] extends [Record<string, any>] ? {
130
+ [K in keyof Combine<T>]-?: unknown;
131
+ } : never;
118
132
  //# sourceMappingURL=types.d.ts.map
package/dist/util.d.ts CHANGED
@@ -12,4 +12,8 @@ export declare function isPlainObject(obj: unknown): obj is Record<string | numb
12
12
  */
13
13
  export declare function deepEqual(left: unknown, right: unknown): boolean;
14
14
  export declare function stripFiles<Type extends string | number | boolean | File | null>(value: Record<string, FormValue<Type>>): Record<string, FormValue<Exclude<Type, File>>>;
15
+ /**
16
+ * Helper to get readable type name for error messages
17
+ */
18
+ export declare function getTypeName(value: unknown): string;
15
19
  //# sourceMappingURL=util.d.ts.map
package/dist/util.js CHANGED
@@ -78,9 +78,23 @@ function stripFiles(value) {
78
78
  return JSON.parse(json);
79
79
  }
80
80
 
81
+ /**
82
+ * Helper to get readable type name for error messages
83
+ */
84
+ function getTypeName(value) {
85
+ if (value === null) return 'null';
86
+ if (Array.isArray(value)) return 'Array';
87
+ if (typeof value === 'object') {
88
+ var _value$constructor$na, _value$constructor;
89
+ return (_value$constructor$na = (_value$constructor = value.constructor) === null || _value$constructor === void 0 ? void 0 : _value$constructor.name) !== null && _value$constructor$na !== void 0 ? _value$constructor$na : 'Object';
90
+ }
91
+ return typeof value;
92
+ }
93
+
81
94
  exports.clone = clone;
82
95
  exports.deepEqual = deepEqual;
83
96
  exports.generateId = generateId;
97
+ exports.getTypeName = getTypeName;
84
98
  exports.invariant = invariant;
85
99
  exports.isPlainObject = isPlainObject;
86
100
  exports.stripFiles = stripFiles;
package/dist/util.mjs CHANGED
@@ -74,4 +74,17 @@ function stripFiles(value) {
74
74
  return JSON.parse(json);
75
75
  }
76
76
 
77
- export { clone, deepEqual, generateId, invariant, isPlainObject, stripFiles };
77
+ /**
78
+ * Helper to get readable type name for error messages
79
+ */
80
+ function getTypeName(value) {
81
+ if (value === null) return 'null';
82
+ if (Array.isArray(value)) return 'Array';
83
+ if (typeof value === 'object') {
84
+ var _value$constructor$na, _value$constructor;
85
+ return (_value$constructor$na = (_value$constructor = value.constructor) === null || _value$constructor === void 0 ? void 0 : _value$constructor.name) !== null && _value$constructor$na !== void 0 ? _value$constructor$na : 'Object';
86
+ }
87
+ return typeof value;
88
+ }
89
+
90
+ export { clone, deepEqual, generateId, getTypeName, invariant, isPlainObject, stripFiles };
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "description": "A set of opinionated helpers built on top of the Constraint Validation API",
4
4
  "homepage": "https://conform.guide",
5
5
  "license": "MIT",
6
- "version": "1.14.0",
6
+ "version": "1.15.0",
7
7
  "main": "./dist/index.js",
8
8
  "module": "./dist/index.mjs",
9
9
  "types": "./dist/index.d.ts",