@jobber/components 9.5.3 → 9.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (178) hide show
  1. package/Combobox.d.ts +1 -0
  2. package/Combobox.js +17 -0
  3. package/dist/Autocomplete/index.cjs +0 -2
  4. package/dist/Autocomplete/index.mjs +0 -2
  5. package/dist/Autocomplete-cjs.js +4 -5
  6. package/dist/Autocomplete-es.js +1 -2
  7. package/dist/Card/index.cjs +2 -2
  8. package/dist/Card/index.mjs +2 -2
  9. package/dist/Chip/index.cjs +0 -2
  10. package/dist/Chip/index.mjs +0 -2
  11. package/dist/Chips/InternalChipDismissible/hooks/index.cjs +0 -2
  12. package/dist/Chips/InternalChipDismissible/hooks/index.mjs +0 -2
  13. package/dist/Chips/InternalChipDismissible/index.cjs +0 -2
  14. package/dist/Chips/InternalChipDismissible/index.mjs +0 -2
  15. package/dist/Chips/index.cjs +0 -2
  16. package/dist/Chips/index.mjs +0 -2
  17. package/dist/Combobox/Combobox.contentSlots.d.ts +5 -0
  18. package/dist/Combobox/Combobox.context.d.ts +34 -0
  19. package/dist/Combobox/Combobox.d.ts +69 -0
  20. package/dist/Combobox/Combobox.footer.d.ts +7 -0
  21. package/dist/Combobox/Combobox.items.d.ts +25 -0
  22. package/dist/Combobox/Combobox.responsive.d.ts +54 -0
  23. package/dist/Combobox/Combobox.selectAll.d.ts +4 -0
  24. package/dist/Combobox/Combobox.slotFactory.d.ts +7 -0
  25. package/dist/Combobox/Combobox.testUtils.d.ts +12 -0
  26. package/dist/Combobox/Combobox.types.d.ts +166 -0
  27. package/dist/Combobox/index.cjs +35 -0
  28. package/dist/Combobox/index.d.ts +2 -0
  29. package/dist/Combobox/index.mjs +29 -0
  30. package/dist/Combobox-cjs.js +675 -0
  31. package/dist/Combobox-es.js +673 -0
  32. package/dist/ComboboxPrimitive-cjs.js +88 -47
  33. package/dist/ComboboxPrimitive-es.js +39 -23
  34. package/dist/{ComboboxChipRemove-cjs.js → ComboboxSeparator-cjs.js} +501 -455
  35. package/dist/{ComboboxChipRemove-es.js → ComboboxSeparator-es.js} +500 -456
  36. package/dist/ConfirmationModal/index.cjs +0 -2
  37. package/dist/ConfirmationModal/index.mjs +0 -2
  38. package/dist/ContentBlock/index.cjs +1 -0
  39. package/dist/ContentBlock/index.mjs +1 -0
  40. package/dist/ContentBlock-cjs.js +2 -18
  41. package/dist/ContentBlock-es.js +1 -17
  42. package/dist/DataDump/index.cjs +2 -2
  43. package/dist/DataDump/index.mjs +2 -2
  44. package/dist/DataList/components/DataListActions/index.cjs +0 -2
  45. package/dist/DataList/components/DataListActions/index.mjs +0 -2
  46. package/dist/DataList/components/DataListBulkActions/index.cjs +0 -2
  47. package/dist/DataList/components/DataListBulkActions/index.mjs +0 -2
  48. package/dist/DataList/components/DataListFilters/components/DataListSort/index.cjs +0 -2
  49. package/dist/DataList/components/DataListFilters/components/DataListSort/index.mjs +0 -2
  50. package/dist/DataList/components/DataListFilters/index.cjs +0 -2
  51. package/dist/DataList/components/DataListFilters/index.mjs +0 -2
  52. package/dist/DataList/components/DataListHeader/index.cjs +0 -2
  53. package/dist/DataList/components/DataListHeader/index.mjs +0 -2
  54. package/dist/DataList/components/DataListItem/index.cjs +0 -2
  55. package/dist/DataList/components/DataListItem/index.mjs +0 -2
  56. package/dist/DataList/components/DataListItemActions/index.cjs +0 -2
  57. package/dist/DataList/components/DataListItemActions/index.mjs +0 -2
  58. package/dist/DataList/components/DataListItemActionsOverflow/index.cjs +0 -2
  59. package/dist/DataList/components/DataListItemActionsOverflow/index.mjs +0 -2
  60. package/dist/DataList/components/DataListItems/index.cjs +0 -2
  61. package/dist/DataList/components/DataListItems/index.mjs +0 -2
  62. package/dist/DataList/components/DataListLayout/index.cjs +0 -2
  63. package/dist/DataList/components/DataListLayout/index.mjs +0 -2
  64. package/dist/DataList/components/DataListLayoutActions/index.cjs +0 -2
  65. package/dist/DataList/components/DataListLayoutActions/index.mjs +0 -2
  66. package/dist/DataList/index.cjs +0 -2
  67. package/dist/DataList/index.mjs +0 -2
  68. package/dist/DatePicker/index.cjs +0 -2
  69. package/dist/DatePicker/index.mjs +0 -2
  70. package/dist/DatePicker-cjs.js +4 -5
  71. package/dist/DatePicker-es.js +1 -2
  72. package/dist/Dialog/index.cjs +2 -2
  73. package/dist/Dialog/index.mjs +2 -2
  74. package/dist/DrawerDescription-cjs.js +268 -99
  75. package/dist/DrawerDescription-es.js +270 -103
  76. package/dist/DrawerTitle-cjs.js +2 -6
  77. package/dist/DrawerTitle-es.js +3 -7
  78. package/dist/FieldDescription-cjs.js +184 -181
  79. package/dist/FieldDescription-es.js +187 -184
  80. package/dist/FilterPicker/components/FilterPickerActivator/index.cjs +0 -2
  81. package/dist/FilterPicker/components/FilterPickerActivator/index.mjs +0 -2
  82. package/dist/FilterPicker/components/FilterPickerContent/index.cjs +0 -2
  83. package/dist/FilterPicker/components/FilterPickerContent/index.mjs +0 -2
  84. package/dist/FilterPicker/components/FilterPickerTrigger/index.cjs +0 -2
  85. package/dist/FilterPicker/components/FilterPickerTrigger/index.mjs +0 -2
  86. package/dist/FilterPicker/index.cjs +0 -2
  87. package/dist/FilterPicker/index.mjs +0 -2
  88. package/dist/FilterPickerContent-cjs.js +4 -5
  89. package/dist/FilterPickerContent-es.js +1 -2
  90. package/dist/FormatFile/index.cjs +0 -2
  91. package/dist/FormatFile/index.mjs +0 -2
  92. package/dist/Gallery/index.cjs +0 -2
  93. package/dist/Gallery/index.mjs +0 -2
  94. package/dist/InputDate/index.cjs +0 -2
  95. package/dist/InputDate/index.mjs +0 -2
  96. package/dist/InputNumber/index.cjs +2 -2
  97. package/dist/InputNumber/index.mjs +2 -2
  98. package/dist/InputNumber/types.d.ts +5 -0
  99. package/dist/InputNumber-cjs.js +2 -2
  100. package/dist/InputNumber-es.js +2 -2
  101. package/dist/LightBox/index.cjs +0 -2
  102. package/dist/LightBox/index.mjs +0 -2
  103. package/dist/Menu/index.cjs +2 -2
  104. package/dist/Menu/index.mjs +2 -2
  105. package/dist/Menu-cjs.js +4 -5
  106. package/dist/Menu-es.js +1 -2
  107. package/dist/MenuSubmenuTrigger-cjs.js +206 -164
  108. package/dist/MenuSubmenuTrigger-es.js +194 -152
  109. package/dist/Modal/index.cjs +0 -2
  110. package/dist/Modal/index.mjs +1 -3
  111. package/dist/NumberFieldInput-cjs.js +187 -308
  112. package/dist/NumberFieldInput-es.js +187 -308
  113. package/dist/Page/index.cjs +2 -2
  114. package/dist/Page/index.mjs +2 -2
  115. package/dist/Popover/index.cjs +0 -2
  116. package/dist/Popover/index.mjs +0 -2
  117. package/dist/Popover-cjs.js +7 -8
  118. package/dist/Popover-es.js +1 -2
  119. package/dist/ScrollAreaViewport-cjs.js +747 -1093
  120. package/dist/ScrollAreaViewport-es.js +734 -1081
  121. package/dist/Select/index.cjs +2 -2
  122. package/dist/Select/index.mjs +2 -2
  123. package/dist/SelectGroupLabel-cjs.js +207 -306
  124. package/dist/SelectGroupLabel-es.js +203 -302
  125. package/dist/SelectPrimitive-cjs.js +11 -17
  126. package/dist/SelectPrimitive-es.js +1 -7
  127. package/dist/Tooltip/index.cjs +0 -2
  128. package/dist/Tooltip/index.mjs +0 -2
  129. package/dist/Tooltip-cjs.js +6 -7
  130. package/dist/Tooltip-es.js +1 -2
  131. package/dist/buttonRenderAdapter-cjs.js +2 -1
  132. package/dist/buttonRenderAdapter-es.js +2 -1
  133. package/dist/dialogReturnFocus-cjs.js +1 -1
  134. package/dist/dialogReturnFocus-es.js +2 -2
  135. package/dist/docs/InputNumber/InputNumber.md +2 -2
  136. package/dist/docs/SelectPrimitive/SelectPrimitive.md +1 -1
  137. package/dist/floating-ui.react-cjs.js +2557 -185
  138. package/dist/floating-ui.react-es.js +2512 -149
  139. package/dist/getMappedBreakpointWidth-cjs.js +21 -0
  140. package/dist/getMappedBreakpointWidth-es.js +18 -0
  141. package/dist/index.cjs +15 -10
  142. package/dist/index.d.mts +1 -0
  143. package/dist/index.d.ts +1 -0
  144. package/dist/index.mjs +14 -10
  145. package/dist/mergeClassName-cjs.js +12 -0
  146. package/dist/mergeClassName-es.js +10 -0
  147. package/dist/primitives/BottomSheet/index.cjs +1 -1
  148. package/dist/primitives/BottomSheet/index.mjs +1 -1
  149. package/dist/primitives/ComboboxPrimitive/ComboboxPrimitive.d.ts +2 -2
  150. package/dist/primitives/ComboboxPrimitive/index.cjs +4 -5
  151. package/dist/primitives/ComboboxPrimitive/index.mjs +4 -5
  152. package/dist/primitives/SelectPrimitive/index.cjs +1 -2
  153. package/dist/primitives/SelectPrimitive/index.mjs +1 -2
  154. package/dist/primitives/index.cjs +3 -3
  155. package/dist/primitives/index.mjs +3 -3
  156. package/dist/scrollEdges-cjs.js +1 -11
  157. package/dist/scrollEdges-es.js +1 -11
  158. package/dist/scrollable-cjs.js +58 -0
  159. package/dist/scrollable-es.js +54 -0
  160. package/dist/styles.css +759 -42
  161. package/dist/unstyledPrimitives/index.cjs +720 -478
  162. package/dist/unstyledPrimitives/index.mjs +663 -421
  163. package/dist/useCompositeListItem-cjs.js +2108 -361
  164. package/dist/useCompositeListItem-es.js +2076 -331
  165. package/dist/useLabel-cjs.js +2 -3
  166. package/dist/useLabel-es.js +2 -3
  167. package/dist/useRenderElement-cjs.js +9 -7
  168. package/dist/useRenderElement-es.js +9 -7
  169. package/dist/useScrollLock-cjs.js +819 -344
  170. package/dist/useScrollLock-es.js +732 -296
  171. package/dist/useScrollToActive-cjs.js +4 -5
  172. package/dist/useScrollToActive-es.js +1 -2
  173. package/dist/utils/meta/meta.json +43 -1
  174. package/package.json +8 -3
  175. package/dist/floating-ui.react-dom-cjs.js +0 -2103
  176. package/dist/floating-ui.react-dom-es.js +0 -2072
  177. package/dist/floating-ui.utils-cjs.js +0 -342
  178. package/dist/floating-ui.utils-es.js +0 -300
@@ -1,7 +1,7 @@
1
1
  import * as React from 'react';
2
- import { u as useBaseUiId, b as useStableCallback, k as useTimeout, a as useIsoLayoutEffect, S as SafeReact, l as error, t as transitionStatusMapping, m as useTransitionStatus, f as useOpenChangeComplete } from './useScrollLock-es.js';
3
- import { u as useLabelableContext, L as LabelableContext, a as useFormContext, D as DEFAULT_VALIDITY_STATE, f as fieldValidityMapping, F as FieldRootContext, b as useFieldRootContext } from './useCompositeListItem-es.js';
4
- import { f as formatErrorMessage, a as useRefWithInit, m as mergeProps, E as EMPTY_OBJECT, u as useRenderElement } from './useRenderElement-es.js';
2
+ import { q as useTimeout, d as useStableCallback, u as useBaseUiId, c as useIsoLayoutEffect, S as SafeReact, r as error, t as transitionStatusMapping, s as useTransitionStatus, l as useOpenChangeComplete } from './useScrollLock-es.js';
3
+ import { a as useFormContext, u as useLabelableContext, D as DEFAULT_VALIDITY_STATE, L as LabelableContext, f as fieldValidityMapping, F as FieldRootContext, b as useFieldRootContext } from './useCompositeListItem-es.js';
4
+ import { a as useRefWithInit, m as mergeProps, E as EMPTY_OBJECT, f as formatErrorMessage, u as useRenderElement } from './useRenderElement-es.js';
5
5
  import { jsx } from 'react/jsx-runtime';
6
6
  import { u as useLabel } from './useLabel-es.js';
7
7
 
@@ -14,77 +14,6 @@ function useFieldItemContext() {
14
14
  return context;
15
15
  }
16
16
 
17
- const FieldsetRootContext = /*#__PURE__*/React.createContext(undefined);
18
- if (process.env.NODE_ENV !== "production") FieldsetRootContext.displayName = "FieldsetRootContext";
19
- function useFieldsetRootContext(optional = false) {
20
- const context = React.useContext(FieldsetRootContext);
21
- if (!context && !optional) {
22
- throw new Error(process.env.NODE_ENV !== "production" ? 'Base UI: FieldsetRootContext is missing. Fieldset parts must be placed within <Fieldset.Root>.' : formatErrorMessage(86));
23
- }
24
- return context;
25
- }
26
-
27
- const LabelableProvider = function LabelableProvider(props) {
28
- const defaultId = useBaseUiId();
29
- const initialControlId = props.controlId === undefined ? defaultId : props.controlId;
30
- const [controlId, setControlIdState] = React.useState(initialControlId);
31
- const [labelId, setLabelId] = React.useState(props.labelId);
32
- const [messageIds, setMessageIds] = React.useState([]);
33
- const registrationsRef = useRefWithInit(() => new Map());
34
- const {
35
- messageIds: parentMessageIds
36
- } = useLabelableContext();
37
- const registerControlId = useStableCallback((source, nextId) => {
38
- const registrations = registrationsRef.current;
39
- if (nextId === undefined) {
40
- registrations.delete(source);
41
- return;
42
- }
43
- registrations.set(source, nextId);
44
-
45
- // Only flush when registering, not when unregistering.
46
- // This prevents loops during rapid unmount/remount cycles (e.g. React Activity).
47
- // The next registration will pick up the correct state.
48
- setControlIdState(prev => {
49
- if (registrations.size === 0) {
50
- return undefined;
51
- }
52
- let nextControlId;
53
- for (const id of registrations.values()) {
54
- if (prev !== undefined && id === prev) {
55
- return prev;
56
- }
57
- if (nextControlId === undefined) {
58
- nextControlId = id;
59
- }
60
- }
61
- return nextControlId;
62
- });
63
- });
64
- const getDescriptionProps = React.useCallback(externalProps => {
65
- const ids = externalProps['aria-describedby'] ? externalProps['aria-describedby'].split(' ') : [];
66
- ids.push(...parentMessageIds, ...messageIds);
67
- return {
68
- ...externalProps,
69
- 'aria-describedby': Array.from(new Set(ids)).join(' ') || undefined
70
- };
71
- }, [parentMessageIds, messageIds]);
72
- const contextValue = React.useMemo(() => ({
73
- controlId,
74
- registerControlId,
75
- labelId,
76
- setLabelId,
77
- messageIds,
78
- setMessageIds,
79
- getDescriptionProps
80
- }), [controlId, registerControlId, labelId, setLabelId, messageIds, setMessageIds, getDescriptionProps]);
81
- return /*#__PURE__*/jsx(LabelableContext.Provider, {
82
- value: contextValue,
83
- children: props.children
84
- });
85
- };
86
- if (process.env.NODE_ENV !== "production") LabelableProvider.displayName = "LabelableProvider";
87
-
88
17
  /**
89
18
  * Combines the field's client-side, stateful validity data with the external invalid state to
90
19
  * determine the field's true validity.
@@ -100,34 +29,35 @@ function getCombinedFieldValidityData(validityData, invalid) {
100
29
  }
101
30
 
102
31
  const validityKeys = Object.keys(DEFAULT_VALIDITY_STATE);
103
- function isOnlyValueMissing(state) {
104
- if (!state || state.valid || !state.valueMissing) {
32
+ /**
33
+ * Whether an input participates in the surrounding Base UI Form. Inputs that are effectively
34
+ * disabled, or whose `form` attribute explicitly associates them with another form, are excluded.
35
+ * DOM position only matters when it associates the input with a different form. Otherwise, field
36
+ * registration is context-driven, so portaled inputs (for example inside a dialog) still belong to
37
+ * the form for both validation and values projected into `onFormSubmit`.
38
+ */
39
+ function isEligibleInput(input, formElement) {
40
+ if (input.matches(':disabled')) {
105
41
  return false;
106
42
  }
107
- let onlyValueMissing = false;
108
- for (const key of validityKeys) {
109
- if (key === 'valid') {
110
- continue;
111
- }
112
- if (key === 'valueMissing') {
113
- onlyValueMissing = state[key];
114
- } else if (state[key]) {
115
- onlyValueMissing = false;
116
- }
43
+ if (!formElement || input.form === formElement) {
44
+ return true;
117
45
  }
118
- return onlyValueMissing;
46
+
47
+ // React context crosses portal boundaries. An unassociated portaled input still participates in
48
+ // contextual validation, unless an explicit `form` attribute opts it out of the surrounding Form.
49
+ return input.form === null && !input.hasAttribute('form');
119
50
  }
120
51
 
121
52
  /**
122
53
  * Picks the input whose native validity should represent a field that owns several inputs (such as a
123
- * checkbox group). Prefers the first enabled currently-invalid input, where "first" follows Set
124
- * insertion order (mount order), and otherwise returns the first enabled input. Disabled inputs are
125
- * skipped because they don't participate in native constraint validation.
54
+ * checkbox or radio group). Prefers the first eligible currently-invalid input, where "first" follows
55
+ * registration order (mount order), and otherwise returns the first eligible input.
126
56
  */
127
- function findRepresentativeInput(inputs) {
57
+ function findRepresentativeInput(inputs, formElement) {
128
58
  let fallback = null;
129
- for (const input of inputs) {
130
- if (input.disabled) {
59
+ for (const input of inputs.keys()) {
60
+ if (!isEligibleInput(input, formElement)) {
131
61
  continue;
132
62
  }
133
63
  if (!input.validity.valid) {
@@ -138,17 +68,14 @@ function findRepresentativeInput(inputs) {
138
68
  return fallback;
139
69
  }
140
70
  function clearCustomValidity(element, inputs) {
141
- let didClearElement = false;
142
- for (const input of inputs) {
71
+ for (const input of inputs.keys()) {
143
72
  input.setCustomValidity('');
144
- didClearElement ||= input === element;
145
- }
146
- if (!didClearElement) {
147
- element.setCustomValidity('');
148
73
  }
74
+ element?.setCustomValidity('');
149
75
  }
150
76
  function useFieldValidation(params) {
151
77
  const {
78
+ elementRef,
152
79
  formRef
153
80
  } = useFormContext();
154
81
  const {
@@ -160,7 +87,7 @@ function useFieldValidation(params) {
160
87
  markedDirtyRef,
161
88
  state,
162
89
  shouldValidateOnChange,
163
- getRegisteredFieldId
90
+ registeredFieldIdRef
164
91
  } = params;
165
92
  const {
166
93
  controlId,
@@ -168,32 +95,27 @@ function useFieldValidation(params) {
168
95
  } = useLabelableContext();
169
96
  const timeout = useTimeout();
170
97
  const inputRef = React.useRef(null);
171
- const registeredInputs = useRefWithInit(() => new Set()).current;
98
+ const registeredInputs = useRefWithInit(() => new Map()).current;
172
99
  const validationCommitIdRef = React.useRef(0);
173
100
 
174
- // Checkbox groups register several inputs against a single field. Track them so a `required`
175
- // checkbox can't be satisfied by another input in the group, matching native per-checkbox behavior.
176
- const registerInput = React.useCallback(element => {
177
- if (!element) {
178
- return undefined;
179
- }
180
- registeredInputs.add(element);
101
+ // Groups register several inputs against a single field so focus, validation, and form-value
102
+ // projection can use the same live controls. This also ensures a `required` checkbox can't be
103
+ // satisfied by another input in the group, matching native per-checkbox behavior.
104
+ const registerInput = React.useCallback((element, registration) => {
105
+ registeredInputs.set(element, registration);
181
106
  return () => {
182
107
  registeredInputs.delete(element);
183
108
  };
184
109
  }, [registeredInputs]);
110
+ const getInputControl = useStableCallback(() => {
111
+ const element = findRepresentativeInput(registeredInputs, elementRef.current);
112
+ return element && registeredInputs.get(element)?.controlRef.current || null;
113
+ });
185
114
  const commit = useStableCallback(async (value, revalidate = false) => {
186
- // A field can own several inputs (a checkbox group), but only the last-mounted one wins the shared
187
- // `inputRef`. Validate against the registry instead so every input counts; `inputRef` is the
188
- // fallback only when no registered input applies (none registered, or all of them disabled).
189
- const element = findRepresentativeInput(registeredInputs) ?? inputRef.current;
190
- if (!element) {
191
- return;
192
- }
193
115
  validationCommitIdRef.current += 1;
194
116
  const validationCommitId = validationCommitIdRef.current;
195
117
  function updateRegisteredFieldValidity(nextValidityData, externalInvalid = invalid) {
196
- const fieldId = getRegisteredFieldId() ?? controlId;
118
+ const fieldId = registeredFieldIdRef.current ?? controlId;
197
119
  if (fieldId == null) {
198
120
  return;
199
121
  }
@@ -207,8 +129,31 @@ function useFieldValidation(params) {
207
129
  validityData: validityDataWithFormErrors
208
130
  });
209
131
  }
132
+ function publishAllValid(input, externalInvalid) {
133
+ const nextValidityData = {
134
+ value,
135
+ state: {
136
+ ...DEFAULT_VALIDITY_STATE,
137
+ valid: true
138
+ },
139
+ error: '',
140
+ errors: [],
141
+ initialValue: validityData.initialValue
142
+ };
143
+ clearCustomValidity(input, registeredInputs);
144
+ updateRegisteredFieldValidity(nextValidityData, externalInvalid);
145
+ setValidityData(nextValidityData);
146
+ }
147
+
148
+ // A field can own several inputs (such as a checkbox or radio group), but only the last-mounted
149
+ // one wins the shared `inputRef`. Validate against the registry instead so every input counts;
150
+ // `inputRef` is the fallback only when no inputs are registered.
151
+ const element = registeredInputs.size > 0 ? findRepresentativeInput(registeredInputs, elementRef.current) : inputRef.current;
152
+ // A field with no eligible input has no native constraint, but its custom validator still
153
+ // applies to the logical value at the configured validation boundary.
154
+
210
155
  if (revalidate) {
211
- if (state.valid !== false) {
156
+ if (state.valid !== false || !element) {
212
157
  return;
213
158
  }
214
159
  const currentNativeValidity = element.validity;
@@ -216,39 +161,20 @@ function useFieldValidation(params) {
216
161
  // The 'valueMissing' (required) condition has been resolved by the user typing.
217
162
  // Temporarily mark the field as valid for this onChange event.
218
163
  // Other native errors (e.g., typeMismatch) will be caught by full validation on blur or submit.
219
- const nextValidityData = {
220
- value,
221
- state: {
222
- ...DEFAULT_VALIDITY_STATE,
223
- valid: true
224
- },
225
- error: '',
226
- errors: [],
227
- initialValue: validityData.initialValue
228
- };
229
- clearCustomValidity(element, registeredInputs);
230
-
231
164
  // The required value is now present; ignore stale external invalid state for this pass.
232
- updateRegisteredFieldValidity(nextValidityData, false);
233
- setValidityData(nextValidityData);
165
+ publishAllValid(element, false);
234
166
  return;
235
167
  }
236
168
 
237
- // Value is still missing, or other conditions apply.
238
- // Let's use a representation of current validity for isOnlyValueMissing.
239
- const currentNativeValidityObject = validityKeys.reduce((acc, key) => {
240
- acc[key] = currentNativeValidity[key];
241
- return acc;
242
- }, {});
243
-
244
- // If it's (still) natively invalid due to something other than just valueMissing,
245
- // then bail from this revalidation on change to avoid "scolding" for other errors.
246
- if (!currentNativeValidityObject.valid && !isOnlyValueMissing(currentNativeValidityObject)) {
247
- return;
169
+ // A stale custom error can coexist with valueMissing, but defer any other native errors.
170
+ for (const key of validityKeys) {
171
+ if (key !== 'valid' && key !== 'valueMissing' && key !== 'customError' && currentNativeValidity[key]) {
172
+ return;
173
+ }
248
174
  }
249
175
 
250
- // If valueMissing is still true AND it's the only issue, or if the field is now natively valid,
251
- // let it fall through to the main validation logic below.
176
+ // Value is still missing: publish the current native state so valueMissing and the changed
177
+ // value are observable immediately. Full custom validation still waits for its boundary.
252
178
  }
253
179
  function getState(el) {
254
180
  const computedState = validityKeys.reduce((acc, key) => {
@@ -278,10 +204,16 @@ function useFieldValidation(params) {
278
204
  timeout.clear();
279
205
  let result = null;
280
206
  let validationErrors = [];
281
- const nextState = getState(element);
207
+
208
+ // With no representative input the field carries no native constraint, so start from an
209
+ // all-valid native state and let the custom `validate` result below decide the outcome.
210
+ const nextState = element ? getState(element) : {
211
+ ...DEFAULT_VALIDITY_STATE,
212
+ valid: true
213
+ };
282
214
  let defaultValidationMessage;
283
215
  const isValidatingOnChange = shouldValidateOnChange();
284
- if (element.validationMessage && !isValidatingOnChange) {
216
+ if (element && element.validationMessage && !isValidatingOnChange) {
285
217
  // not validating on change, if there is a `validationMessage` from
286
218
  // native validity, set errors and skip calling the custom validate fn
287
219
  defaultValidationMessage = element.validationMessage;
@@ -310,20 +242,20 @@ function useFieldValidation(params) {
310
242
  nextState.customError = true;
311
243
  if (Array.isArray(result)) {
312
244
  validationErrors = result;
313
- element.setCustomValidity(result.join('\n'));
245
+ element?.setCustomValidity(result.join('\n'));
314
246
  } else if (result) {
315
247
  validationErrors = [result];
316
- element.setCustomValidity(result);
248
+ element?.setCustomValidity(result);
317
249
  }
318
250
  } else if (isValidatingOnChange) {
319
251
  // validate function returned no errors, if validating on change
320
252
  // we need to clear the custom validity state
321
253
  clearCustomValidity(element, registeredInputs);
322
254
  nextState.customError = false;
323
- if (element.validationMessage) {
255
+ if (element && element.validationMessage) {
324
256
  defaultValidationMessage = element.validationMessage;
325
257
  validationErrors = [element.validationMessage];
326
- } else if (element.validity.valid && !nextState.valid) {
258
+ } else if ((!element || element.validity.valid) && !nextState.valid) {
327
259
  nextState.valid = true;
328
260
  }
329
261
  }
@@ -358,12 +290,85 @@ function useFieldValidation(params) {
358
290
  return React.useMemo(() => ({
359
291
  getValidationProps,
360
292
  inputRef,
293
+ registeredInputs,
361
294
  registerInput,
295
+ getInputControl,
362
296
  commit,
363
297
  change
364
- }), [getValidationProps, registerInput, commit, change]);
298
+ }), [getValidationProps, registeredInputs, registerInput, getInputControl, commit, change]);
365
299
  }
366
300
 
301
+ const FieldsetRootContext = /*#__PURE__*/React.createContext(undefined);
302
+ if (process.env.NODE_ENV !== "production") FieldsetRootContext.displayName = "FieldsetRootContext";
303
+ function useFieldsetRootContext(optional = false) {
304
+ const context = React.useContext(FieldsetRootContext);
305
+ if (!context && !optional) {
306
+ throw new Error(process.env.NODE_ENV !== "production" ? 'Base UI: FieldsetRootContext is missing. Fieldset parts must be placed within <Fieldset.Root>.' : formatErrorMessage(86));
307
+ }
308
+ return context;
309
+ }
310
+
311
+ const LabelableProvider = function LabelableProvider(props) {
312
+ const defaultId = useBaseUiId();
313
+ const initialControlId = props.controlId === undefined ? defaultId : props.controlId;
314
+ const [controlId, setControlIdState] = React.useState(initialControlId);
315
+ const [labelId, setLabelId] = React.useState(props.labelId);
316
+ const [messageIds, setMessageIds] = React.useState([]);
317
+ const registrationsRef = useRefWithInit(() => new Map());
318
+ const {
319
+ messageIds: parentMessageIds
320
+ } = useLabelableContext();
321
+ const registerControlId = useStableCallback((source, nextId) => {
322
+ const registrations = registrationsRef.current;
323
+ if (nextId === undefined) {
324
+ registrations.delete(source);
325
+ return;
326
+ }
327
+ registrations.set(source, nextId);
328
+
329
+ // Only flush when registering, not when unregistering.
330
+ // This prevents loops during rapid unmount/remount cycles (e.g. React Activity).
331
+ // The next registration will pick up the correct state.
332
+ setControlIdState(prev => {
333
+ if (registrations.size === 0) {
334
+ return undefined;
335
+ }
336
+ let nextControlId;
337
+ for (const id of registrations.values()) {
338
+ if (prev !== undefined && id === prev) {
339
+ return prev;
340
+ }
341
+ if (nextControlId === undefined) {
342
+ nextControlId = id;
343
+ }
344
+ }
345
+ return nextControlId;
346
+ });
347
+ });
348
+ const getDescriptionProps = React.useCallback(externalProps => {
349
+ const ids = externalProps['aria-describedby'] ? externalProps['aria-describedby'].split(' ') : [];
350
+ ids.push(...parentMessageIds, ...messageIds);
351
+ return {
352
+ ...externalProps,
353
+ 'aria-describedby': Array.from(new Set(ids)).join(' ') || undefined
354
+ };
355
+ }, [parentMessageIds, messageIds]);
356
+ const contextValue = React.useMemo(() => ({
357
+ controlId,
358
+ registerControlId,
359
+ labelId,
360
+ setLabelId,
361
+ messageIds,
362
+ setMessageIds,
363
+ getDescriptionProps
364
+ }), [controlId, registerControlId, labelId, setLabelId, messageIds, setMessageIds, getDescriptionProps]);
365
+ return /*#__PURE__*/jsx(LabelableContext.Provider, {
366
+ value: contextValue,
367
+ children: props.children
368
+ });
369
+ };
370
+ if (process.env.NODE_ENV !== "production") LabelableProvider.displayName = "LabelableProvider";
371
+
367
372
  function useFieldControlRegistration(params) {
368
373
  const {
369
374
  commit,
@@ -371,7 +376,7 @@ function useFieldControlRegistration(params) {
371
376
  markedDirtyRef,
372
377
  name,
373
378
  setRegisteredFieldName,
374
- setRegisteredFieldId,
379
+ registeredFieldIdRef,
375
380
  setValidityData,
376
381
  validityData
377
382
  } = params;
@@ -380,7 +385,7 @@ function useFieldControlRegistration(params) {
380
385
  } = useFormContext();
381
386
  const activeFieldControlSourceRef = React.useRef(null);
382
387
  const registrationRef = React.useRef(null);
383
- const fallbackControlRef = React.useRef(null);
388
+ const initialValueCapturedRef = React.useRef(false);
384
389
  const getValueForForm = useStableCallback(() => {
385
390
  const registration = registrationRef.current;
386
391
  if (!registration) {
@@ -411,7 +416,7 @@ function useFieldControlRegistration(params) {
411
416
  formRef.current.fields.set(registration.id, {
412
417
  getValue: getValueForForm,
413
418
  name: name ?? registration.name,
414
- controlRef: registration.controlRef ?? fallbackControlRef,
419
+ controlRef: registration.controlRef,
415
420
  validityData: getCombinedFieldValidityData(validityData, invalid),
416
421
  validate
417
422
  });
@@ -421,18 +426,23 @@ function useFieldControlRegistration(params) {
421
426
  formRef.current.fields.delete(id);
422
427
  }
423
428
  }
424
- function syncInitialValue() {
425
- const registration = registrationRef.current;
426
- if (!registration) {
429
+
430
+ // The baseline belongs to the field, not to a control instance: registration re-runs on every
431
+ // value change, and a control that unmounts and remounts (or is swapped for another one) comes
432
+ // back as a brand new registration. Capturing more than once would turn whichever value the
433
+ // control happens to hold at that point into the initial value, so a modified field would read
434
+ // pristine and its real initial value would read dirty. Consumers that want a fresh baseline
435
+ // remount or key `<Field.Root>` itself.
436
+ function captureInitialValue(registration) {
437
+ if (initialValueCapturedRef.current) {
427
438
  return;
428
439
  }
440
+ initialValueCapturedRef.current = true;
429
441
  const initialValue = getRegistrationValue(registration);
430
- if (validityData.initialValue === null && initialValue !== null) {
431
- setValidityData(prev => ({
432
- ...prev,
433
- initialValue
434
- }));
435
- }
442
+ setValidityData(prev => prev.initialValue === initialValue ? prev : {
443
+ ...prev,
444
+ initialValue
445
+ });
436
446
  }
437
447
  useIsoLayoutEffect(() => {
438
448
  const registration = registrationRef.current;
@@ -443,7 +453,7 @@ function useFieldControlRegistration(params) {
443
453
  formRef.current.fields.set(registration.id, {
444
454
  getValue: getValueForForm,
445
455
  name: name ?? registration.name,
446
- controlRef: registration.controlRef ?? fallbackControlRef,
456
+ controlRef: registration.controlRef,
447
457
  validityData: getCombinedFieldValidityData(validityData, invalid),
448
458
  validate
449
459
  });
@@ -464,7 +474,7 @@ function useFieldControlRegistration(params) {
464
474
  deleteRegistration();
465
475
  registrationRef.current = null;
466
476
  setRegisteredFieldName(undefined);
467
- setRegisteredFieldId(undefined);
477
+ registeredFieldIdRef.current = undefined;
468
478
  }
469
479
  return;
470
480
  }
@@ -474,11 +484,11 @@ function useFieldControlRegistration(params) {
474
484
  if (!name) {
475
485
  setRegisteredFieldName(registration.name);
476
486
  }
477
- setRegisteredFieldId(registration.id);
487
+ registeredFieldIdRef.current = registration.id;
478
488
  if (previousId && previousId !== registration.id) {
479
489
  deleteRegistration(previousId);
480
490
  }
481
- syncInitialValue();
491
+ captureInitialValue(registration);
482
492
  refreshRegistration();
483
493
  });
484
494
  return [validate, register];
@@ -523,10 +533,6 @@ const FieldRootInner = /*#__PURE__*/React.forwardRef(function FieldRootInner(com
523
533
  markedDirtyRef.current = dirtyProp;
524
534
  }
525
535
  }, [dirtyProp]);
526
- const getRegisteredFieldId = React.useCallback(() => registeredFieldIdRef.current, []);
527
- const setRegisteredFieldId = React.useCallback(id => {
528
- registeredFieldIdRef.current = id;
529
- }, []);
530
536
  const setDirty = useStableCallback(value => {
531
537
  if (dirtyProp !== undefined) {
532
538
  return;
@@ -553,7 +559,11 @@ const FieldRootInner = /*#__PURE__*/React.forwardRef(function FieldRootInner(com
553
559
  value: null,
554
560
  initialValue: null
555
561
  });
556
- const valid = disabled ? null : !invalid && validityData.state.valid;
562
+
563
+ // App-controlled invalidity (the `invalid` prop and `<Form>` errors) keeps the field marked
564
+ // invalid even while disabled. Only computed validity (native constraints and `validate`)
565
+ // is suppressed when disabled, matching `:disabled` not participating in constraint validation.
566
+ const valid = !invalid && (disabled ? null : validityData.state.valid);
557
567
  const state = React.useMemo(() => ({
558
568
  disabled,
559
569
  touched,
@@ -571,7 +581,7 @@ const FieldRootInner = /*#__PURE__*/React.forwardRef(function FieldRootInner(com
571
581
  markedDirtyRef,
572
582
  state,
573
583
  shouldValidateOnChange,
574
- getRegisteredFieldId
584
+ registeredFieldIdRef
575
585
  });
576
586
  const [validateFieldControl, registerFieldControl] = useFieldControlRegistration({
577
587
  commit: validation.commit,
@@ -579,7 +589,7 @@ const FieldRootInner = /*#__PURE__*/React.forwardRef(function FieldRootInner(com
579
589
  markedDirtyRef,
580
590
  name,
581
591
  setRegisteredFieldName,
582
- setRegisteredFieldId,
592
+ registeredFieldIdRef,
583
593
  setValidityData,
584
594
  validityData
585
595
  });
@@ -592,23 +602,16 @@ const FieldRootInner = /*#__PURE__*/React.forwardRef(function FieldRootInner(com
592
602
  validityData,
593
603
  setValidityData,
594
604
  disabled,
595
- touched,
596
605
  setTouched,
597
- dirty,
598
606
  setDirty,
599
- filled,
600
607
  setFilled,
601
- focused,
602
608
  setFocused,
603
- validate,
604
609
  validationMode,
605
- validationDebounceTime,
606
610
  shouldValidateOnChange,
607
611
  state,
608
- markedDirtyRef,
609
612
  registerFieldControl,
610
613
  validation
611
- }), [invalid, effectiveName, validityData, disabled, touched, setTouched, dirty, setDirty, filled, setFilled, focused, setFocused, validate, validationMode, validationDebounceTime, shouldValidateOnChange, state, registerFieldControl, validation]);
614
+ }), [invalid, effectiveName, validityData, disabled, setTouched, setDirty, setFilled, setFocused, validationMode, shouldValidateOnChange, state, registerFieldControl, validation]);
612
615
  const element = useRenderElement('div', componentProps, {
613
616
  ref: forwardedRef,
614
617
  state,
@@ -765,13 +768,13 @@ const FieldError = /*#__PURE__*/React.forwardRef(function FieldError(componentPr
765
768
  } else if (validityData.errors.length > 1) {
766
769
  error = validityData.errors;
767
770
  }
768
- let errorMessage = error ?? '';
771
+ let errorMessage = error;
769
772
  if (Array.isArray(error)) {
770
773
  errorMessage = error.length > 1 ? /*#__PURE__*/jsx("ul", {
771
774
  children: error.map(message => /*#__PURE__*/jsx("li", {
772
775
  children: message
773
776
  }, message))
774
- }) : error[0] ?? '';
777
+ }) : error[0];
775
778
  }
776
779
  const errorKey = Array.isArray(error) ? JSON.stringify(error) : error;
777
780
  if (rendered && errorKey !== lastRenderedMessageKey) {
@@ -18,10 +18,8 @@ require('../../../useAtlantisI18n-cjs.js');
18
18
  require('../../../AtlantisContext-cjs.js');
19
19
  require('../../../Tooltip-cjs.js');
20
20
  require('../../../floating-ui.react-cjs.js');
21
- require('../../../floating-ui.utils-cjs.js');
22
21
  require('react/jsx-runtime');
23
22
  require('react-dom');
24
- require('../../../floating-ui.react-dom-cjs.js');
25
23
  require('framer-motion');
26
24
  require('../../../FilterPickerProvider-cjs.js');
27
25
  require('../../../constants-cjs.js');
@@ -16,10 +16,8 @@ import '../../../useAtlantisI18n-es.js';
16
16
  import '../../../AtlantisContext-es.js';
17
17
  import '../../../Tooltip-es.js';
18
18
  import '../../../floating-ui.react-es.js';
19
- import '../../../floating-ui.utils-es.js';
20
19
  import 'react/jsx-runtime';
21
20
  import 'react-dom';
22
- import '../../../floating-ui.react-dom-es.js';
23
21
  import 'framer-motion';
24
22
  import '../../../FilterPickerProvider-es.js';
25
23
  import '../../../constants-es.js';
@@ -4,10 +4,8 @@ var FilterPickerContent = require('../../../FilterPickerContent-cjs.js');
4
4
  require('react');
5
5
  require('classnames');
6
6
  require('../../../floating-ui.react-cjs.js');
7
- require('../../../floating-ui.utils-cjs.js');
8
7
  require('react/jsx-runtime');
9
8
  require('react-dom');
10
- require('../../../floating-ui.react-dom-cjs.js');
11
9
  require('../../../FilterPickerContentSearch-cjs.js');
12
10
  require('../../../Icon-cjs.js');
13
11
  require('@jobber/design');
@@ -2,10 +2,8 @@ export { F as FilterPickerContent } from '../../../FilterPickerContent-es.js';
2
2
  import 'react';
3
3
  import 'classnames';
4
4
  import '../../../floating-ui.react-es.js';
5
- import '../../../floating-ui.utils-es.js';
6
5
  import 'react/jsx-runtime';
7
6
  import 'react-dom';
8
- import '../../../floating-ui.react-dom-es.js';
9
7
  import '../../../FilterPickerContentSearch-es.js';
10
8
  import '../../../Icon-es.js';
11
9
  import '@jobber/design';
@@ -16,10 +16,8 @@ require('../../../AtlantisContext-cjs.js');
16
16
  require('../../../Typography-cjs.js');
17
17
  require('../../../Tooltip-cjs.js');
18
18
  require('../../../floating-ui.react-cjs.js');
19
- require('../../../floating-ui.utils-cjs.js');
20
19
  require('react/jsx-runtime');
21
20
  require('react-dom');
22
- require('../../../floating-ui.react-dom-cjs.js');
23
21
  require('framer-motion');
24
22
  require('../../../FilterPickerProvider-cjs.js');
25
23
 
@@ -14,9 +14,7 @@ import '../../../AtlantisContext-es.js';
14
14
  import '../../../Typography-es.js';
15
15
  import '../../../Tooltip-es.js';
16
16
  import '../../../floating-ui.react-es.js';
17
- import '../../../floating-ui.utils-es.js';
18
17
  import 'react/jsx-runtime';
19
18
  import 'react-dom';
20
- import '../../../floating-ui.react-dom-es.js';
21
19
  import 'framer-motion';
22
20
  import '../../../FilterPickerProvider-es.js';