1(function($) { 2 'use strict'; 3 4 const dataNS = 'pds-tabs--'; 5 /** 6 * Accordion plugin function 7 * 8 * @param object options Plugin settings to override the defaults 9 */ 10 $.fn.pdsAccordionItem = function(options) { 11 const defaultAccordionIcon = '<span class="pds-accordion__icon" role="presentation" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 12 7.4099998" ><path d="M12 1.41 10.59 0 6 4.58 1.41 0 0 1.41l6 6z" fill="currentColor"></path></svg></span>'; 12 const iconClass = 'pds-accordion__icon'; 13 14 var settings = $.extend({ 15 // Set default settings 16 initiallyOpen: false, 17 autoClose: true, 18 clickToClose: true, 19 scroll: false, 20 scrollOffset: false, 21 icon: true, /* 22 true: default icon. 23 false: hide the icon 24 '<svg>...</svg>': custom markup for a single-state icon. 180 degree rotation will be applied when the state of the accordion item is open. 25 {closed: '<b>+</b>', open:'<b>-</b>'}: custom markup for a two-state icon 26 */ 27 iconSize: '', 28 }, options); 29 30 var duration = 250; 31 var hashID = window.location.hash.replace('#', ''); 32 33 var item = {}; 34 35 item.self = $(this); 36 item.id = $(this).attr('id'); 37 item.controller = $(this).find('.pds-js-accordion-controller'); 38 item.uuid = getAccordionItemUUID(item.self); 39 item.content = $('#ac-' + item.uuid); 40 item.accordionGroupItems = [item.uuid]; 41 item.accordionAncestorItems = []; 42 initialSetup(); 43 44 45 46 /** 47 * Initial setup 48 * Set the scroll offset, and figure out which items should be open by 49 * default. 50 */ 51 function initialSetup() { 52 // Remove the 'pds-no-js' class since JavaScript is enabled 53 item.self.removeClass('pds-no-js'); 54 /** 55 * Set up some defaults for this controller 56 */ 57 item.controller.attr({ 58 'tabindex': 0, 59 'aria-controls': 'ac-' + item.uuid, 60 'role': 'button' 61 }); 62 63 if(!item.controller.attr('id')) item.controller.id(item.uuid + '_acc_header'); 64 item.content.attr({ 65 'role':'region', 66 'aria-labelledby':item.controller.attr('id') 67 }); 68 69 settings.scrollOffset = Math.floor(parseInt(settings.scrollOffset, 10)) || 0; 70 71 /** 72 * Add any sibling accordion items to the accordionGroupItems array. 73 */ 74 $.each(item.self.siblings('.pds-js-accordion-item'), function(index, ele) { 75 var uuid = getAccordionItemUUID(ele); 76 77 item.accordionGroupItems.push(uuid); 78 }); 79 80 /** 81 * Add any parent accordion items to the accordionAncestorItems array. 82 */ 83 $.each(item.self.parents('.pds-js-accordion-item'), function(index, ele) { 84 var uuid = getAccordionItemUUID(ele); 85 86 item.accordionAncestorItems.push(uuid); 87 }); 88 89 // If this item has `initially-open prop` set to true, open it 90 if (settings.initiallyOpen) { 91 /** 92 * We aren't opening the item here (only setting open attributes) 93 * because the openItem() function fires the `pds-openAccordionItem` 94 * event which, if `autoClose` is set, would override the users 95 * defined initiallyOpen settings. 96 * 97 * Only setting open attributes is fine since the item's content 98 * display (`display: none|block`) is already set by the plugin. 99 */ 100 setOpenItemAttributes(); 101 } 102 // If the hash matches this item, open it 103 else if (item.id === hashID) { 104 /** 105 * Unlike the `initiallyOpen` case above, if a hash is detected 106 * that matches one of the accordion items, we probably _want_ 107 * the other items to close so the user can focus on this item. 108 */ 109 openItem(); 110 111 // Open ancestors if necessary 112 $.each(item.accordionAncestorItems, function(index, uuid) { 113 $(document).trigger('pds-openAncestorAccordionItem', uuid); 114 }); 115 } 116 // Otherwise, close the item 117 else { 118 /** 119 * Don't use closeItem() function call since it animates the 120 * closing. Instead, we only need to set the closed attributes. 121 */ 122 setCloseItemAttributes(); 123 } 124 adjustIcon(); 125 } 126 127 function adjustIcon(){ 128 if(!settings.icon) { 129 item.controller.find('.' + iconClass).remove(); 130 return; 131 } 132 var $icon = item.controller.find('.' + iconClass); 133 if(!$icon.length) { 134 $icon = $(defaultAccordionIcon); 135 item.controller.append($icon); 136 } 137 $icon.css('width', settings.iconSize).css('height', settings.iconSize) 138 if(settings.icon !== true){ 139 if (typeof settings.icon === 'string') { 140 $icon.addClass(iconClass + '--custom').empty().append(settings.icon); 141 } 142 else if (typeof settings.icon === 'object' && settings.icon.open && settings.icon.closed){ 143 //must be a two-state icon definition 144 var $closed = $('<span/>').addClass(iconClass + '--closed').append(settings.icon.closed); 145 var $open = $('<span/>
145').addClass(iconClass + '--open').append(settings.icon.open); 146 $icon.addClass(iconClass + '--two-state').empty().append($closed).append($open) 147 } 148 } 149 } 150 151 152 /** 153 * Default click function 154 * Called when an accordion controller is clicked. 155 */ 156 function clickHandler() { 157 // Only open the item if item isn't already open 158 if (!item.self.hasClass('is-open')) { 159 // Open clicked item 160 openItem(); 161 } 162 // If item is open, and click to close is set, close it 163 else if (settings.clickToClose) { 164 closeItem(); 165 } 166 167 return false; 168 } 169 170 171 172 /** 173 * Get the accordion item UUID for a given accordion item DOM element. 174 */ 175 function getAccordionItemUUID(ele) { 176 return $(ele).find('.pds-js-accordion-controller').attr('id').replace('at-', ''); 177 } 178 179 180 181 /** 182 * Opens an accordion item 183 * Also handles accessibility attribute settings. 184 */ 185 function openItem(doNotPushState) { 186 setOpenItemAttributes(); 187 188 // Clear/stop any previous animations before revealing content 189 item.content.clearQueue().stop().slideDown(duration, function() { 190 // Scroll page to the title 191 if (settings.scroll) { 192 // Pause scrolling until other items have closed 193 setTimeout(function() { 194 $('html, body').animate({ 195 scrollTop: item.self.offset().top - settings.scrollOffset 196 }, duration); 197 }, duration); 198 } 199 }); 200 201 $(document).trigger('pds-openAccordionItem', item); 202 203 if(!doNotPushState) { 204 const newHash = '#' + item.id; 205 if(newHash !== window.location.hash) history.pushState(undefined, undefined, newHash); 206 } 207 } 208 209 210 211 /** 212 * Set open item attributes 213 * Mark accordion item as open and read and set aria attributes. 214 */ 215 function setOpenItemAttributes() { 216 item.self.addClass('is-open is-read'); 217 item.controller.attr('aria-expanded', true); 218 item.content.prop('hidden', false); 219 } 220 221 222 223 /** 224 * Closes an accordion item 225 * Also handles accessibility attribute settings. 226 */ 227 function closeItem() { 228 // Close the item 229 item.content.slideUp(duration, function() { 230 setCloseItemAttributes(); 231 }); 232 } 233 234 235 236 /** 237 * Set closed item attributes 238 * Mark accordion item as closed and set aria attributes. 239 */ 240 function setCloseItemAttributes() { 241 item.self.removeClass('is-open'); 242 item.controller.attr('aria-expanded', false); 243 item.content.attr('hidden', true); 244 } 245 246 247 248 /** 249 * Close all items if auto close is enabled 250 */ 251 function maybeCloseItem() { 252 if (settings.autoClose && item.self.hasClass('is-open')) { 253 closeItem(); 254 } 255 } 256 257 258 259 /** 260 * Add event listeners 261 */ 262 item.controller.on('click', clickHandler); 263 264 265 266 /** 267 * Listen for other accordion items opening 268 * 269 * The `pds-openAccordionItem` event is fired whenever an accordion item is 270 * opened after initial plugin setup. 271 */ 272 $(document).on('pds-openAccordionItem', function(event, ele) { 273 /** 274 * Only trigger potential close these conditions are met: 275 * 276 * 1. This isn't the item the user just clicked to open. 277 * 2. This accordion is in the same group of accordions as the one 278 * that was just clicked to open. 279 * 3. This accordion is not an ancestor of the item that was just 280 * clicked to open. 281 * 282 * This serves two purposes: 283 * 284 * 1. It allows nesting of accordions to work. 285 * 2. It allows users to group accordions to control independently 286 * of other groups of accordions. 287 * 3. It allows child accordions to be opened via hash change 288 * without automatically closing the parent accordion, therefore 289 * hiding the accordion the user just indicated they wanted open. 290 */ 291 if ( 292 ele !== item && 293 ele.accordionGroupItems.indexOf(item.uuid) > 0 && 294 ele.accordionAncestorItems.indexOf(item.uuid) === -1 295 ) { 296 maybeCloseItem(); 297 } 298 }); 299 300 301 302 /** 303 * Listen for ancestor opening requests 304 * 305 * The `pds-openAncestorAccordionItem` event is fired whenever a nested 306 * accordion item is opened, but the ancestors may also need to be 307 * opened. 308 */ 309 $(document).on('pds-openAncestorAccordionItem', function(event, uuid) { 310 if (uuid === item.uuid) { 311 openItem(true); 312 } 313 }); 314 315 316 317 item.controller.on('keydown', function(event) { 318 var code = event.which; 319 320 if (item.controller.prop('tagName') !== 'BUTTON') { 321 // 13 = Return, 32 = Space 322 if ((code === 13) || (code === 32)) { 323 // Simulate click on the controller 324 $(this).click(); 325 } 326 } 327 328 // 27 = Esc 329 if (code === 27) { 330 maybeCloseItem(); 331 } 332 }); 333 334 // Listen for hash changes (in page jump links for accordions) 335 $(window).on('hashchange', function() { 336 hashID = window.location.hash.replace('#', ''); 337 338 // Only open this item if the has matches the ID 339 if (hashID === item.id) { 340 var ele = $('#' + hashID); 341 342 // If there is a hash and the hash is on an accordion item 343 if (ele.length && ele.hasClass('pds-js-accordion-item')) { 344 // Open clicked item 345 openItem(true); 346 347 // Open ancestors if necessary 348 $.each(item.accordionAncestorItems, function(index, uuid) { 349 $(document).trigger('pds-openAncestorAccordionItem', uuid); 350 }); 351 } 352 } 353 }); 354 355 return this; 356 }; 357 358 359 function initializeNewAccordions(){ 360 $('.pds-js-accordion-item.pds-no-js').each(function() { 361 var fromData = { 362 // Set default settings 363 initiallyOpen: $(this).data(dataNS + 'initially-open'), 364 autoClose: $(this).data(dataNS + 'auto-close'), 365 clickToClose: $(this).data(dataNS + 'click-to-close'), 366 scroll: $(this).data(dataNS + 'scroll'), 367 scrollOffset: $(this).data(dataNS + 'scroll-offset'), 368 icon: $(this).data(dataNS + 'icon'), 369 iconSize: $(this).data(dataNS + 'iconSize'), 370 }; 371 $(this).pdsAccordionItem(fromData); 372 }); 373 } 374 // Loop through accordion settings objects 375 // Wait for the entire page to load before loading the accordion 376 $(window).on('load', initializeNewAccordions); 377 $(document).on('pds--apply-widgets pds-accordion--apply-widgets', initializeNewAccordions); 378}(jQuery));
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.