@mui/material 7.3.10 → 7.3.11

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/Autocomplete/Autocomplete.js +63 -12
  2. package/Button/Button.js +18 -1
  3. package/CHANGELOG.md +35 -0
  4. package/Checkbox/Checkbox.js +2 -1
  5. package/ClickAwayListener/ClickAwayListener.js +2 -5
  6. package/Dialog/Dialog.js +11 -6
  7. package/Drawer/Drawer.js +18 -4
  8. package/Fab/Fab.js +7 -1
  9. package/InputBase/InputBase.js +39 -7
  10. package/ListItemButton/ListItemButton.js +7 -1
  11. package/MenuItem/MenuItem.js +6 -1
  12. package/Popper/BasePopper.js +23 -1
  13. package/Slider/useSlider.js +6 -3
  14. package/SwipeableDrawer/SwipeableDrawer.js +5 -4
  15. package/Switch/Switch.js +3 -4
  16. package/Unstable_TrapFocus/FocusTrap.js +15 -5
  17. package/esm/Autocomplete/Autocomplete.js +63 -12
  18. package/esm/Button/Button.js +18 -1
  19. package/esm/Checkbox/Checkbox.js +2 -1
  20. package/esm/ClickAwayListener/ClickAwayListener.js +2 -5
  21. package/esm/Dialog/Dialog.js +11 -6
  22. package/esm/Drawer/Drawer.js +18 -4
  23. package/esm/Fab/Fab.js +7 -1
  24. package/esm/InputBase/InputBase.js +39 -7
  25. package/esm/ListItemButton/ListItemButton.js +7 -1
  26. package/esm/MenuItem/MenuItem.js +6 -1
  27. package/esm/Popper/BasePopper.js +23 -1
  28. package/esm/Slider/useSlider.js +6 -3
  29. package/esm/SwipeableDrawer/SwipeableDrawer.js +5 -4
  30. package/esm/Switch/Switch.js +3 -4
  31. package/esm/Unstable_TrapFocus/FocusTrap.js +15 -5
  32. package/esm/index.js +1 -1
  33. package/esm/useAutocomplete/useAutocomplete.d.ts +4 -5
  34. package/esm/useAutocomplete/useAutocomplete.js +155 -46
  35. package/esm/utils/contains.d.ts +2 -0
  36. package/esm/utils/contains.js +2 -0
  37. package/esm/utils/focusable.d.ts +7 -0
  38. package/esm/utils/focusable.js +13 -0
  39. package/esm/utils/getEventTarget.d.ts +2 -0
  40. package/esm/utils/getEventTarget.js +2 -0
  41. package/esm/version/index.js +2 -2
  42. package/index.js +1 -1
  43. package/package.json +5 -5
  44. package/useAutocomplete/useAutocomplete.d.ts +4 -5
  45. package/useAutocomplete/useAutocomplete.js +155 -46
  46. package/utils/contains.d.ts +2 -0
  47. package/utils/contains.js +9 -0
  48. package/utils/focusable.d.ts +7 -0
  49. package/utils/focusable.js +20 -0
  50. package/utils/getEventTarget.d.ts +2 -0
  51. package/utils/getEventTarget.js +9 -0
  52. package/version/index.js +2 -2
@@ -15,7 +15,9 @@ var _ownerDocument = _interopRequireDefault(require("@mui/utils/ownerDocument"))
15
15
  var _getReactElementRef = _interopRequireDefault(require("@mui/utils/getReactElementRef"));
16
16
  var _exactProp = _interopRequireDefault(require("@mui/utils/exactProp"));
17
17
  var _elementAcceptingRef = _interopRequireDefault(require("@mui/utils/elementAcceptingRef"));
18
+ var _contains = _interopRequireDefault(require("../utils/contains"));
18
19
  var _getActiveElement = _interopRequireDefault(require("../utils/getActiveElement"));
20
+ var _focusable = require("../utils/focusable");
19
21
  var _jsxRuntime = require("react/jsx-runtime");
20
22
  // Inspired by https://github.com/focus-trap/tabbable
21
23
  const candidatesSelector = ['input', 'select', 'textarea', 'a[href]', 'button', '[tabindex]', 'audio[controls]', 'video[controls]', '[contenteditable]:not([contenteditable="false"])'].join(',');
@@ -114,21 +116,29 @@ function FocusTrap(props) {
114
116
  activated.current = !disableAutoFocus;
115
117
  }, [disableAutoFocus, open]);
116
118
  React.useEffect(() => {
119
+ // Reset on every mount — React 18 Strict Mode double-mounts leave this
120
+ // stuck at `true` after the cleanup of the previous mount set it.
121
+ ignoreNextEnforceFocus.current = false;
122
+
117
123
  // We might render an empty child.
118
124
  if (!open || !rootRef.current) {
119
125
  return;
120
126
  }
121
127
  const doc = (0, _ownerDocument.default)(rootRef.current);
122
128
  const activeElement = (0, _getActiveElement.default)(doc);
123
- if (!rootRef.current.contains(activeElement)) {
124
- if (!rootRef.current.hasAttribute('tabIndex')) {
129
+
130
+ // Prefer the explicitly marked focusable element. Fall back to the root
131
+ // element for generic FocusTrap usage.
132
+ const focusTarget = (0, _focusable.getFocusTarget)(rootRef.current) ?? rootRef.current;
133
+ if (!(0, _contains.default)(rootRef.current, activeElement)) {
134
+ if (!focusTarget.hasAttribute('tabIndex')) {
125
135
  if (process.env.NODE_ENV !== 'production') {
126
136
  console.error(['MUI: The modal content node does not accept focus.', 'For the benefit of assistive technologies, ' + 'the tabIndex of the node is being set to "-1".'].join('\n'));
127
137
  }
128
- rootRef.current.setAttribute('tabIndex', '-1');
138
+ focusTarget.setAttribute('tabIndex', '-1');
129
139
  }
130
140
  if (activated.current) {
131
- rootRef.current.focus();
141
+ focusTarget.focus();
132
142
  }
133
143
  }
134
144
  return () => {
@@ -188,7 +198,7 @@ function FocusTrap(props) {
188
198
  }
189
199
 
190
200
  // The focus is already inside
191
- if (rootElement.contains(activeEl)) {
201
+ if ((0, _contains.default)(rootElement, activeEl)) {
192
202
  return;
193
203
  }
194
204
 
@@ -6,6 +6,8 @@ import PropTypes from 'prop-types';
6
6
  import integerPropType from '@mui/utils/integerPropType';
7
7
  import chainPropTypes from '@mui/utils/chainPropTypes';
8
8
  import composeClasses from '@mui/utils/composeClasses';
9
+ import useForcedRerendering from '@mui/utils/useForcedRerendering';
10
+ import useEnhancedEffect from '@mui/utils/useEnhancedEffect';
9
11
  import useAutocomplete, { createFilterOptions } from "../useAutocomplete/index.js";
10
12
  import Popper from "../Popper/index.js";
11
13
  import ListSubheader from "../ListSubheader/index.js";
@@ -495,6 +497,46 @@ const Autocomplete = /*#__PURE__*/React.forwardRef(function Autocomplete(inProps
495
497
  ...props,
496
498
  componentName: 'Autocomplete'
497
499
  });
500
+
501
+ // Re-render when anchorEl resizes so the Popper width stays in sync.
502
+ // Width is always read synchronously from anchorEl.clientWidth during render
503
+ // (no stale cached value). The hook just triggers a re-render on resize.
504
+ const forceRenderOnResize = useForcedRerendering();
505
+ React.useEffect(() => {
506
+ if (!popupOpen || !anchorEl || typeof ResizeObserver === 'undefined') {
507
+ return undefined;
508
+ }
509
+ let lastWidth = anchorEl.clientWidth;
510
+ const observer = new ResizeObserver(() => {
511
+ const newWidth = anchorEl.clientWidth;
512
+ if (lastWidth !== newWidth) {
513
+ lastWidth = newWidth;
514
+ forceRenderOnResize();
515
+ }
516
+ });
517
+ observer.observe(anchorEl);
518
+ return () => {
519
+ observer.disconnect();
520
+ };
521
+ }, [popupOpen, anchorEl, forceRenderOnResize]);
522
+
523
+ // When popupOpen becomes false, useAutocomplete returns [] for groupedOptions.
524
+ // Transitioned Poppers can remain mounted for their exit animation, so keep rendering
525
+ // the last open-state options instead of flashing "No options" or an empty Paper.
526
+ // These options are stale because they no longer reflect the hook's current
527
+ // groupedOptions, but they are non-interactive while closing and reset on next open.
528
+ const previousGroupedOptionsRef = React.useRef([]);
529
+ const prevPopupOpenRef = React.useRef(false);
530
+ const renderedOptions = popupOpen ? groupedOptions : previousGroupedOptionsRef.current;
531
+ useEnhancedEffect(() => {
532
+ if (popupOpen && !prevPopupOpenRef.current) {
533
+ previousGroupedOptionsRef.current = [];
534
+ }
535
+ prevPopupOpenRef.current = popupOpen;
536
+ if (popupOpen && groupedOptions.length > 0) {
537
+ previousGroupedOptionsRef.current = groupedOptions;
538
+ }
539
+ }, [popupOpen, groupedOptions]);
498
540
  const hasClearIcon = !disableClearable && !disabled && dirty && !readOnly;
499
541
  const hasPopupIcon = (!freeSolo || forcePopupIcon === true) && forcePopupIcon !== false;
500
542
  const {
@@ -568,13 +610,24 @@ const Autocomplete = /*#__PURE__*/React.forwardRef(function Autocomplete(inProps
568
610
  additionalProps: {
569
611
  disablePortal,
570
612
  style: {
571
- width: anchorEl ? anchorEl.clientWidth : null
613
+ width: anchorEl ? anchorEl.clientWidth : null,
614
+ // Prevent interaction with stale cached options during exit transitions.
615
+ // The hook's filteredOptions is [] when popupOpen=false, so clicks on stale
616
+ // rendered options would pass undefined to selectNewValue.
617
+ pointerEvents: popupOpen ? undefined : 'none'
572
618
  },
573
619
  role: 'presentation',
574
620
  anchorEl,
575
621
  open: popupOpen
576
622
  }
577
623
  });
624
+
625
+ // Don't render the Popper when there's no content to show.
626
+ // In freeSolo mode, "No options" text is suppressed, so if there are also no
627
+ // matching options and loading is false, the Paper would be empty.
628
+ // Uses renderedOptions (not groupedOptions) so Popper stays during exit transitions.
629
+ // Respect keepMounted from resolved popperProps (handles both object and callback slotProps forms).
630
+ const hasPopupContent = renderedOptions.length > 0 || loading || !freeSolo || popperProps.keepMounted === true;
578
631
  const [ClearIndicatorSlot, clearIndicatorProps] = useSlot('clearIndicator', {
579
632
  elementType: AutocompleteClearIndicator,
580
633
  externalForwardedProps,
@@ -720,17 +773,17 @@ const Autocomplete = /*#__PURE__*/React.forwardRef(function Autocomplete(inProps
720
773
  ...getInputProps()
721
774
  }
722
775
  })
723
- }), anchorEl ? /*#__PURE__*/_jsx(AutocompletePopper, {
776
+ }), anchorEl && hasPopupContent ? /*#__PURE__*/_jsx(AutocompletePopper, {
724
777
  as: PopperSlot,
725
778
  ...popperProps,
726
779
  children: /*#__PURE__*/_jsxs(AutocompletePaper, {
727
780
  as: PaperSlot,
728
781
  ...paperProps,
729
- children: [loading && groupedOptions.length === 0 ? /*#__PURE__*/_jsx(AutocompleteLoading, {
782
+ children: [loading && renderedOptions.length === 0 ? /*#__PURE__*/_jsx(AutocompleteLoading, {
730
783
  className: classes.loading,
731
784
  ownerState: ownerState,
732
785
  children: loadingText
733
- }) : null, groupedOptions.length === 0 && !freeSolo && !loading ? /*#__PURE__*/_jsx(AutocompleteNoOptions, {
786
+ }) : null, renderedOptions.length === 0 && !freeSolo && !loading ? /*#__PURE__*/_jsx(AutocompleteNoOptions, {
734
787
  className: classes.noOptions,
735
788
  ownerState: ownerState,
736
789
  role: "presentation",
@@ -739,10 +792,9 @@ const Autocomplete = /*#__PURE__*/React.forwardRef(function Autocomplete(inProps
739
792
  event.preventDefault();
740
793
  },
741
794
  children: noOptionsText
742
- }) : null, groupedOptions.length > 0 ? /*#__PURE__*/_jsx(ListboxSlot, {
743
- as: ListboxComponentProp,
795
+ }) : null, renderedOptions.length > 0 ? /*#__PURE__*/_jsx(ListboxSlot, {
744
796
  ...listboxProps,
745
- children: groupedOptions.map((option, index) => {
797
+ children: renderedOptions.map((option, index) => {
746
798
  if (groupBy) {
747
799
  return renderGroup({
748
800
  key: option.key,
@@ -775,12 +827,11 @@ process.env.NODE_ENV !== "production" ? Autocomplete.propTypes /* remove-proptyp
775
827
  */
776
828
  autoHighlight: PropTypes.bool,
777
829
  /**
778
- * If `true`, the selected option becomes the value of the input
779
- * when the Autocomplete loses focus unless the user chooses
780
- * a different option or changes the character string in the input.
830
+ * If `true`, the value is updated when the input loses focus under one of these conditions:
781
831
  *
782
- * When using the `freeSolo` mode, the typed value will be the input value
783
- * if the Autocomplete loses focus without highlighting an option.
832
+ * - An option highlighted via keyboard navigation or `autoHighlight` is selected.
833
+ * Hover and touch highlights are ignored.
834
+ * - Otherwise, in `freeSolo` mode, the typed text becomes the value.
784
835
  * @default false
785
836
  */
786
837
  autoSelect: PropTypes.bool,
@@ -102,6 +102,17 @@ const ButtonRoot = styled(ButtonBase, {
102
102
  color: (theme.vars || theme).palette.action.disabled
103
103
  },
104
104
  variants: [{
105
+ props: ({
106
+ ownerState
107
+ }) => ownerState.startIcon || ownerState.loading && ownerState.loadingPosition === 'start',
108
+ style: {
109
+ '&::before': {
110
+ content: '"\\200b"',
111
+ width: 0,
112
+ overflow: 'hidden'
113
+ }
114
+ }
115
+ }, {
105
116
  props: {
106
117
  variant: 'contained'
107
118
  },
@@ -551,6 +562,12 @@ const Button = /*#__PURE__*/React.forwardRef(function Button(inProps, ref) {
551
562
  children: loadingIndicator
552
563
  })
553
564
  }) : null;
565
+
566
+ // Don't forward the 'root' classes to the ButtonBase, as they will get duplicated with the one passed to the className prop.
567
+ const {
568
+ root,
569
+ ...forwardedClasses
570
+ } = classes;
554
571
  return /*#__PURE__*/_jsxs(ButtonRoot, {
555
572
  ownerState: ownerState,
556
573
  className: clsx(contextProps.className, classes.root, className, positionClassName),
@@ -562,7 +579,7 @@ const Button = /*#__PURE__*/React.forwardRef(function Button(inProps, ref) {
562
579
  type: type,
563
580
  id: loading ? loadingId : idProp,
564
581
  ...other,
565
- classes: classes,
582
+ classes: forwardedClasses,
566
583
  children: [startIcon, loadingPosition !== 'end' && loader, children, loadingPosition === 'end' && loader, endIcon]
567
584
  });
568
585
  });
@@ -152,7 +152,8 @@ const Checkbox = /*#__PURE__*/React.forwardRef(function Checkbox(inProps, ref) {
152
152
  slots,
153
153
  slotProps: {
154
154
  input: mergeSlotProps(typeof externalInputProps === 'function' ? externalInputProps(ownerState) : externalInputProps, {
155
- 'data-indeterminate': indeterminate
155
+ 'data-indeterminate': indeterminate,
156
+ 'aria-checked': indeterminate ? 'mixed' : undefined
156
157
  })
157
158
  }
158
159
  }
@@ -2,6 +2,7 @@
2
2
 
3
3
  import * as React from 'react';
4
4
  import PropTypes from 'prop-types';
5
+ import contains from '@mui/utils/contains';
5
6
  import ownerDocument from '@mui/utils/ownerDocument';
6
7
  import useForkRef from '@mui/utils/useForkRef';
7
8
  import useEventCallback from '@mui/utils/useEventCallback';
@@ -84,11 +85,7 @@ function ClickAwayListener(props) {
84
85
  if (event.composedPath) {
85
86
  insideDOM = event.composedPath().includes(nodeRef.current);
86
87
  } else {
87
- insideDOM = !doc.documentElement.contains(
88
- // @ts-expect-error returns `false` as intended when not dispatched from a Node
89
- event.target) || nodeRef.current.contains(
90
- // @ts-expect-error returns `false` as intended when not dispatched from a Node
91
- event.target);
88
+ insideDOM = !contains(doc.documentElement, event.target) || contains(nodeRef.current, event.target);
92
89
  }
93
90
  if (!insideDOM && (disableReactTree || !insideReactTree)) {
94
91
  onClickAway(event);
@@ -16,6 +16,7 @@ import { styled, useTheme } from "../zero-styled/index.js";
16
16
  import memoTheme from "../utils/memoTheme.js";
17
17
  import { useDefaultProps } from "../DefaultPropsProvider/index.js";
18
18
  import useSlot from "../utils/useSlot.js";
19
+ import { FOCUSABLE_ATTRIBUTE } from "../utils/focusable.js";
19
20
  import { jsx as _jsx } from "react/jsx-runtime";
20
21
  const DialogBackdrop = styled(Backdrop, {
21
22
  name: 'MuiDialog',
@@ -294,7 +295,16 @@ const Dialog = /*#__PURE__*/React.forwardRef(function Dialog(inProps, ref) {
294
295
  shouldForwardComponentProp: true,
295
296
  externalForwardedProps,
296
297
  ownerState,
297
- className: clsx(classes.paper, PaperProps.className)
298
+ className: classes.paper,
299
+ additionalProps: {
300
+ elevation: 24,
301
+ role,
302
+ 'aria-describedby': ariaDescribedby,
303
+ 'aria-labelledby': ariaLabelledby,
304
+ 'aria-modal': ariaModal,
305
+ tabIndex: -1,
306
+ [FOCUSABLE_ATTRIBUTE]: ''
307
+ }
298
308
  });
299
309
  const [ContainerSlot, containerSlotProps] = useSlot('container', {
300
310
  elementType: DialogContainer,
@@ -338,11 +348,6 @@ const Dialog = /*#__PURE__*/React.forwardRef(function Dialog(inProps, ref) {
338
348
  ...containerSlotProps,
339
349
  children: /*#__PURE__*/_jsx(PaperSlot, {
340
350
  as: PaperComponent,
341
- elevation: 24,
342
- role: role,
343
- "aria-describedby": ariaDescribedby,
344
- "aria-labelledby": ariaLabelledby,
345
- "aria-modal": ariaModal,
346
351
  ...paperSlotProps,
347
352
  children: /*#__PURE__*/_jsx(DialogContext.Provider, {
348
353
  value: dialogContextValue,
@@ -14,8 +14,10 @@ import rootShouldForwardProp from "../styles/rootShouldForwardProp.js";
14
14
  import { styled, useTheme } from "../zero-styled/index.js";
15
15
  import memoTheme from "../utils/memoTheme.js";
16
16
  import { useDefaultProps } from "../DefaultPropsProvider/index.js";
17
+ import useForkRef from "../utils/useForkRef.js";
17
18
  import { getDrawerUtilityClass } from "./drawerClasses.js";
18
19
  import useSlot from "../utils/useSlot.js";
20
+ import { FOCUSABLE_ATTRIBUTE } from "../utils/focusable.js";
19
21
  import { mergeSlotProps } from "../utils/index.js";
20
22
  import { jsx as _jsx } from "react/jsx-runtime";
21
23
  const overridesResolver = (props, styles) => {
@@ -208,9 +210,15 @@ const Drawer = /*#__PURE__*/React.forwardRef(function Drawer(inProps, ref) {
208
210
  // We use this state is order to skip the appear transition during the
209
211
  // initial mount of the component.
210
212
  const mounted = React.useRef(false);
213
+ const rootRef = React.useRef(null);
214
+ const handleRef = useForkRef(ref, rootRef);
211
215
  React.useEffect(() => {
212
216
  mounted.current = true;
213
217
  }, []);
218
+
219
+ // Resolve the container lazily so Slide reads the mounted modal root
220
+ // after refs are assigned, rather than the initial null ref during render.
221
+ const resolveSlideContainer = React.useCallback(() => rootRef.current, []);
214
222
  const anchorInvariant = getAnchor({
215
223
  direction: isRtl ? 'rtl' : 'ltr'
216
224
  }, anchorProp);
@@ -242,7 +250,7 @@ const Drawer = /*#__PURE__*/React.forwardRef(function Drawer(inProps, ref) {
242
250
  }
243
251
  };
244
252
  const [RootSlot, rootSlotProps] = useSlot('root', {
245
- ref,
253
+ ref: handleRef,
246
254
  elementType: DrawerRoot,
247
255
  className: clsx(classes.root, classes.modal, className),
248
256
  shouldForwardComponentProp: true,
@@ -253,6 +261,7 @@ const Drawer = /*#__PURE__*/React.forwardRef(function Drawer(inProps, ref) {
253
261
  ...ModalProps
254
262
  },
255
263
  additionalProps: {
264
+ closeAfterTransition: true,
256
265
  open,
257
266
  onClose,
258
267
  hideBackdrop,
@@ -275,13 +284,15 @@ const Drawer = /*#__PURE__*/React.forwardRef(function Drawer(inProps, ref) {
275
284
  square: true,
276
285
  ...(variant === 'temporary' && {
277
286
  role: 'dialog',
278
- 'aria-modal': 'true'
287
+ 'aria-modal': 'true',
288
+ [FOCUSABLE_ATTRIBUTE]: '',
289
+ tabIndex: -1
279
290
  })
280
291
  }
281
292
  });
282
293
  const [DockedSlot, dockedSlotProps] = useSlot('docked', {
283
294
  elementType: DrawerDockedRoot,
284
- ref,
295
+ ref: handleRef,
285
296
  className: clsx(classes.root, classes.docked, className),
286
297
  ownerState,
287
298
  externalForwardedProps,
@@ -295,7 +306,10 @@ const Drawer = /*#__PURE__*/React.forwardRef(function Drawer(inProps, ref) {
295
306
  in: open,
296
307
  direction: oppositeDirection[anchorInvariant],
297
308
  timeout: transitionDuration,
298
- appear: mounted.current
309
+ appear: mounted.current,
310
+ ...(variant === 'temporary' && (slots.transition == null || slots.transition === Slide) && {
311
+ container: resolveSlideContainer
312
+ })
299
313
  }
300
314
  });
301
315
  const drawer = /*#__PURE__*/_jsx(PaperSlot, {
package/esm/Fab/Fab.js CHANGED
@@ -187,6 +187,12 @@ const Fab = /*#__PURE__*/React.forwardRef(function Fab(inProps, ref) {
187
187
  variant
188
188
  };
189
189
  const classes = useUtilityClasses(ownerState);
190
+
191
+ // Don't forward the 'root' class to the ButtonBase, as it will get duplicated with the one passed to the className prop.
192
+ const {
193
+ root,
194
+ ...forwardedClasses
195
+ } = classes;
190
196
  return /*#__PURE__*/_jsx(FabRoot, {
191
197
  className: clsx(classes.root, className),
192
198
  component: component,
@@ -196,7 +202,7 @@ const Fab = /*#__PURE__*/React.forwardRef(function Fab(inProps, ref) {
196
202
  ownerState: ownerState,
197
203
  ref: ref,
198
204
  ...other,
199
- classes: classes,
205
+ classes: forwardedClasses,
200
206
  children: children
201
207
  });
202
208
  });
@@ -19,9 +19,13 @@ import { useDefaultProps } from "../DefaultPropsProvider/index.js";
19
19
  import capitalize from "../utils/capitalize.js";
20
20
  import useForkRef from "../utils/useForkRef.js";
21
21
  import useEnhancedEffect from "../utils/useEnhancedEffect.js";
22
+ import ownerDocument from "../utils/ownerDocument.js";
23
+ import getActiveElement from "../utils/getActiveElement.js";
22
24
  import { isFilled } from "./utils.js";
23
25
  import inputBaseClasses, { getInputBaseUtilityClass } from "./inputBaseClasses.js";
24
26
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
27
+ const MUI_AUTO_FILL = 'mui-auto-fill';
28
+ const MUI_AUTO_FILL_CANCEL = 'mui-auto-fill-cancel';
25
29
  export const rootOverridesResolver = (props, styles) => {
26
30
  const {
27
31
  ownerState
@@ -184,11 +188,11 @@ export const InputBaseInput = styled('input', {
184
188
  ownerState
185
189
  }) => !ownerState.disableInjectingGlobalStyles,
186
190
  style: {
187
- animationName: 'mui-auto-fill-cancel',
191
+ animationName: MUI_AUTO_FILL_CANCEL,
188
192
  animationDuration: '10ms',
189
193
  '&:-webkit-autofill': {
190
194
  animationDuration: '5000s',
191
- animationName: 'mui-auto-fill'
195
+ animationName: MUI_AUTO_FILL
192
196
  }
193
197
  }
194
198
  }, {
@@ -219,14 +223,16 @@ export const InputBaseInput = styled('input', {
219
223
  };
220
224
  }));
221
225
  const InputGlobalStyles = globalCss({
222
- '@keyframes mui-auto-fill': {
226
+ // Keep keyframes non-empty for Emotion production builds. Animation properties are ignored
227
+ // inside keyframes, avoiding the visible display animation triggered by Chrome 117+.
228
+ [`@keyframes ${MUI_AUTO_FILL}`]: {
223
229
  from: {
224
- display: 'block'
230
+ animationName: MUI_AUTO_FILL
225
231
  }
226
232
  },
227
- '@keyframes mui-auto-fill-cancel': {
233
+ [`@keyframes ${MUI_AUTO_FILL_CANCEL}`]: {
228
234
  from: {
229
- display: 'block'
235
+ animationName: MUI_AUTO_FILL_CANCEL
230
236
  }
231
237
  }
232
238
  });
@@ -342,6 +348,32 @@ const InputBase = /*#__PURE__*/React.forwardRef(function InputBase(inProps, ref)
342
348
  });
343
349
  }
344
350
  }, [value, checkDirty, isControlled]);
351
+
352
+ // Sync focused state when autoFocus is used in SSR.
353
+ // If the browser focused the element before hydration, the onFocus handler never
354
+ // fires. If it did not, React hydration does not call focus() for autoFocus.
355
+ useEnhancedEffect(() => {
356
+ if (!autoFocus) {
357
+ return;
358
+ }
359
+ const input = inputRef.current;
360
+ if (!input) {
361
+ return;
362
+ }
363
+ const doc = ownerDocument(input);
364
+ const activeElement = getActiveElement(doc);
365
+ const noElementFocused = activeElement == null || activeElement === doc.body || activeElement === doc.documentElement;
366
+ if (input === activeElement) {
367
+ if (muiFormControl && muiFormControl.onFocus) {
368
+ muiFormControl.onFocus();
369
+ } else {
370
+ setFocused(true);
371
+ }
372
+ } else if (noElementFocused) {
373
+ input.focus();
374
+ }
375
+ // eslint-disable-next-line react-hooks/exhaustive-deps
376
+ }, [autoFocus]);
345
377
  const handleFocus = event => {
346
378
  if (onFocus) {
347
379
  onFocus(event);
@@ -430,7 +462,7 @@ const InputBase = /*#__PURE__*/React.forwardRef(function InputBase(inProps, ref)
430
462
  }
431
463
  const handleAutoFill = event => {
432
464
  // Provide a fake value as Chrome might not let you access it for security reasons.
433
- checkDirty(event.animationName === 'mui-auto-fill-cancel' ? inputRef.current : {
465
+ checkDirty(event.animationName === MUI_AUTO_FILL_CANCEL ? inputRef.current : {
434
466
  value: 'x'
435
467
  });
436
468
  };
@@ -164,6 +164,12 @@ const ListItemButton = /*#__PURE__*/React.forwardRef(function ListItemButton(inP
164
164
  selected
165
165
  };
166
166
  const classes = useUtilityClasses(ownerState);
167
+
168
+ // Don't forward the 'root' class to the ButtonBase, as it will get duplicated with the one passed to the className prop.
169
+ const {
170
+ root,
171
+ ...forwardedClasses
172
+ } = classes;
167
173
  const handleRef = useForkRef(listItemRef, ref);
168
174
  return /*#__PURE__*/_jsx(ListContext.Provider, {
169
175
  value: childContext,
@@ -177,7 +183,7 @@ const ListItemButton = /*#__PURE__*/React.forwardRef(function ListItemButton(inP
177
183
  ownerState: ownerState,
178
184
  className: clsx(classes.root, className),
179
185
  ...other,
180
- classes: classes,
186
+ classes: forwardedClasses,
181
187
  children: children
182
188
  })
183
189
  });
@@ -207,6 +207,11 @@ const MenuItem = /*#__PURE__*/React.forwardRef(function MenuItem(inProps, ref) {
207
207
  };
208
208
  const classes = useUtilityClasses(props);
209
209
  const handleRef = useForkRef(menuItemRef, ref);
210
+ // Don't forward the 'root' class to the ButtonBase, as it will get duplicated with the one passed to the className prop.
211
+ const {
212
+ root,
213
+ ...forwardedClasses
214
+ } = classes;
210
215
  let tabIndex;
211
216
  if (!props.disabled) {
212
217
  tabIndex = tabIndexProp !== undefined ? tabIndexProp : -1;
@@ -222,7 +227,7 @@ const MenuItem = /*#__PURE__*/React.forwardRef(function MenuItem(inProps, ref) {
222
227
  className: clsx(classes.root, className),
223
228
  ...other,
224
229
  ownerState: ownerState,
225
- classes: classes
230
+ classes: forwardedClasses
226
231
  })
227
232
  });
228
233
  });
@@ -143,8 +143,30 @@ const PopperTooltip = /*#__PURE__*/React.forwardRef(function PopperTooltip(props
143
143
  modifiers: popperModifiers
144
144
  });
145
145
  handlePopperRefRef.current(popper);
146
+ const popperElement = tooltipRef.current;
146
147
  return () => {
147
- popper.destroy();
148
+ // popper.destroy() clears all inline positioning via the applyStyles
149
+ // modifier cleanup, which causes the element to jump to its static
150
+ // position. Snapshot and restore only the positioning properties so the
151
+ // element stays in place during the destroy/recreate gap (prevents scroll
152
+ // jumps when a child focuses between the two).
153
+ // https://github.com/mui/mui-x/issues/21839
154
+ if (popperElement) {
155
+ const {
156
+ style
157
+ } = popperElement;
158
+ const position = style.position;
159
+ const top = style.top;
160
+ const left = style.left;
161
+ const transform = style.transform;
162
+ popper.destroy();
163
+ style.position = position;
164
+ style.top = top;
165
+ style.left = left;
166
+ style.transform = transform;
167
+ } else {
168
+ popper.destroy();
169
+ }
148
170
  handlePopperRefRef.current(null);
149
171
  };
150
172
  }, [resolvedAnchorElement, disablePortal, modifiers, open, popperOptions, rtlPlacement]);
@@ -11,6 +11,8 @@ import visuallyHidden from '@mui/utils/visuallyHidden';
11
11
  import clamp from '@mui/utils/clamp';
12
12
  import extractEventHandlers from '@mui/utils/extractEventHandlers';
13
13
  import areArraysEqual from "../utils/areArraysEqual.js";
14
+ import contains from "../utils/contains.js";
15
+ import getActiveElement from "../utils/getActiveElement.js";
14
16
  const INTENTIONAL_DRAG_COUNT_THRESHOLD = 2;
15
17
  function getNewValue(currentValue, step, direction, min, max) {
16
18
  return direction === 1 ? Math.min(currentValue + step, max) : Math.max(currentValue - step, min);
@@ -90,8 +92,8 @@ function focusThumb({
90
92
  activeIndex,
91
93
  setActive
92
94
  }) {
93
- const doc = ownerDocument(sliderRef.current);
94
- if (!sliderRef.current?.contains(doc.activeElement) || Number(doc?.activeElement?.getAttribute('data-index')) !== activeIndex) {
95
+ const activeElement = getActiveElement(ownerDocument(sliderRef.current));
96
+ if (!contains(sliderRef.current, activeElement) || Number(activeElement?.getAttribute('data-index')) !== activeIndex) {
95
97
  sliderRef.current?.querySelector(`[type="range"][data-index="${activeIndex}"]`).focus();
96
98
  }
97
99
  if (setActive) {
@@ -347,7 +349,8 @@ export function useSlider(parameters) {
347
349
  otherHandlers?.onKeyDown?.(event);
348
350
  };
349
351
  useEnhancedEffect(() => {
350
- if (disabled && sliderRef.current.contains(document.activeElement)) {
352
+ const activeElement = getActiveElement(ownerDocument(sliderRef.current));
353
+ if (disabled && contains(sliderRef.current, activeElement)) {
351
354
  // This is necessary because Firefox and Safari will keep focus
352
355
  // on a disabled element:
353
356
  // https://codesandbox.io/p/sandbox/mui-pr-22247-forked-h151h?file=/src/App.js
@@ -6,10 +6,11 @@ import PropTypes from 'prop-types';
6
6
  import elementTypeAcceptingRef from '@mui/utils/elementTypeAcceptingRef';
7
7
  import NoSsr from "../NoSsr/index.js";
8
8
  import Drawer, { getAnchor, isHorizontal } from "../Drawer/Drawer.js";
9
- import useForkRef from "../utils/useForkRef.js";
9
+ import contains from "../utils/contains.js";
10
10
  import ownerDocument from "../utils/ownerDocument.js";
11
11
  import ownerWindow from "../utils/ownerWindow.js";
12
12
  import useEventCallback from "../utils/useEventCallback.js";
13
+ import useForkRef from "../utils/useForkRef.js";
13
14
  import useEnhancedEffect from "../utils/useEnhancedEffect.js";
14
15
  import { useTheme } from "../zero-styled/index.js";
15
16
  import { useDefaultProps } from "../DefaultPropsProvider/index.js";
@@ -292,7 +293,7 @@ const SwipeableDrawer = /*#__PURE__*/React.forwardRef(function SwipeableDrawer(i
292
293
  const horizontalSwipe = isHorizontal(anchor);
293
294
  const currentX = calculateCurrentX(anchorRtl, nativeEvent.touches, ownerDocument(nativeEvent.currentTarget));
294
295
  const currentY = calculateCurrentY(anchorRtl, nativeEvent.touches, ownerWindow(nativeEvent.currentTarget));
295
- if (open && paperRef.current.contains(nativeEvent.target) && claimedSwipeInstance === null) {
296
+ if (open && contains(paperRef.current, nativeEvent.target) && claimedSwipeInstance === null) {
296
297
  const domTreeShapes = getDomTreeShapes(nativeEvent.target, paperRef.current);
297
298
  const hasNativeHandler = computeHasNativeHandler({
298
299
  domTreeShapes,
@@ -390,7 +391,7 @@ const SwipeableDrawer = /*#__PURE__*/React.forwardRef(function SwipeableDrawer(i
390
391
  }
391
392
 
392
393
  // At least one element clogs the drawer interaction zone.
393
- if (open && (hideBackdrop || !backdropRef.current.contains(nativeEvent.target)) && !paperRef.current.contains(nativeEvent.target)) {
394
+ if (open && (hideBackdrop || !contains(backdropRef.current, nativeEvent.target)) && !contains(paperRef.current, nativeEvent.target)) {
394
395
  return;
395
396
  }
396
397
  const anchorRtl = getAnchor(theme, anchor);
@@ -402,7 +403,7 @@ const SwipeableDrawer = /*#__PURE__*/React.forwardRef(function SwipeableDrawer(i
402
403
  // if disableSwipeToOpen
403
404
  // if target != swipeArea, and target is not a child of paper ref
404
405
  // if is a child of paper ref, and `allowSwipeInChildren` does not allow it
405
- if (disableSwipeToOpen || !(nativeEvent.target === swipeAreaRef.current || paperRef.current?.contains(nativeEvent.target) && (typeof allowSwipeInChildren === 'function' ? allowSwipeInChildren(nativeEvent, swipeAreaRef.current, paperRef.current) : allowSwipeInChildren))) {
406
+ if (disableSwipeToOpen || !(nativeEvent.target === swipeAreaRef.current || contains(paperRef.current, nativeEvent.target) && (typeof allowSwipeInChildren === 'function' ? allowSwipeInChildren(nativeEvent, swipeAreaRef.current, paperRef.current) : allowSwipeInChildren))) {
406
407
  return;
407
408
  }
408
409
  if (horizontalSwipe) {
@@ -13,6 +13,7 @@ import { styled } from "../zero-styled/index.js";
13
13
  import memoTheme from "../utils/memoTheme.js";
14
14
  import { useDefaultProps } from "../DefaultPropsProvider/index.js";
15
15
  import switchClasses, { getSwitchUtilityClass } from "./switchClasses.js";
16
+ import { mergeSlotProps } from "../utils/index.js";
16
17
  import useSlot from "../utils/useSlot.js";
17
18
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
18
19
  const useUtilityClasses = ownerState => {
@@ -225,6 +226,7 @@ const Switch = /*#__PURE__*/React.forwardRef(function Switch(inProps, ref) {
225
226
  size
226
227
  };
227
228
  const classes = useUtilityClasses(ownerState);
229
+ const externalInputProps = slotProps.input;
228
230
  const externalForwardedProps = {
229
231
  slots,
230
232
  slotProps
@@ -278,11 +280,8 @@ const Switch = /*#__PURE__*/React.forwardRef(function Switch(inProps, ref) {
278
280
  ...(slotProps.switchBase && {
279
281
  root: typeof slotProps.switchBase === 'function' ? slotProps.switchBase(ownerState) : slotProps.switchBase
280
282
  }),
281
- input: {
283
+ input: mergeSlotProps(typeof externalInputProps === 'function' ? externalInputProps(ownerState) : externalInputProps, {
282
284
  role: 'switch'
283
- },
284
- ...(slotProps.input && {
285
- input: typeof slotProps.input === 'function' ? slotProps.input(ownerState) : slotProps.input
286
285
  })
287
286
  }
288
287
  }), /*#__PURE__*/_jsx(TrackSlot, {