vidply 1.2.0 → 1.2.3
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 +974 -946
- package/dist/dev/{vidply.AudioDescriptionManager-R47Q4YR4.js → vidply.AudioDescriptionManager-JYYLK5KB.js} +275 -19
- package/dist/dev/vidply.AudioDescriptionManager-JYYLK5KB.js.map +7 -0
- package/dist/dev/{vidply.CaptionStyleMenu-7XHRBWUM.js → vidply.CaptionStyleMenu-47RCKQF5.js} +4 -4
- package/dist/dev/{vidply.DASHRenderer-PWEMC3BT.js → vidply.DASHRenderer-VEJ4HDHO.js} +3 -3
- package/dist/dev/vidply.DASHRenderer-VEJ4HDHO.js.map +7 -0
- package/dist/dev/{vidply.FloatingPlayerManager-PKNNNAS2.js → vidply.FloatingPlayerManager-C2UVRPZY.js} +5 -5
- package/dist/dev/vidply.FloatingPlayerManager-C2UVRPZY.js.map +7 -0
- package/dist/dev/{vidply.HLSRenderer-E7RQ74HI.js → vidply.HLSRenderer-ZVBK2TNQ.js} +4 -4
- package/dist/dev/vidply.HLSRenderer-ZVBK2TNQ.js.map +7 -0
- package/dist/dev/vidply.HTML5Renderer-HLRQUOCU.js +12 -0
- package/dist/dev/{vidply.SignLanguageManager-FET5FQZ7.js → vidply.SignLanguageManager-FLUCBW5H.js} +8 -8
- package/dist/dev/vidply.SignLanguageManager-FLUCBW5H.js.map +7 -0
- package/dist/dev/{vidply.SoundCloudRenderer-RVSJZODO.js → vidply.SoundCloudRenderer-MGMAJ7NK.js} +3 -3
- package/dist/dev/vidply.SoundCloudRenderer-MGMAJ7NK.js.map +7 -0
- package/dist/dev/{vidply.TranscriptManager-5GXOQCLX.js → vidply.TranscriptManager-Y2RYXHJW.js} +10 -10
- package/dist/dev/vidply.TranscriptManager-Y2RYXHJW.js.map +7 -0
- package/dist/dev/{vidply.VimeoRenderer-ER2LSBBW.js → vidply.VimeoRenderer-RQFQD7OE.js} +3 -3
- package/dist/dev/vidply.VimeoRenderer-RQFQD7OE.js.map +7 -0
- package/dist/dev/{vidply.YouTubeRenderer-NFTOWJAB.js → vidply.YouTubeRenderer-EU2BARZF.js} +3 -3
- package/dist/dev/vidply.YouTubeRenderer-EU2BARZF.js.map +7 -0
- package/dist/dev/{vidply.chunk-BNFIUL2Z.js → vidply.chunk-6LYZYI5G.js} +3 -3
- package/dist/dev/vidply.chunk-6LYZYI5G.js.map +7 -0
- package/dist/dev/{vidply.chunk-NQPURMQY.js → vidply.chunk-A4ESEOQ2.js} +6 -6
- package/dist/dev/vidply.chunk-A4ESEOQ2.js.map +7 -0
- package/dist/dev/{vidply.chunk-W25HUCHS.js → vidply.chunk-CGYIED2I.js} +2 -2
- package/dist/dev/vidply.chunk-CGYIED2I.js.map +7 -0
- package/dist/dev/{vidply.chunk-SK4V2DAU.js → vidply.chunk-HZ7UOKOU.js} +3 -3
- package/dist/dev/{vidply.chunk-RZWZGDV7.js → vidply.chunk-I6EDRYGJ.js} +33 -6
- package/dist/dev/vidply.chunk-I6EDRYGJ.js.map +7 -0
- package/dist/dev/{vidply.chunk-64WXMTGL.js → vidply.chunk-JKMQGLCJ.js} +5 -5
- package/dist/dev/vidply.chunk-JKMQGLCJ.js.map +7 -0
- package/dist/dev/{vidply.chunk-DZSDO3B7.js → vidply.chunk-JZNZX32Z.js} +2 -2
- package/dist/dev/{vidply.chunk-VMSQAZM7.js → vidply.chunk-P25MRJ6O.js} +2 -2
- package/dist/dev/{vidply.chunk-6RQA4ROD.js → vidply.chunk-PRFNESYA.js} +2 -2
- package/dist/dev/{vidply.chunk-KI7C3QVL.js → vidply.chunk-UI3RYCVF.js} +4 -3
- package/dist/dev/vidply.chunk-UI3RYCVF.js.map +7 -0
- package/dist/dev/{vidply.chunk-JJ47NO24.js → vidply.chunk-UNFGMABP.js} +2 -2
- package/dist/dev/vidply.chunk-UNFGMABP.js.map +7 -0
- package/dist/dev/{vidply.de-4YAT7FFM.js → vidply.de-VAUVSQXC.js} +29 -2
- package/dist/dev/vidply.de-VAUVSQXC.js.map +7 -0
- package/dist/dev/{vidply.es-LEWIRUBH.js → vidply.es-G65HFWO6.js} +29 -2
- package/dist/dev/vidply.es-G65HFWO6.js.map +7 -0
- package/dist/dev/vidply.esm.js +648 -35
- package/dist/dev/vidply.esm.js.map +3 -3
- package/dist/dev/{vidply.fr-Q4GV2NOE.js → vidply.fr-MJNOZELM.js} +29 -2
- package/dist/dev/vidply.fr-MJNOZELM.js.map +7 -0
- package/dist/dev/{vidply.ja-S6ITA6WU.js → vidply.ja-INMDHIYJ.js} +29 -2
- package/dist/dev/vidply.ja-INMDHIYJ.js.map +7 -0
- package/dist/legacy/vidply.js +1062 -34
- package/dist/legacy/vidply.js.map +3 -3
- package/dist/legacy/vidply.min.js +2 -2
- package/dist/legacy/vidply.min.meta.json +127 -55
- package/dist/prod/vidply.AudioDescriptionManager-EKWUNOPE.min.js +6 -0
- package/dist/prod/{vidply.CaptionStyleMenu-M3KVQWB5.min.js → vidply.CaptionStyleMenu-JTWSNHRQ.min.js} +2 -2
- package/dist/prod/{vidply.DASHRenderer-DN3S27AV.min.js → vidply.DASHRenderer-CIDAHFWM.min.js} +2 -2
- package/dist/prod/{vidply.FloatingPlayerManager-SKAZ4J43.min.js → vidply.FloatingPlayerManager-VZXHPCPM.min.js} +2 -2
- package/dist/prod/{vidply.HLSRenderer-62HH4YTK.min.js → vidply.HLSRenderer-Q6EFYKID.min.js} +2 -2
- package/dist/prod/vidply.HTML5Renderer-XRDJUTOO.min.js +6 -0
- package/dist/prod/{vidply.SignLanguageManager-V3OBTSSF.min.js → vidply.SignLanguageManager-JUOSA5AM.min.js} +2 -2
- package/dist/prod/{vidply.SoundCloudRenderer-CXRVZIAJ.min.js → vidply.SoundCloudRenderer-7ODD46PO.min.js} +2 -2
- package/dist/prod/{vidply.TranscriptManager-SYM3DFUY.min.js → vidply.TranscriptManager-4B2QKYGQ.min.js} +2 -2
- package/dist/prod/{vidply.VimeoRenderer-72TWRYFQ.min.js → vidply.VimeoRenderer-MR3UYLY7.min.js} +2 -2
- package/dist/prod/{vidply.YouTubeRenderer-UMHEV3KD.min.js → vidply.YouTubeRenderer-ITRTC7NR.min.js} +2 -2
- package/dist/prod/{vidply.chunk-YEENJS3Q.min.js → vidply.chunk-3EEDXU4T.min.js} +2 -2
- package/dist/prod/{vidply.chunk-P4UXOHWA.min.js → vidply.chunk-5HTX5QXW.min.js} +1 -1
- package/dist/prod/{vidply.chunk-ABPXVDLZ.min.js → vidply.chunk-6OGHHIYP.min.js} +2 -2
- package/dist/prod/{vidply.chunk-UK7YGOB5.min.js → vidply.chunk-GUPGMFUZ.min.js} +2 -2
- package/dist/prod/{vidply.chunk-WX3MVEAL.min.js → vidply.chunk-MWEBITCP.min.js} +1 -1
- package/dist/prod/{vidply.chunk-2TB6MUHO.min.js → vidply.chunk-PL3P3JHR.min.js} +1 -1
- package/dist/prod/{vidply.chunk-EKEYJWMG.min.js → vidply.chunk-QQZGV7TD.min.js} +2 -2
- package/dist/prod/{vidply.chunk-Z3YDSA7O.min.js → vidply.chunk-U5IRRYG5.min.js} +1 -1
- package/dist/prod/{vidply.chunk-TM6AXJVD.min.js → vidply.chunk-VKHV4QHA.min.js} +2 -2
- package/dist/prod/{vidply.chunk-P3PQBY7N.min.js → vidply.chunk-XA4M574A.min.js} +1 -1
- package/dist/prod/{vidply.chunk-RI3X7WHK.min.js → vidply.chunk-ZPTV4JEP.min.js} +2 -2
- package/dist/prod/{vidply.de-BCHXRQ53.min.js → vidply.de-UZHFCEZU.min.js} +2 -2
- package/dist/prod/{vidply.es-PICAKMZD.min.js → vidply.es-BOISUV2S.min.js} +2 -2
- package/dist/prod/vidply.esm.min.js +2 -2
- package/dist/prod/{vidply.fr-ZILI7CDR.min.js → vidply.fr-ICSMKPFM.min.js} +2 -2
- package/dist/prod/{vidply.ja-5WQ4OK4J.min.js → vidply.ja-VQ2HKGAY.min.js} +2 -2
- package/dist/vidply.css +5852 -5796
- package/dist/vidply.esm.min.meta.json +204 -149
- package/dist/vidply.min.css +1 -1
- package/package.json +11 -11
- package/src/controls/CaptionManager.ts +598 -598
- package/src/controls/ControlBar.ts +4191 -4159
- package/src/controls/KeyboardHelp.ts +326 -0
- package/src/controls/KeyboardManager.ts +387 -383
- package/src/controls/SettingsDialog.ts +488 -488
- package/src/controls/TranscriptManager.ts +2426 -2426
- package/src/core/AudioDescriptionManager.ts +863 -774
- package/src/core/DescriptionSpeechManager.ts +232 -0
- package/src/core/FloatingPlayerManager.ts +674 -674
- package/src/core/LazyInit.ts +80 -80
- package/src/core/MediaSessionManager.ts +341 -0
- package/src/core/MetadataAlertsManager.ts +514 -514
- package/src/core/Player.ts +2973 -2872
- package/src/core/PosterManager.ts +169 -169
- package/src/core/PseudoFullscreen.ts +179 -179
- package/src/core/ResponsiveManager.ts +231 -231
- package/src/core/ResumeManager.ts +236 -236
- package/src/core/SignLanguageManager.ts +1308 -1308
- package/src/core/ThemeManager.ts +157 -157
- package/src/features/PlaylistManager.ts +1838 -1827
- package/src/i18n/i18n.ts +223 -223
- package/src/i18n/languages/de.ts +244 -217
- package/src/i18n/languages/en.ts +245 -218
- package/src/i18n/languages/es.ts +241 -214
- package/src/i18n/languages/fr.ts +241 -214
- package/src/i18n/languages/ja.ts +241 -214
- package/src/icons/Icons.ts +168 -167
- package/src/index.ts +196 -193
- package/src/renderers/DASHRenderer.ts +1077 -1077
- package/src/renderers/HLSRenderer.ts +944 -944
- package/src/renderers/HTML5Renderer.ts +389 -389
- package/src/renderers/SoundCloudRenderer.ts +437 -437
- package/src/renderers/VimeoRenderer.ts +298 -298
- package/src/renderers/YouTubeRenderer.ts +290 -290
- package/src/styles/vidply.css +5852 -5796
- package/src/types/events.ts +128 -122
- package/src/types/globals.d.ts +313 -313
- package/src/types/options.ts +199 -182
- package/src/types/renderer.ts +58 -58
- package/src/utils/DOMUtils.ts +281 -281
- package/src/utils/DownloadInfo.ts +171 -171
- package/src/utils/DraggablePanel.ts +539 -539
- package/src/utils/DraggablePanelMenu.ts +195 -195
- package/src/utils/DraggableResizable.ts +997 -997
- package/src/utils/EventEmitter.ts +59 -59
- package/src/utils/FormUtils.ts +95 -95
- package/src/utils/MenuFactory.ts +483 -483
- package/src/utils/Sanitize.ts +70 -70
- package/src/utils/SettingsMenuFactory.ts +289 -289
- package/src/utils/StorageManager.ts +287 -287
- package/src/utils/TrackLabelUtils.ts +52 -52
- package/src/utils/UrlSafe.ts +69 -69
- package/src/utils/VideoFrameCapture.ts +114 -114
- package/src/utils/WindowComponents.ts +250 -250
- package/dist/dev/vidply.AudioDescriptionManager-R47Q4YR4.js.map +0 -7
- package/dist/dev/vidply.DASHRenderer-PWEMC3BT.js.map +0 -7
- package/dist/dev/vidply.FloatingPlayerManager-PKNNNAS2.js.map +0 -7
- package/dist/dev/vidply.HLSRenderer-E7RQ74HI.js.map +0 -7
- package/dist/dev/vidply.HTML5Renderer-BU4QJJJY.js +0 -12
- package/dist/dev/vidply.SignLanguageManager-FET5FQZ7.js.map +0 -7
- package/dist/dev/vidply.SoundCloudRenderer-RVSJZODO.js.map +0 -7
- package/dist/dev/vidply.TranscriptManager-5GXOQCLX.js.map +0 -7
- package/dist/dev/vidply.VimeoRenderer-ER2LSBBW.js.map +0 -7
- package/dist/dev/vidply.YouTubeRenderer-NFTOWJAB.js.map +0 -7
- package/dist/dev/vidply.chunk-64WXMTGL.js.map +0 -7
- package/dist/dev/vidply.chunk-BNFIUL2Z.js.map +0 -7
- package/dist/dev/vidply.chunk-JJ47NO24.js.map +0 -7
- package/dist/dev/vidply.chunk-KI7C3QVL.js.map +0 -7
- package/dist/dev/vidply.chunk-NQPURMQY.js.map +0 -7
- package/dist/dev/vidply.chunk-RZWZGDV7.js.map +0 -7
- package/dist/dev/vidply.chunk-W25HUCHS.js.map +0 -7
- package/dist/dev/vidply.de-4YAT7FFM.js.map +0 -7
- package/dist/dev/vidply.es-LEWIRUBH.js.map +0 -7
- package/dist/dev/vidply.fr-Q4GV2NOE.js.map +0 -7
- package/dist/dev/vidply.ja-S6ITA6WU.js.map +0 -7
- package/dist/prod/vidply.AudioDescriptionManager-RHLIJW5C.min.js +0 -6
- package/dist/prod/vidply.HTML5Renderer-IU5T3WEW.min.js +0 -6
- /package/dist/dev/{vidply.CaptionStyleMenu-7XHRBWUM.js.map → vidply.CaptionStyleMenu-47RCKQF5.js.map} +0 -0
- /package/dist/dev/{vidply.HTML5Renderer-BU4QJJJY.js.map → vidply.HTML5Renderer-HLRQUOCU.js.map} +0 -0
- /package/dist/dev/{vidply.chunk-SK4V2DAU.js.map → vidply.chunk-HZ7UOKOU.js.map} +0 -0
- /package/dist/dev/{vidply.chunk-DZSDO3B7.js.map → vidply.chunk-JZNZX32Z.js.map} +0 -0
- /package/dist/dev/{vidply.chunk-VMSQAZM7.js.map → vidply.chunk-P25MRJ6O.js.map} +0 -0
- /package/dist/dev/{vidply.chunk-6RQA4ROD.js.map → vidply.chunk-PRFNESYA.js.map} +0 -0
|
@@ -1,195 +1,195 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Shared helpers for the settings-menu items that toggle drag / resize
|
|
3
|
-
* mode on a `DraggableResizable`-powered panel.
|
|
4
|
-
*
|
|
5
|
-
* Previously `SignLanguageManager` and `TranscriptManager` both carried
|
|
6
|
-
* near-identical copies of:
|
|
7
|
-
* - `updateDragOptionState` / `updateResizeOptionState`
|
|
8
|
-
* (button `aria-checked` + label + i18n-driven text sync)
|
|
9
|
-
* - `positionSettingsMenuImmediate`
|
|
10
|
-
* (place the menu relative to the settings button, with above/below
|
|
11
|
-
* flipping and viewport-edge clamping)
|
|
12
|
-
*
|
|
13
|
-
* This module owns those pieces so a future manager (or a refactor of
|
|
14
|
-
* an existing one) doesn't have to re-derive the rules from scratch.
|
|
15
|
-
*
|
|
16
|
-
* NOTE: the full "DraggablePanel" abstraction that also owns the menu
|
|
17
|
-
* *contents* (language selector, style dialog, pinned state, etc.) is
|
|
18
|
-
* deliberately NOT attempted here — the per-manager specifics are
|
|
19
|
-
* significant and an attempt to unify them would churn security-
|
|
20
|
-
* sensitive code without meaningfully reducing LOC.
|
|
21
|
-
*/
|
|
22
|
-
|
|
23
|
-
/** Visual state of a two-state ("currently enabled / currently disabled")
|
|
24
|
-
* toggle menu item. */
|
|
25
|
-
export interface ToggleMenuItemState {
|
|
26
|
-
/** Whether the underlying mode is currently active. */
|
|
27
|
-
enabled: boolean;
|
|
28
|
-
/** Label shown when the mode is active (i.e. the label invites the
|
|
29
|
-
* user to disable it). */
|
|
30
|
-
enabledText: string;
|
|
31
|
-
/** Label shown when the mode is inactive. */
|
|
32
|
-
disabledText: string;
|
|
33
|
-
/** `aria-label` for screen readers when the mode is active. */
|
|
34
|
-
enabledAria: string;
|
|
35
|
-
/** `aria-label` for screen readers when the mode is inactive. */
|
|
36
|
-
disabledAria: string;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Sync a settings-menu toggle button's `aria-checked`, `aria-label`,
|
|
41
|
-
* and visible text node with the current mode state. Both properties
|
|
42
|
-
* are updated unconditionally so the element stays in sync even if a
|
|
43
|
-
* previous render left them stale.
|
|
44
|
-
*
|
|
45
|
-
* The caller owns the DOM nodes; this helper only mutates known-safe
|
|
46
|
-
* properties (no innerHTML), so a translated label with angle brackets
|
|
47
|
-
* or ampersands is stored as text, not parsed as markup.
|
|
48
|
-
*/
|
|
49
|
-
export function updateToggleMenuItem(
|
|
50
|
-
button: HTMLElement | null | undefined,
|
|
51
|
-
textElement: Element | null | undefined,
|
|
52
|
-
state: ToggleMenuItemState
|
|
53
|
-
): void {
|
|
54
|
-
if (!button) return;
|
|
55
|
-
|
|
56
|
-
button.setAttribute('aria-checked', state.enabled ? 'true' : 'false');
|
|
57
|
-
button.setAttribute('aria-label', state.enabled ? state.enabledAria : state.disabledAria);
|
|
58
|
-
|
|
59
|
-
if (textElement) {
|
|
60
|
-
// `textContent` exists on `Node`, so we can accept the broader
|
|
61
|
-
// `Element` type here and let callers pass either an `HTMLElement`
|
|
62
|
-
// (TranscriptManager keeps the tighter type for the sake of its
|
|
63
|
-
// `.style` access elsewhere) or a plain `Element` returned by
|
|
64
|
-
// `querySelector`.
|
|
65
|
-
textElement.textContent = state.enabled ? state.enabledText : state.disabledText;
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
export interface PositionSettingsMenuOptions {
|
|
70
|
-
/**
|
|
71
|
-
* Horizontal anchoring:
|
|
72
|
-
* - `'left'` : align the menu's left edge with the button's left edge
|
|
73
|
-
* (the TranscriptManager convention — simple panels).
|
|
74
|
-
* - `'center'` : centre the menu horizontally on the button, with
|
|
75
|
-
* viewport-edge clamping (the SignLanguageManager
|
|
76
|
-
* convention — small floating panel that can end up
|
|
77
|
-
* near the viewport edges).
|
|
78
|
-
*/
|
|
79
|
-
align?: 'left' | 'center';
|
|
80
|
-
/** Vertical gap (px) between button and menu. */
|
|
81
|
-
gap?: number;
|
|
82
|
-
/** Extra breathing room (px) before we decide to flip above. */
|
|
83
|
-
spaceReserve?: number;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* Position a settings menu relative to its button, using offsets that
|
|
88
|
-
* are relative to the button's offset parent (so transforms on ancestor
|
|
89
|
-
* elements don't throw the layout off).
|
|
90
|
-
*
|
|
91
|
-
* When there isn't enough room below the button but there is room
|
|
92
|
-
* above, the menu flips upward and picks up a `vidply-menu-above` CSS
|
|
93
|
-
* class so arrows / drop-shadows can be mirrored via stylesheet.
|
|
94
|
-
*/
|
|
95
|
-
export function positionSettingsMenu(
|
|
96
|
-
menu: HTMLElement | null | undefined,
|
|
97
|
-
button: HTMLElement | null | undefined,
|
|
98
|
-
opts: PositionSettingsMenuOptions = {}
|
|
99
|
-
): void {
|
|
100
|
-
if (!menu || !button) return;
|
|
101
|
-
const { align = 'left', gap = 4, spaceReserve = 20 } = opts;
|
|
102
|
-
|
|
103
|
-
const parentContainer = button.parentElement;
|
|
104
|
-
if (!parentContainer) return;
|
|
105
|
-
|
|
106
|
-
const buttonRect = button.getBoundingClientRect();
|
|
107
|
-
const parentRect = parentContainer.getBoundingClientRect();
|
|
108
|
-
const menuRect = menu.getBoundingClientRect();
|
|
109
|
-
const viewportHeight = window.innerHeight;
|
|
110
|
-
const viewportWidth = window.innerWidth;
|
|
111
|
-
|
|
112
|
-
const buttonBottom = buttonRect.bottom - parentRect.top;
|
|
113
|
-
const buttonTop = buttonRect.top - parentRect.top;
|
|
114
|
-
const buttonLeftOffset = buttonRect.left - parentRect.left;
|
|
115
|
-
const buttonCenterX = buttonLeftOffset + buttonRect.width / 2;
|
|
116
|
-
|
|
117
|
-
const spaceAbove = buttonRect.top;
|
|
118
|
-
const spaceBelow = viewportHeight - buttonRect.bottom;
|
|
119
|
-
|
|
120
|
-
// Vertical placement: default below, flip above when tight.
|
|
121
|
-
let menuTop: number | null = buttonBottom + gap;
|
|
122
|
-
let menuBottomPx: number | null = null;
|
|
123
|
-
if (spaceBelow < menuRect.height + spaceReserve && spaceAbove > spaceBelow) {
|
|
124
|
-
if (align === 'center') {
|
|
125
|
-
// Measure from the bottom edge of the parent so the menu sits
|
|
126
|
-
// directly above the button without being pushed off-screen.
|
|
127
|
-
const parentHeight = parentRect.bottom - parentRect.top;
|
|
128
|
-
menuBottomPx = parentHeight - buttonTop + gap;
|
|
129
|
-
menuTop = null;
|
|
130
|
-
} else {
|
|
131
|
-
menuTop = buttonTop - menuRect.height - gap;
|
|
132
|
-
}
|
|
133
|
-
menu.classList.add('vidply-menu-above');
|
|
134
|
-
} else {
|
|
135
|
-
menu.classList.remove('vidply-menu-above');
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
// Horizontal placement: simple left-align vs viewport-clamped centre.
|
|
139
|
-
let leftValue: string;
|
|
140
|
-
let rightValue: string;
|
|
141
|
-
let transform: string;
|
|
142
|
-
|
|
143
|
-
if (align === 'center') {
|
|
144
|
-
const menuLeftAbsolute = buttonRect.left + buttonRect.width / 2 - menuRect.width / 2;
|
|
145
|
-
if (menuLeftAbsolute < 10) {
|
|
146
|
-
leftValue = '0';
|
|
147
|
-
rightValue = 'auto';
|
|
148
|
-
transform = 'translateX(0)';
|
|
149
|
-
} else if (menuLeftAbsolute + menuRect.width > viewportWidth - 10) {
|
|
150
|
-
leftValue = 'auto';
|
|
151
|
-
rightValue = '0';
|
|
152
|
-
transform = 'translateX(0)';
|
|
153
|
-
} else {
|
|
154
|
-
leftValue = `${buttonCenterX}px`;
|
|
155
|
-
rightValue = 'auto';
|
|
156
|
-
transform = 'translateX(-50%)';
|
|
157
|
-
}
|
|
158
|
-
} else {
|
|
159
|
-
leftValue = `${buttonLeftOffset}px`;
|
|
160
|
-
rightValue = 'auto';
|
|
161
|
-
transform = 'translateX(0)';
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
if (menuTop !== null) {
|
|
165
|
-
menu.style.top = `${menuTop}px`;
|
|
166
|
-
menu.style.bottom = 'auto';
|
|
167
|
-
} else if (menuBottomPx !== null) {
|
|
168
|
-
menu.style.top = 'auto';
|
|
169
|
-
menu.style.bottom = `${menuBottomPx}px`;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
menu.style.left = leftValue;
|
|
173
|
-
menu.style.right = rightValue;
|
|
174
|
-
menu.style.transform = transform;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
/**
|
|
178
|
-
* RAF-deferred wrapper around {@link positionSettingsMenu}. Some
|
|
179
|
-
* callers need to re-position after layout has settled (e.g. when the
|
|
180
|
-
* panel size just changed and `getBoundingClientRect` would still
|
|
181
|
-
* return the pre-change measurements on this frame).
|
|
182
|
-
*/
|
|
183
|
-
export function positionSettingsMenuDeferred(
|
|
184
|
-
menu: HTMLElement | null | undefined,
|
|
185
|
-
button: HTMLElement | null | undefined,
|
|
186
|
-
opts: PositionSettingsMenuOptions = {}
|
|
187
|
-
): void {
|
|
188
|
-
requestAnimationFrame(() => {
|
|
189
|
-
// The extra setTimeout is intentional: older WebKit still reports
|
|
190
|
-
// stale geometry during the first RAF callback following a DOM
|
|
191
|
-
// mutation. A 10ms trailing microtask gives layout a chance to
|
|
192
|
-
// settle without being user-perceptible.
|
|
193
|
-
setTimeout(() => positionSettingsMenu(menu, button, opts), 10);
|
|
194
|
-
});
|
|
195
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Shared helpers for the settings-menu items that toggle drag / resize
|
|
3
|
+
* mode on a `DraggableResizable`-powered panel.
|
|
4
|
+
*
|
|
5
|
+
* Previously `SignLanguageManager` and `TranscriptManager` both carried
|
|
6
|
+
* near-identical copies of:
|
|
7
|
+
* - `updateDragOptionState` / `updateResizeOptionState`
|
|
8
|
+
* (button `aria-checked` + label + i18n-driven text sync)
|
|
9
|
+
* - `positionSettingsMenuImmediate`
|
|
10
|
+
* (place the menu relative to the settings button, with above/below
|
|
11
|
+
* flipping and viewport-edge clamping)
|
|
12
|
+
*
|
|
13
|
+
* This module owns those pieces so a future manager (or a refactor of
|
|
14
|
+
* an existing one) doesn't have to re-derive the rules from scratch.
|
|
15
|
+
*
|
|
16
|
+
* NOTE: the full "DraggablePanel" abstraction that also owns the menu
|
|
17
|
+
* *contents* (language selector, style dialog, pinned state, etc.) is
|
|
18
|
+
* deliberately NOT attempted here — the per-manager specifics are
|
|
19
|
+
* significant and an attempt to unify them would churn security-
|
|
20
|
+
* sensitive code without meaningfully reducing LOC.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
/** Visual state of a two-state ("currently enabled / currently disabled")
|
|
24
|
+
* toggle menu item. */
|
|
25
|
+
export interface ToggleMenuItemState {
|
|
26
|
+
/** Whether the underlying mode is currently active. */
|
|
27
|
+
enabled: boolean;
|
|
28
|
+
/** Label shown when the mode is active (i.e. the label invites the
|
|
29
|
+
* user to disable it). */
|
|
30
|
+
enabledText: string;
|
|
31
|
+
/** Label shown when the mode is inactive. */
|
|
32
|
+
disabledText: string;
|
|
33
|
+
/** `aria-label` for screen readers when the mode is active. */
|
|
34
|
+
enabledAria: string;
|
|
35
|
+
/** `aria-label` for screen readers when the mode is inactive. */
|
|
36
|
+
disabledAria: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Sync a settings-menu toggle button's `aria-checked`, `aria-label`,
|
|
41
|
+
* and visible text node with the current mode state. Both properties
|
|
42
|
+
* are updated unconditionally so the element stays in sync even if a
|
|
43
|
+
* previous render left them stale.
|
|
44
|
+
*
|
|
45
|
+
* The caller owns the DOM nodes; this helper only mutates known-safe
|
|
46
|
+
* properties (no innerHTML), so a translated label with angle brackets
|
|
47
|
+
* or ampersands is stored as text, not parsed as markup.
|
|
48
|
+
*/
|
|
49
|
+
export function updateToggleMenuItem(
|
|
50
|
+
button: HTMLElement | null | undefined,
|
|
51
|
+
textElement: Element | null | undefined,
|
|
52
|
+
state: ToggleMenuItemState
|
|
53
|
+
): void {
|
|
54
|
+
if (!button) return;
|
|
55
|
+
|
|
56
|
+
button.setAttribute('aria-checked', state.enabled ? 'true' : 'false');
|
|
57
|
+
button.setAttribute('aria-label', state.enabled ? state.enabledAria : state.disabledAria);
|
|
58
|
+
|
|
59
|
+
if (textElement) {
|
|
60
|
+
// `textContent` exists on `Node`, so we can accept the broader
|
|
61
|
+
// `Element` type here and let callers pass either an `HTMLElement`
|
|
62
|
+
// (TranscriptManager keeps the tighter type for the sake of its
|
|
63
|
+
// `.style` access elsewhere) or a plain `Element` returned by
|
|
64
|
+
// `querySelector`.
|
|
65
|
+
textElement.textContent = state.enabled ? state.enabledText : state.disabledText;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export interface PositionSettingsMenuOptions {
|
|
70
|
+
/**
|
|
71
|
+
* Horizontal anchoring:
|
|
72
|
+
* - `'left'` : align the menu's left edge with the button's left edge
|
|
73
|
+
* (the TranscriptManager convention — simple panels).
|
|
74
|
+
* - `'center'` : centre the menu horizontally on the button, with
|
|
75
|
+
* viewport-edge clamping (the SignLanguageManager
|
|
76
|
+
* convention — small floating panel that can end up
|
|
77
|
+
* near the viewport edges).
|
|
78
|
+
*/
|
|
79
|
+
align?: 'left' | 'center';
|
|
80
|
+
/** Vertical gap (px) between button and menu. */
|
|
81
|
+
gap?: number;
|
|
82
|
+
/** Extra breathing room (px) before we decide to flip above. */
|
|
83
|
+
spaceReserve?: number;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Position a settings menu relative to its button, using offsets that
|
|
88
|
+
* are relative to the button's offset parent (so transforms on ancestor
|
|
89
|
+
* elements don't throw the layout off).
|
|
90
|
+
*
|
|
91
|
+
* When there isn't enough room below the button but there is room
|
|
92
|
+
* above, the menu flips upward and picks up a `vidply-menu-above` CSS
|
|
93
|
+
* class so arrows / drop-shadows can be mirrored via stylesheet.
|
|
94
|
+
*/
|
|
95
|
+
export function positionSettingsMenu(
|
|
96
|
+
menu: HTMLElement | null | undefined,
|
|
97
|
+
button: HTMLElement | null | undefined,
|
|
98
|
+
opts: PositionSettingsMenuOptions = {}
|
|
99
|
+
): void {
|
|
100
|
+
if (!menu || !button) return;
|
|
101
|
+
const { align = 'left', gap = 4, spaceReserve = 20 } = opts;
|
|
102
|
+
|
|
103
|
+
const parentContainer = button.parentElement;
|
|
104
|
+
if (!parentContainer) return;
|
|
105
|
+
|
|
106
|
+
const buttonRect = button.getBoundingClientRect();
|
|
107
|
+
const parentRect = parentContainer.getBoundingClientRect();
|
|
108
|
+
const menuRect = menu.getBoundingClientRect();
|
|
109
|
+
const viewportHeight = window.innerHeight;
|
|
110
|
+
const viewportWidth = window.innerWidth;
|
|
111
|
+
|
|
112
|
+
const buttonBottom = buttonRect.bottom - parentRect.top;
|
|
113
|
+
const buttonTop = buttonRect.top - parentRect.top;
|
|
114
|
+
const buttonLeftOffset = buttonRect.left - parentRect.left;
|
|
115
|
+
const buttonCenterX = buttonLeftOffset + buttonRect.width / 2;
|
|
116
|
+
|
|
117
|
+
const spaceAbove = buttonRect.top;
|
|
118
|
+
const spaceBelow = viewportHeight - buttonRect.bottom;
|
|
119
|
+
|
|
120
|
+
// Vertical placement: default below, flip above when tight.
|
|
121
|
+
let menuTop: number | null = buttonBottom + gap;
|
|
122
|
+
let menuBottomPx: number | null = null;
|
|
123
|
+
if (spaceBelow < menuRect.height + spaceReserve && spaceAbove > spaceBelow) {
|
|
124
|
+
if (align === 'center') {
|
|
125
|
+
// Measure from the bottom edge of the parent so the menu sits
|
|
126
|
+
// directly above the button without being pushed off-screen.
|
|
127
|
+
const parentHeight = parentRect.bottom - parentRect.top;
|
|
128
|
+
menuBottomPx = parentHeight - buttonTop + gap;
|
|
129
|
+
menuTop = null;
|
|
130
|
+
} else {
|
|
131
|
+
menuTop = buttonTop - menuRect.height - gap;
|
|
132
|
+
}
|
|
133
|
+
menu.classList.add('vidply-menu-above');
|
|
134
|
+
} else {
|
|
135
|
+
menu.classList.remove('vidply-menu-above');
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Horizontal placement: simple left-align vs viewport-clamped centre.
|
|
139
|
+
let leftValue: string;
|
|
140
|
+
let rightValue: string;
|
|
141
|
+
let transform: string;
|
|
142
|
+
|
|
143
|
+
if (align === 'center') {
|
|
144
|
+
const menuLeftAbsolute = buttonRect.left + buttonRect.width / 2 - menuRect.width / 2;
|
|
145
|
+
if (menuLeftAbsolute < 10) {
|
|
146
|
+
leftValue = '0';
|
|
147
|
+
rightValue = 'auto';
|
|
148
|
+
transform = 'translateX(0)';
|
|
149
|
+
} else if (menuLeftAbsolute + menuRect.width > viewportWidth - 10) {
|
|
150
|
+
leftValue = 'auto';
|
|
151
|
+
rightValue = '0';
|
|
152
|
+
transform = 'translateX(0)';
|
|
153
|
+
} else {
|
|
154
|
+
leftValue = `${buttonCenterX}px`;
|
|
155
|
+
rightValue = 'auto';
|
|
156
|
+
transform = 'translateX(-50%)';
|
|
157
|
+
}
|
|
158
|
+
} else {
|
|
159
|
+
leftValue = `${buttonLeftOffset}px`;
|
|
160
|
+
rightValue = 'auto';
|
|
161
|
+
transform = 'translateX(0)';
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (menuTop !== null) {
|
|
165
|
+
menu.style.top = `${menuTop}px`;
|
|
166
|
+
menu.style.bottom = 'auto';
|
|
167
|
+
} else if (menuBottomPx !== null) {
|
|
168
|
+
menu.style.top = 'auto';
|
|
169
|
+
menu.style.bottom = `${menuBottomPx}px`;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
menu.style.left = leftValue;
|
|
173
|
+
menu.style.right = rightValue;
|
|
174
|
+
menu.style.transform = transform;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* RAF-deferred wrapper around {@link positionSettingsMenu}. Some
|
|
179
|
+
* callers need to re-position after layout has settled (e.g. when the
|
|
180
|
+
* panel size just changed and `getBoundingClientRect` would still
|
|
181
|
+
* return the pre-change measurements on this frame).
|
|
182
|
+
*/
|
|
183
|
+
export function positionSettingsMenuDeferred(
|
|
184
|
+
menu: HTMLElement | null | undefined,
|
|
185
|
+
button: HTMLElement | null | undefined,
|
|
186
|
+
opts: PositionSettingsMenuOptions = {}
|
|
187
|
+
): void {
|
|
188
|
+
requestAnimationFrame(() => {
|
|
189
|
+
// The extra setTimeout is intentional: older WebKit still reports
|
|
190
|
+
// stale geometry during the first RAF callback following a DOM
|
|
191
|
+
// mutation. A 10ms trailing microtask gives layout a chance to
|
|
192
|
+
// settle without being user-perceptible.
|
|
193
|
+
setTimeout(() => positionSettingsMenu(menu, button, opts), 10);
|
|
194
|
+
});
|
|
195
|
+
}
|