diff --git a/.circleci/config.yml b/.circleci/config.yml index c3333668..d8332f68 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -2,7 +2,7 @@ version: 2 jobs: build: docker: - - image: circleci/node:8.2.1 + - image: circleci/node:8.6.0 working_directory: ~/repo diff --git a/.eslintrc.js b/.eslintrc.js index 80abbfa3..8c6775bb 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -9,5 +9,15 @@ module.exports = { rules: { 'import/no-unresolved': 'off', 'import/no-extraneous-dependencies': 'off', - } + }, + globals: { + jest: false, + afterAll: false, + afterEach: false, + beforeAll: false, + beforeEach: false, + describe: false, + test: false, + expect: false, + }, }; diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 00000000..2dc0523a --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +v8.6.0 diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..8505c215 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,28 @@ +# Changelog + +## v1.0.0-beta.2 - 2017-10-10 + +### Added + +- Code of Conduct +- Contribution guidelines +- Documentation on `appendTo` option for `Draggable` +- Added concept of `originalSource` +- Fix for text selection issue +- Fix for native drag events firing for the `MouseSensor` +- Fix for missing `classes` option + +### Changes + +- README updates +- Touch improvements +- ForceTouchSensor is not included by default anymore +- Folder/File restructure +- Exports `AbstractEvent` as `BaseEvent` +- Update node version from `8.2.1` to `8.6.0` +- Clones event callbacks before triggering (to prevent mutation during iterations) +- Improvements to `closest` utils helper + +## v1.0.0-beta - 2017-09-27 + +Initial release diff --git a/README.md b/README.md index 45ad6f5a..2880dfcd 100644 --- a/README.md +++ b/README.md @@ -127,7 +127,7 @@ using mouse events, otherwise mirror will be `null` in events. Default: `false` **`plugins {Array[Plugin]}`** Plugins add behaviour to Draggable by hooking into its life cycle, e.g. one of the default -plugins controls the mirror movement. Default: `[Mirror, Accessibility]` +plugins controls the mirror movement. Default: `[]` **`appendTo {String|HTMLElement|Function}`** Draggable allows you to specify where the mirror should be appended to. You can specify a css diff --git a/config.json b/config.json index 328f66ad..d8ab2c19 100644 --- a/config.json +++ b/config.json @@ -1,10 +1,10 @@ { - "testMatch": ["/test/src/**/*.js"], - "setupFiles": ["/test/setup.js"], + "testMatch": ["/src/**/*.test.js"], + "setupFiles": ["/scripts/test/setup.js"], "transform": {".*": "/node_modules/babel-jest"}, "moduleFileExtensions": ["js"], "collectCoverageFrom": [ "src/**/*.js" ], - "moduleDirectories": ["node_modules", "src", "test"] + "moduleDirectories": ["node_modules", "src", "scripts/test"] } diff --git a/package.json b/package.json index 72ebcda2..ba84bcb7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@shopify/draggable", - "version": "1.0.0-beta", + "version": "1.0.0-beta.2", "private": false, "license": "MIT", "description": "The JavaScript Drag & Drop library your grandparents warned you about.", diff --git a/test/helper.js b/scripts/test/helper.js similarity index 100% rename from test/helper.js rename to scripts/test/helper.js diff --git a/test/setup.js b/scripts/test/setup.js similarity index 100% rename from test/setup.js rename to scripts/test/setup.js diff --git a/src/events/drag-event.js b/src/Draggable/DragEvent/DragEvent.js similarity index 93% rename from src/events/drag-event.js rename to src/Draggable/DragEvent/DragEvent.js index 24c2887f..a7e7366e 100644 --- a/src/events/drag-event.js +++ b/src/Draggable/DragEvent/DragEvent.js @@ -1,12 +1,12 @@ -import AbstractEvent from './abstract-event'; +import AbstractEvent from 'shared/AbstractEvent'; export class DragEvent extends AbstractEvent { get source() { return this.data.source; } - get movableSource() { - return this.data.movableSource; + get originalSource() { + return this.data.originalSource; } get mirror() { diff --git a/src/Draggable/DragEvent/README.md b/src/Draggable/DragEvent/README.md new file mode 100644 index 00000000..a08c76a2 --- /dev/null +++ b/src/Draggable/DragEvent/README.md @@ -0,0 +1 @@ +## Drag event diff --git a/src/Draggable/DragEvent/index.js b/src/Draggable/DragEvent/index.js new file mode 100644 index 00000000..ba5afda0 --- /dev/null +++ b/src/Draggable/DragEvent/index.js @@ -0,0 +1,10 @@ +export { + DragStartEvent, + DragMoveEvent, + DragOutContainerEvent, + DragOutEvent, + DragOverContainerEvent, + DragOverEvent, + DragStopEvent, + DragPressureEvent, +} from './DragEvent'; diff --git a/src/draggable.js b/src/Draggable/Draggable.js similarity index 86% rename from src/draggable.js rename to src/Draggable/Draggable.js index 13a67dad..bf54e6e4 100644 --- a/src/draggable.js +++ b/src/Draggable/Draggable.js @@ -1,20 +1,17 @@ -import {closest} from './utils'; +import {closest} from 'shared/utils'; -import Accessibility from './core/accessibility'; -import Mirror from './core/mirror'; +import {Accessibility, Mirror} from './Plugins'; -import Collidable from './behaviour/collidable'; -import Snappable from './behaviour/snappable'; - -import DragSensor from './sensors/drag-sensor'; -import MouseSensor from './sensors/mouse-sensor'; -import TouchSensor from './sensors/touch-sensor'; -import ForceTouchSensor from './sensors/force-touch-sensor'; +import { + DragSensor, + MouseSensor, + TouchSensor, +} from './Sensors'; import { DraggableInitializedEvent, DraggableDestroyEvent, -} from './events/draggable-event'; +} from './DraggableEvent'; import { DragStartEvent, @@ -25,14 +22,14 @@ import { DragOverEvent, DragStopEvent, DragPressureEvent, -} from './events/drag-event'; +} from './DragEvent'; import { MirrorCreatedEvent, MirrorAttachedEvent, MirrorMoveEvent, MirrorDestroyEvent, -} from './events/mirror-event'; +} from './MirrorEvent'; const defaults = { draggable: '.draggable-source', @@ -40,7 +37,7 @@ const defaults = { delay: 0, placedTimeout: 800, native: false, - plugins: [Mirror, Accessibility], + plugins: [], classes: { 'container:dragging': 'draggable-container--is-dragging', 'source:dragging': 'draggable-source--is-dragging', @@ -58,13 +55,6 @@ const defaults = { * @module Draggable */ export default class Draggable { - static get Plugins() { - return {Accessibility, Mirror}; - } - - static get Behaviour() { - return {Collidable, Snappable}; - } /** * Draggable constructor. @@ -92,7 +82,7 @@ export default class Draggable { container.addEventListener('drag:pressure', this.dragPressure, true); } - for (const Plugin of this.options.plugins) { + for (const Plugin of [Mirror, Accessibility, ...this.options.plugins]) { const plugin = new Plugin(this); plugin.attach(); this.activePlugins.push(plugin); @@ -168,8 +158,9 @@ export default class Draggable { trigger(type, ...args) { if (!this.callbacks[type]) { return; } - for (let i = this.callbacks[type].length - 1; i >= 0; i--) { - const callback = this.callbacks[type][i]; + const callbacks = Array.from(this.callbacks[type]); + for (let i = callbacks.length - 1; i >= 0; i--) { + const callback = callbacks[i]; callback(...args); } } @@ -181,7 +172,6 @@ export default class Draggable { sensors() { return [ TouchSensor, - ForceTouchSensor, (this.options.native ? DragSensor : MouseSensor), ]; } @@ -196,24 +186,25 @@ export default class Draggable { } // Find draggable source element - this.source = closest(target, this.options.draggable); + this.originalSource = closest(target, this.options.draggable); this.sourceContainer = container; - if (!this.source) { + if (!this.originalSource) { sensorEvent.cancel(); return; } this.dragging = true; - this.movableSource = this.source.cloneNode(true); + this.source = this.originalSource.cloneNode(true); if (!isDragEvent(originalEvent)) { - const appendableContainer = this.getAppendableContainer({source: this.source}); + const appendableContainer = this.getAppendableContainer({source: this.originalSource}); this.mirror = this.source.cloneNode(true); const mirrorCreatedEvent = new MirrorCreatedEvent({ source: this.source, + originalSource: this.originalSource, mirror: this.mirror, sourceContainer: container, sensorEvent, @@ -221,6 +212,7 @@ export default class Draggable { const mirrorAttachedEvent = new MirrorAttachedEvent({ source: this.source, + originalSource: this.originalSource, mirror: this.mirror, sourceContainer: container, sensorEvent, @@ -231,23 +223,19 @@ export default class Draggable { this.triggerEvent(mirrorAttachedEvent); } - this.source.parentNode.insertBefore(this.movableSource, this.source); - - const source = this.source; - - setTimeout(() => { - source.style.display = 'none'; - }, 0); + this.originalSource.parentNode.insertBefore(this.source, this.originalSource); + this.originalSource.style.display = 'none'; this.source.classList.add(this.getClassNameFor('source:dragging')); this.sourceContainer.classList.add(this.getClassNameFor('container:dragging')); document.body.classList.add(this.getClassNameFor('body:dragging')); + applyUserSelect(document.body, 'none'); if (this.mirror) { const mirrorMoveEvent = new MirrorMoveEvent({ source: this.source, mirror: this.mirror, - movableSource: this.movableSource, + originalSource: this.originalSource, sourceContainer: container, sensorEvent, }); @@ -261,7 +249,7 @@ export default class Draggable { const dragEvent = new DragStartEvent({ source: this.source, mirror: this.mirror, - movableSource: this.movableSource, + originalSource: this.originalSource, sourceContainer: container, sensorEvent, }); @@ -293,7 +281,7 @@ export default class Draggable { const dragMoveEvent = new DragMoveEvent({ source: this.source, mirror: this.mirror, - movableSource: this.movableSource, + originalSource: this.originalSource, sourceContainer: container, sensorEvent, }); @@ -308,7 +296,7 @@ export default class Draggable { const mirrorMoveEvent = new MirrorMoveEvent({ source: this.source, mirror: this.mirror, - movableSource: this.movableSource, + originalSource: this.originalSource, sourceContainer: container, sensorEvent, }); @@ -327,7 +315,7 @@ export default class Draggable { const dragOutEvent = new DragOutEvent({ source: this.source, mirror: this.mirror, - movableSource: this.movableSource, + originalSource: this.originalSource, sourceContainer: container, sensorEvent, over: this.currentOver, @@ -343,7 +331,7 @@ export default class Draggable { const dragOutContainerEvent = new DragOutContainerEvent({ source: this.source, mirror: this.mirror, - movableSource: this.movableSource, + originalSource: this.originalSource, sourceContainer: container, sensorEvent, overContainer: this.overContainer, @@ -361,7 +349,7 @@ export default class Draggable { const dragOverContainerEvent = new DragOverContainerEvent({ source: this.source, mirror: this.mirror, - movableSource: this.movableSource, + originalSource: this.originalSource, sourceContainer: container, sensorEvent, overContainer, @@ -378,7 +366,7 @@ export default class Draggable { const dragOverEvent = new DragOverEvent({ source: this.source, mirror: this.mirror, - movableSource: this.movableSource, + originalSource: this.originalSource, sourceContainer: container, sensorEvent, overContainer, @@ -398,22 +386,23 @@ export default class Draggable { const dragStopEvent = new DragStopEvent({ source: this.source, mirror: this.mirror, - movableSource: this.movableSource, + originalSource: this.originalSource, sensorEvent: event.sensorEvent, sourceContainer: this.sourceContainer, }); this.triggerEvent(dragStopEvent); - this.movableSource.parentNode.insertBefore(this.source, this.movableSource); - this.movableSource.parentNode.removeChild(this.movableSource); - this.source.style.display = ''; + this.source.parentNode.insertBefore(this.originalSource, this.source); + this.source.parentNode.removeChild(this.source); + this.originalSource.style.display = ''; this.source.classList.remove(this.getClassNameFor('source:dragging')); - this.source.classList.add(this.getClassNameFor('source:placed')); + this.originalSource.classList.add(this.getClassNameFor('source:placed')); this.sourceContainer.classList.add(this.getClassNameFor('container:placed')); this.sourceContainer.classList.remove(this.getClassNameFor('container:dragging')); document.body.classList.remove(this.getClassNameFor('body:dragging')); + applyUserSelect(document.body, ''); if (this.currentOver) { this.currentOver.classList.remove(this.getClassNameFor('draggable:over')); @@ -438,7 +427,7 @@ export default class Draggable { } } - const lastSource = this.source; + const lastSource = this.originalSource; const lastSourceContainer = this.sourceContainer; setTimeout(() => { @@ -453,7 +442,7 @@ export default class Draggable { this.source = null; this.mirror = null; - this.movableSource = null; + this.originalSource = null; this.currentOverContainer = null; this.currentOver = null; this.sourceContainer = null; @@ -509,3 +498,11 @@ function getSensorEvent(event) { function isDragEvent(event) { return /^drag/.test(event.type); } + +function applyUserSelect(element, value) { + element.style.webkitUserSelect = value; + element.style.mozUserSelect = value; + element.style.msUserSelect = value; + element.style.oUserSelect = value; + element.style.userSelect = value; +} diff --git a/src/events/draggable-event.js b/src/Draggable/DraggableEvent/DraggableEvent.js similarity index 87% rename from src/events/draggable-event.js rename to src/Draggable/DraggableEvent/DraggableEvent.js index ceb1222f..3b9a8b43 100644 --- a/src/events/draggable-event.js +++ b/src/Draggable/DraggableEvent/DraggableEvent.js @@ -1,4 +1,4 @@ -import AbstractEvent from './abstract-event'; +import AbstractEvent from 'shared/AbstractEvent'; export class DraggableEvent extends AbstractEvent { static type = 'draggable'; diff --git a/src/Draggable/DraggableEvent/README.md b/src/Draggable/DraggableEvent/README.md new file mode 100644 index 00000000..03b50760 --- /dev/null +++ b/src/Draggable/DraggableEvent/README.md @@ -0,0 +1 @@ +## Draggable event diff --git a/src/Draggable/DraggableEvent/index.js b/src/Draggable/DraggableEvent/index.js new file mode 100644 index 00000000..cdb91c10 --- /dev/null +++ b/src/Draggable/DraggableEvent/index.js @@ -0,0 +1,4 @@ +export { + DraggableInitializedEvent, + DraggableDestroyEvent, +} from './DraggableEvent'; diff --git a/src/events/mirror-event.js b/src/Draggable/MirrorEvent/MirrorEvent.js similarity index 87% rename from src/events/mirror-event.js rename to src/Draggable/MirrorEvent/MirrorEvent.js index 76e315d5..7d168a67 100644 --- a/src/events/mirror-event.js +++ b/src/Draggable/MirrorEvent/MirrorEvent.js @@ -1,12 +1,12 @@ -import AbstractEvent from './abstract-event'; +import AbstractEvent from 'shared/AbstractEvent'; export class MirrorEvent extends AbstractEvent { get source() { return this.data.source; } - get movableSource() { - return this.data.movableSource; + get originalSource() { + return this.data.originalSource; } get mirror() { diff --git a/src/Draggable/MirrorEvent/README.md b/src/Draggable/MirrorEvent/README.md new file mode 100644 index 00000000..791133c4 --- /dev/null +++ b/src/Draggable/MirrorEvent/README.md @@ -0,0 +1 @@ +## Mirror event diff --git a/src/Draggable/MirrorEvent/index.js b/src/Draggable/MirrorEvent/index.js new file mode 100644 index 00000000..5bf7b842 --- /dev/null +++ b/src/Draggable/MirrorEvent/index.js @@ -0,0 +1,6 @@ +export { + MirrorCreatedEvent, + MirrorAttachedEvent, + MirrorMoveEvent, + MirrorDestroyEvent, +} from './MirrorEvent'; diff --git a/src/core/accessibility.js b/src/Draggable/Plugins/Accessibility/Accessibility.js similarity index 100% rename from src/core/accessibility.js rename to src/Draggable/Plugins/Accessibility/Accessibility.js diff --git a/src/Draggable/Plugins/Accessibility/README.md b/src/Draggable/Plugins/Accessibility/README.md new file mode 100644 index 00000000..f6593a1e --- /dev/null +++ b/src/Draggable/Plugins/Accessibility/README.md @@ -0,0 +1 @@ +## Accessibility diff --git a/src/Draggable/Plugins/Accessibility/index.js b/src/Draggable/Plugins/Accessibility/index.js new file mode 100644 index 00000000..9baa4f76 --- /dev/null +++ b/src/Draggable/Plugins/Accessibility/index.js @@ -0,0 +1,3 @@ +import Accessibility from './Accessibility'; + +export default Accessibility; diff --git a/src/core/mirror.js b/src/Draggable/Plugins/Mirror/Mirror.js similarity index 100% rename from src/core/mirror.js rename to src/Draggable/Plugins/Mirror/Mirror.js diff --git a/src/Draggable/Plugins/Mirror/README.md b/src/Draggable/Plugins/Mirror/README.md new file mode 100644 index 00000000..b98c8fec --- /dev/null +++ b/src/Draggable/Plugins/Mirror/README.md @@ -0,0 +1 @@ +## Mirror diff --git a/src/Draggable/Plugins/Mirror/index.js b/src/Draggable/Plugins/Mirror/index.js new file mode 100644 index 00000000..e746562f --- /dev/null +++ b/src/Draggable/Plugins/Mirror/index.js @@ -0,0 +1,3 @@ +import Mirror from './Mirror'; + +export default Mirror; diff --git a/src/Draggable/Plugins/README.md b/src/Draggable/Plugins/README.md new file mode 100644 index 00000000..852b9b8f --- /dev/null +++ b/src/Draggable/Plugins/README.md @@ -0,0 +1 @@ +## Draggable plugins diff --git a/src/Draggable/Plugins/index.js b/src/Draggable/Plugins/index.js new file mode 100644 index 00000000..ea03b0f4 --- /dev/null +++ b/src/Draggable/Plugins/index.js @@ -0,0 +1,7 @@ +import Mirror from './Mirror'; +import Accessibility from './Accessibility'; + +export { + Mirror, + Accessibility, +}; diff --git a/src/Draggable/README.md b/src/Draggable/README.md new file mode 100644 index 00000000..b5e07981 --- /dev/null +++ b/src/Draggable/README.md @@ -0,0 +1,95 @@ +## Draggable + +### API + +**`new Draggable(containers: Array[HTMLElement]|NodeList, options: Object): Draggable`** +To create a draggable instance you need to specify the containers that hold draggable items, e.g. +`[document.body]` would work too. The second argument is an options object, which is described +below. + +**`draggable.on(eventName: String, listener: Function): Draggable`** +Draggable is an event emitter, so you can register callbacks for events. Draggable +also supports method chaining. + +**`draggable.off(eventName: String, listener: Function): Draggable`** +You can unregister listeners by using `.off()`, make sure to provide the same callback. + +**`draggable.trigger(eventName: String, event: AbstractEvent): Draggable`** +You can trigger events through draggable. This is used to fire events internally or by +extensions of Draggable. + +**`draggable.destroy(): void`** +Detaches all sensors and listeners, and cleans up after itself. + +### Options + +**`draggable {String}`** +A css selector for draggable elements within the `containers` specified. By default it will +look for an element with `.draggable-source` class. Default: `.draggable-source` + +**`handle {String}`** +Specify a css selector for a handle element if you don't want to allow drag action +on the entire element. Default: `null` + +**`delay {Number}`** +If you want to delay a drag start you can specify delay in milliseconds. This can be useful +for draggable elements within scrollable containers. Default: `0` + +**`native {Boolean}`** +If enabled Draggable will use the browsers native drag events to detect drag behaviour. By default +it will use mouse events to detect drag behaviour. You can only customize the mirror element when +using mouse events, otherwise mirror will be `null` in events. Default: `false` + +**`plugins {Array[Plugin]}`** +Plugins add behaviour to Draggable by hooking into its life cycle, e.g. one of the default +plugins controls the mirror movement. Default: `[]` + +**`appendTo {String|HTMLElement|Function}`** +Draggable allows you to specify where the mirror should be appended to. You can specify a css +selector, a HTMLElement or a function that returns a HTMLElement + +**`classes {Object}`** +Draggable adds classes to elements to indicate state. These classes can be used to add styling +on elements in certain states. + +### Events + +| Name | Description | Cancelable | Cancelable action | +| --------------------- | ---------------------------------------------------------- | ----------- | -------------------- | +| `drag:start` | Gets fired when drag action begins | true | Prevents drag start | +| `drag:move` | Gets fired when moving a draggable around | false | - | +| `drag:over` | Gets fired when dragging over other draggable | false | - | +| `drag:over:container` | Gets fired when dragging over other draggable container | false | - | +| `drag:out` | Gets fired when dragging out of other draggable | false | - | +| `drag:out:container` | Gets fired when dragging out of other draggable container | false | - | +| `drag:stop` | Gets fired when draggable has been released | false | - | +| `drag:pressure` | Gets fired when using force touch on draggable element | false | - | +| `mirror:created` | Gets fired when draggable mirror gets created | false | - | +| `mirror:attached` | Gets fired when draggable mirror gets attached to DOM | false | - | +| `mirror:move` | Gets fired when draggable mirror moves | true | Stop mirror movement | + +### Classes + +| Name | Description | Default | +| -------------------- | -------------------------------------------------------------------- | ---------------------------------- | +| `body:dragging` | Class added on the document body while dragging | `draggable--is-dragging` | +| `container:dragging` | Class added on the container where the draggable was picked up from | `draggable-container--is-dragging` | +| `source:dragging` | Class added on the draggable element that has been picked up | `draggable-source--is-dragging` | +| `source:placed` | Class added on the draggable element on `drag:stop` | `draggable-source--placed` | +| `container:placed` | Class added on the draggable container element on `drag:stop` | `draggable-container--placed` | +| `draggable:over` | Class added on draggable element you are dragging over | `draggable--over` | +| `container:over` | Class added on draggable container element you are dragging over | `draggable-container--over` | +| `mirror` | Class added on the mirror element | `draggable-mirror` | + +### Example + +This sample code will make list items draggable: + +```js +import {Draggable} from '@shopify/draggable'; + +new Draggable(document.querySelectorAll('ul')) + .on('drag:start', () => console.log('drag:start')) + .on('drag:move', () => console.log('drag:move')) + .on('drag:stop', () => console.log('drag:stop')); +``` diff --git a/src/sensors/drag-sensor.js b/src/Draggable/Sensors/DragSensor/DragSensor.js similarity index 97% rename from src/sensors/drag-sensor.js rename to src/Draggable/Sensors/DragSensor/DragSensor.js index 47365dca..10fe298e 100644 --- a/src/sensors/drag-sensor.js +++ b/src/Draggable/Sensors/DragSensor/DragSensor.js @@ -1,11 +1,12 @@ -import Sensor from './sensor'; -import {closest} from './../utils'; +import {closest} from 'shared/utils'; + +import Sensor from './../Sensor'; import { DragStartSensorEvent, DragMoveSensorEvent, DragStopSensorEvent, -} from './../events/sensor-event'; +} from './../SensorEvent'; export default class DragSensor extends Sensor { constructor(containers = [], options = {}) { diff --git a/src/Draggable/Sensors/DragSensor/README.md b/src/Draggable/Sensors/DragSensor/README.md new file mode 100644 index 00000000..9ba5650e --- /dev/null +++ b/src/Draggable/Sensors/DragSensor/README.md @@ -0,0 +1 @@ +## Drag Sensor diff --git a/src/Draggable/Sensors/DragSensor/index.js b/src/Draggable/Sensors/DragSensor/index.js new file mode 100644 index 00000000..f864a995 --- /dev/null +++ b/src/Draggable/Sensors/DragSensor/index.js @@ -0,0 +1,3 @@ +import DragSensor from './DragSensor'; + +export default DragSensor; diff --git a/src/sensors/force-touch-sensor.js b/src/Draggable/Sensors/ForceTouchSensor/ForceTouchSensor.js similarity index 98% rename from src/sensors/force-touch-sensor.js rename to src/Draggable/Sensors/ForceTouchSensor/ForceTouchSensor.js index 4f85c571..af11e0dc 100644 --- a/src/sensors/force-touch-sensor.js +++ b/src/Draggable/Sensors/ForceTouchSensor/ForceTouchSensor.js @@ -1,11 +1,11 @@ -import Sensor from './sensor'; +import Sensor from './../Sensor'; import { DragStartSensorEvent, DragMoveSensorEvent, DragStopSensorEvent, DragPressureSensorEvent, -} from './../events/sensor-event'; +} from './../SensorEvent'; export default class ForceTouchSensor extends Sensor { constructor(containers = [], options = {}) { diff --git a/src/Draggable/Sensors/ForceTouchSensor/README.md b/src/Draggable/Sensors/ForceTouchSensor/README.md new file mode 100644 index 00000000..372b4358 --- /dev/null +++ b/src/Draggable/Sensors/ForceTouchSensor/README.md @@ -0,0 +1 @@ +## Force Touch Sensor diff --git a/src/Draggable/Sensors/ForceTouchSensor/index.js b/src/Draggable/Sensors/ForceTouchSensor/index.js new file mode 100644 index 00000000..eae1cce1 --- /dev/null +++ b/src/Draggable/Sensors/ForceTouchSensor/index.js @@ -0,0 +1,3 @@ +import ForceTouchSensor from './ForceTouchSensor'; + +export default ForceTouchSensor; diff --git a/src/sensors/mouse-sensor.js b/src/Draggable/Sensors/MouseSensor/MouseSensor.js similarity index 75% rename from src/sensors/mouse-sensor.js rename to src/Draggable/Sensors/MouseSensor/MouseSensor.js index 1274e4ca..72541884 100644 --- a/src/sensors/mouse-sensor.js +++ b/src/Draggable/Sensors/MouseSensor/MouseSensor.js @@ -1,10 +1,10 @@ -import Sensor from './sensor'; +import Sensor from './../Sensor'; import { DragStartSensorEvent, DragMoveSensorEvent, DragStopSensorEvent, -} from './../events/sensor-event'; +} from './../SensorEvent'; export default class MouseSensor extends Sensor { constructor(containers = [], options = {}) { @@ -14,6 +14,7 @@ export default class MouseSensor extends Sensor { this.mouseDown = false; this.currentContainer = null; + this._onContextMenuWhileDragging = this._onContextMenuWhileDragging.bind(this); this._onMouseDown = this._onMouseDown.bind(this); this._onMouseMove = this._onMouseMove.bind(this); this._onMouseUp = this._onMouseUp.bind(this); @@ -38,7 +39,7 @@ export default class MouseSensor extends Sensor { } _onMouseDown(event) { - if (event.button === 2) { + if (event.button !== 0 || event.ctrlKey) { return; } @@ -64,6 +65,11 @@ export default class MouseSensor extends Sensor { this.currentContainer = container; this.dragging = !dragStartEvent.canceled(); + + if (this.dragging) { + document.addEventListener('contextmenu', this._onContextMenuWhileDragging); + document.addEventListener('dragstart', preventNativeDragStart); + } }, this.options.delay); } @@ -86,7 +92,12 @@ export default class MouseSensor extends Sensor { } _onMouseUp(event) { - this.mouseDown = false; + this.mouseDown = Boolean(this.openedContextMenu); + + if (this.openedContextMenu) { + this.openedContextMenu = false; + return; + } if (!this.dragging) { return; @@ -104,7 +115,19 @@ export default class MouseSensor extends Sensor { this.trigger(this.currentContainer, dragStopEvent); + document.removeEventListener('contextmenu', this._onContextMenuWhileDragging); + document.removeEventListener('dragstart', preventNativeDragStart); + this.currentContainer = null; this.dragging = false; } + + _onContextMenuWhileDragging(event) { + event.preventDefault(); + this.openedContextMenu = true; + } +} + +function preventNativeDragStart(event) { + event.preventDefault(); } diff --git a/src/Draggable/Sensors/MouseSensor/README.md b/src/Draggable/Sensors/MouseSensor/README.md new file mode 100644 index 00000000..a2127402 --- /dev/null +++ b/src/Draggable/Sensors/MouseSensor/README.md @@ -0,0 +1 @@ +## MouseSensor diff --git a/src/Draggable/Sensors/MouseSensor/index.js b/src/Draggable/Sensors/MouseSensor/index.js new file mode 100644 index 00000000..4adec74a --- /dev/null +++ b/src/Draggable/Sensors/MouseSensor/index.js @@ -0,0 +1,3 @@ +import MouseSensor from './MouseSensor'; + +export default MouseSensor; diff --git a/test/src/sensors/mouse-sensor.test.js b/src/Draggable/Sensors/MouseSensor/tests/MouseSensor.test.js similarity index 90% rename from test/src/sensors/mouse-sensor.test.js rename to src/Draggable/Sensors/MouseSensor/tests/MouseSensor.test.js index b000604c..69a082d3 100644 --- a/test/src/sensors/mouse-sensor.test.js +++ b/src/Draggable/Sensors/MouseSensor/tests/MouseSensor.test.js @@ -1,5 +1,3 @@ -import MouseSensor from 'sensors/mouse-sensor'; - import { createSandbox, triggerEvent, @@ -9,6 +7,8 @@ import { getLastSensorEventByType, } from 'helper'; +import MouseSensor from './..'; + const sampleMarkup = `
  • First item
  • @@ -37,7 +37,7 @@ describe('MouseSensor', () => { test('triggers `drag:start` sensor event on mousedown', () => { const draggable = sandbox.querySelector('li'); document.elementFromPoint = () => draggable; - triggerEvent(draggable, 'mousedown'); + triggerEvent(draggable, 'mousedown', {button: 0}); // Wait for delay jest.runTimersToTime(1); @@ -49,7 +49,7 @@ describe('MouseSensor', () => { test('cancels `drag:start` event when canceling sensor event', () => { const draggable = sandbox.querySelector('li'); document.elementFromPoint = () => draggable; - triggerEvent(draggable, 'mousedown'); + triggerEvent(draggable, 'mousedown', {button: 0}); sandbox.addEventListener('drag:start', (event) => { event.detail.cancel(); @@ -66,7 +66,7 @@ describe('MouseSensor', () => { test('does not trigger `drag:start` event releasing mouse before timeout', () => { const draggable = sandbox.querySelector('li'); document.elementFromPoint = () => draggable; - triggerEvent(draggable, 'mousedown'); + triggerEvent(draggable, 'mousedown', {button: 0}); triggerEvent(document.body, 'mouseup'); // Wait for delay @@ -81,7 +81,7 @@ describe('MouseSensor', () => { test('triggers `drag:move` event while moving the mouse', () => { const draggable = sandbox.querySelector('li'); document.elementFromPoint = () => draggable; - triggerEvent(draggable, 'mousedown'); + triggerEvent(draggable, 'mousedown', {button: 0}); // Wait for delay jest.runTimersToTime(1); @@ -96,7 +96,7 @@ describe('MouseSensor', () => { test('triggers `drag:stop` event when releasing mouse', () => { const draggable = sandbox.querySelector('li'); document.elementFromPoint = () => draggable; - triggerEvent(draggable, 'mousedown'); + triggerEvent(draggable, 'mousedown', {button: 0}); // Wait for delay jest.runTimersToTime(1); @@ -122,7 +122,7 @@ describe('MouseSensor', () => { test('does not trigger `drag:start` event when clicking on none draggable element', () => { const draggable = sandbox.querySelector('li'); document.elementFromPoint = () => draggable; - triggerEvent(document.body, 'mousedown'); + triggerEvent(document.body, 'mousedown', {button: 0}); // Wait for delay jest.runTimersToTime(1); diff --git a/src/Draggable/Sensors/README.md b/src/Draggable/Sensors/README.md new file mode 100644 index 00000000..760a8e54 --- /dev/null +++ b/src/Draggable/Sensors/README.md @@ -0,0 +1 @@ +## Sensors diff --git a/src/Draggable/Sensors/Sensor/README.md b/src/Draggable/Sensors/Sensor/README.md new file mode 100644 index 00000000..21b1e5f7 --- /dev/null +++ b/src/Draggable/Sensors/Sensor/README.md @@ -0,0 +1 @@ +## Sensor diff --git a/src/sensors/sensor.js b/src/Draggable/Sensors/Sensor/Sensor.js similarity index 100% rename from src/sensors/sensor.js rename to src/Draggable/Sensors/Sensor/Sensor.js diff --git a/src/Draggable/Sensors/Sensor/index.js b/src/Draggable/Sensors/Sensor/index.js new file mode 100644 index 00000000..06b5e8d2 --- /dev/null +++ b/src/Draggable/Sensors/Sensor/index.js @@ -0,0 +1,3 @@ +import Sensor from './Sensor'; + +export default Sensor; diff --git a/src/Draggable/Sensors/SensorEvent/README.md b/src/Draggable/Sensors/SensorEvent/README.md new file mode 100644 index 00000000..dff9a432 --- /dev/null +++ b/src/Draggable/Sensors/SensorEvent/README.md @@ -0,0 +1 @@ +## Sensor event diff --git a/src/events/sensor-event.js b/src/Draggable/Sensors/SensorEvent/SensorEvent.js similarity index 94% rename from src/events/sensor-event.js rename to src/Draggable/Sensors/SensorEvent/SensorEvent.js index 692b646f..47da5158 100644 --- a/src/events/sensor-event.js +++ b/src/Draggable/Sensors/SensorEvent/SensorEvent.js @@ -1,4 +1,4 @@ -import AbstractEvent from './abstract-event'; +import AbstractEvent from 'shared/AbstractEvent'; export class SensorEvent extends AbstractEvent { get originalEvent() { diff --git a/src/Draggable/Sensors/SensorEvent/index.js b/src/Draggable/Sensors/SensorEvent/index.js new file mode 100644 index 00000000..6f2e9572 --- /dev/null +++ b/src/Draggable/Sensors/SensorEvent/index.js @@ -0,0 +1,7 @@ +export { + SensorEvent, + DragStartSensorEvent, + DragMoveSensorEvent, + DragStopSensorEvent, + DragPressureSensorEvent, +} from './SensorEvent'; diff --git a/src/Draggable/Sensors/TouchSensor/README.md b/src/Draggable/Sensors/TouchSensor/README.md new file mode 100644 index 00000000..e467b1a1 --- /dev/null +++ b/src/Draggable/Sensors/TouchSensor/README.md @@ -0,0 +1 @@ +## Touch Sensor diff --git a/src/sensors/touch-sensor.js b/src/Draggable/Sensors/TouchSensor/TouchSensor.js similarity index 97% rename from src/sensors/touch-sensor.js rename to src/Draggable/Sensors/TouchSensor/TouchSensor.js index 47c0932c..9e48183c 100644 --- a/src/sensors/touch-sensor.js +++ b/src/Draggable/Sensors/TouchSensor/TouchSensor.js @@ -1,11 +1,11 @@ -import Sensor from './sensor'; -import {closest} from './../utils'; +import {closest} from 'shared/utils'; +import Sensor from './../Sensor'; import { DragStartSensorEvent, DragMoveSensorEvent, DragStopSensorEvent, -} from './../events/sensor-event'; +} from './../SensorEvent'; export default class TouchSensor extends Sensor { constructor(containers = [], options = {}) { diff --git a/src/Draggable/Sensors/TouchSensor/index.js b/src/Draggable/Sensors/TouchSensor/index.js new file mode 100644 index 00000000..c0c1ed06 --- /dev/null +++ b/src/Draggable/Sensors/TouchSensor/index.js @@ -0,0 +1,3 @@ +import TouchSensor from './TouchSensor'; + +export default TouchSensor; diff --git a/src/Draggable/Sensors/index.js b/src/Draggable/Sensors/index.js new file mode 100644 index 00000000..9c9bc9af --- /dev/null +++ b/src/Draggable/Sensors/index.js @@ -0,0 +1,13 @@ +import Sensor from './Sensor'; +import MouseSensor from './MouseSensor'; +import TouchSensor from './TouchSensor'; +import DragSensor from './DragSensor'; +import ForceTouchSensor from './ForceTouchSensor'; + +export { + Sensor, + MouseSensor, + TouchSensor, + DragSensor, + ForceTouchSensor, +}; diff --git a/src/Draggable/index.js b/src/Draggable/index.js new file mode 100644 index 00000000..4b8f0c7b --- /dev/null +++ b/src/Draggable/index.js @@ -0,0 +1,5 @@ +import Draggable from './Draggable'; +import {Accessibility, Mirror} from './Plugins'; + +export {Accessibility, Mirror}; +export default Draggable; diff --git a/test/src/draggable.test.js b/src/Draggable/tests/Draggable.test.js similarity index 87% rename from test/src/draggable.test.js rename to src/Draggable/tests/Draggable.test.js index 5d8115b8..a5330a30 100644 --- a/test/src/draggable.test.js +++ b/src/Draggable/tests/Draggable.test.js @@ -1,11 +1,11 @@ -import Draggable from 'draggable'; -import {DragStartEvent} from 'events/drag-event'; - import { createSandbox, triggerEvent, } from 'helper'; +import Draggable from './..'; +import {DragStartEvent} from './../DragEvent'; + const sampleMarkup = `
    • First item
    • @@ -37,7 +37,7 @@ describe('Draggable', () => { const callback = jest.fn(); draggable.on('drag:start', callback); - triggerEvent(draggableElement, 'mousedown'); + triggerEvent(draggableElement, 'mousedown', {button: 0}); // Wait for delay jest.runTimersToTime(100); diff --git a/src/droppable.js b/src/Droppable/Droppable.js similarity index 87% rename from src/droppable.js rename to src/Droppable/Droppable.js index 7ceb65f0..87cda87e 100644 --- a/src/droppable.js +++ b/src/Droppable/Droppable.js @@ -1,20 +1,22 @@ -import Draggable from './draggable'; -import {closest} from './utils'; +import {closest} from 'shared/utils'; +import Draggable from './../Draggable'; import { DroppableOverEvent, DroppableOutEvent, -} from './events/droppable-event'; +} from './DroppableEvent'; -const classes = { - 'droppable:active': 'draggable-droppable--active', - 'droppable:occupied': 'draggable-droppable--occupied', +const defaults = { + classes: { + 'droppable:active': 'draggable-droppable--active', + 'droppable:occupied': 'draggable-droppable--occupied', + }, }; export default class Droppable { constructor(containers = [], options = {}) { this.draggable = new Draggable(containers, options); - this.options = {...options}; + this.options = {...defaults, ...options}; this._onDragStart = this._onDragStart.bind(this); this._onDragMove = this._onDragMove.bind(this); @@ -45,7 +47,7 @@ export default class Droppable { } getClassNameFor(name) { - return this.options.classes[name] || classes[name]; + return this.options.classes[name] || defaults.classes[name]; } _onDragStart(event) { @@ -54,7 +56,7 @@ export default class Droppable { } this.droppables = this._getDroppables(); - const droppable = event.sensorEvent.target.closest(this.options.droppable); + const droppable = closest(event.sensorEvent.target, this.options.droppable); if (!droppable) { event.cancel(); @@ -122,7 +124,7 @@ export default class Droppable { this.lastDroppable.classList.remove(occupiedClass); } - droppable.appendChild(event.movableSource); + droppable.appendChild(event.source); droppable.classList.add(occupiedClass); return true; @@ -140,7 +142,7 @@ export default class Droppable { return; } - this.initialDroppable.appendChild(event.movableSource); + this.initialDroppable.appendChild(event.source); this.lastDroppable.classList.remove(this.getClassNameFor('droppable:occupied')); } diff --git a/src/events/droppable-event.js b/src/Droppable/DroppableEvent/DroppableEvent.js similarity index 88% rename from src/events/droppable-event.js rename to src/Droppable/DroppableEvent/DroppableEvent.js index b7bed379..792f2a34 100644 --- a/src/events/droppable-event.js +++ b/src/Droppable/DroppableEvent/DroppableEvent.js @@ -1,4 +1,4 @@ -import AbstractEvent from './abstract-event'; +import AbstractEvent from 'shared/AbstractEvent'; export class DroppableEvent extends AbstractEvent { static type = 'droppable'; diff --git a/src/Droppable/DroppableEvent/README.md b/src/Droppable/DroppableEvent/README.md new file mode 100644 index 00000000..5967303a --- /dev/null +++ b/src/Droppable/DroppableEvent/README.md @@ -0,0 +1 @@ +## Droppable event diff --git a/src/Droppable/DroppableEvent/index.js b/src/Droppable/DroppableEvent/index.js new file mode 100644 index 00000000..99c0e23a --- /dev/null +++ b/src/Droppable/DroppableEvent/index.js @@ -0,0 +1,4 @@ +export { + DroppableOverEvent, + DroppableOutEvent, +} from './DroppableEvent'; diff --git a/src/Droppable/README.md b/src/Droppable/README.md new file mode 100644 index 00000000..2d282387 --- /dev/null +++ b/src/Droppable/README.md @@ -0,0 +1,61 @@ +## Droppable + +Droppable allows you to declare draggable and droppable elements via options. +Droppable fires two events on top of the draggable events: `droppable:over` and `droppable:out`. + +### API + +**`new Droppable(containers: Array[HTMLElement]|NodeList, options: Object): Droppable`** +To create a droppable instance you need to specify the containers that hold draggable items, e.g. +`[document.body]` would work too. The second argument is an options object, which is described +below. + +**`droppable.on(eventName: String, listener: Function): Droppable`** +Droppable uses Draggables event emitter, so you can register callbacks for events. Droppable +also supports method chaining. + +**`droppable.off(eventName: String, listener: Function): Droppable`** +You can unregister listeners by using `.off()`, make sure to provide the same callback. + +**`droppable.trigger(eventName: String, event: AbstractEvent): Droppable`** +You can trigger events through droppable. This is used to fire events internally or by +extensions of Draggable. + +**`droppable.destroy(): void`** +Detaches all sensors and listeners, and cleans up after itself. + +### Options + +**`droppable {String|Array[HTMLElement]|NodeList|Function}`** +A css selector string, an array of elements, a NodeList or a function returning elements for droppable +elements within the `containers` specified. + +### Events + +| Name | Description | Cancelable | Cancelable action | +| --------------------- | ---------------------------------------------------------- | ----------- | -------------------- | +| `droppable:over` | Gets fired when dragging over droppable element | true | Prevents drop | +| `droppable:out` | Gets fired when dragging out of a droppable element | true | Prevents release | + +### Classes + +| Name | Description | Default | +| -------------------- | -------------------------------------------------------------------- | ---------------------------------- | +| `droppable:active` | Class added on droppables when drag starts | `draggable-droppable--active` | +| `droppable:occupied` | Class added on droppable element, when it contains a draggable | `draggable-droppable--occupied` | + +### Example + +This sample code will make list items draggable and allows to drop them inside another element: + +```js +import {Droppable} from '@shopify/draggable'; + +const droppable = new Droppable(document.querySelectorAll('ul'), { + draggable: 'li', + droppable: '#dropzone', +}); + +droppable.on('droppable:over', () => console.log('droppable:over')) +droppable.on('droppable:out', () => console.log('droppable:out')); +``` diff --git a/src/Droppable/index.js b/src/Droppable/index.js new file mode 100644 index 00000000..007027bc --- /dev/null +++ b/src/Droppable/index.js @@ -0,0 +1,3 @@ +import Droppable from './Droppable'; + +export default Droppable; diff --git a/src/behaviour/collidable.js b/src/Plugins/Collidable/Collidable.js similarity index 97% rename from src/behaviour/collidable.js rename to src/Plugins/Collidable/Collidable.js index 6e830ef8..9ce32ede 100644 --- a/src/behaviour/collidable.js +++ b/src/Plugins/Collidable/Collidable.js @@ -1,9 +1,9 @@ -import {closest} from './../utils'; +import {closest} from 'shared/utils'; import { CollidableInEvent, CollidableOutEvent, -} from './../events/collidable-event'; +} from './CollidableEvent'; export default class Collidable { constructor(draggable) { diff --git a/src/events/collidable-event.js b/src/Plugins/Collidable/CollidableEvent/CollidableEvent.js similarity index 90% rename from src/events/collidable-event.js rename to src/Plugins/Collidable/CollidableEvent/CollidableEvent.js index e73510b4..da915e81 100644 --- a/src/events/collidable-event.js +++ b/src/Plugins/Collidable/CollidableEvent/CollidableEvent.js @@ -1,4 +1,4 @@ -import AbstractEvent from './abstract-event'; +import AbstractEvent from 'shared/AbstractEvent'; export class CollidableEvent extends AbstractEvent { static type = 'collidable'; diff --git a/src/Plugins/Collidable/CollidableEvent/README.md b/src/Plugins/Collidable/CollidableEvent/README.md new file mode 100644 index 00000000..b4f18b69 --- /dev/null +++ b/src/Plugins/Collidable/CollidableEvent/README.md @@ -0,0 +1 @@ +## Collidable event diff --git a/src/Plugins/Collidable/CollidableEvent/index.js b/src/Plugins/Collidable/CollidableEvent/index.js new file mode 100644 index 00000000..cade28f2 --- /dev/null +++ b/src/Plugins/Collidable/CollidableEvent/index.js @@ -0,0 +1,4 @@ +export { + CollidableInEvent, + CollidableOutEvent, +} from './CollidableEvent'; diff --git a/src/Plugins/Collidable/README.md b/src/Plugins/Collidable/README.md new file mode 100644 index 00000000..daea804e --- /dev/null +++ b/src/Plugins/Collidable/README.md @@ -0,0 +1 @@ +## Collidable diff --git a/src/Plugins/Collidable/index.js b/src/Plugins/Collidable/index.js new file mode 100644 index 00000000..db732e28 --- /dev/null +++ b/src/Plugins/Collidable/index.js @@ -0,0 +1,3 @@ +import Collidable from './Collidable'; + +export default Collidable; diff --git a/src/Plugins/README.md b/src/Plugins/README.md new file mode 100644 index 00000000..bf4be4f1 --- /dev/null +++ b/src/Plugins/README.md @@ -0,0 +1 @@ +## Plugins diff --git a/src/Plugins/Snappable/README.md b/src/Plugins/Snappable/README.md new file mode 100644 index 00000000..a19b58f7 --- /dev/null +++ b/src/Plugins/Snappable/README.md @@ -0,0 +1 @@ +## Snappable diff --git a/src/behaviour/snappable.js b/src/Plugins/Snappable/Snappable.js similarity index 91% rename from src/behaviour/snappable.js rename to src/Plugins/Snappable/Snappable.js index 2d1cc799..96af8552 100644 --- a/src/behaviour/snappable.js +++ b/src/Plugins/Snappable/Snappable.js @@ -1,7 +1,7 @@ import { SnapInEvent, SnapOutEvent, -} from './../events/snappable-event'; +} from './SnappableEvent'; export default class Snappable { constructor(draggable) { @@ -38,7 +38,7 @@ export default class Snappable { return; } - this.firstSource = event.movableSource; + this.firstSource = event.source; } _onDragStop() { @@ -50,7 +50,7 @@ export default class Snappable { return; } - const source = event.movableSource || event.dragEvent.source; + const source = event.source || event.dragEvent.source; const mirror = event.mirror || event.dragEvent.mirror; if (source === this.firstSource) { @@ -86,7 +86,7 @@ export default class Snappable { } const mirror = event.mirror || event.dragEvent.mirror; - const source = event.movableSource || event.dragEvent.source; + const source = event.source || event.dragEvent.source; const snapOutEvent = new SnapOutEvent({ dragEvent: event, diff --git a/src/Plugins/Snappable/SnappableEvent/README.md b/src/Plugins/Snappable/SnappableEvent/README.md new file mode 100644 index 00000000..e69de29b diff --git a/src/events/snappable-event.js b/src/Plugins/Snappable/SnappableEvent/SnappableEvent.js similarity index 83% rename from src/events/snappable-event.js rename to src/Plugins/Snappable/SnappableEvent/SnappableEvent.js index f442045d..a7bd135e 100644 --- a/src/events/snappable-event.js +++ b/src/Plugins/Snappable/SnappableEvent/SnappableEvent.js @@ -1,4 +1,4 @@ -import AbstractEvent from './abstract-event'; +import AbstractEvent from 'shared/AbstractEvent'; export class SnapEvent extends AbstractEvent { get dragEvent() { diff --git a/src/Plugins/Snappable/SnappableEvent/index.js b/src/Plugins/Snappable/SnappableEvent/index.js new file mode 100644 index 00000000..4a4d2a7e --- /dev/null +++ b/src/Plugins/Snappable/SnappableEvent/index.js @@ -0,0 +1,4 @@ +export { + SnapInEvent, + SnapOutEvent, +} from './SnappableEvent'; diff --git a/src/Plugins/Snappable/index.js b/src/Plugins/Snappable/index.js new file mode 100644 index 00000000..86c2e2b1 --- /dev/null +++ b/src/Plugins/Snappable/index.js @@ -0,0 +1,3 @@ +import Snappable from './Snappable'; + +export default Snappable; diff --git a/src/Plugins/index.js b/src/Plugins/index.js new file mode 100644 index 00000000..48e1c4ed --- /dev/null +++ b/src/Plugins/index.js @@ -0,0 +1,7 @@ +import Collidable from './Collidable'; +import Snappable from './Snappable'; + +export { + Collidable, + Snappable, +}; diff --git a/src/Sortable/README.md b/src/Sortable/README.md new file mode 100644 index 00000000..d8a90406 --- /dev/null +++ b/src/Sortable/README.md @@ -0,0 +1,54 @@ +## Sortable + +Sortable allows you to reorder elements. It maintains the order internally and fires +three events on top of the draggable events: `sortable:start`, `sortable:sorted` and `sortable:stop`. + +### API + +**`new Sortable(containers: Array[HTMLElement]|NodeList, options: Object): Sortable`** +To create a sortable instance you need to specify the containers that hold draggable items, e.g. +`[document.body]` would work too. The second argument is an options object, which is described +below. + +**`sortable.on(eventName: String, listener: Function): Sortable`** +Sortable uses Draggables event emitter, so you can register callbacks for events. Sortable +also supports method chaining. + +**`sortable.off(eventName: String, listener: Function): Sortable`** +You can unregister listeners by using `.off()`, make sure to provide the same callback. + +**`sortable.trigger(eventName: String, event: AbstractEvent): Sortable`** +You can trigger events through sortable. This is used to fire events internally or by +extensions of Draggable. + +**`sortable.destroy(): void`** +Detaches all sensors and listeners, and cleans up after itself. + +### Options + +__Same as Draggable__ + +### Events + +| Name | Description | Cancelable | Cancelable action | +| --------------------- | ---------------------------------------------------------- | ----------- | -------------------- | +| `sortable:start` | Gets fired when drag action begins | true | Prevents drag start | +| `sortable:sorted` | Gets fired when the source gets sorted in the DOM | false | - | +| `sortable:stop` | Gets fired when dragging over other draggable | false | - | + +### Classes + +__Same as Draggable__ + +### Example + +This sample code will make list items draggable: + +```js +import {Sortable} from '@shopify/draggable'; + +new Sortable(document.querySelectorAll('ul')) + .on('sortable:start', () => console.log('sortable:start')) + .on('sortable:sorted', () => console.log('sortable:sorted')) + .on('sortable:stop', () => console.log('sortable:stop')); +``` diff --git a/src/sortable.js b/src/Sortable/Sortable.js similarity index 92% rename from src/sortable.js rename to src/Sortable/Sortable.js index 3b6446b7..6ff1b3dd 100644 --- a/src/sortable.js +++ b/src/Sortable/Sortable.js @@ -1,10 +1,10 @@ -import Draggable from './draggable'; +import Draggable from './../Draggable'; import { SortableStartEvent, SortableSortedEvent, SortableStopEvent, -} from './events/sortable-event'; +} from './SortableEvent'; export default class Sortable { constructor(containers = [], options = {}) { @@ -61,7 +61,7 @@ export default class Sortable { return; } - const moves = move(event.movableSource, event.over, event.overContainer); + const moves = move(event.source, event.over, event.overContainer); if (!moves) { return; @@ -76,11 +76,11 @@ export default class Sortable { } _onDragOver(event) { - if (event.over === event.movableSource) { + if (event.over === event.originalSource || event.over === event.source) { return; } - const moves = move(event.movableSource, event.over, event.overContainer); + const moves = move(event.source, event.over, event.overContainer); if (!moves) { return; diff --git a/src/Sortable/SortableEvent/README.md b/src/Sortable/SortableEvent/README.md new file mode 100644 index 00000000..92b6116f --- /dev/null +++ b/src/Sortable/SortableEvent/README.md @@ -0,0 +1 @@ +## Sortable event diff --git a/src/events/sortable-event.js b/src/Sortable/SortableEvent/SortableEvent.js similarity index 92% rename from src/events/sortable-event.js rename to src/Sortable/SortableEvent/SortableEvent.js index 663251f3..4f63b677 100644 --- a/src/events/sortable-event.js +++ b/src/Sortable/SortableEvent/SortableEvent.js @@ -1,4 +1,4 @@ -import AbstractEvent from './abstract-event'; +import AbstractEvent from 'shared/AbstractEvent'; export class SortableEvent extends AbstractEvent { get dragEvent() { diff --git a/src/Sortable/SortableEvent/index.js b/src/Sortable/SortableEvent/index.js new file mode 100644 index 00000000..d96f84bd --- /dev/null +++ b/src/Sortable/SortableEvent/index.js @@ -0,0 +1,5 @@ +export { + SortableStartEvent, + SortableSortedEvent, + SortableStopEvent, +} from './SortableEvent'; diff --git a/src/Sortable/index.js b/src/Sortable/index.js new file mode 100644 index 00000000..8f711ebc --- /dev/null +++ b/src/Sortable/index.js @@ -0,0 +1,3 @@ +import Sortable from './Sortable'; + +export default Sortable; diff --git a/src/Swappable/README.md b/src/Swappable/README.md new file mode 100644 index 00000000..9173f895 --- /dev/null +++ b/src/Swappable/README.md @@ -0,0 +1,57 @@ +## Swappable + +Swappable allows you to swap elements by dragging over them. No order will be maintained (unlike Sortable), +so any draggable element that gets dragged over will be swapped with the source element. + +### API + +**`new Swappable(containers: Array[HTMLElement]|NodeList, options: Object): Swappable`** +To create a swappable instance you need to specify the containers that hold draggable items, e.g. +`[document.body]` would work too. The second argument is an options object, which is described +below. + +**`swappable.on(eventName: String, listener: Function): Swappable`** +Swappable uses Draggables event emitter, so you can register callbacks for events. Swappable +also supports method chaining. + +**`swappable.off(eventName: String, listener: Function): Swappable`** +You can unregister listeners by using `.off()`, make sure to provide the same callback. + +**`swappable.trigger(eventName: String, event: AbstractEvent): Swappable`** +You can trigger events through swappable. This is used to fire events internally or by +extensions of Draggable. + +**`droppable.destroy(): void`** +Detaches all sensors and listeners, and cleans up after itself. + +### Options + +__Same as Draggable__ + +### Events + +| Name | Description | Cancelable | Cancelable action | +| --------------------- | ---------------------------------------------------------- | ----------- | -------------------- | +| `swappable:start` | Gets fired when starting to drag | true | Prevents drag | +| `swappable:swapped` | Gets fired before the source gets swapped | true | Prevents swap | +| `swappable:stop` | Gets fired when dragging out of a droppable element | false | - | + +### Classes + +__Same as Draggable__ + +### Example + +This sample code will make list items draggable and allows to drop them inside another element: + +```js +import {Swappable} from '@shopify/draggable'; + +const swappable = new Swappable(document.querySelectorAll('ul'), { + draggable: 'li', +}); + +swappable.on('swappable:start', () => console.log('swappable:start')) +swappable.on('swappable:swapped', () => console.log('swappable:swapped')); +swappable.on('swappable:stop', () => console.log('swappable:stop')); +``` diff --git a/src/swappable.js b/src/Swappable/Swappable.js similarity index 90% rename from src/swappable.js rename to src/Swappable/Swappable.js index cbbc5457..c9899083 100644 --- a/src/swappable.js +++ b/src/Swappable/Swappable.js @@ -1,10 +1,10 @@ -import Draggable from './draggable'; +import Draggable from './../Draggable'; import { SwappableStartEvent, SwappableSwappedEvent, SwappableStopEvent, -} from './events/swappable-event'; +} from './SwappableEvent'; export default class Swappable { constructor(containers = [], options = {}) { @@ -51,13 +51,13 @@ export default class Swappable { } _onDragOver(event) { - if (event.over === event.movableSource || event.canceled()) { + if (event.over === event.originalSource || event.over === event.source || event.canceled()) { return; } // swap originally swapped element back if (this.lastOver && this.lastOver !== event.over) { - swap(this.lastOver, event.movableSource); + swap(this.lastOver, event.source); } if (this.lastOver === event.over) { @@ -66,7 +66,7 @@ export default class Swappable { this.lastOver = event.over; } - swap(event.movableSource, event.over); + swap(event.source, event.over); // Let this cancel the actual swap const swappableSwappedEvent = new SwappableSwappedEvent({ diff --git a/src/Swappable/SwappableEvent/README.md b/src/Swappable/SwappableEvent/README.md new file mode 100644 index 00000000..86f58f53 --- /dev/null +++ b/src/Swappable/SwappableEvent/README.md @@ -0,0 +1 @@ +## Swappable event diff --git a/src/events/swappable-event.js b/src/Swappable/SwappableEvent/SwappableEvent.js similarity index 90% rename from src/events/swappable-event.js rename to src/Swappable/SwappableEvent/SwappableEvent.js index dc4d64d5..87265b37 100644 --- a/src/events/swappable-event.js +++ b/src/Swappable/SwappableEvent/SwappableEvent.js @@ -1,4 +1,4 @@ -import AbstractEvent from './abstract-event'; +import AbstractEvent from 'shared/AbstractEvent'; export class SwappableEvent extends AbstractEvent { get dragEvent() { diff --git a/src/Swappable/SwappableEvent/index.js b/src/Swappable/SwappableEvent/index.js new file mode 100644 index 00000000..dcd9d84d --- /dev/null +++ b/src/Swappable/SwappableEvent/index.js @@ -0,0 +1,5 @@ +export { + SwappableStartEvent, + SwappableSwappedEvent, + SwappableStopEvent, +} from './SwappableEvent'; diff --git a/src/Swappable/index.js b/src/Swappable/index.js new file mode 100644 index 00000000..d87ab0fd --- /dev/null +++ b/src/Swappable/index.js @@ -0,0 +1,3 @@ +import Swappable from './Swappable'; + +export default Swappable; diff --git a/test/src/swappable.test.js b/src/Swappable/tests/Swappable.test.js similarity index 92% rename from test/src/swappable.test.js rename to src/Swappable/tests/Swappable.test.js index 0112f478..67eaaeea 100644 --- a/test/src/swappable.test.js +++ b/src/Swappable/tests/Swappable.test.js @@ -1,6 +1,7 @@ -import Swappable from 'swappable'; import {createSandbox, triggerEvent} from 'helper'; +import Swappable from './..'; + const sampleMarkup = `
      • First item
      • @@ -32,7 +33,7 @@ describe('Swappable', () => { // Simulate drag start document.elementFromPoint = () => draggableElement; - triggerEvent(draggableElement, 'mousedown'); + triggerEvent(draggableElement, 'mousedown', {button: 0}); // Wait for gesture delay jest.runTimersToTime(0); diff --git a/src/index.js b/src/index.js index 29ef6cb9..55df4db6 100644 --- a/src/index.js +++ b/src/index.js @@ -1,10 +1,13 @@ -import Draggable from './draggable'; -import Sortable from './sortable'; -import Swappable from './swappable'; -import Droppable from './droppable'; -import AbstractEvent from './events/abstract-event'; -import Snappable from './behaviour/snappable'; -import Collidable from './behaviour/collidable'; +import AbstractEvent from 'shared/AbstractEvent'; + +import Draggable from './Draggable'; +import Droppable from './Droppable'; +import Swappable from './Swappable'; +import Sortable from './Sortable'; + +import {Snappable, Collidable} from './Plugins'; + +import ForceTouchSensor from './Draggable/Sensors/ForceTouchSensor'; export { Draggable, @@ -13,12 +16,6 @@ export { Droppable, Snappable, Collidable, - AbstractEvent, + ForceTouchSensor, + AbstractEvent as BaseEvent, }; - -export function createEventClass(options) { - function EventConstructor() { return null; } - EventConstructor.prototype = AbstractEvent.prototype; - createEventClass.type = options.type; - return createEventClass; -} diff --git a/src/events/abstract-event.js b/src/shared/AbstractEvent/AbstractEvent.js similarity index 100% rename from src/events/abstract-event.js rename to src/shared/AbstractEvent/AbstractEvent.js diff --git a/src/shared/AbstractEvent/README.md b/src/shared/AbstractEvent/README.md new file mode 100644 index 00000000..9787ff50 --- /dev/null +++ b/src/shared/AbstractEvent/README.md @@ -0,0 +1 @@ +## Abstract Event diff --git a/src/shared/AbstractEvent/index.js b/src/shared/AbstractEvent/index.js new file mode 100644 index 00000000..61457705 --- /dev/null +++ b/src/shared/AbstractEvent/index.js @@ -0,0 +1,3 @@ +import AbstractEvent from './AbstractEvent'; + +export default AbstractEvent; diff --git a/src/shared/README.md b/src/shared/README.md new file mode 100644 index 00000000..166a4942 --- /dev/null +++ b/src/shared/README.md @@ -0,0 +1 @@ +## Shared diff --git a/src/shared/utils/README.md b/src/shared/utils/README.md new file mode 100644 index 00000000..d4c6d743 --- /dev/null +++ b/src/shared/utils/README.md @@ -0,0 +1 @@ +## Utils diff --git a/src/shared/utils/closest/README.md b/src/shared/utils/closest/README.md new file mode 100644 index 00000000..52ec82b2 --- /dev/null +++ b/src/shared/utils/closest/README.md @@ -0,0 +1 @@ +## closest diff --git a/src/shared/utils/closest/closest.js b/src/shared/utils/closest/closest.js new file mode 100644 index 00000000..dcd7eb34 --- /dev/null +++ b/src/shared/utils/closest/closest.js @@ -0,0 +1,32 @@ +const matchFunction = Element.prototype.matches || + Element.prototype.webkitMatchesSelector || + Element.prototype.mozMatchesSelector || + Element.prototype.msMatchesSelector; + +export default function closest(element, selector) { + if (!element) { + return null; + } + + function conditionFn(currentElement) { + if (!currentElement) { + return currentElement; + } else if (typeof selector === 'string') { + return matchFunction.call(currentElement, selector); + } else { + return selector(currentElement); + } + } + + let current = element; + + do { + current = current.correspondingUseElement || current.correspondingElement || current; + if (conditionFn(current)) { + return current; + } + current = current.parentNode; + } while (current && current !== document.body && current !== document); + + return null; +} diff --git a/src/shared/utils/closest/index.js b/src/shared/utils/closest/index.js new file mode 100644 index 00000000..bb4baf97 --- /dev/null +++ b/src/shared/utils/closest/index.js @@ -0,0 +1,3 @@ +import closest from './closest'; + +export default closest; diff --git a/src/shared/utils/index.js b/src/shared/utils/index.js new file mode 100644 index 00000000..b92ac169 --- /dev/null +++ b/src/shared/utils/index.js @@ -0,0 +1,7 @@ +import closest from './closest'; +import scroll from './scroll'; + +export { + closest, + scroll, +}; diff --git a/src/shared/utils/scroll/README.md b/src/shared/utils/scroll/README.md new file mode 100644 index 00000000..d9435846 --- /dev/null +++ b/src/shared/utils/scroll/README.md @@ -0,0 +1 @@ +## scroll diff --git a/src/shared/utils/scroll/index.js b/src/shared/utils/scroll/index.js new file mode 100644 index 00000000..69ca63b9 --- /dev/null +++ b/src/shared/utils/scroll/index.js @@ -0,0 +1,3 @@ +import scroll from './scroll'; + +export default scroll; diff --git a/src/shared/utils/scroll/scroll.js b/src/shared/utils/scroll/scroll.js new file mode 100644 index 00000000..acca23bc --- /dev/null +++ b/src/shared/utils/scroll/scroll.js @@ -0,0 +1,18 @@ +let scrollAnimationFrame; + +export default function scroll(element, {clientX, clientY, speed, sensitivity}) { + if (scrollAnimationFrame) { + cancelAnimationFrame(scrollAnimationFrame); + } + + function scrollFn() { + const rect = element.getBoundingClientRect(); + const offsetY = (Math.abs(rect.bottom - clientY) <= sensitivity) - (Math.abs(rect.top - clientY) <= sensitivity); + const offsetX = (Math.abs(rect.right - clientX) <= sensitivity) - (Math.abs(rect.left - clientX) <= sensitivity); + element.scrollTop += offsetY * speed; + element.scrollLeft += offsetX * speed; + scrollAnimationFrame = requestAnimationFrame(scrollFn); + } + + scrollAnimationFrame = requestAnimationFrame(scrollFn); +} diff --git a/src/utils.js b/src/utils.js deleted file mode 100644 index 6ee18e14..00000000 --- a/src/utils.js +++ /dev/null @@ -1,52 +0,0 @@ -/** @module utils */ - -export function closest(element, selector) { - if (!element) { - return null; - } - - function conditionFn(currentElement) { - if (!currentElement) { - return currentElement; - } else if (typeof selector === 'string') { - const matchFunction = Element.prototype.matches || - Element.prototype.webkitMatchesSelector || - Element.prototype.mozMatchesSelector || - Element.prototype.msMatchesSelector; - return matchFunction.call(currentElement, selector); - } else { - return selector(currentElement); - } - } - - let current = element; - - do { - current = current.correspondingUseElement || current.correspondingElement || current; - if (conditionFn(current)) { - return current; - } - current = current.parentNode; - } while (current !== document.body && current !== document); - - return null; -} - -let scrollAnimationFrame; - -export function scroll(element, {clientX, clientY, speed, sensitivity}) { - if (scrollAnimationFrame) { - cancelAnimationFrame(scrollAnimationFrame); - } - - function scrollFn() { - const rect = element.getBoundingClientRect(); - const offsetY = (Math.abs(rect.bottom - clientY) <= sensitivity) - (Math.abs(rect.top - clientY) <= sensitivity); - const offsetX = (Math.abs(rect.right - clientX) <= sensitivity) - (Math.abs(rect.left - clientX) <= sensitivity); - element.scrollTop += offsetY * speed; - element.scrollLeft += offsetX * speed; - scrollAnimationFrame = requestAnimationFrame(scrollFn); - } - - scrollAnimationFrame = requestAnimationFrame(scrollFn); -} diff --git a/test/.eslintrc.js b/test/.eslintrc.js deleted file mode 100644 index 84ad3b27..00000000 --- a/test/.eslintrc.js +++ /dev/null @@ -1,12 +0,0 @@ -module.exports = { - globals: { - jest: false, - afterAll: false, - afterEach: false, - beforeAll: false, - beforeEach: false, - describe: false, - test: false, - expect: false, - }, -}; diff --git a/webpack.config.js b/webpack.config.js index 37ef31bc..865fc7ca 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -13,6 +13,12 @@ function createConfig({name, filename = name, source, path = ''}) { libraryTarget: 'umd', umdNamedDefine: true }, + resolve: { + modules: [ + 'node_modules', + 'src/', + ], + }, module: { loaders: [ {