@depict-ai/ui 4.10.20 → 4.10.21

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/latest/index.d.ts CHANGED
@@ -11,7 +11,7 @@ import { GetListingRequest } from "@depict-ai/types/api/GetListingRequest";
11
11
  import { BaseSearchRequestV3 } from "@depict-ai/types/api/BaseSearchRequestV3";
12
12
  import { FeaturedInDisplay } from "@depict-ai/types/api/FeaturedInResponseV3";
13
13
  import { SearchFilter as _SearchFilter3 } from "@depict-ai/types/api/SearchRequestV2";
14
- export const version = "4.10.20";
14
+ export const version = "4.10.21";
15
15
  /**
16
16
  * Use `javascript_media_query` but return a reactive accessor instead of taking a callback function
17
17
  */
@@ -485,6 +485,10 @@ export class PseudoRouter {
485
485
  * 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
486
486
  */
487
487
  listing_query_state_key: string | undefined;
488
+ /**
489
+ * 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
490
+ */
491
+ navigation_to_same_href_listeners_: Set<VoidFunction>;
488
492
  navigate_: {
489
493
  /**
490
494
  * Call this if you *just* want to replace the state object in history.state
package/latest/index.js CHANGED
@@ -7,7 +7,7 @@ import { createStore as $9HbP8$createStore, reconcile as $9HbP8$reconcile } from
7
7
  /*! Depict.ai search and product listing SDK */
8
8
  import { register_es_version } from "../multi_import_deduplicator.mjs";
9
9
  register_es_version("ui", "latest");
10
- const $ec291e0010116877$export$83d89fbfd8236492 = "4.10.20";
10
+ const $ec291e0010116877$export$83d89fbfd8236492 = "4.10.21";
11
11
  function $dc9262ef6f786812$export$d9f36cf74e24d595(media_query) {
12
12
  const signal = $9HbP8$createSignal(false);
13
13
  $9HbP8$onCleanup($9HbP8$javascript_media_query(media_query, ({
@@ -2462,6 +2462,61 @@ function $1adfe60f3a14a1d7$export$fecbb157621501bd({
2462
2462
  return outmost_element;
2463
2463
  }
2464
2464
  $9HbP8$delegateEvents(["click"]);
2465
+
2466
+ /**
2467
+ * `HTMLElement.focus()` is a **silent no-op** on an element that is not being rendered
2468
+ * (`display: none`, `visibility: hidden`, a collapsed menu). Nothing throws, focus simply
2469
+ * stays where it was, so a caller that does not check believes it restored focus when it
2470
+ * left it on `<body>` — which puts the keyboard user back at the top of the document.
2471
+ *
2472
+ * That is reachable whenever the element which opened a modal stops being rendered while the
2473
+ * modal is open, and the common way for that to happen is the responsive-duplicate header:
2474
+ * a theme renders the same search trigger twice, once for desktop and once for mobile, and
2475
+ * hides one at the current breakpoint. Open the modal from the desktop trigger, cross the
2476
+ * breakpoint (rotate a tablet, resize a window), close the modal, and the remembered trigger
2477
+ * is still `isConnected` but no longer rendered. Verified against a live merchant storefront
2478
+ * on 4.10.19.
2479
+ *
2480
+ * So: verify that focus landed, and when it did not, look for a rendered twin of the same
2481
+ * control — the other half of the responsive pair — and use that instead.
2482
+ */ /**
2483
+ * An element can only take focus if it is actually being rendered.
2484
+ *
2485
+ * Injectable because it is the one part of this module that needs a layout engine: jsdom
2486
+ * reports no client rects for anything, so the specs pass their own predicate to exercise the
2487
+ * matching rules. The real predicate is only exercised in a browser.
2488
+ */
2489
+ const $07001c226aedc5fb$export$e0652711c614c755 = element => !!element.getClientRects().length;
2490
+ /**
2491
+ * `document.activeElement` is the outermost shadow host when focus is inside a shadow tree,
2492
+ * so ask the element's own root rather than the document — matching how the rest of
2493
+ * modal_opener treats shadow-mounted modals.
2494
+ */
2495
+ const $07001c226aedc5fb$var$has_focus = element => element.getRootNode().activeElement === element;
2496
+ /**
2497
+ * The accessible-name-ish identity we match twins on. Deliberately narrow: an explicit label
2498
+ * or the trimmed text. Two controls that share a tag and this string are the same control
2499
+ * rendered twice, which is exactly the responsive-duplicate case.
2500
+ */
2501
+ const $07001c226aedc5fb$var$identity_of = element => (element.getAttribute("aria-label") || element.getAttribute("title") || element.textContent || "").trim().replace(/\s+/g, " ");
2502
+ function $07001c226aedc5fb$export$5800d9d535a06e96(element, rendered = $07001c226aedc5fb$export$e0652711c614c755) {
2503
+ const identity = $07001c226aedc5fb$var$identity_of(element);
2504
+ if (!identity) return; // nothing to match on — an icon-only button with no label
2505
+ const twins = [...element.ownerDocument.querySelectorAll(element.localName)].filter(candidate => candidate !== element && rendered(candidate) && $07001c226aedc5fb$var$identity_of(candidate) === identity);
2506
+ return twins.length === 1 ? twins[0] : undefined;
2507
+ }
2508
+ function $07001c226aedc5fb$export$51461d633c351649(element, rendered = $07001c226aedc5fb$export$e0652711c614c755) {
2509
+ element.focus({
2510
+ preventScroll: true
2511
+ });
2512
+ if ($07001c226aedc5fb$var$has_focus(element)) return true;
2513
+ const twin = $07001c226aedc5fb$export$5800d9d535a06e96(element, rendered);
2514
+ if (!twin) return false;
2515
+ twin.focus({
2516
+ preventScroll: true
2517
+ });
2518
+ return $07001c226aedc5fb$var$has_focus(twin);
2519
+ }
2465
2520
  async function $194bd6b8e4c59830$export$79b1565500412d9b(search_modal, modal_fn_opts, parent) {
2466
2521
  const body = parent || document.body || (await (0, $9HbP8$observer).wait_for_element("body"));
2467
2522
  let dispose_and_remove;
@@ -2522,9 +2577,12 @@ async function $194bd6b8e4c59830$export$79b1565500412d9b(search_modal, modal_fn_
2522
2577
  });
2523
2578
  focused = requested_focus_target.getRootNode().activeElement === requested_focus_target;
2524
2579
  }
2525
- if (!focused && element_focused_before_open?.isConnected) element_focused_before_open.focus({
2526
- preventScroll: true
2527
- });
2580
+ if (!focused && element_focused_before_open?.isConnected)
2581
+ // restore_focus_to rather than a bare focus(): the remembered trigger can be
2582
+ // connected but no longer rendered (a responsive-duplicate header whose other
2583
+ // half is showing now), and focus() on an unrendered element silently does
2584
+ // nothing, leaving the keyboard user at the top of the page.
2585
+ $07001c226aedc5fb$export$51461d633c351649(element_focused_before_open);
2528
2586
  } finally {
2529
2587
  // Cleared on a task instead of synchronously in case a browser delivers the focus event async
2530
2588
  setTimeout(() => restoring_focus = false, 0);
@@ -9165,16 +9223,20 @@ class $4fd2ba03703a7d92$export$7e1297a3a19f10d8 {
9165
9223
  * Function that will be called when we want to navigate, or a string saying it's not an SPA. You can change this OTG
9166
9224
  */ /**
9167
9225
  * 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
9168
- */
9169
- #throwing_on_navigation(...args) {
9226
+ */ /**
9227
+ * 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
9228
+ */
9229
+ navigation_to_same_href_listeners_ = new Set();
9230
+ #throwing_on_navigation(options, on_throw_) {
9170
9231
  const {
9171
9232
  on_navigation_: on_navigation_
9172
9233
  } = this;
9173
9234
  if (typeof on_navigation_ !== "function") throw new Error("on_navigation_ is not a function");
9174
9235
  // make sure that we don't accidentally catchify if the SDK consumer has fucked up
9175
9236
  try {
9176
- return on_navigation_(...args);
9237
+ return on_navigation_(options);
9177
9238
  } catch (e) {
9239
+ on_throw_?.();
9178
9240
  queueMicrotask(() => {
9179
9241
  throw e;
9180
9242
  });
@@ -9195,6 +9257,28 @@ class $4fd2ba03703a7d92$export$7e1297a3a19f10d8 {
9195
9257
  (0, $9HbP8$instant_exec_on_suspect_history_change).add(handler);
9196
9258
  });
9197
9259
  }
9260
+ /**
9261
+ * Calls navigation_to_same_href_listeners_ if the navigation we're about to request ends on the href we're already on
9262
+ * @returns function to stop watching, call it when the navigation failed
9263
+ */
9264
+ #watch_for_navigation_to_same_href(target_url) {
9265
+ const href_before = location.href;
9266
+ const stop_watching = () => {
9267
+ (0, $9HbP8$instant_exec_on_suspect_history_change).delete(on_history_change);
9268
+ clearTimeout(timeout);
9269
+ };
9270
+ const notify_if_href_unchanged = $9HbP8$catchify1(() => {
9271
+ stop_watching();
9272
+ if (location.href !== href_before) return;
9273
+ this.navigation_to_same_href_listeners_.forEach(listener => $9HbP8$catchify1(listener)());
9274
+ });
9275
+ // 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
9276
+ const on_history_change = () => notify_if_href_unchanged();
9277
+ // 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
9278
+ const timeout = target_url.href === href_before ? setTimeout(notify_if_href_unchanged, 500) : setTimeout(stop_watching, 10000);
9279
+ (0, $9HbP8$instant_exec_on_suspect_history_change).add(on_history_change);
9280
+ return stop_watching;
9281
+ }
9198
9282
  navigate_ = {
9199
9283
  /**
9200
9284
  * Call this if you *just* want to replace the state object in history.state
@@ -9234,6 +9318,7 @@ class $4fd2ba03703a7d92$export$7e1297a3a19f10d8 {
9234
9318
  }
9235
9319
  event_?.preventDefault();
9236
9320
  const promise = this.#get_navigation_happened_promise(true);
9321
+ const stop_watching_for_same_href = this.#watch_for_navigation_to_same_href(new_url_);
9237
9322
  if (force_spa_navigation_ && wants_hard_navigation) {
9238
9323
  // Don't allow hard navigation if we're forcing SPA navigation, which we do for things where PageReplacer can be used for performance
9239
9324
  const new_state = history.state;
@@ -9247,7 +9332,7 @@ class $4fd2ba03703a7d92$export$7e1297a3a19f10d8 {
9247
9332
  is_replace: is_replace_,
9248
9333
  new_url: new_url_ instanceof URL ? new_url_ : new URL(new_url_, location.href),
9249
9334
  scroll: scroll
9250
- });
9335
+ }, stop_watching_for_same_href);
9251
9336
  await promise;
9252
9337
  })
9253
9338
  };
@@ -10974,11 +11059,17 @@ var $ffb84ffbf4014982$var$_tmpl$ = /*#__PURE__*/$9HbP8$template(`<svg xmlns=http
10974
11059
  function $ffb84ffbf4014982$export$8d8893ffd8aca39a() {
10975
11060
  return $9HbP8$getNextElement($ffb84ffbf4014982$var$_tmpl$);
10976
11061
  }
10977
- function $512b994d501f412b$export$93d9bfd0c9c1a5b8(close_modal, search_query_url_param_name_) {
11062
+ function $512b994d501f412b$export$93d9bfd0c9c1a5b8(close_modal, search_query_url_param_name_, router_) {
10978
11063
  const [exit_iterable, exitable_iterable] = $9HbP8$make_asyncIterable_exiter((0, $9HbP8$href_change_ipns));
11064
+ const {
11065
+ navigation_to_same_href_listeners_: navigation_to_same_href_listeners_
11066
+ } = router_;
10979
11067
  let set_initial_values = false;
10980
11068
  let old_pathname;
10981
11069
  let old_param_value;
11070
+ // A card linking to the page one is already on doesn't change the href, close the modal anyway so the click does something
11071
+ navigation_to_same_href_listeners_.add(close_modal);
11072
+ $9HbP8$onCleanup(() => navigation_to_same_href_listeners_.delete(close_modal));
10982
11073
  $9HbP8$onCleanup($9HbP8$catchify1(exit_iterable));
10983
11074
  $9HbP8$catchify1(async () => {
10984
11075
  for await (const href of exitable_iterable) {
@@ -11134,7 +11225,7 @@ function $42912431795f188e$var$RawClassicSearchModal({
11134
11225
  return _el$;
11135
11226
  })();
11136
11227
  if (disable_scrolling_) $aea1edf92433e81e$export$fe334901e8bebd57();
11137
- $512b994d501f412b$export$93d9bfd0c9c1a5b8(close_modal_, search_query_url_param_name_);
11228
+ $512b994d501f412b$export$93d9bfd0c9c1a5b8(close_modal_, search_query_url_param_name_, router_);
11138
11229
  window.addEventListener("keydown", close_on_escape_handler);
11139
11230
  $9HbP8$onCleanup($9HbP8$catchify1(() => window.removeEventListener("keydown", close_on_escape_handler)));
11140
11231
  if (closing_animation_) register_closing_animation_(closing_animation_);
@@ -12013,7 +12104,7 @@ function $500a16dd18ee5a71$var$RawSearchModalV2({
12013
12104
  });
12014
12105
  const noResults = $9HbP8$createMemo(prev => (get_modal_search_results_()?.loading === false && !suggestions_response_.loading || !!prev) && totalNumberResults() === 0 && previous_searches_matching_query().length === 0);
12015
12106
  if (disable_scrolling_) $aea1edf92433e81e$export$fe334901e8bebd57();
12016
- $512b994d501f412b$export$93d9bfd0c9c1a5b8(close_modal_, search_query_url_param_name_);
12107
+ $512b994d501f412b$export$93d9bfd0c9c1a5b8(close_modal_, search_query_url_param_name_, router_);
12017
12108
  addEventListener("keydown", close_on_escape_handler);
12018
12109
  $9HbP8$onCleanup(() => removeEventListener("keydown", close_on_escape_handler));
12019
12110
  $9HbP8$createEffect($9HbP8$on(get_search_field_value_, () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@depict-ai/ui",
3
- "version": "4.10.20",
3
+ "version": "4.10.21",
4
4
  "description": "Base package containing the actual UI of Depict UI along with translations, with a not-simplified API. We recommend using @depict-ai/js-ui or @depict-ai/react-ui instead.",
5
5
  "packageManager": "yarn@3.2.2",
6
6
  "sass": "styles.scss",
@@ -236,9 +236,9 @@
236
236
  "@babel/plugin-transform-typescript": "^7.22.9",
237
237
  "@babel/runtime": "^7.22.6",
238
238
  "@babel/runtime-corejs3": "^7.22.6",
239
- "@depict-ai/plp-styling": "^4.6.12",
240
- "@depict-ai/types": "^5.6.10",
241
- "@depict-ai/utilishared": "^2.6.10",
239
+ "@depict-ai/plp-styling": "^4.6.13",
240
+ "@depict-ai/types": "^5.6.11",
241
+ "@depict-ai/utilishared": "^2.6.11",
242
242
  "solid-js": "^1.9.10"
243
243
  },
244
244
  "type": "module",
@@ -115,7 +115,7 @@ $parcel$export(module.exports, "BreadCrumbs", () => $335c02c1241a9ca8$export$5b7
115
115
  $parcel$export(module.exports, "QuickLinks", () => $335c02c1241a9ca8$export$de035477d6fef996);
116
116
  $parcel$export(module.exports, "CategoryPage", () => $c10e33c4681f8c9b$export$e2bf8ce7779e827e);
117
117
  /*! Depict.ai search and product listing SDK */
118
- const $aa1938dec50d6c12$export$83d89fbfd8236492 = "4.10.20";
118
+ const $aa1938dec50d6c12$export$83d89fbfd8236492 = "4.10.21";
119
119
  function $3e17e871e9d0911d$export$d9f36cf74e24d595(media_query) {
120
120
  const signal = $3hooq$solidjs.createSignal(false);
121
121
  $3hooq$solidjs.onCleanup($3hooq$depictaiutilishared.javascript_media_query(media_query, _ref => {
@@ -2218,6 +2218,63 @@ function $c5842bdb10e57229$export$fecbb157621501bd(_ref) {
2218
2218
  }));
2219
2219
  return outmost_element;
2220
2220
  }
2221
+
2222
+ /**
2223
+ * `HTMLElement.focus()` is a **silent no-op** on an element that is not being rendered
2224
+ * (`display: none`, `visibility: hidden`, a collapsed menu). Nothing throws, focus simply
2225
+ * stays where it was, so a caller that does not check believes it restored focus when it
2226
+ * left it on `<body>` — which puts the keyboard user back at the top of the document.
2227
+ *
2228
+ * That is reachable whenever the element which opened a modal stops being rendered while the
2229
+ * modal is open, and the common way for that to happen is the responsive-duplicate header:
2230
+ * a theme renders the same search trigger twice, once for desktop and once for mobile, and
2231
+ * hides one at the current breakpoint. Open the modal from the desktop trigger, cross the
2232
+ * breakpoint (rotate a tablet, resize a window), close the modal, and the remembered trigger
2233
+ * is still `isConnected` but no longer rendered. Verified against a live merchant storefront
2234
+ * on 4.10.19.
2235
+ *
2236
+ * So: verify that focus landed, and when it did not, look for a rendered twin of the same
2237
+ * control — the other half of the responsive pair — and use that instead.
2238
+ */ /**
2239
+ * An element can only take focus if it is actually being rendered.
2240
+ *
2241
+ * Injectable because it is the one part of this module that needs a layout engine: jsdom
2242
+ * reports no client rects for anything, so the specs pass their own predicate to exercise the
2243
+ * matching rules. The real predicate is only exercised in a browser.
2244
+ */
2245
+ const $1573c1a34371380a$export$e0652711c614c755 = element => !!element.getClientRects().length;
2246
+ /**
2247
+ * `document.activeElement` is the outermost shadow host when focus is inside a shadow tree,
2248
+ * so ask the element's own root rather than the document — matching how the rest of
2249
+ * modal_opener treats shadow-mounted modals.
2250
+ */
2251
+ const $1573c1a34371380a$var$has_focus = element => element.getRootNode().activeElement === element;
2252
+ /**
2253
+ * The accessible-name-ish identity we match twins on. Deliberately narrow: an explicit label
2254
+ * or the trimmed text. Two controls that share a tag and this string are the same control
2255
+ * rendered twice, which is exactly the responsive-duplicate case.
2256
+ */
2257
+ const $1573c1a34371380a$var$identity_of = element => (element.getAttribute("aria-label") || element.getAttribute("title") || element.textContent || "").trim().replace(/\s+/g, " ");
2258
+ function $1573c1a34371380a$export$5800d9d535a06e96(element) {
2259
+ let rendered = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : $1573c1a34371380a$export$e0652711c614c755;
2260
+ const identity = $1573c1a34371380a$var$identity_of(element);
2261
+ if (!identity) return; // nothing to match on — an icon-only button with no label
2262
+ const twins = [...element.ownerDocument.querySelectorAll(element.localName)].filter(candidate => candidate !== element && rendered(candidate) && $1573c1a34371380a$var$identity_of(candidate) === identity);
2263
+ return twins.length === 1 ? twins[0] : undefined;
2264
+ }
2265
+ function $1573c1a34371380a$export$51461d633c351649(element) {
2266
+ let rendered = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : $1573c1a34371380a$export$e0652711c614c755;
2267
+ element.focus({
2268
+ preventScroll: true
2269
+ });
2270
+ if ($1573c1a34371380a$var$has_focus(element)) return true;
2271
+ const twin = $1573c1a34371380a$export$5800d9d535a06e96(element, rendered);
2272
+ if (!twin) return false;
2273
+ twin.focus({
2274
+ preventScroll: true
2275
+ });
2276
+ return $1573c1a34371380a$var$has_focus(twin);
2277
+ }
2221
2278
  async function $4cf7d9829d60d844$export$79b1565500412d9b(search_modal, modal_fn_opts, parent) {
2222
2279
  const body = parent || document.body || (await (0, $3hooq$depictaiutilishared.observer).wait_for_element("body"));
2223
2280
  let dispose_and_remove;
@@ -2283,9 +2340,12 @@ async function $4cf7d9829d60d844$export$79b1565500412d9b(search_modal, modal_fn_
2283
2340
  });
2284
2341
  focused = requested_focus_target.getRootNode().activeElement === requested_focus_target;
2285
2342
  }
2286
- if (!focused && element_focused_before_open !== null && element_focused_before_open !== void 0 && element_focused_before_open.isConnected) element_focused_before_open.focus({
2287
- preventScroll: true
2288
- });
2343
+ if (!focused && element_focused_before_open !== null && element_focused_before_open !== void 0 && element_focused_before_open.isConnected)
2344
+ // restore_focus_to rather than a bare focus(): the remembered trigger can be
2345
+ // connected but no longer rendered (a responsive-duplicate header whose other
2346
+ // half is showing now), and focus() on an unrendered element silently does
2347
+ // nothing, leaving the keyboard user at the top of the page.
2348
+ $1573c1a34371380a$export$51461d633c351649(element_focused_before_open);
2289
2349
  } finally {
2290
2350
  // Cleared on a task instead of synchronously in case a browser delivers the focus event async
2291
2351
  setTimeout(() => restoring_focus = false, 0);
@@ -8044,20 +8104,28 @@ function $73014ff36186a4dd$var$_classPrivateMethodGet(receiver, privateSet, fn)
8044
8104
  }
8045
8105
  var $73014ff36186a4dd$var$_throwing_on_navigation = /*#__PURE__*/new WeakSet();
8046
8106
  var $73014ff36186a4dd$var$_get_navigation_happened_promise = /*#__PURE__*/new WeakSet();
8107
+ var $73014ff36186a4dd$var$_watch_for_navigation_to_same_href = /*#__PURE__*/new WeakSet();
8047
8108
  class $73014ff36186a4dd$export$7e1297a3a19f10d8 {
8048
8109
  /**
8049
8110
  * Function that will be called when we want to navigate, or a string saying it's not an SPA. You can change this OTG
8050
8111
  */ /**
8051
8112
  * 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
8052
8113
  */ /**
8053
- * Vue has async router that needs to be awaited (it navigates after a microtask)
8054
- * This standardises waiting for all kinds of routers
8055
- */
8114
+ * 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
8115
+ */ /**
8116
+ * Vue has async router that needs to be awaited (it navigates after a microtask)
8117
+ * This standardises waiting for all kinds of routers
8118
+ */ /**
8119
+ * Calls navigation_to_same_href_listeners_ if the navigation we're about to request ends on the href we're already on
8120
+ * @returns function to stop watching, call it when the navigation failed
8121
+ */
8056
8122
  constructor(on_navigation, listing_query_state_key) {
8123
+ $73014ff36186a4dd$var$_classPrivateMethodInitSpec(this, $73014ff36186a4dd$var$_watch_for_navigation_to_same_href);
8057
8124
  $73014ff36186a4dd$var$_classPrivateMethodInitSpec(this, $73014ff36186a4dd$var$_get_navigation_happened_promise);
8058
8125
  $73014ff36186a4dd$var$_classPrivateMethodInitSpec(this, $73014ff36186a4dd$var$_throwing_on_navigation);
8059
8126
  $parcel$interopDefault($3hooq$babelruntimehelpersdefineProperty)(this, "on_navigation_", void 0);
8060
8127
  $parcel$interopDefault($3hooq$babelruntimehelpersdefineProperty)(this, "listing_query_state_key", void 0);
8128
+ $parcel$interopDefault($3hooq$babelruntimehelpersdefineProperty)(this, "navigation_to_same_href_listeners_", new Set());
8061
8129
  $parcel$interopDefault($3hooq$babelruntimehelpersdefineProperty)(this, "navigate_", {
8062
8130
  /**
8063
8131
  * Call this if you *just* want to replace the state object in history.state
@@ -8099,6 +8167,7 @@ class $73014ff36186a4dd$export$7e1297a3a19f10d8 {
8099
8167
  }
8100
8168
  event_ === null || event_ === void 0 || event_.preventDefault();
8101
8169
  const promise = $73014ff36186a4dd$var$_classPrivateMethodGet(this, $73014ff36186a4dd$var$_get_navigation_happened_promise, $73014ff36186a4dd$var$_get_navigation_happened_promise2).call(this, true);
8170
+ const stop_watching_for_same_href = $73014ff36186a4dd$var$_classPrivateMethodGet(this, $73014ff36186a4dd$var$_watch_for_navigation_to_same_href, $73014ff36186a4dd$var$_watch_for_navigation_to_same_href2).call(this, new_url_);
8102
8171
  if (force_spa_navigation_ && wants_hard_navigation) {
8103
8172
  // Don't allow hard navigation if we're forcing SPA navigation, which we do for things where PageReplacer can be used for performance
8104
8173
  const new_state = history.state;
@@ -8112,7 +8181,7 @@ class $73014ff36186a4dd$export$7e1297a3a19f10d8 {
8112
8181
  is_replace: is_replace_,
8113
8182
  new_url: new_url_ instanceof URL ? new_url_ : new URL(new_url_, location.href),
8114
8183
  scroll: scroll
8115
- });
8184
+ }, stop_watching_for_same_href);
8116
8185
  await promise;
8117
8186
  })
8118
8187
  });
@@ -8120,15 +8189,16 @@ class $73014ff36186a4dd$export$7e1297a3a19f10d8 {
8120
8189
  this.on_navigation_ = on_navigation;
8121
8190
  }
8122
8191
  }
8123
- function $73014ff36186a4dd$var$_throwing_on_navigation2() {
8192
+ function $73014ff36186a4dd$var$_throwing_on_navigation2(options, on_throw_) {
8124
8193
  const {
8125
8194
  on_navigation_: on_navigation_
8126
8195
  } = this;
8127
8196
  if (typeof on_navigation_ !== "function") throw new Error("on_navigation_ is not a function");
8128
8197
  // make sure that we don't accidentally catchify if the SDK consumer has fucked up
8129
8198
  try {
8130
- return on_navigation_(...arguments);
8199
+ return on_navigation_(options);
8131
8200
  } catch (e) {
8201
+ on_throw_ === null || on_throw_ === void 0 || on_throw_();
8132
8202
  queueMicrotask(() => {
8133
8203
  throw e;
8134
8204
  });
@@ -8145,6 +8215,24 @@ function $73014ff36186a4dd$var$_get_navigation_happened_promise2(is_replace) {
8145
8215
  (0, $3hooq$depictaiutilishared.instant_exec_on_suspect_history_change).add(handler);
8146
8216
  });
8147
8217
  }
8218
+ function $73014ff36186a4dd$var$_watch_for_navigation_to_same_href2(target_url) {
8219
+ const href_before = location.href;
8220
+ const stop_watching = () => {
8221
+ (0, $3hooq$depictaiutilishared.instant_exec_on_suspect_history_change).delete(on_history_change);
8222
+ clearTimeout(timeout);
8223
+ };
8224
+ const notify_if_href_unchanged = $3hooq$depictaiutilishared.catchify(() => {
8225
+ stop_watching();
8226
+ if (location.href !== href_before) return;
8227
+ this.navigation_to_same_href_listeners_.forEach(listener => $3hooq$depictaiutilishared.catchify(listener)());
8228
+ });
8229
+ // 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
8230
+ const on_history_change = () => notify_if_href_unchanged();
8231
+ // 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
8232
+ const timeout = target_url.href === href_before ? setTimeout(notify_if_href_unchanged, 500) : setTimeout(stop_watching, 10000);
8233
+ (0, $3hooq$depictaiutilishared.instant_exec_on_suspect_history_change).add(on_history_change);
8234
+ return stop_watching;
8235
+ }
8148
8236
  var $2c920813c92d6987$var$_tmpl$ = ["<button", ' class="show-more major" type="button">', "</button>"],
8149
8237
  $2c920813c92d6987$var$_tmpl$2 = ["<h2", ' class="recs-title">', "</h2>"],
8150
8238
  $2c920813c92d6987$var$_tmpl$3 = ["<div", ' class="recommendation-grid"><!--$-->', "<!--/--><!--$-->", "<!--/--></div>"];
@@ -9517,11 +9605,17 @@ var $820f2920cc576f7e$var$_tmpl$ = ["<svg", ' xmlns="http://www.w3.org/2000/svg"
9517
9605
  function $820f2920cc576f7e$export$8d8893ffd8aca39a() {
9518
9606
  return $3hooq$solidjsweb.ssr($820f2920cc576f7e$var$_tmpl$, $3hooq$solidjsweb.ssrHydrationKey());
9519
9607
  }
9520
- function $4b71b4fabdee5753$export$93d9bfd0c9c1a5b8(close_modal, search_query_url_param_name_) {
9608
+ function $4b71b4fabdee5753$export$93d9bfd0c9c1a5b8(close_modal, search_query_url_param_name_, router_) {
9521
9609
  const [exit_iterable, exitable_iterable] = $3hooq$depictaiutilishared.make_asyncIterable_exiter((0, $3hooq$depictaiutilishared.href_change_ipns));
9610
+ const {
9611
+ navigation_to_same_href_listeners_: navigation_to_same_href_listeners_
9612
+ } = router_;
9522
9613
  let set_initial_values = false;
9523
9614
  let old_pathname;
9524
9615
  let old_param_value;
9616
+ // A card linking to the page one is already on doesn't change the href, close the modal anyway so the click does something
9617
+ navigation_to_same_href_listeners_.add(close_modal);
9618
+ $3hooq$solidjs.onCleanup(() => navigation_to_same_href_listeners_.delete(close_modal));
9525
9619
  $3hooq$solidjs.onCleanup($3hooq$depictaiutilishared.catchify(exit_iterable));
9526
9620
  $3hooq$depictaiutilishared.catchify(async () => {
9527
9621
  for await (const href of exitable_iterable) {
@@ -9671,7 +9765,7 @@ function $9035b24c2dbd3f36$var$RawClassicSearchModal(_ref) {
9671
9765
  }
9672
9766
  })));
9673
9767
  if (disable_scrolling_) $8c2e68c6d458169b$export$fe334901e8bebd57();
9674
- $4b71b4fabdee5753$export$93d9bfd0c9c1a5b8(close_modal_, search_query_url_param_name_);
9768
+ $4b71b4fabdee5753$export$93d9bfd0c9c1a5b8(close_modal_, search_query_url_param_name_, router_);
9675
9769
  window.addEventListener("keydown", close_on_escape_handler);
9676
9770
  $3hooq$solidjs.onCleanup($3hooq$depictaiutilishared.catchify(() => window.removeEventListener("keydown", close_on_escape_handler)));
9677
9771
  if (closing_animation_) register_closing_animation_(closing_animation_);
@@ -10404,7 +10498,7 @@ function $43600b30502feeda$var$RawSearchModalV2(_ref) {
10404
10498
  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;
10405
10499
  });
10406
10500
  if (disable_scrolling_) $8c2e68c6d458169b$export$fe334901e8bebd57();
10407
- $4b71b4fabdee5753$export$93d9bfd0c9c1a5b8(close_modal_, search_query_url_param_name_);
10501
+ $4b71b4fabdee5753$export$93d9bfd0c9c1a5b8(close_modal_, search_query_url_param_name_, router_);
10408
10502
  addEventListener("keydown", close_on_escape_handler);
10409
10503
  $3hooq$solidjs.onCleanup(() => removeEventListener("keydown", close_on_escape_handler));
10410
10504
  $3hooq$solidjs.createEffect($3hooq$solidjs.on(get_search_field_value_, () => {
@@ -11,7 +11,7 @@ import { GetListingRequest } from "@depict-ai/types/api/GetListingRequest";
11
11
  import { BaseSearchRequestV3 } from "@depict-ai/types/api/BaseSearchRequestV3";
12
12
  import { FeaturedInDisplay } from "@depict-ai/types/api/FeaturedInResponseV3";
13
13
  import { SearchFilter as _SearchFilter3 } from "@depict-ai/types/api/SearchRequestV2";
14
- export const version = "4.10.20";
14
+ export const version = "4.10.21";
15
15
  /**
16
16
  * Use `javascript_media_query` but return a reactive accessor instead of taking a callback function
17
17
  */
@@ -485,6 +485,10 @@ export class PseudoRouter {
485
485
  * 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
486
486
  */
487
487
  listing_query_state_key: string | undefined;
488
+ /**
489
+ * 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
490
+ */
491
+ navigation_to_same_href_listeners_: Set<VoidFunction>;
488
492
  navigate_: {
489
493
  /**
490
494
  * Call this if you *just* want to replace the state object in history.state