@nok-integration/storefront-react 0.20.5 → 0.20.6

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,63 @@ function useTopOffset(ref, property, enabled = true) {
5702
5702
  };
5703
5703
  }, [ref, property, enabled]);
5704
5704
  }
5705
+ const HOLD_MS = 1e3;
5706
+ const RELEASES = ["touchstart", "wheel", "keydown", "pointerdown"];
5707
+ function anchorTop(shouldHold = () => true) {
5708
+ let released = false;
5709
+ const top2 = () => {
5710
+ if (released) return;
5711
+ const scrolled2 = window.scrollY || document.documentElement.scrollTop || 0;
5712
+ if (scrolled2 > 0 && shouldHold()) window.scrollTo(0, 0);
5713
+ };
5714
+ const release = () => {
5715
+ if (released) return;
5716
+ released = true;
5717
+ if (frame2 !== null && typeof cancelAnimationFrame === "function") cancelAnimationFrame(frame2);
5718
+ clearTimeout(timer2);
5719
+ window.removeEventListener("scroll", top2);
5720
+ for (const type of RELEASES) window.removeEventListener(type, release);
5721
+ };
5722
+ top2();
5723
+ const frame2 = typeof requestAnimationFrame === "function" ? requestAnimationFrame(top2) : null;
5724
+ const timer2 = setTimeout(release, HOLD_MS);
5725
+ window.addEventListener("scroll", top2, {
5726
+ passive: true
5727
+ });
5728
+ for (const type of RELEASES) window.addEventListener(type, release, {
5729
+ passive: true
5730
+ });
5731
+ return release;
5732
+ }
5733
+ const TRAVERSAL_WINDOW_MS = 1e3;
5734
+ const now = () => typeof performance !== "undefined" ? performance.now() : Date.now();
5735
+ let lastTraversal = -Infinity;
5736
+ if (typeof window !== "undefined") {
5737
+ window.addEventListener("popstate", () => {
5738
+ lastTraversal = now();
5739
+ });
5740
+ }
5741
+ function documentTop(el) {
5742
+ let top2 = 0;
5743
+ for (let node = el; node; node = node.offsetParent) {
5744
+ top2 += node.offsetTop;
5745
+ }
5746
+ return top2;
5747
+ }
5748
+ function beginsOnTheFirstViewport(el) {
5749
+ return documentTop(el) < window.innerHeight;
5750
+ }
5751
+ function useScreenAnchor(ref, enabled = true) {
5752
+ useEffect(() => {
5753
+ const el = ref.current;
5754
+ if (!el || !enabled) return;
5755
+ if (now() - lastTraversal < TRAVERSAL_WINDOW_MS) return;
5756
+ return anchorTop(() => beginsOnTheFirstViewport(el));
5757
+ }, [ref, enabled]);
5758
+ }
5705
5759
  const ShopSurface = forwardRef(function ShopSurface2({ tone, fill: fill2 = false, children, className, ...rest }, ref) {
5706
5760
  const own = useRef(null);
5761
+ useScreenAnchor(own, fill2);
5707
5762
  useTopOffset(own, "--surface-top", fill2);
5708
5763
  const attach = useCallback((node) => {
5709
5764
  own.current = node;
@@ -8297,11 +8352,19 @@ function Chevron() {
8297
8352
  });
8298
8353
  }
8299
8354
  function OrderListView(props) {
8355
+ const [opened, setOpened] = useState(null);
8356
+ if (opened !== null) {
8357
+ return jsx(ReturnFlowView, {
8358
+ returnRef: opened,
8359
+ onDone: () => setOpened(null)
8360
+ });
8361
+ }
8300
8362
  return jsx(ShopSurface, {
8301
8363
  tone: "light",
8302
8364
  fill: true,
8303
8365
  children: jsx(OrderListContent, {
8304
- ...props
8366
+ ...props,
8367
+ onOpenReturn: props.onOpenReturn ?? setOpened
8305
8368
  })
8306
8369
  });
8307
8370
  }
@@ -10295,14 +10358,14 @@ function formatRemaining(ms) {
10295
10358
  return `${hh}:${mm}:${ss}`;
10296
10359
  }
10297
10360
  function PromoBanner({ promo: promo2 }) {
10298
- const [now, setNow] = useState(null);
10361
+ const [now2, setNow] = useState(null);
10299
10362
  useEffect(() => {
10300
10363
  if (!promo2.ends_at) return;
10301
10364
  setNow(Date.now());
10302
10365
  const id = setInterval(() => setNow(Date.now()), 1e3);
10303
10366
  return () => clearInterval(id);
10304
10367
  }, [promo2.ends_at]);
10305
- const remaining = promo2.ends_at && now !== null ? new Date(promo2.ends_at).getTime() - now : null;
10368
+ const remaining = promo2.ends_at && now2 !== null ? new Date(promo2.ends_at).getTime() - now2 : null;
10306
10369
  const showTimer = remaining !== null && remaining > 0;
10307
10370
  return jsxs("div", {
10308
10371
  className: styles$b.banner,
@@ -11223,6 +11286,7 @@ function useDocumentSnap(ref, enabled = true) {
11223
11286
  const top2 = () => window.scrollTo(0, 0);
11224
11287
  top2();
11225
11288
  const frame2 = typeof window.requestAnimationFrame === "function" ? window.requestAnimationFrame(top2) : null;
11289
+ const release = anchorTop();
11226
11290
  const history = window.history;
11227
11291
  const previousRestoration = history && "scrollRestoration" in history ? history.scrollRestoration : null;
11228
11292
  if (previousRestoration !== null) history.scrollRestoration = "manual";
@@ -11231,6 +11295,7 @@ function useDocumentSnap(ref, enabled = true) {
11231
11295
  };
11232
11296
  window.addEventListener("pageshow", onPageShow);
11233
11297
  return () => {
11298
+ release();
11234
11299
  if (frame2 !== null && typeof window.cancelAnimationFrame === "function") {
11235
11300
  window.cancelAnimationFrame(frame2);
11236
11301
  }
@@ -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,63 @@ function useTopOffset(ref, property, enabled = true) {
5702
5702
  };
5703
5703
  }, [ref, property, enabled]);
5704
5704
  }
5705
+ const HOLD_MS = 1e3;
5706
+ const RELEASES = ["touchstart", "wheel", "keydown", "pointerdown"];
5707
+ function anchorTop(shouldHold = () => true) {
5708
+ let released = false;
5709
+ const top2 = () => {
5710
+ if (released) return;
5711
+ const scrolled2 = window.scrollY || document.documentElement.scrollTop || 0;
5712
+ if (scrolled2 > 0 && shouldHold()) window.scrollTo(0, 0);
5713
+ };
5714
+ const release = () => {
5715
+ if (released) return;
5716
+ released = true;
5717
+ if (frame2 !== null && typeof cancelAnimationFrame === "function") cancelAnimationFrame(frame2);
5718
+ clearTimeout(timer2);
5719
+ window.removeEventListener("scroll", top2);
5720
+ for (const type of RELEASES) window.removeEventListener(type, release);
5721
+ };
5722
+ top2();
5723
+ const frame2 = typeof requestAnimationFrame === "function" ? requestAnimationFrame(top2) : null;
5724
+ const timer2 = setTimeout(release, HOLD_MS);
5725
+ window.addEventListener("scroll", top2, {
5726
+ passive: true
5727
+ });
5728
+ for (const type of RELEASES) window.addEventListener(type, release, {
5729
+ passive: true
5730
+ });
5731
+ return release;
5732
+ }
5733
+ const TRAVERSAL_WINDOW_MS = 1e3;
5734
+ const now = () => typeof performance !== "undefined" ? performance.now() : Date.now();
5735
+ let lastTraversal = -Infinity;
5736
+ if (typeof window !== "undefined") {
5737
+ window.addEventListener("popstate", () => {
5738
+ lastTraversal = now();
5739
+ });
5740
+ }
5741
+ function documentTop(el) {
5742
+ let top2 = 0;
5743
+ for (let node = el; node; node = node.offsetParent) {
5744
+ top2 += node.offsetTop;
5745
+ }
5746
+ return top2;
5747
+ }
5748
+ function beginsOnTheFirstViewport(el) {
5749
+ return documentTop(el) < window.innerHeight;
5750
+ }
5751
+ function useScreenAnchor(ref, enabled = true) {
5752
+ useEffect(() => {
5753
+ const el = ref.current;
5754
+ if (!el || !enabled) return;
5755
+ if (now() - lastTraversal < TRAVERSAL_WINDOW_MS) return;
5756
+ return anchorTop(() => beginsOnTheFirstViewport(el));
5757
+ }, [ref, enabled]);
5758
+ }
5705
5759
  const ShopSurface = forwardRef(function ShopSurface2({ tone, fill: fill2 = false, children, className, ...rest }, ref) {
5706
5760
  const own = useRef(null);
5761
+ useScreenAnchor(own, fill2);
5707
5762
  useTopOffset(own, "--surface-top", fill2);
5708
5763
  const attach = useCallback((node) => {
5709
5764
  own.current = node;
@@ -8297,11 +8352,19 @@ function Chevron() {
8297
8352
  });
8298
8353
  }
8299
8354
  function OrderListView(props) {
8355
+ const [opened, setOpened] = useState(null);
8356
+ if (opened !== null) {
8357
+ return jsx(ReturnFlowView, {
8358
+ returnRef: opened,
8359
+ onDone: () => setOpened(null)
8360
+ });
8361
+ }
8300
8362
  return jsx(ShopSurface, {
8301
8363
  tone: "light",
8302
8364
  fill: true,
8303
8365
  children: jsx(OrderListContent, {
8304
- ...props
8366
+ ...props,
8367
+ onOpenReturn: props.onOpenReturn ?? setOpened
8305
8368
  })
8306
8369
  });
8307
8370
  }
@@ -10295,14 +10358,14 @@ function formatRemaining(ms) {
10295
10358
  return `${hh}:${mm}:${ss}`;
10296
10359
  }
10297
10360
  function PromoBanner({ promo: promo2 }) {
10298
- const [now, setNow] = useState(null);
10361
+ const [now2, setNow] = useState(null);
10299
10362
  useEffect(() => {
10300
10363
  if (!promo2.ends_at) return;
10301
10364
  setNow(Date.now());
10302
10365
  const id = setInterval(() => setNow(Date.now()), 1e3);
10303
10366
  return () => clearInterval(id);
10304
10367
  }, [promo2.ends_at]);
10305
- const remaining = promo2.ends_at && now !== null ? new Date(promo2.ends_at).getTime() - now : null;
10368
+ const remaining = promo2.ends_at && now2 !== null ? new Date(promo2.ends_at).getTime() - now2 : null;
10306
10369
  const showTimer = remaining !== null && remaining > 0;
10307
10370
  return jsxs("div", {
10308
10371
  className: styles$b.banner,
@@ -11223,6 +11286,7 @@ function useDocumentSnap(ref, enabled = true) {
11223
11286
  const top2 = () => window.scrollTo(0, 0);
11224
11287
  top2();
11225
11288
  const frame2 = typeof window.requestAnimationFrame === "function" ? window.requestAnimationFrame(top2) : null;
11289
+ const release = anchorTop();
11226
11290
  const history = window.history;
11227
11291
  const previousRestoration = history && "scrollRestoration" in history ? history.scrollRestoration : null;
11228
11292
  if (previousRestoration !== null) history.scrollRestoration = "manual";
@@ -11231,6 +11295,7 @@ function useDocumentSnap(ref, enabled = true) {
11231
11295
  };
11232
11296
  window.addEventListener("pageshow", onPageShow);
11233
11297
  return () => {
11298
+ release();
11234
11299
  if (frame2 !== null && typeof window.cancelAnimationFrame === "function") {
11235
11300
  window.cancelAnimationFrame(frame2);
11236
11301
  }
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.6",
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",