├── .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 | --------------------------------------------------------------------------------