diff --git a/src/ClickAwayListener.jsx b/src/ClickAwayListener.jsx
new file mode 100644
index 00000000000000..2c43553142feae
--- /dev/null
+++ b/src/ClickAwayListener.jsx
@@ -0,0 +1,65 @@
+import React from 'react';
+import ReactDOM from 'react-dom';
+import events from './utils/events';
+
+const isDescendant = (el, target) => {
+ if (target !== null) {
+ return el === target || isDescendant(el, target.parentNode);
+ }
+ return false;
+};
+
+const clickAwayEvents = ['mousedown', 'touchstart'];
+const bind = (callback) => clickAwayEvents.forEach((event) => events.on(document, event, callback));
+const unbind = (callback) => clickAwayEvents.forEach((event) => events.off(document, event, callback));
+
+export default class ClickAwayListener extends React.Component {
+
+ static propTypes = {
+ children: React.PropTypes.node,
+ onClickAway: React.PropTypes.func,
+ type: React.PropTypes.node,
+ };
+
+ static defaultProps = {
+ type: 'div',
+ };
+
+ componentDidMount() {
+ if (this.props.onClickAway) {
+ bind(this.handleRelease);
+ }
+ }
+
+ componentWillReceiveProps(nextProps) {
+ if (!nextProps.onClickAway) {
+ unbind(this.handleRelease);
+ }
+ }
+
+ componentWillUnmount() {
+ unbind(this.handleRelease);
+ }
+
+ handleRelease = (event) => {
+ if (event.defaultPrevented) {
+ return;
+ }
+
+ const el = ReactDOM.findDOMNode(this);
+ if (document.documentElement.contains(event.target) && !isDescendant(el, event.target)) {
+ this.props.onClickAway(event);
+ }
+ };
+
+ render() {
+ const {
+ children,
+ onClickAway,
+ type,
+ ...other,
+ } = this.props;
+
+ return React.createElement(type, other, children);
+ }
+}
diff --git a/src/menus/menu.jsx b/src/menus/menu.jsx
index 528bbfaaed09fc..03b0a3b20508d0 100644
--- a/src/menus/menu.jsx
+++ b/src/menus/menu.jsx
@@ -1,7 +1,7 @@
import React from 'react';
import ReactDOM from 'react-dom';
import update from 'react-addons-update';
-import ClickAwayable from '../mixins/click-awayable';
+import ClickAwayListener from '../ClickAwayListener';
import autoPrefix from '../styles/auto-prefix';
import Transitions from '../styles/transitions';
import KeyCode from '../utils/key-code';
@@ -132,10 +132,6 @@ const Menu = React.createClass({
muiTheme: React.PropTypes.object,
},
- mixins: [
- ClickAwayable,
- ],
-
getDefaultProps() {
return {
autoWidth: true,
@@ -564,7 +560,8 @@ const Menu = React.createClass({
});
return (
-
@@ -581,7 +578,7 @@ const Menu = React.createClass({
{newChildren}
-
+
);
},
diff --git a/src/mixins/click-awayable.js b/src/mixins/click-awayable.js
deleted file mode 100644
index 5f2350029a8414..00000000000000
--- a/src/mixins/click-awayable.js
+++ /dev/null
@@ -1,43 +0,0 @@
-import ReactDOM from 'react-dom';
-import Events from '../utils/events';
-import Dom from '../utils/dom';
-
-export default {
-
- //When the component mounts, listen to click events and check if we need to
- //Call the componentClickAway function.
- componentDidMount() {
- if (!this.manuallyBindClickAway) this._bindClickAway();
- },
-
- componentWillUnmount() {
- this._unbindClickAway();
- },
-
- _checkClickAway(event) {
- if (this.isMounted()) {
- const el = ReactDOM.findDOMNode(this);
-
- // Check if the target is inside the current component
- if (event.target !== el &&
- !Dom.isDescendant(el, event.target) &&
- document.documentElement.contains(event.target)) {
- if (this.componentClickAway) this.componentClickAway(event);
- }
- }
- },
-
- _bindClickAway() {
- // On touch-enabled devices, both events fire, and the handler is called twice,
- // but it's fine since all operations for which the mixin is used
- // are idempotent.
- Events.on(document, 'mouseup', this._checkClickAway);
- Events.on(document, 'touchend', this._checkClickAway);
- },
-
- _unbindClickAway() {
- Events.off(document, 'mouseup', this._checkClickAway);
- Events.off(document, 'touchend', this._checkClickAway);
- },
-
-};
diff --git a/src/mixins/index.js b/src/mixins/index.js
index 272b08160ca445..21ba89e0b4b987 100644
--- a/src/mixins/index.js
+++ b/src/mixins/index.js
@@ -1,13 +1,10 @@
-import ClickAwayable from './click-awayable';
import StylePropable from './style-propable';
import StyleResizable from './style-resizable';
-export {ClickAwayable};
export {StylePropable};
export {StyleResizable};
export default {
- ClickAwayable,
StylePropable,
StyleResizable,
};
diff --git a/src/snackbar.jsx b/src/snackbar.jsx
index 09a0b3626bddee..b3c36a4633c4ce 100644
--- a/src/snackbar.jsx
+++ b/src/snackbar.jsx
@@ -1,6 +1,6 @@
import React from 'react';
import Transitions from './styles/transitions';
-import ClickAwayable from './mixins/click-awayable';
+import ClickAwayListener from './ClickAwayListener';
import FlatButton from './flat-button';
import getMuiTheme from './styles/getMuiTheme';
import StyleResizable from './mixins/style-resizable';
@@ -138,7 +138,6 @@ const Snackbar = React.createClass({
mixins: [
StyleResizable,
- ClickAwayable,
],
getInitialState() {
@@ -162,7 +161,7 @@ const Snackbar = React.createClass({
//Only Bind clickaway after transition finishes
this.timerTransitionId = setTimeout(() => {
- this._bindClickAway();
+ this.timerTransitionId = 0;
}, 400);
}
},
@@ -204,11 +203,10 @@ const Snackbar = React.createClass({
//Only Bind clickaway after transition finishes
this.timerTransitionId = setTimeout(() => {
- this._bindClickAway();
+ this.timerTransitionId = 0;
}, 400);
} else {
clearTimeout(this.timerAutoHideId);
- this._unbindClickAway();
}
}
},
@@ -217,7 +215,6 @@ const Snackbar = React.createClass({
clearTimeout(this.timerAutoHideId);
clearTimeout(this.timerTransitionId);
clearTimeout(this.timerOneAtTheTimeId);
- this._unbindClickAway();
},
manuallyBindClickAway: true,
@@ -227,6 +224,8 @@ const Snackbar = React.createClass({
timerOneAtTheTimeId: undefined,
componentClickAway() {
+ if (!this.timerTransitionId) return;
+
if (this.props.open !== null && this.props.onRequestClose) {
this.props.onRequestClose('clickaway');
} else {
@@ -263,6 +262,7 @@ const Snackbar = React.createClass({
muiTheme: {
prepareStyles,
},
+ open,
} = this.state;
const styles = getStyles(this.props, this.state);
@@ -276,14 +276,18 @@ const Snackbar = React.createClass({
);
return (
-
+
);
},
diff --git a/src/table/table-body.jsx b/src/table/table-body.jsx
index 3d9e75653ed64d..4245d0d1884114 100644
--- a/src/table/table-body.jsx
+++ b/src/table/table-body.jsx
@@ -1,7 +1,7 @@
import React from 'react';
import Checkbox from '../checkbox';
import TableRowColumn from './table-row-column';
-import ClickAwayable from '../mixins/click-awayable';
+import ClickAwayListener from '../ClickAwayListener';
import getMuiTheme from '../styles/getMuiTheme';
const TableBody = React.createClass({
@@ -121,10 +121,6 @@ const TableBody = React.createClass({
muiTheme: React.PropTypes.object,
},
- mixins: [
- ClickAwayable,
- ],
-
getDefaultProps() {
return {
allRowsSelected: false,
@@ -419,9 +415,14 @@ const TableBody = React.createClass({
const rows = this._createRows();
return (
-
+
{rows}
-
+
);
},