bitmovin-player-ui 3.75.0 → 3.76.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -93,7 +93,9 @@ export declare class SeekBar extends Component<SeekBarConfig> {
93
93
  private label;
94
94
  private seekBarMarkersContainer;
95
95
  private timelineMarkersHandler;
96
+ private uiBoundingRect;
96
97
  private player;
98
+ private uiManager;
97
99
  protected seekBarType: SeekBarType;
98
100
  protected isUiShown: boolean;
99
101
  /**
@@ -206,7 +208,8 @@ export declare class SeekBar extends Component<SeekBarConfig> {
206
208
  */
207
209
  getLabel(): SeekBarLabel | null;
208
210
  protected onSeekEvent(): void;
209
- protected onSeekPreviewEvent(percentage: number, scrubbing: boolean): void;
211
+ private updateLabelPosition;
212
+ protected onSeekPreviewEvent(percentage: number, targetOffsetPx: number, scrubbing: boolean): void;
210
213
  protected onSeekedEvent(percentage: number): void;
211
214
  /**
212
215
  * Gets the event that is fired when a scrubbing seek operation is started.
@@ -92,6 +92,12 @@ var SeekBar = exports.SeekBar = /** @class */ (function (_super) {
92
92
  _this.player.seek(targetPlaybackPosition, 'ui');
93
93
  }
94
94
  };
95
+ _this.updateLabelPosition = function (pixelPosition) {
96
+ if (!_this.uiBoundingRect) {
97
+ _this.uiBoundingRect = _this.uiManager.getUI().getDomElement().get(0).getBoundingClientRect();
98
+ }
99
+ _this.label.setPositionInBounds(pixelPosition, _this.uiBoundingRect);
100
+ };
95
101
  var keyStepIncrements = _this.config.keyStepIncrements || {
96
102
  leftRight: 1,
97
103
  upDown: 5,
@@ -154,6 +160,7 @@ var SeekBar = exports.SeekBar = /** @class */ (function (_super) {
154
160
  if (configureSeek === void 0) { configureSeek = true; }
155
161
  _super.prototype.configure.call(this, player, uimanager);
156
162
  this.player = player;
163
+ this.uiManager = uimanager;
157
164
  // Apply scaling transform to the backdrop bar to have all bars rendered similarly
158
165
  // (the call must be up here to be executed for the volume slider as well)
159
166
  this.setPosition(this.seekBarBackdrop, 100);
@@ -354,6 +361,7 @@ var SeekBar = exports.SeekBar = /** @class */ (function (_super) {
354
361
  // is positioned absolutely and must therefore be updated when the size of the seekbar changes.
355
362
  player.on(player.exports.PlayerEvent.PlayerResized, function () {
356
363
  _this.refreshPlaybackPosition();
364
+ _this.uiBoundingRect = _this.uiManager.getUI().getDomElement().get(0).getBoundingClientRect();
357
365
  });
358
366
  // Additionally, when this code is called, the seekbar is not part of the UI yet and therefore does not have a size,
359
367
  // resulting in a wrong initial position of the marker. Refreshing it once the UI is configured solved this issue.
@@ -551,10 +559,12 @@ var SeekBar = exports.SeekBar = /** @class */ (function (_super) {
551
559
  if (_this.player.vr != null) {
552
560
  e.stopPropagation();
553
561
  }
554
- var targetPercentage = 100 * _this.getOffset(e);
562
+ var offset = _this.getOffset(e);
563
+ var targetPercentage = 100 * offset;
564
+ var seekPositionPx = offset * _this.seekBar.width();
555
565
  _this.setSeekPosition(targetPercentage);
556
566
  _this.setPlaybackPosition(targetPercentage);
557
- _this.onSeekPreviewEvent(targetPercentage, true);
567
+ _this.onSeekPreviewEvent(targetPercentage, seekPositionPx, true);
558
568
  };
559
569
  var mouseTouchUpHandler = function (e) {
560
570
  var _a;
@@ -599,9 +609,11 @@ var SeekBar = exports.SeekBar = /** @class */ (function (_super) {
599
609
  if (seeking) {
600
610
  mouseTouchMoveHandler(e);
601
611
  }
602
- var position = 100 * _this.getOffset(e);
603
- _this.setSeekPosition(position);
604
- _this.onSeekPreviewEvent(position, false);
612
+ var offset = _this.getOffset(e);
613
+ var seekPositionPercentage = 100 * offset;
614
+ var seekPositionPx = offset * _this.seekBar.width();
615
+ _this.setSeekPosition(seekPositionPercentage);
616
+ _this.onSeekPreviewEvent(seekPositionPercentage, seekPositionPx, false);
605
617
  if (_this.hasLabel() && _this.getLabel().isHidden()) {
606
618
  _this.getLabel().show();
607
619
  }
@@ -815,7 +827,7 @@ var SeekBar = exports.SeekBar = /** @class */ (function (_super) {
815
827
  SeekBar.prototype.onSeekEvent = function () {
816
828
  this.seekBarEvents.onSeek.dispatch(this);
817
829
  };
818
- SeekBar.prototype.onSeekPreviewEvent = function (percentage, scrubbing) {
830
+ SeekBar.prototype.onSeekPreviewEvent = function (percentage, targetOffsetPx, scrubbing) {
819
831
  var snappedMarker = this.timelineMarkersHandler && this.timelineMarkersHandler.getMarkerAtPosition(percentage);
820
832
  var seekPositionPercentage = percentage;
821
833
  if (snappedMarker) {
@@ -837,9 +849,7 @@ var SeekBar = exports.SeekBar = /** @class */ (function (_super) {
837
849
  }
838
850
  }
839
851
  if (this.label) {
840
- this.label.getDomElement().css({
841
- 'left': seekPositionPercentage + '%',
842
- });
852
+ this.updateLabelPosition(targetOffsetPx);
843
853
  }
844
854
  this.seekBarEvents.onSeekPreview.dispatch(this, {
845
855
  scrubbing: scrubbing,
@@ -22,9 +22,12 @@ export declare class SeekBarLabel extends Container<SeekBarLabelConfig> {
22
22
  private appliedMarkerCssClasses;
23
23
  private player;
24
24
  private uiManager;
25
+ private readonly container;
26
+ private readonly caret;
25
27
  constructor(config?: SeekBarLabelConfig);
26
28
  configure(player: PlayerAPI, uimanager: UIInstanceManager): void;
27
29
  private handleSeekPreview;
30
+ setPositionInBounds(seekPositionPx: number, bounds: DOMRect): void;
28
31
  /**
29
32
  * Sets arbitrary text on the label.
30
33
  * @param text the text to show on the label
@@ -78,18 +78,20 @@ var SeekBarLabel = /** @class */ (function (_super) {
78
78
  _this.titleLabel = new label_1.Label({ cssClasses: ['seekbar-label-title'] });
79
79
  _this.thumbnail = new component_1.Component({ cssClasses: ['seekbar-thumbnail'], role: 'img' });
80
80
  _this.thumbnailImageLoader = new imageloader_1.ImageLoader();
81
+ _this.container = new container_1.Container({
82
+ components: [
83
+ _this.thumbnail,
84
+ new container_1.Container({
85
+ components: [_this.titleLabel, _this.timeLabel],
86
+ cssClass: 'seekbar-label-metadata',
87
+ }),
88
+ ],
89
+ cssClass: 'seekbar-label-inner',
90
+ });
91
+ _this.caret = new label_1.Label({ cssClasses: ['seekbar-label-caret'] });
81
92
  _this.config = _this.mergeConfig(config, {
82
93
  cssClass: 'ui-seekbar-label',
83
- components: [new container_1.Container({
84
- components: [
85
- _this.thumbnail,
86
- new container_1.Container({
87
- components: [_this.titleLabel, _this.timeLabel],
88
- cssClass: 'seekbar-label-metadata',
89
- })
90
- ],
91
- cssClass: 'seekbar-label-inner',
92
- })],
94
+ components: [_this.container, _this.caret],
93
95
  hidden: true,
94
96
  }, _this.config);
95
97
  return _this;
@@ -111,6 +113,25 @@ var SeekBarLabel = /** @class */ (function (_super) {
111
113
  uimanager.getConfig().events.onUpdated.subscribe(init);
112
114
  init();
113
115
  };
116
+ SeekBarLabel.prototype.setPositionInBounds = function (seekPositionPx, bounds) {
117
+ this.getDomElement().css('left', seekPositionPx + 'px');
118
+ // Check parent container as it has a padding that needs to be considered
119
+ var labelBounding = this.container.getDomElement().get(0).parentElement.getBoundingClientRect();
120
+ var preventOverflowOffset = 0;
121
+ if (labelBounding.right > bounds.right) {
122
+ preventOverflowOffset = labelBounding.right - bounds.right;
123
+ }
124
+ else if (labelBounding.left < bounds.left) {
125
+ preventOverflowOffset = labelBounding.left - bounds.left;
126
+ }
127
+ if (preventOverflowOffset !== 0) {
128
+ this.getDomElement().css('left', seekPositionPx - preventOverflowOffset + 'px');
129
+ this.caret.getDomElement().css('transform', "translateX(".concat(preventOverflowOffset, "px)"));
130
+ }
131
+ else {
132
+ this.caret.getDomElement().css('transform', null);
133
+ }
134
+ };
114
135
  /**
115
136
  * Sets arbitrary text on the label.
116
137
  * @param text the text to show on the label
@@ -16,7 +16,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
17
  exports.ClickOverlay = exports.VolumeControlButton = exports.TitleBar = exports.SubtitleSelectBox = exports.SubtitleOverlay = exports.SeekBarLabel = exports.RecommendationOverlay = exports.ErrorMessageOverlay = exports.Component = exports.CastToggleButton = exports.CastStatusOverlay = exports.AudioTrackSelectBox = exports.AudioQualitySelectBox = exports.Label = exports.Container = exports.UIContainer = exports.Watermark = exports.VRToggleButton = exports.VolumeToggleButton = exports.VideoQualitySelectBox = exports.ToggleButton = exports.SettingsToggleButton = exports.SettingsPanel = exports.ItemSelectionList = exports.SelectBox = exports.SeekBar = exports.PlaybackToggleButton = exports.PlaybackTimeLabelMode = exports.PlaybackTimeLabel = exports.HugePlaybackToggleButton = exports.FullscreenToggleButton = exports.ControlBar = exports.Button = exports.ListOrientation = exports.ListNavigationGroup = exports.RootNavigationGroup = exports.NavigationGroup = exports.SpatialNavigation = exports.I18n = exports.i18n = exports.ErrorUtils = exports.StorageUtils = exports.BrowserUtils = exports.UIUtils = exports.PlayerUtils = exports.StringUtils = exports.ArrayUtils = exports.DemoFactory = exports.UIFactory = exports.version = void 0;
18
18
  exports.ListSelector = exports.QuickSeekButton = exports.ReplayButton = exports.SettingsPanelItem = exports.SubtitleSettingsPanelPage = exports.SettingsPanelPageOpenButton = exports.SettingsPanelPageBackButton = exports.SettingsPanelPage = exports.AudioTrackListBox = exports.SubtitleListBox = exports.ListBox = exports.SubtitleSettingsResetButton = exports.WindowOpacitySelectBox = exports.WindowColorSelectBox = exports.SubtitleSettingsLabel = exports.SubtitleSettingSelectBox = exports.FontSizeSelectBox = exports.FontOpacitySelectBox = exports.FontFamilySelectBox = exports.FontColorSelectBox = exports.CharacterEdgeSelectBox = exports.BackgroundOpacitySelectBox = exports.BackgroundColorSelectBox = exports.Spacer = exports.PictureInPictureToggleButton = exports.VolumeSlider = exports.AirPlayToggleButton = exports.MetadataLabelContent = exports.MetadataLabel = exports.CloseButton = exports.PlaybackToggleOverlay = exports.CastUIContainer = exports.BufferingOverlay = exports.HugeReplayButton = exports.PlaybackSpeedSelectBox = exports.AdClickOverlay = exports.AdMessageLabel = exports.AdSkipButton = void 0;
19
- exports.version = '3.75.0';
19
+ exports.version = '3.76.0';
20
20
  // Management
21
21
  __exportStar(require("./uimanager"), exports);
22
22
  __exportStar(require("./uiconfig"), exports);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bitmovin-player-ui",
3
- "version": "3.75.0",
3
+ "version": "3.76.0",
4
4
  "description": "Bitmovin Player UI Framework",
5
5
  "main": "./dist/js/framework/main.js",
6
6
  "types": "./dist/js/framework/main.d.ts",
@@ -114,7 +114,7 @@ describe('SeekBar', () => {
114
114
 
115
115
  jest.spyOn(playerMock, 'getSeekableRange').mockImplementation(() => ({start: 26, end: 30}));
116
116
 
117
- seekbar['onSeekPreviewEvent'](40, true)
117
+ seekbar['onSeekPreviewEvent'](40, 100, true);
118
118
 
119
119
  playerMock.eventEmitter.fireSegmentRequestFinished();
120
120
 
@@ -127,19 +127,19 @@ describe('SeekBar', () => {
127
127
  it('will update the scrubber location after a successful segment request download and the user is not scrubbing', () => {
128
128
  jest.spyOn(playerMock, 'getSeekableRange').mockImplementation(() => ({start: 26, end: 30}));
129
129
 
130
- seekbar['onSeekPreviewEvent'](18, false)
130
+ seekbar['onSeekPreviewEvent'](18, 100, false);
131
131
 
132
132
  playerMock.eventEmitter.fireSegmentRequestFinished();
133
133
 
134
134
  expect(setPlaybackPositionSpy).toHaveBeenLastCalledWith(18);
135
- expect(setBufferPositionSpy).toHaveBeenLastCalledWith(18)
135
+ expect(setBufferPositionSpy).toHaveBeenLastCalledWith(18);
136
136
  });
137
137
  });
138
138
  });
139
139
 
140
140
  describe('group playback', () => {
141
141
  beforeEach(() => {
142
- jest.spyOn(playerMock, 'getDuration').mockReturnValue(0)
142
+ jest.spyOn(playerMock, 'getDuration').mockReturnValue(0);
143
143
  seekbar.configure(playerMock, uiInstanceManagerMock);
144
144
  });
145
145
 
@@ -2,6 +2,7 @@ import { MockHelper, TestingPlayerAPI } from '../helper/MockHelper';
2
2
  import { UIInstanceManager } from '../../src/ts/uimanager';
3
3
  import { SeekBarLabel } from '../../src/ts/components/seekbarlabel';
4
4
  import { SeekPreviewEventArgs } from '../../src/ts/components/seekbar';
5
+ import { DOM } from '../../src/ts/dom';
5
6
 
6
7
  let playerMock: TestingPlayerAPI;
7
8
  let uiInstanceManagerMock: UIInstanceManager;
@@ -94,4 +95,97 @@ describe('SeekBarLabel', () => {
94
95
  });
95
96
  });
96
97
  });
98
+
99
+ describe("calculates correct values for thumbnail positioning", () => {
100
+ const uiContainerBoundingRect = {
101
+ x: 200,
102
+ y: 150,
103
+ width: 1600,
104
+ height: 900,
105
+ top: 150,
106
+ right: 1800,
107
+ bottom: 1050,
108
+ left: 200,
109
+ } as DOMRect;
110
+
111
+ let containerGetDomElementMock: () => jest.Mocked<DOM>;
112
+ let caretGetDomElementMock: () => jest.Mocked<DOM>;
113
+
114
+ beforeEach(() => {
115
+ containerGetDomElementMock = jest
116
+ .fn()
117
+ .mockReturnValue(MockHelper.generateDOMMock());
118
+
119
+ containerGetDomElementMock().get = jest.fn().mockReturnValue({
120
+ parentElement: jest.fn().mockReturnValue({
121
+ getBoundingClientRect: jest.fn(),
122
+ }),
123
+ });
124
+
125
+ caretGetDomElementMock = jest.fn().mockReturnValue(MockHelper.generateDOMMock());
126
+
127
+ seekbarLabel["container"].getDomElement = containerGetDomElementMock;
128
+ seekbarLabel["caret"].getDomElement = caretGetDomElementMock;
129
+ });
130
+
131
+ it("when thumbnail within UI container bounds", () => {
132
+ const labelRect = {
133
+ x: 400,
134
+ y: 700,
135
+ width: 200,
136
+ height: 120,
137
+ top: 700,
138
+ right: 600,
139
+ bottom: 820,
140
+ left: 400,
141
+ } as DOMRect;
142
+
143
+ containerGetDomElementMock().get(0).parentElement!.getBoundingClientRect =
144
+ jest.fn().mockReturnValue(labelRect);
145
+
146
+ seekbarLabel.setPositionInBounds(100, uiContainerBoundingRect);
147
+
148
+ expect(caretGetDomElementMock().css).toHaveBeenCalledWith('transform', null);
149
+ });
150
+
151
+ it("when thumbnail would overflow UI container leftside", () => {
152
+ const labelRect = {
153
+ x: 180,
154
+ y: 700,
155
+ width: 200,
156
+ height: 120,
157
+ top: 700,
158
+ right: 380,
159
+ bottom: 820,
160
+ left: 180,
161
+ } as DOMRect;
162
+
163
+ containerGetDomElementMock().get(0).parentElement!.getBoundingClientRect =
164
+ jest.fn().mockReturnValue(labelRect);
165
+
166
+ seekbarLabel.setPositionInBounds(100, uiContainerBoundingRect);
167
+
168
+ expect(seekbarLabel.getDomElement().css).toHaveBeenCalledWith('left', '120px');
169
+ });
170
+
171
+ it("when thumbnail would overflow UI container rightside", () => {
172
+ const labelRect = {
173
+ x: 1650,
174
+ y: 700,
175
+ width: 200,
176
+ height: 120,
177
+ top: 700,
178
+ right: 1850,
179
+ bottom: 820,
180
+ left: 1650,
181
+ } as DOMRect;
182
+
183
+ containerGetDomElementMock().get(0).parentElement!.getBoundingClientRect =
184
+ jest.fn().mockReturnValue(labelRect);
185
+
186
+ seekbarLabel.setPositionInBounds(100, uiContainerBoundingRect);
187
+
188
+ expect(seekbarLabel.getDomElement().css).toHaveBeenCalledWith('left', '50px');
189
+ });
190
+ });
97
191
  });
@@ -21,27 +21,28 @@
21
21
 
22
22
  > .#{$prefix}-container-wrapper {
23
23
  @extend %center-on-left-edge;
24
+
25
+ padding-left: 1em;
26
+ padding-right: 1em;
27
+ }
28
+
29
+ // bottom arrow from http://www.cssarrowplease.com/
30
+ .#{$prefix}-seekbar-label-caret {
31
+ border: solid transparent;
32
+ border-color: transparent;
33
+ border-top-color: $color-primary;
34
+ border-width: .5em;
35
+ height: 0;
36
+ margin-left: -.5em;
37
+ pointer-events: none;
38
+ position: absolute;
39
+ top: 100%;
40
+ width: 0;
24
41
  }
25
42
 
26
43
  .#{$prefix}-seekbar-label-inner {
27
44
  border-bottom: .2em solid $color-primary;
28
45
 
29
- // bottom arrow from http://www.cssarrowplease.com/
30
- &::after {
31
- border: solid transparent;
32
- border-color: transparent;
33
- border-top-color: $color-primary;
34
- border-width: .5em;
35
- content: ' ';
36
- height: 0;
37
- left: 50%;
38
- margin-left: -.5em;
39
- pointer-events: none;
40
- position: absolute;
41
- top: 100%;
42
- width: 0;
43
- }
44
-
45
46
  > .#{$prefix}-container-wrapper {
46
47
  position: relative;
47
48
 
@@ -114,7 +114,10 @@ export class SeekBar extends Component<SeekBarConfig> {
114
114
  private seekBarMarkersContainer: DOM;
115
115
  private timelineMarkersHandler: TimelineMarkersHandler;
116
116
 
117
+ private uiBoundingRect: DOMRect;
118
+
117
119
  private player: PlayerAPI;
120
+ private uiManager: UIInstanceManager;
118
121
 
119
122
  protected seekBarType: SeekBarType;
120
123
 
@@ -220,6 +223,7 @@ export class SeekBar extends Component<SeekBarConfig> {
220
223
  super.configure(player, uimanager);
221
224
 
222
225
  this.player = player;
226
+ this.uiManager = uimanager;
223
227
 
224
228
  // Apply scaling transform to the backdrop bar to have all bars rendered similarly
225
229
  // (the call must be up here to be executed for the volume slider as well)
@@ -453,6 +457,7 @@ export class SeekBar extends Component<SeekBarConfig> {
453
457
  // is positioned absolutely and must therefore be updated when the size of the seekbar changes.
454
458
  player.on(player.exports.PlayerEvent.PlayerResized, () => {
455
459
  this.refreshPlaybackPosition();
460
+ this.uiBoundingRect = this.uiManager.getUI().getDomElement().get(0).getBoundingClientRect();
456
461
  });
457
462
  // Additionally, when this code is called, the seekbar is not part of the UI yet and therefore does not have a size,
458
463
  // resulting in a wrong initial position of the marker. Refreshing it once the UI is configured solved this issue.
@@ -716,10 +721,13 @@ export class SeekBar extends Component<SeekBarConfig> {
716
721
  e.stopPropagation();
717
722
  }
718
723
 
719
- let targetPercentage = 100 * this.getOffset(e);
724
+ const offset = this.getOffset(e);
725
+ const targetPercentage = 100 * offset;
726
+ const seekPositionPx = offset * this.seekBar.width();
727
+
720
728
  this.setSeekPosition(targetPercentage);
721
729
  this.setPlaybackPosition(targetPercentage);
722
- this.onSeekPreviewEvent(targetPercentage, true);
730
+ this.onSeekPreviewEvent(targetPercentage, seekPositionPx, true);
723
731
  };
724
732
 
725
733
  let mouseTouchUpHandler = (e: MouseEvent | TouchEvent) => {
@@ -777,10 +785,12 @@ export class SeekBar extends Component<SeekBarConfig> {
777
785
  mouseTouchMoveHandler(e);
778
786
  }
779
787
 
780
- let position = 100 * this.getOffset(e);
781
- this.setSeekPosition(position);
788
+ const offset = this.getOffset(e);
789
+ const seekPositionPercentage = 100 * offset;
790
+ const seekPositionPx = offset * this.seekBar.width();
782
791
 
783
- this.onSeekPreviewEvent(position, false);
792
+ this.setSeekPosition(seekPositionPercentage);
793
+ this.onSeekPreviewEvent(seekPositionPercentage, seekPositionPx, false);
784
794
 
785
795
  if (this.hasLabel() && this.getLabel().isHidden()) {
786
796
  this.getLabel().show();
@@ -1020,7 +1030,15 @@ export class SeekBar extends Component<SeekBarConfig> {
1020
1030
  this.seekBarEvents.onSeek.dispatch(this);
1021
1031
  }
1022
1032
 
1023
- protected onSeekPreviewEvent(percentage: number, scrubbing: boolean) {
1033
+ private updateLabelPosition = (pixelPosition: number) => {
1034
+ if (!this.uiBoundingRect) {
1035
+ this.uiBoundingRect = this.uiManager.getUI().getDomElement().get(0).getBoundingClientRect();
1036
+ }
1037
+
1038
+ this.label.setPositionInBounds(pixelPosition, this.uiBoundingRect);
1039
+ };
1040
+
1041
+ protected onSeekPreviewEvent(percentage: number, targetOffsetPx: number, scrubbing: boolean) {
1024
1042
  let snappedMarker = this.timelineMarkersHandler && this.timelineMarkersHandler.getMarkerAtPosition(percentage);
1025
1043
 
1026
1044
  let seekPositionPercentage = percentage;
@@ -1043,9 +1061,7 @@ export class SeekBar extends Component<SeekBarConfig> {
1043
1061
  }
1044
1062
 
1045
1063
  if (this.label) {
1046
- this.label.getDomElement().css({
1047
- 'left': seekPositionPercentage + '%',
1048
- });
1064
+ this.updateLabelPosition(targetOffsetPx);
1049
1065
  }
1050
1066
 
1051
1067
  this.seekBarEvents.onSeekPreview.dispatch(this, {
@@ -36,6 +36,8 @@ export class SeekBarLabel extends Container<SeekBarLabelConfig> {
36
36
  private appliedMarkerCssClasses: string[] = [];
37
37
  private player: PlayerAPI;
38
38
  private uiManager: UIInstanceManager;
39
+ private readonly container: Container<ContainerConfig>;
40
+ private readonly caret: Label<LabelConfig>;
39
41
 
40
42
  constructor(config: SeekBarLabelConfig = {}) {
41
43
  super(config);
@@ -45,17 +47,22 @@ export class SeekBarLabel extends Container<SeekBarLabelConfig> {
45
47
  this.thumbnail = new Component({ cssClasses: ['seekbar-thumbnail'], role: 'img' });
46
48
  this.thumbnailImageLoader = new ImageLoader();
47
49
 
50
+ this.container = new Container({
51
+ components: [
52
+ this.thumbnail,
53
+ new Container({
54
+ components: [this.titleLabel, this.timeLabel],
55
+ cssClass: 'seekbar-label-metadata',
56
+ }),
57
+ ],
58
+ cssClass: 'seekbar-label-inner',
59
+ });
60
+
61
+ this.caret = new Label({ cssClasses: ['seekbar-label-caret'] });
62
+
48
63
  this.config = this.mergeConfig(config, {
49
64
  cssClass: 'ui-seekbar-label',
50
- components: [new Container({
51
- components: [
52
- this.thumbnail,
53
- new Container({
54
- components: [this.titleLabel, this.timeLabel],
55
- cssClass: 'seekbar-label-metadata',
56
- })],
57
- cssClass: 'seekbar-label-inner',
58
- })],
65
+ components: [this.container, this.caret],
59
66
  hidden: true,
60
67
  }, this.config);
61
68
  }
@@ -129,6 +136,28 @@ export class SeekBarLabel extends Container<SeekBarLabelConfig> {
129
136
  }
130
137
  };
131
138
 
139
+ public setPositionInBounds(seekPositionPx: number, bounds: DOMRect) {
140
+ this.getDomElement().css('left', seekPositionPx + 'px');
141
+
142
+ // Check parent container as it has a padding that needs to be considered
143
+ const labelBounding = this.container.getDomElement().get(0).parentElement.getBoundingClientRect();
144
+
145
+ let preventOverflowOffset = 0;
146
+ if (labelBounding.right > bounds.right) {
147
+ preventOverflowOffset = labelBounding.right - bounds.right;
148
+ } else if (labelBounding.left < bounds.left) {
149
+ preventOverflowOffset = labelBounding.left - bounds.left;
150
+ }
151
+
152
+ if (preventOverflowOffset !== 0) {
153
+ this.getDomElement().css('left', seekPositionPx - preventOverflowOffset + 'px');
154
+
155
+ this.caret.getDomElement().css('transform', `translateX(${preventOverflowOffset}px)`);
156
+ } else {
157
+ this.caret.getDomElement().css('transform', null);
158
+ }
159
+ }
160
+
132
161
  /**
133
162
  * Sets arbitrary text on the label.
134
163
  * @param text the text to show on the label