├── .gitignore ├── .jshintrc ├── .travis.yml ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── bower.json ├── browserify.js ├── component.json ├── dist ├── slideout.js └── slideout.min.js ├── index.css ├── index.js ├── package.json └── test ├── assets └── menu.png ├── index.html ├── index.right.html ├── test.css └── test.js /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | bower_components 4 | coverage 5 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "bitwise": false, 3 | "curly": true, 4 | "eqeqeq": true, 5 | "expr": true, 6 | "forin": true, 7 | "freeze": true, 8 | "funcscope": true, 9 | "globalstrict": true, 10 | "nonbsp": true, 11 | "unused": true, 12 | "debug": true, 13 | "evil": true, 14 | "lastsemic": true, 15 | "loopfunc": true, 16 | "proto": true, 17 | "scripturl": true, 18 | "strict": true, 19 | "browser": true, 20 | "browserify": true, 21 | "node": true 22 | } 23 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 6.9.1 4 | # Send coverage data to Coveralls 5 | after_script: "cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js" 6 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | ## Code style 4 | Regarding code style like indentation and whitespace, **follow the conventions you see used in the source already.** 5 | 6 | ## Modifying the code 7 | 8 | Please don't edit `/dist/slideout.js` and `/dist/slideout.min.js` files. You'll find source code in the `index.js` file. 9 | 10 | 1. Fork and clone the repo. 11 | 2. Run `npm install` to install all dependencies. 12 | 3. Create a new branch, please don't work in your `master` branch directly. 13 | 4. Open the file `/test/index.html`. 14 | 5. Code! 15 | 16 | ### Running the tests 17 | 18 | - Run `npm test` from your command line and check the console 19 | - Open the `/test/index.html` file to run test in your browser. 20 | 21 | ## Pull requests 22 | 23 | 1. Create a new branch, **please don't work in your `master` branch directly**. 24 | 2. Code! 25 | 3. Update the tests and run `npm test` to see the tests. 26 | 4. Run `npm run hint`. 27 | 5. Run `npm run dist` to build a new version. 28 | 6. Update the documentation and package to reflect any changes. 29 | 7. Push to your fork and submit a pull request. -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Mango 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Slideout.js 2 | 3 | [![NPM version][npm-image]][npm-link] [![License][lic-image]][npm-link] [![Build status][travis-image]][travis-link] [![Coverage Status][coverage-image]][coverage-link] [![Dependency status][deps-image]][deps-link] [![devDependency status][devdeps-image]][devdeps-link] [![downloads][dt-image]][npm-link] 4 | 5 | > A touch slideout navigation menu for your mobile web apps. 6 | 7 | ## Features 8 | 9 | - Dependency-free. 10 | - Simple markup. 11 | - Native scrolling. 12 | - Easy customization. 13 | - CSS transforms & transitions. 14 | - Just 2 Kb! (min & gzip) 15 | 16 | ## Demo 17 | 18 | [Check out the demo](https://mango.github.io/slideout/) to see it in action (on your mobile or emulate touches on your browser). 19 | 20 | Slideout.js demo 21 | 22 | ## Installation 23 | 24 | Slideout is available on cdnjs 25 | 26 | ```html 27 | 28 | ``` 29 | 30 | Also you can use one of many package managers 31 | 32 | $ npm install slideout 33 | 34 | $ spm install slideout 35 | 36 | $ bower install slideout.js 37 | 38 | $ component install mango/slideout 39 | 40 | ## Usage 41 | 42 | Implementing Slideout.js into your project is easy. 43 | 44 | First of all, you'll need to create your markup. You should have a menu (`#menu`) and a main content (`#panel`) into your body. 45 | 46 | ```html 47 | 52 | 53 |
54 |
55 |

Panel

56 |
57 |
58 | ``` 59 | 60 | Add the Slideout.js styles (index.css) in your web application. 61 | 62 | ```css 63 | body { 64 | width: 100%; 65 | height: 100%; 66 | } 67 | 68 | .slideout-menu { 69 | position: fixed; 70 | top: 0; 71 | bottom: 0; 72 | width: 256px; 73 | min-height: 100vh; 74 | overflow-y: scroll; 75 | -webkit-overflow-scrolling: touch; 76 | z-index: 0; 77 | display: none; 78 | } 79 | 80 | .slideout-menu-left { 81 | left: 0; 82 | } 83 | 84 | .slideout-menu-right { 85 | right: 0; 86 | } 87 | 88 | .slideout-panel { 89 | position: relative; 90 | z-index: 1; 91 | will-change: transform; 92 | background-color: #FFF; /* A background-color is required */ 93 | min-height: 100vh; 94 | } 95 | 96 | .slideout-open, 97 | .slideout-open body, 98 | .slideout-open .slideout-panel { 99 | overflow: hidden; 100 | } 101 | 102 | .slideout-open .slideout-menu { 103 | display: block; 104 | } 105 | ``` 106 | 107 | Then you just include Slideout.js, create a new instance with some options and call the toggle method: 108 | 109 | ```html 110 | 111 | 124 | ``` 125 | 126 | #### Full example 127 | 128 | ```html 129 | 130 | 131 | 132 | 133 | Slideout Demo 134 | 135 | 136 | 137 | 138 | 139 | 174 | 175 | 176 | 177 | 180 | 181 |
182 |
183 | 184 |

Panel

185 |
186 |
187 | 188 | 189 | 202 | 203 | 204 | 205 | ``` 206 | 207 | ## Browser Support 208 | 209 | - Chrome (IOS, Android, desktop) 210 | - Firefox (Android, desktop) 211 | - Safari (IOS, Android, desktop) 212 | - Opera (desktop) 213 | - IE 10+ (desktop and mobile) 214 | 215 | ## API 216 | 217 | ### Slideout(options) 218 | Create a new instance of `Slideout`. 219 | 220 | - `options` (Object) - Options to customize a new instance of Slideout. 221 | - `options.panel` (HTMLElement) - The DOM element that contains all your application content (`.slideout-panel`). 222 | - `options.menu` (HTMLElement) - The DOM element that contains your menu application (`.slideout-menu`). 223 | - `[options.duration]` (Number) - The time (milliseconds) to open/close the slideout. Default: `300`. 224 | - `[options.easing]` (String) - The CSS effect to use when animating the opening and closing of the slideout. Default: `ease`. Possible values: 225 | - `ease` 226 | - `linear` 227 | - `ease-in` 228 | - `ease-out` 229 | - `ease-in-out` 230 | - `step-start` 231 | - `step-end` 232 | - [`cubic-bezier`](http://cubic-bezier.com/) 233 | - `[options.padding]` (Number) - Default: `256`. 234 | - `[options.tolerance]` (Number) - The number of `px` needed for the menu can be opened completely, otherwise it closes. Default: `70`. 235 | - `[options.touch]` (Boolean) - Set this option to false to disable Slideout touch events. Default: `true`. 236 | - `[options.side]` (String) - The side to open the slideout (`left` or `right`). Default: `left`. 237 | 238 | ```js 239 | var slideout = new Slideout({ 240 | 'panel': document.getElementById('main'), 241 | 'menu': document.getElementById('menu'), 242 | 'padding': 256, 243 | 'tolerance': 70, 244 | 'easing': 'cubic-bezier(.32,2,.55,.27)' 245 | }); 246 | ``` 247 | 248 | ### Slideout.open(); 249 | Opens the slideout menu. It emits `beforeopen` and `open` events. 250 | 251 | ```js 252 | slideout.open(); 253 | ``` 254 | 255 | ### Slideout.close(); 256 | Closes the slideout menu. It emits `beforeclose` and `close` events. 257 | 258 | ```js 259 | slideout.close(); 260 | ``` 261 | 262 | ### Slideout.toggle(); 263 | Toggles (open/close) the slideout menu. 264 | 265 | ```js 266 | slideout.toggle(); 267 | ``` 268 | 269 | ### Slideout.isOpen(); 270 | Returns `true` if the slideout is currently open, and `false` if it is closed. 271 | 272 | ```js 273 | slideout.isOpen(); // true or false 274 | ``` 275 | 276 | ### Slideout.destroy(); 277 | Cleans up the instance so another slideout can be created on the same area. 278 | 279 | ```js 280 | slideout.destroy(); 281 | ``` 282 | 283 | ### Slideout.enableTouch(); 284 | Enables opening the slideout via touch events. 285 | 286 | ```js 287 | slideout.enableTouch(); 288 | ``` 289 | 290 | ### Slideout.disableTouch(); 291 | Disables opening the slideout via touch events. 292 | 293 | ```js 294 | slideout.disableTouch(); 295 | ``` 296 | 297 | ### Slideout.on(event, listener); 298 | ```js 299 | slideout.on('open', function() { ... }); 300 | ``` 301 | 302 | ### Slideout.once(event, listener); 303 | ```js 304 | slideout.once('open', function() { ... }); 305 | ``` 306 | 307 | ### Slideout.off(event, listener); 308 | ```js 309 | slideout.off('open', listener); 310 | ``` 311 | 312 | ### Slideout.emit(event, ...data); 313 | ```js 314 | slideout.emit('open'); 315 | ``` 316 | 317 | ## Events 318 | 319 | An instance of Slideout emits the following events: 320 | 321 | - `beforeclose` 322 | - `close` 323 | - `beforeopen` 324 | - `open` 325 | - `translatestart` 326 | - `translate` 327 | - `translateend` 328 | 329 | The slideout emits `translatestart`, `translate` and `translateend` events only when it is opening/closing via touch events. 330 | 331 | ```js 332 | slideout.on('translatestart', function() { 333 | console.log('Start'); 334 | }); 335 | 336 | slideout.on('translate', function(translated) { 337 | console.log('Translate: ' + translated); // 120 in px 338 | }); 339 | 340 | slideout.on('translateend', function() { 341 | console.log('End'); 342 | }); 343 | 344 | // 'Start' 345 | // 'Translate 120' 346 | // 'End' 347 | ``` 348 | 349 | ## `data-slideout-ignore` attribute 350 | You can use the special HTML attribute `data-slideout-ignore` to disable dragging on some elements. For example, if you have to prevent `slideout` will open when touch on carousels, maps, iframes, etc. 351 | 352 | ```html 353 |
354 |
355 |

Panel

356 |
357 | 361 |
362 | ``` 363 | 364 | ## npm-scripts 365 | ``` 366 | $ npm run build 367 | ``` 368 | 369 | ``` 370 | $ npm run dist 371 | ``` 372 | 373 | ``` 374 | $ npm test 375 | ``` 376 | 377 | ``` 378 | $ npm run hint 379 | ``` 380 | 381 | ## FAQ 382 | 383 | ### How to add a toggle button. 384 | 385 | ```js 386 | // vanilla js 387 | document.querySelector('.toggle-button').addEventListener('click', function() { 388 | slideout.toggle(); 389 | }); 390 | 391 | // jQuery 392 | $('.toggle-button').on('click', function() { 393 | slideout.toggle(); 394 | }); 395 | ``` 396 | 397 | ### How to open slideout from right side. 398 | 399 | You should use the `side` option with the value `right`. 400 | ```js 401 | var slideout = new Slideout({ 402 | 'panel': document.getElementById('content'), 403 | 'menu': document.getElementById('menu'), 404 | 'side': 'right' 405 | }); 406 | ``` 407 | 408 | ### How to enable slideout only on mobile devices. 409 | 410 | You should use `mediaqueries`: 411 | ```css 412 | @media screen and (min-width: 780px) { 413 | .slideout-panel { 414 | margin-left: 256px; 415 | } 416 | 417 | .slideout-menu { 418 | display: block; 419 | } 420 | 421 | .btn-hamburger { 422 | display: none; 423 | } 424 | } 425 | ``` 426 | Demo: http://codepen.io/pazguille/pen/mEdQvX 427 | 428 | ### How to use slideout with a fixed header. 429 | 430 | First, you should define the styles for your fixed header: 431 | ```css 432 | .fixed-header { 433 | position: fixed; 434 | width: 100%; 435 | height: 50px; 436 | backface-visibility: hidden; 437 | z-index: 2; 438 | background-color: red; 439 | } 440 | ``` 441 | 442 | Then, using slideout's events you should translate the fixed header: 443 | ```js 444 | var fixed = document.querySelector('.fixed-header'); 445 | 446 | slideout.on('translate', function(translated) { 447 | fixed.style.transform = 'translateX(' + translated + 'px)'; 448 | }); 449 | 450 | slideout.on('beforeopen', function () { 451 | fixed.style.transition = 'transform 300ms ease'; 452 | fixed.style.transform = 'translateX(256px)'; 453 | }); 454 | 455 | slideout.on('beforeclose', function () { 456 | fixed.style.transition = 'transform 300ms ease'; 457 | fixed.style.transform = 'translateX(0px)'; 458 | }); 459 | 460 | slideout.on('open', function () { 461 | fixed.style.transition = ''; 462 | }); 463 | 464 | slideout.on('close', function () { 465 | fixed.style.transition = ''; 466 | }); 467 | ``` 468 | 469 | Demo: http://codepen.io/pazguille/pen/ZBxdgw 470 | 471 | 472 | ### How to disable dragging on some elements. 473 | You can use the attribute `data-slideout-ignore` to disable dragging on some elements: 474 | 475 | ```html 476 | 481 | 482 |
483 |
484 |

Panel

485 |
486 | 490 |
491 | ``` 492 | 493 | ### How to add an overlay to close the menu on click. 494 | You can do that using the powerful `slideout` API and a little extra CSS: 495 | 496 | ```css 497 | .panel:before { 498 | content: ''; 499 | display: block; 500 | background-color: rgba(0,0,0,0); 501 | transition: background-color 0.5s ease-in-out; 502 | } 503 | 504 | .panel-open:before { 505 | position: absolute; 506 | top: 0; 507 | bottom: 0; 508 | width: 100%; 509 | background-color: rgba(0,0,0,.5); 510 | z-index: 99; 511 | } 512 | ``` 513 | 514 | ```js 515 | function close(eve) { 516 | eve.preventDefault(); 517 | slideout.close(); 518 | } 519 | 520 | slideout 521 | .on('beforeopen', function() { 522 | this.panel.classList.add('panel-open'); 523 | }) 524 | .on('open', function() { 525 | this.panel.addEventListener('click', close); 526 | }) 527 | .on('beforeclose', function() { 528 | this.panel.classList.remove('panel-open'); 529 | this.panel.removeEventListener('click', close); 530 | }); 531 | ``` 532 | 533 | Demo: http://codepen.io/pazguille/pen/BQYRYK 534 | 535 | ## With :heart: by 536 | - Guille Paz (Front-end developer | Web standards lover) 537 | - E-mail: [guille87paz@gmail.com](mailto:guille87paz@gmail.com) 538 | - Twitter: [@pazguille](http://twitter.com/pazguille) 539 | - Web: [http://pazguille.me](http://pazguille.me) 540 | 541 | ## License 542 | MIT license. Copyright © 2015 [Mango](http://getmango.com). 543 | 544 | [npm-image]: https://img.shields.io/npm/v/slideout.svg 545 | [lic-image]: https://img.shields.io/npm/l/slideout.svg 546 | [npm-link]: https://npmjs.org/package/slideout 547 | [travis-image]: https://img.shields.io/travis/Mango/slideout.svg 548 | [travis-link]: https://travis-ci.org/Mango/slideout 549 | [deps-image]: https://img.shields.io/david/mango/slideout.svg 550 | [deps-link]: https://david-dm.org/mango/slideout 551 | [devdeps-image]: https://img.shields.io/david/dev/mango/slideout.svg 552 | [devdeps-link]: https://david-dm.org/mango/slideout#info=devDependencies 553 | [dt-image]: https://img.shields.io/npm/dt/slideout.svg 554 | [coverage-image]: https://img.shields.io/coveralls/Mango/slideout.svg 555 | [coverage-link]: https://coveralls.io/github/Mango/slideout 556 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "slideout.js", 3 | "repository": "https://github.com/Mango/slideout", 4 | "description": "A touch slideout navigation menu for your mobile web apps.", 5 | "author": "Guille Paz ", 6 | "dependencies": { 7 | "decouple": "0.0.2", 8 | "emitter-es6": "0.0.7" 9 | }, 10 | "ignore": [ 11 | ".*", 12 | "package.json", 13 | "component.json", 14 | "node_modules", 15 | "browserify.js", 16 | "**/.*", 17 | "bower_components", 18 | "test", 19 | "demo" 20 | ], 21 | "homepage": "https://github.com/mango/slideout", 22 | "moduleType": [ 23 | "adecouplemd", 24 | "globals", 25 | "node" 26 | ], 27 | "keywords": [ 28 | "slideout", 29 | "offcanvas", 30 | "menu", 31 | "touch" 32 | ], 33 | "main": "dist/slideout.js", 34 | "license": "MIT" 35 | } 36 | -------------------------------------------------------------------------------- /browserify.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var fs = require('fs'); 4 | var browserify = require('browserify'); 5 | 6 | if (!fs.existsSync('./dist')) { 7 | fs.mkdirSync('./dist'); 8 | } 9 | 10 | browserify({'debug': true, 'standalone': 'Slideout'}) 11 | .require('./index.js', {'entry': true}) 12 | .bundle() 13 | .on('error', function(err) { console.log('Error : ' + err.message); }) 14 | .pipe(fs.createWriteStream('dist/slideout.js')); -------------------------------------------------------------------------------- /component.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "slideout", 3 | "repo": "mango/slideout", 4 | "description": "A touch slideout navigation menu for your mobile web apps.", 5 | "author": "Guille Paz ", 6 | "version": "1.0.1", 7 | "twitter": "@mango", 8 | "keywords": [ 9 | "slideout", 10 | "offcanvas", 11 | "menu", 12 | "touch" 13 | ], 14 | "dependencies": { 15 | "pazguille/decouple": "0.0.2", 16 | "Mango/emitter": "0.0.7" 17 | }, 18 | "development": {}, 19 | "license": "MIT", 20 | "scripts": [ 21 | "index.js" 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /dist/slideout.js: -------------------------------------------------------------------------------- 1 | !function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var f;"undefined"!=typeof window?f=window:"undefined"!=typeof global?f=global:"undefined"!=typeof self&&(f=self),f.Slideout=e()}}(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o self._tolerance) ? self.open() : self.close(); 245 | } 246 | self._moved = false; 247 | }; 248 | 249 | this.panel.addEventListener(touch.end, this._onTouchEndFn); 250 | 251 | /** 252 | * Translates panel on touchmove 253 | */ 254 | this._onTouchMoveFn = function(eve) { 255 | if ( 256 | scrolling || 257 | self._preventOpen || 258 | typeof eve.touches === 'undefined' || 259 | hasIgnoredElements(eve.target) 260 | ) { 261 | return; 262 | } 263 | 264 | var dif_x = eve.touches[0].clientX - self._startOffsetX; 265 | var translateX = self._currentOffsetX = dif_x; 266 | 267 | if (Math.abs(translateX) > self._padding) { 268 | return; 269 | } 270 | 271 | if (Math.abs(dif_x) > 20) { 272 | 273 | self._opening = true; 274 | 275 | var oriented_dif_x = dif_x * self._orientation; 276 | 277 | if (self._opened && oriented_dif_x > 0 || !self._opened && oriented_dif_x < 0) { 278 | return; 279 | } 280 | 281 | if (!self._moved) { 282 | self.emit('translatestart'); 283 | } 284 | 285 | if (oriented_dif_x <= 0) { 286 | translateX = dif_x + self._padding * self._orientation; 287 | self._opening = false; 288 | } 289 | 290 | if (!(self._moved && html.classList.contains('slideout-open'))) { 291 | html.classList.add('slideout-open'); 292 | } 293 | 294 | self.panel.style[prefix + 'transform'] = self.panel.style.transform = 'translateX(' + translateX + 'px)'; 295 | self.emit('translate', translateX); 296 | self._moved = true; 297 | } 298 | 299 | }; 300 | 301 | this.panel.addEventListener(touch.move, this._onTouchMoveFn); 302 | 303 | return this; 304 | }; 305 | 306 | /** 307 | * Enable opening the slideout via touch events. 308 | */ 309 | Slideout.prototype.enableTouch = function() { 310 | this._touch = true; 311 | return this; 312 | }; 313 | 314 | /** 315 | * Disable opening the slideout via touch events. 316 | */ 317 | Slideout.prototype.disableTouch = function() { 318 | this._touch = false; 319 | return this; 320 | }; 321 | 322 | /** 323 | * Destroy an instance of slideout. 324 | */ 325 | Slideout.prototype.destroy = function() { 326 | // Close before clean 327 | this.close(); 328 | 329 | // Remove event listeners 330 | doc.removeEventListener(touch.move, this._preventMove); 331 | this.panel.removeEventListener(touch.start, this._resetTouchFn); 332 | this.panel.removeEventListener('touchcancel', this._onTouchCancelFn); 333 | this.panel.removeEventListener(touch.end, this._onTouchEndFn); 334 | this.panel.removeEventListener(touch.move, this._onTouchMoveFn); 335 | doc.removeEventListener('scroll', this._onScrollFn); 336 | 337 | // Remove methods 338 | this.open = this.close = function() {}; 339 | 340 | // Return the instance so it can be easily dereferenced 341 | return this; 342 | }; 343 | 344 | /** 345 | * Expose Slideout 346 | */ 347 | module.exports = Slideout; 348 | 349 | },{"decouple":2,"emitter":3}],2:[function(require,module,exports){ 350 | 'use strict'; 351 | 352 | var requestAnimFrame = (function() { 353 | return window.requestAnimationFrame || 354 | window.webkitRequestAnimationFrame || 355 | function (callback) { 356 | window.setTimeout(callback, 1000 / 60); 357 | }; 358 | }()); 359 | 360 | function decouple(node, event, fn) { 361 | var eve, 362 | tracking = false; 363 | 364 | function captureEvent(e) { 365 | eve = e; 366 | track(); 367 | } 368 | 369 | function track() { 370 | if (!tracking) { 371 | requestAnimFrame(update); 372 | tracking = true; 373 | } 374 | } 375 | 376 | function update() { 377 | fn.call(node, eve); 378 | tracking = false; 379 | } 380 | 381 | node.addEventListener(event, captureEvent, false); 382 | 383 | return captureEvent; 384 | } 385 | 386 | /** 387 | * Expose decouple 388 | */ 389 | module.exports = decouple; 390 | 391 | },{}],3:[function(require,module,exports){ 392 | "use strict"; 393 | 394 | var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }; 395 | 396 | exports.__esModule = true; 397 | /** 398 | * Creates a new instance of Emitter. 399 | * @class 400 | * @returns {Object} Returns a new instance of Emitter. 401 | * @example 402 | * // Creates a new instance of Emitter. 403 | * var Emitter = require('emitter'); 404 | * 405 | * var emitter = new Emitter(); 406 | */ 407 | 408 | var Emitter = (function () { 409 | function Emitter() { 410 | _classCallCheck(this, Emitter); 411 | } 412 | 413 | /** 414 | * Adds a listener to the collection for the specified event. 415 | * @memberof! Emitter.prototype 416 | * @function 417 | * @param {String} event - The event name. 418 | * @param {Function} listener - A listener function to add. 419 | * @returns {Object} Returns an instance of Emitter. 420 | * @example 421 | * // Add an event listener to "foo" event. 422 | * emitter.on('foo', listener); 423 | */ 424 | 425 | Emitter.prototype.on = function on(event, listener) { 426 | // Use the current collection or create it. 427 | this._eventCollection = this._eventCollection || {}; 428 | 429 | // Use the current collection of an event or create it. 430 | this._eventCollection[event] = this._eventCollection[event] || []; 431 | 432 | // Appends the listener into the collection of the given event 433 | this._eventCollection[event].push(listener); 434 | 435 | return this; 436 | }; 437 | 438 | /** 439 | * Adds a listener to the collection for the specified event that will be called only once. 440 | * @memberof! Emitter.prototype 441 | * @function 442 | * @param {String} event - The event name. 443 | * @param {Function} listener - A listener function to add. 444 | * @returns {Object} Returns an instance of Emitter. 445 | * @example 446 | * // Will add an event handler to "foo" event once. 447 | * emitter.once('foo', listener); 448 | */ 449 | 450 | Emitter.prototype.once = function once(event, listener) { 451 | var self = this; 452 | 453 | function fn() { 454 | self.off(event, fn); 455 | listener.apply(this, arguments); 456 | } 457 | 458 | fn.listener = listener; 459 | 460 | this.on(event, fn); 461 | 462 | return this; 463 | }; 464 | 465 | /** 466 | * Removes a listener from the collection for the specified event. 467 | * @memberof! Emitter.prototype 468 | * @function 469 | * @param {String} event - The event name. 470 | * @param {Function} listener - A listener function to remove. 471 | * @returns {Object} Returns an instance of Emitter. 472 | * @example 473 | * // Remove a given listener. 474 | * emitter.off('foo', listener); 475 | */ 476 | 477 | Emitter.prototype.off = function off(event, listener) { 478 | 479 | var listeners = undefined; 480 | 481 | // Defines listeners value. 482 | if (!this._eventCollection || !(listeners = this._eventCollection[event])) { 483 | return this; 484 | } 485 | 486 | listeners.forEach(function (fn, i) { 487 | if (fn === listener || fn.listener === listener) { 488 | // Removes the given listener. 489 | listeners.splice(i, 1); 490 | } 491 | }); 492 | 493 | // Removes an empty event collection. 494 | if (listeners.length === 0) { 495 | delete this._eventCollection[event]; 496 | } 497 | 498 | return this; 499 | }; 500 | 501 | /** 502 | * Execute each item in the listener collection in order with the specified data. 503 | * @memberof! Emitter.prototype 504 | * @function 505 | * @param {String} event - The name of the event you want to emit. 506 | * @param {...Object} data - Data to pass to the listeners. 507 | * @returns {Object} Returns an instance of Emitter. 508 | * @example 509 | * // Emits the "foo" event with 'param1' and 'param2' as arguments. 510 | * emitter.emit('foo', 'param1', 'param2'); 511 | */ 512 | 513 | Emitter.prototype.emit = function emit(event) { 514 | var _this = this; 515 | 516 | for (var _len = arguments.length, args = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { 517 | args[_key - 1] = arguments[_key]; 518 | } 519 | 520 | var listeners = undefined; 521 | 522 | // Defines listeners value. 523 | if (!this._eventCollection || !(listeners = this._eventCollection[event])) { 524 | return this; 525 | } 526 | 527 | // Clone listeners 528 | listeners = listeners.slice(0); 529 | 530 | listeners.forEach(function (fn) { 531 | return fn.apply(_this, args); 532 | }); 533 | 534 | return this; 535 | }; 536 | 537 | return Emitter; 538 | })(); 539 | 540 | /** 541 | * Exports Emitter 542 | */ 543 | exports["default"] = Emitter; 544 | module.exports = exports["default"]; 545 | },{}]},{},[1])(1) 546 | }); 547 | //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIm5vZGVfbW9kdWxlcy9icm93c2VyLXBhY2svX3ByZWx1ZGUuanMiLCJpbmRleC5qcyIsIm5vZGVfbW9kdWxlcy9kZWNvdXBsZS9pbmRleC5qcyIsIm5vZGVfbW9kdWxlcy9lbWl0dGVyL2Rpc3QvaW5kZXguanMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7QUNBQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQzFWQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQ3hDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EiLCJmaWxlIjoiZ2VuZXJhdGVkLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXNDb250ZW50IjpbIihmdW5jdGlvbiBlKHQsbixyKXtmdW5jdGlvbiBzKG8sdSl7aWYoIW5bb10pe2lmKCF0W29dKXt2YXIgYT10eXBlb2YgcmVxdWlyZT09XCJmdW5jdGlvblwiJiZyZXF1aXJlO2lmKCF1JiZhKXJldHVybiBhKG8sITApO2lmKGkpcmV0dXJuIGkobywhMCk7dmFyIGY9bmV3IEVycm9yKFwiQ2Fubm90IGZpbmQgbW9kdWxlICdcIitvK1wiJ1wiKTt0aHJvdyBmLmNvZGU9XCJNT0RVTEVfTk9UX0ZPVU5EXCIsZn12YXIgbD1uW29dPXtleHBvcnRzOnt9fTt0W29dWzBdLmNhbGwobC5leHBvcnRzLGZ1bmN0aW9uKGUpe3ZhciBuPXRbb11bMV1bZV07cmV0dXJuIHMobj9uOmUpfSxsLGwuZXhwb3J0cyxlLHQsbixyKX1yZXR1cm4gbltvXS5leHBvcnRzfXZhciBpPXR5cGVvZiByZXF1aXJlPT1cImZ1bmN0aW9uXCImJnJlcXVpcmU7Zm9yKHZhciBvPTA7bzxyLmxlbmd0aDtvKyspcyhyW29dKTtyZXR1cm4gc30pIiwiJ3VzZSBzdHJpY3QnO1xuXG4vKipcbiAqIE1vZHVsZSBkZXBlbmRlbmNpZXNcbiAqL1xudmFyIGRlY291cGxlID0gcmVxdWlyZSgnZGVjb3VwbGUnKTtcbnZhciBFbWl0dGVyID0gcmVxdWlyZSgnZW1pdHRlcicpO1xuXG4vKipcbiAqIFByaXZhdGVzXG4gKi9cbnZhciBzY3JvbGxUaW1lb3V0O1xudmFyIHNjcm9sbGluZyA9IGZhbHNlO1xudmFyIGRvYyA9IHdpbmRvdy5kb2N1bWVudDtcbnZhciBodG1sID0gZG9jLmRvY3VtZW50RWxlbWVudDtcbnZhciBtc1BvaW50ZXJTdXBwb3J0ZWQgPSB3aW5kb3cubmF2aWdhdG9yLm1zUG9pbnRlckVuYWJsZWQ7XG52YXIgdG91Y2ggPSB7XG4gICdzdGFydCc6IG1zUG9pbnRlclN1cHBvcnRlZCA/ICdNU1BvaW50ZXJEb3duJyA6ICd0b3VjaHN0YXJ0JyxcbiAgJ21vdmUnOiBtc1BvaW50ZXJTdXBwb3J0ZWQgPyAnTVNQb2ludGVyTW92ZScgOiAndG91Y2htb3ZlJyxcbiAgJ2VuZCc6IG1zUG9pbnRlclN1cHBvcnRlZCA/ICdNU1BvaW50ZXJVcCcgOiAndG91Y2hlbmQnXG59O1xudmFyIHByZWZpeCA9IChmdW5jdGlvbiBwcmVmaXgoKSB7XG4gIHZhciByZWdleCA9IC9eKFdlYmtpdHxLaHRtbHxNb3p8bXN8TykoPz1bQS1aXSkvO1xuICB2YXIgc3R5bGVEZWNsYXJhdGlvbiA9IGRvYy5nZXRFbGVtZW50c0J5VGFnTmFtZSgnc2NyaXB0JylbMF0uc3R5bGU7XG4gIGZvciAodmFyIHByb3AgaW4gc3R5bGVEZWNsYXJhdGlvbikge1xuICAgIGlmIChyZWdleC50ZXN0KHByb3ApKSB7XG4gICAgICByZXR1cm4gJy0nICsgcHJvcC5tYXRjaChyZWdleClbMF0udG9Mb3dlckNhc2UoKSArICctJztcbiAgICB9XG4gIH1cbiAgLy8gTm90aGluZyBmb3VuZCBzbyBmYXI/IFdlYmtpdCBkb2VzIG5vdCBlbnVtZXJhdGUgb3ZlciB0aGUgQ1NTIHByb3BlcnRpZXMgb2YgdGhlIHN0eWxlIG9iamVjdC5cbiAgLy8gSG93ZXZlciAocHJvcCBpbiBzdHlsZSkgcmV0dXJucyB0aGUgY29ycmVjdCB2YWx1ZSwgc28gd2UnbGwgaGF2ZSB0byB0ZXN0IGZvclxuICAvLyB0aGUgcHJlY2VuY2Ugb2YgYSBzcGVjaWZpYyBwcm9wZXJ0eVxuICBpZiAoJ1dlYmtpdE9wYWNpdHknIGluIHN0eWxlRGVjbGFyYXRpb24pIHsgcmV0dXJuICctd2Via2l0LSc7IH1cbiAgaWYgKCdLaHRtbE9wYWNpdHknIGluIHN0eWxlRGVjbGFyYXRpb24pIHsgcmV0dXJuICcta2h0bWwtJzsgfVxuICByZXR1cm4gJyc7XG59KCkpO1xuZnVuY3Rpb24gZXh0ZW5kKGRlc3RpbmF0aW9uLCBmcm9tKSB7XG4gIGZvciAodmFyIHByb3AgaW4gZnJvbSkge1xuICAgIGlmIChmcm9tW3Byb3BdKSB7XG4gICAgICBkZXN0aW5hdGlvbltwcm9wXSA9IGZyb21bcHJvcF07XG4gICAgfVxuICB9XG4gIHJldHVybiBkZXN0aW5hdGlvbjtcbn1cbmZ1bmN0aW9uIGluaGVyaXRzKGNoaWxkLCB1YmVyKSB7XG4gIGNoaWxkLnByb3RvdHlwZSA9IGV4dGVuZChjaGlsZC5wcm90b3R5cGUgfHwge30sIHViZXIucHJvdG90eXBlKTtcbn1cbmZ1bmN0aW9uIGhhc0lnbm9yZWRFbGVtZW50cyhlbCkge1xuICB3aGlsZSAoZWwucGFyZW50Tm9kZSkge1xuICAgIGlmIChlbC5nZXRBdHRyaWJ1dGUoJ2RhdGEtc2xpZGVvdXQtaWdub3JlJykgIT09IG51bGwpIHtcbiAgICAgIHJldHVybiBlbDtcbiAgICB9XG4gICAgZWwgPSBlbC5wYXJlbnROb2RlO1xuICB9XG4gIHJldHVybiBudWxsO1xufVxuXG4vKipcbiAqIFNsaWRlb3V0IGNvbnN0cnVjdG9yXG4gKi9cbmZ1bmN0aW9uIFNsaWRlb3V0KG9wdGlvbnMpIHtcbiAgb3B0aW9ucyA9IG9wdGlvbnMgfHwge307XG5cbiAgLy8gU2V0cyBkZWZhdWx0IHZhbHVlc1xuICB0aGlzLl9zdGFydE9mZnNldFggPSAwO1xuICB0aGlzLl9jdXJyZW50T2Zmc2V0WCA9IDA7XG4gIHRoaXMuX29wZW5pbmcgPSBmYWxzZTtcbiAgdGhpcy5fbW92ZWQgPSBmYWxzZTtcbiAgdGhpcy5fb3BlbmVkID0gZmFsc2U7XG4gIHRoaXMuX3ByZXZlbnRPcGVuID0gZmFsc2U7XG5cbiAgLy8gU2V0cyBwYW5lbFxuICB0aGlzLnBhbmVsID0gb3B0aW9ucy5wYW5lbDtcbiAgdGhpcy5tZW51ID0gb3B0aW9ucy5tZW51O1xuXG4gIC8vIFNldHMgb3B0aW9uc1xuICB0aGlzLl90b3VjaCA9IG9wdGlvbnMudG91Y2ggPT09IHVuZGVmaW5lZCA/IHRydWUgOiBvcHRpb25zLnRvdWNoICYmIHRydWU7XG4gIHRoaXMuX3NpZGUgPSBvcHRpb25zLnNpZGUgfHwgJ2xlZnQnO1xuICB0aGlzLl9lYXNpbmcgPSBvcHRpb25zLmZ4IHx8wqBvcHRpb25zLmVhc2luZyB8fCAnZWFzZSc7XG4gIHRoaXMuX2R1cmF0aW9uID0gcGFyc2VJbnQob3B0aW9ucy5kdXJhdGlvbiwgMTApIHx8IDMwMDtcbiAgdGhpcy5fdG9sZXJhbmNlID0gcGFyc2VJbnQob3B0aW9ucy50b2xlcmFuY2UsIDEwKSB8fCA3MDtcbiAgdGhpcy5fcGFkZGluZyA9IHRoaXMuX3RyYW5zbGF0ZVRvID0gcGFyc2VJbnQob3B0aW9ucy5wYWRkaW5nLCAxMCkgfHwgMjU2O1xuICB0aGlzLl9vcmllbnRhdGlvbiA9IHRoaXMuX3NpZGUgPT09ICdyaWdodCcgPyAtMSA6IDE7XG4gIHRoaXMuX3RyYW5zbGF0ZVRvICo9IHRoaXMuX29yaWVudGF0aW9uO1xuXG4gIC8vIFNldHMgIGNsYXNzbmFtZXNcbiAgaWYgKCF0aGlzLnBhbmVsLmNsYXNzTGlzdC5jb250YWlucygnc2xpZGVvdXQtcGFuZWwnKSkge1xuICAgIHRoaXMucGFuZWwuY2xhc3NMaXN0LmFkZCgnc2xpZGVvdXQtcGFuZWwnKTtcbiAgfVxuICBpZiAoIXRoaXMucGFuZWwuY2xhc3NMaXN0LmNvbnRhaW5zKCdzbGlkZW91dC1wYW5lbC0nICsgdGhpcy5fc2lkZSkpIHtcbiAgICB0aGlzLnBhbmVsLmNsYXNzTGlzdC5hZGQoJ3NsaWRlb3V0LXBhbmVsLScgKyB0aGlzLl9zaWRlKTtcbiAgfVxuICBpZiAoIXRoaXMubWVudS5jbGFzc0xpc3QuY29udGFpbnMoJ3NsaWRlb3V0LW1lbnUnKSkge1xuICAgIHRoaXMubWVudS5jbGFzc0xpc3QuYWRkKCdzbGlkZW91dC1tZW51Jyk7XG4gIH1cbiAgaWYgKCF0aGlzLm1lbnUuY2xhc3NMaXN0LmNvbnRhaW5zKCdzbGlkZW91dC1tZW51LScgKyB0aGlzLl9zaWRlKSkge1xuICAgIHRoaXMubWVudS5jbGFzc0xpc3QuYWRkKCdzbGlkZW91dC1tZW51LScgKyB0aGlzLl9zaWRlKTtcbiAgfVxuXG4gIC8vIEluaXQgdG91Y2ggZXZlbnRzXG4gIGlmICh0aGlzLl90b3VjaCkge1xuICAgIHRoaXMuX2luaXRUb3VjaEV2ZW50cygpO1xuICB9XG59XG5cbi8qKlxuICogSW5oZXJpdHMgZnJvbSBFbWl0dGVyXG4gKi9cbmluaGVyaXRzKFNsaWRlb3V0LCBFbWl0dGVyKTtcblxuLyoqXG4gKiBPcGVucyB0aGUgc2xpZGVvdXQgbWVudS5cbiAqL1xuU2xpZGVvdXQucHJvdG90eXBlLm9wZW4gPSBmdW5jdGlvbigpIHtcbiAgdmFyIHNlbGYgPSB0aGlzO1xuICB0aGlzLmVtaXQoJ2JlZm9yZW9wZW4nKTtcbiAgaWYgKCFodG1sLmNsYXNzTGlzdC5jb250YWlucygnc2xpZGVvdXQtb3BlbicpKSB7XG4gICAgaHRtbC5jbGFzc0xpc3QuYWRkKCdzbGlkZW91dC1vcGVuJyk7XG4gIH1cbiAgdGhpcy5fc2V0VHJhbnNpdGlvbigpO1xuICB0aGlzLl90cmFuc2xhdGVYVG8odGhpcy5fdHJhbnNsYXRlVG8pO1xuICB0aGlzLl9vcGVuZWQgPSB0cnVlO1xuICBzZXRUaW1lb3V0KGZ1bmN0aW9uKCkge1xuICAgIHNlbGYucGFuZWwuc3R5bGUudHJhbnNpdGlvbiA9IHNlbGYucGFuZWwuc3R5bGVbJy13ZWJraXQtdHJhbnNpdGlvbiddID0gJyc7XG4gICAgc2VsZi5lbWl0KCdvcGVuJyk7XG4gIH0sIHRoaXMuX2R1cmF0aW9uICsgNTApO1xuICByZXR1cm4gdGhpcztcbn07XG5cbi8qKlxuICogQ2xvc2VzIHNsaWRlb3V0IG1lbnUuXG4gKi9cblNsaWRlb3V0LnByb3RvdHlwZS5jbG9zZSA9IGZ1bmN0aW9uKCkge1xuICB2YXIgc2VsZiA9IHRoaXM7XG4gIGlmICghdGhpcy5pc09wZW4oKSAmJiAhdGhpcy5fb3BlbmluZykge1xuICAgIHJldHVybiB0aGlzO1xuICB9XG4gIHRoaXMuZW1pdCgnYmVmb3JlY2xvc2UnKTtcbiAgdGhpcy5fc2V0VHJhbnNpdGlvbigpO1xuICB0aGlzLl90cmFuc2xhdGVYVG8oMCk7XG4gIHRoaXMuX29wZW5lZCA9IGZhbHNlO1xuICBzZXRUaW1lb3V0KGZ1bmN0aW9uKCkge1xuICAgIGh0bWwuY2xhc3NMaXN0LnJlbW92ZSgnc2xpZGVvdXQtb3BlbicpO1xuICAgIHNlbGYucGFuZWwuc3R5bGUudHJhbnNpdGlvbiA9IHNlbGYucGFuZWwuc3R5bGVbJy13ZWJraXQtdHJhbnNpdGlvbiddID0gc2VsZi5wYW5lbC5zdHlsZVtwcmVmaXggKyAndHJhbnNmb3JtJ10gPSBzZWxmLnBhbmVsLnN0eWxlLnRyYW5zZm9ybSA9ICcnO1xuICAgIHNlbGYuZW1pdCgnY2xvc2UnKTtcbiAgfSwgdGhpcy5fZHVyYXRpb24gKyA1MCk7XG4gIHJldHVybiB0aGlzO1xufTtcblxuLyoqXG4gKiBUb2dnbGVzIChvcGVuL2Nsb3NlKSBzbGlkZW91dCBtZW51LlxuICovXG5TbGlkZW91dC5wcm90b3R5cGUudG9nZ2xlID0gZnVuY3Rpb24oKSB7XG4gIHJldHVybiB0aGlzLmlzT3BlbigpID8gdGhpcy5jbG9zZSgpIDogdGhpcy5vcGVuKCk7XG59O1xuXG4vKipcbiAqIFJldHVybnMgdHJ1ZSBpZiB0aGUgc2xpZGVvdXQgaXMgY3VycmVudGx5IG9wZW4sIGFuZCBmYWxzZSBpZiBpdCBpcyBjbG9zZWQuXG4gKi9cblNsaWRlb3V0LnByb3RvdHlwZS5pc09wZW4gPSBmdW5jdGlvbigpIHtcbiAgcmV0dXJuIHRoaXMuX29wZW5lZDtcbn07XG5cbi8qKlxuICogVHJhbnNsYXRlcyBwYW5lbCBhbmQgdXBkYXRlcyBjdXJyZW50T2Zmc2V0IHdpdGggYSBnaXZlbiBYIHBvaW50XG4gKi9cblNsaWRlb3V0LnByb3RvdHlwZS5fdHJhbnNsYXRlWFRvID0gZnVuY3Rpb24odHJhbnNsYXRlWCkge1xuICB0aGlzLl9jdXJyZW50T2Zmc2V0WCA9IHRyYW5zbGF0ZVg7XG4gIHRoaXMucGFuZWwuc3R5bGVbcHJlZml4ICsgJ3RyYW5zZm9ybSddID0gdGhpcy5wYW5lbC5zdHlsZS50cmFuc2Zvcm0gPSAndHJhbnNsYXRlWCgnICsgdHJhbnNsYXRlWCArICdweCknO1xuICByZXR1cm4gdGhpcztcbn07XG5cbi8qKlxuICogU2V0IHRyYW5zaXRpb24gcHJvcGVydGllc1xuICovXG5TbGlkZW91dC5wcm90b3R5cGUuX3NldFRyYW5zaXRpb24gPSBmdW5jdGlvbigpIHtcbiAgdGhpcy5wYW5lbC5zdHlsZVtwcmVmaXggKyAndHJhbnNpdGlvbiddID0gdGhpcy5wYW5lbC5zdHlsZS50cmFuc2l0aW9uID0gcHJlZml4ICsgJ3RyYW5zZm9ybSAnICsgdGhpcy5fZHVyYXRpb24gKyAnbXMgJyArIHRoaXMuX2Vhc2luZztcbiAgcmV0dXJuIHRoaXM7XG59O1xuXG4vKipcbiAqIEluaXRpYWxpemVzIHRvdWNoIGV2ZW50XG4gKi9cblNsaWRlb3V0LnByb3RvdHlwZS5faW5pdFRvdWNoRXZlbnRzID0gZnVuY3Rpb24oKSB7XG4gIHZhciBzZWxmID0gdGhpcztcblxuICAvKipcbiAgICogRGVjb3VwbGUgc2Nyb2xsIGV2ZW50XG4gICAqL1xuICB0aGlzLl9vblNjcm9sbEZuID0gZGVjb3VwbGUoZG9jLCAnc2Nyb2xsJywgZnVuY3Rpb24oKSB7XG4gICAgaWYgKCFzZWxmLl9tb3ZlZCkge1xuICAgICAgY2xlYXJUaW1lb3V0KHNjcm9sbFRpbWVvdXQpO1xuICAgICAgc2Nyb2xsaW5nID0gdHJ1ZTtcbiAgICAgIHNjcm9sbFRpbWVvdXQgPSBzZXRUaW1lb3V0KGZ1bmN0aW9uKCkge1xuICAgICAgICBzY3JvbGxpbmcgPSBmYWxzZTtcbiAgICAgIH0sIDI1MCk7XG4gICAgfVxuICB9KTtcblxuICAvKipcbiAgICogUHJldmVudHMgdG91Y2htb3ZlIGV2ZW50IGlmIHNsaWRlb3V0IGlzIG1vdmluZ1xuICAgKi9cbiAgdGhpcy5fcHJldmVudE1vdmUgPSBmdW5jdGlvbihldmUpIHtcbiAgICBpZiAoc2VsZi5fbW92ZWQpIHtcbiAgICAgIGV2ZS5wcmV2ZW50RGVmYXVsdCgpO1xuICAgIH1cbiAgfTtcblxuICBkb2MuYWRkRXZlbnRMaXN0ZW5lcih0b3VjaC5tb3ZlLCB0aGlzLl9wcmV2ZW50TW92ZSk7XG5cbiAgLyoqXG4gICAqIFJlc2V0cyB2YWx1ZXMgb24gdG91Y2hzdGFydFxuICAgKi9cbiAgdGhpcy5fcmVzZXRUb3VjaEZuID0gZnVuY3Rpb24oZXZlKSB7XG4gICAgaWYgKHR5cGVvZiBldmUudG91Y2hlcyA9PT0gJ3VuZGVmaW5lZCcpIHtcbiAgICAgIHJldHVybjtcbiAgICB9XG5cbiAgICBzZWxmLl9tb3ZlZCA9IGZhbHNlO1xuICAgIHNlbGYuX29wZW5pbmcgPSBmYWxzZTtcbiAgICBzZWxmLl9zdGFydE9mZnNldFggPSBldmUudG91Y2hlc1swXS5wYWdlWDtcbiAgICBzZWxmLl9wcmV2ZW50T3BlbiA9ICghc2VsZi5fdG91Y2ggfHwgKCFzZWxmLmlzT3BlbigpICYmIHNlbGYubWVudS5jbGllbnRXaWR0aCAhPT0gMCkpO1xuICB9O1xuXG4gIHRoaXMucGFuZWwuYWRkRXZlbnRMaXN0ZW5lcih0b3VjaC5zdGFydCwgdGhpcy5fcmVzZXRUb3VjaEZuKTtcblxuICAvKipcbiAgICogUmVzZXRzIHZhbHVlcyBvbiB0b3VjaGNhbmNlbFxuICAgKi9cbiAgdGhpcy5fb25Ub3VjaENhbmNlbEZuID0gZnVuY3Rpb24oKSB7XG4gICAgc2VsZi5fbW92ZWQgPSBmYWxzZTtcbiAgICBzZWxmLl9vcGVuaW5nID0gZmFsc2U7XG4gIH07XG5cbiAgdGhpcy5wYW5lbC5hZGRFdmVudExpc3RlbmVyKCd0b3VjaGNhbmNlbCcsIHRoaXMuX29uVG91Y2hDYW5jZWxGbik7XG5cbiAgLyoqXG4gICAqIFRvZ2dsZXMgc2xpZGVvdXQgb24gdG91Y2hlbmRcbiAgICovXG4gIHRoaXMuX29uVG91Y2hFbmRGbiA9IGZ1bmN0aW9uKCkge1xuICAgIGlmIChzZWxmLl9tb3ZlZCkge1xuICAgICAgc2VsZi5lbWl0KCd0cmFuc2xhdGVlbmQnKTtcbiAgICAgIChzZWxmLl9vcGVuaW5nICYmIE1hdGguYWJzKHNlbGYuX2N1cnJlbnRPZmZzZXRYKSA+IHNlbGYuX3RvbGVyYW5jZSkgPyBzZWxmLm9wZW4oKSA6IHNlbGYuY2xvc2UoKTtcbiAgICB9XG4gICAgc2VsZi5fbW92ZWQgPSBmYWxzZTtcbiAgfTtcblxuICB0aGlzLnBhbmVsLmFkZEV2ZW50TGlzdGVuZXIodG91Y2guZW5kLCB0aGlzLl9vblRvdWNoRW5kRm4pO1xuXG4gIC8qKlxuICAgKiBUcmFuc2xhdGVzIHBhbmVsIG9uIHRvdWNobW92ZVxuICAgKi9cbiAgdGhpcy5fb25Ub3VjaE1vdmVGbiA9IGZ1bmN0aW9uKGV2ZSkge1xuICAgIGlmIChcbiAgICAgIHNjcm9sbGluZyB8fFxuICAgICAgc2VsZi5fcHJldmVudE9wZW4gfHxcbiAgICAgIHR5cGVvZiBldmUudG91Y2hlcyA9PT0gJ3VuZGVmaW5lZCcgfHxcbiAgICAgIGhhc0lnbm9yZWRFbGVtZW50cyhldmUudGFyZ2V0KVxuICAgICkge1xuICAgICAgcmV0dXJuO1xuICAgIH1cblxuICAgIHZhciBkaWZfeCA9IGV2ZS50b3VjaGVzWzBdLmNsaWVudFggLSBzZWxmLl9zdGFydE9mZnNldFg7XG4gICAgdmFyIHRyYW5zbGF0ZVggPSBzZWxmLl9jdXJyZW50T2Zmc2V0WCA9IGRpZl94O1xuXG4gICAgaWYgKE1hdGguYWJzKHRyYW5zbGF0ZVgpID4gc2VsZi5fcGFkZGluZykge1xuICAgICAgcmV0dXJuO1xuICAgIH1cblxuICAgIGlmIChNYXRoLmFicyhkaWZfeCkgPiAyMCkge1xuXG4gICAgICBzZWxmLl9vcGVuaW5nID0gdHJ1ZTtcblxuICAgICAgdmFyIG9yaWVudGVkX2RpZl94ID0gZGlmX3ggKiBzZWxmLl9vcmllbnRhdGlvbjtcblxuICAgICAgaWYgKHNlbGYuX29wZW5lZCAmJiBvcmllbnRlZF9kaWZfeCA+IDAgfHwgIXNlbGYuX29wZW5lZCAmJiBvcmllbnRlZF9kaWZfeCA8IDApIHtcbiAgICAgICAgcmV0dXJuO1xuICAgICAgfVxuXG4gICAgICBpZiAoIXNlbGYuX21vdmVkKSB7XG4gICAgICAgIHNlbGYuZW1pdCgndHJhbnNsYXRlc3RhcnQnKTtcbiAgICAgIH1cblxuICAgICAgaWYgKG9yaWVudGVkX2RpZl94IDw9IDApIHtcbiAgICAgICAgdHJhbnNsYXRlWCA9IGRpZl94ICsgc2VsZi5fcGFkZGluZyAqIHNlbGYuX29yaWVudGF0aW9uO1xuICAgICAgICBzZWxmLl9vcGVuaW5nID0gZmFsc2U7XG4gICAgICB9XG5cbiAgICAgIGlmICghKHNlbGYuX21vdmVkICYmIGh0bWwuY2xhc3NMaXN0LmNvbnRhaW5zKCdzbGlkZW91dC1vcGVuJykpKSB7XG4gICAgICAgIGh0bWwuY2xhc3NMaXN0LmFkZCgnc2xpZGVvdXQtb3BlbicpO1xuICAgICAgfVxuXG4gICAgICBzZWxmLnBhbmVsLnN0eWxlW3ByZWZpeCArICd0cmFuc2Zvcm0nXSA9IHNlbGYucGFuZWwuc3R5bGUudHJhbnNmb3JtID0gJ3RyYW5zbGF0ZVgoJyArIHRyYW5zbGF0ZVggKyAncHgpJztcbiAgICAgIHNlbGYuZW1pdCgndHJhbnNsYXRlJywgdHJhbnNsYXRlWCk7XG4gICAgICBzZWxmLl9tb3ZlZCA9IHRydWU7XG4gICAgfVxuXG4gIH07XG5cbiAgdGhpcy5wYW5lbC5hZGRFdmVudExpc3RlbmVyKHRvdWNoLm1vdmUsIHRoaXMuX29uVG91Y2hNb3ZlRm4pO1xuXG4gIHJldHVybiB0aGlzO1xufTtcblxuLyoqXG4gKiBFbmFibGUgb3BlbmluZyB0aGUgc2xpZGVvdXQgdmlhIHRvdWNoIGV2ZW50cy5cbiAqL1xuU2xpZGVvdXQucHJvdG90eXBlLmVuYWJsZVRvdWNoID0gZnVuY3Rpb24oKSB7XG4gIHRoaXMuX3RvdWNoID0gdHJ1ZTtcbiAgcmV0dXJuIHRoaXM7XG59O1xuXG4vKipcbiAqIERpc2FibGUgb3BlbmluZyB0aGUgc2xpZGVvdXQgdmlhIHRvdWNoIGV2ZW50cy5cbiAqL1xuU2xpZGVvdXQucHJvdG90eXBlLmRpc2FibGVUb3VjaCA9IGZ1bmN0aW9uKCkge1xuICB0aGlzLl90b3VjaCA9IGZhbHNlO1xuICByZXR1cm4gdGhpcztcbn07XG5cbi8qKlxuICogRGVzdHJveSBhbiBpbnN0YW5jZSBvZiBzbGlkZW91dC5cbiAqL1xuU2xpZGVvdXQucHJvdG90eXBlLmRlc3Ryb3kgPSBmdW5jdGlvbigpIHtcbiAgLy8gQ2xvc2UgYmVmb3JlIGNsZWFuXG4gIHRoaXMuY2xvc2UoKTtcblxuICAvLyBSZW1vdmUgZXZlbnQgbGlzdGVuZXJzXG4gIGRvYy5yZW1vdmVFdmVudExpc3RlbmVyKHRvdWNoLm1vdmUsIHRoaXMuX3ByZXZlbnRNb3ZlKTtcbiAgdGhpcy5wYW5lbC5yZW1vdmVFdmVudExpc3RlbmVyKHRvdWNoLnN0YXJ0LCB0aGlzLl9yZXNldFRvdWNoRm4pO1xuICB0aGlzLnBhbmVsLnJlbW92ZUV2ZW50TGlzdGVuZXIoJ3RvdWNoY2FuY2VsJywgdGhpcy5fb25Ub3VjaENhbmNlbEZuKTtcbiAgdGhpcy5wYW5lbC5yZW1vdmVFdmVudExpc3RlbmVyKHRvdWNoLmVuZCwgdGhpcy5fb25Ub3VjaEVuZEZuKTtcbiAgdGhpcy5wYW5lbC5yZW1vdmVFdmVudExpc3RlbmVyKHRvdWNoLm1vdmUsIHRoaXMuX29uVG91Y2hNb3ZlRm4pO1xuICBkb2MucmVtb3ZlRXZlbnRMaXN0ZW5lcignc2Nyb2xsJywgdGhpcy5fb25TY3JvbGxGbik7XG5cbiAgLy8gUmVtb3ZlIG1ldGhvZHNcbiAgdGhpcy5vcGVuID0gdGhpcy5jbG9zZSA9IGZ1bmN0aW9uKCkge307XG5cbiAgLy8gUmV0dXJuIHRoZSBpbnN0YW5jZSBzbyBpdCBjYW4gYmUgZWFzaWx5IGRlcmVmZXJlbmNlZFxuICByZXR1cm4gdGhpcztcbn07XG5cbi8qKlxuICogRXhwb3NlIFNsaWRlb3V0XG4gKi9cbm1vZHVsZS5leHBvcnRzID0gU2xpZGVvdXQ7XG4iLCIndXNlIHN0cmljdCc7XG5cbnZhciByZXF1ZXN0QW5pbUZyYW1lID0gKGZ1bmN0aW9uKCkge1xuICByZXR1cm4gd2luZG93LnJlcXVlc3RBbmltYXRpb25GcmFtZSB8fFxuICAgIHdpbmRvdy53ZWJraXRSZXF1ZXN0QW5pbWF0aW9uRnJhbWUgfHxcbiAgICBmdW5jdGlvbiAoY2FsbGJhY2spIHtcbiAgICAgIHdpbmRvdy5zZXRUaW1lb3V0KGNhbGxiYWNrLCAxMDAwIC8gNjApO1xuICAgIH07XG59KCkpO1xuXG5mdW5jdGlvbiBkZWNvdXBsZShub2RlLCBldmVudCwgZm4pIHtcbiAgdmFyIGV2ZSxcbiAgICAgIHRyYWNraW5nID0gZmFsc2U7XG5cbiAgZnVuY3Rpb24gY2FwdHVyZUV2ZW50KGUpIHtcbiAgICBldmUgPSBlO1xuICAgIHRyYWNrKCk7XG4gIH1cblxuICBmdW5jdGlvbiB0cmFjaygpIHtcbiAgICBpZiAoIXRyYWNraW5nKSB7XG4gICAgICByZXF1ZXN0QW5pbUZyYW1lKHVwZGF0ZSk7XG4gICAgICB0cmFja2luZyA9IHRydWU7XG4gICAgfVxuICB9XG5cbiAgZnVuY3Rpb24gdXBkYXRlKCkge1xuICAgIGZuLmNhbGwobm9kZSwgZXZlKTtcbiAgICB0cmFja2luZyA9IGZhbHNlO1xuICB9XG5cbiAgbm9kZS5hZGRFdmVudExpc3RlbmVyKGV2ZW50LCBjYXB0dXJlRXZlbnQsIGZhbHNlKTtcblxuICByZXR1cm4gY2FwdHVyZUV2ZW50O1xufVxuXG4vKipcbiAqIEV4cG9zZSBkZWNvdXBsZVxuICovXG5tb2R1bGUuZXhwb3J0cyA9IGRlY291cGxlO1xuIiwiXCJ1c2Ugc3RyaWN0XCI7XG5cbnZhciBfY2xhc3NDYWxsQ2hlY2sgPSBmdW5jdGlvbiAoaW5zdGFuY2UsIENvbnN0cnVjdG9yKSB7IGlmICghKGluc3RhbmNlIGluc3RhbmNlb2YgQ29uc3RydWN0b3IpKSB7IHRocm93IG5ldyBUeXBlRXJyb3IoXCJDYW5ub3QgY2FsbCBhIGNsYXNzIGFzIGEgZnVuY3Rpb25cIik7IH0gfTtcblxuZXhwb3J0cy5fX2VzTW9kdWxlID0gdHJ1ZTtcbi8qKlxuICogQ3JlYXRlcyBhIG5ldyBpbnN0YW5jZSBvZiBFbWl0dGVyLlxuICogQGNsYXNzXG4gKiBAcmV0dXJucyB7T2JqZWN0fSBSZXR1cm5zIGEgbmV3IGluc3RhbmNlIG9mIEVtaXR0ZXIuXG4gKiBAZXhhbXBsZVxuICogLy8gQ3JlYXRlcyBhIG5ldyBpbnN0YW5jZSBvZiBFbWl0dGVyLlxuICogdmFyIEVtaXR0ZXIgPSByZXF1aXJlKCdlbWl0dGVyJyk7XG4gKlxuICogdmFyIGVtaXR0ZXIgPSBuZXcgRW1pdHRlcigpO1xuICovXG5cbnZhciBFbWl0dGVyID0gKGZ1bmN0aW9uICgpIHtcbiAgZnVuY3Rpb24gRW1pdHRlcigpIHtcbiAgICBfY2xhc3NDYWxsQ2hlY2sodGhpcywgRW1pdHRlcik7XG4gIH1cblxuICAvKipcbiAgICogQWRkcyBhIGxpc3RlbmVyIHRvIHRoZSBjb2xsZWN0aW9uIGZvciB0aGUgc3BlY2lmaWVkIGV2ZW50LlxuICAgKiBAbWVtYmVyb2YhIEVtaXR0ZXIucHJvdG90eXBlXG4gICAqIEBmdW5jdGlvblxuICAgKiBAcGFyYW0ge1N0cmluZ30gZXZlbnQgLSBUaGUgZXZlbnQgbmFtZS5cbiAgICogQHBhcmFtIHtGdW5jdGlvbn0gbGlzdGVuZXIgLSBBIGxpc3RlbmVyIGZ1bmN0aW9uIHRvIGFkZC5cbiAgICogQHJldHVybnMge09iamVjdH0gUmV0dXJucyBhbiBpbnN0YW5jZSBvZiBFbWl0dGVyLlxuICAgKiBAZXhhbXBsZVxuICAgKiAvLyBBZGQgYW4gZXZlbnQgbGlzdGVuZXIgdG8gXCJmb29cIiBldmVudC5cbiAgICogZW1pdHRlci5vbignZm9vJywgbGlzdGVuZXIpO1xuICAgKi9cblxuICBFbWl0dGVyLnByb3RvdHlwZS5vbiA9IGZ1bmN0aW9uIG9uKGV2ZW50LCBsaXN0ZW5lcikge1xuICAgIC8vIFVzZSB0aGUgY3VycmVudCBjb2xsZWN0aW9uIG9yIGNyZWF0ZSBpdC5cbiAgICB0aGlzLl9ldmVudENvbGxlY3Rpb24gPSB0aGlzLl9ldmVudENvbGxlY3Rpb24gfHwge307XG5cbiAgICAvLyBVc2UgdGhlIGN1cnJlbnQgY29sbGVjdGlvbiBvZiBhbiBldmVudCBvciBjcmVhdGUgaXQuXG4gICAgdGhpcy5fZXZlbnRDb2xsZWN0aW9uW2V2ZW50XSA9IHRoaXMuX2V2ZW50Q29sbGVjdGlvbltldmVudF0gfHwgW107XG5cbiAgICAvLyBBcHBlbmRzIHRoZSBsaXN0ZW5lciBpbnRvIHRoZSBjb2xsZWN0aW9uIG9mIHRoZSBnaXZlbiBldmVudFxuICAgIHRoaXMuX2V2ZW50Q29sbGVjdGlvbltldmVudF0ucHVzaChsaXN0ZW5lcik7XG5cbiAgICByZXR1cm4gdGhpcztcbiAgfTtcblxuICAvKipcbiAgICogQWRkcyBhIGxpc3RlbmVyIHRvIHRoZSBjb2xsZWN0aW9uIGZvciB0aGUgc3BlY2lmaWVkIGV2ZW50IHRoYXQgd2lsbCBiZSBjYWxsZWQgb25seSBvbmNlLlxuICAgKiBAbWVtYmVyb2YhIEVtaXR0ZXIucHJvdG90eXBlXG4gICAqIEBmdW5jdGlvblxuICAgKiBAcGFyYW0ge1N0cmluZ30gZXZlbnQgLSBUaGUgZXZlbnQgbmFtZS5cbiAgICogQHBhcmFtIHtGdW5jdGlvbn0gbGlzdGVuZXIgLSBBIGxpc3RlbmVyIGZ1bmN0aW9uIHRvIGFkZC5cbiAgICogQHJldHVybnMge09iamVjdH0gUmV0dXJucyBhbiBpbnN0YW5jZSBvZiBFbWl0dGVyLlxuICAgKiBAZXhhbXBsZVxuICAgKiAvLyBXaWxsIGFkZCBhbiBldmVudCBoYW5kbGVyIHRvIFwiZm9vXCIgZXZlbnQgb25jZS5cbiAgICogZW1pdHRlci5vbmNlKCdmb28nLCBsaXN0ZW5lcik7XG4gICAqL1xuXG4gIEVtaXR0ZXIucHJvdG90eXBlLm9uY2UgPSBmdW5jdGlvbiBvbmNlKGV2ZW50LCBsaXN0ZW5lcikge1xuICAgIHZhciBzZWxmID0gdGhpcztcblxuICAgIGZ1bmN0aW9uIGZuKCkge1xuICAgICAgc2VsZi5vZmYoZXZlbnQsIGZuKTtcbiAgICAgIGxpc3RlbmVyLmFwcGx5KHRoaXMsIGFyZ3VtZW50cyk7XG4gICAgfVxuXG4gICAgZm4ubGlzdGVuZXIgPSBsaXN0ZW5lcjtcblxuICAgIHRoaXMub24oZXZlbnQsIGZuKTtcblxuICAgIHJldHVybiB0aGlzO1xuICB9O1xuXG4gIC8qKlxuICAgKiBSZW1vdmVzIGEgbGlzdGVuZXIgZnJvbSB0aGUgY29sbGVjdGlvbiBmb3IgdGhlIHNwZWNpZmllZCBldmVudC5cbiAgICogQG1lbWJlcm9mISBFbWl0dGVyLnByb3RvdHlwZVxuICAgKiBAZnVuY3Rpb25cbiAgICogQHBhcmFtIHtTdHJpbmd9IGV2ZW50IC0gVGhlIGV2ZW50IG5hbWUuXG4gICAqIEBwYXJhbSB7RnVuY3Rpb259IGxpc3RlbmVyIC0gQSBsaXN0ZW5lciBmdW5jdGlvbiB0byByZW1vdmUuXG4gICAqIEByZXR1cm5zIHtPYmplY3R9IFJldHVybnMgYW4gaW5zdGFuY2Ugb2YgRW1pdHRlci5cbiAgICogQGV4YW1wbGVcbiAgICogLy8gUmVtb3ZlIGEgZ2l2ZW4gbGlzdGVuZXIuXG4gICAqIGVtaXR0ZXIub2ZmKCdmb28nLCBsaXN0ZW5lcik7XG4gICAqL1xuXG4gIEVtaXR0ZXIucHJvdG90eXBlLm9mZiA9IGZ1bmN0aW9uIG9mZihldmVudCwgbGlzdGVuZXIpIHtcblxuICAgIHZhciBsaXN0ZW5lcnMgPSB1bmRlZmluZWQ7XG5cbiAgICAvLyBEZWZpbmVzIGxpc3RlbmVycyB2YWx1ZS5cbiAgICBpZiAoIXRoaXMuX2V2ZW50Q29sbGVjdGlvbiB8fCAhKGxpc3RlbmVycyA9IHRoaXMuX2V2ZW50Q29sbGVjdGlvbltldmVudF0pKSB7XG4gICAgICByZXR1cm4gdGhpcztcbiAgICB9XG5cbiAgICBsaXN0ZW5lcnMuZm9yRWFjaChmdW5jdGlvbiAoZm4sIGkpIHtcbiAgICAgIGlmIChmbiA9PT0gbGlzdGVuZXIgfHwgZm4ubGlzdGVuZXIgPT09IGxpc3RlbmVyKSB7XG4gICAgICAgIC8vIFJlbW92ZXMgdGhlIGdpdmVuIGxpc3RlbmVyLlxuICAgICAgICBsaXN0ZW5lcnMuc3BsaWNlKGksIDEpO1xuICAgICAgfVxuICAgIH0pO1xuXG4gICAgLy8gUmVtb3ZlcyBhbiBlbXB0eSBldmVudCBjb2xsZWN0aW9uLlxuICAgIGlmIChsaXN0ZW5lcnMubGVuZ3RoID09PSAwKSB7XG4gICAgICBkZWxldGUgdGhpcy5fZXZlbnRDb2xsZWN0aW9uW2V2ZW50XTtcbiAgICB9XG5cbiAgICByZXR1cm4gdGhpcztcbiAgfTtcblxuICAvKipcbiAgICogRXhlY3V0ZSBlYWNoIGl0ZW0gaW4gdGhlIGxpc3RlbmVyIGNvbGxlY3Rpb24gaW4gb3JkZXIgd2l0aCB0aGUgc3BlY2lmaWVkIGRhdGEuXG4gICAqIEBtZW1iZXJvZiEgRW1pdHRlci5wcm90b3R5cGVcbiAgICogQGZ1bmN0aW9uXG4gICAqIEBwYXJhbSB7U3RyaW5nfSBldmVudCAtIFRoZSBuYW1lIG9mIHRoZSBldmVudCB5b3Ugd2FudCB0byBlbWl0LlxuICAgKiBAcGFyYW0gey4uLk9iamVjdH0gZGF0YSAtIERhdGEgdG8gcGFzcyB0byB0aGUgbGlzdGVuZXJzLlxuICAgKiBAcmV0dXJucyB7T2JqZWN0fSBSZXR1cm5zIGFuIGluc3RhbmNlIG9mIEVtaXR0ZXIuXG4gICAqIEBleGFtcGxlXG4gICAqIC8vIEVtaXRzIHRoZSBcImZvb1wiIGV2ZW50IHdpdGggJ3BhcmFtMScgYW5kICdwYXJhbTInIGFzIGFyZ3VtZW50cy5cbiAgICogZW1pdHRlci5lbWl0KCdmb28nLCAncGFyYW0xJywgJ3BhcmFtMicpO1xuICAgKi9cblxuICBFbWl0dGVyLnByb3RvdHlwZS5lbWl0ID0gZnVuY3Rpb24gZW1pdChldmVudCkge1xuICAgIHZhciBfdGhpcyA9IHRoaXM7XG5cbiAgICBmb3IgKHZhciBfbGVuID0gYXJndW1lbnRzLmxlbmd0aCwgYXJncyA9IEFycmF5KF9sZW4gPiAxID8gX2xlbiAtIDEgOiAwKSwgX2tleSA9IDE7IF9rZXkgPCBfbGVuOyBfa2V5KyspIHtcbiAgICAgIGFyZ3NbX2tleSAtIDFdID0gYXJndW1lbnRzW19rZXldO1xuICAgIH1cblxuICAgIHZhciBsaXN0ZW5lcnMgPSB1bmRlZmluZWQ7XG5cbiAgICAvLyBEZWZpbmVzIGxpc3RlbmVycyB2YWx1ZS5cbiAgICBpZiAoIXRoaXMuX2V2ZW50Q29sbGVjdGlvbiB8fCAhKGxpc3RlbmVycyA9IHRoaXMuX2V2ZW50Q29sbGVjdGlvbltldmVudF0pKSB7XG4gICAgICByZXR1cm4gdGhpcztcbiAgICB9XG5cbiAgICAvLyBDbG9uZSBsaXN0ZW5lcnNcbiAgICBsaXN0ZW5lcnMgPSBsaXN0ZW5lcnMuc2xpY2UoMCk7XG5cbiAgICBsaXN0ZW5lcnMuZm9yRWFjaChmdW5jdGlvbiAoZm4pIHtcbiAgICAgIHJldHVybiBmbi5hcHBseShfdGhpcywgYXJncyk7XG4gICAgfSk7XG5cbiAgICByZXR1cm4gdGhpcztcbiAgfTtcblxuICByZXR1cm4gRW1pdHRlcjtcbn0pKCk7XG5cbi8qKlxuICogRXhwb3J0cyBFbWl0dGVyXG4gKi9cbmV4cG9ydHNbXCJkZWZhdWx0XCJdID0gRW1pdHRlcjtcbm1vZHVsZS5leHBvcnRzID0gZXhwb3J0c1tcImRlZmF1bHRcIl07Il19 548 | -------------------------------------------------------------------------------- /dist/slideout.min.js: -------------------------------------------------------------------------------- 1 | !function(t){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=t();else if("function"==typeof define&&define.amd)define([],t);else{var e;"undefined"!=typeof window?e=window:"undefined"!=typeof global?e=global:"undefined"!=typeof self&&(e=self),e.Slideout=t()}}(function(){var t,e,n;return function i(t,e,n){function o(r,a){if(!e[r]){if(!t[r]){var u=typeof require=="function"&&require;if(!a&&u)return u(r,!0);if(s)return s(r,!0);var l=new Error("Cannot find module '"+r+"'");throw l.code="MODULE_NOT_FOUND",l}var h=e[r]={exports:{}};t[r][0].call(h.exports,function(e){var n=t[r][1][e];return o(n?n:e)},h,h.exports,i,t,e,n)}return e[r].exports}var s=typeof require=="function"&&require;for(var r=0;rt._tolerance?t.open():t.close()}t._moved=false};this.panel.addEventListener(h.end,this._onTouchEndFn);this._onTouchMoveFn=function(e){if(r||t._preventOpen||typeof e.touches==="undefined"||d(e.target)){return}var n=e.touches[0].clientX-t._startOffsetX;var i=t._currentOffsetX=n;if(Math.abs(i)>t._padding){return}if(Math.abs(n)>20){t._opening=true;var o=n*t._orientation;if(t._opened&&o>0||!t._opened&&o<0){return}if(!t._moved){t.emit("translatestart")}if(o<=0){i=n+t._padding*t._orientation;t._opening=false}if(!(t._moved&&u.classList.contains("slideout-open"))){u.classList.add("slideout-open")}t.panel.style[f+"transform"]=t.panel.style.transform="translateX("+i+"px)";t.emit("translate",i);t._moved=true}};this.panel.addEventListener(h.move,this._onTouchMoveFn);return this};_.prototype.enableTouch=function(){this._touch=true;return this};_.prototype.disableTouch=function(){this._touch=false;return this};_.prototype.destroy=function(){this.close();a.removeEventListener(h.move,this._preventMove);this.panel.removeEventListener(h.start,this._resetTouchFn);this.panel.removeEventListener("touchcancel",this._onTouchCancelFn);this.panel.removeEventListener(h.end,this._onTouchEndFn);this.panel.removeEventListener(h.move,this._onTouchMoveFn);a.removeEventListener("scroll",this._onScrollFn);this.open=this.close=function(){};return this};e.exports=_},{decouple:2,emitter:3}],2:[function(t,e,n){"use strict";var i=function(){return window.requestAnimationFrame||window.webkitRequestAnimationFrame||function(t){window.setTimeout(t,1e3/60)}}();function o(t,e,n){var o,s=false;function r(t){o=t;a()}function a(){if(!s){i(u);s=true}}function u(){n.call(t,o);s=false}t.addEventListener(e,r,false);return r}e.exports=o},{}],3:[function(t,e,n){"use strict";var i=function(t,e){if(!(t instanceof e)){throw new TypeError("Cannot call a class as a function")}};n.__esModule=true;var o=function(){function t(){i(this,t)}t.prototype.on=function e(t,n){this._eventCollection=this._eventCollection||{};this._eventCollection[t]=this._eventCollection[t]||[];this._eventCollection[t].push(n);return this};t.prototype.once=function n(t,e){var n=this;function i(){n.off(t,i);e.apply(this,arguments)}i.listener=e;this.on(t,i);return this};t.prototype.off=function o(t,e){var n=undefined;if(!this._eventCollection||!(n=this._eventCollection[t])){return this}n.forEach(function(t,i){if(t===e||t.listener===e){n.splice(i,1)}});if(n.length===0){delete this._eventCollection[t]}return this};t.prototype.emit=function s(t){var e=this;for(var n=arguments.length,i=Array(n>1?n-1:0),o=1;o self._tolerance) ? self.open() : self.close(); 244 | } 245 | self._moved = false; 246 | }; 247 | 248 | this.panel.addEventListener(touch.end, this._onTouchEndFn); 249 | 250 | /** 251 | * Translates panel on touchmove 252 | */ 253 | this._onTouchMoveFn = function(eve) { 254 | if ( 255 | scrolling || 256 | self._preventOpen || 257 | typeof eve.touches === 'undefined' || 258 | hasIgnoredElements(eve.target) 259 | ) { 260 | return; 261 | } 262 | 263 | var dif_x = eve.touches[0].clientX - self._startOffsetX; 264 | var translateX = self._currentOffsetX = dif_x; 265 | 266 | if (Math.abs(translateX) > self._padding) { 267 | return; 268 | } 269 | 270 | if (Math.abs(dif_x) > 20) { 271 | 272 | self._opening = true; 273 | 274 | var oriented_dif_x = dif_x * self._orientation; 275 | 276 | if (self._opened && oriented_dif_x > 0 || !self._opened && oriented_dif_x < 0) { 277 | return; 278 | } 279 | 280 | if (!self._moved) { 281 | self.emit('translatestart'); 282 | } 283 | 284 | if (oriented_dif_x <= 0) { 285 | translateX = dif_x + self._padding * self._orientation; 286 | self._opening = false; 287 | } 288 | 289 | if (!(self._moved && html.classList.contains('slideout-open'))) { 290 | html.classList.add('slideout-open'); 291 | } 292 | 293 | self.panel.style[prefix + 'transform'] = self.panel.style.transform = 'translateX(' + translateX + 'px)'; 294 | self.emit('translate', translateX); 295 | self._moved = true; 296 | } 297 | 298 | }; 299 | 300 | this.panel.addEventListener(touch.move, this._onTouchMoveFn); 301 | 302 | return this; 303 | }; 304 | 305 | /** 306 | * Enable opening the slideout via touch events. 307 | */ 308 | Slideout.prototype.enableTouch = function() { 309 | this._touch = true; 310 | return this; 311 | }; 312 | 313 | /** 314 | * Disable opening the slideout via touch events. 315 | */ 316 | Slideout.prototype.disableTouch = function() { 317 | this._touch = false; 318 | return this; 319 | }; 320 | 321 | /** 322 | * Destroy an instance of slideout. 323 | */ 324 | Slideout.prototype.destroy = function() { 325 | // Close before clean 326 | this.close(); 327 | 328 | // Remove event listeners 329 | doc.removeEventListener(touch.move, this._preventMove); 330 | this.panel.removeEventListener(touch.start, this._resetTouchFn); 331 | this.panel.removeEventListener('touchcancel', this._onTouchCancelFn); 332 | this.panel.removeEventListener(touch.end, this._onTouchEndFn); 333 | this.panel.removeEventListener(touch.move, this._onTouchMoveFn); 334 | doc.removeEventListener('scroll', this._onScrollFn); 335 | 336 | // Remove methods 337 | this.open = this.close = function() {}; 338 | 339 | // Return the instance so it can be easily dereferenced 340 | return this; 341 | }; 342 | 343 | /** 344 | * Expose Slideout 345 | */ 346 | module.exports = Slideout; 347 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "slideout", 3 | "repository": "git@github.com:mango/slideout.git", 4 | "description": "A touch slideout navigation menu for your mobile web apps.", 5 | "author": "Guille Paz ", 6 | "version": "1.0.1", 7 | "scripts": { 8 | "build": "node browserify.js", 9 | "test": "npm run build && istanbul cover _mocha", 10 | "dist": "node browserify.js && uglifyjs ./dist/slideout.js -m -o ./dist/slideout.min.js", 11 | "hint": "jshint index.js" 12 | }, 13 | "dependencies": { 14 | "decouple": "0.0.2", 15 | "emitter": "git+https://github.com/Mango/emitter.git#0.0.7" 16 | }, 17 | "devDependencies": { 18 | "better-assert": "1.0.2", 19 | "browserify": "13.1.1", 20 | "coveralls": "2.11.15", 21 | "istanbul": "0.4.5", 22 | "jsdom": "9.8.3", 23 | "jshint": "2.9.4", 24 | "mocha": "3.1.2", 25 | "uglify-js": "2.7.4" 26 | }, 27 | "main": "index.js", 28 | "keywords": [ 29 | "slideout", 30 | "offcanvas", 31 | "menu", 32 | "touch" 33 | ], 34 | "license": "MIT", 35 | "spm": { 36 | "dependencies": { 37 | "decouple": "0.0.2", 38 | "emitter": "git+https://github.com/Mango/emitter.git#0.0.7" 39 | }, 40 | "main": "index.js" 41 | }, 42 | "filename": "slideout.min.js", 43 | "autoupdate": { 44 | "source": "git", 45 | "target": "git://github.com/Mango/slideout.git", 46 | "basePath": "dist", 47 | "files": [ 48 | "*.js" 49 | ] 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /test/assets/menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mango/slideout/6eb8440ddc7e94194723f86f6580de4d101762a3/test/assets/menu.png -------------------------------------------------------------------------------- /test/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | Slideout tests 17 | 18 | 19 | 20 | 67 | 68 |
69 |
70 | 71 |

Slideout.js

72 |
73 | 74 |
75 |

Test

76 |
77 |
78 | 79 |
80 |

with by Mango

81 |
82 |
83 | 84 | 85 | 92 | 93 | 94 | 107 | 108 | -------------------------------------------------------------------------------- /test/index.right.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 20 | Slideout right tests 21 | 22 | 23 | 24 | 71 | 72 |
73 |
74 | 75 |

Slideout.js

76 |
77 | 78 |
79 |

Test

80 |
81 |
82 | 83 |
84 |

with by Mango

85 |
86 |
87 | 88 | 105 | 106 | 107 | -------------------------------------------------------------------------------- /test/test.css: -------------------------------------------------------------------------------- 1 | /*! normalize.css v3.0.2 | MIT License | git.io/normalize */html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{font-size:2em;margin:.67em 0}mark{background:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{-moz-box-sizing:content-box;box-sizing:content-box;height:0}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input{line-height:normal}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}legend{border:0;padding:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-collapse:collapse;border-spacing:0}td,th{padding:0} 2 | html, 3 | body { 4 | font: 100%/1.4em 'Helvetica Neue', Helvetica, Arial, sans-serif; 5 | margin: 0 auto; 6 | color: #222; 7 | -webkit-text-size-adjust: none; 8 | -webkit-font-smoothing: antialiased; 9 | } 10 | 11 | body, 12 | .panel { 13 | background-color: #fff; 14 | } 15 | 16 | .menu { 17 | background-color: #1D1F20; 18 | background-image: linear-gradient(145deg, #1D1F20, #404348); 19 | } 20 | 21 | a { 22 | color: #4B5; 23 | text-decoration: none; 24 | } 25 | 26 | .menu a { 27 | color: #fff; 28 | } 29 | 30 | .menu a:hover { 31 | text-decoration: underline; 32 | } 33 | 34 | .menu-header { 35 | border-bottom: 1px solid #2a2d2f; 36 | padding: 20px; 37 | background-size: 32px; 38 | } 39 | 40 | .menu-header-title { 41 | font-weight: 400; 42 | letter-spacing: 0.5px; 43 | margin: 0; 44 | } 45 | 46 | .menu-section { 47 | margin: 25px 0; 48 | } 49 | 50 | .menu-section-title { 51 | text-transform: uppercase; 52 | color: #85888d; 53 | font-weight: 200; 54 | font-size: 13px; 55 | letter-spacing: 1px; 56 | padding: 0 20px; 57 | margin:0; 58 | } 59 | 60 | .menu-section-list { 61 | padding:0; 62 | margin: 10px 0; 63 | list-style:none; 64 | } 65 | 66 | .menu-section-list a { 67 | display: block; 68 | padding: 10px 20px; 69 | } 70 | 71 | .panel { 72 | text-align: center; 73 | padding-top: 5px; 74 | min-height: 100%; 75 | } 76 | 77 | #mocha { 78 | text-align: left; 79 | margin:0 !important; 80 | } 81 | 82 | #mocha-stats { 83 | top: 70px; 84 | } 85 | 86 | /** 87 | * Header 88 | */ 89 | .panel-header { 90 | margin: 120px auto 55px; 91 | } 92 | 93 | .title { 94 | font-size: 3.2em; 95 | line-height: 1em; 96 | margin: 0 0 15px; 97 | color: #4B5; 98 | font-weight: 400; 99 | } 100 | 101 | /** 102 | * hamburger 103 | */ 104 | .btn-hamburger { 105 | border: none; 106 | position: absolute; 107 | top: 12px; 108 | left: 12px; 109 | outline:none; 110 | background: url('assets/menu.png') no-repeat center; 111 | width: 50px; 112 | height: 50px; 113 | } 114 | 115 | /** 116 | * Boxes 117 | */ 118 | .box { 119 | border: 1px solid #4b5; 120 | border-radius: 4px; 121 | text-align: left; 122 | margin: 50px 10px; 123 | position: relative; 124 | } 125 | 126 | .box:before, 127 | .box:after { 128 | content: ' '; 129 | display: inline-block; 130 | width: 1px; 131 | height: 50px; 132 | border-left: 1px solid #4b5; 133 | position: absolute; 134 | left: 50%; 135 | } 136 | 137 | .box:before { 138 | top: -50px; 139 | } 140 | 141 | .box:after { 142 | bottom: -50px; 143 | } 144 | 145 | .box-title { 146 | margin: 0; 147 | padding:10px 20px; 148 | border-bottom: 1px solid #4b5; 149 | color: #4b5; 150 | font-size: 1.2em; 151 | font-weight: 400; 152 | } 153 | 154 | .box-content { 155 | padding:20px; 156 | background-color: #f8f8f8; 157 | } 158 | 159 | /** 160 | * Medium Screens 161 | */ 162 | @media all and (min-width:40em) { 163 | 164 | .btn-hamburger { 165 | top: 20px; 166 | left: 30px; 167 | } 168 | 169 | .panel-header { 170 | margin-top: 40px; 171 | width: 455px; 172 | } 173 | 174 | .title { 175 | font-size: 4.2em; 176 | } 177 | } 178 | 179 | /** 180 | * Large Screens 181 | */ 182 | @media all and (min-width: 54em) { 183 | .box { 184 | width: 70%; 185 | max-width: 1200px; 186 | margin: 50px auto; 187 | } 188 | } 189 | 190 | /** 191 | * Footer 192 | */ 193 | .panel-footer { 194 | margin: 10px auto 20px; 195 | } 196 | 197 | .panel-footer p { 198 | padding-bottom: 20px; 199 | } 200 | 201 | .heart { 202 | font-style: normal; 203 | font-weight: 500; 204 | color: #c0392b; 205 | text-decoration: none; 206 | } 207 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | if (exports) { 2 | var fs = require('fs'); 3 | var jsdom = require('jsdom').jsdom; 4 | var html = fs.readFileSync('./test/index.html', 'utf-8'); 5 | var document = jsdom(html); 6 | window = document.defaultView; 7 | var Slideout = require('../'); 8 | var assert = require('better-assert'); 9 | } 10 | 11 | var doc = window.document; 12 | var beforeopenEvent = false; 13 | var openEvent = false; 14 | var beforecloseEvent = false; 15 | var closeEvent = false; 16 | var slideout = new Slideout({ 17 | 'panel': doc.getElementById('panel'), 18 | 'menu': doc.getElementById('menu'), 19 | }); 20 | 21 | slideout 22 | .on('beforeopen', function() { 23 | beforeopenEvent = true; 24 | }) 25 | .on('open', function() { 26 | openEvent = true; 27 | }) 28 | .on('beforeclose', function() { 29 | beforecloseEvent = true; 30 | }) 31 | .on('close', function() { 32 | closeEvent = true; 33 | }); 34 | 35 | describe('Slideout', function () { 36 | 37 | it('should be defined.', function () { 38 | assert(Slideout !== undefined); 39 | }); 40 | 41 | it('should be a function.', function () { 42 | assert(typeof Slideout === 'function'); 43 | }); 44 | 45 | it('should return a new instance.', function () { 46 | assert(slideout instanceof Slideout); 47 | }); 48 | 49 | describe('should have the following methods:', function () { 50 | var methods = [ 51 | 'open', 52 | 'close', 53 | 'toggle', 54 | 'isOpen', 55 | '_initTouchEvents', 56 | '_translateXTo', 57 | '_setTransition', 58 | 'on', 59 | 'once', 60 | 'off', 61 | 'emit' 62 | ]; 63 | var i = 0; 64 | var len = methods.length; 65 | for (i; i < len; i += 1) { 66 | (function (i) { 67 | it('.' + methods[i] + '()', function (done) { 68 | assert(typeof slideout[methods[i]] === 'function'); 69 | done() 70 | }); 71 | }(i)); 72 | } 73 | }); 74 | 75 | describe('should define the following properties:', function () { 76 | var properties = [ 77 | 'panel', 78 | 'menu', 79 | '_startOffsetX', 80 | '_currentOffsetX', 81 | '_opening', 82 | '_moved', 83 | '_opened', 84 | '_easing', 85 | '_duration', 86 | '_tolerance', 87 | '_padding', 88 | '_touch', 89 | '_side' 90 | ]; 91 | var i = 0; 92 | var len = properties.length; 93 | for (i; i < len; i += 1) { 94 | (function (i) { 95 | it('.' + properties[i] + '()', function (done) { 96 | assert(slideout[properties[i]] !== undefined); 97 | done() 98 | }); 99 | }(i)); 100 | } 101 | }); 102 | 103 | it('should add classnames to panel and menu DOM elements.', function () { 104 | assert(slideout.panel.className.search('slideout-panel') !== -1); 105 | assert(slideout.panel.className.search('slideout-panel-left') !== -1); 106 | assert(slideout.menu.className.search('slideout-menu') !== -1); 107 | assert(slideout.menu.className.search('slideout-menu-left') !== -1); 108 | }); 109 | 110 | describe('.open()', function () { 111 | it('should add "slideout-open" classname to HTML.', function () { 112 | assert(doc.documentElement.className.search('slideout-open') === -1); 113 | slideout.open(); 114 | assert(doc.documentElement.className.search('slideout-open') !== -1); 115 | }); 116 | 117 | it('should translateX the panel to the given padding.', function () { 118 | assert(slideout.panel.style.transform === 'translateX(256px)'); 119 | assert(slideout.panel.style.transition.search(/transform 300ms ease/) !== -1); 120 | }); 121 | 122 | it('should set _opened to true.', function () { 123 | assert(slideout._opened === true); 124 | }); 125 | 126 | it('should emit "beforeopen" event.', function () { 127 | assert(beforeopenEvent === true); 128 | }); 129 | 130 | it('should emit "open" event.', function (done) { 131 | setTimeout(function(){ 132 | assert(openEvent === true); 133 | done(); 134 | }, 400); 135 | 136 | }); 137 | }); 138 | 139 | describe('.isOpen()', function () { 140 | it('should return true if the slideout is opened.', function () { 141 | assert(slideout.isOpen()); 142 | }); 143 | }); 144 | 145 | describe('.close()', function () { 146 | it('should remove "slideout-open" classname to HTML.', function (done) { 147 | assert(doc.documentElement.className.search('slideout-open') !== -1); 148 | slideout.close(); 149 | setTimeout(function(){ 150 | assert(doc.documentElement.className.search('slideout-open') === -1); 151 | done(); 152 | }, 350); 153 | 154 | }); 155 | 156 | it('should translateX the panel to 0.', function () { 157 | assert(slideout.panel.style.transform === ''); 158 | assert(slideout.panel.style.transition === ''); 159 | }); 160 | 161 | it('should set _opened to false.', function () { 162 | assert(slideout._opened === false); 163 | }); 164 | 165 | it('should emit "beforeclose" event.', function () { 166 | assert(beforecloseEvent === true); 167 | }); 168 | 169 | it('should emit "close" event.', function () { 170 | assert(closeEvent === true); 171 | }); 172 | }); 173 | 174 | describe('.toggle()', function () { 175 | it('should show the slideout if it is not opened.', function (done) { 176 | assert(doc.documentElement.className.search('slideout-open') === -1); 177 | slideout.toggle(); 178 | assert(doc.documentElement.className.search('slideout-open') !== -1); 179 | slideout.toggle(); 180 | setTimeout(function(){ 181 | assert(doc.documentElement.className.search('slideout-open') === -1); 182 | done(); 183 | }, 350); 184 | }); 185 | }); 186 | 187 | describe('.destroy()', function() { 188 | it('should destroy the instance internals allowing a new one to be created in it\'s place.', function(){ 189 | slideout.destroy(); 190 | slideout = new Slideout({ 191 | 'panel': doc.getElementById('panel'), 192 | 'menu': doc.getElementById('menu') 193 | }); 194 | slideout.open(); 195 | setTimeout(function(){ slideout.close(); }, 750); 196 | }); 197 | }); 198 | }); 199 | --------------------------------------------------------------------------------