@mui/x-date-pickers 6.1.0 → 6.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (198) hide show
  1. package/AdapterDayjs/AdapterDayjs.d.ts +114 -0
  2. package/AdapterDayjs/AdapterDayjs.js +387 -0
  3. package/AdapterDayjs/index.d.ts +1 -28
  4. package/AdapterDayjs/index.js +1 -102
  5. package/CHANGELOG.md +101 -1
  6. package/DateCalendar/DateCalendar.types.d.ts +1 -1
  7. package/DateCalendar/DayCalendar.d.ts +1 -1
  8. package/DateField/DateField.js +6 -0
  9. package/DateField/useDateField.js +3 -1
  10. package/DatePicker/DatePicker.js +6 -0
  11. package/DateTimeField/DateTimeField.js +6 -0
  12. package/DateTimeField/useDateTimeField.js +3 -1
  13. package/DateTimePicker/DateTimePicker.js +6 -0
  14. package/DateTimePicker/shared.js +3 -1
  15. package/DesktopDatePicker/DesktopDatePicker.js +7 -0
  16. package/DesktopDateTimePicker/DesktopDateTimePicker.js +7 -0
  17. package/DesktopTimePicker/DesktopTimePicker.js +7 -0
  18. package/LocalizationProvider/LocalizationProvider.d.ts +1 -1
  19. package/LocalizationProvider/LocalizationProvider.js +1 -1
  20. package/MobileDatePicker/MobileDatePicker.js +7 -0
  21. package/MobileDateTimePicker/MobileDateTimePicker.js +7 -0
  22. package/MobileTimePicker/MobileTimePicker.js +7 -0
  23. package/README.md +1 -0
  24. package/StaticDatePicker/StaticDatePicker.js +1 -0
  25. package/StaticDateTimePicker/StaticDateTimePicker.js +3 -2
  26. package/StaticTimePicker/StaticTimePicker.js +3 -2
  27. package/TimeClock/TimeClock.types.d.ts +1 -1
  28. package/TimeField/TimeField.js +6 -0
  29. package/TimeField/useTimeField.js +3 -1
  30. package/TimePicker/TimePicker.js +6 -0
  31. package/index.js +1 -1
  32. package/internals/components/PickersModalDialog.d.ts +1 -1
  33. package/internals/components/PickersPopper.d.ts +1 -1
  34. package/internals/hooks/useDesktopPicker/useDesktopPicker.d.ts +1 -1
  35. package/internals/hooks/useDesktopPicker/useDesktopPicker.js +15 -14
  36. package/internals/hooks/useDesktopPicker/useDesktopPicker.types.d.ts +2 -2
  37. package/internals/hooks/useField/useField.types.d.ts +7 -2
  38. package/internals/hooks/useField/useField.utils.d.ts +3 -3
  39. package/internals/hooks/useField/useField.utils.js +16 -4
  40. package/internals/hooks/useField/useFieldState.js +4 -3
  41. package/internals/hooks/useMobilePicker/useMobilePicker.d.ts +1 -1
  42. package/internals/hooks/useMobilePicker/useMobilePicker.js +12 -10
  43. package/internals/hooks/useMobilePicker/useMobilePicker.types.d.ts +2 -2
  44. package/internals/hooks/usePicker/index.d.ts +1 -1
  45. package/internals/hooks/usePicker/usePicker.d.ts +1 -1
  46. package/internals/hooks/usePicker/usePicker.js +2 -0
  47. package/internals/hooks/usePicker/usePicker.types.d.ts +2 -2
  48. package/internals/hooks/usePicker/usePickerLayoutProps.d.ts +1 -1
  49. package/internals/hooks/usePicker/usePickerValue.d.ts +4 -180
  50. package/internals/hooks/usePicker/usePickerValue.js +191 -159
  51. package/internals/hooks/usePicker/usePickerValue.types.d.ts +235 -0
  52. package/internals/hooks/usePicker/usePickerValue.types.js +1 -0
  53. package/internals/hooks/usePicker/usePickerViews.d.ts +1 -1
  54. package/internals/hooks/useStaticPicker/useStaticPicker.d.ts +1 -1
  55. package/internals/hooks/useStaticPicker/useStaticPicker.js +10 -10
  56. package/internals/hooks/useStaticPicker/useStaticPicker.types.d.ts +1 -1
  57. package/internals/index.d.ts +1 -1
  58. package/internals/models/props/basePickerProps.d.ts +6 -0
  59. package/internals/utils/valueManagers.js +1 -1
  60. package/legacy/AdapterDayjs/AdapterDayjs.js +403 -0
  61. package/legacy/AdapterDayjs/index.js +1 -119
  62. package/legacy/DateField/DateField.js +6 -0
  63. package/legacy/DateField/useDateField.js +3 -1
  64. package/legacy/DatePicker/DatePicker.js +6 -0
  65. package/legacy/DateTimeField/DateTimeField.js +6 -0
  66. package/legacy/DateTimeField/useDateTimeField.js +3 -1
  67. package/legacy/DateTimePicker/DateTimePicker.js +6 -0
  68. package/legacy/DateTimePicker/shared.js +3 -1
  69. package/legacy/DesktopDatePicker/DesktopDatePicker.js +7 -0
  70. package/legacy/DesktopDateTimePicker/DesktopDateTimePicker.js +7 -0
  71. package/legacy/DesktopTimePicker/DesktopTimePicker.js +7 -0
  72. package/legacy/LocalizationProvider/LocalizationProvider.js +1 -1
  73. package/legacy/MobileDatePicker/MobileDatePicker.js +7 -0
  74. package/legacy/MobileDateTimePicker/MobileDateTimePicker.js +7 -0
  75. package/legacy/MobileTimePicker/MobileTimePicker.js +7 -0
  76. package/legacy/StaticDatePicker/StaticDatePicker.js +1 -0
  77. package/legacy/StaticDateTimePicker/StaticDateTimePicker.js +3 -2
  78. package/legacy/StaticTimePicker/StaticTimePicker.js +3 -2
  79. package/legacy/TimeField/TimeField.js +6 -0
  80. package/legacy/TimeField/useTimeField.js +3 -1
  81. package/legacy/TimePicker/TimePicker.js +6 -0
  82. package/legacy/index.js +1 -1
  83. package/legacy/internals/hooks/useDesktopPicker/useDesktopPicker.js +10 -10
  84. package/legacy/internals/hooks/useField/useField.utils.js +24 -9
  85. package/legacy/internals/hooks/useField/useFieldState.js +6 -4
  86. package/legacy/internals/hooks/useMobilePicker/useMobilePicker.js +7 -6
  87. package/legacy/internals/hooks/usePicker/usePicker.js +2 -0
  88. package/legacy/internals/hooks/usePicker/usePickerValue.js +186 -159
  89. package/legacy/internals/hooks/usePicker/usePickerValue.types.js +1 -0
  90. package/legacy/internals/hooks/useStaticPicker/useStaticPicker.js +7 -8
  91. package/legacy/internals/utils/valueManagers.js +2 -2
  92. package/legacy/tests/describeGregorianAdapter/describeGregorianAdapter.js +20 -0
  93. package/legacy/tests/describeGregorianAdapter/describeGregorianAdapter.types.js +1 -0
  94. package/legacy/tests/describeGregorianAdapter/index.js +1 -0
  95. package/legacy/tests/describeGregorianAdapter/testCalculations.js +273 -0
  96. package/legacy/tests/describeGregorianAdapter/testFormat.js +26 -0
  97. package/legacy/tests/describeGregorianAdapter/testLocalization.js +15 -0
  98. package/legacy/tests/describeValidation/describeValidation.js +2 -1
  99. package/legacy/tests/describeValidation/testMinutesViewValidation.js +201 -0
  100. package/legacy/tests/describeValue/testPickerActionBar.js +52 -3
  101. package/legacy/tests/describeValue/testPickerOpenCloseLifeCycle.js +6 -6
  102. package/models/adapters.d.ts +21 -20
  103. package/models/fields.d.ts +1 -0
  104. package/modern/AdapterDayjs/AdapterDayjs.js +386 -0
  105. package/modern/AdapterDayjs/index.js +1 -101
  106. package/modern/DateField/DateField.js +6 -0
  107. package/modern/DateField/useDateField.js +3 -1
  108. package/modern/DatePicker/DatePicker.js +6 -0
  109. package/modern/DateTimeField/DateTimeField.js +6 -0
  110. package/modern/DateTimeField/useDateTimeField.js +3 -1
  111. package/modern/DateTimePicker/DateTimePicker.js +6 -0
  112. package/modern/DateTimePicker/shared.js +3 -1
  113. package/modern/DesktopDatePicker/DesktopDatePicker.js +7 -0
  114. package/modern/DesktopDateTimePicker/DesktopDateTimePicker.js +7 -0
  115. package/modern/DesktopTimePicker/DesktopTimePicker.js +7 -0
  116. package/modern/LocalizationProvider/LocalizationProvider.js +1 -1
  117. package/modern/MobileDatePicker/MobileDatePicker.js +7 -0
  118. package/modern/MobileDateTimePicker/MobileDateTimePicker.js +7 -0
  119. package/modern/MobileTimePicker/MobileTimePicker.js +7 -0
  120. package/modern/StaticDatePicker/StaticDatePicker.js +1 -0
  121. package/modern/StaticDateTimePicker/StaticDateTimePicker.js +3 -2
  122. package/modern/StaticTimePicker/StaticTimePicker.js +3 -2
  123. package/modern/TimeField/TimeField.js +6 -0
  124. package/modern/TimeField/useTimeField.js +3 -1
  125. package/modern/TimePicker/TimePicker.js +6 -0
  126. package/modern/index.js +1 -1
  127. package/modern/internals/hooks/useDesktopPicker/useDesktopPicker.js +15 -14
  128. package/modern/internals/hooks/useField/useField.utils.js +16 -4
  129. package/modern/internals/hooks/useField/useFieldState.js +4 -3
  130. package/modern/internals/hooks/useMobilePicker/useMobilePicker.js +12 -10
  131. package/modern/internals/hooks/usePicker/usePicker.js +2 -0
  132. package/modern/internals/hooks/usePicker/usePickerValue.js +191 -159
  133. package/modern/internals/hooks/usePicker/usePickerValue.types.js +1 -0
  134. package/modern/internals/hooks/useStaticPicker/useStaticPicker.js +10 -10
  135. package/modern/internals/utils/valueManagers.js +1 -1
  136. package/modern/tests/describeGregorianAdapter/describeGregorianAdapter.js +20 -0
  137. package/modern/tests/describeGregorianAdapter/describeGregorianAdapter.types.js +1 -0
  138. package/modern/tests/describeGregorianAdapter/index.js +1 -0
  139. package/modern/tests/describeGregorianAdapter/testCalculations.js +272 -0
  140. package/modern/tests/describeGregorianAdapter/testFormat.js +27 -0
  141. package/modern/tests/describeGregorianAdapter/testLocalization.js +16 -0
  142. package/modern/tests/describeValidation/describeValidation.js +2 -1
  143. package/modern/tests/describeValidation/testMinutesViewValidation.js +200 -0
  144. package/modern/tests/describeValue/testPickerActionBar.js +52 -3
  145. package/modern/tests/describeValue/testPickerOpenCloseLifeCycle.js +6 -6
  146. package/node/AdapterDayjs/AdapterDayjs.js +395 -0
  147. package/node/AdapterDayjs/index.js +6 -104
  148. package/node/DateField/DateField.js +6 -0
  149. package/node/DateField/useDateField.js +3 -1
  150. package/node/DatePicker/DatePicker.js +6 -0
  151. package/node/DateTimeField/DateTimeField.js +6 -0
  152. package/node/DateTimeField/useDateTimeField.js +3 -1
  153. package/node/DateTimePicker/DateTimePicker.js +6 -0
  154. package/node/DateTimePicker/shared.js +3 -1
  155. package/node/DesktopDatePicker/DesktopDatePicker.js +7 -0
  156. package/node/DesktopDateTimePicker/DesktopDateTimePicker.js +7 -0
  157. package/node/DesktopTimePicker/DesktopTimePicker.js +7 -0
  158. package/node/LocalizationProvider/LocalizationProvider.js +1 -1
  159. package/node/MobileDatePicker/MobileDatePicker.js +7 -0
  160. package/node/MobileDateTimePicker/MobileDateTimePicker.js +7 -0
  161. package/node/MobileTimePicker/MobileTimePicker.js +7 -0
  162. package/node/StaticDatePicker/StaticDatePicker.js +1 -0
  163. package/node/StaticDateTimePicker/StaticDateTimePicker.js +3 -2
  164. package/node/StaticTimePicker/StaticTimePicker.js +3 -2
  165. package/node/TimeField/TimeField.js +6 -0
  166. package/node/TimeField/useTimeField.js +3 -1
  167. package/node/TimePicker/TimePicker.js +6 -0
  168. package/node/index.js +1 -1
  169. package/node/internals/hooks/useDesktopPicker/useDesktopPicker.js +15 -14
  170. package/node/internals/hooks/useField/useField.utils.js +16 -4
  171. package/node/internals/hooks/useField/useFieldState.js +4 -3
  172. package/node/internals/hooks/useMobilePicker/useMobilePicker.js +12 -10
  173. package/node/internals/hooks/usePicker/usePicker.js +2 -0
  174. package/node/internals/hooks/usePicker/usePickerValue.js +191 -158
  175. package/node/internals/hooks/usePicker/usePickerValue.types.js +5 -0
  176. package/node/internals/hooks/useStaticPicker/useStaticPicker.js +10 -10
  177. package/node/internals/utils/valueManagers.js +1 -1
  178. package/node/tests/describeGregorianAdapter/describeGregorianAdapter.js +29 -0
  179. package/node/tests/describeGregorianAdapter/describeGregorianAdapter.types.js +5 -0
  180. package/node/tests/describeGregorianAdapter/index.js +18 -0
  181. package/node/tests/describeGregorianAdapter/testCalculations.js +279 -0
  182. package/node/tests/describeGregorianAdapter/testFormat.js +34 -0
  183. package/node/tests/describeGregorianAdapter/testLocalization.js +23 -0
  184. package/node/tests/describeValidation/describeValidation.js +2 -1
  185. package/node/tests/describeValidation/testMinutesViewValidation.js +210 -0
  186. package/node/tests/describeValue/testPickerActionBar.js +52 -3
  187. package/node/tests/describeValue/testPickerOpenCloseLifeCycle.js +6 -6
  188. package/package.json +9 -9
  189. package/tests/describeGregorianAdapter/describeGregorianAdapter.js +20 -0
  190. package/tests/describeGregorianAdapter/describeGregorianAdapter.types.js +1 -0
  191. package/tests/describeGregorianAdapter/index.js +1 -0
  192. package/tests/describeGregorianAdapter/testCalculations.js +272 -0
  193. package/tests/describeGregorianAdapter/testFormat.js +27 -0
  194. package/tests/describeGregorianAdapter/testLocalization.js +16 -0
  195. package/tests/describeValidation/describeValidation.js +2 -1
  196. package/tests/describeValidation/testMinutesViewValidation.js +200 -0
  197. package/tests/describeValue/testPickerActionBar.js +52 -3
  198. package/tests/describeValue/testPickerOpenCloseLifeCycle.js +6 -6
@@ -5,26 +5,91 @@ import useEventCallback from '@mui/utils/useEventCallback';
5
5
  import { useOpenState } from '../useOpenState';
6
6
  import { useLocalizationContext, useUtils } from '../useUtils';
7
7
  import { useValidation } from '../validation/useValidation';
8
-
9
- /**
10
- * Props used to handle the value that are common to all pickers.
11
- */
12
-
13
8
  /**
14
- * Props used to handle the value of non-static pickers.
9
+ * Decide if the new value should be published
10
+ * The published value will be passed to `onChange` if defined.
15
11
  */
12
+ const shouldPublishValue = params => {
13
+ const {
14
+ action,
15
+ hasChanged,
16
+ dateState,
17
+ isControlled
18
+ } = params;
19
+ const isCurrentValueTheDefaultValue = !isControlled && !dateState.hasBeenModifiedSinceMount;
16
20
 
17
- /**
18
- * Props used to handle the value of the pickers.
19
- */
21
+ // The field is responsible for only calling `onChange` when needed.
22
+ if (action.name === 'setValueFromField') {
23
+ return true;
24
+ }
25
+ if (action.name === 'setValueFromAction') {
26
+ // If the component is not controlled, and the value has not been modified since the mount,
27
+ // Then we want to publish the default value whenever the user pressed the "Accept", "Today" or "Clear" button.
28
+ if (isCurrentValueTheDefaultValue && ['accept', 'today', 'clear'].includes(action.pickerAction)) {
29
+ return true;
30
+ }
31
+ return hasChanged(dateState.lastPublishedValue);
32
+ }
33
+ if (action.name === 'setValueFromView' && action.selectionState !== 'shallow') {
34
+ // On the first view,
35
+ // If the value is not controlled, then clicking on any value (including the one equal to `defaultValue`) should call `onChange`
36
+ if (isCurrentValueTheDefaultValue) {
37
+ return true;
38
+ }
39
+ return hasChanged(dateState.lastPublishedValue);
40
+ }
41
+ return false;
42
+ };
20
43
 
21
44
  /**
22
- * Props passed to `usePickerViews`.
45
+ * Decide if the new value should be committed.
46
+ * The committed value will be passed to `onAccept` if defined.
47
+ * It will also be used as a reset target when calling the `cancel` picker action (when clicking on the "Cancel" button).
23
48
  */
49
+ const shouldCommitValue = params => {
50
+ const {
51
+ action,
52
+ hasChanged,
53
+ dateState,
54
+ isControlled,
55
+ closeOnSelect
56
+ } = params;
57
+ const isCurrentValueTheDefaultValue = !isControlled && !dateState.hasBeenModifiedSinceMount;
58
+ if (action.name === 'setValueFromAction') {
59
+ // If the component is not controlled, and the value has not been modified since the mount,
60
+ // Then we want to commit the default value whenever the user pressed the "Accept", "Today" or "Clear" button.
61
+ if (isCurrentValueTheDefaultValue && ['accept', 'today', 'clear'].includes(action.pickerAction)) {
62
+ return true;
63
+ }
64
+ return hasChanged(dateState.lastCommittedValue);
65
+ }
66
+ if (action.name === 'setValueFromView' && action.selectionState === 'finish' && closeOnSelect) {
67
+ // On picker where the 1st view is also the last view,
68
+ // If the value is not controlled, then clicking on any value (including the one equal to `defaultValue`) should call `onAccept`
69
+ if (isCurrentValueTheDefaultValue) {
70
+ return true;
71
+ }
72
+ return hasChanged(dateState.lastCommittedValue);
73
+ }
74
+ return false;
75
+ };
24
76
 
25
77
  /**
26
- * Props passed to `usePickerLayoutProps`.
78
+ * Decide if the picker should be closed after the value is updated.
27
79
  */
80
+ const shouldClosePicker = params => {
81
+ const {
82
+ action,
83
+ closeOnSelect
84
+ } = params;
85
+ if (action.name === 'setValueFromAction') {
86
+ return true;
87
+ }
88
+ if (action.name === 'setValueFromView') {
89
+ return action.selectionState === 'finish' && closeOnSelect;
90
+ }
91
+ return false;
92
+ };
28
93
 
29
94
  /**
30
95
  * Manage the value lifecycle of all the pickers.
@@ -32,26 +97,43 @@ import { useValidation } from '../validation/useValidation';
32
97
  export const usePickerValue = ({
33
98
  props,
34
99
  valueManager,
100
+ valueType,
35
101
  wrapperVariant,
36
102
  validator
37
103
  }) => {
38
104
  const {
39
- onAccept: onAcceptProp,
105
+ onAccept,
40
106
  onChange,
41
107
  value: inValue,
42
- defaultValue,
108
+ defaultValue: inDefaultValue,
43
109
  closeOnSelect = wrapperVariant === 'desktop',
44
110
  selectedSections: selectedSectionsProp,
45
111
  onSelectedSectionsChange
46
112
  } = props;
113
+ const {
114
+ current: defaultValue
115
+ } = React.useRef(inDefaultValue);
116
+ const {
117
+ current: isControlled
118
+ } = React.useRef(inValue !== undefined);
119
+
120
+ /* eslint-disable react-hooks/rules-of-hooks, react-hooks/exhaustive-deps */
121
+ if (process.env.NODE_ENV !== 'production') {
122
+ React.useEffect(() => {
123
+ if (isControlled !== (inValue !== undefined)) {
124
+ console.error([`MUI: A component is changing the ${isControlled ? '' : 'un'}controlled value of a picker to be ${isControlled ? 'un' : ''}controlled.`, 'Elements should not switch from uncontrolled to controlled (or vice versa).', `Decide between using a controlled or uncontrolled value` + 'for the lifetime of the component.', "The nature of the state is determined during the first render. It's considered controlled if the value is not `undefined`.", 'More info: https://fb.me/react-controlled-components'].join('\n'));
125
+ }
126
+ }, [inValue]);
127
+ React.useEffect(() => {
128
+ if (!isControlled && defaultValue !== inDefaultValue) {
129
+ console.error([`MUI: A component is changing the defaultValue of an uncontrolled picker after being initialized. ` + `To suppress this warning opt to use a controlled value.`].join('\n'));
130
+ }
131
+ }, [JSON.stringify(defaultValue)]);
132
+ }
133
+ /* eslint-enable react-hooks/rules-of-hooks, react-hooks/exhaustive-deps */
134
+
47
135
  const utils = useUtils();
48
136
  const adapter = useLocalizationContext();
49
- const [value, setValue] = useControlled({
50
- controlled: inValue,
51
- default: defaultValue != null ? defaultValue : valueManager.emptyValue,
52
- name: 'usePickerValue',
53
- state: 'value'
54
- });
55
137
  const [selectedSections, setSelectedSections] = useControlled({
56
138
  controlled: selectedSectionsProp,
57
139
  default: null,
@@ -62,170 +144,120 @@ export const usePickerValue = ({
62
144
  isOpen,
63
145
  setIsOpen
64
146
  } = useOpenState(props);
65
- const [dateState, setDateState] = React.useState(() => ({
66
- committed: value,
67
- draft: value,
68
- resetFallback: value
69
- }));
147
+ const [dateState, setDateState] = React.useState(() => {
148
+ let initialValue;
149
+ if (inValue !== undefined) {
150
+ initialValue = inValue;
151
+ } else if (defaultValue !== undefined) {
152
+ initialValue = defaultValue;
153
+ } else {
154
+ initialValue = valueManager.emptyValue;
155
+ }
156
+ return {
157
+ draft: initialValue,
158
+ lastPublishedValue: initialValue,
159
+ lastCommittedValue: initialValue,
160
+ lastControlledValue: inValue,
161
+ hasBeenModifiedSinceMount: false
162
+ };
163
+ });
70
164
  useValidation(_extends({}, props, {
71
- value
165
+ value: dateState.draft
72
166
  }), validator, valueManager.isSameError, valueManager.defaultErrorState);
73
- const setDate = useEventCallback(params => {
74
- setDateState(prev => {
75
- switch (params.action) {
76
- case 'setAll':
77
- case 'acceptAndClose':
78
- {
79
- return {
80
- draft: params.value,
81
- committed: params.value,
82
- resetFallback: params.value
83
- };
84
- }
85
- case 'setCommitted':
86
- {
87
- return _extends({}, prev, {
88
- draft: params.value,
89
- committed: params.value
90
- });
91
- }
92
- case 'setDraft':
93
- {
94
- return _extends({}, prev, {
95
- draft: params.value
96
- });
97
- }
98
- default:
99
- {
100
- return prev;
101
- }
102
- }
103
- });
104
- if (!params.skipOnChangeCall && !valueManager.areValuesEqual(utils, dateState.committed, params.value)) {
105
- setValue(params.value);
106
- if (onChange) {
107
- const context = {
108
- validationError: params.contextFromField == null ? validator({
109
- adapter,
110
- value: params.value,
111
- props: _extends({}, props, {
112
- value: params.value
113
- })
114
- }) : params.contextFromField.validationError
115
- };
116
- onChange(params.value, context);
117
- }
167
+ const updateDate = useEventCallback(action => {
168
+ const updaterParams = {
169
+ action,
170
+ dateState,
171
+ hasChanged: comparison => !valueManager.areValuesEqual(utils, action.value, comparison),
172
+ isControlled,
173
+ closeOnSelect
174
+ };
175
+ const shouldPublish = shouldPublishValue(updaterParams);
176
+ const shouldCommit = shouldCommitValue(updaterParams);
177
+ const shouldClose = shouldClosePicker(updaterParams);
178
+ setDateState(prev => _extends({}, prev, {
179
+ draft: action.value,
180
+ lastPublishedValue: shouldPublish ? action.value : prev.lastPublishedValue,
181
+ lastCommittedValue: shouldCommit ? action.value : prev.lastCommittedValue,
182
+ hasBeenModifiedSinceMount: true
183
+ }));
184
+ if (shouldPublish && onChange) {
185
+ const validationError = action.name === 'setValueFromField' ? action.context.validationError : validator({
186
+ adapter,
187
+ value: action.value,
188
+ props: _extends({}, props, {
189
+ value: action.value
190
+ })
191
+ });
192
+ const context = {
193
+ validationError
194
+ };
195
+ onChange(action.value, context);
118
196
  }
119
- if (params.action === 'acceptAndClose') {
197
+ if (shouldCommit && onAccept) {
198
+ onAccept(action.value);
199
+ }
200
+ if (shouldClose) {
120
201
  setIsOpen(false);
121
- if (onAcceptProp && !valueManager.areValuesEqual(utils, dateState.resetFallback, params.value)) {
122
- onAcceptProp(params.value);
123
- }
124
202
  }
125
203
  });
126
- React.useEffect(() => {
127
- if (isOpen) {
128
- // Update all dates in state to equal the current prop value
129
- setDate({
130
- action: 'setAll',
131
- value,
132
- skipOnChangeCall: true
133
- });
134
- }
135
- }, [isOpen]); // eslint-disable-line react-hooks/exhaustive-deps
136
-
137
- // Set the draft and committed date to equal the new prop value.
138
- if (!valueManager.areValuesEqual(utils, dateState.committed, value)) {
139
- setDate({
140
- action: 'setCommitted',
141
- value,
142
- skipOnChangeCall: true
143
- });
204
+ if (inValue !== undefined && (dateState.lastControlledValue === undefined || !valueManager.areValuesEqual(utils, dateState.lastControlledValue, inValue))) {
205
+ const isUpdateComingFromPicker = valueManager.areValuesEqual(utils, dateState.draft, inValue);
206
+ setDateState(prev => _extends({}, prev, {
207
+ lastControlledValue: inValue
208
+ }, isUpdateComingFromPicker ? {} : {
209
+ lastCommittedValue: inValue,
210
+ lastPublishedValue: inValue,
211
+ draft: inValue,
212
+ hasBeenModifiedSinceMount: true
213
+ }));
144
214
  }
145
215
  const handleClear = useEventCallback(() => {
146
- // Reset all date in state to the empty value and close picker.
147
- setDate({
216
+ updateDate({
148
217
  value: valueManager.emptyValue,
149
- action: 'acceptAndClose'
218
+ name: 'setValueFromAction',
219
+ pickerAction: 'clear'
150
220
  });
151
221
  });
152
222
  const handleAccept = useEventCallback(() => {
153
- // Set all date in state to equal the current draft value and close picker.
154
- setDate({
155
- value: dateState.draft,
156
- action: 'acceptAndClose'
223
+ updateDate({
224
+ value: dateState.lastPublishedValue,
225
+ name: 'setValueFromAction',
226
+ pickerAction: 'accept'
157
227
  });
158
228
  });
159
229
  const handleDismiss = useEventCallback(() => {
160
- // Set all dates in state to equal the last committed date.
161
- // e.g. Reset the state to the last committed value.
162
- setDate({
163
- value: dateState.committed,
164
- action: 'acceptAndClose'
230
+ updateDate({
231
+ value: dateState.lastPublishedValue,
232
+ name: 'setValueFromAction',
233
+ pickerAction: 'dismiss'
165
234
  });
166
235
  });
167
236
  const handleCancel = useEventCallback(() => {
168
- // Set all dates in state to equal the last accepted date and close picker.
169
- // e.g. Reset the state to the last accepted value
170
- setDate({
171
- value: dateState.resetFallback,
172
- action: 'acceptAndClose'
237
+ updateDate({
238
+ value: dateState.lastCommittedValue,
239
+ name: 'setValueFromAction',
240
+ pickerAction: 'cancel'
173
241
  });
174
242
  });
175
243
  const handleSetToday = useEventCallback(() => {
176
- // Set all dates in state to equal today and close picker.
177
- setDate({
178
- value: valueManager.getTodayValue(utils),
179
- action: 'acceptAndClose'
244
+ updateDate({
245
+ value: valueManager.getTodayValue(utils, valueType),
246
+ name: 'setValueFromAction',
247
+ pickerAction: 'today'
180
248
  });
181
249
  });
182
250
  const handleOpen = useEventCallback(() => setIsOpen(true));
183
251
  const handleClose = useEventCallback(() => setIsOpen(false));
184
- const handleChange = useEventCallback((newDate, selectionState = 'partial') => {
185
- switch (selectionState) {
186
- case 'shallow':
187
- {
188
- // Update the `draft` state but do not fire `onChange`
189
- return setDate({
190
- action: 'setDraft',
191
- value: newDate,
192
- skipOnChangeCall: true
193
- });
194
- }
195
- case 'partial':
196
- {
197
- // Update the `draft` state and fire `onChange`
198
- return setDate({
199
- action: 'setDraft',
200
- value: newDate
201
- });
202
- }
203
- case 'finish':
204
- {
205
- if (closeOnSelect) {
206
- // Set all dates in state to equal the new date and close picker.
207
- return setDate({
208
- value: newDate,
209
- action: 'acceptAndClose'
210
- });
211
- }
212
-
213
- // Updates the `committed` state and fire `onChange`
214
- return setDate({
215
- value: newDate,
216
- action: 'setCommitted'
217
- });
218
- }
219
- default:
220
- {
221
- throw new Error('MUI: Invalid selectionState passed to `onDateChange`');
222
- }
223
- }
224
- });
225
- const handleChangeAndCommit = useEventCallback((newValue, contextFromField) => setDate({
226
- action: 'setCommitted',
252
+ const handleChange = useEventCallback((newValue, selectionState = 'partial') => updateDate({
253
+ name: 'setValueFromView',
227
254
  value: newValue,
228
- contextFromField
255
+ selectionState
256
+ }));
257
+ const handleChangeField = useEventCallback((newValue, context) => updateDate({
258
+ name: 'setValueFromField',
259
+ value: newValue,
260
+ context
229
261
  }));
230
262
  const handleFieldSelectedSectionsChange = useEventCallback(newSelectedSections => {
231
263
  setSelectedSections(newSelectedSections);
@@ -242,7 +274,7 @@ export const usePickerValue = ({
242
274
  };
243
275
  const fieldResponse = {
244
276
  value: dateState.draft,
245
- onChange: handleChangeAndCommit,
277
+ onChange: handleChangeField,
246
278
  selectedSections,
247
279
  onSelectedSectionsChange: handleFieldSelectedSectionsChange
248
280
  };
@@ -266,7 +298,7 @@ export const usePickerValue = ({
266
298
  };
267
299
  const layoutResponse = _extends({}, actions, {
268
300
  value: viewValue,
269
- onChange: handleChangeAndCommit,
301
+ onChange: handleChange,
270
302
  isValid
271
303
  });
272
304
  return {
@@ -0,0 +1,235 @@
1
+ import { FieldChangeHandlerContext, UseFieldInternalProps } from '../useField';
2
+ import { InferError, Validator } from '../validation/useValidation';
3
+ import { UseFieldValidationProps } from '../useField/useField.types';
4
+ import { WrapperVariant } from '../../models/common';
5
+ import { FieldSection, FieldSelectedSections, FieldValueType, MuiPickersAdapter } from '../../../models';
6
+ export interface PickerValueManager<TValue, TDate, TError> {
7
+ /**
8
+ * Determines if two values are equal.
9
+ * @template TDate, TValue
10
+ * @param {MuiPickersAdapter<TDate>} utils The adapter.
11
+ * @param {TValue} valueLeft The first value to compare.
12
+ * @param {TValue} valueRight The second value to compare.
13
+ * @returns {boolean} A boolean indicating if the two values are equal.
14
+ */
15
+ areValuesEqual: (utils: MuiPickersAdapter<TDate>, valueLeft: TValue, valueRight: TValue) => boolean;
16
+ /**
17
+ * Value to set when clicking the "Clear" button.
18
+ */
19
+ emptyValue: TValue;
20
+ /**
21
+ * Method returning the value to set when clicking the "Today" button
22
+ * @template TDate, TValue
23
+ * @param {MuiPickersAdapter<TDate>} utils The adapter.
24
+ * @param {FieldValueType} valueType The type of the value being edited.
25
+ * @returns {TValue} The value to set when clicking the "Today" button.
26
+ */
27
+ getTodayValue: (utils: MuiPickersAdapter<TDate>, valueType: FieldValueType) => TValue;
28
+ /**
29
+ * Method parsing the input value to replace all invalid dates by `null`.
30
+ * @template TDate, TValue
31
+ * @param {MuiPickersAdapter<TDate>} utils The adapter.
32
+ * @param {TValue} value The value to parse.
33
+ * @returns {TValue} The value without invalid date.
34
+ */
35
+ cleanValue: (utils: MuiPickersAdapter<TDate>, value: TValue) => TValue;
36
+ /**
37
+ * Generates the new value, given the previous value and the new proposed value.
38
+ * @template TDate, TValue
39
+ * @param {MuiPickersAdapter<TDate>} utils The adapter.
40
+ * @param {TValue} lastValidDateValue The last valid value.
41
+ * @param {TValue} value The proposed value.
42
+ * @returns {TValue} The new value.
43
+ */
44
+ valueReducer?: (utils: MuiPickersAdapter<TDate>, lastValidDateValue: TValue, value: TValue) => TValue;
45
+ /**
46
+ * Compare two errors to know if they are equal.
47
+ * @template TError
48
+ * @param {TError} error The new error
49
+ * @param {TError | null} prevError The previous error
50
+ * @returns {boolean} `true` if the new error is different from the previous one.
51
+ */
52
+ isSameError: (error: TError, prevError: TError | null) => boolean;
53
+ /**
54
+ * Checks if the current error is empty or not.
55
+ * @template TError
56
+ * @param {TError} error The current error.
57
+ * @returns {boolean} `true` if the current error is not empty.
58
+ */
59
+ hasError: (error: TError) => boolean;
60
+ /**
61
+ * The value identifying no error, used to initialise the error state.
62
+ */
63
+ defaultErrorState: TError;
64
+ }
65
+ export interface PickerChangeHandlerContext<TError> {
66
+ validationError: TError;
67
+ }
68
+ export type PickerSelectionState = 'partial' | 'shallow' | 'finish';
69
+ export interface UsePickerValueState<TValue> {
70
+ /**
71
+ * Date displayed on the views and the field.
72
+ * It is updated whenever the user modifies something.
73
+ */
74
+ draft: TValue;
75
+ /**
76
+ * Last value published (e.g: the last value for which `shouldPublishValue` returned `true`).
77
+ * If `onChange` is defined, it's the value that was passed on the last call to this callback.
78
+ */
79
+ lastPublishedValue: TValue;
80
+ /**
81
+ * Last value committed (e.g: the last value for which `shouldCommitValue` returned `true`).
82
+ * If `onAccept` is defined, it's the value that was passed on the last call to this callback.
83
+ */
84
+ lastCommittedValue: TValue;
85
+ /**
86
+ * Last value passed with `props.value`.
87
+ * Used to update the `draft` value whenever the `value` prop changes.
88
+ */
89
+ lastControlledValue: TValue | undefined;
90
+ /**
91
+ * If we never modified the value since the mount of the component,
92
+ * Then we might want to apply some custom logic.
93
+ *
94
+ * For example, when the component is not controlled and `defaultValue` is defined.
95
+ * Then clicking on "Accept", "Today" or "Clear" should fire `onAccept` with `defaultValue`, but clicking on "Cancel" or dimissing the picker should not.
96
+ */
97
+ hasBeenModifiedSinceMount: boolean;
98
+ }
99
+ export interface PickerValueUpdaterParams<TValue, TError> {
100
+ action: PickerValueUpdateAction<TValue, TError>;
101
+ dateState: UsePickerValueState<TValue>;
102
+ /**
103
+ * Check if the new draft value has changed compared to some given value.
104
+ * @template TValue
105
+ * @param {TValue} comparisonValue The value to compare the new draft value with.
106
+ * @returns {boolean} `true` if the new draft value is equal to the comparison value.
107
+ */
108
+ hasChanged: (comparisonValue: TValue) => boolean;
109
+ isControlled: boolean;
110
+ closeOnSelect: boolean;
111
+ }
112
+ export type PickerValueUpdateAction<TValue, TError> = {
113
+ name: 'setValueFromView';
114
+ value: TValue;
115
+ selectionState: PickerSelectionState;
116
+ } | {
117
+ name: 'setValueFromField';
118
+ value: TValue;
119
+ context: FieldChangeHandlerContext<TError>;
120
+ } | {
121
+ name: 'setValueFromAction';
122
+ value: TValue;
123
+ pickerAction: 'accept' | 'today' | 'cancel' | 'dismiss' | 'clear';
124
+ };
125
+ /**
126
+ * Props used to handle the value that are common to all pickers.
127
+ */
128
+ export interface UsePickerValueBaseProps<TValue, TError> {
129
+ /**
130
+ * The selected value.
131
+ * Used when the component is controlled.
132
+ */
133
+ value?: TValue;
134
+ /**
135
+ * The default value.
136
+ * Used when the component is not controlled.
137
+ */
138
+ defaultValue?: TValue;
139
+ /**
140
+ * Callback fired when the value changes.
141
+ * @template TValue The value type. Will be either the same type as `value` or `null`. Can be in `[start, end]` format in case of range value.
142
+ * @template TError The validation error type. Will be either `string` or a `null`. Can be in `[start, end]` format in case of range value.
143
+ * @param {TValue} value The new value.
144
+ * @param {FieldChangeHandlerContext<TError>} context The context containing the validation result of the current value.
145
+ */
146
+ onChange?: (value: TValue, context: PickerChangeHandlerContext<TError>) => void;
147
+ /**
148
+ * Callback fired when the value is accepted.
149
+ * @template TValue The value type. Will be either the same type as `value` or `null`. Can be in `[start, end]` format in case of range value.
150
+ * @param {TValue} value The value that was just accepted.
151
+ */
152
+ onAccept?: (value: TValue) => void;
153
+ /**
154
+ * Callback fired when the error associated to the current value changes.
155
+ * If the error has a non-null value, then the `TextField` will be rendered in `error` state.
156
+ *
157
+ * @template TValue The value type. Will be either the same type as `value` or `null`. Can be in `[start, end]` format in case of range value.
158
+ * @template TError The validation error type. Will be either `string` or a `null`. Can be in `[start, end]` format in case of range value.
159
+ * @param {TError} error The new error describing why the current value is not valid.
160
+ * @param {TValue} value The value associated to the error.
161
+ */
162
+ onError?: (error: TError, value: TValue) => void;
163
+ }
164
+ /**
165
+ * Props used to handle the value of non-static pickers.
166
+ */
167
+ export interface UsePickerValueNonStaticProps<TValue, TSection extends FieldSection> extends Pick<UseFieldInternalProps<TValue, TSection, unknown>, 'selectedSections' | 'onSelectedSectionsChange'> {
168
+ /**
169
+ * If `true`, the popover or modal will close after submitting the full date.
170
+ * @default `true` for desktop, `false` for mobile (based on the chosen wrapper and `desktopModeMediaQuery` prop).
171
+ */
172
+ closeOnSelect?: boolean;
173
+ /**
174
+ * Control the popup or dialog open state.
175
+ * @default false
176
+ */
177
+ open?: boolean;
178
+ /**
179
+ * Callback fired when the popup requests to be closed.
180
+ * Use in controlled mode (see `open`).
181
+ */
182
+ onClose?: () => void;
183
+ /**
184
+ * Callback fired when the popup requests to be opened.
185
+ * Use in controlled mode (see `open`).
186
+ */
187
+ onOpen?: () => void;
188
+ }
189
+ /**
190
+ * Props used to handle the value of the pickers.
191
+ */
192
+ export interface UsePickerValueProps<TValue, TSection extends FieldSection, TError> extends UsePickerValueBaseProps<TValue, TError>, UsePickerValueNonStaticProps<TValue, TSection> {
193
+ }
194
+ export interface UsePickerValueParams<TValue, TDate, TSection extends FieldSection, TExternalProps extends UsePickerValueProps<TValue, TSection, any>> {
195
+ props: TExternalProps;
196
+ valueManager: PickerValueManager<TValue, TDate, InferError<TExternalProps>>;
197
+ valueType: FieldValueType;
198
+ wrapperVariant: WrapperVariant;
199
+ validator: Validator<TValue, TDate, InferError<TExternalProps>, UseFieldValidationProps<TValue, TExternalProps>>;
200
+ }
201
+ export interface UsePickerValueActions {
202
+ onAccept: () => void;
203
+ onClear: () => void;
204
+ onDismiss: () => void;
205
+ onCancel: () => void;
206
+ onSetToday: () => void;
207
+ onOpen: () => void;
208
+ onClose: () => void;
209
+ }
210
+ export type UsePickerValueFieldResponse<TValue, TSection extends FieldSection, TError> = Required<Pick<UseFieldInternalProps<TValue, TSection, TError>, 'value' | 'onChange' | 'selectedSections' | 'onSelectedSectionsChange'>>;
211
+ /**
212
+ * Props passed to `usePickerViews`.
213
+ */
214
+ export interface UsePickerValueViewsResponse<TValue> {
215
+ value: TValue;
216
+ onChange: (value: TValue, selectionState?: PickerSelectionState) => void;
217
+ open: boolean;
218
+ onClose: () => void;
219
+ onSelectedSectionsChange: (newValue: FieldSelectedSections) => void;
220
+ }
221
+ /**
222
+ * Props passed to `usePickerLayoutProps`.
223
+ */
224
+ export interface UsePickerValueLayoutResponse<TValue> extends UsePickerValueActions {
225
+ value: TValue;
226
+ onChange: (newValue: TValue) => void;
227
+ isValid: (value: TValue) => boolean;
228
+ }
229
+ export interface UsePickerValueResponse<TValue, TSection extends FieldSection, TError> {
230
+ open: boolean;
231
+ actions: UsePickerValueActions;
232
+ viewProps: UsePickerValueViewsResponse<TValue>;
233
+ fieldProps: UsePickerValueFieldResponse<TValue, TSection, TError>;
234
+ layoutProps: UsePickerValueLayoutResponse<TValue>;
235
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -3,7 +3,7 @@ import { SxProps } from '@mui/system';
3
3
  import { Theme } from '@mui/material/styles';
4
4
  import { DateOrTimeView } from '../../../models';
5
5
  import { UseViewsOptions } from '../useViews';
6
- import type { UsePickerValueViewsResponse } from './usePickerValue';
6
+ import type { UsePickerValueViewsResponse } from './usePickerValue.types';
7
7
  interface PickerViewsRendererBaseExternalProps<TView extends DateOrTimeView> extends Omit<UsePickerViewsProps<any, TView, any, any>, 'openTo' | 'viewRenderers'> {
8
8
  }
9
9
  export type PickerViewsRendererProps<TValue, TView extends DateOrTimeView, TExternalProps extends PickerViewsRendererBaseExternalProps<TView>, TAdditionalProps extends {}> = TExternalProps & TAdditionalProps & UsePickerValueViewsResponse<TValue> & {