@applicaster/zapp-react-native-utils 14.0.0-rc.9 → 15.0.0-rc.2
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/const.ts +13 -0
- package/appUtils/accessibilityManager/hooks.ts +35 -1
- package/appUtils/accessibilityManager/index.ts +154 -30
- package/appUtils/accessibilityManager/utils.ts +24 -0
- package/appUtils/contextKeysManager/contextResolver.ts +42 -1
- package/appUtils/focusManager/__tests__/__snapshots__/focusManager.test.js.snap +7 -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 +86 -11
- package/appUtils/focusManager/treeDataStructure/Tree/index.js +1 -1
- package/appUtils/focusManagerAux/utils/index.ts +94 -3
- package/appUtils/platform/platformUtils.ts +31 -1
- 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 +64 -35
- package/configurationUtils/manifestKeyParser.ts +57 -32
- package/focusManager/FocusManager.ts +104 -20
- package/focusManager/Tree.ts +25 -21
- package/focusManager/__tests__/FocusManager.test.ts +50 -8
- package/focusManager/aux/index.ts +98 -0
- package/focusManager/utils.ts +12 -6
- package/index.d.ts +1 -10
- 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 +2764 -1539
- package/manifestUtils/index.js +4 -0
- package/manifestUtils/keys.js +12 -0
- package/manifestUtils/progressBar/__tests__/mobileProgressBar.test.js +0 -30
- package/manifestUtils/sharedConfiguration/screenPicker/stylesFields.js +6 -0
- package/navigationUtils/__tests__/mapContentTypesToRivers.test.ts +130 -0
- package/navigationUtils/index.ts +7 -5
- package/package.json +2 -3
- package/playerUtils/PlayerTTS/PlayerTTS.ts +359 -0
- package/playerUtils/PlayerTTS/index.ts +1 -0
- 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 +53 -0
- package/playerUtils/usePlayerTTS.ts +21 -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__/find.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/endsWith.ts +9 -0
- package/utils/find.ts +3 -0
- package/utils/index.ts +33 -1
- 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/zappFrameworkUtils/HookCallback/callbackNavigationAction.ts +164 -0
- package/zappFrameworkUtils/HookCallback/hookCallbackManifestExtensions.config.js +60 -0
- package/zappFrameworkUtils/HookCallback/useCallbackActions.ts +22 -0
- package/zappFrameworkUtils/loginPluginUtils.ts +1 -1
- package/playerUtils/configurationGenerator.ts +0 -2572
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
/// <reference types="@applicaster/applicaster-types" />
|
|
2
2
|
import * as R from "ramda";
|
|
3
3
|
import { Platform } from "react-native";
|
|
4
|
+
import {
|
|
5
|
+
isFilledArray,
|
|
6
|
+
isEmptyArray,
|
|
7
|
+
} from "@applicaster/zapp-react-native-utils/arrayUtils";
|
|
8
|
+
import { isEmpty } from "@applicaster/zapp-react-native-utils/utils";
|
|
9
|
+
|
|
4
10
|
import { applyTransform } from "../transform";
|
|
5
11
|
import { ManifestField } from "../types";
|
|
6
12
|
import { isNilOrEmpty } from "../reactUtils/helpers";
|
|
@@ -122,54 +128,77 @@ export function flattenFields(
|
|
|
122
128
|
}
|
|
123
129
|
|
|
124
130
|
/**
|
|
125
|
-
* Retrieves the
|
|
126
|
-
*
|
|
127
|
-
*
|
|
128
|
-
* @
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
131
|
+
* Retrieves all media items from the entry's media_group that are of type "image" or "thumbnail".
|
|
132
|
+
*
|
|
133
|
+
* @param {ZappEntry} entry - The entry object containing the media_group array.
|
|
134
|
+
* @returns {Option<ZappMediaItem[]>} An array of media items of type "image" or "thumbnail", or undefined if no media_group is present.
|
|
135
|
+
*/
|
|
136
|
+
export function getMediaItems(entry: ZappEntry): Option<ZappMediaItem[]> {
|
|
137
|
+
const mediaGroup = entry?.media_group || [];
|
|
138
|
+
|
|
139
|
+
if (isEmptyArray(mediaGroup)) {
|
|
140
|
+
return undefined;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const mediaItems = mediaGroup
|
|
144
|
+
.filter((group) => ["image", "thumbnail"].includes(group.type))
|
|
145
|
+
.flatMap((group) =>
|
|
146
|
+
Array.isArray(group.media_item) ? group.media_item : [group.media_item]
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
if (isFilledArray(mediaItems)) {
|
|
150
|
+
return mediaItems;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return undefined;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Retrieves the "src" value from a media item in the entry's media group,
|
|
158
|
+
* based on a provided key, with fallback logic.
|
|
159
|
+
*
|
|
160
|
+
* Fallback order:
|
|
161
|
+
* 1. Attempts to find a media item with the specified key (or "image_base" if none provided).
|
|
162
|
+
* 2. If not found, attempts to find a media item with the key "image_base".
|
|
163
|
+
* 3. If still not found, falls back to the first available media item.
|
|
164
|
+
*
|
|
165
|
+
* Special handling: If the resolved item's "src" is an empty string, returns undefined,
|
|
166
|
+
* since empty URIs are invalid in some platforms (e.g., React Native).
|
|
167
|
+
*
|
|
168
|
+
* @param {ZappEntry} entry - The entry object containing a media group.
|
|
169
|
+
* @param {string[] | unknown} arg - A single-element array containing the key to look up, or any unknown value.
|
|
170
|
+
* @returns {?string} The "src" URI from the matched media item, or undefined if not found or empty.
|
|
132
171
|
*/
|
|
133
172
|
export function imageSrcFromMediaItem(
|
|
134
173
|
entry: ZappEntry,
|
|
135
174
|
arg: string[] | unknown
|
|
136
|
-
) {
|
|
175
|
+
): Option<string> {
|
|
137
176
|
const args: unknown = R.unless(Array.isArray, Array)(arg || []);
|
|
138
177
|
const imageKey: string = args?.[0] || "image_base"; // always a single key in this function
|
|
139
|
-
const mediaGroup = R.path<ZappMediaGroup[]>(["media_group"], entry);
|
|
140
178
|
|
|
141
|
-
|
|
179
|
+
const mediaItems = getMediaItems(entry);
|
|
180
|
+
|
|
181
|
+
if (!mediaItems) {
|
|
142
182
|
return undefined;
|
|
143
183
|
}
|
|
144
184
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
R.propEq("type", "thumbnail")
|
|
148
|
-
);
|
|
149
|
-
|
|
150
|
-
const pickMediaItemProp = R.prop<ZappMediaItem>("media_item");
|
|
185
|
+
// Try to find the item with the given key
|
|
186
|
+
let foundItem = mediaItems.find((item) => item.key === imageKey);
|
|
151
187
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
)(mediaGroup);
|
|
188
|
+
// If not found and key was not "image_base", try to find "image_base"
|
|
189
|
+
if (!foundItem && imageKey !== "image_base") {
|
|
190
|
+
foundItem = mediaItems.find((item) => item.key === "image_base");
|
|
191
|
+
}
|
|
157
192
|
|
|
158
|
-
|
|
159
|
-
|
|
193
|
+
// If still not found, default to first item
|
|
194
|
+
if (!foundItem) {
|
|
195
|
+
foundItem = mediaItems[0];
|
|
160
196
|
}
|
|
161
197
|
|
|
162
|
-
const src =
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
R.find(R.propEq("key", imageKey))
|
|
167
|
-
)(mediaItems);
|
|
168
|
-
|
|
169
|
-
// Special case for react native - uri cannot be an empty string (yellow warning).
|
|
170
|
-
// R.isEmpty is tailored specifically for checks like these,
|
|
171
|
-
// it returns false for undefined values.
|
|
172
|
-
return R.isEmpty(src) ? undefined : src;
|
|
198
|
+
const src = foundItem?.src;
|
|
199
|
+
|
|
200
|
+
// React Native quirk: empty string is invalid for URIs
|
|
201
|
+
return isEmpty(src) ? undefined : src;
|
|
173
202
|
}
|
|
174
203
|
|
|
175
204
|
/**
|
|
@@ -370,7 +399,7 @@ export const populateConfigurationValues =
|
|
|
370
399
|
flattenAndPopulateFields(fields, configuration, skipDefaults)
|
|
371
400
|
);
|
|
372
401
|
|
|
373
|
-
export const
|
|
402
|
+
export const getAccessibilityProps = (item: ZappEntry) => ({
|
|
374
403
|
accessible: item?.extensions?.accessibility,
|
|
375
404
|
accessibilityLabel: item?.extensions?.accessibility?.label || item?.title,
|
|
376
405
|
accessibilityHint: item?.extensions?.accessibility?.hint,
|
|
@@ -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
|
};
|
|
@@ -2,11 +2,24 @@ import { path } from "ramda";
|
|
|
2
2
|
import { isString } from "@applicaster/zapp-react-native-utils/stringUtils";
|
|
3
3
|
import * as FOCUS_EVENTS from "@applicaster/zapp-react-native-utils/appUtils/focusManager/events";
|
|
4
4
|
|
|
5
|
+
import {
|
|
6
|
+
QUICK_BRICK_CONTENT,
|
|
7
|
+
QUICK_BRICK_NAVBAR,
|
|
8
|
+
} from "@applicaster/quick-brick-core/const";
|
|
9
|
+
|
|
5
10
|
import { logger } from "./logger";
|
|
6
11
|
import { TreeNode } from "./TreeNode";
|
|
7
12
|
import { Tree } from "./Tree";
|
|
8
13
|
import { subscriber } from "../functionUtils";
|
|
9
14
|
import { getFocusableId, toFocusDirection } from "./utils";
|
|
15
|
+
import {
|
|
16
|
+
findSelectedMenuId,
|
|
17
|
+
isTabsScreenContentFocused,
|
|
18
|
+
findSelectedTabId,
|
|
19
|
+
contextWithoutScrolling,
|
|
20
|
+
} from "./aux";
|
|
21
|
+
|
|
22
|
+
export { contextWithoutScrolling } from "./aux";
|
|
10
23
|
|
|
11
24
|
export {
|
|
12
25
|
toFocusDirection,
|
|
@@ -176,18 +189,30 @@ class FocusManager {
|
|
|
176
189
|
}
|
|
177
190
|
}
|
|
178
191
|
|
|
179
|
-
registerFocusable(
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
isFocusableCell
|
|
183
|
-
|
|
184
|
-
|
|
192
|
+
registerFocusable({
|
|
193
|
+
touchableRef,
|
|
194
|
+
parentFocusableRef,
|
|
195
|
+
isFocusableCell,
|
|
196
|
+
parentFocusableId,
|
|
197
|
+
}: {
|
|
198
|
+
touchableRef: FocusManager.TouchableReactRef;
|
|
199
|
+
parentFocusableRef: FocusManager.TouchableReactRef;
|
|
200
|
+
isFocusableCell: boolean;
|
|
201
|
+
parentFocusableId: string;
|
|
202
|
+
}) {
|
|
203
|
+
const focusableId = getFocusableId(touchableRef);
|
|
204
|
+
|
|
185
205
|
const focusableComponent = FocusManager.findFocusable(focusableId);
|
|
186
206
|
|
|
187
|
-
if (!focusableComponent &&
|
|
188
|
-
this.focusableComponents.push(
|
|
207
|
+
if (!focusableComponent && touchableRef) {
|
|
208
|
+
this.focusableComponents.push(touchableRef);
|
|
189
209
|
|
|
190
|
-
this.tree.add(
|
|
210
|
+
this.tree.add(
|
|
211
|
+
touchableRef,
|
|
212
|
+
parentFocusableRef,
|
|
213
|
+
isFocusableCell,
|
|
214
|
+
parentFocusableId
|
|
215
|
+
);
|
|
191
216
|
} else {
|
|
192
217
|
logger.warning("Focusable component already registered", {
|
|
193
218
|
id: focusableId,
|
|
@@ -209,7 +234,8 @@ class FocusManager {
|
|
|
209
234
|
|
|
210
235
|
private setNextFocus(
|
|
211
236
|
nextFocus: FocusManager.TouchableReactRef,
|
|
212
|
-
options?: FocusManager.Android.CallbackOptions
|
|
237
|
+
options?: FocusManager.Android.CallbackOptions,
|
|
238
|
+
context?: FocusManager.FocusContext
|
|
213
239
|
) {
|
|
214
240
|
if (nextFocus?.current?.props?.blockFocus) {
|
|
215
241
|
return;
|
|
@@ -238,17 +264,15 @@ class FocusManager {
|
|
|
238
264
|
|
|
239
265
|
FocusManager.instance.setPreviousNavigationDirection(options ?? null);
|
|
240
266
|
|
|
241
|
-
nextFocus?.current?.onFocus?.(nextFocus.current, options ?? {});
|
|
267
|
+
nextFocus?.current?.onFocus?.(nextFocus.current, options ?? {}, context);
|
|
242
268
|
}
|
|
243
269
|
}
|
|
244
270
|
|
|
245
271
|
blurPrevious(options?: FocusManager.Android.CallbackOptions) {
|
|
246
|
-
|
|
247
|
-
FocusManager.instance.prevFocused
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
);
|
|
251
|
-
}
|
|
272
|
+
FocusManager.instance.prevFocused?.onBlur?.(
|
|
273
|
+
FocusManager.instance.prevFocused,
|
|
274
|
+
options ?? {} // Adding fallback to avoid potential regression caused by #7509
|
|
275
|
+
);
|
|
252
276
|
}
|
|
253
277
|
|
|
254
278
|
onDisableFocusChange = (id) => {
|
|
@@ -269,7 +293,7 @@ class FocusManager {
|
|
|
269
293
|
|
|
270
294
|
if (nextFocus) {
|
|
271
295
|
// HACK: hack to fix the hack below
|
|
272
|
-
// HACK: putting call to the end of the event loop so the next component has a
|
|
296
|
+
// HACK: putting call to the end of the event loop so the next component has a chance to be registered
|
|
273
297
|
setTimeout(() => {
|
|
274
298
|
FocusManager.instance.setFocus(nextFocus, {
|
|
275
299
|
direction: "down",
|
|
@@ -281,7 +305,8 @@ class FocusManager {
|
|
|
281
305
|
|
|
282
306
|
setFocus(
|
|
283
307
|
newFocus: FocusManager.TouchableReactRef | string,
|
|
284
|
-
options?: FocusManager.Android.CallbackOptions
|
|
308
|
+
options?: FocusManager.Android.CallbackOptions,
|
|
309
|
+
context?: FocusManager.FocusContext
|
|
285
310
|
) {
|
|
286
311
|
// Checks if element is focusable
|
|
287
312
|
const { isFocusable, error } = FocusManager.isFocusable(newFocus);
|
|
@@ -306,7 +331,7 @@ class FocusManager {
|
|
|
306
331
|
}
|
|
307
332
|
|
|
308
333
|
if (newFocusRef) {
|
|
309
|
-
FocusManager.instance.setNextFocus(newFocusRef, options);
|
|
334
|
+
FocusManager.instance.setNextFocus(newFocusRef, options, context);
|
|
310
335
|
}
|
|
311
336
|
}
|
|
312
337
|
}
|
|
@@ -341,6 +366,11 @@ class FocusManager {
|
|
|
341
366
|
FocusManager.instance.focused.onBlur(FocusManager.instance.focused, {});
|
|
342
367
|
}
|
|
343
368
|
|
|
369
|
+
// send reset event to some handler to reset their internal state, before real reset happens
|
|
370
|
+
this.eventHandler?.invokeHandler?.(FOCUS_EVENTS.RESET, {
|
|
371
|
+
focusedId: FocusManager.instance.focusedId,
|
|
372
|
+
});
|
|
373
|
+
|
|
344
374
|
FocusManager.instance.setFocusLocal({ current: null });
|
|
345
375
|
}
|
|
346
376
|
|
|
@@ -407,6 +437,60 @@ class FocusManager {
|
|
|
407
437
|
throw new Error(`Group with id ${id} not found`);
|
|
408
438
|
}
|
|
409
439
|
}
|
|
440
|
+
|
|
441
|
+
isFocusOnMenu(): boolean {
|
|
442
|
+
return this.isFocusableChildOf(
|
|
443
|
+
FocusManager.instance.focusedId,
|
|
444
|
+
QUICK_BRICK_NAVBAR
|
|
445
|
+
);
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
isFocusOnContent(): boolean {
|
|
449
|
+
return this.isFocusableChildOf(
|
|
450
|
+
FocusManager.instance.focusedId,
|
|
451
|
+
QUICK_BRICK_CONTENT
|
|
452
|
+
);
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
private landFocusToWithoutScrolling = (id) => {
|
|
456
|
+
if (id) {
|
|
457
|
+
// set focus on selected menu item
|
|
458
|
+
const direction = undefined;
|
|
459
|
+
|
|
460
|
+
const context: FocusManager.FocusContext =
|
|
461
|
+
contextWithoutScrolling("back");
|
|
462
|
+
|
|
463
|
+
logger.log({ message: "landFocusToWithoutScrolling", data: { id } });
|
|
464
|
+
|
|
465
|
+
this.setFocus(id, direction, context);
|
|
466
|
+
}
|
|
467
|
+
};
|
|
468
|
+
|
|
469
|
+
// Move focus to appropriate top navigation tab with context
|
|
470
|
+
focusOnSelectedTab(index: number): void {
|
|
471
|
+
const selectedTabId = findSelectedTabId(this.tree, index);
|
|
472
|
+
|
|
473
|
+
// Set focus with back button context to tabs-menu
|
|
474
|
+
this.landFocusToWithoutScrolling(selectedTabId);
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
// Move focus to appropriate top navigation tab with context
|
|
478
|
+
focusOnSelectedTopMenuItem(index: number, sectionKey: string): void {
|
|
479
|
+
const selectedMenuItemId = findSelectedMenuId(this.tree, {
|
|
480
|
+
index,
|
|
481
|
+
sectionKey,
|
|
482
|
+
});
|
|
483
|
+
|
|
484
|
+
// Set focus with back button context to top-menu
|
|
485
|
+
this.landFocusToWithoutScrolling(selectedMenuItemId);
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
isTabsScreenContentFocused(): boolean {
|
|
489
|
+
return isTabsScreenContentFocused(
|
|
490
|
+
this.tree,
|
|
491
|
+
FocusManager.instance.focusedId
|
|
492
|
+
);
|
|
493
|
+
}
|
|
410
494
|
}
|
|
411
495
|
|
|
412
496
|
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)
|