From dce143e92e1ef38b34afe54a57fe8da20c6eb5b8 Mon Sep 17 00:00:00 2001 From: Jonathan Hefner Date: Mon, 19 Jan 2026 14:22:00 -0600 Subject: [PATCH] Complete "Entering / exiting fullscreen" pattern documentation Add `App_onhostcontextchanged_respondToDisplayMode` example showing how to listen for display mode changes and update UI with `classList.toggle()`. Update `App_requestDisplayMode_fullscreen` to `App_requestDisplayMode_toggle` to show the common toggle pattern checking `availableDisplayModes` first. Refactor full examples (pdf-server, shadertoy-server) to use `classList.toggle(class, condition)` instead of add/remove branches. Co-Authored-By: Claude Opus 4.5 --- docs/patterns.md | 10 ++++++-- docs/patterns.tsx | 10 -------- examples/pdf-server/src/mcp-app.ts | 16 +++++------- examples/shadertoy-server/src/mcp-app.ts | 19 +++------------ src/app.examples.ts | 31 ++++++++++++++++++------ src/app.ts | 4 +-- 6 files changed, 43 insertions(+), 47 deletions(-) diff --git a/docs/patterns.md b/docs/patterns.md index e10210f..4232554 100644 --- a/docs/patterns.md +++ b/docs/patterns.md @@ -43,9 +43,15 @@ Use the SDK's style helpers to apply host styling, then reference them in your C {@includeCode ./patterns.tsx#hostStylingReact} -## [TODO] Support fullscreen / exit fullscreen +## Entering / Exiting fullscreen -{@includeCode ./patterns.tsx#fullscreen} +Toggle fullscreen mode by calling {@link app!App.requestDisplayMode requestDisplayMode}: + +{@includeCode ../src/app.examples.ts#App_requestDisplayMode_toggle} + +Listen for display mode changes via {@link app!App.onhostcontextchanged onhostcontextchanged} to update your UI: + +{@includeCode ../src/app.examples.ts#App_onhostcontextchanged_respondToDisplayMode} ## [TODO] Persist data (incl. widget state) diff --git a/docs/patterns.tsx b/docs/patterns.tsx index 6d6bfcc..9a2f3dd 100644 --- a/docs/patterns.tsx +++ b/docs/patterns.tsx @@ -97,15 +97,6 @@ function hostStylingReact() { //#endregion hostStylingReact } -/** - * Example: Support fullscreen / exit fullscreen - */ -function fullscreen() { - //#region fullscreen - // TODO: Implement fullscreen support - //#endregion fullscreen -} - /** * Example: Persist data (incl. widget state) */ @@ -155,7 +146,6 @@ void authenticatedCalls; void errorsToModel; void hostStylingVanillaJs; void hostStylingReact; -void fullscreen; void persistData; void lowerPerceivedLatency; void iframeAndMcpApps; diff --git a/examples/pdf-server/src/mcp-app.ts b/examples/pdf-server/src/mcp-app.ts index b5b2419..28bf936 100644 --- a/examples/pdf-server/src/mcp-app.ts +++ b/examples/pdf-server/src/mcp-app.ts @@ -769,16 +769,12 @@ function handleHostContextChanged(ctx: McpUiHostContext) { if (ctx.displayMode) { const wasFullscreen = currentDisplayMode === "fullscreen"; currentDisplayMode = ctx.displayMode as "inline" | "fullscreen"; - if (ctx.displayMode === "fullscreen") { - mainEl.classList.add("fullscreen"); - log.info("Fullscreen mode enabled"); - } else { - mainEl.classList.remove("fullscreen"); - log.info("Inline mode"); - // When exiting fullscreen, request resize to fit content - if (wasFullscreen && pdfDocument) { - requestFitToContent(); - } + const isFullscreen = currentDisplayMode === "fullscreen"; + mainEl.classList.toggle("fullscreen", isFullscreen); + log.info(isFullscreen ? "Fullscreen mode enabled" : "Inline mode"); + // When exiting fullscreen, request resize to fit content + if (wasFullscreen && !isFullscreen && pdfDocument) { + requestFitToContent(); } updateFullscreenButton(); } diff --git a/examples/shadertoy-server/src/mcp-app.ts b/examples/shadertoy-server/src/mcp-app.ts index d19c8f1..546de5a 100644 --- a/examples/shadertoy-server/src/mcp-app.ts +++ b/examples/shadertoy-server/src/mcp-app.ts @@ -65,21 +65,14 @@ function handleHostContextChanged(ctx: McpUiHostContext) { // Show fullscreen button if available (only update if field is present) if (ctx.availableDisplayModes !== undefined) { - if (ctx.availableDisplayModes.includes("fullscreen")) { - fullscreenBtn.classList.add("available"); - } else { - fullscreenBtn.classList.remove("available"); - } + const canFullscreen = ctx.availableDisplayModes.includes("fullscreen"); + fullscreenBtn.classList.toggle("available", canFullscreen); } // Update display mode state and UI if (ctx.displayMode) { currentDisplayMode = ctx.displayMode as "inline" | "fullscreen"; - if (currentDisplayMode === "fullscreen") { - mainEl.classList.add("fullscreen"); - } else { - mainEl.classList.remove("fullscreen"); - } + mainEl.classList.toggle("fullscreen", currentDisplayMode === "fullscreen"); } } @@ -96,11 +89,7 @@ async function toggleFullscreen() { try { const result = await app.requestDisplayMode({ mode: newMode }); currentDisplayMode = result.mode as "inline" | "fullscreen"; - if (currentDisplayMode === "fullscreen") { - mainEl.classList.add("fullscreen"); - } else { - mainEl.classList.remove("fullscreen"); - } + mainEl.classList.toggle("fullscreen", currentDisplayMode === "fullscreen"); } catch (err) { log.error("Failed to change display mode:", err); } diff --git a/src/app.examples.ts b/src/app.examples.ts index 7ff766d..4c09796 100644 --- a/src/app.examples.ts +++ b/src/app.examples.ts @@ -206,6 +206,20 @@ function App_onhostcontextchanged_respondToTheme(app: App) { //#endregion App_onhostcontextchanged_respondToTheme } +/** + * Example: Respond to display mode changes using onhostcontextchanged. + */ +function App_onhostcontextchanged_respondToDisplayMode(app: App) { + //#region App_onhostcontextchanged_respondToDisplayMode + app.onhostcontextchanged = (params) => { + if (params.displayMode) { + const isFullscreen = params.displayMode === "fullscreen"; + document.body.classList.toggle("fullscreen", isFullscreen); + } + }; + //#endregion App_onhostcontextchanged_respondToDisplayMode +} + /** * Example: Perform cleanup before teardown. */ @@ -344,16 +358,17 @@ async function App_openLink_documentation(app: App) { } /** - * Example: Request fullscreen mode. + * Example: Toggle between inline and fullscreen display modes. */ -async function App_requestDisplayMode_fullscreen(app: App) { - //#region App_requestDisplayMode_fullscreen - const context = app.getHostContext(); - if (context?.availableDisplayModes?.includes("fullscreen")) { - const result = await app.requestDisplayMode({ mode: "fullscreen" }); - console.log("Display mode set to:", result.mode); +async function App_requestDisplayMode_toggle(app: App) { + //#region App_requestDisplayMode_toggle + const ctx = app.getHostContext(); + if (ctx?.availableDisplayModes?.includes("fullscreen")) { + const target = ctx.displayMode === "fullscreen" ? "inline" : "fullscreen"; + const result = await app.requestDisplayMode({ mode: target }); + console.log("Now in:", result.mode); } - //#endregion App_requestDisplayMode_fullscreen + //#endregion App_requestDisplayMode_toggle } /** diff --git a/src/app.ts b/src/app.ts index dee12b8..ab5fabd 100644 --- a/src/app.ts +++ b/src/app.ts @@ -729,8 +729,8 @@ export class App extends Protocol { * @param options - Request options (timeout, etc.) * @returns Result containing the actual display mode that was set * - * @example Request fullscreen mode - * {@includeCode ./app.examples.ts#App_requestDisplayMode_fullscreen} + * @example Toggle display mode + * {@includeCode ./app.examples.ts#App_requestDisplayMode_toggle} * * @see {@link McpUiRequestDisplayModeRequest} for request structure * @see {@link McpUiHostContext} for checking availableDisplayModes