@applicaster/zapp-react-native-utils 12.0.0-alpha.4837064762 → 12.0.0-alpha.4885205607
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/accessibilityManager/const.ts +50 -0
- package/appUtils/accessibilityManager/hooks.ts +40 -0
- package/appUtils/accessibilityManager/index.ts +155 -0
- package/appUtils/accessibilityManager/logger.ts +5 -0
- 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 +44 -128
- package/appUtils/focusManager/treeDataStructure/Utils/index.js +14 -38
- 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/index.js +2 -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 +185 -0
- package/appUtils/playerManager/conts.ts +1 -0
- package/appUtils/playerManager/index.ts +11 -5
- package/appUtils/playerManager/nativePlayerManager.ts +12 -0
- package/appUtils/playerManager/player.ts +9 -3
- package/appUtils/playerManager/playerNative.ts +5 -14
- package/appUtils/playerManager/usePlayerControllerSetup.tsx +7 -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 +11 -17
- package/dateUtils/index.ts +11 -0
- package/headersUtils/index.ts +1 -0
- package/index.d.ts +15 -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/navigationUtils/index.ts +48 -0
- package/numberUtils/__tests__/isLikeNumber.test.ts +18 -0
- package/numberUtils/__tests__/toNumber.test.ts +2 -2
- package/numberUtils/index.ts +9 -18
- package/package.json +2 -2
- package/playerUtils/imaScriptDom.ts +59 -0
- package/pluginUtils/index.ts +9 -0
- 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/navigation/__tests__/index.test.tsx +11 -8
- package/reactHooks/navigation/index.ts +46 -68
- 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 +1 -2
- package/reactUtils/helpers.tsx +1 -1
- package/reactUtils/index.ts +104 -0
- package/stringUtils/index.ts +5 -0
- package/types.ts +4 -0
- package/appUtils/focusManager/FocusableEvents.ts +0 -8
- package/appUtils/focusManager/treeDataStructure/Utils/__tests__/Utils_android.test.js +0 -1094
- package/appUtils/focusManager/treeDataStructure/Utils/index.android.js +0 -261
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
import * as R from "ramda";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
isNilOrEmpty,
|
|
4
|
+
isNotNil,
|
|
5
|
+
} from "@applicaster/zapp-react-native-utils/reactUtils/helpers";
|
|
3
6
|
|
|
4
7
|
import { Tree } from "./treeDataStructure/Tree";
|
|
5
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
findFocusableNode,
|
|
10
|
+
findPriorityItem,
|
|
11
|
+
haveSameParentBeforeRoot,
|
|
12
|
+
} from "./treeDataStructure/Utils";
|
|
6
13
|
import { subscriber } from "../../functionUtils";
|
|
7
14
|
import * as FOCUS_EVENTS from "./events";
|
|
8
15
|
import { coreLogger } from "../../logger";
|
|
9
|
-
|
|
10
16
|
const logger = coreLogger.addSubsystem("focusManager");
|
|
11
17
|
|
|
12
18
|
const isFocusEnabled = (focusableItem): boolean => {
|
|
@@ -19,22 +25,6 @@ const isFocusEnabled = (focusableItem): boolean => {
|
|
|
19
25
|
return true;
|
|
20
26
|
};
|
|
21
27
|
|
|
22
|
-
const waitUntil = (conditionFn) => {
|
|
23
|
-
let interval;
|
|
24
|
-
return new Promise((resolve, reject) => {
|
|
25
|
-
interval = setInterval(() => {
|
|
26
|
-
if (conditionFn()) {
|
|
27
|
-
clearInterval(interval);
|
|
28
|
-
resolve(undefined);
|
|
29
|
-
}
|
|
30
|
-
}, 1000);
|
|
31
|
-
|
|
32
|
-
setTimeout(() => {
|
|
33
|
-
reject();
|
|
34
|
-
}, 2500);
|
|
35
|
-
});
|
|
36
|
-
};
|
|
37
|
-
|
|
38
28
|
/**
|
|
39
29
|
* The focusManager is meant to keep references of focusables elements in the UI, and provide
|
|
40
30
|
* ways to assign the focus to one of the focusables.
|
|
@@ -62,21 +52,9 @@ export const focusManager = (function () {
|
|
|
62
52
|
// focusable node which currently holds the focus
|
|
63
53
|
let currentFocusNode = null;
|
|
64
54
|
let lastFocusedId = null;
|
|
65
|
-
let initialFocusOnContent = null;
|
|
66
55
|
let focusDisabled = false;
|
|
67
|
-
let focusAutoEnabled = false;
|
|
68
56
|
const eventHandler = subscriber();
|
|
69
57
|
|
|
70
|
-
function disableAutomaticFocus() {
|
|
71
|
-
logger.log({ message: "disableAutomaticFocus" });
|
|
72
|
-
focusAutoEnabled = false;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
function enableAutomaticFocus() {
|
|
76
|
-
logger.log({ message: "focusManager: enableAutomaticFocus" });
|
|
77
|
-
focusAutoEnabled = true;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
58
|
function enableFocus() {
|
|
81
59
|
focusDisabled = false;
|
|
82
60
|
}
|
|
@@ -245,7 +223,7 @@ export const focusManager = (function () {
|
|
|
245
223
|
logger.log({ message: "setInitialFocus", data: { preferredFocus } });
|
|
246
224
|
|
|
247
225
|
if (isNilOrEmpty(preferredFocus)) {
|
|
248
|
-
return;
|
|
226
|
+
return { success: false };
|
|
249
227
|
}
|
|
250
228
|
|
|
251
229
|
const focusCandidate = preferredFocus[0];
|
|
@@ -254,9 +232,18 @@ export const focusManager = (function () {
|
|
|
254
232
|
const currentFocusedId = R.path(["props", "id"], getCurrentFocus());
|
|
255
233
|
const nextFocusedId = R.path(["props", "id"], focusableItem);
|
|
256
234
|
|
|
257
|
-
if (
|
|
235
|
+
if (
|
|
236
|
+
isNotNil(currentFocusedId) &&
|
|
237
|
+
isNotNil(nextFocusedId) &&
|
|
238
|
+
currentFocusedId === nextFocusedId
|
|
239
|
+
) {
|
|
258
240
|
// we are going to move focus to the same node(already focused) -> nothing to do
|
|
259
|
-
return;
|
|
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 };
|
|
260
247
|
}
|
|
261
248
|
|
|
262
249
|
if (
|
|
@@ -264,10 +251,7 @@ export const focusManager = (function () {
|
|
|
264
251
|
!focusableItem.isGroup &&
|
|
265
252
|
isFocusEnabled(focusableItem)
|
|
266
253
|
) {
|
|
267
|
-
|
|
268
|
-
blur();
|
|
269
|
-
initialFocusOnContent = false;
|
|
270
|
-
}
|
|
254
|
+
blur();
|
|
271
255
|
|
|
272
256
|
logger.log({
|
|
273
257
|
message: "setInitialFocus – currentFocusNode",
|
|
@@ -279,7 +263,11 @@ export const focusManager = (function () {
|
|
|
279
263
|
currentFocusNode = focusCandidate;
|
|
280
264
|
focusableItem.willReceiveFocus(focusableItem, null);
|
|
281
265
|
focusableItem && setFocus(focusCandidate.id, null);
|
|
266
|
+
|
|
267
|
+
return { success: true };
|
|
282
268
|
}
|
|
269
|
+
|
|
270
|
+
return { success: false };
|
|
283
271
|
}
|
|
284
272
|
|
|
285
273
|
function treeLoaded(val, node) {
|
|
@@ -289,17 +277,8 @@ export const focusManager = (function () {
|
|
|
289
277
|
val,
|
|
290
278
|
node,
|
|
291
279
|
currentFocusNode,
|
|
292
|
-
initialFocusOnContent,
|
|
293
280
|
},
|
|
294
281
|
});
|
|
295
|
-
|
|
296
|
-
if ((val && !currentFocusNode) || initialFocusOnContent) {
|
|
297
|
-
logger.log({ message: `focusAutoEnabled ${focusAutoEnabled}` });
|
|
298
|
-
|
|
299
|
-
if (focusAutoEnabled) {
|
|
300
|
-
setInitialFocus(initialFocusOnContent ? node : undefined);
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
282
|
}
|
|
304
283
|
|
|
305
284
|
/**
|
|
@@ -351,8 +330,6 @@ export const focusManager = (function () {
|
|
|
351
330
|
* @returns {React.Component} the registered component
|
|
352
331
|
*/
|
|
353
332
|
function registerGroup(id, component) {
|
|
354
|
-
// console.log("debug_2", "FM registerGroup", { id, component });
|
|
355
|
-
|
|
356
333
|
focusableTree.addNode({ id, component });
|
|
357
334
|
|
|
358
335
|
return component;
|
|
@@ -368,8 +345,6 @@ export const focusManager = (function () {
|
|
|
368
345
|
* @returns {React.Component} the registered component
|
|
369
346
|
*/
|
|
370
347
|
function registerItem(id, component) {
|
|
371
|
-
// console.log("debug_2", "FM registerItem", { id, component });
|
|
372
|
-
|
|
373
348
|
focusableTree.addNode({ id, component });
|
|
374
349
|
|
|
375
350
|
if (!currentFocusNode && id === lastFocusedId && !id.includes("menu")) {
|
|
@@ -386,8 +361,6 @@ export const focusManager = (function () {
|
|
|
386
361
|
* @param {String} id of the group to unregister
|
|
387
362
|
*/
|
|
388
363
|
function unregisterGroup(id) {
|
|
389
|
-
// console.log("debug_2", "FM unregisterGroup", { id });
|
|
390
|
-
|
|
391
364
|
focusableTree.findAndRemoveNode(id);
|
|
392
365
|
}
|
|
393
366
|
|
|
@@ -399,8 +372,6 @@ export const focusManager = (function () {
|
|
|
399
372
|
* @param {String} id of the Focusable to unregister
|
|
400
373
|
*/
|
|
401
374
|
function unregisterItem(id) {
|
|
402
|
-
// console.log("debug_2", "FM unregisterItem", { id });
|
|
403
|
-
|
|
404
375
|
focusableTree.findAndRemoveNode(id);
|
|
405
376
|
|
|
406
377
|
if (currentFocusNode && currentFocusNode.id === id) {
|
|
@@ -438,70 +409,22 @@ export const focusManager = (function () {
|
|
|
438
409
|
group ? unregisterGroup(id) : unregisterItem(id);
|
|
439
410
|
}
|
|
440
411
|
|
|
441
|
-
function
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
if (focusOnContent) {
|
|
445
|
-
initialFocusOnContent = focusOnContent;
|
|
446
|
-
} else {
|
|
447
|
-
setFocus(id);
|
|
448
|
-
}
|
|
449
|
-
}
|
|
412
|
+
function isCurrentFocusOnTheTopScreen() {
|
|
413
|
+
const routes = R.pathOr([], ["root", "children"], focusableTree);
|
|
450
414
|
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
(child) => R.startsWith("navbar", child.id),
|
|
455
|
-
activeScreen.children
|
|
456
|
-
);
|
|
457
|
-
|
|
458
|
-
const selectedMenuItemId = R.find(
|
|
459
|
-
(child) => child.component.props.selected,
|
|
460
|
-
navbar?.children || []
|
|
461
|
-
)?.id;
|
|
462
|
-
|
|
463
|
-
if (selectedMenuItemId) {
|
|
464
|
-
// set focus on selected menu item
|
|
465
|
-
setFocus(selectedMenuItemId);
|
|
466
|
-
return;
|
|
415
|
+
if (routes.length <= 1) {
|
|
416
|
+
// there is only one or zero routes
|
|
417
|
+
return true;
|
|
467
418
|
}
|
|
468
419
|
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
if (firstMenuItemId) {
|
|
472
|
-
// set focus on first menu item
|
|
473
|
-
setFocus(firstMenuItemId);
|
|
474
|
-
}
|
|
420
|
+
return haveSameParentBeforeRoot(currentFocusNode, R.last(routes));
|
|
475
421
|
}
|
|
476
422
|
|
|
477
|
-
function
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
const activeScreen = R.last(focusableTree.root.children);
|
|
483
|
-
const content = R.find(
|
|
484
|
-
(child) => R.startsWith("content", child.id),
|
|
485
|
-
activeScreen.children
|
|
486
|
-
);
|
|
487
|
-
|
|
488
|
-
return content;
|
|
489
|
-
};
|
|
490
|
-
|
|
491
|
-
const contentHasAnyChildren = () => {
|
|
492
|
-
return getContentNode()?.children.length > 0;
|
|
493
|
-
};
|
|
494
|
-
|
|
495
|
-
waitUntil(contentHasAnyChildren)
|
|
496
|
-
.then(() => {
|
|
497
|
-
const content = getContentNode();
|
|
498
|
-
|
|
499
|
-
setInitialFocus(content);
|
|
500
|
-
})
|
|
501
|
-
.catch(() => {
|
|
502
|
-
// we failed to wait any content, put focus on menu as a last chance
|
|
503
|
-
setFocusOnMenu();
|
|
504
|
-
});
|
|
423
|
+
function recoverFocus() {
|
|
424
|
+
if (!isCurrentFocusOnTheTopScreen()) {
|
|
425
|
+
// We've failed to set focused node on the new screen => run focus recovery
|
|
426
|
+
setInitialFocus();
|
|
427
|
+
}
|
|
505
428
|
}
|
|
506
429
|
|
|
507
430
|
/**
|
|
@@ -510,19 +433,17 @@ export const focusManager = (function () {
|
|
|
510
433
|
* @param {Object} direction of the scroll
|
|
511
434
|
*/
|
|
512
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
|
+
|
|
513
441
|
if (focusDisabled) return false;
|
|
514
442
|
|
|
515
443
|
if (currentFocusNode) {
|
|
516
444
|
currentFocusNode.component.willLoseFocus(currentFocusNode, direction);
|
|
517
445
|
const nextFocusable = findFocusableNode(direction, currentFocusNode);
|
|
518
446
|
|
|
519
|
-
console.log("debug_2", "FM moveFocus", {
|
|
520
|
-
direction: direction?.value,
|
|
521
|
-
currentFocusNode,
|
|
522
|
-
focusableTree,
|
|
523
|
-
nextFocusable,
|
|
524
|
-
});
|
|
525
|
-
|
|
526
447
|
if (nextFocusable?.component?.isMounted?.()) {
|
|
527
448
|
const nextRect = nextFocusable.component.getRect();
|
|
528
449
|
|
|
@@ -545,8 +466,6 @@ export const focusManager = (function () {
|
|
|
545
466
|
} else {
|
|
546
467
|
currentFocusNode.component.failedLostFocus(currentFocusNode, direction);
|
|
547
468
|
}
|
|
548
|
-
} else {
|
|
549
|
-
setInitialFocus();
|
|
550
469
|
}
|
|
551
470
|
}
|
|
552
471
|
|
|
@@ -606,7 +525,6 @@ export const focusManager = (function () {
|
|
|
606
525
|
on,
|
|
607
526
|
invokeHandler,
|
|
608
527
|
removeHandler,
|
|
609
|
-
setupInitialFocus,
|
|
610
528
|
longPress,
|
|
611
529
|
pressOut,
|
|
612
530
|
enableFocus,
|
|
@@ -615,11 +533,9 @@ export const focusManager = (function () {
|
|
|
615
533
|
isFocusDisabled,
|
|
616
534
|
getCurrentFocusNode,
|
|
617
535
|
focusableTree,
|
|
618
|
-
disableAutomaticFocus,
|
|
619
|
-
enableAutomaticFocus,
|
|
620
536
|
getGroupRootById,
|
|
621
537
|
isGroupItemFocused,
|
|
622
|
-
|
|
623
|
-
|
|
538
|
+
recoverFocus,
|
|
539
|
+
isCurrentFocusOnTheTopScreen,
|
|
624
540
|
};
|
|
625
541
|
})();
|
|
@@ -1,5 +1,7 @@
|
|
|
1
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 {
|
|
@@ -145,7 +147,7 @@ const hasComponent = (target) => !!target?.component;
|
|
|
145
147
|
|
|
146
148
|
const hasComponentAndChildren = allPass([hasComponent, hasChildren]);
|
|
147
149
|
|
|
148
|
-
const positiveOrZero = (value) => value
|
|
150
|
+
const positiveOrZero = (value) => value > 0 || isZero(value);
|
|
149
151
|
|
|
150
152
|
const excludeFromFocusSearching = pathOr(false, [
|
|
151
153
|
"component",
|
|
@@ -153,12 +155,10 @@ const excludeFromFocusSearching = pathOr(false, [
|
|
|
153
155
|
"excludeFromFocusSearching",
|
|
154
156
|
]);
|
|
155
157
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
const isNavBar = (target) => startsWith("navbar", target?.id);
|
|
158
|
+
const isNavBar = (target) => startsWith(QUICK_BRICK_NAVBAR, target?.id);
|
|
159
159
|
|
|
160
160
|
const ignoreMenuIfMultipleTargets = (nodes) => {
|
|
161
|
-
if (nodes.length > 1
|
|
161
|
+
if (nodes.length > 1) {
|
|
162
162
|
return nodes.filter((n) => !isNavBar(n?.target));
|
|
163
163
|
}
|
|
164
164
|
|
|
@@ -206,7 +206,7 @@ export function getTargetInDirection(
|
|
|
206
206
|
const overrideTarget = currentNode?.component?.props[overridePropName];
|
|
207
207
|
|
|
208
208
|
if (overrideTarget) {
|
|
209
|
-
const target = targetNodes.find((n) => n
|
|
209
|
+
const target = targetNodes.find((n) => n?.id === overrideTarget);
|
|
210
210
|
|
|
211
211
|
if (target) return target;
|
|
212
212
|
}
|
|
@@ -222,24 +222,10 @@ export function getTargetInDirection(
|
|
|
222
222
|
}))
|
|
223
223
|
.filter(({ distance }) => positiveOrZero(distance));
|
|
224
224
|
|
|
225
|
-
// if (isAllScreens(targets)) {
|
|
226
|
-
// return last(targets);
|
|
227
|
-
// }
|
|
228
|
-
|
|
229
225
|
const match = distances
|
|
230
226
|
.sort((a, b) => a.distance - b.distance) // sorts the distances
|
|
231
227
|
.filter(ignoreMenuIfMultipleTargets)[0]; // filters out multiple targets if necessary // gets the first element
|
|
232
228
|
|
|
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
229
|
return match?.target;
|
|
244
230
|
}
|
|
245
231
|
|
|
@@ -337,6 +323,10 @@ export const getNodeBeforeRoot = (node) => {
|
|
|
337
323
|
return undefined;
|
|
338
324
|
}
|
|
339
325
|
|
|
326
|
+
if (isNil(node.parent)) {
|
|
327
|
+
return undefined;
|
|
328
|
+
}
|
|
329
|
+
|
|
340
330
|
if (node.parent.id === "root") {
|
|
341
331
|
return node;
|
|
342
332
|
}
|
|
@@ -344,23 +334,9 @@ export const getNodeBeforeRoot = (node) => {
|
|
|
344
334
|
return getNodeBeforeRoot(node.parent);
|
|
345
335
|
};
|
|
346
336
|
|
|
347
|
-
export const
|
|
348
|
-
const
|
|
349
|
-
const
|
|
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
|
-
// }
|
|
337
|
+
export const haveSameParentBeforeRoot = (currentNode, nextNode) => {
|
|
338
|
+
const routeForCurrentNode = getNodeBeforeRoot(currentNode);
|
|
339
|
+
const routeForNextNode = getNodeBeforeRoot(nextNode);
|
|
364
340
|
|
|
365
|
-
|
|
341
|
+
return routeForCurrentNode?.id === routeForNextNode?.id;
|
|
366
342
|
};
|
|
@@ -0,0 +1,100 @@
|
|
|
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
|
+
};
|
|
@@ -0,0 +1,76 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
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
|
+
};
|
package/appUtils/index.js
CHANGED
|
@@ -9,6 +9,8 @@ export { KEYS, ARROW_KEYS } from "./keyCodes/keys";
|
|
|
9
9
|
|
|
10
10
|
export { playerManager } from "./playerManager";
|
|
11
11
|
|
|
12
|
+
export { accessibilityManager } from "./accessibilityManager";
|
|
13
|
+
|
|
12
14
|
export function deprecationMessage(fn, replacement) {
|
|
13
15
|
if (__DEV__ && process.env.NODE_ENV !== "test") {
|
|
14
16
|
console.warn("/** DEPRECATION WARNING **/");
|