lvwerra's picture
lvwerra HF Staff
Lift captions over the ribbons, light the inflowing flux, move counts to the tooltip (#5)
2c2de0b
Raw History Blame Contribute Delete
3.05 kB
// Delegate to the component root so navigation still works after HTML updates.
function activate(event) {
const target = event.target.closest('[data-path]');
if (!target || !element.contains(target)) return;
if (event.type === 'keydown' && !['Enter', ' '].includes(event.key)) return;
// Native buttons already synthesize a click on keyboard activation.
if (event.type === 'keydown' && target.tagName.toLowerCase() === 'button') return;
event.preventDefault();
trigger('click', {route: target.dataset.path});
}
element.addEventListener('click', activate);
element.addEventListener('keydown', activate);
// A group and the flux arriving at it share a data-node id, so hovering either
// one lights both. The ribbons are painted beneath every group, so no CSS
// sibling rule can reach them from the group being hovered.
let lit = '';
function light(uid) {
if (uid === lit) return;
lit = uid;
element.querySelectorAll('.is-lit').forEach((el) => el.classList.remove('is-lit'));
if (uid) element.querySelectorAll(`[data-node="${CSS.escape(uid)}"]`).forEach((el) => el.classList.add('is-lit'));
}
element.addEventListener('focusin', (event) => light(event.target.closest('[data-node]')?.dataset.node || ''));
element.addEventListener('focusout', () => light(''));
element.addEventListener('pointermove', (event) => {
const node = event.target.closest('[data-detail]');
const tooltip = element.querySelector('.atlas-tooltip');
const atlas = element.querySelector('.atlas');
if (!tooltip || !atlas) return;
if (!node || event.pointerType === 'touch') { tooltip.hidden = true; light(''); return; }
light(node.dataset.node || '');
tooltip.textContent = node.dataset.detail;
tooltip.hidden = false;
const bounds = atlas.getBoundingClientRect();
tooltip.style.left = Math.max(8, Math.min(event.clientX - bounds.left + 15, bounds.width - 305)) + 'px';
tooltip.style.top = Math.max(8, Math.min(event.clientY - bounds.top + 18, bounds.height - tooltip.offsetHeight - 8)) + 'px';
});
element.addEventListener('pointerleave', () => {
const tooltip = element.querySelector('.atlas-tooltip');
if (tooltip) tooltip.hidden = true;
light('');
});
// Expanding adds a level below. The page scrolls, not the widget, so only nudge
// it when the new bottom of the tree has fallen past the viewport.
let shownRoute = element.querySelector('.life-tree')?.dataset.route;
new MutationObserver(() => {
const route = element.querySelector('.life-tree')?.dataset.route;
if (!route || route === shownRoute) return;
const grew = route.length > shownRoute.length;
shownRoute = route;
if (!grew) return;
requestAnimationFrame(() => {
const tree = element.querySelector('.life-tree');
if (!tree) return;
const bottom = tree.getBoundingClientRect().bottom;
if (bottom > window.innerHeight) {
window.scrollBy({top: Math.min(bottom - window.innerHeight + 24, 400), behavior: 'smooth'});
}
});
}).observe(element, {childList: true, subtree: true, attributes: true, attributeFilter: ['data-route']});