Skip to content

Commit

Permalink
feat(tooltip): Add ability to hover on tooltip. Provide optional dela…
Browse files Browse the repository at this point in the history
…y of updating so if the mouse p

issue 411
  • Loading branch information
RobertGaryHPE committed Aug 24, 2018
1 parent f6f8401 commit 79342ce
Show file tree
Hide file tree
Showing 9 changed files with 364 additions and 216 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ className | data-class | String | | extra custom class, can use !importan
html | data-html | Bool | true, false | `<p data-tip="<p>HTML tooltip</p>" data-html={true}></p>` or `<ReactTooltip html={true} />`
delayHide | data-delay-hide | Number | | `<p data-tip="tooltip" data-delay-hide='1000'></p>` or `<ReactTooltip delayHide={1000} />`
delayShow | data-delay-show | Number | | `<p data-tip="tooltip" data-delay-show='1000'></p>` or `<ReactTooltip delayShow={1000} />`
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
insecure | null | Bool | true, false | Whether to inject the style header into the page dynamically (violates CSP style-src but is a convenient default)
border | data-border | Bool | true, false | Add one pixel white border
getContent | null | Func or Array | (dataTip) => {}, [(dataTip) => {}, Interval] | Generate the tip content dynamically
Expand Down
61 changes: 61 additions & 0 deletions example/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,67 @@ class Test extends React.Component {
</div>
</pre>
</div>
<div className="section">
<h4 className='title'>Demonstrate using mouse in tooltip. </h4>
<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>
<p className="sub-title"></p>
<div className="example-jsx">
<div className="block" >
<a data-for='soclose' data-tip='1'>1 (❂‿❂)</a>
</div>
<div className="block">
<a data-for='soclose' data-tip='2'>2 (❂‿❂)</a>
</div>
<div className="block" >
<a data-for='soclose' data-tip='3'>3(❂‿❂)</a>
</div>
<div className="block">
<a data-for='soclose' data-tip='4'>4(❂‿❂)</a>
</div>
<div className="block" >
<a data-for='soclose' data-tip='5'>5(❂‿❂)</a>
</div>
<div className="block">
<a data-for='soclose' data-tip='6'>6(❂‿❂)</a>
</div>
<div className="block" >
<a data-for='soclose' data-tip='7'>7(❂‿❂)</a>
</div>
<div className="block">
<a data-for='soclose' data-tip='8'>8(❂‿❂)</a>
</div>

<ReactTooltip id='soclose'
getContent={(dataTip) => <div><h3>This little buddy is {dataTip}</h3><p>Put mouse here</p></div> }
effect='solid'
delayHide={500}
delayShow={500}
delayUpdate={500}
place={'right'}
border={true}
type={'light'}

/>
</div>
<br />
<pre className='example-pre'>
<div>
<p>{"<a data-for='soclose' data-tip='sooooo cute'>(❂‿❂)</a>"}<p/>{"<a data-for='soclose' data-tip='2'>(❂‿❂)</a>..."}<p/>{
"<a data-for='soclose' data-tip='really high'>(❂‿❂)</a>\n" +
"<ReactTooltip id='soclose'\n" +
" getContent={(dataTip) => \n"}{
" <div><h3>This little buddy is {dataTip}</h3><p>Put mouse here</p></div> }\n" +
" effect='solid'\n" +
" delayHide={500}\n" +
" delayShow={500}\n" +
" delayUpdate={500}\n" +
" place={'right'}\n" +
" border={true}\n" +
" type={'light'}"}</p>
</div>
</pre>
</div>

</section>
</div>
)
Expand Down
12 changes: 12 additions & 0 deletions example/src/index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,18 @@ html, body{
height: 0;
visibility: hidden;
}
.block {
float: left;
$width: 55px;

a {
text-align: center;
width: $width;
height: $width;
border: 1px solid #999;
border-radius: 0px
}
}
.side {
width: 50%;
float: left;
Expand Down
151 changes: 108 additions & 43 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ class ReactTooltip extends React.Component {
id: PropTypes.string,
html: PropTypes.bool,
delayHide: PropTypes.number,
delayUpdate: PropTypes.number,
delayShow: PropTypes.number,
event: PropTypes.string,
eventOff: PropTypes.string,
Expand Down Expand Up @@ -101,12 +102,14 @@ class ReactTooltip extends React.Component {
'globalRebuild',
'globalShow',
'globalHide',
'onWindowResize'
'onWindowResize',
'mouseOnToolTip'
])

this.mount = true
this.delayShowLoop = null
this.delayHideLoop = null
this.delayReshow = null
this.intervalUpdateContent = null
}

Expand Down Expand Up @@ -150,6 +153,22 @@ class ReactTooltip extends React.Component {
this.unbindWindowEvents()
}

/**
* Return if the mouse is on the tooltip.
* @returns {boolean} true - mouse is on the tooltip
*/
mouseOnToolTip () {
const {show} = this.state

if (show && this.tooltipRef) {
/* old IE work around */
if (!this.tooltipRef.matches) {
this.tooltipRef.matches = this.tooltipRef.msMatchesSelector
}
return this.tooltipRef.matches(':hover')
}
return false
}
/**
* Pick out corresponded target elements
*/
Expand Down Expand Up @@ -280,57 +299,72 @@ class ReactTooltip extends React.Component {
// To prevent previously created timers from triggering
this.clearTimer()

this.setState({
originTooltip: originTooltip,
isMultiline: isMultiline,
desiredPlace: e.currentTarget.getAttribute('data-place') || this.props.place || 'top',
place: e.currentTarget.getAttribute('data-place') || this.props.place || 'top',
type: e.currentTarget.getAttribute('data-type') || this.props.type || 'dark',
effect: switchToSolid && 'solid' || this.getEffect(e.currentTarget),
offset: e.currentTarget.getAttribute('data-offset') || this.props.offset || {},
html: e.currentTarget.getAttribute('data-html')
? e.currentTarget.getAttribute('data-html') === 'true'
: (this.props.html || false),
delayShow: e.currentTarget.getAttribute('data-delay-show') || this.props.delayShow || 0,
delayHide: e.currentTarget.getAttribute('data-delay-hide') || this.props.delayHide || 0,
border: e.currentTarget.getAttribute('data-border')
? e.currentTarget.getAttribute('data-border') === 'true'
: (this.props.border || false),
extraClass: e.currentTarget.getAttribute('data-class') || this.props.class || this.props.className || '',
disable: e.currentTarget.getAttribute('data-tip-disable')
? e.currentTarget.getAttribute('data-tip-disable') === 'true'
: (this.props.disable || false),
currentTarget: e.currentTarget
}, () => {
if (scrollHide) this.addScrollListener(this.state.currentTarget)
this.updateTooltip(e)

if (getContent && Array.isArray(getContent)) {
this.intervalUpdateContent = setInterval(() => {
if (this.mount) {
const {getContent} = this.props
const placeholder = getTipContent(originTooltip, '', getContent[0](), isMultiline)
const isEmptyTip = this.isEmptyTip(placeholder)
this.setState({
isEmptyTip
})
this.updatePosition()
}
}, getContent[1])
}
})
var target = e.currentTarget

var reshowDelay = this.state.show ? target.getAttribute('data-delay-update') || this.props.delayUpdate : 0

var self = this

var updateState = function updateState () {
self.setState({
originTooltip: originTooltip,
isMultiline: isMultiline,
desiredPlace: target.getAttribute('data-place') || self.props.place || 'top',
place: target.getAttribute('data-place') || self.props.place || 'top',
type: target.getAttribute('data-type') || self.props.type || 'dark',
effect: switchToSolid && 'solid' || self.getEffect(target),
offset: target.getAttribute('data-offset') || self.props.offset || {},
html: target.getAttribute('data-html') ? target.getAttribute('data-html') === 'true' : self.props.html || false,
delayShow: target.getAttribute('data-delay-show') || self.props.delayShow || 0,
delayHide: target.getAttribute('data-delay-hide') || self.props.delayHide || 0,
delayUpdate: target.getAttribute('data-delay-update') || self.props.delayUpdate || 0,
border: target.getAttribute('data-border') ? target.getAttribute('data-border') === 'true' : self.props.border || false,
extraClass: target.getAttribute('data-class') || self.props.class || self.props.className || '',
disable: target.getAttribute('data-tip-disable') ? target.getAttribute('data-tip-disable') === 'true' : self.props.disable || false,
currentTarget: target
}, () => {
if (scrollHide) self.addScrollListener(self.state.currentTarget)
self.updateTooltip(e)

if (getContent && Array.isArray(getContent)) {
this.intervalUpdateContent = setInterval(() => {
if (self.mount) {
const {getContent} = this.props
const placeholder = getTipContent(originTooltip, '', getContent[0](), isMultiline)
const isEmptyTip = this.isEmptyTip(placeholder)
self.setState({
isEmptyTip
})
self.updatePosition()
}
}, getContent[1])
}
})
}

// If there is no delay call immediately, don't allow events to get in first.
if (reshowDelay) {
this.delayReshow = setTimeout(updateState, reshowDelay)
} else {
updateState()
}
}

/**
* When mouse hover, updatetooltip
*/
updateTooltip (e) {
const {delayShow, show, disable} = this.state
const {delayShow, disable} = this.state
const {afterShow} = this.props
const placeholder = this.getTooltipContent()
const delayTime = show ? 0 : parseInt(delayShow, 10)
const delayTime = parseInt(delayShow, 10)
const eventTarget = e.currentTarget || e.target

// Check if the mouse is actually over the tooltip, if so don't hide the tooltip
if (this.mouseOnToolTip()) {
return
}

if (this.isEmptyTip(placeholder) || disable) return // if the tooltip is empty, disable the tooltip
const updateState = () => {
if (Array.isArray(placeholder) && placeholder.length > 0 || placeholder) {
Expand All @@ -354,6 +388,25 @@ class ReactTooltip extends React.Component {
}
}

/*
* If we're mousing over the tooltip remove it when we leave.
*/
listenForTooltipExit () {
const {show} = this.state

if (show && this.tooltipRef) {
this.tooltipRef.addEventListener('mouseleave', this.hideTooltip)
}
}

removeListenerForTooltipExit () {
const {show} = this.state

if (show && this.tooltipRef) {
this.tooltipRef.removeEventListener('mouseleave', this.hideTooltip)
}
}

/**
* When mouse leave, hide tooltip
*/
Expand All @@ -369,8 +422,16 @@ class ReactTooltip extends React.Component {
const isMyElement = targetArray.some(ele => ele === e.currentTarget)
if (!isMyElement || !this.state.show) return
}

const resetState = () => {
const isVisible = this.state.show
// Check if the mouse is actually over the tooltip, if so don't hide the tooltip
if (this.mouseOnToolTip()) {
this.listenForTooltipExit()
return
}
this.removeListenerForTooltipExit()

this.setState({
show: false
}, () => {
Expand Down Expand Up @@ -437,6 +498,7 @@ class ReactTooltip extends React.Component {
clearTimer () {
clearTimeout(this.delayShowLoop)
clearTimeout(this.delayHideLoop)
clearTimeout(this.delayReshow)
clearInterval(this.intervalUpdateContent)
}

Expand All @@ -457,7 +519,8 @@ class ReactTooltip extends React.Component {
{'type-warning': this.state.type === 'warning'},
{'type-error': this.state.type === 'error'},
{'type-info': this.state.type === 'info'},
{'type-light': this.state.type === 'light'}
{'type-light': this.state.type === 'light'},
{'allow_hover': this.props.delayUpdate}
)

let Wrapper = this.props.wrapper
Expand All @@ -469,6 +532,7 @@ class ReactTooltip extends React.Component {
return (
<Wrapper className={`${tooltipClass} ${extraClass}`}
id={this.props.id}
ref={ref => this.tooltipRef = ref}
{...ariaProps}
data-id='tooltip'
dangerouslySetInnerHTML={{__html: placeholder}}/>
Expand All @@ -478,6 +542,7 @@ class ReactTooltip extends React.Component {
<Wrapper className={`${tooltipClass} ${extraClass}`}
id={this.props.id}
{...ariaProps}
ref={ref => this.tooltipRef = ref}
data-id='tooltip'>{placeholder}</Wrapper>
)
}
Expand Down
3 changes: 3 additions & 0 deletions src/index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@
top: -999em;
visibility: hidden;
z-index: 999;
&.allow_hover {
pointer-events:auto;
}
&:before,
&:after {
content: "";
Expand Down
Loading

0 comments on commit 79342ce

Please sign in to comment.