Skip to content

Commit 6add4b6

Browse files
committedSep 3, 2018
Merge remote-tracking branch 'upstream/master'
# Conflicts: # src/index.js
2 parents ab9c109 + ce365fe commit 6add4b6

11 files changed

+2826
-2669
lines changed
 

‎README.md

+1
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ className | data-class | String | | extra custom class, can use !importan
6767
html | data-html | Bool | true, false | `<p data-tip="<p>HTML tooltip</p>" data-html={true}></p>` or `<ReactTooltip html={true} />`
6868
delayHide | data-delay-hide | Number | | `<p data-tip="tooltip" data-delay-hide='1000'></p>` or `<ReactTooltip delayHide={1000} />`
6969
delayShow | data-delay-show | Number | | `<p data-tip="tooltip" data-delay-show='1000'></p>` or `<ReactTooltip delayShow={1000} />`
70+
delayUpdate | data-delay-update | Number | | `<p data-tip="tooltip" data-delay-update='1000'></p>` or `<ReactTooltip delayUpdate={1000} />` Sets a delay in calling getContent if the tooltip is already shown and you mouse over another target
7071
insecure | null | Bool | true, false | Whether to inject the style header into the page dynamically (violates CSP style-src but is a convenient default)
7172
border | data-border | Bool | true, false | Add one pixel white border
7273
getContent | null | Func or Array | (dataTip) => {}, [(dataTip) => {}, Interval] | Generate the tip content dynamically

‎example/src/index.js

+61
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,67 @@ class Test extends React.Component {
323323
</div>
324324
</pre>
325325
</div>
326+
<div className="section">
327+
<h4 className='title'>Demonstrate using mouse in tooltip. </h4>
328+
<p>Notice that the tooltip delays going away so you can get your mouse in it. You must set delayUpdate and delayHide for the tooltip to stay long enough to get your mouse over it.</p>
329+
<p className="sub-title"></p>
330+
<div className="example-jsx">
331+
<div className="block" >
332+
<a data-for='soclose' data-tip='1'>1 (❂‿❂)</a>
333+
</div>
334+
<div className="block">
335+
<a data-for='soclose' data-tip='2'>2 (❂‿❂)</a>
336+
</div>
337+
<div className="block" >
338+
<a data-for='soclose' data-tip='3'>3(❂‿❂)</a>
339+
</div>
340+
<div className="block">
341+
<a data-for='soclose' data-tip='4'>4(❂‿❂)</a>
342+
</div>
343+
<div className="block" >
344+
<a data-for='soclose' data-tip='5'>5(❂‿❂)</a>
345+
</div>
346+
<div className="block">
347+
<a data-for='soclose' data-tip='6'>6(❂‿❂)</a>
348+
</div>
349+
<div className="block" >
350+
<a data-for='soclose' data-tip='7'>7(❂‿❂)</a>
351+
</div>
352+
<div className="block">
353+
<a data-for='soclose' data-tip='8'>8(❂‿❂)</a>
354+
</div>
355+
356+
<ReactTooltip id='soclose'
357+
getContent={(dataTip) => <div><h3>This little buddy is {dataTip}</h3><p>Put mouse here</p></div> }
358+
effect='solid'
359+
delayHide={500}
360+
delayShow={500}
361+
delayUpdate={500}
362+
place={'right'}
363+
border={true}
364+
type={'light'}
365+
366+
/>
367+
</div>
368+
<br />
369+
<pre className='example-pre'>
370+
<div>
371+
<p>{"<a data-for='soclose' data-tip='sooooo cute'>(❂‿❂)</a>"}<p/>{"<a data-for='soclose' data-tip='2'>(❂‿❂)</a>..."}<p/>{
372+
"<a data-for='soclose' data-tip='really high'>(❂‿❂)</a>\n" +
373+
"<ReactTooltip id='soclose'\n" +
374+
" getContent={(dataTip) => \n"}{
375+
" <div><h3>This little buddy is {dataTip}</h3><p>Put mouse here</p></div> }\n" +
376+
" effect='solid'\n" +
377+
" delayHide={500}\n" +
378+
" delayShow={500}\n" +
379+
" delayUpdate={500}\n" +
380+
" place={'right'}\n" +
381+
" border={true}\n" +
382+
" type={'light'}"}</p>
383+
</div>
384+
</pre>
385+
</div>
386+
326387
</section>
327388
</div>
328389
)

‎example/src/index.scss

+12
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,18 @@ html, body{
157157
height: 0;
158158
visibility: hidden;
159159
}
160+
.block {
161+
float: left;
162+
$width: 55px;
163+
164+
a {
165+
text-align: center;
166+
width: $width;
167+
height: $width;
168+
border: 1px solid #999;
169+
border-radius: 0px
170+
}
171+
}
160172
.side {
161173
width: 50%;
162174
float: left;

‎package-lock.json

+2,438-2,438
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎src/index.js

+117-46
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ class ReactTooltip extends React.Component {
4444
id: PropTypes.string,
4545
html: PropTypes.bool,
4646
delayHide: PropTypes.number,
47+
delayUpdate: PropTypes.number,
4748
delayShow: PropTypes.number,
4849
event: PropTypes.string,
4950
eventOff: PropTypes.string,
@@ -101,12 +102,14 @@ class ReactTooltip extends React.Component {
101102
'globalRebuild',
102103
'globalShow',
103104
'globalHide',
104-
'onWindowResize'
105+
'onWindowResize',
106+
'mouseOnToolTip'
105107
])
106108

107109
this.mount = true
108110
this.delayShowLoop = null
109111
this.delayHideLoop = null
112+
this.delayReshow = null
110113
this.intervalUpdateContent = null
111114
}
112115

@@ -150,6 +153,22 @@ class ReactTooltip extends React.Component {
150153
this.unbindWindowEvents()
151154
}
152155

156+
/**
157+
* Return if the mouse is on the tooltip.
158+
* @returns {boolean} true - mouse is on the tooltip
159+
*/
160+
mouseOnToolTip () {
161+
const {show} = this.state
162+
163+
if (show && this.tooltipRef) {
164+
/* old IE work around */
165+
if (!this.tooltipRef.matches) {
166+
this.tooltipRef.matches = this.tooltipRef.msMatchesSelector
167+
}
168+
return this.tooltipRef.matches(':hover')
169+
}
170+
return false
171+
}
153172
/**
154173
* Pick out corresponded target elements
155174
*/
@@ -277,66 +296,82 @@ class ReactTooltip extends React.Component {
277296
scrollHide = this.props.scrollHide
278297
}
279298

280-
// To prevent previously created timers from triggering
281-
this.clearTimer()
282-
283299
// Make sure the correct place is set
284300
let desiredPlace = e.currentTarget.getAttribute('data-place') || this.props.place || 'top'
285301
let effect = switchToSolid && 'solid' || this.getEffect(e.currentTarget)
286302
let offset = e.currentTarget.getAttribute('data-offset') || this.props.offset || {}
287303
let result = getPosition(e, e.currentTarget, ReactDOM.findDOMNode(this), desiredPlace, desiredPlace, effect, offset)
304+
let place = result.isNewState ? result.newState.place : desiredPlace
288305

289-
this.setState({
290-
originTooltip: originTooltip,
291-
isMultiline: isMultiline,
292-
desiredPlace: desiredPlace,
293-
place: result.isNewState ? result.newState.place : desiredPlace,
294-
type: e.currentTarget.getAttribute('data-type') || this.props.type || 'dark',
295-
effect: effect,
296-
offset: offset,
297-
html: e.currentTarget.getAttribute('data-html')
298-
? e.currentTarget.getAttribute('data-html') === 'true'
299-
: (this.props.html || false),
300-
delayShow: e.currentTarget.getAttribute('data-delay-show') || this.props.delayShow || 0,
301-
delayHide: e.currentTarget.getAttribute('data-delay-hide') || this.props.delayHide || 0,
302-
border: e.currentTarget.getAttribute('data-border')
303-
? e.currentTarget.getAttribute('data-border') === 'true'
304-
: (this.props.border || false),
305-
extraClass: e.currentTarget.getAttribute('data-class') || this.props.class || this.props.className || '',
306-
disable: e.currentTarget.getAttribute('data-tip-disable')
307-
? e.currentTarget.getAttribute('data-tip-disable') === 'true'
308-
: (this.props.disable || false),
309-
currentTarget: e.currentTarget
310-
}, () => {
311-
if (scrollHide) this.addScrollListener(this.state.currentTarget)
312-
this.updateTooltip(e)
313-
314-
if (getContent && Array.isArray(getContent)) {
315-
this.intervalUpdateContent = setInterval(() => {
316-
if (this.mount) {
317-
const {getContent} = this.props
318-
const placeholder = getTipContent(originTooltip, '', getContent[0](), isMultiline)
319-
const isEmptyTip = this.isEmptyTip(placeholder)
320-
this.setState({
321-
isEmptyTip
322-
})
323-
this.updatePosition()
324-
}
325-
}, getContent[1])
326-
}
327-
})
306+
// To prevent previously created timers from triggering
307+
this.clearTimer()
308+
309+
var target = e.currentTarget
310+
311+
var reshowDelay = this.state.show ? target.getAttribute('data-delay-update') || this.props.delayUpdate : 0
312+
313+
var self = this
314+
315+
var updateState = function updateState () {
316+
self.setState({
317+
originTooltip: originTooltip,
318+
isMultiline: isMultiline,
319+
desiredPlace: desiredPlace,
320+
place: place,
321+
type: target.getAttribute('data-type') || self.props.type || 'dark',
322+
effect: effect,
323+
offset: offset,
324+
html: target.getAttribute('data-html') ? target.getAttribute('data-html') === 'true' : self.props.html || false,
325+
delayShow: target.getAttribute('data-delay-show') || self.props.delayShow || 0,
326+
delayHide: target.getAttribute('data-delay-hide') || self.props.delayHide || 0,
327+
delayUpdate: target.getAttribute('data-delay-update') || self.props.delayUpdate || 0,
328+
border: target.getAttribute('data-border') ? target.getAttribute('data-border') === 'true' : self.props.border || false,
329+
extraClass: target.getAttribute('data-class') || self.props.class || self.props.className || '',
330+
disable: target.getAttribute('data-tip-disable') ? target.getAttribute('data-tip-disable') === 'true' : self.props.disable || false,
331+
currentTarget: target
332+
}, () => {
333+
if (scrollHide) self.addScrollListener(self.state.currentTarget)
334+
self.updateTooltip(e)
335+
336+
if (getContent && Array.isArray(getContent)) {
337+
self.intervalUpdateContent = setInterval(() => {
338+
if (self.mount) {
339+
const {getContent} = self.props
340+
const placeholder = getTipContent(originTooltip, '', getContent[0](), isMultiline)
341+
const isEmptyTip = self.isEmptyTip(placeholder)
342+
self.setState({
343+
isEmptyTip
344+
})
345+
self.updatePosition()
346+
}
347+
}, getContent[1])
348+
}
349+
})
350+
}
351+
352+
// If there is no delay call immediately, don't allow events to get in first.
353+
if (reshowDelay) {
354+
this.delayReshow = setTimeout(updateState, reshowDelay)
355+
} else {
356+
updateState()
357+
}
328358
}
329359

330360
/**
331361
* When mouse hover, updatetooltip
332362
*/
333363
updateTooltip (e) {
334-
const {delayShow, show, disable} = this.state
364+
const {delayShow, disable} = this.state
335365
const {afterShow} = this.props
336366
const placeholder = this.getTooltipContent()
337-
const delayTime = show ? 0 : parseInt(delayShow, 10)
367+
const delayTime = parseInt(delayShow, 10)
338368
const eventTarget = e.currentTarget || e.target
339369

370+
// Check if the mouse is actually over the tooltip, if so don't hide the tooltip
371+
if (this.mouseOnToolTip()) {
372+
return
373+
}
374+
340375
if (this.isEmptyTip(placeholder) || disable) return // if the tooltip is empty, disable the tooltip
341376
const updateState = () => {
342377
if (Array.isArray(placeholder) && placeholder.length > 0 || placeholder) {
@@ -360,6 +395,25 @@ class ReactTooltip extends React.Component {
360395
}
361396
}
362397

398+
/*
399+
* If we're mousing over the tooltip remove it when we leave.
400+
*/
401+
listenForTooltipExit () {
402+
const {show} = this.state
403+
404+
if (show && this.tooltipRef) {
405+
this.tooltipRef.addEventListener('mouseleave', this.hideTooltip)
406+
}
407+
}
408+
409+
removeListenerForTooltipExit () {
410+
const {show} = this.state
411+
412+
if (show && this.tooltipRef) {
413+
this.tooltipRef.removeEventListener('mouseleave', this.hideTooltip)
414+
}
415+
}
416+
363417
/**
364418
* When mouse leave, hide tooltip
365419
*/
@@ -375,8 +429,16 @@ class ReactTooltip extends React.Component {
375429
const isMyElement = targetArray.some(ele => ele === e.currentTarget)
376430
if (!isMyElement || !this.state.show) return
377431
}
432+
378433
const resetState = () => {
379434
const isVisible = this.state.show
435+
// Check if the mouse is actually over the tooltip, if so don't hide the tooltip
436+
if (this.mouseOnToolTip()) {
437+
this.listenForTooltipExit()
438+
return
439+
}
440+
this.removeListenerForTooltipExit()
441+
380442
this.setState({
381443
show: false
382444
}, () => {
@@ -433,6 +495,11 @@ class ReactTooltip extends React.Component {
433495
let tag = document.createElement('style')
434496
tag.id = 'react-tooltip'
435497
tag.innerHTML = cssStyle
498+
/* eslint-disable */
499+
if (typeof __webpack_nonce__ !== 'undefined' && __webpack_nonce__) {
500+
tag.setAttribute('nonce', __webpack_nonce__)
501+
}
502+
/* eslint-enable */
436503
head.insertBefore(tag, head.firstChild)
437504
}
438505
}
@@ -443,6 +510,7 @@ class ReactTooltip extends React.Component {
443510
clearTimer () {
444511
clearTimeout(this.delayShowLoop)
445512
clearTimeout(this.delayHideLoop)
513+
clearTimeout(this.delayReshow)
446514
clearInterval(this.intervalUpdateContent)
447515
}
448516

@@ -463,7 +531,8 @@ class ReactTooltip extends React.Component {
463531
{'type-warning': this.state.type === 'warning'},
464532
{'type-error': this.state.type === 'error'},
465533
{'type-info': this.state.type === 'info'},
466-
{'type-light': this.state.type === 'light'}
534+
{'type-light': this.state.type === 'light'},
535+
{'allow_hover': this.props.delayUpdate}
467536
)
468537

469538
let Wrapper = this.props.wrapper
@@ -475,6 +544,7 @@ class ReactTooltip extends React.Component {
475544
return (
476545
<Wrapper className={`${tooltipClass} ${extraClass}`}
477546
id={this.props.id}
547+
ref={ref => this.tooltipRef = ref}
478548
{...ariaProps}
479549
data-id='tooltip'
480550
dangerouslySetInnerHTML={{__html: placeholder}}/>
@@ -484,6 +554,7 @@ class ReactTooltip extends React.Component {
484554
<Wrapper className={`${tooltipClass} ${extraClass}`}
485555
id={this.props.id}
486556
{...ariaProps}
557+
ref={ref => this.tooltipRef = ref}
487558
data-id='tooltip'>{placeholder}</Wrapper>
488559
)
489560
}

‎src/index.scss

+3
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,9 @@
6767
top: -999em;
6868
visibility: hidden;
6969
z-index: 999;
70+
&.allow_hover {
71+
pointer-events:auto;
72+
}
7073
&:before,
7174
&:after {
7275
content: "";

‎src/style.css

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎src/style.js

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎src/utils/getPosition.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,10 @@
99
* - `effect` {String} float / solid
1010
* - `offset` {Object} the offset to default position
1111
*
12-
* @return {Object
12+
* @return {Object}
1313
* - `isNewState` {Bool} required
1414
* - `newState` {Object}
15-
* - `position` {OBject} {left: {Number}, top: {Number}}
15+
* - `position` {Object} {left: {Number}, top: {Number}}
1616
*/
1717
export default function (e, target, node, place, desiredPlace, effect, offset) {
1818
const {

‎standalone/react-tooltip.js

+188-179
Large diffs are not rendered by default.

‎standalone/react-tooltip.min.js

+2-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)
Please sign in to comment.