@real-router/browser-plugin 0.17.6 → 0.17.8
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/package.json +3 -4
- package/src/constants.ts +0 -15
- package/src/factory.ts +0 -225
- package/src/index.ts +0 -82
- package/src/types.ts +0 -36
- package/src/validation.ts +0 -10
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@real-router/browser-plugin",
|
|
3
|
-
"version": "0.17.
|
|
3
|
+
"version": "0.17.8",
|
|
4
4
|
"type": "commonjs",
|
|
5
5
|
"description": "Browser integration plugin with History API, hash routing, and popstate support",
|
|
6
6
|
"main": "./dist/cjs/index.js",
|
|
@@ -18,8 +18,7 @@
|
|
|
18
18
|
}
|
|
19
19
|
},
|
|
20
20
|
"files": [
|
|
21
|
-
"dist"
|
|
22
|
-
"src"
|
|
21
|
+
"dist"
|
|
23
22
|
],
|
|
24
23
|
"repository": {
|
|
25
24
|
"type": "git",
|
|
@@ -45,7 +44,7 @@
|
|
|
45
44
|
},
|
|
46
45
|
"sideEffects": false,
|
|
47
46
|
"dependencies": {
|
|
48
|
-
"@real-router/core": "^0.
|
|
47
|
+
"@real-router/core": "^0.58.0",
|
|
49
48
|
"@real-router/types": "^0.36.0"
|
|
50
49
|
},
|
|
51
50
|
"devDependencies": {
|
package/src/constants.ts
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import type { BrowserPluginOptions } from "./types";
|
|
2
|
-
|
|
3
|
-
export const defaultOptions: Required<BrowserPluginOptions> = {
|
|
4
|
-
forceDeactivate: true,
|
|
5
|
-
base: "",
|
|
6
|
-
};
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Source identifier for transitions triggered by browser events.
|
|
10
|
-
* Used to distinguish browser-initiated navigation (back/forward buttons)
|
|
11
|
-
* from programmatic navigation (router.navigate()).
|
|
12
|
-
*/
|
|
13
|
-
export const POPSTATE_SOURCE = "popstate";
|
|
14
|
-
|
|
15
|
-
export const LOGGER_CONTEXT = "browser-plugin";
|
package/src/factory.ts
DELETED
|
@@ -1,225 +0,0 @@
|
|
|
1
|
-
import { getPluginApi } from "@real-router/core/api";
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
buildUrl,
|
|
5
|
-
createPluginBuildUrl,
|
|
6
|
-
createPopstateHandler,
|
|
7
|
-
createPopstateLifecycle,
|
|
8
|
-
createReplaceHistoryState,
|
|
9
|
-
createSafeBrowser,
|
|
10
|
-
createStartInterceptor,
|
|
11
|
-
createUpdateBrowserState,
|
|
12
|
-
encodeHashFragment,
|
|
13
|
-
extractPath,
|
|
14
|
-
getDecodedHash,
|
|
15
|
-
normalizeBase,
|
|
16
|
-
normalizeHashInput,
|
|
17
|
-
safelyEncodePath,
|
|
18
|
-
shouldReplaceHistory,
|
|
19
|
-
urlToPath,
|
|
20
|
-
} from "./browser-env";
|
|
21
|
-
import { defaultOptions, LOGGER_CONTEXT, POPSTATE_SOURCE } from "./constants";
|
|
22
|
-
import { validateOptions } from "./validation";
|
|
23
|
-
|
|
24
|
-
import type {
|
|
25
|
-
Browser,
|
|
26
|
-
PopstateTransitionOptions,
|
|
27
|
-
SharedFactoryState,
|
|
28
|
-
UrlContext,
|
|
29
|
-
} from "./browser-env";
|
|
30
|
-
import type { BrowserContext, BrowserPluginOptions } from "./types";
|
|
31
|
-
import type {
|
|
32
|
-
NavigationOptions,
|
|
33
|
-
Plugin,
|
|
34
|
-
PluginFactory,
|
|
35
|
-
Router,
|
|
36
|
-
State,
|
|
37
|
-
} from "@real-router/core";
|
|
38
|
-
import type { PluginApi } from "@real-router/core/api";
|
|
39
|
-
|
|
40
|
-
const FROZEN_POPSTATE: BrowserContext = Object.freeze({
|
|
41
|
-
source: "popstate",
|
|
42
|
-
direction: "back",
|
|
43
|
-
});
|
|
44
|
-
const FROZEN_NAVIGATE: BrowserContext = Object.freeze({
|
|
45
|
-
source: "navigate",
|
|
46
|
-
direction: "forward",
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
export function browserPluginFactory(
|
|
50
|
-
opts?: Partial<BrowserPluginOptions>,
|
|
51
|
-
browser?: Browser,
|
|
52
|
-
): PluginFactory {
|
|
53
|
-
validateOptions(opts);
|
|
54
|
-
|
|
55
|
-
const options: Required<BrowserPluginOptions> = {
|
|
56
|
-
...defaultOptions,
|
|
57
|
-
...opts,
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
options.base = normalizeBase(options.base);
|
|
61
|
-
|
|
62
|
-
const resolvedBrowser = browser ?? createDefaultBrowser(options.base);
|
|
63
|
-
|
|
64
|
-
const transitionOptions = {
|
|
65
|
-
forceDeactivate: options.forceDeactivate,
|
|
66
|
-
source: POPSTATE_SOURCE,
|
|
67
|
-
replace: true as const,
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
const shared: SharedFactoryState = { removePopStateListener: undefined };
|
|
71
|
-
|
|
72
|
-
return function browserPlugin(routerBase) {
|
|
73
|
-
return createBrowserPlugin(
|
|
74
|
-
routerBase as Router,
|
|
75
|
-
getPluginApi(routerBase),
|
|
76
|
-
options,
|
|
77
|
-
resolvedBrowser,
|
|
78
|
-
transitionOptions,
|
|
79
|
-
shared,
|
|
80
|
-
);
|
|
81
|
-
};
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* Creates the default `Browser` for the plugin, with a memoized `getLocation`
|
|
86
|
-
* that skips re-running `extractPath`/`safelyEncodePath` when neither
|
|
87
|
-
* `pathname` nor `search` has changed since the last call (#8.2 A7).
|
|
88
|
-
*
|
|
89
|
-
* Initial sentinel is `"\0"` — a NUL byte cannot appear in a real
|
|
90
|
-
* `location.pathname`, so the first call is always a miss without needing a
|
|
91
|
-
* separate "primed" flag.
|
|
92
|
-
*/
|
|
93
|
-
function createDefaultBrowser(base: string): Browser {
|
|
94
|
-
let cachedPathname = "\0";
|
|
95
|
-
let cachedSearch = "";
|
|
96
|
-
let cachedResult = "";
|
|
97
|
-
|
|
98
|
-
return createSafeBrowser(() => {
|
|
99
|
-
const { pathname, search } = globalThis.location;
|
|
100
|
-
|
|
101
|
-
if (pathname === cachedPathname && search === cachedSearch) {
|
|
102
|
-
return cachedResult;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
cachedPathname = pathname;
|
|
106
|
-
cachedSearch = search;
|
|
107
|
-
cachedResult = safelyEncodePath(extractPath(pathname, base)) + search;
|
|
108
|
-
|
|
109
|
-
return cachedResult;
|
|
110
|
-
}, "browser-plugin");
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
function createBrowserPlugin(
|
|
114
|
-
router: Router,
|
|
115
|
-
api: PluginApi,
|
|
116
|
-
options: Required<BrowserPluginOptions>,
|
|
117
|
-
browser: Browser,
|
|
118
|
-
transitionOptions: PopstateTransitionOptions,
|
|
119
|
-
shared: SharedFactoryState,
|
|
120
|
-
): Plugin {
|
|
121
|
-
const claim = api.claimContextNamespace("browser");
|
|
122
|
-
// Shared URL namespace (#532) — both navigation-plugin and browser-plugin
|
|
123
|
-
// claim "url"; mutually exclusive at runtime.
|
|
124
|
-
const urlClaim = api.claimContextNamespace("url") as {
|
|
125
|
-
write: (state: State, value: UrlContext) => void;
|
|
126
|
-
release: () => void;
|
|
127
|
-
};
|
|
128
|
-
const updateState = createUpdateBrowserState();
|
|
129
|
-
const removeStartInterceptor = createStartInterceptor(api, browser);
|
|
130
|
-
|
|
131
|
-
const pluginBuildUrl = createPluginBuildUrl(router, options.base);
|
|
132
|
-
|
|
133
|
-
const removeExtensions = api.extendRouter({
|
|
134
|
-
buildUrl: pluginBuildUrl,
|
|
135
|
-
matchUrl: (url: string) =>
|
|
136
|
-
api.matchPath(urlToPath(url, options.base)) ?? undefined,
|
|
137
|
-
replaceHistoryState: createReplaceHistoryState(
|
|
138
|
-
api,
|
|
139
|
-
router,
|
|
140
|
-
browser,
|
|
141
|
-
pluginBuildUrl,
|
|
142
|
-
),
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
const handler = createPopstateHandler({
|
|
146
|
-
router,
|
|
147
|
-
api,
|
|
148
|
-
browser,
|
|
149
|
-
allowNotFound: api.getOptions().allowNotFound,
|
|
150
|
-
transitionOptions,
|
|
151
|
-
loggerContext: LOGGER_CONTEXT,
|
|
152
|
-
buildUrl: pluginBuildUrl,
|
|
153
|
-
// Hash bridging (#532). popstate doesn't carry a URL — we sample
|
|
154
|
-
// location.hash after the browser has updated to the destination.
|
|
155
|
-
getCurrentHash: () => getDecodedHash(browser),
|
|
156
|
-
getCurrentContextHash: () =>
|
|
157
|
-
(router.getState()?.context as { url?: { hash?: string } } | undefined)
|
|
158
|
-
?.url?.hash ?? "",
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
const lifecycle = createPopstateLifecycle({
|
|
162
|
-
browser,
|
|
163
|
-
shared,
|
|
164
|
-
handler,
|
|
165
|
-
cleanup: () => {
|
|
166
|
-
removeStartInterceptor();
|
|
167
|
-
removeExtensions();
|
|
168
|
-
claim.release();
|
|
169
|
-
urlClaim.release();
|
|
170
|
-
},
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
return {
|
|
174
|
-
...lifecycle,
|
|
175
|
-
|
|
176
|
-
onTransitionSuccess: (
|
|
177
|
-
toState: State,
|
|
178
|
-
fromState: State | undefined,
|
|
179
|
-
navOptions: NavigationOptions,
|
|
180
|
-
) => {
|
|
181
|
-
const replaceHistory = shouldReplaceHistory(
|
|
182
|
-
navOptions,
|
|
183
|
-
toState,
|
|
184
|
-
fromState,
|
|
185
|
-
);
|
|
186
|
-
|
|
187
|
-
// Tri-state hash resolution (#532).
|
|
188
|
-
// navOptions.hash === undefined → preserve current browser hash
|
|
189
|
-
// navOptions.hash === "" → explicitly clear
|
|
190
|
-
// navOptions.hash === "value" → explicitly set
|
|
191
|
-
//
|
|
192
|
-
// The "preserve" branch reads location.hash from the browser, not
|
|
193
|
-
// fromState.context.url.hash — captures dynamic fragment changes
|
|
194
|
-
// (anchor clicks, manual location.hash assignment) made outside the
|
|
195
|
-
// plugin. hashChanged compares the chosen hash against the published
|
|
196
|
-
// previous hash so subscribers see a true signal.
|
|
197
|
-
const browserHash = getDecodedHash(browser);
|
|
198
|
-
const publishedPrevHash =
|
|
199
|
-
(fromState?.context as { url?: { hash?: string } } | undefined)?.url
|
|
200
|
-
?.hash ?? "";
|
|
201
|
-
|
|
202
|
-
const hash =
|
|
203
|
-
navOptions.hash === undefined
|
|
204
|
-
? browserHash
|
|
205
|
-
: normalizeHashInput(navOptions.hash);
|
|
206
|
-
|
|
207
|
-
urlClaim.write(
|
|
208
|
-
toState,
|
|
209
|
-
Object.freeze({
|
|
210
|
-
hash,
|
|
211
|
-
hashChanged: navOptions.hashChange ?? hash !== publishedPrevHash,
|
|
212
|
-
}),
|
|
213
|
-
);
|
|
214
|
-
|
|
215
|
-
const url = buildUrl(toState.path, options.base);
|
|
216
|
-
const finalUrl = hash ? `${url}#${encodeHashFragment(hash)}` : url;
|
|
217
|
-
|
|
218
|
-
updateState(toState, finalUrl, replaceHistory, browser);
|
|
219
|
-
|
|
220
|
-
const isPopstate = navOptions.source === POPSTATE_SOURCE;
|
|
221
|
-
|
|
222
|
-
claim.write(toState, isPopstate ? FROZEN_POPSTATE : FROZEN_NAVIGATE);
|
|
223
|
-
},
|
|
224
|
-
};
|
|
225
|
-
}
|
package/src/index.ts
DELETED
|
@@ -1,82 +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
|
-
// Main plugin factory
|
|
6
|
-
export { browserPluginFactory } from "./factory";
|
|
7
|
-
|
|
8
|
-
// Types
|
|
9
|
-
export type {
|
|
10
|
-
BrowserPluginOptions,
|
|
11
|
-
BrowserContext,
|
|
12
|
-
BrowserDirection,
|
|
13
|
-
BrowserSource,
|
|
14
|
-
} from "./types";
|
|
15
|
-
|
|
16
|
-
export type { Browser } from "./browser-env";
|
|
17
|
-
|
|
18
|
-
// Type guards
|
|
19
|
-
export { isStateStrict as isState } from "type-guards";
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Module augmentation for real-router.
|
|
23
|
-
* Extends Router interface with browser plugin methods.
|
|
24
|
-
*/
|
|
25
|
-
declare module "@real-router/types" {
|
|
26
|
-
interface StateContext {
|
|
27
|
-
browser?: import("./types").BrowserContext;
|
|
28
|
-
/**
|
|
29
|
-
* URL fragment ("hash") layer state (#532). Populated by both URL plugins
|
|
30
|
-
* (navigation-plugin, browser-plugin) — they are mutually exclusive at
|
|
31
|
-
* runtime, so only one writes to this namespace.
|
|
32
|
-
*/
|
|
33
|
-
url?: import("./browser-env").UrlContext;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
interface NavigationOptions {
|
|
37
|
-
/** @internal — set by browser/hash/navigation plugins to mark transition origin. */
|
|
38
|
-
source?: string;
|
|
39
|
-
/**
|
|
40
|
-
* URL fragment override (decoded, no leading "#") (#532).
|
|
41
|
-
* Tri-state: `undefined` → preserve current; `""` → clear; non-empty → set.
|
|
42
|
-
*/
|
|
43
|
-
hash?: string;
|
|
44
|
-
/**
|
|
45
|
-
* @internal — set by URL plugins on hash-only browser-driven navigation.
|
|
46
|
-
* Subscribers should branch on `state.context.url.hashChanged` instead.
|
|
47
|
-
*/
|
|
48
|
-
hashChange?: boolean;
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
declare module "@real-router/core" {
|
|
53
|
-
interface Router {
|
|
54
|
-
/**
|
|
55
|
-
* Builds full URL for a route with base path and (optionally) hash fragment.
|
|
56
|
-
* Added by browser plugin.
|
|
57
|
-
*/
|
|
58
|
-
buildUrl(
|
|
59
|
-
name: string,
|
|
60
|
-
params?: Params,
|
|
61
|
-
options?: { hash?: string },
|
|
62
|
-
): string;
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Matches URL and returns corresponding state.
|
|
66
|
-
* Added by browser plugin.
|
|
67
|
-
*/
|
|
68
|
-
matchUrl(url: string): State | undefined;
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Replaces current history state without triggering navigation.
|
|
72
|
-
* Added by browser plugin.
|
|
73
|
-
*/
|
|
74
|
-
replaceHistoryState(
|
|
75
|
-
name: string,
|
|
76
|
-
params?: Params,
|
|
77
|
-
options?: { hash?: string },
|
|
78
|
-
): void;
|
|
79
|
-
|
|
80
|
-
start(path?: string): Promise<State>;
|
|
81
|
-
}
|
|
82
|
-
}
|
package/src/types.ts
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Browser plugin configuration.
|
|
3
|
-
*/
|
|
4
|
-
export type BrowserSource = "popstate" | "navigate";
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Logical direction of the navigation that produced the current state.
|
|
8
|
-
* Derived from `source`: any popstate event (browser back / forward / hash
|
|
9
|
-
* jump) maps to `"back"`; programmatic `router.navigate()` maps to
|
|
10
|
-
* `"forward"`. The Web Platform does not surface a true forward-vs-back
|
|
11
|
-
* distinction in the popstate event, so consumers wanting reverse-aware
|
|
12
|
-
* animations should read this field rather than maintaining their own
|
|
13
|
-
* popstate listener.
|
|
14
|
-
*/
|
|
15
|
-
export type BrowserDirection = "forward" | "back";
|
|
16
|
-
|
|
17
|
-
export interface BrowserContext {
|
|
18
|
-
source: BrowserSource;
|
|
19
|
-
direction: BrowserDirection;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export interface BrowserPluginOptions {
|
|
23
|
-
/**
|
|
24
|
-
* Force deactivation of current route even if canDeactivate returns false.
|
|
25
|
-
*
|
|
26
|
-
* @default true
|
|
27
|
-
*/
|
|
28
|
-
forceDeactivate?: boolean;
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Base path for all routes (e.g., "/app" for hosted at /app/).
|
|
32
|
-
*
|
|
33
|
-
* @default ""
|
|
34
|
-
*/
|
|
35
|
-
base?: string;
|
|
36
|
-
}
|
package/src/validation.ts
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { createOptionsValidator, safeBaseRule } from "./browser-env";
|
|
2
|
-
import { LOGGER_CONTEXT, defaultOptions } from "./constants";
|
|
3
|
-
|
|
4
|
-
import type { BrowserPluginOptions } from "./types";
|
|
5
|
-
|
|
6
|
-
export const validateOptions = createOptionsValidator<BrowserPluginOptions>(
|
|
7
|
-
defaultOptions,
|
|
8
|
-
LOGGER_CONTEXT,
|
|
9
|
-
{ base: safeBaseRule },
|
|
10
|
-
);
|