feat(org-chart): add core cards and fixed-layout integration
This commit is contained in:
1 parent
d18e3c3380
commit
61c6f8d1f1
161 files changed
+13044
-1
No files matched your search
@@ -0,0 +1,72 @@
|
||||
let tooltipSequence = 0;
|
||||
|
||||
/** Presentation-only overflow. Keep the complete text node for accessibility;
|
||||
* cropped names enter the tab order and expose a hoverable/dismissible tooltip.
|
||||
* Each observer/listener/tooltip belongs to the existing card abort boundary. */
|
||||
export function observePersonNameOverflow(card: HTMLElement, name: HTMLElement, signal: AbortSignal): void {
|
||||
const document = card.ownerDocument;
|
||||
const window = document.defaultView;
|
||||
if (!window) return;
|
||||
const tooltip = document.createElement('span');
|
||||
tooltip.id = `hm-orgchart-person-name-tip-${++tooltipSequence}`;
|
||||
tooltip.className = 'hm-orgchart-person-name-tooltip';
|
||||
tooltip.setAttribute('role', 'tooltip');
|
||||
tooltip.textContent = name.textContent;
|
||||
tooltip.hidden = true;
|
||||
let timer: number | undefined;
|
||||
let hoveringTooltip = false;
|
||||
let scaleListening = false;
|
||||
const focused = () => (name.getRootNode() as ShadowRoot).activeElement === name;
|
||||
function cancelTimer(): void { if (timer !== undefined) window!.clearTimeout(timer); timer = undefined; }
|
||||
function hide(): void { cancelTimer(); tooltip.hidden = true; name.dataset.tooltipOpen = 'false'; }
|
||||
function refresh(): void {
|
||||
if (signal.aborted || !name.isConnected || name.clientWidth === 0) return;
|
||||
const overflow = name.scrollWidth > name.clientWidth;
|
||||
name.dataset.nameOverflow = String(overflow);
|
||||
if (overflow) {
|
||||
name.tabIndex = 0;
|
||||
name.setAttribute('aria-describedby', tooltip.id);
|
||||
// Fixed positioning avoids adding hidden content to tree/document scroll
|
||||
// extents. Append inside the same Shadow Root for IDREF accessibility.
|
||||
const shell = card.closest('.hm-orgchart-shell');
|
||||
shell?.append(tooltip);
|
||||
// Full Composition scale changes move cards without a window resize.
|
||||
if (shell && !scaleListening) { scaleListening = true; shell.addEventListener('hm-orgchart-scale', hide, { signal }); }
|
||||
} else {
|
||||
name.removeAttribute('tabindex');
|
||||
name.removeAttribute('aria-describedby');
|
||||
hide();
|
||||
tooltip.remove();
|
||||
}
|
||||
}
|
||||
function show(): void {
|
||||
cancelTimer();
|
||||
refresh();
|
||||
if (name.dataset.nameOverflow !== 'true') return;
|
||||
tooltip.hidden = false;
|
||||
name.dataset.tooltipOpen = 'true';
|
||||
const bounds = name.getBoundingClientRect();
|
||||
const tip = tooltip.getBoundingClientRect();
|
||||
const left = Math.max(8, Math.min(bounds.left, window!.innerWidth - tip.width - 8));
|
||||
const below = bounds.bottom + 6;
|
||||
const top = below + tip.height <= window!.innerHeight - 8 ? below : Math.max(8, bounds.top - tip.height - 6);
|
||||
tooltip.style.left = `${left}px`;
|
||||
tooltip.style.top = `${top}px`;
|
||||
}
|
||||
name.addEventListener('mouseenter', show, { signal });
|
||||
name.addEventListener('mouseleave', () => {
|
||||
if (!focused()) timer = window.setTimeout(() => { if (!hoveringTooltip) hide(); }, 180);
|
||||
}, { signal });
|
||||
name.addEventListener('focus', show, { signal });
|
||||
name.addEventListener('blur', () => { if (!hoveringTooltip) hide(); }, { signal });
|
||||
name.addEventListener('keydown', event => { if (event.key === 'Escape') hide(); }, { signal });
|
||||
tooltip.addEventListener('mouseenter', () => { hoveringTooltip = true; cancelTimer(); }, { signal });
|
||||
tooltip.addEventListener('mouseleave', () => { hoveringTooltip = false; if (!focused()) hide(); }, { signal });
|
||||
window.addEventListener('scroll', hide, { capture: true, signal });
|
||||
window.addEventListener('resize', hide, { signal });
|
||||
document.fonts.addEventListener('loadingdone', refresh, { signal });
|
||||
const observer = new window.ResizeObserver(refresh);
|
||||
observer.observe(name);
|
||||
void document.fonts.ready.then(refresh);
|
||||
signal.addEventListener('abort', () => { cancelTimer(); observer.disconnect(); tooltip.remove(); }, { once: true });
|
||||
}
|
||||
Reference in new issue
Block a user