@swifttui/web 0.1.4 → 0.1.6
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/README.md +6 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/src/AccessibilityTree.js +5 -4
- package/dist/src/AccessibilityTree.js.map +1 -1
- package/dist/src/PointerGeometry.js +16 -1
- package/dist/src/PointerGeometry.js.map +1 -1
- package/dist/src/WebHostApp.d.ts +0 -1
- package/dist/src/WebHostSceneRuntime.d.ts +27 -2
- package/dist/src/WebHostSceneRuntime.js +41 -1
- package/dist/src/WebHostSceneRuntime.js.map +1 -1
- package/dist/src/WebHostSurfaceTransport.d.ts +40 -2
- package/dist/src/WebHostSurfaceTransport.js +59 -5
- package/dist/src/WebHostSurfaceTransport.js.map +1 -1
- package/dist/src/WebSocketSceneBridge.d.ts +0 -1
- package/dist/src/wasi/BrowserWASIBridge.d.ts +1 -2
- package/dist/src/wasi/BrowserWASIBridge.js +1 -1
- package/dist/src/wasi/SharedInputQueue.js +1 -1
- package/dist/src/wasi/SharedInputQueue.js.map +1 -1
- package/dist/src/wasi/WasmSceneRuntime.d.ts +0 -1
- package/dist/src/wasi/WasmSceneWorker.d.ts +0 -1
- package/dist/src/wasi/WasmSceneWorker.js +2 -2
- package/dist/src/wasi/WasmSceneWorker.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -104,6 +104,12 @@ self-describing.
|
|
|
104
104
|
the library does not provide a built-in mode switcher.
|
|
105
105
|
- `BrowserWASIBridge` sets `TUIGUI_TRANSPORT=surface` and decodes surface
|
|
106
106
|
frames before handing them to the canvas runtime.
|
|
107
|
+
- Hyperlink cells published by the app (`links`/`linkTargets` on the frame)
|
|
108
|
+
are clickable: a click opens the target in a new tab (`http(s)` only) or
|
|
109
|
+
through the `onOpenHyperlink` runtime option; the pointer cursor signals
|
|
110
|
+
linked cells. Accessibility nodes the app marks `hidden` stay out of the
|
|
111
|
+
ARIA tree, and the runtime exposes the frame's `focusPresentation` and
|
|
112
|
+
`preferredGridSize` for embedders.
|
|
107
113
|
|
|
108
114
|
## Developing this package
|
|
109
115
|
|
package/dist/index.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { ResolvedWebHostTerminalPalette, ResolvedWebHostTerminalStyle, WebHostANSIColors, WebHostTerminalAppearance, WebHostTerminalCursorStyle, WebHostTerminalPalette, WebHostTerminalRenderStyle, WebHostTerminalStyle, WebHostTerminalTheme, applyWebHostTerminalStyle, decodeWebHostTerminalRenderStyleBase64, encodeWebHostTerminalRenderStyleBase64, mergeWebHostTerminalStyle, normalizeWebHostTerminalStyle, resolveWebHostTerminalRenderStyle, webTUITerminalBackgroundColor } from "./src/WebHostTerminalStyle.js";
|
|
2
|
-
import { WebHostAccessibilityAnnouncement, WebHostAccessibilityLiveRegion, WebHostAccessibilityNode, WebHostAccessibilityPoint, WebHostFrameDiagnosticRecord, WebHostKeyInput, WebHostMouseInput, WebHostOutputDecoder, WebHostOutputRecord, WebHostOutputSink, WebHostRuntimeIssue, WebHostScrollRegion, WebHostSurfaceCell, WebHostSurfaceDamage, WebHostSurfaceDamageRange, WebHostSurfaceDamageTextRow, WebHostSurfaceDeltaFrame, WebHostSurfaceDeltaRow, WebHostSurfaceFrame, WebHostSurfaceImage, WebHostSurfaceImageFormat, WebHostSurfaceLineStyle, WebHostSurfaceRect, WebHostSurfaceSize, WebHostSurfaceStyle, encodeKeyInputMessage, encodeMouseInputMessage, encodePasteInputMessage, encodeRenderStyleControlMessage, encodeResizeControlMessage } from "./src/WebHostSurfaceTransport.js";
|
|
2
|
+
import { SUPPORTED_SURFACE_VERSION, WebHostAccessibilityAnnouncement, WebHostAccessibilityLiveRegion, WebHostAccessibilityNode, WebHostAccessibilityPoint, WebHostFocusPresentation, WebHostFocusSemantics, WebHostFrameDiagnosticRecord, WebHostKeyInput, WebHostMouseInput, WebHostOutputDecoder, WebHostOutputRecord, WebHostOutputSink, WebHostRuntimeIssue, WebHostScrollRegion, WebHostSurfaceCell, WebHostSurfaceDamage, WebHostSurfaceDamageRange, WebHostSurfaceDamageTextRow, WebHostSurfaceDeltaFrame, WebHostSurfaceDeltaRow, WebHostSurfaceFrame, WebHostSurfaceImage, WebHostSurfaceImageFormat, WebHostSurfaceLineStyle, WebHostSurfaceLinkRow, WebHostSurfaceLinkRun, WebHostSurfaceRect, WebHostSurfaceSize, WebHostSurfaceStyle, encodeKeyInputMessage, encodeMouseInputMessage, encodePasteInputMessage, encodeRenderStyleControlMessage, encodeResizeControlMessage } from "./src/WebHostSurfaceTransport.js";
|
|
3
3
|
import { WebHostSceneDescriptor, WebHostSceneManifest, WebHostSceneManifestSource, loadWebHostSceneManifest, normalizeWebHostSceneManifest, webTUISceneManifestFromDescriptors, webTUISceneManifestToJSON } from "./src/WebHostSceneManifest.js";
|
|
4
4
|
import { WebHostSceneBridge, WebHostSceneRuntime, WebHostSceneRuntimeOptions, WheelMode } from "./src/WebHostSceneRuntime.js";
|
|
5
5
|
import { WebSocketSceneBridge, WebSocketSceneBridgeFactory, WebSocketSceneBridgeOptions, WebSocketSceneSocket, webSocketSceneURL } from "./src/WebSocketSceneBridge.js";
|
|
6
6
|
import { WebHostAppController, WebHostAppOptions, WebHostBridgeFactory, WebHostBridgeFactoryOptions, WebHostEmbeddedHostConfig, createWebHostApp } from "./src/WebHostApp.js";
|
|
7
7
|
import { StdIOPipe } from "./src/wasi/StdIOPipe.js";
|
|
8
8
|
import { BrowserWASIBridge, BrowserWASIBridgeOptions, BrowserWASIOutputSink } from "./src/wasi/BrowserWASIBridge.js";
|
|
9
|
-
export { BrowserWASIBridge, BrowserWASIBridgeOptions, BrowserWASIOutputSink, ResolvedWebHostTerminalPalette, ResolvedWebHostTerminalStyle, StdIOPipe, WebHostANSIColors, WebHostAccessibilityAnnouncement, WebHostAccessibilityLiveRegion, WebHostAccessibilityNode, WebHostAccessibilityPoint, WebHostAppController, WebHostAppOptions, WebHostBridgeFactory, WebHostBridgeFactoryOptions, WebHostEmbeddedHostConfig, WebHostFrameDiagnosticRecord, WebHostKeyInput, WebHostMouseInput, WebHostOutputDecoder, WebHostOutputRecord, WebHostOutputSink, WebHostRuntimeIssue, WebHostSceneBridge, WebHostSceneDescriptor, WebHostSceneManifest, WebHostSceneManifestSource, WebHostSceneRuntime, WebHostSceneRuntimeOptions, WebHostScrollRegion, WebHostSurfaceCell, WebHostSurfaceDamage, WebHostSurfaceDamageRange, WebHostSurfaceDamageTextRow, WebHostSurfaceDeltaFrame, WebHostSurfaceDeltaRow, WebHostSurfaceFrame, WebHostSurfaceImage, WebHostSurfaceImageFormat, WebHostSurfaceLineStyle, WebHostSurfaceRect, WebHostSurfaceSize, WebHostSurfaceStyle, WebHostTerminalAppearance, WebHostTerminalCursorStyle, WebHostTerminalPalette, WebHostTerminalRenderStyle, WebHostTerminalStyle, WebHostTerminalTheme, WebSocketSceneBridge, WebSocketSceneBridgeFactory, WebSocketSceneBridgeOptions, WebSocketSceneSocket, WheelMode, applyWebHostTerminalStyle, createWebHostApp, decodeWebHostTerminalRenderStyleBase64, encodeKeyInputMessage, encodeMouseInputMessage, encodePasteInputMessage, encodeRenderStyleControlMessage, encodeResizeControlMessage, encodeWebHostTerminalRenderStyleBase64, loadWebHostSceneManifest, mergeWebHostTerminalStyle, normalizeWebHostSceneManifest, normalizeWebHostTerminalStyle, resolveWebHostTerminalRenderStyle, webSocketSceneURL, webTUISceneManifestFromDescriptors, webTUISceneManifestToJSON, webTUITerminalBackgroundColor };
|
|
9
|
+
export { BrowserWASIBridge, BrowserWASIBridgeOptions, BrowserWASIOutputSink, ResolvedWebHostTerminalPalette, ResolvedWebHostTerminalStyle, SUPPORTED_SURFACE_VERSION, StdIOPipe, WebHostANSIColors, WebHostAccessibilityAnnouncement, WebHostAccessibilityLiveRegion, WebHostAccessibilityNode, WebHostAccessibilityPoint, WebHostAppController, WebHostAppOptions, WebHostBridgeFactory, WebHostBridgeFactoryOptions, WebHostEmbeddedHostConfig, WebHostFocusPresentation, WebHostFocusSemantics, WebHostFrameDiagnosticRecord, WebHostKeyInput, WebHostMouseInput, WebHostOutputDecoder, WebHostOutputRecord, WebHostOutputSink, WebHostRuntimeIssue, WebHostSceneBridge, WebHostSceneDescriptor, WebHostSceneManifest, WebHostSceneManifestSource, WebHostSceneRuntime, WebHostSceneRuntimeOptions, WebHostScrollRegion, WebHostSurfaceCell, WebHostSurfaceDamage, WebHostSurfaceDamageRange, WebHostSurfaceDamageTextRow, WebHostSurfaceDeltaFrame, WebHostSurfaceDeltaRow, WebHostSurfaceFrame, WebHostSurfaceImage, WebHostSurfaceImageFormat, WebHostSurfaceLineStyle, WebHostSurfaceLinkRow, WebHostSurfaceLinkRun, WebHostSurfaceRect, WebHostSurfaceSize, WebHostSurfaceStyle, WebHostTerminalAppearance, WebHostTerminalCursorStyle, WebHostTerminalPalette, WebHostTerminalRenderStyle, WebHostTerminalStyle, WebHostTerminalTheme, WebSocketSceneBridge, WebSocketSceneBridgeFactory, WebSocketSceneBridgeOptions, WebSocketSceneSocket, WheelMode, applyWebHostTerminalStyle, createWebHostApp, decodeWebHostTerminalRenderStyleBase64, encodeKeyInputMessage, encodeMouseInputMessage, encodePasteInputMessage, encodeRenderStyleControlMessage, encodeResizeControlMessage, encodeWebHostTerminalRenderStyleBase64, loadWebHostSceneManifest, mergeWebHostTerminalStyle, normalizeWebHostSceneManifest, normalizeWebHostTerminalStyle, resolveWebHostTerminalRenderStyle, webSocketSceneURL, webTUISceneManifestFromDescriptors, webTUISceneManifestToJSON, webTUITerminalBackgroundColor };
|
package/dist/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { StdIOPipe } from "./src/wasi/StdIOPipe.js";
|
|
2
2
|
import { applyWebHostTerminalStyle, decodeWebHostTerminalRenderStyleBase64, encodeWebHostTerminalRenderStyleBase64, mergeWebHostTerminalStyle, normalizeWebHostTerminalStyle, resolveWebHostTerminalRenderStyle, webTUITerminalBackgroundColor } from "./src/WebHostTerminalStyle.js";
|
|
3
|
-
import { WebHostOutputDecoder, encodeKeyInputMessage, encodeMouseInputMessage, encodePasteInputMessage, encodeRenderStyleControlMessage, encodeResizeControlMessage } from "./src/WebHostSurfaceTransport.js";
|
|
3
|
+
import { SUPPORTED_SURFACE_VERSION, WebHostOutputDecoder, encodeKeyInputMessage, encodeMouseInputMessage, encodePasteInputMessage, encodeRenderStyleControlMessage, encodeResizeControlMessage } from "./src/WebHostSurfaceTransport.js";
|
|
4
4
|
import { BrowserWASIBridge } from "./src/wasi/BrowserWASIBridge.js";
|
|
5
5
|
import { WebSocketSceneBridge, webSocketSceneURL } from "./src/WebSocketSceneBridge.js";
|
|
6
6
|
import { loadWebHostSceneManifest, normalizeWebHostSceneManifest, webTUISceneManifestFromDescriptors, webTUISceneManifestToJSON } from "./src/WebHostSceneManifest.js";
|
|
7
7
|
import { WebHostSceneRuntime } from "./src/WebHostSceneRuntime.js";
|
|
8
8
|
import { createWebHostApp } from "./src/WebHostApp.js";
|
|
9
|
-
export { BrowserWASIBridge, StdIOPipe, WebHostOutputDecoder, WebHostSceneRuntime, WebSocketSceneBridge, applyWebHostTerminalStyle, createWebHostApp, decodeWebHostTerminalRenderStyleBase64, encodeKeyInputMessage, encodeMouseInputMessage, encodePasteInputMessage, encodeRenderStyleControlMessage, encodeResizeControlMessage, encodeWebHostTerminalRenderStyleBase64, loadWebHostSceneManifest, mergeWebHostTerminalStyle, normalizeWebHostSceneManifest, normalizeWebHostTerminalStyle, resolveWebHostTerminalRenderStyle, webSocketSceneURL, webTUISceneManifestFromDescriptors, webTUISceneManifestToJSON, webTUITerminalBackgroundColor };
|
|
9
|
+
export { BrowserWASIBridge, SUPPORTED_SURFACE_VERSION, StdIOPipe, WebHostOutputDecoder, WebHostSceneRuntime, WebSocketSceneBridge, applyWebHostTerminalStyle, createWebHostApp, decodeWebHostTerminalRenderStyleBase64, encodeKeyInputMessage, encodeMouseInputMessage, encodePasteInputMessage, encodeRenderStyleControlMessage, encodeResizeControlMessage, encodeWebHostTerminalRenderStyleBase64, loadWebHostSceneManifest, mergeWebHostTerminalStyle, normalizeWebHostSceneManifest, normalizeWebHostTerminalStyle, resolveWebHostTerminalRenderStyle, webSocketSceneURL, webTUISceneManifestFromDescriptors, webTUISceneManifestToJSON, webTUITerminalBackgroundColor };
|
|
@@ -15,22 +15,23 @@ var AccessibilityTreeMounter = class {
|
|
|
15
15
|
applyScreenReaderOnlyStyle(this.announcerElement);
|
|
16
16
|
}
|
|
17
17
|
present(nodes, metrics, announcements = [], options = {}) {
|
|
18
|
+
const visibleNodes = nodes.filter((node) => !node.hidden);
|
|
18
19
|
const previousById = this.nodesById;
|
|
19
20
|
const nextById = /* @__PURE__ */ new Map();
|
|
20
|
-
for (const node of
|
|
21
|
+
for (const node of visibleNodes) {
|
|
21
22
|
const element = previousById.get(node.id) ?? document.createElement("div");
|
|
22
23
|
this.applyNodeAttributes(element, node, metrics);
|
|
23
24
|
nextById.set(node.id, element);
|
|
24
25
|
}
|
|
25
26
|
for (const id of previousById.keys()) if (!nextById.has(id)) previousById.get(id)?.remove();
|
|
26
27
|
this.nodesById = nextById;
|
|
27
|
-
for (const node of
|
|
28
|
+
for (const node of visibleNodes) {
|
|
28
29
|
const element = nextById.get(node.id);
|
|
29
30
|
if (!element) continue;
|
|
30
31
|
((node.parentId ? nextById.get(node.parentId) : void 0) ?? this.element).appendChild(element);
|
|
31
32
|
}
|
|
32
|
-
this.announceLiveRegionChanges(
|
|
33
|
-
const focused =
|
|
33
|
+
this.announceLiveRegionChanges(visibleNodes, announcements);
|
|
34
|
+
const focused = visibleNodes.find((node) => node.isFocused);
|
|
34
35
|
if ((options.synchronizeFocus ?? true) && focused) this.nodesById.get(focused.id)?.focus?.({ preventScroll: true });
|
|
35
36
|
}
|
|
36
37
|
applyNodeAttributes(element, node, metrics) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AccessibilityTree.js","names":[],"sources":["../../src/AccessibilityTree.ts"],"sourcesContent":["import type {\n WebHostAccessibilityAnnouncement,\n WebHostAccessibilityNode,\n} from \"./WebHostSurfaceTransport.ts\";\n\ninterface AccessibilityTreeMetrics {\n cellWidth: number;\n cellHeight: number;\n}\n\ninterface AccessibilityTreePresentationOptions {\n synchronizeFocus?: boolean;\n}\n\ninterface RoleMapping {\n role?: string;\n level?: number;\n}\n\nexport class AccessibilityTreeMounter {\n readonly element: HTMLElement;\n readonly announcerElement: HTMLElement;\n\n private nodesById = new Map<string, HTMLElement>();\n private previousLabelsById = new Map<string, string>();\n private hasLiveRegionBaseline = false;\n\n constructor() {\n this.element = document.createElement(\"div\");\n this.element.className = \"webhost-scene__accessibility-tree\";\n applyScreenReaderOnlyStyle(this.element);\n\n this.announcerElement = document.createElement(\"div\");\n this.announcerElement.className = \"webhost-scene__accessibility-announcer\";\n this.announcerElement.setAttribute(\"aria-atomic\", \"true\");\n applyScreenReaderOnlyStyle(this.announcerElement);\n }\n\n present(\n nodes: WebHostAccessibilityNode[],\n metrics: AccessibilityTreeMetrics,\n announcements: WebHostAccessibilityAnnouncement[] = [],\n options: AccessibilityTreePresentationOptions = {}\n ): void {\n const previousById = this.nodesById;\n const nextById = new Map<string, HTMLElement>();\n\n for (const node of nodes) {\n const existing = previousById.get(node.id);\n const element = existing ?? document.createElement(\"div\");\n this.applyNodeAttributes(element, node, metrics);\n nextById.set(node.id, element);\n }\n\n for (const id of previousById.keys()) {\n if (!nextById.has(id)) {\n previousById.get(id)?.remove();\n }\n }\n\n this.nodesById = nextById;\n\n for (const node of nodes) {\n const element = nextById.get(node.id);\n if (!element) {\n continue;\n }\n\n const parent = node.parentId ? nextById.get(node.parentId) : undefined;\n (parent ?? this.element).appendChild(element);\n }\n\n this.announceLiveRegionChanges(nodes, announcements);\n\n const focused = nodes.find((node) => node.isFocused);\n if ((options.synchronizeFocus ?? true) && focused) {\n this.nodesById.get(focused.id)?.focus?.({ preventScroll: true });\n }\n }\n\n private applyNodeAttributes(\n element: HTMLElement,\n node: WebHostAccessibilityNode,\n metrics: AccessibilityTreeMetrics\n ): void {\n element.id = `swifttui-a11y-${stableDOMId(node.id)}`;\n element.dataset.accessibilityId = node.id;\n element.tabIndex = node.isFocused ? 0 : -1;\n\n const role = roleMapping(node.role);\n setOrRemoveAttribute(element, \"role\", role.role);\n setOrRemoveAttribute(\n element,\n \"aria-level\",\n role.level !== undefined ? String(role.level) : undefined\n );\n setOrRemoveAttribute(element, \"aria-label\", node.label || undefined);\n setOrRemoveAttribute(element, \"aria-description\", node.hint || undefined);\n setOrRemoveAttribute(element, \"aria-live\", node.liveRegion || undefined);\n if (node.isFocused) {\n element.dataset.focused = \"true\";\n } else {\n delete element.dataset.focused;\n }\n\n const [x, y, width, height] = node.rect;\n element.style.position = \"absolute\";\n element.style.left = `${x * metrics.cellWidth}px`;\n element.style.top = `${y * metrics.cellHeight}px`;\n element.style.width = `${Math.max(1, width) * metrics.cellWidth}px`;\n element.style.height = `${Math.max(1, height) * metrics.cellHeight}px`;\n }\n\n private announceLiveRegionChanges(\n nodes: WebHostAccessibilityNode[],\n announcements: WebHostAccessibilityAnnouncement[]\n ): void {\n const candidates = nodes.filter(\n (node) => node.liveRegion && node.liveRegion !== \"off\" && node.label\n );\n const currentLabelsById = new Map(candidates.map((node) => [node.id, node.label ?? \"\"]));\n const imperativeAssertive = announcements.filter(\n (announcement) => announcement.politeness === \"assertive\"\n );\n const imperativePolite = announcements.filter(\n (announcement) => announcement.politeness === \"polite\"\n );\n\n if (!this.hasLiveRegionBaseline) {\n this.previousLabelsById = currentLabelsById;\n this.hasLiveRegionBaseline = true;\n this.publishAnnouncements([], imperativeAssertive, [], imperativePolite);\n return;\n }\n\n const changed = candidates.filter((node) => {\n const previous = this.previousLabelsById.get(node.id);\n return previous !== undefined && previous !== node.label;\n });\n this.previousLabelsById = currentLabelsById;\n\n const assertive = changed.filter((node) => node.liveRegion === \"assertive\");\n const polite = changed.filter((node) => node.liveRegion === \"polite\");\n this.publishAnnouncements(assertive, imperativeAssertive, polite, imperativePolite);\n }\n\n private publishAnnouncements(\n assertive: WebHostAccessibilityNode[],\n imperativeAssertive: WebHostAccessibilityAnnouncement[],\n polite: WebHostAccessibilityNode[],\n imperativePolite: WebHostAccessibilityAnnouncement[]\n ): void {\n const ordered = [...assertive, ...imperativeAssertive, ...polite, ...imperativePolite];\n if (ordered.length === 0) {\n return;\n }\n\n const politeness = assertive.length > 0 || imperativeAssertive.length > 0\n ? \"assertive\"\n : \"polite\";\n this.announcerElement.setAttribute(\"aria-live\", politeness);\n this.announcerElement.textContent = ordered.map((entry) => {\n if (\"message\" in entry) {\n return entry.message;\n }\n return entry.label ?? \"\";\n }).join(\"\\n\");\n }\n}\n\nfunction setOrRemoveAttribute(\n element: HTMLElement,\n name: string,\n value: string | undefined\n): void {\n if (value === undefined) {\n element.removeAttribute(name);\n return;\n }\n element.setAttribute(name, value);\n}\n\nfunction applyScreenReaderOnlyStyle(\n element: HTMLElement\n): void {\n element.style.position = \"absolute\";\n element.style.left = \"0\";\n element.style.top = \"0\";\n element.style.width = \"1px\";\n element.style.height = \"1px\";\n element.style.overflow = \"hidden\";\n element.style.clipPath = \"inset(50%)\";\n element.style.whiteSpace = \"nowrap\";\n}\n\nfunction roleMapping(\n role: string\n): RoleMapping {\n const heading = /^heading\\(level: ([0-9]+)\\)$/.exec(role);\n if (heading) {\n return {\n role: \"heading\",\n level: Math.max(1, Math.min(6, Number(heading[1]))),\n };\n }\n\n const custom = /^custom\\((.+)\\)$/.exec(role);\n if (custom) {\n return { role: custom[1] };\n }\n\n switch (role) {\n case \"alert\":\n case \"button\":\n case \"cell\":\n case \"checkbox\":\n case \"grid\":\n case \"group\":\n case \"link\":\n case \"list\":\n case \"menu\":\n case \"region\":\n case \"separator\":\n case \"slider\":\n case \"status\":\n case \"tab\":\n case \"table\":\n case \"timer\":\n return { role };\n case \"columnHeader\":\n return { role: \"columnheader\" };\n case \"confirmationDialog\":\n case \"sheet\":\n return { role: \"dialog\" };\n case \"disclosureGroup\":\n case \"scrollView\":\n case \"scrollViewWithIndicators\":\n case \"section\":\n return { role: \"region\" };\n case \"image\":\n return { role: \"img\" };\n case \"menuItem\":\n return { role: \"menuitem\" };\n case \"picker\":\n return { role: \"combobox\" };\n case \"progressBar\":\n return { role: \"progressbar\" };\n case \"rowHeader\":\n return { role: \"rowheader\" };\n case \"secureField\":\n case \"textEditor\":\n case \"textField\":\n return { role: \"textbox\" };\n case \"stepper\":\n return { role: \"spinbutton\" };\n case \"tabPanel\":\n return { role: \"tabpanel\" };\n case \"tableRow\":\n return { role: \"row\" };\n case \"tabView\":\n return { role: \"tablist\" };\n case \"toggle\":\n return { role: \"checkbox\" };\n default:\n return { role: \"group\" };\n }\n}\n\nfunction stableDOMId(\n id: string\n): string {\n return Array.from(id).map((character) => {\n if (/^[a-zA-Z0-9_-]$/.test(character)) {\n return character;\n }\n return `-${character.codePointAt(0)?.toString(16) ?? \"0\"}-`;\n }).join(\"\");\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"AccessibilityTree.js","names":[],"sources":["../../src/AccessibilityTree.ts"],"sourcesContent":["import type {\n WebHostAccessibilityAnnouncement,\n WebHostAccessibilityNode,\n} from \"./WebHostSurfaceTransport.ts\";\n\ninterface AccessibilityTreeMetrics {\n cellWidth: number;\n cellHeight: number;\n}\n\ninterface AccessibilityTreePresentationOptions {\n synchronizeFocus?: boolean;\n}\n\ninterface RoleMapping {\n role?: string;\n level?: number;\n}\n\nexport class AccessibilityTreeMounter {\n readonly element: HTMLElement;\n readonly announcerElement: HTMLElement;\n\n private nodesById = new Map<string, HTMLElement>();\n private previousLabelsById = new Map<string, string>();\n private hasLiveRegionBaseline = false;\n\n constructor() {\n this.element = document.createElement(\"div\");\n this.element.className = \"webhost-scene__accessibility-tree\";\n applyScreenReaderOnlyStyle(this.element);\n\n this.announcerElement = document.createElement(\"div\");\n this.announcerElement.className = \"webhost-scene__accessibility-announcer\";\n this.announcerElement.setAttribute(\"aria-atomic\", \"true\");\n applyScreenReaderOnlyStyle(this.announcerElement);\n }\n\n present(\n nodes: WebHostAccessibilityNode[],\n metrics: AccessibilityTreeMetrics,\n announcements: WebHostAccessibilityAnnouncement[] = [],\n options: AccessibilityTreePresentationOptions = {}\n ): void {\n // Nodes the app marked hidden stay out of the assistive-technology tree,\n // mirroring the Android host's overlay filter. Hidden is per-node on the\n // wire, so children of a hidden node re-parent to the mount root.\n const visibleNodes = nodes.filter((node) => !node.hidden);\n const previousById = this.nodesById;\n const nextById = new Map<string, HTMLElement>();\n\n for (const node of visibleNodes) {\n const existing = previousById.get(node.id);\n const element = existing ?? document.createElement(\"div\");\n this.applyNodeAttributes(element, node, metrics);\n nextById.set(node.id, element);\n }\n\n for (const id of previousById.keys()) {\n if (!nextById.has(id)) {\n previousById.get(id)?.remove();\n }\n }\n\n this.nodesById = nextById;\n\n for (const node of visibleNodes) {\n const element = nextById.get(node.id);\n if (!element) {\n continue;\n }\n\n const parent = node.parentId ? nextById.get(node.parentId) : undefined;\n (parent ?? this.element).appendChild(element);\n }\n\n this.announceLiveRegionChanges(visibleNodes, announcements);\n\n const focused = visibleNodes.find((node) => node.isFocused);\n if ((options.synchronizeFocus ?? true) && focused) {\n this.nodesById.get(focused.id)?.focus?.({ preventScroll: true });\n }\n }\n\n private applyNodeAttributes(\n element: HTMLElement,\n node: WebHostAccessibilityNode,\n metrics: AccessibilityTreeMetrics\n ): void {\n element.id = `swifttui-a11y-${stableDOMId(node.id)}`;\n element.dataset.accessibilityId = node.id;\n element.tabIndex = node.isFocused ? 0 : -1;\n\n const role = roleMapping(node.role);\n setOrRemoveAttribute(element, \"role\", role.role);\n setOrRemoveAttribute(\n element,\n \"aria-level\",\n role.level !== undefined ? String(role.level) : undefined\n );\n setOrRemoveAttribute(element, \"aria-label\", node.label || undefined);\n setOrRemoveAttribute(element, \"aria-description\", node.hint || undefined);\n setOrRemoveAttribute(element, \"aria-live\", node.liveRegion || undefined);\n if (node.isFocused) {\n element.dataset.focused = \"true\";\n } else {\n delete element.dataset.focused;\n }\n\n const [x, y, width, height] = node.rect;\n element.style.position = \"absolute\";\n element.style.left = `${x * metrics.cellWidth}px`;\n element.style.top = `${y * metrics.cellHeight}px`;\n element.style.width = `${Math.max(1, width) * metrics.cellWidth}px`;\n element.style.height = `${Math.max(1, height) * metrics.cellHeight}px`;\n }\n\n private announceLiveRegionChanges(\n nodes: WebHostAccessibilityNode[],\n announcements: WebHostAccessibilityAnnouncement[]\n ): void {\n const candidates = nodes.filter(\n (node) => node.liveRegion && node.liveRegion !== \"off\" && node.label\n );\n const currentLabelsById = new Map(candidates.map((node) => [node.id, node.label ?? \"\"]));\n const imperativeAssertive = announcements.filter(\n (announcement) => announcement.politeness === \"assertive\"\n );\n const imperativePolite = announcements.filter(\n (announcement) => announcement.politeness === \"polite\"\n );\n\n if (!this.hasLiveRegionBaseline) {\n this.previousLabelsById = currentLabelsById;\n this.hasLiveRegionBaseline = true;\n this.publishAnnouncements([], imperativeAssertive, [], imperativePolite);\n return;\n }\n\n const changed = candidates.filter((node) => {\n const previous = this.previousLabelsById.get(node.id);\n return previous !== undefined && previous !== node.label;\n });\n this.previousLabelsById = currentLabelsById;\n\n const assertive = changed.filter((node) => node.liveRegion === \"assertive\");\n const polite = changed.filter((node) => node.liveRegion === \"polite\");\n this.publishAnnouncements(assertive, imperativeAssertive, polite, imperativePolite);\n }\n\n private publishAnnouncements(\n assertive: WebHostAccessibilityNode[],\n imperativeAssertive: WebHostAccessibilityAnnouncement[],\n polite: WebHostAccessibilityNode[],\n imperativePolite: WebHostAccessibilityAnnouncement[]\n ): void {\n const ordered = [...assertive, ...imperativeAssertive, ...polite, ...imperativePolite];\n if (ordered.length === 0) {\n return;\n }\n\n const politeness = assertive.length > 0 || imperativeAssertive.length > 0\n ? \"assertive\"\n : \"polite\";\n this.announcerElement.setAttribute(\"aria-live\", politeness);\n this.announcerElement.textContent = ordered.map((entry) => {\n if (\"message\" in entry) {\n return entry.message;\n }\n return entry.label ?? \"\";\n }).join(\"\\n\");\n }\n}\n\nfunction setOrRemoveAttribute(\n element: HTMLElement,\n name: string,\n value: string | undefined\n): void {\n if (value === undefined) {\n element.removeAttribute(name);\n return;\n }\n element.setAttribute(name, value);\n}\n\nfunction applyScreenReaderOnlyStyle(\n element: HTMLElement\n): void {\n element.style.position = \"absolute\";\n element.style.left = \"0\";\n element.style.top = \"0\";\n element.style.width = \"1px\";\n element.style.height = \"1px\";\n element.style.overflow = \"hidden\";\n element.style.clipPath = \"inset(50%)\";\n element.style.whiteSpace = \"nowrap\";\n}\n\nfunction roleMapping(\n role: string\n): RoleMapping {\n const heading = /^heading\\(level: ([0-9]+)\\)$/.exec(role);\n if (heading) {\n return {\n role: \"heading\",\n level: Math.max(1, Math.min(6, Number(heading[1]))),\n };\n }\n\n const custom = /^custom\\((.+)\\)$/.exec(role);\n if (custom) {\n return { role: custom[1] };\n }\n\n switch (role) {\n case \"alert\":\n case \"button\":\n case \"cell\":\n case \"checkbox\":\n case \"grid\":\n case \"group\":\n case \"link\":\n case \"list\":\n case \"menu\":\n case \"region\":\n case \"separator\":\n case \"slider\":\n case \"status\":\n case \"tab\":\n case \"table\":\n case \"timer\":\n return { role };\n case \"columnHeader\":\n return { role: \"columnheader\" };\n case \"confirmationDialog\":\n case \"sheet\":\n return { role: \"dialog\" };\n case \"disclosureGroup\":\n case \"scrollView\":\n case \"scrollViewWithIndicators\":\n case \"section\":\n return { role: \"region\" };\n case \"image\":\n return { role: \"img\" };\n case \"menuItem\":\n return { role: \"menuitem\" };\n case \"picker\":\n return { role: \"combobox\" };\n case \"progressBar\":\n return { role: \"progressbar\" };\n case \"rowHeader\":\n return { role: \"rowheader\" };\n case \"secureField\":\n case \"textEditor\":\n case \"textField\":\n return { role: \"textbox\" };\n case \"stepper\":\n return { role: \"spinbutton\" };\n case \"tabPanel\":\n return { role: \"tabpanel\" };\n case \"tableRow\":\n return { role: \"row\" };\n case \"tabView\":\n return { role: \"tablist\" };\n case \"toggle\":\n return { role: \"checkbox\" };\n default:\n return { role: \"group\" };\n }\n}\n\nfunction stableDOMId(\n id: string\n): string {\n return Array.from(id).map((character) => {\n if (/^[a-zA-Z0-9_-]$/.test(character)) {\n return character;\n }\n return `-${character.codePointAt(0)?.toString(16) ?? \"0\"}-`;\n }).join(\"\");\n}\n"],"mappings":"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"}
|
|
@@ -26,6 +26,21 @@ function rawCellLocationForEvent(event, metrics) {
|
|
|
26
26
|
};
|
|
27
27
|
}
|
|
28
28
|
/**
|
|
29
|
+
* The hyperlink destination under `location`, resolved through the frame's
|
|
30
|
+
* per-row link runs and deduplicated `linkTargets` table. Mirrors the Android
|
|
31
|
+
* host's cell hit-test (tap on a linked cell opens its URI). Returns
|
|
32
|
+
* `undefined` when the cell carries no link or the frame published none.
|
|
33
|
+
*/
|
|
34
|
+
function linkTargetAt(links, linkTargets, location) {
|
|
35
|
+
if (!links || !linkTargets || linkTargets.length === 0) return;
|
|
36
|
+
const cellX = Math.floor(location.x);
|
|
37
|
+
const cellY = Math.floor(location.y);
|
|
38
|
+
for (const [row, runs] of links) {
|
|
39
|
+
if (row !== cellY) continue;
|
|
40
|
+
for (const [x, span, targetIndex] of runs) if (cellX >= x && cellX < x + span) return linkTargets[targetIndex];
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
29
44
|
* Whether any scrollable region under `location` can still scroll in the
|
|
30
45
|
* wheel's direction. Mirrors the Swift host's scroll hit-test: a region
|
|
31
46
|
* qualifies when its viewport contains the cell AND it has remaining headroom
|
|
@@ -67,6 +82,6 @@ function regionCanScrollInDirection(region, deltaX, deltaY) {
|
|
|
67
82
|
return false;
|
|
68
83
|
}
|
|
69
84
|
//#endregion
|
|
70
|
-
export { cellLocationForEvent, rawCellLocationForEvent, wheelTargetCanScroll };
|
|
85
|
+
export { cellLocationForEvent, linkTargetAt, rawCellLocationForEvent, wheelTargetCanScroll };
|
|
71
86
|
|
|
72
87
|
//# sourceMappingURL=PointerGeometry.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PointerGeometry.js","names":[],"sources":["../../src/PointerGeometry.ts"],"sourcesContent":["import type {\n WebHostScrollRegion,\n} from \"./WebHostSurfaceTransport.ts\";\nimport type { CellLocation } from \"./InputEventEncoder.ts\";\n\n/**\n * The cell-grid geometry a pointer hit-test needs: the surface's bounding\n * client rect (the canvas's, falling back to the terminal mount's), the cell\n * dimensions, and the grid extent in cells. The host snapshots this per event.\n */\nexport interface PointerGeometryMetrics {\n rect?: { left: number; top: number } | null;\n cellWidth: number;\n cellHeight: number;\n columns: number;\n rows: number;\n}\n\n/**\n * Converts a pointer event to fractional cell coordinates, clamped to the\n * grid. Returns `undefined` when the pointer is outside the cell grid (in the\n * sub-cell margin/gutter), so the host can leave the event unhandled.\n */\nexport function cellLocationForEvent(\n event: MouseEvent,\n metrics: PointerGeometryMetrics\n): CellLocation | undefined {\n const location = rawCellLocationForEvent(event, metrics);\n if (!location) {\n return undefined;\n }\n\n const cellX = Math.floor(location.x);\n const cellY = Math.floor(location.y);\n if (cellX < 0 || cellY < 0 || cellX >= metrics.columns || cellY >= metrics.rows) {\n return undefined;\n }\n return location;\n}\n\n/**\n * Converts a pointer event to fractional cell coordinates *without* clamping to\n * the grid. Used while a pointer is captured (drag) so the host keeps tracking\n * the gesture even when it strays outside the surface bounds.\n */\nexport function rawCellLocationForEvent(\n event: MouseEvent,\n metrics: PointerGeometryMetrics\n): CellLocation | undefined {\n const rect = metrics.rect;\n if (!rect) {\n return undefined;\n }\n\n const x = (event.clientX - rect.left) / metrics.cellWidth;\n const y = (event.clientY - rect.top) / metrics.cellHeight;\n return { x, y };\n}\n\n/**\n * Whether any scrollable region under `location` can still scroll in the\n * wheel's direction. Mirrors the Swift host's scroll hit-test: a region\n * qualifies when its viewport contains the cell AND it has remaining headroom\n * in the delta's direction. Used by \"chain\" wheel mode to decide capture vs.\n * fall-through. With no published `scrollRegions`, nothing can scroll, so the\n * wheel chains to the page (a scene with no ScrollView stays fully passive).\n */\nexport function wheelTargetCanScroll(\n regions: readonly WebHostScrollRegion[] | undefined,\n location: CellLocation,\n deltaX: number,\n deltaY: number\n): boolean {\n if (!regions || regions.length === 0) {\n return false;\n }\n\n const cellX = Math.floor(location.x);\n const cellY = Math.floor(location.y);\n // Any region under the pointer that can move in this direction qualifies —\n // this is what lets an outer ScrollView take over when an inner one is at\n // its edge (nested scroll), and chains to the page only when none can.\n for (const region of regions) {\n const [rx, ry, rw, rh] = region.rect;\n if (cellX < rx || cellY < ry || cellX >= rx + rw || cellY >= ry + rh) {\n continue;\n }\n if (regionCanScrollInDirection(region, deltaX, deltaY)) {\n return true;\n }\n }\n return false;\n}\n\n/**\n * Whether a published scroll region has remaining headroom in the wheel's\n * direction, recomputing the per-direction extent from offset/content/viewport.\n * Mirrors SwiftTUI's clamp (`min(max(0, offset), max(0, content - viewport))`)\n * so the host and the app agree on \"at edge\". Wheel sign convention matches the\n * app: `deltaY > 0` scrolls down (offset grows toward the content bottom).\n * Diagonal wheels qualify if either axis has headroom.\n */\nfunction regionCanScrollInDirection(\n region: WebHostScrollRegion,\n deltaX: number,\n deltaY: number\n): boolean {\n const [, , viewportWidth, viewportHeight] = region.rect;\n const [offsetX, offsetY] = region.offset;\n const [contentWidth, contentHeight] = region.content;\n const maxX = Math.max(0, contentWidth - viewportWidth);\n const maxY = Math.max(0, contentHeight - viewportHeight);\n const clampedX = Math.min(Math.max(0, offsetX), maxX);\n const clampedY = Math.min(Math.max(0, offsetY), maxY);\n\n if (deltaY > 0 && clampedY < maxY) {\n return true;\n }\n if (deltaY < 0 && clampedY > 0) {\n return true;\n }\n if (deltaX > 0 && clampedX < maxX) {\n return true;\n }\n if (deltaX < 0 && clampedX > 0) {\n return true;\n }\n return false;\n}\n"],"mappings":";;;;;;
|
|
1
|
+
{"version":3,"file":"PointerGeometry.js","names":[],"sources":["../../src/PointerGeometry.ts"],"sourcesContent":["import type {\n WebHostScrollRegion,\n WebHostSurfaceLinkRow,\n} from \"./WebHostSurfaceTransport.ts\";\nimport type { CellLocation } from \"./InputEventEncoder.ts\";\n\n/**\n * The cell-grid geometry a pointer hit-test needs: the surface's bounding\n * client rect (the canvas's, falling back to the terminal mount's), the cell\n * dimensions, and the grid extent in cells. The host snapshots this per event.\n */\nexport interface PointerGeometryMetrics {\n rect?: { left: number; top: number } | null;\n cellWidth: number;\n cellHeight: number;\n columns: number;\n rows: number;\n}\n\n/**\n * Converts a pointer event to fractional cell coordinates, clamped to the\n * grid. Returns `undefined` when the pointer is outside the cell grid (in the\n * sub-cell margin/gutter), so the host can leave the event unhandled.\n */\nexport function cellLocationForEvent(\n event: MouseEvent,\n metrics: PointerGeometryMetrics\n): CellLocation | undefined {\n const location = rawCellLocationForEvent(event, metrics);\n if (!location) {\n return undefined;\n }\n\n const cellX = Math.floor(location.x);\n const cellY = Math.floor(location.y);\n if (cellX < 0 || cellY < 0 || cellX >= metrics.columns || cellY >= metrics.rows) {\n return undefined;\n }\n return location;\n}\n\n/**\n * Converts a pointer event to fractional cell coordinates *without* clamping to\n * the grid. Used while a pointer is captured (drag) so the host keeps tracking\n * the gesture even when it strays outside the surface bounds.\n */\nexport function rawCellLocationForEvent(\n event: MouseEvent,\n metrics: PointerGeometryMetrics\n): CellLocation | undefined {\n const rect = metrics.rect;\n if (!rect) {\n return undefined;\n }\n\n const x = (event.clientX - rect.left) / metrics.cellWidth;\n const y = (event.clientY - rect.top) / metrics.cellHeight;\n return { x, y };\n}\n\n/**\n * The hyperlink destination under `location`, resolved through the frame's\n * per-row link runs and deduplicated `linkTargets` table. Mirrors the Android\n * host's cell hit-test (tap on a linked cell opens its URI). Returns\n * `undefined` when the cell carries no link or the frame published none.\n */\nexport function linkTargetAt(\n links: readonly WebHostSurfaceLinkRow[] | undefined,\n linkTargets: readonly string[] | undefined,\n location: CellLocation\n): string | undefined {\n if (!links || !linkTargets || linkTargets.length === 0) {\n return undefined;\n }\n\n const cellX = Math.floor(location.x);\n const cellY = Math.floor(location.y);\n for (const [row, runs] of links) {\n if (row !== cellY) {\n continue;\n }\n for (const [x, span, targetIndex] of runs) {\n if (cellX >= x && cellX < x + span) {\n return linkTargets[targetIndex];\n }\n }\n }\n return undefined;\n}\n\n/**\n * Whether any scrollable region under `location` can still scroll in the\n * wheel's direction. Mirrors the Swift host's scroll hit-test: a region\n * qualifies when its viewport contains the cell AND it has remaining headroom\n * in the delta's direction. Used by \"chain\" wheel mode to decide capture vs.\n * fall-through. With no published `scrollRegions`, nothing can scroll, so the\n * wheel chains to the page (a scene with no ScrollView stays fully passive).\n */\nexport function wheelTargetCanScroll(\n regions: readonly WebHostScrollRegion[] | undefined,\n location: CellLocation,\n deltaX: number,\n deltaY: number\n): boolean {\n if (!regions || regions.length === 0) {\n return false;\n }\n\n const cellX = Math.floor(location.x);\n const cellY = Math.floor(location.y);\n // Any region under the pointer that can move in this direction qualifies —\n // this is what lets an outer ScrollView take over when an inner one is at\n // its edge (nested scroll), and chains to the page only when none can.\n for (const region of regions) {\n const [rx, ry, rw, rh] = region.rect;\n if (cellX < rx || cellY < ry || cellX >= rx + rw || cellY >= ry + rh) {\n continue;\n }\n if (regionCanScrollInDirection(region, deltaX, deltaY)) {\n return true;\n }\n }\n return false;\n}\n\n/**\n * Whether a published scroll region has remaining headroom in the wheel's\n * direction, recomputing the per-direction extent from offset/content/viewport.\n * Mirrors SwiftTUI's clamp (`min(max(0, offset), max(0, content - viewport))`)\n * so the host and the app agree on \"at edge\". Wheel sign convention matches the\n * app: `deltaY > 0` scrolls down (offset grows toward the content bottom).\n * Diagonal wheels qualify if either axis has headroom.\n */\nfunction regionCanScrollInDirection(\n region: WebHostScrollRegion,\n deltaX: number,\n deltaY: number\n): boolean {\n const [, , viewportWidth, viewportHeight] = region.rect;\n const [offsetX, offsetY] = region.offset;\n const [contentWidth, contentHeight] = region.content;\n const maxX = Math.max(0, contentWidth - viewportWidth);\n const maxY = Math.max(0, contentHeight - viewportHeight);\n const clampedX = Math.min(Math.max(0, offsetX), maxX);\n const clampedY = Math.min(Math.max(0, offsetY), maxY);\n\n if (deltaY > 0 && clampedY < maxY) {\n return true;\n }\n if (deltaY < 0 && clampedY > 0) {\n return true;\n }\n if (deltaX > 0 && clampedX < maxX) {\n return true;\n }\n if (deltaX < 0 && clampedX > 0) {\n return true;\n }\n return false;\n}\n"],"mappings":";;;;;;AAwBA,SAAgB,qBACd,OACA,SAC0B;CAC1B,MAAM,WAAW,wBAAwB,OAAO,OAAO;CACvD,IAAI,CAAC,UACH;CAGF,MAAM,QAAQ,KAAK,MAAM,SAAS,CAAC;CACnC,MAAM,QAAQ,KAAK,MAAM,SAAS,CAAC;CACnC,IAAI,QAAQ,KAAK,QAAQ,KAAK,SAAS,QAAQ,WAAW,SAAS,QAAQ,MACzE;CAEF,OAAO;AACT;;;;;;AAOA,SAAgB,wBACd,OACA,SAC0B;CAC1B,MAAM,OAAO,QAAQ;CACrB,IAAI,CAAC,MACH;CAKF,OAAO;EAAE,IAFE,MAAM,UAAU,KAAK,QAAQ,QAAQ;EAEpC,IADD,MAAM,UAAU,KAAK,OAAO,QAAQ;CACjC;AAChB;;;;;;;AAQA,SAAgB,aACd,OACA,aACA,UACoB;CACpB,IAAI,CAAC,SAAS,CAAC,eAAe,YAAY,WAAW,GACnD;CAGF,MAAM,QAAQ,KAAK,MAAM,SAAS,CAAC;CACnC,MAAM,QAAQ,KAAK,MAAM,SAAS,CAAC;CACnC,KAAK,MAAM,CAAC,KAAK,SAAS,OAAO;EAC/B,IAAI,QAAQ,OACV;EAEF,KAAK,MAAM,CAAC,GAAG,MAAM,gBAAgB,MACnC,IAAI,SAAS,KAAK,QAAQ,IAAI,MAC5B,OAAO,YAAY;CAGzB;AAEF;;;;;;;;;AAUA,SAAgB,qBACd,SACA,UACA,QACA,QACS;CACT,IAAI,CAAC,WAAW,QAAQ,WAAW,GACjC,OAAO;CAGT,MAAM,QAAQ,KAAK,MAAM,SAAS,CAAC;CACnC,MAAM,QAAQ,KAAK,MAAM,SAAS,CAAC;CAInC,KAAK,MAAM,UAAU,SAAS;EAC5B,MAAM,CAAC,IAAI,IAAI,IAAI,MAAM,OAAO;EAChC,IAAI,QAAQ,MAAM,QAAQ,MAAM,SAAS,KAAK,MAAM,SAAS,KAAK,IAChE;EAEF,IAAI,2BAA2B,QAAQ,QAAQ,MAAM,GACnD,OAAO;CAEX;CACA,OAAO;AACT;;;;;;;;;AAUA,SAAS,2BACP,QACA,QACA,QACS;CACT,MAAM,KAAK,eAAe,kBAAkB,OAAO;CACnD,MAAM,CAAC,SAAS,WAAW,OAAO;CAClC,MAAM,CAAC,cAAc,iBAAiB,OAAO;CAC7C,MAAM,OAAO,KAAK,IAAI,GAAG,eAAe,aAAa;CACrD,MAAM,OAAO,KAAK,IAAI,GAAG,gBAAgB,cAAc;CACvD,MAAM,WAAW,KAAK,IAAI,KAAK,IAAI,GAAG,OAAO,GAAG,IAAI;CACpD,MAAM,WAAW,KAAK,IAAI,KAAK,IAAI,GAAG,OAAO,GAAG,IAAI;CAEpD,IAAI,SAAS,KAAK,WAAW,MAC3B,OAAO;CAET,IAAI,SAAS,KAAK,WAAW,GAC3B,OAAO;CAET,IAAI,SAAS,KAAK,WAAW,MAC3B,OAAO;CAET,IAAI,SAAS,KAAK,WAAW,GAC3B,OAAO;CAET,OAAO;AACT"}
|
package/dist/src/WebHostApp.d.ts
CHANGED
|
@@ -2,7 +2,6 @@ import { WebHostTerminalStyle } from "./WebHostTerminalStyle.js";
|
|
|
2
2
|
import { WebHostSceneDescriptor, WebHostSceneManifestSource } from "./WebHostSceneManifest.js";
|
|
3
3
|
import { WebHostSceneBridge, WebHostSceneRuntime, WebHostSceneRuntimeOptions } from "./WebHostSceneRuntime.js";
|
|
4
4
|
import { WebSocketSceneBridgeOptions } from "./WebSocketSceneBridge.js";
|
|
5
|
-
|
|
6
5
|
//#region src/WebHostApp.d.ts
|
|
7
6
|
interface WebHostEmbeddedHostConfig {
|
|
8
7
|
token: string;
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { WebHostTerminalStyle } from "./WebHostTerminalStyle.js";
|
|
2
|
-
import { WebHostFrameDiagnosticRecord, WebHostOutputSink, WebHostRuntimeIssue } from "./WebHostSurfaceTransport.js";
|
|
2
|
+
import { WebHostFocusPresentation, WebHostFrameDiagnosticRecord, WebHostOutputSink, WebHostRuntimeIssue } from "./WebHostSurfaceTransport.js";
|
|
3
3
|
import { WebHostSceneDescriptor } from "./WebHostSceneManifest.js";
|
|
4
|
-
|
|
5
4
|
//#region src/WebHostSceneRuntime.d.ts
|
|
6
5
|
interface WebHostSceneBridge {
|
|
7
6
|
bindOutput(sink: WebHostOutputSink): void;
|
|
@@ -39,6 +38,13 @@ interface WebHostSceneRuntimeOptions {
|
|
|
39
38
|
* mode defaults to `"chain"`.
|
|
40
39
|
*/
|
|
41
40
|
captureWheelInput?: boolean;
|
|
41
|
+
/**
|
|
42
|
+
* Called when the user clicks a hyperlink cell (a click is a pointer-down
|
|
43
|
+
* and pointer-up over the same link target). When unset, `http(s)` targets
|
|
44
|
+
* open in a new tab via `window.open(url, "_blank", "noopener,noreferrer")`
|
|
45
|
+
* and other schemes are ignored. Mirrors the Android host's tap-to-open.
|
|
46
|
+
*/
|
|
47
|
+
onOpenHyperlink?: (url: string) => void;
|
|
42
48
|
}
|
|
43
49
|
type WheelMode = "capture" | "chain" | "passive";
|
|
44
50
|
/**
|
|
@@ -72,6 +78,8 @@ declare class WebHostSceneRuntime {
|
|
|
72
78
|
private cellHeight;
|
|
73
79
|
private activePointerButton;
|
|
74
80
|
private hasCapturedPointer;
|
|
81
|
+
private readonly onOpenHyperlink?;
|
|
82
|
+
private pointerDownLinkTarget?;
|
|
75
83
|
private lastSentResize?;
|
|
76
84
|
private isVisible;
|
|
77
85
|
constructor(options: WebHostSceneRuntimeOptions);
|
|
@@ -86,6 +94,23 @@ declare class WebHostSceneRuntime {
|
|
|
86
94
|
sendInput(chunk: Uint8Array): void;
|
|
87
95
|
dispose(): void;
|
|
88
96
|
private presentSurface;
|
|
97
|
+
/**
|
|
98
|
+
* The current frame's preferred grid size in cells, when the app published
|
|
99
|
+
* one — the measured pre-minimum content size, for embedders negotiating
|
|
100
|
+
* with an outer layout system (the Android host's preferred columns/rows).
|
|
101
|
+
*/
|
|
102
|
+
get preferredGridSize(): {
|
|
103
|
+
width: number;
|
|
104
|
+
height: number;
|
|
105
|
+
} | undefined;
|
|
106
|
+
/**
|
|
107
|
+
* The current frame's settled focus presentation, when the app published
|
|
108
|
+
* one. `prefersTextInput` is what the Android host uses to gate its IME;
|
|
109
|
+
* embedders can drive virtual-keyboard or focus affordances from it.
|
|
110
|
+
*/
|
|
111
|
+
get focusPresentation(): WebHostFocusPresentation | undefined;
|
|
112
|
+
private linkTarget;
|
|
113
|
+
private openHyperlink;
|
|
89
114
|
private applyStyle;
|
|
90
115
|
private applyVisibility;
|
|
91
116
|
private installResizeObserver;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { applyWebHostTerminalStyle, normalizeWebHostTerminalStyle, webTUITerminalBackgroundColor } from "./WebHostTerminalStyle.js";
|
|
2
2
|
import { CanvasSurfacePainter, fontForStyle } from "./CanvasSurfacePainter.js";
|
|
3
3
|
import { InputEventEncoder } from "./InputEventEncoder.js";
|
|
4
|
-
import { cellLocationForEvent, rawCellLocationForEvent, wheelTargetCanScroll } from "./PointerGeometry.js";
|
|
4
|
+
import { cellLocationForEvent, linkTargetAt, rawCellLocationForEvent, wheelTargetCanScroll } from "./PointerGeometry.js";
|
|
5
5
|
import { AccessibilityTreeMounter } from "./AccessibilityTree.js";
|
|
6
6
|
//#region src/WebHostSceneRuntime.ts
|
|
7
7
|
/**
|
|
@@ -45,6 +45,8 @@ var WebHostSceneRuntime = class {
|
|
|
45
45
|
cellHeight = 18;
|
|
46
46
|
activePointerButton = "primary";
|
|
47
47
|
hasCapturedPointer = false;
|
|
48
|
+
onOpenHyperlink;
|
|
49
|
+
pointerDownLinkTarget;
|
|
48
50
|
lastSentResize;
|
|
49
51
|
isVisible = false;
|
|
50
52
|
constructor(options) {
|
|
@@ -55,6 +57,7 @@ var WebHostSceneRuntime = class {
|
|
|
55
57
|
this.onFrameDiagnostic = options.onFrameDiagnostic;
|
|
56
58
|
this.synchronizeAccessibilityFocus = options.synchronizeAccessibilityFocus ?? true;
|
|
57
59
|
this.wheelMode = options.wheelMode ?? legacyWheelMode(options.captureWheelInput);
|
|
60
|
+
this.onOpenHyperlink = options.onOpenHyperlink;
|
|
58
61
|
this.element = document.createElement("section");
|
|
59
62
|
this.element.className = "webhost-scene";
|
|
60
63
|
this.element.dataset.sceneId = options.descriptor.id;
|
|
@@ -157,6 +160,38 @@ var WebHostSceneRuntime = class {
|
|
|
157
160
|
this.draw(previousFrame && !resized ? frame.damage : void 0);
|
|
158
161
|
this.syncAccessibilityTree();
|
|
159
162
|
}
|
|
163
|
+
/**
|
|
164
|
+
* The current frame's preferred grid size in cells, when the app published
|
|
165
|
+
* one — the measured pre-minimum content size, for embedders negotiating
|
|
166
|
+
* with an outer layout system (the Android host's preferred columns/rows).
|
|
167
|
+
*/
|
|
168
|
+
get preferredGridSize() {
|
|
169
|
+
const frame = this.currentFrame;
|
|
170
|
+
if (frame?.preferredGridWidth === void 0 || frame.preferredGridHeight === void 0) return;
|
|
171
|
+
return {
|
|
172
|
+
width: frame.preferredGridWidth,
|
|
173
|
+
height: frame.preferredGridHeight
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* The current frame's settled focus presentation, when the app published
|
|
178
|
+
* one. `prefersTextInput` is what the Android host uses to gate its IME;
|
|
179
|
+
* embedders can drive virtual-keyboard or focus affordances from it.
|
|
180
|
+
*/
|
|
181
|
+
get focusPresentation() {
|
|
182
|
+
return this.currentFrame?.focusPresentation;
|
|
183
|
+
}
|
|
184
|
+
linkTarget(location) {
|
|
185
|
+
return linkTargetAt(this.currentFrame?.links, this.currentFrame?.linkTargets, location);
|
|
186
|
+
}
|
|
187
|
+
openHyperlink(url) {
|
|
188
|
+
if (this.onOpenHyperlink) {
|
|
189
|
+
this.onOpenHyperlink(url);
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
if (!/^https?:/i.test(url)) return;
|
|
193
|
+
window.open(url, "_blank", "noopener,noreferrer");
|
|
194
|
+
}
|
|
160
195
|
applyStyle(style) {
|
|
161
196
|
applyWebHostTerminalStyle(this.element, style);
|
|
162
197
|
this.element.style.padding = "0.75rem";
|
|
@@ -208,6 +243,7 @@ var WebHostSceneRuntime = class {
|
|
|
208
243
|
const button = this.inputEncoder.pointerButton(event.button);
|
|
209
244
|
this.activePointerButton = button;
|
|
210
245
|
this.hasCapturedPointer = true;
|
|
246
|
+
this.pointerDownLinkTarget = button === "primary" ? this.linkTarget(location) : void 0;
|
|
211
247
|
this.terminalMount.focus?.({ preventScroll: true });
|
|
212
248
|
this.terminalMount.setPointerCapture?.(event.pointerId);
|
|
213
249
|
this.onInput(this.inputEncoder.encodePointerDown(location, button, event));
|
|
@@ -217,14 +253,18 @@ var WebHostSceneRuntime = class {
|
|
|
217
253
|
const location = this.hasCapturedPointer ? this.rawCellLocation(event) : this.cellLocation(event);
|
|
218
254
|
this.terminalMount.releasePointerCapture?.(event.pointerId);
|
|
219
255
|
this.hasCapturedPointer = false;
|
|
256
|
+
const downLinkTarget = this.pointerDownLinkTarget;
|
|
257
|
+
this.pointerDownLinkTarget = void 0;
|
|
220
258
|
if (!location) return;
|
|
221
259
|
const button = this.inputEncoder.pointerButton(event.button) ?? this.activePointerButton;
|
|
222
260
|
this.onInput(this.inputEncoder.encodePointerUp(location, button, event));
|
|
261
|
+
if (downLinkTarget !== void 0 && this.linkTarget(location) === downLinkTarget) this.openHyperlink(downLinkTarget);
|
|
223
262
|
event.preventDefault();
|
|
224
263
|
};
|
|
225
264
|
const handlePointerMove = (event) => {
|
|
226
265
|
const location = event.buttons && this.hasCapturedPointer ? this.rawCellLocation(event) : this.cellLocation(event);
|
|
227
266
|
if (!location) return;
|
|
267
|
+
if (!this.hasCapturedPointer) this.terminalMount.style.cursor = this.linkTarget(location) !== void 0 ? "pointer" : "";
|
|
228
268
|
this.onInput(this.inputEncoder.encodePointerMove(location, this.activePointerButton, event));
|
|
229
269
|
};
|
|
230
270
|
const handleWheel = (event) => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WebHostSceneRuntime.js","names":[],"sources":["../../src/WebHostSceneRuntime.ts"],"sourcesContent":["import {\n applyWebHostTerminalStyle,\n normalizeWebHostTerminalStyle,\n type ResolvedWebHostTerminalStyle,\n type WebHostTerminalStyle,\n webTUITerminalBackgroundColor,\n} from \"./WebHostTerminalStyle.ts\";\nimport {\n CanvasSurfacePainter,\n fontForStyle,\n type CanvasSurfaceMetrics,\n} from \"./CanvasSurfacePainter.ts\";\nimport {\n InputEventEncoder,\n type CellLocation,\n type PointerButton,\n} from \"./InputEventEncoder.ts\";\nimport {\n cellLocationForEvent,\n rawCellLocationForEvent,\n wheelTargetCanScroll,\n type PointerGeometryMetrics,\n} from \"./PointerGeometry.ts\";\nimport { AccessibilityTreeMounter } from \"./AccessibilityTree.ts\";\nimport {\n type WebHostFrameDiagnosticRecord,\n type WebHostOutputSink,\n type WebHostRuntimeIssue,\n type WebHostSurfaceDamage,\n type WebHostSurfaceFrame,\n} from \"./WebHostSurfaceTransport.ts\";\nimport type { WebHostSceneDescriptor } from \"./WebHostSceneManifest.ts\";\n\nexport interface WebHostSceneBridge {\n bindOutput(sink: WebHostOutputSink): void;\n resize(columns: number, rows: number, cellWidth?: number, cellHeight?: number): void;\n updateRenderStyle(style: WebHostTerminalStyle): void;\n sendInput(chunk: Uint8Array): void;\n dispose(): void;\n}\n\nexport interface WebHostSceneRuntimeOptions {\n mount: HTMLElement;\n descriptor: WebHostSceneDescriptor;\n style: WebHostTerminalStyle;\n bridge?: WebHostSceneBridge;\n onInput(chunk: Uint8Array): void;\n onFrameDiagnostic?: (diagnostic: WebHostFrameDiagnosticRecord) => void;\n synchronizeAccessibilityFocus?: boolean;\n /**\n * How the embedded view treats mouse-wheel input.\n * - `\"chain\"` (default): forward the wheel only while a scrollable region\n * under the pointer can still scroll in that direction; otherwise let it\n * fall through so the page (or parent iframe) scrolls — iframe-like nested\n * scrolling. A scene with no `ScrollView` never traps the wheel. Uses the\n * `scrollRegions` the app publishes in its frames.\n * - `\"capture\"`: always forward the wheel to the app while the pointer is over\n * the surface (and `preventDefault` page scroll). Best for full-screen apps\n * where there is no page to scroll past.\n * - `\"passive\"`: never capture; the page always scrolls.\n *\n * Takes precedence over the legacy `captureWheelInput` flag.\n */\n wheelMode?: WheelMode;\n /**\n * Legacy boolean wheel gate. `true` → `\"capture\"`, `false` → `\"passive\"`.\n * Prefer `wheelMode`. Ignored when `wheelMode` is set. When neither is set the\n * mode defaults to `\"chain\"`.\n */\n captureWheelInput?: boolean;\n}\n\nexport type WheelMode = \"capture\" | \"chain\" | \"passive\";\n\n/**\n * Resolves the legacy `captureWheelInput` flag to a {@link WheelMode}. When the\n * flag is unset the mode defaults to `\"chain\"`, so embeds never trap a visitor\n * who is merely scrolling past the view; `true` maps to `\"capture\"` and `false`\n * to `\"passive\"` to preserve the old boolean behavior.\n */\nfunction legacyWheelMode(captureWheelInput: boolean | undefined): WheelMode {\n if (captureWheelInput === undefined) {\n return \"chain\";\n }\n return captureWheelInput ? \"capture\" : \"passive\";\n}\n\n/**\n * Coordinates a single SwiftTUI scene's browser presentation: it owns the DOM\n * mount, canvas, accessibility tree, and bridge wiring, and delegates the heavy\n * responsibilities to focused collaborators — {@link CanvasSurfacePainter} for\n * canvas drawing, {@link InputEventEncoder} for wire-message encoding, and the\n * {@link PointerGeometry} helpers for pixel→cell hit-testing and wheel chaining.\n */\nexport class WebHostSceneRuntime {\n readonly descriptor: WebHostSceneDescriptor;\n readonly element: HTMLElement;\n readonly terminalMount: HTMLElement;\n\n private readonly bridge?: WebHostSceneBridge;\n private readonly onInput: (chunk: Uint8Array) => void;\n private readonly onFrameDiagnostic?: (diagnostic: WebHostFrameDiagnosticRecord) => void;\n private readonly synchronizeAccessibilityFocus: boolean;\n private readonly wheelMode: WheelMode;\n private readonly painter = new CanvasSurfacePainter();\n private readonly inputEncoder = new InputEventEncoder();\n private currentStyle: ResolvedWebHostTerminalStyle;\n private canvas?: HTMLCanvasElement;\n private accessibilityTree?: AccessibilityTreeMounter;\n private diagnosticText?: HTMLElement;\n private resizeObserver?: ResizeObserver;\n private detachInputHandlers?: () => void;\n private currentFrame?: WebHostSurfaceFrame;\n private columns = 80;\n private rows = 24;\n private cellWidth = 8;\n private cellHeight = 18;\n private activePointerButton: PointerButton = \"primary\";\n private hasCapturedPointer = false;\n private lastSentResize?: {\n columns: number;\n rows: number;\n cellWidth: number;\n cellHeight: number;\n };\n private isVisible = false;\n\n constructor(options: WebHostSceneRuntimeOptions) {\n this.descriptor = options.descriptor;\n this.currentStyle = normalizeWebHostTerminalStyle(options.style);\n this.bridge = options.bridge;\n this.onInput = options.onInput;\n this.onFrameDiagnostic = options.onFrameDiagnostic;\n this.synchronizeAccessibilityFocus = options.synchronizeAccessibilityFocus ?? true;\n this.wheelMode = options.wheelMode ?? legacyWheelMode(options.captureWheelInput);\n this.element = document.createElement(\"section\");\n this.element.className = \"webhost-scene\";\n this.element.dataset.sceneId = options.descriptor.id;\n this.element.hidden = true;\n\n const header = document.createElement(\"div\");\n header.className = \"webhost-scene__header\";\n header.textContent = options.descriptor.title ?? options.descriptor.id;\n\n this.terminalMount = document.createElement(\"div\");\n this.terminalMount.className = \"webhost-scene__terminal\";\n this.terminalMount.tabIndex = 0;\n\n this.element.append(header, this.terminalMount);\n options.mount.appendChild(this.element);\n this.applyVisibility();\n }\n\n async mount(): Promise<void> {\n if (this.canvas) {\n return;\n }\n\n const canvas = document.createElement(\"canvas\");\n canvas.className = \"webhost-scene__surface\";\n canvas.setAttribute(\"aria-hidden\", \"true\");\n this.canvas = canvas;\n this.painter.attach(canvas, () => this.draw());\n this.accessibilityTree = new AccessibilityTreeMounter();\n this.terminalMount.replaceChildren(\n canvas,\n this.accessibilityTree.element,\n this.accessibilityTree.announcerElement\n );\n this.installInputHandlers();\n this.installResizeObserver();\n\n this.bridge?.bindOutput({\n presentSurface: (frame) => this.presentSurface(frame),\n writeClipboard: (text) => this.writeClipboard(text),\n notifyRuntimeIssue: (issue) => this.notifyRuntimeIssue(issue),\n recordFrameDiagnostic: (diagnostic) => this.recordFrameDiagnostic(diagnostic),\n writeOutput: (text) => this.writeOutput(text),\n writeError: (text) => this.writeOutput(text),\n });\n\n this.applyStyle(this.currentStyle);\n this.measureCells();\n this.resizeToMount();\n this.draw();\n this.syncAccessibilityTree();\n }\n\n setVisible(\n visible: boolean\n ): void {\n this.isVisible = visible;\n this.applyVisibility();\n if (visible) {\n this.resizeToMount();\n if (this.synchronizeAccessibilityFocus) {\n this.terminalMount.focus?.({ preventScroll: true });\n }\n }\n }\n\n setStyle(\n style: WebHostTerminalStyle\n ): void {\n this.currentStyle = normalizeWebHostTerminalStyle(style);\n this.applyStyle(this.currentStyle);\n this.bridge?.updateRenderStyle(this.currentStyle);\n this.measureCells();\n this.resizeToMount();\n this.draw();\n this.syncAccessibilityTree();\n }\n\n resize(\n columns: number,\n rows: number\n ): void {\n this.columns = Math.max(1, Math.round(columns));\n this.rows = Math.max(1, Math.round(rows));\n this.resizeCanvas();\n this.draw();\n this.syncAccessibilityTree();\n }\n\n writeOutput(\n text: string\n ): void {\n if (!this.diagnosticText) {\n const diagnosticText = document.createElement(\"pre\");\n diagnosticText.className = \"webhost-scene__diagnostic\";\n this.diagnosticText = diagnosticText;\n this.terminalMount.appendChild(diagnosticText);\n }\n this.diagnosticText.textContent = `${this.diagnosticText.textContent ?? \"\"}${text}`;\n }\n\n notifyRuntimeIssue(\n issue: WebHostRuntimeIssue\n ): void {\n console.log(issue.description);\n }\n\n private recordFrameDiagnostic(\n diagnostic: WebHostFrameDiagnosticRecord\n ): void {\n this.onFrameDiagnostic?.(diagnostic);\n }\n\n async writeClipboard(\n text: string\n ): Promise<void> {\n const clipboard = globalThis.navigator?.clipboard;\n if (!clipboard?.writeText) {\n return;\n }\n\n try {\n await clipboard.writeText(text);\n } catch {\n // Clipboard permissions are browser/user-gesture dependent; hosts treat\n // rejection as a best-effort no-op rather than surfacing diagnostics.\n }\n }\n\n sendInput(\n chunk: Uint8Array\n ): void {\n this.onInput(chunk);\n }\n\n dispose(): void {\n this.detachInputHandlers?.();\n this.resizeObserver?.disconnect();\n this.element.remove();\n }\n\n private presentSurface(\n frame: WebHostSurfaceFrame\n ): void {\n const previousFrame = this.currentFrame;\n this.currentFrame = frame;\n this.columns = Math.max(1, Math.round(frame.width));\n this.rows = Math.max(1, Math.round(frame.height));\n const resized = this.resizeCanvas();\n this.draw(previousFrame && !resized ? frame.damage : undefined);\n this.syncAccessibilityTree();\n }\n\n private applyStyle(\n style: WebHostTerminalStyle\n ): void {\n applyWebHostTerminalStyle(this.element, style);\n this.element.style.padding = \"0.75rem\";\n this.element.style.borderRadius = \"16px\";\n this.element.style.boxShadow = \"0 20px 50px rgba(0, 0, 0, 0.28)\";\n this.element.style.overflow = \"hidden\";\n this.element.style.gap = \"0.5rem\";\n this.element.style.gridTemplateRows = \"auto 1fr\";\n\n this.terminalMount.style.position = \"relative\";\n this.terminalMount.style.overflow = \"hidden\";\n // Keep a captured wheel from rubber-banding/chaining the page; the wheel\n // capture vs. fall-through decision lives in handleWheel.\n this.terminalMount.style.overscrollBehavior = \"contain\";\n this.terminalMount.style.outline = \"none\";\n this.terminalMount.style.background = webTUITerminalBackgroundColor(this.currentStyle);\n this.terminalMount.style.minHeight = `${this.cellHeight * 8}px`;\n\n if (this.canvas) {\n this.canvas.style.display = \"block\";\n this.canvas.style.width = \"100%\";\n this.canvas.style.height = \"100%\";\n }\n }\n\n private applyVisibility(): void {\n this.element.hidden = !this.isVisible;\n this.element.style.setProperty(\n \"display\",\n this.isVisible ? \"grid\" : \"none\",\n \"important\"\n );\n }\n\n private installResizeObserver(): void {\n if (typeof ResizeObserver === \"undefined\") {\n return;\n }\n\n this.resizeObserver = new ResizeObserver(() => {\n this.resizeToMount();\n });\n this.resizeObserver.observe(this.terminalMount);\n }\n\n private installInputHandlers(): void {\n const handleKeyDown = (event: KeyboardEvent) => {\n if (event.metaKey || event.isComposing) {\n return;\n }\n const message = this.inputEncoder.encodeKey(event);\n if (!message) {\n return;\n }\n\n this.onInput(message);\n event.preventDefault();\n };\n\n const handlePaste = (event: ClipboardEvent) => {\n const text = event.clipboardData?.getData(\"text/plain\") ?? \"\";\n if (!text) {\n return;\n }\n this.onInput(this.inputEncoder.encodePaste(text));\n event.preventDefault();\n };\n\n const handlePointerDown = (event: PointerEvent) => {\n const location = this.cellLocation(event);\n if (!location) {\n return;\n }\n\n const button = this.inputEncoder.pointerButton(event.button);\n this.activePointerButton = button;\n this.hasCapturedPointer = true;\n this.terminalMount.focus?.({ preventScroll: true });\n this.terminalMount.setPointerCapture?.(event.pointerId);\n this.onInput(this.inputEncoder.encodePointerDown(location, button, event));\n event.preventDefault();\n };\n\n const handlePointerUp = (event: PointerEvent) => {\n const location = this.hasCapturedPointer\n ? this.rawCellLocation(event)\n : this.cellLocation(event);\n this.terminalMount.releasePointerCapture?.(event.pointerId);\n this.hasCapturedPointer = false;\n if (!location) {\n return;\n }\n\n const button = this.inputEncoder.pointerButton(event.button) ?? this.activePointerButton;\n this.onInput(this.inputEncoder.encodePointerUp(location, button, event));\n event.preventDefault();\n };\n\n const handlePointerMove = (event: PointerEvent) => {\n const location = event.buttons && this.hasCapturedPointer\n ? this.rawCellLocation(event)\n : this.cellLocation(event);\n if (!location) {\n return;\n }\n\n this.onInput(this.inputEncoder.encodePointerMove(location, this.activePointerButton, event));\n };\n\n const handleWheel = (event: WheelEvent) => {\n if (this.wheelMode === \"passive\") {\n return;\n }\n\n const location = this.cellLocation(event);\n if (!location) {\n // Pointer is outside the cell grid (sub-cell margin / gutter). Don't\n // capture — let the wheel fall through to the page.\n return;\n }\n\n // In \"chain\" mode, capture only while a scrollable region under the\n // pointer can still move in this direction; otherwise let the wheel fall\n // through so the page (or parent iframe) scrolls — iframe-like behavior.\n // \"capture\" mode always forwards while over the surface (legacy).\n if (this.wheelMode === \"chain\"\n && !wheelTargetCanScroll(this.currentFrame?.scrollRegions, location, event.deltaX, event.deltaY)) {\n return;\n }\n\n this.onInput(this.inputEncoder.encodeWheel(location, event));\n event.preventDefault();\n };\n\n this.terminalMount.addEventListener(\"keydown\", handleKeyDown);\n this.terminalMount.addEventListener(\"paste\", handlePaste);\n this.terminalMount.addEventListener(\"pointerdown\", handlePointerDown);\n this.terminalMount.addEventListener(\"pointerup\", handlePointerUp);\n this.terminalMount.addEventListener(\"pointermove\", handlePointerMove);\n this.terminalMount.addEventListener(\"wheel\", handleWheel, { passive: false });\n\n this.detachInputHandlers = () => {\n this.terminalMount.removeEventListener(\"keydown\", handleKeyDown);\n this.terminalMount.removeEventListener(\"paste\", handlePaste);\n this.terminalMount.removeEventListener(\"pointerdown\", handlePointerDown);\n this.terminalMount.removeEventListener(\"pointerup\", handlePointerUp);\n this.terminalMount.removeEventListener(\"pointermove\", handlePointerMove);\n this.terminalMount.removeEventListener(\"wheel\", handleWheel);\n };\n }\n\n private resizeToMount(): void {\n this.measureCells();\n const rect = this.terminalMount.getBoundingClientRect?.();\n const width = rect?.width && rect.width > 0 ? rect.width : this.columns * this.cellWidth;\n const height = rect?.height && rect.height > 0 ? rect.height : this.rows * this.cellHeight;\n const nextColumns = Math.max(1, Math.floor(width / this.cellWidth));\n const nextRows = Math.max(1, Math.floor(height / this.cellHeight));\n\n this.columns = nextColumns;\n this.rows = nextRows;\n this.sendResizeIfNeeded();\n this.resizeCanvas();\n }\n\n private sendResizeIfNeeded(): void {\n const current = {\n columns: this.columns,\n rows: this.rows,\n cellWidth: this.cellWidth,\n cellHeight: this.cellHeight,\n };\n if (this.lastSentResize\n && this.lastSentResize.columns === current.columns\n && this.lastSentResize.rows === current.rows\n && this.lastSentResize.cellWidth === current.cellWidth\n && this.lastSentResize.cellHeight === current.cellHeight\n ) {\n return;\n }\n\n this.lastSentResize = current;\n this.bridge?.resize(current.columns, current.rows, current.cellWidth, current.cellHeight);\n }\n\n private resizeCanvas(): boolean {\n if (!this.canvas) {\n return false;\n }\n\n const cssWidth = Math.max(1, this.columns * this.cellWidth);\n const cssHeight = Math.max(1, this.rows * this.cellHeight);\n const scale = globalThis.window?.devicePixelRatio || 1;\n const width = Math.ceil(cssWidth * scale);\n const height = Math.ceil(cssHeight * scale);\n const styleWidth = `${cssWidth}px`;\n const styleHeight = `${cssHeight}px`;\n if (this.canvas.width === width\n && this.canvas.height === height\n && this.canvas.style.width === styleWidth\n && this.canvas.style.height === styleHeight\n ) {\n return false;\n }\n\n this.canvas.width = width;\n this.canvas.height = height;\n this.canvas.style.width = styleWidth;\n this.canvas.style.height = styleHeight;\n return true;\n }\n\n private measureCells(): void {\n const canvas = this.canvas ?? document.createElement(\"canvas\");\n const context = canvas.getContext?.(\"2d\");\n if (!context) {\n this.cellWidth = Math.max(1, Math.round(this.currentStyle.fontSize * 0.62));\n this.cellHeight = Math.max(1, Math.round(this.currentStyle.fontSize * 1.35));\n return;\n }\n\n context.font = fontForStyle(this.currentStyle);\n this.cellWidth = Math.max(1, Math.ceil(context.measureText(\"W\").width));\n this.cellHeight = Math.max(1, Math.ceil(this.currentStyle.fontSize * 1.35));\n }\n\n private draw(\n damage?: WebHostSurfaceDamage\n ): void {\n this.painter.paint(this.surfaceMetrics(), this.currentFrame, damage);\n }\n\n private syncAccessibilityTree(): void {\n const tree = this.accessibilityTree;\n if (!tree || !this.currentFrame) {\n return;\n }\n\n tree.present(this.currentFrame.accessibilityTree ?? [], {\n cellWidth: this.cellWidth,\n cellHeight: this.cellHeight,\n }, this.currentFrame.accessibilityAnnouncements ?? [], {\n synchronizeFocus: this.synchronizeAccessibilityFocus,\n });\n }\n\n private surfaceMetrics(): CanvasSurfaceMetrics {\n return {\n columns: this.columns,\n rows: this.rows,\n cellWidth: this.cellWidth,\n cellHeight: this.cellHeight,\n style: this.currentStyle,\n };\n }\n\n private pointerMetrics(): PointerGeometryMetrics {\n return {\n rect: this.canvas?.getBoundingClientRect?.() ?? this.terminalMount.getBoundingClientRect?.(),\n cellWidth: this.cellWidth,\n cellHeight: this.cellHeight,\n columns: this.columns,\n rows: this.rows,\n };\n }\n\n private cellLocation(\n event: MouseEvent\n ): CellLocation | undefined {\n return cellLocationForEvent(event, this.pointerMetrics());\n }\n\n private rawCellLocation(\n event: MouseEvent\n ): CellLocation | undefined {\n return rawCellLocationForEvent(event, this.pointerMetrics());\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"WebHostSceneRuntime.js","names":[],"sources":["../../src/WebHostSceneRuntime.ts"],"sourcesContent":["import {\n applyWebHostTerminalStyle,\n normalizeWebHostTerminalStyle,\n type ResolvedWebHostTerminalStyle,\n type WebHostTerminalStyle,\n webTUITerminalBackgroundColor,\n} from \"./WebHostTerminalStyle.ts\";\nimport {\n CanvasSurfacePainter,\n fontForStyle,\n type CanvasSurfaceMetrics,\n} from \"./CanvasSurfacePainter.ts\";\nimport {\n InputEventEncoder,\n type CellLocation,\n type PointerButton,\n} from \"./InputEventEncoder.ts\";\nimport {\n cellLocationForEvent,\n linkTargetAt,\n rawCellLocationForEvent,\n wheelTargetCanScroll,\n type PointerGeometryMetrics,\n} from \"./PointerGeometry.ts\";\nimport { AccessibilityTreeMounter } from \"./AccessibilityTree.ts\";\nimport {\n type WebHostFocusPresentation,\n type WebHostFrameDiagnosticRecord,\n type WebHostOutputSink,\n type WebHostRuntimeIssue,\n type WebHostSurfaceDamage,\n type WebHostSurfaceFrame,\n} from \"./WebHostSurfaceTransport.ts\";\nimport type { WebHostSceneDescriptor } from \"./WebHostSceneManifest.ts\";\n\nexport interface WebHostSceneBridge {\n bindOutput(sink: WebHostOutputSink): void;\n resize(columns: number, rows: number, cellWidth?: number, cellHeight?: number): void;\n updateRenderStyle(style: WebHostTerminalStyle): void;\n sendInput(chunk: Uint8Array): void;\n dispose(): void;\n}\n\nexport interface WebHostSceneRuntimeOptions {\n mount: HTMLElement;\n descriptor: WebHostSceneDescriptor;\n style: WebHostTerminalStyle;\n bridge?: WebHostSceneBridge;\n onInput(chunk: Uint8Array): void;\n onFrameDiagnostic?: (diagnostic: WebHostFrameDiagnosticRecord) => void;\n synchronizeAccessibilityFocus?: boolean;\n /**\n * How the embedded view treats mouse-wheel input.\n * - `\"chain\"` (default): forward the wheel only while a scrollable region\n * under the pointer can still scroll in that direction; otherwise let it\n * fall through so the page (or parent iframe) scrolls — iframe-like nested\n * scrolling. A scene with no `ScrollView` never traps the wheel. Uses the\n * `scrollRegions` the app publishes in its frames.\n * - `\"capture\"`: always forward the wheel to the app while the pointer is over\n * the surface (and `preventDefault` page scroll). Best for full-screen apps\n * where there is no page to scroll past.\n * - `\"passive\"`: never capture; the page always scrolls.\n *\n * Takes precedence over the legacy `captureWheelInput` flag.\n */\n wheelMode?: WheelMode;\n /**\n * Legacy boolean wheel gate. `true` → `\"capture\"`, `false` → `\"passive\"`.\n * Prefer `wheelMode`. Ignored when `wheelMode` is set. When neither is set the\n * mode defaults to `\"chain\"`.\n */\n captureWheelInput?: boolean;\n /**\n * Called when the user clicks a hyperlink cell (a click is a pointer-down\n * and pointer-up over the same link target). When unset, `http(s)` targets\n * open in a new tab via `window.open(url, \"_blank\", \"noopener,noreferrer\")`\n * and other schemes are ignored. Mirrors the Android host's tap-to-open.\n */\n onOpenHyperlink?: (url: string) => void;\n}\n\nexport type WheelMode = \"capture\" | \"chain\" | \"passive\";\n\n/**\n * Resolves the legacy `captureWheelInput` flag to a {@link WheelMode}. When the\n * flag is unset the mode defaults to `\"chain\"`, so embeds never trap a visitor\n * who is merely scrolling past the view; `true` maps to `\"capture\"` and `false`\n * to `\"passive\"` to preserve the old boolean behavior.\n */\nfunction legacyWheelMode(captureWheelInput: boolean | undefined): WheelMode {\n if (captureWheelInput === undefined) {\n return \"chain\";\n }\n return captureWheelInput ? \"capture\" : \"passive\";\n}\n\n/**\n * Coordinates a single SwiftTUI scene's browser presentation: it owns the DOM\n * mount, canvas, accessibility tree, and bridge wiring, and delegates the heavy\n * responsibilities to focused collaborators — {@link CanvasSurfacePainter} for\n * canvas drawing, {@link InputEventEncoder} for wire-message encoding, and the\n * {@link PointerGeometry} helpers for pixel→cell hit-testing and wheel chaining.\n */\nexport class WebHostSceneRuntime {\n readonly descriptor: WebHostSceneDescriptor;\n readonly element: HTMLElement;\n readonly terminalMount: HTMLElement;\n\n private readonly bridge?: WebHostSceneBridge;\n private readonly onInput: (chunk: Uint8Array) => void;\n private readonly onFrameDiagnostic?: (diagnostic: WebHostFrameDiagnosticRecord) => void;\n private readonly synchronizeAccessibilityFocus: boolean;\n private readonly wheelMode: WheelMode;\n private readonly painter = new CanvasSurfacePainter();\n private readonly inputEncoder = new InputEventEncoder();\n private currentStyle: ResolvedWebHostTerminalStyle;\n private canvas?: HTMLCanvasElement;\n private accessibilityTree?: AccessibilityTreeMounter;\n private diagnosticText?: HTMLElement;\n private resizeObserver?: ResizeObserver;\n private detachInputHandlers?: () => void;\n private currentFrame?: WebHostSurfaceFrame;\n private columns = 80;\n private rows = 24;\n private cellWidth = 8;\n private cellHeight = 18;\n private activePointerButton: PointerButton = \"primary\";\n private hasCapturedPointer = false;\n private readonly onOpenHyperlink?: (url: string) => void;\n private pointerDownLinkTarget?: string;\n private lastSentResize?: {\n columns: number;\n rows: number;\n cellWidth: number;\n cellHeight: number;\n };\n private isVisible = false;\n\n constructor(options: WebHostSceneRuntimeOptions) {\n this.descriptor = options.descriptor;\n this.currentStyle = normalizeWebHostTerminalStyle(options.style);\n this.bridge = options.bridge;\n this.onInput = options.onInput;\n this.onFrameDiagnostic = options.onFrameDiagnostic;\n this.synchronizeAccessibilityFocus = options.synchronizeAccessibilityFocus ?? true;\n this.wheelMode = options.wheelMode ?? legacyWheelMode(options.captureWheelInput);\n this.onOpenHyperlink = options.onOpenHyperlink;\n this.element = document.createElement(\"section\");\n this.element.className = \"webhost-scene\";\n this.element.dataset.sceneId = options.descriptor.id;\n this.element.hidden = true;\n\n const header = document.createElement(\"div\");\n header.className = \"webhost-scene__header\";\n header.textContent = options.descriptor.title ?? options.descriptor.id;\n\n this.terminalMount = document.createElement(\"div\");\n this.terminalMount.className = \"webhost-scene__terminal\";\n this.terminalMount.tabIndex = 0;\n\n this.element.append(header, this.terminalMount);\n options.mount.appendChild(this.element);\n this.applyVisibility();\n }\n\n async mount(): Promise<void> {\n if (this.canvas) {\n return;\n }\n\n const canvas = document.createElement(\"canvas\");\n canvas.className = \"webhost-scene__surface\";\n canvas.setAttribute(\"aria-hidden\", \"true\");\n this.canvas = canvas;\n this.painter.attach(canvas, () => this.draw());\n this.accessibilityTree = new AccessibilityTreeMounter();\n this.terminalMount.replaceChildren(\n canvas,\n this.accessibilityTree.element,\n this.accessibilityTree.announcerElement\n );\n this.installInputHandlers();\n this.installResizeObserver();\n\n this.bridge?.bindOutput({\n presentSurface: (frame) => this.presentSurface(frame),\n writeClipboard: (text) => this.writeClipboard(text),\n notifyRuntimeIssue: (issue) => this.notifyRuntimeIssue(issue),\n recordFrameDiagnostic: (diagnostic) => this.recordFrameDiagnostic(diagnostic),\n writeOutput: (text) => this.writeOutput(text),\n writeError: (text) => this.writeOutput(text),\n });\n\n this.applyStyle(this.currentStyle);\n this.measureCells();\n this.resizeToMount();\n this.draw();\n this.syncAccessibilityTree();\n }\n\n setVisible(\n visible: boolean\n ): void {\n this.isVisible = visible;\n this.applyVisibility();\n if (visible) {\n this.resizeToMount();\n if (this.synchronizeAccessibilityFocus) {\n this.terminalMount.focus?.({ preventScroll: true });\n }\n }\n }\n\n setStyle(\n style: WebHostTerminalStyle\n ): void {\n this.currentStyle = normalizeWebHostTerminalStyle(style);\n this.applyStyle(this.currentStyle);\n this.bridge?.updateRenderStyle(this.currentStyle);\n this.measureCells();\n this.resizeToMount();\n this.draw();\n this.syncAccessibilityTree();\n }\n\n resize(\n columns: number,\n rows: number\n ): void {\n this.columns = Math.max(1, Math.round(columns));\n this.rows = Math.max(1, Math.round(rows));\n this.resizeCanvas();\n this.draw();\n this.syncAccessibilityTree();\n }\n\n writeOutput(\n text: string\n ): void {\n if (!this.diagnosticText) {\n const diagnosticText = document.createElement(\"pre\");\n diagnosticText.className = \"webhost-scene__diagnostic\";\n this.diagnosticText = diagnosticText;\n this.terminalMount.appendChild(diagnosticText);\n }\n this.diagnosticText.textContent = `${this.diagnosticText.textContent ?? \"\"}${text}`;\n }\n\n notifyRuntimeIssue(\n issue: WebHostRuntimeIssue\n ): void {\n console.log(issue.description);\n }\n\n private recordFrameDiagnostic(\n diagnostic: WebHostFrameDiagnosticRecord\n ): void {\n this.onFrameDiagnostic?.(diagnostic);\n }\n\n async writeClipboard(\n text: string\n ): Promise<void> {\n const clipboard = globalThis.navigator?.clipboard;\n if (!clipboard?.writeText) {\n return;\n }\n\n try {\n await clipboard.writeText(text);\n } catch {\n // Clipboard permissions are browser/user-gesture dependent; hosts treat\n // rejection as a best-effort no-op rather than surfacing diagnostics.\n }\n }\n\n sendInput(\n chunk: Uint8Array\n ): void {\n this.onInput(chunk);\n }\n\n dispose(): void {\n this.detachInputHandlers?.();\n this.resizeObserver?.disconnect();\n this.element.remove();\n }\n\n private presentSurface(\n frame: WebHostSurfaceFrame\n ): void {\n const previousFrame = this.currentFrame;\n this.currentFrame = frame;\n this.columns = Math.max(1, Math.round(frame.width));\n this.rows = Math.max(1, Math.round(frame.height));\n const resized = this.resizeCanvas();\n this.draw(previousFrame && !resized ? frame.damage : undefined);\n this.syncAccessibilityTree();\n }\n\n /**\n * The current frame's preferred grid size in cells, when the app published\n * one — the measured pre-minimum content size, for embedders negotiating\n * with an outer layout system (the Android host's preferred columns/rows).\n */\n get preferredGridSize(): { width: number; height: number } | undefined {\n const frame = this.currentFrame;\n if (frame?.preferredGridWidth === undefined || frame.preferredGridHeight === undefined) {\n return undefined;\n }\n return { width: frame.preferredGridWidth, height: frame.preferredGridHeight };\n }\n\n /**\n * The current frame's settled focus presentation, when the app published\n * one. `prefersTextInput` is what the Android host uses to gate its IME;\n * embedders can drive virtual-keyboard or focus affordances from it.\n */\n get focusPresentation(): WebHostFocusPresentation | undefined {\n return this.currentFrame?.focusPresentation;\n }\n\n private linkTarget(\n location: CellLocation\n ): string | undefined {\n return linkTargetAt(\n this.currentFrame?.links,\n this.currentFrame?.linkTargets,\n location\n );\n }\n\n private openHyperlink(\n url: string\n ): void {\n if (this.onOpenHyperlink) {\n this.onOpenHyperlink(url);\n return;\n }\n // Defense in depth on top of the app-side OSC-8 destination sanitization:\n // the default handler only opens web schemes.\n if (!/^https?:/i.test(url)) {\n return;\n }\n window.open(url, \"_blank\", \"noopener,noreferrer\");\n }\n\n private applyStyle(\n style: WebHostTerminalStyle\n ): void {\n applyWebHostTerminalStyle(this.element, style);\n this.element.style.padding = \"0.75rem\";\n this.element.style.borderRadius = \"16px\";\n this.element.style.boxShadow = \"0 20px 50px rgba(0, 0, 0, 0.28)\";\n this.element.style.overflow = \"hidden\";\n this.element.style.gap = \"0.5rem\";\n this.element.style.gridTemplateRows = \"auto 1fr\";\n\n this.terminalMount.style.position = \"relative\";\n this.terminalMount.style.overflow = \"hidden\";\n // Keep a captured wheel from rubber-banding/chaining the page; the wheel\n // capture vs. fall-through decision lives in handleWheel.\n this.terminalMount.style.overscrollBehavior = \"contain\";\n this.terminalMount.style.outline = \"none\";\n this.terminalMount.style.background = webTUITerminalBackgroundColor(this.currentStyle);\n this.terminalMount.style.minHeight = `${this.cellHeight * 8}px`;\n\n if (this.canvas) {\n this.canvas.style.display = \"block\";\n this.canvas.style.width = \"100%\";\n this.canvas.style.height = \"100%\";\n }\n }\n\n private applyVisibility(): void {\n this.element.hidden = !this.isVisible;\n this.element.style.setProperty(\n \"display\",\n this.isVisible ? \"grid\" : \"none\",\n \"important\"\n );\n }\n\n private installResizeObserver(): void {\n if (typeof ResizeObserver === \"undefined\") {\n return;\n }\n\n this.resizeObserver = new ResizeObserver(() => {\n this.resizeToMount();\n });\n this.resizeObserver.observe(this.terminalMount);\n }\n\n private installInputHandlers(): void {\n const handleKeyDown = (event: KeyboardEvent) => {\n if (event.metaKey || event.isComposing) {\n return;\n }\n const message = this.inputEncoder.encodeKey(event);\n if (!message) {\n return;\n }\n\n this.onInput(message);\n event.preventDefault();\n };\n\n const handlePaste = (event: ClipboardEvent) => {\n const text = event.clipboardData?.getData(\"text/plain\") ?? \"\";\n if (!text) {\n return;\n }\n this.onInput(this.inputEncoder.encodePaste(text));\n event.preventDefault();\n };\n\n const handlePointerDown = (event: PointerEvent) => {\n const location = this.cellLocation(event);\n if (!location) {\n return;\n }\n\n const button = this.inputEncoder.pointerButton(event.button);\n this.activePointerButton = button;\n this.hasCapturedPointer = true;\n this.pointerDownLinkTarget = button === \"primary\"\n ? this.linkTarget(location)\n : undefined;\n this.terminalMount.focus?.({ preventScroll: true });\n this.terminalMount.setPointerCapture?.(event.pointerId);\n this.onInput(this.inputEncoder.encodePointerDown(location, button, event));\n event.preventDefault();\n };\n\n const handlePointerUp = (event: PointerEvent) => {\n const location = this.hasCapturedPointer\n ? this.rawCellLocation(event)\n : this.cellLocation(event);\n this.terminalMount.releasePointerCapture?.(event.pointerId);\n this.hasCapturedPointer = false;\n const downLinkTarget = this.pointerDownLinkTarget;\n this.pointerDownLinkTarget = undefined;\n if (!location) {\n return;\n }\n\n const button = this.inputEncoder.pointerButton(event.button) ?? this.activePointerButton;\n this.onInput(this.inputEncoder.encodePointerUp(location, button, event));\n // A click — down and up over the same link target — opens the link,\n // mirroring the Android host's tap-to-open. The app still receives the\n // pointer messages above.\n if (downLinkTarget !== undefined && this.linkTarget(location) === downLinkTarget) {\n this.openHyperlink(downLinkTarget);\n }\n event.preventDefault();\n };\n\n const handlePointerMove = (event: PointerEvent) => {\n const location = event.buttons && this.hasCapturedPointer\n ? this.rawCellLocation(event)\n : this.cellLocation(event);\n if (!location) {\n return;\n }\n\n if (!this.hasCapturedPointer) {\n this.terminalMount.style.cursor =\n this.linkTarget(location) !== undefined ? \"pointer\" : \"\";\n }\n this.onInput(this.inputEncoder.encodePointerMove(location, this.activePointerButton, event));\n };\n\n const handleWheel = (event: WheelEvent) => {\n if (this.wheelMode === \"passive\") {\n return;\n }\n\n const location = this.cellLocation(event);\n if (!location) {\n // Pointer is outside the cell grid (sub-cell margin / gutter). Don't\n // capture — let the wheel fall through to the page.\n return;\n }\n\n // In \"chain\" mode, capture only while a scrollable region under the\n // pointer can still move in this direction; otherwise let the wheel fall\n // through so the page (or parent iframe) scrolls — iframe-like behavior.\n // \"capture\" mode always forwards while over the surface (legacy).\n if (this.wheelMode === \"chain\"\n && !wheelTargetCanScroll(this.currentFrame?.scrollRegions, location, event.deltaX, event.deltaY)) {\n return;\n }\n\n this.onInput(this.inputEncoder.encodeWheel(location, event));\n event.preventDefault();\n };\n\n this.terminalMount.addEventListener(\"keydown\", handleKeyDown);\n this.terminalMount.addEventListener(\"paste\", handlePaste);\n this.terminalMount.addEventListener(\"pointerdown\", handlePointerDown);\n this.terminalMount.addEventListener(\"pointerup\", handlePointerUp);\n this.terminalMount.addEventListener(\"pointermove\", handlePointerMove);\n this.terminalMount.addEventListener(\"wheel\", handleWheel, { passive: false });\n\n this.detachInputHandlers = () => {\n this.terminalMount.removeEventListener(\"keydown\", handleKeyDown);\n this.terminalMount.removeEventListener(\"paste\", handlePaste);\n this.terminalMount.removeEventListener(\"pointerdown\", handlePointerDown);\n this.terminalMount.removeEventListener(\"pointerup\", handlePointerUp);\n this.terminalMount.removeEventListener(\"pointermove\", handlePointerMove);\n this.terminalMount.removeEventListener(\"wheel\", handleWheel);\n };\n }\n\n private resizeToMount(): void {\n this.measureCells();\n const rect = this.terminalMount.getBoundingClientRect?.();\n const width = rect?.width && rect.width > 0 ? rect.width : this.columns * this.cellWidth;\n const height = rect?.height && rect.height > 0 ? rect.height : this.rows * this.cellHeight;\n const nextColumns = Math.max(1, Math.floor(width / this.cellWidth));\n const nextRows = Math.max(1, Math.floor(height / this.cellHeight));\n\n this.columns = nextColumns;\n this.rows = nextRows;\n this.sendResizeIfNeeded();\n this.resizeCanvas();\n }\n\n private sendResizeIfNeeded(): void {\n const current = {\n columns: this.columns,\n rows: this.rows,\n cellWidth: this.cellWidth,\n cellHeight: this.cellHeight,\n };\n if (this.lastSentResize\n && this.lastSentResize.columns === current.columns\n && this.lastSentResize.rows === current.rows\n && this.lastSentResize.cellWidth === current.cellWidth\n && this.lastSentResize.cellHeight === current.cellHeight\n ) {\n return;\n }\n\n this.lastSentResize = current;\n this.bridge?.resize(current.columns, current.rows, current.cellWidth, current.cellHeight);\n }\n\n private resizeCanvas(): boolean {\n if (!this.canvas) {\n return false;\n }\n\n const cssWidth = Math.max(1, this.columns * this.cellWidth);\n const cssHeight = Math.max(1, this.rows * this.cellHeight);\n const scale = globalThis.window?.devicePixelRatio || 1;\n const width = Math.ceil(cssWidth * scale);\n const height = Math.ceil(cssHeight * scale);\n const styleWidth = `${cssWidth}px`;\n const styleHeight = `${cssHeight}px`;\n if (this.canvas.width === width\n && this.canvas.height === height\n && this.canvas.style.width === styleWidth\n && this.canvas.style.height === styleHeight\n ) {\n return false;\n }\n\n this.canvas.width = width;\n this.canvas.height = height;\n this.canvas.style.width = styleWidth;\n this.canvas.style.height = styleHeight;\n return true;\n }\n\n private measureCells(): void {\n const canvas = this.canvas ?? document.createElement(\"canvas\");\n const context = canvas.getContext?.(\"2d\");\n if (!context) {\n this.cellWidth = Math.max(1, Math.round(this.currentStyle.fontSize * 0.62));\n this.cellHeight = Math.max(1, Math.round(this.currentStyle.fontSize * 1.35));\n return;\n }\n\n context.font = fontForStyle(this.currentStyle);\n this.cellWidth = Math.max(1, Math.ceil(context.measureText(\"W\").width));\n this.cellHeight = Math.max(1, Math.ceil(this.currentStyle.fontSize * 1.35));\n }\n\n private draw(\n damage?: WebHostSurfaceDamage\n ): void {\n this.painter.paint(this.surfaceMetrics(), this.currentFrame, damage);\n }\n\n private syncAccessibilityTree(): void {\n const tree = this.accessibilityTree;\n if (!tree || !this.currentFrame) {\n return;\n }\n\n tree.present(this.currentFrame.accessibilityTree ?? [], {\n cellWidth: this.cellWidth,\n cellHeight: this.cellHeight,\n }, this.currentFrame.accessibilityAnnouncements ?? [], {\n synchronizeFocus: this.synchronizeAccessibilityFocus,\n });\n }\n\n private surfaceMetrics(): CanvasSurfaceMetrics {\n return {\n columns: this.columns,\n rows: this.rows,\n cellWidth: this.cellWidth,\n cellHeight: this.cellHeight,\n style: this.currentStyle,\n };\n }\n\n private pointerMetrics(): PointerGeometryMetrics {\n return {\n rect: this.canvas?.getBoundingClientRect?.() ?? this.terminalMount.getBoundingClientRect?.(),\n cellWidth: this.cellWidth,\n cellHeight: this.cellHeight,\n columns: this.columns,\n rows: this.rows,\n };\n }\n\n private cellLocation(\n event: MouseEvent\n ): CellLocation | undefined {\n return cellLocationForEvent(event, this.pointerMetrics());\n }\n\n private rawCellLocation(\n event: MouseEvent\n ): CellLocation | undefined {\n return rawCellLocationForEvent(event, this.pointerMetrics());\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { WebHostTerminalStyle } from "./WebHostTerminalStyle.js";
|
|
2
|
-
|
|
3
2
|
//#region src/WebHostSurfaceTransport.d.ts
|
|
4
3
|
interface WebHostSurfaceStyle {
|
|
5
4
|
fg?: string;
|
|
@@ -25,10 +24,30 @@ interface WebHostAccessibilityNode {
|
|
|
25
24
|
role: string;
|
|
26
25
|
label?: string;
|
|
27
26
|
hint?: string;
|
|
27
|
+
/** omitted when false */
|
|
28
|
+
hidden?: boolean;
|
|
28
29
|
liveRegion?: WebHostAccessibilityLiveRegion;
|
|
29
30
|
cursorAnchor?: WebHostAccessibilityPoint;
|
|
30
31
|
isFocused?: boolean;
|
|
31
32
|
}
|
|
33
|
+
/**
|
|
34
|
+
* One hyperlink run within a row: [x, spanWidth, linkTargetIndex]. The index
|
|
35
|
+
* points into the frame's deduplicated `linkTargets` table.
|
|
36
|
+
*/
|
|
37
|
+
type WebHostSurfaceLinkRun = [x: number, span: number, targetIndex: number];
|
|
38
|
+
/** Hyperlink runs for one row: [rowIndex, runs]. */
|
|
39
|
+
type WebHostSurfaceLinkRow = [row: number, runs: WebHostSurfaceLinkRun[]];
|
|
40
|
+
type WebHostFocusSemantics = "none" | "automatic" | "activate" | "edit";
|
|
41
|
+
/**
|
|
42
|
+
* The settled focus presentation for a committed frame — the same derivation
|
|
43
|
+
* the Android host consumes (`prefersTextInput` gates its IME).
|
|
44
|
+
*/
|
|
45
|
+
interface WebHostFocusPresentation {
|
|
46
|
+
focusedIdentity?: string;
|
|
47
|
+
semantics: WebHostFocusSemantics;
|
|
48
|
+
prefersTextInput: boolean;
|
|
49
|
+
hasFocusedRegion: boolean;
|
|
50
|
+
}
|
|
32
51
|
interface WebHostAccessibilityAnnouncement {
|
|
33
52
|
message: string;
|
|
34
53
|
politeness: WebHostAccessibilityLiveRegion;
|
|
@@ -80,6 +99,11 @@ interface WebHostSurfaceFrame {
|
|
|
80
99
|
accessibilityTree?: WebHostAccessibilityNode[];
|
|
81
100
|
accessibilityAnnouncements?: WebHostAccessibilityAnnouncement[];
|
|
82
101
|
scrollRegions?: WebHostScrollRegion[];
|
|
102
|
+
links?: WebHostSurfaceLinkRow[];
|
|
103
|
+
linkTargets?: string[];
|
|
104
|
+
focusPresentation?: WebHostFocusPresentation;
|
|
105
|
+
preferredGridWidth?: number;
|
|
106
|
+
preferredGridHeight?: number;
|
|
83
107
|
}
|
|
84
108
|
type WebHostSurfaceDeltaRow = [row: number, cells: WebHostSurfaceCell[]];
|
|
85
109
|
interface WebHostSurfaceDeltaFrame {
|
|
@@ -95,6 +119,11 @@ interface WebHostSurfaceDeltaFrame {
|
|
|
95
119
|
accessibilityTree?: WebHostAccessibilityNode[];
|
|
96
120
|
accessibilityAnnouncements?: WebHostAccessibilityAnnouncement[];
|
|
97
121
|
scrollRegions?: WebHostScrollRegion[];
|
|
122
|
+
links?: WebHostSurfaceLinkRow[];
|
|
123
|
+
linkTargets?: string[];
|
|
124
|
+
focusPresentation?: WebHostFocusPresentation;
|
|
125
|
+
preferredGridWidth?: number;
|
|
126
|
+
preferredGridHeight?: number;
|
|
98
127
|
}
|
|
99
128
|
interface WebHostRuntimeIssue {
|
|
100
129
|
severity: "warning" | "error";
|
|
@@ -147,6 +176,15 @@ interface WebHostMouseInput {
|
|
|
147
176
|
deltaY?: number;
|
|
148
177
|
modifiers?: number;
|
|
149
178
|
}
|
|
179
|
+
/**
|
|
180
|
+
* The newest `surface` record version this runtime understands. Unknown
|
|
181
|
+
* additive object keys are ignored by design (older runtimes render newer
|
|
182
|
+
* frames), but a frame declaring a NEWER version than this is surfaced as an
|
|
183
|
+
* error-severity runtime issue instead of silently degrading to a text
|
|
184
|
+
* diagnostic — silent version skew is the failure mode this guards against
|
|
185
|
+
* (F57).
|
|
186
|
+
*/
|
|
187
|
+
declare const SUPPORTED_SURFACE_VERSION = 3;
|
|
150
188
|
declare class WebHostOutputDecoder {
|
|
151
189
|
private readonly textDecoder;
|
|
152
190
|
private bufferedText;
|
|
@@ -162,5 +200,5 @@ declare function encodeKeyInputMessage(input: WebHostKeyInput): Uint8Array;
|
|
|
162
200
|
declare function encodePasteInputMessage(text: string): Uint8Array;
|
|
163
201
|
declare function encodeMouseInputMessage(input: WebHostMouseInput): Uint8Array;
|
|
164
202
|
//#endregion
|
|
165
|
-
export { WebHostAccessibilityAnnouncement, WebHostAccessibilityLiveRegion, WebHostAccessibilityNode, WebHostAccessibilityPoint, WebHostFrameDiagnosticRecord, WebHostKeyInput, WebHostMouseInput, WebHostOutputDecoder, WebHostOutputRecord, WebHostOutputSink, WebHostRuntimeIssue, WebHostScrollRegion, WebHostSurfaceCell, WebHostSurfaceDamage, WebHostSurfaceDamageRange, WebHostSurfaceDamageTextRow, WebHostSurfaceDeltaFrame, WebHostSurfaceDeltaRow, WebHostSurfaceFrame, WebHostSurfaceImage, WebHostSurfaceImageFormat, WebHostSurfaceLineStyle, WebHostSurfaceRect, WebHostSurfaceSize, WebHostSurfaceStyle, encodeKeyInputMessage, encodeMouseInputMessage, encodePasteInputMessage, encodeRenderStyleControlMessage, encodeResizeControlMessage };
|
|
203
|
+
export { SUPPORTED_SURFACE_VERSION, WebHostAccessibilityAnnouncement, WebHostAccessibilityLiveRegion, WebHostAccessibilityNode, WebHostAccessibilityPoint, WebHostFocusPresentation, WebHostFocusSemantics, WebHostFrameDiagnosticRecord, WebHostKeyInput, WebHostMouseInput, WebHostOutputDecoder, WebHostOutputRecord, WebHostOutputSink, WebHostRuntimeIssue, WebHostScrollRegion, WebHostSurfaceCell, WebHostSurfaceDamage, WebHostSurfaceDamageRange, WebHostSurfaceDamageTextRow, WebHostSurfaceDeltaFrame, WebHostSurfaceDeltaRow, WebHostSurfaceFrame, WebHostSurfaceImage, WebHostSurfaceImageFormat, WebHostSurfaceLineStyle, WebHostSurfaceLinkRow, WebHostSurfaceLinkRun, WebHostSurfaceRect, WebHostSurfaceSize, WebHostSurfaceStyle, encodeKeyInputMessage, encodeMouseInputMessage, encodePasteInputMessage, encodeRenderStyleControlMessage, encodeResizeControlMessage };
|
|
166
204
|
//# sourceMappingURL=WebHostSurfaceTransport.d.ts.map
|
|
@@ -2,6 +2,15 @@ import { encodeWebHostTerminalRenderStyleBase64 } from "./WebHostTerminalStyle.j
|
|
|
2
2
|
//#region src/WebHostSurfaceTransport.ts
|
|
3
3
|
const recordPrefix = "";
|
|
4
4
|
const textEncoder = new TextEncoder();
|
|
5
|
+
/**
|
|
6
|
+
* The newest `surface` record version this runtime understands. Unknown
|
|
7
|
+
* additive object keys are ignored by design (older runtimes render newer
|
|
8
|
+
* frames), but a frame declaring a NEWER version than this is surfaced as an
|
|
9
|
+
* error-severity runtime issue instead of silently degrading to a text
|
|
10
|
+
* diagnostic — silent version skew is the failure mode this guards against
|
|
11
|
+
* (F57).
|
|
12
|
+
*/
|
|
13
|
+
const SUPPORTED_SURFACE_VERSION = 3;
|
|
5
14
|
var WebHostOutputDecoder = class {
|
|
6
15
|
textDecoder = new TextDecoder();
|
|
7
16
|
bufferedText = "";
|
|
@@ -77,6 +86,15 @@ var WebHostOutputDecoder = class {
|
|
|
77
86
|
};
|
|
78
87
|
try {
|
|
79
88
|
const frame = JSON.parse(line.slice(`${recordPrefix}surface:`.length));
|
|
89
|
+
if (declaresNewerSurfaceVersion(frame)) return {
|
|
90
|
+
type: "runtimeIssue",
|
|
91
|
+
issue: {
|
|
92
|
+
severity: "error",
|
|
93
|
+
code: "surface.unsupportedVersion",
|
|
94
|
+
message: `SwiftTUI surface version ${frame.version} is newer than the supported 3`,
|
|
95
|
+
description: `The app emitted a surface record with version ${frame.version}, but this @swifttui/web runtime understands versions up to 3. Update @swifttui/web to render it.`
|
|
96
|
+
}
|
|
97
|
+
};
|
|
80
98
|
if (isWebHostSurfaceFrame(frame)) {
|
|
81
99
|
this.lastSurfaceFrame = frame;
|
|
82
100
|
return {
|
|
@@ -119,10 +137,20 @@ var WebHostOutputDecoder = class {
|
|
|
119
137
|
damage: frame.damage,
|
|
120
138
|
accessibilityTree: frame.accessibilityTree,
|
|
121
139
|
accessibilityAnnouncements: frame.accessibilityAnnouncements,
|
|
122
|
-
scrollRegions: frame.scrollRegions
|
|
140
|
+
scrollRegions: frame.scrollRegions,
|
|
141
|
+
links: frame.links,
|
|
142
|
+
linkTargets: frame.linkTargets,
|
|
143
|
+
focusPresentation: frame.focusPresentation,
|
|
144
|
+
preferredGridWidth: frame.preferredGridWidth,
|
|
145
|
+
preferredGridHeight: frame.preferredGridHeight
|
|
123
146
|
};
|
|
124
147
|
}
|
|
125
148
|
};
|
|
149
|
+
function declaresNewerSurfaceVersion(value) {
|
|
150
|
+
if (!value || typeof value !== "object") return false;
|
|
151
|
+
const version = value.version;
|
|
152
|
+
return typeof version === "number" && Number.isSafeInteger(version) && version > 3;
|
|
153
|
+
}
|
|
126
154
|
function isWebHostClipboardRecord(value) {
|
|
127
155
|
return !!value && typeof value === "object" && typeof value.text === "string";
|
|
128
156
|
}
|
|
@@ -172,12 +200,38 @@ function formatCellCoordinate(value) {
|
|
|
172
200
|
function isWebHostSurfaceFrame(value) {
|
|
173
201
|
if (!value || typeof value !== "object") return false;
|
|
174
202
|
const frame = value;
|
|
175
|
-
return (frame.version === 1 || frame.version === 2) && (frame.sequence === void 0 || Number.isSafeInteger(frame.sequence) && frame.sequence >= 0) && typeof frame.width === "number" && typeof frame.height === "number" && Array.isArray(frame.styles) && Array.isArray(frame.rows) && frame.rows.every(isWebHostSurfaceRow) && (frame.images === void 0 || isWebHostSurfaceImages(frame.images)) && (frame.damage === void 0 || isWebHostSurfaceDamage(frame.damage)) && (frame.accessibilityTree === void 0 || isWebHostAccessibilityNodes(frame.accessibilityTree)) && (frame.accessibilityAnnouncements === void 0 || isWebHostAccessibilityAnnouncements(frame.accessibilityAnnouncements)) && (frame.scrollRegions === void 0 || isWebHostScrollRegions(frame.scrollRegions));
|
|
203
|
+
return (frame.version === 1 || frame.version === 2) && (frame.sequence === void 0 || Number.isSafeInteger(frame.sequence) && frame.sequence >= 0) && typeof frame.width === "number" && typeof frame.height === "number" && Array.isArray(frame.styles) && Array.isArray(frame.rows) && frame.rows.every(isWebHostSurfaceRow) && (frame.images === void 0 || isWebHostSurfaceImages(frame.images)) && (frame.damage === void 0 || isWebHostSurfaceDamage(frame.damage)) && (frame.accessibilityTree === void 0 || isWebHostAccessibilityNodes(frame.accessibilityTree)) && (frame.accessibilityAnnouncements === void 0 || isWebHostAccessibilityAnnouncements(frame.accessibilityAnnouncements)) && (frame.scrollRegions === void 0 || isWebHostScrollRegions(frame.scrollRegions)) && hasValidAdditiveFrameFields(frame);
|
|
176
204
|
}
|
|
177
205
|
function isWebHostSurfaceDeltaFrame(value) {
|
|
178
206
|
if (!value || typeof value !== "object") return false;
|
|
179
207
|
const frame = value;
|
|
180
|
-
return frame.version === 3 && frame.encoding === "delta" && (frame.sequence === void 0 || Number.isSafeInteger(frame.sequence) && frame.sequence >= 0) && typeof frame.width === "number" && typeof frame.height === "number" && Array.isArray(frame.styles) && Array.isArray(frame.deltaRows) && frame.deltaRows.every(isWebHostSurfaceDeltaRow) && (frame.images === void 0 || isWebHostSurfaceImages(frame.images)) && (frame.damage === void 0 || isWebHostSurfaceDamage(frame.damage)) && (frame.accessibilityTree === void 0 || isWebHostAccessibilityNodes(frame.accessibilityTree)) && (frame.accessibilityAnnouncements === void 0 || isWebHostAccessibilityAnnouncements(frame.accessibilityAnnouncements)) && (frame.scrollRegions === void 0 || isWebHostScrollRegions(frame.scrollRegions));
|
|
208
|
+
return frame.version === 3 && frame.encoding === "delta" && (frame.sequence === void 0 || Number.isSafeInteger(frame.sequence) && frame.sequence >= 0) && typeof frame.width === "number" && typeof frame.height === "number" && Array.isArray(frame.styles) && Array.isArray(frame.deltaRows) && frame.deltaRows.every(isWebHostSurfaceDeltaRow) && (frame.images === void 0 || isWebHostSurfaceImages(frame.images)) && (frame.damage === void 0 || isWebHostSurfaceDamage(frame.damage)) && (frame.accessibilityTree === void 0 || isWebHostAccessibilityNodes(frame.accessibilityTree)) && (frame.accessibilityAnnouncements === void 0 || isWebHostAccessibilityAnnouncements(frame.accessibilityAnnouncements)) && (frame.scrollRegions === void 0 || isWebHostScrollRegions(frame.scrollRegions)) && hasValidAdditiveFrameFields(frame);
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* The F19 additive fields shared by the full and delta record shapes. Absent
|
|
212
|
+
* means "feature not present" — servers older than the field omit it.
|
|
213
|
+
*/
|
|
214
|
+
function hasValidAdditiveFrameFields(frame) {
|
|
215
|
+
return (frame.links === void 0 || isWebHostSurfaceLinks(frame.links)) && (frame.linkTargets === void 0 || isWebHostSurfaceLinkTargets(frame.linkTargets)) && (frame.focusPresentation === void 0 || isWebHostFocusPresentation(frame.focusPresentation)) && (frame.preferredGridWidth === void 0 || Number.isSafeInteger(frame.preferredGridWidth) && frame.preferredGridWidth >= 0) && (frame.preferredGridHeight === void 0 || Number.isSafeInteger(frame.preferredGridHeight) && frame.preferredGridHeight >= 0);
|
|
216
|
+
}
|
|
217
|
+
function isWebHostSurfaceLinks(value) {
|
|
218
|
+
return Array.isArray(value) && value.every(isWebHostSurfaceLinkRow);
|
|
219
|
+
}
|
|
220
|
+
function isWebHostSurfaceLinkRow(value) {
|
|
221
|
+
return Array.isArray(value) && value.length === 2 && Number.isSafeInteger(value[0]) && value[0] >= 0 && Array.isArray(value[1]) && value[1].every(isWebHostSurfaceLinkRun);
|
|
222
|
+
}
|
|
223
|
+
function isWebHostSurfaceLinkRun(value) {
|
|
224
|
+
if (!Array.isArray(value) || value.length !== 3) return false;
|
|
225
|
+
const [x, span, targetIndex] = value;
|
|
226
|
+
return Number.isSafeInteger(x) && x >= 0 && Number.isSafeInteger(span) && span >= 1 && Number.isSafeInteger(targetIndex) && targetIndex >= 0;
|
|
227
|
+
}
|
|
228
|
+
function isWebHostSurfaceLinkTargets(value) {
|
|
229
|
+
return Array.isArray(value) && value.every((entry) => typeof entry === "string");
|
|
230
|
+
}
|
|
231
|
+
function isWebHostFocusPresentation(value) {
|
|
232
|
+
if (!value || typeof value !== "object") return false;
|
|
233
|
+
const presentation = value;
|
|
234
|
+
return (presentation.focusedIdentity === void 0 || typeof presentation.focusedIdentity === "string") && (presentation.semantics === "none" || presentation.semantics === "automatic" || presentation.semantics === "activate" || presentation.semantics === "edit") && typeof presentation.prefersTextInput === "boolean" && typeof presentation.hasFocusedRegion === "boolean";
|
|
181
235
|
}
|
|
182
236
|
function isWebHostSurfaceDeltaRow(value) {
|
|
183
237
|
return Array.isArray(value) && value.length === 2 && Number.isSafeInteger(value[0]) && value[0] >= 0 && isWebHostSurfaceRow(value[1]);
|
|
@@ -194,7 +248,7 @@ function isWebHostAccessibilityNodes(value) {
|
|
|
194
248
|
function isWebHostAccessibilityNode(value) {
|
|
195
249
|
if (!value || typeof value !== "object") return false;
|
|
196
250
|
const node = value;
|
|
197
|
-
return typeof node.id === "string" && (node.parentId === void 0 || typeof node.parentId === "string") && isWebHostSurfaceRect(node.rect) && typeof node.role === "string" && (node.label === void 0 || typeof node.label === "string") && (node.hint === void 0 || typeof node.hint === "string") && (node.liveRegion === void 0 || node.liveRegion === "off" || node.liveRegion === "polite" || node.liveRegion === "assertive") && (node.cursorAnchor === void 0 || isWebHostAccessibilityPoint(node.cursorAnchor)) && (node.isFocused === void 0 || typeof node.isFocused === "boolean");
|
|
251
|
+
return typeof node.id === "string" && (node.parentId === void 0 || typeof node.parentId === "string") && isWebHostSurfaceRect(node.rect) && typeof node.role === "string" && (node.label === void 0 || typeof node.label === "string") && (node.hint === void 0 || typeof node.hint === "string") && (node.hidden === void 0 || typeof node.hidden === "boolean") && (node.liveRegion === void 0 || node.liveRegion === "off" || node.liveRegion === "polite" || node.liveRegion === "assertive") && (node.cursorAnchor === void 0 || isWebHostAccessibilityPoint(node.cursorAnchor)) && (node.isFocused === void 0 || typeof node.isFocused === "boolean");
|
|
198
252
|
}
|
|
199
253
|
function isWebHostAccessibilityPoint(value) {
|
|
200
254
|
return Array.isArray(value) && value.length === 2 && value.every((entry) => typeof entry === "number");
|
|
@@ -247,6 +301,6 @@ function isWebHostSurfaceScalingMode(value) {
|
|
|
247
301
|
return value === "stretch" || value === "fit" || value === "fill";
|
|
248
302
|
}
|
|
249
303
|
//#endregion
|
|
250
|
-
export { WebHostOutputDecoder, encodeKeyInputMessage, encodeMouseInputMessage, encodePasteInputMessage, encodeRenderStyleControlMessage, encodeResizeControlMessage };
|
|
304
|
+
export { SUPPORTED_SURFACE_VERSION, WebHostOutputDecoder, encodeKeyInputMessage, encodeMouseInputMessage, encodePasteInputMessage, encodeRenderStyleControlMessage, encodeResizeControlMessage };
|
|
251
305
|
|
|
252
306
|
//# sourceMappingURL=WebHostSurfaceTransport.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WebHostSurfaceTransport.js","names":[],"sources":["../../src/WebHostSurfaceTransport.ts"],"sourcesContent":["import {\n encodeWebHostTerminalRenderStyleBase64,\n type WebHostTerminalStyle,\n} from \"./WebHostTerminalStyle.ts\";\n\nexport interface WebHostSurfaceStyle {\n fg?: string;\n bg?: string;\n em?: number;\n underline?: WebHostSurfaceLineStyle;\n strikethrough?: WebHostSurfaceLineStyle;\n opacity?: number;\n}\n\nexport interface WebHostSurfaceLineStyle {\n pattern: \"solid\" | \"dot\" | \"dash\" | \"dashDot\" | \"dashDotDot\" | \"double\" | \"curly\";\n color?: string;\n}\n\nexport type WebHostSurfaceCell = [\n x: number,\n text: string,\n span: number,\n styleIndex: number,\n];\n\nexport type WebHostSurfaceRect = [\n x: number,\n y: number,\n width: number,\n height: number,\n];\n\nexport type WebHostSurfaceSize = [\n width: number,\n height: number,\n];\n\nexport type WebHostAccessibilityPoint = [\n x: number,\n y: number,\n];\n\nexport type WebHostAccessibilityLiveRegion = \"off\" | \"polite\" | \"assertive\";\n\nexport interface WebHostAccessibilityNode {\n id: string;\n parentId?: string;\n rect: WebHostSurfaceRect;\n role: string;\n label?: string;\n hint?: string;\n liveRegion?: WebHostAccessibilityLiveRegion;\n cursorAnchor?: WebHostAccessibilityPoint;\n isFocused?: boolean;\n}\n\nexport interface WebHostAccessibilityAnnouncement {\n message: string;\n politeness: WebHostAccessibilityLiveRegion;\n}\n\nexport type WebHostSurfaceImageFormat = \"png\" | \"jpeg\" | \"gif\";\n\nexport interface WebHostSurfaceImage {\n id: string;\n format: WebHostSurfaceImageFormat;\n bounds: WebHostSurfaceRect;\n visibleBounds: WebHostSurfaceRect;\n scalingMode: \"stretch\" | \"fit\" | \"fill\";\n pixelSize?: WebHostSurfaceSize;\n dataBase64?: string;\n}\n\nexport type WebHostSurfaceDamageRange = [\n start: number,\n end: number,\n];\n\nexport type WebHostSurfaceDamageTextRow = [\n row: number,\n ranges: WebHostSurfaceDamageRange[],\n];\n\nexport interface WebHostSurfaceDamage {\n textRows: WebHostSurfaceDamageTextRow[];\n requiresFullTextRepaint: boolean;\n requiresFullGraphicsReplay: boolean;\n}\n\n/**\n * Per-region scroll extent published with each frame so the host can implement\n * scroll-chaining: capture the wheel only while the region under the pointer can\n * still scroll in the wheel's direction, otherwise let it fall through to the\n * page. The host recomputes the per-direction headroom from `offset`/`content`/\n * the viewport `rect`, mirroring SwiftTUI's\n * `min(max(0, offset), max(0, content - viewport))` clamp.\n */\nexport interface WebHostScrollRegion {\n /** identity path — same key space as accessibility node ids */\n id: string;\n /** viewport rect in cells: [x, y, width, height] */\n rect: WebHostSurfaceRect;\n /** current clamped scroll offset in cells: [x, y] */\n offset: WebHostAccessibilityPoint;\n /** total content size in cells: [width, height] */\n content: WebHostSurfaceSize;\n}\n\nexport interface WebHostSurfaceFrame {\n version: 1 | 2;\n sequence?: number;\n width: number;\n height: number;\n styles: Array<WebHostSurfaceStyle | null>;\n rows: WebHostSurfaceCell[][];\n images?: WebHostSurfaceImage[];\n damage?: WebHostSurfaceDamage;\n accessibilityTree?: WebHostAccessibilityNode[];\n accessibilityAnnouncements?: WebHostAccessibilityAnnouncement[];\n scrollRegions?: WebHostScrollRegion[];\n}\n\nexport type WebHostSurfaceDeltaRow = [\n row: number,\n cells: WebHostSurfaceCell[],\n];\n\nexport interface WebHostSurfaceDeltaFrame {\n version: 3;\n encoding: \"delta\";\n sequence?: number;\n width: number;\n height: number;\n styles: Array<WebHostSurfaceStyle | null>;\n deltaRows: WebHostSurfaceDeltaRow[];\n images?: WebHostSurfaceImage[];\n damage?: WebHostSurfaceDamage;\n accessibilityTree?: WebHostAccessibilityNode[];\n accessibilityAnnouncements?: WebHostAccessibilityAnnouncement[];\n scrollRegions?: WebHostScrollRegion[];\n}\n\nexport interface WebHostRuntimeIssue {\n severity: \"warning\" | \"error\";\n code: string;\n message: string;\n description: string;\n identity?: string;\n source?: string;\n}\n\nexport interface WebHostFrameDiagnosticRecord {\n format: \"swift-tui-frame-diagnostics-v1\";\n header: string[];\n fields: string[];\n}\n\nexport type WebHostOutputRecord =\n | { type: \"surface\"; frame: WebHostSurfaceFrame }\n | { type: \"clipboard\"; text: string }\n | { type: \"runtimeIssue\"; issue: WebHostRuntimeIssue }\n | { type: \"frameDiagnostic\"; diagnostic: WebHostFrameDiagnosticRecord }\n | { type: \"text\"; text: string };\n\nexport interface WebHostOutputSink {\n presentSurface(frame: WebHostSurfaceFrame): void;\n writeClipboard?(text: string): void | Promise<void>;\n notifyRuntimeIssue?(issue: WebHostRuntimeIssue): void;\n recordFrameDiagnostic?(diagnostic: WebHostFrameDiagnosticRecord): void;\n writeOutput?(text: string): void;\n writeError?(text: string): void;\n}\n\nexport interface WebHostKeyInput {\n key:\n | \"return\"\n | \"space\"\n | \"tab\"\n | \"arrowLeft\"\n | \"arrowRight\"\n | \"arrowUp\"\n | \"arrowDown\"\n | \"backspace\"\n | \"escape\"\n | \"home\"\n | \"end\"\n | \"character\";\n character?: string;\n modifiers?: number;\n}\n\nexport interface WebHostMouseInput {\n kind: \"down\" | \"up\" | \"moved\" | \"dragged\" | \"scrolled\";\n x: number;\n y: number;\n button?: \"primary\" | \"middle\" | \"secondary\";\n deltaX?: number;\n deltaY?: number;\n modifiers?: number;\n}\n\nconst recordPrefix = \"\\u001E\";\nconst textEncoder = new TextEncoder();\n\nexport class WebHostOutputDecoder {\n private readonly textDecoder = new TextDecoder();\n private bufferedText = \"\";\n private lastSurfaceFrame?: WebHostSurfaceFrame;\n\n feed(\n chunk: Uint8Array\n ): WebHostOutputRecord[] {\n this.bufferedText += this.textDecoder.decode(chunk, { stream: true });\n const records: WebHostOutputRecord[] = [];\n\n while (true) {\n const newlineIndex = this.bufferedText.indexOf(\"\\n\");\n if (newlineIndex < 0) {\n break;\n }\n\n const line = this.bufferedText.slice(0, newlineIndex);\n this.bufferedText = this.bufferedText.slice(newlineIndex + 1);\n records.push(this.decodeLine(line));\n }\n\n if (this.bufferedText.length > 4096 && !this.bufferedText.startsWith(recordPrefix)) {\n records.push({ type: \"text\", text: this.bufferedText });\n this.bufferedText = \"\";\n }\n\n return records;\n }\n\n flush(): WebHostOutputRecord[] {\n if (!this.bufferedText) {\n return [];\n }\n const text = this.bufferedText;\n this.bufferedText = \"\";\n return [this.decodeLine(text)];\n }\n\n private decodeLine(\n line: string\n ): WebHostOutputRecord {\n if (line.startsWith(`${recordPrefix}clipboard:`)) {\n try {\n const record = JSON.parse(line.slice(`${recordPrefix}clipboard:`.length));\n if (isWebHostClipboardRecord(record)) {\n return { type: \"clipboard\", text: record.text };\n }\n } catch {\n // Fall through to the text path below so malformed output remains visible.\n }\n\n return { type: \"text\", text: `${line}\\n` };\n }\n\n if (line.startsWith(`${recordPrefix}runtimeIssue:`)) {\n try {\n const record = JSON.parse(line.slice(`${recordPrefix}runtimeIssue:`.length));\n if (isWebHostRuntimeIssue(record)) {\n return { type: \"runtimeIssue\", issue: record };\n }\n } catch {\n // Fall through to the text path below so malformed output remains visible.\n }\n\n return { type: \"text\", text: `${line}\\n` };\n }\n\n if (line.startsWith(`${recordPrefix}frameDiagnostic:`)) {\n try {\n const record = JSON.parse(line.slice(`${recordPrefix}frameDiagnostic:`.length));\n if (isWebHostFrameDiagnosticRecord(record)) {\n return { type: \"frameDiagnostic\", diagnostic: record };\n }\n } catch {\n // Fall through to the text path below so malformed output remains visible.\n }\n\n return { type: \"text\", text: `${line}\\n` };\n }\n\n if (!line.startsWith(`${recordPrefix}surface:`)) {\n return { type: \"text\", text: `${line}\\n` };\n }\n\n try {\n const frame = JSON.parse(line.slice(`${recordPrefix}surface:`.length));\n if (isWebHostSurfaceFrame(frame)) {\n this.lastSurfaceFrame = frame;\n return { type: \"surface\", frame };\n }\n if (isWebHostSurfaceDeltaFrame(frame)) {\n const materialized = this.materializeDeltaFrame(frame);\n if (materialized) {\n this.lastSurfaceFrame = materialized;\n return { type: \"surface\", frame: materialized };\n }\n }\n } catch {\n // Fall through to the text path below so malformed output remains visible.\n }\n\n return { type: \"text\", text: `${line}\\n` };\n }\n\n private materializeDeltaFrame(\n frame: WebHostSurfaceDeltaFrame\n ): WebHostSurfaceFrame | undefined {\n const baseline = this.lastSurfaceFrame;\n if (!baseline || baseline.width !== frame.width || baseline.height !== frame.height) {\n return undefined;\n }\n\n const rows = baseline.rows.slice();\n for (const [row, cells] of frame.deltaRows) {\n if (!Number.isSafeInteger(row) || row < 0 || row >= frame.height) {\n return undefined;\n }\n rows[row] = cells;\n }\n\n return {\n version: baseline.version,\n sequence: frame.sequence,\n width: frame.width,\n height: frame.height,\n styles: frame.styles,\n rows,\n images: frame.images,\n damage: frame.damage,\n accessibilityTree: frame.accessibilityTree,\n accessibilityAnnouncements: frame.accessibilityAnnouncements,\n scrollRegions: frame.scrollRegions,\n };\n }\n}\n\nfunction isWebHostClipboardRecord(\n value: unknown\n): value is { text: string } {\n return !!value && typeof value === \"object\" && typeof (value as { text?: unknown }).text === \"string\";\n}\n\nfunction isWebHostRuntimeIssue(\n value: unknown\n): value is WebHostRuntimeIssue {\n if (!value || typeof value !== \"object\") {\n return false;\n }\n const record = value as Partial<WebHostRuntimeIssue>;\n return (record.severity === \"warning\" || record.severity === \"error\")\n && typeof record.code === \"string\"\n && typeof record.message === \"string\"\n && typeof record.description === \"string\"\n && (record.identity === undefined || typeof record.identity === \"string\")\n && (record.source === undefined || typeof record.source === \"string\");\n}\n\nfunction isWebHostFrameDiagnosticRecord(\n value: unknown\n): value is WebHostFrameDiagnosticRecord {\n if (!value || typeof value !== \"object\") {\n return false;\n }\n const record = value as Partial<WebHostFrameDiagnosticRecord>;\n return record.format === \"swift-tui-frame-diagnostics-v1\"\n && Array.isArray(record.header)\n && record.header.every((field) => typeof field === \"string\")\n && Array.isArray(record.fields)\n && record.fields.every((field) => typeof field === \"string\");\n}\n\nexport function encodeResizeControlMessage(\n columns: number,\n rows: number,\n cellWidth?: number,\n cellHeight?: number\n): Uint8Array {\n const normalizedColumns = Math.max(1, Math.round(columns));\n const normalizedRows = Math.max(1, Math.round(rows));\n if (cellWidth && cellHeight) {\n return textEncoder.encode(\n `${recordPrefix}resize:${normalizedColumns}:${normalizedRows}:${Math.max(1, Math.round(cellWidth))}:${Math.max(1, Math.round(cellHeight))}\\n`\n );\n }\n\n return textEncoder.encode(`${recordPrefix}resize:${normalizedColumns}:${normalizedRows}\\n`);\n}\n\nexport function encodeRenderStyleControlMessage(\n style: WebHostTerminalStyle\n): Uint8Array {\n const encoded = encodeWebHostTerminalRenderStyleBase64(style);\n return textEncoder.encode(`${recordPrefix}style:${encoded}\\n`);\n}\n\nexport function encodeKeyInputMessage(\n input: WebHostKeyInput\n): Uint8Array {\n const modifiers = Math.max(0, Math.round(input.modifiers ?? 0));\n if (input.key === \"character\") {\n return textEncoder.encode(\n `${recordPrefix}key:character:${encodeURIComponent(input.character ?? \"\")}:${modifiers}\\n`\n );\n }\n return textEncoder.encode(`${recordPrefix}key:${input.key}:${modifiers}\\n`);\n}\n\nexport function encodePasteInputMessage(\n text: string\n): Uint8Array {\n return textEncoder.encode(`${recordPrefix}paste:${encodeURIComponent(text)}\\n`);\n}\n\nexport function encodeMouseInputMessage(\n input: WebHostMouseInput\n): Uint8Array {\n return textEncoder.encode(\n recordPrefix + [\n \"mouse\",\n input.kind,\n formatCellCoordinate(input.x),\n formatCellCoordinate(input.y),\n input.button ?? \"none\",\n Math.round(input.deltaX ?? 0),\n Math.round(input.deltaY ?? 0),\n Math.max(0, Math.round(input.modifiers ?? 0)),\n ].join(\":\") + \"\\n\"\n );\n}\n\nfunction formatCellCoordinate(\n value: number\n): string {\n return Number.isFinite(value) ? String(value) : \"0\";\n}\n\nfunction isWebHostSurfaceFrame(\n value: unknown\n): value is WebHostSurfaceFrame {\n if (!value || typeof value !== \"object\") {\n return false;\n }\n const frame = value as Partial<WebHostSurfaceFrame>;\n return (frame.version === 1 || frame.version === 2)\n && (\n frame.sequence === undefined\n || (Number.isSafeInteger(frame.sequence) && frame.sequence >= 0)\n )\n && typeof frame.width === \"number\"\n && typeof frame.height === \"number\"\n && Array.isArray(frame.styles)\n && Array.isArray(frame.rows)\n && frame.rows.every(isWebHostSurfaceRow)\n && (frame.images === undefined || isWebHostSurfaceImages(frame.images))\n && (frame.damage === undefined || isWebHostSurfaceDamage(frame.damage))\n && (\n frame.accessibilityTree === undefined\n || isWebHostAccessibilityNodes(frame.accessibilityTree)\n )\n && (\n frame.accessibilityAnnouncements === undefined\n || isWebHostAccessibilityAnnouncements(frame.accessibilityAnnouncements)\n )\n && (frame.scrollRegions === undefined || isWebHostScrollRegions(frame.scrollRegions));\n}\n\nfunction isWebHostSurfaceDeltaFrame(\n value: unknown\n): value is WebHostSurfaceDeltaFrame {\n if (!value || typeof value !== \"object\") {\n return false;\n }\n const frame = value as Partial<WebHostSurfaceDeltaFrame>;\n return frame.version === 3\n && frame.encoding === \"delta\"\n && (\n frame.sequence === undefined\n || (Number.isSafeInteger(frame.sequence) && frame.sequence >= 0)\n )\n && typeof frame.width === \"number\"\n && typeof frame.height === \"number\"\n && Array.isArray(frame.styles)\n && Array.isArray(frame.deltaRows)\n && frame.deltaRows.every(isWebHostSurfaceDeltaRow)\n && (frame.images === undefined || isWebHostSurfaceImages(frame.images))\n && (frame.damage === undefined || isWebHostSurfaceDamage(frame.damage))\n && (\n frame.accessibilityTree === undefined\n || isWebHostAccessibilityNodes(frame.accessibilityTree)\n )\n && (\n frame.accessibilityAnnouncements === undefined\n || isWebHostAccessibilityAnnouncements(frame.accessibilityAnnouncements)\n )\n && (frame.scrollRegions === undefined || isWebHostScrollRegions(frame.scrollRegions));\n}\n\nfunction isWebHostSurfaceDeltaRow(\n value: unknown\n): value is WebHostSurfaceDeltaRow {\n return Array.isArray(value)\n && value.length === 2\n && Number.isSafeInteger(value[0])\n && value[0] >= 0\n && isWebHostSurfaceRow(value[1]);\n}\n\nfunction isWebHostSurfaceRow(\n value: unknown\n): value is WebHostSurfaceCell[] {\n return Array.isArray(value) && value.every(isWebHostSurfaceCell);\n}\n\nfunction isWebHostSurfaceCell(\n value: unknown\n): value is WebHostSurfaceCell {\n return Array.isArray(value)\n && value.length === 4\n && Number.isSafeInteger(value[0])\n && value[0] >= 0\n && typeof value[1] === \"string\"\n && Number.isSafeInteger(value[2])\n && value[2] >= 1\n && Number.isSafeInteger(value[3])\n && value[3] >= 0;\n}\n\nfunction isWebHostAccessibilityNodes(\n value: unknown\n): value is WebHostAccessibilityNode[] {\n return Array.isArray(value) && value.every(isWebHostAccessibilityNode);\n}\n\nfunction isWebHostAccessibilityNode(\n value: unknown\n): value is WebHostAccessibilityNode {\n if (!value || typeof value !== \"object\") {\n return false;\n }\n const node = value as Partial<WebHostAccessibilityNode>;\n return typeof node.id === \"string\"\n && (node.parentId === undefined || typeof node.parentId === \"string\")\n && isWebHostSurfaceRect(node.rect)\n && typeof node.role === \"string\"\n && (node.label === undefined || typeof node.label === \"string\")\n && (node.hint === undefined || typeof node.hint === \"string\")\n && (\n node.liveRegion === undefined\n || node.liveRegion === \"off\"\n || node.liveRegion === \"polite\"\n || node.liveRegion === \"assertive\"\n )\n && (node.cursorAnchor === undefined || isWebHostAccessibilityPoint(node.cursorAnchor))\n && (node.isFocused === undefined || typeof node.isFocused === \"boolean\");\n}\n\nfunction isWebHostAccessibilityPoint(\n value: unknown\n): value is WebHostAccessibilityPoint {\n return Array.isArray(value)\n && value.length === 2\n && value.every((entry) => typeof entry === \"number\");\n}\n\nfunction isWebHostAccessibilityAnnouncements(\n value: unknown\n): value is WebHostAccessibilityAnnouncement[] {\n return Array.isArray(value) && value.every(isWebHostAccessibilityAnnouncement);\n}\n\nfunction isWebHostAccessibilityAnnouncement(\n value: unknown\n): value is WebHostAccessibilityAnnouncement {\n if (!value || typeof value !== \"object\") {\n return false;\n }\n const announcement = value as Partial<WebHostAccessibilityAnnouncement>;\n return typeof announcement.message === \"string\"\n && (\n announcement.politeness === \"off\"\n || announcement.politeness === \"polite\"\n || announcement.politeness === \"assertive\"\n );\n}\n\nfunction isWebHostSurfaceImages(\n value: unknown\n): value is WebHostSurfaceImage[] {\n return Array.isArray(value) && value.every(isWebHostSurfaceImage);\n}\n\nfunction isWebHostSurfaceImage(\n value: unknown\n): value is WebHostSurfaceImage {\n if (!value || typeof value !== \"object\") {\n return false;\n }\n const image = value as Partial<WebHostSurfaceImage>;\n return typeof image.id === \"string\"\n && isWebHostSurfaceImageFormat(image.format)\n && isWebHostSurfaceRect(image.bounds)\n && isWebHostSurfaceRect(image.visibleBounds)\n && isWebHostSurfaceScalingMode(image.scalingMode)\n && (image.pixelSize === undefined || isWebHostSurfaceSize(image.pixelSize))\n && (image.dataBase64 === undefined || typeof image.dataBase64 === \"string\");\n}\n\nfunction isWebHostSurfaceDamage(\n value: unknown\n): value is WebHostSurfaceDamage {\n if (!value || typeof value !== \"object\") {\n return false;\n }\n const damage = value as Partial<WebHostSurfaceDamage>;\n return Array.isArray(damage.textRows)\n && damage.textRows.every(isWebHostSurfaceDamageTextRow)\n && typeof damage.requiresFullTextRepaint === \"boolean\"\n && typeof damage.requiresFullGraphicsReplay === \"boolean\";\n}\n\nfunction isWebHostSurfaceDamageTextRow(\n value: unknown\n): value is WebHostSurfaceDamageTextRow {\n return Array.isArray(value)\n && value.length === 2\n && typeof value[0] === \"number\"\n && Array.isArray(value[1])\n && value[1].every(isWebHostSurfaceDamageRange);\n}\n\nfunction isWebHostSurfaceDamageRange(\n value: unknown\n): value is WebHostSurfaceDamageRange {\n return Array.isArray(value)\n && value.length === 2\n && typeof value[0] === \"number\"\n && typeof value[1] === \"number\";\n}\n\nfunction isWebHostSurfaceImageFormat(\n value: unknown\n): value is WebHostSurfaceImageFormat {\n return value === \"png\" || value === \"jpeg\" || value === \"gif\";\n}\n\nfunction isWebHostScrollRegions(\n value: unknown\n): value is WebHostScrollRegion[] {\n return Array.isArray(value) && value.every(isWebHostScrollRegion);\n}\n\nfunction isWebHostScrollRegion(\n value: unknown\n): value is WebHostScrollRegion {\n if (!value || typeof value !== \"object\") {\n return false;\n }\n const region = value as Partial<WebHostScrollRegion>;\n return typeof region.id === \"string\"\n && isWebHostSurfaceRect(region.rect)\n && isWebHostSurfaceSize(region.offset)\n && isWebHostSurfaceSize(region.content);\n}\n\nfunction isWebHostSurfaceRect(\n value: unknown\n): value is WebHostSurfaceRect {\n return Array.isArray(value)\n && value.length === 4\n && value.every((entry) => typeof entry === \"number\");\n}\n\nfunction isWebHostSurfaceSize(\n value: unknown\n): value is WebHostSurfaceSize {\n return Array.isArray(value)\n && value.length === 2\n && value.every((entry) => typeof entry === \"number\");\n}\n\nfunction isWebHostSurfaceScalingMode(\n value: unknown\n): value is WebHostSurfaceImage[\"scalingMode\"] {\n return value === \"stretch\" || value === \"fit\" || value === \"fill\";\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"WebHostSurfaceTransport.js","names":[],"sources":["../../src/WebHostSurfaceTransport.ts"],"sourcesContent":["import {\n encodeWebHostTerminalRenderStyleBase64,\n type WebHostTerminalStyle,\n} from \"./WebHostTerminalStyle.ts\";\n\nexport interface WebHostSurfaceStyle {\n fg?: string;\n bg?: string;\n em?: number;\n underline?: WebHostSurfaceLineStyle;\n strikethrough?: WebHostSurfaceLineStyle;\n opacity?: number;\n}\n\nexport interface WebHostSurfaceLineStyle {\n pattern: \"solid\" | \"dot\" | \"dash\" | \"dashDot\" | \"dashDotDot\" | \"double\" | \"curly\";\n color?: string;\n}\n\nexport type WebHostSurfaceCell = [\n x: number,\n text: string,\n span: number,\n styleIndex: number,\n];\n\nexport type WebHostSurfaceRect = [\n x: number,\n y: number,\n width: number,\n height: number,\n];\n\nexport type WebHostSurfaceSize = [\n width: number,\n height: number,\n];\n\nexport type WebHostAccessibilityPoint = [\n x: number,\n y: number,\n];\n\nexport type WebHostAccessibilityLiveRegion = \"off\" | \"polite\" | \"assertive\";\n\nexport interface WebHostAccessibilityNode {\n id: string;\n parentId?: string;\n rect: WebHostSurfaceRect;\n role: string;\n label?: string;\n hint?: string;\n /** omitted when false */\n hidden?: boolean;\n liveRegion?: WebHostAccessibilityLiveRegion;\n cursorAnchor?: WebHostAccessibilityPoint;\n isFocused?: boolean;\n}\n\n/**\n * One hyperlink run within a row: [x, spanWidth, linkTargetIndex]. The index\n * points into the frame's deduplicated `linkTargets` table.\n */\nexport type WebHostSurfaceLinkRun = [\n x: number,\n span: number,\n targetIndex: number,\n];\n\n/** Hyperlink runs for one row: [rowIndex, runs]. */\nexport type WebHostSurfaceLinkRow = [\n row: number,\n runs: WebHostSurfaceLinkRun[],\n];\n\nexport type WebHostFocusSemantics = \"none\" | \"automatic\" | \"activate\" | \"edit\";\n\n/**\n * The settled focus presentation for a committed frame — the same derivation\n * the Android host consumes (`prefersTextInput` gates its IME).\n */\nexport interface WebHostFocusPresentation {\n focusedIdentity?: string;\n semantics: WebHostFocusSemantics;\n prefersTextInput: boolean;\n hasFocusedRegion: boolean;\n}\n\nexport interface WebHostAccessibilityAnnouncement {\n message: string;\n politeness: WebHostAccessibilityLiveRegion;\n}\n\nexport type WebHostSurfaceImageFormat = \"png\" | \"jpeg\" | \"gif\";\n\nexport interface WebHostSurfaceImage {\n id: string;\n format: WebHostSurfaceImageFormat;\n bounds: WebHostSurfaceRect;\n visibleBounds: WebHostSurfaceRect;\n scalingMode: \"stretch\" | \"fit\" | \"fill\";\n pixelSize?: WebHostSurfaceSize;\n dataBase64?: string;\n}\n\nexport type WebHostSurfaceDamageRange = [\n start: number,\n end: number,\n];\n\nexport type WebHostSurfaceDamageTextRow = [\n row: number,\n ranges: WebHostSurfaceDamageRange[],\n];\n\nexport interface WebHostSurfaceDamage {\n textRows: WebHostSurfaceDamageTextRow[];\n requiresFullTextRepaint: boolean;\n requiresFullGraphicsReplay: boolean;\n}\n\n/**\n * Per-region scroll extent published with each frame so the host can implement\n * scroll-chaining: capture the wheel only while the region under the pointer can\n * still scroll in the wheel's direction, otherwise let it fall through to the\n * page. The host recomputes the per-direction headroom from `offset`/`content`/\n * the viewport `rect`, mirroring SwiftTUI's\n * `min(max(0, offset), max(0, content - viewport))` clamp.\n */\nexport interface WebHostScrollRegion {\n /** identity path — same key space as accessibility node ids */\n id: string;\n /** viewport rect in cells: [x, y, width, height] */\n rect: WebHostSurfaceRect;\n /** current clamped scroll offset in cells: [x, y] */\n offset: WebHostAccessibilityPoint;\n /** total content size in cells: [width, height] */\n content: WebHostSurfaceSize;\n}\n\nexport interface WebHostSurfaceFrame {\n version: 1 | 2;\n sequence?: number;\n width: number;\n height: number;\n styles: Array<WebHostSurfaceStyle | null>;\n rows: WebHostSurfaceCell[][];\n images?: WebHostSurfaceImage[];\n damage?: WebHostSurfaceDamage;\n accessibilityTree?: WebHostAccessibilityNode[];\n accessibilityAnnouncements?: WebHostAccessibilityAnnouncement[];\n scrollRegions?: WebHostScrollRegion[];\n links?: WebHostSurfaceLinkRow[];\n linkTargets?: string[];\n focusPresentation?: WebHostFocusPresentation;\n preferredGridWidth?: number;\n preferredGridHeight?: number;\n}\n\nexport type WebHostSurfaceDeltaRow = [\n row: number,\n cells: WebHostSurfaceCell[],\n];\n\nexport interface WebHostSurfaceDeltaFrame {\n version: 3;\n encoding: \"delta\";\n sequence?: number;\n width: number;\n height: number;\n styles: Array<WebHostSurfaceStyle | null>;\n deltaRows: WebHostSurfaceDeltaRow[];\n images?: WebHostSurfaceImage[];\n damage?: WebHostSurfaceDamage;\n accessibilityTree?: WebHostAccessibilityNode[];\n accessibilityAnnouncements?: WebHostAccessibilityAnnouncement[];\n scrollRegions?: WebHostScrollRegion[];\n links?: WebHostSurfaceLinkRow[];\n linkTargets?: string[];\n focusPresentation?: WebHostFocusPresentation;\n preferredGridWidth?: number;\n preferredGridHeight?: number;\n}\n\nexport interface WebHostRuntimeIssue {\n severity: \"warning\" | \"error\";\n code: string;\n message: string;\n description: string;\n identity?: string;\n source?: string;\n}\n\nexport interface WebHostFrameDiagnosticRecord {\n format: \"swift-tui-frame-diagnostics-v1\";\n header: string[];\n fields: string[];\n}\n\nexport type WebHostOutputRecord =\n | { type: \"surface\"; frame: WebHostSurfaceFrame }\n | { type: \"clipboard\"; text: string }\n | { type: \"runtimeIssue\"; issue: WebHostRuntimeIssue }\n | { type: \"frameDiagnostic\"; diagnostic: WebHostFrameDiagnosticRecord }\n | { type: \"text\"; text: string };\n\nexport interface WebHostOutputSink {\n presentSurface(frame: WebHostSurfaceFrame): void;\n writeClipboard?(text: string): void | Promise<void>;\n notifyRuntimeIssue?(issue: WebHostRuntimeIssue): void;\n recordFrameDiagnostic?(diagnostic: WebHostFrameDiagnosticRecord): void;\n writeOutput?(text: string): void;\n writeError?(text: string): void;\n}\n\nexport interface WebHostKeyInput {\n key:\n | \"return\"\n | \"space\"\n | \"tab\"\n | \"arrowLeft\"\n | \"arrowRight\"\n | \"arrowUp\"\n | \"arrowDown\"\n | \"backspace\"\n | \"escape\"\n | \"home\"\n | \"end\"\n | \"character\";\n character?: string;\n modifiers?: number;\n}\n\nexport interface WebHostMouseInput {\n kind: \"down\" | \"up\" | \"moved\" | \"dragged\" | \"scrolled\";\n x: number;\n y: number;\n button?: \"primary\" | \"middle\" | \"secondary\";\n deltaX?: number;\n deltaY?: number;\n modifiers?: number;\n}\n\nconst recordPrefix = \"\\u001E\";\nconst textEncoder = new TextEncoder();\n\n/**\n * The newest `surface` record version this runtime understands. Unknown\n * additive object keys are ignored by design (older runtimes render newer\n * frames), but a frame declaring a NEWER version than this is surfaced as an\n * error-severity runtime issue instead of silently degrading to a text\n * diagnostic — silent version skew is the failure mode this guards against\n * (F57).\n */\nexport const SUPPORTED_SURFACE_VERSION = 3;\n\nexport class WebHostOutputDecoder {\n private readonly textDecoder = new TextDecoder();\n private bufferedText = \"\";\n private lastSurfaceFrame?: WebHostSurfaceFrame;\n\n feed(\n chunk: Uint8Array\n ): WebHostOutputRecord[] {\n this.bufferedText += this.textDecoder.decode(chunk, { stream: true });\n const records: WebHostOutputRecord[] = [];\n\n while (true) {\n const newlineIndex = this.bufferedText.indexOf(\"\\n\");\n if (newlineIndex < 0) {\n break;\n }\n\n const line = this.bufferedText.slice(0, newlineIndex);\n this.bufferedText = this.bufferedText.slice(newlineIndex + 1);\n records.push(this.decodeLine(line));\n }\n\n if (this.bufferedText.length > 4096 && !this.bufferedText.startsWith(recordPrefix)) {\n records.push({ type: \"text\", text: this.bufferedText });\n this.bufferedText = \"\";\n }\n\n return records;\n }\n\n flush(): WebHostOutputRecord[] {\n if (!this.bufferedText) {\n return [];\n }\n const text = this.bufferedText;\n this.bufferedText = \"\";\n return [this.decodeLine(text)];\n }\n\n private decodeLine(\n line: string\n ): WebHostOutputRecord {\n if (line.startsWith(`${recordPrefix}clipboard:`)) {\n try {\n const record = JSON.parse(line.slice(`${recordPrefix}clipboard:`.length));\n if (isWebHostClipboardRecord(record)) {\n return { type: \"clipboard\", text: record.text };\n }\n } catch {\n // Fall through to the text path below so malformed output remains visible.\n }\n\n return { type: \"text\", text: `${line}\\n` };\n }\n\n if (line.startsWith(`${recordPrefix}runtimeIssue:`)) {\n try {\n const record = JSON.parse(line.slice(`${recordPrefix}runtimeIssue:`.length));\n if (isWebHostRuntimeIssue(record)) {\n return { type: \"runtimeIssue\", issue: record };\n }\n } catch {\n // Fall through to the text path below so malformed output remains visible.\n }\n\n return { type: \"text\", text: `${line}\\n` };\n }\n\n if (line.startsWith(`${recordPrefix}frameDiagnostic:`)) {\n try {\n const record = JSON.parse(line.slice(`${recordPrefix}frameDiagnostic:`.length));\n if (isWebHostFrameDiagnosticRecord(record)) {\n return { type: \"frameDiagnostic\", diagnostic: record };\n }\n } catch {\n // Fall through to the text path below so malformed output remains visible.\n }\n\n return { type: \"text\", text: `${line}\\n` };\n }\n\n if (!line.startsWith(`${recordPrefix}surface:`)) {\n return { type: \"text\", text: `${line}\\n` };\n }\n\n try {\n const frame = JSON.parse(line.slice(`${recordPrefix}surface:`.length));\n if (declaresNewerSurfaceVersion(frame)) {\n return {\n type: \"runtimeIssue\",\n issue: {\n severity: \"error\",\n code: \"surface.unsupportedVersion\",\n message: `SwiftTUI surface version ${frame.version} is newer than the supported ${SUPPORTED_SURFACE_VERSION}`,\n description: \"The app emitted a surface record with version \"\n + `${frame.version}, but this @swifttui/web runtime understands `\n + `versions up to ${SUPPORTED_SURFACE_VERSION}. Update @swifttui/web `\n + \"to render it.\",\n },\n };\n }\n if (isWebHostSurfaceFrame(frame)) {\n this.lastSurfaceFrame = frame;\n return { type: \"surface\", frame };\n }\n if (isWebHostSurfaceDeltaFrame(frame)) {\n const materialized = this.materializeDeltaFrame(frame);\n if (materialized) {\n this.lastSurfaceFrame = materialized;\n return { type: \"surface\", frame: materialized };\n }\n }\n } catch {\n // Fall through to the text path below so malformed output remains visible.\n }\n\n return { type: \"text\", text: `${line}\\n` };\n }\n\n private materializeDeltaFrame(\n frame: WebHostSurfaceDeltaFrame\n ): WebHostSurfaceFrame | undefined {\n const baseline = this.lastSurfaceFrame;\n if (!baseline || baseline.width !== frame.width || baseline.height !== frame.height) {\n return undefined;\n }\n\n const rows = baseline.rows.slice();\n for (const [row, cells] of frame.deltaRows) {\n if (!Number.isSafeInteger(row) || row < 0 || row >= frame.height) {\n return undefined;\n }\n rows[row] = cells;\n }\n\n return {\n version: baseline.version,\n sequence: frame.sequence,\n width: frame.width,\n height: frame.height,\n styles: frame.styles,\n rows,\n images: frame.images,\n damage: frame.damage,\n accessibilityTree: frame.accessibilityTree,\n accessibilityAnnouncements: frame.accessibilityAnnouncements,\n scrollRegions: frame.scrollRegions,\n links: frame.links,\n linkTargets: frame.linkTargets,\n focusPresentation: frame.focusPresentation,\n preferredGridWidth: frame.preferredGridWidth,\n preferredGridHeight: frame.preferredGridHeight,\n };\n }\n}\n\nfunction declaresNewerSurfaceVersion(\n value: unknown\n): value is { version: number } {\n if (!value || typeof value !== \"object\") {\n return false;\n }\n const version = (value as { version?: unknown }).version;\n return typeof version === \"number\"\n && Number.isSafeInteger(version)\n && version > SUPPORTED_SURFACE_VERSION;\n}\n\nfunction isWebHostClipboardRecord(\n value: unknown\n): value is { text: string } {\n return !!value && typeof value === \"object\" && typeof (value as { text?: unknown }).text === \"string\";\n}\n\nfunction isWebHostRuntimeIssue(\n value: unknown\n): value is WebHostRuntimeIssue {\n if (!value || typeof value !== \"object\") {\n return false;\n }\n const record = value as Partial<WebHostRuntimeIssue>;\n return (record.severity === \"warning\" || record.severity === \"error\")\n && typeof record.code === \"string\"\n && typeof record.message === \"string\"\n && typeof record.description === \"string\"\n && (record.identity === undefined || typeof record.identity === \"string\")\n && (record.source === undefined || typeof record.source === \"string\");\n}\n\nfunction isWebHostFrameDiagnosticRecord(\n value: unknown\n): value is WebHostFrameDiagnosticRecord {\n if (!value || typeof value !== \"object\") {\n return false;\n }\n const record = value as Partial<WebHostFrameDiagnosticRecord>;\n return record.format === \"swift-tui-frame-diagnostics-v1\"\n && Array.isArray(record.header)\n && record.header.every((field) => typeof field === \"string\")\n && Array.isArray(record.fields)\n && record.fields.every((field) => typeof field === \"string\");\n}\n\nexport function encodeResizeControlMessage(\n columns: number,\n rows: number,\n cellWidth?: number,\n cellHeight?: number\n): Uint8Array {\n const normalizedColumns = Math.max(1, Math.round(columns));\n const normalizedRows = Math.max(1, Math.round(rows));\n if (cellWidth && cellHeight) {\n return textEncoder.encode(\n `${recordPrefix}resize:${normalizedColumns}:${normalizedRows}:${Math.max(1, Math.round(cellWidth))}:${Math.max(1, Math.round(cellHeight))}\\n`\n );\n }\n\n return textEncoder.encode(`${recordPrefix}resize:${normalizedColumns}:${normalizedRows}\\n`);\n}\n\nexport function encodeRenderStyleControlMessage(\n style: WebHostTerminalStyle\n): Uint8Array {\n const encoded = encodeWebHostTerminalRenderStyleBase64(style);\n return textEncoder.encode(`${recordPrefix}style:${encoded}\\n`);\n}\n\nexport function encodeKeyInputMessage(\n input: WebHostKeyInput\n): Uint8Array {\n const modifiers = Math.max(0, Math.round(input.modifiers ?? 0));\n if (input.key === \"character\") {\n return textEncoder.encode(\n `${recordPrefix}key:character:${encodeURIComponent(input.character ?? \"\")}:${modifiers}\\n`\n );\n }\n return textEncoder.encode(`${recordPrefix}key:${input.key}:${modifiers}\\n`);\n}\n\nexport function encodePasteInputMessage(\n text: string\n): Uint8Array {\n return textEncoder.encode(`${recordPrefix}paste:${encodeURIComponent(text)}\\n`);\n}\n\nexport function encodeMouseInputMessage(\n input: WebHostMouseInput\n): Uint8Array {\n return textEncoder.encode(\n recordPrefix + [\n \"mouse\",\n input.kind,\n formatCellCoordinate(input.x),\n formatCellCoordinate(input.y),\n input.button ?? \"none\",\n Math.round(input.deltaX ?? 0),\n Math.round(input.deltaY ?? 0),\n Math.max(0, Math.round(input.modifiers ?? 0)),\n ].join(\":\") + \"\\n\"\n );\n}\n\nfunction formatCellCoordinate(\n value: number\n): string {\n return Number.isFinite(value) ? String(value) : \"0\";\n}\n\nfunction isWebHostSurfaceFrame(\n value: unknown\n): value is WebHostSurfaceFrame {\n if (!value || typeof value !== \"object\") {\n return false;\n }\n const frame = value as Partial<WebHostSurfaceFrame>;\n return (frame.version === 1 || frame.version === 2)\n && (\n frame.sequence === undefined\n || (Number.isSafeInteger(frame.sequence) && frame.sequence >= 0)\n )\n && typeof frame.width === \"number\"\n && typeof frame.height === \"number\"\n && Array.isArray(frame.styles)\n && Array.isArray(frame.rows)\n && frame.rows.every(isWebHostSurfaceRow)\n && (frame.images === undefined || isWebHostSurfaceImages(frame.images))\n && (frame.damage === undefined || isWebHostSurfaceDamage(frame.damage))\n && (\n frame.accessibilityTree === undefined\n || isWebHostAccessibilityNodes(frame.accessibilityTree)\n )\n && (\n frame.accessibilityAnnouncements === undefined\n || isWebHostAccessibilityAnnouncements(frame.accessibilityAnnouncements)\n )\n && (frame.scrollRegions === undefined || isWebHostScrollRegions(frame.scrollRegions))\n && hasValidAdditiveFrameFields(frame);\n}\n\nfunction isWebHostSurfaceDeltaFrame(\n value: unknown\n): value is WebHostSurfaceDeltaFrame {\n if (!value || typeof value !== \"object\") {\n return false;\n }\n const frame = value as Partial<WebHostSurfaceDeltaFrame>;\n return frame.version === 3\n && frame.encoding === \"delta\"\n && (\n frame.sequence === undefined\n || (Number.isSafeInteger(frame.sequence) && frame.sequence >= 0)\n )\n && typeof frame.width === \"number\"\n && typeof frame.height === \"number\"\n && Array.isArray(frame.styles)\n && Array.isArray(frame.deltaRows)\n && frame.deltaRows.every(isWebHostSurfaceDeltaRow)\n && (frame.images === undefined || isWebHostSurfaceImages(frame.images))\n && (frame.damage === undefined || isWebHostSurfaceDamage(frame.damage))\n && (\n frame.accessibilityTree === undefined\n || isWebHostAccessibilityNodes(frame.accessibilityTree)\n )\n && (\n frame.accessibilityAnnouncements === undefined\n || isWebHostAccessibilityAnnouncements(frame.accessibilityAnnouncements)\n )\n && (frame.scrollRegions === undefined || isWebHostScrollRegions(frame.scrollRegions))\n && hasValidAdditiveFrameFields(frame);\n}\n\n/**\n * The F19 additive fields shared by the full and delta record shapes. Absent\n * means \"feature not present\" — servers older than the field omit it.\n */\nfunction hasValidAdditiveFrameFields(\n frame: Partial<WebHostSurfaceFrame> | Partial<WebHostSurfaceDeltaFrame>\n): boolean {\n return (frame.links === undefined || isWebHostSurfaceLinks(frame.links))\n && (frame.linkTargets === undefined || isWebHostSurfaceLinkTargets(frame.linkTargets))\n && (\n frame.focusPresentation === undefined\n || isWebHostFocusPresentation(frame.focusPresentation)\n )\n && (\n frame.preferredGridWidth === undefined\n || (Number.isSafeInteger(frame.preferredGridWidth) && frame.preferredGridWidth >= 0)\n )\n && (\n frame.preferredGridHeight === undefined\n || (Number.isSafeInteger(frame.preferredGridHeight) && frame.preferredGridHeight >= 0)\n );\n}\n\nfunction isWebHostSurfaceLinks(\n value: unknown\n): value is WebHostSurfaceLinkRow[] {\n return Array.isArray(value) && value.every(isWebHostSurfaceLinkRow);\n}\n\nfunction isWebHostSurfaceLinkRow(\n value: unknown\n): value is WebHostSurfaceLinkRow {\n return Array.isArray(value)\n && value.length === 2\n && Number.isSafeInteger(value[0])\n && value[0] >= 0\n && Array.isArray(value[1])\n && value[1].every(isWebHostSurfaceLinkRun);\n}\n\nfunction isWebHostSurfaceLinkRun(\n value: unknown\n): value is WebHostSurfaceLinkRun {\n if (!Array.isArray(value) || value.length !== 3) {\n return false;\n }\n const [x, span, targetIndex] = value as number[];\n return Number.isSafeInteger(x)\n && x >= 0\n && Number.isSafeInteger(span)\n && span >= 1\n && Number.isSafeInteger(targetIndex)\n && targetIndex >= 0;\n}\n\nfunction isWebHostSurfaceLinkTargets(\n value: unknown\n): value is string[] {\n return Array.isArray(value) && value.every((entry) => typeof entry === \"string\");\n}\n\nfunction isWebHostFocusPresentation(\n value: unknown\n): value is WebHostFocusPresentation {\n if (!value || typeof value !== \"object\") {\n return false;\n }\n const presentation = value as Partial<WebHostFocusPresentation>;\n return (\n presentation.focusedIdentity === undefined\n || typeof presentation.focusedIdentity === \"string\"\n )\n && (\n presentation.semantics === \"none\"\n || presentation.semantics === \"automatic\"\n || presentation.semantics === \"activate\"\n || presentation.semantics === \"edit\"\n )\n && typeof presentation.prefersTextInput === \"boolean\"\n && typeof presentation.hasFocusedRegion === \"boolean\";\n}\n\nfunction isWebHostSurfaceDeltaRow(\n value: unknown\n): value is WebHostSurfaceDeltaRow {\n return Array.isArray(value)\n && value.length === 2\n && Number.isSafeInteger(value[0])\n && value[0] >= 0\n && isWebHostSurfaceRow(value[1]);\n}\n\nfunction isWebHostSurfaceRow(\n value: unknown\n): value is WebHostSurfaceCell[] {\n return Array.isArray(value) && value.every(isWebHostSurfaceCell);\n}\n\nfunction isWebHostSurfaceCell(\n value: unknown\n): value is WebHostSurfaceCell {\n return Array.isArray(value)\n && value.length === 4\n && Number.isSafeInteger(value[0])\n && value[0] >= 0\n && typeof value[1] === \"string\"\n && Number.isSafeInteger(value[2])\n && value[2] >= 1\n && Number.isSafeInteger(value[3])\n && value[3] >= 0;\n}\n\nfunction isWebHostAccessibilityNodes(\n value: unknown\n): value is WebHostAccessibilityNode[] {\n return Array.isArray(value) && value.every(isWebHostAccessibilityNode);\n}\n\nfunction isWebHostAccessibilityNode(\n value: unknown\n): value is WebHostAccessibilityNode {\n if (!value || typeof value !== \"object\") {\n return false;\n }\n const node = value as Partial<WebHostAccessibilityNode>;\n return typeof node.id === \"string\"\n && (node.parentId === undefined || typeof node.parentId === \"string\")\n && isWebHostSurfaceRect(node.rect)\n && typeof node.role === \"string\"\n && (node.label === undefined || typeof node.label === \"string\")\n && (node.hint === undefined || typeof node.hint === \"string\")\n && (node.hidden === undefined || typeof node.hidden === \"boolean\")\n && (\n node.liveRegion === undefined\n || node.liveRegion === \"off\"\n || node.liveRegion === \"polite\"\n || node.liveRegion === \"assertive\"\n )\n && (node.cursorAnchor === undefined || isWebHostAccessibilityPoint(node.cursorAnchor))\n && (node.isFocused === undefined || typeof node.isFocused === \"boolean\");\n}\n\nfunction isWebHostAccessibilityPoint(\n value: unknown\n): value is WebHostAccessibilityPoint {\n return Array.isArray(value)\n && value.length === 2\n && value.every((entry) => typeof entry === \"number\");\n}\n\nfunction isWebHostAccessibilityAnnouncements(\n value: unknown\n): value is WebHostAccessibilityAnnouncement[] {\n return Array.isArray(value) && value.every(isWebHostAccessibilityAnnouncement);\n}\n\nfunction isWebHostAccessibilityAnnouncement(\n value: unknown\n): value is WebHostAccessibilityAnnouncement {\n if (!value || typeof value !== \"object\") {\n return false;\n }\n const announcement = value as Partial<WebHostAccessibilityAnnouncement>;\n return typeof announcement.message === \"string\"\n && (\n announcement.politeness === \"off\"\n || announcement.politeness === \"polite\"\n || announcement.politeness === \"assertive\"\n );\n}\n\nfunction isWebHostSurfaceImages(\n value: unknown\n): value is WebHostSurfaceImage[] {\n return Array.isArray(value) && value.every(isWebHostSurfaceImage);\n}\n\nfunction isWebHostSurfaceImage(\n value: unknown\n): value is WebHostSurfaceImage {\n if (!value || typeof value !== \"object\") {\n return false;\n }\n const image = value as Partial<WebHostSurfaceImage>;\n return typeof image.id === \"string\"\n && isWebHostSurfaceImageFormat(image.format)\n && isWebHostSurfaceRect(image.bounds)\n && isWebHostSurfaceRect(image.visibleBounds)\n && isWebHostSurfaceScalingMode(image.scalingMode)\n && (image.pixelSize === undefined || isWebHostSurfaceSize(image.pixelSize))\n && (image.dataBase64 === undefined || typeof image.dataBase64 === \"string\");\n}\n\nfunction isWebHostSurfaceDamage(\n value: unknown\n): value is WebHostSurfaceDamage {\n if (!value || typeof value !== \"object\") {\n return false;\n }\n const damage = value as Partial<WebHostSurfaceDamage>;\n return Array.isArray(damage.textRows)\n && damage.textRows.every(isWebHostSurfaceDamageTextRow)\n && typeof damage.requiresFullTextRepaint === \"boolean\"\n && typeof damage.requiresFullGraphicsReplay === \"boolean\";\n}\n\nfunction isWebHostSurfaceDamageTextRow(\n value: unknown\n): value is WebHostSurfaceDamageTextRow {\n return Array.isArray(value)\n && value.length === 2\n && typeof value[0] === \"number\"\n && Array.isArray(value[1])\n && value[1].every(isWebHostSurfaceDamageRange);\n}\n\nfunction isWebHostSurfaceDamageRange(\n value: unknown\n): value is WebHostSurfaceDamageRange {\n return Array.isArray(value)\n && value.length === 2\n && typeof value[0] === \"number\"\n && typeof value[1] === \"number\";\n}\n\nfunction isWebHostSurfaceImageFormat(\n value: unknown\n): value is WebHostSurfaceImageFormat {\n return value === \"png\" || value === \"jpeg\" || value === \"gif\";\n}\n\nfunction isWebHostScrollRegions(\n value: unknown\n): value is WebHostScrollRegion[] {\n return Array.isArray(value) && value.every(isWebHostScrollRegion);\n}\n\nfunction isWebHostScrollRegion(\n value: unknown\n): value is WebHostScrollRegion {\n if (!value || typeof value !== \"object\") {\n return false;\n }\n const region = value as Partial<WebHostScrollRegion>;\n return typeof region.id === \"string\"\n && isWebHostSurfaceRect(region.rect)\n && isWebHostSurfaceSize(region.offset)\n && isWebHostSurfaceSize(region.content);\n}\n\nfunction isWebHostSurfaceRect(\n value: unknown\n): value is WebHostSurfaceRect {\n return Array.isArray(value)\n && value.length === 4\n && value.every((entry) => typeof entry === \"number\");\n}\n\nfunction isWebHostSurfaceSize(\n value: unknown\n): value is WebHostSurfaceSize {\n return Array.isArray(value)\n && value.length === 2\n && value.every((entry) => typeof entry === \"number\");\n}\n\nfunction isWebHostSurfaceScalingMode(\n value: unknown\n): value is WebHostSurfaceImage[\"scalingMode\"] {\n return value === \"stretch\" || value === \"fit\" || value === \"fill\";\n}\n"],"mappings":"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"}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { WebHostTerminalStyle } from "./WebHostTerminalStyle.js";
|
|
2
2
|
import { WebHostOutputSink } from "./WebHostSurfaceTransport.js";
|
|
3
3
|
import { WebHostSceneBridge } from "./WebHostSceneRuntime.js";
|
|
4
|
-
|
|
5
4
|
//#region src/WebSocketSceneBridge.d.ts
|
|
6
5
|
interface WebSocketSceneBridgeOptions {
|
|
7
6
|
sceneId: string;
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { WebHostTerminalStyle } from "../WebHostTerminalStyle.js";
|
|
2
2
|
import { WebHostOutputSink, encodeRenderStyleControlMessage, encodeResizeControlMessage } from "../WebHostSurfaceTransport.js";
|
|
3
3
|
import { StdIOPipe } from "./StdIOPipe.js";
|
|
4
|
-
|
|
5
4
|
//#region src/wasi/BrowserWASIBridge.d.ts
|
|
6
5
|
interface BrowserWASIBridgeOptions {
|
|
7
6
|
sceneId: string;
|
|
@@ -29,5 +28,5 @@ declare class BrowserWASIBridge {
|
|
|
29
28
|
dispose(): void;
|
|
30
29
|
}
|
|
31
30
|
//#endregion
|
|
32
|
-
export { BrowserWASIBridge, BrowserWASIBridgeOptions, BrowserWASIOutputSink };
|
|
31
|
+
export { BrowserWASIBridge, BrowserWASIBridgeOptions, BrowserWASIOutputSink, encodeRenderStyleControlMessage, encodeResizeControlMessage };
|
|
33
32
|
//# sourceMappingURL=BrowserWASIBridge.d.ts.map
|
|
@@ -53,7 +53,7 @@ var SharedInputQueueReader = class {
|
|
|
53
53
|
}
|
|
54
54
|
}
|
|
55
55
|
readAvailable(maxBytes) {
|
|
56
|
-
if (!Number.isInteger(maxBytes) || maxBytes <= 0) return new Uint8Array();
|
|
56
|
+
if (!Number.isInteger(maxBytes) || maxBytes <= 0) return /* @__PURE__ */ new Uint8Array();
|
|
57
57
|
const length = this.queue.data.length;
|
|
58
58
|
const readIndex = Atomics.load(this.queue.control, 0);
|
|
59
59
|
const availableBytes = ringUsed(readIndex, Atomics.load(this.queue.control, 1), length);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SharedInputQueue.js","names":[],"sources":["../../../src/wasi/SharedInputQueue.ts"],"sourcesContent":["const controlSlots = 3;\n\nconst enum ControlSlot {\n readIndex = 0,\n writeIndex = 1,\n closed = 2,\n}\n\nexport const sharedInputQueueDefaultCapacity = 64 * 1024;\n\nexport interface SharedInputQueueBuffers {\n readonly controlBuffer: SharedArrayBuffer;\n readonly dataBuffer: SharedArrayBuffer;\n}\n\nexport type SharedInputReadiness = \"readable\" | \"closed\" | \"timedOut\";\n\ninterface SharedInputQueueState {\n readonly control: Int32Array;\n readonly data: Uint8Array;\n}\n\nexport function createSharedInputQueue(\n capacity: number = sharedInputQueueDefaultCapacity\n): SharedInputQueueBuffers {\n if (typeof SharedArrayBuffer === \"undefined\") {\n throw new Error(\n \"SharedArrayBuffer is unavailable. Serve the app with COOP/COEP headers so browser WASI stdin can stay live.\"\n );\n }\n\n if (!Number.isInteger(capacity) || capacity <= 0) {\n throw new Error(`Shared input queue capacity must be a positive integer, received ${capacity}.`);\n }\n\n return {\n controlBuffer: new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT * controlSlots),\n dataBuffer: new SharedArrayBuffer(capacity),\n };\n}\n\nexport function hydrateSharedInputQueue(\n buffers: SharedInputQueueBuffers\n): SharedInputQueueState {\n return {\n control: new Int32Array(buffers.controlBuffer),\n data: new Uint8Array(buffers.dataBuffer),\n };\n}\n\nexport class SharedInputQueueWriter {\n private readonly queue: SharedInputQueueState;\n\n constructor(buffers: SharedInputQueueBuffers) {\n this.queue = hydrateSharedInputQueue(buffers);\n }\n\n write(chunk: Uint8Array | string): void {\n if (Atomics.load(this.queue.control, ControlSlot.closed) !== 0) {\n return;\n }\n\n const bytes = normalizeChunk(chunk);\n if (bytes.length == 0) {\n return;\n }\n\n const length = this.queue.data.length;\n const readIndex = Atomics.load(this.queue.control, ControlSlot.readIndex);\n const writeIndex = Atomics.load(this.queue.control, ControlSlot.writeIndex);\n const usedCapacity = ringUsed(readIndex, writeIndex, length);\n const availableCapacity = length - usedCapacity;\n\n if (bytes.length > availableCapacity) {\n throw new Error(\n `Shared input queue overflow: cannot enqueue ${bytes.length} byte(s) into ${availableCapacity} byte(s) of free space.`\n );\n }\n\n writeToRingBuffer(this.queue.data, bytes, writeIndex);\n Atomics.store(\n this.queue.control,\n ControlSlot.writeIndex,\n ringAdvance(writeIndex, bytes.length, length)\n );\n Atomics.notify(this.queue.control, ControlSlot.writeIndex);\n }\n\n close(): void {\n Atomics.store(this.queue.control, ControlSlot.closed, 1);\n Atomics.notify(this.queue.control, ControlSlot.writeIndex);\n }\n}\n\nexport class SharedInputQueueReader {\n private readonly queue: SharedInputQueueState;\n\n constructor(buffers: SharedInputQueueBuffers) {\n this.queue = hydrateSharedInputQueue(buffers);\n }\n\n read(maxBytes: number): Uint8Array | undefined {\n while (true) {\n const next = this.readAvailable(maxBytes);\n if (next) {\n return next;\n }\n\n if (this.isClosed()) {\n return undefined;\n }\n\n const writeIndex = Atomics.load(this.queue.control, ControlSlot.writeIndex);\n Atomics.wait(this.queue.control, ControlSlot.writeIndex, writeIndex);\n }\n }\n\n readAvailable(maxBytes: number): Uint8Array | undefined {\n if (!Number.isInteger(maxBytes) || maxBytes <= 0) {\n return new Uint8Array();\n }\n\n const length = this.queue.data.length;\n const readIndex = Atomics.load(this.queue.control, ControlSlot.readIndex);\n const writeIndex = Atomics.load(this.queue.control, ControlSlot.writeIndex);\n const availableBytes = ringUsed(readIndex, writeIndex, length);\n\n if (availableBytes <= 0) {\n return undefined;\n }\n\n const byteCount = Math.min(maxBytes, availableBytes);\n const chunk = readFromRingBuffer(this.queue.data, readIndex, byteCount);\n Atomics.store(\n this.queue.control,\n ControlSlot.readIndex,\n ringAdvance(readIndex, byteCount, length)\n );\n return chunk;\n }\n\n availableBytes(): number {\n const readIndex = Atomics.load(this.queue.control, ControlSlot.readIndex);\n const writeIndex = Atomics.load(this.queue.control, ControlSlot.writeIndex);\n return ringUsed(readIndex, writeIndex, this.queue.data.length);\n }\n\n waitForReadable(\n timeoutMilliseconds?: number\n ): SharedInputReadiness {\n while (true) {\n if (this.availableBytes() > 0) {\n return \"readable\";\n }\n if (this.isClosed()) {\n return \"closed\";\n }\n\n const writeIndex = Atomics.load(this.queue.control, ControlSlot.writeIndex);\n const result = Atomics.wait(\n this.queue.control,\n ControlSlot.writeIndex,\n writeIndex,\n timeoutMilliseconds\n );\n if (result === \"timed-out\") {\n return \"timedOut\";\n }\n }\n }\n\n isClosed(): boolean {\n return Atomics.load(this.queue.control, ControlSlot.closed) !== 0;\n }\n}\n\nfunction normalizeChunk(\n chunk: Uint8Array | string\n): Uint8Array {\n return typeof chunk == \"string\" ? new TextEncoder().encode(chunk) : new Uint8Array(chunk);\n}\n\n// The read/write cursors are kept in the half-open range [0, 2 * length) — the\n// classic \"two indices mod 2N\" ring buffer. Bounding both cursors keeps them\n// from growing without limit and overflowing Int32 across long sessions, while\n// still distinguishing a full queue (used == length) from an empty one\n// (used == 0). The data-buffer offset for either cursor is cursor % length.\nfunction ringUsed(\n readIndex: number,\n writeIndex: number,\n length: number\n): number {\n const span = 2 * length;\n return ((writeIndex - readIndex) % span + span) % span;\n}\n\nfunction ringAdvance(\n index: number,\n delta: number,\n length: number\n): number {\n return (index + delta) % (2 * length);\n}\n\nfunction writeToRingBuffer(\n buffer: Uint8Array,\n chunk: Uint8Array,\n startIndex: number\n): void {\n const offset = startIndex % buffer.length;\n const firstSegmentLength = Math.min(chunk.length, buffer.length - offset);\n buffer.set(chunk.subarray(0, firstSegmentLength), offset);\n if (firstSegmentLength < chunk.length) {\n buffer.set(chunk.subarray(firstSegmentLength), 0);\n }\n}\n\nfunction readFromRingBuffer(\n buffer: Uint8Array,\n startIndex: number,\n byteCount: number\n): Uint8Array {\n const chunk = new Uint8Array(byteCount);\n const offset = startIndex % buffer.length;\n const firstSegmentLength = Math.min(byteCount, buffer.length - offset);\n chunk.set(buffer.subarray(offset, offset + firstSegmentLength), 0);\n if (firstSegmentLength < byteCount) {\n chunk.set(buffer.subarray(0, byteCount - firstSegmentLength), firstSegmentLength);\n }\n return chunk;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"SharedInputQueue.js","names":[],"sources":["../../../src/wasi/SharedInputQueue.ts"],"sourcesContent":["const controlSlots = 3;\n\nconst enum ControlSlot {\n readIndex = 0,\n writeIndex = 1,\n closed = 2,\n}\n\nexport const sharedInputQueueDefaultCapacity = 64 * 1024;\n\nexport interface SharedInputQueueBuffers {\n readonly controlBuffer: SharedArrayBuffer;\n readonly dataBuffer: SharedArrayBuffer;\n}\n\nexport type SharedInputReadiness = \"readable\" | \"closed\" | \"timedOut\";\n\ninterface SharedInputQueueState {\n readonly control: Int32Array;\n readonly data: Uint8Array;\n}\n\nexport function createSharedInputQueue(\n capacity: number = sharedInputQueueDefaultCapacity\n): SharedInputQueueBuffers {\n if (typeof SharedArrayBuffer === \"undefined\") {\n throw new Error(\n \"SharedArrayBuffer is unavailable. Serve the app with COOP/COEP headers so browser WASI stdin can stay live.\"\n );\n }\n\n if (!Number.isInteger(capacity) || capacity <= 0) {\n throw new Error(`Shared input queue capacity must be a positive integer, received ${capacity}.`);\n }\n\n return {\n controlBuffer: new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT * controlSlots),\n dataBuffer: new SharedArrayBuffer(capacity),\n };\n}\n\nexport function hydrateSharedInputQueue(\n buffers: SharedInputQueueBuffers\n): SharedInputQueueState {\n return {\n control: new Int32Array(buffers.controlBuffer),\n data: new Uint8Array(buffers.dataBuffer),\n };\n}\n\nexport class SharedInputQueueWriter {\n private readonly queue: SharedInputQueueState;\n\n constructor(buffers: SharedInputQueueBuffers) {\n this.queue = hydrateSharedInputQueue(buffers);\n }\n\n write(chunk: Uint8Array | string): void {\n if (Atomics.load(this.queue.control, ControlSlot.closed) !== 0) {\n return;\n }\n\n const bytes = normalizeChunk(chunk);\n if (bytes.length == 0) {\n return;\n }\n\n const length = this.queue.data.length;\n const readIndex = Atomics.load(this.queue.control, ControlSlot.readIndex);\n const writeIndex = Atomics.load(this.queue.control, ControlSlot.writeIndex);\n const usedCapacity = ringUsed(readIndex, writeIndex, length);\n const availableCapacity = length - usedCapacity;\n\n if (bytes.length > availableCapacity) {\n throw new Error(\n `Shared input queue overflow: cannot enqueue ${bytes.length} byte(s) into ${availableCapacity} byte(s) of free space.`\n );\n }\n\n writeToRingBuffer(this.queue.data, bytes, writeIndex);\n Atomics.store(\n this.queue.control,\n ControlSlot.writeIndex,\n ringAdvance(writeIndex, bytes.length, length)\n );\n Atomics.notify(this.queue.control, ControlSlot.writeIndex);\n }\n\n close(): void {\n Atomics.store(this.queue.control, ControlSlot.closed, 1);\n Atomics.notify(this.queue.control, ControlSlot.writeIndex);\n }\n}\n\nexport class SharedInputQueueReader {\n private readonly queue: SharedInputQueueState;\n\n constructor(buffers: SharedInputQueueBuffers) {\n this.queue = hydrateSharedInputQueue(buffers);\n }\n\n read(maxBytes: number): Uint8Array | undefined {\n while (true) {\n const next = this.readAvailable(maxBytes);\n if (next) {\n return next;\n }\n\n if (this.isClosed()) {\n return undefined;\n }\n\n const writeIndex = Atomics.load(this.queue.control, ControlSlot.writeIndex);\n Atomics.wait(this.queue.control, ControlSlot.writeIndex, writeIndex);\n }\n }\n\n readAvailable(maxBytes: number): Uint8Array | undefined {\n if (!Number.isInteger(maxBytes) || maxBytes <= 0) {\n return new Uint8Array();\n }\n\n const length = this.queue.data.length;\n const readIndex = Atomics.load(this.queue.control, ControlSlot.readIndex);\n const writeIndex = Atomics.load(this.queue.control, ControlSlot.writeIndex);\n const availableBytes = ringUsed(readIndex, writeIndex, length);\n\n if (availableBytes <= 0) {\n return undefined;\n }\n\n const byteCount = Math.min(maxBytes, availableBytes);\n const chunk = readFromRingBuffer(this.queue.data, readIndex, byteCount);\n Atomics.store(\n this.queue.control,\n ControlSlot.readIndex,\n ringAdvance(readIndex, byteCount, length)\n );\n return chunk;\n }\n\n availableBytes(): number {\n const readIndex = Atomics.load(this.queue.control, ControlSlot.readIndex);\n const writeIndex = Atomics.load(this.queue.control, ControlSlot.writeIndex);\n return ringUsed(readIndex, writeIndex, this.queue.data.length);\n }\n\n waitForReadable(\n timeoutMilliseconds?: number\n ): SharedInputReadiness {\n while (true) {\n if (this.availableBytes() > 0) {\n return \"readable\";\n }\n if (this.isClosed()) {\n return \"closed\";\n }\n\n const writeIndex = Atomics.load(this.queue.control, ControlSlot.writeIndex);\n const result = Atomics.wait(\n this.queue.control,\n ControlSlot.writeIndex,\n writeIndex,\n timeoutMilliseconds\n );\n if (result === \"timed-out\") {\n return \"timedOut\";\n }\n }\n }\n\n isClosed(): boolean {\n return Atomics.load(this.queue.control, ControlSlot.closed) !== 0;\n }\n}\n\nfunction normalizeChunk(\n chunk: Uint8Array | string\n): Uint8Array {\n return typeof chunk == \"string\" ? new TextEncoder().encode(chunk) : new Uint8Array(chunk);\n}\n\n// The read/write cursors are kept in the half-open range [0, 2 * length) — the\n// classic \"two indices mod 2N\" ring buffer. Bounding both cursors keeps them\n// from growing without limit and overflowing Int32 across long sessions, while\n// still distinguishing a full queue (used == length) from an empty one\n// (used == 0). The data-buffer offset for either cursor is cursor % length.\nfunction ringUsed(\n readIndex: number,\n writeIndex: number,\n length: number\n): number {\n const span = 2 * length;\n return ((writeIndex - readIndex) % span + span) % span;\n}\n\nfunction ringAdvance(\n index: number,\n delta: number,\n length: number\n): number {\n return (index + delta) % (2 * length);\n}\n\nfunction writeToRingBuffer(\n buffer: Uint8Array,\n chunk: Uint8Array,\n startIndex: number\n): void {\n const offset = startIndex % buffer.length;\n const firstSegmentLength = Math.min(chunk.length, buffer.length - offset);\n buffer.set(chunk.subarray(0, firstSegmentLength), offset);\n if (firstSegmentLength < chunk.length) {\n buffer.set(chunk.subarray(firstSegmentLength), 0);\n }\n}\n\nfunction readFromRingBuffer(\n buffer: Uint8Array,\n startIndex: number,\n byteCount: number\n): Uint8Array {\n const chunk = new Uint8Array(byteCount);\n const offset = startIndex % buffer.length;\n const firstSegmentLength = Math.min(byteCount, buffer.length - offset);\n chunk.set(buffer.subarray(offset, offset + firstSegmentLength), 0);\n if (firstSegmentLength < byteCount) {\n chunk.set(buffer.subarray(0, byteCount - firstSegmentLength), firstSegmentLength);\n }\n return chunk;\n}\n"],"mappings":"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"}
|
|
@@ -39,11 +39,11 @@ var BlockingInputFileDescriptor = class extends Fd {
|
|
|
39
39
|
};
|
|
40
40
|
if (this.reader.isClosed()) return {
|
|
41
41
|
ret: wasi.ERRNO_SUCCESS,
|
|
42
|
-
data: new Uint8Array()
|
|
42
|
+
data: /* @__PURE__ */ new Uint8Array()
|
|
43
43
|
};
|
|
44
44
|
return {
|
|
45
45
|
ret: wasi.ERRNO_AGAIN,
|
|
46
|
-
data: new Uint8Array()
|
|
46
|
+
data: /* @__PURE__ */ new Uint8Array()
|
|
47
47
|
};
|
|
48
48
|
}
|
|
49
49
|
availableBytes() {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WasmSceneWorker.js","names":[],"sources":["../../../src/wasi/WasmSceneWorker.ts"],"sourcesContent":["import { ConsoleStdout, Fd, WASI, wasi } from \"@bjorn3/browser_wasi_shim\";\nimport {\n SharedInputQueueReader,\n type SharedInputQueueBuffers,\n} from \"./SharedInputQueue.ts\";\nimport { WasiPollScheduler } from \"./WasiPollScheduler.ts\";\n\nexport interface StartWasmSceneWorkerMessage {\n type: \"start\";\n wasmURL: string;\n environment: Record<string, string>;\n inputQueue: SharedInputQueueBuffers;\n}\n\nexport interface OutputWasmSceneWorkerMessage {\n type: \"stdout\" | \"stderr\";\n chunk: Uint8Array;\n}\n\nexport interface ExitWasmSceneWorkerMessage {\n type: \"exit\";\n code: number;\n}\n\nexport interface ErrorWasmSceneWorkerMessage {\n type: \"error\";\n message: string;\n}\n\nexport type WasmSceneWorkerMessage = StartWasmSceneWorkerMessage;\nexport type WasmSceneWorkerResponse =\n | OutputWasmSceneWorkerMessage\n | ExitWasmSceneWorkerMessage\n | ErrorWasmSceneWorkerMessage;\n\nexport function startWasmSceneWorker(): void {\n globalThis.addEventListener(\"message\", (event: MessageEvent<WasmSceneWorkerMessage>) => {\n if (event.data.type !== \"start\") {\n return;\n }\n\n void startWasmScene(event.data);\n });\n}\n\nclass BlockingInputFileDescriptor extends Fd {\n private readonly reader: SharedInputQueueReader;\n private readonly fdstat = (() => {\n const fdstat = new wasi.Fdstat(wasi.FILETYPE_CHARACTER_DEVICE, 0);\n fdstat.fs_rights_base = BigInt(wasi.RIGHTS_FD_READ);\n return fdstat;\n })();\n\n constructor(inputQueue: SharedInputQueueBuffers) {\n super();\n this.reader = new SharedInputQueueReader(inputQueue);\n }\n\n override fd_fdstat_get(): { ret: number; fdstat: wasi.Fdstat } {\n return {\n ret: wasi.ERRNO_SUCCESS,\n fdstat: this.fdstat,\n };\n }\n\n override fd_filestat_get(): { ret: number; filestat: wasi.Filestat } {\n return {\n ret: wasi.ERRNO_SUCCESS,\n filestat: new wasi.Filestat(0n, wasi.FILETYPE_CHARACTER_DEVICE, 0n),\n };\n }\n\n override fd_read(size: number): { ret: number; data: Uint8Array } {\n const chunk = this.reader.readAvailable(size);\n if (chunk) {\n return {\n ret: wasi.ERRNO_SUCCESS,\n data: chunk,\n };\n }\n\n if (this.reader.isClosed()) {\n return {\n ret: wasi.ERRNO_SUCCESS,\n data: new Uint8Array(),\n };\n }\n\n return {\n ret: wasi.ERRNO_AGAIN,\n data: new Uint8Array(),\n };\n }\n\n availableBytes(): number {\n return this.reader.availableBytes();\n }\n\n waitForReadable(\n timeoutMilliseconds?: number\n ) {\n return this.reader.waitForReadable(timeoutMilliseconds);\n }\n\n isClosed(): boolean {\n return this.reader.isClosed();\n }\n}\n\nfunction installWasiPollScheduler(\n wasiBridge: WASI,\n stdin: BlockingInputFileDescriptor\n): void {\n const originalPoll = wasiBridge.wasiImport.poll_oneoff;\n if (typeof originalPoll !== \"function\") {\n return;\n }\n\n const scheduler = new WasiPollScheduler({\n memory: () => wasiBridge.inst?.exports.memory as WebAssembly.Memory | undefined,\n stdin,\n fallbackPoll: (inPtr, outPtr, nsubscriptions, neventsPtr) =>\n originalPoll(inPtr, outPtr, nsubscriptions, neventsPtr),\n });\n wasiBridge.wasiImport.poll_oneoff = (inPtr, outPtr, nsubscriptions, neventsPtr) =>\n scheduler.pollOneOff(inPtr, outPtr, nsubscriptions, neventsPtr);\n}\n\nasync function startWasmScene(\n message: StartWasmSceneWorkerMessage\n): Promise<void> {\n try {\n const stdin = new BlockingInputFileDescriptor(message.inputQueue);\n const wasiBridge = new WASI(\n [\"app.wasm\"],\n Object.entries(message.environment).map(([key, value]) => `${key}=${value}`),\n [\n stdin,\n new ConsoleStdout((chunk) => {\n postWorkerMessage({\n type: \"stdout\",\n chunk,\n });\n }),\n new ConsoleStdout((chunk) => {\n postWorkerMessage({\n type: \"stderr\",\n chunk,\n });\n }),\n ]\n );\n installWasiPollScheduler(wasiBridge, stdin);\n\n const response = await fetch(message.wasmURL);\n if (!response.ok) {\n throw new Error(`failed to load ${message.wasmURL}: ${response.status} ${response.statusText}`);\n }\n\n const module = await WebAssembly.compile(await response.arrayBuffer());\n const instance = await WebAssembly.instantiate(module, {\n wasi_snapshot_preview1: wasiBridge.wasiImport,\n });\n\n const exitCode = wasiBridge.start(instance as WebAssembly.Instance);\n postWorkerMessage({\n type: \"exit\",\n code: exitCode,\n });\n } catch (error) {\n postWorkerMessage({\n type: \"error\",\n message: error instanceof Error ? error.message : String(error),\n });\n }\n}\n\nfunction postWorkerMessage(\n message: WasmSceneWorkerResponse\n): void {\n globalThis.postMessage(message);\n}\n"],"mappings":";;;;AAmCA,SAAgB,uBAA6B;CAC3C,WAAW,iBAAiB,YAAY,UAAgD;EACtF,IAAI,MAAM,KAAK,SAAS,SACtB;EAGF,eAAoB,MAAM,IAAI;CAChC,CAAC;AACH;AAEA,IAAM,8BAAN,cAA0C,GAAG;CAC3C;CACA,gBAAiC;EAC/B,MAAM,SAAS,IAAI,KAAK,OAAO,KAAK,2BAA2B,CAAC;EAChE,OAAO,iBAAiB,OAAO,KAAK,cAAc;EAClD,OAAO;CACT,EAAA,CAAG;CAEH,YAAY,YAAqC;EAC/C,MAAM;EACN,KAAK,SAAS,IAAI,uBAAuB,UAAU;CACrD;CAEA,gBAA+D;EAC7D,OAAO;GACL,KAAK,KAAK;GACV,QAAQ,KAAK;EACf;CACF;CAEA,kBAAqE;EACnE,OAAO;GACL,KAAK,KAAK;GACV,UAAU,IAAI,KAAK,SAAS,IAAI,KAAK,2BAA2B,EAAE;EACpE;CACF;CAEA,QAAiB,MAAiD;EAChE,MAAM,QAAQ,KAAK,OAAO,cAAc,IAAI;EAC5C,IAAI,OACF,OAAO;GACL,KAAK,KAAK;GACV,MAAM;EACR;EAGF,IAAI,KAAK,OAAO,SAAS,GACvB,OAAO;GACL,KAAK,KAAK;GACV,
|
|
1
|
+
{"version":3,"file":"WasmSceneWorker.js","names":[],"sources":["../../../src/wasi/WasmSceneWorker.ts"],"sourcesContent":["import { ConsoleStdout, Fd, WASI, wasi } from \"@bjorn3/browser_wasi_shim\";\nimport {\n SharedInputQueueReader,\n type SharedInputQueueBuffers,\n} from \"./SharedInputQueue.ts\";\nimport { WasiPollScheduler } from \"./WasiPollScheduler.ts\";\n\nexport interface StartWasmSceneWorkerMessage {\n type: \"start\";\n wasmURL: string;\n environment: Record<string, string>;\n inputQueue: SharedInputQueueBuffers;\n}\n\nexport interface OutputWasmSceneWorkerMessage {\n type: \"stdout\" | \"stderr\";\n chunk: Uint8Array;\n}\n\nexport interface ExitWasmSceneWorkerMessage {\n type: \"exit\";\n code: number;\n}\n\nexport interface ErrorWasmSceneWorkerMessage {\n type: \"error\";\n message: string;\n}\n\nexport type WasmSceneWorkerMessage = StartWasmSceneWorkerMessage;\nexport type WasmSceneWorkerResponse =\n | OutputWasmSceneWorkerMessage\n | ExitWasmSceneWorkerMessage\n | ErrorWasmSceneWorkerMessage;\n\nexport function startWasmSceneWorker(): void {\n globalThis.addEventListener(\"message\", (event: MessageEvent<WasmSceneWorkerMessage>) => {\n if (event.data.type !== \"start\") {\n return;\n }\n\n void startWasmScene(event.data);\n });\n}\n\nclass BlockingInputFileDescriptor extends Fd {\n private readonly reader: SharedInputQueueReader;\n private readonly fdstat = (() => {\n const fdstat = new wasi.Fdstat(wasi.FILETYPE_CHARACTER_DEVICE, 0);\n fdstat.fs_rights_base = BigInt(wasi.RIGHTS_FD_READ);\n return fdstat;\n })();\n\n constructor(inputQueue: SharedInputQueueBuffers) {\n super();\n this.reader = new SharedInputQueueReader(inputQueue);\n }\n\n override fd_fdstat_get(): { ret: number; fdstat: wasi.Fdstat } {\n return {\n ret: wasi.ERRNO_SUCCESS,\n fdstat: this.fdstat,\n };\n }\n\n override fd_filestat_get(): { ret: number; filestat: wasi.Filestat } {\n return {\n ret: wasi.ERRNO_SUCCESS,\n filestat: new wasi.Filestat(0n, wasi.FILETYPE_CHARACTER_DEVICE, 0n),\n };\n }\n\n override fd_read(size: number): { ret: number; data: Uint8Array } {\n const chunk = this.reader.readAvailable(size);\n if (chunk) {\n return {\n ret: wasi.ERRNO_SUCCESS,\n data: chunk,\n };\n }\n\n if (this.reader.isClosed()) {\n return {\n ret: wasi.ERRNO_SUCCESS,\n data: new Uint8Array(),\n };\n }\n\n return {\n ret: wasi.ERRNO_AGAIN,\n data: new Uint8Array(),\n };\n }\n\n availableBytes(): number {\n return this.reader.availableBytes();\n }\n\n waitForReadable(\n timeoutMilliseconds?: number\n ) {\n return this.reader.waitForReadable(timeoutMilliseconds);\n }\n\n isClosed(): boolean {\n return this.reader.isClosed();\n }\n}\n\nfunction installWasiPollScheduler(\n wasiBridge: WASI,\n stdin: BlockingInputFileDescriptor\n): void {\n const originalPoll = wasiBridge.wasiImport.poll_oneoff;\n if (typeof originalPoll !== \"function\") {\n return;\n }\n\n const scheduler = new WasiPollScheduler({\n memory: () => wasiBridge.inst?.exports.memory as WebAssembly.Memory | undefined,\n stdin,\n fallbackPoll: (inPtr, outPtr, nsubscriptions, neventsPtr) =>\n originalPoll(inPtr, outPtr, nsubscriptions, neventsPtr),\n });\n wasiBridge.wasiImport.poll_oneoff = (inPtr, outPtr, nsubscriptions, neventsPtr) =>\n scheduler.pollOneOff(inPtr, outPtr, nsubscriptions, neventsPtr);\n}\n\nasync function startWasmScene(\n message: StartWasmSceneWorkerMessage\n): Promise<void> {\n try {\n const stdin = new BlockingInputFileDescriptor(message.inputQueue);\n const wasiBridge = new WASI(\n [\"app.wasm\"],\n Object.entries(message.environment).map(([key, value]) => `${key}=${value}`),\n [\n stdin,\n new ConsoleStdout((chunk) => {\n postWorkerMessage({\n type: \"stdout\",\n chunk,\n });\n }),\n new ConsoleStdout((chunk) => {\n postWorkerMessage({\n type: \"stderr\",\n chunk,\n });\n }),\n ]\n );\n installWasiPollScheduler(wasiBridge, stdin);\n\n const response = await fetch(message.wasmURL);\n if (!response.ok) {\n throw new Error(`failed to load ${message.wasmURL}: ${response.status} ${response.statusText}`);\n }\n\n const module = await WebAssembly.compile(await response.arrayBuffer());\n const instance = await WebAssembly.instantiate(module, {\n wasi_snapshot_preview1: wasiBridge.wasiImport,\n });\n\n const exitCode = wasiBridge.start(instance as WebAssembly.Instance);\n postWorkerMessage({\n type: \"exit\",\n code: exitCode,\n });\n } catch (error) {\n postWorkerMessage({\n type: \"error\",\n message: error instanceof Error ? error.message : String(error),\n });\n }\n}\n\nfunction postWorkerMessage(\n message: WasmSceneWorkerResponse\n): void {\n globalThis.postMessage(message);\n}\n"],"mappings":";;;;AAmCA,SAAgB,uBAA6B;CAC3C,WAAW,iBAAiB,YAAY,UAAgD;EACtF,IAAI,MAAM,KAAK,SAAS,SACtB;EAGF,eAAoB,MAAM,IAAI;CAChC,CAAC;AACH;AAEA,IAAM,8BAAN,cAA0C,GAAG;CAC3C;CACA,gBAAiC;EAC/B,MAAM,SAAS,IAAI,KAAK,OAAO,KAAK,2BAA2B,CAAC;EAChE,OAAO,iBAAiB,OAAO,KAAK,cAAc;EAClD,OAAO;CACT,EAAA,CAAG;CAEH,YAAY,YAAqC;EAC/C,MAAM;EACN,KAAK,SAAS,IAAI,uBAAuB,UAAU;CACrD;CAEA,gBAA+D;EAC7D,OAAO;GACL,KAAK,KAAK;GACV,QAAQ,KAAK;EACf;CACF;CAEA,kBAAqE;EACnE,OAAO;GACL,KAAK,KAAK;GACV,UAAU,IAAI,KAAK,SAAS,IAAI,KAAK,2BAA2B,EAAE;EACpE;CACF;CAEA,QAAiB,MAAiD;EAChE,MAAM,QAAQ,KAAK,OAAO,cAAc,IAAI;EAC5C,IAAI,OACF,OAAO;GACL,KAAK,KAAK;GACV,MAAM;EACR;EAGF,IAAI,KAAK,OAAO,SAAS,GACvB,OAAO;GACL,KAAK,KAAK;GACV,sBAAM,IAAI,WAAW;EACvB;EAGF,OAAO;GACL,KAAK,KAAK;GACV,sBAAM,IAAI,WAAW;EACvB;CACF;CAEA,iBAAyB;EACvB,OAAO,KAAK,OAAO,eAAe;CACpC;CAEA,gBACE,qBACA;EACA,OAAO,KAAK,OAAO,gBAAgB,mBAAmB;CACxD;CAEA,WAAoB;EAClB,OAAO,KAAK,OAAO,SAAS;CAC9B;AACF;AAEA,SAAS,yBACP,YACA,OACM;CACN,MAAM,eAAe,WAAW,WAAW;CAC3C,IAAI,OAAO,iBAAiB,YAC1B;CAGF,MAAM,YAAY,IAAI,kBAAkB;EACtC,cAAc,WAAW,MAAM,QAAQ;EACvC;EACA,eAAe,OAAO,QAAQ,gBAAgB,eAC5C,aAAa,OAAO,QAAQ,gBAAgB,UAAU;CAC1D,CAAC;CACD,WAAW,WAAW,eAAe,OAAO,QAAQ,gBAAgB,eAClE,UAAU,WAAW,OAAO,QAAQ,gBAAgB,UAAU;AAClE;AAEA,eAAe,eACb,SACe;CACf,IAAI;EACF,MAAM,QAAQ,IAAI,4BAA4B,QAAQ,UAAU;EAChE,MAAM,aAAa,IAAI,KACrB,CAAC,UAAU,GACX,OAAO,QAAQ,QAAQ,WAAW,CAAC,CAAC,KAAK,CAAC,KAAK,WAAW,GAAG,IAAI,GAAG,OAAO,GAC3E;GACE;GACA,IAAI,eAAe,UAAU;IAC3B,kBAAkB;KAChB,MAAM;KACN;IACF,CAAC;GACH,CAAC;GACD,IAAI,eAAe,UAAU;IAC3B,kBAAkB;KAChB,MAAM;KACN;IACF,CAAC;GACH,CAAC;EACH,CACF;EACA,yBAAyB,YAAY,KAAK;EAE1C,MAAM,WAAW,MAAM,MAAM,QAAQ,OAAO;EAC5C,IAAI,CAAC,SAAS,IACZ,MAAM,IAAI,MAAM,kBAAkB,QAAQ,QAAQ,IAAI,SAAS,OAAO,GAAG,SAAS,YAAY;EAGhG,MAAM,SAAS,MAAM,YAAY,QAAQ,MAAM,SAAS,YAAY,CAAC;EACrE,MAAM,WAAW,MAAM,YAAY,YAAY,QAAQ,EACrD,wBAAwB,WAAW,WACrC,CAAC;EAGD,kBAAkB;GAChB,MAAM;GACN,MAHe,WAAW,MAAM,QAGnB;EACf,CAAC;CACH,SAAS,OAAO;EACd,kBAAkB;GAChB,MAAM;GACN,SAAS,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;EAChE,CAAC;CACH;AACF;AAEA,SAAS,kBACP,SACM;CACN,WAAW,YAAY,OAAO;AAChC"}
|