@applicaster/zapp-react-native-utils 15.0.0-rc.9 → 15.0.0-rc.91

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 (107) hide show
  1. package/actionsExecutor/ActionExecutorContext.tsx +3 -6
  2. package/actionsExecutor/feedDecorator.ts +6 -6
  3. package/adsUtils/index.ts +2 -2
  4. package/analyticsUtils/README.md +1 -1
  5. package/analyticsUtils/analyticsMapper.ts +10 -2
  6. package/appUtils/HooksManager/index.ts +10 -10
  7. package/appUtils/RiverFocusManager/{index.js → index.ts} +25 -18
  8. package/appUtils/accessibilityManager/__tests__/utils.test.ts +360 -0
  9. package/appUtils/accessibilityManager/const.ts +4 -0
  10. package/appUtils/accessibilityManager/hooks.ts +20 -13
  11. package/appUtils/accessibilityManager/index.ts +28 -1
  12. package/appUtils/accessibilityManager/utils.ts +59 -8
  13. package/appUtils/contextKeysManager/__tests__/getKeys/failure.test.ts +7 -2
  14. package/appUtils/contextKeysManager/__tests__/getKeys/success.test.ts +48 -0
  15. package/appUtils/contextKeysManager/contextResolver.ts +51 -22
  16. package/appUtils/contextKeysManager/index.ts +65 -10
  17. package/appUtils/focusManager/__tests__/__snapshots__/focusManager.test.js.snap +4 -0
  18. package/appUtils/focusManager/index.ios.ts +59 -3
  19. package/appUtils/focusManagerAux/utils/index.ios.ts +122 -0
  20. package/appUtils/focusManagerAux/utils/index.ts +19 -1
  21. package/appUtils/focusManagerAux/utils/utils.ios.ts +231 -0
  22. package/appUtils/keyCodes/keys/keys.web.ts +1 -4
  23. package/appUtils/orientationHelper.ts +2 -4
  24. package/appUtils/platform/platformUtils.ts +117 -18
  25. package/appUtils/playerManager/OverlayObserver/OverlaysObserver.ts +94 -4
  26. package/appUtils/playerManager/OverlayObserver/utils.ts +32 -20
  27. package/appUtils/playerManager/player.ts +4 -0
  28. package/appUtils/playerManager/playerNative.ts +29 -16
  29. package/appUtils/playerManager/usePlayerState.tsx +14 -2
  30. package/arrayUtils/__tests__/allTruthy.test.ts +24 -0
  31. package/arrayUtils/__tests__/anyThruthy.test.ts +24 -0
  32. package/arrayUtils/index.ts +5 -0
  33. package/cellUtils/index.ts +32 -0
  34. package/configurationUtils/__tests__/imageSrcFromMediaItem.test.ts +38 -0
  35. package/configurationUtils/__tests__/manifestKeyParser.test.ts +26 -26
  36. package/configurationUtils/index.ts +17 -11
  37. package/focusManager/aux/index.ts +1 -1
  38. package/manifestUtils/defaultManifestConfigurations/player.js +125 -11
  39. package/manifestUtils/keys.js +21 -0
  40. package/manifestUtils/sharedConfiguration/screenPicker/utils.js +1 -0
  41. package/manifestUtils/tvAction/container/index.js +1 -1
  42. package/navigationUtils/index.ts +15 -5
  43. package/package.json +4 -4
  44. package/playerUtils/usePlayerTTS.ts +8 -3
  45. package/pluginUtils/index.ts +4 -0
  46. package/reactHooks/advertising/index.ts +2 -2
  47. package/reactHooks/app/__tests__/useAppState.test.ts +1 -1
  48. package/reactHooks/autoscrolling/__tests__/useTrackCurrentAutoScrollingElement.test.ts +1 -1
  49. package/reactHooks/autoscrolling/__tests__/useTrackedView.test.tsx +1 -2
  50. package/reactHooks/cell-click/__tests__/index.test.js +1 -3
  51. package/reactHooks/configuration/__tests__/index.test.tsx +1 -1
  52. package/reactHooks/connection/__tests__/index.test.js +1 -1
  53. package/reactHooks/debugging/__tests__/index.test.js +4 -4
  54. package/reactHooks/device/useMemoizedIsTablet.ts +3 -3
  55. package/reactHooks/feed/__tests__/useBatchLoading.test.tsx +32 -23
  56. package/reactHooks/feed/__tests__/useBuildPipesUrl.test.tsx +19 -19
  57. package/reactHooks/feed/__tests__/useEntryScreenId.test.tsx +4 -1
  58. package/reactHooks/feed/__tests__/useFeedLoader.test.tsx +42 -30
  59. package/reactHooks/feed/__tests__/useFeedRefresh.test.tsx +1 -1
  60. package/reactHooks/feed/__tests__/{useInflatedUrl.test.ts → useInflatedUrl.test.tsx} +62 -7
  61. package/reactHooks/feed/useBatchLoading.ts +7 -1
  62. package/reactHooks/feed/useEntryScreenId.ts +2 -2
  63. package/reactHooks/feed/useInflatedUrl.ts +43 -17
  64. package/reactHooks/feed/usePipesCacheReset.ts +3 -1
  65. package/reactHooks/flatList/useLoadNextPageIfNeeded.ts +13 -16
  66. package/reactHooks/hookModal/hooks/useHookModalScreenData.ts +12 -8
  67. package/reactHooks/layout/__tests__/index.test.tsx +1 -1
  68. package/reactHooks/layout/__tests__/useLayoutVersion.test.tsx +1 -1
  69. package/reactHooks/layout/index.ts +1 -1
  70. package/reactHooks/layout/useDimensions/__tests__/{useDimensions.test.ts → useDimensions.test.tsx} +105 -25
  71. package/reactHooks/layout/useDimensions/useDimensions.ts +2 -2
  72. package/reactHooks/navigation/__tests__/index.test.tsx +40 -9
  73. package/reactHooks/navigation/index.ts +27 -11
  74. package/reactHooks/navigation/useRoute.ts +11 -7
  75. package/reactHooks/player/TVSeekControlller/TVSeekController.ts +27 -10
  76. package/reactHooks/player/__tests__/useAutoSeek._test.tsx +1 -1
  77. package/reactHooks/player/__tests__/useTapSeek._test.ts +1 -1
  78. package/reactHooks/resolvers/__tests__/useCellResolver.test.tsx +1 -1
  79. package/reactHooks/resolvers/__tests__/useComponentResolver.test.tsx +1 -1
  80. package/reactHooks/resolvers/useCellResolver.ts +6 -2
  81. package/reactHooks/resolvers/useComponentResolver.ts +8 -2
  82. package/reactHooks/screen/__tests__/useCurrentScreenData.test.tsx +2 -2
  83. package/reactHooks/screen/__tests__/useScreenBackgroundColor.test.tsx +1 -1
  84. package/reactHooks/screen/__tests__/useScreenData.test.tsx +1 -1
  85. package/reactHooks/screen/__tests__/useTargetScreenData.test.tsx +12 -4
  86. package/reactHooks/screen/useTargetScreenData.ts +4 -2
  87. package/reactHooks/state/useRefWithInitialValue.ts +10 -0
  88. package/reactHooks/state/useRivers.ts +1 -1
  89. package/reactHooks/usePluginConfiguration.ts +2 -2
  90. package/reactHooks/utils/__tests__/index.test.js +1 -1
  91. package/screenState/__tests__/index.test.ts +1 -1
  92. package/searchUtils/const.ts +7 -0
  93. package/searchUtils/index.ts +3 -0
  94. package/services/storageServiceSync.web.ts +1 -1
  95. package/stringUtils/index.ts +1 -1
  96. package/testUtils/index.tsx +30 -21
  97. package/utils/__tests__/mapAccum.test.ts +73 -0
  98. package/utils/__tests__/mergeRight.test.ts +48 -0
  99. package/utils/__tests__/selectors.test.ts +124 -0
  100. package/utils/index.ts +20 -0
  101. package/utils/mapAccum.ts +23 -0
  102. package/utils/mergeRight.ts +5 -0
  103. package/utils/path.ts +6 -3
  104. package/utils/pathOr.ts +5 -1
  105. package/utils/selectors.ts +46 -0
  106. package/zappFrameworkUtils/HookCallback/callbackNavigationAction.ts +49 -12
  107. package/zappFrameworkUtils/HookCallback/hookCallbackManifestExtensions.config.js +1 -1
@@ -36,7 +36,20 @@ export class AccessibilityManager {
36
36
  false
37
37
  );
38
38
 
39
- private constructor() {}
39
+ private constructor() {
40
+ this.ttsManager
41
+ .getScreenReaderEnabledAsObservable()
42
+ .subscribe((enabled) => {
43
+ const state = this.state$.getValue();
44
+
45
+ if (state.screenReaderEnabled !== enabled) {
46
+ this.state$.next({
47
+ ...state,
48
+ screenReaderEnabled: enabled,
49
+ });
50
+ }
51
+ });
52
+ }
40
53
 
41
54
  public static getInstance(): AccessibilityManager {
42
55
  if (!AccessibilityManager._instance) {
@@ -92,8 +105,15 @@ export class AccessibilityManager {
92
105
  /**
93
106
  * Adds a heading to the queue, headings will be read before the next text
94
107
  * Each heading will be read once and removed from the queue
108
+ * Does nothing if screen reader is not enabled
95
109
  */
96
110
  public addHeading(heading: string) {
111
+ const state = this.state$.getValue();
112
+
113
+ if (!state.screenReaderEnabled) {
114
+ return;
115
+ }
116
+
97
117
  if (!this.pendingFocusId) {
98
118
  this.pendingFocusId = Date.now().toString();
99
119
  }
@@ -108,6 +128,7 @@ export class AccessibilityManager {
108
128
  *
109
129
  * Implements a delay mechanism to reduce noise during rapid navigation.
110
130
  * Only the most recent announcement will be read after the delay period.
131
+ * Does nothing if screen reader is not enabled
111
132
  */
112
133
  public readText({
113
134
  text,
@@ -116,6 +137,12 @@ export class AccessibilityManager {
116
137
  text: string;
117
138
  keyOfLocalizedText?: string;
118
139
  }) {
140
+ const state = this.state$.getValue();
141
+
142
+ if (!state.screenReaderEnabled) {
143
+ return;
144
+ }
145
+
119
146
  let textToRead = text;
120
147
 
121
148
  if (keyOfLocalizedText) {
@@ -1,24 +1,75 @@
1
+ import { createLogger } from "../../logger";
2
+
3
+ const { log_error } = createLogger({
4
+ category: "AccessibilityManager",
5
+ subsystem: "AppUtils",
6
+ });
7
+
1
8
  /**
2
9
  * Calculates the reading time for a given text based on word count
3
- * @param text - The text to calculate the reading time for
4
- * @param wordsPerMinute - Words per minute reading speed (default: 160)
10
+ * @param text - The text to calculate the reading time for (string or number)
11
+ * @param wordsPerMinute - Words per minute reading speed (default: 140)
5
12
  * @param minimumPause - Minimum pause time in milliseconds (default: 500)
6
13
  * @param announcementDelay - Additional delay for announcement in milliseconds (default: 700)
7
14
  * @returns The reading time in milliseconds
8
15
  */
9
16
  export function calculateReadingTime(
10
- text: string,
17
+ text: string | number,
11
18
  wordsPerMinute: number = 140,
12
19
  minimumPause: number = 500,
13
20
  announcementDelay: number = 700
14
21
  ): number {
15
- const words = text
16
- .trim()
22
+ if (typeof text !== "string" && typeof text !== "number") {
23
+ log_error(
24
+ `Invalid text input for reading time calculation got: ${
25
+ typeof text === "symbol" ? String(text) : text
26
+ }`
27
+ );
28
+
29
+ return 0;
30
+ }
31
+
32
+ const trimmed = typeof text === "number" ? String(text) : text.trim();
33
+
34
+ if (!trimmed) {
35
+ return 0;
36
+ }
37
+
38
+ const words = trimmed
17
39
  .split(/(?<=\d)(?=[a-zA-Z])|(?<=[a-zA-Z])(?=\d)|[^\w\s]+|\s+/)
18
40
  .filter(Boolean).length;
19
41
 
20
- return (
21
- Math.max(minimumPause, (words / wordsPerMinute) * 60 * 1000) +
22
- announcementDelay
42
+ // Count spaces - multiple consecutive spaces add extra pause time
43
+ const spaceMatches: string[] = trimmed.match(/\s+/g) || [];
44
+
45
+ const totalSpaces = spaceMatches.reduce(
46
+ (sum: number, match: string) => sum + match.length,
47
+ 0
23
48
  );
49
+
50
+ const extraSpaces = Math.max(0, totalSpaces - (words - 1)); // words-1 is normal spacing
51
+
52
+ // Heuristic: punctuation increases TTS duration beyond word-based WPM.
53
+ // Commas typically introduce short pauses, sentence terminators longer ones.
54
+ const commaCount = (trimmed.match(/,/g) || []).length;
55
+ const semicolonCount = (trimmed.match(/;/g) || []).length;
56
+ const colonCount = (trimmed.match(/:/g) || []).length;
57
+ const dashCount = (trimmed.match(/\u2013|\u2014|-/g) || []).length; // – — -
58
+ const sentenceEndCount = (trimmed.match(/[.!?](?!\d)/g) || []).length;
59
+
60
+ const commaPauseMs = 220; // short prosody pause for ","
61
+ const midPauseMs = 260; // for ";", ":", dashes
62
+ const sentenceEndPauseMs = 420; // for ".", "!", "?"
63
+ const extraSpacePauseMs = 50; // per extra space beyond normal spacing
64
+
65
+ const punctuationPause =
66
+ commaCount * commaPauseMs +
67
+ (semicolonCount + colonCount + dashCount) * midPauseMs +
68
+ sentenceEndCount * sentenceEndPauseMs +
69
+ extraSpaces * extraSpacePauseMs;
70
+
71
+ const baseByWordsMs = (words / wordsPerMinute) * 60 * 1000;
72
+ const estimatedMs = Math.max(minimumPause, baseByWordsMs + punctuationPause);
73
+
74
+ return estimatedMs + announcementDelay;
24
75
  }
@@ -1,7 +1,12 @@
1
+ import { NativeModules } from "react-native";
1
2
  import { ContextKeysManager } from "../..";
2
3
 
3
4
  describe("Context Keys Manager - getKeys", () => {
4
- it("returns null if all keys are invalid", async () => {
5
+ beforeEach(() => {
6
+ delete NativeModules.ContextResolverBridge;
7
+ });
8
+
9
+ it("returns null if all keys are invalid 1", async () => {
5
10
  // setup
6
11
  const keys = [null];
7
12
 
@@ -24,7 +29,7 @@ describe("Context Keys Manager - getKeys", () => {
24
29
  expect(getKey.mock.calls).toEqual([[null]]);
25
30
  });
26
31
 
27
- it("returns null if all keys are invalid", async () => {
32
+ it("returns null if all keys are invalid 2", async () => {
28
33
  // setup
29
34
  const keys = [null, undefined];
30
35
 
@@ -1,6 +1,11 @@
1
+ import { NativeModules } from "react-native";
1
2
  import { ContextKeysManager } from "../..";
2
3
 
3
4
  describe("Context Keys Manager - getKeys", () => {
5
+ beforeEach(() => {
6
+ delete NativeModules.ContextResolverBridge;
7
+ });
8
+
4
9
  it("returns value if found by getKey", async () => {
5
10
  // setup
6
11
  const keys = ["namespace.key"];
@@ -165,4 +170,47 @@ describe("Context Keys Manager - getKeys", () => {
165
170
  expect(getKey).toHaveBeenCalledTimes(2);
166
171
  expect(getKey.mock.calls).toEqual([[keys[0]], [keys[1]]]);
167
172
  });
173
+
174
+ it("returns values from native bridge when available", async () => {
175
+ // setup
176
+ const keys = ["namespace.key1", "namespace.key2"];
177
+
178
+ const contextObj = [
179
+ { key: keys[0], required: true },
180
+ { key: keys[1], required: false },
181
+ ];
182
+
183
+ const resolved = {
184
+ [keys[0]]: "value1",
185
+ [keys[1]]: "value2",
186
+ };
187
+
188
+ const reactNative = require("react-native");
189
+
190
+ const bridgeMock = {
191
+ resolveContextKeys: jest.fn().mockResolvedValue(resolved),
192
+ };
193
+
194
+ reactNative.__setContextResolverBridgeMock(bridgeMock);
195
+
196
+ const contextManager = new ContextKeysManager({});
197
+ const getKey = jest.spyOn(contextManager, "getKey");
198
+
199
+ // run
200
+ const result = await contextManager.getKeys(keys, contextObj);
201
+
202
+ // verify
203
+ const map = new Map();
204
+ map.set(keys[0], "value1");
205
+ map.set(keys[1], "value2");
206
+
207
+ expect(result).toEqual(map);
208
+
209
+ expect(bridgeMock.resolveContextKeys).toHaveBeenCalledWith({
210
+ [keys[0]]: true,
211
+ [keys[1]]: false,
212
+ });
213
+
214
+ expect(getKey).not.toHaveBeenCalled();
215
+ });
168
216
  });
@@ -1,8 +1,10 @@
1
1
  import { ContextKeysManager } from "./index";
2
- import * as R from "ramda";
3
- import * as _ from "lodash";
2
+ import { get, values } from "@applicaster/zapp-react-native-utils/utils";
4
3
  import { useScreenStateStore } from "../../reactHooks/navigation/useScreenStateStore";
5
4
 
5
+ const contextKeyPattern = /@{([^/]+)\/([^}]*)}/;
6
+ const contextVariablePattern = /@\{([^}]*)\}/g;
7
+
6
8
  export interface IResolver {
7
9
  resolve: (string) => Promise<string | number | object>;
8
10
  }
@@ -20,7 +22,7 @@ export class EntryResolver implements IResolver {
20
22
  return this.entry;
21
23
  }
22
24
 
23
- return R.view(R.lensPath(key.split(".")), this.entry);
25
+ return get(this.entry, key.split("."));
24
26
  }
25
27
  }
26
28
 
@@ -39,7 +41,7 @@ export class ScreenStateResolver implements IResolver {
39
41
  }
40
42
 
41
43
  if (key.includes(".")) {
42
- return R.view(R.lensPath(key.split(".")), screenState);
44
+ return get(screenState, key.split("."));
43
45
  }
44
46
 
45
47
  return screenState?.[key];
@@ -61,21 +63,16 @@ export const resolveObjectValues = async (
61
63
 
62
64
  const resolvedEntries = await Promise.all(
63
65
  entries.map(async ([key, value]) => {
64
- if (typeof value !== "string") {
66
+ if (typeof value !== "string" || !value.startsWith("@")) {
65
67
  return [key, value];
66
68
  }
67
69
 
68
- if (!value.startsWith("@")) {
69
- return [key, value];
70
- }
71
-
72
- const regex = /@{([^/]+)\/([^}]*)}/;
70
+ const regex = contextKeyPattern;
73
71
 
74
72
  const match = (value as string).match(regex);
75
73
 
76
74
  if (match) {
77
- const contextResolverName = match[1];
78
- const compositeKey = match[2];
75
+ const [_, contextResolverName, compositeKey] = match;
79
76
 
80
77
  const resolver = exResolvers[contextResolverName] || exResolvers.ctx;
81
78
  const resolvedValue = await resolver.resolve(compositeKey);
@@ -90,18 +87,50 @@ export const resolveObjectValues = async (
90
87
  return Object.fromEntries(resolvedEntries);
91
88
  };
92
89
 
93
- export const extractAtValues = _.memoize((input: any): string[] => {
94
- return _.flatMapDeep(input, (value: any) => {
95
- if (_.isString(value)) {
96
- const matches = value.match(/@\{([^}]*)\}/g);
90
+ // Simple memoization cache
91
+ const extractAtValuesCache = new Map<any, string[]>();
92
+
93
+ const flatMapDeep = <T, U>(arr: T[], fn: (value: T) => U | U[]): U[] => {
94
+ const result: U[] = [];
97
95
 
98
- return matches ? matches.map((match) => match.slice(2, -1)) : [];
96
+ const flatten = (items: any) => {
97
+ for (const item of items) {
98
+ if (Array.isArray(item)) {
99
+ flatten(item);
100
+ } else {
101
+ result.push(item);
102
+ }
99
103
  }
104
+ };
105
+
106
+ flatten(arr.map(fn));
100
107
 
101
- if (_.isObject(value)) {
102
- return extractAtValues(_.values(value));
108
+ return result;
109
+ };
110
+
111
+ export const extractAtValues = (input: any): string[] => {
112
+ if (extractAtValuesCache.has(input)) {
113
+ return extractAtValuesCache.get(input)!;
114
+ }
115
+
116
+ const result = flatMapDeep(
117
+ Array.isArray(input) ? input : [input],
118
+ (value: any) => {
119
+ if (typeof value === "string") {
120
+ const matches = value.match(contextVariablePattern);
121
+
122
+ return matches ? matches.map((match) => match.slice(2, -1)) : [];
123
+ }
124
+
125
+ if (typeof value === "object" && value !== null) {
126
+ return extractAtValues(values(value));
127
+ }
128
+
129
+ return [];
103
130
  }
131
+ );
104
132
 
105
- return [];
106
- });
107
- });
133
+ extractAtValuesCache.set(input, result);
134
+
135
+ return result;
136
+ };
@@ -1,4 +1,4 @@
1
- import * as R from "ramda";
1
+ import { isNil } from "@applicaster/zapp-react-native-utils/utils";
2
2
 
3
3
  import { StorageLevel, StorageType } from "./consts";
4
4
  import {
@@ -13,6 +13,7 @@ import {
13
13
  localStorage as LocalStorage,
14
14
  secureStorage as SecureStorage,
15
15
  } from "./storage";
16
+ import { NativeModules } from "react-native";
16
17
 
17
18
  export { StorageLevel };
18
19
 
@@ -154,11 +155,65 @@ export class ContextKeysManager {
154
155
  }
155
156
 
156
157
  public async getKeys(
157
- keys: Array<KeyName>
158
+ keys: Array<KeyName>,
159
+ contextObj?: { key: string; required?: boolean }[]
158
160
  ): Promise<Map<KeyName, ValueOrNothing>> {
159
- const values = await Promise.all(keys.map((key) => this.getKey(key)));
161
+ if (NativeModules.ContextResolverBridge) {
162
+ const keysObj: Record<string, boolean> = {};
160
163
 
161
- return new Map(R.zip(keys, values));
164
+ const requiredByKey = new Map(
165
+ contextObj?.map((item) => [item.key, item.required ?? false]) ?? []
166
+ );
167
+
168
+ let hasResolvableKeys = false;
169
+
170
+ const normalizedKeys: Array<{
171
+ original: KeyName;
172
+ normalized: string | null;
173
+ }> = [];
174
+
175
+ for (const key of keys) {
176
+ const {
177
+ isValid: isValidKey,
178
+ key: parsedKey,
179
+ namespace: parsedNamespace,
180
+ } = this.parseKey(key);
181
+
182
+ if (!isValidKey) {
183
+ normalizedKeys.push({ original: key, normalized: null });
184
+ continue;
185
+ }
186
+
187
+ const normalized = buildNamespaceKey(parsedKey, parsedNamespace);
188
+
189
+ keysObj[normalized] = requiredByKey.get(normalized) ?? false;
190
+ hasResolvableKeys = true;
191
+ normalizedKeys.push({ original: key, normalized });
192
+ }
193
+
194
+ if (!hasResolvableKeys) {
195
+ return new Map(
196
+ normalizedKeys.map(({ original }) => [original, NOTHING])
197
+ );
198
+ }
199
+
200
+ const res =
201
+ await NativeModules.ContextResolverBridge.resolveContextKeys(keysObj);
202
+
203
+ const resolved = res ?? {};
204
+
205
+ return new Map(
206
+ normalizedKeys.map(({ original, normalized }) => {
207
+ if (!normalized) return [original, NOTHING];
208
+
209
+ return [original, resolved[normalized] ?? NOTHING];
210
+ })
211
+ );
212
+ } else {
213
+ const values = await Promise.all(keys.map((key) => this.getKey(key)));
214
+
215
+ return new Map(keys.map((key, index) => [key, values[index]]));
216
+ }
162
217
  }
163
218
 
164
219
  public async getKey(key: KeyName): Promise<ValueOrNothing> {
@@ -181,7 +236,7 @@ export class ContextKeysManager {
181
236
  parsedNamespace
182
237
  );
183
238
 
184
- if (!R.isNil(resultByReference)) {
239
+ if (!isNil(resultByReference)) {
185
240
  return resultByReference;
186
241
  }
187
242
 
@@ -192,7 +247,7 @@ export class ContextKeysManager {
192
247
  parsedNamespace
193
248
  );
194
249
 
195
- if (!R.isNil(resultFromSessionStorage)) {
250
+ if (!isNil(resultFromSessionStorage)) {
196
251
  return resultFromSessionStorage;
197
252
  }
198
253
 
@@ -201,7 +256,7 @@ export class ContextKeysManager {
201
256
  parsedNamespace
202
257
  );
203
258
 
204
- if (!R.isNil(resultFromLocalStorage)) {
259
+ if (!isNil(resultFromLocalStorage)) {
205
260
  return resultFromLocalStorage;
206
261
  }
207
262
 
@@ -210,7 +265,7 @@ export class ContextKeysManager {
210
265
  parsedNamespace
211
266
  );
212
267
 
213
- if (!R.isNil(resultFromSecureStorage)) {
268
+ if (!isNil(resultFromSecureStorage)) {
214
269
  return resultFromSecureStorage;
215
270
  }
216
271
 
@@ -275,7 +330,7 @@ export class ContextKeysManager {
275
330
  keysProp.map((keyProp) => this.setKey(keyProp))
276
331
  );
277
332
 
278
- return new Map(R.zip(keys, values));
333
+ return new Map(keys.map((key, index) => [key, values[index]]));
279
334
  }
280
335
 
281
336
  // when succeed saving - return true
@@ -384,7 +439,7 @@ export class ContextKeysManager {
384
439
  public async removeKeys(keys: KeyName[]): Promise<Map<KeyName, boolean>> {
385
440
  const values = await Promise.all(keys.map((key) => this.removeKey(key)));
386
441
 
387
- return new Map(R.zip(keys, values));
442
+ return new Map(keys.map((key, index) => [key, values[index]]));
388
443
  }
389
444
  // REMOVE
390
445
  }
@@ -69,7 +69,11 @@ exports[`focusManagerIOS should be defined 1`] = `
69
69
  "getGroupRootById": [Function],
70
70
  "getPreferredFocusChild": [Function],
71
71
  "invokeHandler": [Function],
72
+ "isChildOf": [Function],
72
73
  "isFocusOn": [Function],
74
+ "isFocusOnContent": [Function],
75
+ "isFocusOnMenu": [Function],
76
+ "isFocusOnTabsScreenContent": [Function],
73
77
  "isGroupItemFocused": [Function],
74
78
  "moveFocus": [Function],
75
79
  "on": [Function],
@@ -1,12 +1,24 @@
1
1
  import { NativeModules } from "react-native";
2
2
  import * as R from "ramda";
3
3
 
4
- import { isCurrentFocusOn } from "../focusManagerAux/utils";
4
+ import {
5
+ isCurrentFocusOn,
6
+ isChildOf as isChildOfUtils,
7
+ isPartOfMenu,
8
+ isPartOfContent,
9
+ isPartOfTabsScreenContent,
10
+ } from "../focusManagerAux/utils/index.ios";
11
+
5
12
  import { Tree } from "./treeDataStructure/Tree";
6
13
  import { findFocusableNode } from "./treeDataStructure/Utils";
7
14
  import { subscriber } from "../../functionUtils";
8
15
  import { findChild } from "./utils";
9
16
 
17
+ import {
18
+ emitRegistered,
19
+ emitUnregistered,
20
+ } from "../focusManagerAux/utils/utils.ios";
21
+
10
22
  const { FocusableManagerModule } = NativeModules;
11
23
 
12
24
  /**
@@ -180,10 +192,20 @@ export const focusManager = (function () {
180
192
  function register({ id, component }) {
181
193
  const { isGroup = false } = component;
182
194
 
183
- return isGroup ? registerGroup(id, component) : registerItem(id, component);
195
+ if (isGroup) {
196
+ registerGroup(id, component);
197
+ } else {
198
+ registerItem(id, component);
199
+ }
200
+
201
+ const groupId = component?.props?.groupId;
202
+
203
+ emitRegistered({ id, groupId, isGroup });
184
204
  }
185
205
 
186
206
  function unregister(id, { group = false } = {}) {
207
+ emitUnregistered(id);
208
+
187
209
  group ? unregisterGroup(id) : unregisterItem(id);
188
210
  }
189
211
 
@@ -261,7 +283,9 @@ export const focusManager = (function () {
261
283
  function setFocus(
262
284
  id: string,
263
285
  direction?: FocusManager.IOS.Direction,
264
- options?: Partial<{ groupFocusedChanged: boolean }>,
286
+ options?: Partial<{
287
+ groupFocusedChanged: boolean;
288
+ }>,
265
289
  callback?: any
266
290
  ) {
267
291
  blur(direction);
@@ -400,6 +424,34 @@ export const focusManager = (function () {
400
424
  return id && isCurrentFocusOn(id, currentFocusNode);
401
425
  }
402
426
 
427
+ function isFocusOnMenu(): boolean {
428
+ const currentFocusable = getCurrentFocus();
429
+
430
+ return isPartOfMenu(focusableTree, currentFocusable?.props?.id);
431
+ }
432
+
433
+ function isFocusOnContent(): boolean {
434
+ const currentFocusable = getCurrentFocus();
435
+
436
+ return isPartOfContent(focusableTree, currentFocusable?.props?.id);
437
+ }
438
+
439
+ function isFocusOnTabsScreenContent(
440
+ screenPickerContentContainerId: string
441
+ ): boolean {
442
+ const currentFocusable = getCurrentFocus();
443
+
444
+ return isPartOfTabsScreenContent(
445
+ focusableTree,
446
+ screenPickerContentContainerId,
447
+ currentFocusable?.props?.id
448
+ );
449
+ }
450
+
451
+ function isChildOf(childId, parentId): boolean {
452
+ return isChildOfUtils(focusableTree, childId, parentId);
453
+ }
454
+
403
455
  return {
404
456
  on,
405
457
  invokeHandler,
@@ -422,5 +474,9 @@ export const focusManager = (function () {
422
474
  isGroupItemFocused,
423
475
  getPreferredFocusChild,
424
476
  isFocusOn,
477
+ isFocusOnMenu,
478
+ isFocusOnContent,
479
+ isFocusOnTabsScreenContent,
480
+ isChildOf,
425
481
  };
426
482
  })();