dragselect
Advanced tools
Comparing version 1.10.0 to 1.11.0
{ | ||
"name": "dragselect", | ||
"description": "easy javascript drag select functionality to your projects", | ||
"main": "./dist/ds.min.js", | ||
"main": "./docs/ds.min.js", | ||
"authors": [ | ||
@@ -6,0 +6,0 @@ "Thibault Jan Beyer" |
@@ -0,1 +1,8 @@ | ||
# 1.11.0 | ||
- Improve code stability by enforcing typechecks via JS-Docs, inspired by [truckjs](https://medium.com/@trukrs/type-safe-javascript-with-jsdoc-7a2a63209b76) | ||
- Partial rewrite using native ES6 classes transpiling down to ES5 using babel. | ||
*Note: first I refactored the code to TypeScript but then rolled back because it might limit the ability of external people to contribute due to the new language* | ||
- Improve documentation by autogenerating it with JS-Docs | ||
# 1.10.0 | ||
@@ -2,0 +9,0 @@ |
@@ -15,3 +15,3 @@ # DragSelect Developers Guide | ||
Regarding DragSelect, you should work on file `src/DragSelect.js`. | ||
Don’t touch the files in `dist/`. They are automatically generated. | ||
Don’t touch the files in `docs/`. They are automatically generated. | ||
@@ -24,7 +24,7 @@ For your ease of mind, you can run: | ||
it will install `gulp` & run it in dev mode (& on osx open `tests/quicktest.html` for your convenience). | ||
it will install dependencies & run the dev mode (& on osx open `tests/quicktest.html` for your convenience). | ||
*Note: opening quicktest will fail on other operating systems than Mac because I’m using the mac specific `open` command to open the file. However, that is not an issue and you can go on opening the file manually.* | ||
Now whenever you make a change to the `DragSelect.js` in `/src` it will be transpiled and updated in `/dist` automatically. This is important since all test files use the `/dist` version. | ||
Now whenever you make a change to the `DragSelect.js` in `src/` it will be transpiled and updated in `docs/` automatically. This is important since all test files use the `docs/` version. | ||
@@ -31,0 +31,0 @@ |
@@ -19,12 +19,6 @@ # Don‘t do this if you are not project owner! | ||
2. bump version in .js file | ||
3. run `gulp` | ||
4. pushed everything | ||
3. run `npm run build` | ||
4. you can deploy a new version by pushing to github | ||
5. you can deploy a new version using: | ||
``` | ||
npm run deploy | ||
``` | ||
This will build the package and push the changes to `github pages`. Thus also updating the online html and the examples. | ||
Since it uses the `docs/` folder for hosting on `github pages`. | ||
If this was successful, the next step is to publish the new version on `npm`: | ||
@@ -40,2 +34,2 @@ | ||
After that run `npm run deploy` again, just to make sure | ||
After that push again, just to make sure |
{ | ||
"name": "dragselect", | ||
"version": "1.10.0", | ||
"version": "1.11.0", | ||
"description": "easy javascript drag select functionality for your projects", | ||
"main": "./dist/ds.min.js", | ||
"main": "./docs/ds.min.js", | ||
"dependencies": {}, | ||
"scripts": { | ||
"start": "npm install; npm run open | gulp devl", | ||
"start": "npm install; npm run open & npm run watch", | ||
"open": "open tests/quicktest.html -a 'Google Chrome'", | ||
"gulp": "gulp", | ||
"deploy": "gulp && bash deploy.sh", | ||
"watch": "babel ./src/DragSelect.js -w -o ./docs/DragSelect.js", | ||
"build": "npm run docs && npm run transpile && npm run uglify", | ||
"transpile": "babel ./src/DragSelect.js -o ./docs/DragSelect.js", | ||
"uglify": "uglifyjs ./docs/DragSelect.js -o ./docs/ds.min.js", | ||
"docs": "jsdoc ./src/DragSelect.js ./README.md -t ./node_modules/minami -d ./docs", | ||
"test": "jest" | ||
}, | ||
"devDependencies": { | ||
"@babel/core": "^7.1.2", | ||
"@babel/preset-env": "^7.1.0", | ||
"babel-core": "^7.0.0-bridge.0", | ||
"babel-jest": "^23.6.0", | ||
"gulp": "^3.9.1", | ||
"gulp-autoprefixer": "^3.1.1", | ||
"gulp-babel": "^8.0.0", | ||
"gulp-buble": "^0.9.0", | ||
"gulp-csso": "^2.0.0", | ||
"gulp-rename": "^1.2.2", | ||
"gulp-uglify": "^2.0.0", | ||
"jest": "^23.4.1", | ||
"puppeteer": "^1.6.0" | ||
"@babel/cli": "^7.2.3", | ||
"@babel/core": "^7.3.4", | ||
"@babel/plugin-proposal-class-properties": "^7.3.4", | ||
"@babel/plugin-transform-runtime": "^7.3.4", | ||
"@babel/preset-env": "^7.3.4", | ||
"@babel/runtime": "^7.3.4", | ||
"babel-jest": "^24.3.1", | ||
"babel-polyfill": "^6.26.0", | ||
"jest": "^24.3.1", | ||
"jsdoc": "^3.5.5", | ||
"minami": "^1.2.3", | ||
"puppeteer": "^1.13.0" | ||
}, | ||
@@ -29,0 +31,0 @@ "repository": { |
@@ -10,2 +10,4 @@ ``` | ||
[GitHub](https://github.com/ThibaultJanBeyer/DragSelect/) | [NPM](https://www.npmjs.com/package/dragselect) | [Project-Page](https://thibaultjanbeyer.github.io/DragSelect/) | ||
# DragSelect | ||
@@ -16,3 +18,3 @@ easily add a selection algorithm to your application/website. | ||
https://thibaultjanbeyer.github.io/DragSelect/ | ||
[https://thibaultjanbeyer.github.io/DragSelect/](https://thibaultjanbeyer.github.io/DragSelect/) | ||
@@ -24,5 +26,7 @@ # Key-Features | ||
- Add drag selection | ||
- Use modifier keys to make multiple independent selections | ||
- Choose which elements can be selected | ||
- Great browser support, works even like a charm on IE10 | ||
- Lightweight, only ![gzip size](http://img.badgesize.io/https://thibaultjanbeyer.github.io/DragSelect/ds.min.js?compression=gzip) | ||
- Popular: ![npm downloads count](https://img.shields.io/npm/dt/dragselect.svg) on npm | ||
- DragSelect was written with Performance in mind | ||
@@ -34,3 +38,3 @@ - Supports SVG | ||
![dragselect demo](dragselect.gif) | ||
![demo-gif](https://thibaultjanbeyer.github.io/DragSelect/dragselect.gif) | ||
@@ -46,3 +50,3 @@ # Why? | ||
Just download the file (minified) and add it to your document: | ||
Just [download the file](https://github.com/ThibaultJanBeyer/DragSelect/blob/master/docs/DragSelect.js) ([minified](https://github.com/ThibaultJanBeyer/DragSelect/blob/master/docs/ds.min.js)) and add it to your document: | ||
@@ -55,3 +59,3 @@ ```html | ||
```console | ||
npm install --save-dev dragselect | ||
npm install --save dragselect | ||
``` | ||
@@ -61,3 +65,3 @@ | ||
```console | ||
bower install --save-dev dragselect | ||
bower install --save dragselect | ||
``` | ||
@@ -76,2 +80,3 @@ | ||
The simplest possible usage. | ||
Choose which elements can be selected: | ||
@@ -85,6 +90,15 @@ | ||
## complete | ||
<p data-height="265" data-theme-id="0" data-slug-hash="prpwYG" data-default-tab="js,result" data-user="ThibaultJanBeyer" data-embed-version="2" data-pen-title="prpwYG" class="codepen">See the Pen <a href="https://codepen.io/ThibaultJanBeyer/pen/prpwYG/">prpwYG</a> on CodePen.</p> | ||
All options are optional. You could also just initiate the Dragselect by `new DragSelect();` without any option. | ||
## Within a scroll-able Area | ||
Here the selection is constrained. You can only use the selection inside of the container with the red border: | ||
<p data-height="265" data-theme-id="0" data-slug-hash="Nvobgq" data-default-tab="js,result" data-user="ThibaultJanBeyer" data-embed-version="2" data-pen-title="DragSelect with Scrollable AREA" class="codepen">See the Pen <a href="https://codepen.io/ThibaultJanBeyer/pen/Nvobgq/">DragSelect with Scrollable AREA</a> on CodePen.</p> | ||
## extended | ||
All options are optional. You could also just initiate the Dragselect by `new DragSelect();` without any option. | ||
Find all possible properties and methods in **[the docs](https://thibaultjanbeyer.github.io/DragSelect/DragSelect.html)** | ||
```javascript | ||
@@ -116,3 +130,3 @@ var ds = new DragSelect({ | ||
You can also use the "shift", "ctrl" or "command" key to make multiple independent selections. | ||
*You can also use the "shift", "ctrl" or "command" key to make multiple independent selections.* | ||
@@ -128,9 +142,13 @@ ## Mobile/Touch useage | ||
TLDR; => Your `selectables` should be buttons: `<button type="button"></button>`. | ||
> TLDR; => Your `selectables` should be buttons: `<button type="button"></button>`. | ||
Obviously, keyboard users won’t get the full visual experience but it works similarely to the OS default behaviour. You can select items using the default select keys (usually space or enter) and also multiselect when using a modifier key at the same time (unfortunately this does not work in firefox for now since FF doesn’t add the modifier key in the event object when using the keyboard). There is one little thing you have to do tho’: the `selectables` have to be pressable (clickable)! To achieve this, they should be of type `<button type="button"></button>`. | ||
<p data-height="265" data-theme-id="0" data-slug-hash="prpwYG" data-default-tab="html,result" data-user="ThibaultJanBeyer" data-embed-version="2" data-pen-title="DragSelect" class="codepen">See the Pen <a href="https://codepen.io/ThibaultJanBeyer/pen/prpwYG/">DragSelect</a> on CodePen.</p> | ||
# Properties: | ||
# Properties: | ||
Full list of properties is found in **[the docs](https://thibaultjanbeyer.github.io/DragSelect/DragSelect.html)** | ||
Here are some properties for your convenience (not all): | ||
| property | type | usage | | ||
@@ -157,3 +175,4 @@ |--- |--- |--- | | ||
# Methods: | ||
When the function is saved into a variable `var foo = new DragSelect()` you have access to all its inner functions. There are way more than listed here. Here are just the most usable: | ||
When the function is saved into a variable `var foo = new DragSelect()` you have access to all its inner functions. | ||
There are way more than listed here. You can find all in **[the docs](https://thibaultjanbeyer.github.io/DragSelect/DragSelect.html)**. Here are just the most usable: | ||
@@ -188,8 +207,18 @@ | method | properties | usage | | ||
*note: you can change the class names setting the respective property on the constructor, see [Properties](#properties) section.* | ||
*note: you can change the class names setting the respective property on the constructor, see **[the docs](https://thibaultjanbeyer.github.io/DragSelect/DragSelect.html)** properties section.* | ||
# Have Fun! | ||
Don’t forget to give this repository a star if you find it useful. Tell all your friends and start contributing. Thank you :) | ||
Creating and maintaining useful tools is a lot of work. | ||
So don’t forget to give this repository a star if you find it useful. | ||
Tell all your friends and start contributing or [donating 1$](https://paypal.me/kleinanzeigen3) to keep me running. Thank you :) | ||
[![Typewriter Gif](https://thibaultjanbeyer.github.io/DragSelect/typewriter.gif)](http://thibaultjanbeyer.com/) | ||
<script async src="https://production-assets.codepen.io/assets/embed/ei.js"></script> | ||
<br> | ||
<br> | ||
<br> | ||
[documentation](https://thibaultjanbeyer.github.io/DragSelect/DragSelect.html) |
@@ -1,2 +0,3 @@ | ||
// v 1.10.0 | ||
// v 1.11.0 | ||
// @ts-check | ||
/* | ||
@@ -10,60 +11,4 @@ ____ _____ __ __ | ||
Key-Features | ||
- No dependencies | ||
- Ease of use | ||
- Add drag selection | ||
- Accessibility (a11y) | ||
- Choose which elements can be selected. | ||
- Great browser support, works perfectly on IE9 | ||
- Lightweight, only ~2KB gzipped | ||
- Free & open source under MIT License | ||
{*} {*} STAR THIS PLUGIN ON GITHUB: {*} {*} | ||
Default classes | ||
** .ds-selected On elements that are selected | ||
** .ds-hover On elements that are currently hovered | ||
** .ds-selector On the selector element | ||
** .ds-selectable On elements that can be selected | ||
Properties | ||
** @selectables nodes the elements that can be selected | ||
** @selector node the square that will draw the selection | ||
** @area node area in which you can drag. If not provided it will be the whole document | ||
** @customStyles boolean if set to true, no styles (except for position absolute) will be applied by default | ||
** @multiSelectKeys array An array of keys that allows switching to the multi-select mode (see the @multiSelectMode option). The only possible values are keys that are provided via the event object. So far: <kbd>ctrlKey</kbd>, <kbd>shiftKey</kbd>, <kbd>metaKey</kbd> and <kbd>altKey</kbd>. Provide an empty array `[]` if you want to turn off the functionality. Default: `['ctrlKey', 'shiftKey', 'metaKey']` | ||
** @multiSelectMode boolean Add newly selected elements to the selection instead of replacing them. Default = false | ||
** @autoScrollSpeed integer Speed in which the area scrolls while selecting (if available). Unit is pixel per movement. Default = 1 | ||
** @selectedClass string the class assigned to the selected items | ||
** @hoverClass string the class assigned to the mouse hovered items | ||
** @selectorClass string the class assigned to the square selector helper | ||
** @selectableClass string the class assigned to the elements that can be selected | ||
** @onDragStartBegin function Is fired when the user clicks in the area. This callback gets the event object. Executed *before* DragSelect function code ran. | ||
** @onDragStart function It is fired when the user clicks in the area. This callback gets the event object. Executed after DragSelect function code ran, before the setup of event listeners. | ||
** @onDragMove function It is fired when the user drags. This callback gets the event object. Executed before DragSelect function code ran, after getting the current mouse position. | ||
** @onElementSelect function It is fired every time an element is selected. This callback gets a property which is the just selected node | ||
** @onElementUnselect function It is fired every time an element is de-selected. This callback gets a property which is the just de-selected node | ||
** @callback function a callback function that gets fired when the element is dropped. This callback gets a property which is an array that holds all selected nodes. The second property passed is the event object. | ||
Usefull Methods | ||
** .start () reset the functionality after a teardown | ||
** .stop () will teardown/stop the whole functionality | ||
** .break () used in callbacks to disable the execution of the upcoming code (in contrary to "stop", all callbacks are still working, cursor position calculations and event listeners will also continue) | ||
** .getSelection () returns the current selection | ||
** .addSelection ([nodes], bool, bool) adds one or multiple elements to the selection. If boolean is set to true: callback will be called afterwards. By default, adds new elements also to the list of selectables (can be turned off by setting the last boolean to true) | ||
** .removeSelection ([nodes], bool, bool) removes one or multiple elements to the selection. If boolean is set to true: callback will be called afterwards. If last boolean is set to true, it also removes them from the possible selectable nodes if they were. | ||
** .toggleSelection ([nodes], bool, bool) toggles one or multiple elements to the selection. If element is not in selection it will be added, if it is already selected, it will be removed. If boolean is set to true: callback will be called afterward. If last boolean is set to true, it also removes selected elements from possible selectable nodes & doesn’t add them to selectables if they are not. | ||
** .setSelection ([nodes], bool, bool) sets the selection to one or multiple elements. If boolean is set to true: callback will be called afterwards. By default, adds new elements also to the list of selectables (can be turned off by setting the last boolean to true) | ||
** .clearSelection ([nodes], bool) remove all elements from the selection. If boolean is set to true: callback will be called afterwards. | ||
** .addSelectables ([nodes], bool) add elements that can be selected. Intelligent algorithm never adds elements twice. If set to true: will also add them to the current selection | ||
** .removeSelectables ([nodes], bool) remove elements that can be selected. Also removes the 'selected' class from those elements if boolean is set to true. | ||
** .getSelectables () returns all nodes that can be selected. | ||
** .setSelectables ([nodes], bool, bool) sets all elements that can be selected. Removes all current selectables (& their respective applied classes). Adds the new set to the selectables set. Thus, replacing the original set. First boolean if old elements should be removed from the selection. Second boolean if new elements should be added to the selection. | ||
** .getCurrentCursorPosition () returns the last seen position of the cursor/selector | ||
** .getInitialCursorPosition () returns the first position of the cursor/selector | ||
** .getCursorPositionDifference (bool) returns object with the x, y difference between the initial and the last cursor position. If the first argument is set to true, it will instead return the x, y difference to the previous selection | ||
** .getCursorPos (event, node, bool) returns the cursor x, y coordinates based on a click event object. The click event object is required. By default, takes scroll and area into consideration. Area is this.area by default and can be fully ignored by setting the second argument explicitely to false. Scroll can be ignored by setting the third argument to true. | ||
** and everything else | ||
STAR THIS PLUGIN ON GITHUB: | ||
https://github.com/ThibaultJanBeyer/DragSelect | ||
@@ -73,2 +18,4 @@ Please give it a like, this is what makes me happy :-) | ||
{*} {*} STAR THIS PLUGIN ON GITHUB: {*} {*} | ||
****************************************** | ||
@@ -95,2 +42,6 @@ ********* The MIT License (MIT) ********** | ||
SOFTWARE. | ||
--- Notes --- | ||
Checking types using JS-Docs inspired by this post: | ||
https://medium.com/@trukrs/type-safe-javascript-with-jsdoc-7a2a63209b76 | ||
--- | ||
*/ | ||
@@ -101,64 +52,93 @@ | ||
/** | ||
* DragSelect Class. | ||
* | ||
* @constructor | ||
* @param {Object} options - The options object. | ||
*/ | ||
function DragSelect(options) { | ||
this.multiSelectKeyPressed; | ||
this.initialCursorPos = { x: 0, y: 0 }; | ||
this.newCursorPos = { x: 0, y: 0 }; | ||
this.previousCursorPos = { x: 0, y: 0 }; | ||
this.initialScroll; | ||
this.selected = []; | ||
this._prevSelected = []; // memory to fix #9 | ||
class DragSelect { | ||
/** @type {boolean} */ | ||
_multiSelectKeyPressed = false; | ||
/** @type {{x: number, y: number}} */ | ||
_initialCursorPos = { x: 0, y: 0 }; | ||
/** @type {{x: number, y: number}} */ | ||
_newCursorPos = { x: 0, y: 0 }; | ||
/** @type {{x: number, y: number}} */ | ||
_previousCursorPos = { x: 0, y: 0 }; | ||
/** @type {{x: number, y: number}} */ | ||
_initialScroll = { x: 0, y: 0 }; | ||
/** @type {Array.<(SVGElement|HTMLElement)>} */ | ||
_selected = []; | ||
/** @type {Array.<(SVGElement|HTMLElement)>} */ | ||
_prevSelected = []; // memory to fix #9 | ||
_lastTouch; | ||
this._createBindings(); | ||
this._setupOptions(options); | ||
this.start(); | ||
} | ||
/** | ||
* @constructor | ||
* @param {object} options - The options object. | ||
* @param {HTMLElement | SVGElement | Document} [options.area=document] area in which you can drag. If not provided it will be the whole document | ||
* @param {number} [options.autoScrollSpeed=1] Speed in which the area scrolls while selecting (if available). Unit is pixel per movement. Default = 1 | ||
* @param {Function} [options.callback=(selected, event) => {}] a callback function that gets fired when the element is dropped. This callback gets a property which is an array that holds all selected nodes. The second property passed is the event object. | ||
* @param {boolean} [options.customStyles=false] if set to true, no styles (except for position absolute) will be applied by default | ||
* @param {string} [options.hoverClass=ds-hover] the class assigned to the mouse hovered items | ||
* @param {boolean} [options.multiSelectMode=false] Add newly selected elements to the selection instead of replacing them. Default = false | ||
* @param {Function} [options.onDragMove=()=>{}] It is fired when the user drags. This callback gets the event object. Executed before DragSelect function code ran, after getting the current mouse position. | ||
* @param {Function} [options.onDragStartBegin=()=>{}] Is fired when the user clicks in the area. This callback gets the event object. Executed *before* DragSelect function code ran. | ||
* @param {Function} [options.onDragStart=()=>{}] It is fired when the user clicks in the area. This callback gets the event object. Executed after DragSelect function code ran, before the setup of event listeners. | ||
* @param {Function} [options.onElementSelect=()=>{}] It is fired every time an element is selected. This callback gets a property which is the just selected node | ||
* @param {Function} [options.onElementUnselect=()=>{}] It is fired every time an element is de-selected. This callback gets a property which is the just de-selected node | ||
* @param {string} [options.selectableClass=ds-selectable] the class assigned to the elements that can be selected | ||
* @param {HTMLElement[] | SVGElement[] | HTMLElement | SVGElement} [options.selectables=[]] the elements that can be selected | ||
* @param {string} [options.selectedClass=ds-selected] the class assigned to the selected items | ||
* @param {HTMLElement} [options.selector=HTMLElement] the square that will draw the selection | ||
* @param {string} [options.selectorClass=ds-selector] the class assigned to the square selector helper | ||
* @param {string[]} [options.multiSelectKeys=['ctrlKey', 'shiftKey', 'metaKey']] An array of keys that allows switching to the multi-select mode (see the @multiSelectMode option). The only possible values are keys that are provided via the event object. So far: <kbd>ctrlKey</kbd>, <kbd>shiftKey</kbd>, <kbd>metaKey</kbd> and <kbd>altKey</kbd>. Provide an empty array `[]` if you want to turn off the functionality. | ||
*/ | ||
constructor({ | ||
area = document, | ||
autoScrollSpeed = 1, | ||
callback = () => {}, | ||
customStyles = false, | ||
hoverClass = 'ds-hover', | ||
multiSelectKeys = ['ctrlKey', 'shiftKey', 'metaKey'], | ||
multiSelectMode = false, | ||
onDragMove = function() {}, | ||
onDragStart = function() {}, | ||
onDragStartBegin = function() {}, | ||
onElementSelect = function() {}, | ||
onElementUnselect = function() {}, | ||
selectableClass = 'ds-selectable', | ||
selectables = [], | ||
selectedClass = 'ds-selected', | ||
selector = undefined, | ||
selectorClass = 'ds-selector' | ||
}) { | ||
this.selectedClass = selectedClass; | ||
this.hoverClass = hoverClass; | ||
this.selectorClass = selectorClass; | ||
this.selectableClass = selectableClass; | ||
this.selectables = []; | ||
this._handleSelectables(this._toArray(selectables)); | ||
this.multiSelectKeys = multiSelectKeys; | ||
this.multiSelectMode = multiSelectMode; | ||
this.autoScrollSpeed = autoScrollSpeed === 0 ? 0 : autoScrollSpeed; | ||
this.selectCallback = onElementSelect; | ||
this.unselectCallback = onElementUnselect; | ||
this.onDragStartBegin = onDragStartBegin; | ||
this.moveStartCallback = onDragStart; | ||
this.moveCallback = onDragMove; | ||
this.callback = callback; | ||
this.area = this._handleArea(area); | ||
this.customStyles = customStyles; | ||
/** | ||
* Binds the `this` to the event listener functions | ||
*/ | ||
DragSelect.prototype._createBindings = function() { | ||
this._startUp = this._startUp.bind(this); | ||
this._handleMove = this._handleMove.bind(this); | ||
this.reset = this.reset.bind(this); | ||
this._onClick = this._onClick.bind(this); | ||
}; | ||
// Selector | ||
this.selector = selector || this._createSelector(); | ||
this.selector.classList.add(this.selectorClass); | ||
this.start(); | ||
} | ||
/** | ||
* Setup the options | ||
*/ | ||
DragSelect.prototype._setupOptions = function(options) { | ||
this.selectedClass = options.selectedClass || 'ds-selected'; | ||
this.hoverClass = options.hoverClass || 'ds-hover'; | ||
this.selectorClass = options.selectorClass || 'ds-selector'; | ||
this.selectableClass = options.selectableClass || 'ds-selectable'; | ||
/** | ||
* @param {(HTMLElement|SVGElement|any)} area | ||
* @private | ||
*/ | ||
_handleArea(area) { | ||
if (area === document) return area; | ||
this.selectables = []; | ||
this._handleSelectables(this.toArray(options.selectables)); | ||
this.multiSelectKeys = options.multiSelectKeys || [ | ||
'ctrlKey', | ||
'shiftKey', | ||
'metaKey' | ||
]; | ||
this.multiSelectMode = options.multiSelectMode || false; | ||
this.autoScrollSpeed = options.autoScrollSpeed === 0 ? 0 : options.autoScrollSpeed || 1; | ||
this.selectCallback = options.onElementSelect || function() {}; | ||
this.unselectCallback = options.onElementUnselect || function() {}; | ||
this.onDragStartBegin = options.onDragStartBegin || function() {}; | ||
this.moveStartCallback = options.onDragStart || function() {}; | ||
this.moveCallback = options.onDragMove || function() {}; | ||
this.callback = options.callback || function() {}; | ||
this.area = options.area || document; | ||
this.customStyles = options.customStyles; | ||
// Area has to have a special position attribute for calculations | ||
if (this.area !== document) { | ||
var computedArea = getComputedStyle(this.area); | ||
var isPositioned = | ||
// Area has to have a special position attribute for calculations | ||
const computedArea = getComputedStyle(area); | ||
const isPositioned = | ||
computedArea.position === 'absolute' || | ||
@@ -168,1183 +148,1075 @@ computedArea.position === 'relative' || | ||
if (!isPositioned) { | ||
this.area.style.position = 'relative'; | ||
area.style.position = 'relative'; | ||
} | ||
return area; | ||
} | ||
// Selector | ||
this.selector = options.selector || this._createSelector(); | ||
this.addClass(this.selector, this.selectorClass); | ||
}; | ||
/** | ||
* Add/Remove Selectables also handles css classes and event listeners. | ||
* @param {HTMLElement[]|SVGElement[]} selectables - selectable elements. | ||
* @param {boolean} [remove] - if elements should be removed. | ||
* @param {boolean} [fromSelection] - if elements should also be added/removed to the selection. | ||
* @private | ||
*/ | ||
_handleSelectables(selectables, remove, fromSelection) { | ||
for (var index = 0; index < selectables.length; index++) { | ||
var selectable = selectables[index]; | ||
var indexOf = this.selectables.indexOf(selectable); | ||
/** | ||
* Add/Remove Selectables also handles css classes and event listeners. | ||
* | ||
* @param {Object} selectables - selectable elements. | ||
* @param {Boolean} remove - if elements should be removed. | ||
* @param {Boolean} fromSelection - if elements should also be added/removed to the selection. | ||
*/ | ||
DragSelect.prototype._handleSelectables = function( | ||
selectables, | ||
remove, | ||
fromSelection | ||
) { | ||
for (var index = 0; index < selectables.length; index++) { | ||
var selectable = selectables[index]; | ||
var indexOf = this.selectables.indexOf(selectable); | ||
if (indexOf < 0 && !remove) { | ||
// add | ||
this.addClass(selectable, this.selectableClass); | ||
selectable.addEventListener('click', this._onClick); | ||
this.selectables.push(selectable); | ||
// also add to current selection | ||
if (fromSelection && this.selected.indexOf(selectable) < 0) { | ||
this.addClass(selectable, this.selectedClass); | ||
this.selected.push(selectable); | ||
if (indexOf < 0 && !remove) { | ||
this._addSelectable(selectable, fromSelection); | ||
} else if (indexOf > -1 && remove) { | ||
this._removeSelectable(selectable, indexOf, fromSelection); | ||
} | ||
} else if (indexOf > -1 && remove) { | ||
// remove | ||
this.removeClass(selectable, this.hoverClass); | ||
this.removeClass(selectable, this.selectableClass); | ||
selectable.removeEventListener('click', this._onClick); | ||
this.selectables.splice(indexOf, 1); | ||
// also remove from current selection | ||
if (fromSelection && this.selected.indexOf(selectable) > -1) { | ||
this.removeClass(selectable, this.selectedClass); | ||
this.selected.splice(this.selected.indexOf(selectable), 1); | ||
} | ||
} | ||
} | ||
}; | ||
/** | ||
* Triggers when a node is actively selected. | ||
* | ||
* This might be an "onClick" method but it also triggers when | ||
* <button> nodes are pressed via the keyboard. | ||
* Making DragSelect accessible for everyone! | ||
* | ||
* @param {Object} selectables - selectable elements. | ||
* @param {Boolean} remove - if elements were removed. | ||
*/ | ||
DragSelect.prototype._onClick = function(event) { | ||
/** | ||
* @param {(HTMLElement|SVGElement)} selectable | ||
* @param {boolean} toSelection also adds it to the current selection | ||
* @private | ||
*/ | ||
_addSelectable(selectable, toSelection) { | ||
selectable.classList.add(this.selectableClass); | ||
selectable.addEventListener('click', this._onClick); | ||
this.selectables.push(selectable); | ||
if (this.mouseInteraction) { | ||
return; | ||
} // fix firefox doubleclick issue | ||
if (this.isRightClick(event)) { | ||
return; | ||
// also add to current selection | ||
if (toSelection && this._selected.indexOf(selectable) < 0) { | ||
selectable.classList.add(this.selectedClass); | ||
this._selected.push(selectable); | ||
} | ||
} | ||
var node = event.target; | ||
/** | ||
* @param {(HTMLElement|SVGElement)} selectable | ||
* @param {number} indexOf | ||
* @param {boolean} [fromSelection] also adds it to the current selection | ||
* @private | ||
*/ | ||
_removeSelectable(selectable, indexOf, fromSelection) { | ||
selectable.classList.remove(this.hoverClass); | ||
selectable.classList.remove(this.selectableClass); | ||
selectable.removeEventListener('click', this._onClick); | ||
this.selectables.splice(indexOf, 1); | ||
if (this.isMultiSelectKeyPressed(event)) { | ||
this._prevSelected = this.selected.slice(); | ||
} // #9 | ||
else { | ||
this._prevSelected = []; | ||
} // #9 | ||
this.checkIfInsideSelection(true); // reset selection if no multiselectionkeypressed | ||
if (this.selectables.indexOf(node) > -1) { | ||
this.toggle(node); | ||
// also remove from current selection | ||
if (fromSelection && this._selected.indexOf(selectable) > -1) { | ||
selectable.classList.remove(this.selectedClass); | ||
this._selected.splice(this._selected.indexOf(selectable), 1); | ||
} | ||
} | ||
this.reset(); | ||
}; | ||
/** | ||
* Triggers when a node is actively selected. | ||
* | ||
* This might be an "onClick" method but it also triggers when | ||
* <button> nodes are pressed via the keyboard. | ||
* Making DragSelect accessible for everyone! | ||
* | ||
* @param {MouseEvent} event | ||
* @private | ||
*/ | ||
_onClick = event => { | ||
if (this.mouseInteraction) { | ||
return; | ||
} // fix firefox doubleclick issue | ||
if (this._isRightClick(event)) { | ||
return; | ||
} | ||
/** | ||
* Create the selector node when not provided by options object. | ||
* | ||
* @return {Node} | ||
*/ | ||
DragSelect.prototype._createSelector = function() { | ||
var selector = document.createElement('div'); | ||
/** @type {any} */ | ||
const node = event.target; | ||
selector.style.position = 'absolute'; | ||
if (!this.customStyles) { | ||
selector.style.background = 'rgba(0, 0, 255, 0.1)'; | ||
selector.style.border = '1px solid rgba(0, 0, 255, 0.45)'; | ||
selector.style.display = 'none'; | ||
selector.style.pointerEvents = 'none'; // fix for issue #8 (ie11+) | ||
} | ||
if (this._isMultiSelectKeyPressed(event)) { | ||
this._prevSelected = this._selected.slice(); | ||
} // #9 | ||
else { | ||
this._prevSelected = []; | ||
} // #9 | ||
var _area = this.area === document ? document.body : this.area; | ||
_area.appendChild(selector); | ||
this.checkIfInsideSelection(true); // reset selection if no multiselectionkeypressed | ||
return selector; | ||
}; | ||
if (this.selectables.indexOf(node) > -1) { | ||
this.toggle(node); | ||
} | ||
// Start | ||
////////////////////////////////////////////////////////////////////////////////////// | ||
this.reset(); | ||
}; | ||
/** | ||
* Starts the functionality. Automatically triggered when created. | ||
*/ | ||
DragSelect.prototype.start = function() { | ||
/** | ||
* Create the selector node when not provided by options object. | ||
* @return {HTMLElement} | ||
* @private | ||
*/ | ||
_createSelector() { | ||
var selector = document.createElement('div'); | ||
this.area.addEventListener( 'mousedown', this._startUp ); | ||
this.area.addEventListener( 'touchstart', this._startUp, { passive: false } ); | ||
selector.style.position = 'absolute'; | ||
if (!this.customStyles) { | ||
selector.style.background = 'rgba(0, 0, 255, 0.1)'; | ||
selector.style.border = '1px solid rgba(0, 0, 255, 0.45)'; | ||
selector.style.display = 'none'; | ||
selector.style.pointerEvents = 'none'; // fix for issue #8 (ie11+) | ||
} | ||
}; | ||
var _area = this.area === document ? document.body : this.area; | ||
_area.appendChild(selector); | ||
/** | ||
* Startup when the area is clicked. | ||
* | ||
* @param {Object} event - The event object. | ||
*/ | ||
DragSelect.prototype._startUp = function(event) { | ||
return selector; | ||
} | ||
// touchmove handler | ||
if(event.type === 'touchstart') | ||
// Call preventDefault() to prevent double click issue, see https://github.com/ThibaultJanBeyer/DragSelect/pull/29 & https://developer.mozilla.org/vi/docs/Web/API/Touch_events/Supporting_both_TouchEvent_and_MouseEvent | ||
event.preventDefault(); | ||
// Start | ||
////////////////////////////////////////////////////////////////////////////////////// | ||
// callback | ||
this.onDragStartBegin(event); | ||
if (this._breaked) { return false; } | ||
if (this.isRightClick(event)) { | ||
return; | ||
/** | ||
* Starts the functionality. Automatically triggered when created. | ||
* Also, reset the functionality after a teardown | ||
*/ | ||
start() { | ||
this.area.addEventListener('mousedown', this._startUp); | ||
this.area.addEventListener('touchstart', this._startUp, { passive: false }); | ||
} | ||
this.mouseInteraction = true; | ||
this.selector.style.display = 'block'; | ||
/** | ||
* Startup when the area is clicked. | ||
* @param {Object} event - The event object. | ||
* @private | ||
*/ | ||
_startUp = event => { | ||
// touchmove handler | ||
if (event.type === 'touchstart') | ||
// Call preventDefault() to prevent double click issue, see https://github.com/ThibaultJanBeyer/DragSelect/pull/29 & https://developer.mozilla.org/vi/docs/Web/API/Touch_events/Supporting_both_TouchEvent_and_MouseEvent | ||
event.preventDefault(); | ||
if (this.isMultiSelectKeyPressed(event)) { | ||
this._prevSelected = this.selected.slice(); | ||
} // #9 | ||
else { | ||
this._prevSelected = []; | ||
} // #9 | ||
// callback | ||
this.onDragStartBegin(event); | ||
if (this._breaked) { | ||
return false; | ||
} | ||
// move element on location | ||
this._getStartingPositions(event); | ||
this.checkIfInsideSelection(true); | ||
if (this._isRightClick(event)) { | ||
return; | ||
} | ||
this.selector.style.display = 'none'; // hidden unless moved, fix for issue #8 | ||
this.mouseInteraction = true; | ||
this.selector.style.display = 'block'; | ||
// callback | ||
this.moveStartCallback(event); | ||
if (this._breaked) { return false; } | ||
if (this._isMultiSelectKeyPressed(event)) { | ||
this._prevSelected = this._selected.slice(); | ||
} // #9 | ||
else { | ||
this._prevSelected = []; | ||
} // #9 | ||
// event listeners | ||
this.area.removeEventListener( 'mousedown', this._startUp ); | ||
this.area.removeEventListener( 'touchstart', this._startUp, { passive: false } ); | ||
this.area.addEventListener( 'mousemove', this._handleMove ); | ||
this.area.addEventListener( 'touchmove', this._handleMove ); | ||
document.addEventListener( 'mouseup', this.reset ); | ||
document.addEventListener( 'touchend', this.reset ); | ||
// move element on location | ||
this._getStartingPositions(event); | ||
this.checkIfInsideSelection(true); | ||
}; | ||
this.selector.style.display = 'none'; // hidden unless moved, fix for issue #8 | ||
/** | ||
* Check if some multiselection modifier key is pressed | ||
* | ||
* @param {Object} event - The event object. | ||
* @return {Boolean} this.isMultiSelectKeyPressed | ||
*/ | ||
DragSelect.prototype.isMultiSelectKeyPressed = function(event) { | ||
this.multiSelectKeyPressed = false; | ||
if (this.multiSelectMode) { | ||
this.multiSelectKeyPressed = true; | ||
} else { | ||
for (var index = 0; index < this.multiSelectKeys.length; index++) { | ||
var mKey = this.multiSelectKeys[index]; | ||
if (event[mKey]) { | ||
this.multiSelectKeyPressed = true; | ||
} | ||
// callback | ||
this.moveStartCallback(event); | ||
if (this._breaked) { | ||
return false; | ||
} | ||
} | ||
return this.multiSelectKeyPressed; | ||
}; | ||
// event listeners | ||
this.area.removeEventListener('mousedown', this._startUp); | ||
this.area.removeEventListener('touchstart', this._startUp, { | ||
passive: false | ||
}); | ||
this.area.addEventListener('mousemove', this._handleMove); | ||
this.area.addEventListener('touchmove', this._handleMove); | ||
document.addEventListener('mouseup', this.reset); | ||
document.addEventListener('touchend', this.reset); | ||
}; | ||
/** | ||
* Grabs the starting position of all needed elements | ||
* | ||
* @param {Object} event - The event object. | ||
*/ | ||
DragSelect.prototype._getStartingPositions = function(event) { | ||
this.initialCursorPos = this.newCursorPos = this._getCursorPos( | ||
event, | ||
this.area | ||
); | ||
this.initialScroll = this.getScroll(this.area); | ||
/** | ||
* Check if some multiselection modifier key is pressed | ||
* @param {Object} event - The event object. | ||
* @return {boolean} this._isMultiSelectKeyPressed | ||
* @private | ||
*/ | ||
_isMultiSelectKeyPressed(event) { | ||
this._multiSelectKeyPressed = false; | ||
var selectorPos = {}; | ||
selectorPos.x = this.initialCursorPos.x + this.initialScroll.x; | ||
selectorPos.y = this.initialCursorPos.y + this.initialScroll.y; | ||
selectorPos.w = 0; | ||
selectorPos.h = 0; | ||
this.updatePos(this.selector, selectorPos); | ||
}; | ||
if (this.multiSelectMode) { | ||
this._multiSelectKeyPressed = true; | ||
} else { | ||
for (var index = 0; index < this.multiSelectKeys.length; index++) { | ||
var mKey = this.multiSelectKeys[index]; | ||
if (event[mKey]) { | ||
this._multiSelectKeyPressed = true; | ||
} | ||
} | ||
} | ||
// Movements/Sizing of selection | ||
////////////////////////////////////////////////////////////////////////////////////// | ||
return this._multiSelectKeyPressed; | ||
} | ||
/** | ||
* Handles what happens while the mouse is moved | ||
* | ||
* @param {Object} event - The event object. | ||
*/ | ||
DragSelect.prototype._handleMove = function(event) { | ||
var selectorPos = this.getPosition(event); | ||
/** | ||
* Grabs the starting position of all needed elements | ||
* @param {Object} event - The event object. | ||
* @private | ||
*/ | ||
_getStartingPositions(event) { | ||
this._initialCursorPos = this._newCursorPos = this._getCursorPos( | ||
event, | ||
this.area | ||
); | ||
this._initialScroll = this.getScroll(this.area); | ||
// callback | ||
this.moveCallback(event); | ||
if (this._breaked) { | ||
return false; | ||
var selectorPos = {}; | ||
selectorPos.x = this._initialCursorPos.x + this._initialScroll.x; | ||
selectorPos.y = this._initialCursorPos.y + this._initialScroll.y; | ||
selectorPos.w = 0; | ||
selectorPos.h = 0; | ||
this._updatePos(this.selector, selectorPos); | ||
} | ||
this.selector.style.display = 'block'; // hidden unless moved, fix for issue #8 | ||
// Movements/Sizing of selection | ||
////////////////////////////////////////////////////////////////////////////////////// | ||
// move element on location | ||
this.updatePos(this.selector, selectorPos); | ||
this.checkIfInsideSelection(); | ||
/** | ||
* Handles what happens while the mouse is moved | ||
* @param {Object} event - The event object. | ||
* @private | ||
*/ | ||
_handleMove = event => { | ||
var selectorPos = this._getPosition(event); | ||
// scroll area if area is scrollable | ||
this._autoScroll(event); | ||
}; | ||
// callback | ||
this.moveCallback(event); | ||
if (this._breaked) { | ||
return false; | ||
} | ||
/** | ||
* Calculates and returns the exact x,y w,h positions of the selector element | ||
* | ||
* @param {Object} event - The event object. | ||
*/ | ||
DragSelect.prototype.getPosition = function(event) { | ||
var cursorPosNew = this._getCursorPos(event, this.area); | ||
var scrollNew = this.getScroll(this.area); | ||
this.selector.style.display = 'block'; // hidden unless moved, fix for issue #8 | ||
// save for later retrieval | ||
this.newCursorPos = cursorPosNew; | ||
// move element on location | ||
this._updatePos(this.selector, selectorPos); | ||
this.checkIfInsideSelection(); | ||
// if area or document is scrolled those values have to be included aswell | ||
var scrollAmount = { | ||
x: scrollNew.x - this.initialScroll.x, | ||
y: scrollNew.y - this.initialScroll.y | ||
// scroll area if area is scrollable | ||
this._autoScroll(event); | ||
}; | ||
/** check for direction | ||
* | ||
* This is quite complicated math, so also quite complicated to explain. Lemme’ try: | ||
* | ||
* Problem #1: | ||
* Sadly in HTML we can not have negative sizes. | ||
* so if we want to scale our element 10px to the right then it is easy, | ||
* we just have to add +10px to the width. But if we want to scale the element | ||
* -10px to the left then things become more complicated, we have to move | ||
* the element -10px to the left on the x axis and also scale the element | ||
* by +10px width to fake a negative sizing. | ||
* | ||
* One solution to this problem is using css-transforms scale() with | ||
* transform-origin of top left. BUT we can’t use this since it will size | ||
* everything, then when your element has a border for example, the border will | ||
* get inanely huge. Also transforms are not widely supported in IE. | ||
* | ||
* Example #1: | ||
* Unfortunately, things get even more complicated when we are inside a scrollable | ||
* DIV. Then, let’s say we scoll to the right by 10px and move the cursor right by 5px in our | ||
* checks we have to substract 10px from the initialcursor position in our check | ||
* (since the inital position is moved to the left by 10px) so in our example: | ||
* 1. cursorPosNew.x (5) > initialCursorPos.x (0) - scrollAmount.x (10) === 5 > -10 === true | ||
* then reset the x position to its initial position (since we might have changed that | ||
* position when scrolling to the left before going right) in our example: | ||
* 2. selectorPos.x = initialCursorPos.x (0) + initialScroll.x (0) === 0; | ||
* then we cann calculate the elements width, which is | ||
* the new cursor position minus the initial one plus the scroll amount, so in our example: | ||
* 3. selectorPos.w = cursorPosNew.x (5) - initialCursorPos.x (0) + scrollAmount.x (10) === 15; | ||
* | ||
* let’s say after that movement we now scroll 20px to the left and move our cursor by 30px to the left: | ||
* 1b. cursorPosNew.x (-30) > initialCursorPos.x (0) - scrollAmount.x (-20) === -30 > -20 === false; | ||
* 2b. selectorPos.x = cursorPosNew.x (-30) + scrollNew.x (-20) | ||
* === -50; // move left position to cursor (for more info see Problem #1) | ||
* 3b. selectorPos.w = initialCursorPos.x (0) - cursorPosNew.x (-30) - scrollAmount.x (-20) | ||
* === 0--30--20 === 0+30+20 === 50; // scale width to original left position (for more info see Problem #1) | ||
* | ||
* same thing has to be done for top/bottom | ||
* | ||
* I hope that makes sence, try stuff out and play around with variables to get a hang of it. | ||
/** | ||
* Calculates and returns the exact x,y,w,h positions of the selector element | ||
* @param {object} [event] - The event object. | ||
* @returns {{x:number,y:number,w:number,h:number}} | ||
* @private | ||
*/ | ||
var selectorPos = {}; | ||
_getPosition(event) { | ||
var cursorPosNew = this._getCursorPos(event, this.area); | ||
var scrollNew = this.getScroll(this.area); | ||
// right | ||
if (cursorPosNew.x > this.initialCursorPos.x - scrollAmount.x) { | ||
// 1. | ||
selectorPos.x = this.initialCursorPos.x + this.initialScroll.x; // 2. | ||
selectorPos.w = cursorPosNew.x - this.initialCursorPos.x + scrollAmount.x; // 3. | ||
// left | ||
} else { | ||
// 1b. | ||
selectorPos.x = cursorPosNew.x + scrollNew.x; // 2b. | ||
selectorPos.w = this.initialCursorPos.x - cursorPosNew.x - scrollAmount.x; // 3b. | ||
} | ||
// save for later retrieval | ||
this._newCursorPos = cursorPosNew; | ||
// bottom | ||
if (cursorPosNew.y > this.initialCursorPos.y - scrollAmount.y) { | ||
selectorPos.y = this.initialCursorPos.y + this.initialScroll.y; | ||
selectorPos.h = cursorPosNew.y - this.initialCursorPos.y + scrollAmount.y; | ||
// top | ||
} else { | ||
selectorPos.y = cursorPosNew.y + scrollNew.y; | ||
selectorPos.h = this.initialCursorPos.y - cursorPosNew.y - scrollAmount.y; | ||
} | ||
// if area or document is scrolled those values have to be included aswell | ||
var scrollAmount = { | ||
x: scrollNew.x - this._initialScroll.x, | ||
y: scrollNew.y - this._initialScroll.y | ||
}; | ||
return selectorPos; | ||
}; | ||
/** check for direction | ||
* | ||
* This is quite complicated math, so also quite complicated to explain. Lemme’ try: | ||
* | ||
* Problem #1: | ||
* Sadly in HTML we can not have negative sizes. | ||
* so if we want to scale our element 10px to the right then it is easy, | ||
* we just have to add +10px to the width. But if we want to scale the element | ||
* -10px to the left then things become more complicated, we have to move | ||
* the element -10px to the left on the x axis and also scale the element | ||
* by +10px width to fake a negative sizing. | ||
* | ||
* One solution to this problem is using css-transforms scale() with | ||
* transform-origin of top left. BUT we can’t use this since it will size | ||
* everything, then when your element has a border for example, the border will | ||
* get inanely huge. Also transforms are not widely supported in IE. | ||
* | ||
* Example #1: | ||
* Unfortunately, things get even more complicated when we are inside a scrollable | ||
* DIV. Then, let’s say we scoll to the right by 10px and move the cursor right by 5px in our | ||
* checks we have to substract 10px from the initialcursor position in our check | ||
* (since the inital position is moved to the left by 10px) so in our example: | ||
* 1. cursorPosNew.x (5) > initialCursorPos.x (0) - scrollAmount.x (10) === 5 > -10 === true | ||
* then reset the x position to its initial position (since we might have changed that | ||
* position when scrolling to the left before going right) in our example: | ||
* 2. selectorPos.x = initialCursorPos.x (0) + initialScroll.x (0) === 0; | ||
* then we cann calculate the elements width, which is | ||
* the new cursor position minus the initial one plus the scroll amount, so in our example: | ||
* 3. selectorPos.w = cursorPosNew.x (5) - initialCursorPos.x (0) + scrollAmount.x (10) === 15; | ||
* | ||
* let’s say after that movement we now scroll 20px to the left and move our cursor by 30px to the left: | ||
* 1b. cursorPosNew.x (-30) > initialCursorPos.x (0) - scrollAmount.x (-20) === -30 > -20 === false; | ||
* 2b. selectorPos.x = cursorPosNew.x (-30) + scrollNew.x (-20) | ||
* === -50; // move left position to cursor (for more info see Problem #1) | ||
* 3b. selectorPos.w = initialCursorPos.x (0) - cursorPosNew.x (-30) - scrollAmount.x (-20) | ||
* === 0--30--20 === 0+30+20 === 50; // scale width to original left position (for more info see Problem #1) | ||
* | ||
* same thing has to be done for top/bottom | ||
* | ||
* I hope that makes sence, try stuff out and play around with variables to get a hang of it. | ||
*/ | ||
var selectorPos = {}; | ||
// Colision detection | ||
////////////////////////////////////////////////////////////////////////////////////// | ||
// right | ||
if (cursorPosNew.x > this._initialCursorPos.x - scrollAmount.x) { | ||
// 1. | ||
selectorPos.x = this._initialCursorPos.x + this._initialScroll.x; // 2. | ||
selectorPos.w = | ||
cursorPosNew.x - this._initialCursorPos.x + scrollAmount.x; // 3. | ||
// left | ||
} else { | ||
// 1b. | ||
selectorPos.x = cursorPosNew.x + scrollNew.x; // 2b. | ||
selectorPos.w = | ||
this._initialCursorPos.x - cursorPosNew.x - scrollAmount.x; // 3b. | ||
} | ||
/** | ||
* Checks if element is inside selection and takes action based on that | ||
* | ||
* force handles first clicks and accessibility. Here is user is clicking directly onto | ||
* some element at start, (contrary to later hovers) we can assume that he | ||
* really wants to select/deselect that item. | ||
* | ||
* @param {Boolean} force – forces through. | ||
* @return {Boolean} | ||
*/ | ||
DragSelect.prototype.checkIfInsideSelection = function(force) { | ||
var anyInside = false; | ||
for( var i = 0, il = this.selectables.length; i < il; i++ ) { | ||
var selectable = this.selectables[i]; | ||
var scroll = this.getScroll(this.area); | ||
var selectionRect = { | ||
y: this.selector.getBoundingClientRect().top + scroll.y, | ||
x: this.selector.getBoundingClientRect().left + scroll.x, | ||
h: this.selector.offsetHeight, | ||
w: this.selector.offsetWidth | ||
}; | ||
if( this._isElementTouching( selectable, selectionRect, scroll ) ) { | ||
this._handleSelection( selectable, force ); | ||
anyInside = true; | ||
// bottom | ||
if (cursorPosNew.y > this._initialCursorPos.y - scrollAmount.y) { | ||
selectorPos.y = this._initialCursorPos.y + this._initialScroll.y; | ||
selectorPos.h = | ||
cursorPosNew.y - this._initialCursorPos.y + scrollAmount.y; | ||
// top | ||
} else { | ||
this._handleUnselection( selectable, force ); | ||
selectorPos.y = cursorPosNew.y + scrollNew.y; | ||
selectorPos.h = | ||
this._initialCursorPos.y - cursorPosNew.y - scrollAmount.y; | ||
} | ||
} | ||
return anyInside; | ||
}; | ||
/** | ||
* Logic when an item is selected | ||
* | ||
* @param {Node} item – selected item. | ||
* @param {Boolean} force – forces through. | ||
*/ | ||
DragSelect.prototype._handleSelection = function(item, force) { | ||
if (this.hasClass(item, this.hoverClass) && !force) { | ||
return false; | ||
return selectorPos; | ||
} | ||
var posInSelectedArray = this.selected.indexOf(item); | ||
if (posInSelectedArray < 0) { | ||
this.select(item); | ||
} else if (posInSelectedArray > -1 && this.multiSelectKeyPressed) { | ||
this.unselect(item); | ||
} | ||
// Colision detection | ||
////////////////////////////////////////////////////////////////////////////////////// | ||
this.addClass(item, this.hoverClass); | ||
}; | ||
/** | ||
* Checks if any selectable element is inside selection. | ||
* @param {boolean} [force] forces through. Handles first clicks and accessibility. Here is user is clicking directly onto some element at start, (contrary to later hovers) we can assume that he really wants to select/deselect that item. | ||
* @return {boolean} | ||
*/ | ||
checkIfInsideSelection(force) { | ||
var anyInside = false; | ||
for (var i = 0, il = this.selectables.length; i < il; i++) { | ||
var selectable = this.selectables[i]; | ||
/** | ||
* Logic when an item is de-selected | ||
* | ||
* @param {Node} item – selected item. | ||
* @param {Boolean} force – forces through. | ||
*/ | ||
DragSelect.prototype._handleUnselection = function(item, force) { | ||
if (!this.hasClass(item, this.hoverClass) && !force) { | ||
return false; | ||
var scroll = this.getScroll(this.area); | ||
var selectionRect = { | ||
y: this.selector.getBoundingClientRect().top + scroll.y, | ||
x: this.selector.getBoundingClientRect().left + scroll.x, | ||
h: this.selector.offsetHeight, | ||
w: this.selector.offsetWidth | ||
}; | ||
if (this._isElementTouching(selectable, selectionRect, scroll)) { | ||
this._handleSelection(selectable, force); | ||
anyInside = true; | ||
} else { | ||
this._handleUnselection(selectable, force); | ||
} | ||
} | ||
return anyInside; | ||
} | ||
var posInSelectedArray = this.selected.indexOf(item); | ||
var isInPrevSelection = this._prevSelected.indexOf(item); // #9 | ||
/** | ||
* Special algorithm for issue #9. | ||
* if a multiselectkey is pressed, ds 'remembers' the last selection and reverts | ||
* to that state if the selection is not kept, to mimic the natural OS behaviour | ||
* = if item was selected and is not in selection anymore, reselect it | ||
* = if item was not selected and is not in selection anymore, unselect it | ||
* Logic when an item is selected | ||
* @param {(HTMLElement|SVGElement)} item selected item. | ||
* @param {boolean} [force] forces through. | ||
* @private | ||
*/ | ||
if (posInSelectedArray > -1 && isInPrevSelection < 0) { | ||
this.unselect(item); | ||
} else if (posInSelectedArray < 0 && isInPrevSelection > -1) { | ||
this.select(item); | ||
} | ||
_handleSelection(item, force) { | ||
if (item.classList.contains(this.hoverClass) && !force) { | ||
return false; | ||
} | ||
var posInSelectedArray = this._selected.indexOf(item); | ||
this.removeClass(item, this.hoverClass); | ||
}; | ||
if (posInSelectedArray < 0) { | ||
this.select(item); | ||
} else if (posInSelectedArray > -1 && this._multiSelectKeyPressed) { | ||
this.unselect(item); | ||
} | ||
/** | ||
* Adds an item to the selection. | ||
* | ||
* @param {Node} item – item to select. | ||
* @return {Node} item | ||
*/ | ||
DragSelect.prototype.select = function(item) { | ||
if (this.selected.indexOf(item) > -1) { | ||
return false; | ||
item.classList.add(this.hoverClass); | ||
} | ||
this.selected.push(item); | ||
this.addClass(item, this.selectedClass); | ||
/** | ||
* Logic when an item is de-selected | ||
* @param {(HTMLElement|SVGElement)} item selected item. | ||
* @param {boolean} [force] forces through. | ||
* @private | ||
*/ | ||
_handleUnselection(item, force) { | ||
if (!item.classList.contains(this.hoverClass) && !force) { | ||
return false; | ||
} | ||
var posInSelectedArray = this._selected.indexOf(item); | ||
var isInPrevSelection = this._prevSelected.indexOf(item); // #9 | ||
this.selectCallback(item); | ||
if (this._breaked) { | ||
return false; | ||
/** | ||
* Special algorithm for issue #9. | ||
* if a multiselectkey is pressed, ds 'remembers' the last selection and reverts | ||
* to that state if the selection is not kept, to mimic the natural OS behaviour | ||
* = if item was selected and is not in selection anymore, reselect it | ||
* = if item was not selected and is not in selection anymore, unselect it | ||
*/ | ||
if (posInSelectedArray > -1 && isInPrevSelection < 0) { | ||
this.unselect(item); | ||
} else if (posInSelectedArray < 0 && isInPrevSelection > -1) { | ||
this.select(item); | ||
} | ||
item.classList.remove(this.hoverClass); | ||
} | ||
return item; | ||
}; | ||
/** | ||
* Adds an item to the selection. | ||
* @param {(HTMLElement|SVGElement)} item selected item. | ||
* @return {(HTMLElement|SVGElement|false)} item | ||
*/ | ||
select(item) { | ||
if (this._selected.indexOf(item) > -1) return false; | ||
/** | ||
* Removes an item from the selection. | ||
* | ||
* @param {Node} item – item to select. | ||
* @return {Node} item | ||
*/ | ||
DragSelect.prototype.unselect = function(item) { | ||
if (this.selected.indexOf(item) < 0) { | ||
return false; | ||
this._selected.push(item); | ||
item.classList.add(this.selectedClass); | ||
this.selectCallback(item); | ||
if (this._breaked) return false; | ||
return item; | ||
} | ||
this.selected.splice(this.selected.indexOf(item), 1); | ||
this.removeClass(item, this.selectedClass); | ||
/** | ||
* Removes an item from the selection. | ||
* @param {(HTMLElement|SVGElement)} item selected item. | ||
* @return {(HTMLElement|SVGElement|false)} item | ||
*/ | ||
unselect(item) { | ||
if (this._selected.indexOf(item) < 0) return false; | ||
this.unselectCallback(item); | ||
if (this._breaked) { | ||
return false; | ||
this._selected.splice(this._selected.indexOf(item), 1); | ||
item.classList.remove(this.selectedClass); | ||
this.unselectCallback(item); | ||
if (this._breaked) return false; | ||
return item; | ||
} | ||
return item; | ||
}; | ||
/** | ||
* Adds/Removes an item to the selection. | ||
* If it is already selected = remove, if not = add. | ||
* @param {(HTMLElement|SVGElement)} item – item to select. | ||
* @return {(HTMLElement|SVGElement)} item | ||
*/ | ||
toggle(item) { | ||
if (this._selected.indexOf(item) > -1) { | ||
this.unselect(item); | ||
} else { | ||
this.select(item); | ||
} | ||
/** | ||
* Adds/Removes an item to the selection. | ||
* If it is already selected = remove, if not = add. | ||
* | ||
* @param {Node} item – item to select. | ||
* @return {Node} item | ||
*/ | ||
DragSelect.prototype.toggle = function(item) { | ||
if (this.selected.indexOf(item) > -1) { | ||
this.unselect(item); | ||
} else { | ||
this.select(item); | ||
return item; | ||
} | ||
return item; | ||
}; | ||
/** | ||
* Checks if element is touched by the selector (and vice-versa) | ||
* @param {(HTMLElement|SVGElement)} element – item. | ||
* @param {Object} selectionRect – Container bounds: | ||
Example: { | ||
y: this.selector.getBoundingClientRect().top + scroll.y, | ||
x: this.selector.getBoundingClientRect().left + scroll.x, | ||
h: this.selector.offsetHeight, | ||
w: this.selector.offsetWidth | ||
}; | ||
* @param {Object} scroll – Scroll x, y values. | ||
* @return {boolean} | ||
* @private | ||
*/ | ||
_isElementTouching(element, selectionRect, scroll) { | ||
const rect = element.getBoundingClientRect(); | ||
const elementRect = { | ||
y: rect.top + scroll.y, | ||
x: rect.left + scroll.x, | ||
h: rect.height, | ||
w: rect.width | ||
}; | ||
/** | ||
* Checks if element is touched by the selector (and vice-versa) | ||
* | ||
* @param {Node} element – item. | ||
* @param {Object} selectionRect – Container bounds: | ||
Example: { | ||
y: this.selector.getBoundingClientRect().top + scroll.y, | ||
x: this.selector.getBoundingClientRect().left + scroll.x, | ||
h: this.selector.offsetHeight, | ||
w: this.selector.offsetWidth | ||
}; | ||
* @param {Object} scroll – Scroll x, y values. | ||
* @return {Boolean} | ||
*/ | ||
DragSelect.prototype._isElementTouching = function( | ||
element, | ||
selectionRect, | ||
scroll | ||
) { | ||
var elementRect = { | ||
y: element.getBoundingClientRect().top + scroll.y, | ||
x: element.getBoundingClientRect().left + scroll.x, | ||
h: element.offsetHeight || element.getBoundingClientRect().height, | ||
w: element.offsetWidth || element.getBoundingClientRect().width | ||
}; | ||
// Axis-Aligned Bounding Box Colision Detection. | ||
// Imagine following Example: | ||
// b01 | ||
// a01[1]a02 | ||
// b02 b11 | ||
// a11[2]a12 | ||
// b12 | ||
// to check if those two boxes collide we do this AABB calculation: | ||
//& a01 < a12 (left border pos box1 smaller than right border pos box2) | ||
//& a02 > a11 (right border pos box1 larger than left border pos box2) | ||
//& b01 < b12 (top border pos box1 smaller than bottom border pos box2) | ||
//& b02 > b11 (bottom border pos box1 larger than top border pos box2) | ||
// See: https://en.wikipedia.org/wiki/Minimum_bounding_box#Axis-aligned_minimum_bounding_box and https://developer.mozilla.org/en-US/docs/Games/Techniques/2D_collision_detection | ||
if ( | ||
selectionRect.x < elementRect.x + elementRect.w && | ||
selectionRect.x + selectionRect.w > elementRect.x && | ||
selectionRect.y < elementRect.y + elementRect.h && | ||
selectionRect.h + selectionRect.y > elementRect.y | ||
) { | ||
return true; // collision detected! | ||
} else { | ||
return false; | ||
// Axis-Aligned Bounding Box Colision Detection. | ||
// Imagine following Example: | ||
// b01 | ||
// a01[1]a02 | ||
// b02 b11 | ||
// a11[2]a12 | ||
// b12 | ||
// to check if those two boxes collide we do this AABB calculation: | ||
//& a01 < a12 (left border pos box1 smaller than right border pos box2) | ||
//& a02 > a11 (right border pos box1 larger than left border pos box2) | ||
//& b01 < b12 (top border pos box1 smaller than bottom border pos box2) | ||
//& b02 > b11 (bottom border pos box1 larger than top border pos box2) | ||
// See: https://en.wikipedia.org/wiki/Minimum_bounding_box#Axis-aligned_minimum_bounding_box and https://developer.mozilla.org/en-US/docs/Games/Techniques/2D_collision_detection | ||
if ( | ||
selectionRect.x < elementRect.x + elementRect.w && | ||
selectionRect.x + selectionRect.w > elementRect.x && | ||
selectionRect.y < elementRect.y + elementRect.h && | ||
selectionRect.h + selectionRect.y > elementRect.y | ||
) { | ||
return true; // collision detected! | ||
} else { | ||
return false; | ||
} | ||
} | ||
}; | ||
// Autoscroll | ||
////////////////////////////////////////////////////////////////////////////////////// | ||
// Autoscroll | ||
////////////////////////////////////////////////////////////////////////////////////// | ||
/** | ||
* Automatically Scroll the area by selecting | ||
* | ||
* @param {Object} event – event object. | ||
*/ | ||
DragSelect.prototype._autoScroll = function(event) { | ||
var edge = this.isCursorNearEdge(event, this.area); | ||
/** | ||
* Automatically Scroll the area by selecting | ||
* @param {Object} event – event object. | ||
* @private | ||
*/ | ||
_autoScroll(event) { | ||
var edge = this.isCursorNearEdge(event, this.area); | ||
var docEl = document && document.documentElement && document.documentElement.scrollTop && document.documentElement; | ||
var _area = this.area === document ? docEl || document.body : this.area; | ||
var docEl = | ||
document && | ||
document.documentElement && | ||
document.documentElement.scrollTop && | ||
document.documentElement; | ||
var _area = this.area === document ? docEl || document.body : this.area; | ||
if (edge === 'top' && _area.scrollTop > 0) { | ||
_area.scrollTop -= 1 * this.autoScrollSpeed; | ||
} else if (edge === 'bottom') { | ||
_area.scrollTop += 1 * this.autoScrollSpeed; | ||
} else if (edge === 'left' && _area.scrollLeft > 0) { | ||
_area.scrollLeft -= 1 * this.autoScrollSpeed; | ||
} else if (edge === 'right') { | ||
_area.scrollLeft += 1 * this.autoScrollSpeed; | ||
if (edge === 'top' && _area.scrollTop > 0) { | ||
_area.scrollTop -= 1 * this.autoScrollSpeed; | ||
} else if (edge === 'bottom') { | ||
_area.scrollTop += 1 * this.autoScrollSpeed; | ||
} else if (edge === 'left' && _area.scrollLeft > 0) { | ||
_area.scrollLeft -= 1 * this.autoScrollSpeed; | ||
} else if (edge === 'right') { | ||
_area.scrollLeft += 1 * this.autoScrollSpeed; | ||
} | ||
} | ||
}; | ||
/** | ||
* Check if the selector is near an edge of the area | ||
* | ||
* @param {Object} event – event object. | ||
* @param {Node} area – the area. | ||
* @return {String} top / bottom / left / right / false | ||
*/ | ||
DragSelect.prototype.isCursorNearEdge = function(event, area) { | ||
var cursorPosition = this._getCursorPos(event, area); | ||
var areaRect = this.getAreaRect(area); | ||
/** | ||
* Check if the selector is near an edge of the area | ||
* @param {Object} [event] event object. | ||
* @param {(HTMLElement|SVGElement)} area the area. | ||
* @return {('top'|'bottom'|'left'|'right'|false)} | ||
*/ | ||
isCursorNearEdge(event, area) { | ||
var cursorPosition = this._getCursorPos(event, area); | ||
var areaRect = this.getAreaRect(area); | ||
var tolerance = { | ||
x: Math.max(areaRect.width / 10, 30), | ||
y: Math.max(areaRect.height / 10, 30) | ||
}; | ||
var tolerance = { | ||
x: Math.max(areaRect.width / 10, 30), | ||
y: Math.max(areaRect.height / 10, 30) | ||
}; | ||
if (cursorPosition.y < tolerance.y) { | ||
return 'top'; | ||
} else if (areaRect.height - cursorPosition.y < tolerance.y) { | ||
return 'bottom'; | ||
} else if (areaRect.width - cursorPosition.x < tolerance.x) { | ||
return 'right'; | ||
} else if (cursorPosition.x < tolerance.x) { | ||
return 'left'; | ||
if (cursorPosition.y < tolerance.y) { | ||
return 'top'; | ||
} else if (areaRect.height - cursorPosition.y < tolerance.y) { | ||
return 'bottom'; | ||
} else if (areaRect.width - cursorPosition.x < tolerance.x) { | ||
return 'right'; | ||
} else if (cursorPosition.x < tolerance.x) { | ||
return 'left'; | ||
} | ||
return false; | ||
} | ||
return false; | ||
}; | ||
// Ending | ||
////////////////////////////////////////////////////////////////////////////////////// | ||
// Ending | ||
////////////////////////////////////////////////////////////////////////////////////// | ||
/** | ||
* Unbind functions when mouse click is released | ||
*/ | ||
reset = event => { | ||
this._previousCursorPos = this._getCursorPos(event, this.area); | ||
document.removeEventListener('mouseup', this.reset); | ||
document.removeEventListener('touchend', this.reset); | ||
this.area.removeEventListener('mousemove', this._handleMove); | ||
this.area.removeEventListener('touchmove', this._handleMove); | ||
this.area.addEventListener('mousedown', this._startUp); | ||
this.area.addEventListener('touchstart', this._startUp, { passive: false }); | ||
/** | ||
* Unbind functions when mouse click is released | ||
*/ | ||
DragSelect.prototype.reset = function( event ) { | ||
this.callback(this._selected, event); | ||
if (this._breaked) { | ||
return false; | ||
} | ||
this.previousCursorPos = this._getCursorPos( event, this.area ); | ||
document.removeEventListener( 'mouseup', this.reset ); | ||
document.removeEventListener( 'touchend', this.reset ); | ||
this.area.removeEventListener( 'mousemove', this._handleMove ); | ||
this.area.removeEventListener( 'touchmove', this._handleMove ); | ||
this.area.addEventListener( 'mousedown', this._startUp ); | ||
this.area.addEventListener( 'touchstart', this._startUp, { passive: false } ); | ||
this.selector.style.width = '0'; | ||
this.selector.style.height = '0'; | ||
this.selector.style.display = 'none'; | ||
this.callback( this.selected, event ); | ||
if( this._breaked ) { return false; } | ||
setTimeout( | ||
function() { | ||
// debounce in order "onClick" to work | ||
this.mouseInteraction = false; | ||
}.bind(this), | ||
100 | ||
); | ||
}; | ||
this.selector.style.width = '0'; | ||
this.selector.style.height = '0'; | ||
this.selector.style.display = 'none'; | ||
/** | ||
* Function break: used in callbacks to disable the execution of the upcoming code at the specific moment | ||
* In contrary to stop(): | ||
* - Event listeners, callback calls and calculation will continue working | ||
* - Selector won’t display and will not select | ||
*/ | ||
break() { | ||
this._breaked = true; | ||
setTimeout( | ||
// debounce the break should only break once instantly after call | ||
() => (this._breaked = false), | ||
100 | ||
); | ||
} | ||
setTimeout( | ||
function() { | ||
// debounce in order "onClick" to work | ||
this.mouseInteraction = false; | ||
}.bind(this), | ||
100 | ||
); | ||
}; | ||
/** | ||
* Complete function teardown | ||
* Will teardown/stop the whole functionality | ||
*/ | ||
stop() { | ||
this.reset(); | ||
this.area.removeEventListener('mousedown', this._startUp); | ||
this.area.removeEventListener('touchstart', this._startUp, { | ||
passive: false | ||
}); | ||
document.removeEventListener('mouseup', this.reset); | ||
document.removeEventListener('touchend', this.reset); | ||
} | ||
/** | ||
* Function break: used in callbacks to stop break the code at the specific moment | ||
* - Event listeners and calculation will continue working | ||
* - Selector won’t display and will not select | ||
*/ | ||
DragSelect.prototype.break = function() { | ||
this._breaked = true; | ||
setTimeout( | ||
function() { | ||
// debounce the break should only break once instantly after call | ||
this._breaked = false; | ||
}.bind(this), | ||
100 | ||
); | ||
}; | ||
// Usefull methods for user | ||
////////////////////////////////////////////////////////////////////////////////////// | ||
/** | ||
* Complete function teardown | ||
*/ | ||
DragSelect.prototype.stop = function() { | ||
this.reset(); | ||
this.area.removeEventListener( 'mousedown', this._startUp ); | ||
this.area.removeEventListener( 'touchstart', this._startUp, { passive: false } ); | ||
document.removeEventListener( 'mouseup', this.reset ); | ||
document.removeEventListener( 'touchend', this.reset ); | ||
/** | ||
* Returns the current selected nodes | ||
* @return {Array.<(HTMLElement|SVGElement)>} | ||
*/ | ||
getSelection() { | ||
return this._selected; | ||
} | ||
}; | ||
/** | ||
* Returns cursor x, y position based on event object | ||
* Will be relative to an area including the scroll unless advised otherwise | ||
* @param {Object} [event] | ||
* @param {(HTMLElement|SVGElement|false)} [_area] containing area / this.area if === undefined / document if === false | ||
* @param {boolean} [ignoreScroll] if true, the scroll will be ignored | ||
* @return {{x:number,y:number}} cursor { x/y } | ||
*/ | ||
getCursorPos(event, _area, ignoreScroll) { | ||
if (!event) return { x: 0, y: 0 }; | ||
// Usefull methods for user | ||
////////////////////////////////////////////////////////////////////////////////////// | ||
var area = _area || (_area !== false && this.area); | ||
var pos = this._getCursorPos(event, area); | ||
var scroll = ignoreScroll ? { x: 0, y: 0 } : this.getScroll(area); | ||
/** | ||
* Returns the current selected nodes | ||
* | ||
* @return {Nodes} | ||
*/ | ||
DragSelect.prototype.getSelection = function() { | ||
return this.selected; | ||
}; | ||
/** | ||
* Returns cursor x, y position based on event object | ||
* Will be relative to an area including the scroll unless advised otherwise | ||
* | ||
* @param {Object} event | ||
* @param {Node} _area – containing area / this.area if none / document if === false | ||
* @param {Node} ignoreScroll – if true, the scroll will be ignored | ||
* @return {Object} cursor { x/y } | ||
*/ | ||
DragSelect.prototype.getCursorPos = function(event, _area, ignoreScroll) { | ||
if (!event) { | ||
return false; | ||
return { | ||
x: pos.x + scroll.x, | ||
y: pos.y + scroll.y | ||
}; | ||
} | ||
var area = _area || (_area !== false && this.area); | ||
var pos = this._getCursorPos(event, area); | ||
var scroll = ignoreScroll ? { x: 0, y: 0 } : this.getScroll(area); | ||
/** | ||
* Adds several items to the selection list | ||
* also adds the specific classes and take into account all calculations. | ||
* Does not clear the selection, in contrary to .setSelection | ||
* Can add multiple nodes at once, in contrary to .select | ||
* @param {Array.<(HTMLElement|SVGElement)>} _nodes one or multiple nodes | ||
* @param {boolean} [triggerCallback] - if callback should be called | ||
* @param {boolean} [dontAddToSelectables] - if element should not be added to the list of selectable nodes | ||
* @return {Array.<(HTMLElement|SVGElement)>} all selected nodes | ||
*/ | ||
addSelection(_nodes, triggerCallback, dontAddToSelectables) { | ||
var nodes = this._toArray(_nodes); | ||
return { | ||
x: pos.x + scroll.x, | ||
y: pos.y + scroll.y | ||
}; | ||
}; | ||
for (var index = 0, il = nodes.length; index < il; index++) { | ||
var node = nodes[index]; | ||
this.select(node); | ||
} | ||
/** | ||
* Adds several items to the selection list | ||
* also adds the specific classes and take into account | ||
* all calculations. | ||
* Does not clear the selection, in contrary to .setSelection | ||
* Can add multiple nodes at once, in contrary to .select | ||
* | ||
* @param {Nodes} _nodes one or multiple nodes | ||
* @param {Boolean} _callback - if callback should be called | ||
* @param {Boolean} dontAddToSelectables - if element should not be added to the list of selectable nodes | ||
* @return {Array} all selected nodes | ||
*/ | ||
DragSelect.prototype.addSelection = function( | ||
_nodes, | ||
_callback, | ||
dontAddToSelectables | ||
) { | ||
var nodes = this.toArray(_nodes); | ||
if (!dontAddToSelectables) { | ||
this.addSelectables(nodes); | ||
} | ||
if (triggerCallback) { | ||
this.callback(this._selected, false); | ||
} | ||
for (var index = 0, il = nodes.length; index < il; index++) { | ||
var node = nodes[index]; | ||
this.select(node); | ||
return this._selected; | ||
} | ||
if (!dontAddToSelectables) { | ||
this.addSelectables(nodes); | ||
} | ||
if (_callback) { | ||
this.callback(this.selected, false); | ||
} | ||
/** | ||
* Removes specific nodes from the selection | ||
* Multiple nodes can be given at once, in contrary to unselect | ||
* @param {Array.<(HTMLElement|SVGElement)>} _nodes one or multiple nodes | ||
* @param {boolean} [triggerCallback] - if callback should be called | ||
* @param {boolean} [removeFromSelectables] - if element should be removed from the list of selectable nodes | ||
* @return {Array} all selected nodes | ||
*/ | ||
removeSelection(_nodes, triggerCallback, removeFromSelectables) { | ||
var nodes = this._toArray(_nodes); | ||
return this.selected; | ||
}; | ||
for (var index = 0, il = nodes.length; index < il; index++) { | ||
var node = nodes[index]; | ||
this.unselect(node); | ||
} | ||
/** | ||
* Removes specific nodes from the selection | ||
* Multiple nodes can be given at once, in contrary to unselect | ||
* | ||
* @param {Nodes} _nodes one or multiple nodes | ||
* @param {Boolean} _callback - if callback should be called | ||
* @param {Boolean} removeFromSelectables - if element should be removed from the list of selectable nodes | ||
* @return {Array} all selected nodes | ||
*/ | ||
DragSelect.prototype.removeSelection = function( | ||
_nodes, | ||
_callback, | ||
removeFromSelectables | ||
) { | ||
var nodes = this.toArray(_nodes); | ||
if (removeFromSelectables) { | ||
this.removeSelectables(nodes); | ||
} | ||
if (triggerCallback) { | ||
this.callback(this._selected, false); | ||
} | ||
for (var index = 0, il = nodes.length; index < il; index++) { | ||
var node = nodes[index]; | ||
this.unselect(node); | ||
return this._selected; | ||
} | ||
if (removeFromSelectables) { | ||
this.removeSelectables(nodes); | ||
} | ||
if (_callback) { | ||
this.callback(this.selected, false); | ||
} | ||
/** | ||
* Toggles specific nodes from the selection: | ||
* If element is not in selection it will be added, if it is already selected, it will be removed. | ||
* Multiple nodes can be given at once. | ||
* @param {Array.<(HTMLElement|SVGElement)>} _nodes one or multiple nodes | ||
* @param {boolean} [triggerCallback] - if callback should be called | ||
* @param {boolean} [special] - if true, it also removes selected elements from possible selectable nodes & don’t add them to selectables if they are not | ||
* @return {Array} all selected nodes | ||
*/ | ||
toggleSelection(_nodes, triggerCallback, special) { | ||
var nodes = this._toArray(_nodes); | ||
return this.selected; | ||
}; | ||
for (var index = 0, il = nodes.length; index < il; index++) { | ||
var node = nodes[index]; | ||
/** | ||
* Toggles specific nodes from the selection: | ||
* If element is not in selection it will be added, if it is already selected, it will be removed. | ||
* Multiple nodes can be given at once. | ||
* | ||
* @param {Nodes} _nodes one or multiple nodes | ||
* @param {Boolean} _callback - if callback should be called | ||
* @param {Boolean} _special - if true, it also removes selected elements from possible selectable nodes & don’t add them to selectables if they are not | ||
* @return {Array} all selected nodes | ||
*/ | ||
DragSelect.prototype.toggleSelection = function(_nodes, _callback, _special) { | ||
var nodes = this.toArray(_nodes); | ||
if (this._selected.indexOf(node) < 0) { | ||
this.addSelection(node, triggerCallback, special); | ||
} else { | ||
this.removeSelection(node, triggerCallback, special); | ||
} | ||
} | ||
for (var index = 0, il = nodes.length; index < il; index++) { | ||
var node = nodes[index]; | ||
return this._selected; | ||
} | ||
if (this.selected.indexOf(node) < 0) { | ||
this.addSelection(node, _callback, _special); | ||
} else { | ||
this.removeSelection(node, _callback, _special); | ||
} | ||
/** | ||
* Sets the current selected nodes and optionally run the callback | ||
* By default, adds new elements also to the list of selectables | ||
* @param {Array.<(HTMLElement|SVGElement)>} _nodes – dom nodes | ||
* @param {boolean} [triggerCallback] - if callback should be called | ||
* @param {boolean} [dontAddToSelectables] - if element should not be added to the list of selectable nodes | ||
* @return {Array.<(HTMLElement|SVGElement)>} | ||
*/ | ||
setSelection(_nodes, triggerCallback, dontAddToSelectables) { | ||
this.clearSelection(); | ||
this.addSelection(_nodes, triggerCallback, dontAddToSelectables); | ||
return this._selected; | ||
} | ||
return this.selected; | ||
}; | ||
/** | ||
* Unselect / Deselect all current selected Nodes | ||
* @param {boolean} [triggerCallback] - if callback should be called | ||
* @return {Array.<(HTMLElement|SVGElement)>} this.selected, should be empty | ||
*/ | ||
clearSelection(triggerCallback) { | ||
var selection = this._selected.slice(); | ||
for (var index = 0, il = selection.length; index < il; index++) { | ||
var node = selection[index]; | ||
this.unselect(node); | ||
} | ||
/** | ||
* Sets the current selected nodes and optionally run the callback | ||
* | ||
* @param {Nodes} _nodes – dom nodes | ||
* @param {Boolean} runCallback - if callback should be called | ||
* @param {Boolean} dontAddToSelectables - if element should not be added to the list of selectable nodes | ||
* @return {Nodes} | ||
*/ | ||
DragSelect.prototype.setSelection = function( | ||
_nodes, | ||
runCallback, | ||
dontAddToSelectables | ||
) { | ||
this.clearSelection(); | ||
this.addSelection(_nodes, runCallback, dontAddToSelectables); | ||
if (triggerCallback) { | ||
this.callback(this._selected, false); | ||
} | ||
return this.selected; | ||
}; | ||
return this._selected; | ||
} | ||
/** | ||
* Unselect / Deselect all current selected Nodes | ||
* | ||
* @param {Boolean} runCallback - if callback should be called | ||
* @return {Array} this.selected, should be empty | ||
*/ | ||
DragSelect.prototype.clearSelection = function(runCallback) { | ||
var selection = this.selected.slice(); | ||
for (var index = 0, il = selection.length; index < il; index++) { | ||
var node = selection[index]; | ||
this.unselect(node); | ||
/** | ||
* Add nodes that can be selected. | ||
* The algorithm makes sure that no node is added twice | ||
* @param {Array.<(HTMLElement|SVGElement)>} _nodes dom nodes | ||
* @param {boolean} [addToSelection] if elements should also be added to current selection | ||
* @return {Array.<(HTMLElement|SVGElement)>} _nodes the added node(s) | ||
*/ | ||
addSelectables(_nodes, addToSelection) { | ||
var nodes = this._toArray(_nodes); | ||
this._handleSelectables(nodes, false, addToSelection); | ||
return _nodes; | ||
} | ||
if (runCallback) { | ||
this.callback(this.selected, false); | ||
/** | ||
* Gets all nodes that can be selected | ||
* @return {Array.<(HTMLElement|SVGElement)>} this.selectables | ||
*/ | ||
getSelectables() { | ||
return this.selectables; | ||
} | ||
return this.selected; | ||
}; | ||
/** | ||
* Sets all elements that can be selected. | ||
* Removes all current selectables (& their respective classes). | ||
* Adds the new set to the selectables set, thus replacing the original set. | ||
* @param {Array.<(HTMLElement|SVGElement)>} nodes – dom nodes | ||
* @param {boolean} [removeFromSelection] if elements should also be removed from current selection | ||
* @param {boolean} [addToSelection] if elements should also be added to current selection | ||
* @return {Array.<(HTMLElement|SVGElement)>} nodes – the added node(s) | ||
*/ | ||
setSelectables(nodes, removeFromSelection, addToSelection) { | ||
this.removeSelectables(this.getSelectables(), removeFromSelection); | ||
return this.addSelectables(nodes, addToSelection); | ||
} | ||
/** | ||
* Add nodes that can be selected. | ||
* The algorithm makes sure that no node is added twice | ||
* | ||
* @param {Nodes} _nodes – dom nodes | ||
* @param {Boolean} addToSelection – if elements should also be added to current selection | ||
* @return {Nodes} _nodes – the added node(s) | ||
*/ | ||
DragSelect.prototype.addSelectables = function(_nodes, addToSelection) { | ||
var nodes = this.toArray(_nodes); | ||
this._handleSelectables(nodes, false, addToSelection); | ||
return _nodes; | ||
}; | ||
/** | ||
* Remove nodes from the nodes that can be selected. | ||
* @param {Array.<(HTMLElement|SVGElement)>} _nodes – dom nodes | ||
* @param {boolean} [removeFromSelection] if elements should also be removed from current selection | ||
* @return {Array.<(HTMLElement|SVGElement)>} _nodes – the removed node(s) | ||
*/ | ||
removeSelectables(_nodes, removeFromSelection) { | ||
var nodes = this._toArray(_nodes); | ||
this._handleSelectables(nodes, true, removeFromSelection); | ||
return _nodes; | ||
} | ||
/** | ||
* Gets all nodes that can be selected | ||
* | ||
* @return {Nodes} this.selectables | ||
*/ | ||
DragSelect.prototype.getSelectables = function() { | ||
return this.selectables; | ||
}; | ||
// Helpers | ||
////////////////////////////////////////////////////////////////////////////////////// | ||
/** | ||
* Sets all elements that can be selected. | ||
* Removes all current selectables (& their respective classes). | ||
* Adds the new set to the selectables set, | ||
* thus replacing the original set. | ||
* | ||
* @param {Nodes} _nodes – dom nodes | ||
* @param {Boolean} removeFromSelection – if elements should also be removed from current selection | ||
* @param {Boolean} addToSelection – if elements should also be added to current selection | ||
* @return {Nodes} _nodes – the added node(s) | ||
*/ | ||
DragSelect.prototype.setSelectables = function( | ||
_nodes, | ||
removeFromSelection, | ||
addToSelection | ||
) { | ||
this.removeSelectables(this.getSelectables(), removeFromSelection); | ||
return this.addSelectables(_nodes, addToSelection); | ||
}; | ||
/** | ||
* Based on a click event object, | ||
* checks if the right mouse button was pressed. | ||
* (found @ https://stackoverflow.com/a/2405835) | ||
* @param {object} event | ||
* @return {boolean} | ||
* @private | ||
*/ | ||
_isRightClick(event) { | ||
if (!event) { | ||
return false; | ||
} | ||
/** | ||
* Remove nodes from the nodes that can be selected. | ||
* | ||
* @param {Nodes} _nodes – dom nodes | ||
* @param {Boolean} removeFromSelection – if elements should also be removed from current selection | ||
* @return {Nodes} _nodes – the removed node(s) | ||
*/ | ||
DragSelect.prototype.removeSelectables = function(_nodes, removeFromSelection) { | ||
var nodes = this.toArray(_nodes); | ||
this._handleSelectables(nodes, true, removeFromSelection); | ||
return _nodes; | ||
}; | ||
var isRightMB = false; | ||
// Helpers | ||
////////////////////////////////////////////////////////////////////////////////////// | ||
if ('which' in event) { | ||
// Gecko (Firefox), WebKit (Safari/Chrome) & Opera | ||
isRightMB = event.which === 3; | ||
} else if ('button' in event) { | ||
// IE, Opera | ||
isRightMB = event.button === 2; | ||
} | ||
/** | ||
* Based on a click event object, | ||
* checks if the right mouse button was pressed. | ||
* (found @ https://stackoverflow.com/a/2405835) | ||
* | ||
* @param {Object} event | ||
* @return {Boolean} | ||
*/ | ||
DragSelect.prototype.isRightClick = function(event) { | ||
if (!event) { | ||
return false; | ||
return isRightMB; | ||
} | ||
var isRightMB = false; | ||
/** | ||
* Transforms a nodelist or single node to an array | ||
* so user doesn’t have to care. | ||
* @param {any} nodes | ||
* @return {array} | ||
* @private | ||
*/ | ||
_toArray(nodes) { | ||
if (!nodes) return []; | ||
if (!nodes.length && this._isElement(nodes)) return [nodes]; | ||
if ('which' in event) { | ||
// Gecko (Firefox), WebKit (Safari/Chrome) & Opera | ||
isRightMB = event.which === 3; | ||
} else if ('button' in event) { | ||
// IE, Opera | ||
isRightMB = event.button === 2; | ||
const array = []; | ||
for (let i = nodes.length - 1; i >= 0; i--) { | ||
array[i] = nodes[i]; | ||
} | ||
return array; | ||
} | ||
return isRightMB; | ||
}; | ||
/** | ||
* Adds a class to an element | ||
* sadly legacy phones/browsers don’t support .classlist so we use this workaround | ||
* all credits to http://clubmate.fi/javascript-adding-and-removing-class-names-from-elements/ | ||
* | ||
* @param {Node} element | ||
* @param {String} classname | ||
* @return {Node} element | ||
*/ | ||
DragSelect.prototype.addClass = function(element, classname) { | ||
if (element.classList) { | ||
return element.classList.add(classname); | ||
/** | ||
* Checks if a node is of type element | ||
* all credits to vikynandha: https://gist.github.com/vikynandha/6539809 | ||
* @param {HTMLElement|SVGElement} node | ||
* @return {boolean} | ||
* @private | ||
*/ | ||
_isElement(node) { | ||
try { | ||
// Using W3 DOM2 (works for FF, Opera and Chrome), also checking for SVGs | ||
return node instanceof HTMLElement || node instanceof SVGElement; | ||
} catch (e) { | ||
// Browsers not supporting W3 DOM2 don't have HTMLElement and | ||
// an exception is thrown and we end up here. Testing some | ||
// properties that all elements have. (works even on IE7) | ||
return ( | ||
typeof node === 'object' && | ||
node.nodeType === 1 && | ||
typeof node.style === 'object' && | ||
typeof node.ownerDocument === 'object' | ||
); | ||
} | ||
} | ||
var cn = element.getAttribute('class') || ''; | ||
if (cn.indexOf(classname) !== -1) { | ||
return element; | ||
} // test for existance | ||
if (cn !== '') { | ||
classname = ' ' + classname; | ||
} // add a space if the element already has class | ||
element.setAttribute('class', cn + classname); | ||
return element; | ||
}; | ||
/** | ||
* Returns cursor x, y position based on event object | ||
* /!\ for internal calculation reasons it does _not_ take | ||
* the AREA scroll into consideration unless it’s the outer Document. | ||
* Use the public .getCursorPos() from outside, it’s more flexible | ||
* @param {Object} [event] | ||
* @param {(HTMLElement|SVGElement)} area – containing area / document if none | ||
* @return {{x: number, y: number}} cursor X/Y | ||
* @private | ||
*/ | ||
_getCursorPos(event, area) { | ||
if (!event) return { x: 0, y: 0 }; | ||
/** | ||
* Removes a class of an element | ||
* sadly legacy phones/browsers don’t support .classlist so we use this workaround | ||
* all credits to http://clubmate.fi/javascript-adding-and-removing-class-names-from-elements/ | ||
* | ||
* @param {Node} element | ||
* @param {String} classname | ||
* @return {Node} element | ||
*/ | ||
DragSelect.prototype.removeClass = function(element, classname) { | ||
if (element.classList) { | ||
return element.classList.remove(classname); | ||
} | ||
// touchend has not touches. so we take the last toucb if a touchevent, we need to store the positions on the prototype | ||
if (event.touches && event.type !== 'touchend') { | ||
this._lastTouch = event; | ||
} | ||
//if a touchevent, return the last touch rather than the regular event | ||
// we need .touches[0] from that event instead | ||
event = event.touches ? this._lastTouch.touches[0] : event; | ||
var cn = element.getAttribute('class') || ''; | ||
var rxp = new RegExp(classname + '\\b', 'g'); | ||
cn = cn.replace(rxp, ''); | ||
element.setAttribute('class', cn); | ||
return element; | ||
}; | ||
var cPos = { | ||
// event.clientX/Y fallback for <IE8 | ||
x: event.pageX || event.clientX, | ||
y: event.pageY || event.clientY | ||
}; | ||
/** | ||
* Checks if an element has a class | ||
* sadly legacy phones/browsers don’t support .classlist so we use this workaround | ||
* | ||
* @param {Node} element | ||
* @param {String} classname | ||
* @return {Boolean} | ||
*/ | ||
DragSelect.prototype.hasClass = function(element, classname) { | ||
if (element.classList) { | ||
return element.classList.contains(classname); | ||
} | ||
var areaRect = this.getAreaRect(area || document); | ||
var docScroll = this.getScroll(); // needed when document is scrollable but area is not | ||
var cn = element.getAttribute('class') || ''; | ||
if (cn.indexOf(classname) > -1) { | ||
return true; | ||
} else { | ||
return false; | ||
return { | ||
// if it’s constrained in an area the area should be substracted calculate | ||
x: cPos.x - areaRect.left - docScroll.x, | ||
y: cPos.y - areaRect.top - docScroll.y | ||
}; | ||
} | ||
}; | ||
/** | ||
* Transforms a nodelist or single node to an array | ||
* so user doesn’t have to care. | ||
* | ||
* @param {Node} nodes | ||
* @return {array} | ||
*/ | ||
DragSelect.prototype.toArray = function(nodes) { | ||
if (!nodes) { | ||
return false; | ||
/** | ||
* Returns the starting/initial position of the cursor/selector | ||
* @return {{x:number,y:number}} | ||
*/ | ||
getInitialCursorPosition() { | ||
return this._initialCursorPos; | ||
} | ||
if (!nodes.length && this.isElement(nodes)) { | ||
return [nodes]; | ||
} | ||
var array = []; | ||
for (var i = nodes.length - 1; i >= 0; i--) { | ||
array[i] = nodes[i]; | ||
/** | ||
* Returns the last seen position of the cursor/selector | ||
* @return {{x:number,y:number}} | ||
*/ | ||
getCurrentCursorPosition() { | ||
return this._newCursorPos; | ||
} | ||
return array; | ||
}; | ||
/** | ||
* Checks if a node is of type element | ||
* all credits to vikynandha: https://gist.github.com/vikynandha/6539809 | ||
* | ||
* @param {Node} node | ||
* @return {Boolean} | ||
*/ | ||
DragSelect.prototype.isElement = function(node) { | ||
try { | ||
// Using W3 DOM2 (works for FF, Opera and Chrome), also checking for SVGs | ||
return node instanceof HTMLElement || node instanceof SVGElement; | ||
} catch (e) { | ||
// Browsers not supporting W3 DOM2 don't have HTMLElement and | ||
// an exception is thrown and we end up here. Testing some | ||
// properties that all elements have. (works even on IE7) | ||
return ( | ||
typeof node === 'object' && | ||
node.nodeType === 1 && | ||
typeof node.style === 'object' && | ||
typeof node.ownerDocument === 'object' | ||
); | ||
/** | ||
* Returns the previous position of the cursor/selector | ||
* @return {{x:number,y:number}} | ||
*/ | ||
getPreviousCursorPosition() { | ||
return this._previousCursorPos; | ||
} | ||
}; | ||
/** | ||
* Returns cursor x, y position based on event object | ||
* /!\ for internal calculation reasons it does _not_ take | ||
* the AREA scroll into consideration unless it’s the outer Document. | ||
* Use the public .getCursorPos() from outside, it’s more flexible | ||
* | ||
* @param {Object} event | ||
* @param {Node} area – containing area / document if none | ||
* @return {Object} cursor X/Y | ||
*/ | ||
DragSelect.prototype._getCursorPos = function(event, area) { | ||
if (!event) { | ||
return { x: 0, y: 0 }; | ||
} | ||
/** | ||
* Returns the cursor position difference between start and now | ||
* If usePreviousCursorDifference is passed, | ||
* it will output the cursor position difference between the previous selection and now | ||
* @param {boolean} [usePreviousCursorDifference] | ||
* @return {{x:number,y:number}} | ||
*/ | ||
getCursorPositionDifference(usePreviousCursorDifference) { | ||
var posA = this.getCurrentCursorPosition(); | ||
var posB = usePreviousCursorDifference | ||
? this.getPreviousCursorPosition() | ||
: this.getInitialCursorPosition(); | ||
// touchend has not touches. so we take the last toucb if a touchevent, we need to store the positions on the prototype | ||
if (event.touches && event.type !== 'touchend') { | ||
DragSelect.prototype.lastTouch = event | ||
return { | ||
x: posA.x - posB.x, | ||
y: posA.y - posB.y | ||
}; | ||
} | ||
//if a touchevent, return the last touch rather than the regular event | ||
// we need .touches[0] from that event instead | ||
event = event.touches ? DragSelect.prototype.lastTouch.touches[0] : event | ||
var cPos = { // event.clientX/Y fallback for <IE8 | ||
x: event.pageX || event.clientX, | ||
y: event.pageY || event.clientY | ||
}; | ||
/** | ||
* Returns the current x, y scroll value of a container | ||
* If container has no scroll it will return 0 | ||
* @param {(HTMLElement|SVGElement)} [area] | ||
* @return {{x:number,y:number}} scroll X/Y | ||
*/ | ||
getScroll(area) { | ||
var body = { | ||
top: | ||
document.body.scrollTop > 0 | ||
? document.body.scrollTop | ||
: document.documentElement.scrollTop, | ||
left: | ||
document.body.scrollLeft > 0 | ||
? document.body.scrollLeft | ||
: document.documentElement.scrollLeft | ||
}; | ||
var areaRect = this.getAreaRect(area || document); | ||
var docScroll = this.getScroll(); // needed when document is scrollable but area is not | ||
var scroll = { | ||
// when the rectangle is bound to the document, no scroll is needed | ||
y: area && area.scrollTop >= 0 ? area.scrollTop : body.top, | ||
x: area && area.scrollLeft >= 0 ? area.scrollLeft : body.left | ||
}; | ||
return { | ||
// if it’s constrained in an area the area should be substracted calculate | ||
x: cPos.x - areaRect.left - docScroll.x, | ||
y: cPos.y - areaRect.top - docScroll.y | ||
}; | ||
}; | ||
return scroll; | ||
} | ||
/** | ||
* Returns the starting/initial position of the cursor/selector | ||
* | ||
* @return {Object} initialPos. | ||
*/ | ||
DragSelect.prototype.getInitialCursorPosition = function() { | ||
return this.initialCursorPos; | ||
}; | ||
/** | ||
* Returns the top/left/bottom/right/width/height | ||
* values of a node. If Area is document then everything | ||
* except the sizes will be nulled. | ||
* @param {(HTMLElement|SVGElement|any)} area | ||
* @return {{top:number,left:number,bottom:number,right:number,width:number,height:number}} | ||
*/ | ||
getAreaRect(area) { | ||
if (area === document) { | ||
var size = { | ||
y: | ||
area.documentElement.clientHeight > 0 | ||
? area.documentElement.clientHeight | ||
: window.innerHeight, | ||
x: | ||
area.documentElement.clientWidth > 0 | ||
? area.documentElement.clientWidth | ||
: window.innerWidth | ||
}; | ||
return { | ||
top: 0, | ||
left: 0, | ||
bottom: 0, | ||
right: 0, | ||
width: size.x, | ||
height: size.y | ||
}; | ||
} | ||
/** | ||
* Returns the last seen position of the cursor/selector | ||
* | ||
* @return {Object} initialPos. | ||
*/ | ||
DragSelect.prototype.getCurrentCursorPosition = function() { | ||
return this.newCursorPos; | ||
}; | ||
/** | ||
* Returns the previous position of the cursor/selector | ||
* | ||
* @return {Object} initialPos. | ||
*/ | ||
DragSelect.prototype.getPreviousCursorPosition = function() { | ||
return this.previousCursorPos; | ||
}; | ||
/** | ||
* Returns the cursor position difference between start and now | ||
* If usePreviousCursorDifference is passed, | ||
* it will output the cursor position difference between the previous selection and now | ||
* | ||
* @param {boolean} usePreviousCursorDifference | ||
* @return {Object} initialPos. | ||
*/ | ||
DragSelect.prototype.getCursorPositionDifference = function( | ||
usePreviousCursorDifference | ||
) { | ||
var posA = this.getCurrentCursorPosition(); | ||
var posB = usePreviousCursorDifference | ||
? this.getPreviousCursorPosition() | ||
: this.getInitialCursorPosition(); | ||
return { | ||
x: posA.x - posB.x, | ||
y: posA.y - posB.y | ||
}; | ||
}; | ||
/** | ||
* Returns the current x, y scroll value of a container | ||
* If container has no scroll it will return 0 | ||
* | ||
* @param {Node} area | ||
* @return {Object} scroll X/Y | ||
*/ | ||
DragSelect.prototype.getScroll = function(area) { | ||
var body = { | ||
top: | ||
document.body.scrollTop > 0 | ||
? document.body.scrollTop | ||
: document.documentElement.scrollTop, | ||
left: | ||
document.body.scrollLeft > 0 | ||
? document.body.scrollLeft | ||
: document.documentElement.scrollLeft | ||
}; | ||
var scroll = { | ||
// when the rectangle is bound to the document, no scroll is needed | ||
y: area && area.scrollTop >= 0 ? area.scrollTop : body.top, | ||
x: area && area.scrollLeft >= 0 ? area.scrollLeft : body.left | ||
}; | ||
return scroll; | ||
}; | ||
/** | ||
* Returns the top/left/bottom/right/width/height | ||
* values of a node. If Area is document then everything | ||
* except the sizes will be nulled. | ||
* | ||
* @param {Node} area | ||
* @return {Object} | ||
*/ | ||
DragSelect.prototype.getAreaRect = function(area) { | ||
if (area === document) { | ||
var size = { | ||
y: | ||
area.documentElement.clientHeight > 0 | ||
? area.documentElement.clientHeight | ||
: window.innerHeight, | ||
x: | ||
area.documentElement.clientWidth > 0 | ||
? area.documentElement.clientWidth | ||
: window.innerWidth | ||
}; | ||
const rect = area.getBoundingClientRect(); | ||
return { | ||
top: 0, | ||
left: 0, | ||
bottom: 0, | ||
right: 0, | ||
width: size.x, | ||
height: size.y | ||
top: rect.top, | ||
left: rect.left, | ||
bottom: rect.bottom, | ||
right: rect.right, | ||
width: rect.width, | ||
height: rect.height | ||
}; | ||
} | ||
return { | ||
top: area.getBoundingClientRect().top, | ||
left: area.getBoundingClientRect().left, | ||
bottom: area.getBoundingClientRect().bottom, | ||
right: area.getBoundingClientRect().right, | ||
width: area.offsetWidth, | ||
height: area.offsetHeight | ||
}; | ||
}; | ||
/** | ||
* Updates the node style left, top, width, | ||
* height values accordingly. | ||
* @param {(HTMLElement|SVGElement)} node | ||
* @param {Object} pos { x, y, w, h } | ||
* @return {(HTMLElement|SVGElement)} | ||
* @private | ||
*/ | ||
_updatePos(node, pos) { | ||
node.style.left = pos.x + 'px'; | ||
node.style.top = pos.y + 'px'; | ||
node.style.width = pos.w + 'px'; | ||
node.style.height = pos.h + 'px'; | ||
return node; | ||
} | ||
} | ||
/** | ||
* Updates the node style left, top, width, | ||
* height values accordingly. | ||
* | ||
* @param {Node} node | ||
* @param {Object} pos { x, y, w, h } | ||
* | ||
* @return {Node} | ||
*/ | ||
DragSelect.prototype.updatePos = function(node, pos) { | ||
node.style.left = pos.x + 'px'; | ||
node.style.top = pos.y + 'px'; | ||
node.style.width = pos.w + 'px'; | ||
node.style.height = pos.h + 'px'; | ||
return node; | ||
}; | ||
// Make exportable | ||
@@ -1360,6 +1232,10 @@ ////////////////////////////////////////////////////////////////////////////////////// | ||
} else if ( | ||
// @ts-ignore | ||
typeof define !== 'undefined' && | ||
// @ts-ignore | ||
typeof define === 'function' && | ||
// @ts-ignore | ||
define | ||
) { | ||
// @ts-ignore | ||
define(function() { | ||
@@ -1369,3 +1245,4 @@ return DragSelect; | ||
} else { | ||
// @ts-ignore | ||
window.DragSelect = DragSelect; | ||
} |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Native code
Supply chain riskContains native code (e.g., compiled binaries or shared libraries). Including native code can obscure malicious behavior.
Found 14 instances in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Mixed license
License(Experimental) Package contains multiple licenses.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
2780102
12
70
3611
212
1
15