PageSourceSearch

https://www.arwag.at/_resources/app/themes/arwag/js/controllers/RedexpController.js?m=1766124355

js arwag.at collected 2026-10-02 20:04:29 UTC 14,586 bytes, 439 lines download raw bytes

1import {Controller} from "@hotwired/stimulus";
2
3class Animator {
4    expand(controller, animated, doneCallback) {
5        let node = controller.hasCssReceiverTarget ? controller.cssReceiverTarget : controller.element;
6        controller.hasExpandedClass && node.classList.add(controller.expandedClass);
7        controller.hasReducedClass && node.classList.remove(controller.reducedClass);
8        doneCallback && doneCallback();
9    }
10
11    reduce(controller, animated, doneCallback) {
12        let node = controller.hasCssReceiverTarget ? controller.cssReceiverTarget : controller.element;
13        controller.hasExpandedClass && node.classList.remove(controller.expandedClass);
14        controller.hasReducedClass && node.classList.add(controller.reducedClass);
15        doneCallback && doneCallback();
16    }
17}
18
19class SlideAnimator extends Animator {
20
21    constructor(config) {
22        super();
23        this.config = config || {};
24
25        this.pendingTimers = new WeakMap();
26    }
27
28    calculateAnimationDuration(distance) {
29        let duration = Math.abs(distance) * 2;
30        duration = Math.min(1000, duration);
31        duration = Math.max(300, duration);
32        return duration;
33    }
34
35    animateTo(node, currentHeight, targetHeight, duration, initCallback, callback) {
36        requestAnimationFrame(() => {
37            this.prepareTransition(node, duration, currentHeight);
38            initCallback(node, duration);
39
40            requestAnimationFrame(() => {
41                this.performTransition(node, duration, targetHeight);
42            });
43        });
44
45        let pendingTimer = this.pendingTimers.get(node);
46        if (pendingTimer) {
47            clearTimeout(pendingTimer);
48        }
49
50        pendingTimer = setTimeout(() => {
51            this.completeTransition(node);
52            callback();
53        }, duration + 10);
54        this.pendingTimers.set(node, pendingTimer);
55    }
56
57    prepareTransition(node, duration, currentHeight) {
58        node.style.setProperty('transition', 'none', 'important');
59        node.style.maxHeight = 'none';
60        node.style.minHeight = '0px';
61        node.style.overflow = 'hidden';
62        node.style.height = currentHeight + 'px';
63    }
64
65    performTransition(node, duration, targetHeight) {
66        node.style.transition = '';
67        node.style.transitionProperty = 'height';
68        node.style.transitionDuration = duration + 'ms';
69        node.style.height = targetHeight + 'px';
70        node.style.setProperty('--redexp-transitionDuration', duration + 'ms');
71    }
72
73    completeTransition(node) {
74        node.style.transition = '';
75        node.style.height = '';
76        node.style.maxHeight = '';
77        node.style.minHeight = '';
78        node.style.overflow = '';
79    }
80
81    measureHeight(node, before, after) {
82        const currentHeight = node.clientHeight;
83        // Ensure minimum height of body so there is no change in scroll position
84        document.body.style.minHeight = document.body.getBoundingClientRect().height + 'px';
85        // Disable transitions
86        node.style.setProperty('transition', 'none', 'important');
87        // set desired target styling
88        before(node);
89        const result = node.getBoundingClientRect().height;
90        // reset styling
91        after(node);
92
93        // now prepare for transition
94        //node.style.maxHeight = 'none';
95        //node.style.height = currentHeight + 'px';
96        node.style.transition = '';
97
98        document.body.style.minHeight = '';
99
100        return result;
101    }
102
103    expand(controller, animated, doneCallback) {
104        const contentNode = controller.itemTarget;
105
106        let node = controller.hasCssReceiverTarget ? controller.cssReceiverTarget : controller.element;
107
108        const wrappedDoneCallback = () => {
109            if (animated && controller.scrollToValue) {
110                setTimeout(() => {
111                    const scrollTarget = controller.hasScrollerTarget ? controller.scrollerTarget : contentNode;
112                    scrollTarget.scrollIntoView({behavior: 'smooth', block: 'start'});
113                });
114            }
115
116            controller.hasExpandingClass && node.classList.remove(controller.expandingClass);
117            controller.hasExpandedClass && node.classList.add(controller.expandedClass);
118
119            doneCallback && doneCallback();
120        };
121
122        if (!animated) {
123            wrappedDoneCallback();
124        } else {
125
126            const currentHeight = contentNode.getBoundingClientRect().height;
127
128            let height = this.measureHeight(contentNode, (contentNode) => {
129                contentNode.removeAttribute('hidden');
130                contentNode.style.height = '';
131                //contentNode.style.maxHeight = 'none';
132
133                controller.hasReducedClass && node.classList.remove(controller.reducedClass);
134                controller.hasExpandedClass && node.classList.add(controller.expandedClass);
135            }, (contentNode) => {
136                controller.hasReducedClass && node.classList.add(controller.reducedClass);
137                controller.hasExpandedClass && node.classList.remove(controller.expandedClass);
138            });
139
140            console.log(currentHeight, height);
141
142
143            const targetHeight = Math.max(currentHeight, height);
144            let duration = this.calculateAnimationDuration(targetHeight - currentHeight);
145
146            this.animateTo(contentNode, currentHeight, targetHeight, duration, () => {
147                controller.hasReducedClass && node.classList.remove(controller.reducedClass);
148                controller.hasExpandingClass && node.classList.add(controller.expandingClass);
149            }, wrappedDoneCallback);
150
151        }
152
153    }
154
155    reduce(controller, animated, doneCallback) {
156        const contentNode = controller.itemTarget;
157
158        let node = controller.hasCssReceiverTarget ? controller.cssReceiverTarget : controller.element;
159
160        const wrappedDoneCallback = () => {
161            if (animated && controller.scrollToValue) {
162                setTimeout(() => {
163                    const scrollTarget = controller.hasScrollerTarget ? controller.scrollerTarget : contentNode;
164                    scrollTarget.scrollIntoView({behavior: 'smooth', block: 'nearest'});
165                });
166            }
167
168            controller.hasReducingClass && node.classList.remove(controller.reducingClass);
169            controller.hasReducedClass && node.classList.add(controller.reducedClass);
170        };
171
172        if (!animated) {
173            wrappedDoneCallback();
174            return;
175        }
176
177        const currentHeight = contentNode.getBoundingClientRect().height;
178
179        let height = this.measureHeight(contentNode, (contentNode) => {
180            controller.hasExpandedClass && node.classList.remove(controller.expandedClass);
181            controller.hasReducedClass && node.classList.add(controller.reducedClass);
182        }, (contentNode) => {
183            controller.hasExpandedClass && node.classList.add(controller.expandedClass);
184            controller.hasReducedClass && node.classList.remove(controller.reducedClass);
185        });
186
187        if (height >= currentHeight) {
188            height = 0;
189        }
190        const targetHeight = Math.max(0, height);
191        let duration = this.calculateAnimationDuration(currentHeight - targetHeight);
192
193        this.animateTo(contentNode, currentHeight, targetHeight, duration, () => {
194            controller.hasExpandedClass && node.classList.remove(controller.expandedClass);
195            controller.hasReducingClass && node.classList.add(controller.reducingClass);
196        }, wrappedDoneCallback);
197    }
198
199}
200
201const knownAnimators = {
202    default: new Animator(),
203    slide: new SlideAnimator(),
204};
205
206export default class RedexpController extends Controller {
207
208    static get targets() {
209        return [
210            'button',
211            'cssReceiver',
212            'item',
213            'scroller'
214        ];
215    }
216
217    static get classes() {
218        return [
219            'active',
220            'reduced',
221            'reducing',
222            'expanded',
223            'expanding',
224        ];
225    }
226
227    static get values() {
228        return {
229            animator: String,
230            autoExpanded: {
231                default: false,
232                type: Boolean,
233            },
234            expanded: Boolean,
235            forceExpand: Boolean,
236            forceReduce: Boolean,
237            group: String,
238            hash: String,
239            hiddenIfReduced: {
240                default: true,
241                type: Boolean
242            },
243            mediaqueryForceExpand: String,
244            mediaqueryForceReduce: String,
245            scrollTo: Boolean
246        };
247    }
248
249    initialize() {
250        this.element.addEventListener('reduce', () => {
251            this.reduce(null, true);
252        });
253        this.element.addEventListener('expand', () => {
254            this.expand(null, true);
255        });
256    }
257
258    rebind(lastState) {
259        this.expandedValue = lastState.expandedValue;
260
261        if (this.hasActiveClass) {
262            this.element.classList.add(this.activeClass);
263        }
264
265        if (this.expandedValue) {
266            this.expand(undefined, true);
267        } else {
268            this.reduce(undefined, true).then(() => {
269                if (this.autoExpandedValue) {
270                    if (this.itemTarget.scrollHeight <= this.itemTarget.clientHeight) {
271                        console.log(this.itemTarget, this.itemTarget.scrollHeight, this.itemTarget.clientHeight)
272                        this.expand(null, true);
273                        if (this.hasActiveClass) {
274                            this.element.classList.remove(this.activeClass);
275                        }
276                    }
277                }
278            });
279        }
280    }
281
282    _loadFromHash() {
283        if (this.hasHashValue && this.hashValue === location.hash.substr(1)) {
284            this.expand();
285            location.hash = '';
286        }
287    }
288
289    connect() {
290        if (this.hasMediaqueryForceExpandValue || this.hasMediaqueryForceReduceValue) {
291            import('stimulus-use').then((useLib) => {
292                useLib.useWindowResize(this);
293            });
294        }
295
296        if (this.hasActiveClass) {
297            this.element.classList.add(this.activeClass);
298        }
299
300        if (this.expandedValue) {
301            this.expand(undefined, true);
302        } else {
303            this.reduce(undefined, true).then(() => {
304                if (this.autoExpandedValue) {
305                    if (this.itemTarget.scrollHeight <= this.itemTarget.clientHeight) {
306                        console.log(this.itemTarget, this.itemTarget.scrollHeight, this.itemTarget.clientHeight)
307                        this.expand(null, true);
308                        if (this.hasActiveClass) {
309                            this.element.classList.remove(this.activeClass);
310                        }
311                    }
312                }
313            });
314        }
315
316        if (this.hasHashValue) {
317            window.addEventListener('hashchange', () => {
318                this._loadFromHash();
319            });
320            if (location.hash.length > 1) {
321                this._loadFromHash();
322            }
323        }
324    }
325
326    get animator() {
327        return knownAnimators[this.animatorValue || 'default'];
328    }
329
330    static registerAnimator(name, animator) {
331        knownAnimators[name] = animator;
332    }
333
334    #connectButtonsAndItem() {
335        if (this.hasItemTarget) {
336            const itemId = this.itemTarget.getAttribute('id');
337            this.buttonTargets.forEach((button) => {
338                button.setAttribute('aria-controls', itemId);
339            });
340        }
341        this.buttonTargets.forEach((button) => {
342            button.setAttribute('aria-expanded', this.expandedValue);
343        });
344    }
345
346    buttonTargetConnected() {
347        this.#connectButtonsAndItem();
348    }
349
350    itemTargetConnected() {
351        if (!this.itemTarget.getAttribute('id')) {
352            this.itemTarget.setAttribute('id', 'redexp-' + Math.round(Math.random() * 1000000));
353        }
354        this.#connectButtonsAndItem();
355    }
356
357    reduce(event, preventAnimation) {
358        if (this.forceExpandValue) {
359            return Promise.resolve();
360        }
361        if (!this.expandedValue) {
362            return Promise.resolve();
363        }
364        let promiseResolve;
365        const result = new Promise((resolve) => {
366            promiseResolve = resolve;
367        })
368
369        this.expandedValue = false;
370        this.animator.reduce(this, !preventAnimation, () => {
371            if (this.hiddenIfReducedValue) {
372                this.itemTarget.setAttribute('hidden', '');
373            }
374            promiseResolve();
375        });
376        this.dispatch('reduced');
377        this.buttonTargets.forEach((button) => {
378            button.setAttribute('aria-expanded', 'false');
379        });
380        return result;
381    }
382
383    expand(event, preventAnimation) {
384        if (this.forceReduceValue) {
385            return;
386        }
387        if (this.expandedValue) {
388            return;
389        }
390        this.expandedValue = true;
391        this.animator.expand(this, !preventAnimation, () => {
392            this.itemTarget.removeAttribute('hidden');
393        });
394        this.dispatch('expanded');
395        this.buttonTargets.forEach((button) => {
396            button.setAttribute('aria-expanded', 'true');
397        });
398        if (this.hasGroupValue) {
399            document.querySelectorAll(`[data-redexp-group-value="${this.groupValue}"]`).forEach(node => {
400                if (node !== this.element) {
401                    const controller = this.application.getControllerForElementAndIdentifier(node, 'redexp');
402                    controller && controller.reduce();
403                }
404            });
405        }
406    }
407
408    toggle(event, preventAnimation) {
409        if (this.expandedValue) {
410            this.reduce(event, preventAnimation);
411        } else {
412            this.expand(event, preventAnimation);
413        }
414    }
415
416    forceExpandValueChanged() {
417        if (this.forceExpandValue) {
418            this.expand(undefined, true);
419        }
420    }
421
422    forceReduceValueChanged() {
423        if (this.forceReduceValue) {
424            this.reduce(undefined, true);
425        }
426    }
427
428    windowResize(data) {
429        if (this.hasMediaqueryForceExpandValue) {
430            this.forceExpandValue = window.matchMedia(this.mediaqueryForceExpandValue).matches;
431        }
432        if (this.hasMediaqueryForceReduceValue) {
433            this.forceReduceValue = window.matchMedia(this.mediaqueryForceReduceValue).matches;
434        }
435    }
436
437}
438
439export {RedexpController};

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.