Add Bludit ToC Plugin files: metadata, plugin, CSS, and JS
This commit is contained in:
1 parent
133fd35fa5
commit
a21a011e67
4 files changed
+501
No files matched your search
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"author": "spleenftw",
|
||||||
|
"email": "",
|
||||||
|
"website": "https://github.com/spleenftw/bludit-toc",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"releaseDate": "2026-06-04",
|
||||||
|
"license": "MIT",
|
||||||
|
"compatible": "3.22.0",
|
||||||
|
"notes": "Sticky Table of Contents sidebar (desktop) and floating drawer (mobile) built from the article's h2/h3/h4 headings."
|
||||||
|
}
|
||||||
+93
@@ -0,0 +1,93 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Bludit ToC Plugin
|
||||||
|
*
|
||||||
|
* Injects a sticky Table of Contents sidebar (desktop) + floating drawer
|
||||||
|
* (mobile) built from the article's h2/h3/h4 headings. Works standalone
|
||||||
|
* on any Bludit theme that wraps article body in a `.content` element.
|
||||||
|
*
|
||||||
|
* Drop this directory into bl-plugins/ and activate via the admin panel.
|
||||||
|
*/
|
||||||
|
class PluginBluditToc extends Plugin {
|
||||||
|
|
||||||
|
public function init() {
|
||||||
|
$this->dbFields = array(
|
||||||
|
// Label shown at the top of the sidebar and in the mobile drawer.
|
||||||
|
'title' => 'On this page',
|
||||||
|
// Pixel offset to clear the fixed navbar when jumping to a heading.
|
||||||
|
'navbarHeight' => 80,
|
||||||
|
// Viewport width (px) at which the fixed sidebar appears.
|
||||||
|
// Below this width the mobile floating-button drawer is used instead.
|
||||||
|
'minWidth' => 1280,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- Admin settings form ----
|
||||||
|
|
||||||
|
public function form() {
|
||||||
|
global $L;
|
||||||
|
|
||||||
|
$title = htmlspecialchars($this->getDbField('title'), ENT_QUOTES, 'UTF-8');
|
||||||
|
$navbarHeight = (int) $this->getDbField('navbarHeight');
|
||||||
|
$minWidth = (int) $this->getDbField('minWidth');
|
||||||
|
|
||||||
|
$h = '';
|
||||||
|
|
||||||
|
$h .= '<div class="form-group">';
|
||||||
|
$h .= '<label for="bltoc-field-title">' . $L->get('Sidebar title') . '</label>';
|
||||||
|
$h .= '<input class="form-control" type="text" id="bltoc-field-title" name="title" value="' . $title . '">';
|
||||||
|
$h .= '</div>';
|
||||||
|
|
||||||
|
$h .= '<div class="form-group">';
|
||||||
|
$h .= '<label for="bltoc-field-navbar">' . $L->get('Navbar height (px)') . '</label>';
|
||||||
|
$h .= '<input class="form-control" type="number" id="bltoc-field-navbar" name="navbarHeight" min="0" max="300" value="' . $navbarHeight . '">';
|
||||||
|
$h .= '<small class="form-text text-muted">' . $L->get('Used to offset hash-jump anchors so they clear the fixed navbar.') . '</small>';
|
||||||
|
$h .= '</div>';
|
||||||
|
|
||||||
|
$h .= '<div class="form-group">';
|
||||||
|
$h .= '<label for="bltoc-field-minwidth">' . $L->get('Fixed sidebar min-width (px)') . '</label>';
|
||||||
|
$h .= '<input class="form-control" type="number" id="bltoc-field-minwidth" name="minWidth" min="768" max="2560" value="' . $minWidth . '">';
|
||||||
|
$h .= '<small class="form-text text-muted">' . $L->get('Viewport widths below this value use the mobile floating button instead.') . '</small>';
|
||||||
|
$h .= '</div>';
|
||||||
|
|
||||||
|
return $h;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- Front-end hooks ----
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inject the plugin CSS in <head>.
|
||||||
|
*/
|
||||||
|
public function siteHead() {
|
||||||
|
$url = DOMAIN_PLUGINS . $this->directoryName . '/toc.css';
|
||||||
|
return HTML::style($url) . PHP_EOL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inject the ToC sidebar container right after <body>.
|
||||||
|
* It is position:fixed, so placement in the DOM doesn't affect layout.
|
||||||
|
* JS will populate <nav id="bltoc-nav"> and mark the container visible.
|
||||||
|
*/
|
||||||
|
public function siteBodyBegin() {
|
||||||
|
$title = htmlspecialchars($this->getDbField('title'), ENT_QUOTES, 'UTF-8');
|
||||||
|
return '<aside class="bltoc-sidebar" id="bltoc-sidebar" aria-label="' . $title . '">'
|
||||||
|
. '<div class="bltoc-header"><span class="bltoc-title">' . $title . '</span></div>'
|
||||||
|
. '<nav id="bltoc-nav" aria-label="' . $title . '"></nav>'
|
||||||
|
. '</aside>' . PHP_EOL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inject config + script just before </body>.
|
||||||
|
* Config is passed via window.BLTOC so the JS file stays cache-friendly.
|
||||||
|
*/
|
||||||
|
public function siteBodyEnd() {
|
||||||
|
$title = json_encode($this->getDbField('title'));
|
||||||
|
$navbarHeight = (int) $this->getDbField('navbarHeight');
|
||||||
|
$minWidth = (int) $this->getDbField('minWidth');
|
||||||
|
$url = DOMAIN_PLUGINS . $this->directoryName . '/toc.js';
|
||||||
|
|
||||||
|
$out = '<script>window.BLTOC={title:' . $title . ',navbarHeight:' . $navbarHeight . ',minWidth:' . $minWidth . '};</script>' . PHP_EOL;
|
||||||
|
$out .= HTML::script($url) . PHP_EOL;
|
||||||
|
return $out;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,223 @@
|
|||||||
|
/* =============================================================
|
||||||
|
Bludit ToC Plugin — toc.css
|
||||||
|
Self-contained: no dependency on the host theme's CSS variables.
|
||||||
|
Dark mode is handled via prefers-color-scheme.
|
||||||
|
If the theme exposes --accent / --text-muted / --bg-primary,
|
||||||
|
those are layered on top via @supports for a seamless integration.
|
||||||
|
============================================================= */
|
||||||
|
|
||||||
|
/* ---- Color tokens (light) ---- */
|
||||||
|
#bltoc-sidebar {
|
||||||
|
--bltoc-text: #777;
|
||||||
|
--bltoc-hover: #111;
|
||||||
|
--bltoc-active: #0066cc;
|
||||||
|
--bltoc-label: #999;
|
||||||
|
--bltoc-bg: #fff;
|
||||||
|
--bltoc-border: rgba(0,0,0,0.08);
|
||||||
|
--bltoc-fab-bg: #0066cc;
|
||||||
|
--bltoc-fab-color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- Color tokens (dark) ---- */
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
#bltoc-sidebar {
|
||||||
|
--bltoc-text: #999;
|
||||||
|
--bltoc-hover: #eee;
|
||||||
|
--bltoc-active: #4d9fff;
|
||||||
|
--bltoc-label: #666;
|
||||||
|
--bltoc-bg: #1a1a1a;
|
||||||
|
--bltoc-border: rgba(255,255,255,0.08);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Let blowdit (or any theme) override via its own design tokens */
|
||||||
|
@supports (color: var(--accent)) {
|
||||||
|
#bltoc-sidebar { --bltoc-active: var(--accent); }
|
||||||
|
}
|
||||||
|
@supports (color: var(--text-muted)) {
|
||||||
|
#bltoc-sidebar { --bltoc-text: var(--text-muted); }
|
||||||
|
}
|
||||||
|
@supports (color: var(--bg-primary)) {
|
||||||
|
#bltoc-sidebar { --bltoc-bg: var(--bg-primary); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================================
|
||||||
|
Desktop fixed sidebar
|
||||||
|
Visible only at min-width set by the admin (default 1280px).
|
||||||
|
JS adds .bltoc-ready when headings are found, making it appear.
|
||||||
|
============================================================ */
|
||||||
|
.bltoc-sidebar {
|
||||||
|
display: none; /* hidden until JS confirms headings exist */
|
||||||
|
position: fixed;
|
||||||
|
left: 1rem;
|
||||||
|
top: 5rem; /* approx below a typical navbar; fine-tuned with navbarHeight in JS */
|
||||||
|
width: 210px;
|
||||||
|
max-height: calc(100vh - 6rem);
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
z-index: 200;
|
||||||
|
font-size: 0.8125rem;
|
||||||
|
line-height: 1.5;
|
||||||
|
scrollbar-width: thin;
|
||||||
|
scrollbar-color: var(--bltoc-border) transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* JS adds data-bltoc-minwidth on <html>; sidebar shows only above that threshold. */
|
||||||
|
/* Actual media-query breakpoint is injected by JS via a <style> tag since the
|
||||||
|
min-width value is dynamic (set in admin). */
|
||||||
|
|
||||||
|
/* ---- Sidebar header ("On this page") ---- */
|
||||||
|
.bltoc-header {
|
||||||
|
margin-bottom: 0.65rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bltoc-title {
|
||||||
|
font-size: 0.68rem;
|
||||||
|
font-weight: 700;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
color: var(--bltoc-label);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- ToC list ---- */
|
||||||
|
.bltoc-list {
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bltoc-item {
|
||||||
|
padding: 0.12rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bltoc-h3 { padding-left: 0.75rem; }
|
||||||
|
.bltoc-h4 { padding-left: 1.5rem; }
|
||||||
|
|
||||||
|
.bltoc-link {
|
||||||
|
display: block;
|
||||||
|
color: var(--bltoc-text);
|
||||||
|
text-decoration: none;
|
||||||
|
line-height: 1.5;
|
||||||
|
transition: color 0.15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bltoc-link:hover {
|
||||||
|
color: var(--bltoc-hover);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bltoc-link.active {
|
||||||
|
color: var(--bltoc-active);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================================
|
||||||
|
Mobile floating action button + bottom drawer
|
||||||
|
FAB appears below the sidebar breakpoint.
|
||||||
|
Both are injected by JS, but their CSS lives here.
|
||||||
|
============================================================ */
|
||||||
|
.bltoc-fab {
|
||||||
|
display: none; /* shown by JS via flex when headings exist */
|
||||||
|
position: fixed;
|
||||||
|
left: 1.5rem;
|
||||||
|
bottom: 1.5rem;
|
||||||
|
z-index: 1500;
|
||||||
|
width: 46px;
|
||||||
|
height: 46px;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: none;
|
||||||
|
background: var(--bltoc-fab-bg, #0066cc);
|
||||||
|
color: var(--bltoc-fab-color, #fff);
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
cursor: pointer;
|
||||||
|
box-shadow: 0 3px 10px rgba(0,0,0,0.25);
|
||||||
|
transition: transform 0.15s ease, box-shadow 0.15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bltoc-fab:hover {
|
||||||
|
transform: scale(1.08);
|
||||||
|
box-shadow: 0 4px 14px rgba(0,0,0,0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bltoc-fab svg { display: block; }
|
||||||
|
|
||||||
|
/* ---- Bottom drawer backdrop ---- */
|
||||||
|
.bltoc-drawer {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
z-index: 2000;
|
||||||
|
background: rgba(0, 0, 0, 0.45);
|
||||||
|
opacity: 0;
|
||||||
|
visibility: hidden;
|
||||||
|
transition: opacity 0.22s ease, visibility 0s linear 0.22s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bltoc-drawer.is-open {
|
||||||
|
opacity: 1;
|
||||||
|
visibility: visible;
|
||||||
|
transition: opacity 0.22s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- Drawer panel (slides up from bottom) ---- */
|
||||||
|
.bltoc-drawer-panel {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 1rem 1rem 0 0;
|
||||||
|
padding: 1.5rem 1.5rem calc(1.5rem + env(safe-area-inset-bottom, 0px));
|
||||||
|
max-height: 72vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
transform: translateY(100%);
|
||||||
|
transition: transform 0.25s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
.bltoc-drawer-panel { background: #1e1e1e; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@supports (color: var(--bg-primary)) {
|
||||||
|
.bltoc-drawer-panel { background: var(--bg-primary); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.bltoc-drawer.is-open .bltoc-drawer-panel {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bltoc-drawer-title {
|
||||||
|
font-size: 0.68rem;
|
||||||
|
font-weight: 700;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
color: #999;
|
||||||
|
margin-bottom: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bltoc-drawer .bltoc-list {
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bltoc-drawer .bltoc-link {
|
||||||
|
display: block;
|
||||||
|
padding: 0.5rem 0;
|
||||||
|
color: #555;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bltoc-drawer .bltoc-link:hover {
|
||||||
|
color: #0066cc;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
.bltoc-drawer .bltoc-link { color: #ccc; }
|
||||||
|
.bltoc-drawer .bltoc-link:hover { color: #4d9fff; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@supports (color: var(--accent)) {
|
||||||
|
.bltoc-drawer .bltoc-link:hover { color: var(--accent); }
|
||||||
|
}
|
||||||
@@ -0,0 +1,175 @@
|
|||||||
|
/* Bludit ToC Plugin — toc.js
|
||||||
|
Reads config from window.BLTOC (injected inline by plugin.php).
|
||||||
|
No dependencies; works with any Bludit theme that wraps the article
|
||||||
|
body in a .content element (Bludit's default page template does this). */
|
||||||
|
(function () {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var cfg = window.BLTOC || {};
|
||||||
|
var TITLE = cfg.title || 'On this page';
|
||||||
|
var NAVBAR_HEIGHT = cfg.navbarHeight != null ? cfg.navbarHeight : 80;
|
||||||
|
var MIN_WIDTH = cfg.minWidth != null ? cfg.minWidth : 1280;
|
||||||
|
|
||||||
|
/* ---- Inject the dynamic min-width media query for the sidebar ---- */
|
||||||
|
var styleEl = document.createElement('style');
|
||||||
|
styleEl.textContent =
|
||||||
|
'@media (min-width: ' + MIN_WIDTH + 'px) { .bltoc-sidebar.bltoc-ready { display: block; } }' +
|
||||||
|
'@media (max-width: ' + (MIN_WIDTH - 1) + 'px) { .bltoc-fab.bltoc-ready { display: flex; } }';
|
||||||
|
document.head.appendChild(styleEl);
|
||||||
|
|
||||||
|
/* ---- Find the article content area ---- */
|
||||||
|
var content = document.querySelector('.content')
|
||||||
|
|| document.querySelector('article .entry-content')
|
||||||
|
|| document.querySelector('.entry-content')
|
||||||
|
|| document.querySelector('.post-content')
|
||||||
|
|| document.querySelector('article')
|
||||||
|
|| document.querySelector('main');
|
||||||
|
|
||||||
|
if (!content) return;
|
||||||
|
|
||||||
|
/* ---- Collect headings ---- */
|
||||||
|
var headings = content.querySelectorAll('h2, h3, h4');
|
||||||
|
if (!headings.length) return;
|
||||||
|
|
||||||
|
/* ---- Assign stable IDs to any heading that lacks one ---- */
|
||||||
|
var usedIds = {};
|
||||||
|
Array.prototype.forEach.call(headings, function (h) {
|
||||||
|
if (h.id) {
|
||||||
|
usedIds[h.id] = true;
|
||||||
|
} else {
|
||||||
|
var base = h.textContent.trim().toLowerCase()
|
||||||
|
.replace(/[^a-z0-9\s-]/g, '')
|
||||||
|
.replace(/\s+/g, '-')
|
||||||
|
.replace(/^-+|-+$/g, '') || 'heading';
|
||||||
|
var id = base, n = 2;
|
||||||
|
while (usedIds[id]) { id = base + '-' + (n++); }
|
||||||
|
usedIds[id] = true;
|
||||||
|
h.id = id;
|
||||||
|
}
|
||||||
|
/* Offset anchor jumps so the heading clears the fixed navbar */
|
||||||
|
h.style.scrollMarginTop = NAVBAR_HEIGHT + 'px';
|
||||||
|
});
|
||||||
|
|
||||||
|
/* ---- Build the <ul> list ---- */
|
||||||
|
var ul = document.createElement('ul');
|
||||||
|
ul.className = 'bltoc-list';
|
||||||
|
|
||||||
|
Array.prototype.forEach.call(headings, function (h) {
|
||||||
|
var li = document.createElement('li');
|
||||||
|
li.className = 'bltoc-item bltoc-' + h.tagName.toLowerCase();
|
||||||
|
|
||||||
|
var a = document.createElement('a');
|
||||||
|
a.href = '#' + h.id;
|
||||||
|
a.textContent = h.textContent;
|
||||||
|
a.className = 'bltoc-link';
|
||||||
|
|
||||||
|
li.appendChild(a);
|
||||||
|
ul.appendChild(li);
|
||||||
|
});
|
||||||
|
|
||||||
|
/* ---- Populate #bltoc-nav and reveal the sidebar ---- */
|
||||||
|
var nav = document.getElementById('bltoc-nav');
|
||||||
|
if (nav) {
|
||||||
|
nav.appendChild(ul);
|
||||||
|
}
|
||||||
|
|
||||||
|
var sidebar = document.getElementById('bltoc-sidebar');
|
||||||
|
if (sidebar) {
|
||||||
|
sidebar.classList.add('bltoc-ready');
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- Scroll-spy: highlight the last heading above the viewport fold ---- */
|
||||||
|
var headingArr = Array.prototype.slice.call(headings);
|
||||||
|
|
||||||
|
function updateActive() {
|
||||||
|
var scrollY = window.scrollY || window.pageYOffset;
|
||||||
|
var threshold = scrollY + NAVBAR_HEIGHT + 16; /* small extra gap */
|
||||||
|
var active = null;
|
||||||
|
|
||||||
|
headingArr.forEach(function (h) {
|
||||||
|
if (h.getBoundingClientRect().top + scrollY <= threshold) {
|
||||||
|
active = h;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!nav) return;
|
||||||
|
var links = nav.querySelectorAll('.bltoc-link');
|
||||||
|
Array.prototype.forEach.call(links, function (a) {
|
||||||
|
a.classList.remove('active');
|
||||||
|
});
|
||||||
|
if (active) {
|
||||||
|
var link = nav.querySelector('a[href="#' + active.id + '"]');
|
||||||
|
if (link) link.classList.add('active');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('scroll', updateActive, { passive: true });
|
||||||
|
updateActive();
|
||||||
|
|
||||||
|
/* ---- Mobile: floating button + bottom drawer ---- */
|
||||||
|
var fab = document.createElement('button');
|
||||||
|
fab.type = 'button';
|
||||||
|
fab.className = 'bltoc-fab bltoc-ready';
|
||||||
|
fab.setAttribute('aria-label', TITLE);
|
||||||
|
fab.innerHTML =
|
||||||
|
'<svg viewBox="0 0 24 24" width="20" height="20" fill="none" ' +
|
||||||
|
'stroke="currentColor" stroke-width="2" stroke-linecap="round" ' +
|
||||||
|
'stroke-linejoin="round" aria-hidden="true">' +
|
||||||
|
'<path d="M8 6h13M8 12h13M8 18h13M3 6h.01M3 12h.01M3 18h.01"/>' +
|
||||||
|
'</svg>';
|
||||||
|
|
||||||
|
var drawer = document.createElement('div');
|
||||||
|
drawer.className = 'bltoc-drawer';
|
||||||
|
drawer.setAttribute('role', 'dialog');
|
||||||
|
drawer.setAttribute('aria-modal', 'true');
|
||||||
|
drawer.setAttribute('aria-label', TITLE);
|
||||||
|
|
||||||
|
var panel = document.createElement('div');
|
||||||
|
panel.className = 'bltoc-drawer-panel';
|
||||||
|
|
||||||
|
var drawerTitle = document.createElement('div');
|
||||||
|
drawerTitle.className = 'bltoc-drawer-title';
|
||||||
|
drawerTitle.textContent = TITLE;
|
||||||
|
|
||||||
|
var clone = ul.cloneNode(true);
|
||||||
|
|
||||||
|
panel.appendChild(drawerTitle);
|
||||||
|
panel.appendChild(clone);
|
||||||
|
drawer.appendChild(panel);
|
||||||
|
|
||||||
|
document.body.appendChild(fab);
|
||||||
|
document.body.appendChild(drawer);
|
||||||
|
|
||||||
|
function openDrawer() {
|
||||||
|
drawer.classList.add('is-open');
|
||||||
|
document.body.style.overflow = 'hidden';
|
||||||
|
fab.setAttribute('aria-expanded', 'true');
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeDrawer() {
|
||||||
|
drawer.classList.remove('is-open');
|
||||||
|
document.body.style.overflow = '';
|
||||||
|
fab.setAttribute('aria-expanded', 'false');
|
||||||
|
}
|
||||||
|
|
||||||
|
fab.setAttribute('aria-expanded', 'false');
|
||||||
|
fab.addEventListener('click', openDrawer);
|
||||||
|
|
||||||
|
/* Close when clicking the backdrop */
|
||||||
|
drawer.addEventListener('click', function (e) {
|
||||||
|
if (e.target === drawer) closeDrawer();
|
||||||
|
});
|
||||||
|
|
||||||
|
/* Close when a drawer link is followed */
|
||||||
|
Array.prototype.forEach.call(clone.querySelectorAll('a'), function (a) {
|
||||||
|
a.addEventListener('click', closeDrawer);
|
||||||
|
});
|
||||||
|
|
||||||
|
/* Close on Escape */
|
||||||
|
document.addEventListener('keydown', function (e) {
|
||||||
|
if ((e.key === 'Escape' || e.key === 'Esc') && drawer.classList.contains('is-open')) {
|
||||||
|
closeDrawer();
|
||||||
|
fab.focus();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})();
|
||||||
Reference in new issue
Block a user