@swifttui/web 0.0.27 → 0.1.1

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.
@@ -0,0 +1,117 @@
1
+ import { encodeKeyInputMessage, encodeMouseInputMessage, encodePasteInputMessage } from "./WebHostSurfaceTransport.js";
2
+ //#region src/InputEventEncoder.ts
3
+ /**
4
+ * Encodes DOM keyboard/pointer/wheel/paste events into the `web-surface` wire
5
+ * messages the SwiftTUI host consumes. This collaborator owns the translation
6
+ * from browser event vocabulary (key names, button indices, wheel deltas,
7
+ * modifier flags) to the transport's message vocabulary; it produces raw byte
8
+ * chunks and holds no DOM or geometry state.
9
+ *
10
+ * Hit-testing (pixel → cell) lives in the host, which passes already-resolved
11
+ * {@link CellLocation}s here so the encoder stays free of layout concerns.
12
+ */
13
+ var InputEventEncoder = class {
14
+ /**
15
+ * Builds the key message for a keyboard event, or `undefined` when the event
16
+ * does not map to a forwarded key (e.g. a multi-codepoint composed string).
17
+ * Returning `undefined` lets the host leave the event unhandled.
18
+ */
19
+ encodeKey(event) {
20
+ const key = keyInputFromKeyboardEvent(event);
21
+ if (!key) return;
22
+ return encodeKeyInputMessage({
23
+ ...key,
24
+ modifiers: modifierMask(event)
25
+ });
26
+ }
27
+ encodePaste(text) {
28
+ return encodePasteInputMessage(text);
29
+ }
30
+ encodePointerDown(location, button, event) {
31
+ return encodeMouseInputMessage({
32
+ kind: "down",
33
+ x: location.x,
34
+ y: location.y,
35
+ button,
36
+ modifiers: modifierMask(event)
37
+ });
38
+ }
39
+ encodePointerUp(location, button, event) {
40
+ return encodeMouseInputMessage({
41
+ kind: "up",
42
+ x: location.x,
43
+ y: location.y,
44
+ button,
45
+ modifiers: modifierMask(event)
46
+ });
47
+ }
48
+ encodePointerMove(location, button, event) {
49
+ return encodeMouseInputMessage({
50
+ kind: event.buttons ? "dragged" : "moved",
51
+ x: location.x,
52
+ y: location.y,
53
+ button,
54
+ modifiers: modifierMask(event)
55
+ });
56
+ }
57
+ encodeWheel(location, event) {
58
+ return encodeMouseInputMessage({
59
+ kind: "scrolled",
60
+ x: location.x,
61
+ y: location.y,
62
+ deltaX: normalizedWheelDelta(event.deltaX),
63
+ deltaY: normalizedWheelDelta(event.deltaY),
64
+ modifiers: modifierMask(event)
65
+ });
66
+ }
67
+ /** Translates a DOM `MouseEvent.button` index into the wire button identity. */
68
+ pointerButton(button) {
69
+ return pointerButton(button);
70
+ }
71
+ };
72
+ function keyInputFromKeyboardEvent(event) {
73
+ switch (event.key) {
74
+ case "Enter": return { key: "return" };
75
+ case " ": return { key: "space" };
76
+ case "Tab": return { key: "tab" };
77
+ case "ArrowLeft": return { key: "arrowLeft" };
78
+ case "ArrowRight": return { key: "arrowRight" };
79
+ case "ArrowUp": return { key: "arrowUp" };
80
+ case "ArrowDown": return { key: "arrowDown" };
81
+ case "Backspace": return { key: "backspace" };
82
+ case "Escape": return { key: "escape" };
83
+ case "Home": return { key: "home" };
84
+ case "End": return { key: "end" };
85
+ default: {
86
+ const characters = Array.from(event.key);
87
+ if (characters.length !== 1) return;
88
+ return {
89
+ key: "character",
90
+ character: characters[0]
91
+ };
92
+ }
93
+ }
94
+ }
95
+ function pointerButton(button) {
96
+ switch (button) {
97
+ case 1: return "middle";
98
+ case 2: return "secondary";
99
+ default: return "primary";
100
+ }
101
+ }
102
+ function modifierMask(event) {
103
+ let mask = 0;
104
+ if (event.shiftKey) mask |= 1;
105
+ if (event.altKey) mask |= 2;
106
+ if (event.ctrlKey) mask |= 4;
107
+ return mask;
108
+ }
109
+ function normalizedWheelDelta(delta) {
110
+ if (delta > 0) return 1;
111
+ if (delta < 0) return -1;
112
+ return 0;
113
+ }
114
+ //#endregion
115
+ export { InputEventEncoder };
116
+
117
+ //# sourceMappingURL=InputEventEncoder.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"InputEventEncoder.js","names":[],"sources":["../../src/InputEventEncoder.ts"],"sourcesContent":["import {\n encodeKeyInputMessage,\n encodeMouseInputMessage,\n encodePasteInputMessage,\n type WebHostKeyInput,\n} from \"./WebHostSurfaceTransport.ts\";\n\n/** A resolved cell-grid location (fractional cell coordinates). */\nexport interface CellLocation {\n x: number;\n y: number;\n}\n\n/**\n * Pointer button identity in the wire vocabulary. Mirrors the values the Swift\n * host expects on `mouse:` records.\n */\nexport type PointerButton = \"primary\" | \"middle\" | \"secondary\";\n\n/**\n * Encodes DOM keyboard/pointer/wheel/paste events into the `web-surface` wire\n * messages the SwiftTUI host consumes. This collaborator owns the translation\n * from browser event vocabulary (key names, button indices, wheel deltas,\n * modifier flags) to the transport's message vocabulary; it produces raw byte\n * chunks and holds no DOM or geometry state.\n *\n * Hit-testing (pixel → cell) lives in the host, which passes already-resolved\n * {@link CellLocation}s here so the encoder stays free of layout concerns.\n */\nexport class InputEventEncoder {\n /**\n * Builds the key message for a keyboard event, or `undefined` when the event\n * does not map to a forwarded key (e.g. a multi-codepoint composed string).\n * Returning `undefined` lets the host leave the event unhandled.\n */\n encodeKey(\n event: KeyboardEvent\n ): Uint8Array | undefined {\n const key = keyInputFromKeyboardEvent(event);\n if (!key) {\n return undefined;\n }\n return encodeKeyInputMessage({\n ...key,\n modifiers: modifierMask(event),\n });\n }\n\n encodePaste(\n text: string\n ): Uint8Array {\n return encodePasteInputMessage(text);\n }\n\n encodePointerDown(\n location: CellLocation,\n button: PointerButton,\n event: PointerEvent\n ): Uint8Array {\n return encodeMouseInputMessage({\n kind: \"down\",\n x: location.x,\n y: location.y,\n button,\n modifiers: modifierMask(event),\n });\n }\n\n encodePointerUp(\n location: CellLocation,\n button: PointerButton,\n event: PointerEvent\n ): Uint8Array {\n return encodeMouseInputMessage({\n kind: \"up\",\n x: location.x,\n y: location.y,\n button,\n modifiers: modifierMask(event),\n });\n }\n\n encodePointerMove(\n location: CellLocation,\n button: PointerButton,\n event: PointerEvent\n ): Uint8Array {\n return encodeMouseInputMessage({\n kind: event.buttons ? \"dragged\" : \"moved\",\n x: location.x,\n y: location.y,\n button,\n modifiers: modifierMask(event),\n });\n }\n\n encodeWheel(\n location: CellLocation,\n event: WheelEvent\n ): Uint8Array {\n return encodeMouseInputMessage({\n kind: \"scrolled\",\n x: location.x,\n y: location.y,\n deltaX: normalizedWheelDelta(event.deltaX),\n deltaY: normalizedWheelDelta(event.deltaY),\n modifiers: modifierMask(event),\n });\n }\n\n /** Translates a DOM `MouseEvent.button` index into the wire button identity. */\n pointerButton(\n button: number\n ): PointerButton {\n return pointerButton(button);\n }\n}\n\nfunction keyInputFromKeyboardEvent(\n event: KeyboardEvent\n): Pick<WebHostKeyInput, \"key\" | \"character\"> | undefined {\n switch (event.key) {\n case \"Enter\":\n return { key: \"return\" };\n case \" \":\n return { key: \"space\" };\n case \"Tab\":\n return { key: \"tab\" };\n case \"ArrowLeft\":\n return { key: \"arrowLeft\" };\n case \"ArrowRight\":\n return { key: \"arrowRight\" };\n case \"ArrowUp\":\n return { key: \"arrowUp\" };\n case \"ArrowDown\":\n return { key: \"arrowDown\" };\n case \"Backspace\":\n return { key: \"backspace\" };\n case \"Escape\":\n return { key: \"escape\" };\n case \"Home\":\n return { key: \"home\" };\n case \"End\":\n return { key: \"end\" };\n default:\n {\n const characters = Array.from(event.key);\n if (characters.length !== 1) {\n return undefined;\n }\n return {\n key: \"character\",\n character: characters[0],\n };\n }\n }\n}\n\nfunction pointerButton(\n button: number\n): PointerButton {\n switch (button) {\n case 1:\n return \"middle\";\n case 2:\n return \"secondary\";\n default:\n return \"primary\";\n }\n}\n\nfunction modifierMask(\n event: MouseEvent | KeyboardEvent\n): number {\n let mask = 0;\n if (event.shiftKey) {\n mask |= 1;\n }\n if (event.altKey) {\n mask |= 2;\n }\n if (event.ctrlKey) {\n mask |= 4;\n }\n return mask;\n}\n\nfunction normalizedWheelDelta(\n delta: number\n): number {\n if (delta > 0) {\n return 1;\n }\n if (delta < 0) {\n return -1;\n }\n return 0;\n}\n"],"mappings":";;;;;;;;;;;;AA6BA,IAAa,oBAAb,MAA+B;;;;;;CAM7B,UACE,OACwB;EACxB,MAAM,MAAM,0BAA0B,KAAK;EAC3C,IAAI,CAAC,KACH;EAEF,OAAO,sBAAsB;GAC3B,GAAG;GACH,WAAW,aAAa,KAAK;EAC/B,CAAC;CACH;CAEA,YACE,MACY;EACZ,OAAO,wBAAwB,IAAI;CACrC;CAEA,kBACE,UACA,QACA,OACY;EACZ,OAAO,wBAAwB;GAC7B,MAAM;GACN,GAAG,SAAS;GACZ,GAAG,SAAS;GACZ;GACA,WAAW,aAAa,KAAK;EAC/B,CAAC;CACH;CAEA,gBACE,UACA,QACA,OACY;EACZ,OAAO,wBAAwB;GAC7B,MAAM;GACN,GAAG,SAAS;GACZ,GAAG,SAAS;GACZ;GACA,WAAW,aAAa,KAAK;EAC/B,CAAC;CACH;CAEA,kBACE,UACA,QACA,OACY;EACZ,OAAO,wBAAwB;GAC7B,MAAM,MAAM,UAAU,YAAY;GAClC,GAAG,SAAS;GACZ,GAAG,SAAS;GACZ;GACA,WAAW,aAAa,KAAK;EAC/B,CAAC;CACH;CAEA,YACE,UACA,OACY;EACZ,OAAO,wBAAwB;GAC7B,MAAM;GACN,GAAG,SAAS;GACZ,GAAG,SAAS;GACZ,QAAQ,qBAAqB,MAAM,MAAM;GACzC,QAAQ,qBAAqB,MAAM,MAAM;GACzC,WAAW,aAAa,KAAK;EAC/B,CAAC;CACH;;CAGA,cACE,QACe;EACf,OAAO,cAAc,MAAM;CAC7B;AACF;AAEA,SAAS,0BACP,OACwD;CACxD,QAAQ,MAAM,KAAd;EACA,KAAK,SACH,OAAO,EAAE,KAAK,SAAS;EACzB,KAAK,KACH,OAAO,EAAE,KAAK,QAAQ;EACxB,KAAK,OACH,OAAO,EAAE,KAAK,MAAM;EACtB,KAAK,aACH,OAAO,EAAE,KAAK,YAAY;EAC5B,KAAK,cACH,OAAO,EAAE,KAAK,aAAa;EAC7B,KAAK,WACH,OAAO,EAAE,KAAK,UAAU;EAC1B,KAAK,aACH,OAAO,EAAE,KAAK,YAAY;EAC5B,KAAK,aACH,OAAO,EAAE,KAAK,YAAY;EAC5B,KAAK,UACH,OAAO,EAAE,KAAK,SAAS;EACzB,KAAK,QACH,OAAO,EAAE,KAAK,OAAO;EACvB,KAAK,OACH,OAAO,EAAE,KAAK,MAAM;EACtB,SACE;GACE,MAAM,aAAa,MAAM,KAAK,MAAM,GAAG;GACvC,IAAI,WAAW,WAAW,GACxB;GAEF,OAAO;IACL,KAAK;IACL,WAAW,WAAW;GACxB;EACF;CACF;AACF;AAEA,SAAS,cACP,QACe;CACf,QAAQ,QAAR;EACA,KAAK,GACH,OAAO;EACT,KAAK,GACH,OAAO;EACT,SACE,OAAO;CACT;AACF;AAEA,SAAS,aACP,OACQ;CACR,IAAI,OAAO;CACX,IAAI,MAAM,UACR,QAAQ;CAEV,IAAI,MAAM,QACR,QAAQ;CAEV,IAAI,MAAM,SACR,QAAQ;CAEV,OAAO;AACT;AAEA,SAAS,qBACP,OACQ;CACR,IAAI,QAAQ,GACV,OAAO;CAET,IAAI,QAAQ,GACV,OAAO;CAET,OAAO;AACT"}
@@ -0,0 +1,72 @@
1
+ //#region src/PointerGeometry.ts
2
+ /**
3
+ * Converts a pointer event to fractional cell coordinates, clamped to the
4
+ * grid. Returns `undefined` when the pointer is outside the cell grid (in the
5
+ * sub-cell margin/gutter), so the host can leave the event unhandled.
6
+ */
7
+ function cellLocationForEvent(event, metrics) {
8
+ const location = rawCellLocationForEvent(event, metrics);
9
+ if (!location) return;
10
+ const cellX = Math.floor(location.x);
11
+ const cellY = Math.floor(location.y);
12
+ if (cellX < 0 || cellY < 0 || cellX >= metrics.columns || cellY >= metrics.rows) return;
13
+ return location;
14
+ }
15
+ /**
16
+ * Converts a pointer event to fractional cell coordinates *without* clamping to
17
+ * the grid. Used while a pointer is captured (drag) so the host keeps tracking
18
+ * the gesture even when it strays outside the surface bounds.
19
+ */
20
+ function rawCellLocationForEvent(event, metrics) {
21
+ const rect = metrics.rect;
22
+ if (!rect) return;
23
+ return {
24
+ x: (event.clientX - rect.left) / metrics.cellWidth,
25
+ y: (event.clientY - rect.top) / metrics.cellHeight
26
+ };
27
+ }
28
+ /**
29
+ * Whether any scrollable region under `location` can still scroll in the
30
+ * wheel's direction. Mirrors the Swift host's scroll hit-test: a region
31
+ * qualifies when its viewport contains the cell AND it has remaining headroom
32
+ * in the delta's direction. Used by "chain" wheel mode to decide capture vs.
33
+ * fall-through. With no published `scrollRegions`, nothing can scroll, so the
34
+ * wheel chains to the page (a scene with no ScrollView stays fully passive).
35
+ */
36
+ function wheelTargetCanScroll(regions, location, deltaX, deltaY) {
37
+ if (!regions || regions.length === 0) return false;
38
+ const cellX = Math.floor(location.x);
39
+ const cellY = Math.floor(location.y);
40
+ for (const region of regions) {
41
+ const [rx, ry, rw, rh] = region.rect;
42
+ if (cellX < rx || cellY < ry || cellX >= rx + rw || cellY >= ry + rh) continue;
43
+ if (regionCanScrollInDirection(region, deltaX, deltaY)) return true;
44
+ }
45
+ return false;
46
+ }
47
+ /**
48
+ * Whether a published scroll region has remaining headroom in the wheel's
49
+ * direction, recomputing the per-direction extent from offset/content/viewport.
50
+ * Mirrors SwiftTUI's clamp (`min(max(0, offset), max(0, content - viewport))`)
51
+ * so the host and the app agree on "at edge". Wheel sign convention matches the
52
+ * app: `deltaY > 0` scrolls down (offset grows toward the content bottom).
53
+ * Diagonal wheels qualify if either axis has headroom.
54
+ */
55
+ function regionCanScrollInDirection(region, deltaX, deltaY) {
56
+ const [, , viewportWidth, viewportHeight] = region.rect;
57
+ const [offsetX, offsetY] = region.offset;
58
+ const [contentWidth, contentHeight] = region.content;
59
+ const maxX = Math.max(0, contentWidth - viewportWidth);
60
+ const maxY = Math.max(0, contentHeight - viewportHeight);
61
+ const clampedX = Math.min(Math.max(0, offsetX), maxX);
62
+ const clampedY = Math.min(Math.max(0, offsetY), maxY);
63
+ if (deltaY > 0 && clampedY < maxY) return true;
64
+ if (deltaY < 0 && clampedY > 0) return true;
65
+ if (deltaX > 0 && clampedX < maxX) return true;
66
+ if (deltaX < 0 && clampedX > 0) return true;
67
+ return false;
68
+ }
69
+ //#endregion
70
+ export { cellLocationForEvent, rawCellLocationForEvent, wheelTargetCanScroll };
71
+
72
+ //# sourceMappingURL=PointerGeometry.js.map
@@ -0,0 +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":";;;;;;AAuBA,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;;;;;;;;;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"}
@@ -41,6 +41,13 @@ interface WebHostSceneRuntimeOptions {
41
41
  captureWheelInput?: boolean;
42
42
  }
43
43
  type WheelMode = "capture" | "chain" | "passive";
44
+ /**
45
+ * Coordinates a single SwiftTUI scene's browser presentation: it owns the DOM
46
+ * mount, canvas, accessibility tree, and bridge wiring, and delegates the heavy
47
+ * responsibilities to focused collaborators — {@link CanvasSurfacePainter} for
48
+ * canvas drawing, {@link InputEventEncoder} for wire-message encoding, and the
49
+ * {@link PointerGeometry} helpers for pixel→cell hit-testing and wheel chaining.
50
+ */
44
51
  declare class WebHostSceneRuntime {
45
52
  readonly descriptor: WebHostSceneDescriptor;
46
53
  readonly element: HTMLElement;
@@ -50,7 +57,8 @@ declare class WebHostSceneRuntime {
50
57
  private readonly onFrameDiagnostic?;
51
58
  private readonly synchronizeAccessibilityFocus;
52
59
  private readonly wheelMode;
53
- private readonly imageCache;
60
+ private readonly painter;
61
+ private readonly inputEncoder;
54
62
  private currentStyle;
55
63
  private canvas?;
56
64
  private accessibilityTree?;
@@ -87,26 +95,9 @@ declare class WebHostSceneRuntime {
87
95
  private resizeCanvas;
88
96
  private measureCells;
89
97
  private draw;
90
- private drawRows;
91
- private drawRow;
92
98
  private syncAccessibilityTree;
93
- private drawImages;
94
- private drawImage;
95
- private cachedImage;
96
- private drawCell;
97
- private dirtyRegionForDamage;
98
- private cellRect;
99
- private drawTextLine;
100
- private fontForStyle;
101
- /**
102
- * Whether any scrollable region under `location` can still scroll in the
103
- * wheel's direction. Mirrors the Swift host's scroll hit-test: a region
104
- * qualifies when its viewport contains the cell AND it has remaining headroom
105
- * in the delta's direction. Used by "chain" wheel mode to decide capture vs.
106
- * fall-through. With no published `scrollRegions`, nothing can scroll, so the
107
- * wheel chains to the page (a scene with no ScrollView stays fully passive).
108
- */
109
- private wheelTargetCanScroll;
99
+ private surfaceMetrics;
100
+ private pointerMetrics;
110
101
  private cellLocation;
111
102
  private rawCellLocation;
112
103
  }