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.
Files changed (67) hide show
  1. package/README.md +9 -0
  2. package/dist/Client-HrMIqv7d.js +1934 -0
  3. package/dist/accordion.d.ts +2 -2
  4. package/dist/accordion.js +25 -29
  5. package/dist/button.d.ts +52 -0
  6. package/dist/button.js +135 -0
  7. package/dist/checkbox-group.d.ts +6 -5
  8. package/dist/checkbox-group.js +36 -20
  9. package/dist/{checkbox-group.registry-CGwuF7SF.js → checkbox-group.registry-D8P8A4LY.js} +13 -8
  10. package/dist/checkbox.d.ts +18 -4
  11. package/dist/checkbox.js +30 -27
  12. package/dist/client.d.ts +2 -2
  13. package/dist/client.js +2 -2
  14. package/dist/clipboard.d.ts +12 -5
  15. package/dist/clipboard.js +14 -16
  16. package/dist/collapsible.d.ts +3 -3
  17. package/dist/collapsible.js +13 -17
  18. package/dist/combobox.d.ts +37 -0
  19. package/dist/combobox.js +151 -0
  20. package/dist/date-picker.d.ts +67 -0
  21. package/dist/date-picker.js +289 -0
  22. package/dist/dialog.d.ts +3 -3
  23. package/dist/dialog.js +11 -12
  24. package/dist/field-array.d.ts +97 -0
  25. package/dist/field-array.js +223 -0
  26. package/dist/field.d.ts +30 -54
  27. package/dist/field.js +589 -175
  28. package/dist/fieldset.d.ts +15 -0
  29. package/dist/fieldset.js +27 -0
  30. package/dist/file-upload.d.ts +85 -0
  31. package/dist/file-upload.js +255 -0
  32. package/dist/form.d.ts +2 -83
  33. package/dist/form.fields-B58TZukv.js +329 -0
  34. package/dist/form.js +263 -276
  35. package/dist/index-B06gIN_k.d.ts +1358 -0
  36. package/dist/input.d.ts +96 -0
  37. package/dist/input.js +233 -0
  38. package/dist/menu.d.ts +55 -0
  39. package/dist/menu.js +144 -0
  40. package/dist/navigation-menu.d.ts +3 -3
  41. package/dist/navigation-menu.js +29 -43
  42. package/dist/number-input.d.ts +13 -4
  43. package/dist/number-input.js +30 -31
  44. package/dist/popover.d.ts +11 -3
  45. package/dist/popover.js +17 -18
  46. package/dist/radio-group.d.ts +4 -4
  47. package/dist/radio-group.js +35 -42
  48. package/dist/scroll-area.d.ts +3 -3
  49. package/dist/scroll-area.js +10 -9
  50. package/dist/select.d.ts +13 -51
  51. package/dist/select.js +62 -55
  52. package/dist/slider.d.ts +27 -0
  53. package/dist/slider.js +71 -0
  54. package/dist/switch.d.ts +4 -4
  55. package/dist/switch.js +24 -33
  56. package/dist/tabs.d.ts +3 -3
  57. package/dist/tabs.js +12 -15
  58. package/dist/textarea.d.ts +101 -0
  59. package/dist/textarea.js +231 -0
  60. package/dist/tooltip.d.ts +11 -3
  61. package/dist/tooltip.js +11 -10
  62. package/package.json +79 -24
  63. package/dist/Client-C51NSHxg.js +0 -248
  64. package/dist/field.dom-CJQXpQbZ.js +0 -11
  65. package/dist/form.registry-Cb8Pvoe8.d.ts +0 -7
  66. package/dist/form.registry-CmpTny_s.js +0 -51
  67. package/dist/index-B8JCdyld.d.ts +0 -118
package/dist/form.js CHANGED
@@ -1,57 +1,55 @@
1
- import { a as Machine, p as Component, r as normalizeProps } from "./Client-C51NSHxg.js";
2
- import { i as registerFormMachine, t as getFieldMachinesFor } from "./form.registry-CmpTny_s.js";
3
- import { debounce } from "@zag-js/utils";
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 { createAnatomy } from "@zag-js/anatomy";
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 context.get("values");
16
+ return createFormValues(formEl ? new FormData(formEl) : new FormData());
23
17
  }
24
18
  function getErrors() {
25
- return context.get("errors");
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
- return context.get("dirty");
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
- return context.get("touched");
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 getFormEl$1() {
34
- return getFormEl(scope);
42
+ function getSuccessText() {
43
+ return context.get("successText");
35
44
  }
45
+ const fieldHandles = getFieldHandles();
36
46
  const isSubmitting = state.matches("submitting");
37
- const isDirty = Object.values(getDirty()).length > 0;
38
- const isInvalid = Object.keys(getErrors()).length > 0;
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 = Object.values(getTouched()).length > 0;
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: getFormEl$1,
81
+ getFormEl() {
82
+ return formEl;
83
+ },
67
84
  getAllFields() {
68
- return getFieldMachinesFor(getFormEl(scope));
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 getFormEl$1()?.getAttribute("action") || "";
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.form.attrs,
131
+ ...parts.root.attrs(scope.id),
82
132
  noValidate: true,
83
- id: getFormId(scope),
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: async (event) => {
140
+ onSubmit: (event) => {
90
141
  event.preventDefault();
91
- const form = event.currentTarget;
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
- values: bindable(() => ({ defaultValue: new FormData() })),
221
- initialValues: bindable(() => ({ defaultValue: new FormData() })),
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
- VALIDATE_FIELD: { actions: ["validateField"] },
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({ context, send, prop, state, action, event, scope }) {
207
+ validateAll({ send, prop, state, action, event, scope, refs }) {
257
208
  const submitting = state.matches("submitting");
258
- const schema = prop("schema");
259
- if (schema) {
260
- const errs = validateWithSchema(schema, context.get("values"));
261
- context.set("errors", errs);
262
- if (Object.keys(errs).length > 0) {
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
- if (!submitting) {
269
- state.set("ready");
270
- return;
271
- }
272
- const onSubmit = prop("onSubmit");
273
- if (!onSubmit) {
274
- send({ type: "SUCCESS" });
275
- return;
276
- }
277
- (async () => {
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
- if (await onSubmit({
280
- formData: context.get("values"),
247
+ const result = await onSubmit({
248
+ values: submittedValues,
281
249
  api: event.detail.api,
282
250
  event: event.detail.event,
283
- post: async (url, data) => {
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 data.entries()) {
288
- if (key.includes(prefix)) {
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) throw new ValidationError(errorsFromServer(await response.json(), objectName, data));
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
- })) send({ type: "SUCCESS" });
302
- else send({ type: "ERROR" });
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
- context.set("errors", error.errors);
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
- validateField({ context, prop, event }) {
315
- const schema = prop("schema");
316
- if (!schema) return;
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
- const initial = form ? new FormData(form) : context.get("initialValues");
378
- context.set("values", initial);
379
- context.set("initialValues", initial);
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
- focusFirstInvalid({ context, scope }) {
385
- const errors = context.get("errors");
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
- setupFormListeners({ scope, send }) {
395
- const form = getFormEl(scope);
396
- if (!form) return;
397
- form.addEventListener("change", (event) => {
398
- send({
399
- type: "INPUT",
400
- detail: { target: event.target }
401
- });
402
- }, true);
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({ context }) {
405
- context.set("errors", {});
406
- context.set("touched", {});
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 name = "form";
366
+ static componentName = "form";
367
+ fieldSubscriptions = /* @__PURE__ */ new Map();
415
368
  initMachine(props) {
416
- const createdMachine = new Machine(machine, props);
417
- registerFormMachine(this.getElement("form"), createdMachine);
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.getElement("form");
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, ValidationError };
419
+ export { Form };