Files

97 lines
3.4 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Full-viewport Overlay component (mobile). Distinct from the .modal dialog:
// this hosts a whole sidebar at full screen with its own scroll and a top-right close control.
// It reuses the low-level focus/Escape/bfcache patterns from modal.js rather than that component's panel chrome.
//
// openOverlay(node) MOVES `node` (an existing rail <aside>) into the overlay
// body and restores it to its original DOM position on close, so the rail keeps
// its state (expanded tree folders, rendered TOC). Only one overlay is open at
// a time.
(function () {
var overlay = null;
var bodyEl = null;
var closeBtn = null;
var hosted = null; // the moved node
var placeholder = null; // marks where to restore it
var prevFocus = null;
var onKeydown = null;
function build() {
overlay = document.createElement('div');
overlay.className = 'overlay';
overlay.setAttribute('role', 'dialog');
overlay.setAttribute('aria-modal', 'true');
var bar = document.createElement('div');
bar.className = 'overlay-bar';
closeBtn = document.createElement('button');
closeBtn.type = 'button';
closeBtn.className = 'btn overlay-close';
closeBtn.title = 'Close';
closeBtn.setAttribute('aria-label', 'Close');
closeBtn.textContent = '×';
closeBtn.addEventListener('click', close);
bar.appendChild(closeBtn);
bodyEl = document.createElement('div');
bodyEl.className = 'overlay-body';
bodyEl.addEventListener('click', function (e) {
if (e.target.closest('a')) close();
});
overlay.appendChild(bar);
overlay.appendChild(bodyEl);
}
function open(node) {
if (overlay && overlay.parentNode) close();
if (!overlay) build();
prevFocus = document.activeElement;
hosted = node;
placeholder = document.createComment('overlay-slot');
node.parentNode.insertBefore(placeholder, node);
bodyEl.appendChild(node);
document.body.appendChild(overlay);
overlay.classList.add('is-open');
onKeydown = function (e) {
if (e.key === 'Escape') { e.preventDefault(); close(); }
};
document.addEventListener('keydown', onKeydown);
setTimeout(function () { closeBtn.focus(); }, 0);
}
function close() {
if (!overlay || !overlay.parentNode) return;
if (onKeydown) {
document.removeEventListener('keydown', onKeydown);
onKeydown = null;
}
// Restore the hosted rail to its original spot in the shell.
if (hosted && placeholder && placeholder.parentNode) {
placeholder.parentNode.insertBefore(hosted, placeholder);
placeholder.parentNode.removeChild(placeholder);
}
hosted = null;
placeholder = null;
overlay.classList.remove('is-open');
overlay.parentNode.removeChild(overlay);
var toRestore = prevFocus;
prevFocus = null;
if (toRestore && toRestore.focus) {
try { toRestore.focus(); } catch (e) {}
}
}
// Belt-and-suspenders bfcache guard: force the overlay closed (restoring
// the rail) if the page is being hidden while it is still open.
window.addEventListener('pagehide', close);
window.openOverlay = open;
window.closeOverlay = close;
})();