vendor: 28,914 bytes, lines 1-1052
1(function(){ 2 "use strict"; 3 4 var touchEnabled = "ontouchstart" in document.documentElement; 5 var pressEvent = touchEnabled ? "touchend" : "click"; 6 var each = [].forEach; 7 8 9 // Name of the onTransitionEnd event supported by this browser 10 var transitionEnd = (function(){ 11 for(var names = "transitionend webkitTransitionEnd oTransitionEnd otransitionend".split(" "), i = 0; i < 4; ++i) 12 if("on"+names[i].toLowerCase() in window) return names[i]; 13 return names[0]; 14 }()); 15 16 17 18 /** 19 * Conditionally add or remove a token from a token-list. 20 * 21 * @param {DOMTokenList} list 22 * @param {String} token 23 * @param {Boolean} enabled 24 */ 25 function setToken(list, token, enabled){ 26 enabled ? list.add(token) : list.remove(token); 27 } 28 29 30 31 /** 32 * Stop a function from firing too quickly. 33 * 34 * Returns a copy of the original function that runs only after the designated 35 * number of milliseconds have elapsed. Useful for throttling onResize handlers. 36 * 37 * @param {Number} limit - Threshold to stall execution by, in milliseconds. 38 * @param {Boolean} soon - If TRUE, will call the function *before* the threshold's elapsed, rather than after. 39 * @return {Function} 40 */ 41 function debounce(fn, limit, soon){ 42 var limit = limit < 0 ? 0 : limit, 43 started, context, args, timer, 44 45 delayed = function(){ 46 47 // Get the time between now and when the function was first fired 48 var timeSince = Date.now() - started; 49 50 if(timeSince >= limit){ 51 if(!soon) fn.apply(context, args); 52 if(timer) clearTimeout(timer); 53 timer = context = args = null; 54 } 55 56 else timer = setTimeout(delayed, limit - timeSince); 57 }; 58 59 60 // Debounced copy of the original function 61 return function(){ 62 context = this, 63 args = arguments; 64 65 if(!limit) 66 return fn.apply(context, args); 67 68 started = Date.now(); 69 if(!timer){ 70 if(soon) fn.apply(context, args); 71 timer = setTimeout(delayed, limit); 72 } 73 }; 74 }; 75 76 77 78 var uniqueID = (function(){ 79 var IDs = {}; 80 var indexes = {}; 81 82 83 /** 84 * Generate a unique ID for a DOM element. 85 * 86 * By default, minimalist IDs like "_1" or "_2" are generated using internally 87 * tracked incrementation. Uglier, more collision-proof IDs can be generated by 88 * passing a truthy value to the function's first argument. 89 * 90 * Irrespective of whether values are being generated simply or randomly, the 91 * document tree is always consulted first to ensure a duplicate ID is never 92 * returned. 93 * 94 * @param {String} prefix - Prefix prepended to result. Default: "_" 95 * @param {Boolean} random - Generate collision-proof IDs using random symbols 96 * @param {Number} length - Length of random passwords. Default: 6 97 * @return {String} 98 */ 99 function uniqueID(prefix, complex, length){ 100 length = +(length || 6); 101 var result = (prefix = prefix || "_"); 102 103 // Simple IDs 104 if(!complex){ 105 106 // Set this prefix's starting index if it's not been used yet 107 if(!indexes[prefix]) 108 indexes[prefix] = 0; 109 110 result += ++indexes[prefix]; 111 } 112 113 // Uglier/safer IDs 114 else{ 115 var chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; 116 chars += chars.toLowerCase(); 117 result += chars[ Math.round(Math.random() * (chars.length - 1)) ]; 118 chars += "0123456789"; 119 120 while(result.length < length) 121 result += chars[ Math.round(Math.random() * (chars.length - 1))]; 122 } 123 124 return IDs[result] || document.getElementById(result) 125 ? uniqueID(prefix, complex) 126 : (IDs[result] = true, result); 127 } 128 129 130 return uniqueID; 131 }()); 132 133 134 // Name of the CSSOM property used by this browser for CSS transforms 135 var cssTransform = (function(n){ 136 s = document.documentElement.style; 137 if((prop = n.toLowerCase()) in s) return prop; 138 for(var prop, s, p = "Webkit Moz Ms O Khtml", p = (p.toLowerCase() + p).split(" "), i = 0; i < 10; ++i) 139 if((prop = p[i]+n) in s) return prop; 140 return ""; 141 }("Transform")); 142 143 144 // Whether 3D transforms are supported by this browser 145 var css3DSupported = (function(propName){ 146 var e = document.createElement("div"), s = e.style, 147 v = [["translateY(", ")"], ["translate3d(0,", ",0)"]] 148 try{ s[propName] = v[1].join("1px"); } catch(e){} 149 return v[+!!s[propName]] === v[1]; 150 }(cssTransform)); 151 152 153 154 155 156 157 158 159 160 var folds = []; 161 162 163 /** 164 * Represents a single panel of togglable content inside an Accordion. 165 * 166 * @param {Accordion} accordion 167 * @param {HTMLElement} el 168 * @constructor 169 */ 170 var Fold = function(accordion, el){ 171 var THIS = this; 172 var heading = el.firstElementChild; 173 var content = el.lastElementChild; 174 var elClasses = el.classList; 175 var openClass = accordion.openClass; 176 var closeClass = accordion.closeClass; 177 var keysEnabled = !accordion.noKeys; 178 var useBorders = accordion.useBorders; 179 var useTransforms = !accordion.noTransforms && cssTransform; 180 var onToggle = accordion.onToggle; 181 var _disabled = false; 182 var _open, _y, _height, _ariaEnabled; 183 var scrollX, scrollY; 184 var onTouchStart; 185 var onKeyDown; 186 var onPress; 187 188 189 Object.defineProperties(THIS, { 190 fit: {value: fit}, 191 192 193 // Add or remove relevant ARIA attributes from the fold's elements 194 ariaEnabled: { 195 get: function(){ return _ariaEnabled; }, 196 set: function(input){ 197 if((input = !!input) !== !!_ariaEnabled){ 198 _ariaEnabled = input; 199 200 // Enable ARIA-attribute management 201 if(input){ 202 heading.setAttribute("role", "tab"); 203 content.setAttribute("role", "tabpanel"); 204 checkIDs(); 205 206 // Update the attributes that're controlled by .open's setter 207 heading.setAttribute("aria-selected", !!_open); 208 heading.setAttribute("aria-expanded", !!_open); 209 content.setAttribute("aria-hidden", !_open); 210 } 211 212 // Disabling; remove all relevant attributes 213 else{ 214 heading.removeAttribute("role"); 215 heading.removeAttribute("aria-controls"); 216 heading.removeAttribute("aria-selected"); 217 heading.removeAttribute("aria-expanded"); 218 219 content.removeAttribute("role"); 220 content.removeAttribute("aria-labelledby"); 221 content.removeAttribute("aria-hidden"); 222 } 223 } 224 } 225 }, 226 227 228 229 // Whether or not the fold's currently opened 230 open: { 231 232 get: function(){ 233 234 // Derive the fold's opened state from the DOM if it's not been determined yet 235 if(undefined === _open){ 236 _open = elClasses.contains(openClass); 237 setToken(elClasses, closeClass, !_open); 238 } 239 240 return _open; 241 }, 242 243 244 set: function(input){ 245 if((input = !!input) !== _open){ 246 247 // If an onToggle callback was specified, run it. Avoid doing anything if it returns false. 248 if("function" === typeof onToggle && false === onToggle.call(null, THIS, input)) 249 return; 250 251 setToken(elClasses, openClass, input); 252 setToken(elClasses, closeClass, !input); 253 _open = input; 254 255 // Update ARIA attributes 256 if(_ariaEnabled){ 257 heading.setAttribute("aria-selected", input); 258 heading.setAttribute("aria-expanded", input); 259 content.setAttribute("aria-hidden", !input); 260 } 261 262 // If this fold was closed when the screen resized, run a full update in case its contents were juggled around 263 if(THIS.needsRefresh){ 264 delete THIS.needsRefresh; 265 accordion.refresh(); 266 } 267 else accordion.update(); 268 269 // Close other folds if accordion is modal 270 if(accordion.modal && _open){ 271 for(var fold, i = 0, l = accordion.folds.length; i < l; ++i){ 272 if(THIS !== (fold = accordion.folds[i])) 273 fold.open = false; 274 } 275 } 276 } 277 } 278 }, 279 280 281 // Whether the fold's been deactivated 282 disabled: { 283 get: function(){ return _disabled }, 284 set: function(input){ 285 if((input = !!input) !== _disabled){ 286 var style = el.style; 287 288 // Deactivated 289 if(_disabled = input){ 290 style.height = null; 291 useTransforms 292 ? (style[cssTransform] = null) 293 : (style.top = null); 294 295 touchEnabled && heading.removeEventListener("touchstart", onTouchStart); 296 heading.removeEventListener(pressEvent, onPress); 297 elClasses.remove(openClass, closeClass); 298 if(onKeyDown){ 299 heading.removeEventListener("keydown", onKeyDown); 300 heading.removeAttribute("tabindex"); 301 } 302 303 if(_ariaEnabled){ 304 THIS.ariaEnabled = false; 305 _ariaEnabled = true; 306 } 307 } 308 309 // Reactivated 310 else{ 311 style.height = _height + "px"; 312 useTransforms 313 ? style[cssTransform] = 314 css3DSupported 315 ? ("translate3D(0," + _y + "px,0)") 316 : ("translateY(" + _y + "px)") 317 : (style.top = _y + "px"); 318 319 touchEnabled && heading.addEventListener("touchstart", onTouchStart); 320 heading.addEventListener(pressEvent, onPress); 321 322 if(onKeyDown){ 323 heading.addEventListener("keydown", onKeyDown); 324 heading.tabIndex = 0; 325 } 326 } 327 } 328 } 329 }, 330 331 332 // Vertical position of the fold within an accordion's container 333 y: { 334 get: function(){ 335 if(undefined === _y) 336 return (_y = parseInt(el.style.top) || 0); 337 return _y; 338 }, 339 340 set: function(input){ 341 if((input = +input) !== _y){ 342 _y = input; 343 useTransforms 344 ? el.style[cssTransform] = 345 css3DSupported 346 ? ("translate3D(0," + input + "px,0)") 347 : ("translateY(" + input + "px)") 348 : (el.style.top = input + "px"); 349 } 350 } 351 }, 352 353 354 // Height of the fold's outermost container 355 height: { 356 357 get: function(){ 358 if(undefined === _height){ 359 _height = THIS.headingHeight + content.scrollHeight; 360 el.style.height = _height + "px"; 361 } 362 return _height; 363 }, 364 365 set: function(input){ 366 if(input && (input = +input) !== _height){ 367 el.style.height = input + "px" 368 _height = input; 369 } 370 } 371 }, 372 373 374 // Current height of the fold's heading 375 headingHeight: { 376 get: function(){ 377 return heading.scrollHeight 378 + THIS.heightOffset 379 + (useBorders ? THIS.headingBorder : 0) 380 } 381 }, 382 383 // Total height consumed by the heading element's CSS borders, if any 384 headingBorder: { 385 get: function(){ 386 return (heading.offsetHeight || 0) - (heading.clientHeight || 0); 387 } 388 }, 389 390 391 392 // Total height of the fold's container element 393 elHeight: { 394 get: function(){ 395 return el.scrollHeight + (useBorders ? THIS.elBorder : 0); 396 } 397 }, 398 399 // Total height consumed by container element's CSS borders, if any 400 elBorder: { 401 get: function(){ 402 return (el.offsetHeight || 0) - (el.clientHeight || 0); 403 } 404 }, 405 406 407 // Whether the fold's container has been resized incorrectly 408 wrongSize: { 409 get: function(){ 410 return THIS.headingHeight + content.scrollHeight !== el.scrollHeight; 411 } 412 } 413 }); 414 415 416 417 THIS.index = folds.push(THIS) - 1; 418 THIS.accordion = accordion; 419 THIS.el = el; 420 THIS.heading = heading; 421 THIS.content = content; 422 THIS.ariaEnabled = !accordion.noAria; 423 THIS.heightOffset = accordion.heightOffset; 424 el.accordionFold = THIS.index; 425 useBorders = "auto" === useBorders ? (0 !== THIS.elBorder + THIS.headingBorder) : useBorders; 426 427 428 429 function checkIDs(){ 430 var headingSuffix = "-heading"; 431 var contentSuffix = "-content"; 432 var elID = el.id; 433 var id; 434 435 // Neither of the fold's elements have an ID attribute 436 if(!heading.id && !content.id){ 437 id = elID || uniqueID("a"); 438 heading.id = id + headingSuffix; 439 content.id = id + contentSuffix; 440 } 441 442 // Either the heading or element lack an ID 443 else if(!content.id) content.id = (elID || heading.id) + contentSuffix; 444 else if(!heading.id) heading.id = (elID || content.id) + headingSuffix; 445 446 // Finally, double-check each element's ID is really unique 447 var $ = function(s){return document.querySelectorAll("#"+s)}; 448 while($(content.id).length > 1 || $(heading.id).length > 1){ 449 id = uniqueID("a"); 450 content.id = id + contentSuffix; 451 heading.id = id + headingSuffix; 452 } 453 454 // Update ARIA attributes 455 heading.setAttribute("aria-controls", content.id); 456 content.setAttribute("aria-labelledby", heading.id); 457 } 458 459 460 461 // Keyboard navigation 462 if(keysEnabled){ 463 heading.tabIndex = 0; 464 heading.addEventListener("keydown", onKeyDown = function(e){ 465 var key = e.keyCode; 466 var fold; 467 468 switch(key){ 469 470 // Spacebar: Toggle 471 case 32: 472 e.preventDefault(); // Fall-through 473 474 475 // Enter: Toggle 476 case 13: 477 THIS.open = !THIS.open; 478 if("A" === e.target.tagName) 479 e.preventDefault(); 480 break; 481 482 483 // Escape: Clear focus 484 case 27: 485 e.target.blur(); 486 break; 487 488 489 // Up arrow: Previous section 490 case 38:{ 491 492 // Is there a previous sibling to navigate up to? 493 if(fold = THIS.previousFold){ 494 var children = fold.childAccordions; 495 496 // Is it open, and does it have nested accordions? 497 if(fold.open && children){ 498 var lastAcc; 499 var lastFold; 500 501 // Locate the deepest/nearest accordion that's currently exposed 502 while(children){ 503 lastAcc = children[children.length - 1]; 504 lastFold = lastAcc.folds[lastAcc.folds.length - 1]; 505 if(!lastFold.open) break; 506 children = lastFold.childAccordions; 507 } 508 509 lastFold.heading.focus(); 510 } 511 512 // Nope 513 else fold.heading.focus(); 514 } 515 516 // Is there a higher level we can jump back up to? 517 else if(accordion.parent) 518 accordion.parentFold.heading.focus(); 519 520 // There's nothing to move back to, so just let the browser run its usual behaviour 521 else return true; 522 523 e.preventDefault(); 524 return false; 525 } 526 527 528 529 // Down arrow: Next section 530 case 40:{ 531 var children = THIS.childAccordions; 532 533 // Is there a nested accordion to jump into? 534 if(THIS.open && children) 535 children[0].folds[0].heading.focus(); 536 537 // No, there isn't. Is there another sibling to move down to? 538 else if(fold = THIS.nextFold) 539 fold.heading.focus(); 540 541 // Is there a containing accordion we can navigate back up to? 542 else if(THIS.accordion.parent){ 543 var parent = THIS; 544 while(parent = parent.accordion.parentFold) 545 if(fold = parent.nextFold){ 546 fold.heading.focus(); 547 break; 548 } 549 550 // Nowhere left to go... 551 if(!parent) return true; 552 } 553 554 // Nah. Just scroll the window normally, as per browser default 555 else return true; 556 557 e.preventDefault(); 558 return false; 559 } 560 561 562 // Left arrow 563 case 37:{ 564 565 // Close an opened section 566 if(THIS.open) THIS.open = false; 567 568 // Switch focus back to parent 569 else if(accordion.parent) 570 accordion.parentFold.heading.focus(); 571 572 break; 573 } 574 575 // Right arrow 576 case 39:{ 577 var children = THIS.childAccordions; 578 579 // Open a closed section 580 if(!THIS.open) THIS.open = true; 581 582 // Switch focus to a nested accordion 583 else if(children) 584 children[0].folds[0].heading.focus(); 585 586 break; 587 } 588 } 589 }); 590 } 591 592 593 // Listener to record the viewport's scroll offsets at the beginning of a touch 594 touchEnabled && heading.addEventListener("touchstart", onTouchStart = function(e){ 595 scrollX = window.pageXOffset; 596 scrollY = window.pageYOffset; 597 }, {passive: true}); 598 599 600 heading.addEventListener(pressEvent, onPress = function(e){ 601 602 // Pressed on something inside the header 603 if(e.target !== heading && heading.contains(e.target)){ 604 605 // Cancel fold-toggle if user clicked on an anchor-link 606 if("A" === e.target.tagName && e.target.href) 607 return true; 608 } 609 610 if(e.type !== "touchend" || (e.cancelable && window.pageXOffset === scrollX && window.pageYOffset === scrollY)){ 611 THIS.open = !THIS.open; 612 e.preventDefault(); 613 } 614 return false; 615 }); 616 617 618 619 620 /** 621 * Adjust a fold's container to fit its content. 622 */ 623 function fit(){ 624 var height = THIS.headingHeight; 625 if(THIS.open) height += content.scrollHeight; 626 if(useBorders) height += THIS.elBorder; 627 THIS.height = height; 628 } 629 } 630 631 632 633 634 635 636 637 var accordions = []; 638 var activeAccordions = 0; 639 var lastResizeRate; 640 641 642 /** 643 * Represents a column of collapsible content regions. 644 * 645 * @param {HTMLElement} el - Container wrapped around each immediate fold 646 * @param {Object} options - Optional hash of settings 647 * @param {String} options.openClass - CSS class controlling each fold's "open" state 648 * @param {String} options.closeClass - CSS class used to mark a fold as closed 649 * @param {String} options.edgeClass - CSS class toggled based on whether the bottom-edge is visible 650 * @param {String} options.snapClass - CSS class for disabling transitions between window resizes 651 * @param {String} options.enabledClass - CSS class marking an accordion as enabled 652 * @param {String} options.disabledClass - CSS class marking an accordion as disabled 653 * @param {Boolean} options.disabled - Whether to disable the accordion on creation 654 * @param {Boolean} options.modal - Whether to close the current fold when opening another 655 * @param {Boolean} options.noAria - Disable the addition and management of ARIA attributes 656 * @param {Boolean} options.noKeys - Disable keyboard navigation 657 * @param {Boolean} options.noTransforms - Disable CSS transforms; positioning will be used instead 658 * @param {Number} options.heightOffset - Distance to offset each fold by 659 * @param {Boolean} options.useBorders - Consider borders when calculating fold heights 660 * @param {Function} options.onToggle - Callback executed when opening or closing a fold 661 * @constructor 662 */ 663 var Accordion = function(el, options){ 664 var THIS = this; 665 var elClasses = el.classList; 666 var options = options || {}; 667 var edgeClass = (undefined === options.edgeClass ? "edge-visible" : options.edgeClass); 668 var snapClass = (undefined === options.snapClass ? "snap" : options.snapClass); 669 var enabledClass = (undefined === options.enabledClass ? "accordion" : options.enabledClass); 670 var disabledClass = options.disabledClass; 671 var _height, _disabled, _parent, _parentFold, _modal; 672 673 674 Object.defineProperties(THIS, { 675 update: {value: update}, 676 updateFold: {value: updateFold}, 677 refresh: {value: refresh}, 678 679 // Whether the accordion's been deactivated 680 disabled: { 681 get: function(){ return _disabled; }, 682 set: function(input){ 683 if((input = !!input) !== _disabled){ 684 var style = el.style; 685 var folds = THIS.folds; 686 687 enabledClass && setToken(elClasses, enabledClass, !input); 688 disabledClass && setToken(elClasses, disabledClass, input); 689 690 691 // Deactivating 692 if(_disabled = input){ 693 style.height = null; 694 snapClass && elClasses.remove(snapClass); 695 if(edgeClass){ 696 el.removeEventListener(transitionEnd, THIS.onTransitionEnd); 697 elClasses.remove(edgeClass); 698 } 699 700 for(var i = 0, l = folds.length; i < l; ++i) 701 folds[i].disabled = true; 702 703 THIS.noAria || el.removeAttribute("role"); 704 --activeAccordions; 705 } 706 707 708 // Reactivating 709 else{ 710 for(var i = 0, l = folds.length; i < l; ++i) 711 folds[i].disabled = false; 712 713 THIS.noAria || el.setAttribute("role", "tablist"); 714 ++activeAccordions; 715 update(); 716 } 717 718 719 720 // If there're no more active accordions, disable the onResize handler 721 if(activeAccordions <= 0){ 722 activeAccordions = 0; 723 Accordion.setResizeRate(false); 724 } 725 726 // Otherwise, reactivate the onResize handler, assuming it was previously active 727 else if(lastResizeRate) 728 Accordion.setResizeRate(lastResizeRate); 729 } 730 } 731 }, 732 733 // Get or set the accordion enclosing this one 734 parent: { 735 set: function(input){ _parent = input; }, 736 get: function(){ 737 var result = _parent; 738 if(!result) return null; 739 740 // Search for the first ancestor that *isn't* disabled 741 while(result){ 742 if(!result.disabled) return result; 743 result = result.parent; 744 } 745 return null; 746 } 747 }, 748 749 // Get or set the fold of the accordion enclosing this one 750 parentFold: { 751 set: function(input){ _parentFold = input; }, 752 get: function(){ 753 var fold = _parentFold; 754 if(!fold) return null; 755 756 var accordion = fold.accordion; 757 758 // Search for the first ancestor that *isn't* disabled 759 while(fold && accordion){ 760 if(!accordion.disabled) return fold; 761 if(accordion = accordion.parent) 762 fold = accordion.parentFold; 763 } 764 return null; 765 } 766 }, 767 768 // Height of the accordion's container element 769 height: { 770 get: function(){ return _height; }, 771 set: function(input){ 772 if(input && (input = +input) !== _height){ 773 el.style.height = input + "px"; 774 _height = input; 775 } 776 } 777 }, 778 779 // Whether one of the Accordion's folds has been resized incorrectly 780 wrongSize: { 781 get: function(){ 782 var a = this.folds; 783 var l = a.length; 784 var i = 0; 785 for(; i < l; ++i) if(a[i].wrongSize) return true; 786 if(a = this.childAccordions) 787 for(; i < l; ++i) if(a[i].wrongSize) return true; 788 return false; 789 } 790 }, 791 792 // Top-level ancestor this accordion's nested inside 793 root: { 794 get: function(){ 795 var result = this; 796 while(result){ 797 if(!result.parent) return result; 798 result = result.parent; 799 } 800 } 801 } 802 }); 803 804 805 // Assign options as properties 806 THIS.openClass = options.openClass || "open"; 807 THIS.closeClass = options.closeClass || "closed"; 808 THIS.modal = !!options.modal; 809 THIS.noAria = !!options.noAria; 810 THIS.noKeys = !!options.noKeys; 811 THIS.noTransforms = !!options.noTransforms; 812 THIS.index = accordions.push(THIS) - 1; 813 THIS.heightOffset = +options.heightOffset || 0; 814 THIS.useBorders = undefined === options.useBorders ? "auto" : options.useBorders; 815 THIS.onToggle = options.onToggle; 816 817 818 // Create a fold for each immediate descendant of the Accordion's container 819 var folds = []; 820 each.call(el.children, function(i){ 821 var fold = new Fold(THIS, i); 822 folds.push(fold); 823 824 // Connect the fold to its previous sibling, if it's not the first to be added 825 var prev = folds[folds.length - 2]; 826 if(prev){ 827 prev.nextFold = fold; 828 fold.previousFold = prev; 829 } 830 }); 831 832 833 el.accordion = THIS.index; 834 THIS.noAria || el.setAttribute("role", "tablist"); 835 THIS.el = el; 836 THIS.folds = folds; 837 838 // Add .enabledClass early - it might affect the heights of each fold 839 if(!options.disabled && enabledClass) 840 elClasses.add(enabledClass); 841 842 update(); 843 844 845 // Find out if this accordion's nested inside another 846 var next = el; 847 while((next = next.parentNode) && 1 === next.nodeType){ 848 var fold = Accordion.getFold(next); 849 if(fold){ 850 var accordion = fold.accordion; 851 THIS.parent = accordion; 852 THIS.parentFold = fold; 853 edgeClass && elClasses.remove(edgeClass); 854 (accordion.childAccordions = accordion.childAccordions || []).push(THIS); 855 (fold.childAccordions = fold.childAccordions || []).push(THIS); 856 857 // Adjust the height of the containing fold's element 858 if(fold.open){ 859 var scrollHeight = fold.el.scrollHeight; 860 var distance = (fold.headingHeight + fold.content.scrollHeight) - scrollHeight || (scrollHeight - fold.el.clientHeight); 861 accordion.updateFold(fold, distance); 862 } 863 break; 864 } 865 } 866 867 868 edgeClass && el.addEventListener(transitionEnd, this.onTransitionEnd = function(e){ 869 if(!THIS.parent && e.target === el && "height" === e.propertyName && el.getBoundingClientRect().bottom > window.innerHeight) 870 elClasses.remove(edgeClass); 871 }); 872 873 this.disabled = !!options.disabled; 874 875 876 877 /** 878 * Internal method to check if an accordion's bottom-edge is visible to the user (or about to be). 879 * 880 * @param {Number} offset 881 * @private 882 */ 883 function edgeCheck(offset){ 884 if(edgeClass){ 885 var box = el.getBoundingClientRect(); 886 var windowEdge = window.innerHeight; 887 888 // If the bottom-edge is visible (or about to be), enable height animation 889 if(box.bottom + (offset || 0) < windowEdge) 890 elClasses.add(edgeClass) 891 892 // If the bottom-edge isn't visible anyway, disable height animation immediately 893 else if(box.bottom > windowEdge) 894 elClasses.remove(edgeClass); 895 } 896 } 897 898 899 900 /** 901 * Update the vertical ordinate of each sibling for a particular fold. 902 * 903 * @param {Fold} fold 904 * @param {Number} offset - Pixel distance to adjust by 905 */ 906 function updateFold(fold, offset){ 907 var next = fold; 908 var parentFold = THIS.parentFold; 909 910 while(next = next.nextFold) 911 next.y += offset; 912 parentFold || edgeCheck(offset); 913 fold.height += offset; 914 THIS.height += offset; 915 916 parentFold && parentFold.open && THIS.parent.updateFold(parentFold, offset); 917 } 918 919 920 /** 921 * Update the height of each fold to fit its content. 922 */ 923 function update(){ 924 var y = 0; 925 var height = 0; 926 var i = 0; 927 var l = folds.length; 928 var parentFold = THIS.parentFold; 929 var fold, diff; 930 931 for(; i < l; ++i){ 932 fold = folds[i]; 933 fold.y = y; 934 fold.fit(); 935 y += fold.height; 936 height += fold.height; 937 } 938 939 diff = height - _height; 940 parentFold 941 ? (parentFold.open && THIS.parent.updateFold(parentFold, diff)) 942 : edgeCheck(diff); 943 944 THIS.height = height; 945 } 946 947 948 949 /** 950 * Recalculate the boundaries of an Accordion and its descendants. 951 * 952 * This method should only be called if the width of a container changes, 953 * or a fold's contents have resized unexpectedly (such as when images load). 954 * 955 * @param {Boolean} allowSnap - Snap folds instantly into place without transitioning 956 */ 957 function refresh(allowSnap){ 958 var snap = allowSnap ? snapClass : false; 959 snap && elClasses.add(snap); 960 961 THIS.update(); 962 THIS.childAccordions && THIS.childAccordions.forEach(function(a){ 963 a.parentFold.open 964 ? a.refresh(allowSnap) 965 : (a.parentFold.needsRefresh = true); 966 }); 967 968 snap && setTimeout(function(e){elClasses.remove(snap)}, 20); 969 } 970 } 971 972 // If IE8PP exists, it means the author wants/needs IE8 support. See also: tinyurl.com/fixIE8-9 973 if("function" === typeof IE8PP) 974 Accordion = IE8PP(Accordion), 975 Fold = IE8PP(Fold); 976 977 978 979 /** 980 * Alter the rate at which screen-resize events update accordion widths. 981 * 982 * @param {Number} delay - Rate expressed in milliseconds 983 */ 984 Accordion.setResizeRate = function(delay){ 985 var fn = function(e){ 986 for(var a, i = 0, l = accordions.length; i < l; ++i){ 987 a = accordions[i]; 988 a.parent || a.disabled || a.refresh(true); 989 } 990 }; 991 992 var THIS = Accordion; 993 THIS.onResize && window.removeEventListener("resize", THIS.onResize); 994 995 // Make sure we weren't passed an explicit value of FALSE, or a negative value 996 if(false !== delay && (delay = +delay || 0) >= 0){ 997 THIS.onResize = delay ? debounce(fn, delay) : fn; 998 window.addEventListener("resize", THIS.onResize); 999 if(delay) lastResizeRate = delay; 1000 } 1001 } 1002 1003 1004 1005 /** 1006 * Return the closest (most deeply-nested) accordion enclosing an element. 1007 * 1008 * @param {Node} node 1009 * @return {Accordion} 1010 */ 1011 Accordion.getAccordion = function(node){ 1012 while(node){ 1013 if("accordion" in node) 1014 return accordions[node.accordion]; 1015 1016 node = node.parentNode; 1017 if(!node || node.nodeType !== 1) return null; 1018 } 1019 } 1020 1021 1022 /** 1023 * Return the closest (most deeply-nested) fold enclosing an element. 1024 * 1025 * @param {Node} node 1026 * @return {Fold} 1027 */ 1028 Accordion.getFold = function(node){ 1029 while(node){ 1030 if("accordionFold" in node) 1031 return folds[node.accordionFold]; 1032 1033 node = node.parentNode; 1034 if(!node || node.nodeType !== 1) return null; 1035 } 1036 } 1037 1038 1039 1040 Accordion.setResizeRate(25); 1041 1042 1043 // Browser export 1044 window.Accordion = Accordion; 1045 1046 // CommonJS/Node.js 1047 if("object" === typeof module && "object" === typeof module.exports) 1048 module.exports.Accordion = Accordion; 1049 1050 // AMD/UMD-like systems 1051 return Accordion; 1052}());
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.