Something went wrong. Try again.
My theme for forester (+plugins)
Something went wrong. Try again.
forest-theme gabc.js
3.6 kB · 110 lines
JavaScript
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111class GregoChant extends HTMLElement { constructor() { super(); this._initialized = false;
// Attach shadow DOM this.attachShadow({ mode: 'open' }); this.shadowRoot.innerHTML = ` <slot id="light-slot"></slot> <div id="render"></div> `;
this._slot = this.shadowRoot.querySelector('#light-slot'); this._renderContainer = this.shadowRoot.querySelector('#render');
// Always define a bound _doLayout, safe to call anytime this._doLayout = () => { if (!this.score) return; // layout only if score exists this.score.performLayoutAsync(this._ctxt, () => { this.score.layoutChantLines(this._ctxt, this.clientWidth, () => { this._renderContainer.innerHTML = this.score.createSvg(this._ctxt); // hide the slot content this._slot.style.display = 'none'; }); }); };
this._onSlotChange = this._onSlotChange.bind(this); this._onMutations = this._onMutations.bind(this); }
connectedCallback() { // Listen for slot changes this._slot.addEventListener('slotchange', this._onSlotChange);
// MutationObserver for fallback dynamic content this._mo = new MutationObserver(this._onMutations); this._mo.observe(this, { childList: true, subtree: true, characterData: true });
// ResizeObserver can safely call _doLayout now this._resizeObserver = new ResizeObserver(this._doLayout); this._resizeObserver.observe(this);
// Try initializing immediately in case content is already present this._attemptInit(); }
disconnectedCallback() { this._slot.removeEventListener('slotchange', this._onSlotChange); if (this._mo) this._mo.disconnect(); if (this._resizeObserver) this._resizeObserver.disconnect(); }
_gabcTextFromNodes(nodes) { let text = ''; for (const n of nodes) { if (n.nodeType === Node.TEXT_NODE) text += n.textContent; else if (n.nodeType === Node.ELEMENT_NODE) text += n.innerText || n.textContent || ''; } return text.trim(); }
_onSlotChange() { this._attemptInit(); } _onMutations() { this._attemptInit(); }
_attemptInit() { if (this._initialized) return;
// prefer slotted nodes const assigned = this._slot.assignedNodes({ flatten: true }) || []; let gabc = assigned.length ? this._gabcTextFromNodes(assigned) : this.textContent.trim(); if (!gabc) return; // still empty, wait for future content
this._initializeWithGabc(gabc); }
_initializeWithGabc(gabc) { if (this._initialized) return; this._initialized = true;
this.score_src = gabc.replaceAll("GABCSPACE"," "); console.log(this.score_src); const ctxt = new exsurge.ChantContext(); ctxt.lyricTextFont = "'Crimson Text', serif"; ctxt.lyricTextSize *= 1.2; ctxt.dropCapTextFont = ctxt.lyricTextFont; ctxt.annotationTextFont = ctxt.lyricTextFont; this._ctxt = ctxt; // store context for _doLayout const mappings = exsurge.Gabc.createMappingsFromSource(ctxt, this.score_src); const useDropCap = this.getAttribute('use-drop-cap') !== 'false'; this.score = new exsurge.ChantScore(ctxt, mappings, useDropCap);
const annotationAttr = this.getAttribute('annotation'); if (annotationAttr) { this.score.annotation = new exsurge.Annotation(ctxt, annotationAttr); }
// initial layout this._doLayout();
// After initialization, we can disconnect MutationObserver if desired if (this._mo) { this._mo.disconnect(); this._mo = null; } }}
window.customElements.define('grego-chant', GregoChant);