1/* 2 * jQuery Accessible Accordion system, using ARIA 3 * @version v2.6.1 4 * Website: https://a11y.nicolas-hoffmann.net/accordion/ 5 * License MIT: https://github.com/nico3333fr/jquery-accessible-accordion-aria/blob/master/LICENSE 6 */ 7(function(factory) { 8 'use strict'; 9 if (typeof define === 'function' && define.amd) { 10 define(['jquery'], factory); 11 } else if (typeof exports !== 'undefined') { 12 module.exports = factory(require('jquery')); 13 } else { 14 factory(window.jQuery); 15 } 16}(function($) { 17 'use strict'; 18 19 var defaultConfig = { 20 headersSelector: '.js-accordion__header', 21 panelsSelector: '.js-accordion__panel', 22 buttonsSelector: 'button.js-accordion__header', 23 buttonsGeneratedContent: 'text', 24 button: $('<button></button>', { 25 class: 'js-accordion__header', 26 type: 'button' 27 }), 28 buttonSuffixId: '_tab', 29 multiselectable: true, 30 prefixClass: 'accordion', 31 headerSuffixClass: '__title', 32 buttonSuffixClass: '__header', 33 panelSuffixClass: '__panel', 34 direction: 'ltr', 35 accordionPrefixId: 'accordion' 36 }; 37 38 var Accordion = function($el, options) { 39 this.options = $.extend({}, defaultConfig, options); 40 41 this.$wrapper = $el; 42 this.$panels = $(this.options.panelsSelector, this.$wrapper); 43 44 this.initAttributes(); 45 this.initEvents(); 46 }; 47 48 Accordion.prototype.initAttributes = function() { 49 this.$wrapper.attr({ 50 'role': 'tablist', 51 'aria-multiselectable': this.options.multiselectable.toString() 52 }).addClass(this.options.prefixClass); 53 54 // id generated if not present 55 if (!this.$wrapper.attr('id')) { 56 var index_lisible = Math.random().toString(32).slice(2, 12); 57 this.$wrapper.attr('id', this.options.accordionPrefixId + '-' + index_lisible); 58 } 59 60 this.$panels.each($.proxy(function(index, el) { 61 var $panel = $(el); 62 var $header = $(this.options.headersSelector, $panel); 63 var $button = this.options.buttonsGeneratedContent === 'html' ? this.options.button.clone().html($header.html()) : this.options.button.clone().text($header.text()); 64 65 $header.addClass(this.options.prefixClass + this.options.headerSuffixClass); 66 $panel.before($button); 67 68 var panelId = $panel.attr('id') || this.$wrapper.attr('id') + '-' + index; 69 var buttonId = panelId + this.options.buttonSuffixId; 70 71 $button.attr({ 72 'aria-controls': panelId, 73 'aria-expanded': 'false', 74 'role': 'tab', 75 'id': buttonId, 76 'aria-selected': 'false' 77 }).addClass(this.options.prefixClass + this.options.buttonSuffixClass); 78 79 $panel.attr({ 80 'aria-labelledby': buttonId, 81 'role': 'tabpanel', 82 'id': panelId, 83 'aria-hidden': 'true' 84 }).addClass(this.options.prefixClass + this.options.panelSuffixClass); 85 86 // if opened by default 87 if ($panel.attr('data-accordion-opened') === 'true') { 88 $button.attr({ 89 'aria-expanded': 'true', 90 'data-accordion-opened': null 91 }); 92 93 $panel.attr({ 94 'aria-hidden': 'false' 95 }); 96 } 97 98 99 }, this)); 100 101 this.$buttons = $(this.options.buttonsSelector, this.$wrapper); 102 }; 103 104 Accordion.prototype.initEvents = function() { 105 this.$wrapper.on('focus', this.options.buttonsSelector, $.proxy(this.focusButtonEventHandler, this)); 106 107 this.$wrapper.on('click', this.options.buttonsSelector, $.proxy(this.clickButtonEventHandler, this)); 108 109 this.$wrapper.on('keydown', this.options.buttonsSelector, $.proxy(this.keydownButtonEventHandler, this)); 110 }; 111 112 Accordion.prototype.focusButtonEventHandler = function(e) { 113 var $target = $(e.target); 114 var $button = $target.is('button') ? $target : $target.closest('button'); 115 116 $(this.options.buttonsSelector, this.$wrapper).attr({ 117 'aria-selected': 'false' 118 }); 119 120 $button.attr({ 121 'aria-selected': 'true' 122 }); 123 }; 124 125 Accordion.prototype.clickButtonEventHandler = function(e) { 126 var $target = $(e.target); 127 var $button = $target.is('button') ? $target : $target.closest('button'); 128 var $panel = $('#' + $button.attr('aria-controls')); 129 130 this.$buttons.attr('aria-selected', 'false'); 131 $button.attr('aria-selected', 'true'); 132 133 // opened or closed? 134 if ($button.attr('aria-expanded') === 'false') { // closed 135 $button.attr('aria-expanded', 'true'); 136 $panel.attr('aria-hidden', 'false'); 137 } else { // opened 138 $button.attr('aria-expanded', 'false'); 139 $panel.attr('aria-hidden', 'true'); 140 } 141 142 if (this.options.multiselectable === false) { 143 this.$panels.not($panel).attr('aria-hidden', 'true'); 144 this.$buttons.not($button).attr('aria-expanded', 'false'); 145 } 146 147 setTimeout(function() { 148 $button.focus(); 149 }, 0); 150 151 e.stopPropagation(); 152 e.preventDefault(); 153 }; 154 155 Accordion.prototype.keydownButtonEventHandler = function(e) { 156 var $target = $(e.target); 157 var $button = $target.is('button') ? $target : $target.closest('button'); 158 var $firstButton = this.$buttons.first(); 159 var $lastButton = this.$buttons.last(); 160 var index = this.$buttons.index($button); 161 162 $target = null; 163 164 var k = this.options.direction === 'ltr' ? { 165 prev: [38, 37], // up & left 166 next: [40, 39], // down & right 167 first: 36, // home 168 last: 35 // end 169 } : { 170 prev: [38, 39], // up & left 171 next: [40, 37], // down & right 172 first: 36, // home 173 last: 35 // end 174 }; 175 176 var allKeyCode = [].concat(k.prev, k.next, k.first, k.last); 177 178 if ($.inArray(e.keyCode, allKeyCode) >= 0 && !e.ctrlKey) { 179 this.$buttons.attr({ 180 'aria-selected': 'false' 181 }); 182 183 184 if (e.keyCode === 36) { 185 $target = $firstButton; 186 } 187 // strike end in the tab => last tab 188 else if (e.keyCode === 35) { 189 $target = $lastButton; 190 } 191 // strike up or left in the tab => previous tab 192 else if ($.inArray(e.keyCode, k.prev) >= 0) { 193 // if we are on first one, activate last 194 $target = $button.is($firstButton) ? $lastButton : this.$buttons.eq(index - 1); 195 } 196 // strike down or right in the tab => next tab 197 else if ($.inArray(e.keyCode, k.next) >= 0) { 198 // if we are on last one, activate first 199 $target = $button.is($lastButton) ? $firstButton : this.$buttons.eq(index + 1); 200 } 201 202 if ($target !== null) { 203 this.goToHeader($target); 204 } 205 206 e.preventDefault(); 207 } 208 }; 209 210 Accordion.prototype.goToHeader = function($target) { 211 if ($target.length !== 1) { 212 return; 213 } 214 215 $target.attr({ 216 'aria-selected': 'true' 217 }); 218 219 setTimeout(function() {
220 $target.focus(); 221 }, 0); 222 }; 223 224 225 var PLUGIN = 'accordion'; 226 227 $.fn[PLUGIN] = function(params) { 228 var options = $.extend({}, $.fn[PLUGIN].defaults, params); 229 230 231 return this.each(function() { 232 var $el = $(this); 233 234 var specificOptions = { 235 multiselectable: $el.attr('data-accordion-multiselectable') === 'none' ? false : options.multiselectable, 236 prefixClass: typeof($el.attr('data-accordion-prefix-classes')) !== 'undefined' ? $el.attr('data-accordion-prefix-classes') : options.prefixClass, 237 buttonsGeneratedContent: typeof($el.attr('data-accordion-button-generated-content')) !== 'undefined' ? $el.attr('data-accordion-button-generated-content') : options.buttonsGeneratedContent, 238 direction: $el.closest('[dir="rtl"]').length > 0 ? 'rtl' : options.direction 239 }; 240 specificOptions = $.extend({}, options, specificOptions); 241 242 $el.data[PLUGIN] = new Accordion($el, specificOptions); 243 }); 244 }; 245 246 $.fn[PLUGIN].defaults = defaultConfig; 247 248}));
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.