1 2if (!crypto.randomUUID) { 3 crypto.randomUUID = function () { 4 return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, c => 5 (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16) 6 ); 7 }; 8} 9 10export class TextCollapsible { 11 constructor(element) { 12 this.element = element; 13 this.section = element.closest('.section-body-text-narrow'); 14 this.button = this.section.querySelector('.text-collapsible__button') 15 this.isCollapsed = element.classList.contains('collapsed'); 16 17 // Setup accessibility 18 this.id = element.id || crypto.randomUUID(); 19 element.id = this.id; 20 21 this.button.setAttribute('role', 'button'); 22 this.button.setAttribute('tabindex', '0'); 23 this.button.setAttribute('aria-controls', this.id + '-content'); 24 this.element.id = this.id + '-content'; 25 this.element.setAttribute('aria-hidden', this.isOpen ? 'false' : 'true'); 26 this.button.setAttribute('aria-expanded', this.isOpen ? 'true' : 'false'); 27 28 // Set initial styles 29 this.element.style.height = this.isCollapsed ? '500px' : `${this.content.scrollHeight}px`; 30 31 // Bind events 32 this.button.addEventListener('click', () => this.toggle()); 33 } 34 35 toggle() { 36 this.isCollapsed ? this.expand() : this.collapse(); 37 } 38 39 expand() { 40 if (!this.element) return; 41 42 const fullHeight = this.element.scrollHeight + 'px'; 43 this.element.style.height = fullHeight; 44 this.element.classList.remove('collapsed'); 45 this.button.innerText = this.button.dataset.expanded; 46 47 this.element.setAttribute('aria-hidden', 'false'); 48 this.button.setAttribute('aria-expanded', 'true'); 49 this.isCollapsed = false; 50 51 // Reset height after transition to allow dynamic content 52 const transitionDuration = parseFloat( 53 getComputedStyle(document.documentElement).getPropertyValue('--dropdown-transition-duration') 54 ) || 0.3; 55 setTimeout(() => { 56 if (!this.isCollapsed) this.element.style.height = 'auto'; 57 }, transitionDuration * 1000); 58 } 59 60 collapse() { 61 if (!this.element) return; 62 63 const currentHeight = this.element.scrollHeight + 'px'; 64 this.element.style.height = currentHeight; 65 66 this.button.innerText = this.button.dataset.collapsed; 67 68 // Force reflow to trigger transition 69 void this.element.offsetHeight; 70 71 this.element.style.height = '500px'; 72 this.element.classList.add('collapsed'); 73 this.element.setAttribute('aria-hidden', 'true'); 74 this.button.setAttribute('aria-expanded', 'false'); 75 this.isCollapsed = true; 76 } 77} 78 79export function initCollapseText() { 80 if (!document.querySelectorAll('.text-collapsible').length) return; 81 82 const collapsibles = Array.from(document.querySelectorAll('.text-collapsible')); 83 collapsibles.forEach((collapsible) => new TextCollapsible(collapsible)); 84}
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.