@applicaster/zapp-react-native-utils 12.0.0-alpha.4337072244 → 12.0.0-alpha.4342066143

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.
@@ -0,0 +1,25 @@
1
+ import * as R from "ramda";
2
+ import { appStore } from "@applicaster/zapp-react-native-redux/AppStore";
3
+
4
+ export const getVideojsAnalyticPluginsConfig = async (entry: ZappEntry) => {
5
+ let videojsConfiguration = {};
6
+
7
+ const plugins = appStore.get("plugins");
8
+ const analyticPlugins = R.filter(R.propEq("type", "analytics"), plugins);
9
+
10
+ const configurations = await Promise.all(
11
+ analyticPlugins.map(async (plugin) => {
12
+ const { configuration, module } = plugin;
13
+
14
+ return module?.getPlayerConfiguration?.(configuration, entry);
15
+ })
16
+ );
17
+
18
+ configurations.forEach((analyticConfig) => {
19
+ if (typeof analyticConfig === "object" && analyticConfig !== null) {
20
+ videojsConfiguration = { ...videojsConfiguration, ...analyticConfig };
21
+ }
22
+ });
23
+
24
+ return videojsConfiguration;
25
+ };
@@ -3,7 +3,9 @@
3
3
  exports[`focusManager should be defined 1`] = `
4
4
  Object {
5
5
  "blur": [Function],
6
+ "disableAutomaticFocus": [Function],
6
7
  "disableFocus": [Function],
8
+ "enableAutomaticFocus": [Function],
7
9
  "enableFocus": [Function],
8
10
  "focusableTree": Tree {
9
11
  "loadingCounter": 0,
@@ -28,13 +30,11 @@ Object {
28
30
  "on": [Function],
29
31
  "press": [Function],
30
32
  "pressOut": [Function],
31
- "recoverFocus": [Function],
32
33
  "register": [Function],
33
34
  "removeHandler": [Function],
34
35
  "setFocus": [Function],
35
- "setFocusOnContent": [Function],
36
- "setFocusOnMenu": [Function],
37
36
  "setInitialFocus": [Function],
37
+ "setupInitialFocus": [Function],
38
38
  "unregister": [Function],
39
39
  }
40
40
  `;
@@ -14,8 +14,10 @@ describe("focusManager", () => {
14
14
  expect(focusManager).toMatchSnapshot();
15
15
  });
16
16
 
17
- it("manages to land initial focus", () => {
17
+ it("enableAutomaticFocus should not block setting focus", () => {
18
18
  const mockSetFocus = jest.fn();
19
+ focusManager.setupInitialFocus("1", true);
20
+ focusManager.enableAutomaticFocus();
19
21
 
20
22
  focusManager.register({
21
23
  id: "1",
@@ -29,13 +31,31 @@ describe("focusManager", () => {
29
31
  },
30
32
  });
31
33
 
32
- const { success } = focusManager.setInitialFocus();
33
-
34
- expect(success).toBe(true);
35
34
  expect(mockSetFocus).toBeCalledTimes(1);
36
35
  expect(mockSetFocus).toBeCalledWith(null);
37
36
  });
38
37
 
38
+ it("enableAutomaticFocus should block setting focus", () => {
39
+ const mockSetFocus = jest.fn();
40
+ focusManager.setupInitialFocus("1", true);
41
+ focusManager.disableAutomaticFocus();
42
+
43
+ focusManager.register({
44
+ id: "1",
45
+ component: {
46
+ setFocus: mockSetFocus,
47
+ willReceiveFocus: function () {},
48
+ isMounted: function () {
49
+ return true;
50
+ },
51
+ props: { groupId: "121" },
52
+ },
53
+ });
54
+
55
+ expect(mockSetFocus).toBeCalledTimes(0);
56
+ expect(mockSetFocus).not.toBeCalledWith(null);
57
+ });
58
+
39
59
  describe("register", () => {});
40
60
  describe("unregister", () => {});
41
61
  describe("find", () => {});
@@ -18,6 +18,7 @@ describe("focusManager", () => {
18
18
  "on",
19
19
  "invokeHandler",
20
20
  "removeHandler",
21
+ "setupInitialFocus",
21
22
  "longPress",
22
23
  "pressOut",
23
24
  "enableFocus",
@@ -26,11 +27,10 @@ describe("focusManager", () => {
26
27
  "isFocusDisabled",
27
28
  "getCurrentFocusNode",
28
29
  "focusableTree",
30
+ "disableAutomaticFocus",
31
+ "enableAutomaticFocus",
29
32
  "getGroupRootById",
30
33
  "isGroupItemFocused",
31
- "setFocusOnMenu",
32
- "setFocusOnContent",
33
- "recoverFocus",
34
34
  ];
35
35
 
36
36
  expectedMethods.forEach((method) => {
@@ -1,24 +1,9 @@
1
1
  import * as R from "ramda";
2
- import {
3
- isNilOrEmpty,
4
- isNotNil,
5
- } from "@applicaster/zapp-react-native-utils/reactUtils/helpers";
6
-
7
2
  import { Tree } from "./treeDataStructure/Tree";
8
- import {
9
- findFocusableNode,
10
- findPriorityItem,
11
- isTransitionHappening,
12
- } from "./treeDataStructure/Utils";
3
+ import { findFocusableNode, findPriorityItem } from "./treeDataStructure/Utils";
13
4
  import { subscriber } from "../../functionUtils";
14
5
  import * as FOCUS_EVENTS from "./events";
15
6
  import { coreLogger } from "../../logger";
16
- import {
17
- waitForActiveScreen,
18
- waitForContent,
19
- getContentNode,
20
- getNavbarNode,
21
- } from "./utils";
22
7
 
23
8
  const logger = coreLogger.addSubsystem("focusManager");
24
9
 
@@ -59,9 +44,21 @@ export const focusManager = (function () {
59
44
  // focusable node which currently holds the focus
60
45
  let currentFocusNode = null;
61
46
  let lastFocusedId = null;
47
+ let initialFocusOnContent = null;
62
48
  let focusDisabled = false;
49
+ let focusAutoEnabled = true;
63
50
  const eventHandler = subscriber();
64
51
 
52
+ function disableAutomaticFocus() {
53
+ logger.log({ message: "disableAutomaticFocus" });
54
+ focusAutoEnabled = false;
55
+ }
56
+
57
+ function enableAutomaticFocus() {
58
+ logger.log({ message: "focusManager: enableAutomaticFocus" });
59
+ focusAutoEnabled = true;
60
+ }
61
+
65
62
  function enableFocus() {
66
63
  focusDisabled = false;
67
64
  }
@@ -96,7 +93,7 @@ export const focusManager = (function () {
96
93
  !currentFocusable.isGroup &&
97
94
  currentFocusable.isMounted()
98
95
  ) {
99
- currentFocusable.blur(direction);
96
+ currentFocusable.blur(currentFocusable, direction);
100
97
  currentFocusable.hasLostFocus(getCurrentFocus(), direction);
101
98
  }
102
99
  }
@@ -229,52 +226,32 @@ export const focusManager = (function () {
229
226
 
230
227
  logger.log({ message: "setInitialFocus", data: { preferredFocus } });
231
228
 
232
- if (isNilOrEmpty(preferredFocus)) {
233
- return { success: false };
234
- }
235
-
236
- const focusCandidate = preferredFocus[0];
237
- const focusableItem = focusCandidate.component;
238
-
239
- const currentFocusedId = R.path(["props", "id"], getCurrentFocus());
240
- const nextFocusedId = R.path(["props", "id"], focusableItem);
241
-
242
- if (
243
- isNotNil(currentFocusedId) &&
244
- isNotNil(nextFocusedId) &&
245
- currentFocusedId === nextFocusedId
246
- ) {
247
- // we are going to move focus to the same node(already focused) -> nothing to do
248
- return { success: true };
249
- }
250
-
251
- if (focusableItem?.isGroup) {
252
- // focusCandidate is a FocusableGroup, we are not able to land focus on group(only on Focusable)
253
- return { success: false };
254
- }
255
-
256
- if (
257
- focusableItem &&
258
- !focusableItem.isGroup &&
259
- isFocusEnabled(focusableItem)
260
- ) {
261
- blur();
262
-
263
- logger.log({
264
- message: "setInitialFocus – currentFocusNode",
265
- data: {
266
- focusableItem,
267
- },
268
- });
229
+ if (preferredFocus && preferredFocus.length > 0) {
230
+ const focusCandidate = preferredFocus[0];
231
+ const focusableItem = focusCandidate.component;
232
+
233
+ if (
234
+ focusableItem &&
235
+ !focusableItem.isGroup &&
236
+ isFocusEnabled(focusableItem)
237
+ ) {
238
+ if (initialFocusOnContent) {
239
+ blur();
240
+ initialFocusOnContent = false;
241
+ }
269
242
 
270
- currentFocusNode = focusCandidate;
271
- focusableItem.willReceiveFocus(focusableItem, null);
272
- focusableItem && setFocus(focusCandidate.id, null);
243
+ logger.log({
244
+ message: "setInitialFocus – currentFocusNode",
245
+ data: {
246
+ focusableItem,
247
+ },
248
+ });
273
249
 
274
- return { success: true };
250
+ currentFocusNode = focusCandidate;
251
+ focusableItem.willReceiveFocus(focusableItem, null);
252
+ focusableItem && setFocus(focusCandidate.id, null);
253
+ }
275
254
  }
276
-
277
- return { success: false };
278
255
  }
279
256
 
280
257
  function treeLoaded(val, node) {
@@ -284,8 +261,17 @@ export const focusManager = (function () {
284
261
  val,
285
262
  node,
286
263
  currentFocusNode,
264
+ initialFocusOnContent,
287
265
  },
288
266
  });
267
+
268
+ if ((val && !currentFocusNode) || initialFocusOnContent) {
269
+ logger.log({ message: `focusAutoEnabled ${focusAutoEnabled}` });
270
+
271
+ if (focusAutoEnabled) {
272
+ setInitialFocus(initialFocusOnContent ? node : undefined);
273
+ }
274
+ }
289
275
  }
290
276
 
291
277
  /**
@@ -416,79 +402,11 @@ export const focusManager = (function () {
416
402
  group ? unregisterGroup(id) : unregisterItem(id);
417
403
  }
418
404
 
419
- async function setFocusOnMenu(currentRoute: string) {
420
- try {
421
- await waitForActiveScreen(currentRoute, focusableTree);
422
-
423
- const navbar = getNavbarNode(focusableTree);
424
-
425
- const selectedMenuItemId = R.find(
426
- (child) => child.component.props.selected,
427
- navbar.children
428
- )?.id;
429
-
430
- if (selectedMenuItemId) {
431
- // set focus on selected menu item
432
- setFocus(selectedMenuItemId);
433
-
434
- return;
435
- }
436
-
437
- const firstMenuItemId = R.path(["children", 0, "id"], navbar);
438
-
439
- if (firstMenuItemId) {
440
- // set focus on first menu item
441
- setFocus(firstMenuItemId);
442
- }
443
- } catch (error) {
444
- logger.log({
445
- message: "setFocusOnMenu – failed to put focus on top-menu",
446
- data: {
447
- currentRoute,
448
- error,
449
- },
450
- });
451
- }
452
- }
453
-
454
- async function setFocusOnContent(currentRoute: string) {
455
- // we need to put focus on content here
456
- // For doing it we have to wait registration of first focusable node into focusableTree
457
- // we wait until content node starts having children
458
- try {
459
- await waitForActiveScreen(currentRoute, focusableTree);
460
- await waitForContent(focusableTree);
461
-
462
- const content = getContentNode(focusableTree);
463
-
464
- const { success } = setInitialFocus(content);
465
-
466
- if (!success) {
467
- // we've failed to put initial focus on content, re-run "setFocusOnContent" again as a fallback
468
- throw Error(
469
- "we've failed to put initial focus on content, re-run 'setFocusOnContent' again as a fallback"
470
- );
471
- }
472
- } catch (error) {
473
- setFocusOnContent(currentRoute);
474
- }
475
- }
476
-
477
- function isTransitionInProgress() {
478
- const routes = R.pathOr([], ["root", "children"], focusableTree);
479
-
480
- if (routes.length <= 1) {
481
- // there is only one or zero routes
482
- return false;
483
- }
484
-
485
- return isTransitionHappening(currentFocusNode, R.last(routes));
486
- }
487
-
488
- function recoverFocus() {
489
- if (isTransitionInProgress()) {
490
- // We've failed to set focused node on the new screen => run focus recovery
491
- setInitialFocus();
405
+ function setupInitialFocus(id: string, focusOnContent?: boolean) {
406
+ if (focusOnContent) {
407
+ initialFocusOnContent = focusOnContent;
408
+ } else {
409
+ setFocus(id);
492
410
  }
493
411
  }
494
412
 
@@ -498,11 +416,6 @@ export const focusManager = (function () {
498
416
  * @param {Object} direction of the scroll
499
417
  */
500
418
  function moveFocus(direction) {
501
- if (isTransitionInProgress()) {
502
- // We are opening new screen. We need to ignore movement of focus.
503
- return false;
504
- }
505
-
506
419
  if (focusDisabled) return false;
507
420
 
508
421
  if (currentFocusNode) {
@@ -531,6 +444,8 @@ export const focusManager = (function () {
531
444
  } else {
532
445
  currentFocusNode.component.failedLostFocus(currentFocusNode, direction);
533
446
  }
447
+ } else {
448
+ setInitialFocus();
534
449
  }
535
450
  }
536
451
 
@@ -590,6 +505,7 @@ export const focusManager = (function () {
590
505
  on,
591
506
  invokeHandler,
592
507
  removeHandler,
508
+ setupInitialFocus,
593
509
  longPress,
594
510
  pressOut,
595
511
  enableFocus,
@@ -598,10 +514,9 @@ export const focusManager = (function () {
598
514
  isFocusDisabled,
599
515
  getCurrentFocusNode,
600
516
  focusableTree,
517
+ disableAutomaticFocus,
518
+ enableAutomaticFocus,
601
519
  getGroupRootById,
602
520
  isGroupItemFocused,
603
- setFocusOnMenu,
604
- setFocusOnContent,
605
- recoverFocus,
606
521
  };
607
522
  })();
@@ -1,6 +1,5 @@
1
- import { allPass, pathOr, startsWith, isNil } from "ramda";
1
+ import * as R from "ramda";
2
2
  import { isNilOrEmpty } from "@applicaster/zapp-react-native-utils/reactUtils/helpers";
3
- import { isZero } from "@applicaster/zapp-react-native-utils/numberUtils";
4
3
 
5
4
  import { rejectItemByPath } from "../../../../objectUtils";
6
5
  import {
@@ -144,47 +143,7 @@ const hasChildren = (target) =>
144
143
 
145
144
  const hasComponent = (target) => !!target?.component;
146
145
 
147
- const hasComponentAndChildren = allPass([hasComponent, hasChildren]);
148
-
149
- const positiveOrZero = (value) => value > 0 || isZero(value);
150
-
151
- const excludeFromFocusSearching = pathOr(false, [
152
- "component",
153
- "props",
154
- "excludeFromFocusSearching",
155
- ]);
156
-
157
- const isNavBar = (target) => startsWith("navbar", target?.id);
158
-
159
- const ignoreMenuIfMultipleTargets = (nodes) => {
160
- if (nodes.length > 1 && nodes.map((n) => n?.target?.id).includes("navbar")) {
161
- return nodes.filter((n) => !isNavBar(n?.target));
162
- }
163
-
164
- return nodes;
165
- };
166
-
167
- const toDistance = ({ target, direction, comparisonRect }) => {
168
- const componentRect = target.component?.getRect();
169
-
170
- const orientationDistance = measureDistance(
171
- direction,
172
- comparisonRect,
173
- componentRect
174
- );
175
-
176
- if (orientationDistance === -1) {
177
- // ignores offscreen targets when moving up/down
178
- return null;
179
- }
180
-
181
- const euclidDistance = measureEuclidianDistanceToCenter(
182
- comparisonRect,
183
- componentRect
184
- );
185
-
186
- return orientationDistance > 0 ? euclidDistance : orientationDistance;
187
- };
146
+ const hasComponentAndChildren = R.allPass([hasComponent, hasChildren]);
188
147
 
189
148
  /**
190
149
  * Get target in shortest distance in specified direction
@@ -214,18 +173,52 @@ export function getTargetInDirection(
214
173
 
215
174
  const distances = targets
216
175
  .filter(hasComponentAndChildren)
217
- .filter((target) => !excludeFromFocusSearching(target))
218
- .map((target) => ({
219
- distance: toDistance({ target, direction, comparisonRect }),
220
- target,
221
- }))
222
- .filter(({ distance }) => positiveOrZero(distance));
176
+ .map((target) => {
177
+ const componentRect = target.component?.getRect();
178
+
179
+ const distance = measureDistance(
180
+ direction,
181
+ comparisonRect,
182
+ componentRect
183
+ );
184
+
185
+ if (distance === -1) {
186
+ // ignores offscreen targets when moving up/down
187
+ return null;
188
+ }
189
+
190
+ const orientationDistance = measureDistance(
191
+ direction,
192
+ comparisonRect,
193
+ componentRect
194
+ );
195
+
196
+ const euclidDistance = measureEuclidianDistanceToCenter(
197
+ comparisonRect,
198
+ componentRect
199
+ );
200
+
201
+ return {
202
+ distance:
203
+ orientationDistance > 0 ? euclidDistance : orientationDistance,
204
+ target,
205
+ };
206
+ })
207
+ .filter((obj) => obj && obj.distance >= 0);
223
208
 
224
209
  const match = distances
225
210
  .sort((a, b) => a.distance - b.distance) // sorts the distances
226
211
  .filter(ignoreMenuIfMultipleTargets)[0]; // filters out multiple targets if necessary // gets the first element
227
212
 
228
- return match?.target;
213
+ return distances && distances.length > 0 ? match.target : null;
214
+ }
215
+
216
+ function ignoreMenuIfMultipleTargets(nodes) {
217
+ if (nodes.length > 1 && nodes.map((n) => n?.target?.id).includes("navbar")) {
218
+ return nodes.filter((n) => n?.target?.id !== "navbar");
219
+ }
220
+
221
+ return nodes;
229
222
  }
230
223
 
231
224
  /**
@@ -316,26 +309,3 @@ export function hasPriorityInDirection(direction, component) {
316
309
 
317
310
  return (prioritiseFocusOn & mappedDirection) > 0;
318
311
  }
319
-
320
- export const getNodeBeforeRoot = (node) => {
321
- if (isNil(node)) {
322
- return undefined;
323
- }
324
-
325
- if (isNil(node.parent)) {
326
- return undefined;
327
- }
328
-
329
- if (node.parent.id === "root") {
330
- return node;
331
- }
332
-
333
- return getNodeBeforeRoot(node.parent);
334
- };
335
-
336
- export const isTransitionHappening = (currentNode, nextNode) => {
337
- const routeForCurrentNode = getNodeBeforeRoot(currentNode);
338
- const routeForNextNode = getNodeBeforeRoot(nextNode);
339
-
340
- return routeForCurrentNode?.id !== routeForNextNode?.id;
341
- };
@@ -4,6 +4,7 @@ import { useEffect } from "react";
4
4
  import { playerManager } from "./index";
5
5
  import { useValidatePlayerConfig } from "../../playerUtils/useValidatePlayerConfig";
6
6
  import { PlayerRole } from "./conts";
7
+ import { isAppleTV } from "@applicaster/zapp-react-native-ui-components/Helpers/Platform";
7
8
 
8
9
  // TODO: Rename to ControllerType
9
10
  export const usePlayerControllerSetup = ({
@@ -34,10 +35,14 @@ export const usePlayerControllerSetup = ({
34
35
  useValidatePlayerConfig(config);
35
36
 
36
37
  useEffect(() => {
38
+ const newConfig = isAppleTV()
39
+ ? { supports_native_controls: true, ...config }
40
+ : config;
41
+
37
42
  const newPlayerController = new ControllerType({
38
43
  player: ref,
39
44
  listener: props as any,
40
- config,
45
+ config: newConfig,
41
46
  playerId,
42
47
  entry,
43
48
  playerPluginId: pluginId,
@@ -95,8 +95,3 @@ export const makeListOfIndexes = (size: number): number[] =>
95
95
  export const makeListOf = (value: unknown, size: number): number[] => {
96
96
  return Array(size).fill(value);
97
97
  };
98
-
99
- /** Checks if a value is a non-empty array */
100
- export function isFilledArray(value: unknown): boolean {
101
- return R.is(Array, value) && R.length(value) > 0;
102
- }
@@ -7,24 +7,12 @@ describe("isLikeNumber", () => {
7
7
  expect(isLikeNumber(value)).toBe(true);
8
8
  });
9
9
 
10
- it("value is a number", () => {
11
- const value = 123.45;
12
-
13
- expect(isLikeNumber(value)).toBe(true);
14
- });
15
-
16
10
  it("value is a string as number", () => {
17
11
  const value = "123";
18
12
 
19
13
  expect(isLikeNumber(value)).toBe(true);
20
14
  });
21
15
 
22
- it("value is a string as number", () => {
23
- const value = "123.45";
24
-
25
- expect(isLikeNumber(value)).toBe(true);
26
- });
27
-
28
16
  it("value is empty string", () => {
29
17
  const value = "";
30
18
 
@@ -66,10 +54,4 @@ describe("isLikeNumber", () => {
66
54
 
67
55
  expect(isLikeNumber(value)).toBe(false);
68
56
  });
69
-
70
- it("NaN is not a number", () => {
71
- const value = NaN;
72
-
73
- expect(isLikeNumber(value)).toBe(false);
74
- });
75
57
  });
@@ -50,14 +50,27 @@ export const toFiniteNumberWithDefault = (
50
50
  return possibleNumber;
51
51
  };
52
52
 
53
- export const isLikeNumber = (value: unknown): boolean => {
54
- const possibleNumber = toNumber(value);
55
-
56
- if (R.isNil(possibleNumber)) {
53
+ export const isLikeNumber = (value: any): boolean => {
54
+ if (R.isEmpty(value)) {
57
55
  return false;
58
56
  }
59
57
 
60
- return true;
58
+ if (R.is(Number, value)) {
59
+ return true;
60
+ }
61
+
62
+ if (R.is(String, value)) {
63
+ const onlyDigit = value
64
+ .split("")
65
+ .filter((char) =>
66
+ ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"].includes(char)
67
+ )
68
+ .join("");
69
+
70
+ return value === onlyDigit;
71
+ }
72
+
73
+ return false;
61
74
  };
62
75
 
63
76
  export const isZero = (value: unknown): boolean =>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@applicaster/zapp-react-native-utils",
3
- "version": "12.0.0-alpha.4337072244",
3
+ "version": "12.0.0-alpha.4342066143",
4
4
  "description": "Applicaster Zapp React Native utilities package",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -28,7 +28,7 @@
28
28
  },
29
29
  "homepage": "https://github.com/applicaster/quickbrick#readme",
30
30
  "dependencies": {
31
- "@applicaster/applicaster-types": "12.0.0-alpha.4337072244",
31
+ "@applicaster/applicaster-types": "12.0.0-alpha.4342066143",
32
32
  "buffer": "^5.2.1",
33
33
  "camelize": "^1.0.0",
34
34
  "create-react-context": "^0.2.3",