Skip to content

Commit

Permalink
revert to document click. Fixes #11 #6
Browse files Browse the repository at this point in the history
  • Loading branch information
yiminghe committed Jul 8, 2015
1 parent e71bfe6 commit 2b8e8fc
Show file tree
Hide file tree
Showing 7 changed files with 129 additions and 57 deletions.
4 changes: 4 additions & 0 deletions HISTORY.md
@@ -1,6 +1,10 @@
# History
----

### 2.4.0 / 2015-07-08

revert to document click and fix focus/click conflict [#13](https://github.com/react-component/tooltip/issues/6)

### 2.3.0 / 2015-07-07

`new` [#7](https://github.com/react-component/tooltip/issues/7) support delay prop
Expand Down
2 changes: 1 addition & 1 deletion README.md
Expand Up @@ -71,7 +71,7 @@ online example: http://react-component.github.io/tooltip/examples/
<tr>
<td>delay</td>
<td>number</td>
<td></td>
<td>0.1</td>
<td>delay time to show or hide, only valid for hover trigger.unit: s.</td>
</tr>
<tr>
Expand Down
13 changes: 7 additions & 6 deletions examples/simple.js
Expand Up @@ -30,11 +30,6 @@ var Test = React.createClass({
var trigger = assign({}, this.state.trigger);
if (e.target.checked) {
trigger[e.target.value] = 1;
if (e.target.value === 'click') {
delete trigger.hover;
} else if (e.target.value === 'hover') {
delete trigger.click;
}
} else {
delete trigger[e.target.value];
}
Expand Down Expand Up @@ -72,12 +67,18 @@ var Test = React.createClass({
</label>

&nbsp;&nbsp;&nbsp;&nbsp;

trigger:

<label>
trigger:
<input value='hover' checked={trigger.hover} type='checkbox' onChange={this.onTriggerChange}/>
hover
</label>
<label>
<input value='focus' checked={trigger.focus} type='checkbox' onChange={this.onTriggerChange}/>
focus
</label>
<label>
<input value='click' checked={trigger.click} type='checkbox' onChange={this.onTriggerChange}/>
click
</label>
Expand Down
2 changes: 1 addition & 1 deletion package.json
@@ -1,6 +1,6 @@
{
"name": "rc-tooltip",
"version": "2.3.0",
"version": "2.4.0",
"description": "tooltip ui component for react",
"keywords": [
"react",
Expand Down
15 changes: 0 additions & 15 deletions src/Popup.jsx
Expand Up @@ -78,29 +78,14 @@ class Popup extends React.Component {
className += ' ' + props.className;
}
var style = this.props.style;
var maskStyle = {
position: 'fixed',
left: 0,
top: 0,
background: '#000',
opacity: 0,
filter: 'alpha(opacity=0)',
width: '100%',
height: '100%',
zIndex: '-1'
};
if (!props.visible) {
className += ` ${props.prefixCls}-hidden`;
maskStyle.display = 'none';
}
var arrowClassName = `${props.prefixCls}-arrow`;
var innerClassname = `${props.prefixCls}-inner`;
return <div className={className}
ref="popup"
style={style}>
{props.trigger.indexOf('click') !== -1 ? <div style={maskStyle}
onTouchStart={props.onClickOutside}
onClick={props.onClickOutside}></div> : null}
<div className={arrowClassName}></div>
<div className={innerClassname}>
{props.children}
Expand Down
142 changes: 111 additions & 31 deletions src/Tooltip.jsx
Expand Up @@ -8,6 +8,7 @@ var rcUtil = require('rc-util');
var createChainedFunction = rcUtil.createChainedFunction;
var Popup = require('./Popup');


class Tooltip extends React.Component {
constructor(props) {
super(props);
Expand All @@ -17,9 +18,12 @@ class Tooltip extends React.Component {
if ('visible' in props) {
this.state.visible = !!props.visible;
}
['toggle', 'show', 'hide'].forEach((m)=> {
this[m] = this[m].bind(this);
});
['handleClick', 'handleMouseEnter',
'handleMouseDown', 'handleTouchStart',
'handleMouseLeave', 'handleFocus',
'handleBlur', 'handleOutsideClick'].forEach((m)=> {
this[m] = this[m].bind(this);
});
}

getPopupDomNode() {
Expand Down Expand Up @@ -60,34 +64,29 @@ class Tooltip extends React.Component {
placement={props.placement}
animation={props.animation}
wrap={this}
onClickOutside={this.hide}
style={props.overlayStyle}
transitionName={props.transitionName}>
{props.overlay}
</Popup>;
}

toggle(e) {
e.preventDefault();
if (this.state.visible) {
this.hide();
} else {
this.show();
}
}

setVisible(visible) {
if (this.state.visible !== visible) {
this.setState({
visible: visible
});
var currentVisible = this.state.visible;
this.props.onVisibleChange(visible);
// avoid redundant render
// user do not modify visible
if (currentVisible === this.state.visible) {
this.setState({
visible: visible
});
}
}
}

delaySetVisible(visible, e) {
delaySetVisible(visible) {
var delay = this.props.delay * 1000;
if (delay && e && e.type.indexOf('mouse') !== -1) {
if (delay) {
if (this.delayTimer) {
clearTimeout(this.delayTimer);
}
Expand All @@ -100,12 +99,64 @@ class Tooltip extends React.Component {
}
}

show(e) {
this.delaySetVisible(true, e);
handleMouseEnter() {
this.delaySetVisible(true);
}

handleMouseLeave() {
this.delaySetVisible(false);
}

handleFocus() {
this.focusTime = Date.now();
this.setVisible(true);
}

handleMouseDown() {
this.preClickTime = Date.now();
}

handleTouchStart() {
this.preTouchTime = Date.now();
}

hide(e) {
this.delaySetVisible(false, e);
handleBlur() {
this.setVisible(false);
}

handleClick(e) {
// focus will trigger click
if (this.focusTime) {
var preTime;
if (this.preClickTime && this.preTouchTime) {
preTime = Math.min(this.preClickTime, this.preTouchTime);
} else if (this.preClickTime) {
preTime = this.preClickTime;
} else if (this.preTouchTime) {
preTime = this.preTouchTime;
}
if (Math.abs(preTime - this.focusTime) < 20) {
return;
}
this.focusTime = 0;
}
this.preClickTime = 0;
this.preTouchTime = 0;
e.preventDefault();
if (this.state.visible) {
this.setVisible(false);
} else {
this.setVisible(true);
}
}

handleOutsideClick(e) {
var target = e.target;
var root = React.findDOMNode(this);
var popupNode = this.getPopupDomNode();
if (!rcUtil.Dom.contains(root, target) && !rcUtil.Dom.contains(popupNode, target)) {
this.setVisible(false);
}
}

componentDidMount() {
Expand All @@ -119,14 +170,41 @@ class Tooltip extends React.Component {
if (this.props.renderPopupToBody) {
this.popupInstance = React.render(this.getPopupElement(), this.getTipContainer());
}
var props = this.props;
if (props.trigger.indexOf('click') !== -1) {
if (this.state.visible) {
if (!this.clickOutsideHandler) {
this.clickOutsideHandler = rcUtil.Dom.addEventListener(document, 'mousedown', this.handleOutsideClick);
this.touchOutsideHandler = rcUtil.Dom.addEventListener(document, 'touchstart', this.handleOutsideClick);
}
return;
}
}
if (this.clickOutsideHandler) {
this.clickOutsideHandler.remove();
this.touchOutsideHandler.remove();
this.clickOutsideHandler = null;
this.touchOutsideHandler = null;
}
}

componentWillUnmount() {
if (this.tipContainer) {
React.unmountComponentAtNode(this.tipContainer);
document.body.removeChild(this.tipContainer);
var tipContainer = this.tipContainer;
if (tipContainer) {
React.unmountComponentAtNode(tipContainer);
document.body.removeChild(tipContainer);
this.tipContainer = null;
}
if (this.delayTimer) {
clearTimeout(this.delayTimer);
this.delayTimer = null;
}
if (this.clickOutsideHandler) {
this.clickOutsideHandler.remove();
this.touchOutsideHandler.remove();
this.clickOutsideHandler = null;
this.touchOutsideHandler = null;
}
}

render() {
Expand All @@ -141,15 +219,17 @@ class Tooltip extends React.Component {
var trigger = props.trigger;
var mouseProps = {};
if (trigger.indexOf('click') !== -1) {
newChildProps.onClick = createChainedFunction(this.toggle, childProps.onClick);
newChildProps.onClick = createChainedFunction(this.handleClick, childProps.onClick);
newChildProps.onMouseDown = createChainedFunction(this.handleMouseDown, childProps.onMouseDown);
newChildProps.onTouchStart = createChainedFunction(this.handleTouchStart, childProps.onTouchStart);
}
if (trigger.indexOf('hover') !== -1) {
mouseProps.onMouseEnter = createChainedFunction(this.show, childProps.onMouseEnter);
mouseProps.onMouseLeave = createChainedFunction(this.hide, childProps.onMouseLeave);
mouseProps.onMouseEnter = createChainedFunction(this.handleMouseEnter, childProps.onMouseEnter);
mouseProps.onMouseLeave = createChainedFunction(this.handleMouseLeave, childProps.onMouseLeave);
}
if (trigger.indexOf('focus') !== -1) {
newChildProps.onFocus = createChainedFunction(this.show, childProps.onFocus);
newChildProps.onBlur = createChainedFunction(this.hide, childProps.onBlur);
newChildProps.onFocus = createChainedFunction(this.handleFocus, childProps.onFocus);
newChildProps.onBlur = createChainedFunction(this.handleBlur, childProps.onBlur);
}

var popupElement = props.renderPopupToBody ? null : this.getPopupElement();
Expand Down Expand Up @@ -181,7 +261,7 @@ Tooltip.defaultProps = {
renderPopupToBody: true,
onVisibleChange: function () {
},
delay: 0,
delay: 0.1,
overlayStyle: {},
wrapStyle: {},
placement: 'right',
Expand Down
8 changes: 5 additions & 3 deletions tests/index.spec.js
Expand Up @@ -49,18 +49,20 @@ describe('rc-tooltip', function () {
});

it('hover works', (done)=> {
var tooltip = React.render(<Tooltip trigger={['hover']} placement="left" overlay={<strong>tooltip</strong>}>
var tooltip = React.render(<Tooltip trigger={['hover']}
placement="left"
overlay={<strong>tooltip</strong>}>
<div className="target">click</div>
</Tooltip>, div);
var target = scryRenderedDOMComponentsWithClass(tooltip, 'rc-tooltip-wrap')[0];
// can not simulate mouseenter
target.props.onMouseEnter();
async.series([timeout(20), (next)=> {
async.series([timeout(200), (next)=> {
var popupDomNode = tooltip.getPopupDomNode();
expect(popupDomNode).to.be.ok();
target.props.onMouseLeave();
next();
}, timeout(20), (next)=> {
}, timeout(200), (next)=> {
var popupDomNode = tooltip.getPopupDomNode();
expect($(popupDomNode).css('display')).to.be('none');
next();
Expand Down

0 comments on commit 2b8e8fc

Please sign in to comment.