├── README.md
├── webpack.config.js
├── Gulpfile.js
├── loaders.config.js
├── dist.min.config.js
├── dist.config.js
├── .gitignore
├── index.jsx
├── index.html
├── LICENSE
├── lib
├── normalizeWheel
│ ├── ExecutionEnvironment.js
│ ├── isEventSupported.js
│ ├── index.js
│ └── UserAgent_DEPRECATED.js
└── index.js
├── src
├── normalizeWheel
│ ├── ExecutionEnvironment.js
│ ├── isEventSupported.js
│ ├── index.js
│ └── UserAgent_DEPRECATED.js
└── index.jsx
├── index.styl
├── package.json
├── index.css
└── dist
└── react-virtual-scroller.min.js
/README.md:
--------------------------------------------------------------------------------
1 | # react-virtual-scroller
2 | A virtual scroller container for React
3 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | var webpack = require('webpack')
4 |
5 | module.exports = {
6 | entry: [
7 | './index.jsx'
8 | ],
9 | output: {
10 | publicPath: 'http://localhost:9090/assets'
11 | },
12 | module: {
13 | loaders: require('./loaders.config')
14 | },
15 | resolve: {
16 | extensions: ['', '.js', '.jsx']
17 | }
18 | }
--------------------------------------------------------------------------------
/Gulpfile.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var gulp = require('gulp')
4 | var babel = require('gulp-babel')
5 |
6 | var SRC = './src/**'
7 |
8 | gulp.task('lib', function () {
9 | return gulp.src(SRC)
10 | .pipe(babel())
11 | .pipe(gulp.dest('./lib'))
12 | });
13 |
14 | gulp.task('w', function(){
15 | gulp.watch(SRC, ['lib'])
16 | })
17 |
18 | gulp.task('watch',['lib','w'])
19 | gulp.task('default',['lib'])
--------------------------------------------------------------------------------
/loaders.config.js:
--------------------------------------------------------------------------------
1 | module.exports = [
2 | {
3 | test: /\.jsx$/,
4 | exclude: /node_modules/,
5 | loaders: [
6 | 'babel-loader'
7 | ]
8 | },
9 | {
10 | test: /\.js$/,
11 | exclude: /node_modules/,
12 | loaders: [
13 | 'babel-loader'
14 | ]
15 | },
16 | {
17 | test: /\.styl$/,
18 | loader: 'style-loader!css-loader!stylus-loader'
19 | },
20 | {
21 | test: /\.css$/,
22 | loader: 'style-loader!css-loader'
23 | }
24 | ]
--------------------------------------------------------------------------------
/dist.min.config.js:
--------------------------------------------------------------------------------
1 | var webpack = require('webpack')
2 |
3 | module.exports = {
4 | entry: './src/index.jsx',
5 | output: {
6 | path : __dirname + '/dist',
7 | libraryTarget: 'umd',
8 | library : 'Scroller',
9 | filename : 'react-virtual-scroller.min.js'
10 | },
11 | module: {
12 | loaders: require('./loaders.config')
13 | },
14 | externals: {
15 | 'react': 'React'
16 | },
17 | resolve: {
18 | // Allow to omit extensions when requiring these files
19 | extensions: ['', '.js', '.jsx']
20 | }
21 | }
--------------------------------------------------------------------------------
/dist.config.js:
--------------------------------------------------------------------------------
1 | var webpack = require('webpack')
2 |
3 | module.exports = {
4 | entry: './src/index.jsx',
5 | output: {
6 | path : __dirname + '/dist',
7 | libraryTarget: 'umd',
8 | library : 'Scroller',
9 | filename : 'react-virtual-scroller.js'
10 | },
11 | module: {
12 | loaders: require('./loaders.config')
13 | },
14 | externals: {
15 | 'react': 'React'
16 | },
17 | resolve: {
18 | // Allow to omit extensions when requiring these files
19 | extensions: ['', '.js', '.jsx']
20 | },
21 | target: 'web',
22 | process: false
23 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 |
5 | # Runtime data
6 | pids
7 | *.pid
8 | *.seed
9 |
10 | # Directory for instrumented libs generated by jscoverage/JSCover
11 | lib-cov
12 |
13 | # Coverage directory used by tools like istanbul
14 | coverage
15 |
16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
17 | .grunt
18 |
19 | # node-waf configuration
20 | .lock-wscript
21 |
22 | # Compiled binary addons (http://nodejs.org/api/addons.html)
23 | build/Release
24 |
25 | # Dependency directory
26 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
27 | node_modules
28 |
29 | .idea
--------------------------------------------------------------------------------
/index.jsx:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import React from 'react'
4 | import Scroller from './src'
5 |
6 | require('./index.styl')
7 | require('react-load-mask/index.styl')
8 |
9 | const App = class extends React.Component {
10 |
11 | render(){
12 | return
13 |
21 |
22 |
23 | }
24 |
25 | onVerticalScroll(scrollTop) {
26 | console.log(scrollTop)
27 | }
28 | }
29 |
30 | React.render(, document.getElementById('content'))
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | React DataGrid
6 |
7 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Zippy Technologies
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/lib/normalizeWheel/ExecutionEnvironment.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2015, Facebook, Inc.
3 | * All rights reserved.
4 | *
5 | * This source code is licensed under the BSD-style license found in the
6 | * LICENSE file in the root directory of this source tree. An additional grant
7 | * of patent rights can be found in the PATENTS file in the same directory.
8 | *
9 | * @providesModule ExecutionEnvironment
10 | */
11 |
12 | /*jslint evil: true */
13 |
14 | 'use strict';
15 |
16 | var canUseDOM = !!(typeof window !== 'undefined' && window.document && window.document.createElement);
17 |
18 | /**
19 | * Simple, lightweight module assisting with the detection and context of
20 | * Worker. Helps avoid circular dependencies and allows code to reason about
21 | * whether or not they are in a Worker, even if they never include the main
22 | * `ReactWorker` dependency.
23 | */
24 | var ExecutionEnvironment = {
25 |
26 | canUseDOM: canUseDOM,
27 |
28 | canUseWorkers: typeof Worker !== 'undefined',
29 |
30 | canUseEventListeners: canUseDOM && !!(window.addEventListener || window.attachEvent),
31 |
32 | canUseViewport: canUseDOM && !!window.screen,
33 |
34 | isInWorker: !canUseDOM // For now, this is true - might change in the future.
35 |
36 | };
37 |
38 | module.exports = ExecutionEnvironment;
--------------------------------------------------------------------------------
/src/normalizeWheel/ExecutionEnvironment.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2015, Facebook, Inc.
3 | * All rights reserved.
4 | *
5 | * This source code is licensed under the BSD-style license found in the
6 | * LICENSE file in the root directory of this source tree. An additional grant
7 | * of patent rights can be found in the PATENTS file in the same directory.
8 | *
9 | * @providesModule ExecutionEnvironment
10 | */
11 |
12 | /*jslint evil: true */
13 |
14 | "use strict";
15 |
16 | var canUseDOM = !!(
17 | typeof window !== 'undefined' &&
18 | window.document &&
19 | window.document.createElement
20 | );
21 |
22 | /**
23 | * Simple, lightweight module assisting with the detection and context of
24 | * Worker. Helps avoid circular dependencies and allows code to reason about
25 | * whether or not they are in a Worker, even if they never include the main
26 | * `ReactWorker` dependency.
27 | */
28 | var ExecutionEnvironment = {
29 |
30 | canUseDOM: canUseDOM,
31 |
32 | canUseWorkers: typeof Worker !== 'undefined',
33 |
34 | canUseEventListeners:
35 | canUseDOM && !!(window.addEventListener || window.attachEvent),
36 |
37 | canUseViewport: canUseDOM && !!window.screen,
38 |
39 | isInWorker: !canUseDOM // For now, this is true - might change in the future.
40 |
41 | };
42 |
43 | module.exports = ExecutionEnvironment;
--------------------------------------------------------------------------------
/index.styl:
--------------------------------------------------------------------------------
1 | @import '~stylus-normalizer/index.styl'
2 |
3 | .z-scroller
4 | overflow auto
5 | position relative
6 |
7 | //flex related
8 | display flex
9 | flex-flow column
10 | flex 1 1 auto
11 |
12 | .z-content-wrapper
13 | flex 1 1 auto
14 | display flex
15 | align-items stretch
16 | align-content flex-start
17 | justify-content flex-start
18 | flex-flow row
19 | position relative
20 | overflow hidden
21 | z-index 10
22 |
23 |
24 | .z-content-wrapper-fix
25 | z-index -1
26 | flex 1
27 |
28 | .z-vertical-scroller
29 | overflow hidden
30 | overflow-y auto
31 | right 0px
32 | top 0px
33 | width 1px
34 | flex none
35 | visibility hidden
36 |
37 | .z-horizontal-scroller
38 | height 0.1px
39 | visibility hidden
40 |
41 | .z-vertical-scrollbar
42 | overflow hidden
43 | position absolute
44 | height 100%
45 | right 0px
46 | top 0px
47 |
48 | .z-horizontal-scrollbar
49 | transform translate3d(0px, 0px, 1px)
50 | flex 0 0 auto
51 | height auto
52 | width 100%
53 | position relative
54 | left 0px
55 | bottom 0px
56 | overflow auto
57 | z-index 100
58 |
59 | &.mac-fix
60 | display flex
61 | flex-flow row
62 | align-items stretch
63 | align-content flex-start
64 | justify-content stretch
65 |
66 | //needed for mac safari
67 | .z-horizontal-scrollbar-fix
68 | flex 1
69 | overflow auto
70 |
71 | .loadmask
72 | z-index 100
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-virtual-scroller",
3 | "version": "1.1.2",
4 | "description": "A virtual scroller container for React",
5 | "main": "lib/index.js",
6 | "scripts": {
7 | "test": "make",
8 | "dev": "webpack-dev-server --progress --colors --port 9090",
9 | "build-style": "webpack --progress --colors --config build-style.config.js",
10 | "build": "npm run dist && npm run dist.min && npm run lib && npm run build-style",
11 | "dist": "webpack --progress --colors --config dist.config.js",
12 | "wdist": "webpack --watch --progress --colors --config dist.config.js",
13 | "dist.min": "webpack --progress --colors --optimize-minimize --optimize-occurence-order --optimize-dedupe --config dist.min.config.js",
14 | "lib": "gulp",
15 | "wlib": "gulp watch"
16 | },
17 | "repository": {
18 | "type": "git",
19 | "url": "https://github.com/zippyui/react-virtual-scroller.git"
20 | },
21 | "keywords": [
22 | "react",
23 | "react-component",
24 | "scroller",
25 | "scroll",
26 | "container",
27 | "virtual",
28 | "scrolling"
29 | ],
30 | "author": "ZippyUI ",
31 | "license": "MIT",
32 | "bugs": {
33 | "url": "https://github.com/zippyui/react-virtual-scroller/issues"
34 | },
35 | "homepage": "https://github.com/zippyui/react-virtual-scroller",
36 | "devDependencies": {
37 | "babel": "^5.1.13",
38 | "babel-core": "^5.1.13",
39 | "babel-loader": "^5.0.0",
40 | "gulp": "^3.8.11",
41 | "gulp-babel": "^5.1.0",
42 | "webpack": "^1.8.10",
43 | "webpack-dev-server": "^1.8.2",
44 | "style-loader": "^0.12.1",
45 | "css-loader": "^0.12.0",
46 | "stylus-loader": "^1.1.0",
47 | "extract-text-webpack-plugin": "^0.7.0"
48 | },
49 | "dependencies": {
50 | "drag-helper": "^1.2.3",
51 | "has-touch": "^1.0.1",
52 | "object-assign": "^2.0.0",
53 | "react-class": "^1.1.2",
54 | "react-load-mask": "^1.0.1",
55 | "react-style-normalizer": "^1.2.7",
56 | "stylus-normalizer": "^1.0.1"
57 | },
58 | "peerDependencies": {
59 | "react": ">=0.13.0"
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/lib/normalizeWheel/isEventSupported.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2015, Facebook, Inc.
3 | * All rights reserved.
4 | *
5 | * This source code is licensed under the BSD-style license found in the
6 | * LICENSE file in the root directory of this source tree. An additional grant
7 | * of patent rights can be found in the PATENTS file in the same directory.
8 | *
9 | * @providesModule isEventSupported
10 | */
11 |
12 | 'use strict';
13 |
14 | var ExecutionEnvironment = require('./ExecutionEnvironment');
15 |
16 | var useHasFeature;
17 | if (ExecutionEnvironment.canUseDOM) {
18 | useHasFeature = document.implementation && document.implementation.hasFeature &&
19 | // always returns true in newer browsers as per the standard.
20 | // @see http://dom.spec.whatwg.org/#dom-domimplementation-hasfeature
21 | document.implementation.hasFeature('', '') !== true;
22 | }
23 |
24 | /**
25 | * Checks if an event is supported in the current execution environment.
26 | *
27 | * NOTE: This will not work correctly for non-generic events such as `change`,
28 | * `reset`, `load`, `error`, and `select`.
29 | *
30 | * Borrows from Modernizr.
31 | *
32 | * @param {string} eventNameSuffix Event name, e.g. "click".
33 | * @param {?boolean} capture Check if the capture phase is supported.
34 | * @return {boolean} True if the event is supported.
35 | * @internal
36 | * @license Modernizr 3.0.0pre (Custom Build) | MIT
37 | */
38 | function isEventSupported(eventNameSuffix, capture) {
39 | if (!ExecutionEnvironment.canUseDOM || capture && !('addEventListener' in document)) {
40 | return false;
41 | }
42 |
43 | var eventName = 'on' + eventNameSuffix;
44 | var isSupported = (eventName in document);
45 |
46 | if (!isSupported) {
47 | var element = document.createElement('div');
48 | element.setAttribute(eventName, 'return;');
49 | isSupported = typeof element[eventName] === 'function';
50 | }
51 |
52 | if (!isSupported && useHasFeature && eventNameSuffix === 'wheel') {
53 | // This is the only way to test support for the `wheel` event in IE9+.
54 | isSupported = document.implementation.hasFeature('Events.wheel', '3.0');
55 | }
56 |
57 | return isSupported;
58 | }
59 |
60 | module.exports = isEventSupported;
--------------------------------------------------------------------------------
/src/normalizeWheel/isEventSupported.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2015, Facebook, Inc.
3 | * All rights reserved.
4 | *
5 | * This source code is licensed under the BSD-style license found in the
6 | * LICENSE file in the root directory of this source tree. An additional grant
7 | * of patent rights can be found in the PATENTS file in the same directory.
8 | *
9 | * @providesModule isEventSupported
10 | */
11 |
12 | 'use strict';
13 |
14 | var ExecutionEnvironment = require('./ExecutionEnvironment');
15 |
16 | var useHasFeature;
17 | if (ExecutionEnvironment.canUseDOM) {
18 | useHasFeature =
19 | document.implementation &&
20 | document.implementation.hasFeature &&
21 | // always returns true in newer browsers as per the standard.
22 | // @see http://dom.spec.whatwg.org/#dom-domimplementation-hasfeature
23 | document.implementation.hasFeature('', '') !== true;
24 | }
25 |
26 | /**
27 | * Checks if an event is supported in the current execution environment.
28 | *
29 | * NOTE: This will not work correctly for non-generic events such as `change`,
30 | * `reset`, `load`, `error`, and `select`.
31 | *
32 | * Borrows from Modernizr.
33 | *
34 | * @param {string} eventNameSuffix Event name, e.g. "click".
35 | * @param {?boolean} capture Check if the capture phase is supported.
36 | * @return {boolean} True if the event is supported.
37 | * @internal
38 | * @license Modernizr 3.0.0pre (Custom Build) | MIT
39 | */
40 | function isEventSupported(eventNameSuffix, capture) {
41 | if (!ExecutionEnvironment.canUseDOM ||
42 | capture && !('addEventListener' in document)) {
43 | return false;
44 | }
45 |
46 | var eventName = 'on' + eventNameSuffix;
47 | var isSupported = eventName in document;
48 |
49 | if (!isSupported) {
50 | var element = document.createElement('div');
51 | element.setAttribute(eventName, 'return;');
52 | isSupported = typeof element[eventName] === 'function';
53 | }
54 |
55 | if (!isSupported && useHasFeature && eventNameSuffix === 'wheel') {
56 | // This is the only way to test support for the `wheel` event in IE9+.
57 | isSupported = document.implementation.hasFeature('Events.wheel', '3.0');
58 | }
59 |
60 | return isSupported;
61 | }
62 |
63 | module.exports = isEventSupported;
--------------------------------------------------------------------------------
/index.css:
--------------------------------------------------------------------------------
1 | .z-scroller {
2 | overflow: auto;
3 | position: relative;
4 | display: -webkit-box;
5 | display: -moz-box;
6 | display: -ms-flexbox;
7 | display: -webkit-flex;
8 | display: flex;
9 | flex-direction: column;
10 | -webkit-flex-flow: column;
11 | -moz-flex-flow: column;
12 | -ms-flex-flow: column;
13 | -o-flex-flow: column;
14 | flex-flow: column;
15 | flex-flow: column;
16 | box-orient: vertical;
17 | -webkit-box-flex: 1 1 auto;
18 | -moz-box-flex: 1 1 auto;
19 | -ms-box-flex: 1 1 auto;
20 | -ms-flex: 1 1 auto;
21 | -webkit-flex: 1 1 auto;
22 | flex: 1 1 auto;
23 | }
24 | .z-scroller .z-content-wrapper {
25 | -webkit-box-flex: 1 1 auto;
26 | -moz-box-flex: 1 1 auto;
27 | -ms-box-flex: 1 1 auto;
28 | -ms-flex: 1 1 auto;
29 | -webkit-flex: 1 1 auto;
30 | flex: 1 1 auto;
31 | display: -webkit-box;
32 | display: -moz-box;
33 | display: -ms-flexbox;
34 | display: -webkit-flex;
35 | display: flex;
36 | align-items: stretch;
37 | -webkit-align-items: stretch;
38 | align-content: flex-start;
39 | -webkit-align-content: flex-start;
40 | justify-content: flex-start;
41 | -webkit-justify-content: flex-start;
42 | flex-pack: start;
43 | -ms-flex-pack: start;
44 | flex-direction: row;
45 | -webkit-flex-flow: row;
46 | -moz-flex-flow: row;
47 | -ms-flex-flow: row;
48 | -o-flex-flow: row;
49 | flex-flow: row;
50 | flex-flow: row;
51 | box-orient: horizontal;
52 | position: relative;
53 | overflow: hidden;
54 | z-index: 10;
55 | }
56 | .z-scroller .z-content-wrapper-fix {
57 | z-index: -1;
58 | -webkit-box-flex: 1;
59 | -moz-box-flex: 1;
60 | -ms-box-flex: 1;
61 | -ms-flex: 1;
62 | -webkit-flex: 1;
63 | flex: 1;
64 | }
65 | .z-vertical-scroller {
66 | overflow: hidden;
67 | overflow-y: auto;
68 | right: 0px;
69 | top: 0px;
70 | width: 1px;
71 | -webkit-box-flex: none;
72 | -moz-box-flex: none;
73 | -ms-box-flex: none;
74 | -ms-flex: none;
75 | -webkit-flex: none;
76 | flex: none;
77 | visibility: hidden;
78 | }
79 | .z-horizontal-scroller {
80 | height: 0.1px;
81 | visibility: hidden;
82 | }
83 | .z-vertical-scrollbar {
84 | overflow: hidden;
85 | position: absolute;
86 | height: 100%;
87 | right: 0px;
88 | top: 0px;
89 | }
90 | .z-horizontal-scrollbar {
91 | transform: translate3d(0px, 0px, 1px);
92 | -webkit-box-flex: 0 0 auto;
93 | -moz-box-flex: 0 0 auto;
94 | -ms-box-flex: 0 0 auto;
95 | -ms-flex: 0 0 auto;
96 | -webkit-flex: 0 0 auto;
97 | flex: 0 0 auto;
98 | height: auto;
99 | width: 100%;
100 | position: relative;
101 | left: 0px;
102 | bottom: 0px;
103 | overflow: auto;
104 | z-index: 100;
105 | }
106 | .z-horizontal-scrollbar.mac-fix {
107 | display: -webkit-box;
108 | display: -moz-box;
109 | display: -ms-flexbox;
110 | display: -webkit-flex;
111 | display: flex;
112 | flex-direction: row;
113 | -webkit-flex-flow: row;
114 | -moz-flex-flow: row;
115 | -ms-flex-flow: row;
116 | -o-flex-flow: row;
117 | flex-flow: row;
118 | flex-flow: row;
119 | box-orient: horizontal;
120 | align-items: stretch;
121 | -webkit-align-items: stretch;
122 | align-content: flex-start;
123 | -webkit-align-content: flex-start;
124 | justify-content: stretch;
125 | -webkit-justify-content: stretch;
126 | flex-pack: justify;
127 | -ms-flex-pack: justify;
128 | }
129 | .z-horizontal-scrollbar-fix {
130 | -webkit-box-flex: 1;
131 | -moz-box-flex: 1;
132 | -ms-box-flex: 1;
133 | -ms-flex: 1;
134 | -webkit-flex: 1;
135 | flex: 1;
136 | overflow: auto;
137 | }
138 | .loadmask {
139 | z-index: 100;
140 | }
141 |
--------------------------------------------------------------------------------
/src/normalizeWheel/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2015, Facebook, Inc.
3 | * All rights reserved.
4 | *
5 | * This source code is licensed under the BSD-style license found in the
6 | * LICENSE file in the root directory of this source tree. An additional grant
7 | * of patent rights can be found in the PATENTS file in the same directory.
8 | *
9 | * @providesModule normalizeWheel
10 | * @typechecks
11 | */
12 |
13 | "use strict";
14 |
15 | var UserAgent_DEPRECATED = require('./UserAgent_DEPRECATED')
16 | var isEventSupported = require('./isEventSupported')
17 |
18 |
19 | // Reasonable defaults
20 | var PIXEL_STEP = 10;
21 | var LINE_HEIGHT = 40;
22 | var PAGE_HEIGHT = 800;
23 |
24 | /**
25 | * Mouse wheel (and 2-finger trackpad) support on the web sucks. It is
26 | * complicated, thus this doc is long and (hopefully) detailed enough to answer
27 | * your questions.
28 | *
29 | * If you need to react to the mouse wheel in a predictable way, this code is
30 | * like your bestest friend. * hugs *
31 | *
32 | * As of today, there are 4 DOM event types you can listen to:
33 | *
34 | * 'wheel' -- Chrome(31+), FF(17+), IE(9+)
35 | * 'mousewheel' -- Chrome, IE(6+), Opera, Safari
36 | * 'MozMousePixelScroll' -- FF(3.5 only!) (2010-2013) -- don't bother!
37 | * 'DOMMouseScroll' -- FF(0.9.7+) since 2003
38 | *
39 | * So what to do? The is the best:
40 | *
41 | * normalizeWheel.getEventType();
42 | *
43 | * In your event callback, use this code to get sane interpretation of the
44 | * deltas. This code will return an object with properties:
45 | *
46 | * spinX -- normalized spin speed (use for zoom) - x plane
47 | * spinY -- " - y plane
48 | * pixelX -- normalized distance (to pixels) - x plane
49 | * pixelY -- " - y plane
50 | *
51 | * Wheel values are provided by the browser assuming you are using the wheel to
52 | * scroll a web page by a number of lines or pixels (or pages). Values can vary
53 | * significantly on different platforms and browsers, forgetting that you can
54 | * scroll at different speeds. Some devices (like trackpads) emit more events
55 | * at smaller increments with fine granularity, and some emit massive jumps with
56 | * linear speed or acceleration.
57 | *
58 | * This code does its best to normalize the deltas for you:
59 | *
60 | * - spin is trying to normalize how far the wheel was spun (or trackpad
61 | * dragged). This is super useful for zoom support where you want to
62 | * throw away the chunky scroll steps on the PC and make those equal to
63 | * the slow and smooth tiny steps on the Mac. Key data: This code tries to
64 | * resolve a single slow step on a wheel to 1.
65 | *
66 | * - pixel is normalizing the desired scroll delta in pixel units. You'll
67 | * get the crazy differences between browsers, but at least it'll be in
68 | * pixels!
69 | *
70 | * - positive value indicates scrolling DOWN/RIGHT, negative UP/LEFT. This
71 | * should translate to positive value zooming IN, negative zooming OUT.
72 | * This matches the newer 'wheel' event.
73 | *
74 | * Why are there spinX, spinY (or pixels)?
75 | *
76 | * - spinX is a 2-finger side drag on the trackpad, and a shift + wheel turn
77 | * with a mouse. It results in side-scrolling in the browser by default.
78 | *
79 | * - spinY is what you expect -- it's the classic axis of a mouse wheel.
80 | *
81 | * - I dropped spinZ/pixelZ. It is supported by the DOM 3 'wheel' event and
82 | * probably is by browsers in conjunction with fancy 3D controllers .. but
83 | * you know.
84 | *
85 | * Implementation info:
86 | *
87 | * Examples of 'wheel' event if you scroll slowly (down) by one step with an
88 | * average mouse:
89 | *
90 | * OS X + Chrome (mouse) - 4 pixel delta (wheelDelta -120)
91 | * OS X + Safari (mouse) - N/A pixel delta (wheelDelta -12)
92 | * OS X + Firefox (mouse) - 0.1 line delta (wheelDelta N/A)
93 | * Win8 + Chrome (mouse) - 100 pixel delta (wheelDelta -120)
94 | * Win8 + Firefox (mouse) - 3 line delta (wheelDelta -120)
95 | *
96 | * On the trackpad:
97 | *
98 | * OS X + Chrome (trackpad) - 2 pixel delta (wheelDelta -6)
99 | * OS X + Firefox (trackpad) - 1 pixel delta (wheelDelta N/A)
100 | *
101 | * On other/older browsers.. it's more complicated as there can be multiple and
102 | * also missing delta values.
103 | *
104 | * The 'wheel' event is more standard:
105 | *
106 | * http://www.w3.org/TR/DOM-Level-3-Events/#events-wheelevents
107 | *
108 | * The basics is that it includes a unit, deltaMode (pixels, lines, pages), and
109 | * deltaX, deltaY and deltaZ. Some browsers provide other values to maintain
110 | * backward compatibility with older events. Those other values help us
111 | * better normalize spin speed. Example of what the browsers provide:
112 | *
113 | * | event.wheelDelta | event.detail
114 | * ------------------+------------------+--------------
115 | * Safari v5/OS X | -120 | 0
116 | * Safari v5/Win7 | -120 | 0
117 | * Chrome v17/OS X | -120 | 0
118 | * Chrome v17/Win7 | -120 | 0
119 | * IE9/Win7 | -120 | undefined
120 | * Firefox v4/OS X | undefined | 1
121 | * Firefox v4/Win7 | undefined | 3
122 | *
123 | */
124 | function normalizeWheel(/*object*/ event) /*object*/ {
125 | var sX = 0, sY = 0, // spinX, spinY
126 | pX = 0, pY = 0; // pixelX, pixelY
127 |
128 | // Legacy
129 | if ('detail' in event) { sY = event.detail; }
130 | if ('wheelDelta' in event) { sY = -event.wheelDelta / 120; }
131 | if ('wheelDeltaY' in event) { sY = -event.wheelDeltaY / 120; }
132 | if ('wheelDeltaX' in event) { sX = -event.wheelDeltaX / 120; }
133 |
134 | // side scrolling on FF with DOMMouseScroll
135 | if ( 'axis' in event && event.axis === event.HORIZONTAL_AXIS ) {
136 | sX = sY;
137 | sY = 0;
138 | }
139 |
140 | pX = sX * PIXEL_STEP;
141 | pY = sY * PIXEL_STEP;
142 |
143 | if ('deltaY' in event) { pY = event.deltaY; }
144 | if ('deltaX' in event) { pX = event.deltaX; }
145 |
146 | if ((pX || pY) && event.deltaMode) {
147 | if (event.deltaMode == 1) { // delta in LINE units
148 | pX *= LINE_HEIGHT;
149 | pY *= LINE_HEIGHT;
150 | } else { // delta in PAGE units
151 | pX *= PAGE_HEIGHT;
152 | pY *= PAGE_HEIGHT;
153 | }
154 | }
155 |
156 | // Fall-back if spin cannot be determined
157 | if (pX && !sX) { sX = (pX < 1) ? -1 : 1; }
158 | if (pY && !sY) { sY = (pY < 1) ? -1 : 1; }
159 |
160 | return { spinX : sX,
161 | spinY : sY,
162 | pixelX : pX,
163 | pixelY : pY };
164 | }
165 |
166 |
167 | /**
168 | * The best combination if you prefer spinX + spinY normalization. It favors
169 | * the older DOMMouseScroll for Firefox, as FF does not include wheelDelta with
170 | * 'wheel' event, making spin speed determination impossible.
171 | */
172 | normalizeWheel.getEventType = function() /*string*/ {
173 | return (UserAgent_DEPRECATED.firefox())
174 | ? 'DOMMouseScroll'
175 | : (isEventSupported('wheel'))
176 | ? 'wheel'
177 | : 'mousewheel';
178 | };
179 |
180 | module.exports = normalizeWheel;
--------------------------------------------------------------------------------
/lib/normalizeWheel/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2015, Facebook, Inc.
3 | * All rights reserved.
4 | *
5 | * This source code is licensed under the BSD-style license found in the
6 | * LICENSE file in the root directory of this source tree. An additional grant
7 | * of patent rights can be found in the PATENTS file in the same directory.
8 | *
9 | * @providesModule normalizeWheel
10 | * @typechecks
11 | */
12 |
13 | 'use strict';
14 |
15 | var UserAgent_DEPRECATED = require('./UserAgent_DEPRECATED');
16 | var isEventSupported = require('./isEventSupported');
17 |
18 | // Reasonable defaults
19 | var PIXEL_STEP = 10;
20 | var LINE_HEIGHT = 40;
21 | var PAGE_HEIGHT = 800;
22 |
23 | /**
24 | * Mouse wheel (and 2-finger trackpad) support on the web sucks. It is
25 | * complicated, thus this doc is long and (hopefully) detailed enough to answer
26 | * your questions.
27 | *
28 | * If you need to react to the mouse wheel in a predictable way, this code is
29 | * like your bestest friend. * hugs *
30 | *
31 | * As of today, there are 4 DOM event types you can listen to:
32 | *
33 | * 'wheel' -- Chrome(31+), FF(17+), IE(9+)
34 | * 'mousewheel' -- Chrome, IE(6+), Opera, Safari
35 | * 'MozMousePixelScroll' -- FF(3.5 only!) (2010-2013) -- don't bother!
36 | * 'DOMMouseScroll' -- FF(0.9.7+) since 2003
37 | *
38 | * So what to do? The is the best:
39 | *
40 | * normalizeWheel.getEventType();
41 | *
42 | * In your event callback, use this code to get sane interpretation of the
43 | * deltas. This code will return an object with properties:
44 | *
45 | * spinX -- normalized spin speed (use for zoom) - x plane
46 | * spinY -- " - y plane
47 | * pixelX -- normalized distance (to pixels) - x plane
48 | * pixelY -- " - y plane
49 | *
50 | * Wheel values are provided by the browser assuming you are using the wheel to
51 | * scroll a web page by a number of lines or pixels (or pages). Values can vary
52 | * significantly on different platforms and browsers, forgetting that you can
53 | * scroll at different speeds. Some devices (like trackpads) emit more events
54 | * at smaller increments with fine granularity, and some emit massive jumps with
55 | * linear speed or acceleration.
56 | *
57 | * This code does its best to normalize the deltas for you:
58 | *
59 | * - spin is trying to normalize how far the wheel was spun (or trackpad
60 | * dragged). This is super useful for zoom support where you want to
61 | * throw away the chunky scroll steps on the PC and make those equal to
62 | * the slow and smooth tiny steps on the Mac. Key data: This code tries to
63 | * resolve a single slow step on a wheel to 1.
64 | *
65 | * - pixel is normalizing the desired scroll delta in pixel units. You'll
66 | * get the crazy differences between browsers, but at least it'll be in
67 | * pixels!
68 | *
69 | * - positive value indicates scrolling DOWN/RIGHT, negative UP/LEFT. This
70 | * should translate to positive value zooming IN, negative zooming OUT.
71 | * This matches the newer 'wheel' event.
72 | *
73 | * Why are there spinX, spinY (or pixels)?
74 | *
75 | * - spinX is a 2-finger side drag on the trackpad, and a shift + wheel turn
76 | * with a mouse. It results in side-scrolling in the browser by default.
77 | *
78 | * - spinY is what you expect -- it's the classic axis of a mouse wheel.
79 | *
80 | * - I dropped spinZ/pixelZ. It is supported by the DOM 3 'wheel' event and
81 | * probably is by browsers in conjunction with fancy 3D controllers .. but
82 | * you know.
83 | *
84 | * Implementation info:
85 | *
86 | * Examples of 'wheel' event if you scroll slowly (down) by one step with an
87 | * average mouse:
88 | *
89 | * OS X + Chrome (mouse) - 4 pixel delta (wheelDelta -120)
90 | * OS X + Safari (mouse) - N/A pixel delta (wheelDelta -12)
91 | * OS X + Firefox (mouse) - 0.1 line delta (wheelDelta N/A)
92 | * Win8 + Chrome (mouse) - 100 pixel delta (wheelDelta -120)
93 | * Win8 + Firefox (mouse) - 3 line delta (wheelDelta -120)
94 | *
95 | * On the trackpad:
96 | *
97 | * OS X + Chrome (trackpad) - 2 pixel delta (wheelDelta -6)
98 | * OS X + Firefox (trackpad) - 1 pixel delta (wheelDelta N/A)
99 | *
100 | * On other/older browsers.. it's more complicated as there can be multiple and
101 | * also missing delta values.
102 | *
103 | * The 'wheel' event is more standard:
104 | *
105 | * http://www.w3.org/TR/DOM-Level-3-Events/#events-wheelevents
106 | *
107 | * The basics is that it includes a unit, deltaMode (pixels, lines, pages), and
108 | * deltaX, deltaY and deltaZ. Some browsers provide other values to maintain
109 | * backward compatibility with older events. Those other values help us
110 | * better normalize spin speed. Example of what the browsers provide:
111 | *
112 | * | event.wheelDelta | event.detail
113 | * ------------------+------------------+--------------
114 | * Safari v5/OS X | -120 | 0
115 | * Safari v5/Win7 | -120 | 0
116 | * Chrome v17/OS X | -120 | 0
117 | * Chrome v17/Win7 | -120 | 0
118 | * IE9/Win7 | -120 | undefined
119 | * Firefox v4/OS X | undefined | 1
120 | * Firefox v4/Win7 | undefined | 3
121 | *
122 | */
123 | function normalizeWheel( /*object*/event) /*object*/{
124 | var sX = 0,
125 | sY = 0,
126 | // spinX, spinY
127 | pX = 0,
128 | pY = 0; // pixelX, pixelY
129 |
130 | // Legacy
131 | if ('detail' in event) {
132 | sY = event.detail;
133 | }
134 | if ('wheelDelta' in event) {
135 | sY = -event.wheelDelta / 120;
136 | }
137 | if ('wheelDeltaY' in event) {
138 | sY = -event.wheelDeltaY / 120;
139 | }
140 | if ('wheelDeltaX' in event) {
141 | sX = -event.wheelDeltaX / 120;
142 | }
143 |
144 | // side scrolling on FF with DOMMouseScroll
145 | if ('axis' in event && event.axis === event.HORIZONTAL_AXIS) {
146 | sX = sY;
147 | sY = 0;
148 | }
149 |
150 | pX = sX * PIXEL_STEP;
151 | pY = sY * PIXEL_STEP;
152 |
153 | if ('deltaY' in event) {
154 | pY = event.deltaY;
155 | }
156 | if ('deltaX' in event) {
157 | pX = event.deltaX;
158 | }
159 |
160 | if ((pX || pY) && event.deltaMode) {
161 | if (event.deltaMode == 1) {
162 | // delta in LINE units
163 | pX *= LINE_HEIGHT;
164 | pY *= LINE_HEIGHT;
165 | } else {
166 | // delta in PAGE units
167 | pX *= PAGE_HEIGHT;
168 | pY *= PAGE_HEIGHT;
169 | }
170 | }
171 |
172 | // Fall-back if spin cannot be determined
173 | if (pX && !sX) {
174 | sX = pX < 1 ? -1 : 1;
175 | }
176 | if (pY && !sY) {
177 | sY = pY < 1 ? -1 : 1;
178 | }
179 |
180 | return { spinX: sX,
181 | spinY: sY,
182 | pixelX: pX,
183 | pixelY: pY };
184 | }
185 |
186 | /**
187 | * The best combination if you prefer spinX + spinY normalization. It favors
188 | * the older DOMMouseScroll for Firefox, as FF does not include wheelDelta with
189 | * 'wheel' event, making spin speed determination impossible.
190 | */
191 | normalizeWheel.getEventType = function () /*string*/{
192 | return UserAgent_DEPRECATED.firefox() ? 'DOMMouseScroll' : isEventSupported('wheel') ? 'wheel' : 'mousewheel';
193 | };
194 |
195 | module.exports = normalizeWheel;
--------------------------------------------------------------------------------
/src/normalizeWheel/UserAgent_DEPRECATED.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2015, Facebook, Inc.
3 | * All rights reserved.
4 | *
5 | * This source code is licensed under the BSD-style license found in the
6 | * LICENSE file in the root directory of this source tree. An additional grant
7 | * of patent rights can be found in the PATENTS file in the same directory.
8 | *
9 | * @providesModule UserAgent_DEPRECATED
10 | */
11 |
12 | /**
13 | * Provides entirely client-side User Agent and OS detection. You should prefer
14 | * the non-deprecated UserAgent module when possible, which exposes our
15 | * authoritative server-side PHP-based detection to the client.
16 | *
17 | * Usage is straightforward:
18 | *
19 | * if (UserAgent_DEPRECATED.ie()) {
20 | * // IE
21 | * }
22 | *
23 | * You can also do version checks:
24 | *
25 | * if (UserAgent_DEPRECATED.ie() >= 7) {
26 | * // IE7 or better
27 | * }
28 | *
29 | * The browser functions will return NaN if the browser does not match, so
30 | * you can also do version compares the other way:
31 | *
32 | * if (UserAgent_DEPRECATED.ie() < 7) {
33 | * // IE6 or worse
34 | * }
35 | *
36 | * Note that the version is a float and may include a minor version number,
37 | * so you should always use range operators to perform comparisons, not
38 | * strict equality.
39 | *
40 | * **Note:** You should **strongly** prefer capability detection to browser
41 | * version detection where it's reasonable:
42 | *
43 | * http://www.quirksmode.org/js/support.html
44 | *
45 | * Further, we have a large number of mature wrapper functions and classes
46 | * which abstract away many browser irregularities. Check the documentation,
47 | * grep for things, or ask on javascript@lists.facebook.com before writing yet
48 | * another copy of "event || window.event".
49 | *
50 | */
51 |
52 | var _populated = false;
53 |
54 | // Browsers
55 | var _ie, _firefox, _opera, _webkit, _chrome;
56 |
57 | // Actual IE browser for compatibility mode
58 | var _ie_real_version;
59 |
60 | // Platforms
61 | var _osx, _windows, _linux, _android;
62 |
63 | // Architectures
64 | var _win64;
65 |
66 | // Devices
67 | var _iphone, _ipad, _native;
68 |
69 | var _mobile;
70 |
71 | function _populate() {
72 | if (_populated) {
73 | return;
74 | }
75 |
76 | _populated = true;
77 |
78 | // To work around buggy JS libraries that can't handle multi-digit
79 | // version numbers, Opera 10's user agent string claims it's Opera
80 | // 9, then later includes a Version/X.Y field:
81 | //
82 | // Opera/9.80 (foo) Presto/2.2.15 Version/10.10
83 | var uas = navigator.userAgent;
84 | var agent = /(?:MSIE.(\d+\.\d+))|(?:(?:Firefox|GranParadiso|Iceweasel).(\d+\.\d+))|(?:Opera(?:.+Version.|.)(\d+\.\d+))|(?:AppleWebKit.(\d+(?:\.\d+)?))|(?:Trident\/\d+\.\d+.*rv:(\d+\.\d+))/.exec(uas);
85 | var os = /(Mac OS X)|(Windows)|(Linux)/.exec(uas);
86 |
87 | _iphone = /\b(iPhone|iP[ao]d)/.exec(uas);
88 | _ipad = /\b(iP[ao]d)/.exec(uas);
89 | _android = /Android/i.exec(uas);
90 | _native = /FBAN\/\w+;/i.exec(uas);
91 | _mobile = /Mobile/i.exec(uas);
92 |
93 | // Note that the IE team blog would have you believe you should be checking
94 | // for 'Win64; x64'. But MSDN then reveals that you can actually be coming
95 | // from either x64 or ia64; so ultimately, you should just check for Win64
96 | // as in indicator of whether you're in 64-bit IE. 32-bit IE on 64-bit
97 | // Windows will send 'WOW64' instead.
98 | _win64 = !!(/Win64/.exec(uas));
99 |
100 | if (agent) {
101 | _ie = agent[1] ? parseFloat(agent[1]) : (
102 | agent[5] ? parseFloat(agent[5]) : NaN);
103 | // IE compatibility mode
104 | if (_ie && document && document.documentMode) {
105 | _ie = document.documentMode;
106 | }
107 | // grab the "true" ie version from the trident token if available
108 | var trident = /(?:Trident\/(\d+.\d+))/.exec(uas);
109 | _ie_real_version = trident ? parseFloat(trident[1]) + 4 : _ie;
110 |
111 | _firefox = agent[2] ? parseFloat(agent[2]) : NaN;
112 | _opera = agent[3] ? parseFloat(agent[3]) : NaN;
113 | _webkit = agent[4] ? parseFloat(agent[4]) : NaN;
114 | if (_webkit) {
115 | // We do not add the regexp to the above test, because it will always
116 | // match 'safari' only since 'AppleWebKit' appears before 'Chrome' in
117 | // the userAgent string.
118 | agent = /(?:Chrome\/(\d+\.\d+))/.exec(uas);
119 | _chrome = agent && agent[1] ? parseFloat(agent[1]) : NaN;
120 | } else {
121 | _chrome = NaN;
122 | }
123 | } else {
124 | _ie = _firefox = _opera = _chrome = _webkit = NaN;
125 | }
126 |
127 | if (os) {
128 | if (os[1]) {
129 | // Detect OS X version. If no version number matches, set _osx to true.
130 | // Version examples: 10, 10_6_1, 10.7
131 | // Parses version number as a float, taking only first two sets of
132 | // digits. If only one set of digits is found, returns just the major
133 | // version number.
134 | var ver = /(?:Mac OS X (\d+(?:[._]\d+)?))/.exec(uas);
135 |
136 | _osx = ver ? parseFloat(ver[1].replace('_', '.')) : true;
137 | } else {
138 | _osx = false;
139 | }
140 | _windows = !!os[2];
141 | _linux = !!os[3];
142 | } else {
143 | _osx = _windows = _linux = false;
144 | }
145 | }
146 |
147 | var UserAgent_DEPRECATED = {
148 |
149 | /**
150 | * Check if the UA is Internet Explorer.
151 | *
152 | *
153 | * @return float|NaN Version number (if match) or NaN.
154 | */
155 | ie: function() {
156 | return _populate() || _ie;
157 | },
158 |
159 | /**
160 | * Check if we're in Internet Explorer compatibility mode.
161 | *
162 | * @return bool true if in compatibility mode, false if
163 | * not compatibility mode or not ie
164 | */
165 | ieCompatibilityMode: function() {
166 | return _populate() || (_ie_real_version > _ie);
167 | },
168 |
169 |
170 | /**
171 | * Whether the browser is 64-bit IE. Really, this is kind of weak sauce; we
172 | * only need this because Skype can't handle 64-bit IE yet. We need to remove
173 | * this when we don't need it -- tracked by #601957.
174 | */
175 | ie64: function() {
176 | return UserAgent_DEPRECATED.ie() && _win64;
177 | },
178 |
179 | /**
180 | * Check if the UA is Firefox.
181 | *
182 | *
183 | * @return float|NaN Version number (if match) or NaN.
184 | */
185 | firefox: function() {
186 | return _populate() || _firefox;
187 | },
188 |
189 |
190 | /**
191 | * Check if the UA is Opera.
192 | *
193 | *
194 | * @return float|NaN Version number (if match) or NaN.
195 | */
196 | opera: function() {
197 | return _populate() || _opera;
198 | },
199 |
200 |
201 | /**
202 | * Check if the UA is WebKit.
203 | *
204 | *
205 | * @return float|NaN Version number (if match) or NaN.
206 | */
207 | webkit: function() {
208 | return _populate() || _webkit;
209 | },
210 |
211 | /**
212 | * For Push
213 | * WILL BE REMOVED VERY SOON. Use UserAgent_DEPRECATED.webkit
214 | */
215 | safari: function() {
216 | return UserAgent_DEPRECATED.webkit();
217 | },
218 |
219 | /**
220 | * Check if the UA is a Chrome browser.
221 | *
222 | *
223 | * @return float|NaN Version number (if match) or NaN.
224 | */
225 | chrome : function() {
226 | return _populate() || _chrome;
227 | },
228 |
229 |
230 | /**
231 | * Check if the user is running Windows.
232 | *
233 | * @return bool `true' if the user's OS is Windows.
234 | */
235 | windows: function() {
236 | return _populate() || _windows;
237 | },
238 |
239 |
240 | /**
241 | * Check if the user is running Mac OS X.
242 | *
243 | * @return float|bool Returns a float if a version number is detected,
244 | * otherwise true/false.
245 | */
246 | osx: function() {
247 | return _populate() || _osx;
248 | },
249 |
250 | /**
251 | * Check if the user is running Linux.
252 | *
253 | * @return bool `true' if the user's OS is some flavor of Linux.
254 | */
255 | linux: function() {
256 | return _populate() || _linux;
257 | },
258 |
259 | /**
260 | * Check if the user is running on an iPhone or iPod platform.
261 | *
262 | * @return bool `true' if the user is running some flavor of the
263 | * iPhone OS.
264 | */
265 | iphone: function() {
266 | return _populate() || _iphone;
267 | },
268 |
269 | mobile: function() {
270 | return _populate() || (_iphone || _ipad || _android || _mobile);
271 | },
272 |
273 | nativeApp: function() {
274 | // webviews inside of the native apps
275 | return _populate() || _native;
276 | },
277 |
278 | android: function() {
279 | return _populate() || _android;
280 | },
281 |
282 | ipad: function() {
283 | return _populate() || _ipad;
284 | }
285 | };
286 |
287 | module.exports = UserAgent_DEPRECATED;
--------------------------------------------------------------------------------
/lib/normalizeWheel/UserAgent_DEPRECATED.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2015, Facebook, Inc.
3 | * All rights reserved.
4 | *
5 | * This source code is licensed under the BSD-style license found in the
6 | * LICENSE file in the root directory of this source tree. An additional grant
7 | * of patent rights can be found in the PATENTS file in the same directory.
8 | *
9 | * @providesModule UserAgent_DEPRECATED
10 | */
11 |
12 | /**
13 | * Provides entirely client-side User Agent and OS detection. You should prefer
14 | * the non-deprecated UserAgent module when possible, which exposes our
15 | * authoritative server-side PHP-based detection to the client.
16 | *
17 | * Usage is straightforward:
18 | *
19 | * if (UserAgent_DEPRECATED.ie()) {
20 | * // IE
21 | * }
22 | *
23 | * You can also do version checks:
24 | *
25 | * if (UserAgent_DEPRECATED.ie() >= 7) {
26 | * // IE7 or better
27 | * }
28 | *
29 | * The browser functions will return NaN if the browser does not match, so
30 | * you can also do version compares the other way:
31 | *
32 | * if (UserAgent_DEPRECATED.ie() < 7) {
33 | * // IE6 or worse
34 | * }
35 | *
36 | * Note that the version is a float and may include a minor version number,
37 | * so you should always use range operators to perform comparisons, not
38 | * strict equality.
39 | *
40 | * **Note:** You should **strongly** prefer capability detection to browser
41 | * version detection where it's reasonable:
42 | *
43 | * http://www.quirksmode.org/js/support.html
44 | *
45 | * Further, we have a large number of mature wrapper functions and classes
46 | * which abstract away many browser irregularities. Check the documentation,
47 | * grep for things, or ask on javascript@lists.facebook.com before writing yet
48 | * another copy of "event || window.event".
49 | *
50 | */
51 |
52 | 'use strict';
53 |
54 | var _populated = false;
55 |
56 | // Browsers
57 | var _ie, _firefox, _opera, _webkit, _chrome;
58 |
59 | // Actual IE browser for compatibility mode
60 | var _ie_real_version;
61 |
62 | // Platforms
63 | var _osx, _windows, _linux, _android;
64 |
65 | // Architectures
66 | var _win64;
67 |
68 | // Devices
69 | var _iphone, _ipad, _native;
70 |
71 | var _mobile;
72 |
73 | function _populate() {
74 | if (_populated) {
75 | return;
76 | }
77 |
78 | _populated = true;
79 |
80 | // To work around buggy JS libraries that can't handle multi-digit
81 | // version numbers, Opera 10's user agent string claims it's Opera
82 | // 9, then later includes a Version/X.Y field:
83 | //
84 | // Opera/9.80 (foo) Presto/2.2.15 Version/10.10
85 | var uas = navigator.userAgent;
86 | var agent = /(?:MSIE.(\d+\.\d+))|(?:(?:Firefox|GranParadiso|Iceweasel).(\d+\.\d+))|(?:Opera(?:.+Version.|.)(\d+\.\d+))|(?:AppleWebKit.(\d+(?:\.\d+)?))|(?:Trident\/\d+\.\d+.*rv:(\d+\.\d+))/.exec(uas);
87 | var os = /(Mac OS X)|(Windows)|(Linux)/.exec(uas);
88 |
89 | _iphone = /\b(iPhone|iP[ao]d)/.exec(uas);
90 | _ipad = /\b(iP[ao]d)/.exec(uas);
91 | _android = /Android/i.exec(uas);
92 | _native = /FBAN\/\w+;/i.exec(uas);
93 | _mobile = /Mobile/i.exec(uas);
94 |
95 | // Note that the IE team blog would have you believe you should be checking
96 | // for 'Win64; x64'. But MSDN then reveals that you can actually be coming
97 | // from either x64 or ia64; so ultimately, you should just check for Win64
98 | // as in indicator of whether you're in 64-bit IE. 32-bit IE on 64-bit
99 | // Windows will send 'WOW64' instead.
100 | _win64 = !!/Win64/.exec(uas);
101 |
102 | if (agent) {
103 | _ie = agent[1] ? parseFloat(agent[1]) : agent[5] ? parseFloat(agent[5]) : NaN;
104 | // IE compatibility mode
105 | if (_ie && document && document.documentMode) {
106 | _ie = document.documentMode;
107 | }
108 | // grab the "true" ie version from the trident token if available
109 | var trident = /(?:Trident\/(\d+.\d+))/.exec(uas);
110 | _ie_real_version = trident ? parseFloat(trident[1]) + 4 : _ie;
111 |
112 | _firefox = agent[2] ? parseFloat(agent[2]) : NaN;
113 | _opera = agent[3] ? parseFloat(agent[3]) : NaN;
114 | _webkit = agent[4] ? parseFloat(agent[4]) : NaN;
115 | if (_webkit) {
116 | // We do not add the regexp to the above test, because it will always
117 | // match 'safari' only since 'AppleWebKit' appears before 'Chrome' in
118 | // the userAgent string.
119 | agent = /(?:Chrome\/(\d+\.\d+))/.exec(uas);
120 | _chrome = agent && agent[1] ? parseFloat(agent[1]) : NaN;
121 | } else {
122 | _chrome = NaN;
123 | }
124 | } else {
125 | _ie = _firefox = _opera = _chrome = _webkit = NaN;
126 | }
127 |
128 | if (os) {
129 | if (os[1]) {
130 | // Detect OS X version. If no version number matches, set _osx to true.
131 | // Version examples: 10, 10_6_1, 10.7
132 | // Parses version number as a float, taking only first two sets of
133 | // digits. If only one set of digits is found, returns just the major
134 | // version number.
135 | var ver = /(?:Mac OS X (\d+(?:[._]\d+)?))/.exec(uas);
136 |
137 | _osx = ver ? parseFloat(ver[1].replace('_', '.')) : true;
138 | } else {
139 | _osx = false;
140 | }
141 | _windows = !!os[2];
142 | _linux = !!os[3];
143 | } else {
144 | _osx = _windows = _linux = false;
145 | }
146 | }
147 |
148 | var UserAgent_DEPRECATED = {
149 |
150 | /**
151 | * Check if the UA is Internet Explorer.
152 | *
153 | *
154 | * @return float|NaN Version number (if match) or NaN.
155 | */
156 | ie: function ie() {
157 | return _populate() || _ie;
158 | },
159 |
160 | /**
161 | * Check if we're in Internet Explorer compatibility mode.
162 | *
163 | * @return bool true if in compatibility mode, false if
164 | * not compatibility mode or not ie
165 | */
166 | ieCompatibilityMode: function ieCompatibilityMode() {
167 | return _populate() || _ie_real_version > _ie;
168 | },
169 |
170 | /**
171 | * Whether the browser is 64-bit IE. Really, this is kind of weak sauce; we
172 | * only need this because Skype can't handle 64-bit IE yet. We need to remove
173 | * this when we don't need it -- tracked by #601957.
174 | */
175 | ie64: function ie64() {
176 | return UserAgent_DEPRECATED.ie() && _win64;
177 | },
178 |
179 | /**
180 | * Check if the UA is Firefox.
181 | *
182 | *
183 | * @return float|NaN Version number (if match) or NaN.
184 | */
185 | firefox: function firefox() {
186 | return _populate() || _firefox;
187 | },
188 |
189 | /**
190 | * Check if the UA is Opera.
191 | *
192 | *
193 | * @return float|NaN Version number (if match) or NaN.
194 | */
195 | opera: function opera() {
196 | return _populate() || _opera;
197 | },
198 |
199 | /**
200 | * Check if the UA is WebKit.
201 | *
202 | *
203 | * @return float|NaN Version number (if match) or NaN.
204 | */
205 | webkit: function webkit() {
206 | return _populate() || _webkit;
207 | },
208 |
209 | /**
210 | * For Push
211 | * WILL BE REMOVED VERY SOON. Use UserAgent_DEPRECATED.webkit
212 | */
213 | safari: function safari() {
214 | return UserAgent_DEPRECATED.webkit();
215 | },
216 |
217 | /**
218 | * Check if the UA is a Chrome browser.
219 | *
220 | *
221 | * @return float|NaN Version number (if match) or NaN.
222 | */
223 | chrome: function chrome() {
224 | return _populate() || _chrome;
225 | },
226 |
227 | /**
228 | * Check if the user is running Windows.
229 | *
230 | * @return bool `true' if the user's OS is Windows.
231 | */
232 | windows: function windows() {
233 | return _populate() || _windows;
234 | },
235 |
236 | /**
237 | * Check if the user is running Mac OS X.
238 | *
239 | * @return float|bool Returns a float if a version number is detected,
240 | * otherwise true/false.
241 | */
242 | osx: function osx() {
243 | return _populate() || _osx;
244 | },
245 |
246 | /**
247 | * Check if the user is running Linux.
248 | *
249 | * @return bool `true' if the user's OS is some flavor of Linux.
250 | */
251 | linux: function linux() {
252 | return _populate() || _linux;
253 | },
254 |
255 | /**
256 | * Check if the user is running on an iPhone or iPod platform.
257 | *
258 | * @return bool `true' if the user is running some flavor of the
259 | * iPhone OS.
260 | */
261 | iphone: function iphone() {
262 | return _populate() || _iphone;
263 | },
264 |
265 | mobile: function mobile() {
266 | return _populate() || (_iphone || _ipad || _android || _mobile);
267 | },
268 |
269 | nativeApp: function nativeApp() {
270 | // webviews inside of the native apps
271 | return _populate() || _native;
272 | },
273 |
274 | android: function android() {
275 | return _populate() || _android;
276 | },
277 |
278 | ipad: function ipad() {
279 | return _populate() || _ipad;
280 | }
281 | };
282 |
283 | module.exports = UserAgent_DEPRECATED;
--------------------------------------------------------------------------------
/src/index.jsx:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | import Component from 'react-class'
4 |
5 | const React = require('react')
6 | const LoadMask = require('react-load-mask')
7 | const assign = require('object-assign')
8 | const DragHelper = require('drag-helper')
9 | const normalize = require('react-style-normalizer')
10 | const hasTouch = require('has-touch')
11 |
12 | const preventDefault = event => event && event.preventDefault()
13 | const signum = x => x < 0? -1: 1
14 | const emptyFn = () => {}
15 | const ABS = Math.abs
16 |
17 | const LoadMaskFactory = React.createFactory(LoadMask)
18 |
19 | var horizontalScrollbarStyle = {}
20 |
21 | var IS_MAC = global && global.navigator && global.navigator.appVersion && global.navigator.appVersion.indexOf("Mac") != -1
22 | var IS_FIREFOX = global && global.navigator && global.navigator.userAgent && !!~global.navigator.userAgent.toLowerCase().indexOf('firefox')
23 |
24 | if (IS_MAC){
25 | horizontalScrollbarStyle.position = 'absolute'
26 | horizontalScrollbarStyle.height = 20
27 | }
28 |
29 | const PT = React.PropTypes
30 | const DISPLAY_NAME = 'Scroller'
31 |
32 | const ON_OVERFLOW_NAMES = {
33 | vertical : 'onVerticalScrollOverflow',
34 | horizontal: 'onHorizontalScrollOverflow'
35 | }
36 |
37 | const ON_SCROLL_NAMES = {
38 | vertical : 'onVerticalScroll',
39 | horizontal: 'onHorizontalScroll'
40 | }
41 |
42 | /**
43 | * Called on scroll by mouse wheel
44 | */
45 | const syncScrollbar = function(orientation) {
46 |
47 | return function(scrollPos, event){
48 |
49 | var domNode = orientation == 'horizontal'? this.getHorizontalScrollbarNode(): this.getVerticalScrollbarNode()
50 | var scrollPosName = orientation == 'horizontal'? 'scrollLeft': 'scrollTop'
51 | var overflowCallback
52 |
53 | domNode[scrollPosName] = scrollPos
54 |
55 | var newScrollPos = domNode[scrollPosName]
56 |
57 | if (newScrollPos != scrollPos){
58 | // overflowCallback = this.props[ON_OVERFLOW_NAMES[orientation]]
59 | // overflowCallback && overflowCallback(signum(scrollPos), newScrollPos)
60 | } else {
61 | preventDefault(event)
62 | }
63 | }
64 | }
65 |
66 | const syncHorizontalScrollbar = syncScrollbar('horizontal')
67 | const syncVerticalScrollbar = syncScrollbar('vertical')
68 |
69 | const scrollAt = function(orientation){
70 | var syncFn = orientation == 'horizontal'?
71 | syncHorizontalScrollbar:
72 | syncVerticalScrollbar
73 |
74 | return function(scrollPos, event){
75 | // this.mouseWheelScroll = true
76 |
77 | syncFn.call(this, Math.round(scrollPos), event)
78 |
79 | // raf(function(){
80 | // this.mouseWheelScroll = false
81 | // }.bind(this))
82 | }
83 | }
84 |
85 | const onScroll = function(orientation){
86 |
87 | var clientHeightNames = {
88 | vertical : 'clientHeight',
89 | horizontal: 'clientWidth'
90 | }
91 |
92 | var scrollHeightNames = {
93 | vertical : 'scrollHeight',
94 | horizontal: 'scrollWidth'
95 | }
96 |
97 | return function(event){
98 |
99 | var scrollPosName = orientation == 'horizontal'? 'scrollLeft': 'scrollTop'
100 | var target = event.target
101 | var scrollPos = target[scrollPosName]
102 |
103 | var onScroll = this.props[ON_SCROLL_NAMES[orientation]]
104 | var onOverflow = this.props[ON_OVERFLOW_NAMES[orientation]]
105 |
106 | // if (!this.mouseWheelScroll && onOverflow){
107 | if (onOverflow){
108 | if (scrollPos == 0){
109 | onOverflow(-1, scrollPos)
110 | } else if (scrollPos + target[clientHeightNames[orientation]] >= target[scrollHeightNames[orientation]]){
111 | onOverflow(1, scrollPos)
112 | }
113 | }
114 |
115 | ;(onScroll || emptyFn)(scrollPos)
116 | }
117 | }
118 |
119 | /**
120 | * The scroller can have a load mask (loadMask prop is true by default),
121 | * you just need to specify loading=true to see it in action
122 | *
123 | *
124 | *
125 | * If you don't want a load mask, specify
126 | *
127 | *
128 | *
129 | * Or if you want to customize the loadMask factory, specify
130 | *
131 | * function mask(props) { return aMaskFactory(props) }
132 | *
158 |
159 | var renderProps = this.prepareRenderProps(props)
160 |
161 | return
162 | {loadMask}
163 |
164 | {content}
165 | {verticalScrollbar}
166 |
167 |
168 | {horizontalScrollbar}
169 |
170 | }
171 |
172 | prepareRenderProps(props) {
173 | var renderProps = assign({}, props)
174 |
175 | delete renderProps.height
176 | delete renderProps.width
177 |
178 | return renderProps
179 | }
180 |
181 | handleTouchStart(event) {
182 |
183 | var props = this.props
184 | var scroll = {
185 | top : props.scrollTop,
186 | left: props.scrollLeft
187 | }
188 |
189 | var newScrollPos
190 | var side
191 |
192 | DragHelper(event, {
193 | scope: this,
194 | onDrag: function(event, config) {
195 | if (config.diff.top == 0 && config.diff.left == 0){
196 | return
197 | }
198 |
199 | if (!side){
200 | side = ABS(config.diff.top) > ABS(config.diff.left)? 'top': 'left'
201 | }
202 |
203 | var diff = config.diff[side]
204 |
205 | newScrollPos = scroll[side] - diff
206 |
207 | if (side == 'top'){
208 | this.verticalScrollAt(newScrollPos, event)
209 | } else {
210 | this.horizontalScrollAt(newScrollPos, event)
211 | }
212 |
213 | }
214 | })
215 |
216 | event.stopPropagation()
217 | preventDefault(event)
218 | }
219 |
220 | handleWheel(event){
221 |
222 | var props = this.props
223 | // var normalizedEvent = normalizeWheel(event)
224 |
225 | var virtual = props.virtualRendering
226 | var horizontal = event.shiftKey
227 | var scrollStep = props.scrollStep
228 | var minScrollStep = props.minScrollStep
229 |
230 | var scrollTop = props.scrollTop
231 | var scrollLeft = props.scrollLeft
232 |
233 | // var delta = normalizedEvent.pixelY
234 | var delta = event.deltaY
235 |
236 | if (horizontal){
237 | // delta = delta || normalizedEvent.pixelX
238 | delta = delta || event.deltaX
239 |
240 | minScrollStep = props.minHorizontalScrollStep || minScrollStep
241 | } else {
242 | minScrollStep = props.minVerticalScrollStep || minScrollStep
243 | }
244 |
245 | if (typeof props.interceptWheelScroll == 'function'){
246 | delta = props.interceptWheelScroll(delta, normalizedEvent, event)
247 | } else if (minScrollStep){
248 | if (ABS(delta) < minScrollStep){
249 | delta = signum(delta) * minScrollStep
250 | }
251 | }
252 |
253 | if (horizontal){
254 | this.horizontalScrollAt(scrollLeft + delta, event)
255 |
256 | props.preventDefaultHorizontal && preventDefault(event)
257 |
258 | } else {
259 | this.verticalScrollAt(scrollTop + delta, event)
260 |
261 | props.preventDefaultVertical && preventDefault(event)
262 | }
263 | }
264 |
265 | componentWillReceiveProps(){
266 | setTimeout(this.fixHorizontalScrollbar, 0)
267 | }
268 |
269 | componentDidMount() {
270 | this.fixHorizontalScrollbar()
271 |
272 | ;(this.props.onMount || emptyFn)(this);
273 |
274 | setTimeout(function(){
275 | this.fixHorizontalScrollbar();
276 | }.bind(this), 0)
277 | }
278 |
279 | fixHorizontalScrollbar() {
280 | this.horizontalScrollerNode = this.horizontalScrollerNode || React.findDOMNode(this).querySelector('.z-horizontal-scroller')
281 |
282 | var dom = this.horizontalScrollerNode
283 |
284 | if (dom){
285 | var height = dom.style.height
286 |
287 | dom.style.height = height == '0.2px'? '0.1px': '0.2px'
288 | }
289 | }
290 |
291 | getVerticalScrollbarNode(){
292 | return this.verticalScrollbarNode = this.verticalScrollbarNode || React.findDOMNode(this).querySelector('.ref-verticalScrollbar')
293 | }
294 |
295 | getHorizontalScrollbarNode(){
296 | return this.horizontalScrollbarNode = this.horizontalScrollbarNode || React.findDOMNode(this).querySelector('.ref-horizontalScrollbar')
297 | }
298 |
299 | componentWillUnmount(){
300 | delete this.horizontalScrollerNode
301 | delete this.horizontalScrollbarNode
302 | delete this.verticalScrollbarNode
303 | }
304 |
305 | ////////////////////////////////////////////////
306 | //
307 | // RENDER METHODS
308 | //
309 | ////////////////////////////////////////////////
310 | renderVerticalScrollbar(props) {
311 | var height = props.scrollHeight
312 | var verticalScrollbarStyle = {
313 | width: props.scrollbarSize
314 | }
315 |
316 | var onScroll = this.onVerticalScroll
317 |
318 | return
327 | }
328 |
329 | renderHorizontalScrollbar(props) {
330 | var scrollbar
331 | var onScroll = this.onHorizontalScroll
332 | var style = horizontalScrollbarStyle
333 | var minWidth = props.scrollWidth
334 |
335 | var scroller =
336 |
337 | if (IS_MAC){
338 | //needed for mac safari
339 | scrollbar =
343 |
347 | {scroller}
348 |
349 |
350 | } else {
351 | scrollbar =
356 | {scroller}
357 |
358 | }
359 |
360 | return scrollbar
361 | }
362 |
363 | renderLoadMask(props) {
364 | if (props.loadMask){
365 | var loadMaskProps = assign({ visible: props.loading }, props.loadMaskProps)
366 |
367 | var defaultFactory = LoadMaskFactory
368 | var factory = typeof props.loadMask == 'function'?
369 | props.loadMask:
370 | defaultFactory
371 |
372 | var mask = factory(loadMaskProps)
373 |
374 | if (mask === undefined){
375 | //allow the specified factory to just modify props
376 | //and then leave the rendering to the defaultFactory
377 | mask = defaultFactory(loadMaskProps)
378 | }
379 |
380 | return mask
381 | }
382 | }
383 |
384 | ////////////////////////////////////////////////
385 | //
386 | // PREPARE PROPS METHODS
387 | //
388 | ////////////////////////////////////////////////
389 | prepareProps(thisProps) {
390 | const props = assign({}, thisProps)
391 |
392 | props.className = this.prepareClassName(props)
393 | props.style = this.prepareStyle(props)
394 |
395 | return props
396 | }
397 |
398 | prepareStyle(props) {
399 | let style = assign({}, props.style)
400 |
401 | if (props.height != null){
402 | style.height = props.height
403 | }
404 |
405 | if (props.width != null){
406 | style.width = props.width
407 | }
408 |
409 | if (props.normalizeStyles){
410 | style = normalize(style)
411 | }
412 |
413 | return style
414 | }
415 |
416 | prepareClassName(props) {
417 | let className = props.className || ''
418 |
419 | if (Scroller.className){
420 | className += ' ' + Scroller.className
421 | }
422 |
423 | return className
424 | }
425 | }
426 |
427 | Scroller.className = 'z-scroller'
428 | Scroller.displayName = DISPLAY_NAME
429 |
430 | assign(Scroller.prototype, {
431 | onVerticalScroll: onScroll('vertical'),
432 | onHorizontalScroll: onScroll('horizontal'),
433 |
434 | verticalScrollAt : scrollAt('vertical'),
435 | horizontalScrollAt: scrollAt('horizontal'),
436 |
437 | syncHorizontalScrollbar: syncHorizontalScrollbar,
438 | syncVerticalScrollbar : syncVerticalScrollbar
439 | })
440 |
441 | Scroller.propTypes = {
442 | loadMask: PT.oneOfType([
443 | PT.bool,
444 | PT.func
445 | ]),
446 |
447 | loading : PT.bool,
448 | normalizeStyles: PT.bool,
449 |
450 | scrollTop : PT.number,
451 | scrollLeft: PT.number,
452 |
453 | scrollWidth : PT.number.isRequired,
454 | scrollHeight: PT.number.isRequired,
455 |
456 | height: PT.number,
457 | width : PT.number,
458 |
459 | minScrollStep : PT.number,
460 | minHorizontalScrollStep: PT.number,
461 | minVerticalScrollStep : PT.number,
462 |
463 | virtualRendering: PT.oneOf([true]),
464 |
465 | preventDefaultVertical: PT.bool,
466 | preventDefaultHorizontal: PT.bool
467 | },
468 |
469 | Scroller.defaultProps = {
470 | 'data-display-name': DISPLAY_NAME,
471 | loadMask: true,
472 |
473 | virtualRendering: true, //FOR NOW, only true is supported
474 | scrollbarSize: 20,
475 |
476 | scrollTop : 0,
477 | scrollLeft: 0,
478 |
479 | minScrollStep: 10,
480 |
481 | minHorizontalScrollStep: IS_FIREFOX? 40: 1,
482 |
483 | //since FF goes back in browser history on scroll too soon
484 | //chrome and others also do this, but the normal preventDefault in syncScrollbar fn prevents this
485 | preventDefaultHorizontal: IS_FIREFOX
486 | }
487 |
488 | export default Scroller
--------------------------------------------------------------------------------
/lib/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var _interopRequireDefault = function (obj) { return obj && obj.__esModule ? obj : { 'default': obj }; };
4 |
5 | var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } };
6 |
7 | var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
8 |
9 | var _inherits = function (subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) subClass.__proto__ = superClass; };
10 |
11 | var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
12 |
13 | Object.defineProperty(exports, '__esModule', {
14 | value: true
15 | });
16 |
17 | var _Component2 = require('react-class');
18 |
19 | var _Component3 = _interopRequireDefault(_Component2);
20 |
21 | 'use strict';
22 |
23 | var React = require('react');
24 | var LoadMask = require('react-load-mask');
25 | var assign = require('object-assign');
26 | var DragHelper = require('drag-helper');
27 | var normalize = require('react-style-normalizer');
28 | var hasTouch = require('has-touch');
29 |
30 | var preventDefault = function preventDefault(event) {
31 | return event && event.preventDefault();
32 | };
33 | var signum = function signum(x) {
34 | return x < 0 ? -1 : 1;
35 | };
36 | var emptyFn = function emptyFn() {};
37 | var ABS = Math.abs;
38 |
39 | var LoadMaskFactory = React.createFactory(LoadMask);
40 |
41 | var horizontalScrollbarStyle = {};
42 |
43 | var IS_MAC = global && global.navigator && global.navigator.appVersion && global.navigator.appVersion.indexOf('Mac') != -1;
44 | var IS_FIREFOX = global && global.navigator && global.navigator.userAgent && !! ~global.navigator.userAgent.toLowerCase().indexOf('firefox');
45 |
46 | if (IS_MAC) {
47 | horizontalScrollbarStyle.position = 'absolute';
48 | horizontalScrollbarStyle.height = 20;
49 | }
50 |
51 | var PT = React.PropTypes;
52 | var DISPLAY_NAME = 'Scroller';
53 |
54 | var ON_OVERFLOW_NAMES = {
55 | vertical: 'onVerticalScrollOverflow',
56 | horizontal: 'onHorizontalScrollOverflow'
57 | };
58 |
59 | var ON_SCROLL_NAMES = {
60 | vertical: 'onVerticalScroll',
61 | horizontal: 'onHorizontalScroll'
62 | };
63 |
64 | /**
65 | * Called on scroll by mouse wheel
66 | */
67 | var syncScrollbar = function syncScrollbar(orientation) {
68 |
69 | return function (scrollPos, event) {
70 |
71 | var domNode = orientation == 'horizontal' ? this.getHorizontalScrollbarNode() : this.getVerticalScrollbarNode();
72 | var scrollPosName = orientation == 'horizontal' ? 'scrollLeft' : 'scrollTop';
73 | var overflowCallback;
74 |
75 | domNode[scrollPosName] = scrollPos;
76 |
77 | var newScrollPos = domNode[scrollPosName];
78 |
79 | if (newScrollPos != scrollPos) {} else {
80 | preventDefault(event);
81 | }
82 | };
83 | };
84 |
85 | var syncHorizontalScrollbar = syncScrollbar('horizontal');
86 | var syncVerticalScrollbar = syncScrollbar('vertical');
87 |
88 | var scrollAt = function scrollAt(orientation) {
89 | var syncFn = orientation == 'horizontal' ? syncHorizontalScrollbar : syncVerticalScrollbar;
90 |
91 | return function (scrollPos, event) {
92 | // this.mouseWheelScroll = true
93 |
94 | syncFn.call(this, Math.round(scrollPos), event);
95 | };
96 | };
97 |
98 | var onScroll = function onScroll(orientation) {
99 |
100 | var clientHeightNames = {
101 | vertical: 'clientHeight',
102 | horizontal: 'clientWidth'
103 | };
104 |
105 | var scrollHeightNames = {
106 | vertical: 'scrollHeight',
107 | horizontal: 'scrollWidth'
108 | };
109 |
110 | return function (event) {
111 |
112 | var scrollPosName = orientation == 'horizontal' ? 'scrollLeft' : 'scrollTop';
113 | var target = event.target;
114 | var scrollPos = target[scrollPosName];
115 |
116 | var onScroll = this.props[ON_SCROLL_NAMES[orientation]];
117 | var onOverflow = this.props[ON_OVERFLOW_NAMES[orientation]];
118 |
119 | // if (!this.mouseWheelScroll && onOverflow){
120 | if (onOverflow) {
121 | if (scrollPos == 0) {
122 | onOverflow(-1, scrollPos);
123 | } else if (scrollPos + target[clientHeightNames[orientation]] >= target[scrollHeightNames[orientation]]) {
124 | onOverflow(1, scrollPos);
125 | }
126 | }
127 |
128 | ;(onScroll || emptyFn)(scrollPos);
129 | };
130 | };
131 |
132 | /**
133 | * The scroller can have a load mask (loadMask prop is true by default),
134 | * you just need to specify loading=true to see it in action
135 | *
136 | *
137 | *
138 | * If you don't want a load mask, specify
139 | *
140 | *
141 | *
142 | * Or if you want to customize the loadMask factory, specify
143 | *
144 | * function mask(props) { return aMaskFactory(props) }
145 | * ABS(config.diff.left) ? 'top' : 'left';
231 | }
232 |
233 | var diff = config.diff[side];
234 |
235 | newScrollPos = scroll[side] - diff;
236 |
237 | if (side == 'top') {
238 | this.verticalScrollAt(newScrollPos, event);
239 | } else {
240 | this.horizontalScrollAt(newScrollPos, event);
241 | }
242 | }
243 | });
244 |
245 | event.stopPropagation();
246 | preventDefault(event);
247 | }
248 | }, {
249 | key: 'handleWheel',
250 | value: function handleWheel(event) {
251 |
252 | var props = this.props;
253 | // var normalizedEvent = normalizeWheel(event)
254 |
255 | var virtual = props.virtualRendering;
256 | var horizontal = event.shiftKey;
257 | var scrollStep = props.scrollStep;
258 | var minScrollStep = props.minScrollStep;
259 |
260 | var scrollTop = props.scrollTop;
261 | var scrollLeft = props.scrollLeft;
262 |
263 | // var delta = normalizedEvent.pixelY
264 | var delta = event.deltaY;
265 |
266 | if (horizontal) {
267 | // delta = delta || normalizedEvent.pixelX
268 | delta = delta || event.deltaX;
269 |
270 | minScrollStep = props.minHorizontalScrollStep || minScrollStep;
271 | } else {
272 | minScrollStep = props.minVerticalScrollStep || minScrollStep;
273 | }
274 |
275 | if (typeof props.interceptWheelScroll == 'function') {
276 | delta = props.interceptWheelScroll(delta, normalizedEvent, event);
277 | } else if (minScrollStep) {
278 | if (ABS(delta) < minScrollStep) {
279 | delta = signum(delta) * minScrollStep;
280 | }
281 | }
282 |
283 | if (horizontal) {
284 | this.horizontalScrollAt(scrollLeft + delta, event);
285 |
286 | props.preventDefaultHorizontal && preventDefault(event);
287 | } else {
288 | this.verticalScrollAt(scrollTop + delta, event);
289 |
290 | props.preventDefaultVertical && preventDefault(event);
291 | }
292 | }
293 | }, {
294 | key: 'componentWillReceiveProps',
295 | value: function componentWillReceiveProps() {
296 | setTimeout(this.fixHorizontalScrollbar, 0);
297 | }
298 | }, {
299 | key: 'componentDidMount',
300 | value: function componentDidMount() {
301 | this.fixHorizontalScrollbar();(this.props.onMount || emptyFn)(this);
302 |
303 | setTimeout((function () {
304 | this.fixHorizontalScrollbar();
305 | }).bind(this), 0);
306 | }
307 | }, {
308 | key: 'fixHorizontalScrollbar',
309 | value: function fixHorizontalScrollbar() {
310 | this.horizontalScrollerNode = this.horizontalScrollerNode || React.findDOMNode(this).querySelector('.z-horizontal-scroller');
311 |
312 | var dom = this.horizontalScrollerNode;
313 |
314 | if (dom) {
315 | var height = dom.style.height;
316 |
317 | dom.style.height = height == '0.2px' ? '0.1px' : '0.2px';
318 | }
319 | }
320 | }, {
321 | key: 'getVerticalScrollbarNode',
322 | value: function getVerticalScrollbarNode() {
323 | return this.verticalScrollbarNode = this.verticalScrollbarNode || React.findDOMNode(this).querySelector('.ref-verticalScrollbar');
324 | }
325 | }, {
326 | key: 'getHorizontalScrollbarNode',
327 | value: function getHorizontalScrollbarNode() {
328 | return this.horizontalScrollbarNode = this.horizontalScrollbarNode || React.findDOMNode(this).querySelector('.ref-horizontalScrollbar');
329 | }
330 | }, {
331 | key: 'componentWillUnmount',
332 | value: function componentWillUnmount() {
333 | delete this.horizontalScrollerNode;
334 | delete this.horizontalScrollbarNode;
335 | delete this.verticalScrollbarNode;
336 | }
337 | }, {
338 | key: 'renderVerticalScrollbar',
339 |
340 | ////////////////////////////////////////////////
341 | //
342 | // RENDER METHODS
343 | //
344 | ////////////////////////////////////////////////
345 | value: function renderVerticalScrollbar(props) {
346 | var height = props.scrollHeight;
347 | var verticalScrollbarStyle = {
348 | width: props.scrollbarSize
349 | };
350 |
351 | var onScroll = this.onVerticalScroll;
352 |
353 | return React.createElement(
354 | 'div',
355 | { className: 'z-vertical-scrollbar', style: verticalScrollbarStyle },
356 | React.createElement(
357 | 'div',
358 | {
359 | className: 'ref-verticalScrollbar',
360 | onScroll: onScroll,
361 | style: { overflow: 'auto', width: '100%', height: '100%' }
362 | },
363 | React.createElement('div', { className: 'z-vertical-scroller', style: { height: height } })
364 | )
365 | );
366 | }
367 | }, {
368 | key: 'renderHorizontalScrollbar',
369 | value: function renderHorizontalScrollbar(props) {
370 | var scrollbar;
371 | var onScroll = this.onHorizontalScroll;
372 | var style = horizontalScrollbarStyle;
373 | var minWidth = props.scrollWidth;
374 |
375 | var scroller = React.createElement('div', { xref: 'horizontalScroller', className: 'z-horizontal-scroller', style: { width: minWidth } });
376 |
377 | if (IS_MAC) {
378 | //needed for mac safari
379 | scrollbar = React.createElement(
380 | 'div',
381 | {
382 | style: style,
383 | className: 'z-horizontal-scrollbar mac-fix'
384 | },
385 | React.createElement(
386 | 'div',
387 | {
388 | onScroll: onScroll,
389 | className: 'ref-horizontalScrollbar z-horizontal-scrollbar-fix'
390 | },
391 | scroller
392 | )
393 | );
394 | } else {
395 | scrollbar = React.createElement(
396 | 'div',
397 | {
398 | style: style,
399 | className: 'ref-horizontalScrollbar z-horizontal-scrollbar',
400 | onScroll: onScroll
401 | },
402 | scroller
403 | );
404 | }
405 |
406 | return scrollbar;
407 | }
408 | }, {
409 | key: 'renderLoadMask',
410 | value: function renderLoadMask(props) {
411 | if (props.loadMask) {
412 | var loadMaskProps = assign({ visible: props.loading }, props.loadMaskProps);
413 |
414 | var defaultFactory = LoadMaskFactory;
415 | var factory = typeof props.loadMask == 'function' ? props.loadMask : defaultFactory;
416 |
417 | var mask = factory(loadMaskProps);
418 |
419 | if (mask === undefined) {
420 | //allow the specified factory to just modify props
421 | //and then leave the rendering to the defaultFactory
422 | mask = defaultFactory(loadMaskProps);
423 | }
424 |
425 | return mask;
426 | }
427 | }
428 | }, {
429 | key: 'prepareProps',
430 |
431 | ////////////////////////////////////////////////
432 | //
433 | // PREPARE PROPS METHODS
434 | //
435 | ////////////////////////////////////////////////
436 | value: function prepareProps(thisProps) {
437 | var props = assign({}, thisProps);
438 |
439 | props.className = this.prepareClassName(props);
440 | props.style = this.prepareStyle(props);
441 |
442 | return props;
443 | }
444 | }, {
445 | key: 'prepareStyle',
446 | value: function prepareStyle(props) {
447 | var style = assign({}, props.style);
448 |
449 | if (props.height != null) {
450 | style.height = props.height;
451 | }
452 |
453 | if (props.width != null) {
454 | style.width = props.width;
455 | }
456 |
457 | if (props.normalizeStyles) {
458 | style = normalize(style);
459 | }
460 |
461 | return style;
462 | }
463 | }, {
464 | key: 'prepareClassName',
465 | value: function prepareClassName(props) {
466 | var className = props.className || '';
467 |
468 | if (Scroller.className) {
469 | className += ' ' + Scroller.className;
470 | }
471 |
472 | return className;
473 | }
474 | }]);
475 |
476 | return Scroller;
477 | })(_Component3['default']);
478 |
479 | Scroller.className = 'z-scroller';
480 | Scroller.displayName = DISPLAY_NAME;
481 |
482 | assign(Scroller.prototype, {
483 | onVerticalScroll: onScroll('vertical'),
484 | onHorizontalScroll: onScroll('horizontal'),
485 |
486 | verticalScrollAt: scrollAt('vertical'),
487 | horizontalScrollAt: scrollAt('horizontal'),
488 |
489 | syncHorizontalScrollbar: syncHorizontalScrollbar,
490 | syncVerticalScrollbar: syncVerticalScrollbar
491 | });
492 |
493 | Scroller.propTypes = {
494 | loadMask: PT.oneOfType([PT.bool, PT.func]),
495 |
496 | loading: PT.bool,
497 | normalizeStyles: PT.bool,
498 |
499 | scrollTop: PT.number,
500 | scrollLeft: PT.number,
501 |
502 | scrollWidth: PT.number.isRequired,
503 | scrollHeight: PT.number.isRequired,
504 |
505 | height: PT.number,
506 | width: PT.number,
507 |
508 | minScrollStep: PT.number,
509 | minHorizontalScrollStep: PT.number,
510 | minVerticalScrollStep: PT.number,
511 |
512 | virtualRendering: PT.oneOf([true]),
513 |
514 | preventDefaultVertical: PT.bool,
515 | preventDefaultHorizontal: PT.bool
516 | }, Scroller.defaultProps = {
517 | 'data-display-name': DISPLAY_NAME,
518 | loadMask: true,
519 |
520 | virtualRendering: true, //FOR NOW, only true is supported
521 | scrollbarSize: 20,
522 |
523 | scrollTop: 0,
524 | scrollLeft: 0,
525 |
526 | minScrollStep: 10,
527 |
528 | minHorizontalScrollStep: IS_FIREFOX ? 40 : 1,
529 |
530 | //since FF goes back in browser history on scroll too soon
531 | //chrome and others also do this, but the normal preventDefault in syncScrollbar fn prevents this
532 | preventDefaultHorizontal: IS_FIREFOX
533 | };
534 |
535 | exports['default'] = Scroller;
536 | module.exports = exports['default'];
537 |
538 | // overflowCallback = this.props[ON_OVERFLOW_NAMES[orientation]]
539 | // overflowCallback && overflowCallback(signum(scrollPos), newScrollPos)
540 | // raf(function(){
541 | // this.mouseWheelScroll = false
542 | // }.bind(this))
--------------------------------------------------------------------------------
/dist/react-virtual-scroller.min.js:
--------------------------------------------------------------------------------
1 | !function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e(require("React")):"function"==typeof define&&define.amd?define(["React"],e):"object"==typeof exports?exports.Scroller=e(require("React")):t.Scroller=e(t.React)}(this,function(t){return function(t){function e(i){if(n[i])return n[i].exports;var r=n[i]={exports:{},id:i,loaded:!1};return t[i].call(r.exports,r,r.exports,e),r.loaded=!0,r.exports}var n={};return e.m=t,e.c=n,e.p="",e(0)}([function(t,e,n){(function(i){"use strict";var r=function(t){return t&&t.__esModule?t:{"default":t}},o=function(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")},s=function(){function t(t,e){for(var n=0;nt?-1:1},y=function(){},S=Math.abs,x=h.createFactory(f),_={},w=i&&i.navigator&&i.navigator.appVersion&&-1!=i.navigator.appVersion.indexOf("Mac"),z=i&&i.navigator&&i.navigator.userAgent&&!!~i.navigator.userAgent.toLowerCase().indexOf("firefox");w&&(_.position="absolute",_.height=20);var P=h.PropTypes,N="Scroller",E={vertical:"onVerticalScrollOverflow",horizontal:"onHorizontalScrollOverflow"},T={vertical:"onVerticalScroll",horizontal:"onHorizontalScroll"},O=function(t){return function(e,n){var i="horizontal"==t?this.getHorizontalScrollbarNode():this.getVerticalScrollbarNode(),r="horizontal"==t?"scrollLeft":"scrollTop";i[r]=e;var o=i[r];o!=e||m(n)}},C=O("horizontal"),L=O("vertical"),R=function(t){var e="horizontal"==t?C:L;return function(t,n){e.call(this,Math.round(t),n)}},k=function(t){var e={vertical:"clientHeight",horizontal:"clientWidth"},n={vertical:"scrollHeight",horizontal:"scrollWidth"};return function(i){var r="horizontal"==t?"scrollLeft":"scrollTop",o=i.target,s=o[r],a=this.props[T[t]],l=this.props[E[t]];l&&(0==s?l(-1,s):s+o[e[t]]>=o[n[t]]&&l(1,s)),(a||y)(s)}},H=function(t){function e(){o(this,e),null!=t&&t.apply(this,arguments)}return a(e,t),s(e,[{key:"render",value:function(){var t=this.p=this.prepareProps(this.props),e=this.renderLoadMask(t),n=this.renderHorizontalScrollbar(t),i=this.renderVerticalScrollbar(t),r={};v?r.onTouchStart=this.handleTouchStart:r.onWheel=this.handleWheel;var o=h.createElement("div",{className:"z-content-wrapper-fix",style:{maxWidth:"calc(100% - "+t.scrollbarSize+"px)"},children:t.children}),s=this.prepareRenderProps(t);return h.createElement("div",s,e,h.createElement("div",l({className:"z-content-wrapper"},r),o,i),n)}},{key:"prepareRenderProps",value:function(t){var e=p({},t);return delete e.height,delete e.width,e}},{key:"handleTouchStart",value:function(t){var e,n,i=this.props,r={top:i.scrollTop,left:i.scrollLeft};g(t,{scope:this,onDrag:function(t,i){if(0!=i.diff.top||0!=i.diff.left){n||(n=S(i.diff.top)>S(i.diff.left)?"top":"left");var o=i.diff[n];e=r[n]-o,"top"==n?this.verticalScrollAt(e,t):this.horizontalScrollAt(e,t)}}}),t.stopPropagation(),m(t)}},{key:"handleWheel",value:function(t){var e=this.props,n=(e.virtualRendering,t.shiftKey),i=(e.scrollStep,e.minScrollStep),r=e.scrollTop,o=e.scrollLeft,s=t.deltaY;n?(s=s||t.deltaX,i=e.minHorizontalScrollStep||i):i=e.minVerticalScrollStep||i,"function"==typeof e.interceptWheelScroll?s=e.interceptWheelScroll(s,normalizedEvent,t):i&&S(s)l;l++)if(n=s[l],e=n+o(t),"undefined"!=typeof i.style[e])return r=n;return r}},function(t,e,n){"use strict";t.exports=function(t){return t?t.charAt(0).toUpperCase()+t.slice(1):""}},function(t,e,n){"use strict";function i(t,e){function n(i){function r(){var r=arguments.length,o=[].concat(i);return r&&o.push.apply(o,arguments),o.lengthd;d++){if(f=n[d],h=a[d],c.alignToRegion(e,f),h&&(Array.isArray(h)||(h=a[d]=[h.x||h.left,h.y||h.top]),c.shift({left:h[0],top:h[1]})),!o)return t.set(c),f;if(p=c.getIntersection(o),p&&p.equals(c))return t.set(c),f;p&&(g=p.getArea())>m&&(m=g,b=d)}return~b?(f=n[b],h=a[b],c.alignToRegion(e,f),h&&c.shift({left:h[0],top:h[1]}),p=c.getIntersection(o),c.setRegion(p),c.alignToRegion(e,f),h&&c.shift({left:h[0],top:h[1]}),t.set(c),f):void 0}var r=n(1);t.exports=i},function(t,e,n){"use strict";function i(t,e,n,i){t=o.from(t);var s=t.clone(),a=r(s,e,n,i);return{position:a,region:s,widthChanged:s.getWidth()!=t.getWidth(),heightChanged:s.getHeight()!=t.getHeight(),positionChanged:s.equalsPosition(t)}}var r=n(14),o=n(1);t.exports=i},function(t,e,n){"use strict";var i=n(1);n(13),n(12);var r=n(15);i.alignRegions=function(t,e,n,i){var o=r(t,e,n,i),s=o.region;return s.equals(t)||t.setRegion(s),o.position},i.prototype.alignTo=function(t,e,n){n=n||{};var o=this,s=i.from(t),a=r(o,s,e,n),l=a.region;return l.equalsSize(o)||this.setSize(l.getSize()),l.equalsPosition(o)||this.setPosition(l.getPosition(),{absolute:!!n.absolute}),a.position},t.exports=i},function(t,e,n){t.exports=function(){"use strict";var t={};return function(e){if(!t[e]){for(var n=[],i=0;e>i;i++)n.push("a["+i+"]");t[e]=new Function("c","a","return new c("+n.join(",")+")")}return t[e]}}()},function(t,e,n){var i=n(17);t.exports=function(t,e){return i(e.length)(t,e)}},function(t,e,n){"use strict";function i(t,e,n){return t&&n.forEach(function(n){r(t,n)&&(e[n]=t[n])}),e}var r=n(7),o=n(18),s=n(2),a=n(35).EventEmitter,l=n(20),u=n(8),c=Object.prototype.toString,h=function(t){return"[object Object]"===c.apply(t)},f={cy:"YCenter",cx:"XCenter",t:"Top",tc:"TopCenter",tl:"TopLeft",tr:"TopRight",b:"Bottom",bc:"BottomCenter",bl:"BottomLeft",br:"BottomRight",l:"Left",lc:"LeftCenter",r:"Right",rc:"RightCenter",c:"Center"},p=function(t,e,n,r){return this instanceof p?(a.call(this),h(t)?(i(t,this,["top","right","bottom","left"]),null==t.bottom&&null!=t.height&&(this.bottom=this.top+t.height),null==t.right&&null!=t.width&&(this.right=this.left+t.width),t.emitChangeEvents&&(this.emitChangeEvents=t.emitChangeEvents)):(this.top=t,this.right=e,this.bottom=n,this.left=r),this[0]=this.left,this[1]=this.top,void u(this)):o(p,arguments)};l(p,a),s(p.prototype,{emitChangeEvents:!1,getRegion:function(t){return t?this.clone():this},setRegion:function(t){return this.set(t instanceof p?t.get():t),this},validate:function(){return p.validate(this)},_before:function(){return this.emitChangeEvents?i(this,{},["left","top","bottom","right"]):void 0},_after:function(t){this.emitChangeEvents&&((this.top!=t.top||this.left!=t.left)&&this.emitPositionChange(),(this.right!=t.right||this.bottom!=t.bottom)&&this.emitSizeChange())},notifyPositionChange:function(){this.emit("changeposition",this)},emitPositionChange:function(){this.notifyPositionChange()},notifySizeChange:function(){this.emit("changesize",this)},emitSizeChange:function(){this.notifySizeChange()},add:function(t){var e,n=this._before();for(e in t)r(t,e)&&(this[e]+=t[e]);return this[0]=this.left,this[1]=this.top,this._after(n),this},substract:function(t){var e,n=this._before();for(e in t)r(t,e)&&(this[e]-=t[e]);return this[0]=this.left,this[1]=this.top,this._after(n),this},getSize:function(){return{width:this.width,height:this.height}},setPosition:function(t){var e=this.width,n=this.height;return void 0!=t.left&&(t.right=t.left+e),void 0!=t.top&&(t.bottom=t.top+n),this.set(t)},setSize:function(t){return void 0!=t.height&&void 0!=t.width?this.set({right:this.left+t.width,bottom:this.top+t.height}):(void 0!=t.width&&this.setWidth(t.width),void 0!=t.height&&this.setHeight(t.height),this)},setWidth:function(t){return this.set({right:this.left+t})},setHeight:function(t){return this.set({bottom:this.top+t})},set:function(t){var e=this._before();return i(t,this,["left","top","bottom","right"]),null==t.bottom&&null!=t.height&&(this.bottom=this.top+t.height),null==t.right&&null!=t.width&&(this.right=this.left+t.width),this[0]=this.left,this[1]=this.top,this._after(e),this},get:function(t){return t?this[t]:i(this,{},["left","right","top","bottom"])},shift:function(t){var e=this._before();return t.top&&(this.top+=t.top,this.bottom+=t.top),t.left&&(this.left+=t.left,this.right+=t.left),this[0]=this.left,this[1]=this.top,this._after(e),this},unshift:function(t){return t.top&&(t.top*=-1),t.left&&(t.left*=-1),this.shift(t)},equals:function(t){return this.equalsPosition(t)&&this.equalsSize(t)},equalsSize:function(t){var e=t instanceof p,n={width:null==t.width&&e?t.getWidth():t.width,height:null==t.height&&e?t.getHeight():t.height};return this.getWidth()==n.width&&this.getHeight()==n.height},equalsPosition:function(t){return this.top==t.top&&this.left==t.left},addLeft:function(t){var e=this._before();return this.left=this[0]=this.left+t,this._after(e),this},addTop:function(t){var e=this._before();return this.top=this[1]=this.top+t,this._after(e),this},addBottom:function(t){var e=this._before();return this.bottom+=t,this._after(e),this},addRight:function(t){var e=this._before();return this.right+=t,this._after(e),this},minTop:function(){return this.expand({top:1})},maxBottom:function(){return this.expand({bottom:1})},minLeft:function(){return this.expand({left:1})},maxRight:function(){return this.expand({right:1})},expand:function(t,e){var n,o=e||p.getDocRegion(),s=[],a=this._before();for(n in t)r(t,n)&&s.push(n);return i(o,this,s),this[0]=this.left,this[1]=this.top,this._after(a),this},clone:function(){return new p({top:this.top,left:this.left,right:this.right,bottom:this.bottom})},containsPoint:function(t,e){return 1==arguments.length&&(e=t.y,t=t.x),this.left<=t&&t<=this.right&&this.top<=e&&e<=this.bottom},containsRegion:function(t){return this.containsPoint(t.left,t.top)&&this.containsPoint(t.right,t.bottom)},diffHeight:function(t){return this.diff(t,{top:!0,bottom:!0})},diffWidth:function(t){return this.diff(t,{left:!0,right:!0})},diff:function(t,e){var n,i={};for(n in e)r(e,n)&&(i[n]=this[n]-t[n]);return i},getPosition:function(){return{left:this.left,top:this.top}},getPoint:function(t,e){f[t]||console.warn("The position ",t," could not be found! Available options are tl, bl, tr, br, l, r, t, b.");var n="getPoint"+f[t],i=this[n]();return e?{left:i.x,top:i.y}:i},getPointYCenter:function(){return{x:null,y:this.top+this.getHeight()/2}},getPointXCenter:function(){return{x:this.left+this.getWidth()/2,y:null}},getPointTop:function(){return{x:null,y:this.top}},getPointTopCenter:function(){return{x:this.left+this.getWidth()/2,y:this.top}},getPointTopLeft:function(){return{x:this.left,y:this.top}},getPointTopRight:function(){return{x:this.right,y:this.top}},getPointBottom:function(){return{x:null,y:this.bottom}},getPointBottomCenter:function(){return{x:this.left+this.getWidth()/2,y:this.bottom}},getPointBottomLeft:function(){return{x:this.left,y:this.bottom}},getPointBottomRight:function(){return{x:this.right,y:this.bottom}},getPointLeft:function(){return{x:this.left,y:null}},getPointLeftCenter:function(){return{x:this.left,y:this.top+this.getHeight()/2}},getPointRight:function(){return{x:this.right,y:null}},getPointRightCenter:function(){return{x:this.right,y:this.top+this.getHeight()/2}},getPointCenter:function(){return{x:this.left+this.getWidth()/2,y:this.top+this.getHeight()/2}},getHeight:function(){return this.bottom-this.top},getWidth:function(){return this.right-this.left},getTop:function(){return this.top},getLeft:function(){return this.left},getBottom:function(){return this.bottom},getRight:function(){return this.right},getArea:function(){return this.getWidth()*this.getHeight()},constrainTo:function(t){var e,n=this.getIntersection(t);if(!n||!n.equals(this)){var i=t.getWidth(),r=t.getHeight();return this.getWidth()>i&&(this.left=t.left,this.setWidth(i)),this.getHeight()>r&&(this.top=t.top,this.setHeight(r)),e={},this.right>t.right&&(e.left=t.right-this.right),this.bottom>t.bottom&&(e.top=t.bottom-this.bottom),this.lefto?r-o:0},getIntersectionHeight:function(t,i){var r=e(t.top,i.top),o=n(t.bottom,i.bottom);return o>r?o-r:0},getIntersectionArea:function(t,i){var r=e(t.top,i.top),o=n(t.right,i.right),s=n(t.bottom,i.bottom),a=e(t.left,i.left);return s>r&&o>a?{top:r,right:o,bottom:s,left:a,width:o-a,height:s-r}:!1},getUnion:function(i,r){var o=n(i.top,r.top),s=e(i.right,r.right),a=e(i.bottom,r.bottom),l=n(i.left,r.left);return new t(o,s,a,l)},getRegion:function(e){return t.from(e)},fromPoint:function(e){return new t({top:e.y,bottom:e.y,left:e.x,right:e.x})}};Object.keys(o).forEach(function(e){t[e]=o[e]}),t.init()}},function(t,e,n){"use once";t.exports=function(t,e){var n,i;return function(){return n?i:(n=!0,i=t.apply(e||this,arguments))}}},function(t,e,n){"use strict";function i(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}function r(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Super expression must either be null or a function, not "+typeof e);t.prototype=Object.create(e&&e.prototype,{constructor:{value:t,enumerable:!1,writable:!0,configurable:!0}}),e&&(t.__proto__=e)}function o(t){var e=t.constructor.prototype,n=Object.getOwnPropertyNames(e).filter(function(t){return"constructor"!=t&&"render"!=t&&"function"==typeof e[t]});return n.push("setState"),n.forEach(function(e){t[e]=t[e].bind(t)}),t}var s=function(){function t(t,e){for(var n=0;nt||isNaN(t))throw TypeError("n must be a positive number");return this._maxListeners=t,this},i.prototype.emit=function(t){var e,n,i,o,l,u;if(this._events||(this._events={}),"error"===t&&(!this._events.error||s(this._events.error)&&!this._events.error.length)){if(e=arguments[1],e instanceof Error)throw e;throw TypeError('Uncaught, unspecified "error" event.')}if(n=this._events[t],a(n))return!1;if(r(n))switch(arguments.length){case 1:n.call(this);break;case 2:n.call(this,arguments[1]);break;case 3:n.call(this,arguments[1],arguments[2]);break;default:for(i=arguments.length,o=new Array(i-1),l=1;i>l;l++)o[l-1]=arguments[l];n.apply(this,o)}else if(s(n)){for(i=arguments.length,o=new Array(i-1),l=1;i>l;l++)o[l-1]=arguments[l];for(u=n.slice(),i=u.length,l=0;i>l;l++)u[l].apply(this,o)}return!0},i.prototype.addListener=function(t,e){var n;if(!r(e))throw TypeError("listener must be a function");if(this._events||(this._events={}),this._events.newListener&&this.emit("newListener",t,r(e.listener)?e.listener:e),this._events[t]?s(this._events[t])?this._events[t].push(e):this._events[t]=[this._events[t],e]:this._events[t]=e,s(this._events[t])&&!this._events[t].warned){var n;n=a(this._maxListeners)?i.defaultMaxListeners:this._maxListeners,n&&n>0&&this._events[t].length>n&&(this._events[t].warned=!0,console.error("(node) warning: possible EventEmitter memory leak detected. %d listeners added. Use emitter.setMaxListeners() to increase limit.",this._events[t].length),"function"==typeof console.trace&&console.trace())}return this},i.prototype.on=i.prototype.addListener,i.prototype.once=function(t,e){function n(){this.removeListener(t,n),i||(i=!0,e.apply(this,arguments))}if(!r(e))throw TypeError("listener must be a function");var i=!1;return n.listener=e,this.on(t,n),this},i.prototype.removeListener=function(t,e){var n,i,o,a;if(!r(e))throw TypeError("listener must be a function");if(!this._events||!this._events[t])return this;if(n=this._events[t],o=n.length,i=-1,n===e||r(n.listener)&&n.listener===e)delete this._events[t],this._events.removeListener&&this.emit("removeListener",t,e);else if(s(n)){for(a=o;a-->0;)if(n[a]===e||n[a].listener&&n[a].listener===e){i=a;break}if(0>i)return this;1===n.length?(n.length=0,delete this._events[t]):n.splice(i,1),this._events.removeListener&&this.emit("removeListener",t,e)}return this},i.prototype.removeAllListeners=function(t){var e,n;if(!this._events)return this;if(!this._events.removeListener)return 0===arguments.length?this._events={}:this._events[t]&&delete this._events[t],this;if(0===arguments.length){for(e in this._events)"removeListener"!==e&&this.removeAllListeners(e);return this.removeAllListeners("removeListener"),this._events={},this}if(n=this._events[t],r(n))this.removeListener(t,n);
2 | else for(;n.length;)this.removeListener(t,n[n.length-1]);return delete this._events[t],this},i.prototype.listeners=function(t){var e;return e=this._events&&this._events[t]?r(this._events[t])?[this._events[t]]:this._events[t].slice():[]},i.listenerCount=function(t,e){var n;return n=t._events&&t._events[e]?r(t._events[e])?1:t._events[e].length:0}}])});
--------------------------------------------------------------------------------