UNPKG

media-transcript

Version:

A web component for an interactive transcript built from WebVTT cues.

241 lines (196 loc) 4.35 kB
import { hasAttributeToken, timeStrToNumber, } from './util'; /** * The Transcript Cue is a container for the VTTCue text * It functions like a control for the video location. */ let CueInterface = window.VTTCue; class MediaCue extends HTMLElement { constructor(cue = {}) { super(); // VTTCue must be supported by the user agent or polyfilled by the user if (!MediaCue.VTTCue) { throw new Error( 'No VTTCue interface found. ' + 'Please use a browser that supports VTTCue ' + 'or supply a polyfill by setting MediaCue.VTTCue', ); } if (cue instanceof MediaCue.VTTCue) { this.cue = cue; } else { const { startTime = null, endTime = null, text = this.innerHTML || null, } = cue; this.cue = new MediaCue.VTTCue(startTime, endTime, text); } this.textSlot = document.createElement('slot'); /** create shadow root and add style + time + slot */ const shadowRoot = this.attachShadow({ mode: 'open', delegatesFocus: true }); shadowRoot.appendChild(this.textSlot); this.textSlotchangeHandler = () => { if (!this.updating) { let text = ''; this.textSlot.assignedNodes().forEach(({ outerHTML, nodeValue }) => { text += outerHTML || nodeValue; }); this.cue.text = text; // this.triggerSlot.innerHTML = text; } this.updating = false; }; } connectedCallback() { this.selected = false; } attributeChangedCallback(attr, oldValue, newValue) { switch (attr) { case 'start': case 'end': if (newValue && !this.updating) { // bind cue start/end to the attribute values this.cue[`${attr}Time`] = timeStrToNumber(newValue); } break; default: } } update(cue) { if (cue instanceof CueInterface) { this.cue = cue; } else { this.cue.startTime = cue.startTime || this.startInSeconds(); this.cue.endTime = cue.endTime || this.endInSeconds(); this.cue.text = cue.text || this.text; } this.updating = true; this.start = this.cue.startTime; this.end = this.cue.endTime; this.appendChild(this.cue.getCueAsHTML()); return this; } select() { this.selected = true; return this; } deselect() { this.selected = false; return this; } hasRole(...roles) { return hasAttributeToken.call(this, 'role', ...roles); } /** * @todo: parse the DOM nodes into valid WebVTT strings * @see https://w3c.github.io/webvtt/#dom-construction-rules */ toVTTString() { return this; } // PROPERTY REFLECTION get ariaSelected() { return this.getAttribute('aria-selected'); } set ariaSelected(val) { if (val !== null) { this.setAttribute('aria-selected', val); } else { this.removeAttribute('aria-selected'); } } get role() { return this.getAttribute('role'); } set role(val) { if (val) { this.setAttribute('role', val); } else { this.removeAttribute('role'); } } get start() { return this.getAttribute('start') || '0'; } set start(val) { if (val) { this.setAttribute('start', val); } else { this.removeAttribute('start'); } } startInSeconds() { return timeStrToNumber(this.start); } get end() { return this.getAttribute('end'); } set end(val) { if (val) { this.setAttribute('end', val); } else { this.removeAttribute('end'); } } endInSeconds() { return timeStrToNumber(this.end); } get onclick() { return this.clickHandler; } set onclick(fn) { if (typeof fn === 'function') { this.clickHandler = fn.bind(this); this.addEventListener('click', this.clickHandler); this.addEventListener('keydown', (e) => { if (e.key === 'Enter') fn.call(this, e); }); } } // PROPERTY ALIASES get startTime() { return this.cue.startTime; } set startTime(val) { this.start = val; } get endTime() { return this.cue.endTime; } set endTime(val) { this.end = val; } get text() { return this.innerHTML; } set text(val) { this.innerHTML = val; } get selected() { return this.ariaSelected; } set selected(v) { this.ariaSelected = v; } get VTTCue() { return this.cue; } // STATIC PROPERTIES & METHODS static get VTTCue() { return CueInterface; } static set VTTCue(val) { CueInterface = val; } static get observedAttributes() { return [ 'start', 'end', ]; } } export default MediaCue; customElements.define('media-cue', MediaCue);