From 93f2a3b63c0806d418ff754ac3e01801657a5d6e Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Tue, 28 Jan 2025 11:13:40 +0100 Subject: [PATCH 001/123] Add THEOplayerUI to global scope in example --- examples/default-ui.html | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/default-ui.html b/examples/default-ui.html index 16af0681..64e8a7a9 100644 --- a/examples/default-ui.html +++ b/examples/default-ui.html @@ -32,7 +32,8 @@ From 70eade5f675a178caf6b7da45ec34a1cf8ae7dca Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Tue, 17 Oct 2023 18:37:43 +0200 Subject: [PATCH 002/123] Add polyfill bundle --- package-lock.json | 56 ++++++++++++++++++++++++++++++++++++++++------- package.json | 6 +++-- rollup.config.mjs | 12 ++++++++++ src/polyfills.ts | 3 +++ 4 files changed, 67 insertions(+), 10 deletions(-) create mode 100644 src/polyfills.ts diff --git a/package-lock.json b/package-lock.json index 7dee2b25..e80a2a76 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,9 @@ "react" ], "dependencies": { - "@lit-labs/ssr-dom-shim": "^1.3.0" + "@lit-labs/ssr-dom-shim": "^1.3.0", + "@webcomponents/webcomponentsjs": "^2.8.0", + "lit-html": "^3.0.0" }, "devDependencies": { "@changesets/cli": "^2.29.7", @@ -28,7 +30,6 @@ "@swc/helpers": "^0.5.15", "@types/html-minifier": "^4.0.5", "@webcomponents/shadycss": "^1.11.2", - "@webcomponents/webcomponentsjs": "^2.8.0", "cross-env": "^7.0.3", "html-minifier": "^4.0.0", "postcss": "^8.5.2", @@ -2868,6 +2869,12 @@ "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==", "dev": true }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "license": "MIT" + }, "node_modules/@types/uglify-js": { "version": "3.17.2", "resolved": "https://registry.npmjs.org/@types/uglify-js/-/uglify-js-3.17.2.tgz", @@ -2902,8 +2909,7 @@ "node_modules/@webcomponents/webcomponentsjs": { "version": "2.8.0", "resolved": "https://registry.npmjs.org/@webcomponents/webcomponentsjs/-/webcomponentsjs-2.8.0.tgz", - "integrity": "sha512-loGD63sacRzOzSJgQnB9ZAhaQGkN7wl2Zuw7tsphI5Isa0irijrRo6EnJii/GgjGefIFO8AIO7UivzRhFaEk9w==", - "dev": true + "integrity": "sha512-loGD63sacRzOzSJgQnB9ZAhaQGkN7wl2Zuw7tsphI5Isa0irijrRo6EnJii/GgjGefIFO8AIO7UivzRhFaEk9w==" }, "node_modules/@xhmikosr/archive-type": { "version": "7.0.0", @@ -5270,6 +5276,15 @@ "uc.micro": "^2.0.0" } }, + "node_modules/lit-html": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lit-html/-/lit-html-3.3.0.tgz", + "integrity": "sha512-RHoswrFAxY2d8Cf2mm4OZ1DgzCoBKUKSPvA1fhtSELxUERq2aQQ2h05pO9j81gS1o7RIRJ+CePLogfyahwmynw==", + "license": "BSD-3-Clause", + "dependencies": { + "@types/trusted-types": "^2.0.2" + } + }, "node_modules/loader-utils": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-3.2.1.tgz", @@ -10284,6 +10299,7 @@ "@webcomponents/webcomponentsjs": "^2.8.0", "cross-env": "^7.0.3", "html-minifier": "^4.0.0", + "lit-html": "^3.0.0", "postcss": "^8.5.2", "postcss-mixins": "^11.0.3", "postcss-preset-env": "^10.1.3", @@ -11789,6 +11805,11 @@ "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==", "dev": true }, + "@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==" + }, "@types/uglify-js": { "version": "3.17.2", "resolved": "https://registry.npmjs.org/@types/uglify-js/-/uglify-js-3.17.2.tgz", @@ -11821,8 +11842,7 @@ "@webcomponents/webcomponentsjs": { "version": "2.8.0", "resolved": "https://registry.npmjs.org/@webcomponents/webcomponentsjs/-/webcomponentsjs-2.8.0.tgz", - "integrity": "sha512-loGD63sacRzOzSJgQnB9ZAhaQGkN7wl2Zuw7tsphI5Isa0irijrRo6EnJii/GgjGefIFO8AIO7UivzRhFaEk9w==", - "dev": true + "integrity": "sha512-loGD63sacRzOzSJgQnB9ZAhaQGkN7wl2Zuw7tsphI5Isa0irijrRo6EnJii/GgjGefIFO8AIO7UivzRhFaEk9w==" }, "@xhmikosr/archive-type": { "version": "7.0.0", @@ -13403,6 +13423,14 @@ "uc.micro": "^2.0.0" } }, + "lit-html": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lit-html/-/lit-html-3.3.0.tgz", + "integrity": "sha512-RHoswrFAxY2d8Cf2mm4OZ1DgzCoBKUKSPvA1fhtSELxUERq2aQQ2h05pO9j81gS1o7RIRJ+CePLogfyahwmynw==", + "requires": { + "@types/trusted-types": "^2.0.2" + } + }, "loader-utils": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-3.2.1.tgz", @@ -15689,6 +15717,11 @@ "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==", "dev": true }, + "@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==" + }, "@types/uglify-js": { "version": "3.17.2", "resolved": "https://registry.npmjs.org/@types/uglify-js/-/uglify-js-3.17.2.tgz", @@ -15721,8 +15754,7 @@ "@webcomponents/webcomponentsjs": { "version": "2.8.0", "resolved": "https://registry.npmjs.org/@webcomponents/webcomponentsjs/-/webcomponentsjs-2.8.0.tgz", - "integrity": "sha512-loGD63sacRzOzSJgQnB9ZAhaQGkN7wl2Zuw7tsphI5Isa0irijrRo6EnJii/GgjGefIFO8AIO7UivzRhFaEk9w==", - "dev": true + "integrity": "sha512-loGD63sacRzOzSJgQnB9ZAhaQGkN7wl2Zuw7tsphI5Isa0irijrRo6EnJii/GgjGefIFO8AIO7UivzRhFaEk9w==" }, "@xhmikosr/archive-type": { "version": "7.0.0", @@ -17303,6 +17335,14 @@ "uc.micro": "^2.0.0" } }, + "lit-html": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lit-html/-/lit-html-3.3.0.tgz", + "integrity": "sha512-RHoswrFAxY2d8Cf2mm4OZ1DgzCoBKUKSPvA1fhtSELxUERq2aQQ2h05pO9j81gS1o7RIRJ+CePLogfyahwmynw==", + "requires": { + "@types/trusted-types": "^2.0.2" + } + }, "loader-utils": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-3.2.1.tgz", diff --git a/package.json b/package.json index e1dddc9f..400b83ba 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "import": "./dist/THEOplayerUI.es5.mjs", "default": "./dist/THEOplayerUI.es5.js" }, + "./polyfills": "./dist/THEOplayerUI-polyfills.js", "./dist/*": "./dist/*", "./package": "./package.json", "./package.json": "./package.json" @@ -53,7 +54,9 @@ "url": "git+https://github.com/THEOplayer/web-ui.git" }, "dependencies": { - "@lit-labs/ssr-dom-shim": "^1.3.0" + "@lit-labs/ssr-dom-shim": "^1.3.0", + "@webcomponents/webcomponentsjs": "^2.8.0", + "lit-html": "^3.0.0" }, "peerDependencies": { "theoplayer": "^7 || ^8 || ^9 || ^10" @@ -71,7 +74,6 @@ "@swc/helpers": "^0.5.15", "@types/html-minifier": "^4.0.5", "@webcomponents/shadycss": "^1.11.2", - "@webcomponents/webcomponentsjs": "^2.8.0", "cross-env": "^7.0.3", "html-minifier": "^4.0.0", "postcss": "^8.5.2", diff --git a/rollup.config.mjs b/rollup.config.mjs index c325cf30..82f25934 100644 --- a/rollup.config.mjs +++ b/rollup.config.mjs @@ -36,6 +36,18 @@ export default (cliArgs) => { return defineConfig([ ...jsConfig(outputDir, { es5: false, node: true, production, sourcemap: true }), ...jsConfig(outputDir, { es5: true, production, sourcemap: false }), + { + input: './src/polyfills.ts', + output: { + file: path.join(outputDir, `${fileName}.polyfills.js`), + format: 'iife', + sourcemap: false, + indent: false, + banner + }, + context: 'self', + plugins: jsPlugins({ es5: true, module: false, production: true, sourcemap: false }) + }, { input: './src/index.ts', output: { diff --git a/src/polyfills.ts b/src/polyfills.ts new file mode 100644 index 00000000..5f5b4c4c --- /dev/null +++ b/src/polyfills.ts @@ -0,0 +1,3 @@ +import '@webcomponents/webcomponentsjs/custom-elements-es5-adapter.js'; +import '@webcomponents/webcomponentsjs/webcomponents-bundle.js'; +import 'lit-html/polyfill-support.js'; From 9566ddf64ade3d8dd243696d871e80ac401aeffd Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Tue, 17 Oct 2023 18:45:36 +0200 Subject: [PATCH 003/123] Use our own polyfill bundle in examples --- README.md | 15 ++++++++------- .../open-video-ui/v1/examples/web/ads/demo.html | 1 + .../v1/examples/web/custom-ui/demo.html | 1 + .../v1/examples/web/default-ui/demo.html | 1 + .../v1/examples/web/nitflex/demo.html | 1 + .../v1/examples/web/portrait/demo.html | 1 + .../v1/examples/web/styling/demo.html | 1 + examples/default-ui.html | 1 + 8 files changed, 15 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 758e98a0..798f467a 100644 --- a/README.md +++ b/README.md @@ -131,9 +131,12 @@ See [custom-ui/demo.html](https://github.com/THEOplayer/web-ui/blob/main/docs/st By default, Open Video UI for Web targets modern browsers that support modern JavaScript syntax (such as [async/await](https://caniuse.com/async-functions)) and native [Custom Elements](https://caniuse.com/custom-elementsv1). This keeps the download size small, so your viewers can spend less time waiting for your page to load and start watching their video faster. -On older browsers (such as Internet Explorer 11 and older smart TVs), you need to load a different version of the Open Video UI that uses older JavaScript syntax. You also need to load additional polyfills for missing features such as Promises or Custom Elements. We recommend [the Cloudflare mirror of Polyfill.io](https://cdnjs.cloudflare.com/polyfill/) and [Web Components Polyfills](https://github.com/webcomponents/polyfills) for these. +On older browsers (such as Internet Explorer 11 and older smart TVs), you need to load a different version of the Open Video UI that uses older JavaScript syntax. You also need to load additional polyfills for missing features such as Promises or Custom Elements: -- Option 1: in your HTML. This uses [differential serving](https://css-tricks.com/differential-serving/) so modern browsers will load the modern build (with `type="module"`), while legacy browsers will load the legacy build (with `nomodule`). +- For Promises, we recommend [the Cloudflare mirror of Polyfill.io](https://cdnjs.cloudflare.com/polyfill/). +- For Custom Elements, we recommend loading our polyfill bundle from `@theoplayer/web-ui/polyfills`. Alternatively, you can load the [Web Components Polyfills](https://github.com/webcomponents/polyfills). + +* Option 1: in your HTML. This uses [differential serving](https://css-tricks.com/differential-serving/) so modern browsers will load the modern build (with `type="module"`), while legacy browsers will load the legacy build (with `nomodule`). ```html @@ -149,16 +152,14 @@ On older browsers (such as Internet Explorer 11 and older smart TVs), you need t - - + ``` -- Option 2: in your JavaScript. This will load the legacy build on both modern and legacy browsers, which is suboptimal. Instead, we recommend configuring your bundler to produce a modern and legacy build of your entire web app, and to import the appropriate version of Open Video UI for each build flavor. +* Option 2: in your JavaScript. This will load the legacy build on both modern and legacy browsers, which is suboptimal. Instead, we recommend configuring your bundler to produce a modern and legacy build of your entire web app, and to import the appropriate version of Open Video UI for each build flavor. ```js - import '@webcomponents/webcomponentsjs/custom-elements-es5-adapter.js'; - import '@webcomponents/webcomponentsjs/webcomponents-bundle.js'; + import '@theoplayer/web-ui/polyfills'; import { DefaultUI } from '@theoplayer/web-ui/es5'; // note the "/es5" suffix ``` diff --git a/docs/static/open-video-ui/v1/examples/web/ads/demo.html b/docs/static/open-video-ui/v1/examples/web/ads/demo.html index 4f499349..87223778 100644 --- a/docs/static/open-video-ui/v1/examples/web/ads/demo.html +++ b/docs/static/open-video-ui/v1/examples/web/ads/demo.html @@ -27,6 +27,7 @@ } + diff --git a/docs/static/open-video-ui/v1/examples/web/custom-ui/demo.html b/docs/static/open-video-ui/v1/examples/web/custom-ui/demo.html index 945821ee..236cde54 100644 --- a/docs/static/open-video-ui/v1/examples/web/custom-ui/demo.html +++ b/docs/static/open-video-ui/v1/examples/web/custom-ui/demo.html @@ -9,6 +9,7 @@ + diff --git a/docs/static/open-video-ui/v1/examples/web/default-ui/demo.html b/docs/static/open-video-ui/v1/examples/web/default-ui/demo.html index af4d5287..68df7e3d 100644 --- a/docs/static/open-video-ui/v1/examples/web/default-ui/demo.html +++ b/docs/static/open-video-ui/v1/examples/web/default-ui/demo.html @@ -27,6 +27,7 @@ } + diff --git a/docs/static/open-video-ui/v1/examples/web/nitflex/demo.html b/docs/static/open-video-ui/v1/examples/web/nitflex/demo.html index ac74a3f8..395b28fe 100644 --- a/docs/static/open-video-ui/v1/examples/web/nitflex/demo.html +++ b/docs/static/open-video-ui/v1/examples/web/nitflex/demo.html @@ -19,6 +19,7 @@ } + diff --git a/docs/static/open-video-ui/v1/examples/web/portrait/demo.html b/docs/static/open-video-ui/v1/examples/web/portrait/demo.html index bd5ef0d8..4d907089 100644 --- a/docs/static/open-video-ui/v1/examples/web/portrait/demo.html +++ b/docs/static/open-video-ui/v1/examples/web/portrait/demo.html @@ -20,6 +20,7 @@ } + diff --git a/docs/static/open-video-ui/v1/examples/web/styling/demo.html b/docs/static/open-video-ui/v1/examples/web/styling/demo.html index 078c5279..2dd6b04b 100644 --- a/docs/static/open-video-ui/v1/examples/web/styling/demo.html +++ b/docs/static/open-video-ui/v1/examples/web/styling/demo.html @@ -53,6 +53,7 @@ } + diff --git a/examples/default-ui.html b/examples/default-ui.html index 64e8a7a9..82966519 100644 --- a/examples/default-ui.html +++ b/examples/default-ui.html @@ -31,6 +31,7 @@ + - - + - +

Default UI

From e1b6c1e6061619cb7b6966811ca3c3e06f23360d Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Tue, 28 Jan 2025 16:26:00 +0100 Subject: [PATCH 009/123] Fix ES5 build --- rollup.config.mjs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/rollup.config.mjs b/rollup.config.mjs index 11bafa16..e1883d57 100644 --- a/rollup.config.mjs +++ b/rollup.config.mjs @@ -120,6 +120,7 @@ function jsConfig(outputDir, { es5 = false, node = false, production = false, so */ function jsPlugins({ es5 = false, node = false, module = false, production = false, sourcemap = false }) { const browserslist = es5 ? browserslistLegacy : browserslistModern; + const ecmaVersion = es5 ? 5 : 2017; return [ nodeResolve({ exportConditions: [ @@ -166,6 +167,7 @@ function jsPlugins({ es5 = false, node = false, module = false, production = fal }), es5 && replace({ + include: './node_modules/lit-html/**', preventAssignment: true, delimiters: ['', ''], values: { @@ -229,7 +231,7 @@ function jsPlugins({ es5 = false, node = false, module = false, production = fal }, toplevel: true, module, - ecma: es5 ? 5 : 2017 + ecma: ecmaVersion }) ].filter(Boolean); } From fe56b308d07caab5344f8c6d0d23a76f2d2b119e Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Tue, 28 Jan 2025 16:29:02 +0100 Subject: [PATCH 010/123] Use module/nomodule pattern in example --- examples/default-ui.html | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/examples/default-ui.html b/examples/default-ui.html index bf7543bc..00da5de1 100644 --- a/examples/default-ui.html +++ b/examples/default-ui.html @@ -21,6 +21,7 @@ background: #000; } + + + + + + - +

Default UI

From 2d2d1e89fdf68d89302c26f64f35c728f24da217 Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Tue, 28 Jan 2025 17:40:14 +0100 Subject: [PATCH 011/123] Set up Lit --- examples/default-ui.html | 2 +- package-lock.json | 109 +++++++++++++++++++++++++++++++++++---- package.json | 2 +- rollup.config.mjs | 8 ++- tsconfig.json | 1 - 5 files changed, 106 insertions(+), 16 deletions(-) diff --git a/examples/default-ui.html b/examples/default-ui.html index 00da5de1..bd5b9752 100644 --- a/examples/default-ui.html +++ b/examples/default-ui.html @@ -37,7 +37,7 @@ self.THEOplayerUI = THEOplayerUI; - + diff --git a/package-lock.json b/package-lock.json index 90ff8d7b..cd91a717 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,7 +15,7 @@ "dependencies": { "@lit-labs/ssr-dom-shim": "^1.3.0", "@webcomponents/webcomponentsjs": "^2.8.0", - "lit-html": "^3.2.1" + "lit": "^3.3.0" }, "devDependencies": { "@changesets/cli": "^2.29.7", @@ -1581,9 +1581,9 @@ "license": "MIT" }, "node_modules/@lit-labs/ssr-dom-shim": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.3.0.tgz", - "integrity": "sha512-nQIWonJ6eFAvUUrSlwyHDm/aE8PBDu5kRpL0vHMg6K8fK3Diq1xdPjTnsJSwxABhaZ+5eBi1btQB5ShUTKo4nQ==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.4.0.tgz", + "integrity": "sha512-ficsEARKnmmW5njugNYKipTm4SFnbik7CXtoencDZzmzo/dQ+2Q0bgkzJuoJP20Aj0F+izzJjOqsnkd6F/o1bw==", "license": "BSD-3-Clause" }, "node_modules/@lit/react": { @@ -1595,6 +1595,15 @@ "@types/react": "17 || 18 || 19" } }, + "node_modules/@lit/reactive-element": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-2.1.1.tgz", + "integrity": "sha512-N+dm5PAYdQ8e6UlywyyrgI2t++wFGXfHx+dSJ1oBrg6FAxUj40jId++EaRm80MKX5JnlH1sBsyZ5h0bcZKemCg==", + "license": "BSD-3-Clause", + "dependencies": { + "@lit-labs/ssr-dom-shim": "^1.4.0" + } + }, "node_modules/@manypkg/find-root": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@manypkg/find-root/-/find-root-1.1.0.tgz", @@ -5277,6 +5286,28 @@ "uc.micro": "^2.0.0" } }, + "node_modules/lit": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/lit/-/lit-3.3.1.tgz", + "integrity": "sha512-Ksr/8L3PTapbdXJCk+EJVB78jDodUMaP54gD24W186zGRARvwrsPfS60wae/SSCTCNZVPd1chXqio1qHQmu4NA==", + "license": "BSD-3-Clause", + "dependencies": { + "@lit/reactive-element": "^2.1.0", + "lit-element": "^4.2.0", + "lit-html": "^3.3.0" + } + }, + "node_modules/lit-element": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/lit-element/-/lit-element-4.2.1.tgz", + "integrity": "sha512-WGAWRGzirAgyphK2urmYOV72tlvnxw7YfyLDgQ+OZnM9vQQBQnumQ7jUJe6unEzwGU3ahFOjuz1iz1jjrpCPuw==", + "license": "BSD-3-Clause", + "dependencies": { + "@lit-labs/ssr-dom-shim": "^1.4.0", + "@lit/reactive-element": "^2.1.0", + "lit-html": "^3.3.0" + } + }, "node_modules/lit-html": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/lit-html/-/lit-html-3.3.0.tgz", @@ -9651,9 +9682,9 @@ "dev": true }, "@lit-labs/ssr-dom-shim": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.3.0.tgz", - "integrity": "sha512-nQIWonJ6eFAvUUrSlwyHDm/aE8PBDu5kRpL0vHMg6K8fK3Diq1xdPjTnsJSwxABhaZ+5eBi1btQB5ShUTKo4nQ==" + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.4.0.tgz", + "integrity": "sha512-ficsEARKnmmW5njugNYKipTm4SFnbik7CXtoencDZzmzo/dQ+2Q0bgkzJuoJP20Aj0F+izzJjOqsnkd6F/o1bw==" }, "@lit/react": { "version": "1.0.7", @@ -9661,6 +9692,14 @@ "integrity": "sha512-cencnwwLXQKiKxjfFzSgZRngcWJzUDZi/04E0fSaF86wZgchMdvTyu+lE36DrUfvuus3bH8+xLPrhM1cTjwpzw==", "requires": {} }, + "@lit/reactive-element": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-2.1.1.tgz", + "integrity": "sha512-N+dm5PAYdQ8e6UlywyyrgI2t++wFGXfHx+dSJ1oBrg6FAxUj40jId++EaRm80MKX5JnlH1sBsyZ5h0bcZKemCg==", + "requires": { + "@lit-labs/ssr-dom-shim": "^1.4.0" + } + }, "@manypkg/find-root": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@manypkg/find-root/-/find-root-1.1.0.tgz", @@ -10335,7 +10374,7 @@ "@webcomponents/webcomponentsjs": "^2.8.0", "cross-env": "^7.0.3", "html-minifier": "^4.0.0", - "lit-html": "^3.0.0", + "lit": "^3.3.0", "postcss": "^8.5.2", "postcss-mixins": "^11.0.3", "postcss-preset-env": "^10.1.3", @@ -11097,9 +11136,9 @@ "dev": true }, "@lit-labs/ssr-dom-shim": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.3.0.tgz", - "integrity": "sha512-nQIWonJ6eFAvUUrSlwyHDm/aE8PBDu5kRpL0vHMg6K8fK3Diq1xdPjTnsJSwxABhaZ+5eBi1btQB5ShUTKo4nQ==" + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.4.0.tgz", + "integrity": "sha512-ficsEARKnmmW5njugNYKipTm4SFnbik7CXtoencDZzmzo/dQ+2Q0bgkzJuoJP20Aj0F+izzJjOqsnkd6F/o1bw==" }, "@lit/react": { "version": "1.0.7", @@ -11107,6 +11146,14 @@ "integrity": "sha512-cencnwwLXQKiKxjfFzSgZRngcWJzUDZi/04E0fSaF86wZgchMdvTyu+lE36DrUfvuus3bH8+xLPrhM1cTjwpzw==", "requires": {} }, + "@lit/reactive-element": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-2.1.1.tgz", + "integrity": "sha512-N+dm5PAYdQ8e6UlywyyrgI2t++wFGXfHx+dSJ1oBrg6FAxUj40jId++EaRm80MKX5JnlH1sBsyZ5h0bcZKemCg==", + "requires": { + "@lit-labs/ssr-dom-shim": "^1.4.0" + } + }, "@manypkg/find-root": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@manypkg/find-root/-/find-root-1.1.0.tgz", @@ -13471,6 +13518,26 @@ "uc.micro": "^2.0.0" } }, + "lit": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/lit/-/lit-3.3.1.tgz", + "integrity": "sha512-Ksr/8L3PTapbdXJCk+EJVB78jDodUMaP54gD24W186zGRARvwrsPfS60wae/SSCTCNZVPd1chXqio1qHQmu4NA==", + "requires": { + "@lit/reactive-element": "^2.1.0", + "lit-element": "^4.2.0", + "lit-html": "^3.3.0" + } + }, + "lit-element": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/lit-element/-/lit-element-4.2.1.tgz", + "integrity": "sha512-WGAWRGzirAgyphK2urmYOV72tlvnxw7YfyLDgQ+OZnM9vQQBQnumQ7jUJe6unEzwGU3ahFOjuz1iz1jjrpCPuw==", + "requires": { + "@lit-labs/ssr-dom-shim": "^1.4.0", + "@lit/reactive-element": "^2.1.0", + "lit-html": "^3.3.0" + } + }, "lit-html": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/lit-html/-/lit-html-3.3.0.tgz", @@ -17383,6 +17450,26 @@ "uc.micro": "^2.0.0" } }, + "lit": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/lit/-/lit-3.3.1.tgz", + "integrity": "sha512-Ksr/8L3PTapbdXJCk+EJVB78jDodUMaP54gD24W186zGRARvwrsPfS60wae/SSCTCNZVPd1chXqio1qHQmu4NA==", + "requires": { + "@lit/reactive-element": "^2.1.0", + "lit-element": "^4.2.0", + "lit-html": "^3.3.0" + } + }, + "lit-element": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/lit-element/-/lit-element-4.2.1.tgz", + "integrity": "sha512-WGAWRGzirAgyphK2urmYOV72tlvnxw7YfyLDgQ+OZnM9vQQBQnumQ7jUJe6unEzwGU3ahFOjuz1iz1jjrpCPuw==", + "requires": { + "@lit-labs/ssr-dom-shim": "^1.4.0", + "@lit/reactive-element": "^2.1.0", + "lit-html": "^3.3.0" + } + }, "lit-html": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/lit-html/-/lit-html-3.3.0.tgz", diff --git a/package.json b/package.json index a7c6ec55..7139871e 100644 --- a/package.json +++ b/package.json @@ -56,7 +56,7 @@ "dependencies": { "@lit-labs/ssr-dom-shim": "^1.3.0", "@webcomponents/webcomponentsjs": "^2.8.0", - "lit-html": "^3.2.1" + "lit": "^3.3.0" }, "peerDependencies": { "theoplayer": "^7 || ^8 || ^9 || ^10" diff --git a/rollup.config.mjs b/rollup.config.mjs index e1883d57..a60a2402 100644 --- a/rollup.config.mjs +++ b/rollup.config.mjs @@ -167,7 +167,7 @@ function jsPlugins({ es5 = false, node = false, module = false, production = fal }), es5 && replace({ - include: './node_modules/lit-html/**', + include: ['./node_modules/lit/**', './node_modules/lit-html/**', './node_modules/@lit/**'], preventAssignment: true, delimiters: ['', ''], values: { @@ -193,7 +193,11 @@ function jsPlugins({ es5 = false, node = false, module = false, production = fal loose: true, externalHelpers: true, parser: { - syntax: 'typescript' + syntax: 'typescript', + decorators: true + }, + transform: { + decoratorVersion: '2022-03' } } }), diff --git a/tsconfig.json b/tsconfig.json index 870c182c..1ddf70b6 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -8,7 +8,6 @@ "forceConsistentCasingInFileNames": true, "strict": true, "skipDefaultLibCheck": true, - "experimentalDecorators": true, "importHelpers": true, "useDefineForClassFields": true, "isolatedModules": true, From 324374630ac3830eede0ad606d7476671dd08751 Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Wed, 23 Apr 2025 18:05:19 +0200 Subject: [PATCH 012/123] Update dependencies --- package-lock.json | 180 ++++++++++++++++++--------------------------- package.json | 4 +- react/package.json | 2 +- 3 files changed, 73 insertions(+), 113 deletions(-) diff --git a/package-lock.json b/package-lock.json index cd91a717..235d4ba1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,10 +23,10 @@ "@rollup/plugin-inject": "^5.0.5", "@rollup/plugin-json": "^6.1.0", "@rollup/plugin-node-resolve": "^16.0.0", - "@rollup/plugin-replace": "^5.0.4", + "@rollup/plugin-replace": "^6.0.2", "@rollup/plugin-virtual": "^3.0.2", "@rollup/pluginutils": "^5.1.4", - "@swc/cli": "^0.6.0", + "@swc/cli": "^0.7.3", "@swc/core": "^1.10.15", "@swc/helpers": "^0.5.15", "@types/html-minifier": "^4.0.5", @@ -2084,9 +2084,9 @@ } }, "node_modules/@rollup/plugin-replace": { - "version": "5.0.7", - "resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-5.0.7.tgz", - "integrity": "sha512-PqxSfuorkHz/SPpyngLyg5GCEkOcee9M1bkxiVDr41Pd61mqP1PLOoDPbpl44SB2mQGKwV/In74gqQmGITOhEQ==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-6.0.2.tgz", + "integrity": "sha512-7QaYCf8bqF04dOy7w/eHmJeNExxTYwvKAmlSAH/EaWWUzbT0h5sbF6bktFoX/0F/0qwng5/dWFMyf3gzaM8DsQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2474,21 +2474,21 @@ } }, "node_modules/@swc/cli": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/@swc/cli/-/cli-0.6.0.tgz", - "integrity": "sha512-Q5FsI3Cw0fGMXhmsg7c08i4EmXCrcl+WnAxb6LYOLHw4JFFC3yzmx9LaXZ7QMbA+JZXbigU2TirI7RAfO0Qlnw==", + "version": "0.7.8", + "resolved": "https://registry.npmjs.org/@swc/cli/-/cli-0.7.8.tgz", + "integrity": "sha512-27Ov4rm0s2C6LLX+NDXfDVB69LGs8K94sXtFhgeUyQ4DBywZuCgTBu2loCNHRr8JhT9DeQvJM5j9FAu/THbo4w==", "dev": true, "license": "MIT", "dependencies": { "@swc/counter": "^0.1.3", "@xhmikosr/bin-wrapper": "^13.0.5", "commander": "^8.3.0", - "fast-glob": "^3.2.5", "minimatch": "^9.0.3", "piscina": "^4.3.1", "semver": "^7.3.8", "slash": "3.0.0", - "source-map": "^0.7.3" + "source-map": "^0.7.3", + "tinyglobby": "^0.2.13" }, "bin": { "spack": "bin/spack.js", @@ -8503,25 +8503,31 @@ "license": "MIT" }, "node_modules/tinyglobby": { - "version": "0.2.10", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.10.tgz", - "integrity": "sha512-Zc+8eJlFMvgatPZTl6A9L/yht8QqdmUNtURHaKZLmKBE12hNPSrqNkUp2cs3M/UKmNVVAMFQYSjYIVHDjW5zew==", + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", "dev": true, "license": "MIT", "dependencies": { - "fdir": "^6.4.2", - "picomatch": "^4.0.2" + "fdir": "^6.5.0", + "picomatch": "^4.0.3" }, "engines": { "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" } }, "node_modules/tinyglobby/node_modules/fdir": { - "version": "6.4.3", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.3.tgz", - "integrity": "sha512-PMXmW2y1hDDfTSRc9gaXIuCCRpuoz3Kaz8cUelp3smouvfT632ozg2vrT6lJsHKKOF59YLbOGfAWGUcKEfRMQw==", + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", "dev": true, "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, "peerDependencies": { "picomatch": "^3 || ^4" }, @@ -8532,9 +8538,9 @@ } }, "node_modules/tinyglobby/node_modules/picomatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", - "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", "engines": { @@ -8892,7 +8898,7 @@ "@rollup/plugin-json": "^6.1.0", "@rollup/plugin-node-resolve": "^16.0.0", "@rollup/plugin-replace": "^6.0.2", - "@swc/cli": "^0.6.0", + "@swc/cli": "^0.7.3", "@swc/core": "^1.10.15", "@swc/helpers": "^0.5.15", "@types/react": "^18.2.79", @@ -8913,28 +8919,6 @@ "react": "^16.3.0 || ^17 || ^18", "theoplayer": "^7 || ^8 || ^9 || ^10" } - }, - "react/node_modules/@rollup/plugin-replace": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-6.0.2.tgz", - "integrity": "sha512-7QaYCf8bqF04dOy7w/eHmJeNExxTYwvKAmlSAH/EaWWUzbT0h5sbF6bktFoX/0F/0qwng5/dWFMyf3gzaM8DsQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@rollup/pluginutils": "^5.0.1", - "magic-string": "^0.30.3" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } - } } }, "dependencies": { @@ -9961,9 +9945,9 @@ } }, "@rollup/plugin-replace": { - "version": "5.0.7", - "resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-5.0.7.tgz", - "integrity": "sha512-PqxSfuorkHz/SPpyngLyg5GCEkOcee9M1bkxiVDr41Pd61mqP1PLOoDPbpl44SB2mQGKwV/In74gqQmGITOhEQ==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-6.0.2.tgz", + "integrity": "sha512-7QaYCf8bqF04dOy7w/eHmJeNExxTYwvKAmlSAH/EaWWUzbT0h5sbF6bktFoX/0F/0qwng5/dWFMyf3gzaM8DsQ==", "dev": true, "requires": { "@rollup/pluginutils": "^5.0.1", @@ -10168,20 +10152,20 @@ "dev": true }, "@swc/cli": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/@swc/cli/-/cli-0.6.0.tgz", - "integrity": "sha512-Q5FsI3Cw0fGMXhmsg7c08i4EmXCrcl+WnAxb6LYOLHw4JFFC3yzmx9LaXZ7QMbA+JZXbigU2TirI7RAfO0Qlnw==", + "version": "0.7.8", + "resolved": "https://registry.npmjs.org/@swc/cli/-/cli-0.7.8.tgz", + "integrity": "sha512-27Ov4rm0s2C6LLX+NDXfDVB69LGs8K94sXtFhgeUyQ4DBywZuCgTBu2loCNHRr8JhT9DeQvJM5j9FAu/THbo4w==", "dev": true, "requires": { "@swc/counter": "^0.1.3", "@xhmikosr/bin-wrapper": "^13.0.5", "commander": "^8.3.0", - "fast-glob": "^3.2.5", "minimatch": "^9.0.3", "piscina": "^4.3.1", "semver": "^7.3.8", "slash": "3.0.0", - "source-map": "^0.7.3" + "source-map": "^0.7.3", + "tinyglobby": "^0.2.13" }, "dependencies": { "commander": { @@ -10322,7 +10306,7 @@ "@rollup/plugin-json": "^6.1.0", "@rollup/plugin-node-resolve": "^16.0.0", "@rollup/plugin-replace": "^6.0.2", - "@swc/cli": "^0.6.0", + "@swc/cli": "^0.7.3", "@swc/core": "^1.10.15", "@swc/helpers": "^0.5.15", "@theoplayer/web-ui": "^1.14.0", @@ -10338,18 +10322,6 @@ "typedoc-plugin-external-resolver": "^1.0.3", "typedoc-plugin-mdn-links": "^4.0.12", "typescript": "^5.7.3" - }, - "dependencies": { - "@rollup/plugin-replace": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-6.0.2.tgz", - "integrity": "sha512-7QaYCf8bqF04dOy7w/eHmJeNExxTYwvKAmlSAH/EaWWUzbT0h5sbF6bktFoX/0F/0qwng5/dWFMyf3gzaM8DsQ==", - "dev": true, - "requires": { - "@rollup/pluginutils": "^5.0.1", - "magic-string": "^0.30.3" - } - } } }, "@theoplayer/web-ui": { @@ -10361,10 +10333,10 @@ "@rollup/plugin-inject": "^5.0.5", "@rollup/plugin-json": "^6.1.0", "@rollup/plugin-node-resolve": "^16.0.0", - "@rollup/plugin-replace": "^5.0.4", + "@rollup/plugin-replace": "^6.0.2", "@rollup/plugin-virtual": "^3.0.2", "@rollup/pluginutils": "^5.1.4", - "@swc/cli": "^0.6.0", + "@swc/cli": "^0.7.3", "@swc/core": "^1.10.15", "@swc/helpers": "^0.5.15", "@theoplayer/react-ui": "file:react", @@ -11415,9 +11387,9 @@ } }, "@rollup/plugin-replace": { - "version": "5.0.7", - "resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-5.0.7.tgz", - "integrity": "sha512-PqxSfuorkHz/SPpyngLyg5GCEkOcee9M1bkxiVDr41Pd61mqP1PLOoDPbpl44SB2mQGKwV/In74gqQmGITOhEQ==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-6.0.2.tgz", + "integrity": "sha512-7QaYCf8bqF04dOy7w/eHmJeNExxTYwvKAmlSAH/EaWWUzbT0h5sbF6bktFoX/0F/0qwng5/dWFMyf3gzaM8DsQ==", "dev": true, "requires": { "@rollup/pluginutils": "^5.0.1", @@ -11622,20 +11594,20 @@ "dev": true }, "@swc/cli": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/@swc/cli/-/cli-0.6.0.tgz", - "integrity": "sha512-Q5FsI3Cw0fGMXhmsg7c08i4EmXCrcl+WnAxb6LYOLHw4JFFC3yzmx9LaXZ7QMbA+JZXbigU2TirI7RAfO0Qlnw==", + "version": "0.7.8", + "resolved": "https://registry.npmjs.org/@swc/cli/-/cli-0.7.8.tgz", + "integrity": "sha512-27Ov4rm0s2C6LLX+NDXfDVB69LGs8K94sXtFhgeUyQ4DBywZuCgTBu2loCNHRr8JhT9DeQvJM5j9FAu/THbo4w==", "dev": true, "requires": { "@swc/counter": "^0.1.3", "@xhmikosr/bin-wrapper": "^13.0.5", "commander": "^8.3.0", - "fast-glob": "^3.2.5", "minimatch": "^9.0.3", "piscina": "^4.3.1", "semver": "^7.3.8", "slash": "3.0.0", - "source-map": "^0.7.3" + "source-map": "^0.7.3", + "tinyglobby": "^0.2.13" }, "dependencies": { "commander": { @@ -11776,7 +11748,7 @@ "@rollup/plugin-json": "^6.1.0", "@rollup/plugin-node-resolve": "^16.0.0", "@rollup/plugin-replace": "^6.0.2", - "@swc/cli": "^0.6.0", + "@swc/cli": "^0.7.3", "@swc/core": "^1.10.15", "@swc/helpers": "^0.5.15", "@theoplayer/web-ui": "^1.14.0", @@ -11792,18 +11764,6 @@ "typedoc-plugin-external-resolver": "^1.0.3", "typedoc-plugin-mdn-links": "^4.0.12", "typescript": "^5.7.3" - }, - "dependencies": { - "@rollup/plugin-replace": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-6.0.2.tgz", - "integrity": "sha512-7QaYCf8bqF04dOy7w/eHmJeNExxTYwvKAmlSAH/EaWWUzbT0h5sbF6bktFoX/0F/0qwng5/dWFMyf3gzaM8DsQ==", - "dev": true, - "requires": { - "@rollup/pluginutils": "^5.0.1", - "magic-string": "^0.30.3" - } - } } }, "@tokenizer/token": { @@ -15498,26 +15458,26 @@ "dev": true }, "tinyglobby": { - "version": "0.2.10", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.10.tgz", - "integrity": "sha512-Zc+8eJlFMvgatPZTl6A9L/yht8QqdmUNtURHaKZLmKBE12hNPSrqNkUp2cs3M/UKmNVVAMFQYSjYIVHDjW5zew==", + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", "dev": true, "requires": { - "fdir": "^6.4.2", - "picomatch": "^4.0.2" + "fdir": "^6.5.0", + "picomatch": "^4.0.3" }, "dependencies": { "fdir": { - "version": "6.4.3", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.3.tgz", - "integrity": "sha512-PMXmW2y1hDDfTSRc9gaXIuCCRpuoz3Kaz8cUelp3smouvfT632ozg2vrT6lJsHKKOF59YLbOGfAWGUcKEfRMQw==", + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", "dev": true, "requires": {} }, "picomatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", - "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true } } @@ -19430,26 +19390,26 @@ "dev": true }, "tinyglobby": { - "version": "0.2.10", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.10.tgz", - "integrity": "sha512-Zc+8eJlFMvgatPZTl6A9L/yht8QqdmUNtURHaKZLmKBE12hNPSrqNkUp2cs3M/UKmNVVAMFQYSjYIVHDjW5zew==", + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", "dev": true, "requires": { - "fdir": "^6.4.2", - "picomatch": "^4.0.2" + "fdir": "^6.5.0", + "picomatch": "^4.0.3" }, "dependencies": { "fdir": { - "version": "6.4.3", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.3.tgz", - "integrity": "sha512-PMXmW2y1hDDfTSRc9gaXIuCCRpuoz3Kaz8cUelp3smouvfT632ozg2vrT6lJsHKKOF59YLbOGfAWGUcKEfRMQw==", + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", "dev": true, "requires": {} }, "picomatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", - "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true } } diff --git a/package.json b/package.json index 7139871e..3cfbbead 100644 --- a/package.json +++ b/package.json @@ -67,10 +67,10 @@ "@rollup/plugin-inject": "^5.0.5", "@rollup/plugin-json": "^6.1.0", "@rollup/plugin-node-resolve": "^16.0.0", - "@rollup/plugin-replace": "^5.0.4", + "@rollup/plugin-replace": "^6.0.2", "@rollup/plugin-virtual": "^3.0.2", "@rollup/pluginutils": "^5.1.4", - "@swc/cli": "^0.6.0", + "@swc/cli": "^0.7.3", "@swc/core": "^1.10.15", "@swc/helpers": "^0.5.15", "@types/html-minifier": "^4.0.5", diff --git a/react/package.json b/react/package.json index cb735760..1cb87f4d 100644 --- a/react/package.json +++ b/react/package.json @@ -62,7 +62,7 @@ "@rollup/plugin-json": "^6.1.0", "@rollup/plugin-node-resolve": "^16.0.0", "@rollup/plugin-replace": "^6.0.2", - "@swc/cli": "^0.6.0", + "@swc/cli": "^0.7.3", "@swc/core": "^1.10.15", "@swc/helpers": "^0.5.15", "@types/react": "^18.2.79", From 1864dd81ab0f54b66677e91ec8dfd7b85e663f38 Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Tue, 17 Oct 2023 17:59:43 +0200 Subject: [PATCH 013/123] Move LanguageMenu HTML to template literal --- src/components/{LanguageMenu.html => LanguageMenu.html.ts} | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename src/components/{LanguageMenu.html => LanguageMenu.html.ts} (91%) diff --git a/src/components/LanguageMenu.html b/src/components/LanguageMenu.html.ts similarity index 91% rename from src/components/LanguageMenu.html rename to src/components/LanguageMenu.html.ts index 6f3e8238..5d66f412 100644 --- a/src/components/LanguageMenu.html +++ b/src/components/LanguageMenu.html.ts @@ -1,4 +1,4 @@ - +export default ` Language Subtitles - +`; From cf2379564af58cc440fc7f2c6dad0f8508888152 Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Tue, 17 Oct 2023 18:20:35 +0200 Subject: [PATCH 014/123] Use lit-html to render menu groups --- src/components/LanguageMenu.html.ts | 8 +++-- src/components/LanguageMenu.ts | 5 ++- src/components/MenuGroup.ts | 31 +++++++++---------- ...SettingsMenu.html => SettingsMenu.html.ts} | 4 +++ src/components/SettingsMenu.ts | 5 ++- ...leMenu.html => TextTrackStyleMenu.html.ts} | 4 +++ src/components/TextTrackStyleMenu.ts | 8 ++--- 7 files changed, 34 insertions(+), 31 deletions(-) rename src/components/{SettingsMenu.html => SettingsMenu.html.ts} (95%) rename src/components/{TextTrackStyleMenu.html => TextTrackStyleMenu.html.ts} (99%) diff --git a/src/components/LanguageMenu.html.ts b/src/components/LanguageMenu.html.ts index 5d66f412..bc7182a9 100644 --- a/src/components/LanguageMenu.html.ts +++ b/src/components/LanguageMenu.html.ts @@ -1,4 +1,7 @@ -export default ` +import { html } from 'lit-html'; + +export default html` + Language -`; + +`; diff --git a/src/components/LanguageMenu.ts b/src/components/LanguageMenu.ts index 251ce7a8..4a3cf6bf 100644 --- a/src/components/LanguageMenu.ts +++ b/src/components/LanguageMenu.ts @@ -7,13 +7,12 @@ import type { ChromelessPlayer, MediaTrack, MediaTrackList, TextTrack, TextTrack import { isNonForcedSubtitleTrack } from '../util/TrackUtils'; import { Attribute } from '../util/Attribute'; import { toggleAttribute } from '../util/CommonUtils'; -import { createTemplate } from '../util/TemplateUtils'; // Load components used in template import './TrackRadioGroup'; import './TextTrackStyleMenu'; -const template = createTemplate('theoplayer-language-menu', menuGroupTemplate(languageMenuHtml, languageMenuCss)); +const template = menuGroupTemplate(languageMenuHtml, languageMenuCss); const TRACK_EVENTS = ['addtrack', 'removetrack'] as const; @@ -34,7 +33,7 @@ export class LanguageMenu extends StateReceiverMixin(MenuGroup, ['player']) { } constructor() { - super({ template: template() }); + super({ template }); this._upgradeProperty('player'); } diff --git a/src/components/MenuGroup.ts b/src/components/MenuGroup.ts index 7e363ad0..d7a23878 100644 --- a/src/components/MenuGroup.ts +++ b/src/components/MenuGroup.ts @@ -1,16 +1,7 @@ import * as shadyCss from '@webcomponents/shadycss'; import menuGroupCss from './MenuGroup.css'; import { Attribute } from '../util/Attribute'; -import { - arrayFind, - arrayFindIndex, - fromArrayLike, - getSlottedElements, - isElement, - isHTMLElement, - noOp, - upgradeCustomElementIfNeeded -} from '../util/CommonUtils'; +import { arrayFind, arrayFindIndex, fromArrayLike, getSlottedElements, isHTMLElement, noOp, upgradeCustomElementIfNeeded } from '../util/CommonUtils'; import { CLOSE_MENU_EVENT, type CloseMenuEvent } from '../events/CloseMenuEvent'; import { TOGGLE_MENU_EVENT, type ToggleMenuEvent } from '../events/ToggleMenuEvent'; import { isBackKey } from '../util/KeyCode'; @@ -18,17 +9,23 @@ import { createCustomEvent } from '../util/EventUtils'; import type { MenuChangeEvent } from '../events/MenuChangeEvent'; import { MENU_CHANGE_EVENT } from '../events/MenuChangeEvent'; import { Menu } from './Menu'; -import { createTemplate } from '../util/TemplateUtils'; +import { html, render, type TemplateResult } from 'lit-html'; export interface MenuGroupOptions { - template?: HTMLTemplateElement; + template?: TemplateResult; } -export function menuGroupTemplate(content: string, extraCss: string = ''): string { - return `${content}`; +export function menuGroupTemplate(content: TemplateResult, extraCss: string | TemplateResult = ''): TemplateResult { + return html` + + ${content} + `; } -const defaultTemplate = createTemplate('theoplayer-menu-group', menuGroupTemplate(``)); +const defaultTemplate = menuGroupTemplate(html``); interface OpenMenuEntry { menu: Menu | MenuGroup; @@ -56,9 +53,9 @@ export class MenuGroup extends HTMLElement { constructor(options?: MenuGroupOptions) { super(); - const template = options?.template ?? defaultTemplate(); + const template = options?.template ?? defaultTemplate; const shadowRoot = this.attachShadow({ mode: 'open', delegatesFocus: true }); - shadowRoot.appendChild(template.content.cloneNode(true)); + render(template, shadowRoot); this._menuSlot = shadowRoot.querySelector('slot'); } diff --git a/src/components/SettingsMenu.html b/src/components/SettingsMenu.html.ts similarity index 95% rename from src/components/SettingsMenu.html rename to src/components/SettingsMenu.html.ts index 375248f8..a4d641b8 100644 --- a/src/components/SettingsMenu.html +++ b/src/components/SettingsMenu.html.ts @@ -1,3 +1,6 @@ +import { html } from 'lit-html'; + +export default html` Settings @@ -26,3 +29,4 @@ +`; diff --git a/src/components/SettingsMenu.ts b/src/components/SettingsMenu.ts index cb8f4132..4d63d389 100644 --- a/src/components/SettingsMenu.ts +++ b/src/components/SettingsMenu.ts @@ -1,14 +1,13 @@ import { MenuGroup, menuGroupTemplate } from './MenuGroup'; import settingsMenuHtml from './SettingsMenu.html'; import menuTableCss from './MenuTable.css'; -import { createTemplate } from '../util/TemplateUtils'; // Load components used in template import './ActiveQualityDisplay'; import './PlaybackRateDisplay'; import './PlaybackRateMenu'; -const template = createTemplate('theoplayer-settings-menu', menuGroupTemplate(settingsMenuHtml, menuTableCss)); +const template = menuGroupTemplate(settingsMenuHtml, menuTableCss); /** * `` - A menu to change the settings of the player, @@ -20,7 +19,7 @@ const template = createTemplate('theoplayer-settings-menu', menuGroupTemplate(se */ export class SettingsMenu extends MenuGroup { constructor() { - super({ template: template() }); + super({ template }); } } diff --git a/src/components/TextTrackStyleMenu.html b/src/components/TextTrackStyleMenu.html.ts similarity index 99% rename from src/components/TextTrackStyleMenu.html rename to src/components/TextTrackStyleMenu.html.ts index 4ea68ec7..11b2cca7 100644 --- a/src/components/TextTrackStyleMenu.html +++ b/src/components/TextTrackStyleMenu.html.ts @@ -1,3 +1,6 @@ +import { html } from 'lit-html'; + +export default html` Subtitle options @@ -184,3 +187,4 @@ Uniform +`; diff --git a/src/components/TextTrackStyleMenu.ts b/src/components/TextTrackStyleMenu.ts index 5758ccf8..76cd2f7d 100644 --- a/src/components/TextTrackStyleMenu.ts +++ b/src/components/TextTrackStyleMenu.ts @@ -2,16 +2,12 @@ import { MenuGroup, menuGroupTemplate } from './MenuGroup'; import textTrackStyleMenuHtml from './TextTrackStyleMenu.html'; import textTrackStyleMenuCss from './TextTrackStyleMenu.css'; import menuTableCss from './MenuTable.css'; -import { createTemplate } from '../util/TemplateUtils'; // Load components used in template import './TextTrackStyleDisplay'; import './TextTrackStyleRadioGroup'; -const template = createTemplate( - 'theoplayer-text-track-style-menu', - menuGroupTemplate(textTrackStyleMenuHtml, `${menuTableCss}\n${textTrackStyleMenuCss}`) -); +const template = menuGroupTemplate(textTrackStyleMenuHtml, `${menuTableCss}\n${textTrackStyleMenuCss}`); /** * `` - A menu to change the {@link theoplayer!TextTrackStyle | text track style} of the player. @@ -22,7 +18,7 @@ const template = createTemplate( */ export class TextTrackStyleMenu extends MenuGroup { constructor() { - super({ template: template() }); + super({ template }); } } From 73fa390860d4abf4060c5e87d793a4eb4ce3899b Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Wed, 23 Apr 2025 18:09:17 +0200 Subject: [PATCH 015/123] Run Prettier --- src/components/LanguageMenu.html.ts | 36 +-- src/components/SettingsMenu.html.ts | 56 ++-- src/components/TextTrackStyleMenu.html.ts | 374 +++++++++++----------- 3 files changed, 234 insertions(+), 232 deletions(-) diff --git a/src/components/LanguageMenu.html.ts b/src/components/LanguageMenu.html.ts index bc7182a9..23bd61ce 100644 --- a/src/components/LanguageMenu.html.ts +++ b/src/components/LanguageMenu.html.ts @@ -1,23 +1,23 @@ import { html } from 'lit-html'; export default html` - - Language - -
-
-

Audio

- + + Language + +
+
+

Audio

+ +
+
+

Subtitles

+ +
-
-

Subtitles

- -
-
- - + + `; diff --git a/src/components/SettingsMenu.html.ts b/src/components/SettingsMenu.html.ts index a4d641b8..a13afa85 100644 --- a/src/components/SettingsMenu.html.ts +++ b/src/components/SettingsMenu.html.ts @@ -1,32 +1,32 @@ import { html } from 'lit-html'; export default html` - - Settings -
- - - - - - - - -
Quality - - - -
Playback speed - - - -
-
- - + + Settings + + + + + + + + + +
Quality + + + +
Playback speed + + + +
+
+ + `; diff --git a/src/components/TextTrackStyleMenu.html.ts b/src/components/TextTrackStyleMenu.html.ts index 11b2cca7..8c7861aa 100644 --- a/src/components/TextTrackStyleMenu.html.ts +++ b/src/components/TextTrackStyleMenu.html.ts @@ -1,190 +1,192 @@ import { html } from 'lit-html'; export default html` - - Subtitle options - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Font family - - - -
Font color - - - -
Font opacity - - - -
Font size - - - -
Background color - - - -
Background opacity - - - -
Window color - - - -
Window opacity - - - -
Character edge style - - - -
-
- - - - - - - - - + + Subtitle options + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Font family + + + +
Font color + + + +
Font opacity + + + +
Font size + + + +
Background color + + + +
Background opacity + + + +
Window color + + + +
Window opacity + + + +
Character edge style + + + +
+
+ + + + + + + + + `; From bcbfa109bce61d7d999d156f6c8012790bc73eff Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Tue, 28 Jan 2025 16:55:48 +0100 Subject: [PATCH 016/123] Rework MenuGroup to use render() method --- src/components/LanguageMenu.ts | 35 ++++- src/components/MenuGroup.ts | 53 ++++--- src/components/SettingsMenu.ts | 42 +++++- src/components/TextTrackStyleMenu.ts | 207 ++++++++++++++++++++++++++- 4 files changed, 297 insertions(+), 40 deletions(-) diff --git a/src/components/LanguageMenu.ts b/src/components/LanguageMenu.ts index 4a3cf6bf..87abbc74 100644 --- a/src/components/LanguageMenu.ts +++ b/src/components/LanguageMenu.ts @@ -1,6 +1,6 @@ -import { MenuGroup, menuGroupTemplate } from './MenuGroup'; +import { html, type TemplateResult } from 'lit-html'; +import { MenuGroup } from './MenuGroup'; import * as shadyCss from '@webcomponents/shadycss'; -import languageMenuHtml from './LanguageMenu.html'; import languageMenuCss from './LanguageMenu.css'; import { StateReceiverMixin } from './StateReceiverMixin'; import type { ChromelessPlayer, MediaTrack, MediaTrackList, TextTrack, TextTracksList } from 'theoplayer/chromeless'; @@ -12,8 +12,6 @@ import { toggleAttribute } from '../util/CommonUtils'; import './TrackRadioGroup'; import './TextTrackStyleMenu'; -const template = menuGroupTemplate(languageMenuHtml, languageMenuCss); - const TRACK_EVENTS = ['addtrack', 'removetrack'] as const; /** @@ -33,7 +31,7 @@ export class LanguageMenu extends StateReceiverMixin(MenuGroup, ['player']) { } constructor() { - super({ template }); + super(); this._upgradeProperty('player'); } @@ -74,6 +72,33 @@ export class LanguageMenu extends StateReceiverMixin(MenuGroup, ['player']) { shadyCss.styleSubtree(this); } } + + protected override render(): TemplateResult { + return super.renderMenuGroup( + html` + + Language + +
+
+

Audio

+ +
+
+

Subtitles

+ +
+
+
+ + `, + languageMenuCss + ); + } } customElements.define('theoplayer-language-menu', LanguageMenu); diff --git a/src/components/MenuGroup.ts b/src/components/MenuGroup.ts index d7a23878..6e7d3483 100644 --- a/src/components/MenuGroup.ts +++ b/src/components/MenuGroup.ts @@ -11,22 +11,6 @@ import { MENU_CHANGE_EVENT } from '../events/MenuChangeEvent'; import { Menu } from './Menu'; import { html, render, type TemplateResult } from 'lit-html'; -export interface MenuGroupOptions { - template?: TemplateResult; -} - -export function menuGroupTemplate(content: TemplateResult, extraCss: string | TemplateResult = ''): TemplateResult { - return html` - - ${content} - `; -} - -const defaultTemplate = menuGroupTemplate(html``); - interface OpenMenuEntry { menu: Menu | MenuGroup; opener: HTMLElement | undefined; @@ -47,17 +31,14 @@ export class MenuGroup extends HTMLElement { return [Attribute.MENU_OPENED]; } - private readonly _menuSlot: HTMLSlotElement | null; + private _menuSlot: HTMLSlotElement | null = null; private _menus: Array = []; private readonly _openMenuStack: OpenMenuEntry[] = []; - constructor(options?: MenuGroupOptions) { + constructor() { super(); - const template = options?.template ?? defaultTemplate; - const shadowRoot = this.attachShadow({ mode: 'open', delegatesFocus: true }); - render(template, shadowRoot); - - this._menuSlot = shadowRoot.querySelector('slot'); + this.attachShadow({ mode: 'open', delegatesFocus: true }); + this.runRender(); } protected _upgradeProperty(prop: keyof this) { @@ -80,6 +61,9 @@ export class MenuGroup extends HTMLElement { this.shadowRoot!.addEventListener(TOGGLE_MENU_EVENT, this._onToggleMenu); this.shadowRoot!.addEventListener(CLOSE_MENU_EVENT, this._onCloseMenu); this.shadowRoot!.addEventListener(MENU_CHANGE_EVENT, this._onMenuChange); + + this.runRender(); + this._menuSlot = this.shadowRoot!.querySelector('slot'); this._menuSlot?.addEventListener('slotchange', this._onMenuListChange); } @@ -88,6 +72,7 @@ export class MenuGroup extends HTMLElement { this.shadowRoot!.removeEventListener(CLOSE_MENU_EVENT, this._onCloseMenu); this.shadowRoot!.removeEventListener(MENU_CHANGE_EVENT, this._onMenuChange); this._menuSlot?.removeEventListener('slotchange', this._onMenuListChange); + this._menuSlot = null; } attributeChangedCallback(attrName: string, oldValue: any, newValue: any) { @@ -112,6 +97,28 @@ export class MenuGroup extends HTMLElement { } } + private runRender(): void { + render(this.render(), this.shadowRoot!, { + host: this, + creationScope: this.ownerDocument, + isConnected: this.isConnected + }); + } + + protected render(): TemplateResult { + return this.renderMenuGroup(html``); + } + + protected renderMenuGroup(content: TemplateResult, extraCss: string | TemplateResult = ''): TemplateResult { + return html` + + ${content} + `; + } + /** * Get the menu with the given ID. * diff --git a/src/components/SettingsMenu.ts b/src/components/SettingsMenu.ts index 4d63d389..cdf51769 100644 --- a/src/components/SettingsMenu.ts +++ b/src/components/SettingsMenu.ts @@ -1,5 +1,5 @@ -import { MenuGroup, menuGroupTemplate } from './MenuGroup'; -import settingsMenuHtml from './SettingsMenu.html'; +import { html, type TemplateResult } from 'lit-html'; +import { MenuGroup } from './MenuGroup'; import menuTableCss from './MenuTable.css'; // Load components used in template @@ -7,8 +7,6 @@ import './ActiveQualityDisplay'; import './PlaybackRateDisplay'; import './PlaybackRateMenu'; -const template = menuGroupTemplate(settingsMenuHtml, menuTableCss); - /** * `` - A menu to change the settings of the player, * such as the active video quality and the playback speed. @@ -18,8 +16,40 @@ const template = menuGroupTemplate(settingsMenuHtml, menuTableCss); * @group Components */ export class SettingsMenu extends MenuGroup { - constructor() { - super({ template }); + protected override render(): TemplateResult { + return this.renderMenuGroup( + html` + + Settings + + + + + + + + + +
Quality + + + +
Playback speed + + + +
+
+ + + `, + menuTableCss + ); } } diff --git a/src/components/TextTrackStyleMenu.ts b/src/components/TextTrackStyleMenu.ts index 76cd2f7d..51ebb327 100644 --- a/src/components/TextTrackStyleMenu.ts +++ b/src/components/TextTrackStyleMenu.ts @@ -1,5 +1,5 @@ -import { MenuGroup, menuGroupTemplate } from './MenuGroup'; -import textTrackStyleMenuHtml from './TextTrackStyleMenu.html'; +import { html, type TemplateResult } from 'lit-html'; +import { MenuGroup } from './MenuGroup'; import textTrackStyleMenuCss from './TextTrackStyleMenu.css'; import menuTableCss from './MenuTable.css'; @@ -7,8 +7,6 @@ import menuTableCss from './MenuTable.css'; import './TextTrackStyleDisplay'; import './TextTrackStyleRadioGroup'; -const template = menuGroupTemplate(textTrackStyleMenuHtml, `${menuTableCss}\n${textTrackStyleMenuCss}`); - /** * `` - A menu to change the {@link theoplayer!TextTrackStyle | text track style} of the player. * @@ -17,8 +15,205 @@ const template = menuGroupTemplate(textTrackStyleMenuHtml, `${menuTableCss}\n${t * @group Components */ export class TextTrackStyleMenu extends MenuGroup { - constructor() { - super({ template }); + protected override render(): TemplateResult { + return super.renderMenuGroup( + html` + + Subtitle options + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Font family + + + +
Font color + + + +
Font opacity + + + +
Font size + + + +
Background color + + + +
Background opacity + + + +
Window color + + + +
Window opacity + + + +
Character edge style + + + +
+
+ + + + + + + + + + `, + `${menuTableCss}\n${textTrackStyleMenuCss}` + ); } } From 9be3d5b7cee651e980d27e5261c2d878d6a50549 Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Tue, 28 Jan 2025 18:03:29 +0100 Subject: [PATCH 017/123] Rework MenuGroup to a LitElement --- src/components/LanguageMenu.ts | 6 ++-- src/components/MenuGroup.ts | 48 ++++++++-------------------- src/components/SettingsMenu.ts | 6 ++-- src/components/TextTrackStyleMenu.ts | 6 ++-- 4 files changed, 23 insertions(+), 43 deletions(-) diff --git a/src/components/LanguageMenu.ts b/src/components/LanguageMenu.ts index 87abbc74..7fb04dbb 100644 --- a/src/components/LanguageMenu.ts +++ b/src/components/LanguageMenu.ts @@ -1,4 +1,5 @@ -import { html, type TemplateResult } from 'lit-html'; +import { html, type TemplateResult } from 'lit'; +import { customElement } from 'lit/decorators.js'; import { MenuGroup } from './MenuGroup'; import * as shadyCss from '@webcomponents/shadycss'; import languageMenuCss from './LanguageMenu.css'; @@ -21,6 +22,7 @@ const TRACK_EVENTS = ['addtrack', 'removetrack'] as const; * * @group Components */ +@customElement('theoplayer-language-menu') export class LanguageMenu extends StateReceiverMixin(MenuGroup, ['player']) { private _player: ChromelessPlayer | undefined; private _audioTrackList: MediaTrackList | undefined; @@ -101,8 +103,6 @@ export class LanguageMenu extends StateReceiverMixin(MenuGroup, ['player']) { } } -customElements.define('theoplayer-language-menu', LanguageMenu); - declare global { interface HTMLElementTagNameMap { 'theoplayer-language-menu': LanguageMenu; diff --git a/src/components/MenuGroup.ts b/src/components/MenuGroup.ts index 6e7d3483..c1d76b98 100644 --- a/src/components/MenuGroup.ts +++ b/src/components/MenuGroup.ts @@ -1,4 +1,5 @@ -import * as shadyCss from '@webcomponents/shadycss'; +import { html, LitElement, type TemplateResult } from 'lit'; +import { customElement, query } from 'lit/decorators.js'; import menuGroupCss from './MenuGroup.css'; import { Attribute } from '../util/Attribute'; import { arrayFind, arrayFindIndex, fromArrayLike, getSlottedElements, isHTMLElement, noOp, upgradeCustomElementIfNeeded } from '../util/CommonUtils'; @@ -9,7 +10,6 @@ import { createCustomEvent } from '../util/EventUtils'; import type { MenuChangeEvent } from '../events/MenuChangeEvent'; import { MENU_CHANGE_EVENT } from '../events/MenuChangeEvent'; import { Menu } from './Menu'; -import { html, render, type TemplateResult } from 'lit-html'; interface OpenMenuEntry { menu: Menu | MenuGroup; @@ -26,21 +26,17 @@ interface OpenMenuEntry { * @attribute `menu-opened` (readonly) - Whether any menu in the group is currently open. * @group Components */ -export class MenuGroup extends HTMLElement { +@customElement('theoplayer-menu-group') +export class MenuGroup extends LitElement { static get observedAttributes() { - return [Attribute.MENU_OPENED]; + return [...LitElement.observedAttributes, Attribute.MENU_OPENED]; } - private _menuSlot: HTMLSlotElement | null = null; + @query('slot') + private accessor _menuSlot: HTMLSlotElement | null = null; private _menus: Array = []; private readonly _openMenuStack: OpenMenuEntry[] = []; - constructor() { - super(); - this.attachShadow({ mode: 'open', delegatesFocus: true }); - this.runRender(); - } - protected _upgradeProperty(prop: keyof this) { if (this.hasOwnProperty(prop)) { let value = this[prop]; @@ -50,32 +46,25 @@ export class MenuGroup extends HTMLElement { } connectedCallback(): void { - shadyCss.styleElement(this); + super.connectedCallback(); if (!this.hasAttribute(Attribute.MENU_OPENED)) { this.setAttribute('hidden', ''); } - this._onMenuListChange(); - this.shadowRoot!.addEventListener(TOGGLE_MENU_EVENT, this._onToggleMenu); this.shadowRoot!.addEventListener(CLOSE_MENU_EVENT, this._onCloseMenu); this.shadowRoot!.addEventListener(MENU_CHANGE_EVENT, this._onMenuChange); - - this.runRender(); - this._menuSlot = this.shadowRoot!.querySelector('slot'); - this._menuSlot?.addEventListener('slotchange', this._onMenuListChange); } disconnectedCallback(): void { this.shadowRoot!.removeEventListener(TOGGLE_MENU_EVENT, this._onToggleMenu); this.shadowRoot!.removeEventListener(CLOSE_MENU_EVENT, this._onCloseMenu); this.shadowRoot!.removeEventListener(MENU_CHANGE_EVENT, this._onMenuChange); - this._menuSlot?.removeEventListener('slotchange', this._onMenuListChange); - this._menuSlot = null; } attributeChangedCallback(attrName: string, oldValue: any, newValue: any) { + super.attributeChangedCallback(attrName, oldValue, newValue); if (newValue === oldValue) { return; } @@ -92,21 +81,10 @@ export class MenuGroup extends HTMLElement { const changeEvent: MenuChangeEvent = createCustomEvent(MENU_CHANGE_EVENT, { bubbles: true }); this.dispatchEvent(changeEvent); } - if (MenuGroup.observedAttributes.indexOf(attrName as Attribute) >= 0) { - shadyCss.styleSubtree(this); - } - } - - private runRender(): void { - render(this.render(), this.shadowRoot!, { - host: this, - creationScope: this.ownerDocument, - isConnected: this.isConnected - }); } protected render(): TemplateResult { - return this.renderMenuGroup(html``); + return this.renderMenuGroup(html``); } protected renderMenuGroup(content: TemplateResult, extraCss: string | TemplateResult = ''): TemplateResult { @@ -119,6 +97,10 @@ export class MenuGroup extends HTMLElement { `; } + protected override firstUpdated(): void { + this._onMenuListChange(); + } + /** * Get the menu with the given ID. * @@ -343,8 +325,6 @@ export class MenuGroup extends HTMLElement { }; } -customElements.define('theoplayer-menu-group', MenuGroup); - declare global { interface HTMLElementTagNameMap { 'theoplayer-menu-group': MenuGroup; diff --git a/src/components/SettingsMenu.ts b/src/components/SettingsMenu.ts index cdf51769..831c8200 100644 --- a/src/components/SettingsMenu.ts +++ b/src/components/SettingsMenu.ts @@ -1,4 +1,5 @@ -import { html, type TemplateResult } from 'lit-html'; +import { html, type TemplateResult } from 'lit'; +import { customElement } from 'lit/decorators.js'; import { MenuGroup } from './MenuGroup'; import menuTableCss from './MenuTable.css'; @@ -15,6 +16,7 @@ import './PlaybackRateMenu'; * * @group Components */ +@customElement('theoplayer-settings-menu') export class SettingsMenu extends MenuGroup { protected override render(): TemplateResult { return this.renderMenuGroup( @@ -53,8 +55,6 @@ export class SettingsMenu extends MenuGroup { } } -customElements.define('theoplayer-settings-menu', SettingsMenu); - declare global { interface HTMLElementTagNameMap { 'theoplayer-settings-menu': SettingsMenu; diff --git a/src/components/TextTrackStyleMenu.ts b/src/components/TextTrackStyleMenu.ts index 51ebb327..cca81546 100644 --- a/src/components/TextTrackStyleMenu.ts +++ b/src/components/TextTrackStyleMenu.ts @@ -1,4 +1,5 @@ -import { html, type TemplateResult } from 'lit-html'; +import { html, type TemplateResult } from 'lit'; +import { customElement } from 'lit/decorators.js'; import { MenuGroup } from './MenuGroup'; import textTrackStyleMenuCss from './TextTrackStyleMenu.css'; import menuTableCss from './MenuTable.css'; @@ -14,6 +15,7 @@ import './TextTrackStyleRadioGroup'; * * @group Components */ +@customElement('theoplayer-text-track-style-menu') export class TextTrackStyleMenu extends MenuGroup { protected override render(): TemplateResult { return super.renderMenuGroup( @@ -217,8 +219,6 @@ export class TextTrackStyleMenu extends MenuGroup { } } -customElements.define('theoplayer-text-track-style-menu', TextTrackStyleMenu); - declare global { interface HTMLElementTagNameMap { 'theoplayer-text-track-style-menu': TextTrackStyleMenu; From 83587a911bdf385b90aed7ccd7ec103e7816c733 Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Tue, 28 Jan 2025 18:09:39 +0100 Subject: [PATCH 018/123] Use createRenderRoot to attach listeners --- src/components/MenuGroup.ts | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/components/MenuGroup.ts b/src/components/MenuGroup.ts index c1d76b98..ffb36d14 100644 --- a/src/components/MenuGroup.ts +++ b/src/components/MenuGroup.ts @@ -51,16 +51,14 @@ export class MenuGroup extends LitElement { if (!this.hasAttribute(Attribute.MENU_OPENED)) { this.setAttribute('hidden', ''); } - - this.shadowRoot!.addEventListener(TOGGLE_MENU_EVENT, this._onToggleMenu); - this.shadowRoot!.addEventListener(CLOSE_MENU_EVENT, this._onCloseMenu); - this.shadowRoot!.addEventListener(MENU_CHANGE_EVENT, this._onMenuChange); } - disconnectedCallback(): void { - this.shadowRoot!.removeEventListener(TOGGLE_MENU_EVENT, this._onToggleMenu); - this.shadowRoot!.removeEventListener(CLOSE_MENU_EVENT, this._onCloseMenu); - this.shadowRoot!.removeEventListener(MENU_CHANGE_EVENT, this._onMenuChange); + protected createRenderRoot() { + const root = super.createRenderRoot(); + root.addEventListener(TOGGLE_MENU_EVENT, this._onToggleMenu); + root.addEventListener(CLOSE_MENU_EVENT, this._onCloseMenu); + root.addEventListener(MENU_CHANGE_EVENT, this._onMenuChange); + return root; } attributeChangedCallback(attrName: string, oldValue: any, newValue: any) { From 9565468d93b4834e42e1dd36a4bea11b0cfb957f Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Tue, 28 Jan 2025 18:24:44 +0100 Subject: [PATCH 019/123] Use `@property` for menu-opened attribute --- src/components/MenuGroup.ts | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/src/components/MenuGroup.ts b/src/components/MenuGroup.ts index ffb36d14..d79b01cc 100644 --- a/src/components/MenuGroup.ts +++ b/src/components/MenuGroup.ts @@ -1,5 +1,5 @@ import { html, LitElement, type TemplateResult } from 'lit'; -import { customElement, query } from 'lit/decorators.js'; +import { customElement, property, query } from 'lit/decorators.js'; import menuGroupCss from './MenuGroup.css'; import { Attribute } from '../util/Attribute'; import { arrayFind, arrayFindIndex, fromArrayLike, getSlottedElements, isHTMLElement, noOp, upgradeCustomElementIfNeeded } from '../util/CommonUtils'; @@ -28,10 +28,8 @@ interface OpenMenuEntry { */ @customElement('theoplayer-menu-group') export class MenuGroup extends LitElement { - static get observedAttributes() { - return [...LitElement.observedAttributes, Attribute.MENU_OPENED]; - } - + @property({ reflect: true, type: Boolean, attribute: Attribute.MENU_OPENED }) + private accessor _menuOpened: boolean = false; @query('slot') private accessor _menuSlot: HTMLSlotElement | null = null; private _menus: Array = []; @@ -48,7 +46,7 @@ export class MenuGroup extends LitElement { connectedCallback(): void { super.connectedCallback(); - if (!this.hasAttribute(Attribute.MENU_OPENED)) { + if (!this._menuOpened) { this.setAttribute('hidden', ''); } } @@ -67,7 +65,7 @@ export class MenuGroup extends LitElement { return; } if (attrName === Attribute.MENU_OPENED) { - const hasValue = newValue != null; + const hasValue = this._menuOpened; if (hasValue) { this.removeAttribute('hidden'); this.removeEventListener('keydown', this._onKeyDown); @@ -147,7 +145,7 @@ export class MenuGroup extends LitElement { previousEntry.menu.closeMenu(); } menuToOpen.openMenu(); - this.setAttribute(Attribute.MENU_OPENED, ''); + this._menuOpened = true; menuToOpen.focus(); return true; @@ -184,7 +182,7 @@ export class MenuGroup extends LitElement { const nextEntry = this.getCurrentMenu_(); if (nextEntry !== undefined) { nextEntry.menu.openMenu(); - this.setAttribute(Attribute.MENU_OPENED, ''); + this._menuOpened = true; if (oldEntry.opener && nextEntry.menu.contains(oldEntry.opener)) { oldEntry.opener.focus(); } else { @@ -193,7 +191,7 @@ export class MenuGroup extends LitElement { return true; } - this.removeAttribute(Attribute.MENU_OPENED); + this._menuOpened = false; oldEntry.opener?.focus(); return true; } From 2aa7ac05c37e7d952c7152aa01915f3a1c9bdb3f Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Tue, 28 Jan 2025 11:43:31 +0100 Subject: [PATCH 020/123] Add source select to example --- examples/default-ui.html | 58 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 54 insertions(+), 4 deletions(-) diff --git a/examples/default-ui.html b/examples/default-ui.html index bd5b9752..fc8463a6 100644 --- a/examples/default-ui.html +++ b/examples/default-ui.html @@ -44,9 +44,59 @@

Default UI

- + +
+ +
+ From 0afe46d56032dcb5e441ea17cba27510b2682864 Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Wed, 23 Apr 2025 18:32:07 +0200 Subject: [PATCH 021/123] Rework MenuGroup.menuOpened_ --- src/components/MenuGroup.ts | 60 ++++++++++++++++--------------------- 1 file changed, 26 insertions(+), 34 deletions(-) diff --git a/src/components/MenuGroup.ts b/src/components/MenuGroup.ts index d79b01cc..6b73b5e9 100644 --- a/src/components/MenuGroup.ts +++ b/src/components/MenuGroup.ts @@ -28,25 +28,37 @@ interface OpenMenuEntry { */ @customElement('theoplayer-menu-group') export class MenuGroup extends LitElement { - @property({ reflect: true, type: Boolean, attribute: Attribute.MENU_OPENED }) - private accessor _menuOpened: boolean = false; + private _menuOpened: boolean = false; + + private get menuOpened_(): boolean { + return this._menuOpened; + } + + @property({ reflect: true, state: true, type: Boolean, attribute: Attribute.MENU_OPENED }) + private set menuOpened_(menuOpened: boolean) { + if (this._menuOpened === menuOpened) return; + this._menuOpened = menuOpened; + if (menuOpened) { + this.removeAttribute('hidden'); + this.removeEventListener('keydown', this._onKeyDown); + this.addEventListener('keydown', this._onKeyDown); + } else { + this.setAttribute('hidden', ''); + this.removeEventListener('keydown', this._onKeyDown); + } + const changeEvent: MenuChangeEvent = createCustomEvent(MENU_CHANGE_EVENT, { bubbles: true }); + this.dispatchEvent(changeEvent); + } + @query('slot') private accessor _menuSlot: HTMLSlotElement | null = null; private _menus: Array = []; private readonly _openMenuStack: OpenMenuEntry[] = []; - protected _upgradeProperty(prop: keyof this) { - if (this.hasOwnProperty(prop)) { - let value = this[prop]; - delete this[prop]; - this[prop] = value; - } - } - connectedCallback(): void { super.connectedCallback(); - if (!this._menuOpened) { + if (!this.menuOpened_) { this.setAttribute('hidden', ''); } } @@ -59,26 +71,6 @@ export class MenuGroup extends LitElement { return root; } - attributeChangedCallback(attrName: string, oldValue: any, newValue: any) { - super.attributeChangedCallback(attrName, oldValue, newValue); - if (newValue === oldValue) { - return; - } - if (attrName === Attribute.MENU_OPENED) { - const hasValue = this._menuOpened; - if (hasValue) { - this.removeAttribute('hidden'); - this.removeEventListener('keydown', this._onKeyDown); - this.addEventListener('keydown', this._onKeyDown); - } else { - this.setAttribute('hidden', ''); - this.removeEventListener('keydown', this._onKeyDown); - } - const changeEvent: MenuChangeEvent = createCustomEvent(MENU_CHANGE_EVENT, { bubbles: true }); - this.dispatchEvent(changeEvent); - } - } - protected render(): TemplateResult { return this.renderMenuGroup(html``); } @@ -145,7 +137,7 @@ export class MenuGroup extends LitElement { previousEntry.menu.closeMenu(); } menuToOpen.openMenu(); - this._menuOpened = true; + this.menuOpened_ = true; menuToOpen.focus(); return true; @@ -182,7 +174,7 @@ export class MenuGroup extends LitElement { const nextEntry = this.getCurrentMenu_(); if (nextEntry !== undefined) { nextEntry.menu.openMenu(); - this._menuOpened = true; + this.menuOpened_ = true; if (oldEntry.opener && nextEntry.menu.contains(oldEntry.opener)) { oldEntry.opener.focus(); } else { @@ -191,7 +183,7 @@ export class MenuGroup extends LitElement { return true; } - this._menuOpened = false; + this.menuOpened_ = false; oldEntry.opener?.focus(); return true; } From 2c89f59db48a8f854e9c22623cf701b2af1154bb Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Wed, 23 Apr 2025 18:32:19 +0200 Subject: [PATCH 022/123] Simplify LanguageMenu.player --- src/components/LanguageMenu.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/components/LanguageMenu.ts b/src/components/LanguageMenu.ts index 7fb04dbb..fa39902a 100644 --- a/src/components/LanguageMenu.ts +++ b/src/components/LanguageMenu.ts @@ -1,5 +1,5 @@ import { html, type TemplateResult } from 'lit'; -import { customElement } from 'lit/decorators.js'; +import { customElement, property } from 'lit/decorators.js'; import { MenuGroup } from './MenuGroup'; import * as shadyCss from '@webcomponents/shadycss'; import languageMenuCss from './LanguageMenu.css'; @@ -32,15 +32,11 @@ export class LanguageMenu extends StateReceiverMixin(MenuGroup, ['player']) { return [...MenuGroup.observedAttributes, Attribute.HAS_AUDIO, Attribute.HAS_SUBTITLES]; } - constructor() { - super(); - this._upgradeProperty('player'); - } - get player(): ChromelessPlayer | undefined { return this._player; } + @property({ state: true }) set player(player: ChromelessPlayer | undefined) { if (this._player === player) { return; From 3a0d7d8701fbc6ea0979f382cc5c7a0d0cfe1f84 Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Wed, 23 Apr 2025 18:44:07 +0200 Subject: [PATCH 023/123] Add `@stateReceiver` decorator --- src/components/LanguageMenu.ts | 5 +++-- src/components/StateReceiverMixin.ts | 20 ++++++++++++++++++++ 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/src/components/LanguageMenu.ts b/src/components/LanguageMenu.ts index fa39902a..6303340f 100644 --- a/src/components/LanguageMenu.ts +++ b/src/components/LanguageMenu.ts @@ -3,7 +3,7 @@ import { customElement, property } from 'lit/decorators.js'; import { MenuGroup } from './MenuGroup'; import * as shadyCss from '@webcomponents/shadycss'; import languageMenuCss from './LanguageMenu.css'; -import { StateReceiverMixin } from './StateReceiverMixin'; +import { stateReceiver } from './StateReceiverMixin'; import type { ChromelessPlayer, MediaTrack, MediaTrackList, TextTrack, TextTracksList } from 'theoplayer/chromeless'; import { isNonForcedSubtitleTrack } from '../util/TrackUtils'; import { Attribute } from '../util/Attribute'; @@ -23,7 +23,8 @@ const TRACK_EVENTS = ['addtrack', 'removetrack'] as const; * @group Components */ @customElement('theoplayer-language-menu') -export class LanguageMenu extends StateReceiverMixin(MenuGroup, ['player']) { +@stateReceiver(['player']) +export class LanguageMenu extends MenuGroup { private _player: ChromelessPlayer | undefined; private _audioTrackList: MediaTrackList | undefined; private _textTrackList: TextTracksList | undefined; diff --git a/src/components/StateReceiverMixin.ts b/src/components/StateReceiverMixin.ts index b070f8d6..bef44de9 100644 --- a/src/components/StateReceiverMixin.ts +++ b/src/components/StateReceiverMixin.ts @@ -38,6 +38,26 @@ export function isStateReceiverElement(element: Element): element is StateReceiv return StateReceiverProps in element && isArray((element as Partial)[StateReceiverProps]); } +/** + * Decorates a custom element class, such that it will automatically receive selected state updates + * from an ancestor {@link UIContainer | ``} element. + * + * For each property name in `props`, the custom element *MUST* implement a corresponding property with a setter. + * For example, if `props` equals `["player", "fullscreen"]`, then the element must have writable `player` and `fullscreen` + * properties. + * + * @param props - The names of the properties this element will receive. + * @returns A class decorator. + * @see {@link StateReceiverElement} + */ +export const stateReceiver = + (props: Array) => + (target: Constructor, _context: ClassDecoratorContext>): void => { + Object.defineProperty(target.prototype, StateReceiverProps, { + value: props + }); + }; + /** * A [mixin class](https://www.typescriptlang.org/docs/handbook/mixins.html) to apply on the superclass of a custom element, * such that it will automatically receive selected state updates from an ancestor {@link UIContainer | ``} element. From 250a3a1d5043a548bdc5256157630c1d0fdd847b Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Wed, 23 Apr 2025 18:48:01 +0200 Subject: [PATCH 024/123] Clean up --- src/components/LanguageMenu.html.ts | 23 --- src/components/SettingsMenu.html.ts | 32 ---- src/components/TextTrackStyleMenu.html.ts | 192 ---------------------- 3 files changed, 247 deletions(-) delete mode 100644 src/components/LanguageMenu.html.ts delete mode 100644 src/components/SettingsMenu.html.ts delete mode 100644 src/components/TextTrackStyleMenu.html.ts diff --git a/src/components/LanguageMenu.html.ts b/src/components/LanguageMenu.html.ts deleted file mode 100644 index 23bd61ce..00000000 --- a/src/components/LanguageMenu.html.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { html } from 'lit-html'; - -export default html` - - Language - -
-
-

Audio

- -
-
-

Subtitles

- -
-
-
- -`; diff --git a/src/components/SettingsMenu.html.ts b/src/components/SettingsMenu.html.ts deleted file mode 100644 index a13afa85..00000000 --- a/src/components/SettingsMenu.html.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { html } from 'lit-html'; - -export default html` - - Settings - - - - - - - - - -
Quality - - - -
Playback speed - - - -
-
- - -`; diff --git a/src/components/TextTrackStyleMenu.html.ts b/src/components/TextTrackStyleMenu.html.ts deleted file mode 100644 index 8c7861aa..00000000 --- a/src/components/TextTrackStyleMenu.html.ts +++ /dev/null @@ -1,192 +0,0 @@ -import { html } from 'lit-html'; - -export default html` - - Subtitle options - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Font family - - - -
Font color - - - -
Font opacity - - - -
Font size - - - -
Background color - - - -
Background opacity - - - -
Window color - - - -
Window opacity - - - -
Character edge style - - - -
-
- - - - - - - - - -`; From c3e230d6143d8c7fd7d16fbc8a7e0857415c1caf Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Tue, 17 Oct 2023 17:57:35 +0200 Subject: [PATCH 025/123] Minify HTML literals --- package-lock.json | 226 ++++++++++++++++++++++++++++++++++++++++ package.json | 1 + rollup.config.mjs | 18 +++- scripts/minify-html.mjs | 4 +- 4 files changed, 243 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index 235d4ba1..066ebff5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -39,6 +39,7 @@ "prettier": "^3.6.2", "rollup": "^4.34.6", "rollup-plugin-dts": "^6.1.1", + "rollup-plugin-minify-html-literals-v3": "^1.3.3", "rollup-plugin-postcss": "^4.0.2", "rollup-plugin-string": "^3.0.0", "rollup-plugin-swc3": "^0.12.1", @@ -5543,6 +5544,42 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/minify-html-literals": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/minify-html-literals/-/minify-html-literals-1.3.5.tgz", + "integrity": "sha512-p8T8ryePRR8FVfJZLVFmM53WY25FL0moCCTycUDuAu6rf9GMLwy0gNjXBGNin3Yun7Y+tIWd28axOf0t2EpAlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/html-minifier": "^3.5.3", + "clean-css": "^4.2.1", + "html-minifier": "^4.0.0", + "magic-string": "^0.25.0", + "parse-literals": "^1.2.1" + } + }, + "node_modules/minify-html-literals/node_modules/@types/html-minifier": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/@types/html-minifier/-/html-minifier-3.5.3.tgz", + "integrity": "sha512-j1P/4PcWVVCPEy5lofcHnQ6BtXz9tHGiFPWzqm7TtGuWZEfCHEP446HlkSNc9fQgNJaJZ6ewPtp2aaFla/Uerg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/clean-css": "*", + "@types/relateurl": "*", + "@types/uglify-js": "*" + } + }, + "node_modules/minify-html-literals/node_modules/magic-string": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz", + "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "sourcemap-codec": "^1.4.8" + } + }, "node_modules/minimatch": { "version": "9.0.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", @@ -5834,6 +5871,30 @@ "no-case": "^2.2.0" } }, + "node_modules/parse-literals": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/parse-literals/-/parse-literals-1.2.1.tgz", + "integrity": "sha512-Ml0w104Ph2wwzuRdxrg9booVWsngXbB4bZ5T2z6WyF8b5oaNkUmBiDtahi34yUIpXD8Y13JjAK6UyIyApJ73RQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "typescript": "^2.9.2 || ^3.0.0 || ^4.0.0" + } + }, + "node_modules/parse-literals/node_modules/typescript": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -7743,6 +7804,20 @@ "typescript": "^4.5 || ^5.0" } }, + "node_modules/rollup-plugin-minify-html-literals-v3": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/rollup-plugin-minify-html-literals-v3/-/rollup-plugin-minify-html-literals-v3-1.3.4.tgz", + "integrity": "sha512-wPHvQrm31Di7Bi5Fc/R+jogxNQYgxVojSmRVSn6gp29DTgyRlfrtaw10A29nX8g1I0JDqkQbtk2cIBweqtOCLA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.1.0", + "minify-html-literals": "^1.3.5", + "rollup": "^4.9.1", + "tslib": "^2.6.2", + "typescript": "^5.3.3" + } + }, "node_modules/rollup-plugin-postcss": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/rollup-plugin-postcss/-/rollup-plugin-postcss-4.0.2.tgz", @@ -8235,6 +8310,14 @@ "node": ">=0.10.0" } }, + "node_modules/sourcemap-codec": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", + "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", + "deprecated": "Please use @jridgewell/sourcemap-codec instead", + "dev": true, + "license": "MIT" + }, "node_modules/spawndamnit": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/spawndamnit/-/spawndamnit-3.0.1.tgz", @@ -10353,6 +10436,7 @@ "prettier": "^3.6.2", "rollup": "^4.34.6", "rollup-plugin-dts": "^6.1.1", + "rollup-plugin-minify-html-literals-v3": "^1.3.3", "rollup-plugin-postcss": "^4.0.2", "rollup-plugin-string": "^3.0.0", "rollup-plugin-swc3": "^0.12.1", @@ -13673,6 +13757,41 @@ "integrity": "sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg==", "dev": true }, + "minify-html-literals": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/minify-html-literals/-/minify-html-literals-1.3.5.tgz", + "integrity": "sha512-p8T8ryePRR8FVfJZLVFmM53WY25FL0moCCTycUDuAu6rf9GMLwy0gNjXBGNin3Yun7Y+tIWd28axOf0t2EpAlQ==", + "dev": true, + "requires": { + "@types/html-minifier": "^3.5.3", + "clean-css": "^4.2.1", + "html-minifier": "^4.0.0", + "magic-string": "^0.25.0", + "parse-literals": "^1.2.1" + }, + "dependencies": { + "@types/html-minifier": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/@types/html-minifier/-/html-minifier-3.5.3.tgz", + "integrity": "sha512-j1P/4PcWVVCPEy5lofcHnQ6BtXz9tHGiFPWzqm7TtGuWZEfCHEP446HlkSNc9fQgNJaJZ6ewPtp2aaFla/Uerg==", + "dev": true, + "requires": { + "@types/clean-css": "*", + "@types/relateurl": "*", + "@types/uglify-js": "*" + } + }, + "magic-string": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz", + "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==", + "dev": true, + "requires": { + "sourcemap-codec": "^1.4.8" + } + } + } + }, "minimatch": { "version": "9.0.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", @@ -13866,6 +13985,23 @@ "no-case": "^2.2.0" } }, + "parse-literals": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/parse-literals/-/parse-literals-1.2.1.tgz", + "integrity": "sha512-Ml0w104Ph2wwzuRdxrg9booVWsngXbB4bZ5T2z6WyF8b5oaNkUmBiDtahi34yUIpXD8Y13JjAK6UyIyApJ73RQ==", + "dev": true, + "requires": { + "typescript": "^2.9.2 || ^3.0.0 || ^4.0.0" + }, + "dependencies": { + "typescript": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "dev": true + } + } + }, "path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -14929,6 +15065,19 @@ "magic-string": "^0.30.10" } }, + "rollup-plugin-minify-html-literals-v3": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/rollup-plugin-minify-html-literals-v3/-/rollup-plugin-minify-html-literals-v3-1.3.4.tgz", + "integrity": "sha512-wPHvQrm31Di7Bi5Fc/R+jogxNQYgxVojSmRVSn6gp29DTgyRlfrtaw10A29nX8g1I0JDqkQbtk2cIBweqtOCLA==", + "dev": true, + "requires": { + "@rollup/pluginutils": "^5.1.0", + "minify-html-literals": "^1.3.5", + "rollup": "^4.9.1", + "tslib": "^2.6.2", + "typescript": "^5.3.3" + } + }, "rollup-plugin-postcss": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/rollup-plugin-postcss/-/rollup-plugin-postcss-4.0.2.tgz", @@ -15270,6 +15419,12 @@ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", "dev": true }, + "sourcemap-codec": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", + "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", + "dev": true + }, "spawndamnit": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/spawndamnit/-/spawndamnit-3.0.1.tgz", @@ -17605,6 +17760,41 @@ "integrity": "sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg==", "dev": true }, + "minify-html-literals": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/minify-html-literals/-/minify-html-literals-1.3.5.tgz", + "integrity": "sha512-p8T8ryePRR8FVfJZLVFmM53WY25FL0moCCTycUDuAu6rf9GMLwy0gNjXBGNin3Yun7Y+tIWd28axOf0t2EpAlQ==", + "dev": true, + "requires": { + "@types/html-minifier": "^3.5.3", + "clean-css": "^4.2.1", + "html-minifier": "^4.0.0", + "magic-string": "^0.25.0", + "parse-literals": "^1.2.1" + }, + "dependencies": { + "@types/html-minifier": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/@types/html-minifier/-/html-minifier-3.5.3.tgz", + "integrity": "sha512-j1P/4PcWVVCPEy5lofcHnQ6BtXz9tHGiFPWzqm7TtGuWZEfCHEP446HlkSNc9fQgNJaJZ6ewPtp2aaFla/Uerg==", + "dev": true, + "requires": { + "@types/clean-css": "*", + "@types/relateurl": "*", + "@types/uglify-js": "*" + } + }, + "magic-string": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz", + "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==", + "dev": true, + "requires": { + "sourcemap-codec": "^1.4.8" + } + } + } + }, "minimatch": { "version": "9.0.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", @@ -17798,6 +17988,23 @@ "no-case": "^2.2.0" } }, + "parse-literals": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/parse-literals/-/parse-literals-1.2.1.tgz", + "integrity": "sha512-Ml0w104Ph2wwzuRdxrg9booVWsngXbB4bZ5T2z6WyF8b5oaNkUmBiDtahi34yUIpXD8Y13JjAK6UyIyApJ73RQ==", + "dev": true, + "requires": { + "typescript": "^2.9.2 || ^3.0.0 || ^4.0.0" + }, + "dependencies": { + "typescript": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "dev": true + } + } + }, "path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -18861,6 +19068,19 @@ "magic-string": "^0.30.10" } }, + "rollup-plugin-minify-html-literals-v3": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/rollup-plugin-minify-html-literals-v3/-/rollup-plugin-minify-html-literals-v3-1.3.4.tgz", + "integrity": "sha512-wPHvQrm31Di7Bi5Fc/R+jogxNQYgxVojSmRVSn6gp29DTgyRlfrtaw10A29nX8g1I0JDqkQbtk2cIBweqtOCLA==", + "dev": true, + "requires": { + "@rollup/pluginutils": "^5.1.0", + "minify-html-literals": "^1.3.5", + "rollup": "^4.9.1", + "tslib": "^2.6.2", + "typescript": "^5.3.3" + } + }, "rollup-plugin-postcss": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/rollup-plugin-postcss/-/rollup-plugin-postcss-4.0.2.tgz", @@ -19202,6 +19422,12 @@ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", "dev": true }, + "sourcemap-codec": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", + "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", + "dev": true + }, "spawndamnit": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/spawndamnit/-/spawndamnit-3.0.1.tgz", diff --git a/package.json b/package.json index 3cfbbead..88f46f94 100644 --- a/package.json +++ b/package.json @@ -83,6 +83,7 @@ "prettier": "^3.6.2", "rollup": "^4.34.6", "rollup-plugin-dts": "^6.1.1", + "rollup-plugin-minify-html-literals-v3": "^1.3.3", "rollup-plugin-postcss": "^4.0.2", "rollup-plugin-string": "^3.0.0", "rollup-plugin-swc3": "^0.12.1", diff --git a/rollup.config.mjs b/rollup.config.mjs index a60a2402..0df01744 100644 --- a/rollup.config.mjs +++ b/rollup.config.mjs @@ -2,6 +2,7 @@ import { defineConfig } from 'rollup'; import nodeResolve from '@rollup/plugin-node-resolve'; import { minify, swc } from 'rollup-plugin-swc3'; import { minifyHTML } from './scripts/minify-html.mjs'; +import minifyHtmlLiterals from 'rollup-plugin-minify-html-literals-v3'; import postcss from 'rollup-plugin-postcss'; import postcssPresetEnv from 'postcss-preset-env'; import postcssMixins from 'postcss-mixins'; @@ -121,6 +122,12 @@ function jsConfig(outputDir, { es5 = false, node = false, production = false, so function jsPlugins({ es5 = false, node = false, module = false, production = false, sourcemap = false }) { const browserslist = es5 ? browserslistLegacy : browserslistModern; const ecmaVersion = es5 ? 5 : 2017; + const minifyHtmlOptions = { + removeComments: production, + removeRedundantAttributes: true, + sortClassName: true, + collapseWhitespace: production + }; return [ nodeResolve({ exportConditions: [ @@ -156,10 +163,13 @@ function jsPlugins({ es5 = false, node = false, module = false, production = fal // Minify HTML and CSS. minifyHTML({ include: ['./src/**/*.html'], - removeComments: production, - removeRedundantAttributes: true, - sortClassName: true, - collapseWhitespace: production + minifyOptions: minifyHtmlOptions + }), + minifyHtmlLiterals({ + include: ['./src/**/*.ts'], + options: { + minifyOptions: minifyHtmlOptions + } }), // Import HTML and SVG as strings. string({ diff --git a/scripts/minify-html.mjs b/scripts/minify-html.mjs index 571ad8eb..b79295ee 100644 --- a/scripts/minify-html.mjs +++ b/scripts/minify-html.mjs @@ -1,13 +1,13 @@ import { createFilter } from '@rollup/pluginutils'; import { minify } from 'html-minifier'; -export function minifyHTML({ include, exclude, ...options } = {}) { +export function minifyHTML({ include, exclude, minifyOptions } = {}) { const filter = createFilter(include, exclude); return { name: 'minify-html', transform: (code, id) => { if (!filter(id)) return null; - const result = minify(code, options); + const result = minify(code, minifyOptions); return { code: result, map: null }; } }; From 1a19b3fda17f3cd48680b16117ab1db939e001b5 Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Thu, 24 Apr 2025 14:11:12 +0200 Subject: [PATCH 026/123] Rework state attributes in LanguageMenu --- src/components/LanguageMenu.ts | 22 ++++++++-------------- src/components/MenuGroup.ts | 2 +- 2 files changed, 9 insertions(+), 15 deletions(-) diff --git a/src/components/LanguageMenu.ts b/src/components/LanguageMenu.ts index 6303340f..958854b7 100644 --- a/src/components/LanguageMenu.ts +++ b/src/components/LanguageMenu.ts @@ -7,7 +7,6 @@ import { stateReceiver } from './StateReceiverMixin'; import type { ChromelessPlayer, MediaTrack, MediaTrackList, TextTrack, TextTracksList } from 'theoplayer/chromeless'; import { isNonForcedSubtitleTrack } from '../util/TrackUtils'; import { Attribute } from '../util/Attribute'; -import { toggleAttribute } from '../util/CommonUtils'; // Load components used in template import './TrackRadioGroup'; @@ -29,10 +28,6 @@ export class LanguageMenu extends MenuGroup { private _audioTrackList: MediaTrackList | undefined; private _textTrackList: TextTracksList | undefined; - static get observedAttributes() { - return [...MenuGroup.observedAttributes, Attribute.HAS_AUDIO, Attribute.HAS_SUBTITLES]; - } - get player(): ChromelessPlayer | undefined { return this._player; } @@ -53,25 +48,24 @@ export class LanguageMenu extends MenuGroup { this._textTrackList?.addEventListener(TRACK_EVENTS, this._updateTextTracks); } + @property({ reflect: true, state: true, type: Boolean, attribute: Attribute.HAS_AUDIO }) + private accessor _hasAudioTracks: boolean = false; + + @property({ reflect: true, state: true, type: Boolean, attribute: Attribute.HAS_SUBTITLES }) + private accessor _hasSubtitles: boolean = false; + private readonly _updateAudioTracks = (): void => { const newAudioTracks: readonly MediaTrack[] = this._player?.audioTracks ?? []; // Hide audio track selection if there's only one track. - toggleAttribute(this, Attribute.HAS_AUDIO, newAudioTracks.length > 1); + this._hasAudioTracks = newAudioTracks.length > 1; }; private readonly _updateTextTracks = (): void => { const newSubtitleTracks: readonly TextTrack[] = this._player?.textTracks.filter(isNonForcedSubtitleTrack) ?? []; // Hide subtitle track selection if there are no tracks. If there's one, we still show an "off" option. - toggleAttribute(this, Attribute.HAS_SUBTITLES, newSubtitleTracks.length > 0); + this._hasSubtitles = newSubtitleTracks.length > 0; }; - override attributeChangedCallback(attrName: string, oldValue: any, newValue: any) { - super.attributeChangedCallback(attrName, oldValue, newValue); - if (LanguageMenu.observedAttributes.indexOf(attrName as Attribute) >= 0) { - shadyCss.styleSubtree(this); - } - } - protected override render(): TemplateResult { return super.renderMenuGroup( html` diff --git a/src/components/MenuGroup.ts b/src/components/MenuGroup.ts index 6b73b5e9..8149795c 100644 --- a/src/components/MenuGroup.ts +++ b/src/components/MenuGroup.ts @@ -19,7 +19,7 @@ interface OpenMenuEntry { /** * `` - A group of {@link Menu}s. * - * This can contain multiple other menus, which can be opened with {@link MenuGroup.openMenu}. + * This can contain multiple other menus, which can be opened with {@link openMenu}. * When a {@link MenuButton} in one menu opens another menu in this group, it is opened as a "submenu". * When a submenu is closed, the menu that originally opened it is shown again. * From d3e691adf3888673badabc088577b5cea41f5f27 Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Thu, 24 Apr 2025 14:14:00 +0200 Subject: [PATCH 027/123] Rework TextTrackStyleMenu --- src/components/TextTrackStyleMenu.ts | 123 ++++++++++++--------------- 1 file changed, 56 insertions(+), 67 deletions(-) diff --git a/src/components/TextTrackStyleMenu.ts b/src/components/TextTrackStyleMenu.ts index cca81546..59a1035f 100644 --- a/src/components/TextTrackStyleMenu.ts +++ b/src/components/TextTrackStyleMenu.ts @@ -3,11 +3,54 @@ import { customElement } from 'lit/decorators.js'; import { MenuGroup } from './MenuGroup'; import textTrackStyleMenuCss from './TextTrackStyleMenu.css'; import menuTableCss from './MenuTable.css'; +import type { EdgeStyle } from 'theoplayer/chromeless'; // Load components used in template import './TextTrackStyleDisplay'; import './TextTrackStyleRadioGroup'; +const colorOptions: ReadonlyArray<{ label: string; value: `rgb(${number},${number},${number})` | '' }> = [ + { label: 'Default', value: '' }, + { label: 'White', value: 'rgb(255,255,255)' }, + { label: 'Yellow', value: 'rgb(255,255,0)' }, + { label: 'Green', value: 'rgb(0,255,0)' }, + { label: 'Cyan', value: 'rgb(0,255,255)' }, + { label: 'Blue', value: 'rgb(0,0,255)' }, + { label: 'Magenta', value: 'rgb(255,0,255)' }, + { label: 'Red', value: 'rgb(255,0,0)' }, + { label: 'Black', value: 'rgb(0,0,0)' } +]; +const fontFamilyOptions: ReadonlyArray<{ label: string; value: string }> = [ + { label: 'Default', value: '' }, + { label: 'Monospace Serif', value: '"Courier New", Courier, "Nimbus Mono L", "Cutive Mono", monospace' }, + { label: 'Proportional Serif', value: '"Times New Roman", Times, Georgia, Cambria, "PT Serif Caption", serif' }, + { label: 'Monospace Sans', value: '"Deja Vu Sans Mono", "Lucida Console", Monaco, Consolas, "PT Mono", monospace' }, + { label: 'Proportional Sans', value: 'Arial, Helvetica, Verdana, "PT Sans Caption", sans-serif' } +]; +const sizeOptions: ReadonlyArray<{ label: string; value: `${number}%` | '' }> = [ + { label: 'Default', value: '' }, + { label: '50%', value: '50%' }, + { label: '75%', value: '75%' }, + { label: '100%', value: '100%' }, + { label: '150%', value: '150%' }, + { label: '200%', value: '200%' } +]; +const opacityOptions: ReadonlyArray<{ label: string; value: `${number}` | '' }> = [ + { label: 'Default', value: '' }, + { label: '25%', value: '25' }, + { label: '50%', value: '50' }, + { label: '75%', value: '75' }, + { label: '100%', value: '100' } +]; +const edgeStyleOptions: ReadonlyArray<{ label: string; value: EdgeStyle | '' }> = [ + { label: 'Default', value: '' }, + { label: 'None', value: 'none' }, + { label: 'Drop shadow', value: 'dropshadow' }, + { label: 'Raised', value: 'raised' }, + { label: 'Depressed', value: 'depressed' }, + { label: 'Uniform', value: 'uniform' } +]; + /** * `` - A menu to change the {@link theoplayer!TextTrackStyle | text track style} of the player. * @@ -104,113 +147,59 @@ export class TextTrackStyleMenu extends MenuGroup { `, From e3bdb2a51b6e58215e71cacf4fb819abe935ade3 Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Thu, 24 Apr 2025 14:24:57 +0200 Subject: [PATCH 028/123] Import .css files as Lit CSS literals --- package-lock.json | 694 +++++++++++++++++++++++++++++++++++++++++++ package.json | 1 + rollup.config.mjs | 4 + src/definitions.d.ts | 5 +- 4 files changed, 702 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 066ebff5..b783df31 100644 --- a/package-lock.json +++ b/package-lock.json @@ -41,6 +41,7 @@ "rollup-plugin-dts": "^6.1.1", "rollup-plugin-minify-html-literals-v3": "^1.3.3", "rollup-plugin-postcss": "^4.0.2", + "rollup-plugin-postcss-lit": "^2.2.0", "rollup-plugin-string": "^3.0.0", "rollup-plugin-swc3": "^0.12.1", "serve": "^14.2.4", @@ -3120,6 +3121,41 @@ "node": ">= 0.6" } }, + "node_modules/acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-node": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/acorn-node/-/acorn-node-1.8.2.tgz", + "integrity": "sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "acorn": "^7.0.0", + "acorn-walk": "^7.0.0", + "xtend": "^4.0.2" + } + }, + "node_modules/acorn-walk": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", + "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/ajv": { "version": "8.12.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", @@ -3208,6 +3244,16 @@ "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, + "node_modules/ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/arch": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/arch/-/arch-2.2.0.tgz", @@ -3441,6 +3487,13 @@ "node": ">=8" } }, + "node_modules/browser-process-hrtime": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-0.1.3.tgz", + "integrity": "sha512-bRFnI4NnjO6cnyLmOV/7PVoDEMJChlcfN0z4s1YMBY989/SvlfMI1lgCnkFUs53e9gQF+w7qu7XdllSTiSl8Aw==", + "dev": true, + "license": "BSD-2-Clause" + }, "node_modules/browserslist": { "version": "4.24.4", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.4.tgz", @@ -3624,6 +3677,23 @@ ], "license": "CC-BY-4.0" }, + "node_modules/chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/chalk-template": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/chalk-template/-/chalk-template-0.4.0.tgz", @@ -3709,6 +3779,29 @@ "node": ">=8" } }, + "node_modules/chalk/node_modules/ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/chalk/node_modules/strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/chardet": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/chardet/-/chardet-2.1.0.tgz", @@ -3871,6 +3964,13 @@ "node": ">= 0.6" } }, + "node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "dev": true, + "license": "MIT" + }, "node_modules/cross-env": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz", @@ -4217,6 +4317,13 @@ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" }, + "node_modules/dash-ast": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/dash-ast/-/dash-ast-1.0.0.tgz", + "integrity": "sha512-Vy4dx7gquTeMcQR/hDkYLGUnwVil6vk4FOOct+djUnHOUWt+zJPJAaRIXaAFkPXtJjvlY7o3rfRu0/3hpnwoUA==", + "dev": true, + "license": "Apache-2.0" + }, "node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -4449,6 +4556,16 @@ "node": ">=6" } }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/esprima": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", @@ -4864,6 +4981,29 @@ "node": ">= 0.4.0" } }, + "node_modules/has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-ansi/node_modules/ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/he": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", @@ -5046,6 +5186,30 @@ "kind-of": "^6.0.2" } }, + "node_modules/is-buffer": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz", + "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/is-core-module": { "version": "2.13.0", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.0.tgz", @@ -5472,6 +5636,26 @@ "dev": true, "license": "MIT" }, + "node_modules/merge-source-map": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/merge-source-map/-/merge-source-map-1.0.4.tgz", + "integrity": "sha512-PGSmS0kfnTnMJCzJ16BLLCEe6oeYCamKFFdQKshi4BmM6FUwipjVOcBFGxqtQtirtAG4iZvHlqST9CpZKqlRjA==", + "dev": true, + "license": "MIT", + "dependencies": { + "source-map": "^0.5.6" + } + }, + "node_modules/merge-source-map/node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -5621,6 +5805,33 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "dev": true }, + "node_modules/mutexify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/mutexify/-/mutexify-1.4.0.tgz", + "integrity": "sha512-pbYSsOrSB/AKN5h/WzzLRMFgZhClWccf2XIB4RSMC8JbquiB0e0/SH5AIfdQMdyHmYtv4seU7yV/TvAwPLJ1Yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "queue-tick": "^1.0.0" + } + }, + "node_modules/nanobench": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nanobench/-/nanobench-2.1.1.tgz", + "integrity": "sha512-z+Vv7zElcjN+OpzAxAquUayFLGK3JI/ubCl0Oh64YQqsTGG09CGqieJVQw4ui8huDnnAgrvTv93qi5UaOoNj8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "browser-process-hrtime": "^0.1.2", + "chalk": "^1.1.3", + "mutexify": "^1.1.0", + "pretty-hrtime": "^1.0.2" + }, + "bin": { + "nanobench": "run.js", + "nanobench-compare": "compare.js" + } + }, "node_modules/nanoid": { "version": "3.3.8", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", @@ -7498,6 +7709,16 @@ "url": "https://github.com/prettier/prettier?sponsor=1" } }, + "node_modules/pretty-hrtime": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz", + "integrity": "sha512-66hKPCr+72mlfiSjlEB1+45IjXSqvVAIy6mocupoww4tBFE9R9IhwwUGoI4G++Tc9Aq+2rxOt0RFU6gPcrte0A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/promise.series": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/promise.series/-/promise.series-0.2.0.tgz", @@ -7554,6 +7775,13 @@ } ] }, + "node_modules/queue-tick": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/queue-tick/-/queue-tick-1.0.1.tgz", + "integrity": "sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==", + "dev": true, + "license": "MIT" + }, "node_modules/quick-lru": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", @@ -7845,6 +8073,17 @@ "postcss": "8.x" } }, + "node_modules/rollup-plugin-postcss-lit": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/rollup-plugin-postcss-lit/-/rollup-plugin-postcss-lit-2.2.0.tgz", + "integrity": "sha512-Hj9PirzgaoyL9b6p0+ESa1okx6Yb2vHQtZzzuoH5E5XCJRu992/mKDW7SCNA0Z6m7GrH+lrxb9+hRmmACFBwkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.1.4", + "transform-ast": "^2.4.4" + } + }, "node_modules/rollup-plugin-postcss/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -8503,6 +8742,16 @@ "postcss": "^8.3.3" } }, + "node_modules/supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/supports-preserve-symlinks-flag": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", @@ -8664,6 +8913,32 @@ "url": "https://github.com/sponsors/Borewit" } }, + "node_modules/transform-ast": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/transform-ast/-/transform-ast-2.4.4.tgz", + "integrity": "sha512-AxjeZAcIOUO2lev2GDe3/xZ1Q0cVGjIMk5IsriTy8zbWlsEnjeB025AhkhBJHoy997mXpLd4R+kRbvnnQVuQHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn-node": "^1.3.0", + "convert-source-map": "^1.5.1", + "dash-ast": "^1.0.0", + "is-buffer": "^2.0.0", + "magic-string": "^0.23.2", + "merge-source-map": "1.0.4", + "nanobench": "^2.1.1" + } + }, + "node_modules/transform-ast/node_modules/magic-string": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.23.2.tgz", + "integrity": "sha512-oIUZaAxbcxYIp4AyLafV6OVKoB3YouZs0UTCJ8mOKBHNyJgGDaMJ4TgA+VylJh6fx7EQCC52XkbURxxG9IoJXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "sourcemap-codec": "^1.4.1" + } + }, "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", @@ -8946,6 +9221,16 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4" + } + }, "node_modules/yaml": { "version": "1.10.2", "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", @@ -10438,6 +10723,7 @@ "rollup-plugin-dts": "^6.1.1", "rollup-plugin-minify-html-literals-v3": "^1.3.3", "rollup-plugin-postcss": "^4.0.2", + "rollup-plugin-postcss-lit": "^2.2.0", "rollup-plugin-string": "^3.0.0", "rollup-plugin-swc3": "^0.12.1", "serve": "^14.2.4", @@ -12125,6 +12411,29 @@ "negotiator": "0.6.3" } }, + "acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true + }, + "acorn-node": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/acorn-node/-/acorn-node-1.8.2.tgz", + "integrity": "sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A==", + "dev": true, + "requires": { + "acorn": "^7.0.0", + "acorn-walk": "^7.0.0", + "xtend": "^4.0.2" + } + }, + "acorn-walk": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", + "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", + "dev": true + }, "ajv": { "version": "8.12.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", @@ -12192,6 +12501,12 @@ "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", "dev": true }, + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==", + "dev": true + }, "arch": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/arch/-/arch-2.2.0.tgz", @@ -12333,6 +12648,12 @@ "fill-range": "^7.1.1" } }, + "browser-process-hrtime": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-0.1.3.tgz", + "integrity": "sha512-bRFnI4NnjO6cnyLmOV/7PVoDEMJChlcfN0z4s1YMBY989/SvlfMI1lgCnkFUs53e9gQF+w7qu7XdllSTiSl8Aw==", + "dev": true + }, "browserslist": { "version": "4.24.4", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.4.tgz", @@ -12436,6 +12757,36 @@ "integrity": "sha512-b+uH5BakXZ9Do9iK+CkDmctUSEqZl+SP056vc5usa0PL+ev5OHw003rZXcnjNDv3L8P5j6rwT6C0BPKSikW08w==", "dev": true }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==", + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", + "dev": true + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + } + } + }, "chalk-template": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/chalk-template/-/chalk-template-0.4.0.tgz", @@ -12618,6 +12969,12 @@ "safe-buffer": "5.2.1" } }, + "convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "dev": true + }, "cross-env": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz", @@ -12821,6 +13178,12 @@ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" }, + "dash-ast": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/dash-ast/-/dash-ast-1.0.0.tgz", + "integrity": "sha512-Vy4dx7gquTeMcQR/hDkYLGUnwVil6vk4FOOct+djUnHOUWt+zJPJAaRIXaAFkPXtJjvlY7o3rfRu0/3hpnwoUA==", + "dev": true + }, "debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -12980,6 +13343,12 @@ "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", "dev": true }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true + }, "esprima": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", @@ -13264,6 +13633,23 @@ "function-bind": "^1.1.1" } }, + "has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", + "dev": true + } + } + }, "he": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", @@ -13388,6 +13774,12 @@ "kind-of": "^6.0.2" } }, + "is-buffer": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz", + "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==", + "dev": true + }, "is-core-module": { "version": "2.13.0", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.0.tgz", @@ -13708,6 +14100,23 @@ "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", "dev": true }, + "merge-source-map": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/merge-source-map/-/merge-source-map-1.0.4.tgz", + "integrity": "sha512-PGSmS0kfnTnMJCzJ16BLLCEe6oeYCamKFFdQKshi4BmM6FUwipjVOcBFGxqtQtirtAG4iZvHlqST9CpZKqlRjA==", + "dev": true, + "requires": { + "source-map": "^0.5.6" + }, + "dependencies": { + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "dev": true + } + } + }, "merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -13819,6 +14228,27 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "dev": true }, + "mutexify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/mutexify/-/mutexify-1.4.0.tgz", + "integrity": "sha512-pbYSsOrSB/AKN5h/WzzLRMFgZhClWccf2XIB4RSMC8JbquiB0e0/SH5AIfdQMdyHmYtv4seU7yV/TvAwPLJ1Yg==", + "dev": true, + "requires": { + "queue-tick": "^1.0.0" + } + }, + "nanobench": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nanobench/-/nanobench-2.1.1.tgz", + "integrity": "sha512-z+Vv7zElcjN+OpzAxAquUayFLGK3JI/ubCl0Oh64YQqsTGG09CGqieJVQw4ui8huDnnAgrvTv93qi5UaOoNj8A==", + "dev": true, + "requires": { + "browser-process-hrtime": "^0.1.2", + "chalk": "^1.1.3", + "mutexify": "^1.1.0", + "pretty-hrtime": "^1.0.2" + } + }, "nanoid": { "version": "3.3.8", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", @@ -14872,6 +15302,12 @@ "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", "dev": true }, + "pretty-hrtime": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz", + "integrity": "sha512-66hKPCr+72mlfiSjlEB1+45IjXSqvVAIy6mocupoww4tBFE9R9IhwwUGoI4G++Tc9Aq+2rxOt0RFU6gPcrte0A==", + "dev": true + }, "promise.series": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/promise.series/-/promise.series-0.2.0.tgz", @@ -14896,6 +15332,12 @@ "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", "dev": true }, + "queue-tick": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/queue-tick/-/queue-tick-1.0.1.tgz", + "integrity": "sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==", + "dev": true + }, "quick-lru": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", @@ -15156,6 +15598,16 @@ } } }, + "rollup-plugin-postcss-lit": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/rollup-plugin-postcss-lit/-/rollup-plugin-postcss-lit-2.2.0.tgz", + "integrity": "sha512-Hj9PirzgaoyL9b6p0+ESa1okx6Yb2vHQtZzzuoH5E5XCJRu992/mKDW7SCNA0Z6m7GrH+lrxb9+hRmmACFBwkw==", + "dev": true, + "requires": { + "@rollup/pluginutils": "^5.1.4", + "transform-ast": "^2.4.4" + } + }, "rollup-plugin-string": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/rollup-plugin-string/-/rollup-plugin-string-3.0.0.tgz", @@ -15553,6 +16005,12 @@ "dev": true, "requires": {} }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==", + "dev": true + }, "supports-preserve-symlinks-flag": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", @@ -15656,6 +16114,32 @@ "ieee754": "^1.2.1" } }, + "transform-ast": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/transform-ast/-/transform-ast-2.4.4.tgz", + "integrity": "sha512-AxjeZAcIOUO2lev2GDe3/xZ1Q0cVGjIMk5IsriTy8zbWlsEnjeB025AhkhBJHoy997mXpLd4R+kRbvnnQVuQHQ==", + "dev": true, + "requires": { + "acorn-node": "^1.3.0", + "convert-source-map": "^1.5.1", + "dash-ast": "^1.0.0", + "is-buffer": "^2.0.0", + "magic-string": "^0.23.2", + "merge-source-map": "1.0.4", + "nanobench": "^2.1.1" + }, + "dependencies": { + "magic-string": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.23.2.tgz", + "integrity": "sha512-oIUZaAxbcxYIp4AyLafV6OVKoB3YouZs0UTCJ8mOKBHNyJgGDaMJ4TgA+VylJh6fx7EQCC52XkbURxxG9IoJXA==", + "dev": true, + "requires": { + "sourcemap-codec": "^1.4.1" + } + } + } + }, "tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", @@ -15835,6 +16319,12 @@ } } }, + "xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true + }, "yaml": { "version": "1.10.2", "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", @@ -16128,6 +16618,29 @@ "negotiator": "0.6.3" } }, + "acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true + }, + "acorn-node": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/acorn-node/-/acorn-node-1.8.2.tgz", + "integrity": "sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A==", + "dev": true, + "requires": { + "acorn": "^7.0.0", + "acorn-walk": "^7.0.0", + "xtend": "^4.0.2" + } + }, + "acorn-walk": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", + "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", + "dev": true + }, "ajv": { "version": "8.12.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", @@ -16195,6 +16708,12 @@ "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", "dev": true }, + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==", + "dev": true + }, "arch": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/arch/-/arch-2.2.0.tgz", @@ -16336,6 +16855,12 @@ "fill-range": "^7.1.1" } }, + "browser-process-hrtime": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-0.1.3.tgz", + "integrity": "sha512-bRFnI4NnjO6cnyLmOV/7PVoDEMJChlcfN0z4s1YMBY989/SvlfMI1lgCnkFUs53e9gQF+w7qu7XdllSTiSl8Aw==", + "dev": true + }, "browserslist": { "version": "4.24.4", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.4.tgz", @@ -16439,6 +16964,36 @@ "integrity": "sha512-b+uH5BakXZ9Do9iK+CkDmctUSEqZl+SP056vc5usa0PL+ev5OHw003rZXcnjNDv3L8P5j6rwT6C0BPKSikW08w==", "dev": true }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==", + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", + "dev": true + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + } + } + }, "chalk-template": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/chalk-template/-/chalk-template-0.4.0.tgz", @@ -16621,6 +17176,12 @@ "safe-buffer": "5.2.1" } }, + "convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "dev": true + }, "cross-env": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz", @@ -16824,6 +17385,12 @@ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" }, + "dash-ast": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/dash-ast/-/dash-ast-1.0.0.tgz", + "integrity": "sha512-Vy4dx7gquTeMcQR/hDkYLGUnwVil6vk4FOOct+djUnHOUWt+zJPJAaRIXaAFkPXtJjvlY7o3rfRu0/3hpnwoUA==", + "dev": true + }, "debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -16983,6 +17550,12 @@ "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", "dev": true }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true + }, "esprima": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", @@ -17267,6 +17840,23 @@ "function-bind": "^1.1.1" } }, + "has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", + "dev": true + } + } + }, "he": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", @@ -17391,6 +17981,12 @@ "kind-of": "^6.0.2" } }, + "is-buffer": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz", + "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==", + "dev": true + }, "is-core-module": { "version": "2.13.0", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.0.tgz", @@ -17711,6 +18307,23 @@ "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", "dev": true }, + "merge-source-map": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/merge-source-map/-/merge-source-map-1.0.4.tgz", + "integrity": "sha512-PGSmS0kfnTnMJCzJ16BLLCEe6oeYCamKFFdQKshi4BmM6FUwipjVOcBFGxqtQtirtAG4iZvHlqST9CpZKqlRjA==", + "dev": true, + "requires": { + "source-map": "^0.5.6" + }, + "dependencies": { + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "dev": true + } + } + }, "merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -17822,6 +18435,27 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "dev": true }, + "mutexify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/mutexify/-/mutexify-1.4.0.tgz", + "integrity": "sha512-pbYSsOrSB/AKN5h/WzzLRMFgZhClWccf2XIB4RSMC8JbquiB0e0/SH5AIfdQMdyHmYtv4seU7yV/TvAwPLJ1Yg==", + "dev": true, + "requires": { + "queue-tick": "^1.0.0" + } + }, + "nanobench": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nanobench/-/nanobench-2.1.1.tgz", + "integrity": "sha512-z+Vv7zElcjN+OpzAxAquUayFLGK3JI/ubCl0Oh64YQqsTGG09CGqieJVQw4ui8huDnnAgrvTv93qi5UaOoNj8A==", + "dev": true, + "requires": { + "browser-process-hrtime": "^0.1.2", + "chalk": "^1.1.3", + "mutexify": "^1.1.0", + "pretty-hrtime": "^1.0.2" + } + }, "nanoid": { "version": "3.3.8", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", @@ -18875,6 +19509,12 @@ "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", "dev": true }, + "pretty-hrtime": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz", + "integrity": "sha512-66hKPCr+72mlfiSjlEB1+45IjXSqvVAIy6mocupoww4tBFE9R9IhwwUGoI4G++Tc9Aq+2rxOt0RFU6gPcrte0A==", + "dev": true + }, "promise.series": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/promise.series/-/promise.series-0.2.0.tgz", @@ -18899,6 +19539,12 @@ "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", "dev": true }, + "queue-tick": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/queue-tick/-/queue-tick-1.0.1.tgz", + "integrity": "sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==", + "dev": true + }, "quick-lru": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", @@ -19159,6 +19805,16 @@ } } }, + "rollup-plugin-postcss-lit": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/rollup-plugin-postcss-lit/-/rollup-plugin-postcss-lit-2.2.0.tgz", + "integrity": "sha512-Hj9PirzgaoyL9b6p0+ESa1okx6Yb2vHQtZzzuoH5E5XCJRu992/mKDW7SCNA0Z6m7GrH+lrxb9+hRmmACFBwkw==", + "dev": true, + "requires": { + "@rollup/pluginutils": "^5.1.4", + "transform-ast": "^2.4.4" + } + }, "rollup-plugin-string": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/rollup-plugin-string/-/rollup-plugin-string-3.0.0.tgz", @@ -19556,6 +20212,12 @@ "dev": true, "requires": {} }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==", + "dev": true + }, "supports-preserve-symlinks-flag": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", @@ -19659,6 +20321,32 @@ "ieee754": "^1.2.1" } }, + "transform-ast": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/transform-ast/-/transform-ast-2.4.4.tgz", + "integrity": "sha512-AxjeZAcIOUO2lev2GDe3/xZ1Q0cVGjIMk5IsriTy8zbWlsEnjeB025AhkhBJHoy997mXpLd4R+kRbvnnQVuQHQ==", + "dev": true, + "requires": { + "acorn-node": "^1.3.0", + "convert-source-map": "^1.5.1", + "dash-ast": "^1.0.0", + "is-buffer": "^2.0.0", + "magic-string": "^0.23.2", + "merge-source-map": "1.0.4", + "nanobench": "^2.1.1" + }, + "dependencies": { + "magic-string": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.23.2.tgz", + "integrity": "sha512-oIUZaAxbcxYIp4AyLafV6OVKoB3YouZs0UTCJ8mOKBHNyJgGDaMJ4TgA+VylJh6fx7EQCC52XkbURxxG9IoJXA==", + "dev": true, + "requires": { + "sourcemap-codec": "^1.4.1" + } + } + } + }, "tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", @@ -19838,6 +20526,12 @@ } } }, + "xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true + }, "yaml": { "version": "1.10.2", "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", diff --git a/package.json b/package.json index 88f46f94..63d5206a 100644 --- a/package.json +++ b/package.json @@ -85,6 +85,7 @@ "rollup-plugin-dts": "^6.1.1", "rollup-plugin-minify-html-literals-v3": "^1.3.3", "rollup-plugin-postcss": "^4.0.2", + "rollup-plugin-postcss-lit": "^2.2.0", "rollup-plugin-string": "^3.0.0", "rollup-plugin-swc3": "^0.12.1", "serve": "^14.2.4", diff --git a/rollup.config.mjs b/rollup.config.mjs index 0df01744..664ed680 100644 --- a/rollup.config.mjs +++ b/rollup.config.mjs @@ -6,6 +6,7 @@ import minifyHtmlLiterals from 'rollup-plugin-minify-html-literals-v3'; import postcss from 'rollup-plugin-postcss'; import postcssPresetEnv from 'postcss-preset-env'; import postcssMixins from 'postcss-mixins'; +import postcssLit from 'rollup-plugin-postcss-lit'; import * as path from 'node:path'; import { readFile } from 'node:fs/promises'; import { string } from 'rollup-plugin-string'; @@ -160,6 +161,9 @@ function jsPlugins({ es5 = false, node = false, module = false, production = fal ], minimize: production }), + postcssLit({ + include: './src/**/*.css' + }), // Minify HTML and CSS. minifyHTML({ include: ['./src/**/*.html'], diff --git a/src/definitions.d.ts b/src/definitions.d.ts index ee45a0dd..1d5abc2d 100644 --- a/src/definitions.d.ts +++ b/src/definitions.d.ts @@ -1,6 +1,7 @@ declare module '*.css' { - const cssText: string; - export default cssText; + import type { CSSResult } from 'lit'; + const css: CSSResult; + export default css; } declare module '*.html' { From 182b96c730b63f16c9312d813ff4861e3c4f4f85 Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Thu, 24 Apr 2025 14:25:19 +0200 Subject: [PATCH 029/123] Use static styles for MenuGroups --- src/components/LanguageMenu.ts | 46 +++-- src/components/MenuGroup.ts | 14 +- src/components/SettingsMenu.ts | 65 +++--- src/components/TextTrackStyleMenu.ts | 283 +++++++++++++-------------- 4 files changed, 196 insertions(+), 212 deletions(-) diff --git a/src/components/LanguageMenu.ts b/src/components/LanguageMenu.ts index 958854b7..d8701722 100644 --- a/src/components/LanguageMenu.ts +++ b/src/components/LanguageMenu.ts @@ -1,7 +1,6 @@ import { html, type TemplateResult } from 'lit'; import { customElement, property } from 'lit/decorators.js'; import { MenuGroup } from './MenuGroup'; -import * as shadyCss from '@webcomponents/shadycss'; import languageMenuCss from './LanguageMenu.css'; import { stateReceiver } from './StateReceiverMixin'; import type { ChromelessPlayer, MediaTrack, MediaTrackList, TextTrack, TextTracksList } from 'theoplayer/chromeless'; @@ -24,6 +23,8 @@ const TRACK_EVENTS = ['addtrack', 'removetrack'] as const; @customElement('theoplayer-language-menu') @stateReceiver(['player']) export class LanguageMenu extends MenuGroup { + static styles = [...MenuGroup.styles, languageMenuCss]; + private _player: ChromelessPlayer | undefined; private _audioTrackList: MediaTrackList | undefined; private _textTrackList: TextTracksList | undefined; @@ -67,30 +68,27 @@ export class LanguageMenu extends MenuGroup { }; protected override render(): TemplateResult { - return super.renderMenuGroup( - html` - - Language - -
-
-

Audio

- -
-
-

Subtitles

- -
+ return html` + + Language + +
+
+

Audio

+ +
+
+

Subtitles

+
- - - `, - languageMenuCss - ); +
+
+ + `; } } diff --git a/src/components/MenuGroup.ts b/src/components/MenuGroup.ts index 8149795c..ed974352 100644 --- a/src/components/MenuGroup.ts +++ b/src/components/MenuGroup.ts @@ -28,6 +28,8 @@ interface OpenMenuEntry { */ @customElement('theoplayer-menu-group') export class MenuGroup extends LitElement { + static styles = [menuGroupCss]; + private _menuOpened: boolean = false; private get menuOpened_(): boolean { @@ -72,17 +74,7 @@ export class MenuGroup extends LitElement { } protected render(): TemplateResult { - return this.renderMenuGroup(html``); - } - - protected renderMenuGroup(content: TemplateResult, extraCss: string | TemplateResult = ''): TemplateResult { - return html` - - ${content} - `; + return html``; } protected override firstUpdated(): void { diff --git a/src/components/SettingsMenu.ts b/src/components/SettingsMenu.ts index 831c8200..7b99bddb 100644 --- a/src/components/SettingsMenu.ts +++ b/src/components/SettingsMenu.ts @@ -18,40 +18,39 @@ import './PlaybackRateMenu'; */ @customElement('theoplayer-settings-menu') export class SettingsMenu extends MenuGroup { + static styles = [...MenuGroup.styles, menuTableCss]; + protected override render(): TemplateResult { - return this.renderMenuGroup( - html` - - Settings - - - - - - - - - -
Quality - - - -
Playback speed - - - -
-
- - - `, - menuTableCss - ); + return html` + + Settings + + + + + + + + + +
Quality + + + +
Playback speed + + + +
+
+ + + `; } } diff --git a/src/components/TextTrackStyleMenu.ts b/src/components/TextTrackStyleMenu.ts index 59a1035f..5b904f01 100644 --- a/src/components/TextTrackStyleMenu.ts +++ b/src/components/TextTrackStyleMenu.ts @@ -60,151 +60,146 @@ const edgeStyleOptions: ReadonlyArray<{ label: string; value: EdgeStyle | '' }> */ @customElement('theoplayer-text-track-style-menu') export class TextTrackStyleMenu extends MenuGroup { + static styles = [...MenuGroup.styles, menuTableCss, textTrackStyleMenuCss]; + protected override render(): TemplateResult { - return super.renderMenuGroup( - html` - - Subtitle options - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Font family - - - -
Font color - - - -
Font opacity - - - -
Font size - - - -
Background color - - - -
Background opacity - - - -
Window color - - - -
Window opacity - - - -
Character edge style - - - -
-
- - - - - - - - - - `, - `${menuTableCss}\n${textTrackStyleMenuCss}` - ); + return html` + + Subtitle options + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Font family + + + +
Font color + + + +
Font opacity + + + +
Font size + + + +
Background color + + + +
Background opacity + + + +
Window color + + + +
Window opacity + + + +
Character edge style + + + +
+
+ + + + + + + + + + `; } } From 3b4b72cb14c6fe21d5eaa1e010261c439518a2b5 Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Thu, 24 Apr 2025 14:52:10 +0200 Subject: [PATCH 030/123] Rework Button --- src/components/Button.ts | 73 +++++++++++++++++----------------------- 1 file changed, 31 insertions(+), 42 deletions(-) diff --git a/src/components/Button.ts b/src/components/Button.ts index 4eacce90..bc7e074a 100644 --- a/src/components/Button.ts +++ b/src/components/Button.ts @@ -1,20 +1,22 @@ -import * as shadyCss from '@webcomponents/shadycss'; +import { html, type HTMLTemplateResult, LitElement } from 'lit'; +import { customElement, property } from 'lit/decorators.js'; import buttonCss from './Button.css'; import { Attribute } from '../util/Attribute'; -import { toggleAttribute } from '../util/CommonUtils'; -import { createTemplate } from '../util/TemplateUtils'; import { isActivationKey } from '../util/KeyCode'; export interface ButtonOptions { template: HTMLTemplateElement; } -export function buttonTemplate(button: string, extraCss: string = ''): string { - return `${button}`; +export function buttonTemplate(button: string, extraCss: string = ''): HTMLTemplateResult { + return html` + + ${button} + `; } -const defaultTemplate = createTemplate('theoplayer-button', buttonTemplate('')); - /** * `` - A basic button. * @@ -23,27 +25,20 @@ const defaultTemplate = createTemplate('theoplayer-button', buttonTemplate('`). - * Subclasses can override this by passing a different {@link ButtonOptions.template} in the options, - * using {@link buttonTemplate} to correctly style the custom template. - * - * @param options - The options for this button. + * Subclasses can override this by overriding {@link render}. */ - constructor(options?: ButtonOptions) { + constructor() { super(); - const template = options?.template ?? defaultTemplate(); - const shadowRoot = this.attachShadow({ mode: 'open' }); - shadowRoot.appendChild(template.content.cloneNode(true)); - - this._upgradeProperty('disabled'); } protected _upgradeProperty(prop: keyof this) { @@ -55,7 +50,7 @@ export class Button extends HTMLElement { } connectedCallback(): void { - shadyCss.styleElement(this); + super.connectedCallback(); if (!this.hasAttribute('role')) { this.setAttribute('role', 'button'); @@ -67,7 +62,7 @@ export class Button extends HTMLElement { // Let the screen reader user know that the text of the button may change this.setAttribute(Attribute.ARIA_LIVE, 'polite'); } - if (!this.hasAttribute(Attribute.DISABLED)) { + if (!this._disabled) { this._enable(); } } @@ -83,25 +78,17 @@ export class Button extends HTMLElement { * * When disabled, the button cannot be clicked. */ - get disabled() { - return this.hasAttribute(Attribute.DISABLED); - } - - set disabled(disabled: boolean) { - toggleAttribute(this, Attribute.DISABLED, disabled); + get disabled(): boolean { + return this._disabled; } - attributeChangedCallback(attrName: string, oldValue: any, newValue: any) { - if (attrName === Attribute.DISABLED && newValue !== oldValue) { - const hasValue = newValue != null; - if (hasValue) { - this._disable(); - } else { - this._enable(); - } - } - if (Button.observedAttributes.indexOf(attrName as Attribute) >= 0) { - shadyCss.styleSubtree(this); + @property({ reflect: true, type: Boolean, attribute: Attribute.DISABLED }) + set disabled(value: boolean) { + this._disabled = value; + if (value) { + this._disable(); + } else { + this._enable(); } } @@ -151,6 +138,10 @@ export class Button extends HTMLElement { } }; + protected render(): HTMLTemplateResult { + return html``; + } + /** * Handle a button click. * @@ -159,8 +150,6 @@ export class Button extends HTMLElement { protected handleClick(): void {} } -customElements.define('theoplayer-button', Button); - declare global { interface HTMLElementTagNameMap { 'theoplayer-button': Button; From 29439d0a7bfdd778b4b4b0434eaec092bef542b7 Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Thu, 24 Apr 2025 14:52:33 +0200 Subject: [PATCH 031/123] Rework CastButton --- ...rPlayButton.html => AirPlayButton.html.ts} | 6 ++-- src/components/AirPlayButton.ts | 24 +++++++------- src/components/CastButton.ts | 33 +++++++------------ ...stButton.html => ChromecastButton.html.ts} | 10 +++--- src/components/ChromecastButton.ts | 32 ++++++++---------- 5 files changed, 46 insertions(+), 59 deletions(-) rename src/components/{AirPlayButton.html => AirPlayButton.html.ts} (80%) rename src/components/{ChromecastButton.html => ChromecastButton.html.ts} (83%) diff --git a/src/components/AirPlayButton.html b/src/components/AirPlayButton.html.ts similarity index 80% rename from src/components/AirPlayButton.html rename to src/components/AirPlayButton.html.ts index 1948b8de..ab8dd4e6 100644 --- a/src/components/AirPlayButton.html +++ b/src/components/AirPlayButton.html.ts @@ -1,6 +1,8 @@ - +import { html } from 'lit'; + +export default html` - +`; diff --git a/src/components/AirPlayButton.ts b/src/components/AirPlayButton.ts index de3d0659..b7625186 100644 --- a/src/components/AirPlayButton.ts +++ b/src/components/AirPlayButton.ts @@ -1,26 +1,22 @@ +import { customElement } from 'lit/decorators.js'; import type { ChromelessPlayer } from 'theoplayer/chromeless'; -import { StateReceiverMixin } from './StateReceiverMixin'; +import { stateReceiver } from './StateReceiverMixin'; import { CastButton } from './CastButton'; import airPlayButtonHtml from './AirPlayButton.html'; import airPlayButtonCss from './AirPlayButton.css'; -import { buttonTemplate } from './Button'; import { Attribute } from '../util/Attribute'; -import { createTemplate } from '../util/TemplateUtils'; - -const template = createTemplate('theoplayer-airplay-button', buttonTemplate(airPlayButtonHtml, airPlayButtonCss)); /** * `` - A button to start and stop casting using AirPlay. * * @group Components */ -export class AirPlayButton extends StateReceiverMixin(CastButton, ['player']) { - private _player: ChromelessPlayer | undefined; +@customElement('theoplayer-airplay-button') +@stateReceiver(['player']) +export class AirPlayButton extends CastButton { + static styles = [...CastButton.styles, airPlayButtonCss]; - constructor() { - super({ template: template() }); - this._upgradeProperty('player'); - } + private _player: ChromelessPlayer | undefined; override connectedCallback() { super.connectedCallback(); @@ -47,9 +43,11 @@ export class AirPlayButton extends StateReceiverMixin(CastButton, ['player']) { const label = this.castState === 'connecting' || this.castState === 'connected' ? 'stop playing on AirPlay' : 'start playing on AirPlay'; this.setAttribute(Attribute.ARIA_LABEL, label); } -} -customElements.define('theoplayer-airplay-button', AirPlayButton); + protected override render() { + return airPlayButtonHtml; + } +} declare global { interface HTMLElementTagNameMap { diff --git a/src/components/CastButton.ts b/src/components/CastButton.ts index 3d7e9f64..1fb58c04 100644 --- a/src/components/CastButton.ts +++ b/src/components/CastButton.ts @@ -1,7 +1,7 @@ -import { Button, type ButtonOptions } from './Button'; +import { Button } from './Button'; +import { property } from 'lit/decorators.js'; import { Attribute } from '../util/Attribute'; import type { CastState, VendorCast } from 'theoplayer/chromeless'; -import * as shadyCss from '@webcomponents/shadycss'; /** * A generic button to start and stop casting. @@ -11,22 +11,16 @@ import * as shadyCss from '@webcomponents/shadycss'; */ export class CastButton extends Button { private _castApi: VendorCast | undefined; + private _castState: CastState = 'unavailable'; - static get observedAttributes() { - return [...Button.observedAttributes, Attribute.CAST_STATE]; - } - - constructor(options?: ButtonOptions) { - super(options); + constructor() { + super(); this._upgradeProperty('castState'); this._upgradeProperty('castApi'); } override connectedCallback() { super.connectedCallback(); - if (!this.hasAttribute(Attribute.CAST_STATE)) { - this.setAttribute(Attribute.CAST_STATE, 'unavailable'); - } this._updateCastState(); } @@ -52,7 +46,12 @@ export class CastButton extends Button { * The current cast state. */ get castState(): CastState { - return this.getAttribute(Attribute.CAST_STATE) as CastState; + return this._castState; + } + + @property({ reflect: true, state: true, type: String, attribute: Attribute.CAST_STATE }) + private set castState(castState: CastState) { + this._castState = castState; } protected override handleClick(): void { @@ -68,14 +67,6 @@ export class CastButton extends Button { } private readonly _updateCastState = (): void => { - const castState = this._castApi ? this._castApi.state : 'unavailable'; - this.setAttribute(Attribute.CAST_STATE, castState); + this.castState = this._castApi ? this._castApi.state : 'unavailable'; }; - - override attributeChangedCallback(attrName: string, oldValue: any, newValue: any) { - super.attributeChangedCallback(attrName, oldValue, newValue); - if (CastButton.observedAttributes.indexOf(attrName as Attribute) >= 0) { - shadyCss.styleSubtree(this); - } - } } diff --git a/src/components/ChromecastButton.html b/src/components/ChromecastButton.html.ts similarity index 83% rename from src/components/ChromecastButton.html rename to src/components/ChromecastButton.html.ts index 55eaab2c..439d8bf1 100644 --- a/src/components/ChromecastButton.html +++ b/src/components/ChromecastButton.html.ts @@ -1,7 +1,9 @@ - +import { html } from 'lit'; + +export default (id: number) => html` - +`; diff --git a/src/components/ChromecastButton.ts b/src/components/ChromecastButton.ts index 1ee9513e..dd525ed3 100644 --- a/src/components/ChromecastButton.ts +++ b/src/components/ChromecastButton.ts @@ -1,15 +1,11 @@ import type { ChromelessPlayer } from 'theoplayer/chromeless'; -import { StateReceiverMixin } from './StateReceiverMixin'; +import { stateReceiver } from './StateReceiverMixin'; import { CastButton } from './CastButton'; import chromecastButtonHtml from './ChromecastButton.html'; import chromecastButtonCss from './ChromecastButton.css'; -import { buttonTemplate } from './Button'; import { Attribute } from '../util/Attribute'; -import { createTemplate } from '../util/TemplateUtils'; +import { customElement } from 'lit/decorators.js'; -const template = createTemplate('theoplayer-chromecast-button', buttonTemplate(chromecastButtonHtml, chromecastButtonCss)); - -const maskId = 'theoplayer-chromecast-rings-mask'; let chromecastButtonId = 0; /** @@ -17,20 +13,16 @@ let chromecastButtonId = 0; * * @group Components */ -export class ChromecastButton extends StateReceiverMixin(CastButton, ['player']) { +@customElement('theoplayer-chromecast-button') +@stateReceiver(['player']) +export class ChromecastButton extends CastButton { + static styles = [...CastButton.styles, chromecastButtonCss]; + private _player: ChromelessPlayer | undefined; + private readonly _buttonId = ++chromecastButtonId; constructor() { - super({ template: template() }); - - // Make ID attributes unique - const id = ++chromecastButtonId; - const mask = this.shadowRoot!.querySelector(`svg clipPath#${maskId}`); - const rings = this.shadowRoot!.querySelector(`svg .theoplayer-chromecast-rings`)!; - const uniqueMaskId = `${maskId}-${id}`; - mask?.setAttribute('id', uniqueMaskId); - rings.setAttribute('clip-path', uniqueMaskId); - + super(); this._upgradeProperty('player'); } @@ -60,9 +52,11 @@ export class ChromecastButton extends StateReceiverMixin(CastButton, ['player']) this.castState === 'connecting' || this.castState === 'connected' ? 'stop casting to Chromecast' : 'start casting to Chromecast'; this.setAttribute(Attribute.ARIA_LABEL, label); } -} -customElements.define('theoplayer-chromecast-button', ChromecastButton); + protected override render() { + return chromecastButtonHtml(this._buttonId); + } +} declare global { interface HTMLElementTagNameMap { From e09c31eeeac8ad76b01abe68f078c923d02c37ea Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Wed, 24 Sep 2025 16:39:48 +0200 Subject: [PATCH 032/123] Run Prettier --- src/components/ChromecastButton.html.ts | 51 +++++++++++++------------ 1 file changed, 26 insertions(+), 25 deletions(-) diff --git a/src/components/ChromecastButton.html.ts b/src/components/ChromecastButton.html.ts index 439d8bf1..163e2f35 100644 --- a/src/components/ChromecastButton.html.ts +++ b/src/components/ChromecastButton.html.ts @@ -1,27 +1,28 @@ import { html } from 'lit'; -export default (id: number) => html` - -`; +export default (id: number) => + html` + + `; From 64293e4d48b7d45c967fc1e634acc874aee66a47 Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Thu, 24 Apr 2025 15:05:27 +0200 Subject: [PATCH 033/123] Rework PlayButton --- src/components/PlayButton.ts | 64 ++++++++++++------------------------ 1 file changed, 21 insertions(+), 43 deletions(-) diff --git a/src/components/PlayButton.ts b/src/components/PlayButton.ts index 80aade5a..f7a97de7 100644 --- a/src/components/PlayButton.ts +++ b/src/components/PlayButton.ts @@ -1,24 +1,14 @@ -import * as shadyCss from '@webcomponents/shadycss'; -import { Button, buttonTemplate } from './Button'; +import { html, type HTMLTemplateResult } from 'lit'; +import { customElement, property } from 'lit/decorators.js'; +import { unsafeSVG } from 'lit/directives/unsafe-svg.js'; +import { Button } from './Button'; import type { ChromelessPlayer } from 'theoplayer/chromeless'; import playButtonCss from './PlayButton.css'; import playIcon from '../icons/play.svg'; import pauseIcon from '../icons/pause.svg'; import replayIcon from '../icons/replay.svg'; -import { StateReceiverMixin } from './StateReceiverMixin'; +import { stateReceiver } from './StateReceiverMixin'; import { Attribute } from '../util/Attribute'; -import { toggleAttribute } from '../util/CommonUtils'; -import { createTemplate } from '../util/TemplateUtils'; - -const template = createTemplate( - 'theoplayer-play-button', - buttonTemplate( - `${playIcon}` + - `${pauseIcon}` + - `${replayIcon}`, - playButtonCss - ) -); const PLAYER_EVENTS = ['seeking', 'seeked', 'ended', 'emptied', 'sourcechange'] as const; @@ -29,41 +19,24 @@ const PLAYER_EVENTS = ['seeking', 'seeked', 'ended', 'emptied', 'sourcechange'] * @attribute `ended` (readonly) - Whether the player is ended. Reflects `ui.player.ended`. * @group Components */ -export class PlayButton extends StateReceiverMixin(Button, ['player']) { - static get observedAttributes() { - return [...Button.observedAttributes, Attribute.PAUSED, Attribute.ENDED]; - } +@customElement('theoplayer-play-button') +@stateReceiver(['player']) +export class PlayButton extends Button { + static styles = [...Button.styles, playButtonCss]; private _player: ChromelessPlayer | undefined; - constructor() { - super({ template: template() }); - this._upgradeProperty('paused'); - this._upgradeProperty('ended'); - this._upgradeProperty('player'); - } - override connectedCallback(): void { super.connectedCallback(); this._updateFromPlayer(); this._updateAriaLabel(); } - get paused(): boolean { - return this.hasAttribute(Attribute.PAUSED); - } - - set paused(paused: boolean) { - toggleAttribute(this, Attribute.PAUSED, paused); - } - - get ended(): boolean { - return this.hasAttribute(Attribute.ENDED); - } + @property({ reflect: true, state: true, type: Boolean, attribute: Attribute.PAUSED }) + accessor paused: boolean = false; - set ended(ended: boolean) { - toggleAttribute(this, Attribute.ENDED, ended); - } + @property({ reflect: true, state: true, type: Boolean, attribute: Attribute.ENDED }) + accessor ended: boolean = false; get player(): ChromelessPlayer | undefined { return this._player; @@ -128,7 +101,6 @@ export class PlayButton extends StateReceiverMixin(Button, ['player']) { override attributeChangedCallback(attrName: string, oldValue: any, newValue: any) { super.attributeChangedCallback(attrName, oldValue, newValue); if (PlayButton.observedAttributes.indexOf(attrName as Attribute) >= 0) { - shadyCss.styleSubtree(this); this._updateAriaLabel(); } } @@ -137,9 +109,15 @@ export class PlayButton extends StateReceiverMixin(Button, ['player']) { const label = this.ended ? 'replay' : this.paused ? 'play' : 'pause'; this.setAttribute(Attribute.ARIA_LABEL, label); } -} -customElements.define('theoplayer-play-button', PlayButton); + protected override render(): HTMLTemplateResult { + return html` + ${unsafeSVG(playIcon)} + ${unsafeSVG(pauseIcon)} + ${unsafeSVG(replayIcon)} + `; + } +} declare global { interface HTMLElementTagNameMap { From 52195c646c7f8510311474f1592286d462e7bcd5 Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Thu, 24 Apr 2025 15:29:53 +0200 Subject: [PATCH 034/123] Rework radio buttons --- src/components/MediaTrackRadioButton.ts | 27 +++++----- src/components/QualityRadioButton.ts | 42 ++++----------- src/components/RadioButton.ts | 54 +++++-------------- src/components/TextTrackOffRadioButton.ts | 27 ++++------ src/components/TextTrackRadioButton.ts | 34 +++++------- .../quality/AbstractQualitySelector.ts | 24 ++------- .../quality/AutomaticQualitySelector.ts | 23 ++++---- .../quality/BadNetworkModeSelector.ts | 23 ++++---- 8 files changed, 82 insertions(+), 172 deletions(-) diff --git a/src/components/MediaTrackRadioButton.ts b/src/components/MediaTrackRadioButton.ts index 12e65f3f..2fb01d8c 100644 --- a/src/components/MediaTrackRadioButton.ts +++ b/src/components/MediaTrackRadioButton.ts @@ -1,11 +1,9 @@ +import { html, type HTMLTemplateResult } from 'lit'; +import { customElement, property, state } from 'lit/decorators.js'; import { RadioButton } from './RadioButton'; -import { buttonTemplate } from './Button'; import type { MediaTrack } from 'theoplayer/chromeless'; -import { localizeLanguageName, setTextContent } from '../util/CommonUtils'; +import { localizeLanguageName } from '../util/CommonUtils'; import { Attribute } from '../util/Attribute'; -import { createTemplate } from '../util/TemplateUtils'; - -const template = createTemplate('theoplayer-media-track-radio-button', buttonTemplate(``)); const TRACK_EVENTS = ['change', 'update'] as const; @@ -15,16 +13,12 @@ const TRACK_EVENTS = ['change', 'update'] as const; * * @group Components */ +@customElement('theoplayer-media-track-radio-button') export class MediaTrackRadioButton extends RadioButton { - private _slotEl: HTMLSlotElement; private _track: MediaTrack | undefined = undefined; - constructor() { - super({ template: template() }); - this._slotEl = this.shadowRoot!.querySelector('slot')!; - - this._upgradeProperty('track'); - } + @state() + private accessor _trackLabel = ''; /** * The media track that is controlled by this radio button. @@ -33,6 +27,7 @@ export class MediaTrackRadioButton extends RadioButton { return this._track; } + @property({ reflect: false, attribute: false }) set track(track: MediaTrack | undefined) { if (this._track === track) { return; @@ -48,7 +43,7 @@ export class MediaTrackRadioButton extends RadioButton { } private _updateFromTrack(): void { - setTextContent(this._slotEl, this._track ? getTrackLabel(this._track) : ''); + this._trackLabel = this._track ? getTrackLabel(this._track) : ''; this.checked = this._track ? this._track.enabled : false; } @@ -68,6 +63,10 @@ export class MediaTrackRadioButton extends RadioButton { this._updateTrack(); } } + + protected override render(): HTMLTemplateResult { + return html`${this._trackLabel}`; + } } function getTrackLabel(track: MediaTrack): string { @@ -82,8 +81,6 @@ function getTrackLabel(track: MediaTrack): string { return localizeLanguageName(languageCode) || languageCode || ''; } -customElements.define('theoplayer-media-track-radio-button', MediaTrackRadioButton); - declare global { interface HTMLElementTagNameMap { 'theoplayer-media-track-radio-button': MediaTrackRadioButton; diff --git a/src/components/QualityRadioButton.ts b/src/components/QualityRadioButton.ts index fb9cc8f4..4abe463e 100644 --- a/src/components/QualityRadioButton.ts +++ b/src/components/QualityRadioButton.ts @@ -1,13 +1,7 @@ -import * as shadyCss from '@webcomponents/shadycss'; import { RadioButton } from './RadioButton'; -import { buttonTemplate } from './Button'; import type { MediaTrack, VideoQuality } from 'theoplayer/chromeless'; -import { setTextContent } from '../util/CommonUtils'; -import { Attribute } from '../util/Attribute'; import { formatQualityLabel } from '../util/TrackUtils'; -import { createTemplate } from '../util/TemplateUtils'; - -const template = createTemplate('theoplayer-quality-radio-button', buttonTemplate(``)); +import { customElement, property, state } from 'lit/decorators.js'; const TRACK_EVENTS = ['activequalitychanged', 'targetqualitychanged'] as const; const QUALITY_EVENTS = ['update'] as const; @@ -18,18 +12,13 @@ const QUALITY_EVENTS = ['update'] as const; * * @group Components */ +@customElement('theoplayer-quality-radio-button') export class QualityRadioButton extends RadioButton { - private _slotEl: HTMLSlotElement; private _track: MediaTrack | undefined = undefined; private _quality: VideoQuality | undefined = undefined; - constructor() { - super({ template: template() }); - this._slotEl = this.shadowRoot!.querySelector('slot')!; - - this._upgradeProperty('track'); - this._upgradeProperty('quality'); - } + @state() + private accessor _qualityLabel = ''; /** * The video track containing the quality being controlled. @@ -38,6 +27,7 @@ export class QualityRadioButton extends RadioButton { return this._track; } + @property({ reflect: false, attribute: false }) set track(track: MediaTrack | undefined) { if (this._track === track) { return; @@ -59,6 +49,7 @@ export class QualityRadioButton extends RadioButton { return this._quality; } + @property({ reflect: false, attribute: false }) set quality(quality: VideoQuality | undefined) { if (this._quality === quality) { return; @@ -93,31 +84,20 @@ export class QualityRadioButton extends RadioButton { }; private readonly _updateFromQuality = () => { - setTextContent(this._slotEl, formatQualityLabel(this._quality) ?? 'Automatic'); + this._qualityLabel = formatQualityLabel(this._quality) ?? 'Automatic'; }; + protected override handleChange(): void { + this._updateTargetQuality(); + } + private _updateTargetQuality(): void { if (this._track && this.checked) { this._track.targetQuality = this._quality; } } - - override attributeChangedCallback(attrName: string, oldValue: any, newValue: any) { - super.attributeChangedCallback(attrName, oldValue, newValue); - if (newValue === oldValue) { - return; - } - if (attrName === Attribute.ARIA_CHECKED) { - this._updateTargetQuality(); - } - if (QualityRadioButton.observedAttributes.indexOf(attrName as Attribute) >= 0) { - shadyCss.styleSubtree(this); - } - } } -customElements.define('theoplayer-quality-radio-button', QualityRadioButton); - declare global { interface HTMLElementTagNameMap { 'theoplayer-quality-radio-button': QualityRadioButton; diff --git a/src/components/RadioButton.ts b/src/components/RadioButton.ts index c34d5539..3453b953 100644 --- a/src/components/RadioButton.ts +++ b/src/components/RadioButton.ts @@ -1,11 +1,8 @@ -import { Button, type ButtonOptions, buttonTemplate } from './Button'; +import { customElement, property } from 'lit/decorators.js'; +import { Button } from './Button'; import { Attribute } from '../util/Attribute'; -import * as shadyCss from '@webcomponents/shadycss'; import { createEvent } from '../util/EventUtils'; import type { RadioGroup } from './RadioGroup'; -import { createTemplate } from '../util/TemplateUtils'; - -const defaultTemplate = createTemplate('theoplayer-radio-button', buttonTemplate('')); /** * `` - A button that can be checked. @@ -14,17 +11,9 @@ const defaultTemplate = createTemplate('theoplayer-radio-button', buttonTemplate * * @group Components */ +@customElement('theoplayer-radio-button') export class RadioButton extends Button { - static get observedAttributes() { - return [...Button.observedAttributes, Attribute.ARIA_CHECKED, Attribute.VALUE]; - } - - private _value: any = undefined; - - constructor(options?: ButtonOptions) { - super({ template: defaultTemplate(), ...options }); - this._upgradeProperty('checked'); - } + private _checked: boolean = false; override connectedCallback() { if (!this.hasAttribute('role')) { @@ -41,47 +30,32 @@ export class RadioButton extends Button { * Whether this radio button is checked. */ get checked(): boolean { - return this.getAttribute(Attribute.ARIA_CHECKED) === 'true'; + return this._checked; } + @property({ reflect: true, type: Boolean, attribute: Attribute.ARIA_CHECKED }) set checked(checked: boolean) { - this.setAttribute(Attribute.ARIA_CHECKED, checked ? 'true' : 'false'); + if (this._checked !== checked) { + this._checked = checked; + this.handleChange(); + this.dispatchEvent(createEvent('change', { bubbles: true })); + } } /** * The value associated with this radio button. */ - get value(): any { - return this._value; - } - - set value(value: any) { - this._value = value; - } + @property({ attribute: Attribute.VALUE }) + accessor value: any = undefined; protected handleClick(): void { this.checked = true; this.dispatchEvent(createEvent('input', { bubbles: true, composed: true })); } - override attributeChangedCallback(attrName: string, oldValue: any, newValue: any) { - super.attributeChangedCallback(attrName, oldValue, newValue); - if (newValue === oldValue) { - return; - } - if (attrName === Attribute.ARIA_CHECKED) { - this.dispatchEvent(createEvent('change', { bubbles: true })); - } else if (attrName === Attribute.VALUE) { - this.value = newValue; - } - if (RadioButton.observedAttributes.indexOf(attrName as Attribute) >= 0) { - shadyCss.styleSubtree(this); - } - } + protected handleChange(): void {} } -customElements.define('theoplayer-radio-button', RadioButton); - declare global { interface HTMLElementTagNameMap { 'theoplayer-radio-button': RadioButton; diff --git a/src/components/TextTrackOffRadioButton.ts b/src/components/TextTrackOffRadioButton.ts index 53b4b1b3..ceef5a5c 100644 --- a/src/components/TextTrackOffRadioButton.ts +++ b/src/components/TextTrackOffRadioButton.ts @@ -1,11 +1,8 @@ +import { html, type HTMLTemplateResult } from 'lit'; +import { customElement, property } from 'lit/decorators.js'; import { RadioButton } from './RadioButton'; -import { buttonTemplate } from './Button'; import type { TextTracksList } from 'theoplayer/chromeless'; import { isNonForcedSubtitleTrack, isSubtitleTrack } from '../util/TrackUtils'; -import { Attribute } from '../util/Attribute'; -import { createTemplate } from '../util/TemplateUtils'; - -const template = createTemplate('theoplayer-text-track-off-radio-button', buttonTemplate(`Off`)); const TRACK_EVENTS = ['change'] as const; @@ -14,18 +11,15 @@ const TRACK_EVENTS = ['change'] as const; * * @group Components */ +@customElement('theoplayer-text-track-off-radio-button') export class TextTrackOffRadioButton extends RadioButton { private _trackList: TextTracksList | undefined = undefined; - constructor() { - super({ template: template() }); - this._upgradeProperty('trackList'); - } - get trackList(): TextTracksList | undefined { return this._trackList; } + @property({ reflect: false, attribute: false }) set trackList(trackList: TextTracksList | undefined) { if (this._trackList) { this._trackList.removeEventListener(TRACK_EVENTS, this._onTrackChange); @@ -51,11 +45,12 @@ export class TextTrackOffRadioButton extends RadioButton { this._updateFromTrackList(); }; - override attributeChangedCallback(attrName: string, oldValue: any, newValue: any) { - super.attributeChangedCallback(attrName, oldValue, newValue); - if (attrName === Attribute.ARIA_CHECKED && oldValue !== newValue) { - this._updateTrackList(); - } + protected override handleChange() { + this._updateTrackList(); + } + + protected override render(): HTMLTemplateResult { + return html`Off`; } } @@ -71,8 +66,6 @@ function disableSubtitleTracks(trackList: TextTracksList): void { } } -customElements.define('theoplayer-text-track-off-radio-button', TextTrackOffRadioButton); - declare global { interface HTMLElementTagNameMap { 'theoplayer-text-track-off-radio-button': TextTrackOffRadioButton; diff --git a/src/components/TextTrackRadioButton.ts b/src/components/TextTrackRadioButton.ts index 2136eb4e..73393909 100644 --- a/src/components/TextTrackRadioButton.ts +++ b/src/components/TextTrackRadioButton.ts @@ -1,11 +1,8 @@ import { RadioButton } from './RadioButton'; -import { buttonTemplate } from './Button'; import type { TextTrack } from 'theoplayer/chromeless'; -import { localizeLanguageName, setTextContent } from '../util/CommonUtils'; -import { Attribute } from '../util/Attribute'; -import { createTemplate } from '../util/TemplateUtils'; - -const template = createTemplate('theoplayer-text-track-radio-button', buttonTemplate(``)); +import { localizeLanguageName } from '../util/CommonUtils'; +import { customElement, property, state } from 'lit/decorators.js'; +import { html, type HTMLTemplateResult } from 'lit'; const TRACK_EVENTS = ['change', 'update'] as const; @@ -14,15 +11,12 @@ const TRACK_EVENTS = ['change', 'update'] as const; * * @group Components */ +@customElement('theoplayer-text-track-radio-button') export class TextTrackRadioButton extends RadioButton { - private _slotEl: HTMLSlotElement; private _track: TextTrack | undefined = undefined; - constructor() { - super({ template: template() }); - this._slotEl = this.shadowRoot!.querySelector('slot')!; - this._upgradeProperty('track'); - } + @state() + private accessor _trackLabel = ''; /** * The text track that is controlled by this radio button. @@ -31,6 +25,7 @@ export class TextTrackRadioButton extends RadioButton { return this._track; } + @property({ reflect: false, attribute: false }) set track(track: TextTrack | undefined) { if (this._track === track) { return; @@ -46,7 +41,7 @@ export class TextTrackRadioButton extends RadioButton { } private _updateFromTrack(): void { - setTextContent(this._slotEl, this._track ? getTrackLabel(this._track) : ''); + this._trackLabel = this._track ? getTrackLabel(this._track) : ''; this.checked = this._track ? this._track.mode === 'showing' : false; } @@ -60,11 +55,12 @@ export class TextTrackRadioButton extends RadioButton { this._updateFromTrack(); }; - override attributeChangedCallback(attrName: string, oldValue: any, newValue: any) { - super.attributeChangedCallback(attrName, oldValue, newValue); - if (attrName === Attribute.ARIA_CHECKED && oldValue !== newValue) { - this._updateTrack(); - } + protected override handleChange(): void { + this._updateTrack(); + } + + protected override render(): HTMLTemplateResult { + return html`${this._trackLabel}`; } } @@ -80,8 +76,6 @@ function getTrackLabel(track: TextTrack): string { return localizeLanguageName(languageCode) || languageCode || ''; } -customElements.define('theoplayer-text-track-radio-button', TextTrackRadioButton); - declare global { interface HTMLElementTagNameMap { 'theoplayer-text-track-radio-button': TextTrackRadioButton; diff --git a/src/components/theolive/quality/AbstractQualitySelector.ts b/src/components/theolive/quality/AbstractQualitySelector.ts index f000001a..aaa4ee96 100644 --- a/src/components/theolive/quality/AbstractQualitySelector.ts +++ b/src/components/theolive/quality/AbstractQualitySelector.ts @@ -1,8 +1,6 @@ import type { ChromelessPlayer, TheoLiveApi } from 'theoplayer/chromeless'; -import { type ButtonOptions } from '../../Button'; -import { StateReceiverMixin } from '../../StateReceiverMixin'; +import { stateReceiver } from '../../StateReceiverMixin'; import { RadioButton } from '../../RadioButton'; -import { Attribute } from '../../../util/Attribute'; /** * A radio button that shows the label of a given video quality, and switches the video track's @@ -10,17 +8,12 @@ import { Attribute } from '../../../util/Attribute'; * * @group Components */ -export abstract class AbstractQualitySelector extends StateReceiverMixin(RadioButton, ['player']) { +@stateReceiver(['player']) +export abstract class AbstractQualitySelector extends RadioButton { private _player: ChromelessPlayer | undefined; private _theoLive: TheoLiveApi | undefined; - protected _slotEl: HTMLSlotElement; protected _badNetworkMode: boolean = false; - protected constructor(options: ButtonOptions) { - super(options); - this._slotEl = this.shadowRoot!.querySelector('slot')!; - } - get player(): ChromelessPlayer | undefined { return this._player; } @@ -62,15 +55,4 @@ export abstract class AbstractQualitySelector extends StateReceiverMixin(RadioBu } protected abstract handlePlayer(): void; - - protected abstract handleChecked(checked: boolean): void; - - override attributeChangedCallback(attrName: string, oldValue: any, newValue: any) { - if (newValue === oldValue) { - return; - } - if (attrName === Attribute.ARIA_CHECKED && oldValue !== newValue) { - this.handleChecked(this.checked); - } - } } diff --git a/src/components/theolive/quality/AutomaticQualitySelector.ts b/src/components/theolive/quality/AutomaticQualitySelector.ts index 79fd4e4e..8c5178ca 100644 --- a/src/components/theolive/quality/AutomaticQualitySelector.ts +++ b/src/components/theolive/quality/AutomaticQualitySelector.ts @@ -1,16 +1,9 @@ import { AbstractQualitySelector } from './AbstractQualitySelector'; -import { setTextContent } from '../../../util/CommonUtils'; -import { createTemplate } from '../../../util/TemplateUtils'; -import { buttonTemplate } from '../../Button'; - -const template = createTemplate('theolive-automatic-quality-selector', buttonTemplate('')); +import { html, type HTMLTemplateResult } from 'lit'; +import { customElement } from 'lit/decorators.js'; +@customElement('theolive-automatic-quality-selector') export class AutomaticQualitySelector extends AbstractQualitySelector { - constructor() { - super({ template: template() }); - setTextContent(this._slotEl, 'High Quality'); - } - handleEnterBadNetworkMode() { this.update_(); } @@ -30,11 +23,13 @@ export class AutomaticQualitySelector extends AbstractQualitySelector { } } - protected handleChecked(checked: boolean) { - if (checked && this.player && this.player.theoLive) { + protected handleChange() { + if (this.checked && this.player && this.player.theoLive) { this.player.theoLive.badNetworkMode = false; } } -} -customElements.define('theolive-automatic-quality-selector', AutomaticQualitySelector); + protected override render(): HTMLTemplateResult { + return html`High Quality`; + } +} diff --git a/src/components/theolive/quality/BadNetworkModeSelector.ts b/src/components/theolive/quality/BadNetworkModeSelector.ts index bed973f0..cc181ac3 100644 --- a/src/components/theolive/quality/BadNetworkModeSelector.ts +++ b/src/components/theolive/quality/BadNetworkModeSelector.ts @@ -1,16 +1,9 @@ import { AbstractQualitySelector } from './AbstractQualitySelector'; -import { setTextContent } from '../../../util/CommonUtils'; -import { createTemplate } from '../../../util/TemplateUtils'; -import { buttonTemplate } from '../../Button'; - -const template = createTemplate('theolive-bad-network-quality-selector', buttonTemplate('')); +import { html, type HTMLTemplateResult } from 'lit'; +import { customElement } from 'lit/decorators.js'; +@customElement('theolive-bad-network-quality-selector') export class BadNetworkModeSelector extends AbstractQualitySelector { - constructor() { - super({ template: template() }); - setTextContent(this._slotEl, 'Low Quality'); - } - handleEnterBadNetworkMode() { this.update_(); } @@ -30,11 +23,13 @@ export class BadNetworkModeSelector extends AbstractQualitySelector { } } - protected handleChecked(checked: boolean) { - if (checked && this.player && this.player.theoLive) { + protected handleChange(): void { + if (this.checked && this.player && this.player.theoLive) { this.player.theoLive.badNetworkMode = true; } } -} -customElements.define('theolive-bad-network-quality-selector', BadNetworkModeSelector); + protected override render(): HTMLTemplateResult { + return html`Low Quality`; + } +} From a6ca331926d3c709d3ca71deefd89351caefee64 Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Thu, 24 Apr 2025 15:51:05 +0200 Subject: [PATCH 035/123] Rework LinkButton --- src/components/LinkButton.ts | 102 +++++++-------------- src/components/ads/AdClickThroughButton.ts | 63 +++++-------- 2 files changed, 57 insertions(+), 108 deletions(-) diff --git a/src/components/LinkButton.ts b/src/components/LinkButton.ts index 1696e468..0f96d7fa 100644 --- a/src/components/LinkButton.ts +++ b/src/components/LinkButton.ts @@ -1,17 +1,9 @@ -import * as shadyCss from '@webcomponents/shadycss'; +import { html, type HTMLTemplateResult, LitElement } from 'lit'; +import { customElement, property, state } from 'lit/decorators.js'; import linkButtonCss from './LinkButton.css'; import { Attribute } from '../util/Attribute'; -import type { ButtonOptions } from './Button'; -import { Button, buttonTemplate } from './Button'; import { KeyCode } from '../util/KeyCode'; -import { toggleAttribute } from '../util/CommonUtils'; -import { createTemplate } from '../util/TemplateUtils'; - -export function linkButtonTemplate(button: string, extraCss: string = ''): string { - return buttonTemplate(`${button}`, `${linkButtonCss}\n${extraCss}`); -} - -const defaultTemplate = createTemplate('theoplayer-link-button', linkButtonTemplate('')); +import { createRef, ref, type Ref } from 'lit/directives/ref.js'; /** * `` - A {@link Button | button} that opens a hyperlink. @@ -19,35 +11,16 @@ const defaultTemplate = createTemplate('theoplayer-link-button', linkButtonTempl * @attribute `disabled` - Whether the button is disabled. When disabled, the button cannot be clicked. * @group Components */ -export class LinkButton extends HTMLElement { - private readonly _linkEl: HTMLAnchorElement; +@customElement('theoplayer-link-button') +export class LinkButton extends LitElement { + static styles = [linkButtonCss]; - static get observedAttributes() { - return [Attribute.DISABLED]; - } + private _disabled: boolean = false; - constructor(options?: ButtonOptions) { - super(); - - const template = options?.template ?? defaultTemplate(); - const shadowRoot = this.attachShadow({ mode: 'open', delegatesFocus: true }); - shadowRoot.appendChild(template.content.cloneNode(true)); - - this._linkEl = shadowRoot.querySelector('a')!; - - this._upgradeProperty('disabled'); - } - - protected _upgradeProperty(prop: keyof this) { - if (this.hasOwnProperty(prop)) { - let value = this[prop]; - delete this[prop]; - this[prop] = value; - } - } + private readonly _linkRef: Ref = createRef(); connectedCallback(): void { - shadyCss.styleElement(this); + super.connectedCallback(); if (!this.hasAttribute('role')) { this.setAttribute('role', 'button'); @@ -62,14 +35,6 @@ export class LinkButton extends HTMLElement { if (!this.hasAttribute(Attribute.DISABLED)) { this._enable(); } - - this._linkEl.addEventListener('keydown', this._onKeyDown); - this._linkEl.addEventListener('click', this._onClick); - } - - disconnectedCallback(): void { - this._linkEl.removeEventListener('keydown', this._onKeyDown); - this._linkEl.removeEventListener('click', this._onClick); } /** @@ -77,32 +42,25 @@ export class LinkButton extends HTMLElement { * * When disabled, the button cannot be clicked. */ - get disabled() { - return this.hasAttribute(Attribute.DISABLED); + get disabled(): boolean { + return this._disabled; } - set disabled(disabled: boolean) { - toggleAttribute(this, Attribute.DISABLED, disabled); + @property({ reflect: true, type: Boolean, attribute: Attribute.DISABLED }) + set disabled(value: boolean) { + this._disabled = value; + if (value) { + this._disable(); + } else { + this._enable(); + } } - protected setLink(href: string, target: string): void { - this._linkEl.href = href; - this._linkEl.target = target; - } + @state() + protected accessor href: string = ''; - attributeChangedCallback(attrName: string, oldValue: any, newValue: any) { - if (attrName === Attribute.DISABLED && newValue !== oldValue) { - const hasValue = newValue != null; - if (hasValue) { - this._disable(); - } else { - this._enable(); - } - } - if (Button.observedAttributes.indexOf(attrName as Attribute) >= 0) { - shadyCss.styleSubtree(this); - } - } + @state() + protected accessor target: string = ''; private _enable(): void { this.setAttribute('aria-disabled', 'false'); @@ -130,7 +88,7 @@ export class LinkButton extends HTMLElement { // case KeyCode.ENTER: case KeyCode.SPACE: event.preventDefault(); - this._linkEl.click(); + this._linkRef.value?.click(); break; // Any other key press is ignored and passed back to the browser. default: @@ -145,11 +103,19 @@ export class LinkButton extends HTMLElement { this.handleClick(); }; + protected override render(): HTMLTemplateResult { + return html`${this.renderLinkContent()}`; + } + + protected renderLinkContent(): HTMLTemplateResult { + return html``; + } + protected handleClick(): void {} } -customElements.define('theoplayer-link-button', LinkButton); - declare global { interface HTMLElementTagNameMap { 'theoplayer-link-button': LinkButton; diff --git a/src/components/ads/AdClickThroughButton.ts b/src/components/ads/AdClickThroughButton.ts index b8455edb..49a3d6b8 100644 --- a/src/components/ads/AdClickThroughButton.ts +++ b/src/components/ads/AdClickThroughButton.ts @@ -1,13 +1,11 @@ -import * as shadyCss from '@webcomponents/shadycss'; -import { LinkButton, linkButtonTemplate } from '../LinkButton'; -import { StateReceiverMixin } from '../StateReceiverMixin'; +import { html, type HTMLTemplateResult } from 'lit'; +import { customElement, property } from 'lit/decorators.js'; +import { LinkButton } from '../LinkButton'; +import { stateReceiver } from '../StateReceiverMixin'; import { Attribute } from '../../util/Attribute'; import type { Ads, ChromelessPlayer } from 'theoplayer/chromeless'; import { arrayFind } from '../../util/CommonUtils'; import { isLinearAd } from '../../util/AdUtils'; -import { createTemplate } from '../../util/TemplateUtils'; - -const template = createTemplate('theoplayer-ad-clickthrough-button', linkButtonTemplate(`Visit Advertiser`)); const AD_EVENTS = ['adbegin', 'adend', 'adloaded', 'updatead', 'adskip'] as const; @@ -16,19 +14,15 @@ const AD_EVENTS = ['adbegin', 'adend', 'adloaded', 'updatead', 'adskip'] as cons * * @group Components */ -export class AdClickThroughButton extends StateReceiverMixin(LinkButton, ['player']) { +@customElement('theoplayer-ad-clickthrough-button') +@stateReceiver(['player']) +export class AdClickThroughButton extends LinkButton { private _player: ChromelessPlayer | undefined; private _ads: Ads | undefined; - - static get observedAttributes() { - return [...LinkButton.observedAttributes, Attribute.CLICKTHROUGH]; - } + private _clickThrough: string | null = null; constructor() { - super({ template: template() }); - - this._upgradeProperty('clickThrough'); - this._upgradeProperty('player'); + super(); } override connectedCallback(): void { @@ -42,21 +36,26 @@ export class AdClickThroughButton extends StateReceiverMixin(LinkButton, ['playe } get clickThrough(): string | null { - return this.getAttribute(Attribute.CLICKTHROUGH); + return this._clickThrough; } + @property({ reflect: true, type: String, attribute: Attribute.CLICKTHROUGH }) set clickThrough(clickThrough: string | null) { - if (clickThrough == null) { - this.removeAttribute(Attribute.CLICKTHROUGH); - } else { - this.setAttribute(Attribute.CLICKTHROUGH, clickThrough); + if (this._clickThrough === clickThrough) { + return; } + this._clickThrough = clickThrough; + this.href = clickThrough ? String(clickThrough).trim() : '#'; + this.target = '_blank'; + this.disabled = clickThrough == null; + this.style.display = clickThrough != null ? '' : 'none'; } get player(): ChromelessPlayer | undefined { return this._player; } + @property({ reflect: false, attribute: false }) set player(player: ChromelessPlayer | undefined) { if (this._player === player) { return; @@ -68,24 +67,6 @@ export class AdClickThroughButton extends StateReceiverMixin(LinkButton, ['playe this._ads?.addEventListener(AD_EVENTS, this._updateFromPlayer); } - attributeChangedCallback(attrName: string, oldValue: any, newValue: any) { - super.attributeChangedCallback(attrName, oldValue, newValue); - if (attrName === Attribute.CLICKTHROUGH && newValue !== oldValue) { - const hasValue = newValue != null; - const href = newValue ? String(newValue).trim() : '#'; - this.setLink(href, '_blank'); - this.disabled = !hasValue; - if (hasValue) { - this.style.display = ''; - } else { - this.style.display = 'none'; - } - } - if (AdClickThroughButton.observedAttributes.indexOf(attrName as Attribute) >= 0) { - shadyCss.styleSubtree(this); - } - } - private readonly _updateFromPlayer = () => { const ads = this._player?.ads; if (!ads || !ads.playing) { @@ -116,9 +97,11 @@ export class AdClickThroughButton extends StateReceiverMixin(LinkButton, ['playe protected override handleClick(): void { this._player?.pause(); } -} -customElements.define('theoplayer-ad-clickthrough-button', AdClickThroughButton); + protected override renderLinkContent(): HTMLTemplateResult { + return html`Visit Advertiser`; + } +} declare global { interface HTMLElementTagNameMap { From ca59da3e01d0581d22e216f5c0c5c28fdb47748c Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Thu, 24 Apr 2025 16:13:37 +0200 Subject: [PATCH 036/123] Rework SeekButton --- src/components/SeekButton.ts | 55 +++++++++++------------------------- 1 file changed, 17 insertions(+), 38 deletions(-) diff --git a/src/components/SeekButton.ts b/src/components/SeekButton.ts index 5a4b45b9..1bb0cb2a 100644 --- a/src/components/SeekButton.ts +++ b/src/components/SeekButton.ts @@ -1,17 +1,12 @@ -import * as shadyCss from '@webcomponents/shadycss'; -import { Button, buttonTemplate } from './Button'; +import { html } from 'lit'; +import { customElement, property } from 'lit/decorators.js'; +import { Button } from './Button'; import type { ChromelessPlayer } from 'theoplayer/chromeless'; import seekButtonCss from './SeekButton.css'; import seekForwardIcon from '../icons/seek-forward.svg'; -import { StateReceiverMixin } from './StateReceiverMixin'; +import { stateReceiver } from './StateReceiverMixin'; import { Attribute } from '../util/Attribute'; -import { setTextContent } from '../util/CommonUtils'; -import { createTemplate } from '../util/TemplateUtils'; - -const template = createTemplate( - 'theoplayer-seek-button', - buttonTemplate(`${seekForwardIcon}` + ``, seekButtonCss) -); +import { unsafeSVG } from 'lit/directives/unsafe-svg.js'; const DEFAULT_SEEK_OFFSET = 10; @@ -21,20 +16,12 @@ const DEFAULT_SEEK_OFFSET = 10; * @attribute `seek-offset` - The offset (in seconds) by which to seek forward (if positive) or backward (if negative). * @group Components */ -export class SeekButton extends StateReceiverMixin(Button, ['player']) { - static get observedAttributes() { - return [...Button.observedAttributes, Attribute.SEEK_OFFSET]; - } +@customElement('theoplayer-seek-button') +@stateReceiver(['player']) +export class SeekButton extends Button { + static styles = [...Button.styles, seekButtonCss]; private _player: ChromelessPlayer | undefined; - private _offsetEl: HTMLElement; - - constructor() { - super({ template: template() }); - this._offsetEl = this.shadowRoot!.querySelector('[part="offset"]')!; - this._upgradeProperty('player'); - this._upgradeProperty('seekOffset'); - } override connectedCallback() { super.connectedCallback(); @@ -44,18 +31,14 @@ export class SeekButton extends StateReceiverMixin(Button, ['player']) { /** * The offset (in seconds) by which to seek forward (if positive) or backward (if negative). */ - get seekOffset(): number { - return Number(this.getAttribute(Attribute.SEEK_OFFSET) ?? DEFAULT_SEEK_OFFSET); - } - - set seekOffset(value: number) { - this.setAttribute(Attribute.SEEK_OFFSET, String(value)); - } + @property({ reflect: true, type: Number, attribute: Attribute.SEEK_OFFSET }) + accessor seekOffset: number = DEFAULT_SEEK_OFFSET; get player(): ChromelessPlayer | undefined { return this._player; } + @property({ reflect: false, attribute: false }) set player(player: ChromelessPlayer | undefined) { this._player = player; } @@ -73,14 +56,7 @@ export class SeekButton extends StateReceiverMixin(Button, ['player']) { override attributeChangedCallback(attrName: string, oldValue: any, newValue: any) { super.attributeChangedCallback(attrName, oldValue, newValue); - if (newValue === oldValue) { - return; - } - if (attrName === Attribute.SEEK_OFFSET) { - setTextContent(this._offsetEl, String(Math.abs(this.seekOffset))); - } if (SeekButton.observedAttributes.indexOf(attrName as Attribute) >= 0) { - shadyCss.styleSubtree(this); this._updateAriaLabel(); } } @@ -90,9 +66,12 @@ export class SeekButton extends StateReceiverMixin(Button, ['player']) { const label = seekOffset >= 0 ? `seek forward by ${seekOffset} seconds` : `seek backward by ${-seekOffset} seconds`; this.setAttribute(Attribute.ARIA_LABEL, label); } -} -customElements.define('theoplayer-seek-button', SeekButton); + protected override render() { + return html`${unsafeSVG(seekForwardIcon)}${String(Math.abs(this.seekOffset))}`; + } +} declare global { interface HTMLElementTagNameMap { From 8c45be16ad2bbb321b006cd29f960ef51b7a7384 Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Thu, 24 Apr 2025 16:14:09 +0200 Subject: [PATCH 037/123] Rework ad components --- src/components/ads/AdDisplay.ts | 50 ++++++++---------- src/components/ads/AdSkipButton.ts | 82 +++++++++++++----------------- 2 files changed, 55 insertions(+), 77 deletions(-) diff --git a/src/components/ads/AdDisplay.ts b/src/components/ads/AdDisplay.ts index 283ebfe8..b1e22a46 100644 --- a/src/components/ads/AdDisplay.ts +++ b/src/components/ads/AdDisplay.ts @@ -1,13 +1,11 @@ -import * as shadyCss from '@webcomponents/shadycss'; +import { html, type HTMLTemplateResult, LitElement } from 'lit'; +import { customElement, property, state } from 'lit/decorators.js'; import textDisplayCss from '../TextDisplay.css'; import adDisplayCss from './AdDisplay.css'; -import { StateReceiverMixin } from '../StateReceiverMixin'; +import { stateReceiver } from '../StateReceiverMixin'; import type { Ads, ChromelessPlayer } from 'theoplayer/chromeless'; -import { arrayFind, setTextContent } from '../../util/CommonUtils'; +import { arrayFind } from '../../util/CommonUtils'; import { isLinearAd } from '../../util/AdUtils'; -import { createTemplate } from '../../util/TemplateUtils'; - -const template = createTemplate('theoplayer-ad-display', ``); const AD_EVENTS = ['adbreakbegin', 'adbreakend', 'adbreakchange', 'updateadbreak', 'adbegin', 'adend', 'adskip', 'addad', 'updatead'] as const; @@ -17,30 +15,19 @@ const AD_EVENTS = ['adbreakbegin', 'adbreakend', 'adbreakchange', 'updateadbreak * * @group Components */ -export class AdDisplay extends StateReceiverMixin(HTMLElement, ['player']) { - private readonly _spanEl: HTMLElement; +@customElement('theoplayer-ad-display') +@stateReceiver(['player']) +export class AdDisplay extends LitElement { + static styles = [textDisplayCss, adDisplayCss]; + private _player: ChromelessPlayer | undefined; private _ads: Ads | undefined; - constructor() { - super(); - const shadowRoot = this.attachShadow({ mode: 'open' }); - shadowRoot.appendChild(template().content.cloneNode(true)); - this._spanEl = shadowRoot.querySelector('span')!; - - this._upgradeProperty('player'); - } - - protected _upgradeProperty(prop: keyof this) { - if (this.hasOwnProperty(prop)) { - let value = this[prop]; - delete this[prop]; - this[prop] = value; - } - } + @state() + private accessor _text: string = ''; connectedCallback(): void { - shadyCss.styleElement(this); + super.connectedCallback(); this._updateFromPlayer(); } @@ -48,6 +35,7 @@ export class AdDisplay extends StateReceiverMixin(HTMLElement, ['player']) { return this._player; } + @property({ reflect: false, attribute: false }) set player(player: ChromelessPlayer | undefined) { if (this._player === player) { return; @@ -63,7 +51,7 @@ export class AdDisplay extends StateReceiverMixin(HTMLElement, ['player']) { const ads = this._player?.ads; const linearAds = (ads?.currentAdBreak?.ads ?? []).filter(isLinearAd); if (ads === undefined || !ads.playing || linearAds.length === 0) { - setTextContent(this._spanEl, ''); + this._text = ''; this.style.display = 'none'; return; } @@ -73,18 +61,20 @@ export class AdDisplay extends StateReceiverMixin(HTMLElement, ['player']) { if (currentLinearAd) { const currentAdIndex = linearAds.indexOf(currentLinearAd); if (currentAdIndex >= 0) { - setTextContent(this._spanEl, `Ad ${currentAdIndex + 1} of ${linearAds.length}`); + this._text = `Ad ${currentAdIndex + 1} of ${linearAds.length}`; this.style.display = ''; return; } } } - setTextContent(this._spanEl, 'Ad'); + this._text = 'Ad'; this.style.display = ''; }; -} -customElements.define('theoplayer-ad-display', AdDisplay); + protected override render(): HTMLTemplateResult { + return html`${this._text}`; + } +} declare global { interface HTMLElementTagNameMap { diff --git a/src/components/ads/AdSkipButton.ts b/src/components/ads/AdSkipButton.ts index bfe0f074..48c51f52 100644 --- a/src/components/ads/AdSkipButton.ts +++ b/src/components/ads/AdSkipButton.ts @@ -1,25 +1,15 @@ -import * as shadyCss from '@webcomponents/shadycss'; -import { Button, buttonTemplate } from '../Button'; +import { html, type HTMLTemplateResult } from 'lit'; +import { customElement, property, state } from 'lit/decorators.js'; +import { unsafeSVG } from 'lit/directives/unsafe-svg.js'; +import { styleMap } from 'lit/directives/style-map.js'; +import { Button } from '../Button'; import adSkipButtonCss from './AdSkipButton.css'; import skipNextIcon from '../../icons/skip-next.svg'; -import { StateReceiverMixin } from '../StateReceiverMixin'; +import { stateReceiver } from '../StateReceiverMixin'; import { Attribute } from '../../util/Attribute'; import type { Ads, ChromelessPlayer } from 'theoplayer/chromeless'; -import { arrayFind, setTextContent } from '../../util/CommonUtils'; +import { arrayFind } from '../../util/CommonUtils'; import { isLinearAd } from '../../util/AdUtils'; -import { createTemplate } from '../../util/TemplateUtils'; - -const template = createTemplate( - 'theoplayer-ad-skip-button', - buttonTemplate( - `Skip in 0 seconds` + - `` + - `Skip Ad ` + - `${skipNextIcon}` + - ``, - adSkipButtonCss - ) -); const AD_EVENTS = ['adbegin', 'adend', 'adloaded', 'updatead', 'adskip'] as const; @@ -29,23 +19,19 @@ const AD_EVENTS = ['adbegin', 'adend', 'adloaded', 'updatead', 'adskip'] as cons * * @group Components */ -export class AdSkipButton extends StateReceiverMixin(Button, ['player']) { - private readonly _countdownEl: HTMLElement; - private readonly _skipEl: HTMLElement; +@customElement('theoplayer-ad-skip-button') +@stateReceiver(['player']) +export class AdSkipButton extends Button { + static styles = [...Button.styles, adSkipButtonCss]; + private _player: ChromelessPlayer | undefined; private _ads: Ads | undefined; - static get observedAttributes() { - return [...Button.observedAttributes]; - } - - constructor() { - super({ template: template() }); - this._countdownEl = this.shadowRoot!.querySelector('[part="countdown"]')!; - this._skipEl = this.shadowRoot!.querySelector('[part="skip"]')!; + @state() + private accessor _showCountdown: boolean = false; - this._upgradeProperty('player'); - } + @state() + private accessor _timeToSkip: number = 0; override connectedCallback(): void { super.connectedCallback(); @@ -56,6 +42,7 @@ export class AdSkipButton extends StateReceiverMixin(Button, ['player']) { return this._player; } + @property({ reflect: false, attribute: false }) set player(player: ChromelessPlayer | undefined) { if (this._player === player) { return; @@ -72,13 +59,6 @@ export class AdSkipButton extends StateReceiverMixin(Button, ['player']) { this._player?.ads?.skip(); } - attributeChangedCallback(attrName: string, oldValue: any, newValue: any) { - super.attributeChangedCallback(attrName, oldValue, newValue); - if (AdSkipButton.observedAttributes.indexOf(attrName as Attribute) >= 0) { - shadyCss.styleSubtree(this); - } - } - private readonly _onAdChange = () => { if (this._player?.ads?.playing) { this._player.removeEventListener('timeupdate', this._update); @@ -116,27 +96,35 @@ export class AdSkipButton extends StateReceiverMixin(Button, ['player']) { const currentTime = this._player!.currentTime; if (currentTime < skipOffset) { // Show countdown. - const timeToSkip = Math.ceil(skipOffset - currentTime); - setTextContent(this._countdownEl, `Skip in ${timeToSkip}s`); - this._countdownEl.style.visibility = 'visible'; - this._skipEl.style.visibility = 'hidden'; - this._skipEl.style.pointerEvents = 'none'; + this._showCountdown = true; + this._timeToSkip = Math.ceil(skipOffset - currentTime); this.style.display = ''; this.setAttribute(Attribute.DISABLED, ''); this.setAttribute(Attribute.ARIA_LIVE, 'off'); } else { // Show skip button. - this._countdownEl.style.visibility = 'hidden'; - this._skipEl.style.visibility = 'visible'; - this._skipEl.style.pointerEvents = ''; + this._showCountdown = false; this.style.display = ''; this.removeAttribute(Attribute.DISABLED); this.setAttribute(Attribute.ARIA_LIVE, 'polite'); } }; -} -customElements.define('theoplayer-ad-skip-button', AdSkipButton); + protected override render(): HTMLTemplateResult { + const countdownStyles = { + visibility: this._showCountdown ? 'visible' : 'hidden' + }; + const skipStyles = { + visibility: this._showCountdown ? 'hidden' : 'visible', + pointerEvents: this._showCountdown ? 'none' : '' + }; + return html`Skip in ${this._timeToSkip} seconds + + Skip Ad + ${unsafeSVG(skipNextIcon)} + `; + } +} declare global { interface HTMLElementTagNameMap { From 57b0f6df8c1fe2aabc060973b1fe458b1849ff62 Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Thu, 24 Apr 2025 16:22:12 +0200 Subject: [PATCH 038/123] Rework THEOlive components --- .../theolive/quality/BadNetworkModeButton.ts | 47 ++++++++-------- .../theolive/quality/BadNetworkModeMenu.ts | 55 ++++++------------- 2 files changed, 42 insertions(+), 60 deletions(-) diff --git a/src/components/theolive/quality/BadNetworkModeButton.ts b/src/components/theolive/quality/BadNetworkModeButton.ts index 536d62b3..df6a307c 100644 --- a/src/components/theolive/quality/BadNetworkModeButton.ts +++ b/src/components/theolive/quality/BadNetworkModeButton.ts @@ -1,15 +1,14 @@ +import { html, type HTMLTemplateResult } from 'lit'; +import { customElement, property, state } from 'lit/decorators.js'; +import { styleMap } from 'lit/directives/style-map.js'; +import { unsafeSVG } from 'lit/directives/unsafe-svg.js'; import { ChromelessPlayer as TheoPlayer, type TheoLiveApi } from 'theoplayer/chromeless'; -import settingsCss from './BadNetworkModeButton.css'; -import { buttonTemplate } from '../../Button'; +import badNetworkModeButtonCss from './BadNetworkModeButton.css'; import settingsIcon from '../../../icons/settings.svg'; import warningIcon from '../../../icons/warning.svg'; -import { StateReceiverMixin } from '../../StateReceiverMixin'; +import { stateReceiver } from '../../StateReceiverMixin'; import { MenuButton } from '../../MenuButton'; import { Attribute } from '../../../util/Attribute'; -import { createTemplate } from '../../../util/TemplateUtils'; - -const html = `${settingsIcon}${warningIcon}`; -const template = createTemplate('theolive-bad-network-button', buttonTemplate(html, settingsCss)); /** * A menu button that opens a settings menu. @@ -17,14 +16,16 @@ const template = createTemplate('theolive-bad-network-button', buttonTemplate(ht * @attribute `menu` - The ID of the settings menu. * @group Components */ -export class BadNetworkModeButton extends StateReceiverMixin(MenuButton, ['player']) { +@customElement('theolive-bad-network-button') +@stateReceiver(['player']) +export class BadNetworkModeButton extends MenuButton { + static styles = [...MenuButton.styles, badNetworkModeButtonCss]; + private _player: TheoPlayer | undefined; private _theoLive: TheoLiveApi | undefined; - private _warningIcon: HTMLElement | undefined; - constructor() { - super({ template: template() }); - } + @state() + private accessor _inBadNetworkMode = false; override connectedCallback() { super.connectedCallback(); @@ -32,26 +33,21 @@ export class BadNetworkModeButton extends StateReceiverMixin(MenuButton, ['playe if (!this.hasAttribute(Attribute.ARIA_LABEL)) { this.setAttribute(Attribute.ARIA_LABEL, 'open settings menu'); } - - this._warningIcon = this.shadowRoot!.querySelector('[part="warning-icon"]') ?? undefined; } private readonly handleEnterBadNetworkMode_ = () => { - if (this._warningIcon) { - this._warningIcon.style.display = 'block'; - } + this._inBadNetworkMode = true; }; private readonly handleExitBadNetworkMode_ = () => { - if (this._warningIcon) { - this._warningIcon.style.display = 'none'; - } + this._inBadNetworkMode = false; }; get player(): TheoPlayer | undefined { return this._player; } + @property({ reflect: false, attribute: false }) set player(player: TheoPlayer | undefined) { if (this._player === player) { return; @@ -67,6 +63,13 @@ export class BadNetworkModeButton extends StateReceiverMixin(MenuButton, ['playe this._theoLive.addEventListener('exitbadnetworkmode', this.handleExitBadNetworkMode_); } } -} -customElements.define('theolive-bad-network-button', BadNetworkModeButton); + protected override render(): HTMLTemplateResult { + const warningStyles = { + display: this._inBadNetworkMode ? '' : 'none' + }; + return html`${unsafeSVG(settingsIcon)}${unsafeSVG(warningIcon)}`; + } +} diff --git a/src/components/theolive/quality/BadNetworkModeMenu.ts b/src/components/theolive/quality/BadNetworkModeMenu.ts index ec9b28be..3e0ac577 100644 --- a/src/components/theolive/quality/BadNetworkModeMenu.ts +++ b/src/components/theolive/quality/BadNetworkModeMenu.ts @@ -1,55 +1,34 @@ -import * as shadyCss from '@webcomponents/shadycss'; +import { html, type HTMLTemplateResult, LitElement, type PropertyValues } from 'lit'; +import { customElement } from 'lit/decorators.js'; +import { createRef, type Ref } from 'lit/directives/ref.js'; import verticalRadioGroupCss from '../../VerticalRadioGroup.css'; import './AutomaticQualitySelector'; import './BadNetworkModeSelector'; import { RadioGroup } from '../../RadioGroup'; -import { createTemplate } from '../../../util/TemplateUtils'; -const html = ` - - - - -`; -const template = createTemplate('theolive-bad-network-menu', html); +@customElement('theolive-bad-network-menu') +export class BadNetworkModeMenu extends LitElement { + static styles = [verticalRadioGroupCss]; -export class BadNetworkModeMenu extends HTMLElement { - private readonly _radioGroup: RadioGroup; + private readonly _radioGroup: Ref = createRef(); - constructor() { - super(); - const shadowRoot = this.attachShadow({ mode: 'open' }); - shadowRoot.appendChild(template().content.cloneNode(true)); - this._radioGroup = shadowRoot.querySelector('theoplayer-radio-group')!; - } - - protected _upgradeProperty(prop: keyof this) { - if (this.hasOwnProperty(prop)) { - let value = this[prop]; - delete this[prop]; - this[prop] = value; - } - } - - connectedCallback(): void { - shadyCss.styleElement(this); - - if (!(this._radioGroup instanceof RadioGroup)) { - customElements.upgrade(this._radioGroup); + protected firstUpdated(_changedProperties: PropertyValues) { + if (this._radioGroup.value && !(this._radioGroup.value instanceof RadioGroup)) { + customElements.upgrade(this._radioGroup.value); } - this.shadowRoot!.addEventListener('change', this._onChange); - } - - disconnectedCallback(): void { - this.shadowRoot!.removeEventListener('change', this._onChange); } private readonly _onChange = () => { this.dispatchEvent(new Event('change', { bubbles: true })); }; -} -customElements.define('theolive-bad-network-menu', BadNetworkModeMenu); + protected override render(): HTMLTemplateResult { + return html` + + + `; + } +} declare global { interface HTMLElementTagNameMap { From 1ab7c5ab81af9872e55b024a265c87605828c8a2 Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Thu, 24 Apr 2025 16:31:04 +0200 Subject: [PATCH 039/123] Rework MuteButton --- src/components/MuteButton.ts | 59 +++++++++++++++--------------------- 1 file changed, 24 insertions(+), 35 deletions(-) diff --git a/src/components/MuteButton.ts b/src/components/MuteButton.ts index 61bc6300..4b6180a2 100644 --- a/src/components/MuteButton.ts +++ b/src/components/MuteButton.ts @@ -1,23 +1,14 @@ -import * as shadyCss from '@webcomponents/shadycss'; -import { Button, buttonTemplate } from './Button'; +import { html, type HTMLTemplateResult } from 'lit'; +import { customElement, property } from 'lit/decorators.js'; +import { unsafeSVG } from 'lit/directives/unsafe-svg.js'; +import { Button } from './Button'; import type { ChromelessPlayer } from 'theoplayer/chromeless'; import muteButtonCss from './MuteButton.css'; import offIcon from '../icons/volume-off.svg'; import lowIcon from '../icons/volume-low.svg'; import highIcon from '../icons/volume-high.svg'; -import { StateReceiverMixin } from './StateReceiverMixin'; +import { stateReceiver } from './StateReceiverMixin'; import { Attribute } from '../util/Attribute'; -import { createTemplate } from '../util/TemplateUtils'; - -const template = createTemplate( - 'theoplayer-mute-button', - buttonTemplate( - `${offIcon}` + - `${lowIcon}` + - `${highIcon}`, - muteButtonCss - ) -); export type VolumeLevel = 'off' | 'low' | 'high'; @@ -30,18 +21,13 @@ const PLAYER_EVENTS = ['volumechange'] as const; * Can be "off" (muted), "low" (volume < 50%) or "high" (volume >= 50%). * @group Components */ -export class MuteButton extends StateReceiverMixin(Button, ['player']) { - static get observedAttributes() { - return [...Button.observedAttributes, Attribute.VOLUME_LEVEL]; - } +@customElement('theoplayer-mute-button') +@stateReceiver(['player']) +export class MuteButton extends Button { + static styles = [...Button.styles, muteButtonCss]; private _player: ChromelessPlayer | undefined; - constructor() { - super({ template: template() }); - this._upgradeProperty('player'); - } - override connectedCallback(): void { super.connectedCallback(); this._updateFromPlayer(); @@ -51,14 +37,14 @@ export class MuteButton extends StateReceiverMixin(Button, ['player']) { /** * The volume level of the player. */ - get volumeLevel(): VolumeLevel { - return (this.getAttribute(Attribute.VOLUME_LEVEL) as VolumeLevel | null) || 'off'; - } + @property({ reflect: true, state: true, type: String, attribute: Attribute.VOLUME_LEVEL }) + accessor volumeLevel: VolumeLevel = 'off'; get player(): ChromelessPlayer | undefined { return this._player; } + @property({ reflect: false, attribute: false }) set player(player: ChromelessPlayer | undefined) { if (this._player === player) { return; @@ -74,19 +60,19 @@ export class MuteButton extends StateReceiverMixin(Button, ['player']) { } private readonly _updateFromPlayer = () => { - let volumeLevel: VolumeLevel = 'off'; if (this._player !== undefined) { const volume = this._player.volume; const muted = this._player.muted; if (muted) { - volumeLevel = 'off'; + this.volumeLevel = 'off'; } else if (volume < 0.5) { - volumeLevel = 'low'; + this.volumeLevel = 'low'; } else { - volumeLevel = 'high'; + this.volumeLevel = 'high'; } + } else { + this.volumeLevel = 'off'; } - this.setAttribute(Attribute.VOLUME_LEVEL, volumeLevel); }; protected override handleClick() { @@ -99,18 +85,21 @@ export class MuteButton extends StateReceiverMixin(Button, ['player']) { override attributeChangedCallback(attrName: string, oldValue: any, newValue: any) { super.attributeChangedCallback(attrName, oldValue, newValue); if (MuteButton.observedAttributes.indexOf(attrName as Attribute) >= 0) { - shadyCss.styleSubtree(this); this._updateAriaLabel(); } } private _updateAriaLabel(): void { - const label = this.getAttribute(Attribute.VOLUME_LEVEL) === 'off' ? 'unmute' : 'mute'; + const label = this.volumeLevel === 'off' ? 'unmute' : 'mute'; this.setAttribute(Attribute.ARIA_LABEL, label); } -} -customElements.define('theoplayer-mute-button', MuteButton); + protected override render(): HTMLTemplateResult { + return html`${unsafeSVG(offIcon)}${unsafeSVG(lowIcon)}${unsafeSVG(highIcon)}`; + } +} declare global { interface HTMLElementTagNameMap { From 7b7d8917e9e29b9074d92db31d80ebdd4c03ef80 Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Thu, 24 Apr 2025 16:35:22 +0200 Subject: [PATCH 040/123] Rework ActiveQualityDisplay --- src/components/ActiveQualityDisplay.ts | 59 ++++++-------------------- 1 file changed, 12 insertions(+), 47 deletions(-) diff --git a/src/components/ActiveQualityDisplay.ts b/src/components/ActiveQualityDisplay.ts index a34d9e1d..c1e48f50 100644 --- a/src/components/ActiveQualityDisplay.ts +++ b/src/components/ActiveQualityDisplay.ts @@ -1,60 +1,27 @@ -import { StateReceiverMixin } from './StateReceiverMixin'; -import { setTextContent } from '../util/CommonUtils'; +import { html, type HTMLTemplateResult, LitElement } from 'lit'; +import { customElement, property } from 'lit/decorators.js'; +import { stateReceiver } from './StateReceiverMixin'; import type { VideoQuality } from 'theoplayer/chromeless'; import { formatQualityLabel } from '../util/TrackUtils'; -import * as shadyCss from '@webcomponents/shadycss'; /** * `` - A control that displays the name of the active video quality. * * @group Components */ -export class ActiveQualityDisplay extends StateReceiverMixin(HTMLElement, ['activeVideoQuality', 'targetVideoQualities']) { - private readonly _spanEl: HTMLSpanElement; +@customElement('theoplayer-active-quality-display') +@stateReceiver(['activeVideoQuality', 'targetVideoQualities']) +export class ActiveQualityDisplay extends LitElement { private _activeVideoQuality: VideoQuality | undefined = undefined; private _targetVideoQualities: VideoQuality[] | undefined = undefined; - constructor() { - super(); - const shadowRoot = this.attachShadow({ mode: 'open' }); - this._spanEl = document.createElement('span'); - shadowRoot.appendChild(this._spanEl); + @property({ reflect: false, attribute: false }) + accessor activeVideoQuality: VideoQuality | undefined = undefined; - this._upgradeProperty('activeVideoQuality'); - this._upgradeProperty('targetVideoQualities'); - } - - connectedCallback(): void { - shadyCss.styleElement(this); - } - - protected _upgradeProperty(prop: keyof this) { - if (this.hasOwnProperty(prop)) { - let value = this[prop]; - delete this[prop]; - this[prop] = value; - } - } - - get activeVideoQuality(): VideoQuality | undefined { - return this._activeVideoQuality; - } + @property({ reflect: false, attribute: false }) + accessor targetVideoQualities: VideoQuality[] | undefined = undefined; - set activeVideoQuality(quality: VideoQuality | undefined) { - this._activeVideoQuality = quality; - this.update_(); - } - - get targetVideoQualities(): VideoQuality[] | undefined { - return this._targetVideoQualities; - } - - set targetVideoQualities(qualities: VideoQuality[] | undefined) { - this._targetVideoQualities = qualities; - this.update_(); - } - - private update_(): void { + protected override render(): HTMLTemplateResult { // If no target quality is selected, or more than one target quality is selected, // treat as "automatic" quality selection. const hasSingleTargetQuality = this._targetVideoQualities !== undefined && this._targetVideoQualities.length === 1; @@ -70,12 +37,10 @@ export class ActiveQualityDisplay extends StateReceiverMixin(HTMLElement, ['acti // Automatic quality selection: "Automatic" or "Automatic (720p)" label = `Automatic${qualityLabel ? ` (${qualityLabel})` : ''}`; } - setTextContent(this._spanEl, label); + return html`${label}`; } } -customElements.define('theoplayer-active-quality-display', ActiveQualityDisplay); - declare global { interface HTMLElementTagNameMap { 'theoplayer-active-quality-display': ActiveQualityDisplay; From 8d27e4b94d0733124974a2e9d7b0222eb237a2d0 Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Thu, 24 Apr 2025 16:39:49 +0200 Subject: [PATCH 041/123] Rework ChromecastDisplay --- src/components/ChromecastDisplay.ts | 54 +++++++++++++---------------- 1 file changed, 25 insertions(+), 29 deletions(-) diff --git a/src/components/ChromecastDisplay.ts b/src/components/ChromecastDisplay.ts index 893f28cd..fd0f32e0 100644 --- a/src/components/ChromecastDisplay.ts +++ b/src/components/ChromecastDisplay.ts @@ -1,16 +1,17 @@ -import * as shadyCss from '@webcomponents/shadycss'; +import { html, type HTMLTemplateResult, LitElement } from 'lit'; +import { customElement, state } from 'lit/decorators.js'; +import { unsafeSVG } from 'lit/directives/unsafe-svg.js'; import chromecastDisplayCss from './ChromecastDisplay.css'; import chromecastIcon from '../icons/chromecast-48px.svg'; -import { StateReceiverMixin } from './StateReceiverMixin'; +import { stateReceiver } from './StateReceiverMixin'; import type { Chromecast, ChromelessPlayer } from 'theoplayer/chromeless'; -import { setTextContent } from '../util/CommonUtils'; import { Attribute } from '../util/Attribute'; import { createTemplate } from '../util/TemplateUtils'; const template = createTemplate( 'theoplayer-chromecast-display', - `` + - `
${chromecastIcon}
` + + + `
${chromecastIcon}
` + `
` + `

Playing on

` + `

Chromecast Receiver

` + @@ -24,30 +25,19 @@ const CAST_EVENTS = ['statechange'] as const; * * @group Components */ -export class ChromecastDisplay extends StateReceiverMixin(HTMLElement, ['player']) { - private readonly _receiverNameEl: HTMLElement; +@customElement('theoplayer-chromecast-display') +@stateReceiver(['player']) +export class ChromecastDisplay extends LitElement { + static styles = [chromecastDisplayCss]; + private _player: ChromelessPlayer | undefined; private _castApi: Chromecast | undefined; - constructor() { - super(); - const shadowRoot = this.attachShadow({ mode: 'open' }); - shadowRoot.appendChild(template().content.cloneNode(true)); - this._receiverNameEl = shadowRoot.querySelector('[part="receiver"]')!; - - this._upgradeProperty('player'); - } - - protected _upgradeProperty(prop: keyof this) { - if (this.hasOwnProperty(prop)) { - let value = this[prop]; - delete this[prop]; - this[prop] = value; - } - } + @state() + private accessor _receiverName: string = 'Chromecast'; connectedCallback(): void { - shadyCss.styleElement(this); + super.connectedCallback(); if (!this.hasAttribute(Attribute.HIDDEN)) { this.setAttribute(Attribute.HIDDEN, ''); } @@ -77,14 +67,20 @@ export class ChromecastDisplay extends StateReceiverMixin(HTMLElement, ['player' const chromecast = this._player?.cast?.chromecast; if (chromecast === undefined || chromecast.state !== 'connected') { this.setAttribute(Attribute.HIDDEN, ''); - return; + } else { + this._receiverName = chromecast.receiverName || 'Chromecast'; + this.removeAttribute(Attribute.HIDDEN); } - setTextContent(this._receiverNameEl, chromecast.receiverName || ''); - this.removeAttribute(Attribute.HIDDEN); }; -} -customElements.define('theoplayer-chromecast-display', ChromecastDisplay); + protected override render(): HTMLTemplateResult { + return html`
${unsafeSVG(chromecastIcon)}
+
+

Playing on

+

${this._receiverName}

+
`; + } +} declare global { interface HTMLElementTagNameMap { From f952ad2ba9bf8b0357c998c7137d23f36aafe464 Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Fri, 25 Apr 2025 11:18:50 +0200 Subject: [PATCH 042/123] Rework buttons --- src/components/CloseMenuButton.ts | 23 ++--- src/components/FullscreenButton.ts | 49 ++++------- src/components/LanguageMenuButton.ts | 25 +++--- src/components/LiveButton.ts | 94 ++++++--------------- src/components/MenuButton.ts | 38 ++++----- src/components/PlaybackRateMenuButton.ts | 18 ++-- src/components/SettingsMenuButton.ts | 18 ++-- src/components/TextTrackStyleResetButton.ts | 27 +++--- 8 files changed, 103 insertions(+), 189 deletions(-) diff --git a/src/components/CloseMenuButton.ts b/src/components/CloseMenuButton.ts index 35ce48a7..bcfcb5c8 100644 --- a/src/components/CloseMenuButton.ts +++ b/src/components/CloseMenuButton.ts @@ -1,11 +1,11 @@ -import { Button, buttonTemplate } from './Button'; +import { html, type HTMLTemplateResult } from 'lit'; +import { customElement } from 'lit/decorators.js'; +import { unsafeSVG } from 'lit/directives/unsafe-svg.js'; +import { Button } from './Button'; import backIcon from '../icons/back.svg'; import { createCustomEvent } from '../util/EventUtils'; import { CLOSE_MENU_EVENT, type CloseMenuEvent } from '../events/CloseMenuEvent'; import { Attribute } from '../util/Attribute'; -import { createTemplate } from '../util/TemplateUtils'; - -const template = createTemplate('theoplayer-menu-close-button', buttonTemplate(`${backIcon}`)); /** * `` - A button that closes its parent menu. @@ -14,15 +14,8 @@ const template = createTemplate('theoplayer-menu-close-button', buttonTemplate(` * * @group Components */ +@customElement('theoplayer-menu-close-button') export class CloseMenuButton extends Button { - static get observedAttributes() { - return [...Button.observedAttributes]; - } - - constructor() { - super({ template: template() }); - } - override connectedCallback() { super.connectedCallback(); @@ -38,9 +31,11 @@ export class CloseMenuButton extends Button { }); this.dispatchEvent(event); } -} -customElements.define('theoplayer-menu-close-button', CloseMenuButton); + protected override render(): HTMLTemplateResult { + return html`${unsafeSVG(backIcon)}`; + } +} declare global { interface HTMLElementTagNameMap { diff --git a/src/components/FullscreenButton.ts b/src/components/FullscreenButton.ts index 0ede0e9b..0ca3f5cf 100644 --- a/src/components/FullscreenButton.ts +++ b/src/components/FullscreenButton.ts @@ -1,52 +1,33 @@ -import * as shadyCss from '@webcomponents/shadycss'; -import { Button, buttonTemplate } from './Button'; +import { html, type HTMLTemplateResult } from 'lit'; +import { customElement, property } from 'lit/decorators.js'; +import { unsafeSVG } from 'lit/directives/unsafe-svg.js'; +import { Button } from './Button'; import fullscreenButtonCss from './FullscreenButton.css'; import enterIcon from '../icons/fullscreen-enter.svg'; import exitIcon from '../icons/fullscreen-exit.svg'; -import { StateReceiverMixin } from './StateReceiverMixin'; +import { stateReceiver } from './StateReceiverMixin'; import { createCustomEvent } from '../util/EventUtils'; import { ENTER_FULLSCREEN_EVENT, type EnterFullscreenEvent } from '../events/EnterFullscreenEvent'; import { EXIT_FULLSCREEN_EVENT, type ExitFullscreenEvent } from '../events/ExitFullscreenEvent'; import { Attribute } from '../util/Attribute'; -import { toggleAttribute } from '../util/CommonUtils'; -import { createTemplate } from '../util/TemplateUtils'; - -const template = createTemplate( - 'theoplayer-fullscreen-button', - buttonTemplate( - `${enterIcon}` + - `${exitIcon}`, - fullscreenButtonCss - ) -); /** * `` - A button that toggles fullscreen. * * @group Components */ -export class FullscreenButton extends StateReceiverMixin(Button, ['fullscreen']) { - static get observedAttributes() { - return [...Button.observedAttributes, Attribute.FULLSCREEN]; - } - - constructor() { - super({ template: template() }); - this._upgradeProperty('fullscreen'); - } +@customElement('theoplayer-fullscreen-button') +@stateReceiver(['fullscreen']) +export class FullscreenButton extends Button { + static styles = [...Button.styles, fullscreenButtonCss]; override connectedCallback() { super.connectedCallback(); this._updateAriaLabel(); } - get fullscreen(): boolean { - return this.hasAttribute(Attribute.FULLSCREEN); - } - - set fullscreen(fullscreen: boolean) { - toggleAttribute(this, Attribute.FULLSCREEN, fullscreen); - } + @property({ reflect: true, type: Boolean, attribute: Attribute.FULLSCREEN }) + accessor fullscreen: boolean = false; protected override handleClick(): void { if (!this.fullscreen) { @@ -67,7 +48,6 @@ export class FullscreenButton extends StateReceiverMixin(Button, ['fullscreen']) override attributeChangedCallback(attrName: string, oldValue: any, newValue: any) { super.attributeChangedCallback(attrName, oldValue, newValue); if (FullscreenButton.observedAttributes.indexOf(attrName as Attribute) >= 0) { - shadyCss.styleSubtree(this); this._updateAriaLabel(); } } @@ -76,9 +56,12 @@ export class FullscreenButton extends StateReceiverMixin(Button, ['fullscreen']) const label = this.fullscreen ? 'exit fullscreen' : 'enter fullscreen'; this.setAttribute(Attribute.ARIA_LABEL, label); } -} -customElements.define('theoplayer-fullscreen-button', FullscreenButton); + protected override render(): HTMLTemplateResult { + return html`${unsafeSVG(enterIcon)} + ${unsafeSVG(exitIcon)}`; + } +} declare global { interface HTMLElementTagNameMap { diff --git a/src/components/LanguageMenuButton.ts b/src/components/LanguageMenuButton.ts index fc525722..f697c5bd 100644 --- a/src/components/LanguageMenuButton.ts +++ b/src/components/LanguageMenuButton.ts @@ -1,14 +1,13 @@ +import { html, type HTMLTemplateResult } from 'lit'; +import { customElement, property } from 'lit/decorators.js'; +import { unsafeSVG } from 'lit/directives/unsafe-svg.js'; import { MenuButton } from './MenuButton'; -import { buttonTemplate } from './Button'; import languageIcon from '../icons/language.svg'; -import { StateReceiverMixin } from './StateReceiverMixin'; +import { stateReceiver } from './StateReceiverMixin'; import type { ChromelessPlayer, MediaTrackList, TextTracksList } from 'theoplayer/chromeless'; import { isNonForcedSubtitleTrack } from '../util/TrackUtils'; import { Attribute } from '../util/Attribute'; import { toggleAttribute } from '../util/CommonUtils'; -import { createTemplate } from '../util/TemplateUtils'; - -const template = createTemplate('theoplayer-language-menu-button', buttonTemplate(`${languageIcon}`)); const TRACK_EVENTS = ['addtrack', 'removetrack'] as const; @@ -20,16 +19,13 @@ const TRACK_EVENTS = ['addtrack', 'removetrack'] as const; * @attribute `menu` - The ID of the language menu. * @group Components */ -export class LanguageMenuButton extends StateReceiverMixin(MenuButton, ['player']) { +@customElement('theoplayer-language-menu-button') +@stateReceiver(['player']) +export class LanguageMenuButton extends MenuButton { private _player: ChromelessPlayer | undefined; private _audioTrackList: MediaTrackList | undefined; private _textTrackList: TextTracksList | undefined; - constructor() { - super({ template: template() }); - this._upgradeProperty('player'); - } - override connectedCallback() { super.connectedCallback(); @@ -42,6 +38,7 @@ export class LanguageMenuButton extends StateReceiverMixin(MenuButton, ['player' return this._player; } + @property({ reflect: false, attribute: false }) set player(player: ChromelessPlayer | undefined) { if (this._player === player) { return; @@ -61,9 +58,11 @@ export class LanguageMenuButton extends StateReceiverMixin(MenuButton, ['player' this._player !== undefined && (this._player.audioTracks.length >= 2 || this._player.textTracks.some(isNonForcedSubtitleTrack)); toggleAttribute(this, Attribute.HIDDEN, !hasTracks); }; -} -customElements.define('theoplayer-language-menu-button', LanguageMenuButton); + protected override render(): HTMLTemplateResult { + return html`${unsafeSVG(languageIcon)}`; + } +} declare global { interface HTMLElementTagNameMap { diff --git a/src/components/LiveButton.ts b/src/components/LiveButton.ts index 9b095284..9d8459dc 100644 --- a/src/components/LiveButton.ts +++ b/src/components/LiveButton.ts @@ -1,23 +1,12 @@ -import * as shadyCss from '@webcomponents/shadycss'; -import { Button, buttonTemplate } from './Button'; +import { Button } from './Button'; import type { ChromelessPlayer } from 'theoplayer/chromeless'; import liveButtonCss from './LiveButton.css'; import liveIcon from '../icons/live.svg'; -import { StateReceiverMixin } from './StateReceiverMixin'; +import { stateReceiver } from './StateReceiverMixin'; import { Attribute } from '../util/Attribute'; import type { StreamType } from '../util/StreamType'; -import { toggleAttribute } from '../util/CommonUtils'; -import { createTemplate } from '../util/TemplateUtils'; - -const template = createTemplate( - 'theoplayer-live-button', - buttonTemplate( - `${liveIcon}` + - ` ` + - `LIVE`, - liveButtonCss - ) -); +import { html, type HTMLTemplateResult } from 'lit'; +import { customElement, property } from 'lit/decorators.js'; const PAUSED_EVENTS = ['play', 'pause', 'playing', 'emptied'] as const; const LIVE_EVENTS = ['seeking', 'seeked', 'timeupdate', 'durationchange', 'emptied'] as const; @@ -33,22 +22,13 @@ const DEFAULT_LIVE_THRESHOLD = 10; * @attribute `live` (readonly) - Whether the player is considered to be playing at the live point. * @group Components */ -export class LiveButton extends StateReceiverMixin(Button, ['player', 'streamType']) { - static get observedAttributes() { - return [...Button.observedAttributes, Attribute.STREAM_TYPE, Attribute.LIVE, Attribute.PAUSED, Attribute.LIVE_THRESHOLD]; - } +@customElement('theoplayer-live-button') +@stateReceiver(['player', 'streamType']) +export class LiveButton extends Button { + static styles = [...Button.styles, liveButtonCss]; private _player: ChromelessPlayer | undefined; - - constructor() { - super({ template: template() }); - - this._upgradeProperty('paused'); - this._upgradeProperty('streamType'); - this._upgradeProperty('liveThreshold'); - this._upgradeProperty('live'); - this._upgradeProperty('player'); - } + private _liveThreshold: number = DEFAULT_LIVE_THRESHOLD; connectedCallback() { super.connectedCallback(); @@ -58,43 +38,30 @@ export class LiveButton extends StateReceiverMixin(Button, ['player', 'streamTyp } } - get paused(): boolean { - return this.hasAttribute(Attribute.PAUSED); - } - - set paused(paused: boolean) { - toggleAttribute(this, Attribute.PAUSED, paused); - } + @property({ reflect: true, type: Boolean, attribute: Attribute.PAUSED }) + accessor paused: boolean = false; - get streamType(): StreamType { - return (this.getAttribute(Attribute.STREAM_TYPE) || 'vod') as StreamType; - } - - set streamType(streamType: StreamType) { - this.setAttribute(Attribute.STREAM_TYPE, streamType); - } + @property({ reflect: true, type: String, attribute: Attribute.STREAM_TYPE }) + accessor streamType: StreamType = 'vod'; get liveThreshold(): number { - return Number(this.getAttribute(Attribute.LIVE_THRESHOLD) ?? DEFAULT_LIVE_THRESHOLD); + return this._liveThreshold; } + @property({ reflect: true, type: Number, attribute: Attribute.LIVE_THRESHOLD, useDefault: true }) set liveThreshold(value: number) { - value = Number(value); - this.setAttribute(Attribute.LIVE_THRESHOLD, String(isNaN(value) ? 0 : value)); + this._liveThreshold = isNaN(value) ? 0 : value; + this._updateLive(); } - get live(): boolean { - return this.hasAttribute(Attribute.LIVE); - } - - set live(live: boolean) { - toggleAttribute(this, Attribute.LIVE, live); - } + @property({ reflect: true, type: Boolean, attribute: Attribute.LIVE }) + accessor live: boolean = false; get player(): ChromelessPlayer | undefined { return this._player; } + @property({ reflect: false, attribute: false }) set player(player: ChromelessPlayer | undefined) { if (this._player === player) { return; @@ -117,10 +84,7 @@ export class LiveButton extends StateReceiverMixin(Button, ['player', 'streamTyp }; private readonly _updateLive = () => { - const live = this._player !== undefined ? isLive(this._player, this.liveThreshold) : false; - if (this.live !== live) { - this.live = live; - } + this.live = this._player !== undefined ? isLive(this._player, this.liveThreshold) : false; }; protected override handleClick() { @@ -135,22 +99,12 @@ export class LiveButton extends StateReceiverMixin(Button, ['player', 'streamTyp } } - override attributeChangedCallback(attrName: string, oldValue: any, newValue: any) { - super.attributeChangedCallback(attrName, oldValue, newValue); - if (newValue === oldValue) { - return; - } - if (attrName === Attribute.LIVE_THRESHOLD) { - this._updateLive(); - } - if (LiveButton.observedAttributes.indexOf(attrName as Attribute) >= 0) { - shadyCss.styleSubtree(this); - } + protected override render(): HTMLTemplateResult { + return html`${liveIcon} LIVE`; } } -customElements.define('theoplayer-live-button', LiveButton); - declare global { interface HTMLElementTagNameMap { 'theoplayer-live-button': LiveButton; diff --git a/src/components/MenuButton.ts b/src/components/MenuButton.ts index ce7b1f9a..3ee8a195 100644 --- a/src/components/MenuButton.ts +++ b/src/components/MenuButton.ts @@ -1,8 +1,9 @@ -import { Button, type ButtonOptions, buttonTemplate } from './Button'; +import { Button, buttonTemplate } from './Button'; import { createCustomEvent } from '../util/EventUtils'; import { TOGGLE_MENU_EVENT, type ToggleMenuEvent } from '../events/ToggleMenuEvent'; import { Attribute } from '../util/Attribute'; import { createTemplate } from '../util/TemplateUtils'; +import { customElement, property } from 'lit/decorators.js'; const template = createTemplate('theoplayer-menu-button', buttonTemplate(``)); @@ -12,16 +13,9 @@ const template = createTemplate('theoplayer-menu-button', buttonTemplate(` * @attribute `menu` - The ID of the menu to open. * @group Components */ +@customElement('theoplayer-menu-button') export class MenuButton extends Button { - static get observedAttributes() { - return [...Button.observedAttributes, Attribute.MENU]; - } - - constructor(options?: Partial) { - super({ template: template(), ...options }); - - this._upgradeProperty('menu'); - } + private _menuId: string | null = null; override connectedCallback() { super.connectedCallback(); @@ -35,26 +29,26 @@ export class MenuButton extends Button { * The ID of the menu to open. */ get menu(): string | null { - return this.getAttribute(Attribute.MENU); + return this._menuId; } + @property({ reflect: true, type: String, attribute: Attribute.MENU }) set menu(menuId: string | null) { if (menuId) { - this.setAttribute(Attribute.MENU, menuId); + if (this._ariaControls == null || this._ariaControls === this._menuId) { + this._ariaControls = menuId; + } } else { - this.removeAttribute(Attribute.MENU); - } - } - - override attributeChangedCallback(attrName: string, oldValue: any, newValue: any) { - super.attributeChangedCallback(attrName, oldValue, newValue); - if (attrName === Attribute.MENU) { - if (!this.hasAttribute('aria-controls') || this.getAttribute('aria-controls') === oldValue) { - this.setAttribute('aria-controls', newValue); + if (this._ariaControls === this._menuId) { + this._ariaControls = null; } } + this._menuId = menuId; } + @property({ reflect: true, state: true, type: String, attribute: 'aria-controls' }) + private accessor _ariaControls: string | null = null; + protected override handleClick() { const menu = this.menu; if (menu) { @@ -68,8 +62,6 @@ export class MenuButton extends Button { } } -customElements.define('theoplayer-menu-button', MenuButton); - declare global { interface HTMLElementTagNameMap { 'theoplayer-menu-button': MenuButton; diff --git a/src/components/PlaybackRateMenuButton.ts b/src/components/PlaybackRateMenuButton.ts index 60813f24..9f6a28e4 100644 --- a/src/components/PlaybackRateMenuButton.ts +++ b/src/components/PlaybackRateMenuButton.ts @@ -1,10 +1,9 @@ -import { buttonTemplate } from './Button'; +import { html, type HTMLTemplateResult } from 'lit'; +import { customElement } from 'lit/decorators.js'; +import { unsafeSVG } from 'lit/directives/unsafe-svg.js'; import { MenuButton } from './MenuButton'; import speedIcon from '../icons/speed.svg'; import { Attribute } from '../util/Attribute'; -import { createTemplate } from '../util/TemplateUtils'; - -const template = createTemplate('theoplayer-playback-rate-button', buttonTemplate(`${speedIcon}`)); /** * `` - A menu button that opens a [playback rate menu]{@link PlaybackRateMenu}. @@ -12,11 +11,8 @@ const template = createTemplate('theoplayer-playback-rate-button', buttonTemplat * @attribute menu - The ID of the playback rate menu. * @group Components */ +@customElement('theoplayer-playback-rate-menu-button') export class PlaybackRateMenuButton extends MenuButton { - constructor() { - super({ template: template() }); - } - override connectedCallback() { super.connectedCallback(); @@ -24,9 +20,11 @@ export class PlaybackRateMenuButton extends MenuButton { this.setAttribute(Attribute.ARIA_LABEL, 'open playback speed menu'); } } -} -customElements.define('theoplayer-playback-rate-menu-button', PlaybackRateMenuButton); + protected override render(): HTMLTemplateResult { + return html`${unsafeSVG(speedIcon)}`; + } +} declare global { interface HTMLElementTagNameMap { diff --git a/src/components/SettingsMenuButton.ts b/src/components/SettingsMenuButton.ts index cb781dd3..6f2ebf5f 100644 --- a/src/components/SettingsMenuButton.ts +++ b/src/components/SettingsMenuButton.ts @@ -1,10 +1,9 @@ +import { html, type HTMLTemplateResult } from 'lit'; +import { customElement } from 'lit/decorators.js'; +import { unsafeSVG } from 'lit/directives/unsafe-svg.js'; import { MenuButton } from './MenuButton'; -import { buttonTemplate } from './Button'; import settingsIcon from '../icons/settings.svg'; import { Attribute } from '../util/Attribute'; -import { createTemplate } from '../util/TemplateUtils'; - -const template = createTemplate('theoplayer-settings-menu-button', buttonTemplate(`${settingsIcon}`)); /** * `` - A menu button that opens a {@link SettingsMenu}. @@ -12,11 +11,8 @@ const template = createTemplate('theoplayer-settings-menu-button', buttonTemplat * @attribute `menu` - The ID of the settings menu. * @group Components */ +@customElement('theoplayer-settings-menu-button') export class SettingsMenuButton extends MenuButton { - constructor() { - super({ template: template() }); - } - override connectedCallback() { super.connectedCallback(); @@ -24,9 +20,11 @@ export class SettingsMenuButton extends MenuButton { this.setAttribute(Attribute.ARIA_LABEL, 'open settings menu'); } } -} -customElements.define('theoplayer-settings-menu-button', SettingsMenuButton); + protected override render(): HTMLTemplateResult { + return html`${unsafeSVG(settingsIcon)}`; + } +} declare global { interface HTMLElementTagNameMap { diff --git a/src/components/TextTrackStyleResetButton.ts b/src/components/TextTrackStyleResetButton.ts index 1238b6d8..8576da86 100644 --- a/src/components/TextTrackStyleResetButton.ts +++ b/src/components/TextTrackStyleResetButton.ts @@ -1,26 +1,19 @@ -import { Button, buttonTemplate } from './Button'; -import { StateReceiverMixin } from './StateReceiverMixin'; +import { html, type HTMLTemplateResult } from 'lit'; +import { customElement } from 'lit/decorators.js'; +import { Button } from './Button'; +import { stateReceiver } from './StateReceiverMixin'; import type { ChromelessPlayer } from 'theoplayer/chromeless'; -import { createTemplate } from '../util/TemplateUtils'; - -const template = createTemplate('theoplayer-text-track-style-reset-button', buttonTemplate(`Reset`)); /** * `` - A button that resets the text track style. * * @group Components */ -export class TextTrackStyleResetButton extends StateReceiverMixin(Button, ['player']) { - static get observedAttributes() { - return [...Button.observedAttributes]; - } - +@customElement('theoplayer-text-track-style-reset-button') +@stateReceiver(['player']) +export class TextTrackStyleResetButton extends Button { private _player: ChromelessPlayer | undefined; - constructor() { - super({ template: template() }); - } - get player(): ChromelessPlayer | undefined { return this._player; } @@ -41,9 +34,11 @@ export class TextTrackStyleResetButton extends StateReceiverMixin(Button, ['play textTrackStyle.windowColor = undefined; textTrackStyle.edgeStyle = undefined; } -} -customElements.define('theoplayer-text-track-style-reset-button', TextTrackStyleResetButton); + protected override render(): HTMLTemplateResult { + return html`Reset`; + } +} declare global { interface HTMLElementTagNameMap { From 5c707904243d245af34677503af3ec0d595e77c6 Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Fri, 25 Apr 2025 11:29:17 +0200 Subject: [PATCH 043/123] Fix RadioButton not properly showing checked state --- src/components/RadioButton.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/components/RadioButton.ts b/src/components/RadioButton.ts index 3453b953..e6cd5976 100644 --- a/src/components/RadioButton.ts +++ b/src/components/RadioButton.ts @@ -33,7 +33,15 @@ export class RadioButton extends Button { return this._checked; } - @property({ reflect: true, type: Boolean, attribute: Attribute.ARIA_CHECKED }) + @property({ + reflect: true, + type: Boolean, + attribute: Attribute.ARIA_CHECKED, + converter: { + fromAttribute: (value: string | null) => value != null && value !== 'false', + toAttribute: (value: boolean) => String(value) + } + }) set checked(checked: boolean) { if (this._checked !== checked) { this._checked = checked; From e7040d5a6e17b9133f298ccaa3bae3f625b132a0 Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Fri, 25 Apr 2025 11:30:18 +0200 Subject: [PATCH 044/123] Simplify MediaTrackRadioButton --- src/components/MediaTrackRadioButton.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/components/MediaTrackRadioButton.ts b/src/components/MediaTrackRadioButton.ts index 2fb01d8c..ed7813a0 100644 --- a/src/components/MediaTrackRadioButton.ts +++ b/src/components/MediaTrackRadioButton.ts @@ -3,7 +3,6 @@ import { customElement, property, state } from 'lit/decorators.js'; import { RadioButton } from './RadioButton'; import type { MediaTrack } from 'theoplayer/chromeless'; import { localizeLanguageName } from '../util/CommonUtils'; -import { Attribute } from '../util/Attribute'; const TRACK_EVENTS = ['change', 'update'] as const; @@ -57,11 +56,8 @@ export class MediaTrackRadioButton extends RadioButton { this._updateFromTrack(); }; - override attributeChangedCallback(attrName: string, oldValue: any, newValue: any) { - super.attributeChangedCallback(attrName, oldValue, newValue); - if (attrName === Attribute.ARIA_CHECKED && oldValue !== newValue) { - this._updateTrack(); - } + protected override handleChange(): void { + this._updateTrack(); } protected override render(): HTMLTemplateResult { From 703d5350216a430893302658f7e31318ceef2365 Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Fri, 25 Apr 2025 11:48:30 +0200 Subject: [PATCH 045/123] Fix QualityRadioButton not showing its label --- src/components/QualityRadioButton.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/components/QualityRadioButton.ts b/src/components/QualityRadioButton.ts index 4abe463e..c54ed98b 100644 --- a/src/components/QualityRadioButton.ts +++ b/src/components/QualityRadioButton.ts @@ -1,7 +1,8 @@ +import { html, type HTMLTemplateResult } from 'lit'; +import { customElement, property, state } from 'lit/decorators.js'; import { RadioButton } from './RadioButton'; import type { MediaTrack, VideoQuality } from 'theoplayer/chromeless'; import { formatQualityLabel } from '../util/TrackUtils'; -import { customElement, property, state } from 'lit/decorators.js'; const TRACK_EVENTS = ['activequalitychanged', 'targetqualitychanged'] as const; const QUALITY_EVENTS = ['update'] as const; @@ -96,6 +97,10 @@ export class QualityRadioButton extends RadioButton { this._track.targetQuality = this._quality; } } + + protected override render(): HTMLTemplateResult { + return html`${this._qualityLabel}`; + } } declare global { From eca7cadae85f8923c6a5079239e3719f591698e0 Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Fri, 25 Apr 2025 11:48:39 +0200 Subject: [PATCH 046/123] Rework menus --- src/components/Menu.ts | 117 +++++++----------- ...RateMenu.html => PlaybackRateMenu.html.ts} | 6 +- src/components/PlaybackRateMenu.ts | 21 ++-- 3 files changed, 60 insertions(+), 84 deletions(-) rename src/components/{PlaybackRateMenu.html => PlaybackRateMenu.html.ts} (76%) diff --git a/src/components/Menu.ts b/src/components/Menu.ts index 67827b5c..7c0c9a7c 100644 --- a/src/components/Menu.ts +++ b/src/components/Menu.ts @@ -1,25 +1,11 @@ -import * as shadyCss from '@webcomponents/shadycss'; +import { html, type HTMLTemplateResult, LitElement } from 'lit'; +import { customElement, property } from 'lit/decorators.js'; import menuCss from './Menu.css'; import { CLOSE_MENU_EVENT, type CloseMenuEvent } from '../events/CloseMenuEvent'; import { MENU_CHANGE_EVENT, type MenuChangeEvent } from '../events/MenuChangeEvent'; import { createCustomEvent } from '../util/EventUtils'; import { Attribute } from '../util/Attribute'; import { toggleAttribute } from '../util/CommonUtils'; -import { createTemplate } from '../util/TemplateUtils'; - -export interface MenuOptions { - template?: HTMLTemplateElement; -} - -export function menuTemplate(heading: string, content: string, extraCss: string = ''): string { - return ( - `` + - `
${heading}
` + - `
${content}
` - ); -} - -const defaultTemplate = createTemplate('theoplayer-menu', menuTemplate('', '')); /** * `` - A menu that can be opened on top of the player. @@ -34,96 +20,71 @@ const defaultTemplate = createTemplate('theoplayer-menu', menuTemplate('` for its contents, and a named `"heading"` `` for its heading text. - * Subclasses can override this by passing a different {@link MenuOptions.template} in the options, - * using {@link menuTemplate} to correctly style the custom template. - * - * @param options - The options for this menu. + * Subclasses can override this by overriding {@link renderMenuHeading} and/or {@link renderMenuContent}. */ - constructor(options?: MenuOptions) { + constructor() { super(); - const template = options?.template ?? defaultTemplate(); - const shadowRoot = this.attachShadow({ mode: 'open', delegatesFocus: true }); - shadowRoot.appendChild(template.content.cloneNode(true)); - - this._contentEl = shadowRoot.querySelector('[part="content"]')!; - } - - protected _upgradeProperty(prop: keyof this) { - if (this.hasOwnProperty(prop)) { - let value = this[prop]; - delete this[prop]; - this[prop] = value; - } } connectedCallback(): void { - shadyCss.styleElement(this); + super.connectedCallback(); - if (!this.hasAttribute(Attribute.MENU_OPENED)) { - this.setAttribute('hidden', ''); + if (!this.menuOpened_) { + this.setAttribute(Attribute.HIDDEN, ''); } - - this._contentEl.addEventListener('input', this._onContentInput); - } - - disconnectedCallback(): void { - this._contentEl.removeEventListener('input', this._onContentInput); } /** * Whether to automatically close the menu whenever one of its controls * receives an input (e.g. when a radio button is clicked). */ - get closeOnInput(): boolean { - return this.hasAttribute(Attribute.MENU_CLOSE_ON_INPUT); + @property({ reflect: true, type: Boolean, attribute: Attribute.MENU_CLOSE_ON_INPUT }) + accessor closeOnInput: boolean = false; + + private get menuOpened_(): boolean { + return this._menuOpened; } - set closeOnInput(value: boolean) { - toggleAttribute(this, Attribute.MENU_CLOSE_ON_INPUT, value); + @property({ reflect: true, state: true, type: Boolean, attribute: Attribute.MENU_OPENED }) + private set menuOpened_(menuOpened: boolean) { + if (this._menuOpened === menuOpened) return; + this._menuOpened = menuOpened; + toggleAttribute(this, Attribute.HIDDEN, !menuOpened); + const changeEvent: MenuChangeEvent = createCustomEvent(MENU_CHANGE_EVENT, { bubbles: true }); + this.dispatchEvent(changeEvent); } /** * Open the menu. */ openMenu(): void { - this.setAttribute(Attribute.MENU_OPENED, ''); + this.menuOpened_ = true; } /** * Close the menu. */ closeMenu(): void { - this.removeAttribute(Attribute.MENU_OPENED); - } - - attributeChangedCallback(attrName: string, oldValue: any, newValue: any) { - if (newValue === oldValue) { - return; - } - if (attrName === Attribute.MENU_OPENED) { - toggleAttribute(this, Attribute.HIDDEN, newValue == null); - const changeEvent: MenuChangeEvent = createCustomEvent(MENU_CHANGE_EVENT, { bubbles: true }); - this.dispatchEvent(changeEvent); - } - if (Menu.observedAttributes.indexOf(attrName as Attribute) >= 0) { - shadyCss.styleSubtree(this); - } + this.menuOpened_ = false; } private readonly _onContentInput = (): void => { // Close menu when clicking any button - if (this.hasAttribute(Attribute.MENU_CLOSE_ON_INPUT)) { + if (this.closeOnInput) { const event: CloseMenuEvent = createCustomEvent(CLOSE_MENU_EVENT, { bubbles: true, composed: true @@ -131,9 +92,23 @@ export class Menu extends HTMLElement { this.dispatchEvent(event); } }; -} -customElements.define('theoplayer-menu', Menu); + protected override render(): HTMLTemplateResult { + return html`
+ + ${this.renderMenuHeading()} +
+
${this.renderMenuContent()}
`; + } + + protected renderMenuHeading(): HTMLTemplateResult { + return html``; + } + + protected renderMenuContent(): HTMLTemplateResult { + return html``; + } +} declare global { interface HTMLElementTagNameMap { diff --git a/src/components/PlaybackRateMenu.html b/src/components/PlaybackRateMenu.html.ts similarity index 76% rename from src/components/PlaybackRateMenu.html rename to src/components/PlaybackRateMenu.html.ts index deb0349e..6a2b82bf 100644 --- a/src/components/PlaybackRateMenu.html +++ b/src/components/PlaybackRateMenu.html.ts @@ -1,8 +1,10 @@ - +import { html } from 'lit'; + +export default html` 0.25x 0.5x Normal 1.25x 1.5x 2x - +`; diff --git a/src/components/PlaybackRateMenu.ts b/src/components/PlaybackRateMenu.ts index dbbea354..79041082 100644 --- a/src/components/PlaybackRateMenu.ts +++ b/src/components/PlaybackRateMenu.ts @@ -1,15 +1,11 @@ -import { Menu, menuTemplate } from './Menu'; +import { html, type HTMLTemplateResult } from 'lit'; +import { customElement } from 'lit/decorators.js'; +import { Menu } from './Menu'; import playbackRateMenuHtml from './PlaybackRateMenu.html'; -import { createTemplate } from '../util/TemplateUtils'; // Load components used in template import './PlaybackRateRadioGroup'; -const template = createTemplate( - 'theoplayer-playback-rate-menu', - menuTemplate(`Playback speed`, playbackRateMenuHtml) -); - /** * `` - A menu to change the playback rate of the player. * @@ -17,13 +13,16 @@ const template = createTemplate( * * @group Components */ +@customElement('theoplayer-playback-rate-menu') export class PlaybackRateMenu extends Menu { - constructor() { - super({ template: template() }); + protected override renderMenuHeading(): HTMLTemplateResult { + return html`Playback speed`; } -} -customElements.define('theoplayer-playback-rate-menu', PlaybackRateMenu); + protected override renderMenuContent(): HTMLTemplateResult { + return playbackRateMenuHtml; + } +} declare global { interface HTMLElementTagNameMap { From e95fd9808f95794478a0b491695e7d6d5e8202e9 Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Fri, 25 Apr 2025 12:27:14 +0200 Subject: [PATCH 047/123] Rework RadioGroup --- src/components/RadioGroup.ts | 46 +++++++++++++++++++----------------- 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/src/components/RadioGroup.ts b/src/components/RadioGroup.ts index 0c77df49..18489683 100644 --- a/src/components/RadioGroup.ts +++ b/src/components/RadioGroup.ts @@ -1,9 +1,11 @@ -import * as shadyCss from '@webcomponents/shadycss'; +import { html, type HTMLTemplateResult, LitElement } from 'lit'; +import { customElement, property } from 'lit/decorators.js'; +import { createRef, ref, type Ref } from 'lit/directives/ref.js'; import { isArrowKey, KeyCode } from '../util/KeyCode'; import { RadioButton } from './RadioButton'; import { createEvent } from '../util/EventUtils'; -import { arrayFind, getSlottedElements, isElement, noOp, upgradeCustomElementIfNeeded } from '../util/CommonUtils'; -import { StateReceiverMixin } from './StateReceiverMixin'; +import { arrayFind, getSlottedElements, noOp, upgradeCustomElementIfNeeded } from '../util/CommonUtils'; +import { stateReceiver } from './StateReceiverMixin'; import { Attribute } from '../util/Attribute'; import type { DeviceType } from '../util/DeviceType'; import { navigateByArrowKey } from '../util/KeyboardNavigation'; @@ -26,16 +28,14 @@ const template = createTemplate('theoplayer-radio-group', ``); */ // Based on howto-radio-group // https://github.com/GoogleChromeLabs/howto-components/blob/079d0fa34ff9038b26ea8883b1db5dd6b677d7ba/elements/howto-radio-group/howto-radio-group.js -export class RadioGroup extends StateReceiverMixin(HTMLElement, ['deviceType']) { - private _slot: HTMLSlotElement; +@customElement('theoplayer-radio-group') +@stateReceiver(['deviceType']) +export class RadioGroup extends LitElement { + private readonly _slotRef: Ref = createRef(); private _radioButtons: RadioButton[] = []; constructor() { super(); - const shadowRoot = this.attachShadow({ mode: 'open' }); - shadowRoot.appendChild(template().content.cloneNode(true)); - - this._slot = shadowRoot.querySelector('slot')!; this._upgradeProperty('deviceType'); } @@ -48,34 +48,34 @@ export class RadioGroup extends StateReceiverMixin(HTMLElement, ['deviceType']) } connectedCallback(): void { - shadyCss.styleElement(this); + super.connectedCallback(); if (!this.hasAttribute('role')) { this.setAttribute('role', 'radiogroup'); } this.addEventListener('keydown', this._onKeyDown); - this.shadowRoot!.addEventListener('change', this._onButtonChange); - this._slot.addEventListener('slotchange', this._onSlotChange); this._onSlotChange(); } disconnectedCallback(): void { + super.disconnectedCallback(); this.removeEventListener('keydown', this._onKeyDown); - this.shadowRoot!.removeEventListener('change', this._onButtonChange); - this._slot.removeEventListener('slotchange', this._onSlotChange); } - get deviceType(): DeviceType { - return (this.getAttribute(Attribute.DEVICE_TYPE) || 'desktop') as DeviceType; + protected override createRenderRoot(): HTMLElement | DocumentFragment { + const root = super.createRenderRoot(); + root.addEventListener('change', this._onButtonChange); + return root; } - set deviceType(deviceType: DeviceType) { - this.setAttribute(Attribute.DEVICE_TYPE, deviceType); - } + @property({ reflect: true, type: String, attribute: Attribute.DEVICE_TYPE }) + accessor deviceType: DeviceType = 'desktop'; private readonly _onSlotChange = () => { - const children = getSlottedElements(this._slot); + const slot = this._slotRef.value; + if (!slot) return; + const children = getSlottedElements(slot); const upgradePromises: Array> = []; for (const child of children) { if (!isRadioButton(child)) { @@ -226,9 +226,11 @@ export class RadioGroup extends StateReceiverMixin(HTMLElement, ['deviceType']) this.setCheckedRadioButton(event.target as RadioButton); } }; -} -customElements.define('theoplayer-radio-group', RadioGroup); + protected override render(): HTMLTemplateResult { + return html``; + } +} declare global { interface HTMLElementTagNameMap { From e86a22c864a339b3788cb97ed7f8c7c7728f44ca Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Fri, 25 Apr 2025 12:27:23 +0200 Subject: [PATCH 048/123] Rework PlaybackRateRadioGroup --- src/components/PlaybackRateRadioGroup.ts | 80 +++++++++--------------- 1 file changed, 30 insertions(+), 50 deletions(-) diff --git a/src/components/PlaybackRateRadioGroup.ts b/src/components/PlaybackRateRadioGroup.ts index 2cdbb181..f5bf5774 100644 --- a/src/components/PlaybackRateRadioGroup.ts +++ b/src/components/PlaybackRateRadioGroup.ts @@ -1,16 +1,12 @@ -import * as shadyCss from '@webcomponents/shadycss'; +import { html, type HTMLTemplateResult, LitElement } from 'lit'; +import { customElement, state } from 'lit/decorators.js'; +import { createRef, ref, type Ref } from 'lit/directives/ref.js'; import { RadioGroup } from './RadioGroup'; import verticalRadioGroupCss from './VerticalRadioGroup.css'; -import { StateReceiverMixin } from './StateReceiverMixin'; +import { stateReceiver } from './StateReceiverMixin'; import type { ChromelessPlayer } from 'theoplayer/chromeless'; import type { RadioButton } from './RadioButton'; import { createEvent } from '../util/EventUtils'; -import { createTemplate } from '../util/TemplateUtils'; - -const template = createTemplate( - 'theoplayer-playback-rate-radio-group', - `` -); /** * `` - A radio group that shows a list of playback rates, @@ -21,47 +17,17 @@ const template = createTemplate( * For example: `2x` * @group Components */ -export class PlaybackRateRadioGroup extends StateReceiverMixin(HTMLElement, ['player']) { - private readonly _radioGroup: RadioGroup; - private readonly _optionsSlot: HTMLSlotElement; +@customElement('theoplayer-playback-rate-radio-group') +@stateReceiver(['player']) +export class PlaybackRateRadioGroup extends LitElement { + static override styles = [verticalRadioGroupCss]; + + private readonly _radioGroupRef: Ref = createRef(); private _player: ChromelessPlayer | undefined; private _value: number = 1; - constructor() { - super(); - const shadowRoot = this.attachShadow({ mode: 'open' }); - shadowRoot.appendChild(template().content.cloneNode(true)); - - this._radioGroup = shadowRoot.querySelector('theoplayer-radio-group')!; - this._optionsSlot = shadowRoot.querySelector('slot')!; - - this._upgradeProperty('value'); - this._upgradeProperty('player'); - } - - protected _upgradeProperty(prop: keyof this) { - if (this.hasOwnProperty(prop)) { - let value = this[prop]; - delete this[prop]; - this[prop] = value; - } - } - - connectedCallback(): void { - shadyCss.styleElement(this); - - if (!(this._radioGroup instanceof RadioGroup)) { - customElements.upgrade(this._radioGroup); - } - + protected override firstUpdated(): void { this._updateChecked(); - this.shadowRoot!.addEventListener('change', this._onChange); - this._optionsSlot.addEventListener('slotchange', this._updateChecked); - } - - disconnectedCallback(): void { - this.shadowRoot!.removeEventListener('change', this._onChange); - this._optionsSlot.removeEventListener('slotchange', this._updateChecked); } /** @@ -71,6 +37,7 @@ export class PlaybackRateRadioGroup extends StateReceiverMixin(HTMLElement, ['pl return this._value; } + @state() set value(value: number) { value = Number(value); if (this._value === value) { @@ -88,6 +55,7 @@ export class PlaybackRateRadioGroup extends StateReceiverMixin(HTMLElement, ['pl return this._player; } + @state() set player(player: ChromelessPlayer | undefined) { if (this._player === player) { return; @@ -102,16 +70,24 @@ export class PlaybackRateRadioGroup extends StateReceiverMixin(HTMLElement, ['pl } } + private get radioGroup_(): RadioGroup | undefined { + const radioGroup = this._radioGroupRef.value; + if (radioGroup && !(radioGroup instanceof RadioGroup)) { + customElements.upgrade(radioGroup); + } + return radioGroup; + } + private readonly _updateChecked = (): void => { - const buttons = this._radioGroup.allRadioButtons(); + const buttons = this.radioGroup_?.allRadioButtons() ?? []; for (const button of buttons) { button.checked = Number(button.value) === this.value; } }; private readonly _onChange = (): void => { - const button = this._radioGroup.checkedRadioButton as RadioButton | null; - if (button !== null && this.value !== Number(button.value)) { + const button = this.radioGroup_?.checkedRadioButton; + if (button && this.value !== Number(button.value)) { this.value = button.value; } }; @@ -121,9 +97,13 @@ export class PlaybackRateRadioGroup extends StateReceiverMixin(HTMLElement, ['pl this.value = this._player.playbackRate; } }; -} -customElements.define('theoplayer-playback-rate-radio-group', PlaybackRateRadioGroup); + protected override render(): HTMLTemplateResult { + return html``; + } +} declare global { interface HTMLElementTagNameMap { From 6527c0d92105c1370c3bf42f17f98e1203d60d80 Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Fri, 25 Apr 2025 12:27:29 +0200 Subject: [PATCH 049/123] Rework QualityRadioGroup --- src/components/QualityRadioGroup.ts | 105 +++++++++------------------- 1 file changed, 34 insertions(+), 71 deletions(-) diff --git a/src/components/QualityRadioGroup.ts b/src/components/QualityRadioGroup.ts index 457d527f..929c0733 100644 --- a/src/components/QualityRadioGroup.ts +++ b/src/components/QualityRadioGroup.ts @@ -1,17 +1,11 @@ -import * as shadyCss from '@webcomponents/shadycss'; -import { RadioGroup } from './RadioGroup'; +import { html, type HTMLTemplateResult, LitElement } from 'lit'; +import { customElement, state } from 'lit/decorators.js'; import verticalRadioGroupCss from './VerticalRadioGroup.css'; -import { StateReceiverMixin } from './StateReceiverMixin'; +import { stateReceiver } from './StateReceiverMixin'; import type { ChromelessPlayer, MediaTrack, MediaTrackList, Quality, VideoQuality } from 'theoplayer/chromeless'; -import { arrayFind, fromArrayLike } from '../util/CommonUtils'; -import { QualityRadioButton } from './QualityRadioButton'; +import { arrayFind } from '../util/CommonUtils'; import { createEvent } from '../util/EventUtils'; -import { createTemplate } from '../util/TemplateUtils'; - -const template = createTemplate( - 'theoplayer-quality-radio-group', - `` -); +import { repeat } from 'lit/directives/repeat.js'; const TRACK_EVENTS = ['addtrack', 'removetrack', 'change'] as const; @@ -21,49 +15,26 @@ const TRACK_EVENTS = ['addtrack', 'removetrack', 'change'] as const; * * @group Components */ -export class QualityRadioGroup extends StateReceiverMixin(HTMLElement, ['player']) { - private readonly _radioGroup: RadioGroup; +@customElement('theoplayer-quality-radio-group') +@stateReceiver(['player']) +export class QualityRadioGroup extends LitElement { + static override styles = [verticalRadioGroupCss]; + private _player: ChromelessPlayer | undefined; private _videoTracks: MediaTrackList | undefined; - private _track: MediaTrack | undefined; - - constructor() { - super(); - const shadowRoot = this.attachShadow({ mode: 'open' }); - shadowRoot.appendChild(template().content.cloneNode(true)); - - this._radioGroup = shadowRoot.querySelector('theoplayer-radio-group')!; - - this._upgradeProperty('player'); - } - protected _upgradeProperty(prop: keyof this) { - if (this.hasOwnProperty(prop)) { - let value = this[prop]; - delete this[prop]; - this[prop] = value; - } - } - - connectedCallback(): void { - shadyCss.styleElement(this); - - if (!(this._radioGroup instanceof RadioGroup)) { - customElements.upgrade(this._radioGroup); - } + @state() + private accessor _track: MediaTrack | undefined; + protected override firstUpdated(): void { this._updateTrack(); - this.shadowRoot!.addEventListener('change', this._onChange); - } - - disconnectedCallback(): void { - this.shadowRoot!.removeEventListener('change', this._onChange); } get player(): ChromelessPlayer | undefined { return this._player; } + @state() set player(player: ChromelessPlayer | undefined) { if (this._player === player) { return; @@ -90,36 +61,28 @@ export class QualityRadioGroup extends StateReceiverMixin(HTMLElement, ['player' this._update(); }; - private readonly _update = (): void => { - const buttons = fromArrayLike(this._radioGroup.children) as QualityRadioButton[]; - const availableQualities: VideoQuality[] = this._track ? (this._track.qualities as Quality[] as VideoQuality[]) : []; - // If there is only one available quality, *only* show the "Automatic" option (without the single quality). - // Otherwise, add an "Automatic" option at the front. - let qualities: (VideoQuality | undefined)[] = availableQualities.length === 1 ? [undefined] : [undefined, ...availableQualities]; - let i = 0; - // Add buttons for each quality - while (i < buttons.length && i < qualities.length) { - const button = buttons[i]; - button.track = this._track; - button.quality = qualities[i]; - i++; - } - while (i < buttons.length) { - this._radioGroup.removeChild(buttons[i]); - i++; - } - while (i < qualities.length) { - const button = new QualityRadioButton(); - button.track = this._track; - button.quality = qualities[i]; - this._radioGroup.appendChild(button); - i++; - } - }; + private _update = () => this.requestUpdate(); + + protected override render(): HTMLTemplateResult { + const qualities: VideoQuality[] = this._track ? (this._track.qualities as Quality[] as VideoQuality[]) : []; + return html` + + + ${ + /* If there is only one available quality, *only* show the "Automatic" option (without the single quality). */ + qualities.length !== 1 && + repeat( + qualities, + (quality) => quality.uid, + (quality) => + html`` + ) + } + + `; + } } -customElements.define('theoplayer-quality-radio-group', QualityRadioGroup); - declare global { interface HTMLElementTagNameMap { 'theoplayer-quality-radio-group': QualityRadioGroup; From e0386eefcd027817bf4393ce8905e15d6205dd0a Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Fri, 25 Apr 2025 13:49:06 +0200 Subject: [PATCH 050/123] Rework TextTrackStyleRadioGroup --- src/components/TextTrackStyleRadioGroup.ts | 99 ++++++++-------------- 1 file changed, 34 insertions(+), 65 deletions(-) diff --git a/src/components/TextTrackStyleRadioGroup.ts b/src/components/TextTrackStyleRadioGroup.ts index 14f19ba4..ee0e95fc 100644 --- a/src/components/TextTrackStyleRadioGroup.ts +++ b/src/components/TextTrackStyleRadioGroup.ts @@ -1,7 +1,9 @@ -import * as shadyCss from '@webcomponents/shadycss'; +import { html, type HTMLTemplateResult, LitElement } from 'lit'; +import { customElement, property } from 'lit/decorators.js'; +import { createRef, ref, type Ref } from 'lit/directives/ref.js'; import { RadioGroup } from './RadioGroup'; import verticalRadioGroupCss from './VerticalRadioGroup.css'; -import { StateReceiverMixin } from './StateReceiverMixin'; +import { stateReceiver } from './StateReceiverMixin'; import type { ChromelessPlayer, EdgeStyle, TextTrackStyle } from 'theoplayer/chromeless'; import type { RadioButton } from './RadioButton'; import { createEvent } from '../util/EventUtils'; @@ -37,68 +39,33 @@ export type TextTrackStyleOption = keyof TextTrackStyleMap; * For example: `Red` * @group Components */ -export class TextTrackStyleRadioGroup extends StateReceiverMixin(HTMLElement, ['player']) { - static get observedAttributes() { - return [Attribute.PROPERTY]; - } +@customElement('theoplayer-text-track-style-radio-group') +@stateReceiver(['player']) +export class TextTrackStyleRadioGroup extends LitElement { + static override styles = [verticalRadioGroupCss]; - private readonly _radioGroup: RadioGroup; - private readonly _optionsSlot: HTMLSlotElement; + private readonly _radioGroupRef: Ref = createRef(); private _player: ChromelessPlayer | undefined; private _textTrackStyle: TextTrackStyle | undefined; + private _property: TextTrackStyleOption = 'fontColor'; private _value: any; - constructor() { - super(); - const shadowRoot = this.attachShadow({ mode: 'open' }); - shadowRoot.appendChild(template().content.cloneNode(true)); - - this._radioGroup = shadowRoot.querySelector('theoplayer-radio-group')!; - this._optionsSlot = shadowRoot.querySelector('slot')!; - } - - connectedCallback(): void { - shadyCss.styleElement(this); - - if (!(this._radioGroup instanceof RadioGroup)) { - customElements.upgrade(this._radioGroup); - } - - this._upgradeProperty('property'); - this._upgradeProperty('value'); - this._upgradeProperty('player'); - - if (!this.hasAttribute(Attribute.PROPERTY)) { - this.property = 'fontColor'; - } - + protected override firstUpdated(): void { this._updateChecked(); - this.shadowRoot!.addEventListener('change', this._onChange); - this._optionsSlot.addEventListener('slotchange', this._updateChecked); - } - - disconnectedCallback(): void { - this.shadowRoot!.removeEventListener('change', this._onChange); - this._optionsSlot.removeEventListener('slotchange', this._updateChecked); - } - - protected _upgradeProperty(prop: keyof this) { - if (this.hasOwnProperty(prop)) { - let value = this[prop]; - delete this[prop]; - this[prop] = value; - } } /** * The property name of the text track style option. */ get property(): TextTrackStyleOption { - return this.getAttribute(Attribute.PROPERTY) as TextTrackStyleOption; + return this._property; } - set property(value: TextTrackStyleOption) { - this.setAttribute(Attribute.PROPERTY, value); + @property({ reflect: true, type: String, attribute: Attribute.PROPERTY }) + set property(property: TextTrackStyleOption) { + if (this._property === property) return; + this._property = property; + this._updateFromPlayer(); } /** @@ -108,6 +75,7 @@ export class TextTrackStyleRadioGroup extends StateReceiverMixin(HTMLElement, [' return this._value; } + @property({ reflect: false, attribute: false }) set value(value: string) { if (this._value === value) { return; @@ -122,6 +90,7 @@ export class TextTrackStyleRadioGroup extends StateReceiverMixin(HTMLElement, [' return this._player; } + @property({ reflect: false, attribute: false }) set player(player: ChromelessPlayer | undefined) { if (this._player === player) { return; @@ -133,16 +102,24 @@ export class TextTrackStyleRadioGroup extends StateReceiverMixin(HTMLElement, [' this._textTrackStyle?.addEventListener('change', this._updateFromPlayer); } + private get radioGroup_(): RadioGroup | undefined { + const radioGroup = this._radioGroupRef.value; + if (radioGroup && !(radioGroup instanceof RadioGroup)) { + customElements.upgrade(radioGroup); + } + return radioGroup; + } + private readonly _updateChecked = (): void => { - const buttons = this._radioGroup.allRadioButtons(); + const buttons = this.radioGroup_?.allRadioButtons() ?? []; for (const button of buttons) { button.checked = button.value === this.value; } }; private readonly _onChange = (): void => { - const button = this._radioGroup.checkedRadioButton as RadioButton | null; - if (button !== null && this.value !== button.value) { + const button = this.radioGroup_?.checkedRadioButton; + if (button && this.value !== button.value) { this.value = button.value; } }; @@ -225,16 +202,10 @@ export class TextTrackStyleRadioGroup extends StateReceiverMixin(HTMLElement, [' } } - attributeChangedCallback(attrName: string, oldValue: any, newValue: any) { - if (newValue === oldValue) { - return; - } - if (attrName === Attribute.PROPERTY) { - this._updateFromPlayer(); - } - if (TextTrackStyleRadioGroup.observedAttributes.indexOf(attrName as Attribute) >= 0) { - shadyCss.styleSubtree(this); - } + protected override render(): HTMLTemplateResult { + return html``; } } @@ -258,8 +229,6 @@ function updateOpacity(opacityValue: string, colorValue: string | undefined, def } } -customElements.define('theoplayer-text-track-style-radio-group', TextTrackStyleRadioGroup); - declare global { interface HTMLElementTagNameMap { 'theoplayer-text-track-style-radio-group': TextTrackStyleRadioGroup; From a341f95b8e9d36e77263a97c6ef3b3a2e9a4210a Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Fri, 25 Apr 2025 13:49:15 +0200 Subject: [PATCH 051/123] Rework TrackRadioGroup --- src/components/TrackRadioGroup.ts | 177 +++++++----------------------- 1 file changed, 41 insertions(+), 136 deletions(-) diff --git a/src/components/TrackRadioGroup.ts b/src/components/TrackRadioGroup.ts index 9aac2de8..a5b233c0 100644 --- a/src/components/TrackRadioGroup.ts +++ b/src/components/TrackRadioGroup.ts @@ -1,21 +1,12 @@ -import * as shadyCss from '@webcomponents/shadycss'; -import { RadioGroup } from './RadioGroup'; +import { html, type HTMLTemplateResult, LitElement } from 'lit'; +import { customElement, property, state } from 'lit/decorators.js'; import verticalRadioGroupCss from './VerticalRadioGroup.css'; -import { StateReceiverMixin } from './StateReceiverMixin'; +import { stateReceiver } from './StateReceiverMixin'; import type { ChromelessPlayer, MediaTrack, MediaTrackList, TextTrack, TextTracksList } from 'theoplayer/chromeless'; import { Attribute } from '../util/Attribute'; -import { MediaTrackRadioButton } from './MediaTrackRadioButton'; -import { TextTrackRadioButton } from './TextTrackRadioButton'; import { isNonForcedSubtitleTrack } from '../util/TrackUtils'; -import { TextTrackOffRadioButton } from './TextTrackOffRadioButton'; -import { fromArrayLike, toggleAttribute } from '../util/CommonUtils'; import { createEvent } from '../util/EventUtils'; -import { createTemplate } from '../util/TemplateUtils'; - -const template = createTemplate( - 'theoplayer-track-radio-group', - `` -); +import { repeat } from 'lit/directives/repeat.js'; const TRACK_EVENTS = ['addtrack', 'removetrack'] as const; @@ -30,62 +21,28 @@ export type TrackType = 'audio' | 'video' | 'subtitles'; * Can only be used with the "subtitles" track type. * @group Components */ -export class TrackRadioGroup extends StateReceiverMixin(HTMLElement, ['player']) { - static get observedAttributes() { - return [Attribute.TRACK_TYPE, Attribute.SHOW_OFF]; - } +@customElement('theoplayer-track-radio-group') +@stateReceiver(['player']) +export class TrackRadioGroup extends LitElement { + static override styles = [verticalRadioGroupCss]; - private readonly _radioGroup: RadioGroup; - private _offButton: TextTrackOffRadioButton | undefined; private _player: ChromelessPlayer | undefined; - private _tracksList: MediaTrackList | TextTracksList | undefined; - - constructor() { - super(); - const shadowRoot = this.attachShadow({ mode: 'open' }); - shadowRoot.appendChild(template().content.cloneNode(true)); - - this._radioGroup = shadowRoot.querySelector('theoplayer-radio-group')!; - - this._upgradeProperty('trackType'); - this._upgradeProperty('showOffButton'); - this._upgradeProperty('player'); - } + private _trackType: TrackType = 'audio'; - protected _upgradeProperty(prop: keyof this) { - if (this.hasOwnProperty(prop)) { - let value = this[prop]; - delete this[prop]; - this[prop] = value; - } - } - - connectedCallback(): void { - shadyCss.styleElement(this); - - if (!(this._radioGroup instanceof RadioGroup)) { - customElements.upgrade(this._radioGroup); - } - - this._updateOffButton(); - this._updateTracks(); - - this.shadowRoot!.addEventListener('change', this._onChange); - } - - disconnectedCallback(): void { - this.shadowRoot!.removeEventListener('change', this._onChange); - } + @state() + private accessor _tracksList: MediaTrackList | TextTracksList | undefined; /** * The track type of the available tracks. */ get trackType(): TrackType { - return (this.getAttribute(Attribute.TRACK_TYPE) || 'audio') as TrackType; + return this._trackType; } - set trackType(value: TrackType) { - this.setAttribute(Attribute.TRACK_TYPE, value || 'audio'); + @property({ reflect: true, type: String, attribute: Attribute.TRACK_TYPE }) + set trackType(trackType: TrackType) { + this._trackType = trackType; + this._updateTracksList(); } /** @@ -93,13 +50,8 @@ export class TrackRadioGroup extends StateReceiverMixin(HTMLElement, ['player']) * * Can only be used with the `"subtitles"` track type. */ - get showOffButton(): boolean { - return this.hasAttribute(Attribute.SHOW_OFF); - } - - set showOffButton(value: boolean) { - toggleAttribute(this, Attribute.SHOW_OFF, value); - } + @property({ reflect: true, type: Boolean, attribute: Attribute.SHOW_OFF }) + accessor showOffButton: boolean = false; get player(): ChromelessPlayer | undefined { return this._player; @@ -137,7 +89,6 @@ export class TrackRadioGroup extends StateReceiverMixin(HTMLElement, ['player']) oldList?.removeEventListener(TRACK_EVENTS, this._updateTracks); newList?.addEventListener(TRACK_EVENTS, this._updateTracks); this._tracksList = newList; - this._updateOffButton(); } } @@ -157,85 +108,39 @@ export class TrackRadioGroup extends StateReceiverMixin(HTMLElement, ['player']) } } - private readonly _updateTracks = (): void => { - let oldButtons = fromArrayLike(this._radioGroup.children) as (MediaTrackRadioButton | TextTrackRadioButton)[]; - const newTracks = this._getTracks(); - if (this._offButton !== undefined) { - // First child is the "off" button, skip it. - oldButtons = oldButtons.slice(1); - } - for (const oldButton of oldButtons) { - if (!oldButton.track || newTracks.indexOf(oldButton.track) < 0) { - this._radioGroup.removeChild(oldButton); - } - } - for (const newTrack of newTracks) { - if (!hasButtonForTrack(oldButtons, newTrack)) { - let newButton = this._createTrackButton(newTrack); - this._radioGroup.appendChild(newButton); - } - } - }; - - private _createTrackButton(track: MediaTrack | TextTrack): MediaTrackRadioButton | TextTrackRadioButton { - if (this.trackType === 'subtitles') { - let button = new TextTrackRadioButton(); - button.track = track as TextTrack; - return button; - } else { - let button = new MediaTrackRadioButton(); - button.track = track as MediaTrack; - return button; - } - } - - private _updateOffButton(): void { - if (this.trackType === 'subtitles' && this.showOffButton) { - if (this._offButton === undefined) { - this._offButton = new TextTrackOffRadioButton(); - this._radioGroup.insertBefore(this._offButton, this._radioGroup.firstChild); - } - this._offButton.trackList = this._tracksList! as TextTracksList; - } else if (this._offButton !== undefined) { - this._radioGroup.removeChild(this._offButton); - this._offButton = undefined; - } - } + private readonly _updateTracks = (): void => this.requestUpdate(); private readonly _onChange = () => { this.dispatchEvent(createEvent('change', { bubbles: true })); }; - attributeChangedCallback(attrName: string, oldValue: any, newValue: any) { - if (newValue === oldValue) { - return; - } - if (attrName === Attribute.TRACK_TYPE) { - this._updateTracksList(); - this._updateTracks(); - } else if (attrName === Attribute.SHOW_OFF) { - this._updateOffButton(); - this._updateTracks(); - } - if (TrackRadioGroup.observedAttributes.indexOf(attrName as Attribute) >= 0) { - shadyCss.styleSubtree(this); - } + protected override render(): HTMLTemplateResult { + const tracks = this._getTracks(); + const isSubtitles = this.trackType === 'subtitles'; + return html` + ${ + /* "Off" button */ + this.showOffButton && isSubtitles + ? html`` + : undefined + } + ${ + /* Track buttons */ + repeat( + tracks as ReadonlyArray, + (track) => track.uid, + (track) => + isSubtitles + ? html`` + : html`` + ) + } + `; } } -customElements.define('theoplayer-track-radio-group', TrackRadioGroup); - declare global { interface HTMLElementTagNameMap { 'theoplayer-track-radio-group': TrackRadioGroup; } } - -function hasButtonForTrack(buttons: readonly (MediaTrackRadioButton | TextTrackRadioButton)[], track: MediaTrack | TextTrack): boolean { - for (let i = 0; i < buttons.length; i++) { - if (buttons[i].track === track) { - return true; - } - } - return false; -} From 09e777e9a071417190054a0f811f534fcf6e59b7 Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Fri, 25 Apr 2025 13:53:14 +0200 Subject: [PATCH 052/123] Fix upgrading RadioGroup elements --- src/components/PlaybackRateRadioGroup.ts | 15 +++++---------- src/components/TextTrackStyleRadioGroup.ts | 15 +++++---------- .../theolive/quality/BadNetworkModeMenu.ts | 14 +++++++------- 3 files changed, 17 insertions(+), 27 deletions(-) diff --git a/src/components/PlaybackRateRadioGroup.ts b/src/components/PlaybackRateRadioGroup.ts index f5bf5774..ffa23251 100644 --- a/src/components/PlaybackRateRadioGroup.ts +++ b/src/components/PlaybackRateRadioGroup.ts @@ -27,6 +27,9 @@ export class PlaybackRateRadioGroup extends LitElement { private _value: number = 1; protected override firstUpdated(): void { + if (this._radioGroupRef.value && !(this._radioGroupRef.value instanceof RadioGroup)) { + customElements.upgrade(this._radioGroupRef.value); + } this._updateChecked(); } @@ -70,23 +73,15 @@ export class PlaybackRateRadioGroup extends LitElement { } } - private get radioGroup_(): RadioGroup | undefined { - const radioGroup = this._radioGroupRef.value; - if (radioGroup && !(radioGroup instanceof RadioGroup)) { - customElements.upgrade(radioGroup); - } - return radioGroup; - } - private readonly _updateChecked = (): void => { - const buttons = this.radioGroup_?.allRadioButtons() ?? []; + const buttons = this._radioGroupRef.value?.allRadioButtons() ?? []; for (const button of buttons) { button.checked = Number(button.value) === this.value; } }; private readonly _onChange = (): void => { - const button = this.radioGroup_?.checkedRadioButton; + const button = this._radioGroupRef.value?.checkedRadioButton; if (button && this.value !== Number(button.value)) { this.value = button.value; } diff --git a/src/components/TextTrackStyleRadioGroup.ts b/src/components/TextTrackStyleRadioGroup.ts index ee0e95fc..19a3a608 100644 --- a/src/components/TextTrackStyleRadioGroup.ts +++ b/src/components/TextTrackStyleRadioGroup.ts @@ -51,6 +51,9 @@ export class TextTrackStyleRadioGroup extends LitElement { private _value: any; protected override firstUpdated(): void { + if (this._radioGroupRef.value && !(this._radioGroupRef.value instanceof RadioGroup)) { + customElements.upgrade(this._radioGroupRef.value); + } this._updateChecked(); } @@ -102,23 +105,15 @@ export class TextTrackStyleRadioGroup extends LitElement { this._textTrackStyle?.addEventListener('change', this._updateFromPlayer); } - private get radioGroup_(): RadioGroup | undefined { - const radioGroup = this._radioGroupRef.value; - if (radioGroup && !(radioGroup instanceof RadioGroup)) { - customElements.upgrade(radioGroup); - } - return radioGroup; - } - private readonly _updateChecked = (): void => { - const buttons = this.radioGroup_?.allRadioButtons() ?? []; + const buttons = this._radioGroupRef.value?.allRadioButtons() ?? []; for (const button of buttons) { button.checked = button.value === this.value; } }; private readonly _onChange = (): void => { - const button = this.radioGroup_?.checkedRadioButton; + const button = this._radioGroupRef.value?.checkedRadioButton; if (button && this.value !== button.value) { this.value = button.value; } diff --git a/src/components/theolive/quality/BadNetworkModeMenu.ts b/src/components/theolive/quality/BadNetworkModeMenu.ts index 3e0ac577..57d09d8f 100644 --- a/src/components/theolive/quality/BadNetworkModeMenu.ts +++ b/src/components/theolive/quality/BadNetworkModeMenu.ts @@ -1,6 +1,6 @@ -import { html, type HTMLTemplateResult, LitElement, type PropertyValues } from 'lit'; +import { html, type HTMLTemplateResult, LitElement } from 'lit'; import { customElement } from 'lit/decorators.js'; -import { createRef, type Ref } from 'lit/directives/ref.js'; +import { createRef, ref, type Ref } from 'lit/directives/ref.js'; import verticalRadioGroupCss from '../../VerticalRadioGroup.css'; import './AutomaticQualitySelector'; import './BadNetworkModeSelector'; @@ -10,11 +10,11 @@ import { RadioGroup } from '../../RadioGroup'; export class BadNetworkModeMenu extends LitElement { static styles = [verticalRadioGroupCss]; - private readonly _radioGroup: Ref = createRef(); + private readonly _radioGroupRef: Ref = createRef(); - protected firstUpdated(_changedProperties: PropertyValues) { - if (this._radioGroup.value && !(this._radioGroup.value instanceof RadioGroup)) { - customElements.upgrade(this._radioGroup.value); + protected firstUpdated() { + if (this._radioGroupRef.value && !(this._radioGroupRef.value instanceof RadioGroup)) { + customElements.upgrade(this._radioGroupRef.value); } } @@ -23,7 +23,7 @@ export class BadNetworkModeMenu extends LitElement { }; protected override render(): HTMLTemplateResult { - return html` + return html` `; From 014663aae7be8decf52e1853a1bb24bc76bf328e Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Fri, 25 Apr 2025 13:54:20 +0200 Subject: [PATCH 053/123] Clean up --- src/components/RadioGroup.ts | 3 --- src/components/TextTrackStyleRadioGroup.ts | 6 ------ 2 files changed, 9 deletions(-) diff --git a/src/components/RadioGroup.ts b/src/components/RadioGroup.ts index 18489683..dd983c65 100644 --- a/src/components/RadioGroup.ts +++ b/src/components/RadioGroup.ts @@ -9,9 +9,6 @@ import { stateReceiver } from './StateReceiverMixin'; import { Attribute } from '../util/Attribute'; import type { DeviceType } from '../util/DeviceType'; import { navigateByArrowKey } from '../util/KeyboardNavigation'; -import { createTemplate } from '../util/TemplateUtils'; - -const template = createTemplate('theoplayer-radio-group', ``); /** * `` - A group of {@link RadioButton}s. At most one button in the group can be checked. diff --git a/src/components/TextTrackStyleRadioGroup.ts b/src/components/TextTrackStyleRadioGroup.ts index 19a3a608..40c240ad 100644 --- a/src/components/TextTrackStyleRadioGroup.ts +++ b/src/components/TextTrackStyleRadioGroup.ts @@ -9,12 +9,6 @@ import type { RadioButton } from './RadioButton'; import { createEvent } from '../util/EventUtils'; import { Attribute } from '../util/Attribute'; import { COLOR_BLACK, COLOR_WHITE, colorWithAlpha, parseColor, type RgbaColor, rgbEquals, toRgb, toRgba } from '../util/ColorUtils'; -import { createTemplate } from '../util/TemplateUtils'; - -const template = createTemplate( - 'theoplayer-text-track-style-radio-group', - `` -); export interface TextTrackStyleMap { fontFamily: string | undefined; From d802912a930f5a8d1c113941b4809a76cb535894 Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Fri, 25 Apr 2025 13:58:15 +0200 Subject: [PATCH 054/123] Rework AdCountdown --- src/components/ads/AdCountdown.ts | 55 +++++++++++-------------------- 1 file changed, 19 insertions(+), 36 deletions(-) diff --git a/src/components/ads/AdCountdown.ts b/src/components/ads/AdCountdown.ts index 607b7dc3..33f3108a 100644 --- a/src/components/ads/AdCountdown.ts +++ b/src/components/ads/AdCountdown.ts @@ -1,12 +1,9 @@ -import * as shadyCss from '@webcomponents/shadycss'; +import { html, type HTMLTemplateResult, LitElement } from 'lit'; +import { customElement, property, state } from 'lit/decorators.js'; import textDisplayCss from '../TextDisplay.css'; import adCountdownCss from './AdCountdown.css'; -import { StateReceiverMixin } from '../StateReceiverMixin'; +import { stateReceiver } from '../StateReceiverMixin'; import type { Ads, ChromelessPlayer } from 'theoplayer/chromeless'; -import { setTextContent } from '../../util/CommonUtils'; -import { createTemplate } from '../../util/TemplateUtils'; - -const template = createTemplate('theoplayer-ad-countdown', ``); const AD_EVENTS = ['adbreakbegin', 'adbreakend', 'adbreakchange', 'updateadbreak'] as const; @@ -15,37 +12,22 @@ const AD_EVENTS = ['adbreakbegin', 'adbreakend', 'adbreakchange', 'updateadbreak * * @group Components */ -export class AdCountdown extends StateReceiverMixin(HTMLElement, ['player']) { - private readonly _spanEl: HTMLElement; +@customElement('theoplayer-ad-countdown') +@stateReceiver(['player']) +export class AdCountdown extends LitElement { + static override styles = [textDisplayCss, adCountdownCss]; + private _player: ChromelessPlayer | undefined; private _ads: Ads | undefined; - constructor() { - super(); - const shadowRoot = this.attachShadow({ mode: 'open' }); - shadowRoot.appendChild(template().content.cloneNode(true)); - this._spanEl = shadowRoot.querySelector('span')!; - - this._upgradeProperty('player'); - } - - protected _upgradeProperty(prop: keyof this) { - if (this.hasOwnProperty(prop)) { - let value = this[prop]; - delete this[prop]; - this[prop] = value; - } - } - - connectedCallback(): void { - shadyCss.styleElement(this); - this._update(); - } + @state() + private accessor _maxRemainingDuration: number = 0; get player(): ChromelessPlayer | undefined { return this._player; } + @property({ reflect: false, attribute: false }) set player(player: ChromelessPlayer | undefined) { if (this._player === player) { return; @@ -72,17 +54,18 @@ export class AdCountdown extends StateReceiverMixin(HTMLElement, ['player']) { const ads = this._player?.ads; let maxRemainingDuration = ads?.currentAdBreak?.maxRemainingDuration; if (ads === undefined || !ads.playing || maxRemainingDuration === undefined || maxRemainingDuration < 0) { - setTextContent(this._spanEl, ''); + this._maxRemainingDuration = 0; this.style.display = 'none'; - return; + } else { + this._maxRemainingDuration = Math.ceil(maxRemainingDuration); + this.style.display = ''; } - maxRemainingDuration = Math.ceil(maxRemainingDuration); - setTextContent(this._spanEl, `Content will resume in ${maxRemainingDuration}s`); - this.style.display = ''; }; -} -customElements.define('theoplayer-ad-countdown', AdCountdown); + protected override render(): HTMLTemplateResult { + return html`Content will resume in ${this._maxRemainingDuration}s`; + } +} declare global { interface HTMLElementTagNameMap { From e369da72b1560e9944187e73bb315d3853e4f0f8 Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Fri, 25 Apr 2025 14:01:24 +0200 Subject: [PATCH 055/123] Rework ControlBar and SlotContainer --- src/components/ControlBar.ts | 25 +++++++++++-------------- src/components/SlotContainer.ts | 18 ++++++++---------- 2 files changed, 19 insertions(+), 24 deletions(-) diff --git a/src/components/ControlBar.ts b/src/components/ControlBar.ts index 3cb0c767..e530a08e 100644 --- a/src/components/ControlBar.ts +++ b/src/components/ControlBar.ts @@ -1,28 +1,25 @@ -import * as shadyCss from '@webcomponents/shadycss'; +import { html, type HTMLTemplateResult, LitElement } from 'lit'; +import { customElement } from 'lit/decorators.js'; import controlBarCss from './ControlBar.css'; -import { createTemplate } from '../util/TemplateUtils'; - -const template = createTemplate('theoplayer-control-bar', ``); /** * `` - A horizontal control bar that can contain other controls. * * @group Components */ -export class ControlBar extends HTMLElement { - constructor() { - super(); - const shadowRoot = this.attachShadow({ mode: 'open', delegatesFocus: true }); - shadowRoot.appendChild(template().content.cloneNode(true)); - } +@customElement('theoplayer-control-bar') +export class ControlBar extends LitElement { + static override styles = [controlBarCss]; + static override shadowRootOptions = { + ...LitElement.shadowRootOptions, + delegatesFocus: true + }; - connectedCallback(): void { - shadyCss.styleElement(this); + protected override render(): HTMLTemplateResult { + return html``; } } -customElements.define('theoplayer-control-bar', ControlBar); - declare global { interface HTMLElementTagNameMap { 'theoplayer-control-bar': ControlBar; diff --git a/src/components/SlotContainer.ts b/src/components/SlotContainer.ts index 97acb056..c99e7e24 100644 --- a/src/components/SlotContainer.ts +++ b/src/components/SlotContainer.ts @@ -1,8 +1,7 @@ -import { createTemplate } from '../util/TemplateUtils'; +import { html, type HTMLTemplateResult, LitElement } from 'lit'; +import { customElement } from 'lit/decorators.js'; import slotContainerCss from './SlotContainer.css'; -const template = createTemplate('theoplayer-slot-container', ``); - /** * `` - A container that can be assigned to a slot, * and behaves as if all its children are directly assigned to that slot. @@ -18,16 +17,15 @@ const template = createTemplate('theoplayer-slot-container', `` + - `
${errorIcon}
` + - `
` + - `

An error occurred

` + - `

` + - `
` + - `
` + - `` + - `
` -); /** * `` - A screen that shows the details of a fatal player error. * * @group Components */ -export class ErrorDisplay extends StateReceiverMixin(HTMLElement, ['error', 'fullscreen']) { - private readonly _messageSlot: HTMLSlotElement; - private _error: THEOplayerError | undefined; - - static get observedAttributes() { - return [Attribute.FULLSCREEN]; - } - - constructor() { - super(); - - const shadowRoot = this.attachShadow({ mode: 'open', delegatesFocus: true }); - shadowRoot.appendChild(template().content.cloneNode(true)); - - this._messageSlot = shadowRoot.querySelector('slot[name="message"]')!; - - this._upgradeProperty('error'); - } - - protected _upgradeProperty(prop: keyof this) { - if (this.hasOwnProperty(prop)) { - let value = this[prop]; - delete this[prop]; - this[prop] = value; - } - } - - connectedCallback(): void { - shadyCss.styleElement(this); - } +@customElement('theoplayer-error-display') +@stateReceiver(['error', 'fullscreen']) +export class ErrorDisplay extends LitElement { + static override styles = [errorDisplayCss]; + static override shadowRootOptions = { + ...LitElement.shadowRootOptions, + delegatesFocus: true + }; /** * The error. */ - get error(): THEOplayerError | undefined { - return this._error; - } - - set error(error: THEOplayerError | undefined) { - this._error = error; - setTextContent(this._messageSlot, error ? error.message : ''); - } - - get fullscreen(): boolean { - return this.hasAttribute(Attribute.FULLSCREEN); - } - - set fullscreen(fullscreen: boolean) { - toggleAttribute(this, Attribute.FULLSCREEN, fullscreen); - } - - attributeChangedCallback(attrName: string, oldValue: any, newValue: any) { - if (ErrorDisplay.observedAttributes.indexOf(attrName as Attribute) >= 0) { - shadyCss.styleSubtree(this); - } + @property({ reflect: false, attribute: false }) + accessor error: THEOplayerError | undefined; + + @property({ reflect: true, type: Boolean, attribute: Attribute.FULLSCREEN }) + accessor fullscreen: boolean = false; + + protected override render() { + return html`
${unsafeSVG(errorIcon)}
+
+

An error occurred

+

${this.error?.message ?? ''}

+
+
+ +
`; } } -customElements.define('theoplayer-error-display', ErrorDisplay); - declare global { interface HTMLElementTagNameMap { 'theoplayer-error-display': ErrorDisplay; From 9baed82392346f21fb7357ed33ff3575ab84cca4 Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Fri, 25 Apr 2025 14:24:21 +0200 Subject: [PATCH 057/123] Rework DurationDisplay and PlaybackRateDisplay --- src/components/DurationDisplay.ts | 54 +++++++++++---------------- src/components/PlaybackRateDisplay.ts | 51 +++++++------------------ 2 files changed, 35 insertions(+), 70 deletions(-) diff --git a/src/components/DurationDisplay.ts b/src/components/DurationDisplay.ts index 2954c66b..b8fc7bdc 100644 --- a/src/components/DurationDisplay.ts +++ b/src/components/DurationDisplay.ts @@ -1,13 +1,10 @@ -import * as shadyCss from '@webcomponents/shadycss'; +import { html, LitElement } from 'lit'; +import { customElement, property, state } from 'lit/decorators.js'; import textDisplayCss from './TextDisplay.css'; -import { StateReceiverMixin } from './StateReceiverMixin'; +import { stateReceiver } from './StateReceiverMixin'; import type { ChromelessPlayer } from 'theoplayer/chromeless'; -import { setTextContent } from '../util/CommonUtils'; import { formatTime } from '../util/TimeUtils'; import { Attribute } from '../util/Attribute'; -import { createTemplate } from '../util/TemplateUtils'; - -const template = createTemplate('theoplayer-duration-display', ``); const PLAYER_EVENTS = ['durationchange'] as const; @@ -16,42 +13,27 @@ const PLAYER_EVENTS = ['durationchange'] as const; * * @group Components */ -export class DurationDisplay extends StateReceiverMixin(HTMLElement, ['player']) { - private readonly _spanEl: HTMLElement; - private _player: ChromelessPlayer | undefined; +@customElement('theoplayer-duration-display') +@stateReceiver(['player']) +export class DurationDisplay extends LitElement { + static override styles = [textDisplayCss]; - constructor() { - super(); - const shadowRoot = this.attachShadow({ mode: 'open' }); - shadowRoot.appendChild(template().content.cloneNode(true)); - this._spanEl = shadowRoot.querySelector('span')!; - - this._upgradeProperty('player'); - } - - protected _upgradeProperty(prop: keyof this) { - if (this.hasOwnProperty(prop)) { - let value = this[prop]; - delete this[prop]; - this[prop] = value; - } - } + private _player: ChromelessPlayer | undefined; connectedCallback(): void { - shadyCss.styleElement(this); + super.connectedCallback(); if (!this.hasAttribute(Attribute.ARIA_LIVE)) { // Tell screen readers not to automatically read the duration as it changes this.setAttribute(Attribute.ARIA_LIVE, 'off'); } - - this._updateFromPlayer(); } get player(): ChromelessPlayer | undefined { return this._player; } + @property({ reflect: false, attribute: false }) set player(player: ChromelessPlayer | undefined) { if (this._player === player) { return; @@ -66,14 +48,20 @@ export class DurationDisplay extends StateReceiverMixin(HTMLElement, ['player']) } } + /** + * The current duration. + */ + @state() + accessor duration: number = NaN; + private readonly _updateFromPlayer = () => { - const duration = this._player ? this._player.duration : NaN; - const text = formatTime(duration); - setTextContent(this._spanEl, text); + this.duration = this._player ? this._player.duration : NaN; }; -} -customElements.define('theoplayer-duration-display', DurationDisplay); + protected override render() { + return html`${formatTime(this.duration)}`; + } +} declare global { interface HTMLElementTagNameMap { diff --git a/src/components/PlaybackRateDisplay.ts b/src/components/PlaybackRateDisplay.ts index 10d8b555..05e41386 100644 --- a/src/components/PlaybackRateDisplay.ts +++ b/src/components/PlaybackRateDisplay.ts @@ -1,49 +1,26 @@ -import { StateReceiverMixin } from './StateReceiverMixin'; -import { setTextContent } from '../util/CommonUtils'; -import * as shadyCss from '@webcomponents/shadycss'; +import { html, type HTMLTemplateResult, LitElement } from 'lit'; +import { stateReceiver } from './StateReceiverMixin'; +import { customElement, state } from 'lit/decorators.js'; /** * `` - A control that displays the current playback rate of the player. * * @group Components */ -export class PlaybackRateDisplay extends StateReceiverMixin(HTMLElement, ['playbackRate']) { - private readonly _spanEl: HTMLSpanElement; - private _playbackRate: number = 1; - - constructor() { - super(); - const shadowRoot = this.attachShadow({ mode: 'open' }); - this._spanEl = document.createElement('span'); - shadowRoot.appendChild(this._spanEl); - - this._upgradeProperty('playbackRate'); - } - - connectedCallback(): void { - shadyCss.styleElement(this); - } - - protected _upgradeProperty(prop: keyof this) { - if (this.hasOwnProperty(prop)) { - let value = this[prop]; - delete this[prop]; - this[prop] = value; - } - } - - get playbackRate(): number { - return this._playbackRate; - } - - set playbackRate(value: number) { - this._playbackRate = value; - setTextContent(this._spanEl, value === 1 ? 'Normal' : `${value}x`); +@customElement('theoplayer-playback-rate-display') +@stateReceiver(['playbackRate']) +export class PlaybackRateDisplay extends LitElement { + /** + * The current playback rate. + */ + @state() + accessor playbackRate: number = 1; + + protected override render(): HTMLTemplateResult { + return html`${this.playbackRate === 1 ? 'Normal' : `${this.playbackRate}x`}`; } } -customElements.define('theoplayer-playback-rate-display', PlaybackRateDisplay); - declare global { interface HTMLElementTagNameMap { 'theoplayer-playback-rate-display': PlaybackRateDisplay; From 6db0cbc16167c23072fe1300a9e925d4ec404f0c Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Fri, 25 Apr 2025 14:25:09 +0200 Subject: [PATCH 058/123] Rework TimeDisplay --- src/components/TimeDisplay.ts | 101 +++++++++++++++------------------- 1 file changed, 43 insertions(+), 58 deletions(-) diff --git a/src/components/TimeDisplay.ts b/src/components/TimeDisplay.ts index 7f2ec6db..741aa2ce 100644 --- a/src/components/TimeDisplay.ts +++ b/src/components/TimeDisplay.ts @@ -1,14 +1,11 @@ -import * as shadyCss from '@webcomponents/shadycss'; +import { html, type HTMLTemplateResult, LitElement } from 'lit'; +import { customElement, property, state } from 'lit/decorators.js'; import textDisplayCss from './TextDisplay.css'; -import { StateReceiverMixin } from './StateReceiverMixin'; +import { stateReceiver } from './StateReceiverMixin'; import type { ChromelessPlayer } from 'theoplayer/chromeless'; -import { setTextContent } from '../util/CommonUtils'; import { formatAsTimePhrase, formatTime } from '../util/TimeUtils'; import { Attribute } from '../util/Attribute'; import type { StreamType } from '../util/StreamType'; -import { createTemplate } from '../util/TemplateUtils'; - -const template = createTemplate('theoplayer-time-display', ``); const PLAYER_EVENTS = ['timeupdate', 'seeking', 'seeked', 'durationchange'] as const; @@ -23,34 +20,15 @@ const DEFAULT_MISSING_TIME_PHRASE = 'video not loaded, unknown time'; * (until the live point) of the stream. * @group Components */ -export class TimeDisplay extends StateReceiverMixin(HTMLElement, ['player', 'streamType']) { - private readonly _spanEl: HTMLElement; - private _player: ChromelessPlayer | undefined; - - static get observedAttributes() { - return [Attribute.REMAINING, Attribute.REMAINING_WHEN_LIVE, Attribute.SHOW_DURATION, Attribute.STREAM_TYPE]; - } - - constructor() { - super(); - - const shadowRoot = this.attachShadow({ mode: 'open' }); - shadowRoot.appendChild(template().content.cloneNode(true)); - this._spanEl = shadowRoot.querySelector('span')!; - - this._upgradeProperty('player'); - } +@customElement('theoplayer-time-display') +@stateReceiver(['player', 'streamType']) +export class TimeDisplay extends LitElement { + static override styles = [textDisplayCss]; - protected _upgradeProperty(prop: keyof this) { - if (this.hasOwnProperty(prop)) { - let value = this[prop]; - delete this[prop]; - this[prop] = value; - } - } + private _player: ChromelessPlayer | undefined; connectedCallback(): void { - shadyCss.styleElement(this); + super.connectedCallback(); if (!this.hasAttribute('role')) { this.setAttribute('role', 'progressbar'); @@ -62,14 +40,13 @@ export class TimeDisplay extends StateReceiverMixin(HTMLElement, ['player', 'str // Tell screen readers not to automatically read the time as it changes this.setAttribute(Attribute.ARIA_LIVE, 'off'); } - - this._updateFromPlayer(); } get player(): ChromelessPlayer | undefined { return this._player; } + @property({ reflect: false, attribute: false }) set player(player: ChromelessPlayer | undefined) { if (this._player === player) { return; @@ -83,57 +60,65 @@ export class TimeDisplay extends StateReceiverMixin(HTMLElement, ['player', 'str this._player.addEventListener(PLAYER_EVENTS, this._updateFromPlayer); } } + @property({ reflect: true, type: Boolean, attribute: Attribute.REMAINING }) + accessor remaining: boolean = false; - get streamType(): StreamType { - return (this.getAttribute(Attribute.STREAM_TYPE) || 'vod') as StreamType; - } + @property({ reflect: true, type: Boolean, attribute: Attribute.REMAINING_WHEN_LIVE }) + accessor remainingWhenLive: boolean = false; - set streamType(streamType: StreamType) { - this.setAttribute(Attribute.STREAM_TYPE, streamType); - } + @property({ reflect: true, type: Boolean, attribute: Attribute.SHOW_DURATION }) + accessor showDuration: boolean = false; + + @property({ reflect: true, type: String, attribute: Attribute.STREAM_TYPE }) + accessor streamType: StreamType = 'vod'; + + @state() + private accessor _currentTime: number = 0; + + @state() + private accessor _duration: number = NaN; + + @state() + private accessor _endTime: number = 0; private readonly _updateFromPlayer = () => { const currentTime = this._player ? this._player.currentTime : 0; const duration = this._player ? this._player.duration : NaN; const seekable = this._player?.seekable; const endTime = isFinite(duration) ? duration : seekable && seekable.length > 0 ? seekable.end(0) : NaN; - const remaining = this.hasAttribute(Attribute.REMAINING) || (this.hasAttribute(Attribute.REMAINING_WHEN_LIVE) && this.streamType !== 'vod'); - let time = currentTime; + this._currentTime = currentTime; + this._duration = duration; + this._endTime = endTime; + }; + + protected override render(): HTMLTemplateResult { + const remaining = this.remaining || (this.remainingWhenLive && this.streamType !== 'vod'); + let time = this._currentTime; + const endTime = this._endTime; if (remaining) { - time = -((endTime || 0) - currentTime); + time = -((this._endTime || 0) - time); } - const showDuration = this.hasAttribute(Attribute.SHOW_DURATION); let text: string; - if (showDuration && !remaining) { + if (this.showDuration && !remaining) { text = `${formatTime(time, endTime, remaining)} / ${formatTime(endTime)}`; } else { text = formatTime(time, endTime, remaining); } + let ariaValueText: string; - if (isNaN(duration)) { + if (isNaN(this._duration)) { ariaValueText = DEFAULT_MISSING_TIME_PHRASE; - } else if (showDuration) { + } else if (this.showDuration) { ariaValueText = `${formatAsTimePhrase(time, remaining)} of ${formatAsTimePhrase(endTime)}`; } else { ariaValueText = formatAsTimePhrase(time, remaining); } - setTextContent(this._spanEl, text); this.setAttribute('aria-valuetext', ariaValueText); - }; - attributeChangedCallback(attrName: string, oldValue: any, newValue: any) { - if (newValue === oldValue) { - return; - } - if (TimeDisplay.observedAttributes.indexOf(attrName as Attribute) >= 0) { - this._updateFromPlayer(); - shadyCss.styleSubtree(this); - } + return html`${text}`; } } -customElements.define('theoplayer-time-display', TimeDisplay); - declare global { interface HTMLElementTagNameMap { 'theoplayer-time-display': TimeDisplay; From dce7b6e3e26b8f7c7af00bc7efcd1b0ca959b5f9 Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Fri, 25 Apr 2025 14:30:22 +0200 Subject: [PATCH 059/123] Rework GestureReceiver --- src/components/GestureReceiver.ts | 43 +++++++++++++------------------ 1 file changed, 18 insertions(+), 25 deletions(-) diff --git a/src/components/GestureReceiver.ts b/src/components/GestureReceiver.ts index cf0928fc..e0995614 100644 --- a/src/components/GestureReceiver.ts +++ b/src/components/GestureReceiver.ts @@ -1,10 +1,9 @@ -import * as shadyCss from '@webcomponents/shadycss'; +import { html, type HTMLTemplateResult, LitElement } from 'lit'; +import { customElement, property } from 'lit/decorators.js'; import gestureReceiverCss from './GestureReceiver.css'; -import { StateReceiverMixin } from './StateReceiverMixin'; +import { stateReceiver } from './StateReceiverMixin'; import type { ChromelessPlayer } from 'theoplayer/chromeless'; -import { createTemplate } from '../util/TemplateUtils'; - -const template = createTemplate('theoplayer-gesture-receiver', ``); +import type { DeviceType } from '../util/DeviceType'; /** * `` - An overlay that receives and handles gestures on the player. @@ -14,29 +13,17 @@ const template = createTemplate('theoplayer-gesture-receiver', `${loadingIndicatorHtml}`); +import { customElement, property } from 'lit/decorators.js'; +import { type HTMLTemplateResult, LitElement } from 'lit'; const PLAYER_EVENTS = ['readystatechange', 'play', 'pause', 'playing', 'seeking', 'seeked'] as const; @@ -17,39 +14,18 @@ const PLAYER_EVENTS = ['readystatechange', 'play', 'pause', 'playing', 'seeking' * @attribute `loading` (readonly) - Whether the player is waiting for more data. If set, the indicator is shown. * @group Components */ -export class LoadingIndicator extends StateReceiverMixin(HTMLElement, ['player']) { - private _player: ChromelessPlayer | undefined; - - static get observedAttributes() { - return [Attribute.LOADING]; - } - - constructor() { - super(); +@customElement('theoplayer-loading-indicator') +@stateReceiver(['player']) +export class LoadingIndicator extends LitElement { + static override styles = [loadingIndicatorCss]; - const shadowRoot = this.attachShadow({ mode: 'open' }); - shadowRoot.appendChild(template().content.cloneNode(true)); - - this._upgradeProperty('player'); - } - - protected _upgradeProperty(prop: keyof this) { - if (this.hasOwnProperty(prop)) { - let value = this[prop]; - delete this[prop]; - this[prop] = value; - } - } - - connectedCallback(): void { - shadyCss.styleElement(this); - this._updateFromPlayer(); - } + private _player: ChromelessPlayer | undefined; get player(): ChromelessPlayer | undefined { return this._player; } + @property({ reflect: false, attribute: false }) set player(player: ChromelessPlayer | undefined) { if (this._player === player) { return; @@ -64,20 +40,18 @@ export class LoadingIndicator extends StateReceiverMixin(HTMLElement, ['player'] } } + @property({ reflect: true, type: Boolean, attribute: Attribute.LOADING }) + accessor loading: boolean = false; + private readonly _updateFromPlayer = () => { - const loading = this._player !== undefined && !this._player.paused && (this._player.seeking || this._player.readyState < 3); - toggleAttribute(this, Attribute.LOADING, loading); + this.loading = this._player !== undefined && !this._player.paused && (this._player.seeking || this._player.readyState < 3); }; - attributeChangedCallback(attrName: string, oldValue: any, newValue: any) { - if (LoadingIndicator.observedAttributes.indexOf(attrName as Attribute) >= 0) { - shadyCss.styleSubtree(this); - } + protected override render(): HTMLTemplateResult { + return loadingIndicatorHtml; } } -customElements.define('theoplayer-loading-indicator', LoadingIndicator); - declare global { interface HTMLElementTagNameMap { 'theoplayer-loading-indicator': LoadingIndicator; From 35543f2b11d964acaeed1a71265a545ad4dd1933 Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Fri, 25 Apr 2025 14:41:41 +0200 Subject: [PATCH 061/123] Rework TextTrackStyleDisplay --- src/components/TextTrackStyleDisplay.ts | 97 ++++++++----------------- 1 file changed, 29 insertions(+), 68 deletions(-) diff --git a/src/components/TextTrackStyleDisplay.ts b/src/components/TextTrackStyleDisplay.ts index 3405a71f..4c0104d8 100644 --- a/src/components/TextTrackStyleDisplay.ts +++ b/src/components/TextTrackStyleDisplay.ts @@ -1,10 +1,11 @@ -import * as shadyCss from '@webcomponents/shadycss'; -import { StateReceiverMixin } from './StateReceiverMixin'; +import { html, type HTMLTemplateResult, LitElement } from 'lit'; +import { customElement, property } from 'lit/decorators.js'; +import { stateReceiver } from './StateReceiverMixin'; import type { ChromelessPlayer, EdgeStyle, TextTrackStyle } from 'theoplayer/chromeless'; import { Attribute } from '../util/Attribute'; import { parseColor, toRgb } from '../util/ColorUtils'; import type { TextTrackStyleOption } from './TextTrackStyleRadioGroup'; -import { arrayFind, setTextContent } from '../util/CommonUtils'; +import { arrayFind } from '../util/CommonUtils'; import { knownColors, knownEdgeStyles, knownFontFamilies } from '../util/TextTrackStylePresets'; /** @@ -14,56 +15,31 @@ import { knownColors, knownEdgeStyles, knownFontFamilies } from '../util/TextTra * @attribute `property` - The property name of the text track style option. One of {@link TextTrackStyleOption}. * @group Components */ -export class TextTrackStyleDisplay extends StateReceiverMixin(HTMLElement, ['player']) { - static get observedAttributes() { - return [Attribute.PROPERTY]; - } - - private readonly _spanEl: HTMLSpanElement; +@customElement('theoplayer-text-track-style-display') +@stateReceiver(['player']) +export class TextTrackStyleDisplay extends LitElement { private _player: ChromelessPlayer | undefined; + private _property: TextTrackStyleOption = 'fontColor'; private _textTrackStyle: TextTrackStyle | undefined; - constructor() { - super(); - const shadowRoot = this.attachShadow({ mode: 'open' }); - this._spanEl = document.createElement('span'); - shadowRoot.appendChild(this._spanEl); - - this._upgradeProperty('property'); - this._upgradeProperty('player'); - } - - connectedCallback(): void { - shadyCss.styleElement(this); - - if (!this.hasAttribute(Attribute.PROPERTY)) { - this.property = 'fontColor'; - } - } - - protected _upgradeProperty(prop: keyof this) { - if (this.hasOwnProperty(prop)) { - let value = this[prop]; - delete this[prop]; - this[prop] = value; - } - } - /** * The property name of the text track style option. */ get property(): TextTrackStyleOption { - return this.getAttribute(Attribute.PROPERTY) as TextTrackStyleOption; + return this._property; } - set property(value: TextTrackStyleOption) { - this.setAttribute(Attribute.PROPERTY, value); + @property({ reflect: true, type: String, attribute: Attribute.PROPERTY }) + set property(property: TextTrackStyleOption) { + this._property = property; + this._updateFromPlayer(); } get player(): ChromelessPlayer | undefined { return this._player; } + @property({ reflect: false, attribute: false }) set player(player: ChromelessPlayer | undefined) { if (this._player === player) { return; @@ -75,61 +51,46 @@ export class TextTrackStyleDisplay extends StateReceiverMixin(HTMLElement, ['pla this._textTrackStyle?.addEventListener('change', this._updateFromPlayer); } - private readonly _updateFromPlayer = (): void => { + private readonly _updateFromPlayer = () => this.requestUpdate(); + + protected override render(): HTMLTemplateResult { + return html`${this.renderValue()}`; + } + + private renderValue(): string { if (this._player === undefined) { - return; + return ''; } const property = this.property; switch (property) { case 'fontFamily': { - setTextContent(this._spanEl, getFontFamilyLabel(this._player.textTrackStyle.fontFamily)); - break; + return getFontFamilyLabel(this._player.textTrackStyle.fontFamily); } case 'fontColor': case 'backgroundColor': case 'windowColor': { - setTextContent(this._spanEl, getColorLabel(this._player.textTrackStyle[property])); - break; + return getColorLabel(this._player.textTrackStyle[property]); } case 'fontOpacity': { - setTextContent(this._spanEl, getOpacityLabel(this._player.textTrackStyle.fontColor)); - break; + return getOpacityLabel(this._player.textTrackStyle.fontColor); } case 'backgroundOpacity': { - setTextContent(this._spanEl, getOpacityLabel(this._player.textTrackStyle.backgroundColor)); - break; + return getOpacityLabel(this._player.textTrackStyle.backgroundColor); } case 'windowOpacity': { - setTextContent(this._spanEl, getOpacityLabel(this._player.textTrackStyle.windowColor)); - break; + return getOpacityLabel(this._player.textTrackStyle.windowColor); } case 'edgeStyle': { - setTextContent(this._spanEl, getEdgeStyleLabel(this._player.textTrackStyle.edgeStyle)); - break; + return getEdgeStyleLabel(this._player.textTrackStyle.edgeStyle); } default: { const value = this._player.textTrackStyle[property]; - setTextContent(this._spanEl, value ? value : 'Default'); - break; + return value ? value : 'Default'; } } - }; - - attributeChangedCallback(attrName: string, oldValue: any, newValue: any) { - if (newValue === oldValue) { - return; - } - if (attrName === Attribute.PROPERTY) { - this._updateFromPlayer(); - } - if (TextTrackStyleDisplay.observedAttributes.indexOf(attrName as Attribute) >= 0) { - shadyCss.styleSubtree(this); - } } } -customElements.define('theoplayer-text-track-style-display', TextTrackStyleDisplay); - declare global { interface HTMLElementTagNameMap { 'theoplayer-text-track-style-display': TextTrackStyleDisplay; From f4d36117fffae7be105e52229643483f7d42e6ec Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Fri, 25 Apr 2025 14:58:20 +0200 Subject: [PATCH 062/123] Rework PreviewThumbnail and PreviewTimeDisplay --- src/components/PreviewThumbnail.ts | 68 +++++++++--------- src/components/PreviewTimeDisplay.ts | 100 +++++++++------------------ 2 files changed, 66 insertions(+), 102 deletions(-) diff --git a/src/components/PreviewThumbnail.ts b/src/components/PreviewThumbnail.ts index 5c8a479b..9b45fcdd 100644 --- a/src/components/PreviewThumbnail.ts +++ b/src/components/PreviewThumbnail.ts @@ -1,11 +1,11 @@ -import * as shadyCss from '@webcomponents/shadycss'; +import { html, type HTMLTemplateResult, LitElement } from 'lit'; +import { customElement, property, state } from 'lit/decorators.js'; +import { createRef, ref, type Ref } from 'lit/directives/ref.js'; +import { styleMap } from 'lit/directives/style-map.js'; import previewThumbnailCss from './PreviewThumbnail.css'; -import { StateReceiverMixin } from './StateReceiverMixin'; +import { stateReceiver } from './StateReceiverMixin'; import type { ChromelessPlayer, TextTrack, TextTrackCue, TextTrackCueList, TextTracksList } from 'theoplayer/chromeless'; import { arrayFind, noOp } from '../util/CommonUtils'; -import { createTemplate } from '../util/TemplateUtils'; - -const template = createTemplate('theoplayer-preview-thumbnail', ``); const TRACK_EVENTS = ['addtrack', 'removetrack'] as const; @@ -21,9 +21,13 @@ const TRACK_EVENTS = ['addtrack', 'removetrack'] as const; * If the stream does not contain thumbnails, then this display shows nothing. * @group Components */ -export class PreviewThumbnail extends StateReceiverMixin(HTMLElement, ['player', 'previewTime']) { - private readonly _canvasEl: HTMLCanvasElement; - private readonly _canvasContext: CanvasRenderingContext2D; +@customElement('theoplayer-preview-thumbnail') +@stateReceiver(['player', 'previewTime']) +export class PreviewThumbnail extends LitElement { + static override styles = [previewThumbnailCss]; + + private readonly _canvasRef: Ref = createRef(); + private _canvasContext: CanvasRenderingContext2D | undefined; private readonly _thumbnailImageSource: HTMLImageElement; private _player: ChromelessPlayer | undefined; @@ -32,37 +36,21 @@ export class PreviewThumbnail extends StateReceiverMixin(HTMLElement, ['player', private _thumbnailTextTrack: TextTrack | undefined; private _lastLoadedThumbnailUrl: string | undefined; + @state() + private accessor _thumbnailVisible: boolean = false; + constructor() { super(); - const shadowRoot = this.attachShadow({ mode: 'open' }); - shadowRoot.appendChild(template().content.cloneNode(true)); - - this._canvasEl = shadowRoot.querySelector('canvas')!; - this._canvasContext = this._canvasEl.getContext('2d')!; this._thumbnailImageSource = document.createElement('img'); this._thumbnailImageSource.decoding = 'async'; - - this._upgradeProperty('previewTime'); - this._upgradeProperty('player'); - } - - protected _upgradeProperty(prop: keyof this) { - if (this.hasOwnProperty(prop)) { - let value = this[prop]; - delete this[prop]; - this[prop] = value; - } - } - - connectedCallback(): void { - shadyCss.styleElement(this); } get player(): ChromelessPlayer | undefined { return this._player; } + @property({ reflect: false, attribute: false }) set player(player: ChromelessPlayer | undefined) { if (this._player === player) { return; @@ -78,6 +66,7 @@ export class PreviewThumbnail extends StateReceiverMixin(HTMLElement, ['player', return this._previewTime; } + @state() set previewTime(previewTime: number) { this._previewTime = previewTime; this.updateThumbnail_(); @@ -143,26 +132,33 @@ export class PreviewThumbnail extends StateReceiverMixin(HTMLElement, ['player', } private showThumbnail_(sprite: Sprite | undefined): void { + const canvas = this._canvasRef.value; + if (!canvas) return; + this._canvasContext ??= canvas.getContext('2d') ?? undefined; + if (!this._canvasContext) return; if (sprite) { // Draw part of image - this._canvasEl.width = sprite.w; - this._canvasEl.height = sprite.h; + canvas.width = sprite.w; + canvas.height = sprite.h; this._canvasContext.drawImage(this._thumbnailImageSource, sprite.x, sprite.y, sprite.w, sprite.h, 0, 0, sprite.w, sprite.h); } else { // Draw entire image - this._canvasEl.width = this._thumbnailImageSource.naturalWidth; - this._canvasEl.height = this._thumbnailImageSource.naturalHeight; + canvas.width = this._thumbnailImageSource.naturalWidth; + canvas.height = this._thumbnailImageSource.naturalHeight; this._canvasContext.drawImage(this._thumbnailImageSource, 0, 0); } - this._canvasEl.style.display = 'block'; + this._thumbnailVisible = true; } private hideThumbnail_(): void { - this._canvasEl.style.display = 'none'; + this._thumbnailVisible = false; } -} -customElements.define('theoplayer-preview-thumbnail', PreviewThumbnail); + protected override render(): HTMLTemplateResult { + const canvasStyle = { display: this._thumbnailVisible ? 'block' : 'none' }; + return html``; + } +} declare global { interface HTMLElementTagNameMap { diff --git a/src/components/PreviewTimeDisplay.ts b/src/components/PreviewTimeDisplay.ts index e24fa789..fcc6bed6 100644 --- a/src/components/PreviewTimeDisplay.ts +++ b/src/components/PreviewTimeDisplay.ts @@ -1,14 +1,11 @@ -import * as shadyCss from '@webcomponents/shadycss'; +import { html, type HTMLTemplateResult, LitElement } from 'lit'; +import { customElement, property, state } from 'lit/decorators.js'; import textDisplayCss from './TextDisplay.css'; -import { StateReceiverMixin } from './StateReceiverMixin'; -import { setTextContent } from '../util/CommonUtils'; +import { stateReceiver } from './StateReceiverMixin'; import { formatTime } from '../util/TimeUtils'; import { Attribute } from '../util/Attribute'; import type { ChromelessPlayer } from 'theoplayer/chromeless'; import type { StreamType } from '../util/StreamType'; -import { createTemplate } from '../util/TemplateUtils'; - -const template = createTemplate('theoplayer-preview-time-display', ``); const PLAYER_EVENTS = ['timeupdate', 'seeking', 'seeked', 'durationchange'] as const; @@ -20,98 +17,69 @@ const PLAYER_EVENTS = ['timeupdate', 'seeking', 'seeked', 'durationchange'] as c * (until the live point) of the stream. * @group Components */ -export class PreviewTimeDisplay extends StateReceiverMixin(HTMLElement, ['player', 'previewTime', 'streamType']) { - private readonly _spanEl: HTMLElement; - private _previewTime: number = NaN; - private _player: ChromelessPlayer | undefined; - - static get observedAttributes() { - return [Attribute.REMAINING, Attribute.REMAINING_WHEN_LIVE, Attribute.STREAM_TYPE]; - } - - constructor() { - super(); - const shadowRoot = this.attachShadow({ mode: 'open' }); - shadowRoot.appendChild(template().content.cloneNode(true)); - this._spanEl = shadowRoot.querySelector('span')!; - - this._upgradeProperty('previewTime'); - this._upgradeProperty('player'); - } +@customElement('theoplayer-preview-time-display') +@stateReceiver(['player', 'previewTime', 'streamType']) +export class PreviewTimeDisplay extends LitElement { + static override styles = [textDisplayCss]; - protected _upgradeProperty(prop: keyof this) { - if (this.hasOwnProperty(prop)) { - let value = this[prop]; - delete this[prop]; - this[prop] = value; - } - } + private _player: ChromelessPlayer | undefined; connectedCallback(): void { - shadyCss.styleElement(this); - this._update(); + super.connectedCallback(); + this._updateFromPlayer(); } - get previewTime(): number { - return this._previewTime; - } + @state() + accessor previewTime: number = NaN; - set previewTime(previewTime: number) { - this._previewTime = previewTime; - this._update(); - } + @property({ reflect: true, type: Boolean, attribute: Attribute.REMAINING }) + accessor remaining: boolean = false; - get streamType(): StreamType { - return (this.getAttribute(Attribute.STREAM_TYPE) || 'vod') as StreamType; - } + @property({ reflect: true, type: Boolean, attribute: Attribute.REMAINING_WHEN_LIVE }) + accessor remainingWhenLive: boolean = false; - set streamType(streamType: StreamType) { - this.setAttribute(Attribute.STREAM_TYPE, streamType); - } + @property({ reflect: true, type: String, attribute: Attribute.STREAM_TYPE }) + accessor streamType: StreamType = 'vod'; get player(): ChromelessPlayer | undefined { return this._player; } + @property({ reflect: false, attribute: false }) set player(player: ChromelessPlayer | undefined) { if (this._player === player) { return; } if (this._player !== undefined) { - this._player.removeEventListener(PLAYER_EVENTS, this._update); + this._player.removeEventListener(PLAYER_EVENTS, this._updateFromPlayer); } this._player = player; - this._update(); + this._updateFromPlayer(); if (this._player !== undefined) { - this._player.addEventListener(PLAYER_EVENTS, this._update); + this._player.addEventListener(PLAYER_EVENTS, this._updateFromPlayer); } } - private readonly _update = (): void => { - let previewTime = this._previewTime; + @state() + private accessor _endTime: number = 0; + + private readonly _updateFromPlayer = (): void => { const duration = this._player ? this._player.duration : NaN; const seekable = this._player?.seekable; - const endTime = isFinite(duration) ? duration : seekable && seekable.length > 0 ? seekable.end(0) : NaN; - const remaining = this.hasAttribute(Attribute.REMAINING) || (this.hasAttribute(Attribute.REMAINING_WHEN_LIVE) && this.streamType !== 'vod'); - if (remaining) { - previewTime = -((endTime || 0) - previewTime); - } - setTextContent(this._spanEl, formatTime(previewTime, endTime, remaining)); + this._endTime = isFinite(duration) ? duration : seekable && seekable.length > 0 ? seekable.end(0) : NaN; }; - attributeChangedCallback(attrName: string, oldValue: any, newValue: any) { - if (newValue === oldValue) { - return; - } - if (PreviewTimeDisplay.observedAttributes.indexOf(attrName as Attribute) >= 0) { - this._update(); - shadyCss.styleSubtree(this); + protected override render(): HTMLTemplateResult { + let previewTime = this.previewTime; + const endTime = this._endTime; + const remaining = this.remaining || (this.remainingWhenLive && this.streamType !== 'vod'); + if (remaining) { + previewTime = -((endTime || 0) - previewTime); } + return html`${formatTime(previewTime, endTime, remaining)}`; } } -customElements.define('theoplayer-preview-time-display', PreviewTimeDisplay); - declare global { interface HTMLElementTagNameMap { 'theoplayer-preview-time-display': PreviewTimeDisplay; From 3afabb2c15a45227f282a359905434649b4c4752 Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Fri, 25 Apr 2025 16:30:04 +0200 Subject: [PATCH 063/123] Rework TimeRange and VolumeRange --- src/components/Range.ts | 219 ++++++++++++++++++---------------- src/components/TimeRange.html | 9 -- src/components/TimeRange.ts | 137 +++++++++++---------- src/components/VolumeRange.ts | 20 ++-- 4 files changed, 202 insertions(+), 183 deletions(-) delete mode 100644 src/components/TimeRange.html diff --git a/src/components/Range.ts b/src/components/Range.ts index c9dded1f..a1043fc7 100644 --- a/src/components/Range.ts +++ b/src/components/Range.ts @@ -2,18 +2,13 @@ import * as shadyCss from '@webcomponents/shadycss'; import rangeCss from './Range.css'; import { Attribute } from '../util/Attribute'; import { ColorStops } from '../util/ColorStops'; -import { toggleAttribute } from '../util/CommonUtils'; -import { StateReceiverMixin } from './StateReceiverMixin'; +import { stateReceiver } from './StateReceiverMixin'; import type { DeviceType } from '../util/DeviceType'; import { isArrowKey, KeyCode } from '../util/KeyCode'; - -export interface RangeOptions { - template: HTMLTemplateElement; -} - -export function rangeTemplate(range: string, extraCss: string = ''): string { - return `
${range}
`; -} +import { html, type HTMLTemplateResult, LitElement } from 'lit'; +import { createRef, ref, type Ref } from 'lit/directives/ref.js'; +import { property, state } from 'lit/decorators.js'; +import { styleMap } from 'lit/directives/style-map.js'; /** * A slider to select a value from a range. @@ -25,52 +20,29 @@ export function rangeTemplate(range: string, extraCss: string = ''): string { * * @group Components */ -export abstract class Range extends StateReceiverMixin(HTMLElement, ['deviceType']) { - static get observedAttributes() { - return [Attribute.DISABLED, Attribute.HIDDEN, Attribute.INERT]; - } +@stateReceiver(['deviceType']) +export abstract class Range extends LitElement { + static override styles = [rangeCss]; + + protected readonly _rangeRef: Ref = createRef(); + + private _min: number = 0; + private _max: number = 100; + private _value: number = 0; + private _disabled: boolean = false; + private _hidden: boolean = false; + private _inert: boolean = false; - protected readonly _rangeEl: HTMLInputElement; - protected readonly _pointerEl: HTMLElement; private _rangeWidth: number = 0; private _thumbWidth: number = 10; - constructor(options: RangeOptions) { - super(); - const shadowRoot = this.attachShadow({ mode: 'open' }); - shadowRoot.appendChild(options.template.content.cloneNode(true)); - - this._rangeEl = shadowRoot.querySelector('input[type="range"]')!; - this._rangeEl.addEventListener('input', this._onInput); - // Internet Explorer does not fire 'input' events for elements... use 'change' instead. - this._rangeEl.addEventListener('change', this._onInput); - this._rangeEl.addEventListener('keydown', this._onKeyDown); - - this._pointerEl = shadowRoot.querySelector('[part="pointer"]')!; - - this._upgradeProperty('disabled'); - this._upgradeProperty('inert'); - this._upgradeProperty('value'); - this._upgradeProperty('min'); - this._upgradeProperty('max'); - this._upgradeProperty('step'); - this._upgradeProperty('deviceType'); - } - - protected _upgradeProperty(prop: keyof this) { - if (this.hasOwnProperty(prop)) { - let value = this[prop]; - delete this[prop]; - this[prop] = value; - } - } + @state() + private accessor _pointerWidth: number = 0; connectedCallback(): void { - shadyCss.styleElement(this); - - this._rangeEl.setAttribute(Attribute.ARIA_LABEL, this.getAriaLabel()); - this.update(); + super.connectedCallback(); + this._updateRange(); this.addEventListener('pointermove', this._updatePointerBar); } @@ -83,12 +55,29 @@ export abstract class Range extends StateReceiverMixin(HTMLElement, ['deviceType * * When disabled, the slider value cannot be changed, and the slider thumb is hidden. */ - get disabled() { - return this.hasAttribute(Attribute.DISABLED); + get disabled(): boolean { + return this._disabled; } + @property({ reflect: true, type: Boolean, attribute: Attribute.DISABLED }) set disabled(disabled: boolean) { - toggleAttribute(this, Attribute.DISABLED, disabled); + if (this._disabled === disabled) return; + this._disabled = disabled; + this.setAttribute('aria-disabled', this._disabled ? 'true' : 'false'); + } + + /** + * Whether the range is hidden. + */ + get hidden(): boolean { + return this._hidden; + } + + @property({ reflect: true, type: Boolean, attribute: Attribute.HIDDEN }) + set hidden(hidden: boolean) { + if (this._hidden === hidden) return; + this._hidden = hidden; + this._updateRange(); } /** @@ -97,22 +86,32 @@ export abstract class Range extends StateReceiverMixin(HTMLElement, ['deviceType * When inert, the slider value cannot be changed, but the slider thumb is still visible. */ get inert() { - return this.hasAttribute(Attribute.INERT); + return this._inert; } + @property({ reflect: true, type: Boolean, attribute: Attribute.INERT }) set inert(inert: boolean) { - toggleAttribute(this, Attribute.INERT, inert); + this._inert = inert; } /** * The current value. */ get value(): number { - return this._rangeEl.valueAsNumber; + return this._value; } + @property({ reflect: false, attribute: false }) set value(value: number) { - this._rangeEl.valueAsNumber = value; + let newValue = value; + if (!isNaN(this.min)) { + newValue = Math.max(this.min, newValue); + } + if (!isNaN(this.max)) { + newValue = Math.min(this.max, newValue); + } + if (this._value === newValue) return; + this._value = newValue; this.handleInput(); } @@ -120,24 +119,26 @@ export abstract class Range extends StateReceiverMixin(HTMLElement, ['deviceType * The minimum allowed value. */ get min(): number { - return Number(this._rangeEl.min); + return this._min; } + @property({ reflect: true, type: Number, attribute: 'min' }) set min(min: number) { - this._rangeEl.min = String(min); - this.update(); + this._min = min; + this._updateRange(); } /** * The maximum allowed value. */ get max(): number { - return Number(this._rangeEl.max); + return this._max; } + @property({ reflect: true, type: Number, attribute: 'max' }) set max(max: number) { - this._rangeEl.max = String(max); - this.update(); + this._max = max; + this._updateRange(); } /** @@ -145,57 +146,36 @@ export abstract class Range extends StateReceiverMixin(HTMLElement, ['deviceType * * If set to `"any"`, the value can change with arbitrary precision. */ - get step(): number | 'any' { - const raw = this._rangeEl.step; - return raw === 'any' ? raw : Number(raw); - } - - set step(step: number | 'any') { - this._rangeEl.step = String(step); - } + @property({ + reflect: true, + attribute: 'step', + converter: { + fromAttribute: (value): number | 'any' => (value == null || value === 'any' ? 'any' : Number(value)) + } + }) + accessor step: number | 'any' = 'any'; - get deviceType(): DeviceType { - return (this.getAttribute(Attribute.DEVICE_TYPE) || 'desktop') as DeviceType; - } + @property({ reflect: true, type: String, attribute: Attribute.DEVICE_TYPE }) + accessor deviceType: DeviceType = 'desktop'; - set deviceType(deviceType: DeviceType) { - this.setAttribute(Attribute.DEVICE_TYPE, deviceType); - } + @property({ reflect: true, type: String, attribute: 'aria-live' }) + accessor ariaLive: string | null = null; - attributeChangedCallback(attrName: string, oldValue: any, newValue: any) { - if (newValue === oldValue) { - return; - } - const hasValue = newValue != null; - if (attrName === Attribute.DISABLED || attrName === Attribute.INERT) { - this.setAttribute('aria-disabled', this.disabled ? 'true' : 'false'); - toggleAttribute(this._rangeEl, Attribute.DISABLED, this.disabled || this.inert); - } else if (attrName === Attribute.HIDDEN) { - if (!hasValue) { - this.update(); - } - } - if (Range.observedAttributes.indexOf(attrName as Attribute) >= 0) { - shadyCss.styleSubtree(this); - } - } - - private readonly _onInput = () => { - this.handleInput(); + private readonly _onInput = (event: Event) => { + this.value = (event.target as HTMLInputElement).valueAsNumber; }; protected handleInput(): void { - this.update(); + this._updateRange(); } - protected update(useCachedWidth?: boolean): void { - if (this.hasAttribute(Attribute.HIDDEN)) { + protected _updateRange(useCachedWidth?: boolean): void { + if (this.hidden) { return; } if (!useCachedWidth) { this.updateCachedWidths_(); } - this._rangeEl.setAttribute('aria-valuetext', this.getAriaValueText()); this.updateBar_(); } @@ -251,7 +231,7 @@ export abstract class Range extends StateReceiverMixin(HTMLElement, ['deviceType private updateCachedWidths_(): void { // Use the last non-zero range width, in case the range is temporarily hidden. - const rangeWidth = this._rangeEl.offsetWidth; + const rangeWidth = this._rangeRef.value?.offsetWidth ?? 0; if (rangeWidth > 0) { this._rangeWidth = rangeWidth; } @@ -273,11 +253,12 @@ export abstract class Range extends StateReceiverMixin(HTMLElement, ['deviceType } private readonly _updatePointerBar = (e: PointerEvent): void => { - if (this.disabled || this.inert) { + const rangeEl = this._rangeRef.value; + if (this.disabled || this.inert || !rangeEl) { return; } // Get mouse position percent - const rangeRect = this._rangeEl.getBoundingClientRect(); + const rangeRect = rangeEl.getBoundingClientRect(); let mousePercent = (e.clientX - rangeRect.left) / rangeRect.width; // Lock between 0 and 1 mousePercent = Math.max(0, Math.min(1, mousePercent)); @@ -285,7 +266,7 @@ export abstract class Range extends StateReceiverMixin(HTMLElement, ['deviceType }; protected updatePointer_(mousePercent: number, rangeRect: DOMRectReadOnly): void { - this._pointerEl.style.width = `${mousePercent * rangeRect.width}px`; + this._pointerWidth = mousePercent * rangeRect.width; } private readonly _onKeyDown = (e: KeyboardEvent): void => { @@ -305,4 +286,34 @@ export abstract class Range extends StateReceiverMixin(HTMLElement, ['deviceType } } } + + protected override render(): HTMLTemplateResult { + return html`
+
+
+ elements... use 'change' instead. */ + this._onInput + } + @keydown=${this._onKeyDown} + /> + ${this.renderRangeExtras()} +
`; + } + + protected renderRangeExtras(): HTMLTemplateResult { + return html``; + } } diff --git a/src/components/TimeRange.html b/src/components/TimeRange.html deleted file mode 100644 index b716a4dd..00000000 --- a/src/components/TimeRange.html +++ /dev/null @@ -1,9 +0,0 @@ - -
-
- - - - -
-
diff --git a/src/components/TimeRange.ts b/src/components/TimeRange.ts index 5386b998..69d019e7 100644 --- a/src/components/TimeRange.ts +++ b/src/components/TimeRange.ts @@ -1,8 +1,10 @@ -import * as shadyCss from '@webcomponents/shadycss'; -import { Range, rangeTemplate } from './Range'; -import timeRangeHtml from './TimeRange.html'; +import { html, type HTMLTemplateResult, type PropertyValues } from 'lit'; +import { customElement, property, state } from 'lit/decorators.js'; +import { createRef, type Ref, ref } from 'lit/directives/ref.js'; +import { styleMap } from 'lit/directives/style-map.js'; +import { Range } from './Range'; import timeRangeCss from './TimeRange.css'; -import { StateReceiverMixin } from './StateReceiverMixin'; +import { stateReceiver } from './StateReceiverMixin'; import type { Ads, ChromelessPlayer, TimeRanges } from 'theoplayer/chromeless'; import { formatAsTimePhrase } from '../util/TimeUtils'; import { createCustomEvent } from '../util/EventUtils'; @@ -13,14 +15,11 @@ import type { StreamType } from '../util/StreamType'; import { isLinearAd } from '../util/AdUtils'; import type { ColorStops } from '../util/ColorStops'; import { KeyCode } from '../util/KeyCode'; -import { createTemplate } from '../util/TemplateUtils'; // Load components used in template import './PreviewThumbnail'; import './PreviewTimeDisplay'; -const template = createTemplate('theoplayer-time-range', rangeTemplate(timeRangeHtml, timeRangeCss)); - const UPDATE_EVENTS = ['timeupdate', 'durationchange', 'ratechange', 'seeking', 'seeked'] as const; const AUTO_ADVANCE_EVENTS = ['play', 'pause', 'ended', 'durationchange', 'readystatechange', 'error'] as const; const AD_EVENTS = ['adbreakbegin', 'adbreakend', 'adbreakchange', 'updateadbreak', 'adbegin', 'adend', 'adskip', 'addad', 'updatead'] as const; @@ -40,15 +39,16 @@ const AD_MARKER_WIDTH = 1; * the {@link PreviewThumbnail | preview thumbnail}. * @group Components */ -export class TimeRange extends StateReceiverMixin(Range, ['player', 'streamType', 'deviceType']) { - static get observedAttributes() { - return [...Range.observedAttributes, Attribute.SHOW_AD_MARKERS]; - } +@customElement('theoplayer-time-range') +@stateReceiver(['player', 'streamType', 'deviceType']) +export class TimeRange extends Range { + static override styles = [...Range.styles, timeRangeCss]; - private readonly _previewRailEl: HTMLElement; - private readonly _previewBoxEl: HTMLElement; + private readonly _previewBoxEl: Ref = createRef(); private _player: ChromelessPlayer | undefined; + private _streamType: StreamType = 'vod'; + private _showAdMarkers: boolean = false; private _ads: Ads | undefined; private _pausedWhileScrubbing: boolean = false; @@ -58,16 +58,10 @@ export class TimeRange extends StateReceiverMixin(Range, ['player', 'streamType' private _lastPlaybackRate: number = 0; constructor() { - super({ template: template() }); - - this._previewRailEl = this.shadowRoot!.querySelector('.theoplayer-time-range-preview-rail')!; - this._previewBoxEl = this._previewRailEl.querySelector('[part~="preview-box"]')!; - - this._rangeEl.setAttribute(Attribute.ARIA_LIVE, 'off'); - this._rangeEl.addEventListener('mousedown', this._pauseOnScrubStart); - this._rangeEl.addEventListener('pointerdown', this._pauseOnScrubStart); - - this._upgradeProperty('player'); + super(); + this.min = 0; + this.max = 1000; + this.ariaLive = 'off'; } override connectedCallback(): void { @@ -75,8 +69,17 @@ export class TimeRange extends StateReceiverMixin(Range, ['player', 'streamType' this._toggleAutoAdvance(); } + protected override firstUpdated(_changedProperties: PropertyValues) { + this._rangeRef.value?.addEventListener('mousedown', this._pauseOnScrubStart); + this._rangeRef.value?.addEventListener('pointerdown', this._pauseOnScrubStart); + } + override disconnectedCallback(): void { super.disconnectedCallback(); + + this._rangeRef.value?.removeEventListener('mousedown', this._pauseOnScrubStart); + this._rangeRef.value?.removeEventListener('pointerdown', this._pauseOnScrubStart); + this._toggleAutoAdvance(); } @@ -84,6 +87,7 @@ export class TimeRange extends StateReceiverMixin(Range, ['player', 'streamType' return this._player; } + @property({ reflect: false, attribute: false }) set player(player: ChromelessPlayer | undefined) { if (this._player === player) { return; @@ -105,11 +109,23 @@ export class TimeRange extends StateReceiverMixin(Range, ['player', 'streamType' } get streamType(): StreamType { - return (this.getAttribute(Attribute.STREAM_TYPE) || 'vod') as StreamType; + return this._streamType; } + @property({ reflect: true, type: String, attribute: Attribute.STREAM_TYPE }) set streamType(streamType: StreamType) { - this.setAttribute(Attribute.STREAM_TYPE, streamType); + this._streamType = streamType; + this.updateDisabled_(); + } + + get showAdMarkers(): boolean { + return this._showAdMarkers; + } + + @property({ reflect: true, type: Boolean, attribute: Attribute.SHOW_AD_MARKERS }) + set showAdMarkers(showAdMarkers: boolean) { + this._showAdMarkers = showAdMarkers; + this._updateRange(); } private readonly _updateFromPlayer = () => { @@ -122,6 +138,7 @@ export class TimeRange extends StateReceiverMixin(Range, ['player', 'streamType' const seekable = this._player.seekable; let min: number; let max: number; + let value: number = this._lastCurrentTime; if (seekable.length !== 0) { min = seekable.start(0); max = seekable.end(0); @@ -131,12 +148,11 @@ export class TimeRange extends StateReceiverMixin(Range, ['player', 'streamType' } if (!isFinite(this._lastCurrentTime)) { const isLive = this._player.duration === Infinity; - this._lastCurrentTime = isLive ? max : min; + value = isLive ? max : min; } - this._rangeEl.min = String(min); - this._rangeEl.max = String(max); - this._rangeEl.valueAsNumber = this._lastCurrentTime; - this.update(); + this.min = min; + this.max = max; + this.value = value; this.updateDisabled_(seekable); }; @@ -145,9 +161,7 @@ export class TimeRange extends StateReceiverMixin(Range, ['player', 'streamType' if (seekable !== undefined) { disabled ||= seekable.length === 0; } - if (this.disabled !== disabled) { - this.disabled = disabled; - } + this.disabled = disabled; } protected override getAriaLabel(): string { @@ -163,21 +177,6 @@ export class TimeRange extends StateReceiverMixin(Range, ['player', 'streamType' return DEFAULT_MISSING_TIME_PHRASE; } - override attributeChangedCallback(attrName: string, oldValue: any, newValue: any) { - super.attributeChangedCallback(attrName, oldValue, newValue); - if (newValue === oldValue) { - return; - } - if (attrName === Attribute.STREAM_TYPE) { - this.updateDisabled_(); - } else if (attrName === Attribute.SHOW_AD_MARKERS) { - this.update(); - } - if (TimeRange.observedAttributes.indexOf(attrName as Attribute) >= 0) { - shadyCss.styleSubtree(this); - } - } - protected override handleInput(): void { if (this._player !== undefined && this._player.currentTime !== this.value) { this._player.currentTime = this.value; @@ -225,6 +224,7 @@ export class TimeRange extends StateReceiverMixin(Range, ['player', 'streamType' !this._player.ended && !this._player.errorObject && this._player.readyState >= 3 && + isFinite(this._lastCurrentTime) && this.needToUpdateEveryFrame_() ); } @@ -262,26 +262,32 @@ export class TimeRange extends StateReceiverMixin(Range, ['player', 'streamType' const delta = (performance.now() - this._lastUpdateTime) / 1000; const newValue = this._lastCurrentTime + delta * this._lastPlaybackRate; if (Math.abs(newValue - this.value) >= this.getMinimumStepForVisibleChange_()) { - this._rangeEl.valueAsNumber = newValue; + this.value = newValue; // Use cached width to avoid synchronous layout - this.update(/* useCachedWidth = */ true); + this._updateRange(/* useCachedWidth = */ true); } this._autoAdvanceId = requestAnimationFrame(this._autoAdvanceWhilePlaying); }; + @state() + private accessor _previewRailTransform: string = ''; + protected override updatePointer_(mousePercent: number, rangeRect: DOMRectReadOnly): void { super.updatePointer_(mousePercent, rangeRect); // Update preview rail, keeping the preview box within bounds let previewPos = rangeRect.left + mousePercent * rangeRect.width; - const previewBoxRect = this._previewBoxEl.getBoundingClientRect(); - const minPreviewPos = rangeRect.left + previewBoxRect.width / 2; - const maxPreviewPos = rangeRect.right - previewBoxRect.width / 2; - previewPos = Math.max(minPreviewPos, Math.min(maxPreviewPos, previewPos)); - const previewPosFraction = (previewPos - rangeRect.left) / rangeRect.width; - this._previewRailEl.style.transform = `translateX(${(previewPosFraction * 100 * 100).toFixed(6)}%)`; + const previewBoxEl = this._previewBoxEl.value; + if (previewBoxEl) { + const previewBoxRect = previewBoxEl.getBoundingClientRect(); + const minPreviewPos = rangeRect.left + previewBoxRect.width / 2; + const maxPreviewPos = rangeRect.right - previewBoxRect.width / 2; + previewPos = Math.max(minPreviewPos, Math.min(maxPreviewPos, previewPos)); + const previewPosFraction = (previewPos - rangeRect.left) / rangeRect.width; + this._previewRailTransform = `translateX(${(previewPosFraction * 100 * 100).toFixed(6)}%)`; + } // Propagate preview time to parent if (this._player === undefined) { @@ -299,7 +305,7 @@ export class TimeRange extends StateReceiverMixin(Range, ['player', 'streamType' protected override getBarColors(): ColorStops { const colorStops = super.getBarColors(); - if (!this.hasAttribute(Attribute.SHOW_AD_MARKERS) || !this._player || !this._player.ads) { + if (!this.showAdMarkers || !this._player || !this._player.ads) { return colorStops; } if (this._player.ads.playing) { @@ -335,7 +341,7 @@ export class TimeRange extends StateReceiverMixin(Range, ['player', 'streamType' } private readonly _onAdChange = () => { - this.update(); + this._updateRange(); }; protected override handleKeyDown_(e: KeyboardEvent) { @@ -350,9 +356,20 @@ export class TimeRange extends StateReceiverMixin(Range, ['player', 'streamType' } } } -} -customElements.define('theoplayer-time-range', TimeRange); + protected override renderRangeExtras(): HTMLTemplateResult { + return html` +
+
+ + + + +
+
+ `; + } +} declare global { interface HTMLElementTagNameMap { diff --git a/src/components/VolumeRange.ts b/src/components/VolumeRange.ts index e3ca0ca3..f7f7f09c 100644 --- a/src/components/VolumeRange.ts +++ b/src/components/VolumeRange.ts @@ -1,9 +1,7 @@ -import { Range, rangeTemplate } from './Range'; -import { StateReceiverMixin } from './StateReceiverMixin'; +import { Range } from './Range'; +import { customElement, property } from 'lit/decorators.js'; +import { stateReceiver } from './StateReceiverMixin'; import type { ChromelessPlayer } from 'theoplayer/chromeless'; -import { createTemplate } from '../util/TemplateUtils'; - -const template = createTemplate('theoplayer-volume-range', rangeTemplate(``)); function formatAsPercentString(value: number, max: number) { return `${Math.round((value / max) * 100)}%`; @@ -15,18 +13,22 @@ function formatAsPercentString(value: number, max: number) { * * @group Components */ -export class VolumeRange extends StateReceiverMixin(Range, ['player', 'deviceType']) { +@customElement('theoplayer-volume-range') +@stateReceiver(['player', 'deviceType']) +export class VolumeRange extends Range { private _player: ChromelessPlayer | undefined; constructor() { - super({ template: template() }); - this._upgradeProperty('player'); + super(); + this.min = 0; + this.max = 1; } get player(): ChromelessPlayer | undefined { return this._player; } + @property({ reflect: false, attribute: false }) set player(player: ChromelessPlayer | undefined) { if (this._player === player) { return; @@ -63,8 +65,6 @@ export class VolumeRange extends StateReceiverMixin(Range, ['player', 'deviceTyp } } -customElements.define('theoplayer-volume-range', VolumeRange); - declare global { interface HTMLElementTagNameMap { 'theoplayer-volume-range': VolumeRange; From a8935665b5c19b0433f60bae400ca7f1f1d7e7a4 Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Fri, 25 Apr 2025 17:28:38 +0200 Subject: [PATCH 064/123] Rework UIContainer --- src/UIContainer.html | 29 --- src/UIContainer.ts | 508 ++++++++++++++++++++++++------------------- 2 files changed, 284 insertions(+), 253 deletions(-) delete mode 100644 src/UIContainer.html diff --git a/src/UIContainer.html b/src/UIContainer.html deleted file mode 100644 index df76c6d2..00000000 --- a/src/UIContainer.html +++ /dev/null @@ -1,29 +0,0 @@ -
-
- -
-
-
- -
-
- -
-
- -
-
- -
-
- -
-
-
- - - -
-
- -
diff --git a/src/UIContainer.ts b/src/UIContainer.ts index b83b0202..51172405 100644 --- a/src/UIContainer.ts +++ b/src/UIContainer.ts @@ -1,7 +1,9 @@ +import { html, type HTMLTemplateResult, LitElement } from 'lit'; +import { customElement, property } from 'lit/decorators.js'; +import { createRef, ref, type Ref } from 'lit/directives/ref.js'; import * as shadyCss from '@webcomponents/shadycss'; import { ChromelessPlayer, type MediaTrack, type SourceDescription, type UIPlayerConfiguration, type VideoQuality } from 'theoplayer/chromeless'; import elementCss from './UIContainer.css'; -import elementHtml from './UIContainer.html'; import { arrayFind, arrayRemove, @@ -16,7 +18,6 @@ import { } from './util/CommonUtils'; import { forEachStateReceiverElement, type StateReceiverElement, StateReceiverProps } from './components/StateReceiverMixin'; import { TOGGLE_MENU_EVENT, type ToggleMenuEvent } from './events/ToggleMenuEvent'; -import { CLOSE_MENU_EVENT } from './events/CloseMenuEvent'; import { ENTER_FULLSCREEN_EVENT, type EnterFullscreenEvent } from './events/EnterFullscreenEvent'; import { EXIT_FULLSCREEN_EVENT, type ExitFullscreenEvent } from './events/ExitFullscreenEvent'; import { fullscreenAPI } from './util/FullscreenUtils'; @@ -31,20 +32,16 @@ import { USER_IDLE_CHANGE_EVENT } from './events/UserIdleChangeEvent'; import { createCustomEvent } from './util/EventUtils'; import { getTargetQualities } from './util/TrackUtils'; import { MenuGroup } from './components/MenuGroup'; -import { MENU_CHANGE_EVENT } from './events/MenuChangeEvent'; import type { DeviceType } from './util/DeviceType'; import { getFocusedChild, navigateByArrowKey } from './util/KeyboardNavigation'; import { isArrowKey, isBackKey, KeyCode } from './util/KeyCode'; import { READY_EVENT } from './events/ReadyEvent'; -import { createTemplate } from './util/TemplateUtils'; import { addGlobalStyles } from './Global'; import { ACCIDENTAL_CLICK_DELAY } from './util/Constants'; // Load components used in template import './components/GestureReceiver'; -const template = createTemplate('theoplayer-ui', `${elementHtml}`); - const DEFAULT_USER_IDLE_TIMEOUT = 2; const DEFAULT_TV_USER_IDLE_TIMEOUT = 5; const DEFAULT_DVR_THRESHOLD = 60; @@ -115,7 +112,14 @@ const FULL_WINDOW_ROOT_CLASS = 'theoplayer-ui-full-window'; * (see {@link ErrorDisplay | ``}). * @group Components */ -export class UIContainer extends HTMLElement { +@customElement('theoplayer-ui') +export class UIContainer extends LitElement { + static override styles = [elementCss]; + static override shadowRootOptions = { + ...LitElement.shadowRootOptions, + delegatesFocus: true + }; + /** * Fired when the backing player is created, and the {@link UIContainer.player} property is set. * @@ -123,37 +127,15 @@ export class UIContainer extends HTMLElement { */ static READY_EVENT: typeof READY_EVENT = READY_EVENT; - static get observedAttributes() { - return [ - Attribute.CONFIGURATION, - Attribute.SOURCE, - Attribute.MUTED, - Attribute.AUTOPLAY, - Attribute.FULLSCREEN, - Attribute.FLUID, - Attribute.DEVICE_TYPE, - Attribute.PAUSED, - Attribute.ENDED, - Attribute.CASTING, - Attribute.PLAYING_AD, - Attribute.HAS_ERROR, - Attribute.HAS_FIRST_PLAY, - Attribute.STREAM_TYPE, - Attribute.DVR_THRESHOLD, - Attribute.USER_IDLE, - Attribute.USER_IDLE_TIMEOUT - ]; - } - private _configuration: UIPlayerConfiguration = {}; - private readonly _playerEl: HTMLElement; - private readonly _menuEl: HTMLElement; - private readonly _menuGroup: MenuGroup; + private readonly _playerRef: Ref = createRef(); + private readonly _menuRef: Ref = createRef(); + private readonly _menuGroupRef: Ref = createRef(); private _menuOpener: HTMLElement | undefined; - private readonly _topChromeEl: HTMLElement; - private readonly _topChromeSlot: HTMLSlotElement; - private readonly _bottomChromeEl: HTMLElement; - private readonly _bottomChromeSlot: HTMLSlotElement; + private readonly _topChromeRef: Ref = createRef(); + private readonly _topChromeSlotRef: Ref = createRef(); + private readonly _bottomChromeRef: Ref = createRef(); + private readonly _bottomChromeSlotRef: Ref = createRef(); private _pointerType: string = ''; private _lastPointerUpTime: number = 0; @@ -162,8 +144,20 @@ export class UIContainer extends HTMLElement { private readonly _stateReceivers: StateReceiverElement[] = []; private _player: ChromelessPlayer | undefined = undefined; private _source: SourceDescription | undefined = undefined; + private _muted: boolean = false; + private _autoplay: boolean = false; + private _fullscreen: boolean = false; + private _deviceType: DeviceType = 'desktop'; + private _streamType: StreamType = 'vod'; + private _fluid: boolean = false; + private _userIdle: boolean = false; + private _userIdleTimeout: number | undefined = undefined; private _isUserActive: boolean = false; private _userIdleTimer: number = 0; + private _paused: boolean = false; + private _ended: boolean = false; + private _casting: boolean = false; + private _dvrThreshold: number = DEFAULT_DVR_THRESHOLD; private _previewTime: number = NaN; private _activeVideoTrack: MediaTrack | undefined = undefined; @@ -177,53 +171,16 @@ export class UIContainer extends HTMLElement { */ constructor(configuration: UIPlayerConfiguration = {}) { super(); - const shadowRoot = this.attachShadow({ mode: 'open', delegatesFocus: true }); - shadowRoot.appendChild(template().content.cloneNode(true)); - this._configuration = configuration; - this._playerEl = shadowRoot.querySelector('[part~="media-layer"]')!; - this._menuEl = shadowRoot.querySelector('[part~="menu-layer"]')!; - this._menuGroup = shadowRoot.querySelector('theoplayer-menu-group')!; - this._topChromeEl = shadowRoot.querySelector('[part~="top"]')!; - this._topChromeSlot = shadowRoot.querySelector('slot[name="top-chrome"]')!; - this._bottomChromeEl = shadowRoot.querySelector('[part~="bottom"]')!; - this._bottomChromeSlot = shadowRoot.querySelector('slot:not([name])')!; - this._mutationObserver = new MutationObserver(this._onMutation); if (typeof ResizeObserver !== 'undefined') { this._resizeObserver = new ResizeObserver(this._updateTextTrackMargins); } - shadowRoot.addEventListener(TOGGLE_MENU_EVENT, this._onToggleMenu); - shadowRoot.addEventListener(ENTER_FULLSCREEN_EVENT, this._onEnterFullscreen); - shadowRoot.addEventListener(EXIT_FULLSCREEN_EVENT, this._onExitFullscreen); - shadowRoot.addEventListener(PREVIEW_TIME_CHANGE_EVENT, this._onPreviewTimeChange); - - this._topChromeSlot.addEventListener('transitionstart', this._onChromeSlotTransition); - this._topChromeSlot.addEventListener('transitionend', this._onChromeSlotTransition); - this._bottomChromeSlot.addEventListener('transitionstart', this._onChromeSlotTransition); - this._bottomChromeSlot.addEventListener('transitionend', this._onChromeSlotTransition); - - this._upgradeProperty('configuration'); - this._upgradeProperty('source'); - this._upgradeProperty('fluid'); - this._upgradeProperty('muted'); - this._upgradeProperty('autoplay'); - this._upgradeProperty('userIdleTimeout'); - this._upgradeProperty('streamType'); - this.tryInitializePlayer_(); } - private _upgradeProperty(prop: keyof this) { - if (this.hasOwnProperty(prop)) { - let value = this[prop]; - delete this[prop]; - this[prop] = value; - } - } - /** * The underlying THEOplayer player instance. * @@ -242,8 +199,14 @@ export class UIContainer extends HTMLElement { return this._configuration; } + @property({ + reflect: false, + attribute: Attribute.CONFIGURATION, + converter: { + fromAttribute: (value: string | null) => (value ? (JSON.parse(value) as UIPlayerConfiguration) : {}) + } + }) set configuration(playerConfiguration: UIPlayerConfiguration) { - this.removeAttribute(Attribute.CONFIGURATION); this._setConfiguration(playerConfiguration); } @@ -259,8 +222,14 @@ export class UIContainer extends HTMLElement { return this._player ? this._player.source : this._source; } + @property({ + reflect: false, + attribute: Attribute.SOURCE, + converter: { + fromAttribute: (value: string | null) => (value ? (JSON.parse(value) as SourceDescription) : undefined) + } + }) set source(value: SourceDescription | undefined) { - this.removeAttribute(Attribute.SOURCE); this._setSource(value); } @@ -276,75 +245,115 @@ export class UIContainer extends HTMLElement { * Whether to automatically adjusts the player's height to fit the video's aspect ratio. */ get fluid(): boolean { - return this.hasAttribute(Attribute.FLUID); + return this._fluid; } + @property({ reflect: true, type: Boolean, attribute: Attribute.FLUID }) set fluid(value: boolean) { - toggleAttribute(this, Attribute.FLUID, value); + this._fluid = value; + this._updateAspectRatio(); } /** * Whether the player's audio is muted. */ get muted(): boolean { - return this.hasAttribute(Attribute.MUTED); + return this._muted; } + @property({ reflect: true, type: Boolean, attribute: Attribute.MUTED }) set muted(value: boolean) { - toggleAttribute(this, Attribute.MUTED, value); + this._muted = value; + if (this._player) { + this._player.muted = value; + } } /** * Whether the player should attempt to automatically start playback. */ get autoplay(): boolean { - return this.hasAttribute(Attribute.AUTOPLAY); + return this._autoplay; } + @property({ reflect: true, type: Boolean, attribute: Attribute.AUTOPLAY }) set autoplay(value: boolean) { - toggleAttribute(this, Attribute.AUTOPLAY, value); + this._autoplay = value; + if (this._player) { + this._player.autoplay = value; + } } /** * Whether the UI is in fullscreen mode. */ get fullscreen(): boolean { - return this.hasAttribute(Attribute.FULLSCREEN); + return this._fullscreen; + } + + @property({ reflect: true, type: Boolean, attribute: Attribute.FULLSCREEN }) + private set fullscreen(value: boolean) { + if (this._fullscreen === value) return; + this._fullscreen = value; + for (const receiver of this._stateReceivers) { + if (receiver[StateReceiverProps].indexOf('fullscreen') >= 0) { + receiver.fullscreen = value; + } + } } /** * Whether the player is paused. */ get paused(): boolean { - return this.hasAttribute(Attribute.PAUSED); + return this._paused; + } + + @property({ reflect: true, type: Boolean, attribute: Attribute.PAUSED }) + private set paused(value: boolean) { + this._paused = value; + this._updateTextTrackMargins(); + this.updateUserIdle_(); } /** * Whether the player is ended. */ get ended(): boolean { - return this.hasAttribute(Attribute.ENDED); + return this._ended; + } + + @property({ reflect: true, type: Boolean, attribute: Attribute.ENDED }) + private set ended(value: boolean) { + this._ended = value; } /** * Whether the player is casting to a remote receiver. */ get casting(): boolean { - return this.hasAttribute(Attribute.CASTING); + return this._casting; + } + + @property({ reflect: true, type: Boolean, attribute: Attribute.CASTING }) + private set casting(value: boolean) { + this._casting = value; + this._updateTextTrackMargins(); + this.updateUserIdle_(); } /** * Whether the user has stopped interacting with the UI and is considered to be "idle". */ get userIdle(): boolean { - return this.hasAttribute(Attribute.USER_IDLE); + return this._userIdle; } + @property({ reflect: true, type: Boolean, attribute: Attribute.USER_IDLE }) private set userIdle(value: boolean) { - if (this.userIdle === value) { - return; - } - toggleAttribute(this, Attribute.USER_IDLE, value); + if (this._userIdle === value) return; + this._userIdle = value; + this._updateTextTrackMargins(); this.dispatchEvent(createCustomEvent(USER_IDLE_CHANGE_EVENT)); } @@ -353,20 +362,39 @@ export class UIContainer extends HTMLElement { * and when the user is considered to be "idle". */ get userIdleTimeout(): number { - const defaultTimeout = this.deviceType === 'tv' ? DEFAULT_TV_USER_IDLE_TIMEOUT : DEFAULT_USER_IDLE_TIMEOUT; - return Number(this.getAttribute(Attribute.USER_IDLE_TIMEOUT) ?? defaultTimeout); + return this._userIdleTimeout ?? (this.deviceType === 'tv' ? DEFAULT_TV_USER_IDLE_TIMEOUT : DEFAULT_USER_IDLE_TIMEOUT); } - set userIdleTimeout(value: number) { - value = Number(value); - this.setAttribute(Attribute.USER_IDLE_TIMEOUT, String(isNaN(value) ? 0 : value)); + @property({ reflect: true, type: Number, attribute: Attribute.USER_IDLE_TIMEOUT }) + set userIdleTimeout(value: number | undefined) { + this._userIdleTimeout = value === undefined || isNaN(value) ? undefined : value; } /** * The device type, either "desktop", "mobile" or "tv". */ get deviceType(): DeviceType { - return (this.getAttribute(Attribute.DEVICE_TYPE) || 'desktop') as DeviceType; + return this._deviceType; + } + + @property({ reflect: true, type: String, attribute: Attribute.DEVICE_TYPE }) + set deviceType(value: DeviceType) { + if (this._deviceType === value) return; + this._deviceType = value; + + toggleAttribute(this, Attribute.MOBILE, value === 'mobile'); + toggleAttribute(this, Attribute.TV, value === 'tv'); + + window.removeEventListener('keydown', this._onTvKeyDown); + if (value === 'tv') { + window.addEventListener('keydown', this._onTvKeyDown); + } + + for (const receiver of this._stateReceivers) { + if (receiver[StateReceiverProps].indexOf('deviceType') >= 0) { + receiver.deviceType = value; + } + } } /** @@ -376,14 +404,27 @@ export class UIContainer extends HTMLElement { * when the player switches between its VOD-specific and live-only controls. */ get streamType(): StreamType { - return (this.getAttribute(Attribute.STREAM_TYPE) || 'vod') as StreamType; + return this._streamType; } /** * @deprecated use {@link SourceDescription.streamType} instead. */ + @property({ reflect: true, type: String, attribute: Attribute.STREAM_TYPE }) set streamType(streamType: StreamType) { - this.setAttribute(Attribute.STREAM_TYPE, streamType); + if (this._streamType === streamType) return; + this._streamType = streamType; + for (const receiver of this._stateReceivers) { + if (receiver[StateReceiverProps].indexOf('streamType') >= 0) { + receiver.streamType = streamType; + } + } + const streamTypeChangeEvent: StreamTypeChangeEvent = createCustomEvent(STREAM_TYPE_CHANGE_EVENT, { + bubbles: true, + composed: true, + detail: { streamType: streamType } + }); + this.dispatchEvent(streamTypeChangeEvent); } /** @@ -391,28 +432,36 @@ export class UIContainer extends HTMLElement { * and its stream type to be set to "dvr". */ get dvrThreshold(): number { - return Number(this.getAttribute(Attribute.DVR_THRESHOLD) ?? DEFAULT_DVR_THRESHOLD); + return this._dvrThreshold; } + @property({ reflect: true, type: Number, attribute: Attribute.DVR_THRESHOLD, useDefault: true }) set dvrThreshold(value: number) { - value = Number(value); - this.setAttribute(Attribute.DVR_THRESHOLD, String(isNaN(value) ? 0 : value)); + this._dvrThreshold = isNaN(value) ? 0 : value; + this._updateStreamType(); } + @property({ reflect: true, state: true, type: Boolean, attribute: Attribute.HAS_FIRST_PLAY }) + private accessor _hasFirstPlay: boolean = false; + + @property({ reflect: true, state: true, type: Boolean, attribute: Attribute.PLAYING_AD }) + private accessor _isPlayingAd: boolean = false; + + @property({ reflect: true, state: true, type: Boolean, attribute: Attribute.HAS_ERROR }) + private accessor _hasError: boolean = false; + + @property({ reflect: true, state: true, type: Boolean, attribute: Attribute.FULLWINDOW }) + private accessor _isFullWindow: boolean = false; + connectedCallback(): void { - shadyCss.styleElement(this); + super.connectedCallback(); addGlobalStyles(); - if (!(this._menuGroup instanceof MenuGroup)) { - customElements.upgrade(this._menuGroup); - } - if (!this.hasAttribute(Attribute.DEVICE_TYPE)) { - const deviceType: DeviceType = isMobile() ? 'mobile' : isTv() ? 'tv' : 'desktop'; - this.setAttribute(Attribute.DEVICE_TYPE, deviceType); + this.deviceType = isMobile() ? 'mobile' : isTv() ? 'tv' : 'desktop'; } if (!this.hasAttribute(Attribute.PAUSED)) { - this.setAttribute(Attribute.PAUSED, ''); + this.paused = true; } this.tryInitializePlayer_(); @@ -420,16 +469,12 @@ export class UIContainer extends HTMLElement { for (const receiver of this._stateReceivers) { this.propagateStateToReceiver_(receiver); } - void forEachStateReceiverElement(this, this._playerEl, this.registerStateReceiver_); this._mutationObserver.observe(this, { childList: true, subtree: true }); this.shadowRoot!.addEventListener('slotchange', this._onSlotChange); this._resizeObserver?.observe(this); this._updateTextTrackMargins(); - this._menuGroup.addEventListener(CLOSE_MENU_EVENT, this._onCloseMenu); - this._menuGroup.addEventListener(MENU_CHANGE_EVENT, this._onMenuChange); - if (fullscreenAPI !== undefined) { document.addEventListener(fullscreenAPI.fullscreenchange_, this._onFullscreenChange); document.addEventListener(fullscreenAPI.fullscreenerror_, this._onFullscreenChange); @@ -440,7 +485,7 @@ export class UIContainer extends HTMLElement { if (this.deviceType === 'tv') { window.addEventListener('keydown', this._onTvKeyDown); } - if (this.hasAttribute(Attribute.FULLWINDOW)) { + if (this._isFullWindow) { window.addEventListener('keydown', this._exitFullscreenOnEsc); } this.addEventListener('keyup', this._onKeyUp); @@ -453,11 +498,14 @@ export class UIContainer extends HTMLElement { if (this._player !== undefined) { return; } + if (this._playerRef.value === undefined) { + return; + } if (this._configuration.license === undefined && this._configuration.licenseUrl === undefined) { return; } - this._player = new ChromelessPlayer(this._playerEl, this._configuration); + this._player = new ChromelessPlayer(this._playerRef.value, this._configuration); if (this._source) { this._player.source = this._source; this._source = undefined; @@ -475,14 +523,34 @@ export class UIContainer extends HTMLElement { this.dispatchEvent(createCustomEvent(READY_EVENT)); } + protected override createRenderRoot(): HTMLElement | DocumentFragment { + const root = super.createRenderRoot(); + root.addEventListener(TOGGLE_MENU_EVENT, this._onToggleMenu); + root.addEventListener(ENTER_FULLSCREEN_EVENT, this._onEnterFullscreen); + root.addEventListener(EXIT_FULLSCREEN_EVENT, this._onExitFullscreen); + root.addEventListener(PREVIEW_TIME_CHANGE_EVENT, this._onPreviewTimeChange); + return root; + } + + protected override firstUpdated() { + if (this._menuGroupRef.value && !(this._menuGroupRef instanceof MenuGroup)) { + customElements.upgrade(this._menuGroupRef.value); + } + + this.tryInitializePlayer_(); + + if (this._playerRef.value) { + void forEachStateReceiverElement(this, this._playerRef.value, this.registerStateReceiver_); + } + } + disconnectedCallback(): void { + super.disconnectedCallback(); + this._resizeObserver?.disconnect(); this._mutationObserver.disconnect(); this.shadowRoot!.removeEventListener('slotchange', this._onSlotChange); - this._menuGroup.removeEventListener(CLOSE_MENU_EVENT, this._onCloseMenu); - this._menuGroup.removeEventListener(MENU_CHANGE_EVENT, this._onMenuChange); - if (fullscreenAPI !== undefined) { document.removeEventListener(fullscreenAPI.fullscreenchange_, this._onFullscreenChange); document.removeEventListener(fullscreenAPI.fullscreenerror_, this._onFullscreenChange); @@ -506,82 +574,22 @@ export class UIContainer extends HTMLElement { this._stateReceivers.length = 0; } - attributeChangedCallback(attrName: string, oldValue: any, newValue: any): void { - if (newValue === oldValue) { - return; - } - const hasValue = newValue != null; - if (attrName === Attribute.CONFIGURATION) { - this._setConfiguration(newValue ? (JSON.parse(newValue) as UIPlayerConfiguration) : {}); - } else if (attrName === Attribute.SOURCE) { - this._setSource(newValue ? (JSON.parse(newValue) as SourceDescription) : undefined); - } else if (attrName === Attribute.MUTED) { - if (this._player) { - this._player.muted = hasValue; - } - } else if (attrName === Attribute.AUTOPLAY) { - if (this._player) { - this._player.autoplay = hasValue; - } - } else if (attrName === Attribute.FULLSCREEN) { - for (const receiver of this._stateReceivers) { - if (receiver[StateReceiverProps].indexOf('fullscreen') >= 0) { - receiver.fullscreen = hasValue; - } - } - } else if (attrName === Attribute.DEVICE_TYPE) { - toggleAttribute(this, Attribute.MOBILE, newValue === 'mobile'); - toggleAttribute(this, Attribute.TV, newValue === 'tv'); - window.removeEventListener('keydown', this._onTvKeyDown); - if (newValue === 'tv') { - window.addEventListener('keydown', this._onTvKeyDown); - } - for (const receiver of this._stateReceivers) { - if (receiver[StateReceiverProps].indexOf('deviceType') >= 0) { - receiver.deviceType = newValue; - } - } - } else if (attrName === Attribute.STREAM_TYPE) { - for (const receiver of this._stateReceivers) { - if (receiver[StateReceiverProps].indexOf('streamType') >= 0) { - receiver.streamType = newValue; - } - } - const streamTypeChangeEvent: StreamTypeChangeEvent = createCustomEvent(STREAM_TYPE_CHANGE_EVENT, { - bubbles: true, - composed: true, - detail: { streamType: newValue } - }); - this.dispatchEvent(streamTypeChangeEvent); - } else if (attrName === Attribute.FLUID) { - this._updateAspectRatio(); - } else if (attrName === Attribute.USER_IDLE) { - this._updateTextTrackMargins(); - } else if (attrName === Attribute.PAUSED || attrName === Attribute.CASTING) { - this._updateTextTrackMargins(); - this.updateUserIdle_(); - } else if (attrName === Attribute.DVR_THRESHOLD) { - this._updateStreamType(); - } - if (UIContainer.observedAttributes.indexOf(attrName as Attribute) >= 0) { - shadyCss.styleSubtree(this); - } - } - private readonly _onMutation = (mutations: MutationRecord[]): void => { + const playerElement = this._playerRef.value; + if (!playerElement) return; for (const mutation of mutations) { if (mutation.type === 'childList') { const { addedNodes, removedNodes } = mutation; for (let i = 0; i < addedNodes.length; i++) { const node = addedNodes[i]; if (isElement(node)) { - void forEachStateReceiverElement(node, this._playerEl, this.registerStateReceiver_); + void forEachStateReceiverElement(node, playerElement, this.registerStateReceiver_); } } for (let i = 0; i < removedNodes.length; i++) { const node = removedNodes[i]; if (isElement(node)) { - void forEachStateReceiverElement(node, this._playerEl, this.unregisterStateReceiver_); + void forEachStateReceiverElement(node, playerElement, this.unregisterStateReceiver_); } } } @@ -589,7 +597,9 @@ export class UIContainer extends HTMLElement { }; private readonly _onSlotChange = (): void => { - void forEachStateReceiverElement(this, this._playerEl, this.registerStateReceiver_); + if (this._playerRef.value) { + void forEachStateReceiverElement(this, this._playerRef.value, this.registerStateReceiver_); + } this._updateTextTrackMargins(); }; @@ -657,9 +667,12 @@ export class UIContainer extends HTMLElement { } } + @property({ reflect: true, state: true, type: Boolean, attribute: Attribute.MENU_OPENED }) + private accessor _menuOpened: boolean = false; + private openMenu_(menuToOpen: string, opener: HTMLElement | undefined): void { - const topChromeRect = Rectangle.fromRect(this._topChromeEl.getBoundingClientRect()); - const bottomChromeRect = Rectangle.fromRect(this._bottomChromeEl.getBoundingClientRect()); + const topChromeRect = Rectangle.fromRect(this._topChromeRef.value!.getBoundingClientRect()); + const bottomChromeRect = Rectangle.fromRect(this._bottomChromeRef.value!.getBoundingClientRect()); // Open menu in same quadrant as its opener // If there's no opener, open in bottom right corner by default @@ -674,7 +687,7 @@ export class UIContainer extends HTMLElement { } } - this._menuGroup.openMenu(menuToOpen, opener); + this._menuGroupRef.value!.openMenu(menuToOpen, opener); this._menuOpener = opener; const props = { @@ -690,7 +703,7 @@ export class UIContainer extends HTMLElement { private closeMenu_(): void { // Menu group might not be upgraded yet - this._menuGroup.closeMenu?.(); + this._menuGroupRef.value?.closeMenu?.(); this._menuOpener?.focus(); this._menuOpener = undefined; } @@ -699,12 +712,14 @@ export class UIContainer extends HTMLElement { const event = rawEvent as ToggleMenuEvent; event.stopPropagation(); const menuId = event.detail.menu; - if (!this._menuGroup.getMenuById(menuId)) { + const menuGroup = this._menuGroupRef.value; + if (!menuGroup) return; + if (!menuGroup.getMenuById(menuId)) { console.error(`: cannot find menu with ID "${menuId}"`); return; } const opener = isHTMLElement(event.target) ? event.target : undefined; - if (this._menuGroup.isMenuOpen(menuId)) { + if (menuGroup.isMenuOpen(menuId)) { this.closeMenu_(); } else { // Always close the previous menu first @@ -719,14 +734,15 @@ export class UIContainer extends HTMLElement { }; private readonly _onMenuChange = (): void => { - this._menuEl.removeEventListener('pointerdown', this._onMenuPointerDown); - this._menuEl.removeEventListener('click', this._onMenuClick); - if (this._menuGroup.hasCurrentMenu()) { - this._menuEl.addEventListener('pointerdown', this._onMenuPointerDown); - this._menuEl.addEventListener('click', this._onMenuClick); - this.setAttribute(Attribute.MENU_OPENED, ''); + const menuEl = this._menuRef.value!; + menuEl.removeEventListener('pointerdown', this._onMenuPointerDown); + menuEl.removeEventListener('click', this._onMenuClick); + if (this._menuGroupRef.value!.hasCurrentMenu()) { + menuEl.addEventListener('pointerdown', this._onMenuPointerDown); + menuEl.addEventListener('click', this._onMenuClick); + this._menuOpened = true; } else { - this.removeAttribute(Attribute.MENU_OPENED); + this._menuOpened = false; } this.updateUserIdle_(); }; @@ -739,9 +755,9 @@ export class UIContainer extends HTMLElement { // If the browser doesn't support yet `pointerType` on `click` events, // we use the type from the previous `pointerdown` event. const pointerType = (event as PointerEvent).pointerType ?? this._pointerType; - if (event.target === this._menuEl && pointerType === 'mouse') { + if (event.target === this._menuRef.value && pointerType === 'mouse') { // Close menu when clicking (with mouse) on menu backdrop - if (this._menuGroup.closeCurrentMenu()) { + if (this._menuGroupRef.value!.closeCurrentMenu()) { event.preventDefault(); } } @@ -760,8 +776,8 @@ export class UIContainer extends HTMLElement { } } else if (this._player && this._player.presentation.supportsMode('fullscreen')) { this._player.presentation.requestMode('fullscreen'); - } else if (!this.hasAttribute(Attribute.FULLWINDOW)) { - toggleAttribute(this, Attribute.FULLWINDOW, true); + } else if (!this._isFullWindow) { + this._isFullWindow = true; document.documentElement.classList.add(FULL_WINDOW_ROOT_CLASS); window.addEventListener('keydown', this._exitFullscreenOnEsc); this._onFullscreenChange(); @@ -780,8 +796,8 @@ export class UIContainer extends HTMLElement { if (this._player && this._player.presentation.currentMode === 'fullscreen') { this._player.presentation.requestMode('inline'); } - if (this.hasAttribute(Attribute.FULLWINDOW)) { - toggleAttribute(this, Attribute.FULLWINDOW, false); + if (this._isFullWindow) { + this._isFullWindow = false; document.documentElement.classList.remove(FULL_WINDOW_ROOT_CLASS); window.removeEventListener('keydown', this._exitFullscreenOnEsc); this._onFullscreenChange(); @@ -802,10 +818,10 @@ export class UIContainer extends HTMLElement { if (!isFullscreen && this._player !== undefined && this._player.presentation.currentMode === 'fullscreen') { isFullscreen = true; } - if (this.hasAttribute(Attribute.FULLWINDOW)) { + if (this._isFullWindow) { isFullscreen = true; } - toggleAttribute(this, Attribute.FULLSCREEN, isFullscreen); + this.fullscreen = isFullscreen; }; private readonly _exitFullscreenOnEsc = (event: KeyboardEvent): void => { @@ -834,7 +850,7 @@ export class UIContainer extends HTMLElement { private readonly _updateError = (): void => { const error = this._player?.errorObject; - toggleAttribute(this, Attribute.HAS_ERROR, error !== undefined); + this._hasError = error !== undefined; for (const receiver of this._stateReceivers) { if (receiver[StateReceiverProps].indexOf('error') >= 0) { receiver.error = error; @@ -843,25 +859,23 @@ export class UIContainer extends HTMLElement { }; private readonly _onPlay = (): void => { - this.setAttribute(Attribute.HAS_FIRST_PLAY, ''); - this.removeAttribute(Attribute.PAUSED); + this._hasFirstPlay = true; + this.paused = false; this._updateEnded(); }; private readonly _onPause = (): void => { - this.setAttribute(Attribute.PAUSED, ''); + this.paused = true; this._updateEnded(); }; private readonly _updatePausedAndEnded = (): void => { - const paused = this._player ? this._player.paused : true; - toggleAttribute(this, Attribute.PAUSED, paused); + this.paused = this._player ? this._player.paused : true; this._updateEnded(); }; private readonly _updateEnded = (): void => { - const ended = this._player ? this._player.ended : false; - toggleAttribute(this, Attribute.ENDED, ended); + this.ended = this._player ? this._player.ended : false; }; private readonly _updateStreamType = (): void => { @@ -967,23 +981,22 @@ export class UIContainer extends HTMLElement { }; private readonly _updateCasting = (): void => { - const casting = this._player?.cast?.casting ?? false; - toggleAttribute(this, Attribute.CASTING, casting); + this.casting = this._player?.cast?.casting ?? false; }; private readonly _updatePlayingAd = (): void => { - const playingAd = this._player?.ads?.playing ?? false; - toggleAttribute(this, Attribute.PLAYING_AD, playingAd); + this._isPlayingAd = this._player?.ads?.playing ?? false; }; private readonly _onSourceChange = (): void => { this.closeMenu_(); - const isPlaying = this._player !== undefined && !this._player.paused; - toggleAttribute(this, Attribute.HAS_FIRST_PLAY, isPlaying); + this._hasFirstPlay = this._player !== undefined && !this._player.paused; }; private isUserIdle_(): boolean { - return !this._isUserActive && !this.paused && !this.casting && !this._menuGroup.hasCurrentMenu(); + return ( + !this._isUserActive && !this.paused && !this.casting && (this._menuGroupRef.value ? !this._menuGroupRef.value.hasCurrentMenu() : false) + ); } private setUserActive_(): void { @@ -1030,7 +1043,7 @@ export class UIContainer extends HTMLElement { } private isPlayerOrMedia_(node: Node): boolean { - return node === this || this._playerEl.contains(node); + return node === this || this._playerRef.value!.contains(node); } private readonly _onTvKeyDown = (event: KeyboardEvent): void => { @@ -1130,8 +1143,8 @@ export class UIContainer extends HTMLElement { if (player === undefined) { return; } - const topChromeRect = getVisibleRect(this._topChromeSlot); - const bottomChromeRect = getVisibleRect(this._bottomChromeSlot); + const topChromeRect = this._topChromeSlotRef.value && getVisibleRect(this._topChromeSlotRef.value); + const bottomChromeRect = this._bottomChromeSlotRef.value && getVisibleRect(this._bottomChromeSlotRef.value); player.textTrackStyle.marginTop = topChromeRect?.height; player.textTrackStyle.marginBottom = bottomChromeRect?.height; }; @@ -1196,9 +1209,56 @@ export class UIContainer extends HTMLElement { this.propagatePlayerToAllReceivers_(); } }; -} -customElements.define('theoplayer-ui', UIContainer); + protected override render(): HTMLTemplateResult { + return html` +
+
+ +
+
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+ + + +
+
+ +
+ `; + } +} declare global { interface HTMLElementTagNameMap { From 70cc61c9b823a2fc7566949f6fb4bce784028342 Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Fri, 25 Apr 2025 17:33:31 +0200 Subject: [PATCH 065/123] Fix icon on LiveButton --- src/components/LiveButton.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/components/LiveButton.ts b/src/components/LiveButton.ts index 9d8459dc..7de13c99 100644 --- a/src/components/LiveButton.ts +++ b/src/components/LiveButton.ts @@ -1,3 +1,6 @@ +import { html, type HTMLTemplateResult } from 'lit'; +import { customElement, property } from 'lit/decorators.js'; +import { unsafeSVG } from 'lit/directives/unsafe-svg.js'; import { Button } from './Button'; import type { ChromelessPlayer } from 'theoplayer/chromeless'; import liveButtonCss from './LiveButton.css'; @@ -5,8 +8,6 @@ import liveIcon from '../icons/live.svg'; import { stateReceiver } from './StateReceiverMixin'; import { Attribute } from '../util/Attribute'; import type { StreamType } from '../util/StreamType'; -import { html, type HTMLTemplateResult } from 'lit'; -import { customElement, property } from 'lit/decorators.js'; const PAUSED_EVENTS = ['play', 'pause', 'playing', 'emptied'] as const; const LIVE_EVENTS = ['seeking', 'seeked', 'timeupdate', 'durationchange', 'emptied'] as const; @@ -100,7 +101,7 @@ export class LiveButton extends Button { } protected override render(): HTMLTemplateResult { - return html`${liveIcon}${unsafeSVG(liveIcon)} LIVE`; } } From 0ccc4ac00ce7151a91e9cce05c3c8038837e516a Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Fri, 25 Apr 2025 17:34:25 +0200 Subject: [PATCH 066/123] Clean up --- src/components/ChromecastDisplay.ts | 11 ----------- src/components/MenuButton.ts | 5 +---- 2 files changed, 1 insertion(+), 15 deletions(-) diff --git a/src/components/ChromecastDisplay.ts b/src/components/ChromecastDisplay.ts index fd0f32e0..bfcdec14 100644 --- a/src/components/ChromecastDisplay.ts +++ b/src/components/ChromecastDisplay.ts @@ -6,17 +6,6 @@ import chromecastIcon from '../icons/chromecast-48px.svg'; import { stateReceiver } from './StateReceiverMixin'; import type { Chromecast, ChromelessPlayer } from 'theoplayer/chromeless'; import { Attribute } from '../util/Attribute'; -import { createTemplate } from '../util/TemplateUtils'; - -const template = createTemplate( - 'theoplayer-chromecast-display', - - `
${chromecastIcon}
` + - `
` + - `

Playing on

` + - `

Chromecast Receiver

` + - `
` -); const CAST_EVENTS = ['statechange'] as const; diff --git a/src/components/MenuButton.ts b/src/components/MenuButton.ts index 3ee8a195..36e4d616 100644 --- a/src/components/MenuButton.ts +++ b/src/components/MenuButton.ts @@ -1,12 +1,9 @@ -import { Button, buttonTemplate } from './Button'; +import { Button } from './Button'; import { createCustomEvent } from '../util/EventUtils'; import { TOGGLE_MENU_EVENT, type ToggleMenuEvent } from '../events/ToggleMenuEvent'; import { Attribute } from '../util/Attribute'; -import { createTemplate } from '../util/TemplateUtils'; import { customElement, property } from 'lit/decorators.js'; -const template = createTemplate('theoplayer-menu-button', buttonTemplate(``)); - /** * `` - A menu button that opens a {@link Menu}. * From ac9521e0dd27a11a9a04f4d055339d8eed86ffcd Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Fri, 25 Apr 2025 18:08:10 +0200 Subject: [PATCH 067/123] Rework DefaultUI --- src/DefaultUI.html | 68 --------- src/DefaultUI.ts | 359 ++++++++++++++++++++++++--------------------- src/UIContainer.ts | 8 +- 3 files changed, 194 insertions(+), 241 deletions(-) delete mode 100644 src/DefaultUI.html diff --git a/src/DefaultUI.html b/src/DefaultUI.html deleted file mode 100644 index 5309b102..00000000 --- a/src/DefaultUI.html +++ /dev/null @@ -1,68 +0,0 @@ - - -
- -
- - - - - - - -
- -
- - - - - -
-
- -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - -
diff --git a/src/DefaultUI.ts b/src/DefaultUI.ts index 1a5ec909..682bf86b 100644 --- a/src/DefaultUI.ts +++ b/src/DefaultUI.ts @@ -1,23 +1,20 @@ -import * as shadyCss from '@webcomponents/shadycss'; +import { html, type HTMLTemplateResult, LitElement } from 'lit'; +import { customElement, property, queryAssignedNodes, state } from 'lit/decorators.js'; +import { createRef, type Ref } from 'lit/directives/ref.js'; import type { ChromelessPlayer, SourceDescription, UIPlayerConfiguration } from 'theoplayer/chromeless'; -import type { UIContainer } from './UIContainer'; +import { DEFAULT_DVR_THRESHOLD, DEFAULT_TV_USER_IDLE_TIMEOUT, DEFAULT_USER_IDLE_TIMEOUT, type UIContainer } from './UIContainer'; import defaultUiCss from './DefaultUI.css'; -import defaultUiHtml from './DefaultUI.html'; import { Attribute } from './util/Attribute'; import { applyExtensions } from './extensions/ExtensionRegistry'; import { isMobile, isTv } from './util/Environment'; import type { DeviceType } from './util/DeviceType'; import type { StreamType } from './util/StreamType'; -import type { TimeRange } from './components/TimeRange'; -import { STREAM_TYPE_CHANGE_EVENT } from './events/StreamTypeChangeEvent'; import { USER_IDLE_CHANGE_EVENT } from './events/UserIdleChangeEvent'; import { READY_EVENT } from './events/ReadyEvent'; import { ACCIDENTAL_CLICK_DELAY } from './util/Constants'; import { toggleAttribute } from './util/CommonUtils'; import { createCustomEvent } from './util/EventUtils'; -import { createTemplate } from './util/TemplateUtils'; - -const template = createTemplate('theoplayer-default-ui', `${defaultUiHtml}`); +import { classMap } from 'lit/directives/class-map.js'; /** * `` - A default UI for THEOplayer. @@ -69,7 +66,14 @@ const template = createTemplate('theoplayer-default-ui', `${html}`); +import { Attribute } from './util/Attribute'; /** * `` - A default UI for THEOlive. * * @group Components */ +@customElement('theolive-default-ui') export class THEOliveDefaultUI extends DefaultUI { - private readonly _loading: HTMLParagraphElement; - private readonly _offline: HTMLParagraphElement; - private readonly _announcement: HTMLParagraphElement; - private readonly _errorDisplay: ErrorDisplay; - private readonly _playButton: PlayButton; - private readonly _root: HTMLElement; + static override styles = [css]; + + @state() + private accessor _announcementType: 'loading' | 'offline' | 'announcement' | '' = ''; + @state() + private accessor _announcementMessage: string = ''; + @state() + private accessor _hidePlayButton: boolean = false; + @state() + private accessor _hideErrorDisplay: boolean = false; constructor(configuration: UIPlayerConfiguration = {}) { super(configuration); - this._loading = this._shadowRoot.querySelector('#loading-announcement')!; - this._offline = this._shadowRoot.querySelector('#offline-announcement')!; - this._announcement = this._shadowRoot.querySelector('#announcement')!; - this._errorDisplay = this._shadowRoot.querySelector('theoplayer-error-display')!; - this._playButton = this._shadowRoot.querySelector('theoplayer-play-button')!; - this._root = this._shadowRoot.querySelector('theoplayer-ui')!; - - this._ui.addEventListener(READY_EVENT, this.onReady); } - protected initShadowRoot(): ShadowRoot { - const shadowRoot = this.attachShadow({ mode: 'open', delegatesFocus: true }); - shadowRoot.appendChild(template().content.cloneNode(true)); - return shadowRoot; - } - - private readonly onReady = () => { - this._ui.removeEventListener(READY_EVENT, this.onReady); + protected override _onUiReady() { + super._onUiReady(); const player = this.player; if (player) { player.theoLive?.addEventListener(['distributionloadstart', 'publicationloadstart' as never], this.onLoadChannelStart); @@ -50,7 +39,7 @@ export class THEOliveDefaultUI extends DefaultUI { player.theoLive?.addEventListener(['endpointloaded', 'publicationloaded' as never], this.onChannelLoaded); player.addEventListener('error', this.onError); } - }; + } private onLoadChannelStart = () => { this.showMessage_('loading', undefined); @@ -76,48 +65,79 @@ export class THEOliveDefaultUI extends DefaultUI { }; private hidePlayerError(): void { - this._root.removeAttribute('has-error'); - this._errorDisplay.style.display = 'none'; + this._uiRef.value?.removeAttribute(Attribute.HAS_ERROR); + this._hideErrorDisplay = true; } private stopHidingPlayerError(): void { - this._root.setAttribute('has-error', ''); - this._errorDisplay.style.display = 'flex'; + this._uiRef.value?.setAttribute(Attribute.HAS_ERROR, ''); + this._hideErrorDisplay = false; } private hidePlayerPlayButton_(): void { - this._playButton.style.display = 'none'; + this._hidePlayButton = true; } private stopHidingPlayerPlayButton(): void { - this._playButton.style.display = 'inline-flex'; + this._hidePlayButton = false; } private showMessage_(type: 'offline' | 'loading' | 'announcement', text: string | undefined): void { this.hidePlayerError(); - this._loading.style.display = 'none'; - this._offline.style.display = 'none'; - this._announcement.style.display = 'none'; - if (type === 'loading') { - this._loading.style.display = 'block'; - } else if (type === 'offline') { - this._offline.style.display = 'block'; - } else { - this._announcement.textContent = text ?? ''; - this._announcement.style.display = 'block'; - } + this._announcementType = type; + this._announcementMessage = text ?? ''; this.hidePlayerPlayButton_(); } private hideMessage_(): void { - this._loading.style.display = 'none'; - this._offline.style.display = 'none'; - this._announcement.style.display = 'none'; + this._announcementType = ''; + this._announcementMessage = ''; this.stopHidingPlayerPlayButton(); } -} -customElements.define('theolive-default-ui', THEOliveDefaultUI); + protected override renderUiContent(): HTMLTemplateResult { + const loadingStyles = { display: this._announcementType === 'loading' ? '' : 'none' }; + const offlineStyles = { display: this._announcementType === 'offline' ? '' : 'none' }; + const announcementStyles = { display: this._announcementType === 'announcement' ? '' : 'none' }; + const playButtonStyles = { display: this._hidePlayButton ? 'none' : '' }; + const errorDisplayStyles = { display: this._hideErrorDisplay ? 'none' : '' }; + return html` +

+ Loading... +

+

+ The live stream hasn't started yet +

+

${this._announcementMessage}

+ +
+ +
+
+ + + + + + + + + + +
+ + + + `; + } +} declare global { interface HTMLElementTagNameMap { From fc8fc0c22aff403e411778ec1632809d10208e3e Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Fri, 25 Apr 2025 18:32:08 +0200 Subject: [PATCH 069/123] Fix missing ref --- src/DefaultUI.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/DefaultUI.ts b/src/DefaultUI.ts index fd8d7a06..3fd5be7d 100644 --- a/src/DefaultUI.ts +++ b/src/DefaultUI.ts @@ -1,6 +1,6 @@ import { html, type HTMLTemplateResult, LitElement } from 'lit'; import { customElement, property, queryAssignedNodes, state } from 'lit/decorators.js'; -import { createRef, type Ref } from 'lit/directives/ref.js'; +import { createRef, ref, type Ref } from 'lit/directives/ref.js'; import type { ChromelessPlayer, SourceDescription, UIPlayerConfiguration } from 'theoplayer/chromeless'; import { DEFAULT_DVR_THRESHOLD, DEFAULT_TV_USER_IDLE_TIMEOUT, DEFAULT_USER_IDLE_TIMEOUT, type UIContainer } from './UIContainer'; import defaultUiCss from './DefaultUI.css'; @@ -290,6 +290,7 @@ export class DefaultUI extends LitElement { protected override render(): HTMLTemplateResult { return html` Date: Fri, 25 Apr 2025 18:41:59 +0200 Subject: [PATCH 070/123] Set ui.source only once --- src/DefaultUI.ts | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/src/DefaultUI.ts b/src/DefaultUI.ts index 3fd5be7d..8037c422 100644 --- a/src/DefaultUI.ts +++ b/src/DefaultUI.ts @@ -88,6 +88,7 @@ export class DefaultUI extends LitElement { private _appliedExtensions: boolean = false; private _configuration: UIPlayerConfiguration = {}; + private _source: SourceDescription | undefined = undefined; private _userIdleTimeout: number | undefined = undefined; private _deviceType: DeviceType = 'desktop'; private _dvrThreshold: number = DEFAULT_DVR_THRESHOLD; @@ -148,6 +149,10 @@ export class DefaultUI extends LitElement { /** * The player's current source. */ + get source() { + return this._uiRef.value ? this._uiRef.value.source : this._source; + } + @property({ reflect: false, attribute: Attribute.SOURCE, @@ -155,7 +160,14 @@ export class DefaultUI extends LitElement { fromAttribute: (value: string | null) => (value ? (JSON.parse(value) as SourceDescription) : undefined) } }) - accessor source: SourceDescription | undefined = undefined; + set source(source: SourceDescription | undefined) { + if (this._uiRef.value) { + this._source = undefined; + this._uiRef.value.source = source; + } else { + this._source = source; + } + } /** * Whether to automatically adjusts the player's height to fit the video's aspect ratio. @@ -257,6 +269,13 @@ export class DefaultUI extends LitElement { clearTimeout(this._timeRangeInertTimeout); } + protected override firstUpdated() { + if (this._source) { + this._uiRef.value!.source = this._source; + this._source = undefined; + } + } + protected _onUiReady(): void { this.dispatchEvent(createCustomEvent(READY_EVENT)); } @@ -292,7 +311,6 @@ export class DefaultUI extends LitElement { return html` Date: Fri, 25 Apr 2025 18:44:11 +0200 Subject: [PATCH 071/123] Simplify --- src/THEOliveDefaultUI.ts | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/src/THEOliveDefaultUI.ts b/src/THEOliveDefaultUI.ts index 97479965..25239f9a 100644 --- a/src/THEOliveDefaultUI.ts +++ b/src/THEOliveDefaultUI.ts @@ -74,25 +74,17 @@ export class THEOliveDefaultUI extends DefaultUI { this._hideErrorDisplay = false; } - private hidePlayerPlayButton_(): void { - this._hidePlayButton = true; - } - - private stopHidingPlayerPlayButton(): void { - this._hidePlayButton = false; - } - private showMessage_(type: 'offline' | 'loading' | 'announcement', text: string | undefined): void { this.hidePlayerError(); this._announcementType = type; this._announcementMessage = text ?? ''; - this.hidePlayerPlayButton_(); + this._hidePlayButton = true; } private hideMessage_(): void { this._announcementType = ''; this._announcementMessage = ''; - this.stopHidingPlayerPlayButton(); + this._hidePlayButton = false; } protected override renderUiContent(): HTMLTemplateResult { From a166ef3e4d7b9ed3fd9a698b7cd7c0ba1b3fe6e5 Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Fri, 25 Apr 2025 18:45:46 +0200 Subject: [PATCH 072/123] Remove createTemplate --- rollup.config.mjs | 4 +--- src/util/TemplateUtils.ts | 14 -------------- 2 files changed, 1 insertion(+), 17 deletions(-) delete mode 100644 src/util/TemplateUtils.ts diff --git a/rollup.config.mjs b/rollup.config.mjs index 664ed680..54e8dc54 100644 --- a/rollup.config.mjs +++ b/rollup.config.mjs @@ -142,9 +142,7 @@ function jsPlugins({ es5 = false, node = false, module = false, production = fal virtual({ include: './src/**', // Remove THEOplayer altogether. - [theoplayerModule]: `export const ChromelessPlayer = undefined;`, - // Remove createTemplate() helper. - ['./src/util/TemplateUtils']: `export function createTemplate() { return () => undefined; }` + [theoplayerModule]: `export const ChromelessPlayer = undefined;` }), json(), // Run PostCSS on .css files. diff --git a/src/util/TemplateUtils.ts b/src/util/TemplateUtils.ts deleted file mode 100644 index e91ff5de..00000000 --- a/src/util/TemplateUtils.ts +++ /dev/null @@ -1,14 +0,0 @@ -import * as shadyCss from '@webcomponents/shadycss'; - -export function createTemplate(customElementName: string, contents: string): () => HTMLTemplateElement { - let template: HTMLTemplateElement | undefined; - return () => { - if (template === undefined) { - template = document.createElement('template'); - template.innerHTML = contents; - contents = undefined!; - shadyCss.prepareTemplate(template, customElementName); - } - return template; - }; -} From 7805bdf03cc126d7e90013d9436e5bfd5ea7b072 Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Fri, 25 Apr 2025 18:47:42 +0200 Subject: [PATCH 073/123] Export stateReceiver decorator --- src/components/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/index.ts b/src/components/index.ts index 8b2f92a7..8686faad 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -47,4 +47,4 @@ export * from './PreviewThumbnail'; export * from './LiveButton'; export { SlotContainer } from './SlotContainer'; export * from './ads/index'; -export { type StateReceiverElement, type StateReceiverPropertyMap, StateReceiverMixin } from './StateReceiverMixin'; +export { type StateReceiverElement, type StateReceiverPropertyMap, StateReceiverMixin, stateReceiver } from './StateReceiverMixin'; From 7976b77c9e6b62175b958b2b3bc6a7627a39267c Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Fri, 25 Apr 2025 19:03:25 +0200 Subject: [PATCH 074/123] Simplify --- src/DefaultUI.ts | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/DefaultUI.ts b/src/DefaultUI.ts index 8037c422..b2d77c9a 100644 --- a/src/DefaultUI.ts +++ b/src/DefaultUI.ts @@ -1,5 +1,5 @@ import { html, type HTMLTemplateResult, LitElement } from 'lit'; -import { customElement, property, queryAssignedNodes, state } from 'lit/decorators.js'; +import { customElement, property, queryAssignedNodes } from 'lit/decorators.js'; import { createRef, ref, type Ref } from 'lit/directives/ref.js'; import type { ChromelessPlayer, SourceDescription, UIPlayerConfiguration } from 'theoplayer/chromeless'; import { DEFAULT_DVR_THRESHOLD, DEFAULT_TV_USER_IDLE_TIMEOUT, DEFAULT_USER_IDLE_TIMEOUT, type UIContainer } from './UIContainer'; @@ -247,9 +247,6 @@ export class DefaultUI extends LitElement { @property({ reflect: true, type: Boolean, attribute: Attribute.HAS_TITLE }) private accessor _hasTitle: boolean = false; - @state() - private accessor _timeRangeHidden: boolean = false; - connectedCallback(): void { super.connectedCallback(); @@ -284,8 +281,6 @@ export class DefaultUI extends LitElement { if (this._uiRef.value) { this.streamType = this._uiRef.value.streamType; } - // Hide seekbar when stream is live with no DVR - this._timeRangeHidden = this.streamType === 'live'; }; private readonly _updateUserIdle = () => { @@ -377,7 +372,8 @@ export class DefaultUI extends LitElement { .inert=${this._timeRangeInert} class=${classMap({ 'theoplayer-ad-control': true, - [Attribute.HIDDEN]: this._timeRangeHidden + // Hide seekbar when stream is live with no DVR + [Attribute.HIDDEN]: this.streamType === 'live' })} > From 5ee299d89d9399260f47f43deb4bc106b382826e Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Fri, 25 Apr 2025 19:07:34 +0200 Subject: [PATCH 075/123] Remove has-title attribute --- src/DefaultUI.css | 4 ---- src/DefaultUI.ts | 6 +++--- src/util/Attribute.ts | 1 - 3 files changed, 3 insertions(+), 8 deletions(-) diff --git a/src/DefaultUI.css b/src/DefaultUI.css index 7c8e80e4..3192d960 100644 --- a/src/DefaultUI.css +++ b/src/DefaultUI.css @@ -34,10 +34,6 @@ theoplayer-ui { line-height: var(--theoplayer-text-content-height, var(--theoplayer-control-height, 24px)); } -:host(:not([has-title])) [part='title'] { - display: none; -} - [part='centered-chrome'] { display: flex; } diff --git a/src/DefaultUI.ts b/src/DefaultUI.ts index b2d77c9a..ecee0fd6 100644 --- a/src/DefaultUI.ts +++ b/src/DefaultUI.ts @@ -1,5 +1,5 @@ import { html, type HTMLTemplateResult, LitElement } from 'lit'; -import { customElement, property, queryAssignedNodes } from 'lit/decorators.js'; +import { customElement, property, queryAssignedNodes, state } from 'lit/decorators.js'; import { createRef, ref, type Ref } from 'lit/directives/ref.js'; import type { ChromelessPlayer, SourceDescription, UIPlayerConfiguration } from 'theoplayer/chromeless'; import { DEFAULT_DVR_THRESHOLD, DEFAULT_TV_USER_IDLE_TIMEOUT, DEFAULT_USER_IDLE_TIMEOUT, type UIContainer } from './UIContainer'; @@ -244,7 +244,7 @@ export class DefaultUI extends LitElement { this._dvrThreshold = isNaN(value) ? 0 : value; } - @property({ reflect: true, type: Boolean, attribute: Attribute.HAS_TITLE }) + @state() private accessor _hasTitle: boolean = false; connectedCallback(): void { @@ -323,7 +323,7 @@ export class DefaultUI extends LitElement { protected renderUiContent(): HTMLTemplateResult { return html` -
+
diff --git a/src/util/Attribute.ts b/src/util/Attribute.ts index 420fc77c..f97f8fa7 100644 --- a/src/util/Attribute.ts +++ b/src/util/Attribute.ts @@ -45,7 +45,6 @@ export enum Attribute { HAS_SUBTITLES = 'has-subtitles', HAS_ERROR = 'has-error', HAS_FIRST_PLAY = 'has-first-play', - HAS_TITLE = 'has-title', CAST_STATE = 'cast-state', TRACK_TYPE = 'track-type', SHOW_OFF = 'show-off', From 990dbebed8b7f7fa823a123ef7a4fd489a0c8b82 Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Fri, 25 Apr 2025 19:17:05 +0200 Subject: [PATCH 076/123] Fix elements not correctly hidden --- src/DefaultUI.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/DefaultUI.ts b/src/DefaultUI.ts index ecee0fd6..acc52b84 100644 --- a/src/DefaultUI.ts +++ b/src/DefaultUI.ts @@ -1,6 +1,7 @@ import { html, type HTMLTemplateResult, LitElement } from 'lit'; import { customElement, property, queryAssignedNodes, state } from 'lit/decorators.js'; import { createRef, ref, type Ref } from 'lit/directives/ref.js'; +import { styleMap } from 'lit/directives/style-map.js'; import type { ChromelessPlayer, SourceDescription, UIPlayerConfiguration } from 'theoplayer/chromeless'; import { DEFAULT_DVR_THRESHOLD, DEFAULT_TV_USER_IDLE_TIMEOUT, DEFAULT_USER_IDLE_TIMEOUT, type UIContainer } from './UIContainer'; import defaultUiCss from './DefaultUI.css'; @@ -14,7 +15,6 @@ import { READY_EVENT } from './events/ReadyEvent'; import { ACCIDENTAL_CLICK_DELAY } from './util/Constants'; import { toggleAttribute } from './util/CommonUtils'; import { createCustomEvent } from './util/EventUtils'; -import { classMap } from 'lit/directives/class-map.js'; /** * `` - A default UI for THEOplayer. @@ -323,7 +323,7 @@ export class DefaultUI extends LitElement { protected renderUiContent(): HTMLTemplateResult { return html` -
+
@@ -370,10 +370,10 @@ export class DefaultUI extends LitElement { show-ad-markers tv-focus .inert=${this._timeRangeInert} - class=${classMap({ - 'theoplayer-ad-control': true, + class="theoplayer-ad-control" + style=${styleMap({ // Hide seekbar when stream is live with no DVR - [Attribute.HIDDEN]: this.streamType === 'live' + display: this.streamType === 'live' ? 'none' : '' })} > From 96a66fef60842bf43cb823e5fa43e5d90b8e4f61 Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Fri, 25 Apr 2025 19:25:14 +0200 Subject: [PATCH 077/123] Fix user-idle-timeout attribute added unnecessarily --- src/DefaultUI.ts | 2 +- src/UIContainer.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/DefaultUI.ts b/src/DefaultUI.ts index acc52b84..27b38c18 100644 --- a/src/DefaultUI.ts +++ b/src/DefaultUI.ts @@ -211,7 +211,7 @@ export class DefaultUI extends LitElement { return this._userIdleTimeout ?? (this.deviceType === 'tv' ? DEFAULT_TV_USER_IDLE_TIMEOUT : DEFAULT_USER_IDLE_TIMEOUT); } - @property({ reflect: true, type: Number, attribute: Attribute.USER_IDLE_TIMEOUT }) + @property({ reflect: true, type: Number, attribute: Attribute.USER_IDLE_TIMEOUT, useDefault: true }) set userIdleTimeout(value: number | undefined) { this._userIdleTimeout = value === undefined || isNaN(value) ? undefined : value; } diff --git a/src/UIContainer.ts b/src/UIContainer.ts index d4837867..83dcbf4a 100644 --- a/src/UIContainer.ts +++ b/src/UIContainer.ts @@ -365,7 +365,7 @@ export class UIContainer extends LitElement { return this._userIdleTimeout ?? (this.deviceType === 'tv' ? DEFAULT_TV_USER_IDLE_TIMEOUT : DEFAULT_USER_IDLE_TIMEOUT); } - @property({ reflect: true, type: Number, attribute: Attribute.USER_IDLE_TIMEOUT }) + @property({ reflect: true, type: Number, attribute: Attribute.USER_IDLE_TIMEOUT, useDefault: true }) set userIdleTimeout(value: number | undefined) { this._userIdleTimeout = value === undefined || isNaN(value) ? undefined : value; } From 74d6b9a04870c3aaa0e932377b67cd4cb7850574 Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Fri, 25 Apr 2025 19:51:57 +0200 Subject: [PATCH 078/123] Fix menu focus --- src/UIContainer.ts | 16 +++++++++++++--- src/components/MenuGroup.ts | 16 +++++++++++++++- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/src/UIContainer.ts b/src/UIContainer.ts index 83dcbf4a..2ae5a6ff 100644 --- a/src/UIContainer.ts +++ b/src/UIContainer.ts @@ -131,6 +131,7 @@ export class UIContainer extends LitElement { private readonly _playerRef: Ref = createRef(); private readonly _menuRef: Ref = createRef(); private readonly _menuGroupRef: Ref = createRef(); + private _menuOpened: boolean = false; private _menuOpener: HTMLElement | undefined; private readonly _topChromeRef: Ref = createRef(); private readonly _topChromeSlotRef: Ref = createRef(); @@ -667,8 +668,17 @@ export class UIContainer extends LitElement { } } + private get menuOpened_(): boolean { + return this._menuOpened; + } + @property({ reflect: true, state: true, type: Boolean, attribute: Attribute.MENU_OPENED }) - private accessor _menuOpened: boolean = false; + private set menuOpened_(menuOpened: boolean) { + if (this._menuOpened === menuOpened) return; + this._menuOpened = menuOpened; + // Toggle manually, so the menu layer immediately becomes visible and can receive focus. + toggleAttribute(this, Attribute.MENU_OPENED, menuOpened); + } private openMenu_(menuToOpen: string, opener: HTMLElement | undefined): void { const topChromeRect = Rectangle.fromRect(this._topChromeRef.value!.getBoundingClientRect()); @@ -740,9 +750,9 @@ export class UIContainer extends LitElement { if (this._menuGroupRef.value!.hasCurrentMenu()) { menuEl.addEventListener('pointerdown', this._onMenuPointerDown); menuEl.addEventListener('click', this._onMenuClick); - this._menuOpened = true; + this.menuOpened_ = true; } else { - this._menuOpened = false; + this.menuOpened_ = false; } this.updateUserIdle_(); }; diff --git a/src/components/MenuGroup.ts b/src/components/MenuGroup.ts index ed974352..67a09125 100644 --- a/src/components/MenuGroup.ts +++ b/src/components/MenuGroup.ts @@ -2,7 +2,16 @@ import { html, LitElement, type TemplateResult } from 'lit'; import { customElement, property, query } from 'lit/decorators.js'; import menuGroupCss from './MenuGroup.css'; import { Attribute } from '../util/Attribute'; -import { arrayFind, arrayFindIndex, fromArrayLike, getSlottedElements, isHTMLElement, noOp, upgradeCustomElementIfNeeded } from '../util/CommonUtils'; +import { + arrayFind, + arrayFindIndex, + fromArrayLike, + getSlottedElements, + isHTMLElement, + noOp, + toggleAttribute, + upgradeCustomElementIfNeeded +} from '../util/CommonUtils'; import { CLOSE_MENU_EVENT, type CloseMenuEvent } from '../events/CloseMenuEvent'; import { TOGGLE_MENU_EVENT, type ToggleMenuEvent } from '../events/ToggleMenuEvent'; import { isBackKey } from '../util/KeyCode'; @@ -29,6 +38,10 @@ interface OpenMenuEntry { @customElement('theoplayer-menu-group') export class MenuGroup extends LitElement { static styles = [menuGroupCss]; + static override shadowRootOptions = { + ...LitElement.shadowRootOptions, + delegatesFocus: true + }; private _menuOpened: boolean = false; @@ -40,6 +53,7 @@ export class MenuGroup extends LitElement { private set menuOpened_(menuOpened: boolean) { if (this._menuOpened === menuOpened) return; this._menuOpened = menuOpened; + toggleAttribute(this, Attribute.MENU_OPENED, menuOpened); if (menuOpened) { this.removeAttribute('hidden'); this.removeEventListener('keydown', this._onKeyDown); From 4180daeb1f6bb02ec480228954c94f3efb48aaba Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Fri, 25 Apr 2025 19:52:02 +0200 Subject: [PATCH 079/123] Tweaks --- src/components/GestureReceiver.ts | 2 +- src/components/MenuGroup.ts | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/GestureReceiver.ts b/src/components/GestureReceiver.ts index e0995614..0fb307f9 100644 --- a/src/components/GestureReceiver.ts +++ b/src/components/GestureReceiver.ts @@ -58,7 +58,7 @@ export class GestureReceiver extends LitElement { }; private readonly _onClick = (event: MouseEvent) => { - // If the browser doesn't support yet `pointerType` on `click` events, + // If the browser doesn't yet support `pointerType` on `click` events, // we use the type from the previous `pointerdown` event. const pointerType = (event as PointerEvent).pointerType ?? this._pointerType; if (pointerType === 'touch') { diff --git a/src/components/MenuGroup.ts b/src/components/MenuGroup.ts index 67a09125..778ff350 100644 --- a/src/components/MenuGroup.ts +++ b/src/components/MenuGroup.ts @@ -55,11 +55,11 @@ export class MenuGroup extends LitElement { this._menuOpened = menuOpened; toggleAttribute(this, Attribute.MENU_OPENED, menuOpened); if (menuOpened) { - this.removeAttribute('hidden'); + this.removeAttribute(Attribute.HIDDEN); this.removeEventListener('keydown', this._onKeyDown); this.addEventListener('keydown', this._onKeyDown); } else { - this.setAttribute('hidden', ''); + this.setAttribute(Attribute.HIDDEN, ''); this.removeEventListener('keydown', this._onKeyDown); } const changeEvent: MenuChangeEvent = createCustomEvent(MENU_CHANGE_EVENT, { bubbles: true }); @@ -75,7 +75,7 @@ export class MenuGroup extends LitElement { super.connectedCallback(); if (!this.menuOpened_) { - this.setAttribute('hidden', ''); + this.setAttribute(Attribute.HIDDEN, ''); } } From 8d42705b40d3feb1ce40014ddd48e81b8c4e2369 Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Fri, 25 Apr 2025 20:12:07 +0200 Subject: [PATCH 080/123] Simplify PlaybackRateMenu --- src/components/PlaybackRateMenu.html.ts | 10 ---------- src/components/PlaybackRateMenu.ts | 9 +++++++-- 2 files changed, 7 insertions(+), 12 deletions(-) delete mode 100644 src/components/PlaybackRateMenu.html.ts diff --git a/src/components/PlaybackRateMenu.html.ts b/src/components/PlaybackRateMenu.html.ts deleted file mode 100644 index 6a2b82bf..00000000 --- a/src/components/PlaybackRateMenu.html.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { html } from 'lit'; - -export default html` - 0.25x - 0.5x - Normal - 1.25x - 1.5x - 2x -`; diff --git a/src/components/PlaybackRateMenu.ts b/src/components/PlaybackRateMenu.ts index 79041082..53ea9b75 100644 --- a/src/components/PlaybackRateMenu.ts +++ b/src/components/PlaybackRateMenu.ts @@ -1,11 +1,12 @@ import { html, type HTMLTemplateResult } from 'lit'; import { customElement } from 'lit/decorators.js'; import { Menu } from './Menu'; -import playbackRateMenuHtml from './PlaybackRateMenu.html'; // Load components used in template import './PlaybackRateRadioGroup'; +const PLAYBACK_RATES = [0.25, 0.5, 1, 1.25, 1.5, 2]; + /** * `` - A menu to change the playback rate of the player. * @@ -20,7 +21,11 @@ export class PlaybackRateMenu extends Menu { } protected override renderMenuContent(): HTMLTemplateResult { - return playbackRateMenuHtml; + return html` + ${PLAYBACK_RATES.map( + (rate) => html`${rate === 1 ? 'Normal' : `${rate}x`}` + )} + `; } } From 7f4c3b794802b5cb62878a68a3226c1efddcc308 Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Fri, 25 Apr 2025 20:14:26 +0200 Subject: [PATCH 081/123] Fix ActiveQualityDisplay not updating --- src/components/ActiveQualityDisplay.ts | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/components/ActiveQualityDisplay.ts b/src/components/ActiveQualityDisplay.ts index c1e48f50..a57af687 100644 --- a/src/components/ActiveQualityDisplay.ts +++ b/src/components/ActiveQualityDisplay.ts @@ -12,9 +12,6 @@ import { formatQualityLabel } from '../util/TrackUtils'; @customElement('theoplayer-active-quality-display') @stateReceiver(['activeVideoQuality', 'targetVideoQualities']) export class ActiveQualityDisplay extends LitElement { - private _activeVideoQuality: VideoQuality | undefined = undefined; - private _targetVideoQualities: VideoQuality[] | undefined = undefined; - @property({ reflect: false, attribute: false }) accessor activeVideoQuality: VideoQuality | undefined = undefined; @@ -24,10 +21,10 @@ export class ActiveQualityDisplay extends LitElement { protected override render(): HTMLTemplateResult { // If no target quality is selected, or more than one target quality is selected, // treat as "automatic" quality selection. - const hasSingleTargetQuality = this._targetVideoQualities !== undefined && this._targetVideoQualities.length === 1; - const targetQuality = hasSingleTargetQuality ? this._targetVideoQualities![0] : undefined; + const hasSingleTargetQuality = this.targetVideoQualities !== undefined && this.targetVideoQualities.length === 1; + const targetQuality = hasSingleTargetQuality ? this.targetVideoQualities![0] : undefined; // Always show the target quality immediately, even if it's not the active quality yet. - const selectedQuality = targetQuality ?? this._activeVideoQuality; + const selectedQuality = targetQuality ?? this.activeVideoQuality; const qualityLabel = formatQualityLabel(selectedQuality); let label: string; if (hasSingleTargetQuality) { From 6f01c09645313366da03a8fb368ffd6e8e285d6d Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Fri, 25 Apr 2025 20:27:59 +0200 Subject: [PATCH 082/123] Restore support for passing template to constructors --- src/components/Button.ts | 26 +++++++++++++++++--------- src/components/CastButton.ts | 6 +++--- src/components/LinkButton.ts | 18 ++++++++++++++++++ src/components/Menu.ts | 27 ++++++++++++++++++++++++++- src/components/MenuGroup.ts | 26 ++++++++++++++++++++++++++ 5 files changed, 90 insertions(+), 13 deletions(-) diff --git a/src/components/Button.ts b/src/components/Button.ts index bc7e074a..b163515a 100644 --- a/src/components/Button.ts +++ b/src/components/Button.ts @@ -1,20 +1,23 @@ import { html, type HTMLTemplateResult, LitElement } from 'lit'; import { customElement, property } from 'lit/decorators.js'; +import { templateContent } from 'lit/directives/template-content.js'; import buttonCss from './Button.css'; import { Attribute } from '../util/Attribute'; import { isActivationKey } from '../util/KeyCode'; +/** @deprecated */ export interface ButtonOptions { - template: HTMLTemplateElement; + /** + * @deprecated Override {@link Button.render} instead. + */ + template?: HTMLTemplateElement; } -export function buttonTemplate(button: string, extraCss: string = ''): HTMLTemplateResult { - return html` - - ${button} - `; +/** + * @deprecated Override {@link Button.render} instead. + */ +export function buttonTemplate(button: string, extraCss: string = ''): string { + return `${button}`; } /** @@ -29,6 +32,7 @@ export function buttonTemplate(button: string, extraCss: string = ''): HTMLTempl export class Button extends LitElement { static styles = [buttonCss]; + private readonly _template: HTMLTemplateElement | undefined; private _disabled: boolean = false; /** @@ -37,8 +41,9 @@ export class Button extends LitElement { * By default, the button renders the contents of its direct children (i.e. it has a single unnamed ``). * Subclasses can override this by overriding {@link render}. */ - constructor() { + constructor(options?: ButtonOptions) { super(); + this._template = options?.template; } protected _upgradeProperty(prop: keyof this) { @@ -139,6 +144,9 @@ export class Button extends LitElement { }; protected render(): HTMLTemplateResult { + if (this._template) { + return html`${templateContent(this._template)}`; + } return html``; } diff --git a/src/components/CastButton.ts b/src/components/CastButton.ts index 1fb58c04..d09cdd60 100644 --- a/src/components/CastButton.ts +++ b/src/components/CastButton.ts @@ -1,4 +1,4 @@ -import { Button } from './Button'; +import { Button, type ButtonOptions } from './Button'; import { property } from 'lit/decorators.js'; import { Attribute } from '../util/Attribute'; import type { CastState, VendorCast } from 'theoplayer/chromeless'; @@ -13,8 +13,8 @@ export class CastButton extends Button { private _castApi: VendorCast | undefined; private _castState: CastState = 'unavailable'; - constructor() { - super(); + constructor(options?: ButtonOptions) { + super(options); this._upgradeProperty('castState'); this._upgradeProperty('castApi'); } diff --git a/src/components/LinkButton.ts b/src/components/LinkButton.ts index 0f96d7fa..21a21c2f 100644 --- a/src/components/LinkButton.ts +++ b/src/components/LinkButton.ts @@ -1,9 +1,18 @@ import { html, type HTMLTemplateResult, LitElement } from 'lit'; import { customElement, property, state } from 'lit/decorators.js'; +import { templateContent } from 'lit/directives/template-content.js'; import linkButtonCss from './LinkButton.css'; import { Attribute } from '../util/Attribute'; import { KeyCode } from '../util/KeyCode'; import { createRef, ref, type Ref } from 'lit/directives/ref.js'; +import { type ButtonOptions, buttonTemplate } from './Button'; + +/** + * @deprecated Override {@link LinkButton.render} instead. + */ +export function linkButtonTemplate(button: string, extraCss: string = ''): string { + return buttonTemplate(`${button}`, extraCss); +} /** * `` - A {@link Button | button} that opens a hyperlink. @@ -15,10 +24,16 @@ import { createRef, ref, type Ref } from 'lit/directives/ref.js'; export class LinkButton extends LitElement { static styles = [linkButtonCss]; + private readonly _template: HTMLTemplateElement | undefined; private _disabled: boolean = false; private readonly _linkRef: Ref = createRef(); + constructor(options?: ButtonOptions) { + super(); + this._template = options?.template; + } + connectedCallback(): void { super.connectedCallback(); @@ -104,6 +119,9 @@ export class LinkButton extends LitElement { }; protected override render(): HTMLTemplateResult { + if (this._template) { + return html`${templateContent(this._template)}`; + } return html`${this.renderLinkContent()}`; diff --git a/src/components/Menu.ts b/src/components/Menu.ts index 7c0c9a7c..8341e7f3 100644 --- a/src/components/Menu.ts +++ b/src/components/Menu.ts @@ -6,6 +6,26 @@ import { MENU_CHANGE_EVENT, type MenuChangeEvent } from '../events/MenuChangeEve import { createCustomEvent } from '../util/EventUtils'; import { Attribute } from '../util/Attribute'; import { toggleAttribute } from '../util/CommonUtils'; +import { templateContent } from 'lit/directives/template-content.js'; + +/** @deprecated */ +export interface MenuOptions { + /** + * @deprecated Override {@link Menu.render} instead. + */ + template?: HTMLTemplateElement; +} + +/** + * @deprecated Override {@link Menu.render} instead. + */ +export function menuTemplate(heading: string, content: string, extraCss: string = ''): string { + return ( + `` + + `
${heading}
` + + `
${content}
` + ); +} /** * `` - A menu that can be opened on top of the player. @@ -28,6 +48,7 @@ export class Menu extends LitElement { delegatesFocus: true }; + private readonly _template: HTMLTemplateElement | undefined; private _menuOpened: boolean = false; /** @@ -36,8 +57,9 @@ export class Menu extends LitElement { * By default, the button has an unnamed `` for its contents, and a named `"heading"` `` for its heading text. * Subclasses can override this by overriding {@link renderMenuHeading} and/or {@link renderMenuContent}. */ - constructor() { + constructor(options?: MenuOptions) { super(); + this._template = options?.template; } connectedCallback(): void { @@ -94,6 +116,9 @@ export class Menu extends LitElement { }; protected override render(): HTMLTemplateResult { + if (this._template) { + return html`${templateContent(this._template)}`; + } return html`
${this.renderMenuHeading()} diff --git a/src/components/MenuGroup.ts b/src/components/MenuGroup.ts index 778ff350..b6c68ddb 100644 --- a/src/components/MenuGroup.ts +++ b/src/components/MenuGroup.ts @@ -19,6 +19,22 @@ import { createCustomEvent } from '../util/EventUtils'; import type { MenuChangeEvent } from '../events/MenuChangeEvent'; import { MENU_CHANGE_EVENT } from '../events/MenuChangeEvent'; import { Menu } from './Menu'; +import { templateContent } from 'lit/directives/template-content.js'; + +/** @deprecated */ +export interface MenuGroupOptions { + /** + * @deprecated Override {@link MenuGroup.render} instead. + */ + template?: HTMLTemplateElement; +} + +/** + * @deprecated Override {@link MenuGroup.render} instead. + */ +export function menuGroupTemplate(content: string, extraCss: string = ''): string { + return `${content}`; +} interface OpenMenuEntry { menu: Menu | MenuGroup; @@ -43,6 +59,13 @@ export class MenuGroup extends LitElement { delegatesFocus: true }; + private readonly _template: HTMLTemplateElement | undefined; + + constructor(options?: MenuGroupOptions) { + super(); + this._template = options?.template; + } + private _menuOpened: boolean = false; private get menuOpened_(): boolean { @@ -88,6 +111,9 @@ export class MenuGroup extends LitElement { } protected render(): TemplateResult { + if (this._template) { + return html`${templateContent(this._template)}`; + } return html``; } From 405f19018ce93238116c490ac64416a3b1898f30 Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Mon, 28 Apr 2025 11:36:22 +0200 Subject: [PATCH 083/123] Render immediately in constructor when passing options.template --- src/components/Button.ts | 4 ++++ src/components/LinkButton.ts | 4 ++++ src/components/Menu.ts | 4 ++++ src/components/MenuGroup.ts | 4 ++++ 4 files changed, 16 insertions(+) diff --git a/src/components/Button.ts b/src/components/Button.ts index b163515a..161a5c7d 100644 --- a/src/components/Button.ts +++ b/src/components/Button.ts @@ -44,6 +44,10 @@ export class Button extends LitElement { constructor(options?: ButtonOptions) { super(); this._template = options?.template; + if (this._template) { + // Render immediately to populate the shadow DOM. + this.performUpdate(); + } } protected _upgradeProperty(prop: keyof this) { diff --git a/src/components/LinkButton.ts b/src/components/LinkButton.ts index 21a21c2f..3ba80b77 100644 --- a/src/components/LinkButton.ts +++ b/src/components/LinkButton.ts @@ -32,6 +32,10 @@ export class LinkButton extends LitElement { constructor(options?: ButtonOptions) { super(); this._template = options?.template; + if (this._template) { + // Render immediately to populate the shadow DOM. + this.performUpdate(); + } } connectedCallback(): void { diff --git a/src/components/Menu.ts b/src/components/Menu.ts index 8341e7f3..a1ba5ebe 100644 --- a/src/components/Menu.ts +++ b/src/components/Menu.ts @@ -60,6 +60,10 @@ export class Menu extends LitElement { constructor(options?: MenuOptions) { super(); this._template = options?.template; + if (this._template) { + // Render immediately to populate the shadow DOM. + this.performUpdate(); + } } connectedCallback(): void { diff --git a/src/components/MenuGroup.ts b/src/components/MenuGroup.ts index b6c68ddb..3801fbac 100644 --- a/src/components/MenuGroup.ts +++ b/src/components/MenuGroup.ts @@ -64,6 +64,10 @@ export class MenuGroup extends LitElement { constructor(options?: MenuGroupOptions) { super(); this._template = options?.template; + if (this._template) { + // Render immediately to populate the shadow DOM. + this.performUpdate(); + } } private _menuOpened: boolean = false; From a376b5d9c5f5d9ba4abe35f826cf53e684dee881 Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Mon, 28 Apr 2025 11:55:31 +0200 Subject: [PATCH 084/123] Rework StateReceiverMixin --- src/components/StateReceiverMixin.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/components/StateReceiverMixin.ts b/src/components/StateReceiverMixin.ts index bef44de9..e286496c 100644 --- a/src/components/StateReceiverMixin.ts +++ b/src/components/StateReceiverMixin.ts @@ -22,8 +22,9 @@ export interface StateReceiverPropertyMap { * A custom element that automatically receives selected state updates * from an ancestor {@link UIContainer | ``} element. * - * Do not implement this interface directly, instead use {@link StateReceiverMixin}. + * Do not implement this interface directly, instead use {@link stateReceiver} or {@link StateReceiverMixin}. * + * @see {@link stateReceiver} * @see {@link StateReceiverMixin} */ export interface StateReceiverElement extends Partial, Element { @@ -70,15 +71,15 @@ export const stateReceiver = * @param props - The names of the properties this element will receive. * @returns A class constructor that extends `base` and implements {@link StateReceiverElement}. * @see {@link StateReceiverElement} + * @deprecated Use {@link stateReceiver} decorator instead. */ export function StateReceiverMixin>( base: T, props: Array ): T & Constructor { + @stateReceiver(props) abstract class StateReceiver extends base implements StateReceiverElement { - get [StateReceiverProps](): Array { - return props; - } + declare readonly [StateReceiverProps]: Array; } return StateReceiver; From 6147b574d1490527852fe9f562e51b39e03a236d Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Mon, 28 Apr 2025 14:17:48 +0200 Subject: [PATCH 085/123] Stop injecting SSR shims, handled by Lit itself now --- package-lock.json | 48 ----------------------------------------------- package.json | 2 -- rollup.config.mjs | 13 ------------- 3 files changed, 63 deletions(-) diff --git a/package-lock.json b/package-lock.json index b783df31..17e3866a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,14 +13,12 @@ "react" ], "dependencies": { - "@lit-labs/ssr-dom-shim": "^1.3.0", "@webcomponents/webcomponentsjs": "^2.8.0", "lit": "^3.3.0" }, "devDependencies": { "@changesets/cli": "^2.29.7", "@changesets/types": "^6.1.0", - "@rollup/plugin-inject": "^5.0.5", "@rollup/plugin-json": "^6.1.0", "@rollup/plugin-node-resolve": "^16.0.0", "@rollup/plugin-replace": "^6.0.2", @@ -2018,28 +2016,6 @@ "node": ">= 8" } }, - "node_modules/@rollup/plugin-inject": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/@rollup/plugin-inject/-/plugin-inject-5.0.5.tgz", - "integrity": "sha512-2+DEJbNBoPROPkgTDNe8/1YXWcqxbN5DTjASVIOx8HS+pITXushyNiBV56RB08zuptzz8gT3YfkqriTBVycepg==", - "dev": true, - "dependencies": { - "@rollup/pluginutils": "^5.0.1", - "estree-walker": "^2.0.2", - "magic-string": "^0.30.3" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } - } - }, "node_modules/@rollup/plugin-json": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/@rollup/plugin-json/-/plugin-json-6.1.0.tgz", @@ -10279,17 +10255,6 @@ "fastq": "^1.6.0" } }, - "@rollup/plugin-inject": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/@rollup/plugin-inject/-/plugin-inject-5.0.5.tgz", - "integrity": "sha512-2+DEJbNBoPROPkgTDNe8/1YXWcqxbN5DTjASVIOx8HS+pITXushyNiBV56RB08zuptzz8gT3YfkqriTBVycepg==", - "dev": true, - "requires": { - "@rollup/pluginutils": "^5.0.1", - "estree-walker": "^2.0.2", - "magic-string": "^0.30.3" - } - }, "@rollup/plugin-json": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/@rollup/plugin-json/-/plugin-json-6.1.0.tgz", @@ -10697,8 +10662,6 @@ "requires": { "@changesets/cli": "^2.29.7", "@changesets/types": "^6.1.0", - "@lit-labs/ssr-dom-shim": "^1.3.0", - "@rollup/plugin-inject": "^5.0.5", "@rollup/plugin-json": "^6.1.0", "@rollup/plugin-node-resolve": "^16.0.0", "@rollup/plugin-replace": "^6.0.2", @@ -11723,17 +11686,6 @@ "fastq": "^1.6.0" } }, - "@rollup/plugin-inject": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/@rollup/plugin-inject/-/plugin-inject-5.0.5.tgz", - "integrity": "sha512-2+DEJbNBoPROPkgTDNe8/1YXWcqxbN5DTjASVIOx8HS+pITXushyNiBV56RB08zuptzz8gT3YfkqriTBVycepg==", - "dev": true, - "requires": { - "@rollup/pluginutils": "^5.0.1", - "estree-walker": "^2.0.2", - "magic-string": "^0.30.3" - } - }, "@rollup/plugin-json": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/@rollup/plugin-json/-/plugin-json-6.1.0.tgz", diff --git a/package.json b/package.json index 63d5206a..437dda73 100644 --- a/package.json +++ b/package.json @@ -54,7 +54,6 @@ "url": "git+https://github.com/THEOplayer/web-ui.git" }, "dependencies": { - "@lit-labs/ssr-dom-shim": "^1.3.0", "@webcomponents/webcomponentsjs": "^2.8.0", "lit": "^3.3.0" }, @@ -64,7 +63,6 @@ "devDependencies": { "@changesets/cli": "^2.29.7", "@changesets/types": "^6.1.0", - "@rollup/plugin-inject": "^5.0.5", "@rollup/plugin-json": "^6.1.0", "@rollup/plugin-node-resolve": "^16.0.0", "@rollup/plugin-replace": "^6.0.2", diff --git a/rollup.config.mjs b/rollup.config.mjs index 54e8dc54..5859d85b 100644 --- a/rollup.config.mjs +++ b/rollup.config.mjs @@ -12,7 +12,6 @@ import { readFile } from 'node:fs/promises'; import { string } from 'rollup-plugin-string'; import replace from '@rollup/plugin-replace'; import dts from 'rollup-plugin-dts'; -import inject from '@rollup/plugin-inject'; import virtual from '@rollup/plugin-virtual'; import json from '@rollup/plugin-json'; @@ -28,7 +27,6 @@ const banner = `/*! * License: ${license} */`; const theoplayerModule = 'theoplayer/chromeless'; -const domShimModule = '@lit-labs/ssr-dom-shim'; /** * @param {{configOutputDir?: string}} cliArgs @@ -111,7 +109,6 @@ function jsConfig(outputDir, { es5 = false, node = false, production = false, so banner }, context: 'globalThis', - external: [domShimModule], plugins: jsPlugins({ es5, node, module: true, production, sourcemap }) } ]).filter(Boolean); @@ -228,16 +225,6 @@ function jsPlugins({ es5 = false, node = false, module = false, production = fal externalHelpers: true } }), - // For Node, inject SSR shims for custom elements. - node && - inject({ - include: './src/**', - sourceMap: sourcemap, - modules: { - HTMLElement: [domShimModule, 'HTMLElement'], - customElements: [domShimModule, 'customElements'] - } - }), // Minify production builds. production && minify({ From de1fa50473c499b1b5a6a73bba323382ec58a7e6 Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Mon, 28 Apr 2025 14:21:17 +0200 Subject: [PATCH 086/123] Fix imports --- react/src/hooks/useCurrentTime.ts | 2 +- react/src/hooks/useDuration.ts | 2 +- react/src/hooks/usePaused.ts | 2 +- react/src/hooks/useSeeking.ts | 2 +- react/src/hooks/useSource.ts | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/react/src/hooks/useCurrentTime.ts b/react/src/hooks/useCurrentTime.ts index 5fe29834..31779853 100644 --- a/react/src/hooks/useCurrentTime.ts +++ b/react/src/hooks/useCurrentTime.ts @@ -1,6 +1,6 @@ import { useCallback, useContext, useState, useSyncExternalStore } from 'react'; import { PlayerContext } from '../context'; -import type { PlayerEventMap } from 'theoplayer'; +import type { PlayerEventMap } from 'theoplayer/chromeless'; const TIME_CHANGE_EVENTS = ['timeupdate', 'seeking', 'seeked', 'emptied'] satisfies ReadonlyArray; diff --git a/react/src/hooks/useDuration.ts b/react/src/hooks/useDuration.ts index 441ddb90..ba189a97 100644 --- a/react/src/hooks/useDuration.ts +++ b/react/src/hooks/useDuration.ts @@ -1,6 +1,6 @@ import { useCallback, useContext, useSyncExternalStore } from 'react'; import { PlayerContext } from '../context'; -import type { PlayerEventMap } from 'theoplayer'; +import type { PlayerEventMap } from 'theoplayer/chromeless'; const DURATION_CHANGE_EVENTS = ['durationchange', 'emptied'] satisfies ReadonlyArray; diff --git a/react/src/hooks/usePaused.ts b/react/src/hooks/usePaused.ts index bb3d79e6..218b6cbc 100644 --- a/react/src/hooks/usePaused.ts +++ b/react/src/hooks/usePaused.ts @@ -1,6 +1,6 @@ import { useCallback, useContext, useSyncExternalStore } from 'react'; import { PlayerContext } from '../context'; -import type { PlayerEventMap } from 'theoplayer'; +import type { PlayerEventMap } from 'theoplayer/chromeless'; const PAUSED_CHANGE_EVENTS = ['play', 'pause'] satisfies ReadonlyArray; diff --git a/react/src/hooks/useSeeking.ts b/react/src/hooks/useSeeking.ts index 1914a13c..71a85be2 100644 --- a/react/src/hooks/useSeeking.ts +++ b/react/src/hooks/useSeeking.ts @@ -1,6 +1,6 @@ import { useCallback, useContext, useSyncExternalStore } from 'react'; import { PlayerContext } from '../context'; -import type { PlayerEventMap } from 'theoplayer'; +import type { PlayerEventMap } from 'theoplayer/chromeless'; const SEEKING_CHANGE_EVENTS = ['seeking', 'seeked', 'emptied'] satisfies ReadonlyArray; diff --git a/react/src/hooks/useSource.ts b/react/src/hooks/useSource.ts index 89bf7212..3444968f 100644 --- a/react/src/hooks/useSource.ts +++ b/react/src/hooks/useSource.ts @@ -1,6 +1,6 @@ import { useCallback, useContext, useSyncExternalStore } from 'react'; import { PlayerContext } from '../context'; -import type { SourceDescription } from 'theoplayer'; +import type { SourceDescription } from 'theoplayer/chromeless'; /** * Returns {@link theoplayer!ChromelessPlayer.source | the player's source}, automatically updating whenever it changes. From e44d2b7adcfff8d06557eb24a7c6164f8080cf92 Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Mon, 28 Apr 2025 14:30:07 +0200 Subject: [PATCH 087/123] Update dependencies --- package-lock.json | 941 +++++++++++++++++++++++++-------------------- package.json | 4 +- react/package.json | 4 +- 3 files changed, 521 insertions(+), 428 deletions(-) diff --git a/package-lock.json b/package-lock.json index 17e3866a..c40fb7d5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,7 +25,7 @@ "@rollup/plugin-virtual": "^3.0.2", "@rollup/pluginutils": "^5.1.4", "@swc/cli": "^0.7.3", - "@swc/core": "^1.10.15", + "@swc/core": "^1.11.22", "@swc/helpers": "^0.5.15", "@types/html-minifier": "^4.0.5", "@webcomponents/shadycss": "^1.11.2", @@ -35,7 +35,7 @@ "postcss-mixins": "^11.0.3", "postcss-preset-env": "^10.1.3", "prettier": "^3.6.2", - "rollup": "^4.34.6", + "rollup": "^4.40.1", "rollup-plugin-dts": "^6.1.1", "rollup-plugin-minify-html-literals-v3": "^1.3.3", "rollup-plugin-postcss": "^4.0.2", @@ -2137,9 +2137,9 @@ } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.34.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.34.6.tgz", - "integrity": "sha512-+GcCXtOQoWuC7hhX1P00LqjjIiS/iOouHXhMdiDSnq/1DGTox4SpUvO52Xm+div6+106r+TcvOeo/cxvyEyTgg==", + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.52.2.tgz", + "integrity": "sha512-o3pcKzJgSGt4d74lSZ+OCnHwkKBeAbFDmbEm5gg70eA8VkyCuC/zV9TwBnmw6VjDlRdF4Pshfb+WE9E6XY1PoQ==", "cpu": [ "arm" ], @@ -2151,9 +2151,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.34.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.34.6.tgz", - "integrity": "sha512-E8+2qCIjciYUnCa1AiVF1BkRgqIGW9KzJeesQqVfyRITGQN+dFuoivO0hnro1DjT74wXLRZ7QF8MIbz+luGaJA==", + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.52.2.tgz", + "integrity": "sha512-cqFSWO5tX2vhC9hJTK8WAiPIm4Q8q/cU8j2HQA0L3E1uXvBYbOZMhE2oFL8n2pKB5sOCHY6bBuHaRwG7TkfJyw==", "cpu": [ "arm64" ], @@ -2165,9 +2165,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.34.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.34.6.tgz", - "integrity": "sha512-z9Ib+OzqN3DZEjX7PDQMHEhtF+t6Mi2z/ueChQPLS/qUMKY7Ybn5A2ggFoKRNRh1q1T03YTQfBTQCJZiepESAg==", + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.52.2.tgz", + "integrity": "sha512-vngduywkkv8Fkh3wIZf5nFPXzWsNsVu1kvtLETWxTFf/5opZmflgVSeLgdHR56RQh71xhPhWoOkEBvbehwTlVA==", "cpu": [ "arm64" ], @@ -2179,9 +2179,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.34.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.34.6.tgz", - "integrity": "sha512-PShKVY4u0FDAR7jskyFIYVyHEPCPnIQY8s5OcXkdU8mz3Y7eXDJPdyM/ZWjkYdR2m0izD9HHWA8sGcXn+Qrsyg==", + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.52.2.tgz", + "integrity": "sha512-h11KikYrUCYTrDj6h939hhMNlqU2fo/X4NB0OZcys3fya49o1hmFaczAiJWVAFgrM1NCP6RrO7lQKeVYSKBPSQ==", "cpu": [ "x64" ], @@ -2193,9 +2193,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.34.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.34.6.tgz", - "integrity": "sha512-YSwyOqlDAdKqs0iKuqvRHLN4SrD2TiswfoLfvYXseKbL47ht1grQpq46MSiQAx6rQEN8o8URtpXARCpqabqxGQ==", + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.52.2.tgz", + "integrity": "sha512-/eg4CI61ZUkLXxMHyVlmlGrSQZ34xqWlZNW43IAU4RmdzWEx0mQJ2mN/Cx4IHLVZFL6UBGAh+/GXhgvGb+nVxw==", "cpu": [ "arm64" ], @@ -2207,9 +2207,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.34.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.34.6.tgz", - "integrity": "sha512-HEP4CgPAY1RxXwwL5sPFv6BBM3tVeLnshF03HMhJYCNc6kvSqBgTMmsEjb72RkZBAWIqiPUyF1JpEBv5XT9wKQ==", + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.52.2.tgz", + "integrity": "sha512-QOWgFH5X9+p+S1NAfOqc0z8qEpJIoUHf7OWjNUGOeW18Mx22lAUOiA9b6r2/vpzLdfxi/f+VWsYjUOMCcYh0Ng==", "cpu": [ "x64" ], @@ -2221,9 +2221,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.34.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.34.6.tgz", - "integrity": "sha512-88fSzjC5xeH9S2Vg3rPgXJULkHcLYMkh8faix8DX4h4TIAL65ekwuQMA/g2CXq8W+NJC43V6fUpYZNjaX3+IIg==", + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.52.2.tgz", + "integrity": "sha512-kDWSPafToDd8LcBYd1t5jw7bD5Ojcu12S3uT372e5HKPzQt532vW+rGFFOaiR0opxePyUkHrwz8iWYEyH1IIQA==", "cpu": [ "arm" ], @@ -2235,9 +2235,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.34.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.34.6.tgz", - "integrity": "sha512-wM4ztnutBqYFyvNeR7Av+reWI/enK9tDOTKNF+6Kk2Q96k9bwhDDOlnCUNRPvromlVXo04riSliMBs/Z7RteEg==", + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.52.2.tgz", + "integrity": "sha512-gKm7Mk9wCv6/rkzwCiUC4KnevYhlf8ztBrDRT9g/u//1fZLapSRc+eDZj2Eu2wpJ+0RzUKgtNijnVIB4ZxyL+w==", "cpu": [ "arm" ], @@ -2249,9 +2249,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.34.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.34.6.tgz", - "integrity": "sha512-9RyprECbRa9zEjXLtvvshhw4CMrRa3K+0wcp3KME0zmBe1ILmvcVHnypZ/aIDXpRyfhSYSuN4EPdCCj5Du8FIA==", + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.52.2.tgz", + "integrity": "sha512-66lA8vnj5mB/rtDNwPgrrKUOtCLVQypkyDa2gMfOefXK6rcZAxKLO9Fy3GkW8VkPnENv9hBkNOFfGLf6rNKGUg==", "cpu": [ "arm64" ], @@ -2263,9 +2263,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.34.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.34.6.tgz", - "integrity": "sha512-qTmklhCTyaJSB05S+iSovfo++EwnIEZxHkzv5dep4qoszUMX5Ca4WM4zAVUMbfdviLgCSQOu5oU8YoGk1s6M9Q==", + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.52.2.tgz", + "integrity": "sha512-s+OPucLNdJHvuZHuIz2WwncJ+SfWHFEmlC5nKMUgAelUeBUnlB4wt7rXWiyG4Zn07uY2Dd+SGyVa9oyLkVGOjA==", "cpu": [ "arm64" ], @@ -2276,10 +2276,10 @@ "linux" ] }, - "node_modules/@rollup/rollup-linux-loongarch64-gnu": { - "version": "4.34.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.34.6.tgz", - "integrity": "sha512-4Qmkaps9yqmpjY5pvpkfOerYgKNUGzQpFxV6rnS7c/JfYbDSU0y6WpbbredB5cCpLFGJEqYX40WUmxMkwhWCjw==", + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.52.2.tgz", + "integrity": "sha512-8wTRM3+gVMDLLDdaT6tKmOE3lJyRy9NpJUS/ZRWmLCmOPIJhVyXwjBo+XbrrwtV33Em1/eCTd5TuGJm4+DmYjw==", "cpu": [ "loong64" ], @@ -2290,10 +2290,10 @@ "linux" ] }, - "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.34.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.34.6.tgz", - "integrity": "sha512-Zsrtux3PuaxuBTX/zHdLaFmcofWGzaWW1scwLU3ZbW/X+hSsFbz9wDIp6XvnT7pzYRl9MezWqEqKy7ssmDEnuQ==", + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.52.2.tgz", + "integrity": "sha512-6yqEfgJ1anIeuP2P/zhtfBlDpXUb80t8DpbYwXQ3bQd95JMvUaqiX+fKqYqUwZXqdJDd8xdilNtsHM2N0cFm6A==", "cpu": [ "ppc64" ], @@ -2305,9 +2305,23 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.34.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.34.6.tgz", - "integrity": "sha512-aK+Zp+CRM55iPrlyKiU3/zyhgzWBxLVrw2mwiQSYJRobCURb781+XstzvA8Gkjg/hbdQFuDw44aUOxVQFycrAg==", + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.52.2.tgz", + "integrity": "sha512-sshYUiYVSEI2B6dp4jMncwxbrUqRdNApF2c3bhtLAU0qA8Lrri0p0NauOsTWh3yCCCDyBOjESHMExonp7Nzc0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.52.2.tgz", + "integrity": "sha512-duBLgd+3pqC4MMwBrKkFxaZerUxZcYApQVC5SdbF5/e/589GwVvlRUnyqMFbM8iUSb1BaoX/3fRL7hB9m2Pj8Q==", "cpu": [ "riscv64" ], @@ -2319,9 +2333,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.34.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.34.6.tgz", - "integrity": "sha512-WoKLVrY9ogmaYPXwTH326+ErlCIgMmsoRSx6bO+l68YgJnlOXhygDYSZe/qbUJCSiCiZAQ+tKm88NcWuUXqOzw==", + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.52.2.tgz", + "integrity": "sha512-tzhYJJidDUVGMgVyE+PmxENPHlvvqm1KILjjZhB8/xHYqAGeizh3GBGf9u6WdJpZrz1aCpIIHG0LgJgH9rVjHQ==", "cpu": [ "s390x" ], @@ -2333,9 +2347,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.34.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.34.6.tgz", - "integrity": "sha512-Sht4aFvmA4ToHd2vFzwMFaQCiYm2lDFho5rPcvPBT5pCdC+GwHG6CMch4GQfmWTQ1SwRKS0dhDYb54khSrjDWw==", + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.52.2.tgz", + "integrity": "sha512-opH8GSUuVcCSSyHHcl5hELrmnk4waZoVpgn/4FDao9iyE4WpQhyWJ5ryl5M3ocp4qkRuHfyXnGqg8M9oKCEKRA==", "cpu": [ "x64" ], @@ -2347,9 +2361,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.34.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.34.6.tgz", - "integrity": "sha512-zmmpOQh8vXc2QITsnCiODCDGXFC8LMi64+/oPpPx5qz3pqv0s6x46ps4xoycfUiVZps5PFn1gksZzo4RGTKT+A==", + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.52.2.tgz", + "integrity": "sha512-LSeBHnGli1pPKVJ79ZVJgeZWWZXkEe/5o8kcn23M8eMKCUANejchJbF/JqzM4RRjOJfNRhKJk8FuqL1GKjF5oQ==", "cpu": [ "x64" ], @@ -2360,10 +2374,24 @@ "linux" ] }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.52.2.tgz", + "integrity": "sha512-uPj7MQ6/s+/GOpolavm6BPo+6CbhbKYyZHUDvZ/SmJM7pfDBgdGisFX3bY/CBDMg2ZO4utfhlApkSfZ92yXw7Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.34.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.34.6.tgz", - "integrity": "sha512-3/q1qUsO/tLqGBaD4uXsB6coVGB3usxw3qyeVb59aArCgedSF66MPdgRStUd7vbZOsko/CgVaY5fo2vkvPLWiA==", + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.52.2.tgz", + "integrity": "sha512-Z9MUCrSgIaUeeHAiNkm3cQyst2UhzjPraR3gYYfOjAuZI7tcFRTOD+4cHLPoS/3qinchth+V56vtqz1Tv+6KPA==", "cpu": [ "arm64" ], @@ -2375,9 +2403,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.34.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.34.6.tgz", - "integrity": "sha512-oLHxuyywc6efdKVTxvc0135zPrRdtYVjtVD5GUm55I3ODxhU/PwkQFD97z16Xzxa1Fz0AEe4W/2hzRtd+IfpOA==", + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.52.2.tgz", + "integrity": "sha512-+GnYBmpjldD3XQd+HMejo+0gJGwYIOfFeoBQv32xF/RUIvccUz20/V6Otdv+57NE70D5pa8W/jVGDoGq0oON4A==", "cpu": [ "ia32" ], @@ -2388,10 +2416,24 @@ "win32" ] }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.52.2.tgz", + "integrity": "sha512-ApXFKluSB6kDQkAqZOKXBjiaqdF1BlKi+/eqnYe9Ee7U2K3pUDKsIyr8EYm/QDHTJIM+4X+lI0gJc3TTRhd+dA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.34.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.34.6.tgz", - "integrity": "sha512-0PVwmgzZ8+TZ9oGBmdZoQVXflbvuwzN/HRclujpl4N/q3i+y0lqLw8n1bXA8ru3sApDjlmONaNAuYr38y1Kr9w==", + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.52.2.tgz", + "integrity": "sha512-ARz+Bs8kY6FtitYM96PqPEVvPXqEZmPZsSkXvyX19YzDqkCaIlhCieLLMI5hxO9SRZ2XtCtm8wxhy0iJ2jxNfw==", "cpu": [ "x64" ], @@ -2497,15 +2539,15 @@ } }, "node_modules/@swc/core": { - "version": "1.10.15", - "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.10.15.tgz", - "integrity": "sha512-/iFeQuNaGdK7mfJbQcObhAhsMqLT7qgMYl7jX2GEIO+VDTejESpzAyKwaMeYXExN8D6e5BRHBCe7M5YlsuzjDA==", + "version": "1.13.5", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.13.5.tgz", + "integrity": "sha512-WezcBo8a0Dg2rnR82zhwoR6aRNxeTGfK5QCD6TQ+kg3xx/zNT02s/0o+81h/3zhvFSB24NtqEr8FTw88O5W/JQ==", "dev": true, "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { "@swc/counter": "^0.1.3", - "@swc/types": "^0.1.17" + "@swc/types": "^0.1.24" }, "engines": { "node": ">=10" @@ -2515,19 +2557,19 @@ "url": "https://opencollective.com/swc" }, "optionalDependencies": { - "@swc/core-darwin-arm64": "1.10.15", - "@swc/core-darwin-x64": "1.10.15", - "@swc/core-linux-arm-gnueabihf": "1.10.15", - "@swc/core-linux-arm64-gnu": "1.10.15", - "@swc/core-linux-arm64-musl": "1.10.15", - "@swc/core-linux-x64-gnu": "1.10.15", - "@swc/core-linux-x64-musl": "1.10.15", - "@swc/core-win32-arm64-msvc": "1.10.15", - "@swc/core-win32-ia32-msvc": "1.10.15", - "@swc/core-win32-x64-msvc": "1.10.15" + "@swc/core-darwin-arm64": "1.13.5", + "@swc/core-darwin-x64": "1.13.5", + "@swc/core-linux-arm-gnueabihf": "1.13.5", + "@swc/core-linux-arm64-gnu": "1.13.5", + "@swc/core-linux-arm64-musl": "1.13.5", + "@swc/core-linux-x64-gnu": "1.13.5", + "@swc/core-linux-x64-musl": "1.13.5", + "@swc/core-win32-arm64-msvc": "1.13.5", + "@swc/core-win32-ia32-msvc": "1.13.5", + "@swc/core-win32-x64-msvc": "1.13.5" }, "peerDependencies": { - "@swc/helpers": "*" + "@swc/helpers": ">=0.5.17" }, "peerDependenciesMeta": { "@swc/helpers": { @@ -2536,9 +2578,9 @@ } }, "node_modules/@swc/core-darwin-arm64": { - "version": "1.10.15", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.10.15.tgz", - "integrity": "sha512-zFdZ6/yHqMCPk7OhLFqHy/MQ1EqJhcZMpNHd1gXYT7VRU3FaqvvKETrUlG3VYl65McPC7AhMRfXPyJ0JO/jARQ==", + "version": "1.13.5", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.13.5.tgz", + "integrity": "sha512-lKNv7SujeXvKn16gvQqUQI5DdyY8v7xcoO3k06/FJbHJS90zEwZdQiMNRiqpYw/orU543tPaWgz7cIYWhbopiQ==", "cpu": [ "arm64" ], @@ -2553,9 +2595,9 @@ } }, "node_modules/@swc/core-darwin-x64": { - "version": "1.10.15", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.10.15.tgz", - "integrity": "sha512-8g4yiQwbr8fxOOjKXdot0dEkE5zgE8uNZudLy/ZyAhiwiZ8pbJ8/wVrDOu6dqbX7FBXAoDnvZ7fwN1jk4C8jdA==", + "version": "1.13.5", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.13.5.tgz", + "integrity": "sha512-ILd38Fg/w23vHb0yVjlWvQBoE37ZJTdlLHa8LRCFDdX4WKfnVBiblsCU9ar4QTMNdeTBEX9iUF4IrbNWhaF1Ng==", "cpu": [ "x64" ], @@ -2570,9 +2612,9 @@ } }, "node_modules/@swc/core-linux-arm-gnueabihf": { - "version": "1.10.15", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.10.15.tgz", - "integrity": "sha512-rl+eVOltl2+7WXOnvmWBpMgh6aO13G5x0U0g8hjwlmD6ku3Y9iRcThpOhm7IytMEarUp5pQxItNoPq+VUGjVHg==", + "version": "1.13.5", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.13.5.tgz", + "integrity": "sha512-Q6eS3Pt8GLkXxqz9TAw+AUk9HpVJt8Uzm54MvPsqp2yuGmY0/sNaPPNVqctCX9fu/Nu8eaWUen0si6iEiCsazQ==", "cpu": [ "arm" ], @@ -2587,9 +2629,9 @@ } }, "node_modules/@swc/core-linux-arm64-gnu": { - "version": "1.10.15", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.10.15.tgz", - "integrity": "sha512-qxWEQeyAJMWJqjaN4hi58WMpPdt3Tn0biSK9CYRegQtvZWCbewr6v2agtSu5AZ2rudeH6OfCWAMDQQeSgn6PJQ==", + "version": "1.13.5", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.13.5.tgz", + "integrity": "sha512-aNDfeN+9af+y+M2MYfxCzCy/VDq7Z5YIbMqRI739o8Ganz6ST+27kjQFd8Y/57JN/hcnUEa9xqdS3XY7WaVtSw==", "cpu": [ "arm64" ], @@ -2604,9 +2646,9 @@ } }, "node_modules/@swc/core-linux-arm64-musl": { - "version": "1.10.15", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.10.15.tgz", - "integrity": "sha512-QcELd9/+HjZx0WCxRrKcyKGWTiQ0485kFb5w8waxcSNd0d9Lgk4EFfWWVyvIb5gIHpDQmhrgzI/yRaWQX4YSZQ==", + "version": "1.13.5", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.13.5.tgz", + "integrity": "sha512-9+ZxFN5GJag4CnYnq6apKTnnezpfJhCumyz0504/JbHLo+Ue+ZtJnf3RhyA9W9TINtLE0bC4hKpWi8ZKoETyOQ==", "cpu": [ "arm64" ], @@ -2621,9 +2663,9 @@ } }, "node_modules/@swc/core-linux-x64-gnu": { - "version": "1.10.15", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.10.15.tgz", - "integrity": "sha512-S1+ZEEn3+a/MiMeQqQypbwTGoBG8/sPoCvpNbk+uValyygT+jSn3U0xVr45FbukpmMB+NhBMqfedMLqKA0QnJA==", + "version": "1.13.5", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.13.5.tgz", + "integrity": "sha512-WD530qvHrki8Ywt/PloKUjaRKgstQqNGvmZl54g06kA+hqtSE2FTG9gngXr3UJxYu/cNAjJYiBifm7+w4nbHbA==", "cpu": [ "x64" ], @@ -2638,9 +2680,9 @@ } }, "node_modules/@swc/core-linux-x64-musl": { - "version": "1.10.15", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.10.15.tgz", - "integrity": "sha512-qW+H9g/2zTJ4jP7NDw4VAALY0ZlNEKzYsEoSj/HKi7k3tYEHjMzsxjfsY9I8WZCft23bBdV3RTCPoxCshaj1CQ==", + "version": "1.13.5", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.13.5.tgz", + "integrity": "sha512-Luj8y4OFYx4DHNQTWjdIuKTq2f5k6uSXICqx+FSabnXptaOBAbJHNbHT/06JZh6NRUouaf0mYXN0mcsqvkhd7Q==", "cpu": [ "x64" ], @@ -2655,9 +2697,9 @@ } }, "node_modules/@swc/core-win32-arm64-msvc": { - "version": "1.10.15", - "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.10.15.tgz", - "integrity": "sha512-AhRB11aA6LxjIqut+mg7qsu/7soQDmbK6MKR9nP3hgBszpqtXbRba58lr24xIbBCMr+dpo6kgEapWt+t5Po6Zg==", + "version": "1.13.5", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.13.5.tgz", + "integrity": "sha512-cZ6UpumhF9SDJvv4DA2fo9WIzlNFuKSkZpZmPG1c+4PFSEMy5DFOjBSllCvnqihCabzXzpn6ykCwBmHpy31vQw==", "cpu": [ "arm64" ], @@ -2672,9 +2714,9 @@ } }, "node_modules/@swc/core-win32-ia32-msvc": { - "version": "1.10.15", - "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.10.15.tgz", - "integrity": "sha512-UGdh430TQwbDn6KjgvRTg1fO022sbQ4yCCHUev0+5B8uoBwi9a89qAz3emy2m56C8TXxUoihW9Y9OMfaRwPXUw==", + "version": "1.13.5", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.13.5.tgz", + "integrity": "sha512-C5Yi/xIikrFUzZcyGj9L3RpKljFvKiDMtyDzPKzlsDrKIw2EYY+bF88gB6oGY5RGmv4DAX8dbnpRAqgFD0FMEw==", "cpu": [ "ia32" ], @@ -2689,9 +2731,9 @@ } }, "node_modules/@swc/core-win32-x64-msvc": { - "version": "1.10.15", - "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.10.15.tgz", - "integrity": "sha512-XJzBCqO1m929qbJsOG7FZXQWX26TnEoMctS3QjuCoyBmkHxxQmZsy78KjMes1aomTcKHCyFYgrRGWgVmk7tT4Q==", + "version": "1.13.5", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.13.5.tgz", + "integrity": "sha512-YrKdMVxbYmlfybCSbRtrilc6UA8GF5aPmGKBdPvjrarvsmf4i7ZHGCEnLtfOMd3Lwbs2WUZq3WdMbozYeLU93Q==", "cpu": [ "x64" ], @@ -2713,9 +2755,9 @@ "license": "Apache-2.0" }, "node_modules/@swc/helpers": { - "version": "0.5.15", - "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", - "integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==", + "version": "0.5.17", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.17.tgz", + "integrity": "sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -2723,9 +2765,9 @@ } }, "node_modules/@swc/types": { - "version": "0.1.17", - "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.17.tgz", - "integrity": "sha512-V5gRru+aD8YVyCOMAjMpWR1Ui577DD5KSJsHP8RAxopAH22jFz6GZd/qxqjO6MJHQhcsjvjOFXyDhyLQUnMveQ==", + "version": "0.1.25", + "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.25.tgz", + "integrity": "sha512-iAoY/qRhNH8a/hBvm3zKj9qQ4oc2+3w1unPJa2XvTK3XjeLXtzcCingVPw/9e5mn1+0yPqxcBGp9Jf0pkfMb1g==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -2789,9 +2831,9 @@ } }, "node_modules/@types/estree": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", - "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", "dev": true, "license": "MIT" }, @@ -7947,13 +7989,13 @@ } }, "node_modules/rollup": { - "version": "4.34.6", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.34.6.tgz", - "integrity": "sha512-wc2cBWqJgkU3Iz5oztRkQbfVkbxoz5EhnCGOrnJvnLnQ7O0WhQUYyv18qQI79O8L7DdHrrlJNeCHd4VGpnaXKQ==", + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.2.tgz", + "integrity": "sha512-I25/2QgoROE1vYV+NQ1En9T9UFB9Cmfm2CJ83zZOlaDpvz29wGQSZXWKw7MiNXau7wYgB/T9fVIdIuEQ+KbiiA==", "dev": true, "license": "MIT", "dependencies": { - "@types/estree": "1.0.6" + "@types/estree": "1.0.8" }, "bin": { "rollup": "dist/bin/rollup" @@ -7963,25 +8005,28 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.34.6", - "@rollup/rollup-android-arm64": "4.34.6", - "@rollup/rollup-darwin-arm64": "4.34.6", - "@rollup/rollup-darwin-x64": "4.34.6", - "@rollup/rollup-freebsd-arm64": "4.34.6", - "@rollup/rollup-freebsd-x64": "4.34.6", - "@rollup/rollup-linux-arm-gnueabihf": "4.34.6", - "@rollup/rollup-linux-arm-musleabihf": "4.34.6", - "@rollup/rollup-linux-arm64-gnu": "4.34.6", - "@rollup/rollup-linux-arm64-musl": "4.34.6", - "@rollup/rollup-linux-loongarch64-gnu": "4.34.6", - "@rollup/rollup-linux-powerpc64le-gnu": "4.34.6", - "@rollup/rollup-linux-riscv64-gnu": "4.34.6", - "@rollup/rollup-linux-s390x-gnu": "4.34.6", - "@rollup/rollup-linux-x64-gnu": "4.34.6", - "@rollup/rollup-linux-x64-musl": "4.34.6", - "@rollup/rollup-win32-arm64-msvc": "4.34.6", - "@rollup/rollup-win32-ia32-msvc": "4.34.6", - "@rollup/rollup-win32-x64-msvc": "4.34.6", + "@rollup/rollup-android-arm-eabi": "4.52.2", + "@rollup/rollup-android-arm64": "4.52.2", + "@rollup/rollup-darwin-arm64": "4.52.2", + "@rollup/rollup-darwin-x64": "4.52.2", + "@rollup/rollup-freebsd-arm64": "4.52.2", + "@rollup/rollup-freebsd-x64": "4.52.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.52.2", + "@rollup/rollup-linux-arm-musleabihf": "4.52.2", + "@rollup/rollup-linux-arm64-gnu": "4.52.2", + "@rollup/rollup-linux-arm64-musl": "4.52.2", + "@rollup/rollup-linux-loong64-gnu": "4.52.2", + "@rollup/rollup-linux-ppc64-gnu": "4.52.2", + "@rollup/rollup-linux-riscv64-gnu": "4.52.2", + "@rollup/rollup-linux-riscv64-musl": "4.52.2", + "@rollup/rollup-linux-s390x-gnu": "4.52.2", + "@rollup/rollup-linux-x64-gnu": "4.52.2", + "@rollup/rollup-linux-x64-musl": "4.52.2", + "@rollup/rollup-openharmony-arm64": "4.52.2", + "@rollup/rollup-win32-arm64-msvc": "4.52.2", + "@rollup/rollup-win32-ia32-msvc": "4.52.2", + "@rollup/rollup-win32-x64-gnu": "4.52.2", + "@rollup/rollup-win32-x64-msvc": "4.52.2", "fsevents": "~2.3.2" } }, @@ -9243,12 +9288,12 @@ "@rollup/plugin-node-resolve": "^16.0.0", "@rollup/plugin-replace": "^6.0.2", "@swc/cli": "^0.7.3", - "@swc/core": "^1.10.15", + "@swc/core": "^1.11.22", "@swc/helpers": "^0.5.15", "@types/react": "^18.2.79", "react": "^18.2.0", "react-dom": "^18.2.0", - "rollup": "^4.34.6", + "rollup": "^4.40.1", "rollup-plugin-dts": "^6.1.1", "rollup-plugin-swc3": "^0.12.1", "theoplayer": "^10.6.1", @@ -10314,135 +10359,156 @@ } }, "@rollup/rollup-android-arm-eabi": { - "version": "4.34.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.34.6.tgz", - "integrity": "sha512-+GcCXtOQoWuC7hhX1P00LqjjIiS/iOouHXhMdiDSnq/1DGTox4SpUvO52Xm+div6+106r+TcvOeo/cxvyEyTgg==", + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.52.2.tgz", + "integrity": "sha512-o3pcKzJgSGt4d74lSZ+OCnHwkKBeAbFDmbEm5gg70eA8VkyCuC/zV9TwBnmw6VjDlRdF4Pshfb+WE9E6XY1PoQ==", "dev": true, "optional": true }, "@rollup/rollup-android-arm64": { - "version": "4.34.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.34.6.tgz", - "integrity": "sha512-E8+2qCIjciYUnCa1AiVF1BkRgqIGW9KzJeesQqVfyRITGQN+dFuoivO0hnro1DjT74wXLRZ7QF8MIbz+luGaJA==", + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.52.2.tgz", + "integrity": "sha512-cqFSWO5tX2vhC9hJTK8WAiPIm4Q8q/cU8j2HQA0L3E1uXvBYbOZMhE2oFL8n2pKB5sOCHY6bBuHaRwG7TkfJyw==", "dev": true, "optional": true }, "@rollup/rollup-darwin-arm64": { - "version": "4.34.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.34.6.tgz", - "integrity": "sha512-z9Ib+OzqN3DZEjX7PDQMHEhtF+t6Mi2z/ueChQPLS/qUMKY7Ybn5A2ggFoKRNRh1q1T03YTQfBTQCJZiepESAg==", + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.52.2.tgz", + "integrity": "sha512-vngduywkkv8Fkh3wIZf5nFPXzWsNsVu1kvtLETWxTFf/5opZmflgVSeLgdHR56RQh71xhPhWoOkEBvbehwTlVA==", "dev": true, "optional": true }, "@rollup/rollup-darwin-x64": { - "version": "4.34.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.34.6.tgz", - "integrity": "sha512-PShKVY4u0FDAR7jskyFIYVyHEPCPnIQY8s5OcXkdU8mz3Y7eXDJPdyM/ZWjkYdR2m0izD9HHWA8sGcXn+Qrsyg==", + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.52.2.tgz", + "integrity": "sha512-h11KikYrUCYTrDj6h939hhMNlqU2fo/X4NB0OZcys3fya49o1hmFaczAiJWVAFgrM1NCP6RrO7lQKeVYSKBPSQ==", "dev": true, "optional": true }, "@rollup/rollup-freebsd-arm64": { - "version": "4.34.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.34.6.tgz", - "integrity": "sha512-YSwyOqlDAdKqs0iKuqvRHLN4SrD2TiswfoLfvYXseKbL47ht1grQpq46MSiQAx6rQEN8o8URtpXARCpqabqxGQ==", + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.52.2.tgz", + "integrity": "sha512-/eg4CI61ZUkLXxMHyVlmlGrSQZ34xqWlZNW43IAU4RmdzWEx0mQJ2mN/Cx4IHLVZFL6UBGAh+/GXhgvGb+nVxw==", "dev": true, "optional": true }, "@rollup/rollup-freebsd-x64": { - "version": "4.34.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.34.6.tgz", - "integrity": "sha512-HEP4CgPAY1RxXwwL5sPFv6BBM3tVeLnshF03HMhJYCNc6kvSqBgTMmsEjb72RkZBAWIqiPUyF1JpEBv5XT9wKQ==", + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.52.2.tgz", + "integrity": "sha512-QOWgFH5X9+p+S1NAfOqc0z8qEpJIoUHf7OWjNUGOeW18Mx22lAUOiA9b6r2/vpzLdfxi/f+VWsYjUOMCcYh0Ng==", "dev": true, "optional": true }, "@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.34.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.34.6.tgz", - "integrity": "sha512-88fSzjC5xeH9S2Vg3rPgXJULkHcLYMkh8faix8DX4h4TIAL65ekwuQMA/g2CXq8W+NJC43V6fUpYZNjaX3+IIg==", + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.52.2.tgz", + "integrity": "sha512-kDWSPafToDd8LcBYd1t5jw7bD5Ojcu12S3uT372e5HKPzQt532vW+rGFFOaiR0opxePyUkHrwz8iWYEyH1IIQA==", "dev": true, "optional": true }, "@rollup/rollup-linux-arm-musleabihf": { - "version": "4.34.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.34.6.tgz", - "integrity": "sha512-wM4ztnutBqYFyvNeR7Av+reWI/enK9tDOTKNF+6Kk2Q96k9bwhDDOlnCUNRPvromlVXo04riSliMBs/Z7RteEg==", + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.52.2.tgz", + "integrity": "sha512-gKm7Mk9wCv6/rkzwCiUC4KnevYhlf8ztBrDRT9g/u//1fZLapSRc+eDZj2Eu2wpJ+0RzUKgtNijnVIB4ZxyL+w==", "dev": true, "optional": true }, "@rollup/rollup-linux-arm64-gnu": { - "version": "4.34.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.34.6.tgz", - "integrity": "sha512-9RyprECbRa9zEjXLtvvshhw4CMrRa3K+0wcp3KME0zmBe1ILmvcVHnypZ/aIDXpRyfhSYSuN4EPdCCj5Du8FIA==", + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.52.2.tgz", + "integrity": "sha512-66lA8vnj5mB/rtDNwPgrrKUOtCLVQypkyDa2gMfOefXK6rcZAxKLO9Fy3GkW8VkPnENv9hBkNOFfGLf6rNKGUg==", "dev": true, "optional": true }, "@rollup/rollup-linux-arm64-musl": { - "version": "4.34.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.34.6.tgz", - "integrity": "sha512-qTmklhCTyaJSB05S+iSovfo++EwnIEZxHkzv5dep4qoszUMX5Ca4WM4zAVUMbfdviLgCSQOu5oU8YoGk1s6M9Q==", + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.52.2.tgz", + "integrity": "sha512-s+OPucLNdJHvuZHuIz2WwncJ+SfWHFEmlC5nKMUgAelUeBUnlB4wt7rXWiyG4Zn07uY2Dd+SGyVa9oyLkVGOjA==", "dev": true, "optional": true }, - "@rollup/rollup-linux-loongarch64-gnu": { - "version": "4.34.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.34.6.tgz", - "integrity": "sha512-4Qmkaps9yqmpjY5pvpkfOerYgKNUGzQpFxV6rnS7c/JfYbDSU0y6WpbbredB5cCpLFGJEqYX40WUmxMkwhWCjw==", + "@rollup/rollup-linux-loong64-gnu": { + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.52.2.tgz", + "integrity": "sha512-8wTRM3+gVMDLLDdaT6tKmOE3lJyRy9NpJUS/ZRWmLCmOPIJhVyXwjBo+XbrrwtV33Em1/eCTd5TuGJm4+DmYjw==", "dev": true, "optional": true }, - "@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.34.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.34.6.tgz", - "integrity": "sha512-Zsrtux3PuaxuBTX/zHdLaFmcofWGzaWW1scwLU3ZbW/X+hSsFbz9wDIp6XvnT7pzYRl9MezWqEqKy7ssmDEnuQ==", + "@rollup/rollup-linux-ppc64-gnu": { + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.52.2.tgz", + "integrity": "sha512-6yqEfgJ1anIeuP2P/zhtfBlDpXUb80t8DpbYwXQ3bQd95JMvUaqiX+fKqYqUwZXqdJDd8xdilNtsHM2N0cFm6A==", "dev": true, "optional": true }, "@rollup/rollup-linux-riscv64-gnu": { - "version": "4.34.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.34.6.tgz", - "integrity": "sha512-aK+Zp+CRM55iPrlyKiU3/zyhgzWBxLVrw2mwiQSYJRobCURb781+XstzvA8Gkjg/hbdQFuDw44aUOxVQFycrAg==", + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.52.2.tgz", + "integrity": "sha512-sshYUiYVSEI2B6dp4jMncwxbrUqRdNApF2c3bhtLAU0qA8Lrri0p0NauOsTWh3yCCCDyBOjESHMExonp7Nzc0w==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-riscv64-musl": { + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.52.2.tgz", + "integrity": "sha512-duBLgd+3pqC4MMwBrKkFxaZerUxZcYApQVC5SdbF5/e/589GwVvlRUnyqMFbM8iUSb1BaoX/3fRL7hB9m2Pj8Q==", "dev": true, "optional": true }, "@rollup/rollup-linux-s390x-gnu": { - "version": "4.34.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.34.6.tgz", - "integrity": "sha512-WoKLVrY9ogmaYPXwTH326+ErlCIgMmsoRSx6bO+l68YgJnlOXhygDYSZe/qbUJCSiCiZAQ+tKm88NcWuUXqOzw==", + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.52.2.tgz", + "integrity": "sha512-tzhYJJidDUVGMgVyE+PmxENPHlvvqm1KILjjZhB8/xHYqAGeizh3GBGf9u6WdJpZrz1aCpIIHG0LgJgH9rVjHQ==", "dev": true, "optional": true }, "@rollup/rollup-linux-x64-gnu": { - "version": "4.34.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.34.6.tgz", - "integrity": "sha512-Sht4aFvmA4ToHd2vFzwMFaQCiYm2lDFho5rPcvPBT5pCdC+GwHG6CMch4GQfmWTQ1SwRKS0dhDYb54khSrjDWw==", + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.52.2.tgz", + "integrity": "sha512-opH8GSUuVcCSSyHHcl5hELrmnk4waZoVpgn/4FDao9iyE4WpQhyWJ5ryl5M3ocp4qkRuHfyXnGqg8M9oKCEKRA==", "dev": true, "optional": true }, "@rollup/rollup-linux-x64-musl": { - "version": "4.34.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.34.6.tgz", - "integrity": "sha512-zmmpOQh8vXc2QITsnCiODCDGXFC8LMi64+/oPpPx5qz3pqv0s6x46ps4xoycfUiVZps5PFn1gksZzo4RGTKT+A==", + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.52.2.tgz", + "integrity": "sha512-LSeBHnGli1pPKVJ79ZVJgeZWWZXkEe/5o8kcn23M8eMKCUANejchJbF/JqzM4RRjOJfNRhKJk8FuqL1GKjF5oQ==", + "dev": true, + "optional": true + }, + "@rollup/rollup-openharmony-arm64": { + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.52.2.tgz", + "integrity": "sha512-uPj7MQ6/s+/GOpolavm6BPo+6CbhbKYyZHUDvZ/SmJM7pfDBgdGisFX3bY/CBDMg2ZO4utfhlApkSfZ92yXw7Q==", "dev": true, "optional": true }, "@rollup/rollup-win32-arm64-msvc": { - "version": "4.34.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.34.6.tgz", - "integrity": "sha512-3/q1qUsO/tLqGBaD4uXsB6coVGB3usxw3qyeVb59aArCgedSF66MPdgRStUd7vbZOsko/CgVaY5fo2vkvPLWiA==", + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.52.2.tgz", + "integrity": "sha512-Z9MUCrSgIaUeeHAiNkm3cQyst2UhzjPraR3gYYfOjAuZI7tcFRTOD+4cHLPoS/3qinchth+V56vtqz1Tv+6KPA==", "dev": true, "optional": true }, "@rollup/rollup-win32-ia32-msvc": { - "version": "4.34.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.34.6.tgz", - "integrity": "sha512-oLHxuyywc6efdKVTxvc0135zPrRdtYVjtVD5GUm55I3ODxhU/PwkQFD97z16Xzxa1Fz0AEe4W/2hzRtd+IfpOA==", + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.52.2.tgz", + "integrity": "sha512-+GnYBmpjldD3XQd+HMejo+0gJGwYIOfFeoBQv32xF/RUIvccUz20/V6Otdv+57NE70D5pa8W/jVGDoGq0oON4A==", + "dev": true, + "optional": true + }, + "@rollup/rollup-win32-x64-gnu": { + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.52.2.tgz", + "integrity": "sha512-ApXFKluSB6kDQkAqZOKXBjiaqdF1BlKi+/eqnYe9Ee7U2K3pUDKsIyr8EYm/QDHTJIM+4X+lI0gJc3TTRhd+dA==", "dev": true, "optional": true }, "@rollup/rollup-win32-x64-msvc": { - "version": "4.34.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.34.6.tgz", - "integrity": "sha512-0PVwmgzZ8+TZ9oGBmdZoQVXflbvuwzN/HRclujpl4N/q3i+y0lqLw8n1bXA8ru3sApDjlmONaNAuYr38y1Kr9w==", + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.52.2.tgz", + "integrity": "sha512-ARz+Bs8kY6FtitYM96PqPEVvPXqEZmPZsSkXvyX19YzDqkCaIlhCieLLMI5hxO9SRZ2XtCtm8wxhy0iJ2jxNfw==", "dev": true, "optional": true }, @@ -10510,92 +10576,92 @@ } }, "@swc/core": { - "version": "1.10.15", - "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.10.15.tgz", - "integrity": "sha512-/iFeQuNaGdK7mfJbQcObhAhsMqLT7qgMYl7jX2GEIO+VDTejESpzAyKwaMeYXExN8D6e5BRHBCe7M5YlsuzjDA==", - "dev": true, - "requires": { - "@swc/core-darwin-arm64": "1.10.15", - "@swc/core-darwin-x64": "1.10.15", - "@swc/core-linux-arm-gnueabihf": "1.10.15", - "@swc/core-linux-arm64-gnu": "1.10.15", - "@swc/core-linux-arm64-musl": "1.10.15", - "@swc/core-linux-x64-gnu": "1.10.15", - "@swc/core-linux-x64-musl": "1.10.15", - "@swc/core-win32-arm64-msvc": "1.10.15", - "@swc/core-win32-ia32-msvc": "1.10.15", - "@swc/core-win32-x64-msvc": "1.10.15", + "version": "1.13.5", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.13.5.tgz", + "integrity": "sha512-WezcBo8a0Dg2rnR82zhwoR6aRNxeTGfK5QCD6TQ+kg3xx/zNT02s/0o+81h/3zhvFSB24NtqEr8FTw88O5W/JQ==", + "dev": true, + "requires": { + "@swc/core-darwin-arm64": "1.13.5", + "@swc/core-darwin-x64": "1.13.5", + "@swc/core-linux-arm-gnueabihf": "1.13.5", + "@swc/core-linux-arm64-gnu": "1.13.5", + "@swc/core-linux-arm64-musl": "1.13.5", + "@swc/core-linux-x64-gnu": "1.13.5", + "@swc/core-linux-x64-musl": "1.13.5", + "@swc/core-win32-arm64-msvc": "1.13.5", + "@swc/core-win32-ia32-msvc": "1.13.5", + "@swc/core-win32-x64-msvc": "1.13.5", "@swc/counter": "^0.1.3", - "@swc/types": "^0.1.17" + "@swc/types": "^0.1.24" } }, "@swc/core-darwin-arm64": { - "version": "1.10.15", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.10.15.tgz", - "integrity": "sha512-zFdZ6/yHqMCPk7OhLFqHy/MQ1EqJhcZMpNHd1gXYT7VRU3FaqvvKETrUlG3VYl65McPC7AhMRfXPyJ0JO/jARQ==", + "version": "1.13.5", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.13.5.tgz", + "integrity": "sha512-lKNv7SujeXvKn16gvQqUQI5DdyY8v7xcoO3k06/FJbHJS90zEwZdQiMNRiqpYw/orU543tPaWgz7cIYWhbopiQ==", "dev": true, "optional": true }, "@swc/core-darwin-x64": { - "version": "1.10.15", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.10.15.tgz", - "integrity": "sha512-8g4yiQwbr8fxOOjKXdot0dEkE5zgE8uNZudLy/ZyAhiwiZ8pbJ8/wVrDOu6dqbX7FBXAoDnvZ7fwN1jk4C8jdA==", + "version": "1.13.5", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.13.5.tgz", + "integrity": "sha512-ILd38Fg/w23vHb0yVjlWvQBoE37ZJTdlLHa8LRCFDdX4WKfnVBiblsCU9ar4QTMNdeTBEX9iUF4IrbNWhaF1Ng==", "dev": true, "optional": true }, "@swc/core-linux-arm-gnueabihf": { - "version": "1.10.15", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.10.15.tgz", - "integrity": "sha512-rl+eVOltl2+7WXOnvmWBpMgh6aO13G5x0U0g8hjwlmD6ku3Y9iRcThpOhm7IytMEarUp5pQxItNoPq+VUGjVHg==", + "version": "1.13.5", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.13.5.tgz", + "integrity": "sha512-Q6eS3Pt8GLkXxqz9TAw+AUk9HpVJt8Uzm54MvPsqp2yuGmY0/sNaPPNVqctCX9fu/Nu8eaWUen0si6iEiCsazQ==", "dev": true, "optional": true }, "@swc/core-linux-arm64-gnu": { - "version": "1.10.15", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.10.15.tgz", - "integrity": "sha512-qxWEQeyAJMWJqjaN4hi58WMpPdt3Tn0biSK9CYRegQtvZWCbewr6v2agtSu5AZ2rudeH6OfCWAMDQQeSgn6PJQ==", + "version": "1.13.5", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.13.5.tgz", + "integrity": "sha512-aNDfeN+9af+y+M2MYfxCzCy/VDq7Z5YIbMqRI739o8Ganz6ST+27kjQFd8Y/57JN/hcnUEa9xqdS3XY7WaVtSw==", "dev": true, "optional": true }, "@swc/core-linux-arm64-musl": { - "version": "1.10.15", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.10.15.tgz", - "integrity": "sha512-QcELd9/+HjZx0WCxRrKcyKGWTiQ0485kFb5w8waxcSNd0d9Lgk4EFfWWVyvIb5gIHpDQmhrgzI/yRaWQX4YSZQ==", + "version": "1.13.5", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.13.5.tgz", + "integrity": "sha512-9+ZxFN5GJag4CnYnq6apKTnnezpfJhCumyz0504/JbHLo+Ue+ZtJnf3RhyA9W9TINtLE0bC4hKpWi8ZKoETyOQ==", "dev": true, "optional": true }, "@swc/core-linux-x64-gnu": { - "version": "1.10.15", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.10.15.tgz", - "integrity": "sha512-S1+ZEEn3+a/MiMeQqQypbwTGoBG8/sPoCvpNbk+uValyygT+jSn3U0xVr45FbukpmMB+NhBMqfedMLqKA0QnJA==", + "version": "1.13.5", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.13.5.tgz", + "integrity": "sha512-WD530qvHrki8Ywt/PloKUjaRKgstQqNGvmZl54g06kA+hqtSE2FTG9gngXr3UJxYu/cNAjJYiBifm7+w4nbHbA==", "dev": true, "optional": true }, "@swc/core-linux-x64-musl": { - "version": "1.10.15", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.10.15.tgz", - "integrity": "sha512-qW+H9g/2zTJ4jP7NDw4VAALY0ZlNEKzYsEoSj/HKi7k3tYEHjMzsxjfsY9I8WZCft23bBdV3RTCPoxCshaj1CQ==", + "version": "1.13.5", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.13.5.tgz", + "integrity": "sha512-Luj8y4OFYx4DHNQTWjdIuKTq2f5k6uSXICqx+FSabnXptaOBAbJHNbHT/06JZh6NRUouaf0mYXN0mcsqvkhd7Q==", "dev": true, "optional": true }, "@swc/core-win32-arm64-msvc": { - "version": "1.10.15", - "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.10.15.tgz", - "integrity": "sha512-AhRB11aA6LxjIqut+mg7qsu/7soQDmbK6MKR9nP3hgBszpqtXbRba58lr24xIbBCMr+dpo6kgEapWt+t5Po6Zg==", + "version": "1.13.5", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.13.5.tgz", + "integrity": "sha512-cZ6UpumhF9SDJvv4DA2fo9WIzlNFuKSkZpZmPG1c+4PFSEMy5DFOjBSllCvnqihCabzXzpn6ykCwBmHpy31vQw==", "dev": true, "optional": true }, "@swc/core-win32-ia32-msvc": { - "version": "1.10.15", - "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.10.15.tgz", - "integrity": "sha512-UGdh430TQwbDn6KjgvRTg1fO022sbQ4yCCHUev0+5B8uoBwi9a89qAz3emy2m56C8TXxUoihW9Y9OMfaRwPXUw==", + "version": "1.13.5", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.13.5.tgz", + "integrity": "sha512-C5Yi/xIikrFUzZcyGj9L3RpKljFvKiDMtyDzPKzlsDrKIw2EYY+bF88gB6oGY5RGmv4DAX8dbnpRAqgFD0FMEw==", "dev": true, "optional": true }, "@swc/core-win32-x64-msvc": { - "version": "1.10.15", - "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.10.15.tgz", - "integrity": "sha512-XJzBCqO1m929qbJsOG7FZXQWX26TnEoMctS3QjuCoyBmkHxxQmZsy78KjMes1aomTcKHCyFYgrRGWgVmk7tT4Q==", + "version": "1.13.5", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.13.5.tgz", + "integrity": "sha512-YrKdMVxbYmlfybCSbRtrilc6UA8GF5aPmGKBdPvjrarvsmf4i7ZHGCEnLtfOMd3Lwbs2WUZq3WdMbozYeLU93Q==", "dev": true, "optional": true }, @@ -10606,18 +10672,18 @@ "dev": true }, "@swc/helpers": { - "version": "0.5.15", - "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", - "integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==", + "version": "0.5.17", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.17.tgz", + "integrity": "sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A==", "dev": true, "requires": { "tslib": "^2.8.0" } }, "@swc/types": { - "version": "0.1.17", - "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.17.tgz", - "integrity": "sha512-V5gRru+aD8YVyCOMAjMpWR1Ui577DD5KSJsHP8RAxopAH22jFz6GZd/qxqjO6MJHQhcsjvjOFXyDhyLQUnMveQ==", + "version": "0.1.25", + "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.25.tgz", + "integrity": "sha512-iAoY/qRhNH8a/hBvm3zKj9qQ4oc2+3w1unPJa2XvTK3XjeLXtzcCingVPw/9e5mn1+0yPqxcBGp9Jf0pkfMb1g==", "dev": true, "requires": { "@swc/counter": "^0.1.3" @@ -10640,13 +10706,13 @@ "@rollup/plugin-node-resolve": "^16.0.0", "@rollup/plugin-replace": "^6.0.2", "@swc/cli": "^0.7.3", - "@swc/core": "^1.10.15", + "@swc/core": "^1.11.22", "@swc/helpers": "^0.5.15", "@theoplayer/web-ui": "^1.14.0", "@types/react": "^18.2.79", "react": "^18.2.0", "react-dom": "^18.2.0", - "rollup": "^4.34.6", + "rollup": "^4.40.1", "rollup-plugin-dts": "^6.1.1", "rollup-plugin-swc3": "^0.12.1", "theoplayer": "^10.6.1", @@ -10668,7 +10734,7 @@ "@rollup/plugin-virtual": "^3.0.2", "@rollup/pluginutils": "^5.1.4", "@swc/cli": "^0.7.3", - "@swc/core": "^1.10.15", + "@swc/core": "^1.11.22", "@swc/helpers": "^0.5.15", "@theoplayer/react-ui": "file:react", "@theoplayer/web-ui": "file:", @@ -10682,7 +10748,7 @@ "postcss-mixins": "^11.0.3", "postcss-preset-env": "^10.1.3", "prettier": "^3.6.2", - "rollup": "^4.34.6", + "rollup": "^4.40.1", "rollup-plugin-dts": "^6.1.1", "rollup-plugin-minify-html-literals-v3": "^1.3.3", "rollup-plugin-postcss": "^4.0.2", @@ -11745,135 +11811,156 @@ } }, "@rollup/rollup-android-arm-eabi": { - "version": "4.34.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.34.6.tgz", - "integrity": "sha512-+GcCXtOQoWuC7hhX1P00LqjjIiS/iOouHXhMdiDSnq/1DGTox4SpUvO52Xm+div6+106r+TcvOeo/cxvyEyTgg==", + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.52.2.tgz", + "integrity": "sha512-o3pcKzJgSGt4d74lSZ+OCnHwkKBeAbFDmbEm5gg70eA8VkyCuC/zV9TwBnmw6VjDlRdF4Pshfb+WE9E6XY1PoQ==", "dev": true, "optional": true }, "@rollup/rollup-android-arm64": { - "version": "4.34.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.34.6.tgz", - "integrity": "sha512-E8+2qCIjciYUnCa1AiVF1BkRgqIGW9KzJeesQqVfyRITGQN+dFuoivO0hnro1DjT74wXLRZ7QF8MIbz+luGaJA==", + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.52.2.tgz", + "integrity": "sha512-cqFSWO5tX2vhC9hJTK8WAiPIm4Q8q/cU8j2HQA0L3E1uXvBYbOZMhE2oFL8n2pKB5sOCHY6bBuHaRwG7TkfJyw==", "dev": true, "optional": true }, "@rollup/rollup-darwin-arm64": { - "version": "4.34.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.34.6.tgz", - "integrity": "sha512-z9Ib+OzqN3DZEjX7PDQMHEhtF+t6Mi2z/ueChQPLS/qUMKY7Ybn5A2ggFoKRNRh1q1T03YTQfBTQCJZiepESAg==", + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.52.2.tgz", + "integrity": "sha512-vngduywkkv8Fkh3wIZf5nFPXzWsNsVu1kvtLETWxTFf/5opZmflgVSeLgdHR56RQh71xhPhWoOkEBvbehwTlVA==", "dev": true, "optional": true }, "@rollup/rollup-darwin-x64": { - "version": "4.34.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.34.6.tgz", - "integrity": "sha512-PShKVY4u0FDAR7jskyFIYVyHEPCPnIQY8s5OcXkdU8mz3Y7eXDJPdyM/ZWjkYdR2m0izD9HHWA8sGcXn+Qrsyg==", + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.52.2.tgz", + "integrity": "sha512-h11KikYrUCYTrDj6h939hhMNlqU2fo/X4NB0OZcys3fya49o1hmFaczAiJWVAFgrM1NCP6RrO7lQKeVYSKBPSQ==", "dev": true, "optional": true }, "@rollup/rollup-freebsd-arm64": { - "version": "4.34.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.34.6.tgz", - "integrity": "sha512-YSwyOqlDAdKqs0iKuqvRHLN4SrD2TiswfoLfvYXseKbL47ht1grQpq46MSiQAx6rQEN8o8URtpXARCpqabqxGQ==", + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.52.2.tgz", + "integrity": "sha512-/eg4CI61ZUkLXxMHyVlmlGrSQZ34xqWlZNW43IAU4RmdzWEx0mQJ2mN/Cx4IHLVZFL6UBGAh+/GXhgvGb+nVxw==", "dev": true, "optional": true }, "@rollup/rollup-freebsd-x64": { - "version": "4.34.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.34.6.tgz", - "integrity": "sha512-HEP4CgPAY1RxXwwL5sPFv6BBM3tVeLnshF03HMhJYCNc6kvSqBgTMmsEjb72RkZBAWIqiPUyF1JpEBv5XT9wKQ==", + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.52.2.tgz", + "integrity": "sha512-QOWgFH5X9+p+S1NAfOqc0z8qEpJIoUHf7OWjNUGOeW18Mx22lAUOiA9b6r2/vpzLdfxi/f+VWsYjUOMCcYh0Ng==", "dev": true, "optional": true }, "@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.34.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.34.6.tgz", - "integrity": "sha512-88fSzjC5xeH9S2Vg3rPgXJULkHcLYMkh8faix8DX4h4TIAL65ekwuQMA/g2CXq8W+NJC43V6fUpYZNjaX3+IIg==", + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.52.2.tgz", + "integrity": "sha512-kDWSPafToDd8LcBYd1t5jw7bD5Ojcu12S3uT372e5HKPzQt532vW+rGFFOaiR0opxePyUkHrwz8iWYEyH1IIQA==", "dev": true, "optional": true }, "@rollup/rollup-linux-arm-musleabihf": { - "version": "4.34.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.34.6.tgz", - "integrity": "sha512-wM4ztnutBqYFyvNeR7Av+reWI/enK9tDOTKNF+6Kk2Q96k9bwhDDOlnCUNRPvromlVXo04riSliMBs/Z7RteEg==", + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.52.2.tgz", + "integrity": "sha512-gKm7Mk9wCv6/rkzwCiUC4KnevYhlf8ztBrDRT9g/u//1fZLapSRc+eDZj2Eu2wpJ+0RzUKgtNijnVIB4ZxyL+w==", "dev": true, "optional": true }, "@rollup/rollup-linux-arm64-gnu": { - "version": "4.34.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.34.6.tgz", - "integrity": "sha512-9RyprECbRa9zEjXLtvvshhw4CMrRa3K+0wcp3KME0zmBe1ILmvcVHnypZ/aIDXpRyfhSYSuN4EPdCCj5Du8FIA==", + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.52.2.tgz", + "integrity": "sha512-66lA8vnj5mB/rtDNwPgrrKUOtCLVQypkyDa2gMfOefXK6rcZAxKLO9Fy3GkW8VkPnENv9hBkNOFfGLf6rNKGUg==", "dev": true, "optional": true }, "@rollup/rollup-linux-arm64-musl": { - "version": "4.34.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.34.6.tgz", - "integrity": "sha512-qTmklhCTyaJSB05S+iSovfo++EwnIEZxHkzv5dep4qoszUMX5Ca4WM4zAVUMbfdviLgCSQOu5oU8YoGk1s6M9Q==", + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.52.2.tgz", + "integrity": "sha512-s+OPucLNdJHvuZHuIz2WwncJ+SfWHFEmlC5nKMUgAelUeBUnlB4wt7rXWiyG4Zn07uY2Dd+SGyVa9oyLkVGOjA==", "dev": true, "optional": true }, - "@rollup/rollup-linux-loongarch64-gnu": { - "version": "4.34.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.34.6.tgz", - "integrity": "sha512-4Qmkaps9yqmpjY5pvpkfOerYgKNUGzQpFxV6rnS7c/JfYbDSU0y6WpbbredB5cCpLFGJEqYX40WUmxMkwhWCjw==", + "@rollup/rollup-linux-loong64-gnu": { + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.52.2.tgz", + "integrity": "sha512-8wTRM3+gVMDLLDdaT6tKmOE3lJyRy9NpJUS/ZRWmLCmOPIJhVyXwjBo+XbrrwtV33Em1/eCTd5TuGJm4+DmYjw==", "dev": true, "optional": true }, - "@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.34.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.34.6.tgz", - "integrity": "sha512-Zsrtux3PuaxuBTX/zHdLaFmcofWGzaWW1scwLU3ZbW/X+hSsFbz9wDIp6XvnT7pzYRl9MezWqEqKy7ssmDEnuQ==", + "@rollup/rollup-linux-ppc64-gnu": { + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.52.2.tgz", + "integrity": "sha512-6yqEfgJ1anIeuP2P/zhtfBlDpXUb80t8DpbYwXQ3bQd95JMvUaqiX+fKqYqUwZXqdJDd8xdilNtsHM2N0cFm6A==", "dev": true, "optional": true }, "@rollup/rollup-linux-riscv64-gnu": { - "version": "4.34.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.34.6.tgz", - "integrity": "sha512-aK+Zp+CRM55iPrlyKiU3/zyhgzWBxLVrw2mwiQSYJRobCURb781+XstzvA8Gkjg/hbdQFuDw44aUOxVQFycrAg==", + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.52.2.tgz", + "integrity": "sha512-sshYUiYVSEI2B6dp4jMncwxbrUqRdNApF2c3bhtLAU0qA8Lrri0p0NauOsTWh3yCCCDyBOjESHMExonp7Nzc0w==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-riscv64-musl": { + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.52.2.tgz", + "integrity": "sha512-duBLgd+3pqC4MMwBrKkFxaZerUxZcYApQVC5SdbF5/e/589GwVvlRUnyqMFbM8iUSb1BaoX/3fRL7hB9m2Pj8Q==", "dev": true, "optional": true }, "@rollup/rollup-linux-s390x-gnu": { - "version": "4.34.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.34.6.tgz", - "integrity": "sha512-WoKLVrY9ogmaYPXwTH326+ErlCIgMmsoRSx6bO+l68YgJnlOXhygDYSZe/qbUJCSiCiZAQ+tKm88NcWuUXqOzw==", + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.52.2.tgz", + "integrity": "sha512-tzhYJJidDUVGMgVyE+PmxENPHlvvqm1KILjjZhB8/xHYqAGeizh3GBGf9u6WdJpZrz1aCpIIHG0LgJgH9rVjHQ==", "dev": true, "optional": true }, "@rollup/rollup-linux-x64-gnu": { - "version": "4.34.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.34.6.tgz", - "integrity": "sha512-Sht4aFvmA4ToHd2vFzwMFaQCiYm2lDFho5rPcvPBT5pCdC+GwHG6CMch4GQfmWTQ1SwRKS0dhDYb54khSrjDWw==", + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.52.2.tgz", + "integrity": "sha512-opH8GSUuVcCSSyHHcl5hELrmnk4waZoVpgn/4FDao9iyE4WpQhyWJ5ryl5M3ocp4qkRuHfyXnGqg8M9oKCEKRA==", "dev": true, "optional": true }, "@rollup/rollup-linux-x64-musl": { - "version": "4.34.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.34.6.tgz", - "integrity": "sha512-zmmpOQh8vXc2QITsnCiODCDGXFC8LMi64+/oPpPx5qz3pqv0s6x46ps4xoycfUiVZps5PFn1gksZzo4RGTKT+A==", + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.52.2.tgz", + "integrity": "sha512-LSeBHnGli1pPKVJ79ZVJgeZWWZXkEe/5o8kcn23M8eMKCUANejchJbF/JqzM4RRjOJfNRhKJk8FuqL1GKjF5oQ==", + "dev": true, + "optional": true + }, + "@rollup/rollup-openharmony-arm64": { + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.52.2.tgz", + "integrity": "sha512-uPj7MQ6/s+/GOpolavm6BPo+6CbhbKYyZHUDvZ/SmJM7pfDBgdGisFX3bY/CBDMg2ZO4utfhlApkSfZ92yXw7Q==", "dev": true, "optional": true }, "@rollup/rollup-win32-arm64-msvc": { - "version": "4.34.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.34.6.tgz", - "integrity": "sha512-3/q1qUsO/tLqGBaD4uXsB6coVGB3usxw3qyeVb59aArCgedSF66MPdgRStUd7vbZOsko/CgVaY5fo2vkvPLWiA==", + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.52.2.tgz", + "integrity": "sha512-Z9MUCrSgIaUeeHAiNkm3cQyst2UhzjPraR3gYYfOjAuZI7tcFRTOD+4cHLPoS/3qinchth+V56vtqz1Tv+6KPA==", "dev": true, "optional": true }, "@rollup/rollup-win32-ia32-msvc": { - "version": "4.34.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.34.6.tgz", - "integrity": "sha512-oLHxuyywc6efdKVTxvc0135zPrRdtYVjtVD5GUm55I3ODxhU/PwkQFD97z16Xzxa1Fz0AEe4W/2hzRtd+IfpOA==", + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.52.2.tgz", + "integrity": "sha512-+GnYBmpjldD3XQd+HMejo+0gJGwYIOfFeoBQv32xF/RUIvccUz20/V6Otdv+57NE70D5pa8W/jVGDoGq0oON4A==", + "dev": true, + "optional": true + }, + "@rollup/rollup-win32-x64-gnu": { + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.52.2.tgz", + "integrity": "sha512-ApXFKluSB6kDQkAqZOKXBjiaqdF1BlKi+/eqnYe9Ee7U2K3pUDKsIyr8EYm/QDHTJIM+4X+lI0gJc3TTRhd+dA==", "dev": true, "optional": true }, "@rollup/rollup-win32-x64-msvc": { - "version": "4.34.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.34.6.tgz", - "integrity": "sha512-0PVwmgzZ8+TZ9oGBmdZoQVXflbvuwzN/HRclujpl4N/q3i+y0lqLw8n1bXA8ru3sApDjlmONaNAuYr38y1Kr9w==", + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.52.2.tgz", + "integrity": "sha512-ARz+Bs8kY6FtitYM96PqPEVvPXqEZmPZsSkXvyX19YzDqkCaIlhCieLLMI5hxO9SRZ2XtCtm8wxhy0iJ2jxNfw==", "dev": true, "optional": true }, @@ -11941,92 +12028,92 @@ } }, "@swc/core": { - "version": "1.10.15", - "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.10.15.tgz", - "integrity": "sha512-/iFeQuNaGdK7mfJbQcObhAhsMqLT7qgMYl7jX2GEIO+VDTejESpzAyKwaMeYXExN8D6e5BRHBCe7M5YlsuzjDA==", - "dev": true, - "requires": { - "@swc/core-darwin-arm64": "1.10.15", - "@swc/core-darwin-x64": "1.10.15", - "@swc/core-linux-arm-gnueabihf": "1.10.15", - "@swc/core-linux-arm64-gnu": "1.10.15", - "@swc/core-linux-arm64-musl": "1.10.15", - "@swc/core-linux-x64-gnu": "1.10.15", - "@swc/core-linux-x64-musl": "1.10.15", - "@swc/core-win32-arm64-msvc": "1.10.15", - "@swc/core-win32-ia32-msvc": "1.10.15", - "@swc/core-win32-x64-msvc": "1.10.15", + "version": "1.13.5", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.13.5.tgz", + "integrity": "sha512-WezcBo8a0Dg2rnR82zhwoR6aRNxeTGfK5QCD6TQ+kg3xx/zNT02s/0o+81h/3zhvFSB24NtqEr8FTw88O5W/JQ==", + "dev": true, + "requires": { + "@swc/core-darwin-arm64": "1.13.5", + "@swc/core-darwin-x64": "1.13.5", + "@swc/core-linux-arm-gnueabihf": "1.13.5", + "@swc/core-linux-arm64-gnu": "1.13.5", + "@swc/core-linux-arm64-musl": "1.13.5", + "@swc/core-linux-x64-gnu": "1.13.5", + "@swc/core-linux-x64-musl": "1.13.5", + "@swc/core-win32-arm64-msvc": "1.13.5", + "@swc/core-win32-ia32-msvc": "1.13.5", + "@swc/core-win32-x64-msvc": "1.13.5", "@swc/counter": "^0.1.3", - "@swc/types": "^0.1.17" + "@swc/types": "^0.1.24" } }, "@swc/core-darwin-arm64": { - "version": "1.10.15", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.10.15.tgz", - "integrity": "sha512-zFdZ6/yHqMCPk7OhLFqHy/MQ1EqJhcZMpNHd1gXYT7VRU3FaqvvKETrUlG3VYl65McPC7AhMRfXPyJ0JO/jARQ==", + "version": "1.13.5", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.13.5.tgz", + "integrity": "sha512-lKNv7SujeXvKn16gvQqUQI5DdyY8v7xcoO3k06/FJbHJS90zEwZdQiMNRiqpYw/orU543tPaWgz7cIYWhbopiQ==", "dev": true, "optional": true }, "@swc/core-darwin-x64": { - "version": "1.10.15", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.10.15.tgz", - "integrity": "sha512-8g4yiQwbr8fxOOjKXdot0dEkE5zgE8uNZudLy/ZyAhiwiZ8pbJ8/wVrDOu6dqbX7FBXAoDnvZ7fwN1jk4C8jdA==", + "version": "1.13.5", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.13.5.tgz", + "integrity": "sha512-ILd38Fg/w23vHb0yVjlWvQBoE37ZJTdlLHa8LRCFDdX4WKfnVBiblsCU9ar4QTMNdeTBEX9iUF4IrbNWhaF1Ng==", "dev": true, "optional": true }, "@swc/core-linux-arm-gnueabihf": { - "version": "1.10.15", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.10.15.tgz", - "integrity": "sha512-rl+eVOltl2+7WXOnvmWBpMgh6aO13G5x0U0g8hjwlmD6ku3Y9iRcThpOhm7IytMEarUp5pQxItNoPq+VUGjVHg==", + "version": "1.13.5", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.13.5.tgz", + "integrity": "sha512-Q6eS3Pt8GLkXxqz9TAw+AUk9HpVJt8Uzm54MvPsqp2yuGmY0/sNaPPNVqctCX9fu/Nu8eaWUen0si6iEiCsazQ==", "dev": true, "optional": true }, "@swc/core-linux-arm64-gnu": { - "version": "1.10.15", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.10.15.tgz", - "integrity": "sha512-qxWEQeyAJMWJqjaN4hi58WMpPdt3Tn0biSK9CYRegQtvZWCbewr6v2agtSu5AZ2rudeH6OfCWAMDQQeSgn6PJQ==", + "version": "1.13.5", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.13.5.tgz", + "integrity": "sha512-aNDfeN+9af+y+M2MYfxCzCy/VDq7Z5YIbMqRI739o8Ganz6ST+27kjQFd8Y/57JN/hcnUEa9xqdS3XY7WaVtSw==", "dev": true, "optional": true }, "@swc/core-linux-arm64-musl": { - "version": "1.10.15", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.10.15.tgz", - "integrity": "sha512-QcELd9/+HjZx0WCxRrKcyKGWTiQ0485kFb5w8waxcSNd0d9Lgk4EFfWWVyvIb5gIHpDQmhrgzI/yRaWQX4YSZQ==", + "version": "1.13.5", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.13.5.tgz", + "integrity": "sha512-9+ZxFN5GJag4CnYnq6apKTnnezpfJhCumyz0504/JbHLo+Ue+ZtJnf3RhyA9W9TINtLE0bC4hKpWi8ZKoETyOQ==", "dev": true, "optional": true }, "@swc/core-linux-x64-gnu": { - "version": "1.10.15", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.10.15.tgz", - "integrity": "sha512-S1+ZEEn3+a/MiMeQqQypbwTGoBG8/sPoCvpNbk+uValyygT+jSn3U0xVr45FbukpmMB+NhBMqfedMLqKA0QnJA==", + "version": "1.13.5", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.13.5.tgz", + "integrity": "sha512-WD530qvHrki8Ywt/PloKUjaRKgstQqNGvmZl54g06kA+hqtSE2FTG9gngXr3UJxYu/cNAjJYiBifm7+w4nbHbA==", "dev": true, "optional": true }, "@swc/core-linux-x64-musl": { - "version": "1.10.15", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.10.15.tgz", - "integrity": "sha512-qW+H9g/2zTJ4jP7NDw4VAALY0ZlNEKzYsEoSj/HKi7k3tYEHjMzsxjfsY9I8WZCft23bBdV3RTCPoxCshaj1CQ==", + "version": "1.13.5", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.13.5.tgz", + "integrity": "sha512-Luj8y4OFYx4DHNQTWjdIuKTq2f5k6uSXICqx+FSabnXptaOBAbJHNbHT/06JZh6NRUouaf0mYXN0mcsqvkhd7Q==", "dev": true, "optional": true }, "@swc/core-win32-arm64-msvc": { - "version": "1.10.15", - "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.10.15.tgz", - "integrity": "sha512-AhRB11aA6LxjIqut+mg7qsu/7soQDmbK6MKR9nP3hgBszpqtXbRba58lr24xIbBCMr+dpo6kgEapWt+t5Po6Zg==", + "version": "1.13.5", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.13.5.tgz", + "integrity": "sha512-cZ6UpumhF9SDJvv4DA2fo9WIzlNFuKSkZpZmPG1c+4PFSEMy5DFOjBSllCvnqihCabzXzpn6ykCwBmHpy31vQw==", "dev": true, "optional": true }, "@swc/core-win32-ia32-msvc": { - "version": "1.10.15", - "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.10.15.tgz", - "integrity": "sha512-UGdh430TQwbDn6KjgvRTg1fO022sbQ4yCCHUev0+5B8uoBwi9a89qAz3emy2m56C8TXxUoihW9Y9OMfaRwPXUw==", + "version": "1.13.5", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.13.5.tgz", + "integrity": "sha512-C5Yi/xIikrFUzZcyGj9L3RpKljFvKiDMtyDzPKzlsDrKIw2EYY+bF88gB6oGY5RGmv4DAX8dbnpRAqgFD0FMEw==", "dev": true, "optional": true }, "@swc/core-win32-x64-msvc": { - "version": "1.10.15", - "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.10.15.tgz", - "integrity": "sha512-XJzBCqO1m929qbJsOG7FZXQWX26TnEoMctS3QjuCoyBmkHxxQmZsy78KjMes1aomTcKHCyFYgrRGWgVmk7tT4Q==", + "version": "1.13.5", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.13.5.tgz", + "integrity": "sha512-YrKdMVxbYmlfybCSbRtrilc6UA8GF5aPmGKBdPvjrarvsmf4i7ZHGCEnLtfOMd3Lwbs2WUZq3WdMbozYeLU93Q==", "dev": true, "optional": true }, @@ -12037,18 +12124,18 @@ "dev": true }, "@swc/helpers": { - "version": "0.5.15", - "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", - "integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==", + "version": "0.5.17", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.17.tgz", + "integrity": "sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A==", "dev": true, "requires": { "tslib": "^2.8.0" } }, "@swc/types": { - "version": "0.1.17", - "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.17.tgz", - "integrity": "sha512-V5gRru+aD8YVyCOMAjMpWR1Ui577DD5KSJsHP8RAxopAH22jFz6GZd/qxqjO6MJHQhcsjvjOFXyDhyLQUnMveQ==", + "version": "0.1.25", + "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.25.tgz", + "integrity": "sha512-iAoY/qRhNH8a/hBvm3zKj9qQ4oc2+3w1unPJa2XvTK3XjeLXtzcCingVPw/9e5mn1+0yPqxcBGp9Jf0pkfMb1g==", "dev": true, "requires": { "@swc/counter": "^0.1.3" @@ -12071,13 +12158,13 @@ "@rollup/plugin-node-resolve": "^16.0.0", "@rollup/plugin-replace": "^6.0.2", "@swc/cli": "^0.7.3", - "@swc/core": "^1.10.15", + "@swc/core": "^1.11.22", "@swc/helpers": "^0.5.15", "@theoplayer/web-ui": "^1.14.0", "@types/react": "^18.2.79", "react": "^18.2.0", "react-dom": "^18.2.0", - "rollup": "^4.34.6", + "rollup": "^4.40.1", "rollup-plugin-dts": "^6.1.1", "rollup-plugin-swc3": "^0.12.1", "theoplayer": "^10.6.1", @@ -12119,9 +12206,9 @@ } }, "@types/estree": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", - "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", "dev": true }, "@types/hast": { @@ -15421,31 +15508,34 @@ "dev": true }, "rollup": { - "version": "4.34.6", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.34.6.tgz", - "integrity": "sha512-wc2cBWqJgkU3Iz5oztRkQbfVkbxoz5EhnCGOrnJvnLnQ7O0WhQUYyv18qQI79O8L7DdHrrlJNeCHd4VGpnaXKQ==", - "dev": true, - "requires": { - "@rollup/rollup-android-arm-eabi": "4.34.6", - "@rollup/rollup-android-arm64": "4.34.6", - "@rollup/rollup-darwin-arm64": "4.34.6", - "@rollup/rollup-darwin-x64": "4.34.6", - "@rollup/rollup-freebsd-arm64": "4.34.6", - "@rollup/rollup-freebsd-x64": "4.34.6", - "@rollup/rollup-linux-arm-gnueabihf": "4.34.6", - "@rollup/rollup-linux-arm-musleabihf": "4.34.6", - "@rollup/rollup-linux-arm64-gnu": "4.34.6", - "@rollup/rollup-linux-arm64-musl": "4.34.6", - "@rollup/rollup-linux-loongarch64-gnu": "4.34.6", - "@rollup/rollup-linux-powerpc64le-gnu": "4.34.6", - "@rollup/rollup-linux-riscv64-gnu": "4.34.6", - "@rollup/rollup-linux-s390x-gnu": "4.34.6", - "@rollup/rollup-linux-x64-gnu": "4.34.6", - "@rollup/rollup-linux-x64-musl": "4.34.6", - "@rollup/rollup-win32-arm64-msvc": "4.34.6", - "@rollup/rollup-win32-ia32-msvc": "4.34.6", - "@rollup/rollup-win32-x64-msvc": "4.34.6", - "@types/estree": "1.0.6", + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.2.tgz", + "integrity": "sha512-I25/2QgoROE1vYV+NQ1En9T9UFB9Cmfm2CJ83zZOlaDpvz29wGQSZXWKw7MiNXau7wYgB/T9fVIdIuEQ+KbiiA==", + "dev": true, + "requires": { + "@rollup/rollup-android-arm-eabi": "4.52.2", + "@rollup/rollup-android-arm64": "4.52.2", + "@rollup/rollup-darwin-arm64": "4.52.2", + "@rollup/rollup-darwin-x64": "4.52.2", + "@rollup/rollup-freebsd-arm64": "4.52.2", + "@rollup/rollup-freebsd-x64": "4.52.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.52.2", + "@rollup/rollup-linux-arm-musleabihf": "4.52.2", + "@rollup/rollup-linux-arm64-gnu": "4.52.2", + "@rollup/rollup-linux-arm64-musl": "4.52.2", + "@rollup/rollup-linux-loong64-gnu": "4.52.2", + "@rollup/rollup-linux-ppc64-gnu": "4.52.2", + "@rollup/rollup-linux-riscv64-gnu": "4.52.2", + "@rollup/rollup-linux-riscv64-musl": "4.52.2", + "@rollup/rollup-linux-s390x-gnu": "4.52.2", + "@rollup/rollup-linux-x64-gnu": "4.52.2", + "@rollup/rollup-linux-x64-musl": "4.52.2", + "@rollup/rollup-openharmony-arm64": "4.52.2", + "@rollup/rollup-win32-arm64-msvc": "4.52.2", + "@rollup/rollup-win32-ia32-msvc": "4.52.2", + "@rollup/rollup-win32-x64-gnu": "4.52.2", + "@rollup/rollup-win32-x64-msvc": "4.52.2", + "@types/estree": "1.0.8", "fsevents": "~2.3.2" } }, @@ -16326,9 +16416,9 @@ } }, "@types/estree": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", - "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", "dev": true }, "@types/hast": { @@ -19628,31 +19718,34 @@ "dev": true }, "rollup": { - "version": "4.34.6", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.34.6.tgz", - "integrity": "sha512-wc2cBWqJgkU3Iz5oztRkQbfVkbxoz5EhnCGOrnJvnLnQ7O0WhQUYyv18qQI79O8L7DdHrrlJNeCHd4VGpnaXKQ==", - "dev": true, - "requires": { - "@rollup/rollup-android-arm-eabi": "4.34.6", - "@rollup/rollup-android-arm64": "4.34.6", - "@rollup/rollup-darwin-arm64": "4.34.6", - "@rollup/rollup-darwin-x64": "4.34.6", - "@rollup/rollup-freebsd-arm64": "4.34.6", - "@rollup/rollup-freebsd-x64": "4.34.6", - "@rollup/rollup-linux-arm-gnueabihf": "4.34.6", - "@rollup/rollup-linux-arm-musleabihf": "4.34.6", - "@rollup/rollup-linux-arm64-gnu": "4.34.6", - "@rollup/rollup-linux-arm64-musl": "4.34.6", - "@rollup/rollup-linux-loongarch64-gnu": "4.34.6", - "@rollup/rollup-linux-powerpc64le-gnu": "4.34.6", - "@rollup/rollup-linux-riscv64-gnu": "4.34.6", - "@rollup/rollup-linux-s390x-gnu": "4.34.6", - "@rollup/rollup-linux-x64-gnu": "4.34.6", - "@rollup/rollup-linux-x64-musl": "4.34.6", - "@rollup/rollup-win32-arm64-msvc": "4.34.6", - "@rollup/rollup-win32-ia32-msvc": "4.34.6", - "@rollup/rollup-win32-x64-msvc": "4.34.6", - "@types/estree": "1.0.6", + "version": "4.52.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.2.tgz", + "integrity": "sha512-I25/2QgoROE1vYV+NQ1En9T9UFB9Cmfm2CJ83zZOlaDpvz29wGQSZXWKw7MiNXau7wYgB/T9fVIdIuEQ+KbiiA==", + "dev": true, + "requires": { + "@rollup/rollup-android-arm-eabi": "4.52.2", + "@rollup/rollup-android-arm64": "4.52.2", + "@rollup/rollup-darwin-arm64": "4.52.2", + "@rollup/rollup-darwin-x64": "4.52.2", + "@rollup/rollup-freebsd-arm64": "4.52.2", + "@rollup/rollup-freebsd-x64": "4.52.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.52.2", + "@rollup/rollup-linux-arm-musleabihf": "4.52.2", + "@rollup/rollup-linux-arm64-gnu": "4.52.2", + "@rollup/rollup-linux-arm64-musl": "4.52.2", + "@rollup/rollup-linux-loong64-gnu": "4.52.2", + "@rollup/rollup-linux-ppc64-gnu": "4.52.2", + "@rollup/rollup-linux-riscv64-gnu": "4.52.2", + "@rollup/rollup-linux-riscv64-musl": "4.52.2", + "@rollup/rollup-linux-s390x-gnu": "4.52.2", + "@rollup/rollup-linux-x64-gnu": "4.52.2", + "@rollup/rollup-linux-x64-musl": "4.52.2", + "@rollup/rollup-openharmony-arm64": "4.52.2", + "@rollup/rollup-win32-arm64-msvc": "4.52.2", + "@rollup/rollup-win32-ia32-msvc": "4.52.2", + "@rollup/rollup-win32-x64-gnu": "4.52.2", + "@rollup/rollup-win32-x64-msvc": "4.52.2", + "@types/estree": "1.0.8", "fsevents": "~2.3.2" } }, diff --git a/package.json b/package.json index 437dda73..314955b2 100644 --- a/package.json +++ b/package.json @@ -69,7 +69,7 @@ "@rollup/plugin-virtual": "^3.0.2", "@rollup/pluginutils": "^5.1.4", "@swc/cli": "^0.7.3", - "@swc/core": "^1.10.15", + "@swc/core": "^1.11.22", "@swc/helpers": "^0.5.15", "@types/html-minifier": "^4.0.5", "@webcomponents/shadycss": "^1.11.2", @@ -79,7 +79,7 @@ "postcss-mixins": "^11.0.3", "postcss-preset-env": "^10.1.3", "prettier": "^3.6.2", - "rollup": "^4.34.6", + "rollup": "^4.40.1", "rollup-plugin-dts": "^6.1.1", "rollup-plugin-minify-html-literals-v3": "^1.3.3", "rollup-plugin-postcss": "^4.0.2", diff --git a/react/package.json b/react/package.json index 1cb87f4d..a2d7b0d7 100644 --- a/react/package.json +++ b/react/package.json @@ -63,12 +63,12 @@ "@rollup/plugin-node-resolve": "^16.0.0", "@rollup/plugin-replace": "^6.0.2", "@swc/cli": "^0.7.3", - "@swc/core": "^1.10.15", + "@swc/core": "^1.11.22", "@swc/helpers": "^0.5.15", "@types/react": "^18.2.79", "react": "^18.2.0", "react-dom": "^18.2.0", - "rollup": "^4.34.6", + "rollup": "^4.40.1", "rollup-plugin-dts": "^6.1.1", "rollup-plugin-swc3": "^0.12.1", "theoplayer": "^10.6.1", From aa85c64a6aa6cd8f810ee807bc90884a316ef96c Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Tue, 6 May 2025 19:03:03 +0200 Subject: [PATCH 088/123] Fix imported .html and .css files not properly transpiled --- rollup.config.mjs | 1 + 1 file changed, 1 insertion(+) diff --git a/rollup.config.mjs b/rollup.config.mjs index 5859d85b..205c6def 100644 --- a/rollup.config.mjs +++ b/rollup.config.mjs @@ -192,6 +192,7 @@ function jsPlugins({ es5 = false, node = false, module = false, production = fal // Transpile TypeScript. swc({ include: './src/**', + extensions: ['.ts', '.js', '.html', '.css'], sourceMaps: sourcemap, tsconfig: false, env: { From bb1bd035b2e18023ab478f7c102c2ed62f0416bb Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Tue, 6 May 2025 19:28:46 +0200 Subject: [PATCH 089/123] Don't use loose mode on Lit --- rollup.config.mjs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/rollup.config.mjs b/rollup.config.mjs index 205c6def..941ccb1f 100644 --- a/rollup.config.mjs +++ b/rollup.config.mjs @@ -218,12 +218,20 @@ function jsPlugins({ es5 = false, node = false, module = false, production = fal sourceMaps: sourcemap, tsconfig: false, env: { - loose: true, + loose: false, targets: browserslist }, jsc: { - loose: true, - externalHelpers: true + loose: false, + externalHelpers: true, + assumptions: { + constantSuper: true, + noDocumentAll: true, + ignoreFunctionName: true, + ignoreFunctionLength: true, + ignoreToPrimitiveHint: true, + iterableIsArray: false // must NEVER be true! + } } }), // Minify production builds. From 8ca0ecf9e015cbd849250cc906065c3c7795597e Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Tue, 6 May 2025 19:29:15 +0200 Subject: [PATCH 090/123] Update docs --- README.md | 6 +++--- docs/getting-started.mdx | 13 +++++++------ 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 798f467a..6dfe0ece 100644 --- a/README.md +++ b/README.md @@ -131,9 +131,9 @@ See [custom-ui/demo.html](https://github.com/THEOplayer/web-ui/blob/main/docs/st By default, Open Video UI for Web targets modern browsers that support modern JavaScript syntax (such as [async/await](https://caniuse.com/async-functions)) and native [Custom Elements](https://caniuse.com/custom-elementsv1). This keeps the download size small, so your viewers can spend less time waiting for your page to load and start watching their video faster. -On older browsers (such as Internet Explorer 11 and older smart TVs), you need to load a different version of the Open Video UI that uses older JavaScript syntax. You also need to load additional polyfills for missing features such as Promises or Custom Elements: +On older browsers (such as Internet Explorer 11 and older smart TVs), you need to load a different version of the Open Video UI that uses older JavaScript syntax. You also need to load additional polyfills for missing features such as `Promise`, `Symbol.iterator` or Custom Elements: -- For Promises, we recommend [the Cloudflare mirror of Polyfill.io](https://cdnjs.cloudflare.com/polyfill/). +- For ES2015 features like `Promise` and `Symbol.iterator`, we recommend [the Cloudflare mirror of Polyfill.io](https://cdnjs.cloudflare.com/polyfill/). - For Custom Elements, we recommend loading our polyfill bundle from `@theoplayer/web-ui/polyfills`. Alternatively, you can load the [Web Components Polyfills](https://github.com/webcomponents/polyfills). * Option 1: in your HTML. This uses [differential serving](https://css-tricks.com/differential-serving/) so modern browsers will load the modern build (with `type="module"`), while legacy browsers will load the legacy build (with `nomodule`). @@ -151,7 +151,7 @@ On older browsers (such as Internet Explorer 11 and older smart TVs), you need t - + diff --git a/docs/getting-started.mdx b/docs/getting-started.mdx index c89bc671..edf5c454 100644 --- a/docs/getting-started.mdx +++ b/docs/getting-started.mdx @@ -110,7 +110,10 @@ See [this page](examples/custom-ui.mdx) for a complete example. By default, Open Video UI for Web targets modern browsers that support modern JavaScript syntax (such as [async/await](https://caniuse.com/async-functions)) and native [Custom Elements](https://caniuse.com/custom-elementsv1). This keeps the download size small, so your viewers can spend less time waiting for your page to load and start watching their video faster. -On older browsers (such as Internet Explorer 11 and older smart TVs), you need to load a different version of the Open Video UI that uses older JavaScript syntax. You also need to load additional polyfills for missing features such as Promises or Custom Elements. We recommend [the Cloudflare mirror of Polyfill.io](https://cdnjs.cloudflare.com/polyfill/) and [Web Components Polyfills](https://github.com/webcomponents/polyfills) for these. +On older browsers (such as Internet Explorer 11 and older smart TVs), you need to load a different version of the Open Video UI that uses older JavaScript syntax. You also need to load additional polyfills for missing features such as `Promise`, `Symbol.iterator` or Custom Elements: + +- For ES2015 features like `Promise` and `Symbol.iterator`, we recommend [the Cloudflare mirror of Polyfill.io](https://cdnjs.cloudflare.com/polyfill/). +- For Custom Elements, we recommend loading our polyfill bundle from `@theoplayer/web-ui/polyfills`. Alternatively, you can load the [Web Components Polyfills](https://github.com/webcomponents/polyfills). - Option 1: in your HTML. This uses [differential serving](https://css-tricks.com/differential-serving/) so modern browsers will load the modern build (with `type="module"`), while legacy browsers will load the legacy build (with `nomodule`). @@ -127,17 +130,15 @@ On older browsers (such as Internet Explorer 11 and older smart TVs), you need t - - - + + ``` - Option 2: in your JavaScript. This will load the legacy build on both modern and legacy browsers, which is suboptimal. Instead, we recommend configuring your bundler to produce a modern and legacy build of your entire web app, and to import the appropriate version of Open Video UI for each build flavor. ```js - import '@webcomponents/webcomponentsjs/custom-elements-es5-adapter.js'; - import '@webcomponents/webcomponentsjs/webcomponents-bundle.js'; + import '@theoplayer/web-ui/polyfills'; import { DefaultUI } from '@theoplayer/web-ui/es5'; // note the "/es5" suffix ``` From 5bd6a738616b988bc89fbfb4693745951d9e7340 Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Tue, 6 May 2025 18:21:19 +0200 Subject: [PATCH 091/123] Fix typo --- src/DefaultUI.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/DefaultUI.ts b/src/DefaultUI.ts index 27b38c18..5b18b722 100644 --- a/src/DefaultUI.ts +++ b/src/DefaultUI.ts @@ -170,7 +170,7 @@ export class DefaultUI extends LitElement { } /** - * Whether to automatically adjusts the player's height to fit the video's aspect ratio. + * Whether to automatically adjust the player's height to fit the video's aspect ratio. */ @property({ reflect: true, type: Boolean, attribute: Attribute.FLUID }) accessor fluid: boolean = false; From 28216e75a0ae58513747862b02c55a145e8da126 Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Wed, 7 May 2025 15:44:17 +0200 Subject: [PATCH 092/123] Add polyfills for queueMicrotask and Array.flat --- examples/default-ui.html | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/examples/default-ui.html b/examples/default-ui.html index fc8463a6..8ad5a026 100644 --- a/examples/default-ui.html +++ b/examples/default-ui.html @@ -37,7 +37,10 @@ self.THEOplayerUI = THEOplayerUI; - + From c22b769a8c81274432e6e0dd5a2f648e47b3e084 Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Wed, 7 May 2025 15:44:32 +0200 Subject: [PATCH 093/123] Load polyfill-support from Lit itself --- src/polyfills.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/polyfills.ts b/src/polyfills.ts index 5f5b4c4c..7b91e75a 100644 --- a/src/polyfills.ts +++ b/src/polyfills.ts @@ -1,3 +1,3 @@ import '@webcomponents/webcomponentsjs/custom-elements-es5-adapter.js'; import '@webcomponents/webcomponentsjs/webcomponents-bundle.js'; -import 'lit-html/polyfill-support.js'; +import 'lit/polyfill-support.js'; From c0c21e3b934aa504f5e6d7a7190d5d6e589f1a75 Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Wed, 7 May 2025 17:18:27 +0200 Subject: [PATCH 094/123] Simplify menu table CSS --- src/components/MenuTable.css | 16 ++++++++-------- src/components/SettingsMenu.ts | 2 +- src/components/TextTrackStyleMenu.ts | 2 +- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/components/MenuTable.css b/src/components/MenuTable.css index c6bed5f5..9d74fa56 100644 --- a/src/components/MenuTable.css +++ b/src/components/MenuTable.css @@ -1,31 +1,31 @@ -.theoplayer-menu-table { +table { table-layout: auto; border-collapse: collapse; } -.theoplayer-menu-table, -.theoplayer-menu-table tr, -.theoplayer-menu-table td { +table, +table tr, +table td { margin: 0; padding: 0; } -.theoplayer-menu-table td > * { +table td > * { font-size: var(--theoplayer-text-font-size, 14px); line-height: var(--theoplayer-text-content-height, var(--theoplayer-control-height, 24px)); padding: var(--theoplayer-control-padding, 10px); } -.theoplayer-menu-table td:first-child { +table td:first-child { width: 0; white-space: nowrap; } -.theoplayer-menu-table td:last-child { +table td:last-child { text-align: center; } -.theoplayer-menu-table td > theoplayer-menu-button { +table td > theoplayer-menu-button { /* Fill entire cell */ display: flex; } diff --git a/src/components/SettingsMenu.ts b/src/components/SettingsMenu.ts index 7b99bddb..6b6ddee0 100644 --- a/src/components/SettingsMenu.ts +++ b/src/components/SettingsMenu.ts @@ -24,7 +24,7 @@ export class SettingsMenu extends MenuGroup { return html` Settings - +
Quality diff --git a/src/components/TextTrackStyleMenu.ts b/src/components/TextTrackStyleMenu.ts index 5b904f01..64ba1a63 100644 --- a/src/components/TextTrackStyleMenu.ts +++ b/src/components/TextTrackStyleMenu.ts @@ -70,7 +70,7 @@ export class TextTrackStyleMenu extends MenuGroup { class="theoplayer-menu-heading-button" slot="heading" > - +
Font family From bc8188ec333e5959da655dda640aff8667e54c25 Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Wed, 7 May 2025 18:16:35 +0200 Subject: [PATCH 095/123] Add RadioGroup.value --- src/components/PlaybackRateRadioGroup.ts | 30 +++++++---------- src/components/RadioGroup.ts | 39 +++++++++++++++++++--- src/components/TextTrackStyleRadioGroup.ts | 30 +++++++---------- 3 files changed, 59 insertions(+), 40 deletions(-) diff --git a/src/components/PlaybackRateRadioGroup.ts b/src/components/PlaybackRateRadioGroup.ts index ffa23251..829a4099 100644 --- a/src/components/PlaybackRateRadioGroup.ts +++ b/src/components/PlaybackRateRadioGroup.ts @@ -27,10 +27,11 @@ export class PlaybackRateRadioGroup extends LitElement { private _value: number = 1; protected override firstUpdated(): void { - if (this._radioGroupRef.value && !(this._radioGroupRef.value instanceof RadioGroup)) { - customElements.upgrade(this._radioGroupRef.value); + const radioGroup = this._radioGroupRef.value!; + if (!(radioGroup instanceof RadioGroup)) { + customElements.upgrade(radioGroup); } - this._updateChecked(); + radioGroup.value = String(this.value); } /** @@ -50,7 +51,10 @@ export class PlaybackRateRadioGroup extends LitElement { if (this._player !== undefined) { this._player.playbackRate = value; } - this._updateChecked(); + const radioGroup = this._radioGroupRef.value; + if (radioGroup) { + radioGroup.value = String(value); + } this.dispatchEvent(createEvent('change', { bubbles: true })); } @@ -73,18 +77,10 @@ export class PlaybackRateRadioGroup extends LitElement { } } - private readonly _updateChecked = (): void => { - const buttons = this._radioGroupRef.value?.allRadioButtons() ?? []; - for (const button of buttons) { - button.checked = Number(button.value) === this.value; - } - }; - private readonly _onChange = (): void => { - const button = this._radioGroupRef.value?.checkedRadioButton; - if (button && this.value !== Number(button.value)) { - this.value = button.value; - } + const radioGroup = this._radioGroupRef.value; + if (!radioGroup) return; + this.value = Number(radioGroup.value); }; private readonly _updateFromPlayer = (): void => { @@ -94,9 +90,7 @@ export class PlaybackRateRadioGroup extends LitElement { }; protected override render(): HTMLTemplateResult { - return html``; + return html``; } } diff --git a/src/components/RadioGroup.ts b/src/components/RadioGroup.ts index dd983c65..64a73b96 100644 --- a/src/components/RadioGroup.ts +++ b/src/components/RadioGroup.ts @@ -30,6 +30,7 @@ import { navigateByArrowKey } from '../util/KeyboardNavigation'; export class RadioGroup extends LitElement { private readonly _slotRef: Ref = createRef(); private _radioButtons: RadioButton[] = []; + private _value: any = undefined; constructor() { super(); @@ -60,6 +61,10 @@ export class RadioGroup extends LitElement { this.removeEventListener('keydown', this._onKeyDown); } + protected override firstUpdated(): void { + this._onSlotChange(); + } + protected override createRenderRoot(): HTMLElement | DocumentFragment { const root = super.createRenderRoot(); root.addEventListener('change', this._onButtonChange); @@ -69,6 +74,23 @@ export class RadioGroup extends LitElement { @property({ reflect: true, type: String, attribute: Attribute.DEVICE_TYPE }) accessor deviceType: DeviceType = 'desktop'; + /** + * The selected value. + */ + get value(): any { + return this._value; + } + + @property({ attribute: Attribute.VALUE }) + set value(value: any) { + if (this._value === value) { + return; + } + this._value = value; + this.updateCheckedButton(); + this.dispatchEvent(createEvent('change', { bubbles: true })); + } + private readonly _onSlotChange = () => { const slot = this._slotRef.value; if (!slot) return; @@ -92,6 +114,8 @@ export class RadioGroup extends LitElement { if (!firstFocusedButton) { this.firstRadioButton?.setAttribute('tabindex', '0'); } + + this.updateCheckedButton(); }; private readonly _onKeyDown = (event: KeyboardEvent) => { @@ -196,7 +220,7 @@ export class RadioGroup extends LitElement { return true; } - setFocusedRadioButton(button: RadioButton | null): void { + private setFocusedRadioButton(button: RadioButton | null): void { this._unfocusAll(); if (button) { button.tabIndex = 0; @@ -210,17 +234,24 @@ export class RadioGroup extends LitElement { } } - setCheckedRadioButton(checkedButton: RadioButton | null): void { + private setCheckedRadioButton(checkedButton: RadioButton | null): void { for (const button of this.allRadioButtons()) { button.checked = button === checkedButton; } - this.dispatchEvent(createEvent('change', { bubbles: true })); + } + + private updateCheckedButton(): void { + const button = this.allRadioButtons().find((button) => { + // Allow '1' == 1 + return button.value == this.value; + }); + this.setCheckedRadioButton(button ?? null); } private readonly _onButtonChange = (event: Event) => { const button = event.target as RadioButton | null; if (button !== null && button.checked) { - this.setCheckedRadioButton(event.target as RadioButton); + this.value = button.value; } }; diff --git a/src/components/TextTrackStyleRadioGroup.ts b/src/components/TextTrackStyleRadioGroup.ts index 40c240ad..2edf756e 100644 --- a/src/components/TextTrackStyleRadioGroup.ts +++ b/src/components/TextTrackStyleRadioGroup.ts @@ -45,10 +45,11 @@ export class TextTrackStyleRadioGroup extends LitElement { private _value: any; protected override firstUpdated(): void { - if (this._radioGroupRef.value && !(this._radioGroupRef.value instanceof RadioGroup)) { - customElements.upgrade(this._radioGroupRef.value); + const radioGroup = this._radioGroupRef.value!; + if (!(radioGroup instanceof RadioGroup)) { + customElements.upgrade(radioGroup); } - this._updateChecked(); + radioGroup.value = this.value; } /** @@ -79,7 +80,10 @@ export class TextTrackStyleRadioGroup extends LitElement { } this._value = value; this._updateToPlayer(); - this._updateChecked(); + const radioGroup = this._radioGroupRef.value; + if (radioGroup) { + radioGroup.value = value; + } this.dispatchEvent(createEvent('change', { bubbles: true })); } @@ -99,18 +103,10 @@ export class TextTrackStyleRadioGroup extends LitElement { this._textTrackStyle?.addEventListener('change', this._updateFromPlayer); } - private readonly _updateChecked = (): void => { - const buttons = this._radioGroupRef.value?.allRadioButtons() ?? []; - for (const button of buttons) { - button.checked = button.value === this.value; - } - }; - private readonly _onChange = (): void => { - const button = this._radioGroupRef.value?.checkedRadioButton; - if (button && this.value !== button.value) { - this.value = button.value; - } + const radioGroup = this._radioGroupRef.value; + if (!radioGroup) return; + this.value = radioGroup.value; }; private readonly _updateFromPlayer = (): void => { @@ -192,9 +188,7 @@ export class TextTrackStyleRadioGroup extends LitElement { } protected override render(): HTMLTemplateResult { - return html``; + return html``; } } From 6d23274d6b0d23baefecc16a3d6001367632ba6f Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Wed, 7 May 2025 18:35:47 +0200 Subject: [PATCH 096/123] Add missing `@state` --- src/components/CastButton.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/components/CastButton.ts b/src/components/CastButton.ts index d09cdd60..a641e5e2 100644 --- a/src/components/CastButton.ts +++ b/src/components/CastButton.ts @@ -1,5 +1,5 @@ import { Button, type ButtonOptions } from './Button'; -import { property } from 'lit/decorators.js'; +import { property, state } from 'lit/decorators.js'; import { Attribute } from '../util/Attribute'; import type { CastState, VendorCast } from 'theoplayer/chromeless'; @@ -15,8 +15,6 @@ export class CastButton extends Button { constructor(options?: ButtonOptions) { super(options); - this._upgradeProperty('castState'); - this._upgradeProperty('castApi'); } override connectedCallback() { @@ -31,6 +29,7 @@ export class CastButton extends Button { return this._castApi; } + @state() set castApi(castApi: VendorCast | undefined) { if (this._castApi !== undefined) { this._castApi.removeEventListener('statechange', this._updateCastState); From f7c04b595835f6e6c573df1a72915ecb14274535 Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Wed, 7 May 2025 18:35:56 +0200 Subject: [PATCH 097/123] Remove _upgradeProperty helper --- src/components/Button.ts | 8 -------- src/components/ChromecastButton.ts | 1 - src/components/RadioGroup.ts | 9 --------- 3 files changed, 18 deletions(-) diff --git a/src/components/Button.ts b/src/components/Button.ts index 161a5c7d..6e495205 100644 --- a/src/components/Button.ts +++ b/src/components/Button.ts @@ -50,14 +50,6 @@ export class Button extends LitElement { } } - protected _upgradeProperty(prop: keyof this) { - if (this.hasOwnProperty(prop)) { - let value = this[prop]; - delete this[prop]; - this[prop] = value; - } - } - connectedCallback(): void { super.connectedCallback(); diff --git a/src/components/ChromecastButton.ts b/src/components/ChromecastButton.ts index dd525ed3..71375cdd 100644 --- a/src/components/ChromecastButton.ts +++ b/src/components/ChromecastButton.ts @@ -23,7 +23,6 @@ export class ChromecastButton extends CastButton { constructor() { super(); - this._upgradeProperty('player'); } override connectedCallback() { diff --git a/src/components/RadioGroup.ts b/src/components/RadioGroup.ts index 64a73b96..027645c5 100644 --- a/src/components/RadioGroup.ts +++ b/src/components/RadioGroup.ts @@ -34,15 +34,6 @@ export class RadioGroup extends LitElement { constructor() { super(); - this._upgradeProperty('deviceType'); - } - - protected _upgradeProperty(prop: keyof this) { - if (this.hasOwnProperty(prop)) { - let value = this[prop]; - delete this[prop]; - this[prop] = value; - } } connectedCallback(): void { From 7e739f55481ed59ee622253a725a563c1c3caa55 Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Wed, 24 Sep 2025 14:41:37 +0200 Subject: [PATCH 098/123] Fix exports --- src/index.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/index.ts b/src/index.ts index d469b310..9459521d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,8 +1,8 @@ export * from './components/index'; export * from './extensions/index'; -export * from './UIContainer'; -export * from './DefaultUI'; -export * from './THEOliveDefaultUI'; +export { UIContainer } from './UIContainer'; +export { DefaultUI } from './DefaultUI'; +export { THEOliveDefaultUI } from './THEOliveDefaultUI'; export { Attribute } from './util/Attribute'; export { type DeviceType } from './util/DeviceType'; export { type StreamType } from './util/StreamType'; From 6777f21120ed837f29b7a838340ab9ccd3b09384 Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Wed, 24 Sep 2025 14:49:50 +0200 Subject: [PATCH 099/123] Never transpile SWC helpers --- rollup.config.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rollup.config.mjs b/rollup.config.mjs index 941ccb1f..f77bc659 100644 --- a/rollup.config.mjs +++ b/rollup.config.mjs @@ -214,7 +214,7 @@ function jsPlugins({ es5 = false, node = false, module = false, production = fal // Transpile dependencies for older browsers. swc({ include: './node_modules/**', - exclude: './src/**', + exclude: ['./src/**', './node_modules/@swc/helpers/**'], sourceMaps: sourcemap, tsconfig: false, env: { From 4694727e2af8fcc8f5cfc88669ad915b84d3c909 Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Wed, 24 Sep 2025 15:59:14 +0200 Subject: [PATCH 100/123] Tweaks --- react/rollup.config.mjs | 1 + rollup.config.mjs | 15 +++++++++------ 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/react/rollup.config.mjs b/react/rollup.config.mjs index 92a55bda..2eb377f9 100644 --- a/react/rollup.config.mjs +++ b/react/rollup.config.mjs @@ -49,6 +49,7 @@ export default (cliArgs) => { banner, footer: `export as namespace ${umdName};` }, + external: esmExternal, plugins: [nodeResolve(), dts()] } ]); diff --git a/rollup.config.mjs b/rollup.config.mjs index f77bc659..748c13c8 100644 --- a/rollup.config.mjs +++ b/rollup.config.mjs @@ -27,6 +27,10 @@ const banner = `/*! * License: ${license} */`; const theoplayerModule = 'theoplayer/chromeless'; +const globals = { + [theoplayerModule]: 'THEOplayer' +}; +const external = Object.keys(globals); /** * @param {{configOutputDir?: string}} cliArgs @@ -58,7 +62,7 @@ export default (cliArgs) => { banner, footer: `export as namespace ${umdName};` }, - external: [theoplayerModule], + external, plugins: [dts()] } ]); @@ -78,12 +82,10 @@ function jsConfig(outputDir, { es5 = false, node = false, production = false, so sourcemap, indent: false, banner, - globals: { - [theoplayerModule]: 'THEOplayer' - } + globals }, context: 'self', - external: [theoplayerModule], + external, plugins: jsPlugins({ es5, module: false, production, sourcemap }) }, { @@ -96,7 +98,7 @@ function jsConfig(outputDir, { es5 = false, node = false, production = false, so banner }, context: 'self', - external: [theoplayerModule], + external, plugins: jsPlugins({ es5, module: true, production, sourcemap }) }, node && { @@ -109,6 +111,7 @@ function jsConfig(outputDir, { es5 = false, node = false, production = false, so banner }, context: 'globalThis', + external, plugins: jsPlugins({ es5, node, module: true, production, sourcemap }) } ]).filter(Boolean); From 5e437bacd016cce2f76fc0258948b162bae81fc1 Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Wed, 24 Sep 2025 16:00:00 +0200 Subject: [PATCH 101/123] Don't bundle Lit for Node.js --- rollup.config.mjs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rollup.config.mjs b/rollup.config.mjs index 748c13c8..e12447f1 100644 --- a/rollup.config.mjs +++ b/rollup.config.mjs @@ -31,6 +31,7 @@ const globals = { [theoplayerModule]: 'THEOplayer' }; const external = Object.keys(globals); +const nodeExternal = [/^lit/, /^@lit/]; /** * @param {{configOutputDir?: string}} cliArgs @@ -111,7 +112,7 @@ function jsConfig(outputDir, { es5 = false, node = false, production = false, so banner }, context: 'globalThis', - external, + external: nodeExternal, plugins: jsPlugins({ es5, node, module: true, production, sourcemap }) } ]).filter(Boolean); From ed6b01f57a9ced3abea8e4fa3eb854a56643f9ac Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Wed, 24 Sep 2025 16:09:50 +0200 Subject: [PATCH 102/123] Don't bundle Lit for ESM --- examples/default-ui.html | 14 +++++++++++++- examples/react/default-ui.html | 8 ++++++++ rollup.config.mjs | 5 +++-- 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/examples/default-ui.html b/examples/default-ui.html index 8ad5a026..23a40564 100644 --- a/examples/default-ui.html +++ b/examples/default-ui.html @@ -26,7 +26,19 @@ { "imports": { "theoplayer/chromeless": "../node_modules/theoplayer/THEOplayer.chromeless.esm.js", - "@theoplayer/web-ui": "../dist/THEOplayerUI.mjs" + "@theoplayer/web-ui": "../dist/THEOplayerUI.mjs", + "lit": "https://ga.jspm.io/npm:lit@3.3.1/index.js", + "lit/decorators.js": "https://ga.jspm.io/npm:lit@3.3.1/decorators.js", + "lit/directives/": "https://ga.jspm.io/npm:lit@3.3.1/directives/" + }, + "scopes": { + "https://ga.jspm.io/": { + "@lit/reactive-element": "https://ga.jspm.io/npm:@lit/reactive-element@2.1.1/development/reactive-element.js", + "@lit/reactive-element/decorators/": "https://ga.jspm.io/npm:@lit/reactive-element@2.1.1/development/decorators/", + "lit-element/lit-element.js": "https://ga.jspm.io/npm:lit-element@4.2.1/development/lit-element.js", + "lit-html": "https://ga.jspm.io/npm:lit-html@3.3.1/development/lit-html.js", + "lit-html/": "https://ga.jspm.io/npm:lit-html@3.3.1/development/" + } } } diff --git a/examples/react/default-ui.html b/examples/react/default-ui.html index 4d8ef5cb..d8f5ca7f 100644 --- a/examples/react/default-ui.html +++ b/examples/react/default-ui.html @@ -35,12 +35,20 @@ "theoplayer/chromeless": "../../node_modules/theoplayer/THEOplayer.chromeless.esm.js", "@theoplayer/web-ui": "../../dist/THEOplayerUI.mjs", "@theoplayer/react-ui": "../../react/dist/THEOplayerReactUI.mjs", + "lit": "https://ga.jspm.io/npm:lit@3.3.1/index.js", + "lit/decorators.js": "https://ga.jspm.io/npm:lit@3.3.1/decorators.js", + "lit/directives/": "https://ga.jspm.io/npm:lit@3.3.1/directives/", "@lit/react": "https://ga.jspm.io/npm:@lit/react@1.0.7/development/index.js", "react": "https://ga.jspm.io/npm:react@18.2.0/dev.index.js", "react-dom/client": "https://ga.jspm.io/npm:react-dom@18.2.0/dev.client.js" }, "scopes": { "https://ga.jspm.io/": { + "@lit/reactive-element": "https://ga.jspm.io/npm:@lit/reactive-element@2.1.1/development/reactive-element.js", + "@lit/reactive-element/decorators/": "https://ga.jspm.io/npm:@lit/reactive-element@2.1.1/development/decorators/", + "lit-element/lit-element.js": "https://ga.jspm.io/npm:lit-element@4.2.1/development/lit-element.js", + "lit-html": "https://ga.jspm.io/npm:lit-html@3.3.1/development/lit-html.js", + "lit-html/": "https://ga.jspm.io/npm:lit-html@3.3.1/development/", "react-dom": "https://ga.jspm.io/npm:react-dom@18.2.0/dev.index.js", "scheduler": "https://ga.jspm.io/npm:scheduler@0.23.0/dev.index.js" } diff --git a/rollup.config.mjs b/rollup.config.mjs index e12447f1..81d3ba63 100644 --- a/rollup.config.mjs +++ b/rollup.config.mjs @@ -31,6 +31,7 @@ const globals = { [theoplayerModule]: 'THEOplayer' }; const external = Object.keys(globals); +const esmExternal = [...external, /^lit/, /^@lit/]; const nodeExternal = [/^lit/, /^@lit/]; /** @@ -63,7 +64,7 @@ export default (cliArgs) => { banner, footer: `export as namespace ${umdName};` }, - external, + external: esmExternal, plugins: [dts()] } ]); @@ -99,7 +100,7 @@ function jsConfig(outputDir, { es5 = false, node = false, production = false, so banner }, context: 'self', - external, + external: esmExternal, plugins: jsPlugins({ es5, module: true, production, sourcemap }) }, node && { From 16eac538796146fd33372ed25be6c3ade0335eb1 Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Wed, 24 Sep 2025 16:20:24 +0200 Subject: [PATCH 103/123] Keep bundling Lit for ES5 builds --- rollup.config.mjs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/rollup.config.mjs b/rollup.config.mjs index 81d3ba63..68e5f1be 100644 --- a/rollup.config.mjs +++ b/rollup.config.mjs @@ -30,9 +30,8 @@ const theoplayerModule = 'theoplayer/chromeless'; const globals = { [theoplayerModule]: 'THEOplayer' }; -const external = Object.keys(globals); -const esmExternal = [...external, /^lit/, /^@lit/]; -const nodeExternal = [/^lit/, /^@lit/]; +const theoplayerExternals = [theoplayerModule]; +const litExternals = [/^lit/, /^@lit/]; /** * @param {{configOutputDir?: string}} cliArgs @@ -64,7 +63,7 @@ export default (cliArgs) => { banner, footer: `export as namespace ${umdName};` }, - external: esmExternal, + external: [...theoplayerExternals, ...litExternals], plugins: [dts()] } ]); @@ -87,7 +86,7 @@ function jsConfig(outputDir, { es5 = false, node = false, production = false, so globals }, context: 'self', - external, + external: theoplayerExternals, plugins: jsPlugins({ es5, module: false, production, sourcemap }) }, { @@ -100,7 +99,7 @@ function jsConfig(outputDir, { es5 = false, node = false, production = false, so banner }, context: 'self', - external: esmExternal, + external: es5 ? theoplayerExternals : [...theoplayerExternals, ...litExternals], plugins: jsPlugins({ es5, module: true, production, sourcemap }) }, node && { @@ -113,7 +112,7 @@ function jsConfig(outputDir, { es5 = false, node = false, production = false, so banner }, context: 'globalThis', - external: nodeExternal, + external: litExternals, plugins: jsPlugins({ es5, node, module: true, production, sourcemap }) } ]).filter(Boolean); From 2673dbd249b0e3ef1701f62362f4e6fb7bccf996 Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Wed, 24 Sep 2025 16:25:29 +0200 Subject: [PATCH 104/123] Update es-module-shims --- README.md | 4 ++-- docs/getting-started.mdx | 4 ++-- .../open-video-ui/v1/examples/react/custom-ui/demo.html | 2 +- .../open-video-ui/v1/examples/react/default-ui/demo.html | 2 +- .../v1/guides/web/custom-component/my-play-button-step1.html | 2 +- .../v1/guides/web/custom-component/my-play-button-step2.html | 2 +- .../guides/web/custom-component/my-quality-label-step1.html | 2 +- .../guides/web/custom-component/my-quality-label-step2.html | 2 +- examples/default-ui.html | 2 +- examples/react/default-ui.html | 2 +- 10 files changed, 12 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 6dfe0ece..1bd72b0c 100644 --- a/README.md +++ b/README.md @@ -63,7 +63,7 @@ The current THEOplayer Web SDK comes with a built-in UI based on [video.js](http } - + ``` @@ -148,7 +148,7 @@ On older browsers (such as Internet Explorer 11 and older smart TVs), you need t } - + diff --git a/docs/getting-started.mdx b/docs/getting-started.mdx index edf5c454..8af449d4 100644 --- a/docs/getting-started.mdx +++ b/docs/getting-started.mdx @@ -42,7 +42,7 @@ sidebar_custom_props: { 'icon': '🚀' } } - + ``` @@ -127,7 +127,7 @@ On older browsers (such as Internet Explorer 11 and older smart TVs), you need t } - + diff --git a/docs/static/open-video-ui/v1/examples/react/custom-ui/demo.html b/docs/static/open-video-ui/v1/examples/react/custom-ui/demo.html index 707cc77b..eafdb529 100644 --- a/docs/static/open-video-ui/v1/examples/react/custom-ui/demo.html +++ b/docs/static/open-video-ui/v1/examples/react/custom-ui/demo.html @@ -75,7 +75,7 @@ } - + diff --git a/docs/static/open-video-ui/v1/examples/react/default-ui/demo.html b/docs/static/open-video-ui/v1/examples/react/default-ui/demo.html index 99eae1da..bdb328fa 100644 --- a/docs/static/open-video-ui/v1/examples/react/default-ui/demo.html +++ b/docs/static/open-video-ui/v1/examples/react/default-ui/demo.html @@ -50,7 +50,7 @@ } - + diff --git a/docs/static/open-video-ui/v1/guides/web/custom-component/my-play-button-step1.html b/docs/static/open-video-ui/v1/guides/web/custom-component/my-play-button-step1.html index d824e695..ff85a532 100644 --- a/docs/static/open-video-ui/v1/guides/web/custom-component/my-play-button-step1.html +++ b/docs/static/open-video-ui/v1/guides/web/custom-component/my-play-button-step1.html @@ -21,7 +21,7 @@ } - + - + - + - + - + - + From 8ae1c4f58e393f6573b31b21209a901dd5254acf Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Wed, 24 Sep 2025 16:50:05 +0200 Subject: [PATCH 105/123] Update getting started --- README.md | 14 ++++++++++---- docs/getting-started.mdx | 14 ++++++++++---- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 1bd72b0c..33cc5c5b 100644 --- a/README.md +++ b/README.md @@ -51,14 +51,17 @@ The current THEOplayer Web SDK comes with a built-in UI based on [video.js](http ```js import { DefaultUI } from '@theoplayer/web-ui'; ``` - Open Video UI will import THEOplayer from `theoplayer/chromeless`. - If you're using a bundler such as Webpack or Rollup, this dependency should automatically get bundled with your web app. + Open Video UI will import THEOplayer from `theoplayer/chromeless` and [Lit](https://lit.dev/) from `lit`. + If you're using a bundler such as Webpack or Rollup, these dependencies should automatically get bundled with your web app. Alternatively, you can use an [import map](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script/type/importmap) to let the browser resolve it: ```html @@ -143,7 +146,10 @@ On older browsers (such as Internet Explorer 11 and older smart TVs), you need t diff --git a/docs/getting-started.mdx b/docs/getting-started.mdx index 8af449d4..a4655c48 100644 --- a/docs/getting-started.mdx +++ b/docs/getting-started.mdx @@ -30,14 +30,17 @@ sidebar_custom_props: { 'icon': '🚀' } ```js import { DefaultUI } from '@theoplayer/web-ui'; ``` - Open Video UI will import THEOplayer from `theoplayer/chromeless`. - If you're using a bundler such as Webpack or Rollup, this dependency should automatically get bundled with your web app. + Open Video UI will import THEOplayer from `theoplayer/chromeless` and [Lit](https://lit.dev/) from `lit`. + If you're using a bundler such as Webpack or Rollup, these dependencies should automatically get bundled with your web app. Alternatively, you can use an [import map](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script/type/importmap) to let the browser resolve it: ```html @@ -122,7 +125,10 @@ On older browsers (such as Internet Explorer 11 and older smart TVs), you need t From 0f48c5dc94da74db3e76ae7b10b9927a4eb449fa Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Wed, 24 Sep 2025 16:55:34 +0200 Subject: [PATCH 106/123] Add changesets --- .changeset/giant-rats-bake.md | 8 ++++++++ .changeset/little-bars-talk.md | 8 ++++++++ 2 files changed, 16 insertions(+) create mode 100644 .changeset/giant-rats-bake.md create mode 100644 .changeset/little-bars-talk.md diff --git a/.changeset/giant-rats-bake.md b/.changeset/giant-rats-bake.md new file mode 100644 index 00000000..e950ce10 --- /dev/null +++ b/.changeset/giant-rats-bake.md @@ -0,0 +1,8 @@ +--- +'@theoplayer/web-ui': major +--- + +Open Video UI for Web is now built using [Lit](https://lit.dev/). + +- All components now extend [`LitElement`](https://lit.dev/docs/api/LitElement/) and use [reactive rendering](https://lit.dev/docs/components/rendering/), making it much easier to build custom UI components. While existing custom UI components should mostly continue to work, we highly recommend updating them to use a `render()` method instead. +- For older browsers that don't support Custom Elements, we recommend loading our new polyfills library from `@theoplayer/web-ui/polyfills`. diff --git a/.changeset/little-bars-talk.md b/.changeset/little-bars-talk.md new file mode 100644 index 00000000..bbbba1dd --- /dev/null +++ b/.changeset/little-bars-talk.md @@ -0,0 +1,8 @@ +--- +'@theoplayer/react-ui': major +--- + +Open Video UI for Web is now built using [Lit](https://lit.dev/). + +- This shouldn't affect any custom UIs or custom components built using Open Video UI for React. +- For older browsers that don't support Custom Elements, we recommend loading our new polyfills library from `@theoplayer/web-ui/polyfills`. From 8136e0f97a99172e93ee661f381d64b281fb32b7 Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Wed, 24 Sep 2025 18:02:57 +0200 Subject: [PATCH 107/123] Use `adoptedStylesheets` for global CSS --- src/Global.ts | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/Global.ts b/src/Global.ts index 8d5fbfc6..61fb399b 100644 --- a/src/Global.ts +++ b/src/Global.ts @@ -1,5 +1,6 @@ import globalCss from './Global.css'; import { setTextContent } from './util/CommonUtils'; +import { supportsAdoptingStyleSheets } from 'lit'; const GLOBAL_STYLE_ID = 'theoplayer-ui-global-styles'; @@ -9,13 +10,18 @@ export function addGlobalStyles() { if (globalStylesAdded) { return; } - if (document.getElementById(GLOBAL_STYLE_ID)) { - globalStylesAdded = true; - return; + if (supportsAdoptingStyleSheets) { + document.adoptedStyleSheets.push(globalCss.styleSheet!); + } else if (!document.getElementById(GLOBAL_STYLE_ID)) { + const styleEl = document.createElement('style'); + styleEl.id = GLOBAL_STYLE_ID; + // See https://lit.dev/docs/api/LitElement/#LitElement.styles + const nonce = (window as any)['litNonce']; + if (nonce !== undefined) { + styleEl.setAttribute('nonce', nonce); + } + setTextContent(styleEl, globalCss.cssText); + document.head.appendChild(styleEl); } - const styleEl = document.createElement('style'); - styleEl.id = GLOBAL_STYLE_ID; - setTextContent(styleEl, globalCss); - document.head.appendChild(styleEl); globalStylesAdded = true; } From 0ca9e4f60e1db4135c081bc918dd2ede550caf30 Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Thu, 25 Sep 2025 11:50:52 +0200 Subject: [PATCH 108/123] Fix types --- src/DefaultUI.ts | 2 +- src/UIContainer.ts | 2 +- src/components/ControlBar.ts | 2 +- src/components/ErrorDisplay.ts | 2 +- src/components/Menu.ts | 2 +- src/components/MenuGroup.ts | 2 +- src/definitions.d.ts | 8 ++++---- 7 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/DefaultUI.ts b/src/DefaultUI.ts index 5b18b722..89799d23 100644 --- a/src/DefaultUI.ts +++ b/src/DefaultUI.ts @@ -69,7 +69,7 @@ import { createCustomEvent } from './util/EventUtils'; @customElement('theoplayer-default-ui') export class DefaultUI extends LitElement { static override styles = [defaultUiCss]; - static override shadowRootOptions = { + static override shadowRootOptions: ShadowRootInit = { ...LitElement.shadowRootOptions, delegatesFocus: true }; diff --git a/src/UIContainer.ts b/src/UIContainer.ts index 2ae5a6ff..745ce2e0 100644 --- a/src/UIContainer.ts +++ b/src/UIContainer.ts @@ -115,7 +115,7 @@ export const FULL_WINDOW_ROOT_CLASS = 'theoplayer-ui-full-window'; @customElement('theoplayer-ui') export class UIContainer extends LitElement { static override styles = [elementCss]; - static override shadowRootOptions = { + static override shadowRootOptions: ShadowRootInit = { ...LitElement.shadowRootOptions, delegatesFocus: true }; diff --git a/src/components/ControlBar.ts b/src/components/ControlBar.ts index e530a08e..4c80437c 100644 --- a/src/components/ControlBar.ts +++ b/src/components/ControlBar.ts @@ -10,7 +10,7 @@ import controlBarCss from './ControlBar.css'; @customElement('theoplayer-control-bar') export class ControlBar extends LitElement { static override styles = [controlBarCss]; - static override shadowRootOptions = { + static override shadowRootOptions: ShadowRootInit = { ...LitElement.shadowRootOptions, delegatesFocus: true }; diff --git a/src/components/ErrorDisplay.ts b/src/components/ErrorDisplay.ts index 9dc3d069..368d0edf 100644 --- a/src/components/ErrorDisplay.ts +++ b/src/components/ErrorDisplay.ts @@ -16,7 +16,7 @@ import { Attribute } from '../util/Attribute'; @stateReceiver(['error', 'fullscreen']) export class ErrorDisplay extends LitElement { static override styles = [errorDisplayCss]; - static override shadowRootOptions = { + static override shadowRootOptions: ShadowRootInit = { ...LitElement.shadowRootOptions, delegatesFocus: true }; diff --git a/src/components/Menu.ts b/src/components/Menu.ts index a1ba5ebe..510e317a 100644 --- a/src/components/Menu.ts +++ b/src/components/Menu.ts @@ -43,7 +43,7 @@ export function menuTemplate(heading: string, content: string, extraCss: string @customElement('theoplayer-menu') export class Menu extends LitElement { static override styles = [menuCss]; - static override shadowRootOptions = { + static override shadowRootOptions: ShadowRootInit = { ...LitElement.shadowRootOptions, delegatesFocus: true }; diff --git a/src/components/MenuGroup.ts b/src/components/MenuGroup.ts index 3801fbac..73fb2140 100644 --- a/src/components/MenuGroup.ts +++ b/src/components/MenuGroup.ts @@ -54,7 +54,7 @@ interface OpenMenuEntry { @customElement('theoplayer-menu-group') export class MenuGroup extends LitElement { static styles = [menuGroupCss]; - static override shadowRootOptions = { + static override shadowRootOptions: ShadowRootInit = { ...LitElement.shadowRootOptions, delegatesFocus: true }; diff --git a/src/definitions.d.ts b/src/definitions.d.ts index 1d5abc2d..5708ed06 100644 --- a/src/definitions.d.ts +++ b/src/definitions.d.ts @@ -1,7 +1,7 @@ declare module '*.css' { import type { CSSResult } from 'lit'; - const css: CSSResult; - export default css; + const cssText: CSSResult; + export default cssText; } declare module '*.html' { @@ -10,6 +10,6 @@ declare module '*.html' { } declare module '*.svg' { - const svg: string; - export default svg; + const svgText: string; + export default svgText; } From e849a87afc2f0f3f45ded64b00abcbefae49f8e1 Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Thu, 25 Sep 2025 11:57:40 +0200 Subject: [PATCH 109/123] Link Lit types to their API docs --- typedoc.config.mjs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/typedoc.config.mjs b/typedoc.config.mjs index 5774250e..105e18ac 100644 --- a/typedoc.config.mjs +++ b/typedoc.config.mjs @@ -12,10 +12,26 @@ export default { excludePrivate: true, excludeInternal: true, excludeExternals: true, + excludeTags: [ + '@nocollapse' // appears a lot in Lit docs + ], externalDocumentation: { theoplayer: { dtsPath: '~/THEOplayer.d.ts', externalBaseURL: 'https://www.theoplayer.com/docs/theoplayer/v8/api-reference/web' } + }, + externalSymbolLinkMappings: { + 'lit-element': { + LitElement: 'https://lit.dev/docs/api/LitElement/', + '*': 'https://lit.dev/docs/' + }, + '@lit/reactive-element': { + html: 'https://lit.dev/docs/api/templates/#html', + TemplateResult: 'https://lit.dev/docs/api/templates/#TemplateResult', + css: 'https://lit.dev/docs/api/styles/#css', + CSSResult: 'https://lit.dev/docs/api/styles/#CSSResult', + '*': 'https://lit.dev/docs/' + } } }; From 465903de1bdafe4ef52d27c46cba7d46c9e1d30a Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Thu, 25 Sep 2025 12:45:23 +0200 Subject: [PATCH 110/123] Resolve `html` and `css` links from Lit docs --- scripts/typedoc-symbol-resolver.mjs | 20 ++++++++++++++++++++ typedoc.config.mjs | 2 +- 2 files changed, 21 insertions(+), 1 deletion(-) create mode 100644 scripts/typedoc-symbol-resolver.mjs diff --git a/scripts/typedoc-symbol-resolver.mjs b/scripts/typedoc-symbol-resolver.mjs new file mode 100644 index 00000000..37f95add --- /dev/null +++ b/scripts/typedoc-symbol-resolver.mjs @@ -0,0 +1,20 @@ +/** + * @param {import('typedoc').Application} app + */ +export function load(app) { + app.converter.addUnknownSymbolResolver((declaration, _refl, _part, _symbolId) => { + // The Lit docs have some references to `html` and `css` without importing these types from `lit-element`. + // Resolve them manually. + if (declaration.moduleSource === undefined) { + const path = declaration.symbolReference?.path; + if (path && path.length === 1) { + switch (path[0].path) { + case 'css': + return 'https://lit.dev/docs/api/styles/#css'; + case 'html': + return 'https://lit.dev/docs/api/templates/#html'; + } + } + } + }); +} diff --git a/typedoc.config.mjs b/typedoc.config.mjs index 105e18ac..878d0900 100644 --- a/typedoc.config.mjs +++ b/typedoc.config.mjs @@ -4,7 +4,7 @@ export default { out: 'api', hostedBaseUrl: 'https://theoplayer.github.io/web-ui/api/', readme: 'none', - plugin: ['typedoc-plugin-external-resolver', 'typedoc-plugin-mdn-links'], + plugin: ['typedoc-plugin-external-resolver', 'typedoc-plugin-mdn-links', import.meta.resolve('./scripts/typedoc-symbol-resolver.mjs')], navigationLinks: { GitHub: 'https://github.com/THEOplayer/web-ui' }, From dec1b41176765b25d0268d5f7f7ae91f1336c7a0 Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Thu, 25 Sep 2025 12:46:00 +0200 Subject: [PATCH 111/123] Fix highlightLanguages for React --- react/typedoc.config.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/react/typedoc.config.mjs b/react/typedoc.config.mjs index d7fa173e..f15071fe 100644 --- a/react/typedoc.config.mjs +++ b/react/typedoc.config.mjs @@ -9,7 +9,7 @@ export default { navigationLinks: { GitHub: 'https://github.com/THEOplayer/web-ui/tree/main/react' }, - highlightLanguages: ['jsx'], + highlightLanguages: ['typescript', 'tsx', 'jsx'], externalDocumentation: { theoplayer: { dtsPath: '~/THEOplayer.d.ts', From 9e29c3a1c249b087514e1375b78fec4d2165bda4 Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Thu, 25 Sep 2025 13:29:46 +0200 Subject: [PATCH 112/123] Test TypeDoc --- package.json | 3 ++- react/package.json | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 314955b2..45ea6b98 100644 --- a/package.json +++ b/package.json @@ -36,12 +36,13 @@ "watch:docs": "npm run watch -- --configOutputDir ./docs/_site/dist", "start": "serve", "format": "prettier --write .", - "test": "npm run test:format && npm run test:typecheck && npm run test:unit", + "test": "npm run test:format && npm run test:typecheck && npm run test:unit && npm run test:docs", "test:format": "prettier --check .", "test:typecheck": "tsc --project . --noEmit", "test:unit": "node --test test/ssr.test.mjs", "docs": "cross-env NODE_OPTIONS=\"--experimental-require-module\" typedoc --treatWarningsAsErrors", "docs:watch": "cross-env NODE_OPTIONS=\"--experimental-require-module\" typedoc --watch", + "test:docs": "npm run docs -- --emit none", "changeset:version": "changeset version && node .changeset/post-process.mjs" }, "keywords": [], diff --git a/react/package.json b/react/package.json index a2d7b0d7..9bb392e5 100644 --- a/react/package.json +++ b/react/package.json @@ -30,12 +30,13 @@ "build:debug": "rollup -c", "build:prod": "rollup -c --environment BUILD:production", "watch": "rollup -c --watch", - "test": "npm run test:typecheck && npm run test:unit", + "test": "npm run test:typecheck && npm run test:unit && npm run test:docs", "test:format": "prettier --check .", "test:typecheck": "tsc --project . --noEmit", "test:unit": "node --test test/ssr.test.mjs", "docs": "cross-env NODE_OPTIONS=\"--experimental-require-module\" typedoc --treatWarningsAsErrors", - "docs:watch": "cross-env NODE_OPTIONS=\"--experimental-require-module\" typedoc --watch" + "docs:watch": "cross-env NODE_OPTIONS=\"--experimental-require-module\" typedoc --watch", + "test:docs": "npm run docs -- --emit none" }, "keywords": [], "author": "THEO Technologies", From 0bed6e6bad7020e6b3e05c725b44402a36f16d39 Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Thu, 25 Sep 2025 13:51:22 +0200 Subject: [PATCH 113/123] Use TypeScript for TypeDoc plugin --- ...doc-symbol-resolver.mjs => typedoc-symbol-resolver.mts} | 7 +++---- typedoc.config.mjs | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) rename scripts/{typedoc-symbol-resolver.mjs => typedoc-symbol-resolver.mts} (89%) diff --git a/scripts/typedoc-symbol-resolver.mjs b/scripts/typedoc-symbol-resolver.mts similarity index 89% rename from scripts/typedoc-symbol-resolver.mjs rename to scripts/typedoc-symbol-resolver.mts index 37f95add..8e339978 100644 --- a/scripts/typedoc-symbol-resolver.mjs +++ b/scripts/typedoc-symbol-resolver.mts @@ -1,7 +1,6 @@ -/** - * @param {import('typedoc').Application} app - */ -export function load(app) { +import type { Application } from 'typedoc'; + +export function load(app: Application) { app.converter.addUnknownSymbolResolver((declaration, _refl, _part, _symbolId) => { // The Lit docs have some references to `html` and `css` without importing these types from `lit-element`. // Resolve them manually. diff --git a/typedoc.config.mjs b/typedoc.config.mjs index 878d0900..ea1aaa4a 100644 --- a/typedoc.config.mjs +++ b/typedoc.config.mjs @@ -4,7 +4,7 @@ export default { out: 'api', hostedBaseUrl: 'https://theoplayer.github.io/web-ui/api/', readme: 'none', - plugin: ['typedoc-plugin-external-resolver', 'typedoc-plugin-mdn-links', import.meta.resolve('./scripts/typedoc-symbol-resolver.mjs')], + plugin: ['typedoc-plugin-external-resolver', 'typedoc-plugin-mdn-links', import.meta.resolve('./scripts/typedoc-symbol-resolver.mts')], navigationLinks: { GitHub: 'https://github.com/THEOplayer/web-ui' }, From 62f054a016b1f12a24bdec537d2e5f698049d6c5 Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Thu, 25 Sep 2025 15:58:55 +0200 Subject: [PATCH 114/123] Tweak docs --- scripts/typedoc-symbol-resolver.mts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/scripts/typedoc-symbol-resolver.mts b/scripts/typedoc-symbol-resolver.mts index 8e339978..3e00d7a7 100644 --- a/scripts/typedoc-symbol-resolver.mts +++ b/scripts/typedoc-symbol-resolver.mts @@ -1,9 +1,11 @@ import type { Application } from 'typedoc'; +/** + * Resolves references to `html` and `css` in Lit docs, + * which are lacking an explicit import from `lit-element`. + */ export function load(app: Application) { app.converter.addUnknownSymbolResolver((declaration, _refl, _part, _symbolId) => { - // The Lit docs have some references to `html` and `css` without importing these types from `lit-element`. - // Resolve them manually. if (declaration.moduleSource === undefined) { const path = declaration.symbolReference?.path; if (path && path.length === 1) { From d2dfaa11536611afe42622e854cd15995b74dfde Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Thu, 25 Sep 2025 16:00:14 +0200 Subject: [PATCH 115/123] Add extra documentation tags from Lit decorators --- scripts/typedoc-lit-decorators.mts | 92 ++++++++++++++++++++++++++++++ typedoc.config.mjs | 7 ++- 2 files changed, 98 insertions(+), 1 deletion(-) create mode 100644 scripts/typedoc-lit-decorators.mts diff --git a/scripts/typedoc-lit-decorators.mts b/scripts/typedoc-lit-decorators.mts new file mode 100644 index 00000000..7275814d --- /dev/null +++ b/scripts/typedoc-lit-decorators.mts @@ -0,0 +1,92 @@ +import * as typedoc from 'typedoc'; +import { TypeScript as ts } from 'typedoc'; + +/** + * Adds extra documentation tags from Lit decorators such as `@property()` + */ +export function load(app: typedoc.Application) { + app.converter.on(typedoc.Converter.EVENT_CREATE_DECLARATION, onDeclaration); + app.converter.on(typedoc.Converter.EVENT_CREATE_SIGNATURE, onSignature); +} + +function onDeclaration(context: typedoc.Context, refl: typedoc.DeclarationReflection) { + const symbol = context.project.getSymbolFromReflection(refl); + if (!symbol) return; + const declarations = symbol.declarations; + if (!declarations) return; + for (const declaration of declarations) { + extractDecoratorInfo(context, refl, declaration); + } +} + +function onSignature(context: typedoc.Context, refl: typedoc.SignatureReflection) { + const symbol = context.project.getSymbolFromReflection(refl.parent); + if (!symbol) return; + const declarations = symbol.declarations; + if (!declarations) return; + let declaration: ts.Declaration | undefined; + switch (refl.kind) { + case typedoc.ReflectionKind.GetSignature: + declaration = declarations.find(ts.isGetAccessorDeclaration); + break; + case typedoc.ReflectionKind.SetSignature: + declaration = declarations.find(ts.isSetAccessorDeclaration); + break; + case typedoc.ReflectionKind.IndexSignature: + declaration = declarations.find(ts.isIndexSignatureDeclaration); + break; + case typedoc.ReflectionKind.CallSignature: + declaration = declarations.find(ts.isCallSignatureDeclaration); + break; + case typedoc.ReflectionKind.ConstructorSignature: + declaration = declarations.find(ts.isConstructSignatureDeclaration); + break; + default: + return; + } + if (!declaration) return; + extractDecoratorInfo(context, refl, declaration); +} + +function extractDecoratorInfo(context: typedoc.Context, refl: typedoc.Reflection, declaration: ts.Declaration) { + // Based on https://github.com/TypeStrong/typedoc/issues/2346#issuecomment-1656806051 + if ( + !ts.isPropertyDeclaration(declaration) && + !ts.isMethodDeclaration(declaration) && + !ts.isGetAccessorDeclaration(declaration) && + !ts.isSetAccessorDeclaration(declaration) + ) { + return; + } + + const decorators = declaration.modifiers?.filter(ts.isDecorator); + if (!decorators) return; + + for (const decorator of decorators) { + // Look for `@decoratorName()` + const callExpression = decorator.expression; + if (!ts.isCallExpression(callExpression)) continue; + const callIdentifier = callExpression.expression; + const callArgument = callExpression.arguments[0]; + if (!ts.isIdentifier(callIdentifier)) continue; + const decoratorName = callIdentifier.text; + switch (decoratorName) { + case 'property': { + // Look for `attributeValue` in `@property({ attribute: attributeValue })` + if (!ts.isObjectLiteralExpression(callArgument)) continue; + const propertyAttribute = callArgument.properties.find( + (p): p is ts.PropertyAssignment => ts.isPropertyAssignment(p) && ts.isIdentifier(p.name) && p.name.text === 'attribute' + ); + if (!propertyAttribute) continue; + const attributeValue = propertyAttribute.initializer; + if (ts.isLiteralTypeLiteral(attributeValue)) { + // e.g. `@property({ attribute: false })` + continue; + } + const comment = (refl.comment ??= new typedoc.Comment([])); + comment.blockTags.push(new typedoc.CommentTag(`@attribute`, [{ kind: 'inline-tag', tag: '@link', text: attributeValue.getText() }])); + break; + } + } + } +} diff --git a/typedoc.config.mjs b/typedoc.config.mjs index ea1aaa4a..7e56c874 100644 --- a/typedoc.config.mjs +++ b/typedoc.config.mjs @@ -4,7 +4,12 @@ export default { out: 'api', hostedBaseUrl: 'https://theoplayer.github.io/web-ui/api/', readme: 'none', - plugin: ['typedoc-plugin-external-resolver', 'typedoc-plugin-mdn-links', import.meta.resolve('./scripts/typedoc-symbol-resolver.mts')], + plugin: [ + 'typedoc-plugin-external-resolver', + 'typedoc-plugin-mdn-links', + import.meta.resolve('./scripts/typedoc-lit-decorators.mts'), + import.meta.resolve('./scripts/typedoc-symbol-resolver.mts') + ], navigationLinks: { GitHub: 'https://github.com/THEOplayer/web-ui' }, From ca8a7b919a6bdf50fa7be8dcc184bf3e82728792 Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Thu, 25 Sep 2025 16:08:16 +0200 Subject: [PATCH 116/123] Parse `@customElement` decorator --- scripts/typedoc-lit-decorators.mts | 9 +++++++++ tsdoc.json | 4 ++++ 2 files changed, 13 insertions(+) diff --git a/scripts/typedoc-lit-decorators.mts b/scripts/typedoc-lit-decorators.mts index 7275814d..bac57f56 100644 --- a/scripts/typedoc-lit-decorators.mts +++ b/scripts/typedoc-lit-decorators.mts @@ -51,6 +51,7 @@ function onSignature(context: typedoc.Context, refl: typedoc.SignatureReflection function extractDecoratorInfo(context: typedoc.Context, refl: typedoc.Reflection, declaration: ts.Declaration) { // Based on https://github.com/TypeStrong/typedoc/issues/2346#issuecomment-1656806051 if ( + !ts.isClassDeclaration(declaration) && !ts.isPropertyDeclaration(declaration) && !ts.isMethodDeclaration(declaration) && !ts.isGetAccessorDeclaration(declaration) && @@ -71,6 +72,14 @@ function extractDecoratorInfo(context: typedoc.Context, refl: typedoc.Reflection if (!ts.isIdentifier(callIdentifier)) continue; const decoratorName = callIdentifier.text; switch (decoratorName) { + case 'customElement': { + // Look for `tagName` in `@customElement(tagName)` + if (!ts.isLiteralExpression(callArgument)) continue; + const tagName = callArgument.text; + const comment = (refl.comment ??= new typedoc.Comment([])); + comment.blockTags.push(new typedoc.CommentTag(`@customElement`, [{ kind: 'code', text: `\`<${tagName}>\`` }])); + break; + } case 'property': { // Look for `attributeValue` in `@property({ attribute: attributeValue })` if (!ts.isObjectLiteralExpression(callArgument)) continue; diff --git a/tsdoc.json b/tsdoc.json index be684dbe..41fceb68 100644 --- a/tsdoc.json +++ b/tsdoc.json @@ -3,6 +3,10 @@ "extends": ["typedoc/tsdoc.json"], "noStandardTags": false, "tagDefinitions": [ + { + "tagName": "@customElement", + "syntaxKind": "block" + }, { "tagName": "@attribute", "syntaxKind": "block", From 56e92690412a21028964a5a3e8bf2b6068293f97 Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Thu, 25 Sep 2025 16:09:49 +0200 Subject: [PATCH 117/123] Remove tag name from JSDoc --- src/DefaultUI.ts | 2 +- src/UIContainer.ts | 2 +- src/components/ActiveQualityDisplay.ts | 2 +- src/components/AirPlayButton.ts | 2 +- src/components/Button.ts | 2 +- src/components/ChromecastButton.ts | 2 +- src/components/ChromecastDisplay.ts | 2 +- src/components/CloseMenuButton.ts | 2 +- src/components/ControlBar.ts | 2 +- src/components/DurationDisplay.ts | 2 +- src/components/ErrorDisplay.ts | 2 +- src/components/FullscreenButton.ts | 2 +- src/components/GestureReceiver.ts | 2 +- src/components/LanguageMenu.ts | 2 +- src/components/LanguageMenuButton.ts | 2 +- src/components/LinkButton.ts | 2 +- src/components/LiveButton.ts | 2 +- src/components/LoadingIndicator.ts | 2 +- src/components/MediaTrackRadioButton.ts | 2 +- src/components/Menu.ts | 2 +- src/components/MenuButton.ts | 2 +- src/components/MenuGroup.ts | 2 +- src/components/MuteButton.ts | 2 +- src/components/PlayButton.ts | 2 +- src/components/PlaybackRateDisplay.ts | 2 +- src/components/PlaybackRateMenu.ts | 2 +- src/components/PlaybackRateMenuButton.ts | 2 +- src/components/PlaybackRateRadioGroup.ts | 2 +- src/components/PreviewThumbnail.ts | 2 +- src/components/PreviewTimeDisplay.ts | 2 +- src/components/QualityRadioButton.ts | 2 +- src/components/QualityRadioGroup.ts | 2 +- src/components/RadioButton.ts | 2 +- src/components/RadioGroup.ts | 2 +- src/components/SeekButton.ts | 2 +- src/components/SettingsMenu.ts | 2 +- src/components/SettingsMenuButton.ts | 2 +- src/components/SlotContainer.ts | 2 +- src/components/TextTrackOffRadioButton.ts | 2 +- src/components/TextTrackStyleDisplay.ts | 2 +- src/components/TextTrackStyleMenu.ts | 2 +- src/components/TextTrackStyleRadioGroup.ts | 2 +- src/components/TextTrackStyleResetButton.ts | 2 +- src/components/TimeDisplay.ts | 2 +- src/components/TimeRange.ts | 2 +- src/components/TrackRadioGroup.ts | 2 +- src/components/VolumeRange.ts | 2 +- src/components/ads/AdClickThroughButton.ts | 2 +- src/components/ads/AdCountdown.ts | 2 +- src/components/ads/AdDisplay.ts | 2 +- src/components/ads/AdSkipButton.ts | 2 +- 51 files changed, 51 insertions(+), 51 deletions(-) diff --git a/src/DefaultUI.ts b/src/DefaultUI.ts index 89799d23..47a4316a 100644 --- a/src/DefaultUI.ts +++ b/src/DefaultUI.ts @@ -17,7 +17,7 @@ import { toggleAttribute } from './util/CommonUtils'; import { createCustomEvent } from './util/EventUtils'; /** - * `` - A default UI for THEOplayer. + * A default UI for THEOplayer. * * This default UI provides a great player experience out-of-the-box, that works well on all types of devices * and for all types of streams. It provides all the common playback controls for playing, seeking, diff --git a/src/UIContainer.ts b/src/UIContainer.ts index 745ce2e0..36c15f45 100644 --- a/src/UIContainer.ts +++ b/src/UIContainer.ts @@ -48,7 +48,7 @@ export const DEFAULT_DVR_THRESHOLD = 60; export const FULL_WINDOW_ROOT_CLASS = 'theoplayer-ui-full-window'; /** - * `` - The container element for a THEOplayer UI. + * The container element for a THEOplayer UI. * * This element provides a basic layout structure for a general player UI, and handles the creation and management * of a {@link theoplayer!ChromelessPlayer | THEOplayer player instance} for this UI. diff --git a/src/components/ActiveQualityDisplay.ts b/src/components/ActiveQualityDisplay.ts index a57af687..f8bd5c34 100644 --- a/src/components/ActiveQualityDisplay.ts +++ b/src/components/ActiveQualityDisplay.ts @@ -5,7 +5,7 @@ import type { VideoQuality } from 'theoplayer/chromeless'; import { formatQualityLabel } from '../util/TrackUtils'; /** - * `` - A control that displays the name of the active video quality. + * A control that displays the name of the active video quality. * * @group Components */ diff --git a/src/components/AirPlayButton.ts b/src/components/AirPlayButton.ts index b7625186..349b2bd8 100644 --- a/src/components/AirPlayButton.ts +++ b/src/components/AirPlayButton.ts @@ -7,7 +7,7 @@ import airPlayButtonCss from './AirPlayButton.css'; import { Attribute } from '../util/Attribute'; /** - * `` - A button to start and stop casting using AirPlay. + * A button to start and stop casting using AirPlay. * * @group Components */ diff --git a/src/components/Button.ts b/src/components/Button.ts index 6e495205..7b0cf221 100644 --- a/src/components/Button.ts +++ b/src/components/Button.ts @@ -21,7 +21,7 @@ export function buttonTemplate(button: string, extraCss: string = ''): string { } /** - * `` - A basic button. + * A basic button. * * @attribute `disabled` - Whether the button is disabled. When disabled, the button cannot be clicked. * @group Components diff --git a/src/components/ChromecastButton.ts b/src/components/ChromecastButton.ts index 71375cdd..0eaa2c2d 100644 --- a/src/components/ChromecastButton.ts +++ b/src/components/ChromecastButton.ts @@ -9,7 +9,7 @@ import { customElement } from 'lit/decorators.js'; let chromecastButtonId = 0; /** - * `` - A button to start and stop casting using Chromecast. + * A button to start and stop casting using Chromecast. * * @group Components */ diff --git a/src/components/ChromecastDisplay.ts b/src/components/ChromecastDisplay.ts index bfcdec14..9bf5422d 100644 --- a/src/components/ChromecastDisplay.ts +++ b/src/components/ChromecastDisplay.ts @@ -10,7 +10,7 @@ import { Attribute } from '../util/Attribute'; const CAST_EVENTS = ['statechange'] as const; /** - * `` - A control that displays the casting status while using Chromecast. + * A control that displays the casting status while using Chromecast. * * @group Components */ diff --git a/src/components/CloseMenuButton.ts b/src/components/CloseMenuButton.ts index bcfcb5c8..84cf8489 100644 --- a/src/components/CloseMenuButton.ts +++ b/src/components/CloseMenuButton.ts @@ -8,7 +8,7 @@ import { CLOSE_MENU_EVENT, type CloseMenuEvent } from '../events/CloseMenuEvent' import { Attribute } from '../util/Attribute'; /** - * `` - A button that closes its parent menu. + * A button that closes its parent menu. * * This button must be placed inside a {@link Menu | ``}. * diff --git a/src/components/ControlBar.ts b/src/components/ControlBar.ts index 4c80437c..15601a1b 100644 --- a/src/components/ControlBar.ts +++ b/src/components/ControlBar.ts @@ -3,7 +3,7 @@ import { customElement } from 'lit/decorators.js'; import controlBarCss from './ControlBar.css'; /** - * `` - A horizontal control bar that can contain other controls. + * A horizontal control bar that can contain other controls. * * @group Components */ diff --git a/src/components/DurationDisplay.ts b/src/components/DurationDisplay.ts index b8fc7bdc..8e7da86b 100644 --- a/src/components/DurationDisplay.ts +++ b/src/components/DurationDisplay.ts @@ -9,7 +9,7 @@ import { Attribute } from '../util/Attribute'; const PLAYER_EVENTS = ['durationchange'] as const; /** - * `` - A control that displays the duration of the stream. + * A control that displays the duration of the stream. * * @group Components */ diff --git a/src/components/ErrorDisplay.ts b/src/components/ErrorDisplay.ts index 368d0edf..7109877b 100644 --- a/src/components/ErrorDisplay.ts +++ b/src/components/ErrorDisplay.ts @@ -8,7 +8,7 @@ import type { THEOplayerError } from 'theoplayer/chromeless'; import { Attribute } from '../util/Attribute'; /** - * `` - A screen that shows the details of a fatal player error. + * A screen that shows the details of a fatal player error. * * @group Components */ diff --git a/src/components/FullscreenButton.ts b/src/components/FullscreenButton.ts index 0ca3f5cf..cae4177f 100644 --- a/src/components/FullscreenButton.ts +++ b/src/components/FullscreenButton.ts @@ -12,7 +12,7 @@ import { EXIT_FULLSCREEN_EVENT, type ExitFullscreenEvent } from '../events/ExitF import { Attribute } from '../util/Attribute'; /** - * `` - A button that toggles fullscreen. + * A button that toggles fullscreen. * * @group Components */ diff --git a/src/components/GestureReceiver.ts b/src/components/GestureReceiver.ts index 0fb307f9..f9224977 100644 --- a/src/components/GestureReceiver.ts +++ b/src/components/GestureReceiver.ts @@ -6,7 +6,7 @@ import type { ChromelessPlayer } from 'theoplayer/chromeless'; import type { DeviceType } from '../util/DeviceType'; /** - * `` - An overlay that receives and handles gestures on the player. + * An overlay that receives and handles gestures on the player. * * On desktop devices, this plays or pauses the player whenever it is clicked. * On mobile devices, this currently does nothing. diff --git a/src/components/LanguageMenu.ts b/src/components/LanguageMenu.ts index d8701722..9bf3b170 100644 --- a/src/components/LanguageMenu.ts +++ b/src/components/LanguageMenu.ts @@ -14,7 +14,7 @@ import './TextTrackStyleMenu'; const TRACK_EVENTS = ['addtrack', 'removetrack'] as const; /** - * `` - A menu to change the spoken language and subtitles of the stream. + * A menu to change the spoken language and subtitles of the stream. * * @slot `heading` - A slot for the menu's heading. * diff --git a/src/components/LanguageMenuButton.ts b/src/components/LanguageMenuButton.ts index f697c5bd..ab55a495 100644 --- a/src/components/LanguageMenuButton.ts +++ b/src/components/LanguageMenuButton.ts @@ -12,7 +12,7 @@ import { toggleAttribute } from '../util/CommonUtils'; const TRACK_EVENTS = ['addtrack', 'removetrack'] as const; /** - * `` - A menu button that opens a {@link LanguageMenu}. + * A menu button that opens a {@link LanguageMenu}. * * When there are no alternative audio languages or subtitles, this button automatically hides itself. * diff --git a/src/components/LinkButton.ts b/src/components/LinkButton.ts index 3ba80b77..1386ca05 100644 --- a/src/components/LinkButton.ts +++ b/src/components/LinkButton.ts @@ -15,7 +15,7 @@ export function linkButtonTemplate(button: string, extraCss: string = ''): strin } /** - * `` - A {@link Button | button} that opens a hyperlink. + * A {@link Button | button} that opens a hyperlink. * * @attribute `disabled` - Whether the button is disabled. When disabled, the button cannot be clicked. * @group Components diff --git a/src/components/LiveButton.ts b/src/components/LiveButton.ts index 7de13c99..50ffca5e 100644 --- a/src/components/LiveButton.ts +++ b/src/components/LiveButton.ts @@ -15,7 +15,7 @@ const LIVE_EVENTS = ['seeking', 'seeked', 'timeupdate', 'durationchange', 'empti const DEFAULT_LIVE_THRESHOLD = 10; /** - * `` - A button that shows whether the player is currently playing at the live point, + * A button that shows whether the player is currently playing at the live point, * and seeks to the live point when clicked. * * @attribute `live-threshold` - The maximum distance (in seconds) from the live point that the player's current time diff --git a/src/components/LoadingIndicator.ts b/src/components/LoadingIndicator.ts index 5783ac1c..29d4e5b4 100644 --- a/src/components/LoadingIndicator.ts +++ b/src/components/LoadingIndicator.ts @@ -9,7 +9,7 @@ import { type HTMLTemplateResult, LitElement } from 'lit'; const PLAYER_EVENTS = ['readystatechange', 'play', 'pause', 'playing', 'seeking', 'seeked'] as const; /** - * `` - An indicator that shows whether the player is currently waiting for more data to resume playback. + * An indicator that shows whether the player is currently waiting for more data to resume playback. * * @attribute `loading` (readonly) - Whether the player is waiting for more data. If set, the indicator is shown. * @group Components diff --git a/src/components/MediaTrackRadioButton.ts b/src/components/MediaTrackRadioButton.ts index ed7813a0..316101d2 100644 --- a/src/components/MediaTrackRadioButton.ts +++ b/src/components/MediaTrackRadioButton.ts @@ -7,7 +7,7 @@ import { localizeLanguageName } from '../util/CommonUtils'; const TRACK_EVENTS = ['change', 'update'] as const; /** - * `` - A radio button that shows the label of a given media track, + * A radio button that shows the label of a given media track, * and switches to that track when clicked. * * @group Components diff --git a/src/components/Menu.ts b/src/components/Menu.ts index 510e317a..fd4e044f 100644 --- a/src/components/Menu.ts +++ b/src/components/Menu.ts @@ -28,7 +28,7 @@ export function menuTemplate(heading: string, content: string, extraCss: string } /** - * `` - A menu that can be opened on top of the player. + * A menu that can be opened on top of the player. * * The menu has a heading at the top, with a {@link CloseMenuButton | close button} and a heading text. * diff --git a/src/components/MenuButton.ts b/src/components/MenuButton.ts index 36e4d616..c0a0c8c8 100644 --- a/src/components/MenuButton.ts +++ b/src/components/MenuButton.ts @@ -5,7 +5,7 @@ import { Attribute } from '../util/Attribute'; import { customElement, property } from 'lit/decorators.js'; /** - * `` - A menu button that opens a {@link Menu}. + * A menu button that opens a {@link Menu}. * * @attribute `menu` - The ID of the menu to open. * @group Components diff --git a/src/components/MenuGroup.ts b/src/components/MenuGroup.ts index 73fb2140..8799b326 100644 --- a/src/components/MenuGroup.ts +++ b/src/components/MenuGroup.ts @@ -42,7 +42,7 @@ interface OpenMenuEntry { } /** - * `` - A group of {@link Menu}s. + * A group of {@link Menu}s. * * This can contain multiple other menus, which can be opened with {@link openMenu}. * When a {@link MenuButton} in one menu opens another menu in this group, it is opened as a "submenu". diff --git a/src/components/MuteButton.ts b/src/components/MuteButton.ts index 4b6180a2..6d07f29f 100644 --- a/src/components/MuteButton.ts +++ b/src/components/MuteButton.ts @@ -15,7 +15,7 @@ export type VolumeLevel = 'off' | 'low' | 'high'; const PLAYER_EVENTS = ['volumechange'] as const; /** - * `` - A button that toggles whether audio is muted or not. + * A button that toggles whether audio is muted or not. * * @attribute `volume-level` (readonly) - The volume level of the player. * Can be "off" (muted), "low" (volume < 50%) or "high" (volume >= 50%). diff --git a/src/components/PlayButton.ts b/src/components/PlayButton.ts index f7a97de7..8feb67df 100644 --- a/src/components/PlayButton.ts +++ b/src/components/PlayButton.ts @@ -13,7 +13,7 @@ import { Attribute } from '../util/Attribute'; const PLAYER_EVENTS = ['seeking', 'seeked', 'ended', 'emptied', 'sourcechange'] as const; /** - * `` - A button that toggles whether the player is playing or paused. + * A button that toggles whether the player is playing or paused. * * @attribute `paused` (readonly) - Whether the player is paused. Reflects `ui.player.paused`. * @attribute `ended` (readonly) - Whether the player is ended. Reflects `ui.player.ended`. diff --git a/src/components/PlaybackRateDisplay.ts b/src/components/PlaybackRateDisplay.ts index 05e41386..0b2bfa6f 100644 --- a/src/components/PlaybackRateDisplay.ts +++ b/src/components/PlaybackRateDisplay.ts @@ -3,7 +3,7 @@ import { stateReceiver } from './StateReceiverMixin'; import { customElement, state } from 'lit/decorators.js'; /** - * `` - A control that displays the current playback rate of the player. + * A control that displays the current playback rate of the player. * * @group Components */ diff --git a/src/components/PlaybackRateMenu.ts b/src/components/PlaybackRateMenu.ts index 53ea9b75..4b3fd2b8 100644 --- a/src/components/PlaybackRateMenu.ts +++ b/src/components/PlaybackRateMenu.ts @@ -8,7 +8,7 @@ import './PlaybackRateRadioGroup'; const PLAYBACK_RATES = [0.25, 0.5, 1, 1.25, 1.5, 2]; /** - * `` - A menu to change the playback rate of the player. + * A menu to change the playback rate of the player. * * @slot `heading` - A slot for the menu's heading. * diff --git a/src/components/PlaybackRateMenuButton.ts b/src/components/PlaybackRateMenuButton.ts index 9f6a28e4..d5a1c4bb 100644 --- a/src/components/PlaybackRateMenuButton.ts +++ b/src/components/PlaybackRateMenuButton.ts @@ -6,7 +6,7 @@ import speedIcon from '../icons/speed.svg'; import { Attribute } from '../util/Attribute'; /** - * `` - A menu button that opens a [playback rate menu]{@link PlaybackRateMenu}. + * A menu button that opens a [playback rate menu]{@link PlaybackRateMenu}. * * @attribute menu - The ID of the playback rate menu. * @group Components diff --git a/src/components/PlaybackRateRadioGroup.ts b/src/components/PlaybackRateRadioGroup.ts index 829a4099..c1f98f3c 100644 --- a/src/components/PlaybackRateRadioGroup.ts +++ b/src/components/PlaybackRateRadioGroup.ts @@ -9,7 +9,7 @@ import type { RadioButton } from './RadioButton'; import { createEvent } from '../util/EventUtils'; /** - * `` - A radio group that shows a list of playback rates, + * A radio group that shows a list of playback rates, * from which the user can choose a desired playback rate. * * @slot {@link RadioButton} - The possible options for the playback rate. diff --git a/src/components/PreviewThumbnail.ts b/src/components/PreviewThumbnail.ts index 9b45fcdd..60598f4b 100644 --- a/src/components/PreviewThumbnail.ts +++ b/src/components/PreviewThumbnail.ts @@ -10,7 +10,7 @@ import { arrayFind, noOp } from '../util/CommonUtils'; const TRACK_EVENTS = ['addtrack', 'removetrack'] as const; /** - * `` - A display that shows the thumbnail image at the current preview time + * A display that shows the thumbnail image at the current preview time * of a {@link TimeRange | ``}. * * The first `metadata` text track whose label equals `"thumbnails"` is used as source for the thumbnails. diff --git a/src/components/PreviewTimeDisplay.ts b/src/components/PreviewTimeDisplay.ts index fcc6bed6..f81e210b 100644 --- a/src/components/PreviewTimeDisplay.ts +++ b/src/components/PreviewTimeDisplay.ts @@ -10,7 +10,7 @@ import type { StreamType } from '../util/StreamType'; const PLAYER_EVENTS = ['timeupdate', 'seeking', 'seeked', 'durationchange'] as const; /** - * `` - A display that shows the current preview time of a {@link TimeRange | ``}. + * A display that shows the current preview time of a {@link TimeRange | ``}. * * @attribute `remaining` - If set, shows the remaining time of the stream. * @attribute `remaining-when-live` - If set, and the stream is a livestream, shows the remaining time diff --git a/src/components/QualityRadioButton.ts b/src/components/QualityRadioButton.ts index c54ed98b..0186e1f2 100644 --- a/src/components/QualityRadioButton.ts +++ b/src/components/QualityRadioButton.ts @@ -8,7 +8,7 @@ const TRACK_EVENTS = ['activequalitychanged', 'targetqualitychanged'] as const; const QUALITY_EVENTS = ['update'] as const; /** - * `` - A radio button that shows the label of a given video quality, + * A radio button that shows the label of a given video quality, * and switches the video track's {@link theoplayer!MediaTrack.targetQuality | target quality} to that quality when clicked. * * @group Components diff --git a/src/components/QualityRadioGroup.ts b/src/components/QualityRadioGroup.ts index 929c0733..17d9e4dc 100644 --- a/src/components/QualityRadioGroup.ts +++ b/src/components/QualityRadioGroup.ts @@ -10,7 +10,7 @@ import { repeat } from 'lit/directives/repeat.js'; const TRACK_EVENTS = ['addtrack', 'removetrack', 'change'] as const; /** - * `` - A radio group that shows a list of available video qualities, + * A radio group that shows a list of available video qualities, * from which the user can choose a desired target quality. * * @group Components diff --git a/src/components/RadioButton.ts b/src/components/RadioButton.ts index e6cd5976..a55342f3 100644 --- a/src/components/RadioButton.ts +++ b/src/components/RadioButton.ts @@ -5,7 +5,7 @@ import { createEvent } from '../util/EventUtils'; import type { RadioGroup } from './RadioGroup'; /** - * `` - A button that can be checked. + * A button that can be checked. * * When part of a {@link RadioGroup}, at most one button in the group can be checked. * diff --git a/src/components/RadioGroup.ts b/src/components/RadioGroup.ts index 027645c5..78ff557d 100644 --- a/src/components/RadioGroup.ts +++ b/src/components/RadioGroup.ts @@ -11,7 +11,7 @@ import type { DeviceType } from '../util/DeviceType'; import { navigateByArrowKey } from '../util/KeyboardNavigation'; /** - * `` - A group of {@link RadioButton}s. At most one button in the group can be checked. + * A group of {@link RadioButton}s. At most one button in the group can be checked. * * ## Behavior * This radio group implements the [roving tabindex](https://www.w3.org/WAI/ARIA/apg/example-index/radio/radio.html) pattern. diff --git a/src/components/SeekButton.ts b/src/components/SeekButton.ts index 1bb0cb2a..3e49d792 100644 --- a/src/components/SeekButton.ts +++ b/src/components/SeekButton.ts @@ -11,7 +11,7 @@ import { unsafeSVG } from 'lit/directives/unsafe-svg.js'; const DEFAULT_SEEK_OFFSET = 10; /** - * `` - A button that seeks forward or backward by a fixed offset. + * A button that seeks forward or backward by a fixed offset. * * @attribute `seek-offset` - The offset (in seconds) by which to seek forward (if positive) or backward (if negative). * @group Components diff --git a/src/components/SettingsMenu.ts b/src/components/SettingsMenu.ts index 6b6ddee0..8739be34 100644 --- a/src/components/SettingsMenu.ts +++ b/src/components/SettingsMenu.ts @@ -9,7 +9,7 @@ import './PlaybackRateDisplay'; import './PlaybackRateMenu'; /** - * `` - A menu to change the settings of the player, + * A menu to change the settings of the player, * such as the active video quality and the playback speed. * * @slot `heading` - A slot for the menu's heading. diff --git a/src/components/SettingsMenuButton.ts b/src/components/SettingsMenuButton.ts index 6f2ebf5f..dbb03c31 100644 --- a/src/components/SettingsMenuButton.ts +++ b/src/components/SettingsMenuButton.ts @@ -6,7 +6,7 @@ import settingsIcon from '../icons/settings.svg'; import { Attribute } from '../util/Attribute'; /** - * `` - A menu button that opens a {@link SettingsMenu}. + * A menu button that opens a {@link SettingsMenu}. * * @attribute `menu` - The ID of the settings menu. * @group Components diff --git a/src/components/SlotContainer.ts b/src/components/SlotContainer.ts index c99e7e24..5f1956f9 100644 --- a/src/components/SlotContainer.ts +++ b/src/components/SlotContainer.ts @@ -3,7 +3,7 @@ import { customElement } from 'lit/decorators.js'; import slotContainerCss from './SlotContainer.css'; /** - * `` - A container that can be assigned to a slot, + * A container that can be assigned to a slot, * and behaves as if all its children are directly assigned to that slot. * * This behaves approximately like a regular `
` with style `display: contents`, diff --git a/src/components/TextTrackOffRadioButton.ts b/src/components/TextTrackOffRadioButton.ts index ceef5a5c..e44dad4f 100644 --- a/src/components/TextTrackOffRadioButton.ts +++ b/src/components/TextTrackOffRadioButton.ts @@ -7,7 +7,7 @@ import { isNonForcedSubtitleTrack, isSubtitleTrack } from '../util/TrackUtils'; const TRACK_EVENTS = ['change'] as const; /** - * `` - A radio button that disables the active subtitle track when clicked. + * A radio button that disables the active subtitle track when clicked. * * @group Components */ diff --git a/src/components/TextTrackStyleDisplay.ts b/src/components/TextTrackStyleDisplay.ts index 4c0104d8..a5839a2f 100644 --- a/src/components/TextTrackStyleDisplay.ts +++ b/src/components/TextTrackStyleDisplay.ts @@ -9,7 +9,7 @@ import { arrayFind } from '../util/CommonUtils'; import { knownColors, knownEdgeStyles, knownFontFamilies } from '../util/TextTrackStylePresets'; /** - * `` - A control that displays the value of a single text track style option + * A control that displays the value of a single text track style option * in a human-readable format. * * @attribute `property` - The property name of the text track style option. One of {@link TextTrackStyleOption}. diff --git a/src/components/TextTrackStyleMenu.ts b/src/components/TextTrackStyleMenu.ts index 64ba1a63..21d73453 100644 --- a/src/components/TextTrackStyleMenu.ts +++ b/src/components/TextTrackStyleMenu.ts @@ -52,7 +52,7 @@ const edgeStyleOptions: ReadonlyArray<{ label: string; value: EdgeStyle | '' }> ]; /** - * `` - A menu to change the {@link theoplayer!TextTrackStyle | text track style} of the player. + * A menu to change the {@link theoplayer!TextTrackStyle | text track style} of the player. * * @slot `heading` - A slot for the menu's heading. * diff --git a/src/components/TextTrackStyleRadioGroup.ts b/src/components/TextTrackStyleRadioGroup.ts index 2edf756e..26bb2e60 100644 --- a/src/components/TextTrackStyleRadioGroup.ts +++ b/src/components/TextTrackStyleRadioGroup.ts @@ -25,7 +25,7 @@ export interface TextTrackStyleMap { export type TextTrackStyleOption = keyof TextTrackStyleMap; /** - * `` - A radio group that shows a list of values for a text track style option, + * A radio group that shows a list of values for a text track style option, * from which the user can choose a desired value. * * @attribute `property` - The property name of the text track style option. One of {@link TextTrackStyleOption}. diff --git a/src/components/TextTrackStyleResetButton.ts b/src/components/TextTrackStyleResetButton.ts index 8576da86..c2e5ba6c 100644 --- a/src/components/TextTrackStyleResetButton.ts +++ b/src/components/TextTrackStyleResetButton.ts @@ -5,7 +5,7 @@ import { stateReceiver } from './StateReceiverMixin'; import type { ChromelessPlayer } from 'theoplayer/chromeless'; /** - * `` - A button that resets the text track style. + * A button that resets the text track style. * * @group Components */ diff --git a/src/components/TimeDisplay.ts b/src/components/TimeDisplay.ts index 741aa2ce..3c8ccd97 100644 --- a/src/components/TimeDisplay.ts +++ b/src/components/TimeDisplay.ts @@ -12,7 +12,7 @@ const PLAYER_EVENTS = ['timeupdate', 'seeking', 'seeked', 'durationchange'] as c const DEFAULT_MISSING_TIME_PHRASE = 'video not loaded, unknown time'; /** - * `` - A control that displays the current time of the stream. + * A control that displays the current time of the stream. * * @attribute `show-duration` - If set, also shows the duration of the stream. * @attribute `remaining` - If set, shows the remaining time of the stream. Not compatible with `show-duration`. diff --git a/src/components/TimeRange.ts b/src/components/TimeRange.ts index 69d019e7..dbc7a7d7 100644 --- a/src/components/TimeRange.ts +++ b/src/components/TimeRange.ts @@ -31,7 +31,7 @@ const DEFAULT_MISSING_TIME_PHRASE = 'video not loaded, unknown time'; const AD_MARKER_WIDTH = 1; /** - * `` - A seek bar, showing the current time of the player, + * A seek bar, showing the current time of the player, * and which seeks the player when clicked or dragged. * * @slot `preview` - A slot holding a preview of the seek time, shown while hovering the seek bar. diff --git a/src/components/TrackRadioGroup.ts b/src/components/TrackRadioGroup.ts index a5b233c0..dd8ae26b 100644 --- a/src/components/TrackRadioGroup.ts +++ b/src/components/TrackRadioGroup.ts @@ -13,7 +13,7 @@ const TRACK_EVENTS = ['addtrack', 'removetrack'] as const; export type TrackType = 'audio' | 'video' | 'subtitles'; /** - * `` - A radio group that shows a list of media or text tracks, + * A radio group that shows a list of media or text tracks, * from which the user can choose an active track. * * @attribute `track-type` - The track type of the available tracks. Can be "audio", "video" or "subtitles". diff --git a/src/components/VolumeRange.ts b/src/components/VolumeRange.ts index f7f7f09c..a84decbd 100644 --- a/src/components/VolumeRange.ts +++ b/src/components/VolumeRange.ts @@ -8,7 +8,7 @@ function formatAsPercentString(value: number, max: number) { } /** - * `` - A volume slider, showing the current audio volume of the player, + * A volume slider, showing the current audio volume of the player, * and which changes the volume when clicked or dragged. * * @group Components diff --git a/src/components/ads/AdClickThroughButton.ts b/src/components/ads/AdClickThroughButton.ts index 49a3d6b8..541fd45b 100644 --- a/src/components/ads/AdClickThroughButton.ts +++ b/src/components/ads/AdClickThroughButton.ts @@ -10,7 +10,7 @@ import { isLinearAd } from '../../util/AdUtils'; const AD_EVENTS = ['adbegin', 'adend', 'adloaded', 'updatead', 'adskip'] as const; /** - * `` - A button to open the advertisement's click-through webpage. + * A button to open the advertisement's click-through webpage. * * @group Components */ diff --git a/src/components/ads/AdCountdown.ts b/src/components/ads/AdCountdown.ts index 33f3108a..3c35c699 100644 --- a/src/components/ads/AdCountdown.ts +++ b/src/components/ads/AdCountdown.ts @@ -8,7 +8,7 @@ import type { Ads, ChromelessPlayer } from 'theoplayer/chromeless'; const AD_EVENTS = ['adbreakbegin', 'adbreakend', 'adbreakchange', 'updateadbreak'] as const; /** - * `` - A control that displays the remaining time of the current ad break. + * A control that displays the remaining time of the current ad break. * * @group Components */ diff --git a/src/components/ads/AdDisplay.ts b/src/components/ads/AdDisplay.ts index b1e22a46..fb610d1f 100644 --- a/src/components/ads/AdDisplay.ts +++ b/src/components/ads/AdDisplay.ts @@ -10,7 +10,7 @@ import { isLinearAd } from '../../util/AdUtils'; const AD_EVENTS = ['adbreakbegin', 'adbreakend', 'adbreakchange', 'updateadbreak', 'adbegin', 'adend', 'adskip', 'addad', 'updatead'] as const; /** - * `` - A control that shows when an advertisement is playing, + * A control that shows when an advertisement is playing, * and the number of the current ad in the ad break (if the break has multiple ads). * * @group Components diff --git a/src/components/ads/AdSkipButton.ts b/src/components/ads/AdSkipButton.ts index 48c51f52..d2c33b72 100644 --- a/src/components/ads/AdSkipButton.ts +++ b/src/components/ads/AdSkipButton.ts @@ -14,7 +14,7 @@ import { isLinearAd } from '../../util/AdUtils'; const AD_EVENTS = ['adbegin', 'adend', 'adloaded', 'updatead', 'adskip'] as const; /** - * `` - A button that skips the current advertisement (if skippable). + * A button that skips the current advertisement (if skippable). * If the ad cannot be skipped yet, it shows the remaining time until it can be skipped. * * @group Components From 5fa681b16f920953f63400fa147d90f94fce2e35 Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Thu, 25 Sep 2025 16:11:20 +0200 Subject: [PATCH 118/123] Automatically add `@group Components` --- scripts/typedoc-lit-decorators.mts | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/typedoc-lit-decorators.mts b/scripts/typedoc-lit-decorators.mts index bac57f56..220991df 100644 --- a/scripts/typedoc-lit-decorators.mts +++ b/scripts/typedoc-lit-decorators.mts @@ -78,6 +78,7 @@ function extractDecoratorInfo(context: typedoc.Context, refl: typedoc.Reflection const tagName = callArgument.text; const comment = (refl.comment ??= new typedoc.Comment([])); comment.blockTags.push(new typedoc.CommentTag(`@customElement`, [{ kind: 'code', text: `\`<${tagName}>\`` }])); + comment.blockTags.push(new typedoc.CommentTag(`@group`, [{ kind: 'text', text: 'Components' }])); break; } case 'property': { From 9fd6fd03a0bdf256c63a003de39857686225552c Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Thu, 25 Sep 2025 16:12:26 +0200 Subject: [PATCH 119/123] Remove manual `@group` tags --- src/DefaultUI.ts | 1 - src/THEOliveDefaultUI.ts | 2 -- src/UIContainer.ts | 1 - src/components/ActiveQualityDisplay.ts | 2 -- src/components/AirPlayButton.ts | 2 -- src/components/Button.ts | 1 - src/components/ChromecastButton.ts | 2 -- src/components/ChromecastDisplay.ts | 2 -- src/components/CloseMenuButton.ts | 2 -- src/components/ControlBar.ts | 2 -- src/components/DurationDisplay.ts | 2 -- src/components/ErrorDisplay.ts | 2 -- src/components/FullscreenButton.ts | 2 -- src/components/GestureReceiver.ts | 2 -- src/components/LanguageMenu.ts | 2 -- src/components/LanguageMenuButton.ts | 1 - src/components/LinkButton.ts | 1 - src/components/LiveButton.ts | 1 - src/components/LoadingIndicator.ts | 1 - src/components/MediaTrackRadioButton.ts | 2 -- src/components/Menu.ts | 2 -- src/components/MenuButton.ts | 1 - src/components/MenuGroup.ts | 1 - src/components/MuteButton.ts | 1 - src/components/PlayButton.ts | 1 - src/components/PlaybackRateDisplay.ts | 2 -- src/components/PlaybackRateMenu.ts | 2 -- src/components/PlaybackRateMenuButton.ts | 1 - src/components/PlaybackRateRadioGroup.ts | 1 - src/components/PreviewThumbnail.ts | 1 - src/components/PreviewTimeDisplay.ts | 1 - src/components/QualityRadioButton.ts | 2 -- src/components/QualityRadioGroup.ts | 2 -- src/components/RadioButton.ts | 2 -- src/components/RadioGroup.ts | 2 -- src/components/SeekButton.ts | 1 - src/components/SettingsMenu.ts | 2 -- src/components/SettingsMenuButton.ts | 1 - src/components/SlotContainer.ts | 2 -- src/components/TextTrackOffRadioButton.ts | 2 -- src/components/TextTrackRadioButton.ts | 2 -- src/components/TextTrackStyleDisplay.ts | 1 - src/components/TextTrackStyleMenu.ts | 2 -- src/components/TextTrackStyleRadioGroup.ts | 1 - src/components/TextTrackStyleResetButton.ts | 2 -- src/components/TimeDisplay.ts | 1 - src/components/TimeRange.ts | 1 - src/components/TrackRadioGroup.ts | 1 - src/components/VolumeRange.ts | 2 -- src/components/ads/AdClickThroughButton.ts | 2 -- src/components/ads/AdCountdown.ts | 2 -- src/components/ads/AdDisplay.ts | 2 -- src/components/ads/AdSkipButton.ts | 2 -- src/components/theolive/quality/AbstractQualitySelector.ts | 2 -- src/components/theolive/quality/BadNetworkModeButton.ts | 1 - 55 files changed, 87 deletions(-) diff --git a/src/DefaultUI.ts b/src/DefaultUI.ts index 47a4316a..ce3573cc 100644 --- a/src/DefaultUI.ts +++ b/src/DefaultUI.ts @@ -64,7 +64,6 @@ import { createCustomEvent } from './util/EventUtils'; * @slot `menu` - A slot for extra menus (see {@link Menu | ``}). * @slot `error` - A slot for an error display, to show when the player encounters a fatal error. * By default, this shows an {@link ErrorDisplay | ``}. - * @group Components */ @customElement('theoplayer-default-ui') export class DefaultUI extends LitElement { diff --git a/src/THEOliveDefaultUI.ts b/src/THEOliveDefaultUI.ts index 25239f9a..202aede7 100644 --- a/src/THEOliveDefaultUI.ts +++ b/src/THEOliveDefaultUI.ts @@ -10,8 +10,6 @@ import { Attribute } from './util/Attribute'; /** * `` - A default UI for THEOlive. - * - * @group Components */ @customElement('theolive-default-ui') export class THEOliveDefaultUI extends DefaultUI { diff --git a/src/UIContainer.ts b/src/UIContainer.ts index 36c15f45..f3ec2c30 100644 --- a/src/UIContainer.ts +++ b/src/UIContainer.ts @@ -110,7 +110,6 @@ export const FULL_WINDOW_ROOT_CLASS = 'theoplayer-ui-full-window'; * @slot `menu` - A slot for extra menus (see {@link Menu | ``}). * @slot `error` - A slot for an error display, to show when the player encounters a fatal error * (see {@link ErrorDisplay | ``}). - * @group Components */ @customElement('theoplayer-ui') export class UIContainer extends LitElement { diff --git a/src/components/ActiveQualityDisplay.ts b/src/components/ActiveQualityDisplay.ts index f8bd5c34..d0cc408b 100644 --- a/src/components/ActiveQualityDisplay.ts +++ b/src/components/ActiveQualityDisplay.ts @@ -6,8 +6,6 @@ import { formatQualityLabel } from '../util/TrackUtils'; /** * A control that displays the name of the active video quality. - * - * @group Components */ @customElement('theoplayer-active-quality-display') @stateReceiver(['activeVideoQuality', 'targetVideoQualities']) diff --git a/src/components/AirPlayButton.ts b/src/components/AirPlayButton.ts index 349b2bd8..67af8ae1 100644 --- a/src/components/AirPlayButton.ts +++ b/src/components/AirPlayButton.ts @@ -8,8 +8,6 @@ import { Attribute } from '../util/Attribute'; /** * A button to start and stop casting using AirPlay. - * - * @group Components */ @customElement('theoplayer-airplay-button') @stateReceiver(['player']) diff --git a/src/components/Button.ts b/src/components/Button.ts index 7b0cf221..27a00588 100644 --- a/src/components/Button.ts +++ b/src/components/Button.ts @@ -24,7 +24,6 @@ export function buttonTemplate(button: string, extraCss: string = ''): string { * A basic button. * * @attribute `disabled` - Whether the button is disabled. When disabled, the button cannot be clicked. - * @group Components */ // Based on howto-toggle-button // https://github.com/GoogleChromeLabs/howto-components/blob/079d0fa34ff9038b26ea8883b1db5dd6b677d7ba/elements/howto-toggle-button/howto-toggle-button.js diff --git a/src/components/ChromecastButton.ts b/src/components/ChromecastButton.ts index 0eaa2c2d..f27026e4 100644 --- a/src/components/ChromecastButton.ts +++ b/src/components/ChromecastButton.ts @@ -10,8 +10,6 @@ let chromecastButtonId = 0; /** * A button to start and stop casting using Chromecast. - * - * @group Components */ @customElement('theoplayer-chromecast-button') @stateReceiver(['player']) diff --git a/src/components/ChromecastDisplay.ts b/src/components/ChromecastDisplay.ts index 9bf5422d..664746ef 100644 --- a/src/components/ChromecastDisplay.ts +++ b/src/components/ChromecastDisplay.ts @@ -11,8 +11,6 @@ const CAST_EVENTS = ['statechange'] as const; /** * A control that displays the casting status while using Chromecast. - * - * @group Components */ @customElement('theoplayer-chromecast-display') @stateReceiver(['player']) diff --git a/src/components/CloseMenuButton.ts b/src/components/CloseMenuButton.ts index 84cf8489..c935af97 100644 --- a/src/components/CloseMenuButton.ts +++ b/src/components/CloseMenuButton.ts @@ -11,8 +11,6 @@ import { Attribute } from '../util/Attribute'; * A button that closes its parent menu. * * This button must be placed inside a {@link Menu | ``}. - * - * @group Components */ @customElement('theoplayer-menu-close-button') export class CloseMenuButton extends Button { diff --git a/src/components/ControlBar.ts b/src/components/ControlBar.ts index 15601a1b..90f92e82 100644 --- a/src/components/ControlBar.ts +++ b/src/components/ControlBar.ts @@ -4,8 +4,6 @@ import controlBarCss from './ControlBar.css'; /** * A horizontal control bar that can contain other controls. - * - * @group Components */ @customElement('theoplayer-control-bar') export class ControlBar extends LitElement { diff --git a/src/components/DurationDisplay.ts b/src/components/DurationDisplay.ts index 8e7da86b..f9f49cd7 100644 --- a/src/components/DurationDisplay.ts +++ b/src/components/DurationDisplay.ts @@ -10,8 +10,6 @@ const PLAYER_EVENTS = ['durationchange'] as const; /** * A control that displays the duration of the stream. - * - * @group Components */ @customElement('theoplayer-duration-display') @stateReceiver(['player']) diff --git a/src/components/ErrorDisplay.ts b/src/components/ErrorDisplay.ts index 7109877b..5dfd37a1 100644 --- a/src/components/ErrorDisplay.ts +++ b/src/components/ErrorDisplay.ts @@ -9,8 +9,6 @@ import { Attribute } from '../util/Attribute'; /** * A screen that shows the details of a fatal player error. - * - * @group Components */ @customElement('theoplayer-error-display') @stateReceiver(['error', 'fullscreen']) diff --git a/src/components/FullscreenButton.ts b/src/components/FullscreenButton.ts index cae4177f..5de90813 100644 --- a/src/components/FullscreenButton.ts +++ b/src/components/FullscreenButton.ts @@ -13,8 +13,6 @@ import { Attribute } from '../util/Attribute'; /** * A button that toggles fullscreen. - * - * @group Components */ @customElement('theoplayer-fullscreen-button') @stateReceiver(['fullscreen']) diff --git a/src/components/GestureReceiver.ts b/src/components/GestureReceiver.ts index f9224977..fb5564bf 100644 --- a/src/components/GestureReceiver.ts +++ b/src/components/GestureReceiver.ts @@ -10,8 +10,6 @@ import type { DeviceType } from '../util/DeviceType'; * * On desktop devices, this plays or pauses the player whenever it is clicked. * On mobile devices, this currently does nothing. - * - * @group Components */ @customElement('theoplayer-gesture-receiver') @stateReceiver(['player', 'deviceType']) diff --git a/src/components/LanguageMenu.ts b/src/components/LanguageMenu.ts index 9bf3b170..20615493 100644 --- a/src/components/LanguageMenu.ts +++ b/src/components/LanguageMenu.ts @@ -17,8 +17,6 @@ const TRACK_EVENTS = ['addtrack', 'removetrack'] as const; * A menu to change the spoken language and subtitles of the stream. * * @slot `heading` - A slot for the menu's heading. - * - * @group Components */ @customElement('theoplayer-language-menu') @stateReceiver(['player']) diff --git a/src/components/LanguageMenuButton.ts b/src/components/LanguageMenuButton.ts index ab55a495..45a97fd2 100644 --- a/src/components/LanguageMenuButton.ts +++ b/src/components/LanguageMenuButton.ts @@ -17,7 +17,6 @@ const TRACK_EVENTS = ['addtrack', 'removetrack'] as const; * When there are no alternative audio languages or subtitles, this button automatically hides itself. * * @attribute `menu` - The ID of the language menu. - * @group Components */ @customElement('theoplayer-language-menu-button') @stateReceiver(['player']) diff --git a/src/components/LinkButton.ts b/src/components/LinkButton.ts index 1386ca05..a36000d9 100644 --- a/src/components/LinkButton.ts +++ b/src/components/LinkButton.ts @@ -18,7 +18,6 @@ export function linkButtonTemplate(button: string, extraCss: string = ''): strin * A {@link Button | button} that opens a hyperlink. * * @attribute `disabled` - Whether the button is disabled. When disabled, the button cannot be clicked. - * @group Components */ @customElement('theoplayer-link-button') export class LinkButton extends LitElement { diff --git a/src/components/LiveButton.ts b/src/components/LiveButton.ts index 50ffca5e..ed299f94 100644 --- a/src/components/LiveButton.ts +++ b/src/components/LiveButton.ts @@ -21,7 +21,6 @@ const DEFAULT_LIVE_THRESHOLD = 10; * @attribute `live-threshold` - The maximum distance (in seconds) from the live point that the player's current time * can be for it to still be considered "at the live point". If unset, defaults to 10 seconds. * @attribute `live` (readonly) - Whether the player is considered to be playing at the live point. - * @group Components */ @customElement('theoplayer-live-button') @stateReceiver(['player', 'streamType']) diff --git a/src/components/LoadingIndicator.ts b/src/components/LoadingIndicator.ts index 29d4e5b4..00882fe0 100644 --- a/src/components/LoadingIndicator.ts +++ b/src/components/LoadingIndicator.ts @@ -12,7 +12,6 @@ const PLAYER_EVENTS = ['readystatechange', 'play', 'pause', 'playing', 'seeking' * An indicator that shows whether the player is currently waiting for more data to resume playback. * * @attribute `loading` (readonly) - Whether the player is waiting for more data. If set, the indicator is shown. - * @group Components */ @customElement('theoplayer-loading-indicator') @stateReceiver(['player']) diff --git a/src/components/MediaTrackRadioButton.ts b/src/components/MediaTrackRadioButton.ts index 316101d2..0f7c3d27 100644 --- a/src/components/MediaTrackRadioButton.ts +++ b/src/components/MediaTrackRadioButton.ts @@ -9,8 +9,6 @@ const TRACK_EVENTS = ['change', 'update'] as const; /** * A radio button that shows the label of a given media track, * and switches to that track when clicked. - * - * @group Components */ @customElement('theoplayer-media-track-radio-button') export class MediaTrackRadioButton extends RadioButton { diff --git a/src/components/Menu.ts b/src/components/Menu.ts index fd4e044f..fd1c38e5 100644 --- a/src/components/Menu.ts +++ b/src/components/Menu.ts @@ -37,8 +37,6 @@ export function menuTemplate(heading: string, content: string, extraCss: string * @attribute `menu-opened` (readonly) - Whether the menu is currently open. * * @slot `heading` - A slot for the menu's heading. - * - * @group Components */ @customElement('theoplayer-menu') export class Menu extends LitElement { diff --git a/src/components/MenuButton.ts b/src/components/MenuButton.ts index c0a0c8c8..91b3c1b3 100644 --- a/src/components/MenuButton.ts +++ b/src/components/MenuButton.ts @@ -8,7 +8,6 @@ import { customElement, property } from 'lit/decorators.js'; * A menu button that opens a {@link Menu}. * * @attribute `menu` - The ID of the menu to open. - * @group Components */ @customElement('theoplayer-menu-button') export class MenuButton extends Button { diff --git a/src/components/MenuGroup.ts b/src/components/MenuGroup.ts index 8799b326..e192a1cd 100644 --- a/src/components/MenuGroup.ts +++ b/src/components/MenuGroup.ts @@ -49,7 +49,6 @@ interface OpenMenuEntry { * When a submenu is closed, the menu that originally opened it is shown again. * * @attribute `menu-opened` (readonly) - Whether any menu in the group is currently open. - * @group Components */ @customElement('theoplayer-menu-group') export class MenuGroup extends LitElement { diff --git a/src/components/MuteButton.ts b/src/components/MuteButton.ts index 6d07f29f..6ed444b6 100644 --- a/src/components/MuteButton.ts +++ b/src/components/MuteButton.ts @@ -19,7 +19,6 @@ const PLAYER_EVENTS = ['volumechange'] as const; * * @attribute `volume-level` (readonly) - The volume level of the player. * Can be "off" (muted), "low" (volume < 50%) or "high" (volume >= 50%). - * @group Components */ @customElement('theoplayer-mute-button') @stateReceiver(['player']) diff --git a/src/components/PlayButton.ts b/src/components/PlayButton.ts index 8feb67df..e0b5942e 100644 --- a/src/components/PlayButton.ts +++ b/src/components/PlayButton.ts @@ -17,7 +17,6 @@ const PLAYER_EVENTS = ['seeking', 'seeked', 'ended', 'emptied', 'sourcechange'] * * @attribute `paused` (readonly) - Whether the player is paused. Reflects `ui.player.paused`. * @attribute `ended` (readonly) - Whether the player is ended. Reflects `ui.player.ended`. - * @group Components */ @customElement('theoplayer-play-button') @stateReceiver(['player']) diff --git a/src/components/PlaybackRateDisplay.ts b/src/components/PlaybackRateDisplay.ts index 0b2bfa6f..619f9f7a 100644 --- a/src/components/PlaybackRateDisplay.ts +++ b/src/components/PlaybackRateDisplay.ts @@ -4,8 +4,6 @@ import { customElement, state } from 'lit/decorators.js'; /** * A control that displays the current playback rate of the player. - * - * @group Components */ @customElement('theoplayer-playback-rate-display') @stateReceiver(['playbackRate']) diff --git a/src/components/PlaybackRateMenu.ts b/src/components/PlaybackRateMenu.ts index 4b3fd2b8..9e23a026 100644 --- a/src/components/PlaybackRateMenu.ts +++ b/src/components/PlaybackRateMenu.ts @@ -11,8 +11,6 @@ const PLAYBACK_RATES = [0.25, 0.5, 1, 1.25, 1.5, 2]; * A menu to change the playback rate of the player. * * @slot `heading` - A slot for the menu's heading. - * - * @group Components */ @customElement('theoplayer-playback-rate-menu') export class PlaybackRateMenu extends Menu { diff --git a/src/components/PlaybackRateMenuButton.ts b/src/components/PlaybackRateMenuButton.ts index d5a1c4bb..4aef34f5 100644 --- a/src/components/PlaybackRateMenuButton.ts +++ b/src/components/PlaybackRateMenuButton.ts @@ -9,7 +9,6 @@ import { Attribute } from '../util/Attribute'; * A menu button that opens a [playback rate menu]{@link PlaybackRateMenu}. * * @attribute menu - The ID of the playback rate menu. - * @group Components */ @customElement('theoplayer-playback-rate-menu-button') export class PlaybackRateMenuButton extends MenuButton { diff --git a/src/components/PlaybackRateRadioGroup.ts b/src/components/PlaybackRateRadioGroup.ts index c1f98f3c..219bc991 100644 --- a/src/components/PlaybackRateRadioGroup.ts +++ b/src/components/PlaybackRateRadioGroup.ts @@ -15,7 +15,6 @@ import { createEvent } from '../util/EventUtils'; * @slot {@link RadioButton} - The possible options for the playback rate. * The value of each radio button must be a valid number. * For example: `2x` - * @group Components */ @customElement('theoplayer-playback-rate-radio-group') @stateReceiver(['player']) diff --git a/src/components/PreviewThumbnail.ts b/src/components/PreviewThumbnail.ts index 60598f4b..b330ba11 100644 --- a/src/components/PreviewThumbnail.ts +++ b/src/components/PreviewThumbnail.ts @@ -19,7 +19,6 @@ const TRACK_EVENTS = ['addtrack', 'removetrack'] as const; * (e.g. `#xywh=180,80,60,40`), then the thumbnail is clipped to the rectangle defined by that fragment. * * If the stream does not contain thumbnails, then this display shows nothing. - * @group Components */ @customElement('theoplayer-preview-thumbnail') @stateReceiver(['player', 'previewTime']) diff --git a/src/components/PreviewTimeDisplay.ts b/src/components/PreviewTimeDisplay.ts index f81e210b..105a77fc 100644 --- a/src/components/PreviewTimeDisplay.ts +++ b/src/components/PreviewTimeDisplay.ts @@ -15,7 +15,6 @@ const PLAYER_EVENTS = ['timeupdate', 'seeking', 'seeked', 'durationchange'] as c * @attribute `remaining` - If set, shows the remaining time of the stream. * @attribute `remaining-when-live` - If set, and the stream is a livestream, shows the remaining time * (until the live point) of the stream. - * @group Components */ @customElement('theoplayer-preview-time-display') @stateReceiver(['player', 'previewTime', 'streamType']) diff --git a/src/components/QualityRadioButton.ts b/src/components/QualityRadioButton.ts index 0186e1f2..e9141e89 100644 --- a/src/components/QualityRadioButton.ts +++ b/src/components/QualityRadioButton.ts @@ -10,8 +10,6 @@ const QUALITY_EVENTS = ['update'] as const; /** * A radio button that shows the label of a given video quality, * and switches the video track's {@link theoplayer!MediaTrack.targetQuality | target quality} to that quality when clicked. - * - * @group Components */ @customElement('theoplayer-quality-radio-button') export class QualityRadioButton extends RadioButton { diff --git a/src/components/QualityRadioGroup.ts b/src/components/QualityRadioGroup.ts index 17d9e4dc..62e77ffa 100644 --- a/src/components/QualityRadioGroup.ts +++ b/src/components/QualityRadioGroup.ts @@ -12,8 +12,6 @@ const TRACK_EVENTS = ['addtrack', 'removetrack', 'change'] as const; /** * A radio group that shows a list of available video qualities, * from which the user can choose a desired target quality. - * - * @group Components */ @customElement('theoplayer-quality-radio-group') @stateReceiver(['player']) diff --git a/src/components/RadioButton.ts b/src/components/RadioButton.ts index a55342f3..c416ce0e 100644 --- a/src/components/RadioButton.ts +++ b/src/components/RadioButton.ts @@ -8,8 +8,6 @@ import type { RadioGroup } from './RadioGroup'; * A button that can be checked. * * When part of a {@link RadioGroup}, at most one button in the group can be checked. - * - * @group Components */ @customElement('theoplayer-radio-button') export class RadioButton extends Button { diff --git a/src/components/RadioGroup.ts b/src/components/RadioGroup.ts index 78ff557d..98b6e438 100644 --- a/src/components/RadioGroup.ts +++ b/src/components/RadioGroup.ts @@ -20,8 +20,6 @@ import { navigateByArrowKey } from '../util/KeyboardNavigation'; * - `Down`/`Right` arrow moves focus to the next radio button. * - `Home` moves focus to the first radio button. * - `End` moves focus to the last radio button. - * - * @group Components */ // Based on howto-radio-group // https://github.com/GoogleChromeLabs/howto-components/blob/079d0fa34ff9038b26ea8883b1db5dd6b677d7ba/elements/howto-radio-group/howto-radio-group.js diff --git a/src/components/SeekButton.ts b/src/components/SeekButton.ts index 3e49d792..34e82ea9 100644 --- a/src/components/SeekButton.ts +++ b/src/components/SeekButton.ts @@ -14,7 +14,6 @@ const DEFAULT_SEEK_OFFSET = 10; * A button that seeks forward or backward by a fixed offset. * * @attribute `seek-offset` - The offset (in seconds) by which to seek forward (if positive) or backward (if negative). - * @group Components */ @customElement('theoplayer-seek-button') @stateReceiver(['player']) diff --git a/src/components/SettingsMenu.ts b/src/components/SettingsMenu.ts index 8739be34..53a1e82c 100644 --- a/src/components/SettingsMenu.ts +++ b/src/components/SettingsMenu.ts @@ -13,8 +13,6 @@ import './PlaybackRateMenu'; * such as the active video quality and the playback speed. * * @slot `heading` - A slot for the menu's heading. - * - * @group Components */ @customElement('theoplayer-settings-menu') export class SettingsMenu extends MenuGroup { diff --git a/src/components/SettingsMenuButton.ts b/src/components/SettingsMenuButton.ts index dbb03c31..08bcc82a 100644 --- a/src/components/SettingsMenuButton.ts +++ b/src/components/SettingsMenuButton.ts @@ -9,7 +9,6 @@ import { Attribute } from '../util/Attribute'; * A menu button that opens a {@link SettingsMenu}. * * @attribute `menu` - The ID of the settings menu. - * @group Components */ @customElement('theoplayer-settings-menu-button') export class SettingsMenuButton extends MenuButton { diff --git a/src/components/SlotContainer.ts b/src/components/SlotContainer.ts index 5f1956f9..346cbc08 100644 --- a/src/components/SlotContainer.ts +++ b/src/components/SlotContainer.ts @@ -13,8 +13,6 @@ import slotContainerCss from './SlotContainer.css'; * * This is an internal component, used mainly by Open Video UI for React. * You shouldn't need this under normal circumstances. - * - * @group Components * @internal */ @customElement('theoplayer-slot-container') diff --git a/src/components/TextTrackOffRadioButton.ts b/src/components/TextTrackOffRadioButton.ts index e44dad4f..934455b0 100644 --- a/src/components/TextTrackOffRadioButton.ts +++ b/src/components/TextTrackOffRadioButton.ts @@ -8,8 +8,6 @@ const TRACK_EVENTS = ['change'] as const; /** * A radio button that disables the active subtitle track when clicked. - * - * @group Components */ @customElement('theoplayer-text-track-off-radio-button') export class TextTrackOffRadioButton extends RadioButton { diff --git a/src/components/TextTrackRadioButton.ts b/src/components/TextTrackRadioButton.ts index 73393909..a3407ef2 100644 --- a/src/components/TextTrackRadioButton.ts +++ b/src/components/TextTrackRadioButton.ts @@ -8,8 +8,6 @@ const TRACK_EVENTS = ['change', 'update'] as const; /** * `` -A radio button that shows the label of a given text track, and switches to that track when clicked. - * - * @group Components */ @customElement('theoplayer-text-track-radio-button') export class TextTrackRadioButton extends RadioButton { diff --git a/src/components/TextTrackStyleDisplay.ts b/src/components/TextTrackStyleDisplay.ts index a5839a2f..a7bf18c1 100644 --- a/src/components/TextTrackStyleDisplay.ts +++ b/src/components/TextTrackStyleDisplay.ts @@ -13,7 +13,6 @@ import { knownColors, knownEdgeStyles, knownFontFamilies } from '../util/TextTra * in a human-readable format. * * @attribute `property` - The property name of the text track style option. One of {@link TextTrackStyleOption}. - * @group Components */ @customElement('theoplayer-text-track-style-display') @stateReceiver(['player']) diff --git a/src/components/TextTrackStyleMenu.ts b/src/components/TextTrackStyleMenu.ts index 21d73453..91a65c47 100644 --- a/src/components/TextTrackStyleMenu.ts +++ b/src/components/TextTrackStyleMenu.ts @@ -55,8 +55,6 @@ const edgeStyleOptions: ReadonlyArray<{ label: string; value: EdgeStyle | '' }> * A menu to change the {@link theoplayer!TextTrackStyle | text track style} of the player. * * @slot `heading` - A slot for the menu's heading. - * - * @group Components */ @customElement('theoplayer-text-track-style-menu') export class TextTrackStyleMenu extends MenuGroup { diff --git a/src/components/TextTrackStyleRadioGroup.ts b/src/components/TextTrackStyleRadioGroup.ts index 26bb2e60..e0496793 100644 --- a/src/components/TextTrackStyleRadioGroup.ts +++ b/src/components/TextTrackStyleRadioGroup.ts @@ -31,7 +31,6 @@ export type TextTrackStyleOption = keyof TextTrackStyleMap; * @attribute `property` - The property name of the text track style option. One of {@link TextTrackStyleOption}. * @slot {@link RadioButton} - The possible options for the text track style option. * For example: `Red` - * @group Components */ @customElement('theoplayer-text-track-style-radio-group') @stateReceiver(['player']) diff --git a/src/components/TextTrackStyleResetButton.ts b/src/components/TextTrackStyleResetButton.ts index c2e5ba6c..9b41e303 100644 --- a/src/components/TextTrackStyleResetButton.ts +++ b/src/components/TextTrackStyleResetButton.ts @@ -6,8 +6,6 @@ import type { ChromelessPlayer } from 'theoplayer/chromeless'; /** * A button that resets the text track style. - * - * @group Components */ @customElement('theoplayer-text-track-style-reset-button') @stateReceiver(['player']) diff --git a/src/components/TimeDisplay.ts b/src/components/TimeDisplay.ts index 3c8ccd97..6145dfd5 100644 --- a/src/components/TimeDisplay.ts +++ b/src/components/TimeDisplay.ts @@ -18,7 +18,6 @@ const DEFAULT_MISSING_TIME_PHRASE = 'video not loaded, unknown time'; * @attribute `remaining` - If set, shows the remaining time of the stream. Not compatible with `show-duration`. * @attribute `remaining-when-live` - If set, and the stream is a livestream, shows the remaining time * (until the live point) of the stream. - * @group Components */ @customElement('theoplayer-time-display') @stateReceiver(['player', 'streamType']) diff --git a/src/components/TimeRange.ts b/src/components/TimeRange.ts index dbc7a7d7..adcfc9bf 100644 --- a/src/components/TimeRange.ts +++ b/src/components/TimeRange.ts @@ -37,7 +37,6 @@ const AD_MARKER_WIDTH = 1; * @slot `preview` - A slot holding a preview of the seek time, shown while hovering the seek bar. * By default, this shows the {@link PreviewTimeDisplay | preview time} and * the {@link PreviewThumbnail | preview thumbnail}. - * @group Components */ @customElement('theoplayer-time-range') @stateReceiver(['player', 'streamType', 'deviceType']) diff --git a/src/components/TrackRadioGroup.ts b/src/components/TrackRadioGroup.ts index dd8ae26b..324de2f1 100644 --- a/src/components/TrackRadioGroup.ts +++ b/src/components/TrackRadioGroup.ts @@ -19,7 +19,6 @@ export type TrackType = 'audio' | 'video' | 'subtitles'; * @attribute `track-type` - The track type of the available tracks. Can be "audio", "video" or "subtitles". * @attribute `show-off` - If set, shows an "off" button to disable all tracks. * Can only be used with the "subtitles" track type. - * @group Components */ @customElement('theoplayer-track-radio-group') @stateReceiver(['player']) diff --git a/src/components/VolumeRange.ts b/src/components/VolumeRange.ts index a84decbd..38f5e21c 100644 --- a/src/components/VolumeRange.ts +++ b/src/components/VolumeRange.ts @@ -10,8 +10,6 @@ function formatAsPercentString(value: number, max: number) { /** * A volume slider, showing the current audio volume of the player, * and which changes the volume when clicked or dragged. - * - * @group Components */ @customElement('theoplayer-volume-range') @stateReceiver(['player', 'deviceType']) diff --git a/src/components/ads/AdClickThroughButton.ts b/src/components/ads/AdClickThroughButton.ts index 541fd45b..813b47d1 100644 --- a/src/components/ads/AdClickThroughButton.ts +++ b/src/components/ads/AdClickThroughButton.ts @@ -11,8 +11,6 @@ const AD_EVENTS = ['adbegin', 'adend', 'adloaded', 'updatead', 'adskip'] as cons /** * A button to open the advertisement's click-through webpage. - * - * @group Components */ @customElement('theoplayer-ad-clickthrough-button') @stateReceiver(['player']) diff --git a/src/components/ads/AdCountdown.ts b/src/components/ads/AdCountdown.ts index 3c35c699..210f6b02 100644 --- a/src/components/ads/AdCountdown.ts +++ b/src/components/ads/AdCountdown.ts @@ -9,8 +9,6 @@ const AD_EVENTS = ['adbreakbegin', 'adbreakend', 'adbreakchange', 'updateadbreak /** * A control that displays the remaining time of the current ad break. - * - * @group Components */ @customElement('theoplayer-ad-countdown') @stateReceiver(['player']) diff --git a/src/components/ads/AdDisplay.ts b/src/components/ads/AdDisplay.ts index fb610d1f..4a6de4cd 100644 --- a/src/components/ads/AdDisplay.ts +++ b/src/components/ads/AdDisplay.ts @@ -12,8 +12,6 @@ const AD_EVENTS = ['adbreakbegin', 'adbreakend', 'adbreakchange', 'updateadbreak /** * A control that shows when an advertisement is playing, * and the number of the current ad in the ad break (if the break has multiple ads). - * - * @group Components */ @customElement('theoplayer-ad-display') @stateReceiver(['player']) diff --git a/src/components/ads/AdSkipButton.ts b/src/components/ads/AdSkipButton.ts index d2c33b72..93810bc4 100644 --- a/src/components/ads/AdSkipButton.ts +++ b/src/components/ads/AdSkipButton.ts @@ -16,8 +16,6 @@ const AD_EVENTS = ['adbegin', 'adend', 'adloaded', 'updatead', 'adskip'] as cons /** * A button that skips the current advertisement (if skippable). * If the ad cannot be skipped yet, it shows the remaining time until it can be skipped. - * - * @group Components */ @customElement('theoplayer-ad-skip-button') @stateReceiver(['player']) diff --git a/src/components/theolive/quality/AbstractQualitySelector.ts b/src/components/theolive/quality/AbstractQualitySelector.ts index aaa4ee96..95947ab8 100644 --- a/src/components/theolive/quality/AbstractQualitySelector.ts +++ b/src/components/theolive/quality/AbstractQualitySelector.ts @@ -5,8 +5,6 @@ import { RadioButton } from '../../RadioButton'; /** * A radio button that shows the label of a given video quality, and switches the video track's * {@link theoplayer!MediaTrack.targetQuality | target quality} to that quality when clicked. - * - * @group Components */ @stateReceiver(['player']) export abstract class AbstractQualitySelector extends RadioButton { diff --git a/src/components/theolive/quality/BadNetworkModeButton.ts b/src/components/theolive/quality/BadNetworkModeButton.ts index df6a307c..5670bbf0 100644 --- a/src/components/theolive/quality/BadNetworkModeButton.ts +++ b/src/components/theolive/quality/BadNetworkModeButton.ts @@ -14,7 +14,6 @@ import { Attribute } from '../../../util/Attribute'; * A menu button that opens a settings menu. * * @attribute `menu` - The ID of the settings menu. - * @group Components */ @customElement('theolive-bad-network-button') @stateReceiver(['player']) From 225f893956ed8f58d877d2a58f8f5a543cb15142 Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Thu, 25 Sep 2025 16:14:29 +0200 Subject: [PATCH 120/123] Put decorator tags at the front --- scripts/typedoc-lit-decorators.mts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/scripts/typedoc-lit-decorators.mts b/scripts/typedoc-lit-decorators.mts index 220991df..6533b051 100644 --- a/scripts/typedoc-lit-decorators.mts +++ b/scripts/typedoc-lit-decorators.mts @@ -77,8 +77,8 @@ function extractDecoratorInfo(context: typedoc.Context, refl: typedoc.Reflection if (!ts.isLiteralExpression(callArgument)) continue; const tagName = callArgument.text; const comment = (refl.comment ??= new typedoc.Comment([])); - comment.blockTags.push(new typedoc.CommentTag(`@customElement`, [{ kind: 'code', text: `\`<${tagName}>\`` }])); - comment.blockTags.push(new typedoc.CommentTag(`@group`, [{ kind: 'text', text: 'Components' }])); + comment.blockTags.unshift(new typedoc.CommentTag(`@customElement`, [{ kind: 'code', text: `\`<${tagName}>\`` }])); + comment.blockTags.unshift(new typedoc.CommentTag(`@group`, [{ kind: 'text', text: 'Components' }])); break; } case 'property': { @@ -94,7 +94,9 @@ function extractDecoratorInfo(context: typedoc.Context, refl: typedoc.Reflection continue; } const comment = (refl.comment ??= new typedoc.Comment([])); - comment.blockTags.push(new typedoc.CommentTag(`@attribute`, [{ kind: 'inline-tag', tag: '@link', text: attributeValue.getText() }])); + comment.blockTags.unshift( + new typedoc.CommentTag(`@attribute`, [{ kind: 'inline-tag', tag: '@link', text: attributeValue.getText() }]) + ); break; } } From 6e60fcd1d97818f2da1ed90f52862ee9b00306dc Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Thu, 25 Sep 2025 16:18:53 +0200 Subject: [PATCH 121/123] Update to TypeDoc 0.28 --- package-lock.json | 293 ++++++++++++++++++----------- package.json | 6 +- react/package.json | 11 +- scripts/typedoc-lit-decorators.mts | 4 +- 4 files changed, 191 insertions(+), 123 deletions(-) diff --git a/package-lock.json b/package-lock.json index c40fb7d5..7b81ff59 100644 --- a/package-lock.json +++ b/package-lock.json @@ -45,9 +45,9 @@ "serve": "^14.2.4", "theoplayer": "^10.6.1", "tslib": "^2.8.1", - "typedoc": "^0.27.7", - "typedoc-plugin-mdn-links": "^4.0.12", - "typescript": "^5.7.3" + "typedoc": "^0.28.13", + "typedoc-plugin-mdn-links": "^5.0.9", + "typescript": "^5.9.2" }, "peerDependencies": { "theoplayer": "^7 || ^8 || ^9 || ^10" @@ -1540,15 +1540,17 @@ "license": "MIT" }, "node_modules/@gerrit0/mini-shiki": { - "version": "1.27.2", - "resolved": "https://registry.npmjs.org/@gerrit0/mini-shiki/-/mini-shiki-1.27.2.tgz", - "integrity": "sha512-GeWyHz8ao2gBiUW4OJnQDxXQnFgZQwwQk05t/CVVgNBN7/rK8XZ7xY6YhLVv9tH3VppWWmr9DCl3MwemB/i+Og==", + "version": "3.13.0", + "resolved": "https://registry.npmjs.org/@gerrit0/mini-shiki/-/mini-shiki-3.13.0.tgz", + "integrity": "sha512-mCrNvZNYNrwKer5PWLF6cOc0OEe2eKzgy976x+IT2tynwJYl+7UpHTSeXQJGijgTcoOf+f359L946unWlYRnsg==", "dev": true, "license": "MIT", "dependencies": { - "@shikijs/engine-oniguruma": "^1.27.2", - "@shikijs/types": "^1.27.2", - "@shikijs/vscode-textmate": "^10.0.1" + "@shikijs/engine-oniguruma": "^3.13.0", + "@shikijs/langs": "^3.13.0", + "@shikijs/themes": "^3.13.0", + "@shikijs/types": "^3.13.0", + "@shikijs/vscode-textmate": "^10.0.2" } }, "node_modules/@inquirer/external-editor": { @@ -2452,31 +2454,51 @@ "license": "MIT" }, "node_modules/@shikijs/engine-oniguruma": { - "version": "1.29.2", - "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-1.29.2.tgz", - "integrity": "sha512-7iiOx3SG8+g1MnlzZVDYiaeHe7Ez2Kf2HrJzdmGwkRisT7r4rak0e655AcM/tF9JG/kg5fMNYlLLKglbN7gBqA==", + "version": "3.13.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-3.13.0.tgz", + "integrity": "sha512-O42rBGr4UDSlhT2ZFMxqM7QzIU+IcpoTMzb3W7AlziI1ZF7R8eS2M0yt5Ry35nnnTX/LTLXFPUjRFCIW+Operg==", "dev": true, "license": "MIT", "dependencies": { - "@shikijs/types": "1.29.2", - "@shikijs/vscode-textmate": "^10.0.1" + "@shikijs/types": "3.13.0", + "@shikijs/vscode-textmate": "^10.0.2" + } + }, + "node_modules/@shikijs/langs": { + "version": "3.13.0", + "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-3.13.0.tgz", + "integrity": "sha512-672c3WAETDYHwrRP0yLy3W1QYB89Hbpj+pO4KhxK6FzIrDI2FoEXNiNCut6BQmEApYLfuYfpgOZaqbY+E9b8wQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.13.0" + } + }, + "node_modules/@shikijs/themes": { + "version": "3.13.0", + "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-3.13.0.tgz", + "integrity": "sha512-Vxw1Nm1/Od8jyA7QuAenaV78BG2nSr3/gCGdBkLpfLscddCkzkL36Q5b67SrLLfvAJTOUzW39x4FHVCFriPVgg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.13.0" } }, "node_modules/@shikijs/types": { - "version": "1.29.2", - "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-1.29.2.tgz", - "integrity": "sha512-VJjK0eIijTZf0QSTODEXCqinjBn0joAHQ+aPSBzrv4O2d/QSbsMw+ZeSRx03kV34Hy7NzUvV/7NqfYGRLrASmw==", + "version": "3.13.0", + "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-3.13.0.tgz", + "integrity": "sha512-oM9P+NCFri/mmQ8LoFGVfVyemm5Hi27330zuOBp0annwJdKH1kOLndw3zCtAVDehPLg9fKqoEx3Ht/wNZxolfw==", "dev": true, "license": "MIT", "dependencies": { - "@shikijs/vscode-textmate": "^10.0.1", + "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "node_modules/@shikijs/vscode-textmate": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-10.0.1.tgz", - "integrity": "sha512-fTIQwLF+Qhuws31iw7Ncl1R3HUDtGwIipiJ9iU+UsDUwMhegFcQKQHd51nZjb7CArq0MvON8rbgCGQYWHUKAdg==", + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-10.0.2.tgz", + "integrity": "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==", "dev": true, "license": "MIT" }, @@ -8980,26 +9002,27 @@ } }, "node_modules/typedoc": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.27.7.tgz", - "integrity": "sha512-K/JaUPX18+61W3VXek1cWC5gwmuLvYTOXJzBvD9W7jFvbPnefRnCHQCEPw7MSNrP/Hj7JJrhZtDDLKdcYm6ucg==", + "version": "0.28.13", + "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.28.13.tgz", + "integrity": "sha512-dNWY8msnYB2a+7Audha+aTF1Pu3euiE7ySp53w8kEsXoYw7dMouV5A1UsTUY345aB152RHnmRMDiovuBi7BD+w==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@gerrit0/mini-shiki": "^1.24.0", + "@gerrit0/mini-shiki": "^3.12.0", "lunr": "^2.3.9", "markdown-it": "^14.1.0", "minimatch": "^9.0.5", - "yaml": "^2.6.1" + "yaml": "^2.8.1" }, "bin": { "typedoc": "bin/typedoc" }, "engines": { - "node": ">= 18" + "node": ">= 18", + "pnpm": ">= 10" }, "peerDependencies": { - "typescript": "5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x || 5.5.x || 5.6.x || 5.7.x" + "typescript": "5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x || 5.5.x || 5.6.x || 5.7.x || 5.8.x || 5.9.x" } }, "node_modules/typedoc-plugin-external-resolver": { @@ -9013,32 +9036,32 @@ } }, "node_modules/typedoc-plugin-mdn-links": { - "version": "4.0.12", - "resolved": "https://registry.npmjs.org/typedoc-plugin-mdn-links/-/typedoc-plugin-mdn-links-4.0.12.tgz", - "integrity": "sha512-pie8DmMoN9P6oUdwi5fWuPSfseqEqPAew5wJ3XOdQql5Pbuv/TQlS4RhBwV2hSy4u8eGqu00TbFjabicL9K1WQ==", + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/typedoc-plugin-mdn-links/-/typedoc-plugin-mdn-links-5.0.9.tgz", + "integrity": "sha512-kXssRKBhUd0JeHzFmxWVsGWVFR9WXafe70Y8Ed+MYH2Nu2647cqfGQN1OBKgvXpmAT8MTpACmUIQ7GnQnh1/iw==", "dev": true, "license": "MIT", "peerDependencies": { - "typedoc": "0.26.x || 0.27.x" + "typedoc": "0.27.x || 0.28.x" } }, "node_modules/typedoc/node_modules/yaml": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.7.0.tgz", - "integrity": "sha512-+hSoy/QHluxmC9kCIJyL/uyFmLmc+e5CFR5Wa+bpIhIj85LVb9ZH2nVnqrHoSvKogwODv0ClqZkmiSSaIH5LTA==", + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.1.tgz", + "integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==", "dev": true, "license": "ISC", "bin": { "yaml": "bin.mjs" }, "engines": { - "node": ">= 14" + "node": ">= 14.6" } }, "node_modules/typescript": { - "version": "5.7.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz", - "integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==", + "version": "5.9.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz", + "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==", "dev": true, "license": "Apache-2.0", "bin": { @@ -9298,10 +9321,10 @@ "rollup-plugin-swc3": "^0.12.1", "theoplayer": "^10.6.1", "tslib": "^2.8.1", - "typedoc": "^0.27.7", + "typedoc": "^0.28.13", "typedoc-plugin-external-resolver": "^1.0.3", - "typedoc-plugin-mdn-links": "^4.0.12", - "typescript": "^5.7.3" + "typedoc-plugin-mdn-links": "^5.0.9", + "typescript": "^5.9.2" }, "peerDependencies": { "@types/react": "^16.3.0 || ^17 || ^18", @@ -10028,14 +10051,16 @@ "dev": true }, "@gerrit0/mini-shiki": { - "version": "1.27.2", - "resolved": "https://registry.npmjs.org/@gerrit0/mini-shiki/-/mini-shiki-1.27.2.tgz", - "integrity": "sha512-GeWyHz8ao2gBiUW4OJnQDxXQnFgZQwwQk05t/CVVgNBN7/rK8XZ7xY6YhLVv9tH3VppWWmr9DCl3MwemB/i+Og==", + "version": "3.13.0", + "resolved": "https://registry.npmjs.org/@gerrit0/mini-shiki/-/mini-shiki-3.13.0.tgz", + "integrity": "sha512-mCrNvZNYNrwKer5PWLF6cOc0OEe2eKzgy976x+IT2tynwJYl+7UpHTSeXQJGijgTcoOf+f359L946unWlYRnsg==", "dev": true, "requires": { - "@shikijs/engine-oniguruma": "^1.27.2", - "@shikijs/types": "^1.27.2", - "@shikijs/vscode-textmate": "^10.0.1" + "@shikijs/engine-oniguruma": "^3.13.0", + "@shikijs/langs": "^3.13.0", + "@shikijs/themes": "^3.13.0", + "@shikijs/types": "^3.13.0", + "@shikijs/vscode-textmate": "^10.0.2" } }, "@inquirer/external-editor": { @@ -10519,29 +10544,47 @@ "dev": true }, "@shikijs/engine-oniguruma": { - "version": "1.29.2", - "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-1.29.2.tgz", - "integrity": "sha512-7iiOx3SG8+g1MnlzZVDYiaeHe7Ez2Kf2HrJzdmGwkRisT7r4rak0e655AcM/tF9JG/kg5fMNYlLLKglbN7gBqA==", + "version": "3.13.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-3.13.0.tgz", + "integrity": "sha512-O42rBGr4UDSlhT2ZFMxqM7QzIU+IcpoTMzb3W7AlziI1ZF7R8eS2M0yt5Ry35nnnTX/LTLXFPUjRFCIW+Operg==", + "dev": true, + "requires": { + "@shikijs/types": "3.13.0", + "@shikijs/vscode-textmate": "^10.0.2" + } + }, + "@shikijs/langs": { + "version": "3.13.0", + "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-3.13.0.tgz", + "integrity": "sha512-672c3WAETDYHwrRP0yLy3W1QYB89Hbpj+pO4KhxK6FzIrDI2FoEXNiNCut6BQmEApYLfuYfpgOZaqbY+E9b8wQ==", + "dev": true, + "requires": { + "@shikijs/types": "3.13.0" + } + }, + "@shikijs/themes": { + "version": "3.13.0", + "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-3.13.0.tgz", + "integrity": "sha512-Vxw1Nm1/Od8jyA7QuAenaV78BG2nSr3/gCGdBkLpfLscddCkzkL36Q5b67SrLLfvAJTOUzW39x4FHVCFriPVgg==", "dev": true, "requires": { - "@shikijs/types": "1.29.2", - "@shikijs/vscode-textmate": "^10.0.1" + "@shikijs/types": "3.13.0" } }, "@shikijs/types": { - "version": "1.29.2", - "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-1.29.2.tgz", - "integrity": "sha512-VJjK0eIijTZf0QSTODEXCqinjBn0joAHQ+aPSBzrv4O2d/QSbsMw+ZeSRx03kV34Hy7NzUvV/7NqfYGRLrASmw==", + "version": "3.13.0", + "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-3.13.0.tgz", + "integrity": "sha512-oM9P+NCFri/mmQ8LoFGVfVyemm5Hi27330zuOBp0annwJdKH1kOLndw3zCtAVDehPLg9fKqoEx3Ht/wNZxolfw==", "dev": true, "requires": { - "@shikijs/vscode-textmate": "^10.0.1", + "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "@shikijs/vscode-textmate": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-10.0.1.tgz", - "integrity": "sha512-fTIQwLF+Qhuws31iw7Ncl1R3HUDtGwIipiJ9iU+UsDUwMhegFcQKQHd51nZjb7CArq0MvON8rbgCGQYWHUKAdg==", + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-10.0.2.tgz", + "integrity": "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==", "dev": true }, "@sindresorhus/is": { @@ -10717,10 +10760,10 @@ "rollup-plugin-swc3": "^0.12.1", "theoplayer": "^10.6.1", "tslib": "^2.8.1", - "typedoc": "^0.27.7", + "typedoc": "^0.28.13", "typedoc-plugin-external-resolver": "^1.0.3", - "typedoc-plugin-mdn-links": "^4.0.12", - "typescript": "^5.7.3" + "typedoc-plugin-mdn-links": "^5.0.9", + "typescript": "^5.9.2" } }, "@theoplayer/web-ui": { @@ -10758,9 +10801,9 @@ "serve": "^14.2.4", "theoplayer": "^10.6.1", "tslib": "^2.8.1", - "typedoc": "^0.27.7", - "typedoc-plugin-mdn-links": "^4.0.12", - "typescript": "^5.7.3" + "typedoc": "^0.28.13", + "typedoc-plugin-mdn-links": "^5.0.9", + "typescript": "^5.9.2" }, "dependencies": { "@babel/code-frame": { @@ -11480,14 +11523,16 @@ "dev": true }, "@gerrit0/mini-shiki": { - "version": "1.27.2", - "resolved": "https://registry.npmjs.org/@gerrit0/mini-shiki/-/mini-shiki-1.27.2.tgz", - "integrity": "sha512-GeWyHz8ao2gBiUW4OJnQDxXQnFgZQwwQk05t/CVVgNBN7/rK8XZ7xY6YhLVv9tH3VppWWmr9DCl3MwemB/i+Og==", + "version": "3.13.0", + "resolved": "https://registry.npmjs.org/@gerrit0/mini-shiki/-/mini-shiki-3.13.0.tgz", + "integrity": "sha512-mCrNvZNYNrwKer5PWLF6cOc0OEe2eKzgy976x+IT2tynwJYl+7UpHTSeXQJGijgTcoOf+f359L946unWlYRnsg==", "dev": true, "requires": { - "@shikijs/engine-oniguruma": "^1.27.2", - "@shikijs/types": "^1.27.2", - "@shikijs/vscode-textmate": "^10.0.1" + "@shikijs/engine-oniguruma": "^3.13.0", + "@shikijs/langs": "^3.13.0", + "@shikijs/themes": "^3.13.0", + "@shikijs/types": "^3.13.0", + "@shikijs/vscode-textmate": "^10.0.2" } }, "@inquirer/external-editor": { @@ -11971,29 +12016,47 @@ "dev": true }, "@shikijs/engine-oniguruma": { - "version": "1.29.2", - "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-1.29.2.tgz", - "integrity": "sha512-7iiOx3SG8+g1MnlzZVDYiaeHe7Ez2Kf2HrJzdmGwkRisT7r4rak0e655AcM/tF9JG/kg5fMNYlLLKglbN7gBqA==", + "version": "3.13.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-3.13.0.tgz", + "integrity": "sha512-O42rBGr4UDSlhT2ZFMxqM7QzIU+IcpoTMzb3W7AlziI1ZF7R8eS2M0yt5Ry35nnnTX/LTLXFPUjRFCIW+Operg==", + "dev": true, + "requires": { + "@shikijs/types": "3.13.0", + "@shikijs/vscode-textmate": "^10.0.2" + } + }, + "@shikijs/langs": { + "version": "3.13.0", + "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-3.13.0.tgz", + "integrity": "sha512-672c3WAETDYHwrRP0yLy3W1QYB89Hbpj+pO4KhxK6FzIrDI2FoEXNiNCut6BQmEApYLfuYfpgOZaqbY+E9b8wQ==", + "dev": true, + "requires": { + "@shikijs/types": "3.13.0" + } + }, + "@shikijs/themes": { + "version": "3.13.0", + "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-3.13.0.tgz", + "integrity": "sha512-Vxw1Nm1/Od8jyA7QuAenaV78BG2nSr3/gCGdBkLpfLscddCkzkL36Q5b67SrLLfvAJTOUzW39x4FHVCFriPVgg==", "dev": true, "requires": { - "@shikijs/types": "1.29.2", - "@shikijs/vscode-textmate": "^10.0.1" + "@shikijs/types": "3.13.0" } }, "@shikijs/types": { - "version": "1.29.2", - "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-1.29.2.tgz", - "integrity": "sha512-VJjK0eIijTZf0QSTODEXCqinjBn0joAHQ+aPSBzrv4O2d/QSbsMw+ZeSRx03kV34Hy7NzUvV/7NqfYGRLrASmw==", + "version": "3.13.0", + "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-3.13.0.tgz", + "integrity": "sha512-oM9P+NCFri/mmQ8LoFGVfVyemm5Hi27330zuOBp0annwJdKH1kOLndw3zCtAVDehPLg9fKqoEx3Ht/wNZxolfw==", "dev": true, "requires": { - "@shikijs/vscode-textmate": "^10.0.1", + "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "@shikijs/vscode-textmate": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-10.0.1.tgz", - "integrity": "sha512-fTIQwLF+Qhuws31iw7Ncl1R3HUDtGwIipiJ9iU+UsDUwMhegFcQKQHd51nZjb7CArq0MvON8rbgCGQYWHUKAdg==", + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-10.0.2.tgz", + "integrity": "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==", "dev": true }, "@sindresorhus/is": { @@ -12169,10 +12232,10 @@ "rollup-plugin-swc3": "^0.12.1", "theoplayer": "^10.6.1", "tslib": "^2.8.1", - "typedoc": "^0.27.7", + "typedoc": "^0.28.13", "typedoc-plugin-external-resolver": "^1.0.3", - "typedoc-plugin-mdn-links": "^4.0.12", - "typescript": "^5.7.3" + "typedoc-plugin-mdn-links": "^5.0.9", + "typescript": "^5.9.2" } }, "@tokenizer/token": { @@ -16195,22 +16258,22 @@ "dev": true }, "typedoc": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.27.7.tgz", - "integrity": "sha512-K/JaUPX18+61W3VXek1cWC5gwmuLvYTOXJzBvD9W7jFvbPnefRnCHQCEPw7MSNrP/Hj7JJrhZtDDLKdcYm6ucg==", + "version": "0.28.13", + "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.28.13.tgz", + "integrity": "sha512-dNWY8msnYB2a+7Audha+aTF1Pu3euiE7ySp53w8kEsXoYw7dMouV5A1UsTUY345aB152RHnmRMDiovuBi7BD+w==", "dev": true, "requires": { - "@gerrit0/mini-shiki": "^1.24.0", + "@gerrit0/mini-shiki": "^3.12.0", "lunr": "^2.3.9", "markdown-it": "^14.1.0", "minimatch": "^9.0.5", - "yaml": "^2.6.1" + "yaml": "^2.8.1" }, "dependencies": { "yaml": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.7.0.tgz", - "integrity": "sha512-+hSoy/QHluxmC9kCIJyL/uyFmLmc+e5CFR5Wa+bpIhIj85LVb9ZH2nVnqrHoSvKogwODv0ClqZkmiSSaIH5LTA==", + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.1.tgz", + "integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==", "dev": true } } @@ -16223,16 +16286,16 @@ "requires": {} }, "typedoc-plugin-mdn-links": { - "version": "4.0.12", - "resolved": "https://registry.npmjs.org/typedoc-plugin-mdn-links/-/typedoc-plugin-mdn-links-4.0.12.tgz", - "integrity": "sha512-pie8DmMoN9P6oUdwi5fWuPSfseqEqPAew5wJ3XOdQql5Pbuv/TQlS4RhBwV2hSy4u8eGqu00TbFjabicL9K1WQ==", + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/typedoc-plugin-mdn-links/-/typedoc-plugin-mdn-links-5.0.9.tgz", + "integrity": "sha512-kXssRKBhUd0JeHzFmxWVsGWVFR9WXafe70Y8Ed+MYH2Nu2647cqfGQN1OBKgvXpmAT8MTpACmUIQ7GnQnh1/iw==", "dev": true, "requires": {} }, "typescript": { - "version": "5.7.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz", - "integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==", + "version": "5.9.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz", + "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==", "dev": true }, "uc.micro": { @@ -20405,22 +20468,22 @@ "dev": true }, "typedoc": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.27.7.tgz", - "integrity": "sha512-K/JaUPX18+61W3VXek1cWC5gwmuLvYTOXJzBvD9W7jFvbPnefRnCHQCEPw7MSNrP/Hj7JJrhZtDDLKdcYm6ucg==", + "version": "0.28.13", + "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.28.13.tgz", + "integrity": "sha512-dNWY8msnYB2a+7Audha+aTF1Pu3euiE7ySp53w8kEsXoYw7dMouV5A1UsTUY345aB152RHnmRMDiovuBi7BD+w==", "dev": true, "requires": { - "@gerrit0/mini-shiki": "^1.24.0", + "@gerrit0/mini-shiki": "^3.12.0", "lunr": "^2.3.9", "markdown-it": "^14.1.0", "minimatch": "^9.0.5", - "yaml": "^2.6.1" + "yaml": "^2.8.1" }, "dependencies": { "yaml": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.7.0.tgz", - "integrity": "sha512-+hSoy/QHluxmC9kCIJyL/uyFmLmc+e5CFR5Wa+bpIhIj85LVb9ZH2nVnqrHoSvKogwODv0ClqZkmiSSaIH5LTA==", + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.1.tgz", + "integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==", "dev": true } } @@ -20433,16 +20496,16 @@ "requires": {} }, "typedoc-plugin-mdn-links": { - "version": "4.0.12", - "resolved": "https://registry.npmjs.org/typedoc-plugin-mdn-links/-/typedoc-plugin-mdn-links-4.0.12.tgz", - "integrity": "sha512-pie8DmMoN9P6oUdwi5fWuPSfseqEqPAew5wJ3XOdQql5Pbuv/TQlS4RhBwV2hSy4u8eGqu00TbFjabicL9K1WQ==", + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/typedoc-plugin-mdn-links/-/typedoc-plugin-mdn-links-5.0.9.tgz", + "integrity": "sha512-kXssRKBhUd0JeHzFmxWVsGWVFR9WXafe70Y8Ed+MYH2Nu2647cqfGQN1OBKgvXpmAT8MTpACmUIQ7GnQnh1/iw==", "dev": true, "requires": {} }, "typescript": { - "version": "5.7.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz", - "integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==", + "version": "5.9.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz", + "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==", "dev": true }, "uc.micro": { diff --git a/package.json b/package.json index 45ea6b98..c6775589 100644 --- a/package.json +++ b/package.json @@ -90,9 +90,9 @@ "serve": "^14.2.4", "theoplayer": "^10.6.1", "tslib": "^2.8.1", - "typedoc": "^0.27.7", - "typedoc-plugin-mdn-links": "^4.0.12", - "typescript": "^5.7.3" + "typedoc": "^0.28.13", + "typedoc-plugin-mdn-links": "^5.0.9", + "typescript": "^5.9.2" }, "browserslist": [ "last 2 versions", diff --git a/react/package.json b/react/package.json index 9bb392e5..7cd9ccf5 100644 --- a/react/package.json +++ b/react/package.json @@ -74,10 +74,15 @@ "rollup-plugin-swc3": "^0.12.1", "theoplayer": "^10.6.1", "tslib": "^2.8.1", - "typedoc": "^0.27.7", + "typedoc": "^0.28.13", "typedoc-plugin-external-resolver": "^1.0.3", - "typedoc-plugin-mdn-links": "^4.0.12", - "typescript": "^5.7.3" + "typedoc-plugin-mdn-links": "^5.0.9", + "typescript": "^5.9.2" + }, + "overrides": { + "typedoc-plugin-external-resolver": { + "typedoc": "$typedoc" + } }, "browserslist": [ "last 2 versions", diff --git a/scripts/typedoc-lit-decorators.mts b/scripts/typedoc-lit-decorators.mts index 6533b051..5ee3be49 100644 --- a/scripts/typedoc-lit-decorators.mts +++ b/scripts/typedoc-lit-decorators.mts @@ -10,7 +10,7 @@ export function load(app: typedoc.Application) { } function onDeclaration(context: typedoc.Context, refl: typedoc.DeclarationReflection) { - const symbol = context.project.getSymbolFromReflection(refl); + const symbol = context.getSymbolFromReflection(refl); if (!symbol) return; const declarations = symbol.declarations; if (!declarations) return; @@ -20,7 +20,7 @@ function onDeclaration(context: typedoc.Context, refl: typedoc.DeclarationReflec } function onSignature(context: typedoc.Context, refl: typedoc.SignatureReflection) { - const symbol = context.project.getSymbolFromReflection(refl.parent); + const symbol = context.getSymbolFromReflection(refl.parent); if (!symbol) return; const declarations = symbol.declarations; if (!declarations) return; From 0ab86048887615d026d36af0386f938cab4b03d6 Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Mon, 13 Oct 2025 13:39:02 +0200 Subject: [PATCH 122/123] Update to TypeDoc 0.28.14 --- package-lock.json | 244 +++++++++++++++++++++++++++------------------ package.json | 2 +- react/package.json | 2 +- 3 files changed, 148 insertions(+), 100 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7b81ff59..b953a359 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@theoplayer/web-ui", - "version": "1.14.0", + "version": "1.16.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@theoplayer/web-ui", - "version": "1.14.0", + "version": "1.16.0", "license": "MIT", "workspaces": [ ".", @@ -45,7 +45,7 @@ "serve": "^14.2.4", "theoplayer": "^10.6.1", "tslib": "^2.8.1", - "typedoc": "^0.28.13", + "typedoc": "^0.28.14", "typedoc-plugin-mdn-links": "^5.0.9", "typescript": "^5.9.2" }, @@ -477,6 +477,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": ">=18" }, @@ -500,6 +501,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": ">=18" } @@ -584,6 +586,7 @@ "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -931,6 +934,7 @@ "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -1540,16 +1544,16 @@ "license": "MIT" }, "node_modules/@gerrit0/mini-shiki": { - "version": "3.13.0", - "resolved": "https://registry.npmjs.org/@gerrit0/mini-shiki/-/mini-shiki-3.13.0.tgz", - "integrity": "sha512-mCrNvZNYNrwKer5PWLF6cOc0OEe2eKzgy976x+IT2tynwJYl+7UpHTSeXQJGijgTcoOf+f359L946unWlYRnsg==", + "version": "3.20.0", + "resolved": "https://registry.npmjs.org/@gerrit0/mini-shiki/-/mini-shiki-3.20.0.tgz", + "integrity": "sha512-Wa57i+bMpK6PGJZ1f2myxo3iO+K/kZikcyvH8NIqNNZhQUbDav7V9LQmWOXhf946mz5c1NZ19WMsGYiDKTryzQ==", "dev": true, "license": "MIT", "dependencies": { - "@shikijs/engine-oniguruma": "^3.13.0", - "@shikijs/langs": "^3.13.0", - "@shikijs/themes": "^3.13.0", - "@shikijs/types": "^3.13.0", + "@shikijs/engine-oniguruma": "^3.20.0", + "@shikijs/langs": "^3.20.0", + "@shikijs/themes": "^3.20.0", + "@shikijs/types": "^3.20.0", "@shikijs/vscode-textmate": "^10.0.2" } }, @@ -2454,40 +2458,40 @@ "license": "MIT" }, "node_modules/@shikijs/engine-oniguruma": { - "version": "3.13.0", - "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-3.13.0.tgz", - "integrity": "sha512-O42rBGr4UDSlhT2ZFMxqM7QzIU+IcpoTMzb3W7AlziI1ZF7R8eS2M0yt5Ry35nnnTX/LTLXFPUjRFCIW+Operg==", + "version": "3.20.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-3.20.0.tgz", + "integrity": "sha512-Yx3gy7xLzM0ZOjqoxciHjA7dAt5tyzJE3L4uQoM83agahy+PlW244XJSrmJRSBvGYELDhYXPacD4R/cauV5bzQ==", "dev": true, "license": "MIT", "dependencies": { - "@shikijs/types": "3.13.0", + "@shikijs/types": "3.20.0", "@shikijs/vscode-textmate": "^10.0.2" } }, "node_modules/@shikijs/langs": { - "version": "3.13.0", - "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-3.13.0.tgz", - "integrity": "sha512-672c3WAETDYHwrRP0yLy3W1QYB89Hbpj+pO4KhxK6FzIrDI2FoEXNiNCut6BQmEApYLfuYfpgOZaqbY+E9b8wQ==", + "version": "3.20.0", + "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-3.20.0.tgz", + "integrity": "sha512-le+bssCxcSHrygCWuOrYJHvjus6zhQ2K7q/0mgjiffRbkhM4o1EWu2m+29l0yEsHDbWaWPNnDUTRVVBvBBeKaA==", "dev": true, "license": "MIT", "dependencies": { - "@shikijs/types": "3.13.0" + "@shikijs/types": "3.20.0" } }, "node_modules/@shikijs/themes": { - "version": "3.13.0", - "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-3.13.0.tgz", - "integrity": "sha512-Vxw1Nm1/Od8jyA7QuAenaV78BG2nSr3/gCGdBkLpfLscddCkzkL36Q5b67SrLLfvAJTOUzW39x4FHVCFriPVgg==", + "version": "3.20.0", + "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-3.20.0.tgz", + "integrity": "sha512-U1NSU7Sl26Q7ErRvJUouArxfM2euWqq1xaSrbqMu2iqa+tSp0D1Yah8216sDYbdDHw4C8b75UpE65eWorm2erQ==", "dev": true, "license": "MIT", "dependencies": { - "@shikijs/types": "3.13.0" + "@shikijs/types": "3.20.0" } }, "node_modules/@shikijs/types": { - "version": "3.13.0", - "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-3.13.0.tgz", - "integrity": "sha512-oM9P+NCFri/mmQ8LoFGVfVyemm5Hi27330zuOBp0annwJdKH1kOLndw3zCtAVDehPLg9fKqoEx3Ht/wNZxolfw==", + "version": "3.20.0", + "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-3.20.0.tgz", + "integrity": "sha512-lhYAATn10nkZcBQ0BlzSbJA3wcmL5MXUUF8d2Zzon6saZDlToKaiRX60n2+ZaHJCmXEcZRWNzn+k9vplr8Jhsw==", "dev": true, "license": "MIT", "dependencies": { @@ -2567,6 +2571,7 @@ "dev": true, "hasInstallScript": true, "license": "Apache-2.0", + "peer": true, "dependencies": { "@swc/counter": "^0.1.3", "@swc/types": "^0.1.24" @@ -2892,7 +2897,8 @@ "version": "20.7.0", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.7.0.tgz", "integrity": "sha512-zI22/pJW2wUZOVyguFaUL1HABdmSVxpXrzIqkjsHmyUjNhPoWM1CKfvVuXfetHhIok4RY573cqS0mZ1SJEnoTg==", - "dev": true + "dev": true, + "peer": true }, "node_modules/@types/prop-types": { "version": "15.7.11", @@ -2904,6 +2910,7 @@ "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.18.tgz", "integrity": "sha512-t4yC+vtgnkYjNSKlFx1jkAhH8LgTo2N/7Qvi83kdEaUtMDiwpbLAktKDaAMlRcJ5eSxZkH74eEGt1ky31d7kfQ==", "license": "MIT", + "peer": true, "dependencies": { "@types/prop-types": "*", "csstype": "^3.0.2" @@ -3554,6 +3561,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001688", "electron-to-chromium": "^1.5.73", @@ -4154,6 +4162,7 @@ "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -6276,6 +6285,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "nanoid": "^3.3.8", "picocolors": "^1.1.1", @@ -7217,6 +7227,7 @@ "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -7865,6 +7876,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", "dev": true, + "peer": true, "dependencies": { "loose-envify": "^1.1.0" }, @@ -8016,6 +8028,7 @@ "integrity": "sha512-I25/2QgoROE1vYV+NQ1En9T9UFB9Cmfm2CJ83zZOlaDpvz29wGQSZXWKw7MiNXau7wYgB/T9fVIdIuEQ+KbiiA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/estree": "1.0.8" }, @@ -8918,6 +8931,7 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -9002,13 +9016,14 @@ } }, "node_modules/typedoc": { - "version": "0.28.13", - "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.28.13.tgz", - "integrity": "sha512-dNWY8msnYB2a+7Audha+aTF1Pu3euiE7ySp53w8kEsXoYw7dMouV5A1UsTUY345aB152RHnmRMDiovuBi7BD+w==", + "version": "0.28.15", + "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.28.15.tgz", + "integrity": "sha512-mw2/2vTL7MlT+BVo43lOsufkkd2CJO4zeOSuWQQsiXoV2VuEn7f6IZp2jsUDPmBMABpgR0R5jlcJ2OGEFYmkyg==", "dev": true, "license": "Apache-2.0", + "peer": true, "dependencies": { - "@gerrit0/mini-shiki": "^3.12.0", + "@gerrit0/mini-shiki": "^3.17.0", "lunr": "^2.3.9", "markdown-it": "^14.1.0", "minimatch": "^9.0.5", @@ -9064,6 +9079,7 @@ "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -9300,11 +9316,11 @@ }, "react": { "name": "@theoplayer/react-ui", - "version": "1.14.0", + "version": "1.16.0", "license": "MIT", "dependencies": { "@lit/react": "^1.0.7", - "@theoplayer/web-ui": "^1.14.0" + "@theoplayer/web-ui": "^1.16.0" }, "devDependencies": { "@rollup/plugin-json": "^6.1.0", @@ -9321,7 +9337,7 @@ "rollup-plugin-swc3": "^0.12.1", "theoplayer": "^10.6.1", "tslib": "^2.8.1", - "typedoc": "^0.28.13", + "typedoc": "^0.28.14", "typedoc-plugin-external-resolver": "^1.0.3", "typedoc-plugin-mdn-links": "^5.0.9", "typescript": "^5.9.2" @@ -9633,13 +9649,15 @@ "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.4.tgz", "integrity": "sha512-Up7rBoV77rv29d3uKHUIVubz1BTcgyUK72IvCQAbfbMv584xHcGKCKbWh7i8hPrRJ7qU4Y8IO3IY9m+iTB7P3A==", "dev": true, + "peer": true, "requires": {} }, "@csstools/css-tokenizer": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.3.tgz", "integrity": "sha512-UJnjoFsmxfKUdNYdWgOB0mWUypuLvAfQPH1+pyvRJs6euowbFkFC6P13w1l8mJyi3vxYMxc9kld5jZEGRQs6bw==", - "dev": true + "dev": true, + "peer": true }, "@csstools/media-query-list-parser": { "version": "4.0.2", @@ -9670,6 +9688,7 @@ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", "dev": true, + "peer": true, "requires": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -9813,6 +9832,7 @@ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", "dev": true, + "peer": true, "requires": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -10051,15 +10071,15 @@ "dev": true }, "@gerrit0/mini-shiki": { - "version": "3.13.0", - "resolved": "https://registry.npmjs.org/@gerrit0/mini-shiki/-/mini-shiki-3.13.0.tgz", - "integrity": "sha512-mCrNvZNYNrwKer5PWLF6cOc0OEe2eKzgy976x+IT2tynwJYl+7UpHTSeXQJGijgTcoOf+f359L946unWlYRnsg==", + "version": "3.20.0", + "resolved": "https://registry.npmjs.org/@gerrit0/mini-shiki/-/mini-shiki-3.20.0.tgz", + "integrity": "sha512-Wa57i+bMpK6PGJZ1f2myxo3iO+K/kZikcyvH8NIqNNZhQUbDav7V9LQmWOXhf946mz5c1NZ19WMsGYiDKTryzQ==", "dev": true, "requires": { - "@shikijs/engine-oniguruma": "^3.13.0", - "@shikijs/langs": "^3.13.0", - "@shikijs/themes": "^3.13.0", - "@shikijs/types": "^3.13.0", + "@shikijs/engine-oniguruma": "^3.20.0", + "@shikijs/langs": "^3.20.0", + "@shikijs/themes": "^3.20.0", + "@shikijs/types": "^3.20.0", "@shikijs/vscode-textmate": "^10.0.2" } }, @@ -10544,37 +10564,37 @@ "dev": true }, "@shikijs/engine-oniguruma": { - "version": "3.13.0", - "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-3.13.0.tgz", - "integrity": "sha512-O42rBGr4UDSlhT2ZFMxqM7QzIU+IcpoTMzb3W7AlziI1ZF7R8eS2M0yt5Ry35nnnTX/LTLXFPUjRFCIW+Operg==", + "version": "3.20.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-3.20.0.tgz", + "integrity": "sha512-Yx3gy7xLzM0ZOjqoxciHjA7dAt5tyzJE3L4uQoM83agahy+PlW244XJSrmJRSBvGYELDhYXPacD4R/cauV5bzQ==", "dev": true, "requires": { - "@shikijs/types": "3.13.0", + "@shikijs/types": "3.20.0", "@shikijs/vscode-textmate": "^10.0.2" } }, "@shikijs/langs": { - "version": "3.13.0", - "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-3.13.0.tgz", - "integrity": "sha512-672c3WAETDYHwrRP0yLy3W1QYB89Hbpj+pO4KhxK6FzIrDI2FoEXNiNCut6BQmEApYLfuYfpgOZaqbY+E9b8wQ==", + "version": "3.20.0", + "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-3.20.0.tgz", + "integrity": "sha512-le+bssCxcSHrygCWuOrYJHvjus6zhQ2K7q/0mgjiffRbkhM4o1EWu2m+29l0yEsHDbWaWPNnDUTRVVBvBBeKaA==", "dev": true, "requires": { - "@shikijs/types": "3.13.0" + "@shikijs/types": "3.20.0" } }, "@shikijs/themes": { - "version": "3.13.0", - "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-3.13.0.tgz", - "integrity": "sha512-Vxw1Nm1/Od8jyA7QuAenaV78BG2nSr3/gCGdBkLpfLscddCkzkL36Q5b67SrLLfvAJTOUzW39x4FHVCFriPVgg==", + "version": "3.20.0", + "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-3.20.0.tgz", + "integrity": "sha512-U1NSU7Sl26Q7ErRvJUouArxfM2euWqq1xaSrbqMu2iqa+tSp0D1Yah8216sDYbdDHw4C8b75UpE65eWorm2erQ==", "dev": true, "requires": { - "@shikijs/types": "3.13.0" + "@shikijs/types": "3.20.0" } }, "@shikijs/types": { - "version": "3.13.0", - "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-3.13.0.tgz", - "integrity": "sha512-oM9P+NCFri/mmQ8LoFGVfVyemm5Hi27330zuOBp0annwJdKH1kOLndw3zCtAVDehPLg9fKqoEx3Ht/wNZxolfw==", + "version": "3.20.0", + "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-3.20.0.tgz", + "integrity": "sha512-lhYAATn10nkZcBQ0BlzSbJA3wcmL5MXUUF8d2Zzon6saZDlToKaiRX60n2+ZaHJCmXEcZRWNzn+k9vplr8Jhsw==", "dev": true, "requires": { "@shikijs/vscode-textmate": "^10.0.2", @@ -10623,6 +10643,7 @@ "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.13.5.tgz", "integrity": "sha512-WezcBo8a0Dg2rnR82zhwoR6aRNxeTGfK5QCD6TQ+kg3xx/zNT02s/0o+81h/3zhvFSB24NtqEr8FTw88O5W/JQ==", "dev": true, + "peer": true, "requires": { "@swc/core-darwin-arm64": "1.13.5", "@swc/core-darwin-x64": "1.13.5", @@ -10751,7 +10772,7 @@ "@swc/cli": "^0.7.3", "@swc/core": "^1.11.22", "@swc/helpers": "^0.5.15", - "@theoplayer/web-ui": "^1.14.0", + "@theoplayer/web-ui": "^1.16.0", "@types/react": "^18.2.79", "react": "^18.2.0", "react-dom": "^18.2.0", @@ -10760,7 +10781,7 @@ "rollup-plugin-swc3": "^0.12.1", "theoplayer": "^10.6.1", "tslib": "^2.8.1", - "typedoc": "^0.28.13", + "typedoc": "^0.28.14", "typedoc-plugin-external-resolver": "^1.0.3", "typedoc-plugin-mdn-links": "^5.0.9", "typescript": "^5.9.2" @@ -10801,7 +10822,7 @@ "serve": "^14.2.4", "theoplayer": "^10.6.1", "tslib": "^2.8.1", - "typedoc": "^0.28.13", + "typedoc": "^0.28.14", "typedoc-plugin-mdn-links": "^5.0.9", "typescript": "^5.9.2" }, @@ -11105,13 +11126,15 @@ "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.4.tgz", "integrity": "sha512-Up7rBoV77rv29d3uKHUIVubz1BTcgyUK72IvCQAbfbMv584xHcGKCKbWh7i8hPrRJ7qU4Y8IO3IY9m+iTB7P3A==", "dev": true, + "peer": true, "requires": {} }, "@csstools/css-tokenizer": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.3.tgz", "integrity": "sha512-UJnjoFsmxfKUdNYdWgOB0mWUypuLvAfQPH1+pyvRJs6euowbFkFC6P13w1l8mJyi3vxYMxc9kld5jZEGRQs6bw==", - "dev": true + "dev": true, + "peer": true }, "@csstools/media-query-list-parser": { "version": "4.0.2", @@ -11142,6 +11165,7 @@ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", "dev": true, + "peer": true, "requires": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -11285,6 +11309,7 @@ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", "dev": true, + "peer": true, "requires": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -11523,15 +11548,15 @@ "dev": true }, "@gerrit0/mini-shiki": { - "version": "3.13.0", - "resolved": "https://registry.npmjs.org/@gerrit0/mini-shiki/-/mini-shiki-3.13.0.tgz", - "integrity": "sha512-mCrNvZNYNrwKer5PWLF6cOc0OEe2eKzgy976x+IT2tynwJYl+7UpHTSeXQJGijgTcoOf+f359L946unWlYRnsg==", + "version": "3.20.0", + "resolved": "https://registry.npmjs.org/@gerrit0/mini-shiki/-/mini-shiki-3.20.0.tgz", + "integrity": "sha512-Wa57i+bMpK6PGJZ1f2myxo3iO+K/kZikcyvH8NIqNNZhQUbDav7V9LQmWOXhf946mz5c1NZ19WMsGYiDKTryzQ==", "dev": true, "requires": { - "@shikijs/engine-oniguruma": "^3.13.0", - "@shikijs/langs": "^3.13.0", - "@shikijs/themes": "^3.13.0", - "@shikijs/types": "^3.13.0", + "@shikijs/engine-oniguruma": "^3.20.0", + "@shikijs/langs": "^3.20.0", + "@shikijs/themes": "^3.20.0", + "@shikijs/types": "^3.20.0", "@shikijs/vscode-textmate": "^10.0.2" } }, @@ -12016,37 +12041,37 @@ "dev": true }, "@shikijs/engine-oniguruma": { - "version": "3.13.0", - "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-3.13.0.tgz", - "integrity": "sha512-O42rBGr4UDSlhT2ZFMxqM7QzIU+IcpoTMzb3W7AlziI1ZF7R8eS2M0yt5Ry35nnnTX/LTLXFPUjRFCIW+Operg==", + "version": "3.20.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-3.20.0.tgz", + "integrity": "sha512-Yx3gy7xLzM0ZOjqoxciHjA7dAt5tyzJE3L4uQoM83agahy+PlW244XJSrmJRSBvGYELDhYXPacD4R/cauV5bzQ==", "dev": true, "requires": { - "@shikijs/types": "3.13.0", + "@shikijs/types": "3.20.0", "@shikijs/vscode-textmate": "^10.0.2" } }, "@shikijs/langs": { - "version": "3.13.0", - "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-3.13.0.tgz", - "integrity": "sha512-672c3WAETDYHwrRP0yLy3W1QYB89Hbpj+pO4KhxK6FzIrDI2FoEXNiNCut6BQmEApYLfuYfpgOZaqbY+E9b8wQ==", + "version": "3.20.0", + "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-3.20.0.tgz", + "integrity": "sha512-le+bssCxcSHrygCWuOrYJHvjus6zhQ2K7q/0mgjiffRbkhM4o1EWu2m+29l0yEsHDbWaWPNnDUTRVVBvBBeKaA==", "dev": true, "requires": { - "@shikijs/types": "3.13.0" + "@shikijs/types": "3.20.0" } }, "@shikijs/themes": { - "version": "3.13.0", - "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-3.13.0.tgz", - "integrity": "sha512-Vxw1Nm1/Od8jyA7QuAenaV78BG2nSr3/gCGdBkLpfLscddCkzkL36Q5b67SrLLfvAJTOUzW39x4FHVCFriPVgg==", + "version": "3.20.0", + "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-3.20.0.tgz", + "integrity": "sha512-U1NSU7Sl26Q7ErRvJUouArxfM2euWqq1xaSrbqMu2iqa+tSp0D1Yah8216sDYbdDHw4C8b75UpE65eWorm2erQ==", "dev": true, "requires": { - "@shikijs/types": "3.13.0" + "@shikijs/types": "3.20.0" } }, "@shikijs/types": { - "version": "3.13.0", - "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-3.13.0.tgz", - "integrity": "sha512-oM9P+NCFri/mmQ8LoFGVfVyemm5Hi27330zuOBp0annwJdKH1kOLndw3zCtAVDehPLg9fKqoEx3Ht/wNZxolfw==", + "version": "3.20.0", + "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-3.20.0.tgz", + "integrity": "sha512-lhYAATn10nkZcBQ0BlzSbJA3wcmL5MXUUF8d2Zzon6saZDlToKaiRX60n2+ZaHJCmXEcZRWNzn+k9vplr8Jhsw==", "dev": true, "requires": { "@shikijs/vscode-textmate": "^10.0.2", @@ -12095,6 +12120,7 @@ "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.13.5.tgz", "integrity": "sha512-WezcBo8a0Dg2rnR82zhwoR6aRNxeTGfK5QCD6TQ+kg3xx/zNT02s/0o+81h/3zhvFSB24NtqEr8FTw88O5W/JQ==", "dev": true, + "peer": true, "requires": { "@swc/core-darwin-arm64": "1.13.5", "@swc/core-darwin-x64": "1.13.5", @@ -12223,7 +12249,7 @@ "@swc/cli": "^0.7.3", "@swc/core": "^1.11.22", "@swc/helpers": "^0.5.15", - "@theoplayer/web-ui": "^1.14.0", + "@theoplayer/web-ui": "^1.16.0", "@types/react": "^18.2.79", "react": "^18.2.0", "react-dom": "^18.2.0", @@ -12232,7 +12258,7 @@ "rollup-plugin-swc3": "^0.12.1", "theoplayer": "^10.6.1", "tslib": "^2.8.1", - "typedoc": "^0.28.13", + "typedoc": "^0.28.14", "typedoc-plugin-external-resolver": "^1.0.3", "typedoc-plugin-mdn-links": "^5.0.9", "typescript": "^5.9.2" @@ -12304,7 +12330,8 @@ "version": "20.7.0", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.7.0.tgz", "integrity": "sha512-zI22/pJW2wUZOVyguFaUL1HABdmSVxpXrzIqkjsHmyUjNhPoWM1CKfvVuXfetHhIok4RY573cqS0mZ1SJEnoTg==", - "dev": true + "dev": true, + "peer": true }, "@types/prop-types": { "version": "15.7.11", @@ -12315,6 +12342,7 @@ "version": "18.3.18", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.18.tgz", "integrity": "sha512-t4yC+vtgnkYjNSKlFx1jkAhH8LgTo2N/7Qvi83kdEaUtMDiwpbLAktKDaAMlRcJ5eSxZkH74eEGt1ky31d7kfQ==", + "peer": true, "requires": { "@types/prop-types": "*", "csstype": "^3.0.2" @@ -12761,6 +12789,7 @@ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.4.tgz", "integrity": "sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==", "dev": true, + "peer": true, "requires": { "caniuse-lite": "^1.0.30001688", "electron-to-chromium": "^1.5.73", @@ -13148,6 +13177,7 @@ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", "dev": true, + "peer": true, "requires": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -14614,6 +14644,7 @@ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.2.tgz", "integrity": "sha512-MjOadfU3Ys9KYoX0AdkBlFEF1Vx37uCCeN4ZHnmwm9FfpbsGWMZeBLMmmpY+6Ocqod7mkdZ0DT31OlbsFrLlkA==", "dev": true, + "peer": true, "requires": { "nanoid": "^3.3.8", "picocolors": "^1.1.1", @@ -15087,6 +15118,7 @@ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", "dev": true, + "peer": true, "requires": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -15469,6 +15501,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", "dev": true, + "peer": true, "requires": { "loose-envify": "^1.1.0" } @@ -15575,6 +15608,7 @@ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.2.tgz", "integrity": "sha512-I25/2QgoROE1vYV+NQ1En9T9UFB9Cmfm2CJ83zZOlaDpvz29wGQSZXWKw7MiNXau7wYgB/T9fVIdIuEQ+KbiiA==", "dev": true, + "peer": true, "requires": { "@rollup/rollup-android-arm-eabi": "4.52.2", "@rollup/rollup-android-arm64": "4.52.2", @@ -16196,7 +16230,8 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "dev": true + "dev": true, + "peer": true } } }, @@ -16258,12 +16293,13 @@ "dev": true }, "typedoc": { - "version": "0.28.13", - "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.28.13.tgz", - "integrity": "sha512-dNWY8msnYB2a+7Audha+aTF1Pu3euiE7ySp53w8kEsXoYw7dMouV5A1UsTUY345aB152RHnmRMDiovuBi7BD+w==", + "version": "0.28.15", + "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.28.15.tgz", + "integrity": "sha512-mw2/2vTL7MlT+BVo43lOsufkkd2CJO4zeOSuWQQsiXoV2VuEn7f6IZp2jsUDPmBMABpgR0R5jlcJ2OGEFYmkyg==", "dev": true, + "peer": true, "requires": { - "@gerrit0/mini-shiki": "^3.12.0", + "@gerrit0/mini-shiki": "^3.17.0", "lunr": "^2.3.9", "markdown-it": "^14.1.0", "minimatch": "^9.0.5", @@ -16296,7 +16332,8 @@ "version": "5.9.2", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz", "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==", - "dev": true + "dev": true, + "peer": true }, "uc.micro": { "version": "2.1.0", @@ -16514,7 +16551,8 @@ "version": "20.7.0", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.7.0.tgz", "integrity": "sha512-zI22/pJW2wUZOVyguFaUL1HABdmSVxpXrzIqkjsHmyUjNhPoWM1CKfvVuXfetHhIok4RY573cqS0mZ1SJEnoTg==", - "dev": true + "dev": true, + "peer": true }, "@types/prop-types": { "version": "15.7.11", @@ -16525,6 +16563,7 @@ "version": "18.3.18", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.18.tgz", "integrity": "sha512-t4yC+vtgnkYjNSKlFx1jkAhH8LgTo2N/7Qvi83kdEaUtMDiwpbLAktKDaAMlRcJ5eSxZkH74eEGt1ky31d7kfQ==", + "peer": true, "requires": { "@types/prop-types": "*", "csstype": "^3.0.2" @@ -16971,6 +17010,7 @@ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.4.tgz", "integrity": "sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==", "dev": true, + "peer": true, "requires": { "caniuse-lite": "^1.0.30001688", "electron-to-chromium": "^1.5.73", @@ -17358,6 +17398,7 @@ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", "dev": true, + "peer": true, "requires": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -18824,6 +18865,7 @@ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.2.tgz", "integrity": "sha512-MjOadfU3Ys9KYoX0AdkBlFEF1Vx37uCCeN4ZHnmwm9FfpbsGWMZeBLMmmpY+6Ocqod7mkdZ0DT31OlbsFrLlkA==", "dev": true, + "peer": true, "requires": { "nanoid": "^3.3.8", "picocolors": "^1.1.1", @@ -19297,6 +19339,7 @@ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", "dev": true, + "peer": true, "requires": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -19679,6 +19722,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", "dev": true, + "peer": true, "requires": { "loose-envify": "^1.1.0" } @@ -19785,6 +19829,7 @@ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.2.tgz", "integrity": "sha512-I25/2QgoROE1vYV+NQ1En9T9UFB9Cmfm2CJ83zZOlaDpvz29wGQSZXWKw7MiNXau7wYgB/T9fVIdIuEQ+KbiiA==", "dev": true, + "peer": true, "requires": { "@rollup/rollup-android-arm-eabi": "4.52.2", "@rollup/rollup-android-arm64": "4.52.2", @@ -20406,7 +20451,8 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "dev": true + "dev": true, + "peer": true } } }, @@ -20468,12 +20514,13 @@ "dev": true }, "typedoc": { - "version": "0.28.13", - "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.28.13.tgz", - "integrity": "sha512-dNWY8msnYB2a+7Audha+aTF1Pu3euiE7ySp53w8kEsXoYw7dMouV5A1UsTUY345aB152RHnmRMDiovuBi7BD+w==", + "version": "0.28.15", + "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.28.15.tgz", + "integrity": "sha512-mw2/2vTL7MlT+BVo43lOsufkkd2CJO4zeOSuWQQsiXoV2VuEn7f6IZp2jsUDPmBMABpgR0R5jlcJ2OGEFYmkyg==", "dev": true, + "peer": true, "requires": { - "@gerrit0/mini-shiki": "^3.12.0", + "@gerrit0/mini-shiki": "^3.17.0", "lunr": "^2.3.9", "markdown-it": "^14.1.0", "minimatch": "^9.0.5", @@ -20506,7 +20553,8 @@ "version": "5.9.2", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz", "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==", - "dev": true + "dev": true, + "peer": true }, "uc.micro": { "version": "2.1.0", diff --git a/package.json b/package.json index c6775589..23349dee 100644 --- a/package.json +++ b/package.json @@ -90,7 +90,7 @@ "serve": "^14.2.4", "theoplayer": "^10.6.1", "tslib": "^2.8.1", - "typedoc": "^0.28.13", + "typedoc": "^0.28.14", "typedoc-plugin-mdn-links": "^5.0.9", "typescript": "^5.9.2" }, diff --git a/react/package.json b/react/package.json index 7cd9ccf5..e11c94e6 100644 --- a/react/package.json +++ b/react/package.json @@ -74,7 +74,7 @@ "rollup-plugin-swc3": "^0.12.1", "theoplayer": "^10.6.1", "tslib": "^2.8.1", - "typedoc": "^0.28.13", + "typedoc": "^0.28.14", "typedoc-plugin-external-resolver": "^1.0.3", "typedoc-plugin-mdn-links": "^5.0.9", "typescript": "^5.9.2" From b12930792632497142409497a282d6d21112e3fa Mon Sep 17 00:00:00 2001 From: Mattias Buelens Date: Tue, 23 Dec 2025 13:55:43 +0100 Subject: [PATCH 123/123] Don't set initial attributes in constructor --- src/components/TimeRange.ts | 14 +++++++++++--- src/components/VolumeRange.ts | 13 +++++++++++-- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/src/components/TimeRange.ts b/src/components/TimeRange.ts index adcfc9bf..b8f69d11 100644 --- a/src/components/TimeRange.ts +++ b/src/components/TimeRange.ts @@ -58,13 +58,21 @@ export class TimeRange extends Range { constructor() { super(); - this.min = 0; - this.max = 1000; - this.ariaLive = 'off'; } override connectedCallback(): void { super.connectedCallback(); + + if (!this.hasAttribute('min')) { + this.min = 0; + } + if (!this.hasAttribute('max')) { + this.max = 1000; + } + if (!this.hasAttribute(Attribute.ARIA_LIVE)) { + this.ariaLive = 'off'; + } + this._toggleAutoAdvance(); } diff --git a/src/components/VolumeRange.ts b/src/components/VolumeRange.ts index 38f5e21c..322d15a6 100644 --- a/src/components/VolumeRange.ts +++ b/src/components/VolumeRange.ts @@ -18,8 +18,17 @@ export class VolumeRange extends Range { constructor() { super(); - this.min = 0; - this.max = 1; + } + + override connectedCallback(): void { + super.connectedCallback(); + + if (!this.hasAttribute('min')) { + this.min = 0; + } + if (!this.hasAttribute('max')) { + this.max = 1; + } } get player(): ChromelessPlayer | undefined {