@applicaster/zapp-react-native-utils 12.0.0-alpha.5035843303 → 12.0.0-alpha.5050333009

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 (138) hide show
  1. package/actionsExecutor/ActionExecutorContext.tsx +16 -8
  2. package/analyticsUtils/AnalyticsEvents/index.ts +13 -0
  3. package/analyticsUtils/AnalyticsEvents/searchEvents.ts +21 -0
  4. package/analyticsUtils/AnalyticsEvents/sendShareEvent.ts +21 -0
  5. package/analyticsUtils/__tests__/analyticsManager.test.js +75 -73
  6. package/analyticsUtils/analyticsPlugins.ts +25 -0
  7. package/analyticsUtils/events.ts +12 -0
  8. package/analyticsUtils/helpers/hooks.ts +1 -1
  9. package/analyticsUtils/index.tsx +12 -0
  10. package/appUtils/HooksManager/{Hook.js → Hook.ts} +35 -23
  11. package/appUtils/HooksManager/{constants.js → constants.ts} +1 -1
  12. package/appUtils/HooksManager/{index.js → index.ts} +121 -112
  13. package/appUtils/HooksManager/types.ts +116 -0
  14. package/appUtils/RiverFocusManager/index.js +17 -12
  15. package/appUtils/accessibilityManager/const.ts +50 -0
  16. package/appUtils/accessibilityManager/hooks.ts +31 -0
  17. package/appUtils/accessibilityManager/index.ts +150 -0
  18. package/appUtils/accessibilityManager/logger.ts +5 -0
  19. package/appUtils/focusManager/__tests__/__snapshots__/focusManager.test.js.snap +2 -3
  20. package/appUtils/focusManager/__tests__/focusManager.test.js +4 -24
  21. package/appUtils/focusManager/__tests__/index.test.ts +2 -3
  22. package/appUtils/focusManager/index.ts +76 -57
  23. package/appUtils/focusManager/treeDataStructure/Tree/index.js +2 -3
  24. package/appUtils/focusManager/treeDataStructure/Utils/index.js +76 -38
  25. package/appUtils/focusManager/utils/index.ts +100 -0
  26. package/appUtils/focusManagerAux/index.ts +76 -0
  27. package/appUtils/focusManagerAux/utils/index.ts +100 -0
  28. package/appUtils/{index.js → index.ts} +2 -0
  29. package/appUtils/keyCodes/__tests__/__snapshots__/keyCodes.test.js.snap +1 -1
  30. package/appUtils/keyCodes/__tests__/keyCodes.test.js +80 -85
  31. package/appUtils/keyCodes/{index.js → index.ts} +2 -0
  32. package/appUtils/keyCodes/keys/index.ts +3 -0
  33. package/appUtils/keyCodes/{keys.android.js → keys/keyEventMapper.ts} +0 -19
  34. package/appUtils/keyCodes/keys/keys.android.ts +18 -0
  35. package/appUtils/keyCodes/keys/keys.ios.ts +25 -0
  36. package/appUtils/keyCodes/keys/keys.ts +15 -0
  37. package/appUtils/keyCodes/{keys.web.js → keys/keys.web.ts} +9 -0
  38. package/appUtils/keyCodes/{keysByPlatform.js → keys/keysByPlatform.ts} +11 -1
  39. package/appUtils/orientationHelper.ts +1 -1
  40. package/appUtils/platform/const.ts +25 -0
  41. package/appUtils/platform/index.ts +3 -0
  42. package/appUtils/platform/platformUtils.ts +171 -0
  43. package/appUtils/playerManager/OverlayObserver/OverlaysObserver.ts +53 -0
  44. package/appUtils/playerManager/conts.ts +5 -0
  45. package/appUtils/playerManager/index.ts +11 -5
  46. package/appUtils/playerManager/nativePlayerManager.ts +12 -0
  47. package/appUtils/playerManager/player.ts +48 -7
  48. package/appUtils/playerManager/playerFactory.ts +15 -1
  49. package/appUtils/playerManager/playerNative.ts +40 -14
  50. package/appUtils/playerManager/usePlayerControllerSetup.tsx +7 -1
  51. package/appUtils/playerManager/usePlayerState.tsx +13 -1
  52. package/appUtils/playerManager/usePlayerTitleSummaryOverlay.tsx +50 -0
  53. package/arrayUtils/__tests__/isFilledArray.test.ts +63 -0
  54. package/arrayUtils/index.ts +5 -0
  55. package/booleanUtils/index.ts +4 -4
  56. package/cellUtils/__tests__/cellUtils.test.ts +48 -0
  57. package/cellUtils/__tests__/fixtures.ts +13 -0
  58. package/cellUtils/index.ts +40 -2
  59. package/dateUtils/index.ts +11 -0
  60. package/domUtils/{cookieManager.js → cookieManager.ts} +1 -2
  61. package/focusManager/index.ts +1 -1
  62. package/functionUtils/__tests__/__snapshots__/functionUtils.test.js.snap +2 -2
  63. package/functionUtils/__tests__/functionUtils.test.js +244 -248
  64. package/functionUtils/index.ts +15 -11
  65. package/headersUtils/index.ts +1 -0
  66. package/inflatedUrlUtils/index.ts +4 -4
  67. package/keyboardState/index.ts +0 -0
  68. package/localizationUtils/__tests__/localizationUtils.test.ts +78 -55
  69. package/localizationUtils/index.ts +13 -19
  70. package/logger/index.ts +1 -1
  71. package/manifestUtils/defaultManifestConfigurations/generalContent.js +6 -0
  72. package/manifestUtils/defaultManifestConfigurations/player.js +10 -1
  73. package/manifestUtils/index.js +5 -1
  74. package/manifestUtils/secondaryImage/index.js +18 -15
  75. package/modalState/index.ts +53 -0
  76. package/navigationUtils/index.ts +118 -12
  77. package/numberUtils/__tests__/isLikeNumber.test.ts +18 -0
  78. package/numberUtils/index.ts +9 -18
  79. package/package.json +3 -2
  80. package/playerUtils/configurationGenerator.ts +11 -1
  81. package/pluginUtils/index.ts +20 -7
  82. package/reactHooks/autoscrolling/__tests__/useTrackCurrentAutoScrollingElement.test.ts +2 -0
  83. package/reactHooks/autoscrolling/__tests__/{useTrackedView.test.ts → useTrackedView.test.tsx} +18 -1
  84. package/reactHooks/cell-click/__tests__/index.test.js +16 -3
  85. package/reactHooks/cell-click/index.ts +14 -14
  86. package/reactHooks/device/index.ts +0 -1
  87. package/reactHooks/device/useIsTablet.tsx +28 -3
  88. package/reactHooks/device/useMemoizedIsTablet.ts +1 -1
  89. package/reactHooks/device/useOrientation.tsx +3 -1
  90. package/reactHooks/feed/__tests__/useBuildPipesUrl.test.tsx +30 -70
  91. package/reactHooks/feed/__tests__/useFeedRefresh.test.tsx +6 -2
  92. package/reactHooks/feed/__tests__/useInflatedUrl.test.ts +18 -3
  93. package/reactHooks/feed/useBuildPipesUrl.ts +7 -10
  94. package/reactHooks/feed/useFeedRefresh.tsx +9 -7
  95. package/reactHooks/feed/useInflatedUrl.ts +44 -15
  96. package/reactHooks/flatList/useLoadNextPageIfNeeded.ts +16 -9
  97. package/reactHooks/index.ts +2 -0
  98. package/reactHooks/layout/__tests__/{index.test.js → index.test.tsx} +66 -59
  99. package/reactHooks/layout/getDeviceInfo/index.ts +4 -2
  100. package/reactHooks/layout/index.ts +2 -1
  101. package/reactHooks/layout/types/index.ts +2 -0
  102. package/reactHooks/layout/useDimensions/helpers/__tests__/getInitialDimensions.native.test.js +11 -16
  103. package/reactHooks/layout/useDimensions/helpers/__tests__/getInitialDimensions.test.js +12 -14
  104. package/reactHooks/layout/useDimensions/useDimensions.ts +13 -7
  105. package/reactHooks/navigation/__mocks__/index.ts +15 -0
  106. package/reactHooks/navigation/__mocks__/useNavigation.ts +7 -0
  107. package/reactHooks/navigation/__tests__/index.test.tsx +240 -81
  108. package/reactHooks/navigation/index.ts +246 -84
  109. package/reactHooks/resolvers/__tests__/useCellResolver.test.tsx +3 -1
  110. package/reactHooks/screen/__mocks__/useScreenContext.ts +3 -1
  111. package/reactHooks/screen/__tests__/useCurrentScreenData.test.tsx +21 -23
  112. package/reactHooks/screen/index.ts +4 -0
  113. package/reactHooks/screen/useScreenBackgroundColor.ts +23 -0
  114. package/reactHooks/screen/useScreenContext.ts +4 -4
  115. package/reactHooks/screen/useScreenData.ts +1 -1
  116. package/reactHooks/screen/useTargetScreenData.ts +7 -0
  117. package/reactHooks/state/useRivers.ts +3 -1
  118. package/reactHooks/useListenEventBusEvent.ts +6 -15
  119. package/reactHooks/videoModal/hooks/useVideoModalScreenData.tsx +10 -9
  120. package/reactUtils/index.ts +91 -0
  121. package/riverComponetsMeasurementProvider/index.tsx +2 -2
  122. package/stringUtils/index.ts +5 -0
  123. package/testUtils/getByTestId.ts +14 -0
  124. package/testUtils/index.tsx +38 -23
  125. package/types.ts +4 -0
  126. package/zappFrameworkUtils/loginPluginUtils.ts +12 -2
  127. package/appUtils/focusManager/FocusableEvents.ts +0 -8
  128. package/appUtils/focusManager/treeDataStructure/Utils/__tests__/Utils_android.test.js +0 -1094
  129. package/appUtils/focusManager/treeDataStructure/Utils/index.android.js +0 -261
  130. package/appUtils/keyCodes/keys.ios.js +0 -46
  131. package/reactHooks/device/useIsTablet.android.tsx +0 -14
  132. package/reactHooks/device/useIsTablet.ios.tsx +0 -7
  133. package/reactHooks/device/useIsTablet.web.tsx +0 -3
  134. /package/appUtils/HooksManager/{logger.js → logger.ts} +0 -0
  135. /package/appUtils/{directionHelper.js → directionHelper.ts} +0 -0
  136. /package/appUtils/keyCodes/{androidTVKeys.js → androidTVKeys.ts} +0 -0
  137. /package/appUtils/{localizationsHelper.js → localizationsHelper.ts} +0 -0
  138. /package/domUtils/{logger.js → logger.ts} +0 -0
@@ -0,0 +1,150 @@
1
+ import { BehaviorSubject } from "rxjs";
2
+ import { accessibilityManagerLogger as logger } from "./logger";
3
+ import { TTSManager } from "../platform/platformUtils";
4
+ import { getLocalizations } from "../../localizationUtils";
5
+ import { BUTTON_ACCESSIBILITY_KEYS } from "./const";
6
+
7
+ export interface AccessibilityState {
8
+ screenReaderEnabled: boolean;
9
+ reduceMotionEnabled: boolean;
10
+ boldTextEnabled: boolean;
11
+ }
12
+
13
+ export interface AccessibilityProps {
14
+ accessibilityLabel?: string;
15
+ accessibilityHint?: string;
16
+ "aria-label"?: string;
17
+ "aria-description"?: string;
18
+ }
19
+
20
+ export class AccessibilityManager {
21
+ private static _instance: AccessibilityManager | null = null;
22
+ private state$ = new BehaviorSubject<AccessibilityState>({
23
+ screenReaderEnabled: false,
24
+ reduceMotionEnabled: false,
25
+ boldTextEnabled: false,
26
+ });
27
+
28
+ private announcements$ = new BehaviorSubject<{
29
+ message: string;
30
+ localizedMessage?: string | undefined;
31
+ timestamp: number;
32
+ } | null>(null);
33
+
34
+ private ttsManager = TTSManager.getInstance();
35
+ private localizations: { [key: string]: string };
36
+
37
+ private constructor(localizations?: { [key: string]: string }) {
38
+ if (localizations) {
39
+ this.localizations = getLocalizations({ localizations });
40
+ }
41
+ }
42
+
43
+ public static getInstance(): AccessibilityManager {
44
+ if (!AccessibilityManager._instance) {
45
+ AccessibilityManager._instance = new AccessibilityManager();
46
+ }
47
+
48
+ return AccessibilityManager._instance;
49
+ }
50
+
51
+ public updateLocalizations(localizations: { [key: string]: string }) {
52
+ this.localizations = getLocalizations({ localizations });
53
+ }
54
+
55
+ public getState(): AccessibilityState {
56
+ return this.state$.getValue();
57
+ }
58
+
59
+ public getStateAsObservable() {
60
+ return this.state$.asObservable();
61
+ }
62
+
63
+ /**
64
+ * text you want to be read, if you want to use localized text pass keyOfLocalizedText instead
65
+ * keyOfLocalizedText is the key to the localized text
66
+ */
67
+ public readText({
68
+ text,
69
+ keyOfLocalizedText,
70
+ }: {
71
+ text: string;
72
+ keyOfLocalizedText?: string;
73
+ }) {
74
+ logger.debug("Reading text", { text, keyOfLocalizedText });
75
+
76
+ if (keyOfLocalizedText) {
77
+ if (!this.localizations) {
78
+ logger.error(
79
+ "Attempting to use localized key without initialized localizations"
80
+ );
81
+
82
+ return;
83
+ }
84
+
85
+ const localizedMessage = this.getLocalizedMessage(keyOfLocalizedText);
86
+
87
+ if (!localizedMessage) {
88
+ logger.warn(`No localization found for key: ${keyOfLocalizedText}`);
89
+
90
+ return;
91
+ }
92
+
93
+ text = localizedMessage;
94
+ }
95
+
96
+ if (this.ttsManager) {
97
+ this.ttsManager.readText(text);
98
+ }
99
+
100
+ this.announcements$.next({
101
+ message: text,
102
+ localizedMessage: keyOfLocalizedText ? text : undefined,
103
+ timestamp: Date.now(),
104
+ });
105
+ }
106
+
107
+ public getButtonAccessibilityProps(buttonName: string): AccessibilityProps {
108
+ const buttonConfig = BUTTON_ACCESSIBILITY_KEYS[buttonName];
109
+
110
+ if (!buttonConfig) {
111
+ return {
112
+ accessibilityLabel: buttonName,
113
+ accessibilityHint: `Press button to perform action on ${buttonName}`,
114
+ "aria-label": buttonName,
115
+ "aria-description": `Press button to perform action on ${buttonName}`,
116
+ };
117
+ }
118
+
119
+ const labelKey = buttonConfig.label;
120
+ const hintKey = buttonConfig.hint;
121
+
122
+ const label = this.getLocalizedMessage(labelKey) || buttonName;
123
+
124
+ const hint =
125
+ this.getLocalizedMessage(hintKey) ||
126
+ `Press button to perform action on ${buttonName}`;
127
+
128
+ return {
129
+ accessibilityLabel: label,
130
+ accessibilityHint: hint,
131
+ "aria-label": label,
132
+ "aria-description": hint,
133
+ };
134
+ }
135
+
136
+ public getLocalizedMessage(key: string): string | void {
137
+ if (!key) return;
138
+
139
+ if (!this.localizations) {
140
+ logger.error(
141
+ "Attempting to use localized key without initialized localizations",
142
+ { key }
143
+ );
144
+
145
+ return;
146
+ }
147
+
148
+ return this.localizations[key];
149
+ }
150
+ }
@@ -0,0 +1,5 @@
1
+ import { utilsLogger } from "../../logger";
2
+
3
+ export const accessibilityManagerLogger = utilsLogger.addSubsystem(
4
+ "accessibilityManager"
5
+ );
@@ -3,9 +3,7 @@
3
3
  exports[`focusManager should be defined 1`] = `
4
4
  Object {
5
5
  "blur": [Function],
6
- "disableAutomaticFocus": [Function],
7
6
  "disableFocus": [Function],
8
- "enableAutomaticFocus": [Function],
9
7
  "enableFocus": [Function],
10
8
  "focusableTree": Tree {
11
9
  "loadingCounter": 0,
@@ -23,6 +21,7 @@ Object {
23
21
  "getGroupById": [Function],
24
22
  "getGroupRootById": [Function],
25
23
  "invokeHandler": [Function],
24
+ "isCurrentFocusOnTheTopScreen": [Function],
26
25
  "isFocusDisabled": [Function],
27
26
  "isGroupItemFocused": [Function],
28
27
  "longPress": [Function],
@@ -30,11 +29,11 @@ Object {
30
29
  "on": [Function],
31
30
  "press": [Function],
32
31
  "pressOut": [Function],
32
+ "recoverFocus": [Function],
33
33
  "register": [Function],
34
34
  "removeHandler": [Function],
35
35
  "setFocus": [Function],
36
36
  "setInitialFocus": [Function],
37
- "setupInitialFocus": [Function],
38
37
  "unregister": [Function],
39
38
  }
40
39
  `;
@@ -14,10 +14,8 @@ describe("focusManager", () => {
14
14
  expect(focusManager).toMatchSnapshot();
15
15
  });
16
16
 
17
- it("enableAutomaticFocus should not block setting focus", () => {
17
+ it("manages to land initial focus", () => {
18
18
  const mockSetFocus = jest.fn();
19
- focusManager.setupInitialFocus("1", true);
20
- focusManager.enableAutomaticFocus();
21
19
 
22
20
  focusManager.register({
23
21
  id: "1",
@@ -31,31 +29,13 @@ describe("focusManager", () => {
31
29
  },
32
30
  });
33
31
 
32
+ const { success } = focusManager.setInitialFocus();
33
+
34
+ expect(success).toBe(true);
34
35
  expect(mockSetFocus).toBeCalledTimes(1);
35
36
  expect(mockSetFocus).toBeCalledWith(null);
36
37
  });
37
38
 
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
-
59
39
  describe("register", () => {});
60
40
  describe("unregister", () => {});
61
41
  describe("find", () => {});
@@ -18,7 +18,6 @@ describe("focusManager", () => {
18
18
  "on",
19
19
  "invokeHandler",
20
20
  "removeHandler",
21
- "setupInitialFocus",
22
21
  "longPress",
23
22
  "pressOut",
24
23
  "enableFocus",
@@ -27,10 +26,10 @@ describe("focusManager", () => {
27
26
  "isFocusDisabled",
28
27
  "getCurrentFocusNode",
29
28
  "focusableTree",
30
- "disableAutomaticFocus",
31
- "enableAutomaticFocus",
32
29
  "getGroupRootById",
33
30
  "isGroupItemFocused",
31
+ "recoverFocus",
32
+ "isCurrentFocusOnTheTopScreen",
34
33
  ];
35
34
 
36
35
  expectedMethods.forEach((method) => {
@@ -1,10 +1,18 @@
1
1
  import * as R from "ramda";
2
+ import {
3
+ isNilOrEmpty,
4
+ isNotNil,
5
+ } from "@applicaster/zapp-react-native-utils/reactUtils/helpers";
6
+
2
7
  import { Tree } from "./treeDataStructure/Tree";
3
- import { findFocusableNode, findPriorityItem } from "./treeDataStructure/Utils";
8
+ import {
9
+ findFocusableNode,
10
+ findPriorityItem,
11
+ haveSameParentBeforeRoot,
12
+ } from "./treeDataStructure/Utils";
4
13
  import { subscriber } from "../../functionUtils";
5
14
  import * as FOCUS_EVENTS from "./events";
6
15
  import { coreLogger } from "../../logger";
7
-
8
16
  const logger = coreLogger.addSubsystem("focusManager");
9
17
 
10
18
  const isFocusEnabled = (focusableItem): boolean => {
@@ -44,21 +52,9 @@ export const focusManager = (function () {
44
52
  // focusable node which currently holds the focus
45
53
  let currentFocusNode = null;
46
54
  let lastFocusedId = null;
47
- let initialFocusOnContent = null;
48
55
  let focusDisabled = false;
49
- let focusAutoEnabled = true;
50
56
  const eventHandler = subscriber();
51
57
 
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
-
62
58
  function enableFocus() {
63
59
  focusDisabled = false;
64
60
  }
@@ -93,7 +89,7 @@ export const focusManager = (function () {
93
89
  !currentFocusable.isGroup &&
94
90
  currentFocusable.isMounted()
95
91
  ) {
96
- currentFocusable.blur(currentFocusable, direction);
92
+ currentFocusable.blur(direction);
97
93
  currentFocusable.hasLostFocus(getCurrentFocus(), direction);
98
94
  }
99
95
  }
@@ -226,32 +222,52 @@ export const focusManager = (function () {
226
222
 
227
223
  logger.log({ message: "setInitialFocus", data: { preferredFocus } });
228
224
 
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
- }
225
+ if (isNilOrEmpty(preferredFocus)) {
226
+ return { success: false };
227
+ }
242
228
 
243
- logger.log({
244
- message: "setInitialFocus – currentFocusNode",
245
- data: {
246
- focusableItem,
247
- },
248
- });
229
+ const focusCandidate = preferredFocus[0];
230
+ const focusableItem = focusCandidate.component;
249
231
 
250
- currentFocusNode = focusCandidate;
251
- focusableItem.willReceiveFocus(focusableItem, null);
252
- focusableItem && setFocus(focusCandidate.id, null);
253
- }
232
+ const currentFocusedId = R.path(["props", "id"], getCurrentFocus());
233
+ const nextFocusedId = R.path(["props", "id"], focusableItem);
234
+
235
+ if (
236
+ isNotNil(currentFocusedId) &&
237
+ isNotNil(nextFocusedId) &&
238
+ currentFocusedId === nextFocusedId
239
+ ) {
240
+ // we are going to move focus to the same node(already focused) -> nothing to do
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 };
247
+ }
248
+
249
+ if (
250
+ focusableItem &&
251
+ !focusableItem.isGroup &&
252
+ isFocusEnabled(focusableItem)
253
+ ) {
254
+ blur();
255
+
256
+ logger.log({
257
+ message: "setInitialFocus – currentFocusNode",
258
+ data: {
259
+ focusableItem,
260
+ },
261
+ });
262
+
263
+ currentFocusNode = focusCandidate;
264
+ focusableItem.willReceiveFocus(focusableItem, null);
265
+ focusableItem && setFocus(focusCandidate.id, null);
266
+
267
+ return { success: true };
254
268
  }
269
+
270
+ return { success: false };
255
271
  }
256
272
 
257
273
  function treeLoaded(val, node) {
@@ -261,17 +277,8 @@ export const focusManager = (function () {
261
277
  val,
262
278
  node,
263
279
  currentFocusNode,
264
- initialFocusOnContent,
265
280
  },
266
281
  });
267
-
268
- if ((val && !currentFocusNode) || initialFocusOnContent) {
269
- logger.log({ message: `focusAutoEnabled ${focusAutoEnabled}` });
270
-
271
- if (focusAutoEnabled) {
272
- setInitialFocus(initialFocusOnContent ? node : undefined);
273
- }
274
- }
275
282
  }
276
283
 
277
284
  /**
@@ -402,11 +409,21 @@ export const focusManager = (function () {
402
409
  group ? unregisterGroup(id) : unregisterItem(id);
403
410
  }
404
411
 
405
- function setupInitialFocus(id: string, focusOnContent?: boolean) {
406
- if (focusOnContent) {
407
- initialFocusOnContent = focusOnContent;
408
- } else {
409
- setFocus(id);
412
+ function isCurrentFocusOnTheTopScreen() {
413
+ const routes = R.pathOr([], ["root", "children"], focusableTree);
414
+
415
+ if (routes.length <= 1) {
416
+ // there is only one or zero routes
417
+ return true;
418
+ }
419
+
420
+ return haveSameParentBeforeRoot(currentFocusNode, R.last(routes));
421
+ }
422
+
423
+ function recoverFocus() {
424
+ if (!isCurrentFocusOnTheTopScreen()) {
425
+ // We've failed to set focused node on the new screen => run focus recovery
426
+ setInitialFocus();
410
427
  }
411
428
  }
412
429
 
@@ -416,6 +433,11 @@ export const focusManager = (function () {
416
433
  * @param {Object} direction of the scroll
417
434
  */
418
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
+
419
441
  if (focusDisabled) return false;
420
442
 
421
443
  if (currentFocusNode) {
@@ -444,8 +466,6 @@ export const focusManager = (function () {
444
466
  } else {
445
467
  currentFocusNode.component.failedLostFocus(currentFocusNode, direction);
446
468
  }
447
- } else {
448
- setInitialFocus();
449
469
  }
450
470
  }
451
471
 
@@ -505,7 +525,6 @@ export const focusManager = (function () {
505
525
  on,
506
526
  invokeHandler,
507
527
  removeHandler,
508
- setupInitialFocus,
509
528
  longPress,
510
529
  pressOut,
511
530
  enableFocus,
@@ -514,9 +533,9 @@ export const focusManager = (function () {
514
533
  isFocusDisabled,
515
534
  getCurrentFocusNode,
516
535
  focusableTree,
517
- disableAutomaticFocus,
518
- enableAutomaticFocus,
519
536
  getGroupRootById,
520
537
  isGroupItemFocused,
538
+ recoverFocus,
539
+ isCurrentFocusOnTheTopScreen,
521
540
  };
522
541
  })();
@@ -59,7 +59,7 @@ export class Tree {
59
59
  /**
60
60
  * Update node with provided data from newNode
61
61
  * @private
62
- * @param {Object} node to update
62
+ * @param {Object} currentNode to update
63
63
  * @param {Object} newNode node to take data from
64
64
  */
65
65
  updateNode(currentNode, newNode) {
@@ -175,7 +175,6 @@ export class Tree {
175
175
  * @private
176
176
  * @param {Object} node to check
177
177
  * @returns True if node has defined groupID
178
- * @returns False if node has NOT defined groupID
179
178
  */
180
179
  hasGroupID(node) {
181
180
  return node.component && node.component.props.groupId;
@@ -191,7 +190,7 @@ export class Tree {
191
190
  const findChildNode = this.findInTree(node.component.props.groupId);
192
191
 
193
192
  if (findChildNode) {
194
- this.root.children.pop(findChildNode);
193
+ this.root.children.pop();
195
194
  node.children.push(findChildNode);
196
195
  }
197
196
  }
@@ -1,5 +1,7 @@
1
- import * as R from "ramda";
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 {
@@ -143,13 +145,52 @@ const hasChildren = (target) =>
143
145
 
144
146
  const hasComponent = (target) => !!target?.component;
145
147
 
146
- const hasComponentAndChildren = R.allPass([hasComponent, hasChildren]);
148
+ const hasComponentAndChildren = allPass([hasComponent, hasChildren]);
149
+
150
+ const positiveOrZero = (value) => value > 0 || isZero(value);
151
+
152
+ const excludeFromFocusSearching = pathOr(false, [
153
+ "component",
154
+ "props",
155
+ "excludeFromFocusSearching",
156
+ ]);
157
+
158
+ const isNavBar = (target) => startsWith(QUICK_BRICK_NAVBAR, target?.id);
159
+
160
+ const ignoreMenuIfMultipleTargets = (nodes) => {
161
+ if (nodes.length > 1) {
162
+ return nodes.filter((n) => !isNavBar(n?.target));
163
+ }
164
+
165
+ return nodes;
166
+ };
167
+
168
+ const toDistance = ({ target, direction, comparisonRect }) => {
169
+ const componentRect = target.component?.getRect();
170
+
171
+ const orientationDistance = measureDistance(
172
+ direction,
173
+ comparisonRect,
174
+ componentRect
175
+ );
176
+
177
+ if (orientationDistance === -1) {
178
+ // ignores offscreen targets when moving up/down
179
+ return null;
180
+ }
181
+
182
+ const euclidDistance = measureEuclidianDistanceToCenter(
183
+ comparisonRect,
184
+ componentRect
185
+ );
186
+
187
+ return orientationDistance > 0 ? euclidDistance : orientationDistance;
188
+ };
147
189
 
148
190
  /**
149
191
  * Get target in shortest distance in specified direction
150
192
  * @private
151
193
  * @param {Object} direction of the navigation, which led to this focus change
152
- * @param {Object} excludedNode node which should be excluded from target group
153
194
  * @param {Array} targetNodes array of input target nodes
154
195
  * @param {Object} comparisonRect rectangle to compare sides in direction.
155
196
  * @returns {Object} target in direction.
@@ -164,7 +205,7 @@ export function getTargetInDirection(
164
205
  const overrideTarget = currentNode?.component?.props[overridePropName];
165
206
 
166
207
  if (overrideTarget) {
167
- const target = targetNodes.find((n) => n.id === overrideTarget);
208
+ const target = targetNodes.find((n) => n?.id === overrideTarget);
168
209
 
169
210
  if (target) return target;
170
211
  }
@@ -173,43 +214,18 @@ export function getTargetInDirection(
173
214
 
174
215
  const distances = targets
175
216
  .filter(hasComponentAndChildren)
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
- return {
191
- distance:
192
- measureDistance(direction, comparisonRect, componentRect) +
193
- measureEuclidianDistanceToCenter(comparisonRect, componentRect) /
194
- 10000,
195
- target,
196
- };
197
- })
198
- .filter((obj) => obj && obj.distance >= 0);
217
+ .filter((target) => !excludeFromFocusSearching(target))
218
+ .map((target) => ({
219
+ distance: toDistance({ target, direction, comparisonRect }),
220
+ target,
221
+ }))
222
+ .filter(({ distance }) => positiveOrZero(distance));
199
223
 
200
224
  const match = distances
201
225
  .sort((a, b) => a.distance - b.distance) // sorts the distances
202
226
  .filter(ignoreMenuIfMultipleTargets)[0]; // filters out multiple targets if necessary // gets the first element
203
227
 
204
- return distances && distances.length > 0 ? match.target : null;
205
- }
206
-
207
- function ignoreMenuIfMultipleTargets(nodes) {
208
- if (nodes.length > 1 && nodes.map((n) => n?.target?.id).includes("navbar")) {
209
- return nodes.filter((n) => n?.target?.id !== "navbar");
210
- }
211
-
212
- return nodes;
228
+ return match?.target;
213
229
  }
214
230
 
215
231
  /**
@@ -283,8 +299,7 @@ export function findFocusableNode(direction, currentFocusNode) {
283
299
  * @public
284
300
  * @param {Object} direction of the navigation, which led to this focus change
285
301
  * @param {Object} component - object to check props.prioritiseFocusOn
286
- * @returns {Boolean} TRUE if object has priority in direction.
287
- * @returns {Boolean} FALSE if object has NO priority in direction.
302
+ * @returns {Boolean} TRUE if object has priority in direction. FALSE if object has NO priority in direction.
288
303
  */
289
304
  export function hasPriorityInDirection(direction, component) {
290
305
  const { prioritiseFocusOn = 0 } = component.props;
@@ -300,3 +315,26 @@ export function hasPriorityInDirection(direction, component) {
300
315
 
301
316
  return (prioritiseFocusOn & mappedDirection) > 0;
302
317
  }
318
+
319
+ export const getNodeBeforeRoot = (node) => {
320
+ if (isNil(node)) {
321
+ return undefined;
322
+ }
323
+
324
+ if (isNil(node.parent)) {
325
+ return undefined;
326
+ }
327
+
328
+ if (node.parent.id === "root") {
329
+ return node;
330
+ }
331
+
332
+ return getNodeBeforeRoot(node.parent);
333
+ };
334
+
335
+ export const haveSameParentBeforeRoot = (currentNode, nextNode) => {
336
+ const routeForCurrentNode = getNodeBeforeRoot(currentNode);
337
+ const routeForNextNode = getNodeBeforeRoot(nextNode);
338
+
339
+ return routeForCurrentNode?.id === routeForNextNode?.id;
340
+ };