@real-router/solid 0.5.2 → 0.6.0

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
@@ -320,11 +320,23 @@ Enable screen reader announcements for route changes:
320
320
 
321
321
  When enabled, a visually hidden `aria-live` region announces each navigation. Focus moves to the first `<h1>` on the new page. See [Accessibility guide](https://github.com/greydragon888/real-router/wiki/Accessibility) for details.
322
322
 
323
+ ## Scroll Restoration
324
+
325
+ Opt-in preservation of scroll position across navigations:
326
+
327
+ ```tsx
328
+ <RouterProvider router={router} scrollRestoration={{ mode: "restore" }}>
329
+ {/* Your app */}
330
+ </RouterProvider>
331
+ ```
332
+
333
+ Restores scroll on back/forward, scrolls to top (or `#hash`) on push. Three modes: `"restore"` (default), `"top"`, `"manual"`. Custom containers via `scrollContainer: () => HTMLElement | null`. Options are read once on mount — changing the prop at runtime does not reconfigure the utility (Solid `onMount` is non-reactive). See [Scroll Restoration guide](https://github.com/greydragon888/real-router/wiki/Scroll-Restoration) for details.
334
+
323
335
  ## Documentation
324
336
 
325
337
  Full documentation: [Wiki](https://github.com/greydragon888/real-router/wiki)
326
338
 
327
- - [RouterProvider](https://github.com/greydragon888/real-router/wiki/RouterProvider) · [RouteView](https://github.com/greydragon888/real-router/wiki/RouteView) · [RouterErrorBoundary](https://github.com/greydragon888/real-router/wiki/RouterErrorBoundary) · [Link](https://github.com/greydragon888/real-router/wiki/Link)
339
+ - [RouterProvider](https://github.com/greydragon888/real-router/wiki/RouterProvider) · [RouteView](https://github.com/greydragon888/real-router/wiki/RouteView) · [RouterErrorBoundary](https://github.com/greydragon888/real-router/wiki/RouterErrorBoundary) · [Link](https://github.com/greydragon888/real-router/wiki/Link) · [Scroll Restoration](https://github.com/greydragon888/real-router/wiki/Scroll-Restoration)
328
340
  - [useRouter](https://github.com/greydragon888/real-router/wiki/useRouter) · [useRoute](https://github.com/greydragon888/real-router/wiki/useRoute) · [useRouteNode](https://github.com/greydragon888/real-router/wiki/useRouteNode) · [useNavigator](https://github.com/greydragon888/real-router/wiki/useNavigator) · [useRouteUtils](https://github.com/greydragon888/real-router/wiki/useRouteUtils) · [useRouterTransition](https://github.com/greydragon888/real-router/wiki/useRouterTransition)
329
341
 
330
342
  ## Examples
@@ -89,9 +89,17 @@ declare function useRouteNodeStore(nodeName: string): RouteState;
89
89
 
90
90
  declare function useRouterTransition(): Accessor<RouterTransitionSnapshot>;
91
91
 
92
+ type ScrollRestorationMode = "restore" | "top" | "manual";
93
+ interface ScrollRestorationOptions {
94
+ mode?: ScrollRestorationMode | undefined;
95
+ anchorScrolling?: boolean | undefined;
96
+ scrollContainer?: (() => HTMLElement | null) | undefined;
97
+ }
98
+
92
99
  interface RouteProviderProps {
93
100
  router: Router;
94
101
  announceNavigation?: boolean;
102
+ scrollRestoration?: ScrollRestorationOptions;
95
103
  }
96
104
  declare function RouterProvider(props: ParentProps<RouteProviderProps>): JSX.Element;
97
105
 
package/dist/cjs/index.js CHANGED
@@ -284,6 +284,163 @@ function manageFocus(h1) {
284
284
  });
285
285
  }
286
286
 
287
+ const STORAGE_KEY = "real-router:scroll";
288
+ const NOOP_INSTANCE = Object.freeze({
289
+ destroy: () => {
290
+ /* no-op */
291
+ }
292
+ });
293
+ function createScrollRestoration(router, options) {
294
+ if (typeof globalThis.window === "undefined") {
295
+ return NOOP_INSTANCE;
296
+ }
297
+ const mode = options?.mode ?? "restore";
298
+
299
+ // mode "manual" = utility does nothing. Don't flip history.scrollRestoration,
300
+ // don't subscribe, don't register pagehide — leave the browser's native
301
+ // auto-restore intact for the app to override if it wants to.
302
+ if (mode === "manual") {
303
+ return NOOP_INSTANCE;
304
+ }
305
+ const anchorEnabled = options?.anchorScrolling ?? true;
306
+ const getContainer = options?.scrollContainer;
307
+ const prevScrollRestoration = history.scrollRestoration;
308
+ try {
309
+ history.scrollRestoration = "manual";
310
+ } catch {
311
+ // Ignore — some embedded contexts may reject the assignment.
312
+ }
313
+
314
+ // Resolve the container lazily on every event so containers mounted AFTER
315
+ // the provider still get correct scroll handling. Falls back to window when
316
+ // the getter is absent or returns null (pre-mount).
317
+ const readPos = () => {
318
+ const element = getContainer?.();
319
+ return element ? element.scrollTop : globalThis.scrollY;
320
+ };
321
+ const writePos = top => {
322
+ const element = getContainer?.();
323
+ if (element) {
324
+ element.scrollTop = top;
325
+ } else {
326
+ globalThis.scrollTo(0, top);
327
+ }
328
+ };
329
+ const scrollToHashOrTop = () => {
330
+ const hash = globalThis.location.hash;
331
+ if (anchorEnabled && hash.length > 1) {
332
+ // location.hash is percent-encoded; ids in the DOM are the raw string.
333
+ // Decode for the match. Fall back to the raw slice if the hash contains
334
+ // a malformed escape sequence (decodeURIComponent throws on those).
335
+ let id;
336
+ try {
337
+ id = decodeURIComponent(hash.slice(1));
338
+ } catch {
339
+ id = hash.slice(1);
340
+ }
341
+
342
+ // eslint-disable-next-line unicorn/prefer-query-selector -- ids may contain CSS-unsafe chars
343
+ const element = document.getElementById(id);
344
+ if (element) {
345
+ element.scrollIntoView();
346
+ return;
347
+ }
348
+ }
349
+ writePos(0);
350
+ };
351
+ let destroyed = false;
352
+ const unsubscribe = router.subscribe(({
353
+ route,
354
+ previousRoute
355
+ }) => {
356
+ const nav = route.context.navigation;
357
+
358
+ // Browsers dispatch reload as the initial navigation after refresh, so
359
+ // previousRoute is undefined and capture is naturally skipped. The
360
+ // pre-refresh position was already persisted via pagehide.
361
+ if (previousRoute) {
362
+ putPos(keyOf(previousRoute), readPos());
363
+ }
364
+
365
+ // Single rAF so DOM is committed before we read anchors / write scroll.
366
+ // Guard against destroy() racing with the callback.
367
+ requestAnimationFrame(() => {
368
+ if (destroyed) {
369
+ return;
370
+ }
371
+ if (mode === "top" || !nav) {
372
+ scrollToHashOrTop();
373
+ return;
374
+ }
375
+ if (nav.navigationType === "replace") {
376
+ return;
377
+ }
378
+ if (nav.direction === "back" || nav.navigationType === "traverse" || nav.navigationType === "reload") {
379
+ writePos(loadStore()[keyOf(route)] ?? 0);
380
+ return;
381
+ }
382
+ scrollToHashOrTop();
383
+ });
384
+ });
385
+ const onPageHide = () => {
386
+ const current = router.getState();
387
+ if (current) {
388
+ putPos(keyOf(current), readPos());
389
+ }
390
+ };
391
+ globalThis.addEventListener("pagehide", onPageHide);
392
+ return {
393
+ destroy: () => {
394
+ if (destroyed) {
395
+ return;
396
+ }
397
+ destroyed = true;
398
+ unsubscribe();
399
+ globalThis.removeEventListener("pagehide", onPageHide);
400
+ try {
401
+ history.scrollRestoration = prevScrollRestoration;
402
+ } catch {
403
+ // Ignore.
404
+ }
405
+ }
406
+ };
407
+ }
408
+ function keyOf(state) {
409
+ return `${state.name}:${canonicalJson(state.params)}`;
410
+ }
411
+ function loadStore() {
412
+ try {
413
+ const raw = sessionStorage.getItem(STORAGE_KEY);
414
+ return raw ? JSON.parse(raw) : {};
415
+ } catch {
416
+ return {};
417
+ }
418
+ }
419
+ function putPos(key, pos) {
420
+ try {
421
+ const store = loadStore();
422
+ store[key] = pos;
423
+ sessionStorage.setItem(STORAGE_KEY, JSON.stringify(store));
424
+ } catch {
425
+ // Ignore quota / security errors.
426
+ }
427
+ }
428
+ function canonicalJson(value) {
429
+ return JSON.stringify(value, canonicalReplacer);
430
+ }
431
+ function canonicalReplacer(_key, val) {
432
+ if (val !== null && typeof val === "object" && !Array.isArray(val)) {
433
+ const sorted = {};
434
+ // eslint-disable-next-line unicorn/no-array-sort -- ng-packagr uses pre-ES2023 lib; toSorted unavailable
435
+ const keys = Object.keys(val).sort((left, right) => left.localeCompare(right));
436
+ for (const key of keys) {
437
+ sorted[key] = val[key];
438
+ }
439
+ return sorted;
440
+ }
441
+ return val;
442
+ }
443
+
287
444
  function shouldNavigate(evt) {
288
445
  return evt.button === 0 && !evt.metaKey && !evt.altKey && !evt.ctrlKey && !evt.shiftKey;
289
446
  }
@@ -514,6 +671,15 @@ function RouterProvider(props) {
514
671
  announcer.destroy();
515
672
  });
516
673
  });
674
+ solidJs.onMount(() => {
675
+ if (!props.scrollRestoration) {
676
+ return;
677
+ }
678
+ const sr = createScrollRestoration(props.router, props.scrollRestoration);
679
+ solidJs.onCleanup(() => {
680
+ sr.destroy();
681
+ });
682
+ });
517
683
  const navigator = core.getNavigator(props.router);
518
684
  const routeSource = sources.createRouteSource(props.router);
519
685
  const routeSignal = createSignalFromSource(routeSource);
@@ -89,9 +89,17 @@ declare function useRouteNodeStore(nodeName: string): RouteState;
89
89
 
90
90
  declare function useRouterTransition(): Accessor<RouterTransitionSnapshot>;
91
91
 
92
+ type ScrollRestorationMode = "restore" | "top" | "manual";
93
+ interface ScrollRestorationOptions {
94
+ mode?: ScrollRestorationMode | undefined;
95
+ anchorScrolling?: boolean | undefined;
96
+ scrollContainer?: (() => HTMLElement | null) | undefined;
97
+ }
98
+
92
99
  interface RouteProviderProps {
93
100
  router: Router;
94
101
  announceNavigation?: boolean;
102
+ scrollRestoration?: ScrollRestorationOptions;
95
103
  }
96
104
  declare function RouterProvider(props: ParentProps<RouteProviderProps>): JSX.Element;
97
105
 
@@ -282,6 +282,163 @@ function manageFocus(h1) {
282
282
  });
283
283
  }
284
284
 
285
+ const STORAGE_KEY = "real-router:scroll";
286
+ const NOOP_INSTANCE = Object.freeze({
287
+ destroy: () => {
288
+ /* no-op */
289
+ }
290
+ });
291
+ function createScrollRestoration(router, options) {
292
+ if (typeof globalThis.window === "undefined") {
293
+ return NOOP_INSTANCE;
294
+ }
295
+ const mode = options?.mode ?? "restore";
296
+
297
+ // mode "manual" = utility does nothing. Don't flip history.scrollRestoration,
298
+ // don't subscribe, don't register pagehide — leave the browser's native
299
+ // auto-restore intact for the app to override if it wants to.
300
+ if (mode === "manual") {
301
+ return NOOP_INSTANCE;
302
+ }
303
+ const anchorEnabled = options?.anchorScrolling ?? true;
304
+ const getContainer = options?.scrollContainer;
305
+ const prevScrollRestoration = history.scrollRestoration;
306
+ try {
307
+ history.scrollRestoration = "manual";
308
+ } catch {
309
+ // Ignore — some embedded contexts may reject the assignment.
310
+ }
311
+
312
+ // Resolve the container lazily on every event so containers mounted AFTER
313
+ // the provider still get correct scroll handling. Falls back to window when
314
+ // the getter is absent or returns null (pre-mount).
315
+ const readPos = () => {
316
+ const element = getContainer?.();
317
+ return element ? element.scrollTop : globalThis.scrollY;
318
+ };
319
+ const writePos = top => {
320
+ const element = getContainer?.();
321
+ if (element) {
322
+ element.scrollTop = top;
323
+ } else {
324
+ globalThis.scrollTo(0, top);
325
+ }
326
+ };
327
+ const scrollToHashOrTop = () => {
328
+ const hash = globalThis.location.hash;
329
+ if (anchorEnabled && hash.length > 1) {
330
+ // location.hash is percent-encoded; ids in the DOM are the raw string.
331
+ // Decode for the match. Fall back to the raw slice if the hash contains
332
+ // a malformed escape sequence (decodeURIComponent throws on those).
333
+ let id;
334
+ try {
335
+ id = decodeURIComponent(hash.slice(1));
336
+ } catch {
337
+ id = hash.slice(1);
338
+ }
339
+
340
+ // eslint-disable-next-line unicorn/prefer-query-selector -- ids may contain CSS-unsafe chars
341
+ const element = document.getElementById(id);
342
+ if (element) {
343
+ element.scrollIntoView();
344
+ return;
345
+ }
346
+ }
347
+ writePos(0);
348
+ };
349
+ let destroyed = false;
350
+ const unsubscribe = router.subscribe(({
351
+ route,
352
+ previousRoute
353
+ }) => {
354
+ const nav = route.context.navigation;
355
+
356
+ // Browsers dispatch reload as the initial navigation after refresh, so
357
+ // previousRoute is undefined and capture is naturally skipped. The
358
+ // pre-refresh position was already persisted via pagehide.
359
+ if (previousRoute) {
360
+ putPos(keyOf(previousRoute), readPos());
361
+ }
362
+
363
+ // Single rAF so DOM is committed before we read anchors / write scroll.
364
+ // Guard against destroy() racing with the callback.
365
+ requestAnimationFrame(() => {
366
+ if (destroyed) {
367
+ return;
368
+ }
369
+ if (mode === "top" || !nav) {
370
+ scrollToHashOrTop();
371
+ return;
372
+ }
373
+ if (nav.navigationType === "replace") {
374
+ return;
375
+ }
376
+ if (nav.direction === "back" || nav.navigationType === "traverse" || nav.navigationType === "reload") {
377
+ writePos(loadStore()[keyOf(route)] ?? 0);
378
+ return;
379
+ }
380
+ scrollToHashOrTop();
381
+ });
382
+ });
383
+ const onPageHide = () => {
384
+ const current = router.getState();
385
+ if (current) {
386
+ putPos(keyOf(current), readPos());
387
+ }
388
+ };
389
+ globalThis.addEventListener("pagehide", onPageHide);
390
+ return {
391
+ destroy: () => {
392
+ if (destroyed) {
393
+ return;
394
+ }
395
+ destroyed = true;
396
+ unsubscribe();
397
+ globalThis.removeEventListener("pagehide", onPageHide);
398
+ try {
399
+ history.scrollRestoration = prevScrollRestoration;
400
+ } catch {
401
+ // Ignore.
402
+ }
403
+ }
404
+ };
405
+ }
406
+ function keyOf(state) {
407
+ return `${state.name}:${canonicalJson(state.params)}`;
408
+ }
409
+ function loadStore() {
410
+ try {
411
+ const raw = sessionStorage.getItem(STORAGE_KEY);
412
+ return raw ? JSON.parse(raw) : {};
413
+ } catch {
414
+ return {};
415
+ }
416
+ }
417
+ function putPos(key, pos) {
418
+ try {
419
+ const store = loadStore();
420
+ store[key] = pos;
421
+ sessionStorage.setItem(STORAGE_KEY, JSON.stringify(store));
422
+ } catch {
423
+ // Ignore quota / security errors.
424
+ }
425
+ }
426
+ function canonicalJson(value) {
427
+ return JSON.stringify(value, canonicalReplacer);
428
+ }
429
+ function canonicalReplacer(_key, val) {
430
+ if (val !== null && typeof val === "object" && !Array.isArray(val)) {
431
+ const sorted = {};
432
+ // eslint-disable-next-line unicorn/no-array-sort -- ng-packagr uses pre-ES2023 lib; toSorted unavailable
433
+ const keys = Object.keys(val).sort((left, right) => left.localeCompare(right));
434
+ for (const key of keys) {
435
+ sorted[key] = val[key];
436
+ }
437
+ return sorted;
438
+ }
439
+ return val;
440
+ }
441
+
285
442
  function shouldNavigate(evt) {
286
443
  return evt.button === 0 && !evt.metaKey && !evt.altKey && !evt.ctrlKey && !evt.shiftKey;
287
444
  }
@@ -512,6 +669,15 @@ function RouterProvider(props) {
512
669
  announcer.destroy();
513
670
  });
514
671
  });
672
+ onMount(() => {
673
+ if (!props.scrollRestoration) {
674
+ return;
675
+ }
676
+ const sr = createScrollRestoration(props.router, props.scrollRestoration);
677
+ onCleanup(() => {
678
+ sr.destroy();
679
+ });
680
+ });
515
681
  const navigator = getNavigator(props.router);
516
682
  const routeSource = createRouteSource(props.router);
517
683
  const routeSignal = createSignalFromSource(routeSource);
@@ -1,8 +1,10 @@
1
+ import type { ScrollRestorationOptions } from "./dom-utils";
1
2
  import type { Router } from "@real-router/core";
2
3
  import type { ParentProps, JSX } from "solid-js";
3
4
  export interface RouteProviderProps {
4
5
  router: Router;
5
6
  announceNavigation?: boolean;
7
+ scrollRestoration?: ScrollRestorationOptions;
6
8
  }
7
9
  export declare function isRouteActive(linkRouteName: string, currentRouteName: string): boolean;
8
10
  export declare function RouterProvider(props: ParentProps<RouteProviderProps>): JSX.Element;
@@ -1 +1 @@
1
- {"version":3,"file":"RouterProvider.d.ts","sourceRoot":"","sources":["../../src/RouterProvider.tsx"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,KAAK,EAAE,WAAW,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AAEjD,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,kBAAkB,CAAC,EAAE,OAAO,CAAC;CAC9B;AAED,wBAAgB,aAAa,CAC3B,aAAa,EAAE,MAAM,EACrB,gBAAgB,EAAE,MAAM,GACvB,OAAO,CAKT;AAED,wBAAgB,cAAc,CAC5B,KAAK,EAAE,WAAW,CAAC,kBAAkB,CAAC,GACrC,GAAG,CAAC,OAAO,CA+Bb"}
1
+ {"version":3,"file":"RouterProvider.d.ts","sourceRoot":"","sources":["../../src/RouterProvider.tsx"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,aAAa,CAAC;AAC5D,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,KAAK,EAAE,WAAW,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AAEjD,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,iBAAiB,CAAC,EAAE,wBAAwB,CAAC;CAC9C;AAED,wBAAgB,aAAa,CAC3B,aAAa,EAAE,MAAM,EACrB,gBAAgB,EAAE,MAAM,GACvB,OAAO,CAKT;AAED,wBAAgB,cAAc,CAC5B,KAAK,EAAE,WAAW,CAAC,kBAAkB,CAAC,GACrC,GAAG,CAAC,OAAO,CA2Cb"}
@@ -1 +1 @@
1
- {"version":3,"file":"Link.d.ts","sourceRoot":"","sources":["../../../src/components/Link.tsx"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAC1C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AAEpC,wBAAgB,IAAI,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM,EAC5C,KAAK,EAAE,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,GAC5B,GAAG,CAAC,OAAO,CAoFb"}
1
+ {"version":3,"file":"Link.d.ts","sourceRoot":"","sources":["../../../src/components/Link.tsx"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAC1C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AAEpC,wBAAgB,IAAI,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM,EAC5C,KAAK,EAAE,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,GAC5B,GAAG,CAAC,OAAO,CAoFb"}
@@ -1 +1 @@
1
- {"version":3,"file":"link.d.ts","sourceRoot":"","sources":["../../../src/directives/link.tsx"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAEhD,MAAM,WAAW,oBAAoB,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM;IAC7D,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,CAAC,CAAC;IAChB,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACvC,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,iBAAiB,CAAC,EAAE,OAAO,CAAC;CAC7B;AAED,wBAAgB,IAAI,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM,EAC5C,OAAO,EAAE,WAAW,EACpB,QAAQ,EAAE,MAAM,oBAAoB,CAAC,CAAC,CAAC,GACtC,IAAI,CA+DN"}
1
+ {"version":3,"file":"link.d.ts","sourceRoot":"","sources":["../../../src/directives/link.tsx"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAEhD,MAAM,WAAW,oBAAoB,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM;IAC7D,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,CAAC,CAAC;IAChB,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACvC,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,iBAAiB,CAAC,EAAE,OAAO,CAAC;CAC7B;AAED,wBAAgB,IAAI,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM,EAC5C,OAAO,EAAE,WAAW,EACpB,QAAQ,EAAE,MAAM,oBAAoB,CAAC,CAAC,CAAC,GACtC,IAAI,CA+DN"}
@@ -1,4 +1,6 @@
1
1
  export { createRouteAnnouncer } from "./route-announcer.js";
2
+ export { createScrollRestoration } from "./scroll-restore.js";
2
3
  export { shouldNavigate, buildHref, buildActiveClassName, shallowEqual, applyLinkA11y, } from "./link-utils.js";
3
4
  export type { RouteAnnouncerOptions } from "./route-announcer.js";
5
+ export type { ScrollRestorationOptions, ScrollRestorationMode, } from "./scroll-restore.js";
4
6
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/dom-utils/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAE5D,OAAO,EACL,cAAc,EACd,SAAS,EACT,oBAAoB,EACpB,YAAY,EACZ,aAAa,GACd,MAAM,iBAAiB,CAAC;AAEzB,YAAY,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/dom-utils/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAE5D,OAAO,EAAE,uBAAuB,EAAE,MAAM,qBAAqB,CAAC;AAE9D,OAAO,EACL,cAAc,EACd,SAAS,EACT,oBAAoB,EACpB,YAAY,EACZ,aAAa,GACd,MAAM,iBAAiB,CAAC;AAEzB,YAAY,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAElE,YAAY,EACV,wBAAwB,EACxB,qBAAqB,GACtB,MAAM,qBAAqB,CAAC"}
@@ -0,0 +1,11 @@
1
+ import type { Router } from "@real-router/core";
2
+ export type ScrollRestorationMode = "restore" | "top" | "manual";
3
+ export interface ScrollRestorationOptions {
4
+ mode?: ScrollRestorationMode | undefined;
5
+ anchorScrolling?: boolean | undefined;
6
+ scrollContainer?: (() => HTMLElement | null) | undefined;
7
+ }
8
+ export declare function createScrollRestoration(router: Router, options?: ScrollRestorationOptions): {
9
+ destroy: () => void;
10
+ };
11
+ //# sourceMappingURL=scroll-restore.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scroll-restore.d.ts","sourceRoot":"","sources":["../../../src/dom-utils/scroll-restore.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAS,MAAM,mBAAmB,CAAC;AAUvD,MAAM,MAAM,qBAAqB,GAAG,SAAS,GAAG,KAAK,GAAG,QAAQ,CAAC;AAEjE,MAAM,WAAW,wBAAwB;IACvC,IAAI,CAAC,EAAE,qBAAqB,GAAG,SAAS,CAAC;IACzC,eAAe,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IACtC,eAAe,CAAC,EAAE,CAAC,MAAM,WAAW,GAAG,IAAI,CAAC,GAAG,SAAS,CAAC;CAC1D;AAOD,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,MAAM,EACd,OAAO,CAAC,EAAE,wBAAwB,GACjC;IAAE,OAAO,EAAE,MAAM,IAAI,CAAA;CAAE,CA+IzB"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@real-router/solid",
3
- "version": "0.5.2",
3
+ "version": "0.6.0",
4
4
  "type": "commonjs",
5
5
  "description": "Solid.js integration for Real-Router",
6
6
  "main": "./dist/cjs/index.js",
@@ -4,14 +4,16 @@ import { createSelector, onCleanup, onMount } from "solid-js";
4
4
 
5
5
  import { RouterContext, RouteContext } from "./context";
6
6
  import { createSignalFromSource } from "./createSignalFromSource";
7
- import { createRouteAnnouncer } from "./dom-utils/index.js";
7
+ import { createRouteAnnouncer, createScrollRestoration } from "./dom-utils";
8
8
 
9
+ import type { ScrollRestorationOptions } from "./dom-utils";
9
10
  import type { Router } from "@real-router/core";
10
11
  import type { ParentProps, JSX } from "solid-js";
11
12
 
12
13
  export interface RouteProviderProps {
13
14
  router: Router;
14
15
  announceNavigation?: boolean;
16
+ scrollRestoration?: ScrollRestorationOptions;
15
17
  }
16
18
 
17
19
  export function isRouteActive(
@@ -39,6 +41,18 @@ export function RouterProvider(
39
41
  });
40
42
  });
41
43
 
44
+ onMount(() => {
45
+ if (!props.scrollRestoration) {
46
+ return;
47
+ }
48
+
49
+ const sr = createScrollRestoration(props.router, props.scrollRestoration);
50
+
51
+ onCleanup(() => {
52
+ sr.destroy();
53
+ });
54
+ });
55
+
42
56
  const navigator = getNavigator(props.router);
43
57
  const routeSource = createRouteSource(props.router);
44
58
  const routeSignal = createSignalFromSource(routeSource);
@@ -4,11 +4,7 @@ import { createMemo, mergeProps, splitProps, useContext } from "solid-js";
4
4
  import { EMPTY_PARAMS, EMPTY_OPTIONS } from "../constants";
5
5
  import { RouterContext } from "../context";
6
6
  import { createSignalFromSource } from "../createSignalFromSource";
7
- import {
8
- shouldNavigate,
9
- buildHref,
10
- buildActiveClassName,
11
- } from "../dom-utils/index.js";
7
+ import { shouldNavigate, buildHref, buildActiveClassName } from "../dom-utils";
12
8
 
13
9
  import type { LinkProps } from "../types";
14
10
  import type { Params } from "@real-router/core";
@@ -3,11 +3,7 @@ import { createEffect, onCleanup } from "solid-js";
3
3
 
4
4
  import { EMPTY_PARAMS, EMPTY_OPTIONS } from "../constants";
5
5
  import { createSignalFromSource } from "../createSignalFromSource";
6
- import {
7
- shouldNavigate,
8
- applyLinkA11y,
9
- buildHref,
10
- } from "../dom-utils/index.js";
6
+ import { shouldNavigate, applyLinkA11y, buildHref } from "../dom-utils";
11
7
  import { useRouter } from "../hooks/useRouter";
12
8
 
13
9
  import type { Params } from "@real-router/core";