@24i/bigscreen-sdk 1.0.11-alpha.2225 → 1.0.11

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@24i/bigscreen-sdk",
3
- "version": "1.0.11-alpha.2225",
3
+ "version": "1.0.11",
4
4
  "description": "SmartApps BIGscreen SDK monorepo",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -260,6 +260,7 @@
260
260
  "./utils/debounce": "./packages/utils/src/debounce.ts",
261
261
  "./utils/displayToggler": "./packages/utils/src/displayToggler.ts",
262
262
  "./utils/elementUtils": "./packages/utils/src/elementUtils.ts",
263
+ "./utils/forceReflow": "./packages/utils/src/forceReflow.ts",
263
264
  "./utils/generateUuid": "./packages/utils/src/generateUuid.ts",
264
265
  "./utils/memoryInfo": "./packages/utils/src/memoryInfo.ts",
265
266
  "./utils/nTimes": "./packages/utils/src/nTimes.ts",
@@ -1,4 +1,5 @@
1
1
  import { DeviceBase } from '@24i/bigscreen-sdk/driver-base';
2
+ import { DeviceAndroidTV } from '@24i/bigscreen-sdk/driver-androidtv';
2
3
  import { IStorage } from '@24i/bigscreen-sdk/storage/interface';
3
4
  import { getUsedJSHeapSize, getTotalJSHeapSize } from '@24i/bigscreen-sdk/utils/memoryInfo';
4
5
  import { ipify } from '@24i/bigscreen-sdk/ipify';
@@ -270,14 +271,23 @@ class TechnicalInfoProvider implements ITechnicalInfoProvider {
270
271
  return this.props.appCoreVersions || unknown;
271
272
  }
272
273
 
273
- getSourceDataLayout() {
274
+ async getWrapperVersion() {
275
+ let value;
276
+ const { unknown } = this.translations;
277
+ try {
278
+ value = await (this.props.device as DeviceAndroidTV).getWrapperVersion();
279
+ } catch (e) {}
280
+ return value || unknown;
281
+ }
282
+
283
+ async getSourceDataLayout() {
274
284
  const {
275
285
  username, profile, appVersion, firmwareVersion, deviceModel,
276
286
  deviceManufacturer, platform, player, serialNumber, localIpAddress,
277
287
  publicIpAddress, connectionType, macAddress, memoryHeapSize,
278
288
  usedLocalStorage, timeZone, dateAndTime, flavour, userAgent,
279
289
  emeSupport, emeDrmSystems, mseSupport, fourKSupport, resolution, baseUrl,
280
- appCoreVersions,
290
+ appCoreVersions, androidWrapperVersion,
281
291
  } = this.translations;
282
292
  const dataLayout: SourceDataLayout = [
283
293
  { label: username, getter: this.getUserName },
@@ -307,9 +317,21 @@ class TechnicalInfoProvider implements ITechnicalInfoProvider {
307
317
  { label: resolution, getter: this.getAppResolution },
308
318
  { label: baseUrl, getter: this.getBackendBaseUrl },
309
319
  ];
320
+
321
+ if (await this.isAndroidTV()) {
322
+ dataLayout.push(
323
+ { label: androidWrapperVersion, getter: this.getWrapperVersion },
324
+ );
325
+ }
326
+
310
327
  return dataLayout;
311
328
  }
312
329
 
330
+ async isAndroidTV() {
331
+ const platformName = await this.props.device.getPlatformName();
332
+ return platformName === 'AndroidTV';
333
+ }
334
+
313
335
  async loadDataLayout(dataLayout: SourceDataLayout) {
314
336
  const promises = map(dataLayout, async (item) => item.getter.call(this));
315
337
  const values = await Promise.all(promises);
@@ -319,7 +341,7 @@ class TechnicalInfoProvider implements ITechnicalInfoProvider {
319
341
  }
320
342
 
321
343
  async getInfoData() {
322
- const sourceDataLayout = this.getSourceDataLayout();
344
+ const sourceDataLayout = await this.getSourceDataLayout();
323
345
  return this.loadDataLayout(sourceDataLayout);
324
346
  }
325
347
  }
@@ -46,6 +46,10 @@ class Device {
46
46
  async getMacAddress() {
47
47
  return 'mac address';
48
48
  }
49
+
50
+ async getWrapperVersion() {
51
+ return 'android wrapper version';
52
+ }
49
53
  }
50
54
 
51
55
  export { Device };
@@ -3,6 +3,7 @@ export type Translations = TranslationKeys | { [K in keyof TranslationKeys]?: ne
3
3
  export type TranslationKeys = {
4
4
  unknown: string
5
5
  anonymous: string,
6
+ androidWrapperVersion: string,
6
7
  yes: string,
7
8
  no: string,
8
9
  prefixed: string,
@@ -41,6 +42,7 @@ export type TranslationKeys = {
41
42
  export const defaultTranslations = {
42
43
  unknown: 'UNKNOWN',
43
44
  anonymous: 'ANONYMOUS',
45
+ androidWrapperVersion: 'Android wrapper version',
44
46
  yes: 'Yes',
45
47
  no: 'No',
46
48
  prefixed: 'prefixed',
@@ -154,6 +154,10 @@ export class DeviceAndroidTV extends DeviceBase {
154
154
  return VolumeRange.UNSUPPORTED;
155
155
  }
156
156
 
157
+ async getWrapperVersion(): Promise<string> {
158
+ return this.platformInfo.appVersion;
159
+ }
160
+
157
161
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
158
162
  async setVolume(volumePercentage: number): Promise<void> {
159
163
  // not supported on AndroidTV platform
@@ -195,7 +199,9 @@ export class DeviceAndroidTV extends DeviceBase {
195
199
  }
196
200
 
197
201
  async getLocalIP(): Promise<string> {
198
- // not supported on AndroidTV platform
202
+ const ipv4RegExp = /\b(?:[0-9]{1,3}\.){3}[0-9]{1,3}\b/;
203
+ const ip = this.platformInfo.ipLocal.toString().match(ipv4RegExp);
204
+ if (ip) return ip.toString();
199
205
  return '';
200
206
  }
201
207
 
@@ -7,6 +7,10 @@ export const mockJavaScriptBridge: IJavaScriptBridge = {
7
7
  deviceBrand: 'Sony',
8
8
  deviceModel: 'BRAVIA 4K 2015',
9
9
  osVersion: '8.0.0',
10
+ appVersion: '0.0.1',
11
+ appFlavor: 'appstage-bigscreen',
12
+ drmKeySystems: '["W3C Clear Key", "Google Widevine"]',
13
+ ipLocal: '127.0.0.1',
10
14
  })
11
15
  ),
12
16
  bridgeJavaScriptToAndroid_Back: () => {},
@@ -2,7 +2,9 @@ interface JavaScriptBridge {
2
2
  /**
3
3
  * It returns a JSON string with device info.
4
4
  * @return JSON string with the information about the device brand, model and Android version.
5
- * Ex. '{"deviceBrand": "Sony", "deviceModel": "BRAVIA 4K 2015", "osVersion": "7.0" }'
5
+ * Ex. '{"deviceBrand": "Sony", "deviceModel": "BRAVIA 4K 2015", "osVersion": "7.0",
6
+ * "appFlavor": "appstage-bigscreen", "drmKeySystems": ["Widewine", "W3C ClearKey"],
7
+ * "ipLocal": "fe80::475:3045:2928:3a32%wlan0127.0.0.1", "appVersion": '0.0.1' }'
6
8
  */
7
9
  bridgeJavaScriptToAndroid_GetDeviceInfo: () => string,
8
10
  /**
@@ -24,6 +26,22 @@ export type DeviceInfo = {
24
26
  * Android version. Ex. "7.0"
25
27
  */
26
28
  osVersion: string,
29
+ /**
30
+ * Android wrapper version. Ex. "0.0.1"
31
+ */
32
+ appVersion: string,
33
+ /**
34
+ * Android flavor. Ex. "appstage-bigscreen"
35
+ */
36
+ appFlavor: string,
37
+ /**
38
+ * Android DRM key systems. Ex. ["Widewine", "W3C ClearKey"]
39
+ */
40
+ drmKeySystems: Array<string>,
41
+ /**
42
+ * Android local IP adress in array Ex. "fe80::475:3045:2928:3a32%wlan0127.0.0.1"
43
+ */
44
+ ipLocal: string,
27
45
  };
28
46
 
29
47
  export interface IJavaScriptBridge {
@@ -84,67 +84,115 @@ export class DataManager implements IDataManager {
84
84
  return [data[0].program!.startTime, data[data.length - 1].program!.endTime] as DataRange;
85
85
  }
86
86
 
87
- // TODO: Wait for fixes from @thepretiso before looking at this.
87
+ private isInterceptingDataRanges = (firstDataRange: DataRange, secondDataRange: DataRange) => (
88
+ (firstDataRange[START] < secondDataRange[END]
89
+ && firstDataRange[START] > secondDataRange[START])
90
+ || (secondDataRange[START] < firstDataRange[END]
91
+ && secondDataRange[START] > firstDataRange[START])
92
+ );
93
+
94
+ private findRangeBeforeAndAfter = (currentRange: DataRange) => {
95
+ const rangeAfterIndex = findIndex(
96
+ this.dataRanges,
97
+ (dataRange) => dataRange[END] >= currentRange[END],
98
+ );
99
+ if (rangeAfterIndex === -1) {
100
+ return {
101
+ rangeAfter: undefined,
102
+ rangeBefore: this.dataRanges[this.dataRanges.length - 1],
103
+ };
104
+ }
105
+ const rangeAfter = this.dataRanges[rangeAfterIndex];
106
+ const rangeBefore = this.dataRanges[rangeAfterIndex - 1];
107
+ return { rangeAfter, rangeBefore };
108
+ };
109
+
110
+ private generateDataRangesForward = (start: number, end: number, cellSize: number) => {
111
+ const dataRanges: DataRange[] = [[start, start + cellSize]];
112
+ let currentStart = start + cellSize;
113
+ let currentEnd = currentStart + cellSize;
114
+ while (currentStart < end) {
115
+ if (currentEnd >= end) {
116
+ dataRanges.push([currentStart, end]);
117
+ break;
118
+ }
119
+ dataRanges.push([currentStart, currentEnd]);
120
+ currentStart += cellSize;
121
+ currentEnd += cellSize;
122
+ }
123
+ return dataRanges;
124
+ };
125
+
126
+ private generateDataRangesBackward = (start: number, end: number, cellSize: number) => {
127
+ const dataRanges: DataRange[] = [[start - cellSize, start]];
128
+ let currentEnd = start - cellSize;
129
+ let currentStart = currentEnd - cellSize;
130
+ while (currentEnd > start) {
131
+ if (currentStart <= start) {
132
+ dataRanges.push([start, currentEnd]);
133
+ break;
134
+ }
135
+ dataRanges.push([currentStart, currentEnd]);
136
+ currentStart -= cellSize;
137
+ currentEnd -= cellSize;
138
+ }
139
+ return dataRanges;
140
+ };
141
+
88
142
  /* eslint-disable complexity */
89
143
  // eslint-disable-next-line sonarjs/cognitive-complexity
90
144
  private getRangesMissingFromDataInRange([start, end]: DataRange) {
91
- const rangesLength = this.dataRanges.length;
145
+ const currentRange = [start, end] as DataRange;
92
146
  const cellSize = TIMELINE_WIDTHS_TO_FETCH * this.timelineWidthMs;
93
- if (this.dataRanges.length === 0) return [[start, end]] as DataRange[];
94
- const firstRangeFrom = this.dataRanges[0][START];
95
- const firstRangeTo = this.dataRanges[0][END];
96
- if (end <= firstRangeFrom) return [[start, end]] as DataRange[];
97
- if (start < firstRangeFrom && end <= firstRangeTo) {
98
- return [
99
- [firstRangeFrom - cellSize, firstRangeFrom],
100
- ] as DataRange[];
101
- }
102
- const lastRangeFrom = this.dataRanges[rangesLength - 1][START];
103
- const lastRangeTo = this.dataRanges[rangesLength - 1][END];
104
- if (end > lastRangeTo && start <= lastRangeTo) {
105
- return [
106
- [lastRangeTo, lastRangeTo + cellSize],
107
- ] as DataRange[];
108
- }
109
- if (start >= lastRangeTo && end >= lastRangeTo && start - lastRangeTo >= cellSize) {
110
- return [
111
- [start, end],
112
- ] as DataRange[];
113
- }
114
- const ranges: DataRange[] = [];
115
- let newRange: DataRange = [start, end];
116
- for (let i = 0; i < rangesLength; i++) {
117
- const [rangeFrom, rangeTo] = this.dataRanges[i];
118
- if (start >= rangeTo) {
119
- if (start - rangeTo <= cellSize) {
120
- newRange[START] = rangeTo;
121
- } else {
122
- const newStart = lastRangeFrom - cellSize;
123
- newRange[START] = newStart;
147
+ if (this.dataRanges.length === 0) return [currentRange] as DataRange[];
148
+ const { rangeBefore, rangeAfter } = this.findRangeBeforeAndAfter(currentRange);
149
+ if (rangeAfter == null) {
150
+ const lastDataRange = this.dataRanges[this.dataRanges.length - 1];
151
+ if (this.isInterceptingDataRanges(lastDataRange, currentRange)) {
152
+ if (end > lastDataRange[END]) {
153
+ return this.generateDataRangesForward(lastDataRange[END], end, cellSize);
124
154
  }
125
- // eslint-disable-next-line no-continue
126
- continue;
127
- }
128
- if (start < rangeFrom) {
129
- if (end <= rangeFrom) {
130
- if (newRange[START] !== newRange[END]) ranges.push(newRange);
131
- return ranges;
155
+ if (start < lastDataRange[START]) {
156
+ return this.generateDataRangesBackward(start, lastDataRange[START], cellSize);
132
157
  }
133
- newRange[END] = rangeFrom;
134
- if (newRange[START] !== newRange[END]) ranges.push(newRange);
135
- if (end <= rangeTo) {
136
- return ranges;
158
+ }
159
+ return [currentRange] as DataRange[];
160
+ }
161
+ if (rangeBefore == null) {
162
+ const firstDataRange = this.dataRanges[0];
163
+ if (this.isInterceptingDataRanges(firstDataRange, currentRange)) {
164
+ if (end > firstDataRange[END]) {
165
+ return this.generateDataRangesForward(firstDataRange[END], end, cellSize);
137
166
  }
138
- newRange = [rangeTo, end];
139
- } else if (start < rangeTo) {
140
- if (end <= rangeTo) {
141
- return ranges;
167
+ if (start < firstDataRange[START]) {
168
+ return this.generateDataRangesBackward(firstDataRange[START], start, cellSize);
142
169
  }
143
- newRange = [rangeTo, end];
144
170
  }
171
+ return [currentRange] as DataRange[];
172
+ }
173
+ const rangeAfterFrom = rangeAfter[START];
174
+ const rangeBeforeTo = rangeBefore[END];
175
+ // Ranges are collapsed
176
+ if (rangeBeforeTo === rangeAfterFrom) return [];
177
+ // Both are not null and don't intercept
178
+ if (
179
+ !this.isInterceptingDataRanges(rangeAfter, currentRange)
180
+ && !this.isInterceptingDataRanges(rangeBefore, currentRange)
181
+ ) {
182
+ return [currentRange];
183
+ }
184
+ // Left intercepts
185
+ if (this.isInterceptingDataRanges(rangeBefore, currentRange)) {
186
+ return [rangeBefore, ...this.generateDataRangesForward(rangeBeforeTo, end, cellSize)];
187
+ }
188
+ // Right intercepts
189
+ if (this.isInterceptingDataRanges(rangeAfter, currentRange)) {
190
+ return [
191
+ ...this.generateDataRangesBackward(rangeAfterFrom, start, cellSize),
192
+ rangeAfter,
193
+ ];
145
194
  }
146
- if (newRange[START] !== newRange[END]) ranges.push(newRange);
147
- return ranges;
195
+ return [currentRange] as DataRange[];
148
196
  }
149
197
 
150
198
  private getMissingRanges(range: DataRange) {
@@ -100,7 +100,7 @@ export class MenuAndContentContainer<T extends string = string>
100
100
  * Handles opening with mouse.
101
101
  * @param event mouse event
102
102
  */
103
- onMouseMove = (event: MouseEvent) => {
103
+ onMouseDown = (event: MouseEvent) => {
104
104
  const menu = this.menu.current!;
105
105
  if (this.isMenuAvailable() && !menu.isOpen()) {
106
106
  menu.open();
@@ -108,6 +108,30 @@ export class MenuAndContentContainer<T extends string = string>
108
108
  }
109
109
  };
110
110
 
111
+ /**
112
+ * Handles hover with mouse.
113
+ * @param event mouse event
114
+ */
115
+ onMouseEnter = (event: MouseEvent) => {
116
+ const menu = this.menu.current!;
117
+ if (this.isMenuAvailable() && !menu.isOpen() && menu.mrcuEnter) {
118
+ menu.mrcuEnter();
119
+ stopEvent(event);
120
+ }
121
+ };
122
+
123
+ /**
124
+ * Handles hover end with mouse.
125
+ * @param event mouse event
126
+ */
127
+ onMouseLeave = (event: MouseEvent) => {
128
+ const menu = this.menu.current!;
129
+ if (this.isMenuAvailable() && !menu.isOpen() && menu.mrcuLeave) {
130
+ menu.mrcuLeave();
131
+ stopEvent(event);
132
+ }
133
+ };
134
+
111
135
  /**
112
136
  * Handles adding/removing mouse listeners based on whether the mouse is used or not.
113
137
  * @param event mouse active event
@@ -137,7 +161,9 @@ export class MenuAndContentContainer<T extends string = string>
137
161
  if (!this.listensToMouse) {
138
162
  this.listensToMouse = true;
139
163
  if (!this.menu.current!.isOpen()) this.mouseTriggerElementDisplayToggler.show();
140
- this.mouseTriggerElement.current!.addEventListener('mousemove', this.onMouseMove);
164
+ this.mouseTriggerElement.current!.addEventListener('mousedown', this.onMouseDown);
165
+ this.mouseTriggerElement.current!.addEventListener('mouseenter', this.onMouseEnter);
166
+ this.mouseTriggerElement.current!.addEventListener('mouseleave', this.onMouseLeave);
141
167
  }
142
168
  }
143
169
 
@@ -148,7 +174,9 @@ export class MenuAndContentContainer<T extends string = string>
148
174
  if (this.listensToMouse) {
149
175
  this.listensToMouse = false;
150
176
  if (!this.menu.current!.isOpen()) this.mouseTriggerElementDisplayToggler.hide();
151
- this.mouseTriggerElement.current!.removeEventListener('mousemove', this.onMouseMove);
177
+ this.mouseTriggerElement.current!.removeEventListener('mousedown', this.onMouseDown);
178
+ this.mouseTriggerElement.current!.removeEventListener('mouseenter', this.onMouseEnter);
179
+ this.mouseTriggerElement.current!.removeEventListener('mouseleave', this.onMouseLeave);
152
180
  }
153
181
  }
154
182
 
@@ -112,6 +112,14 @@ export interface Menu<
112
112
  * Function to close the menu.
113
113
  */
114
114
  close(): void,
115
+ /**
116
+ * Function for "hover" effect on the menu.
117
+ */
118
+ mrcuEnter?(): void,
119
+ /**
120
+ * Function for ending "hover" effect on the menu.
121
+ */
122
+ mrcuLeave?(): void,
115
123
  /**
116
124
  * Renders new menu items.
117
125
  * @param items new items to render
@@ -4,6 +4,7 @@ import { stopEvent } from '@24i/bigscreen-sdk/utils/stopEvent';
4
4
  import { ITimeUpdateEvent, IDurationChangeEvent } from '@24i/player-base';
5
5
  import { createTimeout } from '@24i/bigscreen-sdk/utils/timers';
6
6
  import { switchByKey } from '@24i/bigscreen-sdk/device/keymap';
7
+ import { getDisplayToggler } from '@24i/bigscreen-sdk/utils/displayToggler';
7
8
  import { PlayerTime } from './PlayerTime';
8
9
  import { getLiveProgressBasedOnCurrentTime } from './timeUtils';
9
10
  import { PlayerUICommonProps } from './types';
@@ -26,6 +27,8 @@ export class Seekbar extends Component<Props> implements IFocusable {
26
27
 
27
28
  seekTime = createRef<PlayerTime>();
28
29
 
30
+ displaySeekPoint = getDisplayToggler(this.seekPoint);
31
+
29
32
  leftConsecutiveness = 0;
30
33
 
31
34
  rightConsecutiveness = 0;
@@ -40,6 +43,8 @@ export class Seekbar extends Component<Props> implements IFocusable {
40
43
 
41
44
  isMouseDownPressed = false;
42
45
 
46
+ seekingDisabled = false;
47
+
43
48
  static defaultProps = {
44
49
  className: '',
45
50
  };
@@ -67,7 +72,7 @@ export class Seekbar extends Component<Props> implements IFocusable {
67
72
  }
68
73
 
69
74
  onMouseMove = (event: MouseEvent) => {
70
- if (!this.isMouseDownPressed) return;
75
+ if (!this.isMouseDownPressed || this.seekingDisabled) return;
71
76
  const { ui: { seeking } } = this.props;
72
77
  const wrapRect = this.wrap.current!.getBoundingClientRect();
73
78
  const clientX = event.clientX - wrapRect.left;
@@ -79,6 +84,7 @@ export class Seekbar extends Component<Props> implements IFocusable {
79
84
  };
80
85
 
81
86
  onMouseClick = (event: MouseEvent) => {
87
+ if (this.seekingDisabled) return;
82
88
  const { ui: { seeking } } = this.props;
83
89
  const wrapWidth = this.wrap.current!.offsetWidth;
84
90
  const percentage = (TO_PERCENT * event.offsetX) / wrapWidth;
@@ -115,6 +121,7 @@ export class Seekbar extends Component<Props> implements IFocusable {
115
121
  };
116
122
 
117
123
  onLeft = () => {
124
+ if (this.seekingDisabled) return true;
118
125
  const { ui } = this.props;
119
126
  this.leftConsecutiveness += 1;
120
127
  if (ui.seeking.isAutomaticallySeeking()) {
@@ -128,6 +135,7 @@ export class Seekbar extends Component<Props> implements IFocusable {
128
135
  };
129
136
 
130
137
  onRight = () => {
138
+ if (this.seekingDisabled) return true;
131
139
  const { ui } = this.props;
132
140
  const { seekTime } = ui.seeking;
133
141
  if (seekTime == null && this.currentTime + SLOW_TIME >= this.duration) return true;
@@ -222,6 +230,20 @@ export class Seekbar extends Component<Props> implements IFocusable {
222
230
  this.seekPoint.current!.style.left = `${percentTime}%`;
223
231
  }
224
232
 
233
+ disableSeeking() {
234
+ this.seekingDisabled = true;
235
+ this.displaySeekPoint.hide();
236
+ }
237
+
238
+ enableSeeking() {
239
+ this.seekingDisabled = false;
240
+ this.displaySeekPoint.show();
241
+ }
242
+
243
+ isSeekingDisabled() {
244
+ return this.seekingDisabled;
245
+ }
246
+
225
247
  render() {
226
248
  const { className } = this.props;
227
249
  return (
@@ -21,6 +21,7 @@ import { generateUuid } from '@24i/bigscreen-sdk/utils/generateUuid';
21
21
  - **[addClass](#addClass)** - safely adds class to reference.
22
22
  - **[debounce](#debounce)** - debounced function is only executed again after a period of not being called, supports leading, trailing (default) and both executions.
23
23
  - **[displayToggler](#displayToggler)** - exports `getDisplayToggler` that allows you to easily show/hide an element with guard preventing unnecessary DOM calls.
24
+ - **[forceReflow](#forceReflow)** - To force a DOM reflow on given DOM element.
24
25
  - **[generateUuid](#generateUuid)** - function to get UUIDv4 according to RFC 4122.
25
26
  - **[isMouseUsed](#isMouseUsed)** - **DEPRECATED** moved to `device` package.
26
27
  - **[memoryInfo](#memoryInfo)** - two functions that returns currently active segment size and total allocated size of JS heap, in bytes.
@@ -92,6 +93,18 @@ And returns `IDisplayToggler` interface with 4 functions:
92
93
  - `isVisible: () => boolean;` - returns true if the element is visible, false otherwise
93
94
  - `isHidden: () => boolean;` - returns true if the element is hidden, false otherwise
94
95
 
96
+ ### forceReflow
97
+ To force a DOM reflow on given DOM element. It can be needed for transitions
98
+ or other purposes.
99
+
100
+ It takes 1 param:
101
+ - `elementOrReference: HTMLElement | Reference<HTMLElement>` - DOM element or
102
+ reference to element on which reflow is called.
103
+
104
+ ```ts
105
+ forceReflow(elementRef);
106
+ ```
107
+
95
108
  ### generateUuid
96
109
  Returns an UUIDv4 according to RFC 4122, like ex. "123e4567-e89b-12d3-a456-426614174000".
97
110
 
@@ -0,0 +1,14 @@
1
+ import { Reference, unwrapReference } from '@24i/bigscreen-sdk/jsx';
2
+
3
+ /**
4
+ * To force a DOM reflow on given DOM element. It can be needed for transitions
5
+ * or other purposes.
6
+ * @param elementOrReference DOM element or reference to element on which reflow is called.
7
+ */
8
+ export const forceReflow = (
9
+ elementOrReference: HTMLElement | Reference<HTMLElement>,
10
+ ) => {
11
+ const element = unwrapReference(elementOrReference);
12
+ if (!element) return;
13
+ void element.offsetHeight; // eslint-disable-line no-void
14
+ };
@@ -6,6 +6,7 @@ export { addClass } from './addClass';
6
6
  export { removeClass } from './removeClass';
7
7
  export { debounce } from './debounce';
8
8
  export { wait } from './wait';
9
+ export { forceReflow } from './forceReflow';
9
10
  export { scaledImage } from './scaledImage';
10
11
  export { nTimes } from './nTimes';
11
12
  export { getDisplayToggler, DISPLAY_NONE } from './displayToggler';
@@ -84,7 +84,9 @@ function xhrSend(url: string, options: Partial<XhrOptions> = {}): Promise<XhrRes
84
84
  const xhr = new XMLHttpRequest();
85
85
 
86
86
  function onLoad() {
87
- const dataText = 'response' in xhr ? xhr.response : xhr.responseText;
87
+ const dataText = 'response' in (xhr as XMLHttpRequest)
88
+ ? xhr.response
89
+ : xhr.responseText;
88
90
  const dataJson = parseJson(dataText);
89
91
  const status = xhr.status === undefined ? HttpStatusCode.OK : xhr.status;
90
92
  const statusText = xhr.statusText === undefined ? 'OK' : xhr.statusText;