Skip to content

Added support for touch-based (mobile) events. #12

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -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)
Expand Down Expand Up @@ -58,3 +58,6 @@ The `<SelectableGroup />` component accepts a few optional props:
* `component` (String) The component to render. Defaults to `div`.
* `fixedPosition` (Boolean) Whether the `<SelectableGroup />` 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 `<SelectableGroup fixedPosition={true} />`, simply use Javascript's boolean object function: `<SelectableGroup fixedPosition={Boolean(true)} />`.

## 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).
57 changes: 53 additions & 4 deletions dist/react-selectable.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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;
}

Expand All @@ -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);
}

/**
Expand All @@ -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',
Expand All @@ -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);

Expand All @@ -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;
});
}

Expand All @@ -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;
Expand Down Expand Up @@ -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);
}

/**
Expand All @@ -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;

Expand All @@ -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) {
Expand All @@ -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}
Expand Down Expand Up @@ -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);
Expand Down
49 changes: 45 additions & 4 deletions src/selectable-group.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);

}


Expand All @@ -39,15 +46,17 @@ 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);
}


/**
* 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);
}


Expand All @@ -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);

Expand All @@ -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;
});
}

Expand All @@ -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;
Expand Down Expand Up @@ -120,15 +143,22 @@ class SelectableGroup extends React.Component {
e.preventDefault();

ReactDOM.findDOMNode(this).addEventListener('mousemove', this._openSelector);
ReactDOM.findDOMNode(this).addEventListener('touchmove', this._openSelector);
}


/**
* 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;

Expand Down Expand Up @@ -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
Expand Down