@real-router/svelte 0.9.0 → 0.10.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/README.md CHANGED
@@ -425,7 +425,7 @@ Opt-in preservation of scroll position across navigations:
425
425
  </RouterProvider>
426
426
  ```
427
427
 
428
- Restores scroll on back/forward, scrolls to top (or `#hash`) on push. Three modes: `"restore"` (default), `"top"`, `"manual"`. Custom containers via `scrollContainer: () => HTMLElement | null`. Lifecycle tied to the provider — created on mount, destroyed on unmount. See [Scroll Restoration guide](https://github.com/greydragon888/real-router/wiki/Scroll-Restoration) for details.
428
+ Restores scroll on back/forward, scrolls to top (or `#hash`) on push. Three modes: `"restore"` (default), `"top"`, `"native"`. Custom containers via `scrollContainer: () => HTMLElement | null`. Lifecycle tied to the provider — created on mount, destroyed on unmount. See [Scroll Restoration guide](https://github.com/greydragon888/real-router/wiki/Scroll-Restoration) for details.
429
429
 
430
430
  ## View Transitions
431
431
 
@@ -42,16 +42,23 @@
42
42
  const srEnabled = $derived(scrollRestoration !== undefined);
43
43
  const srMode = $derived(scrollRestoration?.mode);
44
44
  const srAnchor = $derived(scrollRestoration?.anchorScrolling);
45
+ const srBehavior = $derived(scrollRestoration?.behavior);
46
+ const srStorageKey = $derived(scrollRestoration?.storageKey);
45
47
 
46
48
  $effect(() => {
47
49
  if (!srEnabled) return;
48
- // scrollContainer is a function ref that naturally changes each render.
49
- // Read it via `untrack` so this $effect does NOT depend on the parent
50
- // `scrollRestoration` signal. Without this, a new inline options object
51
- // would re-run the effect regardless of the primitive $derived memos.
50
+ // Read scrollRestoration object props via `untrack` for non-primitive
51
+ // refs that naturally change each render. Primitive $derived memos
52
+ // (mode/anchor/behavior/storageKey) drive re-runs.
53
+ void srMode;
54
+ void srAnchor;
55
+ void srBehavior;
56
+ void srStorageKey;
52
57
  const sr = createScrollRestoration(router, {
53
58
  mode: srMode,
54
59
  anchorScrolling: srAnchor,
60
+ behavior: srBehavior,
61
+ storageKey: srStorageKey,
55
62
  scrollContainer: untrack(() => scrollRestoration?.scrollContainer),
56
63
  });
57
64
  return () => sr.destroy();
@@ -1,9 +1,30 @@
1
1
  import type { Router } from "@real-router/core";
2
- export type ScrollRestorationMode = "restore" | "top" | "manual";
2
+ export type ScrollRestorationMode = "restore" | "top" | "native";
3
3
  export interface ScrollRestorationOptions {
4
4
  mode?: ScrollRestorationMode | undefined;
5
5
  anchorScrolling?: boolean | undefined;
6
6
  scrollContainer?: (() => HTMLElement | null) | undefined;
7
+ /**
8
+ * Scroll behavior passed to `scrollTo({ behavior })` and
9
+ * `scrollIntoView({ behavior })`.
10
+ *
11
+ * - `"auto"` (default) — browser-defined, usually instant.
12
+ * - `"instant"` — explicit instant jump (no animation).
13
+ * - `"smooth"` — animated transition. Note: smooth restore on back/traverse
14
+ * can feel disorienting if the user expects to land at the saved position
15
+ * immediately. Recommended for `mode: "top"` or anchor scroll only.
16
+ *
17
+ * See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/ScrollToOptions/behavior).
18
+ */
19
+ behavior?: ScrollBehavior | undefined;
20
+ /**
21
+ * sessionStorage key used to persist saved scroll positions. Default:
22
+ * `"real-router:scroll"`. Override only when multiple independent
23
+ * `RouterProvider` instances share the same document and you need to
24
+ * isolate their scroll stores (e.g. micro-frontends, embedded widgets,
25
+ * or testing). For a single app with one provider the default is fine.
26
+ */
27
+ storageKey?: string | undefined;
7
28
  }
8
29
  export declare function createScrollRestoration(router: Router, options?: ScrollRestorationOptions): {
9
30
  destroy: () => void;
@@ -1,4 +1,4 @@
1
- const STORAGE_KEY = "real-router:scroll";
1
+ const DEFAULT_STORAGE_KEY = "real-router:scroll";
2
2
  const NOOP_INSTANCE = Object.freeze({
3
3
  destroy: () => {
4
4
  /* no-op */
@@ -9,14 +9,38 @@ export function createScrollRestoration(router, options) {
9
9
  return NOOP_INSTANCE;
10
10
  }
11
11
  const mode = options?.mode ?? "restore";
12
- // mode "manual" = utility does nothing. Don't flip history.scrollRestoration,
13
- // don't subscribe, don't register pagehide — leave the browser's native
14
- // auto-restore intact for the app to override if it wants to.
15
- if (mode === "manual") {
12
+ // mode "native" = utility does nothing. Don't flip history.scrollRestoration,
13
+ // don't subscribe, don't register pagehide — `history.scrollRestoration`
14
+ // stays at the browser default ("auto") so the browser handles scroll
15
+ // restore natively. (Note: this is the OPPOSITE of `history.scrollRestoration
16
+ // === "manual"` — utility's "native" leaves the DOM property at "auto" so
17
+ // the browser is in charge.)
18
+ if (mode === "native") {
16
19
  return NOOP_INSTANCE;
17
20
  }
18
21
  const anchorEnabled = options?.anchorScrolling ?? true;
19
22
  const getContainer = options?.scrollContainer;
23
+ const behavior = options?.behavior ?? "auto";
24
+ const storageKey = options?.storageKey ?? DEFAULT_STORAGE_KEY;
25
+ const loadStore = () => {
26
+ try {
27
+ const raw = sessionStorage.getItem(storageKey);
28
+ return raw ? JSON.parse(raw) : {};
29
+ }
30
+ catch {
31
+ return {};
32
+ }
33
+ };
34
+ const putPos = (key, pos) => {
35
+ try {
36
+ const store = loadStore();
37
+ store[key] = pos;
38
+ sessionStorage.setItem(storageKey, JSON.stringify(store));
39
+ }
40
+ catch {
41
+ // Ignore quota / security errors.
42
+ }
43
+ };
20
44
  const prevScrollRestoration = history.scrollRestoration;
21
45
  try {
22
46
  history.scrollRestoration = "manual";
@@ -34,10 +58,10 @@ export function createScrollRestoration(router, options) {
34
58
  const writePos = (top) => {
35
59
  const element = getContainer?.();
36
60
  if (element) {
37
- element.scrollTop = top;
61
+ element.scrollTo({ top, left: 0, behavior });
38
62
  }
39
63
  else {
40
- globalThis.scrollTo(0, top);
64
+ globalThis.scrollTo({ top, left: 0, behavior });
41
65
  }
42
66
  };
43
67
  const scrollToHashOrTop = (route) => {
@@ -52,7 +76,7 @@ export function createScrollRestoration(router, options) {
52
76
  // eslint-disable-next-line unicorn/prefer-query-selector -- ids may contain CSS-unsafe chars
53
77
  const element = document.getElementById(ctxHash);
54
78
  if (element) {
55
- element.scrollIntoView();
79
+ element.scrollIntoView({ behavior });
56
80
  return;
57
81
  }
58
82
  }
@@ -75,7 +99,7 @@ export function createScrollRestoration(router, options) {
75
99
  // eslint-disable-next-line unicorn/prefer-query-selector -- ids may contain CSS-unsafe chars
76
100
  const element = document.getElementById(id);
77
101
  if (element) {
78
- element.scrollIntoView();
102
+ element.scrollIntoView({ behavior });
79
103
  return;
80
104
  }
81
105
  }
@@ -140,25 +164,6 @@ export function createScrollRestoration(router, options) {
140
164
  function keyOf(state) {
141
165
  return `${state.name}:${canonicalJson(state.params)}`;
142
166
  }
143
- function loadStore() {
144
- try {
145
- const raw = sessionStorage.getItem(STORAGE_KEY);
146
- return raw ? JSON.parse(raw) : {};
147
- }
148
- catch {
149
- return {};
150
- }
151
- }
152
- function putPos(key, pos) {
153
- try {
154
- const store = loadStore();
155
- store[key] = pos;
156
- sessionStorage.setItem(STORAGE_KEY, JSON.stringify(store));
157
- }
158
- catch {
159
- // Ignore quota / security errors.
160
- }
161
- }
162
167
  function canonicalJson(value) {
163
168
  return JSON.stringify(value, canonicalReplacer);
164
169
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@real-router/svelte",
3
- "version": "0.9.0",
3
+ "version": "0.10.1",
4
4
  "type": "module",
5
5
  "description": "Svelte 5 integration for Real-Router",
6
6
  "svelte": "./dist/index.js",
@@ -44,9 +44,9 @@
44
44
  "license": "MIT",
45
45
  "sideEffects": false,
46
46
  "dependencies": {
47
- "@real-router/core": "^0.51.0",
47
+ "@real-router/core": "^0.52.0",
48
48
  "@real-router/route-utils": "^0.2.2",
49
- "@real-router/sources": "^0.8.0"
49
+ "@real-router/sources": "^0.8.1"
50
50
  },
51
51
  "devDependencies": {
52
52
  "@sveltejs/package": "2.5.7",
@@ -58,7 +58,7 @@
58
58
  "svelte": "5.54.0",
59
59
  "svelte-check": "4.4.5",
60
60
  "svelte-eslint-parser": "1.6.0",
61
- "@real-router/browser-plugin": "^0.17.0"
61
+ "@real-router/browser-plugin": "^0.17.1"
62
62
  },
63
63
  "peerDependencies": {
64
64
  "svelte": ">=5.7.0"
@@ -42,16 +42,23 @@
42
42
  const srEnabled = $derived(scrollRestoration !== undefined);
43
43
  const srMode = $derived(scrollRestoration?.mode);
44
44
  const srAnchor = $derived(scrollRestoration?.anchorScrolling);
45
+ const srBehavior = $derived(scrollRestoration?.behavior);
46
+ const srStorageKey = $derived(scrollRestoration?.storageKey);
45
47
 
46
48
  $effect(() => {
47
49
  if (!srEnabled) return;
48
- // scrollContainer is a function ref that naturally changes each render.
49
- // Read it via `untrack` so this $effect does NOT depend on the parent
50
- // `scrollRestoration` signal. Without this, a new inline options object
51
- // would re-run the effect regardless of the primitive $derived memos.
50
+ // Read scrollRestoration object props via `untrack` for non-primitive
51
+ // refs that naturally change each render. Primitive $derived memos
52
+ // (mode/anchor/behavior/storageKey) drive re-runs.
53
+ void srMode;
54
+ void srAnchor;
55
+ void srBehavior;
56
+ void srStorageKey;
52
57
  const sr = createScrollRestoration(router, {
53
58
  mode: srMode,
54
59
  anchorScrolling: srAnchor,
60
+ behavior: srBehavior,
61
+ storageKey: srStorageKey,
55
62
  scrollContainer: untrack(() => scrollRestoration?.scrollContainer),
56
63
  });
57
64
  return () => sr.destroy();