├── .babelrc
├── .circleci
└── config.yml
├── .editorconfig
├── .eslintrc.js
├── .gitignore
├── .npmignore
├── .npmrc
├── .prettierrc.yml
├── CHANGELOG.md
├── LICENSE.md
├── README.md
├── examples
└── basic
│ ├── .gitignore
│ ├── app.js
│ ├── index.html
│ └── package.json
├── package.json
├── src
└── clickdrag.jsx
└── test
├── .eslintrc.js
├── clickdrag.test.jsx
└── setup.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | "env",
4 | "react"
5 | ],
6 | "plugins": [
7 | "transform-object-rest-spread"
8 | ]
9 | }
10 |
--------------------------------------------------------------------------------
/.circleci/config.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | jobs:
3 | test_with_node_6:
4 | docker:
5 | - image: circleci/node:6
6 | steps:
7 | - checkout
8 | - run:
9 | name: Install dependencies
10 | command: npm install
11 | - run:
12 | name: Test
13 | command: npm test
14 | test_with_node_8:
15 | docker:
16 | - image: circleci/node:8
17 | steps:
18 | - checkout
19 | - run:
20 | name: Install dependencies
21 | command: npm install
22 | - run:
23 | name: Test
24 | command: npm test
25 | test_with_node_9:
26 | docker:
27 | - image: circleci/node:9
28 | steps:
29 | - checkout
30 | - run:
31 | name: Install dependencies
32 | command: npm install
33 | - run:
34 | name: Test
35 | command: npm test
36 | test_with_node_10:
37 | docker:
38 | - image: circleci/node:10
39 | steps:
40 | - checkout
41 | - run:
42 | name: Install dependencies
43 | command: npm install
44 | - run:
45 | name: Test
46 | command: npm test
47 | - run:
48 | name: Deploy coverage
49 | command: bash <(curl -s https://codecov.io/bash)
50 | workflows:
51 | version: 2
52 | test_all:
53 | jobs:
54 | - test_with_node_6
55 | - test_with_node_8
56 | - test_with_node_9
57 | - test_with_node_10
58 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | indent_style = space
5 | indent_size = 2
6 | end_of_line = lf
7 | charset = utf-8
8 | trim_trailing_whitespace = true
9 | insert_final_newline = true
10 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | extends: ['eslint:recommended', 'plugin:react/recommended'],
4 | parserOptions: {
5 | ecmaVersion: 2018,
6 | sourceType: 'module',
7 | ecmaFeatures: {
8 | jsx: true,
9 | },
10 | },
11 | rules: {
12 | 'react/no-find-dom-node': '0',
13 | },
14 | globals: {
15 | document: true,
16 | MouseEvent: true,
17 | },
18 | };
19 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules/
3 | npm-debug.log
4 |
5 | /lib
6 | coverage/
7 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | src/
2 | test/
3 | coverage/
4 | .nyc_output
5 |
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | package-lock=false
2 |
--------------------------------------------------------------------------------
/.prettierrc.yml:
--------------------------------------------------------------------------------
1 | singleQuote: true
2 | printWidth: 100
3 | trailingComma: es5
4 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Change Log
2 |
3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
4 |
5 |
6 |
7 | # [4.0.0](https://github.com/tleunen/react-clickdrag/compare/v3.0.2...v4.0.0) (2018-09-06)
8 |
9 | ### Bug Fixes
10 |
11 | - Add passive arg to touchstart ([#99](https://github.com/tleunen/react-clickdrag/issues/99)) ([6ffa1c1](https://github.com/tleunen/react-clickdrag/commit/6ffa1c1))
12 |
13 | ### Features
14 |
15 | - Update project to react16, jest, enzyme3 ([#103](https://github.com/tleunen/react-clickdrag/issues/103)) ([74adbd1](https://github.com/tleunen/react-clickdrag/commit/74adbd1))
16 |
17 | ### BREAKING CHANGES
18 |
19 | - This version is only compatible with React >= 16
20 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2014-2015 Tommy Leunen
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # react-clickdrag
2 | ![npm][npm-version-image] [![Build Status][ci-image]][ci-url] [![Coverage Status][coverage-image]][coverage-url]
3 |
4 | With `react-clickdrag`, you'll be able to easily add a click and drag feature on every component you want.
5 |
6 | ## Usage
7 |
8 | `react-clickdrag` is a special function which takes 2 arguments. The first one is the Component itself, the second argument is the options `react-clickdrag` can take.
9 |
10 | The options are:
11 | - `touch`: Enable touch events (default: false)
12 | - `resetOnSpecialKeys`: Decide to reset the mouse position when a special keys is pressed (ctrl, shift, alt). (default: false)
13 | - `getSpecificEventData`: Function to specify if you need specific data from the mouse/touch event. (default: empty function)
14 | - `onDragStart`: Function called when you start dragging the component. (default: empty function)
15 | - `onDragStop`: Function called when you stop dragging the component. (default: empty function)
16 | - `onDragMove`: Function called when you move the component. (default: empty function)
17 |
18 | When you wrap your component using the `clickdrag` function, your component will receive a special props called `dataDrag`. Inside this `dataDrag` object, you'll find these information:
19 | - `isMouseDown` (boolean)
20 | - `isMoving` (boolean)
21 | - `mouseDownPositionX` (number)
22 | - `mouseDownPositionY` (number)
23 | - `moveDeltaX` (number)
24 | - `moveDeltaY` (number)
25 |
26 | If you defined a specific event data function using `getSpecificEventData`. You'll also find the information you specify in the `dataDrag` props.
27 |
28 | ## Example
29 |
30 | Here's a copy of the example you can find in [example folder](/examples/basic/)
31 |
32 | ```js
33 | import clickdrag from 'react-clickdrag';
34 |
35 |
36 | class ExampleComponent extends React.Component {
37 |
38 | constructor(props) {
39 | super(props);
40 |
41 | this.state = {
42 | lastPositionX: 0,
43 | lastPositionY: 0,
44 | currentX: 0,
45 | currentY: 0
46 | };
47 | }
48 |
49 | componentWillReceiveProps(nextProps) {
50 | if(nextProps.dataDrag.isMoving) {
51 | this.setState({
52 | currentX: this.state.lastPositionX + nextProps.dataDrag.moveDeltaX,
53 | currentY: this.state.lastPositionY + nextProps.dataDrag.moveDeltaY
54 | });
55 | }
56 | else {
57 | this.setState({
58 | lastPositionX: this.state.currentX,
59 | lastPositionY: this.state.currentY
60 | });
61 | }
62 | }
63 |
64 | render() {
65 | var translation = 'translate('+this.state.currentX+'px, '+this.state.currentY+'px)';
66 |
67 | return React.createElement('div', {
68 | style: {width: '200px', height: '200px', backgroundColor: 'red', transform: translation}
69 | });
70 | }
71 | }
72 |
73 | var ClickDragExample = clickDrag(ExampleComponent, {touch: true});
74 |
75 | export default ClickDragExample;
76 |
77 | ```
78 | You can find another example of this module inside [react-number-editor](https://github.com/tleunen/react-number-editor).
79 |
80 | ## License
81 |
82 | MIT, see [LICENSE.md](/LICENSE.md) for details.
83 |
84 |
85 | [ci-image]: https://circleci.com/gh/tleunen/react-clickdrag.svg?style=shield
86 | [ci-url]: https://circleci.com/gh/tleunen/react-clickdrag
87 | [coverage-image]: https://codecov.io/gh/tleunen/react-clickdrag/branch/master/graph/badge.svg
88 | [coverage-url]: https://codecov.io/gh/tleunen/react-clickdrag
89 | [npm-version-image]: https://img.shields.io/npm/v/react-clickdrag.svg
90 |
--------------------------------------------------------------------------------
/examples/basic/.gitignore:
--------------------------------------------------------------------------------
1 | /app.min.js
--------------------------------------------------------------------------------
/examples/basic/app.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var React = require('react');
4 | var { render } = require('react-dom');
5 | var clickDrag = require('../../lib/clickdrag').default;
6 |
7 | class ExampleComponent extends React.Component {
8 | constructor(props) {
9 | super(props);
10 |
11 | this.state = {
12 | lastPositionX: 0,
13 | lastPositionY: 0,
14 | currentX: 0,
15 | currentY: 0,
16 | };
17 | }
18 |
19 | componentWillReceiveProps(nextProps) {
20 | if (nextProps.dataDrag.isMoving) {
21 | this.setState({
22 | currentX: this.state.lastPositionX + nextProps.dataDrag.moveDeltaX,
23 | currentY: this.state.lastPositionY + nextProps.dataDrag.moveDeltaY,
24 | });
25 | } else {
26 | this.setState({
27 | lastPositionX: this.state.currentX,
28 | lastPositionY: this.state.currentY,
29 | });
30 | }
31 | }
32 |
33 | render() {
34 | var translation = 'translate(' + this.state.currentX + 'px, ' + this.state.currentY + 'px)';
35 |
36 | return React.createElement('div', {
37 | style: { width: '200px', height: '200px', backgroundColor: 'red', transform: translation },
38 | });
39 | }
40 | }
41 |
42 | var ClickDragExample = clickDrag(ExampleComponent, { touch: true });
43 |
44 | render(React.createElement(ClickDragExample), document.getElementById('App'));
45 |
--------------------------------------------------------------------------------
/examples/basic/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Example
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/examples/basic/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "scripts": {
3 | "build": "npx browserify app.js --debug -o app.min.js"
4 | },
5 | "devDependencies": {
6 | "babelify": "^5.0.4"
7 | },
8 | "dependencies": {
9 | "browserify": "^16.2.2"
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-clickdrag",
3 | "version": "4.0.0",
4 | "description": "Easily click/touch and drag a react component",
5 | "main": "lib/clickdrag.js",
6 | "files": [
7 | "lib/"
8 | ],
9 | "repository": {
10 | "type": "git",
11 | "url": "https://github.com/tleunen/react-clickdrag.git"
12 | },
13 | "bugs": {
14 | "url": "https://github.com/tleunen/react-clickdrag/issues"
15 | },
16 | "homepage": "https://github.com/tleunen/react-clickdrag",
17 | "keywords": [
18 | "react",
19 | "react-component",
20 | "drag",
21 | "clickdrag",
22 | "touch",
23 | "touchdrag",
24 | "dom",
25 | "element"
26 | ],
27 | "author": "Tommy Leunen (http://tommyleunen.com/)",
28 | "license": "MIT",
29 | "devDependencies": {
30 | "babel-cli": "^6.18.0",
31 | "babel-core": "^6.21.0",
32 | "babel-jest": "^23.4.2",
33 | "babel-plugin-transform-object-rest-spread": "^6.20.2",
34 | "babel-preset-env": "^1.7.0",
35 | "babel-preset-react": "^6.16.0",
36 | "enzyme": "^3.6.0",
37 | "enzyme-adapter-react-16": "^1.5.0",
38 | "eslint": "^5.5.0",
39 | "eslint-plugin-react": "^7.11.1",
40 | "husky": "^1.0.0-rc.13",
41 | "jest": "^23.5.0",
42 | "lint-staged": "^7.2.2",
43 | "prettier": "^1.14.2",
44 | "react": "^16.4.2",
45 | "react-dom": "^16.4.2",
46 | "standard-version": "^4.0.0"
47 | },
48 | "peerDependencies": {
49 | "react": "^16.0.0-0",
50 | "react-dom": "^16.0.0-0"
51 | },
52 | "scripts": {
53 | "clean": "rimraf coverage lib out",
54 | "test:suite": "mocha -r babel-register -r ./test/setup.js 'test/*'",
55 | "test:watch": "npm run test:suite -- -w",
56 | "pretest": "npm run lint",
57 | "test": "jest --coverage",
58 | "lint": "eslint src test --ext .jsx,.js",
59 | "compile": "babel src --out-dir lib",
60 | "prepublish": "npm run compile",
61 | "release": "standard-version"
62 | },
63 | "husky": {
64 | "hooks": {
65 | "pre-commit": "lint-staged"
66 | }
67 | },
68 | "lint-staged": {
69 | "*.{js,jsx,json,md}": [
70 | "prettier --write",
71 | "git add"
72 | ]
73 | },
74 | "jest": {
75 | "setupTestFrameworkScriptFile": "/test/setup.js",
76 | "collectCoverageFrom": [
77 | "src/**/*.js{x}"
78 | ]
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/src/clickdrag.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { findDOMNode } from 'react-dom';
3 |
4 | const noop = () => {};
5 |
6 | function clickDrag(Component, opts = {}) {
7 | const touch = opts.touch || false;
8 | const resetOnSpecialKeys = opts.resetOnSpecialKeys || false;
9 | const getSpecificEventData = opts.getSpecificEventData || (() => ({}));
10 |
11 | const onDragStart = opts.onDragStart || noop;
12 | const onDragStop = opts.onDragStop || noop;
13 | const onDragMove = opts.onDragMove || noop;
14 |
15 | class ClickDrag extends React.Component {
16 | constructor(props) {
17 | super(props);
18 |
19 | this.onMouseDown = this.onMouseDown.bind(this);
20 | this.onMouseUp = this.onMouseUp.bind(this);
21 | this.onMouseMove = this.onMouseMove.bind(this);
22 |
23 | this.state = {
24 | isMouseDown: false,
25 | isMoving: false,
26 | mouseDownPositionX: 0,
27 | mouseDownPositionY: 0,
28 | moveDeltaX: 0,
29 | moveDeltaY: 0,
30 | };
31 |
32 | this.wasUsingSpecialKeys = false;
33 | }
34 |
35 | componentDidMount() {
36 | const node = findDOMNode(this);
37 |
38 | node && node.addEventListener('mousedown', this.onMouseDown);
39 | document.addEventListener('mousemove', this.onMouseMove);
40 | document.addEventListener('mouseup', this.onMouseUp);
41 |
42 | if (touch) {
43 | node && node.addEventListener('touchstart', this.onMouseDown, { passive: true });
44 | document.addEventListener('touchmove', this.onMouseMove);
45 | document.addEventListener('touchend', this.onMouseUp);
46 | }
47 | }
48 |
49 | componentWillUnmount() {
50 | const node = findDOMNode(this);
51 |
52 | node && node.removeEventListener('mousedown', this.onMouseDown);
53 | document.removeEventListener('mousemove', this.onMouseMove);
54 | document.removeEventListener('mouseup', this.onMouseUp);
55 |
56 | if (touch) {
57 | node && node.removeEventListener('touchstart', this.onMouseDown, { passive: true });
58 | document.removeEventListener('touchmove', this.onMouseMove);
59 | document.removeEventListener('touchend', this.onMouseUp);
60 | }
61 | }
62 |
63 | onMouseDown(e) {
64 | // only left mouse button
65 | if (touch || e.button === 0) {
66 | const pt = (e.changedTouches && e.changedTouches[0]) || e;
67 |
68 | this.setMousePosition(pt.clientX, pt.clientY);
69 |
70 | onDragStart(e);
71 | }
72 | }
73 |
74 | onMouseUp(e) {
75 | if (this.state.isMouseDown) {
76 | this.setState({
77 | isMouseDown: false,
78 | isMoving: false,
79 | });
80 |
81 | onDragStop(e);
82 | }
83 | }
84 |
85 | onMouseMove(e) {
86 | if (this.state.isMouseDown) {
87 | const pt = (e.changedTouches && e.changedTouches[0]) || e;
88 |
89 | const isUsingSpecialKeys = e.metaKey || e.ctrlKey || e.shiftKey || e.altKey;
90 | if (resetOnSpecialKeys && this.wasUsingSpecialKeys !== isUsingSpecialKeys) {
91 | this.wasUsingSpecialKeys = isUsingSpecialKeys;
92 | this.setMousePosition(pt.clientX, pt.clientY);
93 | } else {
94 | this.setState({
95 | isMoving: true,
96 | moveDeltaX: pt.clientX - this.state.mouseDownPositionX,
97 | moveDeltaY: pt.clientY - this.state.mouseDownPositionY,
98 | ...getSpecificEventData(e),
99 | });
100 | }
101 |
102 | onDragMove(e);
103 | }
104 | }
105 |
106 | setMousePosition(x, y) {
107 | this.setState({
108 | isMouseDown: true,
109 | isMoving: false,
110 | mouseDownPositionX: x,
111 | mouseDownPositionY: y,
112 | moveDeltaX: 0,
113 | moveDeltaY: 0,
114 | });
115 | }
116 |
117 | render() {
118 | return ;
119 | }
120 | }
121 |
122 | return ClickDrag;
123 | }
124 |
125 | export default clickDrag;
126 |
--------------------------------------------------------------------------------
/test/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | env: {
3 | jest: true,
4 | },
5 | };
6 |
--------------------------------------------------------------------------------
/test/clickdrag.test.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { findDOMNode } from 'react-dom';
3 | import { mount } from 'enzyme';
4 | import clickdrag from '../src/clickdrag';
5 |
6 | const initialState = {
7 | isMouseDown: false,
8 | isMoving: false,
9 | mouseDownPositionX: 0,
10 | mouseDownPositionY: 0,
11 | moveDeltaX: 0,
12 | moveDeltaY: 0,
13 | };
14 |
15 | function mouseDownAt(node, x, y, rightClick) {
16 | node.dispatchEvent(
17 | new MouseEvent('mousedown', {
18 | button: rightClick ? 1 : 0,
19 | clientX: x,
20 | clientY: y,
21 | })
22 | );
23 | }
24 |
25 | function mouseMove(x, y, ctrl, shift, meta) {
26 | document.dispatchEvent(
27 | new MouseEvent('mousemove', {
28 | clientX: x,
29 | clientY: y,
30 | shiftKey: !!shift,
31 | metaKey: !!meta,
32 | ctrlKey: !!ctrl,
33 | })
34 | );
35 | }
36 |
37 | it('should render the given component', () => {
38 | const Comp = () => Hello
;
39 | const DecoraredComp = clickdrag(Comp);
40 | const wrapper = mount();
41 |
42 | expect(wrapper.html()).toEqual('Hello
');
43 | });
44 |
45 | it('should have a default state on first render', () => {
46 | const Comp = () => Hello
;
47 | const DecoraredComp = clickdrag(Comp);
48 | const wrapper = mount();
49 |
50 | expect(wrapper.state()).toEqual(initialState);
51 | });
52 |
53 | describe('attach/detach events', () => {
54 | let spyAttach;
55 | let spyDetach;
56 |
57 | beforeEach(() => {
58 | spyAttach = jest.spyOn(document, 'addEventListener').mockImplementation(() => jest.fn());
59 | spyDetach = jest.spyOn(document, 'removeEventListener').mockImplementation(() => jest.fn());
60 | });
61 |
62 | afterEach(() => {
63 | spyAttach.mockRestore();
64 | spyDetach.mockRestore();
65 | });
66 |
67 | it('should attach events on document', () => {
68 | const Comp = () => Hello
;
69 | const DecoraredComp = clickdrag(Comp);
70 |
71 | mount();
72 |
73 | expect(spyAttach).toHaveBeenCalledTimes(2);
74 | expect(spyAttach.mock.calls[0][0]).toBe('mousemove');
75 | expect(spyAttach.mock.calls[1][0]).toBe('mouseup');
76 | });
77 |
78 | it('should detach events on document', () => {
79 | const Comp = () => Hello
;
80 | const DecoraredComp = clickdrag(Comp);
81 |
82 | const wrapper = mount();
83 |
84 | wrapper.unmount();
85 |
86 | expect(spyDetach).toHaveBeenCalledTimes(2);
87 | expect(spyDetach.mock.calls[0][0]).toBe('mousemove');
88 | expect(spyDetach.mock.calls[1][0]).toBe('mouseup');
89 | });
90 | });
91 |
92 | describe('mousedown', () => {
93 | it('should set the mouse position and isMouseDown on left mousedown', () => {
94 | const Comp = () => Hello
;
95 | const DecoraredComp = clickdrag(Comp);
96 | const wrapper = mount();
97 |
98 | // because we manually listen on the node, we cannot use enzyme simulate
99 | mouseDownAt(findDOMNode(wrapper.instance()), 132, 642);
100 |
101 | expect(wrapper.state()).toEqual({
102 | ...initialState,
103 | isMouseDown: true,
104 | mouseDownPositionX: 132,
105 | mouseDownPositionY: 642,
106 | });
107 | });
108 |
109 | it('should not set the mouse position on right mousedown', () => {
110 | const Comp = () => Hello
;
111 | const DecoraredComp = clickdrag(Comp);
112 | const wrapper = mount();
113 |
114 | // because we manually listen on the node, we cannot use enzyme simulate
115 | mouseDownAt(findDOMNode(wrapper.instance()), 1, 6, true);
116 |
117 | expect(wrapper.state()).toEqual(initialState);
118 | });
119 | });
120 |
121 | describe('mouseup', () => {
122 | it('should not update the sate without previously down', () => {
123 | const Comp = () => Hello
;
124 | const DecoraredComp = clickdrag(Comp);
125 | const wrapper = mount();
126 |
127 | document.dispatchEvent(new MouseEvent('mouseup'));
128 |
129 | expect(wrapper.state()).toEqual(initialState);
130 | });
131 |
132 | it('should reset isMouseDown', () => {
133 | const Comp = () => Hello
;
134 | const DecoraredComp = clickdrag(Comp);
135 | const wrapper = mount();
136 |
137 | mouseDownAt(findDOMNode(wrapper.instance()), 132, 642);
138 | document.dispatchEvent(new MouseEvent('mouseup'));
139 |
140 | expect(wrapper.state()).toEqual({
141 | ...initialState,
142 | isMouseDown: false,
143 | mouseDownPositionX: 132,
144 | mouseDownPositionY: 642,
145 | });
146 | });
147 | });
148 |
149 | describe('mousemove', () => {
150 | it('should not update the sate without previously down', () => {
151 | const Comp = () => Hello
;
152 | const DecoraredComp = clickdrag(Comp);
153 | const wrapper = mount();
154 |
155 | document.dispatchEvent(new MouseEvent('mousemove'));
156 |
157 | expect(wrapper.state()).toEqual(initialState);
158 | });
159 |
160 | it('should set the move delta values', () => {
161 | const Comp = () => Hello
;
162 | const DecoraredComp = clickdrag(Comp);
163 | const wrapper = mount();
164 |
165 | mouseDownAt(findDOMNode(wrapper.instance()), 100, 150);
166 | mouseMove(50, 125);
167 |
168 | expect(wrapper.state()).toEqual({
169 | isMouseDown: true,
170 | isMoving: true,
171 | mouseDownPositionX: 100,
172 | mouseDownPositionY: 150,
173 | moveDeltaX: -50,
174 | moveDeltaY: -25,
175 | });
176 | });
177 |
178 | it('should also set the specified custom event data', () => {
179 | const Comp = () => Hello
;
180 | const DecoraredComp = clickdrag(Comp, {
181 | getSpecificEventData: e => ({
182 | shiftKey: e.shiftKey,
183 | ctrlKey: e.ctrlKey,
184 | }),
185 | });
186 | const wrapper = mount();
187 |
188 | mouseDownAt(findDOMNode(wrapper.instance()), 100, 150);
189 | mouseMove(50, 125, false, true);
190 |
191 | expect(wrapper.state()).toEqual({
192 | isMouseDown: true,
193 | isMoving: true,
194 | mouseDownPositionX: 100,
195 | mouseDownPositionY: 150,
196 | moveDeltaX: -50,
197 | moveDeltaY: -25,
198 | shiftKey: true,
199 | ctrlKey: false,
200 | });
201 | });
202 |
203 | it('should not reset the mouse position when clicking on a special key', () => {
204 | const Comp = () => Hello
;
205 | const DecoraredComp = clickdrag(Comp, {
206 | getSpecificEventData: e => ({
207 | ctrlKey: e.ctrlKey,
208 | }),
209 | });
210 | const wrapper = mount();
211 |
212 | mouseDownAt(findDOMNode(wrapper.instance()), 100, 150);
213 | mouseMove(50, 125);
214 | mouseMove(1, 173, true);
215 |
216 | expect(wrapper.state()).toEqual({
217 | isMouseDown: true,
218 | isMoving: true,
219 | mouseDownPositionX: 100,
220 | mouseDownPositionY: 150,
221 | moveDeltaX: -99,
222 | moveDeltaY: 23,
223 | ctrlKey: true,
224 | });
225 | });
226 |
227 | it('should reset the mouse position when clicking on a special key, with resetOnSpecialKeys', () => {
228 | const Comp = () => Hello
;
229 | const DecoraredComp = clickdrag(Comp, {
230 | resetOnSpecialKeys: true,
231 | getSpecificEventData: e => ({
232 | ctrlKey: e.ctrlKey,
233 | }),
234 | });
235 | const wrapper = mount();
236 |
237 | mouseDownAt(findDOMNode(wrapper.instance()), 100, 150);
238 | mouseMove(50, 125);
239 | mouseMove(1, 173, true);
240 | mouseMove(1, 174, true);
241 |
242 | expect(wrapper.state()).toEqual({
243 | isMouseDown: true,
244 | isMoving: true,
245 | mouseDownPositionX: 1,
246 | mouseDownPositionY: 173,
247 | moveDeltaX: 0,
248 | moveDeltaY: 1,
249 | ctrlKey: true,
250 | });
251 | });
252 | });
253 |
254 | describe('handle touch events', () => {
255 | describe('attach/detach events', () => {
256 | let spyAttach;
257 | let spyDetach;
258 |
259 | beforeEach(() => {
260 | spyAttach = jest.spyOn(document, 'addEventListener').mockImplementation(() => jest.fn());
261 | spyDetach = jest.spyOn(document, 'removeEventListener').mockImplementation(() => jest.fn());
262 | });
263 |
264 | afterEach(() => {
265 | spyAttach.mockRestore();
266 | spyDetach.mockRestore();
267 | });
268 |
269 | it('should attach events on document', () => {
270 | const Comp = () => Hello
;
271 | const DecoraredComp = clickdrag(Comp, { touch: true });
272 |
273 | mount();
274 |
275 | expect(spyAttach).toHaveBeenCalledTimes(4);
276 | expect(spyAttach.mock.calls[2][0]).toBe('touchmove');
277 | expect(spyAttach.mock.calls[3][0]).toBe('touchend');
278 | });
279 |
280 | it('should detach events on document', () => {
281 | const Comp = () => Hello
;
282 | const DecoraredComp = clickdrag(Comp, { touch: true });
283 |
284 | const wrapper = mount();
285 |
286 | wrapper.unmount();
287 |
288 | expect(spyDetach).toHaveBeenCalledTimes(4);
289 | expect(spyDetach.mock.calls[2][0]).toBe('touchmove');
290 | expect(spyDetach.mock.calls[3][0]).toBe('touchend');
291 | });
292 | });
293 |
294 | // describe('touchstart', () => {
295 | // it('should set the mouse position and isMouseDown', () => {
296 | // const Comp = () => Hello
;
297 | // const DecoraredComp = clickdrag(Comp, { touch: true });
298 | // const wrapper = mount();
299 | //
300 | // findDOMNode(wrapper.instance()).dispatchEvent(new MouseEvent('touchstart', {
301 | // changedTouches: [{
302 | // clientX: 1,
303 | // clientY: 4
304 | // }]
305 | // }));
306 | //
307 | // expect(wrapper.state()).toBe({
308 | // ...initialState,
309 | // isMouseDown: true,
310 | // mouseDownPositionX: 123,
311 | // mouseDownPositionY: 321
312 | // });
313 | // });
314 | // });
315 | });
316 |
--------------------------------------------------------------------------------
/test/setup.js:
--------------------------------------------------------------------------------
1 | import Enzyme from 'enzyme';
2 | import Adapter from 'enzyme-adapter-react-16';
3 |
4 | Enzyme.configure({ adapter: new Adapter() });
5 |
--------------------------------------------------------------------------------