@nok-integration/storefront-react 0.20.5 → 0.20.7

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/dist/index.d.ts CHANGED
@@ -4272,8 +4272,8 @@ declare type OrderDetail = z.infer<typeof OrderDetail>;
4272
4272
  /**
4273
4273
  * The fan's orders (Figma `mobile/order/list`).
4274
4274
  *
4275
- * Read-only, like the detail screen it links to. The design's return states have no data
4276
- * behind them — see {@link OrderStatusBadge}.
4275
+ * Read-only, like the detail screen it links to. A return is a row of its own beside the
4276
+ * order it came from, and opens its own progress — see {@link ReturnStatusBadge}.
4277
4277
  *
4278
4278
  * Scoped to the session at the engine: `GET /v1/orders` takes no customer parameter, so
4279
4279
  * there is no identifier a caller could supply to read somebody else's history.
@@ -4284,11 +4284,13 @@ export declare interface OrderListViewProps {
4284
4284
  /** Opens one order. Without it the rows render as static cards rather than dead links. */
4285
4285
  onOpenOrder?: (orderRef: string) => void;
4286
4286
  /**
4287
- * Opens one return, which is its own screen rather than the order's.
4287
+ * Open one return on a route of your own.
4288
4288
  *
4289
- * Without it a return row renders as a static card. It is deliberately not defaulted to
4290
- * `onOpenOrder`: a fan tapping a return is asking about the return, and landing them on
4291
- * the order is a different answer to the question they asked.
4289
+ * Optional, and optional in the way every handler here is: it customises where the row
4290
+ * goes, it does not decide whether the row opens. Omit it and the return's progress opens
4291
+ * in place of the list, the way the order screen opens one. It is deliberately not
4292
+ * defaulted to `onOpenOrder`: a fan tapping a return is asking about the return, and
4293
+ * landing them on the order is a different answer to the question they asked.
4292
4294
  */
4293
4295
  onOpenReturn?: (returnRef: string) => void;
4294
4296
  /** The empty state's call to action — the design's "Continue shopping" button. */
package/dist/index.mjs CHANGED
@@ -5189,8 +5189,8 @@ function resolveAssetUrl(src, base) {
5189
5189
  const MAX_AGE_MS = 5 * 6e4;
5190
5190
  const CAPACITY = 60;
5191
5191
  class ProductCache {
5192
- constructor(now = () => Date.now()) {
5193
- this.now = now;
5192
+ constructor(now2 = () => Date.now()) {
5193
+ this.now = now2;
5194
5194
  }
5195
5195
  now;
5196
5196
  entries = /* @__PURE__ */ new Map();
@@ -5702,8 +5702,66 @@ function useTopOffset(ref, property, enabled = true) {
5702
5702
  };
5703
5703
  }, [ref, property, enabled]);
5704
5704
  }
5705
+ const HOLD_MS = 1e3;
5706
+ const TRAVERSAL_WINDOW_MS = 1e3;
5707
+ const now = () => typeof performance !== "undefined" ? performance.now() : Date.now();
5708
+ let lastTraversal = -Infinity;
5709
+ if (typeof window !== "undefined") {
5710
+ window.addEventListener("popstate", () => {
5711
+ lastTraversal = now();
5712
+ });
5713
+ }
5714
+ function followsTraversal() {
5715
+ return now() - lastTraversal < TRAVERSAL_WINDOW_MS;
5716
+ }
5717
+ const RELEASES = ["touchstart", "wheel", "keydown", "pointerdown"];
5718
+ function anchorTop(shouldHold = () => true) {
5719
+ let released = false;
5720
+ const top2 = () => {
5721
+ if (released) return;
5722
+ const scrolled2 = window.scrollY || document.documentElement.scrollTop || 0;
5723
+ if (scrolled2 > 0 && shouldHold()) window.scrollTo(0, 0);
5724
+ };
5725
+ const release = () => {
5726
+ if (released) return;
5727
+ released = true;
5728
+ if (frame2 !== null && typeof cancelAnimationFrame === "function") cancelAnimationFrame(frame2);
5729
+ clearTimeout(timer2);
5730
+ window.removeEventListener("scroll", top2);
5731
+ for (const type of RELEASES) window.removeEventListener(type, release);
5732
+ };
5733
+ top2();
5734
+ const frame2 = typeof requestAnimationFrame === "function" ? requestAnimationFrame(top2) : null;
5735
+ const timer2 = setTimeout(release, HOLD_MS);
5736
+ window.addEventListener("scroll", top2, {
5737
+ passive: true
5738
+ });
5739
+ for (const type of RELEASES) window.addEventListener(type, release, {
5740
+ passive: true
5741
+ });
5742
+ return release;
5743
+ }
5744
+ function documentTop(el) {
5745
+ let top2 = 0;
5746
+ for (let node = el; node; node = node.offsetParent) {
5747
+ top2 += node.offsetTop;
5748
+ }
5749
+ return top2;
5750
+ }
5751
+ function beginsOnTheFirstViewport(el) {
5752
+ return documentTop(el) < window.innerHeight;
5753
+ }
5754
+ function useScreenAnchor(ref, enabled = true) {
5755
+ useEffect(() => {
5756
+ const el = ref.current;
5757
+ if (!el || !enabled) return;
5758
+ if (followsTraversal()) return;
5759
+ return anchorTop(() => beginsOnTheFirstViewport(el));
5760
+ }, [ref, enabled]);
5761
+ }
5705
5762
  const ShopSurface = forwardRef(function ShopSurface2({ tone, fill: fill2 = false, children, className, ...rest }, ref) {
5706
5763
  const own = useRef(null);
5764
+ useScreenAnchor(own, fill2);
5707
5765
  useTopOffset(own, "--surface-top", fill2);
5708
5766
  const attach = useCallback((node) => {
5709
5767
  own.current = node;
@@ -8297,11 +8355,19 @@ function Chevron() {
8297
8355
  });
8298
8356
  }
8299
8357
  function OrderListView(props) {
8358
+ const [opened, setOpened] = useState(null);
8359
+ if (opened !== null) {
8360
+ return jsx(ReturnFlowView, {
8361
+ returnRef: opened,
8362
+ onDone: () => setOpened(null)
8363
+ });
8364
+ }
8300
8365
  return jsx(ShopSurface, {
8301
8366
  tone: "light",
8302
8367
  fill: true,
8303
8368
  children: jsx(OrderListContent, {
8304
- ...props
8369
+ ...props,
8370
+ onOpenReturn: props.onOpenReturn ?? setOpened
8305
8371
  })
8306
8372
  });
8307
8373
  }
@@ -10295,14 +10361,14 @@ function formatRemaining(ms) {
10295
10361
  return `${hh}:${mm}:${ss}`;
10296
10362
  }
10297
10363
  function PromoBanner({ promo: promo2 }) {
10298
- const [now, setNow] = useState(null);
10364
+ const [now2, setNow] = useState(null);
10299
10365
  useEffect(() => {
10300
10366
  if (!promo2.ends_at) return;
10301
10367
  setNow(Date.now());
10302
10368
  const id = setInterval(() => setNow(Date.now()), 1e3);
10303
10369
  return () => clearInterval(id);
10304
10370
  }, [promo2.ends_at]);
10305
- const remaining = promo2.ends_at && now !== null ? new Date(promo2.ends_at).getTime() - now : null;
10371
+ const remaining = promo2.ends_at && now2 !== null ? new Date(promo2.ends_at).getTime() - now2 : null;
10306
10372
  const showTimer = remaining !== null && remaining > 0;
10307
10373
  return jsxs("div", {
10308
10374
  className: styles$b.banner,
@@ -11221,8 +11287,11 @@ function useDocumentSnap(ref, enabled = true) {
11221
11287
  root2.style.scrollSnapType = "y mandatory";
11222
11288
  root2.style.scrollPaddingTop = "0px";
11223
11289
  const top2 = () => window.scrollTo(0, 0);
11224
- top2();
11225
- const frame2 = typeof window.requestAnimationFrame === "function" ? window.requestAnimationFrame(top2) : null;
11290
+ const entered = !followsTraversal();
11291
+ if (entered) top2();
11292
+ const frame2 = entered && typeof window.requestAnimationFrame === "function" ? window.requestAnimationFrame(top2) : null;
11293
+ const release = entered ? anchorTop() : () => {
11294
+ };
11226
11295
  const history = window.history;
11227
11296
  const previousRestoration = history && "scrollRestoration" in history ? history.scrollRestoration : null;
11228
11297
  if (previousRestoration !== null) history.scrollRestoration = "manual";
@@ -11231,6 +11300,7 @@ function useDocumentSnap(ref, enabled = true) {
11231
11300
  };
11232
11301
  window.addEventListener("pageshow", onPageShow);
11233
11302
  return () => {
11303
+ release();
11234
11304
  if (frame2 !== null && typeof window.cancelAnimationFrame === "function") {
11235
11305
  window.cancelAnimationFrame(frame2);
11236
11306
  }
@@ -5189,8 +5189,8 @@ function resolveAssetUrl(src, base) {
5189
5189
  const MAX_AGE_MS = 5 * 6e4;
5190
5190
  const CAPACITY = 60;
5191
5191
  class ProductCache {
5192
- constructor(now = () => Date.now()) {
5193
- this.now = now;
5192
+ constructor(now2 = () => Date.now()) {
5193
+ this.now = now2;
5194
5194
  }
5195
5195
  now;
5196
5196
  entries = /* @__PURE__ */ new Map();
@@ -5702,8 +5702,66 @@ function useTopOffset(ref, property, enabled = true) {
5702
5702
  };
5703
5703
  }, [ref, property, enabled]);
5704
5704
  }
5705
+ const HOLD_MS = 1e3;
5706
+ const TRAVERSAL_WINDOW_MS = 1e3;
5707
+ const now = () => typeof performance !== "undefined" ? performance.now() : Date.now();
5708
+ let lastTraversal = -Infinity;
5709
+ if (typeof window !== "undefined") {
5710
+ window.addEventListener("popstate", () => {
5711
+ lastTraversal = now();
5712
+ });
5713
+ }
5714
+ function followsTraversal() {
5715
+ return now() - lastTraversal < TRAVERSAL_WINDOW_MS;
5716
+ }
5717
+ const RELEASES = ["touchstart", "wheel", "keydown", "pointerdown"];
5718
+ function anchorTop(shouldHold = () => true) {
5719
+ let released = false;
5720
+ const top2 = () => {
5721
+ if (released) return;
5722
+ const scrolled2 = window.scrollY || document.documentElement.scrollTop || 0;
5723
+ if (scrolled2 > 0 && shouldHold()) window.scrollTo(0, 0);
5724
+ };
5725
+ const release = () => {
5726
+ if (released) return;
5727
+ released = true;
5728
+ if (frame2 !== null && typeof cancelAnimationFrame === "function") cancelAnimationFrame(frame2);
5729
+ clearTimeout(timer2);
5730
+ window.removeEventListener("scroll", top2);
5731
+ for (const type of RELEASES) window.removeEventListener(type, release);
5732
+ };
5733
+ top2();
5734
+ const frame2 = typeof requestAnimationFrame === "function" ? requestAnimationFrame(top2) : null;
5735
+ const timer2 = setTimeout(release, HOLD_MS);
5736
+ window.addEventListener("scroll", top2, {
5737
+ passive: true
5738
+ });
5739
+ for (const type of RELEASES) window.addEventListener(type, release, {
5740
+ passive: true
5741
+ });
5742
+ return release;
5743
+ }
5744
+ function documentTop(el) {
5745
+ let top2 = 0;
5746
+ for (let node = el; node; node = node.offsetParent) {
5747
+ top2 += node.offsetTop;
5748
+ }
5749
+ return top2;
5750
+ }
5751
+ function beginsOnTheFirstViewport(el) {
5752
+ return documentTop(el) < window.innerHeight;
5753
+ }
5754
+ function useScreenAnchor(ref, enabled = true) {
5755
+ useEffect(() => {
5756
+ const el = ref.current;
5757
+ if (!el || !enabled) return;
5758
+ if (followsTraversal()) return;
5759
+ return anchorTop(() => beginsOnTheFirstViewport(el));
5760
+ }, [ref, enabled]);
5761
+ }
5705
5762
  const ShopSurface = forwardRef(function ShopSurface2({ tone, fill: fill2 = false, children, className, ...rest }, ref) {
5706
5763
  const own = useRef(null);
5764
+ useScreenAnchor(own, fill2);
5707
5765
  useTopOffset(own, "--surface-top", fill2);
5708
5766
  const attach = useCallback((node) => {
5709
5767
  own.current = node;
@@ -8297,11 +8355,19 @@ function Chevron() {
8297
8355
  });
8298
8356
  }
8299
8357
  function OrderListView(props) {
8358
+ const [opened, setOpened] = useState(null);
8359
+ if (opened !== null) {
8360
+ return jsx(ReturnFlowView, {
8361
+ returnRef: opened,
8362
+ onDone: () => setOpened(null)
8363
+ });
8364
+ }
8300
8365
  return jsx(ShopSurface, {
8301
8366
  tone: "light",
8302
8367
  fill: true,
8303
8368
  children: jsx(OrderListContent, {
8304
- ...props
8369
+ ...props,
8370
+ onOpenReturn: props.onOpenReturn ?? setOpened
8305
8371
  })
8306
8372
  });
8307
8373
  }
@@ -10295,14 +10361,14 @@ function formatRemaining(ms) {
10295
10361
  return `${hh}:${mm}:${ss}`;
10296
10362
  }
10297
10363
  function PromoBanner({ promo: promo2 }) {
10298
- const [now, setNow] = useState(null);
10364
+ const [now2, setNow] = useState(null);
10299
10365
  useEffect(() => {
10300
10366
  if (!promo2.ends_at) return;
10301
10367
  setNow(Date.now());
10302
10368
  const id = setInterval(() => setNow(Date.now()), 1e3);
10303
10369
  return () => clearInterval(id);
10304
10370
  }, [promo2.ends_at]);
10305
- const remaining = promo2.ends_at && now !== null ? new Date(promo2.ends_at).getTime() - now : null;
10371
+ const remaining = promo2.ends_at && now2 !== null ? new Date(promo2.ends_at).getTime() - now2 : null;
10306
10372
  const showTimer = remaining !== null && remaining > 0;
10307
10373
  return jsxs("div", {
10308
10374
  className: styles$b.banner,
@@ -11221,8 +11287,11 @@ function useDocumentSnap(ref, enabled = true) {
11221
11287
  root2.style.scrollSnapType = "y mandatory";
11222
11288
  root2.style.scrollPaddingTop = "0px";
11223
11289
  const top2 = () => window.scrollTo(0, 0);
11224
- top2();
11225
- const frame2 = typeof window.requestAnimationFrame === "function" ? window.requestAnimationFrame(top2) : null;
11290
+ const entered = !followsTraversal();
11291
+ if (entered) top2();
11292
+ const frame2 = entered && typeof window.requestAnimationFrame === "function" ? window.requestAnimationFrame(top2) : null;
11293
+ const release = entered ? anchorTop() : () => {
11294
+ };
11226
11295
  const history = window.history;
11227
11296
  const previousRestoration = history && "scrollRestoration" in history ? history.scrollRestoration : null;
11228
11297
  if (previousRestoration !== null) history.scrollRestoration = "manual";
@@ -11231,6 +11300,7 @@ function useDocumentSnap(ref, enabled = true) {
11231
11300
  };
11232
11301
  window.addEventListener("pageshow", onPageShow);
11233
11302
  return () => {
11303
+ release();
11234
11304
  if (frame2 !== null && typeof window.cancelAnimationFrame === "function") {
11235
11305
  window.cancelAnimationFrame(frame2);
11236
11306
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nok-integration/storefront-react",
3
- "version": "0.20.5",
3
+ "version": "0.20.7",
4
4
  "description": "Mountable React storefront components: catalogue, product detail and cart, mounted directly into a host DOM tree. No iframe.",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",