@mui/material 9.0.0-beta.0 → 9.0.0-beta.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 (138) hide show
  1. package/Accordion/Accordion.d.mts +1 -1
  2. package/Accordion/Accordion.d.ts +1 -1
  3. package/AccordionSummary/AccordionSummary.js +1 -0
  4. package/AccordionSummary/AccordionSummary.mjs +1 -0
  5. package/Backdrop/Backdrop.d.mts +1 -1
  6. package/Backdrop/Backdrop.d.ts +1 -1
  7. package/BottomNavigationAction/BottomNavigationAction.js +1 -0
  8. package/BottomNavigationAction/BottomNavigationAction.mjs +1 -0
  9. package/Breadcrumbs/BreadcrumbCollapsed.js +5 -1
  10. package/Breadcrumbs/BreadcrumbCollapsed.mjs +5 -1
  11. package/Button/Button.js +1 -0
  12. package/Button/Button.mjs +1 -0
  13. package/ButtonBase/ButtonBase.d.mts +5 -0
  14. package/ButtonBase/ButtonBase.d.ts +5 -0
  15. package/ButtonBase/ButtonBase.js +84 -85
  16. package/ButtonBase/ButtonBase.mjs +84 -85
  17. package/ButtonBase/useButtonBase.d.mts +91 -0
  18. package/ButtonBase/useButtonBase.d.ts +91 -0
  19. package/ButtonBase/useButtonBase.js +174 -0
  20. package/ButtonBase/useButtonBase.mjs +167 -0
  21. package/CHANGELOG.md +48 -0
  22. package/CardActionArea/CardActionArea.js +1 -0
  23. package/CardActionArea/CardActionArea.mjs +1 -0
  24. package/Chip/Chip.d.mts +7 -0
  25. package/Chip/Chip.d.ts +7 -0
  26. package/Chip/Chip.js +18 -1
  27. package/Chip/Chip.mjs +18 -1
  28. package/Dialog/Dialog.d.mts +8 -1
  29. package/Dialog/Dialog.d.ts +8 -1
  30. package/Dialog/Dialog.js +9 -1
  31. package/Dialog/Dialog.mjs +9 -1
  32. package/Divider/Divider.js +0 -8
  33. package/Divider/Divider.mjs +0 -8
  34. package/Drawer/Drawer.d.mts +1 -1
  35. package/Drawer/Drawer.d.ts +1 -1
  36. package/Fab/Fab.js +1 -0
  37. package/Fab/Fab.mjs +1 -0
  38. package/FilledInput/FilledInput.js +1 -1
  39. package/FilledInput/FilledInput.mjs +1 -1
  40. package/Grid/Grid.d.mts +8 -3
  41. package/Grid/Grid.d.ts +8 -3
  42. package/Grid/Grid.js +8 -3
  43. package/Grid/Grid.mjs +8 -3
  44. package/Grid/gridClasses.js +1 -1
  45. package/Grid/gridClasses.mjs +1 -1
  46. package/IconButton/IconButton.js +1 -0
  47. package/IconButton/IconButton.mjs +1 -0
  48. package/Input/Input.js +1 -1
  49. package/Input/Input.mjs +1 -1
  50. package/InputAdornment/inputAdornmentClasses.d.mts +2 -2
  51. package/InputAdornment/inputAdornmentClasses.d.ts +2 -2
  52. package/ListItemButton/ListItemButton.js +1 -0
  53. package/ListItemButton/ListItemButton.mjs +1 -0
  54. package/ListItemIcon/ListItemIcon.js +1 -1
  55. package/ListItemIcon/ListItemIcon.mjs +1 -1
  56. package/ListSubheader/ListSubheader.js +0 -3
  57. package/ListSubheader/ListSubheader.mjs +0 -3
  58. package/Menu/Menu.d.mts +1 -1
  59. package/Menu/Menu.d.ts +1 -1
  60. package/Menu/Menu.js +15 -32
  61. package/Menu/Menu.mjs +15 -32
  62. package/MenuItem/MenuItem.js +36 -26
  63. package/MenuItem/MenuItem.mjs +34 -26
  64. package/MenuList/MenuList.js +136 -101
  65. package/MenuList/MenuList.mjs +135 -100
  66. package/MenuList/MenuListContext.d.mts +11 -0
  67. package/MenuList/MenuListContext.d.ts +11 -0
  68. package/MenuList/MenuListContext.js +25 -0
  69. package/MenuList/MenuListContext.mjs +19 -0
  70. package/PaginationItem/PaginationItem.d.mts +5 -0
  71. package/PaginationItem/PaginationItem.d.ts +5 -0
  72. package/PaginationItem/PaginationItem.js +6 -0
  73. package/PaginationItem/PaginationItem.mjs +6 -0
  74. package/PigmentGrid/PigmentGrid.d.mts +1 -1
  75. package/PigmentGrid/PigmentGrid.d.ts +1 -1
  76. package/PigmentGrid/PigmentGrid.js +1 -1
  77. package/PigmentGrid/PigmentGrid.mjs +1 -1
  78. package/Popover/Popover.d.mts +1 -1
  79. package/Popover/Popover.d.ts +1 -1
  80. package/Popover/Popover.js +19 -7
  81. package/Popover/Popover.mjs +18 -6
  82. package/Snackbar/Snackbar.d.mts +1 -1
  83. package/Snackbar/Snackbar.d.ts +1 -1
  84. package/SpeedDial/SpeedDial.d.mts +1 -1
  85. package/SpeedDial/SpeedDial.d.ts +1 -1
  86. package/StepButton/StepButton.js +44 -14
  87. package/StepButton/StepButton.mjs +44 -14
  88. package/StepContent/StepContent.d.mts +1 -1
  89. package/StepContent/StepContent.d.ts +1 -1
  90. package/Stepper/Stepper.js +54 -22
  91. package/Stepper/Stepper.mjs +54 -22
  92. package/Stepper/StepperContext.d.mts +0 -5
  93. package/Stepper/StepperContext.d.ts +0 -5
  94. package/Stepper/StepperContext.js +1 -2
  95. package/Stepper/StepperContext.mjs +0 -1
  96. package/Tab/Tab.js +17 -1
  97. package/Tab/Tab.mjs +17 -1
  98. package/TabScrollButton/TabScrollButton.d.mts +1 -1
  99. package/TabScrollButton/TabScrollButton.d.ts +1 -1
  100. package/TabScrollButton/TabScrollButton.js +6 -2
  101. package/TabScrollButton/TabScrollButton.mjs +6 -2
  102. package/TableSortLabel/TableSortLabel.js +4 -1
  103. package/TableSortLabel/TableSortLabel.mjs +4 -1
  104. package/Tabs/Tabs.js +30 -21
  105. package/Tabs/Tabs.mjs +29 -20
  106. package/ToggleButton/ToggleButton.js +1 -0
  107. package/ToggleButton/ToggleButton.mjs +1 -0
  108. package/Tooltip/Tooltip.d.mts +1 -1
  109. package/Tooltip/Tooltip.d.ts +1 -1
  110. package/index.js +1 -1
  111. package/index.mjs +1 -1
  112. package/internal/SwitchBase.d.mts +2 -2
  113. package/internal/SwitchBase.d.ts +2 -2
  114. package/internal/SwitchBase.js +5 -1
  115. package/internal/SwitchBase.mjs +5 -1
  116. package/locale/psAF.js +1 -1
  117. package/locale/psAF.mjs +1 -1
  118. package/package.json +5 -5
  119. package/styles/createThemeWithVars.js +9 -9
  120. package/styles/createThemeWithVars.mjs +9 -9
  121. package/useAutocomplete/useAutocomplete.js +8 -0
  122. package/useAutocomplete/useAutocomplete.mjs +8 -0
  123. package/utils/focusWithVisible.js +24 -0
  124. package/utils/focusWithVisible.mjs +19 -0
  125. package/utils/index.d.mts +0 -1
  126. package/utils/index.d.ts +0 -1
  127. package/utils/index.js +0 -7
  128. package/utils/index.mjs +0 -1
  129. package/utils/useFocusableWhenDisabled.d.mts +30 -0
  130. package/utils/useFocusableWhenDisabled.d.ts +30 -0
  131. package/utils/useFocusableWhenDisabled.js +47 -0
  132. package/utils/useFocusableWhenDisabled.mjs +41 -0
  133. package/utils/useRovingTabIndex.d.mts +1 -2
  134. package/utils/useRovingTabIndex.d.ts +1 -2
  135. package/utils/useRovingTabIndex.js +25 -4
  136. package/utils/useRovingTabIndex.mjs +1 -2
  137. package/version/index.js +2 -2
  138. package/version/index.mjs +2 -2
@@ -48,7 +48,7 @@ export type AccordionSlotsAndSlotProps = CreateSlotsAndSlotProps<AccordionSlots,
48
48
  * Props forwarded to the transition slot.
49
49
  * By default, the available props are based on the [Collapse](https://mui.com/material-ui/api/collapse/#props) component.
50
50
  */
51
- transition: SlotComponentProps<React.ElementType, TransitionProps & AccordionTransitionSlotPropsOverrides, AccordionOwnerState>;
51
+ transition: SlotComponentProps<React.ElementType<TransitionProps>, TransitionProps & AccordionTransitionSlotPropsOverrides, AccordionOwnerState>;
52
52
  /**
53
53
  * Props forwarded to the region slot.
54
54
  * By default, the available props are based on the div element.
@@ -48,7 +48,7 @@ export type AccordionSlotsAndSlotProps = CreateSlotsAndSlotProps<AccordionSlots,
48
48
  * Props forwarded to the transition slot.
49
49
  * By default, the available props are based on the [Collapse](https://mui.com/material-ui/api/collapse/#props) component.
50
50
  */
51
- transition: SlotComponentProps<React.ElementType, TransitionProps & AccordionTransitionSlotPropsOverrides, AccordionOwnerState>;
51
+ transition: SlotComponentProps<React.ElementType<TransitionProps>, TransitionProps & AccordionTransitionSlotPropsOverrides, AccordionOwnerState>;
52
52
  /**
53
53
  * Props forwarded to the region slot.
54
54
  * By default, the available props are based on the div element.
@@ -159,6 +159,7 @@ const AccordionSummary = /*#__PURE__*/React.forwardRef(function AccordionSummary
159
159
  additionalProps: {
160
160
  focusRipple: false,
161
161
  disableRipple: true,
162
+ internalNativeButton: true,
162
163
  disabled,
163
164
  'aria-expanded': expanded,
164
165
  focusVisibleClassName: (0, _clsx.default)(classes.focusVisible, focusVisibleClassName)
@@ -152,6 +152,7 @@ const AccordionSummary = /*#__PURE__*/React.forwardRef(function AccordionSummary
152
152
  additionalProps: {
153
153
  focusRipple: false,
154
154
  disableRipple: true,
155
+ internalNativeButton: true,
155
156
  disabled,
156
157
  'aria-expanded': expanded,
157
158
  focusVisibleClassName: clsx(classes.focusVisible, focusVisibleClassName)
@@ -31,7 +31,7 @@ export type BackdropSlotsAndSlotProps = CreateSlotsAndSlotProps<BackdropSlots, {
31
31
  * Props forwarded to the transition slot.
32
32
  * By default, the available props are based on the [Fade](https://mui.com/material-ui/api/fade/#props) component.
33
33
  */
34
- transition: SlotComponentProps<React.ElementType, TransitionProps & BackdropTransitionSlotPropsOverrides, BackdropOwnerState>;
34
+ transition: SlotComponentProps<React.ElementType<TransitionProps>, TransitionProps & BackdropTransitionSlotPropsOverrides, BackdropOwnerState>;
35
35
  }>;
36
36
  export interface BackdropOwnProps extends Partial<Omit<FadeProps, 'children'>>, BackdropSlotsAndSlotProps {
37
37
  /**
@@ -31,7 +31,7 @@ export type BackdropSlotsAndSlotProps = CreateSlotsAndSlotProps<BackdropSlots, {
31
31
  * Props forwarded to the transition slot.
32
32
  * By default, the available props are based on the [Fade](https://mui.com/material-ui/api/fade/#props) component.
33
33
  */
34
- transition: SlotComponentProps<React.ElementType, TransitionProps & BackdropTransitionSlotPropsOverrides, BackdropOwnerState>;
34
+ transition: SlotComponentProps<React.ElementType<TransitionProps>, TransitionProps & BackdropTransitionSlotPropsOverrides, BackdropOwnerState>;
35
35
  }>;
36
36
  export interface BackdropOwnProps extends Partial<Omit<FadeProps, 'children'>>, BackdropSlotsAndSlotProps {
37
37
  /**
@@ -143,6 +143,7 @@ const BottomNavigationAction = /*#__PURE__*/React.forwardRef(function BottomNavi
143
143
  ref,
144
144
  className: (0, _clsx.default)(classes.root, className),
145
145
  additionalProps: {
146
+ internalNativeButton: true,
146
147
  focusRipple: true
147
148
  },
148
149
  getSlotProps: handlers => ({
@@ -136,6 +136,7 @@ const BottomNavigationAction = /*#__PURE__*/React.forwardRef(function BottomNavi
136
136
  ref,
137
137
  className: clsx(classes.root, className),
138
138
  additionalProps: {
139
+ internalNativeButton: true,
139
140
  focusRipple: true
140
141
  },
141
142
  getSlotProps: handlers => ({
@@ -59,11 +59,15 @@ function BreadcrumbCollapsed(props) {
59
59
  slotProps = {},
60
60
  ...otherProps
61
61
  } = props;
62
+ const {
63
+ nativeButton,
64
+ ...buttonBaseProps
65
+ } = otherProps;
62
66
  const ownerState = props;
63
67
  return /*#__PURE__*/(0, _jsxRuntime.jsx)("li", {
64
68
  children: /*#__PURE__*/(0, _jsxRuntime.jsx)(BreadcrumbCollapsedButton, {
65
69
  focusRipple: true,
66
- ...otherProps,
70
+ ...buttonBaseProps,
67
71
  ownerState: ownerState,
68
72
  children: /*#__PURE__*/(0, _jsxRuntime.jsx)(BreadcrumbCollapsedIcon, {
69
73
  as: slots.CollapsedIcon,
@@ -53,11 +53,15 @@ function BreadcrumbCollapsed(props) {
53
53
  slotProps = {},
54
54
  ...otherProps
55
55
  } = props;
56
+ const {
57
+ nativeButton,
58
+ ...buttonBaseProps
59
+ } = otherProps;
56
60
  const ownerState = props;
57
61
  return /*#__PURE__*/_jsx("li", {
58
62
  children: /*#__PURE__*/_jsx(BreadcrumbCollapsedButton, {
59
63
  focusRipple: true,
60
- ...otherProps,
64
+ ...buttonBaseProps,
61
65
  ownerState: ownerState,
62
66
  children: /*#__PURE__*/_jsx(BreadcrumbCollapsedIcon, {
63
67
  as: slots.CollapsedIcon,
package/Button/Button.js CHANGED
@@ -566,6 +566,7 @@ const Button = /*#__PURE__*/React.forwardRef(function Button(inProps, ref) {
566
566
  focusRipple: !disableFocusRipple,
567
567
  focusVisibleClassName: (0, _clsx.default)(classes.focusVisible, focusVisibleClassName),
568
568
  ref: ref,
569
+ internalNativeButton: true,
569
570
  type: type,
570
571
  id: loading ? loadingId : idProp,
571
572
  ...other,
package/Button/Button.mjs CHANGED
@@ -559,6 +559,7 @@ const Button = /*#__PURE__*/React.forwardRef(function Button(inProps, ref) {
559
559
  focusRipple: !disableFocusRipple,
560
560
  focusVisibleClassName: clsx(classes.focusVisible, focusVisibleClassName),
561
561
  ref: ref,
562
+ internalNativeButton: true,
562
563
  type: type,
563
564
  id: loading ? loadingId : idProp,
564
565
  ...other,
@@ -62,6 +62,11 @@ export interface ButtonBaseOwnProps {
62
62
  * @default 'a'
63
63
  */
64
64
  LinkComponent?: React.ElementType | undefined;
65
+ /**
66
+ * Whether the custom component is expected to render a native `<button>` element
67
+ * when passing a React component to the `component` or `slots` prop.
68
+ */
69
+ nativeButton?: boolean | undefined;
65
70
  /**
66
71
  * Callback fired when the component is focused with a keyboard.
67
72
  * We trigger a `onFocus` callback too.
@@ -62,6 +62,11 @@ export interface ButtonBaseOwnProps {
62
62
  * @default 'a'
63
63
  */
64
64
  LinkComponent?: React.ElementType | undefined;
65
+ /**
66
+ * Whether the custom component is expected to render a native `<button>` element
67
+ * when passing a React component to the `component` or `slots` prop.
68
+ */
69
+ nativeButton?: boolean | undefined;
65
70
  /**
66
71
  * Callback fired when the component is focused with a keyboard.
67
72
  * We trigger a `onFocus` callback too.
@@ -18,6 +18,7 @@ var _zeroStyled = require("../zero-styled");
18
18
  var _DefaultPropsProvider = require("../DefaultPropsProvider");
19
19
  var _useForkRef = _interopRequireDefault(require("../utils/useForkRef"));
20
20
  var _useEventCallback = _interopRequireDefault(require("../utils/useEventCallback"));
21
+ var _useButtonBase = _interopRequireDefault(require("./useButtonBase"));
21
22
  var _useLazyRipple = _interopRequireDefault(require("../useLazyRipple"));
22
23
  var _TouchRipple = _interopRequireDefault(require("./TouchRipple"));
23
24
  var _buttonBaseClasses = _interopRequireWildcard(require("./buttonBaseClasses"));
@@ -27,13 +28,14 @@ const useUtilityClasses = ownerState => {
27
28
  disabled,
28
29
  focusVisible,
29
30
  focusVisibleClassName,
31
+ suppressFocusVisible,
30
32
  classes
31
33
  } = ownerState;
32
34
  const slots = {
33
- root: ['root', disabled && 'disabled', focusVisible && 'focusVisible']
35
+ root: ['root', disabled && 'disabled', focusVisible && !suppressFocusVisible && 'focusVisible']
34
36
  };
35
37
  const composedClasses = (0, _composeClasses.default)(slots, _buttonBaseClasses.getButtonBaseUtilityClass, classes);
36
- if (focusVisible && focusVisibleClassName) {
38
+ if (focusVisible && !suppressFocusVisible && focusVisibleClassName) {
37
39
  composedClasses.root += ` ${focusVisibleClassName}`;
38
40
  }
39
41
  return composedClasses;
@@ -102,15 +104,25 @@ const ButtonBase = /*#__PURE__*/React.forwardRef(function ButtonBase(inProps, re
102
104
  disableTouchRipple = false,
103
105
  focusRipple = false,
104
106
  focusVisibleClassName,
107
+ /* eslint-disable react/prop-types */
108
+ // replaces internal handling in Chip, other components can opt-in individually to use this in the future
109
+ focusableWhenDisabled,
110
+ // escape hatch to suppress the focusVisible state and callback
111
+ // used by anchored <Menu>s to to suppress focus visible styling when opened with a pointer
112
+ suppressFocusVisible = false,
113
+ // private prop to allow native vs non-native button props to be resolved before mount
114
+ internalNativeButton: internalNativeButtonProp,
115
+ /* eslint-enable react/prop-types */
105
116
  LinkComponent = 'a',
117
+ nativeButton: nativeButtonProp,
106
118
  onBlur,
107
- onClick,
119
+ onClick: onClickProp,
108
120
  onContextMenu,
109
121
  onDragLeave,
110
122
  onFocus,
111
123
  onFocusVisible,
112
- onKeyDown,
113
- onKeyUp,
124
+ onKeyDown: onKeyDownProp,
125
+ onKeyUp: onKeyUpProp,
114
126
  onMouseDown,
115
127
  onMouseLeave,
116
128
  onMouseUp,
@@ -123,19 +135,68 @@ const ButtonBase = /*#__PURE__*/React.forwardRef(function ButtonBase(inProps, re
123
135
  type,
124
136
  ...other
125
137
  } = props;
126
- const buttonRef = React.useRef(null);
138
+ const isLink = Boolean(other.href || other.to);
139
+ const hasFormAction = Boolean(other.formAction);
140
+ let ComponentProp = component;
141
+ if (ComponentProp === 'button' && isLink) {
142
+ ComponentProp = LinkComponent;
143
+ }
144
+ const internalNativeButton = typeof ComponentProp === 'string' ? ComponentProp === 'button' : internalNativeButtonProp ?? false;
145
+ const nativeButton = nativeButtonProp ?? internalNativeButton;
127
146
  const ripple = (0, _useLazyRipple.default)();
128
147
  const handleRippleRef = (0, _useForkRef.default)(ripple.ref, touchRippleRef);
129
148
  const [focusVisible, setFocusVisible] = React.useState(false);
130
- if (disabled && focusVisible) {
149
+ if ((disabled || suppressFocusVisible) && focusVisible) {
131
150
  setFocusVisible(false);
132
151
  }
152
+ const handleBeforeKeyDown = (0, _useEventCallback.default)(event => {
153
+ // Check if key is already down to avoid repeats being counted as multiple activations
154
+ if (focusRipple && !event.repeat && focusVisible && event.key === ' ') {
155
+ ripple.stop(event, () => {
156
+ ripple.start(event);
157
+ });
158
+ }
159
+ });
160
+ const handleBeforeKeyUp = (0, _useEventCallback.default)(event => {
161
+ // calling preventDefault in keyUp on a <button> will not dispatch a click event if Space is pressed
162
+ // https://codesandbox.io/p/sandbox/button-keyup-preventdefault-dn7f0
163
+ if (focusRipple && event.key === ' ' && focusVisible && !event.defaultPrevented) {
164
+ ripple.stop(event, () => {
165
+ ripple.pulsate(event);
166
+ });
167
+ }
168
+ });
169
+ const {
170
+ getButtonProps,
171
+ rootRef: buttonRef
172
+ } = (0, _useButtonBase.default)({
173
+ nativeButton,
174
+ nativeButtonProp,
175
+ internalNativeButton,
176
+ allowInferredHostMismatch: isLink || typeof ComponentProp === 'string',
177
+ disabled,
178
+ type,
179
+ hasFormAction,
180
+ tabIndex,
181
+ onBeforeKeyDown: handleBeforeKeyDown,
182
+ onBeforeKeyUp: handleBeforeKeyUp
183
+ });
184
+ const {
185
+ onClick,
186
+ onKeyDown,
187
+ onKeyUp,
188
+ ...buttonProps
189
+ } = getButtonProps({
190
+ onClick: onClickProp,
191
+ onKeyDown: onKeyDownProp,
192
+ onKeyUp: onKeyUpProp
193
+ });
133
194
  React.useImperativeHandle(action, () => ({
134
195
  focusVisible: () => {
135
196
  setFocusVisible(true);
136
197
  buttonRef.current.focus();
137
198
  }
138
- }), []);
199
+ }), [buttonRef]);
139
200
  const enableTouchRipple = ripple.shouldMount && !disableRipple && !disabled;
140
201
  React.useEffect(() => {
141
202
  if (focusVisible && focusRipple && !disableRipple) {
@@ -170,7 +231,7 @@ const ButtonBase = /*#__PURE__*/React.forwardRef(function ButtonBase(inProps, re
170
231
  if (!buttonRef.current) {
171
232
  buttonRef.current = event.currentTarget;
172
233
  }
173
- if ((0, _isFocusVisible.default)(event.target)) {
234
+ if (!suppressFocusVisible && (0, _isFocusVisible.default)(event.target)) {
174
235
  setFocusVisible(true);
175
236
  if (onFocusVisible) {
176
237
  onFocusVisible(event);
@@ -180,79 +241,13 @@ const ButtonBase = /*#__PURE__*/React.forwardRef(function ButtonBase(inProps, re
180
241
  onFocus(event);
181
242
  }
182
243
  });
183
- const isNonNativeButton = () => {
184
- const button = buttonRef.current;
185
- if (!button) {
186
- return component && component !== 'button';
187
- }
188
- if (button.tagName === 'BUTTON') {
189
- return false;
190
- }
191
- return !(button.tagName === 'A' && button.href);
192
- };
193
- const handleKeyDown = (0, _useEventCallback.default)(event => {
194
- if (disabled) {
195
- return;
196
- }
197
-
198
- // Check if key is already down to avoid repeats being counted as multiple activations
199
- if (focusRipple && !event.repeat && focusVisible && event.key === ' ') {
200
- ripple.stop(event, () => {
201
- ripple.start(event);
202
- });
203
- }
204
- if (event.target === event.currentTarget && isNonNativeButton() && event.key === ' ') {
205
- event.preventDefault();
206
- }
207
- if (onKeyDown) {
208
- onKeyDown(event);
209
- }
210
-
211
- // Keyboard accessibility for non interactive elements
212
- if (event.target === event.currentTarget && isNonNativeButton() && event.key === 'Enter' && !disabled) {
213
- event.preventDefault();
214
- event.currentTarget.click();
215
- }
216
- });
217
- const handleKeyUp = (0, _useEventCallback.default)(event => {
244
+ const linkProps = {};
245
+ if (isLink) {
246
+ linkProps.tabIndex = disabled ? -1 : tabIndex;
218
247
  if (disabled) {
219
- return;
220
- }
221
-
222
- // calling preventDefault in keyUp on a <button> will not dispatch a click event if Space is pressed
223
- // https://codesandbox.io/p/sandbox/button-keyup-preventdefault-dn7f0
224
- if (focusRipple && event.key === ' ' && focusVisible && !event.defaultPrevented) {
225
- ripple.stop(event, () => {
226
- ripple.pulsate(event);
227
- });
228
- }
229
- if (onKeyUp) {
230
- onKeyUp(event);
231
- }
232
-
233
- // Keyboard accessibility for non interactive elements
234
- if (event.target === event.currentTarget && isNonNativeButton() && event.key === ' ' && !event.defaultPrevented && !disabled) {
235
- event.currentTarget.click();
236
- }
237
- });
238
- let ComponentProp = component;
239
- if (ComponentProp === 'button' && (other.href || other.to)) {
240
- ComponentProp = LinkComponent;
241
- }
242
- const buttonProps = {};
243
- if (ComponentProp === 'button') {
244
- const hasFormAttributes = !!other.formAction;
245
- // ButtonBase was defaulting to type="button" when no type prop was provided, which prevented form submission and broke formAction functionality.
246
- // The fix checks for form-related attributes and skips the default type to allow the browser's natural submit behavior (type="submit").
247
- buttonProps.type = type === undefined && !hasFormAttributes ? 'button' : type;
248
- buttonProps.disabled = disabled;
249
- } else {
250
- if (!other.href && !other.to) {
251
- buttonProps.role = 'button';
252
- }
253
- if (disabled) {
254
- buttonProps['aria-disabled'] = disabled;
248
+ linkProps['aria-disabled'] = disabled;
255
249
  }
250
+ linkProps.type = type;
256
251
  }
257
252
  const handleRef = (0, _useForkRef.default)(ref, buttonRef);
258
253
  const ownerState = {
@@ -263,6 +258,7 @@ const ButtonBase = /*#__PURE__*/React.forwardRef(function ButtonBase(inProps, re
263
258
  disableRipple,
264
259
  disableTouchRipple,
265
260
  focusRipple,
261
+ suppressFocusVisible,
266
262
  tabIndex,
267
263
  focusVisible
268
264
  };
@@ -275,8 +271,8 @@ const ButtonBase = /*#__PURE__*/React.forwardRef(function ButtonBase(inProps, re
275
271
  onClick: onClick,
276
272
  onContextMenu: handleContextMenu,
277
273
  onFocus: handleFocus,
278
- onKeyDown: handleKeyDown,
279
- onKeyUp: handleKeyUp,
274
+ onKeyDown: onKeyDown,
275
+ onKeyUp: onKeyUp,
280
276
  onMouseDown: handleMouseDown,
281
277
  onMouseLeave: handleMouseLeave,
282
278
  onMouseUp: handleMouseUp,
@@ -285,9 +281,7 @@ const ButtonBase = /*#__PURE__*/React.forwardRef(function ButtonBase(inProps, re
285
281
  onTouchMove: handleTouchMove,
286
282
  onTouchStart: handleTouchStart,
287
283
  ref: handleRef,
288
- tabIndex: disabled ? -1 : tabIndex,
289
- type: type,
290
- ...buttonProps,
284
+ ...(isLink ? linkProps : buttonProps),
291
285
  ...other,
292
286
  children: [children, enableTouchRipple ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_TouchRipple.default, {
293
287
  ref: handleRippleRef,
@@ -385,6 +379,11 @@ process.env.NODE_ENV !== "production" ? ButtonBase.propTypes /* remove-proptypes
385
379
  * @default 'a'
386
380
  */
387
381
  LinkComponent: _propTypes.default.elementType,
382
+ /**
383
+ * Whether the custom component is expected to render a native `<button>` element
384
+ * when passing a React component to the `component` or `slots` prop.
385
+ */
386
+ nativeButton: _propTypes.default.bool,
388
387
  /**
389
388
  * @ignore
390
389
  */
@@ -11,6 +11,7 @@ import { styled } from "../zero-styled/index.mjs";
11
11
  import { useDefaultProps } from "../DefaultPropsProvider/index.mjs";
12
12
  import useForkRef from "../utils/useForkRef.mjs";
13
13
  import useEventCallback from "../utils/useEventCallback.mjs";
14
+ import useButtonBase from "./useButtonBase.mjs";
14
15
  import useLazyRipple from "../useLazyRipple/index.mjs";
15
16
  import TouchRipple from "./TouchRipple.mjs";
16
17
  import buttonBaseClasses, { getButtonBaseUtilityClass } from "./buttonBaseClasses.mjs";
@@ -20,13 +21,14 @@ const useUtilityClasses = ownerState => {
20
21
  disabled,
21
22
  focusVisible,
22
23
  focusVisibleClassName,
24
+ suppressFocusVisible,
23
25
  classes
24
26
  } = ownerState;
25
27
  const slots = {
26
- root: ['root', disabled && 'disabled', focusVisible && 'focusVisible']
28
+ root: ['root', disabled && 'disabled', focusVisible && !suppressFocusVisible && 'focusVisible']
27
29
  };
28
30
  const composedClasses = composeClasses(slots, getButtonBaseUtilityClass, classes);
29
- if (focusVisible && focusVisibleClassName) {
31
+ if (focusVisible && !suppressFocusVisible && focusVisibleClassName) {
30
32
  composedClasses.root += ` ${focusVisibleClassName}`;
31
33
  }
32
34
  return composedClasses;
@@ -95,15 +97,25 @@ const ButtonBase = /*#__PURE__*/React.forwardRef(function ButtonBase(inProps, re
95
97
  disableTouchRipple = false,
96
98
  focusRipple = false,
97
99
  focusVisibleClassName,
100
+ /* eslint-disable react/prop-types */
101
+ // replaces internal handling in Chip, other components can opt-in individually to use this in the future
102
+ focusableWhenDisabled,
103
+ // escape hatch to suppress the focusVisible state and callback
104
+ // used by anchored <Menu>s to to suppress focus visible styling when opened with a pointer
105
+ suppressFocusVisible = false,
106
+ // private prop to allow native vs non-native button props to be resolved before mount
107
+ internalNativeButton: internalNativeButtonProp,
108
+ /* eslint-enable react/prop-types */
98
109
  LinkComponent = 'a',
110
+ nativeButton: nativeButtonProp,
99
111
  onBlur,
100
- onClick,
112
+ onClick: onClickProp,
101
113
  onContextMenu,
102
114
  onDragLeave,
103
115
  onFocus,
104
116
  onFocusVisible,
105
- onKeyDown,
106
- onKeyUp,
117
+ onKeyDown: onKeyDownProp,
118
+ onKeyUp: onKeyUpProp,
107
119
  onMouseDown,
108
120
  onMouseLeave,
109
121
  onMouseUp,
@@ -116,19 +128,68 @@ const ButtonBase = /*#__PURE__*/React.forwardRef(function ButtonBase(inProps, re
116
128
  type,
117
129
  ...other
118
130
  } = props;
119
- const buttonRef = React.useRef(null);
131
+ const isLink = Boolean(other.href || other.to);
132
+ const hasFormAction = Boolean(other.formAction);
133
+ let ComponentProp = component;
134
+ if (ComponentProp === 'button' && isLink) {
135
+ ComponentProp = LinkComponent;
136
+ }
137
+ const internalNativeButton = typeof ComponentProp === 'string' ? ComponentProp === 'button' : internalNativeButtonProp ?? false;
138
+ const nativeButton = nativeButtonProp ?? internalNativeButton;
120
139
  const ripple = useLazyRipple();
121
140
  const handleRippleRef = useForkRef(ripple.ref, touchRippleRef);
122
141
  const [focusVisible, setFocusVisible] = React.useState(false);
123
- if (disabled && focusVisible) {
142
+ if ((disabled || suppressFocusVisible) && focusVisible) {
124
143
  setFocusVisible(false);
125
144
  }
145
+ const handleBeforeKeyDown = useEventCallback(event => {
146
+ // Check if key is already down to avoid repeats being counted as multiple activations
147
+ if (focusRipple && !event.repeat && focusVisible && event.key === ' ') {
148
+ ripple.stop(event, () => {
149
+ ripple.start(event);
150
+ });
151
+ }
152
+ });
153
+ const handleBeforeKeyUp = useEventCallback(event => {
154
+ // calling preventDefault in keyUp on a <button> will not dispatch a click event if Space is pressed
155
+ // https://codesandbox.io/p/sandbox/button-keyup-preventdefault-dn7f0
156
+ if (focusRipple && event.key === ' ' && focusVisible && !event.defaultPrevented) {
157
+ ripple.stop(event, () => {
158
+ ripple.pulsate(event);
159
+ });
160
+ }
161
+ });
162
+ const {
163
+ getButtonProps,
164
+ rootRef: buttonRef
165
+ } = useButtonBase({
166
+ nativeButton,
167
+ nativeButtonProp,
168
+ internalNativeButton,
169
+ allowInferredHostMismatch: isLink || typeof ComponentProp === 'string',
170
+ disabled,
171
+ type,
172
+ hasFormAction,
173
+ tabIndex,
174
+ onBeforeKeyDown: handleBeforeKeyDown,
175
+ onBeforeKeyUp: handleBeforeKeyUp
176
+ });
177
+ const {
178
+ onClick,
179
+ onKeyDown,
180
+ onKeyUp,
181
+ ...buttonProps
182
+ } = getButtonProps({
183
+ onClick: onClickProp,
184
+ onKeyDown: onKeyDownProp,
185
+ onKeyUp: onKeyUpProp
186
+ });
126
187
  React.useImperativeHandle(action, () => ({
127
188
  focusVisible: () => {
128
189
  setFocusVisible(true);
129
190
  buttonRef.current.focus();
130
191
  }
131
- }), []);
192
+ }), [buttonRef]);
132
193
  const enableTouchRipple = ripple.shouldMount && !disableRipple && !disabled;
133
194
  React.useEffect(() => {
134
195
  if (focusVisible && focusRipple && !disableRipple) {
@@ -163,7 +224,7 @@ const ButtonBase = /*#__PURE__*/React.forwardRef(function ButtonBase(inProps, re
163
224
  if (!buttonRef.current) {
164
225
  buttonRef.current = event.currentTarget;
165
226
  }
166
- if (isFocusVisible(event.target)) {
227
+ if (!suppressFocusVisible && isFocusVisible(event.target)) {
167
228
  setFocusVisible(true);
168
229
  if (onFocusVisible) {
169
230
  onFocusVisible(event);
@@ -173,79 +234,13 @@ const ButtonBase = /*#__PURE__*/React.forwardRef(function ButtonBase(inProps, re
173
234
  onFocus(event);
174
235
  }
175
236
  });
176
- const isNonNativeButton = () => {
177
- const button = buttonRef.current;
178
- if (!button) {
179
- return component && component !== 'button';
180
- }
181
- if (button.tagName === 'BUTTON') {
182
- return false;
183
- }
184
- return !(button.tagName === 'A' && button.href);
185
- };
186
- const handleKeyDown = useEventCallback(event => {
187
- if (disabled) {
188
- return;
189
- }
190
-
191
- // Check if key is already down to avoid repeats being counted as multiple activations
192
- if (focusRipple && !event.repeat && focusVisible && event.key === ' ') {
193
- ripple.stop(event, () => {
194
- ripple.start(event);
195
- });
196
- }
197
- if (event.target === event.currentTarget && isNonNativeButton() && event.key === ' ') {
198
- event.preventDefault();
199
- }
200
- if (onKeyDown) {
201
- onKeyDown(event);
202
- }
203
-
204
- // Keyboard accessibility for non interactive elements
205
- if (event.target === event.currentTarget && isNonNativeButton() && event.key === 'Enter' && !disabled) {
206
- event.preventDefault();
207
- event.currentTarget.click();
208
- }
209
- });
210
- const handleKeyUp = useEventCallback(event => {
237
+ const linkProps = {};
238
+ if (isLink) {
239
+ linkProps.tabIndex = disabled ? -1 : tabIndex;
211
240
  if (disabled) {
212
- return;
213
- }
214
-
215
- // calling preventDefault in keyUp on a <button> will not dispatch a click event if Space is pressed
216
- // https://codesandbox.io/p/sandbox/button-keyup-preventdefault-dn7f0
217
- if (focusRipple && event.key === ' ' && focusVisible && !event.defaultPrevented) {
218
- ripple.stop(event, () => {
219
- ripple.pulsate(event);
220
- });
221
- }
222
- if (onKeyUp) {
223
- onKeyUp(event);
224
- }
225
-
226
- // Keyboard accessibility for non interactive elements
227
- if (event.target === event.currentTarget && isNonNativeButton() && event.key === ' ' && !event.defaultPrevented && !disabled) {
228
- event.currentTarget.click();
229
- }
230
- });
231
- let ComponentProp = component;
232
- if (ComponentProp === 'button' && (other.href || other.to)) {
233
- ComponentProp = LinkComponent;
234
- }
235
- const buttonProps = {};
236
- if (ComponentProp === 'button') {
237
- const hasFormAttributes = !!other.formAction;
238
- // ButtonBase was defaulting to type="button" when no type prop was provided, which prevented form submission and broke formAction functionality.
239
- // The fix checks for form-related attributes and skips the default type to allow the browser's natural submit behavior (type="submit").
240
- buttonProps.type = type === undefined && !hasFormAttributes ? 'button' : type;
241
- buttonProps.disabled = disabled;
242
- } else {
243
- if (!other.href && !other.to) {
244
- buttonProps.role = 'button';
245
- }
246
- if (disabled) {
247
- buttonProps['aria-disabled'] = disabled;
241
+ linkProps['aria-disabled'] = disabled;
248
242
  }
243
+ linkProps.type = type;
249
244
  }
250
245
  const handleRef = useForkRef(ref, buttonRef);
251
246
  const ownerState = {
@@ -256,6 +251,7 @@ const ButtonBase = /*#__PURE__*/React.forwardRef(function ButtonBase(inProps, re
256
251
  disableRipple,
257
252
  disableTouchRipple,
258
253
  focusRipple,
254
+ suppressFocusVisible,
259
255
  tabIndex,
260
256
  focusVisible
261
257
  };
@@ -268,8 +264,8 @@ const ButtonBase = /*#__PURE__*/React.forwardRef(function ButtonBase(inProps, re
268
264
  onClick: onClick,
269
265
  onContextMenu: handleContextMenu,
270
266
  onFocus: handleFocus,
271
- onKeyDown: handleKeyDown,
272
- onKeyUp: handleKeyUp,
267
+ onKeyDown: onKeyDown,
268
+ onKeyUp: onKeyUp,
273
269
  onMouseDown: handleMouseDown,
274
270
  onMouseLeave: handleMouseLeave,
275
271
  onMouseUp: handleMouseUp,
@@ -278,9 +274,7 @@ const ButtonBase = /*#__PURE__*/React.forwardRef(function ButtonBase(inProps, re
278
274
  onTouchMove: handleTouchMove,
279
275
  onTouchStart: handleTouchStart,
280
276
  ref: handleRef,
281
- tabIndex: disabled ? -1 : tabIndex,
282
- type: type,
283
- ...buttonProps,
277
+ ...(isLink ? linkProps : buttonProps),
284
278
  ...other,
285
279
  children: [children, enableTouchRipple ? /*#__PURE__*/_jsx(TouchRipple, {
286
280
  ref: handleRippleRef,
@@ -378,6 +372,11 @@ process.env.NODE_ENV !== "production" ? ButtonBase.propTypes /* remove-proptypes
378
372
  * @default 'a'
379
373
  */
380
374
  LinkComponent: PropTypes.elementType,
375
+ /**
376
+ * Whether the custom component is expected to render a native `<button>` element
377
+ * when passing a React component to the `component` or `slots` prop.
378
+ */
379
+ nativeButton: PropTypes.bool,
381
380
  /**
382
381
  * @ignore
383
382
  */