├── src ├── js │ ├── main.js │ ├── transition-end.js │ ├── array-from-polyfill.js │ └── badger-accordion.js ├── css │ ├── badger-accordion.css │ └── badger-accordion-demo.css └── scss │ └── badger-accordion.scss ├── .gitignore ├── dist ├── badger-accordion.css ├── badger-accordion.scss ├── array-from-polyfill.js ├── badger-accordion.min.js ├── badger-accordion.esm.min.js ├── badger-accordion.esm.js └── badger-accordion.js ├── example ├── css │ ├── badger-accordion.css │ └── badger-accordion-demo.css ├── scss │ ├── badger-accordion.scss │ ├── _reset.scss │ └── badger-accordion-demo.scss ├── js │ ├── behaviour.js │ └── app.js └── index.html ├── .babelrc ├── .eslintrc.json ├── LICENSE ├── .github └── ISSUE_TEMPLATE ├── rollup.config.js ├── package.json ├── CHANGELOG.md └── README.md /src/js/main.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Importing accordion 4 | import BadgerAccordion from 'badger-accordion'; 5 | 6 | // Creating a new instance of the accordion 7 | const accordion = new BadgerAccordion('.js-badger-accordion'); 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /src/css/badger-accordion 2 | /src/css/badger-accordion.css.map 3 | badger-accordion-demo.css.map 4 | /node_modules 5 | .DS_Store 6 | /example/.sass-cache 7 | /src/.sass-cache 8 | *.map 9 | /offline-issues 10 | /audit-resolve.json 11 | -------------------------------------------------------------------------------- /dist/badger-accordion.css: -------------------------------------------------------------------------------- 1 | .badger-accordion__panel{max-height:75vh;overflow:hidden}.badger-accordion__panel.-ba-is-hidden{max-height:0 !important;visibility:hidden}.badger-accordion--initalised .badger-accordion__panel{transition:max-height ease-in-out .2s} 2 | -------------------------------------------------------------------------------- /example/css/badger-accordion.css: -------------------------------------------------------------------------------- 1 | .badger-accordion__panel{max-height:75vh;overflow:hidden}.badger-accordion__panel.-ba-is-hidden{max-height:0 !important;visibility:hidden}.badger-accordion--initalised .badger-accordion__panel{transition:max-height ease-in-out .2s} 2 | -------------------------------------------------------------------------------- /src/css/badger-accordion.css: -------------------------------------------------------------------------------- 1 | .badger-accordion__panel{max-height:75vh;overflow:hidden}.badger-accordion__panel.-ba-is-hidden{max-height:0 !important;visibility:hidden}.badger-accordion--initalised .badger-accordion__panel{transition:max-height ease-in-out .2s} 2 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["@babel/preset-env", { 4 | "modules": false, 5 | "targets": { 6 | "browsers": ["last 2 versions", "ie >= 11"] 7 | } 8 | }] 9 | ], 10 | "plugins": ["@babel/transform-object-assign"] 11 | } 12 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es6": true 5 | }, 6 | "extends": "eslint:recommended", 7 | "parserOptions": { 8 | "sourceType": "module" 9 | }, 10 | "rules": { 11 | "indent": [ 12 | "error", 13 | 4 14 | ], 15 | "linebreak-style": [ 16 | "error", 17 | "unix" 18 | ], 19 | "quotes": [ 20 | "error", 21 | "single" 22 | ], 23 | "semi": [ 24 | "error", 25 | "always" 26 | ] 27 | } 28 | } -------------------------------------------------------------------------------- /dist/badger-accordion.scss: -------------------------------------------------------------------------------- 1 | // ========================================================================== 2 | // 3 | // ACCORDION 4 | // 5 | // Description: Base accordion styles that are ESSENTIAL for the accordion 6 | // 7 | // ========================================================================== 8 | 9 | // ========================================================================== 10 | // # BASE 11 | // ========================================================================== 12 | 13 | // .badger-accordion {} 14 | 15 | .badger-accordion__panel { 16 | max-height: 75vh; 17 | overflow: hidden; 18 | 19 | // scss-lint:disable ImportantRule 20 | &.-ba-is-hidden { 21 | max-height: 0 !important; 22 | visibility: hidden; 23 | } 24 | 25 | // transition is added via `badger-accordion--initalised` to stop animation on initalition 26 | .badger-accordion--initalised & { transition: max-height ease-in-out 0.2s; } 27 | } 28 | -------------------------------------------------------------------------------- /src/scss/badger-accordion.scss: -------------------------------------------------------------------------------- 1 | // ========================================================================== 2 | // 3 | // ACCORDION 4 | // 5 | // Description: Base accordion styles that are ESSENTIAL for the accordion 6 | // 7 | // ========================================================================== 8 | 9 | // ========================================================================== 10 | // # BASE 11 | // ========================================================================== 12 | 13 | // .badger-accordion {} 14 | 15 | .badger-accordion__panel { 16 | max-height: 75vh; 17 | overflow: hidden; 18 | 19 | // scss-lint:disable ImportantRule 20 | &.-ba-is-hidden { 21 | max-height: 0 !important; 22 | visibility: hidden; 23 | } 24 | 25 | // transition is added via `badger-accordion--initalised` to stop animation on initalition 26 | .badger-accordion--initalised & { transition: max-height ease-in-out 0.2s; } 27 | } 28 | -------------------------------------------------------------------------------- /example/scss/badger-accordion.scss: -------------------------------------------------------------------------------- 1 | // ========================================================================== 2 | // 3 | // ACCORDION 4 | // 5 | // Description: Base accordion styles that are ESSENTIAL for the accordion 6 | // 7 | // ========================================================================== 8 | 9 | // ========================================================================== 10 | // # BASE 11 | // ========================================================================== 12 | 13 | // .badger-accordion {} 14 | 15 | .badger-accordion__panel { 16 | max-height: 75vh; 17 | overflow: hidden; 18 | 19 | // scss-lint:disable ImportantRule 20 | &.-ba-is-hidden { 21 | max-height: 0 !important; 22 | visibility: hidden; 23 | } 24 | 25 | // transition is added via `badger-accordion--initalised` to stop animation on initalition 26 | .badger-accordion--initalised & { transition: max-height ease-in-out 0.2s; } 27 | } 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Stuart John Nelson 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | 3 | Please answer the following questions for yourself before submitting an issue. **YOU MAY DELETE THE PREREQUISITES SECTION.** 4 | 5 | - [ ] I am running the latest version 6 | - [ ] I checked the documentation and found no answer 7 | - [ ] I checked to make sure that this issue has not already been filed 8 | - [ ] I'm reporting the issue to the correct repository (for multi-repository projects) 9 | 10 | # Expected Behavior 11 | 12 | Please describe the behavior you are expecting 13 | 14 | # Current Behavior 15 | 16 | What is the current behavior? 17 | 18 | # Failure Information (for bugs) 19 | 20 | Please help provide information about the failure if this is a bug. If it is not a bug, please remove the rest of this template. 21 | 22 | ## Steps to Reproduce 23 | 24 | Please provide detailed steps for reproducing the issue. 25 | 26 | 1. step 1 27 | 2. step 2 28 | 3. you get it... 29 | 30 | ## Context 31 | 32 | Please provide any relevant information about your setup. This is important in case the issue is not reproducible except for under certain conditions. 33 | 34 | * Operating System: 35 | * Browser/s & version/s: 36 | * NPM version: 37 | * Node version: 38 | 39 | ## Failure Logs 40 | 41 | Please include any relevant log snippets or files here. 42 | -------------------------------------------------------------------------------- /example/js/behaviour.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Importing accordion 4 | import BadgerAccordion from 'dist/badger-accordion'; 5 | 6 | // Creating a new instance of the accordion usign DOM node 7 | // ================================ 8 | // const accordionDomNode = document.querySelector('.js-badger-accordion'); 9 | 10 | // const accordion = new BadgerAccordion(accordionDomNode); 11 | 12 | /* eslint-disable no-console */ 13 | // console.log(accordion.getState([0])); 14 | // accordion.open(0); // Opens the first accordion panel 15 | 16 | 17 | 18 | 19 | // Creating a new instance of the accordion usign DOM node 20 | // ================================ 21 | const accordions = document.querySelectorAll('.js-badger-accordion'); 22 | 23 | Array.from(accordions).forEach((accordion) => { 24 | const ba = new BadgerAccordion(accordion); 25 | 26 | /* eslint-disable no-console */ 27 | console.log(ba.getState([0])); 28 | }); 29 | 30 | 31 | 32 | 33 | 34 | // Creating a new instance of the accordion usign CSS selector 35 | // ================================ 36 | // const accordionCssSelector = new BadgerAccordion('.js-badger-accordion'); 37 | 38 | // API Examples 39 | /* eslint-disable no-console */ 40 | // console.log(accordionCssSelector.getState([0])); 41 | // accordionCssSelector.open( 0 ); 42 | -------------------------------------------------------------------------------- /example/scss/_reset.scss: -------------------------------------------------------------------------------- 1 | html, body, div, span, applet, object, iframe, 2 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, 3 | a, abbr, acronym, address, big, cite, code, 4 | del, dfn, em, img, ins, kbd, q, s, samp, 5 | small, strike, strong, sub, sup, tt, var, 6 | b, u, i, center, 7 | dl, dt, dd, ol, ul, li, 8 | fieldset, form, label, legend, 9 | table, caption, tbody, tfoot, thead, tr, th, td, 10 | article, aside, canvas, details, embed, 11 | figure, figcaption, footer, header, hgroup, 12 | menu, nav, output, ruby, section, summary, 13 | time, mark, audio, video { 14 | margin: 0; 15 | padding: 0; 16 | border: 0; 17 | font-size: 100%; 18 | vertical-align: baseline; 19 | font: inherit; 20 | } 21 | /* HTML5 display-role reset for older browsers */ 22 | article, aside, details, figcaption, figure, 23 | footer, header, hgroup, menu, nav, section { 24 | display: block; 25 | } 26 | body { 27 | line-height: 1; 28 | } 29 | ol, ul { 30 | list-style: none; 31 | } 32 | blockquote, q { 33 | quotes: none; 34 | } 35 | blockquote:before, blockquote:after, 36 | q:before, q:after { 37 | content: ''; 38 | content: none; 39 | } 40 | table { 41 | border-collapse: collapse; 42 | border-spacing: 0; 43 | } 44 | html { 45 | -webkit-font-smoothing: antialiased; 46 | box-sizing: border-box; 47 | } 48 | *, *:before, *:after { 49 | box-sizing: inherit; 50 | } 51 | img { 52 | display: block; 53 | } 54 | a, a:hover, a:active, a:visited { 55 | text-decoration: none; 56 | } 57 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import babel from 'rollup-plugin-babel'; 2 | import eslint from 'rollup-plugin-eslint'; 3 | import resolve from 'rollup-plugin-node-resolve'; 4 | import commonjs from 'rollup-plugin-commonjs'; 5 | import includePaths from 'rollup-plugin-includepaths'; 6 | import uglify from 'rollup-plugin-uglify-es'; 7 | import replace from 'rollup-plugin-replace'; 8 | import copy from 'rollup-plugin-copy'; 9 | 10 | // Default output options 11 | let output = [ 12 | { 13 | file: (process.env.NODE_ENV === 'production' && 'dist/badger-accordion.min.js' || process.env.NODE_ENV === 'example' && 'example/js/app.js' || 'dist/badger-accordion.js'), 14 | format: 'umd' 15 | }, 16 | { 17 | file: (process.env.NODE_ENV === 'production' && 'dist/badger-accordion.esm.min.js' || 'dist/badger-accordion.esm.js'), 18 | format: 'es', 19 | } 20 | ]; 21 | 22 | // Setting output like this to avoid `.esm.js` from having the `/example` code in it 23 | if(process.env.NODE_ENV === 'example') { 24 | output.splice(1, 1); 25 | } 26 | 27 | export default { 28 | input: (process.env.NODE_ENV === 'example' && 'example/js/behaviour.js' || 'src/js/badger-accordion.js'), 29 | sourcemap: 'false', 30 | name: 'BadgerAccordion', 31 | output: output, 32 | plugins: [ 33 | resolve(), 34 | commonjs(), 35 | eslint({ 36 | exclude: ['src/css/**', 'src/scss/**'] 37 | }), 38 | babel({exclude: 'node_modules/**'}), 39 | includePaths(), 40 | replace({ 41 | ENV: JSON.stringify(process.env.NODE_ENV || 'development'), 42 | }), 43 | (process.env.NODE_ENV === 'example' && 44 | copy({ 45 | 'src/css/badger-accordion.css' : 'example/css/badger-accordion.css', 46 | 'src/scss/badger-accordion.scss' : 'example/scss/badger-accordion.scss' 47 | }) 48 | ), 49 | (process.env.NODE_ENV === 'production' && uglify()), 50 | (process.env.NODE_ENV === 'production' && 51 | copy({ 52 | 'src/js/array-from-polyfill.js' : 'dist/array-from-polyfill.js', 53 | 'src/css/badger-accordion.css' : 'dist/badger-accordion.css', 54 | 'src/scss/badger-accordion.scss' : 'dist/badger-accordion.scss' 55 | }) 56 | ) 57 | ] 58 | }; 59 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "badger-accordion", 3 | "version": "1.2.4", 4 | "description": "An accessible vanilla JS accordion with extensible API.", 5 | "main": "dist/badger-accordion.js", 6 | "module": "dist/badger-accordion.esm.js", 7 | "files": [ 8 | "dist", 9 | "!.DS_Store" 10 | ], 11 | "scripts": { 12 | "basic-styles": "sass ./src/scss/badger-accordion.scss:./src/css/badger-accordion.css --style compressed --no-source-map", 13 | "example-styles": "sass ./example/scss/badger-accordion.scss:./example/css/badger-accordion.css --style compressed --no-source-map", 14 | "dev": "NODE_ENV=develop ./node_modules/.bin/rollup -c", 15 | "watch": "NODE_ENV=develop ./node_modules/.bin/rollup -c --watch & php -S 127.0.0.1:3000", 16 | "example": "npm run example-styles && NODE_ENV=example ./node_modules/.bin/rollup -c", 17 | "example-watch": "NODE_ENV=example ./node_modules/.bin/rollup -c --watch", 18 | "build": "npm run basic-styles && NODE_ENV=production ./node_modules/.bin/rollup -c", 19 | "pre-publish": "npm run dev && npm run basic-styles && npm run build && npm run example-styles && npm run example" 20 | }, 21 | "repository": { 22 | "type": "git", 23 | "url": "git+https://github.com/stuartjnelson/badger-accordion.git" 24 | }, 25 | "keywords": [ 26 | "accordion", 27 | "accessible", 28 | "accessible", 29 | "accordion", 30 | "extendable", 31 | "accordion" 32 | ], 33 | "author": "Stuart j Nelson", 34 | "license": "MIT", 35 | "bugs": { 36 | "url": "https://github.com/stuartjnelson/badger-accordion/issues" 37 | }, 38 | "homepage": "https://github.com/stuartjnelson/badger-accordion#readme", 39 | "devDependencies": { 40 | "@babel/core": "^7.7.2", 41 | "@babel/plugin-transform-object-assign": "^7.2.0", 42 | "@babel/preset-env": "^7.7.1", 43 | "babel-plugin-external-helpers": "^6.22.0", 44 | "babel-plugin-transform-object-assign": "^6.22.0", 45 | "babel-preset-env": "^1.7.0", 46 | "braces": "^3.0.2", 47 | "debug": "^3.2.6", 48 | "install": "^0.10.4", 49 | "js-yaml": ">=3.13.1", 50 | "rollup": "^0.52.3", 51 | "rollup-plugin-babel": "^4.3.3", 52 | "rollup-plugin-collect-sass": "^1.0.9", 53 | "rollup-plugin-commonjs": "^8.4.1", 54 | "rollup-plugin-copy": "^0.2.3", 55 | "rollup-plugin-eslint": "^4.0.0", 56 | "rollup-plugin-includepaths": "^0.2.3", 57 | "rollup-plugin-node-resolve": "^3.4.0", 58 | "rollup-plugin-replace": "^2.2.0", 59 | "rollup-plugin-uglify-es": "0.0.1", 60 | "tar": "^4.4.13" 61 | }, 62 | "dependencies": {}, 63 | "engines": { 64 | "npm": ">=6.0.0" 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/js/transition-end.js: -------------------------------------------------------------------------------- 1 | /* 2 | By Osvaldas Valutis, www.osvaldas.info 3 | Available for use under the MIT License 4 | */ 5 | /* eslint-disable no-unused-vars */ 6 | (function(document, window) { 7 | let el = document.body || document.documentElement, 8 | s = el.style, 9 | prefixAnimation = '', 10 | prefixTransition = ''; 11 | 12 | if (s.WebkitAnimation == '') 13 | prefixAnimation = '-webkit-'; 14 | if (s.MozAnimation == '') 15 | prefixAnimation = '-moz-'; 16 | if (s.OAnimation == '') 17 | prefixAnimation = '-o-'; 18 | 19 | if (s.WebkitTransition == '') 20 | prefixTransition = '-webkit-'; 21 | if (s.MozTransition == '') 22 | prefixTransition = '-moz-'; 23 | if (s.OTransition == '') 24 | prefixTransition = '-o-'; 25 | 26 | Object.defineProperty(Object.prototype, 'onCSSAnimationEnd', { 27 | value: function(callback) { 28 | var runOnce = function(e) { 29 | callback(); 30 | e.target.removeEventListener(e.type, runOnce); 31 | }; 32 | this.addEventListener('webkitAnimationEnd', runOnce); 33 | this.addEventListener('mozAnimationEnd', runOnce); 34 | this.addEventListener('oAnimationEnd', runOnce); 35 | this.addEventListener('oanimationend', runOnce); 36 | this.addEventListener('animationend', runOnce); 37 | if ((prefixAnimation == '' && !('animation' in s)) || getComputedStyle(this)[prefixAnimation + 'animation-duration'] == '0s') 38 | callback(); 39 | return this; 40 | }, 41 | enumerable: false, 42 | writable: true 43 | }); 44 | 45 | Object.defineProperty(Object.prototype, 'onCSSTransitionEnd', { 46 | value: function(callback) { 47 | var runOnce = function runOnce(e) { 48 | callback(); 49 | e.target.removeEventListener(e.type, runOnce); 50 | }; 51 | this.addEventListener('webkitTransitionEnd', runOnce); 52 | this.addEventListener('mozTransitionEnd', runOnce); 53 | this.addEventListener('oTransitionEnd', runOnce); 54 | this.addEventListener('transitionend', runOnce); 55 | this.addEventListener('transitionend', runOnce); 56 | if (prefixTransition == '' && !('transition' in s) || getComputedStyle(this)[prefixTransition + 'transition-duration'] == '0s') 57 | callback(); 58 | return this; 59 | }, 60 | enumerable: false, 61 | writable: true 62 | }); 63 | }(document, window, 0)); 64 | 65 | /* eslint-disable no-undef */ 66 | export default module; 67 | -------------------------------------------------------------------------------- /dist/array-from-polyfill.js: -------------------------------------------------------------------------------- 1 | if (!Array.from) { 2 | Array.from = (function() { 3 | var toStr = Object.prototype.toString; 4 | var isCallable = function(fn) { 5 | return typeof fn === 'function' || toStr.call(fn) === '[object Function]'; 6 | }; 7 | var toInteger = function(value) { 8 | var number = Number(value); 9 | if (isNaN(number)) { 10 | return 0; 11 | } 12 | if (number === 0 || !isFinite(number)) { 13 | return number; 14 | } 15 | return ( 16 | number > 0 17 | ? 1 18 | : -1) * Math.floor(Math.abs(number)); 19 | }; 20 | var maxSafeInteger = Math.pow(2, 53) - 1; 21 | var toLength = function(value) { 22 | var len = toInteger(value); 23 | return Math.min(Math.max(len, 0), maxSafeInteger); 24 | }; 25 | 26 | // The length property of the from method is 1. 27 | return function from(arrayLike/* , mapFn, thisArg */) { 28 | // 1. Let C be the this value. 29 | var C = this; 30 | 31 | // 2. Let items be ToObject(arrayLike). 32 | var items = Object(arrayLike); 33 | 34 | // 3. ReturnIfAbrupt(items). 35 | if (arrayLike == null) { 36 | throw new TypeError('Array.from requires an array-like object - not null or undefined'); 37 | } 38 | 39 | // 4. If mapfn is undefined, then let mapping be false. 40 | var mapFn = arguments.length > 1 41 | ? arguments[1] 42 | : void undefined; 43 | var T; 44 | if (typeof mapFn !== 'undefined') { 45 | // 5. else 46 | // 5. a If IsCallable(mapfn) is false, throw a TypeError exception. 47 | if (!isCallable(mapFn)) { 48 | throw new TypeError('Array.from: when provided, the second argument must be a function'); 49 | } 50 | 51 | // 5. b. If thisArg was supplied, let T be thisArg; else let T be undefined. 52 | if (arguments.length > 2) { 53 | T = arguments[2]; 54 | } 55 | } 56 | 57 | // 10. Let lenValue be Get(items, "length"). 58 | // 11. Let len be ToLength(lenValue). 59 | var len = toLength(items.length); 60 | 61 | // 13. If IsConstructor(C) is true, then 62 | // 13. a. Let A be the result of calling the [[Construct]] internal method 63 | // of C with an argument list containing the single item len. 64 | // 14. a. Else, Let A be ArrayCreate(len). 65 | var A = isCallable(C) 66 | ? Object(new C(len)) 67 | : new Array(len); 68 | 69 | // 16. Let k be 0. 70 | var k = 0; 71 | // 17. Repeat, while k < len… (also steps a - h) 72 | var kValue; 73 | while (k < len) { 74 | kValue = items[k]; 75 | if (mapFn) { 76 | A[k] = typeof T === 'undefined' 77 | ? mapFn(kValue, k) 78 | : mapFn.call(T, kValue, k); 79 | } else { 80 | A[k] = kValue; 81 | } 82 | k += 1; 83 | } 84 | // 18. Let putStatus be Put(A, "length", len, true). 85 | A.length = len; 86 | // 20. Return A. 87 | return A; 88 | }; 89 | }()); 90 | } 91 | 92 | /* eslint-disable no-undef */ 93 | export default module; 94 | -------------------------------------------------------------------------------- /src/js/array-from-polyfill.js: -------------------------------------------------------------------------------- 1 | if (!Array.from) { 2 | Array.from = (function() { 3 | var toStr = Object.prototype.toString; 4 | var isCallable = function(fn) { 5 | return typeof fn === 'function' || toStr.call(fn) === '[object Function]'; 6 | }; 7 | var toInteger = function(value) { 8 | var number = Number(value); 9 | if (isNaN(number)) { 10 | return 0; 11 | } 12 | if (number === 0 || !isFinite(number)) { 13 | return number; 14 | } 15 | return ( 16 | number > 0 17 | ? 1 18 | : -1) * Math.floor(Math.abs(number)); 19 | }; 20 | var maxSafeInteger = Math.pow(2, 53) - 1; 21 | var toLength = function(value) { 22 | var len = toInteger(value); 23 | return Math.min(Math.max(len, 0), maxSafeInteger); 24 | }; 25 | 26 | // The length property of the from method is 1. 27 | return function from(arrayLike/* , mapFn, thisArg */) { 28 | // 1. Let C be the this value. 29 | var C = this; 30 | 31 | // 2. Let items be ToObject(arrayLike). 32 | var items = Object(arrayLike); 33 | 34 | // 3. ReturnIfAbrupt(items). 35 | if (arrayLike == null) { 36 | throw new TypeError('Array.from requires an array-like object - not null or undefined'); 37 | } 38 | 39 | // 4. If mapfn is undefined, then let mapping be false. 40 | var mapFn = arguments.length > 1 41 | ? arguments[1] 42 | : void undefined; 43 | var T; 44 | if (typeof mapFn !== 'undefined') { 45 | // 5. else 46 | // 5. a If IsCallable(mapfn) is false, throw a TypeError exception. 47 | if (!isCallable(mapFn)) { 48 | throw new TypeError('Array.from: when provided, the second argument must be a function'); 49 | } 50 | 51 | // 5. b. If thisArg was supplied, let T be thisArg; else let T be undefined. 52 | if (arguments.length > 2) { 53 | T = arguments[2]; 54 | } 55 | } 56 | 57 | // 10. Let lenValue be Get(items, "length"). 58 | // 11. Let len be ToLength(lenValue). 59 | var len = toLength(items.length); 60 | 61 | // 13. If IsConstructor(C) is true, then 62 | // 13. a. Let A be the result of calling the [[Construct]] internal method 63 | // of C with an argument list containing the single item len. 64 | // 14. a. Else, Let A be ArrayCreate(len). 65 | var A = isCallable(C) 66 | ? Object(new C(len)) 67 | : new Array(len); 68 | 69 | // 16. Let k be 0. 70 | var k = 0; 71 | // 17. Repeat, while k < len… (also steps a - h) 72 | var kValue; 73 | while (k < len) { 74 | kValue = items[k]; 75 | if (mapFn) { 76 | A[k] = typeof T === 'undefined' 77 | ? mapFn(kValue, k) 78 | : mapFn.call(T, kValue, k); 79 | } else { 80 | A[k] = kValue; 81 | } 82 | k += 1; 83 | } 84 | // 18. Let putStatus be Put(A, "length", len, true). 85 | A.length = len; 86 | // 20. Return A. 87 | return A; 88 | }; 89 | }()); 90 | } 91 | 92 | /* eslint-disable no-undef */ 93 | export default module; 94 | -------------------------------------------------------------------------------- /src/css/badger-accordion-demo.css: -------------------------------------------------------------------------------- 1 | html, body, div, span, applet, object, iframe, 2 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, 3 | a, abbr, acronym, address, big, cite, code, 4 | del, dfn, em, img, ins, kbd, q, s, samp, 5 | small, strike, strong, sub, sup, tt, var, 6 | b, u, i, center, 7 | dl, dt, dd, ol, ul, li, 8 | fieldset, form, label, legend, 9 | table, caption, tbody, tfoot, thead, tr, th, td, 10 | article, aside, canvas, details, embed, 11 | figure, figcaption, footer, header, hgroup, 12 | menu, nav, output, ruby, section, summary, 13 | time, mark, audio, video { 14 | margin: 0; 15 | padding: 0; 16 | border: 0; 17 | font-size: 100%; 18 | vertical-align: baseline; 19 | font: inherit; } 20 | 21 | /* HTML5 display-role reset for older browsers */ 22 | article, aside, details, figcaption, figure, 23 | footer, header, hgroup, menu, nav, section { 24 | display: block; } 25 | 26 | body { 27 | line-height: 1; } 28 | 29 | ol, ul { 30 | list-style: none; } 31 | 32 | blockquote, q { 33 | quotes: none; } 34 | 35 | blockquote:before, blockquote:after, 36 | q:before, q:after { 37 | content: ''; 38 | content: none; } 39 | 40 | table { 41 | border-collapse: collapse; 42 | border-spacing: 0; } 43 | 44 | html { 45 | -webkit-font-smoothing: antialiased; 46 | box-sizing: border-box; } 47 | 48 | *, *:before, *:after { 49 | box-sizing: inherit; } 50 | 51 | img { 52 | display: block; } 53 | 54 | a, a:hover, a:active, a:visited { 55 | text-decoration: none; } 56 | 57 | body { 58 | color: #323232; 59 | font-size: 16px; 60 | line-height: 1.25; } 61 | 62 | .container { 63 | display: flex; 64 | flex-direction: column; 65 | flex-grow: 1; 66 | max-width: 1020px; 67 | margin: 0 auto; 68 | padding: 40px 0; 69 | width: 90%; } 70 | 71 | .heading--alpha { 72 | font-size: 2.25rem; 73 | line-height: 1.5; 74 | margin-bottom: 40px; } 75 | 76 | button { 77 | border: 0; 78 | width: 100%; 79 | font-size: 1em; } 80 | 81 | p { 82 | line-height: 1.25; } 83 | p:not(:last-of-type) { 84 | margin-bottom: 20px; } 85 | 86 | .badger-accordion__header { 87 | align-content: space-between; 88 | align-items: center; 89 | background-color: #95A5A6; 90 | display: flex; 91 | padding: 20px; 92 | transition: all ease-in-out 0.2s; } 93 | .badger-accordion__header[aria-expanded=true] .badger-accordion__header-icon:before { 94 | transform: rotate(45deg) translate3d(14px, 14px, 0); } 95 | .badger-accordion__header[aria-expanded=true] .badger-accordion__header-icon:after { 96 | transform: rotate(-45deg) translate3d(-14px, 14px, 0); } 97 | .badger-accordion__header:focus, .badger-accordion__header:hover { 98 | background-color: #2574A9; 99 | outline: none; } 100 | .badger-accordion__header:focus .badger-accordion__header-title, .badger-accordion__header:hover .badger-accordion__header-title { 101 | color: #fff; } 102 | .badger-accordion__header:focus .badger-accordion__header-icon:before, .badger-accordion__header:focus .badger-accordion__header-icon:after, .badger-accordion__header:hover .badger-accordion__header-icon:before, .badger-accordion__header:hover .badger-accordion__header-icon:after { 103 | background-color: #fff; } 104 | 105 | .badger-accordion__header-icon { 106 | display: block; 107 | height: 40px; 108 | margin-left: auto; 109 | position: relative; 110 | transition: all ease-in-out 0.2s; 111 | width: 40px; } 112 | .badger-accordion__header-icon:after, .badger-accordion__header-icon:before { 113 | background-color: #333; 114 | content: ""; 115 | height: 3px; 116 | position: absolute; 117 | top: 10px; 118 | transition: all ease-in-out 0.13333s; 119 | width: 30px; } 120 | .badger-accordion__header-icon:before { 121 | left: 0; 122 | transform: rotate(45deg) translate3d(8px, 22px, 0); 123 | transform-origin: 100%; } 124 | .badger-accordion__header-icon:after { 125 | transform: rotate(-45deg) translate3d(-8px, 22px, 0); 126 | right: 0; 127 | transform-origin: 0%; } 128 | 129 | .badger-accordion__panel { 130 | background-color: #fafafa; 131 | position: relative; } 132 | .badger-accordion__panel:after { 133 | background-color: #6C7A89; 134 | bottom: 0; 135 | content: ""; 136 | height: 2px; 137 | left: 0; 138 | position: absolute; 139 | width: 100%; } 140 | 141 | .badger-accordion__panel-inner { 142 | padding: 20px; } 143 | 144 | /*# sourceMappingURL=badger-accordion-demo.css.map */ 145 | -------------------------------------------------------------------------------- /example/css/badger-accordion-demo.css: -------------------------------------------------------------------------------- 1 | html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td,article,aside,canvas,details,embed,figure,figcaption,footer,header,hgroup,menu,nav,output,ruby,section,summary,time,mark,audio,video{margin:0;padding:0;border:0;font-size:100%;vertical-align:baseline;font:inherit}article,aside,details,figcaption,figure,footer,header,hgroup,menu,nav,section{display:block}body{line-height:1}ol,ul{list-style:none}blockquote,q{quotes:none}blockquote:before,blockquote:after,q:before,q:after{content:"";content:none}table{border-collapse:collapse;border-spacing:0}html{-webkit-font-smoothing:antialiased;box-sizing:border-box}*,*:before,*:after{box-sizing:inherit}img{display:block}a,a:hover,a:active,a:visited{text-decoration:none}html{color:#323232;font-family:"Josefin Sans",Arial;font-size:16px;line-height:1.25}body{background:#eff1f0;min-height:100vh}.vh{position:absolute;overflow:hidden;width:1px;height:1px;margin:-1px;padding:0;border:0;clip:rect(0 0 0 0)}.vh.focusable:active,.vh.focusable:focus{position:static;overflow:visible;width:auto;height:auto;margin:0;clip:auto}.container{margin-top:10vh;margin-right:auto;margin-left:auto;max-width:720px}@media screen and (max-width: 767px){.container{margin:10vh auto;width:90%}}.landmark{margin-bottom:40px}.landmark--double{margin:80px}.header{display:block;margin:0 auto 60px;max-width:75%;text-align:center}.heading--alpha{font-family:"Josefin Slab";color:#34495e;font-size:3.25rem;line-height:1.35;margin-bottom:10px}.heading--bravo{font-family:"Josefin Slab";color:#34495e;font-size:2.25rem;line-height:1.35;margin-bottom:10px}button{border:0;width:100%;font-size:1em}p{line-height:1.25;margin:20px 0 0 0}p:not(:last-of-type){margin-bottom:20px}a{color:#34495e}.inline-list{display:block;list-style:none;margin:0;padding:0}.inline-list__item{display:inline-block}.icon-link{align-items:center;display:flex;text-align:center;justify-content:center;padding:5px;text-decoration:none}.icon-link:focus{color:#2574a9;outline:auto 2px #2574a9}.icon-link:focus .icon-link__text{box-shadow:inset 0 -3px #2574a9}.icon-link:focus svg{fill:#2574a9}.icon-link svg{transition:all ease-in-out .2s;fill:#34495e;margin-left:.5rem}@media screen and (min-width: 768px){.icon-link:hover{color:#2574a9}.icon-link:hover .icon-link__text{box-shadow:inset 0 -3px #2574a9}.icon-link:hover svg{fill:#2574a9}}.icon-link__text{transition:all ease-in-out .2s;box-shadow:inset 0 -2px currentColor}.logo{display:block;max-width:420px;margin:0 auto 40px}.badger-accordion{box-shadow:0 1px 10px rgba(0,0,0,.1),0 1px 4px rgba(0,0,0,.1);border-radius:4px;overflow:hidden}.badger-accordion__header:not(:last-of-type){border-bottom:1px solid #eff1f0}.badger-accordion__trigger{align-content:space-between;align-items:center;background-color:#fff;border:0;border-radius:0px;color:#34495e;display:flex;font-family:"Josefin Sans",Arial;font-size:1.25rem;line-height:1;padding:20px;text-align:left;transition:all ease-in-out .2s;width:100%}.badger-accordion__trigger[aria-expanded=true] .badger-accordion__trigger-icon:before{transform:rotate(45deg) translate3d(13px, 14px, 0)}.badger-accordion__trigger[aria-expanded=true] .badger-accordion__trigger-icon:after{transform:rotate(-45deg) translate3d(-13px, 14px, 0)}.badger-accordion__trigger:focus,.badger-accordion__trigger:hover{background-color:#16a085;cursor:pointer;outline:none}.badger-accordion__trigger:focus .badger-accordion__trigger-title,.badger-accordion__trigger:hover .badger-accordion__trigger-title{color:#fff}.badger-accordion__trigger:focus .badger-accordion__trigger-icon:after,.badger-accordion__trigger:focus .badger-accordion__trigger-icon:before,.badger-accordion__trigger:hover .badger-accordion__trigger-icon:after,.badger-accordion__trigger:hover .badger-accordion__trigger-icon:before{background-color:#fff}.badger-accordion__trigger::-moz-focus-inner{border:none}.badger-accordion__trigger-title{font-size:1.2rem;transition:ease-in-out .3s}.badger-accordion__trigger-icon{display:block;height:40px;margin-left:auto;position:relative;transition:all ease-in-out .2s;width:40px}.badger-accordion__trigger-icon:after,.badger-accordion__trigger-icon:before{background-color:#333;content:"";height:3px;position:absolute;top:10px;transition:all ease-in-out .1333333333s;width:30px}.badger-accordion__trigger-icon:before{left:1px;transform:rotate(45deg) translate3d(8px, 22px, 0);transform-origin:100%}.badger-accordion__trigger-icon:after{transform:rotate(-45deg) translate3d(-8px, 22px, 0);right:1px;transform-origin:0}.badger-accordion__panel{background-color:#fafafa;position:relative}.badger-accordion__panel:after{background-color:#eff1f0;bottom:0;content:"";height:2px;left:0;position:absolute;width:100%}.badger-accordion__panel-inner{padding:20px 20px 40px}@media screen and (max-width: 767px){.badger-accordion__trigger-icon{display:none;padding:20px}}/*# sourceMappingURL=badger-accordion-demo.css.map */ 2 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | All notable changes to this project will be documented in this file. 3 | This project adheres to [Semantic Versioning](http://semver.org/). 4 | 5 | For more information about keeping good change logs please refer to [keep a changelog](https://github.com/olivierlacan/keep-a-changelog). 6 | 7 | ## Changelog 8 | 9 | 10 | ## [1.2.4] - 2019-11-9 11 | ### UPDATED 12 | - All dependancies package 13 | 14 | ### FIXED 15 | - Some package security vunerabilities 16 | - NPM package release to include fix for [issue 26](https://github.com/stuartjnelson/badger-accordion/issues/26) 17 | 18 | 19 | ## [1.2.3] - 2019-5-7 20 | ### UPDATED 21 | - Security issue with `tar` package 22 | 23 | ### ADDED 24 | - Option `addListenersOnInit`. See [PR 26](https://github.com/stuartjnelson/badger-accordion/pull/26) for more info 25 | 26 | 27 | ## [1.2.2] - 2019-2-12 28 | ### Fixed 29 | - Running NPM audit to fix vulnerabilities 30 | 31 | 32 | ## [1.2.1] - 2019-2-9 33 | ### Fixed 34 | - Issue#24: Fixing setting for `hiddenClass` and `initializedClass` 35 | 36 | 37 | ## [1.2.0] - 2019-1-29 38 | ### Fixed 39 | - Issue#16: Properly hiding accordion content for all users 40 | - Issue#17: Removed aria-label and deprecated `headerOpenLabel` & `headerCloseLabel` 41 | - Merged in PR from `@micmania1` for the correct spelling of _aria-labelledby_ 42 | 43 | ### Updated 44 | - Made NPM scripts bit nicer by calling each other. Also now compiling .css 45 | 46 | ### Added 47 | - Created the ability to have nested accordions. For this to happen I needed to change how a single accordion instance selected its headers & panels. Now the headers & panels selected are only 1 level deep. 48 | 49 | 50 | ## [1.1.4] - 2018-12-2 51 | ### Updated 52 | - Spelling of `initializedClass` so it is the American spelling 53 | 54 | 55 | ## [1.1.3] - 2018-11-27 56 | ### Fixed 57 | - Fixing demo styles

58 | - Issue #14: [seanjhulse](https://github.com/seanjhulse) created a PR and patched this so openAll/closeAll works. 59 | 60 | ### Added 61 | - Issue #9 Active class to the open header & panel 62 | 63 | ### Changed 64 | - Deprecating `setPanelHeight()` in favour better name 65 | This method was “private” and not named great for it being used after the initialisation IMO. I have now called it `calculateAllPanelsHeight()` which I feel is more descriptive. Also created a method to calculate a single panel’s height `calculatePanelHeight()`. Also updated the docs. 66 | - Issue #8: Setting the roles. 67 | By default both the `presentation` role on the container element & `region` on the panel will be set. You can now using turn them both off `roles: false` or explicitly set one or both of the roles to be set. 68 | 69 | ``` 70 | roles: { 71 | region: true 72 | } 73 | ``` 74 | - Issue #10 - Moved packages to devDependencies and cleaned up package.json 75 | 76 | 77 | ## [1.1.3] - 2018-11-26 78 | ### Updated 79 | - `_openHeadersOnLoad()` updates state with method 80 | - Updated NPM scripts 81 | 82 | ### Fixed 83 | - Issue#20: `Open()` & `Close()` methods were not correctly updating state and therefore if fired upon start the whole state object was incorrect and using the accordion was impossible. 84 | 85 | 86 | ## [1.1.2] - 2018-8-7 87 | ### Updated 88 | - Discarding some temporary changes 89 | 90 | 91 | ## [1.1.1] - 2018-6-12 92 | ### Updated 93 | - LICENSE so its correct... 94 | 95 | 96 | ## [1.1.0] - 2018-4-30 97 | ### Updated 98 | - Plugin so can now pass in a DOM node 99 | - README & example files 100 | - Tweaking minor Firefox CSS bug with the demo 101 | 102 | 103 | ## [1.0.30] - 2018-4-4 104 | ### Updated 105 | - NPM version 106 | 107 | 108 | ## [1.0.29] - 2018-4-4 109 | ### Updated 110 | - Deprecated `hidenClass` option. This was a spelling mistake and has been deprecated. If you have used in from version < 1.0.28 then `hiddenClass` is now equal to `hidenClass` 111 | - Compiled assets and updated readme 112 | 113 | 114 | ## [1.0.28] - 2018-4-4 115 | ### Updated 116 | - Updated default transition to be more specific 117 | 118 | 119 | ## [1.0.27] - 2018-4-4 120 | ### Updated 121 | - Improving SCSS comment 122 | - Updated rollup so default example styles are copied by rollup 123 | 124 | 125 | ## [1.0.26] - 2018-3-22 126 | ### Updated 127 | - NPM version 128 | 129 | 130 | ## [1.0.25] - 2018-4-4 131 | ### Updated 132 | - Dependancies 133 | - Updated essential SCSS/CSS. Renamed default hidden class. Removed some old unnecessary css. 134 | - Updated example SCSS 135 | - Ignored .sass-cache 136 | - Compiled assets 137 | 138 | 139 | ## [1.0.24] - 2018-3-22 140 | ### Updated 141 | - And this time updated `.babelrc`'s preset 142 | 143 | 144 | ## [1.0.23] - 2018-3-22 145 | ### Updated 146 | - Bable setup so it follows the latest standard as well as all dependencies. This should fix [Issue #4](https://github.com/stuartjnelson/badger-accordion/issues/4) 147 | 148 | 149 | ## [1.0.22] - 2018-3-21 150 | ### Changed 151 | - Adding `pre-pubish` npm script. This is a safty net to stop issues with `.esm` file that the `npm example` script was causing before publishing the plugin 152 | 153 | 154 | ## [1.0.21] - 2018-3-21 155 | ### Fixed 156 | - Fixing typo in `package.json` for module 157 | - Error with `npm run example` from inserting code don't want into `dist/northern-badger.esm.js` 158 | 159 | ### Added 160 | - Added `rollup-plugin-copy` to copy style files 161 | 162 | 163 | ## [1.0.20] - 2018-2-08 164 | ### Fixed 165 | - Fixed link to demo site in Readme 166 | 167 | 168 | ## [1.0.19] - 2018-1-31 169 | ### Updated 170 | - NPM version 171 | 172 | 173 | ## [1.0.18] - 2018-1-31 174 | ### Added 175 | - Added rol=“region” to accordion panel 176 | - Added rol=“presentation” to accordion 177 | 178 | ### Updated 179 | - Updated README 180 | - Compiled assets 181 | - Moved setAttributes method up 182 | 183 | 184 | ## [1.0.17] - 2018-1-1 185 | ### Updated 186 | - Importing array.from polyfill by default 187 | - Updated copy with new file size! 188 | 189 | 190 | ## [1.0.16] - 2018-1-1 191 | ### Updated 192 | - Tweaked example demo markup and styles 193 | - Updated packages for Rollup. Now have `umd` & `esm` versions transpiled 194 | 195 | ### Added 196 | - Created .esm files 197 | 198 | 199 | ## [1.0.15] - 2017-12-16 200 | ### Updated 201 | - Improving readme 202 | 203 | 204 | ## [1.0.14] - 2017-12-13 205 | ### Fixed 206 | - Fixed IE11 bug with object assign 207 | 208 | ### Updated 209 | - Babel plugins to fix IE11 bug 210 | - Updated README.md with download info 211 | 212 | 213 | ## [1.0.13] - 2017-12-12 214 | ### Fixed 215 | - Updated transitionEnd JS to ensure that `Object.defineProperty` is writable 216 | 217 | ### Updated 218 | - Updated issue template 219 | 220 | 221 | ## [1.0.12] - 2017-12-11 222 | ### Fixed 223 | - Issue #1: Conflict with jQuery as [raised here](https://github.com/stuartjnelson/badger-accordion/issues/1#issuecomment-350789280). After some investigation I found this [thread](https://stackoverflow.com/questions/21729895/jquery-conflict-with-native-prototype) - I updated the “transitionEnd” functions. This appears to have fixed the issue. 224 | 225 | ### Added 226 | - Main `dist/badger-accordion.js` is now not minifed and added a minifed version in `dist` directory. 227 | 228 | ### Updated 229 | - `rollup.config.js` so that srcmaps aren't included with complied JS files. 230 | 231 | 232 | ## [1.0.0] to [1.0.11] - 2017-12-11 233 | ### Added/Updated/Fixed 234 | - Just added this change log. Wont detail whats happened until now. Released the plugin and updated a bunch of things. 235 | -------------------------------------------------------------------------------- /example/scss/badger-accordion-demo.scss: -------------------------------------------------------------------------------- 1 | // ========================================================================== 2 | // 3 | // BADGER ACCORDION DEMO 4 | // 5 | // Description: Styles for the demo page. Including reset.css 6 | // 7 | // ========================================================================== 8 | 9 | // ========================================================================== 10 | // # IMPORTS 11 | // ========================================================================== 12 | @import "reset"; 13 | 14 | 15 | 16 | 17 | 18 | // ========================================================================== 19 | // # VARIABLES 20 | // ========================================================================== 21 | 22 | // COLORS 23 | $white: #fff; 24 | $off-white: #fafafa; 25 | $black: #323232; 26 | $grey: #6C7A89; 27 | $grey-light: #95A5A6; 28 | $blue: #2574A9; 29 | $dark-blue: #34495e; 30 | $green: #16a085; 31 | 32 | // UNITS 33 | $spacing-unit: 40px; 34 | $half-spacing-unit: $spacing-unit / 2; 35 | 36 | // BORDER 37 | $accordion-border: solid 2px $grey-light; 38 | 39 | // ANIMATION 40 | $transition-time: 0.2s; 41 | @mixin base-trans { 42 | transition: all ease-in-out $transition-time; 43 | } 44 | 45 | // LINKS 46 | @mixin link-underline($color: currentColor, $size: -2px) { 47 | box-shadow: inset 0 $size $color; 48 | } 49 | 50 | @mixin active-link() { 51 | color: $blue; 52 | 53 | .icon-link__text { @include link-underline($blue, -3px); } 54 | 55 | svg { fill: $blue; } 56 | } 57 | 58 | 59 | 60 | 61 | 62 | // ========================================================================== 63 | // # BASE 64 | // ========================================================================== 65 | 66 | html { 67 | color: $black; 68 | font-family: 'Josefin Sans', Arial; 69 | font-size: 16px; 70 | line-height: 1.25; 71 | } 72 | 73 | body { 74 | background: #EFF1F0; 75 | min-height: 100vh; 76 | } 77 | 78 | .vh { 79 | position: absolute; 80 | overflow: hidden; 81 | width: 1px; 82 | height: 1px; 83 | margin: -1px; 84 | padding: 0; 85 | border: 0; 86 | clip: rect(0 0 0 0); 87 | } 88 | 89 | .vh.focusable:active, 90 | .vh.focusable:focus { 91 | position: static; 92 | overflow: visible; 93 | width: auto; 94 | height: auto; 95 | margin: 0; 96 | clip: auto; 97 | } 98 | 99 | .container { 100 | margin-top: 10vh; 101 | margin-right: auto; 102 | margin-left: auto; 103 | max-width: 720px; 104 | } 105 | 106 | @media screen and (max-width: 767px) { 107 | .container { 108 | margin: 10vh auto; 109 | width: 90%; 110 | } 111 | } 112 | 113 | .landmark { 114 | margin-bottom: 40px; 115 | 116 | &--double { margin: 80px; } 117 | } 118 | 119 | .header { 120 | display: block; 121 | margin: 0 auto $spacing-unit * 1.5; 122 | max-width: 75%; 123 | text-align: center; 124 | } 125 | 126 | .heading--alpha { 127 | font-family: 'Josefin Slab'; 128 | color: $dark-blue; 129 | font-size: 3.25rem; 130 | line-height: 1.35; 131 | margin-bottom: $spacing-unit/4; 132 | } 133 | 134 | .heading--bravo { 135 | font-family: 'Josefin Slab'; 136 | color: $dark-blue; 137 | font-size: 2.25rem; 138 | line-height: 1.35; 139 | margin-bottom: $spacing-unit/4; 140 | } 141 | 142 | button { 143 | border: 0; 144 | width: 100%; 145 | font-size: 1em; 146 | } 147 | 148 | p { 149 | line-height: 1.25; 150 | margin: $half-spacing-unit 0 0 0; 151 | 152 | &:not(:last-of-type) { 153 | margin-bottom: $half-spacing-unit; 154 | } 155 | } 156 | 157 | a { color: $dark-blue; } 158 | 159 | .inline-list { 160 | display: block; 161 | list-style: none; 162 | margin: 0; 163 | padding: 0; 164 | } 165 | 166 | .inline-list__item { display: inline-block; } 167 | 168 | 169 | .icon-link { 170 | align-items: center; 171 | display: flex; 172 | text-align: center; 173 | justify-content: center; 174 | padding: $spacing-unit/8; 175 | text-decoration: none; 176 | 177 | &:focus { 178 | @include active-link; 179 | outline: auto 2px $blue; 180 | } 181 | 182 | svg { 183 | @include base-trans; 184 | fill: $dark-blue; 185 | margin-left: 0.5rem; 186 | } 187 | } 188 | 189 | @media screen and (min-width: 768px) { 190 | .icon-link:hover { 191 | @include active-link; 192 | } 193 | } 194 | 195 | .icon-link__text { 196 | @include base-trans; 197 | @include link-underline; 198 | } 199 | 200 | .logo { 201 | display: block; 202 | max-width: 420px; 203 | margin: 0 auto $spacing-unit; 204 | } 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | // ========================================================================== 213 | // # DEMO ACCORDION 214 | // ========================================================================== 215 | 216 | .badger-accordion { 217 | box-shadow: 0 1px 10px rgba(0,0,0,.1), 0 1px 4px rgba(0,0,0,.1); 218 | border-radius: 4px; 219 | overflow: hidden; 220 | } 221 | 222 | .badger-accordion__header { 223 | &:not(:last-of-type) { 224 | border-bottom: 1px solid #EFF1F0; 225 | } 226 | } 227 | 228 | .badger-accordion__trigger { 229 | align-content: space-between; 230 | align-items: center; 231 | background-color: $white; 232 | border: 0; 233 | border-radius: 0px; 234 | color: $dark-blue; 235 | display: flex; 236 | font-family: 'Josefin Sans', Arial; 237 | font-size: 1.25rem; 238 | line-height: 1; 239 | padding: $half-spacing-unit; 240 | text-align: left; 241 | transition: all ease-in-out $transition-time; 242 | width: 100%; 243 | 244 | &[aria-expanded=true] { 245 | .badger-accordion__trigger-icon { 246 | &:before { 247 | transform: rotate(45deg) translate3d(13px, 14px, 0); 248 | } 249 | 250 | &:after { 251 | transform: rotate(-45deg) translate3d(-13px, 14px, 0); 252 | } 253 | } 254 | } 255 | 256 | &:focus, 257 | &:hover { 258 | background-color: $green; 259 | cursor: pointer; 260 | outline: none; 261 | 262 | .badger-accordion__trigger-title { color: $white; } 263 | 264 | .badger-accordion__trigger-icon { 265 | &:after, 266 | &:before { 267 | background-color: $white; 268 | } 269 | } 270 | } 271 | 272 | // Removing "inner outline" for Firefox 273 | &::-moz-focus-inner { 274 | border: none; 275 | } 276 | } 277 | 278 | 279 | .badger-accordion__trigger-title { 280 | font-size: 1.2rem; 281 | transition: ease-in-out 0.3s; 282 | } 283 | 284 | .badger-accordion__trigger-icon { 285 | display: block; 286 | height: $spacing-unit; 287 | margin-left: auto; 288 | position: relative; 289 | transition: all ease-in-out $transition-time; 290 | width: $spacing-unit; 291 | 292 | &:after, 293 | &:before { 294 | background-color: #333; 295 | content: ""; 296 | height: 3px; 297 | position: absolute; 298 | top: 10px; 299 | transition: all ease-in-out (($transition-time / 3) * 2); 300 | width: 30px; 301 | } 302 | 303 | &:before { 304 | left: 1px; 305 | transform: rotate(45deg) translate3d(8px, 22px, 0); 306 | transform-origin: 100%; 307 | } 308 | 309 | &:after { 310 | transform: rotate(-45deg) translate3d(-8px, 22px, 0); 311 | right: 1px; 312 | transform-origin: 0; 313 | } 314 | } 315 | 316 | .badger-accordion__panel { 317 | background-color: $off-white; 318 | position: relative; 319 | 320 | &:after { 321 | background-color: #EFF1F0; 322 | bottom: 0; 323 | content: ""; 324 | height: 2px; 325 | left: 0; 326 | position: absolute; 327 | width: 100%; 328 | } 329 | } 330 | 331 | .badger-accordion__panel-inner { 332 | padding: $half-spacing-unit $half-spacing-unit $spacing-unit; 333 | } 334 | 335 | 336 | @media screen and (max-width: 767px) { 337 | .badger-accordion__trigger-icon { 338 | display: none; 339 | padding: $half-spacing-unit; 340 | } 341 | } 342 | -------------------------------------------------------------------------------- /dist/badger-accordion.min.js: -------------------------------------------------------------------------------- 1 | !function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):t.BadgerAccordion=e()}(this,function(){"use strict";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?1:-1)*Math.floor(Math.abs(e)):e},i=Math.pow(2,53)-1,s=function(t){var e=n(t);return Math.min(Math.max(e,0),i)};return function(t){var n=this,i=Object(t);if(null==t)throw new TypeError("Array.from requires an array-like object - not null or undefined");var a,r=arguments.length>1?arguments[1]:void 0;if(void 0!==r){if(!e(r))throw new TypeError("Array.from: when provided, the second argument must be a function");arguments.length>2&&(a=arguments[2])}for(var o,l=s(i.length),c=e(n)?Object(new n(l)):new Array(l),d=0;d1&&void 0!==arguments[1])||arguments[1])&&this.setState(t),this.togglePanel("open",t)}},{key:"close",value:function(t){(!(arguments.length>1&&void 0!==arguments[1])||arguments[1])&&this.setState(t),this.togglePanel("closed",t)}},{key:"openAll",value:function(){var t=this;this.headers.forEach(function(e,n){t.togglePanel("open",n)})}},{key:"closeAll",value:function(){var t=this;this.headers.forEach(function(e,n){t.togglePanel("closed",n)})}},{key:"togglePanel",value:function(t,e){var n=this;if(void 0!==t&&void 0!==e)if("closed"===t){var i=this.headers[e],s=this.panels[e];s.classList.add(this.settings.hiddenClass),s.classList.remove(this.settings.activeClass),i.classList.remove(this.settings.activeClass),i.setAttribute("aria-expanded",!1),s.onCSSTransitionEnd(function(){return n.toggling=!1})}else if("open"===t){var a=this.headers[e],r=this.panels[e];r.classList.remove(this.settings.hiddenClass),r.classList.add(this.settings.activeClass),a.classList.add(this.settings.activeClass),a.setAttribute("aria-expanded",!0),r.onCSSTransitionEnd(function(){return n.toggling=!1})}}},{key:"getState",value:function(){var t=this,e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[];return e.length&&Array.isArray(e)?e.map(function(e){return t.states[e]}):this.states}},{key:"toggleState",value:function(t){if(void 0!==t)return"closed"===t?"open":"closed"}},{key:"_openHeadersOnLoad",value:function(){var t=this,e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[];e.length&&Array.isArray(e)&&e.filter(function(t){return void 0!=t}).forEach(function(e){t.setState(e)})}},{key:"_setupAttributes",value:function(){this._setupHeaders(),this._setupPanels(),this._insertDataAttrs()}},{key:"_setPanelHeight",value:function(){this.calculateAllPanelsHeight()}},{key:"calculatePanelHeight",value:function(t){var e=t.querySelector(this.settings.panelInnerClass).offsetHeight;return t.style.maxHeight="".concat(e,"px")}},{key:"calculateAllPanelsHeight",value:function(){var t=this;this.panels.forEach(function(e){t.calculatePanelHeight(e)})}},{key:"_setupHeaders",value:function(){var t=this;this.headers.forEach(function(e,n){e.setAttribute("id","badger-accordion-header-".concat(t.ids[n].id)),e.setAttribute("aria-controls","badger-accordion-panel-".concat(t.ids[n].id))})}},{key:"_setupPanels",value:function(){var t=this;this.panels.forEach(function(e,n){e.setAttribute("id","badger-accordion-panel-".concat(t.ids[n].id)),e.setAttribute("aria-labelledby","badger-accordion-header-".concat(t.ids[n].id)),!0!==t.settings.roles&&!1===t.settings.roles.region||t._setRole("region",e)})}}]),e}()}); 2 | //# sourceMappingURL=badger-accordion.min.js.map 3 | -------------------------------------------------------------------------------- /dist/badger-accordion.esm.min.js: -------------------------------------------------------------------------------- 1 | function _classCallCheck(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}function _defineProperties(t,e){for(var n=0;n0?1:-1)*Math.floor(Math.abs(e)):e},i=Math.pow(2,53)-1,s=function(t){var e=n(t);return Math.min(Math.max(e,0),i)};return function(t){var n=this,i=Object(t);if(null==t)throw new TypeError("Array.from requires an array-like object - not null or undefined");var a,r=arguments.length>1?arguments[1]:void 0;if(void 0!==r){if(!e(r))throw new TypeError("Array.from: when provided, the second argument must be a function");arguments.length>2&&(a=arguments[2])}for(var o,l=s(i.length),c=e(n)?Object(new n(l)):new Array(l),d=0;d1&&void 0!==arguments[1])||arguments[1])&&this.setState(t),this.togglePanel("open",t)}},{key:"close",value:function(t){(!(arguments.length>1&&void 0!==arguments[1])||arguments[1])&&this.setState(t),this.togglePanel("closed",t)}},{key:"openAll",value:function(){var t=this;this.headers.forEach(function(e,n){t.togglePanel("open",n)})}},{key:"closeAll",value:function(){var t=this;this.headers.forEach(function(e,n){t.togglePanel("closed",n)})}},{key:"togglePanel",value:function(t,e){var n=this;if(void 0!==t&&void 0!==e)if("closed"===t){var i=this.headers[e],s=this.panels[e];s.classList.add(this.settings.hiddenClass),s.classList.remove(this.settings.activeClass),i.classList.remove(this.settings.activeClass),i.setAttribute("aria-expanded",!1),s.onCSSTransitionEnd(function(){return n.toggling=!1})}else if("open"===t){var a=this.headers[e],r=this.panels[e];r.classList.remove(this.settings.hiddenClass),r.classList.add(this.settings.activeClass),a.classList.add(this.settings.activeClass),a.setAttribute("aria-expanded",!0),r.onCSSTransitionEnd(function(){return n.toggling=!1})}}},{key:"getState",value:function(){var t=this,e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[];return e.length&&Array.isArray(e)?e.map(function(e){return t.states[e]}):this.states}},{key:"toggleState",value:function(t){if(void 0!==t)return"closed"===t?"open":"closed"}},{key:"_openHeadersOnLoad",value:function(){var t=this,e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[];e.length&&Array.isArray(e)&&e.filter(function(t){return void 0!=t}).forEach(function(e){t.setState(e)})}},{key:"_setupAttributes",value:function(){this._setupHeaders(),this._setupPanels(),this._insertDataAttrs()}},{key:"_setPanelHeight",value:function(){this.calculateAllPanelsHeight()}},{key:"calculatePanelHeight",value:function(t){var e=t.querySelector(this.settings.panelInnerClass).offsetHeight;return t.style.maxHeight="".concat(e,"px")}},{key:"calculateAllPanelsHeight",value:function(){var t=this;this.panels.forEach(function(e){t.calculatePanelHeight(e)})}},{key:"_setupHeaders",value:function(){var t=this;this.headers.forEach(function(e,n){e.setAttribute("id","badger-accordion-header-".concat(t.ids[n].id)),e.setAttribute("aria-controls","badger-accordion-panel-".concat(t.ids[n].id))})}},{key:"_setupPanels",value:function(){var t=this;this.panels.forEach(function(e,n){e.setAttribute("id","badger-accordion-panel-".concat(t.ids[n].id)),e.setAttribute("aria-labelledby","badger-accordion-header-".concat(t.ids[n].id)),!0!==t.settings.roles&&!1===t.settings.roles.region||t._setRole("region",e)})}}]),t}();export default BadgerAccordion; 2 | //# sourceMappingURL=badger-accordion.esm.min.js.map 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Badger Accordion 2 | ![Badger Accordion logo](http://ba.northernbadger.co.uk/images/badger-accordion-logo--half.png) 3 |
4 | An accessible light weight, vanilla JavaScript accordion with an extensible API. Just 8.71kb and Gzipped 2.6kb! 5 | 6 |
7 | 8 | - [Demo site](http://ba.northernbadger.co.uk) 9 | - [Codepen demo](https://codepen.io/stuartjnelson/full/WZpxqY) 10 | 11 | --- 12 | 13 | **Contents** 14 | - [The idea](#the-idea) 15 | - [Key terminologies](#key-terminologies) 16 | - [Basic setup](#basic-setup) 17 | - [Download plugin](#download-plugin) 18 | - [Markup](#markup) 19 | - [Styles](#styles) 20 | - [Create new instance of Badger Accordion](#create-new-instance-of-badger-accordion) 21 | - [Create multiple instances of Badger Accordion](#create-multiple-instances-of-badger-accordion) 22 | - [Options](#options) 23 | - [Methods](#methods) 24 | - [Sponsors](#sponsors) 25 | - [Contributors](#contributors) 26 | - [Roadmap](#roadmap) 27 | 28 | 29 | ## The idea 30 | - To make an accessible, animated, accordion with an extensible API. 31 | - Make it using just plain vanilla JavaScript. 32 | - Ensure that it has just plain simple css. Enough to get it to work. Not too much that you have to spend ages overwriting it. 33 | - Ensure that it is accessible as possible. 34 | 35 | 36 | ## Key terminologies 37 | - `panel` - The section of the accordion than opens and closes 38 | - `header` - The button that opens an accordion panel 39 | 40 | 41 | ## Basic setup 42 | ### Download plugin 43 | You can download the plugin using NPM or direct download from Github 44 | * **NPM:**`npm i badger-accordion` 45 | * **Yarn:**`yarn add badger-accordion` 46 | * **Direct download:**[Direct download link](https://github.com/stuartjnelson/badger-accordion/archive/master.zip) 47 | 48 | You'll need to import the plugin and create a new instance so you can use it. There is a working example in the `example` directory (shock horror!) if you'd like something to reference. 49 | 50 | 1. Create your markup 51 | 2. Include the basic styles (which are in `dist/badger-accordion.css` or `dist/badger-accordion.css`) 52 | 3. Import `badger-accordion.js` 53 | 4. Create new instance of the accordion 54 | 55 | ### Markup 56 | There is no fixed structure required for your markup, in my examples I have used a dl (as the WAI-ARIA Authoring Practices guide used it in their example). You will need to add 5 selectors for the plugin to work. The selectors listed below are the default selectors but can all be over written using the plugins [options](#options). 57 | 58 | 1. The containing element, dl, .js-badger-accordion 59 | 2. The header element, button, .js-badger-accordion-header 60 | 3. The panel element, dd, .js-badger-accordion-panel 61 | 4. The panel inner element, div, .js-badger-accordion-panel-inner 62 | 5. The panel element for targeting with CSS, div, .badger-accordion__panel . 63 | 64 | While you could use the selector from point 3 I would not recommend doing this. For keeping everything nice and separated best to use a different selector for targeting with CSS & JS. 65 | ``` 66 |

67 |
68 | 71 |
72 |
73 |
74 | Panel Content 75 |
76 |
77 |
78 | ``` 79 | 80 | ### Styles 81 | I have created some simple CSS styles to help you with creating an accordion which are in `dist/badger-accordion-demo.css` or `dist/badger-accordion-demo.scss`. If you'd like some additional styles checkout the example dir. 82 | ``` 83 | .badger-accordion__panel { 84 | max-height: 75vh; 85 | overflow: hidden; 86 | } 87 | 88 | .badger-accordion__panel.-ba-is-hidden { 89 | max-height: 0 !important; 90 | visibility: hidden; 91 | } 92 | 93 | .badger-accordion--initialized .badger-accordion__panel { 94 | transition: all ease-in-out 0.2s; 95 | } 96 | ``` 97 | 98 | ### Create new instance of Badger Accordion 99 | You just import Badger Accordion. Then you can either pass in a DOM node or CSS Selector. Passing in a DOM node as in the first example below is the best way to create a new instance. 100 | 101 | Please note that currently the [Array.from polyfill](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/from#Polyfill) is being included as standard (but wrapped in a conditional check). If this is an issue for you or you have an awesome idea of how to include it please get in touch. 102 | ``` 103 | import BadgerAccordion from 'badger-accordion'; 104 | 105 | // Creating a new instance of the accordion 106 | const accordionDomNode = document.querySelector('.js-badger-accordion'); 107 | 108 | const accordion = new BadgerAccordion(accordionDomNode); 109 | ``` 110 | 111 | 112 | 113 | 114 | 115 | ### Create multiple instances of Badger Accordion 116 | If you want to have multiple instances of the accordion in the same document you could do it like this by looping over the collection of DOM nodes. 117 | ``` 118 | 119 |
120 | 121 |
122 | 123 |
124 | 125 |
126 | 127 |
128 | 129 |
130 | ``` 131 | 132 | ``` 133 | // Importing accordion 134 | import BadgerAccordion from 'dist/badger-accordion'; 135 | 136 | const accordions = document.querySelectorAll('.js-badger-accordion'); 137 | 138 | Array.from(accordions).forEach((accordion) => { 139 | const ba = new BadgerAccordion(accordion); 140 | 141 | // console.log(ba.getState([0])); 142 | }); 143 | ``` 144 | 145 | 146 | ### Create a nested accordion 147 | With release [1.2.0](https://github.com/stuartjnelson/badger-accordion/releases/tag/1.2.0) you can now created nested accordions. You don't need to do anything for this to work. Currently is you close a parent accordion then the child accordion will retain the previous state. Eg. if your child accordion has it's second item open, you close the parent then reopen the child accordion again it will have it's second item still open. 148 | 149 | 150 | 151 | 152 | ## Options 153 | 154 | The accordion has a selection of options that you can overwrite. For example if you wanted to open the first and 4th panel when the accordion is initialized; 155 | 156 | ``` 157 | new BadgerAccordion('.js-badger-accordion', { 158 | openHeadersOnLoad: [0, 3], 159 | roles: { 160 | region: true 161 | } 162 | }); 163 | ``` 164 | 165 | | Option | Type | Default | Description | 166 | |--- |--- |--- |--- | 167 | | headerClass | String | `.js-badger-accordion-header` | Class for panel's header | 168 | | panelClass | String | `.js-badger-accordion-panel` | Class for panel | 169 | | panelInnerClass | String | `.js-badger-accordion-panel-inner` | Class for panel inner container | 170 | | hiddenClass | String | `-ba-is-hidden` | Class added to panels that are hidden | 171 | | initializedClass | String | `badger-accordion--initialized` | Class add to accordion when it has initialized | 172 | | headerDataAttr | String | `data-badger-accordion-header-id` | Data attribute on each header | 173 | | openMultiplePanels | Boolean | `false` | Give you the ability to have mutiple panels open at one time. By default this is disabled | 174 | | roles | Boolean or Object | `true` | Controls setting `presentation` role on the container element & `region` on the panel. By using a boolean value you will set both attributes. By settings this as an object you will be explicitly setting only that role. Any roles not included in the object will not be set. In the example above only the `region` role will be set. | 175 | | addListenersOnInit | Boolean | `false` | If set to `true` _EventListeners_ will not be added to each accordion header on initialization | 176 | | hidenClass | @Deprecated | @Deprecated | This was a spelling mistake and has been deprecated. If you have used in from version < 1.0.29 then `hiddenClass` is now equal to `hidenClass` | 177 | | headerOpenLabel | @Deprecated | @Deprecated | Aria lable has been removed see `Changelog.md` 1.1.5 | 178 | | headerCloseLabel | @Deprecated | @Deprecated | Aria lable has been removed see `Changelog.md` 1.1.5 | 179 | 180 | 181 | 182 | ## Methods 183 | 184 | The accordion has a series of methods allowing you to have full control over extending the plugin. For example if you wanted to close all your accordion's panels; 185 | 186 | ``` 187 | accordion.closeAll(); 188 | ``` 189 | 190 | | Method | Arguments | Description | Example | 191 | |--- |--- |--- |--- | 192 | | `init()` | | Fires off all methods needed to initialise the accordion. Can be used again after to re-initialise || 193 | | `getState()` | headerId/s - `array` | Returns the state of a panel/s by passing in the _node item index/s_ as an array. | Getting a single Id. `accordion.getState([0])`.
Getting multiple header's state `accordion.getState([0, 1, 2])` | 194 | | `open()` | headerIndex | Opens a given panel using its `headerIndex`. Eg; ` accordion.open( 0 );` || 195 | | `close()` | headerIndex | Closes a given panel using its `headerIndex`. Eg; ` accordion.close( 0 );` || 196 | | `togglePanel()` | animationAction, headerIndex | Toggles panel into opening or closing. `animationAction` is either `open` or `closed` || 197 | | `openAll()` | | Opens all accordion panels || 198 | | `closeAll()` | | Closes all accordion panels || 199 | | `calculatePanelHeight()` | | Calculates and sets a single panels height || 200 | | `calculateAllPanelsHeight()` | | Calculates and sets all panels height || 201 | 202 | 203 | ## Sponsors 204 | A massive thanks to [BrowserStack](https://www.browserstack.com) for supporting me by allowing me to use their platform for free. BrowserStack is a cloud based testing tool that lets you test websites on a wide range web browsers and real mobiles devices. This removes all the hassle of installing chunky VM's. BrowserStack has some great tools such as automated testing, testing local sites (via a browser extension) and taking screenshots. 205 | ![BrowserStack logo](https://digitalscientists.com/system/images/1448/original/logo-browserstack.png) 206 | 207 | 208 | ## Contributors 209 | I've had some awesome people help me out building the accordion. I worked in part on this while working at [Mr B & Friends](https://www.mrbandfriends.co.uk/) big shout out to the digital team there. This wouldn't be anywhere near as good if it wasn't for the wise words of [Dave Smith](https://github.com/getdave). Finally my favourite digital designer [Taavi Kelle](https://twitter.com/taavetkelle) who created the AWESOME logo and gave my demo styles _some love_ Steve Richardson™. 210 | 211 | Also to the following awesome people who have submitted PR's 212 | - [ikenfin](https://github.com/ikenfin) 213 | - [micmania1](https://github.com/micmania1) 214 | - [seanjhulse](https://github.com/seanjhulse) 215 | - [elbojoloco](https://github.com/elbojoloco) 216 | 217 | 218 | ## Roadmap 219 | - General performance & naming review 220 | - Create some mixins to help making custom themes quicker & easier 221 | - Create option for callback methods on each public method 222 | - Export an IE9 safe version in the repo 223 | - Create horizontal accordion option 224 | -------------------------------------------------------------------------------- /src/js/badger-accordion.js: -------------------------------------------------------------------------------- 1 | /** 2 | * ACCORDION 3 | * 4 | * A lightwight vanilla JS accordion with an exstensible API 5 | */ 6 | 7 | // import uuid from 'uuid/v4'; 8 | // const uuidV4 = uuid; 9 | /* eslint-disable no-unused-vars */ 10 | import arrayFromPolyfill from 'array-from-polyfill'; 11 | import onCSSTransitionEnd from 'transition-end'; 12 | 13 | /** 14 | * CONSTRUCTOR 15 | * Initializes the object 16 | */ 17 | class BadgerAccordion { 18 | constructor(el, options) { 19 | const container = typeof el === 'string' ? document.querySelector(el) : el; 20 | 21 | // If el is not defined 22 | if (container == null) { 23 | return; 24 | } 25 | 26 | 27 | const defaults = { 28 | headerClass: '.js-badger-accordion-header', 29 | panelClass: '.js-badger-accordion-panel', 30 | panelInnerClass: '.js-badger-accordion-panel-inner', 31 | hiddenClass: '-ba-is-hidden', 32 | activeClass: '-ba-is-active', 33 | get hidenClass() { return this.hiddenClass; }, 34 | initializedClass: 'badger-accordion--initialized', 35 | get initalisedClass() { return this.initializedClass; }, 36 | headerDataAttr: 'data-badger-accordion-header-id', 37 | openMultiplePanels: false, 38 | openHeadersOnLoad: [], 39 | addListenersOnInit: true, 40 | headerOpenLabel: '', 41 | headerCloseLabel: '', 42 | roles: true 43 | // toggleEl: // If you want to use a different element to trigger the accordion 44 | }; 45 | 46 | // Options 47 | this.settings = Object.assign({}, defaults, options); 48 | 49 | // Setting getting elements 50 | this.container = container; 51 | 52 | // Selecting children of the current accordion instance 53 | const children = Array.from(this.container.children); 54 | 55 | // Since the Accordions header button is nested inside an element with class 56 | // of `badger-accordion__header` it is a grandchild of the accordion instance. 57 | // In order to have nested accordions we need each to only get all the button 58 | // elements for this instance. Here an array is created to show all the children 59 | // of the element `badger-accordion__header`. 60 | const headerParent = children.filter(header => !header.classList.contains(this.settings.panelClass.substr(1))); 61 | 62 | // Creating an array of all DOM nodes that are Accordion headers 63 | this.headers = headerParent.reduce((acc, header) => { 64 | // Gets all the elements that have the headerClass 65 | const a = Array.from(header.children).filter( child => child.classList.contains( this.settings.headerClass.substr(1) )); 66 | 67 | // Merges the current `badger-accordion__header` accordion triggers 68 | // with all the others. 69 | acc = [].concat(...acc, a); 70 | 71 | return acc; 72 | }, []); 73 | 74 | // Creates an array of all panel elements for this instance of the accordion 75 | this.panels = children.filter(panel => panel.classList.contains( this.settings.panelClass.substr(1) )); 76 | 77 | this.toggleEl = this.settings.toggleEl !== undefined ? Array.from(this.container.querySelectorAll(this.settings.toggleEl)) : this.headers; 78 | 79 | 80 | // This is for managing state of the accordion. It by default sets 81 | // all accordion panels to be closed 82 | this.states = [].map.call(this.headers, () => { 83 | return { state: 'closed' }; 84 | }); 85 | 86 | this.ids = [].map.call(this.headers, () => { 87 | return { id: Math.floor((Math.random() * 1000000) + 1) }; 88 | }); 89 | 90 | // This is to ensure that once an open/close event has been fired 91 | // another cannot start until the first event has finished. 92 | // @TODO - get this working... 93 | this.toggling = false; 94 | 95 | // Initiating the accordion 96 | if( this.container ) { 97 | this.init(); 98 | } else { 99 | /* eslint-disable no-console */ 100 | console.log('Something is wrong with you markup...'); 101 | } 102 | } 103 | 104 | 105 | /** 106 | * INIT 107 | * 108 | * Initalises the accordion 109 | */ 110 | init() { 111 | // Sets up ID, aria attrs & data-attrs 112 | this._setupAttributes(); 113 | 114 | // Setting up the inital view of the accordion 115 | this._initalState(); 116 | 117 | // Setting the height of each panel 118 | this.calculateAllPanelsHeight(); 119 | 120 | // Inserting data-attribute onto each `header` 121 | this._insertDataAttrs(); 122 | 123 | // Adding listeners to headers 124 | this._addListeners(); 125 | 126 | // Adds class to accordion for initalisation 127 | this._finishInitialization(); 128 | } 129 | 130 | /** 131 | * CHECK ROLES ETTING 132 | * @return {[boolean]} 133 | * Checks roles setting for all roles or a single role. 134 | * First checks if a `boolean` has been used to set all 135 | * roles to either true or false. If the setting is an 136 | * object it will only set the attribute where each 137 | * attribute has explicitly been set as true, eg; 138 | * ``` 139 | * roles: { 140 | * region: true 141 | * } 142 | * ``` 143 | */ 144 | _setRole(role, el) { 145 | if(typeof this.settings.roles === 'boolean' && this.settings.roles || this.settings.roles[role] !== undefined && this.settings.roles[role] !== false) { 146 | el.setAttribute('role', role); 147 | } 148 | } 149 | 150 | 151 | /** 152 | * INSERT DATA ATTRS 153 | * 154 | * Updates state object for inital loading of the accordion 155 | */ 156 | _initalState() { 157 | // Sets state object as per `this.settings.openHeadersOnLoad` 158 | const headersToOpen = this.settings.openHeadersOnLoad; 159 | 160 | if (headersToOpen.length) { 161 | this._openHeadersOnLoad(headersToOpen); 162 | } 163 | 164 | // Render DOM as per the updates `this.states` object 165 | this._renderDom(); 166 | } 167 | 168 | 169 | /** 170 | * INSERT DATA ATTRS 171 | * 172 | * Adds `headerDataAttr` to all headers 173 | */ 174 | _insertDataAttrs() { 175 | this.headers.forEach( (header, index) => { 176 | header.setAttribute(this.settings.headerDataAttr, index); 177 | }); 178 | } 179 | 180 | 181 | /** 182 | * FINISH INITALISATION 183 | * 184 | * Adds in `initializedClass` to accordion 185 | */ 186 | _finishInitialization() { 187 | this.container.classList.add(this.settings.initializedClass); 188 | this._setRole('presentation', this.container); 189 | } 190 | 191 | 192 | /** 193 | * ADD LISTENERS 194 | * 195 | * Adds click event to each header 196 | */ 197 | _addListeners() { 198 | if (!this.settings.addListenersOnInit) return; 199 | 200 | // So we can reference the badger-accordion object inside out eventListener 201 | const _this = this; 202 | 203 | // Adding click event to accordion 204 | this.headers.forEach((header, index) => { 205 | header.addEventListener('click', function() { 206 | // Getting the target of the click 207 | // const clickedEl = event.target; 208 | 209 | _this.handleClick(header, index); 210 | }); 211 | }); 212 | } 213 | 214 | 215 | /** 216 | * HANDLE CLICK 217 | * 218 | * Handles click and checks if click was on an header element 219 | * @param {object} targetHeader - The header node you want to open 220 | */ 221 | handleClick(targetHeader, headerIndex) { 222 | // Removing current `.` from `this.settings.headerClass` class so it can 223 | // be checked against the `targetHeader` classList 224 | const targetHeaderClass = this.settings.headerClass.substr(1); 225 | 226 | // Checking that the thing that was clicked on was the accordions header 227 | if (targetHeader.classList.contains(targetHeaderClass) && this.toggling === false) { 228 | this.toggling = true; 229 | 230 | // Updating states 231 | this.setState(headerIndex); 232 | 233 | // Render DOM as per the updates `this.states` object 234 | this._renderDom(); 235 | } 236 | } 237 | 238 | 239 | /** 240 | * SET STATES 241 | * 242 | * Sets the state for all headers. The 'target header' will have its state toggeled 243 | * @param {object} targetHeaderId - The header node you want to open 244 | */ 245 | setState(targetHeaderId) { 246 | const states = this.getState(); 247 | 248 | // If `this.settings.openMultiplePanels` is false we need to ensure only one panel 249 | // be can open at once. If it is false then all panels state APART from the one that 250 | // has just been clicked needs to be set to 'closed'. 251 | if (!this.settings.openMultiplePanels) { 252 | states.filter((state, index) => { 253 | if (index != targetHeaderId) { 254 | state.state = 'closed'; 255 | } 256 | }); 257 | } 258 | 259 | // Toggles the state value of the target header. This was `array.find` but `find` 260 | // isnt supported in IE11 261 | states.filter((state, index) => { 262 | if (index == targetHeaderId) { 263 | const newState = this.toggleState(state.state); 264 | return (state.state = newState); 265 | } 266 | }); 267 | } 268 | 269 | 270 | /** 271 | * RENDER DOM 272 | * 273 | * Renders the accordion in the DOM using the `this.states` object 274 | */ 275 | _renderDom() { 276 | // Filter through all open headers and open them 277 | this.states.filter( (state, index) => { 278 | if(state.state === 'open') { 279 | // Opening the current panel but _NOT_ updating the state 280 | this.open(index, false); 281 | } 282 | }); 283 | 284 | // Filter through all closed headers and closes them 285 | this.states.filter( (state, index) => { 286 | if(state.state === 'closed') { 287 | // Closing the current panel but _NOT_ updating the state 288 | this.close(index, false); 289 | } 290 | }); 291 | } 292 | 293 | 294 | /** 295 | * OPEN 296 | * 297 | * Closes a specific panel 298 | * @param {integer} headerIndex - The header node index you want to open 299 | */ 300 | open(headerIndex, setState = true) { 301 | // 1. If being fired directly the state needs to be updated. 302 | if(setState) { 303 | this.setState(headerIndex); 304 | } 305 | 306 | this.togglePanel('open', headerIndex); 307 | } 308 | 309 | 310 | /** 311 | * CLOSE 312 | * 313 | * Closes a specific panel 314 | * @param {integer} headerIndex - The header node index you want to close 315 | */ 316 | close(headerIndex, setState = true) { 317 | // 1. If being fired directly the state needs to be updated. 318 | if(setState) { 319 | this.setState(headerIndex); 320 | } 321 | 322 | this.togglePanel('closed', headerIndex); 323 | } 324 | 325 | 326 | /** 327 | * OPEN ALL 328 | * 329 | * Opens all panels 330 | */ 331 | openAll() { 332 | this.headers.forEach((header, headerIndex) => { 333 | this.togglePanel('open', headerIndex); 334 | }); 335 | } 336 | 337 | 338 | /** 339 | * CLOSE ALL 340 | * 341 | * Closes all panels 342 | */ 343 | closeAll() { 344 | this.headers.forEach((header, headerIndex) => { 345 | this.togglePanel('closed', headerIndex); 346 | }); 347 | } 348 | 349 | 350 | /** 351 | * GET STATE 352 | * 353 | * Getting state of headers. By default gets state of all headers 354 | * @param {string} animationAction - The animation you want to invoke 355 | * @param {integer} headerIndex - The header node index you want to animate 356 | */ 357 | togglePanel(animationAction, headerIndex) { 358 | if(animationAction !== undefined && headerIndex !== undefined) { 359 | if(animationAction === 'closed') { 360 | // 1. Getting ID of panel that we want to close 361 | const header = this.headers[headerIndex]; 362 | const panelToClose = this.panels[headerIndex]; 363 | 364 | // 2. Closeing panel 365 | panelToClose.classList.add(this.settings.hiddenClass); 366 | 367 | // 3. Removing active classes 368 | panelToClose.classList.remove(this.settings.activeClass); 369 | header.classList.remove(this.settings.activeClass); 370 | 371 | // 4. Set aria attrs 372 | header.setAttribute('aria-expanded', false); 373 | 374 | // 5. Resetting toggling so a new event can be fired 375 | panelToClose.onCSSTransitionEnd(() => this.toggling = false ); 376 | } else if(animationAction === 'open') { 377 | // 1. Getting ID of panel that we want to open 378 | const header = this.headers[headerIndex]; 379 | const panelToOpen = this.panels[headerIndex]; 380 | 381 | // 2. Opening panel 382 | panelToOpen.classList.remove(this.settings.hiddenClass); 383 | 384 | // 3. Adding active classes 385 | panelToOpen.classList.add(this.settings.activeClass); 386 | header.classList.add(this.settings.activeClass); 387 | 388 | // 4. Set aria attrs 389 | header.setAttribute('aria-expanded', true); 390 | 391 | // 5. Resetting toggling so a new event can be fired 392 | panelToOpen.onCSSTransitionEnd(() => this.toggling = false ); 393 | } 394 | } 395 | } 396 | 397 | 398 | // @TODO - is this needed anymore? 399 | // checkState(headerId) { 400 | // let state = this.states[headerId].state; 401 | // 402 | // if(state === 'closed') { 403 | // return state; 404 | // } else if(state === 'open') { 405 | // return state; 406 | // } 407 | // } 408 | 409 | 410 | /** 411 | * GET STATE 412 | * 413 | * Getting state of headers. By default gets state of all headers 414 | * @param {array} headerIds - Id/'s of the headers you want to check 415 | */ 416 | getState(headerIds = []) { 417 | if(headerIds.length && Array.isArray(headerIds)) { 418 | let states = headerIds.map( header => this.states[header] ); 419 | 420 | return states; 421 | } else { 422 | return this.states; 423 | } 424 | } 425 | 426 | 427 | /** 428 | * TOGGLE STATE 429 | * 430 | * Toggling the state value 431 | * @param {string} currentState - Current state value for a header 432 | */ 433 | toggleState(currentState) { 434 | if(currentState !== undefined) { 435 | return (currentState === 'closed') ? 'open' : 'closed'; 436 | } 437 | } 438 | 439 | 440 | 441 | /** 442 | * HEADERS TO OPEN 443 | * 444 | * Setting which headers should be open when accordion is initalised 445 | * @param {array} headersToOpen - Array of ID's for the headers to be open 446 | */ 447 | _openHeadersOnLoad(headersToOpen = []) { 448 | if (headersToOpen.length && Array.isArray(headersToOpen)) { 449 | let headers = headersToOpen.filter(header => header != undefined); 450 | 451 | headers.forEach(header => { 452 | this.setState(header); 453 | }); 454 | } 455 | } 456 | 457 | 458 | /** 459 | * SET UP ATTRIBUTES 460 | * 461 | * Initalises accordion attribute methods 462 | */ 463 | _setupAttributes() { 464 | // Adding ID & aria-controls 465 | this._setupHeaders(); 466 | 467 | // Adding ID & aria-labelledby 468 | this._setupPanels(); 469 | 470 | // Inserting data-attribute onto each `header` 471 | this._insertDataAttrs(); 472 | } 473 | 474 | 475 | 476 | /** 477 | * SET PANEL HEIGHT - ** DEPRICATED ** 478 | * 479 | * Depreicated as this method is becoming public and 480 | * I want to name it something that lets devs know 481 | * it's not just for using inside the `init()` method. 482 | */ 483 | _setPanelHeight() { 484 | this.calculateAllPanelsHeight(); 485 | } 486 | 487 | 488 | 489 | /** 490 | * CALCULATE PANEL HEIGHT 491 | * 492 | * Setting height for panels using pannels inner element 493 | */ 494 | calculatePanelHeight(panel) { 495 | const panelInner = panel.querySelector(this.settings.panelInnerClass); 496 | 497 | let activeHeight = panelInner.offsetHeight; 498 | 499 | return panel.style.maxHeight = `${activeHeight}px`; 500 | } 501 | 502 | 503 | 504 | /** 505 | * CALCULATE PANEL HEIGHT 506 | * 507 | * Setting height for panels using pannels inner element 508 | */ 509 | calculateAllPanelsHeight() { 510 | this.panels.forEach(panel => { 511 | this.calculatePanelHeight(panel); 512 | }); 513 | } 514 | 515 | 516 | 517 | /** 518 | * SET UP HEADERS 519 | */ 520 | _setupHeaders() { 521 | this.headers.forEach( (header, index) => { 522 | header.setAttribute('id', `badger-accordion-header-${this.ids[index].id}`); 523 | header.setAttribute('aria-controls', `badger-accordion-panel-${this.ids[index].id}`); 524 | }); 525 | } 526 | 527 | 528 | /** 529 | * SET UP PANELS 530 | */ 531 | _setupPanels() { 532 | this.panels.forEach( (panel, index) => { 533 | panel.setAttribute('id', `badger-accordion-panel-${this.ids[index].id}`); 534 | panel.setAttribute('aria-labelledby', `badger-accordion-header-${this.ids[index].id}`); 535 | if(this.settings.roles === true || this.settings.roles.region !== false) { 536 | this._setRole('region', panel); 537 | } 538 | }); 539 | } 540 | } 541 | 542 | 543 | // Export 544 | export default BadgerAccordion; 545 | -------------------------------------------------------------------------------- /example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Badger Accordion 9 | 10 | 25 | 26 | 27 | 28 | 29 | 30 | 31 |
32 |
33 |
34 |

Badger Accordion

35 | 39 | 40 |

An accessible light weight, vanilla JavaScript accordion with an extensible API. Just 6.14kb and Gzipped 1.86kb! Checkout the links below for more info.

41 | 42 | 55 |
56 | 57 |

Basic Accordion

58 | 59 |
60 |
61 | 68 |
69 |
70 |
71 |

Badgers are short-legged omnivores in the family Mustelidae, which also includes otters, polecats, weasels and wolverines. They belong to the caniform suborder of carnivoran mammals.

72 |

Badgers are thought to have got their name because of the white mark – or badge – on their head, although there are other theories.

73 |

Another old name for badgers is ‘brock’, meaning grey. You can often see the word brock in street names. Brock is also the name of a character in the Pokemon TV series!

74 |

Badgers are fast – they can run up to 30km per hour (nearly 20 mph) for short periods.

75 | I am a link to the main content of this page. 76 |
77 |
78 |
79 | 86 |
87 |
88 |
89 |

Honey badgers can reach 2.4 feet in length and weigh between 19 and 26 pounds. They have bushy tail that is usually 12 inches long.

90 |

Honey badger has incredible thick skin that cannot be pierced with arrows, spears or even machete. Skin is also very loose, which is useful in the case of attack. When predator grabs a badger, animal rotates in its skin and turns 91 | toward predator's face to fight back (attacking its eyes).

92 |

Honey badger has very sharp teeth. They can easily break tortoise shell.

93 |
94 |
95 |
96 | 103 |
104 |
105 |
106 |

Although badgers are a solitary animal the young Hog Badger tends to be quite playful and social. I would be careful playing with any animal that has extremely large claws. Remember folks, it is all fun and games until someone 107 | loses an eye.

108 |

Hog Badgers are omnivores and they feed on a variety of things from honey and fruit to insects and small mammals.

109 |

A young / baby of a hog badger is called a 'kit'. The females are called 'sow' and males 'boar'. A hog badger group is called a 'cete, colony, set or company'.

110 |
111 |
112 |
113 |
114 | 115 |
116 |

Nested Accordion

117 | 118 |
119 |
120 | 127 |
128 |
129 | 130 |
131 |
132 |
133 | 140 |
141 |
142 |
143 |

Badgers are short-legged omnivores in the family Mustelidae, which also includes otters, 144 | polecats, 145 | weasels and wolverines. They belong to the caniform suborder of carnivoran mammals.

146 |

Badgers are thought to have got their name because of the white mark – or badge – on their 147 | head, 148 | although there are other theories.

149 |

Another old name for badgers is ‘brock’, meaning grey. You can often see the word brock in 150 | street names. 151 | Brock is also the name of a character in the Pokemon TV series!

152 |

Badgers are fast – they can run up to 30km per hour (nearly 20 mph) for short periods.

153 | I am a link to the main content of this page. 154 |
155 |
156 |
157 |
158 | 159 |
160 |
161 | 168 |
169 |
170 |
171 |

Badgers are short-legged omnivores in the family Mustelidae, which also includes otters, polecats, 172 | weasels 173 | and wolverines. They belong to the caniform suborder of carnivoran mammals.

174 |

Badgers are thought to have got their name because of the white mark – or badge – on their head, 175 | although 176 | there are other theories.

177 |

Another old name for badgers is ‘brock’, meaning grey. You can often see the word brock in street names. 178 | Brock is also the name of a character in the Pokemon TV series!

179 |

Badgers are fast – they can run up to 30km per hour (nearly 20 mph) for short periods.

180 |
181 |
182 |
183 |
184 |
185 | 186 | 187 | 188 | 189 | 190 | -------------------------------------------------------------------------------- /dist/badger-accordion.esm.js: -------------------------------------------------------------------------------- 1 | function _classCallCheck(instance, Constructor) { 2 | if (!(instance instanceof Constructor)) { 3 | throw new TypeError("Cannot call a class as a function"); 4 | } 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, descriptor.key, descriptor); 14 | } 15 | } 16 | 17 | function _createClass(Constructor, protoProps, staticProps) { 18 | if (protoProps) _defineProperties(Constructor.prototype, protoProps); 19 | if (staticProps) _defineProperties(Constructor, staticProps); 20 | return Constructor; 21 | } 22 | 23 | function _extends() { 24 | _extends = Object.assign || function (target) { 25 | for (var i = 1; i < arguments.length; i++) { 26 | var source = arguments[i]; 27 | 28 | for (var key in source) { 29 | if (Object.prototype.hasOwnProperty.call(source, key)) { 30 | target[key] = source[key]; 31 | } 32 | } 33 | } 34 | 35 | return target; 36 | }; 37 | 38 | return _extends.apply(this, arguments); 39 | } 40 | 41 | function _toConsumableArray(arr) { 42 | return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _nonIterableSpread(); 43 | } 44 | 45 | function _arrayWithoutHoles(arr) { 46 | if (Array.isArray(arr)) { 47 | for (var i = 0, arr2 = new Array(arr.length); i < arr.length; i++) arr2[i] = arr[i]; 48 | 49 | return arr2; 50 | } 51 | } 52 | 53 | function _iterableToArray(iter) { 54 | if (Symbol.iterator in Object(iter) || Object.prototype.toString.call(iter) === "[object Arguments]") return Array.from(iter); 55 | } 56 | 57 | function _nonIterableSpread() { 58 | throw new TypeError("Invalid attempt to spread non-iterable instance"); 59 | } 60 | 61 | if (!Array.from) { 62 | Array.from = function () { 63 | var toStr = Object.prototype.toString; 64 | 65 | var isCallable = function isCallable(fn) { 66 | return typeof fn === 'function' || toStr.call(fn) === '[object Function]'; 67 | }; 68 | 69 | var toInteger = function toInteger(value) { 70 | var number = Number(value); 71 | 72 | if (isNaN(number)) { 73 | return 0; 74 | } 75 | 76 | if (number === 0 || !isFinite(number)) { 77 | return number; 78 | } 79 | 80 | return (number > 0 ? 1 : -1) * Math.floor(Math.abs(number)); 81 | }; 82 | 83 | var maxSafeInteger = Math.pow(2, 53) - 1; 84 | 85 | var toLength = function toLength(value) { 86 | var len = toInteger(value); 87 | return Math.min(Math.max(len, 0), maxSafeInteger); 88 | }; // The length property of the from method is 1. 89 | 90 | 91 | return function from(arrayLike 92 | /* , mapFn, thisArg */ 93 | ) { 94 | // 1. Let C be the this value. 95 | var C = this; // 2. Let items be ToObject(arrayLike). 96 | 97 | var items = Object(arrayLike); // 3. ReturnIfAbrupt(items). 98 | 99 | if (arrayLike == null) { 100 | throw new TypeError('Array.from requires an array-like object - not null or undefined'); 101 | } // 4. If mapfn is undefined, then let mapping be false. 102 | 103 | 104 | var mapFn = arguments.length > 1 ? arguments[1] : void undefined; 105 | var T; 106 | 107 | if (typeof mapFn !== 'undefined') { 108 | // 5. else 109 | // 5. a If IsCallable(mapfn) is false, throw a TypeError exception. 110 | if (!isCallable(mapFn)) { 111 | throw new TypeError('Array.from: when provided, the second argument must be a function'); 112 | } // 5. b. If thisArg was supplied, let T be thisArg; else let T be undefined. 113 | 114 | 115 | if (arguments.length > 2) { 116 | T = arguments[2]; 117 | } 118 | } // 10. Let lenValue be Get(items, "length"). 119 | // 11. Let len be ToLength(lenValue). 120 | 121 | 122 | var len = toLength(items.length); // 13. If IsConstructor(C) is true, then 123 | // 13. a. Let A be the result of calling the [[Construct]] internal method 124 | // of C with an argument list containing the single item len. 125 | // 14. a. Else, Let A be ArrayCreate(len). 126 | 127 | var A = isCallable(C) ? Object(new C(len)) : new Array(len); // 16. Let k be 0. 128 | 129 | var k = 0; // 17. Repeat, while k < len… (also steps a - h) 130 | 131 | var kValue; 132 | 133 | while (k < len) { 134 | kValue = items[k]; 135 | 136 | if (mapFn) { 137 | A[k] = typeof T === 'undefined' ? mapFn(kValue, k) : mapFn.call(T, kValue, k); 138 | } else { 139 | A[k] = kValue; 140 | } 141 | 142 | k += 1; 143 | } // 18. Let putStatus be Put(A, "length", len, true). 144 | 145 | 146 | A.length = len; // 20. Return A. 147 | 148 | return A; 149 | }; 150 | }(); 151 | } 152 | 153 | /* 154 | By Osvaldas Valutis, www.osvaldas.info 155 | Available for use under the MIT License 156 | */ 157 | 158 | /* eslint-disable no-unused-vars */ 159 | (function (document, window) { 160 | var el = document.body || document.documentElement, 161 | s = el.style, 162 | prefixAnimation = '', 163 | prefixTransition = ''; 164 | if (s.WebkitAnimation == '') prefixAnimation = '-webkit-'; 165 | if (s.MozAnimation == '') prefixAnimation = '-moz-'; 166 | if (s.OAnimation == '') prefixAnimation = '-o-'; 167 | if (s.WebkitTransition == '') prefixTransition = '-webkit-'; 168 | if (s.MozTransition == '') prefixTransition = '-moz-'; 169 | if (s.OTransition == '') prefixTransition = '-o-'; 170 | Object.defineProperty(Object.prototype, 'onCSSAnimationEnd', { 171 | value: function value(callback) { 172 | var runOnce = function runOnce(e) { 173 | callback(); 174 | e.target.removeEventListener(e.type, runOnce); 175 | }; 176 | 177 | this.addEventListener('webkitAnimationEnd', runOnce); 178 | this.addEventListener('mozAnimationEnd', runOnce); 179 | this.addEventListener('oAnimationEnd', runOnce); 180 | this.addEventListener('oanimationend', runOnce); 181 | this.addEventListener('animationend', runOnce); 182 | if (prefixAnimation == '' && !('animation' in s) || getComputedStyle(this)[prefixAnimation + 'animation-duration'] == '0s') callback(); 183 | return this; 184 | }, 185 | enumerable: false, 186 | writable: true 187 | }); 188 | Object.defineProperty(Object.prototype, 'onCSSTransitionEnd', { 189 | value: function value(callback) { 190 | var runOnce = function runOnce(e) { 191 | callback(); 192 | e.target.removeEventListener(e.type, runOnce); 193 | }; 194 | 195 | this.addEventListener('webkitTransitionEnd', runOnce); 196 | this.addEventListener('mozTransitionEnd', runOnce); 197 | this.addEventListener('oTransitionEnd', runOnce); 198 | this.addEventListener('transitionend', runOnce); 199 | this.addEventListener('transitionend', runOnce); 200 | if (prefixTransition == '' && !('transition' in s) || getComputedStyle(this)[prefixTransition + 'transition-duration'] == '0s') callback(); 201 | return this; 202 | }, 203 | enumerable: false, 204 | writable: true 205 | }); 206 | })(document, window, 0); 207 | 208 | /** 209 | * ACCORDION 210 | * 211 | * A lightwight vanilla JS accordion with an exstensible API 212 | */ 213 | // import uuid from 'uuid/v4'; 214 | // const uuidV4 = uuid; 215 | 216 | /* eslint-disable no-unused-vars */ 217 | /** 218 | * CONSTRUCTOR 219 | * Initializes the object 220 | */ 221 | 222 | var BadgerAccordion = 223 | /*#__PURE__*/ 224 | function () { 225 | function BadgerAccordion(el, options) { 226 | var _this2 = this; 227 | 228 | _classCallCheck(this, BadgerAccordion); 229 | 230 | var container = typeof el === 'string' ? document.querySelector(el) : el; // If el is not defined 231 | 232 | if (container == null) { 233 | return; 234 | } 235 | 236 | var defaults = { 237 | headerClass: '.js-badger-accordion-header', 238 | panelClass: '.js-badger-accordion-panel', 239 | panelInnerClass: '.js-badger-accordion-panel-inner', 240 | hiddenClass: '-ba-is-hidden', 241 | activeClass: '-ba-is-active', 242 | 243 | get hidenClass() { 244 | return this.hiddenClass; 245 | }, 246 | 247 | initializedClass: 'badger-accordion--initialized', 248 | 249 | get initalisedClass() { 250 | return this.initializedClass; 251 | }, 252 | 253 | headerDataAttr: 'data-badger-accordion-header-id', 254 | openMultiplePanels: false, 255 | openHeadersOnLoad: [], 256 | addListenersOnInit: true, 257 | headerOpenLabel: '', 258 | headerCloseLabel: '', 259 | roles: true // toggleEl: // If you want to use a different element to trigger the accordion 260 | 261 | }; // Options 262 | 263 | this.settings = _extends({}, defaults, options); // Setting getting elements 264 | 265 | this.container = container; // Selecting children of the current accordion instance 266 | 267 | var children = Array.from(this.container.children); // Since the Accordions header button is nested inside an element with class 268 | // of `badger-accordion__header` it is a grandchild of the accordion instance. 269 | // In order to have nested accordions we need each to only get all the button 270 | // elements for this instance. Here an array is created to show all the children 271 | // of the element `badger-accordion__header`. 272 | 273 | var headerParent = children.filter(function (header) { 274 | return !header.classList.contains(_this2.settings.panelClass.substr(1)); 275 | }); // Creating an array of all DOM nodes that are Accordion headers 276 | 277 | this.headers = headerParent.reduce(function (acc, header) { 278 | var _ref; 279 | 280 | // Gets all the elements that have the headerClass 281 | var a = Array.from(header.children).filter(function (child) { 282 | return child.classList.contains(_this2.settings.headerClass.substr(1)); 283 | }); // Merges the current `badger-accordion__header` accordion triggers 284 | // with all the others. 285 | 286 | acc = (_ref = []).concat.apply(_ref, _toConsumableArray(acc).concat([a])); 287 | return acc; 288 | }, []); // Creates an array of all panel elements for this instance of the accordion 289 | 290 | this.panels = children.filter(function (panel) { 291 | return panel.classList.contains(_this2.settings.panelClass.substr(1)); 292 | }); 293 | this.toggleEl = this.settings.toggleEl !== undefined ? Array.from(this.container.querySelectorAll(this.settings.toggleEl)) : this.headers; // This is for managing state of the accordion. It by default sets 294 | // all accordion panels to be closed 295 | 296 | this.states = [].map.call(this.headers, function () { 297 | return { 298 | state: 'closed' 299 | }; 300 | }); 301 | this.ids = [].map.call(this.headers, function () { 302 | return { 303 | id: Math.floor(Math.random() * 1000000 + 1) 304 | }; 305 | }); // This is to ensure that once an open/close event has been fired 306 | // another cannot start until the first event has finished. 307 | // @TODO - get this working... 308 | 309 | this.toggling = false; // Initiating the accordion 310 | 311 | if (this.container) { 312 | this.init(); 313 | } else { 314 | /* eslint-disable no-console */ 315 | console.log('Something is wrong with you markup...'); 316 | } 317 | } 318 | /** 319 | * INIT 320 | * 321 | * Initalises the accordion 322 | */ 323 | 324 | 325 | _createClass(BadgerAccordion, [{ 326 | key: "init", 327 | value: function init() { 328 | // Sets up ID, aria attrs & data-attrs 329 | this._setupAttributes(); // Setting up the inital view of the accordion 330 | 331 | 332 | this._initalState(); // Setting the height of each panel 333 | 334 | 335 | this.calculateAllPanelsHeight(); // Inserting data-attribute onto each `header` 336 | 337 | this._insertDataAttrs(); // Adding listeners to headers 338 | 339 | 340 | this._addListeners(); // Adds class to accordion for initalisation 341 | 342 | 343 | this._finishInitialization(); 344 | } 345 | /** 346 | * CHECK ROLES ETTING 347 | * @return {[boolean]} 348 | * Checks roles setting for all roles or a single role. 349 | * First checks if a `boolean` has been used to set all 350 | * roles to either true or false. If the setting is an 351 | * object it will only set the attribute where each 352 | * attribute has explicitly been set as true, eg; 353 | * ``` 354 | * roles: { 355 | * region: true 356 | * } 357 | * ``` 358 | */ 359 | 360 | }, { 361 | key: "_setRole", 362 | value: function _setRole(role, el) { 363 | if (typeof this.settings.roles === 'boolean' && this.settings.roles || this.settings.roles[role] !== undefined && this.settings.roles[role] !== false) { 364 | el.setAttribute('role', role); 365 | } 366 | } 367 | /** 368 | * INSERT DATA ATTRS 369 | * 370 | * Updates state object for inital loading of the accordion 371 | */ 372 | 373 | }, { 374 | key: "_initalState", 375 | value: function _initalState() { 376 | // Sets state object as per `this.settings.openHeadersOnLoad` 377 | var headersToOpen = this.settings.openHeadersOnLoad; 378 | 379 | if (headersToOpen.length) { 380 | this._openHeadersOnLoad(headersToOpen); 381 | } // Render DOM as per the updates `this.states` object 382 | 383 | 384 | this._renderDom(); 385 | } 386 | /** 387 | * INSERT DATA ATTRS 388 | * 389 | * Adds `headerDataAttr` to all headers 390 | */ 391 | 392 | }, { 393 | key: "_insertDataAttrs", 394 | value: function _insertDataAttrs() { 395 | var _this3 = this; 396 | 397 | this.headers.forEach(function (header, index) { 398 | header.setAttribute(_this3.settings.headerDataAttr, index); 399 | }); 400 | } 401 | /** 402 | * FINISH INITALISATION 403 | * 404 | * Adds in `initializedClass` to accordion 405 | */ 406 | 407 | }, { 408 | key: "_finishInitialization", 409 | value: function _finishInitialization() { 410 | this.container.classList.add(this.settings.initializedClass); 411 | 412 | this._setRole('presentation', this.container); 413 | } 414 | /** 415 | * ADD LISTENERS 416 | * 417 | * Adds click event to each header 418 | */ 419 | 420 | }, { 421 | key: "_addListeners", 422 | value: function _addListeners() { 423 | if (!this.settings.addListenersOnInit) return; // So we can reference the badger-accordion object inside out eventListener 424 | 425 | var _this = this; // Adding click event to accordion 426 | 427 | 428 | this.headers.forEach(function (header, index) { 429 | header.addEventListener('click', function () { 430 | // Getting the target of the click 431 | // const clickedEl = event.target; 432 | _this.handleClick(header, index); 433 | }); 434 | }); 435 | } 436 | /** 437 | * HANDLE CLICK 438 | * 439 | * Handles click and checks if click was on an header element 440 | * @param {object} targetHeader - The header node you want to open 441 | */ 442 | 443 | }, { 444 | key: "handleClick", 445 | value: function handleClick(targetHeader, headerIndex) { 446 | // Removing current `.` from `this.settings.headerClass` class so it can 447 | // be checked against the `targetHeader` classList 448 | var targetHeaderClass = this.settings.headerClass.substr(1); // Checking that the thing that was clicked on was the accordions header 449 | 450 | if (targetHeader.classList.contains(targetHeaderClass) && this.toggling === false) { 451 | this.toggling = true; // Updating states 452 | 453 | this.setState(headerIndex); // Render DOM as per the updates `this.states` object 454 | 455 | this._renderDom(); 456 | } 457 | } 458 | /** 459 | * SET STATES 460 | * 461 | * Sets the state for all headers. The 'target header' will have its state toggeled 462 | * @param {object} targetHeaderId - The header node you want to open 463 | */ 464 | 465 | }, { 466 | key: "setState", 467 | value: function setState(targetHeaderId) { 468 | var _this4 = this; 469 | 470 | var states = this.getState(); // If `this.settings.openMultiplePanels` is false we need to ensure only one panel 471 | // be can open at once. If it is false then all panels state APART from the one that 472 | // has just been clicked needs to be set to 'closed'. 473 | 474 | if (!this.settings.openMultiplePanels) { 475 | states.filter(function (state, index) { 476 | if (index != targetHeaderId) { 477 | state.state = 'closed'; 478 | } 479 | }); 480 | } // Toggles the state value of the target header. This was `array.find` but `find` 481 | // isnt supported in IE11 482 | 483 | 484 | states.filter(function (state, index) { 485 | if (index == targetHeaderId) { 486 | var newState = _this4.toggleState(state.state); 487 | 488 | return state.state = newState; 489 | } 490 | }); 491 | } 492 | /** 493 | * RENDER DOM 494 | * 495 | * Renders the accordion in the DOM using the `this.states` object 496 | */ 497 | 498 | }, { 499 | key: "_renderDom", 500 | value: function _renderDom() { 501 | var _this5 = this; 502 | 503 | // Filter through all open headers and open them 504 | this.states.filter(function (state, index) { 505 | if (state.state === 'open') { 506 | // Opening the current panel but _NOT_ updating the state 507 | _this5.open(index, false); 508 | } 509 | }); // Filter through all closed headers and closes them 510 | 511 | this.states.filter(function (state, index) { 512 | if (state.state === 'closed') { 513 | // Closing the current panel but _NOT_ updating the state 514 | _this5.close(index, false); 515 | } 516 | }); 517 | } 518 | /** 519 | * OPEN 520 | * 521 | * Closes a specific panel 522 | * @param {integer} headerIndex - The header node index you want to open 523 | */ 524 | 525 | }, { 526 | key: "open", 527 | value: function open(headerIndex) { 528 | var setState = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true; 529 | 530 | // 1. If being fired directly the state needs to be updated. 531 | if (setState) { 532 | this.setState(headerIndex); 533 | } 534 | 535 | this.togglePanel('open', headerIndex); 536 | } 537 | /** 538 | * CLOSE 539 | * 540 | * Closes a specific panel 541 | * @param {integer} headerIndex - The header node index you want to close 542 | */ 543 | 544 | }, { 545 | key: "close", 546 | value: function close(headerIndex) { 547 | var setState = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true; 548 | 549 | // 1. If being fired directly the state needs to be updated. 550 | if (setState) { 551 | this.setState(headerIndex); 552 | } 553 | 554 | this.togglePanel('closed', headerIndex); 555 | } 556 | /** 557 | * OPEN ALL 558 | * 559 | * Opens all panels 560 | */ 561 | 562 | }, { 563 | key: "openAll", 564 | value: function openAll() { 565 | var _this6 = this; 566 | 567 | this.headers.forEach(function (header, headerIndex) { 568 | _this6.togglePanel('open', headerIndex); 569 | }); 570 | } 571 | /** 572 | * CLOSE ALL 573 | * 574 | * Closes all panels 575 | */ 576 | 577 | }, { 578 | key: "closeAll", 579 | value: function closeAll() { 580 | var _this7 = this; 581 | 582 | this.headers.forEach(function (header, headerIndex) { 583 | _this7.togglePanel('closed', headerIndex); 584 | }); 585 | } 586 | /** 587 | * GET STATE 588 | * 589 | * Getting state of headers. By default gets state of all headers 590 | * @param {string} animationAction - The animation you want to invoke 591 | * @param {integer} headerIndex - The header node index you want to animate 592 | */ 593 | 594 | }, { 595 | key: "togglePanel", 596 | value: function togglePanel(animationAction, headerIndex) { 597 | var _this8 = this; 598 | 599 | if (animationAction !== undefined && headerIndex !== undefined) { 600 | if (animationAction === 'closed') { 601 | // 1. Getting ID of panel that we want to close 602 | var header = this.headers[headerIndex]; 603 | var panelToClose = this.panels[headerIndex]; // 2. Closeing panel 604 | 605 | panelToClose.classList.add(this.settings.hiddenClass); // 3. Removing active classes 606 | 607 | panelToClose.classList.remove(this.settings.activeClass); 608 | header.classList.remove(this.settings.activeClass); // 4. Set aria attrs 609 | 610 | header.setAttribute('aria-expanded', false); // 5. Resetting toggling so a new event can be fired 611 | 612 | panelToClose.onCSSTransitionEnd(function () { 613 | return _this8.toggling = false; 614 | }); 615 | } else if (animationAction === 'open') { 616 | // 1. Getting ID of panel that we want to open 617 | var _header = this.headers[headerIndex]; 618 | var panelToOpen = this.panels[headerIndex]; // 2. Opening panel 619 | 620 | panelToOpen.classList.remove(this.settings.hiddenClass); // 3. Adding active classes 621 | 622 | panelToOpen.classList.add(this.settings.activeClass); 623 | 624 | _header.classList.add(this.settings.activeClass); // 4. Set aria attrs 625 | 626 | 627 | _header.setAttribute('aria-expanded', true); // 5. Resetting toggling so a new event can be fired 628 | 629 | 630 | panelToOpen.onCSSTransitionEnd(function () { 631 | return _this8.toggling = false; 632 | }); 633 | } 634 | } 635 | } // @TODO - is this needed anymore? 636 | // checkState(headerId) { 637 | // let state = this.states[headerId].state; 638 | // 639 | // if(state === 'closed') { 640 | // return state; 641 | // } else if(state === 'open') { 642 | // return state; 643 | // } 644 | // } 645 | 646 | /** 647 | * GET STATE 648 | * 649 | * Getting state of headers. By default gets state of all headers 650 | * @param {array} headerIds - Id/'s of the headers you want to check 651 | */ 652 | 653 | }, { 654 | key: "getState", 655 | value: function getState() { 656 | var _this9 = this; 657 | 658 | var headerIds = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : []; 659 | 660 | if (headerIds.length && Array.isArray(headerIds)) { 661 | var states = headerIds.map(function (header) { 662 | return _this9.states[header]; 663 | }); 664 | return states; 665 | } else { 666 | return this.states; 667 | } 668 | } 669 | /** 670 | * TOGGLE STATE 671 | * 672 | * Toggling the state value 673 | * @param {string} currentState - Current state value for a header 674 | */ 675 | 676 | }, { 677 | key: "toggleState", 678 | value: function toggleState(currentState) { 679 | if (currentState !== undefined) { 680 | return currentState === 'closed' ? 'open' : 'closed'; 681 | } 682 | } 683 | /** 684 | * HEADERS TO OPEN 685 | * 686 | * Setting which headers should be open when accordion is initalised 687 | * @param {array} headersToOpen - Array of ID's for the headers to be open 688 | */ 689 | 690 | }, { 691 | key: "_openHeadersOnLoad", 692 | value: function _openHeadersOnLoad() { 693 | var _this10 = this; 694 | 695 | var headersToOpen = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : []; 696 | 697 | if (headersToOpen.length && Array.isArray(headersToOpen)) { 698 | var headers = headersToOpen.filter(function (header) { 699 | return header != undefined; 700 | }); 701 | headers.forEach(function (header) { 702 | _this10.setState(header); 703 | }); 704 | } 705 | } 706 | /** 707 | * SET UP ATTRIBUTES 708 | * 709 | * Initalises accordion attribute methods 710 | */ 711 | 712 | }, { 713 | key: "_setupAttributes", 714 | value: function _setupAttributes() { 715 | // Adding ID & aria-controls 716 | this._setupHeaders(); // Adding ID & aria-labelledby 717 | 718 | 719 | this._setupPanels(); // Inserting data-attribute onto each `header` 720 | 721 | 722 | this._insertDataAttrs(); 723 | } 724 | /** 725 | * SET PANEL HEIGHT - ** DEPRICATED ** 726 | * 727 | * Depreicated as this method is becoming public and 728 | * I want to name it something that lets devs know 729 | * it's not just for using inside the `init()` method. 730 | */ 731 | 732 | }, { 733 | key: "_setPanelHeight", 734 | value: function _setPanelHeight() { 735 | this.calculateAllPanelsHeight(); 736 | } 737 | /** 738 | * CALCULATE PANEL HEIGHT 739 | * 740 | * Setting height for panels using pannels inner element 741 | */ 742 | 743 | }, { 744 | key: "calculatePanelHeight", 745 | value: function calculatePanelHeight(panel) { 746 | var panelInner = panel.querySelector(this.settings.panelInnerClass); 747 | var activeHeight = panelInner.offsetHeight; 748 | return panel.style.maxHeight = "".concat(activeHeight, "px"); 749 | } 750 | /** 751 | * CALCULATE PANEL HEIGHT 752 | * 753 | * Setting height for panels using pannels inner element 754 | */ 755 | 756 | }, { 757 | key: "calculateAllPanelsHeight", 758 | value: function calculateAllPanelsHeight() { 759 | var _this11 = this; 760 | 761 | this.panels.forEach(function (panel) { 762 | _this11.calculatePanelHeight(panel); 763 | }); 764 | } 765 | /** 766 | * SET UP HEADERS 767 | */ 768 | 769 | }, { 770 | key: "_setupHeaders", 771 | value: function _setupHeaders() { 772 | var _this12 = this; 773 | 774 | this.headers.forEach(function (header, index) { 775 | header.setAttribute('id', "badger-accordion-header-".concat(_this12.ids[index].id)); 776 | header.setAttribute('aria-controls', "badger-accordion-panel-".concat(_this12.ids[index].id)); 777 | }); 778 | } 779 | /** 780 | * SET UP PANELS 781 | */ 782 | 783 | }, { 784 | key: "_setupPanels", 785 | value: function _setupPanels() { 786 | var _this13 = this; 787 | 788 | this.panels.forEach(function (panel, index) { 789 | panel.setAttribute('id', "badger-accordion-panel-".concat(_this13.ids[index].id)); 790 | panel.setAttribute('aria-labelledby', "badger-accordion-header-".concat(_this13.ids[index].id)); 791 | 792 | if (_this13.settings.roles === true || _this13.settings.roles.region !== false) { 793 | _this13._setRole('region', panel); 794 | } 795 | }); 796 | } 797 | }]); 798 | 799 | return BadgerAccordion; 800 | }(); // Export 801 | 802 | export default BadgerAccordion; 803 | //# sourceMappingURL=badger-accordion.esm.js.map 804 | -------------------------------------------------------------------------------- /dist/badger-accordion.js: -------------------------------------------------------------------------------- 1 | (function (global, factory) { 2 | typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : 3 | typeof define === 'function' && define.amd ? define(factory) : 4 | (global.BadgerAccordion = factory()); 5 | }(this, (function () { 'use strict'; 6 | 7 | function _classCallCheck(instance, Constructor) { 8 | if (!(instance instanceof Constructor)) { 9 | throw new TypeError("Cannot call a class as a function"); 10 | } 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, descriptor.key, descriptor); 20 | } 21 | } 22 | 23 | function _createClass(Constructor, protoProps, staticProps) { 24 | if (protoProps) _defineProperties(Constructor.prototype, protoProps); 25 | if (staticProps) _defineProperties(Constructor, staticProps); 26 | return Constructor; 27 | } 28 | 29 | function _extends() { 30 | _extends = Object.assign || function (target) { 31 | for (var i = 1; i < arguments.length; i++) { 32 | var source = arguments[i]; 33 | 34 | for (var key in source) { 35 | if (Object.prototype.hasOwnProperty.call(source, key)) { 36 | target[key] = source[key]; 37 | } 38 | } 39 | } 40 | 41 | return target; 42 | }; 43 | 44 | return _extends.apply(this, arguments); 45 | } 46 | 47 | function _toConsumableArray(arr) { 48 | return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _nonIterableSpread(); 49 | } 50 | 51 | function _arrayWithoutHoles(arr) { 52 | if (Array.isArray(arr)) { 53 | for (var i = 0, arr2 = new Array(arr.length); i < arr.length; i++) arr2[i] = arr[i]; 54 | 55 | return arr2; 56 | } 57 | } 58 | 59 | function _iterableToArray(iter) { 60 | if (Symbol.iterator in Object(iter) || Object.prototype.toString.call(iter) === "[object Arguments]") return Array.from(iter); 61 | } 62 | 63 | function _nonIterableSpread() { 64 | throw new TypeError("Invalid attempt to spread non-iterable instance"); 65 | } 66 | 67 | if (!Array.from) { 68 | Array.from = function () { 69 | var toStr = Object.prototype.toString; 70 | 71 | var isCallable = function isCallable(fn) { 72 | return typeof fn === 'function' || toStr.call(fn) === '[object Function]'; 73 | }; 74 | 75 | var toInteger = function toInteger(value) { 76 | var number = Number(value); 77 | 78 | if (isNaN(number)) { 79 | return 0; 80 | } 81 | 82 | if (number === 0 || !isFinite(number)) { 83 | return number; 84 | } 85 | 86 | return (number > 0 ? 1 : -1) * Math.floor(Math.abs(number)); 87 | }; 88 | 89 | var maxSafeInteger = Math.pow(2, 53) - 1; 90 | 91 | var toLength = function toLength(value) { 92 | var len = toInteger(value); 93 | return Math.min(Math.max(len, 0), maxSafeInteger); 94 | }; // The length property of the from method is 1. 95 | 96 | 97 | return function from(arrayLike 98 | /* , mapFn, thisArg */ 99 | ) { 100 | // 1. Let C be the this value. 101 | var C = this; // 2. Let items be ToObject(arrayLike). 102 | 103 | var items = Object(arrayLike); // 3. ReturnIfAbrupt(items). 104 | 105 | if (arrayLike == null) { 106 | throw new TypeError('Array.from requires an array-like object - not null or undefined'); 107 | } // 4. If mapfn is undefined, then let mapping be false. 108 | 109 | 110 | var mapFn = arguments.length > 1 ? arguments[1] : void undefined; 111 | var T; 112 | 113 | if (typeof mapFn !== 'undefined') { 114 | // 5. else 115 | // 5. a If IsCallable(mapfn) is false, throw a TypeError exception. 116 | if (!isCallable(mapFn)) { 117 | throw new TypeError('Array.from: when provided, the second argument must be a function'); 118 | } // 5. b. If thisArg was supplied, let T be thisArg; else let T be undefined. 119 | 120 | 121 | if (arguments.length > 2) { 122 | T = arguments[2]; 123 | } 124 | } // 10. Let lenValue be Get(items, "length"). 125 | // 11. Let len be ToLength(lenValue). 126 | 127 | 128 | var len = toLength(items.length); // 13. If IsConstructor(C) is true, then 129 | // 13. a. Let A be the result of calling the [[Construct]] internal method 130 | // of C with an argument list containing the single item len. 131 | // 14. a. Else, Let A be ArrayCreate(len). 132 | 133 | var A = isCallable(C) ? Object(new C(len)) : new Array(len); // 16. Let k be 0. 134 | 135 | var k = 0; // 17. Repeat, while k < len… (also steps a - h) 136 | 137 | var kValue; 138 | 139 | while (k < len) { 140 | kValue = items[k]; 141 | 142 | if (mapFn) { 143 | A[k] = typeof T === 'undefined' ? mapFn(kValue, k) : mapFn.call(T, kValue, k); 144 | } else { 145 | A[k] = kValue; 146 | } 147 | 148 | k += 1; 149 | } // 18. Let putStatus be Put(A, "length", len, true). 150 | 151 | 152 | A.length = len; // 20. Return A. 153 | 154 | return A; 155 | }; 156 | }(); 157 | } 158 | 159 | /* 160 | By Osvaldas Valutis, www.osvaldas.info 161 | Available for use under the MIT License 162 | */ 163 | 164 | /* eslint-disable no-unused-vars */ 165 | (function (document, window) { 166 | var el = document.body || document.documentElement, 167 | s = el.style, 168 | prefixAnimation = '', 169 | prefixTransition = ''; 170 | if (s.WebkitAnimation == '') prefixAnimation = '-webkit-'; 171 | if (s.MozAnimation == '') prefixAnimation = '-moz-'; 172 | if (s.OAnimation == '') prefixAnimation = '-o-'; 173 | if (s.WebkitTransition == '') prefixTransition = '-webkit-'; 174 | if (s.MozTransition == '') prefixTransition = '-moz-'; 175 | if (s.OTransition == '') prefixTransition = '-o-'; 176 | Object.defineProperty(Object.prototype, 'onCSSAnimationEnd', { 177 | value: function value(callback) { 178 | var runOnce = function runOnce(e) { 179 | callback(); 180 | e.target.removeEventListener(e.type, runOnce); 181 | }; 182 | 183 | this.addEventListener('webkitAnimationEnd', runOnce); 184 | this.addEventListener('mozAnimationEnd', runOnce); 185 | this.addEventListener('oAnimationEnd', runOnce); 186 | this.addEventListener('oanimationend', runOnce); 187 | this.addEventListener('animationend', runOnce); 188 | if (prefixAnimation == '' && !('animation' in s) || getComputedStyle(this)[prefixAnimation + 'animation-duration'] == '0s') callback(); 189 | return this; 190 | }, 191 | enumerable: false, 192 | writable: true 193 | }); 194 | Object.defineProperty(Object.prototype, 'onCSSTransitionEnd', { 195 | value: function value(callback) { 196 | var runOnce = function runOnce(e) { 197 | callback(); 198 | e.target.removeEventListener(e.type, runOnce); 199 | }; 200 | 201 | this.addEventListener('webkitTransitionEnd', runOnce); 202 | this.addEventListener('mozTransitionEnd', runOnce); 203 | this.addEventListener('oTransitionEnd', runOnce); 204 | this.addEventListener('transitionend', runOnce); 205 | this.addEventListener('transitionend', runOnce); 206 | if (prefixTransition == '' && !('transition' in s) || getComputedStyle(this)[prefixTransition + 'transition-duration'] == '0s') callback(); 207 | return this; 208 | }, 209 | enumerable: false, 210 | writable: true 211 | }); 212 | })(document, window, 0); 213 | 214 | /** 215 | * ACCORDION 216 | * 217 | * A lightwight vanilla JS accordion with an exstensible API 218 | */ 219 | // import uuid from 'uuid/v4'; 220 | // const uuidV4 = uuid; 221 | 222 | /* eslint-disable no-unused-vars */ 223 | /** 224 | * CONSTRUCTOR 225 | * Initializes the object 226 | */ 227 | 228 | var BadgerAccordion = 229 | /*#__PURE__*/ 230 | function () { 231 | function BadgerAccordion(el, options) { 232 | var _this2 = this; 233 | 234 | _classCallCheck(this, BadgerAccordion); 235 | 236 | var container = typeof el === 'string' ? document.querySelector(el) : el; // If el is not defined 237 | 238 | if (container == null) { 239 | return; 240 | } 241 | 242 | var defaults = { 243 | headerClass: '.js-badger-accordion-header', 244 | panelClass: '.js-badger-accordion-panel', 245 | panelInnerClass: '.js-badger-accordion-panel-inner', 246 | hiddenClass: '-ba-is-hidden', 247 | activeClass: '-ba-is-active', 248 | 249 | get hidenClass() { 250 | return this.hiddenClass; 251 | }, 252 | 253 | initializedClass: 'badger-accordion--initialized', 254 | 255 | get initalisedClass() { 256 | return this.initializedClass; 257 | }, 258 | 259 | headerDataAttr: 'data-badger-accordion-header-id', 260 | openMultiplePanels: false, 261 | openHeadersOnLoad: [], 262 | addListenersOnInit: true, 263 | headerOpenLabel: '', 264 | headerCloseLabel: '', 265 | roles: true // toggleEl: // If you want to use a different element to trigger the accordion 266 | 267 | }; // Options 268 | 269 | this.settings = _extends({}, defaults, options); // Setting getting elements 270 | 271 | this.container = container; // Selecting children of the current accordion instance 272 | 273 | var children = Array.from(this.container.children); // Since the Accordions header button is nested inside an element with class 274 | // of `badger-accordion__header` it is a grandchild of the accordion instance. 275 | // In order to have nested accordions we need each to only get all the button 276 | // elements for this instance. Here an array is created to show all the children 277 | // of the element `badger-accordion__header`. 278 | 279 | var headerParent = children.filter(function (header) { 280 | return !header.classList.contains(_this2.settings.panelClass.substr(1)); 281 | }); // Creating an array of all DOM nodes that are Accordion headers 282 | 283 | this.headers = headerParent.reduce(function (acc, header) { 284 | var _ref; 285 | 286 | // Gets all the elements that have the headerClass 287 | var a = Array.from(header.children).filter(function (child) { 288 | return child.classList.contains(_this2.settings.headerClass.substr(1)); 289 | }); // Merges the current `badger-accordion__header` accordion triggers 290 | // with all the others. 291 | 292 | acc = (_ref = []).concat.apply(_ref, _toConsumableArray(acc).concat([a])); 293 | return acc; 294 | }, []); // Creates an array of all panel elements for this instance of the accordion 295 | 296 | this.panels = children.filter(function (panel) { 297 | return panel.classList.contains(_this2.settings.panelClass.substr(1)); 298 | }); 299 | this.toggleEl = this.settings.toggleEl !== undefined ? Array.from(this.container.querySelectorAll(this.settings.toggleEl)) : this.headers; // This is for managing state of the accordion. It by default sets 300 | // all accordion panels to be closed 301 | 302 | this.states = [].map.call(this.headers, function () { 303 | return { 304 | state: 'closed' 305 | }; 306 | }); 307 | this.ids = [].map.call(this.headers, function () { 308 | return { 309 | id: Math.floor(Math.random() * 1000000 + 1) 310 | }; 311 | }); // This is to ensure that once an open/close event has been fired 312 | // another cannot start until the first event has finished. 313 | // @TODO - get this working... 314 | 315 | this.toggling = false; // Initiating the accordion 316 | 317 | if (this.container) { 318 | this.init(); 319 | } else { 320 | /* eslint-disable no-console */ 321 | console.log('Something is wrong with you markup...'); 322 | } 323 | } 324 | /** 325 | * INIT 326 | * 327 | * Initalises the accordion 328 | */ 329 | 330 | 331 | _createClass(BadgerAccordion, [{ 332 | key: "init", 333 | value: function init() { 334 | // Sets up ID, aria attrs & data-attrs 335 | this._setupAttributes(); // Setting up the inital view of the accordion 336 | 337 | 338 | this._initalState(); // Setting the height of each panel 339 | 340 | 341 | this.calculateAllPanelsHeight(); // Inserting data-attribute onto each `header` 342 | 343 | this._insertDataAttrs(); // Adding listeners to headers 344 | 345 | 346 | this._addListeners(); // Adds class to accordion for initalisation 347 | 348 | 349 | this._finishInitialization(); 350 | } 351 | /** 352 | * CHECK ROLES ETTING 353 | * @return {[boolean]} 354 | * Checks roles setting for all roles or a single role. 355 | * First checks if a `boolean` has been used to set all 356 | * roles to either true or false. If the setting is an 357 | * object it will only set the attribute where each 358 | * attribute has explicitly been set as true, eg; 359 | * ``` 360 | * roles: { 361 | * region: true 362 | * } 363 | * ``` 364 | */ 365 | 366 | }, { 367 | key: "_setRole", 368 | value: function _setRole(role, el) { 369 | if (typeof this.settings.roles === 'boolean' && this.settings.roles || this.settings.roles[role] !== undefined && this.settings.roles[role] !== false) { 370 | el.setAttribute('role', role); 371 | } 372 | } 373 | /** 374 | * INSERT DATA ATTRS 375 | * 376 | * Updates state object for inital loading of the accordion 377 | */ 378 | 379 | }, { 380 | key: "_initalState", 381 | value: function _initalState() { 382 | // Sets state object as per `this.settings.openHeadersOnLoad` 383 | var headersToOpen = this.settings.openHeadersOnLoad; 384 | 385 | if (headersToOpen.length) { 386 | this._openHeadersOnLoad(headersToOpen); 387 | } // Render DOM as per the updates `this.states` object 388 | 389 | 390 | this._renderDom(); 391 | } 392 | /** 393 | * INSERT DATA ATTRS 394 | * 395 | * Adds `headerDataAttr` to all headers 396 | */ 397 | 398 | }, { 399 | key: "_insertDataAttrs", 400 | value: function _insertDataAttrs() { 401 | var _this3 = this; 402 | 403 | this.headers.forEach(function (header, index) { 404 | header.setAttribute(_this3.settings.headerDataAttr, index); 405 | }); 406 | } 407 | /** 408 | * FINISH INITALISATION 409 | * 410 | * Adds in `initializedClass` to accordion 411 | */ 412 | 413 | }, { 414 | key: "_finishInitialization", 415 | value: function _finishInitialization() { 416 | this.container.classList.add(this.settings.initializedClass); 417 | 418 | this._setRole('presentation', this.container); 419 | } 420 | /** 421 | * ADD LISTENERS 422 | * 423 | * Adds click event to each header 424 | */ 425 | 426 | }, { 427 | key: "_addListeners", 428 | value: function _addListeners() { 429 | if (!this.settings.addListenersOnInit) return; // So we can reference the badger-accordion object inside out eventListener 430 | 431 | var _this = this; // Adding click event to accordion 432 | 433 | 434 | this.headers.forEach(function (header, index) { 435 | header.addEventListener('click', function () { 436 | // Getting the target of the click 437 | // const clickedEl = event.target; 438 | _this.handleClick(header, index); 439 | }); 440 | }); 441 | } 442 | /** 443 | * HANDLE CLICK 444 | * 445 | * Handles click and checks if click was on an header element 446 | * @param {object} targetHeader - The header node you want to open 447 | */ 448 | 449 | }, { 450 | key: "handleClick", 451 | value: function handleClick(targetHeader, headerIndex) { 452 | // Removing current `.` from `this.settings.headerClass` class so it can 453 | // be checked against the `targetHeader` classList 454 | var targetHeaderClass = this.settings.headerClass.substr(1); // Checking that the thing that was clicked on was the accordions header 455 | 456 | if (targetHeader.classList.contains(targetHeaderClass) && this.toggling === false) { 457 | this.toggling = true; // Updating states 458 | 459 | this.setState(headerIndex); // Render DOM as per the updates `this.states` object 460 | 461 | this._renderDom(); 462 | } 463 | } 464 | /** 465 | * SET STATES 466 | * 467 | * Sets the state for all headers. The 'target header' will have its state toggeled 468 | * @param {object} targetHeaderId - The header node you want to open 469 | */ 470 | 471 | }, { 472 | key: "setState", 473 | value: function setState(targetHeaderId) { 474 | var _this4 = this; 475 | 476 | var states = this.getState(); // If `this.settings.openMultiplePanels` is false we need to ensure only one panel 477 | // be can open at once. If it is false then all panels state APART from the one that 478 | // has just been clicked needs to be set to 'closed'. 479 | 480 | if (!this.settings.openMultiplePanels) { 481 | states.filter(function (state, index) { 482 | if (index != targetHeaderId) { 483 | state.state = 'closed'; 484 | } 485 | }); 486 | } // Toggles the state value of the target header. This was `array.find` but `find` 487 | // isnt supported in IE11 488 | 489 | 490 | states.filter(function (state, index) { 491 | if (index == targetHeaderId) { 492 | var newState = _this4.toggleState(state.state); 493 | 494 | return state.state = newState; 495 | } 496 | }); 497 | } 498 | /** 499 | * RENDER DOM 500 | * 501 | * Renders the accordion in the DOM using the `this.states` object 502 | */ 503 | 504 | }, { 505 | key: "_renderDom", 506 | value: function _renderDom() { 507 | var _this5 = this; 508 | 509 | // Filter through all open headers and open them 510 | this.states.filter(function (state, index) { 511 | if (state.state === 'open') { 512 | // Opening the current panel but _NOT_ updating the state 513 | _this5.open(index, false); 514 | } 515 | }); // Filter through all closed headers and closes them 516 | 517 | this.states.filter(function (state, index) { 518 | if (state.state === 'closed') { 519 | // Closing the current panel but _NOT_ updating the state 520 | _this5.close(index, false); 521 | } 522 | }); 523 | } 524 | /** 525 | * OPEN 526 | * 527 | * Closes a specific panel 528 | * @param {integer} headerIndex - The header node index you want to open 529 | */ 530 | 531 | }, { 532 | key: "open", 533 | value: function open(headerIndex) { 534 | var setState = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true; 535 | 536 | // 1. If being fired directly the state needs to be updated. 537 | if (setState) { 538 | this.setState(headerIndex); 539 | } 540 | 541 | this.togglePanel('open', headerIndex); 542 | } 543 | /** 544 | * CLOSE 545 | * 546 | * Closes a specific panel 547 | * @param {integer} headerIndex - The header node index you want to close 548 | */ 549 | 550 | }, { 551 | key: "close", 552 | value: function close(headerIndex) { 553 | var setState = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true; 554 | 555 | // 1. If being fired directly the state needs to be updated. 556 | if (setState) { 557 | this.setState(headerIndex); 558 | } 559 | 560 | this.togglePanel('closed', headerIndex); 561 | } 562 | /** 563 | * OPEN ALL 564 | * 565 | * Opens all panels 566 | */ 567 | 568 | }, { 569 | key: "openAll", 570 | value: function openAll() { 571 | var _this6 = this; 572 | 573 | this.headers.forEach(function (header, headerIndex) { 574 | _this6.togglePanel('open', headerIndex); 575 | }); 576 | } 577 | /** 578 | * CLOSE ALL 579 | * 580 | * Closes all panels 581 | */ 582 | 583 | }, { 584 | key: "closeAll", 585 | value: function closeAll() { 586 | var _this7 = this; 587 | 588 | this.headers.forEach(function (header, headerIndex) { 589 | _this7.togglePanel('closed', headerIndex); 590 | }); 591 | } 592 | /** 593 | * GET STATE 594 | * 595 | * Getting state of headers. By default gets state of all headers 596 | * @param {string} animationAction - The animation you want to invoke 597 | * @param {integer} headerIndex - The header node index you want to animate 598 | */ 599 | 600 | }, { 601 | key: "togglePanel", 602 | value: function togglePanel(animationAction, headerIndex) { 603 | var _this8 = this; 604 | 605 | if (animationAction !== undefined && headerIndex !== undefined) { 606 | if (animationAction === 'closed') { 607 | // 1. Getting ID of panel that we want to close 608 | var header = this.headers[headerIndex]; 609 | var panelToClose = this.panels[headerIndex]; // 2. Closeing panel 610 | 611 | panelToClose.classList.add(this.settings.hiddenClass); // 3. Removing active classes 612 | 613 | panelToClose.classList.remove(this.settings.activeClass); 614 | header.classList.remove(this.settings.activeClass); // 4. Set aria attrs 615 | 616 | header.setAttribute('aria-expanded', false); // 5. Resetting toggling so a new event can be fired 617 | 618 | panelToClose.onCSSTransitionEnd(function () { 619 | return _this8.toggling = false; 620 | }); 621 | } else if (animationAction === 'open') { 622 | // 1. Getting ID of panel that we want to open 623 | var _header = this.headers[headerIndex]; 624 | var panelToOpen = this.panels[headerIndex]; // 2. Opening panel 625 | 626 | panelToOpen.classList.remove(this.settings.hiddenClass); // 3. Adding active classes 627 | 628 | panelToOpen.classList.add(this.settings.activeClass); 629 | 630 | _header.classList.add(this.settings.activeClass); // 4. Set aria attrs 631 | 632 | 633 | _header.setAttribute('aria-expanded', true); // 5. Resetting toggling so a new event can be fired 634 | 635 | 636 | panelToOpen.onCSSTransitionEnd(function () { 637 | return _this8.toggling = false; 638 | }); 639 | } 640 | } 641 | } // @TODO - is this needed anymore? 642 | // checkState(headerId) { 643 | // let state = this.states[headerId].state; 644 | // 645 | // if(state === 'closed') { 646 | // return state; 647 | // } else if(state === 'open') { 648 | // return state; 649 | // } 650 | // } 651 | 652 | /** 653 | * GET STATE 654 | * 655 | * Getting state of headers. By default gets state of all headers 656 | * @param {array} headerIds - Id/'s of the headers you want to check 657 | */ 658 | 659 | }, { 660 | key: "getState", 661 | value: function getState() { 662 | var _this9 = this; 663 | 664 | var headerIds = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : []; 665 | 666 | if (headerIds.length && Array.isArray(headerIds)) { 667 | var states = headerIds.map(function (header) { 668 | return _this9.states[header]; 669 | }); 670 | return states; 671 | } else { 672 | return this.states; 673 | } 674 | } 675 | /** 676 | * TOGGLE STATE 677 | * 678 | * Toggling the state value 679 | * @param {string} currentState - Current state value for a header 680 | */ 681 | 682 | }, { 683 | key: "toggleState", 684 | value: function toggleState(currentState) { 685 | if (currentState !== undefined) { 686 | return currentState === 'closed' ? 'open' : 'closed'; 687 | } 688 | } 689 | /** 690 | * HEADERS TO OPEN 691 | * 692 | * Setting which headers should be open when accordion is initalised 693 | * @param {array} headersToOpen - Array of ID's for the headers to be open 694 | */ 695 | 696 | }, { 697 | key: "_openHeadersOnLoad", 698 | value: function _openHeadersOnLoad() { 699 | var _this10 = this; 700 | 701 | var headersToOpen = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : []; 702 | 703 | if (headersToOpen.length && Array.isArray(headersToOpen)) { 704 | var headers = headersToOpen.filter(function (header) { 705 | return header != undefined; 706 | }); 707 | headers.forEach(function (header) { 708 | _this10.setState(header); 709 | }); 710 | } 711 | } 712 | /** 713 | * SET UP ATTRIBUTES 714 | * 715 | * Initalises accordion attribute methods 716 | */ 717 | 718 | }, { 719 | key: "_setupAttributes", 720 | value: function _setupAttributes() { 721 | // Adding ID & aria-controls 722 | this._setupHeaders(); // Adding ID & aria-labelledby 723 | 724 | 725 | this._setupPanels(); // Inserting data-attribute onto each `header` 726 | 727 | 728 | this._insertDataAttrs(); 729 | } 730 | /** 731 | * SET PANEL HEIGHT - ** DEPRICATED ** 732 | * 733 | * Depreicated as this method is becoming public and 734 | * I want to name it something that lets devs know 735 | * it's not just for using inside the `init()` method. 736 | */ 737 | 738 | }, { 739 | key: "_setPanelHeight", 740 | value: function _setPanelHeight() { 741 | this.calculateAllPanelsHeight(); 742 | } 743 | /** 744 | * CALCULATE PANEL HEIGHT 745 | * 746 | * Setting height for panels using pannels inner element 747 | */ 748 | 749 | }, { 750 | key: "calculatePanelHeight", 751 | value: function calculatePanelHeight(panel) { 752 | var panelInner = panel.querySelector(this.settings.panelInnerClass); 753 | var activeHeight = panelInner.offsetHeight; 754 | return panel.style.maxHeight = "".concat(activeHeight, "px"); 755 | } 756 | /** 757 | * CALCULATE PANEL HEIGHT 758 | * 759 | * Setting height for panels using pannels inner element 760 | */ 761 | 762 | }, { 763 | key: "calculateAllPanelsHeight", 764 | value: function calculateAllPanelsHeight() { 765 | var _this11 = this; 766 | 767 | this.panels.forEach(function (panel) { 768 | _this11.calculatePanelHeight(panel); 769 | }); 770 | } 771 | /** 772 | * SET UP HEADERS 773 | */ 774 | 775 | }, { 776 | key: "_setupHeaders", 777 | value: function _setupHeaders() { 778 | var _this12 = this; 779 | 780 | this.headers.forEach(function (header, index) { 781 | header.setAttribute('id', "badger-accordion-header-".concat(_this12.ids[index].id)); 782 | header.setAttribute('aria-controls', "badger-accordion-panel-".concat(_this12.ids[index].id)); 783 | }); 784 | } 785 | /** 786 | * SET UP PANELS 787 | */ 788 | 789 | }, { 790 | key: "_setupPanels", 791 | value: function _setupPanels() { 792 | var _this13 = this; 793 | 794 | this.panels.forEach(function (panel, index) { 795 | panel.setAttribute('id', "badger-accordion-panel-".concat(_this13.ids[index].id)); 796 | panel.setAttribute('aria-labelledby', "badger-accordion-header-".concat(_this13.ids[index].id)); 797 | 798 | if (_this13.settings.roles === true || _this13.settings.roles.region !== false) { 799 | _this13._setRole('region', panel); 800 | } 801 | }); 802 | } 803 | }]); 804 | 805 | return BadgerAccordion; 806 | }(); // Export 807 | 808 | return BadgerAccordion; 809 | 810 | }))); 811 | //# sourceMappingURL=badger-accordion.js.map 812 | -------------------------------------------------------------------------------- /example/js/app.js: -------------------------------------------------------------------------------- 1 | (function (global, factory) { 2 | typeof exports === 'object' && typeof module !== 'undefined' ? factory() : 3 | typeof define === 'function' && define.amd ? define(factory) : 4 | (factory()); 5 | }(this, (function () { 'use strict'; 6 | 7 | var commonjsGlobal = typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {}; 8 | 9 | 10 | 11 | 12 | 13 | function createCommonjsModule(fn, module) { 14 | return module = { exports: {} }, fn(module, module.exports), module.exports; 15 | } 16 | 17 | var badgerAccordion = createCommonjsModule(function (module, exports) { 18 | (function (global, factory) { 19 | module.exports = factory(); 20 | })(commonjsGlobal, function () { 21 | function _classCallCheck(instance, Constructor) { 22 | if (!(instance instanceof Constructor)) { 23 | throw new TypeError("Cannot call a class as a function"); 24 | } 25 | } 26 | 27 | function _defineProperties(target, props) { 28 | for (var i = 0; i < props.length; i++) { 29 | var descriptor = props[i]; 30 | descriptor.enumerable = descriptor.enumerable || false; 31 | descriptor.configurable = true; 32 | if ("value" in descriptor) descriptor.writable = true; 33 | Object.defineProperty(target, descriptor.key, descriptor); 34 | } 35 | } 36 | 37 | function _createClass(Constructor, protoProps, staticProps) { 38 | if (protoProps) _defineProperties(Constructor.prototype, protoProps); 39 | if (staticProps) _defineProperties(Constructor, staticProps); 40 | return Constructor; 41 | } 42 | 43 | function _extends() { 44 | _extends = Object.assign || function (target) { 45 | for (var i = 1; i < arguments.length; i++) { 46 | var source = arguments[i]; 47 | 48 | for (var key in source) { 49 | if (Object.prototype.hasOwnProperty.call(source, key)) { 50 | target[key] = source[key]; 51 | } 52 | } 53 | } 54 | 55 | return target; 56 | }; 57 | 58 | return _extends.apply(this, arguments); 59 | } 60 | 61 | function _toConsumableArray(arr) { 62 | return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _nonIterableSpread(); 63 | } 64 | 65 | function _arrayWithoutHoles(arr) { 66 | if (Array.isArray(arr)) { 67 | for (var i = 0, arr2 = new Array(arr.length); i < arr.length; i++) { 68 | arr2[i] = arr[i]; 69 | } 70 | 71 | return arr2; 72 | } 73 | } 74 | 75 | function _iterableToArray(iter) { 76 | if (Symbol.iterator in Object(iter) || Object.prototype.toString.call(iter) === "[object Arguments]") return Array.from(iter); 77 | } 78 | 79 | function _nonIterableSpread() { 80 | throw new TypeError("Invalid attempt to spread non-iterable instance"); 81 | } 82 | 83 | if (!Array.from) { 84 | Array.from = function () { 85 | var toStr = Object.prototype.toString; 86 | 87 | var isCallable = function isCallable(fn) { 88 | return typeof fn === 'function' || toStr.call(fn) === '[object Function]'; 89 | }; 90 | 91 | var toInteger = function toInteger(value) { 92 | var number = Number(value); 93 | 94 | if (isNaN(number)) { 95 | return 0; 96 | } 97 | 98 | if (number === 0 || !isFinite(number)) { 99 | return number; 100 | } 101 | 102 | return (number > 0 ? 1 : -1) * Math.floor(Math.abs(number)); 103 | }; 104 | 105 | var maxSafeInteger = Math.pow(2, 53) - 1; 106 | 107 | var toLength = function toLength(value) { 108 | var len = toInteger(value); 109 | return Math.min(Math.max(len, 0), maxSafeInteger); 110 | }; // The length property of the from method is 1. 111 | 112 | 113 | return function from(arrayLike 114 | /* , mapFn, thisArg */ 115 | ) { 116 | // 1. Let C be the this value. 117 | var C = this; // 2. Let items be ToObject(arrayLike). 118 | 119 | var items = Object(arrayLike); // 3. ReturnIfAbrupt(items). 120 | 121 | if (arrayLike == null) { 122 | throw new TypeError('Array.from requires an array-like object - not null or undefined'); 123 | } // 4. If mapfn is undefined, then let mapping be false. 124 | 125 | 126 | var mapFn = arguments.length > 1 ? arguments[1] : void undefined; 127 | var T; 128 | 129 | if (typeof mapFn !== 'undefined') { 130 | // 5. else 131 | // 5. a If IsCallable(mapfn) is false, throw a TypeError exception. 132 | if (!isCallable(mapFn)) { 133 | throw new TypeError('Array.from: when provided, the second argument must be a function'); 134 | } // 5. b. If thisArg was supplied, let T be thisArg; else let T be undefined. 135 | 136 | 137 | if (arguments.length > 2) { 138 | T = arguments[2]; 139 | } 140 | } // 10. Let lenValue be Get(items, "length"). 141 | // 11. Let len be ToLength(lenValue). 142 | 143 | 144 | var len = toLength(items.length); // 13. If IsConstructor(C) is true, then 145 | // 13. a. Let A be the result of calling the [[Construct]] internal method 146 | // of C with an argument list containing the single item len. 147 | // 14. a. Else, Let A be ArrayCreate(len). 148 | 149 | var A = isCallable(C) ? Object(new C(len)) : new Array(len); // 16. Let k be 0. 150 | 151 | var k = 0; // 17. Repeat, while k < len… (also steps a - h) 152 | 153 | var kValue; 154 | 155 | while (k < len) { 156 | kValue = items[k]; 157 | 158 | if (mapFn) { 159 | A[k] = typeof T === 'undefined' ? mapFn(kValue, k) : mapFn.call(T, kValue, k); 160 | } else { 161 | A[k] = kValue; 162 | } 163 | 164 | k += 1; 165 | } // 18. Let putStatus be Put(A, "length", len, true). 166 | 167 | 168 | A.length = len; // 20. Return A. 169 | 170 | return A; 171 | }; 172 | }(); 173 | } 174 | /* 175 | By Osvaldas Valutis, www.osvaldas.info 176 | Available for use under the MIT License 177 | */ 178 | 179 | /* eslint-disable no-unused-vars */ 180 | 181 | 182 | (function (document, window) { 183 | var el = document.body || document.documentElement, 184 | s = el.style, 185 | prefixAnimation = '', 186 | prefixTransition = ''; 187 | if (s.WebkitAnimation == '') prefixAnimation = '-webkit-'; 188 | if (s.MozAnimation == '') prefixAnimation = '-moz-'; 189 | if (s.OAnimation == '') prefixAnimation = '-o-'; 190 | if (s.WebkitTransition == '') prefixTransition = '-webkit-'; 191 | if (s.MozTransition == '') prefixTransition = '-moz-'; 192 | if (s.OTransition == '') prefixTransition = '-o-'; 193 | Object.defineProperty(Object.prototype, 'onCSSAnimationEnd', { 194 | value: function value(callback) { 195 | var runOnce = function runOnce(e) { 196 | callback(); 197 | e.target.removeEventListener(e.type, runOnce); 198 | }; 199 | 200 | this.addEventListener('webkitAnimationEnd', runOnce); 201 | this.addEventListener('mozAnimationEnd', runOnce); 202 | this.addEventListener('oAnimationEnd', runOnce); 203 | this.addEventListener('oanimationend', runOnce); 204 | this.addEventListener('animationend', runOnce); 205 | if (prefixAnimation == '' && !('animation' in s) || getComputedStyle(this)[prefixAnimation + 'animation-duration'] == '0s') callback(); 206 | return this; 207 | }, 208 | enumerable: false, 209 | writable: true 210 | }); 211 | Object.defineProperty(Object.prototype, 'onCSSTransitionEnd', { 212 | value: function value(callback) { 213 | var runOnce = function runOnce(e) { 214 | callback(); 215 | e.target.removeEventListener(e.type, runOnce); 216 | }; 217 | 218 | this.addEventListener('webkitTransitionEnd', runOnce); 219 | this.addEventListener('mozTransitionEnd', runOnce); 220 | this.addEventListener('oTransitionEnd', runOnce); 221 | this.addEventListener('transitionend', runOnce); 222 | this.addEventListener('transitionend', runOnce); 223 | if (prefixTransition == '' && !('transition' in s) || getComputedStyle(this)[prefixTransition + 'transition-duration'] == '0s') callback(); 224 | return this; 225 | }, 226 | enumerable: false, 227 | writable: true 228 | }); 229 | })(document, window, 0); 230 | /** 231 | * ACCORDION 232 | * 233 | * A lightwight vanilla JS accordion with an exstensible API 234 | */ 235 | // import uuid from 'uuid/v4'; 236 | // const uuidV4 = uuid; 237 | 238 | /* eslint-disable no-unused-vars */ 239 | 240 | /** 241 | * CONSTRUCTOR 242 | * Initializes the object 243 | */ 244 | 245 | 246 | var BadgerAccordion = 247 | /*#__PURE__*/ 248 | function () { 249 | function BadgerAccordion(el, options) { 250 | var _this2 = this; 251 | 252 | _classCallCheck(this, BadgerAccordion); 253 | 254 | var container = typeof el === 'string' ? document.querySelector(el) : el; // If el is not defined 255 | 256 | if (container == null) { 257 | return; 258 | } 259 | 260 | var defaults = { 261 | headerClass: '.js-badger-accordion-header', 262 | panelClass: '.js-badger-accordion-panel', 263 | panelInnerClass: '.js-badger-accordion-panel-inner', 264 | hiddenClass: '-ba-is-hidden', 265 | activeClass: '-ba-is-active', 266 | 267 | get hidenClass() { 268 | return this.hiddenClass; 269 | }, 270 | 271 | initializedClass: 'badger-accordion--initialized', 272 | 273 | get initalisedClass() { 274 | return this.initializedClass; 275 | }, 276 | 277 | headerDataAttr: 'data-badger-accordion-header-id', 278 | openMultiplePanels: false, 279 | openHeadersOnLoad: [], 280 | addListenersOnInit: true, 281 | headerOpenLabel: '', 282 | headerCloseLabel: '', 283 | roles: true // toggleEl: // If you want to use a different element to trigger the accordion 284 | 285 | }; // Options 286 | 287 | this.settings = _extends({}, defaults, options); // Setting getting elements 288 | 289 | this.container = container; // Selecting children of the current accordion instance 290 | 291 | var children = Array.from(this.container.children); // Since the Accordions header button is nested inside an element with class 292 | // of `badger-accordion__header` it is a grandchild of the accordion instance. 293 | // In order to have nested accordions we need each to only get all the button 294 | // elements for this instance. Here an array is created to show all the children 295 | // of the element `badger-accordion__header`. 296 | 297 | var headerParent = children.filter(function (header) { 298 | return !header.classList.contains(_this2.settings.panelClass.substr(1)); 299 | }); // Creating an array of all DOM nodes that are Accordion headers 300 | 301 | this.headers = headerParent.reduce(function (acc, header) { 302 | var _ref; // Gets all the elements that have the headerClass 303 | 304 | 305 | var a = Array.from(header.children).filter(function (child) { 306 | return child.classList.contains(_this2.settings.headerClass.substr(1)); 307 | }); // Merges the current `badger-accordion__header` accordion triggers 308 | // with all the others. 309 | 310 | acc = (_ref = []).concat.apply(_ref, _toConsumableArray(acc).concat([a])); 311 | return acc; 312 | }, []); // Creates an array of all panel elements for this instance of the accordion 313 | 314 | this.panels = children.filter(function (panel) { 315 | return panel.classList.contains(_this2.settings.panelClass.substr(1)); 316 | }); 317 | this.toggleEl = this.settings.toggleEl !== undefined ? Array.from(this.container.querySelectorAll(this.settings.toggleEl)) : this.headers; // This is for managing state of the accordion. It by default sets 318 | // all accordion panels to be closed 319 | 320 | this.states = [].map.call(this.headers, function () { 321 | return { 322 | state: 'closed' 323 | }; 324 | }); 325 | this.ids = [].map.call(this.headers, function () { 326 | return { 327 | id: Math.floor(Math.random() * 1000000 + 1) 328 | }; 329 | }); // This is to ensure that once an open/close event has been fired 330 | // another cannot start until the first event has finished. 331 | // @TODO - get this working... 332 | 333 | this.toggling = false; // Initiating the accordion 334 | 335 | if (this.container) { 336 | this.init(); 337 | } else { 338 | /* eslint-disable no-console */ 339 | console.log('Something is wrong with you markup...'); 340 | } 341 | } 342 | /** 343 | * INIT 344 | * 345 | * Initalises the accordion 346 | */ 347 | 348 | 349 | _createClass(BadgerAccordion, [{ 350 | key: "init", 351 | value: function init() { 352 | // Sets up ID, aria attrs & data-attrs 353 | this._setupAttributes(); // Setting up the inital view of the accordion 354 | 355 | 356 | this._initalState(); // Setting the height of each panel 357 | 358 | 359 | this.calculateAllPanelsHeight(); // Inserting data-attribute onto each `header` 360 | 361 | this._insertDataAttrs(); // Adding listeners to headers 362 | 363 | 364 | this._addListeners(); // Adds class to accordion for initalisation 365 | 366 | 367 | this._finishInitialization(); 368 | } 369 | /** 370 | * CHECK ROLES ETTING 371 | * @return {[boolean]} 372 | * Checks roles setting for all roles or a single role. 373 | * First checks if a `boolean` has been used to set all 374 | * roles to either true or false. If the setting is an 375 | * object it will only set the attribute where each 376 | * attribute has explicitly been set as true, eg; 377 | * ``` 378 | * roles: { 379 | * region: true 380 | * } 381 | * ``` 382 | */ 383 | 384 | }, { 385 | key: "_setRole", 386 | value: function _setRole(role, el) { 387 | if (typeof this.settings.roles === 'boolean' && this.settings.roles || this.settings.roles[role] !== undefined && this.settings.roles[role] !== false) { 388 | el.setAttribute('role', role); 389 | } 390 | } 391 | /** 392 | * INSERT DATA ATTRS 393 | * 394 | * Updates state object for inital loading of the accordion 395 | */ 396 | 397 | }, { 398 | key: "_initalState", 399 | value: function _initalState() { 400 | // Sets state object as per `this.settings.openHeadersOnLoad` 401 | var headersToOpen = this.settings.openHeadersOnLoad; 402 | 403 | if (headersToOpen.length) { 404 | this._openHeadersOnLoad(headersToOpen); 405 | } // Render DOM as per the updates `this.states` object 406 | 407 | 408 | this._renderDom(); 409 | } 410 | /** 411 | * INSERT DATA ATTRS 412 | * 413 | * Adds `headerDataAttr` to all headers 414 | */ 415 | 416 | }, { 417 | key: "_insertDataAttrs", 418 | value: function _insertDataAttrs() { 419 | var _this3 = this; 420 | 421 | this.headers.forEach(function (header, index) { 422 | header.setAttribute(_this3.settings.headerDataAttr, index); 423 | }); 424 | } 425 | /** 426 | * FINISH INITALISATION 427 | * 428 | * Adds in `initializedClass` to accordion 429 | */ 430 | 431 | }, { 432 | key: "_finishInitialization", 433 | value: function _finishInitialization() { 434 | this.container.classList.add(this.settings.initializedClass); 435 | 436 | this._setRole('presentation', this.container); 437 | } 438 | /** 439 | * ADD LISTENERS 440 | * 441 | * Adds click event to each header 442 | */ 443 | 444 | }, { 445 | key: "_addListeners", 446 | value: function _addListeners() { 447 | if (!this.settings.addListenersOnInit) return; // So we can reference the badger-accordion object inside out eventListener 448 | 449 | var _this = this; // Adding click event to accordion 450 | 451 | 452 | this.headers.forEach(function (header, index) { 453 | header.addEventListener('click', function () { 454 | // Getting the target of the click 455 | // const clickedEl = event.target; 456 | _this.handleClick(header, index); 457 | }); 458 | }); 459 | } 460 | /** 461 | * HANDLE CLICK 462 | * 463 | * Handles click and checks if click was on an header element 464 | * @param {object} targetHeader - The header node you want to open 465 | */ 466 | 467 | }, { 468 | key: "handleClick", 469 | value: function handleClick(targetHeader, headerIndex) { 470 | // Removing current `.` from `this.settings.headerClass` class so it can 471 | // be checked against the `targetHeader` classList 472 | var targetHeaderClass = this.settings.headerClass.substr(1); // Checking that the thing that was clicked on was the accordions header 473 | 474 | if (targetHeader.classList.contains(targetHeaderClass) && this.toggling === false) { 475 | this.toggling = true; // Updating states 476 | 477 | this.setState(headerIndex); // Render DOM as per the updates `this.states` object 478 | 479 | this._renderDom(); 480 | } 481 | } 482 | /** 483 | * SET STATES 484 | * 485 | * Sets the state for all headers. The 'target header' will have its state toggeled 486 | * @param {object} targetHeaderId - The header node you want to open 487 | */ 488 | 489 | }, { 490 | key: "setState", 491 | value: function setState(targetHeaderId) { 492 | var _this4 = this; 493 | 494 | var states = this.getState(); // If `this.settings.openMultiplePanels` is false we need to ensure only one panel 495 | // be can open at once. If it is false then all panels state APART from the one that 496 | // has just been clicked needs to be set to 'closed'. 497 | 498 | if (!this.settings.openMultiplePanels) { 499 | states.filter(function (state, index) { 500 | if (index != targetHeaderId) { 501 | state.state = 'closed'; 502 | } 503 | }); 504 | } // Toggles the state value of the target header. This was `array.find` but `find` 505 | // isnt supported in IE11 506 | 507 | 508 | states.filter(function (state, index) { 509 | if (index == targetHeaderId) { 510 | var newState = _this4.toggleState(state.state); 511 | 512 | return state.state = newState; 513 | } 514 | }); 515 | } 516 | /** 517 | * RENDER DOM 518 | * 519 | * Renders the accordion in the DOM using the `this.states` object 520 | */ 521 | 522 | }, { 523 | key: "_renderDom", 524 | value: function _renderDom() { 525 | var _this5 = this; // Filter through all open headers and open them 526 | 527 | 528 | this.states.filter(function (state, index) { 529 | if (state.state === 'open') { 530 | // Opening the current panel but _NOT_ updating the state 531 | _this5.open(index, false); 532 | } 533 | }); // Filter through all closed headers and closes them 534 | 535 | this.states.filter(function (state, index) { 536 | if (state.state === 'closed') { 537 | // Closing the current panel but _NOT_ updating the state 538 | _this5.close(index, false); 539 | } 540 | }); 541 | } 542 | /** 543 | * OPEN 544 | * 545 | * Closes a specific panel 546 | * @param {integer} headerIndex - The header node index you want to open 547 | */ 548 | 549 | }, { 550 | key: "open", 551 | value: function open(headerIndex) { 552 | var setState = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true; // 1. If being fired directly the state needs to be updated. 553 | 554 | if (setState) { 555 | this.setState(headerIndex); 556 | } 557 | 558 | this.togglePanel('open', headerIndex); 559 | } 560 | /** 561 | * CLOSE 562 | * 563 | * Closes a specific panel 564 | * @param {integer} headerIndex - The header node index you want to close 565 | */ 566 | 567 | }, { 568 | key: "close", 569 | value: function close(headerIndex) { 570 | var setState = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true; // 1. If being fired directly the state needs to be updated. 571 | 572 | if (setState) { 573 | this.setState(headerIndex); 574 | } 575 | 576 | this.togglePanel('closed', headerIndex); 577 | } 578 | /** 579 | * OPEN ALL 580 | * 581 | * Opens all panels 582 | */ 583 | 584 | }, { 585 | key: "openAll", 586 | value: function openAll() { 587 | var _this6 = this; 588 | 589 | this.headers.forEach(function (header, headerIndex) { 590 | _this6.togglePanel('open', headerIndex); 591 | }); 592 | } 593 | /** 594 | * CLOSE ALL 595 | * 596 | * Closes all panels 597 | */ 598 | 599 | }, { 600 | key: "closeAll", 601 | value: function closeAll() { 602 | var _this7 = this; 603 | 604 | this.headers.forEach(function (header, headerIndex) { 605 | _this7.togglePanel('closed', headerIndex); 606 | }); 607 | } 608 | /** 609 | * GET STATE 610 | * 611 | * Getting state of headers. By default gets state of all headers 612 | * @param {string} animationAction - The animation you want to invoke 613 | * @param {integer} headerIndex - The header node index you want to animate 614 | */ 615 | 616 | }, { 617 | key: "togglePanel", 618 | value: function togglePanel(animationAction, headerIndex) { 619 | var _this8 = this; 620 | 621 | if (animationAction !== undefined && headerIndex !== undefined) { 622 | if (animationAction === 'closed') { 623 | // 1. Getting ID of panel that we want to close 624 | var header = this.headers[headerIndex]; 625 | var panelToClose = this.panels[headerIndex]; // 2. Closeing panel 626 | 627 | panelToClose.classList.add(this.settings.hiddenClass); // 3. Removing active classes 628 | 629 | panelToClose.classList.remove(this.settings.activeClass); 630 | header.classList.remove(this.settings.activeClass); // 4. Set aria attrs 631 | 632 | header.setAttribute('aria-expanded', false); // 5. Resetting toggling so a new event can be fired 633 | 634 | panelToClose.onCSSTransitionEnd(function () { 635 | return _this8.toggling = false; 636 | }); 637 | } else if (animationAction === 'open') { 638 | // 1. Getting ID of panel that we want to open 639 | var _header = this.headers[headerIndex]; 640 | var panelToOpen = this.panels[headerIndex]; // 2. Opening panel 641 | 642 | panelToOpen.classList.remove(this.settings.hiddenClass); // 3. Adding active classes 643 | 644 | panelToOpen.classList.add(this.settings.activeClass); 645 | 646 | _header.classList.add(this.settings.activeClass); // 4. Set aria attrs 647 | 648 | 649 | _header.setAttribute('aria-expanded', true); // 5. Resetting toggling so a new event can be fired 650 | 651 | 652 | panelToOpen.onCSSTransitionEnd(function () { 653 | return _this8.toggling = false; 654 | }); 655 | } 656 | } 657 | } // @TODO - is this needed anymore? 658 | // checkState(headerId) { 659 | // let state = this.states[headerId].state; 660 | // 661 | // if(state === 'closed') { 662 | // return state; 663 | // } else if(state === 'open') { 664 | // return state; 665 | // } 666 | // } 667 | 668 | /** 669 | * GET STATE 670 | * 671 | * Getting state of headers. By default gets state of all headers 672 | * @param {array} headerIds - Id/'s of the headers you want to check 673 | */ 674 | 675 | }, { 676 | key: "getState", 677 | value: function getState() { 678 | var _this9 = this; 679 | 680 | var headerIds = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : []; 681 | 682 | if (headerIds.length && Array.isArray(headerIds)) { 683 | var states = headerIds.map(function (header) { 684 | return _this9.states[header]; 685 | }); 686 | return states; 687 | } else { 688 | return this.states; 689 | } 690 | } 691 | /** 692 | * TOGGLE STATE 693 | * 694 | * Toggling the state value 695 | * @param {string} currentState - Current state value for a header 696 | */ 697 | 698 | }, { 699 | key: "toggleState", 700 | value: function toggleState(currentState) { 701 | if (currentState !== undefined) { 702 | return currentState === 'closed' ? 'open' : 'closed'; 703 | } 704 | } 705 | /** 706 | * HEADERS TO OPEN 707 | * 708 | * Setting which headers should be open when accordion is initalised 709 | * @param {array} headersToOpen - Array of ID's for the headers to be open 710 | */ 711 | 712 | }, { 713 | key: "_openHeadersOnLoad", 714 | value: function _openHeadersOnLoad() { 715 | var _this10 = this; 716 | 717 | var headersToOpen = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : []; 718 | 719 | if (headersToOpen.length && Array.isArray(headersToOpen)) { 720 | var headers = headersToOpen.filter(function (header) { 721 | return header != undefined; 722 | }); 723 | headers.forEach(function (header) { 724 | _this10.setState(header); 725 | }); 726 | } 727 | } 728 | /** 729 | * SET UP ATTRIBUTES 730 | * 731 | * Initalises accordion attribute methods 732 | */ 733 | 734 | }, { 735 | key: "_setupAttributes", 736 | value: function _setupAttributes() { 737 | // Adding ID & aria-controls 738 | this._setupHeaders(); // Adding ID & aria-labelledby 739 | 740 | 741 | this._setupPanels(); // Inserting data-attribute onto each `header` 742 | 743 | 744 | this._insertDataAttrs(); 745 | } 746 | /** 747 | * SET PANEL HEIGHT - ** DEPRICATED ** 748 | * 749 | * Depreicated as this method is becoming public and 750 | * I want to name it something that lets devs know 751 | * it's not just for using inside the `init()` method. 752 | */ 753 | 754 | }, { 755 | key: "_setPanelHeight", 756 | value: function _setPanelHeight() { 757 | this.calculateAllPanelsHeight(); 758 | } 759 | /** 760 | * CALCULATE PANEL HEIGHT 761 | * 762 | * Setting height for panels using pannels inner element 763 | */ 764 | 765 | }, { 766 | key: "calculatePanelHeight", 767 | value: function calculatePanelHeight(panel) { 768 | var panelInner = panel.querySelector(this.settings.panelInnerClass); 769 | var activeHeight = panelInner.offsetHeight; 770 | return panel.style.maxHeight = "".concat(activeHeight, "px"); 771 | } 772 | /** 773 | * CALCULATE PANEL HEIGHT 774 | * 775 | * Setting height for panels using pannels inner element 776 | */ 777 | 778 | }, { 779 | key: "calculateAllPanelsHeight", 780 | value: function calculateAllPanelsHeight() { 781 | var _this11 = this; 782 | 783 | this.panels.forEach(function (panel) { 784 | _this11.calculatePanelHeight(panel); 785 | }); 786 | } 787 | /** 788 | * SET UP HEADERS 789 | */ 790 | 791 | }, { 792 | key: "_setupHeaders", 793 | value: function _setupHeaders() { 794 | var _this12 = this; 795 | 796 | this.headers.forEach(function (header, index) { 797 | header.setAttribute('id', "badger-accordion-header-".concat(_this12.ids[index].id)); 798 | header.setAttribute('aria-controls', "badger-accordion-panel-".concat(_this12.ids[index].id)); 799 | }); 800 | } 801 | /** 802 | * SET UP PANELS 803 | */ 804 | 805 | }, { 806 | key: "_setupPanels", 807 | value: function _setupPanels() { 808 | var _this13 = this; 809 | 810 | this.panels.forEach(function (panel, index) { 811 | panel.setAttribute('id', "badger-accordion-panel-".concat(_this13.ids[index].id)); 812 | panel.setAttribute('aria-labelledby', "badger-accordion-header-".concat(_this13.ids[index].id)); 813 | 814 | if (_this13.settings.roles === true || _this13.settings.roles.region !== false) { 815 | _this13._setRole('region', panel); 816 | } 817 | }); 818 | } 819 | }]); 820 | 821 | return BadgerAccordion; 822 | }(); // Export 823 | 824 | 825 | return BadgerAccordion; 826 | }); 827 | }); 828 | 829 | // ================================ 830 | // const accordionDomNode = document.querySelector('.js-badger-accordion'); 831 | // const accordion = new BadgerAccordion(accordionDomNode); 832 | 833 | /* eslint-disable no-console */ 834 | // console.log(accordion.getState([0])); 835 | // accordion.open(0); // Opens the first accordion panel 836 | // Creating a new instance of the accordion usign DOM node 837 | // ================================ 838 | 839 | var accordions = document.querySelectorAll('.js-badger-accordion'); 840 | Array.from(accordions).forEach(function (accordion) { 841 | var ba = new badgerAccordion(accordion); 842 | /* eslint-disable no-console */ 843 | 844 | console.log(ba.getState([0])); 845 | }); // Creating a new instance of the accordion usign CSS selector 846 | // ================================ 847 | // const accordionCssSelector = new BadgerAccordion('.js-badger-accordion'); 848 | // API Examples 849 | 850 | /* eslint-disable no-console */ 851 | // console.log(accordionCssSelector.getState([0])); 852 | // accordionCssSelector.open( 0 ); 853 | 854 | }))); 855 | //# sourceMappingURL=app.js.map 856 | --------------------------------------------------------------------------------