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 <noreply@anthropic.com>
This commit is contained in:
Jonathan Hefner 2026-01-19 14:22:00 -06:00
commit dce143e92e
6 changed files with 43 additions and 47 deletions

View file

@ -43,9 +43,15 @@ Use the SDK's style helpers to apply host styling, then reference them in your C
{@includeCode ./patterns.tsx#hostStylingReact} {@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) ## [TODO] Persist data (incl. widget state)

View file

@ -97,15 +97,6 @@ function hostStylingReact() {
//#endregion hostStylingReact //#endregion hostStylingReact
} }
/**
* Example: Support fullscreen / exit fullscreen
*/
function fullscreen() {
//#region fullscreen
// TODO: Implement fullscreen support
//#endregion fullscreen
}
/** /**
* Example: Persist data (incl. widget state) * Example: Persist data (incl. widget state)
*/ */
@ -155,7 +146,6 @@ void authenticatedCalls;
void errorsToModel; void errorsToModel;
void hostStylingVanillaJs; void hostStylingVanillaJs;
void hostStylingReact; void hostStylingReact;
void fullscreen;
void persistData; void persistData;
void lowerPerceivedLatency; void lowerPerceivedLatency;
void iframeAndMcpApps; void iframeAndMcpApps;

View file

@ -769,16 +769,12 @@ function handleHostContextChanged(ctx: McpUiHostContext) {
if (ctx.displayMode) { if (ctx.displayMode) {
const wasFullscreen = currentDisplayMode === "fullscreen"; const wasFullscreen = currentDisplayMode === "fullscreen";
currentDisplayMode = ctx.displayMode as "inline" | "fullscreen"; currentDisplayMode = ctx.displayMode as "inline" | "fullscreen";
if (ctx.displayMode === "fullscreen") { const isFullscreen = currentDisplayMode === "fullscreen";
mainEl.classList.add("fullscreen"); mainEl.classList.toggle("fullscreen", isFullscreen);
log.info("Fullscreen mode enabled"); log.info(isFullscreen ? "Fullscreen mode enabled" : "Inline mode");
} else { // When exiting fullscreen, request resize to fit content
mainEl.classList.remove("fullscreen"); if (wasFullscreen && !isFullscreen && pdfDocument) {
log.info("Inline mode"); requestFitToContent();
// When exiting fullscreen, request resize to fit content
if (wasFullscreen && pdfDocument) {
requestFitToContent();
}
} }
updateFullscreenButton(); updateFullscreenButton();
} }

View file

@ -65,21 +65,14 @@ function handleHostContextChanged(ctx: McpUiHostContext) {
// Show fullscreen button if available (only update if field is present) // Show fullscreen button if available (only update if field is present)
if (ctx.availableDisplayModes !== undefined) { if (ctx.availableDisplayModes !== undefined) {
if (ctx.availableDisplayModes.includes("fullscreen")) { const canFullscreen = ctx.availableDisplayModes.includes("fullscreen");
fullscreenBtn.classList.add("available"); fullscreenBtn.classList.toggle("available", canFullscreen);
} else {
fullscreenBtn.classList.remove("available");
}
} }
// Update display mode state and UI // Update display mode state and UI
if (ctx.displayMode) { if (ctx.displayMode) {
currentDisplayMode = ctx.displayMode as "inline" | "fullscreen"; currentDisplayMode = ctx.displayMode as "inline" | "fullscreen";
if (currentDisplayMode === "fullscreen") { mainEl.classList.toggle("fullscreen", currentDisplayMode === "fullscreen");
mainEl.classList.add("fullscreen");
} else {
mainEl.classList.remove("fullscreen");
}
} }
} }
@ -96,11 +89,7 @@ async function toggleFullscreen() {
try { try {
const result = await app.requestDisplayMode({ mode: newMode }); const result = await app.requestDisplayMode({ mode: newMode });
currentDisplayMode = result.mode as "inline" | "fullscreen"; currentDisplayMode = result.mode as "inline" | "fullscreen";
if (currentDisplayMode === "fullscreen") { mainEl.classList.toggle("fullscreen", currentDisplayMode === "fullscreen");
mainEl.classList.add("fullscreen");
} else {
mainEl.classList.remove("fullscreen");
}
} catch (err) { } catch (err) {
log.error("Failed to change display mode:", err); log.error("Failed to change display mode:", err);
} }

View file

@ -206,6 +206,20 @@ function App_onhostcontextchanged_respondToTheme(app: App) {
//#endregion App_onhostcontextchanged_respondToTheme //#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. * 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) { async function App_requestDisplayMode_toggle(app: App) {
//#region App_requestDisplayMode_fullscreen //#region App_requestDisplayMode_toggle
const context = app.getHostContext(); const ctx = app.getHostContext();
if (context?.availableDisplayModes?.includes("fullscreen")) { if (ctx?.availableDisplayModes?.includes("fullscreen")) {
const result = await app.requestDisplayMode({ mode: "fullscreen" }); const target = ctx.displayMode === "fullscreen" ? "inline" : "fullscreen";
console.log("Display mode set to:", result.mode); const result = await app.requestDisplayMode({ mode: target });
console.log("Now in:", result.mode);
} }
//#endregion App_requestDisplayMode_fullscreen //#endregion App_requestDisplayMode_toggle
} }
/** /**

View file

@ -729,8 +729,8 @@ export class App extends Protocol<AppRequest, AppNotification, AppResult> {
* @param options - Request options (timeout, etc.) * @param options - Request options (timeout, etc.)
* @returns Result containing the actual display mode that was set * @returns Result containing the actual display mode that was set
* *
* @example Request fullscreen mode * @example Toggle display mode
* {@includeCode ./app.examples.ts#App_requestDisplayMode_fullscreen} * {@includeCode ./app.examples.ts#App_requestDisplayMode_toggle}
* *
* @see {@link McpUiRequestDisplayModeRequest} for request structure * @see {@link McpUiRequestDisplayModeRequest} for request structure
* @see {@link McpUiHostContext} for checking availableDisplayModes * @see {@link McpUiHostContext} for checking availableDisplayModes