@real-router/angular 0.10.0 → 0.11.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 +23 -2
- package/dist/README.md +23 -2
- package/dist/fesm2022/real-router-angular.mjs +533 -20
- package/dist/fesm2022/real-router-angular.mjs.map +1 -1
- package/dist/types/real-router-angular.d.ts +62 -0
- package/dist/types/real-router-angular.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/dom-utils/index.ts +4 -0
- package/src/dom-utils/scroll-restore.ts +99 -12
- package/src/dom-utils/scroll-spy.ts +688 -0
- package/src/internal/install.ts +15 -2
- package/src/providers.ts +13 -1
- package/src/providersFactory.ts +21 -3
|
@@ -35,6 +35,65 @@ interface ScrollRestorationOptions {
|
|
|
35
35
|
storageKey?: string | undefined;
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
/**
|
|
39
|
+
* Router-coordinated scroll spy (#575).
|
|
40
|
+
*
|
|
41
|
+
* On `IntersectionObserver` notifications the utility picks the topmost
|
|
42
|
+
* visible anchor inside the configured scroll container and emits a forced
|
|
43
|
+
* same-route transition with `{ hash, replace: true, force: true, hashChange:
|
|
44
|
+
* true }` through `router.navigate(...)`. The URL plugin
|
|
45
|
+
* (`@real-router/browser-plugin` or `@real-router/navigation-plugin`) updates
|
|
46
|
+
* `state.context.url.hash` so sibling hash-aware `<Link hash>` re-highlights
|
|
47
|
+
* via the standard `createActiveRouteSource` pipeline.
|
|
48
|
+
*
|
|
49
|
+
* **Anti-flicker gates** (RFC §5.2):
|
|
50
|
+
* 1. `getTransitionSource(router).getSnapshot().isTransitioning` — skip emits
|
|
51
|
+
* while a transition is in-flight (re-entrant lock).
|
|
52
|
+
* 2. `coolingDown` — set on a user-driven hash transition (e.g. `<Link hash>`
|
|
53
|
+
* click + smooth `scrollIntoView`). Cleared on `scrollend` or after a
|
|
54
|
+
* 500ms safety timeout. Spy's own emits are excluded via the synchronous
|
|
55
|
+
* `selfEmitting` flag — required so the spy doesn't rate-limit itself.
|
|
56
|
+
*
|
|
57
|
+
* **Self-healing** (RFC §7.3): if the initial URL contains a hash without a
|
|
58
|
+
* matching `id` (e.g. `/page#nonexistent`), the first IO event emitted right
|
|
59
|
+
* after observe()-ing picks the topmost real anchor and corrects the URL.
|
|
60
|
+
*
|
|
61
|
+
* **Hash-only transition pipeline cost** (RFC §5.3): for same-route same-
|
|
62
|
+
* params hash-only navigations, `getTransitionPath` returns empty
|
|
63
|
+
* `toDeactivate` / `toActivate` arrays, so `runGuards` is a no-op. The only
|
|
64
|
+
* work is the URL plugin's `onTransitionSuccess` write and the
|
|
65
|
+
* `getTransitionSource` flip — cheap.
|
|
66
|
+
*
|
|
67
|
+
* **Architecture**: decomposed into 4 private subsystem closure factories
|
|
68
|
+
* (`createUrlPluginDetector`, `createCooldown`, `createDebouncer`,
|
|
69
|
+
* `createObserverPair`). The main `createScrollSpy` wires them together
|
|
70
|
+
* around the shared `silenced` / `destroyed` / `selfEmitting` flags and the
|
|
71
|
+
* `flush()` emit logic. Each subsystem owns its state + cleanup; `destroy()`
|
|
72
|
+
* delegates to each. See section banners below.
|
|
73
|
+
*
|
|
74
|
+
* @returns A `ScrollSpy` handle whose `destroy()` is idempotent.
|
|
75
|
+
*/
|
|
76
|
+
interface ScrollSpyOptions {
|
|
77
|
+
/**
|
|
78
|
+
* CSS selector for anchor candidates. Empty string `""` or `undefined`
|
|
79
|
+
* disables the spy (returns a NOOP handle). Common values:
|
|
80
|
+
* `"[id]"`, `"[id]:is(h1,h2,h3)"`, `"section[id]"`.
|
|
81
|
+
*/
|
|
82
|
+
selector: string;
|
|
83
|
+
/**
|
|
84
|
+
* `IntersectionObserver` `rootMargin`. Default
|
|
85
|
+
* `"-20% 0px -60% 0px"` — an anchor is considered "active" once it crosses
|
|
86
|
+
* into the top 20 % of the viewport (or scroll container).
|
|
87
|
+
*/
|
|
88
|
+
rootMargin?: string | undefined;
|
|
89
|
+
/**
|
|
90
|
+
* Lazy getter for the scrollable container. Resolved on every event.
|
|
91
|
+
* `null` (or missing getter) falls back to the window viewport
|
|
92
|
+
* (`root: null` on the `IntersectionObserver`).
|
|
93
|
+
*/
|
|
94
|
+
scrollContainer?: (() => HTMLElement | null) | undefined;
|
|
95
|
+
}
|
|
96
|
+
|
|
38
97
|
interface RouteSignals<P extends Params = Params> {
|
|
39
98
|
readonly routeState: Signal<RouteSnapshot<P>>;
|
|
40
99
|
readonly navigator: Navigator;
|
|
@@ -49,6 +108,7 @@ declare const NAVIGATOR: InjectionToken<Navigator>;
|
|
|
49
108
|
declare const ROUTE: InjectionToken<RouteSignals<_real_router_types.Params>>;
|
|
50
109
|
interface RealRouterOptions {
|
|
51
110
|
scrollRestoration?: ScrollRestorationOptions;
|
|
111
|
+
scrollSpy?: ScrollSpyOptions;
|
|
52
112
|
viewTransitions?: boolean;
|
|
53
113
|
}
|
|
54
114
|
declare function provideRealRouter(router: Router, options?: RealRouterOptions): EnvironmentProviders;
|
|
@@ -119,6 +179,8 @@ interface RealRouterFactoryOptions<TDeps extends DefaultDependencies = DefaultDe
|
|
|
119
179
|
deps?: RequestDepsFactory<TDeps>;
|
|
120
180
|
/** Optional scroll restoration — same semantics as `provideRealRouter`. */
|
|
121
181
|
scrollRestoration?: ScrollRestorationOptions;
|
|
182
|
+
/** Optional scroll spy — same semantics as `provideRealRouter`. */
|
|
183
|
+
scrollSpy?: ScrollSpyOptions;
|
|
122
184
|
/** Optional view transitions — same semantics as `provideRealRouter`. */
|
|
123
185
|
viewTransitions?: boolean;
|
|
124
186
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"real-router-angular.d.ts","sources":["../../src/dom-utils/scroll-restore.ts","../../src/types.ts","../../src/providers.ts","../../src/providersFactory.ts","../../src/sourceToSignal.ts","../../src/functions/injectRouter.ts","../../src/functions/injectNavigator.ts","../../src/functions/injectRoute.ts","../../src/functions/injectRouteNode.ts","../../src/functions/injectRouteUtils.ts","../../src/functions/injectRouterTransition.ts","../../src/functions/injectIsActiveRoute.ts","../../src/functions/injectRouteExit.ts","../../src/functions/injectRouteEnter.ts","../../src/directives/RouteMatch.ts","../../src/directives/RouteNotFound.ts","../../src/directives/RouteSelf.ts","../../src/components/RouteView.ts","../../src/components/RouterErrorBoundary.ts","../../src/components/NavigationAnnouncer.ts","../../src/directives/RealLink.ts","../../src/directives/RealLinkActive.ts"],"mappings":";;;;;;;;;
|
|
1
|
+
{"version":3,"file":"real-router-angular.d.ts","sources":["../../src/dom-utils/scroll-restore.ts","../../src/dom-utils/scroll-spy.ts","../../src/types.ts","../../src/providers.ts","../../src/providersFactory.ts","../../src/sourceToSignal.ts","../../src/functions/injectRouter.ts","../../src/functions/injectNavigator.ts","../../src/functions/injectRoute.ts","../../src/functions/injectRouteNode.ts","../../src/functions/injectRouteUtils.ts","../../src/functions/injectRouterTransition.ts","../../src/functions/injectIsActiveRoute.ts","../../src/functions/injectRouteExit.ts","../../src/functions/injectRouteEnter.ts","../../src/directives/RouteMatch.ts","../../src/directives/RouteNotFound.ts","../../src/directives/RouteSelf.ts","../../src/components/RouteView.ts","../../src/components/RouterErrorBoundary.ts","../../src/components/NavigationAnnouncer.ts","../../src/directives/RealLink.ts","../../src/directives/RealLinkActive.ts"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@real-router/angular",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"type": "commonjs",
|
|
5
5
|
"description": "Angular 21 integration for Real-Router",
|
|
6
6
|
"exports": {
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"license": "MIT",
|
|
42
42
|
"sideEffects": false,
|
|
43
43
|
"dependencies": {
|
|
44
|
-
"@real-router/core": "^0.54.
|
|
44
|
+
"@real-router/core": "^0.54.6",
|
|
45
45
|
"@real-router/route-utils": "^0.2.2",
|
|
46
46
|
"@real-router/sources": "^0.8.3"
|
|
47
47
|
},
|
package/src/dom-utils/index.ts
CHANGED
|
@@ -4,6 +4,8 @@ export { createRouteAnnouncer } from "./route-announcer";
|
|
|
4
4
|
|
|
5
5
|
export { createScrollRestoration } from "./scroll-restore";
|
|
6
6
|
|
|
7
|
+
export { createScrollSpy } from "./scroll-spy";
|
|
8
|
+
|
|
7
9
|
export { createViewTransitions } from "./view-transitions";
|
|
8
10
|
|
|
9
11
|
export {
|
|
@@ -22,6 +24,8 @@ export type {
|
|
|
22
24
|
ScrollRestorationMode,
|
|
23
25
|
} from "./scroll-restore";
|
|
24
26
|
|
|
27
|
+
export type { ScrollSpy, ScrollSpyOptions } from "./scroll-spy";
|
|
28
|
+
|
|
25
29
|
export type { DirectionTracker } from "./direction-tracker";
|
|
26
30
|
|
|
27
31
|
export type { ViewTransitions } from "./view-transitions";
|
|
@@ -2,6 +2,14 @@ import type { Router, State } from "@real-router/core";
|
|
|
2
2
|
|
|
3
3
|
const DEFAULT_STORAGE_KEY = "real-router:scroll";
|
|
4
4
|
|
|
5
|
+
// Bounded retry budget for resolving a late-mounting scroll container on the
|
|
6
|
+
// restore path. A per-route container (e.g. an `overflow:auto` div rendered
|
|
7
|
+
// only on one route) can be committed to the DOM a few frames after the
|
|
8
|
+
// navigation settles — heavier routes paint later than the subscribe's rAF.
|
|
9
|
+
// ~10 frames (≈160ms at 60fps) comfortably covers a React commit of a large
|
|
10
|
+
// route without being perceptible. See the doc-block on `restorePos`.
|
|
11
|
+
const RESTORE_RETRY_FRAMES = 10;
|
|
12
|
+
|
|
5
13
|
const NOOP_INSTANCE: { destroy: () => void } = Object.freeze({
|
|
6
14
|
destroy: () => {
|
|
7
15
|
/* no-op */
|
|
@@ -135,6 +143,74 @@ export function createScrollRestoration(
|
|
|
135
143
|
}
|
|
136
144
|
};
|
|
137
145
|
|
|
146
|
+
// Restore path (back / traverse / reload). Unlike `writePos`, this tolerates a
|
|
147
|
+
// scroll container that both MOUNTS and LAYS OUT a few frames AFTER the
|
|
148
|
+
// navigation settles.
|
|
149
|
+
//
|
|
150
|
+
// The capture-side `readPos` always runs against an already-mounted DOM (the
|
|
151
|
+
// route being left). On restore the target route — and its container — is
|
|
152
|
+
// still being committed by the view layer. The subscribe callback schedules a
|
|
153
|
+
// single rAF; for a heavy route (e.g. a long virtual list) the framework's
|
|
154
|
+
// commit can land AFTER that frame. Two distinct failures follow, each losing
|
|
155
|
+
// the saved position (Scenario 6 e2e, reproduced under CI's slower runner):
|
|
156
|
+
//
|
|
157
|
+
// 1. Container not mounted yet → `getContainer()` is `null`, the scroll
|
|
158
|
+
// silently falls back to `window`, which on a container-only route has
|
|
159
|
+
// nothing to scroll.
|
|
160
|
+
// 2. Container mounted but its content not laid out yet → `scrollHeight`
|
|
161
|
+
// is still small, so a single `scrollTo({ top })` clamps short of the
|
|
162
|
+
// saved position and never re-applies once layout grows.
|
|
163
|
+
//
|
|
164
|
+
// With no `scrollContainer` getter the target is always `window`, present
|
|
165
|
+
// from the first frame — restore in a single shot (unchanged behaviour). When
|
|
166
|
+
// a getter is configured we cannot tell "this route legitimately uses window"
|
|
167
|
+
// from "the container is still mounting", so re-apply the scroll on every
|
|
168
|
+
// frame for a bounded budget: window as a fallback while the container is
|
|
169
|
+
// absent (harmless clamp on container routes), the container itself once it
|
|
170
|
+
// appears. For instant restores we stop early the moment the position sticks;
|
|
171
|
+
// smooth restores animate asynchronously, so they run the full budget. The
|
|
172
|
+
// frame budget is the hard backstop against an unreachable target (saved
|
|
173
|
+
// position taller than the restored content).
|
|
174
|
+
const restorePos = (top: number): void => {
|
|
175
|
+
if (!getContainer) {
|
|
176
|
+
globalThis.scrollTo({ top, left: 0, behavior });
|
|
177
|
+
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
let frames = 0;
|
|
182
|
+
|
|
183
|
+
const attempt = (): void => {
|
|
184
|
+
if (destroyed) {
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const element = getContainer();
|
|
189
|
+
|
|
190
|
+
if (element) {
|
|
191
|
+
element.scrollTo({ top, left: 0, behavior });
|
|
192
|
+
|
|
193
|
+
// Instant restore landed within rounding tolerance → done; no point
|
|
194
|
+
// re-applying. Smooth restore never matches synchronously, so let it
|
|
195
|
+
// ride the budget.
|
|
196
|
+
if (behavior !== "smooth" && Math.abs(element.scrollTop - top) <= 1) {
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
} else {
|
|
200
|
+
globalThis.scrollTo({ top, left: 0, behavior });
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (frames >= RESTORE_RETRY_FRAMES) {
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
frames += 1;
|
|
208
|
+
requestAnimationFrame(attempt);
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
attempt();
|
|
212
|
+
};
|
|
213
|
+
|
|
138
214
|
const scrollToHashOrTop = (route: State): void => {
|
|
139
215
|
// URL plugin path (#532): `state.context.url.hash` is the source of truth
|
|
140
216
|
// when one of the URL plugins (browser-plugin / navigation-plugin) is
|
|
@@ -240,20 +316,26 @@ export function createScrollRestoration(
|
|
|
240
316
|
return;
|
|
241
317
|
}
|
|
242
318
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
//
|
|
248
|
-
//
|
|
249
|
-
//
|
|
250
|
-
//
|
|
251
|
-
//
|
|
252
|
-
//
|
|
319
|
+
// Restore branches (reload, back/traverse) MUST be evaluated before the
|
|
320
|
+
// replace-skip below. Since #657 lifted `replace` into TransitionMeta, a
|
|
321
|
+
// history TRAVERSAL (back/forward) under navigation-plugin carries
|
|
322
|
+
// `transition.replace === true` — a traversal reuses an existing history
|
|
323
|
+
// entry, which is replace-shaped at the history level. If the replace-skip
|
|
324
|
+
// ran first it would swallow every back/forward navigation and restore
|
|
325
|
+
// would never fire (the Scenario 6 e2e regression). Genuine in-place
|
|
326
|
+
// replaces (`router.navigate({ replace: true })`, navigateToNotFound) are
|
|
327
|
+
// not traversals and fall through to the skip below.
|
|
328
|
+
//
|
|
329
|
+
// Both arms of each check are required: `transition.reload` only fires for
|
|
330
|
+
// programmatic `router.navigate({reload:true})`. F5 under navigation-plugin
|
|
331
|
+
// primes `nav.navigationType === "reload"` via #531 getActivationType but
|
|
332
|
+
// leaves opts.reload undefined, so dropping the plugin arm would regress F5
|
|
333
|
+
// scroll-restore. Browser-plugin's F5 is not covered (no priming, out of
|
|
334
|
+
// scope).
|
|
253
335
|
if (route.transition.reload || nav?.navigationType === "reload") {
|
|
254
336
|
const key = safeKeyOf(route);
|
|
255
337
|
|
|
256
|
-
|
|
338
|
+
restorePos(key === null ? 0 : (loadStore()[key] ?? 0));
|
|
257
339
|
|
|
258
340
|
return;
|
|
259
341
|
}
|
|
@@ -261,11 +343,16 @@ export function createScrollRestoration(
|
|
|
261
343
|
if (nav?.direction === "back" || nav?.navigationType === "traverse") {
|
|
262
344
|
const key = safeKeyOf(route);
|
|
263
345
|
|
|
264
|
-
|
|
346
|
+
restorePos(key === null ? 0 : (loadStore()[key] ?? 0));
|
|
265
347
|
|
|
266
348
|
return;
|
|
267
349
|
}
|
|
268
350
|
|
|
351
|
+
// Genuine in-place replace (not a traversal) — leave scroll untouched.
|
|
352
|
+
if (route.transition.replace || nav?.navigationType === "replace") {
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
|
|
269
356
|
scrollToHashOrTop(route);
|
|
270
357
|
});
|
|
271
358
|
});
|