PageSourceSearch

https://www.kunsthauswien.com/assets/js/utilities/dropdownsHandler.js

js kunsthauswien.com collected 2026-10-02 07:35:25 UTC 4,047 bytes, 130 lines download raw bytes

1// dropdownsHandler.js
2
3if (!crypto.randomUUID) {
4  crypto.randomUUID = function () {
5    return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, c =>
6      (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)
7    );
8  };
9}
10
11export class Dropdown {
12  static registry = new Map(); // keeps track of all dropdown instances
13
14  constructor(element) {
15    this.element = element;
16    this.label = element.querySelector('.dropdown__label');
17    this.content = element.querySelector('.dropdown__content');
18    this.isOpen = element.classList.contains('expanded');
19    this.container = element.closest('.section-dropdowns');
20
21    if (!this.label || !this.content) {
22      console.warn('Dropdown element missing required structure:', element);
23      return;
24    }
25
26    // Setup accessibility
27    this.id = element.id || crypto.randomUUID();
28    element.id = this.id;
29
30    this.label.setAttribute('role', 'button');
31    this.label.setAttribute('tabindex', '0');
32    this.label.setAttribute('aria-controls', this.id + '-content');
33    this.label.setAttribute('aria-expanded', this.isOpen ? 'true' : 'false');
34    this.content.id = this.id + '-content';
35    this.content.setAttribute('aria-hidden', this.isOpen ? 'false' : 'true');
36    this.transitionDuration = parseFloat(
37      getComputedStyle(document.documentElement).getPropertyValue('--dropdown-transition-duration')
38    ) || 0.3;
39
40    // Register instance
41    Dropdown.registry.set(this.id, this);
42
43    // Set initial styles
44    this.content.style.display = this.isOpen ? `block` : 'none';
45    this.content.style.height = this.isOpen ? `${this.content.scrollHeight}px` : '0px';
46
47    // Bind events
48    this.label.addEventListener('click', () => this.toggle());
49    this.label.addEventListener('keydown', (e) => {
50      if (e.key === 'Enter' || e.key === ' ') {
51        e.preventDefault();
52        this.toggle();
53      }
54    });
55  }
56
57  toggle() {
58    this.isOpen ? this.collapse() : this.expand();
59  }
60
61  expand() {
62    if (!this.content) return;
63
64    // Collapse siblings in the same container
65    if (this.container) {
66      Array.from(this.container.querySelectorAll('.dropdown')).forEach((sibling) => {
67        if (sibling !== this.element) {
68          const siblingInstance = Dropdown.registry.get(sibling.id);
69          siblingInstance?.collapse();
70        }
71      });
72    }
73
74    this.content.style.display = 'block';
75    const fullHeight = this.content.scrollHeight + 'px';
76    this.content.style.height = fullHeight;
77
78    this.element.classList.add('expanded');
79    this.content.setAttribute('aria-hidden', 'false');
80    this.label.setAttribute('aria-expanded', 'true');
81    this.isOpen = true;
82
83    // Reset height after transition to allow dynamic content
84    setTimeout(() => {
85      if (this.isOpen) this.content.style.height = 'auto';
86    }, this.transitionDuration * 1000);
87  }
88
89  collapse() {
90    if (!this.content) return;
91
92    const currentHeight = this.content.scrollHeight + 'px';
93    this.content.style.height = currentHeight;
94
95    // Force reflow to trigger transition
96    void this.content.offsetHeight;
97
98    this.content.style.height = '0px';
99    this.element.classList.remove('expanded');
100    this.content.setAttribute('aria-hidden', 'true');
101    this.label.setAttribute('aria-expanded', 'false');
102    this.isOpen = false;
103
104    setTimeout(() => {
105      this.content.style.display = 'none';
106    }, this.transitionDuration * 1000);
107  }
108}
109
110export function initDropdowns() {
111  const elements = Array.from(document.querySelectorAll('.dropdown'));
112  elements.forEach((el) => new Dropdown(el));
113}
114
115/**
116 * Expand the dropdown containing a given element
117 * @param {HTMLElement} element
118 * @returns {boolean} - true if a dropdown was expanded
119 */
120export function expandDropdownContaining(element) {
121  if (!element) return false;
122  const dropdown = element.closest('.dropdown');
123  if (!dropdown) return false;
124
125  const instance = Dropdown.registry.get(dropdown.id);
126  if (!instance) return false;
127
128  if (!instance.isOpen) instance.expand();
129  return true;
130}

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.