@applicaster/zapp-react-native-utils 15.0.0-alpha.1456157166 → 15.0.0-alpha.2239032089
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/appUtils/RiverFocusManager/{index.js → index.ts} +25 -18
- package/appUtils/focusManager/__tests__/__snapshots__/focusManager.test.js.snap +3 -0
- package/appUtils/focusManager/index.ios.ts +37 -2
- package/appUtils/focusManagerAux/utils/index.ios.ts +104 -0
- package/appUtils/focusManagerAux/utils/utils.ios.ts +63 -0
- package/package.json +2 -2
- package/reactHooks/feed/useFeedLoader.tsx +9 -0
- package/reactHooks/feed/useInflatedUrl.ts +29 -23
- package/utils/index.ts +1 -0
|
@@ -1,11 +1,31 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
import {
|
|
1
|
+
import { focusManager } from "@applicaster/zapp-react-native-utils/appUtils/focusManager/index.ios";
|
|
2
|
+
import { QUICK_BRICK_CONTENT } from "@applicaster/quick-brick-core/const";
|
|
3
|
+
import { isNil, isEmpty } from "@applicaster/zapp-react-native-utils/utils";
|
|
4
|
+
import { isNotNil } from "@applicaster/zapp-react-native-utils/reactUtils/helpers";
|
|
4
5
|
|
|
5
6
|
let riverFocusData = {};
|
|
6
7
|
let initialyPresentedScreenFocused = false;
|
|
7
8
|
|
|
8
9
|
export const riverFocusManager = (function () {
|
|
10
|
+
/**
|
|
11
|
+
* Create unique key that will be used for save focused group data inside specific screen
|
|
12
|
+
* @param {{ screenId: string, isInsideContainer: boolean }}
|
|
13
|
+
* screenId Unique Id of the screen from layout.json
|
|
14
|
+
* isInsideContainer If this screen a screen picker child
|
|
15
|
+
*
|
|
16
|
+
*/
|
|
17
|
+
function screenFocusableGroupId({
|
|
18
|
+
screenId,
|
|
19
|
+
isInsideContainer,
|
|
20
|
+
}: {
|
|
21
|
+
screenId: string;
|
|
22
|
+
isInsideContainer: Option<boolean>;
|
|
23
|
+
}) {
|
|
24
|
+
return `${QUICK_BRICK_CONTENT}-${screenId}${
|
|
25
|
+
isNil(isInsideContainer) ? "" : "-isInsideContainer"
|
|
26
|
+
}`;
|
|
27
|
+
}
|
|
28
|
+
|
|
9
29
|
function setScreenFocusableData({
|
|
10
30
|
screenFocusableGroupId,
|
|
11
31
|
groupId,
|
|
@@ -78,8 +98,8 @@ export const riverFocusManager = (function () {
|
|
|
78
98
|
}) {
|
|
79
99
|
// Check if screen should be focused
|
|
80
100
|
const shouldFocus =
|
|
81
|
-
(initialyPresentedScreenFocused === false &&
|
|
82
|
-
|
|
101
|
+
(initialyPresentedScreenFocused === false && isEmpty(riverFocusData)) ||
|
|
102
|
+
isNotNil(riverFocusData[screenFocusableGroupId]) ||
|
|
83
103
|
isDeepLink;
|
|
84
104
|
|
|
85
105
|
// TODO: Uncommit it to start fixing bug where selection wrong item
|
|
@@ -118,19 +138,6 @@ export const riverFocusManager = (function () {
|
|
|
118
138
|
}
|
|
119
139
|
}
|
|
120
140
|
|
|
121
|
-
/**
|
|
122
|
-
* Create unique key that will be used for save focused group data inside specific screen
|
|
123
|
-
* @param {{ screenId: string, isInsideContainer: boolean }}
|
|
124
|
-
* screenId Unique Id of the screen from layout.json
|
|
125
|
-
* isInsideContainer If this screen a screen picker child
|
|
126
|
-
*
|
|
127
|
-
*/
|
|
128
|
-
function screenFocusableGroupId({ screenId, isInsideContainer }) {
|
|
129
|
-
return `RiverFocusableGroup-${screenId}${
|
|
130
|
-
R.isNil(isInsideContainer) ? "" : "-isInsideContainer"
|
|
131
|
-
}`;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
141
|
return {
|
|
135
142
|
setScreenFocusableData,
|
|
136
143
|
clearAllScreensData,
|
|
@@ -70,6 +70,9 @@ exports[`focusManagerIOS should be defined 1`] = `
|
|
|
70
70
|
"getPreferredFocusChild": [Function],
|
|
71
71
|
"invokeHandler": [Function],
|
|
72
72
|
"isFocusOn": [Function],
|
|
73
|
+
"isFocusOnContent": [Function],
|
|
74
|
+
"isFocusOnMenu": [Function],
|
|
75
|
+
"isFocusOnTabsScreenContent": [Function],
|
|
73
76
|
"isGroupItemFocused": [Function],
|
|
74
77
|
"moveFocus": [Function],
|
|
75
78
|
"on": [Function],
|
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
import { NativeModules } from "react-native";
|
|
2
2
|
import * as R from "ramda";
|
|
3
3
|
|
|
4
|
-
import { isCurrentFocusOn } from "../focusManagerAux/utils";
|
|
5
4
|
import { Tree } from "./treeDataStructure/Tree";
|
|
6
5
|
import { findFocusableNode } from "./treeDataStructure/Utils";
|
|
7
6
|
import { subscriber } from "../../functionUtils";
|
|
8
7
|
import { findChild } from "./utils";
|
|
9
8
|
|
|
9
|
+
import {
|
|
10
|
+
isCurrentFocusOn,
|
|
11
|
+
isPartOfMenu,
|
|
12
|
+
isPartOfContent,
|
|
13
|
+
isPartOfTabsScreenContent,
|
|
14
|
+
} from "../focusManagerAux/utils/index.ios";
|
|
15
|
+
|
|
10
16
|
const { FocusableManagerModule } = NativeModules;
|
|
11
17
|
|
|
12
18
|
/**
|
|
@@ -261,7 +267,9 @@ export const focusManager = (function () {
|
|
|
261
267
|
function setFocus(
|
|
262
268
|
id: string,
|
|
263
269
|
direction?: FocusManager.IOS.Direction,
|
|
264
|
-
options?: Partial<{
|
|
270
|
+
options?: Partial<{
|
|
271
|
+
groupFocusedChanged: boolean;
|
|
272
|
+
}>,
|
|
265
273
|
callback?: any
|
|
266
274
|
) {
|
|
267
275
|
blur(direction);
|
|
@@ -400,6 +408,30 @@ export const focusManager = (function () {
|
|
|
400
408
|
return id && isCurrentFocusOn(id, currentFocusNode);
|
|
401
409
|
}
|
|
402
410
|
|
|
411
|
+
function isFocusOnMenu(): boolean {
|
|
412
|
+
const currentFocusable = getCurrentFocus();
|
|
413
|
+
|
|
414
|
+
return isPartOfMenu(focusableTree, currentFocusable?.props?.id);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
function isFocusOnContent(): boolean {
|
|
418
|
+
const currentFocusable = getCurrentFocus();
|
|
419
|
+
|
|
420
|
+
return isPartOfContent(focusableTree, currentFocusable?.props?.id);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
function isFocusOnTabsScreenContent(
|
|
424
|
+
screenPickerContentContainerId: string
|
|
425
|
+
): boolean {
|
|
426
|
+
const currentFocusable = getCurrentFocus();
|
|
427
|
+
|
|
428
|
+
return isPartOfTabsScreenContent(
|
|
429
|
+
focusableTree,
|
|
430
|
+
screenPickerContentContainerId,
|
|
431
|
+
currentFocusable?.props?.id
|
|
432
|
+
);
|
|
433
|
+
}
|
|
434
|
+
|
|
403
435
|
return {
|
|
404
436
|
on,
|
|
405
437
|
invokeHandler,
|
|
@@ -422,5 +454,8 @@ export const focusManager = (function () {
|
|
|
422
454
|
isGroupItemFocused,
|
|
423
455
|
getPreferredFocusChild,
|
|
424
456
|
isFocusOn,
|
|
457
|
+
isFocusOnMenu,
|
|
458
|
+
isFocusOnContent,
|
|
459
|
+
isFocusOnTabsScreenContent,
|
|
425
460
|
};
|
|
426
461
|
})();
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { isNil, startsWith } from "@applicaster/zapp-react-native-utils/utils";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
QUICK_BRICK_CONTENT,
|
|
5
|
+
QUICK_BRICK_NAVBAR,
|
|
6
|
+
} from "@applicaster/quick-brick-core/const";
|
|
7
|
+
|
|
8
|
+
const isNavBar = (node) => startsWith(QUICK_BRICK_NAVBAR, node?.id);
|
|
9
|
+
const isContent = (node) => startsWith(QUICK_BRICK_CONTENT, node?.id);
|
|
10
|
+
const isRoot = (node) => node?.id === "root";
|
|
11
|
+
|
|
12
|
+
export const isPartOfTabsScreenContent = (
|
|
13
|
+
focusableTree,
|
|
14
|
+
screenPickerContentContainerId,
|
|
15
|
+
id
|
|
16
|
+
) => {
|
|
17
|
+
const node = focusableTree.findInTree(id);
|
|
18
|
+
|
|
19
|
+
if (isNil(node)) {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (isRoot(node)) {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (isNavBar(node)) {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (isContent(node)) {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (node?.id === screenPickerContentContainerId) {
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return isPartOfTabsScreenContent(
|
|
40
|
+
focusableTree,
|
|
41
|
+
screenPickerContentContainerId,
|
|
42
|
+
node.parent?.id
|
|
43
|
+
);
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export const isPartOfMenu = (focusableTree, id): boolean => {
|
|
47
|
+
const node = focusableTree.findInTree(id);
|
|
48
|
+
|
|
49
|
+
if (isNil(node)) {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (isRoot(node)) {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (isNavBar(node)) {
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (isContent(node)) {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return isPartOfMenu(focusableTree, node.parent.id);
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
export const isPartOfContent = (focusableTree, id) => {
|
|
69
|
+
const node = focusableTree.findInTree(id);
|
|
70
|
+
|
|
71
|
+
if (isNil(node)) {
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (isRoot(node)) {
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (isNavBar(node)) {
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (isContent(node)) {
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return isPartOfContent(focusableTree, node.parent?.id);
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
export const isCurrentFocusOn = (id, node) => {
|
|
91
|
+
if (!node) {
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (isRoot(node)) {
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (node?.id === id) {
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return isCurrentFocusOn(id, node.parent);
|
|
104
|
+
};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { Subject, ReplaySubject, withLatestFrom } from "rxjs";
|
|
2
|
+
import { filter } from "rxjs/operators";
|
|
3
|
+
|
|
4
|
+
import { isPartOfMenu, isPartOfContent } from "./index.ios";
|
|
5
|
+
|
|
6
|
+
import { focusManager } from "../../focusManager/index.ios";
|
|
7
|
+
|
|
8
|
+
type FocusableID = string;
|
|
9
|
+
const focusedSubject$ = new Subject<FocusableID>();
|
|
10
|
+
|
|
11
|
+
const focused$ = focusedSubject$.asObservable();
|
|
12
|
+
|
|
13
|
+
export const emitFocused = (id: FocusableID): void => {
|
|
14
|
+
focusedSubject$.next(id);
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export const topMenuItemFocused$ = focused$.pipe(
|
|
18
|
+
filter((id) => id && isPartOfMenu(focusManager.focusableTree, id))
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
export const contentFocused$ = focused$.pipe(
|
|
22
|
+
filter((id) => {
|
|
23
|
+
const isContent = isPartOfContent(focusManager.focusableTree, id);
|
|
24
|
+
|
|
25
|
+
return id && isContent;
|
|
26
|
+
})
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
const registeredHomeTopMenuItemSubject$ = new ReplaySubject<FocusableID>(1);
|
|
30
|
+
|
|
31
|
+
export const registeredHomeTopMenuItem$ =
|
|
32
|
+
registeredHomeTopMenuItemSubject$.asObservable();
|
|
33
|
+
|
|
34
|
+
export const homeTopMenuItemFocused$ = topMenuItemFocused$.pipe(
|
|
35
|
+
withLatestFrom(registeredHomeTopMenuItem$),
|
|
36
|
+
filter(([id, homeId]) => id === homeId)
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
export const emitHomeTopMenuItemRegistered = (id) => {
|
|
40
|
+
// save homeId on registration
|
|
41
|
+
registeredHomeTopMenuItemSubject$.next(id);
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export const emitHomeTopMenuItemUnregistered = () => {
|
|
45
|
+
// reset homeId on unregistration
|
|
46
|
+
registeredHomeTopMenuItemSubject$.next(undefined);
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const registeredScreenPickerContentContainerSubject$ =
|
|
50
|
+
new ReplaySubject<FocusableID>(1);
|
|
51
|
+
|
|
52
|
+
export const registeredScreenPickerContentContainer$ =
|
|
53
|
+
registeredScreenPickerContentContainerSubject$.asObservable();
|
|
54
|
+
|
|
55
|
+
export const emitScreenPickerContentContainerRegistered = (id) => {
|
|
56
|
+
// save screenPickerContentContainerId on registration
|
|
57
|
+
registeredScreenPickerContentContainerSubject$.next(id);
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export const emitScreenPickerContentContainerUnregistered = () => {
|
|
61
|
+
// reset screenPickerContentContainerId on unregistration
|
|
62
|
+
registeredScreenPickerContentContainerSubject$.next(undefined);
|
|
63
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@applicaster/zapp-react-native-utils",
|
|
3
|
-
"version": "15.0.0-alpha.
|
|
3
|
+
"version": "15.0.0-alpha.2239032089",
|
|
4
4
|
"description": "Applicaster Zapp React Native utilities package",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
},
|
|
28
28
|
"homepage": "https://github.com/applicaster/quickbrick#readme",
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@applicaster/applicaster-types": "15.0.0-alpha.
|
|
30
|
+
"@applicaster/applicaster-types": "15.0.0-alpha.2239032089",
|
|
31
31
|
"buffer": "^5.2.1",
|
|
32
32
|
"camelize": "^1.0.0",
|
|
33
33
|
"dayjs": "^1.11.10",
|
|
@@ -37,6 +37,15 @@ export const useFeedLoader = ({
|
|
|
37
37
|
mapping,
|
|
38
38
|
pipesOptions = {},
|
|
39
39
|
}: Props): FeedLoaderResponse => {
|
|
40
|
+
useEffect(() => {
|
|
41
|
+
if (!feedUrl) {
|
|
42
|
+
logger.warning({
|
|
43
|
+
message: "Required parameter feedUrl is missing",
|
|
44
|
+
data: { feedUrl },
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
}, []);
|
|
48
|
+
|
|
40
49
|
const isInitialRender = useIsInitialRender();
|
|
41
50
|
|
|
42
51
|
const callableFeedUrl = useInflatedUrl({ feedUrl, mapping });
|
|
@@ -18,7 +18,6 @@ import {
|
|
|
18
18
|
} from "@applicaster/zapp-pipes-v2-client";
|
|
19
19
|
import { appStore } from "@applicaster/zapp-react-native-redux/AppStore";
|
|
20
20
|
import { ENDPOINT_TAGS } from "../../types";
|
|
21
|
-
import { isNilOrEmpty } from "../../reactUtils/helpers";
|
|
22
21
|
|
|
23
22
|
/**
|
|
24
23
|
* will match any occurrence in a string of one or more word characters
|
|
@@ -76,19 +75,15 @@ export const getInflatedDataSourceUrl: GetInflatedDataSourceUrl = ({
|
|
|
76
75
|
* https://foo.com/shows/A1234
|
|
77
76
|
*/
|
|
78
77
|
|
|
79
|
-
if (!
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
}
|
|
78
|
+
if (!source) {
|
|
79
|
+
// eslint-disable-next-line no-console
|
|
80
|
+
console.error("source is empty", {
|
|
81
|
+
source,
|
|
82
|
+
contexts,
|
|
83
|
+
mapping,
|
|
84
|
+
});
|
|
87
85
|
|
|
88
|
-
|
|
89
|
-
}
|
|
90
|
-
} else {
|
|
91
|
-
return source || null;
|
|
86
|
+
return null;
|
|
92
87
|
}
|
|
93
88
|
|
|
94
89
|
// Hack because in tv we expect to get key names instead of values from the fake entry
|
|
@@ -198,17 +193,28 @@ export function useInflatedUrl({
|
|
|
198
193
|
|
|
199
194
|
const url = useMemo(
|
|
200
195
|
() =>
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
196
|
+
mapping
|
|
197
|
+
? getInflatedDataSourceUrl({
|
|
198
|
+
source: feedUrl,
|
|
199
|
+
contexts: {
|
|
200
|
+
entry: entryContext,
|
|
201
|
+
screen: screenContext,
|
|
202
|
+
search: getSearchContext(searchContext, mapping),
|
|
203
|
+
},
|
|
204
|
+
mapping,
|
|
205
|
+
})
|
|
206
|
+
: feedUrl,
|
|
207
|
+
[feedUrl, mapping]
|
|
211
208
|
);
|
|
212
209
|
|
|
210
|
+
if (!feedUrl) {
|
|
211
|
+
logger.warning({
|
|
212
|
+
message: "Required parameter feedUrl is missing",
|
|
213
|
+
data: { feedUrl },
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
return null;
|
|
217
|
+
}
|
|
218
|
+
|
|
213
219
|
return url;
|
|
214
220
|
}
|