PageSourceSearch

https://www.gls.de/assets/js/dotflyAccordion.js?1790764697

js gls.de collected 2026-10-01 10:45:12 UTC 4,718 bytes, 155 lines download raw bytes

1class DotflyAccordion {
2  classes = {
3    element: 'js-accordion',
4    item: 'js-accordion-item',
5    trigger: 'js-accordion-trigger',
6    content: 'js-accordion-content',
7    close: 'js-accordion-close',
8    open: 'is--open'
9  };
10
11  nodes = {
12    triggers: null,
13    closes: null
14  };
15
16  constructor() {
17    this.nodes.triggers = document.querySelectorAll(`.${this.classes.element} .${this.classes.trigger}`);
18    this.nodes.closes = document.querySelectorAll(`.${this.classes.element} .${this.classes.close}`);
19
20    // Set initial height and tabindex on active rows.
21    const openItems = document.querySelectorAll(`.${this.classes.item}.${this.classes.open}`);
22    openItems.forEach(item => {
23      const content = item.querySelector(`.${this.classes.content}`);
24      this.setContentHeight(content);
25      this.setFocusable(content, true);
26    });
27
28    this.addEventListeners();
29  }
30
31  /**
32   * Add all event handlers
33   */
34  addEventListeners() {
35    this.nodes.triggers.forEach(trigger => {
36      trigger.addEventListener('click', () => {
37        this.toggleItem(trigger);
38      });
39    });
40
41    this.nodes.closes.forEach(close => {
42      close.addEventListener('click', () => {
43        const item = close.closest(`.${this.classes.item}`);
44        if (item) {
45          const trigger = item.querySelector(`.${this.classes.trigger}`);
46          this.closeItem(trigger);
47        }
48      });
49    });
50  }
51
52  /**
53   * Toggle the state of an accordion item.
54   * @param {HTMLElement} trigger The trigger button for the accordion item.
55   */
56  toggleItem(trigger) {
57    const item = trigger.closest(`.${this.classes.item}`);
58
59    if (item.classList.contains(this.classes.open)) {
60      this.closeItem(trigger); // Close if already open
61    } else {
62      // Close all open items first
63      const openItems = document.querySelectorAll(`.${this.classes.item}.${this.classes.open}`);
64      openItems.forEach(openItem => {
65        const openTrigger = openItem.querySelector(`.${this.classes.trigger}`);
66        this.closeItem(openTrigger);
67      });
68
69      this.openItem(trigger); // Then open the new one
70    }
71  }
72
73  /**
74   * Open a closed accordion row.
75   * @param {HTMLElement} trigger The trigger button for the accordion item.
76   * @param {boolean} scrollIntoView Scroll element into viewport.
77   */
78  async openItem(trigger, scrollIntoView = true) {
79    if (!trigger) return;
80
81    const item = trigger.closest(`.${this.classes.item}`);
82    if (!item) return;
83
84    const content = item.querySelector(`.${this.classes.content}`);
85    this.setContentHeight(content);
86    item.classList.add(this.classes.open);
87    trigger.setAttribute('aria-expanded', 'true');
88    content.setAttribute('aria-hidden', 'false');
89
90    // Make content focusable
91    this.setFocusable(content, true);
92
93    // Scroll into view
94    if (scrollIntoView && window.innerWidth < 1440) {
95      await new Promise(resolve => setTimeout(resolve, 500));
96      trigger.scrollIntoView({ behavior: 'smooth', block: 'start' });
97    }
98  }
99
100  /**
101   * Close an open accordion row.
102   * @param {HTMLElement} trigger The trigger button for the accordion item.
103   */
104  closeItem(trigger) {
105    if (!trigger) return;
106
107    const item = trigger.closest(`.${this.classes.item}`);
108    if (!item) return;
109
110    const content = item.querySelector(`.${this.classes.content}`);
111
112    item.classList.remove(this.classes.open);
113    trigger.setAttribute('aria-expanded', 'false');
114    content.setAttribute('aria-hidden', 'true');
115
116    // Make content non-focusable
117    this.setFocusable(content, false);
118  }
119
120  /**
121   * Set or remove focusable elements inside the content.
122   * @param {HTMLElement} content The content of the accordion item.
123   * @param {boolean} focusable Whether the content should be focusable or not.
124   */
125  setFocusable(content, focusable) {
126    if (!content) return;
127
128    const focusableElements = content.querySelectorAll('a, button');
129    const tabindexValue = focusable ? '0' : '-1';
130
131    focusableElements.forEach(element => {
132      element.setAttribute('tabindex', tabindexValue);
133    });
134
135    const closeButton = content.closest(`.${this.classes.item}`).querySelector(`.${this.classes.close}`);
136    if (closeButton) {
137      closeButton.setAttribute('tabindex', tabindexValue);
138    }
139  }
140
141  /**
142   * Calculate and set the content height as a CSS variable.
143   * @param {HTMLElement} content The content of the accordion item.
144   */
145  setContentHeight(content) {
146    if (content && content.firstElementChild) {
147      const height = content.firstElementChild.offsetHeight;
148      content.style.setProperty('--accordion-content-height', `${height}px`);
149    }
150  }
151}
152
153document.addEventListener('DOMContentLoaded', () => {
154  new DotflyAccordion();
155});

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.