vidply 1.1.3 → 1.1.5

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 (87) hide show
  1. package/README.md +51 -2
  2. package/dist/dev/{vidply.AudioDescriptionManager-AXPP5OKO.js → vidply.AudioDescriptionManager-DCZFE3N2.js} +4 -4
  3. package/dist/dev/vidply.FloatingPlayerManager-5EA4BYSR.js +515 -0
  4. package/dist/dev/vidply.FloatingPlayerManager-5EA4BYSR.js.map +7 -0
  5. package/dist/dev/vidply.FloatingPlayerManager-AEMEBLF7.js +514 -0
  6. package/dist/dev/vidply.FloatingPlayerManager-AEMEBLF7.js.map +7 -0
  7. package/dist/dev/{vidply.SignLanguageManager-OOZLX2GI.js → vidply.SignLanguageManager-4NOFV4EZ.js} +7 -5
  8. package/dist/dev/{vidply.SignLanguageManager-OOZLX2GI.js.map → vidply.SignLanguageManager-4NOFV4EZ.js.map} +1 -1
  9. package/dist/dev/{vidply.TranscriptManager-BOYEUAA4.js → vidply.TranscriptManager-7TS37MMM.js} +9 -7
  10. package/dist/dev/{vidply.TranscriptManager-BOYEUAA4.js.map → vidply.TranscriptManager-7TS37MMM.js.map} +1 -1
  11. package/dist/dev/{vidply.chunk-55W33EGZ.js → vidply.chunk-2RIPRXWI.js} +3 -3
  12. package/dist/dev/vidply.chunk-2RIPRXWI.js.map +7 -0
  13. package/dist/dev/{vidply.chunk-S36ISCDT.js → vidply.chunk-5SDVVGOD.js} +2 -2
  14. package/dist/dev/vidply.chunk-COW6OPNQ.js +222 -0
  15. package/dist/dev/vidply.chunk-COW6OPNQ.js.map +7 -0
  16. package/dist/dev/{vidply.chunk-37V7VOIQ.js → vidply.chunk-D6GBU4V6.js} +2 -212
  17. package/dist/dev/vidply.chunk-D6GBU4V6.js.map +7 -0
  18. package/dist/dev/{vidply.chunk-VLF6H5PZ.js → vidply.chunk-FDUUJHK6.js} +13 -5
  19. package/dist/dev/vidply.chunk-FDUUJHK6.js.map +7 -0
  20. package/dist/dev/{vidply.chunk-SXDZXXZD.js → vidply.chunk-X6PJOVWZ.js} +7 -1
  21. package/dist/dev/{vidply.chunk-SXDZXXZD.js.map → vidply.chunk-X6PJOVWZ.js.map} +2 -2
  22. package/dist/dev/{vidply.de-HLO4IOPX.js → vidply.de-TEYITA3Z.js} +9 -1
  23. package/dist/dev/vidply.de-TEYITA3Z.js.map +7 -0
  24. package/dist/dev/{vidply.es-OBOYSHKU.js → vidply.es-OKQLRQPJ.js} +9 -1
  25. package/dist/dev/vidply.es-OKQLRQPJ.js.map +7 -0
  26. package/dist/dev/vidply.esm.js +320 -24
  27. package/dist/dev/vidply.esm.js.map +3 -3
  28. package/dist/dev/{vidply.fr-WDRPLLB6.js → vidply.fr-52424DPC.js} +9 -1
  29. package/dist/dev/vidply.fr-52424DPC.js.map +7 -0
  30. package/dist/dev/{vidply.ja-ILEIPUNW.js → vidply.ja-X4QNECSO.js} +9 -1
  31. package/dist/dev/vidply.ja-X4QNECSO.js.map +7 -0
  32. package/dist/legacy/vidply.js +871 -15
  33. package/dist/legacy/vidply.js.map +4 -4
  34. package/dist/legacy/vidply.min.js +1 -1
  35. package/dist/legacy/vidply.min.meta.json +72 -20
  36. package/dist/prod/{vidply.AudioDescriptionManager-623CQKLU.min.js → vidply.AudioDescriptionManager-JJFZSG7G.min.js} +1 -1
  37. package/dist/prod/vidply.FloatingPlayerManager-JVNKAK4N.min.js +6 -0
  38. package/dist/prod/vidply.FloatingPlayerManager-NRKSWDTL.min.js +6 -0
  39. package/dist/prod/vidply.SignLanguageManager-6FBSQB4U.min.js +6 -0
  40. package/dist/prod/vidply.TranscriptManager-L5E737CR.min.js +6 -0
  41. package/dist/prod/{vidply.chunk-ALJYNEGS.min.js → vidply.chunk-DPEZK7YT.min.js} +1 -1
  42. package/dist/prod/vidply.chunk-KXQK2NF7.min.js +6 -0
  43. package/dist/prod/{vidply.chunk-2YTNSNRD.min.js → vidply.chunk-MOAZJSWV.min.js} +1 -1
  44. package/dist/prod/vidply.chunk-QGVFMQUO.min.js +6 -0
  45. package/dist/prod/vidply.chunk-VSKF7AQ2.min.js +6 -0
  46. package/dist/prod/{vidply.chunk-KYQFJSLN.min.js → vidply.chunk-ZDIQYKKX.min.js} +1 -1
  47. package/dist/prod/vidply.de-EIZK243P.min.js +6 -0
  48. package/dist/prod/vidply.es-JGYFGRSI.min.js +6 -0
  49. package/dist/prod/vidply.esm.min.js +1 -1
  50. package/dist/prod/vidply.fr-XAQ6ZIB7.min.js +6 -0
  51. package/dist/prod/vidply.ja-S77KR54S.min.js +6 -0
  52. package/dist/vidply.css +475 -209
  53. package/dist/vidply.esm.min.meta.json +169 -75
  54. package/dist/vidply.min.css +1 -1
  55. package/package.json +1 -1
  56. package/src/controls/CaptionManager.ts +555 -555
  57. package/src/controls/ControlBar.ts +172 -19
  58. package/src/core/FloatingPlayerManager.ts +636 -0
  59. package/src/core/Player.ts +6036 -5946
  60. package/src/i18n/languages/de.ts +8 -0
  61. package/src/i18n/languages/en.ts +8 -0
  62. package/src/i18n/languages/es.ts +8 -0
  63. package/src/i18n/languages/fr.ts +8 -0
  64. package/src/i18n/languages/ja.ts +8 -0
  65. package/src/index.ts +5 -0
  66. package/src/styles/vidply.css +475 -209
  67. package/src/types/options.ts +6 -0
  68. package/src/types/state.ts +1 -0
  69. package/src/utils/DownloadInfo.ts +148 -0
  70. package/src/utils/StorageManager.ts +8 -0
  71. package/dist/dev/vidply.chunk-37V7VOIQ.js.map +0 -7
  72. package/dist/dev/vidply.chunk-55W33EGZ.js.map +0 -7
  73. package/dist/dev/vidply.chunk-VLF6H5PZ.js.map +0 -7
  74. package/dist/dev/vidply.de-HLO4IOPX.js.map +0 -7
  75. package/dist/dev/vidply.es-OBOYSHKU.js.map +0 -7
  76. package/dist/dev/vidply.fr-WDRPLLB6.js.map +0 -7
  77. package/dist/dev/vidply.ja-ILEIPUNW.js.map +0 -7
  78. package/dist/prod/vidply.SignLanguageManager-UPGXSO5F.min.js +0 -6
  79. package/dist/prod/vidply.TranscriptManager-PLVKPYNB.min.js +0 -6
  80. package/dist/prod/vidply.chunk-UO33KGOT.min.js +0 -6
  81. package/dist/prod/vidply.chunk-XHLTMSR5.min.js +0 -6
  82. package/dist/prod/vidply.de-CKLFPHNF.min.js +0 -6
  83. package/dist/prod/vidply.es-FYZBTBVH.min.js +0 -6
  84. package/dist/prod/vidply.fr-E7FMYILU.min.js +0 -6
  85. package/dist/prod/vidply.ja-NOW6P3IH.min.js +0 -6
  86. /package/dist/dev/{vidply.AudioDescriptionManager-AXPP5OKO.js.map → vidply.AudioDescriptionManager-DCZFE3N2.js.map} +0 -0
  87. /package/dist/dev/{vidply.chunk-S36ISCDT.js.map → vidply.chunk-5SDVVGOD.js.map} +0 -0
package/README.md CHANGED
@@ -8,7 +8,7 @@ A modern, feature-rich media player authored in strict TypeScript and shipped as
8
8
  ![TypeScript](https://img.shields.io/badge/TypeScript-strict-blue.svg)
9
9
  ![ESM](https://img.shields.io/badge/ESM-Module-yellow.svg)
10
10
  ![WCAG](https://img.shields.io/badge/WCAG-2.2%20AA-green.svg)
11
- ![Version](https://img.shields.io/badge/version-1.1.3-brightgreen.svg)
11
+ ![Version](https://img.shields.io/badge/version-1.1.4-brightgreen.svg)
12
12
 
13
13
  ## Live Demos
14
14
 
@@ -81,7 +81,14 @@ Try VidPly in action:
81
81
  - **Volume Control** - 0-100% with mute
82
82
  - **Loop Playback** - Single or playlist loop
83
83
  - **Fullscreen Mode** - Native fullscreen API with smart playlist overlay
84
- - **Picture-in-Picture** - PiP support
84
+ - **Picture-in-Picture** - Native browser PiP support (toggled via the standard PiP button)
85
+ - **Custom Floating Player (Miniplayer)** - Optional in-page floating window that
86
+ - automatically floats when the original player scrolls out of the viewport and docks back when it scrolls in
87
+ - can be pinned/unpinned manually via the PiP button (manual pin overrides scroll behavior)
88
+ - is fully draggable and resizable, with persistent geometry per player
89
+ - keeps VidPly captions, transport controls and fullscreen working inside the floating shell
90
+ - suppresses native browser PiP automatically while enabled
91
+ - is desktop-only (disabled below 768 px viewport width by default)
85
92
 
86
93
  ### Internationalization
87
94
  Built-in support for 5 languages:
@@ -258,6 +265,43 @@ const player = new Player('#my-video', {
258
265
  });
259
266
  ```
260
267
 
268
+ ### Custom Floating Player (Miniplayer)
269
+
270
+ Enable the in-page floating player ("own PiP"). When the original video scrolls
271
+ out of the viewport, VidPly pops up a draggable, resizable floating shell in the
272
+ chosen corner; when the original scrolls back in, it docks again. Users can also
273
+ manually pin/unpin the floating player via the PiP button in the control bar.
274
+ The native browser Picture-in-Picture API is automatically suppressed while
275
+ floating is enabled, so users get a single, consistent experience.
276
+
277
+ Enable via the `data-vidply-options` JSON blob:
278
+
279
+ ```html
280
+ <video
281
+ data-vidply
282
+ data-vidply-options='{"floating": true, "floatingPosition": "bottom-right", "floatingMinViewportWidth": 768}'
283
+ src="video.mp4"
284
+ width="800" height="450">
285
+ <track kind="subtitles" src="captions.vtt" srclang="en" label="English">
286
+ </video>
287
+ ```
288
+
289
+ Or programmatically:
290
+
291
+ ```javascript
292
+ const player = new Player('#my-video', {
293
+ floating: true,
294
+ floatingPosition: 'bottom-right', // or 'bottom-left' | 'top-right' | 'top-left'
295
+ floatingMinViewportWidth: 768 // disable feature below this viewport width
296
+ });
297
+ ```
298
+
299
+ Notes:
300
+ - Audio-only players (`<audio>`) ignore the floating option.
301
+ - Closing the floating window pauses playback and prevents auto-float again until the next user-initiated `play`.
302
+ - The floating window persists its size/position per player via local storage.
303
+ - Below `floatingMinViewportWidth` (default 768 px) the PiP button is hidden and the floating feature is disabled.
304
+
261
305
  ### DASH + HLS + MP4 Fallback
262
306
 
263
307
  For maximum device compatibility, provide all three formats:
@@ -312,6 +356,11 @@ const player = new Player('#video', {
312
356
  downloadButton: false, // Show a download button in the control bar
313
357
  downloadUrl: null, // Optional explicit download URL (falls back to current src)
314
358
 
359
+ // Custom Floating Player (in-page miniplayer / "own PiP")
360
+ floating: false, // Enable the custom floating player; also disables native browser PiP
361
+ floatingPosition: 'bottom-right', // 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left'
362
+ floatingMinViewportWidth: 768, // Floating feature is hidden below this viewport width (px)
363
+
315
364
  // Captions
316
365
  captions: true,
317
366
  captionsDefault: false,
@@ -5,9 +5,9 @@
5
5
  */
6
6
  import {
7
7
  CaptionManager
8
- } from "./vidply.chunk-55W33EGZ.js";
9
- import "./vidply.chunk-SXDZXXZD.js";
10
- import "./vidply.chunk-VLF6H5PZ.js";
8
+ } from "./vidply.chunk-2RIPRXWI.js";
9
+ import "./vidply.chunk-X6PJOVWZ.js";
10
+ import "./vidply.chunk-FDUUJHK6.js";
11
11
 
12
12
  // src/core/AudioDescriptionManager.ts
13
13
  var AudioDescriptionManager = class {
@@ -569,4 +569,4 @@ var AudioDescriptionManager = class {
569
569
  export {
570
570
  AudioDescriptionManager
571
571
  };
572
- //# sourceMappingURL=vidply.AudioDescriptionManager-AXPP5OKO.js.map
572
+ //# sourceMappingURL=vidply.AudioDescriptionManager-DCZFE3N2.js.map
@@ -0,0 +1,515 @@
1
+ /*!
2
+ * Universal, Accessible Video Player
3
+ * (c) 2026 Matthias Peltzer
4
+ * Released under GPL-2.0-or-later License
5
+ */
6
+ import {
7
+ DraggableResizable,
8
+ createIconElement
9
+ } from "./vidply.chunk-D6GBU4V6.js";
10
+ import {
11
+ DOMUtils,
12
+ i18n
13
+ } from "./vidply.chunk-FDUUJHK6.js";
14
+
15
+ // src/core/FloatingPlayerManager.ts
16
+ var FLOATING_CLAIM_EVENT = "vidply:floating-claim";
17
+ var DEFAULT_WIDTH = 400;
18
+ var MIN_WIDTH = 240;
19
+ var EDGE_MARGIN = 16;
20
+ var FloatingPlayerManager = class {
21
+ player;
22
+ classPrefix;
23
+ shell;
24
+ dragHandle;
25
+ closeButton;
26
+ resizeHandles;
27
+ placeholder;
28
+ draggable;
29
+ originalParent;
30
+ originalNextSibling;
31
+ intersectionObserver;
32
+ observerTarget;
33
+ lastRatio;
34
+ _autoDismissedThisPlay;
35
+ _playListenerAttached;
36
+ _onPlayAfterDismiss;
37
+ _onClaim;
38
+ _onResize;
39
+ _onKeyDown;
40
+ _onEnterFullscreen;
41
+ _destroyed;
42
+ _triggerFocusEl;
43
+ _claimId;
44
+ constructor(player) {
45
+ this.player = player;
46
+ this.classPrefix = player.options.classPrefix || "vidply";
47
+ this.shell = null;
48
+ this.dragHandle = null;
49
+ this.closeButton = null;
50
+ this.resizeHandles = [];
51
+ this.placeholder = null;
52
+ this.draggable = null;
53
+ this.originalParent = null;
54
+ this.originalNextSibling = null;
55
+ this.intersectionObserver = null;
56
+ this.observerTarget = null;
57
+ this.lastRatio = 1;
58
+ this._autoDismissedThisPlay = false;
59
+ this._playListenerAttached = false;
60
+ this._onPlayAfterDismiss = null;
61
+ this._onClaim = null;
62
+ this._onResize = null;
63
+ this._onKeyDown = null;
64
+ this._onEnterFullscreen = null;
65
+ this._destroyed = false;
66
+ this._triggerFocusEl = null;
67
+ this._claimId = `floating-${player.instanceId}-${Date.now()}`;
68
+ this._setupClaimListener();
69
+ this._setupFullscreenGuard();
70
+ this._startObserving();
71
+ }
72
+ // ---------------------------------------------------------------
73
+ // Public API
74
+ // ---------------------------------------------------------------
75
+ togglePinned(triggerEl) {
76
+ if (this._destroyed) return;
77
+ if (this.player.state.floating === "pinned") {
78
+ this._autoDismissedThisPlay = true;
79
+ this._armPlayListenerToClearDismiss();
80
+ this.exit("manual");
81
+ return;
82
+ }
83
+ this._autoDismissedThisPlay = false;
84
+ this._triggerFocusEl = triggerEl || this._activeElement();
85
+ this.enter("pinned");
86
+ }
87
+ enter(reason) {
88
+ if (this._destroyed) return;
89
+ if (this.player.state.floating === reason) return;
90
+ if (!this._canFloat(reason)) {
91
+ return;
92
+ }
93
+ if (this.player.state.floating && this.player.state.floating !== reason) {
94
+ this.player.state.floating = reason;
95
+ this.player.emit("floatingchange", reason);
96
+ return;
97
+ }
98
+ this._claimSingleton();
99
+ this._ensureShell();
100
+ this._mountIntoShell();
101
+ this._applyInitialGeometry();
102
+ this.player.state.floating = reason;
103
+ this.player.emit("floatingchange", reason);
104
+ queueMicrotask(() => {
105
+ if (this.closeButton && this.player.state.floating) {
106
+ try {
107
+ this.closeButton.focus({ preventScroll: true });
108
+ } catch {
109
+ }
110
+ }
111
+ });
112
+ }
113
+ exit(reason = "manual") {
114
+ if (this._destroyed && reason !== "destroy") return;
115
+ if (!this.player.state.floating) return;
116
+ this._unmountFromShell();
117
+ this._teardownShell();
118
+ const priorTrigger = this._triggerFocusEl;
119
+ this._triggerFocusEl = null;
120
+ this.player.state.floating = null;
121
+ this.player.emit("floatingchange", null);
122
+ if ((reason === "manual" || reason === "dismiss") && priorTrigger) {
123
+ try {
124
+ priorTrigger.focus({ preventScroll: true });
125
+ } catch {
126
+ }
127
+ }
128
+ }
129
+ /**
130
+ * Close button: pause, dismiss, and prevent auto-float until the next
131
+ * user-initiated play event.
132
+ */
133
+ dismiss() {
134
+ if (this._destroyed) return;
135
+ this._autoDismissedThisPlay = true;
136
+ this._armPlayListenerToClearDismiss();
137
+ try {
138
+ this.player.pause();
139
+ } catch {
140
+ }
141
+ this.exit("dismiss");
142
+ }
143
+ destroy() {
144
+ if (this._destroyed) return;
145
+ this._destroyed = true;
146
+ if (this.player.state && this.player.state.floating) {
147
+ try {
148
+ this.exit("destroy");
149
+ } catch {
150
+ }
151
+ }
152
+ if (this.intersectionObserver) {
153
+ try {
154
+ this.intersectionObserver.disconnect();
155
+ } catch {
156
+ }
157
+ this.intersectionObserver = null;
158
+ }
159
+ this.observerTarget = null;
160
+ if (this._onClaim) {
161
+ window.removeEventListener(FLOATING_CLAIM_EVENT, this._onClaim);
162
+ this._onClaim = null;
163
+ }
164
+ if (this._onResize) {
165
+ window.removeEventListener("resize", this._onResize);
166
+ this._onResize = null;
167
+ }
168
+ if (this._onEnterFullscreen) {
169
+ this.player.off("enterfullscreen", this._onEnterFullscreen);
170
+ this._onEnterFullscreen = null;
171
+ }
172
+ if (this._onPlayAfterDismiss && this._playListenerAttached) {
173
+ this.player.off("play", this._onPlayAfterDismiss);
174
+ this._playListenerAttached = false;
175
+ this._onPlayAfterDismiss = null;
176
+ }
177
+ }
178
+ // ---------------------------------------------------------------
179
+ // Internal: guards
180
+ // ---------------------------------------------------------------
181
+ _canFloat(reason) {
182
+ if (!this.player.options.floating) return false;
183
+ if (!this.player.container) return false;
184
+ if (!this.player.element || this.player.element.tagName !== "VIDEO") return false;
185
+ if (this.player.state.fullscreen) return false;
186
+ if (this.player.playlistManager) return false;
187
+ const minWidth = this.player.options.floatingMinViewportWidth ?? 768;
188
+ if (window.innerWidth < minWidth) return false;
189
+ if (reason === "auto") {
190
+ if (this._autoDismissedThisPlay) return false;
191
+ if (this.player.state.paused) return false;
192
+ if (!this.player.state.hasStartedPlayback) return false;
193
+ }
194
+ return true;
195
+ }
196
+ _claimSingleton() {
197
+ try {
198
+ window.dispatchEvent(new CustomEvent(FLOATING_CLAIM_EVENT, {
199
+ detail: { claimId: this._claimId }
200
+ }));
201
+ } catch {
202
+ }
203
+ }
204
+ _setupClaimListener() {
205
+ this._onClaim = (event) => {
206
+ const detail = event.detail;
207
+ if (!detail || detail.claimId === this._claimId) return;
208
+ if (this.player.state.floating) {
209
+ this.exit("claim");
210
+ }
211
+ };
212
+ window.addEventListener(FLOATING_CLAIM_EVENT, this._onClaim);
213
+ this._onResize = () => {
214
+ const minWidth = this.player.options.floatingMinViewportWidth ?? 768;
215
+ if (this.player.state.floating && window.innerWidth < minWidth) {
216
+ this.exit("auto");
217
+ }
218
+ };
219
+ window.addEventListener("resize", this._onResize);
220
+ }
221
+ _setupFullscreenGuard() {
222
+ this._onEnterFullscreen = () => {
223
+ if (this.player.state.floating) {
224
+ this.exit("manual");
225
+ }
226
+ };
227
+ this.player.on("enterfullscreen", this._onEnterFullscreen);
228
+ }
229
+ _armPlayListenerToClearDismiss() {
230
+ if (this._playListenerAttached) return;
231
+ this._onPlayAfterDismiss = () => {
232
+ this._autoDismissedThisPlay = false;
233
+ if (this._onPlayAfterDismiss) {
234
+ this.player.off("play", this._onPlayAfterDismiss);
235
+ }
236
+ this._playListenerAttached = false;
237
+ this._onPlayAfterDismiss = null;
238
+ };
239
+ this.player.on("play", this._onPlayAfterDismiss);
240
+ this._playListenerAttached = true;
241
+ }
242
+ // ---------------------------------------------------------------
243
+ // Internal: IntersectionObserver for scroll-triggered auto-float
244
+ // ---------------------------------------------------------------
245
+ _startObserving() {
246
+ if (!("IntersectionObserver" in window)) return;
247
+ if (!this.player.container) return;
248
+ this.observerTarget = this.player.container;
249
+ this.intersectionObserver = new IntersectionObserver((entries) => {
250
+ const entry = entries[entries.length - 1];
251
+ if (!entry) return;
252
+ this.lastRatio = entry.intersectionRatio;
253
+ if (this.player.options.debug) {
254
+ try {
255
+ console.log("[vidply:floating] intersection", {
256
+ ratio: Number(entry.intersectionRatio.toFixed(3)),
257
+ state: this.player.state.floating,
258
+ paused: this.player.state.paused,
259
+ hasStartedPlayback: this.player.state.hasStartedPlayback,
260
+ dismissed: this._autoDismissedThisPlay
261
+ });
262
+ } catch {
263
+ }
264
+ }
265
+ if (this.player.state.floating === "auto") {
266
+ if (entry.intersectionRatio > 0.5) {
267
+ this.exit("auto");
268
+ }
269
+ return;
270
+ }
271
+ if (this.player.state.floating === "pinned") {
272
+ return;
273
+ }
274
+ if (entry.intersectionRatio < 0.1 && this._canFloat("auto")) {
275
+ this.enter("auto");
276
+ }
277
+ }, { threshold: [0, 0.1, 0.5, 0.9] });
278
+ this.intersectionObserver.observe(this.observerTarget);
279
+ }
280
+ _retargetObserver(target) {
281
+ if (!this.intersectionObserver) return;
282
+ if (this.observerTarget) {
283
+ try {
284
+ this.intersectionObserver.unobserve(this.observerTarget);
285
+ } catch {
286
+ }
287
+ }
288
+ this.observerTarget = target;
289
+ try {
290
+ this.intersectionObserver.observe(target);
291
+ } catch {
292
+ }
293
+ }
294
+ // ---------------------------------------------------------------
295
+ // Internal: shell DOM
296
+ // ---------------------------------------------------------------
297
+ _ensureShell() {
298
+ if (this.shell) return;
299
+ this.shell = DOMUtils.createElement("div", {
300
+ className: `${this.classPrefix}-floating-shell`,
301
+ attributes: {
302
+ "role": "dialog",
303
+ "aria-modal": "false",
304
+ "aria-label": i18n.t("player.floatingPlayer"),
305
+ "data-vidply-floating": "true",
306
+ "tabindex": "-1"
307
+ }
308
+ });
309
+ this.dragHandle = DOMUtils.createElement("div", {
310
+ className: `${this.classPrefix}-floating-drag-handle`,
311
+ attributes: { "aria-hidden": "true" }
312
+ });
313
+ this.shell.appendChild(this.dragHandle);
314
+ this.closeButton = DOMUtils.createElement("button", {
315
+ className: `${this.classPrefix}-floating-close`,
316
+ attributes: {
317
+ "type": "button",
318
+ "aria-label": i18n.t("player.floatingPlayerClose"),
319
+ "title": i18n.t("player.floatingPlayerClose")
320
+ }
321
+ });
322
+ this.closeButton.appendChild(createIconElement("close"));
323
+ this.closeButton.addEventListener("click", (event) => {
324
+ event.stopPropagation();
325
+ this.dismiss();
326
+ });
327
+ this.shell.appendChild(this.closeButton);
328
+ this._createResizeHandles();
329
+ this.resizeHandles.forEach((handle) => this.shell.appendChild(handle));
330
+ this._onKeyDown = (event) => {
331
+ if (event.key === "Escape") {
332
+ event.stopPropagation();
333
+ this.dismiss();
334
+ }
335
+ };
336
+ this.shell.addEventListener("keydown", this._onKeyDown);
337
+ }
338
+ _createResizeHandles() {
339
+ const dirs = ["n", "s", "e", "w", "ne", "nw", "se", "sw"];
340
+ this.resizeHandles = dirs.map((dir) => DOMUtils.createElement("div", {
341
+ className: `${this.classPrefix}-floating-resize-handle ${this.classPrefix}-floating-resize-${dir}`,
342
+ attributes: {
343
+ "data-direction": dir,
344
+ "data-vidply-managed-resize": "true",
345
+ "aria-hidden": "true"
346
+ }
347
+ }));
348
+ }
349
+ _teardownShell() {
350
+ if (this.draggable) {
351
+ try {
352
+ this.draggable.destroy();
353
+ } catch {
354
+ }
355
+ this.draggable = null;
356
+ }
357
+ if (this.shell) {
358
+ if (this._onKeyDown) {
359
+ this.shell.removeEventListener("keydown", this._onKeyDown);
360
+ this._onKeyDown = null;
361
+ }
362
+ if (this.shell.parentNode) {
363
+ this.shell.parentNode.removeChild(this.shell);
364
+ }
365
+ }
366
+ this.shell = null;
367
+ this.dragHandle = null;
368
+ this.closeButton = null;
369
+ this.resizeHandles = [];
370
+ }
371
+ // ---------------------------------------------------------------
372
+ // Internal: mount / unmount the player.container
373
+ // ---------------------------------------------------------------
374
+ _mountIntoShell() {
375
+ const container = this.player.container;
376
+ if (!container || !container.parentNode) return;
377
+ if (!this.shell) return;
378
+ const rect = container.getBoundingClientRect();
379
+ this.originalParent = container.parentNode;
380
+ this.originalNextSibling = container.nextSibling;
381
+ this.placeholder = DOMUtils.createElement("div", {
382
+ className: `${this.classPrefix}-floating-placeholder`,
383
+ attributes: { "aria-hidden": "true" }
384
+ });
385
+ this.placeholder.style.width = `${Math.max(1, rect.width)}px`;
386
+ this.placeholder.style.height = `${Math.max(1, rect.height)}px`;
387
+ this.originalParent.insertBefore(this.placeholder, container);
388
+ this.shell.appendChild(container);
389
+ document.body.appendChild(this.shell);
390
+ container.classList.add(`${this.classPrefix}-is-floating`);
391
+ this._retargetObserver(this.placeholder);
392
+ }
393
+ _unmountFromShell() {
394
+ const container = this.player.container;
395
+ if (container) {
396
+ container.classList.remove(`${this.classPrefix}-is-floating`);
397
+ container.style.removeProperty("width");
398
+ container.style.removeProperty("height");
399
+ }
400
+ if (this.placeholder && this.placeholder.parentNode) {
401
+ if (container) {
402
+ this.placeholder.parentNode.insertBefore(container, this.placeholder);
403
+ }
404
+ this.placeholder.parentNode.removeChild(this.placeholder);
405
+ } else if (container && this.originalParent) {
406
+ if (this.originalNextSibling && this.originalNextSibling.parentNode === this.originalParent) {
407
+ this.originalParent.insertBefore(container, this.originalNextSibling);
408
+ } else {
409
+ this.originalParent.appendChild(container);
410
+ }
411
+ }
412
+ this.placeholder = null;
413
+ this.originalParent = null;
414
+ this.originalNextSibling = null;
415
+ if (container) {
416
+ this._retargetObserver(container);
417
+ }
418
+ }
419
+ // ---------------------------------------------------------------
420
+ // Internal: initial geometry + drag/resize wiring
421
+ // ---------------------------------------------------------------
422
+ _applyInitialGeometry() {
423
+ if (!this.shell) return;
424
+ const prefs = this.player.storage?.getFloatingPreferences?.() || {};
425
+ const vw = window.innerWidth;
426
+ const vh = window.innerHeight;
427
+ let width = prefs.width && prefs.width >= MIN_WIDTH ? prefs.width : DEFAULT_WIDTH;
428
+ width = Math.min(width, Math.max(MIN_WIDTH, vw - EDGE_MARGIN * 2));
429
+ const containerRect = this.player.container?.getBoundingClientRect();
430
+ const aspect = containerRect && containerRect.height > 0 ? containerRect.width / containerRect.height : 16 / 9;
431
+ const defaultHeight = Math.round(width / aspect);
432
+ let height = prefs.height && prefs.height >= 100 ? prefs.height : defaultHeight;
433
+ height = Math.min(height, Math.max(100, vh - EDGE_MARGIN * 2));
434
+ let left;
435
+ let top;
436
+ if (typeof prefs.left === "number" && typeof prefs.top === "number") {
437
+ left = Math.max(EDGE_MARGIN, Math.min(prefs.left, vw - width - EDGE_MARGIN));
438
+ top = Math.max(EDGE_MARGIN, Math.min(prefs.top, vh - height - EDGE_MARGIN));
439
+ } else {
440
+ const pos = this.player.options.floatingPosition || "bottom-right";
441
+ switch (pos) {
442
+ case "bottom-left":
443
+ left = EDGE_MARGIN;
444
+ top = vh - height - EDGE_MARGIN;
445
+ break;
446
+ case "top-right":
447
+ left = vw - width - EDGE_MARGIN;
448
+ top = EDGE_MARGIN;
449
+ break;
450
+ case "top-left":
451
+ left = EDGE_MARGIN;
452
+ top = EDGE_MARGIN;
453
+ break;
454
+ case "bottom-right":
455
+ default:
456
+ left = vw - width - EDGE_MARGIN;
457
+ top = vh - height - EDGE_MARGIN;
458
+ break;
459
+ }
460
+ }
461
+ this.shell.style.width = `${width}px`;
462
+ this.shell.style.height = `${height}px`;
463
+ this.shell.style.left = `${left}px`;
464
+ this.shell.style.top = `${top}px`;
465
+ this._initDraggable();
466
+ }
467
+ _initDraggable() {
468
+ if (!this.shell) return;
469
+ if (this.draggable) return;
470
+ this.draggable = new DraggableResizable(this.shell, {
471
+ dragHandle: this.dragHandle,
472
+ resizeHandles: this.resizeHandles,
473
+ constrainToViewport: true,
474
+ maintainAspectRatio: true,
475
+ minWidth: MIN_WIDTH,
476
+ minHeight: 100,
477
+ maxWidth: () => Math.max(MIN_WIDTH, window.innerWidth - EDGE_MARGIN * 2),
478
+ maxHeight: () => Math.max(100, window.innerHeight - EDGE_MARGIN * 2),
479
+ classPrefix: `${this.classPrefix}-floating`,
480
+ keyboardDragKey: "d",
481
+ keyboardResizeKey: "r",
482
+ keyboardStep: 10,
483
+ keyboardStepLarge: 50,
484
+ pointerResizeIndicatorText: i18n.t("player.floatingPlayerDialog"),
485
+ onDragEnd: () => this._savePrefs(),
486
+ onResizeEnd: () => this._savePrefs(),
487
+ onDragStart: (event) => {
488
+ const target = event.target;
489
+ if (!target) return true;
490
+ if (target.closest(`.${this.classPrefix}-floating-close`)) return false;
491
+ if (target.closest(`.${this.classPrefix}-controls`)) return false;
492
+ if (target.closest(`.${this.classPrefix}-floating-resize-handle`)) return false;
493
+ return true;
494
+ }
495
+ });
496
+ }
497
+ _savePrefs() {
498
+ if (!this.shell || !this.player.storage?.saveFloatingPreferences) return;
499
+ const rect = this.shell.getBoundingClientRect();
500
+ this.player.storage.saveFloatingPreferences({
501
+ width: Math.round(rect.width),
502
+ height: Math.round(rect.height),
503
+ left: Math.round(rect.left),
504
+ top: Math.round(rect.top)
505
+ });
506
+ }
507
+ _activeElement() {
508
+ const active = document.activeElement;
509
+ return active && active instanceof HTMLElement ? active : null;
510
+ }
511
+ };
512
+ export {
513
+ FloatingPlayerManager
514
+ };
515
+ //# sourceMappingURL=vidply.FloatingPlayerManager-5EA4BYSR.js.map