Add events and properties to video, audio and iFrame (#18257)
- Add media-specific events - Extend `iFrame` with missing properties - Enrich `SerializedEventData` with media-related target fields so serialized events carry the media element state. - Refactor the `remote-elements` code generator to support per-element custom events
This commit is contained in:
@@ -48,7 +48,9 @@ const getHtmlElementSchemas = (): ComponentSchema[] => {
|
||||
...HTML_COMMON_PROPERTIES,
|
||||
...element.properties,
|
||||
},
|
||||
events: COMMON_HTML_EVENTS,
|
||||
events: element.events
|
||||
? [...COMMON_HTML_EVENTS, ...element.events]
|
||||
: COMMON_HTML_EVENTS,
|
||||
htmlTag: extractHtmlTag(element.tag),
|
||||
}));
|
||||
};
|
||||
|
||||
@@ -162,12 +162,15 @@ const generateElementDefinition = (
|
||||
sourceFile: SourceFile,
|
||||
component: ComponentSchema,
|
||||
specificProperties: Record<string, PropertySchema>,
|
||||
useSharedEvents: boolean,
|
||||
useSharedPropertiesConfig: boolean,
|
||||
commonEventNames: Set<string>,
|
||||
shouldUseCommonHtmlPropertiesConfig: boolean,
|
||||
): void => {
|
||||
const isHtml = isDefined(component.htmlTag);
|
||||
const useShared = useSharedEvents && isHtml;
|
||||
const hasEvents = component.events.length > 0;
|
||||
const hasCommonHtmlEvents = commonEventNames.size > 0 && isHtml;
|
||||
const customEvents = component.events.filter(
|
||||
(event) => !hasCommonHtmlEvents || !commonEventNames.has(event),
|
||||
);
|
||||
const hasEvents = hasCommonHtmlEvents || customEvents.length > 0;
|
||||
const hasSpecificProps = Object.keys(specificProperties).length > 0;
|
||||
const hasProps = Object.keys(component.properties).length > 0;
|
||||
|
||||
@@ -177,11 +180,19 @@ const generateElementDefinition = (
|
||||
? TYPE_NAMES.COMMON_PROPERTIES
|
||||
: TYPE_NAMES.EMPTY_RECORD;
|
||||
|
||||
const eventsType = hasEvents
|
||||
? useShared
|
||||
? TYPE_NAMES.COMMON_EVENTS
|
||||
: `{ ${component.events.map((event) => `${event}(event: RemoteEvent<SerializedEventData>): void`).join('; ')} }`
|
||||
: TYPE_NAMES.EMPTY_RECORD;
|
||||
const customEventsInline = customEvents
|
||||
.map((event) => `${event}(event: RemoteEvent<SerializedEventData>): void`)
|
||||
.join('; ');
|
||||
|
||||
let eventsType: string = TYPE_NAMES.EMPTY_RECORD;
|
||||
|
||||
if (hasCommonHtmlEvents && customEvents.length > 0) {
|
||||
eventsType = `${TYPE_NAMES.COMMON_EVENTS} & { ${customEventsInline} }`;
|
||||
} else if (hasCommonHtmlEvents) {
|
||||
eventsType = TYPE_NAMES.COMMON_EVENTS;
|
||||
} else if (customEvents.length > 0) {
|
||||
eventsType = `{ ${customEventsInline} }`;
|
||||
}
|
||||
|
||||
sourceFile.addVariableStatement({
|
||||
isExported: true,
|
||||
@@ -220,7 +231,7 @@ const generateElementDefinition = (
|
||||
});
|
||||
writer.write(',');
|
||||
writer.newLine();
|
||||
} else if (useSharedPropertiesConfig && isHtml) {
|
||||
} else if (shouldUseCommonHtmlPropertiesConfig && isHtml) {
|
||||
writer.write(
|
||||
`properties: ${TYPE_NAMES.COMMON_PROPERTIES_CONFIG},`,
|
||||
);
|
||||
@@ -235,10 +246,16 @@ const generateElementDefinition = (
|
||||
}
|
||||
}
|
||||
if (hasEvents) {
|
||||
const formattedCustomEvents = customEvents
|
||||
.map((event) => `'${event}'`)
|
||||
.join(', ');
|
||||
|
||||
writer.write(
|
||||
useShared
|
||||
? `events: [...${TYPE_NAMES.COMMON_EVENTS_ARRAY}],`
|
||||
: `events: [${component.events.map((event) => `'${event}'`).join(', ')}],`,
|
||||
hasCommonHtmlEvents && customEvents.length > 0
|
||||
? `events: [...${TYPE_NAMES.COMMON_EVENTS_ARRAY}, ${formattedCustomEvents}],`
|
||||
: hasCommonHtmlEvents
|
||||
? `events: [...${TYPE_NAMES.COMMON_EVENTS_ARRAY}],`
|
||||
: `events: [${formattedCustomEvents}],`,
|
||||
);
|
||||
writer.newLine();
|
||||
}
|
||||
@@ -304,8 +321,9 @@ export const generateRemoteElements = (
|
||||
overwrite: true,
|
||||
});
|
||||
|
||||
const useSharedEvents = commonEvents.length > 0;
|
||||
const useSharedPropertiesConfig = Object.keys(commonProperties).length > 0;
|
||||
const commonEventNames = new Set(commonEvents);
|
||||
const shouldUseCommonHtmlPropertiesConfig =
|
||||
Object.keys(commonProperties).length > 0;
|
||||
|
||||
sourceFile.addImportDeclaration({
|
||||
moduleSpecifier: '@remote-dom/core/elements',
|
||||
@@ -327,11 +345,11 @@ export const generateRemoteElements = (
|
||||
|
||||
generateCommonPropertiesType(sourceFile, commonProperties);
|
||||
|
||||
if (useSharedEvents) {
|
||||
if (commonEventNames.size > 0) {
|
||||
generateCommonEventsType(sourceFile, commonEvents);
|
||||
}
|
||||
|
||||
if (useSharedPropertiesConfig) {
|
||||
if (shouldUseCommonHtmlPropertiesConfig) {
|
||||
generateCommonPropertiesConfig(sourceFile, commonProperties);
|
||||
}
|
||||
|
||||
@@ -345,8 +363,8 @@ export const generateRemoteElements = (
|
||||
sourceFile,
|
||||
component,
|
||||
specificProperties,
|
||||
useSharedEvents,
|
||||
useSharedPropertiesConfig,
|
||||
commonEventNames,
|
||||
shouldUseCommonHtmlPropertiesConfig,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ export const HtmlElementConfigZ = z.object({
|
||||
.string()
|
||||
.regex(/^Html[A-Z]/, 'Name must be PascalCase starting with Html'),
|
||||
properties: z.record(z.string(), PropertySchemaZ),
|
||||
events: z.array(z.string()).optional(),
|
||||
});
|
||||
|
||||
export const HtmlElementConfigArrayZ = z.array(HtmlElementConfigZ);
|
||||
|
||||
@@ -108,6 +108,27 @@ const serializeEvent = (event: unknown): SerializedEventData => {
|
||||
if ('scrollLeft' in target && typeof target.scrollLeft === 'number') {
|
||||
serialized.scrollLeft = target.scrollLeft;
|
||||
}
|
||||
if ('currentTime' in target && typeof target.currentTime === 'number') {
|
||||
serialized.currentTime = target.currentTime;
|
||||
}
|
||||
if ('duration' in target && typeof target.duration === 'number') {
|
||||
serialized.duration = target.duration;
|
||||
}
|
||||
if ('paused' in target && typeof target.paused === 'boolean') {
|
||||
serialized.paused = target.paused;
|
||||
}
|
||||
if ('ended' in target && typeof target.ended === 'boolean') {
|
||||
serialized.ended = target.ended;
|
||||
}
|
||||
if ('volume' in target && typeof target.volume === 'number') {
|
||||
serialized.volume = target.volume;
|
||||
}
|
||||
if ('muted' in target && typeof target.muted === 'boolean') {
|
||||
serialized.muted = target.muted;
|
||||
}
|
||||
if ('playbackRate' in target && typeof target.playbackRate === 'number') {
|
||||
serialized.playbackRate = target.playbackRate;
|
||||
}
|
||||
}
|
||||
|
||||
return serialized;
|
||||
|
||||
+38
@@ -1172,6 +1172,25 @@ export const HtmlVideo = createRemoteComponent('html-video', HtmlVideoElement, {
|
||||
onWheel: { event: 'wheel' },
|
||||
onContextMenu: { event: 'contextmenu' },
|
||||
onDrag: { event: 'drag' },
|
||||
onTimeUpdate: { event: 'timeupdate' },
|
||||
onPlay: { event: 'play' },
|
||||
onPause: { event: 'pause' },
|
||||
onEnded: { event: 'ended' },
|
||||
onLoadedMetadata: { event: 'loadedmetadata' },
|
||||
onLoadedData: { event: 'loadeddata' },
|
||||
onVolumeChange: { event: 'volumechange' },
|
||||
onSeeking: { event: 'seeking' },
|
||||
onSeeked: { event: 'seeked' },
|
||||
onError: { event: 'error' },
|
||||
onCanPlay: { event: 'canplay' },
|
||||
onCanPlayThrough: { event: 'canplaythrough' },
|
||||
onWaiting: { event: 'waiting' },
|
||||
onProgress: { event: 'progress' },
|
||||
onDurationChange: { event: 'durationchange' },
|
||||
onRateChange: { event: 'ratechange' },
|
||||
onStalled: { event: 'stalled' },
|
||||
onSuspend: { event: 'suspend' },
|
||||
onEmptied: { event: 'emptied' },
|
||||
},
|
||||
});
|
||||
export const HtmlAudio = createRemoteComponent('html-audio', HtmlAudioElement, {
|
||||
@@ -1196,6 +1215,25 @@ export const HtmlAudio = createRemoteComponent('html-audio', HtmlAudioElement, {
|
||||
onWheel: { event: 'wheel' },
|
||||
onContextMenu: { event: 'contextmenu' },
|
||||
onDrag: { event: 'drag' },
|
||||
onTimeUpdate: { event: 'timeupdate' },
|
||||
onPlay: { event: 'play' },
|
||||
onPause: { event: 'pause' },
|
||||
onEnded: { event: 'ended' },
|
||||
onLoadedMetadata: { event: 'loadedmetadata' },
|
||||
onLoadedData: { event: 'loadeddata' },
|
||||
onVolumeChange: { event: 'volumechange' },
|
||||
onSeeking: { event: 'seeking' },
|
||||
onSeeked: { event: 'seeked' },
|
||||
onError: { event: 'error' },
|
||||
onCanPlay: { event: 'canplay' },
|
||||
onCanPlayThrough: { event: 'canplaythrough' },
|
||||
onWaiting: { event: 'waiting' },
|
||||
onProgress: { event: 'progress' },
|
||||
onDurationChange: { event: 'durationchange' },
|
||||
onRateChange: { event: 'ratechange' },
|
||||
onStalled: { event: 'stalled' },
|
||||
onSuspend: { event: 'suspend' },
|
||||
onEmptied: { event: 'emptied' },
|
||||
},
|
||||
});
|
||||
export const HtmlSource = createRemoteComponent(
|
||||
|
||||
+106
-4
@@ -603,11 +603,15 @@ export const HtmlHrElement = createRemoteElement<
|
||||
|
||||
export type HtmlIframeProperties = HtmlCommonProperties & {
|
||||
src?: string;
|
||||
name?: string;
|
||||
width?: string;
|
||||
height?: string;
|
||||
allow?: string;
|
||||
sandbox?: string;
|
||||
allowFullScreen?: boolean;
|
||||
loading?: string;
|
||||
referrerPolicy?: string;
|
||||
srcDoc?: string;
|
||||
};
|
||||
|
||||
export const HtmlIframeElement = createRemoteElement<
|
||||
@@ -619,11 +623,15 @@ export const HtmlIframeElement = createRemoteElement<
|
||||
properties: {
|
||||
...HTML_COMMON_PROPERTIES_CONFIG,
|
||||
src: { type: String },
|
||||
name: { type: String },
|
||||
width: { type: String },
|
||||
height: { type: String },
|
||||
allow: { type: String },
|
||||
sandbox: { type: String },
|
||||
allowFullScreen: { type: Boolean },
|
||||
loading: { type: String },
|
||||
referrerPolicy: { type: String },
|
||||
srcDoc: { type: String },
|
||||
},
|
||||
events: [...HTML_COMMON_EVENTS_ARRAY],
|
||||
});
|
||||
@@ -640,13 +648,35 @@ export type HtmlVideoProperties = HtmlCommonProperties & {
|
||||
height?: string;
|
||||
crossOrigin?: string;
|
||||
playsInline?: boolean;
|
||||
disablePictureInPicture?: boolean;
|
||||
disableRemotePlayback?: boolean;
|
||||
};
|
||||
|
||||
export const HtmlVideoElement = createRemoteElement<
|
||||
HtmlVideoProperties,
|
||||
Record<string, never>,
|
||||
Record<string, never>,
|
||||
HtmlCommonEvents
|
||||
HtmlCommonEvents & {
|
||||
timeupdate(event: RemoteEvent<SerializedEventData>): void;
|
||||
play(event: RemoteEvent<SerializedEventData>): void;
|
||||
pause(event: RemoteEvent<SerializedEventData>): void;
|
||||
ended(event: RemoteEvent<SerializedEventData>): void;
|
||||
loadedmetadata(event: RemoteEvent<SerializedEventData>): void;
|
||||
loadeddata(event: RemoteEvent<SerializedEventData>): void;
|
||||
volumechange(event: RemoteEvent<SerializedEventData>): void;
|
||||
seeking(event: RemoteEvent<SerializedEventData>): void;
|
||||
seeked(event: RemoteEvent<SerializedEventData>): void;
|
||||
error(event: RemoteEvent<SerializedEventData>): void;
|
||||
canplay(event: RemoteEvent<SerializedEventData>): void;
|
||||
canplaythrough(event: RemoteEvent<SerializedEventData>): void;
|
||||
waiting(event: RemoteEvent<SerializedEventData>): void;
|
||||
progress(event: RemoteEvent<SerializedEventData>): void;
|
||||
durationchange(event: RemoteEvent<SerializedEventData>): void;
|
||||
ratechange(event: RemoteEvent<SerializedEventData>): void;
|
||||
stalled(event: RemoteEvent<SerializedEventData>): void;
|
||||
suspend(event: RemoteEvent<SerializedEventData>): void;
|
||||
emptied(event: RemoteEvent<SerializedEventData>): void;
|
||||
}
|
||||
>({
|
||||
properties: {
|
||||
...HTML_COMMON_PROPERTIES_CONFIG,
|
||||
@@ -661,8 +691,31 @@ export const HtmlVideoElement = createRemoteElement<
|
||||
height: { type: String },
|
||||
crossOrigin: { type: String },
|
||||
playsInline: { type: Boolean },
|
||||
disablePictureInPicture: { type: Boolean },
|
||||
disableRemotePlayback: { type: Boolean },
|
||||
},
|
||||
events: [...HTML_COMMON_EVENTS_ARRAY],
|
||||
events: [
|
||||
...HTML_COMMON_EVENTS_ARRAY,
|
||||
'timeupdate',
|
||||
'play',
|
||||
'pause',
|
||||
'ended',
|
||||
'loadedmetadata',
|
||||
'loadeddata',
|
||||
'volumechange',
|
||||
'seeking',
|
||||
'seeked',
|
||||
'error',
|
||||
'canplay',
|
||||
'canplaythrough',
|
||||
'waiting',
|
||||
'progress',
|
||||
'durationchange',
|
||||
'ratechange',
|
||||
'stalled',
|
||||
'suspend',
|
||||
'emptied',
|
||||
],
|
||||
});
|
||||
|
||||
export type HtmlAudioProperties = HtmlCommonProperties & {
|
||||
@@ -679,7 +732,27 @@ export const HtmlAudioElement = createRemoteElement<
|
||||
HtmlAudioProperties,
|
||||
Record<string, never>,
|
||||
Record<string, never>,
|
||||
HtmlCommonEvents
|
||||
HtmlCommonEvents & {
|
||||
timeupdate(event: RemoteEvent<SerializedEventData>): void;
|
||||
play(event: RemoteEvent<SerializedEventData>): void;
|
||||
pause(event: RemoteEvent<SerializedEventData>): void;
|
||||
ended(event: RemoteEvent<SerializedEventData>): void;
|
||||
loadedmetadata(event: RemoteEvent<SerializedEventData>): void;
|
||||
loadeddata(event: RemoteEvent<SerializedEventData>): void;
|
||||
volumechange(event: RemoteEvent<SerializedEventData>): void;
|
||||
seeking(event: RemoteEvent<SerializedEventData>): void;
|
||||
seeked(event: RemoteEvent<SerializedEventData>): void;
|
||||
error(event: RemoteEvent<SerializedEventData>): void;
|
||||
canplay(event: RemoteEvent<SerializedEventData>): void;
|
||||
canplaythrough(event: RemoteEvent<SerializedEventData>): void;
|
||||
waiting(event: RemoteEvent<SerializedEventData>): void;
|
||||
progress(event: RemoteEvent<SerializedEventData>): void;
|
||||
durationchange(event: RemoteEvent<SerializedEventData>): void;
|
||||
ratechange(event: RemoteEvent<SerializedEventData>): void;
|
||||
stalled(event: RemoteEvent<SerializedEventData>): void;
|
||||
suspend(event: RemoteEvent<SerializedEventData>): void;
|
||||
emptied(event: RemoteEvent<SerializedEventData>): void;
|
||||
}
|
||||
>({
|
||||
properties: {
|
||||
...HTML_COMMON_PROPERTIES_CONFIG,
|
||||
@@ -691,13 +764,38 @@ export const HtmlAudioElement = createRemoteElement<
|
||||
preload: { type: String },
|
||||
crossOrigin: { type: String },
|
||||
},
|
||||
events: [...HTML_COMMON_EVENTS_ARRAY],
|
||||
events: [
|
||||
...HTML_COMMON_EVENTS_ARRAY,
|
||||
'timeupdate',
|
||||
'play',
|
||||
'pause',
|
||||
'ended',
|
||||
'loadedmetadata',
|
||||
'loadeddata',
|
||||
'volumechange',
|
||||
'seeking',
|
||||
'seeked',
|
||||
'error',
|
||||
'canplay',
|
||||
'canplaythrough',
|
||||
'waiting',
|
||||
'progress',
|
||||
'durationchange',
|
||||
'ratechange',
|
||||
'stalled',
|
||||
'suspend',
|
||||
'emptied',
|
||||
],
|
||||
});
|
||||
|
||||
export type HtmlSourceProperties = HtmlCommonProperties & {
|
||||
src?: string;
|
||||
type?: string;
|
||||
media?: string;
|
||||
srcSet?: string;
|
||||
sizes?: string;
|
||||
width?: number;
|
||||
height?: number;
|
||||
};
|
||||
|
||||
export const HtmlSourceElement = createRemoteElement<
|
||||
@@ -711,6 +809,10 @@ export const HtmlSourceElement = createRemoteElement<
|
||||
src: { type: String },
|
||||
type: { type: String },
|
||||
media: { type: String },
|
||||
srcSet: { type: String },
|
||||
sizes: { type: String },
|
||||
width: { type: Number },
|
||||
height: { type: Number },
|
||||
},
|
||||
events: [...HTML_COMMON_EVENTS_ARRAY],
|
||||
});
|
||||
|
||||
+3
@@ -9,6 +9,9 @@ const ATTRIBUTE_TO_PROPERTY_MAP: Record<string, string> = {
|
||||
|
||||
tabIndex: 'tabIndex',
|
||||
tabindex: 'tabIndex',
|
||||
|
||||
srcDoc: 'srcDoc',
|
||||
srcdoc: 'srcDoc',
|
||||
};
|
||||
|
||||
export const patchRemoteElementSetAttribute = (): void => {
|
||||
|
||||
@@ -7,6 +7,7 @@ export type AllowedHtmlElement = {
|
||||
tag: string;
|
||||
name: string;
|
||||
properties: Record<string, PropertySchema>;
|
||||
events?: string[];
|
||||
};
|
||||
|
||||
export const ALLOWED_HTML_ELEMENTS: AllowedHtmlElement[] = [
|
||||
@@ -150,12 +151,15 @@ export const ALLOWED_HTML_ELEMENTS: AllowedHtmlElement[] = [
|
||||
name: 'HtmlIframe',
|
||||
properties: {
|
||||
src: { type: 'string', optional: true },
|
||||
title: { type: 'string', optional: true },
|
||||
name: { type: 'string', optional: true },
|
||||
width: { type: 'string', optional: true },
|
||||
height: { type: 'string', optional: true },
|
||||
allow: { type: 'string', optional: true },
|
||||
sandbox: { type: 'string', optional: true },
|
||||
allowFullScreen: { type: 'boolean', optional: true },
|
||||
loading: { type: 'string', optional: true },
|
||||
referrerPolicy: { type: 'string', optional: true },
|
||||
srcDoc: { type: 'string', optional: true },
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -173,7 +177,30 @@ export const ALLOWED_HTML_ELEMENTS: AllowedHtmlElement[] = [
|
||||
height: { type: 'string', optional: true },
|
||||
crossOrigin: { type: 'string', optional: true },
|
||||
playsInline: { type: 'boolean', optional: true },
|
||||
disablePictureInPicture: { type: 'boolean', optional: true },
|
||||
disableRemotePlayback: { type: 'boolean', optional: true },
|
||||
},
|
||||
events: [
|
||||
'timeupdate',
|
||||
'play',
|
||||
'pause',
|
||||
'ended',
|
||||
'loadedmetadata',
|
||||
'loadeddata',
|
||||
'volumechange',
|
||||
'seeking',
|
||||
'seeked',
|
||||
'error',
|
||||
'canplay',
|
||||
'canplaythrough',
|
||||
'waiting',
|
||||
'progress',
|
||||
'durationchange',
|
||||
'ratechange',
|
||||
'stalled',
|
||||
'suspend',
|
||||
'emptied',
|
||||
],
|
||||
},
|
||||
{
|
||||
tag: 'html-audio',
|
||||
@@ -187,6 +214,27 @@ export const ALLOWED_HTML_ELEMENTS: AllowedHtmlElement[] = [
|
||||
preload: { type: 'string', optional: true },
|
||||
crossOrigin: { type: 'string', optional: true },
|
||||
},
|
||||
events: [
|
||||
'timeupdate',
|
||||
'play',
|
||||
'pause',
|
||||
'ended',
|
||||
'loadedmetadata',
|
||||
'loadeddata',
|
||||
'volumechange',
|
||||
'seeking',
|
||||
'seeked',
|
||||
'error',
|
||||
'canplay',
|
||||
'canplaythrough',
|
||||
'waiting',
|
||||
'progress',
|
||||
'durationchange',
|
||||
'ratechange',
|
||||
'stalled',
|
||||
'suspend',
|
||||
'emptied',
|
||||
],
|
||||
},
|
||||
{
|
||||
tag: 'html-source',
|
||||
@@ -195,6 +243,10 @@ export const ALLOWED_HTML_ELEMENTS: AllowedHtmlElement[] = [
|
||||
src: { type: 'string', optional: true },
|
||||
type: { type: 'string', optional: true },
|
||||
media: { type: 'string', optional: true },
|
||||
srcSet: { type: 'string', optional: true },
|
||||
sizes: { type: 'string', optional: true },
|
||||
width: { type: 'number', optional: true },
|
||||
height: { type: 'number', optional: true },
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
@@ -19,4 +19,23 @@ export const EVENT_TO_REACT: Record<string, string> = {
|
||||
wheel: 'onWheel',
|
||||
contextmenu: 'onContextMenu',
|
||||
drag: 'onDrag',
|
||||
timeupdate: 'onTimeUpdate',
|
||||
play: 'onPlay',
|
||||
pause: 'onPause',
|
||||
ended: 'onEnded',
|
||||
loadedmetadata: 'onLoadedMetadata',
|
||||
loadeddata: 'onLoadedData',
|
||||
volumechange: 'onVolumeChange',
|
||||
seeking: 'onSeeking',
|
||||
seeked: 'onSeeked',
|
||||
error: 'onError',
|
||||
canplay: 'onCanPlay',
|
||||
canplaythrough: 'onCanPlayThrough',
|
||||
waiting: 'onWaiting',
|
||||
progress: 'onProgress',
|
||||
durationchange: 'onDurationChange',
|
||||
ratechange: 'onRateChange',
|
||||
stalled: 'onStalled',
|
||||
suspend: 'onSuspend',
|
||||
emptied: 'onEmptied',
|
||||
};
|
||||
|
||||
@@ -23,4 +23,11 @@ export type SerializedEventData = {
|
||||
deltaY?: number;
|
||||
deltaZ?: number;
|
||||
deltaMode?: number;
|
||||
currentTime?: number;
|
||||
duration?: number;
|
||||
paused?: boolean;
|
||||
ended?: boolean;
|
||||
volume?: number;
|
||||
muted?: boolean;
|
||||
playbackRate?: number;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user