1/** 2 * @file 3 * CKEditor Accordion functionality. 4 */ 5 6(function (Drupal, drupalSettings, once) { 7 'use strict'; 8 9 let animating = false; 10 let animateObjects = []; 11 12 // Default config. 13 const defaults = { 14 duration: 300, 15 easing: (t, b, c, d) => { 16 if ((t /= d / 2) < 1) 17 return c / 2 * t * t + b; 18 return -c / 2 * ((--t) * (t - 2) - 1) + b; 19 } 20 }; 21 22 // Two directions. 23 const directions = { 24 OPEN: 1, 25 CLOSE: 2 26 }; 27 28 // Animate fn. 29 const animate = (animateSets, now) => { 30 let animationContinue = false; 31 for(let x = 0; x < animateSets.length; x++) { 32 let element = animateSets[x][0]; 33 let options = animateSets[x][1]; 34 if (!options.startTime) { 35 options.startTime = now; 36 } 37 let currentTime = now - options.startTime; 38 animationContinue = currentTime < options.duration; 39 if (animationContinue) { 40 let newHeight = options.easing(currentTime, options.startingHeight, options.distanceHeight, options.duration); 41 element.style.height = `${newHeight.toFixed(2)}px`; 42 } 43 } 44 45 if (animationContinue) { 46 window.requestAnimationFrame((timestamp) => animate(animateSets, timestamp)); 47 } 48 else { 49 for(let x = 0; x < animateSets.length; x++) { 50 let element = animateSets[x][0]; 51 let options = animateSets[x][1]; 52 if (options.direction === directions.CLOSE) { 53 element.style.display = 'none'; 54 } 55 if (options.direction === directions.OPEN) { 56 element.style.display = 'block'; 57 } 58 59 // Remove element animation styles. 60 element.style.height = null; 61 element.style.overflow = null; 62 element.style.marginTop = null; 63 element.style.marginBottom = null; 64 element.style.paddingTop = null; 65 element.style.paddingBottom = null; 66 } 67 68 // Reset vars. 69 animateObjects = []; 70 animating = false; 71 } 72 } 73 74 // Animation handler. 75 const doAnimations = (animateObjects) => { 76 let doAnimate = drupalSettings.ckeditorAccordion.accordionStyle.animateAccordionOpenAndClose ?? true; 77 if (!doAnimate) { 78 animateObjects.forEach(object => { 79 const element = object[1]; 80 if (object[0] == 'slideUp') { 81 element.style.display = 'none'; 82 } 83 else { 84 element.style.display = 'block'; 85 } 86 }); 87 animating = false; 88 } 89 else { 90 let animateSets = []; 91 animateObjects.forEach(object => { 92 const element = object[1]; 93 let options = Object.assign({}, defaults); 94 if (object[0] == 'slideUp') { 95 options.direction = directions.CLOSE; 96 options.to = 0; 97 options.startingHeight = element.scrollHeight; 98 options.distanceHeight = -options.startingHeight; 99 100 // Set element animation styles. 101 element.style.display = 'block'; 102 element.style.overflow = 'hidden'; 103 element.style.marginTop = '0'; 104 element.style.marginBottom = '0'; 105 element.style.paddingTop = '0'; 106 element.style.paddingBottom = '0'; 107 } else { 108 element.style.height = '0px'; 109 110 // Set element animation styles. 111 element.style.display = 'block'; 112 element.style.overflow = 'hidden'; 113 element.style.marginTop = '0'; 114 element.style.marginBottom = '0'; 115 element.style.paddingTop = '0'; 116 element.style.paddingBottom = '0'; 117 118 options.direction = directions.OPEN; 119 options.to = element.scrollHeight; 120 options.startingHeight = 0; 121 options.distanceHeight = options.to; 122 } 123 124 delete options.startTime; 125 animateSets.push([element, options]); 126 }); 127 128 window.requestAnimationFrame((timestamp) => animate(animateSets, timestamp)); 129 } 130 } 131 132 Drupal.behaviors.ckeditorAccordion = { 133 134 attach: function (context, settings) { 135 const $ckeditorAccordions = once('ckeditorAccordions', '.ckeditor-accordion', context); 136 const doAnimate = drupalSettings.ckeditorAccordion.accordionStyle.animateAccordionOpenAndClose ?? true;
137 const openTabsWithHash = drupalSettings.ckeditorAccordion.accordionStyle.openTabsWithHash ?? false; 138 const allowHtmlInTitles = drupalSettings.ckeditorAccordion.accordionStyle.allowHtmlInTitles ?? false; 139 for (let i = 0; i < $ckeditorAccordions.length; i++) { 140 let $accordion = $ckeditorAccordions[i]; 141 142 // The first one is the correct one. 143 if (!drupalSettings.ckeditorAccordion.accordionStyle.collapseAll) { 144 $accordion.querySelector('dt:first-child').classList.add('active'); 145 let dd = $accordion.querySelector('dd:first-of-type'); 146 dd.classList.add('active'); 147 dd.style.display = 'block'; 148 } 149 150 // Turn the accordion tabs to links so that the content is accessible & can be traversed using keyboard. 151 let childDts = Array.from($accordion.children).filter(function (child) { 152 return (child.tagName.toLowerCase() == 'dt'); 153 }); 154 for (let x = 0; x < childDts.length; x++) { 155 let $tab = childDts[x]; 156 let tabText = $tab.innerHTML.replace('<a','<span').replace('</a>','</span>'); 157 let tabHtml = (allowHtmlInTitles) ? $tab.innerHTML.trim() : tabText; 158 let toggleClass = $tab.classList.contains('active') ? ' active' : ''; 159 let hrefAndIds = 'href="#"'; 160 if (openTabsWithHash) { 161 let tabHash = encodeURIComponent(tabText.replace(/[^A-Za-z0-9]/g, "")); 162 hrefAndIds = 'href="#' + tabHash + '" id="' + tabHash + '" onclick="return false;"'; 163 } 164 $tab.innerHTML = '<a class="ckeditor-accordion-toggler button" ' + hrefAndIds + '><span class="ckeditor-accordion-toggle' + toggleClass + '"></span>' + tabHtml + '</a>'; 165 } 166 167 // Wrap the accordion in a div element so that quick edit function shows the source correctly. 168 $accordion.classList.add('styled'); 169 $accordion.classList.remove('ckeditor-accordion'); 170 let $wrapper = document.createElement('div'); 171 $wrapper.classList.add('ckeditor-accordion-container'); 172 if (!doAnimate) { 173 $wrapper.classList.add('no-animations'); 174 } 175 $accordion.after($wrapper); 176 $wrapper.appendChild($accordion); 177 178 // Trigger an ckeditorAccordionAttached event to let other frameworks know that the accordion has been attached. 179 let eventAccordionAttached = new Event('ckeditorAccordionAttached'); 180 $accordion.dispatchEvent(eventAccordionAttached); 181 182 // Handle click events. 183 let $togglers = $accordion.querySelectorAll('.ckeditor-accordion-toggler'); 184 for (let x = 0; x < $togglers.length; x++) { 185 $togglers[x].addEventListener("click", function(e) { 186 let $title = this.parentNode; 187 let $content = $title.nextElementSibling; 188 let $parent = $title.parentNode; 189 190 if (animating) { 191 // Prevent defaults. 192 e.preventDefault(); 193 return; 194 } 195 animating = true; 196 197 // Clicking on open element, close it. 198 if ($title.classList.contains('active')) { 199 $title.classList.remove('active'); 200 $content.classList.remove('active'); 201 202 // Add to animateObjects. 203 animateObjects.push(['slideUp', $content]); 204 } 205 else { 206 if(!drupalSettings.ckeditorAccordion.accordionStyle.keepRowsOpen) { 207 let activeChilds = Array.from($parent.children).filter(function (child) { 208 return (child.classList.contains('active')); 209 }); 210 activeChilds.forEach(function(activeChild) { 211 // Remove active class from all. 212 activeChild.classList.remove('active'); 213 if (activeChild.tagName.toLowerCase() == 'dd') { 214 // Slide DDs up. 215 animateObjects.push(['slideUp', activeChild]); 216 } 217 }); 218 } 219 220 $title.classList.add('active'); 221 $content.classList.add('active'); 222 223 // Add to animateObjects. 224 animateObjects.push(['slideDown', $content]); 225 } 226 227 doAnimations(animateObjects); 228 229 // Prevent defaults. 230 e.preventDefault(); 231 }); 232 } 233
234 // Open tabs with hash if config requires. 235 if (openTabsWithHash) { 236 // Trigger hash change when clicking an anchor to an accordion tab on the same page. 237 const $hashLinks = document.querySelectorAll('a[href*="#"]:not(.ckeditor-accordion-toggler):not(.visually-hidden)'); 238 for (let x = 0; x < $hashLinks.length; x++) { 239 $hashLinks[x].addEventListener('click', function (e) { 240 var parser = document.createElement('a'), hash; 241 var preventDefault = false; 242 parser.href = this.getAttribute('href'); 243 hash = parser.hash; 244 if (hash) { 245 // Get the matching accordion toggler with hash. 246 var el = document.querySelector('a.ckeditor-accordion-toggler[href="' + hash + '"]'); 247 if (el != null) { 248 // Set / update hash so that the event listener below fires. 249 if (window.location.hash === hash) { 250 window.dispatchEvent(new Event('hashchange')); 251 } 252 else { 253 window.location.hash = hash; 254 } 255 256 preventDefault = true; 257 } 258 } 259 260 if (preventDefault) { 261 e.preventDefault(); 262 } 263 }); 264 } 265 266 // Open content that matches the hash on hash change. 267 window.addEventListener('hashchange', function() { 268 var el = document.querySelector('a.ckeditor-accordion-toggler[href="' + window.location.hash + '"]'); 269 if (el != null && !el.parentNode.classList.contains('active')) { 270 el.click(); 271 } 272 }); 273 // Trigger event once on page load so that the right accordion is open. 274 window.dispatchEvent(new Event('hashchange')); 275 } 276 277 } 278 } 279 }; 280})(Drupal, drupalSettings, once);
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.