PageSourceSearch

https://www.viniveglio.com/wp-content/plugins/wc-product-table-lite/assets/js.js?ver=5.7.0

js viniveglio.com collected 2026-09-30 12:19:11 UTC 434,254 bytes, 14,063 lines download raw bytes

1jQuery(function ($) {
2  var cart_button_selector =
3    ".wcpt-button-cart_ajax, .wcpt-button-cart_redirect, .wcpt-button-cart_refresh, .wcpt-button-cart_checkout";
4
5  // local cache
6  window.wcpt_cache = {
7    data: {},
8    remove: function (url) {
9      delete window.wcpt_cache.data[url];
10    },
11    exist: function (url) {
12      return (
13        window.wcpt_cache.data.hasOwnProperty(url) &&
14        window.wcpt_cache.data[url] !== null
15      );
16    },
17    get: function (url) {
18      return window.wcpt_cache.data[url];
19    },
20    set: function (url, cachedData, callback) {
21      window.wcpt_cache.remove(url);
22      window.wcpt_cache.data[url] = cachedData;
23      if ($.isFunction(callback)) callback(cachedData);
24    },
25  };
26
27  window.wcpt_product_form = {}; // product form cache
28
29  function get_device() {
30    // device
31    var device = "laptop"; // default
32
33    if ($(window).width() <= wcpt_params.breakpoints.phone) {
34      device = "phone";
35    } else if ($(window).width() <= wcpt_params.breakpoints.tablet) {
36      device = "tablet";
37    }
38
39    return device;
40  }
41
42  function get_child_row_device_runtime_config(sc_attrs, layoutMap, device) {
43    var raw_child_columns = {};
44    ["laptop", "tablet", "phone"].forEach(function (dev) {
45      var raw = sc_attrs && sc_attrs[dev + "_child_row_columns"];
46      raw_child_columns[dev] = raw ? String(raw).trim() : "";
47    });
48
49    var has_columns = {};
50    ["laptop", "tablet", "phone"].forEach(function (dev) {
51      has_columns[dev] = get_layout_source_for_device(layoutMap, dev) === dev;
52    });
53
54    var parent_of = {
55      phone: "tablet",
56      tablet: "laptop",
57      laptop: null,
58    };
59
60    var parent_row_device = null;
61    var cursor = device;
62    while (cursor !== null) {
63      if (has_columns[cursor]) {
64        parent_row_device = cursor;
65        break;
66      }
67      cursor = parent_of[cursor];
68    }
69
70    var settings_source = null;
71    if (raw_child_columns[device]) {
72      settings_source = device;
73    } else if (has_columns[device]) {
74      settings_source = null;
75    } else if (device === "phone" && raw_child_columns.tablet) {
76      // Keep parity with PHP: phone may inherit tablet child-row columns
77      // only when phone has no dedicated layout columns.
78      settings_source = "tablet";
79    }
80
81    var trigger_position = device === "phone" ? "right" : "left";
82    if (sc_attrs && sc_attrs[device + "_child_row_trigger_position"]) {
83      trigger_position = sc_attrs[device + "_child_row_trigger_position"];
84    }
85
86    return {
87      enabled: !!(settings_source && parent_row_device),
88      parent_row_device: parent_row_device,
89      settings_source: settings_source,
90      trigger_position: trigger_position,
91      source_columns_raw: settings_source
92        ? raw_child_columns[settings_source]
93        : "",
94    };
95  }
96
97  function child_row_device_signature(sc_attrs, layoutMap, device) {
98    var cfg = get_child_row_device_runtime_config(sc_attrs, layoutMap, device);
99    return [
100      cfg.enabled ? "1" : "0",
101      cfg.parent_row_device || "",
102      cfg.settings_source || "",
103      cfg.trigger_position || "",
104      cfg.source_columns_raw || "",
105    ].join("|");
106  }
107
108  function should_refetch_for_child_row_switch(
109    $container,
110    sc_attrs,
111    layoutMap,
112    current_device,
113    desired_device,
114  ) {
115    if (wcpt_is_module_disabled("child-row", $container)) {
116      return false;
117    }
118
119    var current_signature = child_row_device_signature(
120      sc_attrs,
121      layoutMap,
122      current_device,
123    );
124    var desired_signature = child_row_device_signature(
125      sc_attrs,
126      layoutMap,
127      desired_device,
128    );
129
130    return current_signature !== desired_signature;
131  }
132
133  // html entity encode
134  function htmlentity(string) {
135    return string.replace(/[\u00A0-\u9999<>\&]/gim, function (i) {
136      return "&#" + i.charCodeAt(0) + ";";
137    });
138  }
139
140  // layout handler
141  $("body").on("wcpt_layout", ".wcpt", function layout(e, data) {
142    var $wcpt = $(this),
143      $wrap = $wcpt.find(".wcpt-table-scroll-wrapper").first(),
144      id = $wcpt.attr("data-wcpt-table-id"),
145      sc_attrs = wcpt_util.get_sc_attrs($wcpt);
146
147    // Single device wrapper: if the initially rendered device doesn't match the
148    // current breakpoint, replace content with loader and fetch correct device.
149    // (We reuse the existing loader-icon → device_switch flow below.)
150    if ($wrap.length && !$wcpt.hasClass("wcpt-loading")) {
151      var desired_device = get_device();
152      var current_device = wcpt_get_device_2($wcpt);
153      var layout_map = get_column_layout_source_map($wcpt);
154      var desired_src = get_layout_source_for_device(
155        layout_map,
156        desired_device,
157      );
158      var current_src = get_layout_source_for_device(
159        layout_map,
160        current_device,
161      );
162      var is_same_layout_source =
163        desired_src && current_src && desired_src === current_src;
164      var should_refetch_for_child_row = should_refetch_for_child_row_switch(
165        $wcpt,
166        sc_attrs,
167        layout_map,
168        current_device,
169        desired_device,
170      );
171
172      if (
173        desired_device &&
174        current_device &&
175        desired_device !== current_device &&
176        !$(">.wcpt-device-view-loading-icon", $wrap).length
177      ) {
178        var $outer = $wrap.closest(".wcpt-table-scroll-wrapper-outer");
179
180        // If both devices share the same effective column layout, we can usually
181        // skip a server fetch. Exception: child-row markup can still differ by
182        // device (enabled state, trigger side, source columns), so refetch when
183        // child-row signatures differ.
184        if (is_same_layout_source && !should_refetch_for_child_row) {
185          if ($outer.length) {
186            $outer
187              .removeClass("wcpt-device-" + current_device)
188              .addClass("wcpt-device-" + desired_device);
189          }
190          // Do not return: allow freeze-table, sidebar conversion, etc. to run.
191        }
192        // Only show loader + trigger device_switch when effective column
193        // layout differs (i.e. we truly need a fresh device markup).
194        if (!(is_same_layout_source && !should_refetch_for_child_row)) {
195          if ($outer.length) {
196            $outer
197              .removeClass("wcpt-device-" + current_device)
198              .addClass("wcpt-device-" + desired_device);
199          }
200
201          $wrap.html(
202            '<span class="wcpt-icon wcpt-icon-loader wcpt-device-view-loading-icon">' +
203              '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-loader">' +
204              '<line x1="12" y1="2" x2="12" y2="6"></line>' +
205              '<line x1="12" y1="18" x2="12" y2="22"></line>' +
206              '<line x1="4.93" y1="4.93" x2="7.76" y2="7.76"></line>' +
207              '<line x1="16.24" y1="16.24" x2="19.07" y2="19.07"></line>' +
208              '<line x1="2" y1="12" x2="6" y2="12"></line>' +
209              '<line x1="18" y1="12" x2="22" y2="12"></line>' +
210              '<line x1="4.93" y1="19.07" x2="7.76" y2="16.24"></line>' +
211              '<line x1="16.24" y1="7.76" x2="19.07" y2="4.93"></line>' +
212              "</svg>" +
213              "</span>",
214          );
215        }
216      }
217    }
218
219    if ($(">.wcpt-device-view-loading-icon", $wrap).length) {
220      var url = window.location.href,
221        hash = window.location.hash,
222        query_exists = url.indexOf("?") !== -1,
223        query = "",
224        device = get_device();
225
226      if (hash) {
227        url = url.replace(hash, "");
228      }
229
230      if (query_exists) {
231        var replace = "&*" + id + "_device=(laptop|phone|tablet)";
232        re = new RegExp(replace, "gm");
233
234        url = (url.replace(re, "") + "&" + id + "_device=" + device).replace(
235          "?&",
236          "?",
237        );
238      } else if (!sc_attrs.disable_url_update) {
239        url = url + "?" + id + "_device=" + device;
240      }
241
242      if (hash) {
243        url = url + hash;
244      }
245
246      // archive oveerride + compatible nav filter plugin = requires page refresh
247      if ($wcpt.attr("data-wcpt-sc-attrs").indexOf("_only_loop") !== -1) {
248        window.location = url;
249        return;
250      }
251
252      query = url.substr(url.indexOf("?") + 1);
253
254      if (hash) {
255        query = query.replace(hash, "");
256      }
257
258      attempt_ajax($wcpt, query, false, "device_switch");
259
260      return; // layout on AJAX response
261    }
262
263    // freeze tables
264    if (!wcpt_is_module_disabled("freeze-table", $wcpt)) {
265      if (!wcpt_util.get_freeze_table($wcpt)) {
266        var options = { breakpoint: {} },
267          $table = $(".wcpt-table", $wcpt);
268
269        ["laptop", "tablet", "phone"].forEach((device) => {
270          var freeze_left_columns = sc_attrs[`${device}_freeze_left`],
271            freeze_right_columns = sc_attrs[`${device}_freeze_right`],
272            freeze_heading = sc_attrs[`${device}_freeze_heading`],
273            freeze_heading_offset = sc_attrs[`${device}_freeze_heading_offset`]
274              ? sc_attrs[`${device}_freeze_heading_offset`]
275              : sc_attrs[`${device}_scroll_offset`],
276            grab_and_scroll = sc_attrs.grab_and_scroll,
277            table_width = sc_attrs[`${device}_table_width`],
278            breakpoint_settings = {
279              // columns
280              left: freeze_left_columns ? parseInt(freeze_left_columns) : 0,
281              right: freeze_right_columns ? parseInt(freeze_right_columns) : 0,
282
283              // heading
284              // -- toggle
285              heading: !!(freeze_heading && freeze_heading !== "false"),
286              // -- offset
287              offset: freeze_heading_offset ? freeze_heading_offset : 0,
288
289              // grab and scroll
290              grab_and_scroll: !!grab_and_scroll,
291
292              // table width
293              tableWidth: !table_width ? false : table_width,
294            };
295
296          if (device == "laptop") {
297            $.extend(options, breakpoint_settings);
298          } else {
299            options.breakpoint[wcpt_params.breakpoints[device]] =
300              breakpoint_settings;
301          }
302        });
303
304        $table.freezeTable(options, data.source);
305      }
306    }
307
308    // convert sidebar to header
309    var device = get_device(),
310      $sidebar = $(".wcpt-left-sidebar, .wcpt-was-left-sidebar", $wcpt).not(
311        ".wcpt-nav-modal .wcpt-navigation",
312      ),
313      has_reveal_sidebar = !!$wcpt.find(
314        ".wcpt-header .wcpt-reveal-sidebar",
315      ).length;
316
317    // Reveal-sidebar keeps the left panel as an overlay — do not flatten it
318    // into the header strip on tablet/phone (that would also surface duplicates).
319    if (has_reveal_sidebar) {
320      if ($sidebar.hasClass("wcpt-was-left-sidebar")) {
321        $sidebar
322          .removeClass("wcpt-header wcpt-was-left-sidebar")
323          .addClass("wcpt-left-sidebar");
324      }
325    } else if (device == "laptop" && $sidebar.hasClass("wcpt-was-left-sidebar")) {
326      $sidebar
327        .removeClass("wcpt-header wcpt-was-left-sidebar")
328        .addClass("wcpt-left-sidebar");
329    } else if (
330      device != "laptop" &&
331      !$sidebar.hasClass("wcpt-was-left-sidebar")
332    ) {
333      // tablet / phone
334      $sidebar
335        .removeClass("wcpt-left-sidebar")
336        .addClass("wcpt-header wcpt-was-left-sidebar");
337    }
338
339    // close open dropdowns
340    $(".wcpt-was-left-sidebar .wcpt-dropdown", $wcpt).removeClass("wcpt-open");
341
342    // Hide duplicate filters when header + sidebar are flattened together
343    hide_responsive_duplicate_nav_filters($wcpt);
344
345    // checkboxes
346    var $table = wcpt_get_container_original_table($wcpt);
347
348    if (
349      $table.data("wcpt_checked_rows") &&
350      $table.data("wcpt_checked_rows").length
351    ) {
352      var $rows = $(".wcpt-row", $table);
353
354      $rows.each(function () {
355        var $this = $(this),
356          state = !!$this.data("wcpt_checked");
357
358        $this.trigger("_wcpt_checkbox_change", state);
359      });
360    }
361
362    // dropdown direction in single justified navigation header row
363    var $dropdown = $(
364      ".wcpt-filter-row.wcpt-ratio-flex_justified .wcpt-dropdown",
365      $wcpt,
366    );
367
368    $dropdown.each(function () {
369      var $this = $(this),
370        $nav_row = $this.closest(".wcpt-filter-row"),
371        thisOffset = $this.offset().left,
372        navRowWidth = $nav_row.width(),
373        navRowOffset = $nav_row.offset().left,
374        relativePosition = thisOffset - navRowOffset;
375
376      if (relativePosition > navRowWidth / 2) {
377        $this.addClass("wcpt-dropdown-open-left");
378      }
379    });
380  });
381
382  // resize
383  var resize_timer,
384    throttle = 250,
385    window_width;
386
387  // throttled window resize event listener
388  $(window).on("resize", window_resize);
389
390  function window_resize(e) {
391    var new_window_width = window.innerWidth;
392    if (new_window_width != window_width) {
393      window_width = new_window_width;
394      clearTimeout(resize_timer);
395      resize_timer = setTimeout(function () {
396        trigger_layout("resize");
397        recent_orientationchange = false;
398      }, throttle);
399    }
400  }
401
402  function trigger_layout(source) {
403    $(".wcpt").trigger("wcpt_layout", { source: source });
404  }
405
406  // every load - ajax or page load, needs this
407  function after_every_load($container) {
408    // cache new rows
409    var $new_rows = wcpt_util.get_uninit_rows($container, true);
410
411    // cache sc attrs
412    var sc_attrs = wcpt_util.get_sc_attrs($container, true);
413
414    // sortable column headings
415    // -- enable handler
416    wcpt_util.run_once_on_container(
417      $container,
418      "wcpt_sortable_headings_init",
419      function ($container) {
420        $container.on(
421          "click.wcpt_sort_by_column_headings",
422          ".wcpt-heading.wcpt-sortable",
423          window.wcpt_column_heading_sort_handler,
424        );
425      },
426    );
427    // -- add wcpt-sortable class
428    $(".wcpt-heading", $container).each(function () {
429      var $this = $(this);
430      if ($this.find(".wcpt-sorting-icon").length) {
431        $this.addClass("wcpt-sortable");
432      }
433    });
434
435    // init cart form
436    $(".cart", $new_rows).each(function () {
437      var $this__form = $(this);
438
439      // init wc variation form
440      if ($this__form.hasClass("variations_form")) {
441        $this__form.wc_variation_form();
442      }
443
444      // init product addons
445      if (typeof WC_PAO === "object") {
446        new WC_PAO.Form($this__form);
447      } else {
448        if ($.fn.init_addon_totals) {
449          $this__form.init_addon_totals();
450        }
451        if (typeof wcPaoInitAddonTotals === "object") {
452          wcPaoInitAddonTotals.init($this__form);
453        }
454      }
455
456      // cart: action="*same page*"
457      $this__form.attr("action", window.location.href);
458    });
459
460    // cart: wc measurement price calculator init
461    if (typeof wcpt_wc_mc_init_cart !== "undefined") {
462      $(".cart", $new_rows).each(wcpt_wc_mc_init_cart);
463
464      $(".wc-measurement-price-calculator-input-help", $new_rows).tipTip({
465        attribute: "title",
466        defaultPosition: "left",
467      });
468    }
469
470    // ultimate social media icons
471    if (typeof wcpt_sfsi_init !== "undefined") {
472      wcpt_sfsi_init();
473    }
474
475    // snapshot / restore hooks (before prep resets variation + qty UI)
476    $container.trigger("wcpt_before_every_load");
477
478    // trigger variation
479    prep_variation_options($new_rows);
480
481    // checkbox
482    // -- add heading cb
483    var $tables = wcpt_get_container_tables($container);
484
485    $tables.each(function () {
486      var $table = $(this),
487        $heading_row = wcpt_get_table_element(
488          ".wcpt-heading-row",
489          $table,
490        ).last(),
491        $cb = wcpt_get_table_element(
492          ".wcpt-cart-checkbox[data-wcpt-heading-enabled]",
493          $table,
494        ),
495        col_index = [];
496
497      $cb.each(function () {
498        var $this = $(this),
499          _col_index = $this.closest(".wcpt-cell").index();
500
501        if (-1 == col_index.indexOf(_col_index)) {
502          col_index.push(_col_index);
503        }
504      });
505
506      $.each(col_index, function (key, index) {
507        var $heading = $("th", $heading_row).eq(index);
508        $heading_row.removeClass("wcpt-hide"); // in case it was disabled: had no elements
509        if (!$(".wcpt-cart-checkbox-heading", $heading).length) {
510          $heading.prepend(
511            '<input type="checkbox" class="wcpt-cart-checkbox-heading" />',
512          );
513        }
514      });
515    });
516
517    // background color
518    if (sc_attrs.checked_row_background_color) {
519      $("style", $container)
520        .first()
521        .append(
522          "#" +
523            $container.attr("id") +
524            " .wcpt-row--checked .wcpt-cell, #" +
525            $container.attr("id") +
526            " .wcpt-row--checked + .wcpt-child-row   {background: " +
527            sc_attrs.checked_row_background_color +
528            "! important;}",
529        );
530    }
531
532    // multirange
533    $(".wcpt-range-slider", $container).each(function () {
534      wcpt__multirange(this);
535    });
536
537    $(".wcpt-range-input-min", $container).each(function () {
538      var $this = $(this),
539        val = $this.val(),
540        min = parseFloat($this.attr("min"));
541
542      if (val < min) {
543        $this.val(min);
544      }
545    });
546
547    $(".wcpt-range-input-max", $container).each(function () {
548      var $this = $(this),
549        val = parseFloat($this.val()),
550        max = $this.attr("max");
551
552      if (val > max) {
553        $this.val(max);
554      }
555    });
556
557    // wpc smart compare
558    if (typeof wooscpGetCookie == "function" && typeof wooscpVars == "object") {
559      var compare_items__string = wooscpGetCookie(
560        "wooscp_products_" + wooscpVars.user_id,
561      );
562      if (compare_items__string) {
563        var compare_items = compare_items__string.split(",");
564        compare_items.forEach(function (item) {
565          $(".wooscp-btn-" + item, $new_rows).each(function () {
566            var $this = $(this);
567            $this.addClass("wooscp-btn-added");
568            $this.text(wooscpVars.button_text_added);
569          });
570        });
571      }
572    }
573
574    // @TODO: optimize for infinite scroll
575
576    // #start
577
578    // -- nav filter feedback
579    nav_filter_feedback($container.find(".wcpt-navigation"));
580
581    // -- sonaar integration
582    sonaar_player_auto_status();
583
584    // -- hide empty columns
585    hide_empty_columns($container);
586
587    // -- initialize wp media player
588    if (window.wp && window.wp.mediaelement) {
589      window.wp.mediaelement.initialize();
590    }
591
592    // #end
593
594    // url update
595    if (!sc_attrs.disable_url_update) {
596      wcpt_util.update_url(
597        $container.attr("data-wcpt-query-string"),
598        $container,
599      );
600    }
601
602    // trigger after load event
603    $container.trigger("wcpt_after_every_load").trigger("wcpt_after_ajax");
604
605    // register init rows
606    $new_rows.addClass("wcpt-row--init");
607
608    // trigger cart and its view
609    if (window.wcpt_cart_result_cache) {
610      wcpt_cart({
611        payload: {
612          use_cache: true,
613        },
614      });
615    }
616
617    // layout
618    $container.trigger("wcpt_layout", { source: "after_every_load" });
619  }
620
621  function wcpt_get_container_tables($container) {
622    return wcpt_get_shell_element(
623      ".wcpt-table:not(.frzTbl-clone-table)",
624      ".wcpt",
625      $container,
626    );
627  }
628
629  function wcpt_get_table_element(element_selector, $table) {
630    return wcpt_get_shell_element(
631      element_selector,
632      ".wcpt-table:not(.frzTbl-clone-table)",
633      $table,
634    );
635  }
636
637  function wcpt_get_shell_element(element_selector, shell_selector, $shell) {
638    return $(element_selector, $shell).filter(function () {
639      var $this = $(this);
640      return $this.closest(shell_selector).is($shell);
641    });
642  }
643
644  // hide empty columns
645  function hide_empty_columns($container) {
646    var sc_attrs = get_sc_attrs($container);
647
648    if (!sc_attrs.hide_empty_columns) {
649      return;
650    }
651
652    var $table = wcpt_get_container_original_table($container);
653
654    $(".wcpt-cell", $table).removeClass("wcpt-hide");
655    $(".wcpt-heading", $table).removeClass("wcpt-hide");
656    $(".wcpt-child-row__element", $table).removeClass("wcpt-hide");
657
658    var column_indexes = [];
659    $table
660      .find(".wcpt-row:not(.wcpt-child-row)")
661      .first()
662      .find(".wcpt-cell")
663      .each(function () {
664        var col_index = $(this).attr("data-wcpt-column-index");
665        if (typeof col_index !== "undefined") {
666          column_indexes.push(col_index);
667        }
668      });
669
670    column_indexes.forEach(function (col_index) {
671      var $cells = $table.find(
672        '.wcpt-cell[data-wcpt-column-index="' + col_index + '"]',
673      );
674
675      if (
676        $cells.length &&
677        $cells.filter(":empty").length === $cells.length
678      ) {
679        $cells
680          .add(
681            $table.find(
682              '.wcpt-heading[data-wcpt-column-index="' + col_index + '"]',
683            ),
684          )
685          .add(
686            $table.find(".wcpt-child-row__element--column-" + col_index),
687          )
688          .addClass("wcpt-hide");
689      }
690    });
691
692    // Child rows: hide elements with no cell content (headings alone don't count).
693    $table.find(".wcpt-child-row__element").each(function () {
694      var $element = $(this);
695
696      if (wcpt_child_row_element_content_is_empty($element)) {
697        $element.addClass("wcpt-hide");
698      }
699    });
700  }
701
702  function wcpt_child_row_element_content_is_empty($element) {
703    var $content = $element.find(".wcpt-child-row__element__content");
704
705    if ($.trim($content.text())) {
706      return false;
707    }
708
709    return !$content.find(
710      "img, svg, video, iframe, input, button, select, textarea, table, ul, ol",
711    ).length;
712  }
713
714  // lazy load
715  function lazy_load_start() {
716    if (!window.wcpt_lazy_loaded) {
717      $(".wcpt-lazy-load").each(function () {
718        var $this = $(this),
719          table_id = $this.attr("data-wcpt-table-id"),
720          // Prefer data-wcpt-query-string (set by wcpt_get_container_attributes).
721          // Fall back to legacy data-wcpt-query for older markup.
722          query =
723            $this.attr("data-wcpt-query-string") ||
724            $this.attr("data-wcpt-query") ||
725            "";
726
727        if (query.charAt(0) === "?") {
728          query = query.substring(1);
729        }
730
731        $this
732          .addClass("wcpt wcpt-" + table_id)
733          .removeClass("wcpt-lazy-load")
734          .attr("id", "wcpt-" + table_id);
735        window.wcpt_attempt_ajax($this, query, false, "lazy_load");
736      });
737      window.wcpt_lazy_loaded = true;
738    }
739  }
740
741  // get rows including freeze
742  function get_product_rows($elm) {
743    var $row = $elm.closest(".wcpt-row"),
744      product_id = $row.attr("data-wcpt-product-id"),
745      variation_id = $row.attr("data-wcpt-variation-id"),
746      $scroll_wrapper = $elm.closest(".wcpt-table-scroll-wrapper-outer"),
747      row_selector;
748
749    if (variation_id) {
750      row_selector =
751        '[data-wcpt-variation-id="' +
752        variation_id +
753        '"].wcpt-row.wcpt-product-type-variation';
754    } else {
755      row_selector =
756        '[data-wcpt-product-id="' +
757        product_id +
758        '"].wcpt-row:not(.wcpt-product-type-variation)';
759    }
760
761    if ($scroll_wrapper && $scroll_wrapper.length) {
762      return $scroll_wrapper.find(row_selector);
763    }
764
765    return $(row_selector);
766  }
767
768  // button click listener
769  $("body").on("click", ".wcpt-button", button_click_handler);
770
771  function button_click_handler(e) {
772    var $button = $(this),
773      link_code = $button.attr("data-wcpt-link-code"),
774      $product_rows = get_product_rows($button),
775      product_id = $product_rows.attr("data-wcpt-product-id"),
776      is_variable = $product_rows.hasClass("wcpt-product-type-variable"),
777      complete_match = $product_rows.data("wcpt_complete_match"),
778      is_variation = $product_rows.hasClass("wcpt-product-type-variation"),
779      is_composite = $product_rows.hasClass("wcpt-product-type-composite"),
780      is_bundle = $product_rows.hasClass("wcpt-product-type-woosb"),
781      has_addons = $product_rows.hasClass("wcpt-product-has-addons"),
782      has_measurement = $product_rows.hasClass("wcpt-product-has-measurement"),
783      has_nyp = $product_rows.hasClass("wcpt-product-has-name-your-price"),
784      qty = "",
785      params = {
786        payload: {
787          products: {},
788          variations: {},
789          attributes: {},
790          addons: {},
791          measurement: {},
792          nyp: {}, // name your price
793        },
794      };
795
796    if ($("body").hasClass("wcpt-photoswipe-visible")) {
797      e.preventDefault();
798      return;
799    }
800
801    if (
802      -1 !==
803      $.inArray(link_code, [
804        "product_link",
805        "external_link",
806        "custom_field",
807        "custom_field_media_id",
808        "custom_field_acf",
809        "custom",
810      ])
811    ) {
812      return;
813    }
814
815    e.preventDefault();
816
817    // validation
818    // -- variable product
819    if (is_variable) {
820      var variation_found = $product_rows.data("wcpt_variation_found"),
821        variation_selected = $product_rows.data("wcpt_variation_selected"),
822        variation_available = $product_rows.data("wcpt_variation_available"),
823        variation_ops = $product_rows.data("wcpt_variation_ops");
824
825      // if row has variation selection options but customer did not make selections, show error
826      if (
827        variation_ops &&
828        (!variation_selected || !variation_found || !variation_available)
829      ) {
830        return;
831      }
832    }
833
834    // -- disabled
835    if (!is_variable && $button.hasClass("wcpt-disabled")) {
836      return;
837    }
838
839    // -- name your price
840    if (has_nyp) {
841      var $nyp = get_nyp_input_element($product_rows);
842      if ($nyp.length) {
843        var error = false,
844          name = $nyp.attr("data-wcpt-product-name"),
845          min = $nyp.attr("min"),
846          max = $nyp.attr("max");
847
848        if (!$nyp.val()) {
849          error = wcpt_nyp_error_message_templates["empty"];
850        } else if (min && $nyp.val() < parseFloat(min)) {
851          error = wcpt_nyp_error_message_templates["minimum_js"].replace(
852            "%%MINIMUM%%",
853            woocommerce_nyp_format_price(
854              min,
855              woocommerce_nyp_params.currency_format_symbol,
856              true,
857            ),
858          );
859        } else if (max && $nyp.val() > parseFloat(max)) {
860          error = wcpt_nyp_error_message_templates["maximum_js"].replace(
861            "%%MAXIMUM%%",
862            woocommerce_nyp_format_price(
863              max,
864              woocommerce_nyp_params.currency_format_symbol,
865              true,
866            ),
867          );
868        }
869
870        if (error) {
871          alert(error);
872          return;
873        }
874      }
875    }
876
877    // prepare params
878
879    // -- quantity
880    var $wcpt_qty = $(
881        ".wcpt-quantity input.qty, .wcpt-quantity > select.wcpt-qty-select",
882        $product_rows,
883      ),
884      $wc_qty = $(".cart .qty", $product_rows);
885
886    if ($wc_qty.length) {
887      // from WooCommerce form's qty field
888      qty = $wc_qty.val();
889    }
890
891    if ($wcpt_qty.length) {
892      // from WCPT's own qty element
893      var val = parseFloat($wcpt_qty.val());
894      if (isNaN(val) || !parseFloat($wcpt_qty.val())) {
895        $wcpt_qty
896          .filter("input")
897          .first()
898          .each(function () {
899            var $this = $(this),
900              min = $this.attr("data-wcpt-min");
901
902            $this.val(min);
903            limit_qty_controller($this.parent("wcpt-quantity"));
904            val = $this.val();
905          });
906      }
907
908      qty = val;
909    }
910
911    params.payload.products[product_id] = qty;
912
913    // -- addons (only when the addon UI is actually printed & visible in the row;
914    // select variation "attribute dropdowns" reuses cart_form markup and must not
915    // count as having addons already collected)
916    var has_inline_addons_form = wcpt_row_has_inline_addons_form($product_rows),
917      addons = {};
918    if (has_addons && has_inline_addons_form) {
919      addons = wcpt_get_addons($product_rows);
920      if (!$.isEmptyObject(addons)) {
921        params.payload.addons[product_id] = addons;
922      }
923    }
924
925    // -- measurement
926    if (has_measurement) {
927      var measurement = wcpt_get_measurement($product_rows);
928      if (!$.isEmptyObject(measurement)) {
929        params.payload.measurement[product_id] = measurement;
930      }
931    }
932
933    // -- name your price
934    if (has_nyp) {
935      var nyp = wcpt_get_nyp($product_rows);
936      if (nyp) {
937        params.payload.nyp[product_id] = nyp;
938      }
939    }
940
941    // -- variation
942    if (is_variation) {
943      var variation_id = $product_rows.attr("data-wcpt-variation-id"),
944        variation_attributes = JSON.parse(
945          $product_rows.attr("data-wcpt-variation-attributes"),
946        ),
947        $missing_attribute_select = $(
948          ".wcpt-select-variation-attribute-term",
949          $product_rows,
950        );
951
952      if ($missing_attribute_select.length) {
953        $missing_attribute_select.each(function () {
954          var $this = $(this),
955            attribute = $this.attr("data-wcpt-attribute"),
956            term = $this.val();
957
958          if (term) {
959            variation_attributes[attribute] = term;
960          }
961        });
962      }
963
964      if (typeof params.payload.variations[product_id] === "undefined") {
965        params.payload.variations[product_id] = {};
966      }
967      params.payload.variations[product_id][variation_id] = qty;
968      params.payload.attributes[variation_id] = variation_attributes;
969    } else if ($product_rows.hasClass("wcpt-product-type-variable")) {
970      var variation_id = $product_rows.data("wcpt_variation_id"),
971        variation_attributes = $product_rows.data("wcpt_attributes");
972
973      if (variation_id) {
974        if (typeof params.payload.variations[product_id] === "undefined") {
975          params.payload.variations[product_id] = {};
976        }
977        params.payload.variations[product_id][variation_id] = qty;
978      }
979
980      if (variation_attributes) {
981        params.payload.attributes[variation_id] = variation_attributes;
982      }
983    }
984
985    // prepare 'ajax_data' (required for non-AJAX req, submit over POST)
986    var ajax_data = {
987      action: "wcpt_add_to_cart",
988      "add-to-cart": $product_rows.attr("data-wcpt-product-id"),
989      product_id: product_id,
990      quantity: qty,
991    };
992
993    // -- addons
994    if (has_addons && has_inline_addons_form && !$.isEmptyObject(addons)) {
995      $.extend(ajax_data, addons);
996    }
997
998    // -- measurement (submit via post)
999    if (has_measurement) {
1000      var measurement = wcpt_get_measurement($product_rows);
1001      if (!$.isEmptyObject(measurement)) {
1002        $.extend(ajax_data, measurement);
1003      }
1004    }
1005
1006    // -- name your price (submit via post)
1007    if (has_nyp) {
1008      var nyp = wcpt_get_nyp($product_rows);
1009      if (nyp) {
1010        ajax_data.nyp = nyp;
1011      }
1012    }
1013
1014    // -- variation (submit via post)
1015    if (is_variable || is_variation) {
1016      if (variation_id) {
1017        ajax_data.variation_id = variation_id;
1018      }
1019
1020      if (variation_attributes) {
1021        $.extend(ajax_data, variation_attributes);
1022      }
1023    }
1024
1025    // receive notices from server?
1026    ajax_data.return_notice = link_code == "cart_ajax";
1027
1028    // modal required
1029    if (
1030      is_composite ||
1031      is_bundle ||
1032      (is_variable && !complete_match) ||
1033      (is_variation && is_incomplete_variation(variation_attributes)) ||
1034      (has_addons && !has_inline_addons_form) ||
1035      (has_measurement && !params.payload.measurement[product_id]) ||
1036      (has_nyp && !params.payload.nyp[product_id])
1037    ) {
1038      // deploy modal immediately if it's in cache
1039      if (typeof window.wcpt_product_form[product_id] !== "undefined") {
1040        deploy_product_form_modal(
1041          window.wcpt_product_form[product_id],
1042          $button,
1043          ajax_data,
1044        );
1045
1046        // else fetch modal from server and deploy
1047      } else {
1048        ajax_data.action = "wcpt_get_product_form_modal";
1049        ajax_data.lang = wcpt_i18n.lang;
1050        delete ajax_data["add-to-cart"];
1051
1052        $.ajax({
1053          url: wcpt_params.wc_ajax_url.replace(
1054            "%%endpoint%%",
1055            "wcpt_get_product_form_modal",
1056          ),
1057          method: "POST",
1058          beforeSend: function () {
1059            window.wcpt_modal__last_requested_product_id = product_id;
1060            deploy_loading_modal();
1061          },
1062          data: ajax_data,
1063        }).done(function (response) {
1064          window.wcpt_product_form[product_id] = response;
1065
1066          if (product_id === window.wcpt_modal__last_requested_product_id) {
1067            // skip if req. superseded
1068            $(".wcpt-product-form-loading-modal").trigger("wcpt_close"); // close loading modal
1069            deploy_product_form_modal(response, $button, ajax_data);
1070          }
1071        });
1072      }
1073
1074      return false;
1075    }
1076
1077    // all required info already available, no need for modal
1078    if (link_code == "cart_ajax") {
1079      wcpt_cart(params);
1080    } else {
1081      submit_via_post($button.attr("href"), ajax_data);
1082    }
1083  }
1084
1085  function deploy_product_form_modal(markup, $button, ajax_data) {
1086    var $modal = $(markup);
1087    $modal.appendTo("body");
1088    $("body").addClass("wcpt-modal-on");
1089    prep_product_form($modal, $button, ajax_data);
1090    $("body").trigger("wcpt_product_modal_ready");
1091  }
1092
1093  function deploy_loading_modal() {
1094    var $loading_modal = $($("#tmpl-wcpt-product-form-loading-modal").html());
1095    $("body").append($loading_modal);
1096    $loading_modal.on("wcpt_close", function () {
1097      $loading_modal.remove();
1098    });
1099  }
1100
1101  function is_incomplete_variation(variation_attributes) {
1102    var is_incomplete_variation = false;
1103    $.each(variation_attributes, function (key, value) {
1104      if (!value) {
1105        is_incomplete_variation = true;
1106        return false;
1107      }
1108    });
1109
1110    return is_incomplete_variation;
1111  }
1112
1113  function submit_via_post(href, data) {
1114    // redirect by form
1115    var $form = $(
1116      '<form method="POST" action="' +
1117        href +
1118        '" style="display: none;"></form>',
1119    );
1120    $.each(data, function (key, val) {
1121      if (key == "action") return; // continue
1122      var $input = $('<input type="hidden" name="' + key + '" value="" />');
1123      $input.val(val);
1124      $form.append($input);
1125    });
1126    $form.append('<input type="hidden" name="wcpt_request" value="true" />');
1127    $form.appendTo($("body")).submit();
1128  }
1129
1130  function prep_product_form($modal, $button, pre_select) {
1131    var link_code = $button.attr("data-wcpt-link-code"),
1132      href = link_code == "cart_ajax" ? "" : $button.attr("href");
1133
1134    $modal.on("wcpt_close", function () {
1135      $modal.remove();
1136      $("body").removeClass("wcpt-modal-on");
1137    });
1138
1139    $(".cart", $modal).each(function () {
1140      var $form = $(this);
1141
1142      if ($form.hasClass("variations_form")) {
1143        $form.wc_variation_form();
1144      } else {
1145        // simple product (probably with addon or measurement)
1146        $form.append(
1147          '<input name="add-to-cart" type="hidden" value="' +
1148            pre_select["product_id"] +
1149            '">',
1150        );
1151      }
1152
1153      // init addons
1154      if (typeof WC_PAO === "object") {
1155        new WC_PAO.Form($form);
1156      } else {
1157        if ($.fn.init_addon_totals) {
1158          $form.init_addon_totals();
1159        }
1160        if (typeof wcPaoInitAddonTotals === "object") {
1161          wcPaoInitAddonTotals.init($form);
1162        }
1163      }
1164
1165      // init measurement
1166      if (typeof wcpt_wc_mc_init_cart !== "undefined") {
1167        $form.each(wcpt_wc_mc_init_cart);
1168      }
1169
1170      // cart: name your price
1171      if (typeof jQuery.fn.wc_nyp_form !== "undefined") {
1172        $form.wc_nyp_form();
1173      }
1174
1175      $form.attr("action", href);
1176
1177      $(".qty", $form).attr("autocomplete", "off");
1178
1179      if (pre_select) {
1180        $.each(pre_select, function (key, val) {
1181          var $control = $form.find("[name='" + key + "']");
1182          if ($control.is("input.qty")) {
1183            // working on input
1184            val = parseFloat(val);
1185            var min = $control.attr("min")
1186              ? parseFloat($control.attr("min"))
1187              : 0;
1188            var max = $control.attr("max")
1189              ? parseFloat($control.attr("max"))
1190              : false;
1191
1192            // respect min
1193            if (val < min || isNaN(val)) {
1194              val = min;
1195            }
1196
1197            // respect max
1198            if (max && val > max) {
1199              val = max;
1200            }
1201          }
1202          $control.val(val);
1203        });
1204      }
1205
1206      // try and apply quantity on default variation
1207      if (pre_select.quantity) {
1208        $form.one("show_variation", function () {
1209          var $form_qty = $(".qty", $form),
1210            min = $form_qty.attr("min"),
1211            max = $form_qty.attr("max");
1212          if (
1213            (!min || min <= pre_select.quantity) &&
1214            (!max || max >= pre_select.quantity)
1215          ) {
1216            $form_qty.val(pre_select.quantity);
1217          }
1218        });
1219      }
1220
1221      if (link_code == "cart_ajax") {
1222        $form.on("submit", function (e) {
1223          e.preventDefault();
1224
1225          var external_payload = {};
1226
1227          $.each($form.serializeArray(), function (i, field) {
1228            if (typeof external_payload[field.name] === "undefined") {
1229              external_payload[field.name] = field.value;
1230            } else {
1231              // should be array
1232              if (typeof external_payload[field.name] !== "object") {
1233                external_payload[field.name] = [external_payload[field.name]];
1234              }
1235              external_payload[field.name].push(field.value);
1236            }
1237          });
1238
1239          wcpt_cart({
1240            external_payload: external_payload,
1241            payload: { variation_form: true },
1242          });
1243
1244          $modal.trigger("wcpt_close");
1245        });
1246      }
1247
1248      // reset qty in row
1249      var $rows = wcpt_get_sibling_rows($button.closest(".wcpt-row"));
1250      $rows
1251        .find(".qty[data-wcpt-return-to-initial=1]")
1252        .val(0)
1253        .first()
1254        .trigger("change");
1255    });
1256  }
1257
1258  function disable_button($button, add_condition) {
1259    if (add_condition) {
1260      $button.addClass(add_condition);
1261    }
1262
1263    $button.addClass("wcpt-disabled");
1264  }
1265
1266  function enable_button($button, clear_condition) {
1267    if (clear_condition) {
1268      $button.removeClass(clear_condition);
1269    }
1270
1271    if (
1272      // list of conditions
1273      !$button.hasClass("wcpt-all-variations-out-of-stock") &&
1274      !$button.hasClass("wcpt-variation-out-of-stock") &&
1275      !$button.hasClass("wcpt-no-variation-selected") &&
1276      !$button.hasClass("wcpt-quantity-input-error") &&
1277      !$button.hasClass("wcpt-out-of-stock")
1278    ) {
1279      $button.removeClass("wcpt-disabled");
1280    }
1281  }
1282
1283  function loading_badge_on_button($button) {
1284    disable_button($button);
1285    if (!$button.find(".wcpt-cart-badge-refresh").length) {
1286      var svg =
1287        '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-loader" color="#384047"><line x1="12" y1="2" x2="12" y2="6"></line><line x1="12" y1="18" x2="12" y2="22"></line><line x1="4.93" y1="4.93" x2="7.76" y2="7.76"></line><line x1="16.24" y1="16.24" x2="19.07" y2="19.07"></line><line x1="2" y1="12" x2="6" y2="12"></line><line x1="18" y1="12" x2="22" y2="12"></line><line x1="4.93" y1="19.07" x2="7.76" y2="16.24"></line><line x1="16.24" y1="7.76" x2="19.07" y2="4.93"></line></svg>';
1288      $button.append('<i class="wcpt-cart-badge-refresh">' + svg + "</i>");
1289    }
1290  }
1291
1292  function add_count_badge_to_button(in_cart, $button) {
1293    if (!parseFloat(in_cart)) {
1294      $(".wcpt-cart-badge-number, .wcpt-cart-badge-refresh", $button).remove();
1295      return;
1296    }
1297
1298    // if( $button.closest('.wcpt-row').hasClass('wcpt-sold-individually') ){
1299    //   in_cart = '✓';
1300    // }
1301
1302    if (!$button.find(".wcpt-cart-badge-number").length) {
1303      $button.append('<i class="wcpt-cart-badge-number">' + in_cart + "</i>");
1304    } else {
1305      $button.find(".wcpt-cart-badge-number").html(in_cart);
1306    }
1307
1308    if ($button.find(".wcpt-cart-badge-refresh").length) {
1309      $button.find(".wcpt-cart-badge-refresh").remove();
1310    }
1311  }
1312
1313  // search
1314  // -- submit
1315  $("body").on("click", ".wcpt-search-submit", search_submit);
1316  $("body").on("keydown", ".wcpt-search-input", search_submit);
1317  function search_submit(e) {
1318    var $this = $(this),
1319      $search = $this.closest(".wcpt-search"),
1320      $input = $search.find(".wcpt-search-input"),
1321      table_id = $search.attr("data-wcpt-table-id"),
1322      $container = $("#wcpt-" + table_id),
1323      $nav_modal = $this.closest(".wcpt-nav-modal"),
1324      $nav = $this.closest(".wcpt-navigation"),
1325      keywords = $input.val().trim(),
1326      previous_keywords = $input.attr("data-wcpt-value"),
1327      query = $input.attr("name") + "=" + encodeURIComponent(keywords),
1328      $wrapper = $input.closest(".wcpt-search-wrapper"),
1329      append = !$wrapper.hasClass("wcpt-search--reset-others");
1330
1331    if (previous_keywords === keywords) {
1332      return;
1333    }
1334
1335    if (
1336      // submit button is clicked
1337      ($(e.target).closest(".wcpt-search-submit").length &&
1338        e.type == "click") || // enter key pressed on input
1339      ($(e.target).is(".wcpt-search-input") &&
1340        e.type == "keydown" &&
1341        (e.keyCode == 13 || e.which == 13))
1342    ) {
1343      if ($nav_modal.length) {
1344        $(".wcpt-nm-apply").click();
1345        return;
1346      }
1347
1348      if (append) {
1349        $nav.trigger("change");
1350      } else {
1351        // reset other filters
1352        attempt_ajax($container, query, append, "filter");
1353      }
1354
1355      if ($nav_modal.length) {
1356        $nav_modal.trigger("wcpt_close");
1357      }
1358    }
1359  }
1360  // -- clear
1361  $("body").on("click", ".wcpt-search-clear", function (e) {
1362    var $this = $(this),
1363      $search = $this.closest(".wcpt-search"),
1364      $input = $search.find(".wcpt-search-input"),
1365      table_id = $search.attr("data-wcpt-table-id"),
1366      $container = $("#wcpt-" + table_id),
1367      $nav_modal = $this.closest(".wcpt-nav-modal"),
1368      query = "&" + $input.attr("name") + "=",
1369      append = true;
1370    $input.val("");
1371
1372    if ($nav_modal.length) {
1373      $(".wcpt-nm-apply").click();
1374      return;
1375    }
1376
1377    attempt_ajax($container, query, append, "filter");
1378
1379    if ($nav_modal.length) {
1380      $nav_modal.trigger("wcpt_close");
1381    }
1382  });
1383
1384  // download button - responsive fix
1385  if (wcpt_params.initial_device !== "laptop") {
1386    $("body").on("click", ".wcpt-button[download]", function (e) {
1387      e.preventDefault();
1388      var $this = $(this),
1389        url = $this.attr("href");
1390
1391      if (!url) {
1392        return;
1393      }
1394
1395      // Masked stream URLs: download in a hidden iframe to avoid a blank tab
1396      // and keep the real remote URL off the address bar.
1397      if (url.indexOf("wcpt_stream=") !== -1) {
1398        var iframe = document.createElement("iframe");
1399        iframe.style.display = "none";
1400        iframe.setAttribute("aria-hidden", "true");
1401        iframe.src = url;
1402        document.body.appendChild(iframe);
1403        setTimeout(function () {
1404          if (iframe.parentNode) {
1405            iframe.parentNode.removeChild(iframe);
1406          }
1407        }, 120000);
1408        return;
1409      }
1410
1411      window.open(url, "_blank", false);
1412    });
1413  }
1414
1415  // dropdown / tooltip
1416
1417  // currently assigned trigger ev
1418  // -- default
1419  window.wcpt_global_tooltip_trigger_mode = "hover"; // hover (default) | click
1420  // -- switch to click
1421  $(window).on("touchstart", function () {
1422    window.wcpt_global_tooltip_trigger_mode = "click";
1423  });
1424  // -- switch to hover
1425  $(window).on("resize", function () {
1426    window.wcpt_global_tooltip_trigger_mode = "hover";
1427  });
1428
1429  var target_selector =
1430      ".wcpt-dropdown, .wcpt-tooltip:not(.wcpt-tooltip--inside-portal)",
1431    $body = $("body");
1432
1433  $body.on("mouseenter", target_selector, dropdown_hover_open);
1434  $body.on("mouseleave", target_selector, dropdown_hover_close);
1435  $body.on("click", dropdown_touch_toggle);
1436
1437  function get_event_type_for_dropdown($dropdown) {
1438    var is_tooltip = !!$dropdown.closest(".wcpt-tooltip").length,
1439      is_in_sidebar = !!$dropdown.closest(".wcpt-left-sidebar").length;
1440
1441    // click ev type is forced
1442    if (
1443      // -- in dropdown options
1444      $dropdown.hasClass("wcpt-tooltip--open-on-click") ||
1445      // -- we are on touchscreen
1446      wcpt_global_tooltip_trigger_mode == "click"
1447    ) {
1448      return "click";
1449    }
1450
1451    // filters in sidebar alway use click
1452    if (!is_tooltip && is_in_sidebar) {
1453      return "click";
1454    }
1455
1456    return "hover";
1457  }
1458
1459  function dropdown_hover_open(e) {
1460    var $this = $(this);
1461
1462    if ("hover" !== get_event_type_for_dropdown($this)) {
1463      return;
1464    }
1465
1466    // hover intent
1467    // if( $this.hasClass('wcpt-tooltip--hover-intent-enabled') ){
1468    var clear_timeout = setTimeout(function () {
1469      // if( ! $this.is(':hover') ) return;
1470      $this.addClass("wcpt-open");
1471      fix_tooltip_position($this);
1472    }, 200);
1473
1474    $this.data("wcpt_hover_intent_clear_timeout", clear_timeout);
1475  }
1476
1477  function dropdown_hover_close(e) {
1478    var $this = $(this);
1479
1480    if ("hover" !== get_event_type_for_dropdown($this)) {
1481      return;
1482    }
1483
1484    // hover intent
1485    var clear_timeout = $this.data("wcpt_hover_intent_clear_timeout");
1486    if (clear_timeout) {
1487      clearTimeout(clear_timeout);
1488    }
1489
1490    $this.removeClass("wcpt-open");
1491  }
1492
1493  function dropdown_touch_toggle(e) {
1494    var $target = $(e.target),
1495      container_selector = ".wcpt-dropdown, .wcpt-tooltip",
1496      content_selector =
1497        "> .wcpt-dropdown-menu, > .wcpt-tooltip-content-wrapper > .wcpt-tooltip-content",
1498      $dropdown = $target.closest(container_selector),
1499      $content = $dropdown.find(content_selector),
1500      $body = $("body");
1501
1502    // clicked outside any tootltip / filter
1503    if (!$dropdown.length) {
1504      // close all dropdowns or tooltips not in nav modal or sidebar
1505      if (!$target.closest(".wcpt-nav-modal").length) {
1506        $body.find(container_selector).each(function () {
1507          if (
1508            $(this).hasClass("wcpt-tooltip") ||
1509            (!$(this).closest(".wcpt-nav-modal").length &&
1510              !$(this).closest(".wcpt-left-sidebar").length)
1511          ) {
1512            $(this).removeClass("wcpt-open");
1513          }
1514        });
1515      }
1516
1517      return;
1518    }
1519
1520    if ("click" !== get_event_type_for_dropdown($dropdown)) {
1521      return;
1522    }
1523
1524    // clicked in dropdown
1525    if ($dropdown.length) {
1526      // -- clicked in content
1527      if ($target.closest($content).length) {
1528        // return;
1529        // -- clicked on trigger
1530      } else {
1531        // close all others
1532
1533        // -- except parents and other open sidebar filters
1534        var $parents = $dropdown.parents(container_selector),
1535          $sidebar_filters = $(".wcpt-left-sidebar  .wcpt-dropdown");
1536
1537        $body
1538          .find(container_selector)
1539          .not($dropdown.add($parents).add($sidebar_filters))
1540          .removeClass("wcpt-open");
1541
1542        $dropdown.toggleClass("wcpt-open");
1543
1544        // close all children
1545        if (!$dropdown.hasClass("wcpt-open")) {
1546          $dropdown.find(container_selector).removeClass("wcpt-open");
1547        }
1548
1549        // popup enabled
1550        if ($dropdown.hasClass("wcpt-tooltip--popup-enabled")) {
1551          if ($dropdown.hasClass("wcpt-open")) {
1552            // need to run open logic earlier
1553            $body.addClass("wcpt-tooltip-popup-displayed");
1554          } else {
1555            $body.removeClass("wcpt-tooltip-popup-displayed");
1556          }
1557        }
1558      }
1559
1560      fix_tooltip_position($dropdown);
1561    }
1562  }
1563
1564  function fix_tooltip_position($tooltip) {
1565    // correct position
1566    var $content = $tooltip.find(
1567        " > .wcpt-dropdown-menu, > .wcpt-tooltip-content-wrapper > .wcpt-tooltip-content",
1568      ),
1569      content_width = $content.outerWidth(false),
1570      offset_left = $content.offset().left,
1571      page_width = $(window).width();
1572
1573    if ($tooltip.hasClass("wcpt-tooltip")) {
1574      // tooltip
1575
1576      // if tooltip is already in portal, don't move it
1577      if ($tooltip.closest(".wcpt-tooltip-portal").length) {
1578        return;
1579      }
1580
1581      // narrow width
1582      $content.css("max-width", "");
1583      var $container, margin;
1584
1585      if ($tooltip.hasClass("wcpt-tooltip--popup-enabled")) {
1586        $container = $("body");
1587        margin = 40;
1588      } else {
1589        $container = $tooltip.closest(".wcpt-table-scroll-wrapper-outer").length
1590          ? $tooltip.closest(".wcpt-table-scroll-wrapper-outer")
1591          : $("body");
1592        margin = 20;
1593      }
1594
1595      var container_rect = $.extend({}, $container[0].getBoundingClientRect()),
1596        content_rect = $content[0].getBoundingClientRect();
1597
1598      var $freezeTable = $(".frzTbl-table", $container).not(
1599        ".frzTbl-clone-table",
1600      );
1601
1602      if ($content.closest($freezeTable).length) {
1603        // target is inside the original freeze_table
1604        var $left_freeze_column =
1605            $freezeTable.data("freezeTable").el.$frozenColumnsLeft,
1606          $right_freeze_column =
1607            $freezeTable.data("freezeTable").el.$frozenColumnsRight;
1608
1609        container_rect.left += $left_freeze_column.width();
1610        container_rect.right -= $right_freeze_column.width();
1611        container_rect.width =
1612          container_rect.width -
1613          $left_freeze_column.width() -
1614          $right_freeze_column.width();
1615      }
1616
1617      if (container_rect.width < parseInt(content_rect.width) + margin * 2) {
1618        $content.css("max-width", container_rect.width - margin * 2);
1619      }
1620
1621      // vertical position
1622      var $content_wrapper = $content.parent(),
1623        content_wrapper_width = $content_wrapper.width(),
1624        viewport_height = $(window).height();
1625
1626      $content_wrapper.attr("data-wcpt-position", "");
1627      content_rect = $content[0].getBoundingClientRect(); // refresh
1628
1629      // Check if tooltip would go above viewport or container top
1630      if (content_rect.top - 150 < 0) {
1631        // Position above if not enough space below
1632      } else {
1633        $content.parent().attr("data-wcpt-position", "above");
1634      }
1635
1636      // // Check if tooltip would go below viewport or container bottom
1637      // if (
1638      //   content_rect.bottom + 30 > viewport_height ||
1639      //   container_rect.bottom < content_rect.bottom + 30
1640      // ) {
1641      //   // Position above if not enough space below
1642      //   $content.parent().attr("data-wcpt-position", "above");
1643      // }
1644
1645      // horizontal position
1646      $content.css({ left: "", right: "" });
1647      content_rect = $content[0].getBoundingClientRect(); // refresh
1648
1649      var arrow_margin = 20; // how close arrow gets to left/ right edge
1650
1651      if (content_rect.left - 15 < container_rect.left) {
1652        // excess left
1653        var left = 15 + container_rect.left - content_rect.left,
1654          limit = content_wrapper_width / 2 - arrow_margin;
1655
1656        if (left > limit) {
1657          left = limit;
1658        }
1659
1660        $content.css("left", left);
1661      } else if (content_rect.right + 15 > container_rect.right) {
1662        // excess right
1663        var right = content_rect.right - container_rect.right + 15,
1664          limit = content_wrapper_width / 2 - arrow_margin;
1665
1666        if (right > limit) {
1667          right = limit;
1668        }
1669
1670        $content.css("right", right);
1671      }
1672    } else {
1673      // dropdown
1674      if (content_width + 30 > page_width) {
1675        $content.outerWidth(page_width - 30);
1676        var content_width = $content.outerWidth(false);
1677      }
1678
1679      if ($content.offset().left + content_width > page_width) {
1680        // offscreen right
1681        var offset_required =
1682          $content.offset().left + content_width - page_width;
1683        $content.css("left", "-=" + (offset_required + 15));
1684      } else if ($content.offset().left < 0) {
1685        // offscreen left
1686        $content.css("left", Math.abs($content.offset().left - 15));
1687      }
1688    }
1689
1690    // tooltip arrow
1691    if ($tooltip.hasClass("wcpt-tooltip")) {
1692      var $label = $tooltip.find("> .wcpt-tooltip-label"),
1693        offset_left = $label.offset().left,
1694        width = $label.outerWidth(),
1695        $arrow = $("> .wcpt-tooltip-arrow", $content);
1696
1697      $arrow.css(
1698        "left",
1699        offset_left - $content.offset().left + width / 2 + "px",
1700      );
1701    }
1702
1703    // place tooltip in portal
1704    if (
1705      $tooltip.hasClass("wcpt-tooltip") &&
1706      $container.hasClass("wcpt-table-scroll-wrapper-outer")
1707    ) {
1708      var container_rect = $container[0].getBoundingClientRect();
1709      var content_rect = $content[0].getBoundingClientRect();
1710
1711      var $row = $tooltip.closest(".wcpt-row, .wcpt-heading-row"),
1712        $cell = $tooltip.closest(".wcpt-cell, .wcpt-heading");
1713
1714      $container.children(".wcpt-tooltip-portal").remove();
1715
1716      // Get all data attributes from the original row
1717      var rowAttributes = "";
1718      $.each($row[0].attributes, function (i, attr) {
1719        if (attr.name.startsWith("data-")) {
1720          rowAttributes += " " + attr.name + '="' + attr.value + '"';
1721        }
1722      });
1723
1724      var $portal = $(
1725        '<div class="wcpt-tooltip-portal">' +
1726          '<div class="wcpt-table">' +
1727          '<div class="' +
1728          $row.attr("class") +
1729          '"' +
1730          rowAttributes +
1731          ">" +
1732          '<div class="' +
1733          $cell.attr("class") +
1734          '">' +
1735          '<div class="wcpt-item-row"></div>' +
1736          "</div>" +
1737          "</div>" +
1738          "</div>" +
1739          "</div>",
1740      );
1741      $portal
1742        .find(".wcpt-item-row")
1743        .html($tooltip.clone().addClass("wcpt-tooltip--inside-portal"));
1744
1745      $portal.find(".wcpt-row").data($row.data());
1746
1747      var wrapper_rect = $content_wrapper[0].getBoundingClientRect();
1748      $portal.find(".wcpt-tooltip-content-wrapper").css({
1749        position: "fixed",
1750        top: wrapper_rect.top + "px",
1751        left: wrapper_rect.left + "px",
1752      });
1753
1754      var content_rect = $content[0].getBoundingClientRect();
1755
1756      var $portal_content = $portal.find(".wcpt-tooltip-content");
1757      $portal_content.css({
1758        width: content_rect.width + "px",
1759        transform: "translateY(4px)",
1760        opacity: 0,
1761      });
1762      $content.css({
1763        display: "none",
1764      });
1765      $container.append($portal);
1766
1767      // animate
1768      setTimeout(function () {
1769        $portal_content.css({
1770          transform: "translateY(0px)",
1771          opacity: 1,
1772        });
1773      }, 1);
1774
1775      var $sticky_table_scroll_wrapper = $tooltip.closest(
1776        ".frzTbl-table-wrapper",
1777      );
1778
1779      // Generate unique namespace for this tooltip instance
1780      var portalNamespace =
1781        "wcpt_portal_" + Math.random().toString(36).substr(2, 9);
1782
1783      function remove_portal() {
1784        // Remove all event handlers using unique namespace
1785        $container.off("mousemove." + portalNamespace);
1786        $(window).off("scroll." + portalNamespace);
1787        $container.off("scroll." + portalNamespace);
1788        $("body").off("click." + portalNamespace);
1789        $sticky_table_scroll_wrapper.off("scroll." + portalNamespace);
1790
1791        $portal.remove();
1792        $content.css("display", "");
1793        $tooltip.removeClass("wcpt-open");
1794      }
1795
1796      // Store namespace on portal element for future reference
1797      $portal.data("portalNamespace", portalNamespace);
1798
1799      // remove portal based on
1800      // -- mouse hover
1801      if (!$tooltip.hasClass("wcpt-tooltip--open-on-click")) {
1802        $container.on("mousemove." + portalNamespace, function (e) {
1803          var $target = $(e.target);
1804          if (
1805            !$target.closest($portal).length &&
1806            !$target.closest($tooltip).length
1807          ) {
1808            remove_portal();
1809          }
1810        });
1811      } else {
1812        // -- click outside
1813        $("body").on("click." + portalNamespace, function (e) {
1814          if (!$(e.target).closest($portal).length) {
1815            remove_portal();
1816          }
1817        });
1818      }
1819      // -- scroll
1820      $(window).on("scroll." + portalNamespace, function () {
1821        remove_portal();
1822      });
1823      $container.on("scroll." + portalNamespace, function () {
1824        remove_portal();
1825      });
1826      $sticky_table_scroll_wrapper.on("scroll." + portalNamespace, function () {
1827        remove_portal();
1828      });
1829    }
1830  }
1831
1832  // tooltip with content hover disabled
1833  $("body").on("click mouseover", ".wcpt-tooltip-content", function () {
1834    var $this = $(this),
1835      $tooltip = $this.closest(".wcpt-tooltip");
1836
1837    if ($tooltip.hasClass("wcpt-tooltip--hover-disabled")) {
1838      $tooltip.removeClass("wcpt-open");
1839    }
1840  });
1841
1842  // close dropdown when grab and scroll starts
1843  $("body").on("freeze_table__grab_and_scroll__start", function () {
1844    $(
1845      ".wcpt-navigation:not(.wcpt-left-sidebar) .wcpt-dropdown.wcpt-open",
1846    ).removeClass("wcpt-open");
1847  });
1848
1849  // Unique key for matching the same filter/search across header + sidebar
1850  function get_nav_filter_dedupe_key($el) {
1851    if ($el.hasClass("wcpt-search-wrapper") || $el.hasClass("wcpt-search")) {
1852      var $input = $el
1853        .find(".wcpt-search-input")
1854        .addBack(".wcpt-search-input")
1855        .first();
1856      var name = $input.attr("name") || "";
1857      return name ? "search:" + name : "";
1858    }
1859
1860    if (!$el.hasClass("wcpt-filter") && !$el.is(".wcpt-filter")) {
1861      return "";
1862    }
1863
1864    var filter = $el.attr("data-wcpt-filter");
1865    if (!filter) {
1866      return "";
1867    }
1868
1869    return (
1870      filter +
1871      "|" +
1872      ($el.attr("data-wcpt-taxonomy") || "") +
1873      "|" +
1874      ($el.attr("data-wcpt-meta-key") || "")
1875    );
1876  }
1877
1878  // Match other instances of the same filter across header / sidebar / etc.
1879  function find_duplicate_filters($filter, $nav) {
1880    var filter = $filter.attr("data-wcpt-filter");
1881    if (!filter) {
1882      return $();
1883    }
1884
1885    var selector = '.wcpt-filter[data-wcpt-filter="' + filter + '"]',
1886      taxonomy = $filter.attr("data-wcpt-taxonomy"),
1887      meta_key = $filter.attr("data-wcpt-meta-key");
1888
1889    if (taxonomy) {
1890      selector += '[data-wcpt-taxonomy="' + taxonomy + '"]';
1891    }
1892    if (meta_key) {
1893      selector += '[data-wcpt-meta-key="' + meta_key + '"]';
1894    }
1895
1896    return $nav.find(selector);
1897  }
1898
1899  // When phone/tablet flattens header + sidebar into one strip, hide later
1900  // copies of the same filter so each filter only appears once.
1901  function hide_responsive_duplicate_nav_filters($wcpt) {
1902    $wcpt
1903      .find(".wcpt-nav-duplicate-hidden")
1904      .removeClass("wcpt-nav-duplicate-hidden");
1905
1906    if (get_device() === "laptop") {
1907      return;
1908    }
1909
1910    // Only needed when the sidebar was converted into the combined header strip
1911    if (!$wcpt.find(".wcpt-was-left-sidebar").length) {
1912      return;
1913    }
1914
1915    var seen = {};
1916    $wcpt
1917      .find(".wcpt-navigation-wrapper")
1918      .find(".wcpt-filter, .wcpt-search-wrapper")
1919      .each(function () {
1920        var $el = $(this);
1921        if ($el.closest(".wcpt-nav-modal-tpl, .wcpt-nav-modal").length) {
1922          return;
1923        }
1924
1925        var key = get_nav_filter_dedupe_key($el);
1926        if (!key) {
1927          return;
1928        }
1929
1930        if (seen[key]) {
1931          $el.addClass("wcpt-nav-duplicate-hidden");
1932        } else {
1933          seen[key] = true;
1934        }
1935      });
1936  }
1937
1938  // Keep first of each unique filter/search; drop later duplicates from a set
1939  function dedupe_nav_elements($set) {
1940    var seen = {};
1941    return $set.filter(function () {
1942      var key = get_nav_filter_dedupe_key($(this));
1943      if (!key) {
1944        return true;
1945      }
1946      if (seen[key]) {
1947        return false;
1948      }
1949      seen[key] = true;
1950      return true;
1951    });
1952  }
1953
1954  // Copy checked/value state from one filter instance onto its duplicates
1955  function sync_duplicate_filters_from($source_filter, $nav) {
1956    var $duplicates = find_duplicate_filters($source_filter, $nav).not(
1957      $source_filter,
1958    );
1959    if (!$duplicates.length) {
1960      return;
1961    }
1962
1963    $source_filter
1964      .find('input[type="checkbox"], input[type="radio"]')
1965      .each(function () {
1966        var $src = $(this),
1967          value = $src.attr("value"),
1968          checked = $src.prop("checked"),
1969          type = $src.attr("type");
1970
1971        $duplicates.each(function () {
1972          $(this)
1973            .find('input[type="' + type + '"]')
1974            .filter(function () {
1975              return $(this).attr("value") === value;
1976            })
1977            .each(function () {
1978              var $input = $(this),
1979                $option = $input.closest(".wcpt-option, .wcpt-dropdown-option"),
1980                $label = $input.closest("label");
1981
1982              $input.prop("checked", checked);
1983
1984              if ($option.length) {
1985                $option.toggleClass("wcpt-active", !!checked);
1986              }
1987              if ($label.length) {
1988                $label.toggleClass("wcpt-active", !!checked);
1989              }
1990            });
1991        });
1992      });
1993
1994    // Range / date text inputs (same class names across copies)
1995    [
1996      ".wcpt-range-input-min",
1997      ".wcpt-range-input-max",
1998      ".wcpt-filter-date-picker--start-date",
1999      ".wcpt-filter-date-picker--end-date",
2000    ].forEach(function (sel) {
2001      var $src_inputs = $source_filter.find(sel);
2002      if (!$src_inputs.length) {
2003        return;
2004      }
2005
2006      $src_inputs.each(function (i) {
2007        var val = $(this).val();
2008        $duplicates.each(function () {
2009          var $dup_input = $(this).find(sel).eq(i);
2010          if ($dup_input.length) {
2011            $dup_input.val(val);
2012          }
2013        });
2014      });
2015    });
2016  }
2017
2018  // apply nav filters
2019  $("body").on("change", ".wcpt-navigation", navigation_change);
2020  function navigation_change(e) {
2021    var $target = $(e.target),
2022      $container = $target.closest(".wcpt"),
2023      // Header + left sidebar are nested under .wcpt-navigation-wrapper /
2024      // .wcpt-nav-main, so they are no longer siblings — collect all of them.
2025      $nav = $(
2026        "> .wcpt-navigation, > .wcpt-navigation-wrapper .wcpt-navigation",
2027        $container,
2028      );
2029
2030    // skip search filter options input
2031    if ($target.closest(".wcpt-search-filter-options").length) {
2032      return;
2033    }
2034
2035    // skip date pickers
2036    if ($target.closest(".wcpt-filter-date-picker").length) {
2037      return;
2038    }
2039
2040    // taxonomy hierarchy
2041    if ($target.closest(".wcpt-hierarchy").length) {
2042      var checked = $target.prop("checked");
2043
2044      // effect on child terms
2045      if ($target.hasClass("wcpt-hr-parent-term")) {
2046        var ct_selector = "input[type=checkbox], input[type=radio]",
2047          $child_terms = $target
2048            .closest("label")
2049            .siblings(".wcpt-hr-child-terms-wrapper")
2050            .find(ct_selector);
2051        $child_terms.prop("checked", false);
2052      }
2053
2054      // effect on parent terms
2055      var $ancestors = $target.parents(".wcpt-hr-child-terms-wrapper");
2056      if ($ancestors.length) {
2057        $ancestors.each(function () {
2058          var $parent_term = $(this)
2059            .siblings("label")
2060            .find(".wcpt-hr-parent-term");
2061          $parent_term.prop("checked", false);
2062        });
2063      }
2064    }
2065
2066    // range filter
2067    if ($target.closest(".wcpt-range-filter")) {
2068      // -- number and range inputs shouldn't cause filtering
2069      if (
2070        $target.hasClass("wcpt-range-input-min") ||
2071        $target.hasClass("wcpt-range-input-max") ||
2072        $target.hasClass("wcpt-range-slider")
2073      ) {
2074        // Still sync typed values onto duplicate filter copies
2075        var $range_source = $target.closest(".wcpt-filter");
2076        if ($range_source.length) {
2077          sync_duplicate_filters_from($range_source, $nav);
2078        }
2079        return;
2080      }
2081
2082      // apply selected radio range onto the range slider inputs
2083      if ($target.is("input[type=radio]")) {
2084        var min = $target.attr("data-wcpt-range-min") || "",
2085          max = $target.attr("data-wcpt-range-max") || "",
2086          $range_filter = $target.closest(".wcpt-range-filter"),
2087          $min = $range_filter.find(".wcpt-range-input-min"),
2088          $max = $range_filter.find(".wcpt-range-input-max"),
2089          $range_slider = $range_filter.find(".wcpt-range-slider.original");
2090
2091        $min.val(min);
2092        $max.val(max);
2093        if (!min) {
2094          min = $range_slider.attr("min");
2095        }
2096        if (!max) {
2097          max = $range_slider.attr("max");
2098        }
2099
2100        $range_slider.val(min + "," + max);
2101      }
2102    }
2103
2104    // Keep duplicate copies of the same filter in sync (e.g. header + sidebar).
2105    // Without this, serialize() still picks up a checked option from the other
2106    // copy after deselection, so the filter stays applied.
2107    var $source_filter = $target.closest(".wcpt-filter");
2108    if ($source_filter.length) {
2109      sync_duplicate_filters_from($source_filter, $nav);
2110    }
2111
2112    // return if current control doesn't trigger immediate filtering
2113    // -- search
2114    if ($target.closest(".wcpt-search").length) {
2115      return;
2116    }
2117
2118    // -- nav modal
2119    if ($target.closest(".wcpt-nav-modal").length) {
2120      return;
2121    }
2122
2123    // nav clone
2124    var table_id = $container.attr("id").substring(5),
2125      $nav_clone = $nav.clone();
2126
2127    nav_clone_operations($nav_clone);
2128
2129    // build query
2130    var query = $("<form>").append($nav_clone).serialize();
2131
2132    // include column sort
2133    if (!$(e.target).closest('[data-wcpt-filter="sort_by"]').length) {
2134      var $table = wcpt_get_container_original_table($container),
2135        $sortable_headings = $(".wcpt-heading.wcpt-sortable:visible", $table),
2136        $current_sort_col = $sortable_headings.filter(function () {
2137          return $(this).find(
2138            ".wcpt-sorting-icons.wcpt-sorting-asc, .wcpt-sorting-icons.wcpt-sorting-desc",
2139          ).length;
2140        });
2141      if ($current_sort_col.length) {
2142        var sort_id = $current_sort_col.attr("data-wcpt-sort-id"),
2143          order = $current_sort_col.find(".wcpt-sorting-icons.wcpt-sorting-asc")
2144            .length
2145            ? "ASC"
2146            : "DESC",
2147          $sorting_icons = $current_sort_col
2148            .find(".wcpt-sorting-icons")
2149            .first(),
2150          column_orderby = $sorting_icons.attr("data-wcpt-orderby") || "",
2151          column_meta_key = $sorting_icons.attr("data-wcpt-meta-key") || "",
2152          column_orderby_attribute =
2153            $sorting_icons.attr("data-wcpt-orderby-attribute") || "",
2154          column_orderby_taxonomy =
2155            $sorting_icons.attr("data-wcpt-orderby-taxonomy") || "";
2156        // Skip incomplete sort ids (e.g. "column_" with no index) — those
2157        // produce undefined orderby warnings on the server during filter AJAX.
2158        if (
2159          sort_id &&
2160          sort_id !== "column_" &&
2161          sort_id.indexOf("column_") === 0
2162        ) {
2163          // Prefer explicit heading criteria when available
2164          if (column_orderby) {
2165            sort_id = "column_heading";
2166          }
2167
2168          query +=
2169            "&" +
2170            table_id +
2171            "_orderby=" +
2172            sort_id +
2173            "&" +
2174            table_id +
2175            "_order=" +
2176            order;
2177
2178          if (column_orderby) {
2179            query +=
2180              "&" +
2181              table_id +
2182              "_column_orderby=" +
2183              encodeURIComponent(column_orderby);
2184            query +=
2185              "&" +
2186              table_id +
2187              "_column_meta_key=" +
2188              encodeURIComponent(column_meta_key);
2189            if (column_orderby_attribute) {
2190              query +=
2191                "&" +
2192                table_id +
2193                "_column_orderby_attribute=" +
2194                encodeURIComponent(column_orderby_attribute);
2195            }
2196            if (column_orderby_taxonomy) {
2197              query +=
2198                "&" +
2199                table_id +
2200                "_column_orderby_taxonomy=" +
2201                encodeURIComponent(column_orderby_taxonomy);
2202            }
2203          }
2204        }
2205      }
2206    }
2207
2208    nav_filter_feedback($nav);
2209
2210    var data = {
2211      callback: function () {
2212        attempt_ajax($container, query, false, "filter", e);
2213      },
2214      proceed: true,
2215      event: e,
2216    };
2217
2218    $container.trigger("wcpt_after_navigation_change", data);
2219
2220    if (data.proceed) {
2221      data.callback();
2222    }
2223  }
2224
2225  function nav_clone_operations($nav_clone) {
2226    var $reverse_check = $();
2227
2228    $("[data-wcpt-reverse-value]:not(:checked)", $nav_clone).each(function () {
2229      var $this = $(this);
2230      $this.attr("value", $this.attr("data-wcpt-reverse-value"));
2231      $this.prop("checked", "checked");
2232      $reverse_check = $reverse_check.add($this.clone());
2233    });
2234    $nav_clone = $nav_clone.add($reverse_check);
2235
2236    // get values from duplicate radio buttons and clean up the names
2237    $('input[type="radio"]', $nav_clone).each(function () {
2238      var $this = $(this),
2239        name = $this.attr("name");
2240
2241      if (-1 !== name.indexOf("--")) {
2242        var is_array = name.indexOf("[]");
2243        name = name.substr(0, name.indexOf("--")) + (is_array ? "[]" : "");
2244        $this.attr("name", name);
2245      }
2246    });
2247
2248    // remove redundant range values
2249    $nav_clone.find(".wcpt-range-options-wrapper").each(function () {
2250      var $this = $(this),
2251        $min_input = $this.find(".wcpt-range-input-min"),
2252        $max_input = $this.find(".wcpt-range-input-max"),
2253        $range_slider = $this.find(".wcpt-range-slider"),
2254        $filter = $this.closest(".wcpt-filter"),
2255        $selected_option = $filter.find("input[type=radio]:checked");
2256
2257      // enforce option selected by user
2258      if ($selected_option.length && $selected_option.attr("value")) {
2259        var $selected_option_min = $selected_option.attr("data-wcpt-range-min"),
2260          $selected_option_max = $selected_option.attr("data-wcpt-range-max");
2261
2262        $min_input.val($selected_option_min);
2263        $max_input.val($selected_option_max);
2264        return;
2265      }
2266
2267      var prev_min = $min_input.attr("value"),
2268        prev_max = $max_input.attr("value");
2269
2270      $min_input.each(function () {
2271        var $this = $(this),
2272          min = $this.attr("min"),
2273          current_value = $this.val();
2274        if (min == current_value) {
2275          $this.val(prev_min);
2276        }
2277      });
2278
2279      $max_input.each(function () {
2280        var $this = $(this),
2281          max = $this.attr("max"),
2282          current_value = $this.val();
2283        if (max == current_value) {
2284          $this.val(prev_max);
2285        }
2286      });
2287
2288      $range_slider.remove();
2289    });
2290  }
2291
2292  function nav_filter_feedback($nav) {
2293    // header nav
2294    $(".wcpt-filter", $nav.filter(".wcpt-header")).each(function () {
2295      var $this = $(this),
2296        filter = $this.attr("data-wcpt-filter"),
2297        $filter = $this.closest(".wcpt-filter"),
2298        format = $this.attr("data-wcpt-heading_format__op_selected"),
2299        radio =
2300          $this.find("input[type=radio]").length ||
2301          $this.hasClass("wcpt-range-filter"),
2302        checkbox = $this.find("input[type=checkbox]").length,
2303        $selected = $this.find("input[type=radio]:checked"),
2304        $checked = $this.find("input[type=checkbox]:checked"),
2305        checked_count = $checked.length,
2306        $active_count = $this.find(".wcpt-active-count"),
2307        radio_permit = false,
2308        label_append = "",
2309        $multi_range = $(".wcpt-range-options-wrapper", $filter),
2310        $multi_range__min = $(
2311          ".wcpt-range-options-wrapper .wcpt-range-input-min",
2312          $filter,
2313        ),
2314        $multi_range__max = $(
2315          ".wcpt-range-options-wrapper .wcpt-range-input-max",
2316          $filter,
2317        ),
2318        in_filter_grid = !!$this.closest(".wcpt-ratio-filter_grid").length;
2319
2320      // Skip options-row filters outside filter grid (counts aren't used there)
2321      if ($this.hasClass("wcpt-options-row") && !in_filter_grid) {
2322        return;
2323      }
2324
2325      if (
2326        -1 ==
2327        $.inArray(filter, [
2328          "custom_field",
2329          "attribute",
2330          "category",
2331          "taxonomy",
2332          "price_range",
2333          "rating",
2334          "sort_by",
2335          "results_per_page",
2336          "on_sale",
2337          "availability",
2338        ])
2339      ) {
2340        return;
2341      }
2342
2343      // mark active filters
2344      if (
2345        checked_count ||
2346        ($selected.val() &&
2347          !$selected.closest(".wcpt-default-option").length) || // sort by
2348        ($multi_range.length &&
2349          ($multi_range__min.val() != $multi_range__min.attr("min") ||
2350            $multi_range__max.val() != $multi_range__max.attr("max")))
2351      ) {
2352        $this.closest(".wcpt-filter").addClass("wcpt-filter--active");
2353      } else {
2354        $this.closest(".wcpt-filter").removeClass("wcpt-filter--active");
2355      }
2356
2357      // modify dropdown heading
2358
2359      // -- filter grid: radio filters show (0)/(1) count like checkboxes
2360      if (
2361        radio &&
2362        in_filter_grid &&
2363        filter !== "sort_by" &&
2364        filter !== "results_per_page"
2365      ) {
2366        $this.find(".wcpt-radio-op-selected__heading-append").remove();
2367        $active_count.remove();
2368
2369        var radio_count = 0;
2370        if ($selected.length && $selected.attr("value")) {
2371          // Non-"show all" option selected
2372          radio_count = 1;
2373        } else if (
2374          $multi_range.length &&
2375          ($multi_range__min.val() != $multi_range__min.attr("min") ||
2376            $multi_range__max.val() != $multi_range__max.attr("max"))
2377        ) {
2378          radio_count = 1;
2379        }
2380
2381        var radio_count_class =
2382          "wcpt-active-count wcpt-active-count--filter-grid";
2383        if (!radio_count) {
2384          radio_count_class += " wcpt-active-count--zero";
2385        }
2386
2387        $active_count = $(
2388          '<span class="' + radio_count_class + '">' + radio_count + "</span>",
2389        );
2390        $(
2391          ".wcpt-filter-heading .wcpt-dropdown-label, .wcpt-filter-heading .wcpt-options-heading",
2392          $this,
2393        ).after($active_count);
2394
2395        // -- radio append option label
2396      } else if (radio && format !== "only_heading") {
2397        $this.find(".wcpt-radio-op-selected__heading-append").remove();
2398
2399        if (
2400          !$selected.length ||
2401          !$selected.attr("value") // 'show all' op selected
2402        ) {
2403          $this.removeClass("wcpt-radio-op-selected");
2404        } else {
2405          // selected and has value
2406          $this.addClass("wcpt-radio-op-selected");
2407          label_append = $selected.next()[0].outerHTML;
2408          radio_permit = true;
2409        }
2410
2411        if (!$selected.length && filter == "price_range") {
2412          var min =
2413              wcpt_params.currency_symbol +
2414                $(".wcpt-range-input-min", $this).val() || 0,
2415            max =
2416              wcpt_params.currency_symbol +
2417                $(".wcpt-range-input-max", $this).val() || 0;
2418
2419          label_append = "<span>" + min + " - " + max + "<span>";
2420
2421          if (
2422            $(".wcpt-range-input-min", $this).val() !=
2423              $(".wcpt-range-input-min", $this).attr("min") ||
2424            $(".wcpt-range-input-max", $this).val() !=
2425              $(".wcpt-range-input-max", $this).attr("max")
2426          ) {
2427            $this.addClass("wcpt-radio-op-selected");
2428            radio_permit = true;
2429          }
2430        } else if (!$selected.length && filter == "custom_field") {
2431          var min = $(".wcpt-range-input-min", $this).val() || 0,
2432            max = $(".wcpt-range-input-max", $this).val() || 0;
2433
2434          label_append = "<span>" + min + " - " + max + "<span>";
2435
2436          if (
2437            min != $(".wcpt-range-input-min", $this).attr("min") ||
2438            max != $(".wcpt-range-input-max", $this).attr("max")
2439          ) {
2440            $this.addClass("wcpt-radio-op-selected");
2441            radio_permit = true;
2442          }
2443        } else if ($selected.length && filter == "sort_by") {
2444          var $icon = $this.find(".wcpt-dropdown-label > .wcpt-icon");
2445          if ($icon.length) {
2446            label_append =
2447              $icon[0].outerHTML + " " + $selected.next()[0].outerHTML;
2448
2449            $icon.remove();
2450          }
2451        }
2452
2453        if (radio_permit) {
2454          $this
2455            .find(".wcpt-dropdown-label")
2456            .append(
2457              '<div class="wcpt-radio-op-selected__heading-append">' +
2458                label_append +
2459                "</div>",
2460            );
2461        }
2462
2463        // -- checkbox append selected option count
2464      } else if (checkbox) {
2465        $active_count.remove();
2466
2467        // In filter grid rows, always show the count (including 0)
2468        // so empty checkbox dropdowns still read as (0).
2469        var always_show_count = in_filter_grid;
2470
2471        if (checked_count || always_show_count) {
2472          var active_count_class = "wcpt-active-count";
2473          var active_count_text = checked_count;
2474
2475          if (always_show_count) {
2476            active_count_class += " wcpt-active-count--filter-grid";
2477
2478            if (!checked_count) {
2479              active_count_class += " wcpt-active-count--zero";
2480            }
2481          }
2482
2483          $active_count = $(
2484            '<span class="' +
2485              active_count_class +
2486              '">' +
2487              active_count_text +
2488              "</span>",
2489          );
2490          $(
2491            ".wcpt-filter-heading .wcpt-dropdown-label, .wcpt-filter-heading .wcpt-options-heading",
2492            $this,
2493          ).after($active_count);
2494        }
2495      }
2496    });
2497  }
2498
2499  // submit range by enter
2500  $("body").on(
2501    "keyup",
2502    ".wcpt-range-input-min, .wcpt-range-input-max",
2503    function (e) {
2504      var $this = $(this),
2505        $filters = $this.closest(".wcpt-navigation"),
2506        code = e.keyCode ? e.keyCode : e.which;
2507
2508      if (code == 13) {
2509        var $filter = $this.closest(".wcpt-filter"),
2510          $container = $this.closest(".wcpt"),
2511          $nav = $(
2512            "> .wcpt-navigation, > .wcpt-navigation-wrapper .wcpt-navigation",
2513            $container,
2514          );
2515        if ($filter.length) {
2516          sync_duplicate_filters_from($filter, $nav);
2517        }
2518        $filters.trigger("change");
2519      }
2520    },
2521  );
2522
2523  // submit range
2524  $("body").on("click", ".wcpt-range-submit-button", function (e) {
2525    var $this = $(this),
2526      $range_wrapper = $this.closest(".wcpt-range-options-wrapper"),
2527      $min = $(".wcpt-range-input-min", $range_wrapper),
2528      $max = $(".wcpt-range-input-max", $range_wrapper),
2529      min_val = parseFloat($min.val()),
2530      max_val = parseFloat($max.val());
2531
2532    // ensure max stays greater than min
2533    if (max_val < min_val) {
2534      $max.val(min_val);
2535      $min.val(max_val);
2536    }
2537
2538    $this.trigger("change");
2539  });
2540
2541  // date picker
2542
2543  // -- submit
2544  $("body").on("click", ".wcpt-date-picker-submit-button", function (e) {
2545    var $this = $(this),
2546      $filter = $this.closest('[data-wcpt-filter="date_picker"]'),
2547      $start_date = $(".wcpt-filter-date-picker--start-date", $filter),
2548      $end_date = $(".wcpt-filter-date-picker--end-date", $filter),
2549      $filters = $this.closest(".wcpt-navigation"),
2550      $container = $this.closest(".wcpt"),
2551      $nav = $(
2552        "> .wcpt-navigation, > .wcpt-navigation-wrapper .wcpt-navigation",
2553        $container,
2554      );
2555
2556    if ($start_date.val() && $end_date.val()) {
2557      var start_date = new Date($start_date.val()),
2558        end_date = new Date($end_date.val());
2559
2560      if (start_date > end_date) {
2561        var start_date_val = $start_date.val(),
2562          end_date_val = $end_date.val();
2563
2564        $start_date.val(end_date_val);
2565        $end_date.val(start_date_val);
2566      }
2567    }
2568
2569    if ($filter.length) {
2570      sync_duplicate_filters_from($filter, $nav);
2571    }
2572    $filters.trigger("change");
2573  });
2574  // -- -- enter key
2575  $("body").on("keydown", ".wcpt-filter-date-picker", function (e) {
2576    if (e.keyCode === 13 || e.which === 13) {
2577      var $this = $(this),
2578        $filters = $this.closest(".wcpt-navigation"),
2579        $filter = $this.closest(".wcpt-filter"),
2580        $container = $this.closest(".wcpt"),
2581        $nav = $(
2582          "> .wcpt-navigation, > .wcpt-navigation-wrapper .wcpt-navigation",
2583          $container,
2584        );
2585
2586      if ($filter.length) {
2587        sync_duplicate_filters_from($filter, $nav);
2588      }
2589      $filters.trigger("change");
2590    }
2591  });
2592
2593  // -- reset
2594  $("body").on("click", ".wcpt-date-picker-reset-button", function (e) {
2595    e.preventDefault();
2596
2597    var $this = $(this),
2598      $filter = $this.closest('[data-wcpt-filter="date_picker"]'),
2599      $date_picker = $(".wcpt-filter-date-picker", $filter),
2600      $filters = $this.closest(".wcpt-navigation"),
2601      $container = $this.closest(".wcpt"),
2602      $nav = $(
2603        "> .wcpt-navigation, > .wcpt-navigation-wrapper .wcpt-navigation",
2604        $container,
2605      );
2606
2607    $date_picker.val("");
2608    if ($filter.length) {
2609      sync_duplicate_filters_from($filter, $nav);
2610    }
2611    $filters.trigger("change");
2612  });
2613
2614  // -- validate
2615  $("body").on("keyup", ".wcpt-filter-date-picker", function (e) {
2616    var $this = $(this);
2617    if (
2618      $this.val().length > 10 ||
2619      -1 == $.inArray($this.val()[0], ["1", "2"])
2620    ) {
2621      $this.css("border-color", "red");
2622    } else {
2623      $this.css("border-color", "");
2624    }
2625  });
2626
2627  // clear filter
2628  $("body").on("click", ".wcpt-clear-filter", function (e) {
2629    var $clear_filter = $(this),
2630      $target = $(e.target);
2631
2632    if ($target.closest(".wcpt-dropdown-menu")) {
2633      var $sub_option = $target.closest(".wcpt-dropdown-option");
2634    } else {
2635      $sub_option = false;
2636    }
2637
2638    var $container = $clear_filter.closest(".wcpt"),
2639      filter = $clear_filter.attr("data-wcpt-filter"),
2640      $navs = $(
2641        "> .wcpt-navigation, >
2641 .wcpt-navigation-wrapper .wcpt-navigation",
2642        $container,
2643      ),
2644      $inputs = $();
2645
2646    if (filter == "search") {
2647      var name = $clear_filter.attr("data-wcpt-search-name"),
2648        $inputs = $('.wcpt-search-input[name="' + name + '"]', $navs);
2649    } else if (
2650      filter == "attribute" ||
2651      filter == "category" ||
2652      filter == "taxonomy"
2653    ) {
2654      var taxonomy = $clear_filter.attr("data-wcpt-taxonomy"),
2655        term = $clear_filter.attr("data-wcpt-value"),
2656        $inputs = $navs
2657          .find(
2658            '.wcpt-filter[data-wcpt-filter="' +
2659              filter +
2660              '"][data-wcpt-taxonomy="' +
2661              taxonomy +
2662              '"]',
2663          )
2664          .find('input[value="' + term + '"]');
2665    } else if (filter == "custom_field") {
2666      var meta_key = $clear_filter.attr("data-wcpt-meta-key"),
2667        value = $clear_filter.attr("data-wcpt-value"),
2668        $filter = $navs.find(
2669          '.wcpt-filter[data-wcpt-filter="' +
2670            filter +
2671            '"][data-wcpt-meta-key="' +
2672            meta_key +
2673            '"]',
2674        );
2675
2676      if ($filter.hasClass("wcpt-range-filter")) {
2677        $inputs = $filter.find("input");
2678      } else {
2679        $inputs = $navs
2680          .find(
2681            '.wcpt-filter[data-wcpt-filter="' +
2682              filter +
2683              '"][data-wcpt-meta-key="' +
2684              meta_key +
2685              '"]',
2686          )
2687          .find('input[value="' + value + '"]');
2688      }
2689    } else if (filter == "price_range") {
2690      $inputs = $navs
2691        .find(
2692          '.wcpt-filter[data-wcpt-filter="custom_field"][data-wcpt-meta-key="_price"]',
2693        )
2694        .find("input");
2695    } else if (filter == "search") {
2696      $inputs = $navs.find(
2697        'input[type=search][data-wcpt-value="' +
2698          htmlentity($clear_filter.attr("data-wcpt-value")) +
2699          '"]',
2700      );
2701    } else if (filter == "rating") {
2702      $inputs = $navs
2703        .find('.wcpt-filter[data-wcpt-filter="rating"]')
2704        .find("input");
2705    } else if (filter == "date_picker") {
2706      var val = $clear_filter.attr("data-wcpt-value"),
2707        $inputs = $navs
2708          .find('.wcpt-filter[data-wcpt-filter="date_picker"]')
2709          .find('input[value="' + val + '"]');
2710    } else {
2711      var value = $clear_filter.attr("data-wcpt-value"),
2712        $inputs = $navs
2713          .find('.wcpt-filter[data-wcpt-filter="' + filter + '"]')
2714          .find('input[value="' + value + '"]');
2715    }
2716
2717    $inputs
2718      .filter(":input[type=checkbox], :input[type=radio]")
2719      .prop("checked", false)
2720      .closest("label.wcpt-active")
2721      .removeClass("wcpt-active");
2722
2723    $inputs
2724      .filter(
2725        ":input[type=text], :input[type=number], :input[type=search], :input[type=date]",
2726      )
2727      .val(""); // search and range input
2728
2729    $navs.first().trigger("change");
2730
2731    // remove clear filter
2732    if (!$clear_filter.siblings(".wcpt-clear-filter").length) {
2733      $clear_filter.closest(".wcpt-clear-filters-wrapper").remove();
2734    } else {
2735      $clear_filter.remove();
2736    }
2737  });
2738
2739  // clear all filters
2740  $("body").on(
2741    "click",
2742    ".wcpt-clear-filters, .wcpt-clear-all-filters, .wcpt-reset",
2743    function (e) {
2744      e.preventDefault();
2745      var $this = $(this),
2746        $container = $this.closest(".wcpt"),
2747        query = "",
2748        append = false,
2749        purpose = "filter";
2750
2751      if (!$this.hasClass("wcpt-disabled")) {
2752        attempt_ajax($container, query, append, purpose);
2753      }
2754    },
2755  );
2756
2757  // sort by column heading
2758  window.wcpt_column_heading_sort_handler = function (e) {
2759    var $this = $(this),
2760      $sorting = $this.find(".wcpt-sorting-icons");
2761
2762    if ($(e.target).closest(".wcpt-tooltip").length) {
2763      return;
2764    }
2765
2766    if (!$sorting.length) {
2767      return;
2768    }
2769
2770    // maintain scroll left upon column header sorting
2771    if ($this.closest(".frzTbl").length) {
2772      var $container = $this.closest(".wcpt"),
2773        $scrollOverlay = $this
2774          .closest(".frzTbl-content-wrapper")
2775          .siblings(".frzTbl-scroll-overlay"),
2776        scrollLeft = $scrollOverlay[0].scrollLeft;
2777
2778      $("body").one(
2779        "after_freeze_table_build",
2780        "#" + $container.attr("id") + " .frzTbl-table",
2781        function (e, frzTbl) {
2782          frzTbl.el.$scrollOverlay[0].scrollLeft = scrollLeft;
2783        },
2784      );
2785    }
2786
2787    var order = $sorting.hasClass("wcpt-sorting-asc") ? "desc" : "asc",
2788      sort_id = $this.attr("data-wcpt-sort-id") || "",
2789      $container = $this.closest(".wcpt"),
2790      table_id = $container.attr("id").substring(5),
2791      device = get_device(),
2792      // Explicit criteria from the heading icons — preferred server-side
2793      // source of truth (does not rely on column_{index} resolution).
2794      column_orderby = $sorting.attr("data-wcpt-orderby") || "",
2795      column_meta_key = $sorting.attr("data-wcpt-meta-key") || "",
2796      column_orderby_attribute =
2797        $sorting.attr("data-wcpt-orderby-attribute") || "",
2798      column_orderby_taxonomy =
2799        $sorting.attr("data-wcpt-orderby-taxonomy") || "";
2800
2801    if ($(".wcpt-sorting-" + order + "-icon", $sorting).hasClass("wcpt-hide")) {
2802      if (
2803        $(".wcpt-sorting-" + order + "-icon", $sorting)
2804          .siblings()
2805          .hasClass("wcpt-active")
2806      ) {
2807        return;
2808      } else {
2809        order = order == "asc" ? "desc" : "asc";
2810      }
2811    }
2812
2813    // When the heading icons expose explicit sort criteria, use a stable
2814    // orderby token and send those criteria as the source of truth. This
2815    // avoids fragile column_{index} resolution (layout / original_index mismatch).
2816    if (column_orderby) {
2817      sort_id = "column_heading";
2818    }
2819
2820    if (!sort_id || sort_id === "column_") {
2821      return;
2822    }
2823
2824    var query =
2825      table_id +
2826      "_orderby=" +
2827      encodeURIComponent(sort_id) +
2828      "&" +
2829      table_id +
2830      "_order=" +
2831      order +
2832      "&" +
2833      table_id +
2834      "_device=" +
2835      device +
2836      "&" +
2837      table_id +
2838      "_paged=1";
2839
2840    if (column_orderby) {
2841      query +=
2842        "&" +
2843        table_id +
2844        "_column_orderby=" +
2845        encodeURIComponent(column_orderby);
2846      // Always send meta_key key (even empty) so server can clear stale keys
2847      // when switching away from a custom-field sort.
2848      query +=
2849        "&" +
2850        table_id +
2851        "_column_meta_key=" +
2852        encodeURIComponent(column_meta_key);
2853      if (column_orderby_attribute) {
2854        query +=
2855          "&" +
2856          table_id +
2857          "_column_orderby_attribute=" +
2858          encodeURIComponent(column_orderby_attribute);
2859      }
2860      if (column_orderby_taxonomy) {
2861        query +=
2862          "&" +
2863          table_id +
2864          "_column_orderby_taxonomy=" +
2865          encodeURIComponent(column_orderby_taxonomy);
2866      }
2867    }
2868
2869    attempt_ajax($container, query, true, "column_heading_sort");
2870  };
2871
2872  // ajax
2873  var attempt_ajax = (window.wcpt_attempt_ajax = (
2874    $container,
2875    new_query,
2876    append,
2877    purpose,
2878  ) => {
2879    const query = build_ajax_query_string(
2880      $container,
2881      new_query,
2882      append,
2883      purpose,
2884    );
2885
2886    // trigger body event to hook in
2887    var data = {
2888      query: query,
2889      $wcpt: $container,
2890      purpose: purpose,
2891      proceed: true,
2892    };
2893
2894    var prev_nav_query = $container.attr("data-wcpt-nav-query");
2895
2896    if (prev_nav_query === query) {
2897      data.proceed = false;
2898    }
2899
2900    $("body").trigger("wcpt_before_ajax_query", data);
2901
2902    $container.attr("data-wcpt-nav-query", data.query);
2903
2904    if (data.proceed) {
2905      fetch_markup_and_apply_callback(
2906        $container,
2907        data.query,
2908        purpose,
2909        ajax_success,
2910      );
2911    }
2912  });
2913
2914  function get_response_container(response) {
2915    var $response = $(response),
2916      $responseContainer = $response.filter(".wcpt").first();
2917
2918    if (!$responseContainer.length) {
2919      $responseContainer = $response.find(".wcpt").first();
2920    }
2921
2922    return $responseContainer.length ? $responseContainer : $response;
2923  }
2924
2925  function sync_container_attributes($container, $new_container) {
2926    if (!$new_container.length || !$new_container[0].attributes) {
2927      return;
2928    }
2929
2930    [...$new_container[0].attributes].forEach((attr) => {
2931      if (
2932        attr.nodeName === "class" ||
2933        attr.nodeName.substr(0, 10) === "data-wcpt-"
2934      ) {
2935        $container[0].setAttribute(attr.nodeName, attr.nodeValue);
2936      }
2937    });
2938  }
2939
2940  function replace_paginated_content($container, $new_container) {
2941    var $target_scope = $container.find(".wcpt-nav-main").first(),
2942      $source_scope = $new_container.find(".wcpt-nav-main").first(),
2943      content_selector = [
2944        ".wcpt-table-scroll-wrapper-outer",
2945        ".wcpt-pagination",
2946        ".wcpt-no-results",
2947        ".wcpt-in-footer",
2948        ".wcpt-infinite-scroll-dots",
2949        ".wcpt-add-selected-to-cart-with-pagination",
2950        ".wcpt-loading-screen",
2951      ].join(", "),
2952      previous_min_height,
2953      lock_height,
2954      $fragment;
2955
2956    if (!$target_scope.length) {
2957      $target_scope = $container;
2958    }
2959
2960    if (!$source_scope.length) {
2961      $source_scope = $new_container;
2962    }
2963
2964    // Keep height while swapping results so the page does not collapse and
2965    // jump the viewport to the top before the new table is inserted.
2966    previous_min_height = $target_scope[0].style.minHeight;
2967    lock_height = $target_scope.outerHeight();
2968    if (lock_height) {
2969      $target_scope.css("min-height", lock_height + "px");
2970    }
2971
2972    $fragment = $(document.createDocumentFragment());
2973    $source_scope.children().each(function () {
2974      var $child = $(this);
2975      if ($child.is(content_selector)) {
2976        $fragment.append($child.clone(true, true));
2977      }
2978    });
2979
2980    $target_scope.children(content_selector).remove();
2981    $target_scope.append($fragment);
2982    $target_scope.css("min-height", previous_min_height || "");
2983
2984    // Paginate-only responses keep the outer container; merge variable-switch
2985    // price/availability templates so variation resets can restore prices.
2986    if (
2987      typeof wcpt_util !== "undefined" &&
2988      wcpt_util.merge_variable_switch_templates_from_response
2989    ) {
2990      wcpt_util.merge_variable_switch_templates_from_response(
2991        $new_container,
2992        $container,
2993      );
2994    }
2995  }
2996
2997  // Header pagination lives inside preserved .wcpt-navigation, so update it
2998  // separately after the footer/table swap on paginate-only responses.
2999  function update_header_pagination($container, $new_container) {
3000    var $newPagination = $new_container
3001      .find(
3002        ".wcpt-add-selected-to-cart-with-pagination .wcpt-pagination, .wcpt-pagination",
3003      )
3004      .first();
3005
3006    if (!$newPagination.length) {
3007      return;
3008    }
3009
3010    $container.find(".wcpt-navigation .wcpt-pagination").each(function () {
3011      $(this).replaceWith($newPagination.clone(true, true));
3012    });
3013  }
3014
3015  function replace_result_count_placeholders(template, context) {
3016    if (typeof template !== "string") {
3017      return "";
3018    }
3019
3020    [
3021      ["displayed_results", context.displayed_results || 0],
3022      ["total_results", context.total_results || 0],
3023      ["first_result", context.first_result || 0],
3024      ["last_result", context.last_result || 0],
3025    ].forEach(function (entry) {
3026      var key = entry[0],
3027        value = String(entry[1]);
3028
3029      template = template
3030        .split("[" + key + "]")
3031        .join(value)
3032        .split("[wcpt_" + key + "]")
3033        .join(value);
3034    });
3035
3036    return template;
3037  }
3038
3039  function update_result_count($container) {
3040    var contextAttr = $container.attr("data-wcpt-result-count-context"),
3041      $resultCounts = $container.find(".wcpt-result-count");
3042
3043    if (!contextAttr || !$resultCounts.length) {
3044      return;
3045    }
3046
3047    var context = {};
3048    try {
3049      context = JSON.parse(contextAttr);
3050    } catch (e) {
3051      return;
3052    }
3053
3054    $resultCounts.each(function () {
3055      var $resultCount = $(this);
3056
3057      $resultCount.removeClass(
3058        "wcpt-single-result wcpt-no-results wcpt-single-page",
3059      );
3060
3061      if (context.html_class) {
3062        $resultCount.addClass(context.html_class);
3063      }
3064
3065      $resultCount
3066        .find(".wcpt-result-message")
3067        .text(
3068          replace_result_count_placeholders(
3069            $resultCount.attr("data-wcpt-message-template"),
3070            context,
3071          ),
3072        );
3073
3074      $resultCount
3075        .find(".wcpt-single-page-message")
3076        .text(
3077          replace_result_count_placeholders(
3078            $resultCount.attr("data-wcpt-single-page-template"),
3079            context,
3080          ),
3081        );
3082
3083      $resultCount
3084        .find(".wcpt-single-result-message")
3085        .text(
3086          replace_result_count_placeholders(
3087            $resultCount.attr("data-wcpt-single-result-template"),
3088            context,
3089          ),
3090        );
3091
3092      $resultCount
3093        .find(".wcpt-no-results-message")
3094        .text(
3095          replace_result_count_placeholders(
3096            $resultCount.attr("data-wcpt-no-results-template"),
3097            context,
3098          ),
3099        );
3100    });
3101  }
3102
3103  // ajax successful
3104  function ajax_success(response, $container, purpose) {
3105    $("body").trigger("wcpt_before_ajax_container_replace", {
3106      response: response,
3107      $container: $container,
3108    });
3109
3110    // replace $container inner html
3111    var $new_container = get_response_container(response);
3112    if (purpose === "paginate" && $container.find(".wcpt-navigation").length) {
3113      replace_paginated_content($container, $new_container);
3114      update_header_pagination($container, $new_container);
3115    } else {
3116      $container.html($new_container.html());
3117    }
3118
3119    sync_container_attributes($container, $new_container);
3120    update_result_count($container);
3121    $container.data("wcpt_last_ajax_purpose", purpose);
3122
3123    // scroll
3124    // -- get params
3125    var sc_attrs_string = $container.attr("data-wcpt-sc-attrs"),
3126      default_offset = 40,
3127      sc_attrs =
3128        sc_attrs_string && sc_attrs_string !== "{}"
3129          ? JSON.parse(sc_attrs_string)
3130          : {},
3131      offset = {
3132        laptop:
3133          typeof sc_attrs.laptop_scroll_offset == "undefined" ||
3134          sc_attrs.laptop_scroll_offset == ""
3135            ? default_offset
3136            : sc_attrs.laptop_scroll_offset,
3137        tablet:
3138          typeof sc_attrs.tablet_scroll_offset == "undefined" ||
3139          sc_attrs.tablet_scroll_offset == ""
3140            ? default_offset
3141            : sc_attrs.tablet_scroll_offset,
3142        phone:
3143          typeof sc_attrs.phone_scroll_offset == "undefined" ||
3144          sc_attrs.phone_scroll_offset == ""
3145            ? default_offset
3146            : sc_attrs.phone_scroll_offset,
3147      };
3148
3149    // -- do scroll
3150    var device = wcpt_get_device_2($container);
3151    if (
3152      (sc_attrs["auto_scroll"] === "true" ||
3153        sc_attrs[device + "_auto_scroll"] === "true") &&
3154      ["lazy_load", "device_switch", "refresh_table"].indexOf(purpose) === -1
3155    ) {
3156      var offset = offset[device];
3157
3158      if (isNaN(offset)) {
3159        if (typeof offset === "string") {
3160          // selector
3161          offset = $(offset).height();
3162        } else if (typeof offset === "object") {
3163          // jQuery object
3164          offset = offset.height();
3165        }
3166      }
3167
3168      var containerTop = $container.offset().top - parseFloat(offset);
3169      var windowTop = $(window).scrollTop();
3170      var windowBottom = windowTop + $(window).height();
3171
3172      if (containerTop < windowTop || containerTop > windowBottom) {
3173        $("html, body").animate(
3174          {
3175            scrollTop: Math.max(0, containerTop),
3176          },
3177          200,
3178        );
3179      }
3180    }
3181  }
3182
3183  var build_ajax_query_string = (window.wcpt_build_ajax_query_string = (
3184    $container,
3185    new_query,
3186    append,
3187    purpose,
3188  ) => {
3189    var table_id = $container.attr("data-wcpt-table-id");
3190
3191    if (typeof purpose == "undefined") {
3192      throw "WCPT: Define AJAX purpose";
3193    }
3194
3195    // combine earlier query
3196    var query = "",
3197      earlier_query = $container.attr("data-wcpt-query-string");
3198    if (append && earlier_query) {
3199      earlier_query = earlier_query.substring(1);
3200      query = "?";
3201
3202      $.each(
3203        $.extend(
3204          {},
3205          wcpt_util.parse_query_string(earlier_query),
3206          wcpt_util.parse_query_string(new_query),
3207        ),
3208        function (key, val) {
3209          if (val !== "undefined") {
3210            query += key + "=" + encodeURIComponent(val) + "&";
3211          }
3212        },
3213      );
3214      query = query.substring(0, query.length - 1);
3215    } else {
3216      query = "?" + new_query;
3217    }
3218
3219    // lazy load
3220    if (purpose == "lazy_load") {
3221      query += "&" + window.location.search.substr(1);
3222    }
3223
3224    // persist params
3225    var parsed_params = wcpt_util.parse_query_string(
3226      window.location.search.substring(1),
3227    );
3228    var query_obj = wcpt_util.parse_query_string(query.substring(1));
3229
3230    if (typeof window.wcpt_persist_params !== "undefined") {
3231      $.each(wcpt_persist_params, function (index, i) {
3232        if (
3233          parsed_params[i] !== "undefined" &&
3234          typeof parsed_params[i] !== "undefined" &&
3235          typeof query_obj[i] == "undefined"
3236        ) {
3237          query += "&" + i + "=" + parsed_params[i];
3238        }
3239      });
3240    }
3241
3242    // device
3243    var device = wcpt_get_device_2($container);
3244
3245    var query_obj = wcpt_util.parse_query_string(query);
3246    if (query_obj[table_id + "_device"] !== device) {
3247      query += "&" + table_id + "_device=" + device;
3248    }
3249
3250    // switching device
3251    if (purpose === "device_switch") {
3252      // remove column sort
3253      // first find out if column sort is present
3254      if (query.indexOf(table_id + "_orderby=column_") !== -1) {
3255        query = query.replace(
3256          new RegExp(table_id + "_orderby=column_\\d+", "g"),
3257          "",
3258        );
3259        query = query.replace(
3260          new RegExp(table_id + "_order=(ASC|DESC)", "g"),
3261          "",
3262        );
3263      }
3264    }
3265
3266    // shortcode attributes
3267    var sc_attrs = get_sc_attrs($container);
3268
3269    // search - orderby relevance
3270    var new_query_p = new_query ? wcpt_util.parse_query_string(new_query) : {},
3271      earlier_query_p = earlier_query
3272        ? wcpt_util.parse_query_string(earlier_query.substring(1))
3273        : {},
3274      search_orderby = sc_attrs.search_orderby ? "search_orderby" : "relevance",
3275      search_order = sc_attrs.search_order ? "search_order" : "";
3276
3277    // detect if a new search is taking place in this query
3278    $.each(new_query_p, function (key, val) {
3279      if (
3280        key.indexOf("search") !== -1 &&
3281        val &&
3282        earlier_query_p[key] !== val.replace(/\+/g, " ")
3283      ) {
3284        // reset search orderby and order
3285        query +=
3286          "&" +
3287          table_id +
3288          "_orderby=" +
3289          search_orderby +
3290          "&" +
3291          table_id +
3292          "_order=" +
3293          search_order;
3294        return false;
3295      }
3296    });
3297
3298    // flag table has been filtered
3299    if (purpose == "filter") {
3300      query += "&" + table_id + "_filtered=true";
3301    }
3302
3303    // coming from shop?
3304    if (parsed_params[table_id + "_from_shop"]) {
3305      query += "&" + table_id + "_from_shop=true";
3306    }
3307
3308    // add shortcode attributes
3309    if (!$.isEmptyObject(sc_attrs)) {
3310      // applying fix % conflict with % -> %25
3311      // query += '&' + table_id + '_sc_attrs=' + encodeURIComponent( _sc_attrs.replaceAll('%', '%25') );
3312      query +=
3313        "&" +
3314        table_id +
3315        "_sc_attrs=" +
3316        encodeURIComponent(JSON.stringify(sc_attrs));
3317    }
3318
3319    if (purpose === "paginate") {
3320      query += "&wcpt_paginate_only=1";
3321    }
3322
3323    return query;
3324  });
3325
3326  function get_sc_attrs($container) {
3327    var _sc_attrs = $container.attr("data-wcpt-sc-attrs"),
3328      sc_attrs = _sc_attrs && _sc_attrs !== "{}" ? JSON.parse(_sc_attrs) : {};
3329    return sc_attrs;
3330  }
3331
3332  function get_column_layout_source_map($container) {
3333    var raw = $container.attr("data-wcpt-column-layout-source-map");
3334    if (!raw || raw === "{}") return {};
3335    try {
3336      return JSON.parse(raw);
3337    } catch (e) {
3338      return {};
3339    }
3340  }
3341
3342  function get_layout_source_for_device(layoutMap, device) {
3343    if (!layoutMap || typeof layoutMap !== "object") return device;
3344    return layoutMap[device] ? layoutMap[device] : device;
3345  }
3346
3347  // get device based on device column container being displayed
3348  function wcpt_get_device_2($container) {
3349    var device = "laptop",
3350      // Single-device container system: device wrapper should always exist.
3351      // Avoid :visible here to prevent timing/CSS interactions causing
3352      // an incorrect "current device" and triggering unnecessary reloads.
3353      $scroll_outer = $container
3354        .find(".wcpt-table-scroll-wrapper-outer")
3355        .first();
3356
3357    if ($scroll_outer.length) {
3358      if ($scroll_outer.hasClass("wcpt-device-phone")) {
3359        device = "phone";
3360      } else if ($scroll_outer.hasClass("wcpt-device-tablet")) {
3361        device = "tablet";
3362      }
3363    } else if ($("body").hasClass("wcpt-nav-modal-on")) {
3364      device = $(".wcpt-nav-modal").attr("data-wcpt-device");
3365    } else if (
3366      $(
3367        ".wcpt-required-but-missing-nav-filter-message, .wcpt-no-results",
3368        $container,
3369      ).length
3370    ) {
3371      device = $(
3372        ".wcpt-required-but-missing-nav-filter-message, .wcpt-no-results",
3373        $container,
3374      ).attr("data-wcpt-device");
3375    }
3376
3377    return device;
3378  }
3379
3380  // request markup from sever and apply callback
3381  var fetch_markup_and_apply_callback =
3382    (window.wcpt_fetch_markup_and_apply_callback = (
3383      $container,
3384      query,
3385      purpose,
3386      callback,
3387    ) => {
3388      var table_id = $container.attr("data-wcpt-table-id"),
3389        sc_attrs = wcpt_util.get_sc_attrs($container);
3390
3391      // prep url
3392      var url =
3393          wcpt_params.wc_ajax_url.replace("%%endpoint%%", "wcpt_ajax") +
3394          "&" +
3395          query.slice(1),
3396        data = {
3397          wcpt_encrypted_attrs: wcpt_util.get_encrypted_attrs($container),
3398          id: table_id, // @TODO -- remove this
3399        };
3400
3401      // reload if ajax is disabled
3402      if (sc_attrs.disable_ajax && sc_attrs.disable_ajax !== "false") {
3403        var wcpt_redirect_params = {
3404          $container: $container,
3405          query: query,
3406          base_url: window.location.href.split("?")[0],
3407          purpose: purpose,
3408          proceed: true,
3409        };
3410        $("body").trigger("wcpt_before_nav_redirect", wcpt_redirect_params);
3411        if (wcpt_redirect_params.proceed) {
3412          window.location.href = wcpt_redirect_params.base_url + "?" + query;
3413        }
3414        return;
3415      }
3416
3417      // allow table from cache @TODO -- convert to query param
3418      var permit_cache =
3419        purpose !== "refresh_table" && typeof WavePlayer === "undefined";
3420
3421      if (
3422        // use cache
3423        permit_cache &&
3424        window.wcpt_cache.exist(query)
3425      ) {
3426        callback(window.wcpt_cache.get(query), $container, purpose);
3427        $container.data("wcpt_last_ajax_purpose", purpose);
3428        after_every_load($container);
3429        return;
3430      } else {
3431        // else AJAX
3432        $.ajax({
3433          url: url,
3434          method: "POST",
3435          beforeSend: function () {
3436            $container.addClass("wcpt-loading");
3437            $container.trigger("wcpt_before_ajax");
3438            return true;
3439          },
3440          data: data,
3441        }).done(function (response) {
3442          // success
3443          if (response && response.indexOf("wcpt-table") !== -1) {
3444            window.wcpt_cache.set(query, response); // update cache
3445            callback(window.wcpt_cache.get(query), $container, purpose);
3446            $container.data("wcpt_last_ajax_purpose", purpose);
3447            after_every_load($container);
3448
3449            // fail
3450          } else {
3451            console.log("wcpt notice: query fail");
3452          }
3453
3454          $container.removeClass("wcpt-loading");
3455        });
3456      }
3457    });
3458
3459  // variable product modal form
3460  //-- close modal
3461  $("body").on("click", ".wcpt-modal, .wcpt-close-modal", function (e) {
3462    var $target = $(e.target),
3463      $modal = $(this).closest(".wcpt-modal");
3464    if (
3465      $target.hasClass("wcpt-modal") ||
3466      $target.closest(".wcpt-close-modal").length
3467    ) {
3468      $modal.trigger("wcpt_close");
3469    }
3470  });
3471
3472  window.wcpt_update_cart_items = function (cart) {
3473    var cart_products = {},
3474      total = 0;
3475    $.each(cart, function (key, item) {
3476      if (!cart_products[item.product_id]) {
3477        cart_products[item.product_id] = 0;
3478      }
3479
3480      if (item.variation_id && !cart_products[item.variation_id]) {
3481        cart_products[item.variation_id] = 0;
3482      }
3483
3484      cart_products[item.product_id] += item.quantity;
3485
3486      if (item.variation_id) {
3487        cart_products[item.variation_id] += item.quantity;
3488      }
3489
3490      total += item.quantity;
3491    });
3492
3493    // -- update each product row
3494    $(".wcpt-row").each(function () {
3495      var $this = $(this),
3496        id = $this.attr("data-wcpt-variation-id")
3497          ? $this.attr("data-wcpt-variation-id")
3498          : $this.attr("data-wcpt-product-id"),
3499        qty = cart_products[id] ? cart_products[id] : 0,
3500        $badge = $this.find(".wcpt-cart-badge-number"),
3501        $remove = $this.find(".wcpt-remove");
3502
3503      $this.attr("data-wcpt-in-cart", qty);
3504
3505      if (qty) {
3506        add_count_badge_to_button(qty, $badge.closest(".wcpt-button"));
3507      } else {
3508        $badge.text("");
3509      }
3510    });
3511  };
3512
3513  // anchor tag
3514  $("body").on("click touchstart", "[data-wcpt-href]", function () {
3515    window.location = $(this).attr("data-wcpt-href");
3516  });
3517
3518  // accordion
3519  //-- filters
3520  // $('body').on('click', '.wcpt-left-sidebar .wcpt-filter > .wcpt-filter-heading', function(e){
3521  //   if( $(e.target).closest('.wcpt-tooltip').length ){
3522  //     return;
3523  //   }
3524
3525  //   var $this = $(this),
3526  //       $filter = $this.closest('.wcpt-filter');
3527  //   $filter.toggleClass('wcpt-filter-open wcpt-open');
3528  // })
3529  // //-- filter clicked directly outside of heading
3530  // $('body').on('click', '.wcpt-left-sidebar .wcpt-filter:not(.wcpt-filter-open)', function(e){
3531  //   var $this = $(this);
3532  //   if( e.target === this ){
3533  //     $this.addClass('wcpt-filter-open');
3534  //   }
3535  // })
3536  //-- taxonomy parent
3537  $("body").on(
3538    "click",
3539    ".wcpt-filter-hierarchy-accordion__trigger",
3540    function (e) {
3541      var $this = $(this);
3542      $this
3543        .closest(".wcpt-filter-hierarchy-accordion")
3544        .toggleClass("wcpt-filter-hierarchy-accordion--open");
3545      e.stopPropagation();
3546      return false;
3547    },
3548  );
3549
3550  // nav modal
3551  function nav_modal(e) {
3552    var $button = $(e.target).closest(".wcpt-rn-button"),
3553      modal_type = $button.attr("data-wcpt-modal"),
3554      $wcpt = $button.closest(".wcpt"),
3555      wcpt_id = $wcpt.attr("id"),
3556      $nav_modal = $($wcpt.find(".wcpt-nav-modal-tpl").html()),
3557      // Header + sidebar may both contain the same filters — keep one of each
3558      $filters = dedupe_nav_elements(
3559        $wcpt.find(".wcpt-filter").not('[data-wcpt-filter="sort_by"]'),
3560      ),
3561      $search = dedupe_nav_elements($wcpt.find(".wcpt-search-wrapper")),
3562      $sort = dedupe_nav_elements(
3563        $wcpt.find('[data-wcpt-filter="sort_by"].wcpt-filter'),
3564      ),
3565      radios = {};
3566
3567    $(".wcpt-nm-sort-placeholder", $nav_modal).replaceWith($sort.clone());
3568    $(".wcpt-nm-filters-placeholder", $nav_modal).replaceWith(
3569      $search.clone().add($filters.clone()),
3570    );
3571
3572    $(
3573      '.wcpt-nm-sort-placeholder [data-wcpt-filter="sort_by"]',
3574      $nav_modal,
3575    ).addClass("wcpt-open");
3576
3577    if (modal_type == "sort") {
3578      $nav_modal.addClass("wcpt-show-sort").removeClass("wcpt-show-filters");
3579    } else {
3580      // filter
3581      $nav_modal.addClass("wcpt-show-filters").removeClass("wcpt-show-sort");
3582    }
3583
3584    // record radios
3585    $wcpt.find("input[type=radio]:checked").each(function () {
3586      var $this = $(this);
3587      radios[$this.attr("name")] = $this.val();
3588    });
3589    $nav_modal.data("wcpt-radios", radios);
3590
3591    // ':' at the end of row labels
3592    $nav_modal
3593      .find(
3594        ".wcpt-filter.wcpt-options-row > .wcpt-filter-heading > .wcpt-options-heading > .wcpt-item-row > .wcpt-text:last-child",
3595      )
3596      .each(function () {
3597        var $this = $(this),
3598          text = $this.text().trim();
3599
3600        if (text.substr(-1) === ":") {
3601          $this.text(text.substr(0, text.length - 1));
3602        }
3603      });
3604
3605    // duplicate header
3606    // if( ! $nav_modal.find('.wcpt-nm-heading--sticky').length ){
3607    //   var $header = $nav_modal.find('.wcpt-nm-heading');
3608    //   $header.clone().addClass('wcpt-nm-heading--sticky').insertAfter( $header );
3609    // }
3610
3611    // switch option row to dropdown
3612    const switch_classes = {
3613      "wcpt-options-row": "wcpt-dropdown",
3614      "wcpt-option": "wcpt-dropdown-option",
3615      "wcpt-options-heading": "wcpt-dropdown-label",
3616      "wcpt-options": " wcpt-dropdown-menu",
3617    };
3618    $.each(switch_classes, function (key, value) {
3619      $nav_modal
3620        .find("." + key)
3621        .removeClass(key)
3622        .addClass(value);
3623    });
3624
3625    // append
3626    window.wcpt_nav_modal_scroll = window.scrollY;
3627
3628    $("body")
3629      .trigger("wcpt-nav-modal-on")
3630      .addClass("wcpt-nav-modal-on")
3631      .append('<div class="wcpt-nav-modal-backdrop"></div>')
3632      .append($nav_modal);
3633
3634    $(".wcpt-nav-modal-backdrop").one("click", function () {
3635      $nav_modal.trigger("wcpt_close");
3636    });
3637
3638    // open filter dropdown if its alone
3639    $other_filters = $nav_modal.find(
3640      '.wcpt-filter:not([data-wcpt-filter="sort_by"])',
3641    );
3642
3643    if ($other_filters.length === 1) {
3644      $other_filters.addClass("wcpt-filter-open wcpt-open");
3645    }
3646
3647    // multirange
3648    $(".wcpt-range-slider-wrapper", $nav_modal).each(function () {
3649      var $this = $(this),
3650        $original = $this.children(".original"),
3651        $ghost = $this.children(".ghost"),
3652        $new_slider = $("<input/>").attr({
3653          type: "range",
3654          class: "wcpt-range-slider",
3655          min: $original.attr("min"),
3656          max: $original.attr("max"),
3657          step: $original.attr("step"),
3658          value: $original.attr("data-wcpt-initial-value"),
3659        });
3660
3661      $original.add($ghost).remove();
3662
3663      $this.append($new_slider);
3664      wcpt__multirange($new_slider[0]);
3665    });
3666
3667    // apply
3668    //-- filter
3669    $nav_modal.find(".wcpt-nm-apply").on("click", function () {
3670      var $nav_clone = $nav_modal.clone();
3671
3672      nav_clone_operations($nav_clone);
3673
3674      var query = $("<form>").append($nav_clone).serialize(),
3675        $container = $("#" + wcpt_id);
3676
3677      $nav_modal.trigger("wcpt_close");
3678
3679      attempt_ajax($container, query, false, "filter");
3680    });
3681    //-- sort
3682    $nav_modal.filter(".wcpt-show-sort").on("change", function () {
3683      var $nav_clone = $nav_modal.clone();
3684
3685      nav_clone_operations($nav_clone);
3686
3687      var query = $("<form>").append($nav_clone).serialize(),
3688        $container = $("#" + wcpt_id);
3689
3690      $nav_modal.trigger("wcpt_close");
3691
3692      attempt_ajax($container, query, false, "filter");
3693    });
3694
3695    // clear
3696    $nav_modal.find(".wcpt-nm-reset").on("click", function () {
3697      var query = $("<form>").append($nav_modal.clone()).serialize(),
3698        $container = $("#" + wcpt_id),
3699        query = "";
3700
3701      $nav_modal.trigger("wcpt_close");
3702
3703      attempt_ajax($container, query, false, "filter");
3704    });
3705
3706    // close
3707    $nav_modal.find(".wcpt-nm-close").on("click", function (e) {
3708      e.preventDefault();
3709
3710      var $container = $("#" + wcpt_id),
3711        radios = $.extend({}, $nav_modal.data("wcpt-radios"));
3712
3713      $nav_modal.trigger("wcpt_close");
3714
3715      $.each(radios, function (name, val) {
3716        $wcpt
3717          .find('input[type=radio][name="' + name + '"][value="' + val + '"]')
3718          .each(function () {
3719            $(this).prop("checked", "checked");
3720          });
3721      });
3722    });
3723
3724    // scroll fix
3725    //  var prev_y = false;
3726    // $(".wcpt-nav-modal")
3727    //   .on("touchstart", function (e) {
3728    //     prev_y = e.originalEvent.touches[0].clientY;
3729    //   })
3730    //   .on("touchmove", function (e) {
3731    //     if (
3732    //       (e.originalEvent.touches[0].clientY > prev_y && !this.scrollTop) ||
3733    //       (e.originalEvent.touches[0].clientY < prev_y &&
3734    //         this.scrollTop === this.scrollHeight - this.offsetHeight)
3735    //     ) {
3736    //       e.preventDefault();
3737    //     }
3738    //   });
3739  }
3740
3741  $("body").on("wcpt_close", ".wcpt-nav-modal", function () {
3742    var $this = $(this),
3743      table_id = $this.attr("data-wcpt-table-id"),
3744      $container = $("#wcpt-" + table_id);
3745
3746    $("body").trigger("wcpt-nav-modal-off").removeClass("wcpt-nav-modal-on");
3747
3748    $(".wcpt-nav-modal-backdrop").remove();
3749    $this.remove();
3750    window.scroll(null, wcpt_nav_modal_scroll);
3751  });
3752
3753  // row option change ev
3754  $("body").on("change", ".wcpt-options-row .wcpt-option input", function () {
3755    var $this = $(this),
3756      $label = $this.closest(".wcpt-option ");
3757
3758    if ($this.is(":radio")) {
3759      if (this.checked) {
3760        $label.addClass("wcpt-active").siblings().removeClass("wcpt-active");
3761      }
3762    } else {
3763      // checkbox
3764      if (this.checked) {
3765        $label.addClass("wcpt-active");
3766      } else {
3767        $label.removeClass("wcpt-active");
3768      }
3769    }
3770  });
3771
3772  // toggle
3773  $("body").on("click", ".wcpt-tg-trigger", function () {
3774    var $this = $(this),
3775      $toggle = $this.closest(".wcpt-toggle"),
3776      $table = $this.closest(".wcpt-table");
3777
3778    $toggle.toggleClass(" wcpt-tg-on wcpt-tg-off ");
3779  });
3780
3781  $("body").on("click", ".wcpt-rn-filter, .wcpt-rn-sort", nav_modal);
3782
3783  // photoswipe
3784
3785  // -- open
3786  function init_photoswipe($this, index, append_item) {
3787    // photoswipe gallery
3788    if (
3789      typeof PhotoSwipe !== "undefined" &&
3790      typeof PhotoSwipeUI_Default !== "undefined"
3791    ) {
3792      var items = JSON.parse($this.attr("data-wcpt-photoswipe-items")),
3793        index = typeof index == "undefined" || !index ? 0 : parseInt(index),
3794        color_theme = $this.attr("data-wcpt-lightbox-color-theme") || "black";
3795
3796      // append items
3797      if (append_item) {
3798        items.push(append_item);
3799      }
3800
3801      var index_src = items[index].src;
3802
3803      // remove duplicates
3804      var unique_src = [],
3805        _items = [];
3806
3807      $.each(items, function (index2, item) {
3808        if (-1 === $.inArray(item.src, unique_src)) {
3809          _items.push(item);
3810          unique_src.push(item.src);
3811        }
3812      });
3813      items = _items;
3814
3815      var options = JSON.parse($this.attr("data-wcpt-photoswipe-options")),
3816        photoswipe = new PhotoSwipe(
3817          $(".pswp")[0],
3818          PhotoSwipeUI_Default,
3819          items,
3820          options,
3821        );
3822
3823      photoswipe.init();
3824
3825      // add wcpt-photoswipe class and trigger selector attr
3826      var color_theme_class = "wcpt-photoswipe--theme-" + color_theme;
3827      $(".pswp").addClass("wcpt-photoswipe " + color_theme_class);
3828
3829      // add wcpt class on photoswipe and trigger selector
3830
3831      // reposition index in case item was deleted
3832      $.each(items, function (index3, item) {
3833        if (item.src === index_src) {
3834          index = index3;
3835        }
3836      });
3837
3838      photoswipe.goTo(index);
3839
3840      var $body = $("body");
3841
3842      $body.addClass("wcpt-photoswipe-visible");
3843      photoswipe.listen("close", function () {
3844        setTimeout(function () {
3845          // fix photoswipe click through bug
3846          $body.removeClass("wcpt-photoswipe-visible");
3847        }, 10);
3848      });
3849
3850      // remove wcpt-photoswipe class and trigger selector attr after closing
3851      photoswipe.listen("destroy", function () {
3852        $(".pswp").removeClass("wcpt-photoswipe " + color_theme_class);
3853      });
3854
3855      $(photoswipe.container).data("wcpt_photoswipe", photoswipe);
3856
3857      return true;
3858    } else {
3859      return false;
3860    }
3861  }
3862
3863  // -- close
3864  $("body").on("click", ".pswp__container", function (e) {
3865    var $this = $(this),
3866      photoswipe = $this.data("wcpt_photoswipe");
3867
3868    if (window.innerWidth < 720 && photoswipe) {
3869      var $target = $(e.target);
3870      if (!$target.closest(".pswp__button").length) {
3871        photoswipe.close();
3872      }
3873    }
3874  });
3875
3876  // gallery strip
3877
3878  // -- image
3879  $("body").on("click", ".wcpt-gallery__item", function () {
3880    var $this = $(this),
3881      index = parseInt($this.attr("data-wcpt-gallery-item")),
3882      $gallery = $this.closest(".wcpt-gallery");
3883
3884    if (!$gallery.hasClass("wcpt-gallery--include-featured")) {
3885      index += 1;
3886    }
3887
3888    init_photoswipe($gallery, index);
3889  });
3890
3891  // -- link
3892  $("body").on("click", ".wcpt-gallery a", function (e) {
3893    e.preventDefault();
3894
3895    var $this = $(this),
3896      $gallery = $this.closest(".wcpt-gallery");
3897
3898    init_photoswipe($gallery);
3899  });
3900
3901  // image lightbox
3902  $("body").on("click", ".wcpt-lightbox-enabled", function () {
3903    destroy_offset_zoom_containers();
3904
3905    var $this = $(this);
3906    if (
3907      $this
3908        .closest(".wcpt")
3909        .hasClass("wcpt-quick-view-trigger--product-image") &&
3910      !$this
3911        .closest(".wcpt-row")
3912        .hasClass("wcpt-quick-view-trigger__disabled-for-product")
3913    ) {
3914      return;
3915    }
3916
3917    var index = 0,
3918      $row = get_product_rows($this),
3919      src = $this.attr("data-wcpt-lightbox"),
3920      pswp_items = JSON.parse($this.attr("data-wcpt-photoswipe-items")),
3921      append_item = false;
3922
3923    if (
3924      $row.attr("data-wcpt-type") === "variable" &&
3925      $row.data("wcpt_variation_selected")
3926    ) {
3927      var variation = $row.data("wcpt_variation"),
3928        src = variation.image.full_src,
3929        found = false;
3930
3931      $.each(pswp_items, function (_index, item) {
3932        if (item.src == src) {
3933          index = _index;
3934          found = true;
3935          return false;
3936        }
3937      });
3938
3939      if (!found) {
3940        append_item = {
3941          src: variation.image.full_src,
3942          w: variation.image.full_src_w,
3943          h: variation.image.full_src_h,
3944          title: variation.image.title,
3945        };
3946
3947        index = pswp_items.length;
3948      }
3949    } else {
3950      // get starting index
3951      if ($this.attr("data-wcpt-photoswipe-items")) {
3952        $.each(pswp_items, function (_index, item) {
3953          if (item.src == src) {
3954            index = _index;
3955            return false;
3956          }
3957        });
3958      }
3959    }
3960
3961    if (!init_photoswipe($this, index, append_item)) {
3962      var $el = $(
3963        '<div class="wcpt-lightbox-screen"><div class="wcpt-lightbox-loader"></div><div class="wcpt-lightbox-close"></div><img class="wcpt-lightbox-image" src="' +
3964          src +
3965          '"></div>',
3966      );
3967      $("body").append($el);
3968      $el.on("click ", function () {
3969        $el.remove();
3970      });
3971    }
3972  });
3973
3974  // image zoom
3975  //-- image hover
3976  $("body").on(
3977    "mouseenter",
3978    '.wcpt-zoom-enabled[data-wcpt-zoom-trigger="image_hover"]',
3979    function () {
3980      var $this = $(this),
3981        level = $this.attr("data-wcpt-zoom-level");
3982      if (!level) {
3983        level = "1.5";
3984      }
3985
3986      if ($this.closest(".wcpt-device-tablet, .wcpt-device-phone").length) {
3987        return;
3988      }
3989
3990      $this.css({
3991        transform: "scale(" + level + ")",
3992        "z-index": "2",
3993      });
3994
3995      $this.one("mouseleave", function () {
3996        $this.css({
3997          transform: "",
3998          "z-index": "",
3999        });
4000      });
4001    },
4002  );
4003  //-- row hover
4004  $("body").on("mouseenter", ".wcpt-row", function () {
4005    var $row = $(this);
4006    $row
4007      .find('.wcpt-zoom-enabled[data-wcpt-zoom-trigger="row_hover"]')
4008      .each(function () {
4009        var $zoom_me = $(this),
4010          level = $zoom_me.attr("data-wcpt-zoom-level");
4011        if (!level) {
4012          level = "1.5";
4013        }
4014
4015        if (
4016          $zoom_me.closest(".wcpt-device-tablet, .wcpt-device-phone").length
4017        ) {
4018          return;
4019        }
4020
4021        $zoom_me.css({
4022          transform: "scale(" + level + ")",
4023          "z-index": "2",
4024        });
4025
4026        $row.one("mouseleave", function () {
4027          $zoom_me.css({
4028            transform: "",
4029            "z-index": "",
4030          });
4031        });
4032      });
4033  });
4034
4035  // product image offset zoom
4036  // -- attach hover handler
4037  $("body").on(
4038    "mouseenter.wcpt_offset_zoom",
4039    ".wcpt-product-image-wrapper--offset-zoom-enabled, .wcpt-gallery--offset-zoom-enabled .wcpt-gallery__item-wrapper",
4040    function (e) {
4041      var $this = $(this),
4042        $img = $this.find("img").not(".wcpt-product-image-on-hover").first(),
4043        zoom_src = $this.attr("data-wcpt-offset-zoom-image-src"),
4044        $offset_zoom_img = $("<img>", {
4045          class: "wcpt-offset-zoom-container__image",
4046          src: zoom_src || $img.attr("src"),
4047        });
4048
4049      var $offset_zoom = $("<div>", {
4050          class:
4051            "wcpt-offset-zoom-container " +
4052            $this.attr("data-wcpt-offset-zoom-image-html-class"),
4053        }).append($offset_zoom_img),
4054        $wcpt = $this.closest(".wcpt"),
4055        original_title = $img.attr("title");
4056
4057      // Remove the title on mouseenter and store original in data attr
4058      $img.data("wcpt-original-title", original_title);
4059      $img.removeAttr("title");
4060
4061      if ($this.closest(".frzTbl--grab-and-scroll--grabbing").length) {
4062        return;
4063      }
4064
4065      destroy_offset_zoom_containers();
4066
4067      $wcpt.append($offset_zoom).on("mousemove.wcpt_offset_zoom", function (e) {
4068        position_offset_zoom_container(e, $offset_zoom, $this);
4069      });
4070
4071      setTimeout(function () {
4072        $offset_zoom.addClass("wcpt-offset-zoom-container--fade-in");
4073      }, 50);
4074
4075      $this.on("mouseleave", function () {
4076        // Restore the original title on mouseleave
4077        var $img = $this.find("img"),
4078          original_title = $img.data("wcpt-original-title");
4079        if (typeof original_title !== "undefined") {
4080          $img.attr("title", original_title);
4081        }
4082        destroy_offset_zoom_containers();
4083      });
4084    },
4085  );
4086  // -- turn off hover handler on touch screens
4087  $("body").on("touchstart", function () {
4088    $("body").off("mouseenter.wcpt_offset_zoom");
4089  });
4090
4091  function position_offset_zoom_container(e, $offset_zoom, $trigger) {
4092    var left = e.originalEvent.clientX + 40,
4093      top = e.originalEvent.clientY,
4094      position = "right";
4095
4096    $offset_zoom.css({
4097      left: left,
4098      top: top,
4099    });
4100
4101    var rect = $offset_zoom.get(0).getBoundingClientRect(),
4102      viewport_width =
4103        window.innerWidth || document.documentElement.clientWidth,
4104      viewport_height =
4105        window.innerHeight || document.documentElement.clientHeight;
4106
4107    if (rect.right > viewport_width) {
4108      position = "left";
4109    }
4110
4111    if (position == "left") {
4112      left = $trigger.get(0).getBoundingClientRect().left - 40 - rect.width;
4113    }
4114
4115    if (rect.top < 0) {
4116      top = 0 + 0.25 * rect.height;
4117    } else if (rect.bottom > viewport_height) {
4118      top -= rect.bottom - viewport_height;
4119    }
4120
4121    $offset_zoom.css({
4122      left: left,
4123      top: top,
4124    });
4125  }
4126
4127  function destroy_offset_zoom_containers() {
4128    $(".wcpt-offset-zoom-container").remove();
4129    $body.off("mousemove.wcpt_offset_zoom");
4130  }
4131
4132  // uncheck variation radio
4133  $("body").on("click", ".wcpt-variation-radio", function (e) {
4134    var $this = $(this),
4135      $variation = $this.closest(".wcpt-select-variation"),
4136      $row = $this.closest(".wcpt-row");
4137
4138    if (
4139      $variation.hasClass("wcpt-selected") &&
4140      window.navigator.userAgent.indexOf("Edge") == -1
4141    ) {
4142      $this.prop("checked", false);
4143      $this.change();
4144
4145      $row.trigger("select_variation", {
4146        variation_id: false,
4147        complete_match: false,
4148        attributes: false,
4149        variation: false,
4150        variation_found: false,
4151        variation_selected: false,
4152        variation_available: false,
4153      });
4154    }
4155  });
4156
4157  // variation selected class toggle
4158  $("body").on("change", ".wcpt-variation-radio", function () {
4159    var $this = $(this),
4160      $others = $(
4161        '.wcpt-variation-radio[name="' + $(this).attr("name") + '"]',
4162      ).not($(this)),
4163      $variation = $this.closest(".wcpt-select-variation");
4164
4165    if ($this.is(":checked")) {
4166      $variation.addClass("wcpt-selected");
4167    } else {
4168      $variation.removeClass("wcpt-selected");
4169    }
4170
4171    $others
4172      .not(":checked")
4173      .closest(".wcpt-select-variation")
4174      .removeClass("wcpt-selected");
4175  });
4176
4177  // select variation (main)
4178  //-- sync
4179  $("body").on(
4180    "select_variation",
4181    ".wcpt-product-type-variable",
4182    function (e, data) {
4183      var $row = get_product_rows($(this));
4184
4185      // update dropdown
4186      var $variation_dropdown = $row.find(".wcpt-select-variation-dropdown");
4187      $variation_dropdown.val(data.variation_id ? data.variation_id : "");
4188
4189      // update radio
4190      $row
4191        .find('.wcpt-variation-radio[value="' + data.variation_id + '"]')
4192        .prop("checked", true);
4193
4194      // update form
4195      $row.find(".variations_form").each(function () {
4196        var $this = $(this);
4197        current_variation_id =
4198          $(".variation_id", $this).val() === "0"
4199            ? ""
4200            : $(".variation_id", $this).val();
4201
4202        if (data.variation_id != current_variation_id) {
4203          window.wcpt_form_reset_flag = true;
4204          $(".reset_variations", $this).trigger("click.wc-variation-form");
4205          window.wcpt_form_reset_flag = false;
4206
4207          // select variation in form
4208          if (data.variation_id) {
4209            $(".variations select", $this).each(function () {
4210              var $this = $(this),
4211                name = $this.attr("name");
4212
4213              if (typeof data.attributes[name] !== "undefined") {
4214                $this.val(data.attributes[name]);
4215              } else {
4216                $this.val("");
4217              }
4218            });
4219
4220            $this.trigger("check_variations");
4221          }
4222        }
4223      });
4224
4225      // update row
4226      $row.data("wcpt_variation", data.variation);
4227      $row.data("wcpt_variation_id", data.variation_id);
4228      $row.data("wcpt_complete_match", data.complete_match);
4229      $row.data("wcpt_attributes", data.attributes);
4230      $row.data("wcpt_variation_found", data.variation_found);
4231      $row.data("wcpt_variation_selected", data.variation_selected);
4232      $row.data("wcpt_variation_available", data.variation_available);
4233      $row.data("wcpt_variation_qty", data.variation_qty);
4234
4235      // update total
4236      update_row_total($row);
4237      update_table_add_selected_to_cart.call($row.get(0));
4238
4239      // reset
4240      if (!data.variation_selected) {
4241        // -- add to cart buton
4242        var $button = $row.find('[data-wcpt-link-code^="cart"]');
4243
4244        // -- -- no variation selected
4245        if (
4246          $row.find(".wcpt-add-to-cart-wrapper").length || // cart form
4247          $variation_dropdown.length || // select variation -- dropdown
4248          $row.find(".wcpt-variation-radio").length // select variation -- radio
4249        ) {
4250          disable_button($button, "wcpt-no-variation-selected");
4251        }
4252
4253        // -- -- out of stock
4254        if ($row.hasClass("wcpt-all-variations-out-of-stock")) {
4255          disable_button($button, "wcpt-all-variations-out-of-stock");
4256        } else {
4257          enable_button($button, "wcpt-all-variations-out-of-stock");
4258        }
4259
4260        // -- checkbox (UI only — do not clear cross-page persisted checkbox state)
4261        $row.first().trigger("wcpt_checkbox_change", false);
4262
4263        // -- qty input
4264        var $qty_wrapper = $row.find(".wcpt-quantity"),
4265          $qty = $qty_wrapper.find("input[type=number].qty");
4266
4267        // -- -- disabled
4268        $qty_wrapper.addClass("wcpt-disabled");
4269        $qty.attr("disabled", "disabled");
4270
4271        if ($qty.length) {
4272          $qty.each(function () {
4273            var $this = $(this),
4274              inital_value = $this.attr("data-wcpt-initial-value"),
4275              value = "";
4276
4277            if (inital_value === "0") {
4278              value = 0;
4279            } else if (inital_value == "min") {
4280              // No variation selected → max is 0
4281              value = 0;
4282            } else {
4283              value = "";
4284            }
4285
4286            $this.attr({
4287              min: "0",
4288              max: "0",
4289              step: "",
4290              value: value,
4291            });
4292
4293            $this.val(value);
4294
4295            limit_qty_controller($this.closest(".wcpt-quantity"));
4296          });
4297        }
4298
4299        // -- product image
4300        var $product_image_wrapper = $(".wcpt-product-image-wrapper", $row),
4301          $product_image = $(
4302            ".wcpt-product-image-wrapper > img:not(.wcpt-product-image-on-hover)",
4303            $row,
4304          ),
4305          $original_row = wcpt_get_original_row($row);
4306
4307        if ($product_image_wrapper.length) {
4308          if (!$original_row.data("wcpt_default_image")) {
4309            if ($product_image[0]) {
4310              // lazy load fix
4311              $original_row.data(
4312                "wcpt_default_image",
4313                $product_image[0].outerHTML,
4314              );
4315            } else {
4316              handle_product_image_lazy_load($product_image_wrapper);
4317            }
4318          } else {
4319            $product_image.replaceWith(
4320              $original_row.data("wcpt_default_image"),
4321            );
4322          }
4323
4324          if ($product_image_wrapper.hasClass("wcpt-lightbox-enabled")) {
4325            if (!$product_image_wrapper.attr("data-wcpt-lightbox--original")) {
4326              $product_image_wrapper.attr(
4327                "data-wcpt-lightbox--original",
4328                $product_image_wrapper.attr("data-wcpt-lightbox"),
4329              );
4330            } else {
4331              $product_image_wrapper.attr(
4332                "data-wcpt-lightbox",
4333                $product_image_wrapper.attr("data-wcpt-lightbox--original"),
4334              );
4335            }
4336          }
4337
4338          if (
4339            $product_image_wrapper.hasClass(
4340              "wcpt-product-image-wrapper--offset-zoom-enabled",
4341            )
4342          ) {
4343            if (
4344              !$product_image_wrapper.attr(
4345                "data-wcpt-offset-zoom-image-src--original",
4346              )
4347            ) {
4348              $product_image_wrapper.attr(
4349                "data-wcpt-offset-zoom-image-src--original",
4350                $product_image_wrapper.attr("data-wcpt-offset-zoom-image-src"),
4351              );
4352            } else {
4353              $product_image_wrapper.attr(
4354                "data-wcpt-offset-zoom-image-src",
4355                $product_image_wrapper.attr(
4356                  "data-wcpt-offset-zoom-image-src--original",
4357                ),
4358              );
4359            }
4360          }
4361        }
4362
4363        // -- sku
4364        $row.find(".wcpt-sku").each(function () {
4365          var $sku = $(this),
4366            sku = $sku.attr("data-wcpt-sku");
4367          var $label = $sku.find(".wcpt-property-label");
4368          if ($label.length) {
4369            $sku.html($label.prop("outerHTML") + sku);
4370          } else {
4371            $sku.text(sku);
4372          }
4373        });
4374
4375        // -- gtin
4376        $row.find(".wcpt-gtin").each(function () {
4377          var $gtin = $(this),
4378            gtin = $gtin.attr("data-wcpt-gtin");
4379          $gtin.text(gtin); // revert to variable product gtin
4380        });
4381
4382        // -- product id
4383        $row.find(".wcpt-product-id").each(function () {
4384          var $product_id = $(this),
4385            product_id = $product_id.attr("data-wcpt-product-id");
4386          $product_id.text(product_id); // revert to variable product ID
4387        });
4388
4389        // -- price
4390
4391        // -- -- wcpt price element
4392        $row
4393          .filter(".wcpt-product-type-variable")
4394          .find(".wcpt-price.wcpt-variable-switch")
4395          .each(function () {
4396            var $this = $(this),
4397              id = $this.attr("data-wcpt-element-id"),
4398              tpl = $this.attr("data-wcpt-variable-template"),
4399              template_html = $(
4400                "[data-wcpt-element-id=" +
4401                  id +
4402                  "][data-wcpt-price-type=" +
4403                  tpl +
4404                  "]",
4405              ).html(),
4406              $html,
4407              o = [
4408                "highest-price",
4409                "lowest-price",
4410                "sale-price",
4411                "regular-price",
4412              ];
4413
4414            // No template yet (paginate / infinite scroll before merge) — keep
4415            // the server-rendered default price instead of wiping it blank.
4416            if (!template_html) {
4417              return;
4418            }
4419
4420            $html = $(template_html);
4421
4422            $.each(o, function (index, val) {
4423              $(".wcpt-" + val + " .wcpt-amount", $html).text(
4424                $this.attr("data-wcpt-" + val),
4425              );
4426            });
4427
4428            $this.html($html);
4429
4430            if (tpl == "sale") {
4431              $this.addClass("wcpt-product-on-sale");
4432            } else {
4433              $this.removeClass("wcpt-product-on-sale");
4434            }
4435          });
4436
4437        // -- -- default woocommerce template
4438        $row
4439          .filter(".wcpt-product-type-variable")
4440          .find(".wcpt-variable-price-default-woocommerce-template")
4441          .each(function () {
4442            var $this = $(this);
4443            $default = $(".wcpt-variable-switch__default", $this);
4444            $default.show().next(".price").remove();
4445          });
4446
4447        // -- -- price attribute
4448        $row.attr("data-wcpt-price", "");
4449
4450        // -- on sale
4451        $row
4452          .filter(".wcpt-product-type-variable")
4453          .find(".wcpt-on-sale.wcpt-variable-switch")
4454          .each(function () {
4455            var $this = $(this);
4456            $this.addClass("wcpt-hide");
4457          });
4458
4459        // -- availability
4460        $row
4461          .filter(".wcpt-product-type-variable")
4462          .find(".wcpt-availability.wcpt-variable-switch")
4463          .each(function () {
4464            var $this = $(this),
4465              id = $this.attr("data-wcpt-element-id"),
4466              stock = $this.attr("data-wcpt-stock"),
4467              message_tpl = $this.attr("data-wcpt-default-message_tpl"),
4468              stock_class = $this.attr("data-wcpt-default-stock_class"),
4469              message = $(
4470                "[data-wcpt-element-id=" +
4471                  id +
4472                  '][data-wcpt-availability-message="' +
4473                  message_tpl +
4474                  '"]',
4475              ).html(),
4476              $label = $this.find(".wcpt-property-label"),
4477              label_html = $label.length ? $label.prop("outerHTML") : "",
4478              $message = $($.parseHTML("<span>" + message + "</span>"));
4479
4480            $this.html(
4481              label_html +
4482                "<span class='" +
4483                stock_class +
4484                "'>" +
4485                $message
4486                  .find(".wcpt-stock-placeholder")
4487                  .text(stock)
4488                  .end()
4489                  .html() +
4490                "</span>",
4491            );
4492          });
4493
4494        // -- stock
4495        $row
4496          .filter(".wcpt-product-type-variable")
4497          .find(".wcpt-stock")
4498          .each(function () {
4499            var $this = $(this),
4500              stock = $this.attr("data-wcpt-stock"),
4501              rules = $this.attr("data-wcpt-stock-range-labels"),
4502              parsed_rules = !rules || rules == "{}" ? [] : JSON.parse(rules),
4503              $label = $this.find(".wcpt-property-label"),
4504              label_html = $label.length ? $label.prop("outerHTML") : "",
4505              label = stock;
4506
4507            var found_rule = false;
4508
4509            if (stock && parsed_rules.length) {
4510              $.each(parsed_rules, function (index, rule) {
4511                if (rule[0] <= stock && rule[1] >= stock) {
4512                  label = rule[2];
4513                  found_rule = true;
4514                }
4515              });
4516            }
4517
4518            if (!found_rule && stock < 0) {
4519              stock = "";
4520              label = "";
4521            }
4522
4523            $this.html(label_html + (label + "").replace("[stock]", stock));
4524          });
4525
4526        // -- dimensions
4527        $row
4528          .filter(".wcpt-product-type-variable")
4529          .find(".wcpt-dimensions")
4530          .each(function () {
4531            var $this = $(this);
4532        
4532    $this.html($this.attr("data-wcpt-default-dimensions"));
4533          });
4534
4535        // -- height
4536        $row
4537          .filter(".wcpt-product-type-variable")
4538          .find(".wcpt-height")
4539          .each(function () {
4540            var $this = $(this),
4541              template = $this.attr("data-wcpt-template"),
4542              empty_template = $this.attr("data-wcpt-empty-template"),
4543              height = $this.attr("data-wcpt-default-height");
4544
4545            if (!height) {
4546              $this.html(empty_template ? empty_template : "");
4547            } else {
4548              $this.html(
4549                template ? template.replace("{n}", height) : empty_template,
4550              );
4551            }
4552          });
4553
4554        // -- width
4555        $row
4556          .filter(".wcpt-product-type-variable")
4557          .find(".wcpt-width")
4558          .each(function () {
4559            var $this = $(this),
4560              template = $this.attr("data-wcpt-template"),
4561              empty_template = $this.attr("data-wcpt-empty-template"),
4562              width = $this.attr("data-wcpt-default-width");
4563
4564            if (!width) {
4565              $this.html(empty_template ? empty_template : "");
4566            } else {
4567              $this.html(template ? template.replace("{n}", width) : width);
4568            }
4569          });
4570
4571        // -- length
4572        $row
4573          .filter(".wcpt-product-type-variable")
4574          .find(".wcpt-length")
4575          .each(function () {
4576            var $this = $(this),
4577              template = $this.attr("data-wcpt-template"),
4578              empty_template = $this.attr("data-wcpt-empty-template"),
4579              length = $this.attr("data-wcpt-default-length");
4580
4581            if (!length) {
4582              $this.html(empty_template ? empty_template : "");
4583            } else {
4584              $this.html(template ? template.replace("{n}", length) : length);
4585            }
4586          });
4587
4588        // -- weight
4589        $row
4590          .filter(".wcpt-product-type-variable")
4591          .find(".wcpt-weight")
4592          .each(function () {
4593            var $this = $(this),
4594              template = $this.attr("data-wcpt-template"),
4595              empty_template = $this.attr("data-wcpt-empty-template"),
4596              weight = $this.attr("data-wcpt-default-weight");
4597
4598            if (!weight) {
4599              $this.html(empty_template ? empty_template : "");
4600            } else {
4601              $this.html(template ? template.replace("{n}", weight) : weight);
4602            }
4603          });
4604
4605        // -- custom field
4606        $row
4607          .filter(".wcpt-product-type-variable")
4608          .find(".wcpt-custom-field.wcpt-variable-switch")
4609          .each(function () {
4610            var $this = $(this),
4611              element_id = $this.attr("data-wcpt-element-id"),
4612              product_id = $this
4613                .closest(".wcpt-row")
4614                .attr("data-wcpt-product-id"),
4615              table_id = $this.closest(".wcpt").attr("data-wcpt-table-id"),
4616              $label = $this.find(".wcpt-property-label"),
4617              label_html = $label.length ? $label.prop("outerHTML") : "";
4618
4619            if (
4620              "undefined" !==
4621              typeof window["wcpt_" + table_id + "_variable_switch_cf"]
4622            ) {
4623              var cf_vals = window["wcpt_" + table_id + "_variable_switch_cf"];
4624              if ("undefined" !== typeof cf_vals[element_id]) {
4625                $this.html(label_html + cf_vals[element_id][product_id]);
4626              }
4627            }
4628          });
4629      }
4630
4631      if (!$.isEmptyObject(data.variation)) {
4632        // -- add to cart buton
4633        var $button = $row.find('[data-wcpt-link-code^="cart"]');
4634
4635        // -- -- no variation selected
4636        enable_button($button, "wcpt-no-variation-selected");
4637
4638        // -- -- out of stock
4639        if (data.variation.is_in_stock) {
4640          enable_button($button, "wcpt-variation-out-of-stock");
4641        } else {
4642          disable_button($button, "wcpt-variation-out-of-stock");
4643        }
4644
4645        // -- qty input
4646        var $qty_wrapper = $row.find(".wcpt-quantity"),
4647          $qty = $qty_wrapper.find("input[type=number].qty");
4648
4649        // -- -- disabled
4650        if (data.variation.is_in_stock) {
4651          $qty_wrapper.removeClass("wcpt-disabled");
4652          $qty.removeAttr("disabled");
4653        } else {
4654          $qty_wrapper.addClass("wcpt-disabled");
4655          $qty.attr("disabled", "disabled");
4656        }
4657
4658        if ($qty.length) {
4659          $qty.each(function () {
4660            var $this = $(this),
4661              $qty_wrapper = $this.closest(".wcpt-quantity"),
4662              inital_value = $this.attr("data-wcpt-initial-value"),
4663              min = data.variation.min_qty
4664                ? parseFloat(data.variation.min_qty)
4665                : 1,
4666              max = data.variation.max_qty
4667                ? parseFloat(data.variation.max_qty)
4668                : "",
4669              step = data.variation.step ? parseFloat(data.variation.step) : "",
4670              value = "";
4671
4672            // validate val
4673            var current_val = parseFloat($this.val());
4674            if (current_val && current_val !== NaN) {
4675              // neither empty, nor 0
4676              if (current_val < min) {
4677                value = min;
4678              } else if (max && current_val > max) {
4679                value = max;
4680              } else {
4681                value = current_val;
4682              }
4683            }
4684
4685            if (
4686              inital_value === "min" &&
4687              $this.attr("data-wcpt-reset-on-variation-change")
4688            ) {
4689              value = min;
4690            }
4691
4692            var wcpt_min = min;
4693
4694            if (inital_value === "0") {
4695              min = "0";
4696            }
4697
4698            $this.attr({
4699              value: value,
4700              min: min,
4701              "data-wcpt-min": wcpt_min,
4702              max: max,
4703              step: step,
4704            });
4705
4706            $this.val(value);
4707
4708            $.each(["min", "max", "step"], function (index, attr) {
4709              $qty_wrapper
4710                .find(".wcpt-quantity-error-placeholder--" + attr)
4711                .text($this.attr(attr));
4712            });
4713
4714            $this.trigger("change");
4715          });
4716        }
4717
4718        // -- qty select
4719        var $select = $row.find(".wcpt-quantity > select.wcpt-qty-select");
4720        if ($select.length) {
4721          // re-create select
4722          var qty_label = $select.attr("data-wcpt-qty-label"),
4723            max_qty = parseInt($select.attr("data-wcpt-max-qty")),
4724            val = data.variation.min_qty,
4725            options =
4726              '<option value="' +
4727              data.variation.min_qty +
4728              '" selected="selected">' +
4729              qty_label +
4730              data.variation.min_qty +
4731              "</option>";
4732          if (data.variation.max_qty) {
4733            max_qty = data.variation.max_qty;
4734          }
4735
4736          while (val < max_qty) {
4737            val += data.variation.step || 1;
4738            options += "<option>" + val + "</option>";
4739          }
4740          $select.html(options);
4741          $select.attr("min", data.variation.min_qty);
4742        }
4743
4744        // -- product image
4745        var $product_image_wrapper = $(".wcpt-product-image-wrapper", $row),
4746          $product_image = $(
4747            ".wcpt-product-image-wrapper img:not(.wcpt-product-image-on-hover)",
4748            $row,
4749          ),
4750          $original_row = wcpt_get_original_row($row);
4751
4752        if ($product_image[0]) {
4753          if (!$original_row.data("wcpt_default_image")) {
4754            $original_row.data(
4755              "wcpt_default_image",
4756              $product_image[0].outerHTML,
4757            );
4758          }
4759
4760          // replace variation image accoring to 'product image' element size settings
4761          if (data.variation.wcpt_image && data.variation.wcpt_image.src) {
4762            data.variation.image.src = data.variation.image.srcset =
4763              data.variation.wcpt_image.src;
4764          }
4765
4766          if (
4767            $product_image.length &&
4768            data.variation.image &&
4769            data.variation.image.src
4770          ) {
4771            $product_image.attr({
4772              src: data.variation.image.src,
4773              srcset: data.variation.image.srcset
4774                ? data.variation.image.srcset
4775                : "",
4776              sizes: data.variation.image.sizes,
4777            });
4778
4779            if ($product_image_wrapper.hasClass("wcpt-lightbox-enabled")) {
4780              if (
4781                !$product_image_wrapper.attr("data-wcpt-lightbox--original")
4782              ) {
4783                $product_image_wrapper.attr(
4784                  "data-wcpt-lightbox--original",
4785                  $product_image_wrapper.attr("data-wcpt-lightbox"),
4786                );
4787              }
4788
4789              $product_image_wrapper.attr(
4790                "data-wcpt-lightbox",
4791                data.variation.image.full_src,
4792              );
4793            }
4794
4795            if (
4796              $product_image_wrapper.hasClass(
4797                "wcpt-product-image-wrapper--offset-zoom-enabled",
4798              )
4799            ) {
4800              if (
4801                !$product_image_wrapper.attr(
4802                  "data-wcpt-offset-zoom-image-src--original",
4803                )
4804              ) {
4805                $product_image_wrapper.attr(
4806                  "data-wcpt-offset-zoom-image-src--original",
4807                  $product_image_wrapper.attr(
4808                    "data-wcpt-offset-zoom-image-src",
4809                  ),
4810                );
4811              }
4812
4813              $product_image_wrapper.attr(
4814                "data-wcpt-offset-zoom-image-src",
4815                data.variation.image.full_src,
4816              );
4817            }
4818          }
4819        } else {
4820          handle_product_image_lazy_load($product_image_wrapper);
4821        }
4822
4823        // -- sku
4824        if (data.variation.sku) {
4825          $row.find(".wcpt-sku").each(function () {
4826            var $this = $(this);
4827            if ($this.hasClass("wcpt-variable-switch")) {
4828              var $label = $this.find(".wcpt-property-label");
4829              if ($label.length) {
4830                $this.html($label.prop("outerHTML") + data.variation.sku);
4831              } else {
4832                $this.text(data.variation.sku);
4833              }
4834            }
4835          });
4836        }
4837
4838        // -- gtin
4839        if (data.variation.wcpt_gtin) {
4840          $row.find(".wcpt-gtin").each(function () {
4841            var $this = $(this);
4842            if ($this.hasClass("wcpt-variable-switch")) {
4843              $this.text(data.variation.wcpt_gtin);
4844            }
4845          });
4846        }
4847
4848        // -- product id
4849        if (data.variation.variation_id) {
4850          $row.find(".wcpt-product-id").each(function () {
4851            var $this = $(this);
4852            if ($this.hasClass("wcpt-variable-switch")) {
4853              $this.text(data.variation.variation_id);
4854            }
4855          });
4856        }
4857
4858        // -- price
4859
4860        // -- -- wcpt price element
4861        $row
4862          .filter(".wcpt-product-type-variable")
4863          .find(".wcpt-price.wcpt-variable-switch")
4864          .each(function () {
4865            var $this = $(this),
4866              id = $this.attr("data-wcpt-element-id"),
4867              tpl =
4868                parseFloat(data.variation.display_price) <
4869                parseFloat(data.variation.display_regular_price)
4870                  ? "sale"
4871                  : "regular",
4872              template_html = $(
4873                "[data-wcpt-element-id=" +
4874                  id +
4875                  "][data-wcpt-price-type=" +
4876                  tpl +
4877                  "]",
4878              ).html(),
4879              $html;
4880
4881            if (!template_html) {
4882              return;
4883            }
4884
4885            $html = $(template_html);
4886
4887            $html
4888              .find(".wcpt-regular-price .wcpt-amount")
4889              .text(data.variation.display_regular_price)
4890              .end()
4891              .find(".wcpt-sale-price .wcpt-amount")
4892              .text(data.variation.display_price);
4893            $this.html($html);
4894
4895            if (tpl == "sale") {
4896              $this.addClass("wcpt-product-on-sale");
4897            } else {
4898              $this.removeClass("wcpt-product-on-sale");
4899            }
4900          });
4901
4902        // -- -- default woocommerce template
4903        $row
4904          .filter(".wcpt-product-type-variable")
4905          .find(".wcpt-variable-price-default-woocommerce-template")
4906          .each(function () {
4907            var $this = $(this);
4908            $default = $(".wcpt-variable-switch__default", $this);
4909
4910            if (!data.variation.price_html) {
4911              // single variation won't have price html
4912              return;
4913            }
4914
4915            $default
4916              .hide()
4917              .nextAll(".price")
4918              .remove()
4919              .end()
4920              .after(data.variation.price_html);
4921          });
4922
4923        // -- -- price attribute
4924        $row.attr("data-wcpt-price", data.variation.display_price);
4925
4926        // -- on sale
4927        $row
4928          .filter(".wcpt-product-type-variable")
4929          .find(".wcpt-on-sale.wcpt-variable-switch")
4930          .each(function () {
4931            var $this = $(this),
4932              precision = $this.attr("data-wcpt-precision"),
4933              is_on_sale =
4934                parseFloat(data.variation.display_price) <
4935                parseFloat(data.variation.display_regular_price),
4936              $price_diff = $this.find(".wcpt-on-sale__price-diff"),
4937              $percent_diff = $this.find(".wcpt-on-sale__percent-diff");
4938
4939            if (is_on_sale) {
4940              $this.removeClass("wcpt-hide");
4941
4942              var price_diff =
4943                data.variation.display_regular_price -
4944                data.variation.display_price;
4945              var percent_diff = parseFloat(
4946                (
4947                  (price_diff / data.variation.display_regular_price) *
4948                  100
4949                ).toFixed(precision),
4950              );
4951
4952              $price_diff.html(format_price(price_diff));
4953              $percent_diff.text(percent_diff);
4954            } else {
4955              $this.addClass("wcpt-hide");
4956            }
4957          });
4958
4959        // -- availability
4960        $row
4961          .filter(".wcpt-product-type-variable")
4962          .find(".wcpt-availability.wcpt-variable-switch")
4963          .each(function () {
4964            var $this = $(this),
4965              id = $this.attr("data-wcpt-element-id"),
4966              message_tpl = "",
4967              stock_class = "",
4968              out_of_stock_message = $(
4969                "[data-wcpt-element-id=" +
4970                  id +
4971                  '][data-wcpt-availability-message="out_of_stock_message"]',
4972              ).html(),
4973              low_stock_message = $(
4974                "[data-wcpt-element-id=" +
4975                  id +
4976                  '][data-wcpt-availability-message="low_stock_message"]',
4977              ).html(),
4978              single_stock_message = $(
4979                "[data-wcpt-element-id=" +
4980                  id +
4981                  '][data-wcpt-availability-message="single_stock_message"]',
4982              ).html(),
4983              in_stock_message = $(
4984                "[data-wcpt-element-id=" +
4985                  id +
4986                  '][data-wcpt-availability-message="in_stock_message"]',
4987              ).html(),
4988              in_stock_managed_message = $(
4989                "[data-wcpt-element-id=" +
4990                  id +
4991                  '][data-wcpt-availability-message="in_stock_managed_message"]',
4992              ).html(),
4993              on_backorder_message = $(
4994                "[data-wcpt-element-id=" +
4995                  id +
4996                  '][data-wcpt-availability-message="on_backorder_message"]',
4997              ).html(),
4998              on_backorder_managed_message = $(
4999                "[data-wcpt-element-id=" +
5000                  id +
5001                  '][data-wcpt-availability-message="on_backorder_managed_message"]',
5002              ).html(),
5003              low_stock_threshold = $this.attr("data-wcpt-low_stock_threshold");
5004
5005            $this.show();
5006
5007            if (!data.variation.is_in_stock) {
5008              message_tpl = "out_of_stock_message";
5009              stock_class = "wcpt-out-of-stock";
5010            } else if (
5011              ((data.variation.managing_stock &&
5012                data.variation.is_on_backorder &&
5013                data.variation.backorders_require_notification) ||
5014                (!data.variation.managing_stock &&
5015                  data.variation.is_on_backorder)) &&
5016              on_backorder_message
5017            ) {
5018              message_tpl = "on_backorder_message";
5019              stock_class = "wcpt-on-backorder";
5020            } else if (data.variation.managing_stock) {
5021              // in stock, managed
5022              message_tpl = "in_stock_managed_message";
5023              stock_class = "wcpt-in-stock";
5024
5025              if (!data.variation.backorders_allowed) {
5026                if (
5027                  // low stock
5028                  data.variation.stock == 1 &&
5029                  single_stock_message
5030                ) {
5031                  message_tpl = "single_stock_message";
5032                  stock_class = "wcpt-low-stock";
5033                } else if (
5034                  // single stock
5035                  low_stock_message &&
5036                  low_stock_threshold &&
5037                  data.variation.stock <= low_stock_threshold
5038                ) {
5039                  message_tpl = "low_stock_message";
5040                  stock_class = "wcpt-low-stock";
5041                }
5042              } else if (
5043                data.variation.backorders_require_notification &&
5044                on_backorder_managed_message
5045              ) {
5046                // backorder allowed, managed stock, greater than 0
5047                message_tpl = "on_backorder_managed_message";
5048                stock_class = "wcpt-on-backorder";
5049              } else if (data.variation.stock <= 0) {
5050                // backorder allowed, managed stock, 0 or less
5051                message_tpl = "on_backorder_message";
5052                stock_class = "";
5053                $this.hide();
5054              } else {
5055                // backorder allowed, managed stock, greater than 0
5056                message_tpl = "in_stock_message";
5057                stock_class = "wcpt-in-stock";
5058              }
5059            } else {
5060              // in stock, not managed
5061              message_tpl = "";
5062              stock_class = "";
5063
5064              if (in_stock_message) {
5065                message_tpl = "in_stock_message";
5066                stock_class = "wcpt-in-stock";
5067              }
5068            }
5069
5070            const message_tpl_selector = `[data-wcpt-element-id="${id}"][data-wcpt-availability-message="${message_tpl}"]`;
5071            const $message = $(
5072              $.parseHTML(
5073                "<span>" + $(message_tpl_selector).html() + "</span>",
5074              ),
5075            );
5076            const $label = $this.find(".wcpt-property-label");
5077            const label_html = $label.length ? $label.prop("outerHTML") : "";
5078
5079            $this.html(
5080              label_html +
5081                "<span class='" +
5082                stock_class +
5083                "'>" +
5084                $message
5085                  .find(".wcpt-stock-placeholder")
5086                  .text(data.variation.stock ? data.variation.stock : "")
5087                  .end()
5088                  .html() +
5089                "</span>",
5090            );
5091          });
5092
5093        // -- stock
5094        $row
5095          .filter(".wcpt-product-type-variable")
5096          .find(".wcpt-stock.wcpt-variable-switch")
5097          .each(function () {
5098            var $this = $(this),
5099              stock = data.variation.stock,
5100              rules = $this.attr("data-wcpt-stock-range-labels"),
5101              parsed_rules = !rules || rules == "{}" ? [] : JSON.parse(rules),
5102              $label = $this.find(".wcpt-property-label"),
5103              label_html = $label.length ? $label.prop("outerHTML") : "",
5104              label = stock;
5105
5106            if (
5107              typeof label === "undefined" ||
5108              label === null ||
5109              label == NaN
5110            ) {
5111              $this.hide();
5112              return;
5113            } else {
5114              $this.show();
5115            }
5116
5117            var found_rule = false;
5118            if (stock && parsed_rules.length) {
5119              $.each(parsed_rules, function (index, rule) {
5120                if (rule[0] <= stock && rule[1] >= stock) {
5121                  label = rule[2];
5122                  found_rule = true;
5123                }
5124              });
5125            }
5126
5127            if (!found_rule && stock < 0) {
5128              stock = "";
5129              label = "";
5130            }
5131
5132            $this.html(label_html + (label + "").replace("[stock]", stock));
5133          });
5134
5135        // -- dimensions
5136        $row
5137          .filter(".wcpt-product-type-variable")
5138          .find(".wcpt-dimensions.wcpt-variable-switch")
5139          .each(function () {
5140            var $this = $(this);
5141            $this.html(data.variation.dimensions_html);
5142          });
5143
5144        // -- height
5145        $row
5146          .filter(".wcpt-product-type-variable")
5147          .find(".wcpt-height.wcpt-variable-switch")
5148          .each(function () {
5149            var $this = $(this),
5150              template = $this.attr("data-wcpt-template"),
5151              empty_template = $this.attr("data-wcpt-empty-template"),
5152              height = data.variation.dimensions.height;
5153
5154            if (!height) {
5155              $this.html(empty_template ? empty_template : "");
5156            } else {
5157              $this.html(
5158                template ? template.replace("{n}", height) : empty_template,
5159              );
5160            }
5161          });
5162
5163        // -- width
5164        $row
5165          .filter(".wcpt-product-type-variable")
5166          .find(".wcpt-width.wcpt-variable-switch")
5167          .each(function () {
5168            var $this = $(this),
5169              template = $this.attr("data-wcpt-template"),
5170              empty_template = $this.attr("data-wcpt-empty-template"),
5171              width = data.variation.dimensions.width;
5172
5173            if (!width) {
5174              $this.html(empty_template ? empty_template : "");
5175            } else {
5176              $this.html(template ? template.replace("{n}", width) : width);
5177            }
5178          });
5179
5180        // -- length
5181        $row
5182          .filter(".wcpt-product-type-variable")
5183          .find(".wcpt-length.wcpt-variable-switch")
5184          .each(function () {
5185            var $this = $(this),
5186              template = $this.attr("data-wcpt-template"),
5187              empty_template = $this.attr("data-wcpt-empty-template"),
5188              length = data.variation.dimensions.length;
5189
5190            if (!length) {
5191              $this.html(empty_template ? empty_template : "");
5192            } else {
5193              $this.html(template ? template.replace("{n}", length) : length);
5194            }
5195          });
5196
5197        // -- weight
5198        $row
5199          .filter(".wcpt-product-type-variable")
5200          .find(".wcpt-weight.wcpt-variable-switch")
5201          .each(function () {
5202            var $this = $(this),
5203              template = $this.attr("data-wcpt-template"),
5204              empty_template = $this.attr("data-wcpt-empty-template"),
5205              weight = data.variation.weight;
5206
5207            if (!weight) {
5208              $this.html(empty_template ? empty_template : "");
5209            } else {
5210              $this.html(template ? template.replace("{n}", weight) : weight);
5211            }
5212          });
5213
5214        // -- custom field
5215        $row
5216          .filter(".wcpt-product-type-variable")
5217          .find(".wcpt-custom-field.wcpt-variable-switch")
5218          .each(function () {
5219            var $this = $(this),
5220              element_id = $this.attr("data-wcpt-element-id"),
5221              table_id = $this.closest(".wcpt").attr("data-wcpt-table-id");
5222            (($label = $this.find(".wcpt-property-label")),
5223              (label_html = $label.length ? $label.prop("outerHTML") : ""));
5224
5225            if (
5226              "undefined" !==
5227              typeof window["wcpt_" + table_id + "_variable_switch_cf"]
5228            ) {
5229              var cf_vals = window["wcpt_" + table_id + "_variable_switch_cf"];
5230              if ("undefined" !== typeof cf_vals[element_id]) {
5231                $this.html(
5232                  label_html + cf_vals[element_id][data.variation.variation_id],
5233                );
5234              }
5235            }
5236          });
5237      }
5238    },
5239  );
5240
5241  //-- -- update from dropdown
5242  $("body").on("change", ".wcpt-select-variation-dropdown", function (e) {
5243    var $this = $(this),
5244      $selected = $this.find("option:selected"),
5245      $row = $this.closest(".wcpt-row");
5246
5247    $row.trigger("select_variation", {
5248      variation_id: $this.val(),
5249      complete_match: $selected.hasClass("wcpt-complete_match"),
5250      attributes: $selected.attr("data-wcpt-attributes")
5251        ? JSON.parse($selected.attr("data-wcpt-attributes"))
5252        : "",
5253      variation: $selected.attr("data-wcpt-variation")
5254        ? JSON.parse($selected.attr("data-wcpt-variation"))
5255        : "",
5256      variation_found: !!$selected.attr("value"),
5257      variation_selected: !!$selected.attr("value"),
5258      variation_available:
5259        !$selected.is(":disabled") && !!$selected.attr("value"),
5260    });
5261  });
5262
5263  //-- -- update from radio
5264  $("body").on("change", ".wcpt-variation-radio", function (e) {
5265    var $this = $(this),
5266      $wrapper = $this.closest(".wcpt-select-variation"),
5267      $row = $this.closest(".wcpt-row");
5268
5269    if ($this.is(":checked")) {
5270      $row.trigger("select_variation", {
5271        variation_id: $this.val(),
5272        complete_match: $wrapper.hasClass("wcpt-complete_match"),
5273        attributes: JSON.parse($wrapper.attr("data-wcpt-attributes")),
5274        variation: JSON.parse($wrapper.attr("data-wcpt-variation")),
5275        variation_found: true,
5276        variation_selected: true,
5277        variation_available: !$this.is(":disabled"),
5278      });
5279    }
5280  });
5281
5282  //-- -- update from form
5283  $("body").on(
5284    "woocommerce_variation_has_changed",
5285    ".wcpt-row .variations_form",
5286    function (e) {
5287      get_select_variation_from_cart_form($(this));
5288    },
5289  );
5290
5291  function get_select_variation_from_cart_form($form) {
5292    if (window.wcpt_form_reset_flag) {
5293      // avoid infinite loop
5294      return;
5295    }
5296
5297    var variations = JSON.parse($form.attr("data-product_variations")),
5298      $row = $form.closest(".wcpt-row"),
5299      $variation_id = $(".variation_id", $form),
5300      variation = {},
5301      attributes = {},
5302      selected_variation = $variation_id.val();
5303
5304    $.each(variations, function (index, value) {
5305      if (parseInt(value["variation_id"]) == selected_variation) {
5306        variation = value;
5307        return false;
5308      }
5309    });
5310
5311    var variation_selected = true;
5312    $(".variations select", $form).each(function () {
5313      var $this = $(this);
5314      attributes[$this.attr("name")] = $this.val();
5315
5316      if (!$this.val()) {
5317        variation_selected = false;
5318      }
5319    });
5320
5321    var variation_available = false;
5322    if (
5323      variation &&
5324      !$.isEmptyObject(variation) &&
5325      variation.is_purchasable &&
5326      variation.is_in_stock &&
5327      variation.variation_is_visible
5328    ) {
5329      variation_available = true;
5330    }
5331
5332    $row.trigger("select_variation", {
5333      variation: variation,
5334      variation_id: selected_variation,
5335      complete_match: true,
5336      attributes: attributes,
5337      variation_found: !!selected_variation,
5338      variation_selected: variation_selected,
5339      variation_available: variation_available,
5340    });
5341  }
5342
5343  // prepare variation options
5344  function prep_variation_options($new_rows) {
5345    function mark_row_prep_variation_complete($row) {
5346      $row.data("wcpt_prep_variation_complete", true);
5347      $row.trigger("wcpt_row_prep_variation_complete");
5348    }
5349
5350    function shouldSkipPrepVariationReset($row) {
5351      var productId = $row.attr("data-wcpt-product-id"),
5352        table_id =
5353          typeof wcpt_util !== "undefined"
5354            ? wcpt_util.get_table_id($row.closest(".wcpt"))
5355            : "";
5356
5357      return !!(
5358        productId &&
5359        table_id &&
5360        typeof window.wcpt_cart_checkbox_persist !== "undefined" &&
5361        typeof window.wcpt_cart_checkbox_persist.hasPersistedVariableProduct ===
5362          "function" &&
5363        window.wcpt_cart_checkbox_persist.hasPersistedVariableProduct(
5364          table_id,
5365          productId,
5366        )
5367      );
5368    }
5369
5370    $($new_rows)
5371      .filter(".wcpt-product-type-variable")
5372      .each(function () {
5373        var $row = $(this),
5374          $dropdown = $(".wcpt-select-variation-dropdown", $row),
5375          $radio = $(".wcpt-variation-radio", $row),
5376          $form = $(".variations_form", $row),
5377          $options = $dropdown.add($radio).add($form);
5378
5379        $row.data("wcpt_prep_variation_complete", false);
5380
5381        // flag availability of options in row
5382        if ($options.length) {
5383          $row.data("wcpt_variation_ops", true);
5384        }
5385
5386        // trigger 'select_variation'
5387
5388        // cart form element
5389        if ($form.length) {
5390          $form.each(function () {
5391            var $form = $(this),
5392              skipPrepReset = shouldSkipPrepVariationReset($row);
5393
5394            setTimeout(function () {
5395              // need to match setTimeout in add-to-cart-variation.js that delays form init
5396              // this init syncs the WC form with WCPT native controls including add-to-cart button
5397              if (!skipPrepReset) {
5398                $form.find("select").first().change();
5399              }
5400              mark_row_prep_variation_complete($row);
5401            }, 200);
5402          });
5403
5404          // select variation element
5405
5406          // -- dropdown
5407        } else if ($dropdown.length) {
5408          if (!shouldSkipPrepVariationReset($row)) {
5409            $dropdown.trigger("change");
5410          }
5411          mark_row_prep_variation_complete($row);
5412
5413          // -- radio
5414        } else if ($radio.length) {
5415          if (!shouldSkipPrepVariationReset($row)) {
5416            var $checked = $radio.filter(":checked");
5417            if ($checked.length) {
5418              $checked.trigger("change");
5419            } else {
5420              $row.trigger("select_variation", {
5421                variation_id: false,
5422                complete_match: false,
5423                attributes: false,
5424                variation: false,
5425                variation_found: false,
5426                variation_selected: false,
5427                variation_available: false,
5428              });
5429            }
5430          }
5431          mark_row_prep_variation_complete($row);
5432        } else {
5433          mark_row_prep_variation_complete($row);
5434        }
5435      });
5436  }
5437
5438  // lazy loaded product image
5439  function handle_product_image_lazy_load($product_image_wrapper) {
5440    if (
5441      $product_image_wrapper.length &&
5442      !$product_image_wrapper.hasClass("wcpt-awaiting-image-lazy-load")
5443    ) {
5444      $product_image_wrapper.addClass("wcpt-awaiting-image-lazy-load");
5445
5446      $product_image_wrapper[0].addEventListener(
5447        "load",
5448        function (event) {
5449          if (
5450            event.target.tagName === "IMG" &&
5451            $product_image_wrapper.hasClass("wcpt-awaiting-image-lazy-load")
5452          ) {
5453            $product_image_wrapper.removeClass("wcpt-awaiting-image-lazy-load");
5454
5455            var $row = wcpt_get_original_row(
5456                $product_image_wrapper.closest(".wcpt-row"),
5457              ),
5458              data = $row.data(),
5459              $product_image = $product_image_wrapper.find(".wp-post-image");
5460
5461            $row.data("wcpt_default_image", $product_image[0].outerHTML);
5462
5463            if (
5464              !$.isEmptyObject(data.wcpt_variation) &&
5465              $product_image.length &&
5466              data.wcpt_variation.image &&
5467              data.wcpt_variation.image.src
5468            ) {
5469              $product_image.attr({
5470                src: data.wcpt_variation.image.src,
5471                srcset: data.wcpt_variation.image.srcset
5472                  ? data.wcpt_variation.image.srcset
5473                  : "",
5474              });
5475
5476              if ($product_image_wrapper.hasClass("wcpt-lightbox-enabled")) {
5477                $product_image_wrapper.attr(
5478                  "data-wcpt-lightbox",
5479                  data.wcpt_variation.image.full_src,
5480                );
5481              }
5482
5483              if (
5484                $product_image_wrapper.hasClass(
5485                  "wcpt-product-image-wrapper--offset-zoom-enabled",
5486                )
5487              ) {
5488                $product_image_wrapper.attr(
5489                  "data-wcpt-offset-zoom-image-src",
5490                  data.variation.image.full_src,
5491                );
5492              }
5493            }
5494          }
5495        },
5496        true,
5497      );
5498    }
5499  }
5500
5501  if (window.innerWidth < 1200) {
5502    $("body").on("contextmenu", ".wcpt-noselect", function () {
5503      return false;
5504    });
5505  }
5506
5507  /**
5508   * Device interaction type detection
5509   * Sets appropriate mouse event types based on device capabilities
5510   */
5511  if ("ontouchstart" in document.documentElement) {
5512    var mousedown = "touchstart",
5513      mouseup = "touchend";
5514  } else {
5515    var mousedown = "mousedown",
5516      mouseup = "mouseup";
5517  }
5518
5519  /**
5520   * Quantity Controller Module
5521   *
5522   * This module handles quantity input functionality for WooCommerce product tables,
5523   * including touch screen support, increment/decrement controls, validation, and
5524   * cross-table synchronization.
5525   */
5526
5527  /**
5528   * Touch Screen Handler
5529   * Automatically moves cursor to end of input field on touch devices
5530   * for better user experience when editing quantities
5531   */
5532  $("body").on("touchend", ".wcpt-quantity .qty", function () {
5533    var $this = $(this),
5534      _ = this;
5535    if (!$this.is(":focus")) {
5536      $this.one("focus", function () {
5537        var val = _.value;
5538        _.value = "";
5539        setTimeout(function () {
5540          _.value = val;
5541        }, 1);
5542      });
5543    }
5544  });
5545
5546  /**
5547   * Quantity Controller Click Handler
5548   * Manages increment/decrement button interactions including:
5549   * - Initial value changes
5550   * - Continuous increment/decrement on hold
5551   * - Min/max boundary enforcement
5552   * - Touch device support
5553   */
5554  $("body").on("click", ".wcpt-qty-controller", handleQuantityChange);
5555  $("body").on("wcptPressing", ".wcpt-qty-controller", handleQuantityChange);
5556  $("body").on("dblclick", ".wcpt-qty-controller", function (e) {
5557    e.preventDefault();
5558  });
5559
5560  function handleQuantityChange(e) {
5561    var $this = $(this),
5562      $wrapper = $this.closest(".wcpt-quantity"),
5563      $qty = $wrapper.find(".qty"),
5564      visualMin = $qty.attr("min") ? $qty.attr("min") : 0,
5565      realMin = $qty.attr("data-wcpt-min")
5566        ? $qty.attr("data-wcpt-min")
5567        : visualMin,
5568      max = $qty.attr("max") ? $qty.attr("max") : false,
5569      step = $qty.attr("step") ? $qty.attr("step") : 1,
5570      val = $qty.val() > -1 ? $qty.val() : visualMin;
5571
5572    if ($wrapper.hasClass("wcpt-disabled")) {
5573      return;
5574    }
5575
5576    if ($this.hasClass("wcpt-disabled")) {
5577      return;
5578    }
5579
5580    if (step % 1 === 0) {
5581      visualMin = parseInt(visualMin);
5582      realMin = parseInt(realMin);
5583      max = parseInt(max);
5584      step = parseInt(step);
5585      val = parseInt(val);
5586    } else {
5587      visualMin = parseFloat(visualMin);
5588      realMin = parseFloat(realMin);
5589      max = parseFloat(max);
5590      step = parseFloat(step);
5591      val = parseFloat(val);
5592    }
5593
5594    if (isNaN(val)) {
5595      val = 0;
5596    }
5597
5598    // update quantity value
5599    if ($this.hasClass("wcpt-plus")) {
5600      var next = (val * 1e12 + step * 1e12) / 1e12;
5601      // If increasing from below realMin, jump to realMin
5602      if (val < realMin && next >= visualMin) {
5603        next = realMin;
5604      }
5605    } else {
5606      // wcpt-minus
5607      var next = (val * 1e12 - step * 1e12) / 1e12;
5608      // If decreasing from above or equal to realMin, drop straight to visualMin or 0
5609      if (val >= realMin && next < realMin) {
5610        next = visualMin > 0 ? 0 : visualMin;
5611      } else if (next < visualMin) {
5612        next = 0;
5613      }
5614    }
5615    $qty.val(next).change();
5616
5617    limit_qty_controller($wrapper);
5618  }
5619
5620  function startContinuousQuantityChange(e) {
5621    $("body").addClass("wcpt-noselect--qty-increment");
5622  }
5623
5624  function stopContinuousQuantityChange(e) {
5625    $("body").removeClass("wcpt-noselect--qty-increment");
5626  }
5627
5628  /**
5629   * Quantity Input Validator
5630   * Ensures quantity values respect:
5631   * - Minimum and maximum limits
5632   * - Step increments
5633   * - Initial value rules
5634   * @param {Event} e - Change event
5635   */
5636  $("body").on(
5637    "change",
5638    ".wcpt-quantity .qty",
5639    function qty_controller_validate(e) {
5640      var $this = $(this),
5641        min = $this.attr("min") ? parseFloat($this.attr("min")) : 0,
5642        max =
5643          $this.attr("max") !== undefined && $this.attr("max") !== ""
5644            ? parseFloat($this.attr("max"))
5645            : 1e12,
5646        initial = $this.attr("data-wcpt-initial-value"),
5647        step = $this.attr("step") ? parseFloat($this.attr("step")) : 1,
5648        raw = $this.val(),
5649        val = parseFloat(raw);
5650
5651      // Empty field only — do not treat 0 as empty (OOS uses 0 with max 0).
5652      if (raw === "" || isNaN(val)) {
5653        if (initial === "min") {
5654          $this.val(!isNaN(max) && max < min ? Math.max(0, max) : min);
5655        } else if (initial === "empty") {
5656          $this.val("");
5657        } else if (initial === "0") {
5658          $this.val("0");
5659        }
5660        val = parseFloat($this.val());
5661      }
5662
5663      // enforce min
5664      if (val && val < min) {
5665        $this.val(min);
5666        val = min;
5667      }
5668
5669      // enforce max
5670      if (!isNaN(val) && val > max) {
5671        $this.val(max);
5672        val = max;
5673      }
5674
5675      // enforce step
5676      if (val && (val * 1e12) % (step * 1e12)) {
5677        var _val = val * 1e12,
5678          _step = step * 1e12,
5679          new_val = (_val + _step - (val % step) * 1e12) / 1e12;
5680
5681        $this.val(new_val);
5682      }
5683
5684      limit_qty_controller($(this).parent());
5685    },
5686  );
5687
5688  /**
5689   * Enter Key Cart Addition Handler
5690   * Allows users to press Enter while focused on quantity input
5691   * to trigger the add-to-cart action for that product
5692   *
5693   * @param {KeyboardEvent} e - Keyboard event object
5694   */
5695  $("body").on("keypress", ".wcpt-quantity .qty", function (e) {
5696    if (e.keyCode == 13) {
5697      var $rows = get_product_rows($(this));
5698      $rows.find('.wcpt-button[data-wcpt-link-code^="cart"]').eq(0).click();
5699    }
5700  });
5701
5702  /**
5703   * Quantity Controller State Manager
5704   * Updates the enabled/disabled state of increment/decrement buttons based on:
5705   * - Current quantity value
5706   * - Minimum and maximum limits
5707   * - Step size
5708   * @param {jQuery} $qty_wrapper - The quantity input wrapper element
5709   */
5710  function limit_qty_controller($qty_wrapper) {
5711    $qty_wrapper.each(function () {
5712      var $this = $(this),
5713        $minus = $this.children(".wcpt-minus"),
5714        $plus = $this.children(".wcpt-plus"),
5715        $qty = $this.find(".qty");
5716
5717      if ($this.hasClass("wcpt-disabled")) {
5718        $minus.addClass("wcpt-disabled");
5719        $plus.addClass("wcpt-disabled");
5720        return;
5721      }
5722
5723      var initial = $qty.attr("data-wcpt-initial-value"),
5724        min = $qty.attr("min") ? parseFloat($qty.attr("min")) : 1,
5725        max = $qty.attr("max") ? parseFloat($qty.attr("max")) : false,
5726        step = $qty.attr("step") ? parseFloat($qty.attr("step")) : 1,
5727        val = parseFloat($qty.val());
5728
5729      if (!val || isNaN(val)) {
5730        val = 0;
5731      }
5732
5733      if (-1 !== $.inArray(initial, ["empty", "0"])) {
5734        min = 0;
5735      }
5736
5737      $minus.removeClass("wcpt-disabled");
5738      if (val - step < min) {
5739        $minus.addClass("wcpt-disabled");
5740      }
5741
5742      $plus.removeClass("wcpt-disabled");
5743      if (false !== max && val + step > max) {
5744        $plus.addClass("wcpt-disabled");
5745      }
5746    });
5747  }
5748
5749  /**
5750   * Initial Quantity Value Handler
5751   * Sets up initial quantity values after table load based on configuration:
5752   * - Can set to minimum value
5753   * - Can set to empty
5754   * - Can set to zero
5755   * - Triggers appropriate events for other components
5756   */
5757  $("body").on(
5758    "wcpt_after_every_load.wcpt_initial_qty_update",
5759    ".wcpt",
5760    function () {
5761      var $this__container = $(this),
5762        $target_qty_elms = $(
5763          '.wcpt-quantity input[type="number"].qty',
5764          wcpt_util.get_uninit_rows($this__container),
5765        );
5766
5767      $target_qty_elms.each(function () {
5768        var $this__input = $(this),
5769          initial_value = $this__input.attr("data-wcpt-initial-value");
5770
5771        if (initial_value === "0") {
5772          $this__input.val(0);
5773        } else if (initial_value === "empty") {
5774          $this__input.val("");
5775        } else if (initial_value === "min") {
5776          var min = $this__input.attr("min")
5777              ? parseFloat($this__input.attr("min"))
5778              : 1,
5779            maxAttr = $this__input.attr("max"),
5780            max =
5781              maxAttr !== undefined && maxAttr !== ""
5782                ? parseFloat(maxAttr)
5783                : NaN;
5784          // OOS: max is 0 — don't leave purchase min in the field (WC page-load
5785          // script will also re-bump value if min stays above max).
5786          if (!isNaN(max) && max < min) {
5787            $this__input.attr("min", Math.max(0, max));
5788            $this__input.val(Math.max(0, max));
5789          } else {
5790            $this__input.val(min);
5791          }
5792        }
5793
5794        $this__input
5795          .trigger("wcpt_updating_initial_quantity")
5796          .trigger("change")
5797          .trigger("wcpt_initial_quantity_updated");
5798      });
5799    },
5800  );
5801
5802  /**
5803   * Quantity Error Handler
5804   * Manages error states and visual feedback for:
5805   * - Values below minimum
5806   * - Values above maximum
5807   * - Invalid step increments
5808   * Updates associated cart buttons accordingly
5809   */
5810  $("body").on("keyup change", ".wcpt-quantity .qty", function (e) {
5811    var $this = $(this),
5812      max = $this.attr("max") ? parseFloat($this.attr("max")) : 1e9,
5813      min = $this.attr("min") ? parseFloat($this.attr("min")) : 1,
5814      step = $this.attr("step") ? parseFloat($this.attr("step")) : 1,
5815      val = isNaN(parseFloat($this.val())) ? 0 : parseFloat($this.val()),
5816      $wrapper = $this.closest(".wcpt-quantity");
5817
5818    $wrapper.removeClass(
5819      "wcpt-quantity-error wcpt-quantity-error--min wcpt-quantity-error--max wcpt-quantity-error--step",
5820    );
5821
5822    if (!val) {
5823      return;
5824    }
5825
5826    if (val < min) {
5827      $wrapper.addClass("wcpt-quantity-error wcpt-quantity-error--min");
5828    } else if (val > max) {
5829      $wrapper.addClass("wcpt-quantity-error wcpt-quantity-error--max");
5830    } else if ((val * 1e12) % (step * 1e12)) {
5831      $wrapper.addClass("wcpt-quantity-error wcpt-quantity-error--step");
5832    }
5833
5834    var $row = $this.closest(".wcpt-row"),
5835      $button = $row.find('.wcpt-button[data-wcpt-link-code^="cart_"]');
5836
5837    if ($wrapper.hasClass("wcpt-quantity-error")) {
5838      disable_button($button, "wcpt-quantity-input-error");
5839    } else {
5840      enable_button($button, "wcpt-quantity-input-error");
5841    }
5842
5843    limit_qty_controller($wrapper);
5844  });
5845
5846  /**
5847   * Cross-Table Quantity Synchronization Handler
5848   * Ensures quantity values remain synchronized across:
5849   * - Multiple instances of the same product in different tables
5850   * - WooCommerce default cart buttons
5851   * - Custom quantity select dropdowns
5852   *
5853   * Updates:
5854   * - All quantity inputs for the same product
5855   * - WooCommerce add-to-cart button data attributes
5856   *
5857   * @param {Event} e - Change event object
5858   */
5859  $("body").on(
5860    "change",
5861    ".wcpt-quantity input.qty, select.wcpt-qty-select",
5862    function (e) {
5863      var $input = $(this),
5864        $product_rows = get_product_rows($input),
5865        $siblings = $product_rows
5866          .find("input.qty, select.wcpt-qty-select")
5867          .not(this),
5868        val = $input.val();
5869
5870      $siblings.val(val);
5871
5872      if ($input.closest(".wcpt-add-to-cart-wrapper").length) {
5873        return;
5874      }
5875
5876      var $wc_default_button = $(".add_to_cart_button", $product_rows);
5877      $wc_default_button.data("quantity", val);
5878      $wc_default_button.attr("data-quantity", val);
5879    },
5880  );
5881
5882  /**
5883   * Quantity Input Keyboard Navigation Handler
5884   * Manages up/down arrow key interactions for quantity adjustments:
5885   * - ArrowUp: Increments value by step amount
5886   * - ArrowDown: Decrements value by step amount
5887   * - Respects min/max boundaries
5888   * - Handles special cases for real vs visual minimum values
5889   *
5890   * @param {KeyboardEvent} e - The keyboard event object
5891   */
5892  $("body").on("keydown", ".wcpt-quantity .qty", function (e) {
5893    if (e.key !== "ArrowUp" && e.key !== "ArrowDown") return;
5894
5895    var $qty = $(this);
5896    var visualMin = $qty.attr("min") ? parseFloat($qty.attr("min")) : 0;
5897    var realMin = $qty.attr("data-wcpt-min")
5898      ? parseFloat($qty.attr("data-wcpt-min"))
5899      : visualMin;
5900    var step = $qty.attr("step") ? parseFloat($qty.attr("step")) : 1;
5901    var currentVal = parseFloat($qty.val()) || 0;
5902
5903    // Calculate next value
5904    var next =
5905      e.key === "ArrowUp"
5906        ? (currentVal * 1e12 + step * 1e12) / 1e12
5907        : (currentVal * 1e12 - step * 1e12) / 1e12;
5908
5909    // Apply same logic as plus/minus buttons
5910    if (e.key === "ArrowUp") {
5911      if (currentVal < realMin && next >= visualMin) {
5912        next = realMin;
5913      }
5914    } else {
5915      // ArrowDown
5916      if (currentVal >= realMin && next < realMin) {
5917        next = visualMin > 0 ? 0 : visualMin;
5918      } else if (next < visualMin) {
5919        next = 0;
5920      }
5921    }
5922
5923    $qty.val(next).change();
5924    e.preventDefault();
5925  });
5926
5927  /**
5928   * Handles clicks on disabled cart-related buttons and quantity controls.
5929   * Shows appropriate error messages when users try to interact with:
5930   *
5931   * For variable products:
5932   * - Products missing variation selection
5933   * - Products with no matching variations
5934   * - Products with unavailable variations
5935   *
5936   * For simple products:
5937   * - Out of stock products
5938   *
5939   * @param {Event} event - The click event object
5940   */
5941  $("body").on(
5942    "click",
5943    ".wcpt-plus.wcpt-qty-controller.wcpt-disabled, .wcpt-button-cart_ajax.wcpt-disabled, .wcpt-button-cart_redirect.wcpt-disabled, .wcpt-button-cart_refresh.wcpt-disabled, .wcpt-button-cart_checkout.wcpt-disabled",
5944    function () {
5945      var $product_row = $(this).closest(".wcpt-row"),
5946        product_type = $product_row.attr("data-wcpt-type");
5947
5948      if (product_type === "variable") {
5949        var product_data = $product_row.data();
5950
5951        if (!product_data.wcpt_variation_selected) {
5952          alert(wcpt_i18n.i18n_make_a_selection_text);
5953          return;
5954        }
5955
5956        if (!product_data.wcpt_variation_found) {
5957          alert(wcpt_i18n.i18n_no_matching_variations_text);
5958          return;
5959        }
5960
5961        if (
5962          !product_data.wcpt_variation ||
5963          !product_data.wcpt_variation.is_in_stock
5964        ) {
5965          alert(wcpt_i18n.i18n_unavailable_variation_text);
5966          return;
5967        }
5968      } else if ($product_row.hasClass("wcpt-out-of-stock")) {
5969        alert(wcpt_i18n.i18n_out_of_stock_text);
5970      }
5971    },
5972  );
5973
5974  // total
5975  $("body").on("keyup mouseup change", ".wcpt-quantity .qty", function () {
5976    update_row_total($(this).closest(".wcpt-row"));
5977  });
5978
5979  // -- name your price: ensure qty if name your price is used
5980  $("body").on(
5981    "keyup change",
5982    ".wcpt .wcpt-name-your-price--input",
5983    function (e) {
5984      var $nyp = $(this),
5985        nyp_val = $nyp.val(),
5986        $rows = wcpt_get_sibling_rows($nyp.closest(".wcpt-row")),
5987        $qty = $(".wcpt-quantity input.qty", $rows),
5988        qty_val = $qty.val(),
5989        $cb = $(".wcpt-cart-checkbox ", $rows);
5990
5991      nyp_validate($nyp);
5992
5993      // added val - add qty
5994      if (nyp_val && !qty_val) {
5995        $qty.val($qty.attr("min"));
5996
5997        // removed val - remove qty
5998      } else if (!nyp_val && qty_val) {
5999        var inital_val_type = $qty.attr("data-wcpt-nyp-initial-value"),
6000          val = $qty.attr("min");
6001
6002        if (inital_val_type === "empty") {
6003          val = "";
6004        } else if (inital_val_type === "0") {
6005          val = "0";
6006        }
6007
6008        $qty.val(val);
6009      }
6010
6011      update_row_total($(this).closest(".wcpt-row"));
6012
6013      if ($qty.length) {
6014        $qty.change();
6015      } else if ($cb.length) {
6016        $rows.trigger("wcpt_checkbox_change", !!$nyp.val());
6017      }
6018    },
6019  );
6020
6021  function update_row_total($row, force_qty) {
6022    var $rows = $row,
6023      $qty = $rows.find(".wcpt-quantity .qty").eq(0),
6024      qty = $qty.length ? parseFloat($qty.val() ? $qty.val() : 0) : 1,
6025      $total = $(".wcpt-total", $rows),
6026      $cb = $(".wcpt-cart-checkbox ", $rows),
6027      prev_total = parseFloat($total.attr("data-wcpt-in-cart-total") || 0),
6028      price = $rows.attr("data-wcpt-price")
6029        ? parseFloat($rows.attr("data-wcpt-price"))
6030        : 0,
6031      total = 0;
6032
6033    if (force_qty) {
6034      qty = force_qty;
6035    }
6036
6037    // variable
6038    if ($rows.hasClass("wcpt-product-type-variable")) {
6039      if (
6040        $rows.data("wcpt_variation_found") &&
6041        $rows.data("wcpt_variation") &&
6042        $rows.data("wcpt_variation").display_price
6043      ) {
6044        price = unformat_price_figure(
6045          $rows.data("wcpt_variation").display_price,
6046        );
6047      } else {
6048        price = 0;
6049      }
6050    }
6051
6052    // name your price
6053    if ($row.hasClass("wcpt-product-has-name-your-price")) {
6054      var $nyp = $(".wcpt-name-your-price--input", $rows);
6055      if ($nyp.filter(":visible").length) {
6056        // variable product compatibility
6057        price = $nyp.val() ? parseFloat($nyp.val()) : 0;
6058      }
6059    }
6060
6061    total = (qty * price).toFixed(2);
6062
6063    // if checkbox is unchecked and qty is not min then no total
6064    if (
6065      $cb.length &&
6066      !$rows.data("wcpt_checked") &&
6067      $qty.attr("data-wcpt-initial-value") !== "min"
6068    ) {
6069      total = 0;
6070    }
6071
6072    $total.each(function () {
6073      var $this = $(this),
6074        _total = total;
6075
6076      if ($this.hasClass("wcpt-total--include-total-in-cart")) {
6077        _total = parseFloat(_total) + parseFloat(prev_total);
6078      }
6079
6080      if (parseFloat(_total)) {
6081        $this
6082          .removeClass("wcpt-total--empty")
6083          .find(".wcpt-amount")
6084          .text(format_price_figure(_total));
6085      } else {
6086        $this.addClass("wcpt-total--empty");
6087      }
6088    });
6089
6090    $row.data("wcpt-total", total);
6091
6092    $row.trigger("wcpt_total_updated");
6093  }
6094
6095  // audio player
6096  $("body").on("click", ".wcpt-player__button", function () {
6097    var $button = $(this),
6098      $container = $button.closest(".wcpt-player"),
6099      src = $container.attr("data-wcpt-src"),
6100      loop = $container.attr("data-wcpt-loop"),
6101      $el = $container.data("wcpt-media-el");
6102
6103    if (!$el) {
6104      $el = $('<audio class="wcpt-audio-elm" src="' + src + '"></audio>');
6105      $container.append($el);
6106      $container.data("wcpt-media-el", $el);
6107
6108      if (loop) {
6109        $el.prop("loop", true);
6110      } else {
6111        $el.on("ended", function () {
6112          $container.toggleClass("wcpt-player--playing");
6113        });
6114      }
6115    }
6116
6117    if ($button.hasClass("wcpt-player__play-button")) {
6118      $el[0].play();
6119
6120      if (!$container.hasClass("wcpt-media-loaded")) {
6121        $el.on("canplay", function () {
6122          $container.addClass("wcpt-media-loaded");
6123        });
6124      }
6125
6126      $("audio.wcpt-audio-elm")
6127        .not($el)
6128        .each(function () {
6129          this.currentTime = 0;
6130          this.pause();
6131        });
6132
6133      // pause others
6134      var $other_players = $(".wcpt-player.wcpt-player--playing").not(
6135        $container,
6136      );
6137      $other_players.find(".wcpt-player__playing-button").click();
6138    } else if ($button.hasClass("wcpt-player__pause-button")) {
6139      $el[0].pause();
6140    } else if ($button.hasClass("wcpt-player__stop-button")) {
6141      $el[0].pause();
6142      $el[0].currentTime = 0;
6143    }
6144
6145    $container.toggleClass("wcpt-player--playing");
6146  });
6147
6148  // term click
6149  // -- trigger filter
6150  $("body").on("click", ".wcpt-trigger_filter > [data-wcpt-slug]", function () {
6151    var $this = $(this),
6152      slug = $this.attr("data-wcpt-slug"),
6153      taxonomy = $this.parent().attr("data-wcpt-taxonomy"),
6154      $container = $this.closest(".wcpt"),
6155      $nav = $container.find(".wcpt-navigation"),
6156      $option = $nav.find(
6157        '[data-wcpt-taxonomy="' +
6158          taxonomy +
6159          '"] [data-wcpt-slug="' +
6160          slug +
6161          '"]',
6162      ),
6163      $input = $("input", $option);
6164
6165    if (!$option.length) {
6166      return;
6167    }
6168
6169    // $nav.addClass("wcpt-force-hide-dropdown-menus");
6170    $input.prop("checked", !$input.prop("checked"));
6171    $nav.first().trigger("change");
6172  });
6173  // -- archive redirect
6174  $("body").on(
6175    "click",
6176    ".wcpt-archive_redirect > [data-wcpt-slug]",
6177    function () {
6178      var $this = $(this),
6179        url = $this.attr("data-wcpt-archive-url");
6180      if (!$this.is("a")) {
6181        window.location = url;
6182      }
6183    },
6184  );
6185
6186  // remove
6187  $("body").on(
6188    "click",
6189    ".wcpt-row:not(.wcpt-removing-product) .wcpt-remove:not(.wcpt-disabled)",
6190    function () {
6191      var $this = $(this),
6192        $row = ($product_row = get_product_rows($this)),
6193        product_id = $row.attr("data-wcpt-product-id"),
6194        variation_id = $row.attr("data-wcpt-variation-id"),
6195        params = {
6196          payload: {
6197            products: {},
6198            variations: {},
6199            overwrite_cart_qty: true,
6200          },
6201        };
6202
6203      if ($this.hasClass("wcpt-refresh-enabled")) {
6204        params.redirect = window.location.href;
6205      }
6206
6207      params.payload.products[product_id] = 0;
6208
6209      // variation
6210      if (variation_id) {
6211        params.payload.variations[product_id] = {};
6212        params.payload.variations[product_id][variation_id] = 0;
6213      }
6214
6215      // variable product
6216      if ($row.hasClass("wcpt-product-type-variable")) {
6217        params.payload.variations[product_id] = $.extend(
6218          {},
6219          wcpt_cart_result_cache.in_cart[product_id],
6220        );
6221        $.each(
6222          params.payload.variations[product_id],
6223          function (variation_id, qty) {
6224            params.payload.variations[product_id][variation_id] = 0;
6225          },
6226        );
6227      }
6228
6229      wcpt_cart(params);
6230    },
6231  );
6232
6233  // toggle content (show more / less)
6234  $("body").on("click", ".wcpt-toggle-trigger", function (e) {
6235    e.preventDefault();
6236    var $this = $(this);
6237    $this.closest(".wcpt-toggle-enabled").toggleClass("wcpt-toggle");
6238  });
6239
6240  // freeze table selectors for originals
6241  function wcpt_get_container_original_table($container) {
6242    return $container.find(".wcpt-table:visible").not(".frzTbl-clone-table");
6243  }
6244
6245  // get sibling tables under freeze table
6246  function wcpt_get_sibling_tables($table) {
6247    var $freeze_table = $table.closest(".frzTbl");
6248    if (!$freeze_table.length) {
6249      return $table;
6250    }
6251
6252    return $(".wcpt-table", $freeze_table);
6253  }
6254
6255  // get sibling rows under freeze table
6256  window.wcpt_get_sibling_rows = function ($row) {
6257    var $freeze_table = $row.closest(".frzTbl");
6258    if (!$freeze_table.length) {
6259      return $row;
6260    }
6261
6262    var product_id = $row.attr("data-wcpt-product-id"),
6263      variation_id = $row.attr("data-wcpt-variation-id"),
6264      row_selector;
6265
6266    if (variation_id) {
6267      row_selector =
6268        '[data-wcpt-variation-id="' +
6269        variation_id +
6270        '"].wcpt-row.wcpt-product-type-variation';
6271    } else {
6272      row_selector =
6273        '[data-wcpt-product-id="' +
6274        product_id +
6275        '"].wcpt-row:not(.wcpt-product-type-variation)';
6276    }
6277
6278    return $(row_selector, $freeze_table);
6279  };
6280
6281  // get original row under freeze table
6282  function wcpt_get_original_row($row) {
6283    var $sibling_rows = wcpt_get_sibling_rows($row);
6284
6285    $sibling_rows.each(function () {
6286      var $row = $(this);
6287      if (!$row.closest("table").hasClass("frzTbl-clone-table")) {
6288        $original = $row;
6289        return false;
6290      }
6291    });
6292
6293    return $original;
6294  }
6295
6296  // unique key per row (variation first, then product)
6297  function wcpt_get_row_key($row) {
6298    var variationId = $row.data("wcpt_variation_id");
6299    if (variationId) {
6300      return "v:" + String(variationId);
6301    }
6302    var variationIdAttr = $row.attr("data-wcpt-variation-id");
6303    if (variationIdAttr) {
6304      return "v:" + String(variationIdAttr);
6305    }
6306    var productId = $row.attr("data-wcpt-product-id");
6307    if (productId) {
6308      return "p:" + String(productId);
6309    }
6310    return "";
6311  }
6312
6313  window.wcpt_get_original_row = wcpt_get_original_row;
6314  window.wcpt_get_row_key = wcpt_get_row_key;
6315
6316  // checkbox
6317
6318  // -- $cb 'change' handler -> triggers '_wcpt_checkbox_change' on $row
6319  $("body").on("change", ".wcpt-cart-checkbox", function (e) {
6320    var $this = $(this),
6321      $row = $this.closest(".wcpt-row");
6322
6323    $row.trigger("_wcpt_checkbox_change", $this.prop("checked"));
6324  });
6325
6326  // -- $row '_wcpt_checkbox_change' handler -> triggers 'wcpt_checkbox_change' on original $row, sets same state on sibling $cb
6327  $("body").on("_wcpt_checkbox_change", ".wcpt-row", function (e, state) {
6328    var $this = $(this),
6329      $original_row = wcpt_get_original_row($this),
6330      $table = $original_row.closest("table"),
6331      $sibling_rows = wcpt_get_sibling_rows($this),
6332      $sibling_cbs = $(".wcpt-cart-checkbox", $sibling_rows);
6333
6334    // unnecessary trigger
6335    if (!$sibling_cbs.length || !$sibling_cbs.not(":disabled").length) {
6336      return;
6337    }
6338
6339    // $row .data()-> state
6340    $original_row.data("wcpt_checked", state);
6341
6342    // $table .data()-> checked_rows
6343    var $table_checked_rows = $table.data("wcpt_checked_rows")
6344      ? $table.data("wcpt_checked_rows")
6345      : $();
6346
6347    if (state) {
6348      $table_checked_rows = $table_checked_rows.add($original_row);
6349    } else {
6350      $table_checked_rows = $table_checked_rows.not($original_row);
6351    }
6352
6353    $table.data("wcpt_checked_rows", $table_checked_rows);
6354
6355    // publish event
6356    $original_row.trigger("wcpt_checkbox_change", state);
6357  });
6358
6359  // -- 'wcpt_checkbox_change' handler -> set the same state on siblings rows' $cb and data on sibling rows
6360  $("body").on("wcpt_checkbox_change", ".wcpt-row", function (e, state) {
6361    var $this = $(this),
6362      $sibling_rows = wcpt_get_sibling_rows($this),
6363      $sibling_cbs = $(".wcpt-cart-checkbox", $sibling_rows);
6364
6365    $sibling_rows.data("wcpt_checked", state);
6366    $sibling_cbs.prop("checked", state);
6367  });
6368
6369  // -- row html class
6370  $("body").on("wcpt_checkbox_change", ".wcpt-row", function (e, state) {
6371    var $this = $(this),
6372      $original_row = wcpt_get_original_row($this),
6373      $sibling_rows = wcpt_get_sibling_rows($this),
6374      html_class = "wcpt-row--checked";
6375
6376    if ($original_row.data("wcpt_checked")) {
6377      $sibling_rows.addClass(html_class);
6378    } else {
6379      $sibling_rows.removeClass(html_class);
6380    }
6381  });
6382
6383  // -- select variation
6384  $("body").on("select_variation", ".wcpt-row", function (e) {
6385    var $this = $(this),
6386      selected_variation = $this.data("wcpt_variation"),
6387      $cb = $this.find(".wcpt-cart-checkbox");
6388
6389    // reset
6390    $cb.removeAttr("disabled");
6391    $cb.removeClass("wcpt-disabled");
6392
6393    if (
6394      !selected_variation ||
6395      $.isEmptyObject(selected_variation) ||
6396      (!selected_variation.is_in_stock && !selected_variation.is_on_backorder)
6397    ) {
6398      $this.trigger("wcpt_checkbox_change", false);
6399      $cb.attr("disabled", true);
6400      $cb.addClass("wcpt-disabled");
6401    }
6402  });
6403
6404  // -- qty
6405  // -- -- '.qty' change handler -> trigger '_wcpt_checkbox_change' on $row
6406  setTimeout(function () {
6407    // too many 3rd party modules trigger $qty at page load
6408    $("body").on(
6409      "change",
6410      ".wcpt input.qty, .wcpt select.wcpt-qty-select",
6411      function (e) {
6412        var $this = $(this),
6413          $row = $this.closest(".wcpt-row"),
6414          $original_row = wcpt_get_original_row($row);
6415
6416        if (
6417          $this.closest("form.cart").length ||
6418          // don't permit auto-check when initial-val is min
6419          ($this.attr("data-wcpt-initial-value") === "min" &&
6420            !$original_row.data("wcpt_checked"))
6421        ) {
6422          return;
6423        }
6424
6425        $original_row.trigger(
6426          "_wcpt_checkbox_change",
6427          !!parseFloat($this.val()),
6428        );
6429      },
6430    );
6431  }, 1);
6432
6433  // -- -- raise qty to min or reduce to initial qty upon 'wcpt_checkbox_change'
6434  $("body").on("wcpt_checkbox_change", ".wcpt-row", function (e, state) {
6435    var $this = $(this),
6436      $sibling_rows = wcpt_get_sibling_rows($this),
6437      $sibling_qty = $("input.qty, select.wcpt-qty-select", $sibling_rows),
6438      $sibling_qty_wrappers = $(".wcpt-quantity", $sibling_rows);
6439
6440    $sibling_qty.each(function () {
6441      var $this = $(this),
6442        val = $this.val(),
6443        min = $this.attr("data-wcpt-min")
6444          ? parseFloat($this.attr("data-wcpt-min"))
6445          : $this.attr("min"),
6446        initial_qty = $this.attr("data-wcpt-initial-value");
6447
6448      if (state) {
6449        if (!val || val === "0" || isNaN(val)) {
6450          val = min;
6451        }
6452      } else {
6453        if (initial_qty == "empty") {
6454          val = "";
6455        } else if (initial_qty == "0") {
6456          val = 0;
6457        } else {
6458          val = min;
6459        }
6460      }
6461
6462      $this.val(val);
6463    });
6464
6465    limit_qty_controller($sibling_qty_wrappers);
6466  });
6467
6468  // -- -- update total upon 'wcpt_checkbox_change'
6469  $("body").on("wcpt_checkbox_change", ".wcpt-row", function (e, state) {
6470    var $this = $(this),
6471      $sibling_rows = wcpt_get_sibling_rows($this);
6472
6473    update_row_total($sibling_rows);
6474  });
6475
6476  // -- 'wcpt_checkbox_change' handler -> create / update / hide '.wcpt-cart-checkbox-trigger' button
6477  $("body").on("wcpt_checkbox_change", ".wcpt-row", wcpt_checkbox_trigger_init);
6478
6479  // Also refresh the global trigger after each layout (e.g. AJAX pagination)
6480  $("body").on("wcpt_layout", ".wcpt", function () {
6481    wcpt_checkbox_trigger_init();
6482  });
6483
6484  function wcpt_checkbox_trigger_init() {
6485    // global floating checkbox trigger (across all tables on the page)
6486    // setup / show trigger
6487    var $checkbox_trigger = $(".wcpt-cart-checkbox-trigger");
6488
6489    if (!$checkbox_trigger.length) {
6490      var html = $("#tmpl-wcpt-cart-checkbox-trigger").html();
6491      $checkbox_trigger = $(html).appendTo("body");
6492    } else {
6493      $checkbox_trigger.removeClass("wcpt-hide");
6494    }
6495
6496    // aggregate selection across ALL tables on the page,
6497    // using the per-container cross-page state on each $wcpt
6498    var totalQty = 0,
6499      totalCost = 0;
6500
6501    $(".wcpt").each(function () {
6502      var $container = $(this),
6503        table_id =
6504          typeof wcpt_util !== "undefined"
6505            ? wcpt_util.get_table_id($container)
6506            : "",
6507        tableState = $container.data("wcpt_all_checked") || {};
6508
6509      if (
6510        table_id &&
6511        window.wcpt_cart_checked_state &&
6512        window.wcpt_cart_checked_state[table_id]
6513      ) {
6514        tableState = window.wcpt_cart_checked_state[table_id];
6515      }
6516
6517      if (!tableState || typeof tableState !== "object") {
6518        return;
6519      }
6520
6521      Object.keys(tableState).forEach(function (key) {
6522        var entry = tableState[key];
6523        if (!entry) {
6524          return;
6525        }
6526
6527        var q = !isNaN(parseFloat(entry.qty)) ? parseFloat(entry.qty) : 1,
6528          t = !isNaN(parseFloat(entry.total)) ? parseFloat(entry.total) : 0;
6529
6530        totalQty = (totalQty * 1e12 + q * 1e12) / 1e12;
6531        totalCost = (totalCost * 1e12 + t * 1e12) / 1e12;
6532      });
6533    });
6534
6535    if (!totalQty) {
6536      $checkbox_trigger.hide();
6537      return;
6538    }
6539
6540    $checkbox_trigger
6541      .data({
6542        wcpt_qty: totalQty,
6543        wcpt_cost: totalCost,
6544      })
6545      .find(".wcpt-total-selected")
6546      .text(totalQty)
6547      .end()
6548      .find(".wcpt-total-selected-cost")
6549      .text(format_price_figure(totalCost));
6550
6551    $checkbox_trigger
6552      .trigger("wcpt_checkbox_trigger_updating")
6553      .show()
6554      .trigger("wcpt_checkbox_trigger_updated");
6555  }
6556
6557  window.wcpt_checkbox_trigger_init = wcpt_checkbox_trigger_init;
6558
6559  // -- heading
6560  // -- -- check all $cb in table via $heading_cb '.wcpt-cart-checkbox-heading'
6561  $("body").on("click", ".wcpt-cart-checkbox-heading", function () {
6562    var $this = $(this),
6563      state = $this.prop("checked"),
6564      $container = $this.closest(".wcpt"),
6565      $table = wcpt_get_container_original_table($container),
6566      $rows = $(".wcpt-row", $table);
6567
6568    $rows.trigger("_wcpt_checkbox_change", state);
6569    $("wcpt-cart-checkbox--last-clicked", $table).removeClass(
6570      "wcpt-cart-checkbox--last-clicked",
6571    );
6572  });
6573
6574  // -- -- auto toggle heading checkbox
6575  $("body").on("wcpt_checkbox_change", ".wcpt-row", function (e, state) {
6576    // Debounce multiple calls
6577    clearTimeout(window.wcpt_cb_heading);
6578    window.wcpt_cb_heading = setTimeout(function () {
6579      $(".wcpt").each(function () {
6580        var $container = $(this),
6581          $heading_cb = $container.find(".wcpt-cart-checkbox-heading:visible"),
6582          $checkboxes = $container.find(".wcpt-cart-checkbox"),
6583          $available_cbs = $checkboxes.filter(":visible").not(":disabled"),
6584          $checked_cbs = $available_cbs.filter(":checked"),
6585          all_checked =
6586            $available_cbs.length &&
6587            $checked_cbs.length === $available_cbs.length;
6588
6589        // Always update heading checkbox state
6590        $heading_cb.prop("checked", all_checked);
6591      });
6592    }, 100);
6593  });
6594
6595  // -- shift + click
6596  // -- -- record shift key
6597  $("body").on("keydown", function (e) {
6598    if (e.shiftKey) {
6599      wcpt_shiftKey = true;
6600      $("body").one("keyup", function (e) {
6601        wcpt_shiftKey = false;
6602      });
6603    }
6604  });
6605
6606  // -- -- act upon $cb shift key selected by user
6607  $("body").on("change", ".wcpt-cart-checkbox", function (e, wcpt_sync) {
6608    if (wcpt_sync) {
6609      return false;
6610    }
6611
6612    var $this = $(this),
6613      $table = $this.closest(".wcpt-table"),
6614      $cb = $(".wcpt-cart-checkbox", $table);
6615    (($last_clicked = $cb.filter(".wcpt-cart-checkbox--last-clicked")),
6616      (checked = $this.prop("checked")));
6617
6618    $last_clicked.removeClass("wcpt-cart-checkbox--last-clicked");
6619    $this.addClass("wcpt-cart-checkbox--last-clicked");
6620
6621    if (
6622      $last_clicked.length &&
6623      typeof wcpt_shiftKey !== "undefined" &&
6624      wcpt_shiftKey
6625    ) {
6626      var min = Math.min($cb.index($this), $cb.index($last_clicked)),
6627        max = Math.max($cb.index($this), $cb.index($last_clicked));
6628
6629      $cb
6630        .filter(function () {
6631          var $this = $(this),
6632            index = $cb.index($this);
6633
6634          if ($this.prop("disabled")) {
6635            return false;
6636          }
6637
6638          if (index >= min && index <= max) {
6639            return true;
6640          } else {
6641            return false;
6642          }
6643        })
6644        .prop("checked", checked)
6645        .trigger("change", true);
6646    }
6647  });
6648
6649  // -- select / clear all
6650  $("body").on(
6651    "click",
6652    ".wcpt-add-selected__select-all, .wcpt-add-selected__clear-all",
6653    function (e) {
6654      var $this = $(this),
6655        isSelectAll = $this.hasClass("wcpt-add-selected__select-all"),
6656        $container = $this.closest(".wcpt"),
6657        $table = wcpt_get_container_original_table($container),
6658        $rows = $(".wcpt-row", $table);
6659
6660      $rows.trigger("_wcpt_checkbox_change", isSelectAll);
6661
6662      // If this is clear-all, also clear cross-page selection state for this table
6663      if (!isSelectAll && typeof wcpt_util !== "undefined") {
6664        var table_id = wcpt_util.get_table_id($container);
6665        if (table_id) {
6666          // delegate actual clearing to the persistence module
6667          $(document).trigger("wcpt_clear_all_checked_for_table", [
6668            table_id,
6669            $container,
6670          ]);
6671
6672          // ensure the per-container mirror used by the global trigger is cleared
6673          $container.data("wcpt_all_checked", {});
6674
6675          // immediately recompute / hide the global floating trigger if needed
6676          wcpt_checkbox_trigger_init();
6677        }
6678      }
6679      $("wcpt-cart-checkbox--last-clicked", $table).removeClass(
6680        "wcpt-cart-checkbox--last-clicked",
6681      );
6682    },
6683  );
6684
6685  // -- -- toggle the buttons
6686  $("body").on(
6687    "wcpt_checkbox_change",
6688    ".wcpt-row",
6689    update_table_add_selected_to_cart,
6690  );
6691
6692  function update_table_add_selected_to_cart() {
6693    var _ = this;
6694    setTimeout(function () {
6695      var $this = $(_),
6696        $container = $this.closest(".wcpt"),
6697        $table = wcpt_get_container_original_table($container),
6698        // cross-page selection state for this table (from persistence module)
6699        tableState = $container.data("wcpt_all_checked") || {};
6700      $add_checked = $(".wcpt-add-selected:visible", $container);
6701
6702      $add_checked.removeClass(
6703        "wcpt-add-selected--unselected wcpt-add-selected--single-item-selected",
6704      );
6705
6706      var keys = Object.keys(tableState),
6707        totalQty = 0,
6708        totalCost = 0;
6709
6710      if (keys.length) {
6711        // derive total quantity and total cost across all pages from persisted state
6712        keys.forEach(function (k) {
6713          var entry = tableState[k];
6714          if (!entry) {
6715            return;
6716          }
6717          var q = !isNaN(parseFloat(entry.qty)) ? parseFloat(entry.qty) : 1,
6718            t = !isNaN(parseFloat(entry.total)) ? parseFloat(entry.total) : 0;
6719
6720          totalQty = (totalQty * 1e12 + q * 1e12) / 1e12;
6721          totalCost = (totalCost * 1e12 + t * 1e12) / 1e12;
6722        });
6723
6724        $(".wcpt-total-selected", $add_checked).text(totalQty);
6725        $(".wcpt-total-selected-cost .wcpt-amount", $add_checked).text(
6726          format_price_figure(totalCost),
6727        );
6728
6729        if (totalQty == 1) {
6730          $add_checked.addClass("wcpt-add-selected--single-item-selected");
6731        }
6732      } else {
6733        // nothing selected across any page
6734        $add_checked.addClass("wcpt-add-selected--unselected");
6735        $(".wcpt-total-selected", $add_checked).text(0);
6736        $(".wcpt-total-selected-cost .wcpt-amount", $add_checked).text(
6737          format_price_figure(0),
6738        );
6739      }
6740    }, 100);
6741  }
6742
6743  // price decimal
6744  function format_price_figure(price) {
6745    price = parseFloat(price);
6746
6747    // remove decimal if it is unnecessary ie .00
6748    if (price !== parseInt(price)) {
6749      price = parseFloat(price).toFixed(wcpt_params.price_decimals);
6750    } else {
6751      price = parseInt(price);
6752    }
6753
6754    // decimal separator
6755    price = (price + "").replace(".", wcpt_params.price_decimal_separator);
6756
6757    // thousands separator
6758    var decimal_split = price.split(wcpt_params.price_decimal_separator);
6759    ((non_decimal = decimal_split[0].toString()), (decimal = decimal_split[1]));
6760
6761    if (non_decimal.length > 3) {
6762      formatted_non_decimal =
6763        non_decimal.slice(0, -3) +
6764        wcpt_params.price_thousand_separator +
6765        non_decimal.slice(-3);
6766      price =
6767        formatted_non_decimal +
6768        (decimal ? wcpt_params.price_decimal_separator + decimal : "");
6769    }
6770
6771    return price;
6772  }
6773
6774  function unformat_price_figure(price) {
6775    price = price.toString();
6776
6777    // remove thousands separator
6778    price = price.replace(wcpt_params.price_thousand_separator, "");
6779
6780    // replace decimal separator
6781    price = price.replace(wcpt_params.price_decimal_separator, ".");
6782
6783    return parseFloat(price);
6784  }
6785
6786  // follow wc settings for price display
6787  function format_price(num) {
6788    if (!num && num !== "0" && num !== 0) {
6789      return "";
6790    }
6791
6792    return wcpt_params.price_format
6793      .replace("%1$s", wcpt_params.currency_symbol)
6794      .replace("%2$s", format_price_figure(num));
6795  }
6796
6797  // -- add to cart
6798  $("body").on(
6799    "click",
6800    ".wcpt-cart-checkbox-trigger, .wcpt-cart-checkbox-trigger--local",
6801    wcpt_cart_checkbox,
6802  );
6803  function wcpt_cart_checkbox() {
6804    var $this = $(this),
6805      products = {},
6806      variations = {},
6807      attributes = {},
6808      addons = {},
6809      measurement = {},
6810      nyp = {}, // name your price
6811      $checked_rows = $(),
6812      $table = $(),
6813      tableContainersById = {};
6814
6815    if ($this.hasClass("wcpt-cart-checkbox-trigger")) {
6816      // global
6817      var $container = $(".wcpt");
6818
6819      $container.each(function () {
6820        var $c = $(this),
6821          table_id =
6822            typeof wcpt_util !== "undefined"
6823              ? wcpt_util.get_table_id($c)
6824              : false,
6825          $orig_table = wcpt_get_container_original_table($c);
6826
6827        if (table_id) {
6828          tableContainersById[table_id] = $c;
6829        }
6830
6831        $table = $table.add($orig_table);
6832      });
6833
6834      $this.addClass("wcpt-hide");
6835    } else {
6836      // local
6837      var $container = $this.closest(".wcpt"),
6838        table_id =
6839          typeof wcpt_util !== "undefined"
6840            ? wcpt_util.get_table_id($container)
6841            : false;
6842
6843      if (table_id) {
6844        tableContainersById[table_id] = $container;
6845      }
6846
6847      $table = wcpt_get_container_original_table($container);
6848    }
6849
6850    $table.each(function () {
6851      var $this = $(this);
6852      $table_checked_rows = $this.data("wcpt_checked_rows")
6853        ? $this.data("wcpt_checked_rows")
6854        : $();
6855
6856      $checked_rows = $checked_rows.add($table_checked_rows);
6857    });
6858
6859    $checked_rows.each(function () {
6860      var $row = $(this),
6861        product_id = $row.attr("data-wcpt-product-id"),
6862        variation_id = false,
6863        variation_attributes = false,
6864        $qty = $(".qty, .wcpt-qty-select", $row).first(),
6865        val = parseFloat($qty.length ? $qty.val() : 1);
6866      if (isNaN(val)) {
6867        val = 0;
6868      }
6869
6870      if (typeof products[product_id] === "undefined") {
6871        products[product_id] = val;
6872      } else {
6873        products[product_id] += val; // variation
6874      }
6875
6876      // variable
6877      if ($row.hasClass("wcpt-product-type-variable")) {
6878        var data = $row.data();
6879
6880        if (
6881          data.wcpt_variation_selected &&
6882          data.wcpt_variation_available &&
6883          data.wcpt_complete_match &&
6884          data.wcpt_variation_id
6885        ) {
6886          variation_id = data.wcpt_variation_id;
6887
6888          if (data.wcpt_attributes) {
6889            variation_attributes = data.wcpt_attributes;
6890          }
6891        }
6892
6893        // variation
6894      } else if ($row.hasClass("wcpt-product-type-variation")) {
6895        variation_id = $row.attr("data-wcpt-variation-id");
6896        variation_attributes = JSON.parse(
6897          $row.attr("data-wcpt-variation-attributes"),
6898        );
6899      }
6900
6901      if (variation_id) {
6902        if (!variations[product_id]) {
6903          variations[product_id] = {};
6904        }
6905
6906        if (!variations[product_id][variation_id]) {
6907          variations[product_id][variation_id] = val;
6908        }
6909
6910        if (variation_attributes) {
6911          attributes[variation_id] = variation_attributes;
6912        }
6913      }
6914
6915      // addons
6916      if (
6917        $row.hasClass("wcpt-product-has-addons") &&
6918        wcpt_row_has_inline_addons_form($row)
6919      ) {
6920        addons[product_id] = wcpt_get_addons($row);
6921      }
6922
6923      // measurement
6924      if ($row.hasClass("wcpt-product-has-measurement")) {
6925        measurement[product_id] = wcpt_get_measurement($row);
6926      }
6927
6928      // name your price
6929      if ($row.hasClass("wcpt-product-has-name-your-price")) {
6930        nyp[product_id] = wcpt_get_nyp($row);
6931      }
6932    });
6933
6934    // Off-page selections from the checkbox persistence module
6935    if (window.wcpt_cart_checkbox_persist) {
6936      window.wcpt_cart_checkbox_persist.aggregateOffPageIntoPayload(
6937        tableContainersById,
6938        {
6939          products: products,
6940          variations: variations,
6941          attributes: attributes,
6942        },
6943      );
6944      window.wcpt_cart_checkbox_persist.clearTables(tableContainersById);
6945    }
6946
6947    // Uncheck all currently checked rows on the page before wcpt_cart,
6948    // otherwise totals / visual state may be inconsistent.
6949    $checked_rows.trigger("_wcpt_checkbox_change", false);
6950
6951    // When every selection was off-page, $checked_rows is empty so the
6952    // checkbox-change → update_table_add_selected_to_cart path never runs.
6953    // Refresh the local "Add selected" button(s) from cleared state.
6954    $.each(tableContainersById, function (id, $c) {
6955      if ($c && $c.length) {
6956        update_table_add_selected_to_cart.call($c.get(0));
6957      }
6958    });
6959
6960    var payload = {
6961      products: products,
6962      addons: addons,
6963      measurement: measurement,
6964      variations: variations,
6965      attributes: attributes,
6966      nyp: nyp,
6967    };
6968
6969    // mark this cart request as originating from a checkbox-trigger bulk add
6970    var checkboxScope = $this.hasClass("wcpt-cart-checkbox-trigger")
6971      ? "global"
6972      : "local";
6973
6974    wcpt_cart({
6975      payload: payload,
6976      redirect: $this.attr("data-wcpt-redirect-url"),
6977      external_payload: {
6978        wcpt_checkbox_bulk: checkboxScope,
6979      },
6980    });
6981  }
6982
6983  // addons
6984  // True when addon fields are printed and visible in the row's cart form.
6985  // Select variation "attribute dropdowns" embeds a cart_form with only attributes
6986  // visible — hidden addon markup there must not block the product form modal.
6987  window.wcpt_row_has_inline_addons_form = function ($row) {
6988    return !!$(
6989      [
6990        ".wcpt-add-to-cart-wrapper .prad-addons-wrapper",
6991        ".wcpt-add-to-cart-wrapper .wc-pao-addon",
6992        ".wcpt-add-to-cart-wrapper .wc-pao-addons-container",
6993        ".wcpt-add-to-cart-wrapper #product-addons-total",
6994        ".wcpt-add-to-cart-wrapper .wcpa_form_outer",
6995      ].join(", "),
6996      wcpt_get_sibling_rows($row),
6997    ).filter(":visible").length;
6998  };
6999
7000  window.wcpt_get_addons = function ($row) {
7001    var $form = $(
7002        ".wcpt-add-to-cart-wrapper form",
7003        wcpt_get_sibling_rows($row),
7004      ),
7005      addons = {};
7006
7007    // WooCommerce Custom Product Addons
7008    var $wcpa_fields = $form.find(".wcpa_form_outer");
7009    if ($wcpa_fields.length) {
7010      var $fields = $wcpa_fields,
7011        $_form = $("<form>");
7012
7013      $_form
7014        .append($fields.clone())
7015        .find("select")
7016        .each(function () {
7017          // cloned select needs correct value
7018          var $this = $(this),
7019            name = $this.attr("name"),
7020            value = $fields.find('[name="' + name + '"]select').val();
7021          $this.val(value);
7022        });
7023
7024      $.each($_form.serializeArray(), function (i, field) {
7025        var field_name = field.name,
7026          suffix_index = field_name.indexOf("--wcpt");
7027
7028        if (-1 !== suffix_index) {
7029          // must be checkbox or radio
7030          field_name = field_name.substring(0, suffix_index);
7031
7032          if (field.name.slice(-1) == "]") {
7033            // checkbox
7034            if (!addons[field_name]) {
7035              addons[field_name] = [];
7036            }
7037            addons[field_name].push(field.value);
7038            return;
7039          } else {
7040            // radio
7041            addons[field_name] = field.value;
7042          }
7043        } else {
7044          // other field types
7045          addons[field_name] = field.value;
7046        }
7047      });
7048
7049      // official WooCommerce Product Addons
7050    } else {
7051      $.each($form.serializeArray(), function (i, field) {
7052        var field_name = field.name;
7053
7054        if (field_name.slice(-2) == "[]") {
7055          field_name = field_name.substring(0, field_name.length - 2);
7056        }
7057
7058        if (typeof addons[field_name] === "undefined") {
7059          addons[field_name] = field.value; // add prop
7060        } else {
7061          if (typeof addons[field_name] !== "object") {
7062            // make it an array
7063            addons[field_name] = [addons[field_name]];
7064          }
7065          addons[field_name].push(field.value); // update prop
7066        }
7067      });
7068    }
7069
7070    return addons;
7071  };
7072
7073  // measurement
7074  window.wcpt_get_measurement = function ($row) {
7075    var $price_calculator = $(
7076        ".wcpt-add-to-cart-wrapper form #price_calculator",
7077        wcpt_get_sibling_rows($row),
7078      ),
7079      measurement = {};
7080
7081    $("input", $price_calculator).each(function () {
7082      var $this = $(this);
7083      measurement[$this.attr("name")] = $this.val();
7084    });
7085
7086    return measurement;
7087  };
7088
7089  // name your price
7090  window.wcpt_get_nyp = function ($row) {
7091    var $nyp = get_nyp_input_element($row),
7092      val = 0;
7093
7094    if ($nyp.length) {
7095      val = $nyp.val();
7096    }
7097
7098    return val;
7099  };
7100
7101  function get_nyp_input_element($row) {
7102    return $(".wcpt-name-your-price--input", wcpt_get_sibling_rows($row));
7103  }
7104
7105  $("body").on("keyup input", ".wcpt-search-filter-options", function (e) {
7106    var $this = $(this),
7107      val = $this.val().toLowerCase().trim(),
7108      $filter = $this.closest(".wcpt-filter"),
7109      $option = $(".wcpt-dropdown-option", $filter);
7110
7111    e.preventDefault();
7112
7113    if (!val) {
7114      $option.show();
7115
7116      $(".wcpt-filter-hierarchy-accordion--open", $filter).removeClass(
7117        "wcpt-filter-hierarchy-accordion--open",
7118      );
7119
7120      $("input[type=radio], input[type=checkbox]", $option).each(function () {
7121        var $this = $(this);
7122        if ($this.is(":checked")) {
7123          $this
7124            .closest(".wcpt-dropdown-option.wcpt-filter-hierarchy-accordion")
7125            .addClass("wcpt-filter-hierarchy-accordion--open");
7126        }
7127      });
7128      return;
7129    }
7130
7131    $option.each(function () {
7132      var $this = $(this),
7133        label = $this.text().toLowerCase().trim();
7134      if (label.indexOf(val) == -1) {
7135        $this.hide();
7136      } else {
7137        $this.show();
7138        $this
7139          .closest(".wcpt-dropdown-option.wcpt-filter-hierarchy-accordion")
7140          .addClass("wcpt-filter-hierarchy-accordion--open");
7141      }
7142    });
7143  });
7144
7145  // global product search shortcode
7146  $("body").on(
7147    "change keydown keyup",
7148    ".wcpt-global-search__keyword-input",
7149    function () {
7150      var $this = $(this),
7151        $search = $this.closest(".wcpt-global-search");
7152
7153      if ($this.val()) {
7154        $search.removeClass("wcpt-global-search--empty");
7155      } else {
7156        $search.addClass("wcpt-global-search--empty");
7157      }
7158    },
7159  );
7160
7161  $(".wcpt-global-search__keyword-input").trigger("change");
7162
7163  // -- facade
7164  $("body").on("change", ".wcpt-global-search__category-selector", function () {
7165    var $this = $(this),
7166      value = $this.val(),
7167      $option = $('option[value="' + value + '"]', $this),
7168      text = $option.text().trim(),
7169      $facade = $this.siblings(".wcpt-global-search__category-selector-facade");
7170
7171    $(".wcpt-global-search__category-selector-facade__text", $facade).text(
7172      text,
7173    );
7174  });
7175
7176  // -- redirect empty
7177  $("body").on("submit", ".wcpt-global-search__form", function (e) {
7178    var $this = $(this),
7179      keyword = $this.find(".wcpt-global-search__keyword-input").val(),
7180      $select = $this.find(".wcpt-global-search__category-selector"),
7181      clear_redirect_url = $this.attr("data-wcpt-clear-redirect-url"),
7182      clear_redirect = $this.attr("data-wcpt-clear-redirect"),
7183      redirect = $this.attr("data-wcpt-redirect"),
7184      category = $(".wcpt-global-search__category-selector", $this).val(),
7185      action = $this.attr("action");
7186
7187    // select 'All' category
7188    if (!keyword) {
7189      if (clear_redirect !== "category") {
7190        $select.val("").change();
7191      }
7192
7193      // redirect
7194      if (clear_redirect_url) {
7195        e.preventDefault();
7196        window.location = clear_redirect_url;
7197      }
7198    } else if (redirect == "category" && category) {
7199      e.preventDefault();
7200      window.location = wcpt_product_category_links[category] + "?s=" + keyword;
7201    } else if (redirect == "shop") {
7202      e.preventDefault();
7203      var url = action + "?s=" + keyword;
7204      if (category) {
7205        url += "&wcpt_search_category=" + category;
7206      }
7207      window.location = url;
7208    } else if (redirect == "search") {
7209      e.preventDefault();
7210      var url = action + "?s=" + keyword + "&post_type=product";
7211      if (category) {
7212        url += "&wcpt_search_category=" + category;
7213      }
7214      window.location = url;
7215    }
7216  });
7217
7218  // -- clear
7219  $("body").on("click", ".wcpt-global-search__clear", function () {
7220    var $this = $(this),
7221      $input = $this.siblings(".wcpt-global-search__keyword-input"),
7222      $form = $this.closest(".wcpt-global-search__form");
7223    $input.val("");
7224    $form.submit();
7225  });
7226
7227  // -- focus / blur
7228  $("body")
7229    .on("focus", ".wcpt-global-search__keyword-input", function () {
7230      var $this = $(this),
7231        $wrapper = $this.parent();
7232      $wrapper.addClass("wcpt-global-search__keyword-input-wrapper--focus");
7233    })
7234    .on("blur", ".wcpt-global-search__keyword-input", function () {
7235      var $this = $(this),
7236        $wrapper = $this.parent();
7237      $wrapper.removeClass("wcpt-global-search__keyword-input-wrapper--focus");
7238    });
7239
7240  // cart
7241
7242  window.wcpt_cart = function (params) {
7243    // maybe add to pending requests
7244    if (
7245      // there has been at least one cart call earlier
7246      window.wcpt_cart_call_id &&
7247      // no results cache ie, no call is resolved yet
7248      (!window.wcpt_cart_result_cache ||
7249        // the call id in result cache (ie last resolved call) is uneqal to current call id
7250        window.wcpt_cart_call_id !=
7251          window.wcpt_cart_result_cache.payload.wcpt_cart_call_id)
7252    ) {
7253      !window.wcpt_cart_pending_req_params &&
7254        (window.wcpt_cart_pending_req_params = {});
7255
7256      // -- add this req to pending as well and return
7257      $.extend(true, window.wcpt_cart_pending_req_params, params);
7258      return;
7259    }
7260
7261    // begin / increment the cart call counter
7262    window.wcpt_cart_call_id = window.wcpt_cart_call_id
7263      ? window.wcpt_cart_call_id + 1
7264      : 1;
7265
7266    var _params = {
7267      payload: {
7268        wcpt_cart_call_id: window.wcpt_cart_call_id,
7269      },
7270      before: false,
7271      always: false,
7272      redirect: false,
7273      external_payload: {},
7274    };
7275    params = $.extend({}, _params, params ? params : {});
7276
7277    params.payload.wcpt_cart_call_id = window.wcpt_cart_call_id;
7278    params.payload.cart_widget_permitted = cart_widget_permitted();
7279
7280    // view
7281
7282    $(".wcpt-cart-widget").addClass("wcpt-cart-widget--loading"); // cart widget
7283
7284    // product req data used by view
7285    var product_request = {
7286      products: params.payload.products
7287        ? $.extend({}, params.payload.products)
7288        : {},
7289      variations: params.payload.variations
7290        ? $.extend({}, params.payload.variations)
7291        : {},
7292    };
7293
7294    if (
7295      // add in variation modal request data so it may be used by the view
7296      typeof params.payload !== "undefined" &&
7297      params.payload.variation_form &&
7298      typeof params.external_payload !== "undefined" &&
7299      params.external_payload["add-to-cart"]
7300    ) {
7301      var product_id = params.external_payload["add-to-cart"],
7302        variation_id = params.external_payload["variation_id"]
7303          ? params.external_payload["variation_id"]
7304          : "",
7305        quantity = parseFloat(params.external_payload["quantity"]);
7306
7307      product_request.products[product_id] = quantity;
7308
7309      if (variation_id) {
7310        product_request.variations[product_id] = {};
7311        product_request.variations[product_id][variation_id] = quantity;
7312      }
7313    }
7314
7315    $(".wcpt-row:visible").each(function () {
7316      var $row = $(this),
7317        $sibling_rows = wcpt_get_sibling_rows($row),
7318        product_id = $row.attr("data-wcpt-product-id"),
7319        variation_id = $row.attr("data-wcpt-variation-id"),
7320        $button = $(cart_button_selector, $row),
7321        in_cart = parseFloat($row.attr("data-wcpt-in-cart"));
7322
7323      // before request, update view like - add loading badge, animate Remove el
7324      if (product_request.products) {
7325        $.each(product_request.products, function (id, qty) {
7326          if (id == product_id) {
7327            if (
7328              // product is a variation, and not part of the cart request
7329              variation_id &&
7330              product_request.variations &&
7331              product_request.variations[product_id] &&
7332              typeof product_request.variations[product_id][variation_id] ==
7333                "undefined"
7334            ) {
7335              return;
7336            }
7337
7338            loading_badge_on_button($button);
7339
7340            if (0 === qty && in_cart) {
7341              // removing
7342              $row.addClass("wcpt-removing-product");
7343            } else {
7344              // adding
7345              $row.addClass("wcpt-adding-product");
7346
7347              var $qty = $("input.qty", $row),
7348                $wcpt_qty = $qty.not(".cart .qty"),
7349                initial = $wcpt_qty.attr("data-wcpt-initial-value"),
7350                min = parseFloat($wcpt_qty.attr("data-wcpt-min")),
7351                return_to_initial = $wcpt_qty.attr(
7352                  "data-wcpt-return-to-initial",
7353                );
7354
7355              if (return_to_initial) {
7356                $sibling_rows.trigger("_wcpt_checkbox_change", false);
7357
7358                if (initial == "min") {
7359                  $qty.val(min);
7360                } else if (initial === "0") {
7361                  $qty.val(0);
7362                } else if (initial === "empty") {
7363                  $qty.val("");
7364                }
7365              }
7366
7367              var $wrapper = $qty.closest(".wcpt-quantity");
7368              if ($wrapper.length) {
7369                limit_qty_controller($wrapper);
7370              }
7371
7372              // dropdown
7373              var $qty = $("select.wcpt-qty-select", $row),
7374                $first = $qty.children("option:first-child");
7375
7376              $qty.val($first.attr("value"));
7377            }
7378
7379            // total
7380            var $total = $(".wcpt-total", $sibling_rows),
7381              force_total_qty = qty;
7382
7383            if (
7384              variation_id &&
7385              product_request.variations &&
7386              product_request.variations[product_id] &&
7387              product_request.variations[product_id][variation_id]
7388            ) {
7389              force_total_qty =
7390                product_request.variations[product_id][variation_id];
7391            }
7392
7393            // don't allow it to be reset by qty going to 0
7394            if ($total.length) {
7395              update_row_total($sibling_rows, force_total_qty);
7396            }
7397          }
7398        });
7399      }
7400    });
7401
7402    var data = $.extend(
7403      {},
7404      {
7405        wcpt_payload: params.payload,
7406        lang: wcpt_i18n.lang,
7407      },
7408      params.external_payload ? params.external_payload : {},
7409    );
7410
7411    $("body").trigger("wcpt_cart_request", data);
7412
7413    // use cache
7414    if (params.payload.use_cache && window.wcpt_cart_result_cache) {
7415      window.wcpt_cart_result_cache.payload.wcpt_cart_call_id =
7416        window.wcpt_cart_call_id;
7417      $("body").trigger("wcpt_cart", window.wcpt_cart_result_cache);
7418
7419      // fetch fresh
7420    } else {
7421      $.post(
7422        wcpt_params.wc_ajax_url.replace("%%endpoint%%", "wcpt_cart"),
7423        data,
7424        function (result) {
7425          window.wcpt_cart_result_cache = $.extend({}, result, {
7426            success: true,
7427            notice: "",
7428            is_cache: true,
7429          });
7430          $("body").trigger("wcpt_cart", result);
7431        },
7432      ).always(function (result) {
7433        if (params.always) {
7434          params.always(result);
7435        }
7436
7437        // resolve any pending requests
7438        if (window.wcpt_cart_pending_req_params) {
7439          wcpt_cart(window.wcpt_cart_pending_req_params);
7440          window.wcpt_cart_pending_req_params = false;
7441        }
7442
7443        if (params.redirect) {
7444          if (
7445            // redirect after user reads error
7446            !result.success &&
7447            result.notice
7448          ) {
7449            $("body").on(
7450              "click touchstart",
7451              ".wcpt-notice-wrapper",
7452              function () {
7453                window.location = params.redirect;
7454              },
7455            );
7456          } else {
7457            // redirect immediately
7458            window.location = params.redirect;
7459          }
7460        }
7461      });
7462    }
7463  };
7464
7465  $("body").on("wcpt_cart", function (e, result) {
7466    // do not resolve if there are pending cart calls
7467    if (window.wcpt_cart_pending_req_params) {
7468      return;
7469    }
7470
7471    // cart widget
7472    if (result.payload.wcpt_cart_call_id === window.wcpt_cart_call_id) {
7473      // latest call resolved
7474      $(".wcpt-cart-widget ").removeClass("wcpt-cart-widget--loading");
7475    }
7476
7477    // error
7478    if (!result.success && result.notice) {
7479      var $body = $("body"),
7480        $notice = $(
7481          '<div class="wcpt-notice-wrapper">' + result.notice + "</div>",
7482        );
7483      $body.append($notice);
7484      $body.one("click", function () {
7485        $notice.remove();
7486      });
7487    }
7488
7489    if (result.cart_widget && cart_widget_permitted()) {
7490      var $body = $("body"),
7491        $old = $(".wcpt-cart-widget").not(".wcpt-cart-checkbox-trigger"),
7492        $new = $(result.cart_widget);
7493
7494      if ($new.hasClass("wcpt-hide")) {
7495        $body.removeClass("wcpt-cart-widget-visible");
7496      } else {
7497        $body.addClass("wcpt-cart-widget-visible");
7498      }
7499
7500      $body.append($new);
7501      $old.remove();
7502    }
7503
7504    // If this cart response came from a checkbox-trigger bulk add-to-cart,
7505    // clear all selection state across tables (including persisted state)
7506    // so nothing remains checked on any page.
7507    if (
7508      result &&
7509      result.external_payload &&
7510      result.external_payload.wcpt_checkbox_bulk
7511    ) {
7512      if (typeof wcpt_util !== "undefined") {
7513        $(".wcpt").each(function () {
7514          var $container = $(this),
7515            table_id = wcpt_util.get_table_id($container);
7516
7517          if (!table_id) {
7518            return;
7519          }
7520
7521          // Use the built-in API to clear persisted cross-page state
7522          $(document).trigger("wcpt_clear_all_checked_for_table", [
7523            table_id,
7524            $container,
7525          ]);
7526
7527          // Uncheck any rows currently in the DOM for this table
7528          $container.find(".wcpt-row").each(function () {
7529            $(this).trigger("_wcpt_checkbox_change", false);
7530          });
7531        });
7532      }
7533
7534      // Hide the global floating trigger (if present)
7535      $(".wcpt-cart-checkbox-trigger").addClass("wcpt-hide").hide();
7536    }
7537
7538    // added / removed
7539    var added = (removed = false);
7540
7541    if (result.payload.products && result.in_cart) {
7542      $.each(result.payload.products, function (product_id, product_qty) {
7543        var variation_id = (variation_qty = false);
7544
7545        if (
7546          result.payload.variations &&
7547          typeof result.payload.variations[product_id] !== "undefined"
7548        ) {
7549          variation_id = Object.keys(result.payload.variations[product_id])[0];
7550          variation_qty = Object.values(
7551            result.payload.variations[product_id],
7552          )[0];
7553        }
7554
7555        if (product_qty === "0") {
7556          // req. type: "remove"
7557          if (
7558            $.isEmptyObject(result.in_cart) ||
7559            !result.in_cart.length ||
7560            !result.in_cart[product_id] ||
7561            (variation_id && !result.in_cart[product_id][variation_id])
7562          ) {
7563            removed = true;
7564          }
7565        } else {
7566          // req. type: "add"
7567          if (
7568            !$.isEmptyObject(result.in_cart) &&
7569            result.in_cart[product_id] &&
7570            (!variation_id || result.in_cart[product_id][variation_id])
7571          ) {
7572            added = true;
7573          }
7574        }
7575      });
7576    }
7577
7578    if (result.payload.variation_form) {
7579      added = true;
7580    }
7581
7582    var in_cart_products = []; // variations and parent variable products at same level
7583
7584    if (result.in_cart) {
7585      $.each(result.in_cart, function (key, val) {
7586        // variable product
7587        if (typeof val === "object") {
7588          // related to parent variable product
7589          var qty = 0,
7590            total = 0;
7591
7592          // iterate over variations
7593          $.each(val, function (key2, val2) {
7594            var _total = result.in_cart_total[key][key2];
7595
7596            // insert variations as separate entries
7597            in_cart_products.push({
7598              id: key2,
7599              type: "variation",
7600              quantity: val2,
7601              total: _total,
7602            });
7603
7604            // sum up variation quantities and totals (decimal fix)
7605            qty = (qty * 1000 + parseFloat(val2) * 1000) / 1000;
7606            total = (total * 1000 + parseFloat(_total) * 1000) / 1000;
7607          });
7608
7609          // insert parent variable product
7610          in_cart_products.push({
7611            id: key,
7612            type: "variable",
7613            quantity: qty,
7614            total: total,
7615          });
7616        } else {
7617          // simple
7618          in_cart_products.push({
7619            id: key,
7620            type: "simple",
7621            quantity: val,
7622            total: result.in_cart_total[key],
7623          });
7624        }
7625      });
7626    }
7627
7628    // view
7629
7630    var $rows = $(".wcpt-row");
7631
7632    $rows.each(function () {
7633      var $row = $(this),
7634        type = $row.attr("data-wcpt-type"),
7635        product_id = $row.attr("data-wcpt-product-id"),
7636        variation_id = $row.attr("data-wcpt-variation-id"),
7637        id = type == "variation" ? variation_id : product_id,
7638        cart_item = false,
7639        min_call_id = $row.data("wcpt-min-cart-call-id");
7640
7641      if (min_call_id > result.payload.wcpt_cart_call_id) {
7642        return;
7643      }
7644
7645      $.each(in_cart_products, function (key, item) {
7646        if (
7647          // variation
7648          (variation_id && variation_id == item.id) ||
7649          // regular product
7650          (!variation_id && product_id == item.id)
7651        ) {
7652          cart_item = item;
7653          return false;
7654        }
7655      });
7656
7657      // wcpt initiated adding / removing process is complete
7658      $row.removeClass("wcpt-adding-product wcpt-removing-product");
7659
7660      // update 'in cart' qty
7661      var cart_qty = cart_item ? cart_item.quantity : 0;
7662      $row.attr("data-wcpt-in-cart", cart_qty);
7663      $in_cart = $(".wcpt-in-cart", $row).text(cart_qty);
7664      $in_cart.each(function () {
7665        var $this = $(this),
7666          template = $this.attr("data-wcpt-template"),
7667          empty_template = $this.attr("data-wcpt-empty-template");
7668
7669        if (cart_qty) {
7670          $this.text(template.replace("{n}", cart_qty));
7671        } else {
7672          $this.text(empty_template);
7673        }
7674      });
7675
7676      // update 'in cart' total
7677      var cart_total = cart_item ? cart_item.total : 0;
7678      $(".wcpt-total", $row).attr("data-wcpt-in-cart-total", cart_total);
7679      update_row_total($row);
7680
7681      // badge
7682
7683      if (!$row.hasClass("wcpt-adding-product")) {
7684        $button = $(cart_button_selector, $row);
7685        add_count_badge_to_button(cart_qty, $button);
7686
7687        if (!$button.hasClass("wcpt-out-of-stock")) {
7688          enable_button($button);
7689        }
7690      }
7691
7692      // remove
7693
7694      if (!$row.hasClass("wcpt-removing-product")) {
7695        var $remove = $(".wcpt-remove", $row);
7696
7697        // -- enable
7698        if (cart_item) {
7699          $remove.removeClass("wcpt-disabled");
7700
7701          // -- disable
7702        } else {
7703          // $remove.addClass('wcpt-disabled').removeClass('wcpt-removing');
7704          $remove.addClass("wcpt-disabled");
7705
7706          // wc button
7707          $(".add_to_cart_button", $row)
7708            .removeClass("added")
7709            .next(".added_to_cart")
7710            .remove();
7711        }
7712      }
7713    });
7714
7715    if (
7716      result.fragments &&
7717      !result.payload.skip_cart_triggers &&
7718      !result.is_cache
7719    ) {
7720      var trigger_lock = true,
7721        $body = $("body"),
7722        $button = false;
7723
7724      // fake button for single add / remove
7725      if (
7726        result.payload &&
7727        result.payload.products &&
7728        Object.keys(result.payload.products).length === 1
7729      ) {
7730        $.each(result.payload.products, function (product_id, qty) {
7731          if (qty) {
7732            // added
7733            $button = $('<button data-product_id="' + product_id + '">');
7734          } else {
7735            // removed
7736            $button = $('<a href="" data-product_id="' + product_id + '">');
7737          }
7738        });
7739      }
7740
7741      if (added || result.payload.force_refresh_cart) {
7742        $body.trigger("added_to_cart", [
7743          result.fragments,
7744          result.cart_hash,
7745          $button,
7746          trigger_lock,
7747        ]);
7748      }
7749
7750      if (removed) {
7751        $body.trigger("removed_from_cart", [
7752          result.fragments,
7753          result.cart_hash,
7754          $button,
7755          trigger_lock,
7756        ]);
7757      }
7758    }
7759  });
7760
7761  $("body").on(
7762    "added_to_cart removed_from_cart",
7763    function (e, fragment, cart_hash, $button, trigger_lock) {
7764      if (!trigger_lock) {
7765        wcpt_cart({
7766          payload: { skip_cart_triggers: true },
7767        });
7768      }
7769    },
7770  );
7771
7772  // PRO
7773
7774  $("body").on("click", ".wcpt-nav-redirect-option", function (e) {
7775    var $this = $(this),
7776      $link = $this.find("a.wcpt-nav-redirect-link"),
7777      url = $link.attr("href");
7778
7779    if (
7780      $(e.target).closest(".wcpt-filter-hierarchy-accordion__trigger").length
7781    ) {
7782      return;
7783    }
7784
7785    window.location.href = url;
7786  });
7787
7788  // table heading offset based on nav header
7789  $("body").on("wcpt_layout", ".wcpt", function (e, data) {
7790    var $this = $(this),
7791      $freeze_nav_sidebar = $(
7792        ".wcpt-navigation.wcpt-left-sidebar.wcpt-sticky:visible",
7793        $this,
7794      ),
7795      $freeze_nav_header = $(
7796        ".wcpt-navigation.wcpt-header.wcpt-sticky:visible",
7797        $this,
7798      ),
7799      $freeze_table_heading = $(
7800        ".frzTbl-fixed-heading-wrapper-outer:visible",
7801        $this,
7802      ),
7803      sc_attrs = $this.data("wcpt_sc_attrs"),
7804      device = get_device(),
7805      offset = parseInt(
7806        sc_attrs[device + "_freeze_heading_offset"]
7807          ? sc_attrs[device + "_freeze_heading_offset"]
7808          : sc_attrs[device + "_scroll_offset"]
7809            ? sc_attrs[device + "_scroll_offset"]
7810            : 0,
7811      );
7812
7813    $freeze_nav_sidebar.css("top", offset);
7814
7815    if (device == "laptop") {
7816      $freeze_nav_header.css("top", offset);
7817
7818      offset += parseInt($freeze_nav_header.outerHeight());
7819
7820      $freeze_table_heading.css({ top: offset });
7821    } else {
7822      offset += parseInt($freeze_nav_sidebar.outerHeight());
7823      $freeze_nav_header.css({ top: offset });
7824
7825      offset += $freeze_nav_header.outerHeight();
7826      $freeze_table_heading.css({ top: offset });
7827    }
7828  });
7829
7830  // cart widget permission
7831  function cart_widget_permitted() {
7832    var url = window.location.href.split("?")[0],
7833      include_match = false,
7834      exclude_match = false,
7835      include_urls = false,
7836      exclude_urls = false,
7837      site_url = wcpt_params.site_url + "/";
7838
7839    if (!wcpt_params.cart_widget_enabled_site_wide && !$(".wcpt").length) {
7840      return false;
7841    }
7842
7843    if (wcpt_params.cart_widget_include_urls.trim()) {
7844      include_urls = wcpt_params.cart_widget_include_urls
7845        .trim()
7846        .replace(/\r\n/g, "\n")
7847        .split("\n");
7848    }
7849
7850    if (wcpt_params.cart_widget_exclude_urls.trim()) {
7851      exclude_urls = wcpt_params.cart_widget_exclude_urls
7852        .trim()
7853        .replace(/\r\n/g, "\n")
7854        .split("\n");
7855    }
7856
7857    if (include_urls.length) {
7858      include_match = false;
7859      $.each(include_urls, function (i, path) {
7860        path = path.trim();
7861
7862        if (path.indexOf(site_url) === 0) {
7863          path = path.substring(site_url.length, path.length);
7864        }
7865
7866        if (path.trim() == "/") {
7867          if (url === site_url) {
7868            include_match = true;
7869          }
7870          return;
7871        }
7872
7873        if (path.trim() == "/*") {
7874          include_match = true;
7875          return;
7876        }
7877
7878        path = path.replace(/(^\s*\/)|(\/\s*$)/g, ""); // ensure no slash at start or end
7879        if ("*" === path.substr(-1)) {
7880          var remove = 1;
7881          if ("/*" === path.substr(-2)) {
7882            remove = 2;
7883          }
7884          if (
7885            -1 !==
7886            url.indexOf(
7887              site_url + path.substring(0, path.length - remove) + "/",
7888            )
7889          ) {
7890            include_match = true;
7891          }
7892        } else {
7893          path += "/";
7894          if (url == site_url + path) {
7895            include_match = true;
7896          }
7897        }
7898      });
7899
7900      if (!include_match) {
7901        return false;
7902      }
7903    }
7904
7905    if (exclude_urls.length) {
7906      exclude_match = false;
7907
7908      $.each(exclude_urls, function (i, path) {
7909        path = path.trim();
7910
7911        if (path.indexOf(site_url) === 0) {
7912          path = path.substring(site_url.length, path.length);
7913        }
7914
7915        if (path.trim() == "/") {
7916          if (url === site_url) {
7917            exclude_match = true;
7918          }
7919          return;
7920        }
7921
7922        if (path.trim() == "/*") {
7923          exclude_match = true;
7924          return;
7925        }
7926
7927        path = path.replace(/(^\s*\/)|(\/\s*$)/g, ""); // ensure no slash at start or end
7928        if ("*" === path.substr(-1)) {
7929          var remove = 1;
7930          if ("/*" === path.substr(-2)) {
7931            remove = 2;
7932          }
7933
7934          if (
7935            -1 !==
7936            url.indexOf(
7937              site_url + path.substring(0, path.length - remove) + "/",
7938            )
7939          ) {
7940            exclude_match = true;
7941          }
7942        } else {
7943          path += "/";
7944          if (url == site_url + path) {
7945            exclude_match = true;
7946          }
7947        }
7948      });
7949
7950      if (exclude_match) {
7951        return false;
7952      }
7953    }
7954
7955    return true;
7956  }
7957
7958  // cart widget close x
7959  $("body").on("click", ".wcpt-cart-checkbox-trigger__close", function (e) {
7960    e.stopPropagation();
7961    var $cart_widget = $(this).closest(".wcpt-cart-checkbox-trigger");
7962
7963    if (typeof wcpt_util !== "undefined") {
7964      $(".wcpt").each(function () {
7965        var $container = $(this),
7966          table_id = wcpt_util.get_table_id($container);
7967
7968        if (!table_id) {
7969          return;
7970        }
7971
7972        $(document).trigger("wcpt_clear_all_checked_for_table", [
7973          table_id,
7974          $container,
7975        ]);
7976
7977        $container.find(".wcpt-row").each(function () {
7978          $(this).trigger("_wcpt_checkbox_change", false);
7979        });
7980      });
7981    }
7982
7983    wcpt_checkbox_trigger_init();
7984    $cart_widget.addClass("wcpt-hide").hide();
7985  });
7986
7987  // multirange
7988
7989  // -- input[type=number] change
7990  $("body").on(
7991    "input change",
7992    ".wcpt-range-input-min, .wcpt-range-input-max",
7993    function () {
7994      var $this = $(this),
7995        $container = $this.closest(".wcpt-range-options-wrapper"),
7996        $min = $container.find(".wcpt-range-input-min"),
7997        min_val = parseFloat($min.val()),
7998        $max = $container.find(".wcpt-range-input-max"),
7999        max_val = parseFloat($max.val());
8000
8001      $range = $container.find(".wcpt-range-slider.original");
8002
8003      if (!$range.length) {
8004        return;
8005      }
8006
8007      // max step fix
8008      var actual_max = parseFloat($this.attr("data-wcpt-actual-max")),
8009        val = parseFloat($this.val());
8010
8011      if (!isNaN(actual_max) && !isNaN(val) && val > actual_max) {
8012        $this.val(actual_max);
8013      }
8014
8015      // handle cleared min/max inputs
8016      if (isNaN(min_val)) {
8017        min_val = parseFloat($range.attr("min"));
8018        $min.val(min_val);
8019      }
8020
8021      if (isNaN(max_val)) {
8022        max_val = parseFloat($range.attr("max"));
8023        $max.val(max_val);
8024      }
8025
8026      $range.val(min_val + "," + max_val);
8027    },
8028  );
8029
8030  // -- input[type=range] change
8031  $("body").on("input change", ".wcpt-range-slider", function () {
8032    var $this = $(this),
8033      $container = $this.closest(".wcpt-range-options-wrapper"),
8034      $min = $(".wcpt-range-input-min", $container),
8035      $max = $(".wcpt-range-input-max", $container),
8036      $range_original = $container.find(".wcpt-range-slider.original"),
8037      $range_ghost = $container.find(".wcpt-range-slider.ghost"),
8038      min = parseFloat($range_original.prop("valueLow")),
8039      max = parseFloat($range_original.prop("valueHigh")),
8040      permitted_max = parseFloat($this.attr("max")),
8041      step = parseFloat($this.attr("step"));
8042
8043    // range input max fix
8044    // if( max + step > permitted_max ){
8045    //   max = permitted_max; // sending this to input[type="number"]
8046    // }
8047
8048    $min.val(min);
8049    $max.val(max);
8050  });
8051
8052  // switch elements based on selected variation
8053  $("body").on(
8054    "select_variation",
8055    ".wcpt-product-type-variable",
8056    function (e, data) {
8057      var $row = get_product_rows($(this)),
8058        $items = $(
8059          ".wcpt-variation-description__item, .wcpt-variation-content",
8060          $row,
8061        );
8062
8063      $items.hide();
8064
8065      if ($row.data("wcpt_variation_selected")) {
8066        var variation_id = $row.data("wcpt_variation_id");
8067        $items.filter("[data-wcpt-variation-id=" + variation_id + "]").show();
8068      }
8069    },
8070  );
8071
8072  // name your price
8073  $("body").on(
8074    "select_variation",
8075    ".wcpt-product-type-variable",
8076    function (e, data) {
8077      var $row = get_product_rows($(this)),
8078        $input = $(".wcpt-name-your-price--input", $row),
8079        initial_value_field = $input.attr("data-wcpt-nyp-initial-value-field"),
8080        input = false,
8081        min = false,
8082        max = false,
8083        suggested = false;
8084
8085      // if price has no variable switch, always stays "$min - $max" then just replace it completely with "from $_min"
8086      // if it has variable switch
8087      // then hide it whenever selected variation has nyp
8088
8089      $row.removeClass("wcpt-product-has-name-your-price");
8090
8091      if ($row.data("wcpt_variation_selected")) {
8092        // variation selected
8093        var variation = $row.data("wcpt_variation");
8094
8095        if (variation.is_nyp) {
8096          input = true;
8097          min = variation.minimum_price;
8098          max = variation.maximum_price;
8099          suggested = variation.suggested_price;
8100
8101          $row.addClass("wcpt-product-has-name-your-price");
8102        }
8103      } else {
8104        // variation not selected
8105        input = false;
8106      }
8107
8108      // no nyp input field
8109      if (!input) {
8110        $input.parent().addClass("wcpt-hide");
8111        $row
8112          .find("wcpt-price.wcpt-variable-switch")
8113          .removeClass("wcpt-hide--name-your-price");
8114
8115        // found nyp input field
8116      } else {
8117        $input.parent().removeClass("wcpt-hide");
8118
8119        if (min) {
8120          $input.attr({
8121            min: min,
8122            "data-wcpt-nyp-minimum-price": min,
8123          });
8124        } else {
8125          $input.attr({
8126            min: 0,
8127            "data-wcpt-nyp-minimum-price": 0,
8128          });
8129        }
8130
8131        if (max) {
8132          $input.attr("max", max);
8133
8134          $input.attr({
8135            max: max,
8136            "data-wcpt-nyp-maximum-price": max,
8137          });
8138        } else {
8139          $input.attr({
8140            max: "",
8141            "data-wcpt-nyp-maximum-price": "",
8142          });
8143        }
8144
8145        if (initial_value_field && !$input.val()) {
8146          var value = variation[initial_value_field + "_price"]
8147            ? variation[initial_value_field + "_price"]
8148            : "";
8149          $input.val(value);
8150        }
8151
8152        $row
8153          .find("wcpt-price.wcpt-variable-switch")
8154          .addClass("wcpt-hide--name-your-price");
8155      }
8156
8157      if (!min) {
8158        $row.find(".wcpt-name-your-price--minimum").addClass("wcpt-hide");
8159      } else {
8160        $row.find(".wcpt-name-your-price--minimum").removeClass("wcpt-hide");
8161        $row.find(".wcpt-name-your-price--minimum .wcpt-amount").text(min);
8162      }
8163
8164      if (!max) {
8165        $row.find(".wcpt-name-your-price--maximum").addClass("wcpt-hide");
8166      } else {
8167        $row.find(".wcpt-name-your-price--maximum").removeClass("wcpt-hide");
8168        $row.find(".wcpt-name-your-price--maximum .wcpt-amount").text(max);
8169      }
8170
8171      if (!suggested) {
8172        $row.find(".wcpt-name-your-price--suggested").addClass("wcpt-hide");
8173      } else {
8174        $row.find(".wcpt-name-your-price--suggested").removeClass("wcpt-hide");
8175        $row
8176          .find(".wcpt-name-your-price--suggested .wcpt-amount")
8177          .text(suggested);
8178      }
8179
8180      $input.change();
8181    },
8182  );
8183
8184  // -- checkbox
8185  $("body").on("wcpt_checkbox_change", ".wcpt-row", function (e, checked) {
8186    var $this = $(this),
8187      $rows = wcpt_get_sibling_rows($this.closest(".wcpt-row")),
8188      $nyp = get_nyp_input_element($rows);
8189
8190    if (checked) {
8191      nyp_validate($nyp);
8192    } else if (!$nyp.val()) {
8193      nyp_hide_error($nyp);
8194    }
8195  });
8196
8197  // -- validate
8198  function nyp_validate($nyp) {
8199    var $nyp_wrapper = $nyp.parent(),
8200      nyp_val = $nyp.val(),
8201      nyp_min = $nyp.attr("data-wcpt-nyp-minimum-price"),
8202      nyp_max = $nyp.attr("data-wcpt-nyp-maximum-price"),
8203      message_template = "",
8204      $error = "",
8205      $row = $nyp.closest(".wcpt-row"),
8206      checked = $row.data("wcpt_checked");
8207
8208    if (
8209      (nyp_val && nyp_min && parseFloat(nyp_val) < parseFloat(nyp_min)) ||
8210      (checked && !nyp_val && nyp_min)
8211    ) {
8212      $nyp_wrapper.addClass(
8213        "wcpt-name-your-price-wrapper--input-error wcpt-name-your-price-wrapper--input-error--min-price",
8214      );
8215
8216      $error = $(
8217        ".wcpt-name-your-price-input-error-message--min-price",
8218        $nyp_wrapper,
8219      );
8220      message_template = $error.attr("data-wcpt-error-message-template");
8221      $error.text(
8222        message_template.replace("[min]", format_price_figure(nyp_min)),
8223      );
8224    } else if (
8225      nyp_val &&
8226      nyp_max &&
8227      parseFloat(nyp_val) > parseFloat(nyp_max)
8228    ) {
8229      $nyp_wrapper.addClass(
8230        "wcpt-name-your-price-wrapper--input-error wcpt-name-your-price-wrapper--input-error--max-price",
8231      );
8232
8233      $error = $(
8234        ".wcpt-name-your-price-input-error-message--max-price",
8235        $nyp_wrapper,
8236      );
8237      message_template = $error.attr("data-wcpt-error-message-template");
8238      $error.text(
8239        message_template.replace("[max]", format_price_figure(nyp_max)),
8240      );
8241    } else {
8242      $nyp_wrapper.removeClass(
8243        "wcpt-name-your-price-wrapper--input-error wcpt-name-your-price-wrapper--input-error--min-price wcpt-name-your-price-wrapper--input-error--max-price",
8244      );
8245    }
8246  }
8247
8248  // -- hide validation errors
8249  function nyp_hide_error($nyp) {
8250    $nyp
8251      .parent()
8252      .removeClass(
8253        "wcpt-name-your-price-wrapper--input-error wcpt-name-your-price-wrapper--input-error--min-price wcpt-name-your-price-wrapper--input-error--max-price",
8254      );
8255  }
8256
8257  // trigger nav feedback from range slider and min max
8258  $("body").on("change", ".wcpt-range-options-wrapper input", function () {
8259    var $this = $(this),
8260      $filter = $this.closest(".wcpt-filter");
8261
8262    $filter.find("input[type=radio]").prop("checked", false);
8263    nav_filter_feedback($this.closest(".wcpt-navigation"));
8264  });
8265
8266  // refresh / block table
8267  // -- lock
8268  $("body").on("wcpt_cart_request", function (e, data) {
8269    if (
8270      data.wcpt_payload.skip_cart_triggers ||
8271      data.wcpt_payload.use_cache ||
8272      data.is_cache
8273    ) {
8274      return;
8275    }
8276
8277    $(".wcpt").each(function () {
8278      var $container = $(this),
8279        sc_attrs = JSON.parse($container.attr("data-wcpt-sc-attrs"));
8280
8281      if (sc_attrs.refresh_table) {
8282        $container.addClass("wcpt-loading wcpt-refreshing");
8283      } else if (sc_attrs.block_table) {
8284        $container.addClass("wcpt-loading");
8285      }
8286    });
8287  });
8288  // -- refresh
8289  $("body").on("wcpt_cart", function (e, result) {
8290    if (
8291      result.payload.skip_cart_triggers ||
8292      result.payload.use_cache ||
8293      result.is_cache
8294    ) {
8295      return;
8296    }
8297
8298    $(".wcpt").each(function () {
8299      var $container = $(this),
8300        sc_attrs = JSON.parse($container.attr("data-wcpt-sc-attrs")),
8301        table_id = $container.attr("data-wcpt-table-id");
8302
8303      if (sc_attrs.refresh_table) {
8304        $.each(window.wcpt_cache.data, function (key, val) {
8305          if (
8306            key.indexOf("?" + table_id + "_") !== -1 ||
8307            key.indexOf("&" + table_id + "_") !== -1
8308          ) {
8309            delete window.wcpt_cache.data[key];
8310          }
8311        });
8312
8313        attempt_ajax($container, "", true, "refresh_table");
8314      } else if (sc_attrs.block_table) {
8315        $container.removeClass("wcpt-loading");
8316      }
8317    });
8318  });
8319
8320  // switching variation should update Button element's product link
8321  $("body").on(
8322    "select_variation",
8323    ".wcpt-product-type-variable",
8324    function (e, data) {
8325      var $row = get_product_rows($(this)),
8326        $link_button = $(
8327          ".wcpt-button[data-wcpt-link-code='product_link']",
8328          $row,
8329        );
8330
8331      if ($link_button.length) {
8332        var original_link = $link_button.attr("data-wcpt-product-link"),
8333          new_link = "";
8334
8335        if (!original_link) {
8336          original_link = $link_button.attr("href");
8337          $link_button.attr("data-wcpt-product-link", original_link);
8338        }
8339
8340        if (data.variation_id) {
8341          new_link = original_link + "?";
8342          $.each(data.attributes, function (key, value) {
8343            new_link = new_link + key + "=" + value + "&";
8344          });
8345          new_link = new_link + "variation_id=" + data.variation_id;
8346          $link_button.attr("href", new_link);
8347        } else {
8348          $link_button.attr("href", original_link);
8349        }
8350      }
8351    },
8352  );
8353
8354  // berocket
8355  $(document).on("berocket_ajax_filtering_end", function (e) {
8356    $(".wcpt").each(function () {
8357      after_every_load($(this));
8358    });
8359  });
8360
8361  // waveplayer
8362
8363  // -- holds the detached waveplayer instances
8364  window.$wcpt_waveplayer_preserve = $();
8365
8366  //-- avoid losing functionality on cloned players during freeze table
8367  $("body").on("wcpt_layout", ".wcpt", function () {
8368    var $this = $(this);
8369
8370    if (typeof WavePlayer !== "undefined") {
8371      WavePlayer.loadInstances();
8372
8373      if (!$wcpt_waveplayer_preserve.length) {
8374        return;
8375      }
8376
8377      var $replace = get_matching_waveplayer_elm($this);
8378
8379      if ($replace.length) {
8380        $replace.replaceWith($wcpt_waveplayer_preserve);
8381        $wcpt_waveplayer_preserve
8382          .find(".wvpl-waveform canvas")
8383          .each(function () {
8384            var $this = $(this);
8385            $this.width($this.attr("data-wcpt-last-width"));
8386            $this.height($this.attr("data-wcpt-last-height"));
8387          });
8388
8389        waveplayer_active_ui_feedback.call($wcpt_waveplayer_preserve.get(0));
8390
8391        $wcpt_waveplayer_preserve = $();
8392
8393        WavePlayer.redrawAllInstances();
8394      }
8395    }
8396  });
8397
8398  // -- check for and return waveplayer element in container that needs to be replaced
8399  function get_matching_waveplayer_elm($container) {
8400    if (WavePlayer.persistentTrack && WavePlayer.instances.length) {
8401      var instance = false,
8402        track_index = false;
8403
8404      $.each(WavePlayer.instances, function (_instance_index, _instance) {
8405        var $_instances = $(_instance.node);
8406        if (
8407          $_instances.is(":visible") &&
8408          $_instances.parent().hasClass("wcpt-waveplayer-container")
8409        ) {
8410          var $row = $_instances.closest(".wcpt-row");
8411
8412          if ($row.length) {
8413            $.each(_instance.tracks, function (_track_index, _track) {
8414              if (
8415                _track.file === WavePlayer.persistentTrack.file &&
8416                _track.product_id === WavePlayer.persistentTrack.product_id
8417              ) {
8418                instance = _instance;
8419                track_index = _track_index;
8420              }
8421            });
8422          }
8423        }
8424      });
8425
8426      if (instance) {
8427        return $(instance.node).parent(".wcpt-waveplayer-container");
8428      }
8429    }
8430
8431    return $();
8432  }
8433
8434  // -- add to cart
8435  $("body").on("added_to_cart", function (e, fragment, cart_hash, $button) {
8436    if (typeof WavePlayer === "undefined" || !$button) {
8437      return;
8438    }
8439
8440    var product_id = $button.attr("data-product_id");
8441    if (typeof WavePlayer.updateTrackCartStatus !== "undefined") {
8442      WavePlayer.updateTrackCartStatus(product_id, "add");
8443    }
8444
8445    $(".wvpl-cart[data-product_id=" + product_id + "]")
8446      .attr("title", WavePlayer.__("Add to cart", "waveplayer"))
8447      .attr("data-event", "goToCart")
8448      .attr("data-callback", "goToCart")
8449      .removeClass("wvpl-add_to_cart")
8450      .addClass("wvpl-in_cart")
8451      .removeClass("wvpl-spin");
8452  });
8453
8454  // -- row color
8455  $("body").on(
8456    "click",
8457    ".wcpt-row .wcpt-waveplayer-container",
8458    waveplayer_active_ui_feedback,
8459  );
8460
8461  // -- active feedback
8462  function waveplayer_active_ui_feedback() {
8463    var $this = $(this),
8464      active_row_background_color = $this.attr(
8465        "data-wcpt-waveplayer-active-row-background-color",
8466      ),
8467      active_row_outline_color = $this.attr(
8468        "data-wcpt-waveplayer-active-row-outline-color",
8469      )
8470        ? $this.attr("data-wcpt-waveplayer-active-row-outline-color")
8471        : "#4198de",
8472      active_row_outline_width = $this.attr(
8473        "data-wcpt-waveplayer-active-row-outline-width",
8474      )
8475        ? parseFloat(
8476            $this.attr("data-wcpt-waveplayer-active-row-outline-width"),
8477          ) + "px"
8478        : "1px",
8479      $rows = wcpt_get_sibling_rows($this.closest(".wcpt-row"));
8480
8481    $rows.addClass("wcpt-waveplayer-active").css({
8482      background: active_row_background_color
8483        ? active_row_background_color
8484        : "",
8485      outline: active_row_outline_width + " solid " + active_row_outline_color,
8486    });
8487
8488    $(".wcpt-row").not($rows).removeClass("wcpt-waveplayer-active").css({
8489      background: "",
8490      outline: "",
8491    });
8492  }
8493
8494  // 3rd party tab and accordion compatibility
8495
8496  // -- elementor accordion, tab & toggle
8497  $("body").on("click", ".elementor-tab-title", function () {
8498    var $this = $(this),
8499      $container = $this.closest(".elementor-widget-container"),
8500      $wcpt = $container.find(".wcpt");
8501
8502    $wcpt.trigger("wcpt_layout", { source: "elementor__tab" });
8503  });
8504
8505  // -- divi
8506  // -- -- tab
8507  $("ul.et_pb_tabs_controls > li").on("click", function () {
8508    var $this = $(this),
8509      index = $this.index(),
8510      $controls = $this.closest(".et_pb_tabs_controls"),
8511      $tabs = $controls.siblings(".et_pb_all_tabs"),
8512      $container = $tabs.children().eq(index),
8513      $wcpt = $container.find(".wcpt");
8514
8515    setTimeout(function () {
8516      $wcpt.trigger("wcpt_layout", { source: "divi__tab" });
8517    }, 700);
8518  });
8519  // -- -- accordion
8520  $(".et_pb_toggle_title").on("click", function () {
8521    var $this = $(this),
8522      $content = $this.next(".et_pb_toggle_content"),
8523      $wcpt = $content.find(".wcpt");
8524
8525    setTimeout(function () {
8526      $wcpt.trigger("wcpt_layout", { source: "divi__tab" });
8527    }, 700);
8528  });
8529
8530  // -- beaver builder
8531  $("body").on("click", ".fl-accordion-button, .fl-tabs-label", function () {
8532    var $this = $(this),
8533      $container = $this.closest(".fl-accordion-item, .fl-tabs-panel"),
8534      $wcpt = $container.find(".wcpt");
8535
8536    $wcpt.trigger("wcpt_layout", { source: "beaver_builder__tab" });
8537  });
8538
8539  // -- shortcode ultimate
8540  // -- -- tab
8541  $("body").on("click", ".su-tabs-nav span", function () {
8542    var $this = $(this),
8543      index = $this.index(),
8544      $nav = $this.closest(".su-tabs-nav"),
8545      $panes = $nav.siblings(".su-tabs-panes"),
8546      $container = $panes.children().eq(index),
8547      $wcpt = $container.find(".wcpt");
8548
8549    $wcpt.trigger("wcpt_layout", { source: "shortcode_ultimate__tab" });
8550  });
8551  // -- -- accordion
8552  $("body").on("click", ".su-spoiler-title", function () {
8553    var $this = $(this),
8554      $container = $this.closest(".su-spoiler"),
8555      $wcpt = $container.find(".wcpt");
8556
8557    $wcpt.trigger("wcpt_layout", {
8558      source: "shortcode_ultimate__accordion",
8559    });
8560  });
8561
8562  // -- wp bakery visual composer
8563  // -- -- tab
8564  $("body").on("click", ".vce-classic-tabs-tab", function () {
8565    var $this = $(this),
8566      index = $this.index(),
8567      $nav = $this.closest(".vce-classic-tabs-container"),
8568      $panes = $nav.siblings(".vce-classic-tabs-panels-container"),
8569      $container = $panes
8570        .children(".vce-classic-tabs-panels")
8571        .children()
8572        .eq(index),
8573      $wcpt = $container.find(".wcpt");
8574
8575    setTimeout(function () {
8576      $wcpt.trigger("wcpt_layout", { source: "visual_composer__tab" });
8577    }, 1);
8578  });
8579  // -- -- accordion (1)
8580  $("body").on("click", ".vce-classic-accordion-panel-heading", function () {
8581    var $this = $(this),
8582      $container = $this.closest(".vce-classic-accordion-panel"),
8583      $wcpt = $container.find(".wcpt");
8584    setTimeout(function () {
8585      $wcpt.trigger("wcpt_layout", {
8586        source: "visual_composer__accordion",
8587      });
8588    }, 1);
8589  });
8590  // -- -- accordion (2)
8591  $("body").on("click", ".vc_tta-panel-heading", function () {
8592    var $this = $(this),
8593      $container = $this.closest(".vc_tta-panel"),
8594      $wcpt = $container.find(".wcpt");
8595    setTimeout(function () {
8596      $wcpt.trigger("wcpt_layout", {
8597        source: "visual_composer__accordion",
8598      });
8599    }, 1);
8600  });
8601
8602  // -- responsive accordion and collapse
8603  $("body").on("click", ".wpsm_panel-heading", function () {
8604    var $this = $(this),
8605      $container = $this.closest(".wpsm_panel"),
8606      $content = $container.find(".wpsm_panel-collapse");
8607
8608    $(".wcpt", $content).trigger("wcpt_layout", {
8609      source: "rac__accordion",
8610    });
8611  });
8612
8613  // -- king composer
8614  $("body").on("click", ".kc_accordion_header", function () {
8615    var $this = $(this),
8616      $container = $this.closest(".kc_accordion_section"),
8617      $content = $container.find(".kc_accordion_content");
8618
8619    $(".wcpt", $content).trigger("wcpt_layout", {
8620      source: "king_composer__accordion",
8621    });
8622  });
8623
8624  // -- helpie faq
8625  $("body").on("mouseup touchend", ".accordion__header", function () {
8626    var $this = $(this),
8627      $container = $this.closest(".accordion__item"),
8628      $content = $container.find(".accordion__body");
8629
8630    clearTimeout(window.wcpt_helpie_timeout);
8631    window.wcpt_helpie_timeout = setTimeout(function () {
8632      $(".wcpt:visible", $content).trigger("wcpt_layout", {
8633        source: "helpie_faq__accordion",
8634      });
8635    }, 200);
8636  });
8637
8638  // -- tabs (wpshopmart)
8639  $("body").on("click", ".wpsm_nav li", function () {
8640    var $this = $(this),
8641      index = $this.index(),
8642      $nav = $this.closest(".wpsm_nav"),
8643      $panes = $nav.siblings(".tab-content"),
8644      $container = $panes.children().eq(index),
8645      $wcpt = $container.find(".wcpt");
8646
8647    $wcpt.trigger("wcpt_layout", { source: "tabs_wpshopmart__tab" });
8648  });
8649
8650  // -- tabby responsive tabs
8651  $("body").on("click", ".tabtitle", function () {
8652    var $this = $(this),
8653      $panel = $this.siblings(".tabcontent");
8654
8655    $(".wcpt", $panel).trigger("wcpt_layout", { source: "tabby_tab" });
8656  });
8657
8658  $("body").on("click", ".responsive-tabs__list__item", function () {
8659    var $this = $(this),
8660      index = $this.index(),
8661      $container = $this.closest(".responsive-tabs"),
8662      $panel = $(".tabcontent", $container).eq(index);
8663
8664    $(".wcpt", $panel).trigger("wcpt_layout", { source: "tabby_tab" });
8665  });
8666
8667  // -- kadence blocks
8668  $("body").on("click", ".kt-title-item", function () {
8669    var $this = $(this),
8670      $tab_container = $this.closest(".kt-tabs-wrap");
8671
8672    setTimeout(function () {
8673      $tab_container.find(".wcpt:visible").trigger("wcpt_layout");
8674    }, 100);
8675  });
8676
8677  // sonaar
8678  // -- audio player play / pause
8679  $("body").on("click", ".wcpt-player--sonaar", function () {
8680    var $this = $(this),
8681      playlist_id = $this.attr("data-wcpt-sonaar-playlist-id");
8682
8683    if (typeof IRON == "undefined" || !playlist_id) {
8684      return;
8685    }
8686
8687    $(".wcpt-player--sonaar").not($this).removeClass("wcpt-player--playing");
8688
8689    // pause
8690    if (
8691      IRON.sonaar.player.isPlaying &&
8692      IRON.sonaar.player.playlistID == playlist_id
8693    ) {
8694      IRON.sonaar.player.pause();
8695      $this.removeClass("wcpt-player--playing wcpt-media-loaded");
8696    } else {
8697      $this.addClass("wcpt-player--playing wcpt-media-loaded");
8698
8699      IRON.sonaar.player.setPlayer({
8700        id: playlist_id,
8701        autoplay: true,
8702        soundwave: true,
8703      });
8704    }
8705  });
8706
8707  // -- auto set play / pause status after every load
8708  function sonaar_player_auto_status() {
8709    if (typeof IRON == "undefined") {
8710      return;
8711    }
8712
8713    var playlist_id = IRON.sonaar.player.playlistID;
8714
8715    if (playlist_id) {
8716      $(
8717        '.wcpt-player--sonaar[data-wcpt-sonaar-playlist-id="' +
8718          playlist_id +
8719          '"]',
8720      ).addClass("wcpt-player--playing wcpt-media-loaded");
8721    }
8722  }
8723
8724  // TI Wishlist integration
8725
8726  if (typeof wcpt_ti_wishlist_ids !== "undefined") {
8727    // update counter widget
8728    setTimeout(function () {
8729      $(".wishlist_products_counter_number").text(wcpt_ti_wishlist_ids.length);
8730      if (wcpt_ti_wishlist_ids.length) {
8731        $(".wishlist_products_counter").addClass(
8732          "wishlist-counter-with-products",
8733        );
8734      } else {
8735        $(".wishlist_products_counter").removeClass(
8736          "wishlist-counter-with-products",
8737        );
8738      }
8739    }, 100);
8740
8741    // wishlist button
8742    $("body").on("click", ".wcpt-wishlist", function (e) {
8743      var $this = $(this),
8744        $row = $this.closest(".wcpt-row"),
8745        $wcpt = $this.closest(".wcpt"),
8746        product_type = $row.attr("data-wcpt-type"),
8747        product_id = $row.attr("data-wcpt-product-id"),
8748        variation_id = $row.attr("data-wcpt-variation-id")
8749          ? $row.attr("data-wcpt-variation-id")
8750          : $row.data("wcpt_variation_id")
8751            ? $row.data("wcpt_variation_id")
8752            : false,
8753        variable_permitted = $this.attr("data-wcpt-variable-permitted");
8754
8755      // variable product must have variation selected
8756      if (product_type == "variable" && !variable_permitted && !variation_id) {
8757        alert("Please select some options first");
8758        return;
8759      }
8760
8761      var list_id = variation_id ? variation_id : product_id;
8762
8763      $this.addClass("wcpt-loading");
8764
8765      var data = {
8766        tinv_wishlist_id: false,
8767        tinv_wishlist_name: false,
8768        product_type: $row.attr("data-wcpt-type"),
8769        product_id: $row.attr("data-wcpt-product-id"),
8770        product_variation: variation_id,
8771        redirect: false,
8772      };
8773
8774      if (variation_id) {
8775        var attributes =
8776          data.product_type === "variable"
8777            ? $row.data("wcpt_attributes")
8778            : JSON.parse($row.attr("data-wcpt-variation-attributes"));
8779
8780        $.extend(data, {
8781          form: $.extend(
8782            {
8783              quantity: 1,
8784              product_id: product_id,
8785              variation_id: variation_id,
8786            },
8787            attributes,
8788          ),
8789        });
8790      }
8791
8792      // add
8793      if (-1 === wcpt_ti_wishlist_ids.indexOf(list_id)) {
8794        $.ajax({
8795          url: wcpt_params.ajax_url,
8796          method: "POST",
8797          beforeSend: function () {
8798            // variation added from variable product, refresh page to show it
8799            if (
8800              product_type === "variable" &&
8801              $wcpt.data("wcpt_sc_attrs").ti_wishlist
8802            ) {
8803              $wcpt.addClass("wcpt-loading");
8804            }
8805          },
8806          data: $.extend({}, { product_action: "addto" }, data),
8807        }).done(function (response) {
8808          // variation added from variable product, refresh page to show it
8809          if (
8810            product_type === "variable" &&
8811            $wcpt.data("wcpt_sc_attrs").ti_wishlist
8812          ) {
8813            window.location.reload();
8814          }
8815
8816          wcpt_ti_wishlist_update_row_view($row);
8817
8818          wcpt_ti_wishlist_growler({
8819            name: $this.attr("data-wcpt-product-name"),
8820            view_wishlist_label: $this.attr("data-wcpt-view-wishlist-label"),
8821            item_added_label: $this.attr("data-wcpt-item-added-label"),
8822            icon: $this.attr("data-wcpt-icon"),
8823            url: $this.attr("data-wcpt-custom-url"),
8824            duration_seconds: $this.attr("data-wcpt-duration-seconds"),
8825          });
8826
8827          // update counter widget
8828          if (response && typeof response.wishlists_data !== "undefined") {
8829            $(".wishlist_products_counter_number").text(
8830              response.wishlists_data.counter,
8831            );
8832            if (response.wishlists_data.counter) {
8833              $(".wishlist_products_counter").addClass(
8834                "wishlist-counter-with-products",
8835              );
8836            } else {
8837              $(".wishlist_products_counter").removeClass(
8838                "wishlist-counter-with-products",
8839              );
8840            }
8841          }
8842        });
8843
8844        // add to maintained list as well
8845        wcpt_ti_wishlist_ids.push(list_id);
8846
8847        // update UI
8848        $this.addClass("wcpt-active");
8849
8850        // remove
8851      } else {
8852        $.ajax({
8853          url: wcpt_params.ajax_url,
8854          method: "POST",
8855          data: $.extend({}, { product_action: "remove" }, data),
8856        }).done(function (response) {
8857          // update counter widget
8858          if (response && typeof response.wishlists_data !== "undefined") {
8859            $(".wishlist_products_counter_number").text(
8860              response.wishlists_data.counter,
8861            );
8862            if (response.wishlists_data.counter) {
8863              $(".wishlist_products_counter").addClass(
8864                "wishlist-counter-with-products",
8865              );
8866            } else {
8867              $(".wishlist_products_counter").removeClass(
8868                "wishlist-counter-with-products",
8869              );
8870            }
8871          }
8872        });
8873
8874        // remove from maintained list as well
8875        var index = wcpt_ti_wishlist_ids.indexOf(list_id);
8876        if (index > -1) {
8877          wcpt_ti_wishlist_ids.splice(index, 1);
8878        }
8879
8880        wcpt_ti_wishlist_update_row_view($row);
8881
8882        // update UI
8883        // $this.removeClass('wcpt-active');
8884
8885        // remove product row from wishlist table
8886        if ($wcpt.data("wcpt_sc_attrs").ti_wishlist) {
8887          var $remove_row = $row;
8888
8889          // don't remove variable product if it was being used to remove a variation only
8890          if (variation_id) {
8891            $remove_row = $wcpt.find(
8892              '.wcpt-product-type-variation[data-wcpt-variation-id="' +
8893                variation_id +
8894                '"]',
8895            );
8896          }
8897
8898          $remove_row.addClass("wcpt-wishlist-removing-row");
8899          setTimeout(function () {
8900            $remove_row.remove();
8901          }, 500);
8902        }
8903      }
8904    });
8905
8906    function wcpt_ti_wishlist_growler(data) {
8907      var template = $("#wcpt-ti-wishlist-growler-template").html(),
8908        $growler = $(template.replace("{n}", '"' + data.name + '"')),
8909        reveal_class = "wcpt-ti-wishlist-growler--revealed",
8910        duration_ms = data.duration_seconds * 1000;
8911
8912      $growler.attr("data-wcpt-icon", data.icon);
8913      $(".wcpt-ti-wishlist-growler").remove(); // remove previous
8914
8915      if (data.item_added_label) {
8916        $(".wcpt-ti-wishlist-growler__label--item-added", $growler).text(
8917          data.item_added_label.replace("{n}", '"' + data.name + '"'),
8918        );
8919      }
8920
8921      if (data.view_wishlist_label) {
8922        $(".wcpt-ti-wishlist-growler__label--view-wishlist", $growler).text(
8923          data.view_wishlist_label.replace("{n}", '"' + data.name + '"'),
8924        );
8925      }
8926
8927      if (data.url) {
8928        $growler.attr("href", data.url);
8929      } else if (wcpt_ti_wishlist_url) {
8930        $growler.attr("href", wcpt_ti_wishlist_url);
8931      }
8932
8933      $("body").append($growler);
8934
8935      setTimeout(function () {
8936        $growler.addClass(reveal_class);
8937      }, 100);
8938
8939      setTimeout(function () {
8940        $growler.removeClass(reveal_class);
8941      }, duration_ms);
8942
8943      setTimeout(function () {
8944        $growler.remove();
8945      }, duration_ms + 500);
8946    }
8947
8948    $("body").on("wcpt_after_every_load", ".wcpt", function () {
8949      var $this__container = $(this),
8950        $visible_rows = wcpt_util
8951          .get_uninit_rows($this__container)
8952          .filter(":visible");
8953
8954      // variable products in wishlist were definitely added without variation selected
8955      if (wcpt_util.get_sc_attrs($this__container).ti_wishlist) {
8956        $visible_rows
8957          .filter(".wcpt-product-type-variable")
8958          .trigger("select_variation", {
8959            variation_id: false,
8960            complete_match: false,
8961            attributes: false,
8962            variation: false,
8963            variation_found: false,
8964            variation_selected: false,
8965            variation_available: false,
8966          });
8967      }
8968
8969      $visible_rows.each(function () {
8970        var $this__container = $(this);
8971        wcpt_ti_wishlist_update_row_view($this__container);
8972      });
8973    });
8974
8975    $("body").on("select_variation", ".wcpt-row", function () {
8976      var $this = $(this);
8977      wcpt_ti_wishlist_update_row_view($this);
8978    });
8979
8980    function wcpt_ti_wishlist_update_row_view($row) {
8981      var product_type = $row.attr("data-wcpt-type"),
8982        product_id = $row.attr("data-wcpt-product-id"),
8983        variation_id = $row.data("wcpt_variation_id")
8984          ? $row.data("wcpt_variation_id")
8985          : $row.attr("data-wcpt-variation-id")
8986            ? $row.attr("data-wcpt-variation-id")
8987            : false,
8988        $buttons = $(".wcpt-wishlist", wcpt_get_sibling_rows($row)),
8989        variable_permitted = $buttons.attr("data-wcpt-variable-permitted");
8990
8991      if (product_type === "variable" && !variable_permitted && !variation_id) {
8992        $buttons.removeClass("wcpt-active").addClass("wcpt-disabled");
8993
8994        return;
8995      }
8996
8997      $buttons.removeClass("wcpt-disabled wcpt-loading");
8998
8999      var list_id = variation_id ? variation_id : product_id;
9000
9001      if (-1 === wcpt_ti_wishlist_ids.indexOf(list_id + "")) {
9002        $buttons.removeClass("wcpt-active");
9003      } else {
9004        $buttons.addClass("wcpt-active");
9005      }
9006    }
9007  }
9008
9009  // WooCommerce Wholesale Prices
9010
9011  // -- select_variation handler to switch vals in wcpt_wholesale
9012  $("body").on("select_variation", ".wcpt-row", function () {
9013    var $this = $(this),
9014      variation = $this.data("wcpt_variation"),
9015      $wholesale_shortcode = $(".wcpt-wholesale", $this);
9016
9017    $wholesale_shortcode.each(function () {
9018      var $this = $(this);
9019
9020      // set the view: default or variation
9021      $this.removeClass(
9022        "wcpt-wholesale--variation-view-enabled wcpt-wholesale--default-view-enabled",
9023      );
9024
9025      if (variation) {
9026        $this.addClass("wcpt-wholesale--variation-view-enabled");
9027      } else {
9028        $this.addClass("wcpt-wholesale--default-view-enabled");
9029      }
9030
9031      // wholesale table
9032      if ($this.hasClass("wcpt-wholesale--wholesale-table")) {
9033        var variation_html = "-";
9034
9035        if (variation && variation.wholesale_price) {
9036          var table_html_match =
9037            variation.price_html.match(/(<table.+table)>/s);
9038          if (table_html_match) {
9039            variation_html = table_html_match[0];
9040          }
9041        }
9042
9043        $this.html(variation_html);
9044      }
9045
9046      // wholesale price
9047      if ($this.hasClass("wcpt-wholesale--wholesale-price")) {
9048        var variation_text = "-";
9049
9050        if (variation) {
9051          var wholesale_price = "";
9052          if (variation.wholesale_price) {
9053            wholesale_price = variation.wholesale_price;
9054          }
9055          variation_text = format_price(wholesale_price);
9056        }
9057
9058        $(".wcpt-wholesale__variation-view", $this).text(variation_text);
9059      }
9060
9061      // original price
9062      if ($this.hasClass("wcpt-wholesale--original-price")) {
9063        var variation_text = "-";
9064
9065        if (variation) {
9066          var original_price = "";
9067          if (variation.wholesale_price) {
9068            original_price = variation.original_display_price; // cannot use display_price here because it is set to wholesale_price by WCPT
9069          } else {
9070            original_price = variation.display_price;
9071          }
9072          variation_text = format_price(original_price);
9073        }
9074
9075        $(".wcpt-wholesale__variation-view", $this).text(variation_text);
9076      }
9077
9078      // wholesale label
9079      if ($this.hasClass("wcpt-wholesale--wholesale-label")) {
9080        $this.removeClass(
9081          "wcpt-wholesale--variation-is-on-wholesale-view-enabled wcpt-wholesale--variation-is-not-on-wholesale-view-enabled",
9082        );
9083        if (variation) {
9084          if (variation.wholesale_price) {
9085            $this.addClass(
9086              "wcpt-wholesale--variation-is-on-wholesale-view-enabled",
9087            );
9088          } else {
9089            $this.addClass(
9090              "wcpt-wholesale--variation-is-not-on-wholesale-view-enabled",
9091            );
9092          }
9093        }
9094      }
9095    });
9096  });
9097
9098  // Variation Swatches
9099  $("body").on(
9100    "change",
9101    ".wcpt-row .variations_form .variation_id",
9102    function () {
9103      var $this = $(this),
9104        $form = $this.closest(".variations_form");
9105
9106      if ($form.hasClass("wvs-loaded")) {
9107        get_select_variation_from_cart_form($form);
9108      }
9109    },
9110  );
9111
9112  // init tables
9113  var $wcpt = $(".wcpt");
9114  if ($wcpt.length) {
9115    $wcpt.each(function () {
9116      after_every_load($(this));
9117    });
9118  }
9119
9120  // lazy load
9121  lazy_load_start();
9122
9123  // init cart widget
9124  var cart_init_required = false;
9125
9126  if (
9127    document.cookie.indexOf("woocommerce_items_in_cart") !== -1 ||
9128    (typeof wcpt_cart_result_cache !== "undefined" &&
9129      wcpt_cart_result_cache.cart_quantity)
9130  ) {
9131    cart_init_required = true;
9132  }
9133
9134  if (cart_init_required) {
9135    wcpt_cart({
9136      payload: { skip_cart_triggers: true },
9137    });
9138  }
9139
9140  $(window).on("pageshow", function (e) {
9141    // trigger cart events
9142    if (e.originalEvent.persisted) {
9143      wcpt_cart({
9144        payload: { skip_cart_triggers: true },
9145      });
9146    }
9147
9148    // reset select variation to default
9149    $(".wcpt-select-variation-dropdown").each(function () {
9150      var $this = $(this),
9151        $row = $this.closest(".wcpt-row"),
9152        variation_id = $row.data("wcpt_variation_id");
9153
9154      if (variation_id) {
9155        $this.val(variation_id);
9156      } else {
9157        $this.val("");
9158      }
9159    });
9160  });
9161});
9162
9163// module: child row
9164(function ($) {
9165  /**
9166   * Accordion mode variable. Set to true to allow only one child row open at a time.
9167   */
9168  var accordion_mode = true; // set this variable as needed
9169  var child_row_instance_counter = 0;
9170
9171  function getChildRowContainerSelector($container) {
9172    var instanceId = $container.attr("data-wcpt-child-row-instance-id");
9173
9174    if (!instanceId) {
9175      child_row_instance_counter += 1;
9176      instanceId = "wcpt-child-row-" + child_row_instance_counter;
9177      $container.attr("data-wcpt-child-row-instance-id", instanceId);
9178    }
9179
9180    return '.wcpt[data-wcpt-child-row-instance-id="' + instanceId + '"]';
9181  }
9182
9183  /**
9184   * Register event handlers for child row logic
9185   */
9186  $("body")
9187    // Re-evaluate disabled toggles when viewport/device changes.
9188    .on("wcpt_layout", ".wcpt", function () {
9189      updateChildRowToggleDisabledState($(this));
9190    })
9191    // Initialize all child row DOM logic after table load
9192    .on("wcpt_after_every_load", ".wcpt", initChildRows)
9193    // Each child row: register parent/child data binding and set background
9194    .on("wcpt_init_child_row", ".wcpt-child-row", registerParentChild)
9195    .on("wcpt_init_child_row", ".wcpt-child-row", setChildRowBg)
9196    // Click: toggle child rows by explicit control
9197    .on("click", ".wcpt-child-row-toggle", toggleChildRow)
9198    // Click: anywhere in designated rows, except on interactive elements
9199    .on("click", ".wcpt-has-child-row--click-anywhere", toggleChildRowAnywhere)
9200    // Hover state handlers (parent/child highlighting)
9201    .on("mouseenter", ".wcpt-has-child-row", parentRowMouseenter)
9202    .on("mouseleave", ".wcpt-has-child-row", parentRowMouseleave)
9203    .on("mouseenter", ".wcpt-child-row", childRowMouseenter)
9204    .on("mouseleave", ".wcpt-child-row", childRowMouseleave);
9205
9206  // In pages with multiple tables, some instances can be rendered before this
9207  // module attaches wcpt_after_every_load listeners. Initialize existing tables.
9208  $(function () {
9209    $(".wcpt").each(function () {
9210      initChildRows.call(this);
9211    });
9212  });
9213
9214  /**
9215   * Init all child rows in the current .wcpt table,
9216   * triggers event to register proper properties
9217   */
9218  function initChildRows() {
9219    var $container = $(this);
9220
9221    ensureParentChildBindings($container);
9222
9223    $(".wcpt-child-row:not(.wcpt-child-row--init)", $container).each(
9224      function () {
9225        var $row = $(this);
9226        $row.trigger("wcpt_init_child_row").addClass("wcpt-child-row--init");
9227      },
9228    );
9229
9230    updateChildRowToggleDisabledState($container);
9231  }
9232
9233  function getChildRowViewportDevice($container) {
9234    var device = "laptop";
9235    var $scroll_outer = $container
9236      .find(".wcpt-table-scroll-wrapper-outer")
9237      .first();
9238
9239    if ($scroll_outer.length) {
9240      if ($scroll_outer.hasClass("wcpt-device-phone")) {
9241        device = "phone";
9242      } else if ($scroll_outer.hasClass("wcpt-device-tablet")) {
9243        device = "tablet";
9244      }
9245    } else if (window.wcpt_params && wcpt_params.breakpoints) {
9246      var width = $(window).width();
9247
9248      if (width <= wcpt_params.breakpoints.phone) {
9249        device = "phone";
9250      } else if (width <= wcpt_params.breakpoints.tablet) {
9251        device = "tablet";
9252      }
9253    }
9254
9255    return device;
9256  }
9257
9258  function getChildRowOriginalTable($container) {
9259    return $container.find(".wcpt-table:visible").not(".frzTbl-clone-table");
9260  }
9261
9262  function childRowElementIsConfiguredForDevice($element, device) {
9263    var style = $element.attr("style") || "";
9264
9265    return style.indexOf("--wcpt-child-row-column-order-" + device) !== -1;
9266  }
9267
9268  function childRowElementIsHiddenByEmptyColumns($element, $table) {
9269    if ($element.hasClass("wcpt-hide")) {
9270      return true;
9271    }
9272
9273    var colIndex = $element.attr("data-wcpt-column-index");
9274
9275    if (typeof colIndex === "undefined") {
9276      var match = ($element.attr("class") || "").match(
9277        /wcpt-child-row__element--column-(\d+)/,
9278      );
9279      colIndex = match ? match[1] : null;
9280    }
9281
9282    if (colIndex === null) {
9283      return false;
9284    }
9285
9286    var $cells = $table.find(
9287      '.wcpt-cell[data-wcpt-column-index="' + colIndex + '"]',
9288    );
9289
9290    return (
9291      $cells.length > 0 &&
9292      $cells.filter(".wcpt-hide").length === $cells.length
9293    );
9294  }
9295
9296  function updateChildRowToggleDisabledState($container) {
9297    var sc_attrs =
9298      typeof wcpt_util !== "undefined"
9299        ? wcpt_util.get_sc_attrs($container)
9300        : {};
9301
9302    if (!sc_attrs.hide_empty_columns) {
9303      $container
9304        .find("td.wcpt-child-row-toggle")
9305        .removeClass("wcpt-child-row-toggle--disabled");
9306      return;
9307    }
9308
9309    var device = getChildRowViewportDevice($container);
9310    var $table = getChildRowOriginalTable($container);
9311
9312    $container.find("td.wcpt-child-row-toggle").each(function () {
9313      var $toggle = $(this);
9314      var $row = $toggle.closest(".wcpt-row");
9315      var $childRow = $row.data("wcpt_child_row");
9316      var shouldDisable = false;
9317      var hasConfiguredForDevice = false;
9318
9319      if (
9320        (!$childRow || !$childRow.length) &&
9321        $row.next().hasClass("wcpt-child-row")
9322      ) {
9323        $childRow = $row.next();
9324        $row.data("wcpt_child_row", $childRow);
9325        $childRow.data("wcpt_parent_row", $row);
9326      }
9327
9328      if ($childRow && $childRow.length) {
9329        shouldDisable = true;
9330        var $elements = $childRow.find(".wcpt-child-row__element");
9331
9332        $elements.each(function () {
9333          if (!childRowElementIsConfiguredForDevice($(this), device)) {
9334            return;
9335          }
9336
9337          hasConfiguredForDevice = true;
9338
9339          if (
9340            !childRowElementIsHiddenByEmptyColumns($(this), $table)
9341          ) {
9342            shouldDisable = false;
9343            return false;
9344          }
9345        });
9346
9347        if (!hasConfiguredForDevice) {
9348          shouldDisable = false;
9349        }
9350      }
9351
9352      if (shouldDisable) {
9353        $toggle.addClass("wcpt-child-row-toggle--disabled");
9354
9355        if (!$toggle.hasClass("wcpt-child-row-toggle--closed")) {
9356          $toggle.addClass("wcpt-child-row-toggle--closed");
9357          $row.removeClass("wcpt-has-child-row--visible");
9358
9359          if ($childRow && $childRow.length) {
9360            $childRow.find(".wcpt-child-row__wrapper").hide();
9361          }
9362        }
9363      } else {
9364        $toggle.removeClass("wcpt-child-row-toggle--disabled");
9365      }
9366    });
9367  }
9368
9369  function ensureParentChildBindings($container) {
9370    $container.find(".wcpt-child-row").each(function () {
9371      var $child = $(this);
9372      var $parent = $child.prev(".wcpt-row");
9373
9374      if (!$parent.length) return;
9375
9376      if (!$child.data("wcpt_parent_row")) {
9377        $child.data("wcpt_parent_row", $parent);
9378      }
9379
9380      if (!$parent.data("wcpt_child_row")) {
9381        $parent.data("wcpt_child_row", $child);
9382      }
9383    });
9384  }
9385
9386  /**
9387   * Binds parent <-> child row references for easier later access
9388   */
9389  function registerParentChild() {
9390    var $child = $(this),
9391      $parent = $child.prev();
9392
9393    $child.data("wcpt_parent_row", $parent);
9394    $parent.data("wcpt_child_row", $child);
9395  }
9396
9397  /**
9398   * Sets background color of a child row to match its parent's first cell (or whole row)
9399   */
9400  function setChildRowBg() {
9401    var $child = $(this),
9402      $parent = $child.data("wcpt_parent_row");
9403
9404    // fallback safely in case parent not found
9405    if (!$parent || !$parent.length) return;
9406
9407    var parentBg = $parent.css("background-color") || "",
9408      firstCellBg = $("> td:first-child", $parent).css("background-color"),
9409      chosenBg =
9410        firstCellBg && firstCellBg !== "rgba(0, 0, 0, 0)"
9411          ? firstCellBg
9412          : parentBg;
9413
9414    $("> td", $child).css("background-color", chosenBg);
9415  }
9416
9417  /**
9418   * Ensures all cells in table have stable width when child row opened (for visual stability)
9419   */
9420  function applyColumnWidthStyles($container) {
9421    var $firstOpenRow = $container
9422      .find(
9423        ".wcpt-row:has(td.wcpt-child-row-toggle:not(.wcpt-child-row-toggle--closed))",
9424      )
9425      .first();
9426    var styleHtml = '<style class="wcpt-child-row-width">';
9427    var containerSelector = getChildRowContainerSelector($container);
9428    if ($firstOpenRow.length) {
9429      $firstOpenRow.children(".wcpt-cell").each(function (index) {
9430        var width = $(this).outerWidth();
9431        // Scope styles to a unique table instance on the page.
9432        styleHtml +=
9433          containerSelector +
9434          " .wcpt-row > .wcpt-cell:nth-child(" +
9435          (index + 1) +
9436          ") { width: " +
9437          width +
9438          "px !important; }";
9439      });
9440      styleHtml += "</style>";
9441      $container.find("style.wcpt-child-row-width").remove();
9442      $container.append(styleHtml);
9443    }
9444  }
9445
9446  /**
9447   * Main click handler: toggles child row open/closed for a given row/cell or header cell.
9448   * Supports accordion_mode: if true, only one child row stays open at a time (unless opening all).
9449   */
9450  function toggleChildRow(e) {
9451    var $clicked = $(this),
9452      $container = $clicked.closest(".wcpt");
9453
9454    if ($clicked.hasClass("wcpt-child-row-toggle--disabled")) {
9455      return;
9456    }
9457
9458    // Reconcile bindings if this table was rendered before child-row init hooks.
9459    ensureParentChildBindings($container);
9460
9461    // Detect if this is a table heading (bulk open/close)
9462    var isHeader = !$clicked.is("td");
9463
9464    if (isHeader) {
9465      // Header toggle always continues as normal: opens/closes all
9466      toggleAllChildRows(
9467        $container,
9468        !$clicked.hasClass("wcpt-child-row-toggle--closed"),
9469      );
9470      updateRowVisibilityMarks($container);
9471      handleColumnWidths(
9472        $container,
9473        !$clicked.hasClass("wcpt-child-row-toggle--closed"),
9474      );
9475      $clicked.toggleClass("wcpt-child-row-toggle--closed");
9476      return;
9477    }
9478
9479    var wasClosed = $clicked.hasClass("wcpt-child-row-toggle--closed");
9480
9481    // If accordion mode is on, and we're opening a row (not closing), close any other open rows first (excluding this one)
9482    // if (accordion_mode && wasClosed) {
9483    if (wasClosed) {
9484      // Find all open toggles except the one being clicked
9485      $container
9486        .find("td.wcpt-child-row-toggle:not(.wcpt-child-row-toggle--closed)")
9487        .each(function () {
9488          var $otherTd = $(this);
9489          if (!$otherTd.is($clicked)) {
9490            // Only close if not this one
9491            var $otherRow = $otherTd.closest(".wcpt-row");
9492            var $otherChildRow = $otherRow.data("wcpt_child_row");
9493            $otherTd.addClass("wcpt-child-row-toggle--closed");
9494            if ($otherChildRow && $otherChildRow.length) {
9495              var $otherWrapper = $otherChildRow.find(
9496                ".wcpt-child-row__wrapper",
9497              );
9498              $otherWrapper.slideUp();
9499            }
9500          }
9501        });
9502    }
9503
9504    // Now toggle clicked row as usual
9505    $clicked.toggleClass("wcpt-child-row-toggle--closed");
9506    var isClosedNow = $clicked.hasClass("wcpt-child-row-toggle--closed");
9507
9508    // Handle column width stabilization
9509    handleColumnWidths($container, !isClosedNow);
9510
9511    // Single row toggle logic
9512    toggleSingleChildRow($clicked, $container, isClosedNow);
9513
9514    updateRowVisibilityMarks($container);
9515  }
9516
9517  /**
9518   * Ensures all columns keep their current width (optional: only when opening).
9519   */
9520  function handleColumnWidths($container, openingOne) {
9521    var $openRows = $container.find(
9522      ".wcpt-row:has(td.wcpt-child-row-toggle:not(.wcpt-child-row-toggle--closed))",
9523    );
9524    var $style = $container.find("style.wcpt-child-row-width");
9525    var containerSelector = getChildRowContainerSelector($container);
9526
9527    if (openingOne && $openRows.length) {
9528      // Lock cell widths according to the first row with open toggle
9529      var $row = $openRows.first();
9530      var styleHtml = '<style class="wcpt-child-row-width">';
9531      $row.children(".wcpt-cell").each(function (i) {
9532        styleHtml +=
9533          containerSelector +
9534          " .wcpt-row > .wcpt-cell:nth-child(" +
9535          (i + 1) +
9536          ") { width: " +
9537          $(this).outerWidth() +
9538          "px !important; }";
9539      });
9540      styleHtml += "</style>";
9541      $style.remove();
9542      $container.append(styleHtml);
9543    } else {
9544      // Only remove width style if ALL now closed
9545      var allClosed =
9546        $container.find("td.wcpt-child-row-toggle").length ===
9547        $container.find(
9548          "td.wcpt-child-row-toggle.wcpt-child-row-toggle--closed",
9549        ).length;
9550      if (allClosed) {
9551        $style.remove();
9552      } else {
9553        applyColumnWidthStyles($container); // fallback to routine (already defined)
9554      }
9555    }
9556  }
9557
9558  /**
9559   * Toggle a single row's child row visibility and header icon state.
9560   */
9561  function toggleSingleChildRow($clicked, $container, isClosedNow) {
9562    var $row = $clicked.closest(".wcpt-row");
9563    var $childRow = $row.data("wcpt_child_row");
9564
9565    if (
9566      (!$childRow || !$childRow.length) &&
9567      $row.next().hasClass("wcpt-child-row")
9568    ) {
9569      $childRow = $row.next();
9570      $row.data("wcpt_child_row", $childRow);
9571      $childRow.data("wcpt_parent_row", $row);
9572    }
9573
9574    if ($childRow && $childRow.length) {
9575      var $wrapper = $childRow.find(".wcpt-child-row__wrapper");
9576      if ($row.hasClass("wcpt-has-child-row--visible")) {
9577        $wrapper.slideUp();
9578      } else {
9579        $wrapper.slideDown();
9580      }
9581    }
9582
9583    // Sync header toggle icon: open header icon if ANY row is open, else close header icon
9584    var anyOpen =
9585      $container.find(
9586        "td.wcpt-child-row-toggle:not(.wcpt-child-row-toggle--closed)",
9587      ).length > 0;
9588    var $headerToggles = $container.find("th.wcpt-child-row-toggle");
9589    if (anyOpen) {
9590      $headerToggles.removeClass("wcpt-child-row-toggle--closed");
9591    } else {
9592      $headerToggles.addClass("wcpt-child-row-toggle--closed");
9593    }
9594  }
9595
9596  /**
9597   * Toggle all child rows via the header toggle cell.
9598   * Support "close all" even if accordion_mode is enabled.
9599   */
9600  function toggleAllChildRows($container, closing) {
9601    var $tdToggles = $container.find("td.wcpt-child-row-toggle");
9602    $tdToggles.each(function () {
9603      var $td = $(this);
9604
9605      if ($td.hasClass("wcpt-child-row-toggle--disabled")) {
9606        return;
9607      }
9608
9609      var $row = $td.closest(".wcpt-row");
9610      var $childRow = $row.data("wcpt_child_row");
9611      var $wrapper = $childRow
9612        ? $childRow.find(".wcpt-child-row__wrapper")
9613        : $();
9614      if (closing) {
9615        if (!$td.hasClass("wcpt-child-row-toggle--closed")) {
9616          $td.addClass("wcpt-child-row-toggle--closed");
9617          if ($childRow && $wrapper.length) $wrapper.slideUp();
9618        }
9619      } else {
9620        if ($td.hasClass("wcpt-child-row-toggle--closed")) {
9621          $td.removeClass("wcpt-child-row-toggle--closed");
9622          if ($childRow && $wrapper.length) $wrapper.slideDown();
9623        }
9624      }
9625    });
9626    // If opening bulk, re-apply width stabilization
9627    if (!closing) {
9628      applyColumnWidthStyles($container);
9629    } else {
9630      // Always remove width stabilization on close all
9631      var $style = $container.find("style.wcpt-child-row-width");
9632      $style.remove();
9633    }
9634  }
9635
9636  /**
9637   * Add or remove .wcpt-has-child-row--visible on rows according to open toggle state (for CSS effects).
9638   */
9639  function updateRowVisibilityMarks($container) {
9640    $container.find("td.wcpt-child-row-toggle").each(function () {
9641      var $td = $(this);
9642      var $row = $td.closest(".wcpt-row");
9643      // Use a short delay to let animations run and class state stabilize
9644      setTimeout(function () {
9645        if ($td.hasClass("wcpt-child-row-toggle--closed")) {
9646          $row.removeClass("wcpt-has-child-row--visible");
9647        } else {
9648          $row.addClass("wcpt-has-child-row--visible");
9649        }
9650      }, 100);
9651    });
9652  }
9653
9654  /**
9655   * Handler: clicking anywhere in "click-anywhere" parent row will toggle child,
9656   * but not if clicking on interactive/child row controls or links.
9657   */
9658  function toggleChildRowAnywhere(e) {
9659    var $row = $(this).closest(".wcpt-row");
9660    var $toggle = $(".wcpt-child-row-toggle", $row);
9661
9662    if ($toggle.hasClass("wcpt-child-row-toggle--disabled")) {
9663      return;
9664    }
9665
9666    var ignoreSelectors = [
9667      ".wcpt-child-row-toggle",
9668      "a",
9669      ".wcpt-tooltip",
9670      "input",
9671      "button",
9672      ".wcpt-button",
9673      ".wcpt-link",
9674      ".wcpt-quantity",
9675    ].join(", ");
9676    if (!$(e.target).closest(ignoreSelectors).length) {
9677      $(".wcpt-child-row-toggle", $row).click();
9678    }
9679  }
9680
9681  /** Highlighting logic for hover grouping (parent and child row highlight effects) **/
9682  function parentRowMouseenter() {
9683    var $parent = $(this),
9684      $child = $parent.data("wcpt_child_row");
9685    if ($child && $child.length) {
9686      $child.addClass("wcpt-parent-row-hovered");
9687    }
9688  }
9689
9690  function parentRowMouseleave() {
9691    var $parent = $(this),
9692      $child = $parent.data("wcpt_child_row");
9693    if ($child && $child.length) {
9694      $child.removeClass("wcpt-parent-row-hovered");
9695    }
9696  }
9697
9698  function childRowMouseenter() {
9699    var $child = $(this),
9700      $parent = $child.data("wcpt_parent_row");
9701    if ($parent && $parent.length) {
9702      $parent.addClass("wcpt-child-row-hovered");
9703    }
9704  }
9705
9706  function childRowMouseleave() {
9707    var $child = $(this),
9708      $parent = $child.data("wcpt_parent_row");
9709    if ($parent && $parent.length) {
9710      $parent.removeClass("wcpt-child-row-hovered");
9711    }
9712  }
9713})(jQuery);
9714
9715// module: instant search
9716(function ($) {
9717  $("body").on("wcpt_after_every_load", ".wcpt", init_instant_search);
9718
9719  // -- add instant search html class on $search and stop 'enter' key handler
9720  function init_instant_search() {
9721    var $container = $(this),
9722      $search = $(".wcpt-search-wrapper", $container);
9723
9724    if (
9725      wcpt_util.get_sc_attrs($container).instant_search &&
9726      !$search.hasClass("wcpt-instant-search")
9727    ) {
9728      $search.addClass("wcpt-instant-search");
9729      $(".wcpt-instant-search .wcpt-search-input", $container).on(
9730        "keydown",
9731        function (e) {
9732          if (e.keyCode === 13 || e.which === 13) {
9733            e.stopPropagation();
9734          }
9735        },
9736      );
9737    } else {
9738      $search.removeClass("wcpt-instant-search");
9739    }
9740  }
9741
9742  function get_instant_search_words(val) {
9743    return val.split(/\s+/).filter(Boolean);
9744  }
9745
9746  // Per-column text cache; each search word must match at least one column.
9747  function get_instant_search_cell_texts($row) {
9748    var texts = $row.data("wcpt_instant_search_cell_texts");
9749    if (!texts) {
9750      texts = [];
9751      $row.children(".wcpt-cell").each(function () {
9752        texts.push($(this).text().toLowerCase().trim());
9753      });
9754      if (!texts.length) {
9755        texts = [$row.text().toLowerCase().trim()];
9756      }
9757      $row.data("wcpt_instant_search_cell_texts", texts);
9758    }
9759    return texts;
9760  }
9761
9762  function row_matches_instant_search($row, val) {
9763    var words = get_instant_search_words(val),
9764      cellTexts = get_instant_search_cell_texts($row),
9765      w,
9766      c,
9767      wordMatch;
9768
9769    for (w = 0; w < words.length; w++) {
9770      wordMatch = false;
9771      for (c = 0; c < cellTexts.length; c++) {
9772        if (cellTexts[c].indexOf(words[w]) !== -1) {
9773          wordMatch = true;
9774          break;
9775        }
9776      }
9777      if (!wordMatch) {
9778        return false;
9779      }
9780    }
9781    return true;
9782  }
9783
9784  // -- search logic
9785  $("body").on(
9786    "keyup input",
9787    ".wcpt-instant-search .wcpt-search-input",
9788    function (e) {
9789      var $this = $(this),
9790        val = $this.val().toLowerCase().trim(),
9791        $wcpt = $this.closest(".wcpt"),
9792        $table = $(".wcpt-table:visible:not(.frzTbl-clone-table)", $wcpt),
9793        $rows = $(".wcpt-row", $table);
9794
9795      if (!val) {
9796        $rows.removeClass("wcpt-row--instant-search-hidden");
9797      } else {
9798        $rows.each(function () {
9799          var $this = $(this),
9800            match = row_matches_instant_search($this, val);
9801
9802          if (match) {
9803            $this.removeClass("wcpt-row--instant-search-hidden");
9804          } else {
9805            $this.addClass("wcpt-row--instant-search-hidden");
9806          }
9807
9808          // child row -- reveal both if either has match
9809          if ($this.hasClass("wcpt-child-row")) {
9810            $parent_row = $this.data("wcpt_parent_row");
9811
9812            if (
9813              !$this.hasClass("wcpt-row--instant-search-hidden") ||
9814              !$parent_row.hasClass("wcpt-row--instant-search-hidden")
9815            ) {
9816              $this
9817                .add($parent_row)
9818                .removeClass("wcpt-row--instant-search-hidden");
9819            }
9820          }
9821        });
9822
9823        // show category group headings if it has search results
9824        $rows.filter(".wcpt-row--category-heading").each(function () {
9825          var $this = $(this),
9826            cat_slug = $this.attr("data-wcpt-group-by-category-slug");
9827
9828          if (
9829            $rows.filter(
9830              '[data-wcpt-group-by-category-slug="' + cat_slug + '"]:visible',
9831            ).length
9832          ) {
9833            $this.removeClass("wcpt-row--instant-search-hidden");
9834          }
9835        });
9836      }
9837
9838      wcpt_util.assign_even_odd_row_classes($table);
9839    },
9840  );
9841})(jQuery);
9842
9843// module: instant sort
9844(function ($) {
9845  function get_instant_sort_params_for_heading(
9846    $sorting_icons,
9847    all_sort_params,
9848  ) {
9849    var current_sort_params = {};
9850
9851    if (!$sorting_icons || !$sorting_icons.length || !all_sort_params) {
9852      return current_sort_params;
9853    }
9854
9855    // Preferred: match by element id class (wcpt-{id})
9856    $.each(all_sort_params.column_heading || {}, function (id, params) {
9857      if ($sorting_icons.hasClass("wcpt-" + id)) {
9858        current_sort_params = $.extend({}, params);
9859        return false;
9860      }
9861    });
9862
9863    if (!$.isEmptyObject(current_sort_params) && current_sort_params.orderby) {
9864      return current_sort_params;
9865    }
9866
9867    // Fallback: match by data attributes on the sorting icons.
9868    // Needed when heading.sorting ids were regenerated and no longer match
9869    // the keys stored in data-wcpt-instant-sort-params.
9870    var orderby = $sorting_icons.attr("data-wcpt-orderby") || "",
9871      meta_key = (
9872        $sorting_icons.attr("data-wcpt-meta-key") || ""
9873      ).toLowerCase(),
9874      orderby_attribute = (
9875        $sorting_icons.attr("data-wcpt-orderby-attribute") || ""
9876      ).toLowerCase(),
9877      orderby_taxonomy = (
9878        $sorting_icons.attr("data-wcpt-orderby-taxonomy") || ""
9879      ).toLowerCase();
9880
9881    if (!orderby) {
9882      return {};
9883    }
9884
9885    $.each(all_sort_params.column_heading || {}, function (id, params) {
9886      if (!params || params.orderby !== orderby) {
9887        return;
9888      }
9889
9890      if (
9891        -1 !== $.inArray(orderby, ["meta_value", "meta_value_num"]) &&
9892        String(params.meta_key || "")
9893          .toLowerCase()
9894          .trim() !== meta_key
9895      ) {
9896        return;
9897      }
9898
9899      if (
9900        -1 !== $.inArray(orderby, ["attribute", "attribute_num"]) &&
9901        String(params.orderby_attribute || "")
9902          .toLowerCase()
9903          .trim() !== orderby_attribute
9904      ) {
9905        return;
9906      }
9907
9908      if (
9909        orderby === "taxonomy" &&
9910        String(params.orderby_taxonomy || "")
9911          .toLowerCase()
9912          .trim() !== orderby_taxonomy
9913      ) {
9914        return;
9915      }
9916
9917      current_sort_params = $.extend({}, params);
9918      return false;
9919    });
9920
9921    return current_sort_params;
9922  }
9923
9924  function get_instant_sort_heading_icons($wcpt, id, option_sort_params) {
9925    var $sorting_icons = $(".wcpt-" + id, $wcpt);
9926
9927    if ($sorting_icons.length) {
9928      return $sorting_icons;
9929    }
9930
9931    // Fallback when icon class id doesn't match the params key
9932    return $(".wcpt-sorting-icons", $wcpt).filter(function () {
9933      var $icons = $(this),
9934        orderby = $icons.attr("data-wcpt-orderby") || "",
9935        meta_key = ($icons.attr("data-wcpt-meta-key") || "").toLowerCase(),
9936        orderby_attribute = (
9937          $icons.attr("data-wcpt-orderby-attribute") || ""
9938        ).toLowerCase(),
9939        orderby_taxonomy = (
9940          $icons.attr("data-wcpt-orderby-taxonomy") || ""
9941        ).toLowerCase();
9942
9943      if (!option_sort_params || orderby !== option_sort_params.orderby) {
9944        return false;
9945      }
9946
9947      if (
9948        -1 !== $.inArray(orderby, ["meta_value", "meta_value_num"]) &&
9949        String(option_sort_params.meta_key || "")
9950          .toLowerCase()
9951          .trim() !== meta_key
9952      ) {
9953        return false;
9954      }
9955
9956      if (
9957        -1 !== $.inArray(orderby, ["attribute", "attribute_num"]) &&
9958        String(option_sort_params.orderby_attribute || "")
9959          .toLowerCase()
9960          .trim() !== orderby_attribute
9961      ) {
9962        return false;
9963      }
9964
9965      if (
9966        orderby === "taxonomy" &&
9967        String(option_sort_params.orderby_taxonomy || "")
9968          .toLowerCase()
9969          .trim() !== orderby_taxonomy
9970      ) {
9971        return false;
9972      }
9973
9974      return true;
9975    });
9976  }
9977
9978  // -- init
9979  $("body").on("wcpt_after_every_load", ".wcpt", init_instant_sort);
9980
9981  function init_instant_sort() {
9982    var $this = $(this),
9983      sc_attrs = $this.data("wcpt_sc_attrs");
9984
9985    if (sc_attrs.instant_sort) {
9986      // attach data
9987      var sort_data = [];
9988      $(
9989        ".wcpt-row:not(.wcpt-child-row):not(.wcpt-row--category-heading)",
9990        $this,
9991      ).each(function () {
9992        var $this = $(this),
9993          id = $this.attr("data-wcpt-product-id"),
9994          variation_id = $this.attr("data-wcpt-variation-id"),
9995          product_sort_data = $.extend(
9996            {},
9997            {
9998              id: id,
9999              variation_id: variation_id,
10000            },
10001            JSON.parse($this.attr("data-wcpt-instant-sort-props")),
10002          );
10003        sort_data.push(product_sort_data);
10004      });
10005
10006      $this.data("wcpt_sort_data", sort_data);
10007
10008      // replace handlers
10009      wcpt_util.run_once_on_container(
10010        $this,
10011        "wcpt_instant_sort_init",
10012        function ($container) {
10013          // -- column heading sort icons handler
10014
10015          // -- -- remove previous handlers and the init html class on container
10016          $container.off(
10017            "click.wcpt_sort_by_column_headings",
10018            ".wcpt-heading.wcpt-sortable",
10019          );
10020
10021          // -- -- attach new handlers
10022          $container.on(
10023            "click.wcpt_instant_sort",
10024            ".wcpt-heading.wcpt-sortable",
10025            function (e) {
10026              var $this = $(this),
10027                $sorting_icons = $(".wcpt-sorting-icons", $this),
10028                new_order = $sorting_icons.hasClass("wcpt-sorting-asc")
10029                  ? "desc"
10030                  : "asc",
10031                $wcpt = $this.closest(".wcpt"),
10032                all_sort_params = JSON.parse(
10033                  $wcpt.attr("data-wcpt-instant-sort-params"),
10034                ),
10035                current_sort_params = get_instant_sort_params_for_heading(
10036                  $sorting_icons,
10037                  all_sort_params,
10038                );
10039
10040              // If heading icons aren't linked to instant-sort params (id mismatch),
10041              // fall back to the regular AJAX column-heading sort.
10042              if (
10043                $.isEmptyObject(current_sort_params) ||
10044                !current_sort_params.orderby
10045              ) {
10046                return window.wcpt_column_heading_sort_handler.call(this, e);
10047              }
10048
10049              $.extend(current_sort_params, { order: new_order });
10050
10051              wcpt_instant_sort(current_sort_params, $wcpt);
10052
10053              instant_sort_ui_feedback(current_sort_params, $wcpt);
10054            },
10055          );
10056
10057          // -- 'Sort By' dropdown handler
10058          $container.on(
10059            "change.wcpt_instant_sort",
10060            '[data-wcpt-filter="sort_by"] input[type="radio"]',
10061            function (e) {
10062              e.stopPropagation(); // block access to wcpt regular sort handler
10063
10064              var $this = $(this),
10065                $option = $this.closest(".wcpt-option, .wcpt-dropdown-option"), // @TODO include dropdown option
10066                $wcpt = $this.closest(".wcpt"),
10067                all_sort_params = JSON.parse(
10068                  $wcpt.attr("data-wcpt-instant-sort-params"),
10069                ),
10070                index = $option.index(),
10071                current_sort_params = all_sort_params.dropdown[index];
10072
10073              wcpt_instant_sort(current_sort_params, $wcpt);
10074
10075              instant_sort_ui_feedback(current_sort_params, $wcpt);
10076            },
10077          );
10078        },
10079      );
10080    }
10081  }
10082
10083  function instant_sort_ui_feedback(current_sort_params, $wcpt) {
10084    var all_sort_params = JSON.parse(
10085      $wcpt.attr("data-wcpt-instant-sort-params"),
10086    );
10087
10088    if (
10089      -1 !== $.inArray(current_sort_params.orderby, ["rating", "price-desc"])
10090    ) {
10091      current_sort_params.order = "desc";
10092    }
10093
10094    if (current_sort_params.orderby == "price-desc") {
10095      current_sort_params.orderby = "price";
10096    }
10097
10098    // 'Sort By' dropdown (or row)
10099    $.each(
10100      all_sort_params["dropdown"],
10101      function (dropdown_option_index, option_sort_params) {
10102        if (
10103          -1 !== $.inArray(option_sort_params.orderby, ["rating", "price-desc"])
10104        ) {
10105          option_sort_params.order = "desc";
10106        }
10107
10108        if (option_sort_params.orderby == "price-desc") {
10109          option_sort_params.orderby = "price";
10110        }
10111
10112        if (
10113          current_sort_params.orderby !== option_sort_params.orderby ||
10114          (current_sort_params.order &&
10115            current_sort_params.order.toLowerCase() !==
10116              option_sort_params.order.toLowerCase()) ||
10117          (current_sort_params.orderby == "meta_key" &&
10118            current_sort_params.meta_key &&
10119            current_sort_params.meta_key.toLowerCase() !==
10120              option_sort_params.meta_key.toLowerCase()) ||
10121          (-1 !==
10122            $.inArray(current_sort_params.orderby, [
10123              "attribute",
10124              "attribute_num",
10125            ]) &&
10126            current_sort_params.orderby_attribute &&
10127            current_sort_params.orderby_attribute.toLowerCase() !==
10128              option_sort_params.orderby_attribute.toLowerCase()) ||
10129          (current_sort_params.orderby == "taxonomy" &&
10130            current_sort_params.orderby_taxonomy &&
10131            current_sort_params.orderby_taxonomy.toLowerCase() !==
10132              option_sort_params.orderby_taxonomy.toLowerCase())
10133        ) {
10134          return;
10135        }
10136
10137        var $dropdown = $('[data-wcpt-filter="sort_by"]', $wcpt), // might be row
10138          $selected_input = $dropdown.find("input").eq(dropdown_option_index),
10139          $selected_option = $selected_input.closest(
10140            ".wcpt-dropdown-option, .wcpt-option",
10141          ),
10142          $heading_label = $(".wcpt-dropdown-label", $dropdown);
10143
10144        $heading_label.text($("span", $selected_option).text());
10145        $selected_input.prop("checked", true);
10146        $selected_option
10147          .addClass("wcpt-active")
10148          .siblings()
10149          .removeClass("wcpt-active");
10150
10151        $dropdown.removeClass("wcpt-open");
10152      },
10153    );
10154
10155    // 'Sorting' column heading icons
10156    $.each(
10157      all_sort_params["column_heading"],
10158      function (id, option_sort_params) {
10159        if (
10160          current_sort_params.orderby !== option_sort_params.orderby ||
10161          ($.inArray(current_sort_params.orderby, [
10162            "meta_value",
10163            "meta_value_num",
10164          ]) &&
10165            current_sort_params.meta_key &&
10166            current_sort_params.meta_key.toLowerCase() !==
10167              option_sort_params.meta_key.toLowerCase()) ||
10168          (-1 !==
10169            $.inArray(current_sort_params.orderby, [
10170              "attribute",
10171              "attribute_num",
10172            ]) &&
10173            current_sort_params.orderby_attribute &&
10174            current_sort_params.orderby_attribute.toLowerCase() !==
10175              option_sort_params.orderby_attribute.toLowerCase()) ||
10176          (current_sort_params.orderby == "taxonomy" &&
10177            current_sort_params.orderby_taxonomy &&
10178            current_sort_params.orderby_taxonomy.toLowerCase() !==
10179              option_sort_params.orderby_taxonomy.toLowerCase())
10180        ) {
10181          return;
10182        }
10183
10184        var $sorting_icons = get_instant_sort_heading_icons(
10185            $wcpt,
10186            id,
10187            option_sort_params,
10188          ),
10189          new_order = current_sort_params.order.toLowerCase();
10190
10191        if (!$sorting_icons.length) {
10192          return;
10193        }
10194
10195        // UI feedback
10196        $(".wcpt-sorting-icons", $wcpt).removeClass(
10197          "wcpt-sorting-asc wcpt-sorting-desc",
10198        );
10199        $(".wcpt-sorting-icon", $wcpt).removeClass("wcpt-active wcpt-inactive");
10200
10201        if (new_order == "asc") {
10202          $sorting_icons
10203            .addClass("wcpt-sorting-asc")
10204            .removeClass("wcpt-sorting-desc");
10205        } else {
10206          $sorting_icons
10207            .addClass("wcpt-sorting-desc")
10208            .removeClass("wcpt-sorting-asc");
10209        }
10210
10211        $(".wcpt-sorting-" + new_order + "-icon", $sorting_icons).addClass(
10212          "wcpt-active",
10213        );
10214      },
10215    );
10216  }
10217
10218  window.wcpt_instant_sort = function (params, $wcpt) {
10219    var sort_data = $wcpt.data("wcpt_sort_data");
10220
10221    if (!params.order) {
10222      params.order = "asc";
10223    }
10224
10225    params.order = params.order.toLowerCase();
10226
10227    if (-1 !== $.inArray(params.orderby, ["price-desc", "rating"])) {
10228      params.order = "desc";
10229    }
10230
10231    switch (params.orderby) {
10232      case "title":
10233        sort_data.sort(function (a, b) {
10234          return params.order == "asc"
10235            ? a.title.localeCompare(b.title)
10236            : b.title.localeCompare(a.title);
10237        });
10238
10239        break;
10240
10241      case "sku": // as text
10242        sort_data.sort(function (a, b) {
10243          return params.order == "asc"
10244            ? a.sku.localeCompare(b.sku)
10245            : b.sku.localeCompare(a.sku);
10246        });
10247
10248        break;
10249
10250      case "sku_num":
10251        sort_data.sort(function (a, b) {
10252          var a_sku_num = isNaN(parseFloat(a.sku)) ? 0 : parseFloat(a.sku),
10253            b_sku_num = isNaN(parseFloat(b.sku)) ? 0 : parseFloat(b.sku);
10254
10255          return params.order == "asc"
10256            ? a_sku_num - b_sku_num
10257            : b_sku_num - a_sku_num;
10258        });
10259
10260        break;
10261
10262      case "menu_order":
10263        sort_data.sort(function (a, b) {
10264          return params.order == "asc"
10265            ? a.menu_order - b.menu_order
10266            : b.menu_order - a.menu_order;
10267        });
10268
10269        break;
10270
10271      case "price":
10272      case "price-desc":
10273        sort_data.sort(function (a, b) {
10274          var a_price = a.price,
10275            b_price = b.price;
10276
10277          if (params.order == "asc" && a.min_price) {
10278            a_price = a.min_price;
10279          }
10280
10281          if (params.order == "desc" && a.max_price) {
10282            a_price = a.max_price;
10283          }
10284
10285          if (params.order == "asc" && b.min_price) {
10286            b_price = b.min_price;
10287          }
10288
10289          if (params.order == "desc" && b.max_price) {
10290            b_price = b.max_price;
10291          }
10292
10293          return params.order == "asc" ? a_price - b_price : b_price - a_price;
10294        });
10295
10296        break;
10297
10298      case "meta_value": // as text
10299        sort_data.sort(function (a, b) {
10300          var key = "meta_value__" + params.meta_key,
10301            a_meta = a[key] == null ? "" : String(a[key]),
10302            b_meta = b[key] == null ? "" : String(b[key]);
10303
10304          return params.order == "asc"
10305            ? a_meta.localeCompare(b_meta)
10306            : b_meta.localeCompare(a_meta);
10307        });
10308
10309        break;
10310
10311      case "meta_value_num":
10312        sort_data.sort(function (a, b) {
10313          var key = "meta_value__" + params.meta_key,
10314            a_meta = a[key],
10315            b_meta = b[key];
10316
10317          var a_meta_num = isNaN(parseFloat(a_meta)) ? 0 : parseFloat(a_meta),
10318            b_meta_num = isNaN(parseFloat(b_meta)) ? 0 : parseFloat(b_meta);
10319
10320          return params.order == "asc"
10321            ? a_meta_num - b_meta_num
10322            : b_meta_num - a_meta_num;
10323        });
10324
10325        break;
10326
10327      case "attribute": // as text
10328        sort_data.sort(function (a, b) {
10329          var key = "attribute__" + params.orderby_attribute,
10330            a_val = a[key] == null ? "" : String(a[key]),
10331            b_val = b[key] == null ? "" : String(b[key]);
10332
10333          return params.order == "asc"
10334            ? a_val.localeCompare(b_val)
10335            : b_val.localeCompare(a_val);
10336        });
10337
10338        break;
10339
10340      case "attribute_num": // as number
10341        sort_data.sort(function (a, b) {
10342          var key = "attribute__" + params.orderby_attribute,
10343            a_val = a[key],
10344            b_val = b[key];
10345
10346          var a_val_num = isNaN(parseFloat(a_val)) ? 0 : parseFloat(a_val),
10347            b_val_num = isNaN(parseFloat(b_val)) ? 0 : parseFloat(b_val);
10348
10349          return params.order == "asc"
10350            ? a_val_num - b_val_num
10351            : b_val_num - a_val_num;
10352        });
10353
10354        break;
10355
10356      case "taxonomy": // as text
10357        sort_data.sort(function (a, b) {
10358          var key = "taxonomy__" + params.orderby_taxonomy,
10359            a_val = a[key] == null ? "" : String(a[key]),
10360            b_val = b[key] == null ? "" : String(b[key]);
10361
10362          return params.order == "asc"
10363            ? a_val.localeCompare(b_val)
10364            : b_val.localeCompare(a_val);
10365        });
10366
10367        break;
10368
10369      case "brand": // product_brand taxonomy (same prop key as taxonomy sort)
10370        sort_data.sort(function (a, b) {
10371          var key = "taxonomy__product_brand",
10372            a_val = a[key] || "",
10373            b_val = b[key] || "";
10374
10375          return params.order == "asc"
10376            ? a_val.localeCompare(b_val)
10377            : b_val.localeCompare(a_val);
10378        });
10379
10380        break;
10381
10382      case "taxonomy_num": // as number
10383        sort_data.sort(function (a, b) {
10384          var a_val = a["taxonomy__" + params.orderby_taxonomy],
10385            b_val = b["taxonomy__" + params.orderby_taxonomy];
10386
10387          var a_val_num = isNaN(parseFloat(a_val)) ? 0 : parseFloat(a_val),
10388            b_val_num = isNaN(parseFloat(b_val)) ? 0 : parseFloat(b_val);
10389
10390          return params.order == "asc"
10391            ? a_val_num - b_val_num
10392            : b_val_num - a_val_num;
10393        });
10394
10395        break;
10396
10397      case "category":
10398        sort_data.sort(function (a, b) {
10399          return params.order == "asc"
10400            ? a.category.localeCompare(b.category)
10401            : b.category.localeCompare(a.category);
10402        });
10403
10404      case "date":
10405      case "popularity":
10406      case "id":
10407      case "rating":
10408        sort_data.sort(function (a, b) {
10409          return params.order == "asc"
10410            ? a[params.orderby] - b[params.orderby]
10411            : b[params.orderby] - a[params.orderby];
10412        });
10413
10414        break;
10415
10416      default:
10417        break;
10418    }
10419
10420    // render
10421    $.each(sort_data, function (index, product_sort_data) {
10422      var $row = false;
10423      if (product_sort_data.variation_id) {
10424        // variation
10425        $row = $wcpt.find(
10426          '.wcpt-row[data-wcpt-variation-id="' +
10427            product_sort_data.variation_id +
10428            '"]',
10429        );
10430      } else {
10431        // other
10432        $row = $wcpt.find(
10433          '.wcpt-row[data-wcpt-product-id="' + product_sort_data.id + '"]',
10434        );
10435      }
10436
10437      $row.each(function () {
10438        var $this = $(this),
10439          $tbody = $this.closest("tbody");
10440        $this.detach().appendTo($tbody);
10441      });
10442    });
10443
10444    // -- group by category
10445    if (
10446      wcpt_util.get_sc_attrs($wcpt)[
10447        wcpt_util.get_device() + "_group_by_category"
10448      ]
10449    ) {
10450      $(
10451        $(".wcpt-row:not(.wcpt-row--category-heading)", $wcpt).get().reverse(),
10452      ).each(function () {
10453        var $this = $(this),
10454          cat_slug = $this.attr("data-wcpt-group-by-category-slug"),
10455          $category_heading = $this.siblings(
10456            '.wcpt-row--category-heading[data-wcpt-group-by-category-slug="' +
10457              cat_slug +
10458              '"]',
10459          );
10460        $this.detach().insertAfter($category_heading);
10461      });
10462    }
10463
10464    wcpt_util.assign_even_odd_row_classes($(".wcpt-table", $wcpt));
10465  };
10466})(jQuery);
10467
10468// module: download csv
10469(function ($) {
10470  $("body").on("click", ".wcpt-csv-download", function () {
10471    var $this = $(this),
10472      file_name = $this.attr("data-wcpt-file-name"),
10473      include_all_products = $this.attr("data-wcpt-csv-include-all-products"),
10474      $container = $this.closest(".wcpt"),
10475      encrypted_query_vars = $container.attr("data-wcpt-encrypted-query-vars"),
10476      encrypted_user_filters = $container.attr(
10477        "data-wcpt-encrypted-user-filters",
10478      ),
10479      encrypted_columns = $this.attr("data-wcpt-csv-encrypted-columns"),
10480      table_id = $this.closest(".wcpt").attr("data-wcpt-table-id");
10481
10482    if ($this.hasClass("wcpt-disabled")) {
10483      return;
10484    }
10485
10486    $.ajax({
10487      url: wcpt_params.wc_ajax_url.replace("%%endpoint%%", "wcpt_get_csv"),
10488      method: "POST", // @todo: change to GET
10489      beforeSend: function () {
10490        $this.addClass("wcpt-disabled wcpt-loading");
10491      },
10492      data: {
10493        wcpt_csv_include_all_products: include_all_products,
10494        wcpt_csv_encrypted_query_vars: encrypted_query_vars,
10495        wcpt_csv_encrypted_user_filters: encrypted_user_filters,
10496        wcpt_csv_encrypted_columns: encrypted_columns,
10497        wcpt_csv_table_id: table_id,
10498      },
10499    }).done(function (json_data) {
10500      $this.removeClass("wcpt-disabled wcpt-loading");
10501      build_csv_and_download(json_data, file_name);
10502    });
10503  });
10504
10505  function build_csv_and_download(json_data, file_name) {
10506    var csv = "";
10507    $.each(json_data, function (_, row) {
10508      $.each(row, function (_, val) {
10509        csv += val + ",";
10510      });
10511      csv = csv.slice(0, -1) + "\n";
10512    });
10513
10514    var $pseudo_link = $("<a>", {
10515      href: "data:Application/octet-stream," + encodeURIComponent(csv),
10516      download: file_name + ".csv",
10517    });
10518
10519    $pseudo_link.appendTo("body").get(0).click();
10520    $pseudo_link.remove();
10521  }
10522})(jQuery);
10523
10524// module: reveal sidebar
10525(function ($) {
10526  function get_reveal_sidebar_parts($from) {
10527    var $container = $from.closest(".wcpt"),
10528      $wrapper = $container.find(".wcpt-navigation-wrapper").first(),
10529      $button = $wrapper.find(".wcpt-header .wcpt-reveal-sidebar"),
10530      $sidebar = $wrapper.children(".wcpt-left-sidebar");
10531
10532    return {
10533      $container: $container,
10534      $wrapper: $wrapper,
10535      $button: $button,
10536      $sidebar: $sidebar,
10537    };
10538  }
10539
10540  function get_sidebar_filter_counts($sidebar) {
10541    var used = 0,
10542      total = 0;
10543
10544    if (!$sidebar || !$sidebar.length) {
10545      return { used: 0, total: 0 };
10546    }
10547
10548    $sidebar.find(".wcpt-filter").each(function () {
10549      var $filter = $(this),
10550        filter = $filter.attr("data-wcpt-filter"),
10551        $checked = $filter.find("input[type=checkbox]:checked"),
10552        $selected = $filter.find("input[type=radio]:checked"),
10553        $min = $filter.find(".wcpt-range-input-min"),
10554        $max = $filter.find(".wcpt-range-input-max"),
10555        is_used = false;
10556
10557      if (filter === "sort_by" || filter === "results_per_page") {
10558        return;
10559      }
10560
10561      total += 1;
10562
10563      if ($checked.length) {
10564        is_used = true;
10565      } else if ($selected.length && $selected.attr("value")) {
10566        is_used = true;
10567      } else if (
10568        $min.length &&
10569        ($min.val() != $min.attr("min") || $max.val() != $max.attr("max"))
10570      ) {
10571        is_used = true;
10572      }
10573
10574      if (is_used) {
10575        used += 1;
10576      }
10577    });
10578
10579    $sidebar.find(".wcpt-search").each(function () {
10580      var $search = $(this),
10581        keyword = ($search.find(".wcpt-search-input").val() || "").trim();
10582
10583      total += 1;
10584
10585      if (keyword || $search.hasClass("wcpt-active")) {
10586        used += 1;
10587      }
10588    });
10589
10590    return { used: used, total: total };
10591  }
10592
10593  function update_reveal_sidebar_count($from) {
10594    var parts = get_reveal_sidebar_parts($from),
10595      $button = parts.$button,
10596      $sidebar = parts.$sidebar,
10597      $count = $button.find(".wcpt-active-count--reveal-sidebar"),
10598      counts;
10599
10600    if (!$button.length || !$count.length) {
10601      return;
10602    }
10603
10604    counts = get_sidebar_filter_counts($sidebar);
10605    $count
10606      .text(counts.total)
10607      .toggleClass("wcpt-active-count--zero", !counts.used);
10608  }
10609
10610  function save_reveal_sidebar_scroll($from) {
10611    var parts = get_reveal_sidebar_parts($from),
10612      $container = parts.$container,
10613      $sidebar = parts.$sidebar,
10614      sidebar,
10615      scrollTop,
10616      anchorEl,
10617      $filter,
10618      active;
10619
10620    if (!$container.length || !$sidebar.length || !$sidebar[0]) {
10621      return;
10622    }
10623
10624    sidebar = $sidebar[0];
10625    scrollTop = sidebar.scrollTop;
10626    $container.data("wcpt-reveal-sidebar-scroll", scrollTop);
10627
10628    // Prefer the focused control; otherwise the first filter intersecting
10629    // the current viewport — used to re-align after content height shifts.
10630    active = document.activeElement;
10631    if (active && sidebar.contains(active)) {
10632      anchorEl = active.closest(".wcpt-filter, .wcpt-search");
10633    }
10634
10635    if (!anchorEl) {
10636      $sidebar.find(".wcpt-filter, .wcpt-search").each(function () {
10637        if (this.offsetTop + this.offsetHeight > scrollTop) {
10638          anchorEl = this;
10639          return false;
10640        }
10641      });
10642    }
10643
10644    if (!anchorEl) {
10645      $container.removeData("wcpt-reveal-sidebar-scroll-anchor");
10646      return;
10647    }
10648
10649    $filter = $(anchorEl).closest(".wcpt-filter");
10650    $container.data("wcpt-reveal-sidebar-scroll-anchor", {
10651      filter: $filter.attr("data-wcpt-filter") || "",
10652      taxonomy: $filter.attr("data-wcpt-taxonomy") || "",
10653      metaKey:
10654        $filter.attr("data-wcpt-meta-key") ||
10655        $filter.attr("data-wcpt-meta_key") ||
10656        "",
10657      isSearch: $(anchorEl).closest(".wcpt-search").length > 0,
10658      viewOffset: anchorEl.offsetTop - scrollTop,
10659    });
10660  }
10661
10662  function find_reveal_sidebar_scroll_anchor($sidebar, anchor) {
10663    var $match = $();
10664
10665    if (!anchor) {
10666      return $match;
10667    }
10668
10669    if (anchor.isSearch) {
10670      return $sidebar.find(".wcpt-search").first();
10671    }
10672
10673    if (!anchor.filter) {
10674      return $match;
10675    }
10676
10677    $match = $sidebar.find(
10678      '.wcpt-filter[data-wcpt-filter="' + anchor.filter + '"]',
10679    );
10680
10681    if (anchor.taxonomy) {
10682      $match = $match.filter('[data-wcpt-taxonomy="' + anchor.taxonomy + '"]');
10683    }
10684
10685    if (anchor.metaKey) {
10686      $match = $match.filter(function () {
10687        var $this = $(this);
10688        return (
10689          $this.attr("data-wcpt-meta-key") === anchor.metaKey ||
10690          $this.attr("data-wcpt-meta_key") === anchor.metaKey
10691        );
10692      });
10693    }
10694
10695    return $match.first();
10696  }
10697
10698  function restore_reveal_sidebar_scroll($from) {
10699    var parts = get_reveal_sidebar_parts($from),
10700      $container = parts.$container,
10701      $sidebar = parts.$sidebar,
10702      scroll = $container.data("wcpt-reveal-sidebar-scroll"),
10703      anchor = $container.data("wcpt-reveal-sidebar-scroll-anchor"),
10704      $anchor,
10705      sidebar;
10706
10707    if (!$sidebar.length || !$sidebar[0] || typeof scroll === "undefined") {
10708      return;
10709    }
10710
10711    sidebar = $sidebar[0];
10712    $anchor = find_reveal_sidebar_scroll_anchor($sidebar, anchor);
10713
10714    if ($anchor.length) {
10715      sidebar.scrollTop = Math.max(0, $anchor[0].offsetTop - anchor.viewOffset);
10716    } else {
10717      sidebar.scrollTop = scroll;
10718    }
10719  }
10720
10721  // Re-apply after layout settles — early restore can get clamped while the
10722  // panel's scrollHeight is still shrinking/growing, which drifts scroll up.
10723  function schedule_reveal_sidebar_scroll_restore($from) {
10724    var $container = $from.closest(".wcpt");
10725
10726    $container.data("wcpt-reveal-sidebar-scroll-pending", true);
10727    restore_reveal_sidebar_scroll($container);
10728
10729    requestAnimationFrame(function () {
10730      restore_reveal_sidebar_scroll($container);
10731      requestAnimationFrame(function () {
10732        restore_reveal_sidebar_scroll($container);
10733        $container.removeData("wcpt-reveal-sidebar-scroll-pending");
10734      });
10735    });
10736  }
10737
10738  function close_reveal_sidebar($from) {
10739    var parts = $from && $from.length ? get_reveal_sidebar_parts($from) : null,
10740      $wrappers = parts
10741        ? parts.$wrapper
10742        : $(".wcpt-navigation-wrapper--sidebar-open");
10743
10744    $wrappers.each(function () {
10745      var $wrapper = $(this),
10746        $container = $wrapper.closest(".wcpt");
10747
10748      $wrapper
10749        .removeClass("wcpt-navigation-wrapper--sidebar-open")
10750        .find("> .wcpt-left-sidebar")
10751        .attr("aria-hidden", "true");
10752      $wrapper
10753        .find(".wcpt-header .wcpt-reveal-sidebar")
10754        .attr("aria-expanded", "false");
10755      $container.data("wcpt-reveal-sidebar-open", false);
10756    });
10757
10758    $(".wcpt-reveal-sidebar-backdrop").remove();
10759    if (!$(".wcpt-navigation-wrapper--sidebar-open").length) {
10760      $("body").removeClass("wcpt-reveal-sidebar-on");
10761    }
10762  }
10763
10764  function open_reveal_sidebar($from, skip_animation) {
10765    var parts = get_reveal_sidebar_parts($from),
10766      $container = parts.$container,
10767      $wrapper = parts.$wrapper,
10768      $button = parts.$button,
10769      $sidebar = parts.$sidebar;
10770
10771    if (
10772      !$sidebar.length ||
10773      !$button.length ||
10774      $button.hasClass("wcpt-reveal-sidebar--unavailable")
10775    ) {
10776      return;
10777    }
10778
10779    // When restoring after a table refresh, open instantly (no slide-in)
10780    if (skip_animation) {
10781      $sidebar.addClass("wcpt-reveal-sidebar--no-anim");
10782    }
10783
10784    $wrapper.addClass("wcpt-navigation-wrapper--sidebar-open");
10785    $button.attr("aria-expanded", "true");
10786    $sidebar.attr("aria-hidden", "false");
10787    $container.data("wcpt-reveal-sidebar-open", true);
10788
10789    $("body").addClass("wcpt-reveal-sidebar-on");
10790    if (!$(".wcpt-reveal-sidebar-backdrop").length) {
10791      $("body").append('<div class="wcpt-reveal-sidebar-backdrop"></div>');
10792    }
10793
10794    if (skip_animation) {
10795      // force reflow so the "open" state applies without transition, then
10796      // restore transitions for subsequent interactions
10797      $sidebar[0].offsetHeight;
10798      $sidebar.removeClass("wcpt-reveal-sidebar--no-anim");
10799      // HTML replace resets scroll; put the panel back where the user was
10800      schedule_reveal_sidebar_scroll_restore($container);
10801    }
10802  }
10803
10804  function init_reveal_sidebar() {
10805    var $container = $(this),
10806      parts = get_reveal_sidebar_parts($container),
10807      $wrapper = parts.$wrapper,
10808      $button = parts.$button,
10809      $sidebar = parts.$sidebar;
10810
10811    if (!$button.length) {
10812      $wrapper.removeClass(
10813        "wcpt-navigation-wrapper--reveal-sidebar wcpt-navigation-wrapper--sidebar-open",
10814      );
10815      return;
10816    }
10817
10818    if (!$sidebar.length) {
10819      $button.addClass("wcpt-reveal-sidebar--unavailable");
10820      return;
10821    }
10822
10823    var table_id = $container.attr("data-wcpt-table-id"),
10824      sidebar_id = "wcpt-reveal-sidebar-" + table_id;
10825
10826    $wrapper.addClass("wcpt-navigation-wrapper--reveal-sidebar");
10827    $button.removeClass("wcpt-reveal-sidebar--unavailable").attr({
10828      "aria-controls": sidebar_id,
10829      "aria-expanded": "false",
10830    });
10831    $sidebar.attr({
10832      id: sidebar_id,
10833      "aria-hidden": "true",
10834    });
10835
10836    update_reveal_sidebar_count($container);
10837
10838    // Keep sidebar open across filter refreshes (without re-animating)
10839    if ($container.data("wcpt-reveal-sidebar-open")) {
10840      open_reveal_sidebar($container, true);
10841    }
10842  }
10843
10844  $("body")
10845    .on("wcpt_before_ajax_container_replace", function (e, data) {
10846      if (data && data.$container && data.$container.length) {
10847        save_reveal_sidebar_scroll(data.$container);
10848      }
10849    })
10850    .on("wcpt_layout", ".wcpt", function () {
10851      var $container = $(this);
10852      if (
10853        $container.data("wcpt-reveal-sidebar-open") &&
10854        $container.data("wcpt-reveal-sidebar-scroll-pending")
10855      ) {
10856        restore_reveal_sidebar_scroll($container);
10857      }
10858    })
10859    .on("wcpt_after_every_load", ".wcpt", init_reveal_sidebar)
10860    .on("click", ".wcpt-header .wcpt-reveal-sidebar", function (e) {
10861      e.preventDefault();
10862      open_reveal_sidebar($(this));
10863    })
10864    .on("click", ".wcpt-reveal-sidebar-backdrop", function () {
10865      close_reveal_sidebar($(".wcpt-navigation-wrapper--sidebar-open").first());
10866    })
10867    .on(
10868      "change input",
10869      ".wcpt-left-sidebar .wcpt-filter, .wcpt-left-sidebar .wcpt-search",
10870      function () {
10871        update_reveal_sidebar_count($(this));
10872      },
10873    )
10874    .on("keydown", function (e) {
10875      if (e.key === "Escape") {
10876        close_reveal_sidebar();
10877      }
10878    });
10879
10880  $(function () {
10881    $(".wcpt").each(function () {
10882      init_reveal_sidebar.call(this);
10883    });
10884  });
10885})(jQuery);
10886
10887// module: infinite scroll
10888(function ($) {
10889  // controller
10890  // -- after_every_load handler - attach observer once
10891  $("body").on("wcpt_after_every_load", ".wcpt", function () {
10892    var $this__container = $(this),
10893      sc_attrs = wcpt_util.get_sc_attrs($this__container),
10894      $infinite_scroll_dots = $(".wcpt-infinite-scroll-dots", $this__container);
10895
10896    if (!$infinite_scroll_dots.length) {
10897      return;
10898    }
10899
10900    // intersection observer for load trigger
10901    wcpt_util.run_once_on_container(
10902      $this__container,
10903      "wcpt_infinite_scroll_init",
10904      function ($container) {
10905        const intersectionObserver = new IntersectionObserver(
10906          (entries) => {
10907            if (entries[0].intersectionRatio <= 0) return;
10908
10909            if (
10910              !$container.hasClass(
10911                "wcpt-loading wcpt-infinite-scroll-loading-results",
10912              )
10913            ) {
10914              $container.trigger("wcpt_infinite_scroll");
10915            }
10916          },
10917          { rootMargin: "100px" },
10918        );
10919        // start observing
10920        intersectionObserver.observe($infinite_scroll_dots[0]);
10921        $this__container.data(
10922          "wcpt-infinite-scroll-intersection-observer",
10923          intersectionObserver,
10924        );
10925      },
10926    );
10927
10928    // fresh dots
10929    var intersection_observer = $this__container.data(
10930      "wcpt-infinite-scroll-intersection-observer",
10931    );
10932    intersection_observer.observe($infinite_scroll_dots[0]);
10933  });
10934
10935  // -- a separate trigger 'wcpt_infinite_scroll' that inits infinite scroll on $wcpt
10936  $("body").on("wcpt_infinite_scroll", ".wcpt", function () {
10937    var $this__container = $(this);
10938    append_next_page($this__container);
10939  });
10940
10941  // -- helper: handler
10942  append_next_page = ($container) => {
10943    var table_id = wcpt_util.get_table_id($container),
10944      page_number = wcpt_util.get_current_page_number($container),
10945      new_query = `${table_id}_paged=${page_number + 1}`,
10946      append_new_query = true,
10947      purpose = "infinite_scroll",
10948      query = window.wcpt_build_ajax_query_string(
10949        $container,
10950        new_query,
10951        append_new_query,
10952        purpose,
10953      );
10954
10955    window.wcpt_fetch_markup_and_apply_callback(
10956      $container,
10957      query,
10958      purpose,
10959      ajax_success__infinite_scroll,
10960    );
10961    $container.removeClass("wcpt-loading");
10962    $container.addClass("wcpt-infinite-scroll-loading-results");
10963  };
10964
10965  // ajax success handler
10966  function ajax_success__infinite_scroll(response, $container) {
10967    var $response = $(response),
10968      $new_container = $response.filter(".wcpt").first();
10969
10970    if (!$new_container.length) {
10971      $new_container = $response.find(".wcpt").first();
10972    }
10973    if (!$new_container.length) {
10974      $new_container = $response;
10975    }
10976
10977    var $new_rows = $(".wcpt-row", $new_container);
10978    $(".wcpt-table > tbody", $container).append($new_rows);
10979
10980    if (
10981      typeof wcpt_util !== "undefined" &&
10982      wcpt_util.merge_variable_switch_cf_from_response
10983    ) {
10984      wcpt_util.merge_variable_switch_cf_from_response(
10985        response,
10986        wcpt_util.get_table_id($container),
10987      );
10988    }
10989
10990    if (
10991      typeof wcpt_util !== "undefined" &&
10992      wcpt_util.merge_variable_switch_templates_from_response
10993    ) {
10994      wcpt_util.merge_variable_switch_templates_from_response(
10995        $new_container,
10996        $container,
10997      );
10998    }
10999
11000    $container.attr(
11001      "data-wcpt-query-string",
11002      $new_container.attr("data-wcpt-query-string"),
11003    );
11004    $container.removeClass("wcpt-infinite-scroll-loading-results");
11005
11006    // remove infinit scroll dots if results exhausted
11007    if (!$new_container.find(".wcpt-infinite-scroll-dots").length) {
11008      $container.find(".wcpt-infinite-scroll-dots").hide();
11009    }
11010
11011    $("body").trigger("wcpt_infinite_scroll_after_load", {
11012      $container: $container,
11013    });
11014  }
11015})(jQuery);
11016
11017// module: freeze table
11018(function ($) {
11019  // @TODO - add init command here as well
11020
11021  // destroy previous instance before replacing the table
11022  $("body").on("wcpt_before_ajax_container_replace", (ev, data) => {
11023    var $freeze_table = wcpt_util.get_freeze_table(data.$container);
11024    if ($freeze_table) {
11025      $freeze_table.freezeTable("destroy");
11026    }
11027  });
11028
11029  $("body")
11030    .on("wcpt-nav-modal-on", function () {
11031      $(".frzTbl-table").freezeTable("pause");
11032    })
11033    .on("wcpt-nav-modal-off", function () {
11034      $(".frzTbl-table").freezeTable("unpause");
11035    });
11036})(jQuery);
11037
11038// module: form mode
11039(function ($) {
11040  // redirect to shop with correct table id in url params
11041  $("body").on("wcpt_before_ajax_query", function (e, data) {
11042    var sc_attrs = wcpt_util.get_sc_attrs(data.$wcpt),
11043      purpose = data.purpose,
11044      table_id = wcpt_util.get_table_id(data.$wcpt);
11045
11046    if (purpose == "filter" && sc_attrs.form_mode) {
11047      if (
11048        sc_attrs.dynamic_filters_lazy_load ||
11049        sc_attrs.dynamic_hide_filters ||
11050        sc_attrs.dynamic_recount
11051      ) {
11052        var $apply = data.$wcpt.find(".wcpt-apply");
11053        if ($apply.length && !$apply.hasClass("wcpt-apply--clicked")) {
11054          return;
11055        }
11056      }
11057
11058      // replace table id in query string
11059      data.query = data.query
11060        .split(table_id + "_")
11061        .join(wcpt_params.shop_table_id + "_");
11062
11063      // don't let lang param repeat, lang=fr,fr error
11064      // var i = wcpt_params.shop_url.indexOf("?lang=");
11065      // if (i !== -1 && query.indexOf("lang=") !== -1) {
11066      //   wcpt_params.shop_url = wcpt_params.shop_url.substring(0, i);
11067      // }
11068
11069      // add query string to shop url
11070      data.query =
11071        wcpt_params.shop_url +
11072        (wcpt_params.shop_url.indexOf("?") == -1
11073          ? data.query
11074          : "&" + data.query.slice(1));
11075
11076      // hide form on submit
11077      if (sc_attrs.hide_form_on_submit) {
11078        data.query += "&hide_form=" + table_id;
11079      }
11080
11081      // disable device requirement
11082      data.query += "&" + wcpt_params.shop_table_id + "_device=";
11083
11084      // redirect to shop with correct table id in url params
11085      window.location = data.query;
11086
11087      // disable proceeding with ajax query
11088      data.proceed = false;
11089    }
11090  });
11091})(jQuery);
11092
11093// module permission
11094// -- check if disabled
11095wcpt_is_module_disabled = function (module, $container) {
11096  var disabled_modules = $container.data("wcpt-disabled-modules");
11097  if (!disabled_modules) {
11098    return false;
11099  }
11100
11101  var disabled = false;
11102  jQuery.each(disabled_modules, function (key, val) {
11103    if (val.module == module) {
11104      disabled = true;
11105    }
11106  });
11107
11108  return disabled;
11109};
11110
11111// -- disable
11112wcpt_disable_module = function (module, $container, source) {
11113  var disabled_modules = $container.data("wcpt-disabled-modules");
11114  if (!disabled_modules) {
11115    disabled_modules = [];
11116  }
11117
11118  disabled_modules.push({
11119    module: module,
11120    source: source,
11121  });
11122
11123  $container.data("wcpt-disabled-modules", disabled_modules);
11124};
11125
11126// -- permit
11127wcpt_permit_module = function (module, $container, source) {
11128  var disabled_modules = $container.data("wcpt-disabled-modules");
11129  if (!disabled_modules) {
11130    disabled_modules = [];
11131  }
11132
11133  var i = disabled_modules.length;
11134  while (i) {
11135    --i;
11136    if (
11137      disabled_modules[i].module == module &&
11138      disabled_modules[i].source == source
11139    ) {
11140      disabled_modules.splice(i, 1);
11141    }
11142  }
11143
11144  $container.data("wcpt-disabled-modules", disabled_modules);
11145};
11146
11147// util functions
11148(function ($) {
11149  window.wcpt_util = {
11150    get_table_id: ($wcpt) => {
11151      return $wcpt.attr("data-wcpt-table-id");
11152    },
11153
11154    get_current_page_number: ($wcpt) => {
11155      var params = wcpt_util.get_table_parsed_query($wcpt),
11156        table_id = wcpt_util.get_table_id($wcpt),
11157        sc_attrs = wcpt_util.get_sc_attrs($wcpt),
11158        current_page = params[`${table_id}_paged`]
11159          ? params[`${table_id}_paged`]
11160          : 1;
11161      current_page = parseInt(current_page, 10);
11162
11163      // Archive tables: fall back to the URL when PHP missed the page query var
11164      // (common on the WooCommerce shop page, which paginates via `page` not `paged`).
11165      if (current_page <= 1 && sc_attrs._archive) {
11166        if (wcpt_params.permalink_structure) {
11167          var regex = new RegExp(
11168            "\\/" + wcpt_params.pagination_slug + "\\/(\\d+)",
11169          );
11170          var match = window.location.pathname.match(regex);
11171          if (match && match[1]) {
11172            current_page = parseInt(match[1], 10);
11173          }
11174        } else {
11175          var url_params = new URLSearchParams(window.location.search);
11176          if (url_params.has(wcpt_params.pagination_slug)) {
11177            current_page = parseInt(
11178              url_params.get(wcpt_params.pagination_slug),
11179              10,
11180            );
11181          }
11182        }
11183      }
11184
11185      return current_page;
11186    },
11187
11188    get_table_parsed_query: ($wcpt) => {
11189      var query_string = $wcpt.attr("data-wcpt-query-string"),
11190        _index = query_string.indexOf("?"),
11191        params =
11192          _index == -1
11193            ? {}
11194            : wcpt_util.parse_query_string(query_string.slice(_index + 1));
11195
11196      return params;
11197    },
11198
11199    parse_query_string: (query) => {
11200      // if first character is ? then remove it
11201      if (query.charAt(0) === "?") {
11202        query = query.slice(1);
11203      }
11204
11205      var vars = query.split("&");
11206      var query_string = {};
11207      for (var i = 0; i < vars.length; i++) {
11208        var pair = vars[i].split("="),
11209          key = decodeURIComponent(pair[0]),
11210          value = decodeURIComponent(pair[1]);
11211
11212        // If first entry with this name
11213        if (typeof query_string[key] === "undefined") {
11214          query_string[key] = value;
11215
11216          // If second entry with this name
11217        } else if (typeof query_string[key] === "string") {
11218          var arr = [query_string[key], value];
11219          query_string[key] = arr;
11220
11221          // If third or later entry with this name
11222        } else {
11223          query_string[key].push(value);
11224        }
11225      }
11226      return query_string;
11227    },
11228
11229    get_sc_attrs: ($container, refresh_cache) => {
11230      if (!refresh_cache) {
11231        if ($container.data("wcpt_sc_attrs")) {
11232          return $container.data("wcpt_sc_attrs");
11233        } else {
11234          return {};
11235        }
11236      }
11237
11238      var sc_attrs = {};
11239      var sc_attrs_string = $container.attr("data-wcpt-sc-attrs");
11240      if (sc_attrs_string && -1 == $.inArray(sc_attrs_string, ["[]", "{}"])) {
11241        sc_attrs = JSON.parse(sc_attrs_string);
11242      }
11243      $container.data("wcpt_sc_attrs", sc_attrs); // access sc attrs from here
11244
11245      return sc_attrs;
11246    },
11247
11248    get_device: () => {
11249      // device
11250      var device = "laptop"; // default
11251
11252      if ($(window).width() <= wcpt_params.breakpoints.phone) {
11253        device = "phone";
11254      } else if ($(window).width() <= wcpt_params.breakpoints.tablet) {
11255        device = "tablet";
11256      }
11257
11258      return device;
11259    },
11260
11261    get_uninit_rows: ($container, refresh_cache) => {
11262      if (!refresh_cache) {
11263        return $container.data("wcpt_new_rows");
11264      }
11265
11266      var $new_rows = $container.find(".wcpt-row:not(.wcpt-row--init)");
11267      $container.data("wcpt_new_rows", $new_rows); // access sc attrs from here
11268
11269      return $new_rows;
11270    },
11271
11272    run_once_on_container: ($container, init_data_flag, callback) => {
11273      if (!$container.data(init_data_flag)) {
11274        callback($container);
11275        $container.data(init_data_flag, true);
11276      }
11277    },
11278
11279    // legacy
11280    do_once_on_container: ($container, init_data_flag, callback) => {
11281      wcpt_util.run_once_on_container($container, init_data_flag, callback);
11282    },
11283
11284    get_freeze_table: ($container) => {
11285      var $freeze_table = false;
11286      $container.find(".frzTbl-table").each(function () {
11287        var $this = $(this);
11288        if ($this.data("freezeTable")) {
11289          $freeze_table = $this;
11290        }
11291      });
11292      return $freeze_table;
11293    },
11294
11295    assign_even_odd_row_classes: ($table) => {
11296      var $rows = $(
11297          ".wcpt-row:not(.wcpt-child-row, .wcpt-row--category-heading):visible",
11298          $table,
11299        ),
11300        $child_rows = $(".wcpt-child-row", $table);
11301
11302      $rows.each(function () {
11303        var $this = $(this);
11304
11305        if ($rows.index($this) % 2) {
11306          $this.addClass("wcpt-even").removeClass("wcpt-odd");
11307        } else {
11308          $this.addClass("wcpt-odd").removeClass("wcpt-even");
11309        }
11310      });
11311
11312      $child_rows.each(function () {
11313        var $this = $(this),
11314          $parent_row = $this.data("wcpt_parent_row");
11315
11316        if ($parent_row.hasClass("wcpt-even")) {
11317          $this.removeClass("wcpt-odd").addClass("wcpt-even");
11318        } else {
11319          // odd
11320          $this.removeClass("wcpt-even").addClass("wcpt-odd");
11321        }
11322      });
11323    },
11324
11325    update_url: (query, $container) => {
11326      if (typeof window.history === "undefined") {
11327        return;
11328      }
11329
11330      // Get current URL and params
11331      const current_url = new URL(window.location.href);
11332      const new_params = new URLSearchParams(query);
11333
11334      // Start fresh with current non-table params
11335      const final_params = new URLSearchParams();
11336
11337      // Copy over non-table related params from current URL
11338      current_url.searchParams.forEach((val, key) => {
11339        // Skip table-specific params (those with numeric prefix)
11340        if (!/^\d+_\w+/.test(key)) {
11341          // Only add if not in new_params
11342          if (!new_params.has(key)) {
11343            final_params.append(key, val);
11344          }
11345        }
11346      });
11347
11348      // Add new params, filtering out unnecessary ones
11349      new_params.forEach((val, key) => {
11350        // Skip device params
11351        if (key.slice(-7) === "_device") {
11352          return;
11353        }
11354
11355        // Skip paged=1 params
11356        if (key.slice(-6) === "_paged" && val === "1") {
11357          return;
11358        }
11359
11360        // Add param (will override any existing)
11361        final_params.append(key, val);
11362      });
11363
11364      // update pagination on archive pages
11365      const table_id = wcpt_util.get_table_id($container);
11366      const sc_attrs = wcpt_util.get_sc_attrs($container);
11367
11368      if (sc_attrs._archive) {
11369        // Archive page pagination (uses WP core pagination)
11370        const current_page = wcpt_util.get_current_page_number($container);
11371
11372        // if permalinks are like ?paged=2 then do that
11373        if (!wcpt_params.permalink_structure) {
11374          if (current_page > 1) {
11375            final_params.set("paged", current_page);
11376          } else {
11377            final_params.delete("paged");
11378          }
11379        } else {
11380          if (current_page <= 1) {
11381            // Remove pagination from URL
11382            const regex = new RegExp(
11383              "\\/(?:page|" + wcpt_params.pagination_slug + ")\\/\\d+",
11384            );
11385            current_url.pathname = current_url.pathname.replace(regex, "");
11386          } else {
11387            // Check if URL already has /{pagination_slug}/ in it
11388            if (
11389              current_url.pathname.includes(
11390                "/" + wcpt_params.pagination_slug + "/",
11391              )
11392            ) {
11393              // Replace existing page number
11394              const regex = new RegExp(
11395                "\\/" + wcpt_params.pagination_slug + "\\/\\d+",
11396              );
11397              current_url.pathname = current_url.pathname.replace(
11398                regex,
11399                "/" + wcpt_params.pagination_slug + "/" + current_page,
11400              );
11401            } else {
11402              // Add /{pagination_slug}/N to end of URL before query params
11403              current_url.pathname =
11404                current_url.pathname.replace(/\/$/, "") +
11405                "/" +
11406                wcpt_params.pagination_slug +
11407                "/" +
11408                current_page;
11409            }
11410          }
11411        }
11412
11413        final_params.delete(`${table_id}_paged`);
11414      }
11415
11416      // Update URL
11417      current_url.search = final_params.toString();
11418
11419      var data = {
11420        url: current_url.href,
11421        $container: $container,
11422        proceed: true,
11423      };
11424
11425      $("body").trigger("wcpt_update_url", data);
11426
11427      if (data.proceed) {
11428        window.history.replaceState({}, "", data.url);
11429      }
11430    },
11431
11432    remove_param_from_url: (param) => {
11433      if (
11434        param &&
11435        typeof window.history !== "undefined" &&
11436        window.location.search
11437      ) {
11438        var url_object = new URL(window.location);
11439        url_object.searchParams.delete(param);
11440        history.replaceState({}, "", url_object.href);
11441      }
11442    },
11443
11444    get_encrypted_attrs: ($container) => {
11445      const encrypted_attrs = {};
11446      $container.each(function () {
11447        $.each(this.attributes, function () {
11448          if (this.specified && this.name.startsWith("data-wcpt-encrypted-")) {
11449            encrypted_attrs[this.name] = this.value;
11450          }
11451        });
11452      });
11453      return encrypted_attrs;
11454    },
11455
11456    infinite_scroll_enabled: ($container) => {
11457      var sc_attrs = wcpt_util.get_sc_attrs($container);
11458      var device = wcpt_util.get_device($container);
11459      return sc_attrs[device + "_infinite_scroll"];
11460    },
11461
11462    merge_variable_switch_cf_from_response: (response, table_id) => {
11463      if (!response || !table_id) {
11464        return;
11465      }
11466
11467      var varName = "wcpt_" + table_id + "_variable_switch_cf",
11468        prefix = "var " + varName + " = ",
11469        text = typeof response === "string" ? response : "",
11470        idx = text.indexOf(prefix),
11471        jsonStr,
11472        newData;
11473
11474      if (idx === -1) {
11475        return;
11476      }
11477
11478      jsonStr = text.substring(idx + prefix.length).trim();
11479
11480      if (jsonStr.charAt(jsonStr.length - 1) === ";") {
11481        jsonStr = jsonStr.slice(0, -1).trim();
11482      }
11483
11484      idx = jsonStr.indexOf("</script>");
11485      if (idx !== -1) {
11486        jsonStr = jsonStr.substring(0, idx).trim();
11487        if (jsonStr.charAt(jsonStr.length - 1) === ";") {
11488          jsonStr = jsonStr.slice(0, -1).trim();
11489        }
11490      }
11491
11492      try {
11493        newData = JSON.parse(jsonStr);
11494      } catch (e) {
11495        return;
11496      }
11497
11498      if (!newData || typeof newData !== "object") {
11499        return;
11500      }
11501
11502      if (typeof window[varName] === "undefined") {
11503        window[varName] = newData;
11504        return;
11505      }
11506
11507      $.each(newData, function (element_id, product_vals) {
11508        if (
11509          typeof window[varName][element_id] === "undefined" ||
11510          window[varName][element_id] === null
11511        ) {
11512          window[varName][element_id] = product_vals;
11513        } else if (product_vals && typeof product_vals === "object") {
11514          $.extend(window[varName][element_id], product_vals);
11515        }
11516      });
11517    },
11518
11519    // Merge price / availability <script type="text/template"> nodes from an
11520    // AJAX response into the live table. Needed for paginate-only swaps and
11521    // infinite scroll, which only append rows and would otherwise leave
11522    // variable-switch price resets without a template (blank prices).
11523    merge_variable_switch_templates_from_response: ($source, $container) => {
11524      if (!$source || !$source.length || !$container || !$container.length) {
11525        return;
11526      }
11527
11528      var $target_scope = $container.find(".wcpt-nav-main").first();
11529      if (!$target_scope.length) {
11530        $target_scope = $container;
11531      }
11532
11533      $source
11534        .find(
11535          "script[data-wcpt-price-type], script[data-wcpt-availability-message]",
11536        )
11537        .addBack(
11538          "script[data-wcpt-price-type], script[data-wcpt-availability-message]",
11539        )
11540        .each(function () {
11541          var $tpl = $(this),
11542            element_id = $tpl.attr("data-wcpt-element-id"),
11543            price_type = $tpl.attr("data-wcpt-price-type"),
11544            availability_message = $tpl.attr("data-wcpt-availability-message"),
11545            selector;
11546
11547          if (!element_id) {
11548            return;
11549          }
11550
11551          if (price_type) {
11552            selector =
11553              'script[data-wcpt-element-id="' +
11554              element_id +
11555              '"][data-wcpt-price-type="' +
11556              price_type +
11557              '"]';
11558          } else if (availability_message) {
11559            selector =
11560              'script[data-wcpt-element-id="' +
11561              element_id +
11562              '"][data-wcpt-availability-message="' +
11563              availability_message +
11564              '"]';
11565          } else {
11566            return;
11567          }
11568
11569          if (!$container.find(selector).length) {
11570            $target_scope.append($tpl.clone());
11571          }
11572        });
11573    },
11574
11575    format_price_figure: (price) => {
11576      price = parseFloat(price);
11577
11578      // remove decimal if it is unnecessary ie .00
11579      if (price !== parseInt(price)) {
11580        price = parseFloat(price).toFixed(wcpt_params.price_decimals);
11581      } else {
11582        price = parseInt(price);
11583      }
11584
11585      // decimal separator
11586      price = (price + "").replace(".", wcpt_params.price_decimal_separator);
11587
11588      // thousands separator
11589      var decimal_split = price.split(wcpt_params.price_decimal_separator);
11590      ((non_decimal = decimal_split[0].toString()),
11591        (decimal = decimal_split[1]));
11592
11593      if (non_decimal.length > 3) {
11594        formatted_non_decimal =
11595          non_decimal.slice(0, -3) +
11596          wcpt_params.price_thousand_separator +
11597          non_decimal.slice(-3);
11598        price =
11599          formatted_non_decimal +
11600          (decimal ? wcpt_params.price_decimal_separator + decimal : "");
11601      }
11602
11603      return price;
11604    },
11605
11606    reload_table: ($container) => {
11607      var $wcpt = $container.hasClass("wcpt")
11608        ? $container
11609        : $container.closest(".wcpt");
11610      if (!$wcpt.length) return;
11611      var table_id = $wcpt.attr("data-wcpt-table-id");
11612      // Clear cache for this table so reload fetches fresh data
11613      if (window.wcpt_cache && window.wcpt_cache.data) {
11614        $.each(window.wcpt_cache.data, function (key) {
11615          if (
11616            key.indexOf("?" + table_id + "_") !== -1 ||
11617            key.indexOf("&" + table_id + "_") !== -1
11618          ) {
11619            delete window.wcpt_cache.data[key];
11620          }
11621        });
11622      }
11623      // Reload with all current parameters (same as refresh_table flow)
11624      window.wcpt_attempt_ajax($wcpt, "", true, "refresh_table");
11625    },
11626  };
11627})(jQuery);
11628
11629// Create a new event called wcptPressing
11630(function ($) {
11631  const holdDelay = 300;
11632  const moveThreshold = 10;
11633  const holdingInterval = 100;
11634  const activeHolds = new Map();
11635  let currentMouseTarget = null; // Track the element where mousedown started
11636
11637  // Helper function to handle hold start
11638  function startHold(element, holdData) {
11639    const holdTimer = setTimeout(() => {
11640      const hold = activeHolds.get(holdData.target);
11641      if (hold) {
11642        element.trigger("wcptTouchHoldStart");
11643        hold.hasStarted = true;
11644
11645        const holdingTimer = setInterval(() => {
11646          if (hold.hasStarted) {
11647            element.trigger("wcptPressing");
11648          }
11649        }, holdingInterval);
11650        hold.holdingTimer = holdingTimer;
11651      }
11652    }, holdDelay);
11653
11654    activeHolds.set(holdData.target, {
11655      element,
11656      holdTimer,
11657      holdingTimer: null,
11658      startY: holdData.startY,
11659      hasStarted: false,
11660    });
11661  }
11662
11663  // Helper function to handle hold end
11664  function endHold(target) {
11665    const hold = activeHolds.get(target);
11666    if (hold) {
11667      clearTimeout(hold.holdTimer);
11668      clearInterval(hold.holdingTimer);
11669      if (hold.hasStarted) {
11670        hold.element.trigger("wcptTouchHoldStop");
11671      }
11672      activeHolds.delete(target);
11673    }
11674  }
11675
11676  // Touch Events
11677  $(document).on("touchstart", function (e) {
11678    const element = $(e.target);
11679    startHold(element, {
11680      target: e.target,
11681      startY: e.originalEvent.touches[0].clientY,
11682    });
11683  });
11684
11685  $(document).on("touchmove", function (e) {
11686    const touch = e.originalEvent.touches[0];
11687    const hold = activeHolds.get(e.target);
11688
11689    if (hold) {
11690      const moveY = Math.abs(touch.clientY - hold.startY);
11691      if (moveY > moveThreshold) {
11692        endHold(e.target);
11693      }
11694    }
11695  });
11696
11697  $(document).on("touchend touchcancel", function (e) {
11698    endHold(e.target);
11699  });
11700
11701  // Mouse Events
11702  $(document).on("mousedown", function (e) {
11703    currentMouseTarget = e.target; // Store the initial target
11704    const element = $(currentMouseTarget);
11705    startHold(element, {
11706      target: currentMouseTarget,
11707      startY: e.clientY,
11708    });
11709  });
11710
11711  $(document).on("mousemove", function (e) {
11712    if (currentMouseTarget) {
11713      // Only check if we have an active mousedown
11714      const hold = activeHolds.get(currentMouseTarget);
11715      if (hold) {
11716        const moveY = Math.abs(e.clientY - hold.startY);
11717        if (moveY > moveThreshold) {
11718          endHold(currentMouseTarget);
11719          currentMouseTarget = null;
11720        }
11721      }
11722    }
11723  });
11724
11725  $(document).on("mouseup", function (e) {
11726    if (currentMouseTarget) {
11727      endHold(currentMouseTarget);
11728      currentMouseTarget = null;
11729    }
11730  });
11731
11732  // Handle cases where mouse leaves the document
11733  $(document).on("mouseleave", function (e) {
11734    if (currentMouseTarget) {
11735      endHold(currentMouseTarget);
11736      currentMouseTarget = null;
11737    }
11738  });
11739})(jQuery);
11740
11741// woocommerce mini-cart block integration with MutationObserver
11742(function ($) {
11743  // Set up MutationObserver to watch for changes to data-cart-totals
11744  const miniCartElement = document.querySelector(".wc-block-mini-cart");
11745
11746  if (miniCartElement) {
11747    // Keep track of the previous cart totals to detect actual changes
11748    let previousCartTotals = miniCartElement.getAttribute("data-cart-totals");
11749
11750    // Create a new observer
11751    const observer = new MutationObserver(function (mutations) {
11752      mutations.forEach(function (mutation) {
11753        if (
11754          mutation.type === "attributes" &&
11755          mutation.attributeName === "data-cart-totals"
11756        ) {
11757          const newCartTotals =
11758            miniCartElement.getAttribute("data-cart-totals");
11759
11760          // Only run wcpt_cart if the data-cart-totals value has changed
11761          if (newCartTotals !== previousCartTotals) {
11762            previousCartTotals = newCartTotals;
11763            if (typeof wcpt_cart === "function") {
11764              wcpt_cart();
11765            }
11766          }
11767        }
11768      });
11769    });
11770
11771    // Start observing the mini cart element for attribute changes
11772    observer.observe(miniCartElement, {
11773      attributes: true,
11774      attributeFilter: ["data-cart-totals"], // Only watch the data-cart-totals attribute
11775    });
11776  }
11777
11778  function refreshCartStore() {
11779    if (window.wp && window.wp.data) {
11780      // Option 1: Invalidate the cart store's resolution cache and force a refresh
11781      const cartStore = window.wp.data.dispatch("wc/store/cart");
11782      if (cartStore) {
11783        // First invalidate any cached data
11784        if (cartStore.invalidateResolutionForStore) {
11785          cartStore.invalidateResolutionForStore();
11786        }
11787
11788        // Force a refresh by requesting cart data again
11789        window.wp.data.select("wc/store/cart").getCartData(true); // true forces bypass of cache
11790
11791        return true;
11792      }
11793
11794      // Option 2: Alternative approach using resolveSelect if the above doesn't work
11795      const resolver =
11796        window.wp.data &&
11797        window.wp.data.controls &&
11798        window.wp.data.controls.resolveSelect;
11799      if (resolver) {
11800        resolver("wc/store/cart", "getCartData", [true]); // Force refresh
11801        return true;
11802      }
11803    }
11804  }
11805
11806  $(document).on("wcpt_cart", function (e, data) {
11807    const wp = window.wp;
11808    if (!wp || !wp.data) return;
11809
11810    const cartStore = wp.data.select("wc/store/cart");
11811    if (!cartStore || !cartStore.getCartData) return;
11812
11813    const cartData = cartStore.getCartData();
11814    if (!cartData || !cartData.items) return;
11815
11816    const totalQuantity = cartData.items.reduce((total, item) => {
11817      return total + (item.quantity || 0);
11818    }, 0);
11819
11820    if (data.cart_quantity != totalQuantity) {
11821      refreshCartStore();
11822    }
11823  });
11824
11825  // window.wcptRefreshMiniCart = refreshMiniCart;
11826})(jQuery);
11827
11828// WP MediaElementJS audio player playing HTML class for WCPT tables
11829(function ($) {
11830  // Handle audio player initialization after WCPT content loads
11831  $("body").on("wcpt_after_every_load", ".wcpt", function () {
11832    var $new_rows = wcpt_util.get_uninit_rows($(this), true);
11833
11834    // Find all audio players in the newly loaded rows
11835    var $mejs_players = $new_rows.find(".wcpt-audio-player .mejs-container");
11836    // Initialize each audio player
11837    initMejsPlayers($mejs_players);
11838  });
11839
11840  // Helper function to initialize audio players
11841  function initMejsPlayers($mejs_players) {
11842    $mejs_players.each(function () {
11843      var $player = $(this);
11844      var playingClass = "wcpt-mejs-playing";
11845
11846      // Get the native media element
11847      var mediaElement = $player.find("audio")[0];
11848
11849      // For existing MediaElement.js instances
11850      if (mediaElement.player) {
11851        attachMediaEvents(mediaElement, $player, playingClass);
11852      }
11853      // For players that haven't been initialized yet
11854      else {
11855        // Wait for MediaElement to initialize
11856        $(document).on("MEJSready", function (e, mejsObj) {
11857          // Check if this is our audio element
11858          if (mejsObj && mejsObj.media === mediaElement) {
11859            attachMediaEvents(mediaElement, $player, playingClass);
11860          }
11861        });
11862
11863        // Fallback to native media events
11864        attachMediaEvents(mediaElement, $player, playingClass);
11865      }
11866    });
11867  }
11868
11869  // Helper function to attach media event handlers
11870  function attachMediaEvents(mediaElement, $player, playingClass) {
11871    if (!mediaElement || !$player.length) return;
11872
11873    // Add event listeners
11874    mediaElement.addEventListener("play", handlePlay);
11875    mediaElement.addEventListener("pause", handlePause);
11876    mediaElement.addEventListener("ended", handleEnded);
11877
11878    // Named event handlers so we can remove them if needed
11879    function handlePlay() {
11880      $player.addClass(playingClass);
11881    }
11882
11883    function handlePause() {
11884      $player.removeClass(playingClass);
11885    }
11886
11887    function handleEnded() {
11888      $player.removeClass(playingClass);
11889    }
11890  }
11891})(jQuery);
11892
11893// module: apply / reset navigation buttons
11894(function ($) {
11895  // apply button
11896  // -- trigger change
11897  $("body").on("click", ".wcpt-apply", function () {
11898    var $this = $(this);
11899    $this.addClass("wcpt-apply--clicked");
11900    $(this).trigger("change");
11901  });
11902  // -- stop navigation if change came from anything else
11903  $("body").on("wcpt_after_navigation_change", ".wcpt", function (e, data) {
11904    var $container = $(this),
11905      sc_attrs = wcpt_util.get_sc_attrs($container),
11906      $apply = $(".wcpt-apply", $container);
11907
11908    if (!$apply.length) {
11909      return;
11910    }
11911
11912    // don't interrup:
11913    // -- form mode with adaptive filters
11914    if (
11915      sc_attrs.form_mode &&
11916      (sc_attrs.dynamic_filters_lazy_load ||
11917        sc_attrs.dynamic_hide_filters ||
11918        sc_attrs.dynamic_recount)
11919    ) {
11920      return;
11921    }
11922    // -- inputs marked for immediate filter
11923    if (
11924      $(data.event.target).closest('[data-wcpt-nav-apply-immediately="true"]')
11925        .length
11926    ) {
11927      return;
11928    }
11929
11930    if (!$container.find(".wcpt-apply--clicked").length) {
11931      data.proceed = false;
11932    }
11933  });
11934})(jQuery);
11935
11936// module: dynamic filters
11937(function ($) {
11938  // filters lazy load
11939  $("body").on("wcpt_after_every_load", ".wcpt", function () {
11940    var $container = $(this),
11941      sc_attrs = wcpt_util.get_sc_attrs($container),
11942      last_ajax_purpose = $container.data("wcpt_last_ajax_purpose");
11943
11944    // check for shortcode attrs
11945    if (
11946      last_ajax_purpose === "paginate" ||
11947      !sc_attrs.dynamic_filters_lazy_load ||
11948      (!sc_attrs.dynamic_recount && !sc_attrs.dynamic_hide_filters)
11949    ) {
11950      return;
11951    }
11952
11953    var encrypted_cache = $container.attr(
11954      "data-wcpt-encrypted-dynamic-filters-lazy-load-cache",
11955    );
11956
11957    $.ajax({
11958      url: wcpt_params.wc_ajax_url.replace(
11959        "%%endpoint%%",
11960        "wcpt__dynamic_filter__lazy_load",
11961      ),
11962      method: "POST",
11963      data: {
11964        wcpt_dynamic_filter_encrypted_cache: encrypted_cache,
11965      },
11966      success: function (result) {
11967        var result = JSON.parse(result);
11968
11969        // append style and script
11970        $(result.style + result.script).appendTo($container);
11971
11972        var $filters = $(".wcpt-filter", $container);
11973
11974        var target_filters = [
11975          "category",
11976          "attribute",
11977          "availability",
11978          "on_sale",
11979          "taxonomy",
11980        ];
11981
11982        if ($container.data("wcpt_sc_attrs").dynamic_recount) {
11983          $filters.each(function () {
11984            var $filter = $(this),
11985              filter_type = $filter.attr("data-wcpt-filter");
11986            if (!target_filters.includes(filter_type)) return true;
11987
11988            var taxonomy = false;
11989            if (["taxonomy", "attribute"].includes(filter_type)) {
11990              taxonomy = $filter.attr("data-wcpt-taxonomy");
11991            }
11992
11993            result.options.forEach(function (option) {
11994              if (option.filter !== filter_type) return;
11995              if (taxonomy && option.taxonomy !== taxonomy) return;
11996
11997              var $option = $(
11998                '[data-wcpt-value="' + option.value + '"]',
11999                $filter,
12000              );
12001              if (!$option.length) return;
12002
12003              var count =
12004                  '<span class="wcpt-count">(' + option.count + ")</span>",
12005                $label = $(
12006                  'label[data-wcpt-value="' + option.value + '"]',
12007                  $filter,
12008                ),
12009                $icon = $label.children(".wcpt-icon"),
12010                $prev_count = $(".wcpt-count", $label);
12011
12012              $prev_count.remove(); // in case duplicate taxonomy filter is printed
12013
12014              if ($icon.length) {
12015                $icon.before(count);
12016              } else {
12017                $label.append(count);
12018              }
12019            });
12020          });
12021        }
12022
12023        $filters.removeClass("wcpt--dynamic-filters--loading-filter");
12024      },
12025    });
12026  });
12027})(jQuery);
12028
12029// module: connect with archive search
12030(function ($) {
12031  $("body").on("wcpt_before_ajax_query", function (e, data) {
12032    var $archive_search = data.$wcpt.find(
12033      ".wcpt-search--connect-with-archive input[type='search']",
12034    );
12035
12036    if ($archive_search.length) {
12037      var current_keyword = $archive_search.val().trim();
12038      var previous_keyword = wcpt_util.get_sc_attrs(data.$wcpt)._search;
12039
12040      if (current_keyword !== previous_keyword) {
12041        // redirect to archive search
12042        window.location.href =
12043          wcpt_params.shop_url + "?s=" + current_keyword + "&post_type=product";
12044        data.proceed = false;
12045      } else {
12046        var search_param_name = $archive_search.attr("name");
12047        var regex = new RegExp(search_param_name + "=[^&]*");
12048        data.query = data.query.replace(regex, "");
12049      }
12050    }
12051  });
12052})(jQuery);
12053
12054// module: column stacking
12055(function ($) {
12056  // Remove all stacking markers and stacked values before reapplying
12057  function reset_column_stacking($container) {
12058    $container.find(".wcpt-cell-val--stacked").remove();
12059    $container
12060      .find(".wcpt-cell--stacking,.wcpt-stack--hide")
12061      .removeClass("wcpt-cell--stacking wcpt-stack--hide");
12062  }
12063
12064  function get_stacking_params($container) {
12065    var device = wcpt_util.get_device();
12066    var last_device = $container.data("wcpt-stacking-last-device");
12067
12068    if (last_device === device) {
12069      return false;
12070    }
12071    $container.data("wcpt-stacking-last-device", device);
12072
12073    var sc_attrs = wcpt_util.get_sc_attrs($container);
12074    var stacking_params = sc_attrs[device + "_stack_columns"];
12075    return stacking_params
12076      ? stacking_params.split("|").map(function (name) {
12077          return name.trim();
12078        })
12079      : [];
12080  }
12081
12082  function stack_columns($container, stacking_params) {
12083    reset_column_stacking($container);
12084    if (!stacking_params.length) return;
12085
12086    // Get the name of the first column, so we can skip hiding its heading
12087    var $headingRow = $container.find(".wcpt-heading-row");
12088    var $headingThs = $headingRow.children("th.wcpt-heading");
12089    var firstColName = (
12090      $headingThs.eq(0).attr("data-wcpt-column-name") || ""
12091    ).trim();
12092
12093    // Hide the heading if it's a stacking column, except for the first column
12094    $headingThs.each(function (i) {
12095      var $th = $(this);
12096      var colName = ($th.attr("data-wcpt-column-name") || "").trim();
12097      // Do not hide heading if it's the first column, even if stacking includes it
12098      if (colName === firstColName) {
12099        return; // skip hiding the first heading
12100      }
12101      if (stacking_params.indexOf(colName) !== -1) {
12102        $th.addClass("wcpt-heading--stacking wcpt-stack--hide");
12103      }
12104    });
12105
12106    // For each row, merge stacked columns into previous column
12107    $container.find(".wcpt-row").each(function () {
12108      var $row = $(this);
12109      var $cells = $row.children("td.wcpt-cell");
12110      // If stacking on the first column, just skip: don't stack or hide it
12111      for (var idx = $cells.length - 1; idx > 0; idx--) {
12112        var $cell = $($cells[idx]);
12113        var $prevCell = $($cells[idx - 1]);
12114        var colName = ($cell.attr("data-wcpt-column-name") || "").trim();
12115
12116        // Do not apply stacking if this is the first column (idx==0 is never reached by this loop)
12117        if (colName === firstColName) {
12118          continue; // skip stacking and hiding for first column
12119        }
12120
12121        if (stacking_params.indexOf(colName) !== -1) {
12122          $cell.addClass("wcpt-cell--stacking wcpt-stack--hide");
12123          // Clone contents to move
12124          var $val = $cell.children(".wcpt-cell-val").first();
12125          if ($val.length && $prevCell.length) {
12126            // Create new stacked container and append the content (prevent duplicate IDs, avoid events)
12127            var $stackedDiv = $(
12128              "<div class='wcpt-cell-val wcpt-cell-val--stacked'></div>",
12129            );
12130            $stackedDiv.append($val.contents().clone(true, true));
12131            // Append to previous cell's .wcpt-cell-val
12132            var $prevVal = $prevCell.children(".wcpt-cell-val").first();
12133            if ($prevVal.length) {
12134              $prevVal.append($stackedDiv);
12135            }
12136          }
12137        }
12138      }
12139    });
12140  }
12141
12142  // On layout called, apply stacking
12143  $(document).on("wcpt_layout", ".wcpt", function (e, data) {
12144    var $wcpt = $(this);
12145    var stacking_params = get_stacking_params($wcpt);
12146    if (!stacking_params) return;
12147    stack_columns($wcpt, stacking_params);
12148  });
12149})(jQuery);
12150
12151//module: list layout
12152(function ($) {
12153  $(document).on("wcpt_layout", ".wcpt", function () {
12154    var $container = $(this);
12155
12156    if (!$container.hasClass("wcpt-list-view")) {
12157      return;
12158    }
12159
12160    var table_id = wcpt_util.get_table_id($container),
12161      $table = $container.find(".wcpt-table"),
12162      $first_row = $table.find(
12163        "tbody tr:not(.wcpt-row--category-heading):first",
12164      ),
12165      css = "";
12166
12167    if (!$table.length) {
12168      return;
12169    }
12170
12171    // remove existing css first
12172    $container.find("style.wcpt-list-view-column-widths").remove();
12173
12174    // revert list to tabel mode to get dimensions
12175    $container.addClass("wcpt-list-view--calc");
12176
12177    // get widths and vertical/horizontal alignment from first row cells
12178    var total_width = $first_row.outerWidth();
12179    $first_row.find("td").each(function (index) {
12180      var width = ($(this).outerWidth() / total_width) * 100;
12181      var verticalAlign = $(this).css("vertical-align") || "middle";
12182      var textAlign = $(this).css("text-align");
12183
12184      var alignCSS = "";
12185      // vertical alignment
12186      if (verticalAlign === "top") {
12187        alignCSS = "justify-content: flex-start;";
12188      } else if (verticalAlign === "bottom") {
12189        alignCSS = "justify-content: flex-end;";
12190      } else {
12191        alignCSS = "justify-content: center;";
12192      }
12193
12194      // // horizontal alignment
12195      // if (textAlign === "left") {
12196      //   alignCSS += " justify-content: flex-start;";
12197      // } else if (textAlign === "right") {
12198      //   alignCSS += " justify-content: flex-end;";
12199      // } else if (textAlign === "center") {
12200      //   alignCSS += " justify-content: center;";
12201      // }
12202
12203      css +=
12204        "#wcpt-" +
12205        table_id +
12206        ".wcpt-list-view :is(.wcpt-row, .wcpt-heading-row):not(.wcpt-row--category-heading) :is(.wcpt-cell, .wcpt-heading):nth-child(" +
12207        (index + 1) +
12208        ") { width: " +
12209        width.toFixed(2) +
12210        "% !important; " +
12211        alignCSS +
12212        " }\n";
12213    });
12214
12215    // get border spacing between rows
12216    var border_spacing = parseInt($table.css("border-spacing").split(" ")[1]);
12217    css +=
12218      "#wcpt-" +
12219      table_id +
12220      ".wcpt-list-view :is(.wcpt-row:not(:last-child), .wcpt-heading-row) { margin-bottom: " +
12221      border_spacing +
12222      "px; }\n";
12223
12224    // add new css
12225    var $style = $('<style class="wcpt-list-view-column-widths"></style>');
12226    $container.append($style);
12227    $style.html(css);
12228
12229    $container.removeClass("wcpt-list-view--calc");
12230  });
12231})(jQuery);
12232
12233// module: input height & width match with add to cart button
12234(function ($) {
12235  // range submit button height match
12236  $("body").on("wcpt_after_every_load", ".wcpt", function () {
12237    const $container = $(this),
12238      $range_submit_button = $container.find(
12239        ".wcpt-range-submit-button, .wcpt-date-picker-submit-button",
12240      );
12241
12242    $range_submit_button.each(function () {
12243      const $button = $(this);
12244      const height = $button.siblings(".wcpt-range-input-min").outerHeight();
12245      $button.outerHeight(height);
12246    });
12247  });
12248
12249  // quantity element size match with cart button
12250  $("body").on("wcpt_layout", ".wcpt", function () {
12251    const $container = $(this),
12252      $row = $container.find(".wcpt-row");
12253
12254    $row.each(function () {
12255      const $row = $(this),
12256        $quantity_elements = $row.find(".wcpt-quantity"),
12257        $cart_button_elements = $row.find(
12258          ".wcpt-button[data-wcpt-link-code*='cart']",
12259        );
12260
12261      if ($quantity_elements.length && $cart_button_elements.length) {
12262        $quantity_elements.each(function () {
12263          const $quantity = $(this);
12264          if ($quantity.hasClass("wcpt-cart-button-height-match")) {
12265            const height = $cart_button_elements.outerHeight();
12266            $quantity.outerHeight(height);
12267            $quantity.find("select").outerHeight(height);
12268          }
12269          if ($quantity.hasClass("wcpt-cart-button-width-match")) {
12270            const width = $cart_button_elements.outerWidth();
12271            $quantity
12272              .outerWidth(width)
12273              .css("box-sizing", "border-box !important");
12274            $quantity.find("select").outerWidth(width);
12275          }
12276        });
12277      }
12278    });
12279  });
12280})(jQuery);
12281
12282// module: search option above filter menu
12283(function ($) {
12284  $("body").on("wcpt_layout", ".wcpt", function () {
12285    var $this = $(this),
12286      $nav_dropdown = $(".wcpt-dropdown.wcpt-filter", $this);
12287
12288    $nav_dropdown.each(function () {
12289      var $this = $(this),
12290        $menu = $(".wcpt-dropdown-menu", $this);
12291
12292      if (
12293        $this.hasClass("wcpt-filter--search-filter-options-enabled") &&
12294        !$(".wcpt-search-filter-options", $this).length
12295      ) {
12296        var placeholder = $this.attr(
12297          "data-wcpt-search-filter-options-placeholder",
12298        );
12299
12300        $menu.prepend(
12301          '<input type="search" class="wcpt-search-filter-options" placeholder="' +
12302            placeholder +
12303            '" />',
12304        );
12305
12306        $menu.css("max-height", "none");
12307
12308        $(".wcpt-search-filter-options", $menu)
12309          .nextAll()
12310          .wrapAll(
12311            $(
12312              '<div class="wcpt-search-filter-option-set" style="max-height: ' +
12313                $menu.css("max-height") +
12314                ';"></div>',
12315            ),
12316          );
12317      }
12318
12319      // Always set max-height for option set
12320      var $optionSet = $(".wcpt-search-filter-option-set", $menu);
12321      if ($optionSet.length) {
12322        $menu.css("max-height", "");
12323        $optionSet.css("max-height", $menu.css("max-height"));
12324        $menu.css("max-height", "none");
12325      }
12326    });
12327  });
12328})(jQuery);
12329
12330// module: auto select products - add to cart checkbox
12331(function ($) {
12332  $("body").on("wcpt_after_every_load", ".wcpt", function () {
12333    const $container = $(this),
12334      sc_attrs = wcpt_util.get_sc_attrs($container);
12335
12336    if (!sc_attrs.auto_select_products) {
12337      return;
12338    }
12339
12340    const $checkbox = $container.find(".wcpt-cart-checkbox");
12341
12342    $checkbox.prop("checked", true).trigger("change");
12343  });
12344})(jQuery);
12345
12346// module: pagination
12347(function ($) {
12348  // infinite scroll remove pagination archive slug & table param
12349  $("body").on("wcpt_update_url", function (e, data) {
12350    var $container = data.$container,
12351      table_id = wcpt_util.get_table_id($container),
12352      sc_attrs = wcpt_util.get_sc_attrs($container),
12353      page_param_key = table_id + "_paged",
12354      pagination_slug = wcpt_params.pagination_slug;
12355
12356    if (!wcpt_util.infinite_scroll_enabled($container)) {
12357      return;
12358    }
12359
12360    // Get current URL
12361    var current_url = new URL(data.url);
12362    var pathname = current_url.pathname;
12363    var search = current_url.search;
12364
12365    // Remove any pagination slug from URL
12366    if (sc_attrs && sc_attrs._archive) {
12367      // Remove WP core pagination slug from pathname
12368      pathname = pathname.replace(
12369        new RegExp("/" + pagination_slug + "/\\d+/?"),
12370        "/",
12371      );
12372
12373      // Remove WP core pagination param from search
12374      search = search.replace(
12375        new RegExp("[?&]" + pagination_slug + "=\\d+"),
12376        "",
12377      );
12378    }
12379
12380    // Remove custom pagination from search
12381    search = search.replace(new RegExp("[?&]" + page_param_key + "=\\d+"), "");
12382
12383    data.url = pathname + search;
12384  });
12385
12386  // reset pagination when ajax is disabled
12387  $("body").on("wcpt_before_nav_redirect", function (e, data) {
12388    if (data.purpose !== "paginate") {
12389      // If not paginating, need to reset to page 1
12390      var $container = data.$container,
12391        table_id = wcpt_util.get_table_id($container),
12392        sc_attrs = wcpt_util.get_sc_attrs($container),
12393        page_param_key = table_id + "_paged",
12394        pagination_slug = wcpt_params.pagination_slug;
12395
12396      // Remove any pagination slug from base_url
12397      if (sc_attrs._archive) {
12398        // Remove WP core pagination slug
12399        data.base_url = data.base_url.replace(
12400          new RegExp("/" + pagination_slug + "/\\d+/?"),
12401          "/",
12402        );
12403        // Remove WP core pagination param
12404        data.query = data.query.replace(
12405          new RegExp("[?&]" + pagination_slug + "=\\d+"),
12406          "",
12407        );
12408      }
12409
12410      // Remove custom pagination
12411      data.query = data.query.replace(
12412        new RegExp("[?&]" + page_param_key + "=\\d+"),
12413        "",
12414      );
12415    }
12416  });
12417
12418  // apply pagination - ajax disabled / enabled / archive
12419  $("body").on(
12420    "click",
12421    ".wcpt-pagination .page-numbers:not(.dots):not(.current)",
12422    function (e) {
12423      e.preventDefault();
12424      var $this = $(this),
12425        $container = $this.closest(".wcpt"),
12426        table_id = wcpt_util.get_table_id($container),
12427        page_param_key = table_id + "_paged",
12428        sc_attrs = wcpt_util.get_sc_attrs($container),
12429        url = $this.attr("href"),
12430        pagination_slug = wcpt_params.pagination_slug, // 'page' or 'paged'
12431        page = 1, // Default to page 1
12432        query = "",
12433        append = true;
12434
12435      if (sc_attrs._archive) {
12436        if (sc_attrs.disable_ajax) {
12437          window.location.href = url;
12438          return;
12439        }
12440
12441        // Archive page pagination (uses WP core pagination) unless table delivered over AJAX
12442        if (wcpt_params.permalink_structure) {
12443          // Pretty permalinks: /shop/page/2/
12444          var regex = new RegExp("/" + pagination_slug + "/(\\d+)");
12445          var match = url.match(regex);
12446          if (match && match[1]) {
12447            page = parseInt(match[1], 10);
12448          }
12449        } else {
12450          // Plain permalinks: /?paged=2
12451          var index = url.indexOf("?");
12452          var params =
12453            index === -1
12454              ? false
12455              : wcpt_util.parse_query_string(url.slice(index + 1));
12456          if (params && params[pagination_slug]) {
12457            page = parseInt(params[pagination_slug], 10);
12458          }
12459        }
12460      }
12461
12462      if (!sc_attrs._archive || page == 1) {
12463        // Standard table pagination - uses custom query var like /?123_paged=2
12464        var index = url.indexOf("?");
12465        var params =
12466          index === -1
12467            ? false
12468            : wcpt_util.parse_query_string(url.slice(index + 1));
12469
12470        if (params && params[page_param_key]) {
12471          page = parseInt(params[page_param_key], 10);
12472        }
12473      }
12474      query = page_param_key + "=" + page;
12475
12476      window.wcpt_attempt_ajax($container, query, append, "paginate");
12477    },
12478  );
12479})(jQuery);
12480
12481// module: persistent add-to-cart checkbox state across pages
12482(function ($) {
12483  // ---------------------------------------------------------------------------
12484  // state
12485  // ---------------------------------------------------------------------------
12486
12487  var memoryState = {};
12488  var pendingRestoreState = {};
12489  var suppressVariationPersistMutations = false;
12490  var isRestoring = false;
12491
12492  function getState() {
12493    return memoryState;
12494  }
12495
12496  function publishState() {
12497    window.wcpt_cart_checked_state = memoryState;
12498  }
12499
12500  // ---------------------------------------------------------------------------
12501  // helpers
12502  // ---------------------------------------------------------------------------
12503
12504  var QTY_SELECTOR = "input.qty, select.wcpt-qty-select";
12505
12506  function getPersistedQtyInput($rowElements) {
12507    var $wcptQty = $rowElements
12508      .find(".wcpt-quantity input.qty, .wcpt-quantity select.wcpt-qty-select")
12509      .first();
12510
12511    if ($wcptQty.length) {
12512      return $wcptQty;
12513    }
12514
12515    var $formQty = $rowElements.find(".variations_form input.qty").first();
12516    if ($formQty.length) {
12517      return $formQty;
12518    }
12519
12520    return $rowElements.find(QTY_SELECTOR).first();
12521  }
12522
12523  function rowKey($row) {
12524    return typeof window.wcpt_get_row_key === "function"
12525      ? window.wcpt_get_row_key($row) || ""
12526      : "";
12527  }
12528
12529  function getRowElements($row) {
12530    if (typeof window.wcpt_get_sibling_rows === "function") {
12531      var $siblings = window.wcpt_get_sibling_rows($row);
12532      if ($siblings && $siblings.length) {
12533        return $siblings;
12534      }
12535    }
12536    return $row;
12537  }
12538
12539  function getPrimaryRow($row) {
12540    if (typeof window.wcpt_get_original_row === "function") {
12541      var $original = window.wcpt_get_original_row($row);
12542      if ($original && $original.length) {
12543        return $original;
12544      }
12545    }
12546    if ($row.closest("table").hasClass("frzTbl-clone-table")) {
12547      return $();
12548    }
12549    return $row;
12550  }
12551
12552  function resolvePrimaryRow($row) {
12553    var $primary = getPrimaryRow($row);
12554    return $primary.length ? $primary : $row;
12555  }
12556
12557  function isVariableRow($row) {
12558    return (
12559      $row.hasClass("wcpt-product-type-variable") ||
12560      $row.attr("data-wcpt-type") == "variable"
12561    );
12562  }
12563
12564  function getTableContext($row) {
12565    var $primaryRow = resolvePrimaryRow($row),
12566      $container = $primaryRow.closest(".wcpt");
12567
12568    if (!$container.length || typeof wcpt_util === "undefined") {
12569      return null;
12570    }
12571
12572    var table_id = wcpt_util.get_table_id($container);
12573    if (!table_id) {
12574      return null;
12575    }
12576
12577    return { $row: $primaryRow, $container: $container, table_id: table_id };
12578  }
12579
12580  function variationIdsMatch(a, b) {
12581    return a && b && String(a) === String(b);
12582  }
12583
12584  function releaseRestoreSuppress() {
12585    queueMicrotask(function () {
12586      suppressVariationPersistMutations = false;
12587    });
12588  }
12589
12590  function getVariationRowTarget($row) {
12591    var $target = isVariableRow($row)
12592      ? $row
12593      : $row.find(".wcpt-product-type-variable").first();
12594
12595    return $target.length ? $target : $row;
12596  }
12597
12598  function findVariationInForm($form, variationId) {
12599    var variations = parseJsonAttr($form, "data-product_variations"),
12600      found = null;
12601
12602    $.each(variations || [], function (index, value) {
12603      if (variationIdsMatch(value.variation_id, variationId)) {
12604        found = value;
12605        return false;
12606      }
12607    });
12608
12609    return found;
12610  }
12611
12612  function readFormAttributes($form) {
12613    var attributes = {};
12614
12615    $form.find(".variations select").each(function () {
12616      var $select = $(this),
12617        name = $select.attr("name");
12618
12619      if (name) {
12620        attributes[name] = $select.val();
12621      }
12622    });
12623
12624    return attributes;
12625  }
12626
12627  function isVariationPurchasable(variation) {
12628    return !!(
12629      variation &&
12630      variation.is_purchasable &&
12631      variation.is_in_stock &&
12632      variation.variation_is_visible
12633    );
12634  }
12635
12636  function eachVariationAttributeSelect($rows, fn) {
12637    $rows.find(".wcpt-select-variation-attribute-term").each(function () {
12638      var $select = $(this),
12639        name = $select.attr("data-wcpt-attribute");
12640
12641      if (name) {
12642        fn($select, name);
12643      }
12644    });
12645
12646    $rows.find(".variations_form .variations select").each(function () {
12647      var $select = $(this),
12648        name = $select.attr("name");
12649
12650      if (name) {
12651        fn($select, name);
12652      }
12653    });
12654  }
12655
12656  function buildSelectVariationMeta(meta, $form, variationId) {
12657    var variation = findVariationInForm($form, variationId);
12658
12659    if (variation) {
12660      meta.variation = variation;
12661    }
12662
12663    meta.attributes = readFormAttributes($form);
12664    meta.variation_id = String(variationId);
12665    meta.variation_found = true;
12666    meta.variation_selected = true;
12667    meta.variation_available = isVariationPurchasable(meta.variation);
12668
12669    return meta;
12670  }
12671
12672  function triggerSelectVariation($target, meta) {
12673    $target.trigger("select_variation", meta);
12674  }
12675
12676  function whenCartFormVariationReady(
12677    $primaryRow,
12678    $form,
12679    variationId,
12680    onReady,
12681  ) {
12682    var finished = false;
12683
12684    function finish() {
12685      if (finished) {
12686        return;
12687      }
12688      finished = true;
12689      $primaryRow.off(".wcptPersistRestore");
12690      $form.off(".wcptPersistRestore");
12691      onReady();
12692    }
12693
12694    function isResolved() {
12695      return variationIdsMatch(
12696        $form.find("input.variation_id").val(),
12697        variationId,
12698      );
12699    }
12700
12701    $primaryRow.on("select_variation.wcptPersistRestore", function (e, data) {
12702      if (variationIdsMatch(data && data.variation_id, variationId)) {
12703        finish();
12704      }
12705    });
12706
12707    $form.on(
12708      "woocommerce_variation_has_changed.wcptPersistRestore",
12709      function () {
12710        if (isResolved()) {
12711          finish();
12712        }
12713      },
12714    );
12715
12716    $form.trigger("check_variations");
12717
12718    if (isResolved()) {
12719      finish();
12720    }
12721  }
12722
12723  function parseJsonAttr($el, attr) {
12724    var raw = $el.attr(attr);
12725    if (!raw) {
12726      return null;
12727    }
12728    try {
12729      return JSON.parse(raw);
12730    } catch (e) {
12731      return null;
12732    }
12733  }
12734
12735  function getRowVariationAttributes($row) {
12736    if ($row.hasClass("wcpt-product-type-variation")) {
12737      return parseJsonAttr($row, "data-wcpt-variation-attributes");
12738    }
12739
12740    var attributes = $row.data("wcpt_attributes");
12741    attributes =
12742      attributes && !$.isEmptyObject(attributes)
12743        ? $.extend({}, attributes)
12744        : {};
12745
12746    eachVariationAttributeSelect($row, function ($select, name) {
12747      var val = $select.val();
12748      if (val) {
12749        attributes[name] = val;
12750      }
12751    });
12752
12753    return $.isEmptyObject(attributes) ? null : attributes;
12754  }
12755
12756  function findPersistedEntry(
12757    tableState,
12758    productId,
12759    variationId,
12760    variableProduct,
12761  ) {
12762    if (!tableState || !productId) {
12763      return null;
12764    }
12765
12766    if (variationId) {
12767      var variationKey = "v:" + String(variationId);
12768      if (tableState[variationKey]) {
12769        return { key: variationKey, entry: tableState[variationKey] };
12770      }
12771    }
12772
12773    var foundKey = null;
12774    var foundEntry = null;
12775
12776    $.each(tableState, function (stateKey, entry) {
12777      if (
12778        foundEntry ||
12779        !entry ||
12780        String(entry.product_id) !== String(productId)
12781      ) {
12782        return;
12783      }
12784
12785      if (variationId) {
12786        if (variationIdsMatch(entry.variation_id, variationId)) {
12787          foundKey = stateKey;
12788          foundEntry = entry;
12789        }
12790      } else if (variableProduct) {
12791        if (entry.variation_id) {
12792          foundKey = stateKey;
12793          foundEntry = entry;
12794        }
12795      } else if (!entry.variation_id) {
12796        foundKey = stateKey;
12797        foundEntry = entry;
12798      }
12799    });
12800
12801    return foundEntry ? { key: foundKey, entry: foundEntry } : null;
12802  }
12803
12804  function resolveRestoreKey(match, productId) {
12805    if (match.key) {
12806      return match.key;
12807    }
12808    if (match.entry.variation_id) {
12809      return "v:" + String(match.entry.variation_id);
12810    }
12811    if (productId) {
12812      return "p:" + String(productId);
12813    }
12814    return "";
12815  }
12816
12817  function findRestoreMatch(tableState, $row) {
12818    var productId = $row.attr("data-wcpt-product-id"),
12819      variationId =
12820        $row.attr("data-wcpt-variation-id") || $row.data("wcpt_variation_id");
12821
12822    if (isVariableRow($row) && productId) {
12823      return findPersistedEntry(tableState, productId, false, true);
12824    }
12825
12826    var match = findPersistedEntry(tableState, productId, variationId);
12827    if (!match) {
12828      var key = rowKey($row);
12829      if (key && tableState[key]) {
12830        match = { key: key, entry: tableState[key] };
12831      }
12832    }
12833    return match;
12834  }
12835
12836  // ---------------------------------------------------------------------------
12837  // entry build / apply
12838  // ---------------------------------------------------------------------------
12839
12840  function buildPersistedEntry($row) {
12841    var $rowElements = getRowElements($row),
12842      $qty = getPersistedQtyInput($rowElements),
12843      qtyVal = $qty.length ? parseFloat($qty.val()) : 1,
12844      unitPrice = parseFloat($row.attr("data-wcpt-price"));
12845
12846    if (isNaN(qtyVal) || qtyVal <= 0) {
12847      qtyVal = 1;
12848    }
12849
12850    if (isVariableRow($row)) {
12851      var variationData = $row.data("wcpt_variation");
12852      if (variationData && !isNaN(parseFloat(variationData.display_price))) {
12853        unitPrice = parseFloat(variationData.display_price);
12854      }
12855    }
12856
12857    if (isNaN(unitPrice)) {
12858      unitPrice = 0;
12859    }
12860
12861    return {
12862      product_id: $row.attr("data-wcpt-product-id"),
12863      variation_id: String(
12864        $row.attr("data-wcpt-variation-id") ||
12865          $row.data("wcpt_variation_id") ||
12866          "",
12867      ),
12868      attributes: getRowVariationAttributes($row),
12869      qty: qtyVal,
12870      total: (unitPrice * qtyVal * 1e12) / 1e12,
12871    };
12872  }
12873
12874  function applyPersistedQty($rowElements, qtyVal) {
12875    if (isNaN(qtyVal) || qtyVal <= 0) {
12876      return;
12877    }
12878
12879    var $targets = $rowElements
12880      .find(".wcpt-quantity input.qty, .wcpt-quantity select.wcpt-qty-select")
12881      .add($rowElements.find(".variations_form input.qty"));
12882
12883    if (!$targets.length) {
12884      $targets = $rowElements.find(QTY_SELECTOR);
12885    }
12886
12887    $targets.each(function () {
12888      var $qtyInput = $(this);
12889      $qtyInput.val(qtyVal);
12890      if (!$qtyInput.closest("form.cart").length) {
12891        $qtyInput.trigger("change");
12892      }
12893    });
12894  }
12895
12896  function applyPersistedQtyWhenReady($rowElements, qtyVal, onDone) {
12897    var finished = false;
12898
12899    function complete() {
12900      if (finished) {
12901        return;
12902      }
12903      finished = true;
12904      if (onDone) {
12905        onDone();
12906      }
12907    }
12908
12909    function setQty() {
12910      applyPersistedQty($rowElements, qtyVal);
12911    }
12912
12913    if (isNaN(qtyVal) || qtyVal <= 0) {
12914      complete();
12915      return;
12916    }
12917
12918    var $form = $rowElements.find(".variations_form").first();
12919    if (!$form.length) {
12920      setQty();
12921      complete();
12922      return;
12923    }
12924
12925    // WC cart form re-renders qty on show_variation — apply after that settles
12926    $form.one(
12927      "show_variation.wcptPersistQty found_variation.wcptPersistQty",
12928      function () {
12929        setQty();
12930        complete();
12931      },
12932    );
12933
12934    queueMicrotask(setQty);
12935
12936    requestAnimationFrame(function () {
12937      setQty();
12938      complete();
12939    });
12940  }
12941
12942  function applyPersistedCheckboxAndQty($row, entry) {
12943    if (!$row || !entry) {
12944      return;
12945    }
12946
12947    var $primaryRow = resolvePrimaryRow($row),
12948      $rowElements = getRowElements($primaryRow),
12949      qtyVal = parseFloat(entry.qty);
12950
12951    function checkAndApply() {
12952      var $enabledCb = $rowElements
12953        .find(".wcpt-cart-checkbox")
12954        .not(":disabled")
12955        .first();
12956
12957      if (!$enabledCb.length) {
12958        return false;
12959      }
12960
12961      isRestoring = true;
12962      $primaryRow.trigger("_wcpt_checkbox_change", true);
12963
12964      if (!isNaN(qtyVal) && qtyVal > 0) {
12965        applyPersistedQtyWhenReady($rowElements, qtyVal, function () {
12966          isRestoring = false;
12967        });
12968      } else {
12969        isRestoring = false;
12970      }
12971
12972      return true;
12973    }
12974
12975    if (checkAndApply()) {
12976      return;
12977    }
12978
12979    $primaryRow.one("select_variation.wcptPersistCheckbox", function (e, data) {
12980      if (data && data.variation_selected && data.variation_id) {
12981        checkAndApply();
12982      }
12983    });
12984  }
12985
12986  // ---------------------------------------------------------------------------
12987  // state mutations
12988  // ---------------------------------------------------------------------------
12989
12990  function syncContainerMirror($container, table_id) {
12991    $container.data(
12992      "wcpt_all_checked",
12993      memoryState[table_id] ? memoryState[table_id] : {},
12994    );
12995  }
12996
12997  function refreshFloatingTrigger() {
12998    if (typeof window.wcpt_checkbox_trigger_init === "function") {
12999      window.wcpt_checkbox_trigger_init();
13000    }
13001  }
13002
13003  function commitTableState(table_id, $container) {
13004    if (
13005      memoryState[table_id] &&
13006      Object.keys(memoryState[table_id]).length === 0
13007    ) {
13008      delete memoryState[table_id];
13009    }
13010    if ($container && $container.length) {
13011      syncContainerMirror($container, table_id);
13012    }
13013    publishState();
13014    refreshFloatingTrigger();
13015  }
13016
13017  function flushVisibleRows($container) {
13018    if (!$container || !$container.length || typeof wcpt_util === "undefined") {
13019      return;
13020    }
13021
13022    var flushedVariableProducts = {};
13023
13024    $container.find(".wcpt-row").each(function () {
13025      var $primaryRow = getPrimaryRow($(this));
13026
13027      if (!$primaryRow.length || !$primaryRow.data("wcpt_checked")) {
13028        return;
13029      }
13030
13031      if (
13032        isVariableRow($primaryRow) &&
13033        !$primaryRow.data("wcpt_variation_id")
13034      ) {
13035        return;
13036      }
13037
13038      var productId = $primaryRow.attr("data-wcpt-product-id");
13039      if (productId && isVariableRow($primaryRow)) {
13040        if (flushedVariableProducts[productId]) {
13041          return;
13042        }
13043        flushedVariableProducts[productId] = true;
13044      }
13045
13046      saveRow($primaryRow);
13047    });
13048  }
13049
13050  function pruneUncheckedVisibleEntries($container) {
13051    if (!$container || !$container.length || typeof wcpt_util === "undefined") {
13052      return;
13053    }
13054
13055    var table_id = wcpt_util.get_table_id($container);
13056    if (!table_id || !memoryState[table_id]) {
13057      return;
13058    }
13059
13060    var visibleProductIds = {},
13061      checkedProductIds = {};
13062
13063    $container.find(".wcpt-row").each(function () {
13064      var $primaryRow = getPrimaryRow($(this));
13065      if (!$primaryRow.length) {
13066        return;
13067      }
13068
13069      var productId = $primaryRow.attr("data-wcpt-product-id");
13070      if (!productId) {
13071        return;
13072      }
13073
13074      visibleProductIds[String(productId)] = true;
13075
13076      if ($primaryRow.data("wcpt_checked")) {
13077        checkedProductIds[String(productId)] = true;
13078      }
13079    });
13080
13081    var tableState = memoryState[table_id],
13082      removed = false;
13083
13084    $.each(tableState, function (key, entry) {
13085      if (!entry || !entry.product_id) {
13086        return;
13087      }
13088
13089      var productId = String(entry.product_id);
13090      if (!visibleProductIds[productId] || checkedProductIds[productId]) {
13091        return;
13092      }
13093
13094      delete tableState[key];
13095      removed = true;
13096    });
13097
13098    if (removed) {
13099      commitTableState(table_id, $container);
13100    }
13101  }
13102
13103  function finalizeTablePage($container) {
13104    queueMicrotask(function () {
13105      if ($container && $container.length) {
13106        // pruneUncheckedVisibleEntries($container);
13107
13108        if (typeof wcpt_util !== "undefined") {
13109          var table_id = wcpt_util.get_table_id($container);
13110          if (table_id) {
13111            syncContainerMirror($container, table_id);
13112          }
13113        }
13114      }
13115
13116      releaseRestoreSuppress();
13117      refreshFloatingTrigger();
13118    });
13119  }
13120
13121  function saveRow($row) {
13122    var ctx = getTableContext($row);
13123    if (!ctx) {
13124      return;
13125    }
13126
13127    $row = ctx.$row;
13128
13129    if (!$row.data("wcpt_checked")) {
13130      return;
13131    }
13132
13133    var key = rowKey($row);
13134    if (!key) {
13135      return;
13136    }
13137
13138    if (isVariableRow($row) && !$row.data("wcpt_variation_id")) {
13139      return;
13140    }
13141
13142    if (!memoryState[ctx.table_id]) {
13143      memoryState[ctx.table_id] = {};
13144    }
13145
13146    if (isVariableRow($row)) {
13147      removePersistedEntriesForProduct(
13148        ctx.table_id,
13149        $row.attr("data-wcpt-product-id"),
13150        $row.data("wcpt_variation_id"),
13151        ctx.$container,
13152        true,
13153      );
13154    }
13155
13156    memoryState[ctx.table_id][key] = buildPersistedEntry($row);
13157    commitTableState(ctx.table_id, ctx.$container);
13158  }
13159
13160  function removePersistedEntriesForProduct(
13161    table_id,
13162    productId,
13163    exceptVariationId,
13164    $container,
13165    skipPublish,
13166  ) {
13167    if (!table_id || !productId || !memoryState[table_id]) {
13168      return false;
13169    }
13170
13171    var tableState = memoryState[table_id],
13172      removed = false;
13173
13174    $.each(tableState, function (stateKey, entry) {
13175      if (!entry || String(entry.product_id) !== String(productId)) {
13176        return;
13177      }
13178
13179      if (
13180        exceptVariationId &&
13181        entry.variation_id &&
13182        String(entry.variation_id) === String(exceptVariationId)
13183      ) {
13184        return;
13185      }
13186
13187      delete tableState[stateKey];
13188      removed = true;
13189    });
13190
13191    if (!removed) {
13192      return false;
13193    }
13194
13195    if (!skipPublish) {
13196      commitTableState(table_id, $container);
13197    }
13198    return true;
13199  }
13200
13201  function removeRow($row) {
13202    if (isRestoring) {
13203      return;
13204    }
13205
13206    var ctx = getTableContext($row);
13207    if (!ctx || !memoryState[ctx.table_id]) {
13208      return;
13209    }
13210
13211    var key = rowKey(ctx.$row);
13212    if (!key || !memoryState[ctx.table_id][key]) {
13213      return;
13214    }
13215
13216    delete memoryState[ctx.table_id][key];
13217    commitTableState(ctx.table_id, ctx.$container);
13218  }
13219
13220  function hasPersistedVariableProduct(table_id, productId) {
13221    if (!table_id || !productId) {
13222      return false;
13223    }
13224
13225    var state = pendingRestoreState[table_id] || memoryState[table_id] || null;
13226
13227    return !!findPersistedEntry(state, productId, false, true);
13228  }
13229
13230  function clearTable(table_id, $container) {
13231    if (memoryState[table_id]) {
13232      delete memoryState[table_id];
13233      publishState();
13234    }
13235
13236    if ($container && $container.length) {
13237      $container.data("wcpt_all_checked", {});
13238    }
13239    refreshFloatingTrigger();
13240  }
13241
13242  // ---------------------------------------------------------------------------
13243  // restore
13244  // ---------------------------------------------------------------------------
13245
13246  function getVariationMetaFromEntry($row, entry) {
13247    var $rows = getRowElements($row),
13248      variationId = String(entry.variation_id),
13249      meta = {
13250        variation_id: variationId,
13251        complete_match: true,
13252        attributes: entry.attributes ? $.extend({}, entry.attributes) : {},
13253        variation: false,
13254        variation_found: true,
13255        variation_selected: true,
13256        variation_available: true,
13257      },
13258      $option = $rows
13259        .find(
13260          '.wcpt-select-variation-dropdown option[value="' + variationId + '"]',
13261        )
13262        .first(),
13263      $radioWrap = $option.length
13264        ? $()
13265        : $rows
13266            .find('.wcpt-variation-radio[value="' + variationId + '"]')
13267            .first()
13268            .closest(".wcpt-select-variation"),
13269      $source = $option.length ? $option : $radioWrap,
13270      $form = $rows.find(".variations_form").first();
13271
13272    if ($source.length) {
13273      var attrs = parseJsonAttr($source, "data-wcpt-attributes"),
13274        variation = parseJsonAttr($source, "data-wcpt-variation");
13275
13276      if (attrs) {
13277        meta.attributes = attrs;
13278      }
13279      if (variation) {
13280        meta.variation = variation;
13281      }
13282      meta.complete_match = $source.hasClass("wcpt-complete_match");
13283    }
13284
13285    if ($form.length && ($.isEmptyObject(meta.attributes) || !meta.variation)) {
13286      var variation = findVariationInForm($form, variationId);
13287
13288      if (variation) {
13289        if ($.isEmptyObject(meta.attributes) && variation.attributes) {
13290          meta.attributes = $.extend({}, variation.attributes);
13291        }
13292        if (!meta.variation) {
13293          meta.variation = variation;
13294        }
13295      }
13296    }
13297
13298    return meta;
13299  }
13300
13301  function applyAttributeSelects($row, attributes) {
13302    if (!attributes) {
13303      return;
13304    }
13305
13306    eachVariationAttributeSelect(
13307      getRowElements($row),
13308      function ($select, name) {
13309        if (typeof attributes[name] !== "undefined") {
13310          $select.val(attributes[name]);
13311        }
13312      },
13313    );
13314  }
13315
13316  function rowNeedsPrepWait($row) {
13317    if (!isVariableRow($row)) {
13318      return false;
13319    }
13320
13321    return !!getRowElements($row).find(
13322      ".variations_form, .wcpt-select-variation-dropdown, .wcpt-variation-radio",
13323    ).length;
13324  }
13325
13326  function whenRowPrepComplete($row, callback) {
13327    var $primaryRow = resolvePrimaryRow($row);
13328
13329    if ($primaryRow.data("wcpt_prep_variation_complete")) {
13330      callback();
13331      return;
13332    }
13333
13334    $primaryRow.one("wcpt_row_prep_variation_complete", callback);
13335  }
13336
13337  function selectPersistedVariation($row, entry, callback) {
13338    if (!entry || !entry.variation_id) {
13339      if (callback) {
13340        callback();
13341      }
13342      return;
13343    }
13344
13345    var $primaryRow = resolvePrimaryRow($row),
13346      $rows = getRowElements($primaryRow),
13347      variationId = String(entry.variation_id),
13348      meta = getVariationMetaFromEntry($primaryRow, entry),
13349      $form = $rows.find(".variations_form").first(),
13350      $dropdown = $rows.find(".wcpt-select-variation-dropdown"),
13351      $radio = $rows.find('.wcpt-variation-radio[value="' + variationId + '"]'),
13352      $attrSelects = $rows.find(".wcpt-select-variation-attribute-term"),
13353      $target = getVariationRowTarget($primaryRow);
13354
13355    isRestoring = true;
13356    applyAttributeSelects($primaryRow, meta.attributes);
13357
13358    function finishVariationRestore() {
13359      isRestoring = false;
13360      if (callback) {
13361        callback();
13362      }
13363    }
13364
13365    function triggerControl($control, setup) {
13366      setup();
13367      $control.trigger("change");
13368      finishVariationRestore();
13369    }
13370
13371    if ($dropdown.length) {
13372      triggerControl($dropdown, function () {
13373        $dropdown.val(variationId);
13374      });
13375      return;
13376    }
13377
13378    if ($radio.length) {
13379      triggerControl($radio, function () {
13380        $radio.prop("checked", true);
13381      });
13382      return;
13383    }
13384
13385    if ($form.length) {
13386      whenCartFormVariationReady($primaryRow, $form, variationId, function () {
13387        triggerSelectVariation(
13388          $target,
13389          buildSelectVariationMeta(meta, $form, variationId),
13390        );
13391        finishVariationRestore();
13392      });
13393      return;
13394    }
13395
13396    if ($attrSelects.length) {
13397      $target.one(
13398        "select_variation.wcptPersistRestore",
13399        finishVariationRestore,
13400      );
13401      $attrSelects.trigger("change");
13402      return;
13403    }
13404
13405    triggerSelectVariation($target, meta);
13406    finishVariationRestore();
13407  }
13408
13409  function restorePersistedRow($row, entry, onComplete) {
13410    function runRestore() {
13411      if (isVariableRow($row) && entry.variation_id) {
13412        selectPersistedVariation($row, entry, onComplete);
13413        return;
13414      }
13415
13416      applyPersistedCheckboxAndQty($row, entry);
13417      if (onComplete) {
13418        onComplete();
13419      }
13420    }
13421
13422    if (rowNeedsPrepWait($row)) {
13423      whenRowPrepComplete($row, runRestore);
13424      return;
13425    }
13426
13427    runRestore();
13428  }
13429
13430  function restoreTablePage($container) {
13431    if (typeof wcpt_util === "undefined") {
13432      releaseRestoreSuppress();
13433      return;
13434    }
13435
13436    var table_id = wcpt_util.get_table_id($container);
13437    if (!table_id) {
13438      releaseRestoreSuppress();
13439      return;
13440    }
13441
13442    var tableState =
13443      pendingRestoreState[table_id] ||
13444      (memoryState[table_id]
13445        ? $.extend(true, {}, memoryState[table_id])
13446        : null);
13447
13448    delete pendingRestoreState[table_id];
13449
13450    if (!tableState || $.isEmptyObject(tableState)) {
13451      finalizeTablePage($container);
13452      return;
13453    }
13454
13455    if (!memoryState[table_id]) {
13456      memoryState[table_id] = $.extend(true, {}, tableState);
13457    }
13458
13459    publishState();
13460
13461    var restoredKeys = {},
13462      restoredVariableProducts = {},
13463      pendingRestores = 0;
13464
13465    function finishRestoreCycle() {
13466      pendingRestores--;
13467      if (pendingRestores <= 0) {
13468        finalizeTablePage($container);
13469      }
13470    }
13471
13472    $container.find(".wcpt-row").each(function () {
13473      var $primaryRow = getPrimaryRow($(this));
13474      if (!$primaryRow.length) {
13475        return;
13476      }
13477
13478      var productId = $primaryRow.attr("data-wcpt-product-id"),
13479        match,
13480        restoreKey;
13481
13482      if (isVariableRow($primaryRow) && productId) {
13483        if (restoredVariableProducts[productId]) {
13484          return;
13485        }
13486        match = findPersistedEntry(tableState, productId, false, true);
13487        if (!match) {
13488          return;
13489        }
13490        restoredVariableProducts[productId] = true;
13491        restoreKey = "p:" + String(productId);
13492      } else {
13493        match = findRestoreMatch(tableState, $primaryRow);
13494        if (!match) {
13495          return;
13496        }
13497        restoreKey = resolveRestoreKey(match, productId);
13498      }
13499
13500      if (!restoreKey || restoredKeys[restoreKey]) {
13501        return;
13502      }
13503
13504      restoredKeys[restoreKey] = true;
13505      pendingRestores++;
13506      restorePersistedRow($primaryRow, match.entry, finishRestoreCycle);
13507    });
13508
13509    if (pendingRestores <= 0) {
13510      finalizeTablePage($container);
13511    }
13512  }
13513
13514  function onVariationSelected($row, data) {
13515    var ctx = getTableContext($row);
13516    if (!ctx) {
13517      return;
13518    }
13519
13520    var productId = ctx.$row.attr("data-wcpt-product-id");
13521    if (!productId) {
13522      return;
13523    }
13524
13525    var variationId =
13526        data && data.variation_id ? String(data.variation_id) : "",
13527      isSelected = !!(data && data.variation_selected && variationId);
13528
13529    if (isSelected) {
13530      var match = memoryState[ctx.table_id]
13531        ? findPersistedEntry(memoryState[ctx.table_id], productId, variationId)
13532        : null;
13533
13534      if (match) {
13535        applyPersistedCheckboxAndQty(ctx.$row, match.entry);
13536        return;
13537      }
13538    }
13539
13540    if (isRestoring || suppressVariationPersistMutations) {
13541      return;
13542    }
13543
13544    if (!isSelected) {
13545      removePersistedEntriesForProduct(
13546        ctx.table_id,
13547        productId,
13548        false,
13549        ctx.$container,
13550      );
13551      return;
13552    }
13553
13554    removePersistedEntriesForProduct(
13555      ctx.table_id,
13556      productId,
13557      variationId,
13558      ctx.$container,
13559    );
13560
13561    if (ctx.$row.data("wcpt_checked")) {
13562      saveRow(ctx.$row);
13563    }
13564  }
13565
13566  // ---------------------------------------------------------------------------
13567  // cart payload integration
13568  // ---------------------------------------------------------------------------
13569
13570  function aggregateOffPageIntoPayload(tableContainersById, payload) {
13571    if (typeof wcpt_util === "undefined" || !tableContainersById || !payload) {
13572      return;
13573    }
13574
13575    var products = payload.products || {},
13576      variations = payload.variations || {},
13577      attributes = payload.attributes || {};
13578
13579    try {
13580      $.each(tableContainersById, function (table_id, $container) {
13581        if (!memoryState[table_id]) {
13582          return;
13583        }
13584
13585        $.each(memoryState[table_id], function (key, entry) {
13586          if (!entry || !entry.product_id) {
13587            return;
13588          }
13589
13590          var product_id = String(entry.product_id),
13591            variation_id = entry.variation_id
13592              ? String(entry.variation_id)
13593              : false,
13594            qty = parseFloat(entry.qty);
13595
13596          if (isNaN(qty) || qty <= 0) {
13597            qty = 1;
13598          }
13599
13600          var $candidate_rows = $container.find(
13601            '.wcpt-row[data-wcpt-product-id="' + product_id + '"]',
13602          );
13603
13604          if (variation_id) {
13605            $candidate_rows = $candidate_rows.filter(function () {
13606              var $r = $(this);
13607              return (
13608                $r.attr("data-wcpt-variation-id") === variation_id ||
13609                String($r.data("wcpt_variation_id")) === variation_id
13610              );
13611            });
13612          }
13613
13614          var has_local_checked = $candidate_rows.filter(function () {
13615            return !!$(this).data("wcpt_checked");
13616          }).length;
13617
13618          if (has_local_checked) {
13619            return;
13620          }
13621
13622          if (typeof products[product_id] === "undefined") {
13623            products[product_id] = qty;
13624          } else {
13625            products[product_id] += qty;
13626          }
13627
13628          if (variation_id) {
13629            if (!variations[product_id]) {
13630              variations[product_id] = {};
13631            }
13632            if (!variations[product_id][variation_id]) {
13633              variations[product_id][variation_id] = qty;
13634            } else {
13635              variations[product_id][variation_id] += qty;
13636            }
13637
13638            if (entry.attributes) {
13639              attributes[variation_id] = entry.attributes;
13640            }
13641          }
13642        });
13643      });
13644    } catch (e) {
13645      // ignore aggregation failures
13646    }
13647
13648    payload.products = products;
13649    payload.variations = variations;
13650    payload.attributes = attributes;
13651  }
13652
13653  function clearTables(tableContainersById) {
13654    if (typeof wcpt_util === "undefined" || !tableContainersById) {
13655      return;
13656    }
13657
13658    $.each(tableContainersById, function (table_id, $container) {
13659      if (!table_id || !$container || !$container.length) {
13660        return;
13661      }
13662      clearTable(table_id, $container);
13663    });
13664  }
13665
13666  // ---------------------------------------------------------------------------
13667  // event bindings
13668  // ---------------------------------------------------------------------------
13669
13670  // save on check
13671  $("body").on("wcpt_checkbox_change", ".wcpt-row", function (e, state) {
13672    if (isRestoring || suppressVariationPersistMutations || !state) {
13673      return;
13674    }
13675    saveRow($(this));
13676  });
13677
13678  $("body").on("change", ".wcpt-cart-checkbox", function () {
13679    if (isRestoring || suppressVariationPersistMutations) {
13680      return;
13681    }
13682    var $cb = $(this);
13683    if (!$cb.prop("checked")) {
13684      removeRow($cb.closest(".wcpt-row"));
13685    }
13686  });
13687
13688  // qty -> 0 removes persisted row (not wcpt_checkbox_change false — prep resets fire that too)
13689  $("body").on(
13690    "change",
13691    ".wcpt input.qty, .wcpt select.wcpt-qty-select",
13692    function () {
13693      if (isRestoring || suppressVariationPersistMutations) {
13694        return;
13695      }
13696
13697      var $this = $(this);
13698      if ($this.closest("form.cart").length) {
13699        return;
13700      }
13701
13702      var val = parseFloat($this.val());
13703      if (isNaN(val) || val <= 0) {
13704        var $row = $this.closest(".wcpt-row");
13705        if (typeof window.wcpt_get_original_row === "function") {
13706          $row = window.wcpt_get_original_row($row);
13707        }
13708        removeRow($row);
13709        return;
13710      }
13711
13712      var $row = $this.closest(".wcpt-row");
13713      if (typeof window.wcpt_get_original_row === "function") {
13714        $row = window.wcpt_get_original_row($row);
13715      }
13716
13717      if (isVariableRow($row) && !$row.data("wcpt_variation_id")) {
13718        return;
13719      }
13720
13721      if ($row.data("wcpt_checked")) {
13722        saveRow($row);
13723      }
13724    },
13725  );
13726
13727  // flush checked rows before DOM is replaced (fast pagination)
13728  $("body").on("wcpt_before_ajax_container_replace", function (e, data) {
13729    if (!data || !data.$container) {
13730      return;
13731    }
13732    flushVisibleRows(data.$container);
13733  });
13734
13735  // snapshot before prep_variation_options can clear persisted rows via qty reset
13736  $("body").on("wcpt_before_every_load", ".wcpt", function () {
13737    if (typeof wcpt_util === "undefined") {
13738      return;
13739    }
13740
13741    var table_id = wcpt_util.get_table_id($(this));
13742    if (table_id && memoryState[table_id]) {
13743      pendingRestoreState[table_id] = $.extend(true, {}, memoryState[table_id]);
13744    }
13745
13746    suppressVariationPersistMutations = true;
13747  });
13748
13749  $("body").on(
13750    "select_variation",
13751    ".wcpt-row.wcpt-product-type-variable",
13752    function (e, data) {
13753      var $row = $(this);
13754      queueMicrotask(function () {
13755        onVariationSelected($row, data);
13756      });
13757    },
13758  );
13759
13760  // run after freeze-table layout so sibling rows exist for restore
13761  $("body").on("wcpt_layout", ".wcpt", function (e, data) {
13762    if (!data || data.source !== "after_every_load") {
13763      return;
13764    }
13765
13766    restoreTablePage($(this));
13767  });
13768
13769  $(document).on(
13770    "wcpt_clear_all_checked_for_table",
13771    function (e, table_id, $container) {
13772      clearTable(table_id, $container);
13773    },
13774  );
13775
13776  // ---------------------------------------------------------------------------
13777  // public API
13778  // ---------------------------------------------------------------------------
13779
13780  window.wcpt_cart_checkbox_persist = {
13781    getState: getState,
13782    hasPersistedVariableProduct: hasPersistedVariableProduct,
13783    aggregateOffPageIntoPayload: aggregateOffPageIntoPayload,
13784    clearTables: clearTables,
13785  };
13786
13787  publishState();
13788})(jQuery);
13789
13790// module: product compare
13791(function ($) {
13792  // ---- Compare State "API" ----
13793
13794  // Separate cookie for WCPT native compare logic
13795  var WCPT_COMPARE_COOKIE = "wcpt_compare_products";
13796
13797  // Get products in compare (array of strings)
13798  function getCompareIDs() {
13799    var val = getCookie(WCPT_COMPARE_COOKIE);
13800    if (!val || val === "null") return [];
13801    return val
13802      .split(",")
13803      .map(function (v) {
13804        return v.trim();
13805      })
13806      .filter(function (v) {
13807        return !!v;
13808      });
13809  }
13810
13811  // Set products in compare (array of strings)
13812  function setCompareIDs(ids) {
13813    setCookie(WCPT_COMPARE_COOKIE, ids.join(","), 30); // 30 day expiry for native compare
13814    $(document).trigger("wcpt_compare_changed", [ids]); // Broadcast state change
13815  }
13816
13817  // Add/remove by id (API helpers)
13818  function addToCompare(id) {
13819    var ids = getCompareIDs();
13820    id = String(id);
13821    if (ids.indexOf(id) === -1) {
13822      ids.push(id);
13823      setCompareIDs(ids);
13824    }
13825  }
13826  function removeFromCompare(id) {
13827    var ids = getCompareIDs();
13828    id = String(id);
13829    var idx = ids.indexOf(id);
13830    if (idx !== -1) {
13831      ids.splice(idx, 1);
13832      setCompareIDs(ids);
13833    }
13834  }
13835  function isInCompare(id) {
13836    var ids = getCompareIDs();
13837    return ids.indexOf(String(id)) !== -1;
13838  }
13839  function clearCompare() {
13840    setCompareIDs([]);
13841  }
13842
13843  // Expose public API
13844  window.wcptCompare = {
13845    getIDs: getCompareIDs,
13846    setIDs: setCompareIDs,
13847    add: addToCompare,
13848    remove: removeFromCompare,
13849    isIn: isInCompare,
13850    clear: clearCompare,
13851  };
13852
13853  // --- Cookie utils
13854  function getCookie(name) {
13855    var nameEQ = name + "=";
13856    var ca = document.cookie.split(";");
13857    for (var i = 0; i < ca.length; i++) {
13858      var c = ca[i].trim();
13859      if (c.indexOf(nameEQ) === 0)
13860        return decodeURIComponent(c.substring(nameEQ.length));
13861    }
13862    return "";
13863  }
13864
13865  function setCookie(name, val, days) {
13866    var d = new Date();
13867    d.setTime(d.getTime() + days * 24 * 60 * 60 * 1000);
13868    var expires = "expires=" + d.toUTCString();
13869    document.cookie =
13870      name + "=" + encodeURIComponent(val) + "; " + expires + "; path=/";
13871  }
13872
13873  // --- UI STATE REFRESH ---
13874
13875  // Helper function to get product/variation ID from row
13876  function getProductIdFromRow($row) {
13877    // First check for variation_id (using .data() for variable products with selected variation)
13878    var variationId = $row.data("wcpt_variation_id");
13879    if (variationId) {
13880      return String(variationId);
13881    }
13882    // Then check for data-wcpt-variation-id attribute (for variation products)
13883    var variationIdAttr = $row.attr("data-wcpt-variation-id");
13884    if (variationIdAttr) {
13885      return String(variationIdAttr);
13886    }
13887    // Finally fall back to product ID
13888    var productId = $row.attr("data-wcpt-product-id");
13889    if (productId) {
13890      return String(productId);
13891    }
13892    return "";
13893  }
13894
13895  function updateCompareButtonStates(forceIDs) {
13896    // Optionally accept an override ids (for API parity/event-coalescing)
13897    var ids = forceIDs || getCompareIDs();
13898
13899    $(".wcpt-compare-button").each(function () {
13900      var $btn = $(this);
13901      var $row = $btn.closest(".wcpt-row");
13902      var id = getProductIdFromRow($row);
13903      var $count = $btn.find(".wcpt-compare-button__count");
13904      var $input = $btn.find("input[type='checkbox']");
13905      if (!id) return;
13906
13907      // Update button's data attribute to match current row state
13908      $btn.attr("data-wcpt-product-id", id);
13909
13910      var inCompare = ids.indexOf(id) !== -1;
13911      if (inCompare) {
13912        $btn.removeClass("wcpt-not-in-compare").addClass("wcpt-in-compare");
13913        $input.prop("checked", true);
13914      } else {
13915        $btn.removeClass("wcpt-in-compare").addClass("wcpt-not-in-compare");
13916        $input.prop("checked", false);
13917      }
13918      if ($count.length) {
13919        $count.text("(" + ids.length + ")");
13920      }
13921    });
13922  }
13923
13924  // ---- WCOSC SYNC API (completely separate from native)
13925  // When possible, keep woosc integration as *optionally* in sync for visuals
13926  function updateWooscButtonStatesSyncWithWCPT() {
13927    if (typeof woosc_vars == "undefined") return;
13928    // Set the WOOSC cookie value to our (WCPT) compare IDs
13929    var woosc_cookie_name = "woosc_products_" + woosc_vars.user_id;
13930    var wcpt_ids = getCompareIDs();
13931    setCookie(woosc_cookie_name, wcpt_ids.join(","), 30);
13932
13933    // Use wcpt_ids for button state sync
13934    $(".woosc-btn").each(function () {
13935      var $btn = $(this);
13936      var id = $btn.attr("data-id");
13937      if (wcpt_ids.indexOf(String(id)) !== -1) {
13938        $btn.addClass("woosc-btn-added woosc-added");
13939      } else {
13940        $btn.removeClass("woosc-btn-added woosc-added");
13941      }
13942    });
13943  }
13944
13945  // ---- UI/State Observers ----
13946
13947  // Initial render/init: get compare state from cookie and update UI
13948  function initCompareUI() {
13949    updateCompareButtonStates();
13950    updateWooscButtonStatesSyncWithWCPT();
13951  }
13952
13953  $(function () {
13954    initCompareUI();
13955  });
13956
13957  // Also update on ajax table reloads
13958  $(document).on("wcpt_after_every_load", function () {
13959    updateCompareButtonStates();
13960    updateWooscButtonStatesSyncWithWCPT();
13961  });
13962
13963  // Any time our compare state changes (API), refresh UI
13964  $(document).on("wcpt_compare_changed", function (e, ids) {
13965    if (!ids) ids = getCompareIDs();
13966    updateCompareButtonStates(ids);
13967    updateWooscButtonStatesSyncWithWCPT();
13968    // ^ Only for visual update, but NOT to modify woosc cookie.
13969  });
13970
13971  // ---- Button Event Listeners ----
13972
13973  // Main click handler: toggle state, update UI/state, NO interference with woosc
13974  $(document).on("click", ".wcpt-compare-button", function (e) {
13975    // If the event is from a checkbox (or woosc-integration), skip here
13976    if (
13977      $(e.target).hasClass("wcpt-compare-button__external-link") ||
13978      $(e.target).closest(".wcpt-compare-button__external-link").length ||
13979      $(e.target).hasClass("woosc-btn") ||
13980      $(e.target).closest(".woosc-btn").length ||
13981      $(e.target).hasClass("wcpt-compare-button__icon")
13982    ) {
13983      return;
13984    }
13985
13986    var $btn = $(this);
13987    var $row = $btn.closest(".wcpt-row");
13988    var id = getProductIdFromRow($row);
13989    if (!id) return;
13990
13991    // Update button's data attribute to match current row state
13992    $btn.attr("data-wcpt-product-id", id);
13993
13994    if (isInCompare(id)) {
13995      removeFromCompare(id);
13996    } else {
13997      addToCompare(id);
13998      setTimeout(function () {
13999        $(".woosc-btn.woosc-added").first().trigger("click");
14000      }, 100);
14001    }
14002    // UI updates after wcpt_compare_changed event
14003  });
14004
14005  // push changes from woosc to wcpt
14006  $(document).on("woosc_change_count", function (e, count) {
14007    var woosc_cookie_name = "woosc_products_" + woosc_vars.user_id;
14008    var woosc_compare_ids = getCookie(woosc_cookie_name) || [];
14009
14010    setCompareIDs(
14011      typeof woosc_compare_ids === "string"
14012        ? woosc_compare_ids.split(",")
14013        : woosc_compare_ids,
14014    );
14015    updateCompareButtonStates();
14016  });
14017
14018  // Listen for select_variation event to update button ID and initialize state
14019  $(document).on("select_variation", ".wcpt-row", function (e, data) {
14020    var $row = $(this);
14021    var $btn = $row.find(".wcpt-compare-button");
14022
14023    if (!$btn.length) return;
14024
14025    // Get the current ID from row (will prioritize variation_id if available)
14026    var id = getProductIdFromRow($row);
14027
14028    if (id) {
14029      // Update button's data attribute
14030      $btn.attr("data-wcpt-product-id", id);
14031
14032      // Initialize button state with current compare state
14033      var ids = getCompareIDs();
14034      var inCompare = ids.indexOf(id) !== -1;
14035      var $count = $btn.find(".wcpt-compare-button__count");
14036      var $input = $btn.find("input[type='checkbox']");
14037
14038      if (inCompare) {
14039        $btn.removeClass("wcpt-not-in-compare").addClass("wcpt-in-compare");
14040        $input.prop("checked", true);
14041      } else {
14042        $btn.removeClass("wcpt-in-compare").addClass("wcpt-not-in-compare");
14043        $input.prop("checked", false);
14044      }
14045
14046      if ($count.length) {
14047        $count.text("(" + ids.length + ")");
14048      }
14049    }
14050  });
14051})(jQuery);
14052
14053// module: product quick view
14054(function ($) {
14055  $(document).on("click", ".wcpt-quick-view-button", function (e) {
14056    if ($(e.target).closest(".woosq-btn").length) {
14057      return;
14058    }
14059
14060    var $btn = $(this);
14061    $btn.find(".woosq-btn").click();
14062  });
14063})(jQuery);

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.