function fullscreen({
className = 'custom-fullscreen',
style = null,
breakLayoutAtCell = 1,
hideAfter = 2,
hideBefore = 1,
left = 0,
right = 100,
button
} = {}) {
function hide({ hideAfter = Infinity, hideBefore = 0, reset = false } = {}) {
let cells = document.getElementsByClassName('observablehq');
for (let i = 0; i < cells.length; i++) {
if ((i < hideBefore || i > hideAfter) && !reset)
cells[i].style.display = "none";
else cells[i].style.display = "block";
}
return cells;
}
const buttonStyle =
style != null
? style
: 'font-size:1rem;font-weight:bold;padding:8px;background:hsl(50,100%,90%);border:5px solid hsl(40,100%,50%); border-radius:4px;box-shadow:0 .5px 2px 1px rgba(0,0,0,.2);cursor: pointer';
button = button || html`<button style="${buttonStyle}">Toggle fullscreen!`;
if (document.documentElement.requestFullscreen) {
button.onclick = () => {
if (document.location.host.match(/static.observableusercontent.com/))
hide({ hideBefore, hideAfter });
const parent = document.documentElement;
const cleanup = () => {
if (document.fullscreenElement) return;
parent.classList.remove(className);
if (document.location.host.match(/static.observableusercontent.com/))
hide({ reset: true });
document.removeEventListener('fullscreenchange', cleanup);
};
if (document.fullscreenElement) {
document.exitFullscreen();
} else {
parent.requestFullscreen().then(() => {
parent.classList.add(className);
// Can't use {once: true}, because event fires too late.
document.addEventListener('fullscreenchange', cleanup);
});
}
};
}
// Because Safari is the new IE.
// Note: let as in https://observablehq.com/@mootari/fullscreen-layout-demo.
// The button will not toggle between states
else {
const screenfull = require('screenfull@4.2.0/dist/screenfull.js').catch(
() => window['screenfull']
);
// We would need some debouncing, in case screenfull isn't loaded
// yet and user clicks frantically. Then again, it's Safari.
button.onclick = () => {
screenfull.then(sf => {
const parent = document.documentElement;
sf.request(parent).then(() => {
const cleanup = () => {
if (sf.isFullscreen) return;
parent.classList.remove(className);
sf.off('change', cleanup);
};
parent.classList.add(className);
sf.on('change', cleanup);
});
});
};
}
// Styles don't rely on the :fullscreen selector to avoid interfering with
// Observable's fullscreen mode. Instead a class is applied to html during
// fullscreen.
return html`
${button}
<style>
main.mw8 { max-width: 100vw }
html.${className} body, html.${className} body > div:not(.observablehq) {
overflow: auto;
height: 100vh;
width: 100%;
}
html.${className} body, html.${className} body > div:not(.observablehq) {
display: block;
background: white;
width: auto;
overflow: auto;
position: relative;
}
.observablehq::before {
height: 0;
}
html.${className} .observablehq {
margin-left: ${left}vw;
width: ${right}vw;
margin-top: 0 !important;
margin-bottom: 0 !important;
min-height: 0 !important;
max-height: 75vh;
overflow: auto;
padding: .5rem;
box-sizing: border-box;
}
html.${className} .observablehq:nth-of-type(-n+${breakLayoutAtCell}) {
float: left;
width: ${left}vw;
margin-left: 0;
}
`;
}