├── .config ├── babel.config.js ├── demo │ ├── browsersync.config.js │ └── rollup.config.js ├── postcss.config.js └── rollup.config.js ├── .editorconfig ├── .gitignore ├── .npmignore ├── .travis.yml ├── LICENSE ├── README.md ├── docs ├── css │ └── demo.css ├── index.html ├── js │ ├── demo.bundle.js │ └── demo.js └── scss │ ├── .css │ ├── .css │ │ └── demo.css │ └── demo.css │ └── demo.scss ├── lib ├── css │ ├── handorgel.css │ └── handorgel.min.css └── js │ ├── esm │ ├── handorgel.js │ └── handorgel.min.js │ ├── handorgel.js │ ├── handorgel.min.js │ └── umd │ ├── handorgel.js │ └── handorgel.min.js ├── package-lock.json ├── package.json └── src ├── js ├── fold.js └── index.js └── scss ├── .css ├── style.css └── style.css.map └── style.scss /.config/babel.config.js: -------------------------------------------------------------------------------- 1 | import env from '@babel/preset-env' 2 | 3 | export default { 4 | esEnv: { 5 | exclude: 'node_modules/**', 6 | presets: [ 7 | [env, { modules: false }] 8 | ] 9 | }, 10 | esStage2: { 11 | exclude: 'node_modules/**', 12 | plugins: [ 13 | ['@babel/plugin-proposal-decorators', { legacy: true }], 14 | '@babel/plugin-proposal-function-sent', 15 | '@babel/plugin-proposal-export-namespace-from', 16 | '@babel/plugin-proposal-numeric-separator', 17 | '@babel/plugin-proposal-throw-expressions', 18 | ] 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /.config/demo/browsersync.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | ui: false, 3 | server: true, 4 | startPath: process.env.DEMO_PATH, 5 | files: [ 6 | process.env.DEMO_PATH +'/index.html', 7 | process.env.DEMO_PATH +'/css/demo.css', 8 | process.env.DEMO_PATH +'/js/demo.bundle.js' 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /.config/demo/rollup.config.js: -------------------------------------------------------------------------------- 1 | import resolve from 'rollup-plugin-node-resolve' 2 | import commonjs from 'rollup-plugin-commonjs' 3 | 4 | import babel from 'rollup-plugin-babel' 5 | import babelConfig from '../babel.config' 6 | 7 | export default { 8 | input: `${process.env.DEMO_PATH}/js/demo.js`, 9 | output: { 10 | file: `${process.env.DEMO_PATH}/js/demo.bundle.js`, 11 | format: 'iife' 12 | }, 13 | plugins: [ 14 | resolve(), 15 | commonjs(), 16 | babel(babelConfig.esEnv) 17 | ] 18 | } 19 | 20 | -------------------------------------------------------------------------------- /.config/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = (ctx) => { 2 | return { 3 | map: ctx.options.map, 4 | plugins: { 5 | 'autoprefixer': {}, 6 | 'postcss-import': {}, 7 | 'postcss-pxtorem': { 8 | rootValue: 16, 9 | unitPrecision: 5, 10 | propWhiteList: ['font', 'font-size', 'line-height', 'letter-spacing'], 11 | replace: true, 12 | mediaQuery: false, 13 | minPixelValue: 2 14 | }, 15 | 'postcss-assets': { 16 | relative: true, 17 | cachebuster: true 18 | }, 19 | 'css-mqpacker': {}, 20 | 'cssnano': ctx.env !== 'minified' ? false : { 21 | zindex: false 22 | } 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /.config/rollup.config.js: -------------------------------------------------------------------------------- 1 | import resolve from 'rollup-plugin-node-resolve' 2 | import commonjs from 'rollup-plugin-commonjs' 3 | 4 | import babel from 'rollup-plugin-babel' 5 | import babelConfig from './babel.config' 6 | 7 | import pkg from '../package.json' 8 | 9 | const COPYRIGHT = `/** ${pkg.name} v${pkg.version}, @license MIT */` 10 | const INPUT = `${process.env.INPUT_PATH}/index.js` 11 | const OUTPUT = `${process.env.OUTPUT_PATH}/${pkg.name}.js` 12 | 13 | export default [ 14 | { 15 | input: INPUT, 16 | output: [ 17 | { 18 | file: pkg.main, 19 | name: pkg.name, 20 | format: 'umd', 21 | banner: COPYRIGHT 22 | }, { 23 | file: pkg.module, 24 | format: 'es', 25 | banner: COPYRIGHT 26 | } 27 | ], 28 | plugins: [ 29 | resolve(), 30 | commonjs(), 31 | babel(babelConfig.esEnv) 32 | ] 33 | }, { 34 | input: INPUT, 35 | output: { 36 | file: OUTPUT, 37 | name: pkg.name, 38 | format: 'es', 39 | banner: COPYRIGHT 40 | }, 41 | plugins: [ 42 | resolve(), 43 | commonjs(), 44 | babel(babelConfig.esStage2) 45 | ] 46 | } 47 | ] 48 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # Top-most EditorConfig file 2 | root = true 3 | 4 | # Unix-style newlines with a newline ending every file 5 | [*] 6 | end_of_line = lf 7 | indent_style = space 8 | indent_size = 2 9 | insert_final_newline = true 10 | trim_trailing_whitespace = true 11 | 12 | [*.txt] 13 | insert_final_newline = false 14 | 15 | [*.md] 16 | trim_trailing_whitespace = false 17 | 18 | [Makefile] 19 | indent_style = tab 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /npm-debug.log 3 | /.sass-cache 4 | 5 | .env 6 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # ignore everything 2 | * 3 | 4 | # except the lib and src files 5 | !lib/**/* 6 | !src/**/* 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | dist: trusty 3 | node_js: 4 | - "node" 5 | cache: 6 | directories: 7 | - node_modules 8 | script: 9 | - npm test 10 | notifications: 11 | email: false 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Manuel Sommerhalder 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | 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, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Handorgel 2 | 3 | [![NPM version][npm-image]][npm-url] 4 | [![Coding Style][style-image]][style-url] 5 | [![MIT License][license-image]][license-url] 6 | 7 | Accessible [W3C](https://www.w3.org/TR/wai-aria-practices/#accordion) conform accordion written in ES6. `Handorgel` is the Swiss German name for accordion. 8 | 9 | [Visit the demo](https://oncode.github.io/handorgel/) 10 | 11 | ## Features 12 | 13 | * ARIA accessible 14 | * Keyboard interaction 15 | * Extensive [API](#api) 16 | * Animated collapsing 17 | * Fully customizable via CSS 18 | * No external dependencies 19 | * Lightweight (~3kb minified and gziped) 20 | 21 | ## Installation 22 | 23 | ### Package manager 24 | 25 | Manager | Command 26 | --- | --- 27 | npm | `npm install handorgel` 28 | yarn | `yarn add handorgel` 29 | 30 | ### CDN / Download 31 | 32 | File | CDN 33 | --- | --- 34 | CSS | [handorgel.css](https://unpkg.com/handorgel@1.0/lib/css/handorgel.css) 35 | CSS (minified) | [handorgel.min.css](https://unpkg.com/handorgel@1.0/lib/css/handorgel.min.css) 36 | JS | [handorgel.js](https://unpkg.com/handorgel@1.0/lib/js/umd/handorgel.js) 37 | JS (minified) | [handorgel.min.js](https://unpkg.com/handorgel@1.0/lib/js/umd/handorgel.min.js) 38 | 39 | ## Usage 40 | 41 | ### Markup 42 | 43 | ```html 44 |
45 |

46 | 49 |

50 |
51 |
52 | Content openened by default 53 |
54 |
55 |

56 | 59 |

60 |
61 |
62 | Content closed by default 63 |
64 |
65 | 66 | ... 67 | 68 |
69 | ``` 70 | 71 | **Note**: Use the [heading tags](https://developer.paciellogroup.com/blog/2013/10/html5-document-outline/) that fit into your content to output semantic markup. 72 | 73 | ### CSS 74 | 75 | Import the SASS file from your `node_modules` folder to make use of the variables: 76 | 77 | ```sass 78 | // e.g. changing opening/closing transition times 79 | $handorgel__content--open-transition-height-time: .1s; 80 | $handorgel__content--open-transition-opacity-time: .2s; 81 | $handorgel__content-transition-height-time: .05s; 82 | $handorgel__content-transition-opacity-time: .05s; 83 | //... 84 | 85 | @import '~handorgel/src/scss/style'; 86 | ``` 87 | 88 | Alternatively you can just include the built CSS file inside the `/lib` folder file or from the CDN. 89 | 90 | ### Javascript 91 | 92 | Initialization (with all options and their defaults): 93 | 94 | ```javascript 95 | var accordion = new handorgel(document.querySelector('.handorgel'), { 96 | 97 | // whether multiple folds can be opened at once 98 | multiSelectable: true, 99 | // whether the folds are collapsible 100 | collapsible: true, 101 | 102 | // whether ARIA attributes are enabled 103 | ariaEnabled: true, 104 | // whether W3C keyboard shortcuts are enabled 105 | keyboardInteraction: true, 106 | // whether to loop header focus (sets focus back to first/last header when end/start reached) 107 | carouselFocus: true, 108 | 109 | // attribute for the header or content to open folds at initialization 110 | initialOpenAttribute: 'data-open', 111 | // whether to use transition at initial open 112 | initialOpenTransition: true, 113 | // delay used to show initial transition 114 | initialOpenTransitionDelay: 200, 115 | 116 | // header/content element selectors or array of elements 117 | headerElements: '.handorgel__header', 118 | contentElements: '.handorgel__content', 119 | 120 | // header/content class if fold is open 121 | headerOpenClass: 'handorgel__header--open', 122 | contentOpenClass: 'handorgel__content--open', 123 | 124 | // header/content class if fold has been opened (transition finished) 125 | headerOpenedClass: 'handorgel__header--opened', 126 | contentOpenedClass: 'handorgel__content--opened', 127 | 128 | // header/content class if fold has been focused 129 | headerFocusClass: 'handorgel__header--focus', 130 | contentFocusClass: 'handorgel__content--focus', 131 | 132 | // header/content class if fold is disabled 133 | headerDisabledClass: 'handorgel__header--disabled', 134 | contentDisabledClass: 'handorgel__content--disabled', 135 | 136 | }) 137 | ``` 138 | 139 | ## API 140 | 141 | ### Events 142 | 143 | Event | Description | Parameters 144 | --- | --- | --- 145 | `destroy` | Accordeon is about to be destroyed. | 146 | `destroyed` | Accordeon has been destroyed. | 147 | `fold:open` | Fold is about to be opened. | `HandorgelFold`: Fold instance 148 | `fold:opened` | Fold has opened. | `HandorgelFold`: Fold instance 149 | `fold:close` | Fold is about to be closed. | `HandorgelFold`: Fold instance 150 | `fold:closed` | Fold has closed. | `HandorgelFold`: Fold instance 151 | `fold:focus` | Fold button has been focused. | `HandorgelFold`: Fold instance 152 | `fold:blur` | Fold button has lost focus. | `HandorgelFold`: Fold instance 153 | 154 | How to listen for events: 155 | 156 | ```javascript 157 | var accordion = new handorgel(document.querySelector('.handorgel')) 158 | 159 | // listen for event 160 | accordion.on('fold:open', (fold) => { 161 | // ... 162 | }) 163 | 164 | // listen for event once 165 | accordion.once('fold:open', (fold) => { 166 | // ... 167 | }) 168 | 169 | // remove event listener 170 | accordion.off('fold:open', fn) 171 | ``` 172 | 173 | ### Methods 174 | 175 | #### Handorgel Class 176 | 177 | Method | Description | Parameters 178 | --- | --- | --- 179 | `update` | Update fold instances (use if you dynamically append/remove DOM nodes). | 180 | `focus` | Set focus to a new header button (you can also directly use the native `focus()` method on the button). | `target`: New header button to focus (`next`, `previous`, `last` or `first`) 181 | `destroy` | Destroy fold instances, remove event listeners and ARIA attributes. | 182 | 183 | Example: 184 | 185 | ```javascript 186 | var accordion = new handorgel(document.querySelector('.handorgel')) 187 | 188 | // destroy 189 | accordion.destroy() 190 | ``` 191 | 192 | #### HandorgelFold Class 193 | 194 | Method | Description | Parameters 195 | --- | --- | --- 196 | `open` | Open content. | `transition`: Whether transition should be active during opening (default: `true`). 197 | `close` | Close content. | `transition`: Whether transition should be active during closing (default: `true`). 198 | `toggle` | Toggle content. | `transition`: Whether transition should be active during toggling (default: `true`). 199 | `disable` | Disable fold. | 200 | `enable` | Enable fold. | 201 | `focus` | Set focus to fold button. | 202 | `blur` | Remove focus from fold button. | 203 | `destroy` | Remove event listeners and ARIA attributes. | 204 | 205 | Example: 206 | 207 | ```javascript 208 | var accordion = new handorgel(document.querySelector('.handorgel')) 209 | 210 | // close first fold 211 | accordion.folds[0].close() 212 | ``` 213 | 214 | ## Browser compatibility 215 | 216 | * Newest two browser versions of Chrome, Firefox, Safari and Edge 217 | 218 | ## Development 219 | 220 | * `npm run build` - Build production version of the feature. 221 | * `npm run demo` - Build demo of the feature, run watchers and start browser-sync. 222 | * `npm run test` - Test the feature. 223 | 224 | ## License 225 | 226 | [MIT LICENSE](http://opensource.org/licenses/MIT) 227 | 228 | [npm-image]: https://img.shields.io/npm/v/handorgel.svg 229 | [npm-url]: https://npmjs.com/package/handorgel 230 | 231 | [style-image]: https://img.shields.io/badge/code%20style-standard-yellow.svg 232 | [style-url]: http://standardjs.com/ 233 | 234 | [license-image]: http://img.shields.io/badge/license-MIT-000000.svg 235 | [license-url]: LICENSE 236 | -------------------------------------------------------------------------------- /docs/css/demo.css: -------------------------------------------------------------------------------- 1 | .handorgel { 2 | display: block; 3 | width: 100%; 4 | border: 1px solid #eee; 5 | border-top: none; } 6 | 7 | .handorgel__header { 8 | display: block; 9 | margin: 0; } 10 | 11 | .handorgel__header--open .handorgel__header__button { 12 | background-color: #eee; } 13 | 14 | .handorgel__header--focus .handorgel__header__button { 15 | background-color: #dfdfdf; 16 | outline: none; } 17 | 18 | .handorgel__header__button { 19 | display: block; 20 | width: 100%; 21 | padding: 20px 24px; 22 | margin: 0; 23 | border: none; 24 | border-top: 1px solid #eee; 25 | background-color: #fff; 26 | border-radius: 0; 27 | color: inherit; 28 | cursor: pointer; 29 | font-size: inherit; 30 | text-align: left; 31 | -webkit-transition: background-color 0.2s ease; 32 | transition: background-color 0.2s ease; 33 | -webkit-user-select: none; 34 | -moz-user-select: none; 35 | -ms-user-select: none; 36 | user-select: none; } 37 | 38 | .handorgel__header__button::-moz-focus-inner { 39 | border: 0; } 40 | 41 | .handorgel__content { 42 | display: none; 43 | overflow: hidden; 44 | height: 0; 45 | border-top: 1px solid #eee; 46 | background-color: #fff; 47 | -webkit-transition: height 0.1s ease 0.1s; 48 | transition: height 0.1s ease 0.1s; } 49 | 50 | .handorgel__content--open { 51 | display: block; 52 | -webkit-transition: height 0.2s ease; 53 | transition: height 0.2s ease; } 54 | 55 | .handorgel__content--opened { 56 | overflow: visible; } 57 | 58 | .handorgel__content__inner { 59 | padding: 20px 24px; 60 | opacity: 0; 61 | -webkit-transition: opacity 0.1s ease; 62 | transition: opacity 0.1s ease; } 63 | 64 | .handorgel__content--opened .handorgel__content__inner { 65 | opacity: 1; 66 | -webkit-transition: opacity 0.3s ease; 67 | transition: opacity 0.3s ease; } 68 | 69 | *, 70 | *::after, 71 | *::before { 72 | -webkit-box-sizing: inherit; 73 | box-sizing: inherit; } 74 | 75 | body { 76 | -webkit-box-sizing: border-box; 77 | box-sizing: border-box; 78 | margin: 80px 60px; 79 | background-color: #202328; 80 | color: #dadada; 81 | font-family: 'Helvetica Neue', Helvetica, sans-serif; 82 | font-size: 1rem; 83 | -moz-osx-font-smoothing: grayscale; 84 | -webkit-font-smoothing: antialiased; 85 | line-height: 1.4; 86 | -webkit-text-size-adjust: 100%; 87 | -moz-text-size-adjust: 100%; 88 | -ms-text-size-adjust: 100%; 89 | text-size-adjust: 100%; } 90 | 91 | a { 92 | color: #dadada; } 93 | 94 | p { 95 | margin-top: 0; 96 | margin-bottom: 15px; } 97 | 98 | p:last-child { 99 | margin-bottom: 0; } 100 | 101 | .description { 102 | margin-bottom: 60px; 103 | text-align: center; } 104 | 105 | .wrapper { 106 | width: 80%; 107 | max-width: 700px; 108 | margin: 0 auto; } 109 | 110 | .wrapper .example { 111 | margin-bottom: 30px; } 112 | 113 | .usage { 114 | margin-top: 80px; } 115 | 116 | h1 { 117 | margin-top: 0; 118 | margin-bottom: 10px; 119 | font-size: 2.5rem; 120 | text-align: center; } 121 | 122 | h2 { 123 | margin-top: 40px; 124 | margin-bottom: 10px; 125 | font-size: 1.75rem; } 126 | 127 | h3 { 128 | margin-top: 20px; 129 | margin-bottom: 10px; 130 | font-size: 1.25rem; } 131 | 132 | pre { 133 | margin: 0; 134 | margin-bottom: 30px; } 135 | 136 | .handorgel { 137 | margin-bottom: 80px; 138 | color: #333; } 139 | 140 | @media (max-width: 600px) { 141 | body { 142 | margin: 80px 40px; } 143 | .wrapper { 144 | width: 100%; } } 145 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | handorgel - Accordion library 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | Fork me on GitHub 15 | 16 | 17 |

handorgel

18 | 19 |

20 | Accessible W3C conform accordion written in ES6. 21 |

22 | 23 |
24 | 25 |
26 |

27 | 30 |

31 |
32 |
33 |

Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.

34 |
35 |
36 |

37 | 40 |

41 |
42 |
43 |

Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.

44 |
45 |
46 |

47 | 50 |

51 |
52 |
53 |

Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.

54 |
55 |
56 |

57 | 60 |

61 |
62 |
63 |

Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.

64 |
65 |
66 |
67 | 68 | 69 | 70 |
71 |

Usage

72 | 73 |

Markup

74 |
<div class="handorgel">
 75 |   <h3 class="handorgel__header">
 76 |     <button class="handorgel__header__button" autofocus>
 77 |       Title (autofocused)
 78 |     </button>
 79 |   </h3>
 80 |   <div class="handorgel__content" data-open>
 81 |     <div class="handorgel__content__inner">
 82 |       Content openened by default
 83 |     </div>
 84 |   </div>
 85 |   <h3 class="handorgel__header">
 86 |     <button class="handorgel__header__button">
 87 |       Title 2
 88 |     </button>
 89 |   </h3>
 90 |   <div class="handorgel__content">
 91 |     <div class="handorgel__content__inner">
 92 |       Content closed by default
 93 |     </div>
 94 |   </div>
 95 | </div>
96 |
97 | 98 |

SASS

99 |
@import '~handorgel/src/style';
100 | 101 |

JS

102 |
var h = new handorgel(document.querySelector('.handorgel'), options)
103 | See javascript section for available options. 104 | 105 |

Installation

106 | Please visit the readme to read the installation instructions. 107 | 108 |

Examples

109 | 110 |
111 |

Maximum one item opened

112 | 113 |
new handorgel(document.querySelector('.handorgel'), {
114 |   multiSelectable: false
115 | })
116 | 117 |
118 |

119 | 122 |

123 |
124 |
125 |

Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.

126 |
127 |
128 |

129 | 132 |

133 |
134 |
135 |

Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.

136 |
137 |
138 |

139 | 142 |

143 |
144 |
145 |

Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.

146 |
147 |
148 |

149 | 152 |

153 |
154 |
155 |

Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.

156 |
157 |
158 |
159 |
160 | 161 | 162 |
163 |

Maximum one item opened and not toggable

164 |
new handorgel(document.querySelector('.handorgel'), {
165 |   multiSelectable: false,
166 |   collapsible: false
167 | })
168 | 169 |
170 |

171 | 174 |

175 |
176 |
177 |

Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.

178 |

Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.

179 |
180 |
181 |

182 | 185 |

186 |
187 |
188 |

Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.

189 |

Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.

190 |
191 |
192 |

193 | 196 |

197 |
198 |
199 |

Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.

200 |

Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.

201 |
202 |
203 |

204 | 207 |

208 |
209 |
210 |

Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.

211 |
212 |
213 |
214 |
215 | 216 |
217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | -------------------------------------------------------------------------------- /docs/js/demo.bundle.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | function _classCallCheck(instance, Constructor) { 5 | if (!(instance instanceof Constructor)) { 6 | throw new TypeError("Cannot call a class as a function"); 7 | } 8 | } 9 | function _defineProperties(target, props) { 10 | for (var i = 0; i < props.length; i++) { 11 | var descriptor = props[i]; 12 | descriptor.enumerable = descriptor.enumerable || false; 13 | descriptor.configurable = true; 14 | if ("value" in descriptor) descriptor.writable = true; 15 | Object.defineProperty(target, _toPropertyKey(descriptor.key), descriptor); 16 | } 17 | } 18 | function _createClass(Constructor, protoProps, staticProps) { 19 | if (protoProps) _defineProperties(Constructor.prototype, protoProps); 20 | if (staticProps) _defineProperties(Constructor, staticProps); 21 | Object.defineProperty(Constructor, "prototype", { 22 | writable: false 23 | }); 24 | return Constructor; 25 | } 26 | function _inherits(subClass, superClass) { 27 | if (typeof superClass !== "function" && superClass !== null) { 28 | throw new TypeError("Super expression must either be null or a function"); 29 | } 30 | subClass.prototype = Object.create(superClass && superClass.prototype, { 31 | constructor: { 32 | value: subClass, 33 | writable: true, 34 | configurable: true 35 | } 36 | }); 37 | Object.defineProperty(subClass, "prototype", { 38 | writable: false 39 | }); 40 | if (superClass) _setPrototypeOf(subClass, superClass); 41 | } 42 | function _getPrototypeOf(o) { 43 | _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf.bind() : function _getPrototypeOf(o) { 44 | return o.__proto__ || Object.getPrototypeOf(o); 45 | }; 46 | return _getPrototypeOf(o); 47 | } 48 | function _setPrototypeOf(o, p) { 49 | _setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function _setPrototypeOf(o, p) { 50 | o.__proto__ = p; 51 | return o; 52 | }; 53 | return _setPrototypeOf(o, p); 54 | } 55 | function _isNativeReflectConstruct() { 56 | if (typeof Reflect === "undefined" || !Reflect.construct) return false; 57 | if (Reflect.construct.sham) return false; 58 | if (typeof Proxy === "function") return true; 59 | try { 60 | Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); 61 | return true; 62 | } catch (e) { 63 | return false; 64 | } 65 | } 66 | function _assertThisInitialized(self) { 67 | if (self === void 0) { 68 | throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); 69 | } 70 | return self; 71 | } 72 | function _possibleConstructorReturn(self, call) { 73 | if (call && (typeof call === "object" || typeof call === "function")) { 74 | return call; 75 | } else if (call !== void 0) { 76 | throw new TypeError("Derived constructors may only return object or undefined"); 77 | } 78 | return _assertThisInitialized(self); 79 | } 80 | function _createSuper(Derived) { 81 | var hasNativeReflectConstruct = _isNativeReflectConstruct(); 82 | return function _createSuperInternal() { 83 | var Super = _getPrototypeOf(Derived), 84 | result; 85 | if (hasNativeReflectConstruct) { 86 | var NewTarget = _getPrototypeOf(this).constructor; 87 | result = Reflect.construct(Super, arguments, NewTarget); 88 | } else { 89 | result = Super.apply(this, arguments); 90 | } 91 | return _possibleConstructorReturn(this, result); 92 | }; 93 | } 94 | function _toPrimitive(input, hint) { 95 | if (typeof input !== "object" || input === null) return input; 96 | var prim = input[Symbol.toPrimitive]; 97 | if (prim !== undefined) { 98 | var res = prim.call(input, hint || "default"); 99 | if (typeof res !== "object") return res; 100 | throw new TypeError("@@toPrimitive must return a primitive value."); 101 | } 102 | return (hint === "string" ? String : Number)(input); 103 | } 104 | function _toPropertyKey(arg) { 105 | var key = _toPrimitive(arg, "string"); 106 | return typeof key === "symbol" ? key : String(key); 107 | } 108 | 109 | var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {}; 110 | 111 | function createCommonjsModule(fn, module) { 112 | return module = { exports: {} }, fn(module, module.exports), module.exports; 113 | } 114 | 115 | var evEmitter = createCommonjsModule(function (module) { 116 | /** 117 | * EvEmitter v1.1.0 118 | * Lil' event emitter 119 | * MIT License 120 | */ 121 | 122 | /* jshint unused: true, undef: true, strict: true */ 123 | 124 | ( function( global, factory ) { 125 | // universal module definition 126 | /* jshint strict: false */ /* globals define, module, window */ 127 | if ( module.exports ) { 128 | // CommonJS - Browserify, Webpack 129 | module.exports = factory(); 130 | } else { 131 | // Browser globals 132 | global.EvEmitter = factory(); 133 | } 134 | 135 | }( typeof window != 'undefined' ? window : commonjsGlobal, function() { 136 | 137 | function EvEmitter() {} 138 | 139 | var proto = EvEmitter.prototype; 140 | 141 | proto.on = function( eventName, listener ) { 142 | if ( !eventName || !listener ) { 143 | return; 144 | } 145 | // set events hash 146 | var events = this._events = this._events || {}; 147 | // set listeners array 148 | var listeners = events[ eventName ] = events[ eventName ] || []; 149 | // only add once 150 | if ( listeners.indexOf( listener ) == -1 ) { 151 | listeners.push( listener ); 152 | } 153 | 154 | return this; 155 | }; 156 | 157 | proto.once = function( eventName, listener ) { 158 | if ( !eventName || !listener ) { 159 | return; 160 | } 161 | // add event 162 | this.on( eventName, listener ); 163 | // set once flag 164 | // set onceEvents hash 165 | var onceEvents = this._onceEvents = this._onceEvents || {}; 166 | // set onceListeners object 167 | var onceListeners = onceEvents[ eventName ] = onceEvents[ eventName ] || {}; 168 | // set flag 169 | onceListeners[ listener ] = true; 170 | 171 | return this; 172 | }; 173 | 174 | proto.off = function( eventName, listener ) { 175 | var listeners = this._events && this._events[ eventName ]; 176 | if ( !listeners || !listeners.length ) { 177 | return; 178 | } 179 | var index = listeners.indexOf( listener ); 180 | if ( index != -1 ) { 181 | listeners.splice( index, 1 ); 182 | } 183 | 184 | return this; 185 | }; 186 | 187 | proto.emitEvent = function( eventName, args ) { 188 | var listeners = this._events && this._events[ eventName ]; 189 | if ( !listeners || !listeners.length ) { 190 | return; 191 | } 192 | // copy over to avoid interference if .off() in listener 193 | listeners = listeners.slice(0); 194 | args = args || []; 195 | // once stuff 196 | var onceListeners = this._onceEvents && this._onceEvents[ eventName ]; 197 | 198 | for ( var i=0; i < listeners.length; i++ ) { 199 | var listener = listeners[i]; 200 | var isOnce = onceListeners && onceListeners[ listener ]; 201 | if ( isOnce ) { 202 | // remove listener 203 | // remove before trigger to prevent recursion 204 | this.off( eventName, listener ); 205 | // unset once flag 206 | delete onceListeners[ listener ]; 207 | } 208 | // trigger listener 209 | listener.apply( this, args ); 210 | } 211 | 212 | return this; 213 | }; 214 | 215 | proto.allOff = function() { 216 | delete this._events; 217 | delete this._onceEvents; 218 | }; 219 | 220 | return EvEmitter; 221 | 222 | })); 223 | }); 224 | 225 | var ID_COUNTER = {}; 226 | var ARIA_ATTRIBUTES = { 227 | button: { 228 | 'aria-controls': function ariaControls() { 229 | return this.id + '-content'; 230 | }, 231 | 'aria-expanded': function ariaExpanded() { 232 | return this.expanded ? 'true' : 'false'; 233 | }, 234 | 'aria-disabled': function ariaDisabled() { 235 | return this.disabled ? 'true' : 'false'; 236 | } 237 | }, 238 | content: { 239 | role: function role() { 240 | return 'region'; 241 | }, 242 | 'aria-labelledby': function ariaLabelledby() { 243 | return this.id + '-header'; 244 | } 245 | } 246 | }; 247 | var KEYS = { 248 | arrowDown: 40, 249 | arrowUp: 38, 250 | pageUp: 33, 251 | pageDown: 34, 252 | end: 35, 253 | home: 36 254 | }; 255 | var HandorgelFold = /*#__PURE__*/function () { 256 | function HandorgelFold(handorgel, header, content) { 257 | _classCallCheck(this, HandorgelFold); 258 | if (header.handorgelFold) { 259 | return; 260 | } 261 | this.handorgel = handorgel; 262 | this.header = header; 263 | this.button = header.firstElementChild; 264 | this.content = content; 265 | this.header.handorgelFold = this; 266 | this.content.handorgelFold = this; 267 | if (!ID_COUNTER[this.handorgel.id]) { 268 | ID_COUNTER[this.handorgel.id] = 0; 269 | } 270 | this.id = "".concat(this.handorgel.id, "-fold").concat(++ID_COUNTER[this.handorgel.id]); 271 | this.header.setAttribute('id', this.id + '-header'); 272 | this.content.setAttribute('id', this.id + '-content'); 273 | this.focused = false; 274 | this.expanded = false; 275 | this.disabled = false; 276 | this._listeners = {}; 277 | this._bindEvents(); 278 | this._initAria(); 279 | this._initialOpen(); 280 | this._initialFocus(); 281 | } 282 | _createClass(HandorgelFold, [{ 283 | key: "open", 284 | value: function open() { 285 | var transition = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true; 286 | if (this.expanded) { 287 | return; 288 | } 289 | this.handorgel.emitEvent('fold:open', [this]); 290 | this.expanded = true; 291 | if (!this.handorgel.options.collapsible) { 292 | this.disable(); 293 | } 294 | this._updateAria('button', 'aria-expanded'); 295 | this.header.classList.add(this.handorgel.options.headerOpenClass); 296 | this.content.classList.add(this.handorgel.options.contentOpenClass); 297 | if (!transition) { 298 | this._opened(); 299 | } else { 300 | var height = this.content.firstElementChild.offsetHeight; 301 | this.content.style.height = "".concat(height, "px"); 302 | } 303 | } 304 | }, { 305 | key: "close", 306 | value: function close() { 307 | var _this = this; 308 | var transition = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true; 309 | if (!this.expanded) { 310 | return; 311 | } 312 | this.handorgel.emitEvent('fold:close', [this]); 313 | this.expanded = false; 314 | if (!this.handorgel.options.collapsible) { 315 | this.enable(); 316 | } 317 | this._updateAria('button', 'aria-expanded'); 318 | this.header.classList.remove(this.handorgel.options.headerOpenedClass); 319 | this.content.classList.remove(this.handorgel.options.contentOpenedClass); 320 | if (!transition) { 321 | this._closed(); 322 | } else { 323 | // if we want to transition when closing we 324 | // have to set the current height and replace auto 325 | var height = this.content.firstElementChild.offsetHeight; 326 | this.content.style.height = "".concat(height, "px"); 327 | window.requestAnimationFrame(function () { 328 | window.requestAnimationFrame(function () { 329 | _this.content.style.height = '0px'; 330 | }); 331 | }); 332 | } 333 | } 334 | }, { 335 | key: "disable", 336 | value: function disable() { 337 | this.disabled = true; 338 | this._updateAria('button', 'aria-disabled'); 339 | this.header.classList.add(this.handorgel.options.headerDisabledClass); 340 | this.content.classList.add(this.handorgel.options.contentDisabledClass); 341 | } 342 | }, { 343 | key: "enable", 344 | value: function enable() { 345 | this.disabled = false; 346 | this._updateAria('button', 'aria-disabled'); 347 | this.header.classList.remove(this.handorgel.options.headerDisabledClass); 348 | this.content.classList.remove(this.handorgel.options.contentDisabledClass); 349 | } 350 | }, { 351 | key: "focus", 352 | value: function focus() { 353 | this.button.focus(); 354 | } 355 | }, { 356 | key: "blur", 357 | value: function blur() { 358 | this.button.blur(); 359 | } 360 | }, { 361 | key: "toggle", 362 | value: function toggle() { 363 | var transition = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true; 364 | if (this.expanded) { 365 | this.close(transition); 366 | } else { 367 | this.open(transition); 368 | } 369 | } 370 | }, { 371 | key: "destroy", 372 | value: function destroy() { 373 | this._unbindEvents(); 374 | this._cleanAria(); 375 | 376 | // clean classes 377 | this.header.classList.remove(this.handorgel.options.headerOpenClass); 378 | this.header.classList.remove(this.handorgel.options.headerOpenedClass); 379 | this.header.classList.remove(this.handorgel.options.headerFocusClass); 380 | this.content.classList.remove(this.handorgel.options.contentOpenClass); 381 | this.content.classList.remove(this.handorgel.options.contentOpenedClass); 382 | this.content.classList.remove(this.handorgel.options.contentFocusClass); 383 | 384 | // hide content 385 | this.content.style.height = '0px'; 386 | 387 | // clean reference to this instance 388 | this.header.handorgelFold = null; 389 | this.content.handorgelFold = null; 390 | 391 | // remove ids 392 | this.header.removeAttribute('id'); 393 | this.content.removeAttribute('id'); 394 | 395 | // clean reference to handorgel instance 396 | this.handorgel = null; 397 | } 398 | }, { 399 | key: "_opened", 400 | value: function _opened() { 401 | this.content.style.height = 'auto'; 402 | this.header.classList.add(this.handorgel.options.headerOpenedClass); 403 | this.content.classList.add(this.handorgel.options.contentOpenedClass); 404 | this.handorgel.emitEvent('fold:opened', [this]); 405 | } 406 | }, { 407 | key: "_closed", 408 | value: function _closed() { 409 | this.header.classList.remove(this.handorgel.options.headerOpenClass); 410 | this.content.classList.remove(this.handorgel.options.contentOpenClass); 411 | this.handorgel.emitEvent('fold:closed', [this]); 412 | } 413 | }, { 414 | key: "_initialOpen", 415 | value: function _initialOpen() { 416 | var _this2 = this; 417 | if (this.header.getAttribute(this.handorgel.options.initialOpenAttribute) !== null || this.content.getAttribute(this.handorgel.options.initialOpenAttribute) !== null) { 418 | if (this.handorgel.options.initialOpenTransition) { 419 | window.setTimeout(function () { 420 | _this2.open(); 421 | }, this.handorgel.options.initialOpenTransitionDelay); 422 | } else { 423 | this.open(false); 424 | } 425 | } 426 | } 427 | }, { 428 | key: "_initialFocus", 429 | value: function _initialFocus() { 430 | if (this.button.getAttribute('autofocus') === null) { 431 | return; 432 | } 433 | 434 | // to ensure focus styles if autofocus was applied 435 | // before focus listener was added 436 | this._handleFocus(); 437 | } 438 | }, { 439 | key: "_initAria", 440 | value: function _initAria() { 441 | this._updateAria('button'); 442 | this._updateAria('content'); 443 | } 444 | }, { 445 | key: "_cleanAria", 446 | value: function _cleanAria() { 447 | this._updateAria('button', null, true); 448 | this._updateAria('content', null, true); 449 | } 450 | }, { 451 | key: "_updateAria", 452 | value: function _updateAria(element) { 453 | var property = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null; 454 | var remove = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false; 455 | if (!this.handorgel.options.ariaEnabled) { 456 | return; 457 | } 458 | if (property) { 459 | var newValue = ARIA_ATTRIBUTES[element][property].call(this); 460 | this[element].setAttribute(property, newValue); 461 | } else { 462 | for (var _property in ARIA_ATTRIBUTES[element]) { 463 | if (ARIA_ATTRIBUTES[element].hasOwnProperty(_property)) { 464 | if (remove) { 465 | this[element].removeAttribute(_property); 466 | } else { 467 | var _newValue = ARIA_ATTRIBUTES[element][_property].call(this); 468 | this[element].setAttribute(_property, _newValue); 469 | } 470 | } 471 | } 472 | } 473 | } 474 | }, { 475 | key: "_handleContentTransitionEnd", 476 | value: function _handleContentTransitionEnd(e) { 477 | if (e.target === e.currentTarget && e.propertyName === 'height') { 478 | if (this.expanded) { 479 | this._opened(); 480 | } else { 481 | this._closed(); 482 | } 483 | } 484 | } 485 | }, { 486 | key: "_handleFocus", 487 | value: function _handleFocus() { 488 | this.focused = true; 489 | this.header.classList.add(this.handorgel.options.headerFocusClass); 490 | this.content.classList.add(this.handorgel.options.contentFocusClass); 491 | this.handorgel.emitEvent('fold:focus', [this]); 492 | } 493 | }, { 494 | key: "_handleBlur", 495 | value: function _handleBlur() { 496 | this.focused = false; 497 | this.header.classList.remove(this.handorgel.options.headerFocusClass); 498 | this.content.classList.remove(this.handorgel.options.contentFocusClass); 499 | this.handorgel.emitEvent('fold:blur', [this]); 500 | } 501 | }, { 502 | key: "_handleButtonClick", 503 | value: function _handleButtonClick(e) { 504 | // ensure focus is on button (click is not seting focus on firefox mac) 505 | this.focus(); 506 | if (this.disabled) { 507 | return; 508 | } 509 | this.toggle(); 510 | } 511 | }, { 512 | key: "_handleButtonKeydown", 513 | value: function _handleButtonKeydown(e) { 514 | if (!this.handorgel.options.keyboardInteraction) { 515 | return; 516 | } 517 | var action = null; 518 | switch (e.which) { 519 | case KEYS.arrowDown: 520 | action = 'next'; 521 | break; 522 | case KEYS.arrowUp: 523 | action = 'prev'; 524 | break; 525 | case KEYS.home: 526 | action = 'first'; 527 | break; 528 | case KEYS.end: 529 | action = 'last'; 530 | break; 531 | case KEYS.pageDown: 532 | if (e.ctrlKey) { 533 | action = 'next'; 534 | } 535 | break; 536 | case KEYS.pageUp: 537 | if (e.ctrlKey) { 538 | action = 'prev'; 539 | } 540 | break; 541 | } 542 | if (action) { 543 | e.preventDefault(); 544 | this.handorgel.focus(action); 545 | } 546 | } 547 | }, { 548 | key: "_handleContentKeydown", 549 | value: function _handleContentKeydown(e) { 550 | if (!this.handorgel.options.keyboardInteraction || !e.ctrlKey) { 551 | return; 552 | } 553 | var action = null; 554 | switch (e.which) { 555 | case KEYS.pageDown: 556 | action = 'next'; 557 | break; 558 | case KEYS.pageUp: 559 | action = 'prev'; 560 | break; 561 | } 562 | if (action) { 563 | e.preventDefault(); 564 | this.handorgel.focus(action); 565 | } 566 | } 567 | }, { 568 | key: "_bindEvents", 569 | value: function _bindEvents() { 570 | this._listeners = { 571 | // button listeners 572 | bFocus: ['focus', this.button, this._handleFocus.bind(this)], 573 | bBlur: ['blur', this.button, this._handleBlur.bind(this)], 574 | bClick: ['click', this.button, this._handleButtonClick.bind(this)], 575 | bKeydown: ['keydown', this.button, this._handleButtonKeydown.bind(this)], 576 | // content listeners 577 | cKeydown: ['keydown', this.content, this._handleContentKeydown.bind(this)], 578 | cTransition: ['transitionend', this.content, this._handleContentTransitionEnd.bind(this)] 579 | }; 580 | for (var key in this._listeners) { 581 | if (this._listeners.hasOwnProperty(key)) { 582 | var listener = this._listeners[key]; 583 | listener[1].addEventListener(listener[0], listener[2]); 584 | } 585 | } 586 | } 587 | }, { 588 | key: "_unbindEvents", 589 | value: function _unbindEvents() { 590 | for (var key in this._listeners) { 591 | if (this._listeners.hasOwnProperty(key)) { 592 | var listener = this._listeners[key]; 593 | listener[1].removeEventListener(listener[0], listener[2]); 594 | } 595 | } 596 | } 597 | }]); 598 | return HandorgelFold; 599 | }(); 600 | 601 | var ID_COUNTER$1 = 0; 602 | var Handorgel = /*#__PURE__*/function (_EventEmitter) { 603 | _inherits(Handorgel, _EventEmitter); 604 | var _super = _createSuper(Handorgel); 605 | function Handorgel(element) { 606 | var _this; 607 | var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; 608 | _classCallCheck(this, Handorgel); 609 | _this = _super.call(this); 610 | if (element.handorgel) { 611 | return _possibleConstructorReturn(_this); 612 | } 613 | _this.element = element; 614 | _this.element.handorgel = _assertThisInitialized(_this); 615 | _this.id = "handorgel".concat(++ID_COUNTER$1); 616 | _this.element.setAttribute('id', _this.id); 617 | _this.folds = []; 618 | _this.options = Object.assign({}, Handorgel.defaultOptions, options); 619 | _this._listeners = {}; 620 | _this._bindEvents(); 621 | _this._initAria(); 622 | _this.update(); 623 | return _this; 624 | } 625 | _createClass(Handorgel, [{ 626 | key: "update", 627 | value: function update() { 628 | this.folds = []; 629 | var headerElements = typeof this.options.headerElements === 'string' ? this.element.querySelectorAll(this.options.headerElements) : this.options.headerElements; 630 | var contentElements = typeof this.options.contentElements === 'string' ? this.element.querySelectorAll(this.options.contentElements) : this.options.contentElements; 631 | for (var i = 0; i < headerElements.length; i = i + 1) { 632 | // get fold instance if there is already one 633 | var fold = headerElements[i].handorgelFold; 634 | 635 | // create new one when header and content exist 636 | if (!fold && headerElements[i] && contentElements[i]) { 637 | fold = new HandorgelFold(this, headerElements[i], contentElements[i]); 638 | } 639 | if (fold) { 640 | this.folds.push(fold); 641 | } 642 | } 643 | } 644 | }, { 645 | key: "focus", 646 | value: function focus(target) { 647 | var foldsLength = this.folds.length; 648 | var currentFocusedIndex = null; 649 | for (var i = 0; i < foldsLength && currentFocusedIndex === null; i++) { 650 | if (this.folds[i].focused) currentFocusedIndex = i; 651 | } 652 | if ((target === 'prev' || target === 'next') && currentFocusedIndex === null) { 653 | target = target === 'prev' ? 'last' : 'first'; 654 | } 655 | if (target === 'prev' && currentFocusedIndex === 0) { 656 | if (!this.options.carouselFocus) return; 657 | target = 'last'; 658 | } 659 | if (target === 'next' && currentFocusedIndex === foldsLength - 1) { 660 | if (!this.options.carouselFocus) return; 661 | target = 'first'; 662 | } 663 | switch (target) { 664 | case 'prev': 665 | this.folds[--currentFocusedIndex].focus(); 666 | break; 667 | case 'next': 668 | this.folds[++currentFocusedIndex].focus(); 669 | break; 670 | case 'last': 671 | this.folds[foldsLength - 1].focus(); 672 | break; 673 | case 'first': 674 | default: 675 | this.folds[0].focus(); 676 | } 677 | } 678 | }, { 679 | key: "destroy", 680 | value: function destroy() { 681 | this.emitEvent('destroy'); 682 | this.element.removeAttribute('id'); 683 | this.folds.forEach(function (fold) { 684 | fold.destroy(); 685 | }); 686 | this._unbindEvents(); 687 | this._cleanAria(); 688 | 689 | // clean reference to handorgel instance 690 | this.element.handorgel = null; 691 | this.emitEvent('destroyed'); 692 | } 693 | }, { 694 | key: "_handleFoldOpen", 695 | value: function _handleFoldOpen(openFold) { 696 | if (this.options.multiSelectable) { 697 | return; 698 | } 699 | this.folds.forEach(function (fold) { 700 | if (openFold !== fold) { 701 | fold.close(); 702 | } 703 | }); 704 | } 705 | }, { 706 | key: "_initAria", 707 | value: function _initAria() { 708 | if (!this.options.ariaEnabled) { 709 | return; 710 | } 711 | if (this.options.multiSelectable) { 712 | this.element.setAttribute('aria-multiselectable', 'true'); 713 | } 714 | } 715 | }, { 716 | key: "_cleanAria", 717 | value: function _cleanAria() { 718 | this.element.removeAttribute('aria-multiselectable'); 719 | } 720 | }, { 721 | key: "_bindEvents", 722 | value: function _bindEvents() { 723 | this._listeners.foldOpen = this._handleFoldOpen.bind(this); 724 | this.on('fold:open', this._listeners.foldOpen); 725 | } 726 | }, { 727 | key: "_unbindEvents", 728 | value: function _unbindEvents() { 729 | this.off('fold:open', this._listeners.foldOpen); 730 | } 731 | }]); 732 | return Handorgel; 733 | }(evEmitter); 734 | Handorgel.defaultOptions = { 735 | keyboardInteraction: true, 736 | multiSelectable: true, 737 | ariaEnabled: true, 738 | collapsible: true, 739 | carouselFocus: true, 740 | initialOpenAttribute: 'data-open', 741 | initialOpenTransition: true, 742 | initialOpenTransitionDelay: 200, 743 | headerElements: '.handorgel__header', 744 | contentElements: '.handorgel__content', 745 | headerOpenClass: 'handorgel__header--open', 746 | contentOpenClass: 'handorgel__content--open', 747 | headerOpenedClass: 'handorgel__header--opened', 748 | contentOpenedClass: 'handorgel__content--opened', 749 | headerDisabledClass: 'handorgel__header--disabled', 750 | contentDisabledClass: 'handorgel__content--disabled', 751 | headerFocusClass: 'handorgel__header--focus', 752 | contentFocusClass: 'handorgel__content--focus' 753 | }; 754 | 755 | window.accordion = new Handorgel(document.querySelector('.default')); 756 | window.accordion2 = new Handorgel(document.querySelector('.single-select'), { 757 | multiSelectable: false 758 | }); 759 | window.accordion3 = new Handorgel(document.querySelector('.single-select-not-collapsible'), { 760 | multiSelectable: false, 761 | collapsible: false 762 | }); 763 | 764 | }()); 765 | -------------------------------------------------------------------------------- /docs/js/demo.js: -------------------------------------------------------------------------------- 1 | import Handorgel from '../../src/js' 2 | 3 | window.accordion = new Handorgel(document.querySelector('.default')) 4 | 5 | window.accordion2 = new Handorgel(document.querySelector('.single-select'), { 6 | multiSelectable: false 7 | }) 8 | 9 | window.accordion3 = new Handorgel(document.querySelector('.single-select-not-collapsible'), { 10 | multiSelectable: false, 11 | collapsible: false 12 | }) 13 | -------------------------------------------------------------------------------- /docs/scss/.css/.css/demo.css: -------------------------------------------------------------------------------- 1 | .handorgel { 2 | display: block; 3 | width: 100%; 4 | border: 1px solid #eee; 5 | border-top: none; } 6 | 7 | .handorgel__header { 8 | display: block; 9 | margin: 0; } 10 | 11 | .handorgel__header--open .handorgel__header__button { 12 | background-color: #eee; } 13 | 14 | .handorgel__header--focus .handorgel__header__button { 15 | background-color: #dfdfdf; 16 | outline: none; } 17 | 18 | .handorgel__header__button { 19 | display: block; 20 | width: 100%; 21 | padding: 20px 24px; 22 | margin: 0; 23 | border: none; 24 | border-top: 1px solid #eee; 25 | background-color: #fff; 26 | border-radius: 0; 27 | color: inherit; 28 | cursor: pointer; 29 | font-size: inherit; 30 | text-align: left; 31 | transition: background-color 0.2s ease; 32 | user-select: none; } 33 | 34 | .handorgel__header__button::-moz-focus-inner { 35 | border: 0; } 36 | 37 | .handorgel__content { 38 | display: none; 39 | overflow: hidden; 40 | height: 0; 41 | border-top: 1px solid #eee; 42 | background-color: #fff; 43 | transition: height 0.1s ease 0.1s; } 44 | 45 | .handorgel__content--open { 46 | display: block; 47 | transition: height 0.2s ease; } 48 | 49 | .handorgel__content--opened { 50 | overflow: visible; } 51 | 52 | .handorgel__content__inner { 53 | padding: 20px 24px; 54 | opacity: 0; 55 | transition: opacity 0.1s ease; } 56 | 57 | .handorgel__content--opened .handorgel__content__inner { 58 | opacity: 1; 59 | transition: opacity 0.3s ease; } 60 | 61 | *, 62 | *::after, 63 | *::before { 64 | box-sizing: inherit; } 65 | 66 | body { 67 | box-sizing: border-box; 68 | margin: 80px 60px; 69 | background-color: #202328; 70 | color: #dadada; 71 | font-family: 'Helvetica Neue', Helvetica, sans-serif; 72 | font-size: 16px; 73 | -moz-osx-font-smoothing: grayscale; 74 | -webkit-font-smoothing: antialiased; 75 | line-height: 1.4; 76 | text-size-adjust: 100%; } 77 | 78 | @media (max-width: 600px) { 79 | body { 80 | margin: 80px 40px; } } 81 | 82 | a { 83 | color: #dadada; } 84 | 85 | p { 86 | margin-top: 0; 87 | margin-bottom: 15px; } 88 | 89 | p:last-child { 90 | margin-bottom: 0; } 91 | 92 | .description { 93 | margin-bottom: 60px; 94 | text-align: center; } 95 | 96 | .wrapper { 97 | width: 80%; 98 | max-width: 700px; 99 | margin: 0 auto; } 100 | 101 | @media (max-width: 600px) { 102 | .wrapper { 103 | width: 100%; } } 104 | 105 | .wrapper .example { 106 | margin-bottom: 30px; } 107 | 108 | .usage { 109 | margin-top: 80px; } 110 | 111 | h1 { 112 | margin-top: 0; 113 | margin-bottom: 10px; 114 | font-size: 40px; 115 | text-align: center; } 116 | 117 | h2 { 118 | margin-top: 40px; 119 | margin-bottom: 10px; 120 | font-size: 28px; } 121 | 122 | h3 { 123 | margin-top: 20px; 124 | margin-bottom: 10px; 125 | font-size: 20px; } 126 | 127 | pre { 128 | margin: 0; 129 | margin-bottom: 30px; } 130 | 131 | .handorgel { 132 | margin-bottom: 80px; 133 | color: #333; } 134 | -------------------------------------------------------------------------------- /docs/scss/.css/demo.css: -------------------------------------------------------------------------------- 1 | .handorgel { 2 | display: block; 3 | width: 100%; 4 | border: 1px solid #eee; 5 | border-top: none; } 6 | 7 | .handorgel__header { 8 | display: block; 9 | margin: 0; } 10 | 11 | .handorgel__header--open .handorgel__header__button { 12 | background-color: #eee; } 13 | 14 | .handorgel__header--focus .handorgel__header__button { 15 | background-color: #dfdfdf; 16 | outline: none; } 17 | 18 | .handorgel__header__button { 19 | display: block; 20 | width: 100%; 21 | padding: 20px 24px; 22 | margin: 0; 23 | border: none; 24 | border-top: 1px solid #eee; 25 | background-color: #fff; 26 | border-radius: 0; 27 | color: inherit; 28 | cursor: pointer; 29 | font-size: inherit; 30 | text-align: left; 31 | transition: background-color 0.2s ease; 32 | user-select: none; } 33 | .handorgel__header__button::-moz-focus-inner { 34 | border: 0; } 35 | 36 | .handorgel__content { 37 | display: none; 38 | overflow: hidden; 39 | height: 0; 40 | border-top: 1px solid #eee; 41 | background-color: #fff; 42 | transition: height 0.1s ease 0.1s; } 43 | .handorgel__content--open { 44 | display: block; 45 | transition: height 0.2s ease; } 46 | .handorgel__content--opened { 47 | overflow: visible; } 48 | 49 | .handorgel__content__inner { 50 | padding: 20px 24px; 51 | opacity: 0; 52 | transition: opacity 0.1s ease; } 53 | 54 | .handorgel__content--opened .handorgel__content__inner { 55 | opacity: 1; 56 | transition: opacity 0.3s ease; } 57 | 58 | *, 59 | *::after, 60 | *::before { 61 | box-sizing: inherit; } 62 | 63 | body { 64 | box-sizing: border-box; 65 | margin: 80px 60px; 66 | background-color: #202328; 67 | color: #dadada; 68 | font-family: 'Helvetica Neue', Helvetica, sans-serif; 69 | font-size: 16px; 70 | -moz-osx-font-smoothing: grayscale; 71 | -webkit-font-smoothing: antialiased; 72 | line-height: 1.4; 73 | text-size-adjust: 100%; } 74 | @media (max-width: 600px) { 75 | body { 76 | margin: 80px 40px; } } 77 | 78 | a { 79 | color: #dadada; } 80 | 81 | p { 82 | margin-top: 0; 83 | margin-bottom: 15px; } 84 | p:last-child { 85 | margin-bottom: 0; } 86 | 87 | .description { 88 | margin-bottom: 60px; 89 | text-align: center; } 90 | 91 | .wrapper { 92 | width: 80%; 93 | max-width: 700px; 94 | margin: 0 auto; } 95 | @media (max-width: 600px) { 96 | .wrapper { 97 | width: 100%; } } 98 | 99 | .wrapper .example { 100 | margin-bottom: 30px; } 101 | 102 | .usage { 103 | margin-top: 80px; } 104 | 105 | h1 { 106 | margin-top: 0; 107 | margin-bottom: 10px; 108 | font-size: 40px; 109 | text-align: center; } 110 | 111 | h2 { 112 | margin-top: 40px; 113 | margin-bottom: 10px; 114 | font-size: 28px; } 115 | 116 | h3 { 117 | margin-top: 20px; 118 | margin-bottom: 10px; 119 | font-size: 20px; } 120 | 121 | pre { 122 | margin: 0; 123 | margin-bottom: 30px; } 124 | 125 | .handorgel { 126 | margin-bottom: 80px; 127 | color: #333; } 128 | -------------------------------------------------------------------------------- /docs/scss/demo.scss: -------------------------------------------------------------------------------- 1 | @import 'src/scss/style'; 2 | 3 | *, 4 | *::after, 5 | *::before { 6 | box-sizing: inherit; 7 | } 8 | 9 | body { 10 | box-sizing: border-box; 11 | margin: 80px 60px; 12 | background-color: #202328; 13 | color: #dadada; 14 | font-family: 'Helvetica Neue', Helvetica, sans-serif; 15 | font-size: 16px; 16 | -moz-osx-font-smoothing: grayscale; 17 | -webkit-font-smoothing: antialiased; 18 | line-height: 1.4; 19 | text-size-adjust: 100%; 20 | 21 | @media (max-width: 600px) { 22 | margin: 80px 40px; 23 | } 24 | } 25 | 26 | a { 27 | color: #dadada; 28 | } 29 | 30 | p { 31 | margin-top: 0; 32 | margin-bottom: 15px; 33 | 34 | &:last-child { 35 | margin-bottom: 0; 36 | } 37 | } 38 | 39 | .description { 40 | margin-bottom: 60px; 41 | text-align: center; 42 | } 43 | 44 | .wrapper { 45 | width: 80%; 46 | max-width: 700px; 47 | margin: 0 auto; 48 | 49 | @media (max-width: 600px) { 50 | width: 100%; 51 | } 52 | } 53 | 54 | .wrapper .example { 55 | margin-bottom: 30px; 56 | } 57 | 58 | .usage { 59 | margin-top: 80px; 60 | } 61 | 62 | h1 { 63 | margin-top: 0; 64 | margin-bottom: 10px; 65 | font-size: 40px; 66 | text-align: center; 67 | } 68 | 69 | h2 { 70 | margin-top: 40px; 71 | margin-bottom: 10px; 72 | font-size: 28px; 73 | } 74 | 75 | h3 { 76 | margin-top: 20px; 77 | margin-bottom: 10px; 78 | font-size: 20px; 79 | } 80 | 81 | pre { 82 | margin: 0; 83 | margin-bottom: 30px; 84 | } 85 | 86 | .handorgel { 87 | margin-bottom: 80px; 88 | color: #333; 89 | } 90 | -------------------------------------------------------------------------------- /lib/css/handorgel.css: -------------------------------------------------------------------------------- 1 | .handorgel { 2 | display: block; 3 | width: 100%; 4 | border: 1px solid #eee; 5 | border-top: none; 6 | } 7 | .handorgel__header { 8 | display: block; 9 | margin: 0; 10 | } 11 | .handorgel__header--open .handorgel__header__button { 12 | background-color: #eee; 13 | } 14 | .handorgel__header--focus .handorgel__header__button { 15 | background-color: #dfdfdf; 16 | outline: none; 17 | } 18 | .handorgel__header__button { 19 | display: block; 20 | width: 100%; 21 | padding: 20px 24px; 22 | margin: 0; 23 | border: none; 24 | border-top: 1px solid #eee; 25 | background-color: #fff; 26 | border-radius: 0; 27 | color: inherit; 28 | cursor: pointer; 29 | font-size: inherit; 30 | text-align: left; 31 | -webkit-transition: background-color 0.2s ease; 32 | transition: background-color 0.2s ease; 33 | -webkit-user-select: none; 34 | -moz-user-select: none; 35 | -ms-user-select: none; 36 | user-select: none; 37 | } 38 | .handorgel__header__button::-moz-focus-inner { 39 | border: 0; 40 | } 41 | .handorgel__content { 42 | display: none; 43 | overflow: hidden; 44 | height: 0; 45 | border-top: 1px solid #eee; 46 | background-color: #fff; 47 | -webkit-transition: height 0.1s ease 0.1s; 48 | transition: height 0.1s ease 0.1s; 49 | } 50 | .handorgel__content--open { 51 | display: block; 52 | -webkit-transition: height 0.2s ease; 53 | transition: height 0.2s ease; 54 | } 55 | .handorgel__content--opened { 56 | overflow: visible; 57 | } 58 | .handorgel__content__inner { 59 | padding: 20px 24px; 60 | opacity: 0; 61 | -webkit-transition: opacity 0.1s ease; 62 | transition: opacity 0.1s ease; 63 | } 64 | .handorgel__content--opened .handorgel__content__inner { 65 | opacity: 1; 66 | -webkit-transition: opacity 0.3s ease; 67 | transition: opacity 0.3s ease; 68 | } 69 | -------------------------------------------------------------------------------- /lib/css/handorgel.min.css: -------------------------------------------------------------------------------- 1 | .handorgel{display:block;width:100%;border:1px solid #eee;border-top:none}.handorgel__header{display:block;margin:0}.handorgel__header--open .handorgel__header__button{background-color:#eee}.handorgel__header--focus .handorgel__header__button{background-color:#dfdfdf;outline:none}.handorgel__header__button{display:block;width:100%;padding:20px 24px;margin:0;border:none;border-top:1px solid #eee;background-color:#fff;border-radius:0;color:inherit;cursor:pointer;font-size:inherit;text-align:left;-webkit-transition:background-color .2s ease;transition:background-color .2s ease;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.handorgel__header__button::-moz-focus-inner{border:0}.handorgel__content{display:none;overflow:hidden;height:0;border-top:1px solid #eee;background-color:#fff;-webkit-transition:height .1s ease .1s;transition:height .1s ease .1s}.handorgel__content--open{display:block;-webkit-transition:height .2s ease;transition:height .2s ease}.handorgel__content--opened{overflow:visible}.handorgel__content__inner{padding:20px 24px;opacity:0;-webkit-transition:opacity .1s ease;transition:opacity .1s ease}.handorgel__content--opened .handorgel__content__inner{opacity:1;-webkit-transition:opacity .3s ease;transition:opacity .3s ease} -------------------------------------------------------------------------------- /lib/js/esm/handorgel.js: -------------------------------------------------------------------------------- 1 | /** handorgel v1.0.0, @license MIT */ 2 | function _classCallCheck(instance, Constructor) { 3 | if (!(instance instanceof Constructor)) { 4 | throw new TypeError("Cannot call a class as a function"); 5 | } 6 | } 7 | function _defineProperties(target, props) { 8 | for (var i = 0; i < props.length; i++) { 9 | var descriptor = props[i]; 10 | descriptor.enumerable = descriptor.enumerable || false; 11 | descriptor.configurable = true; 12 | if ("value" in descriptor) descriptor.writable = true; 13 | Object.defineProperty(target, _toPropertyKey(descriptor.key), descriptor); 14 | } 15 | } 16 | function _createClass(Constructor, protoProps, staticProps) { 17 | if (protoProps) _defineProperties(Constructor.prototype, protoProps); 18 | if (staticProps) _defineProperties(Constructor, staticProps); 19 | Object.defineProperty(Constructor, "prototype", { 20 | writable: false 21 | }); 22 | return Constructor; 23 | } 24 | function _inherits(subClass, superClass) { 25 | if (typeof superClass !== "function" && superClass !== null) { 26 | throw new TypeError("Super expression must either be null or a function"); 27 | } 28 | subClass.prototype = Object.create(superClass && superClass.prototype, { 29 | constructor: { 30 | value: subClass, 31 | writable: true, 32 | configurable: true 33 | } 34 | }); 35 | Object.defineProperty(subClass, "prototype", { 36 | writable: false 37 | }); 38 | if (superClass) _setPrototypeOf(subClass, superClass); 39 | } 40 | function _getPrototypeOf(o) { 41 | _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf.bind() : function _getPrototypeOf(o) { 42 | return o.__proto__ || Object.getPrototypeOf(o); 43 | }; 44 | return _getPrototypeOf(o); 45 | } 46 | function _setPrototypeOf(o, p) { 47 | _setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function _setPrototypeOf(o, p) { 48 | o.__proto__ = p; 49 | return o; 50 | }; 51 | return _setPrototypeOf(o, p); 52 | } 53 | function _isNativeReflectConstruct() { 54 | if (typeof Reflect === "undefined" || !Reflect.construct) return false; 55 | if (Reflect.construct.sham) return false; 56 | if (typeof Proxy === "function") return true; 57 | try { 58 | Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); 59 | return true; 60 | } catch (e) { 61 | return false; 62 | } 63 | } 64 | function _assertThisInitialized(self) { 65 | if (self === void 0) { 66 | throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); 67 | } 68 | return self; 69 | } 70 | function _possibleConstructorReturn(self, call) { 71 | if (call && (typeof call === "object" || typeof call === "function")) { 72 | return call; 73 | } else if (call !== void 0) { 74 | throw new TypeError("Derived constructors may only return object or undefined"); 75 | } 76 | return _assertThisInitialized(self); 77 | } 78 | function _createSuper(Derived) { 79 | var hasNativeReflectConstruct = _isNativeReflectConstruct(); 80 | return function _createSuperInternal() { 81 | var Super = _getPrototypeOf(Derived), 82 | result; 83 | if (hasNativeReflectConstruct) { 84 | var NewTarget = _getPrototypeOf(this).constructor; 85 | result = Reflect.construct(Super, arguments, NewTarget); 86 | } else { 87 | result = Super.apply(this, arguments); 88 | } 89 | return _possibleConstructorReturn(this, result); 90 | }; 91 | } 92 | function _toPrimitive(input, hint) { 93 | if (typeof input !== "object" || input === null) return input; 94 | var prim = input[Symbol.toPrimitive]; 95 | if (prim !== undefined) { 96 | var res = prim.call(input, hint || "default"); 97 | if (typeof res !== "object") return res; 98 | throw new TypeError("@@toPrimitive must return a primitive value."); 99 | } 100 | return (hint === "string" ? String : Number)(input); 101 | } 102 | function _toPropertyKey(arg) { 103 | var key = _toPrimitive(arg, "string"); 104 | return typeof key === "symbol" ? key : String(key); 105 | } 106 | 107 | var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {}; 108 | 109 | function createCommonjsModule(fn, module) { 110 | return module = { exports: {} }, fn(module, module.exports), module.exports; 111 | } 112 | 113 | var evEmitter = createCommonjsModule(function (module) { 114 | /** 115 | * EvEmitter v1.1.0 116 | * Lil' event emitter 117 | * MIT License 118 | */ 119 | 120 | /* jshint unused: true, undef: true, strict: true */ 121 | 122 | ( function( global, factory ) { 123 | // universal module definition 124 | /* jshint strict: false */ /* globals define, module, window */ 125 | if ( module.exports ) { 126 | // CommonJS - Browserify, Webpack 127 | module.exports = factory(); 128 | } else { 129 | // Browser globals 130 | global.EvEmitter = factory(); 131 | } 132 | 133 | }( typeof window != 'undefined' ? window : commonjsGlobal, function() { 134 | 135 | function EvEmitter() {} 136 | 137 | var proto = EvEmitter.prototype; 138 | 139 | proto.on = function( eventName, listener ) { 140 | if ( !eventName || !listener ) { 141 | return; 142 | } 143 | // set events hash 144 | var events = this._events = this._events || {}; 145 | // set listeners array 146 | var listeners = events[ eventName ] = events[ eventName ] || []; 147 | // only add once 148 | if ( listeners.indexOf( listener ) == -1 ) { 149 | listeners.push( listener ); 150 | } 151 | 152 | return this; 153 | }; 154 | 155 | proto.once = function( eventName, listener ) { 156 | if ( !eventName || !listener ) { 157 | return; 158 | } 159 | // add event 160 | this.on( eventName, listener ); 161 | // set once flag 162 | // set onceEvents hash 163 | var onceEvents = this._onceEvents = this._onceEvents || {}; 164 | // set onceListeners object 165 | var onceListeners = onceEvents[ eventName ] = onceEvents[ eventName ] || {}; 166 | // set flag 167 | onceListeners[ listener ] = true; 168 | 169 | return this; 170 | }; 171 | 172 | proto.off = function( eventName, listener ) { 173 | var listeners = this._events && this._events[ eventName ]; 174 | if ( !listeners || !listeners.length ) { 175 | return; 176 | } 177 | var index = listeners.indexOf( listener ); 178 | if ( index != -1 ) { 179 | listeners.splice( index, 1 ); 180 | } 181 | 182 | return this; 183 | }; 184 | 185 | proto.emitEvent = function( eventName, args ) { 186 | var listeners = this._events && this._events[ eventName ]; 187 | if ( !listeners || !listeners.length ) { 188 | return; 189 | } 190 | // copy over to avoid interference if .off() in listener 191 | listeners = listeners.slice(0); 192 | args = args || []; 193 | // once stuff 194 | var onceListeners = this._onceEvents && this._onceEvents[ eventName ]; 195 | 196 | for ( var i=0; i < listeners.length; i++ ) { 197 | var listener = listeners[i]; 198 | var isOnce = onceListeners && onceListeners[ listener ]; 199 | if ( isOnce ) { 200 | // remove listener 201 | // remove before trigger to prevent recursion 202 | this.off( eventName, listener ); 203 | // unset once flag 204 | delete onceListeners[ listener ]; 205 | } 206 | // trigger listener 207 | listener.apply( this, args ); 208 | } 209 | 210 | return this; 211 | }; 212 | 213 | proto.allOff = function() { 214 | delete this._events; 215 | delete this._onceEvents; 216 | }; 217 | 218 | return EvEmitter; 219 | 220 | })); 221 | }); 222 | 223 | var ID_COUNTER = {}; 224 | var ARIA_ATTRIBUTES = { 225 | button: { 226 | 'aria-controls': function ariaControls() { 227 | return this.id + '-content'; 228 | }, 229 | 'aria-expanded': function ariaExpanded() { 230 | return this.expanded ? 'true' : 'false'; 231 | }, 232 | 'aria-disabled': function ariaDisabled() { 233 | return this.disabled ? 'true' : 'false'; 234 | } 235 | }, 236 | content: { 237 | role: function role() { 238 | return 'region'; 239 | }, 240 | 'aria-labelledby': function ariaLabelledby() { 241 | return this.id + '-header'; 242 | } 243 | } 244 | }; 245 | var KEYS = { 246 | arrowDown: 40, 247 | arrowUp: 38, 248 | pageUp: 33, 249 | pageDown: 34, 250 | end: 35, 251 | home: 36 252 | }; 253 | var HandorgelFold = /*#__PURE__*/function () { 254 | function HandorgelFold(handorgel, header, content) { 255 | _classCallCheck(this, HandorgelFold); 256 | if (header.handorgelFold) { 257 | return; 258 | } 259 | this.handorgel = handorgel; 260 | this.header = header; 261 | this.button = header.firstElementChild; 262 | this.content = content; 263 | this.header.handorgelFold = this; 264 | this.content.handorgelFold = this; 265 | if (!ID_COUNTER[this.handorgel.id]) { 266 | ID_COUNTER[this.handorgel.id] = 0; 267 | } 268 | this.id = "".concat(this.handorgel.id, "-fold").concat(++ID_COUNTER[this.handorgel.id]); 269 | this.header.setAttribute('id', this.id + '-header'); 270 | this.content.setAttribute('id', this.id + '-content'); 271 | this.focused = false; 272 | this.expanded = false; 273 | this.disabled = false; 274 | this._listeners = {}; 275 | this._bindEvents(); 276 | this._initAria(); 277 | this._initialOpen(); 278 | this._initialFocus(); 279 | } 280 | _createClass(HandorgelFold, [{ 281 | key: "open", 282 | value: function open() { 283 | var transition = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true; 284 | if (this.expanded) { 285 | return; 286 | } 287 | this.handorgel.emitEvent('fold:open', [this]); 288 | this.expanded = true; 289 | if (!this.handorgel.options.collapsible) { 290 | this.disable(); 291 | } 292 | this._updateAria('button', 'aria-expanded'); 293 | this.header.classList.add(this.handorgel.options.headerOpenClass); 294 | this.content.classList.add(this.handorgel.options.contentOpenClass); 295 | if (!transition) { 296 | this._opened(); 297 | } else { 298 | var height = this.content.firstElementChild.offsetHeight; 299 | this.content.style.height = "".concat(height, "px"); 300 | } 301 | } 302 | }, { 303 | key: "close", 304 | value: function close() { 305 | var _this = this; 306 | var transition = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true; 307 | if (!this.expanded) { 308 | return; 309 | } 310 | this.handorgel.emitEvent('fold:close', [this]); 311 | this.expanded = false; 312 | if (!this.handorgel.options.collapsible) { 313 | this.enable(); 314 | } 315 | this._updateAria('button', 'aria-expanded'); 316 | this.header.classList.remove(this.handorgel.options.headerOpenedClass); 317 | this.content.classList.remove(this.handorgel.options.contentOpenedClass); 318 | if (!transition) { 319 | this._closed(); 320 | } else { 321 | // if we want to transition when closing we 322 | // have to set the current height and replace auto 323 | var height = this.content.firstElementChild.offsetHeight; 324 | this.content.style.height = "".concat(height, "px"); 325 | window.requestAnimationFrame(function () { 326 | window.requestAnimationFrame(function () { 327 | _this.content.style.height = '0px'; 328 | }); 329 | }); 330 | } 331 | } 332 | }, { 333 | key: "disable", 334 | value: function disable() { 335 | this.disabled = true; 336 | this._updateAria('button', 'aria-disabled'); 337 | this.header.classList.add(this.handorgel.options.headerDisabledClass); 338 | this.content.classList.add(this.handorgel.options.contentDisabledClass); 339 | } 340 | }, { 341 | key: "enable", 342 | value: function enable() { 343 | this.disabled = false; 344 | this._updateAria('button', 'aria-disabled'); 345 | this.header.classList.remove(this.handorgel.options.headerDisabledClass); 346 | this.content.classList.remove(this.handorgel.options.contentDisabledClass); 347 | } 348 | }, { 349 | key: "focus", 350 | value: function focus() { 351 | this.button.focus(); 352 | } 353 | }, { 354 | key: "blur", 355 | value: function blur() { 356 | this.button.blur(); 357 | } 358 | }, { 359 | key: "toggle", 360 | value: function toggle() { 361 | var transition = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true; 362 | if (this.expanded) { 363 | this.close(transition); 364 | } else { 365 | this.open(transition); 366 | } 367 | } 368 | }, { 369 | key: "destroy", 370 | value: function destroy() { 371 | this._unbindEvents(); 372 | this._cleanAria(); 373 | 374 | // clean classes 375 | this.header.classList.remove(this.handorgel.options.headerOpenClass); 376 | this.header.classList.remove(this.handorgel.options.headerOpenedClass); 377 | this.header.classList.remove(this.handorgel.options.headerFocusClass); 378 | this.content.classList.remove(this.handorgel.options.contentOpenClass); 379 | this.content.classList.remove(this.handorgel.options.contentOpenedClass); 380 | this.content.classList.remove(this.handorgel.options.contentFocusClass); 381 | 382 | // hide content 383 | this.content.style.height = '0px'; 384 | 385 | // clean reference to this instance 386 | this.header.handorgelFold = null; 387 | this.content.handorgelFold = null; 388 | 389 | // remove ids 390 | this.header.removeAttribute('id'); 391 | this.content.removeAttribute('id'); 392 | 393 | // clean reference to handorgel instance 394 | this.handorgel = null; 395 | } 396 | }, { 397 | key: "_opened", 398 | value: function _opened() { 399 | this.content.style.height = 'auto'; 400 | this.header.classList.add(this.handorgel.options.headerOpenedClass); 401 | this.content.classList.add(this.handorgel.options.contentOpenedClass); 402 | this.handorgel.emitEvent('fold:opened', [this]); 403 | } 404 | }, { 405 | key: "_closed", 406 | value: function _closed() { 407 | this.header.classList.remove(this.handorgel.options.headerOpenClass); 408 | this.content.classList.remove(this.handorgel.options.contentOpenClass); 409 | this.handorgel.emitEvent('fold:closed', [this]); 410 | } 411 | }, { 412 | key: "_initialOpen", 413 | value: function _initialOpen() { 414 | var _this2 = this; 415 | if (this.header.getAttribute(this.handorgel.options.initialOpenAttribute) !== null || this.content.getAttribute(this.handorgel.options.initialOpenAttribute) !== null) { 416 | if (this.handorgel.options.initialOpenTransition) { 417 | window.setTimeout(function () { 418 | _this2.open(); 419 | }, this.handorgel.options.initialOpenTransitionDelay); 420 | } else { 421 | this.open(false); 422 | } 423 | } 424 | } 425 | }, { 426 | key: "_initialFocus", 427 | value: function _initialFocus() { 428 | if (this.button.getAttribute('autofocus') === null) { 429 | return; 430 | } 431 | 432 | // to ensure focus styles if autofocus was applied 433 | // before focus listener was added 434 | this._handleFocus(); 435 | } 436 | }, { 437 | key: "_initAria", 438 | value: function _initAria() { 439 | this._updateAria('button'); 440 | this._updateAria('content'); 441 | } 442 | }, { 443 | key: "_cleanAria", 444 | value: function _cleanAria() { 445 | this._updateAria('button', null, true); 446 | this._updateAria('content', null, true); 447 | } 448 | }, { 449 | key: "_updateAria", 450 | value: function _updateAria(element) { 451 | var property = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null; 452 | var remove = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false; 453 | if (!this.handorgel.options.ariaEnabled) { 454 | return; 455 | } 456 | if (property) { 457 | var newValue = ARIA_ATTRIBUTES[element][property].call(this); 458 | this[element].setAttribute(property, newValue); 459 | } else { 460 | for (var _property in ARIA_ATTRIBUTES[element]) { 461 | if (ARIA_ATTRIBUTES[element].hasOwnProperty(_property)) { 462 | if (remove) { 463 | this[element].removeAttribute(_property); 464 | } else { 465 | var _newValue = ARIA_ATTRIBUTES[element][_property].call(this); 466 | this[element].setAttribute(_property, _newValue); 467 | } 468 | } 469 | } 470 | } 471 | } 472 | }, { 473 | key: "_handleContentTransitionEnd", 474 | value: function _handleContentTransitionEnd(e) { 475 | if (e.target === e.currentTarget && e.propertyName === 'height') { 476 | if (this.expanded) { 477 | this._opened(); 478 | } else { 479 | this._closed(); 480 | } 481 | } 482 | } 483 | }, { 484 | key: "_handleFocus", 485 | value: function _handleFocus() { 486 | this.focused = true; 487 | this.header.classList.add(this.handorgel.options.headerFocusClass); 488 | this.content.classList.add(this.handorgel.options.contentFocusClass); 489 | this.handorgel.emitEvent('fold:focus', [this]); 490 | } 491 | }, { 492 | key: "_handleBlur", 493 | value: function _handleBlur() { 494 | this.focused = false; 495 | this.header.classList.remove(this.handorgel.options.headerFocusClass); 496 | this.content.classList.remove(this.handorgel.options.contentFocusClass); 497 | this.handorgel.emitEvent('fold:blur', [this]); 498 | } 499 | }, { 500 | key: "_handleButtonClick", 501 | value: function _handleButtonClick(e) { 502 | // ensure focus is on button (click is not seting focus on firefox mac) 503 | this.focus(); 504 | if (this.disabled) { 505 | return; 506 | } 507 | this.toggle(); 508 | } 509 | }, { 510 | key: "_handleButtonKeydown", 511 | value: function _handleButtonKeydown(e) { 512 | if (!this.handorgel.options.keyboardInteraction) { 513 | return; 514 | } 515 | var action = null; 516 | switch (e.which) { 517 | case KEYS.arrowDown: 518 | action = 'next'; 519 | break; 520 | case KEYS.arrowUp: 521 | action = 'prev'; 522 | break; 523 | case KEYS.home: 524 | action = 'first'; 525 | break; 526 | case KEYS.end: 527 | action = 'last'; 528 | break; 529 | case KEYS.pageDown: 530 | if (e.ctrlKey) { 531 | action = 'next'; 532 | } 533 | break; 534 | case KEYS.pageUp: 535 | if (e.ctrlKey) { 536 | action = 'prev'; 537 | } 538 | break; 539 | } 540 | if (action) { 541 | e.preventDefault(); 542 | this.handorgel.focus(action); 543 | } 544 | } 545 | }, { 546 | key: "_handleContentKeydown", 547 | value: function _handleContentKeydown(e) { 548 | if (!this.handorgel.options.keyboardInteraction || !e.ctrlKey) { 549 | return; 550 | } 551 | var action = null; 552 | switch (e.which) { 553 | case KEYS.pageDown: 554 | action = 'next'; 555 | break; 556 | case KEYS.pageUp: 557 | action = 'prev'; 558 | break; 559 | } 560 | if (action) { 561 | e.preventDefault(); 562 | this.handorgel.focus(action); 563 | } 564 | } 565 | }, { 566 | key: "_bindEvents", 567 | value: function _bindEvents() { 568 | this._listeners = { 569 | // button listeners 570 | bFocus: ['focus', this.button, this._handleFocus.bind(this)], 571 | bBlur: ['blur', this.button, this._handleBlur.bind(this)], 572 | bClick: ['click', this.button, this._handleButtonClick.bind(this)], 573 | bKeydown: ['keydown', this.button, this._handleButtonKeydown.bind(this)], 574 | // content listeners 575 | cKeydown: ['keydown', this.content, this._handleContentKeydown.bind(this)], 576 | cTransition: ['transitionend', this.content, this._handleContentTransitionEnd.bind(this)] 577 | }; 578 | for (var key in this._listeners) { 579 | if (this._listeners.hasOwnProperty(key)) { 580 | var listener = this._listeners[key]; 581 | listener[1].addEventListener(listener[0], listener[2]); 582 | } 583 | } 584 | } 585 | }, { 586 | key: "_unbindEvents", 587 | value: function _unbindEvents() { 588 | for (var key in this._listeners) { 589 | if (this._listeners.hasOwnProperty(key)) { 590 | var listener = this._listeners[key]; 591 | listener[1].removeEventListener(listener[0], listener[2]); 592 | } 593 | } 594 | } 595 | }]); 596 | return HandorgelFold; 597 | }(); 598 | 599 | var ID_COUNTER$1 = 0; 600 | var Handorgel = /*#__PURE__*/function (_EventEmitter) { 601 | _inherits(Handorgel, _EventEmitter); 602 | var _super = _createSuper(Handorgel); 603 | function Handorgel(element) { 604 | var _this; 605 | var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; 606 | _classCallCheck(this, Handorgel); 607 | _this = _super.call(this); 608 | if (element.handorgel) { 609 | return _possibleConstructorReturn(_this); 610 | } 611 | _this.element = element; 612 | _this.element.handorgel = _assertThisInitialized(_this); 613 | _this.id = "handorgel".concat(++ID_COUNTER$1); 614 | _this.element.setAttribute('id', _this.id); 615 | _this.folds = []; 616 | _this.options = Object.assign({}, Handorgel.defaultOptions, options); 617 | _this._listeners = {}; 618 | _this._bindEvents(); 619 | _this._initAria(); 620 | _this.update(); 621 | return _this; 622 | } 623 | _createClass(Handorgel, [{ 624 | key: "update", 625 | value: function update() { 626 | this.folds = []; 627 | var headerElements = typeof this.options.headerElements === 'string' ? this.element.querySelectorAll(this.options.headerElements) : this.options.headerElements; 628 | var contentElements = typeof this.options.contentElements === 'string' ? this.element.querySelectorAll(this.options.contentElements) : this.options.contentElements; 629 | for (var i = 0; i < headerElements.length; i = i + 1) { 630 | // get fold instance if there is already one 631 | var fold = headerElements[i].handorgelFold; 632 | 633 | // create new one when header and content exist 634 | if (!fold && headerElements[i] && contentElements[i]) { 635 | fold = new HandorgelFold(this, headerElements[i], contentElements[i]); 636 | } 637 | if (fold) { 638 | this.folds.push(fold); 639 | } 640 | } 641 | } 642 | }, { 643 | key: "focus", 644 | value: function focus(target) { 645 | var foldsLength = this.folds.length; 646 | var currentFocusedIndex = null; 647 | for (var i = 0; i < foldsLength && currentFocusedIndex === null; i++) { 648 | if (this.folds[i].focused) currentFocusedIndex = i; 649 | } 650 | if ((target === 'prev' || target === 'next') && currentFocusedIndex === null) { 651 | target = target === 'prev' ? 'last' : 'first'; 652 | } 653 | if (target === 'prev' && currentFocusedIndex === 0) { 654 | if (!this.options.carouselFocus) return; 655 | target = 'last'; 656 | } 657 | if (target === 'next' && currentFocusedIndex === foldsLength - 1) { 658 | if (!this.options.carouselFocus) return; 659 | target = 'first'; 660 | } 661 | switch (target) { 662 | case 'prev': 663 | this.folds[--currentFocusedIndex].focus(); 664 | break; 665 | case 'next': 666 | this.folds[++currentFocusedIndex].focus(); 667 | break; 668 | case 'last': 669 | this.folds[foldsLength - 1].focus(); 670 | break; 671 | case 'first': 672 | default: 673 | this.folds[0].focus(); 674 | } 675 | } 676 | }, { 677 | key: "destroy", 678 | value: function destroy() { 679 | this.emitEvent('destroy'); 680 | this.element.removeAttribute('id'); 681 | this.folds.forEach(function (fold) { 682 | fold.destroy(); 683 | }); 684 | this._unbindEvents(); 685 | this._cleanAria(); 686 | 687 | // clean reference to handorgel instance 688 | this.element.handorgel = null; 689 | this.emitEvent('destroyed'); 690 | } 691 | }, { 692 | key: "_handleFoldOpen", 693 | value: function _handleFoldOpen(openFold) { 694 | if (this.options.multiSelectable) { 695 | return; 696 | } 697 | this.folds.forEach(function (fold) { 698 | if (openFold !== fold) { 699 | fold.close(); 700 | } 701 | }); 702 | } 703 | }, { 704 | key: "_initAria", 705 | value: function _initAria() { 706 | if (!this.options.ariaEnabled) { 707 | return; 708 | } 709 | if (this.options.multiSelectable) { 710 | this.element.setAttribute('aria-multiselectable', 'true'); 711 | } 712 | } 713 | }, { 714 | key: "_cleanAria", 715 | value: function _cleanAria() { 716 | this.element.removeAttribute('aria-multiselectable'); 717 | } 718 | }, { 719 | key: "_bindEvents", 720 | value: function _bindEvents() { 721 | this._listeners.foldOpen = this._handleFoldOpen.bind(this); 722 | this.on('fold:open', this._listeners.foldOpen); 723 | } 724 | }, { 725 | key: "_unbindEvents", 726 | value: function _unbindEvents() { 727 | this.off('fold:open', this._listeners.foldOpen); 728 | } 729 | }]); 730 | return Handorgel; 731 | }(evEmitter); 732 | Handorgel.defaultOptions = { 733 | keyboardInteraction: true, 734 | multiSelectable: true, 735 | ariaEnabled: true, 736 | collapsible: true, 737 | carouselFocus: true, 738 | initialOpenAttribute: 'data-open', 739 | initialOpenTransition: true, 740 | initialOpenTransitionDelay: 200, 741 | headerElements: '.handorgel__header', 742 | contentElements: '.handorgel__content', 743 | headerOpenClass: 'handorgel__header--open', 744 | contentOpenClass: 'handorgel__content--open', 745 | headerOpenedClass: 'handorgel__header--opened', 746 | contentOpenedClass: 'handorgel__content--opened', 747 | headerDisabledClass: 'handorgel__header--disabled', 748 | contentDisabledClass: 'handorgel__content--disabled', 749 | headerFocusClass: 'handorgel__header--focus', 750 | contentFocusClass: 'handorgel__content--focus' 751 | }; 752 | 753 | export default Handorgel; 754 | -------------------------------------------------------------------------------- /lib/js/esm/handorgel.min.js: -------------------------------------------------------------------------------- 1 | /** handorgel v1.0.0, @license MIT */ 2 | function t(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}function e(t,e){for(var n=0;n0&&void 0!==arguments[0])||arguments[0];if(!this.expanded)if(this.handorgel.emitEvent("fold:open",[this]),this.expanded=!0,this.handorgel.options.collapsible||this.disable(),this._updateAria("button","aria-expanded"),this.header.classList.add(this.handorgel.options.headerOpenClass),this.content.classList.add(this.handorgel.options.contentOpenClass),t){var e=this.content.firstElementChild.offsetHeight;this.content.style.height="".concat(e,"px")}else this._opened()}},{key:"close",value:function(){var t=this,e=!(arguments.length>0&&void 0!==arguments[0])||arguments[0];if(this.expanded)if(this.handorgel.emitEvent("fold:close",[this]),this.expanded=!1,this.handorgel.options.collapsible||this.enable(),this._updateAria("button","aria-expanded"),this.header.classList.remove(this.handorgel.options.headerOpenedClass),this.content.classList.remove(this.handorgel.options.contentOpenedClass),e){var n=this.content.firstElementChild.offsetHeight;this.content.style.height="".concat(n,"px"),window.requestAnimationFrame(function(){window.requestAnimationFrame(function(){t.content.style.height="0px"})})}else this._closed()}},{key:"disable",value:function(){this.disabled=!0,this._updateAria("button","aria-disabled"),this.header.classList.add(this.handorgel.options.headerDisabledClass),this.content.classList.add(this.handorgel.options.contentDisabledClass)}},{key:"enable",value:function(){this.disabled=!1,this._updateAria("button","aria-disabled"),this.header.classList.remove(this.handorgel.options.headerDisabledClass),this.content.classList.remove(this.handorgel.options.contentDisabledClass)}},{key:"focus",value:function(){this.button.focus()}},{key:"blur",value:function(){this.button.blur()}},{key:"toggle",value:function(){var t=!(arguments.length>0&&void 0!==arguments[0])||arguments[0];this.expanded?this.close(t):this.open(t)}},{key:"destroy",value:function(){this._unbindEvents(),this._cleanAria(),this.header.classList.remove(this.handorgel.options.headerOpenClass),this.header.classList.remove(this.handorgel.options.headerOpenedClass),this.header.classList.remove(this.handorgel.options.headerFocusClass),this.content.classList.remove(this.handorgel.options.contentOpenClass),this.content.classList.remove(this.handorgel.options.contentOpenedClass),this.content.classList.remove(this.handorgel.options.contentFocusClass),this.content.style.height="0px",this.header.handorgelFold=null,this.content.handorgelFold=null,this.header.removeAttribute("id"),this.content.removeAttribute("id"),this.handorgel=null}},{key:"_opened",value:function(){this.content.style.height="auto",this.header.classList.add(this.handorgel.options.headerOpenedClass),this.content.classList.add(this.handorgel.options.contentOpenedClass),this.handorgel.emitEvent("fold:opened",[this])}},{key:"_closed",value:function(){this.header.classList.remove(this.handorgel.options.headerOpenClass),this.content.classList.remove(this.handorgel.options.contentOpenClass),this.handorgel.emitEvent("fold:closed",[this])}},{key:"_initialOpen",value:function(){var t=this;null===this.header.getAttribute(this.handorgel.options.initialOpenAttribute)&&null===this.content.getAttribute(this.handorgel.options.initialOpenAttribute)||(this.handorgel.options.initialOpenTransition?window.setTimeout(function(){t.open()},this.handorgel.options.initialOpenTransitionDelay):this.open(!1))}},{key:"_initialFocus",value:function(){null!==this.button.getAttribute("autofocus")&&this._handleFocus()}},{key:"_initAria",value:function(){this._updateAria("button"),this._updateAria("content")}},{key:"_cleanAria",value:function(){this._updateAria("button",null,!0),this._updateAria("content",null,!0)}},{key:"_updateAria",value:function(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:null,n=arguments.length>2&&void 0!==arguments[2]&&arguments[2];if(this.handorgel.options.ariaEnabled)if(e){var i=u[t][e].call(this);this[t].setAttribute(e,i)}else for(var s in u[t])if(u[t].hasOwnProperty(s))if(n)this[t].removeAttribute(s);else{var o=u[t][s].call(this);this[t].setAttribute(s,o)}}},{key:"_handleContentTransitionEnd",value:function(t){t.target===t.currentTarget&&"height"===t.propertyName&&(this.expanded?this._opened():this._closed())}},{key:"_handleFocus",value:function(){this.focused=!0,this.header.classList.add(this.handorgel.options.headerFocusClass),this.content.classList.add(this.handorgel.options.contentFocusClass),this.handorgel.emitEvent("fold:focus",[this])}},{key:"_handleBlur",value:function(){this.focused=!1,this.header.classList.remove(this.handorgel.options.headerFocusClass),this.content.classList.remove(this.handorgel.options.contentFocusClass),this.handorgel.emitEvent("fold:blur",[this])}},{key:"_handleButtonClick",value:function(t){this.focus(),this.disabled||this.toggle()}},{key:"_handleButtonKeydown",value:function(t){if(this.handorgel.options.keyboardInteraction){var e=null;switch(t.which){case f:e="next";break;case p:e="prev";break;case y:e="first";break;case g:e="last";break;case b:t.ctrlKey&&(e="next");break;case v:t.ctrlKey&&(e="prev")}e&&(t.preventDefault(),this.handorgel.focus(e))}}},{key:"_handleContentKeydown",value:function(t){if(this.handorgel.options.keyboardInteraction&&t.ctrlKey){var e=null;switch(t.which){case b:e="next";break;case v:e="prev"}e&&(t.preventDefault(),this.handorgel.focus(e))}}},{key:"_bindEvents",value:function(){for(var t in this._listeners={bFocus:["focus",this.button,this._handleFocus.bind(this)],bBlur:["blur",this.button,this._handleBlur.bind(this)],bClick:["click",this.button,this._handleButtonClick.bind(this)],bKeydown:["keydown",this.button,this._handleButtonKeydown.bind(this)],cKeydown:["keydown",this.content,this._handleContentKeydown.bind(this)],cTransition:["transitionend",this.content,this._handleContentTransitionEnd.bind(this)]},this._listeners)if(this._listeners.hasOwnProperty(t)){var e=this._listeners[t];e[1].addEventListener(e[0],e[2])}}},{key:"_unbindEvents",value:function(){for(var t in this._listeners)if(this._listeners.hasOwnProperty(t)){var e=this._listeners[t];e[1].removeEventListener(e[0],e[2])}}}]),e}(),m=0,O=function(e){!function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Super expression must either be null or a function");t.prototype=Object.create(e&&e.prototype,{constructor:{value:t,writable:!0,configurable:!0}}),Object.defineProperty(t,"prototype",{writable:!1}),e&&s(t,e)}(l,d);var i=r(l);function l(e){var n,s=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};return t(this,l),n=i.call(this),e.handorgel?a(n):(n.element=e,n.element.handorgel=o(n),n.id="handorgel".concat(++m),n.element.setAttribute("id",n.id),n.folds=[],n.options=Object.assign({},l.defaultOptions,s),n._listeners={},n._bindEvents(),n._initAria(),n.update(),n)}return n(l,[{key:"update",value:function(){this.folds=[];for(var t="string"==typeof this.options.headerElements?this.element.querySelectorAll(this.options.headerElements):this.options.headerElements,e="string"==typeof this.options.contentElements?this.element.querySelectorAll(this.options.contentElements):this.options.contentElements,n=0;n { 213 | window.requestAnimationFrame(() => { 214 | this.content.style.height = '0px'; 215 | }); 216 | }); 217 | } 218 | } 219 | disable() { 220 | this.disabled = true; 221 | this._updateAria('button', 'aria-disabled'); 222 | this.header.classList.add(this.handorgel.options.headerDisabledClass); 223 | this.content.classList.add(this.handorgel.options.contentDisabledClass); 224 | } 225 | enable() { 226 | this.disabled = false; 227 | this._updateAria('button', 'aria-disabled'); 228 | this.header.classList.remove(this.handorgel.options.headerDisabledClass); 229 | this.content.classList.remove(this.handorgel.options.contentDisabledClass); 230 | } 231 | focus() { 232 | this.button.focus(); 233 | } 234 | blur() { 235 | this.button.blur(); 236 | } 237 | toggle(transition = true) { 238 | if (this.expanded) { 239 | this.close(transition); 240 | } else { 241 | this.open(transition); 242 | } 243 | } 244 | destroy() { 245 | this._unbindEvents(); 246 | this._cleanAria(); 247 | 248 | // clean classes 249 | this.header.classList.remove(this.handorgel.options.headerOpenClass); 250 | this.header.classList.remove(this.handorgel.options.headerOpenedClass); 251 | this.header.classList.remove(this.handorgel.options.headerFocusClass); 252 | this.content.classList.remove(this.handorgel.options.contentOpenClass); 253 | this.content.classList.remove(this.handorgel.options.contentOpenedClass); 254 | this.content.classList.remove(this.handorgel.options.contentFocusClass); 255 | 256 | // hide content 257 | this.content.style.height = '0px'; 258 | 259 | // clean reference to this instance 260 | this.header.handorgelFold = null; 261 | this.content.handorgelFold = null; 262 | 263 | // remove ids 264 | this.header.removeAttribute('id'); 265 | this.content.removeAttribute('id'); 266 | 267 | // clean reference to handorgel instance 268 | this.handorgel = null; 269 | } 270 | _opened() { 271 | this.content.style.height = 'auto'; 272 | this.header.classList.add(this.handorgel.options.headerOpenedClass); 273 | this.content.classList.add(this.handorgel.options.contentOpenedClass); 274 | this.handorgel.emitEvent('fold:opened', [this]); 275 | } 276 | _closed() { 277 | this.header.classList.remove(this.handorgel.options.headerOpenClass); 278 | this.content.classList.remove(this.handorgel.options.contentOpenClass); 279 | this.handorgel.emitEvent('fold:closed', [this]); 280 | } 281 | _initialOpen() { 282 | if (this.header.getAttribute(this.handorgel.options.initialOpenAttribute) !== null || this.content.getAttribute(this.handorgel.options.initialOpenAttribute) !== null) { 283 | if (this.handorgel.options.initialOpenTransition) { 284 | window.setTimeout(() => { 285 | this.open(); 286 | }, this.handorgel.options.initialOpenTransitionDelay); 287 | } else { 288 | this.open(false); 289 | } 290 | } 291 | } 292 | _initialFocus() { 293 | if (this.button.getAttribute('autofocus') === null) { 294 | return; 295 | } 296 | 297 | // to ensure focus styles if autofocus was applied 298 | // before focus listener was added 299 | this._handleFocus(); 300 | } 301 | _initAria() { 302 | this._updateAria('button'); 303 | this._updateAria('content'); 304 | } 305 | _cleanAria() { 306 | this._updateAria('button', null, true); 307 | this._updateAria('content', null, true); 308 | } 309 | _updateAria(element, property = null, remove = false) { 310 | if (!this.handorgel.options.ariaEnabled) { 311 | return; 312 | } 313 | if (property) { 314 | const newValue = ARIA_ATTRIBUTES[element][property].call(this); 315 | this[element].setAttribute(property, newValue); 316 | } else { 317 | for (let property in ARIA_ATTRIBUTES[element]) { 318 | if (ARIA_ATTRIBUTES[element].hasOwnProperty(property)) { 319 | if (remove) { 320 | this[element].removeAttribute(property); 321 | } else { 322 | const newValue = ARIA_ATTRIBUTES[element][property].call(this); 323 | this[element].setAttribute(property, newValue); 324 | } 325 | } 326 | } 327 | } 328 | } 329 | _handleContentTransitionEnd(e) { 330 | if (e.target === e.currentTarget && e.propertyName === 'height') { 331 | if (this.expanded) { 332 | this._opened(); 333 | } else { 334 | this._closed(); 335 | } 336 | } 337 | } 338 | _handleFocus() { 339 | this.focused = true; 340 | this.header.classList.add(this.handorgel.options.headerFocusClass); 341 | this.content.classList.add(this.handorgel.options.contentFocusClass); 342 | this.handorgel.emitEvent('fold:focus', [this]); 343 | } 344 | _handleBlur() { 345 | this.focused = false; 346 | this.header.classList.remove(this.handorgel.options.headerFocusClass); 347 | this.content.classList.remove(this.handorgel.options.contentFocusClass); 348 | this.handorgel.emitEvent('fold:blur', [this]); 349 | } 350 | _handleButtonClick(e) { 351 | // ensure focus is on button (click is not seting focus on firefox mac) 352 | this.focus(); 353 | if (this.disabled) { 354 | return; 355 | } 356 | this.toggle(); 357 | } 358 | _handleButtonKeydown(e) { 359 | if (!this.handorgel.options.keyboardInteraction) { 360 | return; 361 | } 362 | let action = null; 363 | switch (e.which) { 364 | case KEYS.arrowDown: 365 | action = 'next'; 366 | break; 367 | case KEYS.arrowUp: 368 | action = 'prev'; 369 | break; 370 | case KEYS.home: 371 | action = 'first'; 372 | break; 373 | case KEYS.end: 374 | action = 'last'; 375 | break; 376 | case KEYS.pageDown: 377 | if (e.ctrlKey) { 378 | action = 'next'; 379 | } 380 | break; 381 | case KEYS.pageUp: 382 | if (e.ctrlKey) { 383 | action = 'prev'; 384 | } 385 | break; 386 | } 387 | if (action) { 388 | e.preventDefault(); 389 | this.handorgel.focus(action); 390 | } 391 | } 392 | _handleContentKeydown(e) { 393 | if (!this.handorgel.options.keyboardInteraction || !e.ctrlKey) { 394 | return; 395 | } 396 | let action = null; 397 | switch (e.which) { 398 | case KEYS.pageDown: 399 | action = 'next'; 400 | break; 401 | case KEYS.pageUp: 402 | action = 'prev'; 403 | break; 404 | } 405 | if (action) { 406 | e.preventDefault(); 407 | this.handorgel.focus(action); 408 | } 409 | } 410 | _bindEvents() { 411 | this._listeners = { 412 | // button listeners 413 | bFocus: ['focus', this.button, this._handleFocus.bind(this)], 414 | bBlur: ['blur', this.button, this._handleBlur.bind(this)], 415 | bClick: ['click', this.button, this._handleButtonClick.bind(this)], 416 | bKeydown: ['keydown', this.button, this._handleButtonKeydown.bind(this)], 417 | // content listeners 418 | cKeydown: ['keydown', this.content, this._handleContentKeydown.bind(this)], 419 | cTransition: ['transitionend', this.content, this._handleContentTransitionEnd.bind(this)] 420 | }; 421 | for (let key in this._listeners) { 422 | if (this._listeners.hasOwnProperty(key)) { 423 | const listener = this._listeners[key]; 424 | listener[1].addEventListener(listener[0], listener[2]); 425 | } 426 | } 427 | } 428 | _unbindEvents() { 429 | for (let key in this._listeners) { 430 | if (this._listeners.hasOwnProperty(key)) { 431 | const listener = this._listeners[key]; 432 | listener[1].removeEventListener(listener[0], listener[2]); 433 | } 434 | } 435 | } 436 | } 437 | 438 | let ID_COUNTER$1 = 0; 439 | class Handorgel extends evEmitter { 440 | constructor(element, options = {}) { 441 | super(); 442 | if (element.handorgel) { 443 | return; 444 | } 445 | this.element = element; 446 | this.element.handorgel = this; 447 | this.id = `handorgel${++ID_COUNTER$1}`; 448 | this.element.setAttribute('id', this.id); 449 | this.folds = []; 450 | this.options = Object.assign({}, Handorgel.defaultOptions, options); 451 | this._listeners = {}; 452 | this._bindEvents(); 453 | this._initAria(); 454 | this.update(); 455 | } 456 | update() { 457 | this.folds = []; 458 | const headerElements = typeof this.options.headerElements === 'string' ? this.element.querySelectorAll(this.options.headerElements) : this.options.headerElements; 459 | const contentElements = typeof this.options.contentElements === 'string' ? this.element.querySelectorAll(this.options.contentElements) : this.options.contentElements; 460 | for (let i = 0; i < headerElements.length; i = i + 1) { 461 | // get fold instance if there is already one 462 | let fold = headerElements[i].handorgelFold; 463 | 464 | // create new one when header and content exist 465 | if (!fold && headerElements[i] && contentElements[i]) { 466 | fold = new HandorgelFold(this, headerElements[i], contentElements[i]); 467 | } 468 | if (fold) { 469 | this.folds.push(fold); 470 | } 471 | } 472 | } 473 | focus(target) { 474 | const foldsLength = this.folds.length; 475 | let currentFocusedIndex = null; 476 | for (let i = 0; i < foldsLength && currentFocusedIndex === null; i++) { 477 | if (this.folds[i].focused) currentFocusedIndex = i; 478 | } 479 | if ((target === 'prev' || target === 'next') && currentFocusedIndex === null) { 480 | target = target === 'prev' ? 'last' : 'first'; 481 | } 482 | if (target === 'prev' && currentFocusedIndex === 0) { 483 | if (!this.options.carouselFocus) return; 484 | target = 'last'; 485 | } 486 | if (target === 'next' && currentFocusedIndex === foldsLength - 1) { 487 | if (!this.options.carouselFocus) return; 488 | target = 'first'; 489 | } 490 | switch (target) { 491 | case 'prev': 492 | this.folds[--currentFocusedIndex].focus(); 493 | break; 494 | case 'next': 495 | this.folds[++currentFocusedIndex].focus(); 496 | break; 497 | case 'last': 498 | this.folds[foldsLength - 1].focus(); 499 | break; 500 | case 'first': 501 | default: 502 | this.folds[0].focus(); 503 | } 504 | } 505 | destroy() { 506 | this.emitEvent('destroy'); 507 | this.element.removeAttribute('id'); 508 | this.folds.forEach(fold => { 509 | fold.destroy(); 510 | }); 511 | this._unbindEvents(); 512 | this._cleanAria(); 513 | 514 | // clean reference to handorgel instance 515 | this.element.handorgel = null; 516 | this.emitEvent('destroyed'); 517 | } 518 | _handleFoldOpen(openFold) { 519 | if (this.options.multiSelectable) { 520 | return; 521 | } 522 | this.folds.forEach(fold => { 523 | if (openFold !== fold) { 524 | fold.close(); 525 | } 526 | }); 527 | } 528 | _initAria() { 529 | if (!this.options.ariaEnabled) { 530 | return; 531 | } 532 | if (this.options.multiSelectable) { 533 | this.element.setAttribute('aria-multiselectable', 'true'); 534 | } 535 | } 536 | _cleanAria() { 537 | this.element.removeAttribute('aria-multiselectable'); 538 | } 539 | _bindEvents() { 540 | this._listeners.foldOpen = this._handleFoldOpen.bind(this); 541 | this.on('fold:open', this._listeners.foldOpen); 542 | } 543 | _unbindEvents() { 544 | this.off('fold:open', this._listeners.foldOpen); 545 | } 546 | } 547 | Handorgel.defaultOptions = { 548 | keyboardInteraction: true, 549 | multiSelectable: true, 550 | ariaEnabled: true, 551 | collapsible: true, 552 | carouselFocus: true, 553 | initialOpenAttribute: 'data-open', 554 | initialOpenTransition: true, 555 | initialOpenTransitionDelay: 200, 556 | headerElements: '.handorgel__header', 557 | contentElements: '.handorgel__content', 558 | headerOpenClass: 'handorgel__header--open', 559 | contentOpenClass: 'handorgel__content--open', 560 | headerOpenedClass: 'handorgel__header--opened', 561 | contentOpenedClass: 'handorgel__content--opened', 562 | headerDisabledClass: 'handorgel__header--disabled', 563 | contentDisabledClass: 'handorgel__content--disabled', 564 | headerFocusClass: 'handorgel__header--focus', 565 | contentFocusClass: 'handorgel__content--focus' 566 | }; 567 | 568 | export default Handorgel; 569 | -------------------------------------------------------------------------------- /lib/js/handorgel.min.js: -------------------------------------------------------------------------------- 1 | /** handorgel v1.0.0, @license MIT */ 2 | var t="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:{};var e,s=(function(e){var s,i;s="undefined"!=typeof window?window:t,i=function(){function t(){}var e=t.prototype;return e.on=function(t,e){if(t&&e){var s=this._events=this._events||{},i=s[t]=s[t]||[];return-1==i.indexOf(e)&&i.push(e),this}},e.once=function(t,e){if(t&&e){this.on(t,e);var s=this._onceEvents=this._onceEvents||{};return(s[t]=s[t]||{})[e]=!0,this}},e.off=function(t,e){var s=this._events&&this._events[t];if(s&&s.length){var i=s.indexOf(e);return-1!=i&&s.splice(i,1),this}},e.emitEvent=function(t,e){var s=this._events&&this._events[t];if(s&&s.length){s=s.slice(0),e=e||[];for(var i=this._onceEvents&&this._onceEvents[t],n=0;n{window.requestAnimationFrame(()=>{this.content.style.height="0px"})})}else this._closed()}disable(){this.disabled=!0,this._updateAria("button","aria-disabled"),this.header.classList.add(this.handorgel.options.headerDisabledClass),this.content.classList.add(this.handorgel.options.contentDisabledClass)}enable(){this.disabled=!1,this._updateAria("button","aria-disabled"),this.header.classList.remove(this.handorgel.options.headerDisabledClass),this.content.classList.remove(this.handorgel.options.contentDisabledClass)}focus(){this.button.focus()}blur(){this.button.blur()}toggle(t=!0){this.expanded?this.close(t):this.open(t)}destroy(){this._unbindEvents(),this._cleanAria(),this.header.classList.remove(this.handorgel.options.headerOpenClass),this.header.classList.remove(this.handorgel.options.headerOpenedClass),this.header.classList.remove(this.handorgel.options.headerFocusClass),this.content.classList.remove(this.handorgel.options.contentOpenClass),this.content.classList.remove(this.handorgel.options.contentOpenedClass),this.content.classList.remove(this.handorgel.options.contentFocusClass),this.content.style.height="0px",this.header.handorgelFold=null,this.content.handorgelFold=null,this.header.removeAttribute("id"),this.content.removeAttribute("id"),this.handorgel=null}_opened(){this.content.style.height="auto",this.header.classList.add(this.handorgel.options.headerOpenedClass),this.content.classList.add(this.handorgel.options.contentOpenedClass),this.handorgel.emitEvent("fold:opened",[this])}_closed(){this.header.classList.remove(this.handorgel.options.headerOpenClass),this.content.classList.remove(this.handorgel.options.contentOpenClass),this.handorgel.emitEvent("fold:closed",[this])}_initialOpen(){null===this.header.getAttribute(this.handorgel.options.initialOpenAttribute)&&null===this.content.getAttribute(this.handorgel.options.initialOpenAttribute)||(this.handorgel.options.initialOpenTransition?window.setTimeout(()=>{this.open()},this.handorgel.options.initialOpenTransitionDelay):this.open(!1))}_initialFocus(){null!==this.button.getAttribute("autofocus")&&this._handleFocus()}_initAria(){this._updateAria("button"),this._updateAria("content")}_cleanAria(){this._updateAria("button",null,!0),this._updateAria("content",null,!0)}_updateAria(t,e=null,s=!1){if(this.handorgel.options.ariaEnabled)if(e){const s=n[t][e].call(this);this[t].setAttribute(e,s)}else for(let e in n[t])if(n[t].hasOwnProperty(e))if(s)this[t].removeAttribute(e);else{const s=n[t][e].call(this);this[t].setAttribute(e,s)}}_handleContentTransitionEnd(t){t.target===t.currentTarget&&"height"===t.propertyName&&(this.expanded?this._opened():this._closed())}_handleFocus(){this.focused=!0,this.header.classList.add(this.handorgel.options.headerFocusClass),this.content.classList.add(this.handorgel.options.contentFocusClass),this.handorgel.emitEvent("fold:focus",[this])}_handleBlur(){this.focused=!1,this.header.classList.remove(this.handorgel.options.headerFocusClass),this.content.classList.remove(this.handorgel.options.contentFocusClass),this.handorgel.emitEvent("fold:blur",[this])}_handleButtonClick(t){this.focus(),this.disabled||this.toggle()}_handleButtonKeydown(t){if(!this.handorgel.options.keyboardInteraction)return;let e=null;switch(t.which){case o.arrowDown:e="next";break;case o.arrowUp:e="prev";break;case o.home:e="first";break;case o.end:e="last";break;case o.pageDown:t.ctrlKey&&(e="next");break;case o.pageUp:t.ctrlKey&&(e="prev")}e&&(t.preventDefault(),this.handorgel.focus(e))}_handleContentKeydown(t){if(!this.handorgel.options.keyboardInteraction||!t.ctrlKey)return;let e=null;switch(t.which){case o.pageDown:e="next";break;case o.pageUp:e="prev"}e&&(t.preventDefault(),this.handorgel.focus(e))}_bindEvents(){this._listeners={bFocus:["focus",this.button,this._handleFocus.bind(this)],bBlur:["blur",this.button,this._handleBlur.bind(this)],bClick:["click",this.button,this._handleButtonClick.bind(this)],bKeydown:["keydown",this.button,this._handleButtonKeydown.bind(this)],cKeydown:["keydown",this.content,this._handleContentKeydown.bind(this)],cTransition:["transitionend",this.content,this._handleContentTransitionEnd.bind(this)]};for(let t in this._listeners)if(this._listeners.hasOwnProperty(t)){const e=this._listeners[t];e[1].addEventListener(e[0],e[2])}}_unbindEvents(){for(let t in this._listeners)if(this._listeners.hasOwnProperty(t)){const e=this._listeners[t];e[1].removeEventListener(e[0],e[2])}}}let a=0;class l extends s{constructor(t,e={}){super(),t.handorgel||(this.element=t,this.element.handorgel=this,this.id=`handorgel${++a}`,this.element.setAttribute("id",this.id),this.folds=[],this.options=Object.assign({},l.defaultOptions,e),this._listeners={},this._bindEvents(),this._initAria(),this.update())}update(){this.folds=[];const t="string"==typeof this.options.headerElements?this.element.querySelectorAll(this.options.headerElements):this.options.headerElements,e="string"==typeof this.options.contentElements?this.element.querySelectorAll(this.options.contentElements):this.options.contentElements;for(let s=0;s{t.destroy()}),this._unbindEvents(),this._cleanAria(),this.element.handorgel=null,this.emitEvent("destroyed")}_handleFoldOpen(t){this.options.multiSelectable||this.folds.forEach(e=>{t!==e&&e.close()})}_initAria(){this.options.ariaEnabled&&this.options.multiSelectable&&this.element.setAttribute("aria-multiselectable","true")}_cleanAria(){this.element.removeAttribute("aria-multiselectable")}_bindEvents(){this._listeners.foldOpen=this._handleFoldOpen.bind(this),this.on("fold:open",this._listeners.foldOpen)}_unbindEvents(){this.off("fold:open",this._listeners.foldOpen)}}l.defaultOptions={keyboardInteraction:!0,multiSelectable:!0,ariaEnabled:!0,collapsible:!0,carouselFocus:!0,initialOpenAttribute:"data-open",initialOpenTransition:!0,initialOpenTransitionDelay:200,headerElements:".handorgel__header",contentElements:".handorgel__content",headerOpenClass:"handorgel__header--open",contentOpenClass:"handorgel__content--open",headerOpenedClass:"handorgel__header--opened",contentOpenedClass:"handorgel__content--opened",headerDisabledClass:"handorgel__header--disabled",contentDisabledClass:"handorgel__content--disabled",headerFocusClass:"handorgel__header--focus",contentFocusClass:"handorgel__content--focus"};export default l; -------------------------------------------------------------------------------- /lib/js/umd/handorgel.js: -------------------------------------------------------------------------------- 1 | /** handorgel v1.0.0, @license MIT */ 2 | (function (global, factory) { 3 | typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : 4 | typeof define === 'function' && define.amd ? define(factory) : 5 | (global = global || self, global.handorgel = factory()); 6 | }(this, function () { 'use strict'; 7 | 8 | function _classCallCheck(instance, Constructor) { 9 | if (!(instance instanceof Constructor)) { 10 | throw new TypeError("Cannot call a class as a function"); 11 | } 12 | } 13 | function _defineProperties(target, props) { 14 | for (var i = 0; i < props.length; i++) { 15 | var descriptor = props[i]; 16 | descriptor.enumerable = descriptor.enumerable || false; 17 | descriptor.configurable = true; 18 | if ("value" in descriptor) descriptor.writable = true; 19 | Object.defineProperty(target, _toPropertyKey(descriptor.key), descriptor); 20 | } 21 | } 22 | function _createClass(Constructor, protoProps, staticProps) { 23 | if (protoProps) _defineProperties(Constructor.prototype, protoProps); 24 | if (staticProps) _defineProperties(Constructor, staticProps); 25 | Object.defineProperty(Constructor, "prototype", { 26 | writable: false 27 | }); 28 | return Constructor; 29 | } 30 | function _inherits(subClass, superClass) { 31 | if (typeof superClass !== "function" && superClass !== null) { 32 | throw new TypeError("Super expression must either be null or a function"); 33 | } 34 | subClass.prototype = Object.create(superClass && superClass.prototype, { 35 | constructor: { 36 | value: subClass, 37 | writable: true, 38 | configurable: true 39 | } 40 | }); 41 | Object.defineProperty(subClass, "prototype", { 42 | writable: false 43 | }); 44 | if (superClass) _setPrototypeOf(subClass, superClass); 45 | } 46 | function _getPrototypeOf(o) { 47 | _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf.bind() : function _getPrototypeOf(o) { 48 | return o.__proto__ || Object.getPrototypeOf(o); 49 | }; 50 | return _getPrototypeOf(o); 51 | } 52 | function _setPrototypeOf(o, p) { 53 | _setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function _setPrototypeOf(o, p) { 54 | o.__proto__ = p; 55 | return o; 56 | }; 57 | return _setPrototypeOf(o, p); 58 | } 59 | function _isNativeReflectConstruct() { 60 | if (typeof Reflect === "undefined" || !Reflect.construct) return false; 61 | if (Reflect.construct.sham) return false; 62 | if (typeof Proxy === "function") return true; 63 | try { 64 | Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); 65 | return true; 66 | } catch (e) { 67 | return false; 68 | } 69 | } 70 | function _assertThisInitialized(self) { 71 | if (self === void 0) { 72 | throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); 73 | } 74 | return self; 75 | } 76 | function _possibleConstructorReturn(self, call) { 77 | if (call && (typeof call === "object" || typeof call === "function")) { 78 | return call; 79 | } else if (call !== void 0) { 80 | throw new TypeError("Derived constructors may only return object or undefined"); 81 | } 82 | return _assertThisInitialized(self); 83 | } 84 | function _createSuper(Derived) { 85 | var hasNativeReflectConstruct = _isNativeReflectConstruct(); 86 | return function _createSuperInternal() { 87 | var Super = _getPrototypeOf(Derived), 88 | result; 89 | if (hasNativeReflectConstruct) { 90 | var NewTarget = _getPrototypeOf(this).constructor; 91 | result = Reflect.construct(Super, arguments, NewTarget); 92 | } else { 93 | result = Super.apply(this, arguments); 94 | } 95 | return _possibleConstructorReturn(this, result); 96 | }; 97 | } 98 | function _toPrimitive(input, hint) { 99 | if (typeof input !== "object" || input === null) return input; 100 | var prim = input[Symbol.toPrimitive]; 101 | if (prim !== undefined) { 102 | var res = prim.call(input, hint || "default"); 103 | if (typeof res !== "object") return res; 104 | throw new TypeError("@@toPrimitive must return a primitive value."); 105 | } 106 | return (hint === "string" ? String : Number)(input); 107 | } 108 | function _toPropertyKey(arg) { 109 | var key = _toPrimitive(arg, "string"); 110 | return typeof key === "symbol" ? key : String(key); 111 | } 112 | 113 | var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {}; 114 | 115 | function createCommonjsModule(fn, module) { 116 | return module = { exports: {} }, fn(module, module.exports), module.exports; 117 | } 118 | 119 | var evEmitter = createCommonjsModule(function (module) { 120 | /** 121 | * EvEmitter v1.1.0 122 | * Lil' event emitter 123 | * MIT License 124 | */ 125 | 126 | /* jshint unused: true, undef: true, strict: true */ 127 | 128 | ( function( global, factory ) { 129 | // universal module definition 130 | /* jshint strict: false */ /* globals define, module, window */ 131 | if ( module.exports ) { 132 | // CommonJS - Browserify, Webpack 133 | module.exports = factory(); 134 | } else { 135 | // Browser globals 136 | global.EvEmitter = factory(); 137 | } 138 | 139 | }( typeof window != 'undefined' ? window : commonjsGlobal, function() { 140 | 141 | function EvEmitter() {} 142 | 143 | var proto = EvEmitter.prototype; 144 | 145 | proto.on = function( eventName, listener ) { 146 | if ( !eventName || !listener ) { 147 | return; 148 | } 149 | // set events hash 150 | var events = this._events = this._events || {}; 151 | // set listeners array 152 | var listeners = events[ eventName ] = events[ eventName ] || []; 153 | // only add once 154 | if ( listeners.indexOf( listener ) == -1 ) { 155 | listeners.push( listener ); 156 | } 157 | 158 | return this; 159 | }; 160 | 161 | proto.once = function( eventName, listener ) { 162 | if ( !eventName || !listener ) { 163 | return; 164 | } 165 | // add event 166 | this.on( eventName, listener ); 167 | // set once flag 168 | // set onceEvents hash 169 | var onceEvents = this._onceEvents = this._onceEvents || {}; 170 | // set onceListeners object 171 | var onceListeners = onceEvents[ eventName ] = onceEvents[ eventName ] || {}; 172 | // set flag 173 | onceListeners[ listener ] = true; 174 | 175 | return this; 176 | }; 177 | 178 | proto.off = function( eventName, listener ) { 179 | var listeners = this._events && this._events[ eventName ]; 180 | if ( !listeners || !listeners.length ) { 181 | return; 182 | } 183 | var index = listeners.indexOf( listener ); 184 | if ( index != -1 ) { 185 | listeners.splice( index, 1 ); 186 | } 187 | 188 | return this; 189 | }; 190 | 191 | proto.emitEvent = function( eventName, args ) { 192 | var listeners = this._events && this._events[ eventName ]; 193 | if ( !listeners || !listeners.length ) { 194 | return; 195 | } 196 | // copy over to avoid interference if .off() in listener 197 | listeners = listeners.slice(0); 198 | args = args || []; 199 | // once stuff 200 | var onceListeners = this._onceEvents && this._onceEvents[ eventName ]; 201 | 202 | for ( var i=0; i < listeners.length; i++ ) { 203 | var listener = listeners[i]; 204 | var isOnce = onceListeners && onceListeners[ listener ]; 205 | if ( isOnce ) { 206 | // remove listener 207 | // remove before trigger to prevent recursion 208 | this.off( eventName, listener ); 209 | // unset once flag 210 | delete onceListeners[ listener ]; 211 | } 212 | // trigger listener 213 | listener.apply( this, args ); 214 | } 215 | 216 | return this; 217 | }; 218 | 219 | proto.allOff = function() { 220 | delete this._events; 221 | delete this._onceEvents; 222 | }; 223 | 224 | return EvEmitter; 225 | 226 | })); 227 | }); 228 | 229 | var ID_COUNTER = {}; 230 | var ARIA_ATTRIBUTES = { 231 | button: { 232 | 'aria-controls': function ariaControls() { 233 | return this.id + '-content'; 234 | }, 235 | 'aria-expanded': function ariaExpanded() { 236 | return this.expanded ? 'true' : 'false'; 237 | }, 238 | 'aria-disabled': function ariaDisabled() { 239 | return this.disabled ? 'true' : 'false'; 240 | } 241 | }, 242 | content: { 243 | role: function role() { 244 | return 'region'; 245 | }, 246 | 'aria-labelledby': function ariaLabelledby() { 247 | return this.id + '-header'; 248 | } 249 | } 250 | }; 251 | var KEYS = { 252 | arrowDown: 40, 253 | arrowUp: 38, 254 | pageUp: 33, 255 | pageDown: 34, 256 | end: 35, 257 | home: 36 258 | }; 259 | var HandorgelFold = /*#__PURE__*/function () { 260 | function HandorgelFold(handorgel, header, content) { 261 | _classCallCheck(this, HandorgelFold); 262 | if (header.handorgelFold) { 263 | return; 264 | } 265 | this.handorgel = handorgel; 266 | this.header = header; 267 | this.button = header.firstElementChild; 268 | this.content = content; 269 | this.header.handorgelFold = this; 270 | this.content.handorgelFold = this; 271 | if (!ID_COUNTER[this.handorgel.id]) { 272 | ID_COUNTER[this.handorgel.id] = 0; 273 | } 274 | this.id = "".concat(this.handorgel.id, "-fold").concat(++ID_COUNTER[this.handorgel.id]); 275 | this.header.setAttribute('id', this.id + '-header'); 276 | this.content.setAttribute('id', this.id + '-content'); 277 | this.focused = false; 278 | this.expanded = false; 279 | this.disabled = false; 280 | this._listeners = {}; 281 | this._bindEvents(); 282 | this._initAria(); 283 | this._initialOpen(); 284 | this._initialFocus(); 285 | } 286 | _createClass(HandorgelFold, [{ 287 | key: "open", 288 | value: function open() { 289 | var transition = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true; 290 | if (this.expanded) { 291 | return; 292 | } 293 | this.handorgel.emitEvent('fold:open', [this]); 294 | this.expanded = true; 295 | if (!this.handorgel.options.collapsible) { 296 | this.disable(); 297 | } 298 | this._updateAria('button', 'aria-expanded'); 299 | this.header.classList.add(this.handorgel.options.headerOpenClass); 300 | this.content.classList.add(this.handorgel.options.contentOpenClass); 301 | if (!transition) { 302 | this._opened(); 303 | } else { 304 | var height = this.content.firstElementChild.offsetHeight; 305 | this.content.style.height = "".concat(height, "px"); 306 | } 307 | } 308 | }, { 309 | key: "close", 310 | value: function close() { 311 | var _this = this; 312 | var transition = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true; 313 | if (!this.expanded) { 314 | return; 315 | } 316 | this.handorgel.emitEvent('fold:close', [this]); 317 | this.expanded = false; 318 | if (!this.handorgel.options.collapsible) { 319 | this.enable(); 320 | } 321 | this._updateAria('button', 'aria-expanded'); 322 | this.header.classList.remove(this.handorgel.options.headerOpenedClass); 323 | this.content.classList.remove(this.handorgel.options.contentOpenedClass); 324 | if (!transition) { 325 | this._closed(); 326 | } else { 327 | // if we want to transition when closing we 328 | // have to set the current height and replace auto 329 | var height = this.content.firstElementChild.offsetHeight; 330 | this.content.style.height = "".concat(height, "px"); 331 | window.requestAnimationFrame(function () { 332 | window.requestAnimationFrame(function () { 333 | _this.content.style.height = '0px'; 334 | }); 335 | }); 336 | } 337 | } 338 | }, { 339 | key: "disable", 340 | value: function disable() { 341 | this.disabled = true; 342 | this._updateAria('button', 'aria-disabled'); 343 | this.header.classList.add(this.handorgel.options.headerDisabledClass); 344 | this.content.classList.add(this.handorgel.options.contentDisabledClass); 345 | } 346 | }, { 347 | key: "enable", 348 | value: function enable() { 349 | this.disabled = false; 350 | this._updateAria('button', 'aria-disabled'); 351 | this.header.classList.remove(this.handorgel.options.headerDisabledClass); 352 | this.content.classList.remove(this.handorgel.options.contentDisabledClass); 353 | } 354 | }, { 355 | key: "focus", 356 | value: function focus() { 357 | this.button.focus(); 358 | } 359 | }, { 360 | key: "blur", 361 | value: function blur() { 362 | this.button.blur(); 363 | } 364 | }, { 365 | key: "toggle", 366 | value: function toggle() { 367 | var transition = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true; 368 | if (this.expanded) { 369 | this.close(transition); 370 | } else { 371 | this.open(transition); 372 | } 373 | } 374 | }, { 375 | key: "destroy", 376 | value: function destroy() { 377 | this._unbindEvents(); 378 | this._cleanAria(); 379 | 380 | // clean classes 381 | this.header.classList.remove(this.handorgel.options.headerOpenClass); 382 | this.header.classList.remove(this.handorgel.options.headerOpenedClass); 383 | this.header.classList.remove(this.handorgel.options.headerFocusClass); 384 | this.content.classList.remove(this.handorgel.options.contentOpenClass); 385 | this.content.classList.remove(this.handorgel.options.contentOpenedClass); 386 | this.content.classList.remove(this.handorgel.options.contentFocusClass); 387 | 388 | // hide content 389 | this.content.style.height = '0px'; 390 | 391 | // clean reference to this instance 392 | this.header.handorgelFold = null; 393 | this.content.handorgelFold = null; 394 | 395 | // remove ids 396 | this.header.removeAttribute('id'); 397 | this.content.removeAttribute('id'); 398 | 399 | // clean reference to handorgel instance 400 | this.handorgel = null; 401 | } 402 | }, { 403 | key: "_opened", 404 | value: function _opened() { 405 | this.content.style.height = 'auto'; 406 | this.header.classList.add(this.handorgel.options.headerOpenedClass); 407 | this.content.classList.add(this.handorgel.options.contentOpenedClass); 408 | this.handorgel.emitEvent('fold:opened', [this]); 409 | } 410 | }, { 411 | key: "_closed", 412 | value: function _closed() { 413 | this.header.classList.remove(this.handorgel.options.headerOpenClass); 414 | this.content.classList.remove(this.handorgel.options.contentOpenClass); 415 | this.handorgel.emitEvent('fold:closed', [this]); 416 | } 417 | }, { 418 | key: "_initialOpen", 419 | value: function _initialOpen() { 420 | var _this2 = this; 421 | if (this.header.getAttribute(this.handorgel.options.initialOpenAttribute) !== null || this.content.getAttribute(this.handorgel.options.initialOpenAttribute) !== null) { 422 | if (this.handorgel.options.initialOpenTransition) { 423 | window.setTimeout(function () { 424 | _this2.open(); 425 | }, this.handorgel.options.initialOpenTransitionDelay); 426 | } else { 427 | this.open(false); 428 | } 429 | } 430 | } 431 | }, { 432 | key: "_initialFocus", 433 | value: function _initialFocus() { 434 | if (this.button.getAttribute('autofocus') === null) { 435 | return; 436 | } 437 | 438 | // to ensure focus styles if autofocus was applied 439 | // before focus listener was added 440 | this._handleFocus(); 441 | } 442 | }, { 443 | key: "_initAria", 444 | value: function _initAria() { 445 | this._updateAria('button'); 446 | this._updateAria('content'); 447 | } 448 | }, { 449 | key: "_cleanAria", 450 | value: function _cleanAria() { 451 | this._updateAria('button', null, true); 452 | this._updateAria('content', null, true); 453 | } 454 | }, { 455 | key: "_updateAria", 456 | value: function _updateAria(element) { 457 | var property = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null; 458 | var remove = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false; 459 | if (!this.handorgel.options.ariaEnabled) { 460 | return; 461 | } 462 | if (property) { 463 | var newValue = ARIA_ATTRIBUTES[element][property].call(this); 464 | this[element].setAttribute(property, newValue); 465 | } else { 466 | for (var _property in ARIA_ATTRIBUTES[element]) { 467 | if (ARIA_ATTRIBUTES[element].hasOwnProperty(_property)) { 468 | if (remove) { 469 | this[element].removeAttribute(_property); 470 | } else { 471 | var _newValue = ARIA_ATTRIBUTES[element][_property].call(this); 472 | this[element].setAttribute(_property, _newValue); 473 | } 474 | } 475 | } 476 | } 477 | } 478 | }, { 479 | key: "_handleContentTransitionEnd", 480 | value: function _handleContentTransitionEnd(e) { 481 | if (e.target === e.currentTarget && e.propertyName === 'height') { 482 | if (this.expanded) { 483 | this._opened(); 484 | } else { 485 | this._closed(); 486 | } 487 | } 488 | } 489 | }, { 490 | key: "_handleFocus", 491 | value: function _handleFocus() { 492 | this.focused = true; 493 | this.header.classList.add(this.handorgel.options.headerFocusClass); 494 | this.content.classList.add(this.handorgel.options.contentFocusClass); 495 | this.handorgel.emitEvent('fold:focus', [this]); 496 | } 497 | }, { 498 | key: "_handleBlur", 499 | value: function _handleBlur() { 500 | this.focused = false; 501 | this.header.classList.remove(this.handorgel.options.headerFocusClass); 502 | this.content.classList.remove(this.handorgel.options.contentFocusClass); 503 | this.handorgel.emitEvent('fold:blur', [this]); 504 | } 505 | }, { 506 | key: "_handleButtonClick", 507 | value: function _handleButtonClick(e) { 508 | // ensure focus is on button (click is not seting focus on firefox mac) 509 | this.focus(); 510 | if (this.disabled) { 511 | return; 512 | } 513 | this.toggle(); 514 | } 515 | }, { 516 | key: "_handleButtonKeydown", 517 | value: function _handleButtonKeydown(e) { 518 | if (!this.handorgel.options.keyboardInteraction) { 519 | return; 520 | } 521 | var action = null; 522 | switch (e.which) { 523 | case KEYS.arrowDown: 524 | action = 'next'; 525 | break; 526 | case KEYS.arrowUp: 527 | action = 'prev'; 528 | break; 529 | case KEYS.home: 530 | action = 'first'; 531 | break; 532 | case KEYS.end: 533 | action = 'last'; 534 | break; 535 | case KEYS.pageDown: 536 | if (e.ctrlKey) { 537 | action = 'next'; 538 | } 539 | break; 540 | case KEYS.pageUp: 541 | if (e.ctrlKey) { 542 | action = 'prev'; 543 | } 544 | break; 545 | } 546 | if (action) { 547 | e.preventDefault(); 548 | this.handorgel.focus(action); 549 | } 550 | } 551 | }, { 552 | key: "_handleContentKeydown", 553 | value: function _handleContentKeydown(e) { 554 | if (!this.handorgel.options.keyboardInteraction || !e.ctrlKey) { 555 | return; 556 | } 557 | var action = null; 558 | switch (e.which) { 559 | case KEYS.pageDown: 560 | action = 'next'; 561 | break; 562 | case KEYS.pageUp: 563 | action = 'prev'; 564 | break; 565 | } 566 | if (action) { 567 | e.preventDefault(); 568 | this.handorgel.focus(action); 569 | } 570 | } 571 | }, { 572 | key: "_bindEvents", 573 | value: function _bindEvents() { 574 | this._listeners = { 575 | // button listeners 576 | bFocus: ['focus', this.button, this._handleFocus.bind(this)], 577 | bBlur: ['blur', this.button, this._handleBlur.bind(this)], 578 | bClick: ['click', this.button, this._handleButtonClick.bind(this)], 579 | bKeydown: ['keydown', this.button, this._handleButtonKeydown.bind(this)], 580 | // content listeners 581 | cKeydown: ['keydown', this.content, this._handleContentKeydown.bind(this)], 582 | cTransition: ['transitionend', this.content, this._handleContentTransitionEnd.bind(this)] 583 | }; 584 | for (var key in this._listeners) { 585 | if (this._listeners.hasOwnProperty(key)) { 586 | var listener = this._listeners[key]; 587 | listener[1].addEventListener(listener[0], listener[2]); 588 | } 589 | } 590 | } 591 | }, { 592 | key: "_unbindEvents", 593 | value: function _unbindEvents() { 594 | for (var key in this._listeners) { 595 | if (this._listeners.hasOwnProperty(key)) { 596 | var listener = this._listeners[key]; 597 | listener[1].removeEventListener(listener[0], listener[2]); 598 | } 599 | } 600 | } 601 | }]); 602 | return HandorgelFold; 603 | }(); 604 | 605 | var ID_COUNTER$1 = 0; 606 | var Handorgel = /*#__PURE__*/function (_EventEmitter) { 607 | _inherits(Handorgel, _EventEmitter); 608 | var _super = _createSuper(Handorgel); 609 | function Handorgel(element) { 610 | var _this; 611 | var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; 612 | _classCallCheck(this, Handorgel); 613 | _this = _super.call(this); 614 | if (element.handorgel) { 615 | return _possibleConstructorReturn(_this); 616 | } 617 | _this.element = element; 618 | _this.element.handorgel = _assertThisInitialized(_this); 619 | _this.id = "handorgel".concat(++ID_COUNTER$1); 620 | _this.element.setAttribute('id', _this.id); 621 | _this.folds = []; 622 | _this.options = Object.assign({}, Handorgel.defaultOptions, options); 623 | _this._listeners = {}; 624 | _this._bindEvents(); 625 | _this._initAria(); 626 | _this.update(); 627 | return _this; 628 | } 629 | _createClass(Handorgel, [{ 630 | key: "update", 631 | value: function update() { 632 | this.folds = []; 633 | var headerElements = typeof this.options.headerElements === 'string' ? this.element.querySelectorAll(this.options.headerElements) : this.options.headerElements; 634 | var contentElements = typeof this.options.contentElements === 'string' ? this.element.querySelectorAll(this.options.contentElements) : this.options.contentElements; 635 | for (var i = 0; i < headerElements.length; i = i + 1) { 636 | // get fold instance if there is already one 637 | var fold = headerElements[i].handorgelFold; 638 | 639 | // create new one when header and content exist 640 | if (!fold && headerElements[i] && contentElements[i]) { 641 | fold = new HandorgelFold(this, headerElements[i], contentElements[i]); 642 | } 643 | if (fold) { 644 | this.folds.push(fold); 645 | } 646 | } 647 | } 648 | }, { 649 | key: "focus", 650 | value: function focus(target) { 651 | var foldsLength = this.folds.length; 652 | var currentFocusedIndex = null; 653 | for (var i = 0; i < foldsLength && currentFocusedIndex === null; i++) { 654 | if (this.folds[i].focused) currentFocusedIndex = i; 655 | } 656 | if ((target === 'prev' || target === 'next') && currentFocusedIndex === null) { 657 | target = target === 'prev' ? 'last' : 'first'; 658 | } 659 | if (target === 'prev' && currentFocusedIndex === 0) { 660 | if (!this.options.carouselFocus) return; 661 | target = 'last'; 662 | } 663 | if (target === 'next' && currentFocusedIndex === foldsLength - 1) { 664 | if (!this.options.carouselFocus) return; 665 | target = 'first'; 666 | } 667 | switch (target) { 668 | case 'prev': 669 | this.folds[--currentFocusedIndex].focus(); 670 | break; 671 | case 'next': 672 | this.folds[++currentFocusedIndex].focus(); 673 | break; 674 | case 'last': 675 | this.folds[foldsLength - 1].focus(); 676 | break; 677 | case 'first': 678 | default: 679 | this.folds[0].focus(); 680 | } 681 | } 682 | }, { 683 | key: "destroy", 684 | value: function destroy() { 685 | this.emitEvent('destroy'); 686 | this.element.removeAttribute('id'); 687 | this.folds.forEach(function (fold) { 688 | fold.destroy(); 689 | }); 690 | this._unbindEvents(); 691 | this._cleanAria(); 692 | 693 | // clean reference to handorgel instance 694 | this.element.handorgel = null; 695 | this.emitEvent('destroyed'); 696 | } 697 | }, { 698 | key: "_handleFoldOpen", 699 | value: function _handleFoldOpen(openFold) { 700 | if (this.options.multiSelectable) { 701 | return; 702 | } 703 | this.folds.forEach(function (fold) { 704 | if (openFold !== fold) { 705 | fold.close(); 706 | } 707 | }); 708 | } 709 | }, { 710 | key: "_initAria", 711 | value: function _initAria() { 712 | if (!this.options.ariaEnabled) { 713 | return; 714 | } 715 | if (this.options.multiSelectable) { 716 | this.element.setAttribute('aria-multiselectable', 'true'); 717 | } 718 | } 719 | }, { 720 | key: "_cleanAria", 721 | value: function _cleanAria() { 722 | this.element.removeAttribute('aria-multiselectable'); 723 | } 724 | }, { 725 | key: "_bindEvents", 726 | value: function _bindEvents() { 727 | this._listeners.foldOpen = this._handleFoldOpen.bind(this); 728 | this.on('fold:open', this._listeners.foldOpen); 729 | } 730 | }, { 731 | key: "_unbindEvents", 732 | value: function _unbindEvents() { 733 | this.off('fold:open', this._listeners.foldOpen); 734 | } 735 | }]); 736 | return Handorgel; 737 | }(evEmitter); 738 | Handorgel.defaultOptions = { 739 | keyboardInteraction: true, 740 | multiSelectable: true, 741 | ariaEnabled: true, 742 | collapsible: true, 743 | carouselFocus: true, 744 | initialOpenAttribute: 'data-open', 745 | initialOpenTransition: true, 746 | initialOpenTransitionDelay: 200, 747 | headerElements: '.handorgel__header', 748 | contentElements: '.handorgel__content', 749 | headerOpenClass: 'handorgel__header--open', 750 | contentOpenClass: 'handorgel__content--open', 751 | headerOpenedClass: 'handorgel__header--opened', 752 | contentOpenedClass: 'handorgel__content--opened', 753 | headerDisabledClass: 'handorgel__header--disabled', 754 | contentDisabledClass: 'handorgel__content--disabled', 755 | headerFocusClass: 'handorgel__header--focus', 756 | contentFocusClass: 'handorgel__content--focus' 757 | }; 758 | 759 | return Handorgel; 760 | 761 | })); 762 | -------------------------------------------------------------------------------- /lib/js/umd/handorgel.min.js: -------------------------------------------------------------------------------- 1 | /** handorgel v1.0.0, @license MIT */ 2 | !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e=e||self).handorgel=t()}(this,function(){"use strict";function e(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function t(e,t){for(var n=0;n0&&void 0!==arguments[0])||arguments[0];if(!this.expanded)if(this.handorgel.emitEvent("fold:open",[this]),this.expanded=!0,this.handorgel.options.collapsible||this.disable(),this._updateAria("button","aria-expanded"),this.header.classList.add(this.handorgel.options.headerOpenClass),this.content.classList.add(this.handorgel.options.contentOpenClass),e){var t=this.content.firstElementChild.offsetHeight;this.content.style.height="".concat(t,"px")}else this._opened()}},{key:"close",value:function(){var e=this,t=!(arguments.length>0&&void 0!==arguments[0])||arguments[0];if(this.expanded)if(this.handorgel.emitEvent("fold:close",[this]),this.expanded=!1,this.handorgel.options.collapsible||this.enable(),this._updateAria("button","aria-expanded"),this.header.classList.remove(this.handorgel.options.headerOpenedClass),this.content.classList.remove(this.handorgel.options.contentOpenedClass),t){var n=this.content.firstElementChild.offsetHeight;this.content.style.height="".concat(n,"px"),window.requestAnimationFrame(function(){window.requestAnimationFrame(function(){e.content.style.height="0px"})})}else this._closed()}},{key:"disable",value:function(){this.disabled=!0,this._updateAria("button","aria-disabled"),this.header.classList.add(this.handorgel.options.headerDisabledClass),this.content.classList.add(this.handorgel.options.contentDisabledClass)}},{key:"enable",value:function(){this.disabled=!1,this._updateAria("button","aria-disabled"),this.header.classList.remove(this.handorgel.options.headerDisabledClass),this.content.classList.remove(this.handorgel.options.contentDisabledClass)}},{key:"focus",value:function(){this.button.focus()}},{key:"blur",value:function(){this.button.blur()}},{key:"toggle",value:function(){var e=!(arguments.length>0&&void 0!==arguments[0])||arguments[0];this.expanded?this.close(e):this.open(e)}},{key:"destroy",value:function(){this._unbindEvents(),this._cleanAria(),this.header.classList.remove(this.handorgel.options.headerOpenClass),this.header.classList.remove(this.handorgel.options.headerOpenedClass),this.header.classList.remove(this.handorgel.options.headerFocusClass),this.content.classList.remove(this.handorgel.options.contentOpenClass),this.content.classList.remove(this.handorgel.options.contentOpenedClass),this.content.classList.remove(this.handorgel.options.contentFocusClass),this.content.style.height="0px",this.header.handorgelFold=null,this.content.handorgelFold=null,this.header.removeAttribute("id"),this.content.removeAttribute("id"),this.handorgel=null}},{key:"_opened",value:function(){this.content.style.height="auto",this.header.classList.add(this.handorgel.options.headerOpenedClass),this.content.classList.add(this.handorgel.options.contentOpenedClass),this.handorgel.emitEvent("fold:opened",[this])}},{key:"_closed",value:function(){this.header.classList.remove(this.handorgel.options.headerOpenClass),this.content.classList.remove(this.handorgel.options.contentOpenClass),this.handorgel.emitEvent("fold:closed",[this])}},{key:"_initialOpen",value:function(){var e=this;null===this.header.getAttribute(this.handorgel.options.initialOpenAttribute)&&null===this.content.getAttribute(this.handorgel.options.initialOpenAttribute)||(this.handorgel.options.initialOpenTransition?window.setTimeout(function(){e.open()},this.handorgel.options.initialOpenTransitionDelay):this.open(!1))}},{key:"_initialFocus",value:function(){null!==this.button.getAttribute("autofocus")&&this._handleFocus()}},{key:"_initAria",value:function(){this._updateAria("button"),this._updateAria("content")}},{key:"_cleanAria",value:function(){this._updateAria("button",null,!0),this._updateAria("content",null,!0)}},{key:"_updateAria",value:function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:null,n=arguments.length>2&&void 0!==arguments[2]&&arguments[2];if(this.handorgel.options.ariaEnabled)if(t){var i=u[e][t].call(this);this[e].setAttribute(t,i)}else for(var s in u[e])if(u[e].hasOwnProperty(s))if(n)this[e].removeAttribute(s);else{var o=u[e][s].call(this);this[e].setAttribute(s,o)}}},{key:"_handleContentTransitionEnd",value:function(e){e.target===e.currentTarget&&"height"===e.propertyName&&(this.expanded?this._opened():this._closed())}},{key:"_handleFocus",value:function(){this.focused=!0,this.header.classList.add(this.handorgel.options.headerFocusClass),this.content.classList.add(this.handorgel.options.contentFocusClass),this.handorgel.emitEvent("fold:focus",[this])}},{key:"_handleBlur",value:function(){this.focused=!1,this.header.classList.remove(this.handorgel.options.headerFocusClass),this.content.classList.remove(this.handorgel.options.contentFocusClass),this.handorgel.emitEvent("fold:blur",[this])}},{key:"_handleButtonClick",value:function(e){this.focus(),this.disabled||this.toggle()}},{key:"_handleButtonKeydown",value:function(e){if(this.handorgel.options.keyboardInteraction){var t=null;switch(e.which){case f:t="next";break;case p:t="prev";break;case y:t="first";break;case g:t="last";break;case b:e.ctrlKey&&(t="next");break;case v:e.ctrlKey&&(t="prev")}t&&(e.preventDefault(),this.handorgel.focus(t))}}},{key:"_handleContentKeydown",value:function(e){if(this.handorgel.options.keyboardInteraction&&e.ctrlKey){var t=null;switch(e.which){case b:t="next";break;case v:t="prev"}t&&(e.preventDefault(),this.handorgel.focus(t))}}},{key:"_bindEvents",value:function(){for(var e in this._listeners={bFocus:["focus",this.button,this._handleFocus.bind(this)],bBlur:["blur",this.button,this._handleBlur.bind(this)],bClick:["click",this.button,this._handleButtonClick.bind(this)],bKeydown:["keydown",this.button,this._handleButtonKeydown.bind(this)],cKeydown:["keydown",this.content,this._handleContentKeydown.bind(this)],cTransition:["transitionend",this.content,this._handleContentTransitionEnd.bind(this)]},this._listeners)if(this._listeners.hasOwnProperty(e)){var t=this._listeners[e];t[1].addEventListener(t[0],t[2])}}},{key:"_unbindEvents",value:function(){for(var e in this._listeners)if(this._listeners.hasOwnProperty(e)){var t=this._listeners[e];t[1].removeEventListener(t[0],t[2])}}}]),t}(),m=0,O=function(t){!function(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function");e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,writable:!0,configurable:!0}}),Object.defineProperty(e,"prototype",{writable:!1}),t&&s(e,t)}(l,d);var i=r(l);function l(t){var n,s=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};return e(this,l),n=i.call(this),t.handorgel?a(n):(n.element=t,n.element.handorgel=o(n),n.id="handorgel".concat(++m),n.element.setAttribute("id",n.id),n.folds=[],n.options=Object.assign({},l.defaultOptions,s),n._listeners={},n._bindEvents(),n._initAria(),n.update(),n)}return n(l,[{key:"update",value:function(){this.folds=[];for(var e="string"==typeof this.options.headerElements?this.element.querySelectorAll(this.options.headerElements):this.options.headerElements,t="string"==typeof this.options.contentElements?this.element.querySelectorAll(this.options.contentElements):this.options.contentElements,n=0;n 5%", 93 | "last 2 versions", 94 | "ie 10-11" 95 | ], 96 | "prettier": { 97 | "printWidth": 100, 98 | "singleQuote": true, 99 | "semi": false 100 | }, 101 | "eslintConfig": { 102 | "extends": [ 103 | "standard", 104 | "prettier" 105 | ], 106 | "plugins": [ 107 | "prettier" 108 | ], 109 | "rules": { 110 | "prettier/prettier": "error" 111 | } 112 | }, 113 | "stylelint": { 114 | "extends": [ 115 | "stylelint-config-idiomatic-order", 116 | "stylelint-config-prettier" 117 | ], 118 | "plugins": [ 119 | "stylelint-scss" 120 | ] 121 | }, 122 | "husky": { 123 | "hooks": { 124 | "pre-commit": "run-p format test" 125 | } 126 | }, 127 | "bugs": { 128 | "url": "https://github.com/oncode/handorgel/issues" 129 | }, 130 | "homepage": "https://github.com/oncode/handorgel" 131 | } 132 | -------------------------------------------------------------------------------- /src/js/fold.js: -------------------------------------------------------------------------------- 1 | let ID_COUNTER = {} 2 | 3 | const ARIA_ATTRIBUTES = { 4 | button: { 5 | 'aria-controls': function() { 6 | return this.id + '-content' 7 | }, 8 | 'aria-expanded': function() { 9 | return this.expanded ? 'true' : 'false' 10 | }, 11 | 'aria-disabled': function() { 12 | return this.disabled ? 'true' : 'false' 13 | } 14 | }, 15 | content: { 16 | role: function() { 17 | return 'region' 18 | }, 19 | 'aria-labelledby': function() { 20 | return this.id + '-header' 21 | } 22 | } 23 | } 24 | 25 | const KEYS = { 26 | arrowDown: 40, 27 | arrowUp: 38, 28 | pageUp: 33, 29 | pageDown: 34, 30 | end: 35, 31 | home: 36 32 | } 33 | 34 | export default class HandorgelFold { 35 | constructor(handorgel, header, content) { 36 | if (header.handorgelFold) { 37 | return 38 | } 39 | 40 | this.handorgel = handorgel 41 | this.header = header 42 | this.button = header.firstElementChild 43 | this.content = content 44 | this.header.handorgelFold = this 45 | this.content.handorgelFold = this 46 | 47 | if (!ID_COUNTER[this.handorgel.id]) { 48 | ID_COUNTER[this.handorgel.id] = 0 49 | } 50 | 51 | this.id = `${this.handorgel.id}-fold${++ID_COUNTER[this.handorgel.id]}` 52 | 53 | this.header.setAttribute('id', this.id + '-header') 54 | this.content.setAttribute('id', this.id + '-content') 55 | 56 | this.focused = false 57 | this.expanded = false 58 | this.disabled = false 59 | 60 | this._listeners = {} 61 | 62 | this._bindEvents() 63 | this._initAria() 64 | this._initialOpen() 65 | this._initialFocus() 66 | } 67 | 68 | open(transition = true) { 69 | if (this.expanded) { 70 | return 71 | } 72 | 73 | this.handorgel.emitEvent('fold:open', [this]) 74 | this.expanded = true 75 | 76 | if (!this.handorgel.options.collapsible) { 77 | this.disable() 78 | } 79 | 80 | this._updateAria('button', 'aria-expanded') 81 | 82 | this.header.classList.add(this.handorgel.options.headerOpenClass) 83 | this.content.classList.add(this.handorgel.options.contentOpenClass) 84 | 85 | if (!transition) { 86 | this._opened() 87 | } else { 88 | const height = this.content.firstElementChild.offsetHeight 89 | this.content.style.height = `${height}px` 90 | } 91 | } 92 | 93 | close(transition = true) { 94 | if (!this.expanded) { 95 | return 96 | } 97 | 98 | this.handorgel.emitEvent('fold:close', [this]) 99 | this.expanded = false 100 | 101 | if (!this.handorgel.options.collapsible) { 102 | this.enable() 103 | } 104 | 105 | this._updateAria('button', 'aria-expanded') 106 | 107 | this.header.classList.remove(this.handorgel.options.headerOpenedClass) 108 | this.content.classList.remove(this.handorgel.options.contentOpenedClass) 109 | 110 | if (!transition) { 111 | this._closed() 112 | } else { 113 | // if we want to transition when closing we 114 | // have to set the current height and replace auto 115 | const height = this.content.firstElementChild.offsetHeight 116 | this.content.style.height = `${height}px` 117 | 118 | window.requestAnimationFrame(() => { 119 | window.requestAnimationFrame(() => { 120 | this.content.style.height = '0px' 121 | }) 122 | }) 123 | } 124 | } 125 | 126 | disable() { 127 | this.disabled = true 128 | this._updateAria('button', 'aria-disabled') 129 | this.header.classList.add(this.handorgel.options.headerDisabledClass) 130 | this.content.classList.add(this.handorgel.options.contentDisabledClass) 131 | } 132 | 133 | enable() { 134 | this.disabled = false 135 | this._updateAria('button', 'aria-disabled') 136 | this.header.classList.remove(this.handorgel.options.headerDisabledClass) 137 | this.content.classList.remove(this.handorgel.options.contentDisabledClass) 138 | } 139 | 140 | focus() { 141 | this.button.focus() 142 | } 143 | 144 | blur() { 145 | this.button.blur() 146 | } 147 | 148 | toggle(transition = true) { 149 | if (this.expanded) { 150 | this.close(transition) 151 | } else { 152 | this.open(transition) 153 | } 154 | } 155 | 156 | destroy() { 157 | this._unbindEvents() 158 | this._cleanAria() 159 | 160 | // clean classes 161 | this.header.classList.remove(this.handorgel.options.headerOpenClass) 162 | this.header.classList.remove(this.handorgel.options.headerOpenedClass) 163 | this.header.classList.remove(this.handorgel.options.headerFocusClass) 164 | 165 | this.content.classList.remove(this.handorgel.options.contentOpenClass) 166 | this.content.classList.remove(this.handorgel.options.contentOpenedClass) 167 | this.content.classList.remove(this.handorgel.options.contentFocusClass) 168 | 169 | // hide content 170 | this.content.style.height = '0px' 171 | 172 | // clean reference to this instance 173 | this.header.handorgelFold = null 174 | this.content.handorgelFold = null 175 | 176 | // remove ids 177 | this.header.removeAttribute('id') 178 | this.content.removeAttribute('id') 179 | 180 | // clean reference to handorgel instance 181 | this.handorgel = null 182 | } 183 | 184 | _opened() { 185 | this.content.style.height = 'auto' 186 | this.header.classList.add(this.handorgel.options.headerOpenedClass) 187 | this.content.classList.add(this.handorgel.options.contentOpenedClass) 188 | this.handorgel.emitEvent('fold:opened', [this]) 189 | } 190 | 191 | _closed() { 192 | this.header.classList.remove(this.handorgel.options.headerOpenClass) 193 | this.content.classList.remove(this.handorgel.options.contentOpenClass) 194 | this.handorgel.emitEvent('fold:closed', [this]) 195 | } 196 | 197 | _initialOpen() { 198 | if ( 199 | this.header.getAttribute(this.handorgel.options.initialOpenAttribute) !== null || 200 | this.content.getAttribute(this.handorgel.options.initialOpenAttribute) !== null 201 | ) { 202 | if (this.handorgel.options.initialOpenTransition) { 203 | window.setTimeout(() => { 204 | this.open() 205 | }, this.handorgel.options.initialOpenTransitionDelay) 206 | } else { 207 | this.open(false) 208 | } 209 | } 210 | } 211 | 212 | _initialFocus() { 213 | if (this.button.getAttribute('autofocus') === null) { 214 | return 215 | } 216 | 217 | // to ensure focus styles if autofocus was applied 218 | // before focus listener was added 219 | this._handleFocus() 220 | } 221 | 222 | _initAria() { 223 | this._updateAria('button') 224 | this._updateAria('content') 225 | } 226 | 227 | _cleanAria() { 228 | this._updateAria('button', null, true) 229 | this._updateAria('content', null, true) 230 | } 231 | 232 | _updateAria(element, property = null, remove = false) { 233 | if (!this.handorgel.options.ariaEnabled) { 234 | return 235 | } 236 | 237 | if (property) { 238 | const newValue = ARIA_ATTRIBUTES[element][property].call(this) 239 | this[element].setAttribute(property, newValue) 240 | } else { 241 | for (let property in ARIA_ATTRIBUTES[element]) { 242 | if (ARIA_ATTRIBUTES[element].hasOwnProperty(property)) { 243 | if (remove) { 244 | this[element].removeAttribute(property) 245 | } else { 246 | const newValue = ARIA_ATTRIBUTES[element][property].call(this) 247 | this[element].setAttribute(property, newValue) 248 | } 249 | } 250 | } 251 | } 252 | } 253 | 254 | _handleContentTransitionEnd(e) { 255 | if (e.target === e.currentTarget && e.propertyName === 'height') { 256 | if (this.expanded) { 257 | this._opened() 258 | } else { 259 | this._closed() 260 | } 261 | } 262 | } 263 | 264 | _handleFocus() { 265 | this.focused = true 266 | this.header.classList.add(this.handorgel.options.headerFocusClass) 267 | this.content.classList.add(this.handorgel.options.contentFocusClass) 268 | this.handorgel.emitEvent('fold:focus', [this]) 269 | } 270 | 271 | _handleBlur() { 272 | this.focused = false 273 | this.header.classList.remove(this.handorgel.options.headerFocusClass) 274 | this.content.classList.remove(this.handorgel.options.contentFocusClass) 275 | this.handorgel.emitEvent('fold:blur', [this]) 276 | } 277 | 278 | _handleButtonClick(e) { 279 | // ensure focus is on button (click is not seting focus on firefox mac) 280 | this.focus() 281 | 282 | if (this.disabled) { 283 | return 284 | } 285 | 286 | this.toggle() 287 | } 288 | 289 | _handleButtonKeydown(e) { 290 | if (!this.handorgel.options.keyboardInteraction) { 291 | return 292 | } 293 | 294 | let action = null 295 | 296 | switch (e.which) { 297 | case KEYS.arrowDown: 298 | action = 'next' 299 | break 300 | case KEYS.arrowUp: 301 | action = 'prev' 302 | break 303 | case KEYS.home: 304 | action = 'first' 305 | break 306 | case KEYS.end: 307 | action = 'last' 308 | break 309 | case KEYS.pageDown: 310 | if (e.ctrlKey) { 311 | action = 'next' 312 | } 313 | break 314 | case KEYS.pageUp: 315 | if (e.ctrlKey) { 316 | action = 'prev' 317 | } 318 | break 319 | } 320 | 321 | if (action) { 322 | e.preventDefault() 323 | this.handorgel.focus(action) 324 | } 325 | } 326 | 327 | _handleContentKeydown(e) { 328 | if (!this.handorgel.options.keyboardInteraction || !e.ctrlKey) { 329 | return 330 | } 331 | 332 | let action = null 333 | 334 | switch (e.which) { 335 | case KEYS.pageDown: 336 | action = 'next' 337 | break 338 | case KEYS.pageUp: 339 | action = 'prev' 340 | break 341 | } 342 | 343 | if (action) { 344 | e.preventDefault() 345 | this.handorgel.focus(action) 346 | } 347 | } 348 | 349 | _bindEvents() { 350 | this._listeners = { 351 | // button listeners 352 | bFocus: ['focus', this.button, this._handleFocus.bind(this)], 353 | bBlur: ['blur', this.button, this._handleBlur.bind(this)], 354 | bClick: ['click', this.button, this._handleButtonClick.bind(this)], 355 | bKeydown: ['keydown', this.button, this._handleButtonKeydown.bind(this)], 356 | // content listeners 357 | cKeydown: ['keydown', this.content, this._handleContentKeydown.bind(this)], 358 | cTransition: ['transitionend', this.content, this._handleContentTransitionEnd.bind(this)] 359 | } 360 | 361 | for (let key in this._listeners) { 362 | if (this._listeners.hasOwnProperty(key)) { 363 | const listener = this._listeners[key] 364 | listener[1].addEventListener(listener[0], listener[2]) 365 | } 366 | } 367 | } 368 | 369 | _unbindEvents() { 370 | for (let key in this._listeners) { 371 | if (this._listeners.hasOwnProperty(key)) { 372 | const listener = this._listeners[key] 373 | listener[1].removeEventListener(listener[0], listener[2]) 374 | } 375 | } 376 | } 377 | } 378 | -------------------------------------------------------------------------------- /src/js/index.js: -------------------------------------------------------------------------------- 1 | import EventEmitter from 'ev-emitter' 2 | import Fold from './fold' 3 | 4 | let ID_COUNTER = 0 5 | 6 | export default class Handorgel extends EventEmitter { 7 | constructor(element, options = {}) { 8 | super() 9 | 10 | if (element.handorgel) { 11 | return 12 | } 13 | 14 | this.element = element 15 | this.element.handorgel = this 16 | this.id = `handorgel${++ID_COUNTER}` 17 | this.element.setAttribute('id', this.id) 18 | this.folds = [] 19 | this.options = Object.assign({}, Handorgel.defaultOptions, options) 20 | 21 | this._listeners = {} 22 | 23 | this._bindEvents() 24 | this._initAria() 25 | this.update() 26 | } 27 | 28 | update() { 29 | this.folds = [] 30 | 31 | const headerElements = 32 | typeof this.options.headerElements === 'string' 33 | ? this.element.querySelectorAll(this.options.headerElements) 34 | : this.options.headerElements 35 | 36 | const contentElements = 37 | typeof this.options.contentElements === 'string' 38 | ? this.element.querySelectorAll(this.options.contentElements) 39 | : this.options.contentElements 40 | 41 | for (let i = 0; i < headerElements.length; i = i + 1) { 42 | // get fold instance if there is already one 43 | let fold = headerElements[i].handorgelFold 44 | 45 | // create new one when header and content exist 46 | if (!fold && headerElements[i] && contentElements[i]) { 47 | fold = new Fold(this, headerElements[i], contentElements[i]) 48 | } 49 | 50 | if (fold) { 51 | this.folds.push(fold) 52 | } 53 | } 54 | } 55 | 56 | focus(target) { 57 | const foldsLength = this.folds.length 58 | let currentFocusedIndex = null 59 | 60 | for (let i = 0; i < foldsLength && currentFocusedIndex === null; i++) { 61 | if (this.folds[i].focused) currentFocusedIndex = i 62 | } 63 | 64 | if ((target === 'prev' || target === 'next') && currentFocusedIndex === null) { 65 | target = target === 'prev' ? 'last' : 'first' 66 | } 67 | 68 | if (target === 'prev' && currentFocusedIndex === 0) { 69 | if (!this.options.carouselFocus) return 70 | target = 'last' 71 | } 72 | 73 | if (target === 'next' && currentFocusedIndex === foldsLength - 1) { 74 | if (!this.options.carouselFocus) return 75 | target = 'first' 76 | } 77 | 78 | switch (target) { 79 | case 'prev': 80 | this.folds[--currentFocusedIndex].focus() 81 | break 82 | case 'next': 83 | this.folds[++currentFocusedIndex].focus() 84 | break 85 | case 'last': 86 | this.folds[foldsLength - 1].focus() 87 | break 88 | case 'first': 89 | default: 90 | this.folds[0].focus() 91 | } 92 | } 93 | 94 | destroy() { 95 | this.emitEvent('destroy') 96 | this.element.removeAttribute('id') 97 | 98 | this.folds.forEach(fold => { 99 | fold.destroy() 100 | }) 101 | 102 | this._unbindEvents() 103 | this._cleanAria() 104 | 105 | // clean reference to handorgel instance 106 | this.element.handorgel = null 107 | this.emitEvent('destroyed') 108 | } 109 | 110 | _handleFoldOpen(openFold) { 111 | if (this.options.multiSelectable) { 112 | return 113 | } 114 | 115 | this.folds.forEach(fold => { 116 | if (openFold !== fold) { 117 | fold.close() 118 | } 119 | }) 120 | } 121 | 122 | _initAria() { 123 | if (!this.options.ariaEnabled) { 124 | return 125 | } 126 | 127 | if (this.options.multiSelectable) { 128 | this.element.setAttribute('aria-multiselectable', 'true') 129 | } 130 | } 131 | 132 | _cleanAria() { 133 | this.element.removeAttribute('aria-multiselectable') 134 | } 135 | 136 | _bindEvents() { 137 | this._listeners.foldOpen = this._handleFoldOpen.bind(this) 138 | this.on('fold:open', this._listeners.foldOpen) 139 | } 140 | 141 | _unbindEvents() { 142 | this.off('fold:open', this._listeners.foldOpen) 143 | } 144 | } 145 | 146 | Handorgel.defaultOptions = { 147 | keyboardInteraction: true, 148 | multiSelectable: true, 149 | ariaEnabled: true, 150 | collapsible: true, 151 | carouselFocus: true, 152 | 153 | initialOpenAttribute: 'data-open', 154 | initialOpenTransition: true, 155 | initialOpenTransitionDelay: 200, 156 | 157 | headerElements: '.handorgel__header', 158 | contentElements: '.handorgel__content', 159 | 160 | headerOpenClass: 'handorgel__header--open', 161 | contentOpenClass: 'handorgel__content--open', 162 | 163 | headerOpenedClass: 'handorgel__header--opened', 164 | contentOpenedClass: 'handorgel__content--opened', 165 | 166 | headerDisabledClass: 'handorgel__header--disabled', 167 | contentDisabledClass: 'handorgel__content--disabled', 168 | 169 | headerFocusClass: 'handorgel__header--focus', 170 | contentFocusClass: 'handorgel__content--focus' 171 | } 172 | -------------------------------------------------------------------------------- /src/scss/.css/style.css: -------------------------------------------------------------------------------- 1 | .handorgel { 2 | display: block; 3 | width: 100%; 4 | border: 1px solid #eee; 5 | border-top: none; 6 | } 7 | .handorgel__header { 8 | display: block; 9 | margin: 0; 10 | } 11 | .handorgel__header--open .handorgel__header__button { 12 | background-color: #eee; 13 | } 14 | .handorgel__header--focus .handorgel__header__button { 15 | background-color: #dfdfdf; 16 | outline: none; 17 | } 18 | .handorgel__header__button { 19 | display: block; 20 | width: 100%; 21 | padding: 20px 24px; 22 | margin: 0; 23 | border: none; 24 | border-top: 1px solid #eee; 25 | background-color: #fff; 26 | border-radius: 0; 27 | color: inherit; 28 | cursor: pointer; 29 | font-size: inherit; 30 | text-align: left; 31 | transition: background-color 0.2s ease; 32 | user-select: none; 33 | } 34 | .handorgel__header__button::-moz-focus-inner { 35 | border: 0; 36 | } 37 | .handorgel__content { 38 | display: none; 39 | overflow: hidden; 40 | height: 0; 41 | border-top: 1px solid #eee; 42 | background-color: #fff; 43 | transition: height 0.1s ease 0.1s; 44 | } 45 | .handorgel__content--open { 46 | display: block; 47 | transition: height 0.2s ease; 48 | } 49 | .handorgel__content--opened { 50 | overflow: visible; 51 | } 52 | .handorgel__content__inner { 53 | padding: 20px 24px; 54 | opacity: 0; 55 | transition: opacity 0.1s ease; 56 | } 57 | .handorgel__content--opened .handorgel__content__inner { 58 | opacity: 1; 59 | transition: opacity 0.3s ease; 60 | } 61 | 62 | /*# sourceMappingURL=style.css.map */ 63 | -------------------------------------------------------------------------------- /src/scss/.css/style.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sourceRoot":"","sources":["../style.scss"],"names":[],"mappings":"AAoDE;EACE;EACA;EACA;EACA;;AAGF;EACE;EACA;;AAGF;EACE,kBA3D+C;;AA8DjD;EACE,kBA9DgD;EA+DhD;;AAGF;EACE;EACA;EACA,SAnEgC;EAoEhC;EACA;EACA;EACA,kBA3EyC;EA4EzC;EACA;EACA;EACA;EACA;EACA,YAzDmC;EA0DnC;;AAEA;EACE;;AAIJ;EACE;EACA;EACA;EACA;EACA,kBAvF4B;EAwF5B,YA1E4B;;AA4E5B;EACE;EACA,YA5DgC;;AA+DlC;EACE;;AAIJ;EACE,SAvGgC;EAwGhC;EACA,YAlFmC;;AAqFrC;EACE;EACA,YAzE2C","file":"style.css"} -------------------------------------------------------------------------------- /src/scss/style.scss: -------------------------------------------------------------------------------- 1 | // Variables 2 | 3 | $handorgel-border-color: #eee !default; 4 | $handorgel-border-width: 1px !default; 5 | 6 | $handorgel__header__button-background-color: #fff !default; 7 | $handorgel__header--open__button-background-color: #eee !default; 8 | $handorgel__header--focus__button-background-color: #dfdfdf !default; 9 | 10 | $handorgel__header__button-padding: 20px 24px !default; 11 | $handorgel__content__inner-padding: 20px 24px !default; 12 | 13 | $handorgel__content-background: #fff !default; 14 | 15 | // Variables for closing transition 16 | 17 | $handorgel__content-transition-opacity-time: 0.1s !default; 18 | $handorgel__content-transition-opacity-method: ease !default; 19 | 20 | $handorgel__content-transition-height-time: 0.1s !default; 21 | $handorgel__content-transition-height-delay: $handorgel__content-transition-opacity-time !default; 22 | $handorgel__content-transition-height-method: ease !default; 23 | 24 | $handorgel__header__button-transition-background-color-time: 0.2s !default; 25 | $handorgel__header__button-transition-background-color-method: ease !default; 26 | 27 | $handorgel__content-transition: height $handorgel__content-transition-height-time 28 | $handorgel__content-transition-height-method $handorgel__content-transition-height-delay !default; 29 | 30 | $handorgel__header__button-transition: background-color 31 | $handorgel__header__button-transition-background-color-time 32 | $handorgel__header__button-transition-background-color-method !default; 33 | 34 | $handorgel__content__inner-transition: opacity $handorgel__content-transition-opacity-time 35 | $handorgel__content-transition-opacity-method !default; 36 | 37 | // Variables for opening transition 38 | 39 | $handorgel__content--open-transition-height-time: 0.2s !default; 40 | $handorgel__content--open-transition-height-method: ease !default; 41 | 42 | $handorgel__content--open-transition-opacity-time: 0.3s !default; 43 | $handorgel__content--open-transition-opacity-method: ease !default; 44 | 45 | $handorgel__content--open-transition: height $handorgel__content--open-transition-height-time 46 | $handorgel__content--open-transition-height-method !default; 47 | 48 | $handorgel__content--opened__inner-transition: opacity 49 | $handorgel__content--open-transition-opacity-time 50 | $handorgel__content--open-transition-opacity-method !default; 51 | 52 | .handorgel { 53 | & { 54 | display: block; 55 | width: 100%; 56 | border: $handorgel-border-width solid $handorgel-border-color; 57 | border-top: none; 58 | } 59 | 60 | &__header { 61 | display: block; 62 | margin: 0; 63 | } 64 | 65 | &__header--open &__header__button { 66 | background-color: $handorgel__header--open__button-background-color; 67 | } 68 | 69 | &__header--focus &__header__button { 70 | background-color: $handorgel__header--focus__button-background-color; 71 | outline: none; 72 | } 73 | 74 | &__header__button { 75 | display: block; 76 | width: 100%; 77 | padding: $handorgel__header__button-padding; 78 | margin: 0; 79 | border: none; 80 | border-top: $handorgel-border-width solid $handorgel-border-color; 81 | background-color: $handorgel__header__button-background-color; 82 | border-radius: 0; 83 | color: inherit; 84 | cursor: pointer; 85 | font-size: inherit; 86 | text-align: left; 87 | transition: $handorgel__header__button-transition; 88 | user-select: none; 89 | 90 | &::-moz-focus-inner { 91 | border: 0; 92 | } 93 | } 94 | 95 | &__content { 96 | display: none; 97 | overflow: hidden; 98 | height: 0; 99 | border-top: $handorgel-border-width solid $handorgel-border-color; 100 | background-color: $handorgel__content-background; 101 | transition: $handorgel__content-transition; 102 | 103 | &--open { 104 | display: block; 105 | transition: $handorgel__content--open-transition; 106 | } 107 | 108 | &--opened { 109 | overflow: visible; 110 | } 111 | } 112 | 113 | &__content__inner { 114 | padding: $handorgel__content__inner-padding; 115 | opacity: 0; 116 | transition: $handorgel__content__inner-transition; 117 | } 118 | 119 | &__content--opened &__content__inner { 120 | opacity: 1; 121 | transition: $handorgel__content--opened__inner-transition; 122 | } 123 | } 124 | --------------------------------------------------------------------------------