// GridSelector component definition // This file should be loaded as a module (type="module") after the // corresponding HTML template (grid-selector.html) is present in the DOM. class GridSelector extends HTMLElement { static get observedAttributes() { return ['grid-color', 'grid-size', 'grid-numbering']; } constructor() { super(); // Attach a shadow root this.attachShadow({ mode: 'open' }); // Load the template defined in grid-selector.html const tmpl = document.getElementById('grid-selector-template'); if (!tmpl) { console.error('GridSelector: template not found in DOM'); return; } this.shadowRoot.appendChild(tmpl.content.cloneNode(true)); // Grab elements from the template this._colorInput = this.shadowRoot.querySelector('#grid-color'); this._sizeInput = this.shadowRoot.querySelector('#grid-size'); this._numberingInput = this.shadowRoot.querySelector('#grid-numbering'); // Bind event handlers this._onColorChange = this._onColorChange.bind(this); this._onSizeChange = this._onSizeChange.bind(this); this._onNumberingChange = this._onNumberingChange.bind(this); } connectedCallback() { // Initialise values from attributes or defaults this._colorInput.value = this.getAttribute('grid-color') || '#000000'; this._sizeInput.value = this.getAttribute('grid-size') || '25'; const numbering = this.hasAttribute('grid-numbering') ? this.getAttribute('grid-numbering') === 'true' : true; this._numberingInput.checked = numbering; // Register listeners this._colorInput.addEventListener('input', this._onColorChange); this._sizeInput.addEventListener('input', this._onSizeChange); this._numberingInput.addEventListener('change', this._onNumberingChange); } disconnectedCallback() { this._colorInput.removeEventListener('input', this._onColorChange); this._sizeInput.removeEventListener('input', this._onSizeChange); this._numberingInput.removeEventListener('change', this._onNumberingChange); } attributeChangedCallback(name, oldValue, newValue) { if (oldValue === newValue) return; switch (name) { case 'grid-color': if (this._colorInput) this._colorInput.value = newValue; break; case 'grid-size': if (this._sizeInput) this._sizeInput.value = newValue; break; case 'grid-numbering': if (this._numberingInput) this._numberingInput.checked = newValue === 'true'; break; } } // ----- Property getters / setters ----- get gridColor() { return this._colorInput ? this._colorInput.value : this.getAttribute('grid-color'); } set gridColor(v) { this.setAttribute('grid-color', v); } get gridSize() { return this._sizeInput ? Number(this._sizeInput.value) : Number(this.getAttribute('grid-size')); } set gridSize(v) { this.setAttribute('grid-size', v); } get gridNumbering() { return this._numberingInput ? this._numberingInput.checked : this.hasAttribute('grid-numbering') && this.getAttribute('grid-numbering') === 'true'; } set gridNumbering(v) { this.setAttribute('grid-numbering', v); } // ----- Internal event handlers ----- _onColorChange(e) { const color = e.target.value; this.setAttribute('grid-color', color); this._dispatchChange(); } _onSizeChange(e) { const size = e.target.value; this.setAttribute('grid-size', size); this._dispatchChange(); } _onNumberingChange(e) { const checked = e.target.checked; this.setAttribute('grid-numbering', checked); this._dispatchChange(); } _dispatchChange() { this.dispatchEvent( new CustomEvent('gridchange', { detail: { color: this.gridColor, size: this.gridSize, numbering: this.gridNumbering, }, bubbles: true, composed: true, }) ); } } // Register the custom element customElements.define('grid-selector', GridSelector);