@mui/x-date-pickers 8.17.0 → 8.19.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 (52) hide show
  1. package/CHANGELOG.md +213 -0
  2. package/DatePicker/DatePicker.js +8 -2
  3. package/DateTimePicker/DateTimePicker.js +8 -2
  4. package/DateTimePicker/DateTimePickerToolbar.js +2 -1
  5. package/DesktopDatePicker/DesktopDatePicker.js +8 -2
  6. package/DesktopDateTimePicker/DesktopDateTimePicker.js +8 -2
  7. package/DesktopTimePicker/DesktopTimePicker.js +8 -2
  8. package/MobileDatePicker/MobileDatePicker.js +8 -2
  9. package/MobileDateTimePicker/MobileDateTimePicker.js +8 -2
  10. package/MobileTimePicker/MobileTimePicker.js +8 -2
  11. package/PickersShortcuts/PickersShortcuts.js +2 -1
  12. package/StaticDatePicker/StaticDatePicker.js +8 -2
  13. package/StaticDateTimePicker/StaticDateTimePicker.js +8 -2
  14. package/StaticTimePicker/StaticTimePicker.js +8 -2
  15. package/TimePicker/TimePicker.js +8 -2
  16. package/TimePicker/TimePickerToolbar.js +2 -1
  17. package/esm/DatePicker/DatePicker.js +8 -2
  18. package/esm/DateTimePicker/DateTimePicker.js +8 -2
  19. package/esm/DateTimePicker/DateTimePickerToolbar.js +2 -1
  20. package/esm/DesktopDatePicker/DesktopDatePicker.js +8 -2
  21. package/esm/DesktopDateTimePicker/DesktopDateTimePicker.js +8 -2
  22. package/esm/DesktopTimePicker/DesktopTimePicker.js +8 -2
  23. package/esm/MobileDatePicker/MobileDatePicker.js +8 -2
  24. package/esm/MobileDateTimePicker/MobileDateTimePicker.js +8 -2
  25. package/esm/MobileTimePicker/MobileTimePicker.js +8 -2
  26. package/esm/PickersShortcuts/PickersShortcuts.js +2 -1
  27. package/esm/StaticDatePicker/StaticDatePicker.js +8 -2
  28. package/esm/StaticDateTimePicker/StaticDateTimePicker.js +8 -2
  29. package/esm/StaticTimePicker/StaticTimePicker.js +8 -2
  30. package/esm/TimePicker/TimePicker.js +8 -2
  31. package/esm/TimePicker/TimePickerToolbar.js +2 -1
  32. package/esm/index.js +1 -1
  33. package/esm/internals/components/PickerProvider.d.ts +8 -0
  34. package/esm/internals/hooks/useDesktopPicker/useDesktopPicker.js +2 -2
  35. package/esm/internals/hooks/useField/useFieldState.js +19 -10
  36. package/esm/internals/hooks/useField/useFieldV6TextField.js +1 -0
  37. package/esm/internals/hooks/useMobilePicker/useMobilePicker.js +2 -2
  38. package/esm/internals/hooks/usePicker/hooks/useValueAndOpenStates.js +14 -2
  39. package/esm/internals/hooks/usePicker/usePicker.js +13 -5
  40. package/esm/internals/hooks/usePicker/usePicker.types.d.ts +8 -2
  41. package/esm/models/pickers.d.ts +8 -0
  42. package/index.js +1 -1
  43. package/internals/components/PickerProvider.d.ts +8 -0
  44. package/internals/hooks/useDesktopPicker/useDesktopPicker.js +2 -2
  45. package/internals/hooks/useField/useFieldState.js +19 -10
  46. package/internals/hooks/useField/useFieldV6TextField.js +1 -0
  47. package/internals/hooks/useMobilePicker/useMobilePicker.js +2 -2
  48. package/internals/hooks/usePicker/hooks/useValueAndOpenStates.js +14 -2
  49. package/internals/hooks/usePicker/usePicker.js +13 -5
  50. package/internals/hooks/usePicker/usePicker.types.d.ts +8 -2
  51. package/models/pickers.d.ts +8 -0
  52. package/package.json +3 -3
@@ -187,7 +187,10 @@ DesktopDatePicker.propTypes = {
187
187
  * @template TValue The value type. It will be the same type as `value` or `null`. It can be in `[start, end]` format in case of range value.
188
188
  * @template TError The validation error type. It will be either `string` or a `null`. It can be in `[start, end]` format in case of range value.
189
189
  * @param {TValue} value The value that was just accepted.
190
- * @param {FieldChangeHandlerContext<TError>} context The context containing the validation result of the current value.
190
+ * @param {FieldChangeHandlerContext<TError>} context Context about this acceptance:
191
+ * - `validationError`: validation result of the current value
192
+ * - `source`: source of the acceptance. One of 'field' | 'picker' | 'unknown'
193
+ * - `shortcut` (optional): the shortcut metadata if the value was accepted via a shortcut selection
191
194
  */
192
195
  onAccept: PropTypes.func,
193
196
  /**
@@ -195,7 +198,10 @@ DesktopDatePicker.propTypes = {
195
198
  * @template TValue The value type. It will be the same type as `value` or `null`. It can be in `[start, end]` format in case of range value.
196
199
  * @template TError The validation error type. It will be either `string` or a `null`. It can be in `[start, end]` format in case of range value.
197
200
  * @param {TValue} value The new value.
198
- * @param {FieldChangeHandlerContext<TError>} context The context containing the validation result of the current value.
201
+ * @param {FieldChangeHandlerContext<TError>} context Context about this change:
202
+ * - `validationError`: validation result of the current value
203
+ * - `source`: source of the change. One of 'field' | 'view' | 'unknown'
204
+ * - `shortcut` (optional): the shortcut metadata if the change was triggered by a shortcut selection
199
205
  */
200
206
  onChange: PropTypes.func,
201
207
  /**
@@ -309,7 +309,10 @@ DesktopDateTimePicker.propTypes = {
309
309
  * @template TValue The value type. It will be the same type as `value` or `null`. It can be in `[start, end]` format in case of range value.
310
310
  * @template TError The validation error type. It will be either `string` or a `null`. It can be in `[start, end]` format in case of range value.
311
311
  * @param {TValue} value The value that was just accepted.
312
- * @param {FieldChangeHandlerContext<TError>} context The context containing the validation result of the current value.
312
+ * @param {FieldChangeHandlerContext<TError>} context Context about this acceptance:
313
+ * - `validationError`: validation result of the current value
314
+ * - `source`: source of the acceptance. One of 'field' | 'picker' | 'unknown'
315
+ * - `shortcut` (optional): the shortcut metadata if the value was accepted via a shortcut selection
313
316
  */
314
317
  onAccept: PropTypes.func,
315
318
  /**
@@ -317,7 +320,10 @@ DesktopDateTimePicker.propTypes = {
317
320
  * @template TValue The value type. It will be the same type as `value` or `null`. It can be in `[start, end]` format in case of range value.
318
321
  * @template TError The validation error type. It will be either `string` or a `null`. It can be in `[start, end]` format in case of range value.
319
322
  * @param {TValue} value The new value.
320
- * @param {FieldChangeHandlerContext<TError>} context The context containing the validation result of the current value.
323
+ * @param {FieldChangeHandlerContext<TError>} context Context about this change:
324
+ * - `validationError`: validation result of the current value
325
+ * - `source`: source of the change. One of 'field' | 'view' | 'unknown'
326
+ * - `shortcut` (optional): the shortcut metadata if the change was triggered by a shortcut selection
321
327
  */
322
328
  onChange: PropTypes.func,
323
329
  /**
@@ -191,7 +191,10 @@ DesktopTimePicker.propTypes = {
191
191
  * @template TValue The value type. It will be the same type as `value` or `null`. It can be in `[start, end]` format in case of range value.
192
192
  * @template TError The validation error type. It will be either `string` or a `null`. It can be in `[start, end]` format in case of range value.
193
193
  * @param {TValue} value The value that was just accepted.
194
- * @param {FieldChangeHandlerContext<TError>} context The context containing the validation result of the current value.
194
+ * @param {FieldChangeHandlerContext<TError>} context Context about this acceptance:
195
+ * - `validationError`: validation result of the current value
196
+ * - `source`: source of the acceptance. One of 'field' | 'picker' | 'unknown'
197
+ * - `shortcut` (optional): the shortcut metadata if the value was accepted via a shortcut selection
195
198
  */
196
199
  onAccept: PropTypes.func,
197
200
  /**
@@ -199,7 +202,10 @@ DesktopTimePicker.propTypes = {
199
202
  * @template TValue The value type. It will be the same type as `value` or `null`. It can be in `[start, end]` format in case of range value.
200
203
  * @template TError The validation error type. It will be either `string` or a `null`. It can be in `[start, end]` format in case of range value.
201
204
  * @param {TValue} value The new value.
202
- * @param {FieldChangeHandlerContext<TError>} context The context containing the validation result of the current value.
205
+ * @param {FieldChangeHandlerContext<TError>} context Context about this change:
206
+ * - `validationError`: validation result of the current value
207
+ * - `source`: source of the change. One of 'field' | 'view' | 'unknown'
208
+ * - `shortcut` (optional): the shortcut metadata if the change was triggered by a shortcut selection
203
209
  */
204
210
  onChange: PropTypes.func,
205
211
  /**
@@ -185,7 +185,10 @@ MobileDatePicker.propTypes = {
185
185
  * @template TValue The value type. It will be the same type as `value` or `null`. It can be in `[start, end]` format in case of range value.
186
186
  * @template TError The validation error type. It will be either `string` or a `null`. It can be in `[start, end]` format in case of range value.
187
187
  * @param {TValue} value The value that was just accepted.
188
- * @param {FieldChangeHandlerContext<TError>} context The context containing the validation result of the current value.
188
+ * @param {FieldChangeHandlerContext<TError>} context Context about this acceptance:
189
+ * - `validationError`: validation result of the current value
190
+ * - `source`: source of the acceptance. One of 'field' | 'picker' | 'unknown'
191
+ * - `shortcut` (optional): the shortcut metadata if the value was accepted via a shortcut selection
189
192
  */
190
193
  onAccept: PropTypes.func,
191
194
  /**
@@ -193,7 +196,10 @@ MobileDatePicker.propTypes = {
193
196
  * @template TValue The value type. It will be the same type as `value` or `null`. It can be in `[start, end]` format in case of range value.
194
197
  * @template TError The validation error type. It will be either `string` or a `null`. It can be in `[start, end]` format in case of range value.
195
198
  * @param {TValue} value The new value.
196
- * @param {FieldChangeHandlerContext<TError>} context The context containing the validation result of the current value.
199
+ * @param {FieldChangeHandlerContext<TError>} context Context about this change:
200
+ * - `validationError`: validation result of the current value
201
+ * - `source`: source of the change. One of 'field' | 'view' | 'unknown'
202
+ * - `shortcut` (optional): the shortcut metadata if the change was triggered by a shortcut selection
197
203
  */
198
204
  onChange: PropTypes.func,
199
205
  /**
@@ -275,7 +275,10 @@ MobileDateTimePicker.propTypes = {
275
275
  * @template TValue The value type. It will be the same type as `value` or `null`. It can be in `[start, end]` format in case of range value.
276
276
  * @template TError The validation error type. It will be either `string` or a `null`. It can be in `[start, end]` format in case of range value.
277
277
  * @param {TValue} value The value that was just accepted.
278
- * @param {FieldChangeHandlerContext<TError>} context The context containing the validation result of the current value.
278
+ * @param {FieldChangeHandlerContext<TError>} context Context about this acceptance:
279
+ * - `validationError`: validation result of the current value
280
+ * - `source`: source of the acceptance. One of 'field' | 'picker' | 'unknown'
281
+ * - `shortcut` (optional): the shortcut metadata if the value was accepted via a shortcut selection
279
282
  */
280
283
  onAccept: PropTypes.func,
281
284
  /**
@@ -283,7 +286,10 @@ MobileDateTimePicker.propTypes = {
283
286
  * @template TValue The value type. It will be the same type as `value` or `null`. It can be in `[start, end]` format in case of range value.
284
287
  * @template TError The validation error type. It will be either `string` or a `null`. It can be in `[start, end]` format in case of range value.
285
288
  * @param {TValue} value The new value.
286
- * @param {FieldChangeHandlerContext<TError>} context The context containing the validation result of the current value.
289
+ * @param {FieldChangeHandlerContext<TError>} context Context about this change:
290
+ * - `validationError`: validation result of the current value
291
+ * - `source`: source of the change. One of 'field' | 'view' | 'unknown'
292
+ * - `shortcut` (optional): the shortcut metadata if the change was triggered by a shortcut selection
287
293
  */
288
294
  onChange: PropTypes.func,
289
295
  /**
@@ -176,7 +176,10 @@ MobileTimePicker.propTypes = {
176
176
  * @template TValue The value type. It will be the same type as `value` or `null`. It can be in `[start, end]` format in case of range value.
177
177
  * @template TError The validation error type. It will be either `string` or a `null`. It can be in `[start, end]` format in case of range value.
178
178
  * @param {TValue} value The value that was just accepted.
179
- * @param {FieldChangeHandlerContext<TError>} context The context containing the validation result of the current value.
179
+ * @param {FieldChangeHandlerContext<TError>} context Context about this acceptance:
180
+ * - `validationError`: validation result of the current value
181
+ * - `source`: source of the acceptance. One of 'field' | 'picker' | 'unknown'
182
+ * - `shortcut` (optional): the shortcut metadata if the value was accepted via a shortcut selection
180
183
  */
181
184
  onAccept: PropTypes.func,
182
185
  /**
@@ -184,7 +187,10 @@ MobileTimePicker.propTypes = {
184
187
  * @template TValue The value type. It will be the same type as `value` or `null`. It can be in `[start, end]` format in case of range value.
185
188
  * @template TError The validation error type. It will be either `string` or a `null`. It can be in `[start, end]` format in case of range value.
186
189
  * @param {TValue} value The new value.
187
- * @param {FieldChangeHandlerContext<TError>} context The context containing the validation result of the current value.
190
+ * @param {FieldChangeHandlerContext<TError>} context Context about this change:
191
+ * - `validationError`: validation result of the current value
192
+ * - `source`: source of the change. One of 'field' | 'view' | 'unknown'
193
+ * - `shortcut` (optional): the shortcut metadata if the change was triggered by a shortcut selection
188
194
  */
189
195
  onChange: PropTypes.func,
190
196
  /**
@@ -52,7 +52,8 @@ function PickersShortcuts(props) {
52
52
  onClick: () => {
53
53
  setValue(newValue, {
54
54
  changeImportance,
55
- shortcut: item
55
+ shortcut: item,
56
+ source: 'view'
56
57
  });
57
58
  },
58
59
  disabled: !isValidValue(newValue)
@@ -142,7 +142,10 @@ StaticDatePicker.propTypes = {
142
142
  * @template TValue The value type. It will be the same type as `value` or `null`. It can be in `[start, end]` format in case of range value.
143
143
  * @template TError The validation error type. It will be either `string` or a `null`. It can be in `[start, end]` format in case of range value.
144
144
  * @param {TValue} value The value that was just accepted.
145
- * @param {FieldChangeHandlerContext<TError>} context The context containing the validation result of the current value.
145
+ * @param {FieldChangeHandlerContext<TError>} context Context about this acceptance:
146
+ * - `validationError`: validation result of the current value
147
+ * - `source`: source of the acceptance. One of 'field' | 'picker' | 'unknown'
148
+ * - `shortcut` (optional): the shortcut metadata if the value was accepted via a shortcut selection
146
149
  */
147
150
  onAccept: PropTypes.func,
148
151
  /**
@@ -150,7 +153,10 @@ StaticDatePicker.propTypes = {
150
153
  * @template TValue The value type. It will be the same type as `value` or `null`. It can be in `[start, end]` format in case of range value.
151
154
  * @template TError The validation error type. It will be either `string` or a `null`. It can be in `[start, end]` format in case of range value.
152
155
  * @param {TValue} value The new value.
153
- * @param {FieldChangeHandlerContext<TError>} context The context containing the validation result of the current value.
156
+ * @param {FieldChangeHandlerContext<TError>} context Context about this change:
157
+ * - `validationError`: validation result of the current value
158
+ * - `source`: source of the change. One of 'field' | 'view' | 'unknown'
159
+ * - `shortcut` (optional): the shortcut metadata if the change was triggered by a shortcut selection
154
160
  */
155
161
  onChange: PropTypes.func,
156
162
  /**
@@ -229,7 +229,10 @@ StaticDateTimePicker.propTypes = {
229
229
  * @template TValue The value type. It will be the same type as `value` or `null`. It can be in `[start, end]` format in case of range value.
230
230
  * @template TError The validation error type. It will be either `string` or a `null`. It can be in `[start, end]` format in case of range value.
231
231
  * @param {TValue} value The value that was just accepted.
232
- * @param {FieldChangeHandlerContext<TError>} context The context containing the validation result of the current value.
232
+ * @param {FieldChangeHandlerContext<TError>} context Context about this acceptance:
233
+ * - `validationError`: validation result of the current value
234
+ * - `source`: source of the acceptance. One of 'field' | 'picker' | 'unknown'
235
+ * - `shortcut` (optional): the shortcut metadata if the value was accepted via a shortcut selection
233
236
  */
234
237
  onAccept: PropTypes.func,
235
238
  /**
@@ -237,7 +240,10 @@ StaticDateTimePicker.propTypes = {
237
240
  * @template TValue The value type. It will be the same type as `value` or `null`. It can be in `[start, end]` format in case of range value.
238
241
  * @template TError The validation error type. It will be either `string` or a `null`. It can be in `[start, end]` format in case of range value.
239
242
  * @param {TValue} value The new value.
240
- * @param {FieldChangeHandlerContext<TError>} context The context containing the validation result of the current value.
243
+ * @param {FieldChangeHandlerContext<TError>} context Context about this change:
244
+ * - `validationError`: validation result of the current value
245
+ * - `source`: source of the change. One of 'field' | 'view' | 'unknown'
246
+ * - `shortcut` (optional): the shortcut metadata if the change was triggered by a shortcut selection
241
247
  */
242
248
  onChange: PropTypes.func,
243
249
  /**
@@ -132,7 +132,10 @@ StaticTimePicker.propTypes = {
132
132
  * @template TValue The value type. It will be the same type as `value` or `null`. It can be in `[start, end]` format in case of range value.
133
133
  * @template TError The validation error type. It will be either `string` or a `null`. It can be in `[start, end]` format in case of range value.
134
134
  * @param {TValue} value The value that was just accepted.
135
- * @param {FieldChangeHandlerContext<TError>} context The context containing the validation result of the current value.
135
+ * @param {FieldChangeHandlerContext<TError>} context Context about this acceptance:
136
+ * - `validationError`: validation result of the current value
137
+ * - `source`: source of the acceptance. One of 'field' | 'picker' | 'unknown'
138
+ * - `shortcut` (optional): the shortcut metadata if the value was accepted via a shortcut selection
136
139
  */
137
140
  onAccept: PropTypes.func,
138
141
  /**
@@ -140,7 +143,10 @@ StaticTimePicker.propTypes = {
140
143
  * @template TValue The value type. It will be the same type as `value` or `null`. It can be in `[start, end]` format in case of range value.
141
144
  * @template TError The validation error type. It will be either `string` or a `null`. It can be in `[start, end]` format in case of range value.
142
145
  * @param {TValue} value The new value.
143
- * @param {FieldChangeHandlerContext<TError>} context The context containing the validation result of the current value.
146
+ * @param {FieldChangeHandlerContext<TError>} context Context about this change:
147
+ * - `validationError`: validation result of the current value
148
+ * - `source`: source of the change. One of 'field' | 'view' | 'unknown'
149
+ * - `shortcut` (optional): the shortcut metadata if the change was triggered by a shortcut selection
144
150
  */
145
151
  onChange: PropTypes.func,
146
152
  /**
@@ -164,7 +164,10 @@ process.env.NODE_ENV !== "production" ? TimePicker.propTypes = {
164
164
  * @template TValue The value type. It will be the same type as `value` or `null`. It can be in `[start, end]` format in case of range value.
165
165
  * @template TError The validation error type. It will be either `string` or a `null`. It can be in `[start, end]` format in case of range value.
166
166
  * @param {TValue} value The value that was just accepted.
167
- * @param {FieldChangeHandlerContext<TError>} context The context containing the validation result of the current value.
167
+ * @param {FieldChangeHandlerContext<TError>} context Context about this acceptance:
168
+ * - `validationError`: validation result of the current value
169
+ * - `source`: source of the acceptance. One of 'field' | 'picker' | 'unknown'
170
+ * - `shortcut` (optional): the shortcut metadata if the value was accepted via a shortcut selection
168
171
  */
169
172
  onAccept: PropTypes.func,
170
173
  /**
@@ -172,7 +175,10 @@ process.env.NODE_ENV !== "production" ? TimePicker.propTypes = {
172
175
  * @template TValue The value type. It will be the same type as `value` or `null`. It can be in `[start, end]` format in case of range value.
173
176
  * @template TError The validation error type. It will be either `string` or a `null`. It can be in `[start, end]` format in case of range value.
174
177
  * @param {TValue} value The new value.
175
- * @param {FieldChangeHandlerContext<TError>} context The context containing the validation result of the current value.
178
+ * @param {FieldChangeHandlerContext<TError>} context Context about this change:
179
+ * - `validationError`: validation result of the current value
180
+ * - `source`: source of the change. One of 'field' | 'view' | 'unknown'
181
+ * - `shortcut` (optional): the shortcut metadata if the change was triggered by a shortcut selection
176
182
  */
177
183
  onChange: PropTypes.func,
178
184
  /**
@@ -139,7 +139,8 @@ function TimePickerToolbar(inProps) {
139
139
  meridiemMode,
140
140
  handleMeridiemChange
141
141
  } = useMeridiemMode(value, ampm, newValue => setValue(newValue, {
142
- changeImportance: 'set'
142
+ changeImportance: 'set',
143
+ source: 'view'
143
144
  }));
144
145
  const formatSection = format => {
145
146
  if (!adapter.isValid(value)) {
package/esm/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @mui/x-date-pickers v8.17.0
2
+ * @mui/x-date-pickers v8.19.0
3
3
  *
4
4
  * @license MIT
5
5
  * This source code is licensed under the MIT license found in the
@@ -232,6 +232,14 @@ export interface SetValueActionOptions<TError = string | null> {
232
232
  * It should not be defined if the change does not come from a shortcut.
233
233
  */
234
234
  shortcut?: PickersShortcutsItemContext;
235
+ /**
236
+ * Source of the change.
237
+ * Simplified to one of the following values:
238
+ * - 'field' (changes coming from the text field)
239
+ * - 'view' (any interaction inside the picker UI: views, toolbar, action bar, shortcuts, etc.)
240
+ * - 'unknown' (unspecified or third-party triggers)
241
+ */
242
+ source?: 'field' | 'view' | 'unknown';
235
243
  /**
236
244
  * Whether the value should call `onChange` and `onAccept` when the value is not controlled and has never been modified.
237
245
  * If `true`, the `onChange` and `onAccept` callback will only be fired if the value has been modified (and is not equal to the last published value).
@@ -75,8 +75,8 @@ export const useDesktopPicker = _ref => {
75
75
  });
76
76
  const renderPicker = () => /*#__PURE__*/_jsxs(PickerProvider, _extends({}, providerProps, {
77
77
  children: [/*#__PURE__*/_jsx(Field, _extends({}, fieldProps, {
78
- slots: slots,
79
- slotProps: slotProps,
78
+ slots: _extends({}, slots, fieldProps.slots),
79
+ slotProps: _extends({}, slotProps, fieldProps.slotProps),
80
80
  inputRef: inputRef
81
81
  })), /*#__PURE__*/_jsx(PickerPopper, {
82
82
  slots: slots,
@@ -68,14 +68,6 @@ export const useFieldState = parameters => {
68
68
  value,
69
69
  onError: internalPropsWithDefaults.onError
70
70
  });
71
- const error = React.useMemo(() => {
72
- // only override when `error` is undefined.
73
- // in case of multi input fields, the `error` value is provided externally and will always be defined.
74
- if (errorProp !== undefined) {
75
- return errorProp;
76
- }
77
- return hasValidationError;
78
- }, [hasValidationError, errorProp]);
79
71
  const localizedDigits = React.useMemo(() => getLocalizedDigits(adapter), [adapter]);
80
72
  const sectionsValueBoundaries = React.useMemo(() => getSectionsBoundaries(adapter, localizedDigits, timezone), [adapter, localizedDigits, timezone]);
81
73
  const getSectionsFromValue = React.useCallback(valueToAnalyze => fieldValueManager.getSectionsFromValue(valueToAnalyze, date => buildSectionsFromFormat({
@@ -130,6 +122,22 @@ export const useFieldState = parameters => {
130
122
  const activeSectionIndex = parsedSelectedSections === 'all' ? 0 : parsedSelectedSections;
131
123
  const sectionOrder = React.useMemo(() => getSectionOrder(state.sections, isRtl && !enableAccessibleFieldDOMStructure), [state.sections, isRtl, enableAccessibleFieldDOMStructure]);
132
124
  const areAllSectionsEmpty = React.useMemo(() => state.sections.every(section => section.value === ''), [state.sections]);
125
+
126
+ // When the field loses focus (no active section), consider partially filled sections as invalid.
127
+ // This enforces that the field must be entirely filled or entirely empty on blur.
128
+ const hasPartiallyFilledSectionsOnBlur = React.useMemo(() => {
129
+ if (activeSectionIndex != null) {
130
+ return false;
131
+ }
132
+ const filledSections = state.sections.filter(s => s.value !== '');
133
+ return filledSections.length > 0 && state.sections.length - filledSections.length > 0;
134
+ }, [state.sections, activeSectionIndex]);
135
+ const error = React.useMemo(() => {
136
+ if (errorProp !== undefined) {
137
+ return errorProp;
138
+ }
139
+ return hasValidationError || hasPartiallyFilledSectionsOnBlur;
140
+ }, [hasValidationError, hasPartiallyFilledSectionsOnBlur, errorProp]);
133
141
  const publishValue = newValue => {
134
142
  const context = {
135
143
  validationError: validator({
@@ -278,11 +286,12 @@ export const useFieldState = parameters => {
278
286
 
279
287
  /**
280
288
  * If the previous date is not null,
281
- * Then we publish the date as `null`.
289
+ * Then we publish the date as `newActiveDate to prevent error state oscillation`.
290
+ * @link: https://github.com/mui/mui-x/issues/17967
282
291
  */
283
292
  if (activeDate != null) {
284
293
  setSectionUpdateToApplyOnNextInvalidDate(newSectionValue);
285
- return publishValue(fieldValueManager.updateDateInValue(value, section, null));
294
+ publishValue(fieldValueManager.updateDateInValue(value, section, newActiveDate));
286
295
  }
287
296
 
288
297
  /**
@@ -384,6 +384,7 @@ export const useFieldV6TextField = parameters => {
384
384
  }));
385
385
  return _extends({}, forwardedProps, {
386
386
  error,
387
+ 'aria-invalid': error,
387
388
  clearable: Boolean(clearable && !areAllSectionsEmpty && !readOnly && !disabled),
388
389
  onBlur: handleContainerBlur,
389
390
  onClick: handleInputClick,
@@ -75,8 +75,8 @@ export const useMobilePicker = _ref => {
75
75
  });
76
76
  const renderPicker = () => /*#__PURE__*/_jsxs(PickerProvider, _extends({}, providerProps, {
77
77
  children: [/*#__PURE__*/_jsx(Field, _extends({}, fieldProps, {
78
- slots: slots,
79
- slotProps: slotProps,
78
+ slots: _extends({}, slots, fieldProps.slots),
79
+ slotProps: _extends({}, slotProps, fieldProps.slotProps),
80
80
  inputRef: inputRef
81
81
  })), /*#__PURE__*/_jsx(PickersModalDialog, {
82
82
  slots: slots,
@@ -105,6 +105,7 @@ export function useValueAndOpenStates(parameters) {
105
105
  skipPublicationIfPristine = false,
106
106
  validationError,
107
107
  shortcut,
108
+ source,
108
109
  shouldClose = changeImportance === 'accept'
109
110
  } = options ?? {};
110
111
  let shouldFireOnChange;
@@ -127,8 +128,18 @@ export function useValueAndOpenStates(parameters) {
127
128
  let cachedContext = null;
128
129
  const getContext = () => {
129
130
  if (!cachedContext) {
131
+ let inferredSource;
132
+ if (source) {
133
+ inferredSource = source;
134
+ } else if (shortcut) {
135
+ inferredSource = 'view';
136
+ } else {
137
+ // Default to unknown when not explicitly tagged by a picker call site
138
+ inferredSource = 'unknown';
139
+ }
130
140
  cachedContext = {
131
- validationError: validationError == null ? getValidationErrorForNewValue(newValue) : validationError
141
+ validationError: validationError == null ? getValidationErrorForNewValue(newValue) : validationError,
142
+ source: inferredSource
132
143
  };
133
144
  if (shortcut) {
134
145
  cachedContext.shortcut = shortcut;
@@ -165,7 +176,8 @@ export function useValueAndOpenStates(parameters) {
165
176
  return;
166
177
  }
167
178
  setValue(newValue, {
168
- changeImportance: selectionState === 'finish' && closeOnSelect ? 'accept' : 'set'
179
+ changeImportance: selectionState === 'finish' && closeOnSelect ? 'accept' : 'set',
180
+ source: 'view'
169
181
  });
170
182
  });
171
183
 
@@ -111,15 +111,23 @@ export const usePicker = ({
111
111
  autoFocus: autoFocusView,
112
112
  getStepNavigation
113
113
  });
114
- const clearValue = useEventCallback(() => setValue(valueManager.emptyValue));
115
- const setValueToToday = useEventCallback(() => setValue(valueManager.getTodayValue(adapter, timezone, valueType)));
116
- const acceptValueChanges = useEventCallback(() => setValue(value));
114
+ const clearValue = useEventCallback(() => setValue(valueManager.emptyValue, {
115
+ source: 'view'
116
+ }));
117
+ const setValueToToday = useEventCallback(() => setValue(valueManager.getTodayValue(adapter, timezone, valueType), {
118
+ source: 'view'
119
+ }));
120
+ const acceptValueChanges = useEventCallback(() => setValue(value, {
121
+ source: 'view'
122
+ }));
117
123
  const cancelValueChanges = useEventCallback(() => setValue(state.lastCommittedValue, {
118
- skipPublicationIfPristine: true
124
+ skipPublicationIfPristine: true,
125
+ source: 'view'
119
126
  }));
120
127
  const dismissViews = useEventCallback(() => {
121
128
  setValue(value, {
122
- skipPublicationIfPristine: true
129
+ skipPublicationIfPristine: true,
130
+ source: 'view'
123
131
  });
124
132
  });
125
133
  const {
@@ -28,7 +28,10 @@ export interface UsePickerBaseProps<TValue extends PickerValidValue, TView exten
28
28
  * @template TValue The value type. It will be the same type as `value` or `null`. It can be in `[start, end]` format in case of range value.
29
29
  * @template TError The validation error type. It will be either `string` or a `null`. It can be in `[start, end]` format in case of range value.
30
30
  * @param {TValue} value The new value.
31
- * @param {FieldChangeHandlerContext<TError>} context The context containing the validation result of the current value.
31
+ * @param {FieldChangeHandlerContext<TError>} context Context about this change:
32
+ * - `validationError`: validation result of the current value
33
+ * - `source`: source of the change. One of 'field' | 'view' | 'unknown'
34
+ * - `shortcut` (optional): the shortcut metadata if the change was triggered by a shortcut selection
32
35
  */
33
36
  onChange?: (value: TValue, context: PickerChangeHandlerContext<TError>) => void;
34
37
  /**
@@ -36,7 +39,10 @@ export interface UsePickerBaseProps<TValue extends PickerValidValue, TView exten
36
39
  * @template TValue The value type. It will be the same type as `value` or `null`. It can be in `[start, end]` format in case of range value.
37
40
  * @template TError The validation error type. It will be either `string` or a `null`. It can be in `[start, end]` format in case of range value.
38
41
  * @param {TValue} value The value that was just accepted.
39
- * @param {FieldChangeHandlerContext<TError>} context The context containing the validation result of the current value.
42
+ * @param {FieldChangeHandlerContext<TError>} context Context about this acceptance:
43
+ * - `validationError`: validation result of the current value
44
+ * - `source`: source of the acceptance. One of 'field' | 'picker' | 'unknown'
45
+ * - `shortcut` (optional): the shortcut metadata if the value was accepted via a shortcut selection
40
46
  */
41
47
  onAccept?: (value: TValue, context: PickerChangeHandlerContext<TError>) => void;
42
48
  /**
@@ -2,6 +2,14 @@ import { PickerOrientation, PickerVariant } from "../internals/models/common.js"
2
2
  import type { PickersShortcutsItemContext } from "../PickersShortcuts/index.js";
3
3
  export interface PickerChangeHandlerContext<TError> {
4
4
  validationError: TError;
5
+ /**
6
+ * Source of the change that triggered `onChange` or `onAccept`.
7
+ * Simplified to one of the following values:
8
+ * - 'field' (changes coming from the text field)
9
+ * - 'view' (any interaction inside the picker UI: views, toolbar, action bar, shortcuts, etc.)
10
+ * - 'unknown' (unspecified or third-party triggers)
11
+ */
12
+ source: 'field' | 'view' | 'unknown';
5
13
  /**
6
14
  * Shortcut causing this `onChange` or `onAccept` call.
7
15
  * If the call has not been caused by a shortcut selection, this property will be `undefined`.
package/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @mui/x-date-pickers v8.17.0
2
+ * @mui/x-date-pickers v8.19.0
3
3
  *
4
4
  * @license MIT
5
5
  * This source code is licensed under the MIT license found in the
@@ -232,6 +232,14 @@ export interface SetValueActionOptions<TError = string | null> {
232
232
  * It should not be defined if the change does not come from a shortcut.
233
233
  */
234
234
  shortcut?: PickersShortcutsItemContext;
235
+ /**
236
+ * Source of the change.
237
+ * Simplified to one of the following values:
238
+ * - 'field' (changes coming from the text field)
239
+ * - 'view' (any interaction inside the picker UI: views, toolbar, action bar, shortcuts, etc.)
240
+ * - 'unknown' (unspecified or third-party triggers)
241
+ */
242
+ source?: 'field' | 'view' | 'unknown';
235
243
  /**
236
244
  * Whether the value should call `onChange` and `onAccept` when the value is not controlled and has never been modified.
237
245
  * If `true`, the `onChange` and `onAccept` callback will only be fired if the value has been modified (and is not equal to the last published value).
@@ -81,8 +81,8 @@ const useDesktopPicker = _ref => {
81
81
  });
82
82
  const renderPicker = () => /*#__PURE__*/(0, _jsxRuntime.jsxs)(_PickerProvider.PickerProvider, (0, _extends2.default)({}, providerProps, {
83
83
  children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(Field, (0, _extends2.default)({}, fieldProps, {
84
- slots: slots,
85
- slotProps: slotProps,
84
+ slots: (0, _extends2.default)({}, slots, fieldProps.slots),
85
+ slotProps: (0, _extends2.default)({}, slotProps, fieldProps.slotProps),
86
86
  inputRef: inputRef
87
87
  })), /*#__PURE__*/(0, _jsxRuntime.jsx)(_PickerPopper.PickerPopper, {
88
88
  slots: slots,
@@ -75,14 +75,6 @@ const useFieldState = parameters => {
75
75
  value,
76
76
  onError: internalPropsWithDefaults.onError
77
77
  });
78
- const error = React.useMemo(() => {
79
- // only override when `error` is undefined.
80
- // in case of multi input fields, the `error` value is provided externally and will always be defined.
81
- if (errorProp !== undefined) {
82
- return errorProp;
83
- }
84
- return hasValidationError;
85
- }, [hasValidationError, errorProp]);
86
78
  const localizedDigits = React.useMemo(() => (0, _useField.getLocalizedDigits)(adapter), [adapter]);
87
79
  const sectionsValueBoundaries = React.useMemo(() => (0, _useField.getSectionsBoundaries)(adapter, localizedDigits, timezone), [adapter, localizedDigits, timezone]);
88
80
  const getSectionsFromValue = React.useCallback(valueToAnalyze => fieldValueManager.getSectionsFromValue(valueToAnalyze, date => (0, _buildSectionsFromFormat.buildSectionsFromFormat)({
@@ -137,6 +129,22 @@ const useFieldState = parameters => {
137
129
  const activeSectionIndex = parsedSelectedSections === 'all' ? 0 : parsedSelectedSections;
138
130
  const sectionOrder = React.useMemo(() => (0, _useField.getSectionOrder)(state.sections, isRtl && !enableAccessibleFieldDOMStructure), [state.sections, isRtl, enableAccessibleFieldDOMStructure]);
139
131
  const areAllSectionsEmpty = React.useMemo(() => state.sections.every(section => section.value === ''), [state.sections]);
132
+
133
+ // When the field loses focus (no active section), consider partially filled sections as invalid.
134
+ // This enforces that the field must be entirely filled or entirely empty on blur.
135
+ const hasPartiallyFilledSectionsOnBlur = React.useMemo(() => {
136
+ if (activeSectionIndex != null) {
137
+ return false;
138
+ }
139
+ const filledSections = state.sections.filter(s => s.value !== '');
140
+ return filledSections.length > 0 && state.sections.length - filledSections.length > 0;
141
+ }, [state.sections, activeSectionIndex]);
142
+ const error = React.useMemo(() => {
143
+ if (errorProp !== undefined) {
144
+ return errorProp;
145
+ }
146
+ return hasValidationError || hasPartiallyFilledSectionsOnBlur;
147
+ }, [hasValidationError, hasPartiallyFilledSectionsOnBlur, errorProp]);
140
148
  const publishValue = newValue => {
141
149
  const context = {
142
150
  validationError: validator({
@@ -285,11 +293,12 @@ const useFieldState = parameters => {
285
293
 
286
294
  /**
287
295
  * If the previous date is not null,
288
- * Then we publish the date as `null`.
296
+ * Then we publish the date as `newActiveDate to prevent error state oscillation`.
297
+ * @link: https://github.com/mui/mui-x/issues/17967
289
298
  */
290
299
  if (activeDate != null) {
291
300
  setSectionUpdateToApplyOnNextInvalidDate(newSectionValue);
292
- return publishValue(fieldValueManager.updateDateInValue(value, section, null));
301
+ publishValue(fieldValueManager.updateDateInValue(value, section, newActiveDate));
293
302
  }
294
303
 
295
304
  /**
@@ -392,6 +392,7 @@ const useFieldV6TextField = parameters => {
392
392
  }));
393
393
  return (0, _extends2.default)({}, forwardedProps, {
394
394
  error,
395
+ 'aria-invalid': error,
395
396
  clearable: Boolean(clearable && !areAllSectionsEmpty && !readOnly && !disabled),
396
397
  onBlur: handleContainerBlur,
397
398
  onClick: handleInputClick,
@@ -81,8 +81,8 @@ const useMobilePicker = _ref => {
81
81
  });
82
82
  const renderPicker = () => /*#__PURE__*/(0, _jsxRuntime.jsxs)(_PickerProvider.PickerProvider, (0, _extends2.default)({}, providerProps, {
83
83
  children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(Field, (0, _extends2.default)({}, fieldProps, {
84
- slots: slots,
85
- slotProps: slotProps,
84
+ slots: (0, _extends2.default)({}, slots, fieldProps.slots),
85
+ slotProps: (0, _extends2.default)({}, slotProps, fieldProps.slotProps),
86
86
  inputRef: inputRef
87
87
  })), /*#__PURE__*/(0, _jsxRuntime.jsx)(_PickersModalDialog.PickersModalDialog, {
88
88
  slots: slots,