From 465342e4314e854332f5bdfee5eb6b4c113f1d87 Mon Sep 17 00:00:00 2001 From: Christian Pillsbury Date: Fri, 12 Sep 2025 06:34:26 -0700 Subject: [PATCH] feat(html): integrate fullscreen button into control bar and improve container lifecycle MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Complete fullscreen button integration: - Add fullscreen button to media-skin-default control bar as rightmost control - Icon styling follows play/pause pattern with proper state-based visibility - Container state owner lifecycle management in media-container component - Component factory passes mediaStore reference to support advanced integrations Control bar layout: Play → Time → Mute → Volume → Fullscreen Icons change appropriately based on [data-fullscreen] attribute state. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- packages/html/html/src/media-container.ts | 21 +++++++++++++++++++ .../html/html/src/skins/media-skin-default.ts | 11 ++++++++++ .../html/html/src/utils/component-factory.ts | 2 +- 3 files changed, 33 insertions(+), 1 deletion(-) diff --git a/packages/html/html/src/media-container.ts b/packages/html/html/src/media-container.ts index 4b1ca416..90aed0b4 100644 --- a/packages/html/html/src/media-container.ts +++ b/packages/html/html/src/media-container.ts @@ -19,6 +19,7 @@ export class MediaContainer extends ConsumerMixin(HTMLElement) { mediaStore: (mediaStore: any) => { this._mediaStore = mediaStore; this._handleMediaSlotChange(); + this._registerContainerStateOwner(); }, }; @@ -38,6 +39,26 @@ export class MediaContainer extends ConsumerMixin(HTMLElement) { this._mediaSlot.addEventListener('slotchange', this._handleMediaSlotChange); } + connectedCallback() { + super.connectedCallback?.(); + this._registerContainerStateOwner(); + } + + disconnectedCallback() { + super.disconnectedCallback?.(); + this._unregisterContainerStateOwner(); + } + + _registerContainerStateOwner = () => { + if (!this._mediaStore) return; + this._mediaStore.dispatch({ type: 'containerstateownerchangerequest', detail: this }); + }; + + _unregisterContainerStateOwner = () => { + if (!this._mediaStore) return; + this._mediaStore.dispatch({ type: 'containerstateownerchangerequest', detail: null }); + }; + _handleMediaSlotChange = () => { const media = this._mediaSlot.assignedElements({ flatten: true })[0]; this._mediaStore.dispatch({ type: 'mediastateownerchangerequest', detail: media }); diff --git a/packages/html/html/src/skins/media-skin-default.ts b/packages/html/html/src/skins/media-skin-default.ts index e58a32d5..71e8fcd6 100644 --- a/packages/html/html/src/skins/media-skin-default.ts +++ b/packages/html/html/src/skins/media-skin-default.ts @@ -5,6 +5,7 @@ import '../components/media-play-button'; import '../components/media-mute-button'; import '../components/media-volume-range'; import '../components/media-time-range'; +import '../components/media-fullscreen-button'; import '@vjs-10/html-icons'; export function getTemplateHTML() { @@ -60,6 +61,12 @@ export function getTemplateHTML() { display: inline-block; } + /* Media Fullscreen Button UI/Styles */ + media-fullscreen-button:not([data-fullscreen]) .fullscreen-enter-icon, + media-fullscreen-button[data-fullscreen] .fullscreen-exit-icon { + display: inline-block; + } + /* One way to define the "default visible" icon (CJP) */ media-mute-button:not([data-volume-level]) .volume-low-icon, media-mute-button[data-volume-level=high] .volume-high-icon, @@ -98,6 +105,10 @@ export function getTemplateHTML() { + + + + diff --git a/packages/html/html/src/utils/component-factory.ts b/packages/html/html/src/utils/component-factory.ts index 774c14de..5e9230bc 100644 --- a/packages/html/html/src/utils/component-factory.ts +++ b/packages/html/html/src/utils/component-factory.ts @@ -52,7 +52,7 @@ export const toConnectedHTMLComponent = ( // @ts-ignore - Element property access const props = propsHook(state ?? {}, this); // @ts-ignore - this._update(props, state); + this._update(props, state, mediaStore); }); }, };