PageSourceSearch

https://culinarymedicine.org/wp-content/plugins/wc-product-table-pro/assets/js.js?ver=5.6.7

js culinarymedicine.org collected 2026-09-24 19:52:47 UTC 428,656 bytes, 13,868 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
656    $table.each(function () {
657      var column_count = $table.find(".wcpt-row").eq(0).children().length;
658
659      while (column_count) {
660        // check if all the cells in this column are empty
661        var $column_cells = $table.find(
662          ".wcpt-cell:nth-child(" + column_count + ")",
663        );
664
665        if ($column_cells.filter(":empty").length == $column_cells.length) {
666          $column_cells
667            .add($table.find(".wcpt-heading:nth-child(" + column_count + ")"))
668            .addClass("wcpt-hide");
669        }
670
671        --column_count;
672      }
673    });
674  }
675
676  // lazy load
677  function lazy_load_start() {
678    if (!window.wcpt_lazy_loaded) {
679      $(".wcpt-lazy-load").each(function () {
680        var $this = $(this),
681          table_id = $this.attr("data-wcpt-table-id"),
682          // Prefer data-wcpt-query-string (set by wcpt_get_container_attributes).
683          // Fall back to legacy data-wcpt-query for older markup.
684          query =
685            $this.attr("data-wcpt-query-string") ||
686            $this.attr("data-wcpt-query") ||
687            "";
688
689        if (query.charAt(0) === "?") {
690          query = query.substring(1);
691        }
692
693        $this
694          .addClass("wcpt wcpt-" + table_id)
695          .removeClass("wcpt-lazy-load")
696          .attr("id", "wcpt-" + table_id);
697        window.wcpt_attempt_ajax($this, query, false, "lazy_load");
698      });
699      window.wcpt_lazy_loaded = true;
700    }
701  }
702
703  // get rows including freeze
704  function get_product_rows($elm) {
705    var $row = $elm.closest(".wcpt-row"),
706      product_id = $row.attr("data-wcpt-product-id"),
707      variation_id = $row.attr("data-wcpt-variation-id"),
708      $scroll_wrapper = $elm.closest(".wcpt-table-scroll-wrapper-outer"),
709      row_selector;
710
711    if (variation_id) {
712      row_selector =
713        '[data-wcpt-variation-id="' +
714        variation_id +
715        '"].wcpt-row.wcpt-product-type-variation';
716    } else {
717      row_selector =
718        '[data-wcpt-product-id="' +
719        product_id +
720        '"].wcpt-row:not(.wcpt-product-type-variation)';
721    }
722
723    if ($scroll_wrapper && $scroll_wrapper.length) {
724      return $scroll_wrapper.find(row_selector);
725    }
726
727    return $(row_selector);
728  }
729
730  // button click listener
731  $("body").on("click", ".wcpt-button", button_click_handler);
732
733  function button_click_handler(e) {
734    var $button = $(this),
735      link_code = $button.attr("data-wcpt-link-code"),
736      $product_rows = get_product_rows($button),
737      product_id = $product_rows.attr("data-wcpt-product-id"),
738      is_variable = $product_rows.hasClass("wcpt-product-type-variable"),
739      complete_match = $product_rows.data("wcpt_complete_match"),
740      is_variation = $product_rows.hasClass("wcpt-product-type-variation"),
741      is_composite = $product_rows.hasClass("wcpt-product-type-composite"),
742      is_bundle = $product_rows.hasClass("wcpt-product-type-woosb"),
743      has_addons = $product_rows.hasClass("wcpt-product-has-addons"),
744      has_measurement = $product_rows.hasClass("wcpt-product-has-measurement"),
745      has_nyp = $product_rows.hasClass("wcpt-product-has-name-your-price"),
746      qty = "",
747      params = {
748        payload: {
749          products: {},
750          variations: {},
751          attributes: {},
752          addons: {},
753          measurement: {},
754          nyp: {}, // name your price
755        },
756      };
757
758    if ($("body").hasClass("wcpt-photoswipe-visible")) {
759      e.preventDefault();
760      return;
761    }
762
763    if (
764      -1 !==
765      $.inArray(link_code, [
766        "product_link",
767        "external_link",
768        "custom_field",
769        "custom_field_media_id",
770        "custom_field_acf",
771        "custom",
772      ])
773    ) {
774      return;
775    }
776
777    e.preventDefault();
778
779    // validation
780    // -- variable product
781    if (is_variable) {
782      var variation_found = $product_rows.data("wcpt_variation_found"),
783        variation_selected = $product_rows.data("wcpt_variation_selected"),
784        variation_available = $product_rows.data("wcpt_variation_available"),
785        variation_ops = $product_rows.data("wcpt_variation_ops");
786
787      // if row has variation selection options but customer did not make selections, show error
788      if (
789        variation_ops &&
790        (!variation_selected || !variation_found || !variation_available)
791      ) {
792        return;
793      }
794    }
795
796    // -- disabled
797    if (!is_variable && $button.hasClass("wcpt-disabled")) {
798      return;
799    }
800
801    // -- name your price
802    if (has_nyp) {
803      var $nyp = get_nyp_input_element($product_rows);
804      if ($nyp.length) {
805        var error = false,
806          name = $nyp.attr("data-wcpt-product-name"),
807          min = $nyp.attr("min"),
808          max = $nyp.attr("max");
809
810        if (!$nyp.val()) {
811          error = wcpt_nyp_error_message_templates["empty"];
812        } else if (min && $nyp.val() < parseFloat(min)) {
813          error = wcpt_nyp_error_message_templates["minimum_js"].replace(
814            "%%MINIMUM%%",
815            woocommerce_nyp_format_price(
816              min,
817              woocommerce_nyp_params.currency_format_symbol,
818              true,
819            ),
820          );
821        } else if (max && $nyp.val() > parseFloat(max)) {
822          error = wcpt_nyp_error_message_templates["maximum_js"].replace(
823            "%%MAXIMUM%%",
824            woocommerce_nyp_format_price(
825              max,
826              woocommerce_nyp_params.currency_format_symbol,
827              true,
828            ),
829          );
830        }
831
832        if (error) {
833          alert(error);
834          return;
835        }
836      }
837    }
838
839    // prepare params
840
841    // -- quantity
842    var $wcpt_qty = $(
843        ".wcpt-quantity input.qty, .wcpt-quantity > select.wcpt-qty-select",
844        $product_rows,
845      ),
846      $wc_qty = $(".cart .qty", $product_rows);
847
848    if ($wc_qty.length) {
849      // from WooCommerce form's qty field
850      qty = $wc_qty.val();
851    }
852
853    if ($wcpt_qty.length) {
854      // from WCPT's own qty element
855      var val = parseFloat($wcpt_qty.val());
856      if (isNaN(val) || !parseFloat($wcpt_qty.val())) {
857        $wcpt_qty
858          .filter("input")
859          .first()
860          .each(function () {
861            var $this = $(this),
862              min = $this.attr("data-wcpt-min");
863
864            $this.val(min);
865            limit_qty_controller($this.parent("wcpt-quantity"));
866            val = $this.val();
867          });
868      }
869
870      qty = val;
871    }
872
873    params.payload.products[product_id] = qty;
874
875    // -- addons (only when the addon UI is actually printed & visible in the row;
876    // select variation "attribute dropdowns" reuses cart_form markup and must not
877    // count as having addons already collected)
878    var has_inline_addons_form = wcpt_row_has_inline_addons_form($product_rows),
879      addons = {};
880    if (has_addons && has_inline_addons_form) {
881      addons = wcpt_get_addons($product_rows);
882      if (!$.isEmptyObject(addons)) {
883        params.payload.addons[product_id] = addons;
884      }
885    }
886
887    // -- measurement
888    if (has_measurement) {
889      var measurement = wcpt_get_measurement($product_rows);
890      if (!$.isEmptyObject(measurement)) {
891        params.payload.measurement[product_id] = measurement;
892      }
893    }
894
895    // -- name your price
896    if (has_nyp) {
897      var nyp = wcpt_get_nyp($product_rows);
898      if (nyp) {
899        params.payload.nyp[product_id] = nyp;
900      }
901    }
902
903    // -- variation
904    if (is_variation) {
905      var variation_id = $product_rows.attr("data-wcpt-variation-id"),
906        variation_attributes = JSON.parse(
907          $product_rows.attr("data-wcpt-variation-attributes"),
908        ),
909        $missing_attribute_select = $(
910          ".wcpt-select-variation-attribute-term",
911          $product_rows,
912        );
913
914      if ($missing_attribute_select.length) {
915        $missing_attribute_select.each(function () {
916          var $this = $(this),
917            attribute = $this.attr("data-wcpt-attribute"),
918            term = $this.val();
919
920          if (term) {
921            variation_attributes[attribute] = term;
922          }
923        });
924      }
925
926      if (typeof params.payload.variations[product_id] === "undefined") {
927        params.payload.variations[product_id] = {};
928      }
929      params.payload.variations[product_id][variation_id] = qty;
930      params.payload.attributes[variation_id] = variation_attributes;
931    } else if ($product_rows.hasClass("wcpt-product-type-variable")) {
932      var variation_id = $product_rows.data("wcpt_variation_id"),
933        variation_attributes = $product_rows.data("wcpt_attributes");
934
935      if (variation_id) {
936        if (typeof params.payload.variations[product_id] === "undefined") {
937          params.payload.variations[product_id] = {};
938        }
939        params.payload.variations[product_id][variation_id] = qty;
940      }
941
942      if (variation_attributes) {
943        params.payload.attributes[variation_id] = variation_attributes;
944      }
945    }
946
947    // prepare 'ajax_data' (required for non-AJAX req, submit over POST)
948    var ajax_data = {
949      action: "wcpt_add_to_cart",
950      "add-to-cart": $product_rows.attr("data-wcpt-product-id"),
951      product_id: product_id,
952      quantity: qty,
953    };
954
955    // -- addons
956    if (has_addons && has_inline_addons_form && !$.isEmptyObject(addons)) {
957      $.extend(ajax_data, addons);
958    }
959
960    // -- measurement (submit via post)
961    if (has_measurement) {
962      var measurement = wcpt_get_measurement($product_rows);
963      if (!$.isEmptyObject(measurement)) {
964        $.extend(ajax_data, measurement);
965      }
966    }
967
968    // -- name your price (submit via post)
969    if (has_nyp) {
970      var nyp = wcpt_get_nyp($product_rows);
971      if (nyp) {
972        ajax_data.nyp = nyp;
973      }
974    }
975
976    // -- variation (submit via post)
977    if (is_variable || is_variation) {
978      if (variation_id) {
979        ajax_data.variation_id = variation_id;
980      }
981
982      if (variation_attributes) {
983        $.extend(ajax_data, variation_attributes);
984      }
985    }
986
987    // receive notices from server?
988    ajax_data.return_notice = link_code == "cart_ajax";
989
990    // modal required
991    if (
992      is_composite ||
993      is_bundle ||
994      (is_variable && !complete_match) ||
995      (is_variation && is_incomplete_variation(variation_attributes)) ||
996      (has_addons && !has_inline_addons_form) ||
997      (has_measurement && !params.payload.measurement[product_id]) ||
998      (has_nyp && !params.payload.nyp[product_id])
999    ) {
1000      // deploy modal immediately if it's in cache
1001      if (typeof window.wcpt_product_form[product_id] !== "undefined") {
1002        deploy_product_form_modal(
1003          window.wcpt_product_form[product_id],
1004          $button,
1005          ajax_data,
1006        );
1007
1008        // else fetch modal from server and deploy
1009      } else {
1010        ajax_data.action = "wcpt_get_product_form_modal";
1011        ajax_data.lang = wcpt_i18n.lang;
1012        delete ajax_data["add-to-cart"];
1013
1014        $.ajax({
1015          url: wcpt_params.wc_ajax_url.replace(
1016            "%%endpoint%%",
1017            "wcpt_get_product_form_modal",
1018          ),
1019          method: "POST",
1020          beforeSend: function () {
1021            window.wcpt_modal__last_requested_product_id = product_id;
1022            deploy_loading_modal();
1023          },
1024          data: ajax_data,
1025        }).done(function (response) {
1026          window.wcpt_product_form[product_id] = response;
1027
1028          if (product_id === window.wcpt_modal__last_requested_product_id) {
1029            // skip if req. superseded
1030            $(".wcpt-product-form-loading-modal").trigger("wcpt_close"); // close loading modal
1031            deploy_product_form_modal(response, $button, ajax_data);
1032          }
1033        });
1034      }
1035
1036      return false;
1037    }
1038
1039    // all required info already available, no need for modal
1040    if (link_code == "cart_ajax") {
1041      wcpt_cart(params);
1042    } else {
1043      submit_via_post($button.attr("href"), ajax_data);
1044    }
1045  }
1046
1047  function deploy_product_form_modal(markup, $button, ajax_data) {
1048    var $modal = $(markup);
1049    $modal.appendTo("body");
1050    $("body").addClass("wcpt-modal-on");
1051    prep_product_form($modal, $button, ajax_data);
1052    $("body").trigger("wcpt_product_modal_ready");
1053  }
1054
1055  function deploy_loading_modal() {
1056    var $loading_modal = $($("#tmpl-wcpt-product-form-loading-modal").html());
1057    $("body").append($loading_modal);
1058    $loading_modal.on("wcpt_close", function () {
1059      $loading_modal.remove();
1060    });
1061  }
1062
1063  function is_incomplete_variation(variation_attributes) {
1064    var is_incomplete_variation = false;
1065    $.each(variation_attributes, function (key, value) {
1066      if (!value) {
1067        is_incomplete_variation = true;
1068        return false;
1069      }
1070    });
1071
1072    return is_incomplete_variation;
1073  }
1074
1075  function submit_via_post(href, data) {
1076    // redirect by form
1077    var $form = $(
1078      '<form method="POST" action="' +
1079        href +
1080        '" style="display: none;"></form>',
1081    );
1082    $.each(data, function (key, val) {
1083      if (key == "action") return; // continue
1084      var $input = $('<input type="hidden" name="' + key + '" value="" />');
1085      $input.val(val);
1086      $form.append($input);
1087    });
1088    $form.append('<input type="hidden" name="wcpt_request" value="true" />');
1089    $form.appendTo($("body")).submit();
1090  }
1091
1092  function prep_product_form($modal, $button, pre_select) {
1093    var link_code = $button.attr("data-wcpt-link-code"),
1094      href = link_code == "cart_ajax" ? "" : $button.attr("href");
1095
1096    $modal.on("wcpt_close", function () {
1097      $modal.remove();
1098      $("body").removeClass("wcpt-modal-on");
1099    });
1100
1101    $(".cart", $modal).each(function () {
1102      var $form = $(this);
1103
1104      if ($form.hasClass("variations_form")) {
1105        $form.wc_variation_form();
1106      } else {
1107        // simple product (probably with addon or measurement)
1108        $form.append(
1109          '<input name="add-to-cart" type="hidden" value="' +
1110            pre_select["product_id"] +
1111            '">',
1112        );
1113      }
1114
1115      // init addons
1116      if (typeof WC_PAO === "object") {
1117        new WC_PAO.Form($form);
1118      } else {
1119        if ($.fn.init_addon_totals) {
1120          $form.init_addon_totals();
1121        }
1122        if (typeof wcPaoInitAddonTotals === "object") {
1123          wcPaoInitAddonTotals.init($form);
1124        }
1125      }
1126
1127      // init measurement
1128      if (typeof wcpt_wc_mc_init_cart !== "undefined") {
1129        $form.each(wcpt_wc_mc_init_cart);
1130      }
1131
1132      // cart: name your price
1133      if (typeof jQuery.fn.wc_nyp_form !== "undefined") {
1134        $form.wc_nyp_form();
1135      }
1136
1137      $form.attr("action", href);
1138
1139      $(".qty", $form).attr("autocomplete", "off");
1140
1141      if (pre_select) {
1142        $.each(pre_select, function (key, val) {
1143          var $control = $form.find("[name='" + key + "']");
1144          if ($control.is("input.qty")) {
1145            // working on input
1146            val = parseFloat(val);
1147            var min = $control.attr("min")
1148              ? parseFloat($control.attr("min"))
1149              : 0;
1150            var max = $control.attr("max")
1151              ? parseFloat($control.attr("max"))
1152              : false;
1153
1154            // respect min
1155            if (val < min || isNaN(val)) {
1156              val = min;
1157            }
1158
1159            // respect max
1160            if (max && val > max) {
1161              val = max;
1162            }
1163          }
1164          $control.val(val);
1165        });
1166      }
1167
1168      // try and apply quantity on default variation
1169      if (pre_select.quantity) {
1170        $form.one("show_variation", function () {
1171          var $form_qty = $(".qty", $form),
1172            min = $form_qty.attr("min"),
1173            max = $form_qty.attr("max");
1174          if (
1175            (!min || min <= pre_select.quantity) &&
1176            (!max || max >= pre_select.quantity)
1177          ) {
1178            $form_qty.val(pre_select.quantity);
1179          }
1180        });
1181      }
1182
1183      if (link_code == "cart_ajax") {
1184        $form.on("submit", function (e) {
1185          e.preventDefault();
1186
1187          var external_payload = {};
1188
1189          $.each($form.serializeArray(), function (i, field) {
1190            if (typeof external_payload[field.name] === "undefined") {
1191              external_payload[field.name] = field.value;
1192            } else {
1193              // should be array
1194              if (typeof external_payload[field.name] !== "object") {
1195                external_payload[field.name] = [external_payload[field.name]];
1196              }
1197              external_payload[field.name].push(field.value);
1198            }
1199          });
1200
1201          wcpt_cart({
1202            external_payload: external_payload,
1203            payload: { variation_form: true },
1204          });
1205
1206          $modal.trigger("wcpt_close");
1207        });
1208      }
1209
1210      // reset qty in row
1211      var $rows = wcpt_get_sibling_rows($button.closest(".wcpt-row"));
1212      $rows
1213        .find(".qty[data-wcpt-return-to-initial=1]")
1214        .val(0)
1215        .first()
1216        .trigger("change");
1217    });
1218  }
1219
1220  function disable_button($button, add_condition) {
1221    if (add_condition) {
1222      $button.addClass(add_condition);
1223    }
1224
1225    $button.addClass("wcpt-disabled");
1226  }
1227
1228  function enable_button($button, clear_condition) {
1229    if (clear_condition) {
1230      $button.removeClass(clear_condition);
1231    }
1232
1233    if (
1234      // list of conditions
1235      !$button.hasClass("wcpt-all-variations-out-of-stock") &&
1236      !$button.hasClass("wcpt-variation-out-of-stock") &&
1237      !$button.hasClass("wcpt-no-variation-selected") &&
1238      !$button.hasClass("wcpt-quantity-input-error") &&
1239      !$button.hasClass("wcpt-out-of-stock")
1240    ) {
1241      $button.removeClass("wcpt-disabled");
1242    }
1243  }
1244
1245  function loading_badge_on_button($button) {
1246    disable_button($button);
1247    if (!$button.find(".wcpt-cart-badge-refresh").length) {
1248      var svg =
1249        '<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>';
1250      $button.append('<i class="wcpt-cart-badge-refresh">' + svg + "</i>");
1251    }
1252  }
1253
1254  function add_count_badge_to_button(in_cart, $button) {
1255    if (!parseFloat(in_cart)) {
1256      $(".wcpt-cart-badge-number, .wcpt-cart-badge-refresh", $button).remove();
1257      return;
1258    }
1259
1260    // if( $button.closest('.wcpt-row').hasClass('wcpt-sold-individually') ){
1261    //   in_cart = '✓';
1262    // }
1263
1264    if (!$button.find(".wcpt-cart-badge-number").length) {
1265      $button.append('<i class="wcpt-cart-badge-number">' + in_cart + "</i>");
1266    } else {
1267      $button.find(".wcpt-cart-badge-number").html(in_cart);
1268    }
1269
1270    if ($button.find(".wcpt-cart-badge-refresh").length) {
1271      $button.find(".wcpt-cart-badge-refresh").remove();
1272    }
1273  }
1274
1275  // search
1276  // -- submit
1277  $("body").on("click", ".wcpt-search-submit", search_submit);
1278  $("body").on("keydown", ".wcpt-search-input", search_submit);
1279  function search_submit(e) {
1280    var $this = $(this),
1281      $search = $this.closest(".wcpt-search"),
1282      $input = $search.find(".wcpt-search-input"),
1283      table_id = $search.attr("data-wcpt-table-id"),
1284      $container = $("#wcpt-" + table_id),
1285      $nav_modal = $this.closest(".wcpt-nav-modal"),
1286      $nav = $this.closest(".wcpt-navigation"),
1287      keywords = $input.val().trim(),
1288      previous_keywords = $input.attr("data-wcpt-value"),
1289      query = $input.attr("name") + "=" + encodeURIComponent(keywords),
1290      $wrapper = $input.closest(".wcpt-search-wrapper"),
1291      append = !$wrapper.hasClass("wcpt-search--reset-others");
1292
1293    if (previous_keywords === keywords) {
1294      return;
1295    }
1296
1297    if (
1298      // submit button is clicked
1299      ($(e.target).closest(".wcpt-search-submit").length &&
1300        e.type == "click") || // enter key pressed on input
1301      ($(e.target).is(".wcpt-search-input") &&
1302        e.type == "keydown" &&
1303        (e.keyCode == 13 || e.which == 13))
1304    ) {
1305      if ($nav_modal.length) {
1306        $(".wcpt-nm-apply").click();
1307        return;
1308      }
1309
1310      if (append) {
1311        $nav.trigger("change");
1312      } else {
1313        // reset other filters
1314        attempt_ajax($container, query, append, "filter");
1315      }
1316
1317      if ($nav_modal.length) {
1318        $nav_modal.trigger("wcpt_close");
1319      }
1320    }
1321  }
1322  // -- clear
1323  $("body").on("click", ".wcpt-search-clear", function (e) {
1324    var $this = $(this),
1325      $search = $this.closest(".wcpt-search"),
1326      $input = $search.find(".wcpt-search-input"),
1327      table_id = $search.attr("data-wcpt-table-id"),
1328      $container = $("#wcpt-" + table_id),
1329      $nav_modal = $this.closest(".wcpt-nav-modal"),
1330      query = "&" + $input.attr("name") + "=",
1331      append = true;
1332    $input.val("");
1333
1334    if ($nav_modal.length) {
1335      $(".wcpt-nm-apply").click();
1336      return;
1337    }
1338
1339    attempt_ajax($container, query, append, "filter");
1340
1341    if ($nav_modal.length) {
1342      $nav_modal.trigger("wcpt_close");
1343    }
1344  });
1345
1346  // download button - responsive fix
1347  if (wcpt_params.initial_device !== "laptop") {
1348    $("body").on("click", ".wcpt-button[download]", function (e) {
1349      e.preventDefault();
1350      var $this = $(this),
1351        url = $this.attr("href");
1352
1353      if (!url) {
1354        return;
1355      }
1356
1357      // Masked stream URLs: download in a hidden iframe to avoid a blank tab
1358      // and keep the real remote URL off the address bar.
1359      if (url.indexOf("wcpt_stream=") !== -1) {
1360        var iframe = document.createElement("iframe");
1361        iframe.style.display = "none";
1362        iframe.setAttribute("aria-hidden", "true");
1363        iframe.src = url;
1364        document.body.appendChild(iframe);
1365        setTimeout(function () {
1366          if (iframe.parentNode) {
1367            iframe.parentNode.removeChild(iframe);
1368          }
1369        }, 120000);
1370        return;
1371      }
1372
1373      window.open(url, "_blank", false);
1374    });
1375  }
1376
1377  // dropdown / tooltip
1378
1379  // currently assigned trigger ev
1380  // -- default
1381  window.wcpt_global_tooltip_trigger_mode = "hover"; // hover (default) | click
1382  // -- switch to click
1383  $(window).on("touchstart", function () {
1384    window.wcpt_global_tooltip_trigger_mode = "click";
1385  });
1386  // -- switch to hover
1387  $(window).on("resize", function () {
1388    window.wcpt_global_tooltip_trigger_mode = "hover";
1389  });
1390
1391  var target_selector =
1392      ".wcpt-dropdown, .wcpt-tooltip:not(.wcpt-tooltip--inside-portal)",
1393    $body = $("body");
1394
1395  $body.on("mouseenter", target_selector, dropdown_hover_open);
1396  $body.on("mouseleave", target_selector, dropdown_hover_close);
1397  $body.on("click", dropdown_touch_toggle);
1398
1399  function get_event_type_for_dropdown($dropdown) {
1400    var is_tooltip = !!$dropdown.closest(".wcpt-tooltip").length,
1401      is_in_sidebar = !!$dropdown.closest(".wcpt-left-sidebar").length;
1402
1403    // click ev type is forced
1404    if (
1405      // -- in dropdown options
1406      $dropdown.hasClass("wcpt-tooltip--open-on-click") ||
1407      // -- we are on touchscreen
1408      wcpt_global_tooltip_trigger_mode == "click"
1409    ) {
1410      return "click";
1411    }
1412
1413    // filters in sidebar alway use click
1414    if (!is_tooltip && is_in_sidebar) {
1415      return "click";
1416    }
1417
1418    return "hover";
1419  }
1420
1421  function dropdown_hover_open(e) {
1422    var $this = $(this);
1423
1424    if ("hover" !== get_event_type_for_dropdown($this)) {
1425      return;
1426    }
1427
1428    // hover intent
1429    // if( $this.hasClass('wcpt-tooltip--hover-intent-enabled') ){
1430    var clear_timeout = setTimeout(function () {
1431      // if( ! $this.is(':hover') ) return;
1432      $this.addClass("wcpt-open");
1433      fix_tooltip_position($this);
1434    }, 200);
1435
1436    $this.data("wcpt_hover_intent_clear_timeout", clear_timeout);
1437  }
1438
1439  function dropdown_hover_close(e) {
1440    var $this = $(this);
1441
1442    if ("hover" !== get_event_type_for_dropdown($this)) {
1443      return;
1444    }
1445
1446    // hover intent
1447    var clear_timeout = $this.data("wcpt_hover_intent_clear_timeout");
1448    if (clear_timeout) {
1449      clearTimeout(clear_timeout);
1450    }
1451
1452    $this.removeClass("wcpt-open");
1453  }
1454
1455  function dropdown_touch_toggle(e) {
1456    var $target = $(e.target),
1457      container_selector = ".wcpt-dropdown, .wcpt-tooltip",
1458      content_selector =
1459        "> .wcpt-dropdown-menu, > .wcpt-tooltip-content-wrapper > .wcpt-tooltip-content",
1460      $dropdown = $target.closest(container_selector),
1461      $content = $dropdown.find(content_selector),
1462      $body = $("body");
1463
1464    // clicked outside any tootltip / filter
1465    if (!$dropdown.length) {
1466      // close all dropdowns or tooltips not in nav modal or sidebar
1467      if (!$target.closest(".wcpt-nav-modal").length) {
1468        $body.find(container_selector).each(function () {
1469          if (
1470            $(this).hasClass("wcpt-tooltip") ||
1471            (!$(this).closest(".wcpt-nav-modal").length &&
1472              !$(this).closest(".wcpt-left-sidebar").length)
1473          ) {
1474            $(this).removeClass("wcpt-open");
1475          }
1476        });
1477      }
1478
1479      return;
1480    }
1481
1482    if ("click" !== get_event_type_for_dropdown($dropdown)) {
1483      return;
1484    }
1485
1486    // clicked in dropdown
1487    if ($dropdown.length) {
1488      // -- clicked in content
1489      if ($target.closest($content).length) {
1490        // return;
1491        // -- clicked on trigger
1492      } else {
1493        // close all others
1494
1495        // -- except parents and other open sidebar filters
1496        var $parents = $dropdown.parents(container_selector),
1497          $sidebar_filters = $(".wcpt-left-sidebar  .wcpt-dropdown");
1498
1499        $body
1500          .find(container_selector)
1501          .not($dropdown.add($parents).add($sidebar_filters))
1502          .removeClass("wcpt-open");
1503
1504        $dropdown.toggleClass("wcpt-open");
1505
1506        // close all children
1507        if (!$dropdown.hasClass("wcpt-open")) {
1508          $dropdown.find(container_selector).removeClass("wcpt-open");
1509        }
1510
1511        // popup enabled
1512        if ($dropdown.hasClass("wcpt-tooltip--popup-enabled")) {
1513          if ($dropdown.hasClass("wcpt-open")) {
1514            // need to run open logic earlier
1515            $body.addClass("wcpt-tooltip-popup-displayed");
1516          } else {
1517            $body.removeClass("wcpt-tooltip-popup-displayed");
1518          }
1519        }
1520      }
1521
1522      fix_tooltip_position($dropdown);
1523    }
1524  }
1525
1526  function fix_tooltip_position($tooltip) {
1527    // correct position
1528    var $content = $tooltip.find(
1529        " > .wcpt-dropdown-menu, > .wcpt-tooltip-content-wrapper > .wcpt-tooltip-content",
1530      ),
1531      content_width = $content.outerWidth(false),
1532      offset_left = $content.offset().left,
1533      page_width = $(window).width();
1534
1535    if ($tooltip.hasClass("wcpt-tooltip")) {
1536      // tooltip
1537
1538      // if tooltip is already in portal, don't move it
1539      if ($tooltip.closest(".wcpt-tooltip-portal").length) {
1540        return;
1541      }
1542
1543      // narrow width
1544      $content.css("max-width", "");
1545      var $container, margin;
1546
1547      if ($tooltip.hasClass("wcpt-tooltip--popup-enabled")) {
1548        $container = $("body");
1549        margin = 40;
1550      } else {
1551        $container = $tooltip.closest(".wcpt-table-scroll-wrapper-outer").length
1552          ? $tooltip.closest(".wcpt-table-scroll-wrapper-outer")
1553          : $("body");
1554        margin = 20;
1555      }
1556
1557      var container_rect = $.extend({}, $container[0].getBoundingClientRect()),
1558        content_rect = $content[0].getBoundingClientRect();
1559
1560      var $freezeTable = $(".frzTbl-table", $container).not(
1561        ".frzTbl-clone-table",
1562      );
1563
1564      if ($content.closest($freezeTable).length) {
1565        // target is inside the original freeze_table
1566        var $left_freeze_column =
1567            $freezeTable.data("freezeTable").el.$frozenColumnsLeft,
1568          $right_freeze_column =
1569            $freezeTable.data("freezeTable").el.$frozenColumnsRight;
1570
1571        container_rect.left += $left_freeze_column.width();
1572        container_rect.right -= $right_freeze_column.width();
1573        container_rect.width =
1574          container_rect.width -
1575          $left_freeze_column.width() -
1576          $right_freeze_column.width();
1577      }
1578
1579      if (container_rect.width < parseInt(content_rect.width) + margin * 2) {
1580        $content.css("max-width", container_rect.width - margin * 2);
1581      }
1582
1583      // vertical position
1584      var $content_wrapper = $content.parent(),
1585        content_wrapper_width = $content_wrapper.width(),
1586        viewport_height = $(window).height();
1587
1588      $content_wrapper.attr("data-wcpt-position", "");
1589      content_rect = $content[0].getBoundingClientRect(); // refresh
1590
1591      // Check if tooltip would go above viewport or container top
1592      if (content_rect.top - 150 < 0) {
1593        // Position above if not enough space below
1594      } else {
1595        $content.parent().attr("data-wcpt-position", "above");
1596      }
1597
1598      // // Check if tooltip would go below viewport or container bottom
1599      // if (
1600      //   content_rect.bottom + 30 > viewport_height ||
1601      //   container_rect.bottom < content_rect.bottom + 30
1602      // ) {
1603      //   // Position above if not enough space below
1604      //   $content.parent().attr("data-wcpt-position", "above");
1605      // }
1606
1607      // horizontal position
1608      $content.css({ left: "", right: "" });
1609      content_rect = $content[0].getBoundingClientRect(); // refresh
1610
1611      var arrow_margin = 20; // how close arrow gets to left/ right edge
1612
1613      if (content_rect.left - 15 < container_rect.left) {
1614        // excess left
1615        var left = 15 + container_rect.left - content_rect.left,
1616          limit = content_wrapper_width / 2 - arrow_margin;
1617
1618        if (left > limit) {
1619          left = limit;
1620        }
1621
1622        $content.css("left", left);
1623      } else if (content_rect.right + 15 > container_rect.right) {
1624        // excess right
1625        var right = content_rect.right - container_rect.right + 15,
1626          limit = content_wrapper_width / 2 - arrow_margin;
1627
1628        if (right > limit) {
1629          right = limit;
1630        }
1631
1632        $content.css("right", right);
1633      }
1634    } else {
1635      // dropdown
1636      if (content_width + 30 > page_width) {
1637        $content.outerWidth(page_width - 30);
1638        var content_width = $content.outerWidth(false);
1639      }
1640
1641      if ($content.offset().left + content_width > page_width) {
1642        // offscreen right
1643        var offset_required =
1644          $content.offset().left + content_width - page_width;
1645        $content.css("left", "-=" + (offset_required + 15));
1646      } else if ($content.offset().left < 0) {
1647        // offscreen left
1648        $content.css("left", Math.abs($content.offset().left - 15));
1649      }
1650    }
1651
1652    // tooltip arrow
1653    if ($tooltip.hasClass("wcpt-tooltip")) {
1654      var $label = $tooltip.find("> .wcpt-tooltip-label"),
1655        offset_left = $label.offset().left,
1656        width = $label.outerWidth(),
1657        $arrow = $("> .wcpt-tooltip-arrow", $content);
1658
1659      $arrow.css(
1660        "left",
1661        offset_left - $content.offset().left + width / 2 + "px",
1662      );
1663    }
1664
1665    // place tooltip in portal
1666    if (
1667      $tooltip.hasClass("wcpt-tooltip") &&
1668      $container.hasClass("wcpt-table-scroll-wrapper-outer")
1669    ) {
1670      var container_rect = $container[0].getBoundingClientRect();
1671      var content_rect = $content[0].getBoundingClientRect();
1672
1673      var $row = $tooltip.closest(".wcpt-row, .wcpt-heading-row"),
1674        $cell = $tooltip.closest(".wcpt-cell, .wcpt-heading");
1675
1676      $container.children(".wcpt-tooltip-portal").remove();
1677
1678      // Get all data attributes from the original row
1679      var rowAttributes = "";
1680      $.each($row[0].attributes, function (i, attr) {
1681        if (attr.name.startsWith("data-")) {
1682          rowAttributes += " " + attr.name + '="' + attr.value + '"';
1683        }
1684      });
1685
1686      var $portal = $(
1687        '<div class="wcpt-tooltip-portal">' +
1688          '<div class="wcpt-table">' +
1689          '<div class="' +
1690          $row.attr("class") +
1691          '"' +
1692          rowAttributes +
1693          ">" +
1694          '<div class="' +
1695          $cell.attr("class") +
1696          '">' +
1697          '<div class="wcpt-item-row"></div>' +
1698          "</div>" +
1699          "</div>" +
1700          "</div>" +
1701          "</div>",
1702      );
1703      $portal
1704        .find(".wcpt-item-row")
1705        .html($tooltip.clone().addClass("wcpt-tooltip--inside-portal"));
1706
1707      $portal.find(".wcpt-row").data($row.data());
1708
1709      var wrapper_rect = $content_wrapper[0].getBoundingClientRect();
1710      $portal.find(".wcpt-tooltip-content-wrapper").css({
1711        position: "fixed",
1712        top: wrapper_rect.top + "px",
1713        left: wrapper_rect.left + "px",
1714      });
1715
1716      var content_rect = $content[0].getBoundingClientRect();
1717
1718      var $portal_content = $portal.find(".wcpt-tooltip-content");
1719      $portal_content.css({
1720        width: content_rect.width + "px",
1721        transform: "translateY(4px)",
1722        opacity: 0,
1723      });
1724      $content.css({
1725        display: "none",
1726      });
1727      $container.append($portal);
1728
1729      // animate
1730      setTimeout(function () {
1731        $portal_content.css({
1732          transform: "translateY(0px)",
1733          opacity: 1,
1734        });
1735      }, 1);
1736
1737      var $sticky_table_scroll_wrapper = $tooltip.closest(
1738        ".frzTbl-table-wrapper",
1739      );
1740
1741      // Generate unique namespace for this tooltip instance
1742      var portalNamespace =
1743        "wcpt_portal_" + Math.random().toString(36).substr(2, 9);
1744
1745      function remove_portal() {
1746        // Remove all event handlers using unique namespace
1747        $container.off("mousemove." + portalNamespace);
1748        $(window).off("scroll." + portalNamespace);
1749        $container.off("scroll." + portalNamespace);
1750        $("body").off("click." + portalNamespace);
1751        $sticky_table_scroll_wrapper.off("scroll." + portalNamespace);
1752
1753        $portal.remove();
1754        $content.css("display", "");
1755        $tooltip.removeClass("wcpt-open");
1756      }
1757
1758      // Store namespace on portal element for future reference
1759      $portal.data("portalNamespace", portalNamespace);
1760
1761      // remove portal based on
1762      // -- mouse hover
1763      if (!$tooltip.hasClass("wcpt-tooltip--open-on-click")) {
1764        $container.on("mousemove." + portalNamespace, function (e) {
1765          var $target = $(e.target);
1766          if (
1767            !$target.closest($portal).length &&
1768            !$target.closest($tooltip).length
1769          ) {
1770            remove_portal();
1771          }
1772        });
1773      } else {
1774        // -- click outside
1775        $("body").on("click." + portalNamespace, function (e) {
1776          if (!$(e.target).closest($portal).length) {
1777            remove_portal();
1778          }
1779        });
1780      }
1781      // -- scroll
1782      $(window).on("scroll." + portalNamespace, function () {
1783        remove_portal();
1784      });
1785      $container.on("scroll." + portalNamespace, function () {
1786        remove_portal();
1787      });
1788      $sticky_table_scroll_wrapper.on("scroll." + portalNamespace, function () {
1789        remove_portal();
1790      });
1791    }
1792  }
1793
1794  // tooltip with content hover disabled
1795  $("body").on("click mouseover", ".wcpt-tooltip-content", function () {
1796    var $this = $(this),
1797      $tooltip = $this.closest(".wcpt-tooltip");
1798
1799    if ($tooltip.hasClass("wcpt-tooltip--hover-disabled")) {
1800      $tooltip.removeClass("wcpt-open");
1801    }
1802  });
1803
1804  // close dropdown when grab and scroll starts
1805  $("body").on("freeze_table__grab_and_scroll__start", function () {
1806    $(
1807      ".wcpt-navigation:not(.wcpt-left-sidebar) .wcpt-dropdown.wcpt-open",
1808    ).removeClass("wcpt-open");
1809  });
1810
1811  // Unique key for matching the same filter/search across header + sidebar
1812  function get_nav_filter_dedupe_key($el) {
1813    if ($el.hasClass("wcpt-search-wrapper") || $el.hasClass("wcpt-search")) {
1814      var $input = $el
1815        .find(".wcpt-search-input")
1816        .addBack(".wcpt-search-input")
1817        .first();
1818      var name = $input.attr("name") || "";
1819      return name ? "search:" + name : "";
1820    }
1821
1822    if (!$el.hasClass("wcpt-filter") && !$el.is(".wcpt-filter")) {
1823      return "";
1824    }
1825
1826    var filter = $el.attr("data-wcpt-filter");
1827    if (!filter) {
1828      return "";
1829    }
1830
1831    return (
1832      filter +
1833      "|" +
1834      ($el.attr("data-wcpt-taxonomy") || "") +
1835      "|" +
1836      ($el.attr("data-wcpt-meta-key") || "")
1837    );
1838  }
1839
1840  // Match other instances of the same filter across header / sidebar / etc.
1841  function find_duplicate_filters($filter, $nav) {
1842    var filter = $filter.attr("data-wcpt-filter");
1843    if (!filter) {
1844      return $();
1845    }
1846
1847    var selector = '.wcpt-filter[data-wcpt-filter="' + filter + '"]',
1848      taxonomy = $filter.attr("data-wcpt-taxonomy"),
1849      meta_key = $filter.attr("data-wcpt-meta-key");
1850
1851    if (taxonomy) {
1852      selector += '[data-wcpt-taxonomy="' + taxonomy + '"]';
1853    }
1854    if (meta_key) {
1855      selector += '[data-wcpt-meta-key="' + meta_key + '"]';
1856    }
1857
1858    return $nav.find(selector);
1859  }
1860
1861  // When phone/tablet flattens header + sidebar into one strip, hide later
1862  // copies of the same filter so each filter only appears once.
1863  function hide_responsive_duplicate_nav_filters($wcpt) {
1864    $wcpt
1865      .find(".wcpt-nav-duplicate-hidden")
1866      .removeClass("wcpt-nav-duplicate-hidden");
1867
1868    if (get_device() === "laptop") {
1869      return;
1870    }
1871
1872    // Only needed when the sidebar was converted into the combined header strip
1873    if (!$wcpt.find(".wcpt-was-left-sidebar").length) {
1874      return;
1875    }
1876
1877    var seen = {};
1878    $wcpt
1879      .find(".wcpt-navigation-wrapper")
1880      .find(".wcpt-filter, .wcpt-search-wrapper")
1881      .each(function () {
1882        var $el = $(this);
1883        if ($el.closest(".wcpt-nav-modal-tpl, .wcpt-nav-modal").length) {
1884          return;
1885        }
1886
1887        var key = get_nav_filter_dedupe_key($el);
1888        if (!key) {
1889          return;
1890        }
1891
1892        if (seen[key]) {
1893          $el.addClass("wcpt-nav-duplicate-hidden");
1894        } else {
1895          seen[key] = true;
1896        }
1897      });
1898  }
1899
1900  // Keep first of each unique filter/search; drop later duplicates from a set
1901  function dedupe_nav_elements($set) {
1902    var seen = {};
1903    return $set.filter(function () {
1904      var key = get_nav_filter_dedupe_key($(this));
1905      if (!key) {
1906        return true;
1907      }
1908      if (seen[key]) {
1909        return false;
1910      }
1911      seen[key] = true;
1912      return true;
1913    });
1914  }
1915
1916  // Copy checked/value state from one filter instance onto its duplicates
1917  function sync_duplicate_filters_from($source_filter, $nav) {
1918    var $duplicates = find_duplicate_filters($source_filter, $nav).not(
1919      $source_filter,
1920    );
1921    if (!$duplicates.length) {
1922      return;
1923    }
1924
1925    $source_filter
1926      .find('input[type="checkbox"], input[type="radio"]')
1927      .each(function () {
1928        var $src = $(this),
1929          value = $src.attr("value"),
1930          checked = $src.prop("checked"),
1931          type = $src.attr("type");
1932
1933        $duplicates.each(function () {
1934          $(this)
1935            .find('input[type="' + type + '"]')
1936            .filter(function () {
1937              return $(this).attr("value") === value;
1938            })
1939            .each(function () {
1940              var $input = $(this),
1941                $option = $input.closest(".wcpt-option, .wcpt-dropdown-option"),
1942                $label = $input.closest("label");
1943
1944              $input.prop("checked", checked);
1945
1946              if ($option.length) {
1947                $option.toggleClass("wcpt-active", !!checked);
1948              }
1949              if ($label.length) {
1950                $label.toggleClass("wcpt-active", !!checked);
1951              }
1952            });
1953        });
1954      });
1955
1956    // Range / date text inputs (same class names across copies)
1957    [
1958      ".wcpt-range-input-min",
1959      ".wcpt-range-input-max",
1960      ".wcpt-filter-date-picker--start-date",
1961      ".wcpt-filter-date-picker--end-date",
1962    ].forEach(function (sel) {
1963      var $src_inputs = $source_filter.find(sel);
1964      if (!$src_inputs.length) {
1965        return;
1966      }
1967
1968      $src_inputs.each(function (i) {
1969        var val = $(this).val();
1970        $duplicates.each(function () {
1971          var $dup_input = $(this).find(sel).eq(i);
1972          if ($dup_input.length) {
1973            $dup_input.val(val);
1974          }
1975        });
1976      });
1977    });
1978  }
1979
1980  // apply nav filters
1981  $("body").on("change", ".wcpt-navigation", navigation_change);
1982  function navigation_change(e) {
1983    var $target = $(e.target),
1984      $container = $target.closest(".wcpt"),
1985      // Header + left sidebar are nested under .wcpt-navigation-wrapper /
1986      // .wcpt-nav-main, so they are no longer siblings — collect all of them.
1987      $nav = $(
1988        "> .wcpt-navigation, > .wcpt-navigation-wrapper .wcpt-navigation",
1989        $container,
1990      );
1991
1992    // skip search filter options input
1993    if ($target.closest(".wcpt-search-filter-options").length) {
1994      return;
1995    }
1996
1997    // skip date pickers
1998    if ($target.closest(".wcpt-filter-date-picker").length) {
1999      return;
2000    }
2001
2002    // taxonomy hierarchy
2003    if ($target.closest(".wcpt-hierarchy").length) {
2004      var checked = $target.prop("checked");
2005
2006      // effect on child terms
2007      if ($target.hasClass("wcpt-hr-parent-term")) {
2008        var ct_selector = "input[type=checkbox], input[type=radio]",
2009          $child_terms = $target
2010            .closest("label")
2011            .siblings(".wcpt-hr-child-terms-wrapper")
2012            .find(ct_selector);
2013        $child_terms.prop("checked", false);
2014      }
2015
2016      // effect on parent terms
2017      var $ancestors = $target.parents(".wcpt-hr-child-terms-wrapper");
2018      if ($ancestors.length) {
2019        $ancestors.each(function () {
2020          var $parent_term = $(this)
2021            .siblings("label")
2022            .find(".wcpt-hr-parent-term");
2023          $parent_term.prop("checked", false);
2024        });
2025      }
2026    }
2027
2028    // range filter
2029    if ($target.closest(".wcpt-range-filter")) {
2030      // -- number and range inputs shouldn't cause filtering
2031      if (
2032        $target.hasClass("wcpt-range-input-min") ||
2033        $target.hasClass("wcpt-range-input-max") ||
2034        $target.hasClass("wcpt-range-slider")
2035      ) {
2036        // Still sync typed values onto duplicate filter copies
2037        var $range_source = $target.closest(".wcpt-filter");
2038        if ($range_source.length) {
2039          sync_duplicate_filters_from($range_source, $nav);
2040        }
2041        return;
2042      }
2043
2044      // apply selected radio range onto the range slider inputs
2045      if ($target.is("input[type=radio]")) {
2046        var min = $target.attr("data-wcpt-range-min") || "",
2047          max = $target.attr("data-wcpt-range-max") || "",
2048          $range_filter = $target.closest(".wcpt-range-filter"),
2049          $min = $range_filter.find(".wcpt-range-input-min"),
2050          $max = $range_filter.find(".wcpt-range-input-max"),
2051          $range_slider = $range_filter.find(".wcpt-range-slider.original");
2052
2053        $min.val(min);
2054        $max.val(max);
2055        if (!min) {
2056          min = $range_slider.attr("min");
2057        }
2058        if (!max) {
2059          max = $range_slider.attr("max");
2060        }
2061
2062        $range_slider.val(min + "," + max);
2063      }
2064    }
2065
2066    // Keep duplicate copies of the same filter in sync (e.g. header + sidebar).
2067    // Without this, serialize() still picks up a checked option from the other
2068    // copy after deselection, so the filter stays applied.
2069    var $source_filter = $target.closest(".wcpt-filter");
2070    if ($source_filter.length) {
2071      sync_duplicate_filters_from($source_filter, $nav);
2072    }
2073
2074    // return if current control doesn't trigger immediate filtering
2075    // -- search
2076    if ($target.closest(".wcpt-search").length) {
2077      return;
2078    }
2079
2080    // -- nav modal
2081    if ($target.closest(".wcpt-nav-modal").length) {
2082      return;
2083    }
2084
2085    // nav clone
2086    var table_id = $container.attr("id").substring(5),
2087      $nav_clone = $nav.clone();
2088
2089    nav_clone_operations($nav_clone);
2090
2091    // build query
2092    var query = $("<form>").append($nav_clone).serialize();
2093
2094    // include column sort
2095    if (!$(e.target).closest('[data-wcpt-filter="sort_by"]').length) {
2096      var $table = wcpt_get_container_original_table($container),
2097        $sortable_headings = $(".wcpt-heading.wcpt-sortable:visible", $table),
2098        $current_sort_col = $sortable_headings.filter(function () {
2099          return $(this).find(
2100            ".wcpt-sorting-icons.wcpt-sorting-asc, .wcpt-sorting-icons.wcpt-sorting-desc",
2101          ).length;
2102        });
2103      if ($current_sort_col.length) {
2104        var sort_id = $current_sort_col.attr("data-wcpt-sort-id"),
2105          order = $current_sort_col.find(".wcpt-sorting-icons.wcpt-sorting-asc")
2106            .length
2107            ? "ASC"
2108            : "DESC",
2109          $sorting_icons = $current_sort_col
2110            .find(".wcpt-sorting-icons")
2111            .first(),
2112          column_orderby = $sorting_icons.attr("data-wcpt-orderby") || "",
2113          column_meta_key = $sorting_icons.attr("data-wcpt-meta-key") || "",
2114          column_orderby_attribute =
2115            $sorting_icons.attr("data-wcpt-orderby-attribute") || "",
2116          column_orderby_taxonomy =
2117            $sorting_icons.attr("data-wcpt-orderby-taxonomy") || "";
2118        // Skip incomplete sort ids (e.g. "column_" with no index) — those
2119        // produce undefined orderby warnings on the server during filter AJAX.
2120        if (
2121          sort_id &&
2122          sort_id !== "column_" &&
2123          sort_id.indexOf("column_") === 0
2124        ) {
2125          // Prefer explicit heading criteria when available
2126          if (column_orderby) {
2127            sort_id = "column_heading";
2128          }
2129
2130          query +=
2131            "&" +
2132            table_id +
2133            "_orderby=" +
2134            sort_id +
2135            "&" +
2136            table_id +
2137            "_order=" +
2138            order;
2139
2140          if (column_orderby) {
2141            query +=
2142              "&" +
2143              table_id +
2144              "_column_orderby=" +
2145              encodeURIComponent(column_orderby);
2146            query +=
2147              "&" +
2148              table_id +
2149              "_column_meta_key=" +
2150              encodeURIComponent(column_meta_key);
2151            if (column_orderby_attribute) {
2152              query +=
2153                "&" +
2154                table_id +
2155                "_column_orderby_attribute=" +
2156                encodeURIComponent(column_orderby_attribute);
2157            }
2158            if (column_orderby_taxonomy) {
2159              query +=
2160                "&" +
2161                table_id +
2162                "_column_orderby_taxonomy=" +
2163                encodeURIComponent(column_orderby_taxonomy);
2164            }
2165          }
2166        }
2167      }
2168    }
2169
2170    nav_filter_feedback($nav);
2171
2172    var data = {
2173      callback: function () {
2174        attempt_ajax($container, query, false, "filter", e);
2175      },
2176      proceed: true,
2177      event: e,
2178    };
2179
2180    $container.trigger("wcpt_after_navigation_change", data);
2181
2182    if (data.proceed) {
2183      data.callback();
2184    }
2185  }
2186
2187  function nav_clone_operations($nav_clone) {
2188    var $reverse_check = $();
2189
2190    $("[data-wcpt-reverse-value]:not(:checked)", $nav_clone).each(function () {
2191      var $this = $(this);
2192      $this.attr("value", $this.attr("data-wcpt-reverse-value"));
2193      $this.prop("checked", "checked");
2194      $reverse_check = $reverse_check.add($this.clone());
2195    });
2196    $nav_clone = $nav_clone.add($reverse_check);
2197
2198    // get values from duplicate radio buttons and clean up the names
2199    $('input[type="radio"]', $nav_clone).each(function () {
2200      var $this = $(this),
2201        name = $this.attr("name");
2202
2203      if (-1 !== name.indexOf("--")) {
2204        var is_array = name.indexOf("[]");
2205        name = name.substr(0, name.indexOf("--")) + (is_array ? "[]" : "");
2206        $this.attr("name", name);
2207      }
2208    });
2209
2210    // remove redundant range values
2211    $nav_clone.find(".wcpt-range-options-wrapper").each(function () {
2212      var $this = $(this),
2213        $min_input = $this.find(".wcpt-range-input-min"),
2214        $max_input = $this.find(".wcpt-range-input-max"),
2215        $range_slider = $this.find(".wcpt-range-slider"),
2216        $filter = $this.closest(".wcpt-filter"),
2217        $selected_option = $filter.find("input[type=radio]:checked");
2218
2219      // enforce option selected by user
2220      if ($selected_option.length && $selected_option.attr("value")) {
2221        var $selected_option_min = $selected_option.attr("data-wcpt-range-min"),
2222          $selected_option_max = $selected_option.attr("data-wcpt-range-max");
2223
2224        $min_input.val($selected_option_min);
2225        $max_input.val($selected_option_max);
2226        return;
2227      }
2228
2229      var prev_min = $min_input.attr("value"),
2230        prev_max = $max_input.attr("value");
2231
2232      $min_input.each(function () {
2233        var $this = $(this),
2234          min = $this.attr("min"),
2235          current_value = $this.val();
2236        if (min == current_value) {
2237          $this.val(prev_min);
2238        }
2239      });
2240
2241      $max_input.each(function () {
2242        var $this = $(this),
2243          max = $this.attr("max"),
2244          current_value = $this.val();
2245        if (max == current_value) {
2246          $this.val(prev_max);
2247        }
2248      });
2249
2250      $range_slider.remove();
2251    });
2252  }
2253
2254  function nav_filter_feedback($nav) {
2255    // header nav
2256    $(".wcpt-filter", $nav.filter(".wcpt-header")).each(function () {
2257      var $this = $(this),
2258        filter = $this.attr("data-wcpt-filter"),
2259        $filter = $this.closest(".wcpt-filter"),
2260        format = $this.attr("data-wcpt-heading_format__op_selected"),
2261        radio =
2262          $this.find("input[type=radio]").length ||
2263          $this.hasClass("wcpt-range-filter"),
2264        checkbox = $this.find("input[type=checkbox]").length,
2265        $selected = $this.find("input[type=radio]:checked"),
2266        $checked = $this.find("input[type=checkbox]:checked"),
2267        checked_count = $checked.length,
2268        $active_count = $this.find(".wcpt-active-count"),
2269        radio_permit = false,
2270        label_append = "",
2271        $multi_range = $(".wcpt-range-options-wrapper", $filter),
2272        $multi_range__min = $(
2273          ".wcpt-range-options-wrapper .wcpt-range-input-min",
2274          $filter,
2275        ),
2276        $multi_range__max = $(
2277          ".wcpt-range-options-wrapper .wcpt-range-input-max",
2278          $filter,
2279        ),
2280        in_filter_grid = !!$this.closest(".wcpt-ratio-filter_grid").length;
2281
2282      // Skip options-row filters outside filter grid (counts aren't used there)
2283      if ($this.hasClass("wcpt-options-row") && !in_filter_grid) {
2284        return;
2285      }
2286
2287      if (
2288        -1 ==
2289        $.inArray(filter, [
2290          "custom_field",
2291          "attribute",
2292          "category",
2293          "taxonomy",
2294          "price_range",
2295          "rating",
2296          "sort_by",
2297          "results_per_page",
2298          "on_sale",
2299          "availability",
2300        ])
2301      ) {
2302        return;
2303      }
2304
2305      // mark active filters
2306      if (
2307        checked_count ||
2308        ($selected.val() &&
2309          !$selected.closest(".wcpt-default-option").length) || // sort by
2310        ($multi_range.length &&
2311          ($multi_range__min.val() != $multi_range__min.attr("min") ||
2312            $multi_range__max.val() != $multi_range__max.attr("max")))
2313      ) {
2314        $this.closest(".wcpt-filter").addClass("wcpt-filter--active");
2315      } else {
2316        $this.closest(".wcpt-filter").removeClass("wcpt-filter--active");
2317      }
2318
2319      // modify dropdown heading
2320
2321      // -- filter grid: radio filters show (0)/(1) count like checkboxes
2322      if (
2323        radio &&
2324        in_filter_grid &&
2325        filter !== "sort_by" &&
2326        filter !== "results_per_page"
2327      ) {
2328        $this.find(".wcpt-radio-op-selected__heading-append").remove();
2329        $active_count.remove();
2330
2331        var radio_count = 0;
2332        if ($selected.length && $selected.attr("value")) {
2333          // Non-"show all" option selected
2334          radio_count = 1;
2335        } else if (
2336          $multi_range.length &&
2337          ($multi_range__min.val() != $multi_range__min.attr("min") ||
2338            $multi_range__max.val() != $multi_range__max.attr("max"))
2339        ) {
2340          radio_count = 1;
2341        }
2342
2343        var radio_count_class =
2344          "wcpt-active-count wcpt-active-count--filter-grid";
2345        if (!radio_count) {
2346          radio_count_class += " wcpt-active-count--zero";
2347        }
2348
2349        $active_count = $(
2350          '<span class="' + radio_count_class + '">' + radio_count + "</span>",
2351        );
2352        $(
2353          ".wcpt-filter-heading .wcpt-dropdown-label, .wcpt-filter-heading .wcpt-options-heading",
2354          $this,
2355        ).after($active_count);
2356
2357        // -- radio append option label
2358      } else if (radio && format !== "only_heading") {
2359        $this.find(".wcpt-radio-op-selected__heading-append").remove();
2360
2361        if (
2362          !$selected.length ||
2363          !$selected.attr("value") // 'show all' op selected
2364        ) {
2365          $this.removeClass("wcpt-radio-op-selected");
2366        } else {
2367          // selected and has value
2368          $this.addClass("wcpt-radio-op-selected");
2369          label_append = $selected.next()[0].outerHTML;
2370          radio_permit = true;
2371        }
2372
2373        if (!$selected.length && filter == "price_range") {
2374          var min =
2375              wcpt_params.currency_symbol +
2376                $(".wcpt-range-input-min", $this).val() || 0,
2377            max =
2378              wcpt_params.currency_symbol +
2379                $(".wcpt-range-input-max", $this).val() || 0;
2380
2381          label_append = "<span>" + min + " - " + max + "<span>";
2382
2383          if (
2384            $(".wcpt-range-input-min", $this).val() !=
2385              $(".wcpt-range-input-min", $this).attr("min") ||
2386            $(".wcpt-range-input-max", $this).val() !=
2387              $(".wcpt-range-input-max", $this).attr("max")
2388          ) {
2389            $this.addClass("wcpt-radio-op-selected");
2390            radio_permit = true;
2391          }
2392        } else if (!$selected.length && filter == "custom_field") {
2393          var min = $(".wcpt-range-input-min", $this).val() || 0,
2394            max = $(".wcpt-range-input-max", $this).val() || 0;
2395
2396          label_append = "<span>" + min + " - " + max + "<span>";
2397
2398          if (
2399            min != $(".wcpt-range-input-min", $this).attr("min") ||
2400            max != $(".wcpt-range-input-max", $this).attr("max")
2401          ) {
2402            $this.addClass("wcpt-radio-op-selected");
2403            radio_permit = true;
2404          }
2405        } else if ($selected.length && filter == "sort_by") {
2406          var $icon = $this.find(".wcpt-dropdown-label > .wcpt-icon");
2407          if ($icon.length) {
2408            label_append =
2409              $icon[0].outerHTML + " " + $selected.next()[0].outerHTML;
2410
2411            $icon.remove();
2412          }
2413        }
2414
2415        if (radio_permit) {
2416          $this
2417            .find(".wcpt-dropdown-label")
2418            .append(
2419              '<div class="wcpt-radio-op-selected__heading-append">' +
2420                label_append +
2421                "</div>",
2422            );
2423        }
2424
2425        // -- checkbox append selected option count
2426      } else if (checkbox) {
2427        $active_count.remove();
2428
2429        // In filter grid rows, always show the count (including 0)
2430        // so empty checkbox dropdowns still read as (0).
2431        var always_show_count = in_filter_grid;
2432
2433        if (checked_count || always_show_count) {
2434          var active_count_class = "wcpt-active-count";
2435          var active_count_text = checked_count;
2436
2437          if (always_show_count) {
2438            active_count_class += " wcpt-active-count--filter-grid";
2439
2440            if (!checked_count) {
2441              active_count_class += " wcpt-active-count--zero";
2442            }
2443          }
2444
2445          $active_count = $(
2446            '<span class="' +
2447              active_count_class +
2448              '">' +
2449              active_count_text +
2450              "</span>",
2451          );
2452          $(
2453            ".wcpt-filter-heading .wcpt-dropdown-label, .wcpt-filter-heading .wcpt-options-heading",
2454            $this,
2455          ).after($active_count);
2456        }
2457      }
2458    });
2459  }
2460
2461  // submit range by enter
2462  $("body").on(
2463    "keyup",
2464    ".wcpt-range-input-min, .wcpt-range-input-max",
2465    function (e) {
2466      var $this = $(this),
2467        $filters = $this.closest(".wcpt-navigation"),
2468        code = e.keyCode ? e.keyCode : e.which;
2469
2470      if (code == 13) {
2471        var $filter = $this.closest(".wcpt-filter"),
2472          $container = $this.closest(".wcpt"),
2473          $nav = $(
2474            "> .wcpt-navigation, > .wcpt-navigation-wrapper .wcpt-navigation",
2475            $container,
2476          );
2477        if ($filter.length) {
2478          sync_duplicate_filters_from($filter, $nav);
2479        }
2480        $filters.trigger("change");
2481      }
2482    },
2483  );
2484
2485  // submit range
2486  $("body").on("click", ".wcpt-range-submit-button", function (e) {
2487    var $this = $(this),
2488      $range_wrapper = $this.closest(".wcpt-range-options-wrapper"),
2489      $min = $(".wcpt-range-input-min", $range_wrapper),
2490      $max = $(".wcpt-range-input-max", $range_wrapper),
2491      min_val = parseFloat($min.val()),
2492      max_val = parseFloat($max.val());
2493
2494    // ensure max stays greater than min
2495    if (max_val < min_val) {
2496      $max.val(min_val);
2497      $min.val(max_val);
2498    }
2499
2500    $this.trigger("change");
2501  });
2502
2503  // date picker
2504
2505  // -- submit
2506  $("body").on("click", ".wcpt-date-picker-submit-button", function (e) {
2507    var $this = $(this),
2508      $filter = $this.closest('[data-wcpt-filter="date_picker"]'),
2509      $start_date = $(".wcpt-filter-date-picker--start-date", $filter),
2510      $end_date = $(".wcpt-filter-date-picker--end-date", $filter),
2511      $filters = $this.closest(".wcpt-navigation"),
2512      $container = $this.closest(".wcpt"),
2513      $nav = $(
2514        "> .wcpt-navigation, > .wcpt-navigation-wrapper .wcpt-navigation",
2515        $container,
2516      );
2517
2518    if ($start_date.val() && $end_date.val()) {
2519      var start_date = new Date($start_date.val()),
2520        end_date = new Date($end_date.val());
2521
2522      if (start_date > end_date) {
2523        var start_date_val = $start_date.val(),
2524          end_date_val = $end_date.val();
2525
2526        $start_date.val(end_date_val);
2527        $end_date.val(start_date_val);
2528      }
2529    }
2530
2531    if ($filter.length) {
2532      sync_duplicate_filters_from($filter, $nav);
2533    }
2534    $filters.trigger("change");
2535  });
2536  // -- -- enter key
2537  $("body").on("keydown", ".wcpt-filter-date-picker", function (e) {
2538    if (e.keyCode === 13 || e.which === 13) {
2539      var $this = $(this),
2540        $filters = $this.closest(".wcpt-navigation"),
2541        $filter = $this.closest(".wcpt-filter"),
2542        $container = $this.closest(".wcpt"),
2543        $nav = $(
2544          "> .wcpt-navigation, > .wcpt-navigation-wrapper .wcpt-navigation",
2545          $container,
2546        );
2547
2548      if ($filter.length) {
2549        sync_duplicate_filters_from($filter, $nav);
2550      }
2551      $filters.trigger("change");
2552    }
2553  });
2554
2555  // -- reset
2556  $("body").on("click", ".wcpt-date-picker-reset-button", function (e) {
2557    e.preventDefault();
2558
2559    var $this = $(this),
2560      $filter = $this.closest('[data-wcpt-filter="date_picker"]'),
2561      $date_picker = $(".wcpt-filter-date-picker", $filter),
2562      $filters = $this.closest(".wcpt-navigation"),
2563      $container = $this.closest(".wcpt"),
2564      $nav = $(
2565        "> .wcpt-navigation, > .wcpt-navigation-wrapper .wcpt-navigation",
2566        $container,
2567      );
2568
2569    $date_picker.val("");
2570    if ($filter.length) {
2571      sync_duplicate_filters_from($filter, $nav);
2572    }
2573    $filters.trigger("change");
2574  });
2575
2576  // -- validate
2577  $("body").on("keyup", ".wcpt-filter-date-picker", function (e) {
2578    var $this = $(this);
2579    if (
2580      $this.val().length > 10 ||
2581      -1 == $.inArray($this.val()[0], ["1", "2"])
2582    ) {
2583      $this.css("border-color", "red");
2584    } else {
2585      $this.css("border-color", "");
2586    }
2587  });
2588
2589  // clear filter
2590  $("body").on("click", ".wcpt-clear-filter", function (e) {
2591    var $clear_filter = $(this),
2592      $target = $(e.target);
2593
2594    if ($target.closest(".wcpt-dropdown-menu")) {
2595      var $sub_option = $target.closest(".wcpt-dropdown-option");
2596    } else {
2597      $sub_option = false;
2598    }
2599
2600    var $container = $clear_filter.closest(".wcpt"),
2601      filter = $clear_filter.attr("data-wcpt-filter"),
2602      $navs = $(
2603        "> .wcpt-navigation, >
2603 .wcpt-navigation-wrapper .wcpt-navigation",
2604        $container,
2605      ),
2606      $inputs = $();
2607
2608    if (filter == "search") {
2609      var name = $clear_filter.attr("data-wcpt-search-name"),
2610        $inputs = $('.wcpt-search-input[name="' + name + '"]', $navs);
2611    } else if (
2612      filter == "attribute" ||
2613      filter == "category" ||
2614      filter == "taxonomy"
2615    ) {
2616      var taxonomy = $clear_filter.attr("data-wcpt-taxonomy"),
2617        term = $clear_filter.attr("data-wcpt-value"),
2618        $inputs = $navs
2619          .find(
2620            '.wcpt-filter[data-wcpt-filter="' +
2621              filter +
2622              '"][data-wcpt-taxonomy="' +
2623              taxonomy +
2624              '"]',
2625          )
2626          .find('input[value="' + term + '"]');
2627    } else if (filter == "custom_field") {
2628      var meta_key = $clear_filter.attr("data-wcpt-meta-key"),
2629        value = $clear_filter.attr("data-wcpt-value"),
2630        $filter = $navs.find(
2631          '.wcpt-filter[data-wcpt-filter="' +
2632            filter +
2633            '"][data-wcpt-meta-key="' +
2634            meta_key +
2635            '"]',
2636        );
2637
2638      if ($filter.hasClass("wcpt-range-filter")) {
2639        $inputs = $filter.find("input");
2640      } else {
2641        $inputs = $navs
2642          .find(
2643            '.wcpt-filter[data-wcpt-filter="' +
2644              filter +
2645              '"][data-wcpt-meta-key="' +
2646              meta_key +
2647              '"]',
2648          )
2649          .find('input[value="' + value + '"]');
2650      }
2651    } else if (filter == "price_range") {
2652      $inputs = $navs
2653        .find(
2654          '.wcpt-filter[data-wcpt-filter="custom_field"][data-wcpt-meta-key="_price"]',
2655        )
2656        .find("input");
2657    } else if (filter == "search") {
2658      $inputs = $navs.find(
2659        'input[type=search][data-wcpt-value="' +
2660          htmlentity($clear_filter.attr("data-wcpt-value")) +
2661          '"]',
2662      );
2663    } else if (filter == "rating") {
2664      $inputs = $navs
2665        .find('.wcpt-filter[data-wcpt-filter="rating"]')
2666        .find("input");
2667    } else if (filter == "date_picker") {
2668      var val = $clear_filter.attr("data-wcpt-value"),
2669        $inputs = $navs
2670          .find('.wcpt-filter[data-wcpt-filter="date_picker"]')
2671          .find('input[value="' + val + '"]');
2672    } else {
2673      var value = $clear_filter.attr("data-wcpt-value"),
2674        $inputs = $navs
2675          .find('.wcpt-filter[data-wcpt-filter="' + filter + '"]')
2676          .find('input[value="' + value + '"]');
2677    }
2678
2679    $inputs
2680      .filter(":input[type=checkbox], :input[type=radio]")
2681      .prop("checked", false)
2682      .closest("label.wcpt-active")
2683      .removeClass("wcpt-active");
2684
2685    $inputs
2686      .filter(
2687        ":input[type=text], :input[type=number], :input[type=search], :input[type=date]",
2688      )
2689      .val(""); // search and range input
2690
2691    $navs.first().trigger("change");
2692
2693    // remove clear filter
2694    if (!$clear_filter.siblings(".wcpt-clear-filter").length) {
2695      $clear_filter.closest(".wcpt-clear-filters-wrapper").remove();
2696    } else {
2697      $clear_filter.remove();
2698    }
2699  });
2700
2701  // clear all filters
2702  $("body").on(
2703    "click",
2704    ".wcpt-clear-filters, .wcpt-clear-all-filters, .wcpt-reset",
2705    function (e) {
2706      e.preventDefault();
2707      var $this = $(this),
2708        $container = $this.closest(".wcpt"),
2709        query = "",
2710        append = false,
2711        purpose = "filter";
2712
2713      if (!$this.hasClass("wcpt-disabled")) {
2714        attempt_ajax($container, query, append, purpose);
2715      }
2716    },
2717  );
2718
2719  // sort by column heading
2720  window.wcpt_column_heading_sort_handler = function (e) {
2721    var $this = $(this),
2722      $sorting = $this.find(".wcpt-sorting-icons");
2723
2724    if ($(e.target).closest(".wcpt-tooltip").length) {
2725      return;
2726    }
2727
2728    if (!$sorting.length) {
2729      return;
2730    }
2731
2732    // maintain scroll left upon column header sorting
2733    if ($this.closest(".frzTbl").length) {
2734      var $container = $this.closest(".wcpt"),
2735        $scrollOverlay = $this
2736          .closest(".frzTbl-content-wrapper")
2737          .siblings(".frzTbl-scroll-overlay"),
2738        scrollLeft = $scrollOverlay[0].scrollLeft;
2739
2740      $("body").one(
2741        "after_freeze_table_build",
2742        "#" + $container.attr("id") + " .frzTbl-table",
2743        function (e, frzTbl) {
2744          frzTbl.el.$scrollOverlay[0].scrollLeft = scrollLeft;
2745        },
2746      );
2747    }
2748
2749    var order = $sorting.hasClass("wcpt-sorting-asc") ? "desc" : "asc",
2750      sort_id = $this.attr("data-wcpt-sort-id") || "",
2751      $container = $this.closest(".wcpt"),
2752      table_id = $container.attr("id").substring(5),
2753      device = get_device(),
2754      // Explicit criteria from the heading icons — preferred server-side
2755      // source of truth (does not rely on column_{index} resolution).
2756      column_orderby = $sorting.attr("data-wcpt-orderby") || "",
2757      column_meta_key = $sorting.attr("data-wcpt-meta-key") || "",
2758      column_orderby_attribute =
2759        $sorting.attr("data-wcpt-orderby-attribute") || "",
2760      column_orderby_taxonomy =
2761        $sorting.attr("data-wcpt-orderby-taxonomy") || "";
2762
2763    if ($(".wcpt-sorting-" + order + "-icon", $sorting).hasClass("wcpt-hide")) {
2764      if (
2765        $(".wcpt-sorting-" + order + "-icon", $sorting)
2766          .siblings()
2767          .hasClass("wcpt-active")
2768      ) {
2769        return;
2770      } else {
2771        order = order == "asc" ? "desc" : "asc";
2772      }
2773    }
2774
2775    // When the heading icons expose explicit sort criteria, use a stable
2776    // orderby token and send those criteria as the source of truth. This
2777    // avoids fragile column_{index} resolution (layout / original_index mismatch).
2778    if (column_orderby) {
2779      sort_id = "column_heading";
2780    }
2781
2782    if (!sort_id || sort_id === "column_") {
2783      return;
2784    }
2785
2786    var query =
2787      table_id +
2788      "_orderby=" +
2789      encodeURIComponent(sort_id) +
2790      "&" +
2791      table_id +
2792      "_order=" +
2793      order +
2794      "&" +
2795      table_id +
2796      "_device=" +
2797      device +
2798      "&" +
2799      table_id +
2800      "_paged=1";
2801
2802    if (column_orderby) {
2803      query +=
2804        "&" +
2805        table_id +
2806        "_column_orderby=" +
2807        encodeURIComponent(column_orderby);
2808      // Always send meta_key key (even empty) so server can clear stale keys
2809      // when switching away from a custom-field sort.
2810      query +=
2811        "&" +
2812        table_id +
2813        "_column_meta_key=" +
2814        encodeURIComponent(column_meta_key);
2815      if (column_orderby_attribute) {
2816        query +=
2817          "&" +
2818          table_id +
2819          "_column_orderby_attribute=" +
2820          encodeURIComponent(column_orderby_attribute);
2821      }
2822      if (column_orderby_taxonomy) {
2823        query +=
2824          "&" +
2825          table_id +
2826          "_column_orderby_taxonomy=" +
2827          encodeURIComponent(column_orderby_taxonomy);
2828      }
2829    }
2830
2831    attempt_ajax($container, query, true, "column_heading_sort");
2832  };
2833
2834  // ajax
2835  var attempt_ajax = (window.wcpt_attempt_ajax = (
2836    $container,
2837    new_query,
2838    append,
2839    purpose,
2840  ) => {
2841    const query = build_ajax_query_string(
2842      $container,
2843      new_query,
2844      append,
2845      purpose,
2846    );
2847
2848    // trigger body event to hook in
2849    var data = {
2850      query: query,
2851      $wcpt: $container,
2852      purpose: purpose,
2853      proceed: true,
2854    };
2855
2856    var prev_nav_query = $container.attr("data-wcpt-nav-query");
2857
2858    if (prev_nav_query === query) {
2859      data.proceed = false;
2860    }
2861
2862    $("body").trigger("wcpt_before_ajax_query", data);
2863
2864    $container.attr("data-wcpt-nav-query", data.query);
2865
2866    if (data.proceed) {
2867      fetch_markup_and_apply_callback(
2868        $container,
2869        data.query,
2870        purpose,
2871        ajax_success,
2872      );
2873    }
2874  });
2875
2876  function get_response_container(response) {
2877    var $response = $(response),
2878      $responseContainer = $response.filter(".wcpt").first();
2879
2880    if (!$responseContainer.length) {
2881      $responseContainer = $response.find(".wcpt").first();
2882    }
2883
2884    return $responseContainer.length ? $responseContainer : $response;
2885  }
2886
2887  function sync_container_attributes($container, $new_container) {
2888    if (!$new_container.length || !$new_container[0].attributes) {
2889      return;
2890    }
2891
2892    [...$new_container[0].attributes].forEach((attr) => {
2893      if (
2894        attr.nodeName === "class" ||
2895        attr.nodeName.substr(0, 10) === "data-wcpt-"
2896      ) {
2897        $container[0].setAttribute(attr.nodeName, attr.nodeValue);
2898      }
2899    });
2900  }
2901
2902  function replace_paginated_content($container, $new_container) {
2903    var $target_scope = $container.find(".wcpt-nav-main").first(),
2904      $source_scope = $new_container.find(".wcpt-nav-main").first(),
2905      content_selector = [
2906        ".wcpt-table-scroll-wrapper-outer",
2907        ".wcpt-pagination",
2908        ".wcpt-no-results",
2909        ".wcpt-in-footer",
2910        ".wcpt-infinite-scroll-dots",
2911        ".wcpt-add-selected-to-cart-with-pagination",
2912        ".wcpt-loading-screen",
2913      ].join(", "),
2914      previous_min_height,
2915      lock_height,
2916      $fragment;
2917
2918    if (!$target_scope.length) {
2919      $target_scope = $container;
2920    }
2921
2922    if (!$source_scope.length) {
2923      $source_scope = $new_container;
2924    }
2925
2926    // Keep height while swapping results so the page does not collapse and
2927    // jump the viewport to the top before the new table is inserted.
2928    previous_min_height = $target_scope[0].style.minHeight;
2929    lock_height = $target_scope.outerHeight();
2930    if (lock_height) {
2931      $target_scope.css("min-height", lock_height + "px");
2932    }
2933
2934    $fragment = $(document.createDocumentFragment());
2935    $source_scope.children().each(function () {
2936      var $child = $(this);
2937      if ($child.is(content_selector)) {
2938        $fragment.append($child.clone(true, true));
2939      }
2940    });
2941
2942    $target_scope.children(content_selector).remove();
2943    $target_scope.append($fragment);
2944    $target_scope.css("min-height", previous_min_height || "");
2945
2946    // Paginate-only responses keep the outer container; merge variable-switch
2947    // price/availability templates so variation resets can restore prices.
2948    if (
2949      typeof wcpt_util !== "undefined" &&
2950      wcpt_util.merge_variable_switch_templates_from_response
2951    ) {
2952      wcpt_util.merge_variable_switch_templates_from_response(
2953        $new_container,
2954        $container,
2955      );
2956    }
2957  }
2958
2959  // Header pagination lives inside preserved .wcpt-navigation, so update it
2960  // separately after the footer/table swap on paginate-only responses.
2961  function update_header_pagination($container, $new_container) {
2962    var $newPagination = $new_container
2963      .find(
2964        ".wcpt-add-selected-to-cart-with-pagination .wcpt-pagination, .wcpt-pagination",
2965      )
2966      .first();
2967
2968    if (!$newPagination.length) {
2969      return;
2970    }
2971
2972    $container.find(".wcpt-navigation .wcpt-pagination").each(function () {
2973      $(this).replaceWith($newPagination.clone(true, true));
2974    });
2975  }
2976
2977  function replace_result_count_placeholders(template, context) {
2978    if (typeof template !== "string") {
2979      return "";
2980    }
2981
2982    [
2983      ["displayed_results", context.displayed_results || 0],
2984      ["total_results", context.total_results || 0],
2985      ["first_result", context.first_result || 0],
2986      ["last_result", context.last_result || 0],
2987    ].forEach(function (entry) {
2988      var key = entry[0],
2989        value = String(entry[1]);
2990
2991      template = template
2992        .split("[" + key + "]")
2993        .join(value)
2994        .split("[wcpt_" + key + "]")
2995        .join(value);
2996    });
2997
2998    return template;
2999  }
3000
3001  function update_result_count($container) {
3002    var contextAttr = $container.attr("data-wcpt-result-count-context"),
3003      $resultCounts = $container.find(".wcpt-result-count");
3004
3005    if (!contextAttr || !$resultCounts.length) {
3006      return;
3007    }
3008
3009    var context = {};
3010    try {
3011      context = JSON.parse(contextAttr);
3012    } catch (e) {
3013      return;
3014    }
3015
3016    $resultCounts.each(function () {
3017      var $resultCount = $(this);
3018
3019      $resultCount.removeClass(
3020        "wcpt-single-result wcpt-no-results wcpt-single-page",
3021      );
3022
3023      if (context.html_class) {
3024        $resultCount.addClass(context.html_class);
3025      }
3026
3027      $resultCount
3028        .find(".wcpt-result-message")
3029        .text(
3030          replace_result_count_placeholders(
3031            $resultCount.attr("data-wcpt-message-template"),
3032            context,
3033          ),
3034        );
3035
3036      $resultCount
3037        .find(".wcpt-single-page-message")
3038        .text(
3039          replace_result_count_placeholders(
3040            $resultCount.attr("data-wcpt-single-page-template"),
3041            context,
3042          ),
3043        );
3044
3045      $resultCount
3046        .find(".wcpt-single-result-message")
3047        .text(
3048          replace_result_count_placeholders(
3049            $resultCount.attr("data-wcpt-single-result-template"),
3050            context,
3051          ),
3052        );
3053
3054      $resultCount
3055        .find(".wcpt-no-results-message")
3056        .text(
3057          replace_result_count_placeholders(
3058            $resultCount.attr("data-wcpt-no-results-template"),
3059            context,
3060          ),
3061        );
3062    });
3063  }
3064
3065  // ajax successful
3066  function ajax_success(response, $container, purpose) {
3067    $("body").trigger("wcpt_before_ajax_container_replace", {
3068      response: response,
3069      $container: $container,
3070    });
3071
3072    // replace $container inner html
3073    var $new_container = get_response_container(response);
3074    if (purpose === "paginate" && $container.find(".wcpt-navigation").length) {
3075      replace_paginated_content($container, $new_container);
3076      update_header_pagination($container, $new_container);
3077    } else {
3078      $container.html($new_container.html());
3079    }
3080
3081    sync_container_attributes($container, $new_container);
3082    update_result_count($container);
3083    $container.data("wcpt_last_ajax_purpose", purpose);
3084
3085    // scroll
3086    // -- get params
3087    var sc_attrs_string = $container.attr("data-wcpt-sc-attrs"),
3088      default_offset = 40,
3089      sc_attrs =
3090        sc_attrs_string && sc_attrs_string !== "{}"
3091          ? JSON.parse(sc_attrs_string)
3092          : {},
3093      offset = {
3094        laptop:
3095          typeof sc_attrs.laptop_scroll_offset == "undefined" ||
3096          sc_attrs.laptop_scroll_offset == ""
3097            ? default_offset
3098            : sc_attrs.laptop_scroll_offset,
3099        tablet:
3100          typeof sc_attrs.tablet_scroll_offset == "undefined" ||
3101          sc_attrs.tablet_scroll_offset == ""
3102            ? default_offset
3103            : sc_attrs.tablet_scroll_offset,
3104        phone:
3105          typeof sc_attrs.phone_scroll_offset == "undefined" ||
3106          sc_attrs.phone_scroll_offset == ""
3107            ? default_offset
3108            : sc_attrs.phone_scroll_offset,
3109      };
3110
3111    // -- do scroll
3112    var device = wcpt_get_device_2($container);
3113    if (
3114      (sc_attrs["auto_scroll"] === "true" ||
3115        sc_attrs[device + "_auto_scroll"] === "true") &&
3116      ["lazy_load", "device_switch", "refresh_table"].indexOf(purpose) === -1
3117    ) {
3118      var offset = offset[device];
3119
3120      if (isNaN(offset)) {
3121        if (typeof offset === "string") {
3122          // selector
3123          offset = $(offset).height();
3124        } else if (typeof offset === "object") {
3125          // jQuery object
3126          offset = offset.height();
3127        }
3128      }
3129
3130      var containerTop = $container.offset().top - parseFloat(offset);
3131      var windowTop = $(window).scrollTop();
3132      var windowBottom = windowTop + $(window).height();
3133
3134      if (containerTop < windowTop || containerTop > windowBottom) {
3135        $("html, body").animate(
3136          {
3137            scrollTop: Math.max(0, containerTop),
3138          },
3139          200,
3140        );
3141      }
3142    }
3143  }
3144
3145  var build_ajax_query_string = (window.wcpt_build_ajax_query_string = (
3146    $container,
3147    new_query,
3148    append,
3149    purpose,
3150  ) => {
3151    var table_id = $container.attr("data-wcpt-table-id");
3152
3153    if (typeof purpose == "undefined") {
3154      throw "WCPT: Define AJAX purpose";
3155    }
3156
3157    // combine earlier query
3158    var query = "",
3159      earlier_query = $container.attr("data-wcpt-query-string");
3160    if (append && earlier_query) {
3161      earlier_query = earlier_query.substring(1);
3162      query = "?";
3163
3164      $.each(
3165        $.extend(
3166          {},
3167          wcpt_util.parse_query_string(earlier_query),
3168          wcpt_util.parse_query_string(new_query),
3169        ),
3170        function (key, val) {
3171          if (val !== "undefined") {
3172            query += key + "=" + encodeURIComponent(val) + "&";
3173          }
3174        },
3175      );
3176      query = query.substring(0, query.length - 1);
3177    } else {
3178      query = "?" + new_query;
3179    }
3180
3181    // lazy load
3182    if (purpose == "lazy_load") {
3183      query += "&" + window.location.search.substr(1);
3184    }
3185
3186    // persist params
3187    var parsed_params = wcpt_util.parse_query_string(
3188      window.location.search.substring(1),
3189    );
3190    var query_obj = wcpt_util.parse_query_string(query.substring(1));
3191
3192    if (typeof window.wcpt_persist_params !== "undefined") {
3193      $.each(wcpt_persist_params, function (index, i) {
3194        if (
3195          parsed_params[i] !== "undefined" &&
3196          typeof parsed_params[i] !== "undefined" &&
3197          typeof query_obj[i] == "undefined"
3198        ) {
3199          query += "&" + i + "=" + parsed_params[i];
3200        }
3201      });
3202    }
3203
3204    // device
3205    var device = wcpt_get_device_2($container);
3206
3207    var query_obj = wcpt_util.parse_query_string(query);
3208    if (query_obj[table_id + "_device"] !== device) {
3209      query += "&" + table_id + "_device=" + device;
3210    }
3211
3212    // switching device
3213    if (purpose === "device_switch") {
3214      // remove column sort
3215      // first find out if column sort is present
3216      if (query.indexOf(table_id + "_orderby=column_") !== -1) {
3217        query = query.replace(
3218          new RegExp(table_id + "_orderby=column_\\d+", "g"),
3219          "",
3220        );
3221        query = query.replace(
3222          new RegExp(table_id + "_order=(ASC|DESC)", "g"),
3223          "",
3224        );
3225      }
3226    }
3227
3228    // shortcode attributes
3229    var sc_attrs = get_sc_attrs($container);
3230
3231    // search - orderby relevance
3232    var new_query_p = new_query ? wcpt_util.parse_query_string(new_query) : {},
3233      earlier_query_p = earlier_query
3234        ? wcpt_util.parse_query_string(earlier_query.substring(1))
3235        : {},
3236      search_orderby = sc_attrs.search_orderby ? "search_orderby" : "relevance",
3237      search_order = sc_attrs.search_order ? "search_order" : "";
3238
3239    // detect if a new search is taking place in this query
3240    $.each(new_query_p, function (key, val) {
3241      if (
3242        key.indexOf("search") !== -1 &&
3243        val &&
3244        earlier_query_p[key] !== val.replace(/\+/g, " ")
3245      ) {
3246        // reset search orderby and order
3247        query +=
3248          "&" +
3249          table_id +
3250          "_orderby=" +
3251          search_orderby +
3252          "&" +
3253          table_id +
3254          "_order=" +
3255          search_order;
3256        return false;
3257      }
3258    });
3259
3260    // flag table has been filtered
3261    if (purpose == "filter") {
3262      query += "&" + table_id + "_filtered=true";
3263    }
3264
3265    // coming from shop?
3266    if (parsed_params[table_id + "_from_shop"]) {
3267      query += "&" + table_id + "_from_shop=true";
3268    }
3269
3270    // add shortcode attributes
3271    if (!$.isEmptyObject(sc_attrs)) {
3272      // applying fix % conflict with % -> %25
3273      // query += '&' + table_id + '_sc_attrs=' + encodeURIComponent( _sc_attrs.replaceAll('%', '%25') );
3274      query +=
3275        "&" +
3276        table_id +
3277        "_sc_attrs=" +
3278        encodeURIComponent(JSON.stringify(sc_attrs));
3279    }
3280
3281    if (purpose === "paginate") {
3282      query += "&wcpt_paginate_only=1";
3283    }
3284
3285    return query;
3286  });
3287
3288  function get_sc_attrs($container) {
3289    var _sc_attrs = $container.attr("data-wcpt-sc-attrs"),
3290      sc_attrs = _sc_attrs && _sc_attrs !== "{}" ? JSON.parse(_sc_attrs) : {};
3291    return sc_attrs;
3292  }
3293
3294  function get_column_layout_source_map($container) {
3295    var raw = $container.attr("data-wcpt-column-layout-source-map");
3296    if (!raw || raw === "{}") return {};
3297    try {
3298      return JSON.parse(raw);
3299    } catch (e) {
3300      return {};
3301    }
3302  }
3303
3304  function get_layout_source_for_device(layoutMap, device) {
3305    if (!layoutMap || typeof layoutMap !== "object") return device;
3306    return layoutMap[device] ? layoutMap[device] : device;
3307  }
3308
3309  // get device based on device column container being displayed
3310  function wcpt_get_device_2($container) {
3311    var device = "laptop",
3312      // Single-device container system: device wrapper should always exist.
3313      // Avoid :visible here to prevent timing/CSS interactions causing
3314      // an incorrect "current device" and triggering unnecessary reloads.
3315      $scroll_outer = $container
3316        .find(".wcpt-table-scroll-wrapper-outer")
3317        .first();
3318
3319    if ($scroll_outer.length) {
3320      if ($scroll_outer.hasClass("wcpt-device-phone")) {
3321        device = "phone";
3322      } else if ($scroll_outer.hasClass("wcpt-device-tablet")) {
3323        device = "tablet";
3324      }
3325    } else if ($("body").hasClass("wcpt-nav-modal-on")) {
3326      device = $(".wcpt-nav-modal").attr("data-wcpt-device");
3327    } else if (
3328      $(
3329        ".wcpt-required-but-missing-nav-filter-message, .wcpt-no-results",
3330        $container,
3331      ).length
3332    ) {
3333      device = $(
3334        ".wcpt-required-but-missing-nav-filter-message, .wcpt-no-results",
3335        $container,
3336      ).attr("data-wcpt-device");
3337    }
3338
3339    return device;
3340  }
3341
3342  // request markup from sever and apply callback
3343  var fetch_markup_and_apply_callback =
3344    (window.wcpt_fetch_markup_and_apply_callback = (
3345      $container,
3346      query,
3347      purpose,
3348      callback,
3349    ) => {
3350      var table_id = $container.attr("data-wcpt-table-id"),
3351        sc_attrs = wcpt_util.get_sc_attrs($container);
3352
3353      // prep url
3354      var url =
3355          wcpt_params.wc_ajax_url.replace("%%endpoint%%", "wcpt_ajax") +
3356          "&" +
3357          query.slice(1),
3358        data = {
3359          wcpt_encrypted_attrs: wcpt_util.get_encrypted_attrs($container),
3360          id: table_id, // @TODO -- remove this
3361        };
3362
3363      // reload if ajax is disabled
3364      if (sc_attrs.disable_ajax && sc_attrs.disable_ajax !== "false") {
3365        var wcpt_redirect_params = {
3366          $container: $container,
3367          query: query,
3368          base_url: window.location.href.split("?")[0],
3369          purpose: purpose,
3370          proceed: true,
3371        };
3372        $("body").trigger("wcpt_before_nav_redirect", wcpt_redirect_params);
3373        if (wcpt_redirect_params.proceed) {
3374          window.location.href = wcpt_redirect_params.base_url + "?" + query;
3375        }
3376        return;
3377      }
3378
3379      // allow table from cache @TODO -- convert to query param
3380      var permit_cache =
3381        purpose !== "refresh_table" && typeof WavePlayer === "undefined";
3382
3383      if (
3384        // use cache
3385        permit_cache &&
3386        window.wcpt_cache.exist(query)
3387      ) {
3388        callback(window.wcpt_cache.get(query), $container, purpose);
3389        $container.data("wcpt_last_ajax_purpose", purpose);
3390        after_every_load($container);
3391        return;
3392      } else {
3393        // else AJAX
3394        $.ajax({
3395          url: url,
3396          method: "POST",
3397          beforeSend: function () {
3398            $container.addClass("wcpt-loading");
3399            $container.trigger("wcpt_before_ajax");
3400            return true;
3401          },
3402          data: data,
3403        }).done(function (response) {
3404          // success
3405          if (response && response.indexOf("wcpt-table") !== -1) {
3406            window.wcpt_cache.set(query, response); // update cache
3407            callback(window.wcpt_cache.get(query), $container, purpose);
3408            $container.data("wcpt_last_ajax_purpose", purpose);
3409            after_every_load($container);
3410
3411            // fail
3412          } else {
3413            console.log("wcpt notice: query fail");
3414          }
3415
3416          $container.removeClass("wcpt-loading");
3417        });
3418      }
3419    });
3420
3421  // variable product modal form
3422  //-- close modal
3423  $("body").on("click", ".wcpt-modal, .wcpt-close-modal", function (e) {
3424    var $target = $(e.target),
3425      $modal = $(this).closest(".wcpt-modal");
3426    if (
3427      $target.hasClass("wcpt-modal") ||
3428      $target.closest(".wcpt-close-modal").length
3429    ) {
3430      $modal.trigger("wcpt_close");
3431    }
3432  });
3433
3434  window.wcpt_update_cart_items = function (cart) {
3435    var cart_products = {},
3436      total = 0;
3437    $.each(cart, function (key, item) {
3438      if (!cart_products[item.product_id]) {
3439        cart_products[item.product_id] = 0;
3440      }
3441
3442      if (item.variation_id && !cart_products[item.variation_id]) {
3443        cart_products[item.variation_id] = 0;
3444      }
3445
3446      cart_products[item.product_id] += item.quantity;
3447
3448      if (item.variation_id) {
3449        cart_products[item.variation_id] += item.quantity;
3450      }
3451
3452      total += item.quantity;
3453    });
3454
3455    // -- update each product row
3456    $(".wcpt-row").each(function () {
3457      var $this = $(this),
3458        id = $this.attr("data-wcpt-variation-id")
3459          ? $this.attr("data-wcpt-variation-id")
3460          : $this.attr("data-wcpt-product-id"),
3461        qty = cart_products[id] ? cart_products[id] : 0,
3462        $badge = $this.find(".wcpt-cart-badge-number"),
3463        $remove = $this.find(".wcpt-remove");
3464
3465      $this.attr("data-wcpt-in-cart", qty);
3466
3467      if (qty) {
3468        add_count_badge_to_button(qty, $badge.closest(".wcpt-button"));
3469      } else {
3470        $badge.text("");
3471      }
3472    });
3473  };
3474
3475  // anchor tag
3476  $("body").on("click touchstart", "[data-wcpt-href]", function () {
3477    window.location = $(this).attr("data-wcpt-href");
3478  });
3479
3480  // accordion
3481  //-- filters
3482  // $('body').on('click', '.wcpt-left-sidebar .wcpt-filter > .wcpt-filter-heading', function(e){
3483  //   if( $(e.target).closest('.wcpt-tooltip').length ){
3484  //     return;
3485  //   }
3486
3487  //   var $this = $(this),
3488  //       $filter = $this.closest('.wcpt-filter');
3489  //   $filter.toggleClass('wcpt-filter-open wcpt-open');
3490  // })
3491  // //-- filter clicked directly outside of heading
3492  // $('body').on('click', '.wcpt-left-sidebar .wcpt-filter:not(.wcpt-filter-open)', function(e){
3493  //   var $this = $(this);
3494  //   if( e.target === this ){
3495  //     $this.addClass('wcpt-filter-open');
3496  //   }
3497  // })
3498  //-- taxonomy parent
3499  $("body").on(
3500    "click",
3501    ".wcpt-filter-hierarchy-accordion__trigger",
3502    function (e) {
3503      var $this = $(this);
3504      $this
3505        .closest(".wcpt-filter-hierarchy-accordion")
3506        .toggleClass("wcpt-filter-hierarchy-accordion--open");
3507      e.stopPropagation();
3508      return false;
3509    },
3510  );
3511
3512  // nav modal
3513  function nav_modal(e) {
3514    var $button = $(e.target).closest(".wcpt-rn-button"),
3515      modal_type = $button.attr("data-wcpt-modal"),
3516      $wcpt = $button.closest(".wcpt"),
3517      wcpt_id = $wcpt.attr("id"),
3518      $nav_modal = $($wcpt.find(".wcpt-nav-modal-tpl").html()),
3519      // Header + sidebar may both contain the same filters — keep one of each
3520      $filters = dedupe_nav_elements(
3521        $wcpt.find(".wcpt-filter").not('[data-wcpt-filter="sort_by"]'),
3522      ),
3523      $search = dedupe_nav_elements($wcpt.find(".wcpt-search-wrapper")),
3524      $sort = dedupe_nav_elements(
3525        $wcpt.find('[data-wcpt-filter="sort_by"].wcpt-filter'),
3526      ),
3527      radios = {};
3528
3529    $(".wcpt-nm-sort-placeholder", $nav_modal).replaceWith($sort.clone());
3530    $(".wcpt-nm-filters-placeholder", $nav_modal).replaceWith(
3531      $search.clone().add($filters.clone()),
3532    );
3533
3534    $(
3535      '.wcpt-nm-sort-placeholder [data-wcpt-filter="sort_by"]',
3536      $nav_modal,
3537    ).addClass("wcpt-open");
3538
3539    if (modal_type == "sort") {
3540      $nav_modal.addClass("wcpt-show-sort").removeClass("wcpt-show-filters");
3541    } else {
3542      // filter
3543      $nav_modal.addClass("wcpt-show-filters").removeClass("wcpt-show-sort");
3544    }
3545
3546    // record radios
3547    $wcpt.find("input[type=radio]:checked").each(function () {
3548      var $this = $(this);
3549      radios[$this.attr("name")] = $this.val();
3550    });
3551    $nav_modal.data("wcpt-radios", radios);
3552
3553    // ':' at the end of row labels
3554    $nav_modal
3555      .find(
3556        ".wcpt-filter.wcpt-options-row > .wcpt-filter-heading > .wcpt-options-heading > .wcpt-item-row > .wcpt-text:last-child",
3557      )
3558      .each(function () {
3559        var $this = $(this),
3560          text = $this.text().trim();
3561
3562        if (text.substr(-1) === ":") {
3563          $this.text(text.substr(0, text.length - 1));
3564        }
3565      });
3566
3567    // duplicate header
3568    // if( ! $nav_modal.find('.wcpt-nm-heading--sticky').length ){
3569    //   var $header = $nav_modal.find('.wcpt-nm-heading');
3570    //   $header.clone().addClass('wcpt-nm-heading--sticky').insertAfter( $header );
3571    // }
3572
3573    // switch option row to dropdown
3574    const switch_classes = {
3575      "wcpt-options-row": "wcpt-dropdown",
3576      "wcpt-option": "wcpt-dropdown-option",
3577      "wcpt-options-heading": "wcpt-dropdown-label",
3578      "wcpt-options": " wcpt-dropdown-menu",
3579    };
3580    $.each(switch_classes, function (key, value) {
3581      $nav_modal
3582        .find("." + key)
3583        .removeClass(key)
3584        .addClass(value);
3585    });
3586
3587    // append
3588    window.wcpt_nav_modal_scroll = window.scrollY;
3589
3590    $("body")
3591      .trigger("wcpt-nav-modal-on")
3592      .addClass("wcpt-nav-modal-on")
3593      .append('<div class="wcpt-nav-modal-backdrop"></div>')
3594      .append($nav_modal);
3595
3596    $(".wcpt-nav-modal-backdrop").one("click", function () {
3597      $nav_modal.trigger("wcpt_close");
3598    });
3599
3600    // open filter dropdown if its alone
3601    $other_filters = $nav_modal.find(
3602      '.wcpt-filter:not([data-wcpt-filter="sort_by"])',
3603    );
3604
3605    if ($other_filters.length === 1) {
3606      $other_filters.addClass("wcpt-filter-open wcpt-open");
3607    }
3608
3609    // multirange
3610    $(".wcpt-range-slider-wrapper", $nav_modal).each(function () {
3611      var $this = $(this),
3612        $original = $this.children(".original"),
3613        $ghost = $this.children(".ghost"),
3614        $new_slider = $("<input/>").attr({
3615          type: "range",
3616          class: "wcpt-range-slider",
3617          min: $original.attr("min"),
3618          max: $original.attr("max"),
3619          step: $original.attr("step"),
3620          value: $original.attr("data-wcpt-initial-value"),
3621        });
3622
3623      $original.add($ghost).remove();
3624
3625      $this.append($new_slider);
3626      wcpt__multirange($new_slider[0]);
3627    });
3628
3629    // apply
3630    //-- filter
3631    $nav_modal.find(".wcpt-nm-apply").on("click", function () {
3632      var $nav_clone = $nav_modal.clone();
3633
3634      nav_clone_operations($nav_clone);
3635
3636      var query = $("<form>").append($nav_clone).serialize(),
3637        $container = $("#" + wcpt_id);
3638
3639      $nav_modal.trigger("wcpt_close");
3640
3641      attempt_ajax($container, query, false, "filter");
3642    });
3643    //-- sort
3644    $nav_modal.filter(".wcpt-show-sort").on("change", function () {
3645      var $nav_clone = $nav_modal.clone();
3646
3647      nav_clone_operations($nav_clone);
3648
3649      var query = $("<form>").append($nav_clone).serialize(),
3650        $container = $("#" + wcpt_id);
3651
3652      $nav_modal.trigger("wcpt_close");
3653
3654      attempt_ajax($container, query, false, "filter");
3655    });
3656
3657    // clear
3658    $nav_modal.find(".wcpt-nm-reset").on("click", function () {
3659      var query = $("<form>").append($nav_modal.clone()).serialize(),
3660        $container = $("#" + wcpt_id),
3661        query = "";
3662
3663      $nav_modal.trigger("wcpt_close");
3664
3665      attempt_ajax($container, query, false, "filter");
3666    });
3667
3668    // close
3669    $nav_modal.find(".wcpt-nm-close").on("click", function (e) {
3670      e.preventDefault();
3671
3672      var $container = $("#" + wcpt_id),
3673        radios = $.extend({}, $nav_modal.data("wcpt-radios"));
3674
3675      $nav_modal.trigger("wcpt_close");
3676
3677      $.each(radios, function (name, val) {
3678        $wcpt
3679          .find('input[type=radio][name="' + name + '"][value="' + val + '"]')
3680          .each(function () {
3681            $(this).prop("checked", "checked");
3682          });
3683      });
3684    });
3685
3686    // scroll fix
3687    //  var prev_y = false;
3688    // $(".wcpt-nav-modal")
3689    //   .on("touchstart", function (e) {
3690    //     prev_y = e.originalEvent.touches[0].clientY;
3691    //   })
3692    //   .on("touchmove", function (e) {
3693    //     if (
3694    //       (e.originalEvent.touches[0].clientY > prev_y && !this.scrollTop) ||
3695    //       (e.originalEvent.touches[0].clientY < prev_y &&
3696    //         this.scrollTop === this.scrollHeight - this.offsetHeight)
3697    //     ) {
3698    //       e.preventDefault();
3699    //     }
3700    //   });
3701  }
3702
3703  $("body").on("wcpt_close", ".wcpt-nav-modal", function () {
3704    var $this = $(this),
3705      table_id = $this.attr("data-wcpt-table-id"),
3706      $container = $("#wcpt-" + table_id);
3707
3708    $("body").trigger("wcpt-nav-modal-off").removeClass("wcpt-nav-modal-on");
3709
3710    $(".wcpt-nav-modal-backdrop").remove();
3711    $this.remove();
3712    window.scroll(null, wcpt_nav_modal_scroll);
3713  });
3714
3715  // row option change ev
3716  $("body").on("change", ".wcpt-options-row .wcpt-option input", function () {
3717    var $this = $(this),
3718      $label = $this.closest(".wcpt-option ");
3719
3720    if ($this.is(":radio")) {
3721      if (this.checked) {
3722        $label.addClass("wcpt-active").siblings().removeClass("wcpt-active");
3723      }
3724    } else {
3725      // checkbox
3726      if (this.checked) {
3727        $label.addClass("wcpt-active");
3728      } else {
3729        $label.removeClass("wcpt-active");
3730      }
3731    }
3732  });
3733
3734  // toggle
3735  $("body").on("click", ".wcpt-tg-trigger", function () {
3736    var $this = $(this),
3737      $toggle = $this.closest(".wcpt-toggle"),
3738      $table = $this.closest(".wcpt-table");
3739
3740    $toggle.toggleClass(" wcpt-tg-on wcpt-tg-off ");
3741  });
3742
3743  $("body").on("click", ".wcpt-rn-filter, .wcpt-rn-sort", nav_modal);
3744
3745  // photoswipe
3746
3747  // -- open
3748  function init_photoswipe($this, index, append_item) {
3749    // photoswipe gallery
3750    if (
3751      typeof PhotoSwipe !== "undefined" &&
3752      typeof PhotoSwipeUI_Default !== "undefined"
3753    ) {
3754      var items = JSON.parse($this.attr("data-wcpt-photoswipe-items")),
3755        index = typeof index == "undefined" || !index ? 0 : parseInt(index),
3756        color_theme = $this.attr("data-wcpt-lightbox-color-theme") || "black";
3757
3758      // append items
3759      if (append_item) {
3760        items.push(append_item);
3761      }
3762
3763      var index_src = items[index].src;
3764
3765      // remove duplicates
3766      var unique_src = [],
3767        _items = [];
3768
3769      $.each(items, function (index2, item) {
3770        if (-1 === $.inArray(item.src, unique_src)) {
3771          _items.push(item);
3772          unique_src.push(item.src);
3773        }
3774      });
3775      items = _items;
3776
3777      var options = JSON.parse($this.attr("data-wcpt-photoswipe-options")),
3778        photoswipe = new PhotoSwipe(
3779          $(".pswp")[0],
3780          PhotoSwipeUI_Default,
3781          items,
3782          options,
3783        );
3784
3785      photoswipe.init();
3786
3787      // add wcpt-photoswipe class and trigger selector attr
3788      var color_theme_class = "wcpt-photoswipe--theme-" + color_theme;
3789      $(".pswp").addClass("wcpt-photoswipe " + color_theme_class);
3790
3791      // add wcpt class on photoswipe and trigger selector
3792
3793      // reposition index in case item was deleted
3794      $.each(items, function (index3, item) {
3795        if (item.src === index_src) {
3796          index = index3;
3797        }
3798      });
3799
3800      photoswipe.goTo(index);
3801
3802      var $body = $("body");
3803
3804      $body.addClass("wcpt-photoswipe-visible");
3805      photoswipe.listen("close", function () {
3806        setTimeout(function () {
3807          // fix photoswipe click through bug
3808          $body.removeClass("wcpt-photoswipe-visible");
3809        }, 10);
3810      });
3811
3812      // remove wcpt-photoswipe class and trigger selector attr after closing
3813      photoswipe.listen("destroy", function () {
3814        $(".pswp").removeClass("wcpt-photoswipe " + color_theme_class);
3815      });
3816
3817      $(photoswipe.container).data("wcpt_photoswipe", photoswipe);
3818
3819      return true;
3820    } else {
3821      return false;
3822    }
3823  }
3824
3825  // -- close
3826  $("body").on("click", ".pswp__container", function (e) {
3827    var $this = $(this),
3828      photoswipe = $this.data("wcpt_photoswipe");
3829
3830    if (window.innerWidth < 720 && photoswipe) {
3831      var $target = $(e.target);
3832      if (!$target.closest(".pswp__button").length) {
3833        photoswipe.close();
3834      }
3835    }
3836  });
3837
3838  // gallery strip
3839
3840  // -- image
3841  $("body").on("click", ".wcpt-gallery__item", function () {
3842    var $this = $(this),
3843      index = parseInt($this.attr("data-wcpt-gallery-item")),
3844      $gallery = $this.closest(".wcpt-gallery");
3845
3846    if (!$gallery.hasClass("wcpt-gallery--include-featured")) {
3847      index += 1;
3848    }
3849
3850    init_photoswipe($gallery, index);
3851  });
3852
3853  // -- link
3854  $("body").on("click", ".wcpt-gallery a", function (e) {
3855    e.preventDefault();
3856
3857    var $this = $(this),
3858      $gallery = $this.closest(".wcpt-gallery");
3859
3860    init_photoswipe($gallery);
3861  });
3862
3863  // image lightbox
3864  $("body").on("click", ".wcpt-lightbox-enabled", function () {
3865    destroy_offset_zoom_containers();
3866
3867    var $this = $(this);
3868    if (
3869      $this
3870        .closest(".wcpt")
3871        .hasClass("wcpt-quick-view-trigger--product-image") &&
3872      !$this
3873        .closest(".wcpt-row")
3874        .hasClass("wcpt-quick-view-trigger__disabled-for-product")
3875    ) {
3876      return;
3877    }
3878
3879    var index = 0,
3880      $row = get_product_rows($this),
3881      src = $this.attr("data-wcpt-lightbox"),
3882      pswp_items = JSON.parse($this.attr("data-wcpt-photoswipe-items")),
3883      append_item = false;
3884
3885    if (
3886      $row.attr("data-wcpt-type") === "variable" &&
3887      $row.data("wcpt_variation_selected")
3888    ) {
3889      var variation = $row.data("wcpt_variation"),
3890        src = variation.image.full_src,
3891        found = false;
3892
3893      $.each(pswp_items, function (_index, item) {
3894        if (item.src == src) {
3895          index = _index;
3896          found = true;
3897          return false;
3898        }
3899      });
3900
3901      if (!found) {
3902        append_item = {
3903          src: variation.image.full_src,
3904          w: variation.image.full_src_w,
3905          h: variation.image.full_src_h,
3906          title: variation.image.title,
3907        };
3908
3909        index = pswp_items.length;
3910      }
3911    } else {
3912      // get starting index
3913      if ($this.attr("data-wcpt-photoswipe-items")) {
3914        $.each(pswp_items, function (_index, item) {
3915          if (item.src == src) {
3916            index = _index;
3917            return false;
3918          }
3919        });
3920      }
3921    }
3922
3923    if (!init_photoswipe($this, index, append_item)) {
3924      var $el = $(
3925        '<div class="wcpt-lightbox-screen"><div class="wcpt-lightbox-loader"></div><div class="wcpt-lightbox-close"></div><img class="wcpt-lightbox-image" src="' +
3926          src +
3927          '"></div>',
3928      );
3929      $("body").append($el);
3930      $el.on("click ", function () {
3931        $el.remove();
3932      });
3933    }
3934  });
3935
3936  // image zoom
3937  //-- image hover
3938  $("body").on(
3939    "mouseenter",
3940    '.wcpt-zoom-enabled[data-wcpt-zoom-trigger="image_hover"]',
3941    function () {
3942      var $this = $(this),
3943        level = $this.attr("data-wcpt-zoom-level");
3944      if (!level) {
3945        level = "1.5";
3946      }
3947
3948      if ($this.closest(".wcpt-device-tablet, .wcpt-device-phone").length) {
3949        return;
3950      }
3951
3952      $this.css({
3953        transform: "scale(" + level + ")",
3954        "z-index": "2",
3955      });
3956
3957      $this.one("mouseleave", function () {
3958        $this.css({
3959          transform: "",
3960          "z-index": "",
3961        });
3962      });
3963    },
3964  );
3965  //-- row hover
3966  $("body").on("mouseenter", ".wcpt-row", function () {
3967    var $row = $(this);
3968    $row
3969      .find('.wcpt-zoom-enabled[data-wcpt-zoom-trigger="row_hover"]')
3970      .each(function () {
3971        var $zoom_me = $(this),
3972          level = $zoom_me.attr("data-wcpt-zoom-level");
3973        if (!level) {
3974          level = "1.5";
3975        }
3976
3977        if (
3978          $zoom_me.closest(".wcpt-device-tablet, .wcpt-device-phone").length
3979        ) {
3980          return;
3981        }
3982
3983        $zoom_me.css({
3984          transform: "scale(" + level + ")",
3985          "z-index": "2",
3986        });
3987
3988        $row.one("mouseleave", function () {
3989          $zoom_me.css({
3990            transform: "",
3991            "z-index": "",
3992          });
3993        });
3994      });
3995  });
3996
3997  // product image offset zoom
3998  // -- attach hover handler
3999  $("body").on(
4000    "mouseenter.wcpt_offset_zoom",
4001    ".wcpt-product-image-wrapper--offset-zoom-enabled, .wcpt-gallery--offset-zoom-enabled .wcpt-gallery__item-wrapper",
4002    function (e) {
4003      var $this = $(this),
4004        $img = $this.find("img").not(".wcpt-product-image-on-hover").first(),
4005        zoom_src = $this.attr("data-wcpt-offset-zoom-image-src"),
4006        $offset_zoom_img = $("<img>", {
4007          class: "wcpt-offset-zoom-container__image",
4008          src: zoom_src || $img.attr("src"),
4009        });
4010
4011      var $offset_zoom = $("<div>", {
4012          class:
4013            "wcpt-offset-zoom-container " +
4014            $this.attr("data-wcpt-offset-zoom-image-html-class"),
4015        }).append($offset_zoom_img),
4016        $wcpt = $this.closest(".wcpt"),
4017        original_title = $img.attr("title");
4018
4019      // Remove the title on mouseenter and store original in data attr
4020      $img.data("wcpt-original-title", original_title);
4021      $img.removeAttr("title");
4022
4023      if ($this.closest(".frzTbl--grab-and-scroll--grabbing").length) {
4024        return;
4025      }
4026
4027      destroy_offset_zoom_containers();
4028
4029      $wcpt.append($offset_zoom).on("mousemove.wcpt_offset_zoom", function (e) {
4030        position_offset_zoom_container(e, $offset_zoom, $this);
4031      });
4032
4033      setTimeout(function () {
4034        $offset_zoom.addClass("wcpt-offset-zoom-container--fade-in");
4035      }, 50);
4036
4037      $this.on("mouseleave", function () {
4038        // Restore the original title on mouseleave
4039        var $img = $this.find("img"),
4040          original_title = $img.data("wcpt-original-title");
4041        if (typeof original_title !== "undefined") {
4042          $img.attr("title", original_title);
4043        }
4044        destroy_offset_zoom_containers();
4045      });
4046    },
4047  );
4048  // -- turn off hover handler on touch screens
4049  $("body").on("touchstart", function () {
4050    $("body").off("mouseenter.wcpt_offset_zoom");
4051  });
4052
4053  function position_offset_zoom_container(e, $offset_zoom, $trigger) {
4054    var left = e.originalEvent.clientX + 40,
4055      top = e.originalEvent.clientY,
4056      position = "right";
4057
4058    $offset_zoom.css({
4059      left: left,
4060      top: top,
4061    });
4062
4063    var rect = $offset_zoom.get(0).getBoundingClientRect(),
4064      viewport_width =
4065        window.innerWidth || document.documentElement.clientWidth,
4066      viewport_height =
4067        window.innerHeight || document.documentElement.clientHeight;
4068
4069    if (rect.right > viewport_width) {
4070      position = "left";
4071    }
4072
4073    if (position == "left") {
4074      left = $trigger.get(0).getBoundingClientRect().left - 40 - rect.width;
4075    }
4076
4077    if (rect.top < 0) {
4078      top = 0 + 0.25 * rect.height;
4079    } else if (rect.bottom > viewport_height) {
4080      top -= rect.bottom - viewport_height;
4081    }
4082
4083    $offset_zoom.css({
4084      left: left,
4085      top: top,
4086    });
4087  }
4088
4089  function destroy_offset_zoom_containers() {
4090    $(".wcpt-offset-zoom-container").remove();
4091    $body.off("mousemove.wcpt_offset_zoom");
4092  }
4093
4094  // uncheck variation radio
4095  $("body").on("click", ".wcpt-variation-radio", function (e) {
4096    var $this = $(this),
4097      $variation = $this.closest(".wcpt-select-variation"),
4098      $row = $this.closest(".wcpt-row");
4099
4100    if (
4101      $variation.hasClass("wcpt-selected") &&
4102      window.navigator.userAgent.indexOf("Edge") == -1
4103    ) {
4104      $this.prop("checked", false);
4105      $this.change();
4106
4107      $row.trigger("select_variation", {
4108        variation_id: false,
4109        complete_match: false,
4110        attributes: false,
4111        variation: false,
4112        variation_found: false,
4113        variation_selected: false,
4114        variation_available: false,
4115      });
4116    }
4117  });
4118
4119  // variation selected class toggle
4120  $("body").on("change", ".wcpt-variation-radio", function () {
4121    var $this = $(this),
4122      $others = $(
4123        '.wcpt-variation-radio[name="' + $(this).attr("name") + '"]',
4124      ).not($(this)),
4125      $variation = $this.closest(".wcpt-select-variation");
4126
4127    if ($this.is(":checked")) {
4128      $variation.addClass("wcpt-selected");
4129    } else {
4130      $variation.removeClass("wcpt-selected");
4131    }
4132
4133    $others
4134      .not(":checked")
4135      .closest(".wcpt-select-variation")
4136      .removeClass("wcpt-selected");
4137  });
4138
4139  // select variation (main)
4140  //-- sync
4141  $("body").on(
4142    "select_variation",
4143    ".wcpt-product-type-variable",
4144    function (e, data) {
4145      var $row = get_product_rows($(this));
4146
4147      // update dropdown
4148      var $variation_dropdown = $row.find(".wcpt-select-variation-dropdown");
4149      $variation_dropdown.val(data.variation_id ? data.variation_id : "");
4150
4151      // update radio
4152      $row
4153        .find('.wcpt-variation-radio[value="' + data.variation_id + '"]')
4154        .prop("checked", true);
4155
4156      // update form
4157      $row.find(".variations_form").each(function () {
4158        var $this = $(this);
4159        current_variation_id =
4160          $(".variation_id", $this).val() === "0"
4161            ? ""
4162            : $(".variation_id", $this).val();
4163
4164        if (data.variation_id != current_variation_id) {
4165          window.wcpt_form_reset_flag = true;
4166          $(".reset_variations", $this).trigger("click.wc-variation-form");
4167          window.wcpt_form_reset_flag = false;
4168
4169          // select variation in form
4170          if (data.variation_id) {
4171            $(".variations select", $this).each(function () {
4172              var $this = $(this),
4173                name = $this.attr("name");
4174
4175              if (typeof data.attributes[name] !== "undefined") {
4176                $this.val(data.attributes[name]);
4177              } else {
4178                $this.val("");
4179              }
4180            });
4181
4182            $this.trigger("check_variations");
4183          }
4184        }
4185      });
4186
4187      // update row
4188      $row.data("wcpt_variation", data.variation);
4189      $row.data("wcpt_variation_id", data.variation_id);
4190      $row.data("wcpt_complete_match", data.complete_match);
4191      $row.data("wcpt_attributes", data.attributes);
4192      $row.data("wcpt_variation_found", data.variation_found);
4193      $row.data("wcpt_variation_selected", data.variation_selected);
4194      $row.data("wcpt_variation_available", data.variation_available);
4195      $row.data("wcpt_variation_qty", data.variation_qty);
4196
4197      // update total
4198      update_row_total($row);
4199      update_table_add_selected_to_cart.call($row.get(0));
4200
4201      // reset
4202      if (!data.variation_selected) {
4203        // -- add to cart buton
4204        var $button = $row.find('[data-wcpt-link-code^="cart"]');
4205
4206        // -- -- no variation selected
4207        if (
4208          $row.find(".wcpt-add-to-cart-wrapper").length || // cart form
4209          $variation_dropdown.length || // select variation -- dropdown
4210          $row.find(".wcpt-variation-radio").length // select variation -- radio
4211        ) {
4212          disable_button($button, "wcpt-no-variation-selected");
4213        }
4214
4215        // -- -- out of stock
4216        if ($row.hasClass("wcpt-all-variations-out-of-stock")) {
4217          disable_button($button, "wcpt-all-variations-out-of-stock");
4218        } else {
4219          enable_button($button, "wcpt-all-variations-out-of-stock");
4220        }
4221
4222        // -- checkbox (UI only — do not clear cross-page persisted checkbox state)
4223        $row.first().trigger("wcpt_checkbox_change", false);
4224
4225        // -- qty input
4226        var $qty_wrapper = $row.find(".wcpt-quantity"),
4227          $qty = $qty_wrapper.find("input[type=number].qty");
4228
4229        // -- -- disabled
4230        $qty_wrapper.addClass("wcpt-disabled");
4231        $qty.attr("disabled", "disabled");
4232
4233        if ($qty.length) {
4234          $qty.each(function () {
4235            var $this = $(this),
4236              inital_value = $this.attr("data-wcpt-initial-value"),
4237              value = "";
4238
4239            if (inital_value === "0") {
4240              value = 0;
4241            } else if (inital_value == "min") {
4242              // No variation selected → max is 0
4243              value = 0;
4244            } else {
4245              value = "";
4246            }
4247
4248            $this.attr({
4249              min: "0",
4250              max: "0",
4251              step: "",
4252              value: value,
4253            });
4254
4255            $this.val(value);
4256
4257            limit_qty_controller($this.closest(".wcpt-quantity"));
4258          });
4259        }
4260
4261        // -- product image
4262        var $product_image_wrapper = $(".wcpt-product-image-wrapper", $row),
4263          $product_image = $(
4264            ".wcpt-product-image-wrapper > img:not(.wcpt-product-image-on-hover)",
4265            $row,
4266          ),
4267          $original_row = wcpt_get_original_row($row);
4268
4269        if ($product_image_wrapper.length) {
4270          if (!$original_row.data("wcpt_default_image")) {
4271            if ($product_image[0]) {
4272              // lazy load fix
4273              $original_row.data(
4274                "wcpt_default_image",
4275                $product_image[0].outerHTML,
4276              );
4277            } else {
4278              handle_product_image_lazy_load($product_image_wrapper);
4279            }
4280          } else {
4281            $product_image.replaceWith(
4282              $original_row.data("wcpt_default_image"),
4283            );
4284          }
4285
4286          if ($product_image_wrapper.hasClass("wcpt-lightbox-enabled")) {
4287            if (!$product_image_wrapper.attr("data-wcpt-lightbox--original")) {
4288              $product_image_wrapper.attr(
4289                "data-wcpt-lightbox--original",
4290                $product_image_wrapper.attr("data-wcpt-lightbox"),
4291              );
4292            } else {
4293              $product_image_wrapper.attr(
4294                "data-wcpt-lightbox",
4295                $product_image_wrapper.attr("data-wcpt-lightbox--original"),
4296              );
4297            }
4298          }
4299
4300          if (
4301            $product_image_wrapper.hasClass(
4302              "wcpt-product-image-wrapper--offset-zoom-enabled",
4303            )
4304          ) {
4305            if (
4306              !$product_image_wrapper.attr(
4307                "data-wcpt-offset-zoom-image-src--original",
4308              )
4309            ) {
4310              $product_image_wrapper.attr(
4311                "data-wcpt-offset-zoom-image-src--original",
4312                $product_image_wrapper.attr("data-wcpt-offset-zoom-image-src"),
4313              );
4314            } else {
4315              $product_image_wrapper.attr(
4316                "data-wcpt-offset-zoom-image-src",
4317                $product_image_wrapper.attr(
4318                  "data-wcpt-offset-zoom-image-src--original",
4319                ),
4320              );
4321            }
4322          }
4323        }
4324
4325        // -- sku
4326        $row.find(".wcpt-sku").each(function () {
4327          var $sku = $(this),
4328            sku = $sku.attr("data-wcpt-sku");
4329          var $label = $sku.find(".wcpt-property-label");
4330          if ($label.length) {
4331            $sku.html($label.prop("outerHTML") + sku);
4332          } else {
4333            $sku.text(sku);
4334          }
4335        });
4336
4337        // -- gtin
4338        $row.find(".wcpt-gtin").each(function () {
4339          var $gtin = $(this),
4340            gtin = $gtin.attr("data-wcpt-gtin");
4341          $gtin.text(gtin); // revert to variable product gtin
4342        });
4343
4344        // -- product id
4345        $row.find(".wcpt-product-id").each(function () {
4346          var $product_id = $(this),
4347            product_id = $product_id.attr("data-wcpt-product-id");
4348          $product_id.text(product_id); // revert to variable product ID
4349        });
4350
4351        // -- price
4352
4353        // -- -- wcpt price element
4354        $row
4355          .filter(".wcpt-product-type-variable")
4356          .find(".wcpt-price.wcpt-variable-switch")
4357          .each(function () {
4358            var $this = $(this),
4359              id = $this.attr("data-wcpt-element-id"),
4360              tpl = $this.attr("data-wcpt-variable-template"),
4361              template_html = $(
4362                "[data-wcpt-element-id=" +
4363                  id +
4364                  "][data-wcpt-price-type=" +
4365                  tpl +
4366                  "]",
4367              ).html(),
4368              $html,
4369              o = [
4370                "highest-price",
4371                "lowest-price",
4372                "sale-price",
4373                "regular-price",
4374              ];
4375
4376            // No template yet (paginate / infinite scroll before merge) — keep
4377            // the server-rendered default price instead of wiping it blank.
4378            if (!template_html) {
4379              return;
4380            }
4381
4382            $html = $(template_html);
4383
4384            $.each(o, function (index, val) {
4385              $(".wcpt-" + val + " .wcpt-amount", $html).text(
4386                $this.attr("data-wcpt-" + val),
4387              );
4388            });
4389
4390            $this.html($html);
4391
4392            if (tpl == "sale") {
4393              $this.addClass("wcpt-product-on-sale");
4394            } else {
4395              $this.removeClass("wcpt-product-on-sale");
4396            }
4397          });
4398
4399        // -- -- default woocommerce template
4400        $row
4401          .filter(".wcpt-product-type-variable")
4402          .find(".wcpt-variable-price-default-woocommerce-template")
4403          .each(function () {
4404            var $this = $(this);
4405            $default = $(".wcpt-variable-switch__default", $this);
4406            $default.show().next(".price").remove();
4407          });
4408
4409        // -- -- price attribute
4410        $row.attr("data-wcpt-price", "");
4411
4412        // -- on sale
4413        $row
4414          .filter(".wcpt-product-type-variable")
4415          .find(".wcpt-on-sale.wcpt-variable-switch")
4416          .each(function () {
4417            var $this = $(this);
4418            $this.addClass("wcpt-hide");
4419          });
4420
4421        // -- availability
4422        $row
4423          .filter(".wcpt-product-type-variable")
4424          .find(".wcpt-availability.wcpt-variable-switch")
4425          .each(function () {
4426            var $this = $(this),
4427              id = $this.attr("data-wcpt-element-id"),
4428              stock = $this.attr("data-wcpt-stock"),
4429              message_tpl = $this.attr("data-wcpt-default-message_tpl"),
4430              stock_class = $this.attr("data-wcpt-default-stock_class"),
4431              message = $(
4432                "[data-wcpt-element-id=" +
4433                  id +
4434                  '][data-wcpt-availability-message="' +
4435                  message_tpl +
4436                  '"]',
4437              ).html(),
4438              $label = $this.find(".wcpt-property-label"),
4439              label_html = $label.length ? $label.prop("outerHTML") : "",
4440              $message = $($.parseHTML("<span>" + message + "</span>"));
4441
4442            $this.html(
4443              label_html +
4444                "<span class='" +
4445                stock_class +
4446                "'>" +
4447                $message
4448                  .find(".wcpt-stock-placeholder")
4449                  .text(stock)
4450                  .end()
4451                  .html() +
4452                "</span>",
4453            );
4454          });
4455
4456        // -- stock
4457        $row
4458          .filter(".wcpt-product-type-variable")
4459          .find(".wcpt-stock")
4460          .each(function () {
4461            var $this = $(this),
4462              stock = $this.attr("data-wcpt-stock"),
4463              rules = $this.attr("data-wcpt-stock-range-labels"),
4464              parsed_rules = !rules || rules == "{}" ? [] : JSON.parse(rules),
4465              $label = $this.find(".wcpt-property-label"),
4466              label_html = $label.length ? $label.prop("outerHTML") : "",
4467              label = stock;
4468
4469            var found_rule = false;
4470
4471            if (stock && parsed_rules.length) {
4472              $.each(parsed_rules, function (index, rule) {
4473                if (rule[0] <= stock && rule[1] >= stock) {
4474                  label = rule[2];
4475                  found_rule = true;
4476                }
4477              });
4478            }
4479
4480            if (!found_rule && stock < 0) {
4481              stock = "";
4482              label = "";
4483            }
4484
4485            $this.html(label_html + (label + "").replace("[stock]", stock));
4486          });
4487
4488        // -- dimensions
4489        $row
4490          .filter(".wcpt-product-type-variable")
4491          .find(".wcpt-dimensions")
4492          .each(function () {
4493            var $this = $(this);
4494        
4494    $this.html($this.attr("data-wcpt-default-dimensions"));
4495          });
4496
4497        // -- height
4498        $row
4499          .filter(".wcpt-product-type-variable")
4500          .find(".wcpt-height")
4501          .each(function () {
4502            var $this = $(this),
4503              template = $this.attr("data-wcpt-template"),
4504              empty_template = $this.attr("data-wcpt-empty-template"),
4505              height = $this.attr("data-wcpt-default-height");
4506
4507            if (!height) {
4508              $this.html(empty_template ? empty_template : "");
4509            } else {
4510              $this.html(
4511                template ? template.replace("{n}", height) : empty_template,
4512              );
4513            }
4514          });
4515
4516        // -- width
4517        $row
4518          .filter(".wcpt-product-type-variable")
4519          .find(".wcpt-width")
4520          .each(function () {
4521            var $this = $(this),
4522              template = $this.attr("data-wcpt-template"),
4523              empty_template = $this.attr("data-wcpt-empty-template"),
4524              width = $this.attr("data-wcpt-default-width");
4525
4526            if (!width) {
4527              $this.html(empty_template ? empty_template : "");
4528            } else {
4529              $this.html(template ? template.replace("{n}", width) : width);
4530            }
4531          });
4532
4533        // -- length
4534        $row
4535          .filter(".wcpt-product-type-variable")
4536          .find(".wcpt-length")
4537          .each(function () {
4538            var $this = $(this),
4539              template = $this.attr("data-wcpt-template"),
4540              empty_template = $this.attr("data-wcpt-empty-template"),
4541              length = $this.attr("data-wcpt-default-length");
4542
4543            if (!length) {
4544              $this.html(empty_template ? empty_template : "");
4545            } else {
4546              $this.html(template ? template.replace("{n}", length) : length);
4547            }
4548          });
4549
4550        // -- weight
4551        $row
4552          .filter(".wcpt-product-type-variable")
4553          .find(".wcpt-weight")
4554          .each(function () {
4555            var $this = $(this),
4556              template = $this.attr("data-wcpt-template"),
4557              empty_template = $this.attr("data-wcpt-empty-template"),
4558              weight = $this.attr("data-wcpt-default-weight");
4559
4560            if (!weight) {
4561              $this.html(empty_template ? empty_template : "");
4562            } else {
4563              $this.html(template ? template.replace("{n}", weight) : weight);
4564            }
4565          });
4566
4567        // -- custom field
4568        $row
4569          .filter(".wcpt-product-type-variable")
4570          .find(".wcpt-custom-field.wcpt-variable-switch")
4571          .each(function () {
4572            var $this = $(this),
4573              element_id = $this.attr("data-wcpt-element-id"),
4574              product_id = $this
4575                .closest(".wcpt-row")
4576                .attr("data-wcpt-product-id"),
4577              table_id = $this.closest(".wcpt").attr("data-wcpt-table-id"),
4578              $label = $this.find(".wcpt-property-label"),
4579              label_html = $label.length ? $label.prop("outerHTML") : "";
4580
4581            if (
4582              "undefined" !==
4583              typeof window["wcpt_" + table_id + "_variable_switch_cf"]
4584            ) {
4585              var cf_vals = window["wcpt_" + table_id + "_variable_switch_cf"];
4586              if ("undefined" !== typeof cf_vals[element_id]) {
4587                $this.html(label_html + cf_vals[element_id][product_id]);
4588              }
4589            }
4590          });
4591      }
4592
4593      if (!$.isEmptyObject(data.variation)) {
4594        // -- add to cart buton
4595        var $button = $row.find('[data-wcpt-link-code^="cart"]');
4596
4597        // -- -- no variation selected
4598        enable_button($button, "wcpt-no-variation-selected");
4599
4600        // -- -- out of stock
4601        if (data.variation.is_in_stock) {
4602          enable_button($button, "wcpt-variation-out-of-stock");
4603        } else {
4604          disable_button($button, "wcpt-variation-out-of-stock");
4605        }
4606
4607        // -- qty input
4608        var $qty_wrapper = $row.find(".wcpt-quantity"),
4609          $qty = $qty_wrapper.find("input[type=number].qty");
4610
4611        // -- -- disabled
4612        if (data.variation.is_in_stock) {
4613          $qty_wrapper.removeClass("wcpt-disabled");
4614          $qty.removeAttr("disabled");
4615        } else {
4616          $qty_wrapper.addClass("wcpt-disabled");
4617          $qty.attr("disabled", "disabled");
4618        }
4619
4620        if ($qty.length) {
4621          $qty.each(function () {
4622            var $this = $(this),
4623              $qty_wrapper = $this.closest(".wcpt-quantity"),
4624              inital_value = $this.attr("data-wcpt-initial-value"),
4625              min = data.variation.min_qty
4626                ? parseFloat(data.variation.min_qty)
4627                : 1,
4628              max = data.variation.max_qty
4629                ? parseFloat(data.variation.max_qty)
4630                : "",
4631              step = data.variation.step ? parseFloat(data.variation.step) : "",
4632              value = "";
4633
4634            // validate val
4635            var current_val = parseFloat($this.val());
4636            if (current_val && current_val !== NaN) {
4637              // neither empty, nor 0
4638              if (current_val < min) {
4639                value = min;
4640              } else if (max && current_val > max) {
4641                value = max;
4642              } else {
4643                value = current_val;
4644              }
4645            }
4646
4647            if (
4648              inital_value === "min" &&
4649              $this.attr("data-wcpt-reset-on-variation-change")
4650            ) {
4651              value = min;
4652            }
4653
4654            var wcpt_min = min;
4655
4656            if (inital_value === "0") {
4657              min = "0";
4658            }
4659
4660            $this.attr({
4661              value: value,
4662              min: min,
4663              "data-wcpt-min": wcpt_min,
4664              max: max,
4665              step: step,
4666            });
4667
4668            $this.val(value);
4669
4670            $.each(["min", "max", "step"], function (index, attr) {
4671              $qty_wrapper
4672                .find(".wcpt-quantity-error-placeholder--" + attr)
4673                .text($this.attr(attr));
4674            });
4675
4676            $this.trigger("change");
4677          });
4678        }
4679
4680        // -- qty select
4681        var $select = $row.find(".wcpt-quantity > select.wcpt-qty-select");
4682        if ($select.length) {
4683          // re-create select
4684          var qty_label = $select.attr("data-wcpt-qty-label"),
4685            max_qty = parseInt($select.attr("data-wcpt-max-qty")),
4686            val = data.variation.min_qty,
4687            options =
4688              '<option value="' +
4689              data.variation.min_qty +
4690              '" selected="selected">' +
4691              qty_label +
4692              data.variation.min_qty +
4693              "</option>";
4694          if (data.variation.max_qty) {
4695            max_qty = data.variation.max_qty;
4696          }
4697
4698          while (val < max_qty) {
4699            val += data.variation.step || 1;
4700            options += "<option>" + val + "</option>";
4701          }
4702          $select.html(options);
4703          $select.attr("min", data.variation.min_qty);
4704        }
4705
4706        // -- product image
4707        var $product_image_wrapper = $(".wcpt-product-image-wrapper", $row),
4708          $product_image = $(
4709            ".wcpt-product-image-wrapper img:not(.wcpt-product-image-on-hover)",
4710            $row,
4711          ),
4712          $original_row = wcpt_get_original_row($row);
4713
4714        if ($product_image[0]) {
4715          if (!$original_row.data("wcpt_default_image")) {
4716            $original_row.data(
4717              "wcpt_default_image",
4718              $product_image[0].outerHTML,
4719            );
4720          }
4721
4722          // replace variation image accoring to 'product image' element size settings
4723          if (data.variation.wcpt_image && data.variation.wcpt_image.src) {
4724            data.variation.image.src = data.variation.image.srcset =
4725              data.variation.wcpt_image.src;
4726          }
4727
4728          if (
4729            $product_image.length &&
4730            data.variation.image &&
4731            data.variation.image.src
4732          ) {
4733            $product_image.attr({
4734              src: data.variation.image.src,
4735              srcset: data.variation.image.srcset
4736                ? data.variation.image.srcset
4737                : "",
4738              sizes: data.variation.image.sizes,
4739            });
4740
4741            if ($product_image_wrapper.hasClass("wcpt-lightbox-enabled")) {
4742              if (
4743                !$product_image_wrapper.attr("data-wcpt-lightbox--original")
4744              ) {
4745                $product_image_wrapper.attr(
4746                  "data-wcpt-lightbox--original",
4747                  $product_image_wrapper.attr("data-wcpt-lightbox"),
4748                );
4749              }
4750
4751              $product_image_wrapper.attr(
4752                "data-wcpt-lightbox",
4753                data.variation.image.full_src,
4754              );
4755            }
4756
4757            if (
4758              $product_image_wrapper.hasClass(
4759                "wcpt-product-image-wrapper--offset-zoom-enabled",
4760              )
4761            ) {
4762              if (
4763                !$product_image_wrapper.attr(
4764                  "data-wcpt-offset-zoom-image-src--original",
4765                )
4766              ) {
4767                $product_image_wrapper.attr(
4768                  "data-wcpt-offset-zoom-image-src--original",
4769                  $product_image_wrapper.attr(
4770                    "data-wcpt-offset-zoom-image-src",
4771                  ),
4772                );
4773              }
4774
4775              $product_image_wrapper.attr(
4776                "data-wcpt-offset-zoom-image-src",
4777                data.variation.image.full_src,
4778              );
4779            }
4780          }
4781        } else {
4782          handle_product_image_lazy_load($product_image_wrapper);
4783        }
4784
4785        // -- sku
4786        if (data.variation.sku) {
4787          $row.find(".wcpt-sku").each(function () {
4788            var $this = $(this);
4789            if ($this.hasClass("wcpt-variable-switch")) {
4790              var $label = $this.find(".wcpt-property-label");
4791              if ($label.length) {
4792                $this.html($label.prop("outerHTML") + data.variation.sku);
4793              } else {
4794                $this.text(data.variation.sku);
4795              }
4796            }
4797          });
4798        }
4799
4800        // -- gtin
4801        if (data.variation.wcpt_gtin) {
4802          $row.find(".wcpt-gtin").each(function () {
4803            var $this = $(this);
4804            if ($this.hasClass("wcpt-variable-switch")) {
4805              $this.text(data.variation.wcpt_gtin);
4806            }
4807          });
4808        }
4809
4810        // -- product id
4811        if (data.variation.variation_id) {
4812          $row.find(".wcpt-product-id").each(function () {
4813            var $this = $(this);
4814            if ($this.hasClass("wcpt-variable-switch")) {
4815              $this.text(data.variation.variation_id);
4816            }
4817          });
4818        }
4819
4820        // -- price
4821
4822        // -- -- wcpt price element
4823        $row
4824          .filter(".wcpt-product-type-variable")
4825          .find(".wcpt-price.wcpt-variable-switch")
4826          .each(function () {
4827            var $this = $(this),
4828              id = $this.attr("data-wcpt-element-id"),
4829              tpl =
4830                parseFloat(data.variation.display_price) <
4831                parseFloat(data.variation.display_regular_price)
4832                  ? "sale"
4833                  : "regular",
4834              template_html = $(
4835                "[data-wcpt-element-id=" +
4836                  id +
4837                  "][data-wcpt-price-type=" +
4838                  tpl +
4839                  "]",
4840              ).html(),
4841              $html;
4842
4843            if (!template_html) {
4844              return;
4845            }
4846
4847            $html = $(template_html);
4848
4849            $html
4850              .find(".wcpt-regular-price .wcpt-amount")
4851              .text(data.variation.display_regular_price)
4852              .end()
4853              .find(".wcpt-sale-price .wcpt-amount")
4854              .text(data.variation.display_price);
4855            $this.html($html);
4856
4857            if (tpl == "sale") {
4858              $this.addClass("wcpt-product-on-sale");
4859            } else {
4860              $this.removeClass("wcpt-product-on-sale");
4861            }
4862          });
4863
4864        // -- -- default woocommerce template
4865        $row
4866          .filter(".wcpt-product-type-variable")
4867          .find(".wcpt-variable-price-default-woocommerce-template")
4868          .each(function () {
4869            var $this = $(this);
4870            $default = $(".wcpt-variable-switch__default", $this);
4871
4872            if (!data.variation.price_html) {
4873              // single variation won't have price html
4874              return;
4875            }
4876
4877            $default
4878              .hide()
4879              .nextAll(".price")
4880              .remove()
4881              .end()
4882              .after(data.variation.price_html);
4883          });
4884
4885        // -- -- price attribute
4886        $row.attr("data-wcpt-price", data.variation.display_price);
4887
4888        // -- on sale
4889        $row
4890          .filter(".wcpt-product-type-variable")
4891          .find(".wcpt-on-sale.wcpt-variable-switch")
4892          .each(function () {
4893            var $this = $(this),
4894              precision = $this.attr("data-wcpt-precision"),
4895              is_on_sale =
4896                parseFloat(data.variation.display_price) <
4897                parseFloat(data.variation.display_regular_price),
4898              $price_diff = $this.find(".wcpt-on-sale__price-diff"),
4899              $percent_diff = $this.find(".wcpt-on-sale__percent-diff");
4900
4901            if (is_on_sale) {
4902              $this.removeClass("wcpt-hide");
4903
4904              var price_diff =
4905                data.variation.display_regular_price -
4906                data.variation.display_price;
4907              var percent_diff = parseFloat(
4908                (
4909                  (price_diff / data.variation.display_regular_price) *
4910                  100
4911                ).toFixed(precision),
4912              );
4913
4914              $price_diff.html(format_price(price_diff));
4915              $percent_diff.text(percent_diff);
4916            } else {
4917              $this.addClass("wcpt-hide");
4918            }
4919          });
4920
4921        // -- availability
4922        $row
4923          .filter(".wcpt-product-type-variable")
4924          .find(".wcpt-availability.wcpt-variable-switch")
4925          .each(function () {
4926            var $this = $(this),
4927              id = $this.attr("data-wcpt-element-id"),
4928              message_tpl = "",
4929              stock_class = "",
4930              out_of_stock_message = $(
4931                "[data-wcpt-element-id=" +
4932                  id +
4933                  '][data-wcpt-availability-message="out_of_stock_message"]',
4934              ).html(),
4935              low_stock_message = $(
4936                "[data-wcpt-element-id=" +
4937                  id +
4938                  '][data-wcpt-availability-message="low_stock_message"]',
4939              ).html(),
4940              single_stock_message = $(
4941                "[data-wcpt-element-id=" +
4942                  id +
4943                  '][data-wcpt-availability-message="single_stock_message"]',
4944              ).html(),
4945              in_stock_message = $(
4946                "[data-wcpt-element-id=" +
4947                  id +
4948                  '][data-wcpt-availability-message="in_stock_message"]',
4949              ).html(),
4950              in_stock_managed_message = $(
4951                "[data-wcpt-element-id=" +
4952                  id +
4953                  '][data-wcpt-availability-message="in_stock_managed_message"]',
4954              ).html(),
4955              on_backorder_message = $(
4956                "[data-wcpt-element-id=" +
4957                  id +
4958                  '][data-wcpt-availability-message="on_backorder_message"]',
4959              ).html(),
4960              on_backorder_managed_message = $(
4961                "[data-wcpt-element-id=" +
4962                  id +
4963                  '][data-wcpt-availability-message="on_backorder_managed_message"]',
4964              ).html(),
4965              low_stock_threshold = $this.attr("data-wcpt-low_stock_threshold");
4966
4967            $this.show();
4968
4969            if (!data.variation.is_in_stock) {
4970              message_tpl = "out_of_stock_message";
4971              stock_class = "wcpt-out-of-stock";
4972            } else if (
4973              ((data.variation.managing_stock &&
4974                data.variation.is_on_backorder &&
4975                data.variation.backorders_require_notification) ||
4976                (!data.variation.managing_stock &&
4977                  data.variation.is_on_backorder)) &&
4978              on_backorder_message
4979            ) {
4980              message_tpl = "on_backorder_message";
4981              stock_class = "wcpt-on-backorder";
4982            } else if (data.variation.managing_stock) {
4983              // in stock, managed
4984              message_tpl = "in_stock_managed_message";
4985              stock_class = "wcpt-in-stock";
4986
4987              if (!data.variation.backorders_allowed) {
4988                if (
4989                  // low stock
4990                  data.variation.stock == 1 &&
4991                  single_stock_message
4992                ) {
4993                  message_tpl = "single_stock_message";
4994                  stock_class = "wcpt-low-stock";
4995                } else if (
4996                  // single stock
4997                  low_stock_message &&
4998                  low_stock_threshold &&
4999                  data.variation.stock <= low_stock_threshold
5000                ) {
5001                  message_tpl = "low_stock_message";
5002                  stock_class = "wcpt-low-stock";
5003                }
5004              } else if (
5005                data.variation.backorders_require_notification &&
5006                on_backorder_managed_message
5007              ) {
5008                // backorder allowed, managed stock, greater than 0
5009                message_tpl = "on_backorder_managed_message";
5010                stock_class = "wcpt-on-backorder";
5011              } else if (data.variation.stock <= 0) {
5012                // backorder allowed, managed stock, 0 or less
5013                message_tpl = "on_backorder_message";
5014                stock_class = "";
5015                $this.hide();
5016              } else {
5017                // backorder allowed, managed stock, greater than 0
5018                message_tpl = "in_stock_message";
5019                stock_class = "wcpt-in-stock";
5020              }
5021            } else {
5022              // in stock, not managed
5023              message_tpl = "";
5024              stock_class = "";
5025
5026              if (in_stock_message) {
5027                message_tpl = "in_stock_message";
5028                stock_class = "wcpt-in-stock";
5029              }
5030            }
5031
5032            const message_tpl_selector = `[data-wcpt-element-id="${id}"][data-wcpt-availability-message="${message_tpl}"]`;
5033            const $message = $(
5034              $.parseHTML(
5035                "<span>" + $(message_tpl_selector).html() + "</span>",
5036              ),
5037            );
5038            const $label = $this.find(".wcpt-property-label");
5039            const label_html = $label.length ? $label.prop("outerHTML") : "";
5040
5041            $this.html(
5042              label_html +
5043                "<span class='" +
5044                stock_class +
5045                "'>" +
5046                $message
5047                  .find(".wcpt-stock-placeholder")
5048                  .text(data.variation.stock ? data.variation.stock : "")
5049                  .end()
5050                  .html() +
5051                "</span>",
5052            );
5053          });
5054
5055        // -- stock
5056        $row
5057          .filter(".wcpt-product-type-variable")
5058          .find(".wcpt-stock.wcpt-variable-switch")
5059          .each(function () {
5060            var $this = $(this),
5061              stock = data.variation.stock,
5062              rules = $this.attr("data-wcpt-stock-range-labels"),
5063              parsed_rules = !rules || rules == "{}" ? [] : JSON.parse(rules),
5064              $label = $this.find(".wcpt-property-label"),
5065              label_html = $label.length ? $label.prop("outerHTML") : "",
5066              label = stock;
5067
5068            if (
5069              typeof label === "undefined" ||
5070              label === null ||
5071              label == NaN
5072            ) {
5073              $this.hide();
5074              return;
5075            } else {
5076              $this.show();
5077            }
5078
5079            var found_rule = false;
5080            if (stock && parsed_rules.length) {
5081              $.each(parsed_rules, function (index, rule) {
5082                if (rule[0] <= stock && rule[1] >= stock) {
5083                  label = rule[2];
5084                  found_rule = true;
5085                }
5086              });
5087            }
5088
5089            if (!found_rule && stock < 0) {
5090              stock = "";
5091              label = "";
5092            }
5093
5094            $this.html(label_html + (label + "").replace("[stock]", stock));
5095          });
5096
5097        // -- dimensions
5098        $row
5099          .filter(".wcpt-product-type-variable")
5100          .find(".wcpt-dimensions.wcpt-variable-switch")
5101          .each(function () {
5102            var $this = $(this);
5103            $this.html(data.variation.dimensions_html);
5104          });
5105
5106        // -- height
5107        $row
5108          .filter(".wcpt-product-type-variable")
5109          .find(".wcpt-height.wcpt-variable-switch")
5110          .each(function () {
5111            var $this = $(this),
5112              template = $this.attr("data-wcpt-template"),
5113              empty_template = $this.attr("data-wcpt-empty-template"),
5114              height = data.variation.dimensions.height;
5115
5116            if (!height) {
5117              $this.html(empty_template ? empty_template : "");
5118            } else {
5119              $this.html(
5120                template ? template.replace("{n}", height) : empty_template,
5121              );
5122            }
5123          });
5124
5125        // -- width
5126        $row
5127          .filter(".wcpt-product-type-variable")
5128          .find(".wcpt-width.wcpt-variable-switch")
5129          .each(function () {
5130            var $this = $(this),
5131              template = $this.attr("data-wcpt-template"),
5132              empty_template = $this.attr("data-wcpt-empty-template"),
5133              width = data.variation.dimensions.width;
5134
5135            if (!width) {
5136              $this.html(empty_template ? empty_template : "");
5137            } else {
5138              $this.html(template ? template.replace("{n}", width) : width);
5139            }
5140          });
5141
5142        // -- length
5143        $row
5144          .filter(".wcpt-product-type-variable")
5145          .find(".wcpt-length.wcpt-variable-switch")
5146          .each(function () {
5147            var $this = $(this),
5148              template = $this.attr("data-wcpt-template"),
5149              empty_template = $this.attr("data-wcpt-empty-template"),
5150              length = data.variation.dimensions.length;
5151
5152            if (!length) {
5153              $this.html(empty_template ? empty_template : "");
5154            } else {
5155              $this.html(template ? template.replace("{n}", length) : length);
5156            }
5157          });
5158
5159        // -- weight
5160        $row
5161          .filter(".wcpt-product-type-variable")
5162          .find(".wcpt-weight.wcpt-variable-switch")
5163          .each(function () {
5164            var $this = $(this),
5165              template = $this.attr("data-wcpt-template"),
5166              empty_template = $this.attr("data-wcpt-empty-template"),
5167              weight = data.variation.weight;
5168
5169            if (!weight) {
5170              $this.html(empty_template ? empty_template : "");
5171            } else {
5172              $this.html(template ? template.replace("{n}", weight) : weight);
5173            }
5174          });
5175
5176        // -- custom field
5177        $row
5178          .filter(".wcpt-product-type-variable")
5179          .find(".wcpt-custom-field.wcpt-variable-switch")
5180          .each(function () {
5181            var $this = $(this),
5182              element_id = $this.attr("data-wcpt-element-id"),
5183              table_id = $this.closest(".wcpt").attr("data-wcpt-table-id");
5184            (($label = $this.find(".wcpt-property-label")),
5185              (label_html = $label.length ? $label.prop("outerHTML") : ""));
5186
5187            if (
5188              "undefined" !==
5189              typeof window["wcpt_" + table_id + "_variable_switch_cf"]
5190            ) {
5191              var cf_vals = window["wcpt_" + table_id + "_variable_switch_cf"];
5192              if ("undefined" !== typeof cf_vals[element_id]) {
5193                $this.html(
5194                  label_html + cf_vals[element_id][data.variation.variation_id],
5195                );
5196              }
5197            }
5198          });
5199      }
5200    },
5201  );
5202
5203  //-- -- update from dropdown
5204  $("body").on("change", ".wcpt-select-variation-dropdown", function (e) {
5205    var $this = $(this),
5206      $selected = $this.find("option:selected"),
5207      $row = $this.closest(".wcpt-row");
5208
5209    $row.trigger("select_variation", {
5210      variation_id: $this.val(),
5211      complete_match: $selected.hasClass("wcpt-complete_match"),
5212      attributes: $selected.attr("data-wcpt-attributes")
5213        ? JSON.parse($selected.attr("data-wcpt-attributes"))
5214        : "",
5215      variation: $selected.attr("data-wcpt-variation")
5216        ? JSON.parse($selected.attr("data-wcpt-variation"))
5217        : "",
5218      variation_found: !!$selected.attr("value"),
5219      variation_selected: !!$selected.attr("value"),
5220      variation_available:
5221        !$selected.is(":disabled") && !!$selected.attr("value"),
5222    });
5223  });
5224
5225  //-- -- update from radio
5226  $("body").on("change", ".wcpt-variation-radio", function (e) {
5227    var $this = $(this),
5228      $wrapper = $this.closest(".wcpt-select-variation"),
5229      $row = $this.closest(".wcpt-row");
5230
5231    if ($this.is(":checked")) {
5232      $row.trigger("select_variation", {
5233        variation_id: $this.val(),
5234        complete_match: $wrapper.hasClass("wcpt-complete_match"),
5235        attributes: JSON.parse($wrapper.attr("data-wcpt-attributes")),
5236        variation: JSON.parse($wrapper.attr("data-wcpt-variation")),
5237        variation_found: true,
5238        variation_selected: true,
5239        variation_available: !$this.is(":disabled"),
5240      });
5241    }
5242  });
5243
5244  //-- -- update from form
5245  $("body").on(
5246    "woocommerce_variation_has_changed",
5247    ".wcpt-row .variations_form",
5248    function (e) {
5249      get_select_variation_from_cart_form($(this));
5250    },
5251  );
5252
5253  function get_select_variation_from_cart_form($form) {
5254    if (window.wcpt_form_reset_flag) {
5255      // avoid infinite loop
5256      return;
5257    }
5258
5259    var variations = JSON.parse($form.attr("data-product_variations")),
5260      $row = $form.closest(".wcpt-row"),
5261      $variation_id = $(".variation_id", $form),
5262      variation = {},
5263      attributes = {},
5264      selected_variation = $variation_id.val();
5265
5266    $.each(variations, function (index, value) {
5267      if (parseInt(value["variation_id"]) == selected_variation) {
5268        variation = value;
5269        return false;
5270      }
5271    });
5272
5273    var variation_selected = true;
5274    $(".variations select", $form).each(function () {
5275      var $this = $(this);
5276      attributes[$this.attr("name")] = $this.val();
5277
5278      if (!$this.val()) {
5279        variation_selected = false;
5280      }
5281    });
5282
5283    var variation_available = false;
5284    if (
5285      variation &&
5286      !$.isEmptyObject(variation) &&
5287      variation.is_purchasable &&
5288      variation.is_in_stock &&
5289      variation.variation_is_visible
5290    ) {
5291      variation_available = true;
5292    }
5293
5294    $row.trigger("select_variation", {
5295      variation: variation,
5296      variation_id: selected_variation,
5297      complete_match: true,
5298      attributes: attributes,
5299      variation_found: !!selected_variation,
5300      variation_selected: variation_selected,
5301      variation_available: variation_available,
5302    });
5303  }
5304
5305  // prepare variation options
5306  function prep_variation_options($new_rows) {
5307    function mark_row_prep_variation_complete($row) {
5308      $row.data("wcpt_prep_variation_complete", true);
5309      $row.trigger("wcpt_row_prep_variation_complete");
5310    }
5311
5312    function shouldSkipPrepVariationReset($row) {
5313      var productId = $row.attr("data-wcpt-product-id"),
5314        table_id =
5315          typeof wcpt_util !== "undefined"
5316            ? wcpt_util.get_table_id($row.closest(".wcpt"))
5317            : "";
5318
5319      return !!(
5320        productId &&
5321        table_id &&
5322        typeof window.wcpt_cart_checkbox_persist !== "undefined" &&
5323        typeof window.wcpt_cart_checkbox_persist.hasPersistedVariableProduct ===
5324          "function" &&
5325        window.wcpt_cart_checkbox_persist.hasPersistedVariableProduct(
5326          table_id,
5327          productId,
5328        )
5329      );
5330    }
5331
5332    $($new_rows)
5333      .filter(".wcpt-product-type-variable")
5334      .each(function () {
5335        var $row = $(this),
5336          $dropdown = $(".wcpt-select-variation-dropdown", $row),
5337          $radio = $(".wcpt-variation-radio", $row),
5338          $form = $(".variations_form", $row),
5339          $options = $dropdown.add($radio).add($form);
5340
5341        $row.data("wcpt_prep_variation_complete", false);
5342
5343        // flag availability of options in row
5344        if ($options.length) {
5345          $row.data("wcpt_variation_ops", true);
5346        }
5347
5348        // trigger 'select_variation'
5349
5350        // cart form element
5351        if ($form.length) {
5352          $form.each(function () {
5353            var $form = $(this),
5354              skipPrepReset = shouldSkipPrepVariationReset($row);
5355
5356            setTimeout(function () {
5357              // need to match setTimeout in add-to-cart-variation.js that delays form init
5358              // this init syncs the WC form with WCPT native controls including add-to-cart button
5359              if (!skipPrepReset) {
5360                $form.find("select").first().change();
5361              }
5362              mark_row_prep_variation_complete($row);
5363            }, 200);
5364          });
5365
5366          // select variation element
5367
5368          // -- dropdown
5369        } else if ($dropdown.length) {
5370          if (!shouldSkipPrepVariationReset($row)) {
5371            $dropdown.trigger("change");
5372          }
5373          mark_row_prep_variation_complete($row);
5374
5375          // -- radio
5376        } else if ($radio.length) {
5377          if (!shouldSkipPrepVariationReset($row)) {
5378            var $checked = $radio.filter(":checked");
5379            if ($checked.length) {
5380              $checked.trigger("change");
5381            } else {
5382              $row.trigger("select_variation", {
5383                variation_id: false,
5384                complete_match: false,
5385                attributes: false,
5386                variation: false,
5387                variation_found: false,
5388                variation_selected: false,
5389                variation_available: false,
5390              });
5391            }
5392          }
5393          mark_row_prep_variation_complete($row);
5394        } else {
5395          mark_row_prep_variation_complete($row);
5396        }
5397      });
5398  }
5399
5400  // lazy loaded product image
5401  function handle_product_image_lazy_load($product_image_wrapper) {
5402    if (
5403      $product_image_wrapper.length &&
5404      !$product_image_wrapper.hasClass("wcpt-awaiting-image-lazy-load")
5405    ) {
5406      $product_image_wrapper.addClass("wcpt-awaiting-image-lazy-load");
5407
5408      $product_image_wrapper[0].addEventListener(
5409        "load",
5410        function (event) {
5411          if (
5412            event.target.tagName === "IMG" &&
5413            $product_image_wrapper.hasClass("wcpt-awaiting-image-lazy-load")
5414          ) {
5415            $product_image_wrapper.removeClass("wcpt-awaiting-image-lazy-load");
5416
5417            var $row = wcpt_get_original_row(
5418                $product_image_wrapper.closest(".wcpt-row"),
5419              ),
5420              data = $row.data(),
5421              $product_image = $product_image_wrapper.find(".wp-post-image");
5422
5423            $row.data("wcpt_default_image", $product_image[0].outerHTML);
5424
5425            if (
5426              !$.isEmptyObject(data.wcpt_variation) &&
5427              $product_image.length &&
5428              data.wcpt_variation.image &&
5429              data.wcpt_variation.image.src
5430            ) {
5431              $product_image.attr({
5432                src: data.wcpt_variation.image.src,
5433                srcset: data.wcpt_variation.image.srcset
5434                  ? data.wcpt_variation.image.srcset
5435                  : "",
5436              });
5437
5438              if ($product_image_wrapper.hasClass("wcpt-lightbox-enabled")) {
5439                $product_image_wrapper.attr(
5440                  "data-wcpt-lightbox",
5441                  data.wcpt_variation.image.full_src,
5442                );
5443              }
5444
5445              if (
5446                $product_image_wrapper.hasClass(
5447                  "wcpt-product-image-wrapper--offset-zoom-enabled",
5448                )
5449              ) {
5450                $product_image_wrapper.attr(
5451                  "data-wcpt-offset-zoom-image-src",
5452                  data.variation.image.full_src,
5453                );
5454              }
5455            }
5456          }
5457        },
5458        true,
5459      );
5460    }
5461  }
5462
5463  if (window.innerWidth < 1200) {
5464    $("body").on("contextmenu", ".wcpt-noselect", function () {
5465      return false;
5466    });
5467  }
5468
5469  /**
5470   * Device interaction type detection
5471   * Sets appropriate mouse event types based on device capabilities
5472   */
5473  if ("ontouchstart" in document.documentElement) {
5474    var mousedown = "touchstart",
5475      mouseup = "touchend";
5476  } else {
5477    var mousedown = "mousedown",
5478      mouseup = "mouseup";
5479  }
5480
5481  /**
5482   * Quantity Controller Module
5483   *
5484   * This module handles quantity input functionality for WooCommerce product tables,
5485   * including touch screen support, increment/decrement controls, validation, and
5486   * cross-table synchronization.
5487   */
5488
5489  /**
5490   * Touch Screen Handler
5491   * Automatically moves cursor to end of input field on touch devices
5492   * for better user experience when editing quantities
5493   */
5494  $("body").on("touchend", ".wcpt-quantity .qty", function () {
5495    var $this = $(this),
5496      _ = this;
5497    if (!$this.is(":focus")) {
5498      $this.one("focus", function () {
5499        var val = _.value;
5500        _.value = "";
5501        setTimeout(function () {
5502          _.value = val;
5503        }, 1);
5504      });
5505    }
5506  });
5507
5508  /**
5509   * Quantity Controller Click Handler
5510   * Manages increment/decrement button interactions including:
5511   * - Initial value changes
5512   * - Continuous increment/decrement on hold
5513   * - Min/max boundary enforcement
5514   * - Touch device support
5515   */
5516  $("body").on("click", ".wcpt-qty-controller", handleQuantityChange);
5517  $("body").on("wcptPressing", ".wcpt-qty-controller", handleQuantityChange);
5518  $("body").on("dblclick", ".wcpt-qty-controller", function (e) {
5519    e.preventDefault();
5520  });
5521
5522  function handleQuantityChange(e) {
5523    var $this = $(this),
5524      $wrapper = $this.closest(".wcpt-quantity"),
5525      $qty = $wrapper.find(".qty"),
5526      visualMin = $qty.attr("min") ? $qty.attr("min") : 0,
5527      realMin = $qty.attr("data-wcpt-min")
5528        ? $qty.attr("data-wcpt-min")
5529        : visualMin,
5530      max = $qty.attr("max") ? $qty.attr("max") : false,
5531      step = $qty.attr("step") ? $qty.attr("step") : 1,
5532      val = $qty.val() > -1 ? $qty.val() : visualMin;
5533
5534    if ($wrapper.hasClass("wcpt-disabled")) {
5535      return;
5536    }
5537
5538    if ($this.hasClass("wcpt-disabled")) {
5539      return;
5540    }
5541
5542    if (step % 1 === 0) {
5543      visualMin = parseInt(visualMin);
5544      realMin = parseInt(realMin);
5545      max = parseInt(max);
5546      step = parseInt(step);
5547      val = parseInt(val);
5548    } else {
5549      visualMin = parseFloat(visualMin);
5550      realMin = parseFloat(realMin);
5551      max = parseFloat(max);
5552      step = parseFloat(step);
5553      val = parseFloat(val);
5554    }
5555
5556    if (isNaN(val)) {
5557      val = 0;
5558    }
5559
5560    // update quantity value
5561    if ($this.hasClass("wcpt-plus")) {
5562      var next = (val * 1e12 + step * 1e12) / 1e12;
5563      // If increasing from below realMin, jump to realMin
5564      if (val < realMin && next >= visualMin) {
5565        next = realMin;
5566      }
5567    } else {
5568      // wcpt-minus
5569      var next = (val * 1e12 - step * 1e12) / 1e12;
5570      // If decreasing from above or equal to realMin, drop straight to visualMin or 0
5571      if (val >= realMin && next < realMin) {
5572        next = visualMin > 0 ? 0 : visualMin;
5573      } else if (next < visualMin) {
5574        next = 0;
5575      }
5576    }
5577    $qty.val(next).change();
5578
5579    limit_qty_controller($wrapper);
5580  }
5581
5582  function startContinuousQuantityChange(e) {
5583    $("body").addClass("wcpt-noselect--qty-increment");
5584  }
5585
5586  function stopContinuousQuantityChange(e) {
5587    $("body").removeClass("wcpt-noselect--qty-increment");
5588  }
5589
5590  /**
5591   * Quantity Input Validator
5592   * Ensures quantity values respect:
5593   * - Minimum and maximum limits
5594   * - Step increments
5595   * - Initial value rules
5596   * @param {Event} e - Change event
5597   */
5598  $("body").on(
5599    "change",
5600    ".wcpt-quantity .qty",
5601    function qty_controller_validate(e) {
5602      var $this = $(this),
5603        min = $this.attr("min") ? parseFloat($this.attr("min")) : 0,
5604        max =
5605          $this.attr("max") !== undefined && $this.attr("max") !== ""
5606            ? parseFloat($this.attr("max"))
5607            : 1e12,
5608        initial = $this.attr("data-wcpt-initial-value"),
5609        step = $this.attr("step") ? parseFloat($this.attr("step")) : 1,
5610        raw = $this.val(),
5611        val = parseFloat(raw);
5612
5613      // Empty field only — do not treat 0 as empty (OOS uses 0 with max 0).
5614      if (raw === "" || isNaN(val)) {
5615        if (initial === "min") {
5616          $this.val(!isNaN(max) && max < min ? Math.max(0, max) : min);
5617        } else if (initial === "empty") {
5618          $this.val("");
5619        } else if (initial === "0") {
5620          $this.val("0");
5621        }
5622        val = parseFloat($this.val());
5623      }
5624
5625      // enforce min
5626      if (val && val < min) {
5627        $this.val(min);
5628        val = min;
5629      }
5630
5631      // enforce max
5632      if (!isNaN(val) && val > max) {
5633        $this.val(max);
5634        val = max;
5635      }
5636
5637      // enforce step
5638      if (val && (val * 1e12) % (step * 1e12)) {
5639        var _val = val * 1e12,
5640          _step = step * 1e12,
5641          new_val = (_val + _step - (val % step) * 1e12) / 1e12;
5642
5643        $this.val(new_val);
5644      }
5645
5646      limit_qty_controller($(this).parent());
5647    },
5648  );
5649
5650  /**
5651   * Enter Key Cart Addition Handler
5652   * Allows users to press Enter while focused on quantity input
5653   * to trigger the add-to-cart action for that product
5654   *
5655   * @param {KeyboardEvent} e - Keyboard event object
5656   */
5657  $("body").on("keypress", ".wcpt-quantity .qty", function (e) {
5658    if (e.keyCode == 13) {
5659      var $rows = get_product_rows($(this));
5660      $rows.find('.wcpt-button[data-wcpt-link-code^="cart"]').eq(0).click();
5661    }
5662  });
5663
5664  /**
5665   * Quantity Controller State Manager
5666   * Updates the enabled/disabled state of increment/decrement buttons based on:
5667   * - Current quantity value
5668   * - Minimum and maximum limits
5669   * - Step size
5670   * @param {jQuery} $qty_wrapper - The quantity input wrapper element
5671   */
5672  function limit_qty_controller($qty_wrapper) {
5673    $qty_wrapper.each(function () {
5674      var $this = $(this),
5675        $minus = $this.children(".wcpt-minus"),
5676        $plus = $this.children(".wcpt-plus"),
5677        $qty = $this.find(".qty");
5678
5679      if ($this.hasClass("wcpt-disabled")) {
5680        $minus.addClass("wcpt-disabled");
5681        $plus.addClass("wcpt-disabled");
5682        return;
5683      }
5684
5685      var initial = $qty.attr("data-wcpt-initial-value"),
5686        min = $qty.attr("min") ? parseFloat($qty.attr("min")) : 1,
5687        max = $qty.attr("max") ? parseFloat($qty.attr("max")) : false,
5688        step = $qty.attr("step") ? parseFloat($qty.attr("step")) : 1,
5689        val = parseFloat($qty.val());
5690
5691      if (!val || isNaN(val)) {
5692        val = 0;
5693      }
5694
5695      if (-1 !== $.inArray(initial, ["empty", "0"])) {
5696        min = 0;
5697      }
5698
5699      $minus.removeClass("wcpt-disabled");
5700      if (val - step < min) {
5701        $minus.addClass("wcpt-disabled");
5702      }
5703
5704      $plus.removeClass("wcpt-disabled");
5705      if (false !== max && val + step > max) {
5706        $plus.addClass("wcpt-disabled");
5707      }
5708    });
5709  }
5710
5711  /**
5712   * Initial Quantity Value Handler
5713   * Sets up initial quantity values after table load based on configuration:
5714   * - Can set to minimum value
5715   * - Can set to empty
5716   * - Can set to zero
5717   * - Triggers appropriate events for other components
5718   */
5719  $("body").on(
5720    "wcpt_after_every_load.wcpt_initial_qty_update",
5721    ".wcpt",
5722    function () {
5723      var $this__container = $(this),
5724        $target_qty_elms = $(
5725          '.wcpt-quantity input[type="number"].qty',
5726          wcpt_util.get_uninit_rows($this__container),
5727        );
5728
5729      $target_qty_elms.each(function () {
5730        var $this__input = $(this),
5731          initial_value = $this__input.attr("data-wcpt-initial-value");
5732
5733        if (initial_value === "0") {
5734          $this__input.val(0);
5735        } else if (initial_value === "empty") {
5736          $this__input.val("");
5737        } else if (initial_value === "min") {
5738          var min = $this__input.attr("min")
5739              ? parseFloat($this__input.attr("min"))
5740              : 1,
5741            maxAttr = $this__input.attr("max"),
5742            max =
5743              maxAttr !== undefined && maxAttr !== ""
5744                ? parseFloat(maxAttr)
5745                : NaN;
5746          // OOS: max is 0 — don't leave purchase min in the field (WC page-load
5747          // script will also re-bump value if min stays above max).
5748          if (!isNaN(max) && max < min) {
5749            $this__input.attr("min", Math.max(0, max));
5750            $this__input.val(Math.max(0, max));
5751          } else {
5752            $this__input.val(min);
5753          }
5754        }
5755
5756        $this__input
5757          .trigger("wcpt_updating_initial_quantity")
5758          .trigger("change")
5759          .trigger("wcpt_initial_quantity_updated");
5760      });
5761    },
5762  );
5763
5764  /**
5765   * Quantity Error Handler
5766   * Manages error states and visual feedback for:
5767   * - Values below minimum
5768   * - Values above maximum
5769   * - Invalid step increments
5770   * Updates associated cart buttons accordingly
5771   */
5772  $("body").on("keyup change", ".wcpt-quantity .qty", function (e) {
5773    var $this = $(this),
5774      max = $this.attr("max") ? parseFloat($this.attr("max")) : 1e9,
5775      min = $this.attr("min") ? parseFloat($this.attr("min")) : 1,
5776      step = $this.attr("step") ? parseFloat($this.attr("step")) : 1,
5777      val = isNaN(parseFloat($this.val())) ? 0 : parseFloat($this.val()),
5778      $wrapper = $this.closest(".wcpt-quantity");
5779
5780    $wrapper.removeClass(
5781      "wcpt-quantity-error wcpt-quantity-error--min wcpt-quantity-error--max wcpt-quantity-error--step",
5782    );
5783
5784    if (!val) {
5785      return;
5786    }
5787
5788    if (val < min) {
5789      $wrapper.addClass("wcpt-quantity-error wcpt-quantity-error--min");
5790    } else if (val > max) {
5791      $wrapper.addClass("wcpt-quantity-error wcpt-quantity-error--max");
5792    } else if ((val * 1e12) % (step * 1e12)) {
5793      $wrapper.addClass("wcpt-quantity-error wcpt-quantity-error--step");
5794    }
5795
5796    var $row = $this.closest(".wcpt-row"),
5797      $button = $row.find('.wcpt-button[data-wcpt-link-code^="cart_"]');
5798
5799    if ($wrapper.hasClass("wcpt-quantity-error")) {
5800      disable_button($button, "wcpt-quantity-input-error");
5801    } else {
5802      enable_button($button, "wcpt-quantity-input-error");
5803    }
5804
5805    limit_qty_controller($wrapper);
5806  });
5807
5808  /**
5809   * Cross-Table Quantity Synchronization Handler
5810   * Ensures quantity values remain synchronized across:
5811   * - Multiple instances of the same product in different tables
5812   * - WooCommerce default cart buttons
5813   * - Custom quantity select dropdowns
5814   *
5815   * Updates:
5816   * - All quantity inputs for the same product
5817   * - WooCommerce add-to-cart button data attributes
5818   *
5819   * @param {Event} e - Change event object
5820   */
5821  $("body").on(
5822    "change",
5823    ".wcpt-quantity input.qty, select.wcpt-qty-select",
5824    function (e) {
5825      var $input = $(this),
5826        $product_rows = get_product_rows($input),
5827        $siblings = $product_rows
5828          .find("input.qty, select.wcpt-qty-select")
5829          .not(this),
5830        val = $input.val();
5831
5832      $siblings.val(val);
5833
5834      if ($input.closest(".wcpt-add-to-cart-wrapper").length) {
5835        return;
5836      }
5837
5838      var $wc_default_button = $(".add_to_cart_button", $product_rows);
5839      $wc_default_button.data("quantity", val);
5840      $wc_default_button.attr("data-quantity", val);
5841    },
5842  );
5843
5844  /**
5845   * Quantity Input Keyboard Navigation Handler
5846   * Manages up/down arrow key interactions for quantity adjustments:
5847   * - ArrowUp: Increments value by step amount
5848   * - ArrowDown: Decrements value by step amount
5849   * - Respects min/max boundaries
5850   * - Handles special cases for real vs visual minimum values
5851   *
5852   * @param {KeyboardEvent} e - The keyboard event object
5853   */
5854  $("body").on("keydown", ".wcpt-quantity .qty", function (e) {
5855    if (e.key !== "ArrowUp" && e.key !== "ArrowDown") return;
5856
5857    var $qty = $(this);
5858    var visualMin = $qty.attr("min") ? parseFloat($qty.attr("min")) : 0;
5859    var realMin = $qty.attr("data-wcpt-min")
5860      ? parseFloat($qty.attr("data-wcpt-min"))
5861      : visualMin;
5862    var step = $qty.attr("step") ? parseFloat($qty.attr("step")) : 1;
5863    var currentVal = parseFloat($qty.val()) || 0;
5864
5865    // Calculate next value
5866    var next =
5867      e.key === "ArrowUp"
5868        ? (currentVal * 1e12 + step * 1e12) / 1e12
5869        : (currentVal * 1e12 - step * 1e12) / 1e12;
5870
5871    // Apply same logic as plus/minus buttons
5872    if (e.key === "ArrowUp") {
5873      if (currentVal < realMin && next >= visualMin) {
5874        next = realMin;
5875      }
5876    } else {
5877      // ArrowDown
5878      if (currentVal >= realMin && next < realMin) {
5879        next = visualMin > 0 ? 0 : visualMin;
5880      } else if (next < visualMin) {
5881        next = 0;
5882      }
5883    }
5884
5885    $qty.val(next).change();
5886    e.preventDefault();
5887  });
5888
5889  /**
5890   * Handles clicks on disabled cart-related buttons and quantity controls.
5891   * Shows appropriate error messages when users try to interact with:
5892   *
5893   * For variable products:
5894   * - Products missing variation selection
5895   * - Products with no matching variations
5896   * - Products with unavailable variations
5897   *
5898   * For simple products:
5899   * - Out of stock products
5900   *
5901   * @param {Event} event - The click event object
5902   */
5903  $("body").on(
5904    "click",
5905    ".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",
5906    function () {
5907      var $product_row = $(this).closest(".wcpt-row"),
5908        product_type = $product_row.attr("data-wcpt-type");
5909
5910      if (product_type === "variable") {
5911        var product_data = $product_row.data();
5912
5913        if (!product_data.wcpt_variation_selected) {
5914          alert(wcpt_i18n.i18n_make_a_selection_text);
5915          return;
5916        }
5917
5918        if (!product_data.wcpt_variation_found) {
5919          alert(wcpt_i18n.i18n_no_matching_variations_text);
5920          return;
5921        }
5922
5923        if (
5924          !product_data.wcpt_variation ||
5925          !product_data.wcpt_variation.is_in_stock
5926        ) {
5927          alert(wcpt_i18n.i18n_unavailable_variation_text);
5928          return;
5929        }
5930      } else if ($product_row.hasClass("wcpt-out-of-stock")) {
5931        alert(wcpt_i18n.i18n_out_of_stock_text);
5932      }
5933    },
5934  );
5935
5936  // total
5937  $("body").on("keyup mouseup change", ".wcpt-quantity .qty", function () {
5938    update_row_total($(this).closest(".wcpt-row"));
5939  });
5940
5941  // -- name your price: ensure qty if name your price is used
5942  $("body").on(
5943    "keyup change",
5944    ".wcpt .wcpt-name-your-price--input",
5945    function (e) {
5946      var $nyp = $(this),
5947        nyp_val = $nyp.val(),
5948        $rows = wcpt_get_sibling_rows($nyp.closest(".wcpt-row")),
5949        $qty = $(".wcpt-quantity input.qty", $rows),
5950        qty_val = $qty.val(),
5951        $cb = $(".wcpt-cart-checkbox ", $rows);
5952
5953      nyp_validate($nyp);
5954
5955      // added val - add qty
5956      if (nyp_val && !qty_val) {
5957        $qty.val($qty.attr("min"));
5958
5959        // removed val - remove qty
5960      } else if (!nyp_val && qty_val) {
5961        var inital_val_type = $qty.attr("data-wcpt-nyp-initial-value"),
5962          val = $qty.attr("min");
5963
5964        if (inital_val_type === "empty") {
5965          val = "";
5966        } else if (inital_val_type === "0") {
5967          val = "0";
5968        }
5969
5970        $qty.val(val);
5971      }
5972
5973      update_row_total($(this).closest(".wcpt-row"));
5974
5975      if ($qty.length) {
5976        $qty.change();
5977      } else if ($cb.length) {
5978        $rows.trigger("wcpt_checkbox_change", !!$nyp.val());
5979      }
5980    },
5981  );
5982
5983  function update_row_total($row, force_qty) {
5984    var $rows = $row,
5985      $qty = $rows.find(".wcpt-quantity .qty").eq(0),
5986      qty = $qty.length ? parseFloat($qty.val() ? $qty.val() : 0) : 1,
5987      $total = $(".wcpt-total", $rows),
5988      $cb = $(".wcpt-cart-checkbox ", $rows),
5989      prev_total = parseFloat($total.attr("data-wcpt-in-cart-total") || 0),
5990      price = $rows.attr("data-wcpt-price")
5991        ? parseFloat($rows.attr("data-wcpt-price"))
5992        : 0,
5993      total = 0;
5994
5995    if (force_qty) {
5996      qty = force_qty;
5997    }
5998
5999    // variable
6000    if ($rows.hasClass("wcpt-product-type-variable")) {
6001      if (
6002        $rows.data("wcpt_variation_found") &&
6003        $rows.data("wcpt_variation") &&
6004        $rows.data("wcpt_variation").display_price
6005      ) {
6006        price = unformat_price_figure(
6007          $rows.data("wcpt_variation").display_price,
6008        );
6009      } else {
6010        price = 0;
6011      }
6012    }
6013
6014    // name your price
6015    if ($row.hasClass("wcpt-product-has-name-your-price")) {
6016      var $nyp = $(".wcpt-name-your-price--input", $rows);
6017      if ($nyp.filter(":visible").length) {
6018        // variable product compatibility
6019        price = $nyp.val() ? parseFloat($nyp.val()) : 0;
6020      }
6021    }
6022
6023    total = (qty * price).toFixed(2);
6024
6025    // if checkbox is unchecked and qty is not min then no total
6026    if (
6027      $cb.length &&
6028      !$rows.data("wcpt_checked") &&
6029      $qty.attr("data-wcpt-initial-value") !== "min"
6030    ) {
6031      total = 0;
6032    }
6033
6034    $total.each(function () {
6035      var $this = $(this),
6036        _total = total;
6037
6038      if ($this.hasClass("wcpt-total--include-total-in-cart")) {
6039        _total = parseFloat(_total) + parseFloat(prev_total);
6040      }
6041
6042      if (parseFloat(_total)) {
6043        $this
6044          .removeClass("wcpt-total--empty")
6045          .find(".wcpt-amount")
6046          .text(format_price_figure(_total));
6047      } else {
6048        $this.addClass("wcpt-total--empty");
6049      }
6050    });
6051
6052    $row.data("wcpt-total", total);
6053
6054    $row.trigger("wcpt_total_updated");
6055  }
6056
6057  // audio player
6058  $("body").on("click", ".wcpt-player__button", function () {
6059    var $button = $(this),
6060      $container = $button.closest(".wcpt-player"),
6061      src = $container.attr("data-wcpt-src"),
6062      loop = $container.attr("data-wcpt-loop"),
6063      $el = $container.data("wcpt-media-el");
6064
6065    if (!$el) {
6066      $el = $('<audio class="wcpt-audio-elm" src="' + src + '"></audio>');
6067      $container.append($el);
6068      $container.data("wcpt-media-el", $el);
6069
6070      if (loop) {
6071        $el.prop("loop", true);
6072      } else {
6073        $el.on("ended", function () {
6074          $container.toggleClass("wcpt-player--playing");
6075        });
6076      }
6077    }
6078
6079    if ($button.hasClass("wcpt-player__play-button")) {
6080      $el[0].play();
6081
6082      if (!$container.hasClass("wcpt-media-loaded")) {
6083        $el.on("canplay", function () {
6084          $container.addClass("wcpt-media-loaded");
6085        });
6086      }
6087
6088      $("audio.wcpt-audio-elm")
6089        .not($el)
6090        .each(function () {
6091          this.currentTime = 0;
6092          this.pause();
6093        });
6094
6095      // pause others
6096      var $other_players = $(".wcpt-player.wcpt-player--playing").not(
6097        $container,
6098      );
6099      $other_players.find(".wcpt-player__playing-button").click();
6100    } else if ($button.hasClass("wcpt-player__pause-button")) {
6101      $el[0].pause();
6102    } else if ($button.hasClass("wcpt-player__stop-button")) {
6103      $el[0].pause();
6104      $el[0].currentTime = 0;
6105    }
6106
6107    $container.toggleClass("wcpt-player--playing");
6108  });
6109
6110  // term click
6111  // -- trigger filter
6112  $("body").on("click", ".wcpt-trigger_filter > [data-wcpt-slug]", function () {
6113    var $this = $(this),
6114      slug = $this.attr("data-wcpt-slug"),
6115      taxonomy = $this.parent().attr("data-wcpt-taxonomy"),
6116      $container = $this.closest(".wcpt"),
6117      $nav = $container.find(".wcpt-navigation"),
6118      $option = $nav.find(
6119        '[data-wcpt-taxonomy="' +
6120          taxonomy +
6121          '"] [data-wcpt-slug="' +
6122          slug +
6123          '"]',
6124      ),
6125      $input = $("input", $option);
6126
6127    if (!$option.length) {
6128      return;
6129    }
6130
6131    // $nav.addClass("wcpt-force-hide-dropdown-menus");
6132    $input.prop("checked", !$input.prop("checked"));
6133    $nav.first().trigger("change");
6134  });
6135  // -- archive redirect
6136  $("body").on(
6137    "click",
6138    ".wcpt-archive_redirect > [data-wcpt-slug]",
6139    function () {
6140      var $this = $(this),
6141        url = $this.attr("data-wcpt-archive-url");
6142      if (!$this.is("a")) {
6143        window.location = url;
6144      }
6145    },
6146  );
6147
6148  // remove
6149  $("body").on(
6150    "click",
6151    ".wcpt-row:not(.wcpt-removing-product) .wcpt-remove:not(.wcpt-disabled)",
6152    function () {
6153      var $this = $(this),
6154        $row = ($product_row = get_product_rows($this)),
6155        product_id = $row.attr("data-wcpt-product-id"),
6156        variation_id = $row.attr("data-wcpt-variation-id"),
6157        params = {
6158          payload: {
6159            products: {},
6160            variations: {},
6161            overwrite_cart_qty: true,
6162          },
6163        };
6164
6165      if ($this.hasClass("wcpt-refresh-enabled")) {
6166        params.redirect = window.location.href;
6167      }
6168
6169      params.payload.products[product_id] = 0;
6170
6171      // variation
6172      if (variation_id) {
6173        params.payload.variations[product_id] = {};
6174        params.payload.variations[product_id][variation_id] = 0;
6175      }
6176
6177      // variable product
6178      if ($row.hasClass("wcpt-product-type-variable")) {
6179        params.payload.variations[product_id] = $.extend(
6180          {},
6181          wcpt_cart_result_cache.in_cart[product_id],
6182        );
6183        $.each(
6184          params.payload.variations[product_id],
6185          function (variation_id, qty) {
6186            params.payload.variations[product_id][variation_id] = 0;
6187          },
6188        );
6189      }
6190
6191      wcpt_cart(params);
6192    },
6193  );
6194
6195  // toggle content (show more / less)
6196  $("body").on("click", ".wcpt-toggle-trigger", function (e) {
6197    e.preventDefault();
6198    var $this = $(this);
6199    $this.closest(".wcpt-toggle-enabled").toggleClass("wcpt-toggle");
6200  });
6201
6202  // freeze table selectors for originals
6203  function wcpt_get_container_original_table($container) {
6204    return $container.find(".wcpt-table:visible").not(".frzTbl-clone-table");
6205  }
6206
6207  // get sibling tables under freeze table
6208  function wcpt_get_sibling_tables($table) {
6209    var $freeze_table = $table.closest(".frzTbl");
6210    if (!$freeze_table.length) {
6211      return $table;
6212    }
6213
6214    return $(".wcpt-table", $freeze_table);
6215  }
6216
6217  // get sibling rows under freeze table
6218  window.wcpt_get_sibling_rows = function ($row) {
6219    var $freeze_table = $row.closest(".frzTbl");
6220    if (!$freeze_table.length) {
6221      return $row;
6222    }
6223
6224    var product_id = $row.attr("data-wcpt-product-id"),
6225      variation_id = $row.attr("data-wcpt-variation-id"),
6226      row_selector;
6227
6228    if (variation_id) {
6229      row_selector =
6230        '[data-wcpt-variation-id="' +
6231        variation_id +
6232        '"].wcpt-row.wcpt-product-type-variation';
6233    } else {
6234      row_selector =
6235        '[data-wcpt-product-id="' +
6236        product_id +
6237        '"].wcpt-row:not(.wcpt-product-type-variation)';
6238    }
6239
6240    return $(row_selector, $freeze_table);
6241  };
6242
6243  // get original row under freeze table
6244  function wcpt_get_original_row($row) {
6245    var $sibling_rows = wcpt_get_sibling_rows($row);
6246
6247    $sibling_rows.each(function () {
6248      var $row = $(this);
6249      if (!$row.closest("table").hasClass("frzTbl-clone-table")) {
6250        $original = $row;
6251        return false;
6252      }
6253    });
6254
6255    return $original;
6256  }
6257
6258  // unique key per row (variation first, then product)
6259  function wcpt_get_row_key($row) {
6260    var variationId = $row.data("wcpt_variation_id");
6261    if (variationId) {
6262      return "v:" + String(variationId);
6263    }
6264    var variationIdAttr = $row.attr("data-wcpt-variation-id");
6265    if (variationIdAttr) {
6266      return "v:" + String(variationIdAttr);
6267    }
6268    var productId = $row.attr("data-wcpt-product-id");
6269    if (productId) {
6270      return "p:" + String(productId);
6271    }
6272    return "";
6273  }
6274
6275  window.wcpt_get_original_row = wcpt_get_original_row;
6276  window.wcpt_get_row_key = wcpt_get_row_key;
6277
6278  // checkbox
6279
6280  // -- $cb 'change' handler -> triggers '_wcpt_checkbox_change' on $row
6281  $("body").on("change", ".wcpt-cart-checkbox", function (e) {
6282    var $this = $(this),
6283      $row = $this.closest(".wcpt-row");
6284
6285    $row.trigger("_wcpt_checkbox_change", $this.prop("checked"));
6286  });
6287
6288  // -- $row '_wcpt_checkbox_change' handler -> triggers 'wcpt_checkbox_change' on original $row, sets same state on sibling $cb
6289  $("body").on("_wcpt_checkbox_change", ".wcpt-row", function (e, state) {
6290    var $this = $(this),
6291      $original_row = wcpt_get_original_row($this),
6292      $table = $original_row.closest("table"),
6293      $sibling_rows = wcpt_get_sibling_rows($this),
6294      $sibling_cbs = $(".wcpt-cart-checkbox", $sibling_rows);
6295
6296    // unnecessary trigger
6297    if (!$sibling_cbs.length || !$sibling_cbs.not(":disabled").length) {
6298      return;
6299    }
6300
6301    // $row .data()-> state
6302    $original_row.data("wcpt_checked", state);
6303
6304    // $table .data()-> checked_rows
6305    var $table_checked_rows = $table.data("wcpt_checked_rows")
6306      ? $table.data("wcpt_checked_rows")
6307      : $();
6308
6309    if (state) {
6310      $table_checked_rows = $table_checked_rows.add($original_row);
6311    } else {
6312      $table_checked_rows = $table_checked_rows.not($original_row);
6313    }
6314
6315    $table.data("wcpt_checked_rows", $table_checked_rows);
6316
6317    // publish event
6318    $original_row.trigger("wcpt_checkbox_change", state);
6319  });
6320
6321  // -- 'wcpt_checkbox_change' handler -> set the same state on siblings rows' $cb and data on sibling rows
6322  $("body").on("wcpt_checkbox_change", ".wcpt-row", function (e, state) {
6323    var $this = $(this),
6324      $sibling_rows = wcpt_get_sibling_rows($this),
6325      $sibling_cbs = $(".wcpt-cart-checkbox", $sibling_rows);
6326
6327    $sibling_rows.data("wcpt_checked", state);
6328    $sibling_cbs.prop("checked", state);
6329  });
6330
6331  // -- row html class
6332  $("body").on("wcpt_checkbox_change", ".wcpt-row", function (e, state) {
6333    var $this = $(this),
6334      $original_row = wcpt_get_original_row($this),
6335      $sibling_rows = wcpt_get_sibling_rows($this),
6336      html_class = "wcpt-row--checked";
6337
6338    if ($original_row.data("wcpt_checked")) {
6339      $sibling_rows.addClass(html_class);
6340    } else {
6341      $sibling_rows.removeClass(html_class);
6342    }
6343  });
6344
6345  // -- select variation
6346  $("body").on("select_variation", ".wcpt-row", function (e) {
6347    var $this = $(this),
6348      selected_variation = $this.data("wcpt_variation"),
6349      $cb = $this.find(".wcpt-cart-checkbox");
6350
6351    // reset
6352    $cb.removeAttr("disabled");
6353    $cb.removeClass("wcpt-disabled");
6354
6355    if (
6356      !selected_variation ||
6357      $.isEmptyObject(selected_variation) ||
6358      (!selected_variation.is_in_stock && !selected_variation.is_on_backorder)
6359    ) {
6360      $this.trigger("wcpt_checkbox_change", false);
6361      $cb.attr("disabled", true);
6362      $cb.addClass("wcpt-disabled");
6363    }
6364  });
6365
6366  // -- qty
6367  // -- -- '.qty' change handler -> trigger '_wcpt_checkbox_change' on $row
6368  setTimeout(function () {
6369    // too many 3rd party modules trigger $qty at page load
6370    $("body").on(
6371      "change",
6372      ".wcpt input.qty, .wcpt select.wcpt-qty-select",
6373      function (e) {
6374        var $this = $(this),
6375          $row = $this.closest(".wcpt-row"),
6376          $original_row = wcpt_get_original_row($row);
6377
6378        if (
6379          $this.closest("form.cart").length ||
6380          // don't permit auto-check when initial-val is min
6381          ($this.attr("data-wcpt-initial-value") === "min" &&
6382            !$original_row.data("wcpt_checked"))
6383        ) {
6384          return;
6385        }
6386
6387        $original_row.trigger(
6388          "_wcpt_checkbox_change",
6389          !!parseFloat($this.val()),
6390        );
6391      },
6392    );
6393  }, 1);
6394
6395  // -- -- raise qty to min or reduce to initial qty upon 'wcpt_checkbox_change'
6396  $("body").on("wcpt_checkbox_change", ".wcpt-row", function (e, state) {
6397    var $this = $(this),
6398      $sibling_rows = wcpt_get_sibling_rows($this),
6399      $sibling_qty = $("input.qty, select.wcpt-qty-select", $sibling_rows),
6400      $sibling_qty_wrappers = $(".wcpt-quantity", $sibling_rows);
6401
6402    $sibling_qty.each(function () {
6403      var $this = $(this),
6404        val = $this.val(),
6405        min = $this.attr("data-wcpt-min")
6406          ? parseFloat($this.attr("data-wcpt-min"))
6407          : $this.attr("min"),
6408        initial_qty = $this.attr("data-wcpt-initial-value");
6409
6410      if (state) {
6411        if (!val || val === "0" || isNaN(val)) {
6412          val = min;
6413        }
6414      } else {
6415        if (initial_qty == "empty") {
6416          val = "";
6417        } else if (initial_qty == "0") {
6418          val = 0;
6419        } else {
6420          val = min;
6421        }
6422      }
6423
6424      $this.val(val);
6425    });
6426
6427    limit_qty_controller($sibling_qty_wrappers);
6428  });
6429
6430  // -- -- update total upon 'wcpt_checkbox_change'
6431  $("body").on("wcpt_checkbox_change", ".wcpt-row", function (e, state) {
6432    var $this = $(this),
6433      $sibling_rows = wcpt_get_sibling_rows($this);
6434
6435    update_row_total($sibling_rows);
6436  });
6437
6438  // -- 'wcpt_checkbox_change' handler -> create / update / hide '.wcpt-cart-checkbox-trigger' button
6439  $("body").on("wcpt_checkbox_change", ".wcpt-row", wcpt_checkbox_trigger_init);
6440
6441  // Also refresh the global trigger after each layout (e.g. AJAX pagination)
6442  $("body").on("wcpt_layout", ".wcpt", function () {
6443    wcpt_checkbox_trigger_init();
6444  });
6445
6446  function wcpt_checkbox_trigger_init() {
6447    // global floating checkbox trigger (across all tables on the page)
6448    // setup / show trigger
6449    var $checkbox_trigger = $(".wcpt-cart-checkbox-trigger");
6450
6451    if (!$checkbox_trigger.length) {
6452      var html = $("#tmpl-wcpt-cart-checkbox-trigger").html();
6453      $checkbox_trigger = $(html).appendTo("body");
6454    } else {
6455      $checkbox_trigger.removeClass("wcpt-hide");
6456    }
6457
6458    // aggregate selection across ALL tables on the page,
6459    // using the per-container cross-page state on each $wcpt
6460    var totalQty = 0,
6461      totalCost = 0;
6462
6463    $(".wcpt").each(function () {
6464      var $container = $(this),
6465        table_id =
6466          typeof wcpt_util !== "undefined"
6467            ? wcpt_util.get_table_id($container)
6468            : "",
6469        tableState = $container.data("wcpt_all_checked") || {};
6470
6471      if (
6472        table_id &&
6473        window.wcpt_cart_checked_state &&
6474        window.wcpt_cart_checked_state[table_id]
6475      ) {
6476        tableState = window.wcpt_cart_checked_state[table_id];
6477      }
6478
6479      if (!tableState || typeof tableState !== "object") {
6480        return;
6481      }
6482
6483      Object.keys(tableState).forEach(function (key) {
6484        var entry = tableState[key];
6485        if (!entry) {
6486          return;
6487        }
6488
6489        var q = !isNaN(parseFloat(entry.qty)) ? parseFloat(entry.qty) : 1,
6490          t = !isNaN(parseFloat(entry.total)) ? parseFloat(entry.total) : 0;
6491
6492        totalQty = (totalQty * 1e12 + q * 1e12) / 1e12;
6493        totalCost = (totalCost * 1e12 + t * 1e12) / 1e12;
6494      });
6495    });
6496
6497    if (!totalQty) {
6498      $checkbox_trigger.hide();
6499      return;
6500    }
6501
6502    $checkbox_trigger
6503      .data({
6504        wcpt_qty: totalQty,
6505        wcpt_cost: totalCost,
6506      })
6507      .find(".wcpt-total-selected")
6508      .text(totalQty)
6509      .end()
6510      .find(".wcpt-total-selected-cost")
6511      .text(format_price_figure(totalCost));
6512
6513    $checkbox_trigger
6514      .trigger("wcpt_checkbox_trigger_updating")
6515      .show()
6516      .trigger("wcpt_checkbox_trigger_updated");
6517  }
6518
6519  window.wcpt_checkbox_trigger_init = wcpt_checkbox_trigger_init;
6520
6521  // -- heading
6522  // -- -- check all $cb in table via $heading_cb '.wcpt-cart-checkbox-heading'
6523  $("body").on("click", ".wcpt-cart-checkbox-heading", function () {
6524    var $this = $(this),
6525      state = $this.prop("checked"),
6526      $container = $this.closest(".wcpt"),
6527      $table = wcpt_get_container_original_table($container),
6528      $rows = $(".wcpt-row", $table);
6529
6530    $rows.trigger("_wcpt_checkbox_change", state);
6531    $("wcpt-cart-checkbox--last-clicked", $table).removeClass(
6532      "wcpt-cart-checkbox--last-clicked",
6533    );
6534  });
6535
6536  // -- -- auto toggle heading checkbox
6537  $("body").on("wcpt_checkbox_change", ".wcpt-row", function (e, state) {
6538    // Debounce multiple calls
6539    clearTimeout(window.wcpt_cb_heading);
6540    window.wcpt_cb_heading = setTimeout(function () {
6541      $(".wcpt").each(function () {
6542        var $container = $(this),
6543          $heading_cb = $container.find(".wcpt-cart-checkbox-heading:visible"),
6544          $checkboxes = $container.find(".wcpt-cart-checkbox"),
6545          $available_cbs = $checkboxes.filter(":visible").not(":disabled"),
6546          $checked_cbs = $available_cbs.filter(":checked"),
6547          all_checked =
6548            $available_cbs.length &&
6549            $checked_cbs.length === $available_cbs.length;
6550
6551        // Always update heading checkbox state
6552        $heading_cb.prop("checked", all_checked);
6553      });
6554    }, 100);
6555  });
6556
6557  // -- shift + click
6558  // -- -- record shift key
6559  $("body").on("keydown", function (e) {
6560    if (e.shiftKey) {
6561      wcpt_shiftKey = true;
6562      $("body").one("keyup", function (e) {
6563        wcpt_shiftKey = false;
6564      });
6565    }
6566  });
6567
6568  // -- -- act upon $cb shift key selected by user
6569  $("body").on("change", ".wcpt-cart-checkbox", function (e, wcpt_sync) {
6570    if (wcpt_sync) {
6571      return false;
6572    }
6573
6574    var $this = $(this),
6575      $table = $this.closest(".wcpt-table"),
6576      $cb = $(".wcpt-cart-checkbox", $table);
6577    (($last_clicked = $cb.filter(".wcpt-cart-checkbox--last-clicked")),
6578      (checked = $this.prop("checked")));
6579
6580    $last_clicked.removeClass("wcpt-cart-checkbox--last-clicked");
6581    $this.addClass("wcpt-cart-checkbox--last-clicked");
6582
6583    if (
6584      $last_clicked.length &&
6585      typeof wcpt_shiftKey !== "undefined" &&
6586      wcpt_shiftKey
6587    ) {
6588      var min = Math.min($cb.index($this), $cb.index($last_clicked)),
6589        max = Math.max($cb.index($this), $cb.index($last_clicked));
6590
6591      $cb
6592        .filter(function () {
6593          var $this = $(this),
6594            index = $cb.index($this);
6595
6596          if ($this.prop("disabled")) {
6597            return false;
6598          }
6599
6600          if (index >= min && index <= max) {
6601            return true;
6602          } else {
6603            return false;
6604          }
6605        })
6606        .prop("checked", checked)
6607        .trigger("change", true);
6608    }
6609  });
6610
6611  // -- select / clear all
6612  $("body").on(
6613    "click",
6614    ".wcpt-add-selected__select-all, .wcpt-add-selected__clear-all",
6615    function (e) {
6616      var $this = $(this),
6617        isSelectAll = $this.hasClass("wcpt-add-selected__select-all"),
6618        $container = $this.closest(".wcpt"),
6619        $table = wcpt_get_container_original_table($container),
6620        $rows = $(".wcpt-row", $table);
6621
6622      $rows.trigger("_wcpt_checkbox_change", isSelectAll);
6623
6624      // If this is clear-all, also clear cross-page selection state for this table
6625      if (!isSelectAll && typeof wcpt_util !== "undefined") {
6626        var table_id = wcpt_util.get_table_id($container);
6627        if (table_id) {
6628          // delegate actual clearing to the persistence module
6629          $(document).trigger("wcpt_clear_all_checked_for_table", [
6630            table_id,
6631            $container,
6632          ]);
6633
6634          // ensure the per-container mirror used by the global trigger is cleared
6635          $container.data("wcpt_all_checked", {});
6636
6637          // immediately recompute / hide the global floating trigger if needed
6638          wcpt_checkbox_trigger_init();
6639        }
6640      }
6641      $("wcpt-cart-checkbox--last-clicked", $table).removeClass(
6642        "wcpt-cart-checkbox--last-clicked",
6643      );
6644    },
6645  );
6646
6647  // -- -- toggle the buttons
6648  $("body").on(
6649    "wcpt_checkbox_change",
6650    ".wcpt-row",
6651    update_table_add_selected_to_cart,
6652  );
6653
6654  function update_table_add_selected_to_cart() {
6655    var _ = this;
6656    setTimeout(function () {
6657      var $this = $(_),
6658        $container = $this.closest(".wcpt"),
6659        $table = wcpt_get_container_original_table($container),
6660        // cross-page selection state for this table (from persistence module)
6661        tableState = $container.data("wcpt_all_checked") || {};
6662      $add_checked = $(".wcpt-add-selected:visible", $container);
6663
6664      $add_checked.removeClass(
6665        "wcpt-add-selected--unselected wcpt-add-selected--single-item-selected",
6666      );
6667
6668      var keys = Object.keys(tableState),
6669        totalQty = 0,
6670        totalCost = 0;
6671
6672      if (keys.length) {
6673        // derive total quantity and total cost across all pages from persisted state
6674        keys.forEach(function (k) {
6675          var entry = tableState[k];
6676          if (!entry) {
6677            return;
6678          }
6679          var q = !isNaN(parseFloat(entry.qty)) ? parseFloat(entry.qty) : 1,
6680            t = !isNaN(parseFloat(entry.total)) ? parseFloat(entry.total) : 0;
6681
6682          totalQty = (totalQty * 1e12 + q * 1e12) / 1e12;
6683          totalCost = (totalCost * 1e12 + t * 1e12) / 1e12;
6684        });
6685
6686        $(".wcpt-total-selected", $add_checked).text(totalQty);
6687        $(".wcpt-total-selected-cost .wcpt-amount", $add_checked).text(
6688          format_price_figure(totalCost),
6689        );
6690
6691        if (totalQty == 1) {
6692          $add_checked.addClass("wcpt-add-selected--single-item-selected");
6693        }
6694      } else {
6695        // nothing selected across any page
6696        $add_checked.addClass("wcpt-add-selected--unselected");
6697        $(".wcpt-total-selected", $add_checked).text(0);
6698        $(".wcpt-total-selected-cost .wcpt-amount", $add_checked).text(
6699          format_price_figure(0),
6700        );
6701      }
6702    }, 100);
6703  }
6704
6705  // price decimal
6706  function format_price_figure(price) {
6707    price = parseFloat(price);
6708
6709    // remove decimal if it is unnecessary ie .00
6710    if (price !== parseInt(price)) {
6711      price = parseFloat(price).toFixed(wcpt_params.price_decimals);
6712    } else {
6713      price = parseInt(price);
6714    }
6715
6716    // decimal separator
6717    price = (price + "").replace(".", wcpt_params.price_decimal_separator);
6718
6719    // thousands separator
6720    var decimal_split = price.split(wcpt_params.price_decimal_separator);
6721    ((non_decimal = decimal_split[0].toString()), (decimal = decimal_split[1]));
6722
6723    if (non_decimal.length > 3) {
6724      formatted_non_decimal =
6725        non_decimal.slice(0, -3) +
6726        wcpt_params.price_thousand_separator +
6727        non_decimal.slice(-3);
6728      price =
6729        formatted_non_decimal +
6730        (decimal ? wcpt_params.price_decimal_separator + decimal : "");
6731    }
6732
6733    return price;
6734  }
6735
6736  function unformat_price_figure(price) {
6737    price = price.toString();
6738
6739    // remove thousands separator
6740    price = price.replace(wcpt_params.price_thousand_separator, "");
6741
6742    // replace decimal separator
6743    price = price.replace(wcpt_params.price_decimal_separator, ".");
6744
6745    return parseFloat(price);
6746  }
6747
6748  // follow wc settings for price display
6749  function format_price(num) {
6750    if (!num && num !== "0" && num !== 0) {
6751      return "";
6752    }
6753
6754    return wcpt_params.price_format
6755      .replace("%1$s", wcpt_params.currency_symbol)
6756      .replace("%2$s", format_price_figure(num));
6757  }
6758
6759  // -- add to cart
6760  $("body").on(
6761    "click",
6762    ".wcpt-cart-checkbox-trigger, .wcpt-cart-checkbox-trigger--local",
6763    wcpt_cart_checkbox,
6764  );
6765  function wcpt_cart_checkbox() {
6766    var $this = $(this),
6767      products = {},
6768      variations = {},
6769      attributes = {},
6770      addons = {},
6771      measurement = {},
6772      nyp = {}, // name your price
6773      $checked_rows = $(),
6774      $table = $(),
6775      tableContainersById = {};
6776
6777    if ($this.hasClass("wcpt-cart-checkbox-trigger")) {
6778      // global
6779      var $container = $(".wcpt");
6780
6781      $container.each(function () {
6782        var $c = $(this),
6783          table_id =
6784            typeof wcpt_util !== "undefined"
6785              ? wcpt_util.get_table_id($c)
6786              : false,
6787          $orig_table = wcpt_get_container_original_table($c);
6788
6789        if (table_id) {
6790          tableContainersById[table_id] = $c;
6791        }
6792
6793        $table = $table.add($orig_table);
6794      });
6795
6796      $this.addClass("wcpt-hide");
6797    } else {
6798      // local
6799      var $container = $this.closest(".wcpt"),
6800        table_id =
6801          typeof wcpt_util !== "undefined"
6802            ? wcpt_util.get_table_id($container)
6803            : false;
6804
6805      if (table_id) {
6806        tableContainersById[table_id] = $container;
6807      }
6808
6809      $table = wcpt_get_container_original_table($container);
6810    }
6811
6812    $table.each(function () {
6813      var $this = $(this);
6814      $table_checked_rows = $this.data("wcpt_checked_rows")
6815        ? $this.data("wcpt_checked_rows")
6816        : $();
6817
6818      $checked_rows = $checked_rows.add($table_checked_rows);
6819    });
6820
6821    $checked_rows.each(function () {
6822      var $row = $(this),
6823        product_id = $row.attr("data-wcpt-product-id"),
6824        variation_id = false,
6825        variation_attributes = false,
6826        $qty = $(".qty, .wcpt-qty-select", $row).first(),
6827        val = parseFloat($qty.length ? $qty.val() : 1);
6828      if (isNaN(val)) {
6829        val = 0;
6830      }
6831
6832      if (typeof products[product_id] === "undefined") {
6833        products[product_id] = val;
6834      } else {
6835        products[product_id] += val; // variation
6836      }
6837
6838      // variable
6839      if ($row.hasClass("wcpt-product-type-variable")) {
6840        var data = $row.data();
6841
6842        if (
6843          data.wcpt_variation_selected &&
6844          data.wcpt_variation_available &&
6845          data.wcpt_complete_match &&
6846          data.wcpt_variation_id
6847        ) {
6848          variation_id = data.wcpt_variation_id;
6849
6850          if (data.wcpt_attributes) {
6851            variation_attributes = data.wcpt_attributes;
6852          }
6853        }
6854
6855        // variation
6856      } else if ($row.hasClass("wcpt-product-type-variation")) {
6857        variation_id = $row.attr("data-wcpt-variation-id");
6858        variation_attributes = JSON.parse(
6859          $row.attr("data-wcpt-variation-attributes"),
6860        );
6861      }
6862
6863      if (variation_id) {
6864        if (!variations[product_id]) {
6865          variations[product_id] = {};
6866        }
6867
6868        if (!variations[product_id][variation_id]) {
6869          variations[product_id][variation_id] = val;
6870        }
6871
6872        if (variation_attributes) {
6873          attributes[variation_id] = variation_attributes;
6874        }
6875      }
6876
6877      // addons
6878      if (
6879        $row.hasClass("wcpt-product-has-addons") &&
6880        wcpt_row_has_inline_addons_form($row)
6881      ) {
6882        addons[product_id] = wcpt_get_addons($row);
6883      }
6884
6885      // measurement
6886      if ($row.hasClass("wcpt-product-has-measurement")) {
6887        measurement[product_id] = wcpt_get_measurement($row);
6888      }
6889
6890      // name your price
6891      if ($row.hasClass("wcpt-product-has-name-your-price")) {
6892        nyp[product_id] = wcpt_get_nyp($row);
6893      }
6894    });
6895
6896    // Off-page selections from the checkbox persistence module
6897    if (window.wcpt_cart_checkbox_persist) {
6898      window.wcpt_cart_checkbox_persist.aggregateOffPageIntoPayload(
6899        tableContainersById,
6900        {
6901          products: products,
6902          variations: variations,
6903          attributes: attributes,
6904        },
6905      );
6906      window.wcpt_cart_checkbox_persist.clearTables(tableContainersById);
6907    }
6908
6909    // Uncheck all currently checked rows on the page before wcpt_cart,
6910    // otherwise totals / visual state may be inconsistent.
6911    $checked_rows.trigger("_wcpt_checkbox_change", false);
6912
6913    // When every selection was off-page, $checked_rows is empty so the
6914    // checkbox-change → update_table_add_selected_to_cart path never runs.
6915    // Refresh the local "Add selected" button(s) from cleared state.
6916    $.each(tableContainersById, function (id, $c) {
6917      if ($c && $c.length) {
6918        update_table_add_selected_to_cart.call($c.get(0));
6919      }
6920    });
6921
6922    var payload = {
6923      products: products,
6924      addons: addons,
6925      measurement: measurement,
6926      variations: variations,
6927      attributes: attributes,
6928      nyp: nyp,
6929    };
6930
6931    // mark this cart request as originating from a checkbox-trigger bulk add
6932    var checkboxScope = $this.hasClass("wcpt-cart-checkbox-trigger")
6933      ? "global"
6934      : "local";
6935
6936    wcpt_cart({
6937      payload: payload,
6938      redirect: $this.attr("data-wcpt-redirect-url"),
6939      external_payload: {
6940        wcpt_checkbox_bulk: checkboxScope,
6941      },
6942    });
6943  }
6944
6945  // addons
6946  // True when addon fields are printed and visible in the row's cart form.
6947  // Select variation "attribute dropdowns" embeds a cart_form with only attributes
6948  // visible — hidden addon markup there must not block the product form modal.
6949  window.wcpt_row_has_inline_addons_form = function ($row) {
6950    return !!$(
6951      [
6952        ".wcpt-add-to-cart-wrapper .prad-addons-wrapper",
6953        ".wcpt-add-to-cart-wrapper .wc-pao-addon",
6954        ".wcpt-add-to-cart-wrapper .wc-pao-addons-container",
6955        ".wcpt-add-to-cart-wrapper #product-addons-total",
6956        ".wcpt-add-to-cart-wrapper .wcpa_form_outer",
6957      ].join(", "),
6958      wcpt_get_sibling_rows($row),
6959    ).filter(":visible").length;
6960  };
6961
6962  window.wcpt_get_addons = function ($row) {
6963    var $form = $(
6964        ".wcpt-add-to-cart-wrapper form",
6965        wcpt_get_sibling_rows($row),
6966      ),
6967      addons = {};
6968
6969    // WooCommerce Custom Product Addons
6970    var $wcpa_fields = $form.find(".wcpa_form_outer");
6971    if ($wcpa_fields.length) {
6972      var $fields = $wcpa_fields,
6973        $_form = $("<form>");
6974
6975      $_form
6976        .append($fields.clone())
6977        .find("select")
6978        .each(function () {
6979          // cloned select needs correct value
6980          var $this = $(this),
6981            name = $this.attr("name"),
6982            value = $fields.find('[name="' + name + '"]select').val();
6983          $this.val(value);
6984        });
6985
6986      $.each($_form.serializeArray(), function (i, field) {
6987        var field_name = field.name,
6988          suffix_index = field_name.indexOf("--wcpt");
6989
6990        if (-1 !== suffix_index) {
6991          // must be checkbox or radio
6992          field_name = field_name.substring(0, suffix_index);
6993
6994          if (field.name.slice(-1) == "]") {
6995            // checkbox
6996            if (!addons[field_name]) {
6997              addons[field_name] = [];
6998            }
6999            addons[field_name].push(field.value);
7000            return;
7001          } else {
7002            // radio
7003            addons[field_name] = field.value;
7004          }
7005        } else {
7006          // other field types
7007          addons[field_name] = field.value;
7008        }
7009      });
7010
7011      // official WooCommerce Product Addons
7012    } else {
7013      $.each($form.serializeArray(), function (i, field) {
7014        var field_name = field.name;
7015
7016        if (field_name.slice(-2) == "[]") {
7017          field_name = field_name.substring(0, field_name.length - 2);
7018        }
7019
7020        if (typeof addons[field_name] === "undefined") {
7021          addons[field_name] = field.value; // add prop
7022        } else {
7023          if (typeof addons[field_name] !== "object") {
7024            // make it an array
7025            addons[field_name] = [addons[field_name]];
7026          }
7027          addons[field_name].push(field.value); // update prop
7028        }
7029      });
7030    }
7031
7032    return addons;
7033  };
7034
7035  // measurement
7036  window.wcpt_get_measurement = function ($row) {
7037    var $price_calculator = $(
7038        ".wcpt-add-to-cart-wrapper form #price_calculator",
7039        wcpt_get_sibling_rows($row),
7040      ),
7041      measurement = {};
7042
7043    $("input", $price_calculator).each(function () {
7044      var $this = $(this);
7045      measurement[$this.attr("name")] = $this.val();
7046    });
7047
7048    return measurement;
7049  };
7050
7051  // name your price
7052  window.wcpt_get_nyp = function ($row) {
7053    var $nyp = get_nyp_input_element($row),
7054      val = 0;
7055
7056    if ($nyp.length) {
7057      val = $nyp.val();
7058    }
7059
7060    return val;
7061  };
7062
7063  function get_nyp_input_element($row) {
7064    return $(".wcpt-name-your-price--input", wcpt_get_sibling_rows($row));
7065  }
7066
7067  $("body").on("keyup input", ".wcpt-search-filter-options", function (e) {
7068    var $this = $(this),
7069      val = $this.val().toLowerCase().trim(),
7070      $filter = $this.closest(".wcpt-filter"),
7071      $option = $(".wcpt-dropdown-option", $filter);
7072
7073    e.preventDefault();
7074
7075    if (!val) {
7076      $option.show();
7077
7078      $(".wcpt-filter-hierarchy-accordion--open", $filter).removeClass(
7079        "wcpt-filter-hierarchy-accordion--open",
7080      );
7081
7082      $("input[type=radio], input[type=checkbox]", $option).each(function () {
7083        var $this = $(this);
7084        if ($this.is(":checked")) {
7085          $this
7086            .closest(".wcpt-dropdown-option.wcpt-filter-hierarchy-accordion")
7087            .addClass("wcpt-filter-hierarchy-accordion--open");
7088        }
7089      });
7090      return;
7091    }
7092
7093    $option.each(function () {
7094      var $this = $(this),
7095        label = $this.text().toLowerCase().trim();
7096      if (label.indexOf(val) == -1) {
7097        $this.hide();
7098      } else {
7099        $this.show();
7100        $this
7101          .closest(".wcpt-dropdown-option.wcpt-filter-hierarchy-accordion")
7102          .addClass("wcpt-filter-hierarchy-accordion--open");
7103      }
7104    });
7105  });
7106
7107  // global product search shortcode
7108  $("body").on(
7109    "change keydown keyup",
7110    ".wcpt-global-search__keyword-input",
7111    function () {
7112      var $this = $(this),
7113        $search = $this.closest(".wcpt-global-search");
7114
7115      if ($this.val()) {
7116        $search.removeClass("wcpt-global-search--empty");
7117      } else {
7118        $search.addClass("wcpt-global-search--empty");
7119      }
7120    },
7121  );
7122
7123  $(".wcpt-global-search__keyword-input").trigger("change");
7124
7125  // -- facade
7126  $("body").on("change", ".wcpt-global-search__category-selector", function () {
7127    var $this = $(this),
7128      value = $this.val(),
7129      $option = $('option[value="' + value + '"]', $this),
7130      text = $option.text().trim(),
7131      $facade = $this.siblings(".wcpt-global-search__category-selector-facade");
7132
7133    $(".wcpt-global-search__category-selector-facade__text", $facade).text(
7134      text,
7135    );
7136  });
7137
7138  // -- redirect empty
7139  $("body").on("submit", ".wcpt-global-search__form", function (e) {
7140    var $this = $(this),
7141      keyword = $this.find(".wcpt-global-search__keyword-input").val(),
7142      $select = $this.find(".wcpt-global-search__category-selector"),
7143      clear_redirect_url = $this.attr("data-wcpt-clear-redirect-url"),
7144      clear_redirect = $this.attr("data-wcpt-clear-redirect"),
7145      redirect = $this.attr("data-wcpt-redirect"),
7146      category = $(".wcpt-global-search__category-selector", $this).val(),
7147      action = $this.attr("action");
7148
7149    // select 'All' category
7150    if (!keyword) {
7151      if (clear_redirect !== "category") {
7152        $select.val("").change();
7153      }
7154
7155      // redirect
7156      if (clear_redirect_url) {
7157        e.preventDefault();
7158        window.location = clear_redirect_url;
7159      }
7160    } else if (redirect == "category" && category) {
7161      e.preventDefault();
7162      window.location = wcpt_product_category_links[category] + "?s=" + keyword;
7163    } else if (redirect == "shop") {
7164      e.preventDefault();
7165      var url = action + "?s=" + keyword;
7166      if (category) {
7167        url += "&wcpt_search_category=" + category;
7168      }
7169      window.location = url;
7170    } else if (redirect == "search") {
7171      e.preventDefault();
7172      var url = action + "?s=" + keyword + "&post_type=product";
7173      if (category) {
7174        url += "&wcpt_search_category=" + category;
7175      }
7176      window.location = url;
7177    }
7178  });
7179
7180  // -- clear
7181  $("body").on("click", ".wcpt-global-search__clear", function () {
7182    var $this = $(this),
7183      $input = $this.siblings(".wcpt-global-search__keyword-input"),
7184      $form = $this.closest(".wcpt-global-search__form");
7185    $input.val("");
7186    $form.submit();
7187  });
7188
7189  // -- focus / blur
7190  $("body")
7191    .on("focus", ".wcpt-global-search__keyword-input", function () {
7192      var $this = $(this),
7193        $wrapper = $this.parent();
7194      $wrapper.addClass("wcpt-global-search__keyword-input-wrapper--focus");
7195    })
7196    .on("blur", ".wcpt-global-search__keyword-input", function () {
7197      var $this = $(this),
7198        $wrapper = $this.parent();
7199      $wrapper.removeClass("wcpt-global-search__keyword-input-wrapper--focus");
7200    });
7201
7202  // cart
7203
7204  window.wcpt_cart = function (params) {
7205    // maybe add to pending requests
7206    if (
7207      // there has been at least one cart call earlier
7208      window.wcpt_cart_call_id &&
7209      // no results cache ie, no call is resolved yet
7210      (!window.wcpt_cart_result_cache ||
7211        // the call id in result cache (ie last resolved call) is uneqal to current call id
7212        window.wcpt_cart_call_id !=
7213          window.wcpt_cart_result_cache.payload.wcpt_cart_call_id)
7214    ) {
7215      !window.wcpt_cart_pending_req_params &&
7216        (window.wcpt_cart_pending_req_params = {});
7217
7218      // -- add this req to pending as well and return
7219      $.extend(true, window.wcpt_cart_pending_req_params, params);
7220      return;
7221    }
7222
7223    // begin / increment the cart call counter
7224    window.wcpt_cart_call_id = window.wcpt_cart_call_id
7225      ? window.wcpt_cart_call_id + 1
7226      : 1;
7227
7228    var _params = {
7229      payload: {
7230        wcpt_cart_call_id: window.wcpt_cart_call_id,
7231      },
7232      before: false,
7233      always: false,
7234      redirect: false,
7235      external_payload: {},
7236    };
7237    params = $.extend({}, _params, params ? params : {});
7238
7239    params.payload.wcpt_cart_call_id = window.wcpt_cart_call_id;
7240    params.payload.cart_widget_permitted = cart_widget_permitted();
7241
7242    // view
7243
7244    $(".wcpt-cart-widget").addClass("wcpt-cart-widget--loading"); // cart widget
7245
7246    // product req data used by view
7247    var product_request = {
7248      products: params.payload.products
7249        ? $.extend({}, params.payload.products)
7250        : {},
7251      variations: params.payload.variations
7252        ? $.extend({}, params.payload.variations)
7253        : {},
7254    };
7255
7256    if (
7257      // add in variation modal request data so it may be used by the view
7258      typeof params.payload !== "undefined" &&
7259      params.payload.variation_form &&
7260      typeof params.external_payload !== "undefined" &&
7261      params.external_payload["add-to-cart"]
7262    ) {
7263      var product_id = params.external_payload["add-to-cart"],
7264        variation_id = params.external_payload["variation_id"]
7265          ? params.external_payload["variation_id"]
7266          : "",
7267        quantity = parseFloat(params.external_payload["quantity"]);
7268
7269      product_request.products[product_id] = quantity;
7270
7271      if (variation_id) {
7272        product_request.variations[product_id] = {};
7273        product_request.variations[product_id][variation_id] = quantity;
7274      }
7275    }
7276
7277    $(".wcpt-row:visible").each(function () {
7278      var $row = $(this),
7279        $sibling_rows = wcpt_get_sibling_rows($row),
7280        product_id = $row.attr("data-wcpt-product-id"),
7281        variation_id = $row.attr("data-wcpt-variation-id"),
7282        $button = $(cart_button_selector, $row),
7283        in_cart = parseFloat($row.attr("data-wcpt-in-cart"));
7284
7285      // before request, update view like - add loading badge, animate Remove el
7286      if (product_request.products) {
7287        $.each(product_request.products, function (id, qty) {
7288          if (id == product_id) {
7289            if (
7290              // product is a variation, and not part of the cart request
7291              variation_id &&
7292              product_request.variations &&
7293              product_request.variations[product_id] &&
7294              typeof product_request.variations[product_id][variation_id] ==
7295                "undefined"
7296            ) {
7297              return;
7298            }
7299
7300            loading_badge_on_button($button);
7301
7302            if (0 === qty && in_cart) {
7303              // removing
7304              $row.addClass("wcpt-removing-product");
7305            } else {
7306              // adding
7307              $row.addClass("wcpt-adding-product");
7308
7309              var $qty = $("input.qty", $row),
7310                $wcpt_qty = $qty.not(".cart .qty"),
7311                initial = $wcpt_qty.attr("data-wcpt-initial-value"),
7312                min = parseFloat($wcpt_qty.attr("data-wcpt-min")),
7313                return_to_initial = $wcpt_qty.attr(
7314                  "data-wcpt-return-to-initial",
7315                );
7316
7317              if (return_to_initial) {
7318                $sibling_rows.trigger("_wcpt_checkbox_change", false);
7319
7320                if (initial == "min") {
7321                  $qty.val(min);
7322                } else if (initial === "0") {
7323                  $qty.val(0);
7324                } else if (initial === "empty") {
7325                  $qty.val("");
7326                }
7327              }
7328
7329              var $wrapper = $qty.closest(".wcpt-quantity");
7330              if ($wrapper.length) {
7331                limit_qty_controller($wrapper);
7332              }
7333
7334              // dropdown
7335              var $qty = $("select.wcpt-qty-select", $row),
7336                $first = $qty.children("option:first-child");
7337
7338              $qty.val($first.attr("value"));
7339            }
7340
7341            // total
7342            var $total = $(".wcpt-total", $sibling_rows),
7343              force_total_qty = qty;
7344
7345            if (
7346              variation_id &&
7347              product_request.variations &&
7348              product_request.variations[product_id] &&
7349              product_request.variations[product_id][variation_id]
7350            ) {
7351              force_total_qty =
7352                product_request.variations[product_id][variation_id];
7353            }
7354
7355            // don't allow it to be reset by qty going to 0
7356            if ($total.length) {
7357              update_row_total($sibling_rows, force_total_qty);
7358            }
7359          }
7360        });
7361      }
7362    });
7363
7364    var data = $.extend(
7365      {},
7366      {
7367        wcpt_payload: params.payload,
7368        lang: wcpt_i18n.lang,
7369      },
7370      params.external_payload ? params.external_payload : {},
7371    );
7372
7373    $("body").trigger("wcpt_cart_request", data);
7374
7375    // use cache
7376    if (params.payload.use_cache && window.wcpt_cart_result_cache) {
7377      window.wcpt_cart_result_cache.payload.wcpt_cart_call_id =
7378        window.wcpt_cart_call_id;
7379      $("body").trigger("wcpt_cart", window.wcpt_cart_result_cache);
7380
7381      // fetch fresh
7382    } else {
7383      $.post(
7384        wcpt_params.wc_ajax_url.replace("%%endpoint%%", "wcpt_cart"),
7385        data,
7386        function (result) {
7387          window.wcpt_cart_result_cache = $.extend({}, result, {
7388            success: true,
7389            notice: "",
7390            is_cache: true,
7391          });
7392          $("body").trigger("wcpt_cart", result);
7393        },
7394      ).always(function (result) {
7395        if (params.always) {
7396          params.always(result);
7397        }
7398
7399        // resolve any pending requests
7400        if (window.wcpt_cart_pending_req_params) {
7401          wcpt_cart(window.wcpt_cart_pending_req_params);
7402          window.wcpt_cart_pending_req_params = false;
7403        }
7404
7405        if (params.redirect) {
7406          if (
7407            // redirect after user reads error
7408            !result.success &&
7409            result.notice
7410          ) {
7411            $("body").on(
7412              "click touchstart",
7413              ".wcpt-notice-wrapper",
7414              function () {
7415                window.location = params.redirect;
7416              },
7417            );
7418          } else {
7419            // redirect immediately
7420            window.location = params.redirect;
7421          }
7422        }
7423      });
7424    }
7425  };
7426
7427  $("body").on("wcpt_cart", function (e, result) {
7428    // do not resolve if there are pending cart calls
7429    if (window.wcpt_cart_pending_req_params) {
7430      return;
7431    }
7432
7433    // cart widget
7434    if (result.payload.wcpt_cart_call_id === window.wcpt_cart_call_id) {
7435      // latest call resolved
7436      $(".wcpt-cart-widget ").removeClass("wcpt-cart-widget--loading");
7437    }
7438
7439    // error
7440    if (!result.success && result.notice) {
7441      var $body = $("body"),
7442        $notice = $(
7443          '<div class="wcpt-notice-wrapper">' + result.notice + "</div>",
7444        );
7445      $body.append($notice);
7446      $body.one("click", function () {
7447        $notice.remove();
7448      });
7449    }
7450
7451    if (result.cart_widget && cart_widget_permitted()) {
7452      var $body = $("body"),
7453        $old = $(".wcpt-cart-widget").not(".wcpt-cart-checkbox-trigger"),
7454        $new = $(result.cart_widget);
7455
7456      if ($new.hasClass("wcpt-hide")) {
7457        $body.removeClass("wcpt-cart-widget-visible");
7458      } else {
7459        $body.addClass("wcpt-cart-widget-visible");
7460      }
7461
7462      $body.append($new);
7463      $old.remove();
7464    }
7465
7466    // If this cart response came from a checkbox-trigger bulk add-to-cart,
7467    // clear all selection state across tables (including persisted state)
7468    // so nothing remains checked on any page.
7469    if (
7470      result &&
7471      result.external_payload &&
7472      result.external_payload.wcpt_checkbox_bulk
7473    ) {
7474      if (typeof wcpt_util !== "undefined") {
7475        $(".wcpt").each(function () {
7476          var $container = $(this),
7477            table_id = wcpt_util.get_table_id($container);
7478
7479          if (!table_id) {
7480            return;
7481          }
7482
7483          // Use the built-in API to clear persisted cross-page state
7484          $(document).trigger("wcpt_clear_all_checked_for_table", [
7485            table_id,
7486            $container,
7487          ]);
7488
7489          // Uncheck any rows currently in the DOM for this table
7490          $container.find(".wcpt-row").each(function () {
7491            $(this).trigger("_wcpt_checkbox_change", false);
7492          });
7493        });
7494      }
7495
7496      // Hide the global floating trigger (if present)
7497      $(".wcpt-cart-checkbox-trigger").addClass("wcpt-hide").hide();
7498    }
7499
7500    // added / removed
7501    var added = (removed = false);
7502
7503    if (result.payload.products && result.in_cart) {
7504      $.each(result.payload.products, function (product_id, product_qty) {
7505        var variation_id = (variation_qty = false);
7506
7507        if (
7508          result.payload.variations &&
7509          typeof result.payload.variations[product_id] !== "undefined"
7510        ) {
7511          variation_id = Object.keys(result.payload.variations[product_id])[0];
7512          variation_qty = Object.values(
7513            result.payload.variations[product_id],
7514          )[0];
7515        }
7516
7517        if (product_qty === "0") {
7518          // req. type: "remove"
7519          if (
7520            $.isEmptyObject(result.in_cart) ||
7521            !result.in_cart.length ||
7522            !result.in_cart[product_id] ||
7523            (variation_id && !result.in_cart[product_id][variation_id])
7524          ) {
7525            removed = true;
7526          }
7527        } else {
7528          // req. type: "add"
7529          if (
7530            !$.isEmptyObject(result.in_cart) &&
7531            result.in_cart[product_id] &&
7532            (!variation_id || result.in_cart[product_id][variation_id])
7533          ) {
7534            added = true;
7535          }
7536        }
7537      });
7538    }
7539
7540    if (result.payload.variation_form) {
7541      added = true;
7542    }
7543
7544    var in_cart_products = []; // variations and parent variable products at same level
7545
7546    if (result.in_cart) {
7547      $.each(result.in_cart, function (key, val) {
7548        // variable product
7549        if (typeof val === "object") {
7550          // related to parent variable product
7551          var qty = 0,
7552            total = 0;
7553
7554          // iterate over variations
7555          $.each(val, function (key2, val2) {
7556            var _total = result.in_cart_total[key][key2];
7557
7558            // insert variations as separate entries
7559            in_cart_products.push({
7560              id: key2,
7561              type: "variation",
7562              quantity: val2,
7563              total: _total,
7564            });
7565
7566            // sum up variation quantities and totals (decimal fix)
7567            qty = (qty * 1000 + parseFloat(val2) * 1000) / 1000;
7568            total = (total * 1000 + parseFloat(_total) * 1000) / 1000;
7569          });
7570
7571          // insert parent variable product
7572          in_cart_products.push({
7573            id: key,
7574            type: "variable",
7575            quantity: qty,
7576            total: total,
7577          });
7578        } else {
7579          // simple
7580          in_cart_products.push({
7581            id: key,
7582            type: "simple",
7583            quantity: val,
7584            total: result.in_cart_total[key],
7585          });
7586        }
7587      });
7588    }
7589
7590    // view
7591
7592    var $rows = $(".wcpt-row");
7593
7594    $rows.each(function () {
7595      var $row = $(this),
7596        type = $row.attr("data-wcpt-type"),
7597        product_id = $row.attr("data-wcpt-product-id"),
7598        variation_id = $row.attr("data-wcpt-variation-id"),
7599        id = type == "variation" ? variation_id : product_id,
7600        cart_item = false,
7601        min_call_id = $row.data("wcpt-min-cart-call-id");
7602
7603      if (min_call_id > result.payload.wcpt_cart_call_id) {
7604        return;
7605      }
7606
7607      $.each(in_cart_products, function (key, item) {
7608        if (
7609          // variation
7610          (variation_id && variation_id == item.id) ||
7611          // regular product
7612          (!variation_id && product_id == item.id)
7613        ) {
7614          cart_item = item;
7615          return false;
7616        }
7617      });
7618
7619      // wcpt initiated adding / removing process is complete
7620      $row.removeClass("wcpt-adding-product wcpt-removing-product");
7621
7622      // update 'in cart' qty
7623      var cart_qty = cart_item ? cart_item.quantity : 0;
7624      $row.attr("data-wcpt-in-cart", cart_qty);
7625      $in_cart = $(".wcpt-in-cart", $row).text(cart_qty);
7626      $in_cart.each(function () {
7627        var $this = $(this),
7628          template = $this.attr("data-wcpt-template"),
7629          empty_template = $this.attr("data-wcpt-empty-template");
7630
7631        if (cart_qty) {
7632          $this.text(template.replace("{n}", cart_qty));
7633        } else {
7634          $this.text(empty_template);
7635        }
7636      });
7637
7638      // update 'in cart' total
7639      var cart_total = cart_item ? cart_item.total : 0;
7640      $(".wcpt-total", $row).attr("data-wcpt-in-cart-total", cart_total);
7641      update_row_total($row);
7642
7643      // badge
7644
7645      if (!$row.hasClass("wcpt-adding-product")) {
7646        $button = $(cart_button_selector, $row);
7647        add_count_badge_to_button(cart_qty, $button);
7648
7649        if (!$button.hasClass("wcpt-out-of-stock")) {
7650          enable_button($button);
7651        }
7652      }
7653
7654      // remove
7655
7656      if (!$row.hasClass("wcpt-removing-product")) {
7657        var $remove = $(".wcpt-remove", $row);
7658
7659        // -- enable
7660        if (cart_item) {
7661          $remove.removeClass("wcpt-disabled");
7662
7663          // -- disable
7664        } else {
7665          // $remove.addClass('wcpt-disabled').removeClass('wcpt-removing');
7666          $remove.addClass("wcpt-disabled");
7667
7668          // wc button
7669          $(".add_to_cart_button", $row)
7670            .removeClass("added")
7671            .next(".added_to_cart")
7672            .remove();
7673        }
7674      }
7675    });
7676
7677    if (
7678      result.fragments &&
7679      !result.payload.skip_cart_triggers &&
7680      !result.is_cache
7681    ) {
7682      var trigger_lock = true,
7683        $body = $("body"),
7684        $button = false;
7685
7686      // fake button for single add / remove
7687      if (
7688        result.payload &&
7689        result.payload.products &&
7690        Object.keys(result.payload.products).length === 1
7691      ) {
7692        $.each(result.payload.products, function (product_id, qty) {
7693          if (qty) {
7694            // added
7695            $button = $('<button data-product_id="' + product_id + '">');
7696          } else {
7697            // removed
7698            $button = $('<a href="" data-product_id="' + product_id + '">');
7699          }
7700        });
7701      }
7702
7703      if (added || result.payload.force_refresh_cart) {
7704        $body.trigger("added_to_cart", [
7705          result.fragments,
7706          result.cart_hash,
7707          $button,
7708          trigger_lock,
7709        ]);
7710      }
7711
7712      if (removed) {
7713        $body.trigger("removed_from_cart", [
7714          result.fragments,
7715          result.cart_hash,
7716          $button,
7717          trigger_lock,
7718        ]);
7719      }
7720    }
7721  });
7722
7723  $("body").on(
7724    "added_to_cart removed_from_cart",
7725    function (e, fragment, cart_hash, $button, trigger_lock) {
7726      if (!trigger_lock) {
7727        wcpt_cart({
7728          payload: { skip_cart_triggers: true },
7729        });
7730      }
7731    },
7732  );
7733
7734  // PRO
7735
7736  $("body").on("click", ".wcpt-nav-redirect-option", function (e) {
7737    var $this = $(this),
7738      $link = $this.find("a.wcpt-nav-redirect-link"),
7739      url = $link.attr("href");
7740
7741    if (
7742      $(e.target).closest(".wcpt-filter-hierarchy-accordion__trigger").length
7743    ) {
7744      return;
7745    }
7746
7747    window.location.href = url;
7748  });
7749
7750  // table heading offset based on nav header
7751  $("body").on("wcpt_layout", ".wcpt", function (e, data) {
7752    var $this = $(this),
7753      $freeze_nav_sidebar = $(
7754        ".wcpt-navigation.wcpt-left-sidebar.wcpt-sticky:visible",
7755        $this,
7756      ),
7757      $freeze_nav_header = $(
7758        ".wcpt-navigation.wcpt-header.wcpt-sticky:visible",
7759        $this,
7760      ),
7761      $freeze_table_heading = $(
7762        ".frzTbl-fixed-heading-wrapper-outer:visible",
7763        $this,
7764      ),
7765      sc_attrs = $this.data("wcpt_sc_attrs"),
7766      device = get_device(),
7767      offset = parseInt(
7768        sc_attrs[device + "_freeze_heading_offset"]
7769          ? sc_attrs[device + "_freeze_heading_offset"]
7770          : sc_attrs[device + "_scroll_offset"]
7771            ? sc_attrs[device + "_scroll_offset"]
7772            : 0,
7773      );
7774
7775    $freeze_nav_sidebar.css("top", offset);
7776
7777    if (device == "laptop") {
7778      $freeze_nav_header.css("top", offset);
7779
7780      offset += parseInt($freeze_nav_header.outerHeight());
7781
7782      $freeze_table_heading.css({ top: offset });
7783    } else {
7784      offset += parseInt($freeze_nav_sidebar.outerHeight());
7785      $freeze_nav_header.css({ top: offset });
7786
7787      offset += $freeze_nav_header.outerHeight();
7788      $freeze_table_heading.css({ top: offset });
7789    }
7790  });
7791
7792  // cart widget permission
7793  function cart_widget_permitted() {
7794    var url = window.location.href.split("?")[0],
7795      include_match = false,
7796      exclude_match = false,
7797      include_urls = false,
7798      exclude_urls = false,
7799      site_url = wcpt_params.site_url + "/";
7800
7801    if (!wcpt_params.cart_widget_enabled_site_wide && !$(".wcpt").length) {
7802      return false;
7803    }
7804
7805    if (wcpt_params.cart_widget_include_urls.trim()) {
7806      include_urls = wcpt_params.cart_widget_include_urls
7807        .trim()
7808        .replace(/\r\n/g, "\n")
7809        .split("\n");
7810    }
7811
7812    if (wcpt_params.cart_widget_exclude_urls.trim()) {
7813      exclude_urls = wcpt_params.cart_widget_exclude_urls
7814        .trim()
7815        .replace(/\r\n/g, "\n")
7816        .split("\n");
7817    }
7818
7819    if (include_urls.length) {
7820      include_match = false;
7821      $.each(include_urls, function (i, path) {
7822        path = path.trim();
7823
7824        if (path.indexOf(site_url) === 0) {
7825          path = path.substring(site_url.length, path.length);
7826        }
7827
7828        if (path.trim() == "/") {
7829          if (url === site_url) {
7830            include_match = true;
7831          }
7832          return;
7833        }
7834
7835        if (path.trim() == "/*") {
7836          include_match = true;
7837          return;
7838        }
7839
7840        path = path.replace(/(^\s*\/)|(\/\s*$)/g, ""); // ensure no slash at start or end
7841        if ("*" === path.substr(-1)) {
7842          var remove = 1;
7843          if ("/*" === path.substr(-2)) {
7844            remove = 2;
7845          }
7846          if (
7847            -1 !==
7848            url.indexOf(
7849              site_url + path.substring(0, path.length - remove) + "/",
7850            )
7851          ) {
7852            include_match = true;
7853          }
7854        } else {
7855          path += "/";
7856          if (url == site_url + path) {
7857            include_match = true;
7858          }
7859        }
7860      });
7861
7862      if (!include_match) {
7863        return false;
7864      }
7865    }
7866
7867    if (exclude_urls.length) {
7868      exclude_match = false;
7869
7870      $.each(exclude_urls, function (i, path) {
7871        path = path.trim();
7872
7873        if (path.indexOf(site_url) === 0) {
7874          path = path.substring(site_url.length, path.length);
7875        }
7876
7877        if (path.trim() == "/") {
7878          if (url === site_url) {
7879            exclude_match = true;
7880          }
7881          return;
7882        }
7883
7884        if (path.trim() == "/*") {
7885          exclude_match = true;
7886          return;
7887        }
7888
7889        path = path.replace(/(^\s*\/)|(\/\s*$)/g, ""); // ensure no slash at start or end
7890        if ("*" === path.substr(-1)) {
7891          var remove = 1;
7892          if ("/*" === path.substr(-2)) {
7893            remove = 2;
7894          }
7895
7896          if (
7897            -1 !==
7898            url.indexOf(
7899              site_url + path.substring(0, path.length - remove) + "/",
7900            )
7901          ) {
7902            exclude_match = true;
7903          }
7904        } else {
7905          path += "/";
7906          if (url == site_url + path) {
7907            exclude_match = true;
7908          }
7909        }
7910      });
7911
7912      if (exclude_match) {
7913        return false;
7914      }
7915    }
7916
7917    return true;
7918  }
7919
7920  // cart widget close x
7921  $("body").on("click", ".wcpt-cart-checkbox-trigger__close", function (e) {
7922    e.stopPropagation();
7923    var $cart_widget = $(this).closest(".wcpt-cart-checkbox-trigger");
7924
7925    if (typeof wcpt_util !== "undefined") {
7926      $(".wcpt").each(function () {
7927        var $container = $(this),
7928          table_id = wcpt_util.get_table_id($container);
7929
7930        if (!table_id) {
7931          return;
7932        }
7933
7934        $(document).trigger("wcpt_clear_all_checked_for_table", [
7935          table_id,
7936          $container,
7937        ]);
7938
7939        $container.find(".wcpt-row").each(function () {
7940          $(this).trigger("_wcpt_checkbox_change", false);
7941        });
7942      });
7943    }
7944
7945    wcpt_checkbox_trigger_init();
7946    $cart_widget.addClass("wcpt-hide").hide();
7947  });
7948
7949  // multirange
7950
7951  // -- input[type=number] change
7952  $("body").on(
7953    "input change",
7954    ".wcpt-range-input-min, .wcpt-range-input-max",
7955    function () {
7956      var $this = $(this),
7957        $container = $this.closest(".wcpt-range-options-wrapper"),
7958        $min = $container.find(".wcpt-range-input-min"),
7959        min_val = parseFloat($min.val()),
7960        $max = $container.find(".wcpt-range-input-max"),
7961        max_val = parseFloat($max.val());
7962
7963      $range = $container.find(".wcpt-range-slider.original");
7964
7965      if (!$range.length) {
7966        return;
7967      }
7968
7969      // max step fix
7970      var actual_max = parseFloat($this.attr("data-wcpt-actual-max")),
7971        val = parseFloat($this.val());
7972
7973      if (!isNaN(actual_max) && !isNaN(val) && val > actual_max) {
7974        $this.val(actual_max);
7975      }
7976
7977      // handle cleared min/max inputs
7978      if (isNaN(min_val)) {
7979        min_val = parseFloat($range.attr("min"));
7980        $min.val(min_val);
7981      }
7982
7983      if (isNaN(max_val)) {
7984        max_val = parseFloat($range.attr("max"));
7985        $max.val(max_val);
7986      }
7987
7988      $range.val(min_val + "," + max_val);
7989    },
7990  );
7991
7992  // -- input[type=range] change
7993  $("body").on("input change", ".wcpt-range-slider", function () {
7994    var $this = $(this),
7995      $container = $this.closest(".wcpt-range-options-wrapper"),
7996      $min = $(".wcpt-range-input-min", $container),
7997      $max = $(".wcpt-range-input-max", $container),
7998      $range_original = $container.find(".wcpt-range-slider.original"),
7999      $range_ghost = $container.find(".wcpt-range-slider.ghost"),
8000      min = parseFloat($range_original.prop("valueLow")),
8001      max = parseFloat($range_original.prop("valueHigh")),
8002      permitted_max = parseFloat($this.attr("max")),
8003      step = parseFloat($this.attr("step"));
8004
8005    // range input max fix
8006    // if( max + step > permitted_max ){
8007    //   max = permitted_max; // sending this to input[type="number"]
8008    // }
8009
8010    $min.val(min);
8011    $max.val(max);
8012  });
8013
8014  // switch elements based on selected variation
8015  $("body").on(
8016    "select_variation",
8017    ".wcpt-product-type-variable",
8018    function (e, data) {
8019      var $row = get_product_rows($(this)),
8020        $items = $(
8021          ".wcpt-variation-description__item, .wcpt-variation-content",
8022          $row,
8023        );
8024
8025      $items.hide();
8026
8027      if ($row.data("wcpt_variation_selected")) {
8028        var variation_id = $row.data("wcpt_variation_id");
8029        $items.filter("[data-wcpt-variation-id=" + variation_id + "]").show();
8030      }
8031    },
8032  );
8033
8034  // name your price
8035  $("body").on(
8036    "select_variation",
8037    ".wcpt-product-type-variable",
8038    function (e, data) {
8039      var $row = get_product_rows($(this)),
8040        $input = $(".wcpt-name-your-price--input", $row),
8041        initial_value_field = $input.attr("data-wcpt-nyp-initial-value-field"),
8042        input = false,
8043        min = false,
8044        max = false,
8045        suggested = false;
8046
8047      // if price has no variable switch, always stays "$min - $max" then just replace it completely with "from $_min"
8048      // if it has variable switch
8049      // then hide it whenever selected variation has nyp
8050
8051      $row.removeClass("wcpt-product-has-name-your-price");
8052
8053      if ($row.data("wcpt_variation_selected")) {
8054        // variation selected
8055        var variation = $row.data("wcpt_variation");
8056
8057        if (variation.is_nyp) {
8058          input = true;
8059          min = variation.minimum_price;
8060          max = variation.maximum_price;
8061          suggested = variation.suggested_price;
8062
8063          $row.addClass("wcpt-product-has-name-your-price");
8064        }
8065      } else {
8066        // variation not selected
8067        input = false;
8068      }
8069
8070      // no nyp input field
8071      if (!input) {
8072        $input.parent().addClass("wcpt-hide");
8073        $row
8074          .find("wcpt-price.wcpt-variable-switch")
8075          .removeClass("wcpt-hide--name-your-price");
8076
8077        // found nyp input field
8078      } else {
8079        $input.parent().removeClass("wcpt-hide");
8080
8081        if (min) {
8082          $input.attr({
8083            min: min,
8084            "data-wcpt-nyp-minimum-price": min,
8085          });
8086        } else {
8087          $input.attr({
8088            min: 0,
8089            "data-wcpt-nyp-minimum-price": 0,
8090          });
8091        }
8092
8093        if (max) {
8094          $input.attr("max", max);
8095
8096          $input.attr({
8097            max: max,
8098            "data-wcpt-nyp-maximum-price": max,
8099          });
8100        } else {
8101          $input.attr({
8102            max: "",
8103            "data-wcpt-nyp-maximum-price": "",
8104          });
8105        }
8106
8107        if (initial_value_field && !$input.val()) {
8108          var value = variation[initial_value_field + "_price"]
8109            ? variation[initial_value_field + "_price"]
8110            : "";
8111          $input.val(value);
8112        }
8113
8114        $row
8115          .find("wcpt-price.wcpt-variable-switch")
8116          .addClass("wcpt-hide--name-your-price");
8117      }
8118
8119      if (!min) {
8120        $row.find(".wcpt-name-your-price--minimum").addClass("wcpt-hide");
8121      } else {
8122        $row.find(".wcpt-name-your-price--minimum").removeClass("wcpt-hide");
8123        $row.find(".wcpt-name-your-price--minimum .wcpt-amount").text(min);
8124      }
8125
8126      if (!max) {
8127        $row.find(".wcpt-name-your-price--maximum").addClass("wcpt-hide");
8128      } else {
8129        $row.find(".wcpt-name-your-price--maximum").removeClass("wcpt-hide");
8130        $row.find(".wcpt-name-your-price--maximum .wcpt-amount").text(max);
8131      }
8132
8133      if (!suggested) {
8134        $row.find(".wcpt-name-your-price--suggested").addClass("wcpt-hide");
8135      } else {
8136        $row.find(".wcpt-name-your-price--suggested").removeClass("wcpt-hide");
8137        $row
8138          .find(".wcpt-name-your-price--suggested .wcpt-amount")
8139          .text(suggested);
8140      }
8141
8142      $input.change();
8143    },
8144  );
8145
8146  // -- checkbox
8147  $("body").on("wcpt_checkbox_change", ".wcpt-row", function (e, checked) {
8148    var $this = $(this),
8149      $rows = wcpt_get_sibling_rows($this.closest(".wcpt-row")),
8150      $nyp = get_nyp_input_element($rows);
8151
8152    if (checked) {
8153      nyp_validate($nyp);
8154    } else if (!$nyp.val()) {
8155      nyp_hide_error($nyp);
8156    }
8157  });
8158
8159  // -- validate
8160  function nyp_validate($nyp) {
8161    var $nyp_wrapper = $nyp.parent(),
8162      nyp_val = $nyp.val(),
8163      nyp_min = $nyp.attr("data-wcpt-nyp-minimum-price"),
8164      nyp_max = $nyp.attr("data-wcpt-nyp-maximum-price"),
8165      message_template = "",
8166      $error = "",
8167      $row = $nyp.closest(".wcpt-row"),
8168      checked = $row.data("wcpt_checked");
8169
8170    if (
8171      (nyp_val && nyp_min && parseFloat(nyp_val) < parseFloat(nyp_min)) ||
8172      (checked && !nyp_val && nyp_min)
8173    ) {
8174      $nyp_wrapper.addClass(
8175        "wcpt-name-your-price-wrapper--input-error wcpt-name-your-price-wrapper--input-error--min-price",
8176      );
8177
8178      $error = $(
8179        ".wcpt-name-your-price-input-error-message--min-price",
8180        $nyp_wrapper,
8181      );
8182      message_template = $error.attr("data-wcpt-error-message-template");
8183      $error.text(
8184        message_template.replace("[min]", format_price_figure(nyp_min)),
8185      );
8186    } else if (
8187      nyp_val &&
8188      nyp_max &&
8189      parseFloat(nyp_val) > parseFloat(nyp_max)
8190    ) {
8191      $nyp_wrapper.addClass(
8192        "wcpt-name-your-price-wrapper--input-error wcpt-name-your-price-wrapper--input-error--max-price",
8193      );
8194
8195      $error = $(
8196        ".wcpt-name-your-price-input-error-message--max-price",
8197        $nyp_wrapper,
8198      );
8199      message_template = $error.attr("data-wcpt-error-message-template");
8200      $error.text(
8201        message_template.replace("[max]", format_price_figure(nyp_max)),
8202      );
8203    } else {
8204      $nyp_wrapper.removeClass(
8205        "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",
8206      );
8207    }
8208  }
8209
8210  // -- hide validation errors
8211  function nyp_hide_error($nyp) {
8212    $nyp
8213      .parent()
8214      .removeClass(
8215        "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",
8216      );
8217  }
8218
8219  // trigger nav feedback from range slider and min max
8220  $("body").on("change", ".wcpt-range-options-wrapper input", function () {
8221    var $this = $(this),
8222      $filter = $this.closest(".wcpt-filter");
8223
8224    $filter.find("input[type=radio]").prop("checked", false);
8225    nav_filter_feedback($this.closest(".wcpt-navigation"));
8226  });
8227
8228  // refresh / block table
8229  // -- lock
8230  $("body").on("wcpt_cart_request", function (e, data) {
8231    if (
8232      data.wcpt_payload.skip_cart_triggers ||
8233      data.wcpt_payload.use_cache ||
8234      data.is_cache
8235    ) {
8236      return;
8237    }
8238
8239    $(".wcpt").each(function () {
8240      var $container = $(this),
8241        sc_attrs = JSON.parse($container.attr("data-wcpt-sc-attrs"));
8242
8243      if (sc_attrs.refresh_table) {
8244        $container.addClass("wcpt-loading wcpt-refreshing");
8245      } else if (sc_attrs.block_table) {
8246        $container.addClass("wcpt-loading");
8247      }
8248    });
8249  });
8250  // -- refresh
8251  $("body").on("wcpt_cart", function (e, result) {
8252    if (
8253      result.payload.skip_cart_triggers ||
8254      result.payload.use_cache ||
8255      result.is_cache
8256    ) {
8257      return;
8258    }
8259
8260    $(".wcpt").each(function () {
8261      var $container = $(this),
8262        sc_attrs = JSON.parse($container.attr("data-wcpt-sc-attrs")),
8263        table_id = $container.attr("data-wcpt-table-id");
8264
8265      if (sc_attrs.refresh_table) {
8266        $.each(window.wcpt_cache.data, function (key, val) {
8267          if (
8268            key.indexOf("?" + table_id + "_") !== -1 ||
8269            key.indexOf("&" + table_id + "_") !== -1
8270          ) {
8271            delete window.wcpt_cache.data[key];
8272          }
8273        });
8274
8275        attempt_ajax($container, "", true, "refresh_table");
8276      } else if (sc_attrs.block_table) {
8277        $container.removeClass("wcpt-loading");
8278      }
8279    });
8280  });
8281
8282  // switching variation should update Button element's product link
8283  $("body").on(
8284    "select_variation",
8285    ".wcpt-product-type-variable",
8286    function (e, data) {
8287      var $row = get_product_rows($(this)),
8288        $link_button = $(
8289          ".wcpt-button[data-wcpt-link-code='product_link']",
8290          $row,
8291        );
8292
8293      if ($link_button.length) {
8294        var original_link = $link_button.attr("data-wcpt-product-link"),
8295          new_link = "";
8296
8297        if (!original_link) {
8298          original_link = $link_button.attr("href");
8299          $link_button.attr("data-wcpt-product-link", original_link);
8300        }
8301
8302        if (data.variation_id) {
8303          new_link = original_link + "?";
8304          $.each(data.attributes, function (key, value) {
8305            new_link = new_link + key + "=" + value + "&";
8306          });
8307          new_link = new_link + "variation_id=" + data.variation_id;
8308          $link_button.attr("href", new_link);
8309        } else {
8310          $link_button.attr("href", original_link);
8311        }
8312      }
8313    },
8314  );
8315
8316  // berocket
8317  $(document).on("berocket_ajax_filtering_end", function (e) {
8318    $(".wcpt").each(function () {
8319      after_every_load($(this));
8320    });
8321  });
8322
8323  // waveplayer
8324
8325  // -- holds the detached waveplayer instances
8326  window.$wcpt_waveplayer_preserve = $();
8327
8328  //-- avoid losing functionality on cloned players during freeze table
8329  $("body").on("wcpt_layout", ".wcpt", function () {
8330    var $this = $(this);
8331
8332    if (typeof WavePlayer !== "undefined") {
8333      WavePlayer.loadInstances();
8334
8335      if (!$wcpt_waveplayer_preserve.length) {
8336        return;
8337      }
8338
8339      var $replace = get_matching_waveplayer_elm($this);
8340
8341      if ($replace.length) {
8342        $replace.replaceWith($wcpt_waveplayer_preserve);
8343        $wcpt_waveplayer_preserve
8344          .find(".wvpl-waveform canvas")
8345          .each(function () {
8346            var $this = $(this);
8347            $this.width($this.attr("data-wcpt-last-width"));
8348            $this.height($this.attr("data-wcpt-last-height"));
8349          });
8350
8351        waveplayer_active_ui_feedback.call($wcpt_waveplayer_preserve.get(0));
8352
8353        $wcpt_waveplayer_preserve = $();
8354
8355        WavePlayer.redrawAllInstances();
8356      }
8357    }
8358  });
8359
8360  // -- check for and return waveplayer element in container that needs to be replaced
8361  function get_matching_waveplayer_elm($container) {
8362    if (WavePlayer.persistentTrack && WavePlayer.instances.length) {
8363      var instance = false,
8364        track_index = false;
8365
8366      $.each(WavePlayer.instances, function (_instance_index, _instance) {
8367        var $_instances = $(_instance.node);
8368        if (
8369          $_instances.is(":visible") &&
8370          $_instances.parent().hasClass("wcpt-waveplayer-container")
8371        ) {
8372          var $row = $_instances.closest(".wcpt-row");
8373
8374          if ($row.length) {
8375            $.each(_instance.tracks, function (_track_index, _track) {
8376              if (
8377                _track.file === WavePlayer.persistentTrack.file &&
8378                _track.product_id === WavePlayer.persistentTrack.product_id
8379              ) {
8380                instance = _instance;
8381                track_index = _track_index;
8382              }
8383            });
8384          }
8385        }
8386      });
8387
8388      if (instance) {
8389        return $(instance.node).parent(".wcpt-waveplayer-container");
8390      }
8391    }
8392
8393    return $();
8394  }
8395
8396  // -- add to cart
8397  $("body").on("added_to_cart", function (e, fragment, cart_hash, $button) {
8398    if (typeof WavePlayer === "undefined" || !$button) {
8399      return;
8400    }
8401
8402    var product_id = $button.attr("data-product_id");
8403    if (typeof WavePlayer.updateTrackCartStatus !== "undefined") {
8404      WavePlayer.updateTrackCartStatus(product_id, "add");
8405    }
8406
8407    $(".wvpl-cart[data-product_id=" + product_id + "]")
8408      .attr("title", WavePlayer.__("Add to cart", "waveplayer"))
8409      .attr("data-event", "goToCart")
8410      .attr("data-callback", "goToCart")
8411      .removeClass("wvpl-add_to_cart")
8412      .addClass("wvpl-in_cart")
8413      .removeClass("wvpl-spin");
8414  });
8415
8416  // -- row color
8417  $("body").on(
8418    "click",
8419    ".wcpt-row .wcpt-waveplayer-container",
8420    waveplayer_active_ui_feedback,
8421  );
8422
8423  // -- active feedback
8424  function waveplayer_active_ui_feedback() {
8425    var $this = $(this),
8426      active_row_background_color = $this.attr(
8427        "data-wcpt-waveplayer-active-row-background-color",
8428      ),
8429      active_row_outline_color = $this.attr(
8430        "data-wcpt-waveplayer-active-row-outline-color",
8431      )
8432        ? $this.attr("data-wcpt-waveplayer-active-row-outline-color")
8433        : "#4198de",
8434      active_row_outline_width = $this.attr(
8435        "data-wcpt-waveplayer-active-row-outline-width",
8436      )
8437        ? parseFloat(
8438            $this.attr("data-wcpt-waveplayer-active-row-outline-width"),
8439          ) + "px"
8440        : "1px",
8441      $rows = wcpt_get_sibling_rows($this.closest(".wcpt-row"));
8442
8443    $rows.addClass("wcpt-waveplayer-active").css({
8444      background: active_row_background_color
8445        ? active_row_background_color
8446        : "",
8447      outline: active_row_outline_width + " solid " + active_row_outline_color,
8448    });
8449
8450    $(".wcpt-row").not($rows).removeClass("wcpt-waveplayer-active").css({
8451      background: "",
8452      outline: "",
8453    });
8454  }
8455
8456  // 3rd party tab and accordion compatibility
8457
8458  // -- elementor accordion, tab & toggle
8459  $("body").on("click", ".elementor-tab-title", function () {
8460    var $this = $(this),
8461      $container = $this.closest(".elementor-widget-container"),
8462      $wcpt = $container.find(".wcpt");
8463
8464    $wcpt.trigger("wcpt_layout", { source: "elementor__tab" });
8465  });
8466
8467  // -- divi
8468  // -- -- tab
8469  $("ul.et_pb_tabs_controls > li").on("click", function () {
8470    var $this = $(this),
8471      index = $this.index(),
8472      $controls = $this.closest(".et_pb_tabs_controls"),
8473      $tabs = $controls.siblings(".et_pb_all_tabs"),
8474      $container = $tabs.children().eq(index),
8475      $wcpt = $container.find(".wcpt");
8476
8477    setTimeout(function () {
8478      $wcpt.trigger("wcpt_layout", { source: "divi__tab" });
8479    }, 700);
8480  });
8481  // -- -- accordion
8482  $(".et_pb_toggle_title").on("click", function () {
8483    var $this = $(this),
8484      $content = $this.next(".et_pb_toggle_content"),
8485      $wcpt = $content.find(".wcpt");
8486
8487    setTimeout(function () {
8488      $wcpt.trigger("wcpt_layout", { source: "divi__tab" });
8489    }, 700);
8490  });
8491
8492  // -- beaver builder
8493  $("body").on("click", ".fl-accordion-button, .fl-tabs-label", function () {
8494    var $this = $(this),
8495      $container = $this.closest(".fl-accordion-item, .fl-tabs-panel"),
8496      $wcpt = $container.find(".wcpt");
8497
8498    $wcpt.trigger("wcpt_layout", { source: "beaver_builder__tab" });
8499  });
8500
8501  // -- shortcode ultimate
8502  // -- -- tab
8503  $("body").on("click", ".su-tabs-nav span", function () {
8504    var $this = $(this),
8505      index = $this.index(),
8506      $nav = $this.closest(".su-tabs-nav"),
8507      $panes = $nav.siblings(".su-tabs-panes"),
8508      $container = $panes.children().eq(index),
8509      $wcpt = $container.find(".wcpt");
8510
8511    $wcpt.trigger("wcpt_layout", { source: "shortcode_ultimate__tab" });
8512  });
8513  // -- -- accordion
8514  $("body").on("click", ".su-spoiler-title", function () {
8515    var $this = $(this),
8516      $container = $this.closest(".su-spoiler"),
8517      $wcpt = $container.find(".wcpt");
8518
8519    $wcpt.trigger("wcpt_layout", {
8520      source: "shortcode_ultimate__accordion",
8521    });
8522  });
8523
8524  // -- wp bakery visual composer
8525  // -- -- tab
8526  $("body").on("click", ".vce-classic-tabs-tab", function () {
8527    var $this = $(this),
8528      index = $this.index(),
8529      $nav = $this.closest(".vce-classic-tabs-container"),
8530      $panes = $nav.siblings(".vce-classic-tabs-panels-container"),
8531      $container = $panes
8532        .children(".vce-classic-tabs-panels")
8533        .children()
8534        .eq(index),
8535      $wcpt = $container.find(".wcpt");
8536
8537    setTimeout(function () {
8538      $wcpt.trigger("wcpt_layout", { source: "visual_composer__tab" });
8539    }, 1);
8540  });
8541  // -- -- accordion (1)
8542  $("body").on("click", ".vce-classic-accordion-panel-heading", function () {
8543    var $this = $(this),
8544      $container = $this.closest(".vce-classic-accordion-panel"),
8545      $wcpt = $container.find(".wcpt");
8546    setTimeout(function () {
8547      $wcpt.trigger("wcpt_layout", {
8548        source: "visual_composer__accordion",
8549      });
8550    }, 1);
8551  });
8552  // -- -- accordion (2)
8553  $("body").on("click", ".vc_tta-panel-heading", function () {
8554    var $this = $(this),
8555      $container = $this.closest(".vc_tta-panel"),
8556      $wcpt = $container.find(".wcpt");
8557    setTimeout(function () {
8558      $wcpt.trigger("wcpt_layout", {
8559        source: "visual_composer__accordion",
8560      });
8561    }, 1);
8562  });
8563
8564  // -- responsive accordion and collapse
8565  $("body").on("click", ".wpsm_panel-heading", function () {
8566    var $this = $(this),
8567      $container = $this.closest(".wpsm_panel"),
8568      $content = $container.find(".wpsm_panel-collapse");
8569
8570    $(".wcpt", $content).trigger("wcpt_layout", {
8571      source: "rac__accordion",
8572    });
8573  });
8574
8575  // -- king composer
8576  $("body").on("click", ".kc_accordion_header", function () {
8577    var $this = $(this),
8578      $container = $this.closest(".kc_accordion_section"),
8579      $content = $container.find(".kc_accordion_content");
8580
8581    $(".wcpt", $content).trigger("wcpt_layout", {
8582      source: "king_composer__accordion",
8583    });
8584  });
8585
8586  // -- helpie faq
8587  $("body").on("mouseup touchend", ".accordion__header", function () {
8588    var $this = $(this),
8589      $container = $this.closest(".accordion__item"),
8590      $content = $container.find(".accordion__body");
8591
8592    clearTimeout(window.wcpt_helpie_timeout);
8593    window.wcpt_helpie_timeout = setTimeout(function () {
8594      $(".wcpt:visible", $content).trigger("wcpt_layout", {
8595        source: "helpie_faq__accordion",
8596      });
8597    }, 200);
8598  });
8599
8600  // -- tabs (wpshopmart)
8601  $("body").on("click", ".wpsm_nav li", function () {
8602    var $this = $(this),
8603      index = $this.index(),
8604      $nav = $this.closest(".wpsm_nav"),
8605      $panes = $nav.siblings(".tab-content"),
8606      $container = $panes.children().eq(index),
8607      $wcpt = $container.find(".wcpt");
8608
8609    $wcpt.trigger("wcpt_layout", { source: "tabs_wpshopmart__tab" });
8610  });
8611
8612  // -- tabby responsive tabs
8613  $("body").on("click", ".tabtitle", function () {
8614    var $this = $(this),
8615      $panel = $this.siblings(".tabcontent");
8616
8617    $(".wcpt", $panel).trigger("wcpt_layout", { source: "tabby_tab" });
8618  });
8619
8620  $("body").on("click", ".responsive-tabs__list__item", function () {
8621    var $this = $(this),
8622      index = $this.index(),
8623      $container = $this.closest(".responsive-tabs"),
8624      $panel = $(".tabcontent", $container).eq(index);
8625
8626    $(".wcpt", $panel).trigger("wcpt_layout", { source: "tabby_tab" });
8627  });
8628
8629  // -- kadence blocks
8630  $("body").on("click", ".kt-title-item", function () {
8631    var $this = $(this),
8632      $tab_container = $this.closest(".kt-tabs-wrap");
8633
8634    setTimeout(function () {
8635      $tab_container.find(".wcpt:visible").trigger("wcpt_layout");
8636    }, 100);
8637  });
8638
8639  // sonaar
8640  // -- audio player play / pause
8641  $("body").on("click", ".wcpt-player--sonaar", function () {
8642    var $this = $(this),
8643      playlist_id = $this.attr("data-wcpt-sonaar-playlist-id");
8644
8645    if (typeof IRON == "undefined" || !playlist_id) {
8646      return;
8647    }
8648
8649    $(".wcpt-player--sonaar").not($this).removeClass("wcpt-player--playing");
8650
8651    // pause
8652    if (
8653      IRON.sonaar.player.isPlaying &&
8654      IRON.sonaar.player.playlistID == playlist_id
8655    ) {
8656      IRON.sonaar.player.pause();
8657      $this.removeClass("wcpt-player--playing wcpt-media-loaded");
8658    } else {
8659      $this.addClass("wcpt-player--playing wcpt-media-loaded");
8660
8661      IRON.sonaar.player.setPlayer({
8662        id: playlist_id,
8663        autoplay: true,
8664        soundwave: true,
8665      });
8666    }
8667  });
8668
8669  // -- auto set play / pause status after every load
8670  function sonaar_player_auto_status() {
8671    if (typeof IRON == "undefined") {
8672      return;
8673    }
8674
8675    var playlist_id = IRON.sonaar.player.playlistID;
8676
8677    if (playlist_id) {
8678      $(
8679        '.wcpt-player--sonaar[data-wcpt-sonaar-playlist-id="' +
8680          playlist_id +
8681          '"]',
8682      ).addClass("wcpt-player--playing wcpt-media-loaded");
8683    }
8684  }
8685
8686  // TI Wishlist integration
8687
8688  if (typeof wcpt_ti_wishlist_ids !== "undefined") {
8689    // update counter widget
8690    setTimeout(function () {
8691      $(".wishlist_products_counter_number").text(wcpt_ti_wishlist_ids.length);
8692      if (wcpt_ti_wishlist_ids.length) {
8693        $(".wishlist_products_counter").addClass(
8694          "wishlist-counter-with-products",
8695        );
8696      } else {
8697        $(".wishlist_products_counter").removeClass(
8698          "wishlist-counter-with-products",
8699        );
8700      }
8701    }, 100);
8702
8703    // wishlist button
8704    $("body").on("click", ".wcpt-wishlist", function (e) {
8705      var $this = $(this),
8706        $row = $this.closest(".wcpt-row"),
8707        $wcpt = $this.closest(".wcpt"),
8708        product_type = $row.attr("data-wcpt-type"),
8709        product_id = $row.attr("data-wcpt-product-id"),
8710        variation_id = $row.attr("data-wcpt-variation-id")
8711          ? $row.attr("data-wcpt-variation-id")
8712          : $row.data("wcpt_variation_id")
8713            ? $row.data("wcpt_variation_id")
8714            : false,
8715        variable_permitted = $this.attr("data-wcpt-variable-permitted");
8716
8717      // variable product must have variation selected
8718      if (product_type == "variable" && !variable_permitted && !variation_id) {
8719        alert("Please select some options first");
8720        return;
8721      }
8722
8723      var list_id = variation_id ? variation_id : product_id;
8724
8725      $this.addClass("wcpt-loading");
8726
8727      var data = {
8728        tinv_wishlist_id: false,
8729        tinv_wishlist_name: false,
8730        product_type: $row.attr("data-wcpt-type"),
8731        product_id: $row.attr("data-wcpt-product-id"),
8732        product_variation: variation_id,
8733        redirect: false,
8734      };
8735
8736      if (variation_id) {
8737        var attributes =
8738          data.product_type === "variable"
8739            ? $row.data("wcpt_attributes")
8740            : JSON.parse($row.attr("data-wcpt-variation-attributes"));
8741
8742        $.extend(data, {
8743          form: $.extend(
8744            {
8745              quantity: 1,
8746              product_id: product_id,
8747              variation_id: variation_id,
8748            },
8749            attributes,
8750          ),
8751        });
8752      }
8753
8754      // add
8755      if (-1 === wcpt_ti_wishlist_ids.indexOf(list_id)) {
8756        $.ajax({
8757          url: wcpt_params.ajax_url,
8758          method: "POST",
8759          beforeSend: function () {
8760            // variation added from variable product, refresh page to show it
8761            if (
8762              product_type === "variable" &&
8763              $wcpt.data("wcpt_sc_attrs").ti_wishlist
8764            ) {
8765              $wcpt.addClass("wcpt-loading");
8766            }
8767          },
8768          data: $.extend({}, { product_action: "addto" }, data),
8769        }).done(function (response) {
8770          // variation added from variable product, refresh page to show it
8771          if (
8772            product_type === "variable" &&
8773            $wcpt.data("wcpt_sc_attrs").ti_wishlist
8774          ) {
8775            window.location.reload();
8776          }
8777
8778          wcpt_ti_wishlist_update_row_view($row);
8779
8780          wcpt_ti_wishlist_growler({
8781            name: $this.attr("data-wcpt-product-name"),
8782            view_wishlist_label: $this.attr("data-wcpt-view-wishlist-label"),
8783            item_added_label: $this.attr("data-wcpt-item-added-label"),
8784            icon: $this.attr("data-wcpt-icon"),
8785            url: $this.attr("data-wcpt-custom-url"),
8786            duration_seconds: $this.attr("data-wcpt-duration-seconds"),
8787          });
8788
8789          // update counter widget
8790          if (response && typeof response.wishlists_data !== "undefined") {
8791            $(".wishlist_products_counter_number").text(
8792              response.wishlists_data.counter,
8793            );
8794            if (response.wishlists_data.counter) {
8795              $(".wishlist_products_counter").addClass(
8796                "wishlist-counter-with-products",
8797              );
8798            } else {
8799              $(".wishlist_products_counter").removeClass(
8800                "wishlist-counter-with-products",
8801              );
8802            }
8803          }
8804        });
8805
8806        // add to maintained list as well
8807        wcpt_ti_wishlist_ids.push(list_id);
8808
8809        // update UI
8810        $this.addClass("wcpt-active");
8811
8812        // remove
8813      } else {
8814        $.ajax({
8815          url: wcpt_params.ajax_url,
8816          method: "POST",
8817          data: $.extend({}, { product_action: "remove" }, data),
8818        }).done(function (response) {
8819          // update counter widget
8820          if (response && typeof response.wishlists_data !== "undefined") {
8821            $(".wishlist_products_counter_number").text(
8822              response.wishlists_data.counter,
8823            );
8824            if (response.wishlists_data.counter) {
8825              $(".wishlist_products_counter").addClass(
8826                "wishlist-counter-with-products",
8827              );
8828            } else {
8829              $(".wishlist_products_counter").removeClass(
8830                "wishlist-counter-with-products",
8831              );
8832            }
8833          }
8834        });
8835
8836        // remove from maintained list as well
8837        var index = wcpt_ti_wishlist_ids.indexOf(list_id);
8838        if (index > -1) {
8839          wcpt_ti_wishlist_ids.splice(index, 1);
8840        }
8841
8842        wcpt_ti_wishlist_update_row_view($row);
8843
8844        // update UI
8845        // $this.removeClass('wcpt-active');
8846
8847        // remove product row from wishlist table
8848        if ($wcpt.data("wcpt_sc_attrs").ti_wishlist) {
8849          var $remove_row = $row;
8850
8851          // don't remove variable product if it was being used to remove a variation only
8852          if (variation_id) {
8853            $remove_row = $wcpt.find(
8854              '.wcpt-product-type-variation[data-wcpt-variation-id="' +
8855                variation_id +
8856                '"]',
8857            );
8858          }
8859
8860          $remove_row.addClass("wcpt-wishlist-removing-row");
8861          setTimeout(function () {
8862            $remove_row.remove();
8863          }, 500);
8864        }
8865      }
8866    });
8867
8868    function wcpt_ti_wishlist_growler(data) {
8869      var template = $("#wcpt-ti-wishlist-growler-template").html(),
8870        $growler = $(template.replace("{n}", '"' + data.name + '"')),
8871        reveal_class = "wcpt-ti-wishlist-growler--revealed",
8872        duration_ms = data.duration_seconds * 1000;
8873
8874      $growler.attr("data-wcpt-icon", data.icon);
8875      $(".wcpt-ti-wishlist-growler").remove(); // remove previous
8876
8877      if (data.item_added_label) {
8878        $(".wcpt-ti-wishlist-growler__label--item-added", $growler).text(
8879          data.item_added_label.replace("{n}", '"' + data.name + '"'),
8880        );
8881      }
8882
8883      if (data.view_wishlist_label) {
8884        $(".wcpt-ti-wishlist-growler__label--view-wishlist", $growler).text(
8885          data.view_wishlist_label.replace("{n}", '"' + data.name + '"'),
8886        );
8887      }
8888
8889      if (data.url) {
8890        $growler.attr("href", data.url);
8891      } else if (wcpt_ti_wishlist_url) {
8892        $growler.attr("href", wcpt_ti_wishlist_url);
8893      }
8894
8895      $("body").append($growler);
8896
8897      setTimeout(function () {
8898        $growler.addClass(reveal_class);
8899      }, 100);
8900
8901      setTimeout(function () {
8902        $growler.removeClass(reveal_class);
8903      }, duration_ms);
8904
8905      setTimeout(function () {
8906        $growler.remove();
8907      }, duration_ms + 500);
8908    }
8909
8910    $("body").on("wcpt_after_every_load", ".wcpt", function () {
8911      var $this__container = $(this),
8912        $visible_rows = wcpt_util
8913          .get_uninit_rows($this__container)
8914          .filter(":visible");
8915
8916      // variable products in wishlist were definitely added without variation selected
8917      if (wcpt_util.get_sc_attrs($this__container).ti_wishlist) {
8918        $visible_rows
8919          .filter(".wcpt-product-type-variable")
8920          .trigger("select_variation", {
8921            variation_id: false,
8922            complete_match: false,
8923            attributes: false,
8924            variation: false,
8925            variation_found: false,
8926            variation_selected: false,
8927            variation_available: false,
8928          });
8929      }
8930
8931      $visible_rows.each(function () {
8932        var $this__container = $(this);
8933        wcpt_ti_wishlist_update_row_view($this__container);
8934      });
8935    });
8936
8937    $("body").on("select_variation", ".wcpt-row", function () {
8938      var $this = $(this);
8939      wcpt_ti_wishlist_update_row_view($this);
8940    });
8941
8942    function wcpt_ti_wishlist_update_row_view($row) {
8943      var product_type = $row.attr("data-wcpt-type"),
8944        product_id = $row.attr("data-wcpt-product-id"),
8945        variation_id = $row.data("wcpt_variation_id")
8946          ? $row.data("wcpt_variation_id")
8947          : $row.attr("data-wcpt-variation-id")
8948            ? $row.attr("data-wcpt-variation-id")
8949            : false,
8950        $buttons = $(".wcpt-wishlist", wcpt_get_sibling_rows($row)),
8951        variable_permitted = $buttons.attr("data-wcpt-variable-permitted");
8952
8953      if (product_type === "variable" && !variable_permitted && !variation_id) {
8954        $buttons.removeClass("wcpt-active").addClass("wcpt-disabled");
8955
8956        return;
8957      }
8958
8959      $buttons.removeClass("wcpt-disabled wcpt-loading");
8960
8961      var list_id = variation_id ? variation_id : product_id;
8962
8963      if (-1 === wcpt_ti_wishlist_ids.indexOf(list_id + "")) {
8964        $buttons.removeClass("wcpt-active");
8965      } else {
8966        $buttons.addClass("wcpt-active");
8967      }
8968    }
8969  }
8970
8971  // WooCommerce Wholesale Prices
8972
8973  // -- select_variation handler to switch vals in wcpt_wholesale
8974  $("body").on("select_variation", ".wcpt-row", function () {
8975    var $this = $(this),
8976      variation = $this.data("wcpt_variation"),
8977      $wholesale_shortcode = $(".wcpt-wholesale", $this);
8978
8979    $wholesale_shortcode.each(function () {
8980      var $this = $(this);
8981
8982      // set the view: default or variation
8983      $this.removeClass(
8984        "wcpt-wholesale--variation-view-enabled wcpt-wholesale--default-view-enabled",
8985      );
8986
8987      if (variation) {
8988        $this.addClass("wcpt-wholesale--variation-view-enabled");
8989      } else {
8990        $this.addClass("wcpt-wholesale--default-view-enabled");
8991      }
8992
8993      // wholesale table
8994      if ($this.hasClass("wcpt-wholesale--wholesale-table")) {
8995        var variation_html = "-";
8996
8997        if (variation && variation.wholesale_price) {
8998          var table_html_match =
8999            variation.price_html.match(/(<table.+table)>/s);
9000          if (table_html_match) {
9001            variation_html = table_html_match[0];
9002          }
9003        }
9004
9005        $this.html(variation_html);
9006      }
9007
9008      // wholesale price
9009      if ($this.hasClass("wcpt-wholesale--wholesale-price")) {
9010        var variation_text = "-";
9011
9012        if (variation) {
9013          var wholesale_price = "";
9014          if (variation.wholesale_price) {
9015            wholesale_price = variation.wholesale_price;
9016          }
9017          variation_text = format_price(wholesale_price);
9018        }
9019
9020        $(".wcpt-wholesale__variation-view", $this).text(variation_text);
9021      }
9022
9023      // original price
9024      if ($this.hasClass("wcpt-wholesale--original-price")) {
9025        var variation_text = "-";
9026
9027        if (variation) {
9028          var original_price = "";
9029          if (variation.wholesale_price) {
9030            original_price = variation.original_display_price; // cannot use display_price here because it is set to wholesale_price by WCPT
9031          } else {
9032            original_price = variation.display_price;
9033          }
9034          variation_text = format_price(original_price);
9035        }
9036
9037        $(".wcpt-wholesale__variation-view", $this).text(variation_text);
9038      }
9039
9040      // wholesale label
9041      if ($this.hasClass("wcpt-wholesale--wholesale-label")) {
9042        $this.removeClass(
9043          "wcpt-wholesale--variation-is-on-wholesale-view-enabled wcpt-wholesale--variation-is-not-on-wholesale-view-enabled",
9044        );
9045        if (variation) {
9046          if (variation.wholesale_price) {
9047            $this.addClass(
9048              "wcpt-wholesale--variation-is-on-wholesale-view-enabled",
9049            );
9050          } else {
9051            $this.addClass(
9052              "wcpt-wholesale--variation-is-not-on-wholesale-view-enabled",
9053            );
9054          }
9055        }
9056      }
9057    });
9058  });
9059
9060  // Variation Swatches
9061  $("body").on(
9062    "change",
9063    ".wcpt-row .variations_form .variation_id",
9064    function () {
9065      var $this = $(this),
9066        $form = $this.closest(".variations_form");
9067
9068      if ($form.hasClass("wvs-loaded")) {
9069        get_select_variation_from_cart_form($form);
9070      }
9071    },
9072  );
9073
9074  // init tables
9075  var $wcpt = $(".wcpt");
9076  if ($wcpt.length) {
9077    $wcpt.each(function () {
9078      after_every_load($(this));
9079    });
9080  }
9081
9082  // lazy load
9083  lazy_load_start();
9084
9085  // init cart widget
9086  var cart_init_required = false;
9087
9088  if (
9089    document.cookie.indexOf("woocommerce_items_in_cart") !== -1 ||
9090    (typeof wcpt_cart_result_cache !== "undefined" &&
9091      wcpt_cart_result_cache.cart_quantity)
9092  ) {
9093    cart_init_required = true;
9094  }
9095
9096  if (cart_init_required) {
9097    wcpt_cart({
9098      payload: { skip_cart_triggers: true },
9099    });
9100  }
9101
9102  $(window).on("pageshow", function (e) {
9103    // trigger cart events
9104    if (e.originalEvent.persisted) {
9105      wcpt_cart({
9106        payload: { skip_cart_triggers: true },
9107      });
9108    }
9109
9110    // reset select variation to default
9111    $(".wcpt-select-variation-dropdown").each(function () {
9112      var $this = $(this),
9113        $row = $this.closest(".wcpt-row"),
9114        variation_id = $row.data("wcpt_variation_id");
9115
9116      if (variation_id) {
9117        $this.val(variation_id);
9118      } else {
9119        $this.val("");
9120      }
9121    });
9122  });
9123});
9124
9125// module: child row
9126(function ($) {
9127  /**
9128   * Accordion mode variable. Set to true to allow only one child row open at a time.
9129   */
9130  var accordion_mode = true; // set this variable as needed
9131  var child_row_instance_counter = 0;
9132
9133  function getChildRowContainerSelector($container) {
9134    var instanceId = $container.attr("data-wcpt-child-row-instance-id");
9135
9136    if (!instanceId) {
9137      child_row_instance_counter += 1;
9138      instanceId = "wcpt-child-row-" + child_row_instance_counter;
9139      $container.attr("data-wcpt-child-row-instance-id", instanceId);
9140    }
9141
9142    return '.wcpt[data-wcpt-child-row-instance-id="' + instanceId + '"]';
9143  }
9144
9145  /**
9146   * Register event handlers for child row logic
9147   */
9148  $("body")
9149    // Initialize all child row DOM logic after table load
9150    .on("wcpt_after_every_load", ".wcpt", initChildRows)
9151    // Each child row: register parent/child data binding and set background
9152    .on("wcpt_init_child_row", ".wcpt-child-row", registerParentChild)
9153    .on("wcpt_init_child_row", ".wcpt-child-row", setChildRowBg)
9154    // Click: toggle child rows by explicit control
9155    .on("click", ".wcpt-child-row-toggle", toggleChildRow)
9156    // Click: anywhere in designated rows, except on interactive elements
9157    .on("click", ".wcpt-has-child-row--click-anywhere", toggleChildRowAnywhere)
9158    // Hover state handlers (parent/child highlighting)
9159    .on("mouseenter", ".wcpt-has-child-row", parentRowMouseenter)
9160    .on("mouseleave", ".wcpt-has-child-row", parentRowMouseleave)
9161    .on("mouseenter", ".wcpt-child-row", childRowMouseenter)
9162    .on("mouseleave", ".wcpt-child-row", childRowMouseleave);
9163
9164  // In pages with multiple tables, some instances can be rendered before this
9165  // module attaches wcpt_after_every_load listeners. Initialize existing tables.
9166  $(function () {
9167    $(".wcpt").each(function () {
9168      initChildRows.call(this);
9169    });
9170  });
9171
9172  /**
9173   * Init all child rows in the current .wcpt table,
9174   * triggers event to register proper properties
9175   */
9176  function initChildRows() {
9177    var $container = $(this);
9178
9179    ensureParentChildBindings($container);
9180
9181    $(".wcpt-child-row:not(.wcpt-child-row--init)", $container).each(
9182      function () {
9183        var $row = $(this);
9184        $row.trigger("wcpt_init_child_row").addClass("wcpt-child-row--init");
9185      },
9186    );
9187  }
9188
9189  function ensureParentChildBindings($container) {
9190    $container.find(".wcpt-child-row").each(function () {
9191      var $child = $(this);
9192      var $parent = $child.prev(".wcpt-row");
9193
9194      if (!$parent.length) return;
9195
9196      if (!$child.data("wcpt_parent_row")) {
9197        $child.data("wcpt_parent_row", $parent);
9198      }
9199
9200      if (!$parent.data("wcpt_child_row")) {
9201        $parent.data("wcpt_child_row", $child);
9202      }
9203    });
9204  }
9205
9206  /**
9207   * Binds parent <-> child row references for easier later access
9208   */
9209  function registerParentChild() {
9210    var $child = $(this),
9211      $parent = $child.prev();
9212
9213    $child.data("wcpt_parent_row", $parent);
9214    $parent.data("wcpt_child_row", $child);
9215  }
9216
9217  /**
9218   * Sets background color of a child row to match its parent's first cell (or whole row)
9219   */
9220  function setChildRowBg() {
9221    var $child = $(this),
9222      $parent = $child.data("wcpt_parent_row");
9223
9224    // fallback safely in case parent not found
9225    if (!$parent || !$parent.length) return;
9226
9227    var parentBg = $parent.css("background-color") || "",
9228      firstCellBg = $("> td:first-child", $parent).css("background-color"),
9229      chosenBg =
9230        firstCellBg && firstCellBg !== "rgba(0, 0, 0, 0)"
9231          ? firstCellBg
9232          : parentBg;
9233
9234    $("> td", $child).css("background-color", chosenBg);
9235  }
9236
9237  /**
9238   * Ensures all cells in table have stable width when child row opened (for visual stability)
9239   */
9240  function applyColumnWidthStyles($container) {
9241    var $firstOpenRow = $container
9242      .find(
9243        ".wcpt-row:has(td.wcpt-child-row-toggle:not(.wcpt-child-row-toggle--closed))",
9244      )
9245      .first();
9246    var styleHtml = '<style class="wcpt-child-row-width">';
9247    var containerSelector = getChildRowContainerSelector($container);
9248    if ($firstOpenRow.length) {
9249      $firstOpenRow.children(".wcpt-cell").each(function (index) {
9250        var width = $(this).outerWidth();
9251        // Scope styles to a unique table instance on the page.
9252        styleHtml +=
9253          containerSelector +
9254          " .wcpt-row > .wcpt-cell:nth-child(" +
9255          (index + 1) +
9256          ") { width: " +
9257          width +
9258          "px !important; }";
9259      });
9260      styleHtml += "</style>";
9261      $container.find("style.wcpt-child-row-width").remove();
9262      $container.append(styleHtml);
9263    }
9264  }
9265
9266  /**
9267   * Main click handler: toggles child row open/closed for a given row/cell or header cell.
9268   * Supports accordion_mode: if true, only one child row stays open at a time (unless opening all).
9269   */
9270  function toggleChildRow(e) {
9271    var $clicked = $(this),
9272      $container = $clicked.closest(".wcpt");
9273
9274    // Reconcile bindings if this table was rendered before child-row init hooks.
9275    ensureParentChildBindings($container);
9276
9277    // Detect if this is a table heading (bulk open/close)
9278    var isHeader = !$clicked.is("td");
9279
9280    if (isHeader) {
9281      // Header toggle always continues as normal: opens/closes all
9282      toggleAllChildRows(
9283        $container,
9284        !$clicked.hasClass("wcpt-child-row-toggle--closed"),
9285      );
9286      updateRowVisibilityMarks($container);
9287      handleColumnWidths(
9288        $container,
9289        !$clicked.hasClass("wcpt-child-row-toggle--closed"),
9290      );
9291      $clicked.toggleClass("wcpt-child-row-toggle--closed");
9292      return;
9293    }
9294
9295    var wasClosed = $clicked.hasClass("wcpt-child-row-toggle--closed");
9296
9297    // If accordion mode is on, and we're opening a row (not closing), close any other open rows first (excluding this one)
9298    // if (accordion_mode && wasClosed) {
9299    if (wasClosed) {
9300      // Find all open toggles except the one being clicked
9301      $container
9302        .find("td.wcpt-child-row-toggle:not(.wcpt-child-row-toggle--closed)")
9303        .each(function () {
9304          var $otherTd = $(this);
9305          if (!$otherTd.is($clicked)) {
9306            // Only close if not this one
9307            var $otherRow = $otherTd.closest(".wcpt-row");
9308            var $otherChildRow = $otherRow.data("wcpt_child_row");
9309            $otherTd.addClass("wcpt-child-row-toggle--closed");
9310            if ($otherChildRow && $otherChildRow.length) {
9311              var $otherWrapper = $otherChildRow.find(
9312                ".wcpt-child-row__wrapper",
9313              );
9314              $otherWrapper.slideUp();
9315            }
9316          }
9317        });
9318    }
9319
9320    // Now toggle clicked row as usual
9321    $clicked.toggleClass("wcpt-child-row-toggle--closed");
9322    var isClosedNow = $clicked.hasClass("wcpt-child-row-toggle--closed");
9323
9324    // Handle column width stabilization
9325    handleColumnWidths($container, !isClosedNow);
9326
9327    // Single row toggle logic
9328    toggleSingleChildRow($clicked, $container, isClosedNow);
9329
9330    updateRowVisibilityMarks($container);
9331  }
9332
9333  /**
9334   * Ensures all columns keep their current width (optional: only when opening).
9335   */
9336  function handleColumnWidths($container, openingOne) {
9337    var $openRows = $container.find(
9338      ".wcpt-row:has(td.wcpt-child-row-toggle:not(.wcpt-child-row-toggle--closed))",
9339    );
9340    var $style = $container.find("style.wcpt-child-row-width");
9341    var containerSelector = getChildRowContainerSelector($container);
9342
9343    if (openingOne && $openRows.length) {
9344      // Lock cell widths according to the first row with open toggle
9345      var $row = $openRows.first();
9346      var styleHtml = '<style class="wcpt-child-row-width">';
9347      $row.children(".wcpt-cell").each(function (i) {
9348        styleHtml +=
9349          containerSelector +
9350          " .wcpt-row > .wcpt-cell:nth-child(" +
9351          (i + 1) +
9352          ") { width: " +
9353          $(this).outerWidth() +
9354          "px !important; }";
9355      });
9356      styleHtml += "</style>";
9357      $style.remove();
9358      $container.append(styleHtml);
9359    } else {
9360      // Only remove width style if ALL now closed
9361      var allClosed =
9362        $container.find("td.wcpt-child-row-toggle").length ===
9363        $container.find(
9364          "td.wcpt-child-row-toggle.wcpt-child-row-toggle--closed",
9365        ).length;
9366      if (allClosed) {
9367        $style.remove();
9368      } else {
9369        applyColumnWidthStyles($container); // fallback to routine (already defined)
9370      }
9371    }
9372  }
9373
9374  /**
9375   * Toggle a single row's child row visibility and header icon state.
9376   */
9377  function toggleSingleChildRow($clicked, $container, isClosedNow) {
9378    var $row = $clicked.closest(".wcpt-row");
9379    var $childRow = $row.data("wcpt_child_row");
9380
9381    if (
9382      (!$childRow || !$childRow.length) &&
9383      $row.next().hasClass("wcpt-child-row")
9384    ) {
9385      $childRow = $row.next();
9386      $row.data("wcpt_child_row", $childRow);
9387      $childRow.data("wcpt_parent_row", $row);
9388    }
9389
9390    if ($childRow && $childRow.length) {
9391      var $wrapper = $childRow.find(".wcpt-child-row__wrapper");
9392      if ($row.hasClass("wcpt-has-child-row--visible")) {
9393        $wrapper.slideUp();
9394      } else {
9395        $wrapper.slideDown();
9396      }
9397    }
9398
9399    // Sync header toggle icon: open header icon if ANY row is open, else close header icon
9400    var anyOpen =
9401      $container.find(
9402        "td.wcpt-child-row-toggle:not(.wcpt-child-row-toggle--closed)",
9403      ).length > 0;
9404    var $headerToggles = $container.find("th.wcpt-child-row-toggle");
9405    if (anyOpen) {
9406      $headerToggles.removeClass("wcpt-child-row-toggle--closed");
9407    } else {
9408      $headerToggles.addClass("wcpt-child-row-toggle--closed");
9409    }
9410  }
9411
9412  /**
9413   * Toggle all child rows via the header toggle cell.
9414   * Support "close all" even if accordion_mode is enabled.
9415   */
9416  function toggleAllChildRows($container, closing) {
9417    var $tdToggles = $container.find("td.wcpt-child-row-toggle");
9418    $tdToggles.each(function () {
9419      var $td = $(this);
9420      var $row = $td.closest(".wcpt-row");
9421      var $childRow = $row.data("wcpt_child_row");
9422      var $wrapper = $childRow
9423        ? $childRow.find(".wcpt-child-row__wrapper")
9424        : $();
9425      if (closing) {
9426        if (!$td.hasClass("wcpt-child-row-toggle--closed")) {
9427          $td.addClass("wcpt-child-row-toggle--closed");
9428          if ($childRow && $wrapper.length) $wrapper.slideUp();
9429        }
9430      } else {
9431        if ($td.hasClass("wcpt-child-row-toggle--closed")) {
9432          $td.removeClass("wcpt-child-row-toggle--closed");
9433          if ($childRow && $wrapper.length) $wrapper.slideDown();
9434        }
9435      }
9436    });
9437    // If opening bulk, re-apply width stabilization
9438    if (!closing) {
9439      applyColumnWidthStyles($container);
9440    } else {
9441      // Always remove width stabilization on close all
9442      var $style = $container.find("style.wcpt-child-row-width");
9443      $style.remove();
9444    }
9445  }
9446
9447  /**
9448   * Add or remove .wcpt-has-child-row--visible on rows according to open toggle state (for CSS effects).
9449   */
9450  function updateRowVisibilityMarks($container) {
9451    $container.find("td.wcpt-child-row-toggle").each(function () {
9452      var $td = $(this);
9453      var $row = $td.closest(".wcpt-row");
9454      // Use a short delay to let animations run and class state stabilize
9455      setTimeout(function () {
9456        if ($td.hasClass("wcpt-child-row-toggle--closed")) {
9457          $row.removeClass("wcpt-has-child-row--visible");
9458        } else {
9459          $row.addClass("wcpt-has-child-row--visible");
9460        }
9461      }, 100);
9462    });
9463  }
9464
9465  /**
9466   * Handler: clicking anywhere in "click-anywhere" parent row will toggle child,
9467   * but not if clicking on interactive/child row controls or links.
9468   */
9469  function toggleChildRowAnywhere(e) {
9470    var $row = $(this).closest(".wcpt-row");
9471    var ignoreSelectors = [
9472      ".wcpt-child-row-toggle",
9473      "a",
9474      ".wcpt-tooltip",
9475      "input",
9476      "button",
9477      ".wcpt-button",
9478      ".wcpt-link",
9479      ".wcpt-quantity",
9480    ].join(", ");
9481    if (!$(e.target).closest(ignoreSelectors).length) {
9482      $(".wcpt-child-row-toggle", $row).click();
9483    }
9484  }
9485
9486  /** Highlighting logic for hover grouping (parent and child row highlight effects) **/
9487  function parentRowMouseenter() {
9488    var $parent = $(this),
9489      $child = $parent.data("wcpt_child_row");
9490    if ($child && $child.length) {
9491      $child.addClass("wcpt-parent-row-hovered");
9492    }
9493  }
9494
9495  function parentRowMouseleave() {
9496    var $parent = $(this),
9497      $child = $parent.data("wcpt_child_row");
9498    if ($child && $child.length) {
9499      $child.removeClass("wcpt-parent-row-hovered");
9500    }
9501  }
9502
9503  function childRowMouseenter() {
9504    var $child = $(this),
9505      $parent = $child.data("wcpt_parent_row");
9506    if ($parent && $parent.length) {
9507      $parent.addClass("wcpt-child-row-hovered");
9508    }
9509  }
9510
9511  function childRowMouseleave() {
9512    var $child = $(this),
9513      $parent = $child.data("wcpt_parent_row");
9514    if ($parent && $parent.length) {
9515      $parent.removeClass("wcpt-child-row-hovered");
9516    }
9517  }
9518})(jQuery);
9519
9520// module: instant search
9521(function ($) {
9522  $("body").on("wcpt_after_every_load", ".wcpt", init_instant_search);
9523
9524  // -- add instant search html class on $search and stop 'enter' key handler
9525  function init_instant_search() {
9526    var $container = $(this),
9527      $search = $(".wcpt-search-wrapper", $container);
9528
9529    if (
9530      wcpt_util.get_sc_attrs($container).instant_search &&
9531      !$search.hasClass("wcpt-instant-search")
9532    ) {
9533      $search.addClass("wcpt-instant-search");
9534      $(".wcpt-instant-search .wcpt-search-input", $container).on(
9535        "keydown",
9536        function (e) {
9537          if (e.keyCode === 13 || e.which === 13) {
9538            e.stopPropagation();
9539          }
9540        },
9541      );
9542    } else {
9543      $search.removeClass("wcpt-instant-search");
9544    }
9545  }
9546
9547  function get_instant_search_words(val) {
9548    return val.split(/\s+/).filter(Boolean);
9549  }
9550
9551  // Per-column text cache; each search word must match at least one column.
9552  function get_instant_search_cell_texts($row) {
9553    var texts = $row.data("wcpt_instant_search_cell_texts");
9554    if (!texts) {
9555      texts = [];
9556      $row.children(".wcpt-cell").each(function () {
9557        texts.push($(this).text().toLowerCase().trim());
9558      });
9559      if (!texts.length) {
9560        texts = [$row.text().toLowerCase().trim()];
9561      }
9562      $row.data("wcpt_instant_search_cell_texts", texts);
9563    }
9564    return texts;
9565  }
9566
9567  function row_matches_instant_search($row, val) {
9568    var words = get_instant_search_words(val),
9569      cellTexts = get_instant_search_cell_texts($row),
9570      w,
9571      c,
9572      wordMatch;
9573
9574    for (w = 0; w < words.length; w++) {
9575      wordMatch = false;
9576      for (c = 0; c < cellTexts.length; c++) {
9577        if (cellTexts[c].indexOf(words[w]) !== -1) {
9578          wordMatch = true;
9579          break;
9580        }
9581      }
9582      if (!wordMatch) {
9583        return false;
9584      }
9585    }
9586    return true;
9587  }
9588
9589  // -- search logic
9590  $("body").on(
9591    "keyup input",
9592    ".wcpt-instant-search .wcpt-search-input",
9593    function (e) {
9594      var $this = $(this),
9595        val = $this.val().toLowerCase().trim(),
9596        $wcpt = $this.closest(".wcpt"),
9597        $table = $(".wcpt-table:visible:not(.frzTbl-clone-table)", $wcpt),
9598        $rows = $(".wcpt-row", $table);
9599
9600      if (!val) {
9601        $rows.removeClass("wcpt-row--instant-search-hidden");
9602      } else {
9603        $rows.each(function () {
9604          var $this = $(this),
9605            match = row_matches_instant_search($this, val);
9606
9607          if (match) {
9608            $this.removeClass("wcpt-row--instant-search-hidden");
9609          } else {
9610            $this.addClass("wcpt-row--instant-search-hidden");
9611          }
9612
9613          // child row -- reveal both if either has match
9614          if ($this.hasClass("wcpt-child-row")) {
9615            $parent_row = $this.data("wcpt_parent_row");
9616
9617            if (
9618              !$this.hasClass("wcpt-row--instant-search-hidden") ||
9619              !$parent_row.hasClass("wcpt-row--instant-search-hidden")
9620            ) {
9621              $this
9622                .add($parent_row)
9623                .removeClass("wcpt-row--instant-search-hidden");
9624            }
9625          }
9626        });
9627
9628        // show category group headings if it has search results
9629        $rows.filter(".wcpt-row--category-heading").each(function () {
9630          var $this = $(this),
9631            cat_slug = $this.attr("data-wcpt-group-by-category-slug");
9632
9633          if (
9634            $rows.filter(
9635              '[data-wcpt-group-by-category-slug="' + cat_slug + '"]:visible',
9636            ).length
9637          ) {
9638            $this.removeClass("wcpt-row--instant-search-hidden");
9639          }
9640        });
9641      }
9642
9643      wcpt_util.assign_even_odd_row_classes($table);
9644    },
9645  );
9646})(jQuery);
9647
9648// module: instant sort
9649(function ($) {
9650  function get_instant_sort_params_for_heading(
9651    $sorting_icons,
9652    all_sort_params,
9653  ) {
9654    var current_sort_params = {};
9655
9656    if (!$sorting_icons || !$sorting_icons.length || !all_sort_params) {
9657      return current_sort_params;
9658    }
9659
9660    // Preferred: match by element id class (wcpt-{id})
9661    $.each(all_sort_params.column_heading || {}, function (id, params) {
9662      if ($sorting_icons.hasClass("wcpt-" + id)) {
9663        current_sort_params = $.extend({}, params);
9664        return false;
9665      }
9666    });
9667
9668    if (!$.isEmptyObject(current_sort_params) && current_sort_params.orderby) {
9669      return current_sort_params;
9670    }
9671
9672    // Fallback: match by data attributes on the sorting icons.
9673    // Needed when heading.sorting ids were regenerated and no longer match
9674    // the keys stored in data-wcpt-instant-sort-params.
9675    var orderby = $sorting_icons.attr("data-wcpt-orderby") || "",
9676      meta_key = (
9677        $sorting_icons.attr("data-wcpt-meta-key") || ""
9678      ).toLowerCase(),
9679      orderby_attribute = (
9680        $sorting_icons.attr("data-wcpt-orderby-attribute") || ""
9681      ).toLowerCase(),
9682      orderby_taxonomy = (
9683        $sorting_icons.attr("data-wcpt-orderby-taxonomy") || ""
9684      ).toLowerCase();
9685
9686    if (!orderby) {
9687      return {};
9688    }
9689
9690    $.each(all_sort_params.column_heading || {}, function (id, params) {
9691      if (!params || params.orderby !== orderby) {
9692        return;
9693      }
9694
9695      if (
9696        -1 !== $.inArray(orderby, ["meta_value", "meta_value_num"]) &&
9697        String(params.meta_key || "")
9698          .toLowerCase()
9699          .trim() !== meta_key
9700      ) {
9701        return;
9702      }
9703
9704      if (
9705        -1 !== $.inArray(orderby, ["attribute", "attribute_num"]) &&
9706        String(params.orderby_attribute || "")
9707          .toLowerCase()
9708          .trim() !== orderby_attribute
9709      ) {
9710        return;
9711      }
9712
9713      if (
9714        orderby === "taxonomy" &&
9715        String(params.orderby_taxonomy || "")
9716          .toLowerCase()
9717          .trim() !== orderby_taxonomy
9718      ) {
9719        return;
9720      }
9721
9722      current_sort_params = $.extend({}, params);
9723      return false;
9724    });
9725
9726    return current_sort_params;
9727  }
9728
9729  function get_instant_sort_heading_icons($wcpt, id, option_sort_params) {
9730    var $sorting_icons = $(".wcpt-" + id, $wcpt);
9731
9732    if ($sorting_icons.length) {
9733      return $sorting_icons;
9734    }
9735
9736    // Fallback when icon class id doesn't match the params key
9737    return $(".wcpt-sorting-icons", $wcpt).filter(function () {
9738      var $icons = $(this),
9739        orderby = $icons.attr("data-wcpt-orderby") || "",
9740        meta_key = ($icons.attr("data-wcpt-meta-key") || "").toLowerCase(),
9741        orderby_attribute = (
9742          $icons.attr("data-wcpt-orderby-attribute") || ""
9743        ).toLowerCase(),
9744        orderby_taxonomy = (
9745          $icons.attr("data-wcpt-orderby-taxonomy") || ""
9746        ).toLowerCase();
9747
9748      if (!option_sort_params || orderby !== option_sort_params.orderby) {
9749        return false;
9750      }
9751
9752      if (
9753        -1 !== $.inArray(orderby, ["meta_value", "meta_value_num"]) &&
9754        String(option_sort_params.meta_key || "")
9755          .toLowerCase()
9756          .trim() !== meta_key
9757      ) {
9758        return false;
9759      }
9760
9761      if (
9762        -1 !== $.inArray(orderby, ["attribute", "attribute_num"]) &&
9763        String(option_sort_params.orderby_attribute || "")
9764          .toLowerCase()
9765          .trim() !== orderby_attribute
9766      ) {
9767        return false;
9768      }
9769
9770      if (
9771        orderby === "taxonomy" &&
9772        String(option_sort_params.orderby_taxonomy || "")
9773          .toLowerCase()
9774          .trim() !== orderby_taxonomy
9775      ) {
9776        return false;
9777      }
9778
9779      return true;
9780    });
9781  }
9782
9783  // -- init
9784  $("body").on("wcpt_after_every_load", ".wcpt", init_instant_sort);
9785
9786  function init_instant_sort() {
9787    var $this = $(this),
9788      sc_attrs = $this.data("wcpt_sc_attrs");
9789
9790    if (sc_attrs.instant_sort) {
9791      // attach data
9792      var sort_data = [];
9793      $(
9794        ".wcpt-row:not(.wcpt-child-row):not(.wcpt-row--category-heading)",
9795        $this,
9796      ).each(function () {
9797        var $this = $(this),
9798          id = $this.attr("data-wcpt-product-id"),
9799          variation_id = $this.attr("data-wcpt-variation-id"),
9800          product_sort_data = $.extend(
9801            {},
9802            {
9803              id: id,
9804              variation_id: variation_id,
9805            },
9806            JSON.parse($this.attr("data-wcpt-instant-sort-props")),
9807          );
9808        sort_data.push(product_sort_data);
9809      });
9810
9811      $this.data("wcpt_sort_data", sort_data);
9812
9813      // replace handlers
9814      wcpt_util.run_once_on_container(
9815        $this,
9816        "wcpt_instant_sort_init",
9817        function ($container) {
9818          // -- column heading sort icons handler
9819
9820          // -- -- remove previous handlers and the init html class on container
9821          $container.off(
9822            "click.wcpt_sort_by_column_headings",
9823            ".wcpt-heading.wcpt-sortable",
9824          );
9825
9826          // -- -- attach new handlers
9827          $container.on(
9828            "click.wcpt_instant_sort",
9829            ".wcpt-heading.wcpt-sortable",
9830            function (e) {
9831              var $this = $(this),
9832                $sorting_icons = $(".wcpt-sorting-icons", $this),
9833                new_order = $sorting_icons.hasClass("wcpt-sorting-asc")
9834                  ? "desc"
9835                  : "asc",
9836                $wcpt = $this.closest(".wcpt"),
9837                all_sort_params = JSON.parse(
9838                  $wcpt.attr("data-wcpt-instant-sort-params"),
9839                ),
9840                current_sort_params = get_instant_sort_params_for_heading(
9841                  $sorting_icons,
9842                  all_sort_params,
9843                );
9844
9845              // If heading icons aren't linked to instant-sort params (id mismatch),
9846              // fall back to the regular AJAX column-heading sort.
9847              if (
9848                $.isEmptyObject(current_sort_params) ||
9849                !current_sort_params.orderby
9850              ) {
9851                return window.wcpt_column_heading_sort_handler.call(this, e);
9852              }
9853
9854              $.extend(current_sort_params, { order: new_order });
9855
9856              wcpt_instant_sort(current_sort_params, $wcpt);
9857
9858              instant_sort_ui_feedback(current_sort_params, $wcpt);
9859            },
9860          );
9861
9862          // -- 'Sort By' dropdown handler
9863          $container.on(
9864            "change.wcpt_instant_sort",
9865            '[data-wcpt-filter="sort_by"] input[type="radio"]',
9866            function (e) {
9867              e.stopPropagation(); // block access to wcpt regular sort handler
9868
9869              var $this = $(this),
9870                $option = $this.closest(".wcpt-option, .wcpt-dropdown-option"), // @TODO include dropdown option
9871                $wcpt = $this.closest(".wcpt"),
9872                all_sort_params = JSON.parse(
9873                  $wcpt.attr("data-wcpt-instant-sort-params"),
9874                ),
9875                index = $option.index(),
9876                current_sort_params = all_sort_params.dropdown[index];
9877
9878              wcpt_instant_sort(current_sort_params, $wcpt);
9879
9880              instant_sort_ui_feedback(current_sort_params, $wcpt);
9881            },
9882          );
9883        },
9884      );
9885    }
9886  }
9887
9888  function instant_sort_ui_feedback(current_sort_params, $wcpt) {
9889    var all_sort_params = JSON.parse(
9890      $wcpt.attr("data-wcpt-instant-sort-params"),
9891    );
9892
9893    if (
9894      -1 !== $.inArray(current_sort_params.orderby, ["rating", "price-desc"])
9895    ) {
9896      current_sort_params.order = "desc";
9897    }
9898
9899    if (current_sort_params.orderby == "price-desc") {
9900      current_sort_params.orderby = "price";
9901    }
9902
9903    // 'Sort By' dropdown (or row)
9904    $.each(
9905      all_sort_params["dropdown"],
9906      function (dropdown_option_index, option_sort_params) {
9907        if (
9908          -1 !== $.inArray(option_sort_params.orderby, ["rating", "price-desc"])
9909        ) {
9910          option_sort_params.order = "desc";
9911        }
9912
9913        if (option_sort_params.orderby == "price-desc") {
9914          option_sort_params.orderby = "price";
9915        }
9916
9917        if (
9918          current_sort_params.orderby !== option_sort_params.orderby ||
9919          (current_sort_params.order &&
9920            current_sort_params.order.toLowerCase() !==
9921              option_sort_params.order.toLowerCase()) ||
9922          (current_sort_params.orderby == "meta_key" &&
9923            current_sort_params.meta_key &&
9924            current_sort_params.meta_key.toLowerCase() !==
9925              option_sort_params.meta_key.toLowerCase()) ||
9926          (-1 !==
9927            $.inArray(current_sort_params.orderby, [
9928              "attribute",
9929              "attribute_num",
9930            ]) &&
9931            current_sort_params.orderby_attribute &&
9932            current_sort_params.orderby_attribute.toLowerCase() !==
9933              option_sort_params.orderby_attribute.toLowerCase()) ||
9934          (current_sort_params.orderby == "taxonomy" &&
9935            current_sort_params.orderby_taxonomy &&
9936            current_sort_params.orderby_taxonomy.toLowerCase() !==
9937              option_sort_params.orderby_taxonomy.toLowerCase())
9938        ) {
9939          return;
9940        }
9941
9942        var $dropdown = $('[data-wcpt-filter="sort_by"]', $wcpt), // might be row
9943          $selected_input = $dropdown.find("input").eq(dropdown_option_index),
9944          $selected_option = $selected_input.closest(
9945            ".wcpt-dropdown-option, .wcpt-option",
9946          ),
9947          $heading_label = $(".wcpt-dropdown-label", $dropdown);
9948
9949        $heading_label.text($("span", $selected_option).text());
9950        $selected_input.prop("checked", true);
9951        $selected_option
9952          .addClass("wcpt-active")
9953          .siblings()
9954          .removeClass("wcpt-active");
9955
9956        $dropdown.removeClass("wcpt-open");
9957      },
9958    );
9959
9960    // 'Sorting' column heading icons
9961    $.each(
9962      all_sort_params["column_heading"],
9963      function (id, option_sort_params) {
9964        if (
9965          current_sort_params.orderby !== option_sort_params.orderby ||
9966          ($.inArray(current_sort_params.orderby, [
9967            "meta_value",
9968            "meta_value_num",
9969          ]) &&
9970            current_sort_params.meta_key &&
9971            current_sort_params.meta_key.toLowerCase() !==
9972              option_sort_params.meta_key.toLowerCase()) ||
9973          (-1 !==
9974            $.inArray(current_sort_params.orderby, [
9975              "attribute",
9976              "attribute_num",
9977            ]) &&
9978            current_sort_params.orderby_attribute &&
9979            current_sort_params.orderby_attribute.toLowerCase() !==
9980              option_sort_params.orderby_attribute.toLowerCase()) ||
9981          (current_sort_params.orderby == "taxonomy" &&
9982            current_sort_params.orderby_taxonomy &&
9983            current_sort_params.orderby_taxonomy.toLowerCase() !==
9984              option_sort_params.orderby_taxonomy.toLowerCase())
9985        ) {
9986          return;
9987        }
9988
9989        var $sorting_icons = get_instant_sort_heading_icons(
9990            $wcpt,
9991            id,
9992            option_sort_params,
9993          ),
9994          new_order = current_sort_params.order.toLowerCase();
9995
9996        if (!$sorting_icons.length) {
9997          return;
9998        }
9999
10000        // UI feedback
10001        $(".wcpt-sorting-icons", $wcpt).removeClass(
10002          "wcpt-sorting-asc wcpt-sorting-desc",
10003        );
10004        $(".wcpt-sorting-icon", $wcpt).removeClass("wcpt-active wcpt-inactive");
10005
10006        if (new_order == "asc") {
10007          $sorting_icons
10008            .addClass("wcpt-sorting-asc")
10009            .removeClass("wcpt-sorting-desc");
10010        } else {
10011          $sorting_icons
10012            .addClass("wcpt-sorting-desc")
10013            .removeClass("wcpt-sorting-asc");
10014        }
10015
10016        $(".wcpt-sorting-" + new_order + "-icon", $sorting_icons).addClass(
10017          "wcpt-active",
10018        );
10019      },
10020    );
10021  }
10022
10023  window.wcpt_instant_sort = function (params, $wcpt) {
10024    var sort_data = $wcpt.data("wcpt_sort_data");
10025
10026    if (!params.order) {
10027      params.order = "asc";
10028    }
10029
10030    params.order = params.order.toLowerCase();
10031
10032    if (-1 !== $.inArray(params.orderby, ["price-desc", "rating"])) {
10033      params.order = "desc";
10034    }
10035
10036    switch (params.orderby) {
10037      case "title":
10038        sort_data.sort(function (a, b) {
10039          return params.order == "asc"
10040            ? a.title.localeCompare(b.title)
10041            : b.title.localeCompare(a.title);
10042        });
10043
10044        break;
10045
10046      case "sku": // as text
10047        sort_data.sort(function (a, b) {
10048          return params.order == "asc"
10049            ? a.sku.localeCompare(b.sku)
10050            : b.sku.localeCompare(a.sku);
10051        });
10052
10053        break;
10054
10055      case "sku_num":
10056        sort_data.sort(function (a, b) {
10057          var a_sku_num = isNaN(parseFloat(a.sku)) ? 0 : parseFloat(a.sku),
10058            b_sku_num = isNaN(parseFloat(b.sku)) ? 0 : parseFloat(b.sku);
10059
10060          return params.order == "asc"
10061            ? a_sku_num - b_sku_num
10062            : b_sku_num - a_sku_num;
10063        });
10064
10065        break;
10066
10067      case "menu_order":
10068        sort_data.sort(function (a, b) {
10069          return params.order == "asc"
10070            ? a.menu_order - b.menu_order
10071            : b.menu_order - a.menu_order;
10072        });
10073
10074        break;
10075
10076      case "price":
10077      case "price-desc":
10078        sort_data.sort(function (a, b) {
10079          var a_price = a.price,
10080            b_price = b.price;
10081
10082          if (params.order == "asc" && a.min_price) {
10083            a_price = a.min_price;
10084          }
10085
10086          if (params.order == "desc" && a.max_price) {
10087            a_price = a.max_price;
10088          }
10089
10090          if (params.order == "asc" && b.min_price) {
10091            b_price = b.min_price;
10092          }
10093
10094          if (params.order == "desc" && b.max_price) {
10095            b_price = b.max_price;
10096          }
10097
10098          return params.order == "asc" ? a_price - b_price : b_price - a_price;
10099        });
10100
10101        break;
10102
10103      case "meta_value": // as text
10104        sort_data.sort(function (a, b) {
10105          var key = "meta_value__" + params.meta_key,
10106            a_meta = a[key] == null ? "" : String(a[key]),
10107            b_meta = b[key] == null ? "" : String(b[key]);
10108
10109          return params.order == "asc"
10110            ? a_meta.localeCompare(b_meta)
10111            : b_meta.localeCompare(a_meta);
10112        });
10113
10114        break;
10115
10116      case "meta_value_num":
10117        sort_data.sort(function (a, b) {
10118          var key = "meta_value__" + params.meta_key,
10119            a_meta = a[key],
10120            b_meta = b[key];
10121
10122          var a_meta_num = isNaN(parseFloat(a_meta)) ? 0 : parseFloat(a_meta),
10123            b_meta_num = isNaN(parseFloat(b_meta)) ? 0 : parseFloat(b_meta);
10124
10125          return params.order == "asc"
10126            ? a_meta_num - b_meta_num
10127            : b_meta_num - a_meta_num;
10128        });
10129
10130        break;
10131
10132      case "attribute": // as text
10133        sort_data.sort(function (a, b) {
10134          var key = "attribute__" + params.orderby_attribute,
10135            a_val = a[key] == null ? "" : String(a[key]),
10136            b_val = b[key] == null ? "" : String(b[key]);
10137
10138          return params.order == "asc"
10139            ? a_val.localeCompare(b_val)
10140            : b_val.localeCompare(a_val);
10141        });
10142
10143        break;
10144
10145      case "attribute_num": // as number
10146        sort_data.sort(function (a, b) {
10147          var key = "attribute__" + params.orderby_attribute,
10148            a_val = a[key],
10149            b_val = b[key];
10150
10151          var a_val_num = isNaN(parseFloat(a_val)) ? 0 : parseFloat(a_val),
10152            b_val_num = isNaN(parseFloat(b_val)) ? 0 : parseFloat(b_val);
10153
10154          return params.order == "asc"
10155            ? a_val_num - b_val_num
10156            : b_val_num - a_val_num;
10157        });
10158
10159        break;
10160
10161      case "taxonomy": // as text
10162        sort_data.sort(function (a, b) {
10163          var key = "taxonomy__" + params.orderby_taxonomy,
10164            a_val = a[key] == null ? "" : String(a[key]),
10165            b_val = b[key] == null ? "" : String(b[key]);
10166
10167          return params.order == "asc"
10168            ? a_val.localeCompare(b_val)
10169            : b_val.localeCompare(a_val);
10170        });
10171
10172        break;
10173
10174      case "brand": // product_brand taxonomy (same prop key as taxonomy sort)
10175        sort_data.sort(function (a, b) {
10176          var key = "taxonomy__product_brand",
10177            a_val = a[key] || "",
10178            b_val = b[key] || "";
10179
10180          return params.order == "asc"
10181            ? a_val.localeCompare(b_val)
10182            : b_val.localeCompare(a_val);
10183        });
10184
10185        break;
10186
10187      case "taxonomy_num": // as number
10188        sort_data.sort(function (a, b) {
10189          var a_val = a["taxonomy__" + params.orderby_taxonomy],
10190            b_val = b["taxonomy__" + params.orderby_taxonomy];
10191
10192          var a_val_num = isNaN(parseFloat(a_val)) ? 0 : parseFloat(a_val),
10193            b_val_num = isNaN(parseFloat(b_val)) ? 0 : parseFloat(b_val);
10194
10195          return params.order == "asc"
10196            ? a_val_num - b_val_num
10197            : b_val_num - a_val_num;
10198        });
10199
10200        break;
10201
10202      case "category":
10203        sort_data.sort(function (a, b) {
10204          return params.order == "asc"
10205            ? a.category.localeCompare(b.category)
10206            : b.category.localeCompare(a.category);
10207        });
10208
10209      case "date":
10210      case "popularity":
10211      case "id":
10212      case "rating":
10213        sort_data.sort(function (a, b) {
10214          return params.order == "asc"
10215            ? a[params.orderby] - b[params.orderby]
10216            : b[params.orderby] - a[params.orderby];
10217        });
10218
10219        break;
10220
10221      default:
10222        break;
10223    }
10224
10225    // render
10226    $.each(sort_data, function (index, product_sort_data) {
10227      var $row = false;
10228      if (product_sort_data.variation_id) {
10229        // variation
10230        $row = $wcpt.find(
10231          '.wcpt-row[data-wcpt-variation-id="' +
10232            product_sort_data.variation_id +
10233            '"]',
10234        );
10235      } else {
10236        // other
10237        $row = $wcpt.find(
10238          '.wcpt-row[data-wcpt-product-id="' + product_sort_data.id + '"]',
10239        );
10240      }
10241
10242      $row.each(function () {
10243        var $this = $(this),
10244          $tbody = $this.closest("tbody");
10245        $this.detach().appendTo($tbody);
10246      });
10247    });
10248
10249    // -- group by category
10250    if (
10251      wcpt_util.get_sc_attrs($wcpt)[
10252        wcpt_util.get_device() + "_group_by_category"
10253      ]
10254    ) {
10255      $(
10256        $(".wcpt-row:not(.wcpt-row--category-heading)", $wcpt).get().reverse(),
10257      ).each(function () {
10258        var $this = $(this),
10259          cat_slug = $this.attr("data-wcpt-group-by-category-slug"),
10260          $category_heading = $this.siblings(
10261            '.wcpt-row--category-heading[data-wcpt-group-by-category-slug="' +
10262              cat_slug +
10263              '"]',
10264          );
10265        $this.detach().insertAfter($category_heading);
10266      });
10267    }
10268
10269    wcpt_util.assign_even_odd_row_classes($(".wcpt-table", $wcpt));
10270  };
10271})(jQuery);
10272
10273// module: download csv
10274(function ($) {
10275  $("body").on("click", ".wcpt-csv-download", function () {
10276    var $this = $(this),
10277      file_name = $this.attr("data-wcpt-file-name"),
10278      include_all_products = $this.attr("data-wcpt-csv-include-all-products"),
10279      $container = $this.closest(".wcpt"),
10280      encrypted_query_vars = $container.attr("data-wcpt-encrypted-query-vars"),
10281      encrypted_user_filters = $container.attr(
10282        "data-wcpt-encrypted-user-filters",
10283      ),
10284      encrypted_columns = $this.attr("data-wcpt-csv-encrypted-columns"),
10285      table_id = $this.closest(".wcpt").attr("data-wcpt-table-id");
10286
10287    if ($this.hasClass("wcpt-disabled")) {
10288      return;
10289    }
10290
10291    $.ajax({
10292      url: wcpt_params.wc_ajax_url.replace("%%endpoint%%", "wcpt_get_csv"),
10293      method: "POST", // @todo: change to GET
10294      beforeSend: function () {
10295        $this.addClass("wcpt-disabled wcpt-loading");
10296      },
10297      data: {
10298        wcpt_csv_include_all_products: include_all_products,
10299        wcpt_csv_encrypted_query_vars: encrypted_query_vars,
10300        wcpt_csv_encrypted_user_filters: encrypted_user_filters,
10301        wcpt_csv_encrypted_columns: encrypted_columns,
10302        wcpt_csv_table_id: table_id,
10303      },
10304    }).done(function (json_data) {
10305      $this.removeClass("wcpt-disabled wcpt-loading");
10306      build_csv_and_download(json_data, file_name);
10307    });
10308  });
10309
10310  function build_csv_and_download(json_data, file_name) {
10311    var csv = "";
10312    $.each(json_data, function (_, row) {
10313      $.each(row, function (_, val) {
10314        csv += val + ",";
10315      });
10316      csv = csv.slice(0, -1) + "\n";
10317    });
10318
10319    var $pseudo_link = $("<a>", {
10320      href: "data:Application/octet-stream," + encodeURIComponent(csv),
10321      download: file_name + ".csv",
10322    });
10323
10324    $pseudo_link.appendTo("body").get(0).click();
10325    $pseudo_link.remove();
10326  }
10327})(jQuery);
10328
10329// module: reveal sidebar
10330(function ($) {
10331  function get_reveal_sidebar_parts($from) {
10332    var $container = $from.closest(".wcpt"),
10333      $wrapper = $container.find(".wcpt-navigation-wrapper").first(),
10334      $button = $wrapper.find(".wcpt-header .wcpt-reveal-sidebar"),
10335      $sidebar = $wrapper.children(".wcpt-left-sidebar");
10336
10337    return {
10338      $container: $container,
10339      $wrapper: $wrapper,
10340      $button: $button,
10341      $sidebar: $sidebar,
10342    };
10343  }
10344
10345  function get_sidebar_filter_counts($sidebar) {
10346    var used = 0,
10347      total = 0;
10348
10349    if (!$sidebar || !$sidebar.length) {
10350      return { used: 0, total: 0 };
10351    }
10352
10353    $sidebar.find(".wcpt-filter").each(function () {
10354      var $filter = $(this),
10355        filter = $filter.attr("data-wcpt-filter"),
10356        $checked = $filter.find("input[type=checkbox]:checked"),
10357        $selected = $filter.find("input[type=radio]:checked"),
10358        $min = $filter.find(".wcpt-range-input-min"),
10359        $max = $filter.find(".wcpt-range-input-max"),
10360        is_used = false;
10361
10362      if (filter === "sort_by" || filter === "results_per_page") {
10363        return;
10364      }
10365
10366      total += 1;
10367
10368      if ($checked.length) {
10369        is_used = true;
10370      } else if ($selected.length && $selected.attr("value")) {
10371        is_used = true;
10372      } else if (
10373        $min.length &&
10374        ($min.val() != $min.attr("min") || $max.val() != $max.attr("max"))
10375      ) {
10376        is_used = true;
10377      }
10378
10379      if (is_used) {
10380        used += 1;
10381      }
10382    });
10383
10384    $sidebar.find(".wcpt-search").each(function () {
10385      var $search = $(this),
10386        keyword = ($search.find(".wcpt-search-input").val() || "").trim();
10387
10388      total += 1;
10389
10390      if (keyword || $search.hasClass("wcpt-active")) {
10391        used += 1;
10392      }
10393    });
10394
10395    return { used: used, total: total };
10396  }
10397
10398  function update_reveal_sidebar_count($from) {
10399    var parts = get_reveal_sidebar_parts($from),
10400      $button = parts.$button,
10401      $sidebar = parts.$sidebar,
10402      $count = $button.find(".wcpt-active-count--reveal-sidebar"),
10403      counts;
10404
10405    if (!$button.length || !$count.length) {
10406      return;
10407    }
10408
10409    counts = get_sidebar_filter_counts($sidebar);
10410    $count
10411      .text(counts.total)
10412      .toggleClass("wcpt-active-count--zero", !counts.used);
10413  }
10414
10415  function save_reveal_sidebar_scroll($from) {
10416    var parts = get_reveal_sidebar_parts($from),
10417      $container = parts.$container,
10418      $sidebar = parts.$sidebar,
10419      sidebar,
10420      scrollTop,
10421      anchorEl,
10422      $filter,
10423      active;
10424
10425    if (!$container.length || !$sidebar.length || !$sidebar[0]) {
10426      return;
10427    }
10428
10429    sidebar = $sidebar[0];
10430    scrollTop = sidebar.scrollTop;
10431    $container.data("wcpt-reveal-sidebar-scroll", scrollTop);
10432
10433    // Prefer the focused control; otherwise the first filter intersecting
10434    // the current viewport — used to re-align after content height shifts.
10435    active = document.activeElement;
10436    if (active && sidebar.contains(active)) {
10437      anchorEl = active.closest(".wcpt-filter, .wcpt-search");
10438    }
10439
10440    if (!anchorEl) {
10441      $sidebar.find(".wcpt-filter, .wcpt-search").each(function () {
10442        if (this.offsetTop + this.offsetHeight > scrollTop) {
10443          anchorEl = this;
10444          return false;
10445        }
10446      });
10447    }
10448
10449    if (!anchorEl) {
10450      $container.removeData("wcpt-reveal-sidebar-scroll-anchor");
10451      return;
10452    }
10453
10454    $filter = $(anchorEl).closest(".wcpt-filter");
10455    $container.data("wcpt-reveal-sidebar-scroll-anchor", {
10456      filter: $filter.attr("data-wcpt-filter") || "",
10457      taxonomy: $filter.attr("data-wcpt-taxonomy") || "",
10458      metaKey:
10459        $filter.attr("data-wcpt-meta-key") ||
10460        $filter.attr("data-wcpt-meta_key") ||
10461        "",
10462      isSearch: $(anchorEl).closest(".wcpt-search").length > 0,
10463      viewOffset: anchorEl.offsetTop - scrollTop,
10464    });
10465  }
10466
10467  function find_reveal_sidebar_scroll_anchor($sidebar, anchor) {
10468    var $match = $();
10469
10470    if (!anchor) {
10471      return $match;
10472    }
10473
10474    if (anchor.isSearch) {
10475      return $sidebar.find(".wcpt-search").first();
10476    }
10477
10478    if (!anchor.filter) {
10479      return $match;
10480    }
10481
10482    $match = $sidebar.find(
10483      '.wcpt-filter[data-wcpt-filter="' + anchor.filter + '"]',
10484    );
10485
10486    if (anchor.taxonomy) {
10487      $match = $match.filter('[data-wcpt-taxonomy="' + anchor.taxonomy + '"]');
10488    }
10489
10490    if (anchor.metaKey) {
10491      $match = $match.filter(function () {
10492        var $this = $(this);
10493        return (
10494          $this.attr("data-wcpt-meta-key") === anchor.metaKey ||
10495          $this.attr("data-wcpt-meta_key") === anchor.metaKey
10496        );
10497      });
10498    }
10499
10500    return $match.first();
10501  }
10502
10503  function restore_reveal_sidebar_scroll($from) {
10504    var parts = get_reveal_sidebar_parts($from),
10505      $container = parts.$container,
10506      $sidebar = parts.$sidebar,
10507      scroll = $container.data("wcpt-reveal-sidebar-scroll"),
10508      anchor = $container.data("wcpt-reveal-sidebar-scroll-anchor"),
10509      $anchor,
10510      sidebar;
10511
10512    if (!$sidebar.length || !$sidebar[0] || typeof scroll === "undefined") {
10513      return;
10514    }
10515
10516    sidebar = $sidebar[0];
10517    $anchor = find_reveal_sidebar_scroll_anchor($sidebar, anchor);
10518
10519    if ($anchor.length) {
10520      sidebar.scrollTop = Math.max(0, $anchor[0].offsetTop - anchor.viewOffset);
10521    } else {
10522      sidebar.scrollTop = scroll;
10523    }
10524  }
10525
10526  // Re-apply after layout settles — early restore can get clamped while the
10527  // panel's scrollHeight is still shrinking/growing, which drifts scroll up.
10528  function schedule_reveal_sidebar_scroll_restore($from) {
10529    var $container = $from.closest(".wcpt");
10530
10531    $container.data("wcpt-reveal-sidebar-scroll-pending", true);
10532    restore_reveal_sidebar_scroll($container);
10533
10534    requestAnimationFrame(function () {
10535      restore_reveal_sidebar_scroll($container);
10536      requestAnimationFrame(function () {
10537        restore_reveal_sidebar_scroll($container);
10538        $container.removeData("wcpt-reveal-sidebar-scroll-pending");
10539      });
10540    });
10541  }
10542
10543  function close_reveal_sidebar($from) {
10544    var parts = $from && $from.length ? get_reveal_sidebar_parts($from) : null,
10545      $wrappers = parts
10546        ? parts.$wrapper
10547        : $(".wcpt-navigation-wrapper--sidebar-open");
10548
10549    $wrappers.each(function () {
10550      var $wrapper = $(this),
10551        $container = $wrapper.closest(".wcpt");
10552
10553      $wrapper
10554        .removeClass("wcpt-navigation-wrapper--sidebar-open")
10555        .find("> .wcpt-left-sidebar")
10556        .attr("aria-hidden", "true");
10557      $wrapper
10558        .find(".wcpt-header .wcpt-reveal-sidebar")
10559        .attr("aria-expanded", "false");
10560      $container.data("wcpt-reveal-sidebar-open", false);
10561    });
10562
10563    $(".wcpt-reveal-sidebar-backdrop").remove();
10564    if (!$(".wcpt-navigation-wrapper--sidebar-open").length) {
10565      $("body").removeClass("wcpt-reveal-sidebar-on");
10566    }
10567  }
10568
10569  function open_reveal_sidebar($from, skip_animation) {
10570    var parts = get_reveal_sidebar_parts($from),
10571      $container = parts.$container,
10572      $wrapper = parts.$wrapper,
10573      $button = parts.$button,
10574      $sidebar = parts.$sidebar;
10575
10576    if (
10577      !$sidebar.length ||
10578      !$button.length ||
10579      $button.hasClass("wcpt-reveal-sidebar--unavailable")
10580    ) {
10581      return;
10582    }
10583
10584    // When restoring after a table refresh, open instantly (no slide-in)
10585    if (skip_animation) {
10586      $sidebar.addClass("wcpt-reveal-sidebar--no-anim");
10587    }
10588
10589    $wrapper.addClass("wcpt-navigation-wrapper--sidebar-open");
10590    $button.attr("aria-expanded", "true");
10591    $sidebar.attr("aria-hidden", "false");
10592    $container.data("wcpt-reveal-sidebar-open", true);
10593
10594    $("body").addClass("wcpt-reveal-sidebar-on");
10595    if (!$(".wcpt-reveal-sidebar-backdrop").length) {
10596      $("body").append('<div class="wcpt-reveal-sidebar-backdrop"></div>');
10597    }
10598
10599    if (skip_animation) {
10600      // force reflow so the "open" state applies without transition, then
10601      // restore transitions for subsequent interactions
10602      $sidebar[0].offsetHeight;
10603      $sidebar.removeClass("wcpt-reveal-sidebar--no-anim");
10604      // HTML replace resets scroll; put the panel back where the user was
10605      schedule_reveal_sidebar_scroll_restore($container);
10606    }
10607  }
10608
10609  function init_reveal_sidebar() {
10610    var $container = $(this),
10611      parts = get_reveal_sidebar_parts($container),
10612      $wrapper = parts.$wrapper,
10613      $button = parts.$button,
10614      $sidebar = parts.$sidebar;
10615
10616    if (!$button.length) {
10617      $wrapper.removeClass(
10618        "wcpt-navigation-wrapper--reveal-sidebar wcpt-navigation-wrapper--sidebar-open",
10619      );
10620      return;
10621    }
10622
10623    if (!$sidebar.length) {
10624      $button.addClass("wcpt-reveal-sidebar--unavailable");
10625      return;
10626    }
10627
10628    var table_id = $container.attr("data-wcpt-table-id"),
10629      sidebar_id = "wcpt-reveal-sidebar-" + table_id;
10630
10631    $wrapper.addClass("wcpt-navigation-wrapper--reveal-sidebar");
10632    $button.removeClass("wcpt-reveal-sidebar--unavailable").attr({
10633      "aria-controls": sidebar_id,
10634      "aria-expanded": "false",
10635    });
10636    $sidebar.attr({
10637      id: sidebar_id,
10638      "aria-hidden": "true",
10639    });
10640
10641    update_reveal_sidebar_count($container);
10642
10643    // Keep sidebar open across filter refreshes (without re-animating)
10644    if ($container.data("wcpt-reveal-sidebar-open")) {
10645      open_reveal_sidebar($container, true);
10646    }
10647  }
10648
10649  $("body")
10650    .on("wcpt_before_ajax_container_replace", function (e, data) {
10651      if (data && data.$container && data.$container.length) {
10652        save_reveal_sidebar_scroll(data.$container);
10653      }
10654    })
10655    .on("wcpt_layout", ".wcpt", function () {
10656      var $container = $(this);
10657      if (
10658        $container.data("wcpt-reveal-sidebar-open") &&
10659        $container.data("wcpt-reveal-sidebar-scroll-pending")
10660      ) {
10661        restore_reveal_sidebar_scroll($container);
10662      }
10663    })
10664    .on("wcpt_after_every_load", ".wcpt", init_reveal_sidebar)
10665    .on("click", ".wcpt-header .wcpt-reveal-sidebar", function (e) {
10666      e.preventDefault();
10667      open_reveal_sidebar($(this));
10668    })
10669    .on("click", ".wcpt-reveal-sidebar-backdrop", function () {
10670      close_reveal_sidebar($(".wcpt-navigation-wrapper--sidebar-open").first());
10671    })
10672    .on(
10673      "change input",
10674      ".wcpt-left-sidebar .wcpt-filter, .wcpt-left-sidebar .wcpt-search",
10675      function () {
10676        update_reveal_sidebar_count($(this));
10677      },
10678    )
10679    .on("keydown", function (e) {
10680      if (e.key === "Escape") {
10681        close_reveal_sidebar();
10682      }
10683    });
10684
10685  $(function () {
10686    $(".wcpt").each(function () {
10687      init_reveal_sidebar.call(this);
10688    });
10689  });
10690})(jQuery);
10691
10692// module: infinite scroll
10693(function ($) {
10694  // controller
10695  // -- after_every_load handler - attach observer once
10696  $("body").on("wcpt_after_every_load", ".wcpt", function () {
10697    var $this__container = $(this),
10698      sc_attrs = wcpt_util.get_sc_attrs($this__container),
10699      $infinite_scroll_dots = $(".wcpt-infinite-scroll-dots", $this__container);
10700
10701    if (!$infinite_scroll_dots.length) {
10702      return;
10703    }
10704
10705    // intersection observer for load trigger
10706    wcpt_util.run_once_on_container(
10707      $this__container,
10708      "wcpt_infinite_scroll_init",
10709      function ($container) {
10710        const intersectionObserver = new IntersectionObserver(
10711          (entries) => {
10712            if (entries[0].intersectionRatio <= 0) return;
10713
10714            if (
10715              !$container.hasClass(
10716                "wcpt-loading wcpt-infinite-scroll-loading-results",
10717              )
10718            ) {
10719              $container.trigger("wcpt_infinite_scroll");
10720            }
10721          },
10722          { rootMargin: "100px" },
10723        );
10724        // start observing
10725        intersectionObserver.observe($infinite_scroll_dots[0]);
10726        $this__container.data(
10727          "wcpt-infinite-scroll-intersection-observer",
10728          intersectionObserver,
10729        );
10730      },
10731    );
10732
10733    // fresh dots
10734    var intersection_observer = $this__container.data(
10735      "wcpt-infinite-scroll-intersection-observer",
10736    );
10737    intersection_observer.observe($infinite_scroll_dots[0]);
10738  });
10739
10740  // -- a separate trigger 'wcpt_infinite_scroll' that inits infinite scroll on $wcpt
10741  $("body").on("wcpt_infinite_scroll", ".wcpt", function () {
10742    var $this__container = $(this);
10743    append_next_page($this__container);
10744  });
10745
10746  // -- helper: handler
10747  append_next_page = ($container) => {
10748    var table_id = wcpt_util.get_table_id($container),
10749      page_number = wcpt_util.get_current_page_number($container),
10750      new_query = `${table_id}_paged=${page_number + 1}`,
10751      append_new_query = true,
10752      purpose = "infinite_scroll",
10753      query = window.wcpt_build_ajax_query_string(
10754        $container,
10755        new_query,
10756        append_new_query,
10757        purpose,
10758      );
10759
10760    window.wcpt_fetch_markup_and_apply_callback(
10761      $container,
10762      query,
10763      purpose,
10764      ajax_success__infinite_scroll,
10765    );
10766    $container.removeClass("wcpt-loading");
10767    $container.addClass("wcpt-infinite-scroll-loading-results");
10768  };
10769
10770  // ajax success handler
10771  function ajax_success__infinite_scroll(response, $container) {
10772    var $response = $(response),
10773      $new_container = $response.filter(".wcpt").first();
10774
10775    if (!$new_container.length) {
10776      $new_container = $response.find(".wcpt").first();
10777    }
10778    if (!$new_container.length) {
10779      $new_container = $response;
10780    }
10781
10782    var $new_rows = $(".wcpt-row", $new_container);
10783    $(".wcpt-table > tbody", $container).append($new_rows);
10784
10785    if (
10786      typeof wcpt_util !== "undefined" &&
10787      wcpt_util.merge_variable_switch_cf_from_response
10788    ) {
10789      wcpt_util.merge_variable_switch_cf_from_response(
10790        response,
10791        wcpt_util.get_table_id($container),
10792      );
10793    }
10794
10795    if (
10796      typeof wcpt_util !== "undefined" &&
10797      wcpt_util.merge_variable_switch_templates_from_response
10798    ) {
10799      wcpt_util.merge_variable_switch_templates_from_response(
10800        $new_container,
10801        $container,
10802      );
10803    }
10804
10805    $container.attr(
10806      "data-wcpt-query-string",
10807      $new_container.attr("data-wcpt-query-string"),
10808    );
10809    $container.removeClass("wcpt-infinite-scroll-loading-results");
10810
10811    // remove infinit scroll dots if results exhausted
10812    if (!$new_container.find(".wcpt-infinite-scroll-dots").length) {
10813      $container.find(".wcpt-infinite-scroll-dots").hide();
10814    }
10815
10816    $("body").trigger("wcpt_infinite_scroll_after_load", {
10817      $container: $container,
10818    });
10819  }
10820})(jQuery);
10821
10822// module: freeze table
10823(function ($) {
10824  // @TODO - add init command here as well
10825
10826  // destroy previous instance before replacing the table
10827  $("body").on("wcpt_before_ajax_container_replace", (ev, data) => {
10828    var $freeze_table = wcpt_util.get_freeze_table(data.$container);
10829    if ($freeze_table) {
10830      $freeze_table.freezeTable("destroy");
10831    }
10832  });
10833
10834  $("body")
10835    .on("wcpt-nav-modal-on", function () {
10836      $(".frzTbl-table").freezeTable("pause");
10837    })
10838    .on("wcpt-nav-modal-off", function () {
10839      $(".frzTbl-table").freezeTable("unpause");
10840    });
10841})(jQuery);
10842
10843// module: form mode
10844(function ($) {
10845  // redirect to shop with correct table id in url params
10846  $("body").on("wcpt_before_ajax_query", function (e, data) {
10847    var sc_attrs = wcpt_util.get_sc_attrs(data.$wcpt),
10848      purpose = data.purpose,
10849      table_id = wcpt_util.get_table_id(data.$wcpt);
10850
10851    if (purpose == "filter" && sc_attrs.form_mode) {
10852      if (
10853        sc_attrs.dynamic_filters_lazy_load ||
10854        sc_attrs.dynamic_hide_filters ||
10855        sc_attrs.dynamic_recount
10856      ) {
10857        var $apply = data.$wcpt.find(".wcpt-apply");
10858        if ($apply.length && !$apply.hasClass("wcpt-apply--clicked")) {
10859          return;
10860        }
10861      }
10862
10863      // replace table id in query string
10864      data.query = data.query
10865        .split(table_id + "_")
10866        .join(wcpt_params.shop_table_id + "_");
10867
10868      // don't let lang param repeat, lang=fr,fr error
10869      // var i = wcpt_params.shop_url.indexOf("?lang=");
10870      // if (i !== -1 && query.indexOf("lang=") !== -1) {
10871      //   wcpt_params.shop_url = wcpt_params.shop_url.substring(0, i);
10872      // }
10873
10874      // add query string to shop url
10875      data.query =
10876        wcpt_params.shop_url +
10877        (wcpt_params.shop_url.indexOf("?") == -1
10878          ? data.query
10879          : "&" + data.query.slice(1));
10880
10881      // hide form on submit
10882      if (sc_attrs.hide_form_on_submit) {
10883        data.query += "&hide_form=" + table_id;
10884      }
10885
10886      // disable device requirement
10887      data.query += "&" + wcpt_params.shop_table_id + "_device=";
10888
10889      // redirect to shop with correct table id in url params
10890      window.location = data.query;
10891
10892      // disable proceeding with ajax query
10893      data.proceed = false;
10894    }
10895  });
10896})(jQuery);
10897
10898// module permission
10899// -- check if disabled
10900wcpt_is_module_disabled = function (module, $container) {
10901  var disabled_modules = $container.data("wcpt-disabled-modules");
10902  if (!disabled_modules) {
10903    return false;
10904  }
10905
10906  var disabled = false;
10907  jQuery.each(disabled_modules, function (key, val) {
10908    if (val.module == module) {
10909      disabled = true;
10910    }
10911  });
10912
10913  return disabled;
10914};
10915
10916// -- disable
10917wcpt_disable_module = function (module, $container, source) {
10918  var disabled_modules = $container.data("wcpt-disabled-modules");
10919  if (!disabled_modules) {
10920    disabled_modules = [];
10921  }
10922
10923  disabled_modules.push({
10924    module: module,
10925    source: source,
10926  });
10927
10928  $container.data("wcpt-disabled-modules", disabled_modules);
10929};
10930
10931// -- permit
10932wcpt_permit_module = function (module, $container, source) {
10933  var disabled_modules = $container.data("wcpt-disabled-modules");
10934  if (!disabled_modules) {
10935    disabled_modules = [];
10936  }
10937
10938  var i = disabled_modules.length;
10939  while (i) {
10940    --i;
10941    if (
10942      disabled_modules[i].module == module &&
10943      disabled_modules[i].source == source
10944    ) {
10945      disabled_modules.splice(i, 1);
10946    }
10947  }
10948
10949  $container.data("wcpt-disabled-modules", disabled_modules);
10950};
10951
10952// util functions
10953(function ($) {
10954  window.wcpt_util = {
10955    get_table_id: ($wcpt) => {
10956      return $wcpt.attr("data-wcpt-table-id");
10957    },
10958
10959    get_current_page_number: ($wcpt) => {
10960      var params = wcpt_util.get_table_parsed_query($wcpt),
10961        table_id = wcpt_util.get_table_id($wcpt),
10962        sc_attrs = wcpt_util.get_sc_attrs($wcpt),
10963        current_page = params[`${table_id}_paged`]
10964          ? params[`${table_id}_paged`]
10965          : 1;
10966      current_page = parseInt(current_page, 10);
10967
10968      // Archive tables: fall back to the URL when PHP missed the page query var
10969      // (common on the WooCommerce shop page, which paginates via `page` not `paged`).
10970      if (current_page <= 1 && sc_attrs._archive) {
10971        if (wcpt_params.permalink_structure) {
10972          var regex = new RegExp(
10973            "\\/" + wcpt_params.pagination_slug + "\\/(\\d+)",
10974          );
10975          var match = window.location.pathname.match(regex);
10976          if (match && match[1]) {
10977            current_page = parseInt(match[1], 10);
10978          }
10979        } else {
10980          var url_params = new URLSearchParams(window.location.search);
10981          if (url_params.has(wcpt_params.pagination_slug)) {
10982            current_page = parseInt(
10983              url_params.get(wcpt_params.pagination_slug),
10984              10,
10985            );
10986          }
10987        }
10988      }
10989
10990      return current_page;
10991    },
10992
10993    get_table_parsed_query: ($wcpt) => {
10994      var query_string = $wcpt.attr("data-wcpt-query-string"),
10995        _index = query_string.indexOf("?"),
10996        params =
10997          _index == -1
10998            ? {}
10999            : wcpt_util.parse_query_string(query_string.slice(_index + 1));
11000
11001      return params;
11002    },
11003
11004    parse_query_string: (query) => {
11005      // if first character is ? then remove it
11006      if (query.charAt(0) === "?") {
11007        query = query.slice(1);
11008      }
11009
11010      var vars = query.split("&");
11011      var query_string = {};
11012      for (var i = 0; i < vars.length; i++) {
11013        var pair = vars[i].split("="),
11014          key = decodeURIComponent(pair[0]),
11015          value = decodeURIComponent(pair[1]);
11016
11017        // If first entry with this name
11018        if (typeof query_string[key] === "undefined") {
11019          query_string[key] = value;
11020
11021          // If second entry with this name
11022        } else if (typeof query_string[key] === "string") {
11023          var arr = [query_string[key], value];
11024          query_string[key] = arr;
11025
11026          // If third or later entry with this name
11027        } else {
11028          query_string[key].push(value);
11029        }
11030      }
11031      return query_string;
11032    },
11033
11034    get_sc_attrs: ($container, refresh_cache) => {
11035      if (!refresh_cache) {
11036        if ($container.data("wcpt_sc_attrs")) {
11037          return $container.data("wcpt_sc_attrs");
11038        } else {
11039          return {};
11040        }
11041      }
11042
11043      var sc_attrs = {};
11044      var sc_attrs_string = $container.attr("data-wcpt-sc-attrs");
11045      if (sc_attrs_string && -1 == $.inArray(sc_attrs_string, ["[]", "{}"])) {
11046        sc_attrs = JSON.parse(sc_attrs_string);
11047      }
11048      $container.data("wcpt_sc_attrs", sc_attrs); // access sc attrs from here
11049
11050      return sc_attrs;
11051    },
11052
11053    get_device: () => {
11054      // device
11055      var device = "laptop"; // default
11056
11057      if ($(window).width() <= wcpt_params.breakpoints.phone) {
11058        device = "phone";
11059      } else if ($(window).width() <= wcpt_params.breakpoints.tablet) {
11060        device = "tablet";
11061      }
11062
11063      return device;
11064    },
11065
11066    get_uninit_rows: ($container, refresh_cache) => {
11067      if (!refresh_cache) {
11068        return $container.data("wcpt_new_rows");
11069      }
11070
11071      var $new_rows = $container.find(".wcpt-row:not(.wcpt-row--init)");
11072      $container.data("wcpt_new_rows", $new_rows); // access sc attrs from here
11073
11074      return $new_rows;
11075    },
11076
11077    run_once_on_container: ($container, init_data_flag, callback) => {
11078      if (!$container.data(init_data_flag)) {
11079        callback($container);
11080        $container.data(init_data_flag, true);
11081      }
11082    },
11083
11084    // legacy
11085    do_once_on_container: ($container, init_data_flag, callback) => {
11086      wcpt_util.run_once_on_container($container, init_data_flag, callback);
11087    },
11088
11089    get_freeze_table: ($container) => {
11090      var $freeze_table = false;
11091      $container.find(".frzTbl-table").each(function () {
11092        var $this = $(this);
11093        if ($this.data("freezeTable")) {
11094          $freeze_table = $this;
11095        }
11096      });
11097      return $freeze_table;
11098    },
11099
11100    assign_even_odd_row_classes: ($table) => {
11101      var $rows = $(
11102          ".wcpt-row:not(.wcpt-child-row, .wcpt-row--category-heading):visible",
11103          $table,
11104        ),
11105        $child_rows = $(".wcpt-child-row", $table);
11106
11107      $rows.each(function () {
11108        var $this = $(this);
11109
11110        if ($rows.index($this) % 2) {
11111          $this.addClass("wcpt-even").removeClass("wcpt-odd");
11112        } else {
11113          $this.addClass("wcpt-odd").removeClass("wcpt-even");
11114        }
11115      });
11116
11117      $child_rows.each(function () {
11118        var $this = $(this),
11119          $parent_row = $this.data("wcpt_parent_row");
11120
11121        if ($parent_row.hasClass("wcpt-even")) {
11122          $this.removeClass("wcpt-odd").addClass("wcpt-even");
11123        } else {
11124          // odd
11125          $this.removeClass("wcpt-even").addClass("wcpt-odd");
11126        }
11127      });
11128    },
11129
11130    update_url: (query, $container) => {
11131      if (typeof window.history === "undefined") {
11132        return;
11133      }
11134
11135      // Get current URL and params
11136      const current_url = new URL(window.location.href);
11137      const new_params = new URLSearchParams(query);
11138
11139      // Start fresh with current non-table params
11140      const final_params = new URLSearchParams();
11141
11142      // Copy over non-table related params from current URL
11143      current_url.searchParams.forEach((val, key) => {
11144        // Skip table-specific params (those with numeric prefix)
11145        if (!/^\d+_\w+/.test(key)) {
11146          // Only add if not in new_params
11147          if (!new_params.has(key)) {
11148            final_params.append(key, val);
11149          }
11150        }
11151      });
11152
11153      // Add new params, filtering out unnecessary ones
11154      new_params.forEach((val, key) => {
11155        // Skip device params
11156        if (key.slice(-7) === "_device") {
11157          return;
11158        }
11159
11160        // Skip paged=1 params
11161        if (key.slice(-6) === "_paged" && val === "1") {
11162          return;
11163        }
11164
11165        // Add param (will override any existing)
11166        final_params.append(key, val);
11167      });
11168
11169      // update pagination on archive pages
11170      const table_id = wcpt_util.get_table_id($container);
11171      const sc_attrs = wcpt_util.get_sc_attrs($container);
11172
11173      if (sc_attrs._archive) {
11174        // Archive page pagination (uses WP core pagination)
11175        const current_page = wcpt_util.get_current_page_number($container);
11176
11177        // if permalinks are like ?paged=2 then do that
11178        if (!wcpt_params.permalink_structure) {
11179          if (current_page > 1) {
11180            final_params.set("paged", current_page);
11181          } else {
11182            final_params.delete("paged");
11183          }
11184        } else {
11185          if (current_page <= 1) {
11186            // Remove pagination from URL
11187            const regex = new RegExp(
11188              "\\/(?:page|" + wcpt_params.pagination_slug + ")\\/\\d+",
11189            );
11190            current_url.pathname = current_url.pathname.replace(regex, "");
11191          } else {
11192            // Check if URL already has /{pagination_slug}/ in it
11193            if (
11194              current_url.pathname.includes(
11195                "/" + wcpt_params.pagination_slug + "/",
11196              )
11197            ) {
11198              // Replace existing page number
11199              const regex = new RegExp(
11200                "\\/" + wcpt_params.pagination_slug + "\\/\\d+",
11201              );
11202              current_url.pathname = current_url.pathname.replace(
11203                regex,
11204                "/" + wcpt_params.pagination_slug + "/" + current_page,
11205              );
11206            } else {
11207              // Add /{pagination_slug}/N to end of URL before query params
11208              current_url.pathname =
11209                current_url.pathname.replace(/\/$/, "") +
11210                "/" +
11211                wcpt_params.pagination_slug +
11212                "/" +
11213                current_page;
11214            }
11215          }
11216        }
11217
11218        final_params.delete(`${table_id}_paged`);
11219      }
11220
11221      // Update URL
11222      current_url.search = final_params.toString();
11223
11224      var data = {
11225        url: current_url.href,
11226        $container: $container,
11227        proceed: true,
11228      };
11229
11230      $("body").trigger("wcpt_update_url", data);
11231
11232      if (data.proceed) {
11233        window.history.replaceState({}, "", data.url);
11234      }
11235    },
11236
11237    remove_param_from_url: (param) => {
11238      if (
11239        param &&
11240        typeof window.history !== "undefined" &&
11241        window.location.search
11242      ) {
11243        var url_object = new URL(window.location);
11244        url_object.searchParams.delete(param);
11245        history.replaceState({}, "", url_object.href);
11246      }
11247    },
11248
11249    get_encrypted_attrs: ($container) => {
11250      const encrypted_attrs = {};
11251      $container.each(function () {
11252        $.each(this.attributes, function () {
11253          if (this.specified && this.name.startsWith("data-wcpt-encrypted-")) {
11254            encrypted_attrs[this.name] = this.value;
11255          }
11256        });
11257      });
11258      return encrypted_attrs;
11259    },
11260
11261    infinite_scroll_enabled: ($container) => {
11262      var sc_attrs = wcpt_util.get_sc_attrs($container);
11263      var device = wcpt_util.get_device($container);
11264      return sc_attrs[device + "_infinite_scroll"];
11265    },
11266
11267    merge_variable_switch_cf_from_response: (response, table_id) => {
11268      if (!response || !table_id) {
11269        return;
11270      }
11271
11272      var varName = "wcpt_" + table_id + "_variable_switch_cf",
11273        prefix = "var " + varName + " = ",
11274        text = typeof response === "string" ? response : "",
11275        idx = text.indexOf(prefix),
11276        jsonStr,
11277        newData;
11278
11279      if (idx === -1) {
11280        return;
11281      }
11282
11283      jsonStr = text.substring(idx + prefix.length).trim();
11284
11285      if (jsonStr.charAt(jsonStr.length - 1) === ";") {
11286        jsonStr = jsonStr.slice(0, -1).trim();
11287      }
11288
11289      idx = jsonStr.indexOf("</script>");
11290      if (idx !== -1) {
11291        jsonStr = jsonStr.substring(0, idx).trim();
11292        if (jsonStr.charAt(jsonStr.length - 1) === ";") {
11293          jsonStr = jsonStr.slice(0, -1).trim();
11294        }
11295      }
11296
11297      try {
11298        newData = JSON.parse(jsonStr);
11299      } catch (e) {
11300        return;
11301      }
11302
11303      if (!newData || typeof newData !== "object") {
11304        return;
11305      }
11306
11307      if (typeof window[varName] === "undefined") {
11308        window[varName] = newData;
11309        return;
11310      }
11311
11312      $.each(newData, function (element_id, product_vals) {
11313        if (
11314          typeof window[varName][element_id] === "undefined" ||
11315          window[varName][element_id] === null
11316        ) {
11317          window[varName][element_id] = product_vals;
11318        } else if (product_vals && typeof product_vals === "object") {
11319          $.extend(window[varName][element_id], product_vals);
11320        }
11321      });
11322    },
11323
11324    // Merge price / availability <script type="text/template"> nodes from an
11325    // AJAX response into the live table. Needed for paginate-only swaps and
11326    // infinite scroll, which only append rows and would otherwise leave
11327    // variable-switch price resets without a template (blank prices).
11328    merge_variable_switch_templates_from_response: ($source, $container) => {
11329      if (!$source || !$source.length || !$container || !$container.length) {
11330        return;
11331      }
11332
11333      var $target_scope = $container.find(".wcpt-nav-main").first();
11334      if (!$target_scope.length) {
11335        $target_scope = $container;
11336      }
11337
11338      $source
11339        .find(
11340          "script[data-wcpt-price-type], script[data-wcpt-availability-message]",
11341        )
11342        .addBack(
11343          "script[data-wcpt-price-type], script[data-wcpt-availability-message]",
11344        )
11345        .each(function () {
11346          var $tpl = $(this),
11347            element_id = $tpl.attr("data-wcpt-element-id"),
11348            price_type = $tpl.attr("data-wcpt-price-type"),
11349            availability_message = $tpl.attr("data-wcpt-availability-message"),
11350            selector;
11351
11352          if (!element_id) {
11353            return;
11354          }
11355
11356          if (price_type) {
11357            selector =
11358              'script[data-wcpt-element-id="' +
11359              element_id +
11360              '"][data-wcpt-price-type="' +
11361              price_type +
11362              '"]';
11363          } else if (availability_message) {
11364            selector =
11365              'script[data-wcpt-element-id="' +
11366              element_id +
11367              '"][data-wcpt-availability-message="' +
11368              availability_message +
11369              '"]';
11370          } else {
11371            return;
11372          }
11373
11374          if (!$container.find(selector).length) {
11375            $target_scope.append($tpl.clone());
11376          }
11377        });
11378    },
11379
11380    format_price_figure: (price) => {
11381      price = parseFloat(price);
11382
11383      // remove decimal if it is unnecessary ie .00
11384      if (price !== parseInt(price)) {
11385        price = parseFloat(price).toFixed(wcpt_params.price_decimals);
11386      } else {
11387        price = parseInt(price);
11388      }
11389
11390      // decimal separator
11391      price = (price + "").replace(".", wcpt_params.price_decimal_separator);
11392
11393      // thousands separator
11394      var decimal_split = price.split(wcpt_params.price_decimal_separator);
11395      ((non_decimal = decimal_split[0].toString()),
11396        (decimal = decimal_split[1]));
11397
11398      if (non_decimal.length > 3) {
11399        formatted_non_decimal =
11400          non_decimal.slice(0, -3) +
11401          wcpt_params.price_thousand_separator +
11402          non_decimal.slice(-3);
11403        price =
11404          formatted_non_decimal +
11405          (decimal ? wcpt_params.price_decimal_separator + decimal : "");
11406      }
11407
11408      return price;
11409    },
11410
11411    reload_table: ($container) => {
11412      var $wcpt = $container.hasClass("wcpt")
11413        ? $container
11414        : $container.closest(".wcpt");
11415      if (!$wcpt.length) return;
11416      var table_id = $wcpt.attr("data-wcpt-table-id");
11417      // Clear cache for this table so reload fetches fresh data
11418      if (window.wcpt_cache && window.wcpt_cache.data) {
11419        $.each(window.wcpt_cache.data, function (key) {
11420          if (
11421            key.indexOf("?" + table_id + "_") !== -1 ||
11422            key.indexOf("&" + table_id + "_") !== -1
11423          ) {
11424            delete window.wcpt_cache.data[key];
11425          }
11426        });
11427      }
11428      // Reload with all current parameters (same as refresh_table flow)
11429      window.wcpt_attempt_ajax($wcpt, "", true, "refresh_table");
11430    },
11431  };
11432})(jQuery);
11433
11434// Create a new event called wcptPressing
11435(function ($) {
11436  const holdDelay = 300;
11437  const moveThreshold = 10;
11438  const holdingInterval = 100;
11439  const activeHolds = new Map();
11440  let currentMouseTarget = null; // Track the element where mousedown started
11441
11442  // Helper function to handle hold start
11443  function startHold(element, holdData) {
11444    const holdTimer = setTimeout(() => {
11445      const hold = activeHolds.get(holdData.target);
11446      if (hold) {
11447        element.trigger("wcptTouchHoldStart");
11448        hold.hasStarted = true;
11449
11450        const holdingTimer = setInterval(() => {
11451          if (hold.hasStarted) {
11452            element.trigger("wcptPressing");
11453          }
11454        }, holdingInterval);
11455        hold.holdingTimer = holdingTimer;
11456      }
11457    }, holdDelay);
11458
11459    activeHolds.set(holdData.target, {
11460      element,
11461      holdTimer,
11462      holdingTimer: null,
11463      startY: holdData.startY,
11464      hasStarted: false,
11465    });
11466  }
11467
11468  // Helper function to handle hold end
11469  function endHold(target) {
11470    const hold = activeHolds.get(target);
11471    if (hold) {
11472      clearTimeout(hold.holdTimer);
11473      clearInterval(hold.holdingTimer);
11474      if (hold.hasStarted) {
11475        hold.element.trigger("wcptTouchHoldStop");
11476      }
11477      activeHolds.delete(target);
11478    }
11479  }
11480
11481  // Touch Events
11482  $(document).on("touchstart", function (e) {
11483    const element = $(e.target);
11484    startHold(element, {
11485      target: e.target,
11486      startY: e.originalEvent.touches[0].clientY,
11487    });
11488  });
11489
11490  $(document).on("touchmove", function (e) {
11491    const touch = e.originalEvent.touches[0];
11492    const hold = activeHolds.get(e.target);
11493
11494    if (hold) {
11495      const moveY = Math.abs(touch.clientY - hold.startY);
11496      if (moveY > moveThreshold) {
11497        endHold(e.target);
11498      }
11499    }
11500  });
11501
11502  $(document).on("touchend touchcancel", function (e) {
11503    endHold(e.target);
11504  });
11505
11506  // Mouse Events
11507  $(document).on("mousedown", function (e) {
11508    currentMouseTarget = e.target; // Store the initial target
11509    const element = $(currentMouseTarget);
11510    startHold(element, {
11511      target: currentMouseTarget,
11512      startY: e.clientY,
11513    });
11514  });
11515
11516  $(document).on("mousemove", function (e) {
11517    if (currentMouseTarget) {
11518      // Only check if we have an active mousedown
11519      const hold = activeHolds.get(currentMouseTarget);
11520      if (hold) {
11521        const moveY = Math.abs(e.clientY - hold.startY);
11522        if (moveY > moveThreshold) {
11523          endHold(currentMouseTarget);
11524          currentMouseTarget = null;
11525        }
11526      }
11527    }
11528  });
11529
11530  $(document).on("mouseup", function (e) {
11531    if (currentMouseTarget) {
11532      endHold(currentMouseTarget);
11533      currentMouseTarget = null;
11534    }
11535  });
11536
11537  // Handle cases where mouse leaves the document
11538  $(document).on("mouseleave", function (e) {
11539    if (currentMouseTarget) {
11540      endHold(currentMouseTarget);
11541      currentMouseTarget = null;
11542    }
11543  });
11544})(jQuery);
11545
11546// woocommerce mini-cart block integration with MutationObserver
11547(function ($) {
11548  // Set up MutationObserver to watch for changes to data-cart-totals
11549  const miniCartElement = document.querySelector(".wc-block-mini-cart");
11550
11551  if (miniCartElement) {
11552    // Keep track of the previous cart totals to detect actual changes
11553    let previousCartTotals = miniCartElement.getAttribute("data-cart-totals");
11554
11555    // Create a new observer
11556    const observer = new MutationObserver(function (mutations) {
11557      mutations.forEach(function (mutation) {
11558        if (
11559          mutation.type === "attributes" &&
11560          mutation.attributeName === "data-cart-totals"
11561        ) {
11562          const newCartTotals =
11563            miniCartElement.getAttribute("data-cart-totals");
11564
11565          // Only run wcpt_cart if the data-cart-totals value has changed
11566          if (newCartTotals !== previousCartTotals) {
11567            previousCartTotals = newCartTotals;
11568            if (typeof wcpt_cart === "function") {
11569              wcpt_cart();
11570            }
11571          }
11572        }
11573      });
11574    });
11575
11576    // Start observing the mini cart element for attribute changes
11577    observer.observe(miniCartElement, {
11578      attributes: true,
11579      attributeFilter: ["data-cart-totals"], // Only watch the data-cart-totals attribute
11580    });
11581  }
11582
11583  function refreshCartStore() {
11584    if (window.wp && window.wp.data) {
11585      // Option 1: Invalidate the cart store's resolution cache and force a refresh
11586      const cartStore = window.wp.data.dispatch("wc/store/cart");
11587      if (cartStore) {
11588        // First invalidate any cached data
11589        if (cartStore.invalidateResolutionForStore) {
11590          cartStore.invalidateResolutionForStore();
11591        }
11592
11593        // Force a refresh by requesting cart data again
11594        window.wp.data.select("wc/store/cart").getCartData(true); // true forces bypass of cache
11595
11596        return true;
11597      }
11598
11599      // Option 2: Alternative approach using resolveSelect if the above doesn't work
11600      const resolver =
11601        window.wp.data &&
11602        window.wp.data.controls &&
11603        window.wp.data.controls.resolveSelect;
11604      if (resolver) {
11605        resolver("wc/store/cart", "getCartData", [true]); // Force refresh
11606        return true;
11607      }
11608    }
11609  }
11610
11611  $(document).on("wcpt_cart", function (e, data) {
11612    const wp = window.wp;
11613    if (!wp || !wp.data) return;
11614
11615    const cartStore = wp.data.select("wc/store/cart");
11616    if (!cartStore || !cartStore.getCartData) return;
11617
11618    const cartData = cartStore.getCartData();
11619    if (!cartData || !cartData.items) return;
11620
11621    const totalQuantity = cartData.items.reduce((total, item) => {
11622      return total + (item.quantity || 0);
11623    }, 0);
11624
11625    if (data.cart_quantity != totalQuantity) {
11626      refreshCartStore();
11627    }
11628  });
11629
11630  // window.wcptRefreshMiniCart = refreshMiniCart;
11631})(jQuery);
11632
11633// WP MediaElementJS audio player playing HTML class for WCPT tables
11634(function ($) {
11635  // Handle audio player initialization after WCPT content loads
11636  $("body").on("wcpt_after_every_load", ".wcpt", function () {
11637    var $new_rows = wcpt_util.get_uninit_rows($(this), true);
11638
11639    // Find all audio players in the newly loaded rows
11640    var $mejs_players = $new_rows.find(".wcpt-audio-player .mejs-container");
11641    // Initialize each audio player
11642    initMejsPlayers($mejs_players);
11643  });
11644
11645  // Helper function to initialize audio players
11646  function initMejsPlayers($mejs_players) {
11647    $mejs_players.each(function () {
11648      var $player = $(this);
11649      var playingClass = "wcpt-mejs-playing";
11650
11651      // Get the native media element
11652      var mediaElement = $player.find("audio")[0];
11653
11654      // For existing MediaElement.js instances
11655      if (mediaElement.player) {
11656        attachMediaEvents(mediaElement, $player, playingClass);
11657      }
11658      // For players that haven't been initialized yet
11659      else {
11660        // Wait for MediaElement to initialize
11661        $(document).on("MEJSready", function (e, mejsObj) {
11662          // Check if this is our audio element
11663          if (mejsObj && mejsObj.media === mediaElement) {
11664            attachMediaEvents(mediaElement, $player, playingClass);
11665          }
11666        });
11667
11668        // Fallback to native media events
11669        attachMediaEvents(mediaElement, $player, playingClass);
11670      }
11671    });
11672  }
11673
11674  // Helper function to attach media event handlers
11675  function attachMediaEvents(mediaElement, $player, playingClass) {
11676    if (!mediaElement || !$player.length) return;
11677
11678    // Add event listeners
11679    mediaElement.addEventListener("play", handlePlay);
11680    mediaElement.addEventListener("pause", handlePause);
11681    mediaElement.addEventListener("ended", handleEnded);
11682
11683    // Named event handlers so we can remove them if needed
11684    function handlePlay() {
11685      $player.addClass(playingClass);
11686    }
11687
11688    function handlePause() {
11689      $player.removeClass(playingClass);
11690    }
11691
11692    function handleEnded() {
11693      $player.removeClass(playingClass);
11694    }
11695  }
11696})(jQuery);
11697
11698// module: apply / reset navigation buttons
11699(function ($) {
11700  // apply button
11701  // -- trigger change
11702  $("body").on("click", ".wcpt-apply", function () {
11703    var $this = $(this);
11704    $this.addClass("wcpt-apply--clicked");
11705    $(this).trigger("change");
11706  });
11707  // -- stop navigation if change came from anything else
11708  $("body").on("wcpt_after_navigation_change", ".wcpt", function (e, data) {
11709    var $container = $(this),
11710      sc_attrs = wcpt_util.get_sc_attrs($container),
11711      $apply = $(".wcpt-apply", $container);
11712
11713    if (!$apply.length) {
11714      return;
11715    }
11716
11717    // don't interrup:
11718    // -- form mode with adaptive filters
11719    if (
11720      sc_attrs.form_mode &&
11721      (sc_attrs.dynamic_filters_lazy_load ||
11722        sc_attrs.dynamic_hide_filters ||
11723        sc_attrs.dynamic_recount)
11724    ) {
11725      return;
11726    }
11727    // -- inputs marked for immediate filter
11728    if (
11729      $(data.event.target).closest('[data-wcpt-nav-apply-immediately="true"]')
11730        .length
11731    ) {
11732      return;
11733    }
11734
11735    if (!$container.find(".wcpt-apply--clicked").length) {
11736      data.proceed = false;
11737    }
11738  });
11739})(jQuery);
11740
11741// module: dynamic filters
11742(function ($) {
11743  // filters lazy load
11744  $("body").on("wcpt_after_every_load", ".wcpt", function () {
11745    var $container = $(this),
11746      sc_attrs = wcpt_util.get_sc_attrs($container),
11747      last_ajax_purpose = $container.data("wcpt_last_ajax_purpose");
11748
11749    // check for shortcode attrs
11750    if (
11751      last_ajax_purpose === "paginate" ||
11752      !sc_attrs.dynamic_filters_lazy_load ||
11753      (!sc_attrs.dynamic_recount && !sc_attrs.dynamic_hide_filters)
11754    ) {
11755      return;
11756    }
11757
11758    var encrypted_cache = $container.attr(
11759      "data-wcpt-encrypted-dynamic-filters-lazy-load-cache",
11760    );
11761
11762    $.ajax({
11763      url: wcpt_params.wc_ajax_url.replace(
11764        "%%endpoint%%",
11765        "wcpt__dynamic_filter__lazy_load",
11766      ),
11767      method: "POST",
11768      data: {
11769        wcpt_dynamic_filter_encrypted_cache: encrypted_cache,
11770      },
11771      success: function (result) {
11772        var result = JSON.parse(result);
11773
11774        // append style and script
11775        $(result.style + result.script).appendTo($container);
11776
11777        var $filters = $(".wcpt-filter", $container);
11778
11779        var target_filters = [
11780          "category",
11781          "attribute",
11782          "availability",
11783          "on_sale",
11784          "taxonomy",
11785        ];
11786
11787        if ($container.data("wcpt_sc_attrs").dynamic_recount) {
11788          $filters.each(function () {
11789            var $filter = $(this),
11790              filter_type = $filter.attr("data-wcpt-filter");
11791            if (!target_filters.includes(filter_type)) return true;
11792
11793            var taxonomy = false;
11794            if (["taxonomy", "attribute"].includes(filter_type)) {
11795              taxonomy = $filter.attr("data-wcpt-taxonomy");
11796            }
11797
11798            result.options.forEach(function (option) {
11799              if (option.filter !== filter_type) return;
11800              if (taxonomy && option.taxonomy !== taxonomy) return;
11801
11802              var $option = $(
11803                '[data-wcpt-value="' + option.value + '"]',
11804                $filter,
11805              );
11806              if (!$option.length) return;
11807
11808              var count =
11809                  '<span class="wcpt-count">(' + option.count + ")</span>",
11810                $label = $(
11811                  'label[data-wcpt-value="' + option.value + '"]',
11812                  $filter,
11813                ),
11814                $icon = $label.children(".wcpt-icon"),
11815                $prev_count = $(".wcpt-count", $label);
11816
11817              $prev_count.remove(); // in case duplicate taxonomy filter is printed
11818
11819              if ($icon.length) {
11820                $icon.before(count);
11821              } else {
11822                $label.append(count);
11823              }
11824            });
11825          });
11826        }
11827
11828        $filters.removeClass("wcpt--dynamic-filters--loading-filter");
11829      },
11830    });
11831  });
11832})(jQuery);
11833
11834// module: connect with archive search
11835(function ($) {
11836  $("body").on("wcpt_before_ajax_query", function (e, data) {
11837    var $archive_search = data.$wcpt.find(
11838      ".wcpt-search--connect-with-archive input[type='search']",
11839    );
11840
11841    if ($archive_search.length) {
11842      var current_keyword = $archive_search.val().trim();
11843      var previous_keyword = wcpt_util.get_sc_attrs(data.$wcpt)._search;
11844
11845      if (current_keyword !== previous_keyword) {
11846        // redirect to archive search
11847        window.location.href =
11848          wcpt_params.shop_url + "?s=" + current_keyword + "&post_type=product";
11849        data.proceed = false;
11850      } else {
11851        var search_param_name = $archive_search.attr("name");
11852        var regex = new RegExp(search_param_name + "=[^&]*");
11853        data.query = data.query.replace(regex, "");
11854      }
11855    }
11856  });
11857})(jQuery);
11858
11859// module: column stacking
11860(function ($) {
11861  // Remove all stacking markers and stacked values before reapplying
11862  function reset_column_stacking($container) {
11863    $container.find(".wcpt-cell-val--stacked").remove();
11864    $container
11865      .find(".wcpt-cell--stacking,.wcpt-stack--hide")
11866      .removeClass("wcpt-cell--stacking wcpt-stack--hide");
11867  }
11868
11869  function get_stacking_params($container) {
11870    var device = wcpt_util.get_device();
11871    var last_device = $container.data("wcpt-stacking-last-device");
11872
11873    if (last_device === device) {
11874      return false;
11875    }
11876    $container.data("wcpt-stacking-last-device", device);
11877
11878    var sc_attrs = wcpt_util.get_sc_attrs($container);
11879    var stacking_params = sc_attrs[device + "_stack_columns"];
11880    return stacking_params
11881      ? stacking_params.split("|").map(function (name) {
11882          return name.trim();
11883        })
11884      : [];
11885  }
11886
11887  function stack_columns($container, stacking_params) {
11888    reset_column_stacking($container);
11889    if (!stacking_params.length) return;
11890
11891    // Get the name of the first column, so we can skip hiding its heading
11892    var $headingRow = $container.find(".wcpt-heading-row");
11893    var $headingThs = $headingRow.children("th.wcpt-heading");
11894    var firstColName = (
11895      $headingThs.eq(0).attr("data-wcpt-column-name") || ""
11896    ).trim();
11897
11898    // Hide the heading if it's a stacking column, except for the first column
11899    $headingThs.each(function (i) {
11900      var $th = $(this);
11901      var colName = ($th.attr("data-wcpt-column-name") || "").trim();
11902      // Do not hide heading if it's the first column, even if stacking includes it
11903      if (colName === firstColName) {
11904        return; // skip hiding the first heading
11905      }
11906      if (stacking_params.indexOf(colName) !== -1) {
11907        $th.addClass("wcpt-heading--stacking wcpt-stack--hide");
11908      }
11909    });
11910
11911    // For each row, merge stacked columns into previous column
11912    $container.find(".wcpt-row").each(function () {
11913      var $row = $(this);
11914      var $cells = $row.children("td.wcpt-cell");
11915      // If stacking on the first column, just skip: don't stack or hide it
11916      for (var idx = $cells.length - 1; idx > 0; idx--) {
11917        var $cell = $($cells[idx]);
11918        var $prevCell = $($cells[idx - 1]);
11919        var colName = ($cell.attr("data-wcpt-column-name") || "").trim();
11920
11921        // Do not apply stacking if this is the first column (idx==0 is never reached by this loop)
11922        if (colName === firstColName) {
11923          continue; // skip stacking and hiding for first column
11924        }
11925
11926        if (stacking_params.indexOf(colName) !== -1) {
11927          $cell.addClass("wcpt-cell--stacking wcpt-stack--hide");
11928          // Clone contents to move
11929          var $val = $cell.children(".wcpt-cell-val").first();
11930          if ($val.length && $prevCell.length) {
11931            // Create new stacked container and append the content (prevent duplicate IDs, avoid events)
11932            var $stackedDiv = $(
11933              "<div class='wcpt-cell-val wcpt-cell-val--stacked'></div>",
11934            );
11935            $stackedDiv.append($val.contents().clone(true, true));
11936            // Append to previous cell's .wcpt-cell-val
11937            var $prevVal = $prevCell.children(".wcpt-cell-val").first();
11938            if ($prevVal.length) {
11939              $prevVal.append($stackedDiv);
11940            }
11941          }
11942        }
11943      }
11944    });
11945  }
11946
11947  // On layout called, apply stacking
11948  $(document).on("wcpt_layout", ".wcpt", function (e, data) {
11949    var $wcpt = $(this);
11950    var stacking_params = get_stacking_params($wcpt);
11951    if (!stacking_params) return;
11952    stack_columns($wcpt, stacking_params);
11953  });
11954})(jQuery);
11955
11956//module: list layout
11957(function ($) {
11958  $(document).on("wcpt_layout", ".wcpt", function () {
11959    var $container = $(this);
11960
11961    if (!$container.hasClass("wcpt-list-view")) {
11962      return;
11963    }
11964
11965    var table_id = wcpt_util.get_table_id($container),
11966      $table = $container.find(".wcpt-table"),
11967      $first_row = $table.find(
11968        "tbody tr:not(.wcpt-row--category-heading):first",
11969      ),
11970      css = "";
11971
11972    if (!$table.length) {
11973      return;
11974    }
11975
11976    // remove existing css first
11977    $container.find("style.wcpt-list-view-column-widths").remove();
11978
11979    // revert list to tabel mode to get dimensions
11980    $container.addClass("wcpt-list-view--calc");
11981
11982    // get widths and vertical/horizontal alignment from first row cells
11983    var total_width = $first_row.outerWidth();
11984    $first_row.find("td").each(function (index) {
11985      var width = ($(this).outerWidth() / total_width) * 100;
11986      var verticalAlign = $(this).css("vertical-align") || "middle";
11987      var textAlign = $(this).css("text-align");
11988
11989      var alignCSS = "";
11990      // vertical alignment
11991      if (verticalAlign === "top") {
11992        alignCSS = "justify-content: flex-start;";
11993      } else if (verticalAlign === "bottom") {
11994        alignCSS = "justify-content: flex-end;";
11995      } else {
11996        alignCSS = "justify-content: center;";
11997      }
11998
11999      // // horizontal alignment
12000      // if (textAlign === "left") {
12001      //   alignCSS += " justify-content: flex-start;";
12002      // } else if (textAlign === "right") {
12003      //   alignCSS += " justify-content: flex-end;";
12004      // } else if (textAlign === "center") {
12005      //   alignCSS += " justify-content: center;";
12006      // }
12007
12008      css +=
12009        "#wcpt-" +
12010        table_id +
12011        ".wcpt-list-view :is(.wcpt-row, .wcpt-heading-row):not(.wcpt-row--category-heading) :is(.wcpt-cell, .wcpt-heading):nth-child(" +
12012        (index + 1) +
12013        ") { width: " +
12014        width.toFixed(2) +
12015        "% !important; " +
12016        alignCSS +
12017        " }\n";
12018    });
12019
12020    // get border spacing between rows
12021    var border_spacing = parseInt($table.css("border-spacing").split(" ")[1]);
12022    css +=
12023      "#wcpt-" +
12024      table_id +
12025      ".wcpt-list-view :is(.wcpt-row:not(:last-child), .wcpt-heading-row) { margin-bottom: " +
12026      border_spacing +
12027      "px; }\n";
12028
12029    // add new css
12030    var $style = $('<style class="wcpt-list-view-column-widths"></style>');
12031    $container.append($style);
12032    $style.html(css);
12033
12034    $container.removeClass("wcpt-list-view--calc");
12035  });
12036})(jQuery);
12037
12038// module: input height & width match with add to cart button
12039(function ($) {
12040  // range submit button height match
12041  $("body").on("wcpt_after_every_load", ".wcpt", function () {
12042    const $container = $(this),
12043      $range_submit_button = $container.find(
12044        ".wcpt-range-submit-button, .wcpt-date-picker-submit-button",
12045      );
12046
12047    $range_submit_button.each(function () {
12048      const $button = $(this);
12049      const height = $button.siblings(".wcpt-range-input-min").outerHeight();
12050      $button.outerHeight(height);
12051    });
12052  });
12053
12054  // quantity element size match with cart button
12055  $("body").on("wcpt_layout", ".wcpt", function () {
12056    const $container = $(this),
12057      $row = $container.find(".wcpt-row");
12058
12059    $row.each(function () {
12060      const $row = $(this),
12061        $quantity_elements = $row.find(".wcpt-quantity"),
12062        $cart_button_elements = $row.find(
12063          ".wcpt-button[data-wcpt-link-code*='cart']",
12064        );
12065
12066      if ($quantity_elements.length && $cart_button_elements.length) {
12067        $quantity_elements.each(function () {
12068          const $quantity = $(this);
12069          if ($quantity.hasClass("wcpt-cart-button-height-match")) {
12070            const height = $cart_button_elements.outerHeight();
12071            $quantity.outerHeight(height);
12072            $quantity.find("select").outerHeight(height);
12073          }
12074          if ($quantity.hasClass("wcpt-cart-button-width-match")) {
12075            const width = $cart_button_elements.outerWidth();
12076            $quantity
12077              .outerWidth(width)
12078              .css("box-sizing", "border-box !important");
12079            $quantity.find("select").outerWidth(width);
12080          }
12081        });
12082      }
12083    });
12084  });
12085})(jQuery);
12086
12087// module: search option above filter menu
12088(function ($) {
12089  $("body").on("wcpt_layout", ".wcpt", function () {
12090    var $this = $(this),
12091      $nav_dropdown = $(".wcpt-dropdown.wcpt-filter", $this);
12092
12093    $nav_dropdown.each(function () {
12094      var $this = $(this),
12095        $menu = $(".wcpt-dropdown-menu", $this);
12096
12097      if (
12098        $this.hasClass("wcpt-filter--search-filter-options-enabled") &&
12099        !$(".wcpt-search-filter-options", $this).length
12100      ) {
12101        var placeholder = $this.attr(
12102          "data-wcpt-search-filter-options-placeholder",
12103        );
12104
12105        $menu.prepend(
12106          '<input type="search" class="wcpt-search-filter-options" placeholder="' +
12107            placeholder +
12108            '" />',
12109        );
12110
12111        $menu.css("max-height", "none");
12112
12113        $(".wcpt-search-filter-options", $menu)
12114          .nextAll()
12115          .wrapAll(
12116            $(
12117              '<div class="wcpt-search-filter-option-set" style="max-height: ' +
12118                $menu.css("max-height") +
12119                ';"></div>',
12120            ),
12121          );
12122      }
12123
12124      // Always set max-height for option set
12125      var $optionSet = $(".wcpt-search-filter-option-set", $menu);
12126      if ($optionSet.length) {
12127        $menu.css("max-height", "");
12128        $optionSet.css("max-height", $menu.css("max-height"));
12129        $menu.css("max-height", "none");
12130      }
12131    });
12132  });
12133})(jQuery);
12134
12135// module: auto select products - add to cart checkbox
12136(function ($) {
12137  $("body").on("wcpt_after_every_load", ".wcpt", function () {
12138    const $container = $(this),
12139      sc_attrs = wcpt_util.get_sc_attrs($container);
12140
12141    if (!sc_attrs.auto_select_products) {
12142      return;
12143    }
12144
12145    const $checkbox = $container.find(".wcpt-cart-checkbox");
12146
12147    $checkbox.prop("checked", true).trigger("change");
12148  });
12149})(jQuery);
12150
12151// module: pagination
12152(function ($) {
12153  // infinite scroll remove pagination archive slug & table param
12154  $("body").on("wcpt_update_url", function (e, data) {
12155    var $container = data.$container,
12156      table_id = wcpt_util.get_table_id($container),
12157      sc_attrs = wcpt_util.get_sc_attrs($container),
12158      page_param_key = table_id + "_paged",
12159      pagination_slug = wcpt_params.pagination_slug;
12160
12161    if (!wcpt_util.infinite_scroll_enabled($container)) {
12162      return;
12163    }
12164
12165    // Get current URL
12166    var current_url = new URL(data.url);
12167    var pathname = current_url.pathname;
12168    var search = current_url.search;
12169
12170    // Remove any pagination slug from URL
12171    if (sc_attrs && sc_attrs._archive) {
12172      // Remove WP core pagination slug from pathname
12173      pathname = pathname.replace(
12174        new RegExp("/" + pagination_slug + "/\\d+/?"),
12175        "/",
12176      );
12177
12178      // Remove WP core pagination param from search
12179      search = search.replace(
12180        new RegExp("[?&]" + pagination_slug + "=\\d+"),
12181        "",
12182      );
12183    }
12184
12185    // Remove custom pagination from search
12186    search = search.replace(new RegExp("[?&]" + page_param_key + "=\\d+"), "");
12187
12188    data.url = pathname + search;
12189  });
12190
12191  // reset pagination when ajax is disabled
12192  $("body").on("wcpt_before_nav_redirect", function (e, data) {
12193    if (data.purpose !== "paginate") {
12194      // If not paginating, need to reset to page 1
12195      var $container = data.$container,
12196        table_id = wcpt_util.get_table_id($container),
12197        sc_attrs = wcpt_util.get_sc_attrs($container),
12198        page_param_key = table_id + "_paged",
12199        pagination_slug = wcpt_params.pagination_slug;
12200
12201      // Remove any pagination slug from base_url
12202      if (sc_attrs._archive) {
12203        // Remove WP core pagination slug
12204        data.base_url = data.base_url.replace(
12205          new RegExp("/" + pagination_slug + "/\\d+/?"),
12206          "/",
12207        );
12208        // Remove WP core pagination param
12209        data.query = data.query.replace(
12210          new RegExp("[?&]" + pagination_slug + "=\\d+"),
12211          "",
12212        );
12213      }
12214
12215      // Remove custom pagination
12216      data.query = data.query.replace(
12217        new RegExp("[?&]" + page_param_key + "=\\d+"),
12218        "",
12219      );
12220    }
12221  });
12222
12223  // apply pagination - ajax disabled / enabled / archive
12224  $("body").on(
12225    "click",
12226    ".wcpt-pagination .page-numbers:not(.dots):not(.current)",
12227    function (e) {
12228      e.preventDefault();
12229      var $this = $(this),
12230        $container = $this.closest(".wcpt"),
12231        table_id = wcpt_util.get_table_id($container),
12232        page_param_key = table_id + "_paged",
12233        sc_attrs = wcpt_util.get_sc_attrs($container),
12234        url = $this.attr("href"),
12235        pagination_slug = wcpt_params.pagination_slug, // 'page' or 'paged'
12236        page = 1, // Default to page 1
12237        query = "",
12238        append = true;
12239
12240      if (sc_attrs._archive) {
12241        if (sc_attrs.disable_ajax) {
12242          window.location.href = url;
12243          return;
12244        }
12245
12246        // Archive page pagination (uses WP core pagination) unless table delivered over AJAX
12247        if (wcpt_params.permalink_structure) {
12248          // Pretty permalinks: /shop/page/2/
12249          var regex = new RegExp("/" + pagination_slug + "/(\\d+)");
12250          var match = url.match(regex);
12251          if (match && match[1]) {
12252            page = parseInt(match[1], 10);
12253          }
12254        } else {
12255          // Plain permalinks: /?paged=2
12256          var index = url.indexOf("?");
12257          var params =
12258            index === -1
12259              ? false
12260              : wcpt_util.parse_query_string(url.slice(index + 1));
12261          if (params && params[pagination_slug]) {
12262            page = parseInt(params[pagination_slug], 10);
12263          }
12264        }
12265      }
12266
12267      if (!sc_attrs._archive || page == 1) {
12268        // Standard table pagination - uses custom query var like /?123_paged=2
12269        var index = url.indexOf("?");
12270        var params =
12271          index === -1
12272            ? false
12273            : wcpt_util.parse_query_string(url.slice(index + 1));
12274
12275        if (params && params[page_param_key]) {
12276          page = parseInt(params[page_param_key], 10);
12277        }
12278      }
12279      query = page_param_key + "=" + page;
12280
12281      window.wcpt_attempt_ajax($container, query, append, "paginate");
12282    },
12283  );
12284})(jQuery);
12285
12286// module: persistent add-to-cart checkbox state across pages
12287(function ($) {
12288  // ---------------------------------------------------------------------------
12289  // state
12290  // ---------------------------------------------------------------------------
12291
12292  var memoryState = {};
12293  var pendingRestoreState = {};
12294  var suppressVariationPersistMutations = false;
12295  var isRestoring = false;
12296
12297  function getState() {
12298    return memoryState;
12299  }
12300
12301  function publishState() {
12302    window.wcpt_cart_checked_state = memoryState;
12303  }
12304
12305  // ---------------------------------------------------------------------------
12306  // helpers
12307  // ---------------------------------------------------------------------------
12308
12309  var QTY_SELECTOR = "input.qty, select.wcpt-qty-select";
12310
12311  function getPersistedQtyInput($rowElements) {
12312    var $wcptQty = $rowElements
12313      .find(".wcpt-quantity input.qty, .wcpt-quantity select.wcpt-qty-select")
12314      .first();
12315
12316    if ($wcptQty.length) {
12317      return $wcptQty;
12318    }
12319
12320    var $formQty = $rowElements.find(".variations_form input.qty").first();
12321    if ($formQty.length) {
12322      return $formQty;
12323    }
12324
12325    return $rowElements.find(QTY_SELECTOR).first();
12326  }
12327
12328  function rowKey($row) {
12329    return typeof window.wcpt_get_row_key === "function"
12330      ? window.wcpt_get_row_key($row) || ""
12331      : "";
12332  }
12333
12334  function getRowElements($row) {
12335    if (typeof window.wcpt_get_sibling_rows === "function") {
12336      var $siblings = window.wcpt_get_sibling_rows($row);
12337      if ($siblings && $siblings.length) {
12338        return $siblings;
12339      }
12340    }
12341    return $row;
12342  }
12343
12344  function getPrimaryRow($row) {
12345    if (typeof window.wcpt_get_original_row === "function") {
12346      var $original = window.wcpt_get_original_row($row);
12347      if ($original && $original.length) {
12348        return $original;
12349      }
12350    }
12351    if ($row.closest("table").hasClass("frzTbl-clone-table")) {
12352      return $();
12353    }
12354    return $row;
12355  }
12356
12357  function resolvePrimaryRow($row) {
12358    var $primary = getPrimaryRow($row);
12359    return $primary.length ? $primary : $row;
12360  }
12361
12362  function isVariableRow($row) {
12363    return (
12364      $row.hasClass("wcpt-product-type-variable") ||
12365      $row.attr("data-wcpt-type") == "variable"
12366    );
12367  }
12368
12369  function getTableContext($row) {
12370    var $primaryRow = resolvePrimaryRow($row),
12371      $container = $primaryRow.closest(".wcpt");
12372
12373    if (!$container.length || typeof wcpt_util === "undefined") {
12374      return null;
12375    }
12376
12377    var table_id = wcpt_util.get_table_id($container);
12378    if (!table_id) {
12379      return null;
12380    }
12381
12382    return { $row: $primaryRow, $container: $container, table_id: table_id };
12383  }
12384
12385  function variationIdsMatch(a, b) {
12386    return a && b && String(a) === String(b);
12387  }
12388
12389  function releaseRestoreSuppress() {
12390    queueMicrotask(function () {
12391      suppressVariationPersistMutations = false;
12392    });
12393  }
12394
12395  function getVariationRowTarget($row) {
12396    var $target = isVariableRow($row)
12397      ? $row
12398      : $row.find(".wcpt-product-type-variable").first();
12399
12400    return $target.length ? $target : $row;
12401  }
12402
12403  function findVariationInForm($form, variationId) {
12404    var variations = parseJsonAttr($form, "data-product_variations"),
12405      found = null;
12406
12407    $.each(variations || [], function (index, value) {
12408      if (variationIdsMatch(value.variation_id, variationId)) {
12409        found = value;
12410        return false;
12411      }
12412    });
12413
12414    return found;
12415  }
12416
12417  function readFormAttributes($form) {
12418    var attributes = {};
12419
12420    $form.find(".variations select").each(function () {
12421      var $select = $(this),
12422        name = $select.attr("name");
12423
12424      if (name) {
12425        attributes[name] = $select.val();
12426      }
12427    });
12428
12429    return attributes;
12430  }
12431
12432  function isVariationPurchasable(variation) {
12433    return !!(
12434      variation &&
12435      variation.is_purchasable &&
12436      variation.is_in_stock &&
12437      variation.variation_is_visible
12438    );
12439  }
12440
12441  function eachVariationAttributeSelect($rows, fn) {
12442    $rows.find(".wcpt-select-variation-attribute-term").each(function () {
12443      var $select = $(this),
12444        name = $select.attr("data-wcpt-attribute");
12445
12446      if (name) {
12447        fn($select, name);
12448      }
12449    });
12450
12451    $rows.find(".variations_form .variations select").each(function () {
12452      var $select = $(this),
12453        name = $select.attr("name");
12454
12455      if (name) {
12456        fn($select, name);
12457      }
12458    });
12459  }
12460
12461  function buildSelectVariationMeta(meta, $form, variationId) {
12462    var variation = findVariationInForm($form, variationId);
12463
12464    if (variation) {
12465      meta.variation = variation;
12466    }
12467
12468    meta.attributes = readFormAttributes($form);
12469    meta.variation_id = String(variationId);
12470    meta.variation_found = true;
12471    meta.variation_selected = true;
12472    meta.variation_available = isVariationPurchasable(meta.variation);
12473
12474    return meta;
12475  }
12476
12477  function triggerSelectVariation($target, meta) {
12478    $target.trigger("select_variation", meta);
12479  }
12480
12481  function whenCartFormVariationReady(
12482    $primaryRow,
12483    $form,
12484    variationId,
12485    onReady,
12486  ) {
12487    var finished = false;
12488
12489    function finish() {
12490      if (finished) {
12491        return;
12492      }
12493      finished = true;
12494      $primaryRow.off(".wcptPersistRestore");
12495      $form.off(".wcptPersistRestore");
12496      onReady();
12497    }
12498
12499    function isResolved() {
12500      return variationIdsMatch(
12501        $form.find("input.variation_id").val(),
12502        variationId,
12503      );
12504    }
12505
12506    $primaryRow.on("select_variation.wcptPersistRestore", function (e, data) {
12507      if (variationIdsMatch(data && data.variation_id, variationId)) {
12508        finish();
12509      }
12510    });
12511
12512    $form.on(
12513      "woocommerce_variation_has_changed.wcptPersistRestore",
12514      function () {
12515        if (isResolved()) {
12516          finish();
12517        }
12518      },
12519    );
12520
12521    $form.trigger("check_variations");
12522
12523    if (isResolved()) {
12524      finish();
12525    }
12526  }
12527
12528  function parseJsonAttr($el, attr) {
12529    var raw = $el.attr(attr);
12530    if (!raw) {
12531      return null;
12532    }
12533    try {
12534      return JSON.parse(raw);
12535    } catch (e) {
12536      return null;
12537    }
12538  }
12539
12540  function getRowVariationAttributes($row) {
12541    if ($row.hasClass("wcpt-product-type-variation")) {
12542      return parseJsonAttr($row, "data-wcpt-variation-attributes");
12543    }
12544
12545    var attributes = $row.data("wcpt_attributes");
12546    attributes =
12547      attributes && !$.isEmptyObject(attributes)
12548        ? $.extend({}, attributes)
12549        : {};
12550
12551    eachVariationAttributeSelect($row, function ($select, name) {
12552      var val = $select.val();
12553      if (val) {
12554        attributes[name] = val;
12555      }
12556    });
12557
12558    return $.isEmptyObject(attributes) ? null : attributes;
12559  }
12560
12561  function findPersistedEntry(
12562    tableState,
12563    productId,
12564    variationId,
12565    variableProduct,
12566  ) {
12567    if (!tableState || !productId) {
12568      return null;
12569    }
12570
12571    if (variationId) {
12572      var variationKey = "v:" + String(variationId);
12573      if (tableState[variationKey]) {
12574        return { key: variationKey, entry: tableState[variationKey] };
12575      }
12576    }
12577
12578    var foundKey = null;
12579    var foundEntry = null;
12580
12581    $.each(tableState, function (stateKey, entry) {
12582      if (
12583        foundEntry ||
12584        !entry ||
12585        String(entry.product_id) !== String(productId)
12586      ) {
12587        return;
12588      }
12589
12590      if (variationId) {
12591        if (variationIdsMatch(entry.variation_id, variationId)) {
12592          foundKey = stateKey;
12593          foundEntry = entry;
12594        }
12595      } else if (variableProduct) {
12596        if (entry.variation_id) {
12597          foundKey = stateKey;
12598          foundEntry = entry;
12599        }
12600      } else if (!entry.variation_id) {
12601        foundKey = stateKey;
12602        foundEntry = entry;
12603      }
12604    });
12605
12606    return foundEntry ? { key: foundKey, entry: foundEntry } : null;
12607  }
12608
12609  function resolveRestoreKey(match, productId) {
12610    if (match.key) {
12611      return match.key;
12612    }
12613    if (match.entry.variation_id) {
12614      return "v:" + String(match.entry.variation_id);
12615    }
12616    if (productId) {
12617      return "p:" + String(productId);
12618    }
12619    return "";
12620  }
12621
12622  function findRestoreMatch(tableState, $row) {
12623    var productId = $row.attr("data-wcpt-product-id"),
12624      variationId =
12625        $row.attr("data-wcpt-variation-id") || $row.data("wcpt_variation_id");
12626
12627    if (isVariableRow($row) && productId) {
12628      return findPersistedEntry(tableState, productId, false, true);
12629    }
12630
12631    var match = findPersistedEntry(tableState, productId, variationId);
12632    if (!match) {
12633      var key = rowKey($row);
12634      if (key && tableState[key]) {
12635        match = { key: key, entry: tableState[key] };
12636      }
12637    }
12638    return match;
12639  }
12640
12641  // ---------------------------------------------------------------------------
12642  // entry build / apply
12643  // ---------------------------------------------------------------------------
12644
12645  function buildPersistedEntry($row) {
12646    var $rowElements = getRowElements($row),
12647      $qty = getPersistedQtyInput($rowElements),
12648      qtyVal = $qty.length ? parseFloat($qty.val()) : 1,
12649      unitPrice = parseFloat($row.attr("data-wcpt-price"));
12650
12651    if (isNaN(qtyVal) || qtyVal <= 0) {
12652      qtyVal = 1;
12653    }
12654
12655    if (isVariableRow($row)) {
12656      var variationData = $row.data("wcpt_variation");
12657      if (variationData && !isNaN(parseFloat(variationData.display_price))) {
12658        unitPrice = parseFloat(variationData.display_price);
12659      }
12660    }
12661
12662    if (isNaN(unitPrice)) {
12663      unitPrice = 0;
12664    }
12665
12666    return {
12667      product_id: $row.attr("data-wcpt-product-id"),
12668      variation_id: String(
12669        $row.attr("data-wcpt-variation-id") ||
12670          $row.data("wcpt_variation_id") ||
12671          "",
12672      ),
12673      attributes: getRowVariationAttributes($row),
12674      qty: qtyVal,
12675      total: (unitPrice * qtyVal * 1e12) / 1e12,
12676    };
12677  }
12678
12679  function applyPersistedQty($rowElements, qtyVal) {
12680    if (isNaN(qtyVal) || qtyVal <= 0) {
12681      return;
12682    }
12683
12684    var $targets = $rowElements
12685      .find(".wcpt-quantity input.qty, .wcpt-quantity select.wcpt-qty-select")
12686      .add($rowElements.find(".variations_form input.qty"));
12687
12688    if (!$targets.length) {
12689      $targets = $rowElements.find(QTY_SELECTOR);
12690    }
12691
12692    $targets.each(function () {
12693      var $qtyInput = $(this);
12694      $qtyInput.val(qtyVal);
12695      if (!$qtyInput.closest("form.cart").length) {
12696        $qtyInput.trigger("change");
12697      }
12698    });
12699  }
12700
12701  function applyPersistedQtyWhenReady($rowElements, qtyVal, onDone) {
12702    var finished = false;
12703
12704    function complete() {
12705      if (finished) {
12706        return;
12707      }
12708      finished = true;
12709      if (onDone) {
12710        onDone();
12711      }
12712    }
12713
12714    function setQty() {
12715      applyPersistedQty($rowElements, qtyVal);
12716    }
12717
12718    if (isNaN(qtyVal) || qtyVal <= 0) {
12719      complete();
12720      return;
12721    }
12722
12723    var $form = $rowElements.find(".variations_form").first();
12724    if (!$form.length) {
12725      setQty();
12726      complete();
12727      return;
12728    }
12729
12730    // WC cart form re-renders qty on show_variation — apply after that settles
12731    $form.one(
12732      "show_variation.wcptPersistQty found_variation.wcptPersistQty",
12733      function () {
12734        setQty();
12735        complete();
12736      },
12737    );
12738
12739    queueMicrotask(setQty);
12740
12741    requestAnimationFrame(function () {
12742      setQty();
12743      complete();
12744    });
12745  }
12746
12747  function applyPersistedCheckboxAndQty($row, entry) {
12748    if (!$row || !entry) {
12749      return;
12750    }
12751
12752    var $primaryRow = resolvePrimaryRow($row),
12753      $rowElements = getRowElements($primaryRow),
12754      qtyVal = parseFloat(entry.qty);
12755
12756    function checkAndApply() {
12757      var $enabledCb = $rowElements
12758        .find(".wcpt-cart-checkbox")
12759        .not(":disabled")
12760        .first();
12761
12762      if (!$enabledCb.length) {
12763        return false;
12764      }
12765
12766      isRestoring = true;
12767      $primaryRow.trigger("_wcpt_checkbox_change", true);
12768
12769      if (!isNaN(qtyVal) && qtyVal > 0) {
12770        applyPersistedQtyWhenReady($rowElements, qtyVal, function () {
12771          isRestoring = false;
12772        });
12773      } else {
12774        isRestoring = false;
12775      }
12776
12777      return true;
12778    }
12779
12780    if (checkAndApply()) {
12781      return;
12782    }
12783
12784    $primaryRow.one("select_variation.wcptPersistCheckbox", function (e, data) {
12785      if (data && data.variation_selected && data.variation_id) {
12786        checkAndApply();
12787      }
12788    });
12789  }
12790
12791  // ---------------------------------------------------------------------------
12792  // state mutations
12793  // ---------------------------------------------------------------------------
12794
12795  function syncContainerMirror($container, table_id) {
12796    $container.data(
12797      "wcpt_all_checked",
12798      memoryState[table_id] ? memoryState[table_id] : {},
12799    );
12800  }
12801
12802  function refreshFloatingTrigger() {
12803    if (typeof window.wcpt_checkbox_trigger_init === "function") {
12804      window.wcpt_checkbox_trigger_init();
12805    }
12806  }
12807
12808  function commitTableState(table_id, $container) {
12809    if (
12810      memoryState[table_id] &&
12811      Object.keys(memoryState[table_id]).length === 0
12812    ) {
12813      delete memoryState[table_id];
12814    }
12815    if ($container && $container.length) {
12816      syncContainerMirror($container, table_id);
12817    }
12818    publishState();
12819    refreshFloatingTrigger();
12820  }
12821
12822  function flushVisibleRows($container) {
12823    if (!$container || !$container.length || typeof wcpt_util === "undefined") {
12824      return;
12825    }
12826
12827    var flushedVariableProducts = {};
12828
12829    $container.find(".wcpt-row").each(function () {
12830      var $primaryRow = getPrimaryRow($(this));
12831
12832      if (!$primaryRow.length || !$primaryRow.data("wcpt_checked")) {
12833        return;
12834      }
12835
12836      if (
12837        isVariableRow($primaryRow) &&
12838        !$primaryRow.data("wcpt_variation_id")
12839      ) {
12840        return;
12841      }
12842
12843      var productId = $primaryRow.attr("data-wcpt-product-id");
12844      if (productId && isVariableRow($primaryRow)) {
12845        if (flushedVariableProducts[productId]) {
12846          return;
12847        }
12848        flushedVariableProducts[productId] = true;
12849      }
12850
12851      saveRow($primaryRow);
12852    });
12853  }
12854
12855  function pruneUncheckedVisibleEntries($container) {
12856    if (!$container || !$container.length || typeof wcpt_util === "undefined") {
12857      return;
12858    }
12859
12860    var table_id = wcpt_util.get_table_id($container);
12861    if (!table_id || !memoryState[table_id]) {
12862      return;
12863    }
12864
12865    var visibleProductIds = {},
12866      checkedProductIds = {};
12867
12868    $container.find(".wcpt-row").each(function () {
12869      var $primaryRow = getPrimaryRow($(this));
12870      if (!$primaryRow.length) {
12871        return;
12872      }
12873
12874      var productId = $primaryRow.attr("data-wcpt-product-id");
12875      if (!productId) {
12876        return;
12877      }
12878
12879      visibleProductIds[String(productId)] = true;
12880
12881      if ($primaryRow.data("wcpt_checked")) {
12882        checkedProductIds[String(productId)] = true;
12883      }
12884    });
12885
12886    var tableState = memoryState[table_id],
12887      removed = false;
12888
12889    $.each(tableState, function (key, entry) {
12890      if (!entry || !entry.product_id) {
12891        return;
12892      }
12893
12894      var productId = String(entry.product_id);
12895      if (!visibleProductIds[productId] || checkedProductIds[productId]) {
12896        return;
12897      }
12898
12899      delete tableState[key];
12900      removed = true;
12901    });
12902
12903    if (removed) {
12904      commitTableState(table_id, $container);
12905    }
12906  }
12907
12908  function finalizeTablePage($container) {
12909    queueMicrotask(function () {
12910      if ($container && $container.length) {
12911        // pruneUncheckedVisibleEntries($container);
12912
12913        if (typeof wcpt_util !== "undefined") {
12914          var table_id = wcpt_util.get_table_id($container);
12915          if (table_id) {
12916            syncContainerMirror($container, table_id);
12917          }
12918        }
12919      }
12920
12921      releaseRestoreSuppress();
12922      refreshFloatingTrigger();
12923    });
12924  }
12925
12926  function saveRow($row) {
12927    var ctx = getTableContext($row);
12928    if (!ctx) {
12929      return;
12930    }
12931
12932    $row = ctx.$row;
12933
12934    if (!$row.data("wcpt_checked")) {
12935      return;
12936    }
12937
12938    var key = rowKey($row);
12939    if (!key) {
12940      return;
12941    }
12942
12943    if (isVariableRow($row) && !$row.data("wcpt_variation_id")) {
12944      return;
12945    }
12946
12947    if (!memoryState[ctx.table_id]) {
12948      memoryState[ctx.table_id] = {};
12949    }
12950
12951    if (isVariableRow($row)) {
12952      removePersistedEntriesForProduct(
12953        ctx.table_id,
12954        $row.attr("data-wcpt-product-id"),
12955        $row.data("wcpt_variation_id"),
12956        ctx.$container,
12957        true,
12958      );
12959    }
12960
12961    memoryState[ctx.table_id][key] = buildPersistedEntry($row);
12962    commitTableState(ctx.table_id, ctx.$container);
12963  }
12964
12965  function removePersistedEntriesForProduct(
12966    table_id,
12967    productId,
12968    exceptVariationId,
12969    $container,
12970    skipPublish,
12971  ) {
12972    if (!table_id || !productId || !memoryState[table_id]) {
12973      return false;
12974    }
12975
12976    var tableState = memoryState[table_id],
12977      removed = false;
12978
12979    $.each(tableState, function (stateKey, entry) {
12980      if (!entry || String(entry.product_id) !== String(productId)) {
12981        return;
12982      }
12983
12984      if (
12985        exceptVariationId &&
12986        entry.variation_id &&
12987        String(entry.variation_id) === String(exceptVariationId)
12988      ) {
12989        return;
12990      }
12991
12992      delete tableState[stateKey];
12993      removed = true;
12994    });
12995
12996    if (!removed) {
12997      return false;
12998    }
12999
13000    if (!skipPublish) {
13001      commitTableState(table_id, $container);
13002    }
13003    return true;
13004  }
13005
13006  function removeRow($row) {
13007    if (isRestoring) {
13008      return;
13009    }
13010
13011    var ctx = getTableContext($row);
13012    if (!ctx || !memoryState[ctx.table_id]) {
13013      return;
13014    }
13015
13016    var key = rowKey(ctx.$row);
13017    if (!key || !memoryState[ctx.table_id][key]) {
13018      return;
13019    }
13020
13021    delete memoryState[ctx.table_id][key];
13022    commitTableState(ctx.table_id, ctx.$container);
13023  }
13024
13025  function hasPersistedVariableProduct(table_id, productId) {
13026    if (!table_id || !productId) {
13027      return false;
13028    }
13029
13030    var state = pendingRestoreState[table_id] || memoryState[table_id] || null;
13031
13032    return !!findPersistedEntry(state, productId, false, true);
13033  }
13034
13035  function clearTable(table_id, $container) {
13036    if (memoryState[table_id]) {
13037      delete memoryState[table_id];
13038      publishState();
13039    }
13040
13041    if ($container && $container.length) {
13042      $container.data("wcpt_all_checked", {});
13043    }
13044    refreshFloatingTrigger();
13045  }
13046
13047  // ---------------------------------------------------------------------------
13048  // restore
13049  // ---------------------------------------------------------------------------
13050
13051  function getVariationMetaFromEntry($row, entry) {
13052    var $rows = getRowElements($row),
13053      variationId = String(entry.variation_id),
13054      meta = {
13055        variation_id: variationId,
13056        complete_match: true,
13057        attributes: entry.attributes ? $.extend({}, entry.attributes) : {},
13058        variation: false,
13059        variation_found: true,
13060        variation_selected: true,
13061        variation_available: true,
13062      },
13063      $option = $rows
13064        .find(
13065          '.wcpt-select-variation-dropdown option[value="' + variationId + '"]',
13066        )
13067        .first(),
13068      $radioWrap = $option.length
13069        ? $()
13070        : $rows
13071            .find('.wcpt-variation-radio[value="' + variationId + '"]')
13072            .first()
13073            .closest(".wcpt-select-variation"),
13074      $source = $option.length ? $option : $radioWrap,
13075      $form = $rows.find(".variations_form").first();
13076
13077    if ($source.length) {
13078      var attrs = parseJsonAttr($source, "data-wcpt-attributes"),
13079        variation = parseJsonAttr($source, "data-wcpt-variation");
13080
13081      if (attrs) {
13082        meta.attributes = attrs;
13083      }
13084      if (variation) {
13085        meta.variation = variation;
13086      }
13087      meta.complete_match = $source.hasClass("wcpt-complete_match");
13088    }
13089
13090    if ($form.length && ($.isEmptyObject(meta.attributes) || !meta.variation)) {
13091      var variation = findVariationInForm($form, variationId);
13092
13093      if (variation) {
13094        if ($.isEmptyObject(meta.attributes) && variation.attributes) {
13095          meta.attributes = $.extend({}, variation.attributes);
13096        }
13097        if (!meta.variation) {
13098          meta.variation = variation;
13099        }
13100      }
13101    }
13102
13103    return meta;
13104  }
13105
13106  function applyAttributeSelects($row, attributes) {
13107    if (!attributes) {
13108      return;
13109    }
13110
13111    eachVariationAttributeSelect(
13112      getRowElements($row),
13113      function ($select, name) {
13114        if (typeof attributes[name] !== "undefined") {
13115          $select.val(attributes[name]);
13116        }
13117      },
13118    );
13119  }
13120
13121  function rowNeedsPrepWait($row) {
13122    if (!isVariableRow($row)) {
13123      return false;
13124    }
13125
13126    return !!getRowElements($row).find(
13127      ".variations_form, .wcpt-select-variation-dropdown, .wcpt-variation-radio",
13128    ).length;
13129  }
13130
13131  function whenRowPrepComplete($row, callback) {
13132    var $primaryRow = resolvePrimaryRow($row);
13133
13134    if ($primaryRow.data("wcpt_prep_variation_complete")) {
13135      callback();
13136      return;
13137    }
13138
13139    $primaryRow.one("wcpt_row_prep_variation_complete", callback);
13140  }
13141
13142  function selectPersistedVariation($row, entry, callback) {
13143    if (!entry || !entry.variation_id) {
13144      if (callback) {
13145        callback();
13146      }
13147      return;
13148    }
13149
13150    var $primaryRow = resolvePrimaryRow($row),
13151      $rows = getRowElements($primaryRow),
13152      variationId = String(entry.variation_id),
13153      meta = getVariationMetaFromEntry($primaryRow, entry),
13154      $form = $rows.find(".variations_form").first(),
13155      $dropdown = $rows.find(".wcpt-select-variation-dropdown"),
13156      $radio = $rows.find('.wcpt-variation-radio[value="' + variationId + '"]'),
13157      $attrSelects = $rows.find(".wcpt-select-variation-attribute-term"),
13158      $target = getVariationRowTarget($primaryRow);
13159
13160    isRestoring = true;
13161    applyAttributeSelects($primaryRow, meta.attributes);
13162
13163    function finishVariationRestore() {
13164      isRestoring = false;
13165      if (callback) {
13166        callback();
13167      }
13168    }
13169
13170    function triggerControl($control, setup) {
13171      setup();
13172      $control.trigger("change");
13173      finishVariationRestore();
13174    }
13175
13176    if ($dropdown.length) {
13177      triggerControl($dropdown, function () {
13178        $dropdown.val(variationId);
13179      });
13180      return;
13181    }
13182
13183    if ($radio.length) {
13184      triggerControl($radio, function () {
13185        $radio.prop("checked", true);
13186      });
13187      return;
13188    }
13189
13190    if ($form.length) {
13191      whenCartFormVariationReady($primaryRow, $form, variationId, function () {
13192        triggerSelectVariation(
13193          $target,
13194          buildSelectVariationMeta(meta, $form, variationId),
13195        );
13196        finishVariationRestore();
13197      });
13198      return;
13199    }
13200
13201    if ($attrSelects.length) {
13202      $target.one(
13203        "select_variation.wcptPersistRestore",
13204        finishVariationRestore,
13205      );
13206      $attrSelects.trigger("change");
13207      return;
13208    }
13209
13210    triggerSelectVariation($target, meta);
13211    finishVariationRestore();
13212  }
13213
13214  function restorePersistedRow($row, entry, onComplete) {
13215    function runRestore() {
13216      if (isVariableRow($row) && entry.variation_id) {
13217        selectPersistedVariation($row, entry, onComplete);
13218        return;
13219      }
13220
13221      applyPersistedCheckboxAndQty($row, entry);
13222      if (onComplete) {
13223        onComplete();
13224      }
13225    }
13226
13227    if (rowNeedsPrepWait($row)) {
13228      whenRowPrepComplete($row, runRestore);
13229      return;
13230    }
13231
13232    runRestore();
13233  }
13234
13235  function restoreTablePage($container) {
13236    if (typeof wcpt_util === "undefined") {
13237      releaseRestoreSuppress();
13238      return;
13239    }
13240
13241    var table_id = wcpt_util.get_table_id($container);
13242    if (!table_id) {
13243      releaseRestoreSuppress();
13244      return;
13245    }
13246
13247    var tableState =
13248      pendingRestoreState[table_id] ||
13249      (memoryState[table_id]
13250        ? $.extend(true, {}, memoryState[table_id])
13251        : null);
13252
13253    delete pendingRestoreState[table_id];
13254
13255    if (!tableState || $.isEmptyObject(tableState)) {
13256      finalizeTablePage($container);
13257      return;
13258    }
13259
13260    if (!memoryState[table_id]) {
13261      memoryState[table_id] = $.extend(true, {}, tableState);
13262    }
13263
13264    publishState();
13265
13266    var restoredKeys = {},
13267      restoredVariableProducts = {},
13268      pendingRestores = 0;
13269
13270    function finishRestoreCycle() {
13271      pendingRestores--;
13272      if (pendingRestores <= 0) {
13273        finalizeTablePage($container);
13274      }
13275    }
13276
13277    $container.find(".wcpt-row").each(function () {
13278      var $primaryRow = getPrimaryRow($(this));
13279      if (!$primaryRow.length) {
13280        return;
13281      }
13282
13283      var productId = $primaryRow.attr("data-wcpt-product-id"),
13284        match,
13285        restoreKey;
13286
13287      if (isVariableRow($primaryRow) && productId) {
13288        if (restoredVariableProducts[productId]) {
13289          return;
13290        }
13291        match = findPersistedEntry(tableState, productId, false, true);
13292        if (!match) {
13293          return;
13294        }
13295        restoredVariableProducts[productId] = true;
13296        restoreKey = "p:" + String(productId);
13297      } else {
13298        match = findRestoreMatch(tableState, $primaryRow);
13299        if (!match) {
13300          return;
13301        }
13302        restoreKey = resolveRestoreKey(match, productId);
13303      }
13304
13305      if (!restoreKey || restoredKeys[restoreKey]) {
13306        return;
13307      }
13308
13309      restoredKeys[restoreKey] = true;
13310      pendingRestores++;
13311      restorePersistedRow($primaryRow, match.entry, finishRestoreCycle);
13312    });
13313
13314    if (pendingRestores <= 0) {
13315      finalizeTablePage($container);
13316    }
13317  }
13318
13319  function onVariationSelected($row, data) {
13320    var ctx = getTableContext($row);
13321    if (!ctx) {
13322      return;
13323    }
13324
13325    var productId = ctx.$row.attr("data-wcpt-product-id");
13326    if (!productId) {
13327      return;
13328    }
13329
13330    var variationId =
13331        data && data.variation_id ? String(data.variation_id) : "",
13332      isSelected = !!(data && data.variation_selected && variationId);
13333
13334    if (isSelected) {
13335      var match = memoryState[ctx.table_id]
13336        ? findPersistedEntry(memoryState[ctx.table_id], productId, variationId)
13337        : null;
13338
13339      if (match) {
13340        applyPersistedCheckboxAndQty(ctx.$row, match.entry);
13341        return;
13342      }
13343    }
13344
13345    if (isRestoring || suppressVariationPersistMutations) {
13346      return;
13347    }
13348
13349    if (!isSelected) {
13350      removePersistedEntriesForProduct(
13351        ctx.table_id,
13352        productId,
13353        false,
13354        ctx.$container,
13355      );
13356      return;
13357    }
13358
13359    removePersistedEntriesForProduct(
13360      ctx.table_id,
13361      productId,
13362      variationId,
13363      ctx.$container,
13364    );
13365
13366    if (ctx.$row.data("wcpt_checked")) {
13367      saveRow(ctx.$row);
13368    }
13369  }
13370
13371  // ---------------------------------------------------------------------------
13372  // cart payload integration
13373  // ---------------------------------------------------------------------------
13374
13375  function aggregateOffPageIntoPayload(tableContainersById, payload) {
13376    if (typeof wcpt_util === "undefined" || !tableContainersById || !payload) {
13377      return;
13378    }
13379
13380    var products = payload.products || {},
13381      variations = payload.variations || {},
13382      attributes = payload.attributes || {};
13383
13384    try {
13385      $.each(tableContainersById, function (table_id, $container) {
13386        if (!memoryState[table_id]) {
13387          return;
13388        }
13389
13390        $.each(memoryState[table_id], function (key, entry) {
13391          if (!entry || !entry.product_id) {
13392            return;
13393          }
13394
13395          var product_id = String(entry.product_id),
13396            variation_id = entry.variation_id
13397              ? String(entry.variation_id)
13398              : false,
13399            qty = parseFloat(entry.qty);
13400
13401          if (isNaN(qty) || qty <= 0) {
13402            qty = 1;
13403          }
13404
13405          var $candidate_rows = $container.find(
13406            '.wcpt-row[data-wcpt-product-id="' + product_id + '"]',
13407          );
13408
13409          if (variation_id) {
13410            $candidate_rows = $candidate_rows.filter(function () {
13411              var $r = $(this);
13412              return (
13413                $r.attr("data-wcpt-variation-id") === variation_id ||
13414                String($r.data("wcpt_variation_id")) === variation_id
13415              );
13416            });
13417          }
13418
13419          var has_local_checked = $candidate_rows.filter(function () {
13420            return !!$(this).data("wcpt_checked");
13421          }).length;
13422
13423          if (has_local_checked) {
13424            return;
13425          }
13426
13427          if (typeof products[product_id] === "undefined") {
13428            products[product_id] = qty;
13429          } else {
13430            products[product_id] += qty;
13431          }
13432
13433          if (variation_id) {
13434            if (!variations[product_id]) {
13435              variations[product_id] = {};
13436            }
13437            if (!variations[product_id][variation_id]) {
13438              variations[product_id][variation_id] = qty;
13439            } else {
13440              variations[product_id][variation_id] += qty;
13441            }
13442
13443            if (entry.attributes) {
13444              attributes[variation_id] = entry.attributes;
13445            }
13446          }
13447        });
13448      });
13449    } catch (e) {
13450      // ignore aggregation failures
13451    }
13452
13453    payload.products = products;
13454    payload.variations = variations;
13455    payload.attributes = attributes;
13456  }
13457
13458  function clearTables(tableContainersById) {
13459    if (typeof wcpt_util === "undefined" || !tableContainersById) {
13460      return;
13461    }
13462
13463    $.each(tableContainersById, function (table_id, $container) {
13464      if (!table_id || !$container || !$container.length) {
13465        return;
13466      }
13467      clearTable(table_id, $container);
13468    });
13469  }
13470
13471  // ---------------------------------------------------------------------------
13472  // event bindings
13473  // ---------------------------------------------------------------------------
13474
13475  // save on check
13476  $("body").on("wcpt_checkbox_change", ".wcpt-row", function (e, state) {
13477    if (isRestoring || suppressVariationPersistMutations || !state) {
13478      return;
13479    }
13480    saveRow($(this));
13481  });
13482
13483  $("body").on("change", ".wcpt-cart-checkbox", function () {
13484    if (isRestoring || suppressVariationPersistMutations) {
13485      return;
13486    }
13487    var $cb = $(this);
13488    if (!$cb.prop("checked")) {
13489      removeRow($cb.closest(".wcpt-row"));
13490    }
13491  });
13492
13493  // qty -> 0 removes persisted row (not wcpt_checkbox_change false — prep resets fire that too)
13494  $("body").on(
13495    "change",
13496    ".wcpt input.qty, .wcpt select.wcpt-qty-select",
13497    function () {
13498      if (isRestoring || suppressVariationPersistMutations) {
13499        return;
13500      }
13501
13502      var $this = $(this);
13503      if ($this.closest("form.cart").length) {
13504        return;
13505      }
13506
13507      var val = parseFloat($this.val());
13508      if (isNaN(val) || val <= 0) {
13509        var $row = $this.closest(".wcpt-row");
13510        if (typeof window.wcpt_get_original_row === "function") {
13511          $row = window.wcpt_get_original_row($row);
13512        }
13513        removeRow($row);
13514        return;
13515      }
13516
13517      var $row = $this.closest(".wcpt-row");
13518      if (typeof window.wcpt_get_original_row === "function") {
13519        $row = window.wcpt_get_original_row($row);
13520      }
13521
13522      if (isVariableRow($row) && !$row.data("wcpt_variation_id")) {
13523        return;
13524      }
13525
13526      if ($row.data("wcpt_checked")) {
13527        saveRow($row);
13528      }
13529    },
13530  );
13531
13532  // flush checked rows before DOM is replaced (fast pagination)
13533  $("body").on("wcpt_before_ajax_container_replace", function (e, data) {
13534    if (!data || !data.$container) {
13535      return;
13536    }
13537    flushVisibleRows(data.$container);
13538  });
13539
13540  // snapshot before prep_variation_options can clear persisted rows via qty reset
13541  $("body").on("wcpt_before_every_load", ".wcpt", function () {
13542    if (typeof wcpt_util === "undefined") {
13543      return;
13544    }
13545
13546    var table_id = wcpt_util.get_table_id($(this));
13547    if (table_id && memoryState[table_id]) {
13548      pendingRestoreState[table_id] = $.extend(true, {}, memoryState[table_id]);
13549    }
13550
13551    suppressVariationPersistMutations = true;
13552  });
13553
13554  $("body").on(
13555    "select_variation",
13556    ".wcpt-row.wcpt-product-type-variable",
13557    function (e, data) {
13558      var $row = $(this);
13559      queueMicrotask(function () {
13560        onVariationSelected($row, data);
13561      });
13562    },
13563  );
13564
13565  // run after freeze-table layout so sibling rows exist for restore
13566  $("body").on("wcpt_layout", ".wcpt", function (e, data) {
13567    if (!data || data.source !== "after_every_load") {
13568      return;
13569    }
13570
13571    restoreTablePage($(this));
13572  });
13573
13574  $(document).on(
13575    "wcpt_clear_all_checked_for_table",
13576    function (e, table_id, $container) {
13577      clearTable(table_id, $container);
13578    },
13579  );
13580
13581  // ---------------------------------------------------------------------------
13582  // public API
13583  // ---------------------------------------------------------------------------
13584
13585  window.wcpt_cart_checkbox_persist = {
13586    getState: getState,
13587    hasPersistedVariableProduct: hasPersistedVariableProduct,
13588    aggregateOffPageIntoPayload: aggregateOffPageIntoPayload,
13589    clearTables: clearTables,
13590  };
13591
13592  publishState();
13593})(jQuery);
13594
13595// module: product compare
13596(function ($) {
13597  // ---- Compare State "API" ----
13598
13599  // Separate cookie for WCPT native compare logic
13600  var WCPT_COMPARE_COOKIE = "wcpt_compare_products";
13601
13602  // Get products in compare (array of strings)
13603  function getCompareIDs() {
13604    var val = getCookie(WCPT_COMPARE_COOKIE);
13605    if (!val || val === "null") return [];
13606    return val
13607      .split(",")
13608      .map(function (v) {
13609        return v.trim();
13610      })
13611      .filter(function (v) {
13612        return !!v;
13613      });
13614  }
13615
13616  // Set products in compare (array of strings)
13617  function setCompareIDs(ids) {
13618    setCookie(WCPT_COMPARE_COOKIE, ids.join(","), 30); // 30 day expiry for native compare
13619    $(document).trigger("wcpt_compare_changed", [ids]); // Broadcast state change
13620  }
13621
13622  // Add/remove by id (API helpers)
13623  function addToCompare(id) {
13624    var ids = getCompareIDs();
13625    id = String(id);
13626    if (ids.indexOf(id) === -1) {
13627      ids.push(id);
13628      setCompareIDs(ids);
13629    }
13630  }
13631  function removeFromCompare(id) {
13632    var ids = getCompareIDs();
13633    id = String(id);
13634    var idx = ids.indexOf(id);
13635    if (idx !== -1) {
13636      ids.splice(idx, 1);
13637      setCompareIDs(ids);
13638    }
13639  }
13640  function isInCompare(id) {
13641    var ids = getCompareIDs();
13642    return ids.indexOf(String(id)) !== -1;
13643  }
13644  function clearCompare() {
13645    setCompareIDs([]);
13646  }
13647
13648  // Expose public API
13649  window.wcptCompare = {
13650    getIDs: getCompareIDs,
13651    setIDs: setCompareIDs,
13652    add: addToCompare,
13653    remove: removeFromCompare,
13654    isIn: isInCompare,
13655    clear: clearCompare,
13656  };
13657
13658  // --- Cookie utils
13659  function getCookie(name) {
13660    var nameEQ = name + "=";
13661    var ca = document.cookie.split(";");
13662    for (var i = 0; i < ca.length; i++) {
13663      var c = ca[i].trim();
13664      if (c.indexOf(nameEQ) === 0)
13665        return decodeURIComponent(c.substring(nameEQ.length));
13666    }
13667    return "";
13668  }
13669
13670  function setCookie(name, val, days) {
13671    var d = new Date();
13672    d.setTime(d.getTime() + days * 24 * 60 * 60 * 1000);
13673    var expires = "expires=" + d.toUTCString();
13674    document.cookie =
13675      name + "=" + encodeURIComponent(val) + "; " + expires + "; path=/";
13676  }
13677
13678  // --- UI STATE REFRESH ---
13679
13680  // Helper function to get product/variation ID from row
13681  function getProductIdFromRow($row) {
13682    // First check for variation_id (using .data() for variable products with selected variation)
13683    var variationId = $row.data("wcpt_variation_id");
13684    if (variationId) {
13685      return String(variationId);
13686    }
13687    // Then check for data-wcpt-variation-id attribute (for variation products)
13688    var variationIdAttr = $row.attr("data-wcpt-variation-id");
13689    if (variationIdAttr) {
13690      return String(variationIdAttr);
13691    }
13692    // Finally fall back to product ID
13693    var productId = $row.attr("data-wcpt-product-id");
13694    if (productId) {
13695      return String(productId);
13696    }
13697    return "";
13698  }
13699
13700  function updateCompareButtonStates(forceIDs) {
13701    // Optionally accept an override ids (for API parity/event-coalescing)
13702    var ids = forceIDs || getCompareIDs();
13703
13704    $(".wcpt-compare-button").each(function () {
13705      var $btn = $(this);
13706      var $row = $btn.closest(".wcpt-row");
13707      var id = getProductIdFromRow($row);
13708      var $count = $btn.find(".wcpt-compare-button__count");
13709      var $input = $btn.find("input[type='checkbox']");
13710      if (!id) return;
13711
13712      // Update button's data attribute to match current row state
13713      $btn.attr("data-wcpt-product-id", id);
13714
13715      var inCompare = ids.indexOf(id) !== -1;
13716      if (inCompare) {
13717        $btn.removeClass("wcpt-not-in-compare").addClass("wcpt-in-compare");
13718        $input.prop("checked", true);
13719      } else {
13720        $btn.removeClass("wcpt-in-compare").addClass("wcpt-not-in-compare");
13721        $input.prop("checked", false);
13722      }
13723      if ($count.length) {
13724        $count.text("(" + ids.length + ")");
13725      }
13726    });
13727  }
13728
13729  // ---- WCOSC SYNC API (completely separate from native)
13730  // When possible, keep woosc integration as *optionally* in sync for visuals
13731  function updateWooscButtonStatesSyncWithWCPT() {
13732    if (typeof woosc_vars == "undefined") return;
13733    // Set the WOOSC cookie value to our (WCPT) compare IDs
13734    var woosc_cookie_name = "woosc_products_" + woosc_vars.user_id;
13735    var wcpt_ids = getCompareIDs();
13736    setCookie(woosc_cookie_name, wcpt_ids.join(","), 30);
13737
13738    // Use wcpt_ids for button state sync
13739    $(".woosc-btn").each(function () {
13740      var $btn = $(this);
13741      var id = $btn.attr("data-id");
13742      if (wcpt_ids.indexOf(String(id)) !== -1) {
13743        $btn.addClass("woosc-btn-added woosc-added");
13744      } else {
13745        $btn.removeClass("woosc-btn-added woosc-added");
13746      }
13747    });
13748  }
13749
13750  // ---- UI/State Observers ----
13751
13752  // Initial render/init: get compare state from cookie and update UI
13753  function initCompareUI() {
13754    updateCompareButtonStates();
13755    updateWooscButtonStatesSyncWithWCPT();
13756  }
13757
13758  $(function () {
13759    initCompareUI();
13760  });
13761
13762  // Also update on ajax table reloads
13763  $(document).on("wcpt_after_every_load", function () {
13764    updateCompareButtonStates();
13765    updateWooscButtonStatesSyncWithWCPT();
13766  });
13767
13768  // Any time our compare state changes (API), refresh UI
13769  $(document).on("wcpt_compare_changed", function (e, ids) {
13770    if (!ids) ids = getCompareIDs();
13771    updateCompareButtonStates(ids);
13772    updateWooscButtonStatesSyncWithWCPT();
13773    // ^ Only for visual update, but NOT to modify woosc cookie.
13774  });
13775
13776  // ---- Button Event Listeners ----
13777
13778  // Main click handler: toggle state, update UI/state, NO interference with woosc
13779  $(document).on("click", ".wcpt-compare-button", function (e) {
13780    // If the event is from a checkbox (or woosc-integration), skip here
13781    if (
13782      $(e.target).hasClass("wcpt-compare-button__external-link") ||
13783      $(e.target).closest(".wcpt-compare-button__external-link").length ||
13784      $(e.target).hasClass("woosc-btn") ||
13785      $(e.target).closest(".woosc-btn").length ||
13786      $(e.target).hasClass("wcpt-compare-button__icon")
13787    ) {
13788      return;
13789    }
13790
13791    var $btn = $(this);
13792    var $row = $btn.closest(".wcpt-row");
13793    var id = getProductIdFromRow($row);
13794    if (!id) return;
13795
13796    // Update button's data attribute to match current row state
13797    $btn.attr("data-wcpt-product-id", id);
13798
13799    if (isInCompare(id)) {
13800      removeFromCompare(id);
13801    } else {
13802      addToCompare(id);
13803      setTimeout(function () {
13804        $(".woosc-btn.woosc-added").first().trigger("click");
13805      }, 100);
13806    }
13807    // UI updates after wcpt_compare_changed event
13808  });
13809
13810  // push changes from woosc to wcpt
13811  $(document).on("woosc_change_count", function (e, count) {
13812    var woosc_cookie_name = "woosc_products_" + woosc_vars.user_id;
13813    var woosc_compare_ids = getCookie(woosc_cookie_name) || [];
13814
13815    setCompareIDs(
13816      typeof woosc_compare_ids === "string"
13817        ? woosc_compare_ids.split(",")
13818        : woosc_compare_ids,
13819    );
13820    updateCompareButtonStates();
13821  });
13822
13823  // Listen for select_variation event to update button ID and initialize state
13824  $(document).on("select_variation", ".wcpt-row", function (e, data) {
13825    var $row = $(this);
13826    var $btn = $row.find(".wcpt-compare-button");
13827
13828    if (!$btn.length) return;
13829
13830    // Get the current ID from row (will prioritize variation_id if available)
13831    var id = getProductIdFromRow($row);
13832
13833    if (id) {
13834      // Update button's data attribute
13835      $btn.attr("data-wcpt-product-id", id);
13836
13837      // Initialize button state with current compare state
13838      var ids = getCompareIDs();
13839      var inCompare = ids.indexOf(id) !== -1;
13840      var $count = $btn.find(".wcpt-compare-button__count");
13841      var $input = $btn.find("input[type='checkbox']");
13842
13843      if (inCompare) {
13844        $btn.removeClass("wcpt-not-in-compare").addClass("wcpt-in-compare");
13845        $input.prop("checked", true);
13846      } else {
13847        $btn.removeClass("wcpt-in-compare").addClass("wcpt-not-in-compare");
13848        $input.prop("checked", false);
13849      }
13850
13851      if ($count.length) {
13852        $count.text("(" + ids.length + ")");
13853      }
13854    }
13855  });
13856})(jQuery);
13857
13858// module: product quick view
13859(function ($) {
13860  $(document).on("click", ".wcpt-quick-view-button", function (e) {
13861    if ($(e.target).closest(".woosq-btn").length) {
13862      return;
13863    }
13864
13865    var $btn = $(this);
13866    $btn.find(".woosq-btn").click();
13867  });
13868})(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.