-
Notifications
You must be signed in to change notification settings - Fork 200
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add reference hint popup on hover of links (#2708)
* Initial working version of ref hints * cleanup * Only add ref for each url to refsData one time. * Simplify * Add visual test. * Add DFN text if different from link text * Fix missing links * Add auto-hide after one second delay * Don't clear timeout if not set. * Handle focus and blur events. * Fix teardown * --rebase * Fix formatting to make black happy. --------- Co-authored-by: Tab Atkins-Bittner <[email protected]>
- Loading branch information
1 parent
7884fd2
commit dba9c3d
Showing
326 changed files
with
109,283 additions
and
1,030 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
:root { | ||
--ref-hint-bg: #ddd; | ||
--ref-hint-text: var(--text); | ||
} | ||
@media (prefers-color-scheme: dark) { | ||
:root { | ||
--ref-hint-bg: #222; | ||
--ref-hint-text: var(--text); | ||
} | ||
} | ||
|
||
.ref-hint { | ||
display: none; | ||
position: absolute; | ||
z-index: 35; | ||
width: 20em; | ||
width: 300px; | ||
height: auto; | ||
max-height: 500px; | ||
overflow: auto; | ||
padding: 0.5em 0.5em; | ||
font: small Helvetica Neue, sans-serif, Droid Sans Fallback; | ||
background: var(--ref-hint-bg); | ||
color: var(--ref-hint-text); | ||
border: outset 0.2em; | ||
white-space: normal; /* in case it's moved into a pre */ | ||
} | ||
|
||
.ref-hint.on { | ||
display: inline-block; | ||
} | ||
|
||
.ref-hint * { margin: 0; padding: 0; text-indent: 0; } | ||
|
||
.ref-hint ul { padding: 0 0 0 1em; list-style: none; } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,207 @@ | ||
"use strict"; | ||
{ | ||
function genRefHint(link, ref) { | ||
const linkText = link.textContent; | ||
let dfnTextElements = ''; | ||
if (ref.text != linkText) { | ||
dfnTextElements = | ||
mk.li({}, | ||
mk.b({}, "DFN: "), | ||
mk.code({ class: "dfn-text" }, ref.text) | ||
); | ||
} | ||
const forList = ref.for_; | ||
const forListElements = forList.length === 0 ? '' : mk.li({}, | ||
mk.b({}, "For: "), | ||
mk.ul({}, | ||
...forList.map(forItem => | ||
mk.li({}, | ||
mk.span({}, `${forItem}`) | ||
), | ||
), | ||
), | ||
); | ||
const url = ref.url; | ||
const safeUrl = encodeURIComponent(url); | ||
const hintPanel = mk.aside({ | ||
class: "ref-hint", | ||
id: `ref-hint-for-${safeUrl}`, | ||
"data-for": url, | ||
"aria-labelled-by": `ref-hint-for-${safeUrl}`, | ||
}, | ||
mk.ul({}, | ||
dfnTextElements, | ||
mk.li({}, | ||
mk.b({}, "URL: "), | ||
mk.a({ href: url, class: "ref" }, url), | ||
), | ||
mk.li({}, | ||
mk.b({}, "Type: "), | ||
mk.span({}, `${ref.type}`), | ||
), | ||
mk.li({}, | ||
mk.b({}, "Spec: "), | ||
mk.span({}, `${ref.spec ? ref.spec : ''}`), | ||
), | ||
forListElements | ||
), | ||
); | ||
hintPanel.forLink = link; | ||
return hintPanel; | ||
} | ||
function genAllRefHints() { | ||
for(const refData of Object.values(window.refsData)) { | ||
const refUrl = refData.url; | ||
const links = document.querySelectorAll(`a[href="${refUrl}"]`); | ||
if (links.length == 0) { | ||
console.log(`Can't find link href="${refUrl}".`, refData); | ||
continue; | ||
} | ||
for (let i = 0; i < links.length; i++) { | ||
const link = links[i]; | ||
const hint = genRefHint(link, refData); | ||
append(document.body, hint); | ||
insertLinkPopupAction(hint) | ||
} | ||
} | ||
} | ||
|
||
function hideAllRefHints() { | ||
queryAll(".ref-hint.on").forEach(el=>hideRefHint(el)); | ||
} | ||
|
||
function hideRefHint(refHint) { | ||
const link = refHint.forLink; | ||
link.setAttribute("aria-expanded", "false"); | ||
refHint.style.position = "absolute"; // unfix it | ||
refHint.classList.remove("on"); | ||
const teardown = refHint.teardownEventListeners; | ||
if (teardown) { | ||
refHint.teardownEventListeners = undefined; | ||
teardown(); | ||
} | ||
} | ||
|
||
function showRefHint(refHint) { | ||
hideAllRefHints(); // Only display one at this time. | ||
|
||
const link = refHint.forLink; | ||
link.setAttribute("aria-expanded", "true"); | ||
refHint.classList.add("on"); | ||
positionRefHint(refHint); | ||
setupRefHintEventListeners(refHint); | ||
} | ||
|
||
function setupRefHintEventListeners(refHint) { | ||
if (refHint.teardownEventListeners) return; | ||
const link = refHint.forLink; | ||
// Add event handlers to hide the refHint after the user moves away | ||
// from both the link and refHint, if not hovering either within one second. | ||
let timeout = null; | ||
const startHidingRefHint = (event) => { | ||
if (timeout) { | ||
clearTimeout(timeout); | ||
} | ||
timeout = setTimeout(() => { | ||
hideRefHint(refHint); | ||
}, 1000); | ||
} | ||
const resetHidingRefHint = (event) => { | ||
if (timeout) clearTimeout(timeout); | ||
timeout = null; | ||
}; | ||
link.addEventListener("mouseleave", startHidingRefHint); | ||
link.addEventListener("mouseenter", resetHidingRefHint); | ||
link.addEventListener("blur", startHidingRefHint); | ||
link.addEventListener("focus", resetHidingRefHint); | ||
refHint.addEventListener("mouseleave", startHidingRefHint); | ||
refHint.addEventListener("mouseenter", resetHidingRefHint); | ||
refHint.addEventListener("blur", startHidingRefHint); | ||
refHint.addEventListener("focus", resetHidingRefHint); | ||
|
||
refHint.teardownEventListeners = () => { | ||
// remove event listeners | ||
resetHidingRefHint(); | ||
link.removeEventListener("mouseleave", startHidingRefHint); | ||
link.removeEventListener("mouseenter", resetHidingRefHint); | ||
link.removeEventListener("blur", startHidingRefHint); | ||
link.removeEventListener("focus", resetHidingRefHint); | ||
refHint.removeEventListener("mouseleave", startHidingRefHint); | ||
refHint.removeEventListener("mouseenter", resetHidingRefHint); | ||
refHint.removeEventListener("blur", startHidingRefHint); | ||
refHint.removeEventListener("focus", resetHidingRefHint); | ||
}; | ||
} | ||
|
||
function positionRefHint(refHint) { | ||
const link = refHint.forLink; | ||
const linkPos = getRootLevelAbsolutePosition(link); | ||
refHint.style.top = linkPos.bottom + "px"; | ||
refHint.style.left = linkPos.left + "px"; | ||
|
||
const panelPos = refHint.getBoundingClientRect(); | ||
const panelMargin = 8; | ||
const maxRight = document.body.parentNode.clientWidth - panelMargin; | ||
if (panelPos.right > maxRight) { | ||
const overflowAmount = panelPos.right - maxRight; | ||
const newLeft = Math.max(panelMargin, linkPos.left - overflowAmount); | ||
refHint.style.left = newLeft + "px"; | ||
} | ||
} | ||
|
||
// TODO: shared util | ||
// Returns the root-level absolute position {left and top} of element. | ||
function getRootLevelAbsolutePosition(el) { | ||
const boundsRect = el.getBoundingClientRect(); | ||
let xPos = 0; | ||
let yPos = 0; | ||
|
||
while (el) { | ||
let xScroll = el.scrollLeft; | ||
let yScroll = el.scrollTop; | ||
|
||
// Ignore scrolling of body. | ||
if (el.tagName === "BODY") { | ||
xScroll = 0; | ||
yScroll = 0; | ||
} | ||
xPos += (el.offsetLeft - xScroll + el.clientLeft); | ||
yPos += (el.offsetTop - yScroll + el.clientTop); | ||
|
||
el = el.offsetParent; | ||
} | ||
return { | ||
left: xPos, | ||
top: yPos, | ||
right: xPos + boundsRect.width, | ||
bottom: yPos + boundsRect.height, | ||
}; | ||
} | ||
|
||
function insertLinkPopupAction(refHint) { | ||
const link = refHint.forLink; | ||
// link.setAttribute('role', 'button'); | ||
link.setAttribute('aria-expanded', 'false') | ||
link.classList.add('has-ref-hint'); | ||
link.addEventListener('mouseover', (event) => { | ||
showRefHint(refHint); | ||
}); | ||
link.addEventListener('focus', (event) => { | ||
showRefHint(refHint); | ||
}) | ||
} | ||
|
||
document.addEventListener("DOMContentLoaded", () => { | ||
genAllRefHints(); | ||
|
||
document.body.addEventListener("click", (e) => { | ||
// If not handled already, just hide all link panels. | ||
hideAllRefHints(); | ||
}); | ||
}); | ||
|
||
window.addEventListener("resize", () => { | ||
// Hide any open ref hint. | ||
hideAllRefHints(); | ||
}); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Binary file added
BIN
+76.6 KB
..._screenshots__/links001_pwtest.js/flex-container-dfn-popup-1-chromium-linux.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+62.2 KB
...__screenshots__/links001_pwtest.js/flex-container-ref-hint-1-chromium-linux.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Oops, something went wrong.