diff --git a/.gitignore b/.gitignore
index 485647b..91892a1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,3 +2,4 @@ node_modules/
dist/
.history/
docs/plans/
+.env
\ No newline at end of file
diff --git a/docs-site/public/examples/vllm/deployment.html b/docs-site/public/examples/vllm/deployment.html
index dee7142..8d5e8b6 100644
--- a/docs-site/public/examples/vllm/deployment.html
+++ b/docs-site/public/examples/vllm/deployment.html
@@ -849,6 +849,7 @@
}
const expandIcon = ``;
const collapseIcon = ``;
+ const fullscreenTarget = this.getStoryShell() ?? this.canvasWrap ?? this.doc.documentElement;
const syncIcon = () => {
const isFs = !!this.doc.fullscreenElement;
btn.innerHTML = isFs ? collapseIcon : expandIcon;
@@ -861,12 +862,15 @@
btn.style.justifyContent = "center";
btn.addEventListener("click", () => {
if (this.doc.fullscreenElement) {
- this.doc.exitFullscreen();
+ void this.doc.exitFullscreen();
} else {
- this.doc.documentElement.requestFullscreen();
+ void fullscreenTarget.requestFullscreen?.();
}
});
- this.doc.addEventListener("fullscreenchange", syncIcon);
+ this.doc.addEventListener("fullscreenchange", () => {
+ syncIcon();
+ this.onResize?.();
+ });
}
destroy() {
if (this.onResize) window.removeEventListener("resize", this.onResize);
diff --git a/examples/vLLM/deployment.html b/examples/vLLM/deployment.html
index dee7142..8d5e8b6 100644
--- a/examples/vLLM/deployment.html
+++ b/examples/vLLM/deployment.html
@@ -849,6 +849,7 @@
}
const expandIcon = ``;
const collapseIcon = ``;
+ const fullscreenTarget = this.getStoryShell() ?? this.canvasWrap ?? this.doc.documentElement;
const syncIcon = () => {
const isFs = !!this.doc.fullscreenElement;
btn.innerHTML = isFs ? collapseIcon : expandIcon;
@@ -861,12 +862,15 @@
btn.style.justifyContent = "center";
btn.addEventListener("click", () => {
if (this.doc.fullscreenElement) {
- this.doc.exitFullscreen();
+ void this.doc.exitFullscreen();
} else {
- this.doc.documentElement.requestFullscreen();
+ void fullscreenTarget.requestFullscreen?.();
}
});
- this.doc.addEventListener("fullscreenchange", syncIcon);
+ this.doc.addEventListener("fullscreenchange", () => {
+ syncIcon();
+ this.onResize?.();
+ });
}
destroy() {
if (this.onResize) window.removeEventListener("resize", this.onResize);
diff --git a/package-lock.json b/package-lock.json
index eb61f9b..f12e02e 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "@biolytics.ai/diascope",
- "version": "0.1.0",
+ "version": "0.1.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@biolytics.ai/diascope",
- "version": "0.1.0",
+ "version": "0.1.1",
"dependencies": {
"commander": "^12.0.0",
"js-yaml": "^4.1.0",
diff --git a/package.json b/package.json
index fdebc33..80c6f07 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "@biolytics.ai/diascope",
- "version": "0.1.0",
+ "version": "0.1.1",
"description": "Turn D2 diagrams into narrated interactive stories",
"homepage": "https://diascope.biolytics.ai",
"repository": {
diff --git a/src/viewer/viewer.ts b/src/viewer/viewer.ts
index c195f14..d8273a5 100644
--- a/src/viewer/viewer.ts
+++ b/src/viewer/viewer.ts
@@ -364,6 +364,10 @@ export class DiaScopeViewer {
}
const expandIcon = ``;
const collapseIcon = ``;
+ const fullscreenTarget =
+ (this.getStoryShell() ?? this.canvasWrap ?? this.doc.documentElement) as (Element & {
+ requestFullscreen?: () => Promise;
+ });
const syncIcon = () => {
const isFs = !!this.doc.fullscreenElement;
btn!.innerHTML = isFs ? collapseIcon : expandIcon;
@@ -376,12 +380,15 @@ export class DiaScopeViewer {
btn.style.justifyContent = "center";
btn.addEventListener("click", () => {
if (this.doc.fullscreenElement) {
- this.doc.exitFullscreen();
+ void this.doc.exitFullscreen();
} else {
- this.doc.documentElement.requestFullscreen();
+ void fullscreenTarget.requestFullscreen?.();
}
});
- this.doc.addEventListener("fullscreenchange", syncIcon);
+ this.doc.addEventListener("fullscreenchange", () => {
+ syncIcon();
+ this.onResize?.();
+ });
}
destroy(): void {
diff --git a/tests/viewer.test.ts b/tests/viewer.test.ts
new file mode 100644
index 0000000..7f22ffb
--- /dev/null
+++ b/tests/viewer.test.ts
@@ -0,0 +1,120 @@
+import { describe, expect, it, vi } from "vitest";
+import { DiaScopeViewer } from "../src/viewer/viewer.js";
+
+class FakeElement {
+ readonly style: Record = {};
+ readonly children: FakeElement[] = [];
+ readonly listeners = new Map void>>();
+ readonly attributes = new Map();
+ innerHTML = "";
+ title = "";
+ id = "";
+ requestFullscreen = vi.fn(() => Promise.resolve());
+
+ constructor(private readonly doc: FakeDocument, id = "") {
+ this.id = id;
+ if (id) this.attributes.set("id", id);
+ }
+
+ setAttribute(name: string, value: string): void {
+ this.attributes.set(name, value);
+ if (name === "id") {
+ this.id = value;
+ this.doc.register(this);
+ }
+ }
+
+ getAttribute(name: string): string | null {
+ return this.attributes.get(name) ?? null;
+ }
+
+ appendChild(child: FakeElement): void {
+ this.children.push(child);
+ if (child.id) this.doc.register(child);
+ }
+
+ addEventListener(type: string, listener: () => void): void {
+ const current = this.listeners.get(type) ?? [];
+ current.push(listener);
+ this.listeners.set(type, current);
+ }
+
+ click(): void {
+ for (const listener of this.listeners.get("click") ?? []) listener();
+ }
+}
+
+class FakeDocument {
+ readonly elements = new Map();
+ readonly listeners = new Map void>>();
+ readonly documentElement = new FakeElement(this, "document-element");
+ fullscreenElement: FakeElement | null = null;
+ exitFullscreen = vi.fn(() => {
+ this.fullscreenElement = null;
+ this.dispatch("fullscreenchange");
+ return Promise.resolve();
+ });
+
+ register(element: FakeElement): FakeElement {
+ if (element.id) this.elements.set(element.id, element);
+ return element;
+ }
+
+ createElement(): FakeElement {
+ return new FakeElement(this);
+ }
+
+ getElementById(id: string): FakeElement | null {
+ return this.elements.get(id) ?? null;
+ }
+
+ querySelector(selector: string): FakeElement | null {
+ if (!selector.startsWith("#")) return null;
+ return this.getElementById(selector.slice(1));
+ }
+
+ addEventListener(type: string, listener: () => void): void {
+ const current = this.listeners.get(type) ?? [];
+ current.push(listener);
+ this.listeners.set(type, current);
+ }
+
+ dispatch(type: string): void {
+ for (const listener of this.listeners.get(type) ?? []) listener();
+ }
+}
+
+describe("DiaScopeViewer expand button", () => {
+ it("fullscreens the viewer shell instead of the whole document when embedded", () => {
+ const doc = new FakeDocument();
+ const canvasWrap = doc.register(new FakeElement(doc, "canvas-wrap"));
+ const storyShell = doc.register(new FakeElement(doc, "story-shell"));
+ const resizeSpy = vi.fn();
+
+ storyShell.requestFullscreen.mockImplementation(() => {
+ doc.fullscreenElement = storyShell;
+ doc.dispatch("fullscreenchange");
+ return Promise.resolve();
+ });
+
+ doc.documentElement.requestFullscreen.mockImplementation(() => {
+ doc.fullscreenElement = doc.documentElement;
+ doc.dispatch("fullscreenchange");
+ return Promise.resolve();
+ });
+
+ const viewer = new DiaScopeViewer({
+ document: doc as unknown as Document,
+ svgPanZoom: (() => null) as never,
+ });
+ viewer.canvasWrap = canvasWrap as unknown as HTMLElement;
+ viewer.onResize = resizeSpy;
+
+ viewer.setupExpandButton();
+ doc.getElementById("btn-expand")?.click();
+
+ expect(storyShell.requestFullscreen).toHaveBeenCalledTimes(1);
+ expect(doc.documentElement.requestFullscreen).not.toHaveBeenCalled();
+ expect(resizeSpy).toHaveBeenCalledTimes(1);
+ });
+});