├── 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
319 |
324 |
325 |
326 |
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}}])}); --------------------------------------------------------------------------------