Skip to content

Commit 73a7a21

Browse files
committed
feat: add rotate-to-fullscreen feature for mode=lightbox
1 parent 72aa40e commit 73a7a21

File tree

4 files changed

+176
-2
lines changed

4 files changed

+176
-2
lines changed

.eslintrc.json

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
}
1919
],
2020
"import/prefer-default-export": 0,
21+
"import/extensions": 0,
2122
"no-underscore-dangle": [
2223
"error", {
2324
"allowAfterThis": true

README.md

+58
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,64 @@ export default () => {
255255
</glomex-dialog>
256256
~~~
257257

258+
### Using rotate-to-fullscreen
259+
260+
You can enable this setting, so that the dialog goes into fullscreen on mobile phones (up until 480px width) when rotated to landscape in lightbox mode.
261+
262+
```js preact
263+
import { html, render, useRef } from 'docup'
264+
265+
export default () => {
266+
const select = useRef();
267+
const dialog = useRef();
268+
const onButtonClick = () => {
269+
dialog.current.setAttribute('mode', select.current.value);
270+
};
271+
272+
return html`
273+
<p>
274+
<select ref=${select}>
275+
<option value="hidden">hidden</option>
276+
<option value="inline">inline</option>
277+
<option value="dock">dock</option>
278+
<option value="lightbox" selected>lightbox</option>
279+
</select>
280+
<button onClick=${onButtonClick} class="button">Switch Dialog Mode</button>
281+
</p>
282+
<style>
283+
glomex-dialog[mode="lightbox"][fullscreen] video {
284+
height: 100vh;
285+
min-height: 100%;
286+
}
287+
glomex-dialog[mode="lightbox"][fullscreen] .placeholder-16x9 {
288+
padding-top: unset;
289+
}
290+
</style>
291+
<glomex-dialog id="with-rotate-to-fullscreen" mode="inline" ref=${dialog} rotate-to-fullscreen>
292+
<div slot="dialog-element">
293+
<div style="position: relative;">
294+
<div class="placeholder-16x9"></div>
295+
<video
296+
class="video-element"
297+
controls
298+
playsinline
299+
webkit-playsinline
300+
preload="none"
301+
src="https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4"
302+
poster="https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/images/BigBuckBunny.jpg">
303+
</video>
304+
</div>
305+
</div>
306+
</glomex-dialog>`
307+
}
308+
```
309+
310+
~~~html
311+
<glomex-dialog rotate-to-fullscreen>
312+
<!-- ... -->
313+
</glomex-dialog>
314+
~~~
315+
258316
### With custom placeholder
259317

260318
```js preact

glomex-dialog.js

+47-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
1+
import { RotataToFullscreen } from './rotate-to-fullscreen.js';
2+
13
const NON_VISIBLE_WIDTH = window.innerWidth < 720 ? 320 : 640;
24
const DEFAULT_DOCK_TARGET_INSET = '0px 10px auto auto';
35
const DEFAULT_TRANSITION_DURATION = 300;
46
const DOCK_Z_INDEX = 9999999;
57
const LIGHTBOX_Z_INDEX = 10000000;
68
const MIN_DOCK_WIDTH = 192;
79
const MAX_DOCK_WIDTH = 400;
10+
const PHONE_MAX_WIDTH = 480;
11+
12+
const allowRotateToFullscreen = Math.min(window.innerWidth, window.innerHeight) <= PHONE_MAX_WIDTH;
813

914
const updateViewPortWidth = (element) => {
1015
let viewPortWidth = window.innerWidth * 0.3;
@@ -200,7 +205,7 @@ function isInDocument(element, document) {
200205

201206
function adjustLightboxModeForLandscapeOnMobile(element) {
202207
if (element.getAttribute('mode') !== 'lightbox') return;
203-
const mobileLandscapeSelector = '(max-device-width: 450px) and (hover: none) and (pointer: coarse) and (orientation: landscape)';
208+
const mobileLandscapeSelector = `(max-device-width: ${PHONE_MAX_WIDTH}px) and (hover: none) and (pointer: coarse) and (orientation: landscape)`;
204209
if (window.matchMedia(mobileLandscapeSelector).matches) {
205210
// allow scrolling in mobile landscape
206211
// so that the user can scroll down to remove the browser bar
@@ -406,7 +411,7 @@ class GlomexDialogElement extends window.HTMLElement {
406411
z-index: ${LIGHTBOX_Z_INDEX};
407412
}
408413
409-
@media (hover: none) and (max-device-width: 450px) and (pointer: coarse) and (orientation: landscape) {
414+
@media (hover: none) and (max-device-width: ${PHONE_MAX_WIDTH}px) and (pointer: coarse) and (orientation: landscape) {
410415
:host([mode=lightbox]) .dialog-content {
411416
animation-name: none;
412417
height: 100%;
@@ -540,6 +545,12 @@ class GlomexDialogElement extends window.HTMLElement {
540545
if (this._disconnectDragAndDrop) this._disconnectDragAndDrop();
541546
this._disconnectResize();
542547
this._disconnectKeyup();
548+
if (this._rotateToFullscreen) {
549+
this._rotateToFullscreen.disable();
550+
this._rotateToFullscreen.removeEventListener('exit', this._onRotateToFullscreenExit);
551+
this._rotateToFullscreen.removeEventListener('enter', this._onRotateToFullscreenEnter);
552+
this._rotateToFullscreen = undefined;
553+
}
543554
}
544555

545556
static get observedAttributes() {
@@ -553,6 +564,7 @@ class GlomexDialogElement extends window.HTMLElement {
553564
'dock-sticky-target-top',
554565
'dock-sticky-aspect-ratio',
555566
'dock-downscale',
567+
'rotate-to-fullscreen',
556568
];
557569
}
558570

@@ -669,6 +681,9 @@ class GlomexDialogElement extends window.HTMLElement {
669681
passive: false,
670682
});
671683
adjustLightboxModeForLandscapeOnMobile(this);
684+
if (this._rotateToFullscreen) {
685+
this._rotateToFullscreen.enable();
686+
}
672687
} else if (newValue === 'hidden') {
673688
this._wasInHiddenMode = true;
674689
}
@@ -680,6 +695,10 @@ class GlomexDialogElement extends window.HTMLElement {
680695
dialogInnerWrapper.removeAttribute('tabindex');
681696
}
682697

698+
if (oldValue === 'lightbox' && this._rotateToFullscreen) {
699+
this._rotateToFullscreen.disable();
700+
}
701+
683702
this.dispatchEvent(
684703
new CustomEvent('modechange', {
685704
detail: {
@@ -745,6 +764,32 @@ class GlomexDialogElement extends window.HTMLElement {
745764
// No implementation when dock-downscale gets reset
746765
}
747766
}
767+
768+
if (name === 'rotate-to-fullscreen') {
769+
if (newValue == null && this._rotateToFullscreen) {
770+
this._rotateToFullscreen.disable();
771+
this._rotateToFullscreen.removeEventListener('exit', this._onRotateToFullscreenExit);
772+
this._rotateToFullscreen.removeEventListener('enter', this._onRotateToFullscreenEnter);
773+
this._rotateToFullscreen = undefined;
774+
} else if (allowRotateToFullscreen) {
775+
this._onRotateToFullscreenExit = (event) => {
776+
if (event.detail.orientation.indexOf('landscape') === 0) {
777+
this._internalModeChange = true;
778+
this.setAttribute('mode', this._wasInHiddenMode ? 'hidden' : 'inline');
779+
}
780+
this.removeAttribute('fullscreen');
781+
};
782+
this._onRotateToFullscreenEnter = () => {
783+
// expose fullscreen to the light-dom so we can apply fullscreen styles there
784+
// (device-mode: fullscreen) media queries somehow don't work in combination
785+
// with shadow dom
786+
this.setAttribute('fullscreen', '');
787+
};
788+
this._rotateToFullscreen = new RotataToFullscreen(window, dialogInnerWrapper);
789+
this._rotateToFullscreen.addEventListener('exit', this._onRotateToFullscreenExit);
790+
this._rotateToFullscreen.addEventListener('enter', this._onRotateToFullscreenEnter);
791+
}
792+
}
748793
}
749794

750795
/**

rotate-to-fullscreen.js

+70
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/**
2+
* Handles <glomex-dialog rotate-to-fullscreen>
3+
*/
4+
export class RotataToFullscreen extends window.EventTarget {
5+
constructor(window, fullscreenElement) {
6+
super();
7+
this._element = fullscreenElement;
8+
this._rootNode = this._element.getRootNode();
9+
this._window = window;
10+
this._onOrientationChange = this._onOrientationChange.bind(this);
11+
this._onIframeFullscreenChange = this._onIframeFullscreenChange.bind(this);
12+
}
13+
14+
enable() {
15+
const { _window: window, _element: element } = this;
16+
const { screen } = window;
17+
18+
if (!(screen && screen.orientation)) return;
19+
20+
screen.orientation.addEventListener('change', this._onOrientationChange);
21+
element.addEventListener('fullscreenchange', this._onIframeFullscreenChange);
22+
23+
const isInLandscape = screen.orientation.type.indexOf('landscape') === 0;
24+
if (this._rootNode.fullscreenElement === null && isInLandscape) {
25+
element.requestFullscreen().catch(() => {});
26+
}
27+
}
28+
29+
disable() {
30+
const { _window: window, _element: element } = this;
31+
const { screen, document } = window;
32+
33+
if (!(screen && screen.orientation)) return;
34+
35+
if (this._rootNode.fullscreenElement && document.exitFullscreen) {
36+
document.exitFullscreen().catch(() => {});
37+
}
38+
39+
screen.orientation.removeEventListener('change', this._onOrientationChange);
40+
element.removeEventListener('fullscreenchange', this._onIframeFullscreenChange);
41+
}
42+
43+
_onOrientationChange() {
44+
const { _window: window, _element: element } = this;
45+
const { screen, document } = window;
46+
47+
if (
48+
this._rootNode.fullscreenElement === null
49+
&& screen.orientation.type.indexOf('landscape') === 0
50+
) {
51+
element.requestFullscreen().catch(() => {});
52+
} else {
53+
document.exitFullscreen().catch(() => {});
54+
}
55+
}
56+
57+
_onIframeFullscreenChange() {
58+
const { screen } = this._window;
59+
60+
if (this._rootNode.fullscreenElement === null) {
61+
this.dispatchEvent(new CustomEvent('exit', {
62+
detail: {
63+
orientation: screen.orientation.type
64+
}
65+
}));
66+
} else {
67+
this.dispatchEvent(new CustomEvent('enter'));
68+
}
69+
}
70+
}

0 commit comments

Comments
 (0)