@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.
- package/actionsExecutor/ActionExecutorContext.tsx +60 -84
- package/actionsExecutor/ScreenActions.ts +164 -0
- package/actionsExecutor/StorageActions.ts +110 -0
- package/actionsExecutor/feedDecorator.ts +171 -0
- package/actionsExecutor/screenResolver.ts +11 -0
- package/analyticsUtils/AnalyticsEvents/helper.ts +81 -0
- package/analyticsUtils/AnalyticsEvents/sendHeaderClickEvent.ts +1 -1
- package/analyticsUtils/AnalyticsEvents/sendMenuClickEvent.ts +2 -1
- package/analyticsUtils/AnalyticsEvents/sendOnClickEvent.ts +14 -4
- package/analyticsUtils/__tests__/analyticsUtils.test.js +3 -0
- package/analyticsUtils/events.ts +8 -0
- package/analyticsUtils/index.tsx +3 -4
- package/analyticsUtils/manager.ts +1 -1
- package/analyticsUtils/playerAnalyticsTracker.ts +2 -1
- package/appUtils/HooksManager/Hook.ts +4 -4
- package/appUtils/HooksManager/index.ts +11 -1
- package/appUtils/accessibilityManager/index.ts +5 -5
- package/appUtils/contextKeysManager/contextResolver.ts +42 -1
- package/appUtils/focusManager/__tests__/__snapshots__/focusManager.test.js.snap +5 -0
- package/appUtils/focusManager/__tests__/focusManager.test.js +1 -1
- package/appUtils/focusManager/events.ts +2 -0
- package/appUtils/focusManager/index.ios.ts +10 -0
- package/appUtils/focusManager/index.ts +82 -11
- package/appUtils/focusManager/treeDataStructure/Tree/index.js +1 -1
- package/appUtils/focusManagerAux/utils/index.ts +106 -3
- package/appUtils/playerManager/OverlayObserver/OverlaysObserver.ts +0 -15
- package/appUtils/playerManager/useChapterMarker.tsx +0 -1
- package/appUtils/playerManager/usePlayerControllerSetup.tsx +16 -0
- package/arrayUtils/__tests__/isEmptyArray.test.ts +63 -0
- package/arrayUtils/__tests__/isFilledArray.test.ts +1 -1
- package/arrayUtils/index.ts +8 -3
- package/audioPlayerUtils/__tests__/getArtworkImage.test.ts +144 -0
- package/audioPlayerUtils/__tests__/getBackgroundImage.test.ts +72 -0
- package/audioPlayerUtils/__tests__/getImageFromEntry.test.ts +110 -0
- package/audioPlayerUtils/assets/index.ts +2 -0
- package/audioPlayerUtils/index.ts +242 -0
- package/componentsUtils/__tests__/isTabsScreen.test.ts +38 -0
- package/componentsUtils/index.ts +4 -1
- package/conf/player/__tests__/selectors.test.ts +34 -0
- package/conf/player/selectors.ts +10 -0
- package/configurationUtils/__tests__/configurationUtils.test.js +0 -31
- package/configurationUtils/__tests__/getMediaItems.test.ts +65 -0
- package/configurationUtils/__tests__/imageSrcFromMediaItem.test.ts +34 -0
- package/configurationUtils/__tests__/manifestKeyParser.test.ts +546 -0
- package/configurationUtils/index.ts +63 -34
- package/configurationUtils/manifestKeyParser.ts +57 -32
- package/focusManager/FocusManager.ts +89 -20
- package/focusManager/Tree.ts +25 -21
- package/focusManager/__tests__/FocusManager.test.ts +50 -8
- package/focusManager/aux/index.ts +167 -0
- package/focusManager/utils.ts +12 -6
- package/index.d.ts +0 -9
- package/manifestUtils/_internals/getDefaultConfiguration.js +28 -0
- package/manifestUtils/{_internals.js → _internals/index.js} +2 -25
- package/manifestUtils/createConfig.js +4 -1
- package/manifestUtils/defaultManifestConfigurations/player.js +1239 -200
- package/manifestUtils/progressBar/__tests__/mobileProgressBar.test.js +0 -30
- package/navigationUtils/__tests__/mapContentTypesToRivers.test.ts +130 -0
- package/navigationUtils/index.ts +7 -5
- package/package.json +2 -3
- package/playerUtils/__tests__/configurationUtils.test.ts +1 -65
- package/playerUtils/__tests__/getPlayerActionButtons.test.ts +54 -0
- package/playerUtils/_internals/__tests__/utils.test.ts +71 -0
- package/playerUtils/_internals/index.ts +1 -0
- package/playerUtils/_internals/utils.ts +31 -0
- package/playerUtils/configurationUtils.ts +0 -44
- package/playerUtils/getPlayerActionButtons.ts +17 -0
- package/playerUtils/index.ts +2 -0
- package/playerUtils/useValidatePlayerConfig.tsx +22 -19
- package/reactHooks/autoscrolling/__tests__/useTrackedView.test.tsx +15 -14
- package/reactHooks/cell-click/__tests__/index.test.js +3 -0
- package/reactHooks/cell-click/index.ts +8 -1
- package/reactHooks/debugging/__tests__/index.test.js +0 -1
- package/reactHooks/feed/__tests__/useBatchLoading.test.tsx +47 -90
- package/reactHooks/feed/__tests__/useFeedLoader.test.tsx +71 -31
- package/reactHooks/feed/index.ts +2 -0
- package/reactHooks/feed/useBatchLoading.ts +17 -10
- package/reactHooks/feed/useFeedLoader.tsx +36 -34
- package/reactHooks/feed/useLoadPipesDataDispatch.ts +63 -0
- package/reactHooks/feed/usePipesCacheReset.ts +3 -3
- package/reactHooks/flatList/useSequentialRenderItem.tsx +3 -3
- package/reactHooks/layout/__tests__/index.test.tsx +3 -1
- package/reactHooks/layout/isTablet/index.ts +12 -5
- package/reactHooks/layout/useDimensions/__tests__/useDimensions.test.ts +34 -36
- package/reactHooks/layout/useDimensions/useDimensions.ts +2 -3
- package/reactHooks/layout/useLayoutVersion.ts +5 -5
- package/reactHooks/navigation/index.ts +7 -5
- package/reactHooks/navigation/useIsScreenActive.ts +9 -5
- package/reactHooks/navigation/useRoute.ts +7 -2
- package/reactHooks/navigation/useScreenStateStore.ts +8 -0
- package/reactHooks/resolvers/__tests__/useCellResolver.test.tsx +4 -0
- package/reactHooks/screen/useScreenContext.ts +1 -1
- package/reactHooks/state/__tests__/ZStoreProvider.test.tsx +2 -1
- package/reactHooks/state/index.ts +1 -1
- package/reactHooks/state/useHomeRiver.ts +4 -2
- package/reactHooks/state/useRivers.ts +7 -8
- package/riverComponetsMeasurementProvider/index.tsx +1 -1
- package/screenPickerUtils/index.ts +13 -0
- package/services/js2native.ts +1 -0
- package/storage/ScreenSingleValueProvider.ts +204 -0
- package/storage/ScreenStateMultiSelectProvider.ts +293 -0
- package/storage/StorageMultiSelectProvider.ts +192 -0
- package/storage/StorageSingleSelectProvider.ts +108 -0
- package/testUtils/index.tsx +7 -8
- package/time/BackgroundTimer.ts +6 -4
- package/utils/__tests__/endsWith.test.ts +30 -0
- package/utils/__tests__/equals.test.ts +65 -0
- package/utils/__tests__/find.test.ts +36 -0
- package/utils/__tests__/max.test.ts +36 -0
- package/utils/__tests__/omit.test.ts +19 -0
- package/utils/__tests__/path.test.ts +33 -0
- package/utils/__tests__/pathOr.test.ts +37 -0
- package/utils/__tests__/startsWith.test.ts +30 -0
- package/utils/__tests__/take.test.ts +40 -0
- package/utils/__tests__/toLower.test.ts +39 -0
- package/utils/endsWith.ts +9 -0
- package/utils/equals.ts +5 -0
- package/utils/find.ts +3 -0
- package/utils/index.ts +37 -1
- package/utils/max.ts +5 -0
- package/utils/omit.ts +5 -0
- package/utils/path.ts +5 -0
- package/utils/pathOr.ts +5 -0
- package/utils/startsWith.ts +9 -0
- package/utils/take.ts +5 -0
- package/utils/toLower.ts +5 -0
- package/playerUtils/configurationGenerator.ts +0 -2572
|
@@ -11,8 +11,11 @@ const currentPlatform = platformSelect({
|
|
|
11
11
|
android_tv: "android",
|
|
12
12
|
});
|
|
13
13
|
|
|
14
|
-
|
|
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
|
-
|
|
38
|
-
}
|
|
39
|
+
if (!outStyles) throw new Error("outStyles is required");
|
|
40
|
+
if (!componentName) throw new Error("componentName is required");
|
|
39
41
|
|
|
40
|
-
|
|
41
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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((
|
|
57
|
-
styleName.startsWith(
|
|
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;
|
|
70
|
+
return;
|
|
63
71
|
}
|
|
64
72
|
|
|
73
|
+
tmpDict = tmpPlatformDict;
|
|
65
74
|
styleName = styleName.slice(platform.length + 1);
|
|
66
75
|
}
|
|
67
76
|
|
|
68
|
-
let state =
|
|
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
|
-
|
|
87
|
+
if (!styleName) return;
|
|
77
88
|
|
|
78
|
-
|
|
89
|
+
const camelCaseKey = toCamelCase(styleName);
|
|
79
90
|
|
|
80
|
-
if (!
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
}
|
|
91
|
+
if (!tmpDict[state]) tmpDict[state] = {};
|
|
92
|
+
tmpDict[state][camelCaseKey] = value;
|
|
93
|
+
};
|
|
84
94
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
95
|
+
for (const [key, value] of Object.entries(configuration)) {
|
|
96
|
+
parseKey(key, value);
|
|
97
|
+
}
|
|
88
98
|
|
|
89
|
-
|
|
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
|
-
|
|
94
|
-
|
|
95
|
-
if (key === defaultKey) {
|
|
96
|
-
return;
|
|
97
|
-
}
|
|
118
|
+
for (const state of Object.keys(outStyles)) {
|
|
119
|
+
if (state === defaultKey) continue;
|
|
98
120
|
|
|
99
|
-
|
|
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
|
-
|
|
181
|
-
|
|
182
|
-
isFocusableCell
|
|
183
|
-
|
|
184
|
-
|
|
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 &&
|
|
188
|
-
this.focusableComponents.push(
|
|
204
|
+
if (!focusableComponent && touchableRef) {
|
|
205
|
+
this.focusableComponents.push(touchableRef);
|
|
189
206
|
|
|
190
|
-
this.tree.add(
|
|
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
|
-
|
|
247
|
-
FocusManager.instance.prevFocused
|
|
248
|
-
|
|
249
|
-
|
|
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
|
|
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();
|
package/focusManager/Tree.ts
CHANGED
|
@@ -8,37 +8,41 @@ export class Tree {
|
|
|
8
8
|
this.tree = focusManagerTree;
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
add(
|
|
12
|
-
|
|
13
|
-
|
|
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(
|
|
23
|
+
focusableComponentInTree.updateNode(touchableRef);
|
|
19
24
|
}
|
|
20
25
|
|
|
21
|
-
if (
|
|
22
|
-
|
|
23
|
-
|
|
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
|
-
|
|
31
|
+
const parentNode = this.find(parentId);
|
|
27
32
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
33
|
+
if (parentNode) {
|
|
34
|
+
if (focusableComponentInTree) {
|
|
35
|
+
focusableComponentInTree.isFocusableCell = isFocusableCell;
|
|
36
|
+
focusableComponentInTree.parentId = parentNode.id;
|
|
32
37
|
|
|
33
|
-
|
|
38
|
+
parentNode.addChild(focusableComponentInTree);
|
|
34
39
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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(
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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(
|
|
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
|
+
};
|
package/focusManager/utils.ts
CHANGED
|
@@ -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 = (
|
|
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 = (
|
|
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 = (
|
|
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 = (
|
|
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
|
-
};
|