/** * dom-polyfill.ts * * Sets up (or extends) the global DOM environment required by @maxgraph/core * when running in Node.js. Safe to import multiple times — idempotent. * * The parser (drawio-parser/parser.ts) already sets up a partial DOM polyfill * for the basic maxGraph model/serializer classes. This module extends it with * the additional globals needed by Graph+GraphView (rendering pipeline): * - location * - SVGElement, MouseEvent, KeyboardEvent, TouchEvent * - requestAnimationFrame / cancelAnimationFrame * * Must be imported before any maxGraph Graph/GraphView usage. */ import { JSDOM } from "jsdom"; // Use the same DOM that may already be set up by parser.ts, or create a new one. // We detect by checking whether global.document already exists. let win: Record; if ((globalThis as Record)["document"]) { // Parser already set up a DOM — re-use the existing window win = (globalThis as Record)["window"] as Record; } else { const dom = new JSDOM( '', { pretendToBeVisual: true } ); win = dom.window as unknown as Record; (globalThis as Record)["document"] = win["document"]; (globalThis as Record)["window"] = win; (globalThis as Record)["DOMParser"] = win["DOMParser"]; (globalThis as Record)["XMLSerializer"] = win["XMLSerializer"]; (globalThis as Record)["HTMLElement"] = win["HTMLElement"]; (globalThis as Record)["Element"] = win["Element"]; (globalThis as Record)["Node"] = win["Node"]; (globalThis as Record)["Event"] = win["Event"]; try { Object.defineProperty(globalThis, "navigator", { value: win["navigator"], writable: true, configurable: true, }); } catch { /* already defined */ } } // Add/overwrite globals needed specifically for Graph+GraphView rendering const g = globalThis as Record; if (!g["SVGElement"]) g["SVGElement"] = win["SVGElement"] ?? class SVGElement {}; if (!g["MouseEvent"]) g["MouseEvent"] = win["MouseEvent"] ?? class MouseEvent {}; if (!g["KeyboardEvent"]) g["KeyboardEvent"] = win["KeyboardEvent"] ?? class KeyboardEvent {}; if (!g["TouchEvent"]) g["TouchEvent"] = win["TouchEvent"] ?? class TouchEvent {}; if (!g["requestAnimationFrame"]) { g["requestAnimationFrame"] = (fn: () => void) => setTimeout(fn, 0); } if (!g["cancelAnimationFrame"]) { g["cancelAnimationFrame"] = clearTimeout; } // location is needed by UrlConverter (used when rendering image shapes) if (!g["location"]) { g["location"] = { protocol: "http:", host: "localhost", href: "http://localhost/", pathname: "/", }; } export {};