Skip to content

Commit 3b3fb5a

Browse files
obiotclaude
andcommitted
review(application): add isFullscreen, clean up the device fullscreen probes (#1467)
Round-out of the move to app-instance fullscreen: - **`Application#isFullscreen`** added so the trio sits together (`isFullscreen` / `requestFullscreen` / `exitFullscreen`). Defaults the documented example to `app.isFullscreen()` instead of mixing in `me.device.isFullscreen()`. - **`device.isFullscreen` deprecated** alongside the other two fullscreen statics. Same `since 19.7.0 — use Application#…` pointer. - The four example sites that still called `device.isFullscreen()` switch to `_app.isFullscreen()` / `game.isFullscreen()` so the fullscreen path is consistently app-instance in user-facing code. - The new `Application#requestFullscreen` JSDoc names `parentElement` directly (with a backlink to {@link Application#getParentElement}) instead of the vaguer "canvas parent element" phrasing. Tag-along cleanup of the deprecated device wrappers themselves: the `prefixed("fullscreenEnabled", ...)` / `prefixed("fullscreenElement", ...)` / `prefixed("requestFullscreen", ...)` calls iterated 5 vendor prefixes per probe via the `prefixed()` helper, with awkward `as unknown as Record<string, unknown>` casts. Replaced with an explicit four-variant OR chain (`fullscreenEnabled || webkit… || moz… || ms…`), the same pattern lib.dom.d.ts uses and what every MDN recipe recommends in 2026. `DocumentLegacy` / `ElementLegacy` gain the missing `webkit*` / `ms*` typings. `requestFullscreen` also `.catch()`-es the Promise the modern (unprefixed) call returns — the vendor-prefixed variants returned undefined so the guard `if (result instanceof Promise)` cleanly covers both. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent d78126c commit 3b3fb5a

7 files changed

Lines changed: 53 additions & 32 deletions

File tree

packages/examples/src/examples/platformer-matter/createGame.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ export const createGame = () => {
125125
audio.setVolume(Math.max(0, audio.getVolume() - 0.1));
126126
}
127127
if (keyCode === input.KEY.F) {
128-
if (!device.isFullscreen()) {
128+
if (!_app.isFullscreen()) {
129129
_app.requestFullscreen();
130130
} else {
131131
_app.exitFullscreen();

packages/examples/src/examples/platformer-matter/entities/HUD.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ class FSControl extends UISpriteElement {
4949
* function called when the object is clicked on
5050
*/
5151
onClick(/* event */) {
52-
if (!device.isFullscreen()) {
52+
if (!game.isFullscreen()) {
5353
game.requestFullscreen();
5454
} else {
5555
game.exitFullscreen();

packages/examples/src/examples/platformer/createGame.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ export const createGame = () => {
7575
audio.setVolume(audio.getVolume() - 0.1);
7676
}
7777
if (keyCode === input.KEY.F) {
78-
if (!device.isFullscreen()) {
78+
if (!_app.isFullscreen()) {
7979
_app.requestFullscreen();
8080
} else {
8181
_app.exitFullscreen();

packages/examples/src/examples/platformer/entities/HUD.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ class FSControl extends UISpriteElement {
4949
* function called when the object is clicked on
5050
*/
5151
onClick(/* event */) {
52-
if (!device.isFullscreen()) {
52+
if (!game.isFullscreen()) {
5353
game.requestFullscreen();
5454
} else {
5555
game.exitFullscreen();

packages/melonjs/CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
- **`device.platform.isMobile` no longer ORs the dead-platform regexes** (#1467). `wp` / `BlackBerry` / `Kindle` regexes were burning cycles on every page load testing for hardware nobody ships (Windows Phone EOL 2017, BB10 EOL 2016, Kindle behaves like Android anyway). The remaining chain — `/Mobi/.test(ua) || iOS || android` — covers ~99.9% of mobile traffic in 2026 per MDN. The deprecated exports themselves still compute and return; only the `isMobile` aggregate stopped consulting them.
3636
- **`initKeyboardEvent` no longer skips listener registration on `isMobile === true`** (#1467). The gate assumed "mobile = no physical keyboard" — invalid for iPads with Magic Keyboard (now correctly detected per the iPad fix above), Samsung DeX, ChromeOS tablet mode, Bluetooth-keyboard-on-phone, etc. Two empty listener slots cost nothing on touch-only devices; the unbound-key path is a single map lookup that returns undefined.
3737
- **`system/device` converted to TypeScript** (#1467, renamed from `device.js` → `device.ts`). 945 lines / 53 exports / 56 JSDoc blocks of feature-detection helpers and platform plumbing now ship as a `.ts` file with native type signatures. JSDoc was already exhaustive, so the conversion is mostly mechanical — `@param {Type}` blocks become parameter annotations and `@type {Type}` constants get TS-inferred. Non-standard / legacy browser surfaces (`Document.mozFullScreenEnabled`, `Navigator.standalone` / `browserLanguage` / `userLanguage`, iOS-only `DeviceOrientationEvent.requestPermission`, deprecated `Screen.lockOrientation`, `webkitAudioContext`) are typed via narrow local intersection types declared at the top of the file. Behavioural parity verified against the full 3975-test suite; downstream call sites (`pointerevent.ts`, `application.ts`, `resize.ts`, `header.ts`, etc.) are unchanged thanks to bundler-resolution rewriting `.js` imports to `.ts` source. One small correctness improvement fell out of the conversion: `onDeviceMotion` now guards against `accelerationIncludingGravity === null` rather than crashing.
38-
- **`Application#requestFullscreen` / `Application#exitFullscreen`** — fullscreen control finally has app-instance context. Defaults to fullscreening the app's `parentElement` (so the canvas + any sibling HUD go fullscreen together); accepts an optional `Element` override. The two examples that wire `F` → toggle fullscreen (platformer + platformer-matter) migrate to the new API. No deprecated `getParent()` / global-game lookup involved — the canonical fullscreen path now reaches the canvas through the Application it was created on.
38+
- **`Application#requestFullscreen` / `Application#exitFullscreen` / `Application#isFullscreen`** — fullscreen control finally has app-instance context. `requestFullscreen` defaults to the app's `parentElement` (the container the canvas was appended into — `getParentElement()`), so the canvas plus any sibling HUD / overlay markup inside that container go fullscreen together; accepts an optional `Element` override. `isFullscreen` is the matching probe (delegates to the underlying document check). The two examples that wire `F` → toggle fullscreen (platformer + platformer-matter) migrate to the new API. No deprecated `getParent()` / global-game lookup involved — the canonical fullscreen path now reaches the canvas through the Application it was created on.
3939

4040
### Deprecated
4141
- **`device.requestFullscreen()` / `device.exitFullscreen()`** (#1467, since 19.7.0). Use `app.requestFullscreen()` / `app.exitFullscreen()` instead. The device wrappers still work for backwards compat through the 19.x line but rely on the deprecated global-game canvas lookup (`getParent()``game.getParentElement()`, deprecated since 18.3.0).

packages/melonjs/src/application/application.ts

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -656,23 +656,34 @@ export default class Application {
656656
}
657657

658658
/**
659-
* Trigger a fullscreen request for this application. Defaults to fullscreening
660-
* the canvas parent element, so the rendered viewport (and any sibling HUD)
661-
* goes fullscreen together.
662-
* @param element - optional element to fullscreen instead of the canvas parent
659+
* Returns `true` if the browser/device is currently in fullscreen mode.
660+
* @category Application
661+
*/
662+
isFullscreen(): boolean {
663+
// eslint-disable-next-line @typescript-eslint/no-deprecated -- device.isFullscreen is the canonical probe; only the public API surface moved
664+
return device.isFullscreen();
665+
}
666+
667+
/**
668+
* Trigger a fullscreen request for this application. Defaults to this
669+
* application's `parentElement` (the container the canvas was appended
670+
* into — see {@link Application#getParentElement}), so the canvas and
671+
* any sibling HUD / overlay markup inside that container go fullscreen
672+
* together.
673+
* @param element - optional element to fullscreen instead of `this.parentElement`
663674
* @example
664675
* // bind F to toggle fullscreen
665676
* me.input.bindKey(me.input.KEY.F, "toggleFullscreen");
666677
* me.event.on(me.event.KEYDOWN, (action) => {
667678
* if (action === "toggleFullscreen") {
668-
* if (!me.device.isFullscreen()) app.requestFullscreen();
679+
* if (!app.isFullscreen()) app.requestFullscreen();
669680
* else app.exitFullscreen();
670681
* }
671682
* });
672683
* @category Application
673684
*/
674685
requestFullscreen(element?: Element): void {
675-
if (device.hasFullscreenSupport && !device.isFullscreen()) {
686+
if (device.hasFullscreenSupport && !this.isFullscreen()) {
676687
const target = element ?? this.parentElement;
677688
target.requestFullscreen?.().catch(console.error);
678689
}
@@ -683,7 +694,7 @@ export default class Application {
683694
* @category Application
684695
*/
685696
exitFullscreen(): void {
686-
if (device.hasFullscreenSupport && device.isFullscreen()) {
697+
if (device.hasFullscreenSupport && this.isFullscreen()) {
687698
globalThis.document.exitFullscreen().catch(console.error);
688699
}
689700
}

packages/melonjs/src/system/device.ts

Lines changed: 30 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,15 @@ type NavigatorLegacy = Navigator & {
1818
type DocumentLegacy = Document & {
1919
mozFullScreenEnabled?: boolean;
2020
mozFullScreenElement?: Element;
21+
webkitFullscreenEnabled?: boolean;
22+
webkitFullscreenElement?: Element;
23+
msFullscreenEnabled?: boolean;
24+
msFullscreenElement?: Element;
2125
};
2226
type ElementLegacy = Element & {
2327
mozRequestFullScreen?: () => void;
28+
webkitRequestFullscreen?: () => void;
29+
msRequestFullscreen?: () => void;
2430
};
2531
type DeviceOrientationEventCtor = typeof DeviceOrientationEvent & {
2632
requestPermission?: () => Promise<"granted" | "denied" | "default">;
@@ -185,10 +191,10 @@ export const hasAccelerometer = !!globalThis.DeviceMotionEvent;
185191
export const hasFullscreenSupport =
186192
typeof globalThis.document !== "undefined" &&
187193
!!(
188-
prefixed(
189-
"fullscreenEnabled",
190-
globalThis.document as unknown as Record<string, unknown>,
191-
) || (globalThis.document as DocumentLegacy).mozFullScreenEnabled
194+
globalThis.document.fullscreenEnabled ||
195+
(globalThis.document as DocumentLegacy).webkitFullscreenEnabled ||
196+
(globalThis.document as DocumentLegacy).mozFullScreenEnabled ||
197+
(globalThis.document as DocumentLegacy).msFullscreenEnabled
192198
);
193199

194200
/**
@@ -419,19 +425,18 @@ export function enableSwipe(enable?: boolean) {
419425

420426
/**
421427
* Returns true if the browser/device is in full screen mode.
428+
* @deprecated since 19.7.0 — use {@link Application#isFullscreen app.isFullscreen()} instead.
422429
* @category Application
423430
*/
424431
export function isFullscreen() {
425-
if (hasFullscreenSupport) {
426-
return !!(
427-
prefixed(
428-
"fullscreenElement",
429-
globalThis.document as unknown as Record<string, unknown>,
430-
) || (globalThis.document as DocumentLegacy).mozFullScreenElement
431-
);
432-
} else {
433-
return false;
434-
}
432+
if (!hasFullscreenSupport) return false;
433+
const doc = globalThis.document as DocumentLegacy;
434+
return !!(
435+
doc.fullscreenElement ||
436+
doc.webkitFullscreenElement ||
437+
doc.mozFullScreenElement ||
438+
doc.msFullscreenElement
439+
);
435440
}
436441

437442
/**
@@ -452,15 +457,19 @@ export function isFullscreen() {
452457
* @category Application
453458
*/
454459
export function requestFullscreen(element?: Element) {
460+
// eslint-disable-next-line @typescript-eslint/no-deprecated -- this whole function is the deprecated wrapper; internal use of the matching deprecated probe is fine
455461
if (hasFullscreenSupport && !isFullscreen()) {
456462
// eslint-disable-next-line @typescript-eslint/no-deprecated -- no Application context available from this static API
457463
const target = (element ?? getParent()) as ElementLegacy;
458-
const vendor = prefixed(
459-
"requestFullscreen",
460-
target as unknown as Record<string, unknown>,
461-
) as (() => void) | undefined;
462-
const request = vendor ?? target.mozRequestFullScreen;
463-
request?.call(target);
464+
/* eslint-disable @typescript-eslint/unbound-method -- `this` is restored explicitly via `.call(target)` below */
465+
const request =
466+
target.requestFullscreen ||
467+
target.webkitRequestFullscreen ||
468+
target.mozRequestFullScreen ||
469+
target.msRequestFullscreen;
470+
/* eslint-enable @typescript-eslint/unbound-method */
471+
const result = request?.call(target);
472+
if (result instanceof Promise) result.catch(console.error);
464473
}
465474
}
466475

@@ -469,6 +478,7 @@ export function requestFullscreen(element?: Element) {
469478
* @deprecated since 19.7.0 — use {@link Application#exitFullscreen app.exitFullscreen()} instead.
470479
*/
471480
export const exitFullscreen = () => {
481+
// eslint-disable-next-line @typescript-eslint/no-deprecated -- this whole function is the deprecated wrapper; internal use of the matching deprecated probe is fine
472482
if (hasFullscreenSupport && isFullscreen()) {
473483
document.exitFullscreen().catch(console.error);
474484
}

0 commit comments

Comments
 (0)