From b5a00fd77607cce83952c0e9c0969e506c41a310 Mon Sep 17 00:00:00 2001 From: Zach Leatherman Date: Fri, 9 Aug 2024 15:48:14 -0500 Subject: [PATCH] Updates to heading-anchors for accessibility. --- content/blog/thirdpost.md | 2 +- public/css/index.css | 23 ------------- public/js/heading-anchors.js | 64 ++++++++++++++++++++++++++++++++---- 3 files changed, 58 insertions(+), 31 deletions(-) diff --git a/content/blog/thirdpost.md b/content/blog/thirdpost.md index bdea878..7241473 100644 --- a/content/blog/thirdpost.md +++ b/content/blog/thirdpost.md @@ -25,7 +25,7 @@ function myCommand() { console.log('Test'); ``` -### Unstyled +### Heading with a [link](#code) Bring to the table win-win survival strategies to ensure proactive domination. At the end of the day, going forward, a new normal that has evolved from generation X is on the runway heading towards a streamlined cloud solution. User generated content in real-time will have multiple touchpoints for offshoring. diff --git a/public/css/index.css b/public/css/index.css index cd9d0bf..b75d493 100644 --- a/public/css/index.css +++ b/public/css/index.css @@ -256,26 +256,3 @@ header { margin-right: 1em; } -/* Direct Links / Headings */ -.heading-anchor { - text-decoration: none; - font-style: normal; - font-size: 1em; - margin-left: .25em; -} -a[href].heading-anchor, -a[href].heading-anchor:visited { - color: transparent; -} -a[href].heading-anchor:focus, -a[href].heading-anchor:hover { - text-decoration: underline; -} -a[href].heading-anchor:focus, -:hover > a[href].heading-anchor { - color: #aaa; -} - -h2 + .heading-anchor { - font-size: 1.5em; -} diff --git a/public/js/heading-anchors.js b/public/js/heading-anchors.js index ea9d6cd..03134af 100644 --- a/public/js/heading-anchors.js +++ b/public/js/heading-anchors.js @@ -1,4 +1,5 @@ // Thank you to https://github.com/daviddarnes/heading-anchors +// Thank you to https://amberwilson.co.uk/blog/are-your-anchor-links-accessible/#what-are-anchor-links-exactly%3F class HeadingAnchors extends HTMLElement { static register(tagName) { @@ -7,22 +8,71 @@ class HeadingAnchors extends HTMLElement { } } + 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 { + content: "#"; + content: "#" / ""; +} +.heading-anchor:focus:after, +.heading-anchor:hover:after { + content: "#"; + content: "#" / ""; +} +.heading-anchor:after { + margin-left: .25em; + color: #aaa; +}`; + + constructor() { + if (!HeadingAnchors.featureTest()) { + return; + } + + super(); + + let sheet = new CSSStyleSheet(); + sheet.replaceSync(HeadingAnchors.css); + document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet]; + } + connectedCallback() { + if (!HeadingAnchors.featureTest()) { + return; + } + this.headings.forEach((heading) => { - if(!heading.querySelector("a.direct-link") || heading.hasAttribute("data-heading-anchors-optout")) { - heading.append(this.anchor(heading)); - heading.setAttribute("tabindex", "-1"); + 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); + } } }); } - anchor(heading) { - // TODO this would be good use case for shadow dom + getAnchorElement(heading) { let anchor = document.createElement("a"); anchor.setAttribute("data-pagefind-ignore", ""); anchor.href = `#${heading.id}`; anchor.classList.add("heading-anchor"); - anchor.innerHTML = `Jump to heading`; return anchor; } @@ -31,7 +81,7 @@ class HeadingAnchors extends HTMLElement { } get selector() { - return this.getAttribute("selector") || "h1,h2,h3,h4" + return this.getAttribute("selector") || HeadingAnchors.defaultSelector; } }