@adukiorg/anza 0.2.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/CHANGELOG.md +137 -0
- package/README.md +215 -0
- package/bin/anza.js +63 -0
- package/bin/create.js +150 -0
- package/importmap.json +72 -0
- package/package.json +100 -0
- package/src/core/animations/index.js +55 -0
- package/src/core/animations/play.js +111 -0
- package/src/core/animations/registry.js +54 -0
- package/src/core/animations/scroll.js +50 -0
- package/src/core/animations/tokens.js +58 -0
- package/src/core/animations/usage.md +301 -0
- package/src/core/animations/waapi.js +86 -0
- package/src/core/api/cache.js +120 -0
- package/src/core/api/caches/glob.js +24 -0
- package/src/core/api/caches/index.js +118 -0
- package/src/core/api/events/index.js +75 -0
- package/src/core/api/fetch.js +99 -0
- package/src/core/api/index.js +158 -0
- package/src/core/api/pipeline.js +98 -0
- package/src/core/api/plan.md +209 -0
- package/src/core/api/prefixes/index.js +66 -0
- package/src/core/api/retry.js +69 -0
- package/src/core/api/stream.js +127 -0
- package/src/core/api/upload.js +180 -0
- package/src/core/api/usage.md +206 -0
- package/src/core/events/bus.js +38 -0
- package/src/core/events/delegate.js +79 -0
- package/src/core/events/index.js +26 -0
- package/src/core/events/listen.js +50 -0
- package/src/core/events/missing.md +103 -0
- package/src/core/events/once.js +49 -0
- package/src/core/events/plan.md +177 -0
- package/src/core/events/types/index.js +34 -0
- package/src/core/events/usage.md +107 -0
- package/src/core/offline/bridge.js +51 -0
- package/src/core/offline/clock.js +100 -0
- package/src/core/offline/connectivity.js +116 -0
- package/src/core/offline/index.js +41 -0
- package/src/core/offline/missing.md +89 -0
- package/src/core/offline/plan.md +143 -0
- package/src/core/offline/queue.js +168 -0
- package/src/core/offline/state.js +18 -0
- package/src/core/offline/sync.js +106 -0
- package/src/core/offline/usage.md +273 -0
- package/src/core/platform/guard.js +104 -0
- package/src/core/platform/index.js +42 -0
- package/src/core/platform/missing.md +119 -0
- package/src/core/platform/platform.d.ts +88 -0
- package/src/core/platform/polyfills/anchor.js +79 -0
- package/src/core/platform/polyfills/navigation.js +142 -0
- package/src/core/platform/polyfills/popover.js +142 -0
- package/src/core/platform/polyfills/scheduler.js +194 -0
- package/src/core/platform/polyfills/shadow.js +35 -0
- package/src/core/platform/polyfills/urlpattern.js +119 -0
- package/src/core/platform/supports.js +186 -0
- package/src/core/platform/usage.md +287 -0
- package/src/core/router/cache.js +95 -0
- package/src/core/router/container.js +146 -0
- package/src/core/router/handler.js +52 -0
- package/src/core/router/history.js +120 -0
- package/src/core/router/index.js +158 -0
- package/src/core/router/intercept.js +376 -0
- package/src/core/router/match.js +145 -0
- package/src/core/router/missing.md +716 -0
- package/src/core/router/outlet.js +139 -0
- package/src/core/router/plan.md +370 -0
- package/src/core/router/sync/index.js +16 -0
- package/src/core/router/sync/tab.js +115 -0
- package/src/core/router/sync/transport.js +139 -0
- package/src/core/router/transitions.js +59 -0
- package/src/core/router/usage.md +773 -0
- package/src/core/security/crypto.js +159 -0
- package/src/core/security/index.js +49 -0
- package/src/core/security/missing.md +97 -0
- package/src/core/security/permissions.js +64 -0
- package/src/core/security/sanitize.js +100 -0
- package/src/core/security/usage.md +283 -0
- package/src/core/state/derived.js +117 -0
- package/src/core/state/index.js +23 -0
- package/src/core/state/missing.md +165 -0
- package/src/core/state/persist.js +284 -0
- package/src/core/state/store.js +308 -0
- package/src/core/state/sync.js +46 -0
- package/src/core/state/usage.md +440 -0
- package/src/core/storage/cache.js +83 -0
- package/src/core/storage/idb.js +196 -0
- package/src/core/storage/index.js +373 -0
- package/src/core/storage/lru.js +107 -0
- package/src/core/storage/missing.md +165 -0
- package/src/core/storage/opfs.js +190 -0
- package/src/core/storage/plan.md +69 -0
- package/src/core/storage/quota.js +69 -0
- package/src/core/storage/usage.md +226 -0
- package/src/core/ui/base.js +50 -0
- package/src/core/ui/define/container.js +82 -0
- package/src/core/ui/define/define.js +12 -0
- package/src/core/ui/define/element.js +390 -0
- package/src/core/ui/define/index.js +9 -0
- package/src/core/ui/define/orchestrator.js +105 -0
- package/src/core/ui/define/proxy.js +644 -0
- package/src/core/ui/define/state.js +6 -0
- package/src/core/ui/define/utils.js +134 -0
- package/src/core/ui/implementation.md +170 -0
- package/src/core/ui/index.js +41 -0
- package/src/core/ui/observe.js +117 -0
- package/src/core/ui/plan.md +510 -0
- package/src/core/ui/schedule.js +60 -0
- package/src/core/ui/template.js +37 -0
- package/src/core/ui/transitions.js +37 -0
- package/src/core/ui/ui.types.md +890 -0
- package/src/core/ui/usage.md +1124 -0
- package/src/core/ui/watch.md +346 -0
- package/src/core/workers/broadcast.js +138 -0
- package/src/core/workers/dedicated.js +153 -0
- package/src/core/workers/index.js +169 -0
- package/src/core/workers/locks.js +160 -0
- package/src/core/workers/offscreen.js +166 -0
- package/src/core/workers/plan.md +381 -0
- package/src/core/workers/pool.js +267 -0
- package/src/core/workers/shared.js +137 -0
- package/src/core/workers/usage.md +622 -0
- package/src/elements/base.js +12 -0
- package/src/elements/data/card/index.html +9 -0
- package/src/elements/data/card/index.js +19 -0
- package/src/elements/data/card/index.tags.json +1 -0
- package/src/elements/data/card/style.css +46 -0
- package/src/elements/data/chart/index.html +1 -0
- package/src/elements/data/chart/index.js +143 -0
- package/src/elements/data/chart/index.tags.json +1 -0
- package/src/elements/data/chart/style.css +13 -0
- package/src/elements/data/list/index.html +3 -0
- package/src/elements/data/list/index.js +19 -0
- package/src/elements/data/list/index.tags.json +1 -0
- package/src/elements/data/list/style.css +39 -0
- package/src/elements/data/stat/index.html +9 -0
- package/src/elements/data/stat/index.js +19 -0
- package/src/elements/data/stat/index.tags.json +1 -0
- package/src/elements/data/stat/style.css +50 -0
- package/src/elements/data/table/index.html +1 -0
- package/src/elements/data/table/index.js +16 -0
- package/src/elements/data/table/index.tags.json +1 -0
- package/src/elements/data/table/style.css +50 -0
- package/src/elements/feedback/alert/index.html +11 -0
- package/src/elements/feedback/alert/index.js +28 -0
- package/src/elements/feedback/alert/index.tags.json +1 -0
- package/src/elements/feedback/alert/style.css +75 -0
- package/src/elements/feedback/empty/index.html +13 -0
- package/src/elements/feedback/empty/index.js +34 -0
- package/src/elements/feedback/empty/index.tags.json +1 -0
- package/src/elements/feedback/empty/style.css +45 -0
- package/src/elements/feedback/progress/index.html +7 -0
- package/src/elements/feedback/progress/index.js +46 -0
- package/src/elements/feedback/progress/index.tags.json +1 -0
- package/src/elements/feedback/progress/style.css +36 -0
- package/src/elements/feedback/skeleton/index.html +1 -0
- package/src/elements/feedback/skeleton/index.js +78 -0
- package/src/elements/feedback/skeleton/index.tags.json +1 -0
- package/src/elements/feedback/skeleton/style.css +28 -0
- package/src/elements/feedback/toast/index.html +3 -0
- package/src/elements/feedback/toast/index.js +65 -0
- package/src/elements/feedback/toast/index.tags.json +1 -0
- package/src/elements/feedback/toast/style.css +36 -0
- package/src/elements/forms/checkbox/index.html +7 -0
- package/src/elements/forms/checkbox/index.js +104 -0
- package/src/elements/forms/checkbox/index.tags.json +1 -0
- package/src/elements/forms/checkbox/style.css +86 -0
- package/src/elements/forms/field/index.html +13 -0
- package/src/elements/forms/field/index.js +42 -0
- package/src/elements/forms/field/index.tags.json +1 -0
- package/src/elements/forms/field/style.css +42 -0
- package/src/elements/forms/form/index.html +3 -0
- package/src/elements/forms/form/index.js +122 -0
- package/src/elements/forms/form/index.tags.json +1 -0
- package/src/elements/forms/form/style.css +11 -0
- package/src/elements/forms/input/index.html +4 -0
- package/src/elements/forms/input/index.js +103 -0
- package/src/elements/forms/input/index.tags.json +1 -0
- package/src/elements/forms/input/style.css +39 -0
- package/src/elements/forms/radio/index.html +4 -0
- package/src/elements/forms/radio/index.js +109 -0
- package/src/elements/forms/radio/index.tags.json +1 -0
- package/src/elements/forms/radio/style.css +65 -0
- package/src/elements/forms/select/index.html +9 -0
- package/src/elements/forms/select/index.js +114 -0
- package/src/elements/forms/select/index.tags.json +1 -0
- package/src/elements/forms/select/style.css +95 -0
- package/src/elements/forms/textarea/index.html +4 -0
- package/src/elements/forms/textarea/index.js +115 -0
- package/src/elements/forms/textarea/index.tags.json +1 -0
- package/src/elements/forms/textarea/style.css +46 -0
- package/src/elements/forms/toggle/index.html +4 -0
- package/src/elements/forms/toggle/index.js +89 -0
- package/src/elements/forms/toggle/index.tags.json +1 -0
- package/src/elements/forms/toggle/style.css +63 -0
- package/src/elements/forms/upload/index.html +13 -0
- package/src/elements/forms/upload/index.js +120 -0
- package/src/elements/forms/upload/index.tags.json +1 -0
- package/src/elements/forms/upload/style.css +61 -0
- package/src/elements/index.js +71 -0
- package/src/elements/layout/app/index.html +7 -0
- package/src/elements/layout/app/index.js +16 -0
- package/src/elements/layout/app/index.tags.json +1 -0
- package/src/elements/layout/app/style.css +41 -0
- package/src/elements/layout/grid/index.html +3 -0
- package/src/elements/layout/grid/index.js +41 -0
- package/src/elements/layout/grid/index.tags.json +1 -0
- package/src/elements/layout/grid/style.css +12 -0
- package/src/elements/layout/header/index.html +8 -0
- package/src/elements/layout/header/index.js +16 -0
- package/src/elements/layout/header/index.tags.json +1 -0
- package/src/elements/layout/header/style.css +28 -0
- package/src/elements/layout/scroll/index.html +3 -0
- package/src/elements/layout/scroll/index.js +19 -0
- package/src/elements/layout/scroll/index.tags.json +1 -0
- package/src/elements/layout/scroll/style.css +24 -0
- package/src/elements/layout/sidebar/index.html +3 -0
- package/src/elements/layout/sidebar/index.js +24 -0
- package/src/elements/layout/sidebar/index.tags.json +1 -0
- package/src/elements/layout/sidebar/style.css +30 -0
- package/src/elements/layout/split/index.html +3 -0
- package/src/elements/layout/split/index.js +18 -0
- package/src/elements/layout/split/index.tags.json +1 -0
- package/src/elements/layout/split/style.css +28 -0
- package/src/elements/layout/stack/index.html +3 -0
- package/src/elements/layout/stack/index.js +31 -0
- package/src/elements/layout/stack/index.tags.json +1 -0
- package/src/elements/layout/stack/style.css +15 -0
- package/src/elements/layout/surface/index.html +3 -0
- package/src/elements/layout/surface/index.js +19 -0
- package/src/elements/layout/surface/index.tags.json +1 -0
- package/src/elements/layout/surface/style.css +29 -0
- package/src/elements/navigation/breadcrumb/index.html +5 -0
- package/src/elements/navigation/breadcrumb/index.js +16 -0
- package/src/elements/navigation/breadcrumb/index.tags.json +1 -0
- package/src/elements/navigation/breadcrumb/style.css +36 -0
- package/src/elements/navigation/nav/index.html +3 -0
- package/src/elements/navigation/nav/index.js +24 -0
- package/src/elements/navigation/nav/index.tags.json +1 -0
- package/src/elements/navigation/nav/style.css +38 -0
- package/src/elements/navigation/pagination/index.html +3 -0
- package/src/elements/navigation/pagination/index.js +94 -0
- package/src/elements/navigation/pagination/index.tags.json +1 -0
- package/src/elements/navigation/pagination/style.css +39 -0
- package/src/elements/navigation/steps/index.html +6 -0
- package/src/elements/navigation/steps/index.js +64 -0
- package/src/elements/navigation/steps/index.tags.json +1 -0
- package/src/elements/navigation/steps/style.css +78 -0
- package/src/elements/navigation/tabs/index.html +6 -0
- package/src/elements/navigation/tabs/index.js +132 -0
- package/src/elements/navigation/tabs/index.tags.json +1 -0
- package/src/elements/navigation/tabs/style.css +52 -0
- package/src/elements/overlay/dialog/index.html +5 -0
- package/src/elements/overlay/dialog/index.js +57 -0
- package/src/elements/overlay/dialog/index.tags.json +1 -0
- package/src/elements/overlay/dialog/style.css +31 -0
- package/src/elements/overlay/drawer/index.html +3 -0
- package/src/elements/overlay/drawer/index.js +56 -0
- package/src/elements/overlay/drawer/index.tags.json +1 -0
- package/src/elements/overlay/drawer/style.css +48 -0
- package/src/elements/overlay/menu/index.html +3 -0
- package/src/elements/overlay/menu/index.js +107 -0
- package/src/elements/overlay/menu/index.tags.json +1 -0
- package/src/elements/overlay/menu/style.css +43 -0
- package/src/elements/overlay/popover/index.html +3 -0
- package/src/elements/overlay/popover/index.js +44 -0
- package/src/elements/overlay/popover/index.tags.json +1 -0
- package/src/elements/overlay/popover/style.css +21 -0
- package/src/elements/overlay/sheet/index.html +8 -0
- package/src/elements/overlay/sheet/index.js +105 -0
- package/src/elements/overlay/sheet/index.tags.json +1 -0
- package/src/elements/overlay/sheet/style.css +64 -0
- package/src/elements/overlay/tooltip/index.html +6 -0
- package/src/elements/overlay/tooltip/index.js +16 -0
- package/src/elements/overlay/tooltip/index.tags.json +1 -0
- package/src/elements/overlay/tooltip/style.css +41 -0
- package/src/elements/primitives/avatar/index.html +2 -0
- package/src/elements/primitives/avatar/index.js +79 -0
- package/src/elements/primitives/avatar/index.tags.json +1 -0
- package/src/elements/primitives/avatar/style.css +36 -0
- package/src/elements/primitives/badge/index.html +3 -0
- package/src/elements/primitives/badge/index.js +20 -0
- package/src/elements/primitives/badge/index.tags.json +1 -0
- package/src/elements/primitives/badge/style.css +67 -0
- package/src/elements/primitives/button/index.html +3 -0
- package/src/elements/primitives/button/index.js +61 -0
- package/src/elements/primitives/button/index.tags.json +1 -0
- package/src/elements/primitives/button/style.css +66 -0
- package/src/elements/primitives/divider/index.html +1 -0
- package/src/elements/primitives/divider/index.js +43 -0
- package/src/elements/primitives/divider/index.tags.json +1 -0
- package/src/elements/primitives/divider/style.css +39 -0
- package/src/elements/primitives/icon/index.html +3 -0
- package/src/elements/primitives/icon/index.js +66 -0
- package/src/elements/primitives/icon/index.tags.json +1 -0
- package/src/elements/primitives/icon/style.css +20 -0
- package/src/elements/primitives/link/index.html +3 -0
- package/src/elements/primitives/link/index.js +129 -0
- package/src/elements/primitives/link/index.tags.json +1 -0
- package/src/elements/primitives/link/style.css +40 -0
- package/src/elements/primitives/spinner/index.html +1 -0
- package/src/elements/primitives/spinner/index.js +62 -0
- package/src/elements/primitives/spinner/index.tags.json +1 -0
- package/src/elements/primitives/spinner/style.css +20 -0
- package/src/elements/primitives/text/index.html +1 -0
- package/src/elements/primitives/text/index.js +79 -0
- package/src/elements/primitives/text/index.tags.json +1 -0
- package/src/elements/primitives/text/style.css +25 -0
- package/src/index.js +23 -0
- package/src/styles/base.css +66 -0
- package/src/styles/index.css +10 -0
- package/src/styles/layers.css +9 -0
- package/src/styles/reset.css +66 -0
- package/src/sw/activate.js +51 -0
- package/src/sw/expire.js +47 -0
- package/src/sw/index.js +28 -0
- package/src/sw/install.js +35 -0
- package/src/sw/push.js +58 -0
- package/src/sw/queue.js +60 -0
- package/src/sw/routes.js +71 -0
- package/src/sw/strategies.js +247 -0
- package/src/sw/sync.js +80 -0
- package/src/tokens/index.css +26 -0
- package/src/tokens/primitives/colors.css +54 -0
- package/src/tokens/primitives/motion.css +34 -0
- package/src/tokens/primitives/radius.css +16 -0
- package/src/tokens/primitives/shadow.css +34 -0
- package/src/tokens/primitives/spacing.css +27 -0
- package/src/tokens/primitives/typography.css +46 -0
- package/src/tokens/primitives/zindex.css +18 -0
- package/src/tokens/registered/colors.css +133 -0
- package/src/tokens/registered/dimensions.css +31 -0
- package/src/tokens/semantic/components.css +125 -0
- package/src/tokens/semantic/contrast.css +33 -0
- package/src/tokens/semantic/dark.css +61 -0
- package/src/tokens/semantic/light.css +64 -0
- package/types/core/animations/index.d.ts +52 -0
- package/types/core/api/index.d.ts +68 -0
- package/types/core/events/index.d.ts +50 -0
- package/types/core/offline/index.d.ts +68 -0
- package/types/core/platform/index.d.ts +60 -0
- package/types/core/router/index.d.ts +203 -0
- package/types/core/security/index.d.ts +33 -0
- package/types/core/state/index.d.ts +68 -0
- package/types/core/storage/index.d.ts +40 -0
- package/types/core/ui/index.d.ts +446 -0
- package/types/core/workers/index.d.ts +221 -0
- package/types/elements/index.d.ts +150 -0
- package/types/index.d.ts +18 -0
|
@@ -0,0 +1,773 @@
|
|
|
1
|
+
# Native Router Usage Guide
|
|
2
|
+
|
|
3
|
+
The Native Router layer owns same-document navigation for browser-native applications. It registers URL patterns, matches paths with `URLPattern`, intercepts Navigation API events, runs guards, emits route lifecycle events, coordinates named UI containers, synchronizes duplicate tabs, and opens or closes route-scoped background connections.
|
|
4
|
+
|
|
5
|
+
Status: this guide documents the implemented public router contract: `router.register`, `router.load`, `router.match`, `router.clear`, `router.guard`, `router.guards`, `router.notFound`, `router.miss`, `router.on`, `router.nav.to`, history wrappers, container registry APIs, `router.sync`, `router.links`, connection coordination APIs, and declarative `ui.element` / `ui.container` integration.
|
|
6
|
+
|
|
7
|
+
Import from the router entry point:
|
|
8
|
+
|
|
9
|
+
```javascript
|
|
10
|
+
import { router } from '@adukiorg/anza/router';
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Named exports are also available:
|
|
14
|
+
|
|
15
|
+
```javascript
|
|
16
|
+
import {
|
|
17
|
+
register,
|
|
18
|
+
load,
|
|
19
|
+
match,
|
|
20
|
+
navigate,
|
|
21
|
+
replace,
|
|
22
|
+
back,
|
|
23
|
+
forward,
|
|
24
|
+
on,
|
|
25
|
+
nav
|
|
26
|
+
} from '@adukiorg/anza/router';
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## 1. Choosing an API
|
|
30
|
+
|
|
31
|
+
| Need | Use |
|
|
32
|
+
|---|---|
|
|
33
|
+
| Register a route | `router.register` |
|
|
34
|
+
| Bulk register routes from JSON | `router.load` |
|
|
35
|
+
| Match a URL manually | `router.match` |
|
|
36
|
+
| Remove all routes | `router.clear` |
|
|
37
|
+
| Protect navigation | `router.guard` |
|
|
38
|
+
| Manage guards | `router.guards` |
|
|
39
|
+
| Handle unmatched routes | `router.notFound` |
|
|
40
|
+
| Manage unmatched-route handler | `router.miss` |
|
|
41
|
+
| Listen to route events | `router.on` |
|
|
42
|
+
| Navigate with callbacks | `router.nav.to` |
|
|
43
|
+
| Navigate directly | `router.navigate` |
|
|
44
|
+
| Replace current URL | `router.replace` |
|
|
45
|
+
| Traverse history | `router.back`, `router.forward`, `router.go` |
|
|
46
|
+
| Read history state | `router.current`, `router.entries` |
|
|
47
|
+
| Register a layout target | `router.registerContainer` |
|
|
48
|
+
| Route with UI components | `ui.element`, `ui.container` |
|
|
49
|
+
| Route-scoped connections | `router.links`, `router.registerConnection` |
|
|
50
|
+
| Prefetch route assets | `router.prefetch` |
|
|
51
|
+
| Manage the route cache | `router.cache` |
|
|
52
|
+
| Cross-tab route sync | `router.sync` |
|
|
53
|
+
| Navigation listener teardown | `router.destroy` |
|
|
54
|
+
|
|
55
|
+
## 2. Route Registration
|
|
56
|
+
|
|
57
|
+
Use `router.register(pattern, handler, meta)` to add a route.
|
|
58
|
+
|
|
59
|
+
```javascript
|
|
60
|
+
router.register('/dashboard', 'page-dashboard');
|
|
61
|
+
router.register('/members/:member', 'page-member');
|
|
62
|
+
router.register('/files/*', 'page-files');
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
The handler is usually a tag name string.
|
|
66
|
+
|
|
67
|
+
```javascript
|
|
68
|
+
router.register('/settings', 'page-settings');
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
It can also be a zero-argument function that returns a tag name. This is useful for lazy resolution.
|
|
72
|
+
|
|
73
|
+
```javascript
|
|
74
|
+
router.register('/reports', async () => {
|
|
75
|
+
await import('/pages/reports.js');
|
|
76
|
+
return 'page-reports';
|
|
77
|
+
});
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Route metadata is stored on the route entry and is used by the router and UI integration.
|
|
81
|
+
|
|
82
|
+
```javascript
|
|
83
|
+
router.register('/members/:member', 'page-member', {
|
|
84
|
+
container: 'main',
|
|
85
|
+
title: 'Member'
|
|
86
|
+
});
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Bulk Route Loading
|
|
90
|
+
|
|
91
|
+
Use `router.load(routesData)` to register multiple routes from a JSON array or object. This is useful for loading route definitions from a configuration file or API response.
|
|
92
|
+
|
|
93
|
+
```javascript
|
|
94
|
+
// Load from an array
|
|
95
|
+
router.load([
|
|
96
|
+
{ pattern: '/dashboard', handler: 'page-dashboard', meta: { container: 'main' } },
|
|
97
|
+
{ pattern: '/members/:member', handler: 'page-member', meta: { container: 'main' } },
|
|
98
|
+
{ pattern: '/files/*', handler: 'page-files', meta: { container: 'main' } }
|
|
99
|
+
]);
|
|
100
|
+
|
|
101
|
+
// Load from a single object
|
|
102
|
+
router.load({
|
|
103
|
+
pattern: '/settings',
|
|
104
|
+
handler: 'page-settings',
|
|
105
|
+
meta: { container: 'main' }
|
|
106
|
+
});
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
`router.load` accepts the same handler shapes as `router.register` (tag strings, lazy factories, callbacks, or object forms).
|
|
110
|
+
|
|
111
|
+
### Handler contract
|
|
112
|
+
|
|
113
|
+
A handler is exactly one of the following shapes. This single contract is shared
|
|
114
|
+
by `match()` and the navigation interceptor, so a handler is never invoked twice:
|
|
115
|
+
|
|
116
|
+
| Shape | Meaning |
|
|
117
|
+
|---|---|
|
|
118
|
+
| `'page-tag'` | static element tag |
|
|
119
|
+
| `async () => 'page-tag'` | lazy tag factory (zero arity) |
|
|
120
|
+
| `{ tag: 'page-tag' }` | static tag (object form) |
|
|
121
|
+
| `{ load: async () => 'page-tag' }` | lazy tag factory (object form) |
|
|
122
|
+
| `(params, event) => { ... }` | callback (arity > 0) |
|
|
123
|
+
| `{ handler: (params, event) => { ... } }` | callback (object form) |
|
|
124
|
+
|
|
125
|
+
```javascript
|
|
126
|
+
router.register('/dashboard', 'page-dashboard'); // tag
|
|
127
|
+
router.register('/reports', { load: () => loadReports() }); // lazy
|
|
128
|
+
router.register('/ping', (params, event) => log(params)); // callback
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Rules:
|
|
132
|
+
|
|
133
|
+
- Prefer a tag string for routed UI.
|
|
134
|
+
- Use a zero-argument function or `{ load }` for lazy tag resolution.
|
|
135
|
+
- `match()` resolves a tag without ever running callbacks; callbacks run once, on
|
|
136
|
+
navigation only.
|
|
137
|
+
- More specific routes are sorted before less specific routes.
|
|
138
|
+
|
|
139
|
+
### Route cache (Cache API)
|
|
140
|
+
|
|
141
|
+
The router exposes an internal cache backed by the browser Cache API for view
|
|
142
|
+
assets and lazily-loaded module responses. Prefetch on hover or when a link
|
|
143
|
+
becomes visible to make navigation instant.
|
|
144
|
+
|
|
145
|
+
```javascript
|
|
146
|
+
// Warm the cache for a route's assets
|
|
147
|
+
await router.prefetch('/pages/reports.js');
|
|
148
|
+
|
|
149
|
+
// Lower-level control
|
|
150
|
+
await router.cache.set('/pages/reports.js', response, 60_000); // ttl ms
|
|
151
|
+
const hit = await router.cache.get('/pages/reports.js'); // Response | null
|
|
152
|
+
await router.cache.purge('/pages/reports.js'); // one entry
|
|
153
|
+
await router.cache.purge(); // whole cache
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Entries use an `x-expires-at` TTL header (the same convention as the storage and
|
|
157
|
+
api caches) and expire automatically on read.
|
|
158
|
+
|
|
159
|
+
## 3. Pattern Matching
|
|
160
|
+
|
|
161
|
+
`router.match(url)` returns the first matching route or `null`.
|
|
162
|
+
|
|
163
|
+
```javascript
|
|
164
|
+
const found = await router.match('/members/42');
|
|
165
|
+
|
|
166
|
+
if (found) {
|
|
167
|
+
console.log(found.tag); // "page-member"
|
|
168
|
+
console.log(found.params.member); // "42"
|
|
169
|
+
}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
Match result shape:
|
|
173
|
+
|
|
174
|
+
```javascript
|
|
175
|
+
{
|
|
176
|
+
route,
|
|
177
|
+
tag,
|
|
178
|
+
params,
|
|
179
|
+
result
|
|
180
|
+
}
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
Examples:
|
|
184
|
+
|
|
185
|
+
```javascript
|
|
186
|
+
router.register('/about', 'page-about');
|
|
187
|
+
router.register('/members/:member', 'page-member');
|
|
188
|
+
router.register('/assets/*', 'page-assets');
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
Specificity order:
|
|
192
|
+
|
|
193
|
+
1. Static paths: `/settings/profile`
|
|
194
|
+
2. Parameter paths: `/members/:member`
|
|
195
|
+
3. Wildcards: `/assets/*`
|
|
196
|
+
|
|
197
|
+
Within each group, longer patterns sort first.
|
|
198
|
+
|
|
199
|
+
## 4. Clearing Routes
|
|
200
|
+
|
|
201
|
+
Use `router.clear()` in tests or full app teardown.
|
|
202
|
+
|
|
203
|
+
```javascript
|
|
204
|
+
router.clear();
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
`clear()` only clears the route table. It does not clear guards, event listeners, containers, sync, or background connections.
|
|
208
|
+
|
|
209
|
+
Use `router.destroy()` to remove Navigation API listeners and reset guard, event, not-found, and sync listener state.
|
|
210
|
+
|
|
211
|
+
```javascript
|
|
212
|
+
router.destroy();
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
## 5. Navigation Events
|
|
216
|
+
|
|
217
|
+
Use `router.on(type, callback, signal?)` to subscribe to router events.
|
|
218
|
+
|
|
219
|
+
```javascript
|
|
220
|
+
const stop = router.on('found', ({ tag, params, url, direction }) => {
|
|
221
|
+
console.log(tag, params, url, direction);
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
stop();
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
Supported events:
|
|
228
|
+
|
|
229
|
+
```javascript
|
|
230
|
+
'found'
|
|
231
|
+
'notfound'
|
|
232
|
+
'error'
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
Event payloads:
|
|
236
|
+
|
|
237
|
+
```javascript
|
|
238
|
+
router.on('found', ({ tag, params, url, direction }) => {});
|
|
239
|
+
router.on('notfound', ({ url }) => {});
|
|
240
|
+
router.on('error', ({ error, url, route, phase }) => {});
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
With abort cleanup:
|
|
244
|
+
|
|
245
|
+
```javascript
|
|
246
|
+
const ctrl = new AbortController();
|
|
247
|
+
|
|
248
|
+
router.on('found', refresh, ctrl.signal);
|
|
249
|
+
|
|
250
|
+
ctrl.abort();
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
Rules:
|
|
254
|
+
|
|
255
|
+
- Listener errors are caught and logged.
|
|
256
|
+
- `router.on` returns a disposer.
|
|
257
|
+
- Pass an `AbortSignal` when the listener belongs to a component lifecycle.
|
|
258
|
+
|
|
259
|
+
## 6. Programmatic Navigation
|
|
260
|
+
|
|
261
|
+
Use `router.navigate(url, options?)` for normal navigation.
|
|
262
|
+
|
|
263
|
+
```javascript
|
|
264
|
+
router.navigate('/members/42');
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
With state and transient info:
|
|
268
|
+
|
|
269
|
+
```javascript
|
|
270
|
+
router.navigate('/members/42', {
|
|
271
|
+
state: { scroll: 0 },
|
|
272
|
+
info: { source: 'search' }
|
|
273
|
+
});
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
Use `router.replace(url, options?)` when the URL change should not add a new history entry.
|
|
277
|
+
|
|
278
|
+
```javascript
|
|
279
|
+
router.replace('/members?page=2');
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
History traversal:
|
|
283
|
+
|
|
284
|
+
```javascript
|
|
285
|
+
router.back();
|
|
286
|
+
router.forward();
|
|
287
|
+
router.go(-2);
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
Read current entries:
|
|
291
|
+
|
|
292
|
+
```javascript
|
|
293
|
+
const entry = router.current();
|
|
294
|
+
const all = router.entries();
|
|
295
|
+
|
|
296
|
+
if (router.canBack()) {
|
|
297
|
+
router.back();
|
|
298
|
+
}
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
These wrappers delegate to `window.navigation`. In non-browser environments or runtimes without Navigation API support, they return safe fallback values.
|
|
302
|
+
|
|
303
|
+
## 7. Fluent Navigation
|
|
304
|
+
|
|
305
|
+
Use `router.nav.to(url)` when you want callbacks tied to a single navigation.
|
|
306
|
+
|
|
307
|
+
```javascript
|
|
308
|
+
router.nav.to('/members/42')
|
|
309
|
+
.on('found', ({ tag, params }) => {
|
|
310
|
+
console.log(tag, params.member);
|
|
311
|
+
})
|
|
312
|
+
.on('notfound', ({ url }) => {
|
|
313
|
+
console.warn('No route:', url);
|
|
314
|
+
})
|
|
315
|
+
.on('error', (err) => {
|
|
316
|
+
console.error(err);
|
|
317
|
+
});
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
Rules:
|
|
321
|
+
|
|
322
|
+
- `nav.to` returns a transition controller.
|
|
323
|
+
- `.on(event, callback)` is chainable.
|
|
324
|
+
- Supported fluent events are `found`, `notfound`, and `error`.
|
|
325
|
+
- URL matching for callbacks compares pathnames, so absolute and relative URLs can both work.
|
|
326
|
+
|
|
327
|
+
## 8. Guards
|
|
328
|
+
|
|
329
|
+
Use `router.guard(fn)` to protect navigations.
|
|
330
|
+
|
|
331
|
+
```javascript
|
|
332
|
+
const stop = router.guard((destination) => {
|
|
333
|
+
const url = new URL(destination.url);
|
|
334
|
+
|
|
335
|
+
if (url.pathname.startsWith('/admin') && !session.admin) {
|
|
336
|
+
return '/login';
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
return null;
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
stop();
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
Guard shape:
|
|
346
|
+
|
|
347
|
+
```javascript
|
|
348
|
+
(destination, controller) => string | null | undefined | Promise<string | null | undefined>
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
Rules:
|
|
352
|
+
|
|
353
|
+
- Return a URL string to redirect.
|
|
354
|
+
- Return `null` or `undefined` to allow.
|
|
355
|
+
- Guards run in registration order.
|
|
356
|
+
- In browsers with `precommitHandler`, guards run before commit.
|
|
357
|
+
- In Safari-style fallback paths, guards also run inside the handler after commit and then replace the URL if blocked.
|
|
358
|
+
|
|
359
|
+
Grouped guard API:
|
|
360
|
+
|
|
361
|
+
```javascript
|
|
362
|
+
const stop = router.guards.add(fn);
|
|
363
|
+
router.guards.clear();
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
## 9. Not Found
|
|
367
|
+
|
|
368
|
+
Use `router.notFound(handler)` for unmatched routes.
|
|
369
|
+
|
|
370
|
+
```javascript
|
|
371
|
+
const stop = router.notFound(async (event) => {
|
|
372
|
+
const page = document.createElement('page-not-found');
|
|
373
|
+
const main = router.getContainer('main');
|
|
374
|
+
main?.replaceChildren(page);
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
stop();
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
The router also emits `notfound`.
|
|
381
|
+
|
|
382
|
+
```javascript
|
|
383
|
+
router.on('notfound', ({ url }) => {
|
|
384
|
+
console.warn(`No route matched ${url}`);
|
|
385
|
+
});
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
Rules:
|
|
389
|
+
|
|
390
|
+
- `notFound` sets one global unmatched-route handler.
|
|
391
|
+
- If no handler exists, the router logs an error.
|
|
392
|
+
|
|
393
|
+
Grouped unmatched-route API:
|
|
394
|
+
|
|
395
|
+
```javascript
|
|
396
|
+
router.miss.set(handler);
|
|
397
|
+
router.miss.clear();
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
## 10. Declarative UI Routes
|
|
401
|
+
|
|
402
|
+
The UI layer can register routes automatically through `ui.element`.
|
|
403
|
+
|
|
404
|
+
```javascript
|
|
405
|
+
import { ui } from '@adukiorg/anza/ui';
|
|
406
|
+
|
|
407
|
+
ui.element('page-member', {
|
|
408
|
+
url: '/members/:member',
|
|
409
|
+
container: 'main',
|
|
410
|
+
|
|
411
|
+
props: {
|
|
412
|
+
member: { type: String, default: '' }
|
|
413
|
+
},
|
|
414
|
+
|
|
415
|
+
template: './member.html',
|
|
416
|
+
style: './member.css',
|
|
417
|
+
|
|
418
|
+
mount({ el }) {
|
|
419
|
+
loadMember(el.member);
|
|
420
|
+
},
|
|
421
|
+
|
|
422
|
+
update({ el, name, val }) {
|
|
423
|
+
if (name === 'member') {
|
|
424
|
+
loadMember(val);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
}, import.meta.url);
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
What happens:
|
|
431
|
+
|
|
432
|
+
1. `ui.element` calls `router.register(url, tag, meta)`.
|
|
433
|
+
2. The router matches the URL.
|
|
434
|
+
3. The UI orchestrator listens for `found`.
|
|
435
|
+
4. The orchestrator finds the named container.
|
|
436
|
+
5. The page element is created and params are assigned as properties.
|
|
437
|
+
6. If the same page tag is already mounted, params are updated on the existing instance.
|
|
438
|
+
|
|
439
|
+
The native toolchain also emits `routes.json` for declarative routes during `scan`, `build`, and `dev`.
|
|
440
|
+
|
|
441
|
+
```json
|
|
442
|
+
{
|
|
443
|
+
"version": 1,
|
|
444
|
+
"routes": [
|
|
445
|
+
{
|
|
446
|
+
"tag": "page-member",
|
|
447
|
+
"path": "/members/:member",
|
|
448
|
+
"container": "main",
|
|
449
|
+
"params": ["member"]
|
|
450
|
+
}
|
|
451
|
+
]
|
|
452
|
+
}
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
## 11. Containers
|
|
456
|
+
|
|
457
|
+
Use `ui.container` for router-owned layout slots.
|
|
458
|
+
|
|
459
|
+
```javascript
|
|
460
|
+
ui.container('app-main', {
|
|
461
|
+
template: '<slot></slot>',
|
|
462
|
+
style: ':host { display: block; }'
|
|
463
|
+
});
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
Mount it in HTML:
|
|
467
|
+
|
|
468
|
+
```html
|
|
469
|
+
<app-main name="main"></app-main>
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
The container registers itself with the router using its `name` attribute or tag name.
|
|
473
|
+
|
|
474
|
+
```javascript
|
|
475
|
+
const main = router.getContainer('main');
|
|
476
|
+
```
|
|
477
|
+
|
|
478
|
+
Container registry APIs:
|
|
479
|
+
|
|
480
|
+
```javascript
|
|
481
|
+
router.registerContainer('main', element);
|
|
482
|
+
router.unregisterContainer('main', element);
|
|
483
|
+
router.getContainer('main');
|
|
484
|
+
router.clearContainers();
|
|
485
|
+
```
|
|
486
|
+
|
|
487
|
+
Rules:
|
|
488
|
+
|
|
489
|
+
- Container names are unique.
|
|
490
|
+
- Registering a second live container with the same name throws.
|
|
491
|
+
- `getContainer(name)` can also resolve CSS selectors such as `#app`.
|
|
492
|
+
- A route with `meta.container` fails if its required container is not active.
|
|
493
|
+
|
|
494
|
+
## 12. Container Swaps
|
|
495
|
+
|
|
496
|
+
`ui.container` injects `swapView(newElement, options?)`.
|
|
497
|
+
|
|
498
|
+
```javascript
|
|
499
|
+
await main.swapView(document.createElement('page-home'), {
|
|
500
|
+
direction: 'push'
|
|
501
|
+
});
|
|
502
|
+
```
|
|
503
|
+
|
|
504
|
+
Swap behavior:
|
|
505
|
+
|
|
506
|
+
1. Try element-scoped `startViewTransition`.
|
|
507
|
+
2. Fall back to `document.startViewTransition`.
|
|
508
|
+
3. Fall back to `replaceChildren`.
|
|
509
|
+
|
|
510
|
+
The container sets `data-transition-direction` while swapping.
|
|
511
|
+
|
|
512
|
+
```css
|
|
513
|
+
:host([data-transition-direction="push"]) {
|
|
514
|
+
view-transition-name: main;
|
|
515
|
+
}
|
|
516
|
+
```
|
|
517
|
+
|
|
518
|
+
## 13. Manual Containers
|
|
519
|
+
|
|
520
|
+
You can register a normal DOM node manually.
|
|
521
|
+
|
|
522
|
+
```javascript
|
|
523
|
+
const el = document.querySelector('#main');
|
|
524
|
+
|
|
525
|
+
router.registerContainer('main', el);
|
|
526
|
+
|
|
527
|
+
router.on('found', ({ tag, params }) => {
|
|
528
|
+
const page = document.createElement(tag);
|
|
529
|
+
Object.assign(page, params);
|
|
530
|
+
el.replaceChildren(page);
|
|
531
|
+
});
|
|
532
|
+
```
|
|
533
|
+
|
|
534
|
+
Clean up when the container is removed.
|
|
535
|
+
|
|
536
|
+
```javascript
|
|
537
|
+
router.unregisterContainer('main', el);
|
|
538
|
+
```
|
|
539
|
+
|
|
540
|
+
## 14. Route-Scoped Connections
|
|
541
|
+
|
|
542
|
+
Use `router.links.add(pattern, factory)` to manage background connections tied to active routes.
|
|
543
|
+
|
|
544
|
+
```javascript
|
|
545
|
+
const stop = router.links.add('/rooms/:room', async ({ url, params }) => {
|
|
546
|
+
console.log(params.room);
|
|
547
|
+
|
|
548
|
+
const socket = new WebSocket(`wss://example.com${url.pathname}`);
|
|
549
|
+
|
|
550
|
+
return {
|
|
551
|
+
close() {
|
|
552
|
+
socket.close();
|
|
553
|
+
}
|
|
554
|
+
};
|
|
555
|
+
});
|
|
556
|
+
|
|
557
|
+
stop();
|
|
558
|
+
```
|
|
559
|
+
|
|
560
|
+
The older direct API remains available:
|
|
561
|
+
|
|
562
|
+
```javascript
|
|
563
|
+
const stop = router.registerConnection('/rooms/:room', factory);
|
|
564
|
+
```
|
|
565
|
+
|
|
566
|
+
The router coordinates connections after `navigatesuccess`.
|
|
567
|
+
|
|
568
|
+
```javascript
|
|
569
|
+
router.getActiveConnections();
|
|
570
|
+
router.links.remove('/rooms/:room');
|
|
571
|
+
router.links.clear();
|
|
572
|
+
router.clearConnections();
|
|
573
|
+
```
|
|
574
|
+
|
|
575
|
+
### Links API
|
|
576
|
+
|
|
577
|
+
The `router.links` object provides normalized verbs for managing route-scoped connections:
|
|
578
|
+
|
|
579
|
+
| Method | Purpose |
|
|
580
|
+
|---|---|
|
|
581
|
+
| `router.links.add(pattern, factory)` | Register a connection factory (returns disposer) |
|
|
582
|
+
| `router.links.remove(pattern)` | Remove a specific connection factory and close its active connection |
|
|
583
|
+
| `router.links.clear()` | Remove all connection factories and close all active connections |
|
|
584
|
+
|
|
585
|
+
Connection behavior:
|
|
586
|
+
|
|
587
|
+
- A matching route opens the connection if it is not active.
|
|
588
|
+
- Navigating away closes stale connections.
|
|
589
|
+
- The factory receives `{ url, params }`.
|
|
590
|
+
- A connection object can expose `close()` or `disconnect()`.
|
|
591
|
+
- Factory errors are caught and logged.
|
|
592
|
+
|
|
593
|
+
For tests or manual coordination:
|
|
594
|
+
|
|
595
|
+
```javascript
|
|
596
|
+
import { coordinateConnections } from '@adukiorg/anza/router';
|
|
597
|
+
|
|
598
|
+
await coordinateConnections('/rooms/general');
|
|
599
|
+
```
|
|
600
|
+
|
|
601
|
+
## 15. Cross-Tab Sync
|
|
602
|
+
|
|
603
|
+
The router bootstraps tab sync on client load when `BroadcastChannel` and `window.navigation` exist.
|
|
604
|
+
|
|
605
|
+
Behavior:
|
|
606
|
+
|
|
607
|
+
- On `navigatesuccess`, the current URL is broadcast on `native-router-sync`.
|
|
608
|
+
- Other same-origin tabs receive the message.
|
|
609
|
+
- The receiving tab calls `router.navigate(url, { state })`.
|
|
610
|
+
- A loop guard prevents repeated same-URL sync events.
|
|
611
|
+
|
|
612
|
+
Public controls:
|
|
613
|
+
|
|
614
|
+
```javascript
|
|
615
|
+
router.sync.stop();
|
|
616
|
+
router.sync.start();
|
|
617
|
+
router.sync.active();
|
|
618
|
+
router.sync.close();
|
|
619
|
+
```
|
|
620
|
+
|
|
621
|
+
Rules:
|
|
622
|
+
|
|
623
|
+
- `stop()` pauses handlers without closing the channel.
|
|
624
|
+
- `start()` resumes sync.
|
|
625
|
+
- `active()` reports whether sync is running.
|
|
626
|
+
- `close()` stops sync, closes the channel, and resets internal state.
|
|
627
|
+
|
|
628
|
+
## 16. View Transitions
|
|
629
|
+
|
|
630
|
+
The router wraps route event work in `transitions.run`.
|
|
631
|
+
|
|
632
|
+
```javascript
|
|
633
|
+
import { transitions } from '@adukiorg/anza/router';
|
|
634
|
+
|
|
635
|
+
await transitions.run(() => {
|
|
636
|
+
outlet.replaceChildren(page);
|
|
637
|
+
});
|
|
638
|
+
```
|
|
639
|
+
|
|
640
|
+
Shared element source:
|
|
641
|
+
|
|
642
|
+
```javascript
|
|
643
|
+
await transitions.run(() => {
|
|
644
|
+
outlet.replaceChildren(detail);
|
|
645
|
+
}, {
|
|
646
|
+
sourceElement: card,
|
|
647
|
+
name: 'selected-card'
|
|
648
|
+
});
|
|
649
|
+
```
|
|
650
|
+
|
|
651
|
+
Rules:
|
|
652
|
+
|
|
653
|
+
- If View Transitions are unsupported, the callback runs directly.
|
|
654
|
+
- If `prefers-reduced-motion: reduce` matches, the callback runs directly.
|
|
655
|
+
- `sourceElement.style.viewTransitionName` is set temporarily.
|
|
656
|
+
- The name is cleared after transition completion.
|
|
657
|
+
- Aborted transition errors are caught and logged.
|
|
658
|
+
|
|
659
|
+
## 17. Router-Aware Links
|
|
660
|
+
|
|
661
|
+
The elements package registers `<nav-link>`.
|
|
662
|
+
|
|
663
|
+
```html
|
|
664
|
+
<nav-link href="/members">Members</nav-link>
|
|
665
|
+
```
|
|
666
|
+
|
|
667
|
+
Behavior:
|
|
668
|
+
|
|
669
|
+
- Same-origin clicks call router navigation.
|
|
670
|
+
- Modified clicks, `_blank`, hash-only links, and external links use normal browser behavior.
|
|
671
|
+
- Matching active routes set `aria-current="page"` on the internal anchor.
|
|
672
|
+
- External links dispatch a cancelable `external` event.
|
|
673
|
+
|
|
674
|
+
External gate:
|
|
675
|
+
|
|
676
|
+
```javascript
|
|
677
|
+
document.addEventListener('external', (event) => {
|
|
678
|
+
if (!confirm(`Open ${event.detail.href}?`)) {
|
|
679
|
+
event.preventDefault();
|
|
680
|
+
}
|
|
681
|
+
});
|
|
682
|
+
```
|
|
683
|
+
|
|
684
|
+
## 18. Testing Router Code
|
|
685
|
+
|
|
686
|
+
Clear route state between tests.
|
|
687
|
+
|
|
688
|
+
```javascript
|
|
689
|
+
beforeEach(() => {
|
|
690
|
+
router.clear();
|
|
691
|
+
router.guards.clear();
|
|
692
|
+
router.miss.clear();
|
|
693
|
+
router.clearContainers();
|
|
694
|
+
router.clearConnections();
|
|
695
|
+
router.sync.stop();
|
|
696
|
+
});
|
|
697
|
+
```
|
|
698
|
+
|
|
699
|
+
Test matching directly:
|
|
700
|
+
|
|
701
|
+
```javascript
|
|
702
|
+
router.register('/members/:member', 'page-member');
|
|
703
|
+
|
|
704
|
+
const found = await router.match('/members/42');
|
|
705
|
+
|
|
706
|
+
if (found.params.member !== '42') {
|
|
707
|
+
throw new Error('wrong param');
|
|
708
|
+
}
|
|
709
|
+
```
|
|
710
|
+
|
|
711
|
+
Test route events with disposers:
|
|
712
|
+
|
|
713
|
+
```javascript
|
|
714
|
+
const stop = router.on('found', (detail) => {
|
|
715
|
+
console.log(detail.tag);
|
|
716
|
+
});
|
|
717
|
+
|
|
718
|
+
stop();
|
|
719
|
+
```
|
|
720
|
+
|
|
721
|
+
## 19. Runtime Requirements
|
|
722
|
+
|
|
723
|
+
The router is built around modern browser APIs:
|
|
724
|
+
|
|
725
|
+
- `window.navigation`
|
|
726
|
+
- `URLPattern`
|
|
727
|
+
- `BroadcastChannel`
|
|
728
|
+
- `WeakRef`
|
|
729
|
+
- `FinalizationRegistry`
|
|
730
|
+
- `MutationObserver`
|
|
731
|
+
- `document.startViewTransition`
|
|
732
|
+
|
|
733
|
+
Polyfills exist in `core/platform` for Navigation API and URLPattern. Other APIs degrade where possible.
|
|
734
|
+
|
|
735
|
+
## 20. Security
|
|
736
|
+
|
|
737
|
+
- Treat route params as untrusted strings.
|
|
738
|
+
- Validate params before using them in network requests.
|
|
739
|
+
- Use guards for client-side UX protection, not as the only security boundary.
|
|
740
|
+
- Protect sensitive routes on the server or service worker layer too.
|
|
741
|
+
- Avoid direct `location.href`, `history.pushState`, and `history.replaceState`.
|
|
742
|
+
- Use `router.navigate` and `router.replace`.
|
|
743
|
+
- Keep guard callbacks fast and deterministic.
|
|
744
|
+
|
|
745
|
+
## 21. Performance
|
|
746
|
+
|
|
747
|
+
- Register routes once at startup.
|
|
748
|
+
- Prefer tag strings or zero-argument lazy tag factories.
|
|
749
|
+
- Keep route guards fast.
|
|
750
|
+
- Use route-scoped connections for sockets and streams.
|
|
751
|
+
- Use containers to avoid remounting stable layouts.
|
|
752
|
+
- Use `replace` for filter/search URL updates that should not create history entries.
|
|
753
|
+
- Debounce search-as-you-type URL updates.
|
|
754
|
+
|
|
755
|
+
## 22. Checklist
|
|
756
|
+
|
|
757
|
+
- Use `router.register` for manual routes.
|
|
758
|
+
- Use `router.load` to bulk-register routes from JSON configuration.
|
|
759
|
+
- Use `ui.element({ url, container })` for declarative page routes.
|
|
760
|
+
- Use `ui.container` for named route outlets.
|
|
761
|
+
- Use `router.on('found')`, `router.on('notfound')`, and `router.on('error')` for events.
|
|
762
|
+
- Use `router.guard` for client-side route gates.
|
|
763
|
+
- Use `router.guards.clear()` in tests or teardown.
|
|
764
|
+
- Use `router.notFound` for unmatched routes.
|
|
765
|
+
- Use `router.miss.clear()` to reset the unmatched-route handler.
|
|
766
|
+
- Use `router.navigate` and `router.replace` for URL writes.
|
|
767
|
+
- Use `router.nav.to` for chainable navigation callbacks.
|
|
768
|
+
- Use `router.links` (add/remove/clear) for route-scoped background connections.
|
|
769
|
+
- Use `router.registerConnection` for the direct connection factory API.
|
|
770
|
+
- Use `router.sync.stop()` when tab mirroring should pause.
|
|
771
|
+
- Clear routes, containers, and connections in tests.
|
|
772
|
+
- Keep URL state in the URL.
|
|
773
|
+
- Treat route params as untrusted input.
|