PageSourceSearch

https://www.austintexas.gov/modules/contrib/ckeditor_accordion/js/accordion.frontend.js?tm7ffa

js austintexas.gov collected 2026-10-09 22:42:29 UTC 10,417 bytes, 280 lines download raw bytes

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.