@bloopjs/web 0.0.106 → 0.0.107

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bloopjs/web",
3
- "version": "0.0.106",
3
+ "version": "0.0.107",
4
4
  "author": "Neil Sarkar",
5
5
  "type": "module",
6
6
  "repository": {
@@ -37,8 +37,8 @@
37
37
  "typescript": "^5"
38
38
  },
39
39
  "dependencies": {
40
- "@bloopjs/bloop": "0.0.106",
41
- "@bloopjs/engine": "0.0.106",
40
+ "@bloopjs/bloop": "0.0.107",
41
+ "@bloopjs/engine": "0.0.107",
42
42
  "@preact/signals": "^1.3.1",
43
43
  "partysocket": "^1.1.6",
44
44
  "preact": "^10.25.4"
package/src/App.ts CHANGED
@@ -39,6 +39,8 @@ export type StartOptions = {
39
39
  debugUi?: boolean | DebugUiOptions;
40
40
  /** Tape recording options */
41
41
  tape?: MountOptions["tape"];
42
+ /** External canvas element to use (preserves user CSS via slot projection) */
43
+ canvas?: HTMLCanvasElement;
42
44
  };
43
45
 
44
46
  const DEFAULT_BROKER_URL = "wss://webrtc-divine-glade-8064.fly.dev/ws";
@@ -55,9 +57,11 @@ export async function start(opts: StartOptions): Promise<App> {
55
57
 
56
58
  const debugOpts = opts.debugUi
57
59
  ? typeof opts.debugUi === "boolean"
58
- ? { initiallyVisible: true }
59
- : opts.debugUi
60
- : undefined;
60
+ ? { initiallyVisible: true, canvas: opts.canvas }
61
+ : { ...opts.debugUi, canvas: opts.canvas ?? opts.debugUi.canvas }
62
+ : opts.canvas
63
+ ? { canvas: opts.canvas }
64
+ : undefined;
61
65
 
62
66
  const app = new App(
63
67
  opts.sim,
@@ -10,6 +10,8 @@ export type DebugUiOptions = {
10
10
  initiallyVisible?: boolean;
11
11
  /** Container element to mount to (default: document.body) */
12
12
  container?: HTMLElement;
13
+ /** External canvas element to use (preserves user CSS via slot projection) */
14
+ canvas?: HTMLCanvasElement;
13
15
  };
14
16
 
15
17
  export class DebugUi {
@@ -49,8 +51,11 @@ export class DebugUi {
49
51
  // Initialize state
50
52
  debugState.layoutMode.value = initiallyVisible ? "letterboxed" : "off";
51
53
 
52
- // Create canvas element (game renders here)
53
- this.#canvas = document.createElement("canvas");
54
+ // Use provided canvas or create new one
55
+ this.#canvas = options.canvas ?? document.createElement("canvas");
56
+
57
+ // Append canvas to host element (light DOM) for slot projection
58
+ this.#host.appendChild(this.#canvas);
54
59
 
55
60
  // Render Preact app
56
61
  this.#render();
@@ -68,10 +73,7 @@ export class DebugUi {
68
73
  }
69
74
 
70
75
  #render(): void {
71
- render(
72
- Root({ canvas: this.#canvas, hotkey: this.#hotkey }),
73
- this.#mountPoint,
74
- );
76
+ render(Root({ hotkey: this.#hotkey }), this.#mountPoint);
75
77
  }
76
78
 
77
79
  #setupHotkey(): () => void {
@@ -1,4 +1,3 @@
1
- import { useRef, useEffect } from "preact/hooks";
2
1
  import { debugState } from "../state.ts";
3
2
  import { Stats } from "./Stats.tsx";
4
3
  import { Logs } from "./Logs.tsx";
@@ -8,18 +7,17 @@ import { VerticalBar } from "./VerticalBar.tsx";
8
7
  import { BottomBar } from "./BottomBar.tsx";
9
8
 
10
9
  type RootProps = {
11
- canvas: HTMLCanvasElement;
12
10
  hotkey?: string;
13
11
  };
14
12
 
15
- export function Root({ canvas, hotkey = "Escape" }: RootProps) {
13
+ export function Root({ hotkey = "Escape" }: RootProps) {
16
14
  const layoutMode = debugState.layoutMode.value;
17
15
 
18
16
  if (layoutMode === "off") {
19
17
  return (
20
18
  <>
21
19
  <main className="fullscreen">
22
- <GameCanvas canvas={canvas} />
20
+ <CanvasSlot />
23
21
  </main>
24
22
  <DebugToggle hotkey={hotkey} />
25
23
  </>
@@ -29,7 +27,7 @@ export function Root({ canvas, hotkey = "Escape" }: RootProps) {
29
27
  if (layoutMode === "letterboxed") {
30
28
  return (
31
29
  <>
32
- <LetterboxedLayout canvas={canvas} />
30
+ <LetterboxedLayout />
33
31
  <DebugToggle hotkey={hotkey} />
34
32
  </>
35
33
  );
@@ -40,7 +38,7 @@ export function Root({ canvas, hotkey = "Escape" }: RootProps) {
40
38
  <>
41
39
  <main className="layout">
42
40
  <section className="game">
43
- <GameCanvas canvas={canvas} />
41
+ <CanvasSlot />
44
42
  </section>
45
43
  <section className="stats">
46
44
  <Stats />
@@ -54,7 +52,7 @@ export function Root({ canvas, hotkey = "Escape" }: RootProps) {
54
52
  );
55
53
  }
56
54
 
57
- function LetterboxedLayout({ canvas }: { canvas: HTMLCanvasElement }) {
55
+ function LetterboxedLayout() {
58
56
  const isOnline = debugState.netStatus.value.peers.length > 0;
59
57
  const advantage = debugState.advantage.value ?? 0;
60
58
  const frameTime = debugState.frameTime.value;
@@ -100,7 +98,7 @@ function LetterboxedLayout({ canvas }: { canvas: HTMLCanvasElement }) {
100
98
  displayValue={leftDisplayValue}
101
99
  />
102
100
  <div className={gameClassName}>
103
- <GameCanvas canvas={canvas} />
101
+ <CanvasSlot />
104
102
  </div>
105
103
  <VerticalBar
106
104
  value={rightValue}
@@ -113,19 +111,10 @@ function LetterboxedLayout({ canvas }: { canvas: HTMLCanvasElement }) {
113
111
  );
114
112
  }
115
113
 
116
- function GameCanvas({ canvas }: { canvas: HTMLCanvasElement }) {
117
- const containerRef = useRef<HTMLDivElement>(null);
118
-
119
- useEffect(() => {
120
- const container = containerRef.current;
121
- if (container && !container.contains(canvas)) {
122
- container.appendChild(canvas);
123
- }
124
-
125
- return () => {
126
- // Don't remove canvas on cleanup - it may need to persist
127
- };
128
- }, [canvas]);
129
-
130
- return <div className="canvas-container" ref={containerRef} />;
114
+ function CanvasSlot() {
115
+ return (
116
+ <div className="canvas-container">
117
+ <slot />
118
+ </div>
119
+ );
131
120
  }