diff --git a/packages/vidstack/src/components/layouts/default/default-layout.ts b/packages/vidstack/src/components/layouts/default/default-layout.ts index 67500f205..828c0206c 100644 --- a/packages/vidstack/src/components/layouts/default/default-layout.ts +++ b/packages/vidstack/src/components/layouts/default/default-layout.ts @@ -1,10 +1,9 @@ -import { Component, computed, provideContext, signal } from 'maverick.js'; +import { Component, computed, prop, provideContext, signal } from 'maverick.js'; import { isBoolean } from 'maverick.js/std'; import { useMediaContext, type MediaContext } from '../../../core/api/media-context'; import type { MediaPlayerQuery } from '../../../core/api/player-state'; import { watchColorScheme } from '../../../utils/dom'; -import { declare_props } from '../../../utils/typed-decorators'; import { defaultLayoutContext } from './context'; import { defaultLayoutProps, type DefaultLayoutProps } from './props'; @@ -23,10 +22,12 @@ export class DefaultLayout extends Component { return this.#matches(when); }); + @prop get isMatch() { return this.#when(); } + @prop get isSmallLayout() { return this.#smallWhen(); } @@ -64,5 +65,3 @@ export class DefaultLayout extends Component { ); } } - -declare_props(DefaultLayout, ['isMatch', 'isSmallLayout']); diff --git a/packages/vidstack/src/components/player.ts b/packages/vidstack/src/components/player.ts index ae2e0090b..d7dcaf414 100644 --- a/packages/vidstack/src/components/player.ts +++ b/packages/vidstack/src/components/player.ts @@ -2,8 +2,10 @@ import { Component, computed, effect, + method, onDispose, peek, + prop, provideContext, signal, type WriteSignalRecord, @@ -59,7 +61,6 @@ import type { AnyMediaProvider, MediaProviderAdapter } from '../providers/types' import { setAttributeIfEmpty } from '../utils/dom'; import { clampNumber } from '../utils/number'; import { IS_IPHONE } from '../utils/support'; -import { declare_methods, declare_props } from '../utils/typed-decorators'; declare global { interface HTMLElementEventMap { @@ -127,8 +128,10 @@ export class MediaPlayer readonly #stateMgr: MediaStateManager; readonly #requestMgr: MediaRequestManager; + @prop readonly canPlayQueue = new RequestQueue(); + @prop readonly remoteControl: MediaRemoteControl; get #provider() { @@ -377,6 +380,7 @@ export class MediaPlayer /** * The current media provider. */ + @prop get provider(): AnyMediaProvider | null { return this.#provider; } @@ -384,6 +388,7 @@ export class MediaPlayer /** * Media controls settings. */ + @prop get controls(): MediaControls { return this.#requestMgr.controls; } @@ -396,11 +401,13 @@ export class MediaPlayer * Controls the screen orientation of the current browser window and dispatches orientation * change events on the player. */ + @prop readonly orientation: ScreenOrientationController; /** * The title of the current media. */ + @prop get title() { return peek(this.$state.providedTitle); } @@ -419,6 +426,7 @@ export class MediaPlayer * * @see {@link https://vidstack.io/docs/player/api/video-quality} */ + @prop get qualities(): VideoQualityList { return this.#media.qualities; } @@ -428,6 +436,7 @@ export class MediaPlayer * * @see {@link https://vidstack.io/docs/player/api/audio-tracks} */ + @prop get audioTracks(): AudioTrackList { return this.#media.audioTracks; } @@ -437,6 +446,7 @@ export class MediaPlayer * * @see {@link https://vidstack.io/docs/player/api/text-tracks} */ + @prop get textTracks(): TextTrackList { return this.#media.textTracks; } @@ -445,10 +455,12 @@ export class MediaPlayer * Contains text renderers which are responsible for loading, parsing, and rendering text * tracks. */ + @prop get textRenderers(): TextRenderers { return this.#media.textRenderers; } + @prop get duration() { return this.$state.duration(); } @@ -457,6 +469,7 @@ export class MediaPlayer this.#props.duration.set(duration); } + @prop get paused() { return peek(this.$state.paused); } @@ -475,6 +488,7 @@ export class MediaPlayer } else this.canPlayQueue.enqueue('paused', () => this.#requestMgr.play()); } + @prop get muted() { return peek(this.$state.muted); } @@ -493,6 +507,7 @@ export class MediaPlayer }); } + @prop get currentTime() { return peek(this.$state.currentTime); } @@ -530,6 +545,7 @@ export class MediaPlayer }); } + @prop get volume() { return peek(this.$state.volume); } @@ -549,6 +565,7 @@ export class MediaPlayer }); } + @prop get playbackRate() { return peek(this.$state.playbackRate); } @@ -615,6 +632,7 @@ export class MediaPlayer * * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/play} */ + @method async play(trigger?: Event) { return this.#requestMgr.play(trigger); } @@ -625,6 +643,7 @@ export class MediaPlayer * * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/pause} */ + @method async pause(trigger?: Event) { return this.#requestMgr.pause(trigger); } @@ -635,6 +654,7 @@ export class MediaPlayer * * @see {@link https://vidstack.io/docs/player/api/fullscreen} */ + @method async enterFullscreen(target?: MediaFullscreenRequestTarget, trigger?: Event) { return this.#requestMgr.enterFullscreen(target, trigger); } @@ -645,6 +665,7 @@ export class MediaPlayer * * @see {@link https://vidstack.io/docs/player/api/fullscreen} */ + @method async exitFullscreen(target?: MediaFullscreenRequestTarget, trigger?: Event) { return this.#requestMgr.exitFullscreen(target, trigger); } @@ -656,6 +677,7 @@ export class MediaPlayer * * @see {@link https://vidstack.io/docs/player/api/picture-in-picture} */ + @method enterPictureInPicture(trigger?: Event) { return this.#requestMgr.enterPictureInPicture(trigger); } @@ -666,6 +688,7 @@ export class MediaPlayer * * @see {@link https://vidstack.io/docs/player/api/picture-in-picture} */ + @method exitPictureInPicture(trigger?: Event) { return this.#requestMgr.exitPictureInPicture(trigger); } @@ -676,6 +699,7 @@ export class MediaPlayer * * @see {@link https://vidstack.io/docs/player/api/live} */ + @method seekToLiveEdge(trigger?: Event): void { this.#requestMgr.seekToLiveEdge(trigger); } @@ -686,6 +710,7 @@ export class MediaPlayer * * @see {@link https://vidstack.io/docs/player/core-concepts/loading#load-strategies} */ + @method startLoading(trigger?: Event): void { this.#media.notify('can-load', undefined, trigger); } @@ -695,6 +720,7 @@ export class MediaPlayer * * @see {@link https://vidstack.io/docs/player/core-concepts/loading#load-strategies} */ + @method startLoadingPoster(trigger?: Event) { this.#media.notify('can-load-poster', undefined, trigger); } @@ -702,6 +728,7 @@ export class MediaPlayer /** * Request Apple AirPlay picker to open. */ + @method requestAirPlay(trigger?: Event) { return this.#requestMgr.requestAirPlay(trigger); } @@ -710,6 +737,7 @@ export class MediaPlayer * Request Google Cast device picker to open. The Google Cast framework will be loaded if it * hasn't yet. */ + @method requestGoogleCast(trigger?: Event) { return this.#requestMgr.requestGoogleCast(trigger); } @@ -719,6 +747,7 @@ export class MediaPlayer * * @see {@link https://vidstack.io/docs/player/api/audio-gain} */ + @method setAudioGain(gain: number, trigger?: Event) { return this.#requestMgr.setAudioGain(gain, trigger); } @@ -729,37 +758,3 @@ export class MediaPlayer this.dispatch('destroy'); } } - -declare_props(MediaPlayer, [ - 'canPlayQueue', - 'remoteControl', - 'provider', - 'controls', - 'orientation', - 'title', - 'qualities', - 'audioTracks', - 'textTracks', - 'textRenderers', - 'duration', - 'paused', - 'muted', - 'currentTime', - 'volume', - 'playbackRate', -]); - -declare_methods(MediaPlayer, [ - 'play', - 'pause', - 'enterFullscreen', - 'exitFullscreen', - 'enterPictureInPicture', - 'exitPictureInPicture', - 'seekToLiveEdge', - 'startLoading', - 'startLoadingPoster', - 'requestAirPlay', - 'requestGoogleCast', - 'setAudioGain', -]); diff --git a/packages/vidstack/src/components/provider/provider.ts b/packages/vidstack/src/components/provider/provider.ts index bb541abdb..8c7c7f5da 100644 --- a/packages/vidstack/src/components/provider/provider.ts +++ b/packages/vidstack/src/components/provider/provider.ts @@ -1,4 +1,4 @@ -import { Component, onDispose, peek, signal, State, tick } from 'maverick.js'; +import { Component, method, onDispose, peek, signal, State, tick } from 'maverick.js'; import { animationFrameThrottle, isString, setStyle } from 'maverick.js/std'; import type { CaptionsFileFormat } from 'media-captions'; @@ -6,7 +6,6 @@ import { useMediaContext, type MediaContext } from '../../core/api/media-context import type { Src } from '../../core/api/src-types'; import { TextTrack, type TextTrackInit } from '../../core/tracks/text/text-track'; import type { MediaProviderLoader } from '../../providers/types'; -import { declare_methods } from '../../utils/typed-decorators'; import { SourceSelection } from './source-select'; import { Tracks } from './tracks'; @@ -75,6 +74,7 @@ export class MediaProvider extends Component #menu!: MenuContext; #hintEl = signal(null); + @prop get expanded() { return this.#menu?.expanded() ?? false; } @@ -93,5 +93,3 @@ export interface MenuButtonEvents { export interface MenuButtonSelectEvent extends DOMEvent { target: MenuButton; } - -declare_props(MenuButton, ['expanded']); diff --git a/packages/vidstack/src/components/ui/menu/menu.ts b/packages/vidstack/src/components/ui/menu/menu.ts index 21e5ab9ea..b1afecb15 100644 --- a/packages/vidstack/src/components/ui/menu/menu.ts +++ b/packages/vidstack/src/components/ui/menu/menu.ts @@ -2,8 +2,10 @@ import { Component, effect, hasProvidedContext, + method, onDispose, peek, + prop, provideContext, signal, tick, @@ -31,7 +33,6 @@ import { onPress, setAttributeIfEmpty, } from '../../../utils/dom'; -import { declare_methods, declare_props } from '../../../utils/typed-decorators'; import { Popper } from '../popper/popper'; import { sliderObserverContext } from '../sliders/slider/slider-context'; import type { MenuButton } from './menu-button'; @@ -82,6 +83,7 @@ export class Menu extends Component { /** * The menu trigger element. */ + @prop get triggerElement() { return this.#trigger(); } @@ -89,6 +91,7 @@ export class Menu extends Component { /** * The menu items element. */ + @prop get contentElement() { return this.#content(); } @@ -96,6 +99,7 @@ export class Menu extends Component { /** * Whether this menu is the child of another menu that contains it. */ + @prop get isSubmenu() { return !!this.#parentMenu; } @@ -558,6 +562,7 @@ export class Menu extends Component { /** * Open this menu. The first menu item will be focused if a `KeyboardEvent` trigger is provided */ + @method open(trigger?: Event) { if (peek(this.#expanded)) return; this.#popper.show(trigger); @@ -568,6 +573,7 @@ export class Menu extends Component { * Close this menu. The menu button that controls this menu will be focused if a `KeyboardEvent` * trigger is provided */ + @method close(trigger?: Event) { if (!peek(this.#expanded)) return; this.#popper.hide(trigger); @@ -575,9 +581,6 @@ export class Menu extends Component { } } -declare_props(Menu, ['triggerElement', 'contentElement', 'isSubmenu']); -declare_methods(Menu, ['open', 'close']); - export interface MenuProps { /** * The amount of time in milliseconds to wait before showing the menu. diff --git a/packages/vidstack/src/components/ui/menu/radio-groups/audio-gain-radio-group.ts b/packages/vidstack/src/components/ui/menu/radio-groups/audio-gain-radio-group.ts index 22005b0d7..7d9861226 100644 --- a/packages/vidstack/src/components/ui/menu/radio-groups/audio-gain-radio-group.ts +++ b/packages/vidstack/src/components/ui/menu/radio-groups/audio-gain-radio-group.ts @@ -1,8 +1,7 @@ -import { Component, effect, hasProvidedContext, useContext } from 'maverick.js'; +import { Component, effect, hasProvidedContext, method, prop, useContext } from 'maverick.js'; import type { DOMEvent } from 'maverick.js/std'; import { useMediaContext, type MediaContext } from '../../../../core/api/media-context'; -import { declare_methods, declare_props } from '../../../../utils/typed-decorators'; import { menuContext, type MenuContext } from '../menu-context'; import type { RadioOption } from '../radio/radio'; import { RadioGroupController } from '../radio/radio-group-controller'; @@ -28,10 +27,12 @@ export class AudioGainRadioGroup extends Component< #menu?: MenuContext; #controller: RadioGroupController; + @prop get value() { return this.#controller.value; } + @prop get disabled() { const { gains } = this.$props, { canSetAudioGain } = this.#media.$state; @@ -57,6 +58,7 @@ export class AudioGainRadioGroup extends Component< effect(this.#watchControllerDisabled.bind(this)); } + @method getOptions(): RadioOption[] { const { gains, normalLabel } = this.$props; return gains().map((gain) => ({ @@ -112,6 +114,3 @@ export interface AudioGainRadioGroupEvents { export interface AudioGainRadioGroupChangeEvent extends DOMEvent { target: AudioGainRadioGroup; } - -declare_props(AudioGainRadioGroup, ['value', 'disabled']); -declare_methods(AudioGainRadioGroup, ['getOptions']); diff --git a/packages/vidstack/src/components/ui/menu/radio-groups/audio-radio-group.ts b/packages/vidstack/src/components/ui/menu/radio-groups/audio-radio-group.ts index 16d8229d9..942ba893c 100644 --- a/packages/vidstack/src/components/ui/menu/radio-groups/audio-radio-group.ts +++ b/packages/vidstack/src/components/ui/menu/radio-groups/audio-radio-group.ts @@ -1,9 +1,8 @@ -import { Component, effect, hasProvidedContext, useContext } from 'maverick.js'; +import { Component, effect, hasProvidedContext, method, prop, useContext } from 'maverick.js'; import type { DOMEvent } from 'maverick.js/std'; import { useMediaContext, type MediaContext } from '../../../../core/api/media-context'; import type { AudioTrack } from '../../../../core/tracks/audio-tracks'; -import { declare_methods, declare_props } from '../../../../utils/typed-decorators'; import { menuContext, type MenuContext } from '../menu-context'; import type { RadioOption } from '../radio/radio'; import { RadioGroupController } from '../radio/radio-group-controller'; @@ -22,10 +21,12 @@ export class AudioRadioGroup extends Component ({ @@ -96,9 +98,6 @@ export class AudioRadioGroup extends Component(null); #cues = signal([]); + @prop get value() { return this.#controller.value; } + @prop get disabled() { return !this.#cues()?.length; } @@ -69,6 +79,7 @@ export class ChaptersRadioGroup extends Component< }); } + @method getOptions(): ChaptersRadioOption[] { const { clipStartTime, clipEndTime } = this.#media.$state, startTime = clipStartTime(), @@ -171,9 +182,6 @@ export class ChaptersRadioGroup extends Component< } } -declare_props(ChaptersRadioGroup, ['value', 'disabled']); -declare_methods(ChaptersRadioGroup, ['getOptions']); - export interface ChapterRadioGroupProps { /** * The thumbnails resource. diff --git a/packages/vidstack/src/components/ui/menu/radio-groups/quality-radio-group.ts b/packages/vidstack/src/components/ui/menu/radio-groups/quality-radio-group.ts index 7c13fa8f2..f25ce7f6d 100644 --- a/packages/vidstack/src/components/ui/menu/radio-groups/quality-radio-group.ts +++ b/packages/vidstack/src/components/ui/menu/radio-groups/quality-radio-group.ts @@ -3,7 +3,9 @@ import { computed, effect, hasProvidedContext, + method, peek, + prop, useContext, type ReadSignal, } from 'maverick.js'; @@ -13,7 +15,6 @@ import { useMediaContext, type MediaContext } from '../../../../core/api/media-c import { sortVideoQualities } from '../../../../core/quality/utils'; import type { VideoQuality } from '../../../../core/quality/video-quality'; import { round } from '../../../../utils/number'; -import { declare_methods, declare_props } from '../../../../utils/typed-decorators'; import { menuContext, type MenuContext } from '../menu-context'; import type { RadioOption } from '../radio/radio'; import { RadioGroupController } from '../radio/radio-group-controller'; @@ -38,10 +39,12 @@ export class QualityRadioGroup extends Component< #menu?: MenuContext; #controller: RadioGroupController; + @prop get value() { return this.#controller.value; } + @prop get disabled() { const { canSetQuality, qualities } = this.#media.$state; return !canSetQuality() || qualities().length <= 1; @@ -72,6 +75,7 @@ export class QualityRadioGroup extends Component< effect(this.#watchHintText.bind(this)); } + @method getOptions(): QualityRadioOption[] { const { autoLabel, hideBitrate } = this.$props; return [ @@ -143,9 +147,6 @@ export class QualityRadioGroup extends Component< } } -declare_props(QualityRadioGroup, ['value', 'disabled']); -declare_methods(QualityRadioGroup, ['getOptions']); - export interface QualityRadioGroupProps { /** The text to display for the auto quality radio option. */ autoLabel: string; diff --git a/packages/vidstack/src/components/ui/menu/radio-groups/speed-radio-group.ts b/packages/vidstack/src/components/ui/menu/radio-groups/speed-radio-group.ts index 1b385d6bf..01e33f25c 100644 --- a/packages/vidstack/src/components/ui/menu/radio-groups/speed-radio-group.ts +++ b/packages/vidstack/src/components/ui/menu/radio-groups/speed-radio-group.ts @@ -1,8 +1,7 @@ -import { Component, effect, hasProvidedContext, useContext } from 'maverick.js'; +import { Component, effect, hasProvidedContext, method, prop, useContext } from 'maverick.js'; import type { DOMEvent } from 'maverick.js/std'; import { useMediaContext, type MediaContext } from '../../../../core/api/media-context'; -import { declare_methods, declare_props } from '../../../../utils/typed-decorators'; import { menuContext, type MenuContext } from '../menu-context'; import type { RadioOption } from '../radio/radio'; import { RadioGroupController } from '../radio/radio-group-controller'; @@ -24,10 +23,12 @@ export class SpeedRadioGroup extends Component ({ @@ -89,9 +91,6 @@ export class SpeedRadioGroup extends Component /** * A list of radio values that belong this group. */ + @prop get values(): string[] { return this.#controller.values; } @@ -27,6 +27,7 @@ export class RadioGroup extends Component /** * The radio value that is checked in this group. */ + @prop get value() { return this.#controller.value; } @@ -75,5 +76,3 @@ export interface RadioGroupEvents { export interface RadioGroupChangeEvent extends DOMEvent { target: RadioGroup; } - -declare_props(RadioGroup, ['values', 'value']); diff --git a/packages/vidstack/src/components/ui/menu/radio/radio.ts b/packages/vidstack/src/components/ui/menu/radio/radio.ts index 359c93cc4..8759d13ae 100644 --- a/packages/vidstack/src/components/ui/menu/radio/radio.ts +++ b/packages/vidstack/src/components/ui/menu/radio/radio.ts @@ -4,6 +4,7 @@ import { hasProvidedContext, onDispose, peek, + prop, scoped, signal, useContext, @@ -14,7 +15,6 @@ import type { DOMEvent } from 'maverick.js/std'; import { FocusVisibleController } from '../../../../foundation/observers/focus-visible'; import { $ariaBool } from '../../../../utils/aria'; import { onPress, setAttributeIfEmpty } from '../../../../utils/dom'; -import { declare_props } from '../../../../utils/typed-decorators'; import { menuContext } from '../menu-context'; import { radioControllerContext, type RadioController } from './radio-controller'; @@ -43,6 +43,7 @@ export class Radio extends Component { /** * Whether this radio is currently checked. */ + @prop get checked(): boolean { return this.#checked(); } @@ -148,5 +149,3 @@ export interface RadioOption { label: string | ReadSignal; value: string; } - -declare_props(Radio, ['checked']); diff --git a/packages/vidstack/src/components/ui/sliders/slider-value.ts b/packages/vidstack/src/components/ui/sliders/slider-value.ts index 54a5baaf7..a4c042e1b 100644 --- a/packages/vidstack/src/components/ui/sliders/slider-value.ts +++ b/packages/vidstack/src/components/ui/sliders/slider-value.ts @@ -1,6 +1,7 @@ import { Component, computed, + method, useContext, useState, type ReadSignal, @@ -9,7 +10,6 @@ import { import { round } from '../../../utils/number'; import { formatTime } from '../../../utils/time'; -import { declare_methods } from '../../../utils/typed-decorators'; import { Slider } from '../sliders/slider/slider'; import type { SliderValueFormat } from './slider/format'; import { sliderValueFormatContext } from './slider/format'; @@ -45,6 +45,7 @@ export class SliderValue extends Component { /** * Returns the current value formatted as text based on prop settings. */ + @method getValueText() { const { type, @@ -77,8 +78,6 @@ export class SliderValue extends Component { } } -declare_methods(SliderValue, ['getValueText']); - export interface SliderValueProps { /** * Whether to use the slider's current value, or pointer value. diff --git a/packages/vidstack/src/components/ui/sliders/slider-video.ts b/packages/vidstack/src/components/ui/sliders/slider-video.ts index 584dc154f..58bca0c7e 100644 --- a/packages/vidstack/src/components/ui/sliders/slider-video.ts +++ b/packages/vidstack/src/components/ui/sliders/slider-video.ts @@ -1,10 +1,9 @@ -import { Component, effect, State, useState, type StateContext } from 'maverick.js'; -import { EventsController, isNull, type DOMEvent } from 'maverick.js/std'; +import { Component, effect, prop, State, useState, type StateContext } from 'maverick.js'; +import { EventsController, isNull, listenEvent, type DOMEvent } from 'maverick.js/std'; import { useMediaContext, type MediaContext } from '../../../core/api/media-context'; import type { MediaCrossOrigin } from '../../../core/api/types'; import { $ariaBool } from '../../../utils/aria'; -import { declare_props } from '../../../utils/typed-decorators'; import { Slider } from './slider/slider'; /** @@ -35,6 +34,7 @@ export class SliderVideo extends Component; + @prop get video() { return this.$state.video(); } @@ -140,8 +140,6 @@ export class SliderVideo extends Component(ctor: { new (): T }, names: Array) { - const proto = ctor.prototype; - for (const name of names) _prop(proto, name as string); -} - -export function declare_methods(ctor: { new (): T }, names: Array) { - const proto = ctor.prototype; - // @ts-expect-error - no descriptor - for (const name of names) _method(proto, name as string); -}