function fullscreen({
className = 'custom-fullscreen',
style = null,
breakLayoutAtCell = 2,
hideAfter = 9999,
hideBefore = 0,
left = 66,
right = 33,
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>
html.${className},
html.${className} body {
overflow: auto;
height: 100vh;
width: 100vw;
}
html.${className} .observablehq-root {
padding: 0 0.5rem;
}
html.${className} body > div {
display: flex;
flex-direction: column;
justify-content: center;
flex-wrap: wrap;
gap: 0.25rem;
background: white;
height: 100%;
width: auto;
overflow: auto;
position: relative;
}
html.${className} body > div > div {
margin-bottom: 0 !important;
min-height: 0 !important;
width: ${left}vw;
max-height: 100%;
overflow: auto;
padding: .25rem;
box-sizing: border-box;
margin: 0;
}
html.${className} .observablehq:nth-of-type(${breakLayoutAtCell + hideBefore}) {
width: ${right}vw;
flex-basis: 95%;
display: flex !important;
flex-direction: column;
align-items: center;
justify-content: center;
}
`;
}