@smartimpact-it/modern-video-embed 2.0.4 → 2.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/README.md +322 -71
  2. package/dist/components/BaseVideoEmbed.d.ts +86 -0
  3. package/dist/components/BaseVideoEmbed.d.ts.map +1 -0
  4. package/dist/components/BaseVideoEmbed.js +256 -0
  5. package/dist/components/BaseVideoEmbed.js.map +1 -0
  6. package/dist/components/VideoEmbed.d.ts +68 -0
  7. package/dist/components/VideoEmbed.d.ts.map +1 -0
  8. package/dist/components/VideoEmbed.js +770 -0
  9. package/dist/components/VideoEmbed.js.map +1 -0
  10. package/dist/components/VimeoEmbed.d.ts +26 -36
  11. package/dist/components/VimeoEmbed.d.ts.map +1 -1
  12. package/dist/components/VimeoEmbed.js +205 -328
  13. package/dist/components/VimeoEmbed.js.map +1 -1
  14. package/dist/components/VimeoEmbed.min.js +1 -1
  15. package/dist/components/YouTubeEmbed.d.ts +108 -42
  16. package/dist/components/YouTubeEmbed.d.ts.map +1 -1
  17. package/dist/components/YouTubeEmbed.js +341 -373
  18. package/dist/components/YouTubeEmbed.js.map +1 -1
  19. package/dist/components/YouTubeEmbed.min.js +1 -1
  20. package/dist/css/components.css +235 -44
  21. package/dist/css/components.css.map +1 -1
  22. package/dist/css/components.min.css +1 -1
  23. package/dist/css/main.css +235 -44
  24. package/dist/css/main.css.map +1 -1
  25. package/dist/css/main.min.css +1 -1
  26. package/dist/index.d.ts +1 -0
  27. package/dist/index.d.ts.map +1 -1
  28. package/dist/index.js +1 -0
  29. package/dist/index.js.map +1 -1
  30. package/dist/index.min.js +1 -1
  31. package/dist/types/index.d.ts +1 -0
  32. package/dist/types/index.d.ts.map +1 -1
  33. package/dist/video-only.d.ts +7 -0
  34. package/dist/video-only.d.ts.map +1 -0
  35. package/dist/video-only.js +8 -0
  36. package/dist/video-only.js.map +1 -0
  37. package/dist/vimeo-only.d.ts +2 -2
  38. package/dist/vimeo-only.d.ts.map +1 -1
  39. package/dist/vimeo-only.js +2 -2
  40. package/dist/vimeo-only.js.map +1 -1
  41. package/dist/vimeo-only.min.js +1 -1
  42. package/dist/youtube-only.d.ts +2 -2
  43. package/dist/youtube-only.d.ts.map +1 -1
  44. package/dist/youtube-only.js +2 -2
  45. package/dist/youtube-only.js.map +1 -1
  46. package/dist/youtube-only.min.js +1 -1
  47. package/package.json +6 -5
  48. package/src/components/BaseVideoEmbed.ts +303 -0
  49. package/src/components/VideoEmbed.ts +852 -0
  50. package/src/components/VideoEmbed.ts.backup +1051 -0
  51. package/src/components/VimeoEmbed.ts +233 -397
  52. package/src/components/YouTubeEmbed.ts +359 -430
  53. package/src/index.ts +1 -0
  54. package/src/styles/_embed-base.scss +255 -0
  55. package/src/styles/_shared-functions.scss +56 -0
  56. package/src/styles/components.scss +4 -3
  57. package/src/styles/main.scss +7 -5
  58. package/src/styles/video-embed.scss +37 -0
  59. package/src/styles/vimeo-embed.scss +8 -248
  60. package/src/styles/youtube-embed.scss +8 -254
  61. package/src/types/index.ts +1 -0
  62. package/src/types/video-embed.d.ts +90 -0
  63. package/src/video-only.ts +9 -0
  64. package/src/vimeo-only.ts +2 -2
  65. package/src/youtube-only.ts +2 -2
package/dist/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
1
  import "./components/YouTubeEmbed.js";
2
2
  import "./components/VimeoEmbed.js";
3
+ import "./components/VideoEmbed.js";
3
4
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,8BAA8B,CAAC;AACtC,OAAO,4BAA4B,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,8BAA8B,CAAC;AACtC,OAAO,4BAA4B,CAAC;AACpC,OAAO,4BAA4B,CAAC"}
package/dist/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  // Main entry point for Video Embed Components
2
2
  import "./components/YouTubeEmbed.js";
3
3
  import "./components/VimeoEmbed.js";
4
+ import "./components/VideoEmbed.js";
4
5
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,8CAA8C;AAC9C,OAAO,8BAA8B,CAAC;AACtC,OAAO,4BAA4B,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,8CAA8C;AAC9C,OAAO,8BAA8B,CAAC;AACtC,OAAO,4BAA4B,CAAC;AACpC,OAAO,4BAA4B,CAAC"}
package/dist/index.min.js CHANGED
@@ -1 +1 @@
1
- import"./components/YouTubeEmbed.js";import"./components/VimeoEmbed.js";
1
+ import"./components/YouTubeEmbed.js";import"./components/VimeoEmbed.js";import"./components/VideoEmbed.js";
@@ -4,4 +4,5 @@
4
4
  export * from "./common";
5
5
  export type { YouTubeEmbedElement } from "./youtube-embed";
6
6
  export type { VimeoEmbedElement } from "./vimeo-embed";
7
+ export type { VideoEmbedElement } from "./video-embed";
7
8
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,cAAc,UAAU,CAAC;AACzB,YAAY,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAC3D,YAAY,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,cAAc,UAAU,CAAC;AACzB,YAAY,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAC3D,YAAY,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AACvD,YAAY,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Video-only bundle entry point
3
+ * Use this for smaller bundle size when only MP4 video embeds are needed
4
+ */
5
+ export { VideoEmbed } from "./components/VideoEmbed.js";
6
+ import "./components/VideoEmbed.js";
7
+ //# sourceMappingURL=video-only.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"video-only.d.ts","sourceRoot":"","sources":["../src/video-only.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AAGxD,OAAO,4BAA4B,CAAC"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Video-only bundle entry point
3
+ * Use this for smaller bundle size when only MP4 video embeds are needed
4
+ */
5
+ export { VideoEmbed } from "./components/VideoEmbed.js";
6
+ // Auto-register the custom element
7
+ import "./components/VideoEmbed.js";
8
+ //# sourceMappingURL=video-only.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"video-only.js","sourceRoot":"","sources":["../src/video-only.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AAExD,mCAAmC;AACnC,OAAO,4BAA4B,CAAC"}
@@ -2,6 +2,6 @@
2
2
  * Vimeo-only bundle entry point
3
3
  * Use this for smaller bundle size when only Vimeo embeds are needed
4
4
  */
5
- export { VimeoEmbed } from "./components/VimeoEmbed";
6
- import "./components/VimeoEmbed";
5
+ export { VimeoEmbed } from "./components/VimeoEmbed.js";
6
+ import "./components/VimeoEmbed.js";
7
7
  //# sourceMappingURL=vimeo-only.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"vimeo-only.d.ts","sourceRoot":"","sources":["../src/vimeo-only.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAGrD,OAAO,yBAAyB,CAAC"}
1
+ {"version":3,"file":"vimeo-only.d.ts","sourceRoot":"","sources":["../src/vimeo-only.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AAGxD,OAAO,4BAA4B,CAAC"}
@@ -2,7 +2,7 @@
2
2
  * Vimeo-only bundle entry point
3
3
  * Use this for smaller bundle size when only Vimeo embeds are needed
4
4
  */
5
- export { VimeoEmbed } from "./components/VimeoEmbed";
5
+ export { VimeoEmbed } from "./components/VimeoEmbed.js";
6
6
  // Auto-register the custom element
7
- import "./components/VimeoEmbed";
7
+ import "./components/VimeoEmbed.js";
8
8
  //# sourceMappingURL=vimeo-only.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"vimeo-only.js","sourceRoot":"","sources":["../src/vimeo-only.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAErD,mCAAmC;AACnC,OAAO,yBAAyB,CAAC"}
1
+ {"version":3,"file":"vimeo-only.js","sourceRoot":"","sources":["../src/vimeo-only.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AAExD,mCAAmC;AACnC,OAAO,4BAA4B,CAAC"}
@@ -1 +1 @@
1
- export{VimeoEmbed}from"./components/VimeoEmbed";import"./components/VimeoEmbed";
1
+ export{VimeoEmbed}from"./components/VimeoEmbed.js";import"./components/VimeoEmbed.js";
@@ -2,6 +2,6 @@
2
2
  * YouTube-only bundle entry point
3
3
  * Use this for smaller bundle size when only YouTube embeds are needed
4
4
  */
5
- export { YouTubeEmbed } from "./components/YouTubeEmbed";
6
- import "./components/YouTubeEmbed";
5
+ export { YouTubeEmbed } from "./components/YouTubeEmbed.js";
6
+ import "./components/YouTubeEmbed.js";
7
7
  //# sourceMappingURL=youtube-only.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"youtube-only.d.ts","sourceRoot":"","sources":["../src/youtube-only.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAGzD,OAAO,2BAA2B,CAAC"}
1
+ {"version":3,"file":"youtube-only.d.ts","sourceRoot":"","sources":["../src/youtube-only.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AAG5D,OAAO,8BAA8B,CAAC"}
@@ -2,7 +2,7 @@
2
2
  * YouTube-only bundle entry point
3
3
  * Use this for smaller bundle size when only YouTube embeds are needed
4
4
  */
5
- export { YouTubeEmbed } from "./components/YouTubeEmbed";
5
+ export { YouTubeEmbed } from "./components/YouTubeEmbed.js";
6
6
  // Auto-register the custom element
7
- import "./components/YouTubeEmbed";
7
+ import "./components/YouTubeEmbed.js";
8
8
  //# sourceMappingURL=youtube-only.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"youtube-only.js","sourceRoot":"","sources":["../src/youtube-only.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAEzD,mCAAmC;AACnC,OAAO,2BAA2B,CAAC"}
1
+ {"version":3,"file":"youtube-only.js","sourceRoot":"","sources":["../src/youtube-only.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AAE5D,mCAAmC;AACnC,OAAO,8BAA8B,CAAC"}
@@ -1 +1 @@
1
- export{YouTubeEmbed}from"./components/YouTubeEmbed";import"./components/YouTubeEmbed";
1
+ export{YouTubeEmbed}from"./components/YouTubeEmbed.js";import"./components/YouTubeEmbed.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@smartimpact-it/modern-video-embed",
3
- "version": "2.0.4",
3
+ "version": "2.0.6",
4
4
  "description": "Modern YouTube and Vimeo embed web components with lazy loading, accessibility, quality control, and comprehensive API",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",
@@ -40,18 +40,19 @@
40
40
  "npm": ">=6.0.0"
41
41
  },
42
42
  "scripts": {
43
- "build": "npm run build:clean && npm run build:ts && npm run build:css && npm run build:validate",
43
+ "build": "npm run build:clean && npm run build:ts && npm run build:css && npm run build:validate && npm run build:cache-bust",
44
44
  "build:clean": "rimraf dist",
45
45
  "build:ts": "tsc",
46
46
  "build:css": "npm run build:css:main && npm run build:css:components",
47
- "build:css:main": "sass src/styles/main.scss:dist/css/main.css",
48
- "build:css:components": "sass src/styles/components.scss:dist/css/components.css",
47
+ "build:css:main": "sass --silence-deprecation=import --silence-deprecation=global-builtin --silence-deprecation=color-functions src/styles/main.scss:dist/css/main.css",
48
+ "build:css:components": "sass --silence-deprecation=import --silence-deprecation=global-builtin --silence-deprecation=color-functions src/styles/components.scss:dist/css/components.css",
49
49
  "build:validate": "node scripts/verify-build.js",
50
+ "build:cache-bust": "node scripts/update-cache-busters.js",
50
51
  "build:prod": "npm run build && npm run build:minify",
51
52
  "build:minify": "npm run minify:js && npm run minify:css",
52
53
  "minify:js": "terser dist/components/YouTubeEmbed.js -o dist/components/YouTubeEmbed.min.js -c -m && terser dist/components/VimeoEmbed.js -o dist/components/VimeoEmbed.min.js -c -m && terser dist/index.js -o dist/index.min.js -c -m && terser dist/youtube-only.js -o dist/youtube-only.min.js -c -m && terser dist/vimeo-only.js -o dist/vimeo-only.min.js -c -m",
53
54
  "minify:css": "csso dist/css/main.css -o dist/css/main.min.css && csso dist/css/components.css -o dist/css/components.min.css",
54
- "watch": "concurrently --names \"TS,CSS\" --prefix-colors \"blue,green\" \"tsc --watch\" \"sass --watch src/styles:dist/css\"",
55
+ "watch": "concurrently --names \"TS,CSS\" --prefix-colors \"blue,green\" \"tsc --watch\" \"sass --watch --silence-deprecation=import --silence-deprecation=global-builtin --silence-deprecation=color-functions src/styles:dist/css\"",
55
56
  "start": "npm run watch",
56
57
  "examples": "npm run build && echo \"Examples built! Open examples/index.html in your browser to view demos.\"",
57
58
  "dev": "npm run build && npm run watch",
@@ -0,0 +1,303 @@
1
+ /**
2
+ * Base class for all video embed components (VideoEmbed, YouTubeEmbed, VimeoEmbed).
3
+ * Contains shared functionality to reduce code duplication and ensure consistency.
4
+ */
5
+ export abstract class BaseVideoEmbed extends HTMLElement {
6
+ // Static properties shared across all components
7
+ protected static instanceCount = 0;
8
+ protected static DEBUG = false;
9
+
10
+ // Common instance properties
11
+ protected playerReady = false;
12
+ protected initialized = false;
13
+ protected updatingAttribute = false;
14
+ protected playPauseButton: HTMLDivElement | null = null;
15
+ protected setCustomControlState: ((playing: boolean) => void) | null = null;
16
+ protected ariaLiveRegion: HTMLElement | null = null;
17
+ protected intersectionObserver: IntersectionObserver | null = null;
18
+ protected hasLoadedVideo = false;
19
+ protected posterClickHandler: EventListener | null = null;
20
+ protected keyboardHandler: ((e: KeyboardEvent) => void) | null = null;
21
+
22
+ // Common protected properties that subclasses can access
23
+ protected _playing: boolean = false;
24
+ protected _autoplay: boolean = false;
25
+ protected _muted: boolean = false;
26
+ protected _controls: boolean = false;
27
+ protected _lazy: boolean = false;
28
+ protected _background: boolean = false;
29
+ protected _poster: string = "";
30
+
31
+ /**
32
+ * Abstract methods that subclasses must implement
33
+ */
34
+ protected abstract initializePlayer(...args: any[]): Promise<void> | void;
35
+ protected abstract reinitializePlayer(): void;
36
+ protected abstract destroyPlayer(): void;
37
+ protected abstract getComponentName(): string;
38
+
39
+ /**
40
+ * Logging utilities
41
+ */
42
+ protected log(...args: any[]): void {
43
+ if ((this.constructor as typeof BaseVideoEmbed).DEBUG) {
44
+ console.log(`${this.getComponentName()}:`, ...args);
45
+ }
46
+ }
47
+
48
+ protected warn(...args: any[]): void {
49
+ console.warn(...args);
50
+ }
51
+
52
+ protected error(...args: any[]): void {
53
+ console.error(...args);
54
+ }
55
+
56
+ /**
57
+ * Event dispatching
58
+ */
59
+ protected dispatchCustomEvent(eventName: string, detail?: any): void {
60
+ this.dispatchEvent(
61
+ new CustomEvent(eventName, {
62
+ detail,
63
+ bubbles: true,
64
+ composed: true,
65
+ }),
66
+ );
67
+ }
68
+
69
+ /**
70
+ * Attribute reflection helpers
71
+ */
72
+ protected reflectBooleanAttribute(name: string, value: boolean): void {
73
+ this.updatingAttribute = true;
74
+ if (value) {
75
+ this.setAttribute(name, "");
76
+ } else {
77
+ this.removeAttribute(name);
78
+ }
79
+ this.updatingAttribute = false;
80
+ }
81
+
82
+ protected reflectAttribute(name: string, value: string): void {
83
+ this.updatingAttribute = true;
84
+ if (value) {
85
+ this.setAttribute(name, value);
86
+ } else {
87
+ this.removeAttribute(name);
88
+ }
89
+ this.updatingAttribute = false;
90
+ }
91
+
92
+ /**
93
+ * Error message display (common across all components)
94
+ */
95
+ protected showErrorMessage(message: string, errorClass: string): void {
96
+ const errorDiv = document.createElement("div");
97
+ errorDiv.className = errorClass;
98
+ errorDiv.setAttribute("role", "alert");
99
+ errorDiv.innerHTML = `
100
+ <div class="error-content">
101
+ <svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
102
+ <circle cx="12" cy="12" r="10"></circle>
103
+ <line x1="12" y1="8" x2="12" y2="12"></line>
104
+ <line x1="12" y1="16" x2="12.01" y2="16"></line>
105
+ </svg>
106
+ <p class="error-message">${message}</p>
107
+ <button class="retry-button">Retry</button>
108
+ </div>
109
+ `;
110
+
111
+ const retryButton = errorDiv.querySelector(".retry-button");
112
+ if (retryButton) {
113
+ retryButton.addEventListener("click", () => {
114
+ errorDiv.remove();
115
+ this.handleRetry();
116
+ });
117
+ }
118
+
119
+ this.innerHTML = "";
120
+ this.appendChild(errorDiv);
121
+ }
122
+
123
+ /**
124
+ * Retry handler - subclasses can override
125
+ */
126
+ protected handleRetry(): void {
127
+ this.reinitializePlayer();
128
+ }
129
+
130
+ /**
131
+ * Background mode management (common across all components)
132
+ */
133
+ protected updateBackgroundMode(): void {
134
+ if (this._background) {
135
+ this.classList.add("is-background");
136
+ // Enforce background mode requirements
137
+ this._autoplay = true;
138
+ this._muted = true;
139
+ this._controls = false;
140
+ this._lazy = false;
141
+ // Reflect the forced property changes back to attributes
142
+ this.reflectBooleanAttribute("autoplay", true);
143
+ this.reflectBooleanAttribute("muted", true);
144
+ this.reflectBooleanAttribute("controls", false);
145
+ this.reflectBooleanAttribute("lazy", false);
146
+ } else {
147
+ this.classList.remove("is-background");
148
+ }
149
+ }
150
+
151
+ /**
152
+ * Custom controls creation (similar pattern across components)
153
+ */
154
+ protected addCustomControls(containerClass: string): void {
155
+ this.classList.add(containerClass);
156
+
157
+ const buttonOverlay = document.createElement("div");
158
+ buttonOverlay.classList.add("button-overlay");
159
+
160
+ this.playPauseButton = document.createElement("div");
161
+ this.playPauseButton.classList.add("button");
162
+ let isPlaying = false;
163
+
164
+ const setPlayingState = (playing: boolean) => {
165
+ isPlaying = playing;
166
+ if (playing) {
167
+ this.classList.add("is-playing");
168
+ } else {
169
+ this.classList.remove("is-playing");
170
+ }
171
+ };
172
+
173
+ const togglePlayPause = () => {
174
+ if (isPlaying) {
175
+ this.handlePause();
176
+ setPlayingState(false);
177
+ } else {
178
+ this.handlePlay();
179
+ setPlayingState(true);
180
+ }
181
+ };
182
+
183
+ this.setCustomControlState = setPlayingState;
184
+
185
+ buttonOverlay.addEventListener("click", togglePlayPause);
186
+ if (this.playPauseButton) {
187
+ buttonOverlay.appendChild(this.playPauseButton);
188
+ }
189
+ this.appendChild(buttonOverlay);
190
+ }
191
+
192
+ /**
193
+ * Play/Pause handlers - subclasses override with platform-specific logic
194
+ */
195
+ protected abstract handlePlay(): void;
196
+ protected abstract handlePause(): void;
197
+
198
+ /**
199
+ * Lazy loading setup (common pattern)
200
+ */
201
+ protected setupLazyLoading(callback: () => void): void {
202
+ this.intersectionObserver = new IntersectionObserver(
203
+ (entries) => {
204
+ entries.forEach((entry) => {
205
+ if (entry.isIntersecting && !this.hasLoadedVideo) {
206
+ this.log("Video entering viewport, loading...");
207
+ this.hasLoadedVideo = true;
208
+ callback();
209
+ if (this.intersectionObserver) {
210
+ this.intersectionObserver.disconnect();
211
+ }
212
+ }
213
+ });
214
+ },
215
+ {
216
+ rootMargin: "100px",
217
+ },
218
+ );
219
+
220
+ this.intersectionObserver.observe(this);
221
+ }
222
+
223
+ /**
224
+ * ARIA announcements for accessibility
225
+ */
226
+ protected announceToScreenReader(message: string): void {
227
+ if (!this.ariaLiveRegion) {
228
+ this.ariaLiveRegion = document.createElement("div");
229
+ this.ariaLiveRegion.setAttribute("role", "status");
230
+ this.ariaLiveRegion.setAttribute("aria-live", "polite");
231
+ this.ariaLiveRegion.setAttribute("aria-atomic", "true");
232
+ this.ariaLiveRegion.style.position = "absolute";
233
+ this.ariaLiveRegion.style.left = "-10000px";
234
+ this.ariaLiveRegion.style.width = "1px";
235
+ this.ariaLiveRegion.style.height = "1px";
236
+ this.ariaLiveRegion.style.overflow = "hidden";
237
+ this.appendChild(this.ariaLiveRegion);
238
+ }
239
+
240
+ this.ariaLiveRegion.textContent = message;
241
+ }
242
+
243
+ /**
244
+ * Lifecycle management
245
+ */
246
+ connectedCallback(): void {
247
+ (this.constructor as typeof BaseVideoEmbed).instanceCount++;
248
+ this.log(
249
+ "connectedCallback called, instance count:",
250
+ (this.constructor as typeof BaseVideoEmbed).instanceCount,
251
+ );
252
+ this.dispatchCustomEvent("connected");
253
+ }
254
+
255
+ disconnectedCallback(): void {
256
+ this.log("disconnectedCallback called. Cleaning up...");
257
+
258
+ // Cleanup player
259
+ try {
260
+ this.destroyPlayer();
261
+ } catch (error) {
262
+ this.warn("Error destroying player:", error);
263
+ }
264
+
265
+ // Cleanup observers
266
+ if (this.intersectionObserver) {
267
+ this.intersectionObserver.disconnect();
268
+ this.intersectionObserver = null;
269
+ }
270
+
271
+ // Cleanup event listeners
272
+ if (this.posterClickHandler) {
273
+ this.removeEventListener("click", this.posterClickHandler);
274
+ this.posterClickHandler = null;
275
+ }
276
+
277
+ if (this.keyboardHandler) {
278
+ this.removeEventListener("keydown", this.keyboardHandler);
279
+ this.keyboardHandler = null;
280
+ }
281
+
282
+ // Decrement instance count
283
+ (this.constructor as typeof BaseVideoEmbed).instanceCount--;
284
+ this.log(
285
+ "Cleanup complete. Remaining instances:",
286
+ (this.constructor as typeof BaseVideoEmbed).instanceCount,
287
+ );
288
+
289
+ this.dispatchCustomEvent("disconnected");
290
+ }
291
+
292
+ /**
293
+ * Static debug toggle
294
+ */
295
+ public static toggleDebug(forceState?: boolean): void {
296
+ if (forceState !== undefined) {
297
+ this.DEBUG = forceState;
298
+ } else {
299
+ this.DEBUG = !this.DEBUG;
300
+ }
301
+ console.log(`Debug mode ${this.DEBUG ? "enabled" : "disabled"}`);
302
+ }
303
+ }