├── .gitignore ├── grab.png ├── ss-01.png ├── ss-02.png ├── grabbing.png ├── .eslintignore ├── .eslintrc.json ├── src ├── .eslintrc.json └── plain-draggable-limit.proc.js ├── test ├── spec │ ├── .eslintrc.json │ ├── common.html │ ├── cursor.html │ ├── common-window.html │ ├── element.html │ ├── funcs.html │ ├── bbox.html │ ├── element.js │ ├── bbox.js │ ├── autoScroll.js │ ├── bbox-lefttop.js │ ├── funcs.js │ └── snap.js ├── snap-view.css ├── index-limit.html ├── index.html ├── httpd.js └── snap-view.js ├── bower.json ├── LICENSE ├── package.json ├── webpack.config.js ├── plain-draggable-limit.min.js └── plain-draggable.min.js /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | node_modules 3 | -------------------------------------------------------------------------------- /grab.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anseki/plain-draggable/HEAD/grab.png -------------------------------------------------------------------------------- /ss-01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anseki/plain-draggable/HEAD/ss-01.png -------------------------------------------------------------------------------- /ss-02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anseki/plain-draggable/HEAD/ss-02.png -------------------------------------------------------------------------------- /grabbing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anseki/plain-draggable/HEAD/grabbing.png -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | *.min.js 2 | *.esm.js 3 | test/plain-draggable-limit.js 4 | test/plain-draggable.js 5 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "extends": "../../_common/files/eslintrc.json" 4 | } 5 | -------------------------------------------------------------------------------- /src/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": {"browser": true, "es6": true}, 3 | "parserOptions": {"sourceType": "module"}, 4 | "rules": { 5 | "no-underscore-dangle": [2, {"allow": ["_id"]}] 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /test/spec/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": {"browser": true, "jasmine": true}, 3 | "globals": {"loadPage": false}, 4 | "rules": { 5 | "no-var": "off", 6 | "prefer-arrow-callback": "off", 7 | "object-shorthand": "off", 8 | "no-underscore-dangle": [2, {"allow": ["_id"]}] 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /test/snap-view.css: -------------------------------------------------------------------------------- 1 | 2 | .snap-view-point { 3 | position: absolute; 4 | background-color: rgba(242, 9, 108, 0.4); 5 | z-index: 1; 6 | } 7 | 8 | .snap-view-point > div { 9 | position: absolute; 10 | right: 100%; 11 | bottom: 100%; 12 | background-color: rgba(24, 98, 208, 0.4); 13 | } 14 | 15 | .snap-view-line { /* horizontal */ 16 | position: absolute; 17 | background-color: rgba(242, 9, 108, 0.4); 18 | z-index: 1; 19 | } 20 | 21 | .snap-view-line > div { 22 | position: absolute; 23 | left: 0; 24 | bottom: 100%; 25 | background-color: rgba(24, 98, 208, 0.4); 26 | width: 100%; 27 | } 28 | 29 | .snap-view-line.snap-view-v > div { /* vertical */ 30 | left: auto; 31 | right: 100%; 32 | bottom: auto; 33 | top: 0; 34 | height: 100%; 35 | } 36 | -------------------------------------------------------------------------------- /test/spec/common.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 |
32 |
33 |
handle
34 | elm1 35 |
36 |
37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /test/spec/cursor.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 11 | 12 | 13 | 14 | 15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | 24 |
25 |
26 |
27 |
28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /test/spec/common-window.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 |
32 |
33 |
handle
34 | elm1 35 |
36 |
37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "plain-draggable", 3 | "version": "2.5.15", 4 | "description": "The simple and high performance library to allow HTML/SVG element to be dragged.", 5 | "keywords": [ 6 | "draggable", 7 | "drag", 8 | "html", 9 | "dom", 10 | "svg", 11 | "grid", 12 | "snaps", 13 | "performance", 14 | "ui", 15 | "requestanimationframe " 16 | ], 17 | "main": "plain-draggable.min.js", 18 | "homepage": "https://anseki.github.io/plain-draggable/", 19 | "repository": { 20 | "type": "git", 21 | "url": "git://github.com/anseki/plain-draggable.git" 22 | }, 23 | "moduleType": [], 24 | "ignore": [ 25 | "**/.*", 26 | "node_modules", 27 | "bower_components", 28 | "test", 29 | "tests" 30 | ], 31 | "license": "MIT", 32 | "authors": [ 33 | "anseki " 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /test/spec/element.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 15 | 16 | 17 | 18 | 19 |
elm1
20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 |
elm-shadow-by-id
32 |
elm-shadow-by-style
33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /test/spec/funcs.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 33 | 34 | 35 |
elm1
36 |
elm2
37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 anseki 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. -------------------------------------------------------------------------------- /test/index-limit.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Jasmine Spec Runner 6 | 7 | 8 | 9 | 10 | 11 | 16 | 17 | 18 | 19 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /test/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Jasmine Spec Runner 6 | 7 | 8 | 9 | 10 | 11 | 16 | 17 | 18 | 19 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "plain-draggable", 3 | "version": "2.5.15", 4 | "title": "PlainDraggable", 5 | "description": "The simple and high performance library to allow HTML/SVG element to be dragged.", 6 | "keywords": [ 7 | "draggable", 8 | "drag", 9 | "html", 10 | "dom", 11 | "svg", 12 | "grid", 13 | "snaps", 14 | "performance", 15 | "ui", 16 | "requestanimationframe " 17 | ], 18 | "main": "plain-draggable.min.js", 19 | "module": "plain-draggable.esm.js", 20 | "jsnext:main": "plain-draggable.esm.js", 21 | "files": [ 22 | "plain-draggable?(-limit)?(-debug).@(min.js|esm.js)", 23 | "bower.json" 24 | ], 25 | "devDependencies": { 26 | "@babel/core": "^7.14.3", 27 | "@babel/preset-env": "^7.14.2", 28 | "anim-event": "^1.0.17", 29 | "babel-core": "^7.0.0-bridge.0", 30 | "babel-loader": "^7.1.5", 31 | "cross-env": "^7.0.3", 32 | "cssprefix": "^2.0.17", 33 | "jasmine-core": "^3.7.1", 34 | "log4js": "^6.4.0", 35 | "m-class-list": "^1.1.10", 36 | "node-static-alias": "^1.1.2", 37 | "pointer-event": "^1.0.2", 38 | "pre-proc": "^1.0.2", 39 | "skeleton-loader": "^2.0.0", 40 | "stats-filelist": "^1.0.1", 41 | "test-page-loader": "^1.0.8", 42 | "webpack": "^4.46.0", 43 | "webpack-cli": "^3.3.12" 44 | }, 45 | "scripts": { 46 | "build": "cross-env NODE_ENV=production webpack --verbose", 47 | "dev": "webpack --verbose", 48 | "build-limit": "cross-env EDITION=limit NODE_ENV=production webpack --verbose", 49 | "dev-limit": "cross-env EDITION=limit webpack --verbose", 50 | "test": "node ./test/httpd" 51 | }, 52 | "homepage": "https://anseki.github.io/plain-draggable/", 53 | "repository": { 54 | "type": "git", 55 | "url": "git://github.com/anseki/plain-draggable.git" 56 | }, 57 | "bugs": "https://github.com/anseki/plain-draggable/issues", 58 | "license": "MIT", 59 | "author": { 60 | "name": "anseki", 61 | "url": "https://github.com/anseki" 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /test/spec/bbox.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 18 | 19 | 20 |
21 |
elm1
22 |
23 |
24 |
elm2
25 |
26 |
27 |
elm3
28 |
29 |
30 |
Lorem ipsum dolor sit amet, consectetur adipiscing elit
31 | 32 | 33 |
34 |
elm4
35 |
36 |
37 |
elm5
38 |
39 |
40 |
elm6
41 |
42 | 43 |
44 |
elm8
45 |
46 | 47 | 48 | 49 |
50 |
elm9
51 |
52 | 53 |
54 |
elm10
55 |
56 | 57 | 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node, es6 */ 2 | 3 | 'use strict'; 4 | 5 | const 6 | BASE_NAME = 'plain-draggable', 7 | OBJECT_NAME = 'PlainDraggable', 8 | LIMIT_TAGS = ['SNAP', 'AUTO-SCROLL', 'SVG', 'LEFTTOP'], 9 | BUILD_MODE = process.env.NODE_ENV === 'production', 10 | LIMIT = process.env.EDITION === 'limit', 11 | BUILD_BASE_NAME = `${BASE_NAME}${LIMIT ? '-limit' : ''}`, 12 | PREPROC_REMOVE_TAGS = (BUILD_MODE ? ['DEBUG'] : []).concat(LIMIT ? LIMIT_TAGS : []), 13 | 14 | webpack = require('webpack'), 15 | preProc = require('pre-proc'), 16 | path = require('path'), 17 | fs = require('fs'), 18 | PKG = require('./package'), 19 | 20 | SRC_DIR_PATH = path.resolve(__dirname, 'src'), 21 | BUILD_DIR_PATH = BUILD_MODE ? __dirname : path.resolve(__dirname, 'test'), 22 | ESM_DIR_PATH = __dirname, 23 | ENTRY_PATH = path.join(SRC_DIR_PATH, `${BASE_NAME}.js`); 24 | 25 | function writeFile(filePath, content, messageClass) { 26 | const HL = '='.repeat(48); 27 | fs.writeFileSync(filePath, 28 | `/* ${HL}\n DON'T MANUALLY EDIT THIS FILE\n${HL} */\n\n${content}`); 29 | console.log(`Output (${messageClass}): ${filePath}`); 30 | } 31 | 32 | module.exports = { 33 | mode: BUILD_MODE ? 'production' : 'development', 34 | entry: ENTRY_PATH, 35 | output: { 36 | path: BUILD_DIR_PATH, 37 | filename: `${BUILD_BASE_NAME}${BUILD_MODE ? '.min' : ''}.js`, 38 | library: OBJECT_NAME, 39 | libraryTarget: 'var', 40 | libraryExport: 'default' 41 | }, 42 | module: { 43 | rules: [ 44 | { 45 | resource: {and: [SRC_DIR_PATH, /\.js$/]}, 46 | use: [ 47 | // ================================ Save ESM file 48 | { 49 | loader: 'skeleton-loader', 50 | options: { 51 | procedure(content) { 52 | if (this.resourcePath === ENTRY_PATH) { 53 | writeFile( 54 | path.join(ESM_DIR_PATH, `${BUILD_BASE_NAME}${BUILD_MODE ? '' : '-debug'}.esm.js`), 55 | content, 'ESM'); 56 | } 57 | return content; 58 | } 59 | } 60 | }, 61 | // ================================ Babel 62 | { 63 | loader: 'babel-loader', 64 | options: {presets: [['@babel/preset-env', {targets: 'defaults', modules: false}]]} 65 | }, 66 | // ================================ Preprocess 67 | PREPROC_REMOVE_TAGS.length ? { 68 | loader: 'skeleton-loader', 69 | options: { 70 | procedure(content) { 71 | content = preProc.removeTag(PREPROC_REMOVE_TAGS, content); 72 | if (BUILD_MODE && this.resourcePath === ENTRY_PATH) { 73 | writeFile(path.join(SRC_DIR_PATH, `${BUILD_BASE_NAME}.proc.js`), content, 'PROC'); 74 | } 75 | return content; 76 | } 77 | } 78 | } : null 79 | ].filter(loader => !!loader) 80 | } 81 | ] 82 | }, 83 | devtool: BUILD_MODE ? false : 'source-map', 84 | plugins: BUILD_MODE ? [ 85 | new webpack.BannerPlugin( 86 | `${PKG.title || PKG.name} v${PKG.version} (c) ${PKG.author.name} ${PKG.homepage}`) 87 | ] : [] 88 | }; 89 | -------------------------------------------------------------------------------- /test/httpd.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node, es6 */ 2 | 3 | 'use strict'; 4 | 5 | const 6 | nodeStaticAlias = require('node-static-alias'), 7 | log4js = require('log4js'), 8 | http = require('http'), 9 | pathUtil = require('path'), 10 | fs = require('fs'), 11 | filelist = require('stats-filelist'), 12 | 13 | DOC_ROOT = __dirname, 14 | PORT = 8080, 15 | 16 | MODULE_PACKAGES = [ 17 | 'jasmine-core', 18 | 'test-page-loader', 19 | 'cssprefix' 20 | ], 21 | 22 | EXT_DIR = pathUtil.resolve(__dirname, '../../test-ext'), 23 | 24 | logger = (() => { 25 | log4js.configure({ // Super simple format 26 | appenders: {out: {type: 'stdout', layout: {type: 'pattern', pattern: '%[[%r]%] %m'}}}, 27 | categories: {default: {appenders: ['out'], level: 'info'}} 28 | }); 29 | return log4js.getLogger('node-static-alias'); 30 | })(), 31 | 32 | staticAlias = new nodeStaticAlias.Server(DOC_ROOT, { 33 | cache: false, 34 | headers: {'Cache-Control': 'no-cache, must-revalidate'}, 35 | alias: 36 | MODULE_PACKAGES.map(packageName => 37 | ({ // node_modules 38 | match: new RegExp(`^/${packageName}/.+`), 39 | serve: `${require.resolve(packageName).replace( 40 | // Include `packageName` for nested `node_modules` 41 | new RegExp(`^(.*[/\\\\]node_modules)[/\\\\]${packageName}[/\\\\].*$`), '$1')}<% reqPath %>`, 42 | allowOutside: true 43 | }) 44 | ).concat([ 45 | // limited-function script 46 | { 47 | match: /^\/plain-draggable\.js$/, 48 | serve: params => 49 | (/\bLIMIT=true\b/.test(params.cookie) 50 | ? params.absPath.replace(/\.js$/, '-limit.js') : params.absPath) 51 | }, 52 | 53 | // test-ext 54 | { 55 | match: /^\/ext\/.+/, 56 | serve: params => params.reqPath.replace(/^\/ext/, EXT_DIR), 57 | allowOutside: true 58 | }, 59 | // test-ext index 60 | { 61 | match: /^\/ext\/?$/, 62 | serve: () => { 63 | const indexPath = pathUtil.join(EXT_DIR, '.index.html'); 64 | fs.writeFileSync(indexPath, 65 | ``); 74 | return indexPath; 75 | }, 76 | allowOutside: true 77 | } 78 | ]), 79 | logger 80 | }); 81 | 82 | http.createServer((request, response) => { 83 | request.addListener('end', () => { 84 | staticAlias.serve(request, response, error => { 85 | if (error) { 86 | response.writeHead(error.status, error.headers); 87 | logger.error('(%s) %s', request.url, response.statusCode); 88 | if (error.status === 404) { 89 | response.end('Not Found'); 90 | } 91 | } else { 92 | logger.info('(%s) %s', request.url, response.statusCode); 93 | } 94 | }); 95 | }).resume(); 96 | }).listen(PORT); 97 | 98 | console.log(`START: http://localhost:${PORT}/\nROOT: ${DOC_ROOT}`); 99 | console.log('(^C to stop)'); 100 | -------------------------------------------------------------------------------- /test/snap-view.js: -------------------------------------------------------------------------------- 1 | /* exported snapView */ 2 | /* eslint-env browser */ 3 | /* eslint no-var: "off", prefer-arrow-callback: "off" */ 4 | 5 | var snapView = (function() { 6 | 'use strict'; 7 | 8 | var items = [], 9 | isFinite = Number.isFinite || 10 | function(value) { return typeof value === 'number' && window.isFinite(value); }, 11 | offset; 12 | 13 | function initOffset(element) { 14 | element.style.left = element.style.top = '0'; 15 | var newBBox = window.getBBox(element); 16 | offset = {left: newBBox.left ? -newBBox.left : 0, top: newBBox.top ? -newBBox.top : 0}; // avoid `-0` 17 | } 18 | 19 | function newItem(snapTarget, minLeft, maxLeft, minTop, maxTop) { 20 | var item = document.body.appendChild(document.createElement('div')), 21 | start = item.appendChild(document.createElement('div')), 22 | itemStyle = item.style, 23 | startStyle = start.style; 24 | 25 | if (snapTarget.x != null && snapTarget.y != null) { // Point 26 | item.className = 'snap-view-point'; 27 | if (!offset) { initOffset(item); } 28 | itemStyle.left = snapTarget.x + offset.left + 'px'; 29 | itemStyle.top = snapTarget.y + offset.top + 'px'; 30 | // End side 31 | itemStyle.width = 32 | (snapTarget.gravityXEnd != null ? snapTarget.gravityXEnd : maxLeft) - snapTarget.x + 'px'; 33 | itemStyle.height = 34 | (snapTarget.gravityYEnd != null ? snapTarget.gravityYEnd : maxTop) - snapTarget.y + 'px'; 35 | // Start side 36 | startStyle.width = 37 | snapTarget.x - (snapTarget.gravityXStart != null ? snapTarget.gravityXStart : minLeft) + 'px'; 38 | startStyle.height = 39 | snapTarget.y - (snapTarget.gravityYStart != null ? snapTarget.gravityYStart : minTop) + 'px'; 40 | 41 | } else if (snapTarget.y != null) { // Line horizontal 42 | item.className = 'snap-view-line'; 43 | if (!offset) { initOffset(item); } 44 | itemStyle.top = snapTarget.y + offset.top + 'px'; 45 | var left = snapTarget.gravityXStart != null ? snapTarget.gravityXStart : minLeft; 46 | itemStyle.left = left + offset.left + 'px'; 47 | itemStyle.width = 48 | (snapTarget.gravityXEnd != null ? snapTarget.gravityXEnd : maxLeft) - left + 'px'; 49 | // End side 50 | itemStyle.height = 51 | (snapTarget.gravityYEnd != null ? snapTarget.gravityYEnd : maxTop) - snapTarget.y + 'px'; 52 | // Start side 53 | startStyle.height = 54 | snapTarget.y - (snapTarget.gravityYStart != null ? snapTarget.gravityYStart : minTop) + 'px'; 55 | 56 | } else { // Line vertical 57 | item.className = 'snap-view-line snap-view-v'; 58 | if (!offset) { initOffset(item); } 59 | itemStyle.left = snapTarget.x + offset.left + 'px'; 60 | var top = snapTarget.gravityYStart != null ? snapTarget.gravityYStart : minTop; 61 | itemStyle.top = top + offset.top + 'px'; 62 | itemStyle.height = 63 | (snapTarget.gravityYEnd != null ? snapTarget.gravityYEnd : maxTop) - top + 'px'; 64 | // End side 65 | itemStyle.width = 66 | (snapTarget.gravityXEnd != null ? snapTarget.gravityXEnd : maxLeft) - snapTarget.x + 'px'; 67 | // Start side 68 | startStyle.width = 69 | snapTarget.x - (snapTarget.gravityXStart != null ? snapTarget.gravityXStart : minLeft) + 'px'; 70 | } 71 | 72 | return item; 73 | } 74 | 75 | function snapView() { 76 | items.forEach(function(item) { item.parentNode.removeChild(item); }); 77 | items = []; 78 | Object.keys(window.insProps).forEach(function(id) { 79 | var props = window.insProps[id]; 80 | if (props.snapTargets) { 81 | props.snapTargets.forEach(function(snapTarget) { 82 | items.push(newItem(snapTarget, props.minLeft, props.maxLeft, props.minTop, props.maxTop)); 83 | }); 84 | if (!isFinite(parseFloat(getComputedStyle(props.element, '').zIndex))) { 85 | props.orgZIndex = props.elementStyle.zIndex = 2; 86 | } 87 | } 88 | }); 89 | } 90 | 91 | return snapView; 92 | })(); 93 | -------------------------------------------------------------------------------- /test/spec/element.js: -------------------------------------------------------------------------------- 1 | describe('element', function() { 2 | 'use strict'; 3 | 4 | var LIMIT = self.top.LIMIT; 5 | var window, document, pageDone, 6 | cssPropTransform, 7 | 8 | // use `top` to get native window 9 | IS_EDGE = '-ms-scroll-limit' in top.document.documentElement.style && 10 | '-ms-ime-align' in top.document.documentElement.style && !top.navigator.msPointerEnabled, 11 | IS_TRIDENT = !IS_EDGE && !!top.document.uniqueID; // Future Edge might support `document.uniqueID`. 12 | 13 | beforeAll(function(beforeDone) { 14 | loadPage('spec/element.html', function(pageWindow, pageDocument, pageBody, done) { 15 | window = pageWindow; 16 | document = pageDocument; 17 | pageDone = done; 18 | 19 | cssPropTransform = window.CSSPrefix.getName('transform'); 20 | 21 | beforeDone(); 22 | }); 23 | }); 24 | 25 | afterAll(function() { 26 | pageDone(); 27 | }); 28 | 29 | it('Check Edition (to be LIMIT: ' + !!LIMIT + ')', function() { 30 | expect(!!window.PlainDraggable.limit).toBe(!!LIMIT); 31 | }); 32 | 33 | it('accepts HTMLElement as layer element', function() { 34 | var draggable = new window.PlainDraggable(document.getElementById('elm1')), 35 | props = window.insProps[draggable._id]; 36 | 37 | expect(props.svgPoint == null).toBe(true); 38 | expect(props.orgStyle[cssPropTransform] != null).toBe(true); 39 | expect(props.orgStyle.position == null).toBe(true); 40 | }); 41 | 42 | it('accepts HTMLElement as layer element with option (left and top)', function() { 43 | var draggable, props; 44 | 45 | if (LIMIT) { 46 | expect(function() { 47 | draggable = new window.PlainDraggable(document.getElementById('elm1'), {leftTop: true}); 48 | console.log(draggable); // dummy 49 | }).toThrowError('`transform` is not supported.'); 50 | } else { 51 | draggable = new window.PlainDraggable(document.getElementById('elm1'), {leftTop: true}); 52 | props = window.insProps[draggable._id]; 53 | expect(props.svgPoint == null).toBe(true); 54 | expect(props.orgStyle[cssPropTransform] == null).toBe(true); 55 | expect(props.orgStyle.position != null).toBe(true); 56 | } 57 | }); 58 | 59 | it('accepts SVGElement that is root view as layer element', function() { 60 | var draggable = new window.PlainDraggable(document.getElementById('svg1')), 61 | props = window.insProps[draggable._id]; 62 | 63 | expect(props.svgPoint == null).toBe(true); 64 | expect(props.orgStyle[cssPropTransform] != null).toBe(true); 65 | expect(props.orgStyle.position == null).toBe(true); 66 | }); 67 | 68 | it('accepts SVGElement that is not root view as SVG element', function() { 69 | if (LIMIT) { return; } 70 | var draggable = new window.PlainDraggable(document.getElementById('rect1')), 71 | props = window.insProps[draggable._id]; 72 | 73 | expect(props.svgPoint != null).toBe(true); // Has SVG info 74 | expect(props.orgStyle == null).toBe(true); 75 | }); 76 | 77 | if (IS_TRIDENT || IS_EDGE) { 78 | it('does not accept SVGSVGElement that has no `transform` (Trident and Edge bug)', function() { 79 | if (LIMIT) { return; } 80 | expect(function() { 81 | var draggable = new window.PlainDraggable(document.getElementById('svg2')); 82 | console.log(draggable); // dummy 83 | }).toThrowError(window.Error, 'This element is not accepted. (SVGAnimatedTransformList)'); 84 | }); 85 | } else { 86 | it('accepts SVGElement (nested SVG) that is not root view as SVG element', function() { 87 | if (LIMIT) { return; } 88 | var draggable = new window.PlainDraggable(document.getElementById('svg2')), 89 | props = window.insProps[draggable._id]; 90 | 91 | expect(props.svgPoint != null).toBe(true); // Has SVG info 92 | expect(props.orgStyle == null).toBe(true); 93 | }); 94 | } 95 | 96 | it('accepts SVGElement (nested rect) that is not root view as SVG element', function() { 97 | if (LIMIT) { return; } 98 | var draggable = new window.PlainDraggable(document.getElementById('rect2')), 99 | props = window.insProps[draggable._id]; 100 | 101 | expect(props.svgPoint != null).toBe(true); // Has SVG info 102 | expect(props.orgStyle == null).toBe(true); 103 | }); 104 | 105 | it('sets shadow to optimize it only when it has no shadow', function() { 106 | var INIT_SHADOW = '1px', // Keyword from initAnim(), the value might be formatted by browser 107 | cssPropBoxShadow = window.CSSPrefix.getName('boxShadow'), 108 | elm = document.getElementById('elm1'), 109 | draggable = new window.PlainDraggable(elm), // eslint-disable-line no-unused-vars 110 | cmpValue; 111 | 112 | // elm1 may has been already set by other tests 113 | expect(elm.style[cssPropBoxShadow].indexOf(INIT_SHADOW)).not.toBe(-1); 114 | 115 | elm = document.getElementById('elm-shadow-by-id'); 116 | cmpValue = window.getComputedStyle(elm, '')[cssPropBoxShadow]; 117 | expect(elm.style[cssPropBoxShadow]).toBe(''); 118 | expect(cmpValue).not.toBe(''); 119 | expect(cmpValue).not.toBe('none'); 120 | expect(cmpValue.indexOf(INIT_SHADOW)).toBe(-1); 121 | expect(cmpValue.indexOf('5px')).not.toBe(-1); 122 | draggable = new window.PlainDraggable(elm); // Setup 123 | // Not changed 124 | cmpValue = window.getComputedStyle(elm, '')[cssPropBoxShadow]; 125 | expect(elm.style[cssPropBoxShadow]).toBe(''); 126 | expect(cmpValue).not.toBe(''); 127 | expect(cmpValue).not.toBe('none'); 128 | expect(cmpValue.indexOf(INIT_SHADOW)).toBe(-1); 129 | expect(cmpValue.indexOf('5px')).not.toBe(-1); 130 | 131 | // Has no shadow yet 132 | elm = document.getElementById('elm-shadow-by-style'); 133 | cmpValue = window.getComputedStyle(elm, '')[cssPropBoxShadow]; 134 | expect(elm.style[cssPropBoxShadow]).toBe(''); 135 | expect(cmpValue).toBe('none'); 136 | draggable = new window.PlainDraggable(elm); // Setup 137 | // Changed 138 | expect(elm.style[cssPropBoxShadow].indexOf(INIT_SHADOW)).not.toBe(-1); 139 | 140 | // Set to style 141 | elm.style[cssPropBoxShadow] = '3px 5px 10px 3px rgba(0, 0, 0, 0.3)'; 142 | cmpValue = window.getComputedStyle(elm, '')[cssPropBoxShadow]; 143 | expect(elm.style[cssPropBoxShadow].indexOf(INIT_SHADOW)).toBe(-1); 144 | expect(elm.style[cssPropBoxShadow].indexOf('5px')).not.toBe(-1); 145 | expect(cmpValue.indexOf(INIT_SHADOW)).toBe(-1); 146 | expect(cmpValue.indexOf('5px')).not.toBe(-1); 147 | draggable = new window.PlainDraggable(elm); // Setup 148 | // Not changed 149 | expect(elm.style[cssPropBoxShadow].indexOf(INIT_SHADOW)).toBe(-1); 150 | expect(elm.style[cssPropBoxShadow].indexOf('5px')).not.toBe(-1); 151 | expect(cmpValue.indexOf(INIT_SHADOW)).toBe(-1); 152 | expect(cmpValue.indexOf('5px')).not.toBe(-1); 153 | }); 154 | 155 | }); 156 | -------------------------------------------------------------------------------- /test/spec/bbox.js: -------------------------------------------------------------------------------- 1 | describe('BBox', function() { 2 | 'use strict'; 3 | 4 | var window, document, pageDone; 5 | 6 | beforeAll(function(beforeDone) { 7 | loadPage('spec/bbox.html', function(pageWindow, pageDocument, pageBody, done) { 8 | window = pageWindow; 9 | document = pageDocument; 10 | pageDone = done; 11 | beforeDone(); 12 | }, 'BBox'); 13 | }); 14 | 15 | afterAll(function() { 16 | pageDone(); 17 | }); 18 | 19 | it('Check Edition (to be LIMIT: ' + !!self.top.LIMIT + ')', function() { 20 | expect(!!window.PlainDraggable.limit).toBe(!!self.top.LIMIT); 21 | }); 22 | 23 | it('keeps original BBox if possible', function() { 24 | var draggable, element, orgBBox, curBBox, saveWidth; 25 | 26 | element = document.getElementById('elm1'); 27 | orgBBox = window.getBBox(element); 28 | expect(orgBBox).toEqual({left: 0, top: 0, x: 0, y: 0, width: 300, height: 20, right: 300, bottom: 20}); 29 | draggable = new window.PlainDraggable(element); 30 | curBBox = window.getBBox(element); 31 | expect(curBBox).toEqual(orgBBox); 32 | expect(window.insProps[draggable._id].htmlOffset).toEqual({left: 0, top: 0}); 33 | 34 | element = document.getElementById('elm2'); 35 | orgBBox = window.getBBox(element); 36 | expect(orgBBox).toEqual({left: 0, top: 30, x: 0, y: 30, width: 300, height: 26, right: 300, bottom: 56}); 37 | draggable = new window.PlainDraggable(element); 38 | curBBox = window.getBBox(element); 39 | expect(curBBox).toEqual(orgBBox); 40 | expect(window.insProps[draggable._id].htmlOffset).toEqual({left: 0, top: -30}); 41 | 42 | element.style.width = '160px'; // border: 1, padding 2 -> BBox.width: 166 43 | orgBBox = window.getBBox(element); 44 | expect(orgBBox).toEqual({left: 0, top: 30, x: 0, y: 30, width: 166, height: 26, right: 166, bottom: 56}); 45 | draggable.position(); 46 | curBBox = window.getBBox(element); 47 | expect(curBBox).toEqual(orgBBox); 48 | expect(element.style.width).toBe('160px'); // Don't change 49 | 50 | element = document.getElementById('elm3'); 51 | orgBBox = window.getBBox(element); 52 | expect(orgBBox).toEqual({left: 0, top: 60, x: 0, y: 60, width: 300, height: 26, right: 300, bottom: 86}); 53 | draggable = new window.PlainDraggable(element); 54 | curBBox = window.getBBox(element); 55 | expect(curBBox).toEqual(orgBBox); 56 | expect(window.insProps[draggable._id].htmlOffset).toEqual({left: 0, top: -60}); 57 | 58 | element = document.getElementById('elm4'); 59 | saveWidth = element.style.width; 60 | orgBBox = window.getBBox(element); 61 | expect(orgBBox).toEqual({left: 0, top: 120, x: 0, y: 120, width: 300, height: 30, right: 300, bottom: 150}); 62 | draggable = new window.PlainDraggable(element); 63 | curBBox = window.getBBox(element); 64 | expect(curBBox).toEqual(orgBBox); 65 | expect(window.insProps[draggable._id].htmlOffset).toEqual({left: 0, top: -120}); 66 | 67 | // Change size (width was not changed by init) 68 | expect(element.style.width).toBe(saveWidth); 69 | element.style.width = '160px'; 70 | orgBBox = window.getBBox(element); 71 | expect(orgBBox).toEqual({left: 0, top: 120, x: 0, y: 120, width: 160, height: 30, right: 160, bottom: 150}); 72 | draggable.position(); 73 | curBBox = window.getBBox(element); 74 | expect(curBBox).toEqual(orgBBox); 75 | expect(element.style.width).toBe('160px'); // Don't change 76 | 77 | element = document.getElementById('elm5'); 78 | orgBBox = window.getBBox(element); 79 | expect(orgBBox).toEqual({left: 0, top: 170, x: 0, y: 170, width: 306, height: 36, right: 306, bottom: 206}); 80 | draggable = new window.PlainDraggable(element); 81 | curBBox = window.getBBox(element); 82 | expect(curBBox).toEqual(orgBBox); 83 | expect(window.insProps[draggable._id].htmlOffset).toEqual({left: 0, top: -170}); 84 | 85 | element = document.getElementById('elm6'); 86 | orgBBox = window.getBBox(element); 87 | expect(orgBBox).toEqual({left: 0, top: 220, x: 0, y: 220, width: 300, height: 30, right: 300, bottom: 250}); 88 | draggable = new window.PlainDraggable(element); 89 | curBBox = window.getBBox(element); 90 | expect(curBBox).toEqual(orgBBox); 91 | expect(window.insProps[draggable._id].htmlOffset).toEqual({left: 0, top: -220}); 92 | 93 | element = document.getElementById('elm7'); 94 | orgBBox = window.getBBox(element); 95 | expect(orgBBox).toEqual({left: 0, top: 90, x: 0, y: 90, width: 300, height: 40, right: 300, bottom: 130}); 96 | draggable = new window.PlainDraggable(element); 97 | curBBox = window.getBBox(element); 98 | expect(curBBox).toEqual(orgBBox); 99 | expect(window.insProps[draggable._id].htmlOffset).toEqual({left: 0, top: -90}); 100 | 101 | element = document.getElementById('elm8'); 102 | orgBBox = window.getBBox(element); 103 | expect(orgBBox).toEqual({left: 0, top: 270, x: 0, y: 270, width: 300, height: 30, right: 300, bottom: 300}); 104 | draggable = new window.PlainDraggable(element); 105 | curBBox = window.getBBox(element); 106 | expect(curBBox).toEqual(orgBBox); 107 | expect(window.insProps[draggable._id].htmlOffset).toEqual({left: 0, top: -270}); 108 | 109 | element = document.getElementById('elm9'); 110 | orgBBox = window.getBBox(element); 111 | // margin: 21px -> top: 21 112 | expect(orgBBox).toEqual({left: 22, top: 342, x: 22, y: 342, width: 300, height: 30, right: 322, bottom: 372}); 113 | draggable = new window.PlainDraggable(element); 114 | curBBox = window.getBBox(element); 115 | // containment height: 48px -> maxTop: 18 116 | expect(curBBox).toEqual({left: 22, top: 339, x: 22, y: 339, width: 300, height: 30, right: 322, bottom: 369}); 117 | expect(window.insProps[draggable._id].htmlOffset).toEqual({left: -22, top: -342}); 118 | 119 | element = document.getElementById('elm10'); 120 | orgBBox = window.getBBox(element); 121 | expect(orgBBox).toEqual({left: 22, top: 392, x: 22, y: 392, width: 300, height: 30, right: 322, bottom: 422}); 122 | draggable = new window.PlainDraggable(element); 123 | curBBox = window.getBBox(element); 124 | expect(curBBox).toEqual(orgBBox); 125 | expect(window.insProps[draggable._id].htmlOffset).toEqual({left: -22, top: -392}); 126 | }); 127 | 128 | it('gets offset by document', function() { 129 | var iWindow = document.getElementById('iframe').contentWindow, 130 | iDocument = iWindow.document, 131 | iBody = iDocument.body, 132 | draggable, element; 133 | 134 | iDocument.getElementById('parent').style.position = 'relative'; 135 | element = iDocument.getElementById('elm1'); 136 | 137 | iBody.style.margin = iBody.style.borderWidth = iBody.style.padding = '0'; 138 | draggable = new iWindow.PlainDraggable(element); 139 | expect(iWindow.insProps[draggable._id].htmlOffset).toEqual({left: 0, top: 0}); 140 | 141 | iBody.style.borderStyle = 'solid'; 142 | iBody.style.marginLeft = '1px'; 143 | iBody.style.marginTop = '2px'; 144 | iBody.style.borderLeftWidth = '4px'; 145 | iBody.style.borderTopWidth = '8px'; 146 | iBody.style.paddingLeft = '16px'; 147 | iBody.style.paddingTop = '32px'; 148 | draggable = new iWindow.PlainDraggable(element); 149 | expect(iWindow.insProps[draggable._id].htmlOffset).toEqual({left: -21, top: -42}); 150 | }); 151 | 152 | }); 153 | -------------------------------------------------------------------------------- /test/spec/autoScroll.js: -------------------------------------------------------------------------------- 1 | describe('autoScroll', function() { 2 | 'use strict'; 3 | 4 | var window, document, pageDone, 5 | parent, elm1, draggable, props, 6 | 7 | clientWidth, clientHeight, 8 | 9 | AUTOSCROLL_SPEED, AUTOSCROLL_SENSITIVITY, 10 | 11 | // bBox of element and window 12 | ELM_W = 102, 13 | ELM_H = 104, 14 | WIN_L = 0, 15 | WIN_T = 0, 16 | WIN_R, WIN_B, WIN_W, WIN_H, 17 | 18 | SCROLL_WIDTH = 640, 19 | SCROLL_HEIGHT = 960, 20 | WIN_WIDTH = 300, 21 | WIN_HEIGHT = 400; 22 | 23 | beforeAll(function(beforeDone) { 24 | loadPage('spec/common-window.html', function(pageWindow, pageDocument, pageBody, done) { 25 | var iframe = pageDocument.getElementById('iframe'); 26 | window = iframe.contentWindow; 27 | document = window.document; 28 | pageDone = done; 29 | 30 | iframe.style.width = WIN_WIDTH + 'px'; 31 | iframe.style.height = WIN_HEIGHT + 'px'; 32 | iframe.style.border = '0 none'; 33 | document.body.style.margin = '0'; 34 | 35 | parent = document.getElementById('parent'); 36 | elm1 = document.getElementById('elm1'); 37 | 38 | parent.style.width = SCROLL_WIDTH + 'px'; 39 | parent.style.height = SCROLL_HEIGHT + 'px'; 40 | clientWidth = document.documentElement.clientWidth; 41 | clientHeight = document.documentElement.clientHeight; 42 | WIN_W = clientWidth; 43 | WIN_H = clientHeight; 44 | WIN_R = WIN_L + WIN_W; 45 | WIN_B = WIN_T + WIN_H; 46 | 47 | draggable = new window.PlainDraggable(elm1); 48 | props = window.insProps[draggable._id]; 49 | 50 | AUTOSCROLL_SPEED = window.AUTOSCROLL_SPEED; 51 | AUTOSCROLL_SENSITIVITY = window.AUTOSCROLL_SENSITIVITY; 52 | 53 | beforeDone(); 54 | }, 'autoScroll'); 55 | }); 56 | 57 | afterAll(function() { 58 | pageDone(); 59 | }); 60 | 61 | it('Check Edition (to be LIMIT: ' + !!self.top.LIMIT + ')', function() { 62 | expect(!!window.PlainDraggable.limit).toBe(!!self.top.LIMIT); 63 | }); 64 | 65 | it('Default', function(done) { 66 | expect(draggable.autoScroll).not.toBeDefined(); // Default 67 | expect(props.autoScroll == null).toBe(true); // Default 68 | 69 | draggable.autoScroll = true; 70 | expect(draggable.autoScroll).toEqual({ 71 | target: window, 72 | speed: AUTOSCROLL_SPEED, 73 | sensitivity: AUTOSCROLL_SENSITIVITY 74 | }); 75 | delete props.autoScroll.scrollableBBox; // Ignore 76 | expect(props.autoScroll).toEqual({ 77 | target: window, 78 | isWindow: true, 79 | scrollWidth: SCROLL_WIDTH, 80 | scrollHeight: SCROLL_HEIGHT, 81 | x: { 82 | min: 0, 83 | max: SCROLL_WIDTH - clientWidth, 84 | lines: [ 85 | {dir: -1, speed: AUTOSCROLL_SPEED[2], position: WIN_L + AUTOSCROLL_SENSITIVITY[2]}, 86 | {dir: 1, speed: AUTOSCROLL_SPEED[2], position: WIN_R - AUTOSCROLL_SENSITIVITY[2] - ELM_W}, 87 | {dir: -1, speed: AUTOSCROLL_SPEED[1], position: WIN_L + AUTOSCROLL_SENSITIVITY[1]}, 88 | {dir: 1, speed: AUTOSCROLL_SPEED[1], position: WIN_R - AUTOSCROLL_SENSITIVITY[1] - ELM_W}, 89 | {dir: -1, speed: AUTOSCROLL_SPEED[0], position: WIN_L + AUTOSCROLL_SENSITIVITY[0]}, 90 | {dir: 1, speed: AUTOSCROLL_SPEED[0], position: WIN_R - AUTOSCROLL_SENSITIVITY[0] - ELM_W} 91 | ] 92 | }, 93 | y: { 94 | min: 0, 95 | max: SCROLL_HEIGHT - clientHeight, 96 | lines: [ 97 | {dir: -1, speed: AUTOSCROLL_SPEED[2], position: WIN_T + AUTOSCROLL_SENSITIVITY[2]}, 98 | {dir: 1, speed: AUTOSCROLL_SPEED[2], position: WIN_B - AUTOSCROLL_SENSITIVITY[2] - ELM_H}, 99 | {dir: -1, speed: AUTOSCROLL_SPEED[1], position: WIN_T + AUTOSCROLL_SENSITIVITY[1]}, 100 | {dir: 1, speed: AUTOSCROLL_SPEED[1], position: WIN_B - AUTOSCROLL_SENSITIVITY[1] - ELM_H}, 101 | {dir: -1, speed: AUTOSCROLL_SPEED[0], position: WIN_T + AUTOSCROLL_SENSITIVITY[0]}, 102 | {dir: 1, speed: AUTOSCROLL_SPEED[0], position: WIN_B - AUTOSCROLL_SENSITIVITY[0] - ELM_H} 103 | ] 104 | } 105 | }); 106 | 107 | done(); 108 | }); 109 | 110 | it('Change `speed`', function(done) { 111 | var SPEED = [8, 16], 112 | SENSITIVITY = [32, 64]; 113 | 114 | draggable.autoScroll = { 115 | speed: SPEED, 116 | sensitivity: SENSITIVITY 117 | }; 118 | expect(draggable.autoScroll).toEqual({ 119 | target: window, 120 | speed: SPEED, 121 | sensitivity: SENSITIVITY 122 | }); 123 | delete props.autoScroll.scrollableBBox; // Ignore 124 | expect(props.autoScroll).toEqual({ 125 | target: window, 126 | isWindow: true, 127 | scrollWidth: SCROLL_WIDTH, 128 | scrollHeight: SCROLL_HEIGHT, 129 | x: { 130 | min: 0, 131 | max: SCROLL_WIDTH - clientWidth, 132 | lines: [ 133 | {dir: -1, speed: SPEED[1], position: WIN_L + SENSITIVITY[1]}, 134 | {dir: 1, speed: SPEED[1], position: WIN_R - SENSITIVITY[1] - ELM_W}, 135 | {dir: -1, speed: SPEED[0], position: WIN_L + SENSITIVITY[0]}, 136 | {dir: 1, speed: SPEED[0], position: WIN_R - SENSITIVITY[0] - ELM_W} 137 | ] 138 | }, 139 | y: { 140 | min: 0, 141 | max: SCROLL_HEIGHT - clientHeight, 142 | lines: [ 143 | {dir: -1, speed: SPEED[1], position: WIN_T + SENSITIVITY[1]}, 144 | {dir: 1, speed: SPEED[1], position: WIN_B - SENSITIVITY[1] - ELM_H}, 145 | {dir: -1, speed: SPEED[0], position: WIN_T + SENSITIVITY[0]}, 146 | {dir: 1, speed: SPEED[0], position: WIN_B - SENSITIVITY[0] - ELM_H} 147 | ] 148 | } 149 | }); 150 | 151 | done(); 152 | }); 153 | 154 | it('min*, max*', function(done) { 155 | draggable.autoScroll = {minX: 8, maxX: 16, minY: 32, maxY: 64}; 156 | expect(draggable.autoScroll).toEqual({ 157 | minX: 8, maxX: 16, minY: 32, maxY: 64, 158 | target: window, 159 | speed: AUTOSCROLL_SPEED, 160 | sensitivity: AUTOSCROLL_SENSITIVITY 161 | }); 162 | expect(props.autoScroll.x.min).toBe(8); 163 | expect(props.autoScroll.x.max).toBe(16); 164 | expect(props.autoScroll.y.min).toBe(32); 165 | expect(props.autoScroll.y.max).toBe(64); 166 | 167 | // Fix max* 168 | draggable.autoScroll = {maxX: 8, maxY: SCROLL_HEIGHT}; // maxY over 169 | expect(draggable.autoScroll).toEqual({ 170 | maxX: 8, maxY: SCROLL_HEIGHT, 171 | target: window, 172 | speed: AUTOSCROLL_SPEED, 173 | sensitivity: AUTOSCROLL_SENSITIVITY 174 | }); 175 | expect(props.autoScroll.x.min).toBe(0); 176 | expect(props.autoScroll.x.max).toBe(8); 177 | expect(props.autoScroll.y.min).toBe(0); 178 | expect(props.autoScroll.y.max).toBe(SCROLL_HEIGHT - clientHeight); // Fixed 179 | 180 | // Default max* -> max scroll 181 | draggable.autoScroll = {minX: 8, minY: SCROLL_WIDTH}; // minY over 182 | expect(draggable.autoScroll).toEqual({ 183 | minX: 8, minY: SCROLL_WIDTH, 184 | target: window, 185 | speed: AUTOSCROLL_SPEED, 186 | sensitivity: AUTOSCROLL_SENSITIVITY 187 | }); 188 | expect(props.autoScroll.x.min).toBe(8); 189 | expect(props.autoScroll.x.max).toBe(SCROLL_WIDTH - clientWidth); 190 | expect(props.autoScroll.y).not.toBeDefined(); // removed 191 | 192 | // over max* -> max scroll 193 | draggable.autoScroll = {minX: 8, minY: SCROLL_WIDTH, maxY: SCROLL_WIDTH + 10}; // minY over 194 | expect(draggable.autoScroll).toEqual({ 195 | minX: 8, minY: SCROLL_WIDTH, maxY: SCROLL_WIDTH + 10, 196 | target: window, 197 | speed: AUTOSCROLL_SPEED, 198 | sensitivity: AUTOSCROLL_SENSITIVITY 199 | }); 200 | expect(props.autoScroll.x.min).toBe(8); 201 | expect(props.autoScroll.x.max).toBe(SCROLL_WIDTH - clientWidth); 202 | expect(props.autoScroll.y).not.toBeDefined(); // removed 203 | 204 | done(); 205 | }); 206 | 207 | }); 208 | -------------------------------------------------------------------------------- /test/spec/bbox-lefttop.js: -------------------------------------------------------------------------------- 1 | describe('BBox with leftTop option', function() { 2 | 'use strict'; 3 | 4 | var window, document, pageDone; 5 | 6 | beforeAll(function(beforeDone) { 7 | loadPage('spec/bbox.html', function(pageWindow, pageDocument, pageBody, done) { 8 | window = pageWindow; 9 | document = pageDocument; 10 | pageDone = done; 11 | beforeDone(); 12 | }, 'BBox with leftTop option'); 13 | }); 14 | 15 | afterAll(function() { 16 | pageDone(); 17 | }); 18 | 19 | it('Check Edition (to be LIMIT: ' + !!self.top.LIMIT + ')', function() { 20 | expect(!!window.PlainDraggable.limit).toBe(!!self.top.LIMIT); 21 | }); 22 | 23 | it('keeps original BBox if possible', function() { 24 | var draggable, element, orgBBox, curBBox, saveWidth; 25 | 26 | element = document.getElementById('elm1'); 27 | orgBBox = window.getBBox(element); 28 | expect(orgBBox).toEqual({left: 0, top: 0, x: 0, y: 0, width: 300, height: 20, right: 300, bottom: 20}); 29 | draggable = new window.PlainDraggable(element, {leftTop: true}); 30 | curBBox = window.getBBox(element); 31 | expect(curBBox).toEqual(orgBBox); 32 | expect(window.insProps[draggable._id].htmlOffset).toEqual({left: 0, top: 0}); 33 | expect(element.style.width).toBe('300px'); 34 | expect(element.style.height).toBe(''); 35 | expect(element.style.left).toBe('0px'); 36 | expect(element.style.top).toBe('0px'); 37 | 38 | element = document.getElementById('elm2'); 39 | saveWidth = element.style.width; 40 | orgBBox = window.getBBox(element); 41 | expect(orgBBox).toEqual({left: 0, top: 30, x: 0, y: 30, width: 300, height: 26, right: 300, bottom: 56}); 42 | draggable = new window.PlainDraggable(element, {leftTop: true}); 43 | curBBox = window.getBBox(element); 44 | expect(curBBox).toEqual(orgBBox); 45 | expect(window.insProps[draggable._id].htmlOffset).toEqual({left: 0, top: 0}); 46 | expect(element.style.width).toBe('294px'); 47 | expect(element.style.height).toBe(''); 48 | expect(element.style.left).toBe('0px'); 49 | expect(element.style.top).toBe('30px'); 50 | 51 | // Change size (width was already changed by init) 52 | expect(element.style.width).not.toBe(saveWidth); 53 | element.style.width = '160px'; // border: 1, padding 2 -> BBox.width: 166 54 | orgBBox = window.getBBox(element); 55 | expect(orgBBox).toEqual({left: 0, top: 30, x: 0, y: 30, width: 166, height: 26, right: 166, bottom: 56}); 56 | draggable.position(); 57 | curBBox = window.getBBox(element); 58 | expect(curBBox).toEqual(orgBBox); 59 | expect(element.style.width).toBe('160px'); // Don't change 60 | 61 | element = document.getElementById('elm3'); 62 | orgBBox = window.getBBox(element); 63 | expect(orgBBox).toEqual({left: 0, top: 60, x: 0, y: 60, width: 300, height: 26, right: 300, bottom: 86}); 64 | draggable = new window.PlainDraggable(element, {leftTop: true}); 65 | curBBox = window.getBBox(element); 66 | expect(curBBox).toEqual(orgBBox); 67 | expect(window.insProps[draggable._id].htmlOffset).toEqual({left: 0, top: 0}); 68 | expect(element.style.width).toBe('300px'); 69 | expect(element.style.height).toBe(''); 70 | expect(element.style.left).toBe('0px'); 71 | expect(element.style.top).toBe('60px'); 72 | 73 | element = document.getElementById('elm4'); 74 | saveWidth = element.style.width; 75 | orgBBox = window.getBBox(element); 76 | expect(orgBBox).toEqual({left: 0, top: 120, x: 0, y: 120, width: 300, height: 30, right: 300, bottom: 150}); 77 | draggable = new window.PlainDraggable(element, {leftTop: true}); 78 | curBBox = window.getBBox(element); 79 | expect(curBBox).toEqual(orgBBox); 80 | expect(window.insProps[draggable._id].htmlOffset).toEqual({left: 0, top: 0}); 81 | expect(element.style.width).toBe('300px'); 82 | expect(element.style.height).toBe('30px'); 83 | expect(element.style.left).toBe('0px'); 84 | expect(element.style.top).toBe('120px'); 85 | 86 | // Change size (width was not changed by init) 87 | expect(element.style.width).toBe(saveWidth); 88 | element.style.width = '160px'; 89 | orgBBox = window.getBBox(element); 90 | expect(orgBBox).toEqual({left: 0, top: 120, x: 0, y: 120, width: 160, height: 30, right: 160, bottom: 150}); 91 | draggable.position(); 92 | curBBox = window.getBBox(element); 93 | expect(curBBox).toEqual(orgBBox); 94 | expect(element.style.width).toBe('160px'); // Don't change 95 | 96 | element = document.getElementById('elm5'); 97 | orgBBox = window.getBBox(element); 98 | expect(orgBBox).toEqual({left: 0, top: 170, x: 0, y: 170, width: 306, height: 36, right: 306, bottom: 206}); 99 | draggable = new window.PlainDraggable(element, {leftTop: true}); 100 | curBBox = window.getBBox(element); 101 | expect(curBBox).toEqual(orgBBox); 102 | expect(window.insProps[draggable._id].htmlOffset).toEqual({left: 0, top: 0}); 103 | expect(element.style.width).toBe('300px'); 104 | expect(element.style.height).toBe('30px'); 105 | expect(element.style.left).toBe('0px'); 106 | expect(element.style.top).toBe('170px'); 107 | 108 | element = document.getElementById('elm6'); 109 | orgBBox = window.getBBox(element); 110 | expect(orgBBox).toEqual({left: 0, top: 220, x: 0, y: 220, width: 300, height: 30, right: 300, bottom: 250}); 111 | draggable = new window.PlainDraggable(element, {leftTop: true}); 112 | curBBox = window.getBBox(element); 113 | expect(curBBox).toEqual(orgBBox); 114 | expect(window.insProps[draggable._id].htmlOffset).toEqual({left: 0, top: 0}); 115 | expect(element.style.width).toBe('300px'); 116 | expect(element.style.height).toBe('30px'); 117 | expect(element.style.left).toBe('0px'); 118 | expect(element.style.top).toBe('220px'); 119 | 120 | element = document.getElementById('elm7'); 121 | orgBBox = window.getBBox(element); 122 | expect(orgBBox).toEqual({left: 0, top: 90, x: 0, y: 90, width: 300, height: 40, right: 300, bottom: 130}); 123 | draggable = new window.PlainDraggable(element, {leftTop: true}); 124 | curBBox = window.getBBox(element); 125 | expect(curBBox).toEqual(orgBBox); 126 | expect(window.insProps[draggable._id].htmlOffset).toEqual({left: 0, top: 0}); 127 | expect(element.style.width).toBe('300px'); 128 | expect(element.style.height).toBe(''); 129 | expect(element.style.left).toBe('0px'); 130 | expect(element.style.top).toBe('90px'); 131 | 132 | element = document.getElementById('elm8'); 133 | orgBBox = window.getBBox(element); 134 | expect(orgBBox).toEqual({left: 0, top: 270, x: 0, y: 270, width: 300, height: 30, right: 300, bottom: 300}); 135 | draggable = new window.PlainDraggable(element, {leftTop: true}); 136 | curBBox = window.getBBox(element); 137 | expect(curBBox).toEqual(orgBBox); 138 | expect(window.insProps[draggable._id].htmlOffset).toEqual({left: 0, top: -270}); 139 | expect(element.style.width).toBe('300px'); 140 | expect(element.style.height).toBe('30px'); 141 | expect(element.style.left).toBe('0px'); 142 | expect(element.style.top).toBe('0px'); 143 | 144 | element = document.getElementById('elm9'); 145 | orgBBox = window.getBBox(element); 146 | // margin: 21px -> top: 21 147 | expect(orgBBox).toEqual({left: 22, top: 342, x: 22, y: 342, width: 300, height: 30, right: 322, bottom: 372}); 148 | draggable = new window.PlainDraggable(element, {leftTop: true}); 149 | curBBox = window.getBBox(element); 150 | // containment height: 48px -> maxTop: 18 151 | expect(curBBox).toEqual({left: 22, top: 339, x: 22, y: 339, width: 300, height: 30, right: 322, bottom: 369}); 152 | expect(window.insProps[draggable._id].htmlOffset).toEqual({left: -1, top: -321}); 153 | expect(element.style.width).toBe('300px'); 154 | expect(element.style.height).toBe('30px'); 155 | expect(element.style.left).toBe('21px'); 156 | expect(element.style.top).toBe('18px'); // not `21px` because maxTop is 18 157 | 158 | element = document.getElementById('elm10'); 159 | orgBBox = window.getBBox(element); 160 | expect(orgBBox).toEqual({left: 22, top: 392, x: 22, y: 392, width: 300, height: 30, right: 322, bottom: 422}); 161 | draggable = new window.PlainDraggable(element, {leftTop: true}); 162 | curBBox = window.getBBox(element); 163 | expect(curBBox).toEqual(orgBBox); 164 | expect(window.insProps[draggable._id].htmlOffset).toEqual({left: 0, top: -370}); 165 | expect(element.style.width).toBe('300px'); 166 | expect(element.style.height).toBe('30px'); 167 | expect(element.style.left).toBe('22px'); 168 | expect(element.style.top).toBe('22px'); 169 | }); 170 | 171 | it('gets offset by document', function() { 172 | var iWindow = document.getElementById('iframe').contentWindow, 173 | iDocument = iWindow.document, 174 | iBody = iDocument.body, 175 | draggable, element; 176 | 177 | iDocument.getElementById('parent').style.position = 'relative'; 178 | element = iDocument.getElementById('elm1'); 179 | 180 | iBody.style.margin = iBody.style.borderWidth = iBody.style.padding = '0'; 181 | draggable = new iWindow.PlainDraggable(element, {leftTop: true}); 182 | expect(iWindow.insProps[draggable._id].htmlOffset).toEqual({left: 0, top: 0}); 183 | 184 | iBody.style.borderStyle = 'solid'; 185 | iBody.style.marginLeft = '1px'; 186 | iBody.style.marginTop = '2px'; 187 | iBody.style.borderLeftWidth = '4px'; 188 | iBody.style.borderTopWidth = '8px'; 189 | iBody.style.paddingLeft = '16px'; 190 | iBody.style.paddingTop = '32px'; 191 | draggable = new iWindow.PlainDraggable(element, {leftTop: true}); 192 | expect(iWindow.insProps[draggable._id].htmlOffset).toEqual({left: -21, top: -42}); 193 | }); 194 | 195 | }); 196 | -------------------------------------------------------------------------------- /test/spec/funcs.js: -------------------------------------------------------------------------------- 1 | describe('functions', function() { 2 | 'use strict'; 3 | 4 | var LIMIT = self.top.LIMIT; 5 | var window, document, pageDone; 6 | 7 | beforeAll(function(beforeDone) { 8 | loadPage('spec/funcs.html', function(pageWindow, pageDocument, pageBody, done) { 9 | window = pageWindow; 10 | document = pageDocument; 11 | pageDone = done; 12 | 13 | beforeDone(); 14 | }, 'functions'); 15 | }); 16 | 17 | afterAll(function() { 18 | pageDone(); 19 | }); 20 | 21 | it('Check Edition (to be LIMIT: ' + !!LIMIT + ')', function() { 22 | expect(!!window.PlainDraggable.limit).toBe(!!LIMIT); 23 | }); 24 | 25 | it('isElement', function() { 26 | var isElement = window.isElement, 27 | element; 28 | 29 | expect(isElement(document.getElementById('elm1'))).toBe(true); 30 | 31 | element = document.createElement('div'); 32 | expect(isElement(element)).toBe(false); // still disconnected 33 | document.body.appendChild(element); 34 | expect(isElement(element)).toBe(true); 35 | 36 | element = document.getElementById('rect1'); 37 | expect(Object.prototype.toString.apply(element)).toBe('[object SVGRectElement]'); 38 | expect(isElement(element)).toBe(true); // strict checking is unnecessary. 39 | }); 40 | 41 | it('validBBox', function() { 42 | var validBBox = window.validBBox; 43 | 44 | expect(validBBox({left: 1, top: 2, width: 4, height: 8})) 45 | .toEqual({left: 1, top: 2, width: 4, height: 8, x: 1, y: 2, right: 5, bottom: 10}); 46 | 47 | expect(validBBox({x: 1, y: 2, width: 4, height: 8})) // x/y 48 | .toEqual({left: 1, top: 2, width: 4, height: 8, x: 1, y: 2, right: 5, bottom: 10}); 49 | expect(validBBox({x: 1, y: 2, width: 4, height: 8, left: 16, top: 32})) // x/y and left/top 50 | .toEqual({left: 16, top: 32, width: 4, height: 8, x: 16, y: 32, right: 20, bottom: 40}); 51 | expect(validBBox({top: 2, width: 4, height: 8}) == null).toBe(true); 52 | 53 | expect(validBBox({left: 1, top: 2, right: 5, bottom: 10})) // right/bottom 54 | .toEqual({left: 1, top: 2, width: 4, height: 8, x: 1, y: 2, right: 5, bottom: 10}); 55 | expect(validBBox({left: 1, top: 2, right: 5, bottom: 10, width: 16, height: 32})) // right/bottom and width/height 56 | .toEqual({left: 1, top: 2, width: 16, height: 32, x: 1, y: 2, right: 17, bottom: 34}); 57 | expect(validBBox({left: 1, top: 2, height: 8}) == null).toBe(true); 58 | expect(validBBox({left: 1, top: 2, width: 0, height: 8})) // width: 0 59 | .toEqual({left: 1, top: 2, width: 0, height: 8, x: 1, y: 2, right: 1, bottom: 10}); 60 | expect(validBBox({left: 1, top: 2, right: 0, height: 8}) == null).toBe(true); 61 | expect(validBBox({left: 1, top: 2, right: 5, bottom: 1}) == null).toBe(true); 62 | }); 63 | 64 | it('getBBox', function() { 65 | var getBBox = window.getBBox, 66 | element; 67 | 68 | element = document.getElementById('elm1'); 69 | expect(getBBox(element)) 70 | .toEqual({left: 100, top: 101, width: 104, height: 106, x: 100, y: 101, right: 204, bottom: 207}); 71 | expect(getBBox(element, true)) 72 | .toEqual({left: 101, top: 102, width: 102, height: 104, x: 101, y: 102, right: 203, bottom: 206}); 73 | 74 | element = document.getElementById('elm2'); 75 | expect(getBBox(element)) 76 | .toEqual({left: 200, top: 201, width: 222, height: 214, x: 200, y: 201, right: 422, bottom: 415}); 77 | expect(getBBox(element, true)) 78 | .toEqual({left: 216, top: 203, width: 202, height: 204, x: 216, y: 203, right: 418, bottom: 407}); 79 | }); 80 | 81 | it('PPValue', function() { 82 | var validPPValue = window.validPPValue, 83 | ppValue2OptionValue = window.ppValue2OptionValue, 84 | ppValue; 85 | 86 | ppValue = validPPValue(1); 87 | expect(ppValue).toEqual({value: 1, isRatio: false}); 88 | expect(ppValue2OptionValue(ppValue)).toBe(1); 89 | ppValue = validPPValue(0); 90 | expect(ppValue).toEqual({value: 0, isRatio: false}); 91 | expect(ppValue2OptionValue(ppValue)).toBe(0); 92 | ppValue = validPPValue(-1); 93 | expect(ppValue).toEqual({value: -1, isRatio: false}); 94 | expect(ppValue2OptionValue(ppValue)).toBe(-1); 95 | 96 | // Not number, string 97 | expect(validPPValue({}) == null).toBe(true); 98 | expect(validPPValue(true) == null).toBe(true); 99 | expect(validPPValue() == null).toBe(true); 100 | 101 | // string 102 | ppValue = validPPValue(' 5 '); 103 | expect(ppValue).toEqual({value: 5, isRatio: false}); 104 | expect(ppValue2OptionValue(ppValue)).toBe(5); 105 | ppValue = validPPValue(' 005.00 '); 106 | expect(ppValue).toEqual({value: 5, isRatio: false}); 107 | expect(ppValue2OptionValue(ppValue)).toBe(5); 108 | ppValue = validPPValue(' + 5 '); 109 | expect(ppValue).toEqual({value: 5, isRatio: false}); 110 | expect(ppValue2OptionValue(ppValue)).toBe(5); 111 | ppValue = validPPValue(' - 005.00 '); 112 | expect(ppValue).toEqual({value: -5, isRatio: false}); 113 | expect(ppValue2OptionValue(ppValue)).toBe(-5); 114 | ppValue = validPPValue(' - 5 '); 115 | expect(ppValue).toEqual({value: -5, isRatio: false}); 116 | expect(ppValue2OptionValue(ppValue)).toBe(-5); 117 | ppValue = validPPValue(' - 005.00 '); 118 | expect(ppValue).toEqual({value: -5, isRatio: false}); 119 | expect(ppValue2OptionValue(ppValue)).toBe(-5); 120 | 121 | ppValue = validPPValue(' + 5 x '); 122 | expect(ppValue).toEqual({value: 5, isRatio: false}); 123 | expect(ppValue2OptionValue(ppValue)).toBe(5); 124 | ppValue = validPPValue(' - 005.00 x '); 125 | expect(ppValue).toEqual({value: -5, isRatio: false}); 126 | expect(ppValue2OptionValue(ppValue)).toBe(-5); 127 | ppValue = validPPValue(' + 5 % '); 128 | expect(ppValue).toEqual({value: 0.05, isRatio: true}); 129 | expect(ppValue2OptionValue(ppValue)).toBe('5%'); 130 | ppValue = validPPValue(' - 005.00 % '); 131 | expect(ppValue).toEqual({value: -0.05, isRatio: true}); 132 | expect(ppValue2OptionValue(ppValue)).toBe('-5%'); 133 | ppValue = validPPValue(' + 5 x % '); 134 | expect(ppValue).toEqual({value: 0.05, isRatio: true}); 135 | expect(ppValue2OptionValue(ppValue)).toBe('5%'); 136 | ppValue = validPPValue(' - 005.00 x % '); 137 | expect(ppValue).toEqual({value: -0.05, isRatio: true}); 138 | expect(ppValue2OptionValue(ppValue)).toBe('-5%'); 139 | ppValue = validPPValue(' + 5 % x '); 140 | expect(ppValue).toEqual({value: 5, isRatio: false}); // `%` is ignored 141 | expect(ppValue2OptionValue(ppValue)).toBe(5); 142 | ppValue = validPPValue(' - 005.00 % x '); 143 | expect(ppValue).toEqual({value: -5, isRatio: false}); // `%` is ignored 144 | expect(ppValue2OptionValue(ppValue)).toBe(-5); 145 | ppValue = validPPValue(' 0% '); 146 | expect(ppValue).toEqual({value: 0, isRatio: false}); // 0% -> 0 147 | expect(ppValue2OptionValue(ppValue)).toBe(0); 148 | 149 | expect(validPPValue('') == null).toBe(true); 150 | expect(validPPValue(' ') == null).toBe(true); 151 | expect(validPPValue('x') == null).toBe(true); 152 | expect(validPPValue(' x 5 ') == null).toBe(true); 153 | expect(validPPValue(' x 005.00 ') == null).toBe(true); 154 | expect(validPPValue(' - x 5 ') == null).toBe(true); 155 | expect(validPPValue(' - x 005.00 ') == null).toBe(true); 156 | }); 157 | 158 | it('PPBBox', function() { 159 | var validPPBBox = window.validPPBBox, 160 | ppBBox2OptionObject = window.ppBBox2OptionObject, 161 | resolvePPBBox = window.resolvePPBBox, 162 | share, ppBBox; 163 | 164 | // Not Object 165 | expect(validPPBBox(1) == null).toBe(true); 166 | expect(validPPBBox('1') == null).toBe(true); 167 | expect(validPPBBox(true) == null).toBe(true); 168 | expect(validPPBBox() == null).toBe(true); 169 | 170 | share = { 171 | x: {value: 2, isRatio: false}, 172 | y: {value: 4, isRatio: false}, 173 | left: {value: 2, isRatio: false}, 174 | top: {value: 4, isRatio: false}, 175 | width: {value: 8, isRatio: false}, 176 | height: {value: 16, isRatio: false} 177 | }; 178 | expect(validPPBBox({x: 2, y: 4, width: 8, height: 16})).toEqual(share); 179 | expect(validPPBBox({x: 2, top: 4, width: 8, height: 16})).toEqual(share); // Alias 180 | expect(ppBBox2OptionObject(share)).toEqual({x: 2, y: 4, left: 2, top: 4, width: 8, height: 16}); 181 | 182 | expect(validPPBBox({x: 2, width: 8, height: 16}) == null).toBe(true); // No y 183 | 184 | ppBBox = validPPBBox({x: 2, y: 4, width: 0, height: 16}); // width: 0 185 | expect(ppBBox).toEqual({ 186 | x: {value: 2, isRatio: false}, 187 | y: {value: 4, isRatio: false}, 188 | left: {value: 2, isRatio: false}, 189 | top: {value: 4, isRatio: false}, 190 | width: {value: 0, isRatio: false}, 191 | height: {value: 16, isRatio: false} 192 | }); 193 | expect(ppBBox2OptionObject(ppBBox)).toEqual({x: 2, y: 4, left: 2, top: 4, width: 0, height: 16}); 194 | 195 | expect(validPPBBox({x: 2, y: 4, width: -1, height: 16}) == null).toBe(true); // width: -1 196 | 197 | ppBBox = validPPBBox({x: 2, y: 4, width: -1, height: 16, right: 32}); // width: -1, right: 32 198 | expect(ppBBox).toEqual({ 199 | x: {value: 2, isRatio: false}, 200 | y: {value: 4, isRatio: false}, 201 | left: {value: 2, isRatio: false}, 202 | top: {value: 4, isRatio: false}, 203 | right: {value: 32, isRatio: false}, 204 | height: {value: 16, isRatio: false} 205 | }); 206 | expect(ppBBox2OptionObject(ppBBox)).toEqual({x: 2, y: 4, left: 2, top: 4, right: 32, height: 16}); 207 | 208 | ppBBox = validPPBBox({x: 2, y: '4%', width: 8, bottom: ' 16 % '}); // n% 209 | expect(ppBBox).toEqual({ 210 | x: {value: 2, isRatio: false}, 211 | y: {value: 0.04, isRatio: true}, 212 | left: {value: 2, isRatio: false}, 213 | top: {value: 0.04, isRatio: true}, 214 | width: {value: 8, isRatio: false}, 215 | bottom: {value: 0.16, isRatio: true} 216 | }); 217 | expect(ppBBox2OptionObject(ppBBox)).toEqual({x: 2, y: '4%', left: 2, top: '4%', width: 8, bottom: '16%'}); 218 | 219 | var baseBBox = {left: 64, top: 32, width: 256, height: 128}, 220 | left, top, width, height; 221 | 222 | ppBBox = validPPBBox({x: 2, y: '25%', width: 8, bottom: '50%'}); 223 | expect(ppBBox == null).toBe(false); 224 | left = 64 + 2; 225 | top = 32 + 32; 226 | width = 8; 227 | height = 32 + 64 - top; 228 | expect(resolvePPBBox(ppBBox, baseBBox)).toEqual({left: left, top: top, width: width, height: height, 229 | x: left, y: top, right: left + width, bottom: top + height}); 230 | 231 | ppBBox = validPPBBox({x: 0, y: '0%', right: '100%', bottom: '100%'}); 232 | expect(ppBBox == null).toBe(false); 233 | left = baseBBox.left; 234 | top = baseBBox.top; 235 | width = baseBBox.width; 236 | height = baseBBox.height; 237 | expect(resolvePPBBox(ppBBox, baseBBox)).toEqual({left: left, top: top, width: width, height: height, 238 | x: left, y: top, right: left + width, bottom: top + height}); 239 | 240 | ppBBox = validPPBBox({x: 128, y: '0%', right: '50%', bottom: '100%'}); 241 | expect(ppBBox == null).toBe(false); 242 | left = 64 + 128; 243 | top = baseBBox.top; 244 | width = 0; 245 | height = baseBBox.height; 246 | expect(resolvePPBBox(ppBBox, baseBBox)).toEqual({left: left, top: top, width: width, height: height, 247 | x: left, y: top, right: left + width, bottom: top + height}); 248 | 249 | ppBBox = validPPBBox({x: 240, y: '0%', right: '50%', bottom: '100%'}); // Invalid 250 | expect(ppBBox == null).toBe(false); // PPBBox is accepted. 251 | expect(resolvePPBBox(ppBBox, baseBBox) == null).toBe(true); 252 | 253 | ppBBox = validPPBBox({x: 0, y: '20%', right: '50%', bottom: '10%'}); // Invalid 254 | expect(ppBBox == null).toBe(false); // PPBBox is accepted. 255 | expect(resolvePPBBox(ppBBox, baseBBox) == null).toBe(true); 256 | 257 | ppBBox = validPPBBox({x: '50%', y: '0%', right: 127, bottom: '100%'}); // Invalid 258 | expect(ppBBox == null).toBe(false); // PPBBox is accepted. 259 | expect(resolvePPBBox(ppBBox, baseBBox) == null).toBe(true); 260 | 261 | ppBBox = validPPBBox({x: 129, y: '0%', right: '50%', bottom: '100%'}); // Invalid 262 | expect(ppBBox == null).toBe(false); // PPBBox is accepted. 263 | expect(resolvePPBBox(ppBBox, baseBBox) == null).toBe(true); 264 | 265 | baseBBox.width = 258; 266 | ppBBox = validPPBBox({x: 129, y: '0%', right: '50%', bottom: '100%'}); 267 | expect(ppBBox == null).toBe(false); 268 | left = 64 + 129; 269 | top = baseBBox.top; 270 | width = 0; 271 | height = baseBBox.height; 272 | expect(resolvePPBBox(ppBBox, baseBBox)).toEqual({left: left, top: top, width: width, height: height, 273 | x: left, y: top, right: left + width, bottom: top + height}); 274 | }); 275 | 276 | it('commonSnapOptions', function() { 277 | if (LIMIT) { return; } 278 | // Export inner functions 279 | new window.PlainDraggable(document.body.appendChild(document.createElement('div'))); // eslint-disable-line no-new 280 | var commonSnapOptions = window.commonSnapOptions; 281 | 282 | // normal 283 | expect(commonSnapOptions({dummy: 1}, 284 | {gravity: 9, corner: 'tr', side: 'end', edge: 'outside', base: 'document'}) 285 | ).toEqual( 286 | {gravity: 9, corner: 'tr', side: 'end', edge: 'outside', base: 'document', dummy: 1}); 287 | 288 | // gravity 289 | expect(commonSnapOptions({dummy: 1}, {gravity: '9'})).toEqual({dummy: 1}); // Invalid 290 | expect(commonSnapOptions({dummy: 1}, {gravity: ''})).toEqual({dummy: 1}); // Invalid 291 | expect(commonSnapOptions({dummy: 1}, {gravity: false})).toEqual({dummy: 1}); // Invalid 292 | expect(commonSnapOptions({dummy: 1}, {gravity: 0})).toEqual({dummy: 1}); // Invalid 293 | expect(commonSnapOptions({dummy: 1}, {gravity: -5})).toEqual({dummy: 1}); // Invalid 294 | expect(commonSnapOptions({dummy: 1}, {gravity: 5})).toEqual({gravity: 5, dummy: 1}); 295 | 296 | // corner 297 | expect(commonSnapOptions({dummy: 1}, {corner: 9})).toEqual({dummy: 1}); // Invalid 298 | expect(commonSnapOptions({dummy: 1}, {corner: ''})).toEqual({dummy: 1}); // Invalid 299 | expect(commonSnapOptions({dummy: 1}, {corner: 'dummy'})).toEqual({dummy: 1}); // Invalid 300 | expect(commonSnapOptions({dummy: 1}, {corner: 'all'})).toEqual({corner: 'all', dummy: 1}); 301 | expect(commonSnapOptions({dummy: 1}, {corner: ' tl br dummy bl '})) 302 | .toEqual({corner: 'tl br bl', dummy: 1}); 303 | expect(commonSnapOptions({dummy: 1}, {corner: 'top-left RIGHT-BOTTOM lb'})) 304 | .toEqual({corner: 'tl br bl', dummy: 1}); 305 | expect(commonSnapOptions({dummy: 1}, {corner: 'top-left,RIGHT-BOTTOM,,,lb'})) // `,` 306 | .toEqual({corner: 'tl br bl', dummy: 1}); 307 | expect(commonSnapOptions({dummy: 1}, {corner: 'top-left tl lt left-top RIGHT-BOTTOM lb'})) 308 | .toEqual({corner: 'tl br bl', dummy: 1}); 309 | expect(commonSnapOptions({dummy: 1}, {corner: 'dummy1 dummy2 dummy3'})).toEqual({dummy: 1}); 310 | expect(commonSnapOptions({dummy: 1}, {corner: 'tl,tr,,bl,br'})) // -> all 311 | .toEqual({corner: 'all', dummy: 1}); 312 | expect(commonSnapOptions({dummy: 1}, {corner: ',tl,tr,,bl,,br,'})) // -> all 313 | .toEqual({corner: 'all', dummy: 1}); 314 | expect(commonSnapOptions({dummy: 1}, {corner: 'top-left RIGHT-BOTTOM lb rt'})) // -> all 315 | .toEqual({corner: 'all', dummy: 1}); 316 | expect(commonSnapOptions({dummy: 1}, {corner: ' lb lb lb rt lb lb'})) 317 | .toEqual({corner: 'bl tr', dummy: 1}); 318 | 319 | // side 320 | expect(commonSnapOptions({dummy: 1}, {side: 9})).toEqual({dummy: 1}); // Invalid 321 | expect(commonSnapOptions({dummy: 1}, {side: ''})).toEqual({dummy: 1}); // Invalid 322 | expect(commonSnapOptions({dummy: 1}, {side: 'dummy'})).toEqual({dummy: 1}); // Invalid 323 | expect(commonSnapOptions({dummy: 1}, {side: ' sTart '})).toEqual({side: 'start', dummy: 1}); 324 | expect(commonSnapOptions({dummy: 1}, {side: ' eNd '})).toEqual({side: 'end', dummy: 1}); 325 | expect(commonSnapOptions({dummy: 1}, {side: 'both'})).toEqual({side: 'both', dummy: 1}); 326 | expect(commonSnapOptions({dummy: 1}, {side: ' start end'})).toEqual({side: 'both', dummy: 1}); // -> both 327 | expect(commonSnapOptions({dummy: 1}, {side: 'end,,start'})).toEqual({side: 'both', dummy: 1}); // => both 328 | expect(commonSnapOptions({dummy: 1}, {side: ',end,,start,'})).toEqual({side: 'both', dummy: 1}); // => both 329 | 330 | // center 331 | expect(commonSnapOptions({dummy: 1}, {center: 9})).toEqual({dummy: 1}); // Invalid 332 | expect(commonSnapOptions({dummy: 1}, {center: ''})).toEqual({dummy: 1}); // Invalid 333 | expect(commonSnapOptions({dummy: 1}, {center: 'dummy'})).toEqual({dummy: 1}); // Invalid 334 | expect(commonSnapOptions({dummy: 1}, {center: true})).toEqual({center: true, dummy: 1}); 335 | expect(commonSnapOptions({dummy: 1}, {center: false})).toEqual({center: false, dummy: 1}); 336 | 337 | // edge 338 | expect(commonSnapOptions({dummy: 1}, {edge: 9})).toEqual({dummy: 1}); // Invalid 339 | expect(commonSnapOptions({dummy: 1}, {edge: ''})).toEqual({dummy: 1}); // Invalid 340 | expect(commonSnapOptions({dummy: 1}, {edge: 'dummy'})).toEqual({dummy: 1}); // Invalid 341 | expect(commonSnapOptions({dummy: 1}, {edge: ' inSide '})).toEqual({edge: 'inside', dummy: 1}); 342 | expect(commonSnapOptions({dummy: 1}, {edge: ' oUtside '})).toEqual({edge: 'outside', dummy: 1}); 343 | expect(commonSnapOptions({dummy: 1}, {edge: 'both'})).toEqual({edge: 'both', dummy: 1}); 344 | expect(commonSnapOptions({dummy: 1}, {edge: ' inSide outside'})) // -> both 345 | .toEqual({edge: 'both', dummy: 1}); 346 | expect(commonSnapOptions({dummy: 1}, {edge: ' inSide,, outside'})) // -> both 347 | .toEqual({edge: 'both', dummy: 1}); 348 | expect(commonSnapOptions({dummy: 1}, {edge: ',inside,, outside'})) // -> both 349 | .toEqual({edge: 'both', dummy: 1}); 350 | 351 | // base 352 | expect(commonSnapOptions({dummy: 1}, {base: 9})).toEqual({dummy: 1}); // Invalid 353 | expect(commonSnapOptions({dummy: 1}, {base: ''})).toEqual({dummy: 1}); // Invalid 354 | expect(commonSnapOptions({dummy: 1}, {base: 'dummy'})).toEqual({dummy: 1}); // Invalid 355 | expect(commonSnapOptions({dummy: 1}, {base: ' conTAinment '})).toEqual({base: 'containment', dummy: 1}); 356 | expect(commonSnapOptions({dummy: 1}, {base: ' docUMent '})).toEqual({base: 'document', dummy: 1}); 357 | }); 358 | 359 | }); 360 | -------------------------------------------------------------------------------- /plain-draggable-limit.min.js: -------------------------------------------------------------------------------- 1 | /*! PlainDraggable v2.5.15 (c) anseki https://anseki.github.io/plain-draggable/ */ 2 | var PlainDraggable=function(t){var e={};function n(o){if(e[o])return e[o].exports;var r=e[o]={i:o,l:!1,exports:{}};return t[o].call(r.exports,r,r.exports,n),r.l=!0,r.exports}return n.m=t,n.c=e,n.d=function(t,e,o){n.o(t,e)||Object.defineProperty(t,e,{enumerable:!0,get:o})},n.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},n.t=function(t,e){if(1&e&&(t=n(t)),8&e)return t;if(4&e&&"object"==typeof t&&t&&t.__esModule)return t;var o=Object.create(null);if(n.r(o),Object.defineProperty(o,"default",{enumerable:!0,value:t}),2&e&&"string"!=typeof t)for(var r in t)n.d(o,r,function(e){return t[e]}.bind(null,r));return o},n.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return n.d(e,"a",e),e},n.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},n.p="",n(n.s=0)}([function(t,e,n){"use strict";n.r(e);var o,r=[],i=window.requestAnimationFrame||window.mozRequestAnimationFrame||window.webkitRequestAnimationFrame||window.msRequestAnimationFrame||function(t){return setTimeout(t,1e3/60)},a=window.cancelAnimationFrame||window.mozCancelAnimationFrame||window.webkitCancelAnimationFrame||window.msCancelAnimationFrame||function(t){return clearTimeout(t)},l=Date.now();function u(){var t,e;o&&(a.call(window,o),o=null),r.forEach((function(e){var n;(n=e.event)&&(e.event=null,e.listener(n),t=!0)})),t?(l=Date.now(),e=!0):Date.now()-l<500&&(e=!0),e&&(o=i.call(window,u))}function c(t){var e=-1;return r.some((function(n,o){return n.listener===t&&(e=o,!0)})),e}var s={add:function(t){var e;return-1===c(t)?(r.push(e={listener:t}),function(t){e.event=t,o||u()}):null},remove:function(t){var e;(e=c(t))>-1&&(r.splice(e,1),!r.length&&o&&(a.call(window,o),o=null))}};function d(t,e){for(var n=0;n=0)t.right=t.left+t.width;else{if(!(lt(t.right)&&t.right>=t.left))return null;t.width=t.right-t.left}if(lt(t.height)&&t.height>=0)t.bottom=t.top+t.height;else{if(!(lt(t.bottom)&&t.bottom>=t.top))return null;t.height=t.bottom-t.top}return t}function bt(t){return lt(t)?{value:t,isRatio:!1}:"string"==typeof t?function(t){var e,n,o=/^(.+?)(%)?$/.exec(t);return o&<(e=parseFloat(o[1]))?{value:(n=!(!o[2]||!e))?e/100:e,isRatio:n}:null}(t.replace(/\s/g,"")):null}function xt(t,e){var n=t.getBoundingClientRect(),o={left:n.left,top:n.top,width:n.width,height:n.height};if(o.left+=window.pageXOffset,o.top+=window.pageYOffset,e){var r=window.getComputedStyle(t,""),i=parseFloat(r.borderTopWidth)||0,a=parseFloat(r.borderRightWidth)||0,l=parseFloat(r.borderBottomWidth)||0,u=parseFloat(r.borderLeftWidth)||0;o.left+=u,o.top+=i,o.width-=u+a,o.height-=i+l}return wt(o)}function Et(t,e){null==V&&(!1!==ft&&(V=H.getValue("cursor",ft)),null==V&&(V=!1)),t.style.cursor=!1===V?e:V}function St(t){null==$&&(!1!==pt&&($=H.getValue("cursor",pt)),null==$&&($=!1)),!1!==$&&(t.style.cursor=$)}function Ct(t,e){var n=t.elementBBox;if(e.left!==n.left||e.top!==n.top){var o=t.htmlOffset;return t.elementStyle[K]="translate(".concat(e.left+o.left,"px, ").concat(e.top+o.top,"px)"),!0}return!1}function Tt(t,e,n){var o=t.elementBBox;function r(){t.minLeft>=t.maxLeft?e.left=o.left:e.leftt.maxLeft&&(e.left=t.maxLeft),t.minTop>=t.maxTop?e.top=o.top:e.topt.maxTop&&(e.top=t.maxTop)}if(r(),n){if(!1===n(e))return!1;r()}var i=t.moveElm(t,e);return i&&(t.elementBBox=wt({left:e.left,top:e.top,width:o.width,height:o.height})),i}function It(t){var e=t.element,n=t.elementStyle,o=xt(e),r=["display","marginTop","marginBottom","width","height"];r.unshift(K);var i=n[J];n[J]="none";var a=xt(e);t.orgStyle?r.forEach((function(e){null!=t.lastStyle[e]&&n[e]!==t.lastStyle[e]||(n[e]=t.orgStyle[e])})):(t.orgStyle=r.reduce((function(t,e){return t[e]=n[e]||"",t}),{}),t.lastStyle={});var l=xt(e),u=window.getComputedStyle(e,"");"inline"===u.display&&(n.display="inline-block",["Top","Bottom"].forEach((function(t){var e=parseFloat(u["padding".concat(t)]);n["margin".concat(t)]=e?"-".concat(e,"px"):"0"}))),n[K]="translate(0, 0)";var c=xt(e),s=t.htmlOffset={left:c.left?-c.left:0,top:c.top?-c.top:0};return n[K]="translate(".concat(o.left+s.left,"px, ").concat(o.top+s.top,"px)"),["width","height"].forEach((function(o){c[o]!==l[o]&&(n[o]=l[o]+"px",(c=xt(e))[o]!==l[o]&&(n[o]=l[o]-(c[o]-l[o])+"px")),t.lastStyle[o]=n[o]})),e.offsetWidth,n[J]=i,a.left===o.left&&a.top===o.top||(n[K]="translate(".concat(a.left+s.left,"px, ").concat(a.top+s.top,"px)")),a}function kt(t,e){var n,o,r,i,a,l=xt(document.documentElement),u=t.elementBBox=t.initElm(t),c=t.containmentBBox=t.containmentIsBBox?(n=t.options.containment,r={left:"x",right:"x",x:"x",width:"x",top:"y",bottom:"y",y:"y",height:"y"},i={x:(o=l).left,y:o.top},a={x:o.width,y:o.height},wt(Object.keys(n).reduce((function(t,e){var o,l,u;return t[e]=(o=n[e],l="width"===e||"height"===e?0:i[r[e]],u=a[r[e]],"number"==typeof o?o:l+o.value*(o.isRatio?u:1)),t}),{}))||l):xt(t.options.containment,!0);t.minLeft=c.left,t.maxLeft=c.right-u.width,t.minTop=c.top,t.maxTop=c.bottom-u.height,Tt(t,{left:u.left,top:u.top})}function Ot(t){Et(t.options.handle,t.orgCursor),q.style.cursor=G,!1!==t.options.zIndex&&(t.elementStyle.zIndex=t.orgZIndex),Q&&(q.style[Q]=tt);var e=L(t.element);vt&&e.remove(vt),mt&&e.remove(mt),U=null,st.cancel(),t.onDragEnd&&t.onDragEnd({left:t.elementBBox.left,top:t.elementBBox.top})}function Pt(t,e){var n,o,r=t.options;e.containment&&(yt(e.containment)?e.containment!==r.containment&&(r.containment=e.containment,t.containmentIsBBox=!1,n=!0):(o=function(t){if(!at(t))return null;var e;if(!(e=bt(t.left))&&!(e=bt(t.x)))return null;if(t.left=t.x=e,!(e=bt(t.top))&&!(e=bt(t.y)))return null;if(t.top=t.y=e,(e=bt(t.width))&&e.value>=0)t.width=e,delete t.right;else{if(!(e=bt(t.right)))return null;t.right=e,delete t.width}if((e=bt(t.height))&&e.value>=0)t.height=e,delete t.bottom;else{if(!(e=bt(t.bottom)))return null;t.bottom=e,delete t.height}return t}(gt(e.containment)))&&function t(e,n){var o,r;return N(e)!==N(n)||(o=at(e)?"obj":Array.isArray(e)?"array":"")!=(at(n)?"obj":Array.isArray(n)?"array":"")||("obj"===o?t(r=Object.keys(e).sort(),Object.keys(n).sort())||r.some((function(o){return t(e[o],n[o])})):"array"===o?e.length!==n.length||e.some((function(e,o){return t(e,n[o])})):e!==n)}(o,r.containment)&&(r.containment=o,t.containmentIsBBox=!0,n=!0));if(n&&kt(t),yt(e.handle)&&e.handle!==r.handle){r.handle&&(r.handle.style.cursor=t.orgCursor,Q&&(r.handle.style[Q]=t.orgUserSelect),st.removeStartHandler(r.handle,t.pointerEventHandlerId));var i=r.handle=e.handle;t.orgCursor=i.style.cursor,Et(i,t.orgCursor),Q&&(t.orgUserSelect=i.style[Q],i.style[Q]="none"),st.addStartHandler(i,t.pointerEventHandlerId)}(lt(e.zIndex)||!1===e.zIndex)&&(r.zIndex=e.zIndex,t===U&&(t.elementStyle.zIndex=!1===r.zIndex?t.orgZIndex:r.zIndex));var a,l={left:t.elementBBox.left,top:t.elementBBox.top};lt(e.left)&&e.left!==l.left&&(l.left=e.left,a=!0),lt(e.top)&&e.top!==l.top&&(l.top=e.top,a=!0),a&&Tt(t,l),["onDrag","onMove","onDragStart","onMoveStart","onDragEnd"].forEach((function(n){"function"==typeof e[n]?(r[n]=e[n],t[n]=r[n].bind(t.ins)):e.hasOwnProperty(n)&&null==e[n]&&(r[n]=t[n]=void 0)}))}var _t=function(){function t(e,n){!function(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}(this,t);var o={ins:this,options:{zIndex:9e3},disabled:!1};if(Object.defineProperty(this,"_id",{value:++dt}),o._id=this._id,ut[this._id]=o,!yt(e)||e===q)throw new Error("This element is not accepted.");if(n){if(!at(n))throw new Error("Invalid options.")}else n={};var r,i=!0,a=H.getName("willChange");if(a&&(i=!1),n.leftTop||!K)throw new Error("`transform` is not supported.");(a&&(e.style[a]="transform"),o.initElm=It,o.moveElm=Ct,o.element=function(t,e){var n=t.style;n.webkitTapHighlightColor="transparent";var o=H.getName("boxShadow"),r=window.getComputedStyle(t,"")[o];return r&&"none"!==r||(n[o]="0 0 1px transparent"),e&&K&&(n[K]="translateZ(0)"),t}(e,i),o.elementStyle=e.style,o.orgZIndex=o.elementStyle.zIndex,ht&&L(e).add(ht),o.pointerEventHandlerId=st.regStartHandler((function(t){return function(t,e){return!t.disabled&&((!t.onDragStart||!1!==t.onDragStart(e))&&(U&&Ot(U),St(t.options.handle),q.style.cursor=$||window.getComputedStyle(t.options.handle,"").cursor,!1!==t.options.zIndex&&(t.elementStyle.zIndex=t.options.zIndex),Q&&(q.style[Q]="none"),mt&&L(t.element).add(mt),U=t,Z=!1,ct.left=t.elementBBox.left-(e.clientX+window.pageXOffset),ct.top=t.elementBBox.top-(e.clientY+window.pageYOffset),!0))}(o,t)})),n.containment)||(n.containment=(r=e.parentNode)&&yt(r)?r:q);n.handle||(n.handle=e),Pt(o,n)}var e,n,o;return e=t,o=[{key:"draggableCursor",get:function(){return ft},set:function(t){ft!==t&&(ft=t,V=null,Object.keys(ut).forEach((function(t){var e=ut[t];e.disabled||e===U&&!1!==$||(Et(e.options.handle,e.orgCursor),e===U&&(q.style.cursor=G,q.style.cursor=window.getComputedStyle(e.options.handle,"").cursor))})))}},{key:"draggingCursor",get:function(){return pt},set:function(t){pt!==t&&(pt=t,$=null,U&&(St(U.options.handle),!1===$&&(Et(U.options.handle,U.orgCursor),q.style.cursor=G),q.style.cursor=$||window.getComputedStyle(U.options.handle,"").cursor))}},{key:"draggableClass",get:function(){return ht},set:function(t){(t=t?t+"":void 0)!==ht&&(Object.keys(ut).forEach((function(e){var n=ut[e];if(!n.disabled){var o=L(n.element);ht&&o.remove(ht),t&&o.add(t)}})),ht=t)}},{key:"draggingClass",get:function(){return mt},set:function(t){if((t=t?t+"":void 0)!==mt){if(U){var e=L(U.element);mt&&e.remove(mt),t&&e.add(t)}mt=t}}},{key:"movingClass",get:function(){return vt},set:function(t){if((t=t?t+"":void 0)!==vt){if(U&&Z){var e=L(U.element);vt&&e.remove(vt),t&&e.add(t)}vt=t}}}],(n=[{key:"remove",value:function(){var t=ut[this._id];this.disabled=!0,st.unregStartHandler(st.removeStartHandler(t.options.handle,t.pointerEventHandlerId)),delete ut[this._id]}},{key:"setOptions",value:function(t){return at(t)&&Pt(ut[this._id],t),this}},{key:"position",value:function(){return kt(ut[this._id]),this}},{key:"disabled",get:function(){return ut[this._id].disabled},set:function(t){var e=ut[this._id];(t=!!t)!==e.disabled&&(e.disabled=t,e.disabled?(e===U&&Ot(e),e.options.handle.style.cursor=e.orgCursor,Q&&(e.options.handle.style[Q]=e.orgUserSelect),ht&&L(e.element).remove(ht)):(Et(e.options.handle,e.orgCursor),Q&&(e.options.handle.style[Q]="none"),ht&&L(e.element).add(ht)))}},{key:"element",get:function(){return ut[this._id].element}},{key:"rect",get:function(){return gt(ut[this._id].elementBBox)}},{key:"left",get:function(){return ut[this._id].elementBBox.left},set:function(t){Pt(ut[this._id],{left:t})}},{key:"top",get:function(){return ut[this._id].elementBBox.top},set:function(t){Pt(ut[this._id],{top:t})}},{key:"containment",get:function(){var t,e=ut[this._id];return e.containmentIsBBox?(t=e.options.containment,Object.keys(t).reduce((function(e,n){var o;return e[n]=(o=t[n]).isRatio?"".concat(100*o.value,"%"):o.value,e}),{})):e.options.containment},set:function(t){Pt(ut[this._id],{containment:t})}},{key:"handle",get:function(){return ut[this._id].options.handle},set:function(t){Pt(ut[this._id],{handle:t})}},{key:"zIndex",get:function(){return ut[this._id].options.zIndex},set:function(t){Pt(ut[this._id],{zIndex:t})}},{key:"onDrag",get:function(){return ut[this._id].options.onDrag},set:function(t){Pt(ut[this._id],{onDrag:t})}},{key:"onMove",get:function(){return ut[this._id].options.onMove},set:function(t){Pt(ut[this._id],{onMove:t})}},{key:"onDragStart",get:function(){return ut[this._id].options.onDragStart},set:function(t){Pt(ut[this._id],{onDragStart:t})}},{key:"onMoveStart",get:function(){return ut[this._id].options.onMoveStart},set:function(t){Pt(ut[this._id],{onMoveStart:t})}},{key:"onDragEnd",get:function(){return ut[this._id].options.onDragEnd},set:function(t){Pt(ut[this._id],{onDragEnd:t})}}])&&z(e.prototype,n),o&&z(e,o),t}();st.addMoveHandler(document,(function(t){if(U){var e={left:t.clientX+window.pageXOffset+ct.left,top:t.clientY+window.pageYOffset+ct.top};Tt(U,e,U.onDrag)&&(Z||(Z=!0,vt&&L(U.element).add(vt),U.onMoveStart&&U.onMoveStart(e)),U.onMove&&U.onMove(e))}}));{function Bt(){U&&Ot(U)}st.addEndHandler(document,Bt),st.addCancelHandler(document,Bt)}{function Dt(){J=H.getName("transitionProperty"),K=H.getName("transform"),G=q.style.cursor,(Q=H.getName("userSelect"))&&(tt=q.style[Q]);var t,e={};function n(t,e){t.initElm&&kt(t)}var o=!1,r=s.add((function(r){o||(o=!0,U&&(n(U,r.type),st.move(),e[U._id]=!0),clearTimeout(t),t=setTimeout((function(){r.type,clearTimeout(t),Object.keys(ut).forEach((function(t){e[t]||n(ut[t])})),e={}}),200),o=!1)}));window.addEventListener("resize",r,!0),window.addEventListener("scroll",r,!0)}(q=document.body)?Dt():document.addEventListener("DOMContentLoaded",(function(){q=document.body,Dt()}),!0)}_t.limit=!0;e.default=_t}]).default; -------------------------------------------------------------------------------- /test/spec/snap.js: -------------------------------------------------------------------------------- 1 | describe('snapTargets', function() { 2 | 'use strict'; 3 | 4 | var window, document, pageDone, 5 | parent, elm1, draggable, props, 6 | elmRect, elmWidth, elmHeight, 7 | 8 | iframe, iWindow, iDocument, iBody, 9 | iElm1, iDraggable, iProps, iElmWidth, iElmHeight, 10 | 11 | SNAP_GRAVITY, SNAP_CORNER, SNAP_SIDE, SNAP_EDGE, SNAP_BASE; 12 | 13 | function merge() { 14 | var obj = {}; 15 | Array.prototype.forEach.call(arguments, function(addObj) { 16 | Object.keys(addObj).forEach(function(key) { 17 | if (addObj[key] != null) { 18 | obj[key] = addObj[key]; 19 | } else { 20 | delete obj[key]; 21 | } 22 | }); 23 | }); 24 | return obj; 25 | } 26 | 27 | function getPointTarget(x, y, gravityX, gravityY) { 28 | var xy = {x: x, y: y}, 29 | gravity = {x: gravityX, y: gravityY}; 30 | return ['x', 'y'].reduce(function(target, axis) { 31 | if (xy[axis] != null) { 32 | target[axis] = xy[axis]; 33 | if (gravity[axis] != null) { 34 | var axisL = axis.toUpperCase(); 35 | target['gravity' + axisL + 'Start'] = target[axis] - gravity[axis]; 36 | target['gravity' + axisL + 'End'] = target[axis] + gravity[axis]; 37 | } 38 | } 39 | return target; 40 | }, {}); 41 | } 42 | 43 | // x/y : number or [start, end] 44 | function getLineTarget(x, y, gravity, elmSize) { 45 | var xy = {x: x, y: y}, 46 | elmSizeAxis = {x: elmSize.width, y: elmSize.height}; 47 | return ['x', 'y'].reduce(function(target, axis) { 48 | var axisL = axis.toUpperCase(); 49 | if (typeof xy[axis] === 'number') { 50 | target[axis] = xy[axis]; 51 | if (gravity != null) { 52 | target['gravity' + axisL + 'Start'] = target[axis] - gravity; 53 | target['gravity' + axisL + 'End'] = target[axis] + gravity; 54 | } 55 | } else if (Array.isArray(xy[axis]) && gravity != null) { // Check for gravity ON/OFF 56 | if (xy[axis][0] != null) { target['gravity' + axisL + 'Start'] = xy[axis][0]; } 57 | if (xy[axis][1] != null) { target['gravity' + axisL + 'End'] = xy[axis][1] - elmSizeAxis[axis]; } 58 | } 59 | return target; 60 | }, {}); 61 | } 62 | 63 | function getBBoxTargets(left, top, width, height, gravity, elmWidth, elmHeight) { 64 | var right = left + width, 65 | bottom = top + height, 66 | elmRect = {width: elmWidth, height: elmHeight}; 67 | return { 68 | inside: [ 69 | getLineTarget([left - gravity, right + gravity], top, gravity, elmRect), // Top 70 | getLineTarget(left, [top - gravity, bottom + gravity], gravity, elmRect), // Left 71 | getLineTarget([left - gravity, right + gravity], bottom - elmHeight, gravity, elmRect), // Bottom 72 | getLineTarget(right - elmWidth, [top - gravity, bottom + gravity], gravity, elmRect) // Right 73 | ], 74 | outside: [ 75 | getLineTarget([left - elmWidth - gravity, right + elmWidth + gravity], 76 | top - elmHeight, gravity, elmRect), // Top 77 | getLineTarget(left - elmWidth, 78 | [top - elmHeight - gravity, bottom + elmHeight + gravity], gravity, elmRect), // Left 79 | getLineTarget([left - elmWidth - gravity, right + elmWidth + gravity], 80 | bottom, gravity, elmRect), // Bottom 81 | getLineTarget(right, 82 | [top - elmHeight - gravity, bottom + elmHeight + gravity], gravity, elmRect) // Right 83 | ] 84 | }; 85 | } 86 | 87 | beforeAll(function(beforeDone) { 88 | loadPage('spec/common-window.html', function(pageWindow, pageDocument, pageBody, done) { 89 | window = pageWindow; 90 | document = pageDocument; 91 | pageDone = done; 92 | 93 | parent = document.getElementById('parent'); 94 | parent.style.height = '600px'; 95 | elm1 = document.getElementById('elm1'); 96 | draggable = new window.PlainDraggable(elm1); 97 | props = window.insProps[draggable._id]; 98 | 99 | elmRect = elm1.getBoundingClientRect(); 100 | elmWidth = elmRect.width; 101 | elmHeight = elmRect.height; 102 | 103 | iframe = document.getElementById('iframe'); 104 | iWindow = iframe.contentWindow; 105 | iDocument = iWindow.document; 106 | iBody = iDocument.body; 107 | iElm1 = iDocument.getElementById('elm1'); 108 | iDocument.getElementById('parent').style.height = '600px'; 109 | iDraggable = new iWindow.PlainDraggable(iElm1); 110 | iProps = iWindow.insProps[iDraggable._id]; 111 | iBody.style.margin = iBody.style.borderWidth = iBody.style.padding = '0'; 112 | iBody.style.overflow = 'hidden'; // Hide vertical scroll bar that change width of document. 113 | 114 | elmRect = iElm1.getBoundingClientRect(); 115 | iElmWidth = elmRect.width; 116 | iElmHeight = elmRect.height; 117 | 118 | SNAP_GRAVITY = window.SNAP_GRAVITY; 119 | SNAP_CORNER = window.SNAP_CORNER; 120 | SNAP_SIDE = window.SNAP_SIDE; 121 | SNAP_EDGE = window.SNAP_EDGE; 122 | SNAP_BASE = window.SNAP_BASE; 123 | 124 | beforeDone(); 125 | }, 'snapTargets'); 126 | }); 127 | 128 | afterAll(function() { 129 | pageDone(); 130 | }); 131 | 132 | it('Check Edition (to be LIMIT: ' + !!self.top.LIMIT + ')', function() { 133 | expect(!!window.PlainDraggable.limit).toBe(!!self.top.LIMIT); 134 | }); 135 | 136 | it('Point', function(done) { 137 | var parentBBox = window.getBBox(parent), 138 | share; 139 | 140 | // Parse pixels 141 | draggable.snap = 300; 142 | expect(props.snapTargets).toEqual([ 143 | getPointTarget( 144 | parentBBox.left + 300, 145 | parentBBox.top + 300, 146 | SNAP_GRAVITY, SNAP_GRAVITY) 147 | ]); 148 | 149 | // Parse n% 150 | draggable.snap = ['50%', {x: 400, y: 300}]; // base 800 x 600 151 | share = getPointTarget( 152 | parentBBox.left + 400, 153 | parentBBox.top + 300, 154 | SNAP_GRAVITY, SNAP_GRAVITY); 155 | expect(props.snapTargets).toEqual([share, share]); 156 | 157 | // Specify gravity 158 | var gravity = 48; 159 | draggable.snap = ['50%', {x: 400, y: 300, gravity: gravity}]; // base 800 x 600 160 | share = { 161 | x: parentBBox.left + 400, 162 | y: parentBBox.top + 300 163 | }; 164 | expect(props.snapTargets).toEqual([ 165 | getPointTarget(share.x, share.y, SNAP_GRAVITY, SNAP_GRAVITY), 166 | getPointTarget(share.x, share.y, gravity, gravity) 167 | ]); 168 | 169 | // corner br 170 | draggable.snap = {x: 400, y: 300, corner: 'br'}; 171 | expect(props.snapTargets).toEqual([ 172 | getPointTarget( 173 | parentBBox.left + 400 - elmWidth, 174 | parentBBox.top + 300 - elmHeight, 175 | SNAP_GRAVITY, SNAP_GRAVITY) 176 | ]); 177 | 178 | // corner tr 179 | draggable.snap = {x: 400, y: 300, corner: 'tr'}; 180 | expect(props.snapTargets).toEqual([ 181 | getPointTarget( 182 | parentBBox.left + 400 - elmWidth, 183 | parentBBox.top + 300, 184 | SNAP_GRAVITY, SNAP_GRAVITY) 185 | ]); 186 | 187 | // corner tr, bl 188 | draggable.snap = {x: 400, y: 300, corner: 'tr, bl'}; 189 | expect(props.snapTargets).toEqual([ 190 | getPointTarget( 191 | parentBBox.left + 400 - elmWidth, 192 | parentBBox.top + 300, 193 | SNAP_GRAVITY, SNAP_GRAVITY), 194 | getPointTarget( 195 | parentBBox.left + 400, 196 | parentBBox.top + 300 - elmHeight, 197 | SNAP_GRAVITY, SNAP_GRAVITY) 198 | ]); 199 | 200 | // corner all 201 | draggable.snap = {x: 400, y: 300, corner: 'all'}; // -> tl, tr, bl, br 202 | expect(props.snapTargets).toEqual([ 203 | getPointTarget( // tl 204 | parentBBox.left + 400, 205 | parentBBox.top + 300, 206 | SNAP_GRAVITY, SNAP_GRAVITY), 207 | getPointTarget( // tr 208 | parentBBox.left + 400 - elmWidth, 209 | parentBBox.top + 300, 210 | SNAP_GRAVITY, SNAP_GRAVITY), 211 | getPointTarget( // bl 212 | parentBBox.left + 400, 213 | parentBBox.top + 300 - elmHeight, 214 | SNAP_GRAVITY, SNAP_GRAVITY), 215 | getPointTarget( // br 216 | parentBBox.left + 400 - elmWidth, 217 | parentBBox.top + 300 - elmHeight, 218 | SNAP_GRAVITY, SNAP_GRAVITY) 219 | ]); 220 | 221 | // center 222 | draggable.snap = {x: 400, y: 300, corner: 'all', center: true}; // corner: all -> tr 223 | expect(props.snapTargets).toEqual([ 224 | getPointTarget( 225 | parentBBox.left + 400 - elmWidth / 2, 226 | parentBBox.top + 300 - elmHeight / 2, 227 | SNAP_GRAVITY, SNAP_GRAVITY) 228 | ]); 229 | 230 | // Outside containment (containment: 800 x 600) 231 | var minLeft = SNAP_GRAVITY, 232 | minTop = SNAP_GRAVITY; 233 | draggable.snap = {x: minLeft + 1, y: minTop + 1}; 234 | expect(props.snapTargets).toEqual([ 235 | getPointTarget( 236 | parentBBox.left + minLeft + 1, 237 | parentBBox.top + minTop + 1, 238 | SNAP_GRAVITY, SNAP_GRAVITY) 239 | ]); 240 | 241 | // Target is removed 242 | draggable.snap = {x: minLeft + 1, y: minTop - SNAP_GRAVITY - 1}; 243 | expect(props.snapTargets == null).toBe(true); 244 | expect(draggable.snap == null).toBe(false); 245 | 246 | // Point on edge: Target is not removed (gravity*Start is removed) 247 | draggable.snap = {x: minLeft + 1, y: minTop - SNAP_GRAVITY}; 248 | share = getPointTarget( 249 | parentBBox.left + minLeft + 1, 250 | parentBBox.top + minTop - SNAP_GRAVITY, 251 | SNAP_GRAVITY, SNAP_GRAVITY); 252 | delete share.gravityYStart; 253 | expect(props.snapTargets).toEqual([share]); 254 | 255 | // gravity*Start is removed 256 | draggable.snap = {x: minLeft + 1, y: minTop - 1}; 257 | share = getPointTarget( 258 | parentBBox.left + minLeft + 1, 259 | parentBBox.top + minTop - 1, 260 | SNAP_GRAVITY, SNAP_GRAVITY); 261 | delete share.gravityYStart; 262 | expect(props.snapTargets).toEqual([share]); 263 | 264 | // gravity*Start on edge: gravity*Start is removed 265 | draggable.snap = {x: minLeft + 1, y: minTop}; 266 | share = getPointTarget( 267 | parentBBox.left + minLeft + 1, 268 | parentBBox.top + minTop, 269 | SNAP_GRAVITY, SNAP_GRAVITY); 270 | delete share.gravityYStart; 271 | expect(props.snapTargets).toEqual([share]); 272 | 273 | // bl is removed 274 | draggable.snap = {x: 750, y: 300, corner: 'tr, bl'}; 275 | expect(props.snapTargets).toEqual([ 276 | getPointTarget( 277 | parentBBox.left + 750 - elmWidth, 278 | parentBBox.top + 300, 279 | SNAP_GRAVITY, SNAP_GRAVITY) 280 | ]); 281 | 282 | // tl, tr, bl are removed 283 | draggable.snap = {x: 750, y: 550, corner: 'all'}; // -> tl, tr, bl, br 284 | expect(props.snapTargets).toEqual([ 285 | getPointTarget( // br 286 | parentBBox.left + 750 - elmWidth, 287 | parentBBox.top + 550 - elmHeight, 288 | SNAP_GRAVITY, SNAP_GRAVITY) 289 | ]); 290 | 291 | done(); 292 | }); 293 | 294 | it('Line', function(done) { 295 | var parentBBox = window.getBBox(parent), 296 | share; 297 | 298 | // Parse pixels, range 299 | draggable.snap = {x: {start: 100, end: 600}, y: 300}; 300 | expect(props.snapTargets).toEqual([ 301 | getLineTarget( 302 | [ 303 | parentBBox.left + 100, 304 | parentBBox.left + 600 305 | ], 306 | parentBBox.top + 300, 307 | SNAP_GRAVITY, elmRect), 308 | getLineTarget( 309 | [ 310 | parentBBox.left + 100, 311 | parentBBox.left + 600 312 | ], 313 | parentBBox.top + 300 - elmHeight, 314 | SNAP_GRAVITY, elmRect) 315 | ]); 316 | 317 | // Parse pixels, no range 318 | draggable.snap = {y: 300}; // x: [0, 100%] -> removed 319 | expect(props.snapTargets).toEqual([ 320 | getLineTarget( 321 | null, 322 | parentBBox.top + 300, 323 | SNAP_GRAVITY, elmRect), 324 | getLineTarget( 325 | null, 326 | parentBBox.top + 300 - elmHeight, 327 | SNAP_GRAVITY, elmRect) 328 | ]); 329 | 330 | // Parse n% 331 | draggable.snap = {side: 'start', targets: [ 332 | {x: '50%', y: {start: '50%', end: '70%'}}, // base 800 x 600 333 | {x: 400, y: {start: 300, end: 420}} 334 | ]}; 335 | share = getLineTarget( 336 | parentBBox.left + 400, 337 | [parentBBox.top + 300, parentBBox.top + 420], 338 | SNAP_GRAVITY, elmRect); 339 | expect(props.snapTargets).toEqual([share, share]); 340 | 341 | // start >= end -> removed (setOptions removes it if start.isRatio === end.isRatio) 342 | draggable.snap = {x: {start: 400, end: '50%'}, y: 300}; // start === end 343 | expect(props.snapTargets == null).toBe(true); 344 | expect(draggable.snap == null).toBe(false); // setOptions didn't remove it. 345 | draggable.snap = {x: {start: 401, end: '50%'}, y: 300}; // start > end 346 | expect(props.snapTargets == null).toBe(true); 347 | expect(draggable.snap == null).toBe(false); // setOptions didn't remove it. 348 | 349 | // Specify gravity 350 | var gravity = 48; 351 | draggable.snap = {side: 'start', targets: [ 352 | {x: '50%', y: {start: '50%', end: '70%'}}, // base 800 x 600 353 | {x: 400, y: {start: 300, end: 420}, gravity: gravity} 354 | ]}; 355 | share = { 356 | x: parentBBox.left + 400, 357 | y: [parentBBox.top + 300, parentBBox.top + 420] 358 | }; 359 | expect(props.snapTargets).toEqual([ 360 | getLineTarget(share.x, share.y, SNAP_GRAVITY, elmRect), 361 | getLineTarget(share.x, share.y, gravity, elmRect) 362 | ]); 363 | 364 | // side start 365 | draggable.snap = {x: 400, y: {start: 300, end: 420}, side: 'start'}; 366 | expect(props.snapTargets).toEqual([ 367 | getLineTarget( 368 | parentBBox.left + 400, 369 | [parentBBox.top + 300, parentBBox.top + 420], 370 | SNAP_GRAVITY, elmRect) 371 | ]); 372 | 373 | // side end 374 | draggable.snap = {x: 400, y: {start: 300, end: 420}, side: 'end'}; 375 | expect(props.snapTargets).toEqual([ 376 | getLineTarget( 377 | parentBBox.left + 400 - elmWidth, 378 | [parentBBox.top + 300, parentBBox.top + 420], 379 | SNAP_GRAVITY, elmRect) 380 | ]); 381 | 382 | // side both 383 | draggable.snap = {x: 400, y: {start: 300, end: 420}, side: 'both'}; // -> start, end 384 | expect(props.snapTargets).toEqual([ 385 | getLineTarget( // start 386 | parentBBox.left + 400, 387 | [parentBBox.top + 300, parentBBox.top + 420], 388 | SNAP_GRAVITY, elmRect), 389 | getLineTarget( // end 390 | parentBBox.left + 400 - elmWidth, 391 | [parentBBox.top + 300, parentBBox.top + 420], 392 | SNAP_GRAVITY, elmRect) 393 | ]); 394 | 395 | // center 396 | draggable.snap = {x: 400, y: {start: 300, end: 420}, side: 'both', center: true}; // side: both -> start 397 | expect(props.snapTargets).toEqual([ 398 | getLineTarget( 399 | parentBBox.left + 400 - elmWidth / 2, 400 | [parentBBox.top + 300, parentBBox.top + 420], 401 | SNAP_GRAVITY, elmRect) 402 | ]); 403 | 404 | // Outside containment (containment: 800 x 600) 405 | var minLeft = SNAP_GRAVITY; 406 | draggable.snap = {x: minLeft + 1, side: 'start'}; 407 | expect(props.snapTargets).toEqual([ 408 | getLineTarget( 409 | parentBBox.left + minLeft + 1, 410 | null, 411 | SNAP_GRAVITY, elmRect) 412 | ]); 413 | 414 | // Target is removed 415 | draggable.snap = {x: minLeft - SNAP_GRAVITY - 1, side: 'start'}; 416 | expect(props.snapTargets == null).toBe(true); 417 | expect(draggable.snap == null).toBe(false); 418 | 419 | // Line on edge: Target is not removed (gravity*Start is removed) 420 | draggable.snap = {x: minLeft - SNAP_GRAVITY, side: 'start'}; 421 | share = getLineTarget( 422 | parentBBox.left + minLeft - SNAP_GRAVITY, 423 | null, 424 | SNAP_GRAVITY, elmRect); 425 | delete share.gravityXStart; 426 | expect(props.snapTargets).toEqual([share]); 427 | 428 | // gravity*Start is removed 429 | draggable.snap = {x: minLeft - 1, side: 'start'}; 430 | share = getLineTarget( 431 | parentBBox.left + minLeft - 1, 432 | null, 433 | SNAP_GRAVITY, elmRect); 434 | delete share.gravityXStart; 435 | expect(props.snapTargets).toEqual([share]); 436 | 437 | // gravity*Start on edge: gravity*Start is removed 438 | draggable.snap = {x: minLeft, side: 'start'}; 439 | share = getLineTarget( 440 | parentBBox.left + minLeft, 441 | null, 442 | SNAP_GRAVITY, elmRect); 443 | delete share.gravityXStart; 444 | expect(props.snapTargets).toEqual([share]); 445 | 446 | // start is removed 447 | draggable.snap = {x: 750, side: 'both'}; 448 | expect(props.snapTargets).toEqual([ 449 | getLineTarget( 450 | parentBBox.left + 750 - elmWidth, 451 | null, 452 | SNAP_GRAVITY, elmRect) 453 | ]); 454 | 455 | // Outside containment, range -> remove 456 | draggable.snap = {x: {start: minLeft - 400, end: minLeft - 100}, y: 300}; 457 | expect(props.snapTargets == null).toBe(true); 458 | expect(draggable.snap == null).toBe(false); // setOptions didn't remove it. 459 | 460 | done(); 461 | }); 462 | 463 | it('Step', function(done) { 464 | var parentBBox = window.getBBox(parent), 465 | share; 466 | 467 | // Parse pixels 468 | draggable.snap = {y: {step: 200}}; // x: [0, 100%] -> removed 469 | expect(props.snapTargets).toEqual([ 470 | merge(getLineTarget(null, parentBBox.top, SNAP_GRAVITY, elmRect), {gravityYStart: null}), 471 | // 0 - elmHeight: removed 472 | getLineTarget(null, parentBBox.top + 200, SNAP_GRAVITY, elmRect), 473 | getLineTarget(null, parentBBox.top + 200 - elmHeight, SNAP_GRAVITY, elmRect), 474 | getLineTarget(null, parentBBox.top + 400, SNAP_GRAVITY, elmRect), 475 | getLineTarget(null, parentBBox.top + 400 - elmHeight, SNAP_GRAVITY, elmRect), 476 | // 600: removed 477 | merge(getLineTarget(null, parentBBox.top + 600 - elmHeight, SNAP_GRAVITY, elmRect), {gravityYEnd: null}) 478 | ]); 479 | 480 | // Parse pixels, range 481 | draggable.snap = {y: {step: 150, start: 50, end: 450}}; 482 | expect(props.snapTargets).toEqual([ 483 | getLineTarget(null, parentBBox.top + 50, SNAP_GRAVITY, elmRect), 484 | // 50 - elmHeight: removed 485 | getLineTarget(null, parentBBox.top + 200, SNAP_GRAVITY, elmRect), 486 | getLineTarget(null, parentBBox.top + 200 - elmHeight, SNAP_GRAVITY, elmRect), 487 | getLineTarget(null, parentBBox.top + 350, SNAP_GRAVITY, elmRect), 488 | getLineTarget(null, parentBBox.top + 350 - elmHeight, SNAP_GRAVITY, elmRect) 489 | ]); 490 | 491 | // Vertical 492 | draggable.snap = {x: {step: 150, start: 150, end: 550}}; 493 | expect(props.snapTargets).toEqual([ 494 | getLineTarget(parentBBox.top + 150, null, SNAP_GRAVITY, elmRect), 495 | getLineTarget(parentBBox.top + 150 - elmWidth, null, SNAP_GRAVITY, elmRect), 496 | getLineTarget(parentBBox.top + 300, null, SNAP_GRAVITY, elmRect), 497 | getLineTarget(parentBBox.top + 300 - elmWidth, null, SNAP_GRAVITY, elmRect), 498 | getLineTarget(parentBBox.top + 450, null, SNAP_GRAVITY, elmRect), 499 | getLineTarget(parentBBox.top + 450 - elmWidth, null, SNAP_GRAVITY, elmRect) 500 | ]); 501 | 502 | // Reduce gravity 503 | draggable.snap = {y: {step: 30, start: 300, end: 380}, side: 'start'}; 504 | expect(props.snapTargets).toEqual([ 505 | getLineTarget(null, parentBBox.top + 300, 15, elmRect), 506 | getLineTarget(null, parentBBox.top + 330, 15, elmRect), 507 | getLineTarget(null, parentBBox.top + 360, 15, elmRect) 508 | ]); 509 | 510 | // Parse pixels, with Line range 511 | draggable.snap = {x: {start: 10, end: 300}, y: {step: 150, start: 50, end: 450}, side: 'start'}; 512 | share = [parentBBox.left + 10, parentBBox.left + 300]; 513 | expect(props.snapTargets).toEqual([ 514 | getLineTarget(share, parentBBox.top + 50, SNAP_GRAVITY, elmRect), 515 | getLineTarget(share, parentBBox.top + 200, SNAP_GRAVITY, elmRect), 516 | getLineTarget(share, parentBBox.top + 350, SNAP_GRAVITY, elmRect) 517 | ]); 518 | 519 | // Parse pixels, with Line range -> gravityXEnd is removed 520 | draggable.snap = {x: {start: 10, end: 800}, y: {step: 150, start: 50, end: 450}, side: 'start'}; 521 | share = [parentBBox.left + 10, parentBBox.left + 800]; 522 | expect(props.snapTargets).toEqual([ 523 | merge(getLineTarget(share, parentBBox.top + 50, SNAP_GRAVITY, elmRect), {gravityXEnd: null}), 524 | merge(getLineTarget(share, parentBBox.top + 200, SNAP_GRAVITY, elmRect), {gravityXEnd: null}), 525 | merge(getLineTarget(share, parentBBox.top + 350, SNAP_GRAVITY, elmRect), {gravityXEnd: null}) 526 | ]); 527 | 528 | // Step < 2px -> remove 529 | // Step === 2px 530 | draggable.snap = {x: {step: '0.25%', start: 300, end: 305}, side: 'start'}; // width: 800 -> 2px 531 | expect(props.snapTargets).toEqual([ 532 | getLineTarget(parentBBox.left + 300, null, 1, elmRect), 533 | getLineTarget(parentBBox.left + 302, null, 1, elmRect), 534 | getLineTarget(parentBBox.left + 304, null, 1, elmRect) 535 | ]); 536 | // Step < 2px -> remove 537 | draggable.snap = {x: {step: '0.24%', start: 300, end: 305}, side: 'start'}; // width: 800 -> 2px 538 | expect(props.snapTargets == null).toBe(true); 539 | expect(draggable.snap == null).toBe(false); // setOptions didn't remove it. 540 | 541 | // xStep, yStep 542 | draggable.snap = { 543 | x: {step: 100, start: 50, end: 300}, 544 | y: {step: 150, start: 50, end: 450} 545 | }; 546 | expect(props.snapTargets).toEqual([ 547 | getPointTarget(parentBBox.left + 50, parentBBox.top + 50, SNAP_GRAVITY, SNAP_GRAVITY), 548 | getPointTarget(parentBBox.left + 50, parentBBox.top + 200, SNAP_GRAVITY, SNAP_GRAVITY), 549 | getPointTarget(parentBBox.left + 50, parentBBox.top + 350, SNAP_GRAVITY, SNAP_GRAVITY), 550 | getPointTarget(parentBBox.left + 150, parentBBox.top + 50, SNAP_GRAVITY, SNAP_GRAVITY), 551 | getPointTarget(parentBBox.left + 150, parentBBox.top + 200, SNAP_GRAVITY, SNAP_GRAVITY), 552 | getPointTarget(parentBBox.left + 150, parentBBox.top + 350, SNAP_GRAVITY, SNAP_GRAVITY), 553 | getPointTarget(parentBBox.left + 250, parentBBox.top + 50, SNAP_GRAVITY, SNAP_GRAVITY), 554 | getPointTarget(parentBBox.left + 250, parentBBox.top + 200, SNAP_GRAVITY, SNAP_GRAVITY), 555 | getPointTarget(parentBBox.left + 250, parentBBox.top + 350, SNAP_GRAVITY, SNAP_GRAVITY) 556 | ]); 557 | 558 | // xStep, yStep, reduce gravity 559 | draggable.snap = { 560 | x: {step: 100, start: 50, end: 300}, 561 | y: {step: 36, start: 50, end: 130} 562 | }; 563 | expect(props.snapTargets).toEqual([ 564 | getPointTarget(parentBBox.left + 50, parentBBox.top + 50, SNAP_GRAVITY, 18), 565 | getPointTarget(parentBBox.left + 50, parentBBox.top + 86, SNAP_GRAVITY, 18), 566 | getPointTarget(parentBBox.left + 50, parentBBox.top + 122, SNAP_GRAVITY, 18), 567 | getPointTarget(parentBBox.left + 150, parentBBox.top + 50, SNAP_GRAVITY, 18), 568 | getPointTarget(parentBBox.left + 150, parentBBox.top + 86, SNAP_GRAVITY, 18), 569 | getPointTarget(parentBBox.left + 150, parentBBox.top + 122, SNAP_GRAVITY, 18), 570 | getPointTarget(parentBBox.left + 250, parentBBox.top + 50, SNAP_GRAVITY, 18), 571 | getPointTarget(parentBBox.left + 250, parentBBox.top + 86, SNAP_GRAVITY, 18), 572 | getPointTarget(parentBBox.left + 250, parentBBox.top + 122, SNAP_GRAVITY, 18) 573 | ]); 574 | 575 | done(); 576 | }); 577 | 578 | it('BBox', function(done) { 579 | var targets, 580 | parentBBox = window.getBBox(parent), 581 | bBox = { 582 | left: 170, 583 | top: 160, 584 | width: 480, 585 | height: 280 586 | }, 587 | bBox2 = {width: bBox.width, height: bBox.height, 588 | left: bBox.left - parentBBox.left, top: bBox.top - parentBBox.top}; 589 | 590 | // inside/outside 591 | draggable.snap = bBox2; 592 | targets = getBBoxTargets(bBox.left, bBox.top, bBox.width, bBox.height, SNAP_GRAVITY, elmWidth, elmHeight); 593 | expect(props.snapTargets).toEqual(targets.inside.concat(targets.outside)); 594 | 595 | // inside 596 | draggable.snap = {boundingBox: bBox2, edge: 'inside'}; 597 | targets = getBBoxTargets(bBox.left, bBox.top, bBox.width, bBox.height, SNAP_GRAVITY, elmWidth, elmHeight); 598 | expect(props.snapTargets).toEqual(targets.inside); 599 | 600 | // outside 601 | draggable.snap = {boundingBox: bBox2, edge: 'outside'}; 602 | targets = getBBoxTargets(bBox.left, bBox.top, bBox.width, bBox.height, SNAP_GRAVITY, elmWidth, elmHeight); 603 | expect(props.snapTargets).toEqual(targets.outside); 604 | 605 | // base: document 606 | draggable.snap = {boundingBox: bBox, base: 'document'}; 607 | targets = getBBoxTargets(bBox.left, bBox.top, bBox.width, bBox.height, SNAP_GRAVITY, elmWidth, elmHeight); 608 | expect(props.snapTargets).toEqual(targets.inside.concat(targets.outside)); 609 | 610 | // n% 611 | expect(parentBBox.width).toBe(800); 612 | expect(parentBBox.height).toBe(600); 613 | bBox = { 614 | left: '20%', // 160px 615 | top: '25%', // 150px 616 | width: '60%', // 480px 617 | height: '45%' // 270px 618 | }; 619 | draggable.snap = bBox; 620 | targets = getBBoxTargets(160 + parentBBox.left, 150 + parentBBox.top, 480, 270, 621 | SNAP_GRAVITY, elmWidth, elmHeight); 622 | expect(props.snapTargets).toEqual(targets.inside.concat(targets.outside)); 623 | // Change base size 624 | parent.style.width = '780px'; 625 | parent.style.height = '700px'; 626 | draggable.position(); 627 | targets = getBBoxTargets( 628 | 780 * 0.2 + parentBBox.left, 629 | 700 * 0.25 + parentBBox.top, 630 | 780 * 0.6, 700 * 0.45, 631 | SNAP_GRAVITY, elmWidth, elmHeight); 632 | expect(props.snapTargets).toEqual(targets.inside.concat(targets.outside)); 633 | parent.style.width = ''; 634 | parent.style.height = '600px'; 635 | 636 | // Invalid 637 | bBox = { // base 800 x 600 638 | left: '50%', // 400px 639 | top: '25%', 640 | right: 300, 641 | height: '45%' 642 | }; 643 | draggable.snap = bBox; 644 | expect(draggable.snap).toEqual({ // PPBBox is accepted. 645 | gravity: SNAP_GRAVITY, 646 | corner: SNAP_CORNER, 647 | side: SNAP_SIDE, 648 | center: false, 649 | edge: SNAP_EDGE, 650 | base: SNAP_BASE, 651 | targets: [{boundingBox: { 652 | left: '50%', 653 | x: '50%', 654 | top: '25%', 655 | y: '25%', 656 | right: 300, 657 | height: '45%' 658 | }}] 659 | }); 660 | expect(props.snapTargets == null).toBe(true); 661 | 662 | // base: document, n% 663 | iframe.style.width = '820px'; 664 | iBody.style.height = '680px'; 665 | bBox = { 666 | left: '22%', 667 | top: '20%', 668 | width: '45%', 669 | height: '30%' 670 | }; 671 | iDraggable.snap = {boundingBox: bBox, base: 'document'}; 672 | targets = getBBoxTargets( 673 | 820 * 0.22, 680 * 0.2, 674 | 820 * 0.45, 680 * 0.3, 675 | SNAP_GRAVITY, iElmWidth, iElmHeight); 676 | expect(iProps.snapTargets).toEqual(targets.inside.concat(targets.outside)); 677 | 678 | // Outside containment (containment: 800 x 600) 679 | var minLeft = parentBBox.left + elmWidth + SNAP_GRAVITY, 680 | minTop = parentBBox.top + elmHeight + SNAP_GRAVITY; 681 | bBox = {left: minLeft + 1, top: minTop + 1, width: 500, height: 300}; 682 | draggable.snap = {boundingBox: bBox, base: 'document'}; 683 | targets = getBBoxTargets(bBox.left, bBox.top, bBox.width, bBox.height, SNAP_GRAVITY, elmWidth, elmHeight); 684 | expect(props.snapTargets).toEqual(targets.inside.concat(targets.outside)); 685 | 686 | // Target is removed 687 | bBox = {left: minLeft + 1, top: minTop - SNAP_GRAVITY - 1, width: 500, height: 300}; 688 | draggable.snap = {boundingBox: bBox, base: 'document'}; 689 | targets = getBBoxTargets(bBox.left, bBox.top, bBox.width, bBox.height, SNAP_GRAVITY, elmWidth, elmHeight); 690 | delete targets.outside[1].gravityYStart; // Reduce left 691 | delete targets.outside[3].gravityYStart; // Reduce right 692 | targets.outside = [targets.outside[1], targets.outside[2], targets.outside[3]]; // Remove top 693 | expect(props.snapTargets).toEqual(targets.inside.concat(targets.outside)); 694 | 695 | // Line on edge: Target is not removed (gravity*Start is removed) 696 | bBox = {left: minLeft + 1, top: minTop - SNAP_GRAVITY, width: 500, height: 300}; 697 | draggable.snap = {boundingBox: bBox, base: 'document'}; 698 | targets = getBBoxTargets(bBox.left, bBox.top, bBox.width, bBox.height, SNAP_GRAVITY, elmWidth, elmHeight); 699 | delete targets.outside[0].gravityYStart; // gravityYStart of top 700 | delete targets.outside[1].gravityYStart; // Reduce left 701 | delete targets.outside[3].gravityYStart; // Reduce right 702 | expect(props.snapTargets).toEqual(targets.inside.concat(targets.outside)); 703 | 704 | // gravity*Start is removed 705 | bBox = {left: minLeft + 1, top: minTop - 1, width: 500, height: 300}; 706 | draggable.snap = {boundingBox: bBox, base: 'document'}; 707 | targets = getBBoxTargets(bBox.left, bBox.top, bBox.width, bBox.height, SNAP_GRAVITY, elmWidth, elmHeight); 708 | delete targets.outside[0].gravityYStart; // gravityYStart of top 709 | delete targets.outside[1].gravityYStart; // Reduce left 710 | delete targets.outside[3].gravityYStart; // Reduce right 711 | expect(props.snapTargets).toEqual(targets.inside.concat(targets.outside)); 712 | 713 | // gravity*Start on edge: gravity*Start is removed 714 | bBox = {left: minLeft + 1, top: minTop, width: 500, height: 300}; 715 | draggable.snap = {boundingBox: bBox, base: 'document'}; 716 | targets = getBBoxTargets(bBox.left, bBox.top, bBox.width, bBox.height, SNAP_GRAVITY, elmWidth, elmHeight); 717 | delete targets.outside[0].gravityYStart; // gravityYStart of top 718 | delete targets.outside[1].gravityYStart; // Reduce left 719 | delete targets.outside[3].gravityYStart; // Reduce right 720 | expect(props.snapTargets).toEqual(targets.inside.concat(targets.outside)); 721 | 722 | done(); 723 | }); 724 | 725 | it('Element', function(done) { 726 | var snapElm = document.body.appendChild(document.createElement('div')), 727 | left, top, width, height, targets; 728 | 729 | snapElm.style.position = 'absolute'; 730 | snapElm.style.backgroundColor = 'rgba(169, 208, 24, 0.6)'; 731 | 732 | snapElm.style.left = (left = 170) + 'px'; 733 | snapElm.style.top = (top = 160) + 'px'; 734 | snapElm.style.width = (width = 480) + 'px'; 735 | snapElm.style.height = (height = 280) + 'px'; 736 | 737 | // inside/outside 738 | draggable.snap = snapElm; 739 | targets = getBBoxTargets(left, top, width, height, SNAP_GRAVITY, elmWidth, elmHeight); 740 | expect(props.snapTargets).toEqual(targets.inside.concat(targets.outside)); 741 | 742 | // inside 743 | draggable.snap = {boundingBox: snapElm, edge: 'inside'}; 744 | targets = getBBoxTargets(left, top, width, height, SNAP_GRAVITY, elmWidth, elmHeight); 745 | expect(props.snapTargets).toEqual(targets.inside); 746 | 747 | // outside 748 | draggable.snap = {boundingBox: snapElm, edge: 'outside'}; 749 | targets = getBBoxTargets(left, top, width, height, SNAP_GRAVITY, elmWidth, elmHeight); 750 | expect(props.snapTargets).toEqual(targets.outside); 751 | 752 | // Change draggable target size 753 | var newWidth = 64; 754 | elm1.style.width = newWidth + 'px'; 755 | draggable.snap = snapElm; // elementBBox also is updated. 756 | targets = getBBoxTargets(left, top, width, height, SNAP_GRAVITY, newWidth, elmHeight); 757 | expect(props.snapTargets).toEqual(targets.inside.concat(targets.outside)); 758 | elm1.style.width = ''; 759 | 760 | // Change gravity 761 | var newGravity = 14; 762 | draggable.snap = {boundingBox: snapElm, gravity: newGravity}; 763 | targets = getBBoxTargets(left, top, width, height, newGravity, elmWidth, elmHeight); 764 | expect(props.snapTargets).toEqual(targets.inside.concat(targets.outside)); 765 | draggable.snap = snapElm; 766 | 767 | // Change snap element size 768 | snapElm.style.left = (left = 220) + 'px'; 769 | snapElm.style.top = (top = 200) + 'px'; 770 | snapElm.style.width = (width = 280) + 'px'; 771 | snapElm.style.height = (height = 220) + 'px'; 772 | draggable.position(); 773 | targets = getBBoxTargets(left, top, width, height, SNAP_GRAVITY, elmWidth, elmHeight); 774 | expect(props.snapTargets).toEqual(targets.inside.concat(targets.outside)); 775 | 776 | done(); 777 | }); 778 | 779 | }); 780 | -------------------------------------------------------------------------------- /plain-draggable.min.js: -------------------------------------------------------------------------------- 1 | /*! PlainDraggable v2.5.15 (c) anseki https://anseki.github.io/plain-draggable/ */ 2 | var PlainDraggable=function(t){var e={};function n(r){if(e[r])return e[r].exports;var o=e[r]={i:r,l:!1,exports:{}};return t[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}return n.m=t,n.c=e,n.d=function(t,e,r){n.o(t,e)||Object.defineProperty(t,e,{enumerable:!0,get:r})},n.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},n.t=function(t,e){if(1&e&&(t=n(t)),8&e)return t;if(4&e&&"object"==typeof t&&t&&t.__esModule)return t;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:t}),2&e&&"string"!=typeof t)for(var o in t)n.d(r,o,function(e){return t[e]}.bind(null,o));return r},n.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return n.d(e,"a",e),e},n.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},n.p="",n(n.s=0)}([function(t,e,n){"use strict";n.r(e);var r,o=[],i=window.requestAnimationFrame||window.mozRequestAnimationFrame||window.webkitRequestAnimationFrame||window.msRequestAnimationFrame||function(t){return setTimeout(t,1e3/60)},a=window.cancelAnimationFrame||window.mozCancelAnimationFrame||window.webkitCancelAnimationFrame||window.msCancelAnimationFrame||function(t){return clearTimeout(t)},l=Date.now();function s(){var t,e;r&&(a.call(window,r),r=null),o.forEach((function(e){var n;(n=e.event)&&(e.event=null,e.listener(n),t=!0)})),t?(l=Date.now(),e=!0):Date.now()-l<500&&(e=!0),e&&(r=i.call(window,s))}function u(t){var e=-1;return o.some((function(n,r){return n.listener===t&&(e=r,!0)})),e}var c={add:function(t){var e;return-1===u(t)?(o.push(e={listener:t}),function(t){e.event=t,r||s()}):null},remove:function(t){var e;(e=u(t))>-1&&(o.splice(e,1),!o.length&&r&&(a.call(window,r),r=null))}};function d(t,e){for(var n=0;nn.min:in.max&&(a=n.max),Ct(Bt,e,a),n.lastValue=a}n.lastFrameTime=t}}))}function Pt(){Tt.call(window,Ot),kt(),Ot=Et.call(window,Pt)}St.move=function(t,e,n){Tt.call(window,Ot),kt(),Bt===t&&(e.x&&Ht.x&&(e.x.lastValue=Ht.x.lastValue),e.y&&Ht.y&&(e.y.lastValue=Ht.y.lastValue)),Bt=t,Ht=e,Ct=n;var r=Date.now();["x","y"].forEach((function(t){var e=Ht[t];e&&(e.lastFrameTime=r)})),Ot=Et.call(window,Pt)},St.stop=function(){Tt.call(window,Ot),kt(),Ht={},Bt=null}}function It(t,e,n){return null!=n&&("x"===e?t.scrollTo(n,t.pageYOffset):t.scrollTo(t.pageXOffset,n)),"x"===e?t.pageXOffset:t.pageYOffset}function _t(t,e,n){var r="x"===e?"scrollLeft":"scrollTop";return null!=n&&(t[r]=n),t[r]}function Dt(t){return t?dt(t)?Object.keys(t).reduce((function(e,n){return e[n]=Dt(t[n]),e}),{}):Array.isArray(t)?t.map(Dt):t:t}function Xt(t,e){var n,r;return R(t)!==R(e)||(n=dt(t)?"obj":Array.isArray(t)?"array":"")!=(dt(e)?"obj":Array.isArray(e)?"array":"")||("obj"===n?Xt(r=Object.keys(t).sort(),Object.keys(e).sort())||r.some((function(n){return Xt(t[n],e[n])})):"array"===n?t.length!==e.length||t.some((function(t,n){return Xt(t,e[n])})):t!==e)}function Yt(t){return!(!t||t.nodeType!==Node.ELEMENT_NODE||"function"!=typeof t.getBoundingClientRect||t.compareDocumentPosition(document)&Node.DOCUMENT_POSITION_DISCONNECTED)}function Lt(t){if(!dt(t))return null;var e;if(!ft(e=t.left)&&!ft(e=t.x))return null;if(t.left=t.x=e,!ft(e=t.top)&&!ft(e=t.y))return null;if(t.top=t.y=e,ft(t.width)&&t.width>=0)t.right=t.left+t.width;else{if(!(ft(t.right)&&t.right>=t.left))return null;t.width=t.right-t.left}if(ft(t.height)&&t.height>=0)t.bottom=t.top+t.height;else{if(!(ft(t.bottom)&&t.bottom>=t.top))return null;t.height=t.bottom-t.top}return t}function At(t){return ft(t)?{value:t,isRatio:!1}:"string"==typeof t?function(t){var e,n,r=/^(.+?)(%)?$/.exec(t);return r&&ft(e=parseFloat(r[1]))?{value:(n=!(!r[2]||!e))?e/100:e,isRatio:n}:null}(t.replace(/\s/g,"")):null}function Ft(t){return t.isRatio?"".concat(100*t.value,"%"):t.value}function Wt(t,e,n){return"number"==typeof t?t:e+t.value*(t.isRatio?n:1)}function jt(t){if(!dt(t))return null;var e;if(!(e=At(t.left))&&!(e=At(t.x)))return null;if(t.left=t.x=e,!(e=At(t.top))&&!(e=At(t.y)))return null;if(t.top=t.y=e,(e=At(t.width))&&e.value>=0)t.width=e,delete t.right;else{if(!(e=At(t.right)))return null;t.right=e,delete t.width}if((e=At(t.height))&&e.value>=0)t.height=e,delete t.bottom;else{if(!(e=At(t.bottom)))return null;t.bottom=e,delete t.height}return t}function Rt(t){return Object.keys(t).reduce((function(e,n){return e[n]=Ft(t[n]),e}),{})}function Mt(t,e){var n={left:"x",right:"x",x:"x",width:"x",top:"y",bottom:"y",y:"y",height:"y"},r={x:e.left,y:e.top},o={x:e.width,y:e.height};return Lt(Object.keys(t).reduce((function(e,i){return e[i]=Wt(t[i],"width"===i||"height"===i?0:r[n[i]],o[n[i]]),e}),{}))}function zt(t,e){var n=t.getBoundingClientRect(),r={left:n.left,top:n.top,width:n.width,height:n.height};if(r.left+=window.pageXOffset,r.top+=window.pageYOffset,e){var o=window.getComputedStyle(t,""),i=parseFloat(o.borderTopWidth)||0,a=parseFloat(o.borderRightWidth)||0,l=parseFloat(o.borderBottomWidth)||0,s=parseFloat(o.borderLeftWidth)||0;r.left+=s,r.top+=i,r.width-=s+a,r.height-=i+l}return Lt(r)}function Nt(t,e){null==U&&(!1!==yt&&(U=D.getValue("cursor",yt)),null==U&&(U=!1)),t.style.cursor=!1===U?e:U}function Vt(t){null==Z&&(!1!==vt&&(Z=D.getValue("cursor",vt)),null==Z&&(Z=!1)),!1!==Z&&(t.style.cursor=Z)}function Gt(t,e,n){var r=t.svgPoint;return r.x=e,r.y=n,r.matrixTransform(t.svgCtmElement.getScreenCTM().inverse())}function qt(t,e){var n=t.elementBBox;if(e.left!==n.left||e.top!==n.top){var r=t.htmlOffset;return t.elementStyle[K]="translate(".concat(e.left+r.left,"px, ").concat(e.top+r.top,"px)"),!0}return!1}function Ut(t,e){var n=t.elementBBox,r=t.elementStyle,o=t.htmlOffset,i=!1;return e.left!==n.left&&(r.left=e.left+o.left+"px",i=!0),e.top!==n.top&&(r.top=e.top+o.top+"px",i=!0),i}function Zt(t,e){var n=t.elementBBox;if(e.left!==n.left||e.top!==n.top){var r=t.svgOffset,o=t.svgOriginBBox,i=Gt(t,e.left-window.pageXOffset,e.top-window.pageYOffset);return t.svgTransform.setTranslate(i.x+r.x-o.x,i.y+r.y-o.y),!0}return!1}function $t(t,e,n){var r=t.elementBBox;function o(){t.minLeft>=t.maxLeft?e.left=r.left:e.leftt.maxLeft&&(e.left=t.maxLeft),t.minTop>=t.maxTop?e.top=r.top:e.topt.maxTop&&(e.top=t.maxTop)}if(o(),n){if(!1===n(e))return!1;o()}var i=t.moveElm(t,e);return i&&(t.elementBBox=Lt({left:e.left,top:e.top,width:r.width,height:r.height})),i}function Jt(t){var e=t.element,n=t.elementStyle,r=zt(e),o=["display","marginTop","marginBottom","width","height"];o.unshift(K);var i=n[J];n[J]="none";var a=zt(e);t.orgStyle?o.forEach((function(e){null!=t.lastStyle[e]&&n[e]!==t.lastStyle[e]||(n[e]=t.orgStyle[e])})):(t.orgStyle=o.reduce((function(t,e){return t[e]=n[e]||"",t}),{}),t.lastStyle={});var l=zt(e),s=window.getComputedStyle(e,"");"inline"===s.display&&(n.display="inline-block",["Top","Bottom"].forEach((function(t){var e=parseFloat(s["padding".concat(t)]);n["margin".concat(t)]=e?"-".concat(e,"px"):"0"}))),n[K]="translate(0, 0)";var u=zt(e),c=t.htmlOffset={left:u.left?-u.left:0,top:u.top?-u.top:0};return n[K]="translate(".concat(r.left+c.left,"px, ").concat(r.top+c.top,"px)"),["width","height"].forEach((function(r){u[r]!==l[r]&&(n[r]=l[r]+"px",(u=zt(e))[r]!==l[r]&&(n[r]=l[r]-(u[r]-l[r])+"px")),t.lastStyle[r]=n[r]})),e.offsetWidth,n[J]=i,a.left===r.left&&a.top===r.top||(n[K]="translate(".concat(a.left+c.left,"px, ").concat(a.top+c.top,"px)")),a}function Kt(t){var e=t.element,n=t.elementStyle,r=zt(e),o=["position","marginTop","marginRight","marginBottom","marginLeft","width","height"],i=n[J];n[J]="none";var a=zt(e);t.orgStyle?o.forEach((function(e){null!=t.lastStyle[e]&&n[e]!==t.lastStyle[e]||(n[e]=t.orgStyle[e])})):(t.orgStyle=o.reduce((function(t,e){return t[e]=n[e]||"",t}),{}),t.lastStyle={});var l=zt(e);n.position="absolute",n.left=n.top=n.margin="0";var s=zt(e),u=t.htmlOffset={left:s.left?-s.left:0,top:s.top?-s.top:0};return n.left=r.left+u.left+"px",n.top=r.top+u.top+"px",["width","height"].forEach((function(r){s[r]!==l[r]&&(n[r]=l[r]+"px",(s=zt(e))[r]!==l[r]&&(n[r]=l[r]-(s[r]-l[r])+"px")),t.lastStyle[r]=n[r]})),e.offsetWidth,n[J]=i,a.left===r.left&&a.top===r.top||(n.left=a.left+u.left+"px",n.top=a.top+u.top+"px"),a}function Qt(t){var e=t.element,n=t.svgTransform,r=e.getBoundingClientRect(),o=zt(e);n.setTranslate(0,0);var i=t.svgOriginBBox=e.getBBox(),a=e.getBoundingClientRect(),l=Gt(t,a.left,a.top),s=t.svgOffset={x:i.x-l.x,y:i.y-l.y},u=Gt(t,r.left,r.top);return n.setTranslate(u.x+s.x-i.x,u.y+s.y-i.y),o}function te(t,e){var n=zt(document.documentElement),r=t.elementBBox=t.initElm(t),o=t.containmentBBox=t.containmentIsBBox?Mt(t.options.containment,n)||n:zt(t.options.containment,!0);if(t.minLeft=o.left,t.maxLeft=o.right-r.width,t.minTop=o.top,t.maxTop=o.bottom-r.height,$t(t,{left:r.left,top:r.top}),t.parsedSnapTargets){var i={x:r.width,y:r.height},a={x:t.minLeft,y:t.minTop},l={x:t.maxLeft,y:t.maxTop},s={left:"x",right:"x",x:"x",width:"x",xStart:"x",xEnd:"x",xStep:"x",top:"y",bottom:"y",y:"y",height:"y",yStart:"y",yEnd:"y",yStep:"y"},u=t.parsedSnapTargets.reduce((function(t,e){var u,c="containment"===e.base?o:n,d={x:c.left,y:c.top},f={x:c.width,y:c.height};function p(n){if(null==n.center&&(n.center=e.center),null==n.xGravity&&(n.xGravity=e.gravity),null==n.yGravity&&(n.yGravity=e.gravity),null!=n.x&&null!=n.y)n.x=Wt(n.x,d.x,f.x),n.y=Wt(n.y,d.y,f.y),n.center&&(n.x-=i.x/2,n.y-=i.y/2,n.corners=["tl"]),(n.corners||e.corners).forEach((function(e){var r=n.x-("tr"===e||"br"===e?i.x:0),o=n.y-("bl"===e||"br"===e?i.y:0);if(r>=a.x&&r<=l.x&&o>=a.y&&o<=l.y){var s={x:r,y:o},u=r-n.xGravity,c=r+n.xGravity,d=o-n.yGravity,f=o+n.yGravity;u>a.x&&(s.gravityXStart=u),ca.y&&(s.gravityYStart=d),fn[u]||n[s]>l[o]||n[u]=a[r]&&d<=l[r]){var f={},p=d-n[c],h=d+n[c];f[r]=d,p>a[r]&&(f[m]=p),ha[o]&&(f[y]=n[s]),n[u]=u)return a;if(null!=c){if(c<2)return a;var d=c/2;d=e.gravity>d?d:null;for(var f=s;f<=u;f+=c){var p=Object.keys(l).reduce((function(t,e){return e!==n&&e!==r&&e!==o&&(t[e]=l[e]),t}),{});p[t]=f,p[i]=d,a.push(p)}}else a.push(l);return a}),[])})),h.forEach((function(t){p(t)}))}return t}),[]);t.snapTargets=u.length?u:null}var c={},d=t.options.autoScroll;if(d){c.isWindow=d.target===window,c.target=d.target;var f="scroll"===e,p=function(t,e,n){var r,o,i,a,l={};a=e?document.documentElement:t,l.clientWidth=a.clientWidth,l.clientHeight=a.clientHeight;var s,u,c,d=0,f=0;return n||(e?(s=It(t,"x"),u=It(t,"y"),r=getComputedStyle(document.documentElement,""),o=getComputedStyle(document.body,""),d=It(t,"x",document.documentElement.scrollWidth+l.clientWidth+["marginLeft","marginRight","borderLeftWidth","borderRightWidth","paddingLeft","paddingRight"].reduce((function(t,e){return t+(parseFloat(r[e])||0)+(parseFloat(o[e])||0)}),0)),f=It(t,"y",document.documentElement.scrollHeight+l.clientHeight+["marginTop","marginBottom","borderTopWidth","borderBottomWidth","paddingTop","paddingBottom"].reduce((function(t,e){return t+(parseFloat(r[e])||0)+(parseFloat(o[e])||0)}),0)),It(t,"x",s),It(t,"y",u)):(s=_t(t,"x"),u=_t(t,"y"),i=getComputedStyle(t,""),d=_t(t,"x",t.scrollWidth+l.clientWidth+["marginLeft","marginRight","borderLeftWidth","borderRightWidth","paddingLeft","paddingRight"].reduce((function(t,e){return t+(parseFloat(i[e])||0)}),0)),f=_t(t,"y",t.scrollHeight+l.clientHeight+["marginTop","marginBottom","borderTopWidth","borderBottomWidth","paddingTop","paddingBottom"].reduce((function(t,e){return t+(parseFloat(i[e])||0)}),0)),_t(t,"x",s),_t(t,"y",u))),l.scrollWidth=l.clientWidth+d,l.scrollHeight=l.clientHeight+f,e?l.clientX=l.clientY=0:(c=t.getBoundingClientRect(),i||(i=getComputedStyle(t,"")),l.clientX=c.left+(parseFloat(i.borderLeftWidth)||0),l.clientY=c.top+(parseFloat(i.borderTopWidth)||0)),l}(d.target,c.isWindow,f),h=Lt({left:p.clientX,top:p.clientY,width:p.clientWidth,height:p.clientHeight});f?t.autoScroll&&(c.scrollWidth=t.autoScroll.scrollWidth,c.scrollHeight=t.autoScroll.scrollHeight):(c.scrollWidth=p.scrollWidth,c.scrollHeight=p.scrollHeight),[["X","Width","left","right"],["Y","Height","top","bottom"]].forEach((function(t){var e=t[0],n=t[1],o=t[2],i=t[3],a=(c["scroll".concat(n)]||0)-p["client".concat(n)],l=d["min".concat(e)]||0,s=ft(d["max".concat(e)])?d["max".concat(e)]:a;if(la&&(s=a);for(var u=[],f=r[n.toLowerCase()],m=d.sensitivity.length-1;m>=0;m--){var g=d.sensitivity[m],y=d.speed[m];u.push({dir:-1,speed:y,position:h[o]+g}),u.push({dir:1,speed:y,position:h[i]-g-f})}c[e.toLowerCase()]={min:l,max:s,lines:u}}}))}t.autoScroll=c.x||c.y?c:null}function ee(t){St.stop(),Nt(t.options.handle,t.orgCursor),q.style.cursor=$,!1!==t.options.zIndex&&(t.elementStyle.zIndex=t.orgZIndex),Q&&(q.style[Q]=tt);var e=W(t.element);bt&&e.remove(bt),wt&&e.remove(wt),V=null,mt.cancel(),t.onDragEnd&&t.onDragEnd({left:t.elementBBox.left,top:t.elementBBox.top})}function ne(t,e){var n,r,o=t.options;e.containment&&(Yt(e.containment)?e.containment!==o.containment&&(o.containment=e.containment,t.containmentIsBBox=!1,n=!0):(r=jt(Dt(e.containment)))&&Xt(r,o.containment)&&(o.containment=r,t.containmentIsBBox=!0,n=!0));function i(t,e){function n(t){return"string"==typeof t?t.replace(/[, ]+/g," ").trim().toLowerCase():null}ft(e.gravity)&&e.gravity>0&&(t.gravity=e.gravity);var r=n(e.corner);if(r){if("all"!==r){var o={},i=r.split(/\s/).reduce((function(t,e){return(e="tl"===(e=e.trim().replace(/^(.).*?-(.).*$/,"$1$2"))||"lt"===e?"tl":"tr"===e||"rt"===e?"tr":"bl"===e||"lb"===e?"bl":"br"===e||"rb"===e?"br":null)&&!o[e]&&(t.push(e),o[e]=!0),t}),[]),a=i.length;r=a?4===a?"all":i.join(" "):null}r&&(t.corner=r)}var l=n(e.side);l&&("start"===l||"end"===l||"both"===l?t.side=l:"start end"!==l&&"end start"!==l||(t.side="both")),"boolean"==typeof e.center&&(t.center=e.center);var s=n(e.edge);s&&("inside"===s||"outside"===s||"both"===s?t.edge=s:"inside outside"!==s&&"outside inside"!==s||(t.edge="both"));var u="string"==typeof e.base?e.base.trim().toLowerCase():null;return!u||"containment"!==u&&"document"!==u||(t.base=u),t}if(null!=e.snap){var a=dt(e.snap)&&null!=e.snap.targets?e.snap:{targets:e.snap},l=[],s=i({targets:l},a);s.gravity||(s.gravity=20),s.corner||(s.corner="tl"),s.side||(s.side="both"),"boolean"!=typeof s.center&&(s.center=!1),s.edge||(s.edge="both"),s.base||(s.base="containment");var u=(Array.isArray(a.targets)?a.targets:[a.targets]).reduce((function(t,e){if(null==e)return t;var n,r=Yt(e),o=jt(Dt(e)),a=r||o?{boundingBox:e}:dt(e)&&null==e.start&&null==e.end&&null==e.step?e:{x:e,y:e},u=[],c={},d=a.boundingBox;if(r||Yt(d))u.push({element:d}),c.boundingBox=d;else if(n=o||jt(Dt(d)))u.push({ppBBox:n}),c.boundingBox=Rt(n);else{var f,p=["x","y"].reduce((function(t,e){var n,r,o,i,l=a[e];(n=At(l))?(t[e]=n,c[e]=Ft(n)):(dt(l)&&(r=At(l.start),o=At(l.end),i=At(l.step),r&&o&&r.isRatio===o.isRatio&&r.value>=o.value&&(f=!0)),r=t["".concat(e,"Start")]=r||{value:0,isRatio:!1},o=t["".concat(e,"End")]=o||{value:1,isRatio:!0},c[e]={start:Ft(r),end:Ft(o)},i&&((i.isRatio?i.value>0:i.value>=2)?(t["".concat(e,"Step")]=i,c[e].step=Ft(i)):f=!0));return t}),{});if(f)return t;p.xStart&&!p.xStep&&p.yStart&&!p.yStep?u.push({xStart:p.xStart,xEnd:p.xEnd,y:p.yStart},{xStart:p.xStart,xEnd:p.xEnd,y:p.yEnd},{x:p.xStart,yStart:p.yStart,yEnd:p.yEnd},{x:p.xEnd,yStart:p.yStart,yEnd:p.yEnd}):u.push(p)}if(u.length){l.push(i(c,a));var h=c.corner||s.corner,m=c.side||s.side,g=c.edge||s.edge,y={gravity:c.gravity||s.gravity,base:c.base||s.base,center:"boolean"==typeof c.center?c.center:s.center,corners:"all"===h?et:h.split(" "),sides:"both"===m?nt:[m],edges:"both"===g?rt:[g]};u.forEach((function(e){["gravity","corners","sides","center","edges","base"].forEach((function(t){e[t]=y[t]})),t.push(e)}))}return t}),[]);u.length&&(o.snap=s,Xt(u,t.parsedSnapTargets)&&(t.parsedSnapTargets=u,n=!0))}else e.hasOwnProperty("snap")&&t.parsedSnapTargets&&(o.snap=t.parsedSnapTargets=t.snapTargets=void 0);if(e.autoScroll){var c=dt(e.autoScroll)?e.autoScroll:{target:!0===e.autoScroll?window:e.autoScroll},d={};d.target=Yt(c.target)?c.target:window,d.speed=[],(Array.isArray(c.speed)?c.speed:[c.speed]).every((function(t,e){return!!(e<=2&&ft(t))&&(d.speed[e]=t,!0)})),d.speed.length||(d.speed=ot);var f=Array.isArray(c.sensitivity)?c.sensitivity:[c.sensitivity];d.sensitivity=d.speed.map((function(t,e){return ft(f[e])?f[e]:it[e]})),["X","Y"].forEach((function(t){var e="min".concat(t),n="max".concat(t);ft(c[e])&&c[e]>=0&&(d[e]=c[e]),ft(c[n])&&c[n]>=0&&(!d[e]||c[n]>=d[e])&&(d[n]=c[n])})),Xt(d,o.autoScroll)&&(o.autoScroll=d,n=!0)}else e.hasOwnProperty("autoScroll")&&(o.autoScroll&&(n=!0),o.autoScroll=void 0);if(n&&te(t),Yt(e.handle)&&e.handle!==o.handle){o.handle&&(o.handle.style.cursor=t.orgCursor,Q&&(o.handle.style[Q]=t.orgUserSelect),mt.removeStartHandler(o.handle,t.pointerEventHandlerId));var p=o.handle=e.handle;t.orgCursor=p.style.cursor,Nt(p,t.orgCursor),Q&&(t.orgUserSelect=p.style[Q],p.style[Q]="none"),mt.addStartHandler(p,t.pointerEventHandlerId)}(ft(e.zIndex)||!1===e.zIndex)&&(o.zIndex=e.zIndex,t===V&&(t.elementStyle.zIndex=!1===o.zIndex?t.orgZIndex:o.zIndex));var h,m={left:t.elementBBox.left,top:t.elementBBox.top};ft(e.left)&&e.left!==m.left&&(m.left=e.left,h=!0),ft(e.top)&&e.top!==m.top&&(m.top=e.top,h=!0),h&&$t(t,m),["onDrag","onMove","onDragStart","onMoveStart","onDragEnd"].forEach((function(n){"function"==typeof e[n]?(o[n]=e[n],t[n]=o[n].bind(t.ins)):e.hasOwnProperty(n)&&null==e[n]&&(o[n]=t[n]=void 0)}))}var re=function(){function t(e,n){!function(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}(this,t);var r={ins:this,options:{zIndex:9e3},disabled:!1};if(Object.defineProperty(this,"_id",{value:++gt}),r._id=this._id,pt[this._id]=r,!Yt(e)||e===q)throw new Error("This element is not accepted.");if(n){if(!dt(n))throw new Error("Invalid options.")}else n={};var o,i,a=!0;if(e instanceof SVGElement&&(o=e.ownerSVGElement)){if(!e.getBBox)throw new Error("This element is not accepted. (SVGLocatable)");if(!e.transform)throw new Error("This element is not accepted. (SVGAnimatedTransformList)");r.svgTransform=e.transform.baseVal.appendItem(o.createSVGTransform()),r.svgPoint=o.createSVGPoint();var l=e.nearestViewportElement||e.viewportElement;r.svgCtmElement=st?l.appendChild(document.createElementNS(o.namespaceURI,"rect")):l,a=!1,r.initElm=Qt,r.moveElm=Zt}else{var s=D.getName("willChange");s&&(a=!1),!n.leftTop&&K?(s&&(e.style[s]="transform"),r.initElm=Jt,r.moveElm=qt):(s&&(e.style[s]="left, top"),r.initElm=Kt,r.moveElm=Ut)}(r.element=function(t,e){var n=t.style;n.webkitTapHighlightColor="transparent";var r=D.getName("boxShadow"),o=window.getComputedStyle(t,"")[r];return o&&"none"!==o||(n[r]="0 0 1px transparent"),e&&K&&(n[K]="translateZ(0)"),t}(e,a),r.elementStyle=e.style,r.orgZIndex=r.elementStyle.zIndex,xt&&W(e).add(xt),r.pointerEventHandlerId=mt.regStartHandler((function(t){return function(t,e){return!t.disabled&&((!t.onDragStart||!1!==t.onDragStart(e))&&(V&&ee(V),Vt(t.options.handle),q.style.cursor=Z||window.getComputedStyle(t.options.handle,"").cursor,!1!==t.options.zIndex&&(t.elementStyle.zIndex=t.options.zIndex),Q&&(q.style[Q]="none"),wt&&W(t.element).add(wt),V=t,G=!1,ht.left=t.elementBBox.left-(e.clientX+window.pageXOffset),ht.top=t.elementBBox.top-(e.clientY+window.pageYOffset),!0))}(r,t)})),n.containment)||(n.containment=(i=e.parentNode)&&Yt(i)?i:q);n.handle||(n.handle=e),ne(r,n)}var e,n,r;return e=t,r=[{key:"draggableCursor",get:function(){return yt},set:function(t){yt!==t&&(yt=t,U=null,Object.keys(pt).forEach((function(t){var e=pt[t];e.disabled||e===V&&!1!==Z||(Nt(e.options.handle,e.orgCursor),e===V&&(q.style.cursor=$,q.style.cursor=window.getComputedStyle(e.options.handle,"").cursor))})))}},{key:"draggingCursor",get:function(){return vt},set:function(t){vt!==t&&(vt=t,Z=null,V&&(Vt(V.options.handle),!1===Z&&(Nt(V.options.handle,V.orgCursor),q.style.cursor=$),q.style.cursor=Z||window.getComputedStyle(V.options.handle,"").cursor))}},{key:"draggableClass",get:function(){return xt},set:function(t){(t=t?t+"":void 0)!==xt&&(Object.keys(pt).forEach((function(e){var n=pt[e];if(!n.disabled){var r=W(n.element);xt&&r.remove(xt),t&&r.add(t)}})),xt=t)}},{key:"draggingClass",get:function(){return wt},set:function(t){if((t=t?t+"":void 0)!==wt){if(V){var e=W(V.element);wt&&e.remove(wt),t&&e.add(t)}wt=t}}},{key:"movingClass",get:function(){return bt},set:function(t){if((t=t?t+"":void 0)!==bt){if(V&&G){var e=W(V.element);bt&&e.remove(bt),t&&e.add(t)}bt=t}}}],(n=[{key:"remove",value:function(){var t=pt[this._id];this.disabled=!0,mt.unregStartHandler(mt.removeStartHandler(t.options.handle,t.pointerEventHandlerId)),delete pt[this._id]}},{key:"setOptions",value:function(t){return dt(t)&&ne(pt[this._id],t),this}},{key:"position",value:function(){return te(pt[this._id]),this}},{key:"disabled",get:function(){return pt[this._id].disabled},set:function(t){var e=pt[this._id];(t=!!t)!==e.disabled&&(e.disabled=t,e.disabled?(e===V&&ee(e),e.options.handle.style.cursor=e.orgCursor,Q&&(e.options.handle.style[Q]=e.orgUserSelect),xt&&W(e.element).remove(xt)):(Nt(e.options.handle,e.orgCursor),Q&&(e.options.handle.style[Q]="none"),xt&&W(e.element).add(xt)))}},{key:"element",get:function(){return pt[this._id].element}},{key:"rect",get:function(){return Dt(pt[this._id].elementBBox)}},{key:"left",get:function(){return pt[this._id].elementBBox.left},set:function(t){ne(pt[this._id],{left:t})}},{key:"top",get:function(){return pt[this._id].elementBBox.top},set:function(t){ne(pt[this._id],{top:t})}},{key:"containment",get:function(){var t=pt[this._id];return t.containmentIsBBox?Rt(t.options.containment):t.options.containment},set:function(t){ne(pt[this._id],{containment:t})}},{key:"snap",get:function(){return Dt(pt[this._id].options.snap)},set:function(t){ne(pt[this._id],{snap:t})}},{key:"autoScroll",get:function(){return Dt(pt[this._id].options.autoScroll)},set:function(t){ne(pt[this._id],{autoScroll:t})}},{key:"handle",get:function(){return pt[this._id].options.handle},set:function(t){ne(pt[this._id],{handle:t})}},{key:"zIndex",get:function(){return pt[this._id].options.zIndex},set:function(t){ne(pt[this._id],{zIndex:t})}},{key:"onDrag",get:function(){return pt[this._id].options.onDrag},set:function(t){ne(pt[this._id],{onDrag:t})}},{key:"onMove",get:function(){return pt[this._id].options.onMove},set:function(t){ne(pt[this._id],{onMove:t})}},{key:"onDragStart",get:function(){return pt[this._id].options.onDragStart},set:function(t){ne(pt[this._id],{onDragStart:t})}},{key:"onMoveStart",get:function(){return pt[this._id].options.onMoveStart},set:function(t){ne(pt[this._id],{onMoveStart:t})}},{key:"onDragEnd",get:function(){return pt[this._id].options.onDragEnd},set:function(t){ne(pt[this._id],{onDragEnd:t})}}])&&j(e.prototype,n),r&&j(e,r),t}();mt.addMoveHandler(document,(function(t){if(V){var e={left:t.clientX+window.pageXOffset+ht.left,top:t.clientY+window.pageYOffset+ht.top};if($t(V,e,V.snapTargets?function(t){var e,n=V.snapTargets.length,r=!1,o=!1;for(e=0;e=i.gravityXStart)&&(null==i.gravityXEnd||t.left<=i.gravityXEnd)&&(null==i.gravityYStart||t.top>=i.gravityYStart)&&(null==i.gravityYEnd||t.top<=i.gravityYEnd)&&(r||null==i.x||(t.left=i.x,r=!0,e=-1),o||null==i.y||(t.top=i.y,o=!0,e=-1))}return t.snapped=r||o,!V.onDrag||V.onDrag(t)}:V.onDrag)){var n={},r=V.autoScroll;if(r){var o={x:V.elementBBox.left-window.pageXOffset,y:V.elementBBox.top-window.pageYOffset};["x","y"].forEach((function(t){if(r[t]){var e=r[t].min,i=r[t].max;r[t].lines.some((function(r){return(-1===r.dir?o[t]<=r.position:o[t]>=r.position)&&(n[t]={dir:r.dir,speed:r.speed/1e3,min:e,max:i},!0)}))}}))}n.x||n.y?(St.move(r.target,n,r.isWindow?It:_t),e.autoScroll=!0):St.stop(),G||(G=!0,bt&&W(V.element).add(bt),V.onMoveStart&&V.onMoveStart(e)),V.onMove&&V.onMove(e)}}}));{function oe(){V&&ee(V)}mt.addEndHandler(document,oe),mt.addCancelHandler(document,oe)}{function ie(){J=D.getName("transitionProperty"),K=D.getName("transform"),$=q.style.cursor,(Q=D.getName("userSelect"))&&(tt=q.style[Q]);var t,e={};function n(t,e){t.initElm&&te(t,e)}var r=!1,o=c.add((function(o){r||(r=!0,V&&(n(V,o.type),mt.move(),e[V._id]=!0),clearTimeout(t),t=setTimeout((function(){var r;r=o.type,clearTimeout(t),Object.keys(pt).forEach((function(t){e[t]||n(pt[t],r)})),e={}}),200),r=!1)}));window.addEventListener("resize",o,!0),window.addEventListener("scroll",o,!0)}(q=document.body)?ie():document.addEventListener("DOMContentLoaded",(function(){q=document.body,ie()}),!0)}e.default=re}]).default; -------------------------------------------------------------------------------- /src/plain-draggable-limit.proc.js: -------------------------------------------------------------------------------- 1 | /* ================================================ 2 | DON'T MANUALLY EDIT THIS FILE 3 | ================================================ */ 4 | 5 | /* 6 | * PlainDraggable 7 | * https://anseki.github.io/plain-draggable/ 8 | * 9 | * Copyright (c) 2024 anseki 10 | * Licensed under the MIT license. 11 | */ 12 | 13 | import PointerEvent from 'pointer-event'; 14 | import CSSPrefix from 'cssprefix'; 15 | import AnimEvent from 'anim-event'; 16 | import mClassList from 'm-class-list'; 17 | mClassList.ignoreNative = true; 18 | 19 | const 20 | ZINDEX = 9000, 21 | 22 | 23 | IS_EDGE = '-ms-scroll-limit' in document.documentElement.style && 24 | '-ms-ime-align' in document.documentElement.style && !window.navigator.msPointerEnabled, 25 | IS_TRIDENT = !IS_EDGE && !!document.uniqueID, // Future Edge might support `document.uniqueID`. 26 | IS_GECKO = 'MozAppearance' in document.documentElement.style, 27 | IS_BLINK = !IS_EDGE && !IS_GECKO && // Edge has `window.chrome`, and future Gecko might have that. 28 | !!window.chrome && !!window.CSS, 29 | IS_WEBKIT = !IS_EDGE && !IS_TRIDENT && 30 | !IS_GECKO && !IS_BLINK && // Some engines support `webkit-*` properties. 31 | !window.chrome && 'WebkitAppearance' in document.documentElement.style, 32 | 33 | isObject = (() => { 34 | const toString = {}.toString, 35 | fnToString = {}.hasOwnProperty.toString, 36 | objFnString = fnToString.call(Object); 37 | return obj => { 38 | let proto, constr; 39 | return obj && toString.call(obj) === '[object Object]' && 40 | (!(proto = Object.getPrototypeOf(obj)) || 41 | (constr = proto.hasOwnProperty('constructor') && proto.constructor) && 42 | typeof constr === 'function' && fnToString.call(constr) === objFnString); 43 | }; 44 | })(), 45 | isFinite = Number.isFinite || (value => typeof value === 'number' && window.isFinite(value)), 46 | 47 | /** @type {Object.<_id: number, props>} */ 48 | insProps = {}, 49 | pointerOffset = {}, 50 | pointerEvent = new PointerEvent(); 51 | 52 | let insId = 0, 53 | activeProps, hasMoved, body, 54 | // CSS property/value 55 | cssValueDraggableCursor, cssValueDraggingCursor, cssOrgValueBodyCursor, 56 | cssPropTransitionProperty, cssPropTransform, cssPropUserSelect, cssOrgValueBodyUserSelect, 57 | // Try to set `cursor` property. 58 | cssWantedValueDraggableCursor = IS_WEBKIT ? ['all-scroll', 'move'] : ['grab', 'all-scroll', 'move'], 59 | cssWantedValueDraggingCursor = IS_WEBKIT ? 'move' : ['grabbing', 'move'], 60 | // class 61 | draggableClass = 'plain-draggable', 62 | draggingClass = 'plain-draggable-dragging', 63 | movingClass = 'plain-draggable-moving'; 64 | 65 | 66 | 67 | function copyTree(obj) { 68 | return !obj ? obj : 69 | isObject(obj) ? Object.keys(obj).reduce((copyObj, key) => { 70 | copyObj[key] = copyTree(obj[key]); 71 | return copyObj; 72 | }, {}) : 73 | Array.isArray(obj) ? obj.map(copyTree) : obj; 74 | } 75 | 76 | function hasChanged(a, b) { 77 | let typeA, keysA; 78 | return typeof a !== typeof b || 79 | (typeA = isObject(a) ? 'obj' : Array.isArray(a) ? 'array' : '') !== 80 | (isObject(b) ? 'obj' : Array.isArray(b) ? 'array' : '') || 81 | ( 82 | typeA === 'obj' 83 | ? hasChanged((keysA = Object.keys(a).sort()), Object.keys(b).sort()) || 84 | keysA.some(prop => hasChanged(a[prop], b[prop])) : 85 | typeA === 'array' 86 | ? a.length !== b.length || a.some((aVal, i) => hasChanged(aVal, b[i])) : 87 | a !== b 88 | ); 89 | } 90 | 91 | /** 92 | * @param {Element} element - A target element. 93 | * @returns {boolean} `true` if connected element. 94 | */ 95 | function isElement(element) { 96 | return !!(element && 97 | element.nodeType === Node.ELEMENT_NODE && 98 | // element instanceof HTMLElement && 99 | typeof element.getBoundingClientRect === 'function' && 100 | !(element.compareDocumentPosition(document) & Node.DOCUMENT_POSITION_DISCONNECTED)); 101 | } 102 | 103 | /** 104 | * An object that simulates `DOMRect` to indicate a bounding-box. 105 | * @typedef {Object} BBox 106 | * @property {(number|null)} left - document coordinate 107 | * @property {(number|null)} top - document coordinate 108 | * @property {(number|null)} right - document coordinate 109 | * @property {(number|null)} bottom - document coordinate 110 | * @property {(number|null)} x - Substitutes for left 111 | * @property {(number|null)} y - Substitutes for top 112 | * @property {(number|null)} width 113 | * @property {(number|null)} height 114 | */ 115 | 116 | /** 117 | * @param {Object} bBox - A target object. 118 | * @returns {(BBox|null)} A normalized `BBox`, or null if `bBox` is invalid. 119 | */ 120 | function validBBox(bBox) { 121 | if (!isObject(bBox)) { return null; } 122 | let value; 123 | if (isFinite((value = bBox.left)) || isFinite((value = bBox.x))) { 124 | bBox.left = bBox.x = value; 125 | } else { return null; } 126 | if (isFinite((value = bBox.top)) || isFinite((value = bBox.y))) { 127 | bBox.top = bBox.y = value; 128 | } else { return null; } 129 | 130 | if (isFinite(bBox.width) && bBox.width >= 0) { 131 | bBox.right = bBox.left + bBox.width; 132 | } else if (isFinite(bBox.right) && bBox.right >= bBox.left) { 133 | bBox.width = bBox.right - bBox.left; 134 | } else { 135 | return null; 136 | } 137 | if (isFinite(bBox.height) && bBox.height >= 0) { 138 | bBox.bottom = bBox.top + bBox.height; 139 | } else if (isFinite(bBox.bottom) && bBox.bottom >= bBox.top) { 140 | bBox.height = bBox.bottom - bBox.top; 141 | } else { 142 | return null; 143 | } 144 | return bBox; 145 | } 146 | 147 | /** 148 | * A value that is Pixels or Ratio 149 | * @typedef {{value: number, isRatio: boolean}} PPValue 150 | */ 151 | 152 | function validPPValue(value) { 153 | 154 | // Get PPValue from string (all `/s` were already removed) 155 | function string2PPValue(inString) { 156 | const matches = /^(.+?)(%)?$/.exec(inString); 157 | let value, isRatio; 158 | return matches && isFinite((value = parseFloat(matches[1]))) 159 | ? {value: (isRatio = !!(matches[2] && value)) ? value / 100 : value, isRatio} : null; // 0% -> 0 160 | } 161 | 162 | return isFinite(value) ? {value, isRatio: false} : 163 | typeof value === 'string' ? string2PPValue(value.replace(/\s/g, '')) : null; 164 | } 165 | 166 | function ppValue2OptionValue(ppValue) { 167 | return ppValue.isRatio ? `${ppValue.value * 100}%` : ppValue.value; 168 | } 169 | 170 | function resolvePPValue(ppValue, baseOrigin, baseSize) { 171 | return typeof ppValue === 'number' ? ppValue : 172 | baseOrigin + ppValue.value * (ppValue.isRatio ? baseSize : 1); 173 | } 174 | 175 | /** 176 | * An object that simulates BBox but properties are PPValue. 177 | * @typedef {Object} PPBBox 178 | */ 179 | 180 | /** 181 | * @param {Object} bBox - A target object. 182 | * @returns {(PPBBox|null)} A normalized `PPBBox`, or null if `bBox` is invalid. 183 | */ 184 | function validPPBBox(bBox) { 185 | if (!isObject(bBox)) { return null; } 186 | let ppValue; 187 | if ((ppValue = validPPValue(bBox.left)) || (ppValue = validPPValue(bBox.x))) { 188 | bBox.left = bBox.x = ppValue; 189 | } else { return null; } 190 | if ((ppValue = validPPValue(bBox.top)) || (ppValue = validPPValue(bBox.y))) { 191 | bBox.top = bBox.y = ppValue; 192 | } else { return null; } 193 | 194 | if ((ppValue = validPPValue(bBox.width)) && ppValue.value >= 0) { 195 | bBox.width = ppValue; 196 | delete bBox.right; 197 | } else if ((ppValue = validPPValue(bBox.right))) { 198 | bBox.right = ppValue; 199 | delete bBox.width; 200 | } else { return null; } 201 | if ((ppValue = validPPValue(bBox.height)) && ppValue.value >= 0) { 202 | bBox.height = ppValue; 203 | delete bBox.bottom; 204 | } else if ((ppValue = validPPValue(bBox.bottom))) { 205 | bBox.bottom = ppValue; 206 | delete bBox.height; 207 | } else { return null; } 208 | return bBox; 209 | } 210 | 211 | function ppBBox2OptionObject(ppBBox) { 212 | return Object.keys(ppBBox).reduce((obj, prop) => { 213 | obj[prop] = ppValue2OptionValue(ppBBox[prop]); 214 | return obj; 215 | }, {}); 216 | } 217 | 218 | // PPBBox -> BBox 219 | function resolvePPBBox(ppBBox, baseBBox) { 220 | const prop2Axis = {left: 'x', right: 'x', x: 'x', width: 'x', 221 | top: 'y', bottom: 'y', y: 'y', height: 'y'}, 222 | baseOriginXY = {x: baseBBox.left, y: baseBBox.top}, 223 | baseSizeXY = {x: baseBBox.width, y: baseBBox.height}; 224 | return validBBox(Object.keys(ppBBox).reduce((bBox, prop) => { 225 | bBox[prop] = resolvePPValue(ppBBox[prop], 226 | prop === 'width' || prop === 'height' ? 0 : baseOriginXY[prop2Axis[prop]], 227 | baseSizeXY[prop2Axis[prop]]); 228 | return bBox; 229 | }, {})); 230 | } 231 | 232 | /** 233 | * @param {Element} element - A target element. 234 | * @param {?boolean} getPaddingBox - Get padding-box instead of border-box as bounding-box. 235 | * @returns {BBox} A bounding-box of `element`. 236 | */ 237 | function getBBox(element, getPaddingBox) { 238 | const rect = element.getBoundingClientRect(), 239 | bBox = {left: rect.left, top: rect.top, width: rect.width, height: rect.height}; 240 | bBox.left += window.pageXOffset; 241 | bBox.top += window.pageYOffset; 242 | if (getPaddingBox) { 243 | const style = window.getComputedStyle(element, ''), 244 | borderTop = parseFloat(style.borderTopWidth) || 0, 245 | borderRight = parseFloat(style.borderRightWidth) || 0, 246 | borderBottom = parseFloat(style.borderBottomWidth) || 0, 247 | borderLeft = parseFloat(style.borderLeftWidth) || 0; 248 | bBox.left += borderLeft; 249 | bBox.top += borderTop; 250 | bBox.width -= borderLeft + borderRight; 251 | bBox.height -= borderTop + borderBottom; 252 | } 253 | return validBBox(bBox); 254 | } 255 | 256 | /** 257 | * Optimize an element for animation. 258 | * @param {Element} element - A target element. 259 | * @param {?boolean} gpuTrigger - Initialize for SVGElement if `true`. 260 | * @returns {Element} A target element. 261 | */ 262 | function initAnim(element, gpuTrigger) { 263 | const style = element.style; 264 | style.webkitTapHighlightColor = 'transparent'; 265 | 266 | // Only when it has no shadow 267 | const cssPropBoxShadow = CSSPrefix.getName('boxShadow'), 268 | boxShadow = window.getComputedStyle(element, '')[cssPropBoxShadow]; 269 | if (!boxShadow || boxShadow === 'none') { 270 | style[cssPropBoxShadow] = '0 0 1px transparent'; 271 | } 272 | 273 | if (gpuTrigger && cssPropTransform) { style[cssPropTransform] = 'translateZ(0)'; } 274 | return element; 275 | } 276 | 277 | function setDraggableCursor(element, orgCursor) { 278 | if (cssValueDraggableCursor == null) { 279 | if (cssWantedValueDraggableCursor !== false) { 280 | cssValueDraggableCursor = CSSPrefix.getValue('cursor', cssWantedValueDraggableCursor); 281 | } 282 | // The wanted value was denied, or changing is not wanted. 283 | if (cssValueDraggableCursor == null) { cssValueDraggableCursor = false; } 284 | } 285 | // Update it to change a state even if cssValueDraggableCursor is false. 286 | element.style.cursor = cssValueDraggableCursor === false ? orgCursor : cssValueDraggableCursor; 287 | } 288 | 289 | function setDraggingCursor(element) { 290 | if (cssValueDraggingCursor == null) { 291 | if (cssWantedValueDraggingCursor !== false) { 292 | cssValueDraggingCursor = CSSPrefix.getValue('cursor', cssWantedValueDraggingCursor); 293 | } 294 | // The wanted value was denied, or changing is not wanted. 295 | if (cssValueDraggingCursor == null) { cssValueDraggingCursor = false; } 296 | } 297 | if (cssValueDraggingCursor !== false) { element.style.cursor = cssValueDraggingCursor; } 298 | } 299 | 300 | 301 | /** 302 | * Move by `translate`. 303 | * @param {props} props - `props` of instance. 304 | * @param {{left: number, top: number}} position - New position. 305 | * @returns {boolean} `true` if it was moved. 306 | */ 307 | function moveTranslate(props, position) { 308 | const elementBBox = props.elementBBox; 309 | if (position.left !== elementBBox.left || position.top !== elementBBox.top) { 310 | const offset = props.htmlOffset; 311 | props.elementStyle[cssPropTransform] = 312 | `translate(${position.left + offset.left}px, ${position.top + offset.top}px)`; 313 | return true; 314 | } 315 | return false; 316 | } 317 | 318 | 319 | 320 | /** 321 | * Set `props.element` position. 322 | * @param {props} props - `props` of instance. 323 | * @param {{left: number, top: number}} position - New position. 324 | * @param {function} [cbCheck] - Callback that is called with valid position, cancel moving if it returns `false`. 325 | * @returns {boolean} `true` if it was moved. 326 | */ 327 | function move(props, position, cbCheck) { 328 | const elementBBox = props.elementBBox; 329 | 330 | function fix() { 331 | if (props.minLeft >= props.maxLeft) { // Disabled 332 | position.left = elementBBox.left; 333 | } else if (position.left < props.minLeft) { 334 | position.left = props.minLeft; 335 | } else if (position.left > props.maxLeft) { 336 | position.left = props.maxLeft; 337 | } 338 | if (props.minTop >= props.maxTop) { // Disabled 339 | position.top = elementBBox.top; 340 | } else if (position.top < props.minTop) { 341 | position.top = props.minTop; 342 | } else if (position.top > props.maxTop) { 343 | position.top = props.maxTop; 344 | } 345 | } 346 | 347 | fix(); 348 | if (cbCheck) { 349 | if (cbCheck(position) === false) { return false; } 350 | fix(); // Again 351 | } 352 | 353 | const moved = props.moveElm(props, position); 354 | if (moved) { // Update elementBBox 355 | props.elementBBox = validBBox({left: position.left, top: position.top, 356 | width: elementBBox.width, height: elementBBox.height}); 357 | } 358 | return moved; 359 | } 360 | 361 | /** 362 | * Initialize HTMLElement for `translate`, and get `offset` that is used by `moveTranslate`. 363 | * @param {props} props - `props` of instance. 364 | * @returns {BBox} Current BBox without animation, i.e. left/top properties. 365 | */ 366 | function initTranslate(props) { 367 | const element = props.element, 368 | elementStyle = props.elementStyle, 369 | curPosition = getBBox(element), // Get BBox before change style. 370 | RESTORE_PROPS = ['display', 'marginTop', 'marginBottom', 'width', 'height']; 371 | RESTORE_PROPS.unshift(cssPropTransform); 372 | 373 | // Reset `transition-property` every time because it might be changed frequently. 374 | const orgTransitionProperty = elementStyle[cssPropTransitionProperty]; 375 | elementStyle[cssPropTransitionProperty] = 'none'; // Disable animation 376 | const fixPosition = getBBox(element); 377 | 378 | if (!props.orgStyle) { 379 | props.orgStyle = RESTORE_PROPS.reduce((orgStyle, prop) => { 380 | orgStyle[prop] = elementStyle[prop] || ''; 381 | return orgStyle; 382 | }, {}); 383 | props.lastStyle = {}; 384 | } else { 385 | RESTORE_PROPS.forEach(prop => { 386 | // Skip this if it seems user changed it. (it can't check perfectly.) 387 | if (props.lastStyle[prop] == null || elementStyle[prop] === props.lastStyle[prop]) { 388 | elementStyle[prop] = props.orgStyle[prop]; 389 | } 390 | }); 391 | } 392 | 393 | const orgSize = getBBox(element), 394 | cmpStyle = window.getComputedStyle(element, ''); 395 | // https://www.w3.org/TR/css-transforms-1/#transformable-element 396 | if (cmpStyle.display === 'inline') { 397 | elementStyle.display = 'inline-block'; 398 | ['Top', 'Bottom'].forEach(dirProp => { 399 | const padding = parseFloat(cmpStyle[`padding${dirProp}`]); 400 | // paddingTop/Bottom make padding but don't make space -> negative margin in inline-block 401 | // marginTop/Bottom don't work in inline element -> `0` in inline-block 402 | elementStyle[`margin${dirProp}`] = padding ? `-${padding}px` : '0'; 403 | }); 404 | } 405 | elementStyle[cssPropTransform] = 'translate(0, 0)'; 406 | // Get document offset. 407 | let newBBox = getBBox(element); 408 | const offset = props.htmlOffset = 409 | {left: newBBox.left ? -newBBox.left : 0, top: newBBox.top ? -newBBox.top : 0}; // avoid `-0` 410 | 411 | // Restore position 412 | elementStyle[cssPropTransform] = 413 | `translate(${curPosition.left + offset.left}px, ${curPosition.top + offset.top}px)`; 414 | // Restore size 415 | ['width', 'height'].forEach(prop => { 416 | if (newBBox[prop] !== orgSize[prop]) { 417 | // Ignore `box-sizing` 418 | elementStyle[prop] = orgSize[prop] + 'px'; 419 | newBBox = getBBox(element); 420 | if (newBBox[prop] !== orgSize[prop]) { // Retry 421 | elementStyle[prop] = orgSize[prop] - (newBBox[prop] - orgSize[prop]) + 'px'; 422 | } 423 | } 424 | props.lastStyle[prop] = elementStyle[prop]; 425 | }); 426 | 427 | // Restore `transition-property` 428 | element.offsetWidth; /* force reflow */ // eslint-disable-line no-unused-expressions 429 | elementStyle[cssPropTransitionProperty] = orgTransitionProperty; 430 | if (fixPosition.left !== curPosition.left || fixPosition.top !== curPosition.top) { 431 | // It seems that it is moving. 432 | elementStyle[cssPropTransform] = 433 | `translate(${fixPosition.left + offset.left}px, ${fixPosition.top + offset.top}px)`; 434 | } 435 | 436 | return fixPosition; 437 | } 438 | 439 | 440 | 441 | /** 442 | * Set `elementBBox`, `containmentBBox`, `min/max``Left/Top` and `snapTargets`. 443 | * @param {props} props - `props` of instance. 444 | * @param {string} [eventType] - A type of event that kicked this method. 445 | * @returns {void} 446 | */ 447 | function initBBox(props, eventType) { // eslint-disable-line no-unused-vars 448 | const docBBox = getBBox(document.documentElement), 449 | elementBBox = props.elementBBox = props.initElm(props), // reset offset etc. 450 | containmentBBox = props.containmentBBox = 451 | props.containmentIsBBox ? (resolvePPBBox(props.options.containment, docBBox) || docBBox) : 452 | getBBox(props.options.containment, true); 453 | props.minLeft = containmentBBox.left; 454 | props.maxLeft = containmentBBox.right - elementBBox.width; 455 | props.minTop = containmentBBox.top; 456 | props.maxTop = containmentBBox.bottom - elementBBox.height; 457 | // Adjust position 458 | move(props, {left: elementBBox.left, top: elementBBox.top}); 459 | 460 | 461 | } 462 | 463 | /** 464 | * @param {props} props - `props` of instance. 465 | * @returns {void} 466 | */ 467 | function dragEnd(props) { 468 | setDraggableCursor(props.options.handle, props.orgCursor); 469 | body.style.cursor = cssOrgValueBodyCursor; 470 | 471 | if (props.options.zIndex !== false) { props.elementStyle.zIndex = props.orgZIndex; } 472 | if (cssPropUserSelect) { body.style[cssPropUserSelect] = cssOrgValueBodyUserSelect; } 473 | const classList = mClassList(props.element); 474 | if (movingClass) { classList.remove(movingClass); } 475 | if (draggingClass) { classList.remove(draggingClass); } 476 | 477 | activeProps = null; 478 | pointerEvent.cancel(); // Reset pointer (activeProps must be null because this calls endHandler) 479 | if (props.onDragEnd) { 480 | props.onDragEnd({left: props.elementBBox.left, top: props.elementBBox.top}); 481 | } 482 | } 483 | 484 | /** 485 | * @param {props} props - `props` of instance. 486 | * @param {{clientX, clientY}} pointerXY - This might be MouseEvent, Touch of TouchEvent or Object. 487 | * @returns {boolean} `true` if it started. 488 | */ 489 | function dragStart(props, pointerXY) { 490 | if (props.disabled) { return false; } 491 | if (props.onDragStart && props.onDragStart(pointerXY) === false) { return false; } 492 | if (activeProps) { dragEnd(activeProps); } // activeItem is normally null by pointerEvent.end. 493 | 494 | setDraggingCursor(props.options.handle); 495 | body.style.cursor = cssValueDraggingCursor || // If it is `false` or `''` 496 | window.getComputedStyle(props.options.handle, '').cursor; 497 | 498 | if (props.options.zIndex !== false) { props.elementStyle.zIndex = props.options.zIndex; } 499 | if (cssPropUserSelect) { body.style[cssPropUserSelect] = 'none'; } 500 | if (draggingClass) { mClassList(props.element).add(draggingClass); } 501 | 502 | activeProps = props; 503 | hasMoved = false; 504 | pointerOffset.left = props.elementBBox.left - (pointerXY.clientX + window.pageXOffset); 505 | pointerOffset.top = props.elementBBox.top - (pointerXY.clientY + window.pageYOffset); 506 | return true; 507 | } 508 | 509 | /** 510 | * @param {props} props - `props` of instance. 511 | * @param {Object} newOptions - New options. 512 | * @returns {void} 513 | */ 514 | function setOptions(props, newOptions) { 515 | const options = props.options; 516 | let needsInitBBox; 517 | 518 | // containment 519 | if (newOptions.containment) { 520 | let bBox; 521 | if (isElement(newOptions.containment)) { // Specific element 522 | if (newOptions.containment !== options.containment) { 523 | options.containment = newOptions.containment; 524 | props.containmentIsBBox = false; 525 | needsInitBBox = true; 526 | } 527 | } else if ((bBox = validPPBBox(copyTree(newOptions.containment))) && // bBox 528 | hasChanged(bBox, options.containment)) { 529 | options.containment = bBox; 530 | props.containmentIsBBox = true; 531 | needsInitBBox = true; 532 | } 533 | } 534 | 535 | 536 | 537 | if (needsInitBBox) { initBBox(props); } 538 | 539 | // handle 540 | if (isElement(newOptions.handle) && newOptions.handle !== options.handle) { 541 | if (options.handle) { // Restore 542 | options.handle.style.cursor = props.orgCursor; 543 | if (cssPropUserSelect) { options.handle.style[cssPropUserSelect] = props.orgUserSelect; } 544 | pointerEvent.removeStartHandler(options.handle, props.pointerEventHandlerId); 545 | } 546 | const handle = options.handle = newOptions.handle; 547 | props.orgCursor = handle.style.cursor; 548 | setDraggableCursor(handle, props.orgCursor); 549 | if (cssPropUserSelect) { 550 | props.orgUserSelect = handle.style[cssPropUserSelect]; 551 | handle.style[cssPropUserSelect] = 'none'; 552 | } 553 | pointerEvent.addStartHandler(handle, props.pointerEventHandlerId); 554 | } 555 | 556 | // zIndex 557 | if (isFinite(newOptions.zIndex) || newOptions.zIndex === false) { 558 | options.zIndex = newOptions.zIndex; 559 | if (props === activeProps) { 560 | props.elementStyle.zIndex = options.zIndex === false ? props.orgZIndex : options.zIndex; 561 | } 562 | } 563 | 564 | // left/top 565 | const position = {left: props.elementBBox.left, top: props.elementBBox.top}; 566 | let needsMove; 567 | if (isFinite(newOptions.left) && newOptions.left !== position.left) { 568 | position.left = newOptions.left; 569 | needsMove = true; 570 | } 571 | if (isFinite(newOptions.top) && newOptions.top !== position.top) { 572 | position.top = newOptions.top; 573 | needsMove = true; 574 | } 575 | if (needsMove) { move(props, position); } 576 | 577 | // Event listeners 578 | ['onDrag', 'onMove', 'onDragStart', 'onMoveStart', 'onDragEnd'].forEach(option => { 579 | if (typeof newOptions[option] === 'function') { 580 | options[option] = newOptions[option]; 581 | props[option] = options[option].bind(props.ins); 582 | } else if (newOptions.hasOwnProperty(option) && newOptions[option] == null) { 583 | options[option] = props[option] = void 0; 584 | } 585 | }); 586 | } 587 | 588 | class PlainDraggable { 589 | /** 590 | * Create a `PlainDraggable` instance. 591 | * @param {Element} element - Target element. 592 | * @param {Object} [options] - Options. 593 | */ 594 | constructor(element, options) { 595 | const props = { 596 | ins: this, 597 | options: { // Initial options (not default) 598 | zIndex: ZINDEX // Initial state. 599 | }, 600 | disabled: false 601 | }; 602 | 603 | Object.defineProperty(this, '_id', {value: ++insId}); 604 | props._id = this._id; 605 | insProps[this._id] = props; 606 | 607 | if (!isElement(element) || element === body) { throw new Error('This element is not accepted.'); } 608 | if (!options) { 609 | options = {}; 610 | } else if (!isObject(options)) { 611 | throw new Error('Invalid options.'); 612 | } 613 | 614 | let gpuTrigger = true; 615 | const cssPropWillChange = CSSPrefix.getName('willChange'); 616 | if (cssPropWillChange) { gpuTrigger = false; } 617 | 618 | if (!options.leftTop && cssPropTransform) { // translate 619 | if (cssPropWillChange) { element.style[cssPropWillChange] = 'transform'; } 620 | props.initElm = initTranslate; 621 | props.moveElm = moveTranslate; 622 | 623 | } else { // left and top 624 | throw new Error('`transform` is not supported.'); 625 | } 626 | 627 | props.element = initAnim(element, gpuTrigger); 628 | props.elementStyle = element.style; 629 | props.orgZIndex = props.elementStyle.zIndex; 630 | if (draggableClass) { mClassList(element).add(draggableClass); } 631 | props.pointerEventHandlerId = 632 | pointerEvent.regStartHandler(pointerXY => dragStart(props, pointerXY)); 633 | 634 | // Default options 635 | if (!options.containment) { 636 | let parent; 637 | options.containment = (parent = element.parentNode) && isElement(parent) ? parent : body; 638 | } 639 | if (!options.handle) { options.handle = element; } 640 | 641 | setOptions(props, options); 642 | } 643 | 644 | remove() { 645 | const props = insProps[this._id]; 646 | this.disabled = true; // To restore element and reset pointer 647 | pointerEvent.unregStartHandler( 648 | pointerEvent.removeStartHandler(props.options.handle, props.pointerEventHandlerId)); 649 | delete insProps[this._id]; 650 | } 651 | 652 | /** 653 | * @param {Object} options - New options. 654 | * @returns {PlainDraggable} Current instance itself. 655 | */ 656 | setOptions(options) { 657 | if (isObject(options)) { 658 | setOptions(insProps[this._id], options); 659 | } 660 | return this; 661 | } 662 | 663 | position() { 664 | initBBox(insProps[this._id]); 665 | return this; 666 | } 667 | 668 | get disabled() { 669 | return insProps[this._id].disabled; 670 | } 671 | set disabled(value) { 672 | const props = insProps[this._id]; 673 | if ((value = !!value) !== props.disabled) { 674 | props.disabled = value; 675 | if (props.disabled) { 676 | if (props === activeProps) { dragEnd(props); } 677 | props.options.handle.style.cursor = props.orgCursor; 678 | if (cssPropUserSelect) { props.options.handle.style[cssPropUserSelect] = props.orgUserSelect; } 679 | if (draggableClass) { mClassList(props.element).remove(draggableClass); } 680 | } else { 681 | setDraggableCursor(props.options.handle, props.orgCursor); 682 | if (cssPropUserSelect) { props.options.handle.style[cssPropUserSelect] = 'none'; } 683 | if (draggableClass) { mClassList(props.element).add(draggableClass); } 684 | } 685 | } 686 | } 687 | 688 | get element() { 689 | return insProps[this._id].element; 690 | } 691 | 692 | get rect() { 693 | return copyTree(insProps[this._id].elementBBox); 694 | } 695 | 696 | get left() { return insProps[this._id].elementBBox.left; } 697 | set left(value) { setOptions(insProps[this._id], {left: value}); } 698 | 699 | get top() { return insProps[this._id].elementBBox.top; } 700 | set top(value) { setOptions(insProps[this._id], {top: value}); } 701 | 702 | get containment() { 703 | const props = insProps[this._id]; 704 | return props.containmentIsBBox 705 | ? ppBBox2OptionObject(props.options.containment) : props.options.containment; 706 | } 707 | set containment(value) { setOptions(insProps[this._id], {containment: value}); } 708 | 709 | 710 | 711 | get handle() { return insProps[this._id].options.handle; } 712 | set handle(value) { setOptions(insProps[this._id], {handle: value}); } 713 | 714 | get zIndex() { return insProps[this._id].options.zIndex; } 715 | set zIndex(value) { setOptions(insProps[this._id], {zIndex: value}); } 716 | 717 | get onDrag() { return insProps[this._id].options.onDrag; } 718 | set onDrag(value) { setOptions(insProps[this._id], {onDrag: value}); } 719 | 720 | get onMove() { return insProps[this._id].options.onMove; } 721 | set onMove(value) { setOptions(insProps[this._id], {onMove: value}); } 722 | 723 | get onDragStart() { return insProps[this._id].options.onDragStart; } 724 | set onDragStart(value) { setOptions(insProps[this._id], {onDragStart: value}); } 725 | 726 | get onMoveStart() { return insProps[this._id].options.onMoveStart; } 727 | set onMoveStart(value) { setOptions(insProps[this._id], {onMoveStart: value}); } 728 | 729 | get onDragEnd() { return insProps[this._id].options.onDragEnd; } 730 | set onDragEnd(value) { setOptions(insProps[this._id], {onDragEnd: value}); } 731 | 732 | static get draggableCursor() { 733 | return cssWantedValueDraggableCursor; 734 | } 735 | static set draggableCursor(value) { 736 | if (cssWantedValueDraggableCursor !== value) { 737 | cssWantedValueDraggableCursor = value; 738 | cssValueDraggableCursor = null; // Reset 739 | Object.keys(insProps).forEach(id => { 740 | const props = insProps[id]; 741 | if (props.disabled || props === activeProps && cssValueDraggingCursor !== false) { return; } 742 | setDraggableCursor(props.options.handle, props.orgCursor); 743 | if (props === activeProps) { // Since cssValueDraggingCursor is `false`, copy cursor again. 744 | body.style.cursor = cssOrgValueBodyCursor; 745 | body.style.cursor = window.getComputedStyle(props.options.handle, '').cursor; 746 | } 747 | }); 748 | } 749 | } 750 | 751 | static get draggingCursor() { 752 | return cssWantedValueDraggingCursor; 753 | } 754 | static set draggingCursor(value) { 755 | if (cssWantedValueDraggingCursor !== value) { 756 | cssWantedValueDraggingCursor = value; 757 | cssValueDraggingCursor = null; // Reset 758 | if (activeProps) { 759 | setDraggingCursor(activeProps.options.handle); 760 | if (cssValueDraggingCursor === false) { 761 | setDraggableCursor(activeProps.options.handle, activeProps.orgCursor); // draggableCursor 762 | body.style.cursor = cssOrgValueBodyCursor; 763 | } 764 | body.style.cursor = cssValueDraggingCursor || // If it is `false` or `''` 765 | window.getComputedStyle(activeProps.options.handle, '').cursor; 766 | } 767 | } 768 | } 769 | 770 | static get draggableClass() { 771 | return draggableClass; 772 | } 773 | static set draggableClass(value) { 774 | value = value ? (value + '') : void 0; 775 | if (value !== draggableClass) { 776 | Object.keys(insProps).forEach(id => { 777 | const props = insProps[id]; 778 | if (!props.disabled) { 779 | const classList = mClassList(props.element); 780 | if (draggableClass) { classList.remove(draggableClass); } 781 | if (value) { classList.add(value); } 782 | } 783 | }); 784 | draggableClass = value; 785 | } 786 | } 787 | 788 | static get draggingClass() { 789 | return draggingClass; 790 | } 791 | static set draggingClass(value) { 792 | value = value ? (value + '') : void 0; 793 | if (value !== draggingClass) { 794 | if (activeProps) { 795 | const classList = mClassList(activeProps.element); 796 | if (draggingClass) { classList.remove(draggingClass); } 797 | if (value) { classList.add(value); } 798 | } 799 | draggingClass = value; 800 | } 801 | } 802 | 803 | static get movingClass() { 804 | return movingClass; 805 | } 806 | static set movingClass(value) { 807 | value = value ? (value + '') : void 0; 808 | if (value !== movingClass) { 809 | if (activeProps && hasMoved) { 810 | const classList = mClassList(activeProps.element); 811 | if (movingClass) { classList.remove(movingClass); } 812 | if (value) { classList.add(value); } 813 | } 814 | movingClass = value; 815 | } 816 | } 817 | } 818 | 819 | pointerEvent.addMoveHandler(document, pointerXY => { 820 | if (!activeProps) { return; } 821 | const position = { 822 | left: pointerXY.clientX + window.pageXOffset + pointerOffset.left, 823 | top: pointerXY.clientY + window.pageYOffset + pointerOffset.top 824 | }; 825 | if (move(activeProps, position, 826 | activeProps.onDrag)) { 827 | 828 | 829 | if (!hasMoved) { 830 | hasMoved = true; 831 | if (movingClass) { mClassList(activeProps.element).add(movingClass); } 832 | if (activeProps.onMoveStart) { activeProps.onMoveStart(position); } 833 | } 834 | if (activeProps.onMove) { activeProps.onMove(position); } 835 | } 836 | }); 837 | 838 | { 839 | function endHandler() { 840 | if (activeProps) { dragEnd(activeProps); } 841 | } 842 | 843 | pointerEvent.addEndHandler(document, endHandler); 844 | pointerEvent.addCancelHandler(document, endHandler); 845 | } 846 | 847 | { 848 | function initDoc() { 849 | cssPropTransitionProperty = CSSPrefix.getName('transitionProperty'); 850 | cssPropTransform = CSSPrefix.getName('transform'); 851 | cssOrgValueBodyCursor = body.style.cursor; 852 | if ((cssPropUserSelect = CSSPrefix.getName('userSelect'))) { 853 | cssOrgValueBodyUserSelect = body.style[cssPropUserSelect]; 854 | } 855 | 856 | // Init active item when layout is changed, and init others later. 857 | 858 | const LAZY_INIT_DELAY = 200; 859 | let initDoneItems = {}, 860 | lazyInitTimer; 861 | 862 | function checkInitBBox(props, eventType) { 863 | if (props.initElm) { // Easy checking for instance without errors. 864 | initBBox(props, eventType); 865 | } // eslint-disable-line brace-style 866 | } 867 | 868 | function initAll(eventType) { 869 | clearTimeout(lazyInitTimer); 870 | Object.keys(insProps).forEach(id => { 871 | if (!initDoneItems[id]) { checkInitBBox(insProps[id], eventType); } 872 | }); 873 | initDoneItems = {}; 874 | } 875 | 876 | let layoutChanging = false; // Gecko bug, multiple calling by `resize`. 877 | const layoutChange = AnimEvent.add(event => { 878 | if (layoutChanging) { 879 | return; 880 | } 881 | layoutChanging = true; 882 | 883 | if (activeProps) { 884 | checkInitBBox(activeProps, event.type); 885 | pointerEvent.move(); 886 | initDoneItems[activeProps._id] = true; 887 | } 888 | clearTimeout(lazyInitTimer); 889 | lazyInitTimer = setTimeout(() => { initAll(event.type); }, LAZY_INIT_DELAY); 890 | 891 | layoutChanging = false; 892 | }); 893 | window.addEventListener('resize', layoutChange, true); 894 | window.addEventListener('scroll', layoutChange, true); 895 | } 896 | 897 | if ((body = document.body)) { 898 | initDoc(); 899 | } else { 900 | document.addEventListener('DOMContentLoaded', () => { 901 | body = document.body; 902 | initDoc(); 903 | }, true); 904 | } 905 | } 906 | 907 | PlainDraggable.limit = true; 908 | 909 | export default PlainDraggable; 910 | --------------------------------------------------------------------------------