@applicaster/zapp-react-native-utils 12.0.0-alpha.4837064762 → 12.0.0-alpha.4885205607

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 (70) hide show
  1. package/analyticsUtils/AnalyticsEvents/index.ts +13 -0
  2. package/analyticsUtils/AnalyticsEvents/searchEvents.ts +21 -0
  3. package/analyticsUtils/AnalyticsEvents/sendShareEvent.ts +21 -0
  4. package/analyticsUtils/analyticsPlugins.ts +25 -0
  5. package/analyticsUtils/events.ts +12 -0
  6. package/analyticsUtils/index.tsx +12 -0
  7. package/appUtils/accessibilityManager/const.ts +50 -0
  8. package/appUtils/accessibilityManager/hooks.ts +40 -0
  9. package/appUtils/accessibilityManager/index.ts +155 -0
  10. package/appUtils/accessibilityManager/logger.ts +5 -0
  11. package/appUtils/focusManager/__tests__/__snapshots__/focusManager.test.js.snap +2 -3
  12. package/appUtils/focusManager/__tests__/focusManager.test.js +4 -24
  13. package/appUtils/focusManager/__tests__/index.test.ts +2 -3
  14. package/appUtils/focusManager/index.ts +44 -128
  15. package/appUtils/focusManager/treeDataStructure/Utils/index.js +14 -38
  16. package/appUtils/focusManager/utils/index.ts +100 -0
  17. package/appUtils/focusManagerAux/index.ts +76 -0
  18. package/appUtils/focusManagerAux/utils/index.ts +100 -0
  19. package/appUtils/index.js +2 -0
  20. package/appUtils/keyCodes/keys.web.js +8 -0
  21. package/appUtils/keyCodes/keysByPlatform.js +11 -1
  22. package/appUtils/platform/const.ts +25 -0
  23. package/appUtils/platform/index.ts +3 -0
  24. package/appUtils/platform/platformUtils.ts +185 -0
  25. package/appUtils/playerManager/conts.ts +1 -0
  26. package/appUtils/playerManager/index.ts +11 -5
  27. package/appUtils/playerManager/nativePlayerManager.ts +12 -0
  28. package/appUtils/playerManager/player.ts +9 -3
  29. package/appUtils/playerManager/playerNative.ts +5 -14
  30. package/appUtils/playerManager/usePlayerControllerSetup.tsx +7 -1
  31. package/arrayUtils/__tests__/isFilledArray.test.ts +63 -0
  32. package/arrayUtils/index.ts +5 -0
  33. package/booleanUtils/index.ts +4 -4
  34. package/cellUtils/__tests__/cellUtils.test.ts +9 -20
  35. package/cellUtils/index.ts +11 -17
  36. package/dateUtils/index.ts +11 -0
  37. package/headersUtils/index.ts +1 -0
  38. package/index.d.ts +15 -0
  39. package/inflatedUrlUtils/index.ts +4 -4
  40. package/keyboardState/index.ts +0 -0
  41. package/localizationUtils/__tests__/localizationUtils.test.ts +72 -53
  42. package/localizationUtils/index.ts +13 -22
  43. package/navigationUtils/index.ts +48 -0
  44. package/numberUtils/__tests__/isLikeNumber.test.ts +18 -0
  45. package/numberUtils/__tests__/toNumber.test.ts +2 -2
  46. package/numberUtils/index.ts +9 -18
  47. package/package.json +2 -2
  48. package/playerUtils/imaScriptDom.ts +59 -0
  49. package/pluginUtils/index.ts +9 -0
  50. package/reactHooks/cell-click/__tests__/index.test.js +10 -1
  51. package/reactHooks/cell-click/index.ts +12 -12
  52. package/reactHooks/feed/__tests__/useBuildPipesUrl.test.tsx +30 -70
  53. package/reactHooks/feed/__tests__/useFeedRefresh.test.tsx +6 -2
  54. package/reactHooks/feed/__tests__/useInflatedUrl.test.ts +18 -3
  55. package/reactHooks/feed/useBuildPipesUrl.ts +7 -10
  56. package/reactHooks/feed/useFeedRefresh.tsx +9 -7
  57. package/reactHooks/feed/useInflatedUrl.ts +44 -15
  58. package/reactHooks/navigation/__tests__/index.test.tsx +11 -8
  59. package/reactHooks/navigation/index.ts +46 -68
  60. package/reactHooks/screen/__tests__/useCurrentScreenData.test.tsx +21 -23
  61. package/reactHooks/screen/index.ts +2 -0
  62. package/reactHooks/screen/useScreenBackgroundColor.ts +23 -0
  63. package/reactHooks/screen/useScreenContext.ts +1 -2
  64. package/reactUtils/helpers.tsx +1 -1
  65. package/reactUtils/index.ts +104 -0
  66. package/stringUtils/index.ts +5 -0
  67. package/types.ts +4 -0
  68. package/appUtils/focusManager/FocusableEvents.ts +0 -8
  69. package/appUtils/focusManager/treeDataStructure/Utils/__tests__/Utils_android.test.js +0 -1094
  70. package/appUtils/focusManager/treeDataStructure/Utils/index.android.js +0 -261
@@ -1,12 +1,18 @@
1
1
  import * as R from "ramda";
2
- import { isNilOrEmpty } from "@applicaster/zapp-react-native-utils/reactUtils/helpers";
2
+ import {
3
+ isNilOrEmpty,
4
+ isNotNil,
5
+ } from "@applicaster/zapp-react-native-utils/reactUtils/helpers";
3
6
 
4
7
  import { Tree } from "./treeDataStructure/Tree";
5
- import { findFocusableNode, findPriorityItem } from "./treeDataStructure/Utils";
8
+ import {
9
+ findFocusableNode,
10
+ findPriorityItem,
11
+ haveSameParentBeforeRoot,
12
+ } from "./treeDataStructure/Utils";
6
13
  import { subscriber } from "../../functionUtils";
7
14
  import * as FOCUS_EVENTS from "./events";
8
15
  import { coreLogger } from "../../logger";
9
-
10
16
  const logger = coreLogger.addSubsystem("focusManager");
11
17
 
12
18
  const isFocusEnabled = (focusableItem): boolean => {
@@ -19,22 +25,6 @@ const isFocusEnabled = (focusableItem): boolean => {
19
25
  return true;
20
26
  };
21
27
 
22
- const waitUntil = (conditionFn) => {
23
- let interval;
24
- return new Promise((resolve, reject) => {
25
- interval = setInterval(() => {
26
- if (conditionFn()) {
27
- clearInterval(interval);
28
- resolve(undefined);
29
- }
30
- }, 1000);
31
-
32
- setTimeout(() => {
33
- reject();
34
- }, 2500);
35
- });
36
- };
37
-
38
28
  /**
39
29
  * The focusManager is meant to keep references of focusables elements in the UI, and provide
40
30
  * ways to assign the focus to one of the focusables.
@@ -62,21 +52,9 @@ export const focusManager = (function () {
62
52
  // focusable node which currently holds the focus
63
53
  let currentFocusNode = null;
64
54
  let lastFocusedId = null;
65
- let initialFocusOnContent = null;
66
55
  let focusDisabled = false;
67
- let focusAutoEnabled = false;
68
56
  const eventHandler = subscriber();
69
57
 
70
- function disableAutomaticFocus() {
71
- logger.log({ message: "disableAutomaticFocus" });
72
- focusAutoEnabled = false;
73
- }
74
-
75
- function enableAutomaticFocus() {
76
- logger.log({ message: "focusManager: enableAutomaticFocus" });
77
- focusAutoEnabled = true;
78
- }
79
-
80
58
  function enableFocus() {
81
59
  focusDisabled = false;
82
60
  }
@@ -245,7 +223,7 @@ export const focusManager = (function () {
245
223
  logger.log({ message: "setInitialFocus", data: { preferredFocus } });
246
224
 
247
225
  if (isNilOrEmpty(preferredFocus)) {
248
- return;
226
+ return { success: false };
249
227
  }
250
228
 
251
229
  const focusCandidate = preferredFocus[0];
@@ -254,9 +232,18 @@ export const focusManager = (function () {
254
232
  const currentFocusedId = R.path(["props", "id"], getCurrentFocus());
255
233
  const nextFocusedId = R.path(["props", "id"], focusableItem);
256
234
 
257
- if (currentFocusedId === nextFocusedId) {
235
+ if (
236
+ isNotNil(currentFocusedId) &&
237
+ isNotNil(nextFocusedId) &&
238
+ currentFocusedId === nextFocusedId
239
+ ) {
258
240
  // we are going to move focus to the same node(already focused) -> nothing to do
259
- return;
241
+ return { success: true };
242
+ }
243
+
244
+ if (focusableItem?.isGroup) {
245
+ // focusCandidate is a FocusableGroup, we are not able to land focus on group(only on Focusable)
246
+ return { success: false };
260
247
  }
261
248
 
262
249
  if (
@@ -264,10 +251,7 @@ export const focusManager = (function () {
264
251
  !focusableItem.isGroup &&
265
252
  isFocusEnabled(focusableItem)
266
253
  ) {
267
- if (initialFocusOnContent) {
268
- blur();
269
- initialFocusOnContent = false;
270
- }
254
+ blur();
271
255
 
272
256
  logger.log({
273
257
  message: "setInitialFocus – currentFocusNode",
@@ -279,7 +263,11 @@ export const focusManager = (function () {
279
263
  currentFocusNode = focusCandidate;
280
264
  focusableItem.willReceiveFocus(focusableItem, null);
281
265
  focusableItem && setFocus(focusCandidate.id, null);
266
+
267
+ return { success: true };
282
268
  }
269
+
270
+ return { success: false };
283
271
  }
284
272
 
285
273
  function treeLoaded(val, node) {
@@ -289,17 +277,8 @@ export const focusManager = (function () {
289
277
  val,
290
278
  node,
291
279
  currentFocusNode,
292
- initialFocusOnContent,
293
280
  },
294
281
  });
295
-
296
- if ((val && !currentFocusNode) || initialFocusOnContent) {
297
- logger.log({ message: `focusAutoEnabled ${focusAutoEnabled}` });
298
-
299
- if (focusAutoEnabled) {
300
- setInitialFocus(initialFocusOnContent ? node : undefined);
301
- }
302
- }
303
282
  }
304
283
 
305
284
  /**
@@ -351,8 +330,6 @@ export const focusManager = (function () {
351
330
  * @returns {React.Component} the registered component
352
331
  */
353
332
  function registerGroup(id, component) {
354
- // console.log("debug_2", "FM registerGroup", { id, component });
355
-
356
333
  focusableTree.addNode({ id, component });
357
334
 
358
335
  return component;
@@ -368,8 +345,6 @@ export const focusManager = (function () {
368
345
  * @returns {React.Component} the registered component
369
346
  */
370
347
  function registerItem(id, component) {
371
- // console.log("debug_2", "FM registerItem", { id, component });
372
-
373
348
  focusableTree.addNode({ id, component });
374
349
 
375
350
  if (!currentFocusNode && id === lastFocusedId && !id.includes("menu")) {
@@ -386,8 +361,6 @@ export const focusManager = (function () {
386
361
  * @param {String} id of the group to unregister
387
362
  */
388
363
  function unregisterGroup(id) {
389
- // console.log("debug_2", "FM unregisterGroup", { id });
390
-
391
364
  focusableTree.findAndRemoveNode(id);
392
365
  }
393
366
 
@@ -399,8 +372,6 @@ export const focusManager = (function () {
399
372
  * @param {String} id of the Focusable to unregister
400
373
  */
401
374
  function unregisterItem(id) {
402
- // console.log("debug_2", "FM unregisterItem", { id });
403
-
404
375
  focusableTree.findAndRemoveNode(id);
405
376
 
406
377
  if (currentFocusNode && currentFocusNode.id === id) {
@@ -438,70 +409,22 @@ export const focusManager = (function () {
438
409
  group ? unregisterGroup(id) : unregisterItem(id);
439
410
  }
440
411
 
441
- function setupInitialFocus(id: string, focusOnContent?: boolean) {
442
- console.log("debug_2", "FM - setupInitialFocus", { id, focusOnContent });
443
-
444
- if (focusOnContent) {
445
- initialFocusOnContent = focusOnContent;
446
- } else {
447
- setFocus(id);
448
- }
449
- }
412
+ function isCurrentFocusOnTheTopScreen() {
413
+ const routes = R.pathOr([], ["root", "children"], focusableTree);
450
414
 
451
- function setFocusOnMenu() {
452
- const activeScreen = R.last(focusableTree.root.children);
453
- const navbar = R.find(
454
- (child) => R.startsWith("navbar", child.id),
455
- activeScreen.children
456
- );
457
-
458
- const selectedMenuItemId = R.find(
459
- (child) => child.component.props.selected,
460
- navbar?.children || []
461
- )?.id;
462
-
463
- if (selectedMenuItemId) {
464
- // set focus on selected menu item
465
- setFocus(selectedMenuItemId);
466
- return;
415
+ if (routes.length <= 1) {
416
+ // there is only one or zero routes
417
+ return true;
467
418
  }
468
419
 
469
- const firstMenuItemId = R.path(["children", 0, "id"], navbar);
470
-
471
- if (firstMenuItemId) {
472
- // set focus on first menu item
473
- setFocus(firstMenuItemId);
474
- }
420
+ return haveSameParentBeforeRoot(currentFocusNode, R.last(routes));
475
421
  }
476
422
 
477
- function setFocusOnContent() {
478
- // we need to put focus on content here
479
- // For doing it we have to wait registration of first focusable node into focusableTree
480
- // this is the temporary hack with waiting until content node starts having children
481
- const getContentNode = () => {
482
- const activeScreen = R.last(focusableTree.root.children);
483
- const content = R.find(
484
- (child) => R.startsWith("content", child.id),
485
- activeScreen.children
486
- );
487
-
488
- return content;
489
- };
490
-
491
- const contentHasAnyChildren = () => {
492
- return getContentNode()?.children.length > 0;
493
- };
494
-
495
- waitUntil(contentHasAnyChildren)
496
- .then(() => {
497
- const content = getContentNode();
498
-
499
- setInitialFocus(content);
500
- })
501
- .catch(() => {
502
- // we failed to wait any content, put focus on menu as a last chance
503
- setFocusOnMenu();
504
- });
423
+ function recoverFocus() {
424
+ if (!isCurrentFocusOnTheTopScreen()) {
425
+ // We've failed to set focused node on the new screen => run focus recovery
426
+ setInitialFocus();
427
+ }
505
428
  }
506
429
 
507
430
  /**
@@ -510,19 +433,17 @@ export const focusManager = (function () {
510
433
  * @param {Object} direction of the scroll
511
434
  */
512
435
  function moveFocus(direction) {
436
+ if (!isCurrentFocusOnTheTopScreen()) {
437
+ // We are opening new screen. We need to ignore movement of focus.
438
+ return false;
439
+ }
440
+
513
441
  if (focusDisabled) return false;
514
442
 
515
443
  if (currentFocusNode) {
516
444
  currentFocusNode.component.willLoseFocus(currentFocusNode, direction);
517
445
  const nextFocusable = findFocusableNode(direction, currentFocusNode);
518
446
 
519
- console.log("debug_2", "FM moveFocus", {
520
- direction: direction?.value,
521
- currentFocusNode,
522
- focusableTree,
523
- nextFocusable,
524
- });
525
-
526
447
  if (nextFocusable?.component?.isMounted?.()) {
527
448
  const nextRect = nextFocusable.component.getRect();
528
449
 
@@ -545,8 +466,6 @@ export const focusManager = (function () {
545
466
  } else {
546
467
  currentFocusNode.component.failedLostFocus(currentFocusNode, direction);
547
468
  }
548
- } else {
549
- setInitialFocus();
550
469
  }
551
470
  }
552
471
 
@@ -606,7 +525,6 @@ export const focusManager = (function () {
606
525
  on,
607
526
  invokeHandler,
608
527
  removeHandler,
609
- setupInitialFocus,
610
528
  longPress,
611
529
  pressOut,
612
530
  enableFocus,
@@ -615,11 +533,9 @@ export const focusManager = (function () {
615
533
  isFocusDisabled,
616
534
  getCurrentFocusNode,
617
535
  focusableTree,
618
- disableAutomaticFocus,
619
- enableAutomaticFocus,
620
536
  getGroupRootById,
621
537
  isGroupItemFocused,
622
- setFocusOnMenu,
623
- setFocusOnContent,
538
+ recoverFocus,
539
+ isCurrentFocusOnTheTopScreen,
624
540
  };
625
541
  })();
@@ -1,5 +1,7 @@
1
1
  import { allPass, pathOr, startsWith, isNil } 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
+ import { QUICK_BRICK_NAVBAR } from "@applicaster/quick-brick-core/const";
3
5
 
4
6
  import { rejectItemByPath } from "../../../../objectUtils";
5
7
  import {
@@ -145,7 +147,7 @@ const hasComponent = (target) => !!target?.component;
145
147
 
146
148
  const hasComponentAndChildren = allPass([hasComponent, hasChildren]);
147
149
 
148
- const positiveOrZero = (value) => value >= 0;
150
+ const positiveOrZero = (value) => value > 0 || isZero(value);
149
151
 
150
152
  const excludeFromFocusSearching = pathOr(false, [
151
153
  "component",
@@ -153,12 +155,10 @@ const excludeFromFocusSearching = pathOr(false, [
153
155
  "excludeFromFocusSearching",
154
156
  ]);
155
157
 
156
- // const isAllScreens = all(excludeFromFocusSearching);
157
-
158
- const isNavBar = (target) => startsWith("navbar", target?.id);
158
+ const isNavBar = (target) => startsWith(QUICK_BRICK_NAVBAR, target?.id);
159
159
 
160
160
  const ignoreMenuIfMultipleTargets = (nodes) => {
161
- if (nodes.length > 1 && nodes.map((n) => n?.target?.id).includes("navbar")) {
161
+ if (nodes.length > 1) {
162
162
  return nodes.filter((n) => !isNavBar(n?.target));
163
163
  }
164
164
 
@@ -206,7 +206,7 @@ export function getTargetInDirection(
206
206
  const overrideTarget = currentNode?.component?.props[overridePropName];
207
207
 
208
208
  if (overrideTarget) {
209
- const target = targetNodes.find((n) => n.id === overrideTarget);
209
+ const target = targetNodes.find((n) => n?.id === overrideTarget);
210
210
 
211
211
  if (target) return target;
212
212
  }
@@ -222,24 +222,10 @@ export function getTargetInDirection(
222
222
  }))
223
223
  .filter(({ distance }) => positiveOrZero(distance));
224
224
 
225
- // if (isAllScreens(targets)) {
226
- // return last(targets);
227
- // }
228
-
229
225
  const match = distances
230
226
  .sort((a, b) => a.distance - b.distance) // sorts the distances
231
227
  .filter(ignoreMenuIfMultipleTargets)[0]; // filters out multiple targets if necessary // gets the first element
232
228
 
233
- // console.log("debug_2", "FM getTargetInDirection", {
234
- // direction: direction?.value,
235
- // currentNode,
236
- // targetNodes,
237
- // targets,
238
- // comparisonRect,
239
- // distances,
240
- // match,
241
- // });
242
-
243
229
  return match?.target;
244
230
  }
245
231
 
@@ -337,6 +323,10 @@ export const getNodeBeforeRoot = (node) => {
337
323
  return undefined;
338
324
  }
339
325
 
326
+ if (isNil(node.parent)) {
327
+ return undefined;
328
+ }
329
+
340
330
  if (node.parent.id === "root") {
341
331
  return node;
342
332
  }
@@ -344,23 +334,9 @@ export const getNodeBeforeRoot = (node) => {
344
334
  return getNodeBeforeRoot(node.parent);
345
335
  };
346
336
 
347
- export const isAllowedToMoveTo = (currentNode, nextNode) => {
348
- const screenForCurrentNode = getNodeBeforeRoot(currentNode);
349
- const screenForNextNode = getNodeBeforeRoot(nextNode);
350
-
351
- return screenForCurrentNode.id === screenForNextNode.id;
352
-
353
- // console.log("debug_2", "isAllowedToMoveTo", {
354
- // nodeBeforeRoot,
355
- // });
356
-
357
- // if (nodeBeforeRoot?.id === "navbar") {
358
- // return true;
359
- // }
360
-
361
- // if (nodeBeforeRoot?.component?.props?.isActive) {
362
- // return true;
363
- // }
337
+ export const haveSameParentBeforeRoot = (currentNode, nextNode) => {
338
+ const routeForCurrentNode = getNodeBeforeRoot(currentNode);
339
+ const routeForNextNode = getNodeBeforeRoot(nextNode);
364
340
 
365
- // return false;
341
+ return routeForCurrentNode?.id === routeForNextNode?.id;
366
342
  };
@@ -0,0 +1,100 @@
1
+ import { isNotNil } from "@applicaster/zapp-react-native-utils/reactUtils/helpers";
2
+ import { find, last, startsWith, pathOr } from "ramda";
3
+ import {
4
+ QUICK_BRICK_NAVBAR,
5
+ QUICK_BRICK_CONTENT,
6
+ } from "@applicaster/quick-brick-core/const";
7
+
8
+ // run check each 300 ms
9
+ // run this check too often could lead to performance penalty on low-end devices
10
+ const HOW_OFTEN_TO_CHECK_CONDITION = 300; // ms
11
+
12
+ type Props = {
13
+ maxTimeout: number;
14
+ conditionFn: () => boolean;
15
+ };
16
+
17
+ export const waitUntil = ({ maxTimeout, conditionFn }: Props) => {
18
+ let interval;
19
+ let timeout;
20
+
21
+ return new Promise((resolve, reject) => {
22
+ // run interval check
23
+ interval = setInterval(() => {
24
+ if (conditionFn()) {
25
+ clearInterval(interval);
26
+ clearTimeout(timeout);
27
+
28
+ resolve(undefined);
29
+ }
30
+ }, HOW_OFTEN_TO_CHECK_CONDITION);
31
+
32
+ // max timeout when we need to stop interval check
33
+ timeout = setTimeout(() => {
34
+ clearInterval(interval);
35
+ clearTimeout(timeout);
36
+
37
+ const error = Error(
38
+ "Failed to wait satisfy condition. Max timeout is reached."
39
+ );
40
+
41
+ reject(error);
42
+ }, maxTimeout);
43
+ });
44
+ };
45
+
46
+ export const waitForActiveScreen = (currentRoute: string, focusableTree) => {
47
+ const isActiveScreenAvailableToFocus = (): boolean => {
48
+ const routes = focusableTree.root.children;
49
+
50
+ const route = find((route) => route.id === currentRoute, routes);
51
+
52
+ return isNotNil(route);
53
+ };
54
+
55
+ return waitUntil({
56
+ maxTimeout: HOW_OFTEN_TO_CHECK_CONDITION * 10, // ms
57
+ conditionFn: isActiveScreenAvailableToFocus,
58
+ });
59
+ };
60
+
61
+ const getActiveScreenChildren = (focusableTree) => {
62
+ return pathOr([], ["children"], last(focusableTree.root.children));
63
+ };
64
+
65
+ export const getContentNode = (focusableTree) => {
66
+ const content = find(
67
+ (child) =>
68
+ startsWith(QUICK_BRICK_CONTENT, child.id) ||
69
+ startsWith("player", child.id),
70
+ getActiveScreenChildren(focusableTree)
71
+ );
72
+
73
+ return content;
74
+ };
75
+
76
+ export const getNavbarNode = (focusableTree) => {
77
+ const navbar = find(
78
+ (child) => startsWith(QUICK_BRICK_NAVBAR, child.id),
79
+ getActiveScreenChildren(focusableTree)
80
+ );
81
+
82
+ return navbar;
83
+ };
84
+
85
+ export const waitForContent = (focusableTree) => {
86
+ const contentHasAnyChildren = (): boolean => {
87
+ const countOfChildren = pathOr(
88
+ 0,
89
+ ["children", "length"],
90
+ getContentNode(focusableTree)
91
+ );
92
+
93
+ return countOfChildren > 0;
94
+ };
95
+
96
+ return waitUntil({
97
+ maxTimeout: HOW_OFTEN_TO_CHECK_CONDITION * 10, // ms
98
+ conditionFn: contentHasAnyChildren,
99
+ });
100
+ };
@@ -0,0 +1,76 @@
1
+ import { focusManager } from "@applicaster/zapp-react-native-utils/appUtils/focusManager";
2
+ import { find, path } from "ramda";
3
+
4
+ import {
5
+ waitForActiveScreen,
6
+ waitForContent,
7
+ getContentNode,
8
+ getNavbarNode,
9
+ } from "./utils";
10
+
11
+ import { coreLogger } from "../../logger";
12
+
13
+ const logger = coreLogger.addSubsystem("focusManagerAux");
14
+
15
+ export async function setFocusOnMenu(currentRoute: string) {
16
+ const { setFocus, focusableTree } = focusManager;
17
+
18
+ try {
19
+ await waitForActiveScreen(currentRoute, focusableTree);
20
+
21
+ const navbar = getNavbarNode(focusableTree);
22
+
23
+ const selectedMenuItemId = find(
24
+ (child) => child.component.props.selected,
25
+ navbar.children
26
+ )?.id;
27
+
28
+ if (selectedMenuItemId) {
29
+ // set focus on selected menu item
30
+ setFocus(selectedMenuItemId);
31
+
32
+ return;
33
+ }
34
+
35
+ const firstMenuItemId = path(["children", 0, "id"], navbar);
36
+
37
+ if (firstMenuItemId) {
38
+ // set focus on first menu item
39
+ setFocus(firstMenuItemId);
40
+ }
41
+ } catch (error) {
42
+ logger.log({
43
+ message: "setFocusOnMenu – failed to put focus on top-menu",
44
+ data: {
45
+ currentRoute,
46
+ error,
47
+ },
48
+ });
49
+ }
50
+ }
51
+
52
+ export async function setFocusOnContent(currentRoute: string) {
53
+ // we need to put focus on content here
54
+ // For doing it we have to wait registration of first focusable node into focusableTree
55
+ // we wait until content node starts having children
56
+
57
+ const { setInitialFocus, focusableTree } = focusManager;
58
+
59
+ try {
60
+ await waitForActiveScreen(currentRoute, focusableTree);
61
+ await waitForContent(focusableTree);
62
+
63
+ const content = getContentNode(focusableTree);
64
+
65
+ const { success } = setInitialFocus(content);
66
+
67
+ if (!success) {
68
+ // we've failed to put initial focus on content, re-run "setFocusOnContent" again as a fallback
69
+ throw Error(
70
+ "we've failed to put initial focus on content, re-run 'setFocusOnContent' again as a fallback"
71
+ );
72
+ }
73
+ } catch (error) {
74
+ setFocusOnContent(currentRoute);
75
+ }
76
+ }
@@ -0,0 +1,100 @@
1
+ import { isNotNil } from "@applicaster/zapp-react-native-utils/reactUtils/helpers";
2
+ import { find, last, startsWith, pathOr } from "ramda";
3
+ import {
4
+ QUICK_BRICK_NAVBAR,
5
+ QUICK_BRICK_CONTENT,
6
+ } from "@applicaster/quick-brick-core/const";
7
+
8
+ // run check each 300 ms
9
+ // run this check too often could lead to performance penalty on low-end devices
10
+ const HOW_OFTEN_TO_CHECK_CONDITION = 300; // ms
11
+
12
+ type Props = {
13
+ maxTimeout: number;
14
+ conditionFn: () => boolean;
15
+ };
16
+
17
+ export const waitUntil = ({ maxTimeout, conditionFn }: Props) => {
18
+ let interval;
19
+ let timeout;
20
+
21
+ return new Promise((resolve, reject) => {
22
+ // run interval check
23
+ interval = setInterval(() => {
24
+ if (conditionFn()) {
25
+ clearInterval(interval);
26
+ clearTimeout(timeout);
27
+
28
+ resolve(undefined);
29
+ }
30
+ }, HOW_OFTEN_TO_CHECK_CONDITION);
31
+
32
+ // max timeout when we need to stop interval check
33
+ timeout = setTimeout(() => {
34
+ clearInterval(interval);
35
+ clearTimeout(timeout);
36
+
37
+ const error = Error(
38
+ "Failed to wait satisfy condition. Max timeout is reached."
39
+ );
40
+
41
+ reject(error);
42
+ }, maxTimeout);
43
+ });
44
+ };
45
+
46
+ export const waitForActiveScreen = (currentRoute: string, focusableTree) => {
47
+ const isActiveScreenAvailableToFocus = (): boolean => {
48
+ const routes = focusableTree.root.children;
49
+
50
+ const route = find((route) => route.id === currentRoute, routes);
51
+
52
+ return isNotNil(route);
53
+ };
54
+
55
+ return waitUntil({
56
+ maxTimeout: HOW_OFTEN_TO_CHECK_CONDITION * 10, // ms
57
+ conditionFn: isActiveScreenAvailableToFocus,
58
+ });
59
+ };
60
+
61
+ const getActiveScreenChildren = (focusableTree) => {
62
+ return pathOr([], ["children"], last(focusableTree.root.children));
63
+ };
64
+
65
+ export const getContentNode = (focusableTree) => {
66
+ const content = find(
67
+ (child) =>
68
+ startsWith(QUICK_BRICK_CONTENT, child.id) ||
69
+ startsWith("player", child.id),
70
+ getActiveScreenChildren(focusableTree)
71
+ );
72
+
73
+ return content;
74
+ };
75
+
76
+ export const getNavbarNode = (focusableTree) => {
77
+ const navbar = find(
78
+ (child) => startsWith(QUICK_BRICK_NAVBAR, child.id),
79
+ getActiveScreenChildren(focusableTree)
80
+ );
81
+
82
+ return navbar;
83
+ };
84
+
85
+ export const waitForContent = (focusableTree) => {
86
+ const contentHasAnyChildren = (): boolean => {
87
+ const countOfChildren = pathOr(
88
+ 0,
89
+ ["children", "length"],
90
+ getContentNode(focusableTree)
91
+ );
92
+
93
+ return countOfChildren > 0;
94
+ };
95
+
96
+ return waitUntil({
97
+ maxTimeout: HOW_OFTEN_TO_CHECK_CONDITION * 10, // ms
98
+ conditionFn: contentHasAnyChildren,
99
+ });
100
+ };
package/appUtils/index.js CHANGED
@@ -9,6 +9,8 @@ export { KEYS, ARROW_KEYS } from "./keyCodes/keys";
9
9
 
10
10
  export { playerManager } from "./playerManager";
11
11
 
12
+ export { accessibilityManager } from "./accessibilityManager";
13
+
12
14
  export function deprecationMessage(fn, replacement) {
13
15
  if (__DEV__ && process.env.NODE_ENV !== "test") {
14
16
  console.warn("/** DEPRECATION WARNING **/");