@applicaster/zapp-react-native-utils 12.0.0-alpha.9004455836 → 12.0.0-alpha.9233494544
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 +16 -8
- package/analyticsUtils/__tests__/analyticsManager.test.js +75 -73
- package/analyticsUtils/helpers/hooks.ts +1 -1
- package/appUtils/HooksManager/index.js +3 -1
- package/appUtils/focusManager/index.ts +147 -25
- package/appUtils/focusManager/treeDataStructure/Utils/index.js +83 -33
- package/appUtils/focusManager/utils/index.ts +77 -0
- package/appUtils/keyCodes/__tests__/__snapshots__/keyCodes.test.js.snap +1 -1
- package/appUtils/keyCodes/__tests__/keyCodes.test.js +80 -85
- package/appUtils/keyCodes/keys.web.js +1 -0
- package/appUtils/playerManager/OverlayObserver/OverlaysObserver.ts +53 -0
- package/appUtils/playerManager/conts.ts +5 -0
- package/appUtils/playerManager/player.ts +47 -4
- package/appUtils/playerManager/playerFactory.ts +12 -10
- package/appUtils/playerManager/playerNative.ts +35 -0
- package/appUtils/playerManager/usePlayerState.tsx +13 -1
- package/appUtils/playerManager/usePlayerTitleSummaryOverlay.tsx +50 -0
- package/cellUtils/__tests__/cellUtils.test.ts +59 -0
- package/cellUtils/__tests__/fixtures.ts +13 -0
- package/cellUtils/index.ts +33 -2
- package/focusManager/index.ts +1 -1
- package/functionUtils/__tests__/__snapshots__/functionUtils.test.js.snap +2 -2
- package/functionUtils/__tests__/functionUtils.test.js +244 -248
- package/headersUtils/index.ts +1 -0
- package/localizationUtils/__tests__/localizationUtils.test.ts +17 -13
- package/localizationUtils/index.ts +6 -3
- package/logger/index.ts +1 -1
- package/manifestUtils/defaultManifestConfigurations/generalContent.js +6 -0
- package/manifestUtils/defaultManifestConfigurations/player.js +10 -1
- package/manifestUtils/index.js +5 -1
- package/manifestUtils/secondaryImage/index.js +18 -15
- package/modalState/index.ts +57 -0
- package/navigationUtils/index.ts +56 -7
- package/package.json +3 -2
- package/playerUtils/configurationGenerator.ts +11 -1
- package/pluginUtils/index.ts +2 -2
- package/reactHooks/autoscrolling/__tests__/useTrackCurrentAutoScrollingElement.test.ts +2 -0
- package/reactHooks/autoscrolling/__tests__/{useTrackedView.test.ts → useTrackedView.test.tsx} +18 -1
- package/reactHooks/cell-click/__tests__/index.test.js +6 -2
- package/reactHooks/cell-click/index.ts +2 -2
- package/reactHooks/flatList/useLoadNextPageIfNeeded.ts +16 -9
- package/reactHooks/index.ts +2 -0
- package/reactHooks/layout/__tests__/{index.test.js → index.test.tsx} +66 -59
- package/reactHooks/layout/getDeviceInfo/index.ts +4 -2
- package/reactHooks/layout/types/index.ts +2 -0
- package/reactHooks/layout/useDimensions/helpers/__tests__/getInitialDimensions.native.test.js +11 -16
- package/reactHooks/layout/useDimensions/helpers/__tests__/getInitialDimensions.test.js +12 -14
- package/reactHooks/layout/useDimensions/useDimensions.ts +13 -7
- package/reactHooks/navigation/__mocks__/index.ts +15 -0
- package/reactHooks/navigation/__mocks__/useNavigation.ts +7 -0
- package/reactHooks/navigation/__tests__/index.test.tsx +234 -78
- package/reactHooks/navigation/index.ts +262 -81
- package/reactHooks/resolvers/__tests__/useCellResolver.test.tsx +3 -1
- package/reactHooks/screen/__mocks__/useScreenContext.ts +3 -1
- package/reactHooks/screen/index.ts +2 -0
- package/reactHooks/screen/useScreenContext.ts +5 -4
- package/reactHooks/screen/useScreenData.ts +1 -1
- package/reactHooks/screen/useTargetScreenData.ts +7 -0
- package/reactHooks/state/useRivers.ts +3 -1
- package/reactHooks/useListenEventBusEvent.ts +1 -4
- package/reactHooks/videoModal/hooks/useVideoModalScreenData.tsx +10 -9
- package/reactUtils/helpers.tsx +1 -1
- package/riverComponetsMeasurementProvider/index.tsx +2 -2
- package/stringUtils/index.ts +5 -0
- package/testUtils/getByTestId.ts +14 -0
- package/testUtils/index.tsx +38 -23
|
@@ -11,11 +11,9 @@ import * as QuickBrickManager from "@applicaster/zapp-react-native-bridge/QuickB
|
|
|
11
11
|
import { showConfirmationDialog } from "../alertUtils";
|
|
12
12
|
import { createCloudEvent, sendCloudEvent } from "../cloudEventsUtils";
|
|
13
13
|
import { createLogger } from "../logger";
|
|
14
|
-
import {
|
|
15
|
-
StorageMultiSelectProvider
|
|
16
|
-
} from "@applicaster/zapp-react-native-bridge/ZappStorage/StorageMultiSelectProvider";
|
|
14
|
+
import { StorageMultiSelectProvider } from "@applicaster/zapp-react-native-bridge/ZappStorage/StorageMultiSelectProvider";
|
|
17
15
|
|
|
18
|
-
const { log_error,
|
|
16
|
+
const { log_error, log_info } = createLogger({
|
|
19
17
|
subsystem: "ActionExecutorContext",
|
|
20
18
|
category: "General",
|
|
21
19
|
});
|
|
@@ -110,11 +108,17 @@ const prepareDefaultActions = (actionExecutor) => {
|
|
|
110
108
|
const tag = entry?.extensions?.tag;
|
|
111
109
|
|
|
112
110
|
if (keyNamespace && tag) {
|
|
113
|
-
const multiSelectProvider =
|
|
111
|
+
const multiSelectProvider =
|
|
112
|
+
StorageMultiSelectProvider.getProvider(keyNamespace);
|
|
114
113
|
|
|
115
114
|
const selectedItems = await multiSelectProvider.getSelectedAsync();
|
|
116
115
|
const isTagInSelectedItems = selectedItems.includes(tag);
|
|
117
|
-
|
|
116
|
+
|
|
117
|
+
log_info(
|
|
118
|
+
`handleAction: localStorageToggleFlag event will ${
|
|
119
|
+
isTagInSelectedItems ? "remove" : "add"
|
|
120
|
+
} tag: ${tag} for keyNamespace: ${keyNamespace}, current selectedItems: ${selectedItems}`
|
|
121
|
+
);
|
|
118
122
|
|
|
119
123
|
if (selectedItems.includes(tag)) {
|
|
120
124
|
await multiSelectProvider.removeItem(tag);
|
|
@@ -122,13 +126,17 @@ const prepareDefaultActions = (actionExecutor) => {
|
|
|
122
126
|
await multiSelectProvider.addItem(tag);
|
|
123
127
|
}
|
|
124
128
|
} else {
|
|
125
|
-
log_error(
|
|
129
|
+
log_error(
|
|
130
|
+
"handleAction: localStorageToggleFlag event missing keyNamespace or tag",
|
|
131
|
+
{ keyNamespace, tag }
|
|
132
|
+
);
|
|
126
133
|
|
|
127
134
|
return ActionResult.Error;
|
|
128
135
|
}
|
|
129
136
|
|
|
130
137
|
return ActionResult.Success;
|
|
131
|
-
}
|
|
138
|
+
}
|
|
139
|
+
);
|
|
132
140
|
};
|
|
133
141
|
|
|
134
142
|
export const ActionExecutorContext =
|
|
@@ -1,18 +1,18 @@
|
|
|
1
|
+
import * as ReactNative from "react-native";
|
|
2
|
+
|
|
1
3
|
const mock_postEvent = jest.fn();
|
|
2
4
|
const mock_setPlugins = jest.fn();
|
|
3
5
|
const mock_postTimedEvent = jest.fn();
|
|
4
6
|
const mock_endTimedEvent = jest.fn();
|
|
5
7
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
},
|
|
15
|
-
}));
|
|
8
|
+
const AnalyticsBridge = {
|
|
9
|
+
postEvent: mock_postEvent,
|
|
10
|
+
setPlugins: mock_setPlugins,
|
|
11
|
+
postTimedEvent: mock_postTimedEvent,
|
|
12
|
+
endTimedEvent: mock_endTimedEvent,
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
ReactNative.NativeModules.AnalyticsBridge = AnalyticsBridge;
|
|
16
16
|
|
|
17
17
|
const eventName = "event";
|
|
18
18
|
const payload = {};
|
|
@@ -26,90 +26,92 @@ function clearAllMocks() {
|
|
|
26
26
|
mock_endTimedEvent.mockClear();
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
describe("
|
|
30
|
-
|
|
29
|
+
describe("AnalyticsManager", () => {
|
|
30
|
+
describe("isValidAnalyticsData", () => {
|
|
31
|
+
beforeEach(clearAllMocks);
|
|
31
32
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
33
|
+
it("returns false if eventName is not a string", () => {
|
|
34
|
+
expect(analyticsManager.isValidAnalyticsData(42)).toBe(false);
|
|
35
|
+
expect(analyticsManager.isValidAnalyticsData({})).toBe(false);
|
|
36
|
+
expect(analyticsManager.isValidAnalyticsData([])).toBe(false);
|
|
37
|
+
expect(analyticsManager.isValidAnalyticsData(() => ({}))).toBe(false);
|
|
38
|
+
});
|
|
38
39
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
40
|
+
it("returns false if payload is not an object", () => {
|
|
41
|
+
expect(analyticsManager.isValidAnalyticsData(eventName, "string")).toBe(
|
|
42
|
+
false
|
|
43
|
+
);
|
|
43
44
|
|
|
44
|
-
|
|
45
|
+
expect(analyticsManager.isValidAnalyticsData(eventName, 42)).toBe(false);
|
|
45
46
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
47
|
+
expect(analyticsManager.isValidAnalyticsData(eventName, () => ({}))).toBe(
|
|
48
|
+
false
|
|
49
|
+
);
|
|
50
|
+
});
|
|
50
51
|
|
|
51
|
-
|
|
52
|
-
|
|
52
|
+
it("returns true if eventName is a string and payload is null or an object", () => {
|
|
53
|
+
expect(analyticsManager.isValidAnalyticsData(eventName)).toBe(true);
|
|
53
54
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
55
|
+
expect(analyticsManager.isValidAnalyticsData(eventName, payload)).toBe(
|
|
56
|
+
true
|
|
57
|
+
);
|
|
58
|
+
});
|
|
57
59
|
});
|
|
58
|
-
});
|
|
59
60
|
|
|
60
|
-
describe("postAnalyticEvent", () => {
|
|
61
|
-
|
|
61
|
+
describe("postAnalyticEvent", () => {
|
|
62
|
+
beforeEach(clearAllMocks);
|
|
62
63
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
64
|
+
it("calls the postEvent method of the analytic bridge", () => {
|
|
65
|
+
analyticsManager.postAnalyticEvent(eventName, payload);
|
|
66
|
+
expect(mock_postEvent).toHaveBeenCalledWith(eventName, payload);
|
|
67
|
+
});
|
|
67
68
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
69
|
+
it("skips the event if it is not valid", () => {
|
|
70
|
+
analyticsManager.postAnalyticEvent(42);
|
|
71
|
+
expect(mock_postTimedEvent).not.toHaveBeenCalled();
|
|
72
|
+
});
|
|
71
73
|
});
|
|
72
|
-
});
|
|
73
74
|
|
|
74
|
-
describe("providePlugins", () => {
|
|
75
|
-
|
|
75
|
+
describe("providePlugins", () => {
|
|
76
|
+
beforeEach(clearAllMocks);
|
|
76
77
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
78
|
+
it("calls the setPlugins method of the analytic bridge", () => {
|
|
79
|
+
const plugins = [];
|
|
80
|
+
const plugin_configurations = {};
|
|
81
|
+
const callback = jest.fn();
|
|
81
82
|
|
|
82
|
-
|
|
83
|
+
analyticsManager.providePlugins(plugins, plugin_configurations, callback);
|
|
83
84
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
85
|
+
expect(mock_setPlugins).toHaveBeenCalledWith(
|
|
86
|
+
plugins,
|
|
87
|
+
plugin_configurations,
|
|
88
|
+
callback
|
|
89
|
+
);
|
|
90
|
+
});
|
|
89
91
|
});
|
|
90
|
-
});
|
|
91
92
|
|
|
92
|
-
describe("startAnalyticsTimedEvent", () => {
|
|
93
|
-
|
|
93
|
+
describe("startAnalyticsTimedEvent", () => {
|
|
94
|
+
beforeEach(clearAllMocks);
|
|
94
95
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
96
|
+
it("skips the event if it is not valid", () => {
|
|
97
|
+
analyticsManager.startAnalyticsTimedEvent(42);
|
|
98
|
+
expect(mock_postTimedEvent).not.toHaveBeenCalled();
|
|
99
|
+
});
|
|
98
100
|
});
|
|
99
|
-
});
|
|
100
101
|
|
|
101
|
-
describe("endAnalyticsTimedEvent", () => {
|
|
102
|
-
|
|
102
|
+
describe("endAnalyticsTimedEvent", () => {
|
|
103
|
+
beforeEach(clearAllMocks);
|
|
103
104
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
105
|
+
it("emits a time_on_screen event and adds duration to payload", () => {
|
|
106
|
+
analyticsManager.endAnalyticsTimedEvent(eventName, payload);
|
|
107
|
+
const newEventName = "time_on_screen";
|
|
108
|
+
payload.duration = "N/A";
|
|
109
|
+
expect(mock_postEvent).toHaveBeenCalledWith(newEventName, payload);
|
|
110
|
+
});
|
|
110
111
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
112
|
+
it("skips the event if it is not valid", () => {
|
|
113
|
+
analyticsManager.endAnalyticsTimedEvent(42);
|
|
114
|
+
expect(mock_postEvent).not.toHaveBeenCalled();
|
|
115
|
+
});
|
|
114
116
|
});
|
|
115
117
|
});
|
|
@@ -26,7 +26,7 @@ export const useHookAnalytics = (props): void => {
|
|
|
26
26
|
if (screenData?.hookPlugin?.module?.runInBackground) {
|
|
27
27
|
sendScreenEvent({ analyticsScreenData });
|
|
28
28
|
}
|
|
29
|
-
}, [hookPlugin.screen_id]);
|
|
29
|
+
}, [sendScreenEvent, screenData, hookPlugin.screen_id]);
|
|
30
30
|
};
|
|
31
31
|
|
|
32
32
|
export const useScreenAnalytics = (props) => {
|
|
@@ -270,7 +270,9 @@ export function HooksManager({
|
|
|
270
270
|
});
|
|
271
271
|
|
|
272
272
|
const isHookInHomescreen = payload?.home;
|
|
273
|
-
|
|
273
|
+
|
|
274
|
+
const isHookFlowBlocker =
|
|
275
|
+
hookPlugin?.module?.isFlowBlocker?.() ?? false;
|
|
274
276
|
|
|
275
277
|
// TODO: We technically need to also support the case where there is more than one hook in the stack,
|
|
276
278
|
// if needed we could add differentiation explicit user cancellation (when we should go to background) and other types of cancellation
|
|
@@ -1,9 +1,24 @@
|
|
|
1
1
|
import * as R from "ramda";
|
|
2
|
+
import {
|
|
3
|
+
isNilOrEmpty,
|
|
4
|
+
isNotNil,
|
|
5
|
+
} from "@applicaster/zapp-react-native-utils/reactUtils/helpers";
|
|
6
|
+
|
|
2
7
|
import { Tree } from "./treeDataStructure/Tree";
|
|
3
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
findFocusableNode,
|
|
10
|
+
findPriorityItem,
|
|
11
|
+
isOnTheSameScreen,
|
|
12
|
+
} from "./treeDataStructure/Utils";
|
|
4
13
|
import { subscriber } from "../../functionUtils";
|
|
5
14
|
import * as FOCUS_EVENTS from "./events";
|
|
6
15
|
import { coreLogger } from "../../logger";
|
|
16
|
+
import {
|
|
17
|
+
waitForActiveScreen,
|
|
18
|
+
waitForContent,
|
|
19
|
+
getContentNode,
|
|
20
|
+
getActiveScreen,
|
|
21
|
+
} from "./utils";
|
|
7
22
|
|
|
8
23
|
const logger = coreLogger.addSubsystem("focusManager");
|
|
9
24
|
|
|
@@ -46,7 +61,7 @@ export const focusManager = (function () {
|
|
|
46
61
|
let lastFocusedId = null;
|
|
47
62
|
let initialFocusOnContent = null;
|
|
48
63
|
let focusDisabled = false;
|
|
49
|
-
let focusAutoEnabled =
|
|
64
|
+
let focusAutoEnabled = false;
|
|
50
65
|
const eventHandler = subscriber();
|
|
51
66
|
|
|
52
67
|
function disableAutomaticFocus() {
|
|
@@ -93,7 +108,7 @@ export const focusManager = (function () {
|
|
|
93
108
|
!currentFocusable.isGroup &&
|
|
94
109
|
currentFocusable.isMounted()
|
|
95
110
|
) {
|
|
96
|
-
currentFocusable.blur(
|
|
111
|
+
currentFocusable.blur(direction);
|
|
97
112
|
currentFocusable.hasLostFocus(getCurrentFocus(), direction);
|
|
98
113
|
}
|
|
99
114
|
}
|
|
@@ -226,32 +241,51 @@ export const focusManager = (function () {
|
|
|
226
241
|
|
|
227
242
|
logger.log({ message: "setInitialFocus", data: { preferredFocus } });
|
|
228
243
|
|
|
229
|
-
if (preferredFocus
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
if (
|
|
234
|
-
focusableItem &&
|
|
235
|
-
!focusableItem.isGroup &&
|
|
236
|
-
isFocusEnabled(focusableItem)
|
|
237
|
-
) {
|
|
238
|
-
if (initialFocusOnContent) {
|
|
239
|
-
blur();
|
|
240
|
-
initialFocusOnContent = false;
|
|
241
|
-
}
|
|
244
|
+
if (isNilOrEmpty(preferredFocus)) {
|
|
245
|
+
return { success: false };
|
|
246
|
+
}
|
|
242
247
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
248
|
+
const focusCandidate = preferredFocus[0];
|
|
249
|
+
const focusableItem = focusCandidate.component;
|
|
250
|
+
|
|
251
|
+
const currentFocusedId = R.path(["props", "id"], getCurrentFocus());
|
|
252
|
+
const nextFocusedId = R.path(["props", "id"], focusableItem);
|
|
253
|
+
|
|
254
|
+
if (currentFocusedId === nextFocusedId) {
|
|
255
|
+
// we are going to move focus to the same node(already focused) -> nothing to do
|
|
256
|
+
return { success: true };
|
|
257
|
+
}
|
|
249
258
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
259
|
+
if (preferredFocus.length === 1 && focusableItem && focusableItem.isGroup) {
|
|
260
|
+
// we've found only one FocusableGroup as a candidate to focus, try to use fallback
|
|
261
|
+
return { success: false };
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
if (
|
|
265
|
+
focusableItem &&
|
|
266
|
+
!focusableItem.isGroup &&
|
|
267
|
+
isFocusEnabled(focusableItem)
|
|
268
|
+
) {
|
|
269
|
+
if (initialFocusOnContent) {
|
|
270
|
+
blur();
|
|
271
|
+
initialFocusOnContent = false;
|
|
253
272
|
}
|
|
273
|
+
|
|
274
|
+
logger.log({
|
|
275
|
+
message: "setInitialFocus – currentFocusNode",
|
|
276
|
+
data: {
|
|
277
|
+
focusableItem,
|
|
278
|
+
},
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
currentFocusNode = focusCandidate;
|
|
282
|
+
focusableItem.willReceiveFocus(focusableItem, null);
|
|
283
|
+
focusableItem && setFocus(focusCandidate.id, null);
|
|
284
|
+
|
|
285
|
+
return { success: true };
|
|
254
286
|
}
|
|
287
|
+
|
|
288
|
+
return { success: false };
|
|
255
289
|
}
|
|
256
290
|
|
|
257
291
|
function treeLoaded(val, node) {
|
|
@@ -323,6 +357,8 @@ export const focusManager = (function () {
|
|
|
323
357
|
* @returns {React.Component} the registered component
|
|
324
358
|
*/
|
|
325
359
|
function registerGroup(id, component) {
|
|
360
|
+
// console.log("debug_2", "FM registerGroup", { id, component });
|
|
361
|
+
|
|
326
362
|
focusableTree.addNode({ id, component });
|
|
327
363
|
|
|
328
364
|
return component;
|
|
@@ -338,6 +374,8 @@ export const focusManager = (function () {
|
|
|
338
374
|
* @returns {React.Component} the registered component
|
|
339
375
|
*/
|
|
340
376
|
function registerItem(id, component) {
|
|
377
|
+
// console.log("debug_2", "FM registerItem", { id, component });
|
|
378
|
+
|
|
341
379
|
focusableTree.addNode({ id, component });
|
|
342
380
|
|
|
343
381
|
if (!currentFocusNode && id === lastFocusedId && !id.includes("menu")) {
|
|
@@ -354,6 +392,8 @@ export const focusManager = (function () {
|
|
|
354
392
|
* @param {String} id of the group to unregister
|
|
355
393
|
*/
|
|
356
394
|
function unregisterGroup(id) {
|
|
395
|
+
// console.log("debug_2", "FM unregisterGroup", { id });
|
|
396
|
+
|
|
357
397
|
focusableTree.findAndRemoveNode(id);
|
|
358
398
|
}
|
|
359
399
|
|
|
@@ -365,6 +405,8 @@ export const focusManager = (function () {
|
|
|
365
405
|
* @param {String} id of the Focusable to unregister
|
|
366
406
|
*/
|
|
367
407
|
function unregisterItem(id) {
|
|
408
|
+
// console.log("debug_2", "FM unregisterItem", { id });
|
|
409
|
+
|
|
368
410
|
focusableTree.findAndRemoveNode(id);
|
|
369
411
|
|
|
370
412
|
if (currentFocusNode && currentFocusNode.id === id) {
|
|
@@ -403,6 +445,8 @@ export const focusManager = (function () {
|
|
|
403
445
|
}
|
|
404
446
|
|
|
405
447
|
function setupInitialFocus(id: string, focusOnContent?: boolean) {
|
|
448
|
+
console.log("debug_2", "FM - setupInitialFocus", { id, focusOnContent });
|
|
449
|
+
|
|
406
450
|
if (focusOnContent) {
|
|
407
451
|
initialFocusOnContent = focusOnContent;
|
|
408
452
|
} else {
|
|
@@ -410,6 +454,74 @@ export const focusManager = (function () {
|
|
|
410
454
|
}
|
|
411
455
|
}
|
|
412
456
|
|
|
457
|
+
async function setFocusOnMenu(currentRoute: string) {
|
|
458
|
+
console.log("debug_2", "FM - setFocusOnMenu", { currentRoute });
|
|
459
|
+
|
|
460
|
+
try {
|
|
461
|
+
await waitForActiveScreen(currentRoute, focusableTree);
|
|
462
|
+
|
|
463
|
+
const navbar = R.find(
|
|
464
|
+
(child) => R.startsWith("navbar", child.id),
|
|
465
|
+
getActiveScreen(focusableTree).children
|
|
466
|
+
);
|
|
467
|
+
|
|
468
|
+
const selectedMenuItemId = R.find(
|
|
469
|
+
(child) => child.component.props.selected,
|
|
470
|
+
navbar?.children || []
|
|
471
|
+
)?.id;
|
|
472
|
+
|
|
473
|
+
if (selectedMenuItemId) {
|
|
474
|
+
// set focus on selected menu item
|
|
475
|
+
setFocus(selectedMenuItemId);
|
|
476
|
+
return;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
const firstMenuItemId = R.path(["children", 0, "id"], navbar);
|
|
480
|
+
|
|
481
|
+
if (firstMenuItemId) {
|
|
482
|
+
// set focus on first menu item
|
|
483
|
+
setFocus(firstMenuItemId);
|
|
484
|
+
}
|
|
485
|
+
} catch (error) {
|
|
486
|
+
// failed to put focus on top-menu => log it
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
async function setFocusOnContent(currentRoute: string) {
|
|
491
|
+
console.log("debug_2", "FM - setFocusOnContent", { currentRoute });
|
|
492
|
+
|
|
493
|
+
// we need to put focus on content here
|
|
494
|
+
// For doing it we have to wait registration of first focusable node into focusableTree
|
|
495
|
+
// this is the temporary hack with waiting until content node starts having children
|
|
496
|
+
try {
|
|
497
|
+
await waitForActiveScreen(currentRoute, focusableTree);
|
|
498
|
+
await waitForContent(focusableTree);
|
|
499
|
+
|
|
500
|
+
const content = getContentNode(focusableTree);
|
|
501
|
+
|
|
502
|
+
const { success } = setInitialFocus(content);
|
|
503
|
+
|
|
504
|
+
if (!success) {
|
|
505
|
+
// we've failed to put initial focus on content, re-run "setFocusOnContent" again
|
|
506
|
+
throw Error(
|
|
507
|
+
"we've failed to put initial focus on content, re-run 'setFocusOnContent' again"
|
|
508
|
+
);
|
|
509
|
+
}
|
|
510
|
+
} catch (e) {
|
|
511
|
+
setFocusOnContent(currentRoute);
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
function recoverFocus() {
|
|
516
|
+
const lastRoute = R.last(focusableTree.root.children);
|
|
517
|
+
const onSameScreen = isOnTheSameScreen(currentFocusNode, lastRoute);
|
|
518
|
+
|
|
519
|
+
if (!onSameScreen) {
|
|
520
|
+
// currentFocusedNode is not from the last route. We need to run recover focus.
|
|
521
|
+
setInitialFocus();
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
|
|
413
525
|
/**
|
|
414
526
|
* moves the focus in a provided direction
|
|
415
527
|
* @public
|
|
@@ -422,6 +534,13 @@ export const focusManager = (function () {
|
|
|
422
534
|
currentFocusNode.component.willLoseFocus(currentFocusNode, direction);
|
|
423
535
|
const nextFocusable = findFocusableNode(direction, currentFocusNode);
|
|
424
536
|
|
|
537
|
+
// console.log("debug_2", "FM moveFocus", {
|
|
538
|
+
// direction: direction?.value,
|
|
539
|
+
// currentFocusNode,
|
|
540
|
+
// focusableTree,
|
|
541
|
+
// nextFocusable,
|
|
542
|
+
// });
|
|
543
|
+
|
|
425
544
|
if (nextFocusable?.component?.isMounted?.()) {
|
|
426
545
|
const nextRect = nextFocusable.component.getRect();
|
|
427
546
|
|
|
@@ -518,5 +637,8 @@ export const focusManager = (function () {
|
|
|
518
637
|
enableAutomaticFocus,
|
|
519
638
|
getGroupRootById,
|
|
520
639
|
isGroupItemFocused,
|
|
640
|
+
setFocusOnMenu,
|
|
641
|
+
setFocusOnContent,
|
|
642
|
+
recoverFocus,
|
|
521
643
|
};
|
|
522
644
|
})();
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { allPass, pathOr, startsWith, isNil } from "ramda";
|
|
2
2
|
import { isNilOrEmpty } from "@applicaster/zapp-react-native-utils/reactUtils/helpers";
|
|
3
3
|
|
|
4
4
|
import { rejectItemByPath } from "../../../../objectUtils";
|
|
@@ -143,7 +143,49 @@ const hasChildren = (target) =>
|
|
|
143
143
|
|
|
144
144
|
const hasComponent = (target) => !!target?.component;
|
|
145
145
|
|
|
146
|
-
const hasComponentAndChildren =
|
|
146
|
+
const hasComponentAndChildren = allPass([hasComponent, hasChildren]);
|
|
147
|
+
|
|
148
|
+
const positiveOrZero = (value) => value >= 0;
|
|
149
|
+
|
|
150
|
+
const excludeFromFocusSearching = pathOr(false, [
|
|
151
|
+
"component",
|
|
152
|
+
"props",
|
|
153
|
+
"excludeFromFocusSearching",
|
|
154
|
+
]);
|
|
155
|
+
|
|
156
|
+
// const isAllScreens = all(excludeFromFocusSearching);
|
|
157
|
+
|
|
158
|
+
const isNavBar = (target) => startsWith("navbar", target?.id);
|
|
159
|
+
|
|
160
|
+
const ignoreMenuIfMultipleTargets = (nodes) => {
|
|
161
|
+
if (nodes.length > 1 && nodes.map((n) => n?.target?.id).includes("navbar")) {
|
|
162
|
+
return nodes.filter((n) => !isNavBar(n?.target));
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return nodes;
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
const toDistance = ({ target, direction, comparisonRect }) => {
|
|
169
|
+
const componentRect = target.component?.getRect();
|
|
170
|
+
|
|
171
|
+
const orientationDistance = measureDistance(
|
|
172
|
+
direction,
|
|
173
|
+
comparisonRect,
|
|
174
|
+
componentRect
|
|
175
|
+
);
|
|
176
|
+
|
|
177
|
+
if (orientationDistance === -1) {
|
|
178
|
+
// ignores offscreen targets when moving up/down
|
|
179
|
+
return null;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const euclidDistance = measureEuclidianDistanceToCenter(
|
|
183
|
+
comparisonRect,
|
|
184
|
+
componentRect
|
|
185
|
+
);
|
|
186
|
+
|
|
187
|
+
return orientationDistance > 0 ? euclidDistance : orientationDistance;
|
|
188
|
+
};
|
|
147
189
|
|
|
148
190
|
/**
|
|
149
191
|
* Get target in shortest distance in specified direction
|
|
@@ -173,43 +215,32 @@ export function getTargetInDirection(
|
|
|
173
215
|
|
|
174
216
|
const distances = targets
|
|
175
217
|
.filter(hasComponentAndChildren)
|
|
176
|
-
.
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
componentRect
|
|
183
|
-
);
|
|
184
|
-
|
|
185
|
-
if (distance === -1) {
|
|
186
|
-
// ignores offscreen targets when moving up/down
|
|
187
|
-
return null;
|
|
188
|
-
}
|
|
218
|
+
.filter((target) => !excludeFromFocusSearching(target))
|
|
219
|
+
.map((target) => ({
|
|
220
|
+
distance: toDistance({ target, direction, comparisonRect }),
|
|
221
|
+
target,
|
|
222
|
+
}))
|
|
223
|
+
.filter(({ distance }) => positiveOrZero(distance));
|
|
189
224
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
measureEuclidianDistanceToCenter(comparisonRect, componentRect) /
|
|
194
|
-
10000,
|
|
195
|
-
target,
|
|
196
|
-
};
|
|
197
|
-
})
|
|
198
|
-
.filter((obj) => obj && obj.distance >= 0);
|
|
225
|
+
// if (isAllScreens(targets)) {
|
|
226
|
+
// return last(targets);
|
|
227
|
+
// }
|
|
199
228
|
|
|
200
229
|
const match = distances
|
|
201
230
|
.sort((a, b) => a.distance - b.distance) // sorts the distances
|
|
202
231
|
.filter(ignoreMenuIfMultipleTargets)[0]; // filters out multiple targets if necessary // gets the first element
|
|
203
232
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
233
|
+
// console.log("debug_2", "FM getTargetInDirection", {
|
|
234
|
+
// direction: direction?.value,
|
|
235
|
+
// currentNode,
|
|
236
|
+
// targetNodes,
|
|
237
|
+
// targets,
|
|
238
|
+
// comparisonRect,
|
|
239
|
+
// distances,
|
|
240
|
+
// match,
|
|
241
|
+
// });
|
|
242
|
+
|
|
243
|
+
return match?.target;
|
|
213
244
|
}
|
|
214
245
|
|
|
215
246
|
/**
|
|
@@ -300,3 +331,22 @@ export function hasPriorityInDirection(direction, component) {
|
|
|
300
331
|
|
|
301
332
|
return (prioritiseFocusOn & mappedDirection) > 0;
|
|
302
333
|
}
|
|
334
|
+
|
|
335
|
+
export const getNodeBeforeRoot = (node) => {
|
|
336
|
+
if (isNil(node)) {
|
|
337
|
+
return undefined;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
if (node.parent.id === "root") {
|
|
341
|
+
return node;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
return getNodeBeforeRoot(node.parent);
|
|
345
|
+
};
|
|
346
|
+
|
|
347
|
+
export const isOnTheSameScreen = (currentNode, nextNode) => {
|
|
348
|
+
const routeForCurrentNode = getNodeBeforeRoot(currentNode);
|
|
349
|
+
const routeForNextNode = getNodeBeforeRoot(nextNode);
|
|
350
|
+
|
|
351
|
+
return routeForCurrentNode.id === routeForNextNode.id;
|
|
352
|
+
};
|