├── .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 |
22 |
23 |
24 |
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 |
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 |
23 |
26 |
29 |
30 |
Lorem ipsum dolor sit amet, consectetur adipiscing elit
31 |
32 |
33 |
36 |
39 |
42 |
43 |
46 |
47 |
48 |
49 |
52 |
53 |
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 | `${
66 | filelist.getSync(EXT_DIR, {
67 | filter: stats => /^[^.].*\.html$/.test(stats.name),
68 | listOf: 'fullPath'
69 | }).sort().map(fullPath => { // abs URL for '/ext' (no trailing slash)
70 | const htmlPath = `/ext/${pathUtil.relative(EXT_DIR, fullPath).replace(/\\/g, '/')}`;
71 | return `- ${htmlPath}
`;
72 | }).join('')
73 | }
`);
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 |
--------------------------------------------------------------------------------