@applicaster/zapp-react-native-utils 12.0.0-alpha.4201443043 → 12.0.0-alpha.4324313105
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/focusManager/__tests__/__snapshots__/focusManager.test.js.snap +3 -2
- package/appUtils/focusManager/__tests__/focusManager.test.js +24 -4
- package/appUtils/focusManager/__tests__/index.test.ts +3 -2
- package/appUtils/focusManager/index.ts +56 -76
- package/appUtils/focusManager/treeDataStructure/Utils/index.js +43 -74
- package/arrayUtils/index.ts +0 -5
- package/numberUtils/__tests__/isLikeNumber.test.ts +0 -18
- package/numberUtils/index.ts +18 -5
- package/package.json +2 -2
- package/reactHooks/cell-click/__tests__/index.test.js +5 -9
- package/reactHooks/cell-click/index.ts +11 -11
- package/reactHooks/feed/__tests__/useBuildPipesUrl.test.tsx +70 -30
- package/reactHooks/feed/useBuildPipesUrl.ts +10 -7
- package/reactHooks/navigation/index.ts +1 -32
- package/reactHooks/screen/__tests__/useCurrentScreenData.test.tsx +23 -21
- package/reactHooks/screen/index.ts +0 -2
- package/reactHooks/screen/useScreenContext.ts +2 -1
- package/appUtils/focusManager/utils/index.ts +0 -100
- package/appUtils/focusManagerAux/index.ts +0 -76
- package/appUtils/focusManagerAux/utils/index.ts +0 -100
- package/arrayUtils/__tests__/isFilledArray.test.ts +0 -63
- package/reactHooks/screen/useScreenBackgroundColor.ts +0 -23
|
@@ -3,7 +3,9 @@
|
|
|
3
3
|
exports[`focusManager should be defined 1`] = `
|
|
4
4
|
Object {
|
|
5
5
|
"blur": [Function],
|
|
6
|
+
"disableAutomaticFocus": [Function],
|
|
6
7
|
"disableFocus": [Function],
|
|
8
|
+
"enableAutomaticFocus": [Function],
|
|
7
9
|
"enableFocus": [Function],
|
|
8
10
|
"focusableTree": Tree {
|
|
9
11
|
"loadingCounter": 0,
|
|
@@ -21,7 +23,6 @@ Object {
|
|
|
21
23
|
"getGroupById": [Function],
|
|
22
24
|
"getGroupRootById": [Function],
|
|
23
25
|
"invokeHandler": [Function],
|
|
24
|
-
"isCurrentFocusOnTheTopScreen": [Function],
|
|
25
26
|
"isFocusDisabled": [Function],
|
|
26
27
|
"isGroupItemFocused": [Function],
|
|
27
28
|
"longPress": [Function],
|
|
@@ -29,11 +30,11 @@ Object {
|
|
|
29
30
|
"on": [Function],
|
|
30
31
|
"press": [Function],
|
|
31
32
|
"pressOut": [Function],
|
|
32
|
-
"recoverFocus": [Function],
|
|
33
33
|
"register": [Function],
|
|
34
34
|
"removeHandler": [Function],
|
|
35
35
|
"setFocus": [Function],
|
|
36
36
|
"setInitialFocus": [Function],
|
|
37
|
+
"setupInitialFocus": [Function],
|
|
37
38
|
"unregister": [Function],
|
|
38
39
|
}
|
|
39
40
|
`;
|
|
@@ -14,8 +14,10 @@ describe("focusManager", () => {
|
|
|
14
14
|
expect(focusManager).toMatchSnapshot();
|
|
15
15
|
});
|
|
16
16
|
|
|
17
|
-
it("
|
|
17
|
+
it("enableAutomaticFocus should not block setting focus", () => {
|
|
18
18
|
const mockSetFocus = jest.fn();
|
|
19
|
+
focusManager.setupInitialFocus("1", true);
|
|
20
|
+
focusManager.enableAutomaticFocus();
|
|
19
21
|
|
|
20
22
|
focusManager.register({
|
|
21
23
|
id: "1",
|
|
@@ -29,13 +31,31 @@ describe("focusManager", () => {
|
|
|
29
31
|
},
|
|
30
32
|
});
|
|
31
33
|
|
|
32
|
-
const { success } = focusManager.setInitialFocus();
|
|
33
|
-
|
|
34
|
-
expect(success).toBe(true);
|
|
35
34
|
expect(mockSetFocus).toBeCalledTimes(1);
|
|
36
35
|
expect(mockSetFocus).toBeCalledWith(null);
|
|
37
36
|
});
|
|
38
37
|
|
|
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
|
+
|
|
39
59
|
describe("register", () => {});
|
|
40
60
|
describe("unregister", () => {});
|
|
41
61
|
describe("find", () => {});
|
|
@@ -18,6 +18,7 @@ describe("focusManager", () => {
|
|
|
18
18
|
"on",
|
|
19
19
|
"invokeHandler",
|
|
20
20
|
"removeHandler",
|
|
21
|
+
"setupInitialFocus",
|
|
21
22
|
"longPress",
|
|
22
23
|
"pressOut",
|
|
23
24
|
"enableFocus",
|
|
@@ -26,10 +27,10 @@ describe("focusManager", () => {
|
|
|
26
27
|
"isFocusDisabled",
|
|
27
28
|
"getCurrentFocusNode",
|
|
28
29
|
"focusableTree",
|
|
30
|
+
"disableAutomaticFocus",
|
|
31
|
+
"enableAutomaticFocus",
|
|
29
32
|
"getGroupRootById",
|
|
30
33
|
"isGroupItemFocused",
|
|
31
|
-
"recoverFocus",
|
|
32
|
-
"isCurrentFocusOnTheTopScreen",
|
|
33
34
|
];
|
|
34
35
|
|
|
35
36
|
expectedMethods.forEach((method) => {
|
|
@@ -1,15 +1,6 @@
|
|
|
1
1
|
import * as R from "ramda";
|
|
2
|
-
import {
|
|
3
|
-
isNilOrEmpty,
|
|
4
|
-
isNotNil,
|
|
5
|
-
} from "@applicaster/zapp-react-native-utils/reactUtils/helpers";
|
|
6
|
-
|
|
7
2
|
import { Tree } from "./treeDataStructure/Tree";
|
|
8
|
-
import {
|
|
9
|
-
findFocusableNode,
|
|
10
|
-
findPriorityItem,
|
|
11
|
-
haveSameParentBeforeRoot,
|
|
12
|
-
} from "./treeDataStructure/Utils";
|
|
3
|
+
import { findFocusableNode, findPriorityItem } from "./treeDataStructure/Utils";
|
|
13
4
|
import { subscriber } from "../../functionUtils";
|
|
14
5
|
import * as FOCUS_EVENTS from "./events";
|
|
15
6
|
import { coreLogger } from "../../logger";
|
|
@@ -53,9 +44,21 @@ export const focusManager = (function () {
|
|
|
53
44
|
// focusable node which currently holds the focus
|
|
54
45
|
let currentFocusNode = null;
|
|
55
46
|
let lastFocusedId = null;
|
|
47
|
+
let initialFocusOnContent = null;
|
|
56
48
|
let focusDisabled = false;
|
|
49
|
+
let focusAutoEnabled = true;
|
|
57
50
|
const eventHandler = subscriber();
|
|
58
51
|
|
|
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
|
+
|
|
59
62
|
function enableFocus() {
|
|
60
63
|
focusDisabled = false;
|
|
61
64
|
}
|
|
@@ -90,7 +93,7 @@ export const focusManager = (function () {
|
|
|
90
93
|
!currentFocusable.isGroup &&
|
|
91
94
|
currentFocusable.isMounted()
|
|
92
95
|
) {
|
|
93
|
-
currentFocusable.blur(direction);
|
|
96
|
+
currentFocusable.blur(currentFocusable, direction);
|
|
94
97
|
currentFocusable.hasLostFocus(getCurrentFocus(), direction);
|
|
95
98
|
}
|
|
96
99
|
}
|
|
@@ -223,52 +226,32 @@ export const focusManager = (function () {
|
|
|
223
226
|
|
|
224
227
|
logger.log({ message: "setInitialFocus", data: { preferredFocus } });
|
|
225
228
|
|
|
226
|
-
if (
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
currentFocusedId === nextFocusedId
|
|
240
|
-
) {
|
|
241
|
-
// we are going to move focus to the same node(already focused) -> nothing to do
|
|
242
|
-
return { success: true };
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
if (focusableItem?.isGroup) {
|
|
246
|
-
// focusCandidate is a FocusableGroup, we are not able to land focus on group(only on Focusable)
|
|
247
|
-
return { success: false };
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
if (
|
|
251
|
-
focusableItem &&
|
|
252
|
-
!focusableItem.isGroup &&
|
|
253
|
-
isFocusEnabled(focusableItem)
|
|
254
|
-
) {
|
|
255
|
-
blur();
|
|
256
|
-
|
|
257
|
-
logger.log({
|
|
258
|
-
message: "setInitialFocus – currentFocusNode",
|
|
259
|
-
data: {
|
|
260
|
-
focusableItem,
|
|
261
|
-
},
|
|
262
|
-
});
|
|
229
|
+
if (preferredFocus && preferredFocus.length > 0) {
|
|
230
|
+
const focusCandidate = preferredFocus[0];
|
|
231
|
+
const focusableItem = focusCandidate.component;
|
|
232
|
+
|
|
233
|
+
if (
|
|
234
|
+
focusableItem &&
|
|
235
|
+
!focusableItem.isGroup &&
|
|
236
|
+
isFocusEnabled(focusableItem)
|
|
237
|
+
) {
|
|
238
|
+
if (initialFocusOnContent) {
|
|
239
|
+
blur();
|
|
240
|
+
initialFocusOnContent = false;
|
|
241
|
+
}
|
|
263
242
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
243
|
+
logger.log({
|
|
244
|
+
message: "setInitialFocus – currentFocusNode",
|
|
245
|
+
data: {
|
|
246
|
+
focusableItem,
|
|
247
|
+
},
|
|
248
|
+
});
|
|
267
249
|
|
|
268
|
-
|
|
250
|
+
currentFocusNode = focusCandidate;
|
|
251
|
+
focusableItem.willReceiveFocus(focusableItem, null);
|
|
252
|
+
focusableItem && setFocus(focusCandidate.id, null);
|
|
253
|
+
}
|
|
269
254
|
}
|
|
270
|
-
|
|
271
|
-
return { success: false };
|
|
272
255
|
}
|
|
273
256
|
|
|
274
257
|
function treeLoaded(val, node) {
|
|
@@ -278,8 +261,17 @@ export const focusManager = (function () {
|
|
|
278
261
|
val,
|
|
279
262
|
node,
|
|
280
263
|
currentFocusNode,
|
|
264
|
+
initialFocusOnContent,
|
|
281
265
|
},
|
|
282
266
|
});
|
|
267
|
+
|
|
268
|
+
if ((val && !currentFocusNode) || initialFocusOnContent) {
|
|
269
|
+
logger.log({ message: `focusAutoEnabled ${focusAutoEnabled}` });
|
|
270
|
+
|
|
271
|
+
if (focusAutoEnabled) {
|
|
272
|
+
setInitialFocus(initialFocusOnContent ? node : undefined);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
283
275
|
}
|
|
284
276
|
|
|
285
277
|
/**
|
|
@@ -410,21 +402,11 @@ export const focusManager = (function () {
|
|
|
410
402
|
group ? unregisterGroup(id) : unregisterItem(id);
|
|
411
403
|
}
|
|
412
404
|
|
|
413
|
-
function
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
return true;
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
return haveSameParentBeforeRoot(currentFocusNode, R.last(routes));
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
function recoverFocus() {
|
|
425
|
-
if (!isCurrentFocusOnTheTopScreen()) {
|
|
426
|
-
// We've failed to set focused node on the new screen => run focus recovery
|
|
427
|
-
setInitialFocus();
|
|
405
|
+
function setupInitialFocus(id: string, focusOnContent?: boolean) {
|
|
406
|
+
if (focusOnContent) {
|
|
407
|
+
initialFocusOnContent = focusOnContent;
|
|
408
|
+
} else {
|
|
409
|
+
setFocus(id);
|
|
428
410
|
}
|
|
429
411
|
}
|
|
430
412
|
|
|
@@ -434,11 +416,6 @@ export const focusManager = (function () {
|
|
|
434
416
|
* @param {Object} direction of the scroll
|
|
435
417
|
*/
|
|
436
418
|
function moveFocus(direction) {
|
|
437
|
-
if (!isCurrentFocusOnTheTopScreen()) {
|
|
438
|
-
// We are opening new screen. We need to ignore movement of focus.
|
|
439
|
-
return false;
|
|
440
|
-
}
|
|
441
|
-
|
|
442
419
|
if (focusDisabled) return false;
|
|
443
420
|
|
|
444
421
|
if (currentFocusNode) {
|
|
@@ -467,6 +444,8 @@ export const focusManager = (function () {
|
|
|
467
444
|
} else {
|
|
468
445
|
currentFocusNode.component.failedLostFocus(currentFocusNode, direction);
|
|
469
446
|
}
|
|
447
|
+
} else {
|
|
448
|
+
setInitialFocus();
|
|
470
449
|
}
|
|
471
450
|
}
|
|
472
451
|
|
|
@@ -526,6 +505,7 @@ export const focusManager = (function () {
|
|
|
526
505
|
on,
|
|
527
506
|
invokeHandler,
|
|
528
507
|
removeHandler,
|
|
508
|
+
setupInitialFocus,
|
|
529
509
|
longPress,
|
|
530
510
|
pressOut,
|
|
531
511
|
enableFocus,
|
|
@@ -534,9 +514,9 @@ export const focusManager = (function () {
|
|
|
534
514
|
isFocusDisabled,
|
|
535
515
|
getCurrentFocusNode,
|
|
536
516
|
focusableTree,
|
|
517
|
+
disableAutomaticFocus,
|
|
518
|
+
enableAutomaticFocus,
|
|
537
519
|
getGroupRootById,
|
|
538
520
|
isGroupItemFocused,
|
|
539
|
-
recoverFocus,
|
|
540
|
-
isCurrentFocusOnTheTopScreen,
|
|
541
521
|
};
|
|
542
522
|
})();
|
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as R 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";
|
|
5
3
|
|
|
6
4
|
import { rejectItemByPath } from "../../../../objectUtils";
|
|
7
5
|
import {
|
|
@@ -145,47 +143,7 @@ const hasChildren = (target) =>
|
|
|
145
143
|
|
|
146
144
|
const hasComponent = (target) => !!target?.component;
|
|
147
145
|
|
|
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
|
-
};
|
|
146
|
+
const hasComponentAndChildren = R.allPass([hasComponent, hasChildren]);
|
|
189
147
|
|
|
190
148
|
/**
|
|
191
149
|
* Get target in shortest distance in specified direction
|
|
@@ -215,18 +173,52 @@ export function getTargetInDirection(
|
|
|
215
173
|
|
|
216
174
|
const distances = targets
|
|
217
175
|
.filter(hasComponentAndChildren)
|
|
218
|
-
.
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
176
|
+
.map((target) => {
|
|
177
|
+
const componentRect = target.component?.getRect();
|
|
178
|
+
|
|
179
|
+
const distance = measureDistance(
|
|
180
|
+
direction,
|
|
181
|
+
comparisonRect,
|
|
182
|
+
componentRect
|
|
183
|
+
);
|
|
184
|
+
|
|
185
|
+
if (distance === -1) {
|
|
186
|
+
// ignores offscreen targets when moving up/down
|
|
187
|
+
return null;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const orientationDistance = measureDistance(
|
|
191
|
+
direction,
|
|
192
|
+
comparisonRect,
|
|
193
|
+
componentRect
|
|
194
|
+
);
|
|
195
|
+
|
|
196
|
+
const euclidDistance = measureEuclidianDistanceToCenter(
|
|
197
|
+
comparisonRect,
|
|
198
|
+
componentRect
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
return {
|
|
202
|
+
distance:
|
|
203
|
+
orientationDistance > 0 ? euclidDistance : orientationDistance,
|
|
204
|
+
target,
|
|
205
|
+
};
|
|
206
|
+
})
|
|
207
|
+
.filter((obj) => obj && obj.distance >= 0);
|
|
224
208
|
|
|
225
209
|
const match = distances
|
|
226
210
|
.sort((a, b) => a.distance - b.distance) // sorts the distances
|
|
227
211
|
.filter(ignoreMenuIfMultipleTargets)[0]; // filters out multiple targets if necessary // gets the first element
|
|
228
212
|
|
|
229
|
-
return match
|
|
213
|
+
return distances && distances.length > 0 ? match.target : null;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function ignoreMenuIfMultipleTargets(nodes) {
|
|
217
|
+
if (nodes.length > 1 && nodes.map((n) => n?.target?.id).includes("navbar")) {
|
|
218
|
+
return nodes.filter((n) => n?.target?.id !== "navbar");
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
return nodes;
|
|
230
222
|
}
|
|
231
223
|
|
|
232
224
|
/**
|
|
@@ -317,26 +309,3 @@ export function hasPriorityInDirection(direction, component) {
|
|
|
317
309
|
|
|
318
310
|
return (prioritiseFocusOn & mappedDirection) > 0;
|
|
319
311
|
}
|
|
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
|
-
};
|
package/arrayUtils/index.ts
CHANGED
|
@@ -95,8 +95,3 @@ export const makeListOfIndexes = (size: number): number[] =>
|
|
|
95
95
|
export const makeListOf = (value: unknown, size: number): number[] => {
|
|
96
96
|
return Array(size).fill(value);
|
|
97
97
|
};
|
|
98
|
-
|
|
99
|
-
/** Checks if a value is a non-empty array */
|
|
100
|
-
export function isFilledArray(value: unknown): boolean {
|
|
101
|
-
return R.is(Array, value) && R.length(value) > 0;
|
|
102
|
-
}
|
|
@@ -7,24 +7,12 @@ describe("isLikeNumber", () => {
|
|
|
7
7
|
expect(isLikeNumber(value)).toBe(true);
|
|
8
8
|
});
|
|
9
9
|
|
|
10
|
-
it("value is a number", () => {
|
|
11
|
-
const value = 123.45;
|
|
12
|
-
|
|
13
|
-
expect(isLikeNumber(value)).toBe(true);
|
|
14
|
-
});
|
|
15
|
-
|
|
16
10
|
it("value is a string as number", () => {
|
|
17
11
|
const value = "123";
|
|
18
12
|
|
|
19
13
|
expect(isLikeNumber(value)).toBe(true);
|
|
20
14
|
});
|
|
21
15
|
|
|
22
|
-
it("value is a string as number", () => {
|
|
23
|
-
const value = "123.45";
|
|
24
|
-
|
|
25
|
-
expect(isLikeNumber(value)).toBe(true);
|
|
26
|
-
});
|
|
27
|
-
|
|
28
16
|
it("value is empty string", () => {
|
|
29
17
|
const value = "";
|
|
30
18
|
|
|
@@ -66,10 +54,4 @@ describe("isLikeNumber", () => {
|
|
|
66
54
|
|
|
67
55
|
expect(isLikeNumber(value)).toBe(false);
|
|
68
56
|
});
|
|
69
|
-
|
|
70
|
-
it("NaN is not a number", () => {
|
|
71
|
-
const value = NaN;
|
|
72
|
-
|
|
73
|
-
expect(isLikeNumber(value)).toBe(false);
|
|
74
|
-
});
|
|
75
57
|
});
|
package/numberUtils/index.ts
CHANGED
|
@@ -50,14 +50,27 @@ export const toFiniteNumberWithDefault = (
|
|
|
50
50
|
return possibleNumber;
|
|
51
51
|
};
|
|
52
52
|
|
|
53
|
-
export const isLikeNumber = (value:
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
if (R.isNil(possibleNumber)) {
|
|
53
|
+
export const isLikeNumber = (value: any): boolean => {
|
|
54
|
+
if (R.isEmpty(value)) {
|
|
57
55
|
return false;
|
|
58
56
|
}
|
|
59
57
|
|
|
60
|
-
|
|
58
|
+
if (R.is(Number, value)) {
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (R.is(String, value)) {
|
|
63
|
+
const onlyDigit = value
|
|
64
|
+
.split("")
|
|
65
|
+
.filter((char) =>
|
|
66
|
+
["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"].includes(char)
|
|
67
|
+
)
|
|
68
|
+
.join("");
|
|
69
|
+
|
|
70
|
+
return value === onlyDigit;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return false;
|
|
61
74
|
};
|
|
62
75
|
|
|
63
76
|
export const isZero = (value: unknown): boolean =>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@applicaster/zapp-react-native-utils",
|
|
3
|
-
"version": "12.0.0-alpha.
|
|
3
|
+
"version": "12.0.0-alpha.4324313105",
|
|
4
4
|
"description": "Applicaster Zapp React Native utilities package",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
},
|
|
29
29
|
"homepage": "https://github.com/applicaster/quickbrick#readme",
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@applicaster/applicaster-types": "12.0.0-alpha.
|
|
31
|
+
"@applicaster/applicaster-types": "12.0.0-alpha.4324313105",
|
|
32
32
|
"buffer": "^5.2.1",
|
|
33
33
|
"camelize": "^1.0.0",
|
|
34
34
|
"create-react-context": "^0.2.3",
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
2
|
import { cleanup, renderHook } from "@testing-library/react-hooks";
|
|
3
3
|
import { CellTapContext } from "@applicaster/zapp-react-native-ui-components/Contexts/CellTapContext";
|
|
4
|
-
import { ActionExecutorContext } from "@applicaster/zapp-react-native-utils/actionsExecutor/ActionExecutorContext";
|
|
5
|
-
|
|
6
4
|
import { waitFor } from "@testing-library/react-native";
|
|
7
5
|
|
|
8
6
|
const navigator = {
|
|
@@ -31,6 +29,10 @@ jest.mock("@applicaster/zapp-react-native-ui-components/Contexts", () => ({
|
|
|
31
29
|
},
|
|
32
30
|
}));
|
|
33
31
|
|
|
32
|
+
jest.mock("@applicaster/zapp-react-native-utils/reactHooks/screen", () => ({
|
|
33
|
+
useScreenContext: jest.fn(() => navigator.data),
|
|
34
|
+
}));
|
|
35
|
+
|
|
34
36
|
const { useCellClick } = require("../");
|
|
35
37
|
|
|
36
38
|
const mockProps = {
|
|
@@ -45,15 +47,9 @@ const mockProps = {
|
|
|
45
47
|
|
|
46
48
|
const onCellTap = jest.fn();
|
|
47
49
|
|
|
48
|
-
const actionExecutor = {
|
|
49
|
-
handleEntryActions: jest.fn(() => Promise.resolve()),
|
|
50
|
-
};
|
|
51
|
-
|
|
52
50
|
const wrapper = ({ children }) => (
|
|
53
51
|
<CellTapContext.Provider value={onCellTap}>
|
|
54
|
-
|
|
55
|
-
{children}
|
|
56
|
-
</ActionExecutorContext.Provider>
|
|
52
|
+
{children}
|
|
57
53
|
</CellTapContext.Provider>
|
|
58
54
|
);
|
|
59
55
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/// <reference types="@applicaster/applicaster-types" />
|
|
2
2
|
|
|
3
3
|
import * as React from "react";
|
|
4
|
+
import { useContext } from "react";
|
|
4
5
|
import * as R from "ramda";
|
|
5
6
|
import { handleActionSchemeUrl } from "@applicaster/quick-brick-core/App/DeepLinking/URLSchemeHandler/SchemeHandlerHooks/useUrlSchemeHandler";
|
|
6
7
|
import { CellTapContext } from "@applicaster/zapp-react-native-ui-components/Contexts/CellTapContext";
|
|
@@ -11,9 +12,8 @@ import {
|
|
|
11
12
|
} from "@applicaster/zapp-react-native-utils/reactHooks/navigation";
|
|
12
13
|
import { ZappPipesEntryContext } from "@applicaster/zapp-react-native-ui-components/Contexts";
|
|
13
14
|
import { toBooleanWithDefaultTrue } from "@applicaster/zapp-react-native-utils/booleanUtils";
|
|
14
|
-
import {
|
|
15
|
-
|
|
16
|
-
import { isFunction, noop } from "../../functionUtils";
|
|
15
|
+
import { noop } from "../../functionUtils";
|
|
16
|
+
import { ActionExecutorContext } from "../../actionsExecutor/ActionExecutorContext";
|
|
17
17
|
import { useSendAnalyticsOnPress } from "../analytics";
|
|
18
18
|
import { logOnPress, warnEmptyContentType } from "./helpers";
|
|
19
19
|
|
|
@@ -30,12 +30,12 @@ type Props = {
|
|
|
30
30
|
zappPipesData?: ZappPipesData;
|
|
31
31
|
};
|
|
32
32
|
|
|
33
|
-
export const useCellClick = (
|
|
33
|
+
export const useCellClick = (props: Props) => {
|
|
34
|
+
const { component, zappPipesData } = props;
|
|
34
35
|
const { push, currentRoute } = useNavigation();
|
|
35
36
|
const { pathname } = useRoute();
|
|
36
|
-
|
|
37
|
-
const
|
|
38
|
-
const actionExecutor = React.useContext(ActionExecutorContext);
|
|
37
|
+
const onCellTap = React.useContext(CellTapContext);
|
|
38
|
+
const actionExecutor = useContext(ActionExecutorContext);
|
|
39
39
|
|
|
40
40
|
const cellSelectable = toBooleanWithDefaultTrue(
|
|
41
41
|
component?.rules?.component_cells_selectable
|
|
@@ -47,7 +47,7 @@ export const useCellClick = ({ component, zappPipesData, item }: Props) => {
|
|
|
47
47
|
const logTimestamp = useProfilerLogging();
|
|
48
48
|
|
|
49
49
|
const sendAnalyticsOnPress = useSendAnalyticsOnPress(
|
|
50
|
-
item,
|
|
50
|
+
props.item,
|
|
51
51
|
component,
|
|
52
52
|
zappPipesData
|
|
53
53
|
);
|
|
@@ -56,7 +56,7 @@ export const useCellClick = ({ component, zappPipesData, item }: Props) => {
|
|
|
56
56
|
async (_item?: ZappEntry | any, _index?: number) => {
|
|
57
57
|
// Making sure we are not getting GestureResponderEvent in here.
|
|
58
58
|
const isZappEntry = _item?.id && _item?.type;
|
|
59
|
-
const selectedItem = isZappEntry ? _item : item;
|
|
59
|
+
const selectedItem = isZappEntry ? _item : props.item;
|
|
60
60
|
setEntryContext?.(selectedItem);
|
|
61
61
|
|
|
62
62
|
if (!selectedItem?.type?.value) {
|
|
@@ -80,8 +80,8 @@ export const useCellClick = ({ component, zappPipesData, item }: Props) => {
|
|
|
80
80
|
});
|
|
81
81
|
}
|
|
82
82
|
} else {
|
|
83
|
-
if (
|
|
84
|
-
onCellTap
|
|
83
|
+
if (typeof onCellTap === "function") {
|
|
84
|
+
onCellTap(selectedItem, _index);
|
|
85
85
|
} else {
|
|
86
86
|
if (currentRoute === pathname || pathname.includes("video-modal")) {
|
|
87
87
|
const targetScreen = component?.data?.target;
|
|
@@ -1,26 +1,77 @@
|
|
|
1
|
+
import React from "react";
|
|
1
2
|
import { renderHook } from "@testing-library/react-hooks";
|
|
3
|
+
import configureStore from "redux-mock-store";
|
|
4
|
+
import thunk from "redux-thunk";
|
|
5
|
+
import { Provider } from "react-redux";
|
|
6
|
+
|
|
2
7
|
import { encodeParams } from "@applicaster/zapp-pipes-v2-client/src/utils";
|
|
3
8
|
|
|
9
|
+
const mockStore = configureStore([thunk]);
|
|
10
|
+
|
|
11
|
+
jest.useFakeTimers();
|
|
12
|
+
|
|
4
13
|
const appData = {
|
|
5
14
|
platform: "foo",
|
|
6
15
|
version_name: "1.2.0",
|
|
7
16
|
sdk_version: "2.0.0",
|
|
8
17
|
};
|
|
9
18
|
|
|
10
|
-
|
|
19
|
+
const pipesEndpoints = {
|
|
20
|
+
"http://foo.com/show": {
|
|
21
|
+
context_obj: [
|
|
22
|
+
{ type: "ctx", key: "platform" },
|
|
23
|
+
{ type: "query", mapper: "version", key: "appVersion" },
|
|
24
|
+
{ type: "header", mapper: "SDK", key: "sdkVersion" },
|
|
25
|
+
],
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const store_mock = { appData, pipesEndpoints };
|
|
30
|
+
|
|
31
|
+
const defaultStore = mockStore({
|
|
32
|
+
pipesEndpoints,
|
|
33
|
+
appData,
|
|
34
|
+
});
|
|
11
35
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
() => ({
|
|
15
|
-
useInflatedUrl: () => INFLATED_URL,
|
|
16
|
-
})
|
|
36
|
+
const wrapper: React.FC<any> = ({ children, store }) => (
|
|
37
|
+
<Provider store={store}>{children}</Provider>
|
|
17
38
|
);
|
|
18
39
|
|
|
40
|
+
const navigator = {
|
|
41
|
+
currentRoute: "/river/A1234",
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
jest.mock("../../navigation", () => ({
|
|
45
|
+
useNavigation: jest.fn(() => navigator),
|
|
46
|
+
useRoute: jest.fn(() => ({})),
|
|
47
|
+
}));
|
|
48
|
+
|
|
49
|
+
const contexts = {
|
|
50
|
+
entry: { id: "show-1" },
|
|
51
|
+
search: {},
|
|
52
|
+
screen: {},
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const contextMock = (type) => ({
|
|
56
|
+
useZappPipesContext: () => [contexts[type] || {}],
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
jest.mock("@applicaster/zapp-react-native-ui-components/Contexts", () => ({
|
|
60
|
+
ZappPipesEntryContext: contextMock("entry"),
|
|
61
|
+
ZappPipesSearchContext: contextMock("search"),
|
|
62
|
+
ZappPipesScreenContext: contextMock("screen"),
|
|
63
|
+
}));
|
|
64
|
+
|
|
65
|
+
jest.mock("@applicaster/zapp-react-native-redux/AppStore", () => ({
|
|
66
|
+
appStore: {
|
|
67
|
+
get: jest.fn((prop) => store_mock[prop]),
|
|
68
|
+
},
|
|
69
|
+
}));
|
|
70
|
+
|
|
19
71
|
const { useBuildPipesUrl } = require("../useBuildPipesUrl");
|
|
20
72
|
|
|
21
73
|
describe("useBuildPipesUrl", () => {
|
|
22
74
|
it("returns the proper url", async () => {
|
|
23
|
-
INFLATED_URL = "http://foo.com/show/show-1";
|
|
24
75
|
const url = "http://foo.com/show/{{showId}}";
|
|
25
76
|
|
|
26
77
|
const mapping = {
|
|
@@ -30,20 +81,12 @@ describe("useBuildPipesUrl", () => {
|
|
|
30
81
|
},
|
|
31
82
|
};
|
|
32
83
|
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
const mockedRequestBuilder = {
|
|
42
|
-
build: jest.fn(() => Promise.resolve(buildResult)),
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
const { result, waitForNextUpdate } = renderHook(() =>
|
|
46
|
-
useBuildPipesUrl({ url, mapping, requestBuilder: mockedRequestBuilder })
|
|
84
|
+
const { result, waitForNextUpdate } = renderHook(
|
|
85
|
+
() => useBuildPipesUrl({ url, mapping }),
|
|
86
|
+
{
|
|
87
|
+
wrapper,
|
|
88
|
+
initialProps: { store: defaultStore },
|
|
89
|
+
}
|
|
47
90
|
);
|
|
48
91
|
|
|
49
92
|
expect(result.current.url).toBe("http://foo.com/show/show-1");
|
|
@@ -65,7 +108,6 @@ describe("useBuildPipesUrl", () => {
|
|
|
65
108
|
});
|
|
66
109
|
|
|
67
110
|
it("returns empty object if url is not in endpoints", async () => {
|
|
68
|
-
INFLATED_URL = "http://foobar.com/show-1";
|
|
69
111
|
const url = "http://foobar.com/{{showId}}";
|
|
70
112
|
|
|
71
113
|
const mapping = {
|
|
@@ -75,14 +117,12 @@ describe("useBuildPipesUrl", () => {
|
|
|
75
117
|
},
|
|
76
118
|
};
|
|
77
119
|
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
const { result, waitForNextUpdate } = renderHook(() =>
|
|
85
|
-
useBuildPipesUrl({ url, mapping, requestBuilder: mockedRequestBuilder })
|
|
120
|
+
const { result, waitForNextUpdate } = renderHook(
|
|
121
|
+
() => useBuildPipesUrl({ url, mapping }),
|
|
122
|
+
{
|
|
123
|
+
wrapper,
|
|
124
|
+
initialProps: { store: defaultStore },
|
|
125
|
+
}
|
|
86
126
|
);
|
|
87
127
|
|
|
88
128
|
expect(result.current.url).toBe("http://foobar.com/show-1");
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
// @ts-ignore
|
|
1
2
|
import { RequestBuilder } from "@applicaster/zapp-pipes-v2-client";
|
|
2
|
-
import
|
|
3
|
-
import { useInflatedUrl } from "
|
|
3
|
+
import { useEffect, useState } from "react";
|
|
4
|
+
import { useInflatedUrl } from "./useInflatedUrl";
|
|
4
5
|
import { reactHooksLogger } from "../logger";
|
|
5
6
|
|
|
6
7
|
export const logger = reactHooksLogger.addSubsystem("build-pipes-url");
|
|
@@ -14,16 +15,18 @@ type PipesBuildRequestObject = {
|
|
|
14
15
|
export function useBuildPipesUrl({
|
|
15
16
|
url,
|
|
16
17
|
mapping = {},
|
|
17
|
-
requestBuilder = new RequestBuilder(),
|
|
18
18
|
}): PipesBuildRequestObject {
|
|
19
|
-
const [requestParams, setRequestParams] =
|
|
19
|
+
const [requestParams, setRequestParams] = useState<object | null>(null);
|
|
20
|
+
const [error, setError] = useState(null);
|
|
20
21
|
|
|
21
|
-
const [error, setError] = React.useState(null);
|
|
22
22
|
const inflatedUrl = useInflatedUrl({ feedUrl: url, mapping });
|
|
23
23
|
|
|
24
|
+
const requestBuilder = new RequestBuilder();
|
|
25
|
+
|
|
24
26
|
const buildRequest = async (_url) => {
|
|
25
27
|
try {
|
|
26
28
|
const result = await requestBuilder.build(_url);
|
|
29
|
+
|
|
27
30
|
setRequestParams(result);
|
|
28
31
|
} catch (e) {
|
|
29
32
|
setError(e);
|
|
@@ -31,7 +34,7 @@ export function useBuildPipesUrl({
|
|
|
31
34
|
}
|
|
32
35
|
};
|
|
33
36
|
|
|
34
|
-
|
|
37
|
+
useEffect(() => {
|
|
35
38
|
if (inflatedUrl && !requestParams) {
|
|
36
39
|
buildRequest(inflatedUrl);
|
|
37
40
|
}
|
|
@@ -39,7 +42,7 @@ export function useBuildPipesUrl({
|
|
|
39
42
|
|
|
40
43
|
return {
|
|
41
44
|
url: inflatedUrl || "",
|
|
42
|
-
requestParams, // TODO should it be: || {} ?
|
|
45
|
+
requestParams: requestParams, // TODO should it be: || {} ?
|
|
43
46
|
error: error || undefined,
|
|
44
47
|
};
|
|
45
48
|
}
|
|
@@ -13,11 +13,7 @@ import {
|
|
|
13
13
|
import { HooksManager } from "@applicaster/zapp-react-native-utils/appUtils/HooksManager";
|
|
14
14
|
|
|
15
15
|
import { legacyScreenData } from "@applicaster/quick-brick-core/App/NavigationProvider/utils";
|
|
16
|
-
import {
|
|
17
|
-
LONG_KEY_PRESS_TIMEOUT,
|
|
18
|
-
QUICK_BRICK_NAVBAR,
|
|
19
|
-
QUICK_BRICK_CONTENT,
|
|
20
|
-
} from "@applicaster/quick-brick-core/const";
|
|
16
|
+
import { LONG_KEY_PRESS_TIMEOUT } from "@applicaster/quick-brick-core/const";
|
|
21
17
|
import { useZappHooksModalState } from "@applicaster/zapp-react-dom-ui-components/Components/ZappHooksModal/ZappHooksModal";
|
|
22
18
|
import { ZappHookModalContext } from "@applicaster/zapp-react-native-ui-components/Contexts";
|
|
23
19
|
import { PathnameContext } from "@applicaster/zapp-react-native-ui-components/Contexts/PathnameContext";
|
|
@@ -456,30 +452,3 @@ export const useIsScreenActive = () => {
|
|
|
456
452
|
|
|
457
453
|
return pathname === currentRoute;
|
|
458
454
|
};
|
|
459
|
-
|
|
460
|
-
type Return = (prefix: string) => string;
|
|
461
|
-
|
|
462
|
-
export const useUniqueRouteSuffix = (): Return => {
|
|
463
|
-
const pathname = usePathname();
|
|
464
|
-
const { currentRoute } = useNavigation();
|
|
465
|
-
|
|
466
|
-
const suffix = `currentRoute:${currentRoute}_pathname:${pathname}`;
|
|
467
|
-
|
|
468
|
-
const withUniqueRouteSuffix = React.useCallback((prefix: string) => {
|
|
469
|
-
return `${prefix}___${suffix}`;
|
|
470
|
-
}, []);
|
|
471
|
-
|
|
472
|
-
return withUniqueRouteSuffix;
|
|
473
|
-
};
|
|
474
|
-
|
|
475
|
-
export const useNavbarId = () => {
|
|
476
|
-
const withUniqueRouteSuffix = useUniqueRouteSuffix();
|
|
477
|
-
|
|
478
|
-
return withUniqueRouteSuffix(QUICK_BRICK_NAVBAR);
|
|
479
|
-
};
|
|
480
|
-
|
|
481
|
-
export const useContentId = () => {
|
|
482
|
-
const withUniqueRouteSuffix = useUniqueRouteSuffix();
|
|
483
|
-
|
|
484
|
-
return withUniqueRouteSuffix(QUICK_BRICK_CONTENT);
|
|
485
|
-
};
|
|
@@ -3,28 +3,23 @@ import { Provider } from "react-redux";
|
|
|
3
3
|
import { renderHook } from "@testing-library/react-hooks";
|
|
4
4
|
import configureStore from "redux-mock-store";
|
|
5
5
|
import thunk from "redux-thunk";
|
|
6
|
+
import { NavigationContext } from "@applicaster/zapp-react-native-ui-components/Contexts/NavigationContext";
|
|
6
7
|
|
|
7
8
|
const mockStore = configureStore([thunk]);
|
|
8
9
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
}),
|
|
23
|
-
}));
|
|
24
|
-
|
|
25
|
-
const { useCurrentScreenData } = require("../useCurrentScreenData");
|
|
26
|
-
|
|
27
|
-
describe("useScreenData", () => {
|
|
10
|
+
import { useCurrentScreenData } from "../useCurrentScreenData";
|
|
11
|
+
|
|
12
|
+
describe("useScreenData", function () {
|
|
13
|
+
const river_id_2: ZappRiver = {
|
|
14
|
+
name: "River 2",
|
|
15
|
+
id: "A1234",
|
|
16
|
+
home: false,
|
|
17
|
+
home_offline: false,
|
|
18
|
+
supports_offline: false,
|
|
19
|
+
navigations: [],
|
|
20
|
+
hooks: { preload_plugins: [] },
|
|
21
|
+
};
|
|
22
|
+
|
|
28
23
|
const river_id_1 = { id: "B5678" };
|
|
29
24
|
|
|
30
25
|
const store = mockStore({
|
|
@@ -32,10 +27,17 @@ describe("useScreenData", () => {
|
|
|
32
27
|
});
|
|
33
28
|
|
|
34
29
|
const wrapper: React.FC<any> = ({ children }) => (
|
|
35
|
-
<Provider
|
|
30
|
+
<NavigationContext.Provider
|
|
31
|
+
value={{
|
|
32
|
+
currentRoute: "/river/river_id_1/river/river_id_2",
|
|
33
|
+
data: { screen: river_id_2 },
|
|
34
|
+
}}
|
|
35
|
+
>
|
|
36
|
+
<Provider store={store}>{children}</Provider>
|
|
37
|
+
</NavigationContext.Provider>
|
|
36
38
|
);
|
|
37
39
|
|
|
38
|
-
it("should return river data for the current screen/route", ()
|
|
40
|
+
it("should return river data for the current screen/route", function () {
|
|
39
41
|
const { result } = renderHook(() => useCurrentScreenData(), { wrapper });
|
|
40
42
|
|
|
41
43
|
expect(result.current).toBe(river_id_2);
|
|
@@ -45,7 +45,8 @@ export function useSetNavbarState() {
|
|
|
45
45
|
|
|
46
46
|
export function useScreenContext(): NavigationScreenData {
|
|
47
47
|
const navigator = useNavigation();
|
|
48
|
-
|
|
48
|
+
// Cover cases where screenContext is used before navigation is initialized
|
|
49
|
+
const screenData = useContext(ScreenDataContext) || navigator.data;
|
|
49
50
|
|
|
50
51
|
const modalScreenData = navigator?.modalData;
|
|
51
52
|
|
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
import { isNotNil } from "@applicaster/zapp-react-native-utils/reactUtils/helpers";
|
|
2
|
-
import { find, last, startsWith, pathOr } from "ramda";
|
|
3
|
-
import {
|
|
4
|
-
QUICK_BRICK_NAVBAR,
|
|
5
|
-
QUICK_BRICK_CONTENT,
|
|
6
|
-
} from "@applicaster/quick-brick-core/const";
|
|
7
|
-
|
|
8
|
-
// run check each 300 ms
|
|
9
|
-
// run this check too often could lead to performance penalty on low-end devices
|
|
10
|
-
const HOW_OFTEN_TO_CHECK_CONDITION = 300; // ms
|
|
11
|
-
|
|
12
|
-
type Props = {
|
|
13
|
-
maxTimeout: number;
|
|
14
|
-
conditionFn: () => boolean;
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
export const waitUntil = ({ maxTimeout, conditionFn }: Props) => {
|
|
18
|
-
let interval;
|
|
19
|
-
let timeout;
|
|
20
|
-
|
|
21
|
-
return new Promise((resolve, reject) => {
|
|
22
|
-
// run interval check
|
|
23
|
-
interval = setInterval(() => {
|
|
24
|
-
if (conditionFn()) {
|
|
25
|
-
clearInterval(interval);
|
|
26
|
-
clearTimeout(timeout);
|
|
27
|
-
|
|
28
|
-
resolve(undefined);
|
|
29
|
-
}
|
|
30
|
-
}, HOW_OFTEN_TO_CHECK_CONDITION);
|
|
31
|
-
|
|
32
|
-
// max timeout when we need to stop interval check
|
|
33
|
-
timeout = setTimeout(() => {
|
|
34
|
-
clearInterval(interval);
|
|
35
|
-
clearTimeout(timeout);
|
|
36
|
-
|
|
37
|
-
const error = Error(
|
|
38
|
-
"Failed to wait satisfy condition. Max timeout is reached."
|
|
39
|
-
);
|
|
40
|
-
|
|
41
|
-
reject(error);
|
|
42
|
-
}, maxTimeout);
|
|
43
|
-
});
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
export const waitForActiveScreen = (currentRoute: string, focusableTree) => {
|
|
47
|
-
const isActiveScreenAvailableToFocus = (): boolean => {
|
|
48
|
-
const routes = focusableTree.root.children;
|
|
49
|
-
|
|
50
|
-
const route = find((route) => route.id === currentRoute, routes);
|
|
51
|
-
|
|
52
|
-
return isNotNil(route);
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
return waitUntil({
|
|
56
|
-
maxTimeout: HOW_OFTEN_TO_CHECK_CONDITION * 10, // ms
|
|
57
|
-
conditionFn: isActiveScreenAvailableToFocus,
|
|
58
|
-
});
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
const getActiveScreenChildren = (focusableTree) => {
|
|
62
|
-
return pathOr([], ["children"], last(focusableTree.root.children));
|
|
63
|
-
};
|
|
64
|
-
|
|
65
|
-
export const getContentNode = (focusableTree) => {
|
|
66
|
-
const content = find(
|
|
67
|
-
(child) =>
|
|
68
|
-
startsWith(QUICK_BRICK_CONTENT, child.id) ||
|
|
69
|
-
startsWith("player", child.id),
|
|
70
|
-
getActiveScreenChildren(focusableTree)
|
|
71
|
-
);
|
|
72
|
-
|
|
73
|
-
return content;
|
|
74
|
-
};
|
|
75
|
-
|
|
76
|
-
export const getNavbarNode = (focusableTree) => {
|
|
77
|
-
const navbar = find(
|
|
78
|
-
(child) => startsWith(QUICK_BRICK_NAVBAR, child.id),
|
|
79
|
-
getActiveScreenChildren(focusableTree)
|
|
80
|
-
);
|
|
81
|
-
|
|
82
|
-
return navbar;
|
|
83
|
-
};
|
|
84
|
-
|
|
85
|
-
export const waitForContent = (focusableTree) => {
|
|
86
|
-
const contentHasAnyChildren = (): boolean => {
|
|
87
|
-
const countOfChildren = pathOr(
|
|
88
|
-
0,
|
|
89
|
-
["children", "length"],
|
|
90
|
-
getContentNode(focusableTree)
|
|
91
|
-
);
|
|
92
|
-
|
|
93
|
-
return countOfChildren > 0;
|
|
94
|
-
};
|
|
95
|
-
|
|
96
|
-
return waitUntil({
|
|
97
|
-
maxTimeout: HOW_OFTEN_TO_CHECK_CONDITION * 10, // ms
|
|
98
|
-
conditionFn: contentHasAnyChildren,
|
|
99
|
-
});
|
|
100
|
-
};
|
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
import { focusManager } from "@applicaster/zapp-react-native-utils/appUtils/focusManager";
|
|
2
|
-
import { find, path } from "ramda";
|
|
3
|
-
|
|
4
|
-
import {
|
|
5
|
-
waitForActiveScreen,
|
|
6
|
-
waitForContent,
|
|
7
|
-
getContentNode,
|
|
8
|
-
getNavbarNode,
|
|
9
|
-
} from "./utils";
|
|
10
|
-
|
|
11
|
-
import { coreLogger } from "../../logger";
|
|
12
|
-
|
|
13
|
-
const logger = coreLogger.addSubsystem("focusManagerAux");
|
|
14
|
-
|
|
15
|
-
export async function setFocusOnMenu(currentRoute: string) {
|
|
16
|
-
const { setFocus, focusableTree } = focusManager;
|
|
17
|
-
|
|
18
|
-
try {
|
|
19
|
-
await waitForActiveScreen(currentRoute, focusableTree);
|
|
20
|
-
|
|
21
|
-
const navbar = getNavbarNode(focusableTree);
|
|
22
|
-
|
|
23
|
-
const selectedMenuItemId = find(
|
|
24
|
-
(child) => child.component.props.selected,
|
|
25
|
-
navbar.children
|
|
26
|
-
)?.id;
|
|
27
|
-
|
|
28
|
-
if (selectedMenuItemId) {
|
|
29
|
-
// set focus on selected menu item
|
|
30
|
-
setFocus(selectedMenuItemId);
|
|
31
|
-
|
|
32
|
-
return;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
const firstMenuItemId = path(["children", 0, "id"], navbar);
|
|
36
|
-
|
|
37
|
-
if (firstMenuItemId) {
|
|
38
|
-
// set focus on first menu item
|
|
39
|
-
setFocus(firstMenuItemId);
|
|
40
|
-
}
|
|
41
|
-
} catch (error) {
|
|
42
|
-
logger.log({
|
|
43
|
-
message: "setFocusOnMenu – failed to put focus on top-menu",
|
|
44
|
-
data: {
|
|
45
|
-
currentRoute,
|
|
46
|
-
error,
|
|
47
|
-
},
|
|
48
|
-
});
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export async function setFocusOnContent(currentRoute: string) {
|
|
53
|
-
// we need to put focus on content here
|
|
54
|
-
// For doing it we have to wait registration of first focusable node into focusableTree
|
|
55
|
-
// we wait until content node starts having children
|
|
56
|
-
|
|
57
|
-
const { setInitialFocus, focusableTree } = focusManager;
|
|
58
|
-
|
|
59
|
-
try {
|
|
60
|
-
await waitForActiveScreen(currentRoute, focusableTree);
|
|
61
|
-
await waitForContent(focusableTree);
|
|
62
|
-
|
|
63
|
-
const content = getContentNode(focusableTree);
|
|
64
|
-
|
|
65
|
-
const { success } = setInitialFocus(content);
|
|
66
|
-
|
|
67
|
-
if (!success) {
|
|
68
|
-
// we've failed to put initial focus on content, re-run "setFocusOnContent" again as a fallback
|
|
69
|
-
throw Error(
|
|
70
|
-
"we've failed to put initial focus on content, re-run 'setFocusOnContent' again as a fallback"
|
|
71
|
-
);
|
|
72
|
-
}
|
|
73
|
-
} catch (error) {
|
|
74
|
-
setFocusOnContent(currentRoute);
|
|
75
|
-
}
|
|
76
|
-
}
|
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
import { isNotNil } from "@applicaster/zapp-react-native-utils/reactUtils/helpers";
|
|
2
|
-
import { find, last, startsWith, pathOr } from "ramda";
|
|
3
|
-
import {
|
|
4
|
-
QUICK_BRICK_NAVBAR,
|
|
5
|
-
QUICK_BRICK_CONTENT,
|
|
6
|
-
} from "@applicaster/quick-brick-core/const";
|
|
7
|
-
|
|
8
|
-
// run check each 300 ms
|
|
9
|
-
// run this check too often could lead to performance penalty on low-end devices
|
|
10
|
-
const HOW_OFTEN_TO_CHECK_CONDITION = 300; // ms
|
|
11
|
-
|
|
12
|
-
type Props = {
|
|
13
|
-
maxTimeout: number;
|
|
14
|
-
conditionFn: () => boolean;
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
export const waitUntil = ({ maxTimeout, conditionFn }: Props) => {
|
|
18
|
-
let interval;
|
|
19
|
-
let timeout;
|
|
20
|
-
|
|
21
|
-
return new Promise((resolve, reject) => {
|
|
22
|
-
// run interval check
|
|
23
|
-
interval = setInterval(() => {
|
|
24
|
-
if (conditionFn()) {
|
|
25
|
-
clearInterval(interval);
|
|
26
|
-
clearTimeout(timeout);
|
|
27
|
-
|
|
28
|
-
resolve(undefined);
|
|
29
|
-
}
|
|
30
|
-
}, HOW_OFTEN_TO_CHECK_CONDITION);
|
|
31
|
-
|
|
32
|
-
// max timeout when we need to stop interval check
|
|
33
|
-
timeout = setTimeout(() => {
|
|
34
|
-
clearInterval(interval);
|
|
35
|
-
clearTimeout(timeout);
|
|
36
|
-
|
|
37
|
-
const error = Error(
|
|
38
|
-
"Failed to wait satisfy condition. Max timeout is reached."
|
|
39
|
-
);
|
|
40
|
-
|
|
41
|
-
reject(error);
|
|
42
|
-
}, maxTimeout);
|
|
43
|
-
});
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
export const waitForActiveScreen = (currentRoute: string, focusableTree) => {
|
|
47
|
-
const isActiveScreenAvailableToFocus = (): boolean => {
|
|
48
|
-
const routes = focusableTree.root.children;
|
|
49
|
-
|
|
50
|
-
const route = find((route) => route.id === currentRoute, routes);
|
|
51
|
-
|
|
52
|
-
return isNotNil(route);
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
return waitUntil({
|
|
56
|
-
maxTimeout: HOW_OFTEN_TO_CHECK_CONDITION * 10, // ms
|
|
57
|
-
conditionFn: isActiveScreenAvailableToFocus,
|
|
58
|
-
});
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
const getActiveScreenChildren = (focusableTree) => {
|
|
62
|
-
return pathOr([], ["children"], last(focusableTree.root.children));
|
|
63
|
-
};
|
|
64
|
-
|
|
65
|
-
export const getContentNode = (focusableTree) => {
|
|
66
|
-
const content = find(
|
|
67
|
-
(child) =>
|
|
68
|
-
startsWith(QUICK_BRICK_CONTENT, child.id) ||
|
|
69
|
-
startsWith("player", child.id),
|
|
70
|
-
getActiveScreenChildren(focusableTree)
|
|
71
|
-
);
|
|
72
|
-
|
|
73
|
-
return content;
|
|
74
|
-
};
|
|
75
|
-
|
|
76
|
-
export const getNavbarNode = (focusableTree) => {
|
|
77
|
-
const navbar = find(
|
|
78
|
-
(child) => startsWith(QUICK_BRICK_NAVBAR, child.id),
|
|
79
|
-
getActiveScreenChildren(focusableTree)
|
|
80
|
-
);
|
|
81
|
-
|
|
82
|
-
return navbar;
|
|
83
|
-
};
|
|
84
|
-
|
|
85
|
-
export const waitForContent = (focusableTree) => {
|
|
86
|
-
const contentHasAnyChildren = (): boolean => {
|
|
87
|
-
const countOfChildren = pathOr(
|
|
88
|
-
0,
|
|
89
|
-
["children", "length"],
|
|
90
|
-
getContentNode(focusableTree)
|
|
91
|
-
);
|
|
92
|
-
|
|
93
|
-
return countOfChildren > 0;
|
|
94
|
-
};
|
|
95
|
-
|
|
96
|
-
return waitUntil({
|
|
97
|
-
maxTimeout: HOW_OFTEN_TO_CHECK_CONDITION * 10, // ms
|
|
98
|
-
conditionFn: contentHasAnyChildren,
|
|
99
|
-
});
|
|
100
|
-
};
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
import { isFilledArray } from "..";
|
|
2
|
-
|
|
3
|
-
describe("isFilledArray", () => {
|
|
4
|
-
it("non-empty array is not empty", () => {
|
|
5
|
-
const value = [1, 2, 3];
|
|
6
|
-
|
|
7
|
-
expect(isFilledArray(value)).toBe(true);
|
|
8
|
-
});
|
|
9
|
-
|
|
10
|
-
it("empty array is empty", () => {
|
|
11
|
-
const value = [];
|
|
12
|
-
|
|
13
|
-
expect(isFilledArray(value)).toBe(false);
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
it("number is not array", () => {
|
|
17
|
-
const value = 123;
|
|
18
|
-
|
|
19
|
-
expect(isFilledArray(value)).toBe(false);
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
it("string is not array", () => {
|
|
23
|
-
const value = "vfnjdk";
|
|
24
|
-
|
|
25
|
-
expect(isFilledArray(value)).toBe(false);
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
it("empty string is not array", () => {
|
|
29
|
-
const value = "";
|
|
30
|
-
|
|
31
|
-
expect(isFilledArray(value)).toBe(false);
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
it("NaN is not array", () => {
|
|
35
|
-
const value = "";
|
|
36
|
-
|
|
37
|
-
expect(isFilledArray(value)).toBe(false);
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
it("object is not array", () => {
|
|
41
|
-
const value = { test: 1 };
|
|
42
|
-
|
|
43
|
-
expect(isFilledArray(value)).toBe(false);
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
it("empty object is not array", () => {
|
|
47
|
-
const value = {};
|
|
48
|
-
|
|
49
|
-
expect(isFilledArray(value)).toBe(false);
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
it("undefined is not array", () => {
|
|
53
|
-
const value = undefined;
|
|
54
|
-
|
|
55
|
-
expect(isFilledArray(value)).toBe(false);
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
it("null is not array", () => {
|
|
59
|
-
const value = null;
|
|
60
|
-
|
|
61
|
-
expect(isFilledArray(value)).toBe(false);
|
|
62
|
-
});
|
|
63
|
-
});
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import * as React from "react";
|
|
2
|
-
import { useTheme } from "@applicaster/zapp-react-native-utils/theme";
|
|
3
|
-
import { useScreenConfiguration } from "@applicaster/zapp-react-native-ui-components/Components/River/useScreenConfiguration";
|
|
4
|
-
import { ifEmptyUseFallback } from "@applicaster/zapp-react-native-utils/cellUtils";
|
|
5
|
-
|
|
6
|
-
export const useScreenBackgroundColor = (screenId: string): string => {
|
|
7
|
-
const { backgroundColor: screenBackgroundColor } =
|
|
8
|
-
useScreenConfiguration(screenId);
|
|
9
|
-
|
|
10
|
-
const theme = useTheme();
|
|
11
|
-
|
|
12
|
-
const themeBackgroundColor = React.useMemo(
|
|
13
|
-
() => theme.app_background_color,
|
|
14
|
-
[theme.app_background_color]
|
|
15
|
-
);
|
|
16
|
-
|
|
17
|
-
const backgroundColor = ifEmptyUseFallback(
|
|
18
|
-
screenBackgroundColor,
|
|
19
|
-
themeBackgroundColor
|
|
20
|
-
);
|
|
21
|
-
|
|
22
|
-
return backgroundColor;
|
|
23
|
-
};
|