diff --git a/src/Popover.js b/src/Popover.js
index cd1153e1e..afbb7ed00 100644
--- a/src/Popover.js
+++ b/src/Popover.js
@@ -11,8 +11,7 @@ const defaultProps = {
const Popover = (props) => {
const popperClasses = classNames(
'popover',
- 'show',
- props.className
+ 'show'
);
const classes = classNames(
@@ -24,7 +23,7 @@ const Popover = (props) => {
return (
);
diff --git a/src/PopperContent.js b/src/PopperContent.js
index 9f523f906..8d4051990 100644
--- a/src/PopperContent.js
+++ b/src/PopperContent.js
@@ -4,10 +4,13 @@ import ReactDOM from 'react-dom';
import classNames from 'classnames';
import { Arrow, Popper as ReactPopper } from 'react-popper';
import { getTarget, targetPropType, mapToCssModules, DOMElement, tagPropType } from './utils';
+import Fade from './Fade';
+
+function noop() { }
const propTypes = {
children: PropTypes.node.isRequired,
- className: PropTypes.string,
+ popperClassName: PropTypes.string,
placement: PropTypes.string,
placementPrefix: PropTypes.string,
arrowClassName: PropTypes.string,
@@ -22,6 +25,9 @@ const propTypes = {
target: targetPropType.isRequired,
modifiers: PropTypes.object,
boundariesElement: PropTypes.oneOfType([PropTypes.string, DOMElement]),
+ onClosed: PropTypes.func,
+ fade: PropTypes.bool,
+ transition: PropTypes.shape(Fade.propTypes),
};
const defaultProps = {
@@ -34,6 +40,11 @@ const defaultProps = {
flip: true,
container: 'body',
modifiers: {},
+ onClosed: noop,
+ fade: true,
+ transition: {
+ ...Fade.defaultProps,
+ }
};
const childContextTypes = {
@@ -48,7 +59,8 @@ class PopperContent extends React.Component {
this.setTargetNode = this.setTargetNode.bind(this);
this.getTargetNode = this.getTargetNode.bind(this);
this.getRef = this.getRef.bind(this);
- this.state = {};
+ this.onClosed = this.onClosed.bind(this);
+ this.state = { isOpen: props.isOpen };
}
getChildContext() {
@@ -60,6 +72,13 @@ class PopperContent extends React.Component {
};
}
+ static getDerivedStateFromProps(props, state) {
+ if (props.isOpen && !state.isOpen) {
+ return { isOpen: props.isOpen };
+ }
+ else return null;
+ }
+
componentDidUpdate() {
if (this._element && this._element.childNodes && this._element.childNodes[0] && this._element.childNodes[0].focus) {
this._element.childNodes[0].focus();
@@ -89,6 +108,11 @@ class PopperContent extends React.Component {
return data;
}
+ onClosed() {
+ this.props.onClosed();
+ this.setState({ isOpen: false });
+ }
+
renderChildren() {
const {
cssModule,
@@ -101,11 +125,14 @@ class PopperContent extends React.Component {
placementPrefix,
arrowClassName: _arrowClassName,
hideArrow,
- className,
+ popperClassName: _popperClassName,
tag,
container,
modifiers,
boundariesElement,
+ onClosed,
+ fade,
+ transition,
...attrs
} = this.props;
const arrowClassName = mapToCssModules(classNames(
@@ -114,7 +141,7 @@ class PopperContent extends React.Component {
), cssModule);
const placement = (this.state.placement || attrs.placement).split('-')[0];
const popperClassName = mapToCssModules(classNames(
- className,
+ _popperClassName,
placementPrefix ? `${placementPrefix}-${placement}` : placement
), this.props.cssModule);
@@ -130,18 +157,33 @@ class PopperContent extends React.Component {
...modifiers,
};
+ const popperTransition = {
+ ...Fade.defaultProps,
+ ...transition,
+ baseClass: fade ? transition.baseClass : '',
+ timeout: fade ? transition.timeout : 0,
+ }
+
return (
-
- {children}
- {!hideArrow && }
-
+
+
+ {children}
+ {!hideArrow && }
+
+
);
}
render() {
this.setTargetNode(getTarget(this.props.target));
- if (this.props.isOpen) {
+ if (this.state.isOpen) {
return this.props.container === 'inline' ?
this.renderChildren() :
ReactDOM.createPortal((
{this.renderChildren()}
), this.getContainerNode());
diff --git a/src/Tooltip.js b/src/Tooltip.js
index e78efb0d7..40d254e03 100644
--- a/src/Tooltip.js
+++ b/src/Tooltip.js
@@ -12,8 +12,7 @@ const defaultProps = {
const Tooltip = (props) => {
const popperClasses = classNames(
'tooltip',
- 'show',
- props.className
+ 'show'
);
const classes = classNames(
@@ -25,7 +24,7 @@ const Tooltip = (props) => {
return (
);
diff --git a/src/TooltipPopoverWrapper.js b/src/TooltipPopoverWrapper.js
index df86443e8..21a398e0d 100644
--- a/src/TooltipPopoverWrapper.js
+++ b/src/TooltipPopoverWrapper.js
@@ -21,6 +21,7 @@ export const propTypes = {
className: PropTypes.string,
innerClassName: PropTypes.string,
arrowClassName: PropTypes.string,
+ popperClassName: PropTypes.string,
cssModule: PropTypes.object,
toggle: PropTypes.func,
autohide: PropTypes.bool,
@@ -37,11 +38,12 @@ export const propTypes = {
PropTypes.object
]),
trigger: PropTypes.string,
+ fade: PropTypes.bool,
};
const DEFAULT_DELAYS = {
show: 0,
- hide: 250
+ hide: 0
};
const defaultProps = {
@@ -51,6 +53,7 @@ const defaultProps = {
delay: DEFAULT_DELAYS,
toggle: function () {},
trigger: 'click',
+ fade: true,
};
function isInDOMSubtree(element, subtreeRoot) {
@@ -76,6 +79,8 @@ class TooltipPopoverWrapper extends React.Component {
this.hide = this.hide.bind(this);
this.onEscKeyDown = this.onEscKeyDown.bind(this);
this.getRef = this.getRef.bind(this);
+ this.onClosed = this.onClosed.bind(this);
+ this.state = { isOpen: props.isOpen };
}
componentDidMount() {
@@ -86,11 +91,21 @@ class TooltipPopoverWrapper extends React.Component {
this.removeTargetEvents();
}
+ static getDerivedStateFromProps(props, state) {
+ if (props.isOpen && !state.isOpen) {
+ return { isOpen: props.isOpen };
+ }
+ else return null;
+ }
+
onMouseOverTooltipContent() {
if (this.props.trigger.indexOf('hover') > -1 && !this.props.autohide) {
if (this._hideTimeout) {
this.clearHideTimeout();
}
+ if (this.state.isOpen && !this.props.isOpen) {
+ this.toggle();
+ }
}
}
@@ -274,8 +289,12 @@ class TooltipPopoverWrapper extends React.Component {
return this.props.toggle(e);
}
+ onClosed() {
+ this.setState({ isOpen: false });
+ }
+
render() {
- if (!this.props.isOpen) {
+ if (!this.state.isOpen) {
return null;
}
@@ -292,20 +311,22 @@ class TooltipPopoverWrapper extends React.Component {
placement,
placementPrefix,
arrowClassName,
+ popperClassName,
container,
modifiers,
offset,
+ fade,
} = this.props;
const attributes = omit(this.props, Object.keys(propTypes));
- const popperClasses = mapToCssModules(className, cssModule);
+ const popperClasses = mapToCssModules(popperClassName, cssModule);
const classes = mapToCssModules(innerClassName, cssModule);
return (
{
{ attachTo: container }
);
+ jest.runTimersToTime(150);
+ expect(document.getElementsByClassName('tooltip').length).toBe(0);
+
+ wrapper.setProps({ isOpen: true });
+ jest.runTimersToTime(150);
+ expect(document.getElementsByClassName('tooltip').length).toBe(1);
+
+ wrapper.setProps({ isOpen: false });
+ jest.runTimersToTime(150);
+ expect(document.getElementsByClassName('tooltip').length).toBe(0);
+ wrapper.detach();
+ });
+
+ it('should toggle isOpen', () => {
+ const wrapper = mount(
+
+ Tooltip Content
+ ,
+ { attachTo: container }
+ );
+
expect(document.getElementsByClassName('tooltip').length).toBe(0);
wrapper.setProps({ isOpen: true });
+ jest.runTimersToTime(0); // slight async delay for getDerivedStateFromProps to update isOpen
expect(document.getElementsByClassName('tooltip').length).toBe(1);
wrapper.setProps({ isOpen: false });
+ jest.runTimersToTime(0);
expect(document.getElementsByClassName('tooltip').length).toBe(0);
wrapper.detach();
});