import { globalThis } from './utils/server-safe-globals.js'; import { MediaChromeRange } from './media-chrome-range.js'; import { MediaUIAttributes, MediaUIEvents } from './constants.js'; import { t } from './utils/i18n.js'; import { getBooleanAttr, getNumericAttr, getStringAttr, setBooleanAttr, setNumericAttr, setStringAttr, } from './utils/element-utils.js'; const DEFAULT_VOLUME = 1; const toVolume = (el: any): number => { if (el.mediaMuted) return 0; return el.mediaVolume; }; const formatAsPercentString = (value: number): string => `${Math.round(value * 100)}%`; /** * @attr {string} mediavolume - (read-only) Set to the media volume. * @attr {boolean} mediamuted - (read-only) Set to the media muted state. * @attr {string} mediavolumeunavailable - (read-only) Set if changing volume is unavailable. * * @cssproperty [--media-volume-range-display = inline-block] - `display` property of range. */ class MediaVolumeRange extends MediaChromeRange { static get observedAttributes(): string[] { return [ ...super.observedAttributes, MediaUIAttributes.MEDIA_VOLUME, MediaUIAttributes.MEDIA_MUTED, MediaUIAttributes.MEDIA_VOLUME_UNAVAILABLE, ]; } #handleRangeInput: () => void = () => { const detail = this.range.value; const evt = new globalThis.CustomEvent( MediaUIEvents.MEDIA_VOLUME_REQUEST, { composed: true, bubbles: true, detail, } ); this.dispatchEvent(evt); } connectedCallback(): void { super.connectedCallback(); this.range.setAttribute('aria-label', t('volume')); this.range.addEventListener('input', this.#handleRangeInput); } disconnectedCallback(): void { this.range.removeEventListener('input', this.#handleRangeInput); super.disconnectedCallback(); } attributeChangedCallback( attrName: string, oldValue: string | null, newValue: string | null ): void { super.attributeChangedCallback(attrName, oldValue, newValue); if ( attrName === MediaUIAttributes.MEDIA_VOLUME || attrName === MediaUIAttributes.MEDIA_MUTED ) { this.range.valueAsNumber = toVolume(this); this.range.setAttribute( 'aria-valuetext', formatAsPercentString(this.range.valueAsNumber) ); this.updateBar(); } } /** * */ get mediaVolume(): number { return getNumericAttr(this, MediaUIAttributes.MEDIA_VOLUME, DEFAULT_VOLUME); } set mediaVolume(value: number) { setNumericAttr(this, MediaUIAttributes.MEDIA_VOLUME, value); } /** * Is the media currently muted */ get mediaMuted(): boolean { return getBooleanAttr(this, MediaUIAttributes.MEDIA_MUTED); } set mediaMuted(value: boolean) { setBooleanAttr(this, MediaUIAttributes.MEDIA_MUTED, value); } /** * The volume unavailability state */ get mediaVolumeUnavailable(): string | undefined { return getStringAttr(this, MediaUIAttributes.MEDIA_VOLUME_UNAVAILABLE); } set mediaVolumeUnavailable(value: string | undefined) { setStringAttr(this, MediaUIAttributes.MEDIA_VOLUME_UNAVAILABLE, value); } } if (!globalThis.customElements.get('media-volume-range')) { globalThis.customElements.define('media-volume-range', MediaVolumeRange); } export default MediaVolumeRange;