diff --git a/README.md b/README.md
index ede1202..c8101e3 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
# Selectable items for React
-Allows individual or group selection of items using the mouse.
+Allows individual or group selection of items using the mouse or touch.
## Demo
[Try it out](http://unclecheese.github.io/react-selectable/example)
@@ -58,3 +58,6 @@ The `` component accepts a few optional props:
* `component` (String) The component to render. Defaults to `div`.
* `fixedPosition` (Boolean) Whether the `` container is a fixed/absolute position element or the grandchild of one. Note: if you get an error that `Value must be omitted for boolean attributes` when you try ``, simply use Javascript's boolean object function: ``.
+## Extended Features
+
+If you need extended features such as item click selection (without dragging), not clearing the previous selection at the start of a drag, or a function callback during selection, check out [react-selectable-extended](https://github.com/leopoldjoy/react-selectable-extended).
\ No newline at end of file
diff --git a/dist/react-selectable.js b/dist/react-selectable.js
index 55a4e97..60802d9 100644
--- a/dist/react-selectable.js
+++ b/dist/react-selectable.js
@@ -80,12 +80,12 @@ return /******/ (function(modules) { // webpackBootstrap
'use strict';
- var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
-
Object.defineProperty(exports, "__esModule", {
value: true
});
+ var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
+
var _react = __webpack_require__(2);
var _react2 = _interopRequireDefault(_react);
@@ -131,12 +131,19 @@ return /******/ (function(modules) { // webpackBootstrap
_this._mouseDownData = null;
_this._registry = [];
+ // Used to prevent actions from firing twice on devices that are both click and touch enabled
+ _this._mouseDownStarted = false;
+ _this._mouseMoveStarted = false;
+ _this._mouseUpStarted = false;
+
_this._openSelector = _this._openSelector.bind(_this);
_this._mouseDown = _this._mouseDown.bind(_this);
_this._mouseUp = _this._mouseUp.bind(_this);
_this._selectElements = _this._selectElements.bind(_this);
_this._registerSelectable = _this._registerSelectable.bind(_this);
_this._unregisterSelectable = _this._unregisterSelectable.bind(_this);
+ _this._desktopEventCoords = _this._desktopEventCoords.bind(_this);
+
return _this;
}
@@ -154,6 +161,7 @@ return /******/ (function(modules) { // webpackBootstrap
key: 'componentDidMount',
value: function componentDidMount() {
_reactDom2.default.findDOMNode(this).addEventListener('mousedown', this._mouseDown);
+ _reactDom2.default.findDOMNode(this).addEventListener('touchstart', this._mouseDown);
}
/**
@@ -164,6 +172,7 @@ return /******/ (function(modules) { // webpackBootstrap
key: 'componentWillUnmount',
value: function componentWillUnmount() {
_reactDom2.default.findDOMNode(this).removeEventListener('mousedown', this._mouseDown);
+ _reactDom2.default.findDOMNode(this).removeEventListener('touchstart', this._mouseDown);
}
}, {
key: '_registerSelectable',
@@ -186,6 +195,13 @@ return /******/ (function(modules) { // webpackBootstrap
}, {
key: '_openSelector',
value: function _openSelector(e) {
+ var _this2 = this;
+
+ if (this._mouseMoveStarted) return;
+ this._mouseMoveStarted = true;
+
+ e = this._desktopEventCoords(e);
+
var w = Math.abs(this._mouseDownData.initialW - e.pageX);
var h = Math.abs(this._mouseDownData.initialH - e.pageY);
@@ -195,6 +211,8 @@ return /******/ (function(modules) { // webpackBootstrap
boxHeight: h,
boxLeft: Math.min(e.pageX, this._mouseDownData.initialW),
boxTop: Math.min(e.pageY, this._mouseDownData.initialH)
+ }, function () {
+ _this2._mouseMoveStarted = false;
});
}
@@ -206,11 +224,18 @@ return /******/ (function(modules) { // webpackBootstrap
}, {
key: '_mouseDown',
value: function _mouseDown(e) {
+ if (this._mouseDownStarted) return;
+ this._mouseDownStarted = true;
+ this._mouseUpStarted = false;
+
+ e = this._desktopEventCoords(e);
+
var node = _reactDom2.default.findDOMNode(this);
var collides = undefined,
offsetData = undefined,
distanceData = undefined;
_reactDom2.default.findDOMNode(this).addEventListener('mouseup', this._mouseUp);
+ _reactDom2.default.findDOMNode(this).addEventListener('touchend', this._mouseUp);
// Right clicks
if (e.which === 3 || e.button === 2) return;
@@ -241,6 +266,7 @@ return /******/ (function(modules) { // webpackBootstrap
e.preventDefault();
_reactDom2.default.findDOMNode(this).addEventListener('mousemove', this._openSelector);
+ _reactDom2.default.findDOMNode(this).addEventListener('touchmove', this._openSelector);
}
/**
@@ -250,8 +276,14 @@ return /******/ (function(modules) { // webpackBootstrap
}, {
key: '_mouseUp',
value: function _mouseUp(e) {
+ if (this._mouseUpStarted) return;
+ this._mouseUpStarted = true;
+ this._mouseDownStarted = false;
+
_reactDom2.default.findDOMNode(this).removeEventListener('mousemove', this._openSelector);
_reactDom2.default.findDOMNode(this).removeEventListener('mouseup', this._mouseUp);
+ _reactDom2.default.findDOMNode(this).removeEventListener('touchmove', this._openSelector);
+ _reactDom2.default.findDOMNode(this).removeEventListener('touchend', this._mouseUp);
if (!this._mouseDownData) return;
@@ -270,6 +302,7 @@ return /******/ (function(modules) { // webpackBootstrap
var selectbox = _reactDom2.default.findDOMNode(this.refs.selectbox);
var tolerance = this.props.tolerance;
+
if (!selectbox) return;
this._registry.forEach(function (itemData) {
@@ -287,6 +320,22 @@ return /******/ (function(modules) { // webpackBootstrap
this.props.onSelection(currentItems);
}
+ /**
+ * Used to return event object with desktop (non-touch) format of event
+ * coordinates, regardless of whether the action is from mobile or desktop.
+ */
+
+ }, {
+ key: '_desktopEventCoords',
+ value: function _desktopEventCoords(e) {
+ if (e.pageX == undefined || e.pageY == undefined) {
+ // Touch-device
+ e.pageX = e.targetTouches[0].pageX;
+ e.pageY = e.targetTouches[0].pageY;
+ }
+ return e;
+ }
+
/**
* Renders the component
* @return {ReactComponent}
@@ -496,12 +545,12 @@ return /******/ (function(modules) { // webpackBootstrap
'use strict';
- var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
-
Object.defineProperty(exports, "__esModule", {
value: true
});
+ var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
+
var _react = __webpack_require__(2);
var _react2 = _interopRequireDefault(_react);
diff --git a/src/selectable-group.js b/src/selectable-group.js
index aa5ee0e..9ec9dae 100644
--- a/src/selectable-group.js
+++ b/src/selectable-group.js
@@ -13,18 +13,25 @@ class SelectableGroup extends React.Component {
this.state = {
isBoxSelecting: false,
boxWidth: 0,
- boxHeight: 0
+ boxHeight: 0
}
this._mouseDownData = null;
this._registry = [];
+ // Used to prevent actions from firing twice on devices that are both click and touch enabled
+ this._mouseDownStarted = false;
+ this._mouseMoveStarted = false;
+ this._mouseUpStarted = false;
+
this._openSelector = this._openSelector.bind(this);
this._mouseDown = this._mouseDown.bind(this);
this._mouseUp = this._mouseUp.bind(this);
this._selectElements = this._selectElements.bind(this);
this._registerSelectable = this._registerSelectable.bind(this);
this._unregisterSelectable = this._unregisterSelectable.bind(this);
+ this._desktopEventCoords = this._desktopEventCoords.bind(this);
+
}
@@ -39,7 +46,8 @@ class SelectableGroup extends React.Component {
componentDidMount () {
- ReactDOM.findDOMNode(this).addEventListener('mousedown', this._mouseDown);
+ ReactDOM.findDOMNode(this).addEventListener('mousedown', this._mouseDown);
+ ReactDOM.findDOMNode(this).addEventListener('touchstart', this._mouseDown);
}
@@ -47,7 +55,8 @@ class SelectableGroup extends React.Component {
* Remove global event listeners
*/
componentWillUnmount () {
- ReactDOM.findDOMNode(this).removeEventListener('mousedown', this._mouseDown);
+ ReactDOM.findDOMNode(this).removeEventListener('mousedown', this._mouseDown);
+ ReactDOM.findDOMNode(this).removeEventListener('touchstart', this._mouseDown);
}
@@ -65,7 +74,12 @@ class SelectableGroup extends React.Component {
* Called while moving the mouse with the button down. Changes the boundaries
* of the selection box
*/
- _openSelector (e) {
+ _openSelector (e) {
+ if(this._mouseMoveStarted) return;
+ this._mouseMoveStarted = true;
+
+ e = this._desktopEventCoords(e);
+
const w = Math.abs(this._mouseDownData.initialW - e.pageX);
const h = Math.abs(this._mouseDownData.initialH - e.pageY);
@@ -75,6 +89,8 @@ class SelectableGroup extends React.Component {
boxHeight: h,
boxLeft: Math.min(e.pageX, this._mouseDownData.initialW),
boxTop: Math.min(e.pageY, this._mouseDownData.initialH)
+ }, () => {
+ this._mouseMoveStarted = false;
});
}
@@ -84,9 +100,16 @@ class SelectableGroup extends React.Component {
* be added, and if so, attach event listeners
*/
_mouseDown (e) {
+ if(this._mouseDownStarted) return;
+ this._mouseDownStarted = true;
+ this._mouseUpStarted = false;
+
+ e = this._desktopEventCoords(e);
+
const node = ReactDOM.findDOMNode(this);
let collides, offsetData, distanceData;
ReactDOM.findDOMNode(this).addEventListener('mouseup', this._mouseUp);
+ ReactDOM.findDOMNode(this).addEventListener('touchend', this._mouseUp);
// Right clicks
if(e.which === 3 || e.button === 2) return;
@@ -120,6 +143,7 @@ class SelectableGroup extends React.Component {
e.preventDefault();
ReactDOM.findDOMNode(this).addEventListener('mousemove', this._openSelector);
+ ReactDOM.findDOMNode(this).addEventListener('touchmove', this._openSelector);
}
@@ -127,8 +151,14 @@ class SelectableGroup extends React.Component {
* Called when the user has completed selection
*/
_mouseUp (e) {
+ if(this._mouseUpStarted) return;
+ this._mouseUpStarted = true;
+ this._mouseDownStarted = false;
+
ReactDOM.findDOMNode(this).removeEventListener('mousemove', this._openSelector);
ReactDOM.findDOMNode(this).removeEventListener('mouseup', this._mouseUp);
+ ReactDOM.findDOMNode(this).removeEventListener('touchmove', this._openSelector);
+ ReactDOM.findDOMNode(this).removeEventListener('touchend', this._mouseUp);
if(!this._mouseDownData) return;
@@ -162,6 +192,17 @@ class SelectableGroup extends React.Component {
this.props.onSelection(currentItems);
}
+ /**
+ * Used to return event object with desktop (non-touch) format of event
+ * coordinates, regardless of whether the action is from mobile or desktop.
+ */
+ _desktopEventCoords (e){
+ if(e.pageX==undefined || e.pageY==undefined){ // Touch-device
+ e.pageX = e.targetTouches[0].pageX;
+ e.pageY = e.targetTouches[0].pageY;
+ }
+ return e;
+ }
/**
* Renders the component