@applicaster/zapp-react-native-utils 12.0.0-alpha.3977160850 → 12.0.0-alpha.4054240460
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/analyticsUtils/AnalyticsEvents/index.ts +13 -0
- package/analyticsUtils/AnalyticsEvents/searchEvents.ts +21 -0
- package/analyticsUtils/AnalyticsEvents/sendShareEvent.ts +21 -0
- package/analyticsUtils/analyticsPlugins.ts +25 -0
- package/analyticsUtils/events.ts +12 -0
- package/analyticsUtils/index.tsx +12 -0
- package/appUtils/HooksManager/index.js +3 -1
- package/appUtils/focusManager/__tests__/__snapshots__/focusManager.test.js.snap +2 -3
- package/appUtils/focusManager/__tests__/focusManager.test.js +4 -24
- package/appUtils/focusManager/__tests__/index.test.ts +2 -3
- package/appUtils/focusManager/index.ts +76 -57
- package/appUtils/focusManager/treeDataStructure/Utils/index.js +74 -34
- package/appUtils/focusManager/utils/index.ts +100 -0
- package/appUtils/focusManagerAux/index.ts +76 -0
- package/appUtils/focusManagerAux/utils/index.ts +100 -0
- package/appUtils/keyCodes/keys.web.js +8 -0
- package/appUtils/keyCodes/keysByPlatform.js +11 -1
- package/appUtils/platform/const.ts +25 -0
- package/appUtils/platform/index.ts +3 -0
- package/appUtils/platform/platformUtils.ts +279 -0
- package/appUtils/playerManager/conts.ts +5 -0
- package/appUtils/playerManager/index.ts +11 -5
- package/appUtils/playerManager/nativePlayerManager.ts +12 -0
- package/appUtils/playerManager/player.ts +35 -3
- package/appUtils/playerManager/playerFactory.ts +12 -10
- package/appUtils/playerManager/playerNative.ts +14 -14
- package/appUtils/playerManager/usePlayerControllerSetup.tsx +6 -1
- package/arrayUtils/__tests__/isFilledArray.test.ts +63 -0
- package/arrayUtils/index.ts +5 -0
- package/booleanUtils/index.ts +4 -4
- package/cellUtils/__tests__/cellUtils.test.ts +9 -20
- package/cellUtils/index.ts +10 -14
- package/dateUtils/index.ts +11 -0
- package/headersUtils/index.ts +1 -0
- package/inflatedUrlUtils/index.ts +4 -4
- package/keyboardState/index.ts +0 -0
- package/localizationUtils/__tests__/localizationUtils.test.ts +72 -53
- package/localizationUtils/index.ts +13 -22
- package/manifestUtils/defaultManifestConfigurations/generalContent.js +6 -0
- package/manifestUtils/defaultManifestConfigurations/player.js +2 -1
- package/manifestUtils/index.js +5 -1
- package/manifestUtils/secondaryImage/index.js +18 -15
- package/navigationUtils/index.ts +8 -4
- package/numberUtils/__tests__/isLikeNumber.test.ts +18 -0
- package/numberUtils/index.ts +9 -18
- package/package.json +2 -2
- package/playerUtils/configurationGenerator.ts +3 -1
- 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 +10 -1
- package/reactHooks/cell-click/index.ts +12 -12
- package/reactHooks/feed/__tests__/useBuildPipesUrl.test.tsx +30 -70
- package/reactHooks/feed/__tests__/useFeedRefresh.test.tsx +6 -2
- package/reactHooks/feed/__tests__/useInflatedUrl.test.ts +18 -3
- package/reactHooks/feed/useBuildPipesUrl.ts +7 -10
- package/reactHooks/feed/useFeedRefresh.tsx +9 -7
- package/reactHooks/feed/useInflatedUrl.ts +44 -15
- package/reactHooks/layout/__tests__/{index.test.js → index.test.tsx} +44 -29
- package/reactHooks/layout/getDeviceInfo/index.ts +4 -2
- package/reactHooks/layout/types/index.ts +2 -0
- 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 +26 -8
- package/reactHooks/navigation/index.ts +60 -21
- package/reactHooks/resolvers/__tests__/useCellResolver.test.tsx +3 -1
- package/reactHooks/screen/__mocks__/useScreenContext.ts +3 -1
- package/reactHooks/screen/__tests__/useCurrentScreenData.test.tsx +21 -23
- package/reactHooks/screen/index.ts +2 -0
- package/reactHooks/screen/useScreenBackgroundColor.ts +23 -0
- package/reactHooks/screen/useScreenContext.ts +4 -4
- package/reactHooks/screen/useScreenData.ts +1 -1
- package/reactHooks/screen/useTargetScreenData.ts +7 -0
- package/reactHooks/useListenEventBusEvent.ts +1 -4
- package/reactHooks/videoModal/index.ts +0 -3
- package/reactUtils/index.ts +105 -0
- package/screenState/index.ts +0 -8
- package/stringUtils/index.ts +5 -0
- package/testUtils/getByTestId.ts +14 -0
- package/testUtils/index.tsx +38 -23
- package/types.ts +4 -0
|
@@ -7,6 +7,15 @@ import { sendHeaderClickEvent } from "./sendHeaderClickEvent";
|
|
|
7
7
|
import { sendHardwareBackButtonClickEvent } from "./sendHardwareBackButtonClickEvent";
|
|
8
8
|
import { sendDownloadsEvent } from "./sendDownloadsEvent";
|
|
9
9
|
import { sendScreenEvent } from "./sendScreenEvent";
|
|
10
|
+
import {
|
|
11
|
+
sendSearchCanceledEvent,
|
|
12
|
+
sendSearchExecutedEvent,
|
|
13
|
+
} from "./searchEvents";
|
|
14
|
+
|
|
15
|
+
import {
|
|
16
|
+
sendShareInitiatedEvent,
|
|
17
|
+
sendShareSuccessEvent,
|
|
18
|
+
} from "./sendShareEvent";
|
|
10
19
|
|
|
11
20
|
export {
|
|
12
21
|
sendScreenEvent,
|
|
@@ -18,4 +27,8 @@ export {
|
|
|
18
27
|
sendHeaderClickEvent,
|
|
19
28
|
sendHardwareBackButtonClickEvent,
|
|
20
29
|
sendDownloadsEvent,
|
|
30
|
+
sendSearchCanceledEvent,
|
|
31
|
+
sendSearchExecutedEvent,
|
|
32
|
+
sendShareInitiatedEvent,
|
|
33
|
+
sendShareSuccessEvent,
|
|
21
34
|
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { log_debug } from "../logger";
|
|
2
|
+
import { postAnalyticEvent } from "../manager";
|
|
3
|
+
import { ANALYTICS_CORE_EVENTS } from "../events";
|
|
4
|
+
|
|
5
|
+
export const sendSearchCanceledEvent = (analyticsProps) => {
|
|
6
|
+
const eventName = ANALYTICS_CORE_EVENTS.SEARCH_CANCELED;
|
|
7
|
+
postAnalyticEvent(eventName, analyticsProps);
|
|
8
|
+
|
|
9
|
+
log_debug(`sendSearchCanceledEvent: send event: ${eventName}`, {
|
|
10
|
+
analyticsProps,
|
|
11
|
+
});
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export const sendSearchExecutedEvent = (analyticsProps) => {
|
|
15
|
+
const eventName = ANALYTICS_CORE_EVENTS.SEARCH_EXECUTED;
|
|
16
|
+
postAnalyticEvent(eventName, analyticsProps);
|
|
17
|
+
|
|
18
|
+
log_debug(`sendSearchExecutedEvent: send event: ${eventName}`, {
|
|
19
|
+
analyticsProps,
|
|
20
|
+
});
|
|
21
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { log_debug } from "../logger";
|
|
2
|
+
import { postAnalyticEvent } from "../manager";
|
|
3
|
+
import { ANALYTICS_CORE_EVENTS } from "../events";
|
|
4
|
+
|
|
5
|
+
export const sendShareInitiatedEvent = (analyticsProps) => {
|
|
6
|
+
const eventName = ANALYTICS_CORE_EVENTS.SHARE_INITIATED;
|
|
7
|
+
postAnalyticEvent(eventName, analyticsProps);
|
|
8
|
+
|
|
9
|
+
log_debug(`sendShareInitiatedEvent: send event: ${eventName}`, {
|
|
10
|
+
analyticsProps,
|
|
11
|
+
});
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export const sendShareSuccessEvent = (analyticsProps) => {
|
|
15
|
+
const eventName = ANALYTICS_CORE_EVENTS.SHARE_SUCCESS;
|
|
16
|
+
postAnalyticEvent(eventName, analyticsProps);
|
|
17
|
+
|
|
18
|
+
log_debug(`sendShareSuccessEvent: send event: ${eventName}`, {
|
|
19
|
+
analyticsProps,
|
|
20
|
+
});
|
|
21
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import * as R from "ramda";
|
|
2
|
+
import { appStore } from "@applicaster/zapp-react-native-redux/AppStore";
|
|
3
|
+
|
|
4
|
+
export const getVideojsAnalyticPluginsConfig = async (entry: ZappEntry) => {
|
|
5
|
+
let videojsConfiguration = {};
|
|
6
|
+
|
|
7
|
+
const plugins = appStore.get("plugins");
|
|
8
|
+
const analyticPlugins = R.filter(R.propEq("type", "analytics"), plugins);
|
|
9
|
+
|
|
10
|
+
const configurations = await Promise.all(
|
|
11
|
+
analyticPlugins.map(async (plugin) => {
|
|
12
|
+
const { configuration, module } = plugin;
|
|
13
|
+
|
|
14
|
+
return module?.getPlayerConfiguration?.(configuration, entry);
|
|
15
|
+
})
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
configurations.forEach((analyticConfig) => {
|
|
19
|
+
if (typeof analyticConfig === "object" && analyticConfig !== null) {
|
|
20
|
+
videojsConfiguration = { ...videojsConfiguration, ...analyticConfig };
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
return videojsConfiguration;
|
|
25
|
+
};
|
package/analyticsUtils/events.ts
CHANGED
|
@@ -23,10 +23,22 @@ export const TAPPING_EVENTS = {
|
|
|
23
23
|
TAP_NAVBAR_BACK_BUTTON: "tap_navbar_back_button",
|
|
24
24
|
};
|
|
25
25
|
|
|
26
|
+
export const SEARCH_EVENTS = {
|
|
27
|
+
SEARCH_EXECUTED: "search_executed",
|
|
28
|
+
SEARCH_CANCELED: "search_canceled",
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export const SHARE_EVENTS = {
|
|
32
|
+
SHARE_INITIATED: "share_initiated",
|
|
33
|
+
SHARE_SUCCESS: "share_success",
|
|
34
|
+
};
|
|
35
|
+
|
|
26
36
|
export const ANALYTICS_CORE_EVENTS = {
|
|
27
37
|
...LEGACY_EVENTS,
|
|
28
38
|
...SCREEN_VIEW_EVENTS,
|
|
29
39
|
...TAPPING_EVENTS,
|
|
40
|
+
...SEARCH_EVENTS,
|
|
41
|
+
...SHARE_EVENTS,
|
|
30
42
|
};
|
|
31
43
|
|
|
32
44
|
export const WEBVIEW_EVENTS = {
|
package/analyticsUtils/index.tsx
CHANGED
|
@@ -18,6 +18,10 @@ import {
|
|
|
18
18
|
sendHardwareBackButtonClickEvent,
|
|
19
19
|
sendDownloadsEvent,
|
|
20
20
|
sendScreenEvent,
|
|
21
|
+
sendSearchCanceledEvent,
|
|
22
|
+
sendSearchExecutedEvent,
|
|
23
|
+
sendShareInitiatedEvent,
|
|
24
|
+
sendShareSuccessEvent,
|
|
21
25
|
} from "./AnalyticsEvents";
|
|
22
26
|
|
|
23
27
|
import { postAnalyticEvent } from "./manager";
|
|
@@ -66,6 +70,10 @@ type AnalyticsFunctions = {
|
|
|
66
70
|
item: ZappEntry;
|
|
67
71
|
state: OfflineItemState;
|
|
68
72
|
}) => void;
|
|
73
|
+
sendSearchCanceledEvent: (props: AnalyticsDefaultProperties) => void;
|
|
74
|
+
sendSearchExecutedEvent: (props: AnalyticsDefaultProperties) => void;
|
|
75
|
+
sendShareInitiatedEvent: (props: AnalyticsDefaultProperties) => void;
|
|
76
|
+
sendShareSuccessEvent: (props: AnalyticsDefaultProperties) => void;
|
|
69
77
|
};
|
|
70
78
|
|
|
71
79
|
type GetAnalyticsFunctions = (
|
|
@@ -102,6 +110,10 @@ export function getAnalyticsFunctions({
|
|
|
102
110
|
sendHardwareBackButtonClickEvent,
|
|
103
111
|
sendScreenEvent,
|
|
104
112
|
sendDownloadsEvent,
|
|
113
|
+
sendSearchCanceledEvent,
|
|
114
|
+
sendSearchExecutedEvent,
|
|
115
|
+
sendShareInitiatedEvent,
|
|
116
|
+
sendShareSuccessEvent,
|
|
105
117
|
};
|
|
106
118
|
}
|
|
107
119
|
|
|
@@ -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
|
|
@@ -3,9 +3,7 @@
|
|
|
3
3
|
exports[`focusManager should be defined 1`] = `
|
|
4
4
|
Object {
|
|
5
5
|
"blur": [Function],
|
|
6
|
-
"disableAutomaticFocus": [Function],
|
|
7
6
|
"disableFocus": [Function],
|
|
8
|
-
"enableAutomaticFocus": [Function],
|
|
9
7
|
"enableFocus": [Function],
|
|
10
8
|
"focusableTree": Tree {
|
|
11
9
|
"loadingCounter": 0,
|
|
@@ -23,6 +21,7 @@ Object {
|
|
|
23
21
|
"getGroupById": [Function],
|
|
24
22
|
"getGroupRootById": [Function],
|
|
25
23
|
"invokeHandler": [Function],
|
|
24
|
+
"isCurrentFocusOnTheTopScreen": [Function],
|
|
26
25
|
"isFocusDisabled": [Function],
|
|
27
26
|
"isGroupItemFocused": [Function],
|
|
28
27
|
"longPress": [Function],
|
|
@@ -30,11 +29,11 @@ Object {
|
|
|
30
29
|
"on": [Function],
|
|
31
30
|
"press": [Function],
|
|
32
31
|
"pressOut": [Function],
|
|
32
|
+
"recoverFocus": [Function],
|
|
33
33
|
"register": [Function],
|
|
34
34
|
"removeHandler": [Function],
|
|
35
35
|
"setFocus": [Function],
|
|
36
36
|
"setInitialFocus": [Function],
|
|
37
|
-
"setupInitialFocus": [Function],
|
|
38
37
|
"unregister": [Function],
|
|
39
38
|
}
|
|
40
39
|
`;
|
|
@@ -14,10 +14,8 @@ describe("focusManager", () => {
|
|
|
14
14
|
expect(focusManager).toMatchSnapshot();
|
|
15
15
|
});
|
|
16
16
|
|
|
17
|
-
it("
|
|
17
|
+
it("manages to land initial focus", () => {
|
|
18
18
|
const mockSetFocus = jest.fn();
|
|
19
|
-
focusManager.setupInitialFocus("1", true);
|
|
20
|
-
focusManager.enableAutomaticFocus();
|
|
21
19
|
|
|
22
20
|
focusManager.register({
|
|
23
21
|
id: "1",
|
|
@@ -31,31 +29,13 @@ describe("focusManager", () => {
|
|
|
31
29
|
},
|
|
32
30
|
});
|
|
33
31
|
|
|
32
|
+
const { success } = focusManager.setInitialFocus();
|
|
33
|
+
|
|
34
|
+
expect(success).toBe(true);
|
|
34
35
|
expect(mockSetFocus).toBeCalledTimes(1);
|
|
35
36
|
expect(mockSetFocus).toBeCalledWith(null);
|
|
36
37
|
});
|
|
37
38
|
|
|
38
|
-
it("enableAutomaticFocus should block setting focus", () => {
|
|
39
|
-
const mockSetFocus = jest.fn();
|
|
40
|
-
focusManager.setupInitialFocus("1", true);
|
|
41
|
-
focusManager.disableAutomaticFocus();
|
|
42
|
-
|
|
43
|
-
focusManager.register({
|
|
44
|
-
id: "1",
|
|
45
|
-
component: {
|
|
46
|
-
setFocus: mockSetFocus,
|
|
47
|
-
willReceiveFocus: function () {},
|
|
48
|
-
isMounted: function () {
|
|
49
|
-
return true;
|
|
50
|
-
},
|
|
51
|
-
props: { groupId: "121" },
|
|
52
|
-
},
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
expect(mockSetFocus).toBeCalledTimes(0);
|
|
56
|
-
expect(mockSetFocus).not.toBeCalledWith(null);
|
|
57
|
-
});
|
|
58
|
-
|
|
59
39
|
describe("register", () => {});
|
|
60
40
|
describe("unregister", () => {});
|
|
61
41
|
describe("find", () => {});
|
|
@@ -18,7 +18,6 @@ describe("focusManager", () => {
|
|
|
18
18
|
"on",
|
|
19
19
|
"invokeHandler",
|
|
20
20
|
"removeHandler",
|
|
21
|
-
"setupInitialFocus",
|
|
22
21
|
"longPress",
|
|
23
22
|
"pressOut",
|
|
24
23
|
"enableFocus",
|
|
@@ -27,10 +26,10 @@ describe("focusManager", () => {
|
|
|
27
26
|
"isFocusDisabled",
|
|
28
27
|
"getCurrentFocusNode",
|
|
29
28
|
"focusableTree",
|
|
30
|
-
"disableAutomaticFocus",
|
|
31
|
-
"enableAutomaticFocus",
|
|
32
29
|
"getGroupRootById",
|
|
33
30
|
"isGroupItemFocused",
|
|
31
|
+
"recoverFocus",
|
|
32
|
+
"isCurrentFocusOnTheTopScreen",
|
|
34
33
|
];
|
|
35
34
|
|
|
36
35
|
expectedMethods.forEach((method) => {
|
|
@@ -1,10 +1,18 @@
|
|
|
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
|
+
haveSameParentBeforeRoot,
|
|
12
|
+
} from "./treeDataStructure/Utils";
|
|
4
13
|
import { subscriber } from "../../functionUtils";
|
|
5
14
|
import * as FOCUS_EVENTS from "./events";
|
|
6
15
|
import { coreLogger } from "../../logger";
|
|
7
|
-
|
|
8
16
|
const logger = coreLogger.addSubsystem("focusManager");
|
|
9
17
|
|
|
10
18
|
const isFocusEnabled = (focusableItem): boolean => {
|
|
@@ -44,21 +52,9 @@ export const focusManager = (function () {
|
|
|
44
52
|
// focusable node which currently holds the focus
|
|
45
53
|
let currentFocusNode = null;
|
|
46
54
|
let lastFocusedId = null;
|
|
47
|
-
let initialFocusOnContent = null;
|
|
48
55
|
let focusDisabled = false;
|
|
49
|
-
let focusAutoEnabled = true;
|
|
50
56
|
const eventHandler = subscriber();
|
|
51
57
|
|
|
52
|
-
function disableAutomaticFocus() {
|
|
53
|
-
logger.log({ message: "disableAutomaticFocus" });
|
|
54
|
-
focusAutoEnabled = false;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
function enableAutomaticFocus() {
|
|
58
|
-
logger.log({ message: "focusManager: enableAutomaticFocus" });
|
|
59
|
-
focusAutoEnabled = true;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
58
|
function enableFocus() {
|
|
63
59
|
focusDisabled = false;
|
|
64
60
|
}
|
|
@@ -93,7 +89,7 @@ export const focusManager = (function () {
|
|
|
93
89
|
!currentFocusable.isGroup &&
|
|
94
90
|
currentFocusable.isMounted()
|
|
95
91
|
) {
|
|
96
|
-
currentFocusable.blur(
|
|
92
|
+
currentFocusable.blur(direction);
|
|
97
93
|
currentFocusable.hasLostFocus(getCurrentFocus(), direction);
|
|
98
94
|
}
|
|
99
95
|
}
|
|
@@ -226,32 +222,52 @@ export const focusManager = (function () {
|
|
|
226
222
|
|
|
227
223
|
logger.log({ message: "setInitialFocus", data: { preferredFocus } });
|
|
228
224
|
|
|
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
|
-
}
|
|
225
|
+
if (isNilOrEmpty(preferredFocus)) {
|
|
226
|
+
return { success: false };
|
|
227
|
+
}
|
|
242
228
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
data: {
|
|
246
|
-
focusableItem,
|
|
247
|
-
},
|
|
248
|
-
});
|
|
229
|
+
const focusCandidate = preferredFocus[0];
|
|
230
|
+
const focusableItem = focusCandidate.component;
|
|
249
231
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
232
|
+
const currentFocusedId = R.path(["props", "id"], getCurrentFocus());
|
|
233
|
+
const nextFocusedId = R.path(["props", "id"], focusableItem);
|
|
234
|
+
|
|
235
|
+
if (
|
|
236
|
+
isNotNil(currentFocusedId) &&
|
|
237
|
+
isNotNil(nextFocusedId) &&
|
|
238
|
+
currentFocusedId === nextFocusedId
|
|
239
|
+
) {
|
|
240
|
+
// we are going to move focus to the same node(already focused) -> nothing to do
|
|
241
|
+
return { success: true };
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if (focusableItem?.isGroup) {
|
|
245
|
+
// focusCandidate is a FocusableGroup, we are not able to land focus on group(only on Focusable)
|
|
246
|
+
return { success: false };
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if (
|
|
250
|
+
focusableItem &&
|
|
251
|
+
!focusableItem.isGroup &&
|
|
252
|
+
isFocusEnabled(focusableItem)
|
|
253
|
+
) {
|
|
254
|
+
blur();
|
|
255
|
+
|
|
256
|
+
logger.log({
|
|
257
|
+
message: "setInitialFocus – currentFocusNode",
|
|
258
|
+
data: {
|
|
259
|
+
focusableItem,
|
|
260
|
+
},
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
currentFocusNode = focusCandidate;
|
|
264
|
+
focusableItem.willReceiveFocus(focusableItem, null);
|
|
265
|
+
focusableItem && setFocus(focusCandidate.id, null);
|
|
266
|
+
|
|
267
|
+
return { success: true };
|
|
254
268
|
}
|
|
269
|
+
|
|
270
|
+
return { success: false };
|
|
255
271
|
}
|
|
256
272
|
|
|
257
273
|
function treeLoaded(val, node) {
|
|
@@ -261,17 +277,8 @@ export const focusManager = (function () {
|
|
|
261
277
|
val,
|
|
262
278
|
node,
|
|
263
279
|
currentFocusNode,
|
|
264
|
-
initialFocusOnContent,
|
|
265
280
|
},
|
|
266
281
|
});
|
|
267
|
-
|
|
268
|
-
if ((val && !currentFocusNode) || initialFocusOnContent) {
|
|
269
|
-
logger.log({ message: `focusAutoEnabled ${focusAutoEnabled}` });
|
|
270
|
-
|
|
271
|
-
if (focusAutoEnabled) {
|
|
272
|
-
setInitialFocus(initialFocusOnContent ? node : undefined);
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
282
|
}
|
|
276
283
|
|
|
277
284
|
/**
|
|
@@ -402,11 +409,21 @@ export const focusManager = (function () {
|
|
|
402
409
|
group ? unregisterGroup(id) : unregisterItem(id);
|
|
403
410
|
}
|
|
404
411
|
|
|
405
|
-
function
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
412
|
+
function isCurrentFocusOnTheTopScreen() {
|
|
413
|
+
const routes = R.pathOr([], ["root", "children"], focusableTree);
|
|
414
|
+
|
|
415
|
+
if (routes.length <= 1) {
|
|
416
|
+
// there is only one or zero routes
|
|
417
|
+
return true;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
return haveSameParentBeforeRoot(currentFocusNode, R.last(routes));
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
function recoverFocus() {
|
|
424
|
+
if (!isCurrentFocusOnTheTopScreen()) {
|
|
425
|
+
// We've failed to set focused node on the new screen => run focus recovery
|
|
426
|
+
setInitialFocus();
|
|
410
427
|
}
|
|
411
428
|
}
|
|
412
429
|
|
|
@@ -416,6 +433,11 @@ export const focusManager = (function () {
|
|
|
416
433
|
* @param {Object} direction of the scroll
|
|
417
434
|
*/
|
|
418
435
|
function moveFocus(direction) {
|
|
436
|
+
if (!isCurrentFocusOnTheTopScreen()) {
|
|
437
|
+
// We are opening new screen. We need to ignore movement of focus.
|
|
438
|
+
return false;
|
|
439
|
+
}
|
|
440
|
+
|
|
419
441
|
if (focusDisabled) return false;
|
|
420
442
|
|
|
421
443
|
if (currentFocusNode) {
|
|
@@ -444,8 +466,6 @@ export const focusManager = (function () {
|
|
|
444
466
|
} else {
|
|
445
467
|
currentFocusNode.component.failedLostFocus(currentFocusNode, direction);
|
|
446
468
|
}
|
|
447
|
-
} else {
|
|
448
|
-
setInitialFocus();
|
|
449
469
|
}
|
|
450
470
|
}
|
|
451
471
|
|
|
@@ -505,7 +525,6 @@ export const focusManager = (function () {
|
|
|
505
525
|
on,
|
|
506
526
|
invokeHandler,
|
|
507
527
|
removeHandler,
|
|
508
|
-
setupInitialFocus,
|
|
509
528
|
longPress,
|
|
510
529
|
pressOut,
|
|
511
530
|
enableFocus,
|
|
@@ -514,9 +533,9 @@ export const focusManager = (function () {
|
|
|
514
533
|
isFocusDisabled,
|
|
515
534
|
getCurrentFocusNode,
|
|
516
535
|
focusableTree,
|
|
517
|
-
disableAutomaticFocus,
|
|
518
|
-
enableAutomaticFocus,
|
|
519
536
|
getGroupRootById,
|
|
520
537
|
isGroupItemFocused,
|
|
538
|
+
recoverFocus,
|
|
539
|
+
isCurrentFocusOnTheTopScreen,
|
|
521
540
|
};
|
|
522
541
|
})();
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { allPass, pathOr, startsWith, isNil } from "ramda";
|
|
2
2
|
import { isNilOrEmpty } from "@applicaster/zapp-react-native-utils/reactUtils/helpers";
|
|
3
|
+
import { isZero } from "@applicaster/zapp-react-native-utils/numberUtils";
|
|
4
|
+
import { QUICK_BRICK_NAVBAR } from "@applicaster/quick-brick-core/const";
|
|
3
5
|
|
|
4
6
|
import { rejectItemByPath } from "../../../../objectUtils";
|
|
5
7
|
import {
|
|
@@ -143,7 +145,47 @@ const hasChildren = (target) =>
|
|
|
143
145
|
|
|
144
146
|
const hasComponent = (target) => !!target?.component;
|
|
145
147
|
|
|
146
|
-
const hasComponentAndChildren =
|
|
148
|
+
const hasComponentAndChildren = allPass([hasComponent, hasChildren]);
|
|
149
|
+
|
|
150
|
+
const positiveOrZero = (value) => value > 0 || isZero(value);
|
|
151
|
+
|
|
152
|
+
const excludeFromFocusSearching = pathOr(false, [
|
|
153
|
+
"component",
|
|
154
|
+
"props",
|
|
155
|
+
"excludeFromFocusSearching",
|
|
156
|
+
]);
|
|
157
|
+
|
|
158
|
+
const isNavBar = (target) => startsWith(QUICK_BRICK_NAVBAR, target?.id);
|
|
159
|
+
|
|
160
|
+
const ignoreMenuIfMultipleTargets = (nodes) => {
|
|
161
|
+
if (nodes.length > 1) {
|
|
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,18 @@ 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
|
-
}
|
|
189
|
-
|
|
190
|
-
return {
|
|
191
|
-
distance:
|
|
192
|
-
measureDistance(direction, comparisonRect, componentRect) +
|
|
193
|
-
measureEuclidianDistanceToCenter(comparisonRect, componentRect) /
|
|
194
|
-
10000,
|
|
195
|
-
target,
|
|
196
|
-
};
|
|
197
|
-
})
|
|
198
|
-
.filter((obj) => obj && obj.distance >= 0);
|
|
218
|
+
.filter((target) => !excludeFromFocusSearching(target))
|
|
219
|
+
.map((target) => ({
|
|
220
|
+
distance: toDistance({ target, direction, comparisonRect }),
|
|
221
|
+
target,
|
|
222
|
+
}))
|
|
223
|
+
.filter(({ distance }) => positiveOrZero(distance));
|
|
199
224
|
|
|
200
225
|
const match = distances
|
|
201
226
|
.sort((a, b) => a.distance - b.distance) // sorts the distances
|
|
202
227
|
.filter(ignoreMenuIfMultipleTargets)[0]; // filters out multiple targets if necessary // gets the first element
|
|
203
228
|
|
|
204
|
-
return
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
function ignoreMenuIfMultipleTargets(nodes) {
|
|
208
|
-
if (nodes.length > 1 && nodes.map((n) => n?.target?.id).includes("navbar")) {
|
|
209
|
-
return nodes.filter((n) => n?.target?.id !== "navbar");
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
return nodes;
|
|
229
|
+
return match?.target;
|
|
213
230
|
}
|
|
214
231
|
|
|
215
232
|
/**
|
|
@@ -300,3 +317,26 @@ export function hasPriorityInDirection(direction, component) {
|
|
|
300
317
|
|
|
301
318
|
return (prioritiseFocusOn & mappedDirection) > 0;
|
|
302
319
|
}
|
|
320
|
+
|
|
321
|
+
export const getNodeBeforeRoot = (node) => {
|
|
322
|
+
if (isNil(node)) {
|
|
323
|
+
return undefined;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
if (isNil(node.parent)) {
|
|
327
|
+
return undefined;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
if (node.parent.id === "root") {
|
|
331
|
+
return node;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
return getNodeBeforeRoot(node.parent);
|
|
335
|
+
};
|
|
336
|
+
|
|
337
|
+
export const haveSameParentBeforeRoot = (currentNode, nextNode) => {
|
|
338
|
+
const routeForCurrentNode = getNodeBeforeRoot(currentNode);
|
|
339
|
+
const routeForNextNode = getNodeBeforeRoot(nextNode);
|
|
340
|
+
|
|
341
|
+
return routeForCurrentNode?.id === routeForNextNode?.id;
|
|
342
|
+
};
|