Skip to content

Commit 82e64d5

Browse files
committed
Separate viewport and containerDimensions
1 parent c22e862 commit 82e64d5

File tree

6 files changed

+320
-110
lines changed

6 files changed

+320
-110
lines changed

specification/draft/apps.mdx

Lines changed: 37 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -461,16 +461,20 @@ interface HostContext {
461461
displayMode?: "inline" | "fullscreen" | "pip";
462462
/** Display modes the host supports */
463463
availableDisplayModes?: string[];
464-
/** Current and maximum dimensions available to the UI. */
464+
/** Container dimensions for the iframe. Specify either width or maxWidth, and either height or maxHeight. */
465+
containerDimensions?: (
466+
| { height: number } // If specified, container is fixed at this height
467+
| { maxHeight?: number } // Otherwise, container height is determined by the UI height, up to this maximum height (if defined)
468+
) & (
469+
| { width: number } // If specified, container is fixed at this width
470+
| { maxWidth?: number } // Otherwise, container width is determined by the UI width, up to this maximum width (if defined)
471+
);
472+
/** Host window viewport dimensions */
465473
viewport?: {
466-
/** Viewport width (if fixed). Only pass width or maxWidth, not both. */
474+
/** Window viewport width in pixels. */
467475
width?: number;
468-
/** Viewport height (if fixed). Only pass height or maxHeight, not both. */
476+
/** Window viewport height in pixels. */
469477
height?: number;
470-
/** Maximum available viewport width in pixels (if constrained). */
471-
maxWidth?: number;
472-
/** Maximum available viewport height in pixels (if constrained). */
473-
maxHeight?: number;
474478
};
475479
/** User's language/region preference (BCP 47, e.g., "en-US") */
476480
locale?: string;
@@ -520,20 +524,25 @@ Example:
520524
}
521525
},
522526
"displayMode": "inline",
523-
"viewport": { "width": 400, "maxHeight": 600 }
527+
"containerDimensions": { "width": 400, "maxHeight": 600 }
528+
"viewport": { "width": 1920, "height": 1080 },
524529
}
525530
}
526531
}
527532
```
528533

529-
### Viewport and Sizing
534+
### Viewport and Dimensions
530535

531-
The `viewport` field in `HostContext` communicates sizing constraints between host and app. Each dimension (height and width) operates independently and can be either **fixed** or **flexible**.
536+
The `HostContext` provides two separate fields for sizing information:
532537

533-
#### Viewport Modes
538+
- **`containerDimensions`**: The dimensions of the container that holds the app. This controls the actual space the app occupies within the host. Each dimension (height and width) operates independently and can be either **fixed** or **flexible**.
534539

535-
| Mode | Viewport Field | Meaning |
536-
|------|---------------|---------|
540+
- **`viewport`**: The host window's dimensions (e.g., `window.innerWidth` and `window.innerHeight`). Apps can use this to make responsive layout decisions based on the overall screen size.
541+
542+
#### Dimension Modes
543+
544+
| Mode | Dimensions Field | Meaning |
545+
|------|-----------------|---------|
537546
| Fixed | `height` or `width` | Host controls the size. App should fill the available space. |
538547
| Flexible | `maxHeight` or `maxWidth` | App controls the size, up to the specified maximum. |
539548
| Unbounded | Field omitted | App controls the size with no limit. |
@@ -542,33 +551,39 @@ These modes can be combined independently. For example, a host might specify a f
542551

543552
#### App Behavior
544553

545-
Apps should check the viewport configuration and apply appropriate CSS:
554+
Apps should check the containerDimensions configuration and apply appropriate CSS:
546555

547556
```typescript
548557
// In the app's initialization
549-
const viewport = hostContext.viewport;
558+
const containerDimensions = hostContext.containerDimensions;
550559

551-
if (viewport) {
560+
if (containerDimensions) {
552561
// Handle height
553-
if ("height" in viewport) {
562+
if ("height" in containerDimensions) {
554563
// Fixed height: fill the container
555564
document.documentElement.style.height = "100vh";
556-
} else if (viewport.maxHeight) {
565+
} else if ("maxHeight" in containerDimensions && containerDimensions.maxHeight) {
557566
// Flexible with max: let content determine size, up to max
558-
document.documentElement.style.maxHeight = `${viewport.maxHeight}px`;
567+
document.documentElement.style.maxHeight = `${containerDimensions.maxHeight}px`;
559568
}
560569
// If neither, height is unbounded
561570

562571
// Handle width
563-
if ("width" in viewport) {
572+
if ("width" in containerDimensions) {
564573
// Fixed width: fill the container
565574
document.documentElement.style.width = "100vw";
566-
} else if (viewport.maxWidth) {
575+
} else if ("maxWidth" in containerDimensions && containerDimensions.maxWidth) {
567576
// Flexible with max: let content determine size, up to max
568-
document.documentElement.style.maxWidth = `${viewport.maxWidth}px`;
577+
document.documentElement.style.maxWidth = `${containerDimensions.maxWidth}px`;
569578
}
570579
// If neither, width is unbounded
571580
}
581+
582+
// Apps can also use viewport for additional data to make responsive layout decisions
583+
const viewport = hostContext.viewport;
584+
if (viewport?.width && viewport.width < 768) {
585+
// Apply mobile-friendly layout
586+
}
572587
```
573588

574589
#### Host Behavior

src/app-bridge.test.ts

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,8 @@ describe("App <-> AppBridge integration", () => {
113113
const testHostContext = {
114114
theme: "dark" as const,
115115
locale: "en-US",
116-
viewport: { width: 800, height: 600, maxHeight: 600 },
116+
viewport: { width: 800, height: 600 },
117+
containerDimensions: { width: 800, maxHeight: 600 },
117118
};
118119
const newBridge = new AppBridge(
119120
createMockClient() as Client,
@@ -337,7 +338,8 @@ describe("App <-> AppBridge integration", () => {
337338
const initialContext = {
338339
theme: "light" as const,
339340
locale: "en-US",
340-
viewport: { width: 800, height: 600, maxHeight: 600 },
341+
viewport: { width: 800, height: 600 },
342+
containerDimensions: { width: 800, maxHeight: 600 },
341343
};
342344
const newBridge = new AppBridge(
343345
createMockClient() as Client,
@@ -354,22 +356,26 @@ describe("App <-> AppBridge integration", () => {
354356
newBridge.sendHostContextChange({ theme: "dark" });
355357
await flush();
356358

357-
// Send another partial update: only viewport changes
359+
// Send another partial update: only viewport and containerDimensions change
358360
newBridge.sendHostContextChange({
359-
viewport: { width: 1024, height: 768, maxHeight: 768 },
361+
viewport: { width: 1024, height: 768 },
362+
containerDimensions: { width: 1024, maxHeight: 768 },
360363
});
361364
await flush();
362365

363366
// getHostContext should have accumulated all updates:
364367
// - locale from initial (unchanged)
365368
// - theme from first partial update
366-
// - viewport from second partial update
369+
// - viewport and containerDimensions from second partial update
367370
const context = newApp.getHostContext();
368371
expect(context?.theme).toBe("dark");
369372
expect(context?.locale).toBe("en-US");
370373
expect(context?.viewport).toEqual({
371374
width: 1024,
372375
height: 768,
376+
});
377+
expect(context?.containerDimensions).toEqual({
378+
width: 1024,
373379
maxHeight: 768,
374380
});
375381

src/app-bridge.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1008,7 +1008,8 @@ export class AppBridge extends Protocol<
10081008
* ```typescript
10091009
* bridge.setHostContext({
10101010
* theme: "dark",
1011-
* viewport: { width: 800, maxHeight: 600 }
1011+
* viewport: { width: 800, height: 600 },
1012+
* containerDimensions: { maxHeight: 600, width: 800 }
10121013
* });
10131014
* ```
10141015
*

0 commit comments

Comments
 (0)