mirror of
https://github.com/zoriya/v10.git
synced 2026-08-05 05:37:21 +00:00
feat(html): add fullscreen button component and icons
Implement HTML fullscreen button following established patterns: - MediaFullscreenButton web component with hook-style architecture - HTML icon components: MediaFullscreenEnterIcon, MediaFullscreenExitIcon - State management via useFullscreenButtonState hook - Props handling via useFullscreenButtonProps hook - Explicit enter/exit fullscreen methods (no toggle) - Proper accessibility with aria-label and data-tooltip Components integrate seamlessly with existing HTML component architecture and provide foundation for control bar integration. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
committed by
Christian Pillsbury
co-authored by
Claude
parent
e19b98b120
commit
63a340213f
@@ -2,4 +2,6 @@ export * as MediaPlayIcon from './media-play-icon.js';
|
||||
export * as MediaPauseIcon from './media-pause-icon.js';
|
||||
export * as MediaVolumeHighIcon from './media-volume-high-icon.js';
|
||||
export * as MediaVolumeLowIcon from './media-volume-low-icon.js';
|
||||
export * as MediaVolumeOffIcon from './media-volume-off-icon.js';
|
||||
export * as MediaVolumeOffIcon from './media-volume-off-icon.js';
|
||||
export * as MediaFullscreenEnterIcon from './media-fullscreen-enter-icon.js';
|
||||
export * as MediaFullscreenExitIcon from './media-fullscreen-exit-icon.js';
|
||||
@@ -0,0 +1,20 @@
|
||||
import { MediaChromeIcon } from './media-chrome-icon.js';
|
||||
import { SVG_ICONS } from '@vjs-10/icons';
|
||||
|
||||
export function getTemplateHTML() {
|
||||
return /* html */ `
|
||||
${MediaChromeIcon.getTemplateHTML()}
|
||||
<style>
|
||||
:host {
|
||||
display: var(--media-fullscreen-enter-icon-display, inline-flex);
|
||||
}
|
||||
</style>
|
||||
${SVG_ICONS.fullscreenEnter}
|
||||
`;
|
||||
}
|
||||
|
||||
export class MediaFullscreenEnterIcon extends MediaChromeIcon {
|
||||
static getTemplateHTML = getTemplateHTML;
|
||||
}
|
||||
|
||||
customElements.define('media-fullscreen-enter-icon', MediaFullscreenEnterIcon);
|
||||
@@ -0,0 +1,20 @@
|
||||
import { MediaChromeIcon } from './media-chrome-icon.js';
|
||||
import { SVG_ICONS } from '@vjs-10/icons';
|
||||
|
||||
export function getTemplateHTML() {
|
||||
return /* html */ `
|
||||
${MediaChromeIcon.getTemplateHTML()}
|
||||
<style>
|
||||
:host {
|
||||
display: var(--media-fullscreen-exit-icon-display, inline-flex);
|
||||
}
|
||||
</style>
|
||||
${SVG_ICONS.fullscreenExit}
|
||||
`;
|
||||
}
|
||||
|
||||
export class MediaFullscreenExitIcon extends MediaChromeIcon {
|
||||
static getTemplateHTML = getTemplateHTML;
|
||||
}
|
||||
|
||||
customElements.define('media-fullscreen-exit-icon', MediaFullscreenExitIcon);
|
||||
@@ -0,0 +1,101 @@
|
||||
import {
|
||||
toConnectedHTMLComponent,
|
||||
StateHook,
|
||||
PropsHook,
|
||||
} from '../utils/component-factory';
|
||||
import { MediaChromeButton } from './media-chrome-button';
|
||||
import { fullscreenButtonStateDefinition } from '@vjs-10/media-store';
|
||||
|
||||
export class FullscreenButtonBase extends MediaChromeButton {
|
||||
_state:
|
||||
| {
|
||||
fullscreen: boolean;
|
||||
requestEnterFullscreen: () => void;
|
||||
requestExitFullscreen: () => void;
|
||||
}
|
||||
| undefined;
|
||||
|
||||
handleEvent(event: Event) {
|
||||
const { type } = event;
|
||||
const state = this._state;
|
||||
if (state && type === 'click') {
|
||||
if (state.fullscreen) {
|
||||
state.requestExitFullscreen();
|
||||
} else {
|
||||
state.requestEnterFullscreen();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
get fullscreen() {
|
||||
return this._state?.fullscreen;
|
||||
}
|
||||
|
||||
_update(props: any, state: any, _mediaStore?: any) {
|
||||
this._state = state;
|
||||
/** @TODO Follow up with React vs. W.C. data-* attributes discrepancies (CJP) */
|
||||
// Make generic
|
||||
this.toggleAttribute('data-fullscreen', props['data-fullscreen']);
|
||||
this.setAttribute('role', props['role']);
|
||||
this.setAttribute('aria-label', props['aria-label']);
|
||||
this.setAttribute('data-tooltip', props['data-tooltip']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* FullscreenButton state hook - equivalent to React's useFullscreenButtonState
|
||||
* Handles media store state subscription and transformation
|
||||
*/
|
||||
export const useFullscreenButtonState: StateHook<{ fullscreen: boolean }> = {
|
||||
keys: fullscreenButtonStateDefinition.keys,
|
||||
transform: (rawState, mediaStore) => ({
|
||||
...fullscreenButtonStateDefinition.stateTransform(rawState),
|
||||
...fullscreenButtonStateDefinition.createRequestMethods(
|
||||
mediaStore.dispatch,
|
||||
),
|
||||
}),
|
||||
};
|
||||
|
||||
/**
|
||||
* FullscreenButton props hook - equivalent to React's useFullscreenButtonProps
|
||||
* Handles element attributes and properties based on state
|
||||
*/
|
||||
export const useFullscreenButtonProps: PropsHook<{ fullscreen: boolean }> = (
|
||||
state,
|
||||
_element,
|
||||
) => {
|
||||
const baseProps: Record<string, any> = {
|
||||
/** data attributes/props */
|
||||
['data-fullscreen']: state.fullscreen,
|
||||
/** @TODO Need another state provider in core for i18n (CJP) */
|
||||
/** aria attributes/props */
|
||||
role: 'button',
|
||||
['aria-label']: state.fullscreen ? 'exit fullscreen' : 'enter fullscreen',
|
||||
/** tooltip */
|
||||
['data-tooltip']: state.fullscreen ? 'Exit Fullscreen' : 'Enter Fullscreen',
|
||||
/** @TODO Figure out how we want to handle attr overrides (e.g. aria-label) (CJP) */
|
||||
/** external props spread last to allow for overriding */
|
||||
// ...props,
|
||||
};
|
||||
|
||||
return baseProps;
|
||||
};
|
||||
|
||||
/**
|
||||
* Connected FullscreenButton component using hook-style architecture
|
||||
* Equivalent to React's FullscreenButton = toConnectedComponent(...)
|
||||
*/
|
||||
export const FullscreenButton = toConnectedHTMLComponent(
|
||||
FullscreenButtonBase,
|
||||
useFullscreenButtonState,
|
||||
useFullscreenButtonProps,
|
||||
'FullscreenButton',
|
||||
);
|
||||
|
||||
// NOTE: In this architecture it will be important to decouple component class definitions from their registration in the CustomElementsRegistry. (CJP)
|
||||
if (!globalThis.customElements.get('media-fullscreen-button')) {
|
||||
// @ts-ignore - Custom element constructor compatibility
|
||||
globalThis.customElements.define('media-fullscreen-button', FullscreenButton);
|
||||
}
|
||||
|
||||
export default FullscreenButton;
|
||||
@@ -5,6 +5,7 @@ export * as MediaThemeDefault from './skins/media-skin-default.js';
|
||||
export { PlayButton } from './components/media-play-button.js';
|
||||
export { MuteButton } from './components/media-mute-button.js';
|
||||
export { VolumeRange } from './components/media-volume-range.js';
|
||||
export { FullscreenButton } from './components/media-fullscreen-button.js';
|
||||
|
||||
export function defineVjsPlayer() {
|
||||
/** @TODO - Reimplement me (at least as a POC) (CJP) */
|
||||
|
||||
Reference in New Issue
Block a user