Skip to content

feat(mouseover): Add ability to hover on tooltip. #416

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Sep 3, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)) {
self.intervalUpdateContent = setInterval(() => {
if (self.mount) {
const {getContent} = self.props
const placeholder = getTipContent(originTooltip, '', getContent[0](), isMultiline)
const isEmptyTip = self.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