@moku-labs/web 1.12.0 → 1.12.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/dist/browser.mjs CHANGED
@@ -3738,6 +3738,17 @@ async function performNavigation(pathname, handlers, signal) {
3738
3738
  }
3739
3739
  }
3740
3740
  /**
3741
+ * Whether the user has asked the platform to minimise motion.
3742
+ *
3743
+ * @returns `true` when `(prefers-reduced-motion: reduce)` currently matches; `false` when
3744
+ * it does not, or when `matchMedia` is absent (guards SSR/test environments).
3745
+ * @example
3746
+ * const behavior: ScrollBehavior = prefersReducedMotion() ? "instant" : "smooth";
3747
+ */
3748
+ function prefersReducedMotion() {
3749
+ return typeof globalThis.matchMedia === "function" && globalThis.matchMedia("(prefers-reduced-motion: reduce)").matches;
3750
+ }
3751
+ /**
3741
3752
  * Run a DOM-mutating swap, optionally wrapped in the View Transitions API when
3742
3753
  * enabled and supported (instant swap otherwise — never throws).
3743
3754
  *
@@ -3758,7 +3769,7 @@ async function performNavigation(pathname, handlers, signal) {
3758
3769
  * runSwap(() => current.replaceWith(next), true, () => scrollTo({ top: 0, behavior: "instant" }));
3759
3770
  */
3760
3771
  function runSwap(doSwap, viewTransitions, beforeCapture) {
3761
- const reduced = typeof globalThis.matchMedia === "function" && globalThis.matchMedia("(prefers-reduced-motion: reduce)").matches;
3772
+ const reduced = prefersReducedMotion();
3762
3773
  const docWithVt = document;
3763
3774
  const canUseViewTransitions = viewTransitions && !reduced && typeof docWithVt.startViewTransition === "function";
3764
3775
  beforeCapture?.();
@@ -3856,7 +3867,7 @@ function attachHistoryFallback(handlers, navigate = (pathname, _scrollToTop, sig
3856
3867
  if (pathWithSearch(url) === pathWithSearch(location)) {
3857
3868
  window.scrollTo({
3858
3869
  top: 0,
3859
- behavior: "smooth"
3870
+ behavior: prefersReducedMotion() ? "instant" : "smooth"
3860
3871
  });
3861
3872
  return;
3862
3873
  }
@@ -3910,7 +3921,7 @@ function attachNavigationApi(navigation, handlers, navigate = (pathname, _scroll
3910
3921
  navEvent.intercept({ handler: () => {
3911
3922
  window.scrollTo({
3912
3923
  top: 0,
3913
- behavior: "smooth"
3924
+ behavior: prefersReducedMotion() ? "instant" : "smooth"
3914
3925
  });
3915
3926
  return Promise.resolve();
3916
3927
  } });
@@ -4087,26 +4098,30 @@ function createSpaKernel(state, config, emit, deps) {
4087
4098
  let pendingScrollToTop = true;
4088
4099
  /**
4089
4100
  * Apply the in-flight navigation's scroll intent — the swap's `beforeCapture` hook.
4090
- * For a forward nav it scrolls to top BEFORE the snapshot is captured, so the old and
4091
- * new states share scrollY=0 (no delta the sticky header never un-pins) and there is
4092
- * no pre-fetch scroll pause. Traverse (back/forward) sets `pendingScrollToTop = false`
4093
- * and restores its saved position after the swap instead.
4101
+ * For a forward nav it scrolls to top BEFORE the swap (and, with view transitions on,
4102
+ * before the "old" snapshot is captured), so the old and new states share scrollY=0:
4103
+ * no delta for a transition to animate → a `position: sticky` header never un-pins.
4104
+ * Traverse (back/forward) sets `pendingScrollToTop = false` and restores its saved
4105
+ * position after the swap instead.
4094
4106
  *
4095
- * Scroll behaviour: `"instant"` ONLY when view transitions are enabled — that is what
4096
- * keeps scrollY=0 in the captured snapshot (a `scroll-behavior: smooth` would otherwise
4097
- * animate the reset and re-create the delta sticky-header flicker). With view
4098
- * transitions OFF there is no snapshot to protect, so it honours the page's
4099
- * `scroll-behavior` (`"auto"` = use the CSS value, e.g. a smooth scroll-to-top on nav).
4107
+ * The reset is ALWAYS `"instant"`, never the CSS-driven `"auto"`. It runs synchronously
4108
+ * immediately before the swap, and the swap mutates document height (the outgoing page is
4109
+ * usually taller than the incoming one). A smooth scroll from `behavior: "smooth"` or a
4110
+ * page `scroll-behavior: smooth` that `"auto"` would inherit is still animating when that
4111
+ * height change lands; the browser clamps scrollY to the new, smaller maximum and cancels
4112
+ * the in-flight animation there (worst on WebKit), stranding the page near the OLD position
4113
+ * instead of the top. Instant lands scrollY=0 before the swap, every time. (A smooth
4114
+ * scroll-to-top on the SAME page is unaffected — the router's same-page handler animates
4115
+ * it, where there is no swap to race.)
4100
4116
  *
4101
4117
  * @example
4102
4118
  * runSwap(renderAndMount, viewTransitions, applyPendingScroll);
4103
4119
  */
4104
4120
  const applyPendingScroll = () => {
4105
4121
  if (!pendingScrollToTop) return;
4106
- const behavior = resolved.viewTransitions ? "instant" : "auto";
4107
4122
  window.scrollTo({
4108
4123
  top: 0,
4109
- behavior
4124
+ behavior: "instant"
4110
4125
  });
4111
4126
  };
4112
4127
  /**
package/dist/index.cjs CHANGED
@@ -10639,6 +10639,17 @@ async function performNavigation(pathname, handlers, signal) {
10639
10639
  }
10640
10640
  }
10641
10641
  /**
10642
+ * Whether the user has asked the platform to minimise motion.
10643
+ *
10644
+ * @returns `true` when `(prefers-reduced-motion: reduce)` currently matches; `false` when
10645
+ * it does not, or when `matchMedia` is absent (guards SSR/test environments).
10646
+ * @example
10647
+ * const behavior: ScrollBehavior = prefersReducedMotion() ? "instant" : "smooth";
10648
+ */
10649
+ function prefersReducedMotion() {
10650
+ return typeof globalThis.matchMedia === "function" && globalThis.matchMedia("(prefers-reduced-motion: reduce)").matches;
10651
+ }
10652
+ /**
10642
10653
  * Run a DOM-mutating swap, optionally wrapped in the View Transitions API when
10643
10654
  * enabled and supported (instant swap otherwise — never throws).
10644
10655
  *
@@ -10659,7 +10670,7 @@ async function performNavigation(pathname, handlers, signal) {
10659
10670
  * runSwap(() => current.replaceWith(next), true, () => scrollTo({ top: 0, behavior: "instant" }));
10660
10671
  */
10661
10672
  function runSwap(doSwap, viewTransitions, beforeCapture) {
10662
- const reduced = typeof globalThis.matchMedia === "function" && globalThis.matchMedia("(prefers-reduced-motion: reduce)").matches;
10673
+ const reduced = prefersReducedMotion();
10663
10674
  const docWithVt = document;
10664
10675
  const canUseViewTransitions = viewTransitions && !reduced && typeof docWithVt.startViewTransition === "function";
10665
10676
  beforeCapture?.();
@@ -10757,7 +10768,7 @@ function attachHistoryFallback(handlers, navigate = (pathname, _scrollToTop, sig
10757
10768
  if (pathWithSearch(url) === pathWithSearch(location)) {
10758
10769
  window.scrollTo({
10759
10770
  top: 0,
10760
- behavior: "smooth"
10771
+ behavior: prefersReducedMotion() ? "instant" : "smooth"
10761
10772
  });
10762
10773
  return;
10763
10774
  }
@@ -10811,7 +10822,7 @@ function attachNavigationApi(navigation, handlers, navigate = (pathname, _scroll
10811
10822
  navEvent.intercept({ handler: () => {
10812
10823
  window.scrollTo({
10813
10824
  top: 0,
10814
- behavior: "smooth"
10825
+ behavior: prefersReducedMotion() ? "instant" : "smooth"
10815
10826
  });
10816
10827
  return Promise.resolve();
10817
10828
  } });
@@ -10988,26 +10999,30 @@ function createSpaKernel(state, config, emit, deps) {
10988
10999
  let pendingScrollToTop = true;
10989
11000
  /**
10990
11001
  * Apply the in-flight navigation's scroll intent — the swap's `beforeCapture` hook.
10991
- * For a forward nav it scrolls to top BEFORE the snapshot is captured, so the old and
10992
- * new states share scrollY=0 (no delta the sticky header never un-pins) and there is
10993
- * no pre-fetch scroll pause. Traverse (back/forward) sets `pendingScrollToTop = false`
10994
- * and restores its saved position after the swap instead.
11002
+ * For a forward nav it scrolls to top BEFORE the swap (and, with view transitions on,
11003
+ * before the "old" snapshot is captured), so the old and new states share scrollY=0:
11004
+ * no delta for a transition to animate → a `position: sticky` header never un-pins.
11005
+ * Traverse (back/forward) sets `pendingScrollToTop = false` and restores its saved
11006
+ * position after the swap instead.
10995
11007
  *
10996
- * Scroll behaviour: `"instant"` ONLY when view transitions are enabled — that is what
10997
- * keeps scrollY=0 in the captured snapshot (a `scroll-behavior: smooth` would otherwise
10998
- * animate the reset and re-create the delta sticky-header flicker). With view
10999
- * transitions OFF there is no snapshot to protect, so it honours the page's
11000
- * `scroll-behavior` (`"auto"` = use the CSS value, e.g. a smooth scroll-to-top on nav).
11008
+ * The reset is ALWAYS `"instant"`, never the CSS-driven `"auto"`. It runs synchronously
11009
+ * immediately before the swap, and the swap mutates document height (the outgoing page is
11010
+ * usually taller than the incoming one). A smooth scroll from `behavior: "smooth"` or a
11011
+ * page `scroll-behavior: smooth` that `"auto"` would inherit is still animating when that
11012
+ * height change lands; the browser clamps scrollY to the new, smaller maximum and cancels
11013
+ * the in-flight animation there (worst on WebKit), stranding the page near the OLD position
11014
+ * instead of the top. Instant lands scrollY=0 before the swap, every time. (A smooth
11015
+ * scroll-to-top on the SAME page is unaffected — the router's same-page handler animates
11016
+ * it, where there is no swap to race.)
11001
11017
  *
11002
11018
  * @example
11003
11019
  * runSwap(renderAndMount, viewTransitions, applyPendingScroll);
11004
11020
  */
11005
11021
  const applyPendingScroll = () => {
11006
11022
  if (!pendingScrollToTop) return;
11007
- const behavior = resolved.viewTransitions ? "instant" : "auto";
11008
11023
  window.scrollTo({
11009
11024
  top: 0,
11010
- behavior
11025
+ behavior: "instant"
11011
11026
  });
11012
11027
  };
11013
11028
  /**
package/dist/index.mjs CHANGED
@@ -10626,6 +10626,17 @@ async function performNavigation(pathname, handlers, signal) {
10626
10626
  }
10627
10627
  }
10628
10628
  /**
10629
+ * Whether the user has asked the platform to minimise motion.
10630
+ *
10631
+ * @returns `true` when `(prefers-reduced-motion: reduce)` currently matches; `false` when
10632
+ * it does not, or when `matchMedia` is absent (guards SSR/test environments).
10633
+ * @example
10634
+ * const behavior: ScrollBehavior = prefersReducedMotion() ? "instant" : "smooth";
10635
+ */
10636
+ function prefersReducedMotion() {
10637
+ return typeof globalThis.matchMedia === "function" && globalThis.matchMedia("(prefers-reduced-motion: reduce)").matches;
10638
+ }
10639
+ /**
10629
10640
  * Run a DOM-mutating swap, optionally wrapped in the View Transitions API when
10630
10641
  * enabled and supported (instant swap otherwise — never throws).
10631
10642
  *
@@ -10646,7 +10657,7 @@ async function performNavigation(pathname, handlers, signal) {
10646
10657
  * runSwap(() => current.replaceWith(next), true, () => scrollTo({ top: 0, behavior: "instant" }));
10647
10658
  */
10648
10659
  function runSwap(doSwap, viewTransitions, beforeCapture) {
10649
- const reduced = typeof globalThis.matchMedia === "function" && globalThis.matchMedia("(prefers-reduced-motion: reduce)").matches;
10660
+ const reduced = prefersReducedMotion();
10650
10661
  const docWithVt = document;
10651
10662
  const canUseViewTransitions = viewTransitions && !reduced && typeof docWithVt.startViewTransition === "function";
10652
10663
  beforeCapture?.();
@@ -10744,7 +10755,7 @@ function attachHistoryFallback(handlers, navigate = (pathname, _scrollToTop, sig
10744
10755
  if (pathWithSearch(url) === pathWithSearch(location)) {
10745
10756
  window.scrollTo({
10746
10757
  top: 0,
10747
- behavior: "smooth"
10758
+ behavior: prefersReducedMotion() ? "instant" : "smooth"
10748
10759
  });
10749
10760
  return;
10750
10761
  }
@@ -10798,7 +10809,7 @@ function attachNavigationApi(navigation, handlers, navigate = (pathname, _scroll
10798
10809
  navEvent.intercept({ handler: () => {
10799
10810
  window.scrollTo({
10800
10811
  top: 0,
10801
- behavior: "smooth"
10812
+ behavior: prefersReducedMotion() ? "instant" : "smooth"
10802
10813
  });
10803
10814
  return Promise.resolve();
10804
10815
  } });
@@ -10975,26 +10986,30 @@ function createSpaKernel(state, config, emit, deps) {
10975
10986
  let pendingScrollToTop = true;
10976
10987
  /**
10977
10988
  * Apply the in-flight navigation's scroll intent — the swap's `beforeCapture` hook.
10978
- * For a forward nav it scrolls to top BEFORE the snapshot is captured, so the old and
10979
- * new states share scrollY=0 (no delta the sticky header never un-pins) and there is
10980
- * no pre-fetch scroll pause. Traverse (back/forward) sets `pendingScrollToTop = false`
10981
- * and restores its saved position after the swap instead.
10989
+ * For a forward nav it scrolls to top BEFORE the swap (and, with view transitions on,
10990
+ * before the "old" snapshot is captured), so the old and new states share scrollY=0:
10991
+ * no delta for a transition to animate → a `position: sticky` header never un-pins.
10992
+ * Traverse (back/forward) sets `pendingScrollToTop = false` and restores its saved
10993
+ * position after the swap instead.
10982
10994
  *
10983
- * Scroll behaviour: `"instant"` ONLY when view transitions are enabled — that is what
10984
- * keeps scrollY=0 in the captured snapshot (a `scroll-behavior: smooth` would otherwise
10985
- * animate the reset and re-create the delta sticky-header flicker). With view
10986
- * transitions OFF there is no snapshot to protect, so it honours the page's
10987
- * `scroll-behavior` (`"auto"` = use the CSS value, e.g. a smooth scroll-to-top on nav).
10995
+ * The reset is ALWAYS `"instant"`, never the CSS-driven `"auto"`. It runs synchronously
10996
+ * immediately before the swap, and the swap mutates document height (the outgoing page is
10997
+ * usually taller than the incoming one). A smooth scroll from `behavior: "smooth"` or a
10998
+ * page `scroll-behavior: smooth` that `"auto"` would inherit is still animating when that
10999
+ * height change lands; the browser clamps scrollY to the new, smaller maximum and cancels
11000
+ * the in-flight animation there (worst on WebKit), stranding the page near the OLD position
11001
+ * instead of the top. Instant lands scrollY=0 before the swap, every time. (A smooth
11002
+ * scroll-to-top on the SAME page is unaffected — the router's same-page handler animates
11003
+ * it, where there is no swap to race.)
10988
11004
  *
10989
11005
  * @example
10990
11006
  * runSwap(renderAndMount, viewTransitions, applyPendingScroll);
10991
11007
  */
10992
11008
  const applyPendingScroll = () => {
10993
11009
  if (!pendingScrollToTop) return;
10994
- const behavior = resolved.viewTransitions ? "instant" : "auto";
10995
11010
  window.scrollTo({
10996
11011
  top: 0,
10997
- behavior
11012
+ behavior: "instant"
10998
11013
  });
10999
11014
  };
11000
11015
  /**
package/package.json CHANGED
@@ -125,5 +125,5 @@
125
125
  "test:build-e2e": "bun test src/plugins/build/__tests__/e2e/",
126
126
  "test:coverage": "vitest run --project unit --project integration --coverage"
127
127
  },
128
- "version": "1.12.0"
128
+ "version": "1.12.1"
129
129
  }