// CanvasDisplay component logic // This file defines the custom element which renders a canvas // and an SVG grid overlay. It reacts to background‑color and grid‑settings // events from the toolbar or directly via attributes. class CanvasDisplay extends HTMLElement { static get observedAttributes() { return ["bg-color", "grid-color", "grid-size", "grid-numbering"]; } constructor() { super(); // Attach shadow root this.attachShadow({ mode: "open" }); // Create a template element with the canvas-display markup directly in JS const tmpl = document.createElement("template"); tmpl.innerHTML = `
`; this.shadowRoot.appendChild(tmpl.content.cloneNode(true)); // Elements inside the shadow DOM this._canvas = this.shadowRoot.querySelector("#draw-canvas"); this._gridOverlay = this.shadowRoot.querySelector("#grid-overlay"); // Canvas drawing context this._ctx = this._canvas.getContext("2d"); // Bind handlers this._onBgColorChange = this._onBgColorChange.bind(this); this._onGridSettingsChange = this._onGridSettingsChange.bind(this); this._onGridToggle = this._onGridToggle.bind(this); } connectedCallback() { // Initialise size – a default that can be overridden by CSS this._canvas.width = this.clientWidth || 300; this._canvas.height = this.clientHeight || 300; // Apply default attribute values if none are set if (!this.hasAttribute("bg-color")) this.setAttribute("bg-color", "#ffffff"); if (!this.hasAttribute("grid-color")) this.setAttribute("grid-color", "#000000"); if (!this.hasAttribute("grid-size")) this.setAttribute("grid-size", "25"); if (!this.hasAttribute("grid-numbering")) this.setAttribute("grid-numbering", "true"); // Initial draw this._updateCanvasBackground(); this._drawGrid(); // Listen for events bubbled up from slotted child components this.addEventListener("bgcolorchange", this._onBgColorChange); this.addEventListener("gridsettingschange", this._onGridSettingsChange); this.addEventListener("gridtoggle", this._onGridToggle); } disconnectedCallback() { this.removeEventListener("bgcolorchange", this._onBgColorChange); this.removeEventListener("gridsettingschange", this._onGridSettingsChange); this.removeEventListener("gridtoggle", this._onGridToggle); } attributeChangedCallback(name, oldVal, newVal) { if (oldVal === newVal) return; switch (name) { case "bg-color": this._updateCanvasBackground(); break; case "grid-color": case "grid-size": case "grid-numbering": this._drawGrid(); break; } } // ----- Property getters/setters ----- get bgColor() { return this.getAttribute("bg-color"); } set bgColor(v) { this.setAttribute("bg-color", v); } get gridColor() { return this.getAttribute("grid-color"); } set gridColor(v) { this.setAttribute("grid-color", v); } get gridSize() { return Number(this.getAttribute("grid-size")); } set gridSize(v) { this.setAttribute("grid-size", v); } get gridNumbering() { return this.getAttribute("grid-numbering") === "true"; } set gridNumbering(v) { this.setAttribute("grid-numbering", v); } // ----- Event handlers ----- _onBgColorChange(e) { const { color } = e.detail; this.bgColor = color; } _onGridSettingsChange(e) { const { color, size, numbering } = e.detail; this.gridColor = color; this.gridSize = size; this.gridNumbering = numbering; } _onGridToggle(e) { const { enabled } = e.detail; this.gridNumbering = enabled; // Reflect the change to the attribute so CSS/DOM stays in sync this.setAttribute("grid-numbering", enabled); } // ----- Rendering helpers ----- _updateCanvasBackground() { const { _ctx, _canvas, bgColor } = this; _ctx.save(); _ctx.fillStyle = bgColor; _ctx.fillRect(0, 0, _canvas.width, _canvas.height); _ctx.restore(); } _drawGrid() { const { _gridOverlay, _canvas, gridColor, gridSize, gridNumbering } = this; // Clear any previous grid while (_gridOverlay.firstChild) _gridOverlay.removeChild(_gridOverlay.firstChild); const width = _canvas.width; const height = _canvas.height; const spacing = gridSize; // Set SVG size and viewBox to match canvas dimensions _gridOverlay.setAttribute("width", width); _gridOverlay.setAttribute("height", height); _gridOverlay.setAttribute("viewBox", `0 0 ${width} ${height}`); // Helper to create a line const makeLine = (x1, y1, x2, y2, thick) => { const line = document.createElementNS( "http://www.w3.org/2000/svg", "line", ); line.setAttribute("x1", x1); line.setAttribute("y1", y1); line.setAttribute("x2", x2); line.setAttribute("y2", y2); line.setAttribute("stroke", gridColor); line.setAttribute("stroke-width", thick ? 2 : 1); return line; }; // Draw vertical lines for (let x = 0; x <= width; x += spacing) { const thick = (x / spacing) % 5 === 0; _gridOverlay.appendChild(makeLine(x, 0, x, height, thick)); } // Draw horizontal lines for (let y = 0; y <= height; y += spacing) { const thick = (y / spacing) % 5 === 0; _gridOverlay.appendChild(makeLine(0, y, width, y, thick)); } // Optional numbering if (gridNumbering) { const makeText = (x, y, txt) => { const t = document.createElementNS( "http://www.w3.org/2000/svg", "text", ); t.setAttribute("x", x + 2); t.setAttribute("y", y + 10); t.setAttribute("fill", gridColor); t.setAttribute("font-size", "8"); t.textContent = txt; return t; }; // Number columns for (let x = spacing; x <= width; x += spacing) { const col = x / spacing; if (col % 5 === 0) _gridOverlay.appendChild(makeText(x, 12, col)); } // Number rows for (let y = spacing; y <= height; y += spacing) { const row = y / spacing; if (row % 5 === 0) _gridOverlay.appendChild(makeText(2, y, row)); } } } } // Register the custom element customElements.define("canvas-display", CanvasDisplay);