@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,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/core/platform/polyfills/urlpattern.js
|
|
3
|
+
*
|
|
4
|
+
* A robust URLPattern fallback for browsers lacking native support.
|
|
5
|
+
* Implements pathname matching with named parameters, modifiers, wildcards,
|
|
6
|
+
* and smart trailing slash normalization.
|
|
7
|
+
*
|
|
8
|
+
* Source: doc 18 §3, library2.md §Phase 1-A
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
class URLPatternPolyfill {
|
|
12
|
+
#pathnameRegex;
|
|
13
|
+
#paramNames = [];
|
|
14
|
+
|
|
15
|
+
constructor(init) {
|
|
16
|
+
let pathnamePattern = '*';
|
|
17
|
+
if (typeof init === 'string') {
|
|
18
|
+
if (init.includes('://')) {
|
|
19
|
+
try {
|
|
20
|
+
const match = init.match(/^[a-zA-Z]+:\/\/[^\/]+(\/[^?#]*)/);
|
|
21
|
+
pathnamePattern = match ? match[1] : '/';
|
|
22
|
+
} catch {
|
|
23
|
+
pathnamePattern = init;
|
|
24
|
+
}
|
|
25
|
+
} else {
|
|
26
|
+
pathnamePattern = init;
|
|
27
|
+
}
|
|
28
|
+
} else if (init && typeof init === 'object') {
|
|
29
|
+
pathnamePattern = init.pathname || '*';
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Ensure pathnamePattern starts with /
|
|
33
|
+
if (!pathnamePattern.startsWith('/') && pathnamePattern !== '*') {
|
|
34
|
+
pathnamePattern = '/' + pathnamePattern;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
this.#paramNames = [];
|
|
38
|
+
let wildcardCount = 0;
|
|
39
|
+
|
|
40
|
+
const segments = pathnamePattern.split('/');
|
|
41
|
+
const regexSegments = segments.map((segment, idx) => {
|
|
42
|
+
if (idx === 0 && segment === '') return '';
|
|
43
|
+
|
|
44
|
+
if (segment.startsWith(':')) {
|
|
45
|
+
let paramName = segment.slice(1);
|
|
46
|
+
let modifier = '';
|
|
47
|
+
if (paramName.endsWith('?') || paramName.endsWith('*') || paramName.endsWith('+')) {
|
|
48
|
+
modifier = paramName.slice(-1);
|
|
49
|
+
paramName = paramName.slice(0, -1);
|
|
50
|
+
}
|
|
51
|
+
this.#paramNames.push(paramName);
|
|
52
|
+
|
|
53
|
+
if (modifier === '?') {
|
|
54
|
+
return '([^/]*)';
|
|
55
|
+
} else if (modifier === '*') {
|
|
56
|
+
return '(.*)';
|
|
57
|
+
} else if (modifier === '+') {
|
|
58
|
+
return '([^/]+.*)';
|
|
59
|
+
} else {
|
|
60
|
+
return '([^/]+)';
|
|
61
|
+
}
|
|
62
|
+
} else if (segment === '*') {
|
|
63
|
+
this.#paramNames.push(String(wildcardCount++));
|
|
64
|
+
return '(.*)';
|
|
65
|
+
} else if (segment.includes('*')) {
|
|
66
|
+
this.#paramNames.push(String(wildcardCount++));
|
|
67
|
+
return segment.replace(/\*/g, '(.*)');
|
|
68
|
+
} else {
|
|
69
|
+
return segment.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
let patternStr = regexSegments.join('/');
|
|
74
|
+
if (patternStr === '') patternStr = '/';
|
|
75
|
+
|
|
76
|
+
// Support optional trailing slash if the pattern does not end in a wildcard or parameter modifier
|
|
77
|
+
if (!pathnamePattern.endsWith('*') && !pathnamePattern.endsWith('?') && pathnamePattern !== '/') {
|
|
78
|
+
patternStr += '/?';
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
this.#pathnameRegex = new RegExp('^' + patternStr + '$');
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
test(url) {
|
|
85
|
+
try {
|
|
86
|
+
const parsed = new URL(url, globalThis.location?.href || 'https://a.com');
|
|
87
|
+
return this.#pathnameRegex.test(parsed.pathname);
|
|
88
|
+
} catch {
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
exec(url) {
|
|
94
|
+
try {
|
|
95
|
+
const parsed = new URL(url, globalThis.location?.href || 'https://a.com');
|
|
96
|
+
const match = parsed.pathname.match(this.#pathnameRegex);
|
|
97
|
+
if (!match) return null;
|
|
98
|
+
|
|
99
|
+
const groups = {};
|
|
100
|
+
this.#paramNames.forEach((name, index) => {
|
|
101
|
+
groups[name] = match[index + 1] || '';
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
return {
|
|
105
|
+
pathname: {
|
|
106
|
+
input: parsed.pathname,
|
|
107
|
+
groups
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
} catch {
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (!globalThis.URLPattern) {
|
|
117
|
+
globalThis.URLPattern = URLPatternPolyfill;
|
|
118
|
+
}
|
|
119
|
+
export default URLPatternPolyfill;
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* core/platform/supports.js
|
|
3
|
+
*
|
|
4
|
+
* All feature-detection booleans for the platform.
|
|
5
|
+
* Each flag is lazily evaluated on first access and then cached.
|
|
6
|
+
* Detection tests are conservative — they check for correct behaviour,
|
|
7
|
+
* not just API presence, where known browser bugs exist.
|
|
8
|
+
*
|
|
9
|
+
* Source authority: doc 18 — Limitations, Browser Gaps, and Polyfill Strategy §17
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const _cache = Object.create(null);
|
|
13
|
+
|
|
14
|
+
function lazy(key, detect) {
|
|
15
|
+
Object.defineProperty(supports, key, {
|
|
16
|
+
get() {
|
|
17
|
+
if (!(key in _cache)) _cache[key] = detect();
|
|
18
|
+
return _cache[key];
|
|
19
|
+
},
|
|
20
|
+
configurable: true,
|
|
21
|
+
enumerable: true,
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export const supports = {};
|
|
26
|
+
|
|
27
|
+
// --- Routing ---
|
|
28
|
+
|
|
29
|
+
lazy('navigationAPI', () => 'navigation' in window);
|
|
30
|
+
|
|
31
|
+
lazy('urlPattern', () => 'URLPattern' in globalThis);
|
|
32
|
+
|
|
33
|
+
// --- Component Model ---
|
|
34
|
+
|
|
35
|
+
lazy('declarativeShadowDOM', () =>
|
|
36
|
+
'shadowRootMode' in HTMLTemplateElement.prototype
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
lazy('customStatePseudo', () =>
|
|
40
|
+
typeof ElementInternals !== 'undefined' &&
|
|
41
|
+
'states' in ElementInternals.prototype
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
lazy('formAssociated', () =>
|
|
45
|
+
typeof HTMLElement !== 'undefined' &&
|
|
46
|
+
'attachInternals' in HTMLElement.prototype
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
// --- Overlay / Popover ---
|
|
50
|
+
|
|
51
|
+
lazy('popoverAPI', () => 'popover' in HTMLElement.prototype);
|
|
52
|
+
|
|
53
|
+
lazy('anchorPositioning', () =>
|
|
54
|
+
CSS.supports('anchor-name', '--a')
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
// --- Animation ---
|
|
58
|
+
|
|
59
|
+
lazy('viewTransitions', () => 'startViewTransition' in document);
|
|
60
|
+
|
|
61
|
+
lazy('scrollTimeline', () => 'ScrollTimeline' in window);
|
|
62
|
+
|
|
63
|
+
lazy('viewTimeline', () => 'ViewTimeline' in window);
|
|
64
|
+
|
|
65
|
+
// --- Scheduling ---
|
|
66
|
+
|
|
67
|
+
lazy('schedulerPostTask', () =>
|
|
68
|
+
'scheduler' in globalThis && 'postTask' in scheduler
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
lazy('schedulerYield', () =>
|
|
72
|
+
'scheduler' in globalThis && 'yield' in scheduler
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
// --- CSS ---
|
|
76
|
+
|
|
77
|
+
lazy('contentVisibility', () =>
|
|
78
|
+
CSS.supports('content-visibility', 'auto')
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
lazy('cssScope', () => CSS.supports('selector(:scope)'));
|
|
82
|
+
|
|
83
|
+
lazy('cssLayer', () => {
|
|
84
|
+
try {
|
|
85
|
+
// @supports at-rule(@layer) is not universally parseable in JS;
|
|
86
|
+
// inject a sheet and check if a layer-wrapped rule is applied.
|
|
87
|
+
const s = document.createElement('style');
|
|
88
|
+
s.textContent = '@layer _test { ._testlayer { --_l: 1 } }';
|
|
89
|
+
document.head.append(s);
|
|
90
|
+
const ok = !!s.sheet;
|
|
91
|
+
s.remove();
|
|
92
|
+
return ok;
|
|
93
|
+
} catch { return false; }
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
lazy('cssModuleScripts', () => {
|
|
97
|
+
// CSS Module Scripts: import with { type: 'css' }
|
|
98
|
+
// Absent Firefox / Safari as of 2026 — doc 18 §14, library2.md §CSS Distribution
|
|
99
|
+
try {
|
|
100
|
+
return HTMLScriptElement.supports('importmap'); // proxy: if importmap exists, env is modern enough to test
|
|
101
|
+
// We check actual CSS module support via import attributes presence
|
|
102
|
+
} catch { return false; }
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
// --- Module System ---
|
|
106
|
+
|
|
107
|
+
lazy('importMaps', () =>
|
|
108
|
+
HTMLScriptElement.supports && HTMLScriptElement.supports('importmap')
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
// --- Security ---
|
|
112
|
+
|
|
113
|
+
lazy('sanitizerAPI', () => 'Sanitizer' in window);
|
|
114
|
+
|
|
115
|
+
lazy('trustedTypes', () => 'trustedTypes' in window);
|
|
116
|
+
|
|
117
|
+
lazy('subtleCrypto', () =>
|
|
118
|
+
typeof crypto !== 'undefined' && 'subtle' in crypto
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
// --- Storage ---
|
|
122
|
+
|
|
123
|
+
lazy('opfs', () =>
|
|
124
|
+
'storage' in navigator &&
|
|
125
|
+
typeof navigator.storage.getDirectory === 'function'
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
lazy('storageManager', () => 'storage' in navigator);
|
|
129
|
+
|
|
130
|
+
lazy('fileSystemPickers', () => 'showOpenFilePicker' in window);
|
|
131
|
+
|
|
132
|
+
lazy('compression', () =>
|
|
133
|
+
typeof CompressionStream !== 'undefined' &&
|
|
134
|
+
typeof DecompressionStream !== 'undefined'
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
lazy('storagePersistence', () =>
|
|
138
|
+
'storage' in navigator &&
|
|
139
|
+
typeof navigator.storage.persist === 'function' &&
|
|
140
|
+
typeof navigator.storage.persisted === 'function'
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
// --- Networking / Workers ---
|
|
144
|
+
|
|
145
|
+
lazy('backgroundSync', () =>
|
|
146
|
+
'ServiceWorkerRegistration' in window &&
|
|
147
|
+
'sync' in ServiceWorkerRegistration.prototype
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
lazy('speculationRules', () =>
|
|
151
|
+
HTMLScriptElement.supports &&
|
|
152
|
+
HTMLScriptElement.supports('speculationrules')
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
lazy('sharedWorker', () => 'SharedWorker' in window);
|
|
156
|
+
|
|
157
|
+
lazy('webLocks', () => 'locks' in navigator);
|
|
158
|
+
|
|
159
|
+
lazy('offscreenCanvas', () => 'OffscreenCanvas' in window);
|
|
160
|
+
|
|
161
|
+
// --- Notifications / Push ---
|
|
162
|
+
|
|
163
|
+
lazy('pushAPI', () => 'PushManager' in window);
|
|
164
|
+
|
|
165
|
+
lazy('notificationsAPI', () => 'Notification' in window);
|
|
166
|
+
|
|
167
|
+
// --- Device ---
|
|
168
|
+
|
|
169
|
+
lazy('screenWakeLock', () => 'wakeLock' in navigator);
|
|
170
|
+
|
|
171
|
+
lazy('idleDetection', () => 'IdleDetector' in window);
|
|
172
|
+
|
|
173
|
+
lazy('webAuthn', () => 'PublicKeyCredential' in window);
|
|
174
|
+
|
|
175
|
+
// --- Reset cached value (for testing only) ---
|
|
176
|
+
export function reset(key) {
|
|
177
|
+
delete _cache[key];
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// --- Type guard: assert feature support at runtime ---
|
|
181
|
+
export function typeGuard(key, message) {
|
|
182
|
+
if (!supports[key]) {
|
|
183
|
+
const msg = message || `Platform does not support: ${key}`;
|
|
184
|
+
throw new Error(msg);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
# Platform Module — Developer Usage Guide
|
|
2
|
+
|
|
3
|
+
The `@adukiorg/anza/platform` module acts as a smart, lazy-evaluated browser capability layer and zero-overhead polyfill manager. It exposes simple feature flags, unified asynchronous feature gates, a prioritized task scheduler, leak-proof popovers, and resilient URLPattern pathname matching.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 1. Import System
|
|
8
|
+
|
|
9
|
+
To keep your client application highly performant and structured, import everything directly from the unified platform specifier:
|
|
10
|
+
|
|
11
|
+
```javascript
|
|
12
|
+
import { supports, guard, reset, typeGuard } from '@adukiorg/anza/platform';
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## 2. Browser Capabilities Matrix (`supports`)
|
|
18
|
+
|
|
19
|
+
The `supports` object is a lazy-evaluated, cached registry containing 30+ boolean flags for modern HTML5, CSS, Web Cryptography, OPFS, compression, storage managers, and Worker APIs.
|
|
20
|
+
|
|
21
|
+
### Example Usage
|
|
22
|
+
|
|
23
|
+
```javascript
|
|
24
|
+
import { supports } from '@adukiorg/anza/platform';
|
|
25
|
+
|
|
26
|
+
// 1. OPFS support
|
|
27
|
+
if (supports.opfs) {
|
|
28
|
+
console.log('Origin Private File System is natively supported!');
|
|
29
|
+
} else {
|
|
30
|
+
console.log('OPFS absent; using standard IndexedDB storage fallback.');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// 2. Storage Persistence capability
|
|
34
|
+
if (supports.storagePersistence) {
|
|
35
|
+
navigator.storage.persist().then(granted => {
|
|
36
|
+
console.log(`Durable storage granted: ${granted}`);
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// 3. Compression Streams capability
|
|
41
|
+
if (supports.compression) {
|
|
42
|
+
const compressor = new CompressionStream('gzip');
|
|
43
|
+
console.log('Compression Stream is natively supported!');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// 4. Check CSS View Transitions support
|
|
47
|
+
if (supports.viewTransitions) {
|
|
48
|
+
document.startViewTransition(() => updateTheDOM());
|
|
49
|
+
} else {
|
|
50
|
+
updateTheDOM();
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
*Note: For unit testing, you can clear cached lazy-evaluated values using the `reset(flag)` method. This allows mocking feature states dynamically in tests:*
|
|
55
|
+
|
|
56
|
+
```javascript
|
|
57
|
+
import { reset, supports } from '@adukiorg/anza/platform';
|
|
58
|
+
|
|
59
|
+
// Force urlPattern to false for testing fallback polyfill loading
|
|
60
|
+
Object.defineProperty(supports, 'urlPattern', { value: false, configurable: true });
|
|
61
|
+
|
|
62
|
+
// Run your test assertions here...
|
|
63
|
+
|
|
64
|
+
// Clear mock value and restore original lazy capability checker
|
|
65
|
+
reset('urlPattern');
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Runtime Type Guarding
|
|
69
|
+
|
|
70
|
+
Use `typeGuard` to assert feature support at runtime. It throws an error with a custom message if the feature is not available, making it useful for early-fail checks in critical code paths:
|
|
71
|
+
|
|
72
|
+
```javascript
|
|
73
|
+
import { typeGuard } from '@adukiorg/anza/platform';
|
|
74
|
+
|
|
75
|
+
// Assert OPFS support before attempting file operations
|
|
76
|
+
typeGuard('opfs', 'OPFS is required for this feature');
|
|
77
|
+
|
|
78
|
+
// Assert Navigation API support before routing
|
|
79
|
+
typeGuard('navigationAPI', 'Navigation API is required for routing');
|
|
80
|
+
|
|
81
|
+
// Custom error message
|
|
82
|
+
try {
|
|
83
|
+
typeGuard('viewTransitions', 'View transitions are required for page transitions');
|
|
84
|
+
document.startViewTransition(() => updateDOM());
|
|
85
|
+
} catch (err) {
|
|
86
|
+
// Fallback for browsers without view transitions
|
|
87
|
+
updateDOM();
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## 3. Dynamic Feature Guards (`guard`)
|
|
94
|
+
|
|
95
|
+
Asynchronous wrappers that transparently route calls to native browser APIs (if present) or automatically load, bootstrap, and install the lightweight polyfills on demand.
|
|
96
|
+
|
|
97
|
+
| Guard Method | Returns | Action / Polyfill Fallback |
|
|
98
|
+
| --- | --- | --- |
|
|
99
|
+
| `await guard.urlPattern()` | `URLPattern` Class | Returns spec-compliant pathname matching template class |
|
|
100
|
+
| `await guard.navigation()` | `navigation` Object | Bootstraps the Single Page Application Navigation API polyfill |
|
|
101
|
+
| `await guard.popover()` | `undefined` | Installs HTMLElement popover prototype methods & light dismiss |
|
|
102
|
+
| `await guard.shadow(root)` | `undefined` | Applies polyfill parser for Declarative Shadow DOM template nodes |
|
|
103
|
+
| `await guard.anchor(float, anchor, opts)` | `undefined` | Computes high-performance dynamic anchor positioning fallback |
|
|
104
|
+
| `await guard.sanitizer()` | Sanitizer Wrapper | Exposes uniform `.sanitizeToString(html)` helper |
|
|
105
|
+
| `await guard.scheduler()` | `scheduler` Object | Returns priority-aware task execution queues |
|
|
106
|
+
| `await guard.yield()` | `Promise<undefined>` | Yields execution thread control back to the event loop |
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
## 4. Prioritized Task Scheduler
|
|
111
|
+
|
|
112
|
+
When native `globalThis.scheduler` is absent, the polyfill exposes a robust microtask/macrotask priority queue system supporting three priority levels (`user-blocking`, `user-visible`, `background`), delayed scheduling, and task cancellation via `AbortSignal`.
|
|
113
|
+
|
|
114
|
+
### Prioritized Task Enqueuing
|
|
115
|
+
|
|
116
|
+
```javascript
|
|
117
|
+
import { guard } from '@adukiorg/anza/platform';
|
|
118
|
+
|
|
119
|
+
const scheduler = await guard.scheduler();
|
|
120
|
+
|
|
121
|
+
// 1. High priority UI rendering task
|
|
122
|
+
scheduler.postTask(() => {
|
|
123
|
+
renderCriticalInteractiveUI();
|
|
124
|
+
}, { priority: 'user-blocking' });
|
|
125
|
+
|
|
126
|
+
// 2. Default priority application logic task
|
|
127
|
+
scheduler.postTask(() => {
|
|
128
|
+
fetchFreshStateUpdates();
|
|
129
|
+
}, { priority: 'user-visible' });
|
|
130
|
+
|
|
131
|
+
// 3. Low priority telemetry sync task
|
|
132
|
+
scheduler.postTask(() => {
|
|
133
|
+
flushTelemetryLogs();
|
|
134
|
+
}, { priority: 'background' });
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### Delayed Task Execution
|
|
138
|
+
|
|
139
|
+
```javascript
|
|
140
|
+
// Schedule a default priority task to run after 200ms
|
|
141
|
+
scheduler.postTask(() => {
|
|
142
|
+
lazyLoadImagePreviews();
|
|
143
|
+
}, { delay: 200 });
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### Task Cancellation via AbortSignal
|
|
147
|
+
|
|
148
|
+
```javascript
|
|
149
|
+
const controller = new AbortController();
|
|
150
|
+
|
|
151
|
+
scheduler.postTask(() => {
|
|
152
|
+
console.log('This will not execute if aborted in time!');
|
|
153
|
+
}, { signal: controller.signal }).catch(err => {
|
|
154
|
+
if (err.name === 'AbortError') {
|
|
155
|
+
console.log('Task successfully cancelled!');
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
// Abort the controller
|
|
160
|
+
controller.abort();
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### Dynamic Event Loop Yielding
|
|
164
|
+
|
|
165
|
+
Use `guard.yield()` inside complex, long-running loops to split execution into distinct chunks, preventing browser UI freezes and maintaining a 60fps frame rate.
|
|
166
|
+
|
|
167
|
+
```javascript
|
|
168
|
+
import { guard } from '@adukiorg/anza/platform';
|
|
169
|
+
|
|
170
|
+
async function processMassiveDataset(items) {
|
|
171
|
+
for (let idx = 0; idx < items.length; idx++) {
|
|
172
|
+
computeHeavyItem(items[idx]);
|
|
173
|
+
|
|
174
|
+
// Yield execution control back to the event loop every 50 operations
|
|
175
|
+
if (idx % 50 === 0) {
|
|
176
|
+
await guard.yield();
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
|
|
184
|
+
## 5. Leak-Proof Popover API
|
|
185
|
+
|
|
186
|
+
Provides native popover styling (`fixed` viewport positioning, high-tier top-layer simulations), light dismiss clicks, and complete DOM unmount safety to avoid memory leaks.
|
|
187
|
+
|
|
188
|
+
### Declarative Markup Integration
|
|
189
|
+
|
|
190
|
+
```html
|
|
191
|
+
<!-- Trigger Button -->
|
|
192
|
+
<button popovertarget="dropdown-menu" popovertargetaction="toggle">
|
|
193
|
+
Open Dropdown
|
|
194
|
+
</button>
|
|
195
|
+
|
|
196
|
+
<!-- Popover Container Element -->
|
|
197
|
+
<div id="dropdown-menu" popover="auto">
|
|
198
|
+
<h3>Dropdown Options</h3>
|
|
199
|
+
<ul>
|
|
200
|
+
<li><a href="/profile">Profile</a></li>
|
|
201
|
+
<li><a href="/settings">Settings</a></li>
|
|
202
|
+
</ul>
|
|
203
|
+
</div>
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### Programmatic API Control
|
|
207
|
+
|
|
208
|
+
```javascript
|
|
209
|
+
const menu = document.getElementById('dropdown-menu');
|
|
210
|
+
|
|
211
|
+
// Explicitly show the dropdown
|
|
212
|
+
menu.showPopover();
|
|
213
|
+
|
|
214
|
+
// Explicitly hide the dropdown
|
|
215
|
+
menu.hidePopover();
|
|
216
|
+
|
|
217
|
+
// Toggle visibility state
|
|
218
|
+
menu.togglePopover();
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
### Memory Safety & Automated Garbage Collection
|
|
222
|
+
|
|
223
|
+
When a popover is declared with `popover="auto"` or `popover=""`, standard polyfills register global document-level click listener closures, which can trigger severe memory leaks when components are removed or unmounted.
|
|
224
|
+
|
|
225
|
+
Our polyfill leverages an internal `MutationObserver` on `document.body` to track DOM unmount actions. If a popover element is programmatically removed from the document while open, it **automatically invokes hidePopover(), detaches its global click listeners, and disconnects all MutationObservers**, guaranteeing zero memory leaks:
|
|
226
|
+
|
|
227
|
+
```javascript
|
|
228
|
+
const userMenu = document.createElement('div');
|
|
229
|
+
userMenu.setAttribute('popover', 'auto');
|
|
230
|
+
document.body.appendChild(userMenu);
|
|
231
|
+
|
|
232
|
+
userMenu.showPopover();
|
|
233
|
+
|
|
234
|
+
// Programmatically unmounted from the DOM
|
|
235
|
+
userMenu.remove(); // Internally cleans up all global event listeners automatically!
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
---
|
|
239
|
+
|
|
240
|
+
## 6. Trailing-Slash Resilient URLPattern
|
|
241
|
+
|
|
242
|
+
Spec-compliant URLPattern pathname parser that extracts named parameters and wildcards, Normalizing trailing slashes seamlessly.
|
|
243
|
+
|
|
244
|
+
### Segment Parameter & Wildcard Matching
|
|
245
|
+
|
|
246
|
+
```javascript
|
|
247
|
+
import { guard } from '@adukiorg/anza/platform';
|
|
248
|
+
|
|
249
|
+
const URLPattern = await guard.urlPattern();
|
|
250
|
+
|
|
251
|
+
// Template matching both '/posts/2026/my-first-post' and '/posts/2026/my-first-post/'
|
|
252
|
+
const pattern = new URLPattern({ pathname: '/posts/:year/:slug' });
|
|
253
|
+
|
|
254
|
+
const result = pattern.exec('https://example.com/posts/2026/my-first-post/');
|
|
255
|
+
if (result) {
|
|
256
|
+
const { year, slug } = result.pathname.groups;
|
|
257
|
+
console.log(`Year: ${year}, Slug: ${slug}`);
|
|
258
|
+
// Output: Year: 2026, Slug: my-first-post
|
|
259
|
+
}
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
### Wildcard Segment Extraction
|
|
263
|
+
|
|
264
|
+
Wildcards are parsed sequentially into indexed string parameter names starting at `'0'`:
|
|
265
|
+
|
|
266
|
+
```javascript
|
|
267
|
+
const filesPattern = new URLPattern({ pathname: '/docs/*' });
|
|
268
|
+
const match = filesPattern.exec('/docs/api/platform/usage.pdf');
|
|
269
|
+
|
|
270
|
+
if (match) {
|
|
271
|
+
const filePath = match.pathname.groups['0'];
|
|
272
|
+
console.log(`File Path: ${filePath}`);
|
|
273
|
+
// Output: File Path: api/platform/usage.pdf
|
|
274
|
+
}
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
### Parameter Modifiers
|
|
278
|
+
|
|
279
|
+
The polyfill supports `?` (optional), `*` (zero-or-more), and `+` (one-or-more) named modifiers:
|
|
280
|
+
|
|
281
|
+
```javascript
|
|
282
|
+
// Match optional member profiles: /member or /member/fescii
|
|
283
|
+
const pattern = new URLPattern({ pathname: '/member/:username?' });
|
|
284
|
+
|
|
285
|
+
const match = pattern.exec('/member/');
|
|
286
|
+
console.log(match.pathname.groups.username); // Output: ""
|
|
287
|
+
```
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/core/router/cache.js
|
|
3
|
+
*
|
|
4
|
+
* Internal route cache backed by the browser Cache API. The router uses this to
|
|
5
|
+
* prefetch and store view assets / lazily-loaded module responses keyed by URL,
|
|
6
|
+
* so revisiting or anticipating a route serves from cache instead of the network.
|
|
7
|
+
*
|
|
8
|
+
* Uses the same `x-expires-at` TTL convention as storage and api caches.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const NAME = 'router-cache';
|
|
12
|
+
const DEFAULT_TTL = 5 * 60 * 1000; // 5 minutes
|
|
13
|
+
|
|
14
|
+
async function open() {
|
|
15
|
+
if (typeof caches === 'undefined') return null;
|
|
16
|
+
return caches.open(NAME);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function key(url) {
|
|
20
|
+
return new Request(new URL(url, globalThis.location?.href || 'http://localhost').href);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Read a cached response for a route URL, honoring TTL. Returns `null` on miss
|
|
25
|
+
* or when the Cache API is unavailable.
|
|
26
|
+
*/
|
|
27
|
+
export async function get(url) {
|
|
28
|
+
const store = await open();
|
|
29
|
+
if (!store) return null;
|
|
30
|
+
|
|
31
|
+
const req = key(url);
|
|
32
|
+
const res = await store.match(req);
|
|
33
|
+
if (!res) return null;
|
|
34
|
+
|
|
35
|
+
const expires = res.headers.get('x-expires-at');
|
|
36
|
+
if (expires && Date.now() > Number(expires)) {
|
|
37
|
+
await store.delete(req);
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
return res.clone();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Store a response for a route URL with an optional TTL (ms).
|
|
45
|
+
*/
|
|
46
|
+
export async function set(url, response, ttl = DEFAULT_TTL) {
|
|
47
|
+
const store = await open();
|
|
48
|
+
if (!store || !response || response.type === 'opaque') return;
|
|
49
|
+
|
|
50
|
+
const headers = new Headers(response.headers);
|
|
51
|
+
if (ttl) headers.set('x-expires-at', String(Date.now() + ttl));
|
|
52
|
+
|
|
53
|
+
const body = response.body ? response.clone().body : null;
|
|
54
|
+
const stamped = new Response(body, {
|
|
55
|
+
status: response.status,
|
|
56
|
+
statusText: response.statusText,
|
|
57
|
+
headers
|
|
58
|
+
});
|
|
59
|
+
await store.put(key(url), stamped);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Prefetch a route URL into the cache. Returns the cached `Response` (or `null`).
|
|
64
|
+
* Use on link hover / when a view becomes visible to make navigation instant.
|
|
65
|
+
*/
|
|
66
|
+
export async function prefetch(url, { ttl = DEFAULT_TTL, signal } = {}) {
|
|
67
|
+
const existing = await get(url);
|
|
68
|
+
if (existing) return existing;
|
|
69
|
+
|
|
70
|
+
try {
|
|
71
|
+
const res = await fetch(key(url), { signal });
|
|
72
|
+
if (res && res.ok) {
|
|
73
|
+
await set(url, res, ttl);
|
|
74
|
+
return res.clone();
|
|
75
|
+
}
|
|
76
|
+
} catch {
|
|
77
|
+
// Network failure during prefetch is non-fatal.
|
|
78
|
+
}
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Remove a single URL, or clear the entire route cache when called with no args.
|
|
84
|
+
*/
|
|
85
|
+
export async function purge(url) {
|
|
86
|
+
const store = await open();
|
|
87
|
+
if (!store) return;
|
|
88
|
+
if (url) {
|
|
89
|
+
await store.delete(key(url));
|
|
90
|
+
} else if (typeof caches !== 'undefined') {
|
|
91
|
+
await caches.delete(NAME);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export const cache = { get, set, prefetch, purge };
|