@tanstack/charts
Version:
A chart grammar for TypeScript and JavaScript. Marks consume your data directly, channels describe visual encodings, and the engine compiles them into a renderer-neutral keyed scene. TanStack's compact scales cover common numeric and categorical mappings.
185 lines (184 loc) • 4.94 kB
JavaScript
import {
placeTooltip,
pointInBounds,
sceneToClient,
viewportBounds
} from "./tooltip-position.js";
const portal = {
id: "portal",
create: createPortal
};
function createPortal(context, initialOptions) {
let options = initialOptions;
let listening = false;
let usesPopover = false;
let popoverOpen = false;
let popoverFailed = false;
let positionFrame;
let positionContext;
const { container, element } = context;
const document = container.ownerDocument;
const view = document.defaultView;
const resizeObserver = view?.ResizeObserver ? new view.ResizeObserver(schedulePosition) : void 0;
resizeObserver?.observe(element);
function schedulePosition() {
if (!positionContext || positionFrame !== void 0) return;
if (!view?.requestAnimationFrame) {
position(positionContext);
return;
}
positionFrame = view.requestAnimationFrame(() => {
positionFrame = void 0;
if (positionContext) position(positionContext);
});
}
function startListening() {
if (listening) return;
listening = true;
view?.addEventListener("scroll", schedulePosition, {
capture: true,
passive: true
});
view?.addEventListener("resize", schedulePosition, { passive: true });
view?.visualViewport?.addEventListener("scroll", schedulePosition, {
passive: true
});
view?.visualViewport?.addEventListener("resize", schedulePosition, {
passive: true
});
resizeObserver?.observe(container);
}
function stopListening() {
if (!listening) return;
listening = false;
view?.removeEventListener("scroll", schedulePosition, true);
view?.removeEventListener("resize", schedulePosition);
view?.visualViewport?.removeEventListener("scroll", schedulePosition);
view?.visualViewport?.removeEventListener("resize", schedulePosition);
resizeObserver?.unobserve(container);
if (positionFrame !== void 0) {
view?.cancelAnimationFrame?.(positionFrame);
positionFrame = void 0;
}
}
function configureParent() {
const showPopover2 = element.showPopover;
if (typeof showPopover2 === "function" && !popoverFailed) {
if (element.parentNode !== container) container.append(element);
element.setAttribute("popover", "manual");
element.dataset.tsChartTooltipPortal = "popover";
element.style.zIndex = "1";
usesPopover = true;
} else {
moveToFallback();
}
Object.assign(element.style, {
position: "fixed",
right: "auto",
bottom: "auto",
margin: "0"
});
}
function position(next) {
positionContext = next;
startListening();
configureParent();
const anchor = sceneToClient(
next.surface.element,
next.scene.width,
next.scene.height,
next.anchor
);
const boundary = viewportBounds(document);
if (!anchor || boundary.right <= boundary.left || boundary.bottom <= boundary.top || !pointInBounds(anchor, boundary)) {
hide(false);
return false;
}
element.removeAttribute("hidden");
if (usesPopover && !showPopover()) {
popoverFailed = true;
moveToFallback();
}
placeTooltip(
element,
anchor.x,
anchor.y,
boundary,
next.placement,
next.offset
);
return true;
}
function moveToFallback() {
hidePopover();
usesPopover = false;
element.removeAttribute("popover");
element.dataset.tsChartTooltipPortal = "fallback";
element.style.zIndex = "2147483647";
const parent = document.body ?? document.documentElement;
if (element.parentNode !== parent) parent.append(element);
}
function showPopover() {
if (!usesPopover) return false;
if (popoverIsOpen()) return true;
try {
;
element.showPopover();
popoverOpen = true;
return true;
} catch {
popoverOpen = false;
return false;
}
}
function hidePopover() {
if (!usesPopover) return;
try {
if (popoverIsOpen()) {
;
element.hidePopover?.();
}
} catch {
}
popoverOpen = false;
}
function popoverIsOpen() {
if (!usesPopover) return false;
try {
return element.matches(":popover-open");
} catch {
return popoverOpen;
}
}
function hide(stop = true) {
hidePopover();
element.setAttribute("hidden", "");
if (stop) {
positionContext = void 0;
stopListening();
}
}
configureParent();
return {
update(nextOptions) {
options = nextOptions;
void options;
},
position,
hide,
destroy() {
positionContext = void 0;
stopListening();
hidePopover();
resizeObserver?.disconnect();
usesPopover = false;
popoverOpen = false;
popoverFailed = false;
element.removeAttribute("popover");
delete element.dataset.tsChartTooltipPortal;
}
};
}
export {
portal
};