From 082c7b8433cda61bd4e4e16691503a297148db7f Mon Sep 17 00:00:00 2001 From: Ezequiel Manzanares Date: Fri, 27 Feb 2026 13:58:30 +0100 Subject: [PATCH] Add config params mapping for Wistia --- examples/nextjs/app/wistia-video/page.tsx | 3 + packages/wistia-video-element/test/test.js | 48 +++++++++++++++- .../wistia-video-element.d.ts | 55 +++++++++++++++++-- .../wistia-video-element.js | 35 +++++++++++- 4 files changed, 133 insertions(+), 8 deletions(-) diff --git a/examples/nextjs/app/wistia-video/page.tsx b/examples/nextjs/app/wistia-video/page.tsx index 4c140cff..c569555c 100644 --- a/examples/nextjs/app/wistia-video/page.tsx +++ b/examples/nextjs/app/wistia-video/page.tsx @@ -13,6 +13,9 @@ export default function Page() { diff --git a/packages/wistia-video-element/test/test.js b/packages/wistia-video-element/test/test.js index 54e4b2d1..7aff4979 100644 --- a/packages/wistia-video-element/test/test.js +++ b/packages/wistia-video-element/test/test.js @@ -61,7 +61,7 @@ test('load promise', async function (t) { await video.loadComplete; t.ok( - loadComplete != video.loadComplete, + loadComplete !== video.loadComplete, 'creates a new promise after new src' ); @@ -86,6 +86,52 @@ test('play promise', async function (t) { t.ok(!video.paused, 'is playing after video.play()'); }); +test('passes config into Wistia options', async function (t) { + const previousWistia = globalThis.Wistia; + const previousWq = globalThis._wq; + + try { + globalThis.Wistia = {}; + + const fakeApi = { + elem: () => document.createElement('video'), + duration: () => 0, + play: () => {}, + bigPlayButtonEnabled: () => {}, + releaseChromeless: () => {}, + requestChromeless: () => {}, + }; + + let pushed; + globalThis._wq = { + push: (payload) => { + pushed = payload; + payload.onReady(fakeApi); + }, + }; + + const video = await fixture(``); + + // Ensure user config takes precedence over element-derived defaults. + video.config = { + chromeless: true, + playButton: false, + playerColor: 'ff0000', + }; + + video.src = 'https://wesleyluyten.wistia.com/medias/oifkgmxnkb'; + await video.loadComplete; + + t.ok(pushed?.options, 'push payload includes options'); + t.equal(pushed.options.chromeless, true, 'config overrides default chromeless'); + t.equal(pushed.options.playButton, false, 'config overrides default playButton'); + t.equal(pushed.options.playerColor, 'ff0000', 'config is forwarded into options'); + } finally { + globalThis.Wistia = previousWistia; + globalThis._wq = previousWq; + } +}); + function delay(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); } diff --git a/packages/wistia-video-element/wistia-video-element.d.ts b/packages/wistia-video-element/wistia-video-element.d.ts index 67fc775e..182ecf57 100644 --- a/packages/wistia-video-element/wistia-video-element.d.ts +++ b/packages/wistia-video-element/wistia-video-element.d.ts @@ -1,10 +1,55 @@ +export type WistiaPluginConfig = Record; + +export type WistiaEmbedOptions = { + autoPlay?: boolean; + controlsVisibleOnLoad?: boolean; + copyLinkAndThumbnailEnabled?: boolean; + doNotTrack?: boolean; + email?: string; + endVideoBehavior?: 'default' | 'reset' | 'loop'; + fakeFullscreen?: boolean; + fitStrategy?: 'contain' | 'cover' | 'fill' | 'none'; + fullscreenButton?: boolean; + muted?: boolean; + playbackRateControl?: boolean; + playbar?: boolean; + playButton?: boolean; + playerColor?: string; + playlistLinks?: boolean; + playlistLoop?: boolean; + playsinline?: boolean; + playPauseNotifier?: boolean; + playSuspendedOffScreen?: boolean; + preload?: 'metadata' | 'auto' | 'none' | boolean; + qualityControl?: boolean; + qualityMax?: 224 | 360 | 540 | 720 | 1080 | 3840; + qualityMin?: 224 | 360 | 540 | 720 | 1080 | 3840; + resumable?: boolean | 'auto'; + seo?: boolean; + settingsControl?: boolean; + silentAutoPlay?: boolean | 'allow'; + smallPlayButton?: boolean; + stillUrl?: string; + time?: number | string; + thumbnailAltText?: string; + videoFoam?: + | boolean + | { + minWidth?: number; + maxWidth?: number; + minHeight?: number; + maxHeight?: number; + }; + volume?: number; + volumeControl?: boolean; + wmode?: string; + plugin?: WistiaPluginConfig; +}; + export default class CustomVideoElement extends HTMLVideoElement { static readonly observedAttributes: string[]; - attributeChangedCallback( - attrName: string, - oldValue?: string | null, - newValue?: string | null - ): void; + attributeChangedCallback(attrName: string, oldValue?: string | null, newValue?: string | null): void; connectedCallback(): void; disconnectedCallback(): void; + config: WistiaEmbedOptions | null; } diff --git a/packages/wistia-video-element/wistia-video-element.js b/packages/wistia-video-element/wistia-video-element.js index 7a8555c3..99aa64da 100644 --- a/packages/wistia-video-element/wistia-video-element.js +++ b/packages/wistia-video-element/wistia-video-element.js @@ -28,10 +28,38 @@ if (templateShadowDOM) { `; } +function upgradeProperty(el, prop) { + // This is a pattern to update property values that are set before + // the custom element is upgraded. + // https://web.dev/custom-elements-best-practices/#make-properties-lazy + if (Object.hasOwn(el, prop)) { + const value = el[prop]; + // Delete the set property from this instance. + delete el[prop]; + // Set the value again via the (prototype) setter on this class. + el[prop] = value; + } +} + class WistiaVideoElement extends SuperVideoElement { static template = templateShadowDOM; static skipAttributes = ['src']; + #config = null; + + constructor() { + super(); + upgradeProperty(this, 'config'); + } + + get config() { + return this.#config; + } + + set config(value) { + this.#config = value; + } + get nativeEl() { return this.api?.elem() ?? this.querySelector('video'); } @@ -58,6 +86,7 @@ class WistiaVideoElement extends SuperVideoElement { chromeless: !this.controls, playButton: this.controls, muted: this.defaultMuted, + ...(this.#config ?? {}), }; // Sadly the setup/render will not work in the shadow DOM. @@ -119,14 +148,16 @@ async function loadScript(src, globalName) { if (!globalName) return import(/* webpackIgnore: true */ src); if (loadScriptCache[src]) return loadScriptCache[src]; if (self[globalName]) return self[globalName]; - return (loadScriptCache[src] = new Promise((resolve, reject) => { + const promise = new Promise((resolve, reject) => { const script = document.createElement('script'); script.defer = true; script.src = src; script.onload = () => resolve(self[globalName]); script.onerror = reject; document.head.append(script); - })); + }); + loadScriptCache[src] = promise; + return promise; } let idCounter = 0;