@applicaster/zapp-react-native-utils 12.0.0-alpha.5135101838 → 12.0.0-alpha.5208030571
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/index.ts +124 -25
- package/appUtils/focusManager/treeDataStructure/Utils/index.js +97 -42
- package/appUtils/focusManager/utils/index.ts +65 -0
- package/appUtils/playerManager/playerNative.ts +4 -1
- package/package.json +2 -2
- package/reactHooks/navigation/__mocks__/index.ts +13 -0
- package/reactHooks/navigation/__mocks__/useNavigation.ts +3 -0
- package/reactHooks/navigation/index.ts +15 -0
- package/reactHooks/screen/useScreenContext.ts +4 -3
- package/reactUtils/helpers.tsx +1 -1
- package/screenState/index.ts +0 -8
- package/stringUtils/index.ts +5 -0
- package/testUtils/getByTestId.ts +14 -0
- package/testUtils/index.tsx +24 -22
|
@@ -1,9 +1,20 @@
|
|
|
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
8
|
import { findFocusableNode, findPriorityItem } from "./treeDataStructure/Utils";
|
|
4
9
|
import { subscriber } from "../../functionUtils";
|
|
5
10
|
import * as FOCUS_EVENTS from "./events";
|
|
6
11
|
import { coreLogger } from "../../logger";
|
|
12
|
+
import {
|
|
13
|
+
waitForActiveScreen,
|
|
14
|
+
waitForContent,
|
|
15
|
+
getContentNode,
|
|
16
|
+
getActiveScreen,
|
|
17
|
+
} from "./utils";
|
|
7
18
|
|
|
8
19
|
const logger = coreLogger.addSubsystem("focusManager");
|
|
9
20
|
|
|
@@ -46,7 +57,7 @@ export const focusManager = (function () {
|
|
|
46
57
|
let lastFocusedId = null;
|
|
47
58
|
let initialFocusOnContent = null;
|
|
48
59
|
let focusDisabled = false;
|
|
49
|
-
let focusAutoEnabled =
|
|
60
|
+
let focusAutoEnabled = false;
|
|
50
61
|
const eventHandler = subscriber();
|
|
51
62
|
|
|
52
63
|
function disableAutomaticFocus() {
|
|
@@ -93,7 +104,7 @@ export const focusManager = (function () {
|
|
|
93
104
|
!currentFocusable.isGroup &&
|
|
94
105
|
currentFocusable.isMounted()
|
|
95
106
|
) {
|
|
96
|
-
currentFocusable.blur(
|
|
107
|
+
currentFocusable.blur(direction);
|
|
97
108
|
currentFocusable.hasLostFocus(getCurrentFocus(), direction);
|
|
98
109
|
}
|
|
99
110
|
}
|
|
@@ -219,38 +230,55 @@ export const focusManager = (function () {
|
|
|
219
230
|
/**
|
|
220
231
|
* sets the initial focus when the screen loads, or when focus is lost
|
|
221
232
|
*/
|
|
222
|
-
function setInitialFocus(lastAddedParentNode?: any) {
|
|
233
|
+
function setInitialFocus(lastAddedParentNode?: any, fallback?: () => void) {
|
|
223
234
|
const preferredFocus = findPriorityItem(
|
|
224
235
|
lastAddedParentNode?.children || focusableTree.root.children
|
|
225
236
|
);
|
|
226
237
|
|
|
227
238
|
logger.log({ message: "setInitialFocus", data: { preferredFocus } });
|
|
228
239
|
|
|
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
|
-
}
|
|
240
|
+
if (isNilOrEmpty(preferredFocus)) {
|
|
241
|
+
fallback?.();
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
242
244
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
data: {
|
|
246
|
-
focusableItem,
|
|
247
|
-
},
|
|
248
|
-
});
|
|
245
|
+
const focusCandidate = preferredFocus[0];
|
|
246
|
+
const focusableItem = focusCandidate.component;
|
|
249
247
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
248
|
+
const currentFocusedId = R.path(["props", "id"], getCurrentFocus());
|
|
249
|
+
const nextFocusedId = R.path(["props", "id"], focusableItem);
|
|
250
|
+
|
|
251
|
+
if (currentFocusedId === nextFocusedId) {
|
|
252
|
+
// we are going to move focus to the same node(already focused) -> nothing to do
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
if (preferredFocus.length === 1 && focusableItem && focusableItem.isGroup) {
|
|
257
|
+
// we've found only one FocusableGroup as a candidate to focus, try to use fallback
|
|
258
|
+
fallback?.();
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
if (
|
|
263
|
+
focusableItem &&
|
|
264
|
+
!focusableItem.isGroup &&
|
|
265
|
+
isFocusEnabled(focusableItem)
|
|
266
|
+
) {
|
|
267
|
+
if (initialFocusOnContent) {
|
|
268
|
+
blur();
|
|
269
|
+
initialFocusOnContent = false;
|
|
253
270
|
}
|
|
271
|
+
|
|
272
|
+
logger.log({
|
|
273
|
+
message: "setInitialFocus – currentFocusNode",
|
|
274
|
+
data: {
|
|
275
|
+
focusableItem,
|
|
276
|
+
},
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
currentFocusNode = focusCandidate;
|
|
280
|
+
focusableItem.willReceiveFocus(focusableItem, null);
|
|
281
|
+
focusableItem && setFocus(focusCandidate.id, null);
|
|
254
282
|
}
|
|
255
283
|
}
|
|
256
284
|
|
|
@@ -323,6 +351,8 @@ export const focusManager = (function () {
|
|
|
323
351
|
* @returns {React.Component} the registered component
|
|
324
352
|
*/
|
|
325
353
|
function registerGroup(id, component) {
|
|
354
|
+
// console.log("debug_2", "FM registerGroup", { id, component });
|
|
355
|
+
|
|
326
356
|
focusableTree.addNode({ id, component });
|
|
327
357
|
|
|
328
358
|
return component;
|
|
@@ -338,6 +368,8 @@ export const focusManager = (function () {
|
|
|
338
368
|
* @returns {React.Component} the registered component
|
|
339
369
|
*/
|
|
340
370
|
function registerItem(id, component) {
|
|
371
|
+
// console.log("debug_2", "FM registerItem", { id, component });
|
|
372
|
+
|
|
341
373
|
focusableTree.addNode({ id, component });
|
|
342
374
|
|
|
343
375
|
if (!currentFocusNode && id === lastFocusedId && !id.includes("menu")) {
|
|
@@ -354,6 +386,8 @@ export const focusManager = (function () {
|
|
|
354
386
|
* @param {String} id of the group to unregister
|
|
355
387
|
*/
|
|
356
388
|
function unregisterGroup(id) {
|
|
389
|
+
// console.log("debug_2", "FM unregisterGroup", { id });
|
|
390
|
+
|
|
357
391
|
focusableTree.findAndRemoveNode(id);
|
|
358
392
|
}
|
|
359
393
|
|
|
@@ -365,6 +399,8 @@ export const focusManager = (function () {
|
|
|
365
399
|
* @param {String} id of the Focusable to unregister
|
|
366
400
|
*/
|
|
367
401
|
function unregisterItem(id) {
|
|
402
|
+
// console.log("debug_2", "FM unregisterItem", { id });
|
|
403
|
+
|
|
368
404
|
focusableTree.findAndRemoveNode(id);
|
|
369
405
|
|
|
370
406
|
if (currentFocusNode && currentFocusNode.id === id) {
|
|
@@ -403,6 +439,8 @@ export const focusManager = (function () {
|
|
|
403
439
|
}
|
|
404
440
|
|
|
405
441
|
function setupInitialFocus(id: string, focusOnContent?: boolean) {
|
|
442
|
+
console.log("debug_2", "FM - setupInitialFocus", { id, focusOnContent });
|
|
443
|
+
|
|
406
444
|
if (focusOnContent) {
|
|
407
445
|
initialFocusOnContent = focusOnContent;
|
|
408
446
|
} else {
|
|
@@ -410,6 +448,58 @@ export const focusManager = (function () {
|
|
|
410
448
|
}
|
|
411
449
|
}
|
|
412
450
|
|
|
451
|
+
async function setFocusOnMenu(currentRoute: string) {
|
|
452
|
+
try {
|
|
453
|
+
await waitForActiveScreen(currentRoute, focusableTree);
|
|
454
|
+
|
|
455
|
+
const navbar = R.find(
|
|
456
|
+
(child) => R.startsWith("navbar", child.id),
|
|
457
|
+
getActiveScreen(focusableTree).children
|
|
458
|
+
);
|
|
459
|
+
|
|
460
|
+
const selectedMenuItemId = R.find(
|
|
461
|
+
(child) => child.component.props.selected,
|
|
462
|
+
navbar?.children || []
|
|
463
|
+
)?.id;
|
|
464
|
+
|
|
465
|
+
if (selectedMenuItemId) {
|
|
466
|
+
// set focus on selected menu item
|
|
467
|
+
setFocus(selectedMenuItemId);
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
const firstMenuItemId = R.path(["children", 0, "id"], navbar);
|
|
472
|
+
|
|
473
|
+
if (firstMenuItemId) {
|
|
474
|
+
// set focus on first menu item
|
|
475
|
+
setFocus(firstMenuItemId);
|
|
476
|
+
}
|
|
477
|
+
} catch (error) {
|
|
478
|
+
// failed to put focus on top-menu => log it
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
async function setFocusOnContent(currentRoute: string) {
|
|
483
|
+
// we need to put focus on content here
|
|
484
|
+
// For doing it we have to wait registration of first focusable node into focusableTree
|
|
485
|
+
// this is the temporary hack with waiting until content node starts having children
|
|
486
|
+
try {
|
|
487
|
+
await waitForActiveScreen(currentRoute, focusableTree);
|
|
488
|
+
await waitForContent(focusableTree);
|
|
489
|
+
|
|
490
|
+
const content = getContentNode(focusableTree);
|
|
491
|
+
|
|
492
|
+
const fallback = () => {
|
|
493
|
+
throw Error("Failed to land focus on content");
|
|
494
|
+
};
|
|
495
|
+
|
|
496
|
+
setInitialFocus(content, fallback);
|
|
497
|
+
} catch (e) {
|
|
498
|
+
// we've failed to put initial focus on content, put focus on menu as a last chance
|
|
499
|
+
setFocusOnMenu(currentRoute);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
|
|
413
503
|
/**
|
|
414
504
|
* moves the focus in a provided direction
|
|
415
505
|
* @public
|
|
@@ -422,6 +512,13 @@ export const focusManager = (function () {
|
|
|
422
512
|
currentFocusNode.component.willLoseFocus(currentFocusNode, direction);
|
|
423
513
|
const nextFocusable = findFocusableNode(direction, currentFocusNode);
|
|
424
514
|
|
|
515
|
+
// console.log("debug_2", "FM moveFocus", {
|
|
516
|
+
// direction: direction?.value,
|
|
517
|
+
// currentFocusNode,
|
|
518
|
+
// focusableTree,
|
|
519
|
+
// nextFocusable,
|
|
520
|
+
// });
|
|
521
|
+
|
|
425
522
|
if (nextFocusable?.component?.isMounted?.()) {
|
|
426
523
|
const nextRect = nextFocusable.component.getRect();
|
|
427
524
|
|
|
@@ -518,5 +615,7 @@ export const focusManager = (function () {
|
|
|
518
615
|
enableAutomaticFocus,
|
|
519
616
|
getGroupRootById,
|
|
520
617
|
isGroupItemFocused,
|
|
618
|
+
setFocusOnMenu,
|
|
619
|
+
setFocusOnContent,
|
|
521
620
|
};
|
|
522
621
|
})();
|
|
@@ -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,52 +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
|
-
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);
|
|
225
|
+
// if (isAllScreens(targets)) {
|
|
226
|
+
// return last(targets);
|
|
227
|
+
// }
|
|
208
228
|
|
|
209
229
|
const match = distances
|
|
210
230
|
.sort((a, b) => a.distance - b.distance) // sorts the distances
|
|
211
231
|
.filter(ignoreMenuIfMultipleTargets)[0]; // filters out multiple targets if necessary // gets the first element
|
|
212
232
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
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;
|
|
222
244
|
}
|
|
223
245
|
|
|
224
246
|
/**
|
|
@@ -309,3 +331,36 @@ export function hasPriorityInDirection(direction, component) {
|
|
|
309
331
|
|
|
310
332
|
return (prioritiseFocusOn & mappedDirection) > 0;
|
|
311
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 isAllowedToMoveTo = (currentNode, nextNode) => {
|
|
348
|
+
const screenForCurrentNode = getNodeBeforeRoot(currentNode);
|
|
349
|
+
const screenForNextNode = getNodeBeforeRoot(nextNode);
|
|
350
|
+
|
|
351
|
+
return screenForCurrentNode.id === screenForNextNode.id;
|
|
352
|
+
|
|
353
|
+
// console.log("debug_2", "isAllowedToMoveTo", {
|
|
354
|
+
// nodeBeforeRoot,
|
|
355
|
+
// });
|
|
356
|
+
|
|
357
|
+
// if (nodeBeforeRoot?.id === "navbar") {
|
|
358
|
+
// return true;
|
|
359
|
+
// }
|
|
360
|
+
|
|
361
|
+
// if (nodeBeforeRoot?.component?.props?.isActive) {
|
|
362
|
+
// return true;
|
|
363
|
+
// }
|
|
364
|
+
|
|
365
|
+
// return false;
|
|
366
|
+
};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { isNotNil } from "@applicaster/zapp-react-native-utils/reactUtils/helpers";
|
|
2
|
+
import { find, last, startsWith } from "ramda";
|
|
3
|
+
|
|
4
|
+
// run check each 300 ms
|
|
5
|
+
// run this check too often could lead to performance penalty on low-end devices
|
|
6
|
+
const HOW_OFTEN_TO_CHECK_CONDITION = 300; //ms
|
|
7
|
+
|
|
8
|
+
type Props = {
|
|
9
|
+
maxTimeout: number;
|
|
10
|
+
conditionFn: () => boolean;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export const waitUntil = ({ maxTimeout, conditionFn }: Props) => {
|
|
14
|
+
let interval;
|
|
15
|
+
return new Promise((resolve, reject) => {
|
|
16
|
+
interval = setInterval(() => {
|
|
17
|
+
if (conditionFn()) {
|
|
18
|
+
clearInterval(interval);
|
|
19
|
+
resolve(undefined);
|
|
20
|
+
}
|
|
21
|
+
}, HOW_OFTEN_TO_CHECK_CONDITION);
|
|
22
|
+
|
|
23
|
+
setTimeout(() => {
|
|
24
|
+
reject();
|
|
25
|
+
}, maxTimeout);
|
|
26
|
+
});
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export const waitForActiveScreen = (currentRoute: string, focusableTree) => {
|
|
30
|
+
const isActiveScreenAvailableToFocus = (): boolean => {
|
|
31
|
+
const routes = focusableTree.root.children;
|
|
32
|
+
|
|
33
|
+
const route = find((route) => route.id === currentRoute, routes);
|
|
34
|
+
return isNotNil(route);
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
return waitUntil({
|
|
38
|
+
maxTimeout: 2500, //ms
|
|
39
|
+
conditionFn: isActiveScreenAvailableToFocus,
|
|
40
|
+
});
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export const getActiveScreen = (focusableTree) => {
|
|
44
|
+
return last(focusableTree.root.children);
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export const getContentNode = (focusableTree) => {
|
|
48
|
+
const content = find(
|
|
49
|
+
(child) => startsWith("content", child.id),
|
|
50
|
+
getActiveScreen(focusableTree)?.children
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
return content;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export const waitForContent = (focusableTree) => {
|
|
57
|
+
const contentHasAnyChildren = (): boolean => {
|
|
58
|
+
return getContentNode(focusableTree)?.children.length > 0;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
return waitUntil({
|
|
62
|
+
maxTimeout: 1000, //ms
|
|
63
|
+
conditionFn: contentHasAnyChildren,
|
|
64
|
+
});
|
|
65
|
+
};
|
|
@@ -296,5 +296,8 @@ export class PlayerNative extends Player {
|
|
|
296
296
|
supportsNativeControls = (): boolean =>
|
|
297
297
|
toBooleanWithDefaultFalse(this.getConfig()?.["supports_native_controls"]);
|
|
298
298
|
|
|
299
|
-
supportNativeCast = (): boolean =>
|
|
299
|
+
supportNativeCast = (): boolean =>
|
|
300
|
+
toBooleanWithDefaultFalse(
|
|
301
|
+
this.playerPluginId === "quick-brick-player-brightcove"
|
|
302
|
+
);
|
|
300
303
|
}
|
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.5208030571",
|
|
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.5208030571",
|
|
32
32
|
"buffer": "^5.2.1",
|
|
33
33
|
"camelize": "^1.0.0",
|
|
34
34
|
"create-react-context": "^0.2.3",
|
|
@@ -15,8 +15,21 @@ export const mockMenu: ZappNavigation = {
|
|
|
15
15
|
// preset_id?: null | unknown;
|
|
16
16
|
};
|
|
17
17
|
|
|
18
|
+
export const useRoute = jest.fn(() => ({
|
|
19
|
+
screenData: {
|
|
20
|
+
name: "someName",
|
|
21
|
+
},
|
|
22
|
+
pathname: "someRoute",
|
|
23
|
+
}));
|
|
24
|
+
|
|
18
25
|
export const useNavigation = jest.fn(() => ({
|
|
19
26
|
data: {},
|
|
27
|
+
screenData: {
|
|
28
|
+
name: "someName",
|
|
29
|
+
},
|
|
30
|
+
activeRiver: {
|
|
31
|
+
id: "someId",
|
|
32
|
+
},
|
|
20
33
|
currentRoute: "someRoute",
|
|
21
34
|
}));
|
|
22
35
|
|
|
@@ -496,3 +496,18 @@ export const useIsScreenActive = () => {
|
|
|
496
496
|
|
|
497
497
|
return pathname === currentRoute;
|
|
498
498
|
};
|
|
499
|
+
|
|
500
|
+
type Return = (prefix: string) => string;
|
|
501
|
+
|
|
502
|
+
export const useUniqueRouteSuffix = (): Return => {
|
|
503
|
+
const pathname = usePathname();
|
|
504
|
+
const { currentRoute } = useNavigation();
|
|
505
|
+
|
|
506
|
+
const suffix = `currentRoute:${currentRoute}_pathname:${pathname}`;
|
|
507
|
+
|
|
508
|
+
const withUniqueRouteSuffix = React.useCallback((prefix: string) => {
|
|
509
|
+
return `${prefix}___${suffix}`;
|
|
510
|
+
}, []);
|
|
511
|
+
|
|
512
|
+
return withUniqueRouteSuffix;
|
|
513
|
+
};
|
|
@@ -5,8 +5,9 @@ import { useNestedNavigationContext } from "@applicaster/zapp-react-native-ui-co
|
|
|
5
5
|
import { useNavigation } from "../navigation/useNavigation";
|
|
6
6
|
|
|
7
7
|
import { ScreenContext } from "@applicaster/zapp-react-native-ui-components/Contexts/ScreenContext";
|
|
8
|
+
import { ScreenDataContext } from "@applicaster/zapp-react-native-ui-components/Components/Layout/TV";
|
|
8
9
|
|
|
9
|
-
function useScreenContextV2() {
|
|
10
|
+
export function useScreenContextV2() {
|
|
10
11
|
return useContext(ScreenContext);
|
|
11
12
|
}
|
|
12
13
|
|
|
@@ -45,8 +46,8 @@ export function useSetNavbarState() {
|
|
|
45
46
|
export function useScreenContext(): NavigationScreenData {
|
|
46
47
|
const navigator = useNavigation();
|
|
47
48
|
|
|
48
|
-
const
|
|
49
|
-
|
|
49
|
+
const screenData = useContext(ScreenDataContext);
|
|
50
|
+
|
|
50
51
|
const modalScreenData = navigator?.modalData;
|
|
51
52
|
|
|
52
53
|
const isModal = useModalNavigationContext();
|
package/reactUtils/helpers.tsx
CHANGED
|
@@ -7,7 +7,7 @@ export const isAnyNil = R.any(R.isNil);
|
|
|
7
7
|
|
|
8
8
|
export const isNilOrEmpty = R.either(R.isNil, R.isEmpty);
|
|
9
9
|
|
|
10
|
-
export const isNotEmpty = R.complement(R.isEmpty);
|
|
10
|
+
export const isNotEmpty = R.both(isNotNil, R.complement(R.isEmpty));
|
|
11
11
|
|
|
12
12
|
export const withoutNil = R.reject(R.isNil);
|
|
13
13
|
|
package/screenState/index.ts
CHANGED
|
@@ -49,11 +49,3 @@ export function useScreenState(
|
|
|
49
49
|
setSelectedEntry: (entryId) => setSelectedEntry(screenId, entryId),
|
|
50
50
|
};
|
|
51
51
|
}
|
|
52
|
-
|
|
53
|
-
export const useScreenScrollStore = create<{
|
|
54
|
-
scrollPosition: number;
|
|
55
|
-
scrollingVelocity: number;
|
|
56
|
-
}>(() => ({
|
|
57
|
-
scrollPosition: 0,
|
|
58
|
-
scrollingVelocity: 0,
|
|
59
|
-
}));
|
package/stringUtils/index.ts
CHANGED
|
@@ -152,3 +152,8 @@ export function objectToCamelCase(obj) {
|
|
|
152
152
|
export function isString(value: unknown): value is string {
|
|
153
153
|
return typeof value === "string" || value instanceof String;
|
|
154
154
|
}
|
|
155
|
+
|
|
156
|
+
/** Checks if a value is a non-empty string */
|
|
157
|
+
export function isNotEmptyString(value: unknown): value is string {
|
|
158
|
+
return isString(value) && value.length > 0;
|
|
159
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export const getByTestId = (wrapper, testId) => {
|
|
2
|
+
try {
|
|
3
|
+
if (wrapper?.getByTestId) {
|
|
4
|
+
return wrapper?.queryAllByTestId(testId)?.[0] ?? undefined;
|
|
5
|
+
} else if (wrapper?.root?.findByProps) {
|
|
6
|
+
return wrapper.root.findAllByProps({ testID: testId })?.[0] ?? undefined;
|
|
7
|
+
}
|
|
8
|
+
} catch (e) {
|
|
9
|
+
// eslint-disable-next-line no-console
|
|
10
|
+
console.log("getByTestId E:", e);
|
|
11
|
+
|
|
12
|
+
return undefined;
|
|
13
|
+
}
|
|
14
|
+
};
|
package/testUtils/index.tsx
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as R from "ramda";
|
|
2
2
|
|
|
3
|
-
import React from "react";
|
|
3
|
+
import React, { PropsWithChildren } from "react";
|
|
4
4
|
import { View } from "react-native";
|
|
5
5
|
|
|
6
6
|
import { Provider } from "react-redux";
|
|
@@ -11,6 +11,8 @@ import { SafeAreaProvider } from "react-native-safe-area-context";
|
|
|
11
11
|
import { render } from "@testing-library/react-native";
|
|
12
12
|
import { AnalyticsProvider } from "../analyticsUtils";
|
|
13
13
|
|
|
14
|
+
export { getByTestId } from "./getByTestId";
|
|
15
|
+
|
|
14
16
|
/**
|
|
15
17
|
* Function to be used in tandem with enzyme findWhere(node => node.prop('testID') === 'yourID')
|
|
16
18
|
* Example:
|
|
@@ -21,17 +23,6 @@ import { AnalyticsProvider } from "../analyticsUtils";
|
|
|
21
23
|
export const testIDEquals = (testId: any): boolean =>
|
|
22
24
|
R.compose(R.equals(testId), R.invoker(1, "prop")("testID"));
|
|
23
25
|
|
|
24
|
-
export const getByTestId = (wrapper, testId) => {
|
|
25
|
-
try {
|
|
26
|
-
return wrapper?.queryAllByTestId(testId)?.[0] ?? undefined;
|
|
27
|
-
} catch (e) {
|
|
28
|
-
// eslint-disable-next-line no-console
|
|
29
|
-
console.log("getByTestId E:", e);
|
|
30
|
-
|
|
31
|
-
return undefined;
|
|
32
|
-
}
|
|
33
|
-
};
|
|
34
|
-
|
|
35
26
|
/** Helper function for react-test-renderer, accepts type and renderer
|
|
36
27
|
* * @param {*} type React Component type instance or name of component
|
|
37
28
|
* * @param {*} renderer renderer instance
|
|
@@ -47,11 +38,11 @@ export const findByType = (type, renderer) => {
|
|
|
47
38
|
export function createComponentMock(mockName, hasChildren = false) {
|
|
48
39
|
return hasChildren
|
|
49
40
|
? jest.fn(({ children }) => (
|
|
50
|
-
<View
|
|
41
|
+
<View testID={mockName}>
|
|
51
42
|
{typeof children === "function" ? children() : children}
|
|
52
43
|
</View>
|
|
53
44
|
))
|
|
54
|
-
: jest.fn(() => <View
|
|
45
|
+
: jest.fn(() => <View testID={mockName} />);
|
|
55
46
|
}
|
|
56
47
|
|
|
57
48
|
const initialState = {
|
|
@@ -64,14 +55,21 @@ const initialState = {
|
|
|
64
55
|
AppContainer: createComponentMock("appContainer", true),
|
|
65
56
|
DisplayError: createComponentMock("displayError"),
|
|
66
57
|
},
|
|
67
|
-
rivers:
|
|
58
|
+
rivers: { id: "A1234", home: true },
|
|
68
59
|
appSettings: {},
|
|
69
60
|
plugins: [],
|
|
70
|
-
appState: { appReady: true },
|
|
61
|
+
appState: { appReady: true, appLaunched: true },
|
|
62
|
+
appData: { layoutVersion: "v1" },
|
|
63
|
+
contentTypes: null,
|
|
71
64
|
};
|
|
72
65
|
|
|
73
|
-
export const WrappedWithProviders = ({
|
|
74
|
-
|
|
66
|
+
export const WrappedWithProviders = ({
|
|
67
|
+
children,
|
|
68
|
+
store: storeObj = {},
|
|
69
|
+
}: any) => {
|
|
70
|
+
const _store = configureStore([thunk])(
|
|
71
|
+
R.mergeDeepWith(R.concat, { ...initialState }, storeObj)
|
|
72
|
+
);
|
|
75
73
|
|
|
76
74
|
return (
|
|
77
75
|
<SafeAreaProvider
|
|
@@ -93,8 +91,12 @@ export const WrappedWithProviders = ({ children, store = {} }: any) => {
|
|
|
93
91
|
* @param store optional store to pass to the provider
|
|
94
92
|
* @returns
|
|
95
93
|
*/
|
|
96
|
-
export const renderWithProviders = (component,
|
|
97
|
-
return render(
|
|
98
|
-
|
|
99
|
-
|
|
94
|
+
export const renderWithProviders = (component, storeObj?: unknown) => {
|
|
95
|
+
return render(component, {
|
|
96
|
+
wrapper: function TestWrapper({ children }: PropsWithChildren<any>) {
|
|
97
|
+
return (
|
|
98
|
+
<WrappedWithProviders store={storeObj}>{children}</WrappedWithProviders>
|
|
99
|
+
);
|
|
100
|
+
},
|
|
101
|
+
});
|
|
100
102
|
};
|