From 10d2c3c3d24b2f632ec5e95588d769f34e654d38 Mon Sep 17 00:00:00 2001 From: Christian Pillsbury Date: Wed, 12 Jun 2024 15:53:57 -0700 Subject: [PATCH] fix: beef up constants types. Migrate media-chrome-button as POC. minimal updates to resolve type errors. --- src/js/constants.js | 281 ------------------ src/js/constants.ts | 242 +++++++++++++++ .../media-audio-track-selectmenu.js | 8 +- .../experimental/media-captions-selectmenu.js | 8 +- src/js/experimental/media-chrome-listbox.js | 10 +- src/js/experimental/media-chrome-option.js | 2 +- .../experimental/media-chrome-selectmenu.js | 27 +- .../media-playback-rate-selectmenu.js | 10 +- .../media-rendition-selectmenu.js | 8 +- src/js/media-captions-button.js | 1 + src/js/media-chrome-button.ts | 10 +- src/js/media-chrome-menu.js | 4 +- src/js/media-container.js | 2 + src/js/media-control-bar.js | 2 +- src/js/media-loading-indicator.js | 2 +- src/js/media-playback-rate-button.js | 2 + src/js/media-store/state-mediator.js | 2 +- src/js/media-text-display.js | 3 +- src/js/media-time-display.js | 4 +- src/js/utils/server-safe-globals.ts | 9 +- 20 files changed, 333 insertions(+), 304 deletions(-) delete mode 100644 src/js/constants.js create mode 100644 src/js/constants.ts diff --git a/src/js/constants.js b/src/js/constants.js deleted file mode 100644 index 7d9eee15a..000000000 --- a/src/js/constants.js +++ /dev/null @@ -1,281 +0,0 @@ -/** - * @typedef {{ - * MEDIA_PLAY_REQUEST: 'mediaplayrequest', - * MEDIA_PAUSE_REQUEST: 'mediapauserequest', - * MEDIA_MUTE_REQUEST: 'mediamuterequest', - * MEDIA_UNMUTE_REQUEST: 'mediaunmuterequest', - * MEDIA_VOLUME_REQUEST: 'mediavolumerequest', - * MEDIA_SEEK_REQUEST: 'mediaseekrequest', - * MEDIA_AIRPLAY_REQUEST: 'mediaairplayrequest', - * MEDIA_ENTER_FULLSCREEN_REQUEST: 'mediaenterfullscreenrequest', - * MEDIA_EXIT_FULLSCREEN_REQUEST: 'mediaexitfullscreenrequest', - * MEDIA_PREVIEW_REQUEST: 'mediapreviewrequest', - * MEDIA_ENTER_PIP_REQUEST: 'mediaenterpiprequest', - * MEDIA_EXIT_PIP_REQUEST: 'mediaexitpiprequest', - * MEDIA_ENTER_CAST_REQUEST: 'mediaentercastrequest', - * MEDIA_EXIT_CAST_REQUEST: 'mediaexitcastrequest', - * MEDIA_SHOW_TEXT_TRACKS_REQUEST: 'mediashowtexttracksrequest', - * MEDIA_HIDE_TEXT_TRACKS_REQUEST: 'mediahidetexttracksrequest', - * MEDIA_SHOW_SUBTITLES_REQUEST: 'mediashowsubtitlesrequest', - * MEDIA_DISABLE_SUBTITLES_REQUEST: 'mediadisablesubtitlesrequest', - * MEDIA_TOGGLE_SUBTITLES_REQUEST: 'mediatogglesubtitlesrequest', - * MEDIA_PLAYBACK_RATE_REQUEST: 'mediaplaybackraterequest', - * MEDIA_RENDITION_REQUEST: 'mediarenditionrequest', - * MEDIA_AUDIO_TRACK_REQUEST: 'mediaaudiotrackrequest', - * MEDIA_SEEK_TO_LIVE_REQUEST: 'mediaseektoliverequest', - * REGISTER_MEDIA_STATE_RECEIVER: 'registermediastatereceiver', - * UNREGISTER_MEDIA_STATE_RECEIVER: 'unregistermediastatereceiver', - * }} MediaUIEvents - */ - -/** @type {MediaUIEvents} */ -export const MediaUIEvents = { - MEDIA_PLAY_REQUEST: 'mediaplayrequest', - MEDIA_PAUSE_REQUEST: 'mediapauserequest', - MEDIA_MUTE_REQUEST: 'mediamuterequest', - MEDIA_UNMUTE_REQUEST: 'mediaunmuterequest', - MEDIA_VOLUME_REQUEST: 'mediavolumerequest', - MEDIA_SEEK_REQUEST: 'mediaseekrequest', - MEDIA_AIRPLAY_REQUEST: 'mediaairplayrequest', - MEDIA_ENTER_FULLSCREEN_REQUEST: 'mediaenterfullscreenrequest', - MEDIA_EXIT_FULLSCREEN_REQUEST: 'mediaexitfullscreenrequest', - MEDIA_PREVIEW_REQUEST: 'mediapreviewrequest', - MEDIA_ENTER_PIP_REQUEST: 'mediaenterpiprequest', - MEDIA_EXIT_PIP_REQUEST: 'mediaexitpiprequest', - MEDIA_ENTER_CAST_REQUEST: 'mediaentercastrequest', - MEDIA_EXIT_CAST_REQUEST: 'mediaexitcastrequest', - MEDIA_SHOW_TEXT_TRACKS_REQUEST: 'mediashowtexttracksrequest', - MEDIA_HIDE_TEXT_TRACKS_REQUEST: 'mediahidetexttracksrequest', - MEDIA_SHOW_SUBTITLES_REQUEST: 'mediashowsubtitlesrequest', - MEDIA_DISABLE_SUBTITLES_REQUEST: 'mediadisablesubtitlesrequest', - MEDIA_TOGGLE_SUBTITLES_REQUEST: 'mediatogglesubtitlesrequest', - MEDIA_PLAYBACK_RATE_REQUEST: 'mediaplaybackraterequest', - MEDIA_RENDITION_REQUEST: 'mediarenditionrequest', - MEDIA_AUDIO_TRACK_REQUEST: 'mediaaudiotrackrequest', - MEDIA_SEEK_TO_LIVE_REQUEST: 'mediaseektoliverequest', - REGISTER_MEDIA_STATE_RECEIVER: 'registermediastatereceiver', - UNREGISTER_MEDIA_STATE_RECEIVER: 'unregistermediastatereceiver', -}; - -export const MediaStateReceiverAttributes = { - MEDIA_CHROME_ATTRIBUTES: 'mediachromeattributes', - MEDIA_CONTROLLER: 'mediacontroller', -}; - -/** - * @typedef {{ - * MEDIA_AIRPLAY_UNAVAILABLE: 'mediaAirplayUnavailable', - * MEDIA_FULLSCREEN_UNAVAILABLE: 'mediaFullscreenUnavailable', - * MEDIA_PIP_UNAVAILABLE: 'mediaPipUnavailable', - * MEDIA_CAST_UNAVAILABLE: 'mediaCastUnavailable', - * MEDIA_RENDITION_UNAVAILABLE: 'mediaRenditionUnavailable', - * MEDIA_AUDIO_TRACK_UNAVAILABLE: 'mediaAudioTrackUnavailable', - * MEDIA_PAUSED: 'mediaPaused', - * MEDIA_HAS_PLAYED: 'mediaHasPlayed', - * MEDIA_ENDED: 'mediaEnded', - * MEDIA_MUTED: 'mediaMuted', - * MEDIA_VOLUME_LEVEL: 'mediaVolumeLevel', - * MEDIA_VOLUME: 'mediaVolume', - * MEDIA_VOLUME_UNAVAILABLE: 'mediaVolumeUnavailable', - * MEDIA_IS_PIP: 'mediaIsPip', - * MEDIA_IS_CASTING: 'mediaIsCasting', - * MEDIA_IS_AIRPLAYING: 'mediaIsAirplaying', - * MEDIA_SUBTITLES_LIST: 'mediaSubtitlesList', - * MEDIA_SUBTITLES_SHOWING: 'mediaSubtitlesShowing', - * MEDIA_IS_FULLSCREEN: 'mediaIsFullscreen', - * MEDIA_PLAYBACK_RATE: 'mediaPlaybackRate', - * MEDIA_CURRENT_TIME: 'mediaCurrentTime', - * MEDIA_DURATION: 'mediaDuration', - * MEDIA_SEEKABLE: 'mediaSeekable', - * MEDIA_PREVIEW_TIME: 'mediaPreviewTime', - * MEDIA_PREVIEW_IMAGE: 'mediaPreviewImage', - * MEDIA_PREVIEW_COORDS: 'mediaPreviewCoords', - * MEDIA_PREVIEW_CHAPTER: 'mediaPreviewChapter', - * MEDIA_LOADING: 'mediaLoading', - * MEDIA_BUFFERED: 'mediaBuffered', - * MEDIA_STREAM_TYPE: 'mediaStreamType', - * MEDIA_TARGET_LIVE_WINDOW: 'mediaTargetLiveWindow', - * MEDIA_TIME_IS_LIVE: 'mediaTimeIsLive', - * MEDIA_RENDITION_LIST: 'mediaRenditionList', - * MEDIA_RENDITION_SELECTED: 'mediaRenditionSelected', - * MEDIA_AUDIO_TRACK_LIST: 'mediaAudioTrackList', - * MEDIA_AUDIO_TRACK_ENABLED: 'mediaAudioTrackEnabled', - * MEDIA_CHAPTERS_CUES: 'mediaChaptersCues', - * }} MediaUIProps - */ - -/** @type {MediaUIProps} */ -export const MediaUIProps = { - MEDIA_AIRPLAY_UNAVAILABLE: 'mediaAirplayUnavailable', - MEDIA_FULLSCREEN_UNAVAILABLE: 'mediaFullscreenUnavailable', - MEDIA_PIP_UNAVAILABLE: 'mediaPipUnavailable', - MEDIA_CAST_UNAVAILABLE: 'mediaCastUnavailable', - MEDIA_RENDITION_UNAVAILABLE: 'mediaRenditionUnavailable', - MEDIA_AUDIO_TRACK_UNAVAILABLE: 'mediaAudioTrackUnavailable', - MEDIA_PAUSED: 'mediaPaused', - MEDIA_HAS_PLAYED: 'mediaHasPlayed', - MEDIA_ENDED: 'mediaEnded', - MEDIA_MUTED: 'mediaMuted', - MEDIA_VOLUME_LEVEL: 'mediaVolumeLevel', - MEDIA_VOLUME: 'mediaVolume', - MEDIA_VOLUME_UNAVAILABLE: 'mediaVolumeUnavailable', - MEDIA_IS_PIP: 'mediaIsPip', - MEDIA_IS_CASTING: 'mediaIsCasting', - MEDIA_IS_AIRPLAYING: 'mediaIsAirplaying', - MEDIA_SUBTITLES_LIST: 'mediaSubtitlesList', - MEDIA_SUBTITLES_SHOWING: 'mediaSubtitlesShowing', - MEDIA_IS_FULLSCREEN: 'mediaIsFullscreen', - MEDIA_PLAYBACK_RATE: 'mediaPlaybackRate', - MEDIA_CURRENT_TIME: 'mediaCurrentTime', - MEDIA_DURATION: 'mediaDuration', - MEDIA_SEEKABLE: 'mediaSeekable', - MEDIA_PREVIEW_TIME: 'mediaPreviewTime', - MEDIA_PREVIEW_IMAGE: 'mediaPreviewImage', - MEDIA_PREVIEW_COORDS: 'mediaPreviewCoords', - MEDIA_PREVIEW_CHAPTER: 'mediaPreviewChapter', - MEDIA_LOADING: 'mediaLoading', - MEDIA_BUFFERED: 'mediaBuffered', - MEDIA_STREAM_TYPE: 'mediaStreamType', - MEDIA_TARGET_LIVE_WINDOW: 'mediaTargetLiveWindow', - MEDIA_TIME_IS_LIVE: 'mediaTimeIsLive', - MEDIA_RENDITION_LIST: 'mediaRenditionList', - MEDIA_RENDITION_SELECTED: 'mediaRenditionSelected', - MEDIA_AUDIO_TRACK_LIST: 'mediaAudioTrackList', - MEDIA_AUDIO_TRACK_ENABLED: 'mediaAudioTrackEnabled', - MEDIA_CHAPTERS_CUES: 'mediaChaptersCues', -}; - -const MediaUIPropsEntries = /** @type {[keyof MediaUIProps, string][]} */ ( - Object.entries(MediaUIProps) -); - -export const MediaUIAttributes = - /** @type {{ [k in keyof MediaUIProps]: string }} */ ( - MediaUIPropsEntries.reduce((dictObj, [key, propName]) => { - dictObj[key] = `${propName.toLowerCase()}`; - return dictObj; - }, /** @type {Partial<{ [k in keyof MediaUIProps]: string }>} */ ({})) - ); - -export const MediaStateChangeEvents = - /** @type {{ [k in keyof MediaUIProps | 'USER_INACTIVE' | 'BREAKPOINTS_CHANGE' | 'BREAKPOINTS_COMPUTED']: string }} */ ( - MediaUIPropsEntries.reduce( - (dictObj, [key, propName]) => { - dictObj[key] = `${propName.toLowerCase()}`; - return dictObj; - }, - /** @type {Partial<{ [k in keyof MediaUIProps | 'USER_INACTIVE' | 'BREAKPOINTS_CHANGE' | 'BREAKPOINTS_COMPUTED']: string }>} */ ({ - USER_INACTIVE: 'userinactivechange', - BREAKPOINTS_CHANGE: 'breakpointchange', - BREAKPOINTS_COMPUTED: 'breakpointscomputed', - }) - ) - ); - -// Maps from state change event type -> attribute name -export const StateChangeEventToAttributeMap = Object.entries( - MediaStateChangeEvents -).reduce( - (mapObj, [key, eventType]) => { - const attrName = MediaUIAttributes[key]; - if (attrName) { - mapObj[eventType] = attrName; - } - return mapObj; - }, - { userinactivechange: 'userinactive' } -); - -// Maps from attribute name -> state change event type -export const AttributeToStateChangeEventMap = Object.entries( - MediaUIAttributes -).reduce( - (mapObj, [key, attrName]) => { - const evtType = MediaStateChangeEvents[key]; - if (evtType) { - mapObj[attrName] = evtType; - } - return mapObj; - }, - { userinactive: 'userinactivechange' } -); - -export const TextTrackKinds = { - SUBTITLES: 'subtitles', - CAPTIONS: 'captions', - DESCRIPTIONS: 'descriptions', - CHAPTERS: 'chapters', - METADATA: 'metadata', -}; - -export const TextTrackModes = { - DISABLED: 'disabled', - HIDDEN: 'hidden', - SHOWING: 'showing', -}; - -export const ReadyStates = { - HAVE_NOTHING: 0, - HAVE_METADATA: 1, - HAVE_CURRENT_DATA: 2, - HAVE_FUTURE_DATA: 3, - HAVE_ENOUGH_DATA: 4, -}; - -export const PointerTypes = { - MOUSE: 'mouse', - PEN: 'pen', - TOUCH: 'touch', -}; - -/** - * @type {{ - * UNAVAILABLE: 'unavailable'; - * UNSUPPORTED: 'unsupported'; - * }} - */ -export const AvailabilityStates = { - UNAVAILABLE: 'unavailable', - UNSUPPORTED: 'unsupported', -}; - -/** - * @type {{ - * LIVE: 'live'; - * ON_DEMAND: 'on-demand'; - * UNKNOWN: 'unknown'; - * }} - */ -export const StreamTypes = { - LIVE: 'live', - ON_DEMAND: 'on-demand', - UNKNOWN: 'unknown', -}; - -/** - * @type {{ - * HIGH: 'high'; - * MEDIUM: 'medium'; - * LOW: 'low'; - * OFF: 'off'; - * }} - */ -export const VolumeLevels = { - HIGH: 'high', - MEDIUM: 'medium', - LOW: 'low', - OFF: 'off', -}; - -/** - * @type {{ - * INLINE: 'inline'; - * FULLSCREEN: 'fullscreen'; - * PICTURE_IN_PICTURE: 'picture-in-picture'; - * }} - */ -export const WebkitPresentationModes = { - INLINE: 'inline', - FULLSCREEN: 'fullscreen', - PICTURE_IN_PICTURE: 'picture-in-picture', -}; diff --git a/src/js/constants.ts b/src/js/constants.ts new file mode 100644 index 000000000..041c99772 --- /dev/null +++ b/src/js/constants.ts @@ -0,0 +1,242 @@ +export const MediaUIEvents = { + MEDIA_PLAY_REQUEST: 'mediaplayrequest', + MEDIA_PAUSE_REQUEST: 'mediapauserequest', + MEDIA_MUTE_REQUEST: 'mediamuterequest', + MEDIA_UNMUTE_REQUEST: 'mediaunmuterequest', + MEDIA_VOLUME_REQUEST: 'mediavolumerequest', + MEDIA_SEEK_REQUEST: 'mediaseekrequest', + MEDIA_AIRPLAY_REQUEST: 'mediaairplayrequest', + MEDIA_ENTER_FULLSCREEN_REQUEST: 'mediaenterfullscreenrequest', + MEDIA_EXIT_FULLSCREEN_REQUEST: 'mediaexitfullscreenrequest', + MEDIA_PREVIEW_REQUEST: 'mediapreviewrequest', + MEDIA_ENTER_PIP_REQUEST: 'mediaenterpiprequest', + MEDIA_EXIT_PIP_REQUEST: 'mediaexitpiprequest', + MEDIA_ENTER_CAST_REQUEST: 'mediaentercastrequest', + MEDIA_EXIT_CAST_REQUEST: 'mediaexitcastrequest', + MEDIA_SHOW_TEXT_TRACKS_REQUEST: 'mediashowtexttracksrequest', + MEDIA_HIDE_TEXT_TRACKS_REQUEST: 'mediahidetexttracksrequest', + MEDIA_SHOW_SUBTITLES_REQUEST: 'mediashowsubtitlesrequest', + MEDIA_DISABLE_SUBTITLES_REQUEST: 'mediadisablesubtitlesrequest', + MEDIA_TOGGLE_SUBTITLES_REQUEST: 'mediatogglesubtitlesrequest', + MEDIA_PLAYBACK_RATE_REQUEST: 'mediaplaybackraterequest', + MEDIA_RENDITION_REQUEST: 'mediarenditionrequest', + MEDIA_AUDIO_TRACK_REQUEST: 'mediaaudiotrackrequest', + MEDIA_SEEK_TO_LIVE_REQUEST: 'mediaseektoliverequest', + REGISTER_MEDIA_STATE_RECEIVER: 'registermediastatereceiver', + UNREGISTER_MEDIA_STATE_RECEIVER: 'unregistermediastatereceiver', +} as const; + +export type MediaUIEvents = typeof MediaUIEvents; + +export const MediaStateReceiverAttributes = { + MEDIA_CHROME_ATTRIBUTES: 'mediachromeattributes', + MEDIA_CONTROLLER: 'mediacontroller', +} as const; + +export type MediaStateReceiverAttributes = typeof MediaStateReceiverAttributes; + +export const MediaUIProps = { + MEDIA_AIRPLAY_UNAVAILABLE: 'mediaAirplayUnavailable', + MEDIA_FULLSCREEN_UNAVAILABLE: 'mediaFullscreenUnavailable', + MEDIA_PIP_UNAVAILABLE: 'mediaPipUnavailable', + MEDIA_CAST_UNAVAILABLE: 'mediaCastUnavailable', + MEDIA_RENDITION_UNAVAILABLE: 'mediaRenditionUnavailable', + MEDIA_AUDIO_TRACK_UNAVAILABLE: 'mediaAudioTrackUnavailable', + MEDIA_PAUSED: 'mediaPaused', + MEDIA_HAS_PLAYED: 'mediaHasPlayed', + MEDIA_ENDED: 'mediaEnded', + MEDIA_MUTED: 'mediaMuted', + MEDIA_VOLUME_LEVEL: 'mediaVolumeLevel', + MEDIA_VOLUME: 'mediaVolume', + MEDIA_VOLUME_UNAVAILABLE: 'mediaVolumeUnavailable', + MEDIA_IS_PIP: 'mediaIsPip', + MEDIA_IS_CASTING: 'mediaIsCasting', + MEDIA_IS_AIRPLAYING: 'mediaIsAirplaying', + MEDIA_SUBTITLES_LIST: 'mediaSubtitlesList', + MEDIA_SUBTITLES_SHOWING: 'mediaSubtitlesShowing', + MEDIA_IS_FULLSCREEN: 'mediaIsFullscreen', + MEDIA_PLAYBACK_RATE: 'mediaPlaybackRate', + MEDIA_CURRENT_TIME: 'mediaCurrentTime', + MEDIA_DURATION: 'mediaDuration', + MEDIA_SEEKABLE: 'mediaSeekable', + MEDIA_PREVIEW_TIME: 'mediaPreviewTime', + MEDIA_PREVIEW_IMAGE: 'mediaPreviewImage', + MEDIA_PREVIEW_COORDS: 'mediaPreviewCoords', + MEDIA_PREVIEW_CHAPTER: 'mediaPreviewChapter', + MEDIA_LOADING: 'mediaLoading', + MEDIA_BUFFERED: 'mediaBuffered', + MEDIA_STREAM_TYPE: 'mediaStreamType', + MEDIA_TARGET_LIVE_WINDOW: 'mediaTargetLiveWindow', + MEDIA_TIME_IS_LIVE: 'mediaTimeIsLive', + MEDIA_RENDITION_LIST: 'mediaRenditionList', + MEDIA_RENDITION_SELECTED: 'mediaRenditionSelected', + MEDIA_AUDIO_TRACK_LIST: 'mediaAudioTrackList', + MEDIA_AUDIO_TRACK_ENABLED: 'mediaAudioTrackEnabled', + MEDIA_CHAPTERS_CUES: 'mediaChaptersCues', +} as const; + +export type MediaUIProps = typeof MediaUIProps; + +type Entries = { [k in keyof T]: [k, T[k]] }[keyof T][]; + +type LowercaseValues> = { + [k in keyof T]: Lowercase; +}; + +type Writeable = { + -readonly [k in keyof T]: T[k]; +}; + +type MediaUIPropsEntries = Entries; +const MediaUIPropsEntries: MediaUIPropsEntries = Object.entries( + MediaUIProps +) as MediaUIPropsEntries; + +export type MediaUIAttributes = LowercaseValues; +export const MediaUIAttributes = MediaUIPropsEntries.reduce( + (dictObj, [key, propName]) => { + // @ts-ignore + dictObj[key] = propName.toLowerCase(); + return dictObj; + }, + {} as Partial> +) as MediaUIAttributes; + +const AdditionalStateChangeEvents = { + USER_INACTIVE: 'userinactivechange', + BREAKPOINTS_CHANGE: 'breakpointchange', + BREAKPOINTS_COMPUTED: 'breakpointscomputed', +} as const; + +export type MediaStateChangeEvents = { + [k in keyof MediaUIProps]: Lowercase; +} & typeof AdditionalStateChangeEvents; + +/** @TODO In a prior migration, we dropped the 'change' from our state change event types. Although a breaking change, we should consider re-adding (CJP) */ +// export type MediaStateChangeEvents = { +// [k in keyof MediaUIProps]: `${Lowercase}change`; +// } & typeof AdditionalStateChangeEvents; + +export const MediaStateChangeEvents = MediaUIPropsEntries.reduce( + (dictObj, [key, propName]) => { + // @ts-ignore + dictObj[key] = propName.toLowerCase(); + // dictObj[key] = `${propName.toLowerCase()}change`; + return dictObj; + }, + { ...AdditionalStateChangeEvents } as Partial< + Writeable + > +) as MediaStateChangeEvents; + +/** @TODO Make types more precise derivations, at least after updates to event type names mentioned above (CJP) */ +export type StateChangeEventToAttributeMap = { + [k in MediaStateChangeEvents[keyof MediaStateChangeEvents & + keyof MediaUIAttributes]]: MediaUIAttributes[keyof MediaUIAttributes]; +} & { userinactivechange: 'userinactive' }; + +// Maps from state change event type -> attribute name +export const StateChangeEventToAttributeMap = Object.entries( + MediaStateChangeEvents +).reduce( + (mapObj, [key, eventType]) => { + const attrName = MediaUIAttributes[key]; + if (attrName) { + mapObj[eventType] = attrName; + } + return mapObj; + }, + { userinactivechange: 'userinactive' } as Partial< + Writeable + > +) as StateChangeEventToAttributeMap; + +/** @TODO Make types more precise derivations, at least after updates to event type names mentioned above (CJP) */ +export type AttributeToStateChangeEventMap = { + [k in MediaUIAttributes[keyof MediaUIAttributes & + keyof MediaStateChangeEvents]]: MediaStateChangeEvents[keyof MediaStateChangeEvents]; +} & { userinactive: 'userinactivechange' }; + +// Maps from attribute name -> state change event type +export const AttributeToStateChangeEventMap = Object.entries( + MediaUIAttributes +).reduce( + (mapObj, [key, attrName]) => { + const evtType = MediaStateChangeEvents[key]; + if (evtType) { + mapObj[attrName] = evtType; + } + return mapObj; + }, + { userinactive: 'userinactivechange' } as Partial< + Writeable + > +) as AttributeToStateChangeEventMap; + +export const TextTrackKinds = { + SUBTITLES: 'subtitles', + CAPTIONS: 'captions', + DESCRIPTIONS: 'descriptions', + CHAPTERS: 'chapters', + METADATA: 'metadata', +} as const; + +export type TextTrackKinds = typeof TextTrackKinds; + +export const TextTrackModes = { + DISABLED: 'disabled', + HIDDEN: 'hidden', + SHOWING: 'showing', +} as const; + +export type TextTrackModes = typeof TextTrackModes; + +export const ReadyStates = { + HAVE_NOTHING: 0, + HAVE_METADATA: 1, + HAVE_CURRENT_DATA: 2, + HAVE_FUTURE_DATA: 3, + HAVE_ENOUGH_DATA: 4, +} as const; + +export type ReadyStates = typeof ReadyStates; + +export const PointerTypes = { + MOUSE: 'mouse', + PEN: 'pen', + TOUCH: 'touch', +} as const; + +export type PointerTypes = typeof PointerTypes; + +export const AvailabilityStates = { + UNAVAILABLE: 'unavailable', + UNSUPPORTED: 'unsupported', +} as const; + +export type AvailabilityStates = typeof AvailabilityStates; + +export const StreamTypes = { + LIVE: 'live', + ON_DEMAND: 'on-demand', + UNKNOWN: 'unknown', +} as const; + +export type StreamTypes = typeof StreamTypes; + +export const VolumeLevels = { + HIGH: 'high', + MEDIUM: 'medium', + LOW: 'low', + OFF: 'off', +} as const; + +export type VolumeLevels = typeof VolumeLevels; + +export const WebkitPresentationModes = { + INLINE: 'inline', + FULLSCREEN: 'fullscreen', + PICTURE_IN_PICTURE: 'picture-in-picture', +} as const; + +export type WebkitPresentationModes = typeof WebkitPresentationModes; diff --git a/src/js/experimental/media-audio-track-selectmenu.js b/src/js/experimental/media-audio-track-selectmenu.js index b39db17ad..a447dbf7a 100644 --- a/src/js/experimental/media-audio-track-selectmenu.js +++ b/src/js/experimental/media-audio-track-selectmenu.js @@ -4,6 +4,10 @@ import './media-audio-track-listbox.js'; import { MediaUIAttributes } from '../constants.js'; import { globalThis, document } from '../utils/server-safe-globals.js'; +/** + * @typedef {import('./media-audio-track-button.js').MediaAudioTrackButton} MediaAudioTrackButton + */ + /** * @attr {string} mediaaudiotrackenabled - (read-only) Set to the selected audio track id. * @attr {(unavailable|unsupported)} mediaaudiotrackunavailable - (read-only) Set if audio track selection is unavailable. @@ -22,7 +26,9 @@ class MediaAudioTrackSelectMenu extends MediaChromeSelectMenu { } init() { - const audioTrackButton = document.createElement('media-audio-track-button'); + const audioTrackButton = /** @type {MediaAudioTrackButton} */ ( + /** @type {unknown} */ document.createElement('media-audio-track-button') + ); audioTrackButton.part.add('button'); audioTrackButton.preventClick = true; diff --git a/src/js/experimental/media-captions-selectmenu.js b/src/js/experimental/media-captions-selectmenu.js index 7ee9c1a68..e772dcc36 100644 --- a/src/js/experimental/media-captions-selectmenu.js +++ b/src/js/experimental/media-captions-selectmenu.js @@ -3,6 +3,10 @@ import '../media-captions-button.js'; import './media-captions-listbox.js'; import { globalThis, document } from '../utils/server-safe-globals.js'; +/** + * @typedef {import('../media-captions-button.js').MediaCaptionsButton} MediaCaptionsButton + */ + /** * @csspart button - The default button that's in the shadow DOM. * @csspart listbox - The default listbox that's in the shadow DOM. @@ -10,7 +14,9 @@ import { globalThis, document } from '../utils/server-safe-globals.js'; */ class MediaCaptionsSelectMenu extends MediaChromeSelectMenu { init() { - const captionsButton = document.createElement('media-captions-button'); + const captionsButton = /** @type {MediaCaptionsButton} */ ( + /** @type {unknown} */ document.createElement('media-captions-button') + ); captionsButton.part.add('button'); captionsButton.preventClick = true; diff --git a/src/js/experimental/media-chrome-listbox.js b/src/js/experimental/media-chrome-listbox.js index 96f689c89..9e7414d59 100644 --- a/src/js/experimental/media-chrome-listbox.js +++ b/src/js/experimental/media-chrome-listbox.js @@ -7,7 +7,9 @@ const checkIcon = /*html*/ ` `; export function createOption(text, value, selected) { - const option = document.createElement('media-chrome-option'); + const option = /** @type {HTMLOptionElement} */ ( + /** @type unknown */ document.createElement('media-chrome-option') + ); option.part.add('option'); option.value = value; option.selected = selected; @@ -167,6 +169,8 @@ class MediaChromeListbox extends globalThis.HTMLElement { #keysSoFar = ''; #clearKeysTimeout = null; #metaPressed = false; + /** @type {DocumentFragment} */ + nativeEl; constructor(options = {}) { super(); @@ -175,7 +179,9 @@ class MediaChromeListbox extends globalThis.HTMLElement { // Set up the Shadow DOM if not using Declarative Shadow DOM. this.attachShadow({ mode: 'open' }); - this.nativeEl = template.content.cloneNode(true); + this.nativeEl = /** @type {DocumentFragment} */ ( + /** @type {unknown} */ template.content.cloneNode(true) + ); if (options.slotTemplate) { this.nativeEl.append(options.slotTemplate.content.cloneNode(true)); diff --git a/src/js/experimental/media-chrome-option.js b/src/js/experimental/media-chrome-option.js index ab6f506f6..fd53c5ff4 100644 --- a/src/js/experimental/media-chrome-option.js +++ b/src/js/experimental/media-chrome-option.js @@ -109,7 +109,7 @@ class MediaChromeOption extends globalThis.HTMLElement { enable() { if (!this.hasAttribute('tabindex')) { - this.setAttribute('tabindex', -1); + this.tabIndex = -1; } if (!this.hasAttribute('aria-selected')) { this.setAttribute('aria-selected', 'false'); diff --git a/src/js/experimental/media-chrome-selectmenu.js b/src/js/experimental/media-chrome-selectmenu.js index 4df11ca6e..832a56363 100644 --- a/src/js/experimental/media-chrome-selectmenu.js +++ b/src/js/experimental/media-chrome-selectmenu.js @@ -10,6 +10,12 @@ import { import { observeResize, unobserveResize } from '../utils/resize-observer.js'; import { MediaStateReceiverAttributes } from '../constants.js'; +/** @typedef {import('../media-controller.js').MediaController} MediaController */ + +/** + * @typedef {import('../media-chrome-button.js').MediaChromeButton} MediaChromeButton + */ + const template = document.createElement('template'); template.innerHTML = /*html*/ `