PageSourceSearch

https://zuomod.com/wp-content/mu-plugins/widgets/atoms/AccordionWidgetJs.js?ver=84f8a34a

js zuomod.com collected 2026-09-25 18:44:13 UTC 14,966 bytes, 366 lines download raw bytes

1(function () {
2    "use strict";
3
4    if (window._wizAccordionInitialized) {
5        return;
6    }
7    window._wizAccordionInitialized = true;
8
9    const defaultExpandedSvg =
10        '<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="transform: rotate(180deg);"><path d="M6 9l6 6l6 -6"/></svg>';
11    const defaultCollapsedSvg =
12        '<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M6 9l6 6l6 -6"/></svg>';
13
14    const deferredContent = (function () {
15        const VIEWPORT_LEAD = "200px 0px";
16        const DEFERRED_SELECTOR = ".wiz-accordion-attr--deferred";
17
18        let observer = null;
19
20        function isPending(node) {
21            return !!node?.querySelector("template.wiz-accordion-lazy-template");
22        }
23
24        function hydrate(node) {
25            if (!isPending(node)) {
26                return false;
27            }
28
29            const template = node.querySelector("template.wiz-accordion-lazy-template");
30            const mount = node.querySelector(".wiz-accordion-lazy-mount");
31            if (!template || !mount) {
32                return false;
33            }
34
35            mount.appendChild(template.content.cloneNode(true));
36            template.remove();
37            mount.removeAttribute("aria-hidden");
38            node.classList.remove("wiz-accordion-attr--deferred");
39
40            if (typeof applyShowMoreLess === "function") {
41                applyShowMoreLess(node);
42            }
43
44            return true;
45        }
46
47        function hydrateWithin(root) {
48            const scope = root?.querySelectorAll ? root : document;
49            scope.querySelectorAll(DEFERRED_SELECTOR).forEach((node) => {
50                hydrate(node);
51            });
52        }
53
54        function onIntersect(entries) {
55            entries.forEach((entry) => {
56                if (!entry.isIntersecting) {
57                    return;
58                }
59                hydrate(entry.target);
60                observer?.unobserve(entry.target);
61            });
62        }
63
64        function observe(root) {
65            const scope = root?.querySelectorAll ? root : document;
66            const nodes = scope.querySelectorAll(DEFERRED_SELECTOR);
67            if (!nodes.length) {
68                return;
69            }
70
71            if (!("IntersectionObserver" in window)) {
72                nodes.forEach((node) => hydrate(node));
73                return;
74            }
75
76            if (!observer) {
77                observer = new IntersectionObserver(onIntersect, {
78                    rootMargin: VIEWPORT_LEAD,
79                    threshold: 0,
80                });
81            }
82
83            nodes.forEach((node) => {
84                if (isPending(node)) {
85                    observer.observe(node);
86                }
87            });
88        }
89
90        return { hydrate, hydrateWithin, observe, isPending };
91    })();
92
93    function resolveAccordionIcons(container) {
94        const widgetId =
95            container?.id?.replace(/^wiz-accordion-/, "") || "";
96        const icons =
97            widgetId && window.wizAccordionIcons?.[widgetId]
98                ? window.wizAccordionIcons[widgetId]
99                : null;
100
101        return {
102            expanded:
103                typeof icons?.expanded === "string" && icons.expanded
104                    ? icons.expanded
105                    : defaultExpandedSvg,
106            collapsed:
107                typeof icons?.collapsed === "string" && icons.collapsed
108                    ? icons.collapsed
109                    : defaultCollapsedSvg,
110        };
111    }
112
113    function handleAccordionClick() {
114        const content = this.nextElementSibling;
115        if (!content?.classList.contains("wiz-accordion-content")) {
116            return;
117        }
118
119        const icon = this.querySelector(".wiz-accordion-icon");
120        const { expanded: expandedHtml, collapsed: collapsedHtml } = resolveAccordionIcons(
121            this.closest(".wiz-accordion-container")
122        );
123        const isCollapsed =
124            content.style.display === "none" || content.style.display === "";
125
126        if (isCollapsed) {
127            deferredContent.hydrateWithin(content);
128
129            content.style.display = "block";
130            content.style.overflow = "hidden";
131            content.style.transition = "max-height 0.3s ease, padding 0.3s ease";
132            content.style.maxHeight = "0px";
133            content.offsetHeight;
134            content.style.maxHeight = `${content.scrollHeight}px`;
135
136            setTimeout(() => {
137                content.style.maxHeight = "none";
138                content.style.overflow = "";
139                if (typeof applyShowMoreLess === "function") {
140                    applyShowMoreLess(content);
141                }
142            }, 300);
143
144            if (icon) {
145                icon.style.fontSize = "28px";
146                icon.style.color = "#666";
147                icon.innerHTML = expandedHtml;
148            }
149            return;
150        }
151
152        if (content.style.maxHeight === "none" || !content.style.maxHeight) {
153            content.style.maxHeight = `${content.scrollHeight}px`;
154        }
155        content.style.overflow = "hidden";
156        content.style.transition = "max-height 0.3s ease, padding 0.3s ease";
157        content.offsetHeight;
158        content.style.maxHeight = "0px";
159        content.style.paddingTop = "0";
160        content.style.paddingBottom = "0";
161
162        setTimeout(() => {
163            content.style.display = "none";
164            content.style.paddingTop = "";
165            content.style.paddingBottom = "";
166            content.style.maxHeight = "";
167            content.style.overflow = "";
168        }, 300);
169
170        if (icon) {
171            icon.style.fontSize = "28px";
172            icon.style.color = "#666";
173            icon.innerHTML = collapsedHtml;
174        }
175    }
176
177    function initAccordion(root) {
178        const scope = root?.querySelectorAll ? root : document;
179
180        scope.querySelectorAll(".wiz-accordion-header").forEach((header) => {
181            header.removeEventListener("click", handleAccordionCl
181ick);
182            header.addEventListener("click", handleAccordionClick);
183        });
184
185        deferredContent.observe(scope);
186    }
187
188    // Widgets whose selected_attributes control participates in manual ordering.
189    const ORDERABLE_WIDGETS = ["accordion-widget", "product_listing_modern_widget"];
190    // jQuery UI lowercases every sortable event name before emitting it.
191    const SORTABLE_BUBBLING_EVENTS = [
192        "sortcreate", "sortstart", "sort", "sortchange",
193        "sortover", "sortout", "sortactivate", "sortdeactivate",
194        "sortbeforestop", "sortupdate", "sortreceive", "sortremove", "sortstop",
195    ].join(" ");
196
197    // True while one of those widgets' panels is open. The Select2 prototype below is
198    // shared by every Select2 control in the editor, and register_quick_view_controls()
199    // registers the same selected_attributes key for search_modern_widget and
200    // product_by_category_widget — this keeps those two on their existing behaviour.
201    let orderingActive = false;
202
203    function initOrderingScope() {
204        if (typeof elementor === "undefined" || !elementor.hooks) {
205            return;
206        }
207
208        elementor.hooks.addAction("panel/open_editor/widget", (panel, model) => {
209            orderingActive = ORDERABLE_WIDGETS.includes(model && model.get("widgetType"));
210        });
211    }
212
213    function initAttributeOrderRestore() {
214        const Select2View =
215            typeof elementor !== "undefined" &&
216            elementor.modules &&
217            elementor.modules.controls &&
218            elementor.modules.controls.Select2;
219
220        if (!Select2View || Select2View.prototype._wizAttrOrderPatched) {
221            return;
222        }
223        Select2View.prototype._wizAttrOrderPatched = true;
224
225        const applySavedValue = Select2View.prototype.applySavedValue;
226
227        // Elementor renders the <option> list in get_available_attributes_options()
228        // order and then calls .val(savedArray), which marks options selected without
229        // moving them. Since .val() on a multi-select reads back in DOM order, the
230        // saved order would be lost and the next change would overwrite it with the
231        // backend order. Reorder the options here — before the base implementation
232        // applies the value — so every later read matches what is actually saved.
233        Select2View.prototype.applySavedValue = function () {
234            // Scope by the open panel rather than the DOM: this runs during Marionette's
235            // buffered render, while this.$el is still detached, so closest() would find
236            // no ancestors and silently disable the whole restore.
237            if (orderingActive && this.model && this.model.get("name") === "selected_attributes") {
238                const $select = this.$el.find("select[data-setting='selected_attributes']");
239                const savedOrder = this.getControlValue();
240
241                if ($select.length && Array.isArray(savedOrder)) {
242                    savedOrder.forEach((value) => {
243                        $select.find("option").filter(function () {
244                            return this.value === String(value);
245                        }).first().detach().appendTo($select);
246                    });
247                }
248            }
249
250            return applySavedValue.apply(this, arguments);
251        };
252    }
253
254    function initAttributeOrderingControl() {
255        if (typeof elementor === "undefined" || !elementor.hooks || typeof jQuery.fn.sortable !== "function") {
256            return;
257        }
258
259        const selector = ".elementor-control-selected_attributes select[data-setting='selected_attributes']";
260        let orderingObserver = null;
261
262        const initPanel = (panel) => {
263            const makeSortable = ($select) => {
264                const $chips = $select.next(".select2").find(".select2-selection__rendered");
265                if (!$chips.length || $chips.hasClass("ui-sortable")) {
266                    return;
267                }
268
269                // jQuery UI fires these as bubbling DOM events, and the chips sit inside an
270                // Elementor repeater row whose own sortable then treats a chip drag as a row
271                // reorder and throws. That exception aborts jQuery UI's trigger loop before
272                // it reaches the stop callback below, so the reorder is silently lost.
273                // Handlers bound here run in the target phase, before any ancestor sees it.
274                $chips.on(SORTABLE_BUBBLING_EVENTS, (event) => event.stopPropagation());
275
276                $chips.sortable({
277                    items: "> .select2-selection__choice",
278                    start: function () {
279                        // Select2 renders one chip per selected <option> in option DOM order,
280                        // so chip i maps to the i-th selected option. jQuery UI's placeholder
281                        // copies the dragged chip's class and is inserted before this fires,
282                        // so it must be excluded or every later chip shifts by one.
283                        const $selected = $select.find("option:selected");
284                        $chips.find(".select2-selection__choice")
285                            .not(".ui-sortable-placeholder")
286                            .each(function (i) {
287                                jQuery(this).attr("data-wiz-attr-value", $selected.eq(i).val());
288                            });
289                    },
290                    stop: function () {
291                        // Match on value (attribute id) rather than the visible label, so two
292                        // attributes sharing a display name don't both resolve to one option.
293                        $chips.find(".select2-selection__choice").each(function () {
294                            const value = jQuery(this).attr("data-wiz-attr-value");
295                            $select.find("option").filter(function () {
296                                return this.value === value;
297                            }).first().detach().appendTo($select);
298                        });
299                        $select.trigger("change");
300                    },
301                });
302            };
303
304            panel.$el.find(selector).each(function () {
305                makeSortable(jQuery(this));
306            });
307
308            // Repeater row controls are lazy-rendered — a section's <select> only enters
309            // the DOM when that row is expanded, so the scan above misses collapsed rows.
310            // Re-run as rows appear; makeSortable no-ops when chips are absent or sortable
311            // is already attached, so repeated calls are safe.
312            if (orderingObserver) {
313                orderingObserver.disconnect();
314            }
315            if (panel.$el && panel.$el[0]) {
316                orderingObserver = new MutationObserver(() => {
317                    panel.$el.find(selector).each(function () {
318                        makeSortable(jQuery(this));
319                    });
320                });
321                orderingObserver.observe(panel.$el[0], { childList: true, subtree: true });
322            }
323
324            panel.$el.off("select2:select.wizAttributeOrder", selector)
325                .on("select2:select.wizAttributeOrder", selector, function (event) {
326                    jQuery(event.params.data.element).detach().appendTo(this);
327                    jQuery(this).trigger("change");
328                    makeSortable(jQuery(this));
329                });
330        };
331
332        // product_listing_modern_widget registers the same selected_attributes control for
333        // its QuickView accordion, so it needs the same ordering behaviour.
334        ORDERABLE_WIDGETS.forEach((widgetType) => {
335            elementor.hooks.addAction(`panel/open_editor/widget/${widgetType}`, initPanel);
336        });
337    }
338
339    window.initAccordion = initAccordion;
340
341    function boot() {
342        initAccordion(document);
343        // Must run regardless of jquery-ui-sortable, which initAttributeOrderingControl()
344        // requires — the restore below depends on the flag it sets.
345        initOrderingScope();
346        initAttributeOrderRestore();
347        initAttributeOrderingControl();
348
349        if (typeof elementorFrontend !== "undefined") {
350            elementorFrontend.hooks.addAction(
351                "frontend/element_ready/widget",
352                ($scope) => {
353                    if ($scope.find(".wiz-accordion-header").length) {
354                        initAccordion($scope[0]);
355                    }
356                }
357            );
358        }
359    }
360
361    if (document.readyState === "loading") {
362        document.addEventListener("DOMContentLoaded", boot);
363    } else {
364        boot();
365    }
366})();

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.