Press n or j to go to the next uncovered block, b, p or k for the previous block.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 | 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 404x 404x 404x 404x 404x 404x 404x 821x 821x 821x 821x 404x 56x 56x 404x 404x 106x 106x 404x 404x 404x 404x 404x 404x 404x 404x 404x 404x 372x 372x 404x 125x 125x 404x 31x 31x 404x 31x 31x 404x 113x 113x 113x 113x 113x 404x 59x 59x 7x 7x 7x 52x 52x 52x 52x 59x 404x 12x 11x 12x 6x 4x 4x 6x 6x 5x 5x 5x 5x 12x 404x 32x 32x 23x 23x 23x 23x 32x 404x 134x 134x 134x 134x 134x 134x 134x 404x 118x 118x 118x 118x 118x 118x 118x 404x 404x 404x 404x 404x 404x 206x 206x 404x 16x 16x 404x 337x 337x 337x 337x 404x | import { LAYER_ID } from './increment-id'; import { MicroApp } from './micro-app'; import { Navigation } from './navigation'; import { parsedOptions } from './options'; import { Route } from './route'; import { RouteTransition } from './route-transition'; import { createLinkResolver } from './router-link'; import { RouteType, RouterMode } from './types'; import type { RouteConfirmHook, RouteLayerOptions, RouteLayerResult, RouteLocationInput, RouteMatchType, RouteNotifyHook, RouteState, RouterLinkProps, RouterLinkResolved, RouterOptions, RouterParsedOptions } from './types'; import { isNotNullish, isPlainObject, isRouteMatched } from './util'; export class Router { public readonly options: RouterOptions; public readonly parsedOptions: RouterParsedOptions; public readonly isLayer: boolean; public readonly navigation: Navigation; public readonly microApp: MicroApp = new MicroApp(); // Route transition manager public readonly transition = new RouteTransition(this); public get route() { const route = this.transition.route; if (route === null) { throw new Error( 'No active route found. Please navigate to a route first using router.push() or router.replace().' ); } return route; } public get root() { return this.parsedOptions.root; } public get req() { return this.parsedOptions.req ?? null; } public get res() { return this.parsedOptions.res ?? null; } public constructor(options: RouterOptions) { this.options = options; this.parsedOptions = parsedOptions(options); this.isLayer = this.parsedOptions.layer; this.navigation = new Navigation( this.parsedOptions, (url: string, state: RouteState) => { this.transition.to(RouteType.unknown, { url, state }); } ); } public push(toInput: RouteLocationInput): Promise<Route> { return this.transition.to(RouteType.push, toInput); } public replace(toInput: RouteLocationInput): Promise<Route> { return this.transition.to(RouteType.replace, toInput); } public pushWindow(toInput: RouteLocationInput): Promise<Route> { return this.transition.to(RouteType.pushWindow, toInput); } public replaceWindow(toInput: RouteLocationInput): Promise<Route> { return this.transition.to(RouteType.replaceWindow, toInput); } public restartApp(toInput?: RouteLocationInput): Promise<Route> { return this.transition.to( RouteType.restartApp, toInput ?? this.route.url.href ); } public async back(): Promise<Route | null> { const result = await this.navigation.go(-1); if (result === null) { this.parsedOptions.handleBackBoundary(this); return null; } return this.transition.to(RouteType.back, { url: result.url, state: result.state }); } public async go(index: number): Promise<Route | null> { // go(0) refreshes the page in browser, but we return null directly in router if (index === 0) return null; const result = await this.navigation.go(index); if (result === null) { // Call handleBackBoundary when backward navigation has no response if (index < 0) { this.parsedOptions.handleBackBoundary(this); } return null; } return this.transition.to(RouteType.go, { url: result.url, state: result.state }); } public async forward(): Promise<Route | null> { const result = await this.navigation.go(1); if (result === null) return null; return this.transition.to(RouteType.forward, { url: result.url, state: result.state }); } /** * Parse route location without performing actual navigation * * This method is used to parse route configuration and return the corresponding route object, * but does not trigger actual page navigation. It is mainly used for the following scenarios: * - Generate link URLs without jumping * - Pre-check route matching * - Get route parameters, meta information, etc. * - Test the validity of route configuration * * @param toInput Target route location, can be a string path or route configuration object * @returns Parsed route object containing complete route information * * @example * ```typescript * // Parse string path * const route = router.resolve('/user/123'); * const url = route.url.href; // Get complete URL * * // Parse named route * const userRoute = router.resolve({ * name: 'user', * params: { id: '123' } * }); * console.log(userRoute.params.id); // '123' * * // Check route validity * const testRoute = router.resolve('/some/path'); * if (testRoute.matched.length > 0) { * // Route matched successfully * } * ``` */ public resolve(toInput: RouteLocationInput, toType?: RouteType): Route { return new Route({ options: this.parsedOptions, toType, toInput, from: this.transition.route?.url ?? null }); } /** * Check if the route matches the current route * * @param targetRoute Target route object to compare * @param matchType Match type * - 'route': Route-level matching, compare if route configurations are the same * - 'exact': Exact matching, compare if paths are completely the same * - 'include': Include matching, check if current path contains target path * @returns Whether it matches */ public isRouteMatched( targetRoute: Route, matchType: RouteMatchType = 'include' ): boolean { const currentRoute = this.transition.route; if (!currentRoute) return false; return isRouteMatched(currentRoute, targetRoute, matchType); } /** * Resolve router link configuration and return complete link data * * This method analyzes router link properties and returns a comprehensive * link resolution result including route information, navigation functions, * HTML attributes, and event handlers. It's primarily used for: * - Framework-agnostic link component implementation * - Generating link attributes and navigation handlers * - Computing active states and CSS classes * - Creating event handlers for different frameworks * * @param props Router link configuration properties * @returns Complete link resolution result with all necessary data * * @example * ```typescript * // Basic link resolution * const linkData = router.resolveLink({ * to: '/user/123', * type: 'push' * }); * * // Access resolved data * console.log(linkData.route.path); // '/user/123' * console.log(linkData.attributes.href); // Full href URL * console.log(linkData.isActive); // Active state * * // Use navigation function * linkData.navigate(); // Programmatic navigation * * // Get event handlers for React * const handlers = linkData.getEventHandlers(name => `on${name.charAt(0).toUpperCase() + name.slice(1)}`); * // handlers.onClick for React * ``` */ public resolveLink(props: RouterLinkProps): RouterLinkResolved { return createLinkResolver(this, props); } public async createLayer( toInput: RouteLocationInput ): Promise<{ promise: Promise<RouteLayerResult>; router: Router }> { const layerOptions: RouteLayerOptions = (isPlainObject(toInput) ? toInput.layer : null) || {}; const zIndex = layerOptions.zIndex ?? this.parsedOptions.zIndex + LAYER_ID.next(); let promiseResolve: (result: RouteLayerResult) => void; const promise = new Promise<RouteLayerResult>((resolve) => { promiseResolve = resolve; }); const router = new Router({ ...this.options, mode: RouterMode.memory, rootStyle: { position: 'fixed', top: '0', left: '0', width: '100%', height: '100%', zIndex: String(zIndex), background: 'rgba(0,0,0,.6)', display: 'flex', alignItems: 'center', justifyContent: 'center' }, root: undefined, ...layerOptions.routerOptions, handleBackBoundary(router) { router.destroy(); promiseResolve({ type: 'close', route: router.route }); }, handleLayerClose(router, data) { router.destroy(); if (isNotNullish(data)) { promiseResolve({ type: 'success', route: router.route, data }); } else { promiseResolve({ type: 'close', route: router.route }); } }, layer: true }); await router.replace(toInput); router.afterEach((to, from) => { if (layerOptions.shouldClose) { const result = layerOptions.shouldClose(to, from, router); if (result === false) { router.destroy(); promiseResolve({ type: 'push', route: to }); } } }); if (layerOptions.push) { router.navigation.pushHistoryState( router.route.state, router.route.url ); } return { promise, router }; } public async pushLayer( toInput: RouteLocationInput ): Promise<RouteLayerResult> { const result = await this.transition.to(RouteType.pushLayer, toInput); return result.handleResult as RouteLayerResult; } public closeLayer(data?: any) { if (!this.isLayer) return; this.parsedOptions.handleLayerClose(this, data); } public async renderToString(throwError = false): Promise<string | null> { try { const result = await this.microApp.app?.renderToString?.(); return result ?? null; } catch (e) { if (throwError) throw e; else console.error(e); return null; } } public beforeEach(guard: RouteConfirmHook): () => void { return this.transition.beforeEach(guard); } public afterEach(guard: RouteNotifyHook): () => void { return this.transition.afterEach(guard); } public destroy() { this.transition.destroy(); this.navigation.destroy(); this.microApp.destroy(); } } |