Files
Styleguide/scripts/tooltip-overlays.js
T

165 lines
5.0 KiB
JavaScript

(function initTooltipOverlayModule() {
const getViewportWidth = () => {
if (window.visualViewport && typeof window.visualViewport.width === 'number') {
return window.visualViewport.width;
}
return window.innerWidth;
};
const getPixelValue = (propertyName) => {
const rootStyles = getComputedStyle(document.documentElement);
const rawValue = rootStyles.getPropertyValue(propertyName).trim();
const rootFontSize = parseFloat(rootStyles.fontSize) || 16;
const value = parseFloat(rawValue);
if (Number.isNaN(value)) {
return 0;
}
if (rawValue.endsWith('rem')) {
return value * rootFontSize;
}
return value;
};
const setTooltipState = (wrap, isOpen) => {
const trigger = wrap.querySelector('[data-component-part="tooltip-trigger"]');
const panel = wrap.querySelector('[data-component-part="tooltip-panel"]');
if (!trigger || !panel) {
return;
}
wrap.dataset.tooltipOpen = String(isOpen);
panel.hidden = !isOpen;
trigger.setAttribute('aria-expanded', String(isOpen));
};
const positionTooltip = (wrap) => {
const trigger = wrap.querySelector('[data-component-part="tooltip-trigger"]');
const panel = wrap.querySelector('[data-component-part="tooltip-panel"]');
if (!trigger || !panel || panel.hidden) {
return;
}
const viewportWidth = getViewportWidth();
const safeInset = getPixelValue('--spacing-large');
const triggerRect = trigger.getBoundingClientRect();
const panelRect = panel.getBoundingClientRect();
const gap = getPixelValue('--spacing-small');
let placement = 'bottom';
if (triggerRect.right + (panelRect.width / 2) > viewportWidth - safeInset) {
placement = 'left';
} else if (triggerRect.left - (panelRect.width / 2) < safeInset) {
placement = 'right';
}
wrap.dataset.tooltipPlacement = placement;
if (placement === 'bottom') {
const nextPanelRect = panel.getBoundingClientRect();
if (nextPanelRect.bottom > window.innerHeight - safeInset
&& triggerRect.top - panelRect.height - gap >= safeInset) {
wrap.dataset.tooltipPlacement = 'top';
}
}
};
const closeTooltip = (wrap) => {
wrap.dataset.tooltipPinned = 'false';
wrap.dataset.tooltipHover = 'false';
wrap.dataset.tooltipFocus = 'false';
wrap.dataset.tooltipDismissed = 'false';
setTooltipState(wrap, false);
};
const openTooltip = (wrap) => {
setTooltipState(wrap, true);
positionTooltip(wrap);
};
const initTooltip = (wrap) => {
if (wrap.dataset.tooltipInit === 'true') {
return;
}
wrap.dataset.tooltipInit = 'true';
wrap.dataset.tooltipPlacement = 'bottom';
wrap.dataset.tooltipPinned = 'false';
wrap.dataset.tooltipHover = 'false';
wrap.dataset.tooltipFocus = 'false';
wrap.dataset.tooltipDismissed = 'false';
setTooltipState(wrap, false);
const trigger = wrap.querySelector('[data-component-part="tooltip-trigger"]');
if (!trigger) {
return;
}
wrap.addEventListener('pointerenter', () => {
if (wrap.dataset.tooltipDismissed === 'true') {
return;
}
wrap.dataset.tooltipHover = 'true';
openTooltip(wrap);
});
wrap.addEventListener('pointerleave', () => {
wrap.dataset.tooltipHover = 'false';
wrap.dataset.tooltipDismissed = 'false';
if (wrap.dataset.tooltipPinned !== 'true' && wrap.dataset.tooltipFocus !== 'true') {
setTooltipState(wrap, false);
}
});
wrap.addEventListener('focusin', () => {
if (wrap.dataset.tooltipDismissed === 'true') {
return;
}
wrap.dataset.tooltipFocus = 'true';
openTooltip(wrap);
});
wrap.addEventListener('focusout', (event) => {
if (wrap.contains(event.relatedTarget)) {
return;
}
wrap.dataset.tooltipFocus = 'false';
wrap.dataset.tooltipDismissed = 'false';
if (wrap.dataset.tooltipPinned !== 'true' && wrap.dataset.tooltipHover !== 'true') {
setTooltipState(wrap, false);
}
});
trigger.addEventListener('click', (event) => {
event.stopPropagation();
const isPinned = wrap.dataset.tooltipPinned === 'true';
wrap.dataset.tooltipPinned = String(!isPinned);
if (isPinned) {
wrap.dataset.tooltipDismissed = 'true';
setTooltipState(wrap, false);
return;
}
wrap.dataset.tooltipDismissed = 'false';
openTooltip(wrap);
});
};
document.querySelectorAll('.sg-tooltip-wrap').forEach(initTooltip);
document.addEventListener('click', (event) => {
document.querySelectorAll('.sg-tooltip-wrap').forEach((wrap) => {
if (!wrap.contains(event.target)) {
closeTooltip(wrap);
}
});
});
const repositionOpenTooltips = () => {
document.querySelectorAll('.sg-tooltip-wrap[data-tooltip-open="true"]').forEach(positionTooltip);
};
window.addEventListener('resize', repositionOpenTooltips);
window.addEventListener('scroll', repositionOpenTooltips, true);
})();