@mui/x-date-pickers 8.29.2 → 8.29.3
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.
- package/CHANGELOG.md +72 -0
- package/PickersTextField/PickersFilledInput/PickersFilledInput.js +1 -0
- package/PickersTextField/PickersFilledInput/PickersFilledInput.mjs +1 -0
- package/PickersTextField/PickersInput/PickersInput.js +1 -0
- package/PickersTextField/PickersInput/PickersInput.mjs +1 -0
- package/PickersTextField/PickersInputBase/PickersInputBase.js +20 -20
- package/PickersTextField/PickersInputBase/PickersInputBase.mjs +20 -20
- package/PickersTextField/PickersInputBase/PickersInputBase.types.d.mts +1 -0
- package/PickersTextField/PickersInputBase/PickersInputBase.types.d.ts +1 -0
- package/PickersTextField/PickersInputBase/pickersInputBaseClasses.js +1 -1
- package/PickersTextField/PickersInputBase/pickersInputBaseClasses.mjs +1 -1
- package/PickersTextField/PickersOutlinedInput/PickersOutlinedInput.js +1 -0
- package/PickersTextField/PickersOutlinedInput/PickersOutlinedInput.mjs +1 -0
- package/PickersTextField/PickersTextField.js +4 -1
- package/PickersTextField/PickersTextField.mjs +4 -1
- package/index.js +1 -1
- package/index.mjs +1 -1
- package/internals/hooks/useField/useField.types.d.mts +1 -0
- package/internals/hooks/useField/useField.types.d.ts +1 -0
- package/internals/hooks/useField/useFieldRootProps.d.mts +1 -0
- package/internals/hooks/useField/useFieldRootProps.d.ts +1 -0
- package/internals/hooks/useField/useFieldRootProps.js +135 -8
- package/internals/hooks/useField/useFieldRootProps.mjs +135 -8
- package/internals/hooks/useField/useFieldSectionContainerProps.js +23 -4
- package/internals/hooks/useField/useFieldSectionContainerProps.mjs +22 -4
- package/internals/hooks/useField/useFieldV7TextField.js +13 -0
- package/internals/hooks/useField/useFieldV7TextField.mjs +13 -0
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,78 @@
|
|
|
5
5
|
All notable changes to this project will be documented in this file.
|
|
6
6
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
7
7
|
|
|
8
|
+
## 8.29.3
|
|
9
|
+
|
|
10
|
+
_Aug 21, 2026_
|
|
11
|
+
|
|
12
|
+
We'd like to extend a big thank you to the 2 contributors who made this release possible. Here are some highlights ✨:
|
|
13
|
+
|
|
14
|
+
- 🐞 Bugfixes
|
|
15
|
+
|
|
16
|
+
The following team members contributed to this release:
|
|
17
|
+
@LukasTy, @MBilalShafi
|
|
18
|
+
|
|
19
|
+
### Data Grid
|
|
20
|
+
|
|
21
|
+
#### `@mui/x-data-grid@8.29.3`
|
|
22
|
+
|
|
23
|
+
- [DataGrid] Fix toolbar button stealing focus when a sibling's disabled state changes (#23265) @MBilalShafi
|
|
24
|
+
|
|
25
|
+
#### `@mui/x-data-grid-pro@8.29.3` [](https://mui.com/r/x-pro-svg-link 'Pro plan')
|
|
26
|
+
|
|
27
|
+
Same changes as in `@mui/x-data-grid@8.29.3`.
|
|
28
|
+
|
|
29
|
+
#### `@mui/x-data-grid-premium@8.29.3` [](https://mui.com/r/x-premium-svg-link 'Premium plan')
|
|
30
|
+
|
|
31
|
+
Same changes as in `@mui/x-data-grid-pro@8.29.3`.
|
|
32
|
+
|
|
33
|
+
### Date and Time Pickers
|
|
34
|
+
|
|
35
|
+
#### `@mui/x-date-pickers@8.29.3`
|
|
36
|
+
|
|
37
|
+
- [fields] Keep the field editable on blank space clicks (#23372) @LukasTy
|
|
38
|
+
- [pickers] Fix format placeholder brightness with a start adornment (#23205) @LukasTy
|
|
39
|
+
|
|
40
|
+
#### `@mui/x-date-pickers-pro@8.29.3` [](https://mui.com/r/x-pro-svg-link 'Pro plan')
|
|
41
|
+
|
|
42
|
+
Same changes as in `@mui/x-date-pickers@8.29.3`, plus:
|
|
43
|
+
|
|
44
|
+
- [DateRangePicker] Fix broken active range position underline in single input range fields (#23201) @LukasTy
|
|
45
|
+
|
|
46
|
+
### Charts
|
|
47
|
+
|
|
48
|
+
#### `@mui/x-charts@8.29.3`
|
|
49
|
+
|
|
50
|
+
Internal changes.
|
|
51
|
+
|
|
52
|
+
#### `@mui/x-charts-pro@8.29.3` [](https://mui.com/r/x-pro-svg-link 'Pro plan')
|
|
53
|
+
|
|
54
|
+
Same changes as in `@mui/x-charts@8.29.3`.
|
|
55
|
+
|
|
56
|
+
#### `@mui/x-charts-premium@8.29.3` [](https://mui.com/r/x-premium-svg-link 'Premium plan')
|
|
57
|
+
|
|
58
|
+
Same changes as in `@mui/x-charts-pro@8.29.3`.
|
|
59
|
+
|
|
60
|
+
### Tree View
|
|
61
|
+
|
|
62
|
+
#### `@mui/x-tree-view@8.29.3`
|
|
63
|
+
|
|
64
|
+
Internal changes.
|
|
65
|
+
|
|
66
|
+
#### `@mui/x-tree-view-pro@8.29.3` [](https://mui.com/r/x-pro-svg-link 'Pro plan')
|
|
67
|
+
|
|
68
|
+
Same changes as in `@mui/x-tree-view@8.29.3`.
|
|
69
|
+
|
|
70
|
+
### Codemod
|
|
71
|
+
|
|
72
|
+
#### `@mui/x-codemod@8.29.3`
|
|
73
|
+
|
|
74
|
+
Internal changes.
|
|
75
|
+
|
|
76
|
+
### Miscellaneous
|
|
77
|
+
|
|
78
|
+
- [core] Fix `test-react-18` install failure on v8.x (#23202) @LukasTy
|
|
79
|
+
|
|
8
80
|
## 8.29.2
|
|
9
81
|
|
|
10
82
|
_Jul 2, 2026_
|
|
@@ -279,6 +279,7 @@ process.env.NODE_ENV !== "production" ? PickersFilledInput.propTypes = {
|
|
|
279
279
|
onClick: _propTypes.default.func.isRequired,
|
|
280
280
|
onInput: _propTypes.default.func.isRequired,
|
|
281
281
|
onKeyDown: _propTypes.default.func.isRequired,
|
|
282
|
+
onMouseDown: _propTypes.default.func.isRequired,
|
|
282
283
|
onPaste: _propTypes.default.func.isRequired,
|
|
283
284
|
ownerState: _propTypes.default /* @typescript-to-proptypes-ignore */.any,
|
|
284
285
|
readOnly: _propTypes.default.bool,
|
|
@@ -272,6 +272,7 @@ process.env.NODE_ENV !== "production" ? PickersFilledInput.propTypes = {
|
|
|
272
272
|
onClick: PropTypes.func.isRequired,
|
|
273
273
|
onInput: PropTypes.func.isRequired,
|
|
274
274
|
onKeyDown: PropTypes.func.isRequired,
|
|
275
|
+
onMouseDown: PropTypes.func.isRequired,
|
|
275
276
|
onPaste: PropTypes.func.isRequired,
|
|
276
277
|
ownerState: PropTypes /* @typescript-to-proptypes-ignore */.any,
|
|
277
278
|
readOnly: PropTypes.bool,
|
|
@@ -197,6 +197,7 @@ process.env.NODE_ENV !== "production" ? PickersInput.propTypes = {
|
|
|
197
197
|
onClick: _propTypes.default.func.isRequired,
|
|
198
198
|
onInput: _propTypes.default.func.isRequired,
|
|
199
199
|
onKeyDown: _propTypes.default.func.isRequired,
|
|
200
|
+
onMouseDown: _propTypes.default.func.isRequired,
|
|
200
201
|
onPaste: _propTypes.default.func.isRequired,
|
|
201
202
|
ownerState: _propTypes.default /* @typescript-to-proptypes-ignore */.any,
|
|
202
203
|
readOnly: _propTypes.default.bool,
|
|
@@ -190,6 +190,7 @@ process.env.NODE_ENV !== "production" ? PickersInput.propTypes = {
|
|
|
190
190
|
onClick: PropTypes.func.isRequired,
|
|
191
191
|
onInput: PropTypes.func.isRequired,
|
|
192
192
|
onKeyDown: PropTypes.func.isRequired,
|
|
193
|
+
onMouseDown: PropTypes.func.isRequired,
|
|
193
194
|
onPaste: PropTypes.func.isRequired,
|
|
194
195
|
ownerState: PropTypes /* @typescript-to-proptypes-ignore */.any,
|
|
195
196
|
readOnly: PropTypes.bool,
|
|
@@ -88,39 +88,28 @@ const PickersInputBaseSectionsContainer = exports.PickersInputBaseSectionsContai
|
|
|
88
88
|
paddingTop: 1
|
|
89
89
|
}
|
|
90
90
|
}, {
|
|
91
|
+
// While the field is empty and blurred, the format is shown as a placeholder.
|
|
91
92
|
props: {
|
|
92
|
-
hasStartAdornment: false,
|
|
93
93
|
isFieldFocused: false,
|
|
94
94
|
isFieldValueEmpty: true
|
|
95
95
|
},
|
|
96
|
-
style: {
|
|
97
|
-
color: 'currentColor',
|
|
98
|
-
opacity: 0
|
|
99
|
-
}
|
|
100
|
-
}, {
|
|
101
|
-
props: {
|
|
102
|
-
hasStartAdornment: false,
|
|
103
|
-
isFieldFocused: false,
|
|
104
|
-
isFieldValueEmpty: true,
|
|
105
|
-
inputHasLabel: false
|
|
106
|
-
},
|
|
107
96
|
style: theme.vars ? {
|
|
108
97
|
opacity: theme.vars.opacity.inputPlaceholder
|
|
109
98
|
} : {
|
|
110
99
|
opacity: theme.palette.mode === 'light' ? 0.42 : 0.5
|
|
111
100
|
}
|
|
112
101
|
}, {
|
|
102
|
+
// ...except when a non-shrunk label sits in the notch, where the format would overlap it.
|
|
103
|
+
// A start adornment always shrinks the label, so it never triggers this case.
|
|
113
104
|
props: {
|
|
114
|
-
hasStartAdornment: false,
|
|
115
105
|
isFieldFocused: false,
|
|
116
106
|
isFieldValueEmpty: true,
|
|
107
|
+
hasStartAdornment: false,
|
|
117
108
|
inputHasLabel: true,
|
|
118
|
-
isLabelShrunk:
|
|
109
|
+
isLabelShrunk: false
|
|
119
110
|
},
|
|
120
|
-
style:
|
|
121
|
-
opacity:
|
|
122
|
-
} : {
|
|
123
|
-
opacity: theme.palette.mode === 'light' ? 0.42 : 0.5
|
|
111
|
+
style: {
|
|
112
|
+
opacity: 0
|
|
124
113
|
}
|
|
125
114
|
}]
|
|
126
115
|
}));
|
|
@@ -150,7 +139,16 @@ const PickersInputBaseSectionContent = (0, _styles.styled)(_PickersSectionList.U
|
|
|
150
139
|
// 23px
|
|
151
140
|
letterSpacing: 'inherit',
|
|
152
141
|
width: 'fit-content',
|
|
153
|
-
outline: 'none'
|
|
142
|
+
outline: 'none',
|
|
143
|
+
// Disables Chromium's focus-delegation onto contenteditable descendants
|
|
144
|
+
// while the field is not focused. Gated to Chromium via a Blink-only
|
|
145
|
+
// property, because the rule breaks Playwright's `fill()` on WebKit.
|
|
146
|
+
'@supports (-webkit-app-region: drag)': {
|
|
147
|
+
[`.${_pickersInputBaseClasses.pickersInputBaseClasses.root}:not(:focus-within) &`]: {
|
|
148
|
+
WebkitUserModify: 'read-only',
|
|
149
|
+
userSelect: 'none'
|
|
150
|
+
}
|
|
151
|
+
}
|
|
154
152
|
}));
|
|
155
153
|
const PickersInputBaseSectionSeparator = (0, _styles.styled)(_PickersSectionList.Unstable_PickersSectionListSectionSeparator, {
|
|
156
154
|
name: 'MuiPickersInputBase',
|
|
@@ -216,7 +214,8 @@ const useUtilityClasses = (classes, ownerState) => {
|
|
|
216
214
|
return (0, _composeClasses.default)(slots, _pickersInputBaseClasses.getPickersInputBaseUtilityClass, classes);
|
|
217
215
|
};
|
|
218
216
|
function resolveSectionElementWidth(sectionElement, rootRef, index, dateRangePosition) {
|
|
219
|
-
|
|
217
|
+
// Only measure sections that belong to a range date.
|
|
218
|
+
if (sectionElement.content['data-range-position'] !== undefined) {
|
|
220
219
|
const activeSectionElements = rootRef.current?.querySelectorAll(`[data-sectionindex="${index}"] [data-range-position="${dateRangePosition}"]`);
|
|
221
220
|
if (activeSectionElements) {
|
|
222
221
|
return Array.from(activeSectionElements).reduce((currentActiveBarWidth, element) => {
|
|
@@ -461,6 +460,7 @@ process.env.NODE_ENV !== "production" ? PickersInputBase.propTypes = {
|
|
|
461
460
|
onClick: _propTypes.default.func.isRequired,
|
|
462
461
|
onInput: _propTypes.default.func.isRequired,
|
|
463
462
|
onKeyDown: _propTypes.default.func.isRequired,
|
|
463
|
+
onMouseDown: _propTypes.default.func.isRequired,
|
|
464
464
|
onPaste: _propTypes.default.func.isRequired,
|
|
465
465
|
ownerState: _propTypes.default /* @typescript-to-proptypes-ignore */.any,
|
|
466
466
|
readOnly: _propTypes.default.bool,
|
|
@@ -81,39 +81,28 @@ export const PickersInputBaseSectionsContainer = styled(PickersSectionListRoot,
|
|
|
81
81
|
paddingTop: 1
|
|
82
82
|
}
|
|
83
83
|
}, {
|
|
84
|
+
// While the field is empty and blurred, the format is shown as a placeholder.
|
|
84
85
|
props: {
|
|
85
|
-
hasStartAdornment: false,
|
|
86
86
|
isFieldFocused: false,
|
|
87
87
|
isFieldValueEmpty: true
|
|
88
88
|
},
|
|
89
|
-
style: {
|
|
90
|
-
color: 'currentColor',
|
|
91
|
-
opacity: 0
|
|
92
|
-
}
|
|
93
|
-
}, {
|
|
94
|
-
props: {
|
|
95
|
-
hasStartAdornment: false,
|
|
96
|
-
isFieldFocused: false,
|
|
97
|
-
isFieldValueEmpty: true,
|
|
98
|
-
inputHasLabel: false
|
|
99
|
-
},
|
|
100
89
|
style: theme.vars ? {
|
|
101
90
|
opacity: theme.vars.opacity.inputPlaceholder
|
|
102
91
|
} : {
|
|
103
92
|
opacity: theme.palette.mode === 'light' ? 0.42 : 0.5
|
|
104
93
|
}
|
|
105
94
|
}, {
|
|
95
|
+
// ...except when a non-shrunk label sits in the notch, where the format would overlap it.
|
|
96
|
+
// A start adornment always shrinks the label, so it never triggers this case.
|
|
106
97
|
props: {
|
|
107
|
-
hasStartAdornment: false,
|
|
108
98
|
isFieldFocused: false,
|
|
109
99
|
isFieldValueEmpty: true,
|
|
100
|
+
hasStartAdornment: false,
|
|
110
101
|
inputHasLabel: true,
|
|
111
|
-
isLabelShrunk:
|
|
102
|
+
isLabelShrunk: false
|
|
112
103
|
},
|
|
113
|
-
style:
|
|
114
|
-
opacity:
|
|
115
|
-
} : {
|
|
116
|
-
opacity: theme.palette.mode === 'light' ? 0.42 : 0.5
|
|
104
|
+
style: {
|
|
105
|
+
opacity: 0
|
|
117
106
|
}
|
|
118
107
|
}]
|
|
119
108
|
}));
|
|
@@ -143,7 +132,16 @@ const PickersInputBaseSectionContent = styled(PickersSectionListSectionContent,
|
|
|
143
132
|
// 23px
|
|
144
133
|
letterSpacing: 'inherit',
|
|
145
134
|
width: 'fit-content',
|
|
146
|
-
outline: 'none'
|
|
135
|
+
outline: 'none',
|
|
136
|
+
// Disables Chromium's focus-delegation onto contenteditable descendants
|
|
137
|
+
// while the field is not focused. Gated to Chromium via a Blink-only
|
|
138
|
+
// property, because the rule breaks Playwright's `fill()` on WebKit.
|
|
139
|
+
'@supports (-webkit-app-region: drag)': {
|
|
140
|
+
[`.${pickersInputBaseClasses.root}:not(:focus-within) &`]: {
|
|
141
|
+
WebkitUserModify: 'read-only',
|
|
142
|
+
userSelect: 'none'
|
|
143
|
+
}
|
|
144
|
+
}
|
|
147
145
|
}));
|
|
148
146
|
const PickersInputBaseSectionSeparator = styled(PickersSectionListSectionSeparator, {
|
|
149
147
|
name: 'MuiPickersInputBase',
|
|
@@ -209,7 +207,8 @@ const useUtilityClasses = (classes, ownerState) => {
|
|
|
209
207
|
return composeClasses(slots, getPickersInputBaseUtilityClass, classes);
|
|
210
208
|
};
|
|
211
209
|
function resolveSectionElementWidth(sectionElement, rootRef, index, dateRangePosition) {
|
|
212
|
-
|
|
210
|
+
// Only measure sections that belong to a range date.
|
|
211
|
+
if (sectionElement.content['data-range-position'] !== undefined) {
|
|
213
212
|
const activeSectionElements = rootRef.current?.querySelectorAll(`[data-sectionindex="${index}"] [data-range-position="${dateRangePosition}"]`);
|
|
214
213
|
if (activeSectionElements) {
|
|
215
214
|
return Array.from(activeSectionElements).reduce((currentActiveBarWidth, element) => {
|
|
@@ -454,6 +453,7 @@ process.env.NODE_ENV !== "production" ? PickersInputBase.propTypes = {
|
|
|
454
453
|
onClick: PropTypes.func.isRequired,
|
|
455
454
|
onInput: PropTypes.func.isRequired,
|
|
456
455
|
onKeyDown: PropTypes.func.isRequired,
|
|
456
|
+
onMouseDown: PropTypes.func.isRequired,
|
|
457
457
|
onPaste: PropTypes.func.isRequired,
|
|
458
458
|
ownerState: PropTypes /* @typescript-to-proptypes-ignore */.any,
|
|
459
459
|
readOnly: PropTypes.bool,
|
|
@@ -11,6 +11,7 @@ export interface PickersInputPropsUsedByField extends Pick<PickersSectionListPro
|
|
|
11
11
|
*/
|
|
12
12
|
areAllSectionsEmpty: boolean;
|
|
13
13
|
onClick: React.MouseEventHandler<HTMLDivElement>;
|
|
14
|
+
onMouseDown: React.MouseEventHandler<HTMLDivElement>;
|
|
14
15
|
onKeyDown: React.EventHandler<MuiEvent<React.KeyboardEvent<HTMLDivElement>>>;
|
|
15
16
|
onInput: React.FormEventHandler<HTMLDivElement>;
|
|
16
17
|
onPaste: React.ClipboardEventHandler<HTMLDivElement>;
|
|
@@ -11,6 +11,7 @@ export interface PickersInputPropsUsedByField extends Pick<PickersSectionListPro
|
|
|
11
11
|
*/
|
|
12
12
|
areAllSectionsEmpty: boolean;
|
|
13
13
|
onClick: React.MouseEventHandler<HTMLDivElement>;
|
|
14
|
+
onMouseDown: React.MouseEventHandler<HTMLDivElement>;
|
|
14
15
|
onKeyDown: React.EventHandler<MuiEvent<React.KeyboardEvent<HTMLDivElement>>>;
|
|
15
16
|
onInput: React.FormEventHandler<HTMLDivElement>;
|
|
16
17
|
onPaste: React.ClipboardEventHandler<HTMLDivElement>;
|
|
@@ -11,4 +11,4 @@ var _generateUtilityClasses = _interopRequireDefault(require("@mui/utils/generat
|
|
|
11
11
|
function getPickersInputBaseUtilityClass(slot) {
|
|
12
12
|
return (0, _generateUtilityClass.default)('MuiPickersInputBase', slot);
|
|
13
13
|
}
|
|
14
|
-
const pickersInputBaseClasses = exports.pickersInputBaseClasses = (0, _generateUtilityClasses.default)('MuiPickersInputBase', ['root', 'focused', 'disabled', 'error', 'notchedOutline', 'sectionContent', 'sectionBefore', 'sectionAfter', 'adornedStart', 'adornedEnd', 'input', 'inputSizeSmall', 'activeBar']);
|
|
14
|
+
const pickersInputBaseClasses = exports.pickersInputBaseClasses = (0, _generateUtilityClasses.default)('MuiPickersInputBase', ['root', 'focused', 'disabled', 'readOnly', 'error', 'notchedOutline', 'sectionsContainer', 'sectionContent', 'sectionBefore', 'sectionAfter', 'adornedStart', 'adornedEnd', 'input', 'inputSizeSmall', 'activeBar']);
|
|
@@ -3,4 +3,4 @@ import generateUtilityClasses from '@mui/utils/generateUtilityClasses';
|
|
|
3
3
|
export function getPickersInputBaseUtilityClass(slot) {
|
|
4
4
|
return generateUtilityClass('MuiPickersInputBase', slot);
|
|
5
5
|
}
|
|
6
|
-
export const pickersInputBaseClasses = generateUtilityClasses('MuiPickersInputBase', ['root', 'focused', 'disabled', 'error', 'notchedOutline', 'sectionContent', 'sectionBefore', 'sectionAfter', 'adornedStart', 'adornedEnd', 'input', 'inputSizeSmall', 'activeBar']);
|
|
6
|
+
export const pickersInputBaseClasses = generateUtilityClasses('MuiPickersInputBase', ['root', 'focused', 'disabled', 'readOnly', 'error', 'notchedOutline', 'sectionsContainer', 'sectionContent', 'sectionBefore', 'sectionAfter', 'adornedStart', 'adornedEnd', 'input', 'inputSizeSmall', 'activeBar']);
|
|
@@ -172,6 +172,7 @@ process.env.NODE_ENV !== "production" ? PickersOutlinedInput.propTypes = {
|
|
|
172
172
|
onClick: _propTypes.default.func.isRequired,
|
|
173
173
|
onInput: _propTypes.default.func.isRequired,
|
|
174
174
|
onKeyDown: _propTypes.default.func.isRequired,
|
|
175
|
+
onMouseDown: _propTypes.default.func.isRequired,
|
|
175
176
|
onPaste: _propTypes.default.func.isRequired,
|
|
176
177
|
ownerState: _propTypes.default /* @typescript-to-proptypes-ignore */.any,
|
|
177
178
|
readOnly: _propTypes.default.bool,
|
|
@@ -165,6 +165,7 @@ process.env.NODE_ENV !== "production" ? PickersOutlinedInput.propTypes = {
|
|
|
165
165
|
onClick: PropTypes.func.isRequired,
|
|
166
166
|
onInput: PropTypes.func.isRequired,
|
|
167
167
|
onKeyDown: PropTypes.func.isRequired,
|
|
168
|
+
onMouseDown: PropTypes.func.isRequired,
|
|
168
169
|
onPaste: PropTypes.func.isRequired,
|
|
169
170
|
ownerState: PropTypes /* @typescript-to-proptypes-ignore */.any,
|
|
170
171
|
readOnly: PropTypes.bool,
|
|
@@ -27,7 +27,7 @@ var _PickersInput = require("./PickersInput");
|
|
|
27
27
|
var _useFieldOwnerState = require("../internals/hooks/useFieldOwnerState");
|
|
28
28
|
var _usePickerTextFieldOwnerState = require("./usePickerTextFieldOwnerState");
|
|
29
29
|
var _jsxRuntime = require("react/jsx-runtime");
|
|
30
|
-
const _excluded = ["onFocus", "onBlur", "className", "classes", "color", "disabled", "error", "variant", "required", "hiddenLabel", "InputProps", "inputProps", "inputRef", "sectionListRef", "elements", "areAllSectionsEmpty", "onClick", "onKeyDown", "onKeyUp", "onPaste", "onInput", "endAdornment", "startAdornment", "tabIndex", "contentEditable", "focused", "value", "onChange", "fullWidth", "id", "name", "helperText", "FormHelperTextProps", "label", "InputLabelProps", "data-active-range-position"];
|
|
30
|
+
const _excluded = ["onFocus", "onBlur", "className", "classes", "color", "disabled", "error", "variant", "required", "hiddenLabel", "InputProps", "inputProps", "inputRef", "sectionListRef", "elements", "areAllSectionsEmpty", "onClick", "onMouseDown", "onKeyDown", "onKeyUp", "onPaste", "onInput", "endAdornment", "startAdornment", "tabIndex", "contentEditable", "focused", "value", "onChange", "fullWidth", "id", "name", "helperText", "FormHelperTextProps", "label", "InputLabelProps", "data-active-range-position"];
|
|
31
31
|
const VARIANT_COMPONENT = {
|
|
32
32
|
standard: _PickersInput.PickersInput,
|
|
33
33
|
filled: _PickersFilledInput.PickersFilledInput,
|
|
@@ -75,6 +75,7 @@ const PickersTextField = exports.PickersTextField = /*#__PURE__*/React.forwardRe
|
|
|
75
75
|
elements,
|
|
76
76
|
areAllSectionsEmpty,
|
|
77
77
|
onClick,
|
|
78
|
+
onMouseDown,
|
|
78
79
|
onKeyDown,
|
|
79
80
|
onKeyUp,
|
|
80
81
|
onPaste,
|
|
@@ -155,6 +156,7 @@ const PickersTextField = exports.PickersTextField = /*#__PURE__*/React.forwardRe
|
|
|
155
156
|
elements: elements,
|
|
156
157
|
areAllSectionsEmpty: areAllSectionsEmpty,
|
|
157
158
|
onClick: onClick,
|
|
159
|
+
onMouseDown: onMouseDown,
|
|
158
160
|
onKeyDown: onKeyDown,
|
|
159
161
|
onKeyUp: onKeyUp,
|
|
160
162
|
onInput: onInput,
|
|
@@ -267,6 +269,7 @@ process.env.NODE_ENV !== "production" ? PickersTextField.propTypes = {
|
|
|
267
269
|
onFocus: _propTypes.default.func.isRequired,
|
|
268
270
|
onInput: _propTypes.default.func.isRequired,
|
|
269
271
|
onKeyDown: _propTypes.default.func.isRequired,
|
|
272
|
+
onMouseDown: _propTypes.default.func.isRequired,
|
|
270
273
|
onPaste: _propTypes.default.func.isRequired,
|
|
271
274
|
readOnly: _propTypes.default.bool,
|
|
272
275
|
/**
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import _extends from "@babel/runtime/helpers/esm/extends";
|
|
4
4
|
import _objectWithoutPropertiesLoose from "@babel/runtime/helpers/esm/objectWithoutPropertiesLoose";
|
|
5
|
-
const _excluded = ["onFocus", "onBlur", "className", "classes", "color", "disabled", "error", "variant", "required", "hiddenLabel", "InputProps", "inputProps", "inputRef", "sectionListRef", "elements", "areAllSectionsEmpty", "onClick", "onKeyDown", "onKeyUp", "onPaste", "onInput", "endAdornment", "startAdornment", "tabIndex", "contentEditable", "focused", "value", "onChange", "fullWidth", "id", "name", "helperText", "FormHelperTextProps", "label", "InputLabelProps", "data-active-range-position"];
|
|
5
|
+
const _excluded = ["onFocus", "onBlur", "className", "classes", "color", "disabled", "error", "variant", "required", "hiddenLabel", "InputProps", "inputProps", "inputRef", "sectionListRef", "elements", "areAllSectionsEmpty", "onClick", "onMouseDown", "onKeyDown", "onKeyUp", "onPaste", "onInput", "endAdornment", "startAdornment", "tabIndex", "contentEditable", "focused", "value", "onChange", "fullWidth", "id", "name", "helperText", "FormHelperTextProps", "label", "InputLabelProps", "data-active-range-position"];
|
|
6
6
|
import * as React from 'react';
|
|
7
7
|
import PropTypes from 'prop-types';
|
|
8
8
|
import clsx from 'clsx';
|
|
@@ -68,6 +68,7 @@ const PickersTextField = /*#__PURE__*/React.forwardRef(function PickersTextField
|
|
|
68
68
|
elements,
|
|
69
69
|
areAllSectionsEmpty,
|
|
70
70
|
onClick,
|
|
71
|
+
onMouseDown,
|
|
71
72
|
onKeyDown,
|
|
72
73
|
onKeyUp,
|
|
73
74
|
onPaste,
|
|
@@ -148,6 +149,7 @@ const PickersTextField = /*#__PURE__*/React.forwardRef(function PickersTextField
|
|
|
148
149
|
elements: elements,
|
|
149
150
|
areAllSectionsEmpty: areAllSectionsEmpty,
|
|
150
151
|
onClick: onClick,
|
|
152
|
+
onMouseDown: onMouseDown,
|
|
151
153
|
onKeyDown: onKeyDown,
|
|
152
154
|
onKeyUp: onKeyUp,
|
|
153
155
|
onInput: onInput,
|
|
@@ -260,6 +262,7 @@ process.env.NODE_ENV !== "production" ? PickersTextField.propTypes = {
|
|
|
260
262
|
onFocus: PropTypes.func.isRequired,
|
|
261
263
|
onInput: PropTypes.func.isRequired,
|
|
262
264
|
onKeyDown: PropTypes.func.isRequired,
|
|
265
|
+
onMouseDown: PropTypes.func.isRequired,
|
|
263
266
|
onPaste: PropTypes.func.isRequired,
|
|
264
267
|
readOnly: PropTypes.bool,
|
|
265
268
|
/**
|
package/index.js
CHANGED
package/index.mjs
CHANGED
|
@@ -107,6 +107,7 @@ export type UseFieldForwardedProps<TEnableAccessibleFieldDOMStructure extends bo
|
|
|
107
107
|
focused?: boolean;
|
|
108
108
|
sectionListRef?: React.Ref<PickersSectionListRef>;
|
|
109
109
|
onClick?: React.MouseEventHandler;
|
|
110
|
+
onMouseDown?: React.MouseEventHandler;
|
|
110
111
|
onKeyDown?: React.KeyboardEventHandler;
|
|
111
112
|
onFocus?: React.FocusEventHandler;
|
|
112
113
|
onBlur?: React.FocusEventHandler;
|
|
@@ -107,6 +107,7 @@ export type UseFieldForwardedProps<TEnableAccessibleFieldDOMStructure extends bo
|
|
|
107
107
|
focused?: boolean;
|
|
108
108
|
sectionListRef?: React.Ref<PickersSectionListRef>;
|
|
109
109
|
onClick?: React.MouseEventHandler;
|
|
110
|
+
onMouseDown?: React.MouseEventHandler;
|
|
110
111
|
onKeyDown?: React.KeyboardEventHandler;
|
|
111
112
|
onFocus?: React.FocusEventHandler;
|
|
112
113
|
onBlur?: React.FocusEventHandler;
|
|
@@ -24,6 +24,7 @@ interface UseFieldRootPropsReturnValue {
|
|
|
24
24
|
onBlur: React.FocusEventHandler<HTMLDivElement>;
|
|
25
25
|
onFocus: React.FocusEventHandler<HTMLDivElement>;
|
|
26
26
|
onClick: React.MouseEventHandler<HTMLDivElement>;
|
|
27
|
+
onMouseDown: React.MouseEventHandler<HTMLDivElement>;
|
|
27
28
|
onPaste: React.ClipboardEventHandler<HTMLDivElement>;
|
|
28
29
|
onInput: React.FormEventHandler<HTMLDivElement>;
|
|
29
30
|
contentEditable: boolean;
|
|
@@ -24,6 +24,7 @@ interface UseFieldRootPropsReturnValue {
|
|
|
24
24
|
onBlur: React.FocusEventHandler<HTMLDivElement>;
|
|
25
25
|
onFocus: React.FocusEventHandler<HTMLDivElement>;
|
|
26
26
|
onClick: React.MouseEventHandler<HTMLDivElement>;
|
|
27
|
+
onMouseDown: React.MouseEventHandler<HTMLDivElement>;
|
|
27
28
|
onPaste: React.ClipboardEventHandler<HTMLDivElement>;
|
|
28
29
|
onInput: React.FormEventHandler<HTMLDivElement>;
|
|
29
30
|
contentEditable: boolean;
|
|
@@ -50,14 +50,22 @@ function useFieldRootProps(parameters) {
|
|
|
50
50
|
stateResponse
|
|
51
51
|
});
|
|
52
52
|
const containerClickTimeout = (0, _useTimeout.default)();
|
|
53
|
-
const handleClick = (0, _useEventCallback.default)(
|
|
53
|
+
const handleClick = (0, _useEventCallback.default)(() => {
|
|
54
54
|
if (disabled || !domGetters.isReady()) {
|
|
55
55
|
return;
|
|
56
56
|
}
|
|
57
|
-
setFocused(true);
|
|
58
57
|
if (parsedSelectedSections === 'all') {
|
|
58
|
+
setFocused(true);
|
|
59
59
|
containerClickTimeout.start(0, () => {
|
|
60
|
-
|
|
60
|
+
// `getSelection`/`getRangeAt` can be unset transiently (e.g. focus
|
|
61
|
+
// moved before this 0-tick callback ran). Fall back to the first
|
|
62
|
+
// section in that case.
|
|
63
|
+
const selection = document.getSelection();
|
|
64
|
+
if (!selection || selection.rangeCount === 0) {
|
|
65
|
+
setSelectedSections(sectionOrder.startIndex);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
const cursorPosition = selection.getRangeAt(0).startOffset;
|
|
61
69
|
if (cursorPosition === 0) {
|
|
62
70
|
setSelectedSections(sectionOrder.startIndex);
|
|
63
71
|
return;
|
|
@@ -71,15 +79,83 @@ function useFieldRootProps(parameters) {
|
|
|
71
79
|
}
|
|
72
80
|
setSelectedSections(sectionIndex - 1);
|
|
73
81
|
});
|
|
74
|
-
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// For trusted pointer input, `handleMouseDown` already ran and set the
|
|
86
|
+
// section. This branch only matters for `click` events that arrive
|
|
87
|
+
// without a preceding `mousedown` (programmatic `element.click()`, some
|
|
88
|
+
// assistive-technology activations, synthetic event sequences in tests).
|
|
89
|
+
// Fall back to the first section so the field doesn't enter a "focused
|
|
90
|
+
// but no active section" state.
|
|
91
|
+
if (!focused) {
|
|
75
92
|
setFocused(true);
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
93
|
+
if (parsedSelectedSections == null) {
|
|
94
|
+
setSelectedSections(sectionOrder.startIndex);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
// Replaces Chromium's focus delegation with an explicit section focus on
|
|
100
|
+
// every primary mousedown inside the sections container. The CSS gate
|
|
101
|
+
// (`WebkitUserModify: read-only` while not `:focus-within`) can make the
|
|
102
|
+
// section's contenteditable span temporarily non-focusable, in which case
|
|
103
|
+
// Chromium falls back to the sections-container `tabindex=0` and our
|
|
104
|
+
// `handleFocus` then runs the "no active section" fallback, briefly
|
|
105
|
+
// selecting the first section before the click bubble corrects it.
|
|
106
|
+
// Setting the target section here (and letting `syncSelectionToDOM` move
|
|
107
|
+
// focus via `.focus()`, which works even with the user-modify rule
|
|
108
|
+
// active) skips that race entirely.
|
|
109
|
+
const handleMouseDown = (0, _useEventCallback.default)(event => {
|
|
110
|
+
if (disabled || !domGetters.isReady() || parsedSelectedSections === 'all') {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
if (event.button !== 0) {
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
const target = event.target;
|
|
117
|
+
const sectionListRoot = domGetters.getRoot();
|
|
118
|
+
const isInsideSectionList = sectionListRoot.contains(target);
|
|
119
|
+
// Only the field root is ours outside the sections container: it is the
|
|
120
|
+
// target of a padding click. The adornments keep their own behavior.
|
|
121
|
+
if (!isInsideSectionList && target !== event.currentTarget) {
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
const sectionElement = isInsideSectionList ? target.closest('[data-sectionindex]') : null;
|
|
125
|
+
|
|
126
|
+
// Blank space is the field padding and the area past the sections. Mimic
|
|
127
|
+
// the native date input: first section on entry, keep it afterwards.
|
|
128
|
+
// `preventDefault` is what keeps it, by stopping both the browser blur and
|
|
129
|
+
// Chromium's focus delegation.
|
|
130
|
+
const isBlankSpaceClick = sectionElement == null && (!isInsideSectionList || isPointOutsideSections(sectionListRoot, event.clientX));
|
|
131
|
+
if (isBlankSpaceClick) {
|
|
132
|
+
event.preventDefault();
|
|
133
|
+
if (!focused) {
|
|
134
|
+
setFocused(true);
|
|
80
135
|
setSelectedSections(sectionOrder.startIndex);
|
|
81
136
|
}
|
|
137
|
+
return;
|
|
82
138
|
}
|
|
139
|
+
|
|
140
|
+
// Prefer the visually-containing section (matches Chromium's
|
|
141
|
+
// delegation + section container `onClick`), fall back to the
|
|
142
|
+
// closest-by-distance section for clicks on the container padding.
|
|
143
|
+
const parsedIndex = sectionElement ? Number(sectionElement.dataset.sectionindex) : findClosestSectionIndexToPoint(sectionListRoot, event.clientX);
|
|
144
|
+
// `Number(undefined) === NaN` and `NaN == null === false`, so guard
|
|
145
|
+
// explicitly here even though `data-sectionindex` is set by
|
|
146
|
+
// `useFieldSectionContainerProps` for every section in practice.
|
|
147
|
+
if (parsedIndex == null || !Number.isInteger(parsedIndex)) {
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
event.preventDefault();
|
|
151
|
+
setFocused(true);
|
|
152
|
+
// `mousedown` is now authoritative for pointer section selection. The
|
|
153
|
+
// section container's own `onClick` deduplicates against the resulting
|
|
154
|
+
// `parsedSelectedSections` on the click bubble (see
|
|
155
|
+
// `useFieldSectionContainerProps`), so we always select here and let that
|
|
156
|
+
// guard absorb the redundant call -- matching the pre-PR
|
|
157
|
+
// `onSelectedSectionsChange` invocation count.
|
|
158
|
+
setSelectedSections(parsedIndex);
|
|
83
159
|
});
|
|
84
160
|
const handleInput = (0, _useEventCallback.default)(event => {
|
|
85
161
|
if (!domGetters.isReady() || parsedSelectedSections !== 'all') {
|
|
@@ -148,10 +224,61 @@ function useFieldRootProps(parameters) {
|
|
|
148
224
|
onBlur: handleBlur,
|
|
149
225
|
onFocus: handleFocus,
|
|
150
226
|
onClick: handleClick,
|
|
227
|
+
onMouseDown: handleMouseDown,
|
|
151
228
|
onPaste: handlePaste,
|
|
152
229
|
onInput: handleInput,
|
|
153
230
|
// Other
|
|
154
231
|
contentEditable: parsedSelectedSections === 'all',
|
|
155
232
|
tabIndex: internalPropsWithDefaults.disabled || parsedSelectedSections === 0 ? -1 : 0 // TODO: Try to set to undefined when there is a section selected.
|
|
156
233
|
};
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Slop on each side of the sections, so a click that just misses the outermost
|
|
238
|
+
* one still selects it. Under half a digit at the default font size.
|
|
239
|
+
*/
|
|
240
|
+
const BLANK_SPACE_TOLERANCE = 4;
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Returns `true` when `clientX` sits past the sections on either side.
|
|
244
|
+
* Horizontal only: a click in the container's vertical padding still belongs to
|
|
245
|
+
* the section above or below it.
|
|
246
|
+
*/
|
|
247
|
+
function isPointOutsideSections(root, clientX) {
|
|
248
|
+
const sections = root.querySelectorAll('[data-sectionindex]');
|
|
249
|
+
if (sections.length === 0) {
|
|
250
|
+
return false;
|
|
251
|
+
}
|
|
252
|
+
let left = Infinity;
|
|
253
|
+
let right = -Infinity;
|
|
254
|
+
for (let i = 0; i < sections.length; i += 1) {
|
|
255
|
+
const rect = sections[i].getBoundingClientRect();
|
|
256
|
+
left = Math.min(left, rect.left);
|
|
257
|
+
right = Math.max(right, rect.right);
|
|
258
|
+
}
|
|
259
|
+
return clientX < left - BLANK_SPACE_TOLERANCE || clientX > right + BLANK_SPACE_TOLERANCE;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Returns the index of the section whose horizontal center is closest to `clientX`.
|
|
264
|
+
* Returns `null` if the field renders no `[role="spinbutton"]` descendants
|
|
265
|
+
* (defensive -- every section content span sets `role="spinbutton"` in practice).
|
|
266
|
+
*/
|
|
267
|
+
function findClosestSectionIndexToPoint(root, clientX) {
|
|
268
|
+
const sections = root.querySelectorAll('[role="spinbutton"]');
|
|
269
|
+
if (sections.length === 0) {
|
|
270
|
+
return null;
|
|
271
|
+
}
|
|
272
|
+
let closestIndex = 0;
|
|
273
|
+
let closestDistance = Infinity;
|
|
274
|
+
for (let i = 0; i < sections.length; i += 1) {
|
|
275
|
+
const rect = sections[i].getBoundingClientRect();
|
|
276
|
+
const center = (rect.left + rect.right) / 2;
|
|
277
|
+
const distance = Math.abs(clientX - center);
|
|
278
|
+
if (distance < closestDistance) {
|
|
279
|
+
closestDistance = distance;
|
|
280
|
+
closestIndex = i;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
return closestIndex;
|
|
157
284
|
}
|
|
@@ -44,14 +44,22 @@ export function useFieldRootProps(parameters) {
|
|
|
44
44
|
stateResponse
|
|
45
45
|
});
|
|
46
46
|
const containerClickTimeout = useTimeout();
|
|
47
|
-
const handleClick = useEventCallback(
|
|
47
|
+
const handleClick = useEventCallback(() => {
|
|
48
48
|
if (disabled || !domGetters.isReady()) {
|
|
49
49
|
return;
|
|
50
50
|
}
|
|
51
|
-
setFocused(true);
|
|
52
51
|
if (parsedSelectedSections === 'all') {
|
|
52
|
+
setFocused(true);
|
|
53
53
|
containerClickTimeout.start(0, () => {
|
|
54
|
-
|
|
54
|
+
// `getSelection`/`getRangeAt` can be unset transiently (e.g. focus
|
|
55
|
+
// moved before this 0-tick callback ran). Fall back to the first
|
|
56
|
+
// section in that case.
|
|
57
|
+
const selection = document.getSelection();
|
|
58
|
+
if (!selection || selection.rangeCount === 0) {
|
|
59
|
+
setSelectedSections(sectionOrder.startIndex);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
const cursorPosition = selection.getRangeAt(0).startOffset;
|
|
55
63
|
if (cursorPosition === 0) {
|
|
56
64
|
setSelectedSections(sectionOrder.startIndex);
|
|
57
65
|
return;
|
|
@@ -65,15 +73,83 @@ export function useFieldRootProps(parameters) {
|
|
|
65
73
|
}
|
|
66
74
|
setSelectedSections(sectionIndex - 1);
|
|
67
75
|
});
|
|
68
|
-
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// For trusted pointer input, `handleMouseDown` already ran and set the
|
|
80
|
+
// section. This branch only matters for `click` events that arrive
|
|
81
|
+
// without a preceding `mousedown` (programmatic `element.click()`, some
|
|
82
|
+
// assistive-technology activations, synthetic event sequences in tests).
|
|
83
|
+
// Fall back to the first section so the field doesn't enter a "focused
|
|
84
|
+
// but no active section" state.
|
|
85
|
+
if (!focused) {
|
|
69
86
|
setFocused(true);
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
87
|
+
if (parsedSelectedSections == null) {
|
|
88
|
+
setSelectedSections(sectionOrder.startIndex);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
// Replaces Chromium's focus delegation with an explicit section focus on
|
|
94
|
+
// every primary mousedown inside the sections container. The CSS gate
|
|
95
|
+
// (`WebkitUserModify: read-only` while not `:focus-within`) can make the
|
|
96
|
+
// section's contenteditable span temporarily non-focusable, in which case
|
|
97
|
+
// Chromium falls back to the sections-container `tabindex=0` and our
|
|
98
|
+
// `handleFocus` then runs the "no active section" fallback, briefly
|
|
99
|
+
// selecting the first section before the click bubble corrects it.
|
|
100
|
+
// Setting the target section here (and letting `syncSelectionToDOM` move
|
|
101
|
+
// focus via `.focus()`, which works even with the user-modify rule
|
|
102
|
+
// active) skips that race entirely.
|
|
103
|
+
const handleMouseDown = useEventCallback(event => {
|
|
104
|
+
if (disabled || !domGetters.isReady() || parsedSelectedSections === 'all') {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
if (event.button !== 0) {
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
const target = event.target;
|
|
111
|
+
const sectionListRoot = domGetters.getRoot();
|
|
112
|
+
const isInsideSectionList = sectionListRoot.contains(target);
|
|
113
|
+
// Only the field root is ours outside the sections container: it is the
|
|
114
|
+
// target of a padding click. The adornments keep their own behavior.
|
|
115
|
+
if (!isInsideSectionList && target !== event.currentTarget) {
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
const sectionElement = isInsideSectionList ? target.closest('[data-sectionindex]') : null;
|
|
119
|
+
|
|
120
|
+
// Blank space is the field padding and the area past the sections. Mimic
|
|
121
|
+
// the native date input: first section on entry, keep it afterwards.
|
|
122
|
+
// `preventDefault` is what keeps it, by stopping both the browser blur and
|
|
123
|
+
// Chromium's focus delegation.
|
|
124
|
+
const isBlankSpaceClick = sectionElement == null && (!isInsideSectionList || isPointOutsideSections(sectionListRoot, event.clientX));
|
|
125
|
+
if (isBlankSpaceClick) {
|
|
126
|
+
event.preventDefault();
|
|
127
|
+
if (!focused) {
|
|
128
|
+
setFocused(true);
|
|
74
129
|
setSelectedSections(sectionOrder.startIndex);
|
|
75
130
|
}
|
|
131
|
+
return;
|
|
76
132
|
}
|
|
133
|
+
|
|
134
|
+
// Prefer the visually-containing section (matches Chromium's
|
|
135
|
+
// delegation + section container `onClick`), fall back to the
|
|
136
|
+
// closest-by-distance section for clicks on the container padding.
|
|
137
|
+
const parsedIndex = sectionElement ? Number(sectionElement.dataset.sectionindex) : findClosestSectionIndexToPoint(sectionListRoot, event.clientX);
|
|
138
|
+
// `Number(undefined) === NaN` and `NaN == null === false`, so guard
|
|
139
|
+
// explicitly here even though `data-sectionindex` is set by
|
|
140
|
+
// `useFieldSectionContainerProps` for every section in practice.
|
|
141
|
+
if (parsedIndex == null || !Number.isInteger(parsedIndex)) {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
event.preventDefault();
|
|
145
|
+
setFocused(true);
|
|
146
|
+
// `mousedown` is now authoritative for pointer section selection. The
|
|
147
|
+
// section container's own `onClick` deduplicates against the resulting
|
|
148
|
+
// `parsedSelectedSections` on the click bubble (see
|
|
149
|
+
// `useFieldSectionContainerProps`), so we always select here and let that
|
|
150
|
+
// guard absorb the redundant call -- matching the pre-PR
|
|
151
|
+
// `onSelectedSectionsChange` invocation count.
|
|
152
|
+
setSelectedSections(parsedIndex);
|
|
77
153
|
});
|
|
78
154
|
const handleInput = useEventCallback(event => {
|
|
79
155
|
if (!domGetters.isReady() || parsedSelectedSections !== 'all') {
|
|
@@ -142,10 +218,61 @@ export function useFieldRootProps(parameters) {
|
|
|
142
218
|
onBlur: handleBlur,
|
|
143
219
|
onFocus: handleFocus,
|
|
144
220
|
onClick: handleClick,
|
|
221
|
+
onMouseDown: handleMouseDown,
|
|
145
222
|
onPaste: handlePaste,
|
|
146
223
|
onInput: handleInput,
|
|
147
224
|
// Other
|
|
148
225
|
contentEditable: parsedSelectedSections === 'all',
|
|
149
226
|
tabIndex: internalPropsWithDefaults.disabled || parsedSelectedSections === 0 ? -1 : 0 // TODO: Try to set to undefined when there is a section selected.
|
|
150
227
|
};
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Slop on each side of the sections, so a click that just misses the outermost
|
|
232
|
+
* one still selects it. Under half a digit at the default font size.
|
|
233
|
+
*/
|
|
234
|
+
const BLANK_SPACE_TOLERANCE = 4;
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Returns `true` when `clientX` sits past the sections on either side.
|
|
238
|
+
* Horizontal only: a click in the container's vertical padding still belongs to
|
|
239
|
+
* the section above or below it.
|
|
240
|
+
*/
|
|
241
|
+
function isPointOutsideSections(root, clientX) {
|
|
242
|
+
const sections = root.querySelectorAll('[data-sectionindex]');
|
|
243
|
+
if (sections.length === 0) {
|
|
244
|
+
return false;
|
|
245
|
+
}
|
|
246
|
+
let left = Infinity;
|
|
247
|
+
let right = -Infinity;
|
|
248
|
+
for (let i = 0; i < sections.length; i += 1) {
|
|
249
|
+
const rect = sections[i].getBoundingClientRect();
|
|
250
|
+
left = Math.min(left, rect.left);
|
|
251
|
+
right = Math.max(right, rect.right);
|
|
252
|
+
}
|
|
253
|
+
return clientX < left - BLANK_SPACE_TOLERANCE || clientX > right + BLANK_SPACE_TOLERANCE;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Returns the index of the section whose horizontal center is closest to `clientX`.
|
|
258
|
+
* Returns `null` if the field renders no `[role="spinbutton"]` descendants
|
|
259
|
+
* (defensive -- every section content span sets `role="spinbutton"` in practice).
|
|
260
|
+
*/
|
|
261
|
+
function findClosestSectionIndexToPoint(root, clientX) {
|
|
262
|
+
const sections = root.querySelectorAll('[role="spinbutton"]');
|
|
263
|
+
if (sections.length === 0) {
|
|
264
|
+
return null;
|
|
265
|
+
}
|
|
266
|
+
let closestIndex = 0;
|
|
267
|
+
let closestDistance = Infinity;
|
|
268
|
+
for (let i = 0; i < sections.length; i += 1) {
|
|
269
|
+
const rect = sections[i].getBoundingClientRect();
|
|
270
|
+
const center = (rect.left + rect.right) / 2;
|
|
271
|
+
const distance = Math.abs(clientX - center);
|
|
272
|
+
if (distance < closestDistance) {
|
|
273
|
+
closestDistance = distance;
|
|
274
|
+
closestIndex = i;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
return closestIndex;
|
|
151
278
|
}
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
+
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault").default;
|
|
3
4
|
var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard").default;
|
|
4
5
|
Object.defineProperty(exports, "__esModule", {
|
|
5
6
|
value: true
|
|
6
7
|
});
|
|
7
8
|
exports.useFieldSectionContainerProps = useFieldSectionContainerProps;
|
|
8
9
|
var React = _interopRequireWildcard(require("react"));
|
|
10
|
+
var _useEventCallback = _interopRequireDefault(require("@mui/utils/useEventCallback"));
|
|
9
11
|
/**
|
|
10
12
|
* Generate the props to pass to the container element of each section of the field.
|
|
11
13
|
* It is not used by the non-accessible DOM structure (with an <input /> element for editing).
|
|
@@ -16,6 +18,8 @@ var React = _interopRequireWildcard(require("react"));
|
|
|
16
18
|
function useFieldSectionContainerProps(parameters) {
|
|
17
19
|
const {
|
|
18
20
|
stateResponse: {
|
|
21
|
+
// States and derived states
|
|
22
|
+
parsedSelectedSections,
|
|
19
23
|
// Methods to update the states
|
|
20
24
|
setSelectedSections
|
|
21
25
|
},
|
|
@@ -23,16 +27,31 @@ function useFieldSectionContainerProps(parameters) {
|
|
|
23
27
|
disabled = false
|
|
24
28
|
}
|
|
25
29
|
} = parameters;
|
|
26
|
-
|
|
30
|
+
|
|
31
|
+
// A single stable handler shared by every section, rather than a per-index
|
|
32
|
+
// factory. The section index is read from the container's `data-sectionindex`
|
|
33
|
+
// at click time.
|
|
34
|
+
const handleClick = (0, _useEventCallback.default)(event => {
|
|
27
35
|
// The click event on the clear button would propagate to the input, trigger this handler and result in a wrong section selection.
|
|
28
36
|
// We avoid this by checking if the call to this function is actually intended, or a side effect.
|
|
29
37
|
if (disabled || event.isDefaultPrevented()) {
|
|
30
38
|
return;
|
|
31
39
|
}
|
|
40
|
+
const sectionIndex = Number(event.currentTarget.dataset.sectionindex);
|
|
41
|
+
if (!Number.isInteger(sectionIndex)) {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// For pointer input the `mousedown` handler already selected the section
|
|
46
|
+
// before this click bubbles, so bail to avoid a redundant
|
|
47
|
+
// `onSelectedSectionsChange` invocation.
|
|
48
|
+
if (parsedSelectedSections === sectionIndex) {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
32
51
|
setSelectedSections(sectionIndex);
|
|
33
|
-
}
|
|
52
|
+
});
|
|
34
53
|
return React.useCallback(sectionIndex => ({
|
|
35
54
|
'data-sectionindex': sectionIndex,
|
|
36
|
-
onClick:
|
|
37
|
-
}), [
|
|
55
|
+
onClick: handleClick
|
|
56
|
+
}), [handleClick]);
|
|
38
57
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
+
import useEventCallback from '@mui/utils/useEventCallback';
|
|
2
3
|
/**
|
|
3
4
|
* Generate the props to pass to the container element of each section of the field.
|
|
4
5
|
* It is not used by the non-accessible DOM structure (with an <input /> element for editing).
|
|
@@ -9,6 +10,8 @@ import * as React from 'react';
|
|
|
9
10
|
export function useFieldSectionContainerProps(parameters) {
|
|
10
11
|
const {
|
|
11
12
|
stateResponse: {
|
|
13
|
+
// States and derived states
|
|
14
|
+
parsedSelectedSections,
|
|
12
15
|
// Methods to update the states
|
|
13
16
|
setSelectedSections
|
|
14
17
|
},
|
|
@@ -16,16 +19,31 @@ export function useFieldSectionContainerProps(parameters) {
|
|
|
16
19
|
disabled = false
|
|
17
20
|
}
|
|
18
21
|
} = parameters;
|
|
19
|
-
|
|
22
|
+
|
|
23
|
+
// A single stable handler shared by every section, rather than a per-index
|
|
24
|
+
// factory. The section index is read from the container's `data-sectionindex`
|
|
25
|
+
// at click time.
|
|
26
|
+
const handleClick = useEventCallback(event => {
|
|
20
27
|
// The click event on the clear button would propagate to the input, trigger this handler and result in a wrong section selection.
|
|
21
28
|
// We avoid this by checking if the call to this function is actually intended, or a side effect.
|
|
22
29
|
if (disabled || event.isDefaultPrevented()) {
|
|
23
30
|
return;
|
|
24
31
|
}
|
|
32
|
+
const sectionIndex = Number(event.currentTarget.dataset.sectionindex);
|
|
33
|
+
if (!Number.isInteger(sectionIndex)) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// For pointer input the `mousedown` handler already selected the section
|
|
38
|
+
// before this click bubbles, so bail to avoid a redundant
|
|
39
|
+
// `onSelectedSectionsChange` invocation.
|
|
40
|
+
if (parsedSelectedSections === sectionIndex) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
25
43
|
setSelectedSections(sectionIndex);
|
|
26
|
-
}
|
|
44
|
+
});
|
|
27
45
|
return React.useCallback(sectionIndex => ({
|
|
28
46
|
'data-sectionindex': sectionIndex,
|
|
29
|
-
onClick:
|
|
30
|
-
}), [
|
|
47
|
+
onClick: handleClick
|
|
48
|
+
}), [handleClick]);
|
|
31
49
|
}
|
|
@@ -46,6 +46,7 @@ const useFieldV7TextField = parameters => {
|
|
|
46
46
|
sectionListRef: sectionListRefProp,
|
|
47
47
|
onBlur,
|
|
48
48
|
onClick,
|
|
49
|
+
onMouseDown,
|
|
49
50
|
onFocus,
|
|
50
51
|
onInput,
|
|
51
52
|
onPaste,
|
|
@@ -147,6 +148,17 @@ const useFieldV7TextField = parameters => {
|
|
|
147
148
|
onClick?.(event);
|
|
148
149
|
rootProps.onClick(event);
|
|
149
150
|
});
|
|
151
|
+
const handleRootMouseDown = (0, _useEventCallback.default)(event => {
|
|
152
|
+
// The `isDefaultPrevented` check skips mousedowns that have already been
|
|
153
|
+
// suppressed before this handler runs -- in particular, propagated events
|
|
154
|
+
// from the clear / open buttons whose own handlers `preventDefault`, and
|
|
155
|
+
// capture-phase parents that intentionally block field interactions.
|
|
156
|
+
if (event.isDefaultPrevented()) {
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
onMouseDown?.(event);
|
|
160
|
+
rootProps.onMouseDown(event);
|
|
161
|
+
});
|
|
150
162
|
const handleRootPaste = (0, _useEventCallback.default)(event => {
|
|
151
163
|
onPaste?.(event);
|
|
152
164
|
rootProps.onPaste(event);
|
|
@@ -229,6 +241,7 @@ const useFieldV7TextField = parameters => {
|
|
|
229
241
|
return (0, _extends2.default)({}, forwardedProps, rootProps, {
|
|
230
242
|
onBlur: handleRootBlur,
|
|
231
243
|
onClick: handleRootClick,
|
|
244
|
+
onMouseDown: handleRootMouseDown,
|
|
232
245
|
onFocus: handleRootFocus,
|
|
233
246
|
onInput: handleRootInput,
|
|
234
247
|
onPaste: handleRootPaste,
|
|
@@ -39,6 +39,7 @@ export const useFieldV7TextField = parameters => {
|
|
|
39
39
|
sectionListRef: sectionListRefProp,
|
|
40
40
|
onBlur,
|
|
41
41
|
onClick,
|
|
42
|
+
onMouseDown,
|
|
42
43
|
onFocus,
|
|
43
44
|
onInput,
|
|
44
45
|
onPaste,
|
|
@@ -140,6 +141,17 @@ export const useFieldV7TextField = parameters => {
|
|
|
140
141
|
onClick?.(event);
|
|
141
142
|
rootProps.onClick(event);
|
|
142
143
|
});
|
|
144
|
+
const handleRootMouseDown = useEventCallback(event => {
|
|
145
|
+
// The `isDefaultPrevented` check skips mousedowns that have already been
|
|
146
|
+
// suppressed before this handler runs -- in particular, propagated events
|
|
147
|
+
// from the clear / open buttons whose own handlers `preventDefault`, and
|
|
148
|
+
// capture-phase parents that intentionally block field interactions.
|
|
149
|
+
if (event.isDefaultPrevented()) {
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
onMouseDown?.(event);
|
|
153
|
+
rootProps.onMouseDown(event);
|
|
154
|
+
});
|
|
143
155
|
const handleRootPaste = useEventCallback(event => {
|
|
144
156
|
onPaste?.(event);
|
|
145
157
|
rootProps.onPaste(event);
|
|
@@ -222,6 +234,7 @@ export const useFieldV7TextField = parameters => {
|
|
|
222
234
|
return _extends({}, forwardedProps, rootProps, {
|
|
223
235
|
onBlur: handleRootBlur,
|
|
224
236
|
onClick: handleRootClick,
|
|
237
|
+
onMouseDown: handleRootMouseDown,
|
|
225
238
|
onFocus: handleRootFocus,
|
|
226
239
|
onInput: handleRootInput,
|
|
227
240
|
onPaste: handleRootPaste,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mui/x-date-pickers",
|
|
3
|
-
"version": "8.29.
|
|
3
|
+
"version": "8.29.3",
|
|
4
4
|
"author": "MUI Team",
|
|
5
5
|
"description": "The community edition of the MUI X Date and Time Picker components.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"clsx": "^2.1.1",
|
|
40
40
|
"prop-types": "^15.8.1",
|
|
41
41
|
"react-transition-group": "^4.4.5",
|
|
42
|
-
"@mui/x-internals": "8.29.
|
|
42
|
+
"@mui/x-internals": "8.29.3"
|
|
43
43
|
},
|
|
44
44
|
"peerDependencies": {
|
|
45
45
|
"@emotion/react": "^11.9.0",
|