akanjs 2.3.6-rc.2 → 2.3.6-rc.4
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/client/capacitor.ts +2 -0
- package/client/csrTypes.ts +132 -9
- package/client/device.ts +13 -4
- package/client/frameConfig.ts +248 -0
- package/client/frameDebug.ts +28 -0
- package/client/index.ts +2 -0
- package/client/router.ts +9 -0
- package/fetch/requestStorage.ts +11 -0
- package/package.json +1 -1
- package/server/hmr/clientScript.ts +28 -0
- package/server/hmr/wsHub.ts +29 -0
- package/server/routeElementComposer.tsx +56 -0
- package/server/routeTreeBuilder.ts +8 -0
- package/server/routing/apiRouter.ts +4 -1
- package/server/rscClient.tsx +8 -0
- package/server/rscWorker.tsx +30 -5
- package/store/baseSt.ts +2 -2
- package/types/client/capacitor.d.ts +4 -0
- package/types/client/csrTypes.d.ts +125 -7
- package/types/client/frameConfig.d.ts +19 -0
- package/types/client/frameDebug.d.ts +3 -0
- package/types/client/index.d.ts +2 -0
- package/types/client/router.d.ts +2 -0
- package/types/fetch/requestStorage.d.ts +3 -0
- package/types/server/hmr/clientScript.d.ts +1 -1
- package/types/server/hmr/wsHub.d.ts +6 -0
- package/types/server/routeElementComposer.d.ts +8 -0
- package/types/server/rscClient.d.ts +3 -0
- package/types/store/baseSt.d.ts +2 -2
- package/types/ui/Layout/BottomInset.d.ts +7 -1
- package/types/ui/Layout/Navbar.d.ts +1 -1
- package/types/ui/Layout/TopInset.d.ts +7 -0
- package/types/ui/Layout/index.d.ts +3 -2
- package/types/ui/Link/types.d.ts +9 -1
- package/types/ui/Portal.d.ts +2 -1
- package/types/ui/ServerPortal.d.ts +15 -0
- package/types/ui/System/Client.d.ts +7 -5
- package/types/ui/System/SSR.d.ts +3 -2
- package/types/ui/System/frameCssVars.d.ts +5 -0
- package/types/webkit/useCsrValues.d.ts +6 -1
- package/types/webkit/useFrameRuntime.d.ts +111 -0
- package/ui/Layout/BottomInset.tsx +89 -20
- package/ui/Layout/BottomTab.tsx +11 -6
- package/ui/Layout/Navbar.tsx +38 -12
- package/ui/Layout/TopInset.tsx +45 -0
- package/ui/Layout/TopLeftAction.tsx +2 -2
- package/ui/Layout/index.ts +2 -0
- package/ui/Link/types.ts +9 -1
- package/ui/Portal.tsx +16 -2
- package/ui/ServerPortal.tsx +49 -0
- package/ui/System/CSR.tsx +236 -90
- package/ui/System/Client.tsx +90 -10
- package/ui/System/SSR.tsx +119 -43
- package/ui/System/frameCssVars.ts +25 -0
- package/ui/styles.css +56 -0
- package/webkit/bootCsr.tsx +44 -35
- package/webkit/useCsrValues.ts +636 -105
- package/webkit/useFrameRuntime.ts +539 -0
- package/types/ui/Layout/BottomAction.d.ts +0 -6
- package/ui/Layout/BottomAction.tsx +0 -15
|
@@ -1,8 +1,16 @@
|
|
|
1
1
|
|
|
2
2
|
|
|
3
|
+
const SYNC_NAVIGATION_ENABLED =
|
|
4
|
+
process.env.AKAN_PUBLIC_SYNC_NAVIGATION === "true" ||
|
|
5
|
+
process.env.AKAN_PUBLIC_SYNC_NAVIGATION === "1" ||
|
|
6
|
+
process.env.SYNC_DOMAIN === "true" ||
|
|
7
|
+
process.env.SYNC_DOMAIN === "1";
|
|
8
|
+
|
|
3
9
|
export const HMR_CLIENT_SCRIPT = `(function(){
|
|
4
10
|
if (self.__AKAN_HMR_INSTALLED__) return;
|
|
5
11
|
self.__AKAN_HMR_INSTALLED__ = true;
|
|
12
|
+
var syncNavigationEnabled = ${JSON.stringify(SYNC_NAVIGATION_ENABLED)};
|
|
13
|
+
var syncNavigationClientId = Math.random().toString(36).slice(2) + Date.now().toString(36);
|
|
6
14
|
var proto = location.protocol === "https:" ? "wss:" : "ws:";
|
|
7
15
|
var url = proto + "//" + location.host + "/_akan/hmr";
|
|
8
16
|
var attempts = 0;
|
|
@@ -22,6 +30,19 @@ export const HMR_CLIENT_SCRIPT = `(function(){
|
|
|
22
30
|
var overlayJobs = {};
|
|
23
31
|
var buildErrorStates = {};
|
|
24
32
|
self.__AKAN_HMR_PHASE__ = null;
|
|
33
|
+
self.__AKAN_DEV_SYNC_NAVIGATION__ = function(href, kind){
|
|
34
|
+
if (self.__AKAN_DEV_SYNC_NAVIGATION_APPLYING__ || !syncNavigationEnabled || !socket || socket.readyState !== WebSocket.OPEN) return;
|
|
35
|
+
try {
|
|
36
|
+
socket.send(JSON.stringify({
|
|
37
|
+
type: "sync-navigation",
|
|
38
|
+
clientId: syncNavigationClientId,
|
|
39
|
+
href: new URL(href, location.origin).pathname + new URL(href, location.origin).search + new URL(href, location.origin).hash,
|
|
40
|
+
kind: kind || "push"
|
|
41
|
+
}));
|
|
42
|
+
} catch(e) {
|
|
43
|
+
console.warn("[akan-hmr] sync navigation send failed", e);
|
|
44
|
+
}
|
|
45
|
+
};
|
|
25
46
|
|
|
26
47
|
self.$RefreshReg$ = self.$RefreshReg$ || function(type, id){
|
|
27
48
|
if (refreshRuntime) refreshRuntime.register(type, id);
|
|
@@ -71,6 +92,13 @@ export const HMR_CLIENT_SCRIPT = `(function(){
|
|
|
71
92
|
}
|
|
72
93
|
return;
|
|
73
94
|
}
|
|
95
|
+
if (msg.type === "sync-navigation") {
|
|
96
|
+
if (!syncNavigationEnabled || msg.clientId === syncNavigationClientId || !msg.href) return;
|
|
97
|
+
window.dispatchEvent(new CustomEvent("akan:sync-navigation", {
|
|
98
|
+
detail: { href: msg.href, kind: msg.kind || "push" }
|
|
99
|
+
}));
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
74
102
|
if (msg.type === "build-status") { handleBuildStatus(msg); return; }
|
|
75
103
|
if (msg.type === "ok") {
|
|
76
104
|
clearBuildErrorOverlay({
|
package/server/hmr/wsHub.ts
CHANGED
|
@@ -22,6 +22,7 @@ export type HmrMessage =
|
|
|
22
22
|
routeIds?: string[];
|
|
23
23
|
}
|
|
24
24
|
| { type: "css-update"; cssAssets?: Record<string, { cssUrl: string; cssRelPath: string }> }
|
|
25
|
+
| { type: "sync-navigation"; clientId: string; href: string; kind?: "push" | "replace" | "back" | "pop" }
|
|
25
26
|
| { type: "ok"; generation?: number }
|
|
26
27
|
| {
|
|
27
28
|
type: "build-status";
|
|
@@ -63,4 +64,32 @@ export class HmrWsHub {
|
|
|
63
64
|
const payload = JSON.stringify(msg);
|
|
64
65
|
this.#publish?.(HMR_WS_TOPIC, payload);
|
|
65
66
|
}
|
|
67
|
+
|
|
68
|
+
handleMessage(message: string): void {
|
|
69
|
+
if (!isSyncNavigationEnabled()) return;
|
|
70
|
+
let parsed: unknown;
|
|
71
|
+
try {
|
|
72
|
+
parsed = JSON.parse(message);
|
|
73
|
+
} catch {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
if (!isSyncNavigationMessage(parsed)) return;
|
|
77
|
+
this.broadcast(parsed);
|
|
78
|
+
}
|
|
66
79
|
}
|
|
80
|
+
|
|
81
|
+
const isSyncNavigationEnabled = () =>
|
|
82
|
+
process.env.AKAN_PUBLIC_SYNC_NAVIGATION === "true" ||
|
|
83
|
+
process.env.AKAN_PUBLIC_SYNC_NAVIGATION === "1" ||
|
|
84
|
+
process.env.SYNC_DOMAIN === "true" ||
|
|
85
|
+
process.env.SYNC_DOMAIN === "1";
|
|
86
|
+
|
|
87
|
+
const isSyncNavigationMessage = (value: unknown): value is Extract<HmrMessage, { type: "sync-navigation" }> => {
|
|
88
|
+
if (!value || typeof value !== "object") return false;
|
|
89
|
+
const msg = value as Partial<Extract<HmrMessage, { type: "sync-navigation" }>>;
|
|
90
|
+
if (msg.type !== "sync-navigation") return false;
|
|
91
|
+
if (typeof msg.clientId !== "string" || msg.clientId.length === 0) return false;
|
|
92
|
+
if (typeof msg.href !== "string" || msg.href.length === 0) return false;
|
|
93
|
+
if (msg.kind === undefined) return true;
|
|
94
|
+
return msg.kind === "push" || msg.kind === "replace" || msg.kind === "back" || msg.kind === "pop";
|
|
95
|
+
};
|
|
@@ -2,11 +2,13 @@ import type {
|
|
|
2
2
|
Head,
|
|
3
3
|
LayoutErrorRender,
|
|
4
4
|
LayoutFallbackRoute,
|
|
5
|
+
PageConfig,
|
|
5
6
|
LayoutNotFoundRender,
|
|
6
7
|
PathRoute,
|
|
7
8
|
ResolvedHead,
|
|
8
9
|
RouteRender,
|
|
9
10
|
} from "akanjs/client";
|
|
11
|
+
import { getExplicitPageConfigKeys, resolvePageState } from "../client/frameConfig";
|
|
10
12
|
import { Children, cloneElement, isValidElement, type ReactElement, type ReactNode, Suspense } from "react";
|
|
11
13
|
import { resolveHeadResult } from "./metadata";
|
|
12
14
|
import { type AkanRouteSegmentState, createAkanRouteSegments, createAkanSegmentOutletKey } from "./routeState";
|
|
@@ -14,6 +16,47 @@ import { isAkanRscPartialCommitEnabled } from "./rscPartialCommit";
|
|
|
14
16
|
import { AkanSegmentOutletReference } from "./rscSegmentOutletReference";
|
|
15
17
|
|
|
16
18
|
export class RouteElementComposer {
|
|
19
|
+
static async resolveSsrFramePathRoute({
|
|
20
|
+
pathRoute,
|
|
21
|
+
basePath,
|
|
22
|
+
}: {
|
|
23
|
+
pathRoute: PathRoute;
|
|
24
|
+
basePath?: string | null;
|
|
25
|
+
}): Promise<PathRoute> {
|
|
26
|
+
const pageConfigChain = await RouteElementComposer.#resolvePageConfigChain(pathRoute);
|
|
27
|
+
return {
|
|
28
|
+
...pathRoute,
|
|
29
|
+
pageState: resolvePageState({
|
|
30
|
+
configChain: pageConfigChain,
|
|
31
|
+
path: pathRoute.path,
|
|
32
|
+
basePath: basePath ?? undefined,
|
|
33
|
+
platform: "web",
|
|
34
|
+
deviceSafeArea: { top: 0, bottom: 0 },
|
|
35
|
+
cssSafeArea: { top: 0, bottom: 0 },
|
|
36
|
+
}),
|
|
37
|
+
pageConfigChain,
|
|
38
|
+
explicitPageConfigKeys: getExplicitPageConfigKeys(pageConfigChain),
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
static async resolveSsrFallbackFrameState({
|
|
43
|
+
route,
|
|
44
|
+
basePath,
|
|
45
|
+
}: {
|
|
46
|
+
route: PathRoute | LayoutFallbackRoute;
|
|
47
|
+
basePath?: string | null;
|
|
48
|
+
}) {
|
|
49
|
+
const pageConfigChain = await RouteElementComposer.#resolveLayoutPageConfigChain(route);
|
|
50
|
+
return resolvePageState({
|
|
51
|
+
configChain: pageConfigChain,
|
|
52
|
+
path: route.path,
|
|
53
|
+
basePath: basePath ?? undefined,
|
|
54
|
+
platform: "web",
|
|
55
|
+
deviceSafeArea: { top: 0, bottom: 0 },
|
|
56
|
+
cssSafeArea: { top: 0, bottom: 0 },
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
17
60
|
static compose({
|
|
18
61
|
pathRoute,
|
|
19
62
|
params,
|
|
@@ -231,6 +274,19 @@ export class RouteElementComposer {
|
|
|
231
274
|
return [...pathRoute.renderRootLayouts, ...pathRoute.renderLayouts, pathRoute.renderPage];
|
|
232
275
|
}
|
|
233
276
|
|
|
277
|
+
static async #resolveLayoutPageConfigChain(route: PathRoute | LayoutFallbackRoute): Promise<PageConfig[]> {
|
|
278
|
+
const configs = await Promise.all(
|
|
279
|
+
[...route.renderRootLayouts, ...route.renderLayouts].map((render) => render.getLayoutPageConfig?.()),
|
|
280
|
+
);
|
|
281
|
+
return configs.filter((config): config is PageConfig => Boolean(config));
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
static async #resolvePageConfigChain(pathRoute: PathRoute): Promise<PageConfig[]> {
|
|
285
|
+
const layoutConfigs = await RouteElementComposer.#resolveLayoutPageConfigChain(pathRoute);
|
|
286
|
+
const pageConfig = await pathRoute.renderPage.getPageConfig?.();
|
|
287
|
+
return [...layoutConfigs, ...(pageConfig ? [pageConfig] : [])];
|
|
288
|
+
}
|
|
289
|
+
|
|
234
290
|
static #composeLoadingFallback(renders: RouteRender[], params: Record<string, string>): ReactNode {
|
|
235
291
|
let element: ReactNode = null;
|
|
236
292
|
for (let i = renders.length - 1; i >= 0; i--) {
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
parseRouteModuleKey,
|
|
19
19
|
routeSegmentToTreePath,
|
|
20
20
|
} from "akanjs/common";
|
|
21
|
+
import { validatePageConfig } from "../client/frameConfig";
|
|
21
22
|
import { resolveHeadExport, resolveMetadataHead } from "./metadata";
|
|
22
23
|
|
|
23
24
|
export type PagesContext = Record<string, () => Promise<RouteModule>>;
|
|
@@ -55,6 +56,7 @@ export class RouteTreeBuilder {
|
|
|
55
56
|
]);
|
|
56
57
|
static readonly #rootLayoutExports = new Set([
|
|
57
58
|
"default",
|
|
59
|
+
"pageConfig",
|
|
58
60
|
"head",
|
|
59
61
|
"metadata",
|
|
60
62
|
"generateHead",
|
|
@@ -72,6 +74,7 @@ export class RouteTreeBuilder {
|
|
|
72
74
|
]);
|
|
73
75
|
static readonly #layoutRouteExports = new Set([
|
|
74
76
|
"default",
|
|
77
|
+
"pageConfig",
|
|
75
78
|
"head",
|
|
76
79
|
"metadata",
|
|
77
80
|
"generateHead",
|
|
@@ -271,6 +274,7 @@ export class RouteTreeBuilder {
|
|
|
271
274
|
RouteTreeBuilder.#moduleCacheStats.cacheMisses += 1;
|
|
272
275
|
const mod = await loader();
|
|
273
276
|
RouteTreeBuilder.#validateRouteModuleExports(key, kind, mod);
|
|
277
|
+
validatePageConfig(key, "pageConfig" in mod ? mod.pageConfig : undefined);
|
|
274
278
|
if (!loaded) {
|
|
275
279
|
RouteTreeBuilder.#moduleCacheStats.loadedModuleCount += 1;
|
|
276
280
|
RouteTreeBuilder.#moduleCacheStats.loadedModuleKeys.push(key);
|
|
@@ -354,6 +358,10 @@ export class RouteTreeBuilder {
|
|
|
354
358
|
return "pageConfig" in mod ? mod.pageConfig : undefined;
|
|
355
359
|
};
|
|
356
360
|
} else {
|
|
361
|
+
routeRender.getLayoutPageConfig = async () => {
|
|
362
|
+
const mod = await loadModule();
|
|
363
|
+
return "pageConfig" in mod ? mod.pageConfig : undefined;
|
|
364
|
+
};
|
|
357
365
|
routeRender.resolveNotFound = async () => {
|
|
358
366
|
const mod = (await loadModule()) as LayoutModule;
|
|
359
367
|
routeRender.NotFound = mod.NotFound;
|
|
@@ -130,7 +130,10 @@ export class ApiRouter {
|
|
|
130
130
|
},
|
|
131
131
|
message: async (ws, message) => {
|
|
132
132
|
const data = ws.data as WsTaggedData | undefined;
|
|
133
|
-
if (data?.kind === "akan-hmr")
|
|
133
|
+
if (data?.kind === "akan-hmr") {
|
|
134
|
+
if (typeof message === "string" && typeof hmrHub?.handleMessage === "function") hmrHub.handleMessage(message);
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
134
137
|
try {
|
|
135
138
|
if (typeof message === "string") {
|
|
136
139
|
const msg = JSON.parse(message) as WebsocketReqData;
|
package/server/rscClient.tsx
CHANGED
|
@@ -47,6 +47,9 @@ declare global {
|
|
|
47
47
|
| undefined;
|
|
48
48
|
var __AKAN_RSC_REFRESH__: ((options?: { buildId?: number }) => Promise<void>) | undefined;
|
|
49
49
|
var __AKAN_RSC_CLEAR_CACHE__: (() => void) | undefined;
|
|
50
|
+
var __AKAN_DEV_SYNC_NAVIGATION__: ((href: string, kind: "push" | "replace" | "back" | "pop") => void) | undefined;
|
|
51
|
+
var __AKAN_DEV_SYNC_NAVIGATION_APPLYING__: boolean | undefined;
|
|
52
|
+
var __AKAN_GET_SYNC_ROUTE_HREF__: ((href: string) => string) | undefined;
|
|
50
53
|
}
|
|
51
54
|
|
|
52
55
|
function decodeBase64(b64: string): Uint8Array {
|
|
@@ -514,6 +517,11 @@ function Root(): ReactNode {
|
|
|
514
517
|
|
|
515
518
|
window.addEventListener("popstate", () => {
|
|
516
519
|
void globalThis.__AKAN_RSC_NAVIGATE__?.(window.location.href, { replace: true, scrollToTop: false });
|
|
520
|
+
window.setTimeout(() => {
|
|
521
|
+
if (globalThis.__AKAN_DEV_SYNC_NAVIGATION_APPLYING__) return;
|
|
522
|
+
const href = globalThis.__AKAN_GET_SYNC_ROUTE_HREF__?.(window.location.href) ?? window.location.href;
|
|
523
|
+
globalThis.__AKAN_DEV_SYNC_NAVIGATION__?.(href, "pop");
|
|
524
|
+
}, 0);
|
|
517
525
|
});
|
|
518
526
|
|
|
519
527
|
const hydrate = () => hydrateRoot(document, createElement(Root));
|
package/server/rscWorker.tsx
CHANGED
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
getRequestPolicy,
|
|
13
13
|
getRequestTheme,
|
|
14
14
|
requestStorage,
|
|
15
|
+
setRequestFrameState,
|
|
15
16
|
untrackedCookies,
|
|
16
17
|
untrackedRequest,
|
|
17
18
|
updateRequestPolicy,
|
|
@@ -1164,6 +1165,12 @@ export class RscRenderer {
|
|
|
1164
1165
|
error?: unknown;
|
|
1165
1166
|
digest?: string;
|
|
1166
1167
|
}): Promise<ReactNode | null> {
|
|
1168
|
+
setRequestFrameState(
|
|
1169
|
+
await RouteElementComposer.resolveSsrFallbackFrameState({
|
|
1170
|
+
route,
|
|
1171
|
+
basePath: this.#getBasePath(url),
|
|
1172
|
+
}),
|
|
1173
|
+
);
|
|
1167
1174
|
const body = await RouteElementComposer.composeFallback({
|
|
1168
1175
|
kind,
|
|
1169
1176
|
route,
|
|
@@ -1218,17 +1225,22 @@ export class RscRenderer {
|
|
|
1218
1225
|
this.#logger.verbose(
|
|
1219
1226
|
`composing route element pathname=${url.pathname} search=${url.search || "(none)"} params=${JSON.stringify(match.params)}`,
|
|
1220
1227
|
);
|
|
1221
|
-
const
|
|
1228
|
+
const pathRoute = await RouteElementComposer.resolveSsrFramePathRoute({
|
|
1222
1229
|
pathRoute: match.pathRoute,
|
|
1230
|
+
basePath: this.#getBasePath(url),
|
|
1231
|
+
});
|
|
1232
|
+
setRequestFrameState(pathRoute.pageState);
|
|
1233
|
+
const routeHead = await RouteElementComposer.resolveHeadWithMetadata({
|
|
1234
|
+
pathRoute,
|
|
1223
1235
|
params: match.params,
|
|
1224
1236
|
searchParams,
|
|
1225
1237
|
});
|
|
1226
1238
|
const routeHeadSnapshot = this.#createRouteHeadSnapshot(url, routeHead, {
|
|
1227
|
-
isSpecialRoute:
|
|
1239
|
+
isSpecialRoute: pathRoute.isSpecialRoute,
|
|
1228
1240
|
hasExplicitLanguageAlternates: routeHead.hasExplicitLanguageAlternates,
|
|
1229
1241
|
});
|
|
1230
1242
|
const body = RouteElementComposer.compose({
|
|
1231
|
-
pathRoute
|
|
1243
|
+
pathRoute,
|
|
1232
1244
|
params: match.params,
|
|
1233
1245
|
searchParams,
|
|
1234
1246
|
});
|
|
@@ -1245,7 +1257,7 @@ export class RscRenderer {
|
|
|
1245
1257
|
: (routeHead.node ?? this.#renderDefaultHead())}
|
|
1246
1258
|
{!routeHeadSnapshot &&
|
|
1247
1259
|
shouldRenderLocaleAlternates({
|
|
1248
|
-
isSpecialRoute:
|
|
1260
|
+
isSpecialRoute: pathRoute.isSpecialRoute,
|
|
1249
1261
|
hasExplicitLanguageAlternates: routeHead.hasExplicitLanguageAlternates,
|
|
1250
1262
|
})
|
|
1251
1263
|
? this.#renderLocaleAlternates(url)
|
|
@@ -1266,8 +1278,13 @@ export class RscRenderer {
|
|
|
1266
1278
|
this.#logger.verbose(
|
|
1267
1279
|
`composing route suffix pathname=${url.pathname} start=${patchStartIndex} params=${JSON.stringify(match.params)}`,
|
|
1268
1280
|
);
|
|
1269
|
-
|
|
1281
|
+
const pathRoute = await RouteElementComposer.resolveSsrFramePathRoute({
|
|
1270
1282
|
pathRoute: match.pathRoute,
|
|
1283
|
+
basePath: this.#getBasePath(url),
|
|
1284
|
+
});
|
|
1285
|
+
setRequestFrameState(pathRoute.pageState);
|
|
1286
|
+
return RouteElementComposer.composeSuffix({
|
|
1287
|
+
pathRoute,
|
|
1271
1288
|
params: match.params,
|
|
1272
1289
|
searchParams,
|
|
1273
1290
|
patchStartIndex,
|
|
@@ -1315,6 +1332,14 @@ export class RscRenderer {
|
|
|
1315
1332
|
return <title key="title">{process.env.AKAN_PUBLIC_APP_NAME ?? "Akan App"}</title>;
|
|
1316
1333
|
}
|
|
1317
1334
|
|
|
1335
|
+
#getBasePath(url: URL): string | null {
|
|
1336
|
+
return getBasePathFromPathname(url.pathname, {
|
|
1337
|
+
basePaths: this.#basePaths,
|
|
1338
|
+
i18n: this.#i18n,
|
|
1339
|
+
headerBasePath: untrackedRequest()?.headers.get("x-base-path"),
|
|
1340
|
+
});
|
|
1341
|
+
}
|
|
1342
|
+
|
|
1318
1343
|
async #resolveRouteHeadSnapshot(
|
|
1319
1344
|
url: URL,
|
|
1320
1345
|
match: { pathRoute: PathRoute; params: Record<string, string> },
|
package/store/baseSt.ts
CHANGED
|
@@ -44,8 +44,8 @@ export class BaseStore extends store("base" as const, () => ({
|
|
|
44
44
|
bottomInset: 0,
|
|
45
45
|
gesture: true,
|
|
46
46
|
cache: false,
|
|
47
|
-
topSafeAreaColor: "
|
|
48
|
-
bottomSafeAreaColor: "
|
|
47
|
+
topSafeAreaColor: "var(--color-base-100, Canvas)",
|
|
48
|
+
bottomSafeAreaColor: "var(--color-base-100, Canvas)",
|
|
49
49
|
} as PageState,
|
|
50
50
|
devMode: false,
|
|
51
51
|
deviceToken: "" as string,
|
|
@@ -12,6 +12,7 @@ export type CapacitorAppModule = {
|
|
|
12
12
|
App: {
|
|
13
13
|
addListener: (eventName: string, listenerFunc: (...args: unknown[]) => void) => Promise<unknown> | unknown;
|
|
14
14
|
removeAllListeners: () => Promise<void> | void;
|
|
15
|
+
exitApp?: () => Promise<void> | void;
|
|
15
16
|
getInfo: () => Promise<{
|
|
16
17
|
id: string;
|
|
17
18
|
version: string;
|
|
@@ -128,6 +129,9 @@ export type CapacitorKeyboardModule = {
|
|
|
128
129
|
Keyboard: {
|
|
129
130
|
show: () => Promise<void> | void;
|
|
130
131
|
hide: () => Promise<void> | void;
|
|
132
|
+
setResizeMode?: (options: {
|
|
133
|
+
mode: "body" | "ionic" | "native" | "none";
|
|
134
|
+
}) => Promise<void> | void;
|
|
131
135
|
addListener: (eventName: string, listenerFunc: (info: CapacitorKeyboardInfo) => void) => Promise<unknown> | unknown;
|
|
132
136
|
removeAllListeners: () => Promise<void> | void;
|
|
133
137
|
};
|
|
@@ -5,15 +5,19 @@ import type { AnimatedComponent, AnimatedProps, Interpolation, SpringValue } fro
|
|
|
5
5
|
import type { RouterInstance } from "./router.d.ts";
|
|
6
6
|
import type { ReactFont } from "./types.d.ts";
|
|
7
7
|
export type TransitionType = "none" | "fade" | "bottomUp" | "stack" | "scaleOut";
|
|
8
|
+
export type PageSafeAreaConfig = boolean | "top" | "bottom" | {
|
|
9
|
+
top?: boolean;
|
|
10
|
+
bottom?: boolean;
|
|
11
|
+
android?: "auto" | "edge-to-edge" | "none";
|
|
12
|
+
};
|
|
8
13
|
/** Per-page CSR configuration for transition, safe-area, and gesture behavior. */
|
|
9
14
|
export interface PageConfig {
|
|
10
15
|
transition?: TransitionType;
|
|
11
|
-
safeArea?:
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
bottomInset?: boolean | number;
|
|
16
|
+
safeArea?: PageSafeAreaConfig;
|
|
17
|
+
/** Top chrome reservation in px. Use true for the default 48px reservation. */
|
|
18
|
+
topInset?: number | boolean;
|
|
19
|
+
/** Bottom chrome reservation in px. Use true for the default 48px reservation. */
|
|
20
|
+
bottomInset?: number | boolean;
|
|
17
21
|
gesture?: boolean;
|
|
18
22
|
cache?: boolean;
|
|
19
23
|
/**
|
|
@@ -100,6 +104,7 @@ export interface RouteRender {
|
|
|
100
104
|
resolveError?: () => PromiseOrObject<LayoutErrorRender | undefined>;
|
|
101
105
|
resolveHead?: ResolveHead;
|
|
102
106
|
getPageConfig?: () => PromiseOrObject<PageConfig | undefined>;
|
|
107
|
+
getLayoutPageConfig?: () => PromiseOrObject<PageConfig | undefined>;
|
|
103
108
|
}
|
|
104
109
|
export interface WebAppManifestIcon {
|
|
105
110
|
src: string;
|
|
@@ -161,6 +166,7 @@ export interface PageModule {
|
|
|
161
166
|
}
|
|
162
167
|
export interface LayoutModule {
|
|
163
168
|
default?: LayoutRender;
|
|
169
|
+
pageConfig?: PageConfig;
|
|
164
170
|
head?: Head;
|
|
165
171
|
metadata?: AkanMetadata;
|
|
166
172
|
generateHead?: GenerateHead;
|
|
@@ -179,6 +185,8 @@ export interface LayoutModule {
|
|
|
179
185
|
export type RouteModule = PageModule | LayoutModule;
|
|
180
186
|
export interface Route {
|
|
181
187
|
PageConfig?: PageConfig;
|
|
188
|
+
pageConfig?: PageConfig;
|
|
189
|
+
layoutPageConfig?: PageConfig;
|
|
182
190
|
path: string;
|
|
183
191
|
renderPage?: RouteRender;
|
|
184
192
|
renderLayout?: RouteRender;
|
|
@@ -186,6 +194,8 @@ export interface Route {
|
|
|
186
194
|
isSpecialRoute?: boolean;
|
|
187
195
|
loader?: () => unknown;
|
|
188
196
|
pageState?: PageState;
|
|
197
|
+
pageConfigChain?: PageConfig[];
|
|
198
|
+
explicitPageConfigKeys?: Partial<Record<keyof PageConfig, boolean>>;
|
|
189
199
|
children: Map<string, Route>;
|
|
190
200
|
}
|
|
191
201
|
export type AnimatedDivProps = AnimatedComponent<"div"> extends ForwardRefExoticComponent<AnimatedProps<infer P>> ? P : never;
|
|
@@ -229,9 +239,23 @@ export interface Location {
|
|
|
229
239
|
pathRoute: PathRoute;
|
|
230
240
|
hash: string;
|
|
231
241
|
}
|
|
242
|
+
export type CsrNavigationPhase = "idle" | "preparing" | "transitioning";
|
|
243
|
+
export type CsrNavigationKind = "push" | "replace" | "back" | "popForward" | "popBack";
|
|
244
|
+
export interface NavigationIntent {
|
|
245
|
+
id: number;
|
|
246
|
+
kind: CsrNavigationKind;
|
|
247
|
+
from: Location;
|
|
248
|
+
to: Location;
|
|
249
|
+
scrollTop: number;
|
|
250
|
+
scrollToTop?: boolean;
|
|
251
|
+
createdAt: number;
|
|
252
|
+
}
|
|
232
253
|
export interface LocationState {
|
|
233
254
|
location: Location;
|
|
234
255
|
prevLocation: Location | null;
|
|
256
|
+
pendingLocation?: Location | null;
|
|
257
|
+
navigationIntent?: NavigationIntent | null;
|
|
258
|
+
phase: CsrNavigationPhase;
|
|
235
259
|
}
|
|
236
260
|
export interface History {
|
|
237
261
|
type: "initial" | "forward" | "back";
|
|
@@ -252,6 +276,9 @@ export interface RouteState {
|
|
|
252
276
|
clientHeight: number;
|
|
253
277
|
location: Location;
|
|
254
278
|
prevLocation: Location | null;
|
|
279
|
+
pendingLocation: Location | null;
|
|
280
|
+
navigationIntent: NavigationIntent | null;
|
|
281
|
+
phase: CsrNavigationPhase;
|
|
255
282
|
history: RefObject<History>;
|
|
256
283
|
topSafeAreaRef: RefObject<HTMLDivElement | null>;
|
|
257
284
|
bottomSafeAreaRef: RefObject<HTMLDivElement | null>;
|
|
@@ -263,6 +290,8 @@ export interface RouteState {
|
|
|
263
290
|
}>;
|
|
264
291
|
router: RouterInstance;
|
|
265
292
|
pathRoutes: PathRoute[];
|
|
293
|
+
registerFrameSlot: (path: string, slot: FrameSlotRegistration, bucket?: FrameSlotBucket) => () => void;
|
|
294
|
+
frameLayout: FrameLayoutState;
|
|
266
295
|
}
|
|
267
296
|
export type UseCsrTransition = CsrTransitionStyles & {
|
|
268
297
|
pageBind: (...args: unknown[]) => ReactDOMAttributes;
|
|
@@ -277,11 +306,12 @@ export type CsrContextType = RouteState & UseCsrTransition;
|
|
|
277
306
|
export declare const csrContext: import("react").Context<CsrContextType>;
|
|
278
307
|
export declare const useCsr: () => CsrContextType;
|
|
279
308
|
export interface PathContextType {
|
|
280
|
-
pageType: "current" | "prev" | "cached";
|
|
309
|
+
pageType: "current" | "prev" | "cached" | "pending";
|
|
281
310
|
location: Location;
|
|
282
311
|
prefix?: string;
|
|
283
312
|
gestureEnabled: boolean;
|
|
284
313
|
setGestureEnabled: (enabled: boolean) => void;
|
|
314
|
+
registerFrameSlot: (slot: FrameSlotRegistration) => () => void;
|
|
285
315
|
}
|
|
286
316
|
export declare const pathContext: import("react").Context<PathContextType>;
|
|
287
317
|
export declare const usePathCtx: () => PathContextType;
|
|
@@ -290,11 +320,99 @@ export interface PathRoute {
|
|
|
290
320
|
pathSegments: string[];
|
|
291
321
|
renderPage: RouteRender;
|
|
292
322
|
pageState: PageState;
|
|
323
|
+
pageConfigChain?: PageConfig[];
|
|
324
|
+
explicitPageConfigKeys?: Partial<Record<keyof PageConfig, boolean>>;
|
|
293
325
|
renderRootLayouts: RouteRender[];
|
|
294
326
|
renderLayouts: RouteRender[];
|
|
295
327
|
resolveHead?: ResolveHead;
|
|
296
328
|
isSpecialRoute?: boolean;
|
|
297
329
|
}
|
|
330
|
+
export type FrameSlotScope = "page" | "layout";
|
|
331
|
+
export type FrameSlotType = "topInset" | "bottomInset";
|
|
332
|
+
export type FrameSlotBucket = "active" | "pending";
|
|
333
|
+
export type FrameLayer = "page" | "topChrome" | "bottomChrome" | "keyboard" | "overlay";
|
|
334
|
+
export type FrameSlotRole = "topChrome" | "bottomChrome" | "keyboardAccessory";
|
|
335
|
+
export type FramePlatformProfile = "ios" | "android" | "web" | "mobileWeb";
|
|
336
|
+
export interface FrameViewportState {
|
|
337
|
+
width: number;
|
|
338
|
+
height: number;
|
|
339
|
+
visualWidth: number;
|
|
340
|
+
visualHeight: number;
|
|
341
|
+
visualOffsetTop: number;
|
|
342
|
+
}
|
|
343
|
+
export interface KeyboardFrameState {
|
|
344
|
+
height: number;
|
|
345
|
+
offset: number;
|
|
346
|
+
visible: boolean;
|
|
347
|
+
sticky: boolean;
|
|
348
|
+
frozen?: boolean;
|
|
349
|
+
source?: "native" | "visualViewport" | "fallback";
|
|
350
|
+
animationDuration?: number;
|
|
351
|
+
animationEasing?: string;
|
|
352
|
+
}
|
|
353
|
+
export interface FrameContentViewportState {
|
|
354
|
+
top: number;
|
|
355
|
+
bottom: number;
|
|
356
|
+
height: number;
|
|
357
|
+
}
|
|
358
|
+
export interface KeyboardAccessoryFrameState {
|
|
359
|
+
top: number;
|
|
360
|
+
bottom: number;
|
|
361
|
+
height: number;
|
|
362
|
+
visible: boolean;
|
|
363
|
+
slotHeight: number;
|
|
364
|
+
}
|
|
365
|
+
export interface FrameSnapshot {
|
|
366
|
+
location: Location;
|
|
367
|
+
pageState: PageState;
|
|
368
|
+
viewport: FrameViewportState;
|
|
369
|
+
frameSlots: FrameSlotRegistration[];
|
|
370
|
+
measuredAt: number;
|
|
371
|
+
}
|
|
372
|
+
export interface TransitionActionContext {
|
|
373
|
+
plan: TransitionPlan;
|
|
374
|
+
}
|
|
375
|
+
export interface TransitionAction {
|
|
376
|
+
type: "page" | "topChrome" | "bottomChrome" | "keyboard" | "safeArea" | (string & {});
|
|
377
|
+
run: (ctx: TransitionActionContext) => Promise<void> | void;
|
|
378
|
+
}
|
|
379
|
+
export interface TransitionPlan {
|
|
380
|
+
id: number;
|
|
381
|
+
intent: NavigationIntent;
|
|
382
|
+
type: TransitionType;
|
|
383
|
+
direction: "forward" | "back";
|
|
384
|
+
fromFrame: FrameSnapshot;
|
|
385
|
+
toFrame: FrameSnapshot;
|
|
386
|
+
actions: TransitionAction[];
|
|
387
|
+
duration: number;
|
|
388
|
+
}
|
|
389
|
+
export interface FrameLayerZIndex {
|
|
390
|
+
page: number;
|
|
391
|
+
previousPage: number;
|
|
392
|
+
cachedPage: number;
|
|
393
|
+
topChrome: number;
|
|
394
|
+
bottomChrome: number;
|
|
395
|
+
keyboard: number;
|
|
396
|
+
overlay: number;
|
|
397
|
+
}
|
|
398
|
+
export interface FrameLayoutState {
|
|
399
|
+
viewport: FrameViewportState;
|
|
400
|
+
keyboard: KeyboardFrameState;
|
|
401
|
+
contentViewport: FrameContentViewportState;
|
|
402
|
+
keyboardAccessory: KeyboardAccessoryFrameState;
|
|
403
|
+
platformProfile: FramePlatformProfile;
|
|
404
|
+
zIndex: FrameLayerZIndex;
|
|
405
|
+
pageStateByPath: Map<string, PageState>;
|
|
406
|
+
}
|
|
407
|
+
export interface FrameSlotRegistration {
|
|
408
|
+
scope?: FrameSlotScope;
|
|
409
|
+
type: FrameSlotType;
|
|
410
|
+
role?: FrameSlotRole;
|
|
411
|
+
height?: number;
|
|
412
|
+
estimatedHeight?: number;
|
|
413
|
+
source?: "navbar" | "topInset" | "bottomInset" | "bottomTab" | (string & {});
|
|
414
|
+
cache?: boolean;
|
|
415
|
+
}
|
|
298
416
|
export interface LayoutFallbackRoute {
|
|
299
417
|
path: string;
|
|
300
418
|
pathSegments: string[];
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { PageConfig, PageState } from "./csrTypes.d.ts";
|
|
2
|
+
export type DevicePlatform = "ios" | "android" | "web" | (string & {});
|
|
3
|
+
export type SafeAreaInsets = {
|
|
4
|
+
top: number;
|
|
5
|
+
bottom: number;
|
|
6
|
+
};
|
|
7
|
+
export interface ResolvePageStateOptions {
|
|
8
|
+
configChain?: PageConfig[];
|
|
9
|
+
path: string;
|
|
10
|
+
basePath?: string;
|
|
11
|
+
platform: DevicePlatform;
|
|
12
|
+
deviceSafeArea: SafeAreaInsets;
|
|
13
|
+
cssSafeArea?: SafeAreaInsets;
|
|
14
|
+
}
|
|
15
|
+
export declare function validatePageConfig(routeKey: string, config?: PageConfig): void;
|
|
16
|
+
export declare function mergePageConfigs(configChain?: PageConfig[]): PageConfig;
|
|
17
|
+
export declare function getExplicitPageConfigKeys(configChain?: PageConfig[]): Partial<Record<keyof PageConfig, boolean>>;
|
|
18
|
+
export declare function resolvePageState({ configChain, path, basePath, platform, deviceSafeArea, cssSafeArea, }: ResolvePageStateOptions): PageState;
|
|
19
|
+
export declare function readCssSafeAreaInsets(): SafeAreaInsets;
|
package/types/client/index.d.ts
CHANGED
|
@@ -4,6 +4,8 @@ export * from "./createFont.d.ts";
|
|
|
4
4
|
export * from "./csrTypes.d.ts";
|
|
5
5
|
export * from "./decorators.d.ts";
|
|
6
6
|
export * from "./device.d.ts";
|
|
7
|
+
export * from "./frameConfig.d.ts";
|
|
8
|
+
export * from "./frameDebug.d.ts";
|
|
7
9
|
export * from "./locale.d.ts";
|
|
8
10
|
export * from "./makePageProto.d.ts";
|
|
9
11
|
export * from "./router.d.ts";
|
package/types/client/router.d.ts
CHANGED
|
@@ -43,6 +43,8 @@ export declare class AkanNotFoundError extends Error {
|
|
|
43
43
|
}
|
|
44
44
|
declare global {
|
|
45
45
|
var __AKAN_ROUTER__: Router | undefined;
|
|
46
|
+
var __AKAN_DEV_SYNC_NAVIGATION__: ((href: string, kind: "push" | "replace" | "back" | "pop") => void) | undefined;
|
|
47
|
+
var __AKAN_DEV_SYNC_NAVIGATION_APPLYING__: boolean | undefined;
|
|
46
48
|
}
|
|
47
49
|
export declare const getPathInfo: (requestUrl: string, lang: string, prefix: string) => {
|
|
48
50
|
path: string;
|
|
@@ -12,6 +12,7 @@ export interface AkanDynamicUsage {
|
|
|
12
12
|
export interface AkanRequestStore {
|
|
13
13
|
request: Request;
|
|
14
14
|
theme?: AkanTheme;
|
|
15
|
+
frameState?: unknown;
|
|
15
16
|
queryCache: Map<string, Promise<unknown>>;
|
|
16
17
|
policy: AkanRequestPolicy;
|
|
17
18
|
dynamicUsage: AkanDynamicUsage;
|
|
@@ -29,6 +30,8 @@ export declare function createRequestStore(request: Request, policy?: Partial<Om
|
|
|
29
30
|
/** Stores theme preference on the active request when server rendering. */
|
|
30
31
|
export declare function setRequestTheme(theme: AkanTheme | undefined): void;
|
|
31
32
|
export declare function getRequestTheme(): AkanTheme | undefined;
|
|
33
|
+
export declare function setRequestFrameState(frameState: unknown): void;
|
|
34
|
+
export declare function getRequestFrameState<FrameState = unknown>(): FrameState | undefined;
|
|
32
35
|
export declare function pushRequestFallback(storeOrRequest: Request | AkanRequestStore): () => void;
|
|
33
36
|
/** Returns the active server request store from AsyncLocalStorage or the fallback stack. */
|
|
34
37
|
export declare function getRequestStore(): AkanRequestStore | undefined;
|