fix(html): fix html container sizing (#881)

This commit is contained in:
Sam Potts
2026-03-11 14:27:40 +11:00
committed by GitHub
parent 6d044e3c0f
commit abf8753fe6
15 changed files with 65 additions and 70 deletions
+25
View File
@@ -0,0 +1,25 @@
video-player {
display: contents;
}
video-player video,
video-player hls-video,
video-player simple-hls-video {
display: block;
width: 100%;
height: 100%;
}
video-player video::-webkit-media-text-track-container {
transition: transform var(--media-caption-track-duration, 150ms) ease-out;
transition-delay: var(--media-caption-track-delay, 600ms);
transform: translateY(var(--media-caption-track-y, 0)) scale(0.98);
z-index: var(--media-caption-track-z, 1);
font-family: inherit;
}
@media (prefers-reduced-motion: reduce) {
video-player video::-webkit-media-text-track-container {
transition-duration: 50ms;
}
}
+9 -33
View File
@@ -1,37 +1,14 @@
import type { ReactiveElement } from '@videojs/element';
import type { Constructor } from '@videojs/utils/types';
import styles from './base.css?inline';
/**
* Light DOM stylesheet that bridges CSS custom properties set by skins
* to native WebKit text track pseudo-elements on the slotted `<video>`.
*
* `::slotted(video)::-webkit-media-text-track-container` is invalid per
* CSS spec, so this bridge lives in light DOM where it can directly
* target the video element's pseudo-elements.
*/
const NATIVE_CAPTION_BRIDGE_CSS = /* css */ `
.media-skin > video::-webkit-media-text-track-container {
transition: transform var(--media-caption-track-duration, 150ms) ease-out;
transition-delay: var(--media-caption-track-delay, 600ms);
transform: translateY(var(--media-caption-track-y, 0)) scale(0.98);
z-index: var(--media-caption-track-z, 1);
font-family: inherit;
}
const STYLES_ID = '__media-styles';
@media (prefers-reduced-motion: reduce) {
.media-skin > video::-webkit-media-text-track-container {
transition-duration: 50ms;
}
}
`;
const BRIDGE_ID = 'media-caption-bridge';
function ensureCaptionBridge(): void {
if (document.getElementById(BRIDGE_ID)) return;
function ensureStyles(): void {
if (document.getElementById(STYLES_ID)) return;
const style = document.createElement('style');
style.id = BRIDGE_ID;
style.textContent = NATIVE_CAPTION_BRIDGE_CSS;
style.id = STYLES_ID;
style.textContent = styles;
document.head.appendChild(style);
}
@@ -40,8 +17,8 @@ function ensureCaptionBridge(): void {
* `getTemplateHTML` method into a shadow root. Native `<slot>` elements
* handle light DOM projection automatically.
*
* When `static styles` is set, the stylesheet is adopted into the shadow
* root via `adoptedStyleSheets`.
* When `static styles` is set, the stylesheet is adopted into the
* shadow root via `adoptedStyleSheets`.
*/
export function SkinMixin<Base extends Constructor<ReactiveElement>>(
BaseClass: Base
@@ -53,8 +30,7 @@ export function SkinMixin<Base extends Constructor<ReactiveElement>>(
constructor(...args: any[]) {
super(...args);
this.classList.add('media-skin');
ensureCaptionBridge();
ensureStyles();
if (!this.shadowRoot) {
const ctor = this.constructor as typeof SkinElement & { getTemplateHTML?: () => string };
+1 -1
View File
@@ -106,7 +106,7 @@ function getTemplateHTML() {
<span class="media-tooltip-label media-tooltip-label--disable-captions">Disable captions</span>
</media-tooltip>
<media-pip-button commandfor="pip-tooltip" class="media-button media-button--icon">
<media-pip-button commandfor="pip-tooltip" class="media-button media-button--icon media-button--pip">
${renderIcon('pip', { class: 'media-icon' })}
</media-pip-button>
<media-tooltip id="pip-tooltip" side="top" class="media-surface media-tooltip">
@@ -1,4 +1,4 @@
import type { Skin } from '@app/types';
import type { Skin } from '../../types';
const videoStylesheets: Record<Skin, string> = {
default: new URL('@videojs/html/video/skin.css', import.meta.url).href,
+1
View File
@@ -34,6 +34,7 @@
"prompts": "^2.4.2",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"rimraf": "^6.1.3",
"tailwindcss": "^4.2.0",
"tsx": "^4.0.0",
"typescript": "^5.0.0",
@@ -21,7 +21,7 @@ function render() {
loadVideoStylesheets(currentSkin);
document.getElementById('root')!.innerHTML = html`
<video-player class="contents">
<video-player>
<${tag} class="w-full aspect-video max-w-4xl mx-auto">
<hls-video slot="media" src="${SOURCES[currentSource].url}"></hls-video>
</${tag}>
@@ -21,7 +21,7 @@ function render() {
loadVideoStylesheets(currentSkin);
document.getElementById('root')!.innerHTML = html`
<video-player class="contents">
<video-player>
<${tag} class="w-full aspect-video max-w-4xl mx-auto">
<simple-hls-video slot="media" src="${SOURCES[currentSource].url}"></simple-hls-video>
</${tag}>
@@ -20,7 +20,7 @@ function render() {
const tag = TAILWIND_SKIN_TAGS[currentSkin].video;
document.getElementById('root')!.innerHTML = html`
<video-player class="contents">
<video-player>
<${tag} class="w-full aspect-video max-w-4xl mx-auto">
<video slot="media" src="${SOURCES[currentSource].url}"></video>
</${tag}>
@@ -20,7 +20,7 @@ function render() {
loadVideoStylesheets(currentSkin);
document.getElementById('root')!.innerHTML = html`
<video-player class="contents">
<video-player>
<${tag} class="w-full aspect-video max-w-4xl mx-auto">
<video slot="media" src="${SOURCES[currentSource].url}"></video>
</${tag}>
@@ -28,10 +28,6 @@ document.getElementById('root')!.innerHTML = html`
overflow: hidden;
}
video-player {
display: contents;
}
simple-hls-video {
width: 640px;
aspect-ratio: 16 / 9;
@@ -6,6 +6,8 @@
position: relative;
isolation: isolate;
display: block;
height: 100%;
width: 100%;
container: media-root / inline-size;
border-radius: var(--media-border-radius, 2rem);
font-family:
@@ -2,7 +2,7 @@ import { cn } from '@videojs/utils/style';
export const root = cn(
// Layout & containment
'block relative isolate @container/media-root',
'block relative isolate h-full w-full @container/media-root',
// Appearance
'rounded-(--media-border-radius,2rem)',
'font-[Inter_Variable,Inter,ui-sans-serif,system-ui,sans-serif] text-[0.8125rem] leading-normal subpixel-antialiased',
@@ -1,26 +0,0 @@
/* ==========================================================================
Native Caption Bridge
---------------------------------------------------------------------------
Receives CSS custom properties set by any skin and applies them to the
native WebKit text track container on the slotted <video> element.
This stylesheet MUST live in the light DOM because ::slotted() cannot be
chained with vendor pseudo-elements like ::-webkit-media-text-track-container.
CSS custom properties cascade through the shadow DOM boundary, so skins
define the values and this bridge reads them.
========================================================================== */
.media-skin > video::-webkit-media-text-track-container {
transition: transform var(--media-caption-track-duration, 150ms) ease-out;
transition-delay: var(--media-caption-track-delay, 600ms);
transform: translateY(var(--media-caption-track-y, 0)) scale(0.98);
z-index: var(--media-caption-track-z, 1);
font-family: inherit;
}
@media (prefers-reduced-motion: reduce) {
.media-skin > video::-webkit-media-text-track-container {
transition-duration: 50ms;
}
}
+13
View File
@@ -299,6 +299,9 @@ importers:
react-dom:
specifier: ^19.0.0
version: 19.2.4(react@19.2.4)
rimraf:
specifier: ^6.1.3
version: 6.1.3
tailwindcss:
specifier: ^4.2.0
version: 4.2.1
@@ -6549,6 +6552,11 @@ packages:
rfdc@1.4.1:
resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==}
rimraf@6.1.3:
resolution: {integrity: sha512-LKg+Cr2ZF61fkcaK1UdkH2yEBBKnYjTyWzTJT6KNPcSPaiT7HSdhtMXQuN5wkTX0Xu72KQ1l8S42rlmexS2hSA==}
engines: {node: 20 || >=22}
hasBin: true
rolldown-plugin-dts@0.17.8:
resolution: {integrity: sha512-76EEBlhF00yeY6M7VpMkWKI4r9WjuoMiOGey7j4D6zf3m0BR+ZrrY9hvSXdueJ3ljxSLq4DJBKFpX/X9+L7EKw==}
engines: {node: '>=20.19.0'}
@@ -14516,6 +14524,11 @@ snapshots:
rfdc@1.4.1: {}
rimraf@6.1.3:
dependencies:
glob: 13.0.6
package-json-from-dist: 1.0.1
rolldown-plugin-dts@0.17.8(rolldown@1.0.0-beta.45)(typescript@5.9.3):
dependencies:
'@babel/generator': 7.29.1
@@ -13,6 +13,14 @@ function getCdnFileName(useCase: UseCase, skin: Skin): string {
}
function generateCdnCode(useCase: UseCase, skin: Skin): string {
if (useCase === 'background-video') {
return `<script type="module">
import '${CDN_BASE}/background/player.js';
import '${CDN_BASE}/background/skin.js';
</script>
<link rel="stylesheet" href="${CDN_BASE}/background/skin.css" />`;
}
const name = getCdnFileName(useCase, skin);
return `<script type="module" src="${CDN_BASE}/${name}.js"></script>