@smartimpact-it/modern-video-embed 2.0.6 → 2.0.7
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.
- package/dist/components/BaseVideoEmbed.d.ts +5 -0
- package/dist/components/BaseVideoEmbed.d.ts.map +1 -1
- package/dist/components/BaseVideoEmbed.js +19 -0
- package/dist/components/BaseVideoEmbed.js.map +1 -1
- package/dist/components/VideoEmbed.d.ts.map +1 -1
- package/dist/components/VideoEmbed.js +19 -3
- package/dist/components/VideoEmbed.js.map +1 -1
- package/dist/components/VimeoEmbed.d.ts.map +1 -1
- package/dist/components/VimeoEmbed.js +33 -5
- package/dist/components/VimeoEmbed.js.map +1 -1
- package/dist/components/VimeoEmbed.min.js +1 -1
- package/dist/components/YouTubeEmbed.d.ts.map +1 -1
- package/dist/components/YouTubeEmbed.js +25 -7
- package/dist/components/YouTubeEmbed.js.map +1 -1
- package/dist/components/YouTubeEmbed.min.js +1 -1
- package/dist/css/components.css +68 -42
- package/dist/css/components.css.map +1 -1
- package/dist/css/components.min.css +1 -1
- package/dist/css/main.css +68 -42
- package/dist/css/main.css.map +1 -1
- package/dist/css/main.min.css +1 -1
- package/package.json +1 -1
- package/src/components/BaseVideoEmbed.ts +32 -0
- package/src/components/VideoEmbed.ts +20 -2
- package/src/components/VimeoEmbed.ts +31 -4
- package/src/components/YouTubeEmbed.ts +25 -8
- package/src/styles/_embed-base.scss +35 -15
- package/src/styles/video-embed.scss +18 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@smartimpact-it/modern-video-embed",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.7",
|
|
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",
|
|
@@ -148,6 +148,38 @@ export abstract class BaseVideoEmbed extends HTMLElement {
|
|
|
148
148
|
}
|
|
149
149
|
}
|
|
150
150
|
|
|
151
|
+
/**
|
|
152
|
+
* Automatically set aspect ratio CSS custom properties
|
|
153
|
+
* This allows background videos to maintain correct proportions
|
|
154
|
+
*/
|
|
155
|
+
protected setAspectRatio(width: number, height: number): void {
|
|
156
|
+
if (width > 0 && height > 0) {
|
|
157
|
+
// Calculate GCD for simplest ratio
|
|
158
|
+
const gcd = (a: number, b: number): number =>
|
|
159
|
+
b === 0 ? a : gcd(b, a % b);
|
|
160
|
+
const divisor = gcd(width, height);
|
|
161
|
+
const aspectWidth = width / divisor;
|
|
162
|
+
const aspectHeight = height / divisor;
|
|
163
|
+
|
|
164
|
+
this.style.setProperty("--si-embed-aspect-width", aspectWidth.toString());
|
|
165
|
+
this.style.setProperty(
|
|
166
|
+
"--si-embed-aspect-height",
|
|
167
|
+
aspectHeight.toString(),
|
|
168
|
+
);
|
|
169
|
+
|
|
170
|
+
// Also update the padding-top percentage for normal mode
|
|
171
|
+
const aspectRatioPercent = (height / width) * 100;
|
|
172
|
+
this.style.setProperty(
|
|
173
|
+
"--si-embed-video-aspect-ratio",
|
|
174
|
+
`${aspectRatioPercent}%`,
|
|
175
|
+
);
|
|
176
|
+
|
|
177
|
+
this.log(
|
|
178
|
+
`Aspect ratio auto-detected: ${aspectWidth}:${aspectHeight} (${width}x${height})`,
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
151
183
|
/**
|
|
152
184
|
* Custom controls creation (similar pattern across components)
|
|
153
185
|
*/
|
|
@@ -133,6 +133,11 @@ export class VideoEmbed extends BaseVideoEmbed {
|
|
|
133
133
|
return this._controls;
|
|
134
134
|
}
|
|
135
135
|
set controls(value: boolean) {
|
|
136
|
+
// Background mode videos must not have controls
|
|
137
|
+
if (this._background && value) {
|
|
138
|
+
this.warn("Cannot enable controls on background video");
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
136
141
|
this._controls = value;
|
|
137
142
|
this.reflectBooleanAttribute("controls", value);
|
|
138
143
|
if (this.video) {
|
|
@@ -152,6 +157,11 @@ export class VideoEmbed extends BaseVideoEmbed {
|
|
|
152
157
|
return this._muted;
|
|
153
158
|
}
|
|
154
159
|
set muted(value: boolean) {
|
|
160
|
+
// Background mode videos must be muted
|
|
161
|
+
if (this._background && !value) {
|
|
162
|
+
this.warn("Cannot unmute background video");
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
155
165
|
this._muted = value;
|
|
156
166
|
this.reflectBooleanAttribute("muted", value);
|
|
157
167
|
if (this.video) {
|
|
@@ -556,10 +566,18 @@ export class VideoEmbed extends BaseVideoEmbed {
|
|
|
556
566
|
});
|
|
557
567
|
|
|
558
568
|
this.video.addEventListener("loadedmetadata", () => {
|
|
569
|
+
const width = this.video?.videoWidth || 0;
|
|
570
|
+
const height = this.video?.videoHeight || 0;
|
|
571
|
+
|
|
572
|
+
// Automatically set aspect ratio CSS custom properties
|
|
573
|
+
if (width > 0 && height > 0) {
|
|
574
|
+
this.setAspectRatio(width, height);
|
|
575
|
+
}
|
|
576
|
+
|
|
559
577
|
this.dispatchCustomEvent("loadedmetadata", {
|
|
560
578
|
duration: this.video?.duration || 0,
|
|
561
|
-
videoWidth:
|
|
562
|
-
videoHeight:
|
|
579
|
+
videoWidth: width,
|
|
580
|
+
videoHeight: height,
|
|
563
581
|
});
|
|
564
582
|
});
|
|
565
583
|
}
|
|
@@ -152,6 +152,11 @@ export class VimeoEmbed extends BaseVideoEmbed {
|
|
|
152
152
|
return this._controls;
|
|
153
153
|
}
|
|
154
154
|
set controls(value: boolean) {
|
|
155
|
+
// Background mode videos must not have controls
|
|
156
|
+
if (this._background && value) {
|
|
157
|
+
this.warn("Cannot enable controls on background video");
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
155
160
|
this._controls = value;
|
|
156
161
|
this.reflectBooleanAttribute("controls", value);
|
|
157
162
|
if (this.initialized) {
|
|
@@ -171,6 +176,11 @@ export class VimeoEmbed extends BaseVideoEmbed {
|
|
|
171
176
|
return this._muted;
|
|
172
177
|
}
|
|
173
178
|
set muted(value: boolean) {
|
|
179
|
+
// Background mode videos must be muted
|
|
180
|
+
if (this._background && !value) {
|
|
181
|
+
this.warn("Cannot unmute background video");
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
174
184
|
this._muted = value;
|
|
175
185
|
this.reflectBooleanAttribute("muted", value);
|
|
176
186
|
if (this.player && this.playerReady) {
|
|
@@ -639,13 +649,17 @@ export class VimeoEmbed extends BaseVideoEmbed {
|
|
|
639
649
|
this.iframe.allow =
|
|
640
650
|
"autoplay; fullscreen; picture-in-picture; clipboard-write";
|
|
641
651
|
this.iframe.allowFullscreen = true;
|
|
642
|
-
// Set responsive sizing
|
|
643
|
-
this.iframe.style.width = "100%";
|
|
644
|
-
this.iframe.style.height = "100%";
|
|
645
652
|
this.iframe.style.border = "none";
|
|
646
653
|
|
|
654
|
+
// Set responsive sizing based on mode
|
|
647
655
|
if (this._background) {
|
|
656
|
+
// In background mode, let CSS handle sizing with aspect-ratio
|
|
648
657
|
this.iframe.style.pointerEvents = "none";
|
|
658
|
+
// Don't set width/height inline to allow CSS aspect-ratio to work
|
|
659
|
+
} else {
|
|
660
|
+
// In normal mode, fill the container
|
|
661
|
+
this.iframe.style.width = "100%";
|
|
662
|
+
this.iframe.style.height = "100%";
|
|
649
663
|
}
|
|
650
664
|
|
|
651
665
|
// Add tabindex for keyboard accessibility
|
|
@@ -683,7 +697,7 @@ export class VimeoEmbed extends BaseVideoEmbed {
|
|
|
683
697
|
// Use the iframe element directly since it's already in the DOM
|
|
684
698
|
this.player = new Vimeo.Player(iframe);
|
|
685
699
|
|
|
686
|
-
this.player.on("loaded", () => {
|
|
700
|
+
this.player.on("loaded", async () => {
|
|
687
701
|
this.log("VimeoEmbed: Player is ready.");
|
|
688
702
|
this.playerReady = true;
|
|
689
703
|
|
|
@@ -691,6 +705,19 @@ export class VimeoEmbed extends BaseVideoEmbed {
|
|
|
691
705
|
this.player.setVolume(0);
|
|
692
706
|
}
|
|
693
707
|
|
|
708
|
+
// Get video dimensions for aspect ratio
|
|
709
|
+
try {
|
|
710
|
+
const width = await this.player.getVideoWidth();
|
|
711
|
+
const height = await this.player.getVideoHeight();
|
|
712
|
+
if (width > 0 && height > 0) {
|
|
713
|
+
this.setAspectRatio(width, height);
|
|
714
|
+
}
|
|
715
|
+
} catch (error) {
|
|
716
|
+
this.warn("Could not get video dimensions:", error);
|
|
717
|
+
// Default to 16:9 if dimensions not available
|
|
718
|
+
this.setAspectRatio(16, 9);
|
|
719
|
+
}
|
|
720
|
+
|
|
694
721
|
if (autoplay && this.setCustomControlState) {
|
|
695
722
|
this.setCustomControlState(true);
|
|
696
723
|
this._playing = true;
|
|
@@ -174,6 +174,11 @@ export class YouTubeEmbed extends BaseVideoEmbed {
|
|
|
174
174
|
return this._controls;
|
|
175
175
|
}
|
|
176
176
|
set controls(value: boolean) {
|
|
177
|
+
// Background mode videos must not have controls
|
|
178
|
+
if (this._background && value) {
|
|
179
|
+
this.warn("Cannot enable controls on background video");
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
177
182
|
this._controls = value;
|
|
178
183
|
this.reflectBooleanAttribute("controls", value);
|
|
179
184
|
if (this.initialized) {
|
|
@@ -200,6 +205,11 @@ export class YouTubeEmbed extends BaseVideoEmbed {
|
|
|
200
205
|
return this._muted;
|
|
201
206
|
}
|
|
202
207
|
set muted(value: boolean) {
|
|
208
|
+
// Background mode videos must be muted
|
|
209
|
+
if (this._background && !value) {
|
|
210
|
+
this.warn("Cannot unmute background video");
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
203
213
|
this._muted = value;
|
|
204
214
|
this.reflectBooleanAttribute("muted", value);
|
|
205
215
|
if (this.player && this.playerReady) {
|
|
@@ -737,15 +747,22 @@ export class YouTubeEmbed extends BaseVideoEmbed {
|
|
|
737
747
|
// Get the iframe element that YT.Player created
|
|
738
748
|
this.iframe = this.player.getIframe();
|
|
739
749
|
|
|
740
|
-
//
|
|
741
|
-
|
|
742
|
-
this.iframe.style.width = "100%";
|
|
743
|
-
this.iframe.style.height = "100%";
|
|
744
|
-
}
|
|
750
|
+
// Set aspect ratio (YouTube is typically 16:9)
|
|
751
|
+
this.setAspectRatio(16, 9);
|
|
745
752
|
|
|
746
|
-
// Apply
|
|
747
|
-
if (this.
|
|
748
|
-
this.
|
|
753
|
+
// Apply styling to iframe
|
|
754
|
+
if (this.iframe) {
|
|
755
|
+
if (this._background) {
|
|
756
|
+
// In background mode, let CSS handle sizing with aspect-ratio
|
|
757
|
+
this.iframe.style.pointerEvents = "none";
|
|
758
|
+
// Remove any inline sizing to let CSS aspect-ratio work
|
|
759
|
+
this.iframe.style.removeProperty("width");
|
|
760
|
+
this.iframe.style.removeProperty("height");
|
|
761
|
+
} else {
|
|
762
|
+
// In normal mode, fill the container
|
|
763
|
+
this.iframe.style.width = "100%";
|
|
764
|
+
this.iframe.style.height = "100%";
|
|
765
|
+
}
|
|
749
766
|
}
|
|
750
767
|
|
|
751
768
|
// Sync muted state with YouTube player
|
|
@@ -6,13 +6,16 @@
|
|
|
6
6
|
@use "shared-functions" as *;
|
|
7
7
|
|
|
8
8
|
// CSS Variables (common across all components)
|
|
9
|
+
// All variables are prefixed with --si-embed- to prevent conflicts
|
|
9
10
|
@mixin embed-css-variables {
|
|
10
|
-
--video-aspect-ratio: 56.25%; // Default 16:9
|
|
11
|
-
--
|
|
12
|
-
--
|
|
13
|
-
--
|
|
14
|
-
--
|
|
15
|
-
--
|
|
11
|
+
--si-embed-video-aspect-ratio: 56.25%; // Default 16:9
|
|
12
|
+
--si-embed-aspect-width: 16; // For dynamic aspect ratio calculations
|
|
13
|
+
--si-embed-aspect-height: 9; // For dynamic aspect ratio calculations
|
|
14
|
+
--si-embed-poster-object-fit: cover;
|
|
15
|
+
--si-embed-video-object-fit: contain;
|
|
16
|
+
--si-embed-control-button-size: 70px;
|
|
17
|
+
--si-embed-control-button-color: #ffffff;
|
|
18
|
+
--si-embed-overlay-background-color: rgba(0, 0, 0, 0.5);
|
|
16
19
|
}
|
|
17
20
|
|
|
18
21
|
// Base container styles
|
|
@@ -20,7 +23,7 @@
|
|
|
20
23
|
display: block;
|
|
21
24
|
position: relative;
|
|
22
25
|
width: 100%;
|
|
23
|
-
padding-top: var(--video-aspect-ratio);
|
|
26
|
+
padding-top: var(--si-embed-video-aspect-ratio);
|
|
24
27
|
overflow: hidden;
|
|
25
28
|
background-color: #000;
|
|
26
29
|
}
|
|
@@ -33,7 +36,7 @@
|
|
|
33
36
|
width: 100%;
|
|
34
37
|
height: 100%;
|
|
35
38
|
border: none;
|
|
36
|
-
object-fit: var(--video-object-fit);
|
|
39
|
+
object-fit: var(--si-embed-video-object-fit);
|
|
37
40
|
}
|
|
38
41
|
|
|
39
42
|
// Poster image styles
|
|
@@ -46,7 +49,7 @@
|
|
|
46
49
|
left: 0;
|
|
47
50
|
width: 100%;
|
|
48
51
|
height: 100%;
|
|
49
|
-
object-fit: var(--poster-object-fit);
|
|
52
|
+
object-fit: var(--si-embed-poster-object-fit);
|
|
50
53
|
display: block;
|
|
51
54
|
cursor: pointer;
|
|
52
55
|
}
|
|
@@ -55,7 +58,7 @@
|
|
|
55
58
|
// Custom control button overlay
|
|
56
59
|
@mixin embed-button-overlay {
|
|
57
60
|
.button-overlay {
|
|
58
|
-
background: var(--overlay-background-color);
|
|
61
|
+
background: var(--si-embed-overlay-background-color);
|
|
59
62
|
cursor: pointer;
|
|
60
63
|
display: flex;
|
|
61
64
|
align-items: center;
|
|
@@ -86,8 +89,8 @@
|
|
|
86
89
|
display: flex;
|
|
87
90
|
align-items: center;
|
|
88
91
|
justify-content: center;
|
|
89
|
-
height: var(--control-button-size);
|
|
90
|
-
width: var(--control-button-size);
|
|
92
|
+
height: var(--si-embed-control-button-size);
|
|
93
|
+
width: var(--si-embed-control-button-size);
|
|
91
94
|
background-image: inline-svg(
|
|
92
95
|
'<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="#ffffff" d="M8,5.14V19.14L19,12.14L8,5.14Z" /></svg>'
|
|
93
96
|
);
|
|
@@ -122,9 +125,13 @@
|
|
|
122
125
|
top: 50%;
|
|
123
126
|
left: 50%;
|
|
124
127
|
transform: translate(-50%, -50%);
|
|
125
|
-
aspect-ratio
|
|
128
|
+
// Use aspect-ratio with min-dimensions to ensure coverage
|
|
129
|
+
aspect-ratio: var(--si-embed-aspect-width) /
|
|
130
|
+
var(--si-embed-aspect-height);
|
|
131
|
+
// Start with auto sizing based on aspect ratio
|
|
126
132
|
width: auto;
|
|
127
133
|
height: auto;
|
|
134
|
+
// Ensure iframe covers entire container
|
|
128
135
|
min-width: 100%;
|
|
129
136
|
min-height: 100%;
|
|
130
137
|
pointer-events: none;
|
|
@@ -135,10 +142,23 @@
|
|
|
135
142
|
top: 50%;
|
|
136
143
|
left: 50%;
|
|
137
144
|
transform: translate(-50%, -50%);
|
|
145
|
+
// Use CSS custom properties for dynamic aspect ratio
|
|
138
146
|
width: 100%;
|
|
139
|
-
height:
|
|
147
|
+
height: calc(
|
|
148
|
+
(100% * var(--si-embed-aspect-height)) / var(--si-embed-aspect-width)
|
|
149
|
+
);
|
|
150
|
+
min-width: 100%;
|
|
151
|
+
min-height: 100%;
|
|
140
152
|
object-fit: cover;
|
|
141
153
|
pointer-events: none;
|
|
154
|
+
|
|
155
|
+
// Hide native media controls in background mode
|
|
156
|
+
&::-webkit-media-controls {
|
|
157
|
+
display: none !important;
|
|
158
|
+
}
|
|
159
|
+
&::-webkit-media-controls-enclosure {
|
|
160
|
+
display: none !important;
|
|
161
|
+
}
|
|
142
162
|
}
|
|
143
163
|
}
|
|
144
164
|
|
|
@@ -157,7 +177,7 @@
|
|
|
157
177
|
padding: 1rem;
|
|
158
178
|
|
|
159
179
|
.button {
|
|
160
|
-
--control-button-size: 40px;
|
|
180
|
+
--si-embed-control-button-size: 40px;
|
|
161
181
|
opacity: 0.7;
|
|
162
182
|
transition: opacity 0.2s ease;
|
|
163
183
|
&:hover {
|
|
@@ -11,6 +11,24 @@ video-embed {
|
|
|
11
11
|
// Video-specific styles only
|
|
12
12
|
video {
|
|
13
13
|
@include embed-media-base;
|
|
14
|
+
|
|
15
|
+
// Hide native controls when controls attribute is not set
|
|
16
|
+
&::-webkit-media-controls {
|
|
17
|
+
display: none !important;
|
|
18
|
+
}
|
|
19
|
+
&::-webkit-media-controls-enclosure {
|
|
20
|
+
display: none !important;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Show native controls only when explicitly enabled
|
|
25
|
+
&[controls] video {
|
|
26
|
+
&::-webkit-media-controls {
|
|
27
|
+
display: flex !important;
|
|
28
|
+
}
|
|
29
|
+
&::-webkit-media-controls-enclosure {
|
|
30
|
+
display: flex !important;
|
|
31
|
+
}
|
|
14
32
|
}
|
|
15
33
|
|
|
16
34
|
// Poster image specific to video (uses background-image pattern)
|