media-chrome
Version:
Custom elements (web components) for making audio and video player controls that look great in your website or app.
140 lines (125 loc) • 4.64 kB
text/typescript
import { MediaStateReceiverAttributes } from './constants.js';
import MediaController from './media-controller.js';
import { getOrInsertCSSRule } from './utils/element-utils.js';
import { globalThis, document } from './utils/server-safe-globals.js';
// Todo: Use data locals: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toLocaleTimeString
const template: HTMLTemplateElement = document.createElement('template');
template.innerHTML = /*html*/ `
<style>
:host {
font: var(--media-font,
var(--media-font-weight, normal)
var(--media-font-size, 14px) /
var(--media-text-content-height, var(--media-control-height, 24px))
var(--media-font-family, helvetica neue, segoe ui, roboto, arial, sans-serif));
color: var(--media-text-color, var(--media-primary-color, rgb(238 238 238)));
background: var(--media-text-background, var(--media-control-background, var(--media-secondary-color, rgb(20 20 30 / .7))));
padding: var(--media-control-padding, 10px);
display: inline-flex;
justify-content: center;
align-items: center;
vertical-align: middle;
box-sizing: border-box;
text-align: center;
pointer-events: auto;
}
${
/*
Only show outline when keyboard focusing.
https://drafts.csswg.org/selectors-4/#the-focus-visible-pseudo
*/ ''
}
:host(:focus-visible) {
box-shadow: inset 0 0 0 2px rgb(27 127 204 / .9);
outline: 0;
}
${
/*
* hide default focus ring, particularly when using mouse
*/ ''
}
:host(:where(:focus)) {
box-shadow: none;
outline: 0;
}
</style>
<slot></slot>
`;
/**
* @slot - Default slotted elements.
*
* @attr {string} mediacontroller - The element `id` of the media controller to connect to (if not nested within).
*
* @cssproperty --media-primary-color - Default color of text.
* @cssproperty --media-secondary-color - Default color of background.
* @cssproperty --media-text-color - `color` of text.
*
* @cssproperty --media-control-display - `display` property of control.
* @cssproperty --media-control-background - `background` of control.
* @cssproperty --media-control-padding - `padding` of control.
* @cssproperty --media-control-height - `line-height` of control.
*
* @cssproperty --media-font - `font` shorthand property.
* @cssproperty --media-font-weight - `font-weight` property.
* @cssproperty --media-font-family - `font-family` property.
* @cssproperty --media-font-size - `font-size` property.
* @cssproperty --media-text-content-height - `line-height` of text.
*/
class MediaTextDisplay extends globalThis.HTMLElement {
#mediaController: MediaController | null;
static get observedAttributes(): string[] {
return [MediaStateReceiverAttributes.MEDIA_CONTROLLER];
}
constructor() {
super();
if (!this.shadowRoot) {
// Set up the Shadow DOM if not using Declarative Shadow DOM.
this.attachShadow({ mode: 'open' });
this.shadowRoot.appendChild(template.content.cloneNode(true));
}
}
attributeChangedCallback(
attrName: string,
oldValue: string | null,
newValue: string | null
): void {
if (attrName === MediaStateReceiverAttributes.MEDIA_CONTROLLER) {
if (oldValue) {
this.#mediaController?.unassociateElement?.(this);
this.#mediaController = null;
}
if (newValue && this.isConnected) {
// @ts-ignore
this.#mediaController = this.getRootNode()?.getElementById(newValue);
this.#mediaController?.associateElement?.(this);
}
}
}
connectedCallback(): void {
const { style } = getOrInsertCSSRule(this.shadowRoot, ':host');
style.setProperty(
'display',
`var(--media-control-display, var(--${this.localName}-display, inline-flex))`
);
const mediaControllerId = this.getAttribute(
MediaStateReceiverAttributes.MEDIA_CONTROLLER
);
if (mediaControllerId) {
// @ts-ignore
this.#mediaController = (this.getRootNode() as Document)?.getElementById(
mediaControllerId
);
this.#mediaController?.associateElement?.(this);
}
}
disconnectedCallback(): void {
// Use cached mediaController, getRootNode() doesn't work if disconnected.
this.#mediaController?.unassociateElement?.(this);
this.#mediaController = null;
}
}
if (!globalThis.customElements.get('media-text-display')) {
globalThis.customElements.define('media-text-display', MediaTextDisplay);
}
export { MediaTextDisplay };
export default MediaTextDisplay;