@real-router/solid 0.5.1 → 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 +13 -1
- package/dist/cjs/index.d.ts +8 -0
- package/dist/cjs/index.js +166 -0
- package/dist/esm/index.d.mts +8 -0
- package/dist/esm/index.mjs +166 -0
- package/dist/types/RouterProvider.d.ts +2 -0
- package/dist/types/RouterProvider.d.ts.map +1 -1
- package/dist/types/components/Link.d.ts.map +1 -1
- package/dist/types/directives/link.d.ts.map +1 -1
- package/dist/types/dom-utils/index.d.ts +2 -0
- package/dist/types/dom-utils/index.d.ts.map +1 -1
- package/dist/types/dom-utils/scroll-restore.d.ts +11 -0
- package/dist/types/dom-utils/scroll-restore.d.ts.map +1 -0
- package/package.json +4 -4
- package/src/RouterProvider.tsx +15 -1
- package/src/components/Link.tsx +1 -5
- package/src/directives/link.tsx +1 -5
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
|
package/dist/cjs/index.d.ts
CHANGED
|
@@ -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);
|
package/dist/esm/index.d.mts
CHANGED
|
@@ -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/esm/index.mjs
CHANGED
|
@@ -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;
|
|
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":"
|
|
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":"
|
|
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.
|
|
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",
|
|
@@ -51,9 +51,9 @@
|
|
|
51
51
|
"license": "MIT",
|
|
52
52
|
"sideEffects": false,
|
|
53
53
|
"dependencies": {
|
|
54
|
-
"@real-router/core": "^0.
|
|
54
|
+
"@real-router/core": "^0.50.0",
|
|
55
55
|
"@real-router/route-utils": "^0.2.1",
|
|
56
|
-
"@real-router/sources": "^0.7.
|
|
56
|
+
"@real-router/sources": "^0.7.2"
|
|
57
57
|
},
|
|
58
58
|
"devDependencies": {
|
|
59
59
|
"@babel/core": "7.29.0",
|
|
@@ -71,7 +71,7 @@
|
|
|
71
71
|
"solid-js": "1.9.12",
|
|
72
72
|
"vite-plugin-solid": "2.11.11",
|
|
73
73
|
"vitest": "4.1.0",
|
|
74
|
-
"@real-router/browser-plugin": "^0.
|
|
74
|
+
"@real-router/browser-plugin": "^0.14.0"
|
|
75
75
|
},
|
|
76
76
|
"peerDependencies": {
|
|
77
77
|
"solid-js": ">=1.7.0"
|
package/src/RouterProvider.tsx
CHANGED
|
@@ -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
|
|
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);
|
package/src/components/Link.tsx
CHANGED
|
@@ -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";
|
package/src/directives/link.tsx
CHANGED
|
@@ -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";
|