vidply 1.0.34 → 1.0.35

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 (42) hide show
  1. package/dist/dev/{vidply.SoundCloudRenderer-RIA3QKP3.js → vidply.SoundCloudRenderer-HCMKXHSX.js} +1 -3
  2. package/dist/dev/vidply.SoundCloudRenderer-HCMKXHSX.js.map +7 -0
  3. package/dist/dev/{vidply.TranscriptManager-T3BVTZHZ.js → vidply.TranscriptManager-EIIN5YOF.js} +2 -2
  4. package/dist/dev/{vidply.VimeoRenderer-DY2FG7LZ.js → vidply.VimeoRenderer-SLEBCZTT.js} +1 -2
  5. package/dist/dev/vidply.VimeoRenderer-SLEBCZTT.js.map +7 -0
  6. package/dist/dev/{vidply.YouTubeRenderer-EVXXE34A.js → vidply.YouTubeRenderer-E6F4UGVF.js} +1 -2
  7. package/dist/dev/vidply.YouTubeRenderer-E6F4UGVF.js.map +7 -0
  8. package/dist/dev/{vidply.chunk-74NJTDQI.js → vidply.chunk-AXXU22HR.js} +87 -10
  9. package/dist/dev/{vidply.chunk-74NJTDQI.js.map → vidply.chunk-AXXU22HR.js.map} +2 -2
  10. package/dist/dev/vidply.esm.js +32 -50
  11. package/dist/dev/vidply.esm.js.map +2 -2
  12. package/dist/legacy/vidply.js +119 -58
  13. package/dist/legacy/vidply.js.map +3 -3
  14. package/dist/legacy/vidply.min.js +1 -1
  15. package/dist/legacy/vidply.min.meta.json +18 -18
  16. package/dist/prod/vidply.SoundCloudRenderer-D2FNOEG6.min.js +6 -0
  17. package/dist/prod/{vidply.TranscriptManager-GPAOXEK4.min.js → vidply.TranscriptManager-VXCTCJ7X.min.js} +1 -1
  18. package/dist/prod/vidply.VimeoRenderer-QELFZVDU.min.js +6 -0
  19. package/dist/prod/vidply.YouTubeRenderer-ZL6YUHTF.min.js +6 -0
  20. package/dist/prod/{vidply.chunk-OM7DNW5P.min.js → vidply.chunk-Z6BHMOGK.min.js} +1 -1
  21. package/dist/prod/vidply.esm.min.js +3 -3
  22. package/dist/vidply.css +218 -108
  23. package/dist/vidply.esm.min.meta.json +33 -33
  24. package/dist/vidply.min.css +1 -1
  25. package/package.json +3 -3
  26. package/src/controls/ControlBar.js +3 -0
  27. package/src/controls/KeyboardManager.js +19 -0
  28. package/src/core/Player.js +1 -64
  29. package/src/core/SignLanguageManager.js +18 -4
  30. package/src/index.js +3 -1
  31. package/src/renderers/SoundCloudRenderer.js +0 -2
  32. package/src/renderers/VimeoRenderer.js +0 -1
  33. package/src/renderers/YouTubeRenderer.js +0 -1
  34. package/src/styles/vidply.css +218 -108
  35. package/src/utils/DraggableResizable.js +123 -12
  36. package/dist/dev/vidply.SoundCloudRenderer-RIA3QKP3.js.map +0 -7
  37. package/dist/dev/vidply.VimeoRenderer-DY2FG7LZ.js.map +0 -7
  38. package/dist/dev/vidply.YouTubeRenderer-EVXXE34A.js.map +0 -7
  39. package/dist/prod/vidply.SoundCloudRenderer-BFV5SSIU.min.js +0 -6
  40. package/dist/prod/vidply.VimeoRenderer-UQWHQ4LC.min.js +0 -6
  41. package/dist/prod/vidply.YouTubeRenderer-K7A57ICA.min.js +0 -6
  42. /package/dist/dev/{vidply.TranscriptManager-T3BVTZHZ.js.map → vidply.TranscriptManager-EIIN5YOF.js.map} +0 -0
@@ -145,6 +145,9 @@ export class ControlBar {
145
145
  // Ensure menu doesn't go off top or bottom
146
146
  if (menuRect.top < 10) {
147
147
  menu.style.top = '10px';
148
+ // Important: clear bottom constraint, otherwise top+bottom will squeeze the menu height
149
+ // into a tiny strip on some mobile layouts.
150
+ menu.style.bottom = 'auto';
148
151
  }
149
152
 
150
153
  if (menuRect.bottom > viewportHeight - 10) {
@@ -46,6 +46,25 @@ export class KeyboardManager {
46
46
  if (playlistButton) {
47
47
  return; // Let the playlist handle keyboard events
48
48
  }
49
+
50
+ // Don't steal arrow keys when the sign-language overlay is in keyboard drag/resize mode.
51
+ // DraggableResizable listens on the overlay itself, but we run in CAPTURE phase, so we must opt out here.
52
+ const signWrapper = activeElement.closest('.vidply-sign-language-wrapper');
53
+ if (signWrapper) {
54
+ const draggable = this.player.signLanguageManager?.draggable;
55
+ if (draggable?.keyboardDragMode || draggable?.keyboardResizeMode) {
56
+ return;
57
+ }
58
+ }
59
+
60
+ // Same idea for the transcript floating window (it also uses DraggableResizable).
61
+ const transcriptWindow = activeElement.closest('.vidply-transcript-window');
62
+ if (transcriptWindow) {
63
+ const draggable = this.player.transcriptManager?.draggableResizable;
64
+ if (draggable?.keyboardDragMode || draggable?.keyboardResizeMode) {
65
+ return;
66
+ }
67
+ }
49
68
  }
50
69
 
51
70
  const key = e.key;
@@ -588,6 +588,7 @@ export class Player extends EventEmitter {
588
588
  this.container.classList.add(`${this.options.classPrefix}-responsive`);
589
589
  }
590
590
 
591
+
591
592
  // Create video wrapper (for proper positioning of controls)
592
593
  this.videoWrapper = DOMUtils.createElement('div', {
593
594
  className: `${this.options.classPrefix}-video-wrapper`
@@ -639,35 +640,10 @@ export class Player extends EventEmitter {
639
640
  : this.options.height;
640
641
  }
641
642
 
642
- // If no explicit height is set, ensure video players still have a stable layout box
643
- // even before any media is loaded (important for deferLoad + playlists).
644
- // We use the element's width/height attributes (e.g. from TYPO3) as aspect ratio.
645
- if (this.element.tagName === 'VIDEO' && !this.options.height) {
646
- const wAttr = parseInt(this.element.getAttribute('width') || '', 10);
647
- const hAttr = parseInt(this.element.getAttribute('height') || '', 10);
648
- if (Number.isFinite(wAttr) && Number.isFinite(hAttr) && wAttr > 0 && hAttr > 0) {
649
- // Only set if not already defined by CSS/inline style
650
- if (!this.container.style.aspectRatio) {
651
- this.container.style.aspectRatio = `${wAttr} / ${hAttr}`;
652
- }
653
-
654
- // The actual visual box is the videoWrapper (the video element is 100% height).
655
- // Give the wrapper the same aspect ratio so posters render correctly before metadata is loaded.
656
- if (this.videoWrapper && !this.videoWrapper.style.aspectRatio) {
657
- this.videoWrapper.style.aspectRatio = `${wAttr} / ${hAttr}`;
658
- // Override default CSS height:100% (which depends on parent having a height)
659
- this.videoWrapper.style.height = 'auto';
660
- }
661
- }
662
- }
663
-
664
643
  // Set poster (convert relative paths to absolute URLs)
665
644
  if (this.options.poster && this.element.tagName === 'VIDEO') {
666
645
  const resolvedPoster = this.resolvePosterPath(this.options.poster);
667
646
  this.element.poster = resolvedPoster;
668
- // If we intentionally have no media loaded yet (e.g. deferLoad/playlist),
669
- // use poster aspect ratio to size the wrapper so the poster isn't stretched.
670
- this.applyPosterAspectRatio(resolvedPoster);
671
647
  }
672
648
 
673
649
  // Create centered play button overlay (only for video)
@@ -711,45 +687,6 @@ export class Player extends EventEmitter {
711
687
  }, { once: true });
712
688
  }
713
689
 
714
- /**
715
- * Apply aspect ratio to the video wrapper based on the poster's intrinsic size.
716
- * This helps render correct poster sizing before media metadata is available.
717
- */
718
- applyPosterAspectRatio(posterUrl) {
719
- try {
720
- if (!posterUrl) return;
721
- if (this.element.tagName !== 'VIDEO') return;
722
- if (!this.videoWrapper) return;
723
-
724
- // If user explicitly configured dimensions, don't override.
725
- if (this.options.width || this.options.height) return;
726
-
727
- // Avoid repeated work
728
- if (this._posterAspectAppliedFor === posterUrl) return;
729
- this._posterAspectAppliedFor = posterUrl;
730
-
731
- const img = new Image();
732
- img.decoding = 'async';
733
- img.onload = () => {
734
- const w = img.naturalWidth;
735
- const h = img.naturalHeight;
736
- if (!w || !h) return;
737
-
738
- // Apply to wrapper (the actual layout box)
739
- this.videoWrapper.style.aspectRatio = `${w} / ${h}`;
740
- this.videoWrapper.style.height = 'auto';
741
-
742
- // Also apply to container if not explicitly set
743
- if (this.container && !this.container.style.aspectRatio) {
744
- this.container.style.aspectRatio = `${w} / ${h}`;
745
- }
746
- };
747
- img.src = posterUrl;
748
- } catch (e) {
749
- // ignore
750
- }
751
- }
752
-
753
690
  createPlayButtonOverlay() {
754
691
  // Create complete SVG play button from Icons.js
755
692
  this.playButtonOverlay = createPlayOverlay();
@@ -459,8 +459,11 @@ export class SignLanguageManager {
459
459
  _setupInteraction() {
460
460
  const isMobile = window.innerWidth < 768;
461
461
  const isFullscreen = this.player.state.fullscreen;
462
-
463
- if (isMobile && !isFullscreen) {
462
+
463
+ // Historically, drag/resize was disabled on mobile unless fullscreen to avoid scroll conflicts.
464
+ // Now that we support touch/pointer dragging with proper `touch-action` handling, enable it
465
+ // by default on iOS/Android as well. Allow opting out via option.
466
+ if (isMobile && !isFullscreen && this.player?.options?.signLanguageDragOnMobile === false) {
464
467
  if (this.draggable) {
465
468
  this.draggable.destroy();
466
469
  this.draggable = null;
@@ -473,7 +476,9 @@ export class SignLanguageManager {
473
476
  const classPrefix = this.player.options.classPrefix;
474
477
 
475
478
  this.draggable = new DraggableResizable(this.wrapper, {
476
- dragHandle: this.header,
479
+ // Allow dragging from anywhere on the sign-language window (better for touch).
480
+ // We still block dragging when interacting with controls via `onDragStart` below.
481
+ dragHandle: this.wrapper,
477
482
  resizeHandles: this.resizeHandles,
478
483
  constrainToViewport: true,
479
484
  maintainAspectRatio: true,
@@ -761,9 +766,18 @@ export class SignLanguageManager {
761
766
  hasTextClass: true,
762
767
  onClick: () => {
763
768
  this.toggleKeyboardDragMode();
764
- this.hideSettingsMenu();
769
+ // Keep focus off the settings button so arrow keys go to the draggable overlay.
770
+ this.hideSettingsMenu({ focusButton: false });
771
+ // If we just enabled keyboard drag mode, focus the overlay.
772
+ if (this.draggable?.keyboardDragMode) {
773
+ setTimeout(() => {
774
+ this.wrapper?.focus?.({ preventScroll: true });
775
+ }, 20);
776
+ }
765
777
  }
766
778
  });
779
+ // Allow CSS to hide this option on touch/mobile where dragging is always enabled
780
+ dragOption.setAttribute('data-setting', 'keyboard-drag');
767
781
  dragOption.setAttribute('role', 'switch');
768
782
  dragOption.setAttribute('aria-checked', 'false');
769
783
  this._removeTooltipFromMenuItem(dragOption);
package/src/index.js CHANGED
@@ -57,7 +57,9 @@ function parseDataAttributes(dataset) {
57
57
  'keyboard': 'keyboard',
58
58
  'responsive': 'responsive',
59
59
  'pipButton': 'pipButton',
60
- 'fullscreenButton': 'fullscreenButton'
60
+ 'fullscreenButton': 'fullscreenButton',
61
+
62
+ // Layout
61
63
  };
62
64
 
63
65
  // Parse each data attribute
@@ -125,10 +125,8 @@ export class SoundCloudRenderer {
125
125
  // Use different aspect ratio for playlists vs single tracks
126
126
  // Playlists need more height to show the track list
127
127
  if (this.isPlaylist()) {
128
- this.iframe.style.aspectRatio = '16 / 9'; // More height for playlist
129
128
  this.iframe.classList.add('vidply-soundcloud-iframe', 'vidply-soundcloud-playlist');
130
129
  } else {
131
- this.iframe.style.aspectRatio = '16 / 3'; // Banner-like for single track
132
130
  this.iframe.classList.add('vidply-soundcloud-iframe');
133
131
  }
134
132
  this.iframe.style.maxHeight = '100%';
@@ -70,7 +70,6 @@ export class VimeoRenderer {
70
70
  this.iframe = document.createElement('div');
71
71
  this.iframe.id = `vimeo-player-${Math.random().toString(36).substr(2, 9)}`;
72
72
  this.iframe.style.width = '100%';
73
- this.iframe.style.aspectRatio = '16 / 9';
74
73
  this.iframe.style.maxHeight = '100%';
75
74
 
76
75
  this.player.element.parentNode.insertBefore(this.iframe, this.player.element);
@@ -86,7 +86,6 @@ export class YouTubeRenderer {
86
86
  this.iframe = document.createElement('div');
87
87
  this.iframe.id = `youtube-player-${Math.random().toString(36).substr(2, 9)}`;
88
88
  this.iframe.style.width = '100%';
89
- this.iframe.style.aspectRatio = '16 / 9';
90
89
  this.iframe.style.maxHeight = '100%';
91
90
 
92
91
  this.player.element.parentNode.insertBefore(this.iframe, this.player.element);