diff --git a/packages/vidstack/src/components/layouts/default/props.ts b/packages/vidstack/src/components/layouts/default/props.ts index 1985a3232..3ab4527ff 100644 --- a/packages/vidstack/src/components/layouts/default/props.ts +++ b/packages/vidstack/src/components/layouts/default/props.ts @@ -10,9 +10,17 @@ export const defaultLayoutProps: DefaultLayoutProps = { disableTimeSlider: false, menuContainer: null, menuGroup: 'bottom', + flatSettingsMenu: false, noAudioGain: false, + noAudioTracks: false, + noMediaLoop: false, + noMediaSpeed: false, + noMediaQuality: false, noGestures: false, + noAnnouncements: false, noKeyboardAnimations: false, + noCaptionStyles: false, + noCaptions: false, noModal: false, noScrubGesture: false, playbackRates: { min: 0, max: 2, step: 0.25 }, @@ -71,10 +79,30 @@ export interface DefaultLayoutProps { * only applies to the large video layout. */ menuGroup: 'top' | 'bottom'; + /** + * Do not split settings menu into submenus + */ + flatSettingsMenu: boolean; /** * Disable audio boost slider in the settings menu. */ noAudioGain: boolean; + /** + * Disable audio tracks in the settings menu. + */ + noAudioTracks: boolean; + /** + * Disable loop switch in the settings menu. + */ + noMediaLoop: boolean; + /** + * Disable media speed slider in the settings menu. + */ + noMediaSpeed: boolean; + /** + * Disable media quality slider in the settings menu. + */ + noMediaQuality: boolean; /** * Whether modal menus should be disabled when the small layout is active. A modal menu is * a floating panel that floats up from the bottom of the screen (outside of the player). It's @@ -97,10 +125,22 @@ export interface DefaultLayoutProps { * Whether all gestures such as press to play or seek should not be active. */ noGestures: boolean; + /** + * Whether announcements should not be displayed. + */ + noAnnouncements: boolean; /** * Whether keyboard actions should not be displayed. */ noKeyboardAnimations: boolean; + /** + * Whether caption styles should not be displayed. + */ + noCaptionStyles: boolean; + /** + * Whether captions should not be displayed. + */ + noCaptions: boolean; /** * Whether the bitrate should be hidden in the settings quality hint. * diff --git a/packages/vidstack/src/components/ui/menu/menu.ts b/packages/vidstack/src/components/ui/menu/menu.ts index b1afecb15..90df5abb1 100644 --- a/packages/vidstack/src/components/ui/menu/menu.ts +++ b/packages/vidstack/src/components/ui/menu/menu.ts @@ -519,6 +519,8 @@ export class Menu extends Component { #onResize = animationFrameThrottle(() => { const content = peek(this.#content); if (!content || __SERVER__) return; + // Disable resize for flatSettingsMenu because it works wrong with DefaultFontMenu() + if (content.getAttribute('flat') == 'true') return; let height = 0, styles = getComputedStyle(content), diff --git a/packages/vidstack/src/elements/define/layouts/default/ui/menu/accessibility-menu.ts b/packages/vidstack/src/elements/define/layouts/default/ui/menu/accessibility-menu.ts index 9c1fa3bb6..335d99b81 100644 --- a/packages/vidstack/src/elements/define/layouts/default/ui/menu/accessibility-menu.ts +++ b/packages/vidstack/src/elements/define/layouts/default/ui/menu/accessibility-menu.ts @@ -12,26 +12,72 @@ import { DefaultMenuButton, DefaultMenuItem, DefaultMenuSection } from './items/ export function DefaultAccessibilityMenu() { return $signal(() => { - const { translations } = useDefaultLayoutContext(); + const { + flatSettingsMenu, + noAnnouncements, + noKeyboardAnimations, + noCaptionStyles, + translations, + } = useDefaultLayoutContext(); + + if (flatSettingsMenu()) { + const items: any[] = []; + if (!noAnnouncements()) { + items.push(DefaultAnnouncementsMenuCheckbox()); + } + if (!noKeyboardAnimations()) { + items.push(DefaultKeyboardAnimationsMenuCheckbox()); + } + if (!noCaptionStyles()) { + items.push(DefaultFontMenu()); + } + + return items.length + ? DefaultMenuSection({ + label: i18n(translations, 'Accessibility'), + children: items, + }) + : null; + } + + const items: any[] = []; + const topItems: any[] = []; + const bottomItems: any[] = []; + + if (!noAnnouncements()) { + topItems.push(DefaultAnnouncementsMenuCheckbox()); + } + if (!noKeyboardAnimations()) { + topItems.push(DefaultKeyboardAnimationsMenuCheckbox()); + } + if (!noCaptionStyles()) { + bottomItems.push(DefaultFontMenu()); + } + + if (topItems.length) { + items.push( + DefaultMenuSection({ + children: topItems, + }), + ); + } + if (bottomItems.length) { + items.push( + DefaultMenuSection({ + children: bottomItems, + }), + ); + } + + if (!items.length) return null; + return html` ${DefaultMenuButton({ label: () => i18n(translations, 'Accessibility'), icon: 'menu-accessibility', })} - - ${[ - DefaultMenuSection({ - children: [ - DefaultAnnouncementsMenuCheckbox(), - DefaultKeyboardAnimationsMenuCheckbox(), - ], - }), - DefaultMenuSection({ - children: [DefaultFontMenu()], - }), - ]} - + ${items} `; }); diff --git a/packages/vidstack/src/elements/define/layouts/default/ui/menu/audio-menu.ts b/packages/vidstack/src/elements/define/layouts/default/ui/menu/audio-menu.ts index 1ed84b8ca..43e8bca7a 100644 --- a/packages/vidstack/src/elements/define/layouts/default/ui/menu/audio-menu.ts +++ b/packages/vidstack/src/elements/define/layouts/default/ui/menu/audio-menu.ts @@ -12,7 +12,8 @@ import { DefaultMenuSliderItem, DefaultSliderParts, DefaultSliderSteps } from '. export function DefaultAudioMenu() { return $signal(() => { - const { noAudioGain, translations } = useDefaultLayoutContext(), + const { flatSettingsMenu, noAudioGain, noAudioTracks, translations } = + useDefaultLayoutContext(), { audioTracks, canSetAudioGain } = useMediaState(), $disabled = computed(() => { const hasGainSlider = canSetAudioGain() && !noAudioGain(); @@ -21,15 +22,33 @@ export function DefaultAudioMenu() { if ($disabled()) return null; + const items: any[] = []; + if (!noAudioGain()) { + items.push(DefaultAudioBoostSection()); + } + if (!noAudioTracks()) { + items.push(DefaultAudioTracksMenu()); + } + + if (!items.length) { + return null; + } + + if (flatSettingsMenu()) + return [ + DefaultMenuSection({ + label: i18n(translations, 'Audio'), + children: items, + }), + ]; + return html` ${DefaultMenuButton({ label: () => i18n(translations, 'Audio'), icon: 'menu-audio', })} - - ${[DefaultAudioTracksMenu(), DefaultAudioBoostSection()]} - + ${items} `; }); @@ -119,13 +138,13 @@ function DefaultAudioGainSlider() { function getGainMin() { const { audioGains } = useDefaultLayoutContext(), gains = audioGains(); - return isArray(gains) ? gains[0] ?? 0 : gains.min; + return isArray(gains) ? (gains[0] ?? 0) : gains.min; } function getGainMax() { const { audioGains } = useDefaultLayoutContext(), gains = audioGains(); - return isArray(gains) ? gains[gains.length - 1] ?? 300 : gains.max; + return isArray(gains) ? (gains[gains.length - 1] ?? 300) : gains.max; } function getGainStep() { diff --git a/packages/vidstack/src/elements/define/layouts/default/ui/menu/captions-menu.ts b/packages/vidstack/src/elements/define/layouts/default/ui/menu/captions-menu.ts index a87a1e3ee..50431a8d8 100644 --- a/packages/vidstack/src/elements/define/layouts/default/ui/menu/captions-menu.ts +++ b/packages/vidstack/src/elements/define/layouts/default/ui/menu/captions-menu.ts @@ -5,15 +5,22 @@ import { i18n } from '../../../../../../components/layouts/default/translations' import { useMediaState } from '../../../../../../core/api/media-context'; import { $signal } from '../../../../../lit/directives/signal'; import { $i18n } from '../utils'; -import { DefaultMenuButton } from './items/menu-items'; +import { DefaultMenuButton, DefaultMenuSection } from './items/menu-items'; export function DefaultCaptionsMenu() { return $signal(() => { - const { translations } = useDefaultLayoutContext(), - { hasCaptions } = useMediaState(), - $offText = $i18n(translations, 'Off'); + const { flatSettingsMenu, noCaptions, translations } = useDefaultLayoutContext(), + { hasCaptions } = useMediaState(); - if (!hasCaptions()) return null; + if (!hasCaptions() || noCaptions()) return null; + + if (flatSettingsMenu()) + return [ + DefaultMenuSection({ + label: i18n(translations, 'Captions'), + children: [DefaultCaptionsMenuItems()], + }), + ]; return html` @@ -21,20 +28,27 @@ export function DefaultCaptionsMenu() { label: () => i18n(translations, 'Captions'), icon: 'menu-captions', })} - - - - - + ${DefaultCaptionsMenuItems()} `; }); } + +function DefaultCaptionsMenuItems() { + const { translations } = useDefaultLayoutContext(), + $offText = $i18n(translations, 'Off'); + + return html` + + + + `; +} diff --git a/packages/vidstack/src/elements/define/layouts/default/ui/menu/playback-menu.ts b/packages/vidstack/src/elements/define/layouts/default/ui/menu/playback-menu.ts index b61627d19..081e90dbc 100644 --- a/packages/vidstack/src/elements/define/layouts/default/ui/menu/playback-menu.ts +++ b/packages/vidstack/src/elements/define/layouts/default/ui/menu/playback-menu.ts @@ -14,7 +14,29 @@ import { DefaultMenuSliderItem, DefaultSliderParts, DefaultSliderSteps } from '. export function DefaultPlaybackMenu() { return $signal(() => { - const { translations } = useDefaultLayoutContext(); + const { flatSettingsMenu, noMediaLoop, noMediaSpeed, noMediaQuality, translations } = + useDefaultLayoutContext(); + + const items: any[] = []; + + if (!noMediaLoop()) { + items.push( + DefaultMenuSection({ + children: DefaultLoopCheckbox(), + }), + ); + } + if (!noMediaSpeed()) { + items.push(DefaultSpeedMenuSection()); + } + if (!noMediaQuality()) { + items.push(DefaultQualityMenuSection()); + } + + if (!items.length) return null; + + if (flatSettingsMenu()) return items; + return html` ${DefaultMenuButton({ @@ -22,13 +44,7 @@ export function DefaultPlaybackMenu() { icon: 'menu-playback', })} - ${[ - DefaultMenuSection({ - children: DefaultLoopCheckbox(), - }), - DefaultSpeedMenuSection(), - DefaultQualityMenuSection(), - ]} + ${items} `; diff --git a/packages/vidstack/src/elements/define/layouts/default/ui/menu/settings-menu.ts b/packages/vidstack/src/elements/define/layouts/default/ui/menu/settings-menu.ts index 4f909ad3b..a6b4b93a9 100644 --- a/packages/vidstack/src/elements/define/layouts/default/ui/menu/settings-menu.ts +++ b/packages/vidstack/src/elements/define/layouts/default/ui/menu/settings-menu.ts @@ -32,6 +32,7 @@ export function DefaultSettingsMenu({ menuPortal, noModal, menuGroup, + flatSettingsMenu, smallWhen: smWhen, } = useDefaultLayoutContext(), $placement = computed(() => @@ -57,6 +58,7 @@ export function DefaultSettingsMenu({ class="vds-settings-menu-items vds-menu-items" placement=${$signal($placement)} offset=${$signal($offset)} + flat="${flatSettingsMenu()}" > ${$signal(() => { if (!$isOpen()) {