Skip to content

feat: Add Fullscreen API wrapping the browser Fullscreen API#24326

Open
Artur- wants to merge 5 commits into
mainfrom
feature/fullscreen
Open

feat: Add Fullscreen API wrapping the browser Fullscreen API#24326
Artur- wants to merge 5 commits into
mainfrom
feature/fullscreen

Conversation

@Artur-
Copy link
Copy Markdown
Member

@Artur- Artur- commented May 12, 2026

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.

Artur- added 3 commits May 11, 2026 09:06
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.
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 12, 2026

Test Results

 1 408 files  + 2   1 408 suites  +2   1h 21m 45s ⏱️ -44s
10 159 tests +20  10 089 ✅ +20  70 💤 ±0  0 ❌ ±0 
10 634 runs  +20  10 555 ✅ +20  79 💤 ±0  0 ❌ ±0 

Results for commit b65531b. ± Comparison against base commit 32183d1.

♻️ This comment has been updated with latest results.

Artur- added 2 commits May 12, 2026 16:59
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.
@sonarqubecloud
Copy link
Copy Markdown

Artur- added a commit to vaadin/use-cases that referenced this pull request May 13, 2026
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.
@Artur-
Copy link
Copy Markdown
Member Author

Artur- commented May 15, 2026

This should be based on trigger/action type API

@mshabarov mshabarov requested a review from tltv May 18, 2026 12:41
Copy link
Copy Markdown
Member

@tltv tltv left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This field looks unused, only set but not read.

Artur- added a commit that referenced this pull request May 22, 2026
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

Status: 🔎Iteration reviews

Development

Successfully merging this pull request may close these issues.

3 participants