feat(html): add slider thumbnail element (#714)

This commit is contained in:
rahim
2026-03-04 15:35:27 -08:00
committed by GitHub
parent 5c422452e4
commit 32ee304da4
5 changed files with 128 additions and 0 deletions
@@ -0,0 +1,10 @@
import { SliderThumbnailElement } from '../../ui/slider/slider-thumbnail-element';
import { safeDefine } from '../safe-define';
safeDefine(SliderThumbnailElement);
declare global {
interface HTMLElementTagNameMap {
[SliderThumbnailElement.tagName]: SliderThumbnailElement;
}
}
@@ -1,6 +1,7 @@
import { SliderBufferElement } from '../../ui/slider/slider-buffer-element';
import { SliderFillElement } from '../../ui/slider/slider-fill-element';
import { SliderThumbElement } from '../../ui/slider/slider-thumb-element';
import { SliderThumbnailElement } from '../../ui/slider/slider-thumbnail-element';
import { SliderTrackElement } from '../../ui/slider/slider-track-element';
import { SliderValueElement } from '../../ui/slider/slider-value-element';
import { TimeSliderElement } from '../../ui/time-slider/time-slider-element';
@@ -11,6 +12,7 @@ safeDefine(TimeSliderElement);
safeDefine(SliderBufferElement);
safeDefine(SliderFillElement);
safeDefine(SliderThumbElement);
safeDefine(SliderThumbnailElement);
safeDefine(SliderTrackElement);
safeDefine(SliderValueElement);
+1
View File
@@ -35,6 +35,7 @@ export { SliderElement } from './ui/slider/slider-element';
export type { SliderEventMap, SliderValueEventDetail } from './ui/slider/slider-events';
export { SliderFillElement } from './ui/slider/slider-fill-element';
export { SliderThumbElement } from './ui/slider/slider-thumb-element';
export { SliderThumbnailElement } from './ui/slider/slider-thumbnail-element';
export { SliderTrackElement } from './ui/slider/slider-track-element';
export { SliderValueElement } from './ui/slider/slider-value-element';
export { ThumbnailElement } from './ui/thumbnail/thumbnail-element';
@@ -0,0 +1,21 @@
import type { PropertyValues } from '@videojs/element';
import { ContextConsumer } from '@videojs/element/context';
import { ThumbnailElement } from '../thumbnail/thumbnail-element';
import { sliderContext } from './context';
// @ts-expect-error TS2417 — tagName narrows to a different literal for custom element registration.
export class SliderThumbnailElement extends ThumbnailElement {
static override readonly tagName = 'media-slider-thumbnail';
readonly #ctx = new ContextConsumer(this, {
context: sliderContext,
subscribe: true,
});
protected override update(changed: PropertyValues): void {
const ctx = this.#ctx.value;
if (ctx) this.time = ctx.pointerValue;
super.update(changed);
}
}
@@ -0,0 +1,94 @@
import { afterEach, describe, expect, it } from 'vitest';
import { ThumbnailElement } from '../../thumbnail/thumbnail-element';
import { SliderElement } from '../slider-element';
import { SliderThumbnailElement } from '../slider-thumbnail-element';
let tagCounter = 0;
function uniqueTag(base: string): string {
return `${base}-${tagCounter++}`;
}
function createElement<Element extends HTMLElement>(Base: abstract new () => Element): Element {
const tag = uniqueTag('test-slt');
customElements.define(tag, class extends (Base as unknown as typeof HTMLElement) {});
return document.createElement(tag) as Element;
}
afterEach(() => {
document.body.innerHTML = '';
});
describe('SliderThumbnailElement', () => {
it('has the correct tag name', () => {
expect(SliderThumbnailElement.tagName).toBe('media-slider-thumbnail');
});
it('extends ThumbnailElement', () => {
const el = createElement(SliderThumbnailElement);
expect(el).toBeInstanceOf(ThumbnailElement);
});
it('has a shadow root with an img element', () => {
const el = createElement(SliderThumbnailElement);
expect(el.shadowRoot).toBeTruthy();
const img = el.shadowRoot!.querySelector('img');
expect(img).toBeTruthy();
expect(img!.getAttribute('aria-hidden')).toBe('true');
});
it('inherits time property from ThumbnailElement', () => {
const el = createElement(SliderThumbnailElement);
expect(el.time).toBe(0);
el.time = 10;
expect(el.time).toBe(10);
});
it('sets data-hidden when no thumbnails are available', async () => {
const el = createElement(SliderThumbnailElement);
document.body.appendChild(el);
await el.updateComplete;
expect(el.hasAttribute('data-hidden')).toBe(true);
});
it('reads pointerValue from slider context as time', async () => {
const slider = createElement(SliderElement);
const thumbnail = createElement(SliderThumbnailElement);
thumbnail.thumbnails = [
{ url: 'thumb-0.jpg', startTime: 0 },
{ url: 'thumb-30.jpg', startTime: 30 },
{ url: 'thumb-60.jpg', startTime: 60 },
];
slider.appendChild(thumbnail);
document.body.appendChild(slider);
await slider.updateComplete;
await thumbnail.updateComplete;
// In idle state, pointerPercent=0 → pointerValue=0 → selects 'thumb-0.jpg'.
const img = thumbnail.shadowRoot!.querySelector('img');
expect(img!.getAttribute('src')).toBe('thumb-0.jpg');
});
it('does not have data-hidden when thumbnails match', async () => {
const slider = createElement(SliderElement);
const thumbnail = createElement(SliderThumbnailElement);
thumbnail.thumbnails = [{ url: 'thumb.jpg', startTime: 0 }];
slider.appendChild(thumbnail);
document.body.appendChild(slider);
await slider.updateComplete;
await thumbnail.updateComplete;
expect(thumbnail.hasAttribute('data-hidden')).toBe(false);
});
});