Skip to content

Commit

Permalink
Merge pull request #149 from wwayne/shakes
Browse files Browse the repository at this point in the history
Update algorithm for get positon to fix the shake problem #146
  • Loading branch information
wwayne authored Jul 27, 2016
2 parents 8687152 + 20b563b commit e07359d
Show file tree
Hide file tree
Showing 6 changed files with 84 additions and 51 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ class | data-class | String | | extra custom class, can use !important to
delayShow | data-delay-show | Number | | `<p data-tip="tooltip" data-delay-show='1000'></p>` or `<ReactTooltip delayShow={1000} />`
border | data-border | Bool | true, false | Add one pixel white border
getContent | null | Func or Array | () => {}, [() => {}, Interval] | Generate the tip content dynamically
countTransform | data-count-transform | Bool | True, False | Tell tooltip if it needs to count parents' transform into position calculation, the default is true, but it should be set to false when using with react-list

## Using react component as tooltip
Check the example [React-tooltip Test](http://wwayne.com/react-tooltip)
Expand Down
20 changes: 10 additions & 10 deletions example/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -147,24 +147,24 @@ const Test = React.createClass({

<section className="advance">
<div className="section">
<h4 className='title'>Customer event</h4>
<h4 className='title'>Custom event</h4>
<p className="sub-title"></p>

<div className="example-jsx">
<div className="side">
<a data-for='customer-event' data-tip='customer show' data-event='click focus'>( •̀д•́)</a>
<ReactTooltip id='customer-event' globalEventOff='click'/>
<a data-for='custom-event' data-tip='custom show' data-event='click focus'>( •̀д•́)</a>
<ReactTooltip id='custom-event' globalEventOff='click'/>
</div>

<div className="side">
<a data-for='customer-off-event' data-tip='custom show and hide' data-event='click' data-event-off='dblclick'>( •̀д•́)</a>
<ReactTooltip id='customer-off-event'/>
<a data-for='custom-off-event' data-tip='custom show and hide' data-event='click' data-event-off='dblclick'>( •̀д•́)</a>
<ReactTooltip id='custom-off-event'/>
</div>
</div>
<br />
<pre className='example-pre'>
<div>
<p>{"<a data-tip='customer show' data-event='click focus'>( •̀д•́)</a>\n" +
<p>{"<a data-tip='custom show' data-event='click focus'>( •̀д•́)</a>\n" +
"<ReactTooltip globalEventOff='click' />"}</p>
</div>
<div>
Expand All @@ -180,13 +180,13 @@ const Test = React.createClass({

<div className="example-jsx">
<div className="side">
<a data-for='customer-class' data-tip='hover on me will keep the tootlip'>(・ω´・ )</a>
<ReactTooltip id='customer-class' class='extraClass' delayHide={1000} effect='solid'/>
<a data-for='custom-class' data-tip='hover on me will keep the tootlip'>(・ω´・ )</a>
<ReactTooltip id='custom-class' class='extraClass' delayHide={1000} effect='solid'/>
</div>

<div className="side">
<a data-for='customer-theme' data-tip='custom theme'>(・ω´・ )</a>
<ReactTooltip id='customer-theme' class='customeTheme'/>
<a data-for='custom-theme' data-tip='custom theme'>(・ω´・ )</a>
<ReactTooltip id='custom-theme' class='customeTheme'/>
</div>
</div>
<br />
Expand Down
13 changes: 8 additions & 5 deletions src/decorators/customEvent.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,12 @@ const setUntargetItems = function (currentTarget, targetArray) {
}
}

let customListener

export default function (target) {
target.prototype.isCustomEvent = function (ele) {
const {event} = this.state
return event || ele.getAttribute('data-event')
return event || !!ele.getAttribute('data-event')
}

/* Bind listener for custom event */
Expand All @@ -46,13 +48,14 @@ export default function (target) {
const dataEventOff = ele.getAttribute('data-event-off') || eventOff

dataEvent.split(' ').forEach(event => {
ele.removeEventListener(event, checkStatus)
ele.addEventListener(event, checkStatus.bind(this, dataEventOff), false)
ele.removeEventListener(event, customListener)
customListener = checkStatus.bind(this, dataEventOff)
ele.addEventListener(event, customListener, false)
})
if (dataEventOff) {
dataEventOff.split(' ').forEach(event => {
ele.removeEventListener(event, this.hideTooltip)
ele.addEventListener(event, ::this.hideTooltip, false)
ele.addEventListener(event, this.hideTooltip, false)
})
}
}
Expand All @@ -63,7 +66,7 @@ export default function (target) {
const dataEvent = event || ele.getAttribute('data-event')
const dataEventOff = eventOff || ele.getAttribute('data-event-off')

ele.removeEventListener(dataEvent, checkStatus)
ele.removeEventListener(dataEvent, customListener)
if (dataEventOff) ele.removeEventListener(dataEventOff, this.hideTooltip)
}
}
7 changes: 3 additions & 4 deletions src/decorators/windowListener.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,20 @@ export default function (target) {
target.prototype.bindWindowEvents = function () {
// ReactTooltip.hide
window.removeEventListener(CONSTANT.GLOBAL.HIDE, this.hideTooltip)
window.addEventListener(CONSTANT.GLOBAL.HIDE, ::this.hideTooltip, false)
window.addEventListener(CONSTANT.GLOBAL.HIDE, this.hideTooltip, false)

// ReactTooltip.rebuild
window.removeEventListener(CONSTANT.GLOBAL.REBUILD, this.globalRebuild)
window.addEventListener(CONSTANT.GLOBAL.REBUILD, ::this.globalRebuild, false)
window.addEventListener(CONSTANT.GLOBAL.REBUILD, this.globalRebuild, false)

// Resize
window.removeEventListener('resize', this.onWindowResize)
window.addEventListener('resize', ::this.onWindowResize, false)
window.addEventListener('resize', this.onWindowResize, false)
}

target.prototype.unbindWindowEvents = function () {
window.removeEventListener(CONSTANT.GLOBAL.HIDE, this.hideTooltip)
window.removeEventListener(CONSTANT.GLOBAL.REBUILD, this.globalRebuild)
window.removeEventListener(CONSTANT.GLOBAL.REBUILD, this.globalShow)
window.removeEventListener('resize', this.onWindowResize)
}

Expand Down
86 changes: 58 additions & 28 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,14 @@ class ReactTooltip extends Component {
watchWindow: PropTypes.bool,
isCapture: PropTypes.bool,
globalEventOff: PropTypes.string,
getContent: PropTypes.any
getContent: PropTypes.any,
countTransform: PropTypes.bool
}

constructor (props) {
super(props)
this.state = {
place: 'top', // Direction of tooltip
place: '', // Direction of tooltip
type: 'dark', // Color theme of tooltip
effect: 'float', // float or fixed
show: false,
Expand All @@ -61,12 +62,29 @@ class ReactTooltip extends Component {
currentTarget: null // Current target of mouse event
}

this.bind([
'showTooltip',
'updateTooltip',
'hideTooltip',
'globalRebuild',
'onWindowResize'
])

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

/**
* For unify the bind and unbind listener
*/
bind (methodArray) {
methodArray.forEach(method => {
this[method] = this[method].bind(this)
})
}

componentDidMount () {
this.setStyleHeader() // Set the style to the <link>
this.bindListener() // Bind listener for tooltip
Expand Down Expand Up @@ -115,27 +133,24 @@ class ReactTooltip extends Component {
if (target.getAttribute('currentItem') === null) {
target.setAttribute('currentItem', 'false')
}
this.unbindBasicListener(target)

if (this.isCustomEvent(target)) {
this.customBindListener(target)
return
}

target.removeEventListener('mouseenter', this.showTooltip)
target.addEventListener('mouseenter', ::this.showTooltip, isCaptureMode)
target.addEventListener('mouseenter', this.showTooltip, isCaptureMode)
if (this.state.effect === 'float') {
target.removeEventListener('mousemove', this.updateTooltip)
target.addEventListener('mousemove', ::this.updateTooltip, isCaptureMode)
target.addEventListener('mousemove', this.updateTooltip, isCaptureMode)
}

target.removeEventListener('mouseleave', this.hideTooltip)
target.addEventListener('mouseleave', ::this.hideTooltip, isCaptureMode)
target.addEventListener('mouseleave', this.hideTooltip, isCaptureMode)
})

// Global event to hide tooltip
if (globalEventOff) {
window.removeEventListener(globalEventOff, this.hideTooltip)
window.addEventListener(globalEventOff, ::this.hideTooltip, false)
window.addEventListener(globalEventOff, this.hideTooltip, false)
}
}

Expand All @@ -145,21 +160,25 @@ class ReactTooltip extends Component {
unbindListener () {
const {id, globalEventOff} = this.props
const targetArray = this.getTargetArray(id)

targetArray.forEach(target => {
if (this.isCustomEvent(target)) {
this.customUnbindListener(target)
return
}

target.removeEventListener('mouseenter', this.showTooltip)
target.removeEventListener('mousemove', this.updateTooltip)
target.removeEventListener('mouseleave', this.hideTooltip)
this.unbindBasicListener(target)
if (this.isCustomEvent(target)) this.customUnbindListener(target)
})

if (globalEventOff) window.removeEventListener(globalEventOff, this.hideTooltip)
}

/**
* Invoke this before bind listener and ummount the compont
* it is necessary to invloke this even when binding custom event
* so that the tooltip can switch between custom and default listener
*/
unbindBasicListener (target) {
target.removeEventListener('mouseenter', this.showTooltip)
target.removeEventListener('mousemove', this.updateTooltip)
target.removeEventListener('mouseleave', this.hideTooltip)
}

/**
* When mouse enter, show the tooltip
*/
Expand All @@ -170,6 +189,7 @@ class ReactTooltip extends Component {
const originTooltip = e.currentTarget.getAttribute('data-tip')
const isMultiline = e.currentTarget.getAttribute('data-multiline') || multiline || false

// Generate tootlip content
let content = children
if (getContent) {
if (Array.isArray(getContent)) {
Expand All @@ -178,20 +198,29 @@ class ReactTooltip extends Component {
content = getContent()
}
}

const placeholder = getTipContent(originTooltip, content, isMultiline)

// If it is focus event, switch to `solid` effect
const isFocus = e instanceof window.FocusEvent

this.setState({
placeholder,
place: e.currentTarget.getAttribute('data-place') || this.props.place || 'top',
type: e.currentTarget.getAttribute('data-type') || this.props.type || 'dark',
effect: e.currentTarget.getAttribute('data-effect') || this.props.effect || 'float',
effect: isFocus && 'solid' || e.currentTarget.getAttribute('data-effect') || this.props.effect || 'float',
offset: e.currentTarget.getAttribute('data-offset') || this.props.offset || {},
html: e.currentTarget.getAttribute('data-html') === 'true' || this.props.html || false,
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') === 'true' || this.props.border || false,
extraClass: e.currentTarget.getAttribute('data-class') || this.props.class || ''
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 || '',
countTransform: e.currentTarget.getAttribute('data-count-transform')
? e.currentTarget.getAttribute('data-count-transform') === 'true'
: (this.props.countTransform != null ? this.props.countTransform : true)
}, () => {
this.addScrollListener(e)
this.updateTooltip(e)
Expand Down Expand Up @@ -243,7 +272,8 @@ class ReactTooltip extends Component {
this.clearTimer()
this.delayHideLoop = setTimeout(() => {
this.setState({
show: false
show: false,
place: ''
})
this.removeScrollListener()
}, parseInt(delayHide, 10))
Expand All @@ -255,7 +285,7 @@ class ReactTooltip extends Component {
*/
addScrollListener (e) {
const isCaptureMode = this.isCapture(e.currentTarget)
window.addEventListener('scroll', ::this.hideTooltip, isCaptureMode)
window.addEventListener('scroll', this.hideTooltip, isCaptureMode)
}

removeScrollListener () {
Expand All @@ -264,10 +294,10 @@ class ReactTooltip extends Component {

// Calculation the position
updatePosition () {
const {currentEvent, currentTarget, place, effect, offset} = this.state
const {currentEvent, currentTarget, place, effect, offset, countTransform} = this.state
const node = ReactDOM.findDOMNode(this)

const result = getPosition(currentEvent, currentTarget, node, place, effect, offset)
const result = getPosition(currentEvent, currentTarget, node, place, effect, offset, countTransform)

if (result.isNewState) {
// Switch to reverse placement
Expand Down
8 changes: 4 additions & 4 deletions src/utils/getPosition.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
* - `newState` {Object}
* - `position` {OBject} {left: {Number}, top: {Number}}
*/
export default function (e, target, node, place, effect, offset) {
export default function (e, target, node, place, effect, offset, countTransform) {
const tipWidth = node.clientWidth
const tipHeight = node.clientHeight
const {mouseX, mouseY} = getCurrentOffset(e, target, effect)
Expand All @@ -24,7 +24,7 @@ export default function (e, target, node, place, effect, offset) {
const windowWidth = window.innerWidth
const windowHeight = window.innerHeight

const {parentTop, parentLeft} = getParent(target)
const {parentTop, parentLeft} = countTransform && getParent(target, countTransform) || {parentTop: 0, parentLeft: 0}

// Get the edge offset of the tooltip
const getTipOffsetLeft = (place) => {
Expand Down Expand Up @@ -155,8 +155,8 @@ export default function (e, target, node, place, effect, offset) {
return {
isNewState: false,
position: {
left: getTipOffsetLeft(place) - parentLeft,
top: getTipOffsetTop(place) - parentTop
left: parseInt(getTipOffsetLeft(place) - parentLeft, 10),
top: parseInt(getTipOffsetTop(place) - parentTop, 10)
}
}
}
Expand Down

0 comments on commit e07359d

Please sign in to comment.