@depict-ai/ui 4.10.19 → 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.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.19";
112
+ const $b757f75811f3261f$export$83d89fbfd8236492 = "4.10.21";
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, ({
@@ -2564,6 +2564,61 @@ function $235a2771a44e28e9$export$fecbb157621501bd({
2564
2564
  return outmost_element;
2565
2565
  }
2566
2566
  $gVhhF$solidjsweb.delegateEvents(["click"]);
2567
+
2568
+ /**
2569
+ * `HTMLElement.focus()` is a **silent no-op** on an element that is not being rendered
2570
+ * (`display: none`, `visibility: hidden`, a collapsed menu). Nothing throws, focus simply
2571
+ * stays where it was, so a caller that does not check believes it restored focus when it
2572
+ * left it on `<body>` — which puts the keyboard user back at the top of the document.
2573
+ *
2574
+ * That is reachable whenever the element which opened a modal stops being rendered while the
2575
+ * modal is open, and the common way for that to happen is the responsive-duplicate header:
2576
+ * a theme renders the same search trigger twice, once for desktop and once for mobile, and
2577
+ * hides one at the current breakpoint. Open the modal from the desktop trigger, cross the
2578
+ * breakpoint (rotate a tablet, resize a window), close the modal, and the remembered trigger
2579
+ * is still `isConnected` but no longer rendered. Verified against a live merchant storefront
2580
+ * on 4.10.19.
2581
+ *
2582
+ * So: verify that focus landed, and when it did not, look for a rendered twin of the same
2583
+ * control — the other half of the responsive pair — and use that instead.
2584
+ */ /**
2585
+ * An element can only take focus if it is actually being rendered.
2586
+ *
2587
+ * Injectable because it is the one part of this module that needs a layout engine: jsdom
2588
+ * reports no client rects for anything, so the specs pass their own predicate to exercise the
2589
+ * matching rules. The real predicate is only exercised in a browser.
2590
+ */
2591
+ const $7f80d1bc95bbbc60$export$e0652711c614c755 = element => !!element.getClientRects().length;
2592
+ /**
2593
+ * `document.activeElement` is the outermost shadow host when focus is inside a shadow tree,
2594
+ * so ask the element's own root rather than the document — matching how the rest of
2595
+ * modal_opener treats shadow-mounted modals.
2596
+ */
2597
+ const $7f80d1bc95bbbc60$var$has_focus = element => element.getRootNode().activeElement === element;
2598
+ /**
2599
+ * The accessible-name-ish identity we match twins on. Deliberately narrow: an explicit label
2600
+ * or the trimmed text. Two controls that share a tag and this string are the same control
2601
+ * rendered twice, which is exactly the responsive-duplicate case.
2602
+ */
2603
+ const $7f80d1bc95bbbc60$var$identity_of = element => (element.getAttribute("aria-label") || element.getAttribute("title") || element.textContent || "").trim().replace(/\s+/g, " ");
2604
+ function $7f80d1bc95bbbc60$export$5800d9d535a06e96(element, rendered = $7f80d1bc95bbbc60$export$e0652711c614c755) {
2605
+ const identity = $7f80d1bc95bbbc60$var$identity_of(element);
2606
+ if (!identity) return; // nothing to match on — an icon-only button with no label
2607
+ const twins = [...element.ownerDocument.querySelectorAll(element.localName)].filter(candidate => candidate !== element && rendered(candidate) && $7f80d1bc95bbbc60$var$identity_of(candidate) === identity);
2608
+ return twins.length === 1 ? twins[0] : undefined;
2609
+ }
2610
+ function $7f80d1bc95bbbc60$export$51461d633c351649(element, rendered = $7f80d1bc95bbbc60$export$e0652711c614c755) {
2611
+ element.focus({
2612
+ preventScroll: true
2613
+ });
2614
+ if ($7f80d1bc95bbbc60$var$has_focus(element)) return true;
2615
+ const twin = $7f80d1bc95bbbc60$export$5800d9d535a06e96(element, rendered);
2616
+ if (!twin) return false;
2617
+ twin.focus({
2618
+ preventScroll: true
2619
+ });
2620
+ return $7f80d1bc95bbbc60$var$has_focus(twin);
2621
+ }
2567
2622
  async function $9abb0a35d4b60ed3$export$79b1565500412d9b(search_modal, modal_fn_opts, parent) {
2568
2623
  const body = parent || document.body || (await (0, $gVhhF$depictaiutilishared.observer).wait_for_element("body"));
2569
2624
  let dispose_and_remove;
@@ -2593,6 +2648,7 @@ async function $9abb0a35d4b60ed3$export$79b1565500412d9b(search_modal, modal_fn_
2593
2648
  const modal_els = $gVhhF$solidjs.createRoot(dispose => {
2594
2649
  let closing_animation;
2595
2650
  let animation_started;
2651
+ let focus_target_after_close;
2596
2652
  dispose_and_remove = () => {
2597
2653
  const actually_close = () => {
2598
2654
  dispose();
@@ -2611,13 +2667,24 @@ async function $9abb0a35d4b60ed3$export$79b1565500412d9b(search_modal, modal_fn_
2611
2667
  activeElement?.blur?.();
2612
2668
  el.remove();
2613
2669
  });
2614
- if (should_restore_focus && element_focused_before_open?.isConnected) {
2670
+ const requested_focus_target = focus_target_after_close?.();
2671
+ if (should_restore_focus && (requested_focus_target?.isConnected || element_focused_before_open?.isConnected)) {
2615
2672
  restoring_focus = true;
2616
2673
  try {
2617
2674
  // preventScroll so this can't re-introduce the safari page-jump the blur() above works around
2618
- element_focused_before_open.focus({
2619
- preventScroll: true
2620
- });
2675
+ let focused = false;
2676
+ if (requested_focus_target?.isConnected) {
2677
+ requested_focus_target.focus({
2678
+ preventScroll: true
2679
+ });
2680
+ focused = requested_focus_target.getRootNode().activeElement === requested_focus_target;
2681
+ }
2682
+ if (!focused && element_focused_before_open?.isConnected)
2683
+ // restore_focus_to rather than a bare focus(): the remembered trigger can be
2684
+ // connected but no longer rendered (a responsive-duplicate header whose other
2685
+ // half is showing now), and focus() on an unrendered element silently does
2686
+ // nothing, leaving the keyboard user at the top of the page.
2687
+ $7f80d1bc95bbbc60$export$51461d633c351649(element_focused_before_open);
2621
2688
  } finally {
2622
2689
  // Cleared on a task instead of synchronously in case a browser delivers the focus event async
2623
2690
  setTimeout(() => restoring_focus = false, 0);
@@ -2636,7 +2703,8 @@ async function $9abb0a35d4b60ed3$export$79b1565500412d9b(search_modal, modal_fn_
2636
2703
  ...extra_options_for_modal,
2637
2704
  close_modal_: close_modal_,
2638
2705
  open_modal_: open_modal_,
2639
- register_closing_animation_: animation => closing_animation = animation
2706
+ register_closing_animation_: animation => closing_animation = animation,
2707
+ set_focus_target_after_close_: get_target => focus_target_after_close = get_target
2640
2708
  });
2641
2709
  });
2642
2710
  body.append(...modal_els);
@@ -9257,16 +9325,20 @@ class $e96dd13889871fc0$export$7e1297a3a19f10d8 {
9257
9325
  * Function that will be called when we want to navigate, or a string saying it's not an SPA. You can change this OTG
9258
9326
  */ /**
9259
9327
  * 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
9260
- */
9261
- #throwing_on_navigation(...args) {
9328
+ */ /**
9329
+ * 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
9330
+ */
9331
+ navigation_to_same_href_listeners_ = new Set();
9332
+ #throwing_on_navigation(options, on_throw_) {
9262
9333
  const {
9263
9334
  on_navigation_: on_navigation_
9264
9335
  } = this;
9265
9336
  if (typeof on_navigation_ !== "function") throw new Error("on_navigation_ is not a function");
9266
9337
  // make sure that we don't accidentally catchify if the SDK consumer has fucked up
9267
9338
  try {
9268
- return on_navigation_(...args);
9339
+ return on_navigation_(options);
9269
9340
  } catch (e) {
9341
+ on_throw_?.();
9270
9342
  queueMicrotask(() => {
9271
9343
  throw e;
9272
9344
  });
@@ -9287,6 +9359,28 @@ class $e96dd13889871fc0$export$7e1297a3a19f10d8 {
9287
9359
  (0, $gVhhF$depictaiutilishared.instant_exec_on_suspect_history_change).add(handler);
9288
9360
  });
9289
9361
  }
9362
+ /**
9363
+ * Calls navigation_to_same_href_listeners_ if the navigation we're about to request ends on the href we're already on
9364
+ * @returns function to stop watching, call it when the navigation failed
9365
+ */
9366
+ #watch_for_navigation_to_same_href(target_url) {
9367
+ const href_before = location.href;
9368
+ const stop_watching = () => {
9369
+ (0, $gVhhF$depictaiutilishared.instant_exec_on_suspect_history_change).delete(on_history_change);
9370
+ clearTimeout(timeout);
9371
+ };
9372
+ const notify_if_href_unchanged = $gVhhF$depictaiutilishared.catchify(() => {
9373
+ stop_watching();
9374
+ if (location.href !== href_before) return;
9375
+ this.navigation_to_same_href_listeners_.forEach(listener => $gVhhF$depictaiutilishared.catchify(listener)());
9376
+ });
9377
+ // 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
9378
+ const on_history_change = () => notify_if_href_unchanged();
9379
+ // 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
9380
+ const timeout = target_url.href === href_before ? setTimeout(notify_if_href_unchanged, 500) : setTimeout(stop_watching, 10000);
9381
+ (0, $gVhhF$depictaiutilishared.instant_exec_on_suspect_history_change).add(on_history_change);
9382
+ return stop_watching;
9383
+ }
9290
9384
  navigate_ = {
9291
9385
  /**
9292
9386
  * Call this if you *just* want to replace the state object in history.state
@@ -9326,6 +9420,7 @@ class $e96dd13889871fc0$export$7e1297a3a19f10d8 {
9326
9420
  }
9327
9421
  event_?.preventDefault();
9328
9422
  const promise = this.#get_navigation_happened_promise(true);
9423
+ const stop_watching_for_same_href = this.#watch_for_navigation_to_same_href(new_url_);
9329
9424
  if (force_spa_navigation_ && wants_hard_navigation) {
9330
9425
  // Don't allow hard navigation if we're forcing SPA navigation, which we do for things where PageReplacer can be used for performance
9331
9426
  const new_state = history.state;
@@ -9339,7 +9434,7 @@ class $e96dd13889871fc0$export$7e1297a3a19f10d8 {
9339
9434
  is_replace: is_replace_,
9340
9435
  new_url: new_url_ instanceof URL ? new_url_ : new URL(new_url_, location.href),
9341
9436
  scroll: scroll
9342
- });
9437
+ }, stop_watching_for_same_href);
9343
9438
  await promise;
9344
9439
  })
9345
9440
  };
@@ -10808,6 +10903,7 @@ var $66b8b9a9613c141d$var$_tmpl$ = /*#__PURE__*/$gVhhF$solidjsweb.template(`<but
10808
10903
  $66b8b9a9613c141d$var$_tmpl$3 = /*#__PURE__*/$gVhhF$solidjsweb.template(`<div class=spacer>`),
10809
10904
  $66b8b9a9613c141d$var$_tmpl$4 = /*#__PURE__*/$gVhhF$solidjsweb.template(`<button class="submit major"type=button>`),
10810
10905
  $66b8b9a9613c141d$var$_tmpl$5 = /*#__PURE__*/$gVhhF$solidjsweb.template(`<div><!$><!/><!$><!/><div class=field><!$><!/><input class=query tabindex=0 enterkeyhint=search><!$><!/></div><!$><!/><!$><!/>`);
10906
+ const $66b8b9a9613c141d$var$keys_not_opening_modal = new Set(["Tab", "Shift", "Control", "Alt", "AltGraph", "Meta", "CapsLock", "Escape", "ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight", "PageUp", "PageDown", "Home", "End", "Insert", "ContextMenu", "NumLock", "ScrollLock", "Pause", "PrintScreen"]);
10811
10907
  function $66b8b9a9613c141d$export$b94867ecbd698f21({
10812
10908
  search_field_value_: [searchFieldValue, setSearchFieldValue],
10813
10909
  input_field_ref_: input_field_ref_,
@@ -10985,7 +11081,7 @@ function $66b8b9a9613c141d$export$b94867ecbd698f21({
10985
11081
  get onKeyDown() {
10986
11082
  return $gVhhF$depictaiutilishared.catchify(e => {
10987
11083
  if (disabled_) {
10988
- if (e.key !== "Tab" && e.key !== "Shift")
11084
+ if (!$66b8b9a9613c141d$var$keys_not_opening_modal.has(e.key) && !/^F\d{1,2}$/.test(e.key))
10989
11085
  // alow tab-selecting the input field and then open the modal when the user start writing but also to move past the field with tab
10990
11086
  input_element.click();
10991
11087
  } else if (e.key === "Enter") {
@@ -11065,11 +11161,17 @@ var $610b584d4f54699e$var$_tmpl$ = /*#__PURE__*/$gVhhF$solidjsweb.template(`<svg
11065
11161
  function $610b584d4f54699e$export$8d8893ffd8aca39a() {
11066
11162
  return $gVhhF$solidjsweb.getNextElement($610b584d4f54699e$var$_tmpl$);
11067
11163
  }
11068
- function $eb3039a27db40508$export$93d9bfd0c9c1a5b8(close_modal, search_query_url_param_name_) {
11164
+ function $eb3039a27db40508$export$93d9bfd0c9c1a5b8(close_modal, search_query_url_param_name_, router_) {
11069
11165
  const [exit_iterable, exitable_iterable] = $gVhhF$depictaiutilishared.make_asyncIterable_exiter((0, $gVhhF$depictaiutilishared.href_change_ipns));
11166
+ const {
11167
+ navigation_to_same_href_listeners_: navigation_to_same_href_listeners_
11168
+ } = router_;
11070
11169
  let set_initial_values = false;
11071
11170
  let old_pathname;
11072
11171
  let old_param_value;
11172
+ // A card linking to the page one is already on doesn't change the href, close the modal anyway so the click does something
11173
+ navigation_to_same_href_listeners_.add(close_modal);
11174
+ $gVhhF$solidjs.onCleanup(() => navigation_to_same_href_listeners_.delete(close_modal));
11073
11175
  $gVhhF$solidjs.onCleanup($gVhhF$depictaiutilishared.catchify(exit_iterable));
11074
11176
  $gVhhF$depictaiutilishared.catchify(async () => {
11075
11177
  for await (const href of exitable_iterable) {
@@ -11088,6 +11190,31 @@ function $eb3039a27db40508$export$93d9bfd0c9c1a5b8(close_modal, search_query_url
11088
11190
  }
11089
11191
  })();
11090
11192
  }
11193
+ function $5f9dc76a99175920$export$9e9e2ae069cf2fe1({
11194
+ props_search_field_value_: props_search_field_value_,
11195
+ props_submit_query_: props_submit_query_,
11196
+ dont_sync_search_field_value_except_on_submit_: dont_sync_search_field_value_except_on_submit_,
11197
+ set_focus_target_after_close_: set_focus_target_after_close_,
11198
+ get_search_page_input_element_: get_search_page_input_element_
11199
+ }) {
11200
+ const search_field_value_ = dont_sync_search_field_value_except_on_submit_ ? $gVhhF$solidjs.createSignal("") : props_search_field_value_;
11201
+ let submits_in_flight = 0;
11202
+ const submit_query_ = async (new_query = $gVhhF$solidjs.untrack(search_field_value_[0])) => {
11203
+ submits_in_flight++;
11204
+ try {
11205
+ return await props_submit_query_(new_query, () => set_focus_target_after_close_(get_search_page_input_element_));
11206
+ } finally {
11207
+ submits_in_flight--;
11208
+ }
11209
+ };
11210
+ if (dont_sync_search_field_value_except_on_submit_) $gVhhF$solidjs.createEffect($gVhhF$solidjs.on(search_field_value_[0], value => submits_in_flight > 0 && props_search_field_value_[1](value), {
11211
+ defer: true
11212
+ }));
11213
+ return {
11214
+ search_field_value_: search_field_value_,
11215
+ submit_query_: submit_query_
11216
+ };
11217
+ }
11091
11218
  var $44f0b2e312a9b710$var$_tmpl$ = /*#__PURE__*/$gVhhF$solidjsweb.template(`<div class=discover-more><button class=major type=button>`),
11092
11219
  $44f0b2e312a9b710$var$_tmpl$2 = /*#__PURE__*/$gVhhF$solidjsweb.template(`<b>`),
11093
11220
  $44f0b2e312a9b710$var$_tmpl$3 = /*#__PURE__*/$gVhhF$solidjsweb.template(`<div class=depict-search-modal><div class=body role=search aria-label="Search modal with search field, query suggestions and result preview"><!$><!/><div id=depict-suggestions-and-result-preview class=padded><!$><!/><!$><!/><!$><!/></div><!$><!/></div><!$><!/>`),
@@ -11101,6 +11228,8 @@ function $44f0b2e312a9b710$var$RawClassicSearchModal({
11101
11228
  search_query_url_param_name_: search_query_url_param_name_,
11102
11229
  previous_searches_: previous_searches_,
11103
11230
  close_modal_: close_modal_,
11231
+ set_focus_target_after_close_: set_focus_target_after_close_,
11232
+ get_search_page_input_element_: get_search_page_input_element_,
11104
11233
  depict_api_: depict_api_,
11105
11234
  merchant_: merchant_,
11106
11235
  market_: market_,
@@ -11125,15 +11254,16 @@ function $44f0b2e312a9b710$var$RawClassicSearchModal({
11125
11254
  // As per feedback from stronger we sometimes want a modal with the search field value disconnected from the one in the rest of the UI, see https://depictaiworkspace.slack.com/archives/C02J16R9XEH/p1679065878487709.
11126
11255
  // This is desired for when the modal is opened from a UI element that visually does not look connected with the search page or current query
11127
11256
  // Basically the UI signals that the button that opens the modal starts a new search.
11128
- const search_field_value_ = dont_sync_search_field_value_except_on_submit_ ? $gVhhF$solidjs.createSignal("") : props_search_field_value;
11129
- const propagate_search_field_value_back = dont_sync_search_field_value_except_on_submit_ ? () => props_search_field_value[1]($gVhhF$solidjs.untrack(search_field_value_[0])) // Set actual search field value to the throwaway internal one before submitting
11130
- : undefined;
11131
- const submit_query_ = (...args) => {
11132
- // submit_query expects to read the current search field value which is why we have to write the current one to the signal before calling it
11133
- // It will write the old one back and then write the new one after submit
11134
- propagate_search_field_value_back?.();
11135
- return props_submit_query(...args);
11136
- };
11257
+ const {
11258
+ search_field_value_: search_field_value_,
11259
+ submit_query_: submit_query_
11260
+ } = $5f9dc76a99175920$export$9e9e2ae069cf2fe1({
11261
+ props_search_field_value_: props_search_field_value,
11262
+ props_submit_query_: props_submit_query,
11263
+ dont_sync_search_field_value_except_on_submit_: dont_sync_search_field_value_except_on_submit_,
11264
+ set_focus_target_after_close_: set_focus_target_after_close_,
11265
+ get_search_page_input_element_: get_search_page_input_element_
11266
+ });
11137
11267
  const [input_field, set_input_field] = $gVhhF$solidjs.createSignal();
11138
11268
  const [get_search_field_value_] = search_field_value_;
11139
11269
  // Search field alignment unused in ClassicSearchModal since the search field spans the whole modal
@@ -11197,7 +11327,7 @@ function $44f0b2e312a9b710$var$RawClassicSearchModal({
11197
11327
  return _el$;
11198
11328
  })();
11199
11329
  if (disable_scrolling_) $ca1176b694a6ee2e$export$fe334901e8bebd57();
11200
- $eb3039a27db40508$export$93d9bfd0c9c1a5b8(close_modal_, search_query_url_param_name_);
11330
+ $eb3039a27db40508$export$93d9bfd0c9c1a5b8(close_modal_, search_query_url_param_name_, router_);
11201
11331
  window.addEventListener("keydown", close_on_escape_handler);
11202
11332
  $gVhhF$solidjs.onCleanup($gVhhF$depictaiutilishared.catchify(() => window.removeEventListener("keydown", close_on_escape_handler)));
11203
11333
  if (closing_animation_) register_closing_animation_(closing_animation_);
@@ -11928,6 +12058,8 @@ function $11597ae03e703a47$var$RawSearchModalV2({
11928
12058
  search_query_url_param_name_: search_query_url_param_name_,
11929
12059
  previous_searches_: previous_searches_,
11930
12060
  close_modal_: close_modal_,
12061
+ set_focus_target_after_close_: set_focus_target_after_close_,
12062
+ get_search_page_input_element_: get_search_page_input_element_,
11931
12063
  alignmentSignals_: alignmentSignals_,
11932
12064
  depict_api_: depict_api_,
11933
12065
  merchant_: merchant_,
@@ -11958,15 +12090,16 @@ function $11597ae03e703a47$var$RawSearchModalV2({
11958
12090
  // As per feedback from stronger we sometimes want a modal with the search field value disconnected from the one in the rest of the UI, see https://depictaiworkspace.slack.com/archives/C02J16R9XEH/p1679065878487709.
11959
12091
  // This is desired for when the modal is opened from a UI element that visually does not look connected with the search page or current query
11960
12092
  // Basically the UI signals that the button that opens the modal starts a new search.
11961
- const search_field_value_ = dont_sync_search_field_value_except_on_submit_ ? $gVhhF$solidjs.createSignal("") : props_search_field_value;
11962
- const propagate_search_field_value_back = dont_sync_search_field_value_except_on_submit_ ? () => props_search_field_value[1]($gVhhF$solidjs.untrack(search_field_value_[0])) // Set actual search field value to the throwaway internal one before submitting
11963
- : undefined;
11964
- const submit_query_ = (...args) => {
11965
- // submit_query expects to read the current search field value which is why we have to write the current one to the signal before calling it
11966
- // It will write the old one back and then write the new one after submit
11967
- propagate_search_field_value_back?.();
11968
- return props_submit_query(...args);
11969
- };
12093
+ const {
12094
+ search_field_value_: search_field_value_,
12095
+ submit_query_: submit_query_
12096
+ } = $5f9dc76a99175920$export$9e9e2ae069cf2fe1({
12097
+ props_search_field_value_: props_search_field_value,
12098
+ props_submit_query_: props_submit_query,
12099
+ dont_sync_search_field_value_except_on_submit_: dont_sync_search_field_value_except_on_submit_,
12100
+ set_focus_target_after_close_: set_focus_target_after_close_,
12101
+ get_search_page_input_element_: get_search_page_input_element_
12102
+ });
11970
12103
  const [input_field, set_input_field] = $gVhhF$solidjs.createSignal();
11971
12104
  const [searching_for_value_, set_searching_for_value_] = $gVhhF$solidjs.createSignal();
11972
12105
  const [show_stale_content_results_, set_show_stale_content_results_] = $gVhhF$solidjs.createSignal(false);
@@ -12073,7 +12206,7 @@ function $11597ae03e703a47$var$RawSearchModalV2({
12073
12206
  });
12074
12207
  const noResults = $gVhhF$solidjs.createMemo(prev => (get_modal_search_results_()?.loading === false && !suggestions_response_.loading || !!prev) && totalNumberResults() === 0 && previous_searches_matching_query().length === 0);
12075
12208
  if (disable_scrolling_) $ca1176b694a6ee2e$export$fe334901e8bebd57();
12076
- $eb3039a27db40508$export$93d9bfd0c9c1a5b8(close_modal_, search_query_url_param_name_);
12209
+ $eb3039a27db40508$export$93d9bfd0c9c1a5b8(close_modal_, search_query_url_param_name_, router_);
12077
12210
  addEventListener("keydown", close_on_escape_handler);
12078
12211
  $gVhhF$solidjs.onCleanup(() => removeEventListener("keydown", close_on_escape_handler));
12079
12212
  $gVhhF$solidjs.createEffect($gVhhF$solidjs.on(get_search_field_value_, () => {
@@ -14097,11 +14230,12 @@ function $2b7a005a65221045$export$84bba84a7b837187(read_search_query) {
14097
14230
  async function $f328bd45f8ede33c$export$e16630ecb9644a1c({
14098
14231
  on_submit_with_unchanged_value_: on_submit_with_unchanged_value_,
14099
14232
  search_param_name_: search_param_name_,
14100
- set_search_field_value_: set_search_field_value_,
14233
+ search_field_value_: search_field_value_,
14101
14234
  get_search_query_: get_search_query_,
14102
14235
  url_transformer_: url_transformer_,
14103
14236
  router_: router_,
14104
14237
  after_submit_: after_submit_,
14238
+ before_navigate_: before_navigate_,
14105
14239
  get_new_query: get_new_query
14106
14240
  }) {
14107
14241
  const new_search_query = get_new_query();
@@ -14109,21 +14243,89 @@ async function $f328bd45f8ede33c$export$e16630ecb9644a1c({
14109
14243
  if (previous_query === new_search_query && new URLSearchParams(location.search).has(search_param_name_)) {
14110
14244
  // don't push a history entry / re-search if someone submits a search again
14111
14245
  // do close the modal though
14246
+ search_field_value_.settle_(new_search_query);
14112
14247
  on_submit_with_unchanged_value_?.();
14113
- return;
14248
+ return false;
14114
14249
  }
14115
- set_search_field_value_(previous_query); // restore the value to the previous query since this one goes to the new history entry
14116
14250
  const target_url = new URL(location.href);
14117
14251
  url_transformer_?.(target_url);
14118
14252
  target_url.searchParams.set(search_param_name_, new_search_query);
14119
- await router_.navigate_.go_to_({
14120
- new_url_: target_url,
14121
- is_replace_: false,
14122
- force_spa_navigation_: true
14123
- });
14124
- set_search_field_value_(new_search_query);
14253
+ const end_submit = search_field_value_.begin_submit_(new_search_query, previous_query);
14254
+ before_navigate_?.();
14255
+ let landed;
14256
+ try {
14257
+ await router_.navigate_.go_to_({
14258
+ new_url_: target_url,
14259
+ is_replace_: false,
14260
+ force_spa_navigation_: true
14261
+ });
14262
+ } finally {
14263
+ landed = end_submit();
14264
+ }
14265
+ if (!landed) return false;
14125
14266
  scrollTo(0, 0);
14126
14267
  after_submit_?.();
14268
+ return true;
14269
+ }
14270
+ function $380b434363eee5f6$export$d13a185c53aa5edf(persisted_, get_search_query_) {
14271
+ const [get_persisted, set_persisted] = persisted_;
14272
+ const [get_submitting, set_submitting] = $gVhhF$solidjs.createSignal();
14273
+ const get_value = $gVhhF$solidjs.createMemo(() => get_submitting() ?? get_persisted());
14274
+ const set_value = value => {
14275
+ const new_value = typeof value === "function" ? value($gVhhF$solidjs.untrack(get_value)) : value;
14276
+ if ($gVhhF$solidjs.untrack(get_submitting) === undefined) set_persisted(new_value);else set_submitting(new_value);
14277
+ return new_value;
14278
+ };
14279
+ let current_submit;
14280
+ const end_submit = landed => {
14281
+ current_submit.landed_ = landed;
14282
+ current_submit = undefined;
14283
+ $gVhhF$solidjs.batch(() => {
14284
+ const submitting = $gVhhF$solidjs.untrack(get_submitting);
14285
+ set_submitting(undefined);
14286
+ if (landed && submitting !== undefined) set_persisted(submitting);
14287
+ });
14288
+ };
14289
+ const abort_on_popstate = what_happened => {
14290
+ if (what_happened === "popstate" && current_submit) end_submit(false);
14291
+ };
14292
+ (0, $gVhhF$depictaiutilishared.instant_exec_on_suspect_history_change).add(abort_on_popstate);
14293
+ $gVhhF$solidjs.onCleanup(() => (0, $gVhhF$depictaiutilishared.instant_exec_on_suspect_history_change).delete(abort_on_popstate));
14294
+ $gVhhF$solidjs.createEffect($gVhhF$solidjs.on(get_search_query_, query => {
14295
+ const token = current_submit;
14296
+ if (!token || query !== token.query_) return;
14297
+ queueMicrotask(() => {
14298
+ if (current_submit === token) end_submit(true);
14299
+ });
14300
+ }, {
14301
+ defer: true
14302
+ }));
14303
+ const begin_submit_ = (submitted_query, query_of_current_history_entry) => {
14304
+ const token = current_submit = {
14305
+ query_: submitted_query,
14306
+ landed_: false
14307
+ };
14308
+ $gVhhF$solidjs.batch(() => {
14309
+ set_submitting(submitted_query);
14310
+ set_persisted(query_of_current_history_entry);
14311
+ });
14312
+ return () => {
14313
+ if (current_submit === token) end_submit(true);
14314
+ return token.landed_;
14315
+ };
14316
+ };
14317
+ const settle_ = query => {
14318
+ if (current_submit) return;
14319
+ $gVhhF$solidjs.batch(() => {
14320
+ set_submitting(undefined);
14321
+ set_persisted(query);
14322
+ });
14323
+ };
14324
+ return {
14325
+ value_: [get_value, set_value],
14326
+ begin_submit_: begin_submit_,
14327
+ settle_: settle_
14328
+ };
14127
14329
  }
14128
14330
  function $331c7a65de274e6c$export$181cceed5c91919e(callback, alsoRaF, passiveListener) {
14129
14331
  let aligned_this_microtask = false;
@@ -14827,6 +15029,7 @@ function $f1d7cb69f839a526$export$a54ec244c8863715({
14827
15029
  scroll_restoration_data_: scroll_restoration_data_,
14828
15030
  product_card_template_: product_card_template_,
14829
15031
  modalAlignmentSignalsRef_: modalAlignmentSignalsRef_,
15032
+ inputFieldRef_: inputFieldRef_,
14830
15033
  router_: router_,
14831
15034
  content_results_rows_: content_results_rows_,
14832
15035
  content_layout_options_: content_layout_options_,
@@ -15015,6 +15218,8 @@ function $f1d7cb69f839a526$export$a54ec244c8863715({
15015
15218
  if (!search_field) break add_input_field;
15016
15219
  setModalBodyStyle({});
15017
15220
  modalAlignmentSignalsRef_?.(alignmentSignals_);
15221
+ inputFieldRef_?.(input_element);
15222
+ $gVhhF$solidjs.onCleanup(() => inputFieldRef_?.(undefined));
15018
15223
  const [pollAlignment, setPollAlignment_] = $gVhhF$solidjs.createSignal(false);
15019
15224
  const align_position = $gVhhF$depictaiutilishared.catchify((0, $fd463ff0edb29915$export$b3ad617e9036868))(alignmentSignals_, search_field, (0, $fd463ff0edb29915$export$3f238c96c45e6cc6) | (0, $fd463ff0edb29915$export$7895aadd1956b1c7) | (modalVersionUsed_ === 1 ? (0, $fd463ff0edb29915$export$902f8b4a7dae5d78) : (0, $fd463ff0edb29915$export$6060bc6bee850eb6) | (0, $fd463ff0edb29915$export$5097308c5200105a)), true, pollAlignment);
15020
15225
  div_with_field_class.addEventListener("click", $gVhhF$depictaiutilishared.catchify(e => {
@@ -15357,14 +15562,16 @@ class $2c61e4cd8229c49a$export$ac6237957a15b3f0 {
15357
15562
  set_search_content_results_rows((0, $3716b8caf7f959e2$export$70ceae36059a1036));
15358
15563
  }; // do this every time after we have submitted a new query - otherwise there might already be a saved number of rows in the state
15359
15564
  const on_submit_with_unchanged_value_ = () => this.modal_open = false;
15360
- const [get_search_field_value_, set_search_field_value_] = state_.field_value;
15361
- const configured_submit_query_ = new_query => $f328bd45f8ede33c$export$e16630ecb9644a1c({
15565
+ const search_field_value_ = $380b434363eee5f6$export$d13a185c53aa5edf(state_.field_value, get_search_query_);
15566
+ const [get_search_field_value_] = search_field_value_.value_;
15567
+ const configured_submit_query_ = (new_query, before_navigate_) => $f328bd45f8ede33c$export$e16630ecb9644a1c({
15362
15568
  on_submit_with_unchanged_value_: on_submit_with_unchanged_value_,
15363
- set_search_field_value_: set_search_field_value_,
15569
+ search_field_value_: search_field_value_,
15364
15570
  get_search_query_: get_search_query_,
15365
15571
  search_param_name_: search_query_url_param_name_,
15366
15572
  after_submit_: after_submit_,
15367
- get_new_query: () => new_query || $gVhhF$solidjs.untrack(get_search_field_value_),
15573
+ before_navigate_: before_navigate_,
15574
+ get_new_query: () => new_query ?? $gVhhF$solidjs.untrack(get_search_field_value_),
15368
15575
  url_transformer_: url_transformer,
15369
15576
  router_: this.#router
15370
15577
  });
@@ -15383,6 +15590,7 @@ class $2c61e4cd8229c49a$export$ac6237957a15b3f0 {
15383
15590
  search_query_url_param_name_: search_query_url_param_name_,
15384
15591
  reset_history_state_: reset_history_state_,
15385
15592
  state_: state_,
15593
+ search_field_value_: search_field_value_.value_,
15386
15594
  router_: this.#router,
15387
15595
  url_transformer_: url_transformer,
15388
15596
  depict_api_: depict_api_,
@@ -15406,7 +15614,8 @@ class $2c61e4cd8229c49a$export$ac6237957a15b3f0 {
15406
15614
  $gVhhF$solidjs.onCleanup(() => this.modal_open = false);
15407
15615
  return searchModalComponent(...args);
15408
15616
  }, {
15409
- search_field_value_: state_.field_value,
15617
+ search_field_value_: search_field_value_.value_,
15618
+ get_search_page_input_element_: () => $2c61e4cd8229c49a$export$dde08b2dca8cc1ce.get(this)?.search_page_input_element_,
15410
15619
  get_search_query_: get_search_query_,
15411
15620
  search_query_url_param_name_: search_query_url_param_name_,
15412
15621
  previous_searches_: previous_searches,
@@ -15554,6 +15763,7 @@ function $2c61e4cd8229c49a$export$f2dc7e4e4aa11abb(props) {
15554
15763
  const {
15555
15764
  state_: state_
15556
15765
  } = shared_properties;
15766
+ let registered_input_element;
15557
15767
  const grid_spacing_override = $gVhhF$solidjs.createMemo(() => {
15558
15768
  const {
15559
15769
  grid_spacing: grid_spacing
@@ -15582,7 +15792,7 @@ function $2c61e4cd8229c49a$export$f2dc7e4e4aa11abb(props) {
15582
15792
  min_products_to_fetch_: $2c61e4cd8229c49a$var$min_products_to_fetch_,
15583
15793
  local_filter_cache_: state_.local_filter_cache,
15584
15794
  content_search_enabled_: () => depict_search.enable_content_search ?? $2c61e4cd8229c49a$var$content_search_default,
15585
- search_field_value_: state_.field_value,
15795
+ search_field_value_: shared_properties.search_field_value_,
15586
15796
  get_search_query_: shared_properties.get_search_query_,
15587
15797
  clear_filters_on_next_submit_: shared_properties.clear_filters_on_next_submit_,
15588
15798
  open_modal_: (...args) => depict_search.modal_open = args,
@@ -15590,6 +15800,16 @@ function $2c61e4cd8229c49a$export$f2dc7e4e4aa11abb(props) {
15590
15800
  modalVersionUsed_: shared_properties.modalVersionUsed_,
15591
15801
  showSliderArrow_: () => props.showSliderArrow_,
15592
15802
  i18n_: shared_properties.i18n_,
15803
+ inputFieldRef_: input_element => {
15804
+ const shared_info_right_now = $2c61e4cd8229c49a$export$dde08b2dca8cc1ce.get(depict_search);
15805
+ if (!shared_info_right_now) return;
15806
+ if (input_element === undefined && shared_info_right_now.search_page_input_element_ !== registered_input_element) return;
15807
+ registered_input_element = input_element;
15808
+ $2c61e4cd8229c49a$export$dde08b2dca8cc1ce.set(depict_search, {
15809
+ ...shared_info_right_now,
15810
+ search_page_input_element_: input_element
15811
+ });
15812
+ },
15593
15813
  modalAlignmentSignalsRef_: style => {
15594
15814
  const shared_info_right_now = $2c61e4cd8229c49a$export$dde08b2dca8cc1ce.get(depict_search);
15595
15815
  if (!shared_info_right_now) return;
@@ -15940,7 +16160,7 @@ function $c0a0659cf97e8eb2$export$316f6f283ae64765(props) {
15940
16160
  return shared_properties.BackIcon_;
15941
16161
  },
15942
16162
  get search_field_value_() {
15943
- return shared_properties.state_.field_value;
16163
+ return shared_properties.search_field_value_;
15944
16164
  },
15945
16165
  get submit_query_() {
15946
16166
  return shared_properties.configured_submit_query_;
@@ -15983,7 +16203,7 @@ function $c0a0659cf97e8eb2$export$316f6f283ae64765(props) {
15983
16203
  return shared_properties.BackIcon_;
15984
16204
  },
15985
16205
  get search_field_value_() {
15986
- return shared_properties.state_.field_value;
16206
+ return shared_properties.search_field_value_;
15987
16207
  },
15988
16208
  get submit_query_() {
15989
16209
  return shared_properties.configured_submit_query_;
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.19";
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
@@ -713,6 +717,8 @@ declare function RawSearchModalV2<InputDisplay extends Display, OutputDisplay ex
713
717
  search_query_url_param_name_,
714
718
  previous_searches_,
715
719
  close_modal_,
720
+ set_focus_target_after_close_,
721
+ get_search_page_input_element_,
716
722
  alignmentSignals_,
717
723
  depict_api_,
718
724
  merchant_,
@@ -743,12 +749,14 @@ declare function RawSearchModalV2<InputDisplay extends Display, OutputDisplay ex
743
749
  search_query_url_param_name_: string;
744
750
  previous_searches_: Signal<string[]>;
745
751
  close_modal_: VoidFunction;
752
+ set_focus_target_after_close_: (get_target: (() => HTMLElement | undefined) | undefined) => void;
753
+ get_search_page_input_element_: () => HTMLInputElement | undefined;
746
754
  alignmentSignals_?: ModalAlignmentSignals;
747
755
  depict_api_: DepictAPI<InputDisplay, OutputDisplay>;
748
756
  clear_filters_: (user_triggered: boolean) => void;
749
757
  merchant_: Accessor<string>;
750
758
  market_: Accessor<string>;
751
- submit_query_: (new_query?: string) => void;
759
+ submit_query_: (new_query?: string, before_navigate_?: VoidFunction) => Promise<boolean>;
752
760
  class_list_?: Accessor<Record<string, boolean>>;
753
761
  url_transformer_?: (url_object: URL) => unknown;
754
762
  disable_scrolling_?: boolean;
@@ -785,6 +793,8 @@ declare function RawClassicSearchModal({
785
793
  search_query_url_param_name_,
786
794
  previous_searches_,
787
795
  close_modal_,
796
+ set_focus_target_after_close_,
797
+ get_search_page_input_element_,
788
798
  depict_api_,
789
799
  merchant_,
790
800
  market_,
@@ -810,11 +820,13 @@ declare function RawClassicSearchModal({
810
820
  search_query_url_param_name_: string;
811
821
  previous_searches_: Signal<string[]>;
812
822
  close_modal_: VoidFunction;
823
+ set_focus_target_after_close_: (get_target: (() => HTMLElement | undefined) | undefined) => void;
824
+ get_search_page_input_element_: () => HTMLInputElement | undefined;
813
825
  depict_api_: DepictAPI<any>;
814
826
  clear_filters_: (user_triggered: boolean) => void;
815
827
  merchant_: Accessor<string>;
816
828
  market_: Accessor<string>;
817
- submit_query_: (new_query?: string) => void;
829
+ submit_query_: (new_query?: string, before_navigate_?: VoidFunction) => Promise<boolean>;
818
830
  class_list_?: Accessor<Record<string, boolean>>;
819
831
  url_transformer_?: (url_object: URL) => unknown;
820
832
  disable_scrolling_?: boolean;
@@ -1038,6 +1050,7 @@ export function general_modal_abstraction<StaticOptions extends {
1038
1050
  close_modal_: VoidFunction;
1039
1051
  open_modal_: (extra_options_for_modal?: ExtraOptionsProvidedOnOpen, on_dispose?: VoidFunction) => void;
1040
1052
  register_closing_animation_: (animation: () => Promise<any>) => void;
1053
+ set_focus_target_after_close_: (get_target: (() => HTMLElement | undefined) | undefined) => void;
1041
1054
  }) => Node_Array, modal_fn_opts: StaticOptions, parent?: HTMLElement | ShadowRoot): Promise<{
1042
1055
  open_modal_: (extra_options_for_modal?: any, on_dispose?: VoidFunction | undefined) => Promise<void | undefined>;
1043
1056
  close_modal_: () => void | undefined;