@conform-to/dom 0.6.0 → 0.6.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.
package/dom.d.ts ADDED
@@ -0,0 +1,55 @@
1
+ export type FormControl = HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement | HTMLButtonElement;
2
+ export type Submitter = HTMLInputElement | HTMLButtonElement;
3
+ /**
4
+ * A type guard to check if the provided reference is a form control element, including
5
+ * `input`, `select`, `textarea` or `button`
6
+ */
7
+ export declare function isFormControl(element: unknown): element is FormControl;
8
+ /**
9
+ * A type guard to check if the provided reference is a focusable form control element.
10
+ */
11
+ export declare function isFocusableFormControl(element: unknown): element is FormControl;
12
+ /**
13
+ * Resolves the form action based on the submit event
14
+ */
15
+ export declare function getFormAction(event: SubmitEvent): string;
16
+ /**
17
+ * Resolves the form encoding type based on the submit event
18
+ */
19
+ export declare function getFormEncType(event: SubmitEvent): 'application/x-www-form-urlencoded' | 'multipart/form-data';
20
+ /**
21
+ * Resolves the form method based on the submit event
22
+ */
23
+ export declare function getFormMethod(event: SubmitEvent): 'get' | 'post' | 'put' | 'patch' | 'delete';
24
+ /**
25
+ * Resolve the form element
26
+ */
27
+ export declare function getFormElement(element: HTMLFormElement | HTMLFieldSetElement | HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement | HTMLButtonElement | null): HTMLFormElement | null;
28
+ /**
29
+ * Returns a list of form control elements in the form
30
+ */
31
+ export declare function getFormControls(form: HTMLFormElement): FormControl[];
32
+ /**
33
+ * A function to create a submitter button element
34
+ */
35
+ export declare function createSubmitter(config: {
36
+ name: string;
37
+ value: string;
38
+ hidden?: boolean;
39
+ formAction?: string;
40
+ formEnctype?: ReturnType<typeof getFormEncType>;
41
+ formMethod?: ReturnType<typeof getFormMethod>;
42
+ formNoValidate?: boolean;
43
+ }): HTMLButtonElement;
44
+ /**
45
+ * Trigger form submission with a submitter.
46
+ */
47
+ export declare function requestSubmit(form: HTMLFormElement, submitter: Submitter | null): void;
48
+ /**
49
+ * Focus on the first invalid form control in the form
50
+ */
51
+ export declare function focusFirstInvalidControl(form: HTMLFormElement): void;
52
+ /**
53
+ * Focus on the first form control with the provided name
54
+ */
55
+ export declare function focusFormControl(form: HTMLFormElement, name: string): void;
package/dom.js ADDED
@@ -0,0 +1,169 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ /**
6
+ * A type guard to check if the provided reference is a form control element, including
7
+ * `input`, `select`, `textarea` or `button`
8
+ */
9
+ function isFormControl(element) {
10
+ return element instanceof Element && (element.tagName === 'INPUT' || element.tagName === 'SELECT' || element.tagName === 'TEXTAREA' || element.tagName === 'BUTTON');
11
+ }
12
+
13
+ /**
14
+ * A type guard to check if the provided reference is a focusable form control element.
15
+ */
16
+ function isFocusableFormControl(element) {
17
+ return isFormControl(element) && element.willValidate && element.type !== 'submit';
18
+ }
19
+
20
+ /**
21
+ * Resolves the form action based on the submit event
22
+ */
23
+ function getFormAction(event) {
24
+ var _ref, _submitter$getAttribu;
25
+ var form = event.target;
26
+ var submitter = event.submitter;
27
+ return (_ref = (_submitter$getAttribu = submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute('formaction')) !== null && _submitter$getAttribu !== void 0 ? _submitter$getAttribu : form.getAttribute('action')) !== null && _ref !== void 0 ? _ref : "".concat(location.pathname).concat(location.search);
28
+ }
29
+
30
+ /**
31
+ * Resolves the form encoding type based on the submit event
32
+ */
33
+ function getFormEncType(event) {
34
+ var _submitter$getAttribu2;
35
+ var form = event.target;
36
+ var submitter = event.submitter;
37
+ var encType = (_submitter$getAttribu2 = submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute('formenctype')) !== null && _submitter$getAttribu2 !== void 0 ? _submitter$getAttribu2 : form.enctype;
38
+ if (['application/x-www-form-urlencoded', 'multipart/form-data'].includes(encType)) {
39
+ return encType;
40
+ }
41
+ return 'application/x-www-form-urlencoded';
42
+ }
43
+
44
+ /**
45
+ * Resolves the form method based on the submit event
46
+ */
47
+ function getFormMethod(event) {
48
+ var _submitter$getAttribu3;
49
+ var form = event.target;
50
+ var submitter = event.submitter;
51
+ var method = (_submitter$getAttribu3 = submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute('formmethod')) !== null && _submitter$getAttribu3 !== void 0 ? _submitter$getAttribu3 : form.getAttribute('method');
52
+ if (['get', 'post', 'put', 'patch', 'delete'].includes(method)) {
53
+ return method;
54
+ }
55
+ return 'get';
56
+ }
57
+
58
+ /**
59
+ * Resolve the form element
60
+ */
61
+ function getFormElement(element) {
62
+ var form = element instanceof HTMLFormElement ? element : element === null || element === void 0 ? void 0 : element.form;
63
+ if (!form) {
64
+ return null;
65
+ }
66
+ return form;
67
+ }
68
+
69
+ /**
70
+ * Returns a list of form control elements in the form
71
+ */
72
+ function getFormControls(form) {
73
+ var formControls = [];
74
+ for (var element of form.elements) {
75
+ if (isFormControl(element)) {
76
+ formControls.push(element);
77
+ }
78
+ }
79
+ return formControls;
80
+ }
81
+
82
+ /**
83
+ * A function to create a submitter button element
84
+ */
85
+ function createSubmitter(config) {
86
+ var button = document.createElement('button');
87
+ button.name = config.name;
88
+ button.value = config.value;
89
+ if (config.hidden) {
90
+ button.hidden = true;
91
+ }
92
+ if (config.formAction) {
93
+ button.formAction = config.formAction;
94
+ }
95
+ if (config.formEnctype) {
96
+ button.formEnctype = config.formEnctype;
97
+ }
98
+ if (config.formMethod) {
99
+ button.formMethod = config.formMethod;
100
+ }
101
+ if (config.formNoValidate) {
102
+ button.formNoValidate = true;
103
+ }
104
+ return button;
105
+ }
106
+
107
+ /**
108
+ * Trigger form submission with a submitter.
109
+ */
110
+ function requestSubmit(form, submitter) {
111
+ var shouldRemoveSubmitter = false;
112
+ if (submitter && !submitter.isConnected) {
113
+ shouldRemoveSubmitter = true;
114
+ form.appendChild(submitter);
115
+ }
116
+ if (typeof form.requestSubmit === 'function') {
117
+ form.requestSubmit(submitter);
118
+ } else {
119
+ var event = new SubmitEvent('submit', {
120
+ bubbles: true,
121
+ cancelable: true,
122
+ submitter
123
+ });
124
+ form.dispatchEvent(event);
125
+ }
126
+ if (submitter && shouldRemoveSubmitter) {
127
+ form.removeChild(submitter);
128
+ }
129
+ }
130
+
131
+ /**
132
+ * Focus on the first invalid form control in the form
133
+ */
134
+ function focusFirstInvalidControl(form) {
135
+ for (var element of form.elements) {
136
+ if (isFocusableFormControl(element) && !element.validity.valid) {
137
+ element.focus();
138
+ break;
139
+ }
140
+ }
141
+ }
142
+
143
+ /**
144
+ * Focus on the first form control with the provided name
145
+ */
146
+ function focusFormControl(form, name) {
147
+ var element = form.elements.namedItem(name);
148
+ if (!element) {
149
+ return;
150
+ }
151
+ if (element instanceof RadioNodeList) {
152
+ element = element.item(0);
153
+ }
154
+ if (isFocusableFormControl(element)) {
155
+ element.focus();
156
+ }
157
+ }
158
+
159
+ exports.createSubmitter = createSubmitter;
160
+ exports.focusFirstInvalidControl = focusFirstInvalidControl;
161
+ exports.focusFormControl = focusFormControl;
162
+ exports.getFormAction = getFormAction;
163
+ exports.getFormControls = getFormControls;
164
+ exports.getFormElement = getFormElement;
165
+ exports.getFormEncType = getFormEncType;
166
+ exports.getFormMethod = getFormMethod;
167
+ exports.isFocusableFormControl = isFocusableFormControl;
168
+ exports.isFormControl = isFormControl;
169
+ exports.requestSubmit = requestSubmit;
package/formdata.d.ts ADDED
@@ -0,0 +1,39 @@
1
+ /**
2
+ * A ponyfill-like helper to get the form data with the submitter value.
3
+ * It does not respect the tree order nor handles the image input.
4
+ *
5
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/FormData/FormData#parameters
6
+ */
7
+ export declare function getFormData(form: HTMLFormElement, submitter?: HTMLInputElement | HTMLButtonElement | null): FormData;
8
+ /**
9
+ * Returns the paths from a name based on the JS syntax convention
10
+ * @example
11
+ * ```js
12
+ * const paths = getPaths('todos[0].content'); // ['todos', 0, 'content']
13
+ * ```
14
+ */
15
+ export declare function getPaths(name: string): Array<string | number>;
16
+ /**
17
+ * Returns a formatted name from the paths based on the JS syntax convention
18
+ * @example
19
+ * ```js
20
+ * const name = formatPaths(['todos', 0, 'content']); // "todos[0].content"
21
+ * ```
22
+ */
23
+ export declare function formatPaths(paths: Array<string | number>): string;
24
+ /**
25
+ * Assign a value to a target object by following the paths on the name
26
+ */
27
+ export declare function setValue(target: any, name: string, valueFn: (prev?: unknown) => any): void;
28
+ /**
29
+ * Resolves the payload into a plain object based on the JS syntax convention
30
+ */
31
+ export declare function resolve(payload: FormData | URLSearchParams): {};
32
+ /**
33
+ * Format the error messages into a validation message
34
+ */
35
+ export declare function getValidationMessage(errors?: string | string[]): string;
36
+ /**
37
+ * Retrieve the error messages from the validation message
38
+ */
39
+ export declare function getErrors(validationMessage: string | undefined): string[];
package/formdata.js ADDED
@@ -0,0 +1,127 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ /**
6
+ * A ponyfill-like helper to get the form data with the submitter value.
7
+ * It does not respect the tree order nor handles the image input.
8
+ *
9
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/FormData/FormData#parameters
10
+ */
11
+
12
+ function getFormData(form, submitter) {
13
+ var payload = new FormData(form);
14
+ if (submitter && submitter.type === 'submit' && submitter.name !== '') {
15
+ payload.append(submitter.name, submitter.value);
16
+ }
17
+ return payload;
18
+ }
19
+
20
+ /**
21
+ * Returns the paths from a name based on the JS syntax convention
22
+ * @example
23
+ * ```js
24
+ * const paths = getPaths('todos[0].content'); // ['todos', 0, 'content']
25
+ * ```
26
+ */
27
+ function getPaths(name) {
28
+ var pattern = /(\w*)\[(\d+)\]/;
29
+ if (!name) {
30
+ return [];
31
+ }
32
+ return name.split('.').flatMap(key => {
33
+ var matches = pattern.exec(key);
34
+ if (!matches) {
35
+ return key;
36
+ }
37
+ if (matches[1] === '') {
38
+ return Number(matches[2]);
39
+ }
40
+ return [matches[1], Number(matches[2])];
41
+ });
42
+ }
43
+
44
+ /**
45
+ * Returns a formatted name from the paths based on the JS syntax convention
46
+ * @example
47
+ * ```js
48
+ * const name = formatPaths(['todos', 0, 'content']); // "todos[0].content"
49
+ * ```
50
+ */
51
+ function formatPaths(paths) {
52
+ return paths.reduce((name, path) => {
53
+ if (typeof path === 'number') {
54
+ return "".concat(name, "[").concat(path, "]");
55
+ }
56
+ if (name === '' || path === '') {
57
+ return [name, path].join('');
58
+ }
59
+ return [name, path].join('.');
60
+ }, '');
61
+ }
62
+
63
+ /**
64
+ * Assign a value to a target object by following the paths on the name
65
+ */
66
+ function setValue(target, name, valueFn) {
67
+ var paths = getPaths(name);
68
+ var length = paths.length;
69
+ var lastIndex = length - 1;
70
+ var index = -1;
71
+ var pointer = target;
72
+ while (pointer != null && ++index < length) {
73
+ var _pointer$key;
74
+ var key = paths[index];
75
+ var next = paths[index + 1];
76
+ var newValue = index != lastIndex ? (_pointer$key = pointer[key]) !== null && _pointer$key !== void 0 ? _pointer$key : typeof next === 'number' ? [] : {} : valueFn(pointer[key]);
77
+ pointer[key] = newValue;
78
+ pointer = pointer[key];
79
+ }
80
+ }
81
+
82
+ /**
83
+ * Resolves the payload into a plain object based on the JS syntax convention
84
+ */
85
+ function resolve(payload) {
86
+ var data = {};
87
+ var _loop = function _loop(value) {
88
+ setValue(data, name, prev => {
89
+ if (!prev) {
90
+ return value;
91
+ } else if (Array.isArray(prev)) {
92
+ return prev.concat(value);
93
+ } else {
94
+ return [prev, value];
95
+ }
96
+ });
97
+ };
98
+ for (var [name, value] of payload.entries()) {
99
+ _loop(value);
100
+ }
101
+ return data;
102
+ }
103
+
104
+ /**
105
+ * Format the error messages into a validation message
106
+ */
107
+ function getValidationMessage(errors) {
108
+ return [].concat(errors !== null && errors !== void 0 ? errors : []).join(String.fromCharCode(31));
109
+ }
110
+
111
+ /**
112
+ * Retrieve the error messages from the validation message
113
+ */
114
+ function getErrors(validationMessage) {
115
+ if (!validationMessage) {
116
+ return [];
117
+ }
118
+ return validationMessage.split(String.fromCharCode(31));
119
+ }
120
+
121
+ exports.formatPaths = formatPaths;
122
+ exports.getErrors = getErrors;
123
+ exports.getFormData = getFormData;
124
+ exports.getPaths = getPaths;
125
+ exports.getValidationMessage = getValidationMessage;
126
+ exports.resolve = resolve;
127
+ exports.setValue = setValue;
package/index.d.ts CHANGED
@@ -1,202 +1,5 @@
1
- export type Primitive = null | undefined | string | number | boolean | Date;
2
- export type FieldElement = HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement | HTMLButtonElement;
3
- export interface FieldConfig<Schema = unknown> extends FieldConstraint<Schema> {
4
- id?: string;
5
- name: string;
6
- defaultValue?: FieldValue<Schema>;
7
- initialError?: Record<string, string | string[]>;
8
- form?: string;
9
- errorId?: string;
10
- /**
11
- * The frist error of the field
12
- */
13
- error?: string;
14
- /**
15
- * All of the field errors
16
- */
17
- errors?: string[];
18
- }
19
- export type FieldValue<Schema> = Schema extends Primitive ? string : Schema extends File ? File : Schema extends Array<infer InnerType> ? Array<FieldValue<InnerType>> : Schema extends Record<string, any> ? {
20
- [Key in keyof Schema]?: FieldValue<Schema[Key]>;
21
- } : any;
22
- export type FieldConstraint<Schema = any> = {
23
- required?: boolean;
24
- minLength?: number;
25
- maxLength?: number;
26
- min?: Schema extends number ? number : string | number;
27
- max?: Schema extends number ? number : string | number;
28
- step?: Schema extends number ? number : string | number;
29
- multiple?: boolean;
30
- pattern?: string;
31
- };
32
- export type FieldsetConstraint<Schema extends Record<string, any>> = {
33
- [Key in keyof Schema]?: FieldConstraint<Schema[Key]>;
34
- };
35
- export type Submission<Schema extends Record<string, any> | unknown = unknown> = unknown extends Schema ? {
36
- intent: string;
37
- payload: Record<string, any>;
38
- error: Record<string, string | string[]>;
39
- } : {
40
- intent: string;
41
- payload: Record<string, any>;
42
- value?: Schema;
43
- error: Record<string, string | string[]>;
44
- toJSON(): Submission;
45
- };
46
- export interface IntentButtonProps {
47
- name: typeof INTENT;
48
- value: string;
49
- formNoValidate?: boolean;
50
- }
51
- /**
52
- * Check if the provided reference is a form element (_input_ / _select_ / _textarea_ or _button_)
53
- */
54
- export declare function isFieldElement(element: unknown): element is FieldElement;
55
- /**
56
- * Find the corresponding paths based on the formatted name
57
- * @param name formatted name
58
- * @returns paths
59
- */
60
- export declare function getPaths(name: string): Array<string | number>;
61
- export declare function getFormData(form: HTMLFormElement, submitter?: HTMLInputElement | HTMLButtonElement | null): FormData;
62
- export type FormMethod = 'get' | 'post' | 'put' | 'patch' | 'delete';
63
- export type FormEncType = 'application/x-www-form-urlencoded' | 'multipart/form-data';
64
- export declare function getFormAttributes(form: HTMLFormElement, submitter?: HTMLInputElement | HTMLButtonElement | null): {
65
- action: string;
66
- encType: FormEncType;
67
- method: FormMethod;
68
- };
69
- export declare function getName(paths: Array<string | number>): string;
70
- export declare function getScope(intent: string): string | null;
71
- export declare function isFocusedOnIntentButton(form: HTMLFormElement, intent: string): boolean;
72
- export declare function getValidationMessage(errors?: string | string[]): string;
73
- export declare function getErrors(message: string | undefined): string[];
74
- export declare const FORM_ERROR_ELEMENT_NAME = "__form__";
75
- export declare const INTENT = "__intent__";
76
- export declare const VALIDATION_UNDEFINED = "__undefined__";
77
- export declare const VALIDATION_SKIPPED = "__skipped__";
78
- export declare function reportSubmission(form: HTMLFormElement, submission: Submission): void;
79
- export declare function setValue<T>(target: any, paths: Array<string | number>, valueFn: (prev?: T) => T): void;
80
- /**
81
- * Creates an intent button on demand and trigger a form submit by clicking it.
82
- */
83
- export declare function requestIntent(form: HTMLFormElement | undefined, buttonProps: {
84
- value: string;
85
- formNoValidate?: boolean;
86
- }): void;
87
- /**
88
- * Returns the properties required to configure an intent button for validation
89
- *
90
- * @see https://conform.guide/api/react#validate
91
- */
92
- export declare function validate(field?: string): IntentButtonProps;
93
- export declare function getFormElement(element: HTMLFormElement | HTMLFieldSetElement | HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement | HTMLButtonElement | null): HTMLFormElement | null;
94
- export declare function parse(payload: FormData | URLSearchParams): Submission;
95
- export declare function parse<Schema>(payload: FormData | URLSearchParams, options?: {
96
- resolve?: (payload: Record<string, any>, intent: string) => {
97
- value: Schema;
98
- } | {
99
- error: Record<string, string | string[]>;
100
- };
101
- }): Submission<Schema>;
102
- export declare function parse<Schema>(payload: FormData | URLSearchParams, options?: {
103
- resolve?: (payload: Record<string, any>, intent: string) => Promise<{
104
- value: Schema;
105
- } | {
106
- error: Record<string, string | string[]>;
107
- }>;
108
- }): Promise<Submission<Schema>>;
109
- export declare function parse<Schema>(payload: FormData | URLSearchParams, options?: {
110
- resolve?: (payload: Record<string, any>, intent: string) => ({
111
- value: Schema;
112
- } | {
113
- error: Record<string, string | string[]>;
114
- }) | Promise<{
115
- value: Schema;
116
- } | {
117
- error: Record<string, string | string[]>;
118
- }>;
119
- }): Submission<Schema> | Promise<Submission<Schema>>;
120
- export type ListCommand<Schema = unknown> = {
121
- type: 'prepend';
122
- scope: string;
123
- payload: {
124
- defaultValue: Schema;
125
- };
126
- } | {
127
- type: 'append';
128
- scope: string;
129
- payload: {
130
- defaultValue: Schema;
131
- };
132
- } | {
133
- type: 'replace';
134
- scope: string;
135
- payload: {
136
- defaultValue: Schema;
137
- index: number;
138
- };
139
- } | {
140
- type: 'remove';
141
- scope: string;
142
- payload: {
143
- index: number;
144
- };
145
- } | {
146
- type: 'reorder';
147
- scope: string;
148
- payload: {
149
- from: number;
150
- to: number;
151
- };
152
- };
153
- export declare function parseListCommand<Schema = unknown>(intent: string): ListCommand<Schema> | null;
154
- export declare function updateList<Schema>(list: Array<Schema>, command: ListCommand<Schema>): Array<Schema>;
155
- export interface ListCommandButtonBuilder {
156
- append<Schema>(name: string, payload?: {
157
- defaultValue: Schema;
158
- }): IntentButtonProps;
159
- prepend<Schema>(name: string, payload?: {
160
- defaultValue: Schema;
161
- }): IntentButtonProps;
162
- replace<Schema>(name: string, payload: {
163
- defaultValue: Schema;
164
- index: number;
165
- }): IntentButtonProps;
166
- remove(name: string, payload: {
167
- index: number;
168
- }): IntentButtonProps;
169
- reorder(name: string, payload: {
170
- from: number;
171
- to: number;
172
- }): IntentButtonProps;
173
- }
174
- /**
175
- * Helpers to configure an intent button for modifying a list
176
- *
177
- * @see https://conform.guide/api/react#list
178
- */
179
- export declare const list: ListCommandButtonBuilder;
180
- /**
181
- * Validate the form with the Constraint Validation API
182
- * @see https://conform.guide/api/react#validateconstraint
183
- */
184
- export declare function validateConstraint(options: {
185
- form: HTMLFormElement;
186
- formData?: FormData;
187
- constraint?: Record<Lowercase<string>, (value: string, context: {
188
- formData: FormData;
189
- attributeValue: string;
190
- }) => boolean>;
191
- acceptMultipleErrors?: ({ name, intent, payload, }: {
192
- name: string;
193
- intent: string;
194
- payload: Record<string, any>;
195
- }) => boolean;
196
- formatMessages?: ({ name, validity, constraint, defaultErrors, }: {
197
- name: string;
198
- validity: ValidityState;
199
- constraint: Record<string, boolean>;
200
- defaultErrors: string[];
201
- }) => string[];
202
- }): Submission;
1
+ export { type FormControl as FieldElement, isFormControl as isFieldElement, isFocusableFormControl, getFormAction, getFormControls, getFormElement, getFormEncType, getFormMethod, focusFirstInvalidControl, focusFormControl, createSubmitter, requestSubmit, } from './dom';
2
+ export { formatPaths as getName, getPaths, getFormData, getValidationMessage, getErrors, } from './formdata';
3
+ export { type ListCommand, INTENT, getScope, isSubmitting, validate, list, parseListCommand, updateList, requestIntent, } from './intent';
4
+ export { type Submission, parse } from './parse';
5
+ export { type FieldConstraint, type FieldsetConstraint, type ResolveType, type KeysOf, } from './types';