@unlk/keymaster 1.6.9 → 1.7.0

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/js/video-modal.js CHANGED
@@ -10,55 +10,128 @@ const SELECTOR_MODAL = '.modal';
10
10
  const SELECTOR_DIALOG = '.modal-dialog-media';
11
11
  const SELECTOR_RATIO = '.ratio';
12
12
 
13
- // eslint-disable-next-line no-unused-vars
14
- let youTubeAPIReady = false;
15
- const youTubeAPIInitPromise = new Promise((resolve) => {
13
+ let youTubeAPIInitPromise = null;
14
+ let vimeoAPIInitPromise = null;
15
+
16
+ /**
17
+ * Load the YouTube iframe API only when a YouTube video is opened.
18
+ */
19
+ function loadYouTubeAPI() {
16
20
  if (globalThis.YT?.Player) {
17
- youTubeAPIReady = true;
18
- resolve();
19
- } else {
20
- const prev = globalThis.onYouTubeIframeAPIReady;
21
+ return Promise.resolve();
22
+ }
23
+
24
+ if (youTubeAPIInitPromise) {
25
+ return youTubeAPIInitPromise;
26
+ }
27
+
28
+ youTubeAPIInitPromise = new Promise((resolve, reject) => {
29
+ const existingScript = document.querySelector(
30
+ 'script[src="https://www.youtube.com/iframe_api"]'
31
+ );
32
+
33
+ const previousReady = globalThis.onYouTubeIframeAPIReady;
34
+
21
35
  globalThis.onYouTubeIframeAPIReady = () => {
22
36
  try {
23
- if (typeof prev === 'function') prev();
37
+ if (typeof previousReady === 'function') {
38
+ previousReady();
39
+ }
24
40
  } catch {}
25
41
 
26
- youTubeAPIReady = true;
27
42
  resolve();
28
43
  };
29
44
 
45
+ if (existingScript) {
46
+ if (globalThis.YT?.Player) {
47
+ resolve();
48
+ }
49
+
50
+ return;
51
+ }
52
+
30
53
  const script = document.createElement('script');
54
+
31
55
  script.src = 'https://www.youtube.com/iframe_api';
56
+ script.async = true;
57
+
58
+ script.addEventListener('error', () => {
59
+ youTubeAPIInitPromise = null;
60
+ reject(new Error('Failed to load YouTube iframe API'));
61
+ });
62
+
32
63
  document.head.append(script);
33
- }
34
- });
64
+ });
65
+
66
+ return youTubeAPIInitPromise;
67
+ }
35
68
 
36
- // eslint-disable-next-line no-unused-vars
37
- let vimeoAPIReady = false;
38
- const vimeoAPIInitPromise = new Promise((resolve) => {
69
+ /**
70
+ * Load the Vimeo Player API only when a Vimeo video is opened.
71
+ */
72
+ function loadVimeoAPI() {
39
73
  if (globalThis.Vimeo?.Player) {
40
- vimeoAPIReady = true;
41
- resolve();
42
- } else {
74
+ return Promise.resolve();
75
+ }
76
+
77
+ if (vimeoAPIInitPromise) {
78
+ return vimeoAPIInitPromise;
79
+ }
80
+
81
+ vimeoAPIInitPromise = new Promise((resolve, reject) => {
82
+ const existingScript = document.querySelector(
83
+ 'script[src="https://player.vimeo.com/api/player.js"]'
84
+ );
85
+
86
+ if (existingScript) {
87
+ existingScript.addEventListener('load', () => resolve(), { once: true });
88
+
89
+ existingScript.addEventListener(
90
+ 'error',
91
+ () => {
92
+ vimeoAPIInitPromise = null;
93
+ reject(new Error('Failed to load Vimeo Player API'));
94
+ },
95
+ { once: true }
96
+ );
97
+
98
+ return;
99
+ }
100
+
43
101
  const script = document.createElement('script');
102
+
44
103
  script.src = 'https://player.vimeo.com/api/player.js';
45
- script.addEventListener('load', () => {
46
- vimeoAPIReady = true;
47
- resolve();
48
- });
104
+ script.async = true;
105
+
106
+ script.addEventListener('load', () => resolve(), { once: true });
107
+
108
+ script.addEventListener(
109
+ 'error',
110
+ () => {
111
+ vimeoAPIInitPromise = null;
112
+ reject(new Error('Failed to load Vimeo Player API'));
113
+ },
114
+ { once: true }
115
+ );
49
116
 
50
117
  document.head.append(script);
51
- }
52
- });
118
+ });
119
+
120
+ return vimeoAPIInitPromise;
121
+ }
53
122
 
54
123
  class VideoModal extends BaseComponent {
55
124
  constructor(element) {
56
125
  super(element);
57
126
 
58
127
  this._dialog = SelectorEngine.findOne(SELECTOR_DIALOG, this._element);
128
+
59
129
  this._container = SelectorEngine.findOne(SELECTOR_RATIO, this._element);
130
+
60
131
  this._youtubeSrc = this._container?.getAttribute('data-src');
132
+
61
133
  this._vimeoUrl = this._container?.getAttribute('data-vimeo-url');
134
+
62
135
  this._youtubePlayer = null;
63
136
  this._vimeoPlayer = null;
64
137
  }
@@ -72,53 +145,87 @@ class VideoModal extends BaseComponent {
72
145
  const { id } = this._element;
73
146
  const playerID = `video-${id}-youtube-player`;
74
147
 
75
- if (this._vimeoUrl) {
76
- await vimeoAPIInitPromise;
77
- const vimeoId = this._getVimeoId(this._vimeoUrl);
148
+ try {
149
+ if (this._vimeoUrl) {
150
+ await loadVimeoAPI();
78
151
 
79
- this._vimeoPlayer = new globalThis.Vimeo.Player(this._container, {
80
- id: vimeoId,
81
- autoplay: true
82
- });
152
+ const vimeoId = this._getVimeoId(this._vimeoUrl);
83
153
 
84
- this._element.dataset.vimeoId = String(vimeoId);
85
- this._element.__unlkVimeoPlayer = this._vimeoPlayer;
154
+ if (!vimeoId) {
155
+ return;
156
+ }
86
157
 
87
- this._element.dispatchEvent(
88
- new CustomEvent('videomodal.ready', {
89
- bubbles: true,
90
- detail: { provider: 'vimeo', player: this._vimeoPlayer, videoId: vimeoId }
91
- })
92
- );
158
+ this._vimeoPlayer = new globalThis.Vimeo.Player(this._container, {
159
+ id: vimeoId,
160
+ autoplay: true
161
+ });
93
162
 
94
- return;
95
- }
163
+ this._element.dataset.vimeoId = String(vimeoId);
96
164
 
97
- if (this._youtubeSrc) {
98
- await youTubeAPIInitPromise;
99
- const youtubeId = this._getYouTubeId(this._youtubeSrc);
100
- const playerDiv = this._container.querySelector(`#${playerID}`);
165
+ this._element.__unlkVimeoPlayer = this._vimeoPlayer;
101
166
 
102
- this._youtubePlayer = new globalThis.YT.Player(playerDiv, {
103
- videoId: youtubeId,
104
- playerVars: { autoplay: 1, rel: 0 }
105
- });
167
+ this._element.dispatchEvent(
168
+ new CustomEvent('videomodal.ready', {
169
+ bubbles: true,
170
+ detail: {
171
+ provider: 'vimeo',
172
+ player: this._vimeoPlayer,
173
+ videoId: vimeoId
174
+ }
175
+ })
176
+ );
106
177
 
107
- this._element.dataset.ytId = youtubeId;
108
- this._element.__unlkYtPlayer = this._youtubePlayer;
178
+ return;
179
+ }
109
180
 
110
- this._element.dispatchEvent(
111
- new CustomEvent('videomodal.ready', {
112
- bubbles: true,
113
- detail: { provider: 'youtube', player: this._youtubePlayer, videoId: youtubeId }
114
- })
115
- );
181
+ if (this._youtubeSrc) {
182
+ await loadYouTubeAPI();
183
+
184
+ const youtubeId = this._getYouTubeId(this._youtubeSrc);
185
+
186
+ if (!youtubeId) {
187
+ return;
188
+ }
189
+
190
+ const playerDiv = this._container.querySelector(`#${playerID}`);
191
+
192
+ if (!playerDiv) {
193
+ return;
194
+ }
195
+
196
+ this._youtubePlayer = new globalThis.YT.Player(playerDiv, {
197
+ videoId: youtubeId,
198
+ playerVars: {
199
+ autoplay: 1,
200
+ rel: 0
201
+ }
202
+ });
203
+
204
+ this._element.dataset.ytId = youtubeId;
205
+
206
+ this._element.__unlkYtPlayer = this._youtubePlayer;
207
+
208
+ this._element.dispatchEvent(
209
+ new CustomEvent('videomodal.ready', {
210
+ bubbles: true,
211
+ detail: {
212
+ provider: 'youtube',
213
+ player: this._youtubePlayer,
214
+ videoId: youtubeId
215
+ }
216
+ })
217
+ );
218
+ }
219
+ } catch (error) {
220
+ // eslint-disable-next-line no-console
221
+ console.error('[VideoModal] Failed to initialize video player:', error);
116
222
  }
117
223
  }
118
224
 
119
225
  _onHide() {
120
226
  if (this._vimeoPlayer) {
121
227
  this._vimeoPlayer.unload().catch(() => {});
228
+
122
229
  this._vimeoPlayer = null;
123
230
  }
124
231
 
@@ -129,17 +236,20 @@ class VideoModal extends BaseComponent {
129
236
 
130
237
  delete this._element.__unlkYtPlayer;
131
238
  delete this._element.dataset.ytId;
239
+
132
240
  delete this._element.__unlkVimeoPlayer;
133
241
  delete this._element.dataset.vimeoId;
134
242
  }
135
243
 
136
244
  _getYouTubeId(url) {
137
245
  const match = url.match(/(?:youtube\.com\/(?:watch\?v=|embed\/|v\/)|youtu\.be\/)([\w-]{11})/);
246
+
138
247
  return match ? match[1] : '';
139
248
  }
140
249
 
141
250
  _getVimeoId(url) {
142
251
  const match = url.match(/vimeo\.com\/(\d+)/);
252
+
143
253
  return match ? match[1] : '';
144
254
  }
145
255
 
@@ -155,12 +265,18 @@ class VideoModal extends BaseComponent {
155
265
  }
156
266
 
157
267
  EventHandler.on(document, EVENT_SHOW, SELECTOR_MODAL, function () {
158
- if (!SelectorEngine.findOne(SELECTOR_DIALOG, this)) return;
268
+ if (!SelectorEngine.findOne(SELECTOR_DIALOG, this)) {
269
+ return;
270
+ }
271
+
159
272
  VideoModal.getOrCreateInstance(this)._onShow();
160
273
  });
161
274
 
162
275
  EventHandler.on(document, EVENT_HIDDEN, SELECTOR_MODAL, function () {
163
- if (!SelectorEngine.findOne(SELECTOR_DIALOG, this)) return;
276
+ if (!SelectorEngine.findOne(SELECTOR_DIALOG, this)) {
277
+ return;
278
+ }
279
+
164
280
  VideoModal.getOrCreateInstance(this)._onHide();
165
281
  });
166
282
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unlk/keymaster",
3
- "version": "1.6.9",
3
+ "version": "1.7.0",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -1,2 +1,2 @@
1
1
  // GENERATED FILE – do not edit manually
2
- $km-version: "1.6.9" !default;
2
+ $km-version: "1.7.0" !default;
@@ -17,6 +17,7 @@
17
17
 
18
18
  input:checked {
19
19
  label {
20
+ background-color: $action-25;
20
21
  border-color: $action;
21
22
  }
22
23
  }
@@ -32,8 +33,8 @@
32
33
 
33
34
  &:checked {
34
35
  ~ .form-check-label {
36
+ background-color: $action-25;
35
37
  border-color: $action;
36
-
37
38
  &::before {
38
39
  background-color: $action;
39
40
  border-color: $action;