2024-07-16 16:45:08 -04:00
|
|
|
// Thank you to https://github.com/daviddarnes/heading-anchors
|
2024-08-09 16:48:14 -04:00
|
|
|
// Thank you to https://amberwilson.co.uk/blog/are-your-anchor-links-accessible/#what-are-anchor-links-exactly%3F
|
2024-07-16 16:45:08 -04:00
|
|
|
|
|
|
|
class HeadingAnchors extends HTMLElement {
|
|
|
|
static register(tagName) {
|
|
|
|
if ("customElements" in window) {
|
|
|
|
customElements.define(tagName || "heading-anchors", HeadingAnchors);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-08-09 16:48:14 -04:00
|
|
|
static defaultSelector = "h2,h3,h4";
|
|
|
|
|
|
|
|
static featureTest() {
|
|
|
|
return "replaceSync" in CSSStyleSheet.prototype;
|
|
|
|
}
|
|
|
|
|
|
|
|
static css = `
|
|
|
|
.heading-anchor {
|
|
|
|
text-decoration: none;
|
|
|
|
}
|
|
|
|
/* For headings that already have links */
|
|
|
|
:is(h1, h2, h3, h4, h5, h6):has(a[href]:not(.heading-anchor)):is(:hover, :focus-within) .heading-anchor:after {
|
2024-08-09 17:38:07 -04:00
|
|
|
opacity: 1;
|
2024-08-09 16:48:14 -04:00
|
|
|
}
|
|
|
|
.heading-anchor:focus:after,
|
|
|
|
.heading-anchor:hover:after {
|
2024-08-09 17:38:07 -04:00
|
|
|
opacity: 1;
|
2024-08-09 16:48:14 -04:00
|
|
|
}
|
|
|
|
.heading-anchor:after {
|
2024-08-09 17:38:07 -04:00
|
|
|
content: "#";
|
|
|
|
content: "#" / "";
|
2024-08-09 16:48:14 -04:00
|
|
|
margin-left: .25em;
|
|
|
|
color: #aaa;
|
2024-08-09 17:38:07 -04:00
|
|
|
opacity: 0;
|
2024-08-09 16:48:14 -04:00
|
|
|
}`;
|
|
|
|
|
|
|
|
constructor() {
|
|
|
|
if (!HeadingAnchors.featureTest()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
super();
|
|
|
|
|
|
|
|
let sheet = new CSSStyleSheet();
|
|
|
|
sheet.replaceSync(HeadingAnchors.css);
|
|
|
|
document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet];
|
|
|
|
}
|
|
|
|
|
2024-07-16 16:45:08 -04:00
|
|
|
connectedCallback() {
|
2024-08-09 16:48:14 -04:00
|
|
|
if (!HeadingAnchors.featureTest()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2024-07-16 16:45:08 -04:00
|
|
|
this.headings.forEach((heading) => {
|
2024-08-09 16:48:14 -04:00
|
|
|
if(!heading.hasAttribute("data-heading-anchors-optout")) {
|
|
|
|
let anchor = this.getAnchorElement(heading);
|
|
|
|
if(heading.querySelector(":scope a[href]")) {
|
|
|
|
// Fallback if the heading already has a link
|
|
|
|
anchor.setAttribute("aria-label", `Jump to section: ${heading.textContent}`);
|
|
|
|
heading.appendChild(anchor);
|
|
|
|
} else {
|
|
|
|
// entire heading is a link
|
|
|
|
for(let child of heading.childNodes) {
|
|
|
|
anchor.appendChild(child);
|
|
|
|
}
|
|
|
|
heading.appendChild(anchor);
|
|
|
|
}
|
2024-07-16 16:45:08 -04:00
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2024-08-09 16:48:14 -04:00
|
|
|
getAnchorElement(heading) {
|
2024-07-16 16:45:08 -04:00
|
|
|
let anchor = document.createElement("a");
|
|
|
|
anchor.href = `#${heading.id}`;
|
|
|
|
anchor.classList.add("heading-anchor");
|
|
|
|
return anchor;
|
|
|
|
}
|
|
|
|
|
|
|
|
get headings() {
|
|
|
|
return this.querySelectorAll(this.selector.split(",").map(entry => `${entry.trim()}[id]`));
|
|
|
|
}
|
|
|
|
|
|
|
|
get selector() {
|
2024-08-09 16:48:14 -04:00
|
|
|
return this.getAttribute("selector") || HeadingAnchors.defaultSelector;
|
2024-07-16 16:45:08 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
HeadingAnchors.register();
|