UNPKG

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