@luigisbox/luma 0.7.0 → 0.7.1

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/CHANGELOG.md CHANGED
@@ -5,6 +5,13 @@ before 1.0, a minor version may break the integration API.
5
5
 
6
6
  ## Unreleased
7
7
 
8
+ ## 0.7.1 — 2026-11-02
9
+
10
+ ### Fixed
11
+
12
+ - iOS Safari: swiping Back from a product to a listing showed a blank page
13
+ for a moment; the swipe now shows the listing.
14
+
8
15
  ## 0.7.0 — 2026-10-29
9
16
 
10
17
  ### Added
package/README.md CHANGED
@@ -12,7 +12,7 @@ its own repository and depends on `@luigisbox/luma`
12
12
 
13
13
  > **Pre-release.** Until 1.0, a minor version (0.8.0) may change the
14
14
  > integration API (config keys, template scope, the build); a patch version
15
- > (0.7.1) only fixes. Depend on `^0.7.0` to take fixes without breaking
15
+ > (0.7.2) only fixes. Depend on `^0.7.1` to take fixes without breaking
16
16
  > changes. [`CHANGELOG.md`](CHANGELOG.md) says what each version changed.
17
17
 
18
18
  ## Core principles
@@ -474,7 +474,7 @@ built as on.
474
474
  | `search.prefetchWhileTyping` | the search result is fetched once the typed phrase settles, so Enter shows it at once | |
475
475
  | `search.pageWhileTyping` (`'prefetch'`, `'prerender'`, `false`) | the search page of the typed phrase is fetched ahead (Chrome, Edge; Firefox and Safari as they ship it), so Enter from another page skips the site's server render; `'prerender'` renders it ahead as well (Chrome, Edge) | the site cannot take a document request per typed phrase; `'prerender'` only where its tags wait for `prerenderingchange` |
476
476
  | `search.queryUrl` (an `_init.ts` hook, off unless set) | where the site's server redirects its search form to the query's page (`/search?q=x` to `/search/x/`), names that page: Enter, the autocomplete's "show all" and a picked query load it directly, without the redirect, and the result fetched while typing is shown there | the form's other fields decide the page, or the site must handle every search itself |
477
- | `restoreOnBack` | Back from a product restores the listing (every page shown) and the followed tile's position, as does the page loaded again within a minute of following a link (a reload, a link whose server redirects back to it); Luma turns the browser's own scroll restoration (`history.scrollRestoration`) off for the listing's history entry until it has placed the page, unless the site set it | |
477
+ | `restoreOnBack` | Back from a product restores the listing (every page shown) and the followed tile's position, as does the page loaded again within a minute of following a link (a reload, a link whose server redirects back to it); Luma turns the browser's own scroll restoration (`history.scrollRestoration`) off for the listing's history entry until it has placed the page (not while the page is in the browser's back/forward cache, which restores it from memory), unless the site set it | |
478
478
  | `prefetchNextPage` | "show more" is fetched as the shopper nears the end | |
479
479
  | `contentVisibility` (opt-in) | tiles out of view skip style, layout and paint, each at its own measured size: a long list's re-render about halves | keep it off where a tile draws outside its box (a hover panel, a badge hanging out) or holds a `position: fixed` element |
480
480
  | `variants: { fields }` (opt-in) | each tile's result gets its variant group (`r.variants`, "Templates" above): one request to Luigi's Box per page of results, never waited for | the surface does not collapse variants (every group is a group of one) |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@luigisbox/luma",
3
- "version": "0.7.0",
3
+ "version": "0.7.1",
4
4
  "type": "module",
5
5
  "description": "Luma: Luigi's Box frontend integrations (autocomplete, search, category listings, recommenders) on the Luigi's Box AI API. Pre-release.",
6
6
  "license": "SEE LICENSE IN LICENSE.md",
@@ -403,7 +403,7 @@ export const startListing = async (ctx: WidgetContext<ListingConfig>, tk: Takeov
403
403
  private hiddenAt = 0;
404
404
  /** scrollY when a Back stash was last written (leave()): the page hidden from there is stashed already. */
405
405
  private leftAt = -1;
406
- private onPageHide = () => {
406
+ private onPageHide = (e: PageTransitionEvent) => {
407
407
  this.hiddenAt = Date.now();
408
408
  // Left with more than the first page shown, not by a link of ours (a
409
409
  // reload, their menu, the address bar): stashed too, so a reload within
@@ -412,6 +412,8 @@ export const startListing = async (ctx: WidgetContext<ListingConfig>, tk: Takeov
412
412
  // shorter than where the shopper was). Into the back/forward cache
413
413
  // too: it may be evicted there (then Back is a page load).
414
414
  if (restoreOn && (this.last?.page ?? 0) > 1 && this.leftAt !== Math.round(scrollY)) this.leave();
415
+ // Into the back/forward cache: the browser's restoration on again (instant.ts `holdScroll`).
416
+ if (e.persisted) releaseScroll();
415
417
  };
416
418
  /**
417
419
  * Restored from the back/forward cache (pageshow, persisted): the page is as
@@ -424,14 +426,13 @@ export const startListing = async (ctx: WidgetContext<ListingConfig>, tk: Takeov
424
426
  */
425
427
  private onPageShow = (e: PageTransitionEvent) => {
426
428
  if (!e.persisted) return;
427
- // Where the shopper was: the browser's own restoration is off for the
428
- // entry (holdScroll), and iOS Safari reads scrollY 0 at this event
429
- // even when it restores it. Placed again, then the browser's back on.
429
+ // Where the shopper was: iOS Safari reads scrollY 0 at this event even
430
+ // when it restores it. Placed again (the browser's restoration is on
431
+ // again since pagehide, a host's own setting kept).
430
432
  const st = peekStash(location.href);
431
433
  dropStash(location.href);
432
434
  mark(`${route.id} restored from the back/forward cache`);
433
- if (st?.kind === 'back' && st.scroll && tk.state === 'ours') restoreScroll(this, st.scroll, releaseScroll);
434
- else releaseScroll();
435
+ if (st?.kind === 'back' && st.scroll && tk.state === 'ours') restoreScroll(this, st.scroll);
435
436
  const away = this.hiddenAt ? Date.now() - this.hiddenAt : 0;
436
437
  if (away > BFCACHE_STALE_MS && this.last && this.lastQ && this.lastQ === this.current) this.revalidate(this.lastQ, this.last, new URL(location.href), this.href);
437
438
  };
@@ -106,8 +106,10 @@ export const restoreScroll = (root: Element, s: Spot, after?: () => void) => {
106
106
  * after its render. The browser's restoration ran before it, on a page not
107
107
  * grown to the stashed pages yet, and could land after Luma's place too.
108
108
  * Only from `auto`: a host that set it (Turbo, a router) keeps its own.
109
- * Back on (`releaseScroll`) once Luma has placed the page, or when the
110
- * document moves on within itself (an SPA's navigation: theirs to restore).
109
+ * Back on (`releaseScroll`) once Luma has placed the page, when the
110
+ * document moves on within itself (an SPA's navigation: theirs to restore),
111
+ * or as the page goes into the back/forward cache (restored from memory:
112
+ * iOS Safari's swipe Back shows the page behind only with it on).
111
113
  */
112
114
  let ownScroll = false;
113
115
  /** Off for this entry, when it is the browser's (or already ours): 1, for the stash's `sr`. */
@@ -121,8 +123,16 @@ export const holdScroll = (): 1 | undefined => {
121
123
  return undefined;
122
124
  }
123
125
  };
124
- /** A page loaded on an entry Luma turned it off for (the stash's `sr`): Luma's to turn back on. */
125
- export const adoptScroll = () => void (ownScroll = true);
126
+ /**
127
+ * A page loaded on an entry Luma turned it off for (the stash's `sr`): Luma's
128
+ * to turn back on. Off again where it was turned on as the page went into
129
+ * the back/forward cache (the page evicted from it since): the browser's
130
+ * restoration stops here, Luma places the page.
131
+ */
132
+ export const adoptScroll = () => {
133
+ ownScroll = true;
134
+ holdScroll();
135
+ };
126
136
  /** Back to the browser's, if Luma turned it off. */
127
137
  export const releaseScroll = () => {
128
138
  if (!ownScroll) return;