vidply 1.1.4 → 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.
- package/README.md +51 -2
- package/dist/dev/vidply.FloatingPlayerManager-AEMEBLF7.js +514 -0
- package/dist/dev/vidply.FloatingPlayerManager-AEMEBLF7.js.map +7 -0
- package/dist/dev/vidply.esm.js +1 -1
- package/dist/legacy/vidply.js +0 -1
- package/dist/legacy/vidply.js.map +2 -2
- package/dist/legacy/vidply.min.js +1 -1
- package/dist/legacy/vidply.min.meta.json +3 -3
- package/dist/prod/vidply.FloatingPlayerManager-JVNKAK4N.min.js +1 -1
- package/dist/prod/vidply.FloatingPlayerManager-NRKSWDTL.min.js +6 -0
- package/dist/prod/vidply.esm.min.js +1 -1
- package/dist/vidply.esm.min.meta.json +5 -5
- package/package.json +1 -1
- package/src/core/FloatingPlayerManager.ts +6 -1
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@ A modern, feature-rich media player authored in strict TypeScript and shipped as
|
|
|
8
8
|

|
|
9
9
|

|
|
10
10
|

|
|
11
|
-

|
|
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,
|
|
@@ -0,0 +1,514 @@
|
|
|
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
|
+
"aria-hidden": "true"
|
|
345
|
+
}
|
|
346
|
+
}));
|
|
347
|
+
}
|
|
348
|
+
_teardownShell() {
|
|
349
|
+
if (this.draggable) {
|
|
350
|
+
try {
|
|
351
|
+
this.draggable.destroy();
|
|
352
|
+
} catch {
|
|
353
|
+
}
|
|
354
|
+
this.draggable = null;
|
|
355
|
+
}
|
|
356
|
+
if (this.shell) {
|
|
357
|
+
if (this._onKeyDown) {
|
|
358
|
+
this.shell.removeEventListener("keydown", this._onKeyDown);
|
|
359
|
+
this._onKeyDown = null;
|
|
360
|
+
}
|
|
361
|
+
if (this.shell.parentNode) {
|
|
362
|
+
this.shell.parentNode.removeChild(this.shell);
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
this.shell = null;
|
|
366
|
+
this.dragHandle = null;
|
|
367
|
+
this.closeButton = null;
|
|
368
|
+
this.resizeHandles = [];
|
|
369
|
+
}
|
|
370
|
+
// ---------------------------------------------------------------
|
|
371
|
+
// Internal: mount / unmount the player.container
|
|
372
|
+
// ---------------------------------------------------------------
|
|
373
|
+
_mountIntoShell() {
|
|
374
|
+
const container = this.player.container;
|
|
375
|
+
if (!container || !container.parentNode) return;
|
|
376
|
+
if (!this.shell) return;
|
|
377
|
+
const rect = container.getBoundingClientRect();
|
|
378
|
+
this.originalParent = container.parentNode;
|
|
379
|
+
this.originalNextSibling = container.nextSibling;
|
|
380
|
+
this.placeholder = DOMUtils.createElement("div", {
|
|
381
|
+
className: `${this.classPrefix}-floating-placeholder`,
|
|
382
|
+
attributes: { "aria-hidden": "true" }
|
|
383
|
+
});
|
|
384
|
+
this.placeholder.style.width = `${Math.max(1, rect.width)}px`;
|
|
385
|
+
this.placeholder.style.height = `${Math.max(1, rect.height)}px`;
|
|
386
|
+
this.originalParent.insertBefore(this.placeholder, container);
|
|
387
|
+
this.shell.appendChild(container);
|
|
388
|
+
document.body.appendChild(this.shell);
|
|
389
|
+
container.classList.add(`${this.classPrefix}-is-floating`);
|
|
390
|
+
this._retargetObserver(this.placeholder);
|
|
391
|
+
}
|
|
392
|
+
_unmountFromShell() {
|
|
393
|
+
const container = this.player.container;
|
|
394
|
+
if (container) {
|
|
395
|
+
container.classList.remove(`${this.classPrefix}-is-floating`);
|
|
396
|
+
container.style.removeProperty("width");
|
|
397
|
+
container.style.removeProperty("height");
|
|
398
|
+
}
|
|
399
|
+
if (this.placeholder && this.placeholder.parentNode) {
|
|
400
|
+
if (container) {
|
|
401
|
+
this.placeholder.parentNode.insertBefore(container, this.placeholder);
|
|
402
|
+
}
|
|
403
|
+
this.placeholder.parentNode.removeChild(this.placeholder);
|
|
404
|
+
} else if (container && this.originalParent) {
|
|
405
|
+
if (this.originalNextSibling && this.originalNextSibling.parentNode === this.originalParent) {
|
|
406
|
+
this.originalParent.insertBefore(container, this.originalNextSibling);
|
|
407
|
+
} else {
|
|
408
|
+
this.originalParent.appendChild(container);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
this.placeholder = null;
|
|
412
|
+
this.originalParent = null;
|
|
413
|
+
this.originalNextSibling = null;
|
|
414
|
+
if (container) {
|
|
415
|
+
this._retargetObserver(container);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
// ---------------------------------------------------------------
|
|
419
|
+
// Internal: initial geometry + drag/resize wiring
|
|
420
|
+
// ---------------------------------------------------------------
|
|
421
|
+
_applyInitialGeometry() {
|
|
422
|
+
if (!this.shell) return;
|
|
423
|
+
const prefs = this.player.storage?.getFloatingPreferences?.() || {};
|
|
424
|
+
const vw = window.innerWidth;
|
|
425
|
+
const vh = window.innerHeight;
|
|
426
|
+
let width = prefs.width && prefs.width >= MIN_WIDTH ? prefs.width : DEFAULT_WIDTH;
|
|
427
|
+
width = Math.min(width, Math.max(MIN_WIDTH, vw - EDGE_MARGIN * 2));
|
|
428
|
+
const containerRect = this.player.container?.getBoundingClientRect();
|
|
429
|
+
const aspect = containerRect && containerRect.height > 0 ? containerRect.width / containerRect.height : 16 / 9;
|
|
430
|
+
const defaultHeight = Math.round(width / aspect);
|
|
431
|
+
let height = prefs.height && prefs.height >= 100 ? prefs.height : defaultHeight;
|
|
432
|
+
height = Math.min(height, Math.max(100, vh - EDGE_MARGIN * 2));
|
|
433
|
+
let left;
|
|
434
|
+
let top;
|
|
435
|
+
if (typeof prefs.left === "number" && typeof prefs.top === "number") {
|
|
436
|
+
left = Math.max(EDGE_MARGIN, Math.min(prefs.left, vw - width - EDGE_MARGIN));
|
|
437
|
+
top = Math.max(EDGE_MARGIN, Math.min(prefs.top, vh - height - EDGE_MARGIN));
|
|
438
|
+
} else {
|
|
439
|
+
const pos = this.player.options.floatingPosition || "bottom-right";
|
|
440
|
+
switch (pos) {
|
|
441
|
+
case "bottom-left":
|
|
442
|
+
left = EDGE_MARGIN;
|
|
443
|
+
top = vh - height - EDGE_MARGIN;
|
|
444
|
+
break;
|
|
445
|
+
case "top-right":
|
|
446
|
+
left = vw - width - EDGE_MARGIN;
|
|
447
|
+
top = EDGE_MARGIN;
|
|
448
|
+
break;
|
|
449
|
+
case "top-left":
|
|
450
|
+
left = EDGE_MARGIN;
|
|
451
|
+
top = EDGE_MARGIN;
|
|
452
|
+
break;
|
|
453
|
+
case "bottom-right":
|
|
454
|
+
default:
|
|
455
|
+
left = vw - width - EDGE_MARGIN;
|
|
456
|
+
top = vh - height - EDGE_MARGIN;
|
|
457
|
+
break;
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
this.shell.style.width = `${width}px`;
|
|
461
|
+
this.shell.style.height = `${height}px`;
|
|
462
|
+
this.shell.style.left = `${left}px`;
|
|
463
|
+
this.shell.style.top = `${top}px`;
|
|
464
|
+
this._initDraggable();
|
|
465
|
+
}
|
|
466
|
+
_initDraggable() {
|
|
467
|
+
if (!this.shell) return;
|
|
468
|
+
if (this.draggable) return;
|
|
469
|
+
this.draggable = new DraggableResizable(this.shell, {
|
|
470
|
+
dragHandle: this.dragHandle,
|
|
471
|
+
resizeHandles: this.resizeHandles,
|
|
472
|
+
constrainToViewport: true,
|
|
473
|
+
maintainAspectRatio: true,
|
|
474
|
+
minWidth: MIN_WIDTH,
|
|
475
|
+
minHeight: 100,
|
|
476
|
+
maxWidth: () => Math.max(MIN_WIDTH, window.innerWidth - EDGE_MARGIN * 2),
|
|
477
|
+
maxHeight: () => Math.max(100, window.innerHeight - EDGE_MARGIN * 2),
|
|
478
|
+
classPrefix: `${this.classPrefix}-floating`,
|
|
479
|
+
keyboardDragKey: "d",
|
|
480
|
+
keyboardResizeKey: "r",
|
|
481
|
+
keyboardStep: 10,
|
|
482
|
+
keyboardStepLarge: 50,
|
|
483
|
+
pointerResizeIndicatorText: i18n.t("player.floatingPlayerDialog"),
|
|
484
|
+
onDragEnd: () => this._savePrefs(),
|
|
485
|
+
onResizeEnd: () => this._savePrefs(),
|
|
486
|
+
onDragStart: (event) => {
|
|
487
|
+
const target = event.target;
|
|
488
|
+
if (!target) return true;
|
|
489
|
+
if (target.closest(`.${this.classPrefix}-floating-close`)) return false;
|
|
490
|
+
if (target.closest(`.${this.classPrefix}-controls`)) return false;
|
|
491
|
+
if (target.closest(`.${this.classPrefix}-floating-resize-handle`)) return false;
|
|
492
|
+
return true;
|
|
493
|
+
}
|
|
494
|
+
});
|
|
495
|
+
}
|
|
496
|
+
_savePrefs() {
|
|
497
|
+
if (!this.shell || !this.player.storage?.saveFloatingPreferences) return;
|
|
498
|
+
const rect = this.shell.getBoundingClientRect();
|
|
499
|
+
this.player.storage.saveFloatingPreferences({
|
|
500
|
+
width: Math.round(rect.width),
|
|
501
|
+
height: Math.round(rect.height),
|
|
502
|
+
left: Math.round(rect.left),
|
|
503
|
+
top: Math.round(rect.top)
|
|
504
|
+
});
|
|
505
|
+
}
|
|
506
|
+
_activeElement() {
|
|
507
|
+
const active = document.activeElement;
|
|
508
|
+
return active && active instanceof HTMLElement ? active : null;
|
|
509
|
+
}
|
|
510
|
+
};
|
|
511
|
+
export {
|
|
512
|
+
FloatingPlayerManager
|
|
513
|
+
};
|
|
514
|
+
//# sourceMappingURL=vidply.FloatingPlayerManager-AEMEBLF7.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/core/FloatingPlayerManager.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Floating Player Manager\n *\n * Custom in-page Picture-in-Picture. Moves the entire player.container\n * into a position:fixed, draggable and resizable shell that lives outside\n * the document flow (appended to document.body). A transparent placeholder\n * preserves layout and, while floating, serves as the IntersectionObserver\n * sentinel so scroll-based auto-float/redock decisions stay anchored to\n * the original slot.\n *\n * Triggers:\n * - pinned: the user clicks the control-bar PiP button (only when\n * options.floating is true; otherwise the button uses native PiP).\n * Pinned floating ignores scroll-based docking.\n * - auto: the video is playing and the original slot has scrolled out\n * of the viewport. Auto-float redocks automatically when the slot\n * scrolls back in.\n *\n * Close button: pauses playback, exits the floating shell and marks the\n * current play session as dismissed so auto-float will not re-trigger\n * until the user presses play again (matches YouTube miniplayer UX).\n *\n * Only one player is allowed to float at a time across a page; enter()\n * broadcasts a 'vidply:floating-claim' CustomEvent that other managers\n * listen for and auto-exit on.\n */\n\nimport { DOMUtils } from '../utils/DOMUtils.js';\nimport { DraggableResizable } from '../utils/DraggableResizable.js';\nimport { createIconElement } from '../icons/Icons.js';\nimport { i18n } from '../i18n/i18n.js';\nimport type { Player } from './Player.js';\n\ntype FloatingState = 'pinned' | 'auto';\ntype ExitReason = 'manual' | 'auto' | 'dismiss' | 'claim' | 'destroy';\n\nconst FLOATING_CLAIM_EVENT = 'vidply:floating-claim';\nconst DEFAULT_WIDTH = 400;\nconst MIN_WIDTH = 240;\nconst EDGE_MARGIN = 16;\n\ninterface FloatingPrefs {\n width?: number;\n height?: number;\n left?: number;\n top?: number;\n}\n\nexport class FloatingPlayerManager {\n player: Player;\n classPrefix: string;\n\n shell: HTMLElement | null;\n dragHandle: HTMLElement | null;\n closeButton: HTMLButtonElement | null;\n resizeHandles: HTMLElement[];\n placeholder: HTMLElement | null;\n draggable: DraggableResizable | null;\n\n originalParent: HTMLElement | null;\n originalNextSibling: Node | null;\n\n intersectionObserver: IntersectionObserver | null;\n observerTarget: HTMLElement | null;\n lastRatio: number;\n\n _autoDismissedThisPlay: boolean;\n _playListenerAttached: boolean;\n _onPlayAfterDismiss: (() => void) | null;\n _onClaim: ((event: Event) => void) | null;\n _onResize: (() => void) | null;\n _onKeyDown: ((event: KeyboardEvent) => void) | null;\n _onEnterFullscreen: (() => void) | null;\n _destroyed: boolean;\n _triggerFocusEl: HTMLElement | null;\n _claimId: string;\n\n constructor(player: Player) {\n this.player = player;\n this.classPrefix = player.options.classPrefix || 'vidply';\n\n this.shell = null;\n this.dragHandle = null;\n this.closeButton = null;\n this.resizeHandles = [];\n this.placeholder = null;\n this.draggable = null;\n\n this.originalParent = null;\n this.originalNextSibling = null;\n\n this.intersectionObserver = null;\n this.observerTarget = null;\n this.lastRatio = 1;\n\n this._autoDismissedThisPlay = false;\n this._playListenerAttached = false;\n this._onPlayAfterDismiss = null;\n this._onClaim = null;\n this._onResize = null;\n this._onKeyDown = null;\n this._onEnterFullscreen = null;\n this._destroyed = false;\n this._triggerFocusEl = null;\n this._claimId = `floating-${player.instanceId}-${Date.now()}`;\n\n this._setupClaimListener();\n this._setupFullscreenGuard();\n this._startObserving();\n }\n\n // ---------------------------------------------------------------\n // Public API\n // ---------------------------------------------------------------\n\n togglePinned(triggerEl?: HTMLElement | null) {\n if (this._destroyed) return;\n if (this.player.state.floating === 'pinned') {\n // Manual unpin: suppress auto-float for the remainder of the\n // current play session so scrolling doesn't immediately re-pop\n // the floating shell. A fresh 'play' event clears the flag.\n this._autoDismissedThisPlay = true;\n this._armPlayListenerToClearDismiss();\n this.exit('manual');\n return;\n }\n // If the user pins while in 'auto' mode, that's an explicit request\n // to float; any previous auto-dismiss flag should be cleared so the\n // pinned state behaves naturally.\n this._autoDismissedThisPlay = false;\n this._triggerFocusEl = triggerEl || this._activeElement();\n this.enter('pinned');\n }\n\n enter(reason: FloatingState) {\n if (this._destroyed) return;\n if (this.player.state.floating === reason) return;\n\n if (!this._canFloat(reason)) {\n return;\n }\n\n // Upgrade 'auto' -> 'pinned' without rebuilding the shell\n if (this.player.state.floating && this.player.state.floating !== reason) {\n this.player.state.floating = reason;\n this.player.emit('floatingchange', reason);\n return;\n }\n\n this._claimSingleton();\n\n this._ensureShell();\n this._mountIntoShell();\n this._applyInitialGeometry();\n\n this.player.state.floating = reason;\n this.player.emit('floatingchange', reason);\n\n // Focus the close button after a microtask so screen readers announce\n // the dialog opening.\n queueMicrotask(() => {\n if (this.closeButton && this.player.state.floating) {\n try { this.closeButton.focus({ preventScroll: true }); } catch { /* ignore */ }\n }\n });\n }\n\n exit(reason: ExitReason = 'manual') {\n if (this._destroyed && reason !== 'destroy') return;\n if (!this.player.state.floating) return;\n\n this._unmountFromShell();\n this._teardownShell();\n\n const priorTrigger = this._triggerFocusEl;\n this._triggerFocusEl = null;\n\n this.player.state.floating = null;\n this.player.emit('floatingchange', null);\n\n // Restore focus to the element that triggered floating (pinned flow).\n // For scroll-based exits we leave focus untouched.\n if ((reason === 'manual' || reason === 'dismiss') && priorTrigger) {\n try { priorTrigger.focus({ preventScroll: true }); } catch { /* ignore */ }\n }\n }\n\n /**\n * Close button: pause, dismiss, and prevent auto-float until the next\n * user-initiated play event.\n */\n dismiss() {\n if (this._destroyed) return;\n this._autoDismissedThisPlay = true;\n this._armPlayListenerToClearDismiss();\n\n try {\n this.player.pause();\n } catch { /* ignore */ }\n\n this.exit('dismiss');\n }\n\n destroy() {\n if (this._destroyed) return;\n this._destroyed = true;\n\n if (this.player.state && this.player.state.floating) {\n try { this.exit('destroy'); } catch { /* ignore */ }\n }\n\n if (this.intersectionObserver) {\n try { this.intersectionObserver.disconnect(); } catch { /* ignore */ }\n this.intersectionObserver = null;\n }\n this.observerTarget = null;\n\n if (this._onClaim) {\n window.removeEventListener(FLOATING_CLAIM_EVENT, this._onClaim as EventListener);\n this._onClaim = null;\n }\n if (this._onResize) {\n window.removeEventListener('resize', this._onResize);\n this._onResize = null;\n }\n if (this._onEnterFullscreen) {\n this.player.off('enterfullscreen', this._onEnterFullscreen as any);\n this._onEnterFullscreen = null;\n }\n if (this._onPlayAfterDismiss && this._playListenerAttached) {\n this.player.off('play', this._onPlayAfterDismiss as any);\n this._playListenerAttached = false;\n this._onPlayAfterDismiss = null;\n }\n }\n\n // ---------------------------------------------------------------\n // Internal: guards\n // ---------------------------------------------------------------\n\n _canFloat(reason: FloatingState): boolean {\n if (!this.player.options.floating) return false;\n if (!this.player.container) return false;\n if (!this.player.element || this.player.element.tagName !== 'VIDEO') return false;\n if (this.player.state.fullscreen) return false;\n\n // Playlist players move through multiple tracks and manage their own\n // UI; floating support for playlists is out of scope for this v1.\n if (this.player.playlistManager) return false;\n\n const minWidth = this.player.options.floatingMinViewportWidth ?? 768;\n if (window.innerWidth < minWidth) return false;\n\n // Auto-float only engages when the user is actively watching.\n if (reason === 'auto') {\n if (this._autoDismissedThisPlay) return false;\n if (this.player.state.paused) return false;\n // Never auto-float before the user has started playback; stops\n // auto-entry from firing while the page is still below the fold\n // on initial load.\n if (!this.player.state.hasStartedPlayback) return false;\n }\n\n return true;\n }\n\n _claimSingleton() {\n try {\n window.dispatchEvent(new CustomEvent(FLOATING_CLAIM_EVENT, {\n detail: { claimId: this._claimId }\n }));\n } catch { /* ignore (older browsers) */ }\n }\n\n _setupClaimListener() {\n this._onClaim = (event: Event) => {\n const detail = (event as CustomEvent).detail;\n if (!detail || detail.claimId === this._claimId) return;\n if (this.player.state.floating) {\n this.exit('claim');\n }\n };\n window.addEventListener(FLOATING_CLAIM_EVENT, this._onClaim as EventListener);\n\n this._onResize = () => {\n const minWidth = this.player.options.floatingMinViewportWidth ?? 768;\n if (this.player.state.floating && window.innerWidth < minWidth) {\n this.exit('auto');\n }\n };\n window.addEventListener('resize', this._onResize);\n }\n\n _setupFullscreenGuard() {\n this._onEnterFullscreen = () => {\n if (this.player.state.floating) {\n this.exit('manual');\n }\n };\n this.player.on('enterfullscreen', this._onEnterFullscreen as any);\n }\n\n _armPlayListenerToClearDismiss() {\n if (this._playListenerAttached) return;\n this._onPlayAfterDismiss = () => {\n this._autoDismissedThisPlay = false;\n if (this._onPlayAfterDismiss) {\n this.player.off('play', this._onPlayAfterDismiss as any);\n }\n this._playListenerAttached = false;\n this._onPlayAfterDismiss = null;\n };\n this.player.on('play', this._onPlayAfterDismiss as any);\n this._playListenerAttached = true;\n }\n\n // ---------------------------------------------------------------\n // Internal: IntersectionObserver for scroll-triggered auto-float\n // ---------------------------------------------------------------\n\n _startObserving() {\n if (!('IntersectionObserver' in window)) return;\n if (!this.player.container) return;\n\n this.observerTarget = this.player.container;\n this.intersectionObserver = new IntersectionObserver((entries) => {\n const entry = entries[entries.length - 1];\n if (!entry) return;\n this.lastRatio = entry.intersectionRatio;\n\n if (this.player.options.debug) {\n try {\n console.log('[vidply:floating] intersection', {\n ratio: Number(entry.intersectionRatio.toFixed(3)),\n state: this.player.state.floating,\n paused: this.player.state.paused,\n hasStartedPlayback: this.player.state.hasStartedPlayback,\n dismissed: this._autoDismissedThisPlay\n });\n } catch { /* ignore */ }\n }\n\n if (this.player.state.floating === 'auto') {\n if (entry.intersectionRatio > 0.5) {\n this.exit('auto');\n }\n return;\n }\n\n // Pinned state: scroll is a no-op, the user has taken control.\n if (this.player.state.floating === 'pinned') {\n return;\n }\n\n if (entry.intersectionRatio < 0.1 && this._canFloat('auto')) {\n this.enter('auto');\n }\n }, { threshold: [0, 0.1, 0.5, 0.9] });\n\n this.intersectionObserver.observe(this.observerTarget);\n }\n\n _retargetObserver(target: HTMLElement) {\n if (!this.intersectionObserver) return;\n if (this.observerTarget) {\n try { this.intersectionObserver.unobserve(this.observerTarget); } catch { /* ignore */ }\n }\n this.observerTarget = target;\n try { this.intersectionObserver.observe(target); } catch { /* ignore */ }\n }\n\n // ---------------------------------------------------------------\n // Internal: shell DOM\n // ---------------------------------------------------------------\n\n _ensureShell() {\n if (this.shell) return;\n\n this.shell = DOMUtils.createElement('div', {\n className: `${this.classPrefix}-floating-shell`,\n attributes: {\n 'role': 'dialog',\n 'aria-modal': 'false',\n 'aria-label': i18n.t('player.floatingPlayer'),\n 'data-vidply-floating': 'true',\n 'tabindex': '-1'\n }\n });\n\n this.dragHandle = DOMUtils.createElement('div', {\n className: `${this.classPrefix}-floating-drag-handle`,\n attributes: { 'aria-hidden': 'true' }\n });\n this.shell.appendChild(this.dragHandle);\n\n this.closeButton = DOMUtils.createElement('button', {\n className: `${this.classPrefix}-floating-close`,\n attributes: {\n 'type': 'button',\n 'aria-label': i18n.t('player.floatingPlayerClose'),\n 'title': i18n.t('player.floatingPlayerClose')\n }\n }) as HTMLButtonElement;\n this.closeButton.appendChild(createIconElement('close'));\n this.closeButton.addEventListener('click', (event) => {\n event.stopPropagation();\n this.dismiss();\n });\n this.shell.appendChild(this.closeButton);\n\n this._createResizeHandles();\n this.resizeHandles.forEach(handle => this.shell!.appendChild(handle));\n\n this._onKeyDown = (event: KeyboardEvent) => {\n if (event.key === 'Escape') {\n event.stopPropagation();\n this.dismiss();\n }\n };\n this.shell.addEventListener('keydown', this._onKeyDown as EventListener);\n }\n\n _createResizeHandles() {\n // Resize handles are always-on for the floating shell. Do NOT set\n // `data-vidply-managed-resize=\"true\"`: that flag is used by the\n // transcript / sign-language overlays to gate the handles behind\n // an \"R\" keypress, and DraggableResizable hides managed handles\n // during init() — which would leave the shell with no visible or\n // hittable edges/corners.\n const dirs = ['n', 's', 'e', 'w', 'ne', 'nw', 'se', 'sw'];\n this.resizeHandles = dirs.map(dir => DOMUtils.createElement('div', {\n className: `${this.classPrefix}-floating-resize-handle ${this.classPrefix}-floating-resize-${dir}`,\n attributes: {\n 'data-direction': dir,\n 'aria-hidden': 'true'\n }\n }));\n }\n\n _teardownShell() {\n if (this.draggable) {\n try { this.draggable.destroy(); } catch { /* ignore */ }\n this.draggable = null;\n }\n if (this.shell) {\n if (this._onKeyDown) {\n this.shell.removeEventListener('keydown', this._onKeyDown as EventListener);\n this._onKeyDown = null;\n }\n if (this.shell.parentNode) {\n this.shell.parentNode.removeChild(this.shell);\n }\n }\n this.shell = null;\n this.dragHandle = null;\n this.closeButton = null;\n this.resizeHandles = [];\n }\n\n // ---------------------------------------------------------------\n // Internal: mount / unmount the player.container\n // ---------------------------------------------------------------\n\n _mountIntoShell() {\n const container = this.player.container;\n if (!container || !container.parentNode) return;\n if (!this.shell) return;\n\n const rect = container.getBoundingClientRect();\n this.originalParent = container.parentNode as HTMLElement;\n this.originalNextSibling = container.nextSibling;\n\n this.placeholder = DOMUtils.createElement('div', {\n className: `${this.classPrefix}-floating-placeholder`,\n attributes: { 'aria-hidden': 'true' }\n });\n this.placeholder.style.width = `${Math.max(1, rect.width)}px`;\n this.placeholder.style.height = `${Math.max(1, rect.height)}px`;\n\n this.originalParent.insertBefore(this.placeholder, container);\n\n this.shell.appendChild(container);\n document.body.appendChild(this.shell);\n\n container.classList.add(`${this.classPrefix}-is-floating`);\n\n // Observe the placeholder while floating so scroll-back decisions are\n // anchored to the original slot instead of the moved container.\n this._retargetObserver(this.placeholder);\n }\n\n _unmountFromShell() {\n const container = this.player.container;\n\n if (container) {\n container.classList.remove(`${this.classPrefix}-is-floating`);\n // Clear inline sizes applied while floating\n container.style.removeProperty('width');\n container.style.removeProperty('height');\n }\n\n if (this.placeholder && this.placeholder.parentNode) {\n if (container) {\n this.placeholder.parentNode.insertBefore(container, this.placeholder);\n }\n this.placeholder.parentNode.removeChild(this.placeholder);\n } else if (container && this.originalParent) {\n if (this.originalNextSibling && this.originalNextSibling.parentNode === this.originalParent) {\n this.originalParent.insertBefore(container, this.originalNextSibling);\n } else {\n this.originalParent.appendChild(container);\n }\n }\n\n this.placeholder = null;\n this.originalParent = null;\n this.originalNextSibling = null;\n\n // Retarget the observer to the docked container\n if (container) {\n this._retargetObserver(container);\n }\n }\n\n // ---------------------------------------------------------------\n // Internal: initial geometry + drag/resize wiring\n // ---------------------------------------------------------------\n\n _applyInitialGeometry() {\n if (!this.shell) return;\n\n const prefs = (this.player.storage?.getFloatingPreferences?.() || {}) as FloatingPrefs;\n\n const vw = window.innerWidth;\n const vh = window.innerHeight;\n\n let width = prefs.width && prefs.width >= MIN_WIDTH ? prefs.width : DEFAULT_WIDTH;\n width = Math.min(width, Math.max(MIN_WIDTH, vw - EDGE_MARGIN * 2));\n\n // If we know the source container's aspect ratio, match it by default.\n const containerRect = this.player.container?.getBoundingClientRect();\n const aspect = containerRect && containerRect.height > 0\n ? containerRect.width / containerRect.height\n : 16 / 9;\n const defaultHeight = Math.round(width / aspect);\n let height = prefs.height && prefs.height >= 100 ? prefs.height : defaultHeight;\n height = Math.min(height, Math.max(100, vh - EDGE_MARGIN * 2));\n\n let left: number;\n let top: number;\n\n if (typeof prefs.left === 'number' && typeof prefs.top === 'number') {\n left = Math.max(EDGE_MARGIN, Math.min(prefs.left, vw - width - EDGE_MARGIN));\n top = Math.max(EDGE_MARGIN, Math.min(prefs.top, vh - height - EDGE_MARGIN));\n } else {\n const pos = this.player.options.floatingPosition || 'bottom-right';\n switch (pos) {\n case 'bottom-left':\n left = EDGE_MARGIN;\n top = vh - height - EDGE_MARGIN;\n break;\n case 'top-right':\n left = vw - width - EDGE_MARGIN;\n top = EDGE_MARGIN;\n break;\n case 'top-left':\n left = EDGE_MARGIN;\n top = EDGE_MARGIN;\n break;\n case 'bottom-right':\n default:\n left = vw - width - EDGE_MARGIN;\n top = vh - height - EDGE_MARGIN;\n break;\n }\n }\n\n this.shell.style.width = `${width}px`;\n this.shell.style.height = `${height}px`;\n this.shell.style.left = `${left}px`;\n this.shell.style.top = `${top}px`;\n\n this._initDraggable();\n }\n\n _initDraggable() {\n if (!this.shell) return;\n if (this.draggable) return;\n\n this.draggable = new DraggableResizable(this.shell, {\n dragHandle: this.dragHandle,\n resizeHandles: this.resizeHandles,\n constrainToViewport: true,\n maintainAspectRatio: true,\n minWidth: MIN_WIDTH,\n minHeight: 100,\n maxWidth: () => Math.max(MIN_WIDTH, window.innerWidth - EDGE_MARGIN * 2),\n maxHeight: () => Math.max(100, window.innerHeight - EDGE_MARGIN * 2),\n classPrefix: `${this.classPrefix}-floating`,\n keyboardDragKey: 'd',\n keyboardResizeKey: 'r',\n keyboardStep: 10,\n keyboardStepLarge: 50,\n pointerResizeIndicatorText: i18n.t('player.floatingPlayerDialog'),\n onDragEnd: () => this._savePrefs(),\n onResizeEnd: () => this._savePrefs(),\n onDragStart: (event: Event) => {\n const target = event.target as HTMLElement;\n if (!target) return true;\n // Don't start drag when interacting with the close button or\n // with any control inside the embedded player (play/pause,\n // captions menu, etc.).\n if (target.closest(`.${this.classPrefix}-floating-close`)) return false;\n if (target.closest(`.${this.classPrefix}-controls`)) return false;\n if (target.closest(`.${this.classPrefix}-floating-resize-handle`)) return false;\n return true;\n }\n });\n }\n\n _savePrefs() {\n if (!this.shell || !this.player.storage?.saveFloatingPreferences) return;\n const rect = this.shell.getBoundingClientRect();\n this.player.storage.saveFloatingPreferences({\n width: Math.round(rect.width),\n height: Math.round(rect.height),\n left: Math.round(rect.left),\n top: Math.round(rect.top)\n });\n }\n\n _activeElement(): HTMLElement | null {\n const active = document.activeElement;\n return active && active instanceof HTMLElement ? active : null;\n }\n}\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;AAoCA,IAAM,uBAAuB;AAC7B,IAAM,gBAAgB;AACtB,IAAM,YAAY;AAClB,IAAM,cAAc;AASb,IAAM,wBAAN,MAA4B;AAAA,EAC/B;AAAA,EACA;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAEA;AAAA,EACA;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAEA,YAAY,QAAgB;AACxB,SAAK,SAAS;AACd,SAAK,cAAc,OAAO,QAAQ,eAAe;AAEjD,SAAK,QAAQ;AACb,SAAK,aAAa;AAClB,SAAK,cAAc;AACnB,SAAK,gBAAgB,CAAC;AACtB,SAAK,cAAc;AACnB,SAAK,YAAY;AAEjB,SAAK,iBAAiB;AACtB,SAAK,sBAAsB;AAE3B,SAAK,uBAAuB;AAC5B,SAAK,iBAAiB;AACtB,SAAK,YAAY;AAEjB,SAAK,yBAAyB;AAC9B,SAAK,wBAAwB;AAC7B,SAAK,sBAAsB;AAC3B,SAAK,WAAW;AAChB,SAAK,YAAY;AACjB,SAAK,aAAa;AAClB,SAAK,qBAAqB;AAC1B,SAAK,aAAa;AAClB,SAAK,kBAAkB;AACvB,SAAK,WAAW,YAAY,OAAO,UAAU,IAAI,KAAK,IAAI,CAAC;AAE3D,SAAK,oBAAoB;AACzB,SAAK,sBAAsB;AAC3B,SAAK,gBAAgB;AAAA,EACzB;AAAA;AAAA;AAAA;AAAA,EAMA,aAAa,WAAgC;AACzC,QAAI,KAAK,WAAY;AACrB,QAAI,KAAK,OAAO,MAAM,aAAa,UAAU;AAIzC,WAAK,yBAAyB;AAC9B,WAAK,+BAA+B;AACpC,WAAK,KAAK,QAAQ;AAClB;AAAA,IACJ;AAIA,SAAK,yBAAyB;AAC9B,SAAK,kBAAkB,aAAa,KAAK,eAAe;AACxD,SAAK,MAAM,QAAQ;AAAA,EACvB;AAAA,EAEA,MAAM,QAAuB;AACzB,QAAI,KAAK,WAAY;AACrB,QAAI,KAAK,OAAO,MAAM,aAAa,OAAQ;AAE3C,QAAI,CAAC,KAAK,UAAU,MAAM,GAAG;AACzB;AAAA,IACJ;AAGA,QAAI,KAAK,OAAO,MAAM,YAAY,KAAK,OAAO,MAAM,aAAa,QAAQ;AACrE,WAAK,OAAO,MAAM,WAAW;AAC7B,WAAK,OAAO,KAAK,kBAAkB,MAAM;AACzC;AAAA,IACJ;AAEA,SAAK,gBAAgB;AAErB,SAAK,aAAa;AAClB,SAAK,gBAAgB;AACrB,SAAK,sBAAsB;AAE3B,SAAK,OAAO,MAAM,WAAW;AAC7B,SAAK,OAAO,KAAK,kBAAkB,MAAM;AAIzC,mBAAe,MAAM;AACjB,UAAI,KAAK,eAAe,KAAK,OAAO,MAAM,UAAU;AAChD,YAAI;AAAE,eAAK,YAAY,MAAM,EAAE,eAAe,KAAK,CAAC;AAAA,QAAG,QAAQ;AAAA,QAAe;AAAA,MAClF;AAAA,IACJ,CAAC;AAAA,EACL;AAAA,EAEA,KAAK,SAAqB,UAAU;AAChC,QAAI,KAAK,cAAc,WAAW,UAAW;AAC7C,QAAI,CAAC,KAAK,OAAO,MAAM,SAAU;AAEjC,SAAK,kBAAkB;AACvB,SAAK,eAAe;AAEpB,UAAM,eAAe,KAAK;AAC1B,SAAK,kBAAkB;AAEvB,SAAK,OAAO,MAAM,WAAW;AAC7B,SAAK,OAAO,KAAK,kBAAkB,IAAI;AAIvC,SAAK,WAAW,YAAY,WAAW,cAAc,cAAc;AAC/D,UAAI;AAAE,qBAAa,MAAM,EAAE,eAAe,KAAK,CAAC;AAAA,MAAG,QAAQ;AAAA,MAAe;AAAA,IAC9E;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,UAAU;AACN,QAAI,KAAK,WAAY;AACrB,SAAK,yBAAyB;AAC9B,SAAK,+BAA+B;AAEpC,QAAI;AACA,WAAK,OAAO,MAAM;AAAA,IACtB,QAAQ;AAAA,IAAe;AAEvB,SAAK,KAAK,SAAS;AAAA,EACvB;AAAA,EAEA,UAAU;AACN,QAAI,KAAK,WAAY;AACrB,SAAK,aAAa;AAElB,QAAI,KAAK,OAAO,SAAS,KAAK,OAAO,MAAM,UAAU;AACjD,UAAI;AAAE,aAAK,KAAK,SAAS;AAAA,MAAG,QAAQ;AAAA,MAAe;AAAA,IACvD;AAEA,QAAI,KAAK,sBAAsB;AAC3B,UAAI;AAAE,aAAK,qBAAqB,WAAW;AAAA,MAAG,QAAQ;AAAA,MAAe;AACrE,WAAK,uBAAuB;AAAA,IAChC;AACA,SAAK,iBAAiB;AAEtB,QAAI,KAAK,UAAU;AACf,aAAO,oBAAoB,sBAAsB,KAAK,QAAyB;AAC/E,WAAK,WAAW;AAAA,IACpB;AACA,QAAI,KAAK,WAAW;AAChB,aAAO,oBAAoB,UAAU,KAAK,SAAS;AACnD,WAAK,YAAY;AAAA,IACrB;AACA,QAAI,KAAK,oBAAoB;AACzB,WAAK,OAAO,IAAI,mBAAmB,KAAK,kBAAyB;AACjE,WAAK,qBAAqB;AAAA,IAC9B;AACA,QAAI,KAAK,uBAAuB,KAAK,uBAAuB;AACxD,WAAK,OAAO,IAAI,QAAQ,KAAK,mBAA0B;AACvD,WAAK,wBAAwB;AAC7B,WAAK,sBAAsB;AAAA,IAC/B;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA,EAMA,UAAU,QAAgC;AACtC,QAAI,CAAC,KAAK,OAAO,QAAQ,SAAU,QAAO;AAC1C,QAAI,CAAC,KAAK,OAAO,UAAW,QAAO;AACnC,QAAI,CAAC,KAAK,OAAO,WAAW,KAAK,OAAO,QAAQ,YAAY,QAAS,QAAO;AAC5E,QAAI,KAAK,OAAO,MAAM,WAAY,QAAO;AAIzC,QAAI,KAAK,OAAO,gBAAiB,QAAO;AAExC,UAAM,WAAW,KAAK,OAAO,QAAQ,4BAA4B;AACjE,QAAI,OAAO,aAAa,SAAU,QAAO;AAGzC,QAAI,WAAW,QAAQ;AACnB,UAAI,KAAK,uBAAwB,QAAO;AACxC,UAAI,KAAK,OAAO,MAAM,OAAQ,QAAO;AAIrC,UAAI,CAAC,KAAK,OAAO,MAAM,mBAAoB,QAAO;AAAA,IACtD;AAEA,WAAO;AAAA,EACX;AAAA,EAEA,kBAAkB;AACd,QAAI;AACA,aAAO,cAAc,IAAI,YAAY,sBAAsB;AAAA,QACvD,QAAQ,EAAE,SAAS,KAAK,SAAS;AAAA,MACrC,CAAC,CAAC;AAAA,IACN,QAAQ;AAAA,IAAgC;AAAA,EAC5C;AAAA,EAEA,sBAAsB;AAClB,SAAK,WAAW,CAAC,UAAiB;AAC9B,YAAM,SAAU,MAAsB;AACtC,UAAI,CAAC,UAAU,OAAO,YAAY,KAAK,SAAU;AACjD,UAAI,KAAK,OAAO,MAAM,UAAU;AAC5B,aAAK,KAAK,OAAO;AAAA,MACrB;AAAA,IACJ;AACA,WAAO,iBAAiB,sBAAsB,KAAK,QAAyB;AAE5E,SAAK,YAAY,MAAM;AACnB,YAAM,WAAW,KAAK,OAAO,QAAQ,4BAA4B;AACjE,UAAI,KAAK,OAAO,MAAM,YAAY,OAAO,aAAa,UAAU;AAC5D,aAAK,KAAK,MAAM;AAAA,MACpB;AAAA,IACJ;AACA,WAAO,iBAAiB,UAAU,KAAK,SAAS;AAAA,EACpD;AAAA,EAEA,wBAAwB;AACpB,SAAK,qBAAqB,MAAM;AAC5B,UAAI,KAAK,OAAO,MAAM,UAAU;AAC5B,aAAK,KAAK,QAAQ;AAAA,MACtB;AAAA,IACJ;AACA,SAAK,OAAO,GAAG,mBAAmB,KAAK,kBAAyB;AAAA,EACpE;AAAA,EAEA,iCAAiC;AAC7B,QAAI,KAAK,sBAAuB;AAChC,SAAK,sBAAsB,MAAM;AAC7B,WAAK,yBAAyB;AAC9B,UAAI,KAAK,qBAAqB;AAC1B,aAAK,OAAO,IAAI,QAAQ,KAAK,mBAA0B;AAAA,MAC3D;AACA,WAAK,wBAAwB;AAC7B,WAAK,sBAAsB;AAAA,IAC/B;AACA,SAAK,OAAO,GAAG,QAAQ,KAAK,mBAA0B;AACtD,SAAK,wBAAwB;AAAA,EACjC;AAAA;AAAA;AAAA;AAAA,EAMA,kBAAkB;AACd,QAAI,EAAE,0BAA0B,QAAS;AACzC,QAAI,CAAC,KAAK,OAAO,UAAW;AAE5B,SAAK,iBAAiB,KAAK,OAAO;AAClC,SAAK,uBAAuB,IAAI,qBAAqB,CAAC,YAAY;AAC9D,YAAM,QAAQ,QAAQ,QAAQ,SAAS,CAAC;AACxC,UAAI,CAAC,MAAO;AACZ,WAAK,YAAY,MAAM;AAEvB,UAAI,KAAK,OAAO,QAAQ,OAAO;AAC3B,YAAI;AACA,kBAAQ,IAAI,kCAAkC;AAAA,YAC1C,OAAO,OAAO,MAAM,kBAAkB,QAAQ,CAAC,CAAC;AAAA,YAChD,OAAO,KAAK,OAAO,MAAM;AAAA,YACzB,QAAQ,KAAK,OAAO,MAAM;AAAA,YAC1B,oBAAoB,KAAK,OAAO,MAAM;AAAA,YACtC,WAAW,KAAK;AAAA,UACpB,CAAC;AAAA,QACL,QAAQ;AAAA,QAAe;AAAA,MAC3B;AAEA,UAAI,KAAK,OAAO,MAAM,aAAa,QAAQ;AACvC,YAAI,MAAM,oBAAoB,KAAK;AAC/B,eAAK,KAAK,MAAM;AAAA,QACpB;AACA;AAAA,MACJ;AAGA,UAAI,KAAK,OAAO,MAAM,aAAa,UAAU;AACzC;AAAA,MACJ;AAEA,UAAI,MAAM,oBAAoB,OAAO,KAAK,UAAU,MAAM,GAAG;AACzD,aAAK,MAAM,MAAM;AAAA,MACrB;AAAA,IACJ,GAAG,EAAE,WAAW,CAAC,GAAG,KAAK,KAAK,GAAG,EAAE,CAAC;AAEpC,SAAK,qBAAqB,QAAQ,KAAK,cAAc;AAAA,EACzD;AAAA,EAEA,kBAAkB,QAAqB;AACnC,QAAI,CAAC,KAAK,qBAAsB;AAChC,QAAI,KAAK,gBAAgB;AACrB,UAAI;AAAE,aAAK,qBAAqB,UAAU,KAAK,cAAc;AAAA,MAAG,QAAQ;AAAA,MAAe;AAAA,IAC3F;AACA,SAAK,iBAAiB;AACtB,QAAI;AAAE,WAAK,qBAAqB,QAAQ,MAAM;AAAA,IAAG,QAAQ;AAAA,IAAe;AAAA,EAC5E;AAAA;AAAA;AAAA;AAAA,EAMA,eAAe;AACX,QAAI,KAAK,MAAO;AAEhB,SAAK,QAAQ,SAAS,cAAc,OAAO;AAAA,MACvC,WAAW,GAAG,KAAK,WAAW;AAAA,MAC9B,YAAY;AAAA,QACR,QAAQ;AAAA,QACR,cAAc;AAAA,QACd,cAAc,KAAK,EAAE,uBAAuB;AAAA,QAC5C,wBAAwB;AAAA,QACxB,YAAY;AAAA,MAChB;AAAA,IACJ,CAAC;AAED,SAAK,aAAa,SAAS,cAAc,OAAO;AAAA,MAC5C,WAAW,GAAG,KAAK,WAAW;AAAA,MAC9B,YAAY,EAAE,eAAe,OAAO;AAAA,IACxC,CAAC;AACD,SAAK,MAAM,YAAY,KAAK,UAAU;AAEtC,SAAK,cAAc,SAAS,cAAc,UAAU;AAAA,MAChD,WAAW,GAAG,KAAK,WAAW;AAAA,MAC9B,YAAY;AAAA,QACR,QAAQ;AAAA,QACR,cAAc,KAAK,EAAE,4BAA4B;AAAA,QACjD,SAAS,KAAK,EAAE,4BAA4B;AAAA,MAChD;AAAA,IACJ,CAAC;AACD,SAAK,YAAY,YAAY,kBAAkB,OAAO,CAAC;AACvD,SAAK,YAAY,iBAAiB,SAAS,CAAC,UAAU;AAClD,YAAM,gBAAgB;AACtB,WAAK,QAAQ;AAAA,IACjB,CAAC;AACD,SAAK,MAAM,YAAY,KAAK,WAAW;AAEvC,SAAK,qBAAqB;AAC1B,SAAK,cAAc,QAAQ,YAAU,KAAK,MAAO,YAAY,MAAM,CAAC;AAEpE,SAAK,aAAa,CAAC,UAAyB;AACxC,UAAI,MAAM,QAAQ,UAAU;AACxB,cAAM,gBAAgB;AACtB,aAAK,QAAQ;AAAA,MACjB;AAAA,IACJ;AACA,SAAK,MAAM,iBAAiB,WAAW,KAAK,UAA2B;AAAA,EAC3E;AAAA,EAEA,uBAAuB;AAOnB,UAAM,OAAO,CAAC,KAAK,KAAK,KAAK,KAAK,MAAM,MAAM,MAAM,IAAI;AACxD,SAAK,gBAAgB,KAAK,IAAI,SAAO,SAAS,cAAc,OAAO;AAAA,MAC/D,WAAW,GAAG,KAAK,WAAW,2BAA2B,KAAK,WAAW,oBAAoB,GAAG;AAAA,MAChG,YAAY;AAAA,QACR,kBAAkB;AAAA,QAClB,eAAe;AAAA,MACnB;AAAA,IACJ,CAAC,CAAC;AAAA,EACN;AAAA,EAEA,iBAAiB;AACb,QAAI,KAAK,WAAW;AAChB,UAAI;AAAE,aAAK,UAAU,QAAQ;AAAA,MAAG,QAAQ;AAAA,MAAe;AACvD,WAAK,YAAY;AAAA,IACrB;AACA,QAAI,KAAK,OAAO;AACZ,UAAI,KAAK,YAAY;AACjB,aAAK,MAAM,oBAAoB,WAAW,KAAK,UAA2B;AAC1E,aAAK,aAAa;AAAA,MACtB;AACA,UAAI,KAAK,MAAM,YAAY;AACvB,aAAK,MAAM,WAAW,YAAY,KAAK,KAAK;AAAA,MAChD;AAAA,IACJ;AACA,SAAK,QAAQ;AACb,SAAK,aAAa;AAClB,SAAK,cAAc;AACnB,SAAK,gBAAgB,CAAC;AAAA,EAC1B;AAAA;AAAA;AAAA;AAAA,EAMA,kBAAkB;AACd,UAAM,YAAY,KAAK,OAAO;AAC9B,QAAI,CAAC,aAAa,CAAC,UAAU,WAAY;AACzC,QAAI,CAAC,KAAK,MAAO;AAEjB,UAAM,OAAO,UAAU,sBAAsB;AAC7C,SAAK,iBAAiB,UAAU;AAChC,SAAK,sBAAsB,UAAU;AAErC,SAAK,cAAc,SAAS,cAAc,OAAO;AAAA,MAC7C,WAAW,GAAG,KAAK,WAAW;AAAA,MAC9B,YAAY,EAAE,eAAe,OAAO;AAAA,IACxC,CAAC;AACD,SAAK,YAAY,MAAM,QAAQ,GAAG,KAAK,IAAI,GAAG,KAAK,KAAK,CAAC;AACzD,SAAK,YAAY,MAAM,SAAS,GAAG,KAAK,IAAI,GAAG,KAAK,MAAM,CAAC;AAE3D,SAAK,eAAe,aAAa,KAAK,aAAa,SAAS;AAE5D,SAAK,MAAM,YAAY,SAAS;AAChC,aAAS,KAAK,YAAY,KAAK,KAAK;AAEpC,cAAU,UAAU,IAAI,GAAG,KAAK,WAAW,cAAc;AAIzD,SAAK,kBAAkB,KAAK,WAAW;AAAA,EAC3C;AAAA,EAEA,oBAAoB;AAChB,UAAM,YAAY,KAAK,OAAO;AAE9B,QAAI,WAAW;AACX,gBAAU,UAAU,OAAO,GAAG,KAAK,WAAW,cAAc;AAE5D,gBAAU,MAAM,eAAe,OAAO;AACtC,gBAAU,MAAM,eAAe,QAAQ;AAAA,IAC3C;AAEA,QAAI,KAAK,eAAe,KAAK,YAAY,YAAY;AACjD,UAAI,WAAW;AACX,aAAK,YAAY,WAAW,aAAa,WAAW,KAAK,WAAW;AAAA,MACxE;AACA,WAAK,YAAY,WAAW,YAAY,KAAK,WAAW;AAAA,IAC5D,WAAW,aAAa,KAAK,gBAAgB;AACzC,UAAI,KAAK,uBAAuB,KAAK,oBAAoB,eAAe,KAAK,gBAAgB;AACzF,aAAK,eAAe,aAAa,WAAW,KAAK,mBAAmB;AAAA,MACxE,OAAO;AACH,aAAK,eAAe,YAAY,SAAS;AAAA,MAC7C;AAAA,IACJ;AAEA,SAAK,cAAc;AACnB,SAAK,iBAAiB;AACtB,SAAK,sBAAsB;AAG3B,QAAI,WAAW;AACX,WAAK,kBAAkB,SAAS;AAAA,IACpC;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA,EAMA,wBAAwB;AACpB,QAAI,CAAC,KAAK,MAAO;AAEjB,UAAM,QAAS,KAAK,OAAO,SAAS,yBAAyB,KAAK,CAAC;AAEnE,UAAM,KAAK,OAAO;AAClB,UAAM,KAAK,OAAO;AAElB,QAAI,QAAQ,MAAM,SAAS,MAAM,SAAS,YAAY,MAAM,QAAQ;AACpE,YAAQ,KAAK,IAAI,OAAO,KAAK,IAAI,WAAW,KAAK,cAAc,CAAC,CAAC;AAGjE,UAAM,gBAAgB,KAAK,OAAO,WAAW,sBAAsB;AACnE,UAAM,SAAS,iBAAiB,cAAc,SAAS,IACjD,cAAc,QAAQ,cAAc,SACpC,KAAK;AACX,UAAM,gBAAgB,KAAK,MAAM,QAAQ,MAAM;AAC/C,QAAI,SAAS,MAAM,UAAU,MAAM,UAAU,MAAM,MAAM,SAAS;AAClE,aAAS,KAAK,IAAI,QAAQ,KAAK,IAAI,KAAK,KAAK,cAAc,CAAC,CAAC;AAE7D,QAAI;AACJ,QAAI;AAEJ,QAAI,OAAO,MAAM,SAAS,YAAY,OAAO,MAAM,QAAQ,UAAU;AACjE,aAAO,KAAK,IAAI,aAAa,KAAK,IAAI,MAAM,MAAM,KAAK,QAAQ,WAAW,CAAC;AAC3E,YAAM,KAAK,IAAI,aAAa,KAAK,IAAI,MAAM,KAAK,KAAK,SAAS,WAAW,CAAC;AAAA,IAC9E,OAAO;AACH,YAAM,MAAM,KAAK,OAAO,QAAQ,oBAAoB;AACpD,cAAQ,KAAK;AAAA,QACT,KAAK;AACD,iBAAO;AACP,gBAAM,KAAK,SAAS;AACpB;AAAA,QACJ,KAAK;AACD,iBAAO,KAAK,QAAQ;AACpB,gBAAM;AACN;AAAA,QACJ,KAAK;AACD,iBAAO;AACP,gBAAM;AACN;AAAA,QACJ,KAAK;AAAA,QACL;AACI,iBAAO,KAAK,QAAQ;AACpB,gBAAM,KAAK,SAAS;AACpB;AAAA,MACR;AAAA,IACJ;AAEA,SAAK,MAAM,MAAM,QAAQ,GAAG,KAAK;AACjC,SAAK,MAAM,MAAM,SAAS,GAAG,MAAM;AACnC,SAAK,MAAM,MAAM,OAAO,GAAG,IAAI;AAC/B,SAAK,MAAM,MAAM,MAAM,GAAG,GAAG;AAE7B,SAAK,eAAe;AAAA,EACxB;AAAA,EAEA,iBAAiB;AACb,QAAI,CAAC,KAAK,MAAO;AACjB,QAAI,KAAK,UAAW;AAEpB,SAAK,YAAY,IAAI,mBAAmB,KAAK,OAAO;AAAA,MAChD,YAAY,KAAK;AAAA,MACjB,eAAe,KAAK;AAAA,MACpB,qBAAqB;AAAA,MACrB,qBAAqB;AAAA,MACrB,UAAU;AAAA,MACV,WAAW;AAAA,MACX,UAAU,MAAM,KAAK,IAAI,WAAW,OAAO,aAAa,cAAc,CAAC;AAAA,MACvE,WAAW,MAAM,KAAK,IAAI,KAAK,OAAO,cAAc,cAAc,CAAC;AAAA,MACnE,aAAa,GAAG,KAAK,WAAW;AAAA,MAChC,iBAAiB;AAAA,MACjB,mBAAmB;AAAA,MACnB,cAAc;AAAA,MACd,mBAAmB;AAAA,MACnB,4BAA4B,KAAK,EAAE,6BAA6B;AAAA,MAChE,WAAW,MAAM,KAAK,WAAW;AAAA,MACjC,aAAa,MAAM,KAAK,WAAW;AAAA,MACnC,aAAa,CAAC,UAAiB;AAC3B,cAAM,SAAS,MAAM;AACrB,YAAI,CAAC,OAAQ,QAAO;AAIpB,YAAI,OAAO,QAAQ,IAAI,KAAK,WAAW,iBAAiB,EAAG,QAAO;AAClE,YAAI,OAAO,QAAQ,IAAI,KAAK,WAAW,WAAW,EAAG,QAAO;AAC5D,YAAI,OAAO,QAAQ,IAAI,KAAK,WAAW,yBAAyB,EAAG,QAAO;AAC1E,eAAO;AAAA,MACX;AAAA,IACJ,CAAC;AAAA,EACL;AAAA,EAEA,aAAa;AACT,QAAI,CAAC,KAAK,SAAS,CAAC,KAAK,OAAO,SAAS,wBAAyB;AAClE,UAAM,OAAO,KAAK,MAAM,sBAAsB;AAC9C,SAAK,OAAO,QAAQ,wBAAwB;AAAA,MACxC,OAAO,KAAK,MAAM,KAAK,KAAK;AAAA,MAC5B,QAAQ,KAAK,MAAM,KAAK,MAAM;AAAA,MAC9B,MAAM,KAAK,MAAM,KAAK,IAAI;AAAA,MAC1B,KAAK,KAAK,MAAM,KAAK,GAAG;AAAA,IAC5B,CAAC;AAAA,EACL;AAAA,EAEA,iBAAqC;AACjC,UAAM,SAAS,SAAS;AACxB,WAAO,UAAU,kBAAkB,cAAc,SAAS;AAAA,EAC9D;AACJ;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|