├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .gitignore ├── .travis.yml ├── LICENSE.md ├── README.md ├── examples ├── dragColumn.html ├── dragColumn.js ├── dragColumn.less ├── index.less ├── simple.html └── simple.js ├── index.d.ts ├── package.json ├── plugins └── DragDropTouch.js └── src ├── ReactDragColumnView.jsx ├── ReactDragListView.jsx ├── index.js └── util.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | 15 | [Makefile] 16 | indent_style = tab 17 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raisezhang/react-drag-listview/36aec4a875267ff8d59b79dd48aed3c19db4ec23/.eslintignore -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "eslint-config-airbnb", 3 | "rules": { 4 | "no-useless-computed-key": 0, 5 | "strict": 0, 6 | "func-names": 0, 7 | "space-before-function-paren": [0, "always"], 8 | 9 | "no-var": 0, 10 | "vars-on-top": 0, 11 | 12 | "comma-dangle": 0, 13 | "consistent-return": 1, 14 | "no-return-assign": 0, 15 | "no-extend-native": 1, 16 | "import/no-extraneous-dependencies": 0, 17 | "import/no-unresolved": 0, 18 | "import/extensions": 0, 19 | "react/no-array-index-key": 0, 20 | "jsx-a11y/anchor-is-valid": 0, 21 | "react/jsx-filename-extension": 0 22 | }, 23 | "globals": { 24 | "document": true, 25 | "window": true 26 | }, 27 | "parser": "babel-eslint" 28 | } 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | tmp 3 | node_modules 4 | lib 5 | dist 6 | coverage 7 | npm-debug.log* 8 | build 9 | package-lock.json 10 | es/ 11 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - "6" 5 | - "7" 6 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 raisezhang 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 all 13 | 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 THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-drag-listview 2 | 3 | React drag list component. 4 | 5 | [![NPM version][npm-image]][npm-url] 6 | [![build status][travis-image]][travis-url] 7 | [![npm download][download-image]][download-url] 8 | 9 | [npm-image]: http://img.shields.io/npm/v/react-drag-listview.svg?style=flat-square 10 | [npm-url]: http://npmjs.org/package/react-drag-listview 11 | [travis-image]: https://img.shields.io/travis/raisezhang/react-drag-listview.svg?style=flat-square 12 | [travis-url]: https://travis-ci.org/raisezhang/react-drag-listview 13 | [download-image]: https://img.shields.io/npm/dm/react-drag-listview.svg?style=flat-square 14 | [download-url]: https://npmjs.org/package/react-drag-listview 15 | 16 | ## install 17 | 18 | [![rc-table](https://nodei.co/npm/react-drag-listview.png)](https://npmjs.org/package/react-drag-listview) 19 | 20 | 21 | ## Use on mobile (touch) devices 22 | 23 | * `react-drag-listview` already supports mobile (touch) devices, which can be easily implemented based on the [dragdroptouch](https://github.com/Bernardo-Castilho/dragdroptouch) polyfill 24 | * Need to manually add polyfill `dragdroptouch` to your website. e.g. 25 | ```html 26 | 27 | ``` 28 | * Example: [Drag on mobile devices](https://codepen.io/raisezhang/pen/wvpVbQO) 29 | 30 | ## Example 31 | 32 | * ###### Drag Rows 33 | * [Simple dragging demo](https://raisezhang.github.io/react-drag-listview/examples/simple.html) 34 | * [Dragging Ant-Design table](https://codepen.io/raisezhang/pen/MmjypX) 35 | * [Dragging Ant-Design table width expanded rows](https://codepen.io/raisezhang/pen/OrrGJL) 36 | * [Dragging Ant-Design transfer items](https://codepen.io/raisezhang/pen/rNdGEzN) 37 | * [Dragging Ant-Design Nested List Items](https://codesandbox.io/s/react-drag-listview-nested-drag-example-mdrbh?file=/src/questions.js) 38 | 39 | * ###### Drag Columns 40 | * [Simple dragging columns demo](https://raisezhang.github.io/react-drag-listview/examples/dragColumn.html) 41 | * [Dragging Ant-Design table columns](https://codepen.io/raisezhang/pen/MoMoyz) 42 | 43 | ## Development 44 | 45 | ```bash 46 | npm install 47 | npm start 48 | ``` 49 | 50 | ## Usage 51 | 52 | ```javascript 53 | const ReactDragListView = require('react-drag-listview'); 54 | 55 | class Demo extends React.Component { 56 | constructor(props) { 57 | super(props); 58 | 59 | const data = []; 60 | for (let i = 1, len = 7; i < len; i++) { 61 | data.push({ 62 | title: `rows${i}` 63 | }); 64 | } 65 | 66 | this.state = { 67 | data 68 | }; 69 | } 70 | 71 | render() { 72 | const that = this; 73 | const dragProps = { 74 | onDragEnd(fromIndex, toIndex) { 75 | const data = [...that.state.data]; 76 | const item = data.splice(fromIndex, 1)[0]; 77 | data.splice(toIndex, 0, item); 78 | that.setState({ data }); 79 | }, 80 | nodeSelector: 'li', 81 | handleSelector: 'a' 82 | }; 83 | 84 | return ( 85 | 86 |
    87 | {this.state.data.map((item, index) => ( 88 |
  1. 89 | {item.title} 90 | Drag 91 |
  2. 92 | ))} 93 |
94 |
95 | ); 96 | } 97 | } 98 | 99 | ``` 100 | 101 | ## API 102 | 103 | ### Properties 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 |
NameTypeDefaultDescription
onDragEndFunction(fromIndex, toIndex)on drag end callback, required
nodeSelectorStringtrget drag item cssQuery
handleSelectorStringnodeSelectorget drag handle cssQuery
ignoreSelectorStringignore node list
enableScrollBooleantruewhether use auto scroll for dragging
scrollSpeedNumber10scroll speed
lineClassNameStringget dragLine's className, css properties must be use !important
159 | 160 | ## License 161 | 162 | react-drag-listview is released under the MIT license. 163 | -------------------------------------------------------------------------------- /examples/dragColumn.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raisezhang/react-drag-listview/36aec4a875267ff8d59b79dd48aed3c19db4ec23/examples/dragColumn.html -------------------------------------------------------------------------------- /examples/dragColumn.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console,func-names,react/no-multi-comp */ 2 | import React from 'react'; 3 | import ReactDOM from 'react-dom'; 4 | import ReactDragListView from 'react-drag-listview/src/index.js'; 5 | 6 | import './index.less'; 7 | import './dragColumn.less'; 8 | 9 | // Import only if you need to support touch screen devices 10 | // https://bernardo-castilho.github.io/DragDropTouch/DragDropTouch.js 11 | import '../plugins/DragDropTouch.js'; 12 | 13 | const { DragColumn } = ReactDragListView; 14 | 15 | class Demo extends React.Component { 16 | constructor(props) { 17 | super(props); 18 | const data = []; 19 | for (let i = 1, len = 21; i < len; i += 1) { 20 | data.push({ 21 | title: `col${i}` 22 | }); 23 | } 24 | this.state = { 25 | data 26 | }; 27 | } 28 | 29 | render() { 30 | const that = this; 31 | const dragProps = { 32 | onDragEnd(fromIndex, toIndex) { 33 | const data = [...that.state.data]; 34 | const item = data.splice(fromIndex, 1)[0]; 35 | data.splice(toIndex, 0, item); 36 | that.setState({ data }); 37 | }, 38 | nodeSelector: 'li', 39 | handleSelector: 'a' 40 | }; 41 | 42 | return ( 43 |
44 |

Dragging columns

45 |
46 | 47 |
    48 | {this.state.data.map((item, index) => ( 49 |
  1. 50 | {item.title} 51 | Drag 52 |
  2. 53 | ))} 54 |
55 |
56 |
57 |
58 | ); 59 | } 60 | } 61 | 62 | ReactDOM.render(, document.getElementById('__react-content')); 63 | -------------------------------------------------------------------------------- /examples/dragColumn.less: -------------------------------------------------------------------------------- 1 | .simple2 .simple-inner { 2 | height: auto; 3 | } 4 | 5 | .simple2 ol li { 6 | border-right: solid 1px #ddd; 7 | border-bottom: 0 none; 8 | padding: 30px 0; 9 | position: relative; 10 | transition: all ease 0.3s; 11 | float: left; 12 | height: 368px; 13 | box-sizing: border-box; 14 | width: 70px; 15 | text-align: center; 16 | display: block; 17 | } 18 | 19 | .simple2 ol::after { 20 | content: '.'; 21 | display: block; 22 | visibility: hidden; 23 | height: 0; 24 | line-height: 0; 25 | font-size: 0; 26 | clear: both; 27 | } 28 | 29 | .simple2 ol li:nth-last-child(1) { 30 | border-right: 0 none; 31 | } 32 | 33 | .simple2 ol li a { 34 | position: static; 35 | display: block; 36 | margin-top: 20px; 37 | } 38 | -------------------------------------------------------------------------------- /examples/index.less: -------------------------------------------------------------------------------- 1 | .simple { 2 | background: #f5f5f5; 3 | padding: 10px 20px 20px; 4 | border-radius: 5px; 5 | } 6 | 7 | .simple h2 { 8 | margin: 0; 9 | padding: 10px 0; 10 | } 11 | 12 | .simple a, 13 | .simple a:active, 14 | .simple a:hover, 15 | .simple a:visited { 16 | color: #22abed; 17 | } 18 | 19 | ol, 20 | ul { 21 | list-style-type: none; 22 | padding: 0; 23 | margin: 0; 24 | } 25 | 26 | .simple1 ol { 27 | background: #fff; 28 | } 29 | 30 | .simple1 ol li { 31 | border-bottom: solid 1px #ddd; 32 | padding: 8px 16px; 33 | min-height: 60px; 34 | position: relative; 35 | transition: all ease 0.3s; 36 | display: flex; 37 | align-items: center; 38 | } 39 | 40 | .simple1 ol li:hover { 41 | background: rgba(34, 171, 237, 0.1); 42 | } 43 | 44 | .simple1 ol li a { 45 | position: absolute; 46 | right: 20px; 47 | cursor: move; 48 | user-select: none; 49 | padding: 10px; 50 | } 51 | 52 | .simple1 ol li:nth-last-child(1) { 53 | border-bottom: 0 none; 54 | } 55 | 56 | .simple1 .simple-inner { 57 | max-width: 600px; 58 | overflow: hidden; 59 | border: solid 1px #ddd; 60 | border-radius: 5px; 61 | height: 400px; 62 | overflow: auto; 63 | margin: 0 auto; 64 | } 65 | -------------------------------------------------------------------------------- /examples/simple.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raisezhang/react-drag-listview/36aec4a875267ff8d59b79dd48aed3c19db4ec23/examples/simple.html -------------------------------------------------------------------------------- /examples/simple.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console,func-names,react/no-multi-comp */ 2 | import React from 'react'; 3 | import ReactDOM from 'react-dom'; 4 | import ReactDragListView from 'react-drag-listview/src/index.js'; 5 | 6 | import './index.less'; 7 | 8 | // Import only if you need to support touch screen devices 9 | // https://bernardo-castilho.github.io/DragDropTouch/DragDropTouch.js 10 | import '../plugins/DragDropTouch.js'; 11 | 12 | class Demo extends React.Component { 13 | constructor(props) { 14 | super(props); 15 | const data = []; 16 | for (let i = 1, len = 21; i < len; i += 1) { 17 | data.push({ 18 | title: `rows${i}` 19 | }); 20 | } 21 | this.state = { 22 | data 23 | }; 24 | } 25 | 26 | render() { 27 | const that = this; 28 | const dragProps = { 29 | onDragEnd(fromIndex, toIndex) { 30 | const data = [...that.state.data]; 31 | const item = data.splice(fromIndex, 1)[0]; 32 | data.splice(toIndex, 0, item); 33 | that.setState({ data }); 34 | }, 35 | nodeSelector: 'li', 36 | handleSelector: 'a' 37 | }; 38 | 39 | return ( 40 |
41 |

Dragging handle

42 |
43 | 44 |
    45 | {this.state.data.map((item, index) => ( 46 |
  1. 47 | {item.title} 48 | Start Drag 49 |
  2. 50 | ))} 51 |
52 |
53 |
54 |
55 | ); 56 | } 57 | } 58 | 59 | ReactDOM.render(, document.getElementById('__react-content')); 60 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | export interface DragListViewProps { 4 | // on drag end callback, required 5 | onDragEnd: (fromIndex: number, toIndex: number) => void; 6 | // get drag handle cssQuery 7 | handleSelector?: string | undefined; 8 | // get drag item cssQuery 9 | nodeSelector?: string | undefined; 10 | // ignore node list 11 | ignoreSelector?: string | undefined; 12 | // whether use auto scroll for dragging 13 | enableScroll?: boolean | undefined; 14 | // scroll speed 15 | scrollSpeed?: number | undefined; 16 | // get dragLine's className, css properties must be use !important 17 | lineClassName?: string | undefined; 18 | // children 19 | children?: React.ReactNode 20 | } 21 | 22 | declare class ReactDragListView extends React.Component { 23 | } 24 | 25 | declare class ReactDragColumnView extends ReactDragListView { 26 | } 27 | 28 | declare const DragListView: typeof ReactDragListView & { DragColumn: typeof ReactDragColumnView }; 29 | export default DragListView; 30 | 31 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-drag-listview", 3 | "version": "2.0.0", 4 | "author": "raisezhang@hotmail.com", 5 | "description": "drag list view and table view component for react", 6 | "keywords": [ 7 | "react", 8 | "react-component", 9 | "sortable", 10 | "antd", 11 | "rc-table", 12 | "react-drag", 13 | "react-drag-table", 14 | "react-drag-list" 15 | ], 16 | "license": "MIT", 17 | "homepage": "https://github.com/raisezhang/react-drag-listview#readme", 18 | "main": "./lib/index", 19 | "module": "./es/index", 20 | "types": "./index.d.ts", 21 | "files": [ 22 | "assets/*.css", 23 | "dist", 24 | "es", 25 | "lib", 26 | "index.d.ts" 27 | ], 28 | "maintainers": [ 29 | "raisezhang@hotmail.com" 30 | ], 31 | "repository": { 32 | "type": "git", 33 | "url": "git+https://github.com/raisezhang/react-drag-listview.git" 34 | }, 35 | "bugs": { 36 | "url": "https://github.com/raisezhang/react-drag-listview/issues" 37 | }, 38 | "config": { 39 | "entry": { 40 | "react-drag-listview": [ 41 | "./src/index.js" 42 | ] 43 | }, 44 | "port": 8000 45 | }, 46 | "scripts": { 47 | "dist": "rc-tools run dist", 48 | "build": "rc-tools run build", 49 | "compile": "rc-tools run compile --babel-runtime", 50 | "gh-pages": "rc-tools run gh-pages", 51 | "start": "rc-tools run server", 52 | "pub": "rc-tools run pub --babel-runtime", 53 | "lint": "rc-tools run lint", 54 | "lint:fix": "rc-tools run lint --fix" 55 | }, 56 | "dependencies": { 57 | "babel-runtime": "^6.26.0", 58 | "prop-types": "^15.5.8" 59 | }, 60 | "devDependencies": { 61 | "expect.js": "~0.3.1", 62 | "pre-commit": "1.x", 63 | "rc-tools": "^8.2.2", 64 | "react": "^16.0.0", 65 | "react-dom": "^16.0.0" 66 | }, 67 | "pre-commit": [ 68 | "lint" 69 | ] 70 | } 71 | -------------------------------------------------------------------------------- /plugins/DragDropTouch.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | var DragDropTouch; 3 | (function (DragDropTouch_1) { 4 | 'use strict'; 5 | /** 6 | * Object used to hold the data that is being dragged during drag and drop operations. 7 | * 8 | * It may hold one or more data items of different types. For more information about 9 | * drag and drop operations and data transfer objects, see 10 | * HTML Drag and Drop API. 11 | * 12 | * This object is created automatically by the @see:DragDropTouch singleton and is 13 | * accessible through the @see:dataTransfer property of all drag events. 14 | */ 15 | var DataTransfer = (function () { 16 | function DataTransfer() { 17 | this._dropEffect = 'move'; 18 | this._effectAllowed = 'all'; 19 | this._data = {}; 20 | } 21 | Object.defineProperty(DataTransfer.prototype, "dropEffect", { 22 | /** 23 | * Gets or sets the type of drag-and-drop operation currently selected. 24 | * The value must be 'none', 'copy', 'link', or 'move'. 25 | */ 26 | get: function () { 27 | return this._dropEffect; 28 | }, 29 | set: function (value) { 30 | this._dropEffect = value; 31 | }, 32 | enumerable: true, 33 | configurable: true 34 | }); 35 | Object.defineProperty(DataTransfer.prototype, "effectAllowed", { 36 | /** 37 | * Gets or sets the types of operations that are possible. 38 | * Must be one of 'none', 'copy', 'copyLink', 'copyMove', 'link', 39 | * 'linkMove', 'move', 'all' or 'uninitialized'. 40 | */ 41 | get: function () { 42 | return this._effectAllowed; 43 | }, 44 | set: function (value) { 45 | this._effectAllowed = value; 46 | }, 47 | enumerable: true, 48 | configurable: true 49 | }); 50 | Object.defineProperty(DataTransfer.prototype, "types", { 51 | /** 52 | * Gets an array of strings giving the formats that were set in the @see:dragstart event. 53 | */ 54 | get: function () { 55 | return Object.keys(this._data); 56 | }, 57 | enumerable: true, 58 | configurable: true 59 | }); 60 | /** 61 | * Removes the data associated with a given type. 62 | * 63 | * The type argument is optional. If the type is empty or not specified, the data 64 | * associated with all types is removed. If data for the specified type does not exist, 65 | * or the data transfer contains no data, this method will have no effect. 66 | * 67 | * @param type Type of data to remove. 68 | */ 69 | DataTransfer.prototype.clearData = function (type) { 70 | if (type != null) { 71 | delete this._data[type.toLowerCase()]; 72 | } 73 | else { 74 | this._data = {}; 75 | } 76 | }; 77 | /** 78 | * Retrieves the data for a given type, or an empty string if data for that type does 79 | * not exist or the data transfer contains no data. 80 | * 81 | * @param type Type of data to retrieve. 82 | */ 83 | DataTransfer.prototype.getData = function (type) { 84 | return this._data[type.toLowerCase()] || ''; 85 | }; 86 | /** 87 | * Set the data for a given type. 88 | * 89 | * For a list of recommended drag types, please see 90 | * https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Recommended_Drag_Types. 91 | * 92 | * @param type Type of data to add. 93 | * @param value Data to add. 94 | */ 95 | DataTransfer.prototype.setData = function (type, value) { 96 | this._data[type.toLowerCase()] = value; 97 | }; 98 | /** 99 | * Set the image to be used for dragging if a custom one is desired. 100 | * 101 | * @param img An image element to use as the drag feedback image. 102 | * @param offsetX The horizontal offset within the image. 103 | * @param offsetY The vertical offset within the image. 104 | */ 105 | DataTransfer.prototype.setDragImage = function (img, offsetX, offsetY) { 106 | var ddt = DragDropTouch._instance; 107 | ddt._imgCustom = img; 108 | ddt._imgOffset = { x: offsetX, y: offsetY }; 109 | }; 110 | return DataTransfer; 111 | }()); 112 | DragDropTouch_1.DataTransfer = DataTransfer; 113 | /** 114 | * Defines a class that adds support for touch-based HTML5 drag/drop operations. 115 | * 116 | * The @see:DragDropTouch class listens to touch events and raises the 117 | * appropriate HTML5 drag/drop events as if the events had been caused 118 | * by mouse actions. 119 | * 120 | * The purpose of this class is to enable using existing, standard HTML5 121 | * drag/drop code on mobile devices running IOS or Android. 122 | * 123 | * To use, include the DragDropTouch.js file on the page. The class will 124 | * automatically start monitoring touch events and will raise the HTML5 125 | * drag drop events (dragstart, dragenter, dragleave, drop, dragend) which 126 | * should be handled by the application. 127 | * 128 | * For details and examples on HTML drag and drop, see 129 | * https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Drag_operations. 130 | */ 131 | var DragDropTouch = (function () { 132 | /** 133 | * Initializes the single instance of the @see:DragDropTouch class. 134 | */ 135 | function DragDropTouch() { 136 | this._lastClick = 0; 137 | // enforce singleton pattern 138 | if (DragDropTouch._instance) { 139 | throw 'DragDropTouch instance already created.'; 140 | } 141 | // detect passive event support 142 | // https://github.com/Modernizr/Modernizr/issues/1894 143 | var supportsPassive = false; 144 | document.addEventListener('test', function () { }, { 145 | get passive() { 146 | supportsPassive = true; 147 | return true; 148 | } 149 | }); 150 | // listen to touch events 151 | if (navigator.maxTouchPoints) { 152 | var d = document, 153 | ts = this._touchstart.bind(this), 154 | tm = this._touchmove.bind(this), 155 | te = this._touchend.bind(this), 156 | opt = supportsPassive ? { passive: false, capture: false } : false; 157 | d.addEventListener('touchstart', ts, opt); 158 | d.addEventListener('touchmove', tm, opt); 159 | d.addEventListener('touchend', te); 160 | d.addEventListener('touchcancel', te); 161 | } 162 | } 163 | /** 164 | * Gets a reference to the @see:DragDropTouch singleton. 165 | */ 166 | DragDropTouch.getInstance = function () { 167 | return DragDropTouch._instance; 168 | }; 169 | // ** event handlers 170 | DragDropTouch.prototype._touchstart = function (e) { 171 | var _this = this; 172 | if (this._shouldHandle(e)) { 173 | // raise double-click and prevent zooming 174 | if (Date.now() - this._lastClick < DragDropTouch._DBLCLICK) { 175 | if (this._dispatchEvent(e, 'dblclick', e.target)) { 176 | e.preventDefault(); 177 | this._reset(); 178 | return; 179 | } 180 | } 181 | // clear all variables 182 | this._reset(); 183 | // get nearest draggable element 184 | var src = this._closestDraggable(e.target); 185 | if (src) { 186 | // give caller a chance to handle the hover/move events 187 | if (!this._dispatchEvent(e, 'mousemove', e.target) && 188 | !this._dispatchEvent(e, 'mousedown', e.target)) { 189 | // get ready to start dragging 190 | this._dragSource = src; 191 | this._ptDown = this._getPoint(e); 192 | this._lastTouch = e; 193 | e.preventDefault(); 194 | // show context menu if the user hasn't started dragging after a while 195 | setTimeout(function () { 196 | if (_this._dragSource == src && _this._img == null) { 197 | if (_this._dispatchEvent(e, 'contextmenu', src)) { 198 | _this._reset(); 199 | } 200 | } 201 | }, DragDropTouch._CTXMENU); 202 | if (DragDropTouch._ISPRESSHOLDMODE) { 203 | this._pressHoldInterval = setTimeout(function () { 204 | _this._isDragEnabled = true; 205 | _this._touchmove(e); 206 | }, DragDropTouch._PRESSHOLDAWAIT); 207 | } 208 | } 209 | } 210 | } 211 | }; 212 | DragDropTouch.prototype._touchmove = function (e) { 213 | if (this._shouldCancelPressHoldMove(e)) { 214 | this._reset(); 215 | return; 216 | } 217 | if (this._shouldHandleMove(e) || this._shouldHandlePressHoldMove(e)) { 218 | // see if target wants to handle move 219 | var target = this._getTarget(e); 220 | if (this._dispatchEvent(e, 'mousemove', target)) { 221 | this._lastTouch = e; 222 | e.preventDefault(); 223 | return; 224 | } 225 | // start dragging 226 | if (this._dragSource && !this._img && this._shouldStartDragging(e)) { 227 | this._dispatchEvent(e, 'dragstart', this._dragSource); 228 | this._createImage(e); 229 | this._dispatchEvent(e, 'dragenter', target); 230 | } 231 | // continue dragging 232 | if (this._img) { 233 | this._lastTouch = e; 234 | e.preventDefault(); // prevent scrolling 235 | this._dispatchEvent(e, 'drag', this._dragSource); 236 | if (target != this._lastTarget) { 237 | this._dispatchEvent(this._lastTouch, 'dragleave', this._lastTarget); 238 | this._dispatchEvent(e, 'dragenter', target); 239 | this._lastTarget = target; 240 | } 241 | this._moveImage(e); 242 | this._isDropZone = this._dispatchEvent(e, 'dragover', target); 243 | } 244 | } 245 | }; 246 | DragDropTouch.prototype._touchend = function (e) { 247 | if (this._shouldHandle(e)) { 248 | // see if target wants to handle up 249 | if (this._dispatchEvent(this._lastTouch, 'mouseup', e.target)) { 250 | e.preventDefault(); 251 | return; 252 | } 253 | // user clicked the element but didn't drag, so clear the source and simulate a click 254 | if (!this._img) { 255 | this._dragSource = null; 256 | this._dispatchEvent(this._lastTouch, 'click', e.target); 257 | this._lastClick = Date.now(); 258 | } 259 | // finish dragging 260 | this._destroyImage(); 261 | if (this._dragSource) { 262 | if (e.type.indexOf('cancel') < 0 && this._isDropZone) { 263 | this._dispatchEvent(this._lastTouch, 'drop', this._lastTarget); 264 | } 265 | this._dispatchEvent(this._lastTouch, 'dragend', this._dragSource); 266 | this._reset(); 267 | } 268 | } 269 | }; 270 | // ** utilities 271 | // ignore events that have been handled or that involve more than one touch 272 | DragDropTouch.prototype._shouldHandle = function (e) { 273 | return e && 274 | !e.defaultPrevented && 275 | e.touches && e.touches.length < 2; 276 | }; 277 | 278 | // use regular condition outside of press & hold mode 279 | DragDropTouch.prototype._shouldHandleMove = function (e) { 280 | return !DragDropTouch._ISPRESSHOLDMODE && this._shouldHandle(e); 281 | }; 282 | 283 | // allow to handle moves that involve many touches for press & hold 284 | DragDropTouch.prototype._shouldHandlePressHoldMove = function (e) { 285 | return DragDropTouch._ISPRESSHOLDMODE && 286 | this._isDragEnabled && e && e.touches && e.touches.length; 287 | }; 288 | 289 | // reset data if user drags without pressing & holding 290 | DragDropTouch.prototype._shouldCancelPressHoldMove = function (e) { 291 | return DragDropTouch._ISPRESSHOLDMODE && !this._isDragEnabled && 292 | this._getDelta(e) > DragDropTouch._PRESSHOLDMARGIN; 293 | }; 294 | 295 | // start dragging when specified delta is detected 296 | DragDropTouch.prototype._shouldStartDragging = function (e) { 297 | var delta = this._getDelta(e); 298 | return delta > DragDropTouch._THRESHOLD || 299 | (DragDropTouch._ISPRESSHOLDMODE && delta >= DragDropTouch._PRESSHOLDTHRESHOLD); 300 | } 301 | 302 | // clear all members 303 | DragDropTouch.prototype._reset = function () { 304 | this._destroyImage(); 305 | this._dragSource = null; 306 | this._lastTouch = null; 307 | this._lastTarget = null; 308 | this._ptDown = null; 309 | this._isDragEnabled = false; 310 | this._isDropZone = false; 311 | this._dataTransfer = new DataTransfer(); 312 | clearInterval(this._pressHoldInterval); 313 | }; 314 | // get point for a touch event 315 | DragDropTouch.prototype._getPoint = function (e, page) { 316 | if (e && e.touches) { 317 | e = e.touches[0]; 318 | } 319 | return { x: page ? e.pageX : e.clientX, y: page ? e.pageY : e.clientY }; 320 | }; 321 | // get distance between the current touch event and the first one 322 | DragDropTouch.prototype._getDelta = function (e) { 323 | if (DragDropTouch._ISPRESSHOLDMODE && !this._ptDown) { return 0; } 324 | var p = this._getPoint(e); 325 | return Math.abs(p.x - this._ptDown.x) + Math.abs(p.y - this._ptDown.y); 326 | }; 327 | // get the element at a given touch event 328 | DragDropTouch.prototype._getTarget = function (e) { 329 | var pt = this._getPoint(e), el = document.elementFromPoint(pt.x, pt.y); 330 | while (el && getComputedStyle(el).pointerEvents == 'none') { 331 | el = el.parentElement; 332 | } 333 | return el; 334 | }; 335 | // create drag image from source element 336 | DragDropTouch.prototype._createImage = function (e) { 337 | // just in case... 338 | if (this._img) { 339 | this._destroyImage(); 340 | } 341 | // create drag image from custom element or drag source 342 | var src = this._imgCustom || this._dragSource; 343 | this._img = src.cloneNode(true); 344 | this._copyStyle(src, this._img); 345 | this._img.style.top = this._img.style.left = '-9999px'; 346 | // if creating from drag source, apply offset and opacity 347 | if (!this._imgCustom) { 348 | var rc = src.getBoundingClientRect(), pt = this._getPoint(e); 349 | this._imgOffset = { x: pt.x - rc.left, y: pt.y - rc.top }; 350 | this._img.style.opacity = DragDropTouch._OPACITY.toString(); 351 | } 352 | // add image to document 353 | this._moveImage(e); 354 | document.body.appendChild(this._img); 355 | }; 356 | // dispose of drag image element 357 | DragDropTouch.prototype._destroyImage = function () { 358 | if (this._img && this._img.parentElement) { 359 | this._img.parentElement.removeChild(this._img); 360 | } 361 | this._img = null; 362 | this._imgCustom = null; 363 | }; 364 | // move the drag image element 365 | DragDropTouch.prototype._moveImage = function (e) { 366 | var _this = this; 367 | requestAnimationFrame(function () { 368 | if (_this._img) { 369 | var pt = _this._getPoint(e, true), s = _this._img.style; 370 | s.position = 'absolute'; 371 | s.pointerEvents = 'none'; 372 | s.zIndex = '999999'; 373 | s.left = Math.round(pt.x - _this._imgOffset.x) + 'px'; 374 | s.top = Math.round(pt.y - _this._imgOffset.y) + 'px'; 375 | } 376 | }); 377 | }; 378 | // copy properties from an object to another 379 | DragDropTouch.prototype._copyProps = function (dst, src, props) { 380 | for (var i = 0; i < props.length; i++) { 381 | var p = props[i]; 382 | dst[p] = src[p]; 383 | } 384 | }; 385 | DragDropTouch.prototype._copyStyle = function (src, dst) { 386 | // remove potentially troublesome attributes 387 | DragDropTouch._rmvAtts.forEach(function (att) { 388 | dst.removeAttribute(att); 389 | }); 390 | // copy canvas content 391 | if (src instanceof HTMLCanvasElement) { 392 | var cSrc = src, cDst = dst; 393 | cDst.width = cSrc.width; 394 | cDst.height = cSrc.height; 395 | cDst.getContext('2d').drawImage(cSrc, 0, 0); 396 | } 397 | // copy style (without transitions) 398 | var cs = getComputedStyle(src); 399 | for (var i = 0; i < cs.length; i++) { 400 | var key = cs[i]; 401 | if (key.indexOf('transition') < 0) { 402 | dst.style[key] = cs[key]; 403 | } 404 | } 405 | dst.style.pointerEvents = 'none'; 406 | // and repeat for all children 407 | for (var i = 0; i < src.children.length; i++) { 408 | this._copyStyle(src.children[i], dst.children[i]); 409 | } 410 | }; 411 | DragDropTouch.prototype._dispatchEvent = function (e, type, target) { 412 | if (e && target) { 413 | var evt = document.createEvent('Event'), t = e.touches ? e.touches[0] : e; 414 | evt.initEvent(type, true, true); 415 | evt.button = 0; 416 | evt.which = evt.buttons = 1; 417 | this._copyProps(evt, e, DragDropTouch._kbdProps); 418 | this._copyProps(evt, t, DragDropTouch._ptProps); 419 | evt.dataTransfer = this._dataTransfer; 420 | target.dispatchEvent(evt); 421 | return evt.defaultPrevented; 422 | } 423 | return false; 424 | }; 425 | // gets an element's closest draggable ancestor 426 | DragDropTouch.prototype._closestDraggable = function (e) { 427 | for (; e; e = e.parentElement) { 428 | if (e.hasAttribute('draggable') && e.draggable) { 429 | return e; 430 | } 431 | } 432 | return null; 433 | }; 434 | return DragDropTouch; 435 | }()); 436 | /*private*/ DragDropTouch._instance = new DragDropTouch(); // singleton 437 | // constants 438 | DragDropTouch._THRESHOLD = 5; // pixels to move before drag starts 439 | DragDropTouch._OPACITY = 0.5; // drag image opacity 440 | DragDropTouch._DBLCLICK = 500; // max ms between clicks in a double click 441 | DragDropTouch._CTXMENU = 900; // ms to hold before raising 'contextmenu' event 442 | DragDropTouch._ISPRESSHOLDMODE = false; // decides of press & hold mode presence 443 | DragDropTouch._PRESSHOLDAWAIT = 400; // ms to wait before press & hold is detected 444 | DragDropTouch._PRESSHOLDMARGIN = 25; // pixels that finger might shiver while pressing 445 | DragDropTouch._PRESSHOLDTHRESHOLD = 0; // pixels to move before drag starts 446 | // copy styles/attributes from drag source to drag image element 447 | DragDropTouch._rmvAtts = 'id,class,style,draggable'.split(','); 448 | // synthesize and dispatch an event 449 | // returns true if the event has been handled (e.preventDefault == true) 450 | DragDropTouch._kbdProps = 'altKey,ctrlKey,metaKey,shiftKey'.split(','); 451 | DragDropTouch._ptProps = 'pageX,pageY,clientX,clientY,screenX,screenY,offsetX,offsetY'.split(','); 452 | DragDropTouch_1.DragDropTouch = DragDropTouch; 453 | })(DragDropTouch || (DragDropTouch = {})); 454 | -------------------------------------------------------------------------------- /src/ReactDragColumnView.jsx: -------------------------------------------------------------------------------- 1 | import ReactDragListView from './ReactDragListView'; 2 | 3 | const UNIT_PX = 'px'; 4 | const DRAG_LIND_STYLE = 'width:0;margin-left:-1px;margin-top:0;' + 5 | 'border-bottom:0 none;border-left:dashed 2px red;'; 6 | const DIRECTIONS = { 7 | RIGHT: 2, 8 | LEFT: 4 9 | }; 10 | 11 | class ReactDragColumnView extends ReactDragListView { 12 | getDragLine() { 13 | if (!this.dragLine) { 14 | super.getDragLine(); 15 | this.dragLine.setAttribute('style', this.dragLine.getAttribute('style') + DRAG_LIND_STYLE); 16 | } 17 | return this.dragLine; 18 | } 19 | 20 | resolveAutoScroll(e, target) { 21 | if (!this.scrollElement) { 22 | return; 23 | } 24 | const { left, width } = this.scrollElement.getBoundingClientRect(); 25 | const targetWidth = target.offsetWidth; 26 | const { pageX } = e; 27 | const compatibleWidth = (targetWidth * 2) / 3; 28 | this.direction = 0; 29 | if (pageX > ((left + width) - compatibleWidth)) { 30 | this.direction = DIRECTIONS.RIGHT; 31 | } else if (pageX < (left + compatibleWidth)) { 32 | this.direction = DIRECTIONS.LEFT; 33 | } 34 | if (this.direction) { 35 | if (this.scrollTimerId < 0) { 36 | this.scrollTimerId = setInterval(this.autoScroll, 20); 37 | } 38 | } else { 39 | this.stopAutoScroll(); 40 | } 41 | } 42 | 43 | autoScroll() { 44 | const { scrollLeft } = this.scrollElement; 45 | if (this.direction === DIRECTIONS.RIGHT) { 46 | this.scrollElement.scrollLeft = scrollLeft + this.props.scrollSpeed; 47 | if (scrollLeft === this.scrollElement.scrollLeft) { 48 | this.stopAutoScroll(); 49 | } 50 | } else if (this.direction === DIRECTIONS.LEFT) { 51 | this.scrollElement.scrollLeft = scrollLeft - this.props.scrollSpeed; 52 | if (this.scrollElement.scrollLeft <= 0) { 53 | this.stopAutoScroll(); 54 | } 55 | } else { 56 | this.stopAutoScroll(); 57 | } 58 | } 59 | 60 | fixDragLine(target) { 61 | const dragLine = this.getDragLine(); 62 | if (!target || this.state.fromIndex < 0 63 | || this.state.fromIndex === this.state.toIndex) { 64 | this.hideDragLine(); 65 | return; 66 | } 67 | const { 68 | left, top, width, height 69 | } = target.getBoundingClientRect(); 70 | const lineLeft = (this.state.toIndex < this.state.fromIndex 71 | ? left 72 | : (left + width)); 73 | if (this.props.enableScroll && this.scrollElement) { 74 | const { 75 | width: scrollWidth, 76 | left: scrollLeft 77 | } = this.scrollElement.getBoundingClientRect(); 78 | if (lineLeft < (scrollLeft - 2) || lineLeft > (scrollLeft + scrollWidth + 2)) { 79 | this.hideDragLine(); 80 | return; 81 | } 82 | } 83 | dragLine.style.top = top + UNIT_PX; 84 | dragLine.style.height = height + UNIT_PX; 85 | dragLine.style.left = lineLeft + UNIT_PX; 86 | dragLine.style.display = 'block'; 87 | } 88 | } 89 | 90 | export default ReactDragColumnView; 91 | -------------------------------------------------------------------------------- /src/ReactDragListView.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { closest, getDomIndex, getScrollElement } from './util'; 4 | 5 | const DEFAULT_NODE_SELECTOR = 'tr'; 6 | const DIRECTIONS = { 7 | TOP: 1, 8 | BOTTOM: 3 9 | }; 10 | const UNIT_PX = 'px'; 11 | const DRAG_LIND_STYLE = 'position:fixed;z-index:9999;height:0;' + 12 | 'margin-top:-1px;border-bottom:dashed 2px red;display:none;'; 13 | 14 | class ReactDragListView extends Component { 15 | static propTypes = { 16 | onDragEnd: PropTypes.func.isRequired, 17 | handleSelector: PropTypes.string, 18 | nodeSelector: PropTypes.string, 19 | ignoreSelector: PropTypes.string, 20 | enableScroll: PropTypes.bool, 21 | scrollSpeed: PropTypes.number, 22 | lineClassName: PropTypes.string, 23 | children: PropTypes.node 24 | } 25 | 26 | static defaultProps = { 27 | nodeSelector: DEFAULT_NODE_SELECTOR, 28 | ignoreSelector: '', 29 | enableScroll: true, 30 | scrollSpeed: 10, 31 | handleSelector: '', 32 | lineClassName: '', 33 | children: null 34 | } 35 | 36 | constructor(props) { 37 | super(props); 38 | this.onMouseDown = this.onMouseDown.bind(this); 39 | this.onDragStart = this.onDragStart.bind(this); 40 | this.onDragEnter = this.onDragEnter.bind(this); 41 | this.onDragEnd = this.onDragEnd.bind(this); 42 | this.autoScroll = this.autoScroll.bind(this); 43 | 44 | this.state = { 45 | fromIndex: -1, 46 | toIndex: -1 47 | }; 48 | 49 | this.scrollElement = null; 50 | this.scrollTimerId = -1; 51 | this.direction = DIRECTIONS.BOTTOM; 52 | } 53 | 54 | componentWillUnmount() { 55 | if (this.dragLine && this.dragLine.parentNode) { 56 | this.dragLine.parentNode.removeChild(this.dragLine); 57 | this.dragLine = null; 58 | this.cacheDragTarget = null; 59 | } 60 | } 61 | 62 | onMouseDown(e) { 63 | this.startDrag(e); 64 | } 65 | 66 | onDragStart(e) { 67 | const target = this.getDragNode(e.target); 68 | const eventData = e; 69 | if (target) { 70 | const { parentNode } = target; 71 | eventData.dataTransfer.setData('Text', ''); 72 | eventData.dataTransfer.effectAllowed = 'move'; 73 | parentNode.ondragenter = this.onDragEnter; 74 | parentNode.ondragover = function(ev) { 75 | ev.preventDefault(); 76 | return true; 77 | }; 78 | const fromIndex = getDomIndex(target, this.props.ignoreSelector); 79 | this.setState({ fromIndex, toIndex: fromIndex }); 80 | this.scrollElement = getScrollElement(parentNode); 81 | } 82 | } 83 | 84 | onDragEnter(e) { 85 | const target = this.getDragNode(e.target); 86 | const eventData = e; 87 | let toIndex; 88 | if (target) { 89 | toIndex = getDomIndex(target, this.props.ignoreSelector); 90 | if (this.props.enableScroll) { 91 | this.resolveAutoScroll(eventData, target); 92 | } 93 | } else { 94 | toIndex = -1; 95 | this.stopAutoScroll(); 96 | } 97 | this.cacheDragTarget = target; 98 | this.setState({ toIndex }); 99 | this.fixDragLine(target); 100 | } 101 | 102 | onDragEnd(e) { 103 | const target = this.getDragNode(e.target); 104 | this.stopAutoScroll(); 105 | if (target) { 106 | target.removeAttribute('draggable'); 107 | target.ondragstart = null; 108 | target.ondragend = null; 109 | target.parentNode.ondragenter = null; 110 | target.parentNode.ondragover = null; 111 | if (this.state.fromIndex >= 0 && this.state.fromIndex !== this.state.toIndex) { 112 | this.props.onDragEnd(this.state.fromIndex, this.state.toIndex); 113 | } 114 | } 115 | this.hideDragLine(); 116 | this.setState({ fromIndex: -1, toIndex: -1 }); 117 | } 118 | 119 | getDragNode(target) { 120 | return closest(target, this.props.nodeSelector, this.dragList); 121 | } 122 | 123 | getHandleNode(target) { 124 | return closest( 125 | target, 126 | this.props.handleSelector || this.props.nodeSelector, 127 | this.dragList 128 | ); 129 | } 130 | 131 | getDragLine() { 132 | if (!this.dragLine) { 133 | this.dragLine = window.document.createElement('div'); 134 | this.dragLine.setAttribute('style', DRAG_LIND_STYLE); 135 | window.document.body.appendChild(this.dragLine); 136 | } 137 | this.dragLine.className = this.props.lineClassName || ''; 138 | return this.dragLine; 139 | } 140 | 141 | startDrag(e) { 142 | const handle = this.getHandleNode(e.target); 143 | if (handle) { 144 | const target = (!this.props.handleSelector || this.props.handleSelector 145 | === this.props.nodeSelector) 146 | ? handle 147 | : this.getDragNode(handle); 148 | if (target) { 149 | handle.setAttribute('draggable', false); 150 | target.setAttribute('draggable', true); 151 | target.ondragstart = this.onDragStart; 152 | target.ondragend = this.onDragEnd; 153 | } 154 | } 155 | } 156 | 157 | resolveAutoScroll(e, target) { 158 | if (!this.scrollElement) { 159 | return; 160 | } 161 | const { top, height } = this.scrollElement.getBoundingClientRect(); 162 | const targetHeight = target.offsetHeight; 163 | const { pageY } = e; 164 | const compatibleHeight = targetHeight * (2 / 3); 165 | this.direction = 0; 166 | if (pageY > ((top + height) - compatibleHeight)) { 167 | this.direction = DIRECTIONS.BOTTOM; 168 | } else if (pageY < (top + compatibleHeight)) { 169 | this.direction = DIRECTIONS.TOP; 170 | } 171 | if (this.direction) { 172 | if (this.scrollTimerId < 0) { 173 | this.scrollTimerId = setInterval(this.autoScroll, 20); 174 | } 175 | } else { 176 | this.stopAutoScroll(); 177 | } 178 | } 179 | 180 | stopAutoScroll() { 181 | clearInterval(this.scrollTimerId); 182 | this.scrollTimerId = -1; 183 | this.fixDragLine(this.cacheDragTarget); 184 | } 185 | 186 | autoScroll() { 187 | const { scrollTop } = this.scrollElement; 188 | if (this.direction === DIRECTIONS.BOTTOM) { 189 | this.scrollElement.scrollTop = scrollTop + this.props.scrollSpeed; 190 | if (scrollTop === this.scrollElement.scrollTop) { 191 | this.stopAutoScroll(); 192 | } 193 | } else if (this.direction === DIRECTIONS.TOP) { 194 | this.scrollElement.scrollTop = scrollTop - this.props.scrollSpeed; 195 | if (this.scrollElement.scrollTop <= 0) { 196 | this.stopAutoScroll(); 197 | } 198 | } else { 199 | this.stopAutoScroll(); 200 | } 201 | } 202 | 203 | hideDragLine() { 204 | if (this.dragLine) { 205 | this.dragLine.style.display = 'none'; 206 | } 207 | } 208 | 209 | fixDragLine(target) { 210 | const dragLine = this.getDragLine(); 211 | if (!target || this.state.fromIndex < 0 212 | || this.state.fromIndex === this.state.toIndex) { 213 | this.hideDragLine(); 214 | return; 215 | } 216 | const { 217 | left, top, width, height 218 | } = target.getBoundingClientRect(); 219 | const lineTop = (this.state.toIndex < this.state.fromIndex 220 | ? top 221 | : (top + height)); 222 | if (this.props.enableScroll && this.scrollElement) { 223 | const { 224 | height: scrollHeight, 225 | top: scrollTop 226 | } = this.scrollElement.getBoundingClientRect(); 227 | if (lineTop < (scrollTop - 2) || lineTop > (scrollTop + scrollHeight + 2)) { 228 | this.hideDragLine(); 229 | return; 230 | } 231 | } 232 | dragLine.style.left = left + UNIT_PX; 233 | dragLine.style.width = width + UNIT_PX; 234 | dragLine.style.top = lineTop + UNIT_PX; 235 | dragLine.style.display = 'block'; 236 | } 237 | 238 | render() { 239 | return ( 240 | // eslint-disable-next-line react/no-unknown-property 241 |
{ this.dragList = c; }}> 242 | {this.props.children} 243 |
244 | ); 245 | } 246 | } 247 | 248 | export default ReactDragListView; 249 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import ReactDragListView from './ReactDragListView'; 2 | import ReactDragColumnView from './ReactDragColumnView'; 3 | 4 | ReactDragListView.DragColumn = ReactDragColumnView; 5 | export default ReactDragListView; 6 | 7 | // build node version: 11.15.0 8 | -------------------------------------------------------------------------------- /src/util.js: -------------------------------------------------------------------------------- 1 | /* global Element */ 2 | 3 | if (typeof Element !== 'undefined' && !Element.prototype.matches) { 4 | var proto = Element.prototype; 5 | proto.matches = proto.matchesSelector || 6 | proto.mozMatchesSelector || proto.msMatchesSelector || 7 | proto.oMatchesSelector || proto.webkitMatchesSelector; 8 | } 9 | 10 | const closest = function(el, selector, rootNode) { 11 | let element = el; 12 | while (element) { 13 | const isRoot = element === rootNode || element === document.body; 14 | if (isRoot || (element.nodeType === 1 && element.matches(selector))) { 15 | if (isRoot) { 16 | element = null; 17 | } 18 | break; 19 | } 20 | element = element.parentNode; 21 | } 22 | return element; 23 | }; 24 | 25 | const getScrollElement = function(el) { 26 | let element = el; 27 | do { 28 | const { overflow } = window.getComputedStyle(element); 29 | if ((overflow === 'auto' || overflow === 'scroll') 30 | && (element && element.nodeType 31 | && (element.offsetWidth < element.scrollWidth 32 | || element.offsetHeight < element.scrollHeight))) { 33 | break; 34 | } 35 | if (!element || !element.nodeType || element === document.body) { 36 | element = null; 37 | break; 38 | } 39 | element = element.parentNode; 40 | } while (element); 41 | return element; 42 | }; 43 | 44 | const getDomIndex = function(el, ignoreSelectors) { 45 | return Array.from(el.parentNode.children) 46 | .filter(e => (ignoreSelectors === '' ? true : !e.matches(ignoreSelectors))) 47 | .indexOf(el); 48 | }; 49 | 50 | // const isTouchScreen = function() { 51 | // return window.navigator.maxTouchPoints > 0; 52 | // }; 53 | 54 | export { getScrollElement, closest, getDomIndex }; 55 | --------------------------------------------------------------------------------