@applicaster/zapp-react-native-utils 15.0.0-rc.11 → 15.0.0-rc.111
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.
- package/README.md +0 -6
- package/actionsExecutor/ActionExecutorContext.tsx +3 -6
- package/actionsExecutor/feedDecorator.ts +6 -6
- package/adsUtils/__tests__/createVMAP.test.ts +419 -0
- package/adsUtils/index.ts +2 -2
- package/analyticsUtils/README.md +1 -1
- package/analyticsUtils/analyticsMapper.ts +10 -2
- package/appDataUtils/__tests__/urlScheme.test.ts +678 -0
- package/appUtils/HooksManager/__tests__/__snapshots__/hooksManager.test.js.snap +0 -188
- package/appUtils/HooksManager/__tests__/hooksManager.test.js +16 -2
- package/appUtils/HooksManager/index.ts +10 -10
- package/appUtils/RiverFocusManager/{index.js → index.ts} +25 -18
- package/appUtils/accessibilityManager/__tests__/utils.test.ts +360 -0
- package/appUtils/accessibilityManager/const.ts +4 -0
- package/appUtils/accessibilityManager/hooks.ts +20 -13
- package/appUtils/accessibilityManager/index.ts +28 -1
- package/appUtils/accessibilityManager/utils.ts +59 -8
- package/appUtils/contextKeysManager/__tests__/getKeys/failure.test.ts +7 -2
- package/appUtils/contextKeysManager/__tests__/getKeys/success.test.ts +48 -0
- package/appUtils/contextKeysManager/contextResolver.ts +51 -22
- package/appUtils/contextKeysManager/index.ts +65 -10
- package/appUtils/focusManager/__tests__/__snapshots__/focusManager.test.js.snap +4 -0
- package/appUtils/focusManager/index.ios.ts +59 -3
- package/appUtils/focusManager/treeDataStructure/Tree/__tests__/Tree.test.js +46 -0
- package/appUtils/focusManager/treeDataStructure/Tree/index.js +18 -18
- package/appUtils/focusManagerAux/utils/index.ios.ts +122 -0
- package/appUtils/focusManagerAux/utils/index.ts +21 -5
- package/appUtils/focusManagerAux/utils/utils.ios.ts +234 -0
- package/appUtils/keyCodes/keys/keys.web.ts +1 -4
- package/appUtils/orientationHelper.ts +2 -4
- package/appUtils/platform/platformUtils.ts +117 -18
- package/appUtils/playerManager/OverlayObserver/OverlaysObserver.ts +94 -4
- package/appUtils/playerManager/OverlayObserver/utils.ts +32 -20
- package/appUtils/playerManager/player.ts +4 -0
- package/appUtils/playerManager/playerNative.ts +31 -17
- package/appUtils/playerManager/usePlayerState.tsx +14 -2
- package/arrayUtils/__tests__/allTruthy.test.ts +24 -0
- package/arrayUtils/__tests__/anyThruthy.test.ts +24 -0
- package/arrayUtils/index.ts +5 -0
- package/cellUtils/index.ts +32 -0
- package/cloudEventsUtils/__tests__/index.test.ts +529 -0
- package/cloudEventsUtils/index.ts +65 -1
- package/configurationUtils/__tests__/imageSrcFromMediaItem.test.ts +38 -0
- package/configurationUtils/__tests__/manifestKeyParser.test.ts +26 -26
- package/configurationUtils/index.ts +17 -11
- package/dateUtils/__tests__/dayjs.test.ts +330 -0
- package/enumUtils/__tests__/getEnumKeyByEnumValue.test.ts +207 -0
- package/errorUtils/__tests__/GeneralError.test.ts +97 -0
- package/errorUtils/__tests__/HttpStatusCode.test.ts +344 -0
- package/errorUtils/__tests__/MissingPluginError.test.ts +113 -0
- package/errorUtils/__tests__/NetworkError.test.ts +202 -0
- package/errorUtils/__tests__/getParsedResponse.test.ts +188 -0
- package/errorUtils/__tests__/invariant.test.ts +112 -0
- package/focusManager/aux/index.ts +1 -1
- package/headersUtils/__tests__/headersUtils.test.js +11 -1
- package/headersUtils/index.ts +2 -1
- package/manifestUtils/defaultManifestConfigurations/player.js +109 -11
- package/manifestUtils/keys.js +21 -0
- package/manifestUtils/platformIsTV.js +13 -0
- package/manifestUtils/sharedConfiguration/screenPicker/utils.js +1 -0
- package/manifestUtils/tvAction/container/index.js +1 -1
- package/navigationUtils/index.ts +15 -5
- package/numberUtils/__tests__/toNumber.test.ts +12 -0
- package/numberUtils/__tests__/toPositiveNumber.test.ts +165 -0
- package/numberUtils/index.ts +19 -1
- package/package.json +4 -4
- package/playerUtils/usePlayerTTS.ts +8 -3
- package/pluginUtils/index.ts +4 -0
- package/reactHooks/advertising/index.ts +2 -2
- package/reactHooks/analytics/__tests__/useSendAnalyticsOnPress.test.ts +537 -0
- package/reactHooks/app/__tests__/useAppState.test.ts +1 -1
- package/reactHooks/autoscrolling/__tests__/useTrackCurrentAutoScrollingElement.test.ts +1 -1
- package/reactHooks/autoscrolling/__tests__/useTrackedView.test.tsx +1 -2
- package/reactHooks/cell-click/__tests__/index.test.js +1 -3
- package/reactHooks/configuration/__tests__/index.test.tsx +1 -1
- package/reactHooks/connection/__tests__/index.test.js +1 -1
- package/reactHooks/debugging/__tests__/index.test.js +4 -4
- package/reactHooks/dev/__tests__/useReRenderLog.test.ts +188 -0
- package/reactHooks/device/useIsTablet.tsx +14 -19
- package/reactHooks/device/useMemoizedIsTablet.ts +3 -3
- package/reactHooks/events/index.ts +20 -0
- package/reactHooks/feed/__tests__/useBatchLoading.test.tsx +32 -23
- package/reactHooks/feed/__tests__/useBuildPipesUrl.test.tsx +19 -19
- package/reactHooks/feed/__tests__/useEntryScreenId.test.tsx +4 -1
- package/reactHooks/feed/__tests__/useFeedLoader.test.tsx +42 -30
- package/reactHooks/feed/__tests__/{useInflatedUrl.test.ts → useInflatedUrl.test.tsx} +62 -7
- package/reactHooks/feed/index.ts +0 -2
- package/reactHooks/feed/useBatchLoading.ts +7 -1
- package/reactHooks/feed/useEntryScreenId.ts +2 -2
- package/reactHooks/feed/useInflatedUrl.ts +43 -17
- package/reactHooks/feed/usePipesCacheReset.ts +3 -1
- package/reactHooks/flatList/useLoadNextPageIfNeeded.ts +13 -16
- package/reactHooks/hookModal/hooks/useHookModalScreenData.ts +12 -8
- package/reactHooks/index.ts +2 -0
- package/reactHooks/layout/__tests__/index.test.tsx +1 -1
- package/reactHooks/layout/__tests__/useLayoutVersion.test.tsx +1 -1
- package/reactHooks/layout/index.ts +1 -1
- package/reactHooks/layout/useDimensions/__tests__/{useDimensions.test.ts → useDimensions.test.tsx} +105 -25
- package/reactHooks/layout/useDimensions/useDimensions.ts +2 -2
- package/reactHooks/navigation/__tests__/index.test.tsx +40 -9
- package/reactHooks/navigation/index.ts +27 -11
- package/reactHooks/navigation/useRoute.ts +11 -7
- package/reactHooks/player/TVSeekControlller/TVSeekController.ts +27 -10
- package/reactHooks/player/__tests__/useAutoSeek._test.tsx +1 -1
- package/reactHooks/player/__tests__/useTapSeek._test.ts +1 -1
- package/reactHooks/resolvers/__tests__/useCellResolver.test.tsx +1 -1
- package/reactHooks/resolvers/__tests__/useComponentResolver.test.tsx +1 -1
- package/reactHooks/resolvers/useCellResolver.ts +6 -2
- package/reactHooks/resolvers/useComponentResolver.ts +8 -2
- package/reactHooks/screen/__tests__/useCurrentScreenData.test.tsx +2 -2
- package/reactHooks/screen/__tests__/useScreenBackgroundColor.test.tsx +1 -1
- package/reactHooks/screen/__tests__/useScreenData.test.tsx +1 -1
- package/reactHooks/screen/__tests__/useTargetScreenData.test.tsx +12 -4
- package/reactHooks/screen/useTargetScreenData.ts +4 -2
- package/reactHooks/state/__tests__/useComponentScreenState.test.ts +246 -0
- package/reactHooks/state/index.ts +2 -0
- package/reactHooks/state/useComponentScreenState.ts +45 -0
- package/reactHooks/state/useRefWithInitialValue.ts +10 -0
- package/reactHooks/state/useRivers.ts +1 -1
- package/reactHooks/ui/__tests__/useFadeOutWhenBlurred.test.ts +580 -0
- package/reactHooks/usePluginConfiguration.ts +2 -2
- package/reactHooks/utils/__tests__/index.test.js +1 -1
- package/rectUtils/__tests__/index.test.ts +549 -0
- package/rectUtils/index.ts +2 -2
- package/refreshUtils/RefreshCoordinator/__tests__/refreshCoordinator.test.ts +161 -0
- package/refreshUtils/RefreshCoordinator/index.ts +216 -0
- package/refreshUtils/RefreshCoordinator/utils/__tests__/getDataRefreshConfig.test.ts +104 -0
- package/refreshUtils/RefreshCoordinator/utils/index.ts +29 -0
- package/screenPickerUtils/__tests__/index.test.ts +333 -0
- package/screenPickerUtils/index.ts +5 -0
- package/screenState/__tests__/index.test.ts +1 -1
- package/screenUtils/index.ts +3 -0
- package/searchUtils/const.ts +7 -0
- package/searchUtils/index.ts +3 -0
- package/services/storageServiceSync.web.ts +1 -1
- package/stringUtils/index.ts +1 -1
- package/testUtils/index.tsx +30 -21
- package/time/__tests__/BackgroundTimer.test.ts +156 -0
- package/time/__tests__/Timer.test.ts +236 -0
- package/typeGuards/__tests__/isString.test.ts +21 -0
- package/typeGuards/index.ts +4 -0
- package/utils/__tests__/clone.test.ts +158 -0
- package/utils/__tests__/mapAccum.test.ts +73 -0
- package/utils/__tests__/mergeRight.test.ts +48 -0
- package/utils/__tests__/path.test.ts +7 -0
- package/utils/__tests__/selectors.test.ts +124 -0
- package/utils/clone.ts +7 -0
- package/utils/index.ts +22 -1
- package/utils/mapAccum.ts +23 -0
- package/utils/mergeRight.ts +5 -0
- package/utils/path.ts +6 -3
- package/utils/pathOr.ts +5 -1
- package/utils/selectors.ts +46 -0
- package/zappFrameworkUtils/HookCallback/callbackNavigationAction.ts +49 -12
- package/zappFrameworkUtils/HookCallback/hookCallbackManifestExtensions.config.js +1 -1
- package/reactHooks/componentsMap/index.ts +0 -55
- package/reactHooks/feed/__tests__/useFeedRefresh.test.tsx +0 -75
- package/reactHooks/feed/useFeedRefresh.tsx +0 -65
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { ContextKeysManager } from "./index";
|
|
2
|
-
import
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
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
|
-
|
|
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
|
-
|
|
102
|
-
|
|
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
|
-
|
|
106
|
-
|
|
107
|
-
|
|
133
|
+
extractAtValuesCache.set(input, result);
|
|
134
|
+
|
|
135
|
+
return result;
|
|
136
|
+
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
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
|
-
|
|
161
|
+
if (NativeModules.ContextResolverBridge) {
|
|
162
|
+
const keysObj: Record<string, boolean> = {};
|
|
160
163
|
|
|
161
|
-
|
|
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 (!
|
|
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 (!
|
|
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 (!
|
|
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 (!
|
|
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(
|
|
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(
|
|
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 {
|
|
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
|
-
|
|
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<{
|
|
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
|
})();
|
|
@@ -373,3 +373,49 @@ describe("addNode", () => {
|
|
|
373
373
|
checkParents(tree.root);
|
|
374
374
|
});
|
|
375
375
|
});
|
|
376
|
+
|
|
377
|
+
describe("findInTree", () => {
|
|
378
|
+
function createNode(id, children) {
|
|
379
|
+
return {
|
|
380
|
+
id,
|
|
381
|
+
children,
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
it("returns a direct child match from root children", () => {
|
|
386
|
+
const tree = new Tree(treeLoaded);
|
|
387
|
+
const direct = createNode("direct-node");
|
|
388
|
+
|
|
389
|
+
tree.root.children = [direct];
|
|
390
|
+
|
|
391
|
+
expect(tree.findInTree("direct-node")).toEqual(direct);
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
it("returns a nested descendant match", () => {
|
|
395
|
+
const tree = new Tree(treeLoaded);
|
|
396
|
+
const nested = createNode("nested-node");
|
|
397
|
+
const intermediate = createNode("intermediate-node", [nested]);
|
|
398
|
+
const rootNode = createNode("root-node", [intermediate]);
|
|
399
|
+
|
|
400
|
+
tree.root.children = [rootNode];
|
|
401
|
+
|
|
402
|
+
expect(tree.findInTree("nested-node")).toEqual(nested);
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
it("returns null when node id does not exist", () => {
|
|
406
|
+
const tree = new Tree(treeLoaded);
|
|
407
|
+
const leaf = createNode("leaf-node");
|
|
408
|
+
const rootNode = createNode("root-node", [leaf]);
|
|
409
|
+
|
|
410
|
+
tree.root.children = [rootNode];
|
|
411
|
+
|
|
412
|
+
expect(tree.findInTree("missing-node")).toEqual(null);
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
it("returns null when tree has no children", () => {
|
|
416
|
+
const tree = new Tree(treeLoaded);
|
|
417
|
+
tree.root.children = null;
|
|
418
|
+
|
|
419
|
+
expect(tree.findInTree("any-node")).toEqual(null);
|
|
420
|
+
});
|
|
421
|
+
});
|
|
@@ -205,31 +205,31 @@ export class Tree {
|
|
|
205
205
|
* @returns founded node or null
|
|
206
206
|
*/
|
|
207
207
|
findInTree(id) {
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
return this.findInArray(id, this.root.children, retVal);
|
|
208
|
+
return this.findInArray(id, this.root.children);
|
|
211
209
|
}
|
|
212
210
|
|
|
213
|
-
findInArray(id, children
|
|
214
|
-
if (!
|
|
215
|
-
|
|
211
|
+
findInArray(id, children) {
|
|
212
|
+
if (!children) {
|
|
213
|
+
return null;
|
|
214
|
+
}
|
|
216
215
|
|
|
217
|
-
|
|
218
|
-
children.forEach((child) => {
|
|
219
|
-
if (child.children) {
|
|
220
|
-
retVal = this.findInArray(id, child.children, retVal);
|
|
216
|
+
const directMatch = children.find((obj) => obj.id === id);
|
|
221
217
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
218
|
+
if (directMatch) {
|
|
219
|
+
return directMatch;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
for (const child of children) {
|
|
223
|
+
if (child.children) {
|
|
224
|
+
const nestedMatch = this.findInArray(id, child.children);
|
|
225
|
+
|
|
226
|
+
if (nestedMatch) {
|
|
227
|
+
return nestedMatch;
|
|
228
|
+
}
|
|
229
229
|
}
|
|
230
230
|
}
|
|
231
231
|
|
|
232
|
-
return
|
|
232
|
+
return null;
|
|
233
233
|
}
|
|
234
234
|
|
|
235
235
|
/**
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { isNil, startsWith } from "@applicaster/zapp-react-native-utils/utils";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
QUICK_BRICK_CONTENT,
|
|
5
|
+
QUICK_BRICK_NAVBAR,
|
|
6
|
+
} from "@applicaster/quick-brick-core/const";
|
|
7
|
+
|
|
8
|
+
const isNavBar = (node) => startsWith(QUICK_BRICK_NAVBAR, node?.id);
|
|
9
|
+
const isContent = (node) => startsWith(QUICK_BRICK_CONTENT, node?.id);
|
|
10
|
+
const isRoot = (node) => node?.id === "root";
|
|
11
|
+
|
|
12
|
+
export const isPartOfTabsScreenContent = (
|
|
13
|
+
focusableTree,
|
|
14
|
+
screenPickerContentContainerId,
|
|
15
|
+
id
|
|
16
|
+
) => {
|
|
17
|
+
const node = focusableTree.findInTree(id);
|
|
18
|
+
|
|
19
|
+
if (isNil(node)) {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (isRoot(node)) {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (isNavBar(node)) {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (isContent(node)) {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (node?.id === screenPickerContentContainerId) {
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return isPartOfTabsScreenContent(
|
|
40
|
+
focusableTree,
|
|
41
|
+
screenPickerContentContainerId,
|
|
42
|
+
node.parent?.id
|
|
43
|
+
);
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export const isPartOfMenu = (focusableTree, id): boolean => {
|
|
47
|
+
const node = focusableTree.findInTree(id);
|
|
48
|
+
|
|
49
|
+
if (isNil(node)) {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (isRoot(node)) {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (isNavBar(node)) {
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (isContent(node)) {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return isPartOfMenu(focusableTree, node.parent?.id);
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
export const isPartOfContent = (focusableTree, id) => {
|
|
69
|
+
const node = focusableTree.findInTree(id);
|
|
70
|
+
|
|
71
|
+
if (isNil(node)) {
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (isRoot(node)) {
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (isNavBar(node)) {
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (isContent(node)) {
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return isPartOfContent(focusableTree, node.parent?.id);
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
export const isCurrentFocusOn = (id, node) => {
|
|
91
|
+
if (!node) {
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (isRoot(node)) {
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (node?.id === id) {
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return isCurrentFocusOn(id, node.parent);
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
export const isChildOf = (focusableTree, childId, parentId) => {
|
|
107
|
+
if (isNil(childId) || isNil(parentId)) {
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const childNode = focusableTree.findInTree(childId);
|
|
112
|
+
|
|
113
|
+
if (isNil(childNode)) {
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (childNode.parent?.id === parentId) {
|
|
118
|
+
return true;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return isChildOf(focusableTree, childNode.parent?.id, parentId);
|
|
122
|
+
};
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
|
|
14
14
|
import {
|
|
15
15
|
getFocusableId,
|
|
16
|
-
|
|
16
|
+
isTabsScreenContentContainerId,
|
|
17
17
|
} from "@applicaster/zapp-react-native-utils/screenPickerUtils";
|
|
18
18
|
|
|
19
19
|
// run check each 300 ms
|
|
@@ -24,8 +24,6 @@ const isTopMenu = (node) => startsWith(QUICK_BRICK_NAVBAR, node?.id);
|
|
|
24
24
|
const isContent = (node) => startsWith(QUICK_BRICK_CONTENT, node?.id);
|
|
25
25
|
const isRoot = (node) => node?.id === "root";
|
|
26
26
|
|
|
27
|
-
const isScrenPicker = (node) => startsWith(SCREEN_PICKER_CONTAINER, node?.id);
|
|
28
|
-
|
|
29
27
|
type Props = {
|
|
30
28
|
maxTimeout: number;
|
|
31
29
|
conditionFn: () => boolean;
|
|
@@ -102,7 +100,7 @@ export const getNavbarNode = (focusableTree) => {
|
|
|
102
100
|
|
|
103
101
|
export const waitForContent = (focusableTree) => {
|
|
104
102
|
const contentHasAnyChildren = (): boolean => {
|
|
105
|
-
const countOfChildren = pathOr(
|
|
103
|
+
const countOfChildren = pathOr<number>(
|
|
106
104
|
0,
|
|
107
105
|
["children", "length"],
|
|
108
106
|
getContentNode(focusableTree)
|
|
@@ -136,7 +134,7 @@ export const isTabsScreenOnContentFocused = (node) => {
|
|
|
136
134
|
return false;
|
|
137
135
|
}
|
|
138
136
|
|
|
139
|
-
if (
|
|
137
|
+
if (isTabsScreenContentContainerId(node?.id)) {
|
|
140
138
|
return true;
|
|
141
139
|
}
|
|
142
140
|
|
|
@@ -190,3 +188,21 @@ export const isCurrentFocusOn = (id, node) => {
|
|
|
190
188
|
|
|
191
189
|
return isCurrentFocusOn(id, node.parent);
|
|
192
190
|
};
|
|
191
|
+
|
|
192
|
+
export const isChildOf = (focusableTree, childId, parentId) => {
|
|
193
|
+
if (isNil(childId) || isNil(parentId)) {
|
|
194
|
+
return false;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const childNode = focusableTree.findInTree(childId);
|
|
198
|
+
|
|
199
|
+
if (isNil(childNode)) {
|
|
200
|
+
return false;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (childNode.parent?.id === parentId) {
|
|
204
|
+
return true;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return isChildOf(focusableTree, childNode.parent?.id, parentId);
|
|
208
|
+
};
|