UNPKG

14.9 kBJavaScriptView Raw
1import _extends from "@babel/runtime/helpers/esm/extends";
2import _objectWithoutPropertiesLoose from "@babel/runtime/helpers/esm/objectWithoutPropertiesLoose";
3const _excluded = ["action", "centerRipple", "children", "className", "component", "disabled", "disableRipple", "disableTouchRipple", "focusRipple", "focusVisibleClassName", "LinkComponent", "onBlur", "onClick", "onContextMenu", "onDragLeave", "onFocus", "onFocusVisible", "onKeyDown", "onKeyUp", "onMouseDown", "onMouseLeave", "onMouseUp", "onTouchEnd", "onTouchMove", "onTouchStart", "tabIndex", "TouchRippleProps", "touchRippleRef", "type"];
4import * as React from 'react';
5import PropTypes from 'prop-types';
6import clsx from 'clsx';
7import { elementTypeAcceptingRef, refType } from '@mui/utils';
8import composeClasses from '@mui/base/composeClasses';
9import styled from '../styles/styled';
10import useThemeProps from '../styles/useThemeProps';
11import useForkRef from '../utils/useForkRef';
12import useEventCallback from '../utils/useEventCallback';
13import useIsFocusVisible from '../utils/useIsFocusVisible';
14import TouchRipple from './TouchRipple';
15import buttonBaseClasses, { getButtonBaseUtilityClass } from './buttonBaseClasses';
16import { jsx as _jsx } from "react/jsx-runtime";
17import { jsxs as _jsxs } from "react/jsx-runtime";
18
19const useUtilityClasses = ownerState => {
20 const {
21 disabled,
22 focusVisible,
23 focusVisibleClassName,
24 classes
25 } = ownerState;
26 const slots = {
27 root: ['root', disabled && 'disabled', focusVisible && 'focusVisible']
28 };
29 const composedClasses = composeClasses(slots, getButtonBaseUtilityClass, classes);
30
31 if (focusVisible && focusVisibleClassName) {
32 composedClasses.root += ` ${focusVisibleClassName}`;
33 }
34
35 return composedClasses;
36};
37
38export const ButtonBaseRoot = styled('button', {
39 name: 'MuiButtonBase',
40 slot: 'Root',
41 overridesResolver: (props, styles) => styles.root
42})({
43 display: 'inline-flex',
44 alignItems: 'center',
45 justifyContent: 'center',
46 position: 'relative',
47 boxSizing: 'border-box',
48 WebkitTapHighlightColor: 'transparent',
49 backgroundColor: 'transparent',
50 // Reset default value
51 // We disable the focus ring for mouse, touch and keyboard users.
52 outline: 0,
53 border: 0,
54 margin: 0,
55 // Remove the margin in Safari
56 borderRadius: 0,
57 padding: 0,
58 // Remove the padding in Firefox
59 cursor: 'pointer',
60 userSelect: 'none',
61 verticalAlign: 'middle',
62 MozAppearance: 'none',
63 // Reset
64 WebkitAppearance: 'none',
65 // Reset
66 textDecoration: 'none',
67 // So we take precedent over the style of a native <a /> element.
68 color: 'inherit',
69 '&::-moz-focus-inner': {
70 borderStyle: 'none' // Remove Firefox dotted outline.
71
72 },
73 [`&.${buttonBaseClasses.disabled}`]: {
74 pointerEvents: 'none',
75 // Disable link interactions
76 cursor: 'default'
77 },
78 '@media print': {
79 colorAdjust: 'exact'
80 }
81});
82/**
83 * `ButtonBase` contains as few styles as possible.
84 * It aims to be a simple building block for creating a button.
85 * It contains a load of style reset and some focus/ripple logic.
86 */
87
88const ButtonBase = /*#__PURE__*/React.forwardRef(function ButtonBase(inProps, ref) {
89 const props = useThemeProps({
90 props: inProps,
91 name: 'MuiButtonBase'
92 });
93
94 const {
95 action,
96 centerRipple = false,
97 children,
98 className,
99 component = 'button',
100 disabled = false,
101 disableRipple = false,
102 disableTouchRipple = false,
103 focusRipple = false,
104 LinkComponent = 'a',
105 onBlur,
106 onClick,
107 onContextMenu,
108 onDragLeave,
109 onFocus,
110 onFocusVisible,
111 onKeyDown,
112 onKeyUp,
113 onMouseDown,
114 onMouseLeave,
115 onMouseUp,
116 onTouchEnd,
117 onTouchMove,
118 onTouchStart,
119 tabIndex = 0,
120 TouchRippleProps,
121 touchRippleRef,
122 type
123 } = props,
124 other = _objectWithoutPropertiesLoose(props, _excluded);
125
126 const buttonRef = React.useRef(null);
127 const rippleRef = React.useRef(null);
128 const handleRippleRef = useForkRef(rippleRef, touchRippleRef);
129 const {
130 isFocusVisibleRef,
131 onFocus: handleFocusVisible,
132 onBlur: handleBlurVisible,
133 ref: focusVisibleRef
134 } = useIsFocusVisible();
135 const [focusVisible, setFocusVisible] = React.useState(false);
136
137 if (disabled && focusVisible) {
138 setFocusVisible(false);
139 }
140
141 React.useImperativeHandle(action, () => ({
142 focusVisible: () => {
143 setFocusVisible(true);
144 buttonRef.current.focus();
145 }
146 }), []);
147 const [mountedState, setMountedState] = React.useState(false);
148 React.useEffect(() => {
149 setMountedState(true);
150 }, []);
151 const enableTouchRipple = mountedState && !disableRipple && !disabled;
152 React.useEffect(() => {
153 if (focusVisible && focusRipple && !disableRipple && mountedState) {
154 rippleRef.current.pulsate();
155 }
156 }, [disableRipple, focusRipple, focusVisible, mountedState]);
157
158 function useRippleHandler(rippleAction, eventCallback, skipRippleAction = disableTouchRipple) {
159 return useEventCallback(event => {
160 if (eventCallback) {
161 eventCallback(event);
162 }
163
164 const ignore = skipRippleAction;
165
166 if (!ignore && rippleRef.current) {
167 rippleRef.current[rippleAction](event);
168 }
169
170 return true;
171 });
172 }
173
174 const handleMouseDown = useRippleHandler('start', onMouseDown);
175 const handleContextMenu = useRippleHandler('stop', onContextMenu);
176 const handleDragLeave = useRippleHandler('stop', onDragLeave);
177 const handleMouseUp = useRippleHandler('stop', onMouseUp);
178 const handleMouseLeave = useRippleHandler('stop', event => {
179 if (focusVisible) {
180 event.preventDefault();
181 }
182
183 if (onMouseLeave) {
184 onMouseLeave(event);
185 }
186 });
187 const handleTouchStart = useRippleHandler('start', onTouchStart);
188 const handleTouchEnd = useRippleHandler('stop', onTouchEnd);
189 const handleTouchMove = useRippleHandler('stop', onTouchMove);
190 const handleBlur = useRippleHandler('stop', event => {
191 handleBlurVisible(event);
192
193 if (isFocusVisibleRef.current === false) {
194 setFocusVisible(false);
195 }
196
197 if (onBlur) {
198 onBlur(event);
199 }
200 }, false);
201 const handleFocus = useEventCallback(event => {
202 // Fix for https://github.com/facebook/react/issues/7769
203 if (!buttonRef.current) {
204 buttonRef.current = event.currentTarget;
205 }
206
207 handleFocusVisible(event);
208
209 if (isFocusVisibleRef.current === true) {
210 setFocusVisible(true);
211
212 if (onFocusVisible) {
213 onFocusVisible(event);
214 }
215 }
216
217 if (onFocus) {
218 onFocus(event);
219 }
220 });
221
222 const isNonNativeButton = () => {
223 const button = buttonRef.current;
224 return component && component !== 'button' && !(button.tagName === 'A' && button.href);
225 };
226 /**
227 * IE11 shim for https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/repeat
228 */
229
230
231 const keydownRef = React.useRef(false);
232 const handleKeyDown = useEventCallback(event => {
233 // Check if key is already down to avoid repeats being counted as multiple activations
234 if (focusRipple && !keydownRef.current && focusVisible && rippleRef.current && event.key === ' ') {
235 keydownRef.current = true;
236 rippleRef.current.stop(event, () => {
237 rippleRef.current.start(event);
238 });
239 }
240
241 if (event.target === event.currentTarget && isNonNativeButton() && event.key === ' ') {
242 event.preventDefault();
243 }
244
245 if (onKeyDown) {
246 onKeyDown(event);
247 } // Keyboard accessibility for non interactive elements
248
249
250 if (event.target === event.currentTarget && isNonNativeButton() && event.key === 'Enter' && !disabled) {
251 event.preventDefault();
252
253 if (onClick) {
254 onClick(event);
255 }
256 }
257 });
258 const handleKeyUp = useEventCallback(event => {
259 // calling preventDefault in keyUp on a <button> will not dispatch a click event if Space is pressed
260 // https://codesandbox.io/s/button-keyup-preventdefault-dn7f0
261 if (focusRipple && event.key === ' ' && rippleRef.current && focusVisible && !event.defaultPrevented) {
262 keydownRef.current = false;
263 rippleRef.current.stop(event, () => {
264 rippleRef.current.pulsate(event);
265 });
266 }
267
268 if (onKeyUp) {
269 onKeyUp(event);
270 } // Keyboard accessibility for non interactive elements
271
272
273 if (onClick && event.target === event.currentTarget && isNonNativeButton() && event.key === ' ' && !event.defaultPrevented) {
274 onClick(event);
275 }
276 });
277 let ComponentProp = component;
278
279 if (ComponentProp === 'button' && (other.href || other.to)) {
280 ComponentProp = LinkComponent;
281 }
282
283 const buttonProps = {};
284
285 if (ComponentProp === 'button') {
286 buttonProps.type = type === undefined ? 'button' : type;
287 buttonProps.disabled = disabled;
288 } else {
289 if (!other.href && !other.to) {
290 buttonProps.role = 'button';
291 }
292
293 if (disabled) {
294 buttonProps['aria-disabled'] = disabled;
295 }
296 }
297
298 const handleOwnRef = useForkRef(focusVisibleRef, buttonRef);
299 const handleRef = useForkRef(ref, handleOwnRef);
300
301 if (process.env.NODE_ENV !== 'production') {
302 // eslint-disable-next-line react-hooks/rules-of-hooks
303 React.useEffect(() => {
304 if (enableTouchRipple && !rippleRef.current) {
305 console.error(['MUI: The `component` prop provided to ButtonBase is invalid.', 'Please make sure the children prop is rendered in this custom component.'].join('\n'));
306 }
307 }, [enableTouchRipple]);
308 }
309
310 const ownerState = _extends({}, props, {
311 centerRipple,
312 component,
313 disabled,
314 disableRipple,
315 disableTouchRipple,
316 focusRipple,
317 tabIndex,
318 focusVisible
319 });
320
321 const classes = useUtilityClasses(ownerState);
322 return /*#__PURE__*/_jsxs(ButtonBaseRoot, _extends({
323 as: ComponentProp,
324 className: clsx(classes.root, className),
325 ownerState: ownerState,
326 onBlur: handleBlur,
327 onClick: onClick,
328 onContextMenu: handleContextMenu,
329 onFocus: handleFocus,
330 onKeyDown: handleKeyDown,
331 onKeyUp: handleKeyUp,
332 onMouseDown: handleMouseDown,
333 onMouseLeave: handleMouseLeave,
334 onMouseUp: handleMouseUp,
335 onDragLeave: handleDragLeave,
336 onTouchEnd: handleTouchEnd,
337 onTouchMove: handleTouchMove,
338 onTouchStart: handleTouchStart,
339 ref: handleRef,
340 tabIndex: disabled ? -1 : tabIndex,
341 type: type
342 }, buttonProps, other, {
343 children: [children, enableTouchRipple ?
344 /*#__PURE__*/
345
346 /* TouchRipple is only needed client-side, x2 boost on the server. */
347 _jsx(TouchRipple, _extends({
348 ref: handleRippleRef,
349 center: centerRipple
350 }, TouchRippleProps)) : null]
351 }));
352});
353process.env.NODE_ENV !== "production" ? ButtonBase.propTypes
354/* remove-proptypes */
355= {
356 // ----------------------------- Warning --------------------------------
357 // | These PropTypes are generated from the TypeScript type definitions |
358 // | To update them edit the d.ts file and run "yarn proptypes" |
359 // ----------------------------------------------------------------------
360
361 /**
362 * A ref for imperative actions.
363 * It currently only supports `focusVisible()` action.
364 */
365 action: refType,
366
367 /**
368 * If `true`, the ripples are centered.
369 * They won't start at the cursor interaction position.
370 * @default false
371 */
372 centerRipple: PropTypes.bool,
373
374 /**
375 * The content of the component.
376 */
377 children: PropTypes.node,
378
379 /**
380 * Override or extend the styles applied to the component.
381 */
382 classes: PropTypes.object,
383
384 /**
385 * @ignore
386 */
387 className: PropTypes.string,
388
389 /**
390 * The component used for the root node.
391 * Either a string to use a HTML element or a component.
392 */
393 component: elementTypeAcceptingRef,
394
395 /**
396 * If `true`, the component is disabled.
397 * @default false
398 */
399 disabled: PropTypes.bool,
400
401 /**
402 * If `true`, the ripple effect is disabled.
403 *
404 * ⚠️ Without a ripple there is no styling for :focus-visible by default. Be sure
405 * to highlight the element by applying separate styles with the `.Mui-focusVisible` class.
406 * @default false
407 */
408 disableRipple: PropTypes.bool,
409
410 /**
411 * If `true`, the touch ripple effect is disabled.
412 * @default false
413 */
414 disableTouchRipple: PropTypes.bool,
415
416 /**
417 * If `true`, the base button will have a keyboard focus ripple.
418 * @default false
419 */
420 focusRipple: PropTypes.bool,
421
422 /**
423 * This prop can help identify which element has keyboard focus.
424 * The class name will be applied when the element gains the focus through keyboard interaction.
425 * It's a polyfill for the [CSS :focus-visible selector](https://drafts.csswg.org/selectors-4/#the-focus-visible-pseudo).
426 * The rationale for using this feature [is explained here](https://github.com/WICG/focus-visible/blob/HEAD/explainer.md).
427 * A [polyfill can be used](https://github.com/WICG/focus-visible) to apply a `focus-visible` class to other components
428 * if needed.
429 */
430 focusVisibleClassName: PropTypes.string,
431
432 /**
433 * @ignore
434 */
435 href: PropTypes
436 /* @typescript-to-proptypes-ignore */
437 .any,
438
439 /**
440 * The component used to render a link when the `href` prop is provided.
441 * @default 'a'
442 */
443 LinkComponent: PropTypes.elementType,
444
445 /**
446 * @ignore
447 */
448 onBlur: PropTypes.func,
449
450 /**
451 * @ignore
452 */
453 onClick: PropTypes.func,
454
455 /**
456 * @ignore
457 */
458 onContextMenu: PropTypes.func,
459
460 /**
461 * @ignore
462 */
463 onDragLeave: PropTypes.func,
464
465 /**
466 * @ignore
467 */
468 onFocus: PropTypes.func,
469
470 /**
471 * Callback fired when the component is focused with a keyboard.
472 * We trigger a `onFocus` callback too.
473 */
474 onFocusVisible: PropTypes.func,
475
476 /**
477 * @ignore
478 */
479 onKeyDown: PropTypes.func,
480
481 /**
482 * @ignore
483 */
484 onKeyUp: PropTypes.func,
485
486 /**
487 * @ignore
488 */
489 onMouseDown: PropTypes.func,
490
491 /**
492 * @ignore
493 */
494 onMouseLeave: PropTypes.func,
495
496 /**
497 * @ignore
498 */
499 onMouseUp: PropTypes.func,
500
501 /**
502 * @ignore
503 */
504 onTouchEnd: PropTypes.func,
505
506 /**
507 * @ignore
508 */
509 onTouchMove: PropTypes.func,
510
511 /**
512 * @ignore
513 */
514 onTouchStart: PropTypes.func,
515
516 /**
517 * The system prop that allows defining system overrides as well as additional CSS styles.
518 */
519 sx: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.func, PropTypes.object, PropTypes.bool])), PropTypes.func, PropTypes.object]),
520
521 /**
522 * @default 0
523 */
524 tabIndex: PropTypes.number,
525
526 /**
527 * Props applied to the `TouchRipple` element.
528 */
529 TouchRippleProps: PropTypes.object,
530
531 /**
532 * A ref that points to the `TouchRipple` element.
533 */
534 touchRippleRef: PropTypes.oneOfType([PropTypes.func, PropTypes.shape({
535 current: PropTypes.shape({
536 pulsate: PropTypes.func.isRequired,
537 start: PropTypes.func.isRequired,
538 stop: PropTypes.func.isRequired
539 })
540 })]),
541
542 /**
543 * @ignore
544 */
545 type: PropTypes.oneOfType([PropTypes.oneOf(['button', 'reset', 'submit']), PropTypes.string])
546} : void 0;
547export default ButtonBase;
\No newline at end of file