@applicaster/zapp-react-native-utils 14.0.0-alpha.3552323332 → 14.0.0-alpha.3652810444

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 (127) hide show
  1. package/actionsExecutor/ActionExecutorContext.tsx +60 -84
  2. package/actionsExecutor/ScreenActions.ts +164 -0
  3. package/actionsExecutor/StorageActions.ts +110 -0
  4. package/actionsExecutor/feedDecorator.ts +171 -0
  5. package/actionsExecutor/screenResolver.ts +11 -0
  6. package/analyticsUtils/AnalyticsEvents/helper.ts +81 -0
  7. package/analyticsUtils/AnalyticsEvents/sendHeaderClickEvent.ts +1 -1
  8. package/analyticsUtils/AnalyticsEvents/sendMenuClickEvent.ts +2 -1
  9. package/analyticsUtils/AnalyticsEvents/sendOnClickEvent.ts +14 -4
  10. package/analyticsUtils/__tests__/analyticsUtils.test.js +3 -0
  11. package/analyticsUtils/events.ts +8 -0
  12. package/analyticsUtils/index.tsx +3 -4
  13. package/analyticsUtils/manager.ts +1 -1
  14. package/analyticsUtils/playerAnalyticsTracker.ts +2 -1
  15. package/appUtils/HooksManager/Hook.ts +4 -4
  16. package/appUtils/HooksManager/index.ts +11 -1
  17. package/appUtils/accessibilityManager/index.ts +5 -5
  18. package/appUtils/contextKeysManager/contextResolver.ts +42 -1
  19. package/appUtils/focusManager/__tests__/__snapshots__/focusManager.test.js.snap +5 -0
  20. package/appUtils/focusManager/__tests__/focusManager.test.js +1 -1
  21. package/appUtils/focusManager/events.ts +2 -0
  22. package/appUtils/focusManager/index.ios.ts +10 -0
  23. package/appUtils/focusManager/index.ts +82 -11
  24. package/appUtils/focusManager/treeDataStructure/Tree/index.js +1 -1
  25. package/appUtils/focusManagerAux/utils/index.ts +106 -3
  26. package/appUtils/playerManager/OverlayObserver/OverlaysObserver.ts +0 -15
  27. package/appUtils/playerManager/useChapterMarker.tsx +0 -1
  28. package/appUtils/playerManager/usePlayerControllerSetup.tsx +16 -0
  29. package/arrayUtils/__tests__/isEmptyArray.test.ts +63 -0
  30. package/arrayUtils/__tests__/isFilledArray.test.ts +1 -1
  31. package/arrayUtils/index.ts +8 -3
  32. package/audioPlayerUtils/__tests__/getArtworkImage.test.ts +144 -0
  33. package/audioPlayerUtils/__tests__/getBackgroundImage.test.ts +72 -0
  34. package/audioPlayerUtils/__tests__/getImageFromEntry.test.ts +110 -0
  35. package/audioPlayerUtils/assets/index.ts +2 -0
  36. package/audioPlayerUtils/index.ts +242 -0
  37. package/componentsUtils/__tests__/isTabsScreen.test.ts +38 -0
  38. package/componentsUtils/index.ts +4 -1
  39. package/conf/player/__tests__/selectors.test.ts +34 -0
  40. package/conf/player/selectors.ts +10 -0
  41. package/configurationUtils/__tests__/configurationUtils.test.js +0 -31
  42. package/configurationUtils/__tests__/getMediaItems.test.ts +65 -0
  43. package/configurationUtils/__tests__/imageSrcFromMediaItem.test.ts +34 -0
  44. package/configurationUtils/__tests__/manifestKeyParser.test.ts +546 -0
  45. package/configurationUtils/index.ts +63 -34
  46. package/configurationUtils/manifestKeyParser.ts +57 -32
  47. package/focusManager/FocusManager.ts +89 -20
  48. package/focusManager/Tree.ts +25 -21
  49. package/focusManager/__tests__/FocusManager.test.ts +50 -8
  50. package/focusManager/aux/index.ts +167 -0
  51. package/focusManager/utils.ts +12 -6
  52. package/index.d.ts +0 -9
  53. package/manifestUtils/_internals/getDefaultConfiguration.js +28 -0
  54. package/manifestUtils/{_internals.js → _internals/index.js} +2 -25
  55. package/manifestUtils/createConfig.js +4 -1
  56. package/manifestUtils/defaultManifestConfigurations/player.js +1239 -200
  57. package/manifestUtils/progressBar/__tests__/mobileProgressBar.test.js +0 -30
  58. package/navigationUtils/__tests__/mapContentTypesToRivers.test.ts +130 -0
  59. package/navigationUtils/index.ts +7 -5
  60. package/package.json +2 -3
  61. package/playerUtils/__tests__/configurationUtils.test.ts +1 -65
  62. package/playerUtils/__tests__/getPlayerActionButtons.test.ts +54 -0
  63. package/playerUtils/_internals/__tests__/utils.test.ts +71 -0
  64. package/playerUtils/_internals/index.ts +1 -0
  65. package/playerUtils/_internals/utils.ts +31 -0
  66. package/playerUtils/configurationUtils.ts +0 -44
  67. package/playerUtils/getPlayerActionButtons.ts +17 -0
  68. package/playerUtils/index.ts +2 -0
  69. package/playerUtils/useValidatePlayerConfig.tsx +22 -19
  70. package/reactHooks/autoscrolling/__tests__/useTrackedView.test.tsx +15 -14
  71. package/reactHooks/cell-click/__tests__/index.test.js +3 -0
  72. package/reactHooks/cell-click/index.ts +8 -1
  73. package/reactHooks/debugging/__tests__/index.test.js +0 -1
  74. package/reactHooks/feed/__tests__/useBatchLoading.test.tsx +47 -90
  75. package/reactHooks/feed/__tests__/useFeedLoader.test.tsx +71 -31
  76. package/reactHooks/feed/index.ts +2 -0
  77. package/reactHooks/feed/useBatchLoading.ts +17 -10
  78. package/reactHooks/feed/useFeedLoader.tsx +36 -34
  79. package/reactHooks/feed/useLoadPipesDataDispatch.ts +63 -0
  80. package/reactHooks/feed/usePipesCacheReset.ts +3 -3
  81. package/reactHooks/flatList/useSequentialRenderItem.tsx +3 -3
  82. package/reactHooks/layout/__tests__/index.test.tsx +3 -1
  83. package/reactHooks/layout/isTablet/index.ts +12 -5
  84. package/reactHooks/layout/useDimensions/__tests__/useDimensions.test.ts +34 -36
  85. package/reactHooks/layout/useDimensions/useDimensions.ts +2 -3
  86. package/reactHooks/layout/useLayoutVersion.ts +5 -5
  87. package/reactHooks/navigation/index.ts +7 -5
  88. package/reactHooks/navigation/useIsScreenActive.ts +9 -5
  89. package/reactHooks/navigation/useRoute.ts +7 -2
  90. package/reactHooks/navigation/useScreenStateStore.ts +8 -0
  91. package/reactHooks/resolvers/__tests__/useCellResolver.test.tsx +4 -0
  92. package/reactHooks/screen/useScreenContext.ts +1 -1
  93. package/reactHooks/state/__tests__/ZStoreProvider.test.tsx +2 -1
  94. package/reactHooks/state/index.ts +1 -1
  95. package/reactHooks/state/useHomeRiver.ts +4 -2
  96. package/reactHooks/state/useRivers.ts +7 -8
  97. package/riverComponetsMeasurementProvider/index.tsx +1 -1
  98. package/screenPickerUtils/index.ts +13 -0
  99. package/services/js2native.ts +1 -0
  100. package/storage/ScreenSingleValueProvider.ts +204 -0
  101. package/storage/ScreenStateMultiSelectProvider.ts +293 -0
  102. package/storage/StorageMultiSelectProvider.ts +192 -0
  103. package/storage/StorageSingleSelectProvider.ts +108 -0
  104. package/testUtils/index.tsx +7 -8
  105. package/time/BackgroundTimer.ts +6 -4
  106. package/utils/__tests__/endsWith.test.ts +30 -0
  107. package/utils/__tests__/equals.test.ts +65 -0
  108. package/utils/__tests__/find.test.ts +36 -0
  109. package/utils/__tests__/max.test.ts +36 -0
  110. package/utils/__tests__/omit.test.ts +19 -0
  111. package/utils/__tests__/path.test.ts +33 -0
  112. package/utils/__tests__/pathOr.test.ts +37 -0
  113. package/utils/__tests__/startsWith.test.ts +30 -0
  114. package/utils/__tests__/take.test.ts +40 -0
  115. package/utils/__tests__/toLower.test.ts +39 -0
  116. package/utils/endsWith.ts +9 -0
  117. package/utils/equals.ts +5 -0
  118. package/utils/find.ts +3 -0
  119. package/utils/index.ts +37 -1
  120. package/utils/max.ts +5 -0
  121. package/utils/omit.ts +5 -0
  122. package/utils/path.ts +5 -0
  123. package/utils/pathOr.ts +5 -0
  124. package/utils/startsWith.ts +9 -0
  125. package/utils/take.ts +5 -0
  126. package/utils/toLower.ts +5 -0
  127. package/playerUtils/configurationGenerator.ts +0 -2572
@@ -11,8 +11,11 @@ const currentPlatform = platformSelect({
11
11
  android_tv: "android",
12
12
  });
13
13
 
14
- const stateSuffixes = ["pressed", "focused"];
14
+ // Do not change the order, focused_selected should be first
15
+ const states = ["focused_selected", "pressed", "focused", "selected"];
16
+
15
17
  const platformSuffixes = ["ios", "tvos", "samsung", "lg", "android"];
18
+ type StateKey = (typeof states)[number] | "default";
16
19
 
17
20
  export type GetAllSpecificStylesProps = {
18
21
  componentName: string;
@@ -33,39 +36,47 @@ export const getAllSpecificStyles = ({
33
36
  subComponentName,
34
37
  outStyles,
35
38
  }: GetAllSpecificStylesProps) => {
36
- if (!outStyles) {
37
- throw new Error("outStyles is required");
38
- }
39
+ if (!outStyles) throw new Error("outStyles is required");
40
+ if (!componentName) throw new Error("componentName is required");
39
41
 
40
- // Pressed, platform specific styles are not supported
41
- const prefix = `${componentName}_style_${subComponentName}_`;
42
+ const prefix =
43
+ subComponentName?.length > 0
44
+ ? `${componentName}_style_${subComponentName}_`
45
+ : `${componentName}_style_`;
42
46
 
43
47
  const defaultKey = "default";
44
48
 
45
- if (!outStyles[defaultKey]) {
46
- outStyles[defaultKey] = {};
47
- }
49
+ if (!outStyles[defaultKey]) outStyles[defaultKey] = {};
50
+
51
+ const tmpGenericDict: Record<StateKey, Record<string, any>> = {};
52
+ const tmpPlatformDict: Record<StateKey, Record<string, any>> = {};
48
53
 
49
- Object.keys(configuration).forEach((key) => {
54
+ const parseKey = (key: string, value: any) => {
50
55
  if (!key.startsWith(prefix)) {
51
56
  return;
52
57
  }
53
58
 
54
59
  let styleName = key.slice(prefix.length);
60
+ if (!styleName || styleName.startsWith("_")) return;
55
61
 
56
- const platform = platformSuffixes.find((suffix) =>
57
- styleName.startsWith(suffix)
62
+ const platform = platformSuffixes.find((prefix) =>
63
+ styleName.startsWith(prefix)
58
64
  );
59
65
 
66
+ let tmpDict = tmpGenericDict;
67
+
60
68
  if (platform) {
61
69
  if (currentPlatform !== platform) {
62
- return; // Continue
70
+ return;
63
71
  }
64
72
 
73
+ tmpDict = tmpPlatformDict;
65
74
  styleName = styleName.slice(platform.length + 1);
66
75
  }
67
76
 
68
- let state = stateSuffixes.find((suffix) => styleName.startsWith(suffix));
77
+ let state = states.find((prefix) => styleName.startsWith(prefix)) as
78
+ | StateKey
79
+ | undefined;
69
80
 
70
81
  if (state) {
71
82
  styleName = styleName.slice(state.length + 1);
@@ -73,30 +84,44 @@ export const getAllSpecificStyles = ({
73
84
  state = defaultKey;
74
85
  }
75
86
 
76
- const camelCaseKey = toCamelCase(styleName);
87
+ if (!styleName) return;
77
88
 
78
- let stateOverrides = outStyles[state];
89
+ const camelCaseKey = toCamelCase(styleName);
79
90
 
80
- if (!stateOverrides) {
81
- stateOverrides = {};
82
- outStyles[state] = stateOverrides;
83
- }
91
+ if (!tmpDict[state]) tmpDict[state] = {};
92
+ tmpDict[state][camelCaseKey] = value;
93
+ };
84
94
 
85
- if (stateOverrides[camelCaseKey]) {
86
- // Add warning, key is already defined
87
- }
95
+ for (const [key, value] of Object.entries(configuration)) {
96
+ parseKey(key, value);
97
+ }
88
98
 
89
- stateOverrides[camelCaseKey] = configuration[key];
90
- });
99
+ const allStates = Array.from(
100
+ new Set<StateKey>([
101
+ defaultKey,
102
+ ...(Object.keys(tmpGenericDict) as StateKey[]),
103
+ ...(Object.keys(tmpPlatformDict) as StateKey[]),
104
+ ...(Object.keys(outStyles) as StateKey[]),
105
+ ])
106
+ );
107
+
108
+ for (const state of allStates) {
109
+ outStyles[state] = Object.assign(
110
+ {},
111
+ outStyles[state] || {},
112
+ tmpGenericDict[state] || {},
113
+ tmpPlatformDict[state] || {}
114
+ );
115
+ }
91
116
 
92
117
  // Merge default styles with state specific styles
93
- if (Object.keys(outStyles).length > 1) {
94
- Object.keys(outStyles).forEach((key) => {
95
- if (key === defaultKey) {
96
- return;
97
- }
118
+ for (const state of Object.keys(outStyles)) {
119
+ if (state === defaultKey) continue;
98
120
 
99
- outStyles[key] = Object.assign({}, outStyles[defaultKey], outStyles[key]);
100
- });
121
+ outStyles[state] = Object.assign(
122
+ {},
123
+ outStyles[defaultKey],
124
+ outStyles[state]
125
+ );
101
126
  }
102
127
  };
@@ -7,6 +7,16 @@ import { TreeNode } from "./TreeNode";
7
7
  import { Tree } from "./Tree";
8
8
  import { subscriber } from "../functionUtils";
9
9
  import { getFocusableId, toFocusDirection } from "./utils";
10
+ import {
11
+ isCurrentFocusOnMenu,
12
+ isCurrentFocusOnContent,
13
+ findSelectedMenuId,
14
+ isTabsScreenContentFocused,
15
+ findSelectedTabId,
16
+ contextWithoutScrolling,
17
+ } from "./aux";
18
+
19
+ export { contextWithoutScrolling } from "./aux";
10
20
 
11
21
  export {
12
22
  toFocusDirection,
@@ -176,18 +186,30 @@ class FocusManager {
176
186
  }
177
187
  }
178
188
 
179
- registerFocusable(
180
- component: FocusManager.TouchableReactRef,
181
- parentFocusable: FocusManager.TouchableReactRef,
182
- isFocusableCell: boolean
183
- ) {
184
- const focusableId = getFocusableId(component);
189
+ registerFocusable({
190
+ touchableRef,
191
+ parentFocusableRef,
192
+ isFocusableCell,
193
+ parentFocusableId,
194
+ }: {
195
+ touchableRef: FocusManager.TouchableReactRef;
196
+ parentFocusableRef: FocusManager.TouchableReactRef;
197
+ isFocusableCell: boolean;
198
+ parentFocusableId: string;
199
+ }) {
200
+ const focusableId = getFocusableId(touchableRef);
201
+
185
202
  const focusableComponent = FocusManager.findFocusable(focusableId);
186
203
 
187
- if (!focusableComponent && component) {
188
- this.focusableComponents.push(component);
204
+ if (!focusableComponent && touchableRef) {
205
+ this.focusableComponents.push(touchableRef);
189
206
 
190
- this.tree.add(component, parentFocusable, isFocusableCell);
207
+ this.tree.add(
208
+ touchableRef,
209
+ parentFocusableRef,
210
+ isFocusableCell,
211
+ parentFocusableId
212
+ );
191
213
  } else {
192
214
  logger.warning("Focusable component already registered", {
193
215
  id: focusableId,
@@ -209,7 +231,8 @@ class FocusManager {
209
231
 
210
232
  private setNextFocus(
211
233
  nextFocus: FocusManager.TouchableReactRef,
212
- options?: FocusManager.Android.CallbackOptions
234
+ options?: FocusManager.Android.CallbackOptions,
235
+ context?: FocusManager.FocusContext
213
236
  ) {
214
237
  if (nextFocus?.current?.props?.blockFocus) {
215
238
  return;
@@ -238,17 +261,15 @@ class FocusManager {
238
261
 
239
262
  FocusManager.instance.setPreviousNavigationDirection(options ?? null);
240
263
 
241
- nextFocus?.current?.onFocus?.(nextFocus.current, options ?? {});
264
+ nextFocus?.current?.onFocus?.(nextFocus.current, options ?? {}, context);
242
265
  }
243
266
  }
244
267
 
245
268
  blurPrevious(options?: FocusManager.Android.CallbackOptions) {
246
- if (options) {
247
- FocusManager.instance.prevFocused?.onBlur?.(
248
- FocusManager.instance.prevFocused,
249
- options
250
- );
251
- }
269
+ FocusManager.instance.prevFocused?.onBlur?.(
270
+ FocusManager.instance.prevFocused,
271
+ options ?? {} // Adding fallback to avoid potential regression caused by #7509
272
+ );
252
273
  }
253
274
 
254
275
  onDisableFocusChange = (id) => {
@@ -269,7 +290,7 @@ class FocusManager {
269
290
 
270
291
  if (nextFocus) {
271
292
  // HACK: hack to fix the hack below
272
- // HACK: putting call to the end of the event loop so the next component has a chane to be registered
293
+ // HACK: putting call to the end of the event loop so the next component has a chance to be registered
273
294
  setTimeout(() => {
274
295
  FocusManager.instance.setFocus(nextFocus, {
275
296
  direction: "down",
@@ -281,7 +302,8 @@ class FocusManager {
281
302
 
282
303
  setFocus(
283
304
  newFocus: FocusManager.TouchableReactRef | string,
284
- options?: FocusManager.Android.CallbackOptions
305
+ options?: FocusManager.Android.CallbackOptions,
306
+ context?: FocusManager.FocusContext
285
307
  ) {
286
308
  // Checks if element is focusable
287
309
  const { isFocusable, error } = FocusManager.isFocusable(newFocus);
@@ -306,7 +328,7 @@ class FocusManager {
306
328
  }
307
329
 
308
330
  if (newFocusRef) {
309
- FocusManager.instance.setNextFocus(newFocusRef, options);
331
+ FocusManager.instance.setNextFocus(newFocusRef, options, context);
310
332
  }
311
333
  }
312
334
  }
@@ -341,6 +363,11 @@ class FocusManager {
341
363
  FocusManager.instance.focused.onBlur(FocusManager.instance.focused, {});
342
364
  }
343
365
 
366
+ // send reset event to some handler to reset their internal state, before real reset happens
367
+ this.eventHandler?.invokeHandler?.(FOCUS_EVENTS.RESET, {
368
+ focusedId: FocusManager.instance.focusedId,
369
+ });
370
+
344
371
  FocusManager.instance.setFocusLocal({ current: null });
345
372
  }
346
373
 
@@ -407,6 +434,48 @@ class FocusManager {
407
434
  throw new Error(`Group with id ${id} not found`);
408
435
  }
409
436
  }
437
+
438
+ isFocusOnMenu(): boolean {
439
+ return isCurrentFocusOnMenu(this.tree, FocusManager.instance.focusedId);
440
+ }
441
+
442
+ isFocusOnContent(): boolean {
443
+ return isCurrentFocusOnContent(this.tree, FocusManager.instance.focusedId);
444
+ }
445
+
446
+ // Move focus to appropriate top navigation tab with context
447
+ focusTopNavigation(isTabsScreen: boolean, item: ZappEntry): void {
448
+ const landFocusTo = (id) => {
449
+ if (id) {
450
+ // set focus on selected menu item
451
+ const direction = undefined;
452
+
453
+ const context: FocusManager.FocusContext =
454
+ contextWithoutScrolling("back");
455
+
456
+ logger.log({ message: "landFocusTo", data: { id } });
457
+
458
+ this.setFocus(id, direction, context);
459
+ }
460
+ };
461
+
462
+ if (
463
+ isTabsScreen &&
464
+ isTabsScreenContentFocused(this.tree, FocusManager.instance.focusedId)
465
+ ) {
466
+ const selectedTabId = findSelectedTabId(this.tree, item);
467
+
468
+ // Set focus with back button context to tabs-menu
469
+ landFocusTo(selectedTabId);
470
+
471
+ return;
472
+ }
473
+
474
+ const selectedMenuItemId = findSelectedMenuId(this.tree);
475
+
476
+ // Set focus with back button context to top-menu
477
+ landFocusTo(selectedMenuItemId);
478
+ }
410
479
  }
411
480
 
412
481
  export const focusManager = FocusManager.getInstance();
@@ -8,37 +8,41 @@ export class Tree {
8
8
  this.tree = focusManagerTree;
9
9
  }
10
10
 
11
- add(component, parentFocusable, isFocusableCell) {
12
- const focusableId = getFocusableId(component);
13
- const parentId = getFocusableId(parentFocusable);
11
+ add(
12
+ touchableRef: FocusManager.TouchableReactRef,
13
+ parentFocusableRef: FocusManager.TouchableReactRef,
14
+ isFocusableCell: boolean,
15
+ parentFocusableId: string
16
+ ) {
17
+ const focusableId = getFocusableId(touchableRef);
18
+ const parentId = getFocusableId(parentFocusableRef) || parentFocusableId;
14
19
  const focusableComponentInTree = this.find(focusableId);
15
20
 
16
21
  // update node if it already exists
17
22
  if (focusableComponentInTree) {
18
- focusableComponentInTree.updateNode(component);
23
+ focusableComponentInTree.updateNode(touchableRef);
19
24
  }
20
25
 
21
- if (parentFocusable?.current) {
22
- if (!this.find(parentId)) {
23
- this.tree.push(new TreeNode(null, parentId, null, isFocusableCell));
24
- }
26
+ if (!this.find(parentId)) {
27
+ // create temporary node to the root of the tree
28
+ this.tree.push(new TreeNode(null, parentId, null, isFocusableCell));
29
+ }
25
30
 
26
- const parentNode = this.find(parentId);
31
+ const parentNode = this.find(parentId);
27
32
 
28
- if (parentNode) {
29
- if (focusableComponentInTree) {
30
- focusableComponentInTree.isFocusableCell = isFocusableCell;
31
- focusableComponentInTree.parentId = parentNode.id;
33
+ if (parentNode) {
34
+ if (focusableComponentInTree) {
35
+ focusableComponentInTree.isFocusableCell = isFocusableCell;
36
+ focusableComponentInTree.parentId = parentNode.id;
32
37
 
33
- parentNode.addChild(focusableComponentInTree);
38
+ parentNode.addChild(focusableComponentInTree);
34
39
 
35
- // remove root object from the list
36
- this.tree = this.tree.filter(
37
- (node) => node !== focusableComponentInTree
38
- );
39
- } else {
40
- parentNode.addChild(component, focusableId, isFocusableCell);
41
- }
40
+ // remove root object from the list
41
+ this.tree = this.tree.filter(
42
+ (node) => node !== focusableComponentInTree
43
+ );
44
+ } else {
45
+ parentNode.addChild(touchableRef, focusableId, isFocusableCell);
42
46
  }
43
47
  }
44
48
  }
@@ -1,5 +1,8 @@
1
1
  import { focusManager } from "../FocusManager";
2
2
 
3
+ const isFocusableCell = true;
4
+ const parentFocusableId = "parentFocusableId";
5
+
3
6
  const group = {
4
7
  current: {
5
8
  props: {
@@ -62,13 +65,47 @@ jest.useFakeTimers();
62
65
 
63
66
  describe("FocusManager", () => {
64
67
  beforeAll(() => {
65
- focusManager.registerFocusable(group, { current: null });
66
- focusManager.registerFocusable(child1, group);
67
- focusManager.registerFocusable(child2, group);
68
- focusManager.registerFocusable(child3, child2);
69
-
70
- focusManager.registerFocusable(child4, child2);
71
- focusManager.registerFocusable(child5, child2);
68
+ focusManager.registerFocusable({
69
+ touchableRef: group,
70
+ parentFocusableRef: { current: null },
71
+ isFocusableCell,
72
+ parentFocusableId,
73
+ });
74
+
75
+ focusManager.registerFocusable({
76
+ touchableRef: child1,
77
+ parentFocusableRef: group,
78
+ isFocusableCell,
79
+ parentFocusableId,
80
+ });
81
+
82
+ focusManager.registerFocusable({
83
+ touchableRef: child2,
84
+ parentFocusableRef: group,
85
+ isFocusableCell,
86
+ parentFocusableId,
87
+ });
88
+
89
+ focusManager.registerFocusable({
90
+ touchableRef: child3,
91
+ parentFocusableRef: child2,
92
+ isFocusableCell,
93
+ parentFocusableId,
94
+ });
95
+
96
+ focusManager.registerFocusable({
97
+ touchableRef: child4,
98
+ parentFocusableRef: child2,
99
+ isFocusableCell,
100
+ parentFocusableId,
101
+ });
102
+
103
+ focusManager.registerFocusable({
104
+ touchableRef: child5,
105
+ parentFocusableRef: child2,
106
+ isFocusableCell,
107
+ parentFocusableId,
108
+ });
72
109
  });
73
110
 
74
111
  it("focusManager should be defined", () => {
@@ -199,7 +236,12 @@ describe("FocusManager", () => {
199
236
  });
200
237
 
201
238
  it("focusManager registerFocusable should register", () => {
202
- focusManager.registerFocusable(child5, child2);
239
+ focusManager.registerFocusable({
240
+ touchableRef: child5,
241
+ parentFocusableRef: child2,
242
+ isFocusableCell,
243
+ parentFocusableId,
244
+ });
203
245
 
204
246
  expect(
205
247
  focusManager.isFocusableChildOf(child5.current.props.id, child2)
@@ -0,0 +1,167 @@
1
+ import {
2
+ isNil,
3
+ startsWith,
4
+ find,
5
+ pathOr,
6
+ } from "@applicaster/zapp-react-native-utils/utils";
7
+
8
+ import {
9
+ QUICK_BRICK_CONTENT,
10
+ QUICK_BRICK_NAVBAR,
11
+ QUICK_BRICK_NAVBAR_SECTIONS,
12
+ } from "@applicaster/quick-brick-core/const";
13
+
14
+ const isNavBar = (node) => startsWith(QUICK_BRICK_NAVBAR, node?.id);
15
+ const isContent = (node) => startsWith(QUICK_BRICK_CONTENT, node?.id);
16
+
17
+ // SCREEN_PICKER_SELECTOR_CONTAINER(we assume there is only one SCREEN_PICKER)
18
+ let screenPickerSelectorContainerId;
19
+
20
+ export const onRegisterScreenPickerSelectorContainer = (id) => {
21
+ screenPickerSelectorContainerId = id;
22
+ };
23
+
24
+ export const onUnregisterScreenPickerSelectorContainer = (id) => {
25
+ // reset screenSelectorId on unregistration
26
+ if (screenPickerSelectorContainerId === id) {
27
+ screenPickerSelectorContainerId = undefined;
28
+ }
29
+ };
30
+ // SCREEN_PICKER_SELECTOR_CONTAINER
31
+
32
+ // SCREEN_PICKER_CONTENT_CONTAINER(we assume there is only one SCREEN_PICKER)
33
+ let screenPickerContentContainerId;
34
+
35
+ export const onRegisterScreenPickerContentContainer = (id) => {
36
+ screenPickerContentContainerId = id;
37
+ };
38
+
39
+ export const onUnregisterScreenPickerContentContainer = (id) => {
40
+ // reset screenSelectorId on unregistration
41
+ if (screenPickerContentContainerId === id) {
42
+ screenPickerContentContainerId = undefined;
43
+ }
44
+ };
45
+
46
+ const isScreenPickerContentContainer = (node) =>
47
+ screenPickerContentContainerId === node?.id;
48
+
49
+ // SCREEN_PICKER_CONTENT_CONTAINER
50
+
51
+ const findSelectedMenuIdInSection = (focusableTree, section) => {
52
+ const children = pathOr([], ["children"], focusableTree.find(section));
53
+
54
+ return find((child) => child.component.current.props.isSelected(), children)
55
+ ?.id;
56
+ };
57
+
58
+ export const findSelectedMenuId = (focusableTree) => {
59
+ const selectedMenuItemIdInLeftSection = findSelectedMenuIdInSection(
60
+ focusableTree,
61
+ QUICK_BRICK_NAVBAR_SECTIONS.left
62
+ );
63
+
64
+ if (selectedMenuItemIdInLeftSection) {
65
+ return selectedMenuItemIdInLeftSection;
66
+ }
67
+
68
+ const selectedMenuItemIdInMenuSection = findSelectedMenuIdInSection(
69
+ focusableTree,
70
+ QUICK_BRICK_NAVBAR_SECTIONS.menu
71
+ );
72
+
73
+ if (selectedMenuItemIdInMenuSection) {
74
+ return selectedMenuItemIdInMenuSection;
75
+ }
76
+
77
+ const selectedMenuItemIdInRightSection = findSelectedMenuIdInSection(
78
+ focusableTree,
79
+ QUICK_BRICK_NAVBAR_SECTIONS.right
80
+ );
81
+
82
+ if (selectedMenuItemIdInRightSection) {
83
+ return selectedMenuItemIdInRightSection;
84
+ }
85
+
86
+ return undefined;
87
+ };
88
+
89
+ export const findSelectedTabId = (focusableTree, item: ZappEntry): string => {
90
+ const screenSelectorContainerNode = focusableTree.find(
91
+ screenPickerSelectorContainerId
92
+ );
93
+
94
+ const selectedTabId = find(
95
+ (child) => child.component.current.props.isSelected(item.id),
96
+ screenSelectorContainerNode.children
97
+ )?.id;
98
+
99
+ return selectedTabId;
100
+ };
101
+
102
+ export const isTabsScreenContentFocused = (focusableTree, id) => {
103
+ const node = focusableTree.find(id);
104
+
105
+ if (isNil(node)) {
106
+ return false;
107
+ }
108
+
109
+ if (isNavBar(node)) {
110
+ return false;
111
+ }
112
+
113
+ if (isContent(node)) {
114
+ return false;
115
+ }
116
+
117
+ if (isScreenPickerContentContainer(node)) {
118
+ return true;
119
+ }
120
+
121
+ return isTabsScreenContentFocused(focusableTree, node.parentId);
122
+ };
123
+
124
+ export const isCurrentFocusOnMenu = (focusableTree, id): boolean => {
125
+ const node = focusableTree.find(id);
126
+
127
+ if (isNil(node)) {
128
+ return false;
129
+ }
130
+
131
+ if (isNavBar(node)) {
132
+ return true;
133
+ }
134
+
135
+ if (isContent(node)) {
136
+ return false;
137
+ }
138
+
139
+ return isCurrentFocusOnMenu(focusableTree, node.parentId);
140
+ };
141
+
142
+ export const isCurrentFocusOnContent = (focusableTree, id) => {
143
+ const node = focusableTree.find(id);
144
+
145
+ if (isNil(node)) {
146
+ return false;
147
+ }
148
+
149
+ if (isNavBar(node)) {
150
+ return false;
151
+ }
152
+
153
+ if (isContent(node)) {
154
+ return true;
155
+ }
156
+
157
+ return isCurrentFocusOnContent(focusableTree, node.parentId);
158
+ };
159
+
160
+ export const contextWithoutScrolling = (
161
+ source: FocusManager.FocusContext["source"]
162
+ ): FocusManager.FocusContext => {
163
+ return {
164
+ source,
165
+ preserveScroll: true,
166
+ };
167
+ };
@@ -4,11 +4,11 @@ import { isNilOrEmpty } from "@applicaster/zapp-react-native-utils/reactUtils/he
4
4
 
5
5
  export const getFocusableId = (ref) => ref?.current?.props.id;
6
6
 
7
- type Direction = "up" | "down" | "left" | "right";
8
-
9
7
  const normalizeDirection = (direction) => direction.toLowerCase();
10
8
 
11
- const checkDirection = (direction: Direction) => {
9
+ const checkDirection = (
10
+ direction: FocusManager.Android.FocusNavigationDirections
11
+ ) => {
12
12
  invariant(!isNilOrEmpty(direction), "direction should not be empty");
13
13
 
14
14
  invariant(
@@ -17,19 +17,25 @@ const checkDirection = (direction: Direction) => {
17
17
  );
18
18
  };
19
19
 
20
- export const toFocusDirection = (direction: Direction) => {
20
+ export const toFocusDirection = (
21
+ direction: FocusManager.Android.FocusNavigationDirections
22
+ ) => {
21
23
  checkDirection(direction);
22
24
 
23
25
  return `nextFocus${capitalize(normalizeDirection(direction))}`;
24
26
  };
25
27
 
26
- export const isHorizontalDirection = (direction: Direction) => {
28
+ export const isHorizontalDirection = (
29
+ direction: FocusManager.Android.FocusNavigationDirections
30
+ ) => {
27
31
  checkDirection(direction);
28
32
 
29
33
  return ["left", "right"].includes(normalizeDirection(direction));
30
34
  };
31
35
 
32
- export const isVerticalDirection = (direction: Direction) => {
36
+ export const isVerticalDirection = (
37
+ direction: FocusManager.Android.FocusNavigationDirections
38
+ ) => {
33
39
  checkDirection(direction);
34
40
 
35
41
  return ["up", "down"].includes(normalizeDirection(direction));
package/index.d.ts CHANGED
@@ -137,12 +137,3 @@ declare type AccessibilityState = {
137
137
  reduceMotionEnabled: boolean;
138
138
  boldTextEnabled: boolean;
139
139
  };
140
-
141
- declare type AccessibilityProps = {
142
- accessibilityLabel?: string;
143
- accessibilityHint?: string;
144
- "aria-label"?: string;
145
- "aria-description"?: string;
146
- accessibilityRole?: string;
147
- "aria-role"?: string;
148
- };