@jobber/components 9.6.0 → 9.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (187) hide show
  1. package/dist/ActivityIndicator-cjs.js +1 -0
  2. package/dist/ActivityIndicator-es.js +1 -0
  3. package/dist/Autocomplete/index.cjs +2 -0
  4. package/dist/Autocomplete/index.mjs +2 -0
  5. package/dist/Autocomplete-cjs.js +6 -4
  6. package/dist/Autocomplete-es.js +3 -1
  7. package/dist/Banner-cjs.js +1 -0
  8. package/dist/Banner-es.js +1 -0
  9. package/dist/Card/index.cjs +2 -1
  10. package/dist/Card/index.mjs +2 -1
  11. package/dist/Chip/index.cjs +2 -0
  12. package/dist/Chip/index.mjs +2 -0
  13. package/dist/Chip-cjs.js +1 -0
  14. package/dist/Chip-es.js +1 -0
  15. package/dist/Chips/InternalChipDismissible/hooks/index.cjs +2 -0
  16. package/dist/Chips/InternalChipDismissible/hooks/index.mjs +2 -0
  17. package/dist/Chips/InternalChipDismissible/index.cjs +2 -0
  18. package/dist/Chips/InternalChipDismissible/index.mjs +2 -0
  19. package/dist/Chips/index.cjs +2 -0
  20. package/dist/Chips/index.mjs +2 -0
  21. package/dist/Combobox/index.cjs +4 -2
  22. package/dist/Combobox/index.mjs +4 -2
  23. package/dist/{ComboboxSeparator-cjs.js → ComboboxChipRemove-cjs.js} +455 -501
  24. package/dist/{ComboboxSeparator-es.js → ComboboxChipRemove-es.js} +456 -500
  25. package/dist/ComboboxPrimitive-cjs.js +30 -29
  26. package/dist/ComboboxPrimitive-es.js +3 -2
  27. package/dist/ConfirmationModal/index.cjs +2 -0
  28. package/dist/ConfirmationModal/index.mjs +2 -0
  29. package/dist/ConfirmationModal-cjs.js +1 -0
  30. package/dist/ConfirmationModal-es.js +1 -0
  31. package/dist/DataDump/index.cjs +2 -1
  32. package/dist/DataDump/index.mjs +2 -1
  33. package/dist/DataDump-cjs.js +1 -0
  34. package/dist/DataDump-es.js +1 -0
  35. package/dist/DataList/components/DataListActions/index.cjs +2 -0
  36. package/dist/DataList/components/DataListActions/index.mjs +2 -0
  37. package/dist/DataList/components/DataListBulkActions/index.cjs +2 -0
  38. package/dist/DataList/components/DataListBulkActions/index.mjs +2 -0
  39. package/dist/DataList/components/DataListFilters/components/DataListSort/index.cjs +2 -0
  40. package/dist/DataList/components/DataListFilters/components/DataListSort/index.mjs +2 -0
  41. package/dist/DataList/components/DataListFilters/index.cjs +2 -0
  42. package/dist/DataList/components/DataListFilters/index.mjs +2 -0
  43. package/dist/DataList/components/DataListHeader/index.cjs +2 -0
  44. package/dist/DataList/components/DataListHeader/index.mjs +2 -0
  45. package/dist/DataList/components/DataListItem/index.cjs +2 -0
  46. package/dist/DataList/components/DataListItem/index.mjs +2 -0
  47. package/dist/DataList/components/DataListItemActions/index.cjs +2 -0
  48. package/dist/DataList/components/DataListItemActions/index.mjs +2 -0
  49. package/dist/DataList/components/DataListItemActionsOverflow/index.cjs +2 -0
  50. package/dist/DataList/components/DataListItemActionsOverflow/index.mjs +2 -0
  51. package/dist/DataList/components/DataListItems/index.cjs +2 -0
  52. package/dist/DataList/components/DataListItems/index.mjs +2 -0
  53. package/dist/DataList/components/DataListLayout/index.cjs +2 -0
  54. package/dist/DataList/components/DataListLayout/index.mjs +2 -0
  55. package/dist/DataList/components/DataListLayoutActions/index.cjs +2 -0
  56. package/dist/DataList/components/DataListLayoutActions/index.mjs +2 -0
  57. package/dist/DataList/index.cjs +2 -0
  58. package/dist/DataList/index.mjs +2 -0
  59. package/dist/DataTable-cjs.js +1 -0
  60. package/dist/DataTable-es.js +1 -0
  61. package/dist/DatePicker/index.cjs +2 -0
  62. package/dist/DatePicker/index.mjs +2 -0
  63. package/dist/DatePicker-cjs.js +8 -7
  64. package/dist/DatePicker-es.js +6 -5
  65. package/dist/Dialog/index.cjs +2 -1
  66. package/dist/Dialog/index.mjs +2 -1
  67. package/dist/DrawerDescription-cjs.js +99 -268
  68. package/dist/DrawerDescription-es.js +103 -270
  69. package/dist/DrawerGrid-cjs.js +1 -0
  70. package/dist/DrawerGrid-es.js +1 -0
  71. package/dist/DrawerTitle-cjs.js +6 -2
  72. package/dist/DrawerTitle-es.js +7 -3
  73. package/dist/FeatureSwitch-cjs.js +1 -0
  74. package/dist/FeatureSwitch-es.js +1 -0
  75. package/dist/FieldDescription-cjs.js +181 -184
  76. package/dist/FieldDescription-es.js +184 -187
  77. package/dist/FilterPicker/components/FilterPickerActivator/index.cjs +2 -0
  78. package/dist/FilterPicker/components/FilterPickerActivator/index.mjs +2 -0
  79. package/dist/FilterPicker/components/FilterPickerContent/index.cjs +2 -0
  80. package/dist/FilterPicker/components/FilterPickerContent/index.mjs +2 -0
  81. package/dist/FilterPicker/components/FilterPickerTrigger/index.cjs +2 -0
  82. package/dist/FilterPicker/components/FilterPickerTrigger/index.mjs +2 -0
  83. package/dist/FilterPicker/index.cjs +2 -0
  84. package/dist/FilterPicker/index.mjs +2 -0
  85. package/dist/FilterPickerContent-cjs.js +5 -4
  86. package/dist/FilterPickerContent-es.js +2 -1
  87. package/dist/FormFieldWrapper-cjs.js +1 -0
  88. package/dist/FormFieldWrapper-es.js +1 -0
  89. package/dist/FormatFile/index.cjs +2 -0
  90. package/dist/FormatFile/index.mjs +2 -0
  91. package/dist/FormatFile-cjs.js +1 -0
  92. package/dist/FormatFile-es.js +1 -0
  93. package/dist/Gallery/index.cjs +2 -0
  94. package/dist/Gallery/index.mjs +2 -0
  95. package/dist/InputAvatar-cjs.js +1 -0
  96. package/dist/InputAvatar-es.js +1 -0
  97. package/dist/InputDate/index.cjs +2 -0
  98. package/dist/InputDate/index.mjs +2 -0
  99. package/dist/InputDate-cjs.js +1 -0
  100. package/dist/InputDate-es.js +1 -0
  101. package/dist/InputNumber/index.cjs +2 -1
  102. package/dist/InputNumber/index.mjs +2 -1
  103. package/dist/InternalChipDismissible-cjs.js +1 -0
  104. package/dist/InternalChipDismissible-es.js +1 -0
  105. package/dist/LightBox/index.cjs +2 -0
  106. package/dist/LightBox/index.mjs +2 -0
  107. package/dist/LightBox-cjs.js +1 -0
  108. package/dist/LightBox-es.js +1 -0
  109. package/dist/Menu/index.cjs +2 -1
  110. package/dist/Menu/index.mjs +2 -1
  111. package/dist/Menu-cjs.js +6 -4
  112. package/dist/Menu-es.js +3 -1
  113. package/dist/MenuSubmenuTrigger-cjs.js +164 -206
  114. package/dist/MenuSubmenuTrigger-es.js +152 -194
  115. package/dist/Modal/index.cjs +3 -1
  116. package/dist/Modal/index.mjs +4 -2
  117. package/dist/NumberFieldInput-cjs.js +308 -187
  118. package/dist/NumberFieldInput-es.js +308 -187
  119. package/dist/Page/index.cjs +2 -1
  120. package/dist/Page/index.mjs +2 -1
  121. package/dist/Page-cjs.js +1 -0
  122. package/dist/Page-es.js +1 -0
  123. package/dist/Popover/index.cjs +2 -0
  124. package/dist/Popover/index.mjs +2 -0
  125. package/dist/Popover-cjs.js +9 -7
  126. package/dist/Popover-es.js +3 -1
  127. package/dist/ProgressBar-cjs.js +1 -0
  128. package/dist/ProgressBar-es.js +1 -0
  129. package/dist/ScrollAreaViewport-cjs.js +1093 -747
  130. package/dist/ScrollAreaViewport-es.js +1081 -734
  131. package/dist/Select/index.cjs +2 -1
  132. package/dist/Select/index.mjs +2 -1
  133. package/dist/SelectGroupLabel-cjs.js +306 -207
  134. package/dist/SelectGroupLabel-es.js +302 -203
  135. package/dist/SideDrawer-cjs.js +1 -0
  136. package/dist/SideDrawer-es.js +1 -0
  137. package/dist/Spinner-cjs.js +1 -0
  138. package/dist/Spinner-es.js +1 -0
  139. package/dist/Tooltip/index.cjs +2 -0
  140. package/dist/Tooltip/index.mjs +2 -0
  141. package/dist/Tooltip-cjs.js +7 -6
  142. package/dist/Tooltip-es.js +2 -1
  143. package/dist/dialogReturnFocus-cjs.js +1 -1
  144. package/dist/dialogReturnFocus-es.js +2 -2
  145. package/dist/floating-ui.react-cjs.js +192 -2564
  146. package/dist/floating-ui.react-dom-cjs.js +2103 -0
  147. package/dist/floating-ui.react-dom-es.js +2072 -0
  148. package/dist/floating-ui.react-es.js +156 -2519
  149. package/dist/floating-ui.utils-cjs.js +342 -0
  150. package/dist/floating-ui.utils-es.js +300 -0
  151. package/dist/index.cjs +4 -3
  152. package/dist/index.mjs +4 -3
  153. package/dist/primitives/BottomSheet/index.cjs +1 -1
  154. package/dist/primitives/BottomSheet/index.mjs +1 -1
  155. package/dist/primitives/ComboboxPrimitive/index.cjs +4 -2
  156. package/dist/primitives/ComboboxPrimitive/index.mjs +4 -2
  157. package/dist/primitives/SelectPrimitive/index.cjs +2 -0
  158. package/dist/primitives/SelectPrimitive/index.mjs +2 -0
  159. package/dist/primitives/index.cjs +3 -2
  160. package/dist/primitives/index.mjs +3 -2
  161. package/dist/scrollEdges-cjs.js +11 -1
  162. package/dist/scrollEdges-es.js +11 -1
  163. package/dist/sharedHelpers/i18n/index.cjs +1 -0
  164. package/dist/sharedHelpers/i18n/index.d.ts +1 -0
  165. package/dist/sharedHelpers/i18n/index.mjs +1 -1
  166. package/dist/sharedHelpers/i18n/releasedLanguages.d.ts +12 -0
  167. package/dist/sharedHelpers/i18n/useReleasedLocale.d.ts +6 -0
  168. package/dist/showToast-cjs.js +1 -1
  169. package/dist/showToast-es.js +1 -1
  170. package/dist/testUtils/releasedLanguages.d.ts +16 -0
  171. package/dist/unstyledPrimitives/index.cjs +478 -720
  172. package/dist/unstyledPrimitives/index.mjs +421 -663
  173. package/dist/useAtlantisI18n-cjs.js +44 -11
  174. package/dist/useAtlantisI18n-es.js +44 -12
  175. package/dist/useCompositeListItem-cjs.js +361 -2108
  176. package/dist/useCompositeListItem-es.js +331 -2076
  177. package/dist/useLabel-cjs.js +3 -2
  178. package/dist/useLabel-es.js +3 -2
  179. package/dist/useRenderElement-cjs.js +7 -9
  180. package/dist/useRenderElement-es.js +7 -9
  181. package/dist/useScrollLock-cjs.js +344 -819
  182. package/dist/useScrollLock-es.js +296 -732
  183. package/dist/useScrollToActive-cjs.js +5 -4
  184. package/dist/useScrollToActive-es.js +2 -1
  185. package/package.json +3 -3
  186. package/dist/scrollable-cjs.js +0 -58
  187. package/dist/scrollable-es.js +0 -54
@@ -35,6 +35,77 @@ function useFieldItemContext() {
35
35
  return context;
36
36
  }
37
37
 
38
+ const FieldsetRootContext = /*#__PURE__*/React__namespace.createContext(undefined);
39
+ if (process.env.NODE_ENV !== "production") FieldsetRootContext.displayName = "FieldsetRootContext";
40
+ function useFieldsetRootContext(optional = false) {
41
+ const context = React__namespace.useContext(FieldsetRootContext);
42
+ if (!context && !optional) {
43
+ throw new Error(process.env.NODE_ENV !== "production" ? 'Base UI: FieldsetRootContext is missing. Fieldset parts must be placed within <Fieldset.Root>.' : useRenderElement.formatErrorMessage(86));
44
+ }
45
+ return context;
46
+ }
47
+
48
+ const LabelableProvider = function LabelableProvider(props) {
49
+ const defaultId = useScrollLock.useBaseUiId();
50
+ const initialControlId = props.controlId === undefined ? defaultId : props.controlId;
51
+ const [controlId, setControlIdState] = React__namespace.useState(initialControlId);
52
+ const [labelId, setLabelId] = React__namespace.useState(props.labelId);
53
+ const [messageIds, setMessageIds] = React__namespace.useState([]);
54
+ const registrationsRef = useRenderElement.useRefWithInit(() => new Map());
55
+ const {
56
+ messageIds: parentMessageIds
57
+ } = useCompositeListItem.useLabelableContext();
58
+ const registerControlId = useScrollLock.useStableCallback((source, nextId) => {
59
+ const registrations = registrationsRef.current;
60
+ if (nextId === undefined) {
61
+ registrations.delete(source);
62
+ return;
63
+ }
64
+ registrations.set(source, nextId);
65
+
66
+ // Only flush when registering, not when unregistering.
67
+ // This prevents loops during rapid unmount/remount cycles (e.g. React Activity).
68
+ // The next registration will pick up the correct state.
69
+ setControlIdState(prev => {
70
+ if (registrations.size === 0) {
71
+ return undefined;
72
+ }
73
+ let nextControlId;
74
+ for (const id of registrations.values()) {
75
+ if (prev !== undefined && id === prev) {
76
+ return prev;
77
+ }
78
+ if (nextControlId === undefined) {
79
+ nextControlId = id;
80
+ }
81
+ }
82
+ return nextControlId;
83
+ });
84
+ });
85
+ const getDescriptionProps = React__namespace.useCallback(externalProps => {
86
+ const ids = externalProps['aria-describedby'] ? externalProps['aria-describedby'].split(' ') : [];
87
+ ids.push(...parentMessageIds, ...messageIds);
88
+ return {
89
+ ...externalProps,
90
+ 'aria-describedby': Array.from(new Set(ids)).join(' ') || undefined
91
+ };
92
+ }, [parentMessageIds, messageIds]);
93
+ const contextValue = React__namespace.useMemo(() => ({
94
+ controlId,
95
+ registerControlId,
96
+ labelId,
97
+ setLabelId,
98
+ messageIds,
99
+ setMessageIds,
100
+ getDescriptionProps
101
+ }), [controlId, registerControlId, labelId, setLabelId, messageIds, setMessageIds, getDescriptionProps]);
102
+ return /*#__PURE__*/jsxRuntime.jsx(useCompositeListItem.LabelableContext.Provider, {
103
+ value: contextValue,
104
+ children: props.children
105
+ });
106
+ };
107
+ if (process.env.NODE_ENV !== "production") LabelableProvider.displayName = "LabelableProvider";
108
+
38
109
  /**
39
110
  * Combines the field's client-side, stateful validity data with the external invalid state to
40
111
  * determine the field's true validity.
@@ -50,35 +121,34 @@ function getCombinedFieldValidityData(validityData, invalid) {
50
121
  }
51
122
 
52
123
  const validityKeys = Object.keys(useCompositeListItem.DEFAULT_VALIDITY_STATE);
53
- /**
54
- * Whether an input participates in the surrounding Base UI Form. Inputs that are effectively
55
- * disabled, or whose `form` attribute explicitly associates them with another form, are excluded.
56
- * DOM position only matters when it associates the input with a different form. Otherwise, field
57
- * registration is context-driven, so portaled inputs (for example inside a dialog) still belong to
58
- * the form for both validation and values projected into `onFormSubmit`.
59
- */
60
- function isEligibleInput(input, formElement) {
61
- if (input.matches(':disabled')) {
124
+ function isOnlyValueMissing(state) {
125
+ if (!state || state.valid || !state.valueMissing) {
62
126
  return false;
63
127
  }
64
- if (!formElement || input.form === formElement) {
65
- return true;
128
+ let onlyValueMissing = false;
129
+ for (const key of validityKeys) {
130
+ if (key === 'valid') {
131
+ continue;
132
+ }
133
+ if (key === 'valueMissing') {
134
+ onlyValueMissing = state[key];
135
+ } else if (state[key]) {
136
+ onlyValueMissing = false;
137
+ }
66
138
  }
67
-
68
- // React context crosses portal boundaries. An unassociated portaled input still participates in
69
- // contextual validation, unless an explicit `form` attribute opts it out of the surrounding Form.
70
- return input.form === null && !input.hasAttribute('form');
139
+ return onlyValueMissing;
71
140
  }
72
141
 
73
142
  /**
74
143
  * Picks the input whose native validity should represent a field that owns several inputs (such as a
75
- * checkbox or radio group). Prefers the first eligible currently-invalid input, where "first" follows
76
- * registration order (mount order), and otherwise returns the first eligible input.
144
+ * checkbox group). Prefers the first enabled currently-invalid input, where "first" follows Set
145
+ * insertion order (mount order), and otherwise returns the first enabled input. Disabled inputs are
146
+ * skipped because they don't participate in native constraint validation.
77
147
  */
78
- function findRepresentativeInput(inputs, formElement) {
148
+ function findRepresentativeInput(inputs) {
79
149
  let fallback = null;
80
- for (const input of inputs.keys()) {
81
- if (!isEligibleInput(input, formElement)) {
150
+ for (const input of inputs) {
151
+ if (input.disabled) {
82
152
  continue;
83
153
  }
84
154
  if (!input.validity.valid) {
@@ -89,14 +159,17 @@ function findRepresentativeInput(inputs, formElement) {
89
159
  return fallback;
90
160
  }
91
161
  function clearCustomValidity(element, inputs) {
92
- for (const input of inputs.keys()) {
162
+ let didClearElement = false;
163
+ for (const input of inputs) {
93
164
  input.setCustomValidity('');
165
+ didClearElement ||= input === element;
166
+ }
167
+ if (!didClearElement) {
168
+ element.setCustomValidity('');
94
169
  }
95
- element?.setCustomValidity('');
96
170
  }
97
171
  function useFieldValidation(params) {
98
172
  const {
99
- elementRef,
100
173
  formRef
101
174
  } = useCompositeListItem.useFormContext();
102
175
  const {
@@ -108,7 +181,7 @@ function useFieldValidation(params) {
108
181
  markedDirtyRef,
109
182
  state,
110
183
  shouldValidateOnChange,
111
- registeredFieldIdRef
184
+ getRegisteredFieldId
112
185
  } = params;
113
186
  const {
114
187
  controlId,
@@ -116,27 +189,32 @@ function useFieldValidation(params) {
116
189
  } = useCompositeListItem.useLabelableContext();
117
190
  const timeout = useScrollLock.useTimeout();
118
191
  const inputRef = React__namespace.useRef(null);
119
- const registeredInputs = useRenderElement.useRefWithInit(() => new Map()).current;
192
+ const registeredInputs = useRenderElement.useRefWithInit(() => new Set()).current;
120
193
  const validationCommitIdRef = React__namespace.useRef(0);
121
194
 
122
- // Groups register several inputs against a single field so focus, validation, and form-value
123
- // projection can use the same live controls. This also ensures a `required` checkbox can't be
124
- // satisfied by another input in the group, matching native per-checkbox behavior.
125
- const registerInput = React__namespace.useCallback((element, registration) => {
126
- registeredInputs.set(element, registration);
195
+ // Checkbox groups register several inputs against a single field. Track them so a `required`
196
+ // checkbox can't be satisfied by another input in the group, matching native per-checkbox behavior.
197
+ const registerInput = React__namespace.useCallback(element => {
198
+ if (!element) {
199
+ return undefined;
200
+ }
201
+ registeredInputs.add(element);
127
202
  return () => {
128
203
  registeredInputs.delete(element);
129
204
  };
130
205
  }, [registeredInputs]);
131
- const getInputControl = useScrollLock.useStableCallback(() => {
132
- const element = findRepresentativeInput(registeredInputs, elementRef.current);
133
- return element && registeredInputs.get(element)?.controlRef.current || null;
134
- });
135
206
  const commit = useScrollLock.useStableCallback(async (value, revalidate = false) => {
207
+ // A field can own several inputs (a checkbox group), but only the last-mounted one wins the shared
208
+ // `inputRef`. Validate against the registry instead so every input counts; `inputRef` is the
209
+ // fallback only when no registered input applies (none registered, or all of them disabled).
210
+ const element = findRepresentativeInput(registeredInputs) ?? inputRef.current;
211
+ if (!element) {
212
+ return;
213
+ }
136
214
  validationCommitIdRef.current += 1;
137
215
  const validationCommitId = validationCommitIdRef.current;
138
216
  function updateRegisteredFieldValidity(nextValidityData, externalInvalid = invalid) {
139
- const fieldId = registeredFieldIdRef.current ?? controlId;
217
+ const fieldId = getRegisteredFieldId() ?? controlId;
140
218
  if (fieldId == null) {
141
219
  return;
142
220
  }
@@ -150,31 +228,8 @@ function useFieldValidation(params) {
150
228
  validityData: validityDataWithFormErrors
151
229
  });
152
230
  }
153
- function publishAllValid(input, externalInvalid) {
154
- const nextValidityData = {
155
- value,
156
- state: {
157
- ...useCompositeListItem.DEFAULT_VALIDITY_STATE,
158
- valid: true
159
- },
160
- error: '',
161
- errors: [],
162
- initialValue: validityData.initialValue
163
- };
164
- clearCustomValidity(input, registeredInputs);
165
- updateRegisteredFieldValidity(nextValidityData, externalInvalid);
166
- setValidityData(nextValidityData);
167
- }
168
-
169
- // A field can own several inputs (such as a checkbox or radio group), but only the last-mounted
170
- // one wins the shared `inputRef`. Validate against the registry instead so every input counts;
171
- // `inputRef` is the fallback only when no inputs are registered.
172
- const element = registeredInputs.size > 0 ? findRepresentativeInput(registeredInputs, elementRef.current) : inputRef.current;
173
- // A field with no eligible input has no native constraint, but its custom validator still
174
- // applies to the logical value at the configured validation boundary.
175
-
176
231
  if (revalidate) {
177
- if (state.valid !== false || !element) {
232
+ if (state.valid !== false) {
178
233
  return;
179
234
  }
180
235
  const currentNativeValidity = element.validity;
@@ -182,20 +237,39 @@ function useFieldValidation(params) {
182
237
  // The 'valueMissing' (required) condition has been resolved by the user typing.
183
238
  // Temporarily mark the field as valid for this onChange event.
184
239
  // Other native errors (e.g., typeMismatch) will be caught by full validation on blur or submit.
240
+ const nextValidityData = {
241
+ value,
242
+ state: {
243
+ ...useCompositeListItem.DEFAULT_VALIDITY_STATE,
244
+ valid: true
245
+ },
246
+ error: '',
247
+ errors: [],
248
+ initialValue: validityData.initialValue
249
+ };
250
+ clearCustomValidity(element, registeredInputs);
251
+
185
252
  // The required value is now present; ignore stale external invalid state for this pass.
186
- publishAllValid(element, false);
253
+ updateRegisteredFieldValidity(nextValidityData, false);
254
+ setValidityData(nextValidityData);
187
255
  return;
188
256
  }
189
257
 
190
- // A stale custom error can coexist with valueMissing, but defer any other native errors.
191
- for (const key of validityKeys) {
192
- if (key !== 'valid' && key !== 'valueMissing' && key !== 'customError' && currentNativeValidity[key]) {
193
- return;
194
- }
258
+ // Value is still missing, or other conditions apply.
259
+ // Let's use a representation of current validity for isOnlyValueMissing.
260
+ const currentNativeValidityObject = validityKeys.reduce((acc, key) => {
261
+ acc[key] = currentNativeValidity[key];
262
+ return acc;
263
+ }, {});
264
+
265
+ // If it's (still) natively invalid due to something other than just valueMissing,
266
+ // then bail from this revalidation on change to avoid "scolding" for other errors.
267
+ if (!currentNativeValidityObject.valid && !isOnlyValueMissing(currentNativeValidityObject)) {
268
+ return;
195
269
  }
196
270
 
197
- // Value is still missing: publish the current native state so valueMissing and the changed
198
- // value are observable immediately. Full custom validation still waits for its boundary.
271
+ // If valueMissing is still true AND it's the only issue, or if the field is now natively valid,
272
+ // let it fall through to the main validation logic below.
199
273
  }
200
274
  function getState(el) {
201
275
  const computedState = validityKeys.reduce((acc, key) => {
@@ -225,16 +299,10 @@ function useFieldValidation(params) {
225
299
  timeout.clear();
226
300
  let result = null;
227
301
  let validationErrors = [];
228
-
229
- // With no representative input the field carries no native constraint, so start from an
230
- // all-valid native state and let the custom `validate` result below decide the outcome.
231
- const nextState = element ? getState(element) : {
232
- ...useCompositeListItem.DEFAULT_VALIDITY_STATE,
233
- valid: true
234
- };
302
+ const nextState = getState(element);
235
303
  let defaultValidationMessage;
236
304
  const isValidatingOnChange = shouldValidateOnChange();
237
- if (element && element.validationMessage && !isValidatingOnChange) {
305
+ if (element.validationMessage && !isValidatingOnChange) {
238
306
  // not validating on change, if there is a `validationMessage` from
239
307
  // native validity, set errors and skip calling the custom validate fn
240
308
  defaultValidationMessage = element.validationMessage;
@@ -263,20 +331,20 @@ function useFieldValidation(params) {
263
331
  nextState.customError = true;
264
332
  if (Array.isArray(result)) {
265
333
  validationErrors = result;
266
- element?.setCustomValidity(result.join('\n'));
334
+ element.setCustomValidity(result.join('\n'));
267
335
  } else if (result) {
268
336
  validationErrors = [result];
269
- element?.setCustomValidity(result);
337
+ element.setCustomValidity(result);
270
338
  }
271
339
  } else if (isValidatingOnChange) {
272
340
  // validate function returned no errors, if validating on change
273
341
  // we need to clear the custom validity state
274
342
  clearCustomValidity(element, registeredInputs);
275
343
  nextState.customError = false;
276
- if (element && element.validationMessage) {
344
+ if (element.validationMessage) {
277
345
  defaultValidationMessage = element.validationMessage;
278
346
  validationErrors = [element.validationMessage];
279
- } else if ((!element || element.validity.valid) && !nextState.valid) {
347
+ } else if (element.validity.valid && !nextState.valid) {
280
348
  nextState.valid = true;
281
349
  }
282
350
  }
@@ -311,85 +379,12 @@ function useFieldValidation(params) {
311
379
  return React__namespace.useMemo(() => ({
312
380
  getValidationProps,
313
381
  inputRef,
314
- registeredInputs,
315
382
  registerInput,
316
- getInputControl,
317
383
  commit,
318
384
  change
319
- }), [getValidationProps, registeredInputs, registerInput, getInputControl, commit, change]);
385
+ }), [getValidationProps, registerInput, commit, change]);
320
386
  }
321
387
 
322
- const FieldsetRootContext = /*#__PURE__*/React__namespace.createContext(undefined);
323
- if (process.env.NODE_ENV !== "production") FieldsetRootContext.displayName = "FieldsetRootContext";
324
- function useFieldsetRootContext(optional = false) {
325
- const context = React__namespace.useContext(FieldsetRootContext);
326
- if (!context && !optional) {
327
- throw new Error(process.env.NODE_ENV !== "production" ? 'Base UI: FieldsetRootContext is missing. Fieldset parts must be placed within <Fieldset.Root>.' : useRenderElement.formatErrorMessage(86));
328
- }
329
- return context;
330
- }
331
-
332
- const LabelableProvider = function LabelableProvider(props) {
333
- const defaultId = useScrollLock.useBaseUiId();
334
- const initialControlId = props.controlId === undefined ? defaultId : props.controlId;
335
- const [controlId, setControlIdState] = React__namespace.useState(initialControlId);
336
- const [labelId, setLabelId] = React__namespace.useState(props.labelId);
337
- const [messageIds, setMessageIds] = React__namespace.useState([]);
338
- const registrationsRef = useRenderElement.useRefWithInit(() => new Map());
339
- const {
340
- messageIds: parentMessageIds
341
- } = useCompositeListItem.useLabelableContext();
342
- const registerControlId = useScrollLock.useStableCallback((source, nextId) => {
343
- const registrations = registrationsRef.current;
344
- if (nextId === undefined) {
345
- registrations.delete(source);
346
- return;
347
- }
348
- registrations.set(source, nextId);
349
-
350
- // Only flush when registering, not when unregistering.
351
- // This prevents loops during rapid unmount/remount cycles (e.g. React Activity).
352
- // The next registration will pick up the correct state.
353
- setControlIdState(prev => {
354
- if (registrations.size === 0) {
355
- return undefined;
356
- }
357
- let nextControlId;
358
- for (const id of registrations.values()) {
359
- if (prev !== undefined && id === prev) {
360
- return prev;
361
- }
362
- if (nextControlId === undefined) {
363
- nextControlId = id;
364
- }
365
- }
366
- return nextControlId;
367
- });
368
- });
369
- const getDescriptionProps = React__namespace.useCallback(externalProps => {
370
- const ids = externalProps['aria-describedby'] ? externalProps['aria-describedby'].split(' ') : [];
371
- ids.push(...parentMessageIds, ...messageIds);
372
- return {
373
- ...externalProps,
374
- 'aria-describedby': Array.from(new Set(ids)).join(' ') || undefined
375
- };
376
- }, [parentMessageIds, messageIds]);
377
- const contextValue = React__namespace.useMemo(() => ({
378
- controlId,
379
- registerControlId,
380
- labelId,
381
- setLabelId,
382
- messageIds,
383
- setMessageIds,
384
- getDescriptionProps
385
- }), [controlId, registerControlId, labelId, setLabelId, messageIds, setMessageIds, getDescriptionProps]);
386
- return /*#__PURE__*/jsxRuntime.jsx(useCompositeListItem.LabelableContext.Provider, {
387
- value: contextValue,
388
- children: props.children
389
- });
390
- };
391
- if (process.env.NODE_ENV !== "production") LabelableProvider.displayName = "LabelableProvider";
392
-
393
388
  function useFieldControlRegistration(params) {
394
389
  const {
395
390
  commit,
@@ -397,7 +392,7 @@ function useFieldControlRegistration(params) {
397
392
  markedDirtyRef,
398
393
  name,
399
394
  setRegisteredFieldName,
400
- registeredFieldIdRef,
395
+ setRegisteredFieldId,
401
396
  setValidityData,
402
397
  validityData
403
398
  } = params;
@@ -406,7 +401,7 @@ function useFieldControlRegistration(params) {
406
401
  } = useCompositeListItem.useFormContext();
407
402
  const activeFieldControlSourceRef = React__namespace.useRef(null);
408
403
  const registrationRef = React__namespace.useRef(null);
409
- const initialValueCapturedRef = React__namespace.useRef(false);
404
+ const fallbackControlRef = React__namespace.useRef(null);
410
405
  const getValueForForm = useScrollLock.useStableCallback(() => {
411
406
  const registration = registrationRef.current;
412
407
  if (!registration) {
@@ -437,7 +432,7 @@ function useFieldControlRegistration(params) {
437
432
  formRef.current.fields.set(registration.id, {
438
433
  getValue: getValueForForm,
439
434
  name: name ?? registration.name,
440
- controlRef: registration.controlRef,
435
+ controlRef: registration.controlRef ?? fallbackControlRef,
441
436
  validityData: getCombinedFieldValidityData(validityData, invalid),
442
437
  validate
443
438
  });
@@ -447,23 +442,18 @@ function useFieldControlRegistration(params) {
447
442
  formRef.current.fields.delete(id);
448
443
  }
449
444
  }
450
-
451
- // The baseline belongs to the field, not to a control instance: registration re-runs on every
452
- // value change, and a control that unmounts and remounts (or is swapped for another one) comes
453
- // back as a brand new registration. Capturing more than once would turn whichever value the
454
- // control happens to hold at that point into the initial value, so a modified field would read
455
- // pristine and its real initial value would read dirty. Consumers that want a fresh baseline
456
- // remount or key `<Field.Root>` itself.
457
- function captureInitialValue(registration) {
458
- if (initialValueCapturedRef.current) {
445
+ function syncInitialValue() {
446
+ const registration = registrationRef.current;
447
+ if (!registration) {
459
448
  return;
460
449
  }
461
- initialValueCapturedRef.current = true;
462
450
  const initialValue = getRegistrationValue(registration);
463
- setValidityData(prev => prev.initialValue === initialValue ? prev : {
464
- ...prev,
465
- initialValue
466
- });
451
+ if (validityData.initialValue === null && initialValue !== null) {
452
+ setValidityData(prev => ({
453
+ ...prev,
454
+ initialValue
455
+ }));
456
+ }
467
457
  }
468
458
  useScrollLock.useIsoLayoutEffect(() => {
469
459
  const registration = registrationRef.current;
@@ -474,7 +464,7 @@ function useFieldControlRegistration(params) {
474
464
  formRef.current.fields.set(registration.id, {
475
465
  getValue: getValueForForm,
476
466
  name: name ?? registration.name,
477
- controlRef: registration.controlRef,
467
+ controlRef: registration.controlRef ?? fallbackControlRef,
478
468
  validityData: getCombinedFieldValidityData(validityData, invalid),
479
469
  validate
480
470
  });
@@ -495,7 +485,7 @@ function useFieldControlRegistration(params) {
495
485
  deleteRegistration();
496
486
  registrationRef.current = null;
497
487
  setRegisteredFieldName(undefined);
498
- registeredFieldIdRef.current = undefined;
488
+ setRegisteredFieldId(undefined);
499
489
  }
500
490
  return;
501
491
  }
@@ -505,11 +495,11 @@ function useFieldControlRegistration(params) {
505
495
  if (!name) {
506
496
  setRegisteredFieldName(registration.name);
507
497
  }
508
- registeredFieldIdRef.current = registration.id;
498
+ setRegisteredFieldId(registration.id);
509
499
  if (previousId && previousId !== registration.id) {
510
500
  deleteRegistration(previousId);
511
501
  }
512
- captureInitialValue(registration);
502
+ syncInitialValue();
513
503
  refreshRegistration();
514
504
  });
515
505
  return [validate, register];
@@ -554,6 +544,10 @@ const FieldRootInner = /*#__PURE__*/React__namespace.forwardRef(function FieldRo
554
544
  markedDirtyRef.current = dirtyProp;
555
545
  }
556
546
  }, [dirtyProp]);
547
+ const getRegisteredFieldId = React__namespace.useCallback(() => registeredFieldIdRef.current, []);
548
+ const setRegisteredFieldId = React__namespace.useCallback(id => {
549
+ registeredFieldIdRef.current = id;
550
+ }, []);
557
551
  const setDirty = useScrollLock.useStableCallback(value => {
558
552
  if (dirtyProp !== undefined) {
559
553
  return;
@@ -580,11 +574,7 @@ const FieldRootInner = /*#__PURE__*/React__namespace.forwardRef(function FieldRo
580
574
  value: null,
581
575
  initialValue: null
582
576
  });
583
-
584
- // App-controlled invalidity (the `invalid` prop and `<Form>` errors) keeps the field marked
585
- // invalid even while disabled. Only computed validity (native constraints and `validate`)
586
- // is suppressed when disabled, matching `:disabled` not participating in constraint validation.
587
- const valid = !invalid && (disabled ? null : validityData.state.valid);
577
+ const valid = disabled ? null : !invalid && validityData.state.valid;
588
578
  const state = React__namespace.useMemo(() => ({
589
579
  disabled,
590
580
  touched,
@@ -602,7 +592,7 @@ const FieldRootInner = /*#__PURE__*/React__namespace.forwardRef(function FieldRo
602
592
  markedDirtyRef,
603
593
  state,
604
594
  shouldValidateOnChange,
605
- registeredFieldIdRef
595
+ getRegisteredFieldId
606
596
  });
607
597
  const [validateFieldControl, registerFieldControl] = useFieldControlRegistration({
608
598
  commit: validation.commit,
@@ -610,7 +600,7 @@ const FieldRootInner = /*#__PURE__*/React__namespace.forwardRef(function FieldRo
610
600
  markedDirtyRef,
611
601
  name,
612
602
  setRegisteredFieldName,
613
- registeredFieldIdRef,
603
+ setRegisteredFieldId,
614
604
  setValidityData,
615
605
  validityData
616
606
  });
@@ -623,16 +613,23 @@ const FieldRootInner = /*#__PURE__*/React__namespace.forwardRef(function FieldRo
623
613
  validityData,
624
614
  setValidityData,
625
615
  disabled,
616
+ touched,
626
617
  setTouched,
618
+ dirty,
627
619
  setDirty,
620
+ filled,
628
621
  setFilled,
622
+ focused,
629
623
  setFocused,
624
+ validate,
630
625
  validationMode,
626
+ validationDebounceTime,
631
627
  shouldValidateOnChange,
632
628
  state,
629
+ markedDirtyRef,
633
630
  registerFieldControl,
634
631
  validation
635
- }), [invalid, effectiveName, validityData, disabled, setTouched, setDirty, setFilled, setFocused, validationMode, shouldValidateOnChange, state, registerFieldControl, validation]);
632
+ }), [invalid, effectiveName, validityData, disabled, touched, setTouched, dirty, setDirty, filled, setFilled, focused, setFocused, validate, validationMode, validationDebounceTime, shouldValidateOnChange, state, registerFieldControl, validation]);
636
633
  const element = useRenderElement.useRenderElement('div', componentProps, {
637
634
  ref: forwardedRef,
638
635
  state,
@@ -789,13 +786,13 @@ const FieldError = /*#__PURE__*/React__namespace.forwardRef(function FieldError(
789
786
  } else if (validityData.errors.length > 1) {
790
787
  error = validityData.errors;
791
788
  }
792
- let errorMessage = error;
789
+ let errorMessage = error ?? '';
793
790
  if (Array.isArray(error)) {
794
791
  errorMessage = error.length > 1 ? /*#__PURE__*/jsxRuntime.jsx("ul", {
795
792
  children: error.map(message => /*#__PURE__*/jsxRuntime.jsx("li", {
796
793
  children: message
797
794
  }, message))
798
- }) : error[0];
795
+ }) : error[0] ?? '';
799
796
  }
800
797
  const errorKey = Array.isArray(error) ? JSON.stringify(error) : error;
801
798
  if (rendered && errorKey !== lastRenderedMessageKey) {