feat: Add Fullscreen API wrapping the browser Fullscreen API#24326
feat: Add Fullscreen API wrapping the browser Fullscreen API#24326Artur- wants to merge 5 commits into
Conversation
Adds Component.requestFullscreen() that uses a wrapper approach to handle Vaadin theming and overlay components correctly, along with Page.requestFullscreen() for whole-page fullscreen, Page.exitFullscreen(), and Page.fullscreenSignal() for reactive observation of the fullscreen state (FULLSCREEN, NOT_FULLSCREEN, UNSUPPORTED, UNKNOWN). The signal is seeded from the initial client bootstrap (v-fs parameter) and updated via a vaadin-fullscreen-change DOM event, mirroring the page-visibility wiring. The client-side bridge lives in flow-client/src/main/frontend/Fullscreen.ts, imported from Flow.ts the same way Geolocation.ts is. Fixes #21902
The file already has an exported function (currentFullscreenState), so
the trailing `export {}` is flagged by
@typescript-eslint/no-useless-empty-export and breaks the flow-client
build.
Reworks the Fullscreen API based on API-GAPS feedback:
- Page.requestFullscreen() and Component.requestFullscreen() now return
a FullscreenSession with a state signal carrying PENDING / ACTIVE /
REJECTED / EXITED_BY_USER / EXITED_BY_CODE. This covers three gaps in
one shape: which component is fullscreen (session.owner()), how the
session ended (terminal state), and whether the browser accepted the
request (PENDING -> ACTIVE or REJECTED).
- The JS connector now returns { ok, error? } from each request so
rejections (no user activation, permissions policy, document detached,
fullscreen unsupported) reach the server and are logged at WARN level
with the browser-provided message.
- Page#requestFullscreen(Component) overload lets code that already
holds a Page reference start a component session without reaching
through the component.
- Component#exitFullscreen() restores symmetry with requestFullscreen(),
delegating to Page#exitFullscreen() so component code does not need
to reach for getUI().getPage().
- Page#simulateFullscreenChange(FullscreenState) exposes a public
test-only seam so downstream tests no longer have to reflect into the
package-private setter.
- Javadoc on Page#fullscreenSignal() and Page#requestFullscreen() now
carries worked binding / lifecycle examples.
The page-level Page#fullscreenSignal() is unchanged: it answers the
broad "is anything fullscreen right now" question across sessions.
Javadoc rejects H2 inside a method comment because the surrounding context already uses H3, breaking attach-javadocs in the unit-tests (3) shard. Switch to the <p><b>...</b></p> pattern Geolocation uses, which renders the same in javadoc output.
Replaces the toggle-button example on Page#fullscreenSignal() with the two patterns most useful in practice — bindClassName for declarative CSS toggling while fullscreen, and Signal.effect for side-effecting reactions such as hiding a button when fullscreen is UNSUPPORTED. The toggle-button example was correct but covered three patterns at once; downstream use cases (chart card expand, "fullscreen unavailable" empty states) needed the focused shapes instead.
|
Adds a sibling `fullscreen` module mirroring `geolocation` and `page-visibility`, demonstrating Component#requestFullscreen(), Page#requestFullscreen(), Page#fullscreenSignal(), FullscreenSession and Component#exitFullscreen() across six self-contained views: - image lightbox using per-component fullscreen (UC1) - slideshow / presentation via Page#requestFullscreen() (UC2) - distraction-free editor exiting through Component#exitFullscreen() (UC3) - reactive layout bound directly to fullscreenSignal() (UC4) - kiosk exit detection via FullscreenSession state (UC5) - chart expand using session.owner() to scope the active card (UC6) Pinned to flow 25.2.fullscreen-SNAPSHOT (vaadin/flow#24326) until the API lands in mainline. Remaining API gaps surfaced during the work are recorded in fullscreen/API-GAPS.md.
|
This should be based on trigger/action type API |
tltv
left a comment
There was a problem hiding this comment.
Along the changes from other mentioned pull requests, new API could also take in account the fullscreen component's visibility state.
requestFullscreen throws if component is not attached to UI yet, so it could at minimum also throw if its not visible. Better idea, since visibility can change, is to postpone executeJs of startFullscreenSession to component's attach event once, and run it only if component is visible. Hidden component may throw error in Fullscreen.ts (not sure, since there's no ITs).
A unit test that verifies that new ts import is in correct place would be nice.
Some ITs that verify that typescript is working would be nice. Need to use --headless=new and trigger the request from a real click via TestBench.
| private final Signal<FullscreenState> fullscreenSignalReadOnly = fullscreenSignal | ||
| .asReadonly(); | ||
| private @Nullable FullscreenSession currentFullscreenSession; | ||
| private boolean expectProgrammaticFullscreenExit; |
There was a problem hiding this comment.
This field looks unused, only set but not read.
Addresses the inline review comments on PR #24394: PromiseAction - Drop the Geolocation cross-link from the javadoc — it was a stale inline reference, not load-bearing API documentation. - Replace `onSuccess: Runnable` / `onError: Consumer<String>` with typed records: `onSuccess: Consumer<Success>` carrying the resolved value (when the subclass' promise produced one) and `onError: Consumer<Error>` carrying the rejection's name and message. The error name (typically a `DOMException` class like "NotAllowedError") is what callers usually want to switch on. - Update the wire shape accordingly: `{ok, value, error: {name, message}}`. The dispatch now decodes one `Outcome` record via Jackson and constructs `Success`/`Error` from it. - Tighten the fire-and-forget detection to check both callbacks (defensive against future constructor changes that might break the both-null-or-both-non-null invariant). - Add a javadoc note explaining `final` on `appendStatement` — subclasses customise via `appendPromiseExpression`, not by overriding the wiring; that's what keeps the `Outcome` wire contract stable. CopyTextToClipboardAction - Constructor takes `Consumer<String> onCopied` (typed) and adapts to PromiseAction's `Consumer<Success>` internally. The promise expression is wrapped in an IIFE `((v) => writeText(v).then(() => v))(<textExpr>)` so the resolved value is the copied string — the server sees what actually reached the clipboard even when the input was a client-side `PropertyInput`. RequestFullscreenAction - Clarify the "low-level" javadoc note: explicitly point at PR #24326 (`Component.requestFullscreen()`) as the higher-level facade that wraps the element for Vaadin theming/overlays; this action is the trigger-framework primitive it builds on. - Constructor stays `Runnable onSuccess` (no meaningful value to deliver) but `Consumer<Error>` for the error path. Tests - PromiseActionTest splits the channel-invocation cases into "value present", "value missing", and "error with name+message". - CopyTextToClipboardActionTest gains an `onCopied_receivesTheString` test and updates the rendered-JS assertions to the IIFE wrapper. - ITs use the new typed callbacks; the rejecting shims now throw `new DOMException('DeniedByTest', 'NotAllowedError')` so the IT can assert both `err.name()` and `err.message()` reach the server.



Adds Component.requestFullscreen() that uses a wrapper approach to handle
Vaadin theming and overlay components correctly, along with
Page.requestFullscreen() for whole-page fullscreen, Page.exitFullscreen(),
and Page.fullscreenSignal() for reactive observation of the fullscreen
state (FULLSCREEN, NOT_FULLSCREEN, UNSUPPORTED, UNKNOWN).
The signal is seeded from the initial client bootstrap (v-fs parameter)
and updated via a vaadin-fullscreen-change DOM event, mirroring the
page-visibility wiring.