import { html } from "htm/preact"; import { useEffect, useState } from "preact/hooks"; import { getArrow, getBoxToBoxArrow } from "perfect-arrows"; interface ArrowProps { from: string | Element; // Element selector to: string | Element; // Element selector fromSide: "top" | "bottom" | "left" | "right"; toSide: "top" | "bottom" | "left" | "right"; } export function Arrow({ from, to, fromSide, toSide }: ArrowProps) { // Rerender 5fps const [, setTick] = useState(0); useEffect(() => { const interval = setInterval(() => { setTick((t) => t + 1); }, 200); return () => clearInterval(interval); }, []); const fromElem = from instanceof Element ? from : document.querySelector(from); const toElem = to instanceof Element ? to : document.querySelector(to); if (!fromElem || !toElem) { // console.error("Arrow: from or to element not found", { fromElem, toElem }); return null; } const fromRect = fromElem.getBoundingClientRect(); const toRect = toElem.getBoundingClientRect(); const arrowOptions = { bow: 0.2, stretch: 0.5, stretchMin: 40, stretchMax: 420, padStart: 0, padEnd: 0, flip: false, straights: false, }; // const arrow = getBoxToBoxArrow( // fromRect.left, // fromRect.top, // fromRect.width, // fromRect.height, // toRect.left, // toRect.top, // toRect.width, // toRect.height, // arrowOptions, // ); const arrow = getArrow( ...getSide(fromRect, fromSide), ...getSide(toRect, toSide), arrowOptions, ); const [sx, sy, cx, cy, ex, ey, ae, as, ec] = arrow; const endAngleAsDegrees = ae * (180 / Math.PI); return html` `; } function getSide( box: DOMRect, side: "top" | "bottom" | "left" | "right", ): [number, number] { switch (side) { case "top": return [box.left + box.width / 2, box.top]; case "bottom": return [box.left + box.width / 2, box.bottom]; case "left": return [box.left, box.top + box.height / 2]; case "right": return [box.right, box.top + box.height / 2]; } }