├── .csslintrc ├── .editorconfig ├── .gitattributes ├── .github └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── .gitignore ├── .jshintrc ├── Gruntfile.js ├── LICENSE ├── README.md ├── demo ├── bs-dashboard.html ├── bs-starter.html ├── index.html └── no-trigger-button.html ├── dist ├── _css │ ├── js-offcanvas.css │ ├── js-offcanvas.css.map │ ├── minified │ │ └── js-offcanvas.css │ └── prefixed │ │ └── js-offcanvas.css └── _js │ ├── js-offcanvas.js │ ├── js-offcanvas.min.js │ ├── js-offcanvas.pkgd.js │ └── js-offcanvas.pkgd.min.js ├── grunt ├── banner.txt ├── config-lib │ ├── bytesize.js │ ├── clean.js │ ├── concat.js │ ├── cssmin.js │ ├── gh-pages.js │ ├── jshint.js │ ├── lintspaces.js │ ├── mkdir.js │ ├── modernizr.js │ ├── postcss.js │ ├── qunit.js │ ├── sass.js │ ├── uglify.js │ ├── usebanner.js │ └── watch.js ├── config │ └── concat.js └── tasks.js ├── package.json ├── src ├── js-offcanvas-init.js ├── js-offcanvas-trigger-init.js ├── js-offcanvas-trigger.js ├── js-offcanvas.js ├── js-offcanvas.mixins.scss ├── js-offcanvas.scss └── js-offcanvas.settings.scss ├── test ├── .jshintrc ├── test.html └── tests.js └── vendor └── modernizr.js /.csslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "adjoining-classes": false, 3 | "box-sizing": false 4 | } -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = tab 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 16 | **Expected behavior** 17 | A clear and concise description of what you expected to happen. 18 | 19 | **Screenshots** 20 | If applicable, add screenshots to help explain your problem. 21 | 22 | **Desktop (please complete the following information):** 23 | - OS: [e.g. iOS] 24 | - Browser [e.g. chrome, safari] 25 | - Version [e.g. 22] 26 | 27 | **Smartphone (please complete the following information):** 28 | - Device: [e.g. iPhone6] 29 | - OS: [e.g. iOS8.1] 30 | - Browser [e.g. stock browser, safari] 31 | - Version [e.g. 22] 32 | 33 | **Additional context** 34 | Add any other context about the problem here. 35 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Windows image file caches 2 | Thumbs.db 3 | ehthumbs.db 4 | 5 | # Folder config file 6 | Desktop.ini 7 | 8 | # Recycle Bin used on file shares 9 | $RECYCLE.BIN/ 10 | 11 | # Windows Installer files 12 | *.cab 13 | *.msi 14 | *.msm 15 | *.msp 16 | 17 | # Windows shortcuts 18 | *.lnk 19 | 20 | # ========================= 21 | # Operating System Files 22 | # ========================= 23 | 24 | # OSX 25 | # ========================= 26 | 27 | .DS_Store 28 | .AppleDouble 29 | .LSOverride 30 | 31 | # Thumbnails 32 | ._* 33 | 34 | # Files that might appear in the root of a volume 35 | .DocumentRevisions-V100 36 | .fseventsd 37 | .Spotlight-V100 38 | .TemporaryItems 39 | .Trashes 40 | .VolumeIcon.icns 41 | 42 | # Directories potentially created on remote AFP share 43 | .AppleDB 44 | .AppleDesktop 45 | Network Trash Folder 46 | Temporary Items 47 | .apdisk 48 | 49 | ### compass 50 | *.sass-cache* 51 | 52 | ### npm bower grunt 53 | *.idea* 54 | *node_modules* 55 | *bower_components* 56 | 57 | package-lock.json 58 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "curly": true, 3 | "eqeqeq": true, 4 | "eqnull": true, 5 | "forin": false, 6 | "immed": true, 7 | "indent": 2, 8 | "latedef": true, 9 | "noarg": true, 10 | "noempty": true, 11 | "nonew": true, 12 | "undef": true, 13 | "unused": true, 14 | "strict": true, 15 | "trailing": true, 16 | "browser": true, 17 | "devel": true, 18 | "jquery": true, 19 | "node": true, 20 | "predef": { 21 | "asyncTest": false, 22 | "deepEqual": false, 23 | "equal": false, 24 | "expect": false, 25 | "module": false, 26 | "notDeepEqual": false, 27 | "notEqual": false, 28 | "notStrictEqual": false, 29 | "ok": false, 30 | "QUnit": false, 31 | "raises": false, 32 | "start": false, 33 | "stop": false, 34 | "strictEqual": false, 35 | "test": false 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function( grunt ) { 2 | 'use strict'; 3 | 4 | function loadConfig( path ) { 5 | var glob = require( "glob" ), 6 | object = {}, 7 | key; 8 | 9 | glob.sync( "*", { 10 | cwd: path 11 | }).forEach(function( option ) { 12 | key = option.replace( /\.js$/, "" ); 13 | if( !object.hasOwnProperty( key ) ) { 14 | object[ key ] = {}; 15 | } 16 | grunt.util._.extend( object[ key ], require( path + option ) ); 17 | }); 18 | 19 | return object; 20 | } 21 | 22 | var config = { 23 | pkg: grunt.file.readJSON( "package.json" ), 24 | banner: grunt.file.read( "grunt/banner.txt" ) 25 | }; 26 | 27 | grunt.util._.merge( config, loadConfig( "./grunt/config-lib/" ), loadConfig( "./grunt/config/" ) ); 28 | 29 | grunt.initConfig( config ); 30 | 31 | require( "matchdep" ).filterDev( "grunt-*" ).forEach( grunt.loadNpmTasks ); 32 | 33 | grunt.loadTasks( "grunt" ); 34 | 35 | }; 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Vasileios Mitsaras 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # js-offcanvas 2 | 3 | [![Downloads](https://img.shields.io/npm/dt/js-offcanvas.svg)](https://www.npmjs.com/package/js-offcanvas) [![Version](https://img.shields.io/npm/v/js-offcanvas.svg)](https://www.npmjs.com/package/js-offcanvas) [![AMA](https://img.shields.io/badge/ask%20me-anything-1abc9c.svg)](http://twitter.com/?status=@vmitsaras) 4 | 5 | jQuery accessible Offcanvas plugin, using ARIA 6 | 7 | - [Wiki](https://github.com/vmitsaras/js-offcanvas/wiki) 8 | - [Demo](http://codepen.io/vmitsaras/pen/gwGwJE) 9 | 10 | ## Why it is accessible 11 | 12 | - It relies on ARIA Design pattern for Dialogs 13 | - The tab key loops through all of the keyboard focusable items within the offcanvas 14 | - You can close it using Esc. 15 | 16 | ## Features 17 | 18 | - Uses CSS transforms & transitions. 19 | - BEM c-offcanvas c-offcanvas--left is-open 20 | - From Any Direction: left, right, top and bottom. 21 | - Overlay, Reveal and Push. 22 | - API & Events 23 | - Package managers Bower & NPM 24 | 25 | *** 26 | https://github.com/vmitsaras/js-offcanvas/wiki 27 | 28 | 29 | ## Dependencies 30 | * jQuery 31 | 32 | --- 33 | 34 | ## License 35 | Licensed under the MIT license. 36 | -------------------------------------------------------------------------------- /demo/bs-dashboard.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | js-Offcanvas Test Suite 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 59 | 81 | 210 | 211 | 212 | 247 |
248 | 249 |
250 | 251 |
252 |
253 | 299 | 300 |
301 |

Dashboard

302 |

303 | Login 304 |

305 |
306 |
307 | Generic placeholder thumbnail 308 |

Label

309 |
Something else
310 |
311 |
312 | Generic placeholder thumbnail 313 |

Label

314 | Something else 315 |
316 |
317 | Generic placeholder thumbnail 318 |

Label

319 | Something else 320 |
321 |
322 | Generic placeholder thumbnail 323 |

Label

324 | Something else 325 |
326 |
327 | 328 |

Section title

329 |
330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 |
#HeaderHeaderHeaderHeader
1,001Loremipsumdolorsit
1,002ametconsecteturadipiscingelit
1,003IntegernecodioPraesent
1,003liberoSedcursusante
1,004dapibusdiamSednisi
1,005Nullaquissemat
1,006nibhelementumimperdietDuis
1,007sagittisipsumPraesentmauris
1,008Fuscenectellussed
1,009auguesemperportaMauris
1,010massaVestibulumlaciniaarcu
1,011egetnullaClassaptent
1,012tacitisociosquadlitora
1,013torquentperconubianostra
1,014perinceptoshimenaeosCurabitur
1,015sodalesligulainlibero
455 |
456 |
457 |
458 |
459 |
460 | 461 |
462 | 463 | 476 | 477 | 481 | 496 | 497 | 498 | -------------------------------------------------------------------------------- /demo/bs-starter.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | js-Offcanvas Test Suite 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 59 | 73 | 84 | 85 | 86 |
87 | 119 |
120 | 121 |
122 | 123 |
124 |

Bootstrap starter template

125 |

Use this document as a way to quickly start any new project.
All you get is this text and a mostly barebones HTML document.

126 |
127 | 128 |
129 | 130 |
131 |
132 | 133 | 147 | 148 | 149 | -------------------------------------------------------------------------------- /demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | js-Offcanvas Test Suite 7 | 8 | 9 | 10 | 11 | 12 | 84 | 135 | 136 | 137 | 138 |
139 |
140 | Left 141 | Right 142 | Top 143 | Bottom 144 | 145 | 146 |
147 |
148 | 149 | 153 | 154 | 158 | 159 | 164 | 168 | 169 | 170 | 171 | -------------------------------------------------------------------------------- /demo/no-trigger-button.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | js-Offcanvas without triggerButton 7 | 8 | 9 | 10 | 11 | 12 | 46 | 56 | 57 | 58 | 59 |
60 |
61 |

Offcanvas without triggerButton

62 | 63 | 64 |
65 |             
66 | 
67 |       var leftOffcanvas;
68 | 
69 |       $( '#left' ).on( "create.offcanvas", function( e ){
70 |             leftOffcanvas = $(this).data('offcanvas-component');
71 |             console.log(leftOffcanvas);
72 |         } );
73 | 
74 |         function openOffcanvas () {
75 |             leftOffcanvas.open();
76 |         }
77 |         
78 |         
79 |
80 |
81 | 82 | 83 | 87 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /dist/_css/js-offcanvas.css: -------------------------------------------------------------------------------- 1 | .c-offcanvas { 2 | transform: translate3d(0, 0, 0); 3 | -webkit-backface-visibility: hidden; 4 | backface-visibility: hidden; 5 | } 6 | 7 | .c-offcanvas-bg.c-offcanvas-bg--push, .c-offcanvas-bg.c-offcanvas-bg--reveal, .c-offcanvas-content-wrap, .c-offcanvas { 8 | transition: transform 300ms cubic-bezier(0.4, 0, 0.6, 1); 9 | } 10 | 11 | .c-offcanvas.is-open { 12 | transform: translate3d(0, 0, 0); 13 | visibility: visible; 14 | } 15 | 16 | /** 17 | * Offcanvas-content-wrap 18 | */ 19 | .c-offcanvas-content-wrap { 20 | z-index: 3; 21 | } 22 | 23 | /** 24 | * Offcanvas Panel 25 | */ 26 | .c-offcanvas { 27 | position: fixed; 28 | min-height: 100%; 29 | max-height: none; 30 | top: 0; 31 | display: block; 32 | background: #fff; 33 | overflow-x: hidden; 34 | overflow-y: auto; 35 | } 36 | .c-offcanvas--opening { 37 | transition-timing-function: cubic-bezier(0.4, 0, 0.6, 1); 38 | } 39 | .c-offcanvas.is-closed { 40 | max-height: 100%; 41 | overflow: hidden; 42 | visibility: hidden; 43 | box-shadow: none; 44 | } 45 | 46 | .c-offcanvas--overlay { 47 | z-index: 1080; 48 | } 49 | 50 | .c-offcanvas--reveal { 51 | z-index: 2; 52 | } 53 | 54 | /** 55 | * Offcanvas BG-Overlay 56 | */ 57 | .c-offcanvas-bg { 58 | position: fixed; 59 | top: 0; 60 | height: 100%; 61 | width: 100%; 62 | z-index: 1079; 63 | left: -100%; 64 | background-color: transparent; 65 | transition: background-color 400ms cubic-bezier(0.23, 1, 0.32, 1) 0ms; 66 | } 67 | .c-offcanvas-bg.is-animating, .c-offcanvas-bg.is-open { 68 | left: 0; 69 | background-color: rgba(0, 0, 0, 0.68); 70 | visibility: visible; 71 | } 72 | .c-offcanvas-bg.is-closed { 73 | visibility: hidden; 74 | } 75 | .c-offcanvas-bg--closing.is-animating { 76 | background: transparent; 77 | } 78 | 79 | /** 80 | * Position Left 81 | * 82 | */ 83 | .c-offcanvas--left { 84 | height: 100%; 85 | width: 17em; 86 | transform: translate3d(-17em, 0, 0); 87 | } 88 | 89 | /** 90 | * Position Right 91 | * 92 | */ 93 | .c-offcanvas--right { 94 | height: 100%; 95 | width: 17em; 96 | right: 0; 97 | transform: translate3d(17em, 0, 0); 98 | } 99 | 100 | /** 101 | * Position Top 102 | * 103 | */ 104 | .c-offcanvas--top { 105 | left: 0; 106 | right: 0; 107 | top: 0; 108 | height: 12.5em; 109 | min-height: auto; 110 | width: 100%; 111 | transform: translate3d(0, -12.5em, 0); 112 | } 113 | 114 | /** 115 | * Position Bottom 116 | * 117 | */ 118 | .c-offcanvas--bottom { 119 | top: auto; 120 | left: 0; 121 | right: 0; 122 | bottom: 0; 123 | height: 12.5em; 124 | min-height: auto; 125 | width: 100%; 126 | transform: translate3d(0, 12.5em, 0); 127 | } 128 | 129 | /** 130 | * Reveal 131 | * 132 | */ 133 | .c-offcanvas-content-wrap { 134 | z-index: 3; 135 | } 136 | 137 | .c-offcanvas-content-wrap--reveal.c-offcanvas-content-wrap--left.is-open { 138 | transform: translate3d(17em, 0, 0); 139 | } 140 | .c-offcanvas-content-wrap--reveal.c-offcanvas-content-wrap--right.is-open { 141 | transform: translate3d(-17em, 0, 0); 142 | } 143 | 144 | .c-offcanvas--reveal { 145 | z-index: 0; 146 | transform: translate3d(0, 0, 0); 147 | } 148 | 149 | .c-offcanvas-bg.c-offcanvas-bg--reveal.c-offcanvas-bg--left.is-open { 150 | transform: translate3d(17em, 0, 0); 151 | } 152 | .c-offcanvas-bg.c-offcanvas-bg--reveal.c-offcanvas-bg--right.is-open { 153 | transform: translate3d(-17em, 0, 0); 154 | } 155 | 156 | /** 157 | * Push 158 | * 159 | */ 160 | .c-offcanvas--push { 161 | z-index: 6; 162 | } 163 | .c-offcanvas--push--opening { 164 | transition-timing-function: cubic-bezier(0, 0, 0.2, 1); 165 | } 166 | 167 | .c-offcanvas-content-wrap { 168 | z-index: 3; 169 | } 170 | 171 | .c-offcanvas-content-wrap--push.c-offcanvas-content-wrap--left.is-open { 172 | transform: translate3d(17em, 0, 0); 173 | } 174 | .c-offcanvas-content-wrap--push.c-offcanvas-content-wrap--right.is-open { 175 | transform: translate3d(-17em, 0, 0); 176 | } 177 | 178 | .c-offcanvas-bg.c-offcanvas-bg--push.c-offcanvas-bg--left.is-open { 179 | transform: translate3d(17em, 0, 0); 180 | } 181 | .c-offcanvas-bg.c-offcanvas-bg--push.c-offcanvas-bg--right.is-open { 182 | transform: translate3d(-17em, 0, 0); 183 | } 184 | 185 | /*# sourceMappingURL=js-offcanvas.css.map */ 186 | -------------------------------------------------------------------------------- /dist/_css/js-offcanvas.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sourceRoot":"","sources":["../../src/js-offcanvas.mixins.scss","../../src/js-offcanvas.scss","../../src/js-offcanvas.settings.scss"],"names":[],"mappings":"AAKA;EACE;EACA;EACA;;;AAEF;EACE;;;AAGF;EACE;EACA;;;ACTF;AAAA;AAAA;AAGA;EAGE;;;AAGF;AAAA;AAAA;AAGA;EAGE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAKA;EACE,4BCCU;;ADCZ;EAEE;EACA;EACA;EACA;;;AAKJ;EACE;;;AAGF;EACE;;;AAGF;AAAA;AAAA;AAGA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EAEE;EACA;EACA;;AAGF;EAAa;;AAEX;EAAe;;;AAInB;AAAA;AAAA;AAAA;AAME;EACE;EDnEA,OEhBc;EFiBd;;;ACwFJ;AAAA;AAAA;AAAA;AAME;EACE;ED/EA,OEjCc;EFkCd;EACA;;;ACkFJ;AAAA;AAAA;AAAA;AAME;EDtFE;EACA;EACA;EACA,QEnCU;EFoCV;EACA;EACA;;;ACoFJ;AAAA;AAAA;AAAA;AAME;EDxFE;EACA;EACA;EACA;EACA,QE3Ca;EF4Cb;EACA;EACA;;;ACqFJ;AAAA;AAAA;AAAA;AAKE;EAEE;;;AD/EE;EACE;;AANF;EACE;;;ACmGN;EACE;EACA;;;ADjFA;EACE;;AANF;EACE;;;ACuGN;AAAA;AAAA;AAAA;AAME;EACE;;AACA;EACE,4BChKe;;;ADmKnB;EAEE;;;ADjIE;EACE;;AANF;EACE;;;AAoBJ;EACE;;AANF;EACE","file":"js-offcanvas.css"} -------------------------------------------------------------------------------- /dist/_css/minified/js-offcanvas.css: -------------------------------------------------------------------------------- 1 | .c-offcanvas{transform:translate3d(0,0,0);-webkit-backface-visibility:hidden;backface-visibility:hidden;position:fixed;min-height:100%;max-height:none;top:0;display:block;background:#fff;overflow-x:hidden;overflow-y:auto}.c-offcanvas,.c-offcanvas-bg.c-offcanvas-bg--push,.c-offcanvas-bg.c-offcanvas-bg--reveal,.c-offcanvas-content-wrap{transition:transform .3s cubic-bezier(.4,0,.6,1)}.c-offcanvas.is-open{transform:translate3d(0,0,0);visibility:visible}.c-offcanvas--opening{transition-timing-function:cubic-bezier(.4,0,.6,1)}.c-offcanvas.is-closed{max-height:100%;overflow:hidden;visibility:hidden;box-shadow:none}.c-offcanvas--overlay{z-index:1080}.c-offcanvas-bg{position:fixed;top:0;height:100%;width:100%;z-index:1079;left:-100%;background-color:transparent;transition:background-color .4s cubic-bezier(.23,1,.32,1) 0s}.c-offcanvas-bg.is-animating,.c-offcanvas-bg.is-open{left:0;background-color:rgba(0,0,0,.68);visibility:visible}.c-offcanvas-bg.is-closed{visibility:hidden}.c-offcanvas-bg--closing.is-animating{background:0 0}.c-offcanvas--left{height:100%;width:17em;transform:translate3d(-17em,0,0)}.c-offcanvas--right{height:100%;width:17em;right:0;transform:translate3d(17em,0,0)}.c-offcanvas--bottom,.c-offcanvas--top{left:0;right:0;height:12.5em;min-height:auto;width:100%}.c-offcanvas--top{top:0;transform:translate3d(0,-12.5em,0)}.c-offcanvas--bottom{top:auto;bottom:0;transform:translate3d(0,12.5em,0)}.c-offcanvas-content-wrap--reveal.c-offcanvas-content-wrap--left.is-open{transform:translate3d(17em,0,0)}.c-offcanvas-content-wrap--reveal.c-offcanvas-content-wrap--right.is-open{transform:translate3d(-17em,0,0)}.c-offcanvas--reveal{z-index:0;transform:translate3d(0,0,0)}.c-offcanvas-bg.c-offcanvas-bg--reveal.c-offcanvas-bg--left.is-open{transform:translate3d(17em,0,0)}.c-offcanvas-bg.c-offcanvas-bg--reveal.c-offcanvas-bg--right.is-open{transform:translate3d(-17em,0,0)}.c-offcanvas--push{z-index:6}.c-offcanvas--push--opening{transition-timing-function:cubic-bezier(0,0,.2,1)}.c-offcanvas-content-wrap{z-index:3}.c-offcanvas-content-wrap--push.c-offcanvas-content-wrap--left.is-open{transform:translate3d(17em,0,0)}.c-offcanvas-content-wrap--push.c-offcanvas-content-wrap--right.is-open{transform:translate3d(-17em,0,0)}.c-offcanvas-bg.c-offcanvas-bg--push.c-offcanvas-bg--left.is-open{transform:translate3d(17em,0,0)}.c-offcanvas-bg.c-offcanvas-bg--push.c-offcanvas-bg--right.is-open{transform:translate3d(-17em,0,0)} -------------------------------------------------------------------------------- /dist/_css/prefixed/js-offcanvas.css: -------------------------------------------------------------------------------- 1 | .c-offcanvas { 2 | transform: translate3d(0, 0, 0); 3 | -webkit-backface-visibility: hidden; 4 | backface-visibility: hidden; 5 | } 6 | 7 | .c-offcanvas-bg.c-offcanvas-bg--push, .c-offcanvas-bg.c-offcanvas-bg--reveal, .c-offcanvas-content-wrap, .c-offcanvas { 8 | transition: transform 300ms cubic-bezier(0.4, 0, 0.6, 1); 9 | } 10 | 11 | .c-offcanvas.is-open { 12 | transform: translate3d(0, 0, 0); 13 | visibility: visible; 14 | } 15 | 16 | /** 17 | * Offcanvas-content-wrap 18 | */ 19 | .c-offcanvas-content-wrap { 20 | z-index: 3; 21 | } 22 | 23 | /** 24 | * Offcanvas Panel 25 | */ 26 | .c-offcanvas { 27 | position: fixed; 28 | min-height: 100%; 29 | max-height: none; 30 | top: 0; 31 | display: block; 32 | background: #fff; 33 | overflow-x: hidden; 34 | overflow-y: auto; 35 | } 36 | .c-offcanvas--opening { 37 | transition-timing-function: cubic-bezier(0.4, 0, 0.6, 1); 38 | } 39 | .c-offcanvas.is-closed { 40 | max-height: 100%; 41 | overflow: hidden; 42 | visibility: hidden; 43 | box-shadow: none; 44 | } 45 | 46 | .c-offcanvas--overlay { 47 | z-index: 1080; 48 | } 49 | 50 | .c-offcanvas--reveal { 51 | z-index: 2; 52 | } 53 | 54 | /** 55 | * Offcanvas BG-Overlay 56 | */ 57 | .c-offcanvas-bg { 58 | position: fixed; 59 | top: 0; 60 | height: 100%; 61 | width: 100%; 62 | z-index: 1079; 63 | left: -100%; 64 | background-color: transparent; 65 | transition: background-color 400ms cubic-bezier(0.23, 1, 0.32, 1) 0ms; 66 | } 67 | .c-offcanvas-bg.is-animating, .c-offcanvas-bg.is-open { 68 | left: 0; 69 | background-color: rgba(0, 0, 0, 0.68); 70 | visibility: visible; 71 | } 72 | .c-offcanvas-bg.is-closed { 73 | visibility: hidden; 74 | } 75 | .c-offcanvas-bg--closing.is-animating { 76 | background: transparent; 77 | } 78 | 79 | /** 80 | * Position Left 81 | * 82 | */ 83 | .c-offcanvas--left { 84 | height: 100%; 85 | width: 17em; 86 | transform: translate3d(-17em, 0, 0); 87 | } 88 | 89 | /** 90 | * Position Right 91 | * 92 | */ 93 | .c-offcanvas--right { 94 | height: 100%; 95 | width: 17em; 96 | right: 0; 97 | transform: translate3d(17em, 0, 0); 98 | } 99 | 100 | /** 101 | * Position Top 102 | * 103 | */ 104 | .c-offcanvas--top { 105 | left: 0; 106 | right: 0; 107 | top: 0; 108 | height: 12.5em; 109 | min-height: auto; 110 | width: 100%; 111 | transform: translate3d(0, -12.5em, 0); 112 | } 113 | 114 | /** 115 | * Position Bottom 116 | * 117 | */ 118 | .c-offcanvas--bottom { 119 | top: auto; 120 | left: 0; 121 | right: 0; 122 | bottom: 0; 123 | height: 12.5em; 124 | min-height: auto; 125 | width: 100%; 126 | transform: translate3d(0, 12.5em, 0); 127 | } 128 | 129 | /** 130 | * Reveal 131 | * 132 | */ 133 | .c-offcanvas-content-wrap { 134 | z-index: 3; 135 | } 136 | 137 | .c-offcanvas-content-wrap--reveal.c-offcanvas-content-wrap--left.is-open { 138 | transform: translate3d(17em, 0, 0); 139 | } 140 | .c-offcanvas-content-wrap--reveal.c-offcanvas-content-wrap--right.is-open { 141 | transform: translate3d(-17em, 0, 0); 142 | } 143 | 144 | .c-offcanvas--reveal { 145 | z-index: 0; 146 | transform: translate3d(0, 0, 0); 147 | } 148 | 149 | .c-offcanvas-bg.c-offcanvas-bg--reveal.c-offcanvas-bg--left.is-open { 150 | transform: translate3d(17em, 0, 0); 151 | } 152 | .c-offcanvas-bg.c-offcanvas-bg--reveal.c-offcanvas-bg--right.is-open { 153 | transform: translate3d(-17em, 0, 0); 154 | } 155 | 156 | /** 157 | * Push 158 | * 159 | */ 160 | .c-offcanvas--push { 161 | z-index: 6; 162 | } 163 | .c-offcanvas--push--opening { 164 | transition-timing-function: cubic-bezier(0, 0, 0.2, 1); 165 | } 166 | 167 | .c-offcanvas-content-wrap { 168 | z-index: 3; 169 | } 170 | 171 | .c-offcanvas-content-wrap--push.c-offcanvas-content-wrap--left.is-open { 172 | transform: translate3d(17em, 0, 0); 173 | } 174 | .c-offcanvas-content-wrap--push.c-offcanvas-content-wrap--right.is-open { 175 | transform: translate3d(-17em, 0, 0); 176 | } 177 | 178 | .c-offcanvas-bg.c-offcanvas-bg--push.c-offcanvas-bg--left.is-open { 179 | transform: translate3d(17em, 0, 0); 180 | } 181 | .c-offcanvas-bg.c-offcanvas-bg--push.c-offcanvas-bg--right.is-open { 182 | transform: translate3d(-17em, 0, 0); 183 | } 184 | -------------------------------------------------------------------------------- /dist/_js/js-offcanvas.js: -------------------------------------------------------------------------------- 1 | ;(function( window, $ ){ 2 | "use strict"; 3 | 4 | var name = "offcanvas", 5 | componentName = name + "-component", 6 | utils = window.utils, 7 | doc = document; 8 | 9 | window.componentNamespace = window.componentNamespace || {}; 10 | 11 | var Offcanvas = window.componentNamespace.Offcanvas = function( element,options ){ 12 | if( !element ){ 13 | throw new Error( "Element required to initialize object" ); 14 | } 15 | // assign element for method events 16 | this.element = element; 17 | this.$element = $( element ); 18 | // Options 19 | this.options = options = options || {}; 20 | this.metadata = utils.getMetaOptions( this.element, name ); 21 | this.options = $.extend( {}, this.defaults, this.metadata, options ); 22 | this.isOpen = false; 23 | this.onOpen = this.options.onOpen; 24 | this.onClose = this.options.onClose; 25 | this.onInit = this.options.onInit; 26 | }; 27 | 28 | Offcanvas.prototype.init = function(){ 29 | if ( this.$element.data( componentName ) ) { 30 | return; 31 | } 32 | this.$element.data( componentName, this ); 33 | this.$element.trigger( "beforecreate." + name ); 34 | this._addAttributes(); 35 | this._initTrigger(); 36 | this._createModal(); 37 | this._trapTabKey(); 38 | this._closeButton(); 39 | if( this.onInit && typeof this.onInit === 'function' ) { 40 | this.onInit.call(this.element); 41 | } 42 | this.$element.trigger( "create." + name ); 43 | }; 44 | 45 | Offcanvas.prototype._addAttributes = function(){ 46 | var options = this.options, 47 | panelAttr = { 48 | tabindex: "-1", 49 | "aria-hidden": !this.isOpen 50 | }; 51 | 52 | if ( options.role) { 53 | panelAttr.role = options.role; 54 | } 55 | this._panelClasses = [options.baseClass,utils.classes.isClosed]; 56 | 57 | if(!window.utils.supportTransition){ 58 | this._panelClasses.push( utils.createModifierClass(options.baseClass, options.supportNoTransitionsClass)); 59 | } 60 | utils.cssModifiers(options.modifiers,this._panelClasses,options.baseClass ); 61 | this.$element.attr(panelAttr).addClass( this._panelClasses.join( " " ) ); 62 | 63 | // Content-wrap 64 | this.$content = $('.' + options.contentClass); 65 | this._contentOpenClasses = []; 66 | utils.cssModifiers(options.modifiers,this._contentOpenClasses,options.contentClass ); 67 | 68 | // Modal 69 | this._modalOpenClasses = [options.modalClass,utils.classes.isClosed ]; 70 | utils.cssModifiers(options.modifiers,this._modalOpenClasses,options.modalClass ); 71 | 72 | // body 73 | this._bodyOpenClasses = [options.bodyModifierClass+"--visible"]; 74 | utils.cssModifiers(options.modifiers,this._bodyOpenClasses,options.bodyModifierClass); 75 | 76 | if (options.modifiers.toLowerCase().indexOf("reveal") >= 0) { 77 | this.transitionElement = this.$content[0]; 78 | } else { 79 | this.transitionElement = this.element ; 80 | } 81 | }; 82 | 83 | Offcanvas.prototype._createModal= function() { 84 | var self = this, 85 | target = self.$element.parent(); 86 | if (this.options.modal) { 87 | this.$modal = $( "
" ) 88 | .on( "mousedown."+name, function() { 89 | self.close(); 90 | }) 91 | .appendTo( target ); 92 | this.$modal.addClass( this._modalOpenClasses.join( " " ) ); 93 | } 94 | }; 95 | 96 | Offcanvas.prototype._trapTabKey = function() { 97 | this.trapTabKey = new window.componentNamespace.TrapTabKey(this.element); 98 | this.trapTabKey.init(); 99 | }; 100 | 101 | Offcanvas.prototype._trapTabEscKey = function() { 102 | var self = this; 103 | // close on ESC 104 | $( doc ).on( "keyup." + name, function(ev){ 105 | var keyCode = ev.keyCode || ev.which; 106 | if( keyCode === utils.keyCodes.ESCAPE && self.isOpen ) { 107 | if ($("input").is(":focus")) { 108 | return; 109 | } 110 | self.close(); 111 | } 112 | } ); 113 | }; 114 | 115 | Offcanvas.prototype._closeButton = function() { 116 | var self = this, 117 | options = self.options; 118 | function closeOffcanvas(){ 119 | self.close(); 120 | } 121 | this.$closeBtn = this.$element.find('.'+options.closeButtonClass); 122 | if( this.$closeBtn.length ){ 123 | this.closeBtn = new window.componentNamespace.Button(this.$closeBtn[0]); 124 | this.closeBtn.init(); 125 | this.closeBtn.controls(this.$element.attr('id')); 126 | utils.a11yclickBind(this.$closeBtn,closeOffcanvas,name); 127 | } 128 | }; 129 | 130 | Offcanvas.prototype.open = function(){ 131 | var self = this, 132 | options = self.options; 133 | if (!this.isOpen) { 134 | if (options.resize) { 135 | this.resize(); 136 | } 137 | if( doc.activeElement ){ 138 | this.lastFocus = doc.activeElement; 139 | this.lastFocusTrigger = $(this.lastFocus).data( "button-component"); 140 | } 141 | this.isOpen = true; 142 | $('html, body').addClass(this._bodyOpenClasses.join(" ")); 143 | 144 | this._addClasses(this.$element,this.isOpen,true); 145 | this._addClasses(this.$content,this.isOpen,true); 146 | if (options.modal) { 147 | this._addClasses(this.$modal,this.isOpen,true); 148 | this.$modal.addClass(utils.createModifierClass(options.modalClass,'opening')); 149 | } 150 | 151 | this.$element.attr( "aria-hidden", "false" ) 152 | .addClass(utils.createModifierClass(options.baseClass,'opening')) 153 | .trigger( "opening." + name ); 154 | this.$content.addClass( this._contentOpenClasses.join( " " )); 155 | 156 | // Transition End Callback 157 | utils.onEndTransition ( this.transitionElement, function() { 158 | self.trapTabKey.giveFocus(); 159 | self.trapTabKey.bindTrap(); 160 | self._addClasses(self.$element,self.isOpen,false); 161 | self._addClasses(self.$content,self.isOpen,false); 162 | if (options.modal) { 163 | self._addClasses(self.$modal,self.isOpen,false); 164 | self.$modal.removeClass(utils.createModifierClass(options.modalClass,'opening')); 165 | } 166 | self.$element.removeClass(utils.createModifierClass(options.baseClass,'opening')); 167 | } ); 168 | if( this.$trigger ){ 169 | this.$trigger.button._isExpanded(true); 170 | } 171 | if(this.lastFocusTrigger) { 172 | this.lastFocusTrigger._isExpanded(true); 173 | } 174 | // callback on open 175 | if( this.onOpen && typeof this.onOpen === 'function' ) { 176 | this.onOpen.call(this.$element); 177 | } 178 | // close on ESC 179 | this._trapTabEscKey(); 180 | this.$element.trigger( "open." + name ); 181 | } 182 | }; 183 | 184 | Offcanvas.prototype.close = function(){ 185 | var self = this, 186 | options = self.options; 187 | if( !this.isOpen ){ 188 | return; 189 | } 190 | this.isOpen = false; 191 | 192 | this._addClasses(this.$element,this.isOpen,true); 193 | this._addClasses(this.$content,this.isOpen,true); 194 | 195 | if (this.options.modal) { 196 | this._addClasses(this.$modal,this.isOpen,true); 197 | this.$modal.addClass(utils.createModifierClass(options.modalClass,'closing')); 198 | } 199 | 200 | this.$element.attr( "aria-hidden", "true" ) 201 | .addClass(utils.createModifierClass(options.baseClass,'closing')) 202 | .trigger( "closing." + name ); 203 | 204 | this.trapTabKey.unbindTrap(); 205 | 206 | if( self.$trigger ){ 207 | self.$trigger.button._isExpanded(false); 208 | } 209 | 210 | if(this.lastFocusTrigger) { 211 | this.lastFocusTrigger._isExpanded(false); 212 | this.lastFocusTrigger = null; 213 | } 214 | 215 | utils.onEndTransition ( this.transitionElement, function() { 216 | 217 | self._addClasses(self.$element,self.isOpen,false); 218 | self._addClasses(self.$content,self.isOpen,false); 219 | 220 | if (self.options.modal) { 221 | self._addClasses(self.$modal,self.isOpen,false); 222 | self.$modal.removeClass(utils.createModifierClass(options.modalClass,'closing')); 223 | } 224 | 225 | self.$content.removeClass( self._contentOpenClasses.join( " " ) ); 226 | self.$element.removeClass(utils.createModifierClass(options.baseClass,'closing')); 227 | 228 | $('html, body').removeClass(self._bodyOpenClasses.join(" ")); 229 | 230 | if( self.lastFocus ){ 231 | self.lastFocus.focus(); 232 | } 233 | } ); 234 | // callback onClose 235 | if( this.onClose && typeof this.onClose === 'function' ) { 236 | this.onClose.call(this.element); 237 | } 238 | this.$element.trigger( "close." + name ); 239 | $( doc ).off( "keyup." + name); 240 | $(window).off('.'+name); 241 | }; 242 | 243 | Offcanvas.prototype._addClasses = function(el,isOpen,beforeTransition){ 244 | if (isOpen) { 245 | if (beforeTransition) { 246 | el 247 | .removeClass(utils.classes.isClosed) 248 | .addClass(utils.classes.isAnimating) 249 | .addClass(utils.classes.isOpen); 250 | } else { 251 | el.removeClass(utils.classes.isAnimating); 252 | } 253 | } else { 254 | if (beforeTransition) { 255 | el 256 | .removeClass( utils.classes.isOpen ) 257 | .addClass( utils.classes.isAnimating ); 258 | } else { 259 | el 260 | .addClass( utils.classes.isClosed ) 261 | .removeClass( utils.classes.isAnimating ); 262 | } 263 | } 264 | }; 265 | 266 | Offcanvas.prototype.toggle = function(){ 267 | this[ this.isOpen ? "close" : "open" ](); 268 | }; 269 | 270 | Offcanvas.prototype.resize = function(){ 271 | var self = this,ticking; 272 | 273 | var raf = (function(){ 274 | return window.requestAnimationFrame || 275 | window.webkitRequestAnimationFrame || 276 | window.mozRequestAnimationFrame || 277 | function( callback ){ 278 | window.setTimeout(callback, 1000 / 60); 279 | }; 280 | })(); 281 | 282 | function update() { 283 | ticking = false; 284 | } 285 | function requestTick() { 286 | if(!ticking) { 287 | raf(update); 288 | } 289 | ticking = true; 290 | } 291 | function onResize() { 292 | requestTick(); 293 | self.$element.trigger( "resizing." + name ); 294 | if (self.options.resize) { 295 | self.close(); 296 | } 297 | } 298 | $(window).on('resize.' + name + ' orientationchange.' + name, onResize); 299 | }; 300 | 301 | Offcanvas.prototype._initTrigger = function() { 302 | var self = this, 303 | options = self.options, 304 | offcanvasID = this.$element.attr('id'); 305 | 306 | if (options.triggerButton ) { 307 | this.$triggerBtn = $(options.triggerButton); 308 | new window.componentNamespace.OffcanvasTrigger(this.$triggerBtn[0], {"offcanvas": offcanvasID}).init(); 309 | } 310 | }; 311 | 312 | Offcanvas.prototype.setButton = function(trigger){ 313 | this.$element.data( componentName + "-trigger", trigger ); 314 | }; 315 | 316 | Offcanvas.prototype.destroy = function(){ 317 | 318 | this.$element.trigger( "destroy." + name ); 319 | 320 | if( this.isOpen ){ 321 | this.close(); 322 | } 323 | 324 | this.$element 325 | .removeData() 326 | .removeClass( this._panelClasses.join( " " ) ) 327 | .removeAttr('tabindex') 328 | .removeAttr('aria-hidden'); 329 | 330 | if( this.$triggerBtn ){ 331 | this.$triggerBtn 332 | .removeData('offcanvas-trigger-component') 333 | .off(".offcanvas") 334 | .off(".offcanvas-trigger") 335 | .data('button-component').destroy(); 336 | } 337 | 338 | this.$element.off( "." + name ); 339 | $( doc ).off( "." + name); 340 | $(window).off('.'+name); 341 | 342 | }; 343 | 344 | Offcanvas.prototype.defaults = { 345 | role: "dialog", 346 | modifiers: "left,overlay", 347 | baseClass: "c-offcanvas", 348 | modalClass: "c-offcanvas-bg", 349 | contentClass: "c-offcanvas-content-wrap", 350 | closeButtonClass: "js-offcanvas-close", 351 | bodyModifierClass: "has-offcanvas", 352 | supportNoTransitionsClass: "support-no-transitions", 353 | resize: false, 354 | triggerButton: null, 355 | modal: true, 356 | onOpen: null, 357 | onClose: null, 358 | onInit: null 359 | }; 360 | 361 | Offcanvas.defaults = Offcanvas.prototype.defaults; 362 | 363 | })(this, jQuery); 364 | 365 | (function( w, $ ){ 366 | "use strict"; 367 | 368 | var pluginName = "offcanvas", 369 | initSelector = ".js-" + pluginName; 370 | 371 | $.fn[ pluginName ] = function(options){ 372 | return this.each( function(){ 373 | new w.componentNamespace.Offcanvas( this, options ).init(); 374 | }); 375 | }; 376 | 377 | // auto-init on enhance (which is called on domready) 378 | $( w.document ).on( "enhance", function(e){ 379 | $( $( e.target ).is( initSelector ) && e.target ).add( initSelector, e.target ).filter( initSelector )[ pluginName ](); 380 | }); 381 | 382 | })(this, jQuery); 383 | 384 | (function( w, $ ){ 385 | "use strict"; 386 | 387 | var name = "offcanvas-trigger", 388 | componentName = name + "-component", 389 | utils = w.utils; 390 | 391 | w.componentNamespace = w.componentNamespace || {}; 392 | 393 | var OffcanvasTrigger = w.componentNamespace.OffcanvasTrigger = function( element,options ){ 394 | if( !element ){ 395 | throw new Error( "Element required to initialize object" ); 396 | } 397 | // assign element for method events 398 | this.element = element; 399 | this.$element = $( element ); 400 | // Options 401 | this.options = options = options || {}; 402 | this.options = $.extend( {}, this.defaults, options ); 403 | }; 404 | 405 | OffcanvasTrigger.prototype.init = function(){ 406 | 407 | if ( this.$element.data( componentName ) ) { 408 | return; 409 | } 410 | this.$element.data( componentName, this ); 411 | this._create(); 412 | }; 413 | 414 | OffcanvasTrigger.prototype._create = function(){ 415 | this.options.offcanvas = this.options.offcanvas || this.$element.attr( "data-offcanvas-trigger" ); 416 | this.$offcanvas = $( "#" + this.options.offcanvas ); 417 | this.offcanvas = this.$offcanvas.data( "offcanvas-component" ); 418 | if (!this.offcanvas) { 419 | throw new Error( "Offcanvas Element not found" ); 420 | } 421 | this.button = new w.componentNamespace.Button(this.element); 422 | this.button.init(); 423 | this.button.controls(this.options.offcanvas); 424 | this.button._isExpanded(false); 425 | this._bindbehavior(); 426 | }; 427 | 428 | OffcanvasTrigger.prototype._bindbehavior = function(){ 429 | var self = this; 430 | this.offcanvas.setButton(self); 431 | function toggleOffcanvas(){ 432 | self.offcanvas.toggle(); 433 | } 434 | utils.a11yclickBind(this.$element,toggleOffcanvas,name); 435 | }; 436 | 437 | OffcanvasTrigger.prototype.defaults = { 438 | offcanvas: null 439 | }; 440 | 441 | })(this, jQuery); 442 | 443 | (function( w, $ ){ 444 | "use strict"; 445 | 446 | var pluginName = "offcanvasTrigger", 447 | initSelector = "[data-offcanvas-trigger],.js-" + pluginName; 448 | 449 | $.fn[ pluginName ] = function(options){ 450 | return this.each( function(){ 451 | new w.componentNamespace.OffcanvasTrigger( this,options ).init(); 452 | }); 453 | }; 454 | 455 | // auto-init on enhance (which is called on domready) 456 | $( w.document ).on( "enhance", function(e){ 457 | $( $( e.target ).is( initSelector ) && e.target ).add( initSelector, e.target ).filter( initSelector )[ pluginName ](); 458 | }); 459 | 460 | })(this, jQuery); 461 | -------------------------------------------------------------------------------- /dist/_js/js-offcanvas.min.js: -------------------------------------------------------------------------------- 1 | /*! js-Offcanvas - v1.2.11 - 2019-10-16 2 | jQuery Accesible Offcanvas Panels 3 | * https://github.com/vmitsaras/js-offcanvas 4 | * Copyright (c) 2019 Vasileios Mitsaras (@vmitsaras) 5 | * MIT License */ 6 | !function(a,b){"use strict";var c="offcanvas",d=c+"-component",e=a.utils,f=document;a.componentNamespace=a.componentNamespace||{};var g=a.componentNamespace.Offcanvas=function(a,d){if(!a)throw new Error("Element required to initialize object");this.element=a,this.$element=b(a),this.options=d=d||{},this.metadata=e.getMetaOptions(this.element,c),this.options=b.extend({},this.defaults,this.metadata,d),this.isOpen=!1,this.onOpen=this.options.onOpen,this.onClose=this.options.onClose,this.onInit=this.options.onInit};g.prototype.init=function(){this.$element.data(d)||(this.$element.data(d,this),this.$element.trigger("beforecreate."+c),this._addAttributes(),this._initTrigger(),this._createModal(),this._trapTabKey(),this._closeButton(),this.onInit&&"function"==typeof this.onInit&&this.onInit.call(this.element),this.$element.trigger("create."+c))},g.prototype._addAttributes=function(){var c=this.options,d={tabindex:"-1","aria-hidden":!this.isOpen};c.role&&(d.role=c.role),this._panelClasses=[c.baseClass,e.classes.isClosed],a.utils.supportTransition||this._panelClasses.push(e.createModifierClass(c.baseClass,c.supportNoTransitionsClass)),e.cssModifiers(c.modifiers,this._panelClasses,c.baseClass),this.$element.attr(d).addClass(this._panelClasses.join(" ")),this.$content=b("."+c.contentClass),this._contentOpenClasses=[],e.cssModifiers(c.modifiers,this._contentOpenClasses,c.contentClass),this._modalOpenClasses=[c.modalClass,e.classes.isClosed],e.cssModifiers(c.modifiers,this._modalOpenClasses,c.modalClass),this._bodyOpenClasses=[c.bodyModifierClass+"--visible"],e.cssModifiers(c.modifiers,this._bodyOpenClasses,c.bodyModifierClass),c.modifiers.toLowerCase().indexOf("reveal")>=0?this.transitionElement=this.$content[0]:this.transitionElement=this.element},g.prototype._createModal=function(){var a=this,d=a.$element.parent();this.options.modal&&(this.$modal=b("
").on("mousedown."+c,function(){a.close()}).appendTo(d),this.$modal.addClass(this._modalOpenClasses.join(" ")))},g.prototype._trapTabKey=function(){this.trapTabKey=new a.componentNamespace.TrapTabKey(this.element),this.trapTabKey.init()},g.prototype._trapTabEscKey=function(){var a=this;b(f).on("keyup."+c,function(c){var d=c.keyCode||c.which;if(d===e.keyCodes.ESCAPE&&a.isOpen){if(b("input").is(":focus"))return;a.close()}})},g.prototype._closeButton=function(){function b(){d.close()}var d=this,f=d.options;this.$closeBtn=this.$element.find("."+f.closeButtonClass),this.$closeBtn.length&&(this.closeBtn=new a.componentNamespace.Button(this.$closeBtn[0]),this.closeBtn.init(),this.closeBtn.controls(this.$element.attr("id")),e.a11yclickBind(this.$closeBtn,b,c))},g.prototype.open=function(){var a=this,d=a.options;this.isOpen||(d.resize&&this.resize(),f.activeElement&&(this.lastFocus=f.activeElement,this.lastFocusTrigger=b(this.lastFocus).data("button-component")),this.isOpen=!0,b("html, body").addClass(this._bodyOpenClasses.join(" ")),this._addClasses(this.$element,this.isOpen,!0),this._addClasses(this.$content,this.isOpen,!0),d.modal&&(this._addClasses(this.$modal,this.isOpen,!0),this.$modal.addClass(e.createModifierClass(d.modalClass,"opening"))),this.$element.attr("aria-hidden","false").addClass(e.createModifierClass(d.baseClass,"opening")).trigger("opening."+c),this.$content.addClass(this._contentOpenClasses.join(" ")),e.onEndTransition(this.transitionElement,function(){a.trapTabKey.giveFocus(),a.trapTabKey.bindTrap(),a._addClasses(a.$element,a.isOpen,!1),a._addClasses(a.$content,a.isOpen,!1),d.modal&&(a._addClasses(a.$modal,a.isOpen,!1),a.$modal.removeClass(e.createModifierClass(d.modalClass,"opening"))),a.$element.removeClass(e.createModifierClass(d.baseClass,"opening"))}),this.$trigger&&this.$trigger.button._isExpanded(!0),this.lastFocusTrigger&&this.lastFocusTrigger._isExpanded(!0),this.onOpen&&"function"==typeof this.onOpen&&this.onOpen.call(this.$element),this._trapTabEscKey(),this.$element.trigger("open."+c))},g.prototype.close=function(){var d=this,g=d.options;this.isOpen&&(this.isOpen=!1,this._addClasses(this.$element,this.isOpen,!0),this._addClasses(this.$content,this.isOpen,!0),this.options.modal&&(this._addClasses(this.$modal,this.isOpen,!0),this.$modal.addClass(e.createModifierClass(g.modalClass,"closing"))),this.$element.attr("aria-hidden","true").addClass(e.createModifierClass(g.baseClass,"closing")).trigger("closing."+c),this.trapTabKey.unbindTrap(),d.$trigger&&d.$trigger.button._isExpanded(!1),this.lastFocusTrigger&&(this.lastFocusTrigger._isExpanded(!1),this.lastFocusTrigger=null),e.onEndTransition(this.transitionElement,function(){d._addClasses(d.$element,d.isOpen,!1),d._addClasses(d.$content,d.isOpen,!1),d.options.modal&&(d._addClasses(d.$modal,d.isOpen,!1),d.$modal.removeClass(e.createModifierClass(g.modalClass,"closing"))),d.$content.removeClass(d._contentOpenClasses.join(" ")),d.$element.removeClass(e.createModifierClass(g.baseClass,"closing")),b("html, body").removeClass(d._bodyOpenClasses.join(" ")),d.lastFocus&&d.lastFocus.focus()}),this.onClose&&"function"==typeof this.onClose&&this.onClose.call(this.element),this.$element.trigger("close."+c),b(f).off("keyup."+c),b(a).off("."+c))},g.prototype._addClasses=function(a,b,c){b?c?a.removeClass(e.classes.isClosed).addClass(e.classes.isAnimating).addClass(e.classes.isOpen):a.removeClass(e.classes.isAnimating):c?a.removeClass(e.classes.isOpen).addClass(e.classes.isAnimating):a.addClass(e.classes.isClosed).removeClass(e.classes.isAnimating)},g.prototype.toggle=function(){this[this.isOpen?"close":"open"]()},g.prototype.resize=function(){function d(){g=!1}function e(){g||i(d),g=!0}function f(){e(),h.$element.trigger("resizing."+c),h.options.resize&&h.close()}var g,h=this,i=function(){return a.requestAnimationFrame||a.webkitRequestAnimationFrame||a.mozRequestAnimationFrame||function(b){a.setTimeout(b,1e3/60)}}();b(a).on("resize."+c+" orientationchange."+c,f)},g.prototype._initTrigger=function(){var c=this,d=c.options,e=this.$element.attr("id");d.triggerButton&&(this.$triggerBtn=b(d.triggerButton),new a.componentNamespace.OffcanvasTrigger(this.$triggerBtn[0],{offcanvas:e}).init())},g.prototype.setButton=function(a){this.$element.data(d+"-trigger",a)},g.prototype.destroy=function(){this.$element.trigger("destroy."+c),this.isOpen&&this.close(),this.$element.removeData().removeClass(this._panelClasses.join(" ")).removeAttr("tabindex").removeAttr("aria-hidden"),this.$triggerBtn&&this.$triggerBtn.removeData("offcanvas-trigger-component").off(".offcanvas").off(".offcanvas-trigger").data("button-component").destroy(),this.$element.off("."+c),b(f).off("."+c),b(a).off("."+c)},g.prototype.defaults={role:"dialog",modifiers:"left,overlay",baseClass:"c-offcanvas",modalClass:"c-offcanvas-bg",contentClass:"c-offcanvas-content-wrap",closeButtonClass:"js-offcanvas-close",bodyModifierClass:"has-offcanvas",supportNoTransitionsClass:"support-no-transitions",resize:!1,triggerButton:null,modal:!0,onOpen:null,onClose:null,onInit:null},g.defaults=g.prototype.defaults}(this,jQuery),function(a,b){"use strict";var c="offcanvas",d=".js-"+c;b.fn[c]=function(b){return this.each(function(){new a.componentNamespace.Offcanvas(this,b).init()})},b(a.document).on("enhance",function(a){b(b(a.target).is(d)&&a.target).add(d,a.target).filter(d)[c]()})}(this,jQuery),function(a,b){"use strict";var c="offcanvas-trigger",d=c+"-component",e=a.utils;a.componentNamespace=a.componentNamespace||{};var f=a.componentNamespace.OffcanvasTrigger=function(a,c){if(!a)throw new Error("Element required to initialize object");this.element=a,this.$element=b(a),this.options=c=c||{},this.options=b.extend({},this.defaults,c)};f.prototype.init=function(){this.$element.data(d)||(this.$element.data(d,this),this._create())},f.prototype._create=function(){if(this.options.offcanvas=this.options.offcanvas||this.$element.attr("data-offcanvas-trigger"),this.$offcanvas=b("#"+this.options.offcanvas),this.offcanvas=this.$offcanvas.data("offcanvas-component"),!this.offcanvas)throw new Error("Offcanvas Element not found");this.button=new a.componentNamespace.Button(this.element),this.button.init(),this.button.controls(this.options.offcanvas),this.button._isExpanded(!1),this._bindbehavior()},f.prototype._bindbehavior=function(){function a(){b.offcanvas.toggle()}var b=this;this.offcanvas.setButton(b),e.a11yclickBind(this.$element,a,c)},f.prototype.defaults={offcanvas:null}}(this,jQuery),function(a,b){"use strict";var c="offcanvasTrigger",d="[data-offcanvas-trigger],.js-"+c;b.fn[c]=function(b){return this.each(function(){new a.componentNamespace.OffcanvasTrigger(this,b).init()})},b(a.document).on("enhance",function(a){b(b(a.target).is(d)&&a.target).add(d,a.target).filter(d)[c]()})}(this,jQuery); -------------------------------------------------------------------------------- /dist/_js/js-offcanvas.pkgd.js: -------------------------------------------------------------------------------- 1 | ;(function( window ){ 2 | "use strict"; 3 | 4 | var utils = window.utils || {}; 5 | 6 | utils.classes = { 7 | hiddenVisually: "u-hidden-visually", 8 | modifier: "--", 9 | isActive: "is-active", 10 | isClosed: "is-closed", 11 | isOpen: "is-open", 12 | isClicked: "is-clicked", 13 | isAnimating: "is-animating", 14 | isVisible: "is-visible", 15 | hidden: "u-hidden" 16 | }; 17 | 18 | utils.keyCodes = { 19 | BACKSPACE: 8, 20 | COMMA: 188, 21 | DELETE: 46, 22 | DOWN: 40, 23 | END: 35, 24 | ENTER: 13, 25 | ESCAPE: 27, 26 | HOME: 36, 27 | LEFT: 37, 28 | PAGE_DOWN: 34, 29 | PAGE_UP: 33, 30 | PERIOD: 190, 31 | RIGHT: 39, 32 | SPACE: 32, 33 | TAB: 9, 34 | UP: 38 35 | }; 36 | 37 | utils.a11yclick = function(event) { 38 | var code = event.charCode || event.keyCode, 39 | type = event.type; 40 | 41 | if (type === 'click') { 42 | return true; 43 | } else if (type === 'keydown') { 44 | if (code === utils.keyCodes.SPACE || code === utils.keyCodes.ENTER) { 45 | return true; 46 | } 47 | } else { 48 | return false; 49 | } 50 | }; 51 | 52 | utils.a11yclickBind = function(el, callback, name) { 53 | el.on("click." + name + " keydown." + name,function(event){ 54 | if ( utils.a11yclick(event)) { 55 | event.preventDefault(event); 56 | if( callback && typeof callback === 'function' ) { callback.call(); } 57 | el.trigger('clicked.'+name); 58 | } 59 | }); 60 | }; 61 | 62 | utils.supportTransition = ('transition' in document.documentElement.style) || ('WebkitTransition' in document.documentElement.style); 63 | 64 | utils.whichTransitionEvent = function () { 65 | var el = document.createElement('fakeelement'), 66 | transitions = { 67 | 'transition': 'transitionend', 68 | 'WebkitTransition': 'webkitTransitionEnd' 69 | }; 70 | 71 | for (var t in transitions) { 72 | if (el.style[t] !== undefined) { 73 | return transitions[t]; 74 | } 75 | } 76 | }; 77 | 78 | utils.transEndEventName = utils.whichTransitionEvent(); 79 | 80 | utils.onEndTransition = function( el, callback ) { 81 | var onEndCallbackFn = function( ev ) { 82 | if( utils.supportTransition ) { 83 | if( ev.target != this ) return; 84 | this.removeEventListener( utils.transEndEventName, onEndCallbackFn ); 85 | } 86 | if( callback && typeof callback === 'function' ) { callback.call(); } 87 | }; 88 | if( utils.supportTransition ) { 89 | el.addEventListener( utils.transEndEventName, onEndCallbackFn ); 90 | } 91 | else { 92 | onEndCallbackFn(); 93 | } 94 | }; 95 | 96 | utils.createModifierClass = function( cl, modifier ){ 97 | return cl + utils.classes.modifier + modifier 98 | }; 99 | 100 | utils.cssModifiers = function( modifiers, cssClasses, baseClass ){ 101 | var arr = modifiers.split(","); 102 | for(var i=0, l = arr.length; i < l; i++){ 103 | cssClasses.push( utils.createModifierClass(baseClass,arr[i].trim()) ); 104 | } 105 | }; 106 | 107 | utils.getMetaOptions = function( el, name, metadata ){ 108 | var dataAttr = 'data-' + name, 109 | dataOptionsAttr = dataAttr + '-options', 110 | attr = el.getAttribute( dataAttr ) || el.getAttribute( dataOptionsAttr ); 111 | try { 112 | return attr && JSON.parse( attr ) || {}; 113 | } catch ( error ) { 114 | // log error, do not initialize 115 | if ( console ) { 116 | console.error( 'Error parsing ' + dataAttr + ' on ' + el.className + ': ' + error ); 117 | } 118 | return; 119 | } 120 | }; 121 | 122 | window.utils = utils; 123 | 124 | })(this); 125 | 126 | 127 | /* 128 | * TrapTabKey 129 | * Based on https://github.com/gdkraus/accessible-modal-dialog/blob/master/modal-window.js 130 | * Copyright (c) 2016 Vasileios Mitsaras. 131 | * Licensed under MIT 132 | */ 133 | 134 | (function( w, $ ){ 135 | "use strict"; 136 | 137 | var name = "trab-tab", 138 | componentName = name + "-component"; 139 | 140 | w.componentNamespace = w.componentNamespace || {}; 141 | 142 | var TrapTabKey = w.componentNamespace.TrapTabKey = function( element,options ){ 143 | if( !element ){ 144 | throw new Error( "Element required to initialize object" ); 145 | } 146 | // assign element for method events 147 | this.element = element; 148 | this.$element = $( element ); 149 | // Options 150 | options = options || {}; 151 | this.options = $.extend( {}, this.defaults, options ); 152 | }; 153 | 154 | 155 | TrapTabKey.prototype.init = function(){ 156 | 157 | if ( this.$element.data( componentName ) ) { 158 | return; 159 | } 160 | 161 | this.$element.data( componentName, this ); 162 | }; 163 | 164 | TrapTabKey.prototype.bindTrap = function(){ 165 | var self = this; 166 | 167 | this.$element 168 | .on( 'keydown.' + name, function( e ){ 169 | self._trapTabKey(self.$element, e ); 170 | } ); 171 | }; 172 | 173 | TrapTabKey.prototype.unbindTrap = function(){ 174 | this.$element 175 | .off( 'keydown.' + name); 176 | }; 177 | 178 | TrapTabKey.prototype.giveFocus = function(){ 179 | var self = this, 180 | opts = self.options; 181 | 182 | // get list of all children elements in given object 183 | var o = self.$element.find('*'), 184 | focusEl = self.$element.find('[data-focus]'); 185 | 186 | // set the focus to the first keyboard focusable item 187 | focusEl.length ? focusEl.first().focus() : o.filter(opts.focusableElementsString).filter(':visible').first().focus(); 188 | 189 | }; 190 | 191 | 192 | TrapTabKey.prototype._trapTabKey = function(obj, evt){ 193 | var self = this, 194 | opts = self.options; 195 | 196 | // if tab or shift-tab pressed 197 | if (evt.which == 9) { 198 | 199 | // get list of all children elements in given object 200 | var o = obj.find('*'); 201 | 202 | // get list of focusable items 203 | var focusableItems; 204 | focusableItems = o.filter(opts.focusableElementsString).filter(':visible'); 205 | 206 | // get currently focused item 207 | var focusedItem; 208 | focusedItem = jQuery(':focus'); 209 | 210 | // get the number of focusable items 211 | var numberOfFocusableItems; 212 | numberOfFocusableItems = focusableItems.length; 213 | 214 | // get the index of the currently focused item 215 | var focusedItemIndex; 216 | focusedItemIndex = focusableItems.index(focusedItem); 217 | 218 | if (evt.shiftKey) { 219 | //back tab 220 | // if focused on first item and user preses back-tab, go to the last focusable item 221 | if (focusedItemIndex == 0) { 222 | focusableItems.get(numberOfFocusableItems - 1).focus(); 223 | evt.preventDefault(); 224 | } 225 | 226 | } else { 227 | //forward tab 228 | // if focused on the last item and user preses tab, go to the first focusable item 229 | if (focusedItemIndex == numberOfFocusableItems - 1) { 230 | focusableItems.get(0).focus(); 231 | evt.preventDefault(); 232 | } 233 | } 234 | } 235 | 236 | }; 237 | 238 | TrapTabKey.prototype.defaults = { 239 | focusableElementsString : "a[href], area[href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), iframe, object, embed, *[tabindex], *[contenteditable]" 240 | }; 241 | 242 | TrapTabKey.defaults = TrapTabKey.prototype.defaults; 243 | 244 | })(this, jQuery); 245 | 246 | (function( window, $ ){ 247 | "use strict"; 248 | var name = "button", 249 | componentName = name + "-component", 250 | utils = window.utils, 251 | cl = { 252 | iconOnly: "icon-only", 253 | withIcon: "icon", 254 | toggleState: "toggle-state", 255 | showHide: "visible-on-active" 256 | }; 257 | 258 | window.componentNamespace = window.componentNamespace || {}; 259 | 260 | var Button = window.componentNamespace.Button = function( element, options ){ 261 | if( !element ){ 262 | throw new Error( "Element required to initialize object" ); 263 | } 264 | // assign element for method events 265 | this.element = element; 266 | this.$element = $( element ); 267 | // Options 268 | this.options = options = options || {}; 269 | this.metadata = utils.getMetaOptions( this.element, name ); 270 | this.options = $.extend( {}, this.defaults, this.metadata, options ); 271 | }; 272 | 273 | Button.prototype.init = function(){ 274 | 275 | if ( this.$element.data( componentName ) ) { 276 | return; 277 | } 278 | 279 | this.$element.data( componentName, this ); 280 | this.hasTitle = !!this.$element.attr( "title" ); 281 | this.$element.trigger( "beforecreate." + name ); 282 | this.isPressed = false; 283 | this.isExpanded = false; 284 | this._create(); 285 | 286 | }; 287 | 288 | Button.prototype._create = function(){ 289 | var options = this.options, 290 | buttonTextClasses = [options.baseClass + '__text']; 291 | 292 | this._buttonClasses = [options.baseClass]; 293 | if ( options.label === null ) { 294 | options.label = this.$element.html(); 295 | } 296 | 297 | if ( options.wrapText ) { 298 | this.$buttonText = $( '' ).html( options.label ).appendTo(this.$element.empty()); 299 | } 300 | 301 | if ( options.icon ) { 302 | 303 | this.$buttonIcon = $( "" ).prependTo(this.$element); 304 | this._buttonClasses.push( utils.createModifierClass(options.baseClass,cl.withIcon) ); 305 | 306 | if ( options.iconActive ) { 307 | options.toggle = true; 308 | this.$buttonIconActive = $( "" ).insertAfter(this.$buttonIcon); 309 | this._buttonClasses.push( utils.createModifierClass(options.baseClass,cl.toggleState) ); 310 | } 311 | if ( options.hideText ) { 312 | buttonTextClasses.push(utils.classes.hiddenVisually ); 313 | this._buttonClasses.push( utils.createModifierClass(options.baseClass,cl.iconOnly) ); 314 | } 315 | } 316 | 317 | if ( options.modifiers ) { 318 | utils.cssModifiers(options.modifiers,this._buttonClasses,options.baseClass); 319 | } 320 | if ( options.wrapText ) { 321 | this.$buttonText.addClass( buttonTextClasses.join( " " ) ); 322 | } 323 | 324 | if ( options.textActive && options.wrapText ) { 325 | options.toggle = true; 326 | buttonTextClasses.push( utils.createModifierClass(options.baseClass+'__text',cl.showHide) ); 327 | this._buttonClasses.push( utils.createModifierClass(options.baseClass,cl.toggleState) ); 328 | 329 | this.$buttonTextActive = $( '' ) 330 | .addClass( buttonTextClasses.join( " " ) ) 331 | .html( options.textActive ) 332 | .insertAfter(this.$buttonText); 333 | this.$element.attr('aria-live','polite'); 334 | } 335 | 336 | this.$element.addClass( this._buttonClasses.join( " " ) ); 337 | 338 | if ( options.role) { 339 | this.$element.attr( "role", options.role ); 340 | } 341 | if ( options.controls ) { 342 | this.controls(options.controls); 343 | } 344 | if ( options.pressed ) { 345 | this._isPressed(options.pressed); 346 | } 347 | if ( options.expanded ) { 348 | this.isPressed = true; 349 | this._isExpanded(options.expanded); 350 | } 351 | if ( !this.hasTitle && options.hideText && !options.hideTitle ) { 352 | this.$element.attr('title',this.$element.text()); 353 | } 354 | this.$element.trigger( "create." + name ); 355 | }; 356 | 357 | Button.prototype._isPressed = function(state){ 358 | this.isPressed = state; 359 | this.$element.attr( "aria-pressed", state )[ state ? "addClass" : "removeClass" ](utils.classes.isActive); 360 | }; 361 | 362 | Button.prototype._isExpanded = function(state){ 363 | this.isExpanded = state; 364 | this.$element.attr( "aria-expanded", state )[ state ? "addClass" : "removeClass" ](utils.classes.isActive); 365 | }; 366 | 367 | Button.prototype.controls = function(el){ 368 | this.$element.attr( "aria-controls", el ); 369 | }; 370 | 371 | Button.prototype.destroy = function(){ 372 | var options = this.options; 373 | 374 | this.$element 375 | .removeData(componentName) 376 | .removeAttr('role') 377 | .removeAttr('aria-pressed') 378 | .removeAttr('aria-expanded') 379 | .removeAttr('aria-controls') 380 | .removeClass( this._buttonClasses.join( " " ) ) 381 | .removeClass( utils.classes.isActive) 382 | .off("."+name); 383 | if ( this.options.icon ) { 384 | this.$element.find('[class^="'+this.options.iconFamily+'"]').remove(); 385 | } 386 | 387 | if ( options.wrapText ) { 388 | var btnHtml = this.$buttonText.html(); 389 | this.$element.empty().html(btnHtml); 390 | } 391 | 392 | this.element = null; 393 | this.$element = null; 394 | }; 395 | 396 | Button.prototype.defaults = { 397 | baseClass:"c-button", 398 | role: "button", 399 | label: null, 400 | modifiers: null, 401 | controls: null, 402 | textActive: null, 403 | wrapText: true, 404 | hideText: false, 405 | hideTitle: false, 406 | icon: null, 407 | iconActive: null, 408 | iconFamily: "o-icon", 409 | iconPosition: null, 410 | pressed: false, 411 | expanded: false 412 | }; 413 | 414 | Button.defaults = Button.prototype.defaults; 415 | 416 | })(this, jQuery); 417 | 418 | (function( w, $ ){ 419 | "use strict"; 420 | 421 | var pluginName = "jsButton", 422 | initSelector = ".js-button"; 423 | 424 | $.fn[ pluginName ] = function(){ 425 | return this.each( function(){ 426 | new window.componentNamespace.Button( this ).init(); 427 | }); 428 | }; 429 | 430 | // auto-init on enhance (which is called on domready) 431 | $( document ).bind( "enhance", function( e ){ 432 | $( $( e.target ).is( initSelector ) && e.target ).add( initSelector, e.target ).filter( initSelector )[ pluginName ](); 433 | }); 434 | })(this, jQuery); 435 | 436 | ;(function( window, $ ){ 437 | "use strict"; 438 | 439 | var name = "offcanvas", 440 | componentName = name + "-component", 441 | utils = window.utils, 442 | doc = document; 443 | 444 | window.componentNamespace = window.componentNamespace || {}; 445 | 446 | var Offcanvas = window.componentNamespace.Offcanvas = function( element,options ){ 447 | if( !element ){ 448 | throw new Error( "Element required to initialize object" ); 449 | } 450 | // assign element for method events 451 | this.element = element; 452 | this.$element = $( element ); 453 | // Options 454 | this.options = options = options || {}; 455 | this.metadata = utils.getMetaOptions( this.element, name ); 456 | this.options = $.extend( {}, this.defaults, this.metadata, options ); 457 | this.isOpen = false; 458 | this.onOpen = this.options.onOpen; 459 | this.onClose = this.options.onClose; 460 | this.onInit = this.options.onInit; 461 | }; 462 | 463 | Offcanvas.prototype.init = function(){ 464 | if ( this.$element.data( componentName ) ) { 465 | return; 466 | } 467 | this.$element.data( componentName, this ); 468 | this.$element.trigger( "beforecreate." + name ); 469 | this._addAttributes(); 470 | this._initTrigger(); 471 | this._createModal(); 472 | this._trapTabKey(); 473 | this._closeButton(); 474 | if( this.onInit && typeof this.onInit === 'function' ) { 475 | this.onInit.call(this.element); 476 | } 477 | this.$element.trigger( "create." + name ); 478 | }; 479 | 480 | Offcanvas.prototype._addAttributes = function(){ 481 | var options = this.options, 482 | panelAttr = { 483 | tabindex: "-1", 484 | "aria-hidden": !this.isOpen 485 | }; 486 | 487 | if ( options.role) { 488 | panelAttr.role = options.role; 489 | } 490 | this._panelClasses = [options.baseClass,utils.classes.isClosed]; 491 | 492 | if(!window.utils.supportTransition){ 493 | this._panelClasses.push( utils.createModifierClass(options.baseClass, options.supportNoTransitionsClass)); 494 | } 495 | utils.cssModifiers(options.modifiers,this._panelClasses,options.baseClass ); 496 | this.$element.attr(panelAttr).addClass( this._panelClasses.join( " " ) ); 497 | 498 | // Content-wrap 499 | this.$content = $('.' + options.contentClass); 500 | this._contentOpenClasses = []; 501 | utils.cssModifiers(options.modifiers,this._contentOpenClasses,options.contentClass ); 502 | 503 | // Modal 504 | this._modalOpenClasses = [options.modalClass,utils.classes.isClosed ]; 505 | utils.cssModifiers(options.modifiers,this._modalOpenClasses,options.modalClass ); 506 | 507 | // body 508 | this._bodyOpenClasses = [options.bodyModifierClass+"--visible"]; 509 | utils.cssModifiers(options.modifiers,this._bodyOpenClasses,options.bodyModifierClass); 510 | 511 | if (options.modifiers.toLowerCase().indexOf("reveal") >= 0) { 512 | this.transitionElement = this.$content[0]; 513 | } else { 514 | this.transitionElement = this.element ; 515 | } 516 | }; 517 | 518 | Offcanvas.prototype._createModal= function() { 519 | var self = this, 520 | target = self.$element.parent(); 521 | if (this.options.modal) { 522 | this.$modal = $( "
" ) 523 | .on( "mousedown."+name, function() { 524 | self.close(); 525 | }) 526 | .appendTo( target ); 527 | this.$modal.addClass( this._modalOpenClasses.join( " " ) ); 528 | } 529 | }; 530 | 531 | Offcanvas.prototype._trapTabKey = function() { 532 | this.trapTabKey = new window.componentNamespace.TrapTabKey(this.element); 533 | this.trapTabKey.init(); 534 | }; 535 | 536 | Offcanvas.prototype._trapTabEscKey = function() { 537 | var self = this; 538 | // close on ESC 539 | $( doc ).on( "keyup." + name, function(ev){ 540 | var keyCode = ev.keyCode || ev.which; 541 | if( keyCode === utils.keyCodes.ESCAPE && self.isOpen ) { 542 | if ($("input").is(":focus")) { 543 | return; 544 | } 545 | self.close(); 546 | } 547 | } ); 548 | }; 549 | 550 | Offcanvas.prototype._closeButton = function() { 551 | var self = this, 552 | options = self.options; 553 | function closeOffcanvas(){ 554 | self.close(); 555 | } 556 | this.$closeBtn = this.$element.find('.'+options.closeButtonClass); 557 | if( this.$closeBtn.length ){ 558 | this.closeBtn = new window.componentNamespace.Button(this.$closeBtn[0]); 559 | this.closeBtn.init(); 560 | this.closeBtn.controls(this.$element.attr('id')); 561 | utils.a11yclickBind(this.$closeBtn,closeOffcanvas,name); 562 | } 563 | }; 564 | 565 | Offcanvas.prototype.open = function(){ 566 | var self = this, 567 | options = self.options; 568 | if (!this.isOpen) { 569 | if (options.resize) { 570 | this.resize(); 571 | } 572 | if( doc.activeElement ){ 573 | this.lastFocus = doc.activeElement; 574 | this.lastFocusTrigger = $(this.lastFocus).data( "button-component"); 575 | } 576 | this.isOpen = true; 577 | $('html, body').addClass(this._bodyOpenClasses.join(" ")); 578 | 579 | this._addClasses(this.$element,this.isOpen,true); 580 | this._addClasses(this.$content,this.isOpen,true); 581 | if (options.modal) { 582 | this._addClasses(this.$modal,this.isOpen,true); 583 | this.$modal.addClass(utils.createModifierClass(options.modalClass,'opening')); 584 | } 585 | 586 | this.$element.attr( "aria-hidden", "false" ) 587 | .addClass(utils.createModifierClass(options.baseClass,'opening')) 588 | .trigger( "opening." + name ); 589 | this.$content.addClass( this._contentOpenClasses.join( " " )); 590 | 591 | // Transition End Callback 592 | utils.onEndTransition ( this.transitionElement, function() { 593 | self.trapTabKey.giveFocus(); 594 | self.trapTabKey.bindTrap(); 595 | self._addClasses(self.$element,self.isOpen,false); 596 | self._addClasses(self.$content,self.isOpen,false); 597 | if (options.modal) { 598 | self._addClasses(self.$modal,self.isOpen,false); 599 | self.$modal.removeClass(utils.createModifierClass(options.modalClass,'opening')); 600 | } 601 | self.$element.removeClass(utils.createModifierClass(options.baseClass,'opening')); 602 | } ); 603 | if( this.$trigger ){ 604 | this.$trigger.button._isExpanded(true); 605 | } 606 | if(this.lastFocusTrigger) { 607 | this.lastFocusTrigger._isExpanded(true); 608 | } 609 | // callback on open 610 | if( this.onOpen && typeof this.onOpen === 'function' ) { 611 | this.onOpen.call(this.$element); 612 | } 613 | // close on ESC 614 | this._trapTabEscKey(); 615 | this.$element.trigger( "open." + name ); 616 | } 617 | }; 618 | 619 | Offcanvas.prototype.close = function(){ 620 | var self = this, 621 | options = self.options; 622 | if( !this.isOpen ){ 623 | return; 624 | } 625 | this.isOpen = false; 626 | 627 | this._addClasses(this.$element,this.isOpen,true); 628 | this._addClasses(this.$content,this.isOpen,true); 629 | 630 | if (this.options.modal) { 631 | this._addClasses(this.$modal,this.isOpen,true); 632 | this.$modal.addClass(utils.createModifierClass(options.modalClass,'closing')); 633 | } 634 | 635 | this.$element.attr( "aria-hidden", "true" ) 636 | .addClass(utils.createModifierClass(options.baseClass,'closing')) 637 | .trigger( "closing." + name ); 638 | 639 | this.trapTabKey.unbindTrap(); 640 | 641 | if( self.$trigger ){ 642 | self.$trigger.button._isExpanded(false); 643 | } 644 | 645 | if(this.lastFocusTrigger) { 646 | this.lastFocusTrigger._isExpanded(false); 647 | this.lastFocusTrigger = null; 648 | } 649 | 650 | utils.onEndTransition ( this.transitionElement, function() { 651 | 652 | self._addClasses(self.$element,self.isOpen,false); 653 | self._addClasses(self.$content,self.isOpen,false); 654 | 655 | if (self.options.modal) { 656 | self._addClasses(self.$modal,self.isOpen,false); 657 | self.$modal.removeClass(utils.createModifierClass(options.modalClass,'closing')); 658 | } 659 | 660 | self.$content.removeClass( self._contentOpenClasses.join( " " ) ); 661 | self.$element.removeClass(utils.createModifierClass(options.baseClass,'closing')); 662 | 663 | $('html, body').removeClass(self._bodyOpenClasses.join(" ")); 664 | 665 | if( self.lastFocus ){ 666 | self.lastFocus.focus(); 667 | } 668 | } ); 669 | // callback onClose 670 | if( this.onClose && typeof this.onClose === 'function' ) { 671 | this.onClose.call(this.element); 672 | } 673 | this.$element.trigger( "close." + name ); 674 | $( doc ).off( "keyup." + name); 675 | $(window).off('.'+name); 676 | }; 677 | 678 | Offcanvas.prototype._addClasses = function(el,isOpen,beforeTransition){ 679 | if (isOpen) { 680 | if (beforeTransition) { 681 | el 682 | .removeClass(utils.classes.isClosed) 683 | .addClass(utils.classes.isAnimating) 684 | .addClass(utils.classes.isOpen); 685 | } else { 686 | el.removeClass(utils.classes.isAnimating); 687 | } 688 | } else { 689 | if (beforeTransition) { 690 | el 691 | .removeClass( utils.classes.isOpen ) 692 | .addClass( utils.classes.isAnimating ); 693 | } else { 694 | el 695 | .addClass( utils.classes.isClosed ) 696 | .removeClass( utils.classes.isAnimating ); 697 | } 698 | } 699 | }; 700 | 701 | Offcanvas.prototype.toggle = function(){ 702 | this[ this.isOpen ? "close" : "open" ](); 703 | }; 704 | 705 | Offcanvas.prototype.resize = function(){ 706 | var self = this,ticking; 707 | 708 | var raf = (function(){ 709 | return window.requestAnimationFrame || 710 | window.webkitRequestAnimationFrame || 711 | window.mozRequestAnimationFrame || 712 | function( callback ){ 713 | window.setTimeout(callback, 1000 / 60); 714 | }; 715 | })(); 716 | 717 | function update() { 718 | ticking = false; 719 | } 720 | function requestTick() { 721 | if(!ticking) { 722 | raf(update); 723 | } 724 | ticking = true; 725 | } 726 | function onResize() { 727 | requestTick(); 728 | self.$element.trigger( "resizing." + name ); 729 | if (self.options.resize) { 730 | self.close(); 731 | } 732 | } 733 | $(window).on('resize.' + name + ' orientationchange.' + name, onResize); 734 | }; 735 | 736 | Offcanvas.prototype._initTrigger = function() { 737 | var self = this, 738 | options = self.options, 739 | offcanvasID = this.$element.attr('id'); 740 | 741 | if (options.triggerButton ) { 742 | this.$triggerBtn = $(options.triggerButton); 743 | new window.componentNamespace.OffcanvasTrigger(this.$triggerBtn[0], {"offcanvas": offcanvasID}).init(); 744 | } 745 | }; 746 | 747 | Offcanvas.prototype.setButton = function(trigger){ 748 | this.$element.data( componentName + "-trigger", trigger ); 749 | }; 750 | 751 | Offcanvas.prototype.destroy = function(){ 752 | 753 | this.$element.trigger( "destroy." + name ); 754 | 755 | if( this.isOpen ){ 756 | this.close(); 757 | } 758 | 759 | this.$element 760 | .removeData() 761 | .removeClass( this._panelClasses.join( " " ) ) 762 | .removeAttr('tabindex') 763 | .removeAttr('aria-hidden'); 764 | 765 | if( this.$triggerBtn ){ 766 | this.$triggerBtn 767 | .removeData('offcanvas-trigger-component') 768 | .off(".offcanvas") 769 | .off(".offcanvas-trigger") 770 | .data('button-component').destroy(); 771 | } 772 | 773 | this.$element.off( "." + name ); 774 | $( doc ).off( "." + name); 775 | $(window).off('.'+name); 776 | 777 | }; 778 | 779 | Offcanvas.prototype.defaults = { 780 | role: "dialog", 781 | modifiers: "left,overlay", 782 | baseClass: "c-offcanvas", 783 | modalClass: "c-offcanvas-bg", 784 | contentClass: "c-offcanvas-content-wrap", 785 | closeButtonClass: "js-offcanvas-close", 786 | bodyModifierClass: "has-offcanvas", 787 | supportNoTransitionsClass: "support-no-transitions", 788 | resize: false, 789 | triggerButton: null, 790 | modal: true, 791 | onOpen: null, 792 | onClose: null, 793 | onInit: null 794 | }; 795 | 796 | Offcanvas.defaults = Offcanvas.prototype.defaults; 797 | 798 | })(this, jQuery); 799 | 800 | (function( w, $ ){ 801 | "use strict"; 802 | 803 | var pluginName = "offcanvas", 804 | initSelector = ".js-" + pluginName; 805 | 806 | $.fn[ pluginName ] = function(options){ 807 | return this.each( function(){ 808 | new w.componentNamespace.Offcanvas( this, options ).init(); 809 | }); 810 | }; 811 | 812 | // auto-init on enhance (which is called on domready) 813 | $( w.document ).on( "enhance", function(e){ 814 | $( $( e.target ).is( initSelector ) && e.target ).add( initSelector, e.target ).filter( initSelector )[ pluginName ](); 815 | }); 816 | 817 | })(this, jQuery); 818 | 819 | (function( w, $ ){ 820 | "use strict"; 821 | 822 | var name = "offcanvas-trigger", 823 | componentName = name + "-component", 824 | utils = w.utils; 825 | 826 | w.componentNamespace = w.componentNamespace || {}; 827 | 828 | var OffcanvasTrigger = w.componentNamespace.OffcanvasTrigger = function( element,options ){ 829 | if( !element ){ 830 | throw new Error( "Element required to initialize object" ); 831 | } 832 | // assign element for method events 833 | this.element = element; 834 | this.$element = $( element ); 835 | // Options 836 | this.options = options = options || {}; 837 | this.options = $.extend( {}, this.defaults, options ); 838 | }; 839 | 840 | OffcanvasTrigger.prototype.init = function(){ 841 | 842 | if ( this.$element.data( componentName ) ) { 843 | return; 844 | } 845 | this.$element.data( componentName, this ); 846 | this._create(); 847 | }; 848 | 849 | OffcanvasTrigger.prototype._create = function(){ 850 | this.options.offcanvas = this.options.offcanvas || this.$element.attr( "data-offcanvas-trigger" ); 851 | this.$offcanvas = $( "#" + this.options.offcanvas ); 852 | this.offcanvas = this.$offcanvas.data( "offcanvas-component" ); 853 | if (!this.offcanvas) { 854 | throw new Error( "Offcanvas Element not found" ); 855 | } 856 | this.button = new w.componentNamespace.Button(this.element); 857 | this.button.init(); 858 | this.button.controls(this.options.offcanvas); 859 | this.button._isExpanded(false); 860 | this._bindbehavior(); 861 | }; 862 | 863 | OffcanvasTrigger.prototype._bindbehavior = function(){ 864 | var self = this; 865 | this.offcanvas.setButton(self); 866 | function toggleOffcanvas(){ 867 | self.offcanvas.toggle(); 868 | } 869 | utils.a11yclickBind(this.$element,toggleOffcanvas,name); 870 | }; 871 | 872 | OffcanvasTrigger.prototype.defaults = { 873 | offcanvas: null 874 | }; 875 | 876 | })(this, jQuery); 877 | 878 | (function( w, $ ){ 879 | "use strict"; 880 | 881 | var pluginName = "offcanvasTrigger", 882 | initSelector = "[data-offcanvas-trigger],.js-" + pluginName; 883 | 884 | $.fn[ pluginName ] = function(options){ 885 | return this.each( function(){ 886 | new w.componentNamespace.OffcanvasTrigger( this,options ).init(); 887 | }); 888 | }; 889 | 890 | // auto-init on enhance (which is called on domready) 891 | $( w.document ).on( "enhance", function(e){ 892 | $( $( e.target ).is( initSelector ) && e.target ).add( initSelector, e.target ).filter( initSelector )[ pluginName ](); 893 | }); 894 | 895 | })(this, jQuery); 896 | -------------------------------------------------------------------------------- /dist/_js/js-offcanvas.pkgd.min.js: -------------------------------------------------------------------------------- 1 | /*! js-Offcanvas - v1.2.11 - 2019-10-16 2 | jQuery Accesible Offcanvas Panels 3 | * https://github.com/vmitsaras/js-offcanvas 4 | * Copyright (c) 2019 Vasileios Mitsaras (@vmitsaras) 5 | * MIT License */ 6 | !function(a){"use strict";var b=a.utils||{};b.classes={hiddenVisually:"u-hidden-visually",modifier:"--",isActive:"is-active",isClosed:"is-closed",isOpen:"is-open",isClicked:"is-clicked",isAnimating:"is-animating",isVisible:"is-visible",hidden:"u-hidden"},b.keyCodes={BACKSPACE:8,COMMA:188,DELETE:46,DOWN:40,END:35,ENTER:13,ESCAPE:27,HOME:36,LEFT:37,PAGE_DOWN:34,PAGE_UP:33,PERIOD:190,RIGHT:39,SPACE:32,TAB:9,UP:38},b.a11yclick=function(a){var c=a.charCode||a.keyCode,d=a.type;return"click"===d||"keydown"===d&&(c===b.keyCodes.SPACE||c===b.keyCodes.ENTER||void 0)},b.a11yclickBind=function(a,c,d){a.on("click."+d+" keydown."+d,function(e){b.a11yclick(e)&&(e.preventDefault(e),c&&"function"==typeof c&&c.call(),a.trigger("clicked."+d))})},b.supportTransition="transition"in document.documentElement.style||"WebkitTransition"in document.documentElement.style,b.whichTransitionEvent=function(){var a=document.createElement("fakeelement"),b={transition:"transitionend",WebkitTransition:"webkitTransitionEnd"};for(var c in b)if(void 0!==a.style[c])return b[c]},b.transEndEventName=b.whichTransitionEvent(),b.onEndTransition=function(a,c){var d=function(a){if(b.supportTransition){if(a.target!=this)return;this.removeEventListener(b.transEndEventName,d)}c&&"function"==typeof c&&c.call()};b.supportTransition?a.addEventListener(b.transEndEventName,d):d()},b.createModifierClass=function(a,c){return a+b.classes.modifier+c},b.cssModifiers=function(a,c,d){for(var e=a.split(","),f=0,g=e.length;f").html(a.label).appendTo(this.$element.empty())),a.icon&&(this.$buttonIcon=b("").prependTo(this.$element),this._buttonClasses.push(e.createModifierClass(a.baseClass,f.withIcon)),a.iconActive&&(a.toggle=!0,this.$buttonIconActive=b("").insertAfter(this.$buttonIcon),this._buttonClasses.push(e.createModifierClass(a.baseClass,f.toggleState))),a.hideText&&(d.push(e.classes.hiddenVisually),this._buttonClasses.push(e.createModifierClass(a.baseClass,f.iconOnly)))),a.modifiers&&e.cssModifiers(a.modifiers,this._buttonClasses,a.baseClass),a.wrapText&&this.$buttonText.addClass(d.join(" ")),a.textActive&&a.wrapText&&(a.toggle=!0,d.push(e.createModifierClass(a.baseClass+"__text",f.showHide)),this._buttonClasses.push(e.createModifierClass(a.baseClass,f.toggleState)),this.$buttonTextActive=b("").addClass(d.join(" ")).html(a.textActive).insertAfter(this.$buttonText),this.$element.attr("aria-live","polite")),this.$element.addClass(this._buttonClasses.join(" ")),a.role&&this.$element.attr("role",a.role),a.controls&&this.controls(a.controls),a.pressed&&this._isPressed(a.pressed),a.expanded&&(this.isPressed=!0,this._isExpanded(a.expanded)),this.hasTitle||!a.hideText||a.hideTitle||this.$element.attr("title",this.$element.text()),this.$element.trigger("create."+c)},g.prototype._isPressed=function(a){this.isPressed=a,this.$element.attr("aria-pressed",a)[a?"addClass":"removeClass"](e.classes.isActive)},g.prototype._isExpanded=function(a){this.isExpanded=a,this.$element.attr("aria-expanded",a)[a?"addClass":"removeClass"](e.classes.isActive)},g.prototype.controls=function(a){this.$element.attr("aria-controls",a)},g.prototype.destroy=function(){var a=this.options;if(this.$element.removeData(d).removeAttr("role").removeAttr("aria-pressed").removeAttr("aria-expanded").removeAttr("aria-controls").removeClass(this._buttonClasses.join(" ")).removeClass(e.classes.isActive).off("."+c),this.options.icon&&this.$element.find('[class^="'+this.options.iconFamily+'"]').remove(),a.wrapText){var b=this.$buttonText.html();this.$element.empty().html(b)}this.element=null,this.$element=null},g.prototype.defaults={baseClass:"c-button",role:"button",label:null,modifiers:null,controls:null,textActive:null,wrapText:!0,hideText:!1,hideTitle:!1,icon:null,iconActive:null,iconFamily:"o-icon",iconPosition:null,pressed:!1,expanded:!1},g.defaults=g.prototype.defaults}(this,jQuery),function(a,b){"use strict";var c="jsButton",d=".js-button";b.fn[c]=function(){return this.each(function(){new window.componentNamespace.Button(this).init()})},b(document).bind("enhance",function(a){b(b(a.target).is(d)&&a.target).add(d,a.target).filter(d)[c]()})}(this,jQuery),function(a,b){"use strict";var c="offcanvas",d=c+"-component",e=a.utils,f=document;a.componentNamespace=a.componentNamespace||{};var g=a.componentNamespace.Offcanvas=function(a,d){if(!a)throw new Error("Element required to initialize object");this.element=a,this.$element=b(a),this.options=d=d||{},this.metadata=e.getMetaOptions(this.element,c),this.options=b.extend({},this.defaults,this.metadata,d),this.isOpen=!1,this.onOpen=this.options.onOpen,this.onClose=this.options.onClose,this.onInit=this.options.onInit};g.prototype.init=function(){this.$element.data(d)||(this.$element.data(d,this),this.$element.trigger("beforecreate."+c),this._addAttributes(),this._initTrigger(),this._createModal(),this._trapTabKey(),this._closeButton(),this.onInit&&"function"==typeof this.onInit&&this.onInit.call(this.element),this.$element.trigger("create."+c))},g.prototype._addAttributes=function(){var c=this.options,d={tabindex:"-1","aria-hidden":!this.isOpen};c.role&&(d.role=c.role),this._panelClasses=[c.baseClass,e.classes.isClosed],a.utils.supportTransition||this._panelClasses.push(e.createModifierClass(c.baseClass,c.supportNoTransitionsClass)),e.cssModifiers(c.modifiers,this._panelClasses,c.baseClass),this.$element.attr(d).addClass(this._panelClasses.join(" ")),this.$content=b("."+c.contentClass),this._contentOpenClasses=[],e.cssModifiers(c.modifiers,this._contentOpenClasses,c.contentClass),this._modalOpenClasses=[c.modalClass,e.classes.isClosed],e.cssModifiers(c.modifiers,this._modalOpenClasses,c.modalClass),this._bodyOpenClasses=[c.bodyModifierClass+"--visible"],e.cssModifiers(c.modifiers,this._bodyOpenClasses,c.bodyModifierClass),c.modifiers.toLowerCase().indexOf("reveal")>=0?this.transitionElement=this.$content[0]:this.transitionElement=this.element},g.prototype._createModal=function(){var a=this,d=a.$element.parent();this.options.modal&&(this.$modal=b("
").on("mousedown."+c,function(){a.close()}).appendTo(d),this.$modal.addClass(this._modalOpenClasses.join(" ")))},g.prototype._trapTabKey=function(){this.trapTabKey=new a.componentNamespace.TrapTabKey(this.element),this.trapTabKey.init()},g.prototype._trapTabEscKey=function(){var a=this;b(f).on("keyup."+c,function(c){var d=c.keyCode||c.which;if(d===e.keyCodes.ESCAPE&&a.isOpen){if(b("input").is(":focus"))return;a.close()}})},g.prototype._closeButton=function(){function b(){d.close()}var d=this,f=d.options;this.$closeBtn=this.$element.find("."+f.closeButtonClass),this.$closeBtn.length&&(this.closeBtn=new a.componentNamespace.Button(this.$closeBtn[0]),this.closeBtn.init(),this.closeBtn.controls(this.$element.attr("id")),e.a11yclickBind(this.$closeBtn,b,c))},g.prototype.open=function(){var a=this,d=a.options;this.isOpen||(d.resize&&this.resize(),f.activeElement&&(this.lastFocus=f.activeElement,this.lastFocusTrigger=b(this.lastFocus).data("button-component")),this.isOpen=!0,b("html, body").addClass(this._bodyOpenClasses.join(" ")),this._addClasses(this.$element,this.isOpen,!0),this._addClasses(this.$content,this.isOpen,!0),d.modal&&(this._addClasses(this.$modal,this.isOpen,!0),this.$modal.addClass(e.createModifierClass(d.modalClass,"opening"))),this.$element.attr("aria-hidden","false").addClass(e.createModifierClass(d.baseClass,"opening")).trigger("opening."+c),this.$content.addClass(this._contentOpenClasses.join(" ")),e.onEndTransition(this.transitionElement,function(){a.trapTabKey.giveFocus(),a.trapTabKey.bindTrap(),a._addClasses(a.$element,a.isOpen,!1),a._addClasses(a.$content,a.isOpen,!1),d.modal&&(a._addClasses(a.$modal,a.isOpen,!1),a.$modal.removeClass(e.createModifierClass(d.modalClass,"opening"))),a.$element.removeClass(e.createModifierClass(d.baseClass,"opening"))}),this.$trigger&&this.$trigger.button._isExpanded(!0),this.lastFocusTrigger&&this.lastFocusTrigger._isExpanded(!0),this.onOpen&&"function"==typeof this.onOpen&&this.onOpen.call(this.$element),this._trapTabEscKey(),this.$element.trigger("open."+c))},g.prototype.close=function(){var d=this,g=d.options;this.isOpen&&(this.isOpen=!1,this._addClasses(this.$element,this.isOpen,!0),this._addClasses(this.$content,this.isOpen,!0),this.options.modal&&(this._addClasses(this.$modal,this.isOpen,!0),this.$modal.addClass(e.createModifierClass(g.modalClass,"closing"))),this.$element.attr("aria-hidden","true").addClass(e.createModifierClass(g.baseClass,"closing")).trigger("closing."+c),this.trapTabKey.unbindTrap(),d.$trigger&&d.$trigger.button._isExpanded(!1),this.lastFocusTrigger&&(this.lastFocusTrigger._isExpanded(!1),this.lastFocusTrigger=null),e.onEndTransition(this.transitionElement,function(){d._addClasses(d.$element,d.isOpen,!1),d._addClasses(d.$content,d.isOpen,!1),d.options.modal&&(d._addClasses(d.$modal,d.isOpen,!1),d.$modal.removeClass(e.createModifierClass(g.modalClass,"closing"))),d.$content.removeClass(d._contentOpenClasses.join(" ")),d.$element.removeClass(e.createModifierClass(g.baseClass,"closing")),b("html, body").removeClass(d._bodyOpenClasses.join(" ")),d.lastFocus&&d.lastFocus.focus()}),this.onClose&&"function"==typeof this.onClose&&this.onClose.call(this.element),this.$element.trigger("close."+c),b(f).off("keyup."+c),b(a).off("."+c))},g.prototype._addClasses=function(a,b,c){b?c?a.removeClass(e.classes.isClosed).addClass(e.classes.isAnimating).addClass(e.classes.isOpen):a.removeClass(e.classes.isAnimating):c?a.removeClass(e.classes.isOpen).addClass(e.classes.isAnimating):a.addClass(e.classes.isClosed).removeClass(e.classes.isAnimating)},g.prototype.toggle=function(){this[this.isOpen?"close":"open"]()},g.prototype.resize=function(){function d(){g=!1}function e(){g||i(d),g=!0}function f(){e(),h.$element.trigger("resizing."+c),h.options.resize&&h.close()}var g,h=this,i=function(){return a.requestAnimationFrame||a.webkitRequestAnimationFrame||a.mozRequestAnimationFrame||function(b){a.setTimeout(b,1e3/60)}}();b(a).on("resize."+c+" orientationchange."+c,f)},g.prototype._initTrigger=function(){var c=this,d=c.options,e=this.$element.attr("id");d.triggerButton&&(this.$triggerBtn=b(d.triggerButton),new a.componentNamespace.OffcanvasTrigger(this.$triggerBtn[0],{offcanvas:e}).init())},g.prototype.setButton=function(a){this.$element.data(d+"-trigger",a)},g.prototype.destroy=function(){this.$element.trigger("destroy."+c),this.isOpen&&this.close(),this.$element.removeData().removeClass(this._panelClasses.join(" ")).removeAttr("tabindex").removeAttr("aria-hidden"),this.$triggerBtn&&this.$triggerBtn.removeData("offcanvas-trigger-component").off(".offcanvas").off(".offcanvas-trigger").data("button-component").destroy(),this.$element.off("."+c),b(f).off("."+c),b(a).off("."+c)},g.prototype.defaults={role:"dialog",modifiers:"left,overlay",baseClass:"c-offcanvas",modalClass:"c-offcanvas-bg",contentClass:"c-offcanvas-content-wrap",closeButtonClass:"js-offcanvas-close",bodyModifierClass:"has-offcanvas",supportNoTransitionsClass:"support-no-transitions",resize:!1,triggerButton:null,modal:!0,onOpen:null,onClose:null,onInit:null},g.defaults=g.prototype.defaults}(this,jQuery),function(a,b){"use strict";var c="offcanvas",d=".js-"+c;b.fn[c]=function(b){return this.each(function(){new a.componentNamespace.Offcanvas(this,b).init()})},b(a.document).on("enhance",function(a){b(b(a.target).is(d)&&a.target).add(d,a.target).filter(d)[c]()})}(this,jQuery),function(a,b){"use strict";var c="offcanvas-trigger",d=c+"-component",e=a.utils;a.componentNamespace=a.componentNamespace||{};var f=a.componentNamespace.OffcanvasTrigger=function(a,c){if(!a)throw new Error("Element required to initialize object");this.element=a,this.$element=b(a),this.options=c=c||{},this.options=b.extend({},this.defaults,c)};f.prototype.init=function(){this.$element.data(d)||(this.$element.data(d,this),this._create())},f.prototype._create=function(){if(this.options.offcanvas=this.options.offcanvas||this.$element.attr("data-offcanvas-trigger"),this.$offcanvas=b("#"+this.options.offcanvas),this.offcanvas=this.$offcanvas.data("offcanvas-component"),!this.offcanvas)throw new Error("Offcanvas Element not found");this.button=new a.componentNamespace.Button(this.element),this.button.init(),this.button.controls(this.options.offcanvas),this.button._isExpanded(!1),this._bindbehavior()},f.prototype._bindbehavior=function(){function a(){b.offcanvas.toggle()}var b=this;this.offcanvas.setButton(b),e.a11yclickBind(this.$element,a,c)},f.prototype.defaults={offcanvas:null}}(this,jQuery),function(a,b){"use strict";var c="offcanvasTrigger",d="[data-offcanvas-trigger],.js-"+c;b.fn[c]=function(b){return this.each(function(){new a.componentNamespace.OffcanvasTrigger(this,b).init()})},b(a.document).on("enhance",function(a){b(b(a.target).is(d)&&a.target).add(d,a.target).filter(d)[c]()})}(this,jQuery); -------------------------------------------------------------------------------- /grunt/banner.txt: -------------------------------------------------------------------------------- 1 | /*! <%= pkg.title || pkg.name %> - v<%= pkg.version %> - <%= grunt.template.today("yyyy-mm-dd") %> 2 | <%= pkg.description %> 3 | <%= pkg.homepage ? " * " + pkg.homepage : "" %> 4 | * Copyright (c) <%= grunt.template.today("yyyy") %> <%= pkg.author.name %> (@<%= pkg.author.twitter %>) 5 | * <%= pkg.license %> License */ 6 | -------------------------------------------------------------------------------- /grunt/config-lib/bytesize.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | dist: { 3 | src: [ 4 | "<%= pkg.config.dist %>/*.js", 5 | "<%= pkg.config.dist %>/*.css" 6 | ] 7 | } 8 | }; 9 | -------------------------------------------------------------------------------- /grunt/config-lib/clean.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | files: ["<%= pkg.config.dist %>/"] 3 | }; 4 | -------------------------------------------------------------------------------- /grunt/config-lib/concat.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | options: { 3 | stripBanners: true 4 | }, 5 | js: { 6 | src: [], 7 | dest: "<%= pkg.config.dist %>/_js/<%= pkg.name %>.js" 8 | }, 9 | pkgd: { 10 | src: [], 11 | dest: "<%= pkg.config.dist %>/_js/<%= pkg.name %>.pkgd.js" 12 | } 13 | }; 14 | -------------------------------------------------------------------------------- /grunt/config-lib/cssmin.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | combine: { 3 | files: { 4 | 'dist/_css/minified/<%= pkg.name %>.css': ['dist/_css/prefixed/<%= pkg.name %>.css'] 5 | } 6 | } 7 | } -------------------------------------------------------------------------------- /grunt/config-lib/gh-pages.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | options: { 3 | branch: "gh-pages", 4 | tag: "v<%= pkg.version %>", 5 | message: "<%= pkg.version %> [ci skip]" 6 | }, 7 | src: [ 8 | "<%= pkg.config.dist %>/**/*", 9 | "<%= pkg.config.demo %>/**/*" 10 | ] 11 | }; 12 | -------------------------------------------------------------------------------- /grunt/config-lib/jshint.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | options: { 3 | // Use a .jshintrc file so the same options are reused in our editor. 4 | jshintrc: ".jshintrc" 5 | }, 6 | src: [ "Gruntfile.js", "<%= pkg.config.src %>/**/*.js" ] 7 | }; 8 | -------------------------------------------------------------------------------- /grunt/config-lib/lintspaces.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | all: { 3 | src: [ 4 | '<%= pkg.config.src %>/**/*.js' 5 | ], 6 | options: { 7 | editorconfig: '.editorconfig' 8 | } 9 | } 10 | }; 11 | -------------------------------------------------------------------------------- /grunt/config-lib/mkdir.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | init: { 3 | options: { 4 | create: [ "<%= pkg.config.src %>", "<%= pkg.config.demo %>" ] 5 | } 6 | } 7 | }; 8 | -------------------------------------------------------------------------------- /grunt/config-lib/modernizr.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | dist: { 3 | "crawl": false, 4 | "cache" : false, 5 | "dest": "vendor/modernizr.js", 6 | "classPrefix": "support-", 7 | "tests": [ 8 | "touchevents", 9 | "cssanimations", 10 | "flexbox", 11 | "csstransforms", 12 | "csstransforms3d", 13 | "csstransitions" 14 | ], 15 | "options": [ 16 | "domPrefixes", 17 | "prefixes", 18 | "addTest", 19 | "hasEvent", 20 | "mq", 21 | "prefixed", 22 | "testAllProps", 23 | "testProp", 24 | "testStyles", 25 | "html5shiv", 26 | "setClasses" 27 | ], 28 | "uglify": false 29 | } 30 | }; 31 | -------------------------------------------------------------------------------- /grunt/config-lib/postcss.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 3 | options: { 4 | banner: '<%= banner %>', 5 | stripBanners: true, 6 | 7 | processors: [ 8 | 9 | require('autoprefixer')({ 10 | // browsers: ['last 2 versions'] 11 | }) 12 | 13 | ] 14 | }, 15 | dist: { 16 | src: 'dist/_css/<%= pkg.name %>.css', 17 | dest: 'dist/_css/prefixed/<%= pkg.name %>.css' 18 | } 19 | } 20 | 21 | 22 | -------------------------------------------------------------------------------- /grunt/config-lib/qunit.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | all: ["<%= pkg.config.test %>/**/*.html"] 3 | }; 4 | -------------------------------------------------------------------------------- /grunt/config-lib/sass.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | dist: { 3 | options: { 4 | // cssmin will minify later 5 | style: 'expanded' 6 | //lineNumbers:true 7 | }, 8 | files: { 9 | 'dist/_css/<%= pkg.name %>.css': 'src/<%= pkg.name %>.scss' 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /grunt/config-lib/uglify.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 3 | options: { 4 | banner: '<%= banner %>' 5 | }, 6 | js: { 7 | src: "<%= pkg.config.dist %>/_js/<%= pkg.name %>.js", 8 | dest: "<%= pkg.config.dist %>/_js/<%= pkg.name %>.min.js" 9 | }, 10 | pkgd: { 11 | src: "<%= pkg.config.dist %>/_js/<%= pkg.name %>.pkgd.js", 12 | dest: "<%= pkg.config.dist %>/_js/<%= pkg.name %>.pkgd.min.js" 13 | } 14 | }; 15 | -------------------------------------------------------------------------------- /grunt/config-lib/usebanner.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | dist: { 3 | options: { 4 | position: "top", 5 | banner: "<%= banner %>" 6 | }, 7 | files: { 8 | src: [ "<%= pkg.config.dist %>/*.js", "<%= pkg.config.dist %>/*.css" ] 9 | } 10 | } 11 | }; 12 | -------------------------------------------------------------------------------- /grunt/config-lib/watch.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | files: [ "<%= pkg.config.src %>/**/*" ], 3 | //tasks: [ "src", "test" ] 4 | tasks: [ "src","scss"] 5 | }; 6 | -------------------------------------------------------------------------------- /grunt/config/concat.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | js: { 3 | src: [ 4 | "src/<%= pkg.name %>.js", 5 | "src/<%= pkg.name %>-init.js", 6 | "src/<%= pkg.name %>-trigger.js", 7 | "src/<%= pkg.name %>-trigger-init.js" 8 | ] 9 | }, 10 | pkgd: { 11 | src: [ 12 | "node_modules/js-utilities/utils.js", 13 | "node_modules/js-trap-tab/trap-tab.js", 14 | "node_modules/js-button/dist/_js/js-button.js", 15 | "src/<%= pkg.name %>.js", 16 | "src/<%= pkg.name %>-init.js", 17 | "src/<%= pkg.name %>-trigger.js", 18 | "src/<%= pkg.name %>-trigger-init.js" 19 | ] 20 | } 21 | }; 22 | -------------------------------------------------------------------------------- /grunt/tasks.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt) { 2 | "use strict"; 3 | 4 | grunt.registerTask( "init", [ "mkdir" ] ); 5 | 6 | grunt.registerTask( "default", [ "clean", "src", "uglify", "scss", "bytesize" ] ); 7 | 8 | grunt.registerTask( "mdzr", [ "modernizr:dist" ] ); 9 | grunt.registerTask( "lint", [ "jshint", "lintspaces" ] ); 10 | grunt.registerTask( "src", [ "lint", "concat", "usebanner" ] ); 11 | grunt.registerTask( "scss", [ "sass", "postcss", "cssmin" ] ); 12 | grunt.registerTask( "test", [ "qunit" ] ); 13 | 14 | grunt.registerTask( "deploy", [ "default", "gh-pages" ] ); 15 | 16 | }; 17 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "js-offcanvas", 3 | "title": "js-Offcanvas", 4 | "main": "src/js-offcanvas.js", 5 | "description": "jQuery Accesible Offcanvas Panels", 6 | "version": "1.2.11", 7 | "homepage": "https://github.com/vmitsaras/js-offcanvas", 8 | "license": "MIT", 9 | "keywords": [ 10 | "jquery", 11 | "jquery-plugin", 12 | "accesible", 13 | "offcanvas" 14 | ], 15 | "author": { 16 | "name": "Vasileios Mitsaras", 17 | "twitter": "vmitsaras", 18 | "github": "vmitsaras", 19 | "email": "github@vasilis.co", 20 | "url": "http://vasilis.co" 21 | }, 22 | "scripts": { 23 | "test": "grunt test", 24 | "clean": "rm -Rf node_modules/ && rm -f ./package-lock.json && npm cache clean -f", 25 | "clean_windows": "IF EXIST node_modules rd /s /q node_modules && IF EXIST package-lock.json DEL package-lock.json && npm cache clean -f", 26 | "rebuild": "npm run clean && npm i", 27 | "rebuild_windows": "npm run clean_windows && npm i" 28 | }, 29 | "devDependencies": { 30 | "autoprefixer": "^6.3.3", 31 | "bower": "^1.7.7", 32 | "cssnano": "^3.5.2", 33 | "glob": "^7.0.3", 34 | "grunt": "^0.4.5", 35 | "grunt-banner": "^0.6.0", 36 | "grunt-bytesize": "^0.1.1", 37 | "grunt-cli": "^1.2.0", 38 | "grunt-contrib-clean": "^1.0.0", 39 | "grunt-contrib-concat": "^1.0.0", 40 | "grunt-contrib-cssmin": "^1.0.0", 41 | "grunt-contrib-jshint": "^1.0.0", 42 | "grunt-contrib-qunit": "^1.0.1", 43 | "grunt-contrib-sass": "^1.0.0", 44 | "grunt-contrib-uglify": "^1.0.0", 45 | "grunt-contrib-watch": "^0.6.1", 46 | "grunt-gh-pages": "^1.0.0", 47 | "grunt-lintspaces": "^0.7.4", 48 | "grunt-mkdir": "^1.0.0", 49 | "grunt-modernizr": "^1.0.3", 50 | "grunt-postcss": "^0.8.0", 51 | "load-grunt-tasks": "^3.4.1", 52 | "matchdep": "^1.0.1", 53 | "qunitjs": "^1.22.0" 54 | }, 55 | "dependencies": { 56 | "js-utilities": "*", 57 | "js-trap-tab": "*", 58 | "js-button": "*" 59 | }, 60 | "engines": { 61 | "node": ">=0.8.0", 62 | "npm": ">=1.2.10", 63 | "yarn": ">= 1.0.0" 64 | }, 65 | "config": { 66 | "grunt": "grunt", 67 | "src": "src", 68 | "dist": "dist", 69 | "test": "test", 70 | "demo": "demo" 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/js-offcanvas-init.js: -------------------------------------------------------------------------------- 1 | (function( w, $ ){ 2 | "use strict"; 3 | 4 | var pluginName = "offcanvas", 5 | initSelector = ".js-" + pluginName; 6 | 7 | $.fn[ pluginName ] = function(options){ 8 | return this.each( function(){ 9 | new w.componentNamespace.Offcanvas( this, options ).init(); 10 | }); 11 | }; 12 | 13 | // auto-init on enhance (which is called on domready) 14 | $( w.document ).on( "enhance", function(e){ 15 | $( $( e.target ).is( initSelector ) && e.target ).add( initSelector, e.target ).filter( initSelector )[ pluginName ](); 16 | }); 17 | 18 | })(this, jQuery); 19 | -------------------------------------------------------------------------------- /src/js-offcanvas-trigger-init.js: -------------------------------------------------------------------------------- 1 | (function( w, $ ){ 2 | "use strict"; 3 | 4 | var pluginName = "offcanvasTrigger", 5 | initSelector = "[data-offcanvas-trigger],.js-" + pluginName; 6 | 7 | $.fn[ pluginName ] = function(options){ 8 | return this.each( function(){ 9 | new w.componentNamespace.OffcanvasTrigger( this,options ).init(); 10 | }); 11 | }; 12 | 13 | // auto-init on enhance (which is called on domready) 14 | $( w.document ).on( "enhance", function(e){ 15 | $( $( e.target ).is( initSelector ) && e.target ).add( initSelector, e.target ).filter( initSelector )[ pluginName ](); 16 | }); 17 | 18 | })(this, jQuery); 19 | -------------------------------------------------------------------------------- /src/js-offcanvas-trigger.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Offcanvas Trigger 3 | */ 4 | (function( w, $ ){ 5 | "use strict"; 6 | 7 | var name = "offcanvas-trigger", 8 | componentName = name + "-component", 9 | utils = w.utils; 10 | 11 | w.componentNamespace = w.componentNamespace || {}; 12 | 13 | var OffcanvasTrigger = w.componentNamespace.OffcanvasTrigger = function( element,options ){ 14 | if( !element ){ 15 | throw new Error( "Element required to initialize object" ); 16 | } 17 | // assign element for method events 18 | this.element = element; 19 | this.$element = $( element ); 20 | // Options 21 | this.options = options = options || {}; 22 | this.options = $.extend( {}, this.defaults, options ); 23 | }; 24 | 25 | OffcanvasTrigger.prototype.init = function(){ 26 | 27 | if ( this.$element.data( componentName ) ) { 28 | return; 29 | } 30 | this.$element.data( componentName, this ); 31 | this._create(); 32 | }; 33 | 34 | OffcanvasTrigger.prototype._create = function(){ 35 | this.options.offcanvas = this.options.offcanvas || this.$element.attr( "data-offcanvas-trigger" ); 36 | this.$offcanvas = $( "#" + this.options.offcanvas ); 37 | this.offcanvas = this.$offcanvas.data( "offcanvas-component" ); 38 | if (!this.offcanvas) { 39 | throw new Error( "Offcanvas Element not found" ); 40 | } 41 | this.button = new w.componentNamespace.Button(this.element); 42 | this.button.init(); 43 | this.button.controls(this.options.offcanvas); 44 | this.button._isExpanded(false); 45 | this._bindbehavior(); 46 | }; 47 | 48 | OffcanvasTrigger.prototype._bindbehavior = function(){ 49 | var self = this; 50 | this.offcanvas.setButton(self); 51 | function toggleOffcanvas(){ 52 | self.offcanvas.toggle(); 53 | } 54 | utils.a11yclickBind(this.$element,toggleOffcanvas,name); 55 | }; 56 | 57 | OffcanvasTrigger.prototype.defaults = { 58 | offcanvas: null 59 | }; 60 | 61 | })(this, jQuery); 62 | -------------------------------------------------------------------------------- /src/js-offcanvas.js: -------------------------------------------------------------------------------- 1 | /* global jQuery:true */ 2 | ;(function( window, $ ){ 3 | "use strict"; 4 | 5 | var name = "offcanvas", 6 | componentName = name + "-component", 7 | utils = window.utils, 8 | doc = document; 9 | 10 | window.componentNamespace = window.componentNamespace || {}; 11 | 12 | var Offcanvas = window.componentNamespace.Offcanvas = function( element,options ){ 13 | if( !element ){ 14 | throw new Error( "Element required to initialize object" ); 15 | } 16 | // assign element for method events 17 | this.element = element; 18 | this.$element = $( element ); 19 | // Options 20 | this.options = options = options || {}; 21 | this.metadata = utils.getMetaOptions( this.element, name ); 22 | this.options = $.extend( {}, this.defaults, this.metadata, options ); 23 | this.isOpen = false; 24 | this.onOpen = this.options.onOpen; 25 | this.onClose = this.options.onClose; 26 | this.onInit = this.options.onInit; 27 | }; 28 | 29 | Offcanvas.prototype.init = function(){ 30 | if ( this.$element.data( componentName ) ) { 31 | return; 32 | } 33 | this.$element.data( componentName, this ); 34 | this.$element.trigger( "beforecreate." + name ); 35 | this._addAttributes(); 36 | this._initTrigger(); 37 | this._createModal(); 38 | this._trapTabKey(); 39 | this._closeButton(); 40 | if( this.onInit && typeof this.onInit === 'function' ) { 41 | this.onInit.call(this.element); 42 | } 43 | this.$element.trigger( "create." + name ); 44 | }; 45 | 46 | Offcanvas.prototype._addAttributes = function(){ 47 | var options = this.options, 48 | panelAttr = { 49 | tabindex: "-1", 50 | "aria-hidden": !this.isOpen 51 | }; 52 | 53 | if ( options.role) { 54 | panelAttr.role = options.role; 55 | } 56 | this._panelClasses = [options.baseClass,utils.classes.isClosed]; 57 | 58 | if(!window.utils.supportTransition){ 59 | this._panelClasses.push( utils.createModifierClass(options.baseClass, options.supportNoTransitionsClass)); 60 | } 61 | utils.cssModifiers(options.modifiers,this._panelClasses,options.baseClass ); 62 | this.$element.attr(panelAttr).addClass( this._panelClasses.join( " " ) ); 63 | 64 | // Content-wrap 65 | this.$content = $('.' + options.contentClass); 66 | this._contentOpenClasses = []; 67 | utils.cssModifiers(options.modifiers,this._contentOpenClasses,options.contentClass ); 68 | 69 | // Modal 70 | this._modalOpenClasses = [options.modalClass,utils.classes.isClosed ]; 71 | utils.cssModifiers(options.modifiers,this._modalOpenClasses,options.modalClass ); 72 | 73 | // body 74 | this._bodyOpenClasses = [options.bodyModifierClass+"--visible"]; 75 | utils.cssModifiers(options.modifiers,this._bodyOpenClasses,options.bodyModifierClass); 76 | 77 | if (options.modifiers.toLowerCase().indexOf("reveal") >= 0) { 78 | this.transitionElement = this.$content[0]; 79 | } else { 80 | this.transitionElement = this.element ; 81 | } 82 | }; 83 | 84 | Offcanvas.prototype._createModal= function() { 85 | var self = this, 86 | target = self.$element.parent(); 87 | if (this.options.modal) { 88 | this.$modal = $( "
" ) 89 | .on( "mousedown."+name, function() { 90 | self.close(); 91 | }) 92 | .appendTo( target ); 93 | this.$modal.addClass( this._modalOpenClasses.join( " " ) ); 94 | } 95 | }; 96 | 97 | Offcanvas.prototype._trapTabKey = function() { 98 | this.trapTabKey = new window.componentNamespace.TrapTabKey(this.element); 99 | this.trapTabKey.init(); 100 | }; 101 | 102 | Offcanvas.prototype._trapTabEscKey = function() { 103 | var self = this; 104 | // close on ESC 105 | $( doc ).on( "keyup." + name, function(ev){ 106 | var keyCode = ev.keyCode || ev.which; 107 | if( keyCode === utils.keyCodes.ESCAPE && self.isOpen ) { 108 | if ($("input").is(":focus")) { 109 | return; 110 | } 111 | self.close(); 112 | } 113 | } ); 114 | }; 115 | 116 | Offcanvas.prototype._closeButton = function() { 117 | var self = this, 118 | options = self.options; 119 | function closeOffcanvas(){ 120 | self.close(); 121 | } 122 | this.$closeBtn = this.$element.find('.'+options.closeButtonClass); 123 | if( this.$closeBtn.length ){ 124 | this.closeBtn = new window.componentNamespace.Button(this.$closeBtn[0]); 125 | this.closeBtn.init(); 126 | this.closeBtn.controls(this.$element.attr('id')); 127 | utils.a11yclickBind(this.$closeBtn,closeOffcanvas,name); 128 | } 129 | }; 130 | 131 | Offcanvas.prototype.open = function(){ 132 | var self = this, 133 | options = self.options; 134 | if (!this.isOpen) { 135 | if (options.resize) { 136 | this.resize(); 137 | } 138 | if( doc.activeElement ){ 139 | this.lastFocus = doc.activeElement; 140 | this.lastFocusTrigger = $(this.lastFocus).data( "button-component"); 141 | } 142 | this.isOpen = true; 143 | $('html, body').addClass(this._bodyOpenClasses.join(" ")); 144 | 145 | this._addClasses(this.$element,this.isOpen,true); 146 | this._addClasses(this.$content,this.isOpen,true); 147 | if (options.modal) { 148 | this._addClasses(this.$modal,this.isOpen,true); 149 | this.$modal.addClass(utils.createModifierClass(options.modalClass,'opening')); 150 | } 151 | 152 | this.$element.attr( "aria-hidden", "false" ) 153 | .addClass(utils.createModifierClass(options.baseClass,'opening')) 154 | .trigger( "opening." + name ); 155 | this.$content.addClass( this._contentOpenClasses.join( " " )); 156 | 157 | // Transition End Callback 158 | utils.onEndTransition ( this.transitionElement, function() { 159 | self.trapTabKey.giveFocus(); 160 | self.trapTabKey.bindTrap(); 161 | self._addClasses(self.$element,self.isOpen,false); 162 | self._addClasses(self.$content,self.isOpen,false); 163 | if (options.modal) { 164 | self._addClasses(self.$modal,self.isOpen,false); 165 | self.$modal.removeClass(utils.createModifierClass(options.modalClass,'opening')); 166 | } 167 | self.$element.removeClass(utils.createModifierClass(options.baseClass,'opening')); 168 | } ); 169 | if( this.$trigger ){ 170 | this.$trigger.button._isExpanded(true); 171 | } 172 | if(this.lastFocusTrigger) { 173 | this.lastFocusTrigger._isExpanded(true); 174 | } 175 | // callback on open 176 | if( this.onOpen && typeof this.onOpen === 'function' ) { 177 | this.onOpen.call(this.$element); 178 | } 179 | // close on ESC 180 | this._trapTabEscKey(); 181 | this.$element.trigger( "open." + name ); 182 | } 183 | }; 184 | 185 | Offcanvas.prototype.close = function(){ 186 | var self = this, 187 | options = self.options; 188 | if( !this.isOpen ){ 189 | return; 190 | } 191 | this.isOpen = false; 192 | 193 | this._addClasses(this.$element,this.isOpen,true); 194 | this._addClasses(this.$content,this.isOpen,true); 195 | 196 | if (this.options.modal) { 197 | this._addClasses(this.$modal,this.isOpen,true); 198 | this.$modal.addClass(utils.createModifierClass(options.modalClass,'closing')); 199 | } 200 | 201 | this.$element.attr( "aria-hidden", "true" ) 202 | .addClass(utils.createModifierClass(options.baseClass,'closing')) 203 | .trigger( "closing." + name ); 204 | 205 | this.trapTabKey.unbindTrap(); 206 | 207 | if( self.$trigger ){ 208 | self.$trigger.button._isExpanded(false); 209 | } 210 | 211 | if(this.lastFocusTrigger) { 212 | this.lastFocusTrigger._isExpanded(false); 213 | this.lastFocusTrigger = null; 214 | } 215 | 216 | utils.onEndTransition ( this.transitionElement, function() { 217 | 218 | self._addClasses(self.$element,self.isOpen,false); 219 | self._addClasses(self.$content,self.isOpen,false); 220 | 221 | if (self.options.modal) { 222 | self._addClasses(self.$modal,self.isOpen,false); 223 | self.$modal.removeClass(utils.createModifierClass(options.modalClass,'closing')); 224 | } 225 | 226 | self.$content.removeClass( self._contentOpenClasses.join( " " ) ); 227 | self.$element.removeClass(utils.createModifierClass(options.baseClass,'closing')); 228 | 229 | $('html, body').removeClass(self._bodyOpenClasses.join(" ")); 230 | 231 | if( self.lastFocus ){ 232 | self.lastFocus.focus(); 233 | } 234 | } ); 235 | // callback onClose 236 | if( this.onClose && typeof this.onClose === 'function' ) { 237 | this.onClose.call(this.element); 238 | } 239 | this.$element.trigger( "close." + name ); 240 | $( doc ).off( "keyup." + name); 241 | $(window).off('.'+name); 242 | }; 243 | 244 | Offcanvas.prototype._addClasses = function(el,isOpen,beforeTransition){ 245 | if (isOpen) { 246 | if (beforeTransition) { 247 | el 248 | .removeClass(utils.classes.isClosed) 249 | .addClass(utils.classes.isAnimating) 250 | .addClass(utils.classes.isOpen); 251 | } else { 252 | el.removeClass(utils.classes.isAnimating); 253 | } 254 | } else { 255 | if (beforeTransition) { 256 | el 257 | .removeClass( utils.classes.isOpen ) 258 | .addClass( utils.classes.isAnimating ); 259 | } else { 260 | el 261 | .addClass( utils.classes.isClosed ) 262 | .removeClass( utils.classes.isAnimating ); 263 | } 264 | } 265 | }; 266 | 267 | Offcanvas.prototype.toggle = function(){ 268 | this[ this.isOpen ? "close" : "open" ](); 269 | }; 270 | 271 | Offcanvas.prototype.resize = function(){ 272 | var self = this,ticking; 273 | 274 | var raf = (function(){ 275 | return window.requestAnimationFrame || 276 | window.webkitRequestAnimationFrame || 277 | window.mozRequestAnimationFrame || 278 | function( callback ){ 279 | window.setTimeout(callback, 1000 / 60); 280 | }; 281 | })(); 282 | 283 | function update() { 284 | ticking = false; 285 | } 286 | function requestTick() { 287 | if(!ticking) { 288 | raf(update); 289 | } 290 | ticking = true; 291 | } 292 | function onResize() { 293 | requestTick(); 294 | self.$element.trigger( "resizing." + name ); 295 | if (self.options.resize) { 296 | self.close(); 297 | } 298 | } 299 | $(window).on('resize.' + name + ' orientationchange.' + name, onResize); 300 | }; 301 | 302 | Offcanvas.prototype._initTrigger = function() { 303 | var self = this, 304 | options = self.options, 305 | offcanvasID = this.$element.attr('id'); 306 | 307 | if (options.triggerButton ) { 308 | this.$triggerBtn = $(options.triggerButton); 309 | new window.componentNamespace.OffcanvasTrigger(this.$triggerBtn[0], {"offcanvas": offcanvasID}).init(); 310 | } 311 | }; 312 | 313 | Offcanvas.prototype.setButton = function(trigger){ 314 | this.$element.data( componentName + "-trigger", trigger ); 315 | }; 316 | 317 | Offcanvas.prototype.destroy = function(){ 318 | 319 | this.$element.trigger( "destroy." + name ); 320 | 321 | if( this.isOpen ){ 322 | this.close(); 323 | } 324 | 325 | this.$element 326 | .removeData() 327 | .removeClass( this._panelClasses.join( " " ) ) 328 | .removeAttr('tabindex') 329 | .removeAttr('aria-hidden'); 330 | 331 | if( this.$triggerBtn ){ 332 | this.$triggerBtn 333 | .removeData('offcanvas-trigger-component') 334 | .off(".offcanvas") 335 | .off(".offcanvas-trigger") 336 | .data('button-component').destroy(); 337 | } 338 | 339 | this.$element.off( "." + name ); 340 | $( doc ).off( "." + name); 341 | $(window).off('.'+name); 342 | 343 | }; 344 | 345 | Offcanvas.prototype.defaults = { 346 | role: "dialog", 347 | modifiers: "left,overlay", 348 | baseClass: "c-offcanvas", 349 | modalClass: "c-offcanvas-bg", 350 | contentClass: "c-offcanvas-content-wrap", 351 | closeButtonClass: "js-offcanvas-close", 352 | bodyModifierClass: "has-offcanvas", 353 | supportNoTransitionsClass: "support-no-transitions", 354 | resize: false, 355 | triggerButton: null, 356 | modal: true, 357 | onOpen: null, 358 | onClose: null, 359 | onInit: null 360 | }; 361 | 362 | Offcanvas.defaults = Offcanvas.prototype.defaults; 363 | 364 | })(this, jQuery); 365 | -------------------------------------------------------------------------------- /src/js-offcanvas.mixins.scss: -------------------------------------------------------------------------------- 1 | // ========================================================================== 2 | // Offcanvas Mixins 3 | // ========================================================================== 4 | 5 | //GPU acceleration 6 | %u-ha { 7 | transform: translate3d(0,0,0); 8 | -webkit-backface-visibility: hidden; 9 | backface-visibility: hidden; 10 | } 11 | %u-offcanvas-animate { 12 | transition: transform 300ms $sharp-curve; 13 | } 14 | 15 | %is-open { 16 | transform: translate3d(0,0,0); 17 | visibility: visible; 18 | } 19 | 20 | @mixin offcanvas($position:left, $offset:$offcanvas-width, $overlay:false, $reveal:false) { 21 | @if $position == 'left' { 22 | width: $offcanvas-left-width; 23 | transform: translate3d(-$offset,0,0); 24 | 25 | @if ($supportCSSTransforms == false) { 26 | 27 | &.c-offcanvas--support-no-transitions { 28 | left: -$offset--left; 29 | &.is-open { 30 | left: 0; 31 | 32 | } 33 | 34 | } 35 | 36 | } 37 | } 38 | @if ($position == 'right') { 39 | width: $offcanvas-right-width; 40 | right: 0; 41 | transform: translate3d($offset,0,0); 42 | } @else if ($position == 'top') { 43 | left: 0; 44 | right: 0; 45 | top: 0; 46 | height:$offset; 47 | min-height: auto; 48 | width:100%; 49 | transform: translate3d(0,-$offset,0); 50 | } @else if ( $position == 'bottom' ) { 51 | top: auto; 52 | left: 0; 53 | right: 0; 54 | bottom: 0; 55 | height:$offset; 56 | min-height: auto; 57 | width:100%; 58 | transform: translate3d(0,$offset,0); 59 | } 60 | } 61 | 62 | 63 | @mixin offcanvas-content($position:left,$offset:$offcanvas-width, $reveal:true) { 64 | @if ($reveal == true) { 65 | @if ($position == 'right') { 66 | &.is-open { 67 | transform: translate3d(-$offset,0,0); 68 | } 69 | } 70 | @if ($position == 'left') { 71 | &.is-open { 72 | transform: translate3d($offset,0,0); 73 | } 74 | } 75 | 76 | } 77 | } 78 | 79 | @mixin offcanvas-bg($position:left, $offset:$offcanvas-width) { 80 | 81 | @if ($position == 'right') { 82 | &.is-open { 83 | transform: translate3d(-$offset,0,0); 84 | } 85 | } 86 | @if ($position == 'left') { 87 | &.is-open { 88 | transform: translate3d($offset,0,0); 89 | } 90 | } 91 | } -------------------------------------------------------------------------------- /src/js-offcanvas.scss: -------------------------------------------------------------------------------- 1 | // ========================================================================== 2 | // Offcanvas Variables 3 | // ========================================================================== 4 | 5 | @import "js-offcanvas.settings"; 6 | @import "js-offcanvas.mixins"; 7 | 8 | /** 9 | * Offcanvas-content-wrap 10 | */ 11 | .c-offcanvas-content-wrap { 12 | //position: relative; 13 | //overflow: hidden; 14 | z-index: index($elements, offcanvas-content-wrap); 15 | } 16 | 17 | /** 18 | * Offcanvas Panel 19 | */ 20 | .c-offcanvas { 21 | @extend %u-ha; 22 | @extend %u-offcanvas-animate; 23 | position: fixed; 24 | min-height: 100%; 25 | max-height: none; 26 | top: 0; 27 | display: block; 28 | background: #fff; 29 | overflow-x: hidden; 30 | overflow-y: auto; 31 | 32 | &.is-open { 33 | @extend %is-open; 34 | } 35 | &--opening { 36 | transition-timing-function: $sharp-curve; 37 | } 38 | &.is-closed { 39 | //width: 0; IE10 BUG 40 | max-height: 100%; 41 | overflow: hidden; 42 | visibility: hidden; 43 | box-shadow: none; 44 | } 45 | 46 | } 47 | 48 | .c-offcanvas--overlay { 49 | z-index: 1080; 50 | } 51 | 52 | .c-offcanvas--reveal { 53 | z-index: index($elements, offcanvas-reveal); 54 | } 55 | 56 | /** 57 | * Offcanvas BG-Overlay 58 | */ 59 | .c-offcanvas-bg { 60 | position: fixed; 61 | top: 0; 62 | height: 100%; 63 | width: 100%; 64 | z-index: 1079; 65 | left: -100%; 66 | background-color: transparent; 67 | transition: background-color 400ms cubic-bezier(0.23, 1, 0.32, 1) 0ms; 68 | 69 | &.is-animating, 70 | &.is-open { 71 | left: 0; 72 | background-color: hsla(0, 0%, 0%, 0.68); 73 | visibility: visible; 74 | } 75 | 76 | &.is-closed {visibility: hidden} 77 | &--closing { 78 | &.is-animating{background: transparent;} 79 | } 80 | } 81 | 82 | /** 83 | * Position Left 84 | * 85 | */ 86 | 87 | @if ($offcanvas-enable-left == true) { 88 | .c-offcanvas--left { 89 | height: 100%; 90 | @include offcanvas(left,$offset--left,$offcanvas-enable-overlay,$offcanvas-enable-push); 91 | } 92 | 93 | @if ($supportCSSTransforms == false) { 94 | 95 | .c-offcanvas.c-offcanvas--support-no-transitions { 96 | left: -$offset--left; 97 | 98 | .c-offcanvas--left{ 99 | &.c-offcanvas--overlay, 100 | &.c-offcanvas--push, 101 | &.is-open { 102 | left: 0; 103 | } 104 | } 105 | 106 | } 107 | 108 | } 109 | } 110 | 111 | /** 112 | * Position Right 113 | * 114 | */ 115 | @if ($offcanvas-enable-right == true) { 116 | 117 | .c-offcanvas--right { 118 | height: 100%; 119 | @include offcanvas(right,$offset--right,$offcanvas-enable-overlay,$offcanvas-enable-push); 120 | } 121 | } 122 | 123 | /** 124 | * Position Top 125 | * 126 | */ 127 | @if ($offcanvas-enable-top == true) { 128 | 129 | .c-offcanvas--top { 130 | @include offcanvas(top,$offset--top,$offcanvas-enable-overlay,$offcanvas-enable-push); 131 | } 132 | } 133 | /** 134 | * Position Bottom 135 | * 136 | */ 137 | @if ($offcanvas-enable-bottom == true) { 138 | 139 | .c-offcanvas--bottom { 140 | @include offcanvas(bottom,$offset--bottom,$offcanvas-enable-overlay,$offcanvas-enable-push); 141 | } 142 | } 143 | /** 144 | * Reveal 145 | * 146 | */ 147 | @if ($offcanvas-enable-reveal== true) { 148 | .c-offcanvas-content-wrap{ 149 | @extend %u-offcanvas-animate; 150 | z-index: index($elements, offcanvas-content-wrap); 151 | } 152 | .c-offcanvas-content-wrap--reveal { 153 | //Left 154 | @if ($offcanvas-enable-left == true) { 155 | &.c-offcanvas-content-wrap--left { 156 | @include offcanvas-content(left,$offcanvas-content-reveal-left-offset); 157 | } 158 | } 159 | // Right 160 | @if ($offcanvas-enable-right == true) { 161 | &.c-offcanvas-content-wrap--right { 162 | @include offcanvas-content(right,$offcanvas-content-reveal-right-offset); 163 | } 164 | } 165 | } 166 | .c-offcanvas--reveal{ 167 | z-index: 0; 168 | transform: translate3d(0,0,0); 169 | } 170 | 171 | .c-offcanvas-bg.c-offcanvas-bg--reveal{ 172 | @extend %u-offcanvas-animate; 173 | @if ($offcanvas-enable-left == true) { 174 | &.c-offcanvas-bg--left { 175 | @include offcanvas-bg(left,$offset--left); 176 | } 177 | } 178 | @if ($offcanvas-enable-right == true) { 179 | &.c-offcanvas-bg--right { 180 | @include offcanvas-bg(right,$offset--right); 181 | } 182 | } 183 | } 184 | } 185 | 186 | /** 187 | * Push 188 | * 189 | */ 190 | @if ($offcanvas-enable-push== true) { 191 | 192 | .c-offcanvas--push { 193 | z-index: index($elements, offcanvas); 194 | &--opening { 195 | transition-timing-function: $deceleration-curve; 196 | } 197 | } 198 | .c-offcanvas-content-wrap{ 199 | @extend %u-offcanvas-animate; 200 | z-index: index($elements, offcanvas-content-wrap); 201 | } 202 | .c-offcanvas-content-wrap--push { 203 | //Left 204 | @if ($offcanvas-enable-left == true) { 205 | &.c-offcanvas-content-wrap--left { 206 | @include offcanvas-content(left,$offcanvas-content-reveal-left-offset); 207 | } 208 | } 209 | // Right 210 | @if ($offcanvas-enable-right == true) { 211 | &.c-offcanvas-content-wrap--right { 212 | @include offcanvas-content(right,$offcanvas-content-reveal-right-offset); 213 | } 214 | } 215 | } 216 | 217 | .c-offcanvas-bg.c-offcanvas-bg--push{ 218 | @extend %u-offcanvas-animate; 219 | @if ($offcanvas-enable-left == true) { 220 | &.c-offcanvas-bg--left { 221 | @include offcanvas-bg(left,$offset--left); 222 | } 223 | } 224 | @if ($offcanvas-enable-right == true) { 225 | &.c-offcanvas-bg--right { 226 | @include offcanvas-bg(right,$offset--right); 227 | } 228 | } 229 | } 230 | } -------------------------------------------------------------------------------- /src/js-offcanvas.settings.scss: -------------------------------------------------------------------------------- 1 | // ========================================================================== 2 | // Offcanvas Settings 3 | // ========================================================================== 4 | 5 | 6 | $offcanvas-width: 17em !default; 7 | $offcanvas-left-width: $offcanvas-width; 8 | $offcanvas-right-width: $offcanvas-width; 9 | $offset--left: 17em !default; 10 | $offset--right: 17em !default; 11 | $offset--top: 12.5em !default; 12 | $offset--bottom: 12.5em !default; 13 | $offcanvas-content-reveal-left-offset: $offset--left!default; 14 | $offcanvas-content-reveal-right-offset: $offset--right!default; 15 | 16 | //content 17 | $offset-content-wrap--left: 17em !default; 18 | $offset-content-wrap--right: $offset-content-wrap--left !default; 19 | 20 | // position 21 | $offcanvas-enable-left: true !default; 22 | $offcanvas-enable-right: true !default; 23 | $offcanvas-enable-top: true !default; 24 | $offcanvas-enable-bottom: true !default; 25 | // style 26 | $offcanvas-enable-overlay: true !default; 27 | $offcanvas-enable-push: true !default; 28 | $offcanvas-enable-reveal: true !default; 29 | 30 | $supportCSSTransforms: true !default; 31 | 32 | $elements: body-text,offcanvas-reveal,offcanvas-content-wrap,header,offcanvas-overlay,offcanvas,offcanvas-trigger,offcanvas-panel,offcanvas-btn; 33 | 34 | // https://material.google.com/motion/duration-easing.html#duration-easing-common-durations 35 | $deceleration-curve: cubic-bezier(0.0, 0.0, 0.2, 1); //Easing out 36 | $acceleration-curve: cubic-bezier(0.4, 0.0, 1, 1); // Easing in 37 | $sharp-curve: cubic-bezier(0.4, 0.0, 0.6, 1); 38 | $standard-curve: cubic-bezier(0.4, 0.0, 0.2, 1); -------------------------------------------------------------------------------- /test/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "curly": true, 3 | "eqeqeq": true, 4 | "eqnull": true, 5 | "forin": false, 6 | "immed": true, 7 | "indent": 2, 8 | "latedef": true, 9 | "noarg": true, 10 | "noempty": true, 11 | "nonew": true, 12 | "undef": true, 13 | "unused": true, 14 | "strict": true, 15 | "trailing": true, 16 | "browser": true, 17 | "devel": true, 18 | "jquery": true, 19 | "node": true, 20 | "predef": { 21 | "asyncTest": false, 22 | "deepEqual": false, 23 | "equal": false, 24 | "expect": false, 25 | "module": false, 26 | "notDeepEqual": false, 27 | "notEqual": false, 28 | "notStrictEqual": false, 29 | "ok": false, 30 | "QUnit": false, 31 | "raises": false, 32 | "start": false, 33 | "stop": false, 34 | "strictEqual": false, 35 | "test": false 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /test/test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | js-Offcanvas Test Suite 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 23 | 24 | 25 |
26 |
27 |
28 | default 29 | 34 |
35 |
36 | 37 | -------------------------------------------------------------------------------- /test/tests.js: -------------------------------------------------------------------------------- 1 | /*global module:true*/ 2 | /*global test:true*/ 3 | /*global equal:true*/ 4 | /*global jQuery:true*/ 5 | /*global ok:true*/ 6 | /*global console:true*/ 7 | (function(w, $ ) { 8 | "use strict"; 9 | 10 | module( "Constructor", { 11 | setup: function() { 12 | $( "#qunit-fixture" ).trigger( "enhance" ); 13 | this.offcanvas = $( "#offcanvas" ).data( "offcanvas-component" ); 14 | }, 15 | teardown: function() { 16 | this.offcanvas = null; 17 | } 18 | }); 19 | 20 | test( "Initialization", function() { 21 | ok( $( "#offcanvas" ).is( ".c-offcanvas" ), "Has individual initialization class." ); 22 | console.log( 'modifier classes: '+this.offcanvas.element.className ); 23 | ok( $( "#offcanvas" ).not( ":hidden" ).length, "Offcanvas is hidden by default." ); 24 | }); 25 | 26 | test( "Aria", function() { 27 | ok( $( "#offcanvas" ).is( "[tabindex='-1']" ), "Tabindex added." ); 28 | ok( $( "#offcanvas" ).is( "[role=dialog]" ), "Role added." ); 29 | ok( $( "#offcanvas" ).is( "[id]" ), "offcanvas has an ID." ); 30 | ok( $( "#offcanvas" ).is( "[aria-hidden]" ), "aria-hidden attribute added." ); 31 | ok( $( "#offcanvas-trigger" ).is( "[aria-controls]" ), "Btn aria-controls added." ); 32 | ok( $( "#offcanvas-trigger" ).is( "[aria-pressed]" ), "Btn aria-pressed added." ); 33 | ok( $( "#offcanvas-trigger" ).is( "[aria-expanded]" ), "Btn aria-expanded added." ); 34 | ok( $( "#offcanvas-trigger" ).is( "[role='button']" ), "Btn has button role." ); 35 | equal( $( "#offcanvas-trigger" ).attr( "aria-controls" ), $( "#offcanvas" ).attr( "id" ), "aria-controls value matches offcanvas ID." ); 36 | }); 37 | 38 | test( "States", function() { 39 | this.offcanvas.open(); 40 | ok( this.offcanvas.$element.is( ".is-open" ), "Has is-open class." ); 41 | ok( this.offcanvas.$element.is( "[aria-hidden='false']" ), "aria-hidden set to false." ); 42 | ok( this.offcanvas.$trigger.$element.is( ".is-active" ), "Button has is-active class." ); 43 | ok( this.offcanvas.$trigger.$element.is( "[aria-expanded='true']" ), "Button aria-expanded set to true" ); 44 | this.offcanvas.close(); 45 | ok( this.offcanvas.$element.is( ".is-closed" ), "Has is-closed class." ); 46 | ok( this.offcanvas.$element.is( "[aria-hidden='true']" ), "aria-hidden set to true." ); 47 | ok( this.offcanvas.$trigger.$element.is( "[aria-expanded='false']" ), "Button aria-expanded set to false." ); 48 | 49 | }); 50 | 51 | })( window, jQuery ); --------------------------------------------------------------------------------