Skip to content

Commit e07359d

Browse files
authored
Merge pull request #149 from wwayne/shakes
Update algorithm for get positon to fix the shake problem #146
2 parents 8687152 + 20b563b commit e07359d

File tree

6 files changed

+84
-51
lines changed

6 files changed

+84
-51
lines changed

Diff for: README.md

+1
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ class | data-class | String | | extra custom class, can use !important to
6262
delayShow | data-delay-show | Number | | `<p data-tip="tooltip" data-delay-show='1000'></p>` or `<ReactTooltip delayShow={1000} />`
6363
border | data-border | Bool | true, false | Add one pixel white border
6464
getContent | null | Func or Array | () => {}, [() => {}, Interval] | Generate the tip content dynamically
65+
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
6566

6667
## Using react component as tooltip
6768
Check the example [React-tooltip Test](http://wwayne.com/react-tooltip)

Diff for: example/src/index.js

+10-10
Original file line numberDiff line numberDiff line change
@@ -147,24 +147,24 @@ const Test = React.createClass({
147147

148148
<section className="advance">
149149
<div className="section">
150-
<h4 className='title'>Customer event</h4>
150+
<h4 className='title'>Custom event</h4>
151151
<p className="sub-title"></p>
152152

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

159159
<div className="side">
160-
<a data-for='customer-off-event' data-tip='custom show and hide' data-event='click' data-event-off='dblclick'>( •̀д•́)</a>
161-
<ReactTooltip id='customer-off-event'/>
160+
<a data-for='custom-off-event' data-tip='custom show and hide' data-event='click' data-event-off='dblclick'>( •̀д•́)</a>
161+
<ReactTooltip id='custom-off-event'/>
162162
</div>
163163
</div>
164164
<br />
165165
<pre className='example-pre'>
166166
<div>
167-
<p>{"<a data-tip='customer show' data-event='click focus'>( •̀д•́)</a>\n" +
167+
<p>{"<a data-tip='custom show' data-event='click focus'>( •̀д•́)</a>\n" +
168168
"<ReactTooltip globalEventOff='click' />"}</p>
169169
</div>
170170
<div>
@@ -180,13 +180,13 @@ const Test = React.createClass({
180180

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

187187
<div className="side">
188-
<a data-for='customer-theme' data-tip='custom theme'>(・ω´・ )</a>
189-
<ReactTooltip id='customer-theme' class='customeTheme'/>
188+
<a data-for='custom-theme' data-tip='custom theme'>(・ω´・ )</a>
189+
<ReactTooltip id='custom-theme' class='customeTheme'/>
190190
</div>
191191
</div>
192192
<br />

Diff for: src/decorators/customEvent.js

+8-5
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,12 @@ const setUntargetItems = function (currentTarget, targetArray) {
3333
}
3434
}
3535

36+
let customListener
37+
3638
export default function (target) {
3739
target.prototype.isCustomEvent = function (ele) {
3840
const {event} = this.state
39-
return event || ele.getAttribute('data-event')
41+
return event || !!ele.getAttribute('data-event')
4042
}
4143

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

4850
dataEvent.split(' ').forEach(event => {
49-
ele.removeEventListener(event, checkStatus)
50-
ele.addEventListener(event, checkStatus.bind(this, dataEventOff), false)
51+
ele.removeEventListener(event, customListener)
52+
customListener = checkStatus.bind(this, dataEventOff)
53+
ele.addEventListener(event, customListener, false)
5154
})
5255
if (dataEventOff) {
5356
dataEventOff.split(' ').forEach(event => {
5457
ele.removeEventListener(event, this.hideTooltip)
55-
ele.addEventListener(event, ::this.hideTooltip, false)
58+
ele.addEventListener(event, this.hideTooltip, false)
5659
})
5760
}
5861
}
@@ -63,7 +66,7 @@ export default function (target) {
6366
const dataEvent = event || ele.getAttribute('data-event')
6467
const dataEventOff = eventOff || ele.getAttribute('data-event-off')
6568

66-
ele.removeEventListener(dataEvent, checkStatus)
69+
ele.removeEventListener(dataEvent, customListener)
6770
if (dataEventOff) ele.removeEventListener(dataEventOff, this.hideTooltip)
6871
}
6972
}

Diff for: src/decorators/windowListener.js

+3-4
Original file line numberDiff line numberDiff line change
@@ -7,21 +7,20 @@ export default function (target) {
77
target.prototype.bindWindowEvents = function () {
88
// ReactTooltip.hide
99
window.removeEventListener(CONSTANT.GLOBAL.HIDE, this.hideTooltip)
10-
window.addEventListener(CONSTANT.GLOBAL.HIDE, ::this.hideTooltip, false)
10+
window.addEventListener(CONSTANT.GLOBAL.HIDE, this.hideTooltip, false)
1111

1212
// ReactTooltip.rebuild
1313
window.removeEventListener(CONSTANT.GLOBAL.REBUILD, this.globalRebuild)
14-
window.addEventListener(CONSTANT.GLOBAL.REBUILD, ::this.globalRebuild, false)
14+
window.addEventListener(CONSTANT.GLOBAL.REBUILD, this.globalRebuild, false)
1515

1616
// Resize
1717
window.removeEventListener('resize', this.onWindowResize)
18-
window.addEventListener('resize', ::this.onWindowResize, false)
18+
window.addEventListener('resize', this.onWindowResize, false)
1919
}
2020

2121
target.prototype.unbindWindowEvents = function () {
2222
window.removeEventListener(CONSTANT.GLOBAL.HIDE, this.hideTooltip)
2323
window.removeEventListener(CONSTANT.GLOBAL.REBUILD, this.globalRebuild)
24-
window.removeEventListener(CONSTANT.GLOBAL.REBUILD, this.globalShow)
2524
window.removeEventListener('resize', this.onWindowResize)
2625
}
2726

Diff for: src/index.js

+58-28
Original file line numberDiff line numberDiff line change
@@ -38,13 +38,14 @@ class ReactTooltip extends Component {
3838
watchWindow: PropTypes.bool,
3939
isCapture: PropTypes.bool,
4040
globalEventOff: PropTypes.string,
41-
getContent: PropTypes.any
41+
getContent: PropTypes.any,
42+
countTransform: PropTypes.bool
4243
}
4344

4445
constructor (props) {
4546
super(props)
4647
this.state = {
47-
place: 'top', // Direction of tooltip
48+
place: '', // Direction of tooltip
4849
type: 'dark', // Color theme of tooltip
4950
effect: 'float', // float or fixed
5051
show: false,
@@ -61,12 +62,29 @@ class ReactTooltip extends Component {
6162
currentTarget: null // Current target of mouse event
6263
}
6364

65+
this.bind([
66+
'showTooltip',
67+
'updateTooltip',
68+
'hideTooltip',
69+
'globalRebuild',
70+
'onWindowResize'
71+
])
72+
6473
this.mount = true
6574
this.delayShowLoop = null
6675
this.delayHideLoop = null
6776
this.intervalUpdateContent = null
6877
}
6978

79+
/**
80+
* For unify the bind and unbind listener
81+
*/
82+
bind (methodArray) {
83+
methodArray.forEach(method => {
84+
this[method] = this[method].bind(this)
85+
})
86+
}
87+
7088
componentDidMount () {
7189
this.setStyleHeader() // Set the style to the <link>
7290
this.bindListener() // Bind listener for tooltip
@@ -115,27 +133,24 @@ class ReactTooltip extends Component {
115133
if (target.getAttribute('currentItem') === null) {
116134
target.setAttribute('currentItem', 'false')
117135
}
136+
this.unbindBasicListener(target)
118137

119138
if (this.isCustomEvent(target)) {
120139
this.customBindListener(target)
121140
return
122141
}
123142

124-
target.removeEventListener('mouseenter', this.showTooltip)
125-
target.addEventListener('mouseenter', ::this.showTooltip, isCaptureMode)
143+
target.addEventListener('mouseenter', this.showTooltip, isCaptureMode)
126144
if (this.state.effect === 'float') {
127-
target.removeEventListener('mousemove', this.updateTooltip)
128-
target.addEventListener('mousemove', ::this.updateTooltip, isCaptureMode)
145+
target.addEventListener('mousemove', this.updateTooltip, isCaptureMode)
129146
}
130-
131-
target.removeEventListener('mouseleave', this.hideTooltip)
132-
target.addEventListener('mouseleave', ::this.hideTooltip, isCaptureMode)
147+
target.addEventListener('mouseleave', this.hideTooltip, isCaptureMode)
133148
})
134149

135150
// Global event to hide tooltip
136151
if (globalEventOff) {
137152
window.removeEventListener(globalEventOff, this.hideTooltip)
138-
window.addEventListener(globalEventOff, ::this.hideTooltip, false)
153+
window.addEventListener(globalEventOff, this.hideTooltip, false)
139154
}
140155
}
141156

@@ -145,21 +160,25 @@ class ReactTooltip extends Component {
145160
unbindListener () {
146161
const {id, globalEventOff} = this.props
147162
const targetArray = this.getTargetArray(id)
148-
149163
targetArray.forEach(target => {
150-
if (this.isCustomEvent(target)) {
151-
this.customUnbindListener(target)
152-
return
153-
}
154-
155-
target.removeEventListener('mouseenter', this.showTooltip)
156-
target.removeEventListener('mousemove', this.updateTooltip)
157-
target.removeEventListener('mouseleave', this.hideTooltip)
164+
this.unbindBasicListener(target)
165+
if (this.isCustomEvent(target)) this.customUnbindListener(target)
158166
})
159167

160168
if (globalEventOff) window.removeEventListener(globalEventOff, this.hideTooltip)
161169
}
162170

171+
/**
172+
* Invoke this before bind listener and ummount the compont
173+
* it is necessary to invloke this even when binding custom event
174+
* so that the tooltip can switch between custom and default listener
175+
*/
176+
unbindBasicListener (target) {
177+
target.removeEventListener('mouseenter', this.showTooltip)
178+
target.removeEventListener('mousemove', this.updateTooltip)
179+
target.removeEventListener('mouseleave', this.hideTooltip)
180+
}
181+
163182
/**
164183
* When mouse enter, show the tooltip
165184
*/
@@ -170,6 +189,7 @@ class ReactTooltip extends Component {
170189
const originTooltip = e.currentTarget.getAttribute('data-tip')
171190
const isMultiline = e.currentTarget.getAttribute('data-multiline') || multiline || false
172191

192+
// Generate tootlip content
173193
let content = children
174194
if (getContent) {
175195
if (Array.isArray(getContent)) {
@@ -178,20 +198,29 @@ class ReactTooltip extends Component {
178198
content = getContent()
179199
}
180200
}
181-
182201
const placeholder = getTipContent(originTooltip, content, isMultiline)
183202

203+
// If it is focus event, switch to `solid` effect
204+
const isFocus = e instanceof window.FocusEvent
205+
184206
this.setState({
185207
placeholder,
186208
place: e.currentTarget.getAttribute('data-place') || this.props.place || 'top',
187209
type: e.currentTarget.getAttribute('data-type') || this.props.type || 'dark',
188-
effect: e.currentTarget.getAttribute('data-effect') || this.props.effect || 'float',
210+
effect: isFocus && 'solid' || e.currentTarget.getAttribute('data-effect') || this.props.effect || 'float',
189211
offset: e.currentTarget.getAttribute('data-offset') || this.props.offset || {},
190-
html: e.currentTarget.getAttribute('data-html') === 'true' || this.props.html || false,
212+
html: e.currentTarget.getAttribute('data-html')
213+
? e.currentTarget.getAttribute('data-html') === 'true'
214+
: (this.props.html || false),
191215
delayShow: e.currentTarget.getAttribute('data-delay-show') || this.props.delayShow || 0,
192216
delayHide: e.currentTarget.getAttribute('data-delay-hide') || this.props.delayHide || 0,
193-
border: e.currentTarget.getAttribute('data-border') === 'true' || this.props.border || false,
194-
extraClass: e.currentTarget.getAttribute('data-class') || this.props.class || ''
217+
border: e.currentTarget.getAttribute('data-border')
218+
? e.currentTarget.getAttribute('data-border') === 'true'
219+
: (this.props.border || false),
220+
extraClass: e.currentTarget.getAttribute('data-class') || this.props.class || '',
221+
countTransform: e.currentTarget.getAttribute('data-count-transform')
222+
? e.currentTarget.getAttribute('data-count-transform') === 'true'
223+
: (this.props.countTransform != null ? this.props.countTransform : true)
195224
}, () => {
196225
this.addScrollListener(e)
197226
this.updateTooltip(e)
@@ -243,7 +272,8 @@ class ReactTooltip extends Component {
243272
this.clearTimer()
244273
this.delayHideLoop = setTimeout(() => {
245274
this.setState({
246-
show: false
275+
show: false,
276+
place: ''
247277
})
248278
this.removeScrollListener()
249279
}, parseInt(delayHide, 10))
@@ -255,7 +285,7 @@ class ReactTooltip extends Component {
255285
*/
256286
addScrollListener (e) {
257287
const isCaptureMode = this.isCapture(e.currentTarget)
258-
window.addEventListener('scroll', ::this.hideTooltip, isCaptureMode)
288+
window.addEventListener('scroll', this.hideTooltip, isCaptureMode)
259289
}
260290

261291
removeScrollListener () {
@@ -264,10 +294,10 @@ class ReactTooltip extends Component {
264294

265295
// Calculation the position
266296
updatePosition () {
267-
const {currentEvent, currentTarget, place, effect, offset} = this.state
297+
const {currentEvent, currentTarget, place, effect, offset, countTransform} = this.state
268298
const node = ReactDOM.findDOMNode(this)
269299

270-
const result = getPosition(currentEvent, currentTarget, node, place, effect, offset)
300+
const result = getPosition(currentEvent, currentTarget, node, place, effect, offset, countTransform)
271301

272302
if (result.isNewState) {
273303
// Switch to reverse placement

Diff for: src/utils/getPosition.js

+4-4
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
* - `newState` {Object}
1515
* - `position` {OBject} {left: {Number}, top: {Number}}
1616
*/
17-
export default function (e, target, node, place, effect, offset) {
17+
export default function (e, target, node, place, effect, offset, countTransform) {
1818
const tipWidth = node.clientWidth
1919
const tipHeight = node.clientHeight
2020
const {mouseX, mouseY} = getCurrentOffset(e, target, effect)
@@ -24,7 +24,7 @@ export default function (e, target, node, place, effect, offset) {
2424
const windowWidth = window.innerWidth
2525
const windowHeight = window.innerHeight
2626

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

2929
// Get the edge offset of the tooltip
3030
const getTipOffsetLeft = (place) => {
@@ -155,8 +155,8 @@ export default function (e, target, node, place, effect, offset) {
155155
return {
156156
isNewState: false,
157157
position: {
158-
left: getTipOffsetLeft(place) - parentLeft,
159-
top: getTipOffsetTop(place) - parentTop
158+
left: parseInt(getTipOffsetLeft(place) - parentLeft, 10),
159+
top: parseInt(getTipOffsetTop(place) - parentTop, 10)
160160
}
161161
}
162162
}

0 commit comments

Comments
 (0)