import { chromaticScale } from "./scale.mjs"; customElements.define('x-string', class extends HTMLElement { static observedAttributes = ['tonic', 'value', 'frets']; constructor() { super(); console.debug('x-string#constructor', this); this.muted.onclick = this.muteClicked.bind(this); this.tonic.onclick = this.fretClicked.bind(this); this.attachShadow({mode: 'open'}); } connectedCallback() { const template = this.querySelector('template').content; this.shadowRoot.appendChild(template.cloneNode(true)); console.debug('attached', template, 'to shadow root', shadowRoot); } get tonic() { return this.fretList.querySelector('.tonic'); } get fretList() { return this.querySelector('.fretlist'); } get fretInputs() { return this.querySelectorAll('.fretlist input[name="string"]'); } get muted() { return this.querySelector('input[name="muted"]'); } get selected() { return this.querySelector('[slot="selected"]'); } get chromIndex() { return chromaticScale.indexOf(this.getAttribute('tonic')); } muteClicked(e) { e.preventDefault(); console.debug('x-string#muteClicked', this, e.target.checked); if (e.target.checked) { this.setAttribute('value', 'x'); } else { this.setAttribute('value', this.getAttribute('tonic')); } } fretClicked(e) { e.preventDefault(); console.debug('x-string#fretClicked', this, e.target.getAttribute('value')); this.setAttribute('value', e.target.getAttribute('value')); } attributeChangedCallback(name, old, v) { console.debug('x-string#attributeChangedCallback', this, name, old, v); switch (name) { case 'tonic': return this.updateTonic(v); case 'value': return this.updateValue(v); case 'frets': return this.updateFrets(Number(old || '0'), Number(v)); } } updateTonic(tonic) { console.debug('x-string#updateTonic', this, tonic); // for compat with the radio inputs. this.tonic.setAttribute('value', tonic); this.tonic.textContent = tonic; // update all fret values this.fretInputs .forEach((fret, i) => { console.debug(' -- fret', i, fret); fret.setAttribute('value', i.toString()); }); } updateValue(value) { console.debug('x-string#updateValue', this, value); this.selected.textContent = value; if (value === 'x') { this.classList.add('muted'); this.muted.checked = true; return; } else { this.classList.remove('muted'); this.muted.checked = false; } this.fretInputs .forEach(fret => { fret.checked = this.getAttribute('value') === fret.getAttribute('value'); if (fret.checked) { fret.classList.add('checked'); } else { fret.classList.remove('checked'); } console.debug(' -- fret', fret, fret.checked); }); } updateFrets(old, frets) { console.debug('x-string#updateFrets', this, old, frets); if (old < frets) { const tonic = this.getAttribute('tonic'); if (!tonic) { return; } const chromIndex = this.chromIndex; const fretList = this.fretList; for (let i = old+1; i <= frets; i++) { const note = chromaticScale[chromIndex + i]; console.debug(' -- appending fret', i, 'from', note, fretList); const input = document.createElement('input'); input.setAttribute('type', 'radio'); input.setAttribute('name', 'string'); input.setAttribute('value', note); input.onclick = this.fretClicked.bind(this); const li = document.createElement('li'); li.appendChild(input); fretList.appendChild(li); } } } });