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];
}
}