import { MediaChromeButton } from './media-chrome-button.js'; import { globalThis } from './utils/server-safe-globals.js'; import { MediaUIAttributes, MediaUIEvents } from './constants.js'; import { areSubsOn, parseTextTracksStr, stringifyTextTrackList, } from './utils/captions.js'; import { TextTrackLike } from './utils/TextTrackLike.js'; import { t } from './utils/i18n.js'; const ccIconOn = ``; const ccIconOff = ``; function getSlotTemplateHTML(_attrs: Record) { return /*html*/ ` ${ccIconOn} ${ccIconOff} `; } function getTooltipContentHTML() { return /*html*/ ` ${t('Enable captions')} ${t('Disable captions')} `; } const updateAriaChecked = (el: HTMLElement) => { el.setAttribute('aria-checked', areSubsOn(el).toString()); }; /** * @slot on - An element that will be shown while closed captions or subtitles are on. * @slot off - An element that will be shown while closed captions or subtitles are off. * @slot icon - An element for representing on and off states in a single icon * * @attr {string} mediasubtitleslist - (read-only) A list of all subtitles and captions. * @attr {string} mediasubtitlesshowing - (read-only) A list of the showing subtitles and captions. * * @cssproperty [--media-captions-button-display = inline-flex] - `display` property of button. */ class MediaCaptionsButton extends MediaChromeButton { static getSlotTemplateHTML = getSlotTemplateHTML; static getTooltipContentHTML = getTooltipContentHTML; static get observedAttributes() { return [ ...super.observedAttributes, MediaUIAttributes.MEDIA_SUBTITLES_LIST, MediaUIAttributes.MEDIA_SUBTITLES_SHOWING, ]; } connectedCallback(): void { super.connectedCallback(); this.setAttribute('role', 'button'); this.setAttribute('aria-label', t('closed captions')); updateAriaChecked(this); } attributeChangedCallback( attrName: string, oldValue: string, newValue: string ) { super.attributeChangedCallback(attrName, oldValue, newValue); if (attrName === MediaUIAttributes.MEDIA_SUBTITLES_SHOWING) { updateAriaChecked(this); } } /** * An array of TextTrack-like objects. * Objects must have the properties: kind, language, and label. */ get mediaSubtitlesList(): TextTrackLike[] { return getSubtitlesListAttr(this, MediaUIAttributes.MEDIA_SUBTITLES_LIST); } set mediaSubtitlesList(list: TextTrackLike[]) { setSubtitlesListAttr(this, MediaUIAttributes.MEDIA_SUBTITLES_LIST, list); } /** * An array of TextTrack-like objects. * Objects must have the properties: kind, language, and label. */ get mediaSubtitlesShowing(): TextTrackLike[] { return getSubtitlesListAttr( this, MediaUIAttributes.MEDIA_SUBTITLES_SHOWING ); } set mediaSubtitlesShowing(list: TextTrackLike[]) { setSubtitlesListAttr(this, MediaUIAttributes.MEDIA_SUBTITLES_SHOWING, list); } handleClick() { this.dispatchEvent( new globalThis.CustomEvent(MediaUIEvents.MEDIA_TOGGLE_SUBTITLES_REQUEST, { composed: true, bubbles: true, }) ); } } /** * @param el - Should be HTMLElement but issues with globalThis shim * @param attrName - The attribute name to get * @returns An array of TextTrack-like objects. */ const getSubtitlesListAttr = ( el: HTMLElement, attrName: string ): TextTrackLike[] => { const attrVal = el.getAttribute(attrName); return attrVal ? parseTextTracksStr(attrVal) : []; }; /** * * @param el - Should be HTMLElement but issues with globalThis shim * @param attrName - The attribute name to set * @param list - An array of TextTrack-like objects */ const setSubtitlesListAttr = ( el: HTMLElement, attrName: string, list: TextTrackLike[] ) => { // null, undefined, and empty arrays are treated as "no value" here if (!list?.length) { el.removeAttribute(attrName); return; } // don't set if the new value is the same as existing const newValStr = stringifyTextTrackList(list); const oldVal = el.getAttribute(attrName); if (oldVal === newValStr) return; el.setAttribute(attrName, newValStr); }; if (!globalThis.customElements.get('media-captions-button')) { globalThis.customElements.define( 'media-captions-button', MediaCaptionsButton ); } export { MediaCaptionsButton }; export default MediaCaptionsButton;