mirror of
https://github.com/zoriya/v10.git
synced 2026-08-15 18:34:22 +00:00
feat(html): add <media-gesture> element (#1305)
This commit is contained in:
@@ -137,7 +137,7 @@ function getTemplateHTML() {
|
||||
<media-hotkey keys="m" action="toggleMuted"></media-hotkey>
|
||||
<media-hotkey keys="f" action="toggleFullscreen"></media-hotkey>
|
||||
<media-hotkey keys="c" action="toggleSubtitles"></media-hotkey>
|
||||
<media-hotkey keys="i" action="togglePiP"></media-hotkey>
|
||||
<media-hotkey keys="i" action="togglePictureInPicture"></media-hotkey>
|
||||
<media-hotkey keys="ArrowRight" action="seekStep" value="5"></media-hotkey>
|
||||
<media-hotkey keys="ArrowLeft" action="seekStep" value="-5"></media-hotkey>
|
||||
<media-hotkey keys="l" action="seekStep" value="10"></media-hotkey>
|
||||
|
||||
@@ -28,6 +28,7 @@ export { ControlsElement } from './ui/controls/controls-element';
|
||||
export { ControlsGroupElement } from './ui/controls/controls-group-element';
|
||||
export { ErrorDialogElement } from './ui/error-dialog/error-dialog-element';
|
||||
export { FullscreenButtonElement } from './ui/fullscreen-button/fullscreen-button-element';
|
||||
export { GestureElement } from './ui/gesture/gesture-element';
|
||||
export { AriaKeyShortcutsController } from './ui/hotkey/aria-key-shortcuts-controller';
|
||||
export { HotkeyElement } from './ui/hotkey/hotkey-element';
|
||||
export { MediaButtonElement } from './ui/media-button-element';
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
import {
|
||||
createDoubleTapGesture,
|
||||
createTapGesture,
|
||||
type GestureActionName,
|
||||
type GesturePointerType,
|
||||
type GestureRegion,
|
||||
resolveGestureAction,
|
||||
} from '@videojs/core/dom';
|
||||
import type { PropertyDeclarationMap, PropertyValues } from '@videojs/element';
|
||||
import { ContextConsumer } from '@videojs/element/context';
|
||||
|
||||
import { containerContext, playerContext } from '../../player/context';
|
||||
import { PlayerController } from '../../player/player-controller';
|
||||
import { MediaElement } from '../media-element';
|
||||
|
||||
export class GestureElement extends MediaElement {
|
||||
static readonly tagName = 'media-gesture';
|
||||
|
||||
static override properties: PropertyDeclarationMap = {
|
||||
type: { type: String },
|
||||
action: { type: String },
|
||||
value: { type: Number },
|
||||
pointer: { type: String },
|
||||
region: { type: String },
|
||||
disabled: { type: Boolean },
|
||||
};
|
||||
|
||||
type: 'tap' | 'doubletap' | (string & {}) = '';
|
||||
action: GestureActionName | (string & {}) = '';
|
||||
value: number | undefined = undefined;
|
||||
pointer: GesturePointerType | undefined = undefined;
|
||||
region: GestureRegion | undefined = undefined;
|
||||
disabled = false;
|
||||
|
||||
readonly #player = new PlayerController(this, playerContext);
|
||||
readonly #container = new ContextConsumer(this, {
|
||||
context: containerContext,
|
||||
callback: () => this.requestUpdate(),
|
||||
subscribe: true,
|
||||
});
|
||||
#cleanup: (() => void) | null = null;
|
||||
|
||||
override connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
this.style.display = 'none';
|
||||
this.#register();
|
||||
}
|
||||
|
||||
override disconnectedCallback(): void {
|
||||
super.disconnectedCallback();
|
||||
this.#unregister();
|
||||
}
|
||||
|
||||
protected override update(changed: PropertyValues): void {
|
||||
super.update(changed);
|
||||
|
||||
// Re-register when attributes change.
|
||||
if (this.isConnected) {
|
||||
this.#unregister();
|
||||
this.#register();
|
||||
}
|
||||
}
|
||||
|
||||
#register(): void {
|
||||
const store = this.#player.value;
|
||||
const container = this.#container.value?.container;
|
||||
if (!this.type || !this.action || !store || !container) return;
|
||||
|
||||
const resolver = resolveGestureAction(this.action);
|
||||
if (!resolver) return;
|
||||
|
||||
const { value } = this;
|
||||
|
||||
const onActivate = (event: PointerEvent) => {
|
||||
resolver({ store, value, event });
|
||||
};
|
||||
|
||||
const options = {
|
||||
pointer: this.pointer,
|
||||
region: this.region,
|
||||
disabled: this.disabled,
|
||||
};
|
||||
|
||||
if (this.type === 'doubletap') {
|
||||
this.#cleanup = createDoubleTapGesture(container, onActivate, options);
|
||||
} else {
|
||||
this.#cleanup = createTapGesture(container, onActivate, options);
|
||||
}
|
||||
}
|
||||
|
||||
#unregister(): void {
|
||||
this.#cleanup?.();
|
||||
this.#cleanup = null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
import { afterEach, beforeAll, describe, expect, it } from 'vitest';
|
||||
|
||||
import { GestureElement } from '../gesture-element';
|
||||
|
||||
beforeAll(() => {
|
||||
customElements.define('media-gesture', GestureElement);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
document.body.innerHTML = '';
|
||||
});
|
||||
|
||||
describe('GestureElement', () => {
|
||||
it('has the correct tag name', () => {
|
||||
expect(GestureElement.tagName).toBe('media-gesture');
|
||||
});
|
||||
|
||||
it('declares expected properties', () => {
|
||||
const props = GestureElement.properties;
|
||||
expect(props).toHaveProperty('type');
|
||||
expect(props).toHaveProperty('action');
|
||||
expect(props).toHaveProperty('value');
|
||||
expect(props).toHaveProperty('pointer');
|
||||
expect(props).toHaveProperty('region');
|
||||
expect(props).toHaveProperty('disabled');
|
||||
});
|
||||
|
||||
it('initializes with default property values', () => {
|
||||
const el = document.createElement('media-gesture') as GestureElement;
|
||||
expect(el.type).toBe('');
|
||||
expect(el.action).toBe('');
|
||||
expect(el.value).toBeUndefined();
|
||||
expect(el.pointer).toBeUndefined();
|
||||
expect(el.region).toBeUndefined();
|
||||
expect(el.disabled).toBe(false);
|
||||
});
|
||||
|
||||
it('is hidden when connected', () => {
|
||||
const el = document.createElement('media-gesture') as GestureElement;
|
||||
document.body.appendChild(el);
|
||||
expect(el.style.display).toBe('none');
|
||||
});
|
||||
});
|
||||
@@ -11,7 +11,7 @@ export class PiPButtonElement extends MediaButtonElement<PiPButtonCore> {
|
||||
protected readonly core = new PiPButtonCore();
|
||||
protected readonly stateAttrMap = PiPButtonDataAttrs;
|
||||
protected readonly mediaState = new PlayerController(this, playerContext, selectPiP);
|
||||
protected override readonly hotkeyAction = 'togglePiP';
|
||||
protected override readonly hotkeyAction = 'togglePictureInPicture';
|
||||
|
||||
protected activate(state: MediaPictureInPictureState): void {
|
||||
this.core.toggle(state);
|
||||
|
||||
Reference in New Issue
Block a user