fluid-primitives 0.19.1 → 1.0.0-beta.1
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 +9 -0
- package/dist/Client-HrMIqv7d.js +1934 -0
- package/dist/accordion.d.ts +2 -2
- package/dist/accordion.js +25 -29
- package/dist/button.d.ts +52 -0
- package/dist/button.js +135 -0
- package/dist/checkbox-group.d.ts +6 -5
- package/dist/checkbox-group.js +36 -20
- package/dist/{checkbox-group.registry-CGwuF7SF.js → checkbox-group.registry-D8P8A4LY.js} +13 -8
- package/dist/checkbox.d.ts +18 -4
- package/dist/checkbox.js +30 -27
- package/dist/client.d.ts +2 -2
- package/dist/client.js +2 -2
- package/dist/clipboard.d.ts +12 -5
- package/dist/clipboard.js +14 -16
- package/dist/collapsible.d.ts +3 -3
- package/dist/collapsible.js +13 -17
- package/dist/combobox.d.ts +37 -0
- package/dist/combobox.js +151 -0
- package/dist/date-picker.d.ts +67 -0
- package/dist/date-picker.js +289 -0
- package/dist/dialog.d.ts +3 -3
- package/dist/dialog.js +11 -12
- package/dist/field-array.d.ts +97 -0
- package/dist/field-array.js +223 -0
- package/dist/field.d.ts +30 -54
- package/dist/field.js +589 -175
- package/dist/fieldset.d.ts +15 -0
- package/dist/fieldset.js +27 -0
- package/dist/file-upload.d.ts +85 -0
- package/dist/file-upload.js +255 -0
- package/dist/form.d.ts +2 -83
- package/dist/form.fields-B58TZukv.js +329 -0
- package/dist/form.js +263 -276
- package/dist/index-B06gIN_k.d.ts +1358 -0
- package/dist/input.d.ts +96 -0
- package/dist/input.js +233 -0
- package/dist/menu.d.ts +55 -0
- package/dist/menu.js +144 -0
- package/dist/navigation-menu.d.ts +3 -3
- package/dist/navigation-menu.js +29 -43
- package/dist/number-input.d.ts +13 -4
- package/dist/number-input.js +30 -31
- package/dist/popover.d.ts +11 -3
- package/dist/popover.js +17 -18
- package/dist/radio-group.d.ts +4 -4
- package/dist/radio-group.js +35 -42
- package/dist/scroll-area.d.ts +3 -3
- package/dist/scroll-area.js +10 -9
- package/dist/select.d.ts +13 -51
- package/dist/select.js +62 -55
- package/dist/slider.d.ts +27 -0
- package/dist/slider.js +71 -0
- package/dist/switch.d.ts +4 -4
- package/dist/switch.js +24 -33
- package/dist/tabs.d.ts +3 -3
- package/dist/tabs.js +12 -15
- package/dist/textarea.d.ts +101 -0
- package/dist/textarea.js +231 -0
- package/dist/tooltip.d.ts +11 -3
- package/dist/tooltip.js +11 -10
- package/package.json +79 -24
- package/dist/Client-C51NSHxg.js +0 -248
- package/dist/field.dom-CJQXpQbZ.js +0 -11
- package/dist/form.registry-Cb8Pvoe8.d.ts +0 -7
- package/dist/form.registry-CmpTny_s.js +0 -51
- package/dist/index-B8JCdyld.d.ts +0 -118
package/dist/form.js
CHANGED
|
@@ -1,57 +1,55 @@
|
|
|
1
|
-
import { a as Machine,
|
|
2
|
-
import { i as
|
|
3
|
-
import {
|
|
1
|
+
import { H as trimArraySuffix, P as registerFormMachine, a as normalizeProps, at as Machine$1, f as createFieldHandle, j as getFieldMachinesFor, rt as Component, z as prefixFieldName } from "./Client-HrMIqv7d.js";
|
|
2
|
+
import { _ as createFormValues, a as getRegisteredFieldMachines, b as getFormEl, c as pruneStaleFieldMachines, d as settleFieldMachines, f as attachErrorValues, g as validateWithValidation, h as mapServerErrors, i as getFormData, l as renameFieldMachine, m as getFormErrorMessages, o as hasInvalidFieldMachines, p as filterErrorsForCurrentValues, r as getFirstInvalidField, s as hasUnownedErrors, t as focusField, u as sendToFieldMachines, v as FormError, x as parts, y as ValidationError } from "./form.fields-B58TZukv.js";
|
|
3
|
+
import { nextTick } from "@zag-js/dom-query";
|
|
4
|
+
import { createSplitProps } from "@zag-js/utils";
|
|
4
5
|
import { createMachine } from "@zag-js/core";
|
|
5
|
-
import {
|
|
6
|
-
import * as z from "zod";
|
|
6
|
+
import { createProps } from "@zag-js/types";
|
|
7
7
|
|
|
8
|
-
//#region Resources/Private/Primitives/Form/src/form.anatomy.ts
|
|
9
|
-
const anatomy = createAnatomy("form").parts("form");
|
|
10
|
-
const parts = anatomy.build();
|
|
11
|
-
|
|
12
|
-
//#endregion
|
|
13
|
-
//#region Resources/Private/Primitives/Form/src/form.dom.ts
|
|
14
|
-
const getFormId = (scope) => scope.ids?.form ?? `form:${scope.id}:form`;
|
|
15
|
-
const getFormEl = (scope) => scope.getById(getFormId(scope));
|
|
16
|
-
|
|
17
|
-
//#endregion
|
|
18
8
|
//#region Resources/Private/Primitives/Form/src/form.connect.ts
|
|
19
9
|
function connect(service, normalize) {
|
|
20
10
|
const { context, state, send, scope, prop } = service;
|
|
11
|
+
const formEl = getFormEl(scope);
|
|
12
|
+
function getFieldHandles() {
|
|
13
|
+
return new Map(Array.from(getRegisteredFieldMachines(scope), ([name, fieldMachine]) => [name, createFieldHandle(fieldMachine)]));
|
|
14
|
+
}
|
|
21
15
|
function getValues() {
|
|
22
|
-
return
|
|
16
|
+
return createFormValues(formEl ? new FormData(formEl) : new FormData());
|
|
23
17
|
}
|
|
24
18
|
function getErrors() {
|
|
25
|
-
|
|
19
|
+
const errors = {};
|
|
20
|
+
for (const [name, field] of getFieldHandles()) {
|
|
21
|
+
if (field.errors.length === 0) continue;
|
|
22
|
+
errors[name] = {
|
|
23
|
+
messages: [...field.errors],
|
|
24
|
+
value: field.getValue()
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
return errors;
|
|
26
28
|
}
|
|
27
29
|
function getDirty() {
|
|
28
|
-
|
|
30
|
+
const dirty = {};
|
|
31
|
+
for (const [name, field] of getFieldHandles()) if (field.dirty) dirty[name] = true;
|
|
32
|
+
return dirty;
|
|
29
33
|
}
|
|
30
34
|
function getTouched() {
|
|
31
|
-
|
|
35
|
+
const touched = {};
|
|
36
|
+
for (const [name, field] of getFieldHandles()) if (field.touched) touched[name] = true;
|
|
37
|
+
return touched;
|
|
38
|
+
}
|
|
39
|
+
function getErrorText() {
|
|
40
|
+
return context.get("errorText");
|
|
32
41
|
}
|
|
33
|
-
function
|
|
34
|
-
return
|
|
42
|
+
function getSuccessText() {
|
|
43
|
+
return context.get("successText");
|
|
35
44
|
}
|
|
45
|
+
const fieldHandles = getFieldHandles();
|
|
36
46
|
const isSubmitting = state.matches("submitting");
|
|
37
|
-
const isDirty =
|
|
38
|
-
const isInvalid =
|
|
47
|
+
const isDirty = Array.from(fieldHandles.values()).some((field) => field.dirty);
|
|
48
|
+
const isInvalid = Array.from(fieldHandles.values()).some((field) => field.invalid);
|
|
39
49
|
const isSuccessful = state.matches("success");
|
|
40
50
|
const isError = state.matches("error");
|
|
41
|
-
const isTouched =
|
|
51
|
+
const isTouched = Array.from(fieldHandles.values()).some((field) => field.touched);
|
|
42
52
|
const stateValue = state.get();
|
|
43
|
-
const inputDebounceMs = prop("inputDebounceMs") ?? 100;
|
|
44
|
-
const debouncedSendInput = inputDebounceMs > 0 ? debounce((target) => {
|
|
45
|
-
send({
|
|
46
|
-
type: "INPUT",
|
|
47
|
-
detail: { target }
|
|
48
|
-
});
|
|
49
|
-
}, inputDebounceMs) : (target) => {
|
|
50
|
-
send({
|
|
51
|
-
type: "INPUT",
|
|
52
|
-
detail: { target }
|
|
53
|
-
});
|
|
54
|
-
};
|
|
55
53
|
return {
|
|
56
54
|
isSubmitting,
|
|
57
55
|
isDirty,
|
|
@@ -62,34 +60,86 @@ function connect(service, normalize) {
|
|
|
62
60
|
getErrors,
|
|
63
61
|
getDirty,
|
|
64
62
|
getTouched,
|
|
63
|
+
getErrorText,
|
|
64
|
+
getSuccessText,
|
|
65
|
+
setErrorText(text) {
|
|
66
|
+
send({
|
|
67
|
+
type: "SET_ERROR_TEXT",
|
|
68
|
+
detail: { text }
|
|
69
|
+
});
|
|
70
|
+
},
|
|
71
|
+
setSuccessText(text) {
|
|
72
|
+
send({
|
|
73
|
+
type: "SET_SUCCESS_TEXT",
|
|
74
|
+
detail: { text }
|
|
75
|
+
});
|
|
76
|
+
},
|
|
77
|
+
clearStatusText() {
|
|
78
|
+
send({ type: "CLEAR_STATUS_TEXT" });
|
|
79
|
+
},
|
|
65
80
|
_userRenderFn: prop("render"),
|
|
66
|
-
getFormEl
|
|
81
|
+
getFormEl() {
|
|
82
|
+
return formEl;
|
|
83
|
+
},
|
|
67
84
|
getAllFields() {
|
|
68
|
-
return
|
|
85
|
+
return getFieldHandles();
|
|
69
86
|
},
|
|
70
87
|
getField(name) {
|
|
71
|
-
return this.getAllFields().get(name);
|
|
88
|
+
return this.getAllFields().get(trimArraySuffix(name));
|
|
72
89
|
},
|
|
73
90
|
getAction() {
|
|
74
|
-
return
|
|
91
|
+
return formEl?.getAttribute("action") || "";
|
|
75
92
|
},
|
|
76
93
|
reset() {
|
|
77
94
|
send({ type: "RESET" });
|
|
78
95
|
},
|
|
96
|
+
syncFields() {
|
|
97
|
+
send({ type: "SYNC_FIELDS" });
|
|
98
|
+
},
|
|
99
|
+
renameField(oldName, newName) {
|
|
100
|
+
renameFieldMachine(scope, oldName, newName);
|
|
101
|
+
},
|
|
102
|
+
getContentProps() {
|
|
103
|
+
return normalize.element({
|
|
104
|
+
...parts.content.attrs(scope.id),
|
|
105
|
+
hidden: isError || isSuccessful
|
|
106
|
+
});
|
|
107
|
+
},
|
|
108
|
+
getIndicatorProps(indicatorState) {
|
|
109
|
+
return normalize.element({
|
|
110
|
+
...parts.indicator.attrs(scope.id),
|
|
111
|
+
hidden: stateValue !== indicatorState
|
|
112
|
+
});
|
|
113
|
+
},
|
|
114
|
+
getErrorTextProps() {
|
|
115
|
+
return normalize.element({
|
|
116
|
+
...parts["errorText"].attrs(scope.id),
|
|
117
|
+
hidden: !isError,
|
|
118
|
+
role: "alert"
|
|
119
|
+
});
|
|
120
|
+
},
|
|
121
|
+
getSuccessTextProps() {
|
|
122
|
+
return normalize.element({
|
|
123
|
+
...parts["successText"].attrs(scope.id),
|
|
124
|
+
hidden: !isSuccessful,
|
|
125
|
+
role: "status",
|
|
126
|
+
"aria-live": "polite"
|
|
127
|
+
});
|
|
128
|
+
},
|
|
79
129
|
getFormProps() {
|
|
80
130
|
return normalize.element({
|
|
81
|
-
...parts.
|
|
131
|
+
...parts.root.attrs(scope.id),
|
|
82
132
|
noValidate: true,
|
|
83
|
-
|
|
133
|
+
/** The state of the form. */
|
|
84
134
|
"data-state": stateValue,
|
|
135
|
+
/** Present while the form is being submitted. */
|
|
85
136
|
"data-submitting": isSubmitting ? "" : void 0,
|
|
86
137
|
"data-invalid": isInvalid ? "" : void 0,
|
|
87
138
|
"data-dirty": isDirty ? "" : void 0,
|
|
88
139
|
"data-touched": isTouched ? "" : void 0,
|
|
89
|
-
onSubmit:
|
|
140
|
+
onSubmit: (event) => {
|
|
90
141
|
event.preventDefault();
|
|
91
|
-
|
|
92
|
-
context.set("values", new FormData(form));
|
|
142
|
+
if (isSubmitting) return;
|
|
93
143
|
send({
|
|
94
144
|
type: "SUBMIT",
|
|
95
145
|
detail: {
|
|
@@ -103,112 +153,12 @@ function connect(service, normalize) {
|
|
|
103
153
|
type: "RESET",
|
|
104
154
|
detail: { omitManualReset: true }
|
|
105
155
|
});
|
|
106
|
-
},
|
|
107
|
-
onInput: (event) => {
|
|
108
|
-
debouncedSendInput(event.target);
|
|
109
|
-
},
|
|
110
|
-
onBlur: (event) => {
|
|
111
|
-
const target = event.target;
|
|
112
|
-
if (!(target instanceof HTMLInputElement) && !(target instanceof HTMLTextAreaElement) && !(target instanceof HTMLSelectElement) && !(target instanceof HTMLButtonElement && target.getAttribute("data-scope") === "select")) return;
|
|
113
|
-
send({
|
|
114
|
-
type: "BLUR",
|
|
115
|
-
detail: { target }
|
|
116
|
-
});
|
|
117
156
|
}
|
|
118
157
|
});
|
|
119
158
|
}
|
|
120
159
|
};
|
|
121
160
|
}
|
|
122
161
|
|
|
123
|
-
//#endregion
|
|
124
|
-
//#region Resources/Private/Primitives/Form/src/form.types.ts
|
|
125
|
-
/**
|
|
126
|
-
* Error thrown by post() when server returns 422 validation errors.
|
|
127
|
-
* The machine catches this and transitions to 'invalid' state.
|
|
128
|
-
*/
|
|
129
|
-
var ValidationError = class extends Error {
|
|
130
|
-
constructor(errors) {
|
|
131
|
-
super("Server validation failed");
|
|
132
|
-
this.errors = errors;
|
|
133
|
-
this.name = "ValidationError";
|
|
134
|
-
}
|
|
135
|
-
};
|
|
136
|
-
|
|
137
|
-
//#endregion
|
|
138
|
-
//#region Resources/Private/Primitives/Form/src/form.utils.ts
|
|
139
|
-
function validateWithSchema(schema, formData) {
|
|
140
|
-
if (!schema) return {};
|
|
141
|
-
const dataObject = formDataToObject(formData);
|
|
142
|
-
const validationResult = schema.safeParse(dataObject);
|
|
143
|
-
if (validationResult.success) return {};
|
|
144
|
-
const flat = z.flattenError(validationResult.error).fieldErrors;
|
|
145
|
-
const errors = {};
|
|
146
|
-
for (const key in flat) {
|
|
147
|
-
if (!flat[key]) continue;
|
|
148
|
-
errors[key] = {
|
|
149
|
-
messages: flat[key],
|
|
150
|
-
value: getFormDataValue(formData, key)
|
|
151
|
-
};
|
|
152
|
-
}
|
|
153
|
-
return errors;
|
|
154
|
-
}
|
|
155
|
-
function errorsFromServer(responseErrors, objectName, formData) {
|
|
156
|
-
const out = {};
|
|
157
|
-
for (const key in responseErrors) {
|
|
158
|
-
let newKey = key;
|
|
159
|
-
if (objectName) newKey = newKey.replace(objectName + ".", "");
|
|
160
|
-
out[newKey] = {
|
|
161
|
-
messages: responseErrors[key],
|
|
162
|
-
value: getFormDataValue(formData, newKey)
|
|
163
|
-
};
|
|
164
|
-
}
|
|
165
|
-
return out;
|
|
166
|
-
}
|
|
167
|
-
function getInputValue(target) {
|
|
168
|
-
const el = target;
|
|
169
|
-
if (!el || !("type" in el)) return void 0;
|
|
170
|
-
if (el.type === "checkbox") {
|
|
171
|
-
const input = el;
|
|
172
|
-
if (input.name) {
|
|
173
|
-
const form = input.form;
|
|
174
|
-
if (form) {
|
|
175
|
-
const checkboxes = Array.from(form.querySelectorAll(`input[type="checkbox"][name="${CSS.escape(input.name)}"]`));
|
|
176
|
-
const values = checkboxes.filter((c) => c.checked).map((c) => c.value || "on");
|
|
177
|
-
if (checkboxes.length === 1 && !checkboxes[0].value) return checkboxes[0].checked;
|
|
178
|
-
return values;
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
return input.checked;
|
|
182
|
-
}
|
|
183
|
-
if (el.type === "radio") {
|
|
184
|
-
const input = el;
|
|
185
|
-
if (input.form && input.name) {
|
|
186
|
-
const selected = input.form.querySelector(`input[type="radio"][name="${CSS.escape(input.name)}"]:checked`);
|
|
187
|
-
return selected ? selected.value : "";
|
|
188
|
-
}
|
|
189
|
-
return input.checked ? input.value : "";
|
|
190
|
-
}
|
|
191
|
-
if (el instanceof HTMLSelectElement && el.multiple) return Array.from(el.selectedOptions).map((o) => o.value);
|
|
192
|
-
return el.value;
|
|
193
|
-
}
|
|
194
|
-
function getFormDataValue(formData, fieldName) {
|
|
195
|
-
return formData.getAll(fieldName).length > 1 || fieldName.endsWith("[]") ? formData.getAll(fieldName) : formData.get(fieldName);
|
|
196
|
-
}
|
|
197
|
-
function formDataToObject(formData) {
|
|
198
|
-
return Object.fromEntries(Array.from(formData.keys()).map((key) => [key.endsWith("[]") ? key.slice(0, -2) : key, getFormDataValue(formData, key) ?? ""]));
|
|
199
|
-
}
|
|
200
|
-
function prefixFieldName(fieldName, prefix, objectName) {
|
|
201
|
-
if (fieldName === "") return "";
|
|
202
|
-
const isArrayField = fieldName.endsWith("[]");
|
|
203
|
-
if (isArrayField) fieldName = fieldName.slice(0, -2);
|
|
204
|
-
if (objectName) fieldName = `${objectName}[${fieldName}]`;
|
|
205
|
-
const fieldNameSegments = fieldName.split("[", 2);
|
|
206
|
-
let prefixedFieldName = `${prefix}[${fieldNameSegments[0]}]`;
|
|
207
|
-
if (fieldNameSegments.length > 1) prefixedFieldName += `[${fieldNameSegments[1]}`;
|
|
208
|
-
if (isArrayField) prefixedFieldName += "[]";
|
|
209
|
-
return prefixedFieldName;
|
|
210
|
-
}
|
|
211
|
-
|
|
212
162
|
//#endregion
|
|
213
163
|
//#region Resources/Private/Primitives/Form/src/form.machine.ts
|
|
214
164
|
const machine = createMachine({
|
|
@@ -217,13 +167,13 @@ const machine = createMachine({
|
|
|
217
167
|
},
|
|
218
168
|
context({ bindable }) {
|
|
219
169
|
return {
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
errors: bindable(() => ({ defaultValue: {} })),
|
|
223
|
-
dirty: bindable(() => ({ defaultValue: {} })),
|
|
224
|
-
touched: bindable(() => ({ defaultValue: {} }))
|
|
170
|
+
errorText: bindable(() => ({ defaultValue: null })),
|
|
171
|
+
successText: bindable(() => ({ defaultValue: null }))
|
|
225
172
|
};
|
|
226
173
|
},
|
|
174
|
+
refs() {
|
|
175
|
+
return { serverErrors: {} };
|
|
176
|
+
},
|
|
227
177
|
states: {
|
|
228
178
|
ready: {},
|
|
229
179
|
invalid: {},
|
|
@@ -234,13 +184,12 @@ const machine = createMachine({
|
|
|
234
184
|
on: {
|
|
235
185
|
SUBMIT: {
|
|
236
186
|
target: "submitting",
|
|
237
|
-
actions: ["validateAll"]
|
|
187
|
+
actions: ["clearStatusText", "validateAll"]
|
|
238
188
|
},
|
|
239
189
|
VALIDATE: { actions: ["validateAll"] },
|
|
240
|
-
|
|
190
|
+
SYNC_FIELDS: { actions: ["syncFields"] },
|
|
191
|
+
FIELDS_CHANGED: { actions: ["syncState"] },
|
|
241
192
|
INVALID: { target: "invalid" },
|
|
242
|
-
INPUT: { actions: ["handleInput"] },
|
|
243
|
-
BLUR: { actions: ["handleBlur"] },
|
|
244
193
|
RESET: {
|
|
245
194
|
target: "ready",
|
|
246
195
|
actions: ["resetForm"]
|
|
@@ -249,43 +198,62 @@ const machine = createMachine({
|
|
|
249
198
|
SUCCESS: {
|
|
250
199
|
target: "success",
|
|
251
200
|
actions: ["clearErrors"]
|
|
252
|
-
}
|
|
201
|
+
},
|
|
202
|
+
SET_ERROR_TEXT: { actions: ["setErrorText"] },
|
|
203
|
+
SET_SUCCESS_TEXT: { actions: ["setSuccessText"] },
|
|
204
|
+
CLEAR_STATUS_TEXT: { actions: ["clearStatusText"] }
|
|
253
205
|
},
|
|
254
|
-
entry: ["setupFormListeners"],
|
|
255
206
|
implementations: { actions: {
|
|
256
|
-
validateAll({
|
|
207
|
+
validateAll({ send, prop, state, action, event, scope, refs }) {
|
|
257
208
|
const submitting = state.matches("submitting");
|
|
258
|
-
const
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
209
|
+
const validation = prop("validation");
|
|
210
|
+
pruneStaleFieldMachines(scope);
|
|
211
|
+
const submittedFormData = getFormData(scope);
|
|
212
|
+
const submittedValues = createFormValues(submittedFormData);
|
|
213
|
+
const cachedServerErrors = filterErrorsForCurrentValues(refs.get("serverErrors"), submittedValues);
|
|
214
|
+
let errors = cachedServerErrors;
|
|
215
|
+
if (validation) errors = {
|
|
216
|
+
...cachedServerErrors,
|
|
217
|
+
...validateWithValidation(validation, submittedValues)
|
|
218
|
+
};
|
|
219
|
+
sendToFieldMachines(scope, submitting ? "SUBMIT.INVALID" : "VALIDATE");
|
|
220
|
+
(async () => {
|
|
221
|
+
await settleFieldMachines(scope);
|
|
222
|
+
if (hasInvalidFieldMachines(scope) || hasUnownedErrors(scope, errors)) {
|
|
263
223
|
send({ type: "INVALID" });
|
|
264
224
|
if (submitting) action(["focusFirstInvalid"]);
|
|
265
225
|
return;
|
|
266
226
|
}
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
227
|
+
if (!submitting) {
|
|
228
|
+
state.set("ready");
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
const onSubmit = prop("onSubmit");
|
|
232
|
+
if (!onSubmit) {
|
|
233
|
+
send({ type: "SUCCESS" });
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
const invalidateWithErrors = (nextErrors) => {
|
|
237
|
+
const currentErrors = attachErrorValues(nextErrors, submittedValues);
|
|
238
|
+
refs.set("serverErrors", {
|
|
239
|
+
...refs.get("serverErrors"),
|
|
240
|
+
...currentErrors
|
|
241
|
+
});
|
|
242
|
+
sendToFieldMachines(scope, "VALIDATE");
|
|
243
|
+
send({ type: "INVALID" });
|
|
244
|
+
action(["focusFirstInvalid"]);
|
|
245
|
+
};
|
|
278
246
|
try {
|
|
279
|
-
|
|
280
|
-
|
|
247
|
+
const result = await onSubmit({
|
|
248
|
+
values: submittedValues,
|
|
281
249
|
api: event.detail.api,
|
|
282
250
|
event: event.detail.event,
|
|
283
|
-
post: async (url
|
|
251
|
+
post: async (url) => {
|
|
284
252
|
const prefixedData = new FormData();
|
|
285
253
|
const objectName = prop("objectName");
|
|
286
254
|
const prefix = getFormEl(scope)?.getAttribute("data-field-name-prefix") || "";
|
|
287
|
-
for (const [key, value] of
|
|
288
|
-
if (key.
|
|
255
|
+
for (const [key, value] of submittedFormData.entries()) {
|
|
256
|
+
if (prefix && key.startsWith(`${prefix}[`)) {
|
|
289
257
|
prefixedData.append(key, value);
|
|
290
258
|
continue;
|
|
291
259
|
}
|
|
@@ -295,138 +263,157 @@ const machine = createMachine({
|
|
|
295
263
|
method: "POST",
|
|
296
264
|
body: prefixedData
|
|
297
265
|
});
|
|
298
|
-
if (response.status === 422)
|
|
266
|
+
if (response.status === 422) {
|
|
267
|
+
const responseErrors = await response.json();
|
|
268
|
+
const formErrorMessages = getFormErrorMessages(responseErrors, objectName);
|
|
269
|
+
if (formErrorMessages) throw new FormError(formErrorMessages);
|
|
270
|
+
throw new ValidationError(mapServerErrors(responseErrors, objectName, submittedValues));
|
|
271
|
+
}
|
|
299
272
|
return response;
|
|
300
273
|
}
|
|
301
|
-
})
|
|
302
|
-
|
|
274
|
+
});
|
|
275
|
+
if (result === true) {
|
|
276
|
+
send({ type: "SUCCESS" });
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
if (result === false) {
|
|
280
|
+
send({ type: "ERROR" });
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
if (isFormErrors(result)) {
|
|
284
|
+
if (Object.keys(result).length === 0) {
|
|
285
|
+
send({ type: "SUCCESS" });
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
invalidateWithErrors(result);
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
send({ type: "ERROR" });
|
|
303
292
|
} catch (error) {
|
|
293
|
+
if (error instanceof FormError) {
|
|
294
|
+
send({
|
|
295
|
+
type: "SET_ERROR_TEXT",
|
|
296
|
+
detail: { text: error.errors.join(" ") }
|
|
297
|
+
});
|
|
298
|
+
send({ type: "ERROR" });
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
304
301
|
if (error instanceof ValidationError) {
|
|
305
|
-
|
|
306
|
-
send({ type: "INVALID" });
|
|
307
|
-
action(["focusFirstInvalid"]);
|
|
302
|
+
invalidateWithErrors(error.errors);
|
|
308
303
|
return;
|
|
309
304
|
}
|
|
310
305
|
send({ type: "ERROR" });
|
|
311
306
|
}
|
|
312
307
|
})();
|
|
313
308
|
},
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
let fieldName = event.detail?.fieldName;
|
|
318
|
-
if (!fieldName) return;
|
|
319
|
-
const formData = context.get("values");
|
|
320
|
-
const currentErrors = context.get("errors");
|
|
321
|
-
const existingError = currentErrors[fieldName];
|
|
322
|
-
const currentValue = getFormDataValue(formData, fieldName);
|
|
323
|
-
if (existingError) {
|
|
324
|
-
const errorValue = existingError.value;
|
|
325
|
-
if (JSON.stringify(currentValue) === JSON.stringify(errorValue)) return;
|
|
326
|
-
}
|
|
327
|
-
const allErrors = validateWithSchema(schema, formData);
|
|
328
|
-
const updatedErrors = { ...currentErrors };
|
|
329
|
-
const fieldNameWithoutSuffix = fieldName.replace(/\[\]$/, "");
|
|
330
|
-
if (allErrors[fieldNameWithoutSuffix]) updatedErrors[fieldNameWithoutSuffix] = allErrors[fieldNameWithoutSuffix];
|
|
331
|
-
else delete updatedErrors[fieldNameWithoutSuffix];
|
|
332
|
-
context.set("errors", updatedErrors);
|
|
333
|
-
},
|
|
334
|
-
handleInput({ context, event, send }) {
|
|
335
|
-
const e = event;
|
|
336
|
-
const target = e?.detail?.target ?? e?.target ?? e?.currentTarget;
|
|
337
|
-
const name = target?.name;
|
|
338
|
-
if (!name) return;
|
|
339
|
-
const value = e?.detail?.value ?? getInputValue(target);
|
|
340
|
-
const values = context.get("values");
|
|
341
|
-
values.set(name, value);
|
|
342
|
-
context.set("values", values);
|
|
343
|
-
const initial = context.get("initialValues").get(name);
|
|
344
|
-
const dirty = { ...context.get("dirty") };
|
|
345
|
-
dirty[name] = JSON.stringify(values.get(name)) !== JSON.stringify(initial);
|
|
346
|
-
context.set("dirty", dirty);
|
|
347
|
-
const currentErrors = context.get("errors");
|
|
348
|
-
const touched = context.get("touched");
|
|
349
|
-
const hasFieldError = !!currentErrors[name];
|
|
350
|
-
const fieldTouched = !!touched[name];
|
|
351
|
-
if (hasFieldError || fieldTouched) send({
|
|
352
|
-
type: "VALIDATE_FIELD",
|
|
353
|
-
detail: { fieldName: name }
|
|
354
|
-
});
|
|
355
|
-
},
|
|
356
|
-
handleBlur({ context, send, prop, event }) {
|
|
357
|
-
const target = event?.detail?.target;
|
|
358
|
-
let name = target?.name;
|
|
359
|
-
if (!name) name = target?.closest("[data-scope=\"field\"]")?.getAttribute("name") || void 0;
|
|
360
|
-
if (!name) return;
|
|
361
|
-
const touched = { ...context.get("touched") };
|
|
362
|
-
touched[name] = true;
|
|
363
|
-
context.set("touched", touched);
|
|
364
|
-
const form = target.form;
|
|
365
|
-
if (form) {
|
|
366
|
-
const values = new FormData(form);
|
|
367
|
-
context.set("values", values);
|
|
368
|
-
}
|
|
369
|
-
if (prop("schema")) send({
|
|
370
|
-
type: "VALIDATE_FIELD",
|
|
371
|
-
detail: { fieldName: name }
|
|
372
|
-
});
|
|
309
|
+
syncState({ scope, state }) {
|
|
310
|
+
if (!state.matches("ready", "invalid")) return;
|
|
311
|
+
state.set(hasInvalidFieldMachines(scope) ? "invalid" : "ready");
|
|
373
312
|
},
|
|
374
|
-
resetForm({ context, scope, event }) {
|
|
313
|
+
resetForm({ context, scope, event, refs }) {
|
|
375
314
|
const form = getFormEl(scope);
|
|
376
315
|
if (form && !event?.detail?.omitManualReset) form.reset();
|
|
377
|
-
|
|
378
|
-
context.set("
|
|
379
|
-
|
|
380
|
-
context.set("errors", {});
|
|
381
|
-
context.set("touched", {});
|
|
382
|
-
context.set("dirty", {});
|
|
316
|
+
context.set("errorText", null);
|
|
317
|
+
context.set("successText", null);
|
|
318
|
+
refs.set("serverErrors", {});
|
|
383
319
|
},
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
const firstKey = Object.keys(errors)[0];
|
|
387
|
-
if (!firstKey) return;
|
|
388
|
-
const form = getFormEl(scope);
|
|
389
|
-
if (!form) return;
|
|
390
|
-
const invalidEl = form.querySelector(`[name="${CSS.escape(firstKey)}"]`);
|
|
391
|
-
invalidEl?.focus();
|
|
392
|
-
if (invalidEl instanceof HTMLInputElement || invalidEl instanceof HTMLTextAreaElement) invalidEl.select();
|
|
320
|
+
setErrorText({ context, event }) {
|
|
321
|
+
context.set("errorText", event.detail?.text ?? null);
|
|
393
322
|
},
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
323
|
+
setSuccessText({ context, event }) {
|
|
324
|
+
context.set("successText", event.detail?.text ?? null);
|
|
325
|
+
},
|
|
326
|
+
clearStatusText({ context }) {
|
|
327
|
+
context.set("errorText", null);
|
|
328
|
+
context.set("successText", null);
|
|
329
|
+
},
|
|
330
|
+
focusFirstInvalid({ scope }) {
|
|
331
|
+
nextTick(() => {
|
|
332
|
+
const firstInvalidField = getFirstInvalidField(scope);
|
|
333
|
+
const form = getFormEl(scope);
|
|
334
|
+
if (!firstInvalidField || !form) return;
|
|
335
|
+
focusField(form, ...firstInvalidField);
|
|
336
|
+
});
|
|
403
337
|
},
|
|
404
|
-
clearErrors({
|
|
405
|
-
|
|
406
|
-
|
|
338
|
+
clearErrors({ scope, refs }) {
|
|
339
|
+
refs.set("serverErrors", {});
|
|
340
|
+
sendToFieldMachines(scope, "ERRORS.CLEAR");
|
|
341
|
+
},
|
|
342
|
+
syncFields({ scope }) {
|
|
343
|
+
pruneStaleFieldMachines(scope);
|
|
407
344
|
}
|
|
408
345
|
} }
|
|
409
346
|
});
|
|
347
|
+
function isFormErrors(result) {
|
|
348
|
+
return typeof result === "object" && result !== null && !Array.isArray(result);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
//#endregion
|
|
352
|
+
//#region Resources/Private/Primitives/Form/src/form.props.ts
|
|
353
|
+
const props = createProps()([
|
|
354
|
+
"id",
|
|
355
|
+
"validation",
|
|
356
|
+
"objectName",
|
|
357
|
+
"inputDebounceMs",
|
|
358
|
+
"onSubmit",
|
|
359
|
+
"render"
|
|
360
|
+
]);
|
|
361
|
+
const splitProps = createSplitProps(props);
|
|
410
362
|
|
|
411
363
|
//#endregion
|
|
412
364
|
//#region Resources/Private/Primitives/Form/Form.ts
|
|
413
365
|
var Form = class extends Component {
|
|
414
|
-
static
|
|
366
|
+
static componentName = "form";
|
|
367
|
+
fieldSubscriptions = /* @__PURE__ */ new Map();
|
|
415
368
|
initMachine(props) {
|
|
416
|
-
const
|
|
417
|
-
|
|
369
|
+
const [machineProps] = splitProps(props);
|
|
370
|
+
const createdMachine = new Machine$1(machine, machineProps);
|
|
371
|
+
registerFormMachine(this.hydrator.query("root"), createdMachine);
|
|
418
372
|
return createdMachine;
|
|
419
373
|
}
|
|
420
374
|
initApi() {
|
|
421
375
|
return connect(this.machine.service, normalizeProps);
|
|
422
376
|
}
|
|
377
|
+
subscribeToFieldMachines(formEl) {
|
|
378
|
+
for (const fieldMachine of getFieldMachinesFor(formEl).values()) {
|
|
379
|
+
if (this.fieldSubscriptions.has(fieldMachine)) continue;
|
|
380
|
+
const unsubscribe = fieldMachine.subscribe(() => {
|
|
381
|
+
this.machine.send({ type: "FIELDS_CHANGED" });
|
|
382
|
+
this.api = this.initApi();
|
|
383
|
+
this.render();
|
|
384
|
+
});
|
|
385
|
+
this.fieldSubscriptions.set(fieldMachine, unsubscribe);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
423
388
|
render() {
|
|
424
|
-
const formEl = this.
|
|
389
|
+
const formEl = this.hydrator.query("root");
|
|
425
390
|
if (!formEl) return;
|
|
391
|
+
this.subscribeToFieldMachines(formEl);
|
|
426
392
|
this.spreadProps(formEl, this.api.getFormProps());
|
|
393
|
+
this.hydrator.queryAll("content").forEach((contentEl) => {
|
|
394
|
+
this.spreadProps(contentEl, this.api.getContentProps());
|
|
395
|
+
});
|
|
396
|
+
this.spreadPropsByValue("indicator", ({ value }) => this.api.getIndicatorProps(value));
|
|
397
|
+
this.hydrator.queryAll("errorText").forEach((errorTextEl) => {
|
|
398
|
+
this.spreadProps(errorTextEl, this.api.getErrorTextProps());
|
|
399
|
+
syncStatusText(errorTextEl, this.api.getErrorText());
|
|
400
|
+
});
|
|
401
|
+
this.hydrator.queryAll("successText").forEach((successTextEl) => {
|
|
402
|
+
this.spreadProps(successTextEl, this.api.getSuccessTextProps());
|
|
403
|
+
syncStatusText(successTextEl, this.api.getSuccessText());
|
|
404
|
+
});
|
|
427
405
|
this.api._userRenderFn?.(this);
|
|
428
406
|
}
|
|
407
|
+
destroy() {
|
|
408
|
+
for (const unsubscribe of this.fieldSubscriptions.values()) unsubscribe();
|
|
409
|
+
this.fieldSubscriptions.clear();
|
|
410
|
+
super.destroy();
|
|
411
|
+
}
|
|
429
412
|
};
|
|
413
|
+
function syncStatusText(element, text) {
|
|
414
|
+
if (element.dataset.defaultText === void 0) element.dataset.defaultText = element.textContent ?? "";
|
|
415
|
+
element.textContent = text ?? element.dataset.defaultText;
|
|
416
|
+
}
|
|
430
417
|
|
|
431
418
|
//#endregion
|
|
432
|
-
export { Form
|
|
419
|
+
export { Form };
|