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.