akanjs 3.1.0-rc.0 → 3.1.0-rc.2
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/base/primitiveRegistry.ts +75 -6
- package/client/cookie.ts +2 -1
- package/client/csrTypes.ts +8 -8
- package/client/frameConfig.ts +48 -46
- package/client/index.ts +2 -0
- package/client/mcpView.ts +2 -0
- package/client/route/RouteHead.ts +66 -12
- package/client/router.ts +35 -19
- package/client/transitionTypes.ts +6 -0
- package/client/viewTransition.ts +10 -0
- package/client/viewportMeta.ts +5 -5
- package/client/webManifest.ts +16 -0
- package/common/Logger.ts +7 -3
- package/common/PipelineNames.ts +9 -10
- package/common/PipelinePattern.ts +65 -19
- package/common/PipelinePlanSchema.ts +147 -155
- package/common/PipelineValueReader.ts +76 -0
- package/common/bearerTokenOf.ts +4 -0
- package/common/index.ts +3 -3
- package/common/jwtDecode.ts +12 -8
- package/common/mcpExposure.ts +5 -3
- package/common/parseSearchParams.ts +11 -0
- package/common/pipelinePlan.ts +6 -14
- package/common/types.ts +4 -5
- package/constant/via.ts +6 -2
- package/dictionary/base.dictionary.ts +4 -0
- package/fetch/client/fetchClient.ts +55 -30
- package/fetch/client/httpClient.ts +47 -12
- package/fetch/client/wsClient.ts +70 -30
- package/fetch/requestStorage.ts +1 -14
- package/index.ts +12 -176
- package/package.json +6 -1
- package/pipelineConfig.ts +167 -0
- package/server/akanApp.ts +3 -11
- package/server/akanServer.ts +6 -5
- package/server/assetHeaders.ts +19 -0
- package/server/di/diLifecycle.ts +6 -2
- package/server/di/utils.ts +3 -9
- package/server/edDsaJwt.ts +57 -0
- package/server/logging/logStreamRoute.ts +13 -6
- package/server/machine/machineToken.ts +9 -38
- package/server/mcp/McpAppShell.ts +51 -0
- package/server/mcp/McpApps.ts +14 -122
- package/server/mcp/McpAuth.ts +4 -4
- package/server/mcp/McpDispatcher.ts +27 -10
- package/server/mcp/McpPages.ts +243 -0
- package/server/mcp/McpRouter.ts +65 -207
- package/server/mcp/McpUiSession.ts +67 -0
- package/server/mcp/mcpAppViewRuntime.ts +87 -0
- package/server/ops/opsToken.ts +9 -30
- package/server/resolver/PendingSubscribes.ts +34 -0
- package/server/resolver/signal.resolver.ts +105 -66
- package/server/routeTreeBuilder.ts +15 -28
- package/server/routing/roomReplay.ts +79 -14
- package/server/rscClient.tsx +10 -8
- package/server/rscHeadPatch.ts +6 -4
- package/server/rscPagePrompts.ts +2 -3
- package/server/rscWorker.tsx +72 -48
- package/server/sitemap.ts +0 -14
- package/server/ssrFromRscRenderer.tsx +13 -16
- package/server/webRouter.ts +55 -57
- package/service/adapt.ts +1 -1
- package/service/predefinedAdaptor/queue.adaptor.ts +14 -8
- package/service/predefinedAdaptor/solidQueue.adaptor.ts +7 -5
- package/service/predefinedAdaptor/sql/SqlDocumentStore.ts +14 -13
- package/service/serve.ts +2 -2
- package/signal/TaggedJson.ts +79 -0
- package/signal/endpointCache.ts +6 -4
- package/signal/endpointIdempotency.ts +175 -27
- package/signal/mcp/McpDocument.ts +11 -7
- package/signal/mcp/mcpProtocol.ts +30 -1
- package/signal/signalContext.ts +27 -14
- package/store/action.ts +15 -20
- package/store/servedEndpointOf.ts +8 -0
- package/types/base/primitiveRegistry.d.ts +24 -2
- package/types/client/csrTypes.d.ts +9 -8
- package/types/client/frameConfig.d.ts +14 -11
- package/types/client/index.d.ts +2 -0
- package/types/client/mcpView.d.ts +2 -0
- package/types/client/route/RouteHead.d.ts +2 -0
- package/types/client/router.d.ts +10 -0
- package/types/client/transitionTypes.d.ts +4 -0
- package/types/client/viewTransition.d.ts +3 -0
- package/types/client/webManifest.d.ts +2 -0
- package/types/common/Logger.d.ts +1 -0
- package/types/common/PipelineNames.d.ts +1 -0
- package/types/common/PipelineValueReader.d.ts +36 -0
- package/types/common/bearerTokenOf.d.ts +1 -0
- package/types/common/index.d.ts +4 -1
- package/types/common/jwtDecode.d.ts +3 -1
- package/types/common/mcpExposure.d.ts +3 -1
- package/types/common/parseSearchParams.d.ts +3 -0
- package/types/common/pipelinePlan.d.ts +6 -8
- package/types/common/types.d.ts +3 -5
- package/types/dictionary/base.dictionary.d.ts +1 -1
- package/types/dictionary/dictionary.d.ts +8 -8
- package/types/fetch/client/fetchClient.d.ts +4 -2
- package/types/fetch/client/httpClient.d.ts +2 -0
- package/types/fetch/client/wsClient.d.ts +3 -8
- package/types/fetch/requestStorage.d.ts +0 -2
- package/types/index.d.ts +12 -158
- package/types/pipelineConfig.d.ts +148 -0
- package/types/server/assetHeaders.d.ts +9 -0
- package/types/server/edDsaJwt.d.ts +23 -0
- package/types/server/mcp/McpAppShell.d.ts +5 -0
- package/types/server/mcp/McpApps.d.ts +1 -9
- package/types/server/mcp/McpPages.d.ts +57 -0
- package/types/server/mcp/McpUiSession.d.ts +11 -0
- package/types/server/mcp/mcpAppViewRuntime.d.ts +1 -0
- package/types/server/ops/opsToken.d.ts +1 -1
- package/types/server/resolver/PendingSubscribes.d.ts +7 -0
- package/types/server/routeTreeBuilder.d.ts +0 -1
- package/types/server/routing/roomReplay.d.ts +14 -3
- package/types/server/sitemap.d.ts +0 -2
- package/types/service/adapt.d.ts +1 -1
- package/types/service/predefinedAdaptor/queue.adaptor.d.ts +7 -3
- package/types/service/predefinedAdaptor/solidQueue.adaptor.d.ts +2 -0
- package/types/service/serve.d.ts +2 -2
- package/types/signal/TaggedJson.d.ts +12 -0
- package/types/signal/endpointIdempotency.d.ts +2 -1
- package/types/signal/mcp/McpDocument.d.ts +5 -2
- package/types/signal/mcp/mcpProtocol.d.ts +29 -1
- package/types/signal/signalContext.d.ts +1 -0
- package/types/store/action.d.ts +1 -1
- package/types/store/servedEndpointOf.d.ts +1 -0
- package/types/ui/Agent/Launcher.d.ts +4 -2
- package/types/ui/Agent/attachment.d.ts +0 -1
- package/types/ui/Dialog/Modal.d.ts +2 -6
- package/types/ui/Dialog/modalSurface.d.ts +7 -1
- package/types/ui/Layout/FrameSlotPortal.d.ts +8 -0
- package/types/ui/Layout/frameSlot.d.ts +11 -0
- package/types/ui/Link/activePath.d.ts +1 -0
- package/types/ui/Modal.d.ts +2 -6
- package/types/ui/Model/EditModal.d.ts +2 -6
- package/types/ui/Model/New.d.ts +2 -6
- package/types/ui/Model/ViewEditModal.d.ts +2 -6
- package/types/ui/Model/ViewModal.d.ts +2 -6
- package/types/ui/Model/newFormOrigin.d.ts +10 -1
- package/types/ui/Portal.d.ts +1 -3
- package/types/ui/SheetHandle.d.ts +8 -0
- package/types/ui/System/Common.d.ts +1 -7
- package/types/ui/System/SSR.d.ts +4 -5
- package/types/ui/index.d.ts +1 -1
- package/types/ui/overlayLayer.d.ts +5 -0
- package/types/ui/useDismissDrag.d.ts +25 -0
- package/types/vendor/@akanjs/native/native/desktop/src/kiosk.d.ts +1 -0
- package/types/vendor/@akanjs/native/packages/cli/src/api.d.ts +24 -5
- package/types/vendor/@akanjs/native/packages/cli/src/commands/update.d.ts +1 -1
- package/types/vendor/@akanjs/native/packages/cli/src/config.d.ts +6 -0
- package/types/vendor/@akanjs/native/packages/cli/src/lib/compat.d.ts +6 -0
- package/types/vendor/@akanjs/native/packages/cli/src/lib/desktop-server.d.ts +2 -6
- package/types/vendor/@akanjs/native/packages/cli/src/lib/publish.d.ts +29 -1
- package/types/vendor/@akanjs/native/packages/cli/src/lib/updates.d.ts +29 -6
- package/types/vendor/@akanjs/native/packages/cli/src/platforms/windows-installer.d.ts +10 -6
- package/types/vendor/@akanjs/native/packages/cli/src/platforms/windows.d.ts +1 -0
- package/types/vendor/@akanjs/native/packages/core/src/kernel.d.ts +83 -1
- package/types/vendor/@akanjs/native/packages/desktop/src/dispatcher.d.ts +6 -0
- package/types/vendor/@akanjs/native/packages/desktop/src/host.d.ts +9 -0
- package/types/vendor/@akanjs/native/packages/desktop/src/main.d.ts +5 -0
- package/types/vendor/@akanjs/native/packages/desktop/src/server.d.ts +18 -6
- package/types/vendor/@akanjs/native/plugins/external-view/src/index.d.ts +26 -8
- package/types/vendor/@akanjs/native/plugins/single-instance/src/socket.d.ts +5 -1
- package/types/vendor/@akanjs/native/plugins/updates/src/desktop.d.ts +5 -0
- package/types/vendor/@akanjs/native/plugins/updates/src/index.d.ts +4 -3
- package/types/vendor/@akanjs/native/plugins/window/src/index.d.ts +3 -3
- package/types/vendor/use-agentic/AgentActivity.d.ts +3 -1
- package/types/vendor/use-agentic/AgenticSurface.d.ts +4 -2
- package/types/webkit/EmbeddedLayout.d.ts +10 -0
- package/types/webkit/FrameRootVars.d.ts +6 -0
- package/types/webkit/McpAppAgent.d.ts +4 -3
- package/types/webkit/McpAppBridge.d.ts +8 -29
- package/types/webkit/McpAppDownloads.d.ts +7 -0
- package/types/webkit/McpAppFetchBridge.d.ts +9 -0
- package/types/webkit/McpAppSize.d.ts +9 -0
- package/types/webkit/McpAppView.d.ts +0 -1
- package/types/webkit/RenderLayer.d.ts +3 -1
- package/ui/Agent/Chat.tsx +9 -6
- package/ui/Agent/EmbedApproval.tsx +6 -1
- package/ui/Agent/Launcher.tsx +5 -2
- package/ui/Agent/attachment.ts +3 -9
- package/ui/Agent/useKeyboardFrame.ts +5 -3
- package/ui/Agent/useScreenActions.ts +8 -4
- package/ui/BottomSheet.tsx +70 -51
- package/ui/Dialog/LegacyModal.tsx +15 -15
- package/ui/Dialog/Modal.tsx +14 -20
- package/ui/Dialog/modalSurface.ts +18 -8
- package/ui/Dropdown.tsx +8 -1
- package/ui/Layout/BottomInset.tsx +22 -22
- package/ui/Layout/BottomTab.tsx +3 -7
- package/ui/Layout/FrameSlotPortal.tsx +87 -0
- package/ui/Layout/Navbar.tsx +24 -21
- package/ui/Layout/TopInset.tsx +13 -26
- package/ui/Layout/frameSlot.ts +42 -0
- package/ui/Link/Action.tsx +9 -10
- package/ui/Link/Anchor.tsx +3 -6
- package/ui/Link/activePath.ts +8 -1
- package/ui/Load/Pagination.tsx +2 -2
- package/ui/Load/Units.tsx +2 -2
- package/ui/Modal.tsx +2 -6
- package/ui/Model/EditModal.tsx +4 -9
- package/ui/Model/New.tsx +2 -6
- package/ui/Model/ViewEditModal.tsx +30 -17
- package/ui/Model/ViewModal.tsx +2 -6
- package/ui/Model/newFormOrigin.ts +52 -15
- package/ui/Portal.tsx +2 -4
- package/ui/Select.tsx +12 -7
- package/ui/SheetHandle.tsx +13 -0
- package/ui/System/CSR.tsx +23 -13
- package/ui/System/Client.tsx +8 -15
- package/ui/System/Common.tsx +2 -36
- package/ui/System/Reconnect.tsx +5 -1
- package/ui/System/SSR.tsx +23 -37
- package/ui/Toast.tsx +3 -2
- package/ui/Tooltip.tsx +4 -2
- package/ui/index.ts +1 -1
- package/ui/overlayLayer.ts +5 -0
- package/ui/overlayPosition.ts +20 -26
- package/ui/recipe/inputRecipe.ts +4 -4
- package/ui/useDismissDrag.ts +48 -0
- package/vendor/@akanjs/native/native/android/src/com/akanjs/runtime/AkanNativeActivity.kt +19 -6
- package/vendor/@akanjs/native/native/android/src/com/akanjs/runtime/AkanNativeKernel.kt +104 -0
- package/vendor/@akanjs/native/native/android/src/com/akanjs/runtime/AkanNativeUpdateReports.kt +61 -70
- package/vendor/@akanjs/native/native/android/src/com/akanjs/runtime/AkanNativeUpdates.kt +37 -41
- package/vendor/@akanjs/native/native/android/src/com/akanjs/runtime/AkanNativeVectors.kt +39 -2
- package/vendor/@akanjs/native/native/desktop/src/dialog_args.rs +1 -1
- package/vendor/@akanjs/native/native/desktop/src/external.rs +693 -277
- package/vendor/@akanjs/native/native/desktop/src/kiosk.js +85 -0
- package/vendor/@akanjs/native/native/desktop/src/kiosk.rs +41 -0
- package/vendor/@akanjs/native/native/desktop/src/lib.rs +104 -126
- package/vendor/@akanjs/native/native/desktop/src/linux/kiosk.rs +18 -0
- package/vendor/@akanjs/native/native/desktop/src/linux/mod.rs +1 -0
- package/vendor/@akanjs/native/native/desktop/src/navigation.rs +79 -1
- package/vendor/@akanjs/native/native/desktop/src/page_cursor.rs +72 -0
- package/vendor/@akanjs/native/native/desktop/src/page_menu.rs +31 -1
- package/vendor/@akanjs/native/native/desktop/src/placement.rs +208 -1
- package/vendor/@akanjs/native/native/desktop/src/win/external.rs +307 -0
- package/vendor/@akanjs/native/native/desktop/src/win/kiosk.rs +42 -0
- package/vendor/@akanjs/native/native/desktop/src/win/mod.rs +2 -0
- package/vendor/@akanjs/native/native/ios/Sources/AkanNativeApp.swift +15 -10
- package/vendor/@akanjs/native/native/ios/Sources/AkanNativeKernel.swift +88 -0
- package/vendor/@akanjs/native/native/ios/Sources/AkanNativeUpdateReports.swift +65 -58
- package/vendor/@akanjs/native/native/ios/Sources/AkanNativeUpdates.swift +42 -31
- package/vendor/@akanjs/native/native/ios/Sources/AkanNativeVectors.swift +43 -0
- package/vendor/@akanjs/native/native/ios/Sources/AkanNativeViewController.swift +19 -10
- package/vendor/@akanjs/native/packages/cli/src/api.ts +57 -15
- package/vendor/@akanjs/native/packages/cli/src/commands/build.ts +4 -2
- package/vendor/@akanjs/native/packages/cli/src/commands/update.ts +53 -10
- package/vendor/@akanjs/native/packages/cli/src/config.ts +6 -0
- package/vendor/@akanjs/native/packages/cli/src/lib/compat.ts +17 -2
- package/vendor/@akanjs/native/packages/cli/src/lib/desktop-server.ts +5 -13
- package/vendor/@akanjs/native/packages/cli/src/lib/prepare.ts +11 -0
- package/vendor/@akanjs/native/packages/cli/src/lib/publish.ts +164 -4
- package/vendor/@akanjs/native/packages/cli/src/lib/updates.ts +114 -48
- package/vendor/@akanjs/native/packages/cli/src/lib/vectors.ts +11 -1
- package/vendor/@akanjs/native/packages/cli/src/platforms/android.ts +9 -9
- package/vendor/@akanjs/native/packages/cli/src/platforms/ios.ts +9 -9
- package/vendor/@akanjs/native/packages/cli/src/platforms/macos.ts +2 -2
- package/vendor/@akanjs/native/packages/cli/src/platforms/windows-installer.ts +77 -21
- package/vendor/@akanjs/native/packages/cli/src/platforms/windows.ts +10 -3
- package/vendor/@akanjs/native/packages/core/src/kernel.ts +138 -1
- package/vendor/@akanjs/native/packages/core/vectors/updates.json +92 -3
- package/vendor/@akanjs/native/packages/desktop/src/dispatcher.ts +13 -5
- package/vendor/@akanjs/native/packages/desktop/src/host.ts +49 -13
- package/vendor/@akanjs/native/packages/desktop/src/main.ts +6 -2
- package/vendor/@akanjs/native/packages/desktop/src/server.ts +33 -18
- package/vendor/@akanjs/native/plugins/external-view/src/desktop.ts +30 -11
- package/vendor/@akanjs/native/plugins/external-view/src/index.ts +26 -8
- package/vendor/@akanjs/native/plugins/single-instance/src/desktop.ts +4 -0
- package/vendor/@akanjs/native/plugins/single-instance/src/socket.ts +49 -6
- package/vendor/@akanjs/native/plugins/updates/android/UpdatesPlugin.kt +58 -54
- package/vendor/@akanjs/native/plugins/updates/ios/UpdatesPlugin.swift +80 -45
- package/vendor/@akanjs/native/plugins/updates/src/desktop.ts +95 -7
- package/vendor/@akanjs/native/plugins/updates/src/index.ts +4 -3
- package/vendor/@akanjs/native/plugins/window/src/index.ts +8 -4
- package/vendor/use-agentic/AgentActivity.tsx +4 -2
- package/vendor/use-agentic/AgenticSurface.ts +10 -3
- package/vendor/use-agentic/ToolRunner.ts +29 -7
- package/webkit/CsrRouteTable.ts +61 -13
- package/webkit/EmbeddedLayout.tsx +82 -0
- package/webkit/FrameRootVars.ts +15 -0
- package/webkit/McpAppAgent.ts +32 -10
- package/webkit/McpAppBridge.ts +44 -87
- package/webkit/McpAppDownloads.ts +63 -0
- package/webkit/McpAppFetchBridge.ts +63 -0
- package/webkit/McpAppSize.ts +101 -0
- package/webkit/McpAppView.ts +19 -134
- package/webkit/RenderLayer.tsx +20 -5
- package/webkit/bootCsr.tsx +4 -13
- package/webkit/useCamera.tsx +2 -7
- package/webkit/useCsrValues.ts +14 -5
- package/webkit/useLocation.ts +2 -12
- package/types/ui/Layout/FrameSlotFallback.d.ts +0 -9
- package/ui/Layout/FrameSlotFallback.tsx +0 -59
|
@@ -192,7 +192,7 @@ export class Any extends PrimitiveScalar {
|
|
|
192
192
|
}
|
|
193
193
|
PrimitiveRegistry.register(Any);
|
|
194
194
|
|
|
195
|
-
type NodeBuffer = Uint8Array & { toString(encoding: string): string };
|
|
195
|
+
type NodeBuffer = Uint8Array<ArrayBuffer> & { toString(encoding: string): string };
|
|
196
196
|
interface BufferCtor {
|
|
197
197
|
from(buffer: ArrayBufferLike, byteOffset: number, length: number): NodeBuffer;
|
|
198
198
|
from(text: string, encoding: string): NodeBuffer;
|
|
@@ -210,13 +210,13 @@ export class Binary extends PrimitiveScalar {
|
|
|
210
210
|
return value instanceof Uint8Array || typeof value === "string";
|
|
211
211
|
}
|
|
212
212
|
static override parseValue(input: Uint8Array | string): Uint8Array {
|
|
213
|
-
return typeof input === "string" ? Binary
|
|
213
|
+
return typeof input === "string" ? Binary.fromBase64(input) : input;
|
|
214
214
|
}
|
|
215
215
|
static override serializeValue(value: Uint8Array | string): string {
|
|
216
|
-
return typeof value === "string" ? value : Binary
|
|
216
|
+
return typeof value === "string" ? value : Binary.toBase64(value);
|
|
217
217
|
}
|
|
218
218
|
|
|
219
|
-
static
|
|
219
|
+
static toBase64(bytes: Uint8Array): string {
|
|
220
220
|
const buffer = (globalThis as { Buffer?: BufferCtor }).Buffer;
|
|
221
221
|
if (buffer) return buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength).toString("base64");
|
|
222
222
|
let binary = "";
|
|
@@ -224,7 +224,7 @@ export class Binary extends PrimitiveScalar {
|
|
|
224
224
|
binary += String.fromCharCode(...bytes.subarray(idx, idx + 0x8000));
|
|
225
225
|
return btoa(binary);
|
|
226
226
|
}
|
|
227
|
-
static
|
|
227
|
+
static fromBase64(text: string): Uint8Array<ArrayBuffer> {
|
|
228
228
|
const buffer = (globalThis as { Buffer?: BufferCtor }).Buffer;
|
|
229
229
|
if (buffer) return buffer.from(text, "base64");
|
|
230
230
|
const binary = atob(text);
|
|
@@ -246,6 +246,65 @@ export class Upload extends PrimitiveScalar {
|
|
|
246
246
|
}
|
|
247
247
|
PrimitiveRegistry.register(Upload);
|
|
248
248
|
|
|
249
|
+
export type ImageResultMimeType = (typeof ImageResult.mimeTypes)[number];
|
|
250
|
+
export interface ImageResultValue {
|
|
251
|
+
/** Standard padded base64, without a `data:` prefix. */
|
|
252
|
+
data: string;
|
|
253
|
+
mimeType: ImageResultMimeType;
|
|
254
|
+
caption?: string | null;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/** A picture an endpoint returns: JSON over HTTP, an image content block over MCP. Not storable. */
|
|
258
|
+
export class ImageResult extends PrimitiveScalar {
|
|
259
|
+
static override refName: "ImageResult" = "ImageResult";
|
|
260
|
+
static override [SERVER_VALUE]: ImageResultValue;
|
|
261
|
+
static override [CLIENT_VALUE]: ImageResultValue;
|
|
262
|
+
static override [DEFAULT_VALUE]: ImageResultValue | null = null;
|
|
263
|
+
static override [PURIFIED_VALUE]: ImageResultValue | null = null;
|
|
264
|
+
static override [EXAMPLE_VALUE]: ImageResultValue = {
|
|
265
|
+
data: "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=",
|
|
266
|
+
mimeType: "image/png",
|
|
267
|
+
caption: "A 1x1 pixel.",
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
static readonly mimeTypes = ["image/png", "image/jpeg", "image/webp", "image/gif"] as const;
|
|
271
|
+
static override jsonSchema: PrimitiveJsonSchema = {
|
|
272
|
+
type: "object",
|
|
273
|
+
properties: {
|
|
274
|
+
data: { type: "string", contentEncoding: "base64" },
|
|
275
|
+
mimeType: { type: "string", enum: [...ImageResult.mimeTypes] },
|
|
276
|
+
caption: { type: ["string", "null"] },
|
|
277
|
+
},
|
|
278
|
+
required: ["data", "mimeType"],
|
|
279
|
+
additionalProperties: false,
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
static override agent: PrimitiveAgentFace<ImageResultValue> = {
|
|
283
|
+
schema: {
|
|
284
|
+
type: "object",
|
|
285
|
+
properties: { mimeType: { type: "string" }, caption: { type: "string" } },
|
|
286
|
+
required: ["mimeType"],
|
|
287
|
+
},
|
|
288
|
+
read: ({ mimeType, caption }) => (caption ? { mimeType, caption } : { mimeType }),
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
static override validate(value: ImageResultValue): boolean {
|
|
292
|
+
if (!value || typeof value !== "object") return false;
|
|
293
|
+
const { data, mimeType, caption } = value;
|
|
294
|
+
return (
|
|
295
|
+
typeof data === "string" &&
|
|
296
|
+
ImageResult.#isBase64(data) &&
|
|
297
|
+
(ImageResult.mimeTypes as readonly string[]).includes(mimeType) &&
|
|
298
|
+
(caption === undefined || caption === null || typeof caption === "string")
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
static #isBase64(data: string) {
|
|
303
|
+
return data.length > 0 && data.length % 4 === 0 && /^[A-Za-z0-9+/]+={0,2}$/.test(data);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
PrimitiveRegistry.register(ImageResult);
|
|
307
|
+
|
|
249
308
|
declare global {
|
|
250
309
|
interface StringConstructor {
|
|
251
310
|
refName: "String";
|
|
@@ -388,4 +447,14 @@ Object.assign(Date, {
|
|
|
388
447
|
});
|
|
389
448
|
PrimitiveRegistry.register(Date);
|
|
390
449
|
|
|
391
|
-
export type DefaultPrimitiveName =
|
|
450
|
+
export type DefaultPrimitiveName =
|
|
451
|
+
| "String"
|
|
452
|
+
| "Boolean"
|
|
453
|
+
| "Date"
|
|
454
|
+
| "Int"
|
|
455
|
+
| "Float"
|
|
456
|
+
| "ID"
|
|
457
|
+
| "Any"
|
|
458
|
+
| "Binary"
|
|
459
|
+
| "Upload"
|
|
460
|
+
| "ImageResult";
|
package/client/cookie.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { getEnv } from "akanjs/base";
|
|
2
2
|
import {
|
|
3
3
|
authTokenKey,
|
|
4
|
+
bearerTokenOf,
|
|
4
5
|
decodeJwtPayload,
|
|
5
6
|
isOwnAuthToken,
|
|
6
7
|
Logger,
|
|
@@ -115,7 +116,7 @@ export const getAccount = <AddData = unknown>(): Account<AddData> => {
|
|
|
115
116
|
const embedded = globalThis.__AKAN_EMBED__;
|
|
116
117
|
if (embedded)
|
|
117
118
|
return embedded.account ? ({ ...defaultAccount, ...embedded.account } as Account<AddData>) : defaultAccount;
|
|
118
|
-
const jwt = getAuthToken() ?? getHeader("authorization")
|
|
119
|
+
const jwt = getAuthToken() ?? bearerTokenOf(getHeader("authorization"));
|
|
119
120
|
if (!jwt) return defaultAccount;
|
|
120
121
|
const account = decodeJwtPayload<Account<AddData>>(jwt);
|
|
121
122
|
if (account.appName !== getEnv().appName || account.environment !== getEnv().environment) return defaultAccount;
|
package/client/csrTypes.ts
CHANGED
|
@@ -3,12 +3,13 @@ import type { ReactDOMAttributes } from "@use-gesture/react/dist/declarations/sr
|
|
|
3
3
|
import type { PromiseOrObject } from "akanjs/base";
|
|
4
4
|
import { type ForwardRefExoticComponent, type ReactNode, type RefObject, useContext } from "react";
|
|
5
5
|
import type { AnimatedComponent, AnimatedProps, Interpolation, SpringValue } from "react-spring";
|
|
6
|
+
import type { TailwindBreakpoint } from "./frameConfig";
|
|
6
7
|
import type { RouteDefinition } from "./route/RouteDefinition";
|
|
7
8
|
import type { RouterInstance } from "./router";
|
|
8
9
|
import { sharedContext } from "./sharedContext";
|
|
10
|
+
import type { TransitionType } from "./transitionTypes";
|
|
9
11
|
import type { ReactFont } from "./types";
|
|
10
12
|
|
|
11
|
-
export type TransitionType = "none" | "fade" | "bottomUp" | "stack" | "scaleOut";
|
|
12
13
|
export type PageSafeAreaConfig =
|
|
13
14
|
| boolean
|
|
14
15
|
| "top"
|
|
@@ -22,14 +23,9 @@ export type PageSafeAreaConfig =
|
|
|
22
23
|
* `"block"` buffers the whole document, so an error in slow content can still render a clean error page. */
|
|
23
24
|
export type SsrRenderMode = "stream" | "block";
|
|
24
25
|
|
|
25
|
-
/** Px per
|
|
26
|
-
export interface ResponsiveInset {
|
|
26
|
+
/** Px per breakpoint of `insetBreakpoints`, each from its own width up, and `base` below the first. */
|
|
27
|
+
export interface ResponsiveInset extends Partial<{ [name in TailwindBreakpoint]: number }> {
|
|
27
28
|
base: number;
|
|
28
|
-
sm?: number;
|
|
29
|
-
md?: number;
|
|
30
|
-
lg?: number;
|
|
31
|
-
xl?: number;
|
|
32
|
-
"2xl"?: number;
|
|
33
29
|
}
|
|
34
30
|
export interface PageConfig {
|
|
35
31
|
transition?: TransitionType;
|
|
@@ -364,6 +360,10 @@ export const usePathCtx = () => {
|
|
|
364
360
|
return contextValues;
|
|
365
361
|
};
|
|
366
362
|
|
|
363
|
+
/** Inside a layout's own markup that an MCP view hides while the page is drawn on its own: frame chrome draws nothing. */
|
|
364
|
+
export const hiddenLayoutContext = sharedContext<boolean>("hiddenLayout", false);
|
|
365
|
+
export const useInHiddenLayout = () => useContext(hiddenLayoutContext);
|
|
366
|
+
|
|
367
367
|
export type PageActivity = "current" | "prev" | "pending" | "hidden";
|
|
368
368
|
export interface PageActivityState {
|
|
369
369
|
activity: PageActivity;
|
package/client/frameConfig.ts
CHANGED
|
@@ -1,12 +1,6 @@
|
|
|
1
1
|
import { isRecord } from "akanjs/common";
|
|
2
|
-
import type {
|
|
3
|
-
|
|
4
|
-
PageSafeAreaConfig,
|
|
5
|
-
PageState,
|
|
6
|
-
ResponsiveInset,
|
|
7
|
-
SsrRenderMode,
|
|
8
|
-
TransitionType,
|
|
9
|
-
} from "./csrTypes";
|
|
2
|
+
import type { PageConfig, PageSafeAreaConfig, PageState, ResponsiveInset, SsrRenderMode } from "./csrTypes";
|
|
3
|
+
import { transitionTypes } from "./transitionTypes";
|
|
10
4
|
|
|
11
5
|
export type DevicePlatform = "ios" | "android" | "web" | (string & {});
|
|
12
6
|
export type SafeAreaInsets = { top: number; bottom: number };
|
|
@@ -22,7 +16,7 @@ export interface ResolvePageStateOptions {
|
|
|
22
16
|
width?: number;
|
|
23
17
|
}
|
|
24
18
|
|
|
25
|
-
/** Tailwind's default breakpoints in rem, the widths a `ResponsiveInset` keys on. */
|
|
19
|
+
/** Tailwind's default breakpoints in rem, the widths a `ResponsiveInset` keys on; `ui/styles.css` repeats them. */
|
|
26
20
|
export const insetBreakpoints = [
|
|
27
21
|
["sm", 40],
|
|
28
22
|
["md", 48],
|
|
@@ -30,7 +24,14 @@ export const insetBreakpoints = [
|
|
|
30
24
|
["xl", 80],
|
|
31
25
|
["2xl", 96],
|
|
32
26
|
] as const;
|
|
33
|
-
export type
|
|
27
|
+
export type TailwindBreakpoint = (typeof insetBreakpoints)[number][0];
|
|
28
|
+
export type InsetBreakpoint = "base" | TailwindBreakpoint;
|
|
29
|
+
|
|
30
|
+
const breakpointRem = Object.fromEntries(insetBreakpoints) as { [name in TailwindBreakpoint]: number };
|
|
31
|
+
|
|
32
|
+
/** The media query Tailwind's `<name>:` variant matches, or with `below` its `max-<name>:` one. */
|
|
33
|
+
export const breakpointQuery = (name: TailwindBreakpoint, { below = false }: { below?: boolean } = {}) =>
|
|
34
|
+
`(width ${below ? "<" : ">="} ${breakpointRem[name]}rem)`;
|
|
34
35
|
|
|
35
36
|
const pageConfigKeys = new Set<keyof PageConfig>([
|
|
36
37
|
"transition",
|
|
@@ -46,13 +47,12 @@ const pageConfigKeys = new Set<keyof PageConfig>([
|
|
|
46
47
|
]);
|
|
47
48
|
|
|
48
49
|
const buildPageConfigKeys = new Set<keyof PageConfig>(["devOnly"]);
|
|
49
|
-
const transitionTypes = new Set<TransitionType>(["none", "fade", "bottomUp", "stack", "scaleOut"]);
|
|
50
50
|
const ssrRenderModes = new Set<SsrRenderMode>(["stream", "block"]);
|
|
51
51
|
const DEFAULT_BOOLEAN_INSET = 48;
|
|
52
52
|
|
|
53
53
|
const hasOwn = <Key extends PropertyKey>(value: object, key: Key) => Object.hasOwn(value, key);
|
|
54
54
|
|
|
55
|
-
export
|
|
55
|
+
export const validatePageConfig = (routeKey: string, config?: PageConfig) => {
|
|
56
56
|
if (config === undefined) return;
|
|
57
57
|
if (!isRecord(config)) throw new Error(`[route-convention] pageConfig in ${routeKey} must be an object.`);
|
|
58
58
|
const pageConfig = config as PageConfig;
|
|
@@ -64,7 +64,7 @@ export function validatePageConfig(routeKey: string, config?: PageConfig) {
|
|
|
64
64
|
if (pageConfig.devOnly !== undefined && typeof pageConfig.devOnly !== "boolean") {
|
|
65
65
|
throw new Error(`[route-convention] pageConfig.devOnly in ${routeKey} must be a boolean.`);
|
|
66
66
|
}
|
|
67
|
-
if (pageConfig.transition !== undefined && !transitionTypes.
|
|
67
|
+
if (pageConfig.transition !== undefined && !transitionTypes.includes(pageConfig.transition)) {
|
|
68
68
|
throw new Error(`[route-convention] unsupported pageConfig.transition "${pageConfig.transition}" in ${routeKey}`);
|
|
69
69
|
}
|
|
70
70
|
if (pageConfig.ssr !== undefined && !ssrRenderModes.has(pageConfig.ssr)) {
|
|
@@ -77,22 +77,22 @@ export function validatePageConfig(routeKey: string, config?: PageConfig) {
|
|
|
77
77
|
);
|
|
78
78
|
}
|
|
79
79
|
validateSafeAreaConfig(routeKey, pageConfig.safeArea);
|
|
80
|
-
}
|
|
80
|
+
};
|
|
81
81
|
|
|
82
82
|
const isInsetPx = (value: unknown): value is number =>
|
|
83
83
|
typeof value === "number" && Number.isFinite(value) && value >= 0;
|
|
84
84
|
const responsiveInsetKeys = new Set<string>(["base", ...insetBreakpoints.map(([name]) => name)]);
|
|
85
85
|
|
|
86
|
-
|
|
86
|
+
const isValidInsetValue = (value: unknown): value is NonNullable<PageConfig["topInset"]> => {
|
|
87
87
|
if (typeof value === "boolean" || isInsetPx(value)) return true;
|
|
88
88
|
if (!isRecord(value) || !isInsetPx(value.base)) return false;
|
|
89
89
|
return Object.entries(value).every(([key, px]) => responsiveInsetKeys.has(key) && isInsetPx(px));
|
|
90
|
-
}
|
|
90
|
+
};
|
|
91
91
|
|
|
92
92
|
export const isResponsiveInset = (inset: PageConfig["topInset"]): inset is ResponsiveInset => isRecord(inset);
|
|
93
93
|
|
|
94
94
|
/** The inset at each breakpoint, each one the nearest declared value at or below it. */
|
|
95
|
-
export
|
|
95
|
+
export const insetsByBreakpoint = (inset: PageConfig["topInset"]): { [bp in InsetBreakpoint]: number } => {
|
|
96
96
|
const base = isResponsiveInset(inset) ? inset.base : resolveInset(inset);
|
|
97
97
|
const values = { base } as { [bp in InsetBreakpoint]: number };
|
|
98
98
|
let current = base;
|
|
@@ -101,17 +101,17 @@ export function insetsByBreakpoint(inset: PageConfig["topInset"]): { [bp in Inse
|
|
|
101
101
|
values[name] = current;
|
|
102
102
|
}
|
|
103
103
|
return values;
|
|
104
|
-
}
|
|
104
|
+
};
|
|
105
105
|
|
|
106
106
|
/** The inset in force at `width` px, assuming a 16px rem. */
|
|
107
|
-
export
|
|
107
|
+
export const insetAt = (inset: PageConfig["topInset"], width?: number): number => {
|
|
108
108
|
if (!isResponsiveInset(inset)) return resolveInset(inset);
|
|
109
109
|
if (width === undefined) return inset.base;
|
|
110
110
|
const values = insetsByBreakpoint(inset);
|
|
111
111
|
return insetBreakpoints.reduce((value, [name, rem]) => (width >= rem * 16 ? values[name] : value), values.base);
|
|
112
|
-
}
|
|
112
|
+
};
|
|
113
113
|
|
|
114
|
-
|
|
114
|
+
const validateSafeAreaConfig = (routeKey: string, safeArea?: PageSafeAreaConfig) => {
|
|
115
115
|
if (safeArea === undefined || typeof safeArea === "boolean" || safeArea === "top" || safeArea === "bottom") return;
|
|
116
116
|
if (!isRecord(safeArea)) throw new Error(`[route-convention] pageConfig.safeArea in ${routeKey} is invalid.`);
|
|
117
117
|
for (const key of Object.keys(safeArea)) {
|
|
@@ -131,9 +131,9 @@ function validateSafeAreaConfig(routeKey: string, safeArea?: PageSafeAreaConfig)
|
|
|
131
131
|
) {
|
|
132
132
|
throw new Error(`[route-convention] pageConfig.safeArea.android in ${routeKey} is invalid.`);
|
|
133
133
|
}
|
|
134
|
-
}
|
|
134
|
+
};
|
|
135
135
|
|
|
136
|
-
export
|
|
136
|
+
export const mergePageConfigs = (configChain: PageConfig[] = []): PageConfig => {
|
|
137
137
|
const merged: PageConfig = {};
|
|
138
138
|
for (const config of configChain) {
|
|
139
139
|
for (const key of pageConfigKeys) {
|
|
@@ -147,9 +147,11 @@ export function mergePageConfigs(configChain: PageConfig[] = []): PageConfig {
|
|
|
147
147
|
}
|
|
148
148
|
}
|
|
149
149
|
return merged;
|
|
150
|
-
}
|
|
150
|
+
};
|
|
151
151
|
|
|
152
|
-
export
|
|
152
|
+
export const getExplicitPageConfigKeys = (
|
|
153
|
+
configChain: PageConfig[] = [],
|
|
154
|
+
): Partial<Record<keyof PageConfig, boolean>> => {
|
|
153
155
|
const explicitKeys: Partial<Record<keyof PageConfig, boolean>> = {};
|
|
154
156
|
for (const config of configChain) {
|
|
155
157
|
for (const key of pageConfigKeys) {
|
|
@@ -157,7 +159,7 @@ export function getExplicitPageConfigKeys(configChain: PageConfig[] = []): Parti
|
|
|
157
159
|
}
|
|
158
160
|
}
|
|
159
161
|
return explicitKeys;
|
|
160
|
-
}
|
|
162
|
+
};
|
|
161
163
|
|
|
162
164
|
export const defaultPageState: PageState = {
|
|
163
165
|
transition: "none",
|
|
@@ -172,7 +174,7 @@ export const defaultPageState: PageState = {
|
|
|
172
174
|
bottomSafeAreaColor: "var(--color-background, Canvas)",
|
|
173
175
|
};
|
|
174
176
|
|
|
175
|
-
export
|
|
177
|
+
export const resolvePageState = ({
|
|
176
178
|
configChain = [],
|
|
177
179
|
path,
|
|
178
180
|
basePath,
|
|
@@ -180,7 +182,7 @@ export function resolvePageState({
|
|
|
180
182
|
deviceSafeArea,
|
|
181
183
|
cssSafeArea,
|
|
182
184
|
width,
|
|
183
|
-
}: ResolvePageStateOptions): PageState {
|
|
185
|
+
}: ResolvePageStateOptions): PageState => {
|
|
184
186
|
const profile = getPlatformFrameProfile(platform);
|
|
185
187
|
const routeRole = getRouteRoleFrameDefaults(path, platform, basePath);
|
|
186
188
|
const config = mergePageConfigs([profile, routeRole, ...configChain]);
|
|
@@ -221,29 +223,29 @@ export function resolvePageState({
|
|
|
221
223
|
topSafeAreaColor: config.topSafeAreaColor ?? defaultPageState.topSafeAreaColor,
|
|
222
224
|
bottomSafeAreaColor: config.bottomSafeAreaColor ?? defaultPageState.bottomSafeAreaColor,
|
|
223
225
|
};
|
|
224
|
-
}
|
|
226
|
+
};
|
|
225
227
|
|
|
226
|
-
|
|
228
|
+
const resolveInset = (inset: PageConfig["topInset"]): number => {
|
|
227
229
|
if (inset === true) return DEFAULT_BOOLEAN_INSET;
|
|
228
230
|
if (inset === false || inset === undefined) return 0;
|
|
229
231
|
if (isResponsiveInset(inset)) return inset.base;
|
|
230
232
|
return inset;
|
|
231
|
-
}
|
|
233
|
+
};
|
|
232
234
|
|
|
233
|
-
|
|
235
|
+
const resolveSafeAreaEnv = (safeArea?: PageSafeAreaConfig): { top: boolean; bottom: boolean } | undefined => {
|
|
234
236
|
if (safeArea === undefined || safeArea === false) return undefined;
|
|
235
237
|
const top = safeArea === true || safeArea === "top" || (isRecord(safeArea) && safeArea.top !== false);
|
|
236
238
|
const bottom = safeArea === true || safeArea === "bottom" || (isRecord(safeArea) && safeArea.bottom !== false);
|
|
237
239
|
return top || bottom ? { top, bottom } : undefined;
|
|
238
|
-
}
|
|
240
|
+
};
|
|
239
241
|
|
|
240
|
-
|
|
242
|
+
const getPlatformFrameProfile = (platform: DevicePlatform): PageConfig => {
|
|
241
243
|
if (platform === "ios") return { safeArea: true, transition: "stack" };
|
|
242
244
|
if (platform === "android") return { safeArea: { android: "auto" }, transition: "scaleOut", gesture: false };
|
|
243
245
|
return { safeArea: false, transition: "none", gesture: false };
|
|
244
|
-
}
|
|
246
|
+
};
|
|
245
247
|
|
|
246
|
-
|
|
248
|
+
const getRouteRoleFrameDefaults = (path: string, platform: DevicePlatform, basePath?: string): PageConfig => {
|
|
247
249
|
const normalizedBasePath = basePath?.replace(/^\/+|\/+$/g, "");
|
|
248
250
|
const depth = path
|
|
249
251
|
.split("/")
|
|
@@ -255,9 +257,9 @@ function getRouteRoleFrameDefaults(path: string, platform: DevicePlatform, baseP
|
|
|
255
257
|
transition: platform === "ios" ? "stack" : platform === "android" ? "scaleOut" : "none",
|
|
256
258
|
gesture: platform === "ios",
|
|
257
259
|
};
|
|
258
|
-
}
|
|
260
|
+
};
|
|
259
261
|
|
|
260
|
-
|
|
262
|
+
const resolveSafeArea = ({
|
|
261
263
|
safeArea,
|
|
262
264
|
platform,
|
|
263
265
|
deviceSafeArea,
|
|
@@ -267,7 +269,7 @@ function resolveSafeArea({
|
|
|
267
269
|
platform: DevicePlatform;
|
|
268
270
|
deviceSafeArea: SafeAreaInsets;
|
|
269
271
|
cssSafeArea?: SafeAreaInsets;
|
|
270
|
-
}): SafeAreaInsets {
|
|
272
|
+
}): SafeAreaInsets => {
|
|
271
273
|
if (safeArea === false) return { top: 0, bottom: 0 };
|
|
272
274
|
const enabledAt = (side: "top" | "bottom") =>
|
|
273
275
|
safeArea === true ||
|
|
@@ -296,7 +298,7 @@ function resolveSafeArea({
|
|
|
296
298
|
top: topEnabled ? source.top : 0,
|
|
297
299
|
bottom: bottomEnabled ? source.bottom : 0,
|
|
298
300
|
};
|
|
299
|
-
}
|
|
301
|
+
};
|
|
300
302
|
|
|
301
303
|
const hasInset = (insets: SafeAreaInsets) => insets.top > 0 || insets.bottom > 0;
|
|
302
304
|
|
|
@@ -305,16 +307,16 @@ const maxInsets = (a: SafeAreaInsets, b?: SafeAreaInsets): SafeAreaInsets => ({
|
|
|
305
307
|
bottom: Math.max(a.bottom, b?.bottom ?? 0),
|
|
306
308
|
});
|
|
307
309
|
|
|
308
|
-
export
|
|
310
|
+
export const readCssSafeAreaInsets = (): SafeAreaInsets => {
|
|
309
311
|
if (typeof window === "undefined") return { top: 0, bottom: 0 };
|
|
310
312
|
const style = window.getComputedStyle?.(document.documentElement);
|
|
311
313
|
return {
|
|
312
314
|
top: readCssPixel(style?.getPropertyValue("--akan-native-safe-area-top")) || readCssEnvProbe("top"),
|
|
313
315
|
bottom: readCssPixel(style?.getPropertyValue("--akan-native-safe-area-bottom")) || readCssEnvProbe("bottom"),
|
|
314
316
|
};
|
|
315
|
-
}
|
|
317
|
+
};
|
|
316
318
|
|
|
317
|
-
|
|
319
|
+
const readCssEnvProbe = (side: "top" | "bottom") => {
|
|
318
320
|
if (typeof document === "undefined") return 0;
|
|
319
321
|
const probe = document.createElement("div");
|
|
320
322
|
probe.style.position = "fixed";
|
|
@@ -325,10 +327,10 @@ function readCssEnvProbe(side: "top" | "bottom") {
|
|
|
325
327
|
const value = readCssPixel(window.getComputedStyle(probe)[side]);
|
|
326
328
|
probe.remove();
|
|
327
329
|
return value;
|
|
328
|
-
}
|
|
330
|
+
};
|
|
329
331
|
|
|
330
|
-
|
|
332
|
+
const readCssPixel = (value?: string) => {
|
|
331
333
|
if (!value) return 0;
|
|
332
334
|
const parsed = Number.parseFloat(value);
|
|
333
335
|
return Number.isFinite(parsed) ? parsed : 0;
|
|
334
|
-
}
|
|
336
|
+
};
|
package/client/index.ts
CHANGED
|
@@ -9,12 +9,14 @@ export * from "./device";
|
|
|
9
9
|
export * from "./frameConfig";
|
|
10
10
|
export * from "./locale";
|
|
11
11
|
export * from "./makePageProto";
|
|
12
|
+
export * from "./mcpView";
|
|
12
13
|
export * from "./resolveServerUrl";
|
|
13
14
|
export * from "./route";
|
|
14
15
|
export * from "./router";
|
|
15
16
|
export * from "./rscNavigation";
|
|
16
17
|
export * from "./sharedContext";
|
|
17
18
|
export * from "./storage";
|
|
19
|
+
export * from "./transitionTypes";
|
|
18
20
|
export * from "./translator";
|
|
19
21
|
export * from "./types";
|
|
20
22
|
export * from "./useClient";
|
|
@@ -14,14 +14,27 @@ export class RouteHead {
|
|
|
14
14
|
"apple-touch-icon",
|
|
15
15
|
"mask-icon",
|
|
16
16
|
]);
|
|
17
|
+
|
|
18
|
+
static readonly #pageIdentityKeys = new Set(["link:canonical", "meta:property:og:url"]);
|
|
17
19
|
|
|
18
20
|
static merge(levels: ResolveHeadResult[]): ResolveHeadResult {
|
|
19
21
|
const present = levels.filter((level) => RouteHead.#nodeOf(level) != null || RouteHead.#snapshotOf(level));
|
|
20
22
|
if (present.length <= 1) return present[0];
|
|
21
23
|
const node = RouteHead.mergeNodes(present.map((level) => RouteHead.#nodeOf(level)));
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
24
|
+
if (!present.some((level) => RouteHead.#snapshotOf(level))) return node;
|
|
25
|
+
const snapshotLevels: AkanHeadSnapshotNode[][] = [];
|
|
26
|
+
for (const level of present) {
|
|
27
|
+
const nodes = RouteHead.#snapshotOf(level)?.nodes ?? RouteHead.#snapshotNodesOf(RouteHead.#nodeOf(level));
|
|
28
|
+
|
|
29
|
+
if (!nodes) return node;
|
|
30
|
+
snapshotLevels.push(nodes);
|
|
31
|
+
}
|
|
32
|
+
return { node, headSnapshot: RouteHead.#mergeSnapshots(snapshotLevels) } satisfies ResolvedHead;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** `merge` of a page's head over its layouts': a canonical or `og:url` comes from the page or not at all. */
|
|
36
|
+
static mergePage(layouts: ResolveHeadResult, page: ResolveHeadResult): ResolveHeadResult {
|
|
37
|
+
return RouteHead.merge([RouteHead.#withoutPageIdentity(layouts), page]);
|
|
25
38
|
}
|
|
26
39
|
|
|
27
40
|
/** What makes two tags the same tag; `null` for one that never replaces another. */
|
|
@@ -60,11 +73,7 @@ export class RouteHead {
|
|
|
60
73
|
const present = nodes.filter((node) => node !== null && node !== undefined);
|
|
61
74
|
if (present.length <= 1) return present[0];
|
|
62
75
|
const levels = present.map((node) => RouteHead.#flatten(node));
|
|
63
|
-
const kept = RouteHead.#keepDeepest(levels, (child) =>
|
|
64
|
-
isValidElement(child) && typeof child.type === "string"
|
|
65
|
-
? RouteHead.keyOf(child.type, child.props as TagProps)
|
|
66
|
-
: null,
|
|
67
|
-
);
|
|
76
|
+
const kept = RouteHead.#keepDeepest(levels, (child) => RouteHead.#keyOfNode(child));
|
|
68
77
|
|
|
69
78
|
const children = kept.map(({ item, level, idx }) =>
|
|
70
79
|
isValidElement(item) ? cloneElement(item as ReactElement, { key: `head:${level}:${idx}` }) : item,
|
|
@@ -72,14 +81,59 @@ export class RouteHead {
|
|
|
72
81
|
return createElement(Fragment, null, ...children);
|
|
73
82
|
}
|
|
74
83
|
|
|
75
|
-
static #
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
84
|
+
static #keyOfNode(child: ReactNode): string | null {
|
|
85
|
+
return isValidElement(child) && typeof child.type === "string"
|
|
86
|
+
? RouteHead.keyOf(child.type, child.props as TagProps)
|
|
87
|
+
: null;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
static #withoutPageIdentity(level: ResolveHeadResult): ResolveHeadResult {
|
|
91
|
+
const isIdentity = (key: string | null) => key !== null && RouteHead.#pageIdentityKeys.has(key);
|
|
92
|
+
const node = RouteHead.#nodeOf(level);
|
|
93
|
+
const children = RouteHead.#flatten(node);
|
|
94
|
+
const kept = children.filter((child) => !isIdentity(RouteHead.#keyOfNode(child)));
|
|
95
|
+
const keptNode =
|
|
96
|
+
kept.length === children.length ? node : kept.length > 0 ? createElement(Fragment, null, ...kept) : null;
|
|
97
|
+
const snapshot = RouteHead.#snapshotOf(level);
|
|
98
|
+
if (!snapshot) return keptNode;
|
|
99
|
+
const nodes = snapshot.nodes.filter((item) => !isIdentity(RouteHead.keyOf(item.tag, item.attrs ?? {})));
|
|
100
|
+
return { node: keptNode, headSnapshot: { ...snapshot, nodes } };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
static #mergeSnapshots(levels: AkanHeadSnapshotNode[][]): AkanHeadSnapshotV1 {
|
|
104
|
+
const kept = RouteHead.#keepDeepest(levels, (node: AkanHeadSnapshotNode) =>
|
|
105
|
+
RouteHead.keyOf(node.tag, node.attrs ?? {}),
|
|
79
106
|
);
|
|
80
107
|
return { version: 1, nodes: kept.map(({ item }) => item) };
|
|
81
108
|
}
|
|
82
109
|
|
|
110
|
+
static #snapshotNodesOf(node: ReactNode): AkanHeadSnapshotNode[] | null {
|
|
111
|
+
const nodes: AkanHeadSnapshotNode[] = [];
|
|
112
|
+
for (const child of RouteHead.#flatten(node)) {
|
|
113
|
+
const snapshotNode = RouteHead.#snapshotNodeOf(child);
|
|
114
|
+
if (!snapshotNode) return null;
|
|
115
|
+
nodes.push(snapshotNode);
|
|
116
|
+
}
|
|
117
|
+
return nodes;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
static #snapshotNodeOf(child: ReactNode): AkanHeadSnapshotNode | null {
|
|
121
|
+
if (!isValidElement(child)) return null;
|
|
122
|
+
const tag = child.type;
|
|
123
|
+
if (tag !== "title" && tag !== "meta" && tag !== "link") return null;
|
|
124
|
+
const { children, ...props } = child.props as TagProps;
|
|
125
|
+
if (tag === "title")
|
|
126
|
+
return typeof children === "string" || typeof children === "number" ? { tag, text: String(children) } : null;
|
|
127
|
+
if (children !== undefined && children !== null) return null;
|
|
128
|
+
const attrs: Record<string, string> = {};
|
|
129
|
+
for (const [name, value] of Object.entries(props)) {
|
|
130
|
+
if (value === undefined || value === null) continue;
|
|
131
|
+
if (typeof value !== "string" && typeof value !== "number") return null;
|
|
132
|
+
attrs[name] = String(value);
|
|
133
|
+
}
|
|
134
|
+
return { tag, attrs };
|
|
135
|
+
}
|
|
136
|
+
|
|
83
137
|
static #keepDeepest<T>(levels: T[][], keyOf: (item: T) => string | null) {
|
|
84
138
|
const owner = new Map<string, number>();
|
|
85
139
|
levels.forEach((items, level) => {
|