@depict-ai/ui 4.10.20 → 4.10.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/ES10/index.js CHANGED
@@ -10,7 +10,7 @@ import $8tj43$babelruntimehelpersclassPrivateFieldGet from "@babel/runtime/helpe
10
10
  /*! Depict.ai search and product listing SDK */
11
11
  import { register_es_version } from "../multi_import_deduplicator.mjs";
12
12
  register_es_version("ui", "ES10");
13
- const $8db9a15135765c11$export$83d89fbfd8236492 = "4.10.20";
13
+ const $8db9a15135765c11$export$83d89fbfd8236492 = "4.10.22";
14
14
  function $00a5b94f0c11efbb$export$d9f36cf74e24d595(media_query) {
15
15
  const signal = $8tj43$createSignal(false);
16
16
  $8tj43$onCleanup($8tj43$javascript_media_query(media_query, _ref => {
@@ -1179,8 +1179,15 @@ function $45a8953d760b5e58$export$a1fd619a4743ca08(_ref) {
1179
1179
  setFirstResultInViewport(Math.min(...intersecting) + 1); // +1 because we want to start counting at 1
1180
1180
  setLastResultsInViewport(Math.max(...intersecting) + 1);
1181
1181
  });
1182
- return renderedDisplays_;
1182
+ // Content blocks placed after the last product (but still in its row) have no product card to render in front of, so render them after the cards once there are no more products to load
1183
+ const trailingContentBlocks_ = $8tj43$createMemo(() => {
1184
+ var _content_blocks_, _content_blocks_$trai;
1185
+ if (!all_products_loaded_[0]()) return [];
1186
+ return (content_blocks_ === null || content_blocks_ === void 0 ? void 0 : (_content_blocks_ = content_blocks_()) === null || _content_blocks_ === void 0 ? void 0 : (_content_blocks_$trai = _content_blocks_.trailing_) === null || _content_blocks_$trai === void 0 ? void 0 : _content_blocks_$trai.call(_content_blocks_, displays_by_key_().size)) || [];
1187
+ });
1188
+ return [renderedDisplays_, trailingContentBlocks_]; // Regarding the as unknown: https://github.com/solidjs/solid/releases/tag/v1.7.0
1183
1189
  }
1190
+
1184
1191
  var $63f1f8dbcd07d3b0$var$_tmpl$ = /*#__PURE__*/$8tj43$template(`<div>`),
1185
1192
  $63f1f8dbcd07d3b0$var$_tmpl$2 = /*#__PURE__*/$8tj43$template(`<style type=text/css>`);
1186
1193
  function $63f1f8dbcd07d3b0$export$d21e32dfedac04be(props) {
@@ -1553,10 +1560,18 @@ function $ff7feae4dddd6073$export$791ef57e62e21c3f(_ref) {
1553
1560
  set_top_row_shortened_by_(blocks_in_top_row_taken - 1); // minus one because the content block wrapper also is an element
1554
1561
  });
1555
1562
 
1556
- return $8tj43$createMemo(() => $ff7feae4dddd6073$export$92aa5d691d588f3f({
1563
+ const remapped_ = $8tj43$createMemo(() => $ff7feae4dddd6073$export$92aa5d691d588f3f({
1557
1564
  wrapped_blocks_: get_wrapped_blocks_(),
1558
1565
  n_cols_currently_showing_: n_cols_currently_showing_()
1559
- }).content_blocks_by_index_);
1566
+ }));
1567
+ const content_blocks_by_index_ = $8tj43$createMemo(() => remapped_().content_blocks_by_index_);
1568
+ return Object.assign(content_blocks_by_index_, {
1569
+ trailing_: product_count => $ff7feae4dddd6073$export$b94605e5b3ce01c({
1570
+ ...remapped_(),
1571
+ n_cols_currently_showing_: n_cols_currently_showing_(),
1572
+ product_count: product_count
1573
+ })
1574
+ });
1560
1575
  }
1561
1576
  function $ff7feae4dddd6073$export$92aa5d691d588f3f(_ref2) {
1562
1577
  let {
@@ -1565,13 +1580,16 @@ function $ff7feae4dddd6073$export$92aa5d691d588f3f(_ref2) {
1565
1580
  } = _ref2;
1566
1581
  // This computation should be quite cheap since it just moves around things into an array
1567
1582
  const content_blocks_by_index_ = [];
1583
+ const rows_by_index_ = []; // the row of each block at each index of content_blocks_by_index_, in the same order
1568
1584
  const constructed_block_arrays = new WeakSet();
1569
1585
  // Need to keep track of products displaced by blocks above when mapping rows to indexes in the actual grid
1570
1586
  const displaced_products_ = []; // undefined = is product, true = is content block if number instead of tuple number indicates content blocks in that row. Index in first array is row, index in second array is column
1571
1587
  if (!n_cols_currently_showing_)
1572
1588
  // Nope out of here if we don't know how many columns are showing
1573
1589
  return {
1574
- content_blocks_by_index_: content_blocks_by_index_
1590
+ content_blocks_by_index_: content_blocks_by_index_,
1591
+ rows_by_index_: rows_by_index_,
1592
+ displaced_products_: displaced_products_
1575
1593
  };
1576
1594
  for (let i = 0; i < wrapped_blocks_.length; i++) {
1577
1595
  // i = row to show at
@@ -1619,6 +1637,7 @@ function $ff7feae4dddd6073$export$92aa5d691d588f3f(_ref2) {
1619
1637
  content_blocks_by_index_[final_target_index] = array;
1620
1638
  }
1621
1639
  } else content_blocks_by_index_[final_target_index] = block_content;
1640
+ (rows_by_index_[final_target_index] || (rows_by_index_[final_target_index] = [])).push(i);
1622
1641
  // Update displacement
1623
1642
  for (let j = 0; j < span_rows; j++) {
1624
1643
  var _ref3;
@@ -1628,9 +1647,46 @@ function $ff7feae4dddd6073$export$92aa5d691d588f3f(_ref2) {
1628
1647
  }
1629
1648
  return {
1630
1649
  content_blocks_by_index_: content_blocks_by_index_,
1650
+ rows_by_index_: rows_by_index_,
1631
1651
  displaced_products_: displaced_products_
1632
1652
  };
1633
1653
  }
1654
+ function $ff7feae4dddd6073$export$b94605e5b3ce01c(_ref4) {
1655
+ let {
1656
+ content_blocks_by_index_: content_blocks_by_index_,
1657
+ rows_by_index_: rows_by_index_,
1658
+ displaced_products_: displaced_products_,
1659
+ n_cols_currently_showing_: n_cols_currently_showing_,
1660
+ product_count: product_count
1661
+ } = _ref4;
1662
+ const trailing = [];
1663
+ if (!n_cols_currently_showing_ || product_count <= 0) return trailing;
1664
+ // Find the row the last product lands in: each row has room for the columns that blocks don't take up
1665
+ let last_product_row = 0;
1666
+ for (let seen_products = 0;; last_product_row++) {
1667
+ const row = displaced_products_[last_product_row];
1668
+ const taken_by_blocks = typeof row === "number" ? row : row ? row.filter(item => item).length : 0;
1669
+ seen_products += Math.max(0, n_cols_currently_showing_ - taken_by_blocks);
1670
+ if (seen_products >= product_count) break;
1671
+ // Rows past the ones blocks take up all have room for products, so we're done by then at the latest
1672
+ if (last_product_row >= displaced_products_.length + product_count) break;
1673
+ }
1674
+ for (let i = product_count; i < content_blocks_by_index_.length; i++) {
1675
+ var _rows$;
1676
+ const block = content_blocks_by_index_[i];
1677
+ if (block === undefined) continue;
1678
+ const rows = rows_by_index_[i] || [];
1679
+ if (Array.isArray(block) && rows.length > 1) {
1680
+ // Several blocks share this index (e.g. the end of one row and the start of the next): keep the ones in the last product's row
1681
+ const in_last_row = block.filter((_, j) => rows[j] <= last_product_row);
1682
+ if (in_last_row.length) trailing.push(in_last_row);
1683
+ continue;
1684
+ }
1685
+ if (((_rows$ = rows[0]) !== null && _rows$ !== void 0 ? _rows$ : Infinity) > last_product_row) continue; // a row below the last product's row: keep it hidden
1686
+ trailing.push(block);
1687
+ }
1688
+ return trailing;
1689
+ }
1634
1690
  /**
1635
1691
  * Takes a store that contains an array from where index is number of row and value is the result of executing the content block rendering function.
1636
1692
  * Then renders the content and wraps it into a div with the correct styling.
@@ -1683,6 +1739,7 @@ function $ff7feae4dddd6073$var$wrap_blocks_in_div(rendered_content_blocks, max_s
1683
1739
  content: () => {
1684
1740
  // Sneaky on-demand rendering, so we don't render more blocks than we can show: we have the rendered value here and don't return a function that renders because if our block would be moved around we'd be guaranteed to re-render
1685
1741
  // However, no need to count products to know if we should render: the <For> loops in Placeholders and render_displays will only call this function if there's actually a product here that wants to display this block
1742
+ // (or, once all products have loaded, ResultsProductCards renders the blocks placed after the last product)
1686
1743
  set_rendering_delayed_(false);
1687
1744
  return content;
1688
1745
  },
@@ -2573,6 +2630,63 @@ function $4711a82d08d2021b$export$fecbb157621501bd(_ref) {
2573
2630
  return outmost_element;
2574
2631
  }
2575
2632
  $8tj43$delegateEvents(["click"]);
2633
+
2634
+ /**
2635
+ * `HTMLElement.focus()` is a **silent no-op** on an element that is not being rendered
2636
+ * (`display: none`, `visibility: hidden`, a collapsed menu). Nothing throws, focus simply
2637
+ * stays where it was, so a caller that does not check believes it restored focus when it
2638
+ * left it on `<body>` — which puts the keyboard user back at the top of the document.
2639
+ *
2640
+ * That is reachable whenever the element which opened a modal stops being rendered while the
2641
+ * modal is open, and the common way for that to happen is the responsive-duplicate header:
2642
+ * a theme renders the same search trigger twice, once for desktop and once for mobile, and
2643
+ * hides one at the current breakpoint. Open the modal from the desktop trigger, cross the
2644
+ * breakpoint (rotate a tablet, resize a window), close the modal, and the remembered trigger
2645
+ * is still `isConnected` but no longer rendered. Verified against a live merchant storefront
2646
+ * on 4.10.19.
2647
+ *
2648
+ * So: verify that focus landed, and when it did not, look for a rendered twin of the same
2649
+ * control — the other half of the responsive pair — and use that instead.
2650
+ */ /**
2651
+ * An element can only take focus if it is actually being rendered.
2652
+ *
2653
+ * Injectable because it is the one part of this module that needs a layout engine: jsdom
2654
+ * reports no client rects for anything, so the specs pass their own predicate to exercise the
2655
+ * matching rules. The real predicate is only exercised in a browser.
2656
+ */
2657
+ const $1c5106dfe72d554c$export$e0652711c614c755 = element => !!element.getClientRects().length;
2658
+ /**
2659
+ * `document.activeElement` is the outermost shadow host when focus is inside a shadow tree,
2660
+ * so ask the element's own root rather than the document — matching how the rest of
2661
+ * modal_opener treats shadow-mounted modals.
2662
+ */
2663
+ const $1c5106dfe72d554c$var$has_focus = element => element.getRootNode().activeElement === element;
2664
+ /**
2665
+ * The accessible-name-ish identity we match twins on. Deliberately narrow: an explicit label
2666
+ * or the trimmed text. Two controls that share a tag and this string are the same control
2667
+ * rendered twice, which is exactly the responsive-duplicate case.
2668
+ */
2669
+ const $1c5106dfe72d554c$var$identity_of = element => (element.getAttribute("aria-label") || element.getAttribute("title") || element.textContent || "").trim().replace(/\s+/g, " ");
2670
+ function $1c5106dfe72d554c$export$5800d9d535a06e96(element) {
2671
+ let rendered = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : $1c5106dfe72d554c$export$e0652711c614c755;
2672
+ const identity = $1c5106dfe72d554c$var$identity_of(element);
2673
+ if (!identity) return; // nothing to match on — an icon-only button with no label
2674
+ const twins = [...element.ownerDocument.querySelectorAll(element.localName)].filter(candidate => candidate !== element && rendered(candidate) && $1c5106dfe72d554c$var$identity_of(candidate) === identity);
2675
+ return twins.length === 1 ? twins[0] : undefined;
2676
+ }
2677
+ function $1c5106dfe72d554c$export$51461d633c351649(element) {
2678
+ let rendered = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : $1c5106dfe72d554c$export$e0652711c614c755;
2679
+ element.focus({
2680
+ preventScroll: true
2681
+ });
2682
+ if ($1c5106dfe72d554c$var$has_focus(element)) return true;
2683
+ const twin = $1c5106dfe72d554c$export$5800d9d535a06e96(element, rendered);
2684
+ if (!twin) return false;
2685
+ twin.focus({
2686
+ preventScroll: true
2687
+ });
2688
+ return $1c5106dfe72d554c$var$has_focus(twin);
2689
+ }
2576
2690
  async function $d5f1e6a0f9117286$export$79b1565500412d9b(search_modal, modal_fn_opts, parent) {
2577
2691
  const body = parent || document.body || (await (0, $8tj43$observer).wait_for_element("body"));
2578
2692
  let dispose_and_remove;
@@ -2638,9 +2752,12 @@ async function $d5f1e6a0f9117286$export$79b1565500412d9b(search_modal, modal_fn_
2638
2752
  });
2639
2753
  focused = requested_focus_target.getRootNode().activeElement === requested_focus_target;
2640
2754
  }
2641
- if (!focused && element_focused_before_open !== null && element_focused_before_open !== void 0 && element_focused_before_open.isConnected) element_focused_before_open.focus({
2642
- preventScroll: true
2643
- });
2755
+ if (!focused && element_focused_before_open !== null && element_focused_before_open !== void 0 && element_focused_before_open.isConnected)
2756
+ // restore_focus_to rather than a bare focus(): the remembered trigger can be
2757
+ // connected but no longer rendered (a responsive-duplicate header whose other
2758
+ // half is showing now), and focus() on an unrendered element silently does
2759
+ // nothing, leaving the keyboard user at the top of the page.
2760
+ $1c5106dfe72d554c$export$51461d633c351649(element_focused_before_open);
2644
2761
  } finally {
2645
2762
  // Cleared on a task instead of synchronously in case a browser delivers the focus event async
2646
2763
  setTimeout(() => restoring_focus = false, 0);
@@ -9683,20 +9800,28 @@ function $ac571292d606f30b$var$_classPrivateMethodGet(receiver, privateSet, fn)
9683
9800
  }
9684
9801
  var $ac571292d606f30b$var$_throwing_on_navigation = /*#__PURE__*/new WeakSet();
9685
9802
  var $ac571292d606f30b$var$_get_navigation_happened_promise = /*#__PURE__*/new WeakSet();
9803
+ var $ac571292d606f30b$var$_watch_for_navigation_to_same_href = /*#__PURE__*/new WeakSet();
9686
9804
  class $ac571292d606f30b$export$7e1297a3a19f10d8 {
9687
9805
  /**
9688
9806
  * Function that will be called when we want to navigate, or a string saying it's not an SPA. You can change this OTG
9689
9807
  */ /**
9690
9808
  * We store listing_id in history.state and do operate on this both here and in js-ui, this is the key to be used which js-ui provides
9691
9809
  */ /**
9692
- * Vue has async router that needs to be awaited (it navigates after a microtask)
9693
- * This standardises waiting for all kinds of routers
9694
- */
9810
+ * Called when a go_to_ didn't change location.href, for example when following a link to the page one is already on. href_change_ipns stays silent then, so whoever waits for a href change to react to a navigation has to listen here too
9811
+ */ /**
9812
+ * Vue has async router that needs to be awaited (it navigates after a microtask)
9813
+ * This standardises waiting for all kinds of routers
9814
+ */ /**
9815
+ * Calls navigation_to_same_href_listeners_ if the navigation we're about to request ends on the href we're already on
9816
+ * @returns function to stop watching, call it when the navigation failed
9817
+ */
9695
9818
  constructor(on_navigation, listing_query_state_key) {
9819
+ $ac571292d606f30b$var$_classPrivateMethodInitSpec(this, $ac571292d606f30b$var$_watch_for_navigation_to_same_href);
9696
9820
  $ac571292d606f30b$var$_classPrivateMethodInitSpec(this, $ac571292d606f30b$var$_get_navigation_happened_promise);
9697
9821
  $ac571292d606f30b$var$_classPrivateMethodInitSpec(this, $ac571292d606f30b$var$_throwing_on_navigation);
9698
9822
  $8tj43$babelruntimehelpersdefineProperty(this, "on_navigation_", void 0);
9699
9823
  $8tj43$babelruntimehelpersdefineProperty(this, "listing_query_state_key", void 0);
9824
+ $8tj43$babelruntimehelpersdefineProperty(this, "navigation_to_same_href_listeners_", new Set());
9700
9825
  $8tj43$babelruntimehelpersdefineProperty(this, "navigate_", {
9701
9826
  /**
9702
9827
  * Call this if you *just* want to replace the state object in history.state
@@ -9738,6 +9863,7 @@ class $ac571292d606f30b$export$7e1297a3a19f10d8 {
9738
9863
  }
9739
9864
  event_ === null || event_ === void 0 || event_.preventDefault();
9740
9865
  const promise = $ac571292d606f30b$var$_classPrivateMethodGet(this, $ac571292d606f30b$var$_get_navigation_happened_promise, $ac571292d606f30b$var$_get_navigation_happened_promise2).call(this, true);
9866
+ const stop_watching_for_same_href = $ac571292d606f30b$var$_classPrivateMethodGet(this, $ac571292d606f30b$var$_watch_for_navigation_to_same_href, $ac571292d606f30b$var$_watch_for_navigation_to_same_href2).call(this, new_url_);
9741
9867
  if (force_spa_navigation_ && wants_hard_navigation) {
9742
9868
  // Don't allow hard navigation if we're forcing SPA navigation, which we do for things where PageReplacer can be used for performance
9743
9869
  const new_state = history.state;
@@ -9751,7 +9877,7 @@ class $ac571292d606f30b$export$7e1297a3a19f10d8 {
9751
9877
  is_replace: is_replace_,
9752
9878
  new_url: new_url_ instanceof URL ? new_url_ : new URL(new_url_, location.href),
9753
9879
  scroll: scroll
9754
- });
9880
+ }, stop_watching_for_same_href);
9755
9881
  await promise;
9756
9882
  })
9757
9883
  });
@@ -9759,15 +9885,16 @@ class $ac571292d606f30b$export$7e1297a3a19f10d8 {
9759
9885
  this.on_navigation_ = on_navigation;
9760
9886
  }
9761
9887
  }
9762
- function $ac571292d606f30b$var$_throwing_on_navigation2() {
9888
+ function $ac571292d606f30b$var$_throwing_on_navigation2(options, on_throw_) {
9763
9889
  const {
9764
9890
  on_navigation_: on_navigation_
9765
9891
  } = this;
9766
9892
  if (typeof on_navigation_ !== "function") throw new Error("on_navigation_ is not a function");
9767
9893
  // make sure that we don't accidentally catchify if the SDK consumer has fucked up
9768
9894
  try {
9769
- return on_navigation_(...arguments);
9895
+ return on_navigation_(options);
9770
9896
  } catch (e) {
9897
+ on_throw_ === null || on_throw_ === void 0 || on_throw_();
9771
9898
  queueMicrotask(() => {
9772
9899
  throw e;
9773
9900
  });
@@ -9784,6 +9911,24 @@ function $ac571292d606f30b$var$_get_navigation_happened_promise2(is_replace) {
9784
9911
  (0, $8tj43$instant_exec_on_suspect_history_change).add(handler);
9785
9912
  });
9786
9913
  }
9914
+ function $ac571292d606f30b$var$_watch_for_navigation_to_same_href2(target_url) {
9915
+ const href_before = location.href;
9916
+ const stop_watching = () => {
9917
+ (0, $8tj43$instant_exec_on_suspect_history_change).delete(on_history_change);
9918
+ clearTimeout(timeout);
9919
+ };
9920
+ const notify_if_href_unchanged = $8tj43$catchify1(() => {
9921
+ stop_watching();
9922
+ if (location.href !== href_before) return;
9923
+ this.navigation_to_same_href_listeners_.forEach(listener => $8tj43$catchify1(listener)());
9924
+ });
9925
+ // The router has acted once it touches history, if the href is still the same then it took us to the page we're on
9926
+ const on_history_change = () => notify_if_href_unchanged();
9927
+ // A router asked to go to the exact href we're on might not touch history at all, so don't wait longer than 500ms for it. For any other target we can't tell a slow navigation from one that goes nowhere, so only stop listening after a while
9928
+ const timeout = target_url.href === href_before ? setTimeout(notify_if_href_unchanged, 500) : setTimeout(stop_watching, 10000);
9929
+ (0, $8tj43$instant_exec_on_suspect_history_change).add(on_history_change);
9930
+ return stop_watching;
9931
+ }
9787
9932
  var $e3efeb558d952696$var$_tmpl$ = /*#__PURE__*/$8tj43$template(`<button class="show-more major"type=button>`),
9788
9933
  $e3efeb558d952696$var$_tmpl$2 = /*#__PURE__*/$8tj43$template(`<h2 class=recs-title>`),
9789
9934
  $e3efeb558d952696$var$_tmpl$3 = /*#__PURE__*/$8tj43$template(`<div class=recommendation-grid><!$><!/><!$><!/>`);
@@ -11593,11 +11738,17 @@ var $fac58b5610eec6a9$var$_tmpl$ = /*#__PURE__*/$8tj43$template(`<svg xmlns=http
11593
11738
  function $fac58b5610eec6a9$export$8d8893ffd8aca39a() {
11594
11739
  return $8tj43$getNextElement($fac58b5610eec6a9$var$_tmpl$);
11595
11740
  }
11596
- function $a82a41ab7188eda2$export$93d9bfd0c9c1a5b8(close_modal, search_query_url_param_name_) {
11741
+ function $a82a41ab7188eda2$export$93d9bfd0c9c1a5b8(close_modal, search_query_url_param_name_, router_) {
11597
11742
  const [exit_iterable, exitable_iterable] = $8tj43$make_asyncIterable_exiter((0, $8tj43$href_change_ipns));
11743
+ const {
11744
+ navigation_to_same_href_listeners_: navigation_to_same_href_listeners_
11745
+ } = router_;
11598
11746
  let set_initial_values = false;
11599
11747
  let old_pathname;
11600
11748
  let old_param_value;
11749
+ // A card linking to the page one is already on doesn't change the href, close the modal anyway so the click does something
11750
+ navigation_to_same_href_listeners_.add(close_modal);
11751
+ $8tj43$onCleanup(() => navigation_to_same_href_listeners_.delete(close_modal));
11601
11752
  $8tj43$onCleanup($8tj43$catchify1(exit_iterable));
11602
11753
  $8tj43$catchify1(async () => {
11603
11754
  for await (const href of exitable_iterable) {
@@ -11758,7 +11909,7 @@ function $40411f2ec772c376$var$RawClassicSearchModal(_ref) {
11758
11909
  return _el$;
11759
11910
  })();
11760
11911
  if (disable_scrolling_) $e56d241d0f33d459$export$fe334901e8bebd57();
11761
- $a82a41ab7188eda2$export$93d9bfd0c9c1a5b8(close_modal_, search_query_url_param_name_);
11912
+ $a82a41ab7188eda2$export$93d9bfd0c9c1a5b8(close_modal_, search_query_url_param_name_, router_);
11762
11913
  window.addEventListener("keydown", close_on_escape_handler);
11763
11914
  $8tj43$onCleanup($8tj43$catchify1(() => window.removeEventListener("keydown", close_on_escape_handler)));
11764
11915
  if (closing_animation_) register_closing_animation_(closing_animation_);
@@ -12679,7 +12830,7 @@ function $06fcd2a345efd24d$var$RawSearchModalV2(_ref) {
12679
12830
  return (((_get_modal_search_res5 = get_modal_search_results_()) === null || _get_modal_search_res5 === void 0 ? void 0 : _get_modal_search_res5.loading) === false && !suggestions_response_.loading || !!prev) && totalNumberResults() === 0 && previous_searches_matching_query().length === 0;
12680
12831
  });
12681
12832
  if (disable_scrolling_) $e56d241d0f33d459$export$fe334901e8bebd57();
12682
- $a82a41ab7188eda2$export$93d9bfd0c9c1a5b8(close_modal_, search_query_url_param_name_);
12833
+ $a82a41ab7188eda2$export$93d9bfd0c9c1a5b8(close_modal_, search_query_url_param_name_, router_);
12683
12834
  addEventListener("keydown", close_on_escape_handler);
12684
12835
  $8tj43$onCleanup(() => removeEventListener("keydown", close_on_escape_handler));
12685
12836
  $8tj43$createEffect($8tj43$on(get_search_field_value_, () => {
package/latest/index.cjs CHANGED
@@ -109,7 +109,7 @@ $parcel$export(module.exports, "BreadCrumbs", () => $1f4925833f07df29$export$5b7
109
109
  $parcel$export(module.exports, "QuickLinks", () => $1f4925833f07df29$export$de035477d6fef996);
110
110
  $parcel$export(module.exports, "CategoryPage", () => $14f289047713d67a$export$e2bf8ce7779e827e);
111
111
  /*! Depict.ai search and product listing SDK */
112
- const $b757f75811f3261f$export$83d89fbfd8236492 = "4.10.20";
112
+ const $b757f75811f3261f$export$83d89fbfd8236492 = "4.10.22";
113
113
  function $b54e6192d0caa293$export$d9f36cf74e24d595(media_query) {
114
114
  const signal = $gVhhF$solidjs.createSignal(false);
115
115
  $gVhhF$solidjs.onCleanup($gVhhF$depictaiutilishared.javascript_media_query(media_query, ({
@@ -1232,8 +1232,14 @@ function $ac167f6af6a48fc3$export$a1fd619a4743ca08({
1232
1232
  setFirstResultInViewport(Math.min(...intersecting) + 1); // +1 because we want to start counting at 1
1233
1233
  setLastResultsInViewport(Math.max(...intersecting) + 1);
1234
1234
  });
1235
- return renderedDisplays_;
1235
+ // Content blocks placed after the last product (but still in its row) have no product card to render in front of, so render them after the cards once there are no more products to load
1236
+ const trailingContentBlocks_ = $gVhhF$solidjs.createMemo(() => {
1237
+ if (!all_products_loaded_[0]()) return [];
1238
+ return content_blocks_?.()?.trailing_?.(displays_by_key_().size) || [];
1239
+ });
1240
+ return [renderedDisplays_, trailingContentBlocks_]; // Regarding the as unknown: https://github.com/solidjs/solid/releases/tag/v1.7.0
1236
1241
  }
1242
+
1237
1243
  var $f48d0fbb00835aaa$var$_tmpl$ = /*#__PURE__*/$gVhhF$solidjsweb.template(`<div>`),
1238
1244
  $f48d0fbb00835aaa$var$_tmpl$2 = /*#__PURE__*/$gVhhF$solidjsweb.template(`<style type=text/css>`);
1239
1245
  function $f48d0fbb00835aaa$export$d21e32dfedac04be(props) {
@@ -1583,10 +1589,18 @@ function $d92f338ba8f9da9b$export$791ef57e62e21c3f({
1583
1589
  set_top_row_shortened_by_(blocks_in_top_row_taken - 1); // minus one because the content block wrapper also is an element
1584
1590
  });
1585
1591
 
1586
- return $gVhhF$solidjs.createMemo(() => $d92f338ba8f9da9b$export$92aa5d691d588f3f({
1592
+ const remapped_ = $gVhhF$solidjs.createMemo(() => $d92f338ba8f9da9b$export$92aa5d691d588f3f({
1587
1593
  wrapped_blocks_: get_wrapped_blocks_(),
1588
1594
  n_cols_currently_showing_: n_cols_currently_showing_()
1589
- }).content_blocks_by_index_);
1595
+ }));
1596
+ const content_blocks_by_index_ = $gVhhF$solidjs.createMemo(() => remapped_().content_blocks_by_index_);
1597
+ return Object.assign(content_blocks_by_index_, {
1598
+ trailing_: product_count => $d92f338ba8f9da9b$export$b94605e5b3ce01c({
1599
+ ...remapped_(),
1600
+ n_cols_currently_showing_: n_cols_currently_showing_(),
1601
+ product_count: product_count
1602
+ })
1603
+ });
1590
1604
  }
1591
1605
  function $d92f338ba8f9da9b$export$92aa5d691d588f3f({
1592
1606
  wrapped_blocks_: wrapped_blocks_,
@@ -1594,13 +1608,16 @@ function $d92f338ba8f9da9b$export$92aa5d691d588f3f({
1594
1608
  }) {
1595
1609
  // This computation should be quite cheap since it just moves around things into an array
1596
1610
  const content_blocks_by_index_ = [];
1611
+ const rows_by_index_ = []; // the row of each block at each index of content_blocks_by_index_, in the same order
1597
1612
  const constructed_block_arrays = new WeakSet();
1598
1613
  // Need to keep track of products displaced by blocks above when mapping rows to indexes in the actual grid
1599
1614
  const displaced_products_ = []; // undefined = is product, true = is content block if number instead of tuple number indicates content blocks in that row. Index in first array is row, index in second array is column
1600
1615
  if (!n_cols_currently_showing_)
1601
1616
  // Nope out of here if we don't know how many columns are showing
1602
1617
  return {
1603
- content_blocks_by_index_: content_blocks_by_index_
1618
+ content_blocks_by_index_: content_blocks_by_index_,
1619
+ rows_by_index_: rows_by_index_,
1620
+ displaced_products_: displaced_products_
1604
1621
  };
1605
1622
  for (let i = 0; i < wrapped_blocks_.length; i++) {
1606
1623
  // i = row to show at
@@ -1645,6 +1662,7 @@ function $d92f338ba8f9da9b$export$92aa5d691d588f3f({
1645
1662
  content_blocks_by_index_[final_target_index] = array;
1646
1663
  }
1647
1664
  } else content_blocks_by_index_[final_target_index] = block_content;
1665
+ (rows_by_index_[final_target_index] ||= []).push(i);
1648
1666
  // Update displacement
1649
1667
  for (let j = 0; j < span_rows; j++) {
1650
1668
  const row = displaced_products_[i + j] ||= [];
@@ -1653,9 +1671,44 @@ function $d92f338ba8f9da9b$export$92aa5d691d588f3f({
1653
1671
  }
1654
1672
  return {
1655
1673
  content_blocks_by_index_: content_blocks_by_index_,
1674
+ rows_by_index_: rows_by_index_,
1656
1675
  displaced_products_: displaced_products_
1657
1676
  };
1658
1677
  }
1678
+ function $d92f338ba8f9da9b$export$b94605e5b3ce01c({
1679
+ content_blocks_by_index_: content_blocks_by_index_,
1680
+ rows_by_index_: rows_by_index_,
1681
+ displaced_products_: displaced_products_,
1682
+ n_cols_currently_showing_: n_cols_currently_showing_,
1683
+ product_count: product_count
1684
+ }) {
1685
+ const trailing = [];
1686
+ if (!n_cols_currently_showing_ || product_count <= 0) return trailing;
1687
+ // Find the row the last product lands in: each row has room for the columns that blocks don't take up
1688
+ let last_product_row = 0;
1689
+ for (let seen_products = 0;; last_product_row++) {
1690
+ const row = displaced_products_[last_product_row];
1691
+ const taken_by_blocks = typeof row === "number" ? row : row ? row.filter(item => item).length : 0;
1692
+ seen_products += Math.max(0, n_cols_currently_showing_ - taken_by_blocks);
1693
+ if (seen_products >= product_count) break;
1694
+ // Rows past the ones blocks take up all have room for products, so we're done by then at the latest
1695
+ if (last_product_row >= displaced_products_.length + product_count) break;
1696
+ }
1697
+ for (let i = product_count; i < content_blocks_by_index_.length; i++) {
1698
+ const block = content_blocks_by_index_[i];
1699
+ if (block === undefined) continue;
1700
+ const rows = rows_by_index_[i] || [];
1701
+ if (Array.isArray(block) && rows.length > 1) {
1702
+ // Several blocks share this index (e.g. the end of one row and the start of the next): keep the ones in the last product's row
1703
+ const in_last_row = block.filter((_, j) => rows[j] <= last_product_row);
1704
+ if (in_last_row.length) trailing.push(in_last_row);
1705
+ continue;
1706
+ }
1707
+ if ((rows[0] ?? Infinity) > last_product_row) continue; // a row below the last product's row: keep it hidden
1708
+ trailing.push(block);
1709
+ }
1710
+ return trailing;
1711
+ }
1659
1712
  /**
1660
1713
  * Takes a store that contains an array from where index is number of row and value is the result of executing the content block rendering function.
1661
1714
  * Then renders the content and wraps it into a div with the correct styling.
@@ -1705,6 +1758,7 @@ function $d92f338ba8f9da9b$var$wrap_blocks_in_div(rendered_content_blocks, max_s
1705
1758
  content: () => {
1706
1759
  // Sneaky on-demand rendering, so we don't render more blocks than we can show: we have the rendered value here and don't return a function that renders because if our block would be moved around we'd be guaranteed to re-render
1707
1760
  // However, no need to count products to know if we should render: the <For> loops in Placeholders and render_displays will only call this function if there's actually a product here that wants to display this block
1761
+ // (or, once all products have loaded, ResultsProductCards renders the blocks placed after the last product)
1708
1762
  set_rendering_delayed_(false);
1709
1763
  return content;
1710
1764
  },
@@ -2564,6 +2618,61 @@ function $235a2771a44e28e9$export$fecbb157621501bd({
2564
2618
  return outmost_element;
2565
2619
  }
2566
2620
  $gVhhF$solidjsweb.delegateEvents(["click"]);
2621
+
2622
+ /**
2623
+ * `HTMLElement.focus()` is a **silent no-op** on an element that is not being rendered
2624
+ * (`display: none`, `visibility: hidden`, a collapsed menu). Nothing throws, focus simply
2625
+ * stays where it was, so a caller that does not check believes it restored focus when it
2626
+ * left it on `<body>` — which puts the keyboard user back at the top of the document.
2627
+ *
2628
+ * That is reachable whenever the element which opened a modal stops being rendered while the
2629
+ * modal is open, and the common way for that to happen is the responsive-duplicate header:
2630
+ * a theme renders the same search trigger twice, once for desktop and once for mobile, and
2631
+ * hides one at the current breakpoint. Open the modal from the desktop trigger, cross the
2632
+ * breakpoint (rotate a tablet, resize a window), close the modal, and the remembered trigger
2633
+ * is still `isConnected` but no longer rendered. Verified against a live merchant storefront
2634
+ * on 4.10.19.
2635
+ *
2636
+ * So: verify that focus landed, and when it did not, look for a rendered twin of the same
2637
+ * control — the other half of the responsive pair — and use that instead.
2638
+ */ /**
2639
+ * An element can only take focus if it is actually being rendered.
2640
+ *
2641
+ * Injectable because it is the one part of this module that needs a layout engine: jsdom
2642
+ * reports no client rects for anything, so the specs pass their own predicate to exercise the
2643
+ * matching rules. The real predicate is only exercised in a browser.
2644
+ */
2645
+ const $7f80d1bc95bbbc60$export$e0652711c614c755 = element => !!element.getClientRects().length;
2646
+ /**
2647
+ * `document.activeElement` is the outermost shadow host when focus is inside a shadow tree,
2648
+ * so ask the element's own root rather than the document — matching how the rest of
2649
+ * modal_opener treats shadow-mounted modals.
2650
+ */
2651
+ const $7f80d1bc95bbbc60$var$has_focus = element => element.getRootNode().activeElement === element;
2652
+ /**
2653
+ * The accessible-name-ish identity we match twins on. Deliberately narrow: an explicit label
2654
+ * or the trimmed text. Two controls that share a tag and this string are the same control
2655
+ * rendered twice, which is exactly the responsive-duplicate case.
2656
+ */
2657
+ const $7f80d1bc95bbbc60$var$identity_of = element => (element.getAttribute("aria-label") || element.getAttribute("title") || element.textContent || "").trim().replace(/\s+/g, " ");
2658
+ function $7f80d1bc95bbbc60$export$5800d9d535a06e96(element, rendered = $7f80d1bc95bbbc60$export$e0652711c614c755) {
2659
+ const identity = $7f80d1bc95bbbc60$var$identity_of(element);
2660
+ if (!identity) return; // nothing to match on — an icon-only button with no label
2661
+ const twins = [...element.ownerDocument.querySelectorAll(element.localName)].filter(candidate => candidate !== element && rendered(candidate) && $7f80d1bc95bbbc60$var$identity_of(candidate) === identity);
2662
+ return twins.length === 1 ? twins[0] : undefined;
2663
+ }
2664
+ function $7f80d1bc95bbbc60$export$51461d633c351649(element, rendered = $7f80d1bc95bbbc60$export$e0652711c614c755) {
2665
+ element.focus({
2666
+ preventScroll: true
2667
+ });
2668
+ if ($7f80d1bc95bbbc60$var$has_focus(element)) return true;
2669
+ const twin = $7f80d1bc95bbbc60$export$5800d9d535a06e96(element, rendered);
2670
+ if (!twin) return false;
2671
+ twin.focus({
2672
+ preventScroll: true
2673
+ });
2674
+ return $7f80d1bc95bbbc60$var$has_focus(twin);
2675
+ }
2567
2676
  async function $9abb0a35d4b60ed3$export$79b1565500412d9b(search_modal, modal_fn_opts, parent) {
2568
2677
  const body = parent || document.body || (await (0, $gVhhF$depictaiutilishared.observer).wait_for_element("body"));
2569
2678
  let dispose_and_remove;
@@ -2624,9 +2733,12 @@ async function $9abb0a35d4b60ed3$export$79b1565500412d9b(search_modal, modal_fn_
2624
2733
  });
2625
2734
  focused = requested_focus_target.getRootNode().activeElement === requested_focus_target;
2626
2735
  }
2627
- if (!focused && element_focused_before_open?.isConnected) element_focused_before_open.focus({
2628
- preventScroll: true
2629
- });
2736
+ if (!focused && element_focused_before_open?.isConnected)
2737
+ // restore_focus_to rather than a bare focus(): the remembered trigger can be
2738
+ // connected but no longer rendered (a responsive-duplicate header whose other
2739
+ // half is showing now), and focus() on an unrendered element silently does
2740
+ // nothing, leaving the keyboard user at the top of the page.
2741
+ $7f80d1bc95bbbc60$export$51461d633c351649(element_focused_before_open);
2630
2742
  } finally {
2631
2743
  // Cleared on a task instead of synchronously in case a browser delivers the focus event async
2632
2744
  setTimeout(() => restoring_focus = false, 0);
@@ -9267,16 +9379,20 @@ class $e96dd13889871fc0$export$7e1297a3a19f10d8 {
9267
9379
  * Function that will be called when we want to navigate, or a string saying it's not an SPA. You can change this OTG
9268
9380
  */ /**
9269
9381
  * We store listing_id in history.state and do operate on this both here and in js-ui, this is the key to be used which js-ui provides
9270
- */
9271
- #throwing_on_navigation(...args) {
9382
+ */ /**
9383
+ * Called when a go_to_ didn't change location.href, for example when following a link to the page one is already on. href_change_ipns stays silent then, so whoever waits for a href change to react to a navigation has to listen here too
9384
+ */
9385
+ navigation_to_same_href_listeners_ = new Set();
9386
+ #throwing_on_navigation(options, on_throw_) {
9272
9387
  const {
9273
9388
  on_navigation_: on_navigation_
9274
9389
  } = this;
9275
9390
  if (typeof on_navigation_ !== "function") throw new Error("on_navigation_ is not a function");
9276
9391
  // make sure that we don't accidentally catchify if the SDK consumer has fucked up
9277
9392
  try {
9278
- return on_navigation_(...args);
9393
+ return on_navigation_(options);
9279
9394
  } catch (e) {
9395
+ on_throw_?.();
9280
9396
  queueMicrotask(() => {
9281
9397
  throw e;
9282
9398
  });
@@ -9297,6 +9413,28 @@ class $e96dd13889871fc0$export$7e1297a3a19f10d8 {
9297
9413
  (0, $gVhhF$depictaiutilishared.instant_exec_on_suspect_history_change).add(handler);
9298
9414
  });
9299
9415
  }
9416
+ /**
9417
+ * Calls navigation_to_same_href_listeners_ if the navigation we're about to request ends on the href we're already on
9418
+ * @returns function to stop watching, call it when the navigation failed
9419
+ */
9420
+ #watch_for_navigation_to_same_href(target_url) {
9421
+ const href_before = location.href;
9422
+ const stop_watching = () => {
9423
+ (0, $gVhhF$depictaiutilishared.instant_exec_on_suspect_history_change).delete(on_history_change);
9424
+ clearTimeout(timeout);
9425
+ };
9426
+ const notify_if_href_unchanged = $gVhhF$depictaiutilishared.catchify(() => {
9427
+ stop_watching();
9428
+ if (location.href !== href_before) return;
9429
+ this.navigation_to_same_href_listeners_.forEach(listener => $gVhhF$depictaiutilishared.catchify(listener)());
9430
+ });
9431
+ // The router has acted once it touches history, if the href is still the same then it took us to the page we're on
9432
+ const on_history_change = () => notify_if_href_unchanged();
9433
+ // A router asked to go to the exact href we're on might not touch history at all, so don't wait longer than 500ms for it. For any other target we can't tell a slow navigation from one that goes nowhere, so only stop listening after a while
9434
+ const timeout = target_url.href === href_before ? setTimeout(notify_if_href_unchanged, 500) : setTimeout(stop_watching, 10000);
9435
+ (0, $gVhhF$depictaiutilishared.instant_exec_on_suspect_history_change).add(on_history_change);
9436
+ return stop_watching;
9437
+ }
9300
9438
  navigate_ = {
9301
9439
  /**
9302
9440
  * Call this if you *just* want to replace the state object in history.state
@@ -9336,6 +9474,7 @@ class $e96dd13889871fc0$export$7e1297a3a19f10d8 {
9336
9474
  }
9337
9475
  event_?.preventDefault();
9338
9476
  const promise = this.#get_navigation_happened_promise(true);
9477
+ const stop_watching_for_same_href = this.#watch_for_navigation_to_same_href(new_url_);
9339
9478
  if (force_spa_navigation_ && wants_hard_navigation) {
9340
9479
  // Don't allow hard navigation if we're forcing SPA navigation, which we do for things where PageReplacer can be used for performance
9341
9480
  const new_state = history.state;
@@ -9349,7 +9488,7 @@ class $e96dd13889871fc0$export$7e1297a3a19f10d8 {
9349
9488
  is_replace: is_replace_,
9350
9489
  new_url: new_url_ instanceof URL ? new_url_ : new URL(new_url_, location.href),
9351
9490
  scroll: scroll
9352
- });
9491
+ }, stop_watching_for_same_href);
9353
9492
  await promise;
9354
9493
  })
9355
9494
  };
@@ -11076,11 +11215,17 @@ var $610b584d4f54699e$var$_tmpl$ = /*#__PURE__*/$gVhhF$solidjsweb.template(`<svg
11076
11215
  function $610b584d4f54699e$export$8d8893ffd8aca39a() {
11077
11216
  return $gVhhF$solidjsweb.getNextElement($610b584d4f54699e$var$_tmpl$);
11078
11217
  }
11079
- function $eb3039a27db40508$export$93d9bfd0c9c1a5b8(close_modal, search_query_url_param_name_) {
11218
+ function $eb3039a27db40508$export$93d9bfd0c9c1a5b8(close_modal, search_query_url_param_name_, router_) {
11080
11219
  const [exit_iterable, exitable_iterable] = $gVhhF$depictaiutilishared.make_asyncIterable_exiter((0, $gVhhF$depictaiutilishared.href_change_ipns));
11220
+ const {
11221
+ navigation_to_same_href_listeners_: navigation_to_same_href_listeners_
11222
+ } = router_;
11081
11223
  let set_initial_values = false;
11082
11224
  let old_pathname;
11083
11225
  let old_param_value;
11226
+ // A card linking to the page one is already on doesn't change the href, close the modal anyway so the click does something
11227
+ navigation_to_same_href_listeners_.add(close_modal);
11228
+ $gVhhF$solidjs.onCleanup(() => navigation_to_same_href_listeners_.delete(close_modal));
11084
11229
  $gVhhF$solidjs.onCleanup($gVhhF$depictaiutilishared.catchify(exit_iterable));
11085
11230
  $gVhhF$depictaiutilishared.catchify(async () => {
11086
11231
  for await (const href of exitable_iterable) {
@@ -11236,7 +11381,7 @@ function $44f0b2e312a9b710$var$RawClassicSearchModal({
11236
11381
  return _el$;
11237
11382
  })();
11238
11383
  if (disable_scrolling_) $ca1176b694a6ee2e$export$fe334901e8bebd57();
11239
- $eb3039a27db40508$export$93d9bfd0c9c1a5b8(close_modal_, search_query_url_param_name_);
11384
+ $eb3039a27db40508$export$93d9bfd0c9c1a5b8(close_modal_, search_query_url_param_name_, router_);
11240
11385
  window.addEventListener("keydown", close_on_escape_handler);
11241
11386
  $gVhhF$solidjs.onCleanup($gVhhF$depictaiutilishared.catchify(() => window.removeEventListener("keydown", close_on_escape_handler)));
11242
11387
  if (closing_animation_) register_closing_animation_(closing_animation_);
@@ -12115,7 +12260,7 @@ function $11597ae03e703a47$var$RawSearchModalV2({
12115
12260
  });
12116
12261
  const noResults = $gVhhF$solidjs.createMemo(prev => (get_modal_search_results_()?.loading === false && !suggestions_response_.loading || !!prev) && totalNumberResults() === 0 && previous_searches_matching_query().length === 0);
12117
12262
  if (disable_scrolling_) $ca1176b694a6ee2e$export$fe334901e8bebd57();
12118
- $eb3039a27db40508$export$93d9bfd0c9c1a5b8(close_modal_, search_query_url_param_name_);
12263
+ $eb3039a27db40508$export$93d9bfd0c9c1a5b8(close_modal_, search_query_url_param_name_, router_);
12119
12264
  addEventListener("keydown", close_on_escape_handler);
12120
12265
  $gVhhF$solidjs.onCleanup(() => removeEventListener("keydown", close_on_escape_handler));
12121
12266
  $gVhhF$solidjs.createEffect($gVhhF$solidjs.on(get_search_field_value_, () => {