import { useEffect, useRef, useState } from "react"; import { createPortal } from "react-dom"; import { X, Calculator, Maximize2, Minimize2 } from "lucide-react"; // ─── GeoGebra type shim ─────────────────────────────────────────────────────── declare global { interface Window { GGBApplet: new ( params: Record, defer?: boolean, ) => { inject: (containerId: string) => void; }; ggbApplet?: { reset: () => void; setXML: (xml: string) => void; }; } } // ─── Hook: load GeoGebra script once ───────────────────────────────────────── const GEOGEBRA_SCRIPT = "https://www.geogebra.org/apps/deployggb.js"; const useGeoGebraScript = () => { const [ready, setReady] = useState(false); useEffect(() => { if (document.querySelector(`script[src="${GEOGEBRA_SCRIPT}"]`)) { if (window.GGBApplet) setReady(true); return; } const script = document.createElement("script"); script.src = GEOGEBRA_SCRIPT; script.async = true; script.onload = () => setReady(true); document.head.appendChild(script); }, []); return ready; }; // ─── GeoGebra Calculator ────────────────────────────────────────────────────── const GeoGebraCalculator = ({ containerId }: { containerId: string }) => { const scriptReady = useGeoGebraScript(); const injected = useRef(false); const wrapperRef = useRef(null); const [dims, setDims] = useState<{ w: number; h: number } | null>(null); // Measure the wrapper first — GeoGebra needs explicit px dimensions useEffect(() => { const el = wrapperRef.current; if (!el) return; const ro = new ResizeObserver((entries) => { for (const entry of entries) { const { width, height } = entry.contentRect; if (width > 0 && height > 0) { setDims({ w: Math.floor(width), h: Math.floor(height) }); } } }); ro.observe(el); return () => ro.disconnect(); }, []); useEffect(() => { if (!scriptReady || !dims || injected.current) return; injected.current = true; const params = { appName: "graphing", width: dims.w, height: dims.h, showToolBar: true, showAlgebraInput: true, showMenuBar: false, enableLabelDrags: true, enableShiftDragZoom: true, enableRightClick: true, showZoomButtons: true, capturingThreshold: null, showFullscreenButton: false, scale: 1, disableAutoScale: false, allowUpscale: false, clickToLoad: false, appletOnLoad: () => {}, useBrowserForJS: false, showLogging: false, errorDialogsActive: true, showTutorialLink: false, showSuggestionButtons: false, language: "en", id: "ggbApplet", }; try { const applet = new window.GGBApplet(params, true); applet.inject(containerId); } catch (e) { console.error("GeoGebra init error:", e); } }, [scriptReady, dims, containerId]); return (
{!dims && (
Loading calculator...
)}
); }; // ─── Modal ──────────────────────────────────────────────────────────────────── interface GraphCalculatorModalProps { open: boolean; onClose: () => void; } const GraphCalculatorModal = ({ open, onClose }: GraphCalculatorModalProps) => { const [fullscreen, setFullscreen] = useState(false); const containerId = "geogebra-container"; // Trap focus & keyboard dismiss useEffect(() => { if (!open) return; const onKey = (e: KeyboardEvent) => { if (e.key === "Escape") onClose(); }; window.addEventListener("keydown", onKey); return () => window.removeEventListener("keydown", onKey); }, [open, onClose]); // Prevent body scroll while open useEffect(() => { document.body.style.overflow = open ? "hidden" : ""; return () => { document.body.style.overflow = ""; }; }, [open]); if (!open) return null; return createPortal(
{/* Backdrop */}
{/* Panel */}
e.stopPropagation()} > {/* Header */}
Graph Calculator Powered by GeoGebra
{/* GeoGebra canvas area */}
, document.body, ); }; // ─── Trigger button + modal — drop this wherever you need it ────────────────── export const GraphCalculatorButton = () => { const [open, setOpen] = useState(false); return ( <> setOpen(false)} /> ); }; // ─── Standalone modal export if you need to control it externally ───────────── export { GraphCalculatorModal };