UNPKG

22.3 kBJavaScriptView Raw
1"use strict";
2
3var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
5Object.defineProperty(exports, "__esModule", {
6 value: true
7});
8exports.testReset = testReset;
9exports.default = exports.styles = void 0;
10
11var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends"));
12
13var _objectWithoutProperties2 = _interopRequireDefault(require("@babel/runtime/helpers/objectWithoutProperties"));
14
15var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
16
17var _react = _interopRequireDefault(require("react"));
18
19var _reactDom = _interopRequireDefault(require("react-dom"));
20
21var _propTypes = _interopRequireDefault(require("prop-types"));
22
23var _clsx = _interopRequireDefault(require("clsx"));
24
25var _utils = require("@material-ui/utils");
26
27var _colorManipulator = require("../styles/colorManipulator");
28
29var _withStyles = _interopRequireDefault(require("../styles/withStyles"));
30
31var _capitalize = _interopRequireDefault(require("../utils/capitalize"));
32
33var _Grow = _interopRequireDefault(require("../Grow"));
34
35var _Popper = _interopRequireDefault(require("../Popper"));
36
37var _useForkRef = _interopRequireDefault(require("../utils/useForkRef"));
38
39var _setRef = _interopRequireDefault(require("../utils/setRef"));
40
41var _focusVisible = require("../utils/focusVisible");
42
43var _useTheme = _interopRequireDefault(require("../styles/useTheme"));
44
45function round(value) {
46 return Math.round(value * 1e5) / 1e5;
47}
48
49function arrowGenerator() {
50 return {
51 '&[x-placement*="bottom"] $arrow': {
52 flip: false,
53 top: 0,
54 left: 0,
55 marginTop: '-0.95em',
56 marginLeft: 4,
57 marginRight: 4,
58 width: '2em',
59 height: '1em',
60 '&::before': {
61 flip: false,
62 borderWidth: '0 1em 1em 1em',
63 borderColor: 'transparent transparent currentcolor transparent'
64 }
65 },
66 '&[x-placement*="top"] $arrow': {
67 flip: false,
68 bottom: 0,
69 left: 0,
70 marginBottom: '-0.95em',
71 marginLeft: 4,
72 marginRight: 4,
73 width: '2em',
74 height: '1em',
75 '&::before': {
76 flip: false,
77 borderWidth: '1em 1em 0 1em',
78 borderColor: 'currentcolor transparent transparent transparent'
79 }
80 },
81 '&[x-placement*="right"] $arrow': {
82 flip: false,
83 left: 0,
84 marginLeft: '-0.95em',
85 marginTop: 4,
86 marginBottom: 4,
87 height: '2em',
88 width: '1em',
89 '&::before': {
90 flip: false,
91 borderWidth: '1em 1em 1em 0',
92 borderColor: 'transparent currentcolor transparent transparent'
93 }
94 },
95 '&[x-placement*="left"] $arrow': {
96 flip: false,
97 right: 0,
98 marginRight: '-0.95em',
99 marginTop: 4,
100 marginBottom: 4,
101 height: '2em',
102 width: '1em',
103 '&::before': {
104 flip: false,
105 borderWidth: '1em 0 1em 1em',
106 borderColor: 'transparent transparent transparent currentcolor'
107 }
108 }
109 };
110}
111
112var styles = function styles(theme) {
113 return {
114 /* Styles applied to the Popper component. */
115 popper: {
116 zIndex: theme.zIndex.tooltip,
117 pointerEvents: 'none',
118 flip: false // disable jss-rtl plugin
119
120 },
121
122 /* Styles applied to the Popper component if `interactive={true}`. */
123 popperInteractive: {
124 pointerEvents: 'auto'
125 },
126
127 /* Styles applied to the Popper component if `arrow={true}`. */
128 popperArrow: arrowGenerator(),
129
130 /* Styles applied to the tooltip (label wrapper) element. */
131 tooltip: {
132 backgroundColor: (0, _colorManipulator.fade)(theme.palette.grey[700], 0.9),
133 borderRadius: theme.shape.borderRadius,
134 color: theme.palette.common.white,
135 fontFamily: theme.typography.fontFamily,
136 padding: '4px 8px',
137 fontSize: theme.typography.pxToRem(10),
138 lineHeight: "".concat(round(14 / 10), "em"),
139 maxWidth: 300,
140 wordWrap: 'break-word',
141 fontWeight: theme.typography.fontWeightMedium
142 },
143
144 /* Styles applied to the tooltip (label wrapper) element if `arrow={true}`. */
145 tooltipArrow: {
146 position: 'relative',
147 margin: '0'
148 },
149
150 /* Styles applied to the arrow element. */
151 arrow: {
152 position: 'absolute',
153 fontSize: 6,
154 color: (0, _colorManipulator.fade)(theme.palette.grey[700], 0.9),
155 '&::before': {
156 content: '""',
157 margin: 'auto',
158 display: 'block',
159 width: 0,
160 height: 0,
161 borderStyle: 'solid'
162 }
163 },
164
165 /* Styles applied to the tooltip (label wrapper) element if the tooltip is opened by touch. */
166 touch: {
167 padding: '8px 16px',
168 fontSize: theme.typography.pxToRem(14),
169 lineHeight: "".concat(round(16 / 14), "em"),
170 fontWeight: theme.typography.fontWeightRegular
171 },
172
173 /* Styles applied to the tooltip (label wrapper) element if `placement` contains "left". */
174 tooltipPlacementLeft: (0, _defineProperty2.default)({
175 transformOrigin: 'right center',
176 margin: '0 24px '
177 }, theme.breakpoints.up('sm'), {
178 margin: '0 14px'
179 }),
180
181 /* Styles applied to the tooltip (label wrapper) element if `placement` contains "right". */
182 tooltipPlacementRight: (0, _defineProperty2.default)({
183 transformOrigin: 'left center',
184 margin: '0 24px'
185 }, theme.breakpoints.up('sm'), {
186 margin: '0 14px'
187 }),
188
189 /* Styles applied to the tooltip (label wrapper) element if `placement` contains "top". */
190 tooltipPlacementTop: (0, _defineProperty2.default)({
191 transformOrigin: 'center bottom',
192 margin: '24px 0'
193 }, theme.breakpoints.up('sm'), {
194 margin: '14px 0'
195 }),
196
197 /* Styles applied to the tooltip (label wrapper) element if `placement` contains "bottom". */
198 tooltipPlacementBottom: (0, _defineProperty2.default)({
199 transformOrigin: 'center top',
200 margin: '24px 0'
201 }, theme.breakpoints.up('sm'), {
202 margin: '14px 0'
203 })
204 };
205};
206
207exports.styles = styles;
208var hystersisOpen = false;
209var hystersisTimer = null;
210
211function testReset() {
212 hystersisOpen = false;
213 clearTimeout(hystersisTimer);
214}
215
216var Tooltip = _react.default.forwardRef(function Tooltip(props, ref) {
217 var _props$arrow = props.arrow,
218 arrow = _props$arrow === void 0 ? false : _props$arrow,
219 children = props.children,
220 classes = props.classes,
221 _props$disableFocusLi = props.disableFocusListener,
222 disableFocusListener = _props$disableFocusLi === void 0 ? false : _props$disableFocusLi,
223 _props$disableHoverLi = props.disableHoverListener,
224 disableHoverListener = _props$disableHoverLi === void 0 ? false : _props$disableHoverLi,
225 _props$disableTouchLi = props.disableTouchListener,
226 disableTouchListener = _props$disableTouchLi === void 0 ? false : _props$disableTouchLi,
227 _props$enterDelay = props.enterDelay,
228 enterDelay = _props$enterDelay === void 0 ? 0 : _props$enterDelay,
229 _props$enterTouchDela = props.enterTouchDelay,
230 enterTouchDelay = _props$enterTouchDela === void 0 ? 700 : _props$enterTouchDela,
231 idProp = props.id,
232 _props$interactive = props.interactive,
233 interactive = _props$interactive === void 0 ? false : _props$interactive,
234 _props$leaveDelay = props.leaveDelay,
235 leaveDelay = _props$leaveDelay === void 0 ? 0 : _props$leaveDelay,
236 _props$leaveTouchDela = props.leaveTouchDelay,
237 leaveTouchDelay = _props$leaveTouchDela === void 0 ? 1500 : _props$leaveTouchDela,
238 onClose = props.onClose,
239 onOpen = props.onOpen,
240 openProp = props.open,
241 _props$placement = props.placement,
242 placement = _props$placement === void 0 ? 'bottom' : _props$placement,
243 PopperProps = props.PopperProps,
244 title = props.title,
245 _props$TransitionComp = props.TransitionComponent,
246 TransitionComponent = _props$TransitionComp === void 0 ? _Grow.default : _props$TransitionComp,
247 TransitionProps = props.TransitionProps,
248 other = (0, _objectWithoutProperties2.default)(props, ["arrow", "children", "classes", "disableFocusListener", "disableHoverListener", "disableTouchListener", "enterDelay", "enterTouchDelay", "id", "interactive", "leaveDelay", "leaveTouchDelay", "onClose", "onOpen", "open", "placement", "PopperProps", "title", "TransitionComponent", "TransitionProps"]);
249 var theme = (0, _useTheme.default)();
250
251 var _React$useState = _react.default.useState(),
252 childNode = _React$useState[0],
253 setChildNode = _React$useState[1];
254
255 var _React$useState2 = _react.default.useState(null),
256 arrowRef = _React$useState2[0],
257 setArrowRef = _React$useState2[1];
258
259 var ignoreNonTouchEvents = _react.default.useRef(false);
260
261 var closeTimer = _react.default.useRef();
262
263 var enterTimer = _react.default.useRef();
264
265 var leaveTimer = _react.default.useRef();
266
267 var touchTimer = _react.default.useRef();
268
269 var _React$useRef = _react.default.useRef(openProp != null),
270 isControlled = _React$useRef.current;
271
272 var _React$useState3 = _react.default.useState(false),
273 openState = _React$useState3[0],
274 setOpenState = _React$useState3[1];
275
276 var open = isControlled ? openProp : openState;
277
278 if (process.env.NODE_ENV !== 'production') {
279 // eslint-disable-next-line react-hooks/rules-of-hooks
280 _react.default.useEffect(function () {
281 if (isControlled !== (openProp != null)) {
282 console.error(["Material-UI: A component is changing ".concat(isControlled ? 'a ' : 'an un', "controlled Tooltip to be ").concat(isControlled ? 'un' : '', "controlled."), 'Elements should not switch from uncontrolled to controlled (or vice versa).', 'Decide between using a controlled or uncontrolled Tooltip ' + 'element for the lifetime of the component.', 'More info: https://fb.me/react-controlled-components'].join('\n'));
283 }
284 }, [openProp, isControlled]);
285 }
286
287 if (process.env.NODE_ENV !== 'production') {
288 // eslint-disable-next-line react-hooks/rules-of-hooks
289 _react.default.useEffect(function () {
290 if (childNode && childNode.disabled && !isControlled && title !== '' && childNode.tagName.toLowerCase() === 'button') {
291 console.error(['Material-UI: you are providing a disabled `button` child to the Tooltip component.', 'A disabled element does not fire events.', "Tooltip needs to listen to the child element's events to display the title.", '', 'Add a simple wrapper element, such as a `span`.'].join('\n'));
292 }
293 }, [isControlled, title, childNode]);
294 }
295
296 var _React$useState4 = _react.default.useState(),
297 defaultId = _React$useState4[0],
298 setDefaultId = _React$useState4[1];
299
300 var id = idProp || defaultId;
301
302 _react.default.useEffect(function () {
303 if (!open || defaultId) {
304 return;
305 } // Fallback to this default id when possible.
306 // Use the random value for client-side rendering only.
307 // We can't use it server-side.
308
309
310 setDefaultId("mui-tooltip-".concat(Math.round(Math.random() * 1e5)));
311 }, [open, defaultId]);
312
313 _react.default.useEffect(function () {
314 return function () {
315 clearTimeout(closeTimer.current);
316 clearTimeout(enterTimer.current);
317 clearTimeout(leaveTimer.current);
318 clearTimeout(touchTimer.current);
319 };
320 }, []);
321
322 var handleOpen = function handleOpen(event) {
323 clearTimeout(hystersisTimer);
324 hystersisOpen = true; // The mouseover event will trigger for every nested element in the tooltip.
325 // We can skip rerendering when the tooltip is already open.
326 // We are using the mouseover event instead of the mouseenter event to fix a hide/show issue.
327
328 if (!isControlled) {
329 setOpenState(true);
330 }
331
332 if (onOpen) {
333 onOpen(event);
334 }
335 };
336
337 var handleEnter = function handleEnter(event) {
338 var childrenProps = children.props;
339
340 if (event.type === 'mouseover' && childrenProps.onMouseOver && event.currentTarget === childNode) {
341 childrenProps.onMouseOver(event);
342 }
343
344 if (ignoreNonTouchEvents.current && event.type !== 'touchstart') {
345 return;
346 } // Remove the title ahead of time.
347 // We don't want to wait for the next render commit.
348 // We would risk displaying two tooltips at the same time (native + this one).
349
350
351 if (childNode) {
352 childNode.removeAttribute('title');
353 }
354
355 clearTimeout(enterTimer.current);
356 clearTimeout(leaveTimer.current);
357
358 if (enterDelay && !hystersisOpen) {
359 event.persist();
360 enterTimer.current = setTimeout(function () {
361 handleOpen(event);
362 }, enterDelay);
363 } else {
364 handleOpen(event);
365 }
366 };
367
368 var _useIsFocusVisible = (0, _focusVisible.useIsFocusVisible)(),
369 isFocusVisible = _useIsFocusVisible.isFocusVisible,
370 onBlurVisible = _useIsFocusVisible.onBlurVisible,
371 focusVisibleRef = _useIsFocusVisible.ref;
372
373 var _React$useState5 = _react.default.useState(false),
374 childIsFocusVisible = _React$useState5[0],
375 setChildIsFocusVisible = _React$useState5[1];
376
377 var handleBlur = function handleBlur() {
378 if (childIsFocusVisible) {
379 setChildIsFocusVisible(false);
380 onBlurVisible();
381 }
382 };
383
384 var handleFocus = function handleFocus(event) {
385 // Workaround for https://github.com/facebook/react/issues/7769
386 // The autoFocus of React might trigger the event before the componentDidMount.
387 // We need to account for this eventuality.
388 if (!childNode) {
389 setChildNode(event.currentTarget);
390 }
391
392 if (isFocusVisible(event)) {
393 setChildIsFocusVisible(true);
394 handleEnter(event);
395 }
396
397 var childrenProps = children.props;
398
399 if (childrenProps.onFocus && event.currentTarget === childNode) {
400 childrenProps.onFocus(event);
401 }
402 };
403
404 var handleClose = function handleClose(event) {
405 clearTimeout(hystersisTimer);
406 hystersisTimer = setTimeout(function () {
407 hystersisOpen = false;
408 }, 500); // Use 500 ms per https://github.com/reach/reach-ui/blob/3b5319027d763a3082880be887d7a29aee7d3afc/packages/tooltip/src/index.js#L214
409
410 if (!isControlled) {
411 setOpenState(false);
412 }
413
414 if (onClose) {
415 onClose(event);
416 }
417
418 clearTimeout(closeTimer.current);
419 closeTimer.current = setTimeout(function () {
420 ignoreNonTouchEvents.current = false;
421 }, theme.transitions.duration.shortest);
422 };
423
424 var handleLeave = function handleLeave(event) {
425 var childrenProps = children.props;
426
427 if (event.type === 'blur') {
428 if (childrenProps.onBlur && event.currentTarget === childNode) {
429 childrenProps.onBlur(event);
430 }
431
432 handleBlur(event);
433 }
434
435 if (event.type === 'mouseleave' && childrenProps.onMouseLeave && event.currentTarget === childNode) {
436 childrenProps.onMouseLeave(event);
437 }
438
439 clearTimeout(enterTimer.current);
440 clearTimeout(leaveTimer.current);
441 event.persist();
442 leaveTimer.current = setTimeout(function () {
443 handleClose(event);
444 }, leaveDelay);
445 };
446
447 var handleTouchStart = function handleTouchStart(event) {
448 ignoreNonTouchEvents.current = true;
449 var childrenProps = children.props;
450
451 if (childrenProps.onTouchStart) {
452 childrenProps.onTouchStart(event);
453 }
454
455 clearTimeout(leaveTimer.current);
456 clearTimeout(closeTimer.current);
457 clearTimeout(touchTimer.current);
458 event.persist();
459 touchTimer.current = setTimeout(function () {
460 handleEnter(event);
461 }, enterTouchDelay);
462 };
463
464 var handleTouchEnd = function handleTouchEnd(event) {
465 if (children.props.onTouchEnd) {
466 children.props.onTouchEnd(event);
467 }
468
469 clearTimeout(touchTimer.current);
470 clearTimeout(leaveTimer.current);
471 event.persist();
472 leaveTimer.current = setTimeout(function () {
473 handleClose(event);
474 }, leaveTouchDelay);
475 };
476
477 var handleUseRef = (0, _useForkRef.default)(setChildNode, ref);
478 var handleFocusRef = (0, _useForkRef.default)(focusVisibleRef, handleUseRef); // can be removed once we drop support for non ref forwarding class components
479
480 var handleOwnRef = _react.default.useCallback(function (instance) {
481 // #StrictMode ready
482 (0, _setRef.default)(handleFocusRef, _reactDom.default.findDOMNode(instance));
483 }, [handleFocusRef]);
484
485 var handleRef = (0, _useForkRef.default)(children.ref, handleOwnRef); // There is no point in displaying an empty tooltip.
486
487 if (title === '') {
488 open = false;
489 } // For accessibility and SEO concerns, we render the title to the DOM node when
490 // the tooltip is hidden. However, we have made a tradeoff when
491 // `disableHoverListener` is set. This title logic is disabled.
492 // It's allowing us to keep the implementation size minimal.
493 // We are open to change the tradeoff.
494
495
496 var shouldShowNativeTitle = !open && !disableHoverListener;
497 var childrenProps = (0, _extends2.default)({
498 'aria-describedby': open ? id : null,
499 title: shouldShowNativeTitle && typeof title === 'string' ? title : null
500 }, other, {}, children.props, {
501 className: (0, _clsx.default)(other.className, children.props.className)
502 });
503
504 if (!disableTouchListener) {
505 childrenProps.onTouchStart = handleTouchStart;
506 childrenProps.onTouchEnd = handleTouchEnd;
507 }
508
509 if (!disableHoverListener) {
510 childrenProps.onMouseOver = handleEnter;
511 childrenProps.onMouseLeave = handleLeave;
512 }
513
514 if (!disableFocusListener) {
515 childrenProps.onFocus = handleFocus;
516 childrenProps.onBlur = handleLeave;
517 }
518
519 var interactiveWrapperListeners = interactive ? {
520 onMouseOver: childrenProps.onMouseOver,
521 onMouseLeave: childrenProps.onMouseLeave,
522 onFocus: childrenProps.onFocus,
523 onBlur: childrenProps.onBlur
524 } : {};
525
526 if (process.env.NODE_ENV !== 'production') {
527 if (children.props.title) {
528 console.error(['Material-UI: you have provided a `title` prop to the child of <Tooltip />.', "Remove this title prop `".concat(children.props.title, "` or the Tooltip component.")].join('\n'));
529 }
530 }
531
532 return _react.default.createElement(_react.default.Fragment, null, _react.default.cloneElement(children, (0, _extends2.default)({
533 ref: handleRef
534 }, childrenProps)), _react.default.createElement(_Popper.default, (0, _extends2.default)({
535 className: (0, _clsx.default)(classes.popper, interactive && classes.popperInteractive, arrow && classes.popperArrow),
536 placement: placement,
537 anchorEl: childNode,
538 open: childNode ? open : false,
539 id: childrenProps['aria-describedby'],
540 transition: true,
541 popperOptions: {
542 modifiers: {
543 arrow: {
544 enabled: Boolean(arrowRef),
545 element: arrowRef
546 }
547 }
548 }
549 }, interactiveWrapperListeners, PopperProps), function (_ref) {
550 var placementInner = _ref.placement,
551 TransitionPropsInner = _ref.TransitionProps;
552 return _react.default.createElement(TransitionComponent, (0, _extends2.default)({
553 timeout: theme.transitions.duration.shorter
554 }, TransitionPropsInner, TransitionProps), _react.default.createElement("div", {
555 className: (0, _clsx.default)(classes.tooltip, classes["tooltipPlacement".concat((0, _capitalize.default)(placementInner.split('-')[0]))], ignoreNonTouchEvents.current && classes.touch, arrow && classes.tooltipArrow)
556 }, title, arrow ? _react.default.createElement("span", {
557 className: classes.arrow,
558 ref: setArrowRef
559 }) : null));
560 }));
561});
562
563process.env.NODE_ENV !== "production" ? Tooltip.propTypes = {
564 /**
565 * If `true`, adds an arrow to the tooltip.
566 */
567 arrow: _propTypes.default.bool,
568
569 /**
570 * Tooltip reference element.
571 */
572 children: _utils.elementAcceptingRef.isRequired,
573
574 /**
575 * Override or extend the styles applied to the component.
576 * See [CSS API](#css) below for more details.
577 */
578 classes: _propTypes.default.object.isRequired,
579
580 /**
581 * Do not respond to focus events.
582 */
583 disableFocusListener: _propTypes.default.bool,
584
585 /**
586 * Do not respond to hover events.
587 */
588 disableHoverListener: _propTypes.default.bool,
589
590 /**
591 * Do not respond to long press touch events.
592 */
593 disableTouchListener: _propTypes.default.bool,
594
595 /**
596 * The number of milliseconds to wait before showing the tooltip.
597 * This prop won't impact the enter touch delay (`enterTouchDelay`).
598 */
599 enterDelay: _propTypes.default.number,
600
601 /**
602 * The number of milliseconds a user must touch the element before showing the tooltip.
603 */
604 enterTouchDelay: _propTypes.default.number,
605
606 /**
607 * This prop is used to help implement the accessibility logic.
608 * If you don't provide this prop. It falls back to a randomly generated id.
609 */
610 id: _propTypes.default.string,
611
612 /**
613 * Makes a tooltip interactive, i.e. will not close when the user
614 * hovers over the tooltip before the `leaveDelay` is expired.
615 */
616 interactive: _propTypes.default.bool,
617
618 /**
619 * The number of milliseconds to wait before hiding the tooltip.
620 * This prop won't impact the leave touch delay (`leaveTouchDelay`).
621 */
622 leaveDelay: _propTypes.default.number,
623
624 /**
625 * The number of milliseconds after the user stops touching an element before hiding the tooltip.
626 */
627 leaveTouchDelay: _propTypes.default.number,
628
629 /**
630 * Callback fired when the component requests to be closed.
631 *
632 * @param {object} event The event source of the callback.
633 */
634 onClose: _propTypes.default.func,
635
636 /**
637 * Callback fired when the component requests to be open.
638 *
639 * @param {object} event The event source of the callback.
640 */
641 onOpen: _propTypes.default.func,
642
643 /**
644 * If `true`, the tooltip is shown.
645 */
646 open: _propTypes.default.bool,
647
648 /**
649 * Tooltip placement.
650 */
651 placement: _propTypes.default.oneOf(['bottom-end', 'bottom-start', 'bottom', 'left-end', 'left-start', 'left', 'right-end', 'right-start', 'right', 'top-end', 'top-start', 'top']),
652
653 /**
654 * Props applied to the [`Popper`](/api/popper/) element.
655 */
656 PopperProps: _propTypes.default.object,
657
658 /**
659 * Tooltip title. Zero-length titles string are never displayed.
660 */
661 title: _propTypes.default.node.isRequired,
662
663 /**
664 * The component used for the transition.
665 */
666 TransitionComponent: _propTypes.default.elementType,
667
668 /**
669 * Props applied to the `Transition` element.
670 */
671 TransitionProps: _propTypes.default.object
672} : void 0;
673
674var _default = (0, _withStyles.default)(styles, {
675 name: 'MuiTooltip'
676})(Tooltip);
677
678exports.default = _default;
\No newline at end of file