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/field.js CHANGED
@@ -1,211 +1,625 @@
1
- import { a as Machine, f as registerFieldMachine, p as Component, r as normalizeProps } from "./Client-C51NSHxg.js";
2
- import { a as getLabelId, i as getErrorId, n as getDescriptionEl, o as getRootEl, r as getDescriptionId, s as getRootId, t as getControlId } from "./field.dom-CJQXpQbZ.js";
3
- import { n as getFormMachineFor, r as registerFieldMachineForForm } from "./form.registry-CmpTny_s.js";
4
- import { createMachine } from "@zag-js/core";
5
- import { createAnatomy } from "@zag-js/anatomy";
1
+ import { A as queryRootEl, C as toErrorArray, D as getVisibleErrorTextIds, E as getControlEl, H as trimArraySuffix, I as unregisterFieldMachineForForm, M as getFormMachineFor, N as registerFieldMachineForForm, O as hasHelperText, S as suppressValueMissing, T as FIELD_VALUE_CHANGE_EVENT, at as Machine$1, b as resolveValidation, c as registerFieldMachine, d as connectField, j as getFieldMachinesFor, k as isInsideField, l as unregisterFieldMachine, m as getCurrentFieldValue, p as getComparableFieldValue, rt as Component, u as FIELD_DEPENDENCY_CHANGE_EVENT, v as VALID_SNAPSHOT, w as withValueMissing, x as shouldCommit, y as getValiditySnapshot } from "./Client-HrMIqv7d.js";
2
+ import { _ as createFormValues, n as getFieldMessages } from "./form.fields-B58TZukv.js";
3
+ import { observeChildren, trackFormControl } from "@zag-js/dom-query";
4
+ import { createSplitProps, isEqual } from "@zag-js/utils";
5
+ import { setup } from "@zag-js/core";
6
+ import { createProps } from "@zag-js/types";
6
7
 
7
- //#region Resources/Private/Primitives/Field/src/field.anatomy.ts
8
- const anatomy = createAnatomy("field").parts("root", "label", "control", "error", "description");
9
- const parts = anatomy.build();
10
-
11
- //#endregion
12
- //#region Resources/Private/Primitives/Field/src/field.connect.ts
13
- function connect(service, normalize) {
14
- const { scope, prop, context, computed } = service;
15
- const invalid = context.get("invalid");
16
- const disabled = context.get("disabled");
17
- const required = context.get("required");
18
- const readOnly = context.get("readOnly");
19
- const errors = computed("errors");
20
- return {
21
- getFormMachine: () => context.get("formMachine"),
22
- invalid,
23
- disabled,
24
- required,
25
- readOnly,
26
- errors,
27
- name: prop("name"),
28
- getErrorText() {
29
- return errors.length > 0 ? errors.join(" ") : null;
30
- },
31
- getRootProps() {
32
- return normalize.element({
33
- ...parts.root.attrs,
34
- id: getRootId(scope),
35
- "data-invalid": invalid ? "" : void 0,
36
- "data-disabled": disabled ? "" : void 0,
37
- "data-readonly": readOnly ? "" : void 0,
38
- "data-required": required ? "" : void 0,
39
- "data-name": prop("name")
40
- });
41
- },
42
- getLabelProps() {
43
- return normalize.label({
44
- ...parts.label.attrs,
45
- id: getLabelId(scope),
46
- htmlFor: getControlId(scope),
47
- "data-invalid": invalid ? "" : void 0,
48
- "data-disabled": disabled ? "" : void 0,
49
- "data-required": required ? "" : void 0
50
- });
51
- },
52
- getControlProps() {
53
- return normalize.element({
54
- ...parts.control.attrs,
55
- id: getControlId(scope),
56
- name: prop("name"),
57
- disabled: disabled || void 0,
58
- readOnly: readOnly || void 0,
59
- required: required || void 0,
60
- "aria-invalid": invalid ? "true" : void 0,
61
- "aria-describedby": context.get("describeIds") || void 0,
62
- "aria-required": required ? "true" : void 0,
63
- "data-invalid": invalid ? "" : void 0,
64
- "data-disabled": disabled ? "" : void 0,
65
- "data-readonly": readOnly ? "" : void 0
66
- });
67
- },
68
- getErrorProps() {
69
- return normalize.element({
70
- ...parts.error.attrs,
71
- id: getErrorId(scope),
72
- hidden: !invalid
73
- });
74
- },
75
- getDescriptionProps() {
76
- return normalize.element({
77
- ...parts.description.attrs,
78
- id: getDescriptionId(scope)
79
- });
80
- }
81
- };
82
- }
83
-
84
- //#endregion
85
8
  //#region Resources/Private/Primitives/Field/src/field.machine.ts
86
- const machine = createMachine({
9
+ /**
10
+ * Derived from @zag-js/field of Zag.js (https://github.com/chakra-ui/zag, packages/machines/field,
11
+ * commit 0ce8e3de63b571d37e438bcf28e64759466e56e9), MIT License, Copyright (c) 2021 Chakra UI.
12
+ * Adapted for Fluid Primitives, the git history shows what changed from the original.
13
+ */
14
+ const { createMachine: createMachine$1 } = setup();
15
+ const machine = createMachine$1({
16
+ props({ props }) {
17
+ return {
18
+ dir: "ltr",
19
+ disabled: false,
20
+ readOnly: false,
21
+ required: false,
22
+ validationMode: "onBlur",
23
+ ...props
24
+ };
25
+ },
87
26
  initialState() {
88
- return "ready";
27
+ return "idle";
89
28
  },
90
- context({ bindable, prop }) {
29
+ context({ prop, bindable }) {
91
30
  return {
92
- invalid: bindable(() => ({ defaultValue: prop("invalid") ?? false })),
93
- required: bindable(() => ({ defaultValue: prop("required") ?? false })),
94
- disabled: bindable(() => ({ defaultValue: prop("disabled") ?? false })),
95
- readOnly: bindable(() => ({ defaultValue: prop("readOnly") ?? false })),
96
- formMachine: bindable(() => ({
97
- defaultValue: null,
98
- hash: (value) => JSON.stringify({
99
- state: value?.state,
100
- errors: value?.context.get("errors")
101
- })
31
+ touched: bindable(() => ({
32
+ defaultValue: false,
33
+ value: prop("touched")
34
+ })),
35
+ dirty: bindable(() => ({
36
+ defaultValue: false,
37
+ value: prop("dirty")
102
38
  })),
103
- describeIds: bindable(() => ({ defaultValue: void 0 })),
104
- hasDescription: bindable(() => ({ defaultValue: false }))
39
+ filled: bindable(() => ({ defaultValue: false })),
40
+ focused: bindable(() => ({ defaultValue: false })),
41
+ validating: bindable(() => ({ defaultValue: false })),
42
+ errors: bindable(() => ({ defaultValue: [] })),
43
+ validity: bindable(() => ({ defaultValue: null })),
44
+ errorTextIds: bindable(() => ({ defaultValue: [] })),
45
+ hasHelperText: bindable(() => ({ defaultValue: false })),
46
+ fieldsetDisabled: bindable(() => ({ defaultValue: false })),
47
+ submitAttempted: bindable(() => ({ defaultValue: false }))
105
48
  };
106
49
  },
107
- entry: [
108
- "getFormMachine",
109
- "checkForDescription",
110
- "determineDescribeIds",
111
- "updateInvalid"
112
- ],
113
- states: { ready: {} },
114
- computed: { errors({ context, prop }) {
115
- const formMachineCtx = context.get("formMachine")?.context;
116
- if (!formMachineCtx) return [];
117
- return (formMachineCtx.get("errors")?.[prop("name")])?.messages ?? [];
118
- } },
119
- watch({ track, context, computed, action }) {
120
- track([() => context.hash("formMachine"), () => computed("errors").join("|")], () => {
121
- action(["updateInvalid"]);
50
+ refs() {
51
+ return {
52
+ markedDirty: false,
53
+ initialValue: null,
54
+ seq: 0,
55
+ rootEl: null
56
+ };
57
+ },
58
+ computed: {
59
+ disabled: ({ prop, context }) => !!prop("disabled") || context.get("fieldsetDisabled"),
60
+ valid: ({ prop, context }) => {
61
+ if (prop("invalid")) return false;
62
+ if (prop("disabled") || context.get("fieldsetDisabled")) return null;
63
+ return context.get("validity")?.valid ?? null;
64
+ },
65
+ invalid: ({ prop, context }) => {
66
+ if (prop("invalid")) return true;
67
+ if (prop("disabled") || context.get("fieldsetDisabled")) return false;
68
+ return context.get("validity")?.valid === false;
69
+ }
70
+ },
71
+ watch({ track, prop, context, refs }) {
72
+ track([() => prop("disabled")], () => {
73
+ if (prop("disabled") && context.get("focused")) context.set("focused", false);
122
74
  });
123
- track([() => context.get("invalid"), () => context.get("hasDescription")], () => {
124
- action(["determineDescribeIds"]);
75
+ track([() => context.get("dirty")], () => {
76
+ if (context.get("dirty")) refs.set("markedDirty", true);
125
77
  });
126
78
  },
127
- implementations: { actions: {
128
- getFormMachine({ context, scope }) {
129
- if (context.get("formMachine")) return;
130
- const fieldRootEl = getRootEl(scope);
131
- if (!fieldRootEl) return;
132
- const formMachine = getFormMachineFor(fieldRootEl) ?? null;
133
- if (formMachine) context.set("formMachine", formMachine);
134
- else {
135
- const closestForm = fieldRootEl.closest("form");
136
- if (!closestForm) return;
137
- const handler = () => {
138
- const fs = getFormMachineFor(fieldRootEl) ?? null;
139
- context.set("formMachine", fs);
140
- closestForm.removeEventListener("fluid-primitives:form:registered", handler);
141
- };
142
- closestForm.addEventListener("fluid-primitives:form:registered", handler);
143
- }
79
+ effects: [
80
+ "trackRoot",
81
+ "trackControlState",
82
+ "trackTextParts",
83
+ "trackFieldEvents"
84
+ ],
85
+ on: {
86
+ "CONTROL.FOCUS": { actions: ["setFocused"] },
87
+ "CONTROL.BLUR": [{
88
+ guard: "shouldCommit",
89
+ actions: ["setBlurred", "commitValidation"]
90
+ }, { actions: ["setBlurred"] }],
91
+ "CONTROL.CHANGE": [{
92
+ guard: "shouldCommit",
93
+ actions: ["trackValueState", "commitValidation"]
94
+ }, { actions: [
95
+ "trackValueState",
96
+ "dropStaleValidation",
97
+ "recoverValueMissing"
98
+ ] }],
99
+ "SUBMIT.INVALID": { actions: ["markSubmitAttempted", "commitValidation"] },
100
+ VALIDATE: { actions: ["forceDirty", "commitValidation"] },
101
+ "VALIDATE.RESOLVE": {
102
+ guard: "isCurrentValidation",
103
+ actions: ["applyAsyncValidation"]
144
104
  },
145
- checkForDescription({ context, scope }) {
146
- const descriptionEl = getDescriptionEl(scope);
147
- context.set("hasDescription", !!descriptionEl);
105
+ "ERRORS.CLEAR": { actions: ["clearValidation"] },
106
+ RESET: { actions: ["resetField"] },
107
+ BASELINE: { actions: ["rebaseline"] }
108
+ },
109
+ states: { idle: {} },
110
+ implementations: {
111
+ guards: {
112
+ isCurrentValidation: ({ refs, event }) => event.seq === refs.get("seq"),
113
+ shouldCommit: ({ prop, context, refs, event }) => shouldCommit({
114
+ mode: prop("validationMode"),
115
+ submitAttempted: context.get("submitAttempted"),
116
+ eventType: event.type,
117
+ edited: refs.get("markedDirty") || context.get("dirty"),
118
+ showingErrors: context.get("errors").length > 0
119
+ })
148
120
  },
149
- determineDescribeIds({ context, scope }) {
150
- const ids = [];
151
- if (context.get("hasDescription")) ids.push(getDescriptionId(scope));
152
- if (context.get("invalid")) ids.push(getErrorId(scope));
153
- const idsStr = ids.join(" ") || void 0;
154
- context.set("describeIds", idsStr);
121
+ effects: {
122
+ trackRoot({ refs, scope }) {
123
+ refs.set("rootEl", queryRootEl(scope));
124
+ },
125
+ trackControlState({ context, refs, send, prop }) {
126
+ const rootEl = refs.get("rootEl");
127
+ if (!rootEl) return;
128
+ if (refs.get("initialValue") == null) {
129
+ const value = readValue({
130
+ refs,
131
+ prop
132
+ });
133
+ refs.set("initialValue", value);
134
+ context.set("filled", value.length > 0);
135
+ }
136
+ return trackFormControl(rootEl, {
137
+ onFieldsetDisabledChange(disabled) {
138
+ context.set("fieldsetDisabled", disabled);
139
+ },
140
+ onFormReset() {
141
+ send({ type: "RESET" });
142
+ }
143
+ });
144
+ },
145
+ trackTextParts({ context, refs }) {
146
+ const rootEl = refs.get("rootEl");
147
+ if (!rootEl) return;
148
+ const sync = () => {
149
+ const errorTextIds = getVisibleErrorTextIds(rootEl);
150
+ if (!isEqual(context.get("errorTextIds"), errorTextIds)) context.set("errorTextIds", errorTextIds);
151
+ context.set("hasHelperText", hasHelperText(rootEl));
152
+ };
153
+ sync();
154
+ return observeChildren(rootEl, {
155
+ defer: true,
156
+ callback: sync,
157
+ attributes: true,
158
+ attributeFilter: ["hidden"]
159
+ });
160
+ },
161
+ trackFieldEvents({ refs, send, prop }) {
162
+ const rootEl = refs.get("rootEl");
163
+ if (!rootEl) return;
164
+ let timer;
165
+ const read = () => readValue({
166
+ refs,
167
+ prop
168
+ });
169
+ let announced = read();
170
+ const sendChange = () => {
171
+ timer = void 0;
172
+ const value = read();
173
+ send({
174
+ type: "CONTROL.CHANGE",
175
+ value
176
+ });
177
+ if (value === announced) return;
178
+ announced = value;
179
+ rootEl.dispatchEvent(new CustomEvent(FIELD_VALUE_CHANGE_EVENT, {
180
+ bubbles: true,
181
+ detail: { value }
182
+ }));
183
+ };
184
+ const schedule = () => {
185
+ clearTimeout(timer);
186
+ timer = setTimeout(sendChange, getSettleDelay(rootEl));
187
+ };
188
+ const sendNow = () => {
189
+ clearTimeout(timer);
190
+ sendChange();
191
+ };
192
+ const flush = () => {
193
+ if (timer !== void 0) sendNow();
194
+ };
195
+ const listeners = {
196
+ focusin: () => send({ type: "CONTROL.FOCUS" }),
197
+ focusout: (event) => {
198
+ if (isInsideField(rootEl, event.relatedTarget)) return;
199
+ flush();
200
+ send({
201
+ type: "CONTROL.BLUR",
202
+ value: read()
203
+ });
204
+ },
205
+ input: schedule,
206
+ change: (event) => event.isTrusted ? sendNow() : schedule(),
207
+ click: (event) => {
208
+ if (!event.isTrusted && isChoiceInput(event.target)) schedule();
209
+ },
210
+ invalid: (event) => {
211
+ event.preventDefault();
212
+ send({
213
+ type: "SUBMIT.INVALID",
214
+ value: read()
215
+ });
216
+ }
217
+ };
218
+ for (const [type, listener] of Object.entries(listeners)) rootEl.addEventListener(type, listener, true);
219
+ return () => {
220
+ clearTimeout(timer);
221
+ for (const [type, listener] of Object.entries(listeners)) rootEl.removeEventListener(type, listener, true);
222
+ };
223
+ }
155
224
  },
156
- updateInvalid({ context, prop }) {
157
- const formMachineCtx = context.get("formMachine")?.context;
158
- if (!formMachineCtx) {
159
- context.set("invalid", prop("invalid") ?? false);
160
- return;
225
+ actions: {
226
+ setFocused({ context }) {
227
+ context.set("focused", true);
228
+ },
229
+ setBlurred({ context }) {
230
+ context.set("focused", false);
231
+ context.set("touched", true);
232
+ },
233
+ trackValueState({ context, refs, event }) {
234
+ const value = event.value;
235
+ const dirty = value !== (refs.get("initialValue") ?? "");
236
+ context.set("dirty", dirty);
237
+ if (dirty) refs.set("markedDirty", true);
238
+ context.set("filled", value.length > 0);
239
+ },
240
+ dropStaleValidation({ context, refs }) {
241
+ if (!context.get("validating")) return;
242
+ refs.set("seq", refs.get("seq") + 1);
243
+ context.set("validating", false);
244
+ },
245
+ recoverValueMissing(params) {
246
+ const { context, event } = params;
247
+ const validity = context.get("validity");
248
+ if (!validity || validity.valid || !validity.valueMissing) return;
249
+ const nextValidity = readValidity(params, event.value);
250
+ if (!nextValidity.valid) return;
251
+ applyValidation(params, {
252
+ customErrors: [],
253
+ validity: nextValidity,
254
+ value: event.value,
255
+ nativeMessage: ""
256
+ });
257
+ },
258
+ markSubmitAttempted({ context, refs }) {
259
+ context.set("submitAttempted", true);
260
+ refs.set("markedDirty", true);
261
+ },
262
+ forceDirty({ refs }) {
263
+ refs.set("markedDirty", true);
264
+ },
265
+ commitValidation(params) {
266
+ commitValidation(params, { value: params.event.value });
267
+ },
268
+ applyAsyncValidation(params) {
269
+ const { event } = params;
270
+ applyValidation(params, {
271
+ customErrors: toErrorArray(event.result),
272
+ validity: event.validity,
273
+ value: event.value,
274
+ nativeMessage: event.nativeMessage
275
+ });
276
+ },
277
+ clearValidation(params) {
278
+ clearValidation(params);
279
+ },
280
+ resetField(params) {
281
+ const { context, refs } = params;
282
+ clearValidation(params);
283
+ context.set("touched", false);
284
+ context.set("dirty", false);
285
+ context.set("focused", false);
286
+ context.set("submitAttempted", false);
287
+ refs.set("markedDirty", false);
288
+ context.set("filled", (refs.get("initialValue") ?? "").length > 0);
289
+ },
290
+ rebaseline(params) {
291
+ rebaseline(params);
161
292
  }
162
- const errs = (formMachineCtx.get("errors")?.[prop("name")])?.messages ?? [];
163
- context.set("invalid", errs.length > 0);
164
293
  }
165
- } }
294
+ }
166
295
  });
296
+ function commitValidation(params, options = {}) {
297
+ const { prop, context, refs, send } = params;
298
+ const hostEl = getHostEl(params);
299
+ hostEl?.setCustomValidity("");
300
+ const value = options.value ?? readValue(params);
301
+ let validity = readValidity(params, value);
302
+ if (!refs.get("markedDirty") && !context.get("dirty")) validity = suppressValueMissing(validity);
303
+ const nativeMessage = validity.valid ? "" : getNativeMessage(params, hostEl, validity);
304
+ const seq = refs.get("seq") + 1;
305
+ refs.set("seq", seq);
306
+ const result = prop("validate")?.({
307
+ value,
308
+ validity
309
+ });
310
+ if (isPromise$1(result)) {
311
+ context.set("validating", true);
312
+ result.then((resolved) => send({
313
+ type: "VALIDATE.RESOLVE",
314
+ seq,
315
+ result: resolved,
316
+ validity,
317
+ value,
318
+ nativeMessage
319
+ }), (error) => {
320
+ send({
321
+ type: "VALIDATE.RESOLVE",
322
+ seq,
323
+ result: null,
324
+ validity,
325
+ value,
326
+ nativeMessage
327
+ });
328
+ queueMicrotask(() => {
329
+ throw error;
330
+ });
331
+ });
332
+ return;
333
+ }
334
+ applyValidation(params, {
335
+ customErrors: toErrorArray(result),
336
+ validity,
337
+ value,
338
+ nativeMessage
339
+ });
340
+ }
341
+ function applyValidation(params, options) {
342
+ const { context, prop } = params;
343
+ const { errors, validity } = resolveValidation(options);
344
+ if (options.customErrors.length > 0) getHostEl(params)?.setCustomValidity(options.customErrors.join(" "));
345
+ const changed = context.get("validity")?.valid !== validity.valid || !isEqual(context.get("errors"), errors);
346
+ context.set("validating", false);
347
+ context.set("validity", validity);
348
+ context.set("errors", errors);
349
+ if (changed) prop("onValidityChange")?.({
350
+ valid: validity.valid,
351
+ errors,
352
+ validity,
353
+ value: options.value
354
+ });
355
+ }
356
+ function clearValidation(params) {
357
+ const { context, refs } = params;
358
+ refs.set("seq", refs.get("seq") + 1);
359
+ context.set("validating", false);
360
+ context.set("errors", []);
361
+ context.set("validity", null);
362
+ getHostEl(params)?.setCustomValidity("");
363
+ }
364
+ /**
365
+ * The native control that hosts constraint validation (`setCustomValidity`, `validity`), when there
366
+ * is one: the element the control id points at, if it is the one carrying the field's name. Looked
367
+ * up at call time, and absent for groups, a Slider, or a Combobox's visible input (no name).
368
+ */
369
+ function getHostEl({ scope, prop }) {
370
+ const controlEl = getControlEl(scope);
371
+ if (!controlEl || typeof controlEl.setCustomValidity !== "function") return null;
372
+ return trimArraySuffix(controlEl.name) === trimArraySuffix(prop("name")) ? controlEl : null;
373
+ }
374
+ /** Makes the current value the baseline for `dirty`, unless the user already changed it. */
375
+ function rebaseline(params) {
376
+ const { context, refs } = params;
377
+ if (context.get("dirty") || refs.get("markedDirty")) return;
378
+ const value = readValue(params);
379
+ refs.set("initialValue", value);
380
+ context.set("filled", value.length > 0);
381
+ }
382
+ function readValue({ refs, prop }) {
383
+ return getComparableFieldValue(refs.get("rootEl"), prop("name"), prop("defaultValue"));
384
+ }
385
+ /** Native flags of the host, with `valueMissing` decided by the field's `required` and its value. */
386
+ function readValidity(params, value) {
387
+ const { prop } = params;
388
+ const hostEl = getHostEl(params);
389
+ return withValueMissing(hostEl ? getValiditySnapshot(hostEl) : VALID_SNAPSHOT, !!prop("required") && !prop("readOnly") && value === "");
390
+ }
391
+ function getNativeMessage(params, hostEl, validity) {
392
+ if (hostEl?.validationMessage) return hostEl.validationMessage;
393
+ if (!validity.valueMissing) return "";
394
+ const probe = params.scope.getDoc().createElement("input");
395
+ probe.required = true;
396
+ return probe.validationMessage;
397
+ }
398
+ function isPromise$1(value) {
399
+ return typeof value === "object" && value !== null && "then" in value;
400
+ }
401
+ /** At least a short settle, whatever the form's `inputDebounceMs`: see `schedule` in `trackFieldEvents`. */
402
+ function getSettleDelay(rootEl) {
403
+ const debounceMs = getFormMachineFor(rootEl)?.prop("inputDebounceMs") ?? 100;
404
+ return Math.max(debounceMs, 50);
405
+ }
406
+ function isChoiceInput(target) {
407
+ return target instanceof HTMLInputElement && (target.type === "checkbox" || target.type === "radio");
408
+ }
409
+
410
+ //#endregion
411
+ //#region Resources/Private/Primitives/Field/src/field.pointer.ts
412
+ /**
413
+ * Whether a pointer button is held down, and a way to wait for its release. A field blurs on the
414
+ * mousedown that moves the focus away, and showing its error text then shifts the layout: by the
415
+ * mouseup the button the user pressed has moved, and the click is lost.
416
+ */
417
+ const MAX_PRESS_MS = 1500;
418
+ const waiting = /* @__PURE__ */ new Set();
419
+ let pressed = false;
420
+ let installed = false;
421
+ let giveUp;
422
+ function release() {
423
+ pressed = false;
424
+ clearTimeout(giveUp);
425
+ const callbacks = Array.from(waiting);
426
+ waiting.clear();
427
+ callbacks.forEach((callback) => callback());
428
+ }
429
+ function install() {
430
+ if (installed) return;
431
+ installed = true;
432
+ document.addEventListener("pointerdown", () => {
433
+ pressed = true;
434
+ clearTimeout(giveUp);
435
+ giveUp = setTimeout(release, MAX_PRESS_MS);
436
+ }, true);
437
+ for (const type of ["pointerup", "pointercancel"]) document.addEventListener(type, () => setTimeout(release, 0), true);
438
+ }
439
+ function isPointerPressed() {
440
+ install();
441
+ return pressed;
442
+ }
443
+ /** Runs `callback` once no pointer button is held down, right away if none is. Returns a cancel function. */
444
+ function whenPointerReleased(callback) {
445
+ install();
446
+ if (!pressed) {
447
+ callback();
448
+ return () => {};
449
+ }
450
+ waiting.add(callback);
451
+ return () => waiting.delete(callback);
452
+ }
453
+
454
+ //#endregion
455
+ //#region Resources/Private/Primitives/Field/src/field.props.ts
456
+ /**
457
+ * Derived from @zag-js/field of Zag.js (https://github.com/chakra-ui/zag, packages/machines/field,
458
+ * commit 0ce8e3de63b571d37e438bcf28e64759466e56e9), MIT License, Copyright (c) 2021 Chakra UI.
459
+ * Adapted for Fluid Primitives, the git history shows what changed from the original.
460
+ */
461
+ const props = createProps()([
462
+ "defaultValue",
463
+ "dir",
464
+ "dirty",
465
+ "disabled",
466
+ "getRootNode",
467
+ "id",
468
+ "ids",
469
+ "invalid",
470
+ "listenTo",
471
+ "name",
472
+ "onValidityChange",
473
+ "readOnly",
474
+ "required",
475
+ "touched",
476
+ "validate",
477
+ "validationMode"
478
+ ]);
479
+ const splitProps = createSplitProps(props);
167
480
 
168
481
  //#endregion
169
482
  //#region Resources/Private/Primitives/Field/Field.ts
170
483
  var Field = class extends Component {
171
- static name = "field";
172
- subscribedToForm = false;
484
+ static componentName = "field";
485
+ /** The `validate` a consumer set; the machine itself runs {@link validate}, which adds the form's. */
486
+ ownValidate;
487
+ cleanups = [];
488
+ cancelDeferredRender;
489
+ /** The base class types its machine as `Machine<any>`. */
490
+ get fieldMachine() {
491
+ return this.machine;
492
+ }
173
493
  initMachine(props) {
174
- const createdMachine = new Machine(machine, props);
175
- registerFieldMachine(this.getElement("root"), createdMachine);
176
- registerFieldMachineForForm(this.getElement("root"), createdMachine);
494
+ const [machineProps] = splitProps(props);
495
+ this.ownValidate = machineProps.validate;
496
+ const createdMachine = new Machine$1(machine, {
497
+ ...machineProps,
498
+ validate: (details) => this.validate(details)
499
+ });
500
+ const rootEl = this.hydrator.query("root");
501
+ registerFieldMachine(rootEl, createdMachine);
502
+ registerFieldMachineForForm(rootEl, createdMachine);
177
503
  return createdMachine;
178
504
  }
179
505
  initApi() {
180
- return connect(this.machine.service, normalizeProps);
506
+ return connectField(this.fieldMachine);
181
507
  }
182
- subscribeToFormMachine() {
183
- if (this.subscribedToForm) return;
184
- const formMachine = this.api.getFormMachine();
185
- if (!formMachine) return;
186
- this.subscribedToForm = true;
187
- formMachine.subscribe(() => {
188
- this.machine.notify();
189
- });
508
+ init() {
509
+ super.init();
510
+ this.listenToDependencies();
511
+ }
512
+ updateProps(newProps) {
513
+ const { validate, ...rest } = newProps;
514
+ if ("validate" in newProps) this.ownValidate = validate;
515
+ super.updateProps(rest);
190
516
  }
191
517
  render() {
192
- this.subscribeToFormMachine();
193
- const rootEl = this.getElement("root");
518
+ const rootEl = this.hydrator.query("root");
194
519
  if (rootEl) this.spreadProps(rootEl, this.api.getRootProps());
195
- const labelEl = this.getElement("label");
520
+ const labelEl = this.hydrator.query("label");
196
521
  if (labelEl) this.spreadProps(labelEl, this.api.getLabelProps());
197
- const controlEl = this.getElement("control");
198
- if (controlEl) this.spreadProps(controlEl, this.api.getControlProps());
199
- const descriptionEl = this.getElement("description");
200
- if (descriptionEl) this.spreadProps(descriptionEl, this.api.getDescriptionProps());
201
- const errorEl = this.getElement("error");
202
- if (errorEl) {
203
- this.spreadProps(errorEl, this.api.getErrorProps());
204
- const msg = this.api.getErrorText();
205
- if (msg) errorEl.textContent = msg;
522
+ const controlEl = this.hydrator.query("control");
523
+ if (controlEl) this.spreadProps(controlEl, this.getControlProps(controlEl));
524
+ const helperTextEl = this.hydrator.query("helperText");
525
+ if (helperTextEl) this.spreadProps(helperTextEl, this.api.getHelperTextProps());
526
+ if (isPointerPressed()) {
527
+ this.cancelDeferredRender ??= whenPointerReleased(() => {
528
+ this.cancelDeferredRender = void 0;
529
+ this.render();
530
+ });
531
+ return;
532
+ }
533
+ this.spreadPropsByOptionalValue("errorText", ({ el, value }) => {
534
+ if (value === void 0 && this.api.errors.length > 0) el.textContent = this.api.errors.join(" ");
535
+ return this.api.getErrorTextProps({ match: value });
536
+ });
537
+ this.spreadPropsByValue("indicator", ({ value }) => this.api.getIndicatorProps({ type: value }));
538
+ }
539
+ destroy() {
540
+ const rootEl = this.fieldMachine.refs.get("rootEl");
541
+ if (rootEl) unregisterFieldMachine(rootEl);
542
+ unregisterFieldMachineForForm(rootEl, this.fieldMachine);
543
+ this.cancelDeferredRender?.();
544
+ for (const cleanup of this.cleanups.splice(0)) cleanup();
545
+ super.destroy();
546
+ }
547
+ /**
548
+ * Zag's props differ per element only in `readOnly`, and the control part is the one place a
549
+ * bare native element is wired up by the field itself (primitives bring their own control).
550
+ */
551
+ getControlProps(controlEl) {
552
+ switch (controlEl.localName) {
553
+ case "input": return this.api.getInputProps();
554
+ case "textarea": return this.api.getTextareaProps();
555
+ case "select": return this.api.getSelectProps();
556
+ default: return this.api.getControlProps();
557
+ }
558
+ }
559
+ /** What the machine validates with: the form's messages for this field, then the consumer's own. */
560
+ validate(details) {
561
+ const formMachine = getFormMachineFor(this.fieldMachine.refs.get("rootEl"));
562
+ const formMessages = formMachine ? getFieldMessages(formMachine, this.fieldMachine.prop("name")) : [];
563
+ const own = this.ownValidate?.(details);
564
+ if (isPromise(own)) return own.then((result) => [...formMessages, ...toErrorArray(result)]);
565
+ return [...formMessages, ...toErrorArray(own)];
566
+ }
567
+ /**
568
+ * `listenTo`: when a named sibling field in the same form changes value, this field announces
569
+ * `fluid-primitives:field:dependencychange` for consumer DOM reactions and treats it like a
570
+ * change of its own, so it revalidates under the same rules as when its user types.
571
+ *
572
+ * A sibling's registration may come later than this field's (the form can hydrate after its
573
+ * fields), so subscribing is retried whenever a form registers, until every name resolved.
574
+ */
575
+ listenToDependencies() {
576
+ const listenTo = this.fieldMachine.prop("listenTo");
577
+ const rootEl = this.fieldMachine.refs.get("rootEl");
578
+ const formEl = rootEl?.closest("form");
579
+ if (!listenTo?.length || !rootEl || !formEl) return;
580
+ const names = listenTo.map(trimArraySuffix);
581
+ const pending = new Set(names);
582
+ const readSibling = (sibling) => getCurrentFieldValue(sibling.refs.get("rootEl"), sibling.prop("name"), sibling.prop("defaultValue"));
583
+ const announce = () => {
584
+ const dependencies = {};
585
+ listenTo.forEach((name, index) => {
586
+ const sibling = getFieldMachinesFor(rootEl).get(names[index]);
587
+ dependencies[name] = sibling ? readSibling(sibling) : null;
588
+ });
589
+ const detail = {
590
+ name: this.fieldMachine.prop("name"),
591
+ dependencies,
592
+ values: createFormValues(new FormData(formEl))
593
+ };
594
+ rootEl.dispatchEvent(new CustomEvent(FIELD_DEPENDENCY_CHANGE_EVENT, {
595
+ bubbles: true,
596
+ detail
597
+ }));
598
+ this.fieldMachine.send({
599
+ type: "CONTROL.CHANGE",
600
+ value: getComparableFieldValue(rootEl, this.fieldMachine.prop("name"), this.fieldMachine.prop("defaultValue"))
601
+ });
602
+ };
603
+ const trySubscribe = () => {
604
+ for (const name of Array.from(pending)) {
605
+ const siblingRootEl = getFieldMachinesFor(rootEl).get(name)?.refs.get("rootEl");
606
+ if (!siblingRootEl) continue;
607
+ siblingRootEl.addEventListener(FIELD_VALUE_CHANGE_EVENT, announce);
608
+ this.cleanups.push(() => siblingRootEl.removeEventListener(FIELD_VALUE_CHANGE_EVENT, announce));
609
+ pending.delete(name);
610
+ }
611
+ if (pending.size === 0) formEl.removeEventListener("fluid-primitives:form:registered", trySubscribe);
612
+ };
613
+ trySubscribe();
614
+ if (pending.size > 0) {
615
+ formEl.addEventListener("fluid-primitives:form:registered", trySubscribe);
616
+ this.cleanups.push(() => formEl.removeEventListener("fluid-primitives:form:registered", trySubscribe));
206
617
  }
207
618
  }
208
619
  };
620
+ function isPromise(value) {
621
+ return typeof value === "object" && value !== null && "then" in value;
622
+ }
209
623
 
210
624
  //#endregion
211
625
  export { Field };