// drawer.jsx — detail view as a side drawer. function StockDrawer({ ticker, onClose, onRemove }) { const [quote, setQuote] = React.useState(null); const [range, setRange] = React.useState("1D"); const [confirmingRemove, setConfirmingRemove] = React.useState(false); React.useEffect(() => { if (!ticker) return undefined; let unsub; let cancelled = false; fetchQuote(ticker).then((q) => { if (cancelled) return; setQuote({ ...q }); unsub = subscribeToQuoteUpdates(ticker, (next) => setQuote({ ...next })); }); return () => { cancelled = true; if (unsub) unsub(); }; }, [ticker]); React.useEffect(() => { if (!ticker) return undefined; const onKey = (e) => { if (e.key === "Escape") onClose(); }; window.addEventListener("keydown", onKey); document.body.style.overflow = "hidden"; return () => { window.removeEventListener("keydown", onKey); document.body.style.overflow = ""; }; }, [ticker, onClose]); // A fresh ticker — or a close/reopen — always starts out of the confirm state. React.useEffect(() => { setConfirmingRemove(false); }, [ticker]); if (!ticker) return null; const d = quote ? resolveDisplayQuote(quote) : null; const direction = d ? d.displayChange > 0 ? "up" : d.displayChange < 0 ? "down" : "flat" : "flat"; const sessionKey = d ? d.sessionLabel.toLowerCase().replace(/\s/g, "-") : ""; return ( <>
); } function Stat({ label, value }) { return (
{label}
{value ?? "—"}
); } // Deterministic sparkline so each ticker/range pair stays stable across re-renders function MiniChart({ seed, direction, points = 48 }) { const path = React.useMemo(() => { const W = 1000, H = 220, pad = 8; let s = 0; for (let i = 0; i < seed.length; i++) s = (s * 31 + seed.charCodeAt(i)) | 0; const rnd = () => { s = (s * 1664525 + 1013904223) | 0; return ((s >>> 0) % 10000) / 10000; }; const ys = []; let v = 0.5; for (let i = 0; i < points; i++) { v += (rnd() - 0.5) * 0.08; v = Math.max(0.1, Math.min(0.9, v)); ys.push(v); } // Bias the endpoint to match direction so the chart visually agrees with the price move const bias = direction === "up" ? -0.18 : direction === "down" ? 0.18 : 0; ys[0] = Math.max(0.1, Math.min(0.9, ys[0] + bias)); const stepX = (W - pad * 2) / (points - 1); const toY = (n) => pad + (1 - n) * (H - pad * 2); let line = "M " + pad + " " + toY(ys[0]); for (let i = 1; i < points; i++) { line += " L " + (pad + i * stepX) + " " + toY(ys[i]); } const fill = line + " L " + (W - pad) + " " + (H - pad) + " L " + pad + " " + (H - pad) + " Z"; return { line, fill, last: ys[ys.length - 1], lastX: W - pad }; }, [seed, points, direction]); return ( ); } window.StockDrawer = StockDrawer;