@real-router/navigation-plugin 0.7.5 → 0.7.7

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.
@@ -1,214 +0,0 @@
1
- import { extractPathFromAbsoluteUrl } from "./browser-env";
2
-
3
- import type { NavigationBrowser } from "./types";
4
- import type { State } from "@real-router/core";
5
- import type { PluginApi } from "@real-router/core/api";
6
-
7
- /**
8
- * Validates a candidate history entry for `traverseToLast(routeName)` and
9
- * returns both the entry (now known non-null) and the matched router state.
10
- * Extracted from `NavigationPlugin` so the three error branches (missing
11
- * entry, null url, unmatched url) can be tested directly without vi.spyOn
12
- * on module namespaces — the star-import spy pattern is fragile under ESM
13
- * and was working by accident in history-extensions.test.ts.
14
- *
15
- * Throws a descriptive Error on any failure; the caller (NavigationPlugin)
16
- * propagates it as the rejection of `traverseToLast`.
17
- */
18
- export function resolveEntryToMatchedState(
19
- entry: NavigationHistoryEntry | undefined,
20
- routeName: string,
21
- api: PluginApi,
22
- base: string,
23
- ): { entry: NavigationHistoryEntry; entryUrl: string; matchedState: State } {
24
- if (!entry) {
25
- throw new Error(`No history entry for route "${routeName}"`);
26
- }
27
-
28
- const entryUrl = entry.url;
29
-
30
- if (!entryUrl) {
31
- throw new Error(`No matching route for entry URL "${entryUrl}"`);
32
- }
33
-
34
- const path = extractPathFromAbsoluteUrl(entryUrl, base);
35
- const matchedState = api.matchPath(path);
36
-
37
- if (!matchedState) {
38
- throw new Error(`No matching route for entry URL "${entryUrl}"`);
39
- }
40
-
41
- // entryUrl is returned alongside `entry` so callers can read the validated
42
- // URL without re-doing the null check — TypeScript cannot narrow a property
43
- // access through a control-flow guard on `entry.url`.
44
- return { entry, entryUrl, matchedState };
45
- }
46
-
47
- /**
48
- * Converts a NavigationHistoryEntry to a State via URL matching.
49
- * Uses URL matching (not entry.getState()) because:
50
- * - Entries before plugin init have no state
51
- * - Entries after router.replace(routes) may have stale state
52
- * - Entries from other SPAs on the same origin have foreign state
53
- */
54
- export function entryToState(
55
- entry: NavigationHistoryEntry | undefined,
56
- api: PluginApi,
57
- base: string,
58
- ): State | undefined {
59
- if (!entry?.url) {
60
- return undefined;
61
- }
62
-
63
- return (
64
- api.matchPath(extractPathFromAbsoluteUrl(entry.url, base)) ?? undefined
65
- );
66
- }
67
-
68
- function peekAt(
69
- browser: NavigationBrowser,
70
- api: PluginApi,
71
- base: string,
72
- offset: number,
73
- ): State | undefined {
74
- const idx = browser.currentEntry?.index;
75
-
76
- if (idx == null) {
77
- return undefined;
78
- }
79
-
80
- return entryToState(browser.entries()[idx + offset], api, base);
81
- }
82
-
83
- export function peekBack(
84
- browser: NavigationBrowser,
85
- api: PluginApi,
86
- base: string,
87
- ): State | undefined {
88
- return peekAt(browser, api, base, -1);
89
- }
90
-
91
- export function peekForward(
92
- browser: NavigationBrowser,
93
- api: PluginApi,
94
- base: string,
95
- ): State | undefined {
96
- return peekAt(browser, api, base, 1);
97
- }
98
-
99
- export function hasVisited(
100
- browser: NavigationBrowser,
101
- api: PluginApi,
102
- base: string,
103
- routeName: string,
104
- ): boolean {
105
- return browser.entries().some((entry) => {
106
- const state = entryToState(entry, api, base);
107
-
108
- return state?.name === routeName;
109
- });
110
- }
111
-
112
- export function getVisitedRoutes(
113
- browser: NavigationBrowser,
114
- api: PluginApi,
115
- base: string,
116
- ): string[] {
117
- const names = new Set<string>();
118
-
119
- for (const entry of browser.entries()) {
120
- const state = entryToState(entry, api, base);
121
-
122
- if (state) {
123
- names.add(state.name);
124
- }
125
- }
126
-
127
- return [...names];
128
- }
129
-
130
- export function getRouteVisitCount(
131
- browser: NavigationBrowser,
132
- api: PluginApi,
133
- base: string,
134
- routeName: string,
135
- ): number {
136
- let count = 0;
137
-
138
- for (const entry of browser.entries()) {
139
- if (entryToState(entry, api, base)?.name === routeName) {
140
- count++;
141
- }
142
- }
143
-
144
- return count;
145
- }
146
-
147
- /**
148
- * Finds the last NavigationHistoryEntry matching the given route name,
149
- * excluding the current entry (to avoid SAME_STATES on traverseToLast("current-route")).
150
- */
151
- export function findLastEntryForRoute(
152
- entries: NavigationHistoryEntry[],
153
- routeName: string,
154
- api: PluginApi,
155
- base: string,
156
- currentKey: string | undefined,
157
- ): NavigationHistoryEntry | undefined {
158
- for (let i = entries.length - 1; i >= 0; i--) {
159
- const entry = entries[i];
160
-
161
- if (entry.key === currentKey) {
162
- continue;
163
- }
164
-
165
- const state = entryToState(entry, api, base);
166
-
167
- if (state?.name === routeName) {
168
- return entry;
169
- }
170
- }
171
-
172
- return undefined;
173
- }
174
-
175
- export function canGoBack(browser: NavigationBrowser): boolean {
176
- const idx = browser.currentEntry?.index;
177
-
178
- return idx != null && idx > 0;
179
- }
180
-
181
- export function canGoForward(browser: NavigationBrowser): boolean {
182
- const idx = browser.currentEntry?.index;
183
-
184
- if (idx == null) {
185
- return false;
186
- }
187
-
188
- return idx < browser.entries().length - 1;
189
- }
190
-
191
- export function canGoBackTo(
192
- browser: NavigationBrowser,
193
- api: PluginApi,
194
- base: string,
195
- routeName: string,
196
- ): boolean {
197
- const idx = browser.currentEntry?.index;
198
-
199
- if (idx == null) {
200
- return false;
201
- }
202
-
203
- const entries = browser.entries();
204
-
205
- for (let i = idx - 1; i >= 0; i--) {
206
- const state = entryToState(entries[i], api, base);
207
-
208
- if (state?.name === routeName) {
209
- return true;
210
- }
211
- }
212
-
213
- return false;
214
- }
package/src/href-utils.ts DELETED
@@ -1,65 +0,0 @@
1
- /**
2
- * Pure URL-comparison helper extracted from `plugin.ts` so it can be unit and
3
- * property-tested in isolation. See INVARIANTS.md section K for the formal
4
- * properties this function satisfies.
5
- *
6
- * Sole production call site: the same-URL guard in
7
- * `NavigationPlugin.onTransitionSuccess` (#580).
8
- */
9
-
10
- /**
11
- * Returns `true` when resolving `target` (a relative or absolute URL) against
12
- * `currentHref` identifies the same logical document — same protocol, host,
13
- * pathname (with empty pathname normalised to `"/"`), search, and hash.
14
- *
15
- * Why component-wise (not raw `.href` equality):
16
- *
17
- * For **special schemes** (`http:`, `https:`, `ws:`, `wss:`, `file:`) the URL
18
- * parser canonicalises empty pathname to `"/"`, so `http://x` and `http://x/`
19
- * share the same `.href` and either comparison would work.
20
- *
21
- * For **non-special schemes** (`tauri://`, `app://`, custom protocols used by
22
- * Tauri/Electron) the parser preserves the empty pathname:
23
- *
24
- * new URL("tauri://localhost").href === "tauri://localhost"
25
- * new URL("/", "tauri://localhost").href === "tauri://localhost/"
26
- *
27
- * A naive `.href` equality would treat these as different. `nav.navigate("/")`
28
- * against `tauri://localhost` would then go through the navigate path — and
29
- * under Safari 26.2 WKWebView that round-trip triggers a cross-document
30
- * reload (the #580 root cause). The first time the user observed this, the
31
- * `same-URL guard` fix only kicked in on the SECOND iteration (after the
32
- * URL had been auto-normalised to include the trailing slash). Component-
33
- * wise comparison with `pathname || "/"` closes that first-iteration hole.
34
- *
35
- * Returns `false` when:
36
- * - `currentHref` is null, undefined or empty (SSR fallback / pre-start),
37
- * - either URL construction throws (malformed input).
38
- *
39
- * Total over all string inputs: never throws.
40
- *
41
- * @internal — exported for property testing; not part of the public surface.
42
- */
43
- export function isSameHref(
44
- target: string,
45
- currentHref: string | null | undefined,
46
- ): boolean {
47
- if (!currentHref) {
48
- return false;
49
- }
50
-
51
- try {
52
- const resolved = new URL(target, currentHref);
53
- const base = new URL(currentHref);
54
-
55
- return (
56
- resolved.protocol === base.protocol &&
57
- resolved.host === base.host &&
58
- (resolved.pathname || "/") === (base.pathname || "/") &&
59
- resolved.search === base.search &&
60
- resolved.hash === base.hash
61
- );
62
- } catch {
63
- return false;
64
- }
65
- }
package/src/index.ts DELETED
@@ -1,65 +0,0 @@
1
- /* eslint-disable @typescript-eslint/method-signature-style -- method syntax required for declaration merging overload (property syntax causes TS2717) */
2
-
3
- import type { Params, State } from "@real-router/core";
4
-
5
- export { navigationPluginFactory } from "./factory";
6
-
7
- export { PLUGIN_SYNC_INFO } from "./navigation-browser";
8
-
9
- export type {
10
- NavigationPluginOptions,
11
- NavigationBrowser,
12
- NavigationMeta,
13
- NavigationDirection,
14
- } from "./types";
15
-
16
- declare module "@real-router/types" {
17
- interface StateContext {
18
- navigation?: import("./types").NavigationMeta;
19
- /**
20
- * URL fragment ("hash") layer state (#532). Populated by both URL plugins
21
- * (navigation-plugin, browser-plugin) — they are mutually exclusive at
22
- * runtime, so only one writes to this namespace.
23
- */
24
- url?: import("./browser-env").UrlContext;
25
- }
26
-
27
- interface NavigationOptions {
28
- /**
29
- * URL fragment override (decoded, no leading "#") (#532).
30
- * Tri-state: `undefined` → preserve current; `""` → clear; non-empty → set.
31
- */
32
- hash?: string;
33
- /**
34
- * @internal — set by URL plugins on hash-only browser-driven navigation.
35
- * Subscribers should branch on `state.context.url.hashChanged` instead.
36
- */
37
- hashChange?: boolean;
38
- }
39
- }
40
-
41
- declare module "@real-router/core" {
42
- interface Router {
43
- buildUrl(
44
- name: string,
45
- params?: Params,
46
- options?: { hash?: string },
47
- ): string;
48
- matchUrl(url: string): State | undefined;
49
- replaceHistoryState(
50
- name: string,
51
- params?: Params,
52
- options?: { hash?: string },
53
- ): void;
54
- peekBack(): State | undefined;
55
- peekForward(): State | undefined;
56
- hasVisited(routeName: string): boolean;
57
- getVisitedRoutes(): string[];
58
- getRouteVisitCount(routeName: string): number;
59
- traverseToLast(routeName: string): Promise<State>;
60
- canGoBack(): boolean;
61
- canGoForward(): boolean;
62
- canGoBackTo(routeName: string): boolean;
63
- start(path?: string): Promise<State>;
64
- }
65
- }
@@ -1,260 +0,0 @@
1
- import { errorCodes, RouterError } from "@real-router/core";
2
-
3
- import { urlToPathAndHash } from "./browser-env";
4
- import { PLUGIN_SYNC_INFO } from "./navigation-browser";
5
-
6
- import type {
7
- NavigationBrowser,
8
- NavigationDirection,
9
- NavigationMeta,
10
- } from "./types";
11
- import type { Router } from "@real-router/core";
12
- import type { PluginApi } from "@real-router/core/api";
13
-
14
- // Hoisted noop intercept options — reused on every plugin-originated
15
- // navigate event (the hot path). `event.intercept` reads `handler` once per
16
- // call per Navigation API spec, so a shared object/function is safe and
17
- // saves two allocations per intercepted event.
18
- //
19
- // `scroll: "manual"` is critical for plugin-originated re-emits: by the time
20
- // the navigate event fires for a router-driven mutation (e.g. scroll-spy's
21
- // hash-only nav, scroll-restoration's URL sync), the router has already
22
- // committed the transition and the app owns scroll position. Default
23
- // `scroll: "after-transition"` would auto-scroll the new URL fragment into
24
- // view, fighting against the user's own scroll motion (concrete bug:
25
- // scroll-spy + slow user scroll → viewport jump on every emit).
26
- // Aligns with browser-plugin (History API has no auto-scroll on
27
- // programmatic URL changes). Apps that want hash-anchor auto-scroll opt
28
- // in via `createScrollRestoration({ anchorScrolling: true })`.
29
- const NOOP_ASYNC = async (): Promise<void> => {};
30
- const NOOP_INTERCEPT: NavigationInterceptOptions = {
31
- handler: NOOP_ASYNC,
32
- scroll: "manual",
33
- };
34
-
35
- interface NavigateHandlerDeps {
36
- router: Router;
37
- api: PluginApi;
38
- browser: NavigationBrowser;
39
- setCapturedMeta: (meta: NavigationMeta) => void;
40
- base: string;
41
- transitionOptions: {
42
- source: string;
43
- replace: true;
44
- forceDeactivate?: boolean;
45
- };
46
- }
47
-
48
- export function computeDirection(
49
- navigationType: NavigationMeta["navigationType"],
50
- destinationIndex: number,
51
- currentIndex: number,
52
- ): NavigationDirection {
53
- if (navigationType === "traverse") {
54
- if (destinationIndex === currentIndex) {
55
- return "unknown";
56
- }
57
-
58
- return destinationIndex > currentIndex ? "forward" : "back";
59
- }
60
-
61
- return navigationType === "push" ? "forward" : "unknown";
62
- }
63
-
64
- export function createNavigateHandler(deps: NavigateHandlerDeps) {
65
- const { router, api, browser, base, transitionOptions } = deps;
66
- const { allowNotFound } = api.getOptions();
67
-
68
- return function handleNavigateEvent(event: NavigateEvent): void {
69
- if (!event.canIntercept || !router.isActive()) {
70
- return;
71
- }
72
-
73
- if (event.info === PLUGIN_SYNC_INFO) {
74
- // Plugin-originated navigate event after its own successful transition
75
- // (onTransitionSuccess calls browser.navigate to sync URL). We must still
76
- // intercept — a bare `return` leaves the event un-intercepted, and
77
- // Chromium falls back to a cross-document navigation (full page reload).
78
- // The noop handler cancels the fallback without running router logic;
79
- // state is already committed.
80
- //
81
- // Detection by `event.info` (identity) instead of a synchronous flag
82
- // (timing) so this works under Safari 26.2 WKWebView, which delivers
83
- // navigate events on a subsequent task — by then a `finally`-cleared
84
- // flag would already be false and the handler would loop (#580).
85
- //
86
- // NOOP_INTERCEPT is module-level so the intercept options + handler
87
- // are not re-allocated per navigation (hot path).
88
- event.intercept(NOOP_INTERCEPT);
89
-
90
- return;
91
- }
92
-
93
- const { path, hash } = urlToPathAndHash(event.destination.url, base);
94
- const matchedState = api.matchPath(path);
95
-
96
- const navType = event.navigationType;
97
- const currentIndex = browser.currentEntry?.index ?? -1;
98
-
99
- deps.setCapturedMeta({
100
- navigationType: navType,
101
- userInitiated: event.userInitiated,
102
- info: event.info,
103
- direction: computeDirection(
104
- navType,
105
- event.destination.index,
106
- currentIndex,
107
- ),
108
- sourceElement: event.sourceElement ?? null,
109
- });
110
-
111
- if (matchedState) {
112
- event.intercept({
113
- handler: () =>
114
- withRecovery(
115
- () =>
116
- // api.navigateToState: matchPath already applied forwardState +
117
- // matchSourceTrailingSlash; reusing the State avoids the redundant
118
- // round-trip and preserves trailing slashes (#525). Plugin-only
119
- // entry point — not on the public Router/Navigator surface.
120
- //
121
- // Hash extraction (#532): pass through the destination's hash so
122
- // onTransitionSuccess sets state.context.url.hash. When the
123
- // browser fires hashChange (same-document fragment-only nav),
124
- // add force+hashChange to bypass SAME_STATES — subscribers
125
- // disambiguate via state.context.url.hashChanged, not via the
126
- // overloaded force flag.
127
- api.navigateToState(matchedState, {
128
- ...transitionOptions,
129
- hash,
130
- ...(event.hashChange ? { force: true, hashChange: true } : {}),
131
- signal: event.signal,
132
- }),
133
- router,
134
- browser,
135
- ),
136
- });
137
- } else if (allowNotFound) {
138
- event.intercept({
139
- handler: () => {
140
- router.navigateToNotFound(path);
141
- },
142
- });
143
- } else {
144
- // Strict mode — unmatched URL is an error. Emit $$error and reject the
145
- // intercept so the Navigation API auto-rolls back the URL. No silent
146
- // fallback to defaultRoute.
147
- event.intercept({
148
- // eslint-disable-next-line @typescript-eslint/require-await -- Navigation API requires async handler; synchronous throw is the rollback signal
149
- handler: async () => {
150
- const err = new RouterError(errorCodes.ROUTE_NOT_FOUND, { path });
151
-
152
- api.emitTransitionError(err);
153
-
154
- throw err;
155
- },
156
- });
157
- }
158
- };
159
- }
160
-
161
- /**
162
- * Module-scope helper hoisted out of handleNavigateEvent so the closure is
163
- * not re-allocated on every navigate event. The router/browser refs come from
164
- * arguments instead of an enclosing scope; identical behaviour, fewer GC'd
165
- * closures.
166
- */
167
- async function withRecovery(
168
- run: () => Promise<unknown>,
169
- router: Router,
170
- browser: NavigationBrowser,
171
- ): Promise<void> {
172
- try {
173
- await run();
174
- } catch (error) {
175
- if (!(error instanceof RouterError)) {
176
- recoverFromNavigateError(error, router, browser);
177
-
178
- return;
179
- }
180
-
181
- // TRANSITION_CANCELLED: a newer navigation aborted this one — the newer
182
- // navigate event is (or will be) handled by this same plugin, and THAT
183
- // event is responsible for syncing URL/state. Firing our own sync here
184
- // races against it: browser.navigate(replace, same-url) would cancel the
185
- // in-flight newer transition, which is exactly the rapid-fire-events storm
186
- // failure mode.
187
- //
188
- // SAME_STATES: router refused because router.getState() already equals the
189
- // target. URL and router state are already consistent — no sync needed.
190
- if (
191
- error.code === errorCodes.TRANSITION_CANCELLED ||
192
- error.code === errorCodes.SAME_STATES
193
- ) {
194
- return;
195
- }
196
-
197
- // Other RouterError codes (CANNOT_DEACTIVATE, CANNOT_ACTIVATE,
198
- // ROUTE_NOT_FOUND, …) — router rejected the transition, state is
199
- // unchanged, but URL may have already committed to a different value by
200
- // the Navigation API. Sync the URL back to the current router state in a
201
- // single visible transition (headless Chromium and some cross-origin
202
- // setups leave "committed-then-reverted" windows if we relied on the
203
- // native rollback via intercept reject). Observers that care about the
204
- // error see it through the router's TRANSITION_ERROR event.
205
- syncUrlToRouterState(router, browser);
206
- }
207
- }
208
-
209
- function recoverFromNavigateError(
210
- error: unknown,
211
- router: Router,
212
- browser: NavigationBrowser,
213
- ): void {
214
- console.error(
215
- "[navigation-plugin] Critical error in navigate handler",
216
- error,
217
- );
218
-
219
- syncUrlToRouterState(router, browser);
220
- }
221
-
222
- function syncUrlToRouterState(
223
- router: Router,
224
- browser: NavigationBrowser,
225
- ): void {
226
- try {
227
- const currentState = router.getState();
228
-
229
- if (currentState) {
230
- // Preserve hash on recovery (#532): reading from state.context.url
231
- // keeps the visible URL fragment intact when a guard rejects a hash-
232
- // bearing navigation.
233
- const ctxHash = (
234
- currentState.context as { url?: { hash?: string } } | undefined
235
- )?.url?.hash;
236
- const url = router.buildUrl(
237
- currentState.name,
238
- currentState.params,
239
- ctxHash ? { hash: ctxHash } : undefined,
240
- );
241
-
242
- // browser.navigate inside `createNavigationBrowser` tags `info` with
243
- // PLUGIN_SYNC_INFO so the navigate event this fires is recognised by
244
- // the handler and short-circuited — no manual flag management here.
245
- browser.navigate(url, {
246
- state: {
247
- name: currentState.name,
248
- params: currentState.params,
249
- path: currentState.path,
250
- },
251
- history: "replace",
252
- });
253
- }
254
- } catch (syncError) {
255
- console.error(
256
- "[navigation-plugin] Failed to sync URL to router state",
257
- syncError,
258
- );
259
- }
260
- }
@@ -1,92 +0,0 @@
1
- import { safelyEncodePath, extractPath } from "./browser-env";
2
-
3
- import type { NavigationBrowser } from "./types";
4
-
5
- /**
6
- * Sentinel carried on `event.info` for every router-driven mutation.
7
- *
8
- * The navigate-event handler reads `event.info === PLUGIN_SYNC_INFO` to detect
9
- * plugin-originated events and short-circuit them with a noop intercept.
10
- * Identity-based detection works regardless of whether the navigate event is
11
- * delivered synchronously inside `nav.navigate(...)` (Chromium) or
12
- * asynchronously on the next task (Safari 26.2 WKWebView — #580).
13
- *
14
- * The previous `SyncingFlag` mechanism raised a per-instance boolean before
15
- * the call and lowered it in a synchronous `finally`. Under Safari WKWebView
16
- * the flag was already `false` by the time the event arrived, so the handler
17
- * treated the plugin's own write as user-initiated and re-issued
18
- * `router.navigate(...)` — render loop on macOS 26.2 Tauri release.
19
- *
20
- * Consumers supplying a custom `NavigationBrowser` should pass this value as
21
- * `info` in their `nav.navigate` / `nav.traverseTo` calls so the plugin can
22
- * recognise plugin-initiated events. See packages/navigation-plugin/CLAUDE.md.
23
- */
24
- export const PLUGIN_SYNC_INFO = "@real-router/navigation-plugin:syncing";
25
-
26
- // `traverseTo` options never carry per-call data — the sentinel `info` is the
27
- // only field — so a single frozen constant is reused across every traversal.
28
- // Saves one allocation per `nav.traverseTo` on the hot path.
29
- const TRAVERSE_OPTS: NavigationOptions = Object.freeze({
30
- info: PLUGIN_SYNC_INFO,
31
- });
32
-
33
- /**
34
- * Creates a NavigationBrowser wrapping the real Navigation API.
35
- * Only call this when `"navigation" in globalThis` is true.
36
- *
37
- * Every router-driven mutation (`navigate`, `replaceState`, `traverseTo`)
38
- * tags `info` with `PLUGIN_SYNC_INFO` so the navigate-event handler can
39
- * recognise and short-circuit the event it fires — see `PLUGIN_SYNC_INFO`
40
- * for the rationale. `updateCurrentEntry` is excluded because it fires
41
- * `currententrychange`, not `navigate`. Scroll-after-transition suppression
42
- * (`scroll: "manual"`) lives in `navigate-handler.ts`'s `NOOP_INTERCEPT`
43
- * — `scroll` is an `event.intercept()` option, not a `nav.navigate()`
44
- * option per WHATWG Navigation API spec.
45
- */
46
- export function createNavigationBrowser(base: string): NavigationBrowser {
47
- const nav = globalThis.navigation;
48
-
49
- return {
50
- getLocation: () =>
51
- safelyEncodePath(extractPath(globalThis.location.pathname, base)) +
52
- globalThis.location.search,
53
-
54
- getHash: () => globalThis.location.hash,
55
-
56
- navigate: (url, options) => {
57
- nav.navigate(url, { ...options, info: PLUGIN_SYNC_INFO });
58
- },
59
-
60
- replaceState: (state, url) => {
61
- nav.navigate(url, {
62
- state,
63
- history: "replace",
64
- info: PLUGIN_SYNC_INFO,
65
- });
66
- },
67
-
68
- updateCurrentEntry: (options) => {
69
- nav.updateCurrentEntry(options);
70
- },
71
-
72
- traverseTo: (key) => {
73
- nav.traverseTo(key, TRAVERSE_OPTS);
74
- },
75
-
76
- addNavigateListener: (fn) => {
77
- nav.addEventListener("navigate", fn);
78
-
79
- return () => {
80
- nav.removeEventListener("navigate", fn);
81
- };
82
- },
83
-
84
- entries: () => nav.entries(),
85
-
86
- get currentEntry() {
87
- return nav.currentEntry;
88
- },
89
-
90
- getActivationType: () => nav.activation?.navigationType,
91
- };
92
- }