@applicaster/zapp-react-native-utils 13.0.0-alpha.3755455151 → 13.0.0-alpha.3962361544
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/README.md +0 -10
- package/actionsExecutor/ActionExecutor.ts +16 -9
- package/actionsExecutor/ActionExecutorContext.tsx +59 -8
- package/alertUtils/alert.ts +148 -0
- package/alertUtils/index.ts +1 -148
- package/analyticsUtils/currentScreenProperities.ts +6 -4
- package/appUtils/HooksManager/Hook.ts +2 -2
- package/appUtils/HooksManager/types.ts +5 -2
- package/appUtils/contextKeysManager/contextResolver.ts +77 -0
- package/appUtils/contextKeysManager/index.ts +2 -2
- package/appUtils/contextKeysManager/utils/index.ts +2 -1
- package/appUtils/focusManager/__tests__/__snapshots__/focusManager.test.js.snap +1 -0
- package/appUtils/focusManager/index.ts +11 -3
- package/appUtils/focusManagerAux/utils/index.ts +4 -3
- package/appUtils/keyCodes/index.ts +11 -7
- package/appUtils/platform/const.ts +14 -0
- package/appUtils/platform/platformUtils.ts +47 -4
- package/appUtils/playerManager/index.ts +1 -1
- package/appUtils/playerManager/player.ts +2 -2
- package/equalUtils/__tests__/isEqual/combination.test.ts +263 -0
- package/equalUtils/index.ts +49 -30
- package/focusManager/FocusManager.ts +11 -1
- package/focusManager/index.ts +41 -4
- package/idleUtils/__tests__/withTimeout$.test.ts +37 -0
- package/idleUtils/index.ts +1 -1
- package/idleUtils/withTimeout.ts +15 -2
- package/logger/const.ts +9 -67
- package/manifestUtils/__tests__/createConfig.test.js +0 -2
- package/manifestUtils/fonts/index.js +5 -6
- package/manifestUtils/sharedConfiguration/screenPicker/__tests__/utils.test.js +304 -0
- package/manifestUtils/sharedConfiguration/screenPicker/stylesFields.js +875 -0
- package/manifestUtils/sharedConfiguration/screenPicker/utils.js +130 -0
- package/navigationUtils/index.ts +3 -3
- package/package.json +4 -6
- package/reactHooks/cell-click/__tests__/index.test.js +5 -0
- package/reactHooks/cell-click/index.ts +7 -1
- package/reactHooks/feed/useFeedLoader.tsx +1 -1
- package/reactHooks/layout/useDimensions/helpers/__tests__/getInitialDimensions.test.js +1 -1
- package/reactHooks/layout/useDimensions/useDimensions.ts +8 -0
- package/reactHooks/navigation/__mocks__/useNavigation.ts +3 -0
- package/reactHooks/navigation/__tests__/index.test.tsx +7 -7
- package/reactHooks/navigation/index.ts +10 -22
- package/reactHooks/navigation/useGetTabBarHeight.ts +1 -3
- package/reactHooks/navigation/useProfilerLogging.ts +15 -0
- package/reactHooks/state/useRefWithInitialValue.ts +14 -0
- package/reactHooks/state/useStateFromSubscribe.ts +23 -0
- package/reactHooks/usePluginConfiguration.ts +1 -1
- package/reactHooks/useSubscriberFor.ts +4 -3
- package/reactUtils/__tests__/__snapshots__/reactUtilsHelpers.test.js.snap +16 -4
- package/reactUtils/__tests__/reactUtils.test.js +1 -19
- package/reactUtils/__tests__/reactUtilsHelpers.test.js +72 -71
- package/reactUtils/createContext/__tests__/__snapshots__/helpers.test.js.snap +6 -72
- package/reactUtils/createContext/__tests__/__snapshots__/provider.test.js.snap +3 -28
- package/reactUtils/createContext/__tests__/__snapshots__/withConsumer.test.js.snap +3 -4
- package/reactUtils/createContext/__tests__/__snapshots__/withProvider.test.js.snap +7 -3
- package/reactUtils/createContext/__tests__/helpers.test.js +107 -121
- package/reactUtils/createContext/__tests__/{index.test.js → index.test.ts} +2 -2
- package/reactUtils/createContext/__tests__/provider.test.js +4 -6
- package/reactUtils/createContext/__tests__/withConsumer.test.js +12 -12
- package/reactUtils/createContext/__tests__/withProvider.test.js +4 -4
- package/reactUtils/createContext/helpers.ts +6 -1
- package/reactUtils/createContext/index.ts +11 -25
- package/reactUtils/index.ts +0 -11
- package/screenState/index.ts +24 -10
- package/testUtils/index.tsx +0 -10
- package/theme/index.ts +1 -1
- package/time/Timer.ts +5 -2
- package/types.ts +1 -0
- package/zappFrameworkUtils/presentPluginOneTime.ts +2 -3
- package/reactUtils/createContext/__tests__/index-no-react.test.js +0 -27
- /package/reactUtils/createContext/__tests__/__snapshots__/{index.test.js.snap → index.test.ts.snap} +0 -0
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
import { subscriber } from "../../functionUtils";
|
|
14
14
|
import { coreLogger } from "../../logger";
|
|
15
15
|
import { ACTION } from "./utils/enums";
|
|
16
|
+
|
|
16
17
|
const logger = coreLogger.addSubsystem("focusManager");
|
|
17
18
|
|
|
18
19
|
const isFocusEnabled = (focusableItem): boolean => {
|
|
@@ -325,8 +326,6 @@ export const focusManager = (function () {
|
|
|
325
326
|
},
|
|
326
327
|
});
|
|
327
328
|
|
|
328
|
-
currentFocusNode = focusCandidate;
|
|
329
|
-
|
|
330
329
|
focusableItem && setFocus(focusCandidate.id, null);
|
|
331
330
|
|
|
332
331
|
return { success: true };
|
|
@@ -346,6 +345,14 @@ export const focusManager = (function () {
|
|
|
346
345
|
});
|
|
347
346
|
}
|
|
348
347
|
|
|
348
|
+
function pressIn() {
|
|
349
|
+
const currentFocusable = getCurrentFocus();
|
|
350
|
+
|
|
351
|
+
if (currentFocusable) {
|
|
352
|
+
currentFocusable.pressIn(null);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
349
356
|
/**
|
|
350
357
|
* calls the `press` method on the current focus component
|
|
351
358
|
* @public
|
|
@@ -477,7 +484,7 @@ export const focusManager = (function () {
|
|
|
477
484
|
function isCurrentFocusOnTheTopScreen() {
|
|
478
485
|
const routes = R.pathOr([], ["root", "children"], focusableTree);
|
|
479
486
|
|
|
480
|
-
if (routes.length <= 1) {
|
|
487
|
+
if (currentFocusNode && routes.length <= 1) {
|
|
481
488
|
// there is only one or zero routes
|
|
482
489
|
return true;
|
|
483
490
|
}
|
|
@@ -553,6 +560,7 @@ export const focusManager = (function () {
|
|
|
553
560
|
invokeHandler,
|
|
554
561
|
removeHandler,
|
|
555
562
|
longPress,
|
|
563
|
+
pressIn,
|
|
556
564
|
pressOut,
|
|
557
565
|
enableFocus,
|
|
558
566
|
setInitialFocus,
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { isNotNil } from "@applicaster/zapp-react-native-utils/reactUtils/helpers";
|
|
2
|
-
import { find, last,
|
|
2
|
+
import { find, last, pathOr, startsWith } from "ramda";
|
|
3
3
|
import {
|
|
4
|
-
QUICK_BRICK_NAVBAR,
|
|
5
4
|
QUICK_BRICK_CONTENT,
|
|
5
|
+
QUICK_BRICK_NAVBAR,
|
|
6
6
|
} from "@applicaster/quick-brick-core/const";
|
|
7
7
|
|
|
8
8
|
// run check each 300 ms
|
|
@@ -66,7 +66,8 @@ export const getContentNode = (focusableTree) => {
|
|
|
66
66
|
const content = find(
|
|
67
67
|
(child) =>
|
|
68
68
|
startsWith(QUICK_BRICK_CONTENT, child.id) ||
|
|
69
|
-
startsWith("player", child.id)
|
|
69
|
+
startsWith("player", child.id) ||
|
|
70
|
+
startsWith("/hooks", child?.parent?.id), // For startup hooks case
|
|
70
71
|
getActiveScreenChildren(focusableTree)
|
|
71
72
|
);
|
|
72
73
|
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import * as R from "ramda";
|
|
2
|
-
|
|
3
1
|
// more keys can be added here. Simply make sure
|
|
4
2
|
// the key matches the code value
|
|
5
3
|
|
|
@@ -34,12 +32,18 @@ export function keyCode(key) {
|
|
|
34
32
|
keyCode(keyEvent).matchesAny("Enter", "Backspace", KEYS.ArrowLeft, ...)
|
|
35
33
|
*/
|
|
36
34
|
|
|
37
|
-
const matches =
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
35
|
+
const matches = (compareKey) => {
|
|
36
|
+
// If compareKey is a string, look it up in KEYS object
|
|
37
|
+
const keyToCompare =
|
|
38
|
+
typeof compareKey === "string" ? KEYS[compareKey] : compareKey;
|
|
39
|
+
|
|
40
|
+
// Compare either code or keyCode properties
|
|
41
|
+
return (
|
|
42
|
+
keyToCompare?.code === key.code || keyToCompare?.keyCode === key.keyCode
|
|
43
|
+
);
|
|
44
|
+
};
|
|
41
45
|
|
|
42
|
-
const matchesAny = (...args) =>
|
|
46
|
+
const matchesAny = (...args) => args.some(matches);
|
|
43
47
|
|
|
44
48
|
const direction = () =>
|
|
45
49
|
matchesAny(...ARROW_KEYS) ? directionHelper(key) : null;
|
|
@@ -23,3 +23,17 @@ export const PLATFORMS = {
|
|
|
23
23
|
export const PLATFORM_KEYS = {
|
|
24
24
|
deviceClosedCaptioningEnabled: "device_closed_captioning_enabled",
|
|
25
25
|
};
|
|
26
|
+
|
|
27
|
+
export enum ZappPlatform {
|
|
28
|
+
Android = "android",
|
|
29
|
+
AndroidTV = "android_tv",
|
|
30
|
+
AmazonFireTV = "amazon",
|
|
31
|
+
iOS = "ios",
|
|
32
|
+
tvOS = "tvos",
|
|
33
|
+
Roku = "roku",
|
|
34
|
+
Tizen = "tizen",
|
|
35
|
+
Web = "web",
|
|
36
|
+
WebOS = "webos",
|
|
37
|
+
Vizio = "vizio",
|
|
38
|
+
Unknown = "unknown",
|
|
39
|
+
}
|
|
@@ -6,8 +6,10 @@ import {
|
|
|
6
6
|
isSamsungPlatform,
|
|
7
7
|
} from "@applicaster/zapp-react-dom-app/App/Loader/utils/platform";
|
|
8
8
|
|
|
9
|
-
import { PLATFORM_KEYS, PLATFORMS } from "./const";
|
|
9
|
+
import { PLATFORM_KEYS, PLATFORMS, ZappPlatform } from "./const";
|
|
10
10
|
import { createLogger, utilsLogger } from "../../logger";
|
|
11
|
+
import { getPlatform } from "../../reactUtils";
|
|
12
|
+
import { appStore } from "@applicaster/zapp-react-native-redux/AppStore";
|
|
11
13
|
|
|
12
14
|
const { log_debug } = createLogger({
|
|
13
15
|
category: "General",
|
|
@@ -15,6 +17,20 @@ const { log_debug } = createLogger({
|
|
|
15
17
|
parent: utilsLogger,
|
|
16
18
|
});
|
|
17
19
|
|
|
20
|
+
export const getZappPlatform = (): ZappPlatform => {
|
|
21
|
+
const platform = appStore.get("appData")?.platform;
|
|
22
|
+
|
|
23
|
+
switch (platform) {
|
|
24
|
+
case "amazon_fire_tv":
|
|
25
|
+
return ZappPlatform.AmazonFireTV;
|
|
26
|
+
case ZappPlatform.Vizio:
|
|
27
|
+
return ZappPlatform.Vizio;
|
|
28
|
+
default:
|
|
29
|
+
// TODO: Use only ZappPlatform enum
|
|
30
|
+
return getPlatform() as ZappPlatform;
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
|
|
18
34
|
export const getUserAgent = () => window?.navigator?.userAgent;
|
|
19
35
|
|
|
20
36
|
/** VIZIO UTILS */
|
|
@@ -61,13 +77,22 @@ export class ClosedCaptioningManager {
|
|
|
61
77
|
|
|
62
78
|
private constructor() {
|
|
63
79
|
this.initialize();
|
|
80
|
+
|
|
81
|
+
window["vizioDebug"] = {
|
|
82
|
+
setCCEnabled: (isCCEnabled: boolean) => {
|
|
83
|
+
this.ccState$.next(isCCEnabled);
|
|
84
|
+
},
|
|
85
|
+
};
|
|
64
86
|
}
|
|
65
87
|
|
|
66
88
|
static getInstance(): ClosedCaptioningManager {
|
|
67
89
|
if (!ClosedCaptioningManager.ccManagerInstance) {
|
|
90
|
+
log_debug("ClosedCaptioningManager: Creating new instance");
|
|
68
91
|
ClosedCaptioningManager.ccManagerInstance = new ClosedCaptioningManager();
|
|
69
92
|
}
|
|
70
93
|
|
|
94
|
+
log_debug("ClosedCaptioningManager: Returning existing instance");
|
|
95
|
+
|
|
71
96
|
return ClosedCaptioningManager.ccManagerInstance;
|
|
72
97
|
}
|
|
73
98
|
|
|
@@ -76,23 +101,41 @@ export class ClosedCaptioningManager {
|
|
|
76
101
|
PLATFORM_KEYS.deviceClosedCaptioningEnabled
|
|
77
102
|
);
|
|
78
103
|
|
|
79
|
-
log_debug("Initializing closed captioning manager", { existingState });
|
|
80
|
-
|
|
81
104
|
if (existingState !== null) {
|
|
105
|
+
log_debug(
|
|
106
|
+
`ClosedCaptioningManager: Setting initial state to ${existingState}`
|
|
107
|
+
);
|
|
108
|
+
|
|
82
109
|
this.ccState$.next(existingState === "true" || existingState === true);
|
|
110
|
+
} else {
|
|
111
|
+
log_debug("ClosedCaptioningManager: No existing state found");
|
|
83
112
|
}
|
|
84
113
|
|
|
85
114
|
if (hasVizioAPIs()) {
|
|
115
|
+
log_debug("ClosedCaptioningManager: Setting closed captioning handler");
|
|
116
|
+
|
|
86
117
|
window.VIZIO!.setClosedCaptionHandler((isCCEnabled) => {
|
|
87
|
-
log_debug(
|
|
118
|
+
log_debug(
|
|
119
|
+
"ClosedCaptioningManager: There was a change in the CC state",
|
|
120
|
+
{ isCCEnabled }
|
|
121
|
+
);
|
|
88
122
|
|
|
89
123
|
sessionStorage.setItem(
|
|
90
124
|
PLATFORM_KEYS.deviceClosedCaptioningEnabled,
|
|
91
125
|
isCCEnabled
|
|
92
126
|
);
|
|
93
127
|
|
|
128
|
+
log_debug(
|
|
129
|
+
"ClosedCaptioningManager: Saved CC state to session storage",
|
|
130
|
+
{ isCCEnabled }
|
|
131
|
+
);
|
|
132
|
+
|
|
94
133
|
this.ccState$.next(isCCEnabled);
|
|
95
134
|
});
|
|
135
|
+
} else {
|
|
136
|
+
log_debug(
|
|
137
|
+
"ClosedCaptioningManager: Vizio APIs are not available, skipping setup"
|
|
138
|
+
);
|
|
96
139
|
}
|
|
97
140
|
}
|
|
98
141
|
|
|
@@ -452,7 +452,7 @@ export class PlayerManager {
|
|
|
452
452
|
if (this.castingReceiver) {
|
|
453
453
|
this.castingReceiver.closeNativePlayer();
|
|
454
454
|
} else {
|
|
455
|
-
this.getActivePlayer()
|
|
455
|
+
this.getActivePlayer()?.closeNativePlayer();
|
|
456
456
|
this.invokeHandler("close");
|
|
457
457
|
}
|
|
458
458
|
}
|
|
@@ -154,8 +154,8 @@ export class Player {
|
|
|
154
154
|
this.isSeeking()
|
|
155
155
|
? this.playerState.seekPosition
|
|
156
156
|
: this.playerState.adState
|
|
157
|
-
|
|
158
|
-
|
|
157
|
+
? this.playerState.adState.adPosition
|
|
158
|
+
: this.playerState.contentPosition;
|
|
159
159
|
|
|
160
160
|
getEntry = (): ZappEntry | null => this.entry;
|
|
161
161
|
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
import { isEqual } from "../..";
|
|
2
|
+
|
|
3
|
+
const array = [1, 2, 3];
|
|
4
|
+
const sameProp = "sameProp";
|
|
5
|
+
|
|
6
|
+
describe("isEqual - comprehensive tests", () => {
|
|
7
|
+
describe("all conditions together", () => {
|
|
8
|
+
it("all conditions checked and not equal", () => {
|
|
9
|
+
const prev = {
|
|
10
|
+
byValueProp: { a: 1, b: 2 },
|
|
11
|
+
byRefProp: array,
|
|
12
|
+
byComparatorProp: "test",
|
|
13
|
+
extraProp1: 123,
|
|
14
|
+
extraProp2: sameProp,
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const next = {
|
|
18
|
+
byValueProp: { a: 1, b: 2 },
|
|
19
|
+
byRefProp: array,
|
|
20
|
+
byComparatorProp: "different",
|
|
21
|
+
extraProp1: 456,
|
|
22
|
+
extraProp2: sameProp,
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const customComparator = jest.fn().mockReturnValue(true);
|
|
26
|
+
|
|
27
|
+
const result = isEqual({
|
|
28
|
+
byValue: ["byValueProp"],
|
|
29
|
+
byReference: ["byRefProp"],
|
|
30
|
+
byComparator: {
|
|
31
|
+
comparator: customComparator,
|
|
32
|
+
keys: ["byComparatorProp"],
|
|
33
|
+
},
|
|
34
|
+
compareRemainingKeysBySameValue: true,
|
|
35
|
+
})(prev, next);
|
|
36
|
+
|
|
37
|
+
expect(result).toBe(false);
|
|
38
|
+
|
|
39
|
+
expect(customComparator).toHaveBeenCalledWith(
|
|
40
|
+
prev.byComparatorProp,
|
|
41
|
+
next.byComparatorProp
|
|
42
|
+
);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("all conditions pass with exact remaining keys check", () => {
|
|
46
|
+
const prev = {
|
|
47
|
+
byValueProp: { a: 1, b: 2 },
|
|
48
|
+
byRefProp: array,
|
|
49
|
+
byComparatorProp: "test",
|
|
50
|
+
extraProp: sameProp,
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const next = {
|
|
54
|
+
byValueProp: { a: 1, b: 2 },
|
|
55
|
+
byRefProp: array,
|
|
56
|
+
byComparatorProp: "test",
|
|
57
|
+
extraProp: sameProp,
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const customComparator = jest.fn().mockReturnValue(true);
|
|
61
|
+
|
|
62
|
+
const result = isEqual({
|
|
63
|
+
byValue: ["byValueProp"],
|
|
64
|
+
byReference: ["byRefProp"],
|
|
65
|
+
byComparator: {
|
|
66
|
+
comparator: customComparator,
|
|
67
|
+
keys: ["byComparatorProp"],
|
|
68
|
+
},
|
|
69
|
+
compareRemainingKeysBySameValue: true,
|
|
70
|
+
})(prev, next);
|
|
71
|
+
|
|
72
|
+
expect(result).toBe(true);
|
|
73
|
+
|
|
74
|
+
expect(customComparator).toHaveBeenCalledWith(
|
|
75
|
+
prev.byComparatorProp,
|
|
76
|
+
next.byComparatorProp
|
|
77
|
+
);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it("byReference check fails first (early return)", () => {
|
|
81
|
+
const prev = {
|
|
82
|
+
byValueProp: { a: 1, b: 2 },
|
|
83
|
+
byRefProp: array,
|
|
84
|
+
byComparatorProp: sameProp,
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const next = {
|
|
88
|
+
byValueProp: { a: 1, b: 2 },
|
|
89
|
+
byRefProp: [1, 2, 3],
|
|
90
|
+
byComparatorProp: sameProp,
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const customComparator = jest.fn().mockReturnValue(true);
|
|
94
|
+
|
|
95
|
+
const result = isEqual({
|
|
96
|
+
byValue: ["byValueProp"],
|
|
97
|
+
byReference: ["byRefProp"],
|
|
98
|
+
byComparator: {
|
|
99
|
+
comparator: customComparator,
|
|
100
|
+
keys: ["byComparatorProp"],
|
|
101
|
+
},
|
|
102
|
+
})(prev, next);
|
|
103
|
+
|
|
104
|
+
expect(result).toBe(false);
|
|
105
|
+
expect(customComparator).not.toHaveBeenCalled();
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it("byValue check fails after byReference passes", () => {
|
|
109
|
+
const prev = {
|
|
110
|
+
byValueProp: { a: 1, b: 2 },
|
|
111
|
+
byRefProp: array,
|
|
112
|
+
byComparatorProp: sameProp,
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const next = {
|
|
116
|
+
byValueProp: { a: 1, b: 3 },
|
|
117
|
+
byRefProp: array,
|
|
118
|
+
byComparatorProp: sameProp,
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
const customComparator = jest.fn().mockReturnValue(true);
|
|
122
|
+
|
|
123
|
+
const result = isEqual({
|
|
124
|
+
byValue: ["byValueProp"],
|
|
125
|
+
byReference: ["byRefProp"],
|
|
126
|
+
byComparator: {
|
|
127
|
+
comparator: customComparator,
|
|
128
|
+
keys: ["byComparatorProp"],
|
|
129
|
+
},
|
|
130
|
+
})(prev, next);
|
|
131
|
+
|
|
132
|
+
expect(result).toBe(false);
|
|
133
|
+
expect(customComparator).not.toHaveBeenCalled();
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it("byComparator check fails after other checks pass", () => {
|
|
137
|
+
const prev = {
|
|
138
|
+
byValueProp: { a: 1, b: 2 },
|
|
139
|
+
byRefProp: array,
|
|
140
|
+
byComparatorProp: "test",
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
const next = {
|
|
144
|
+
byValueProp: { a: 1, b: 2 },
|
|
145
|
+
byRefProp: array,
|
|
146
|
+
byComparatorProp: "different",
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
const customComparator = jest.fn().mockReturnValue(false);
|
|
150
|
+
|
|
151
|
+
const result = isEqual({
|
|
152
|
+
byValue: ["byValueProp"],
|
|
153
|
+
byReference: ["byRefProp"],
|
|
154
|
+
byComparator: {
|
|
155
|
+
comparator: customComparator,
|
|
156
|
+
keys: ["byComparatorProp"],
|
|
157
|
+
},
|
|
158
|
+
})(prev, next);
|
|
159
|
+
|
|
160
|
+
expect(result).toBe(false);
|
|
161
|
+
|
|
162
|
+
expect(customComparator).toHaveBeenCalledWith(
|
|
163
|
+
prev.byComparatorProp,
|
|
164
|
+
next.byComparatorProp
|
|
165
|
+
);
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it("remaining keys comparison fails", () => {
|
|
169
|
+
const sharedArray = [1, 2, 3];
|
|
170
|
+
|
|
171
|
+
const prev = {
|
|
172
|
+
byValueProp: { a: 1, b: 2 },
|
|
173
|
+
byRefProp: sharedArray,
|
|
174
|
+
byComparatorProp: "test",
|
|
175
|
+
extraProp: "original",
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
const next = {
|
|
179
|
+
byValueProp: { a: 1, b: 2 },
|
|
180
|
+
byRefProp: sharedArray,
|
|
181
|
+
byComparatorProp: "different",
|
|
182
|
+
extraProp: "changed",
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
const customComparator = jest.fn().mockReturnValue(true);
|
|
186
|
+
|
|
187
|
+
const result = isEqual({
|
|
188
|
+
byValue: ["byValueProp"],
|
|
189
|
+
byReference: ["byRefProp"],
|
|
190
|
+
byComparator: {
|
|
191
|
+
comparator: customComparator,
|
|
192
|
+
keys: ["byComparatorProp"],
|
|
193
|
+
},
|
|
194
|
+
compareRemainingKeysBySameValue: true,
|
|
195
|
+
})(prev, next);
|
|
196
|
+
|
|
197
|
+
expect(result).toBe(false);
|
|
198
|
+
expect(customComparator).toHaveBeenCalled();
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it("with debug flag", () => {
|
|
202
|
+
const consoleSpy = jest.spyOn(console, "log").mockImplementation();
|
|
203
|
+
|
|
204
|
+
const prev = {
|
|
205
|
+
byValueProp: { a: 1, b: 2 },
|
|
206
|
+
byRefProp: array,
|
|
207
|
+
byComparatorProp: "test",
|
|
208
|
+
extraProp: sameProp,
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
const next = {
|
|
212
|
+
byValueProp: { a: 1, b: 2 },
|
|
213
|
+
byRefProp: array,
|
|
214
|
+
byComparatorProp: "different",
|
|
215
|
+
extraProp: sameProp,
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
const customComparator = jest.fn().mockReturnValue(true);
|
|
219
|
+
|
|
220
|
+
const result = isEqual({
|
|
221
|
+
byValue: ["byValueProp"],
|
|
222
|
+
byReference: ["byRefProp"],
|
|
223
|
+
byComparator: {
|
|
224
|
+
comparator: customComparator,
|
|
225
|
+
keys: ["byComparatorProp"],
|
|
226
|
+
},
|
|
227
|
+
compareRemainingKeysBySameValue: true,
|
|
228
|
+
debug: true,
|
|
229
|
+
})(prev, next);
|
|
230
|
+
|
|
231
|
+
expect(result).toBe(true);
|
|
232
|
+
expect(consoleSpy).toHaveBeenCalled();
|
|
233
|
+
|
|
234
|
+
consoleSpy.mockRestore();
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
it("handles missing properties", () => {
|
|
238
|
+
const prev = {
|
|
239
|
+
byValueProp: { a: 1, b: 2 },
|
|
240
|
+
byRefProp: [1, 2, 3],
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
const next = {
|
|
244
|
+
byValueProp: { a: 1, b: 2 },
|
|
245
|
+
byComparatorProp: "test",
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
const customComparator = jest.fn().mockReturnValue(true);
|
|
249
|
+
|
|
250
|
+
const result = isEqual({
|
|
251
|
+
byValue: ["byValueProp"],
|
|
252
|
+
byReference: ["byRefProp"],
|
|
253
|
+
byComparator: {
|
|
254
|
+
comparator: customComparator,
|
|
255
|
+
keys: ["byComparatorProp"],
|
|
256
|
+
},
|
|
257
|
+
})(prev, next);
|
|
258
|
+
|
|
259
|
+
expect(result).toBe(false);
|
|
260
|
+
expect(customComparator).not.toHaveBeenCalled();
|
|
261
|
+
});
|
|
262
|
+
});
|
|
263
|
+
});
|
package/equalUtils/index.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
/* eslint no-console: off */
|
|
2
2
|
|
|
3
|
-
import
|
|
3
|
+
import { equals } from "ramda";
|
|
4
|
+
|
|
5
|
+
const alwaysTrue = () => true;
|
|
4
6
|
|
|
5
7
|
export const isParentFocusEqual = (
|
|
6
8
|
prevParentFocus: ParentFocus,
|
|
@@ -18,7 +20,7 @@ export const isEqual =
|
|
|
18
20
|
({
|
|
19
21
|
byValue = [],
|
|
20
22
|
byReference = [],
|
|
21
|
-
byComparator = { comparator:
|
|
23
|
+
byComparator = { comparator: alwaysTrue, keys: [] },
|
|
22
24
|
debug = false,
|
|
23
25
|
compareRemainingKeysBySameValue = false,
|
|
24
26
|
}: {
|
|
@@ -29,42 +31,59 @@ export const isEqual =
|
|
|
29
31
|
compareRemainingKeysBySameValue?: boolean;
|
|
30
32
|
}) =>
|
|
31
33
|
(prev, next) => {
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
34
|
+
for (const key of byReference) {
|
|
35
|
+
if (prev[key] !== next[key]) {
|
|
36
|
+
if (debug) console.log({ isEqualByReference: false });
|
|
35
37
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
39
41
|
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
(key) => byComparator.comparator(prev[key], next[key]),
|
|
44
|
-
byComparator.keys
|
|
45
|
-
);
|
|
42
|
+
for (const key of byValue) {
|
|
43
|
+
if (!equals(prev[key], next[key])) {
|
|
44
|
+
if (debug) console.log({ isEqualByValue: false });
|
|
46
45
|
|
|
47
|
-
|
|
48
|
-
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
for (const key of byComparator.keys) {
|
|
51
|
+
if (!byComparator.comparator(prev[key], next[key])) {
|
|
52
|
+
if (debug) console.log({ isEqualByComparator: false });
|
|
53
|
+
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
49
56
|
}
|
|
50
57
|
|
|
51
58
|
if (compareRemainingKeysBySameValue) {
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
59
|
+
const allKeys = new Set([...Object.keys(prev), ...Object.keys(next)]);
|
|
60
|
+
|
|
61
|
+
const declaredKeys = new Set([
|
|
62
|
+
...byValue,
|
|
63
|
+
...byReference,
|
|
64
|
+
...byComparator.keys,
|
|
65
|
+
]);
|
|
56
66
|
|
|
57
|
-
const
|
|
58
|
-
Object.is(prev[key], next[key])
|
|
59
|
-
|
|
67
|
+
for (const key of Array.from(allKeys)) {
|
|
68
|
+
if (!declaredKeys.has(key) && !Object.is(prev[key], next[key])) {
|
|
69
|
+
if (debug) console.log({ isRestKeysEqualAllBySameValue: false });
|
|
60
70
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// if we made it here, everything matched
|
|
77
|
+
if (debug) {
|
|
78
|
+
console.log({
|
|
79
|
+
isEqualByValue: true,
|
|
80
|
+
isEqualByReference: true,
|
|
81
|
+
isEqualByComparator: true,
|
|
82
|
+
isRestKeysEqualAllBySameValue: compareRemainingKeysBySameValue
|
|
83
|
+
? true
|
|
84
|
+
: undefined,
|
|
85
|
+
});
|
|
67
86
|
}
|
|
68
87
|
|
|
69
|
-
return
|
|
88
|
+
return true;
|
|
70
89
|
};
|
|
@@ -250,7 +250,17 @@ class FocusManager {
|
|
|
250
250
|
}
|
|
251
251
|
|
|
252
252
|
onDisableFocusChange = (id) => {
|
|
253
|
-
|
|
253
|
+
const isFocused = FocusManager.instance.isFocused(id);
|
|
254
|
+
|
|
255
|
+
const isDescendantFocused = (() => {
|
|
256
|
+
try {
|
|
257
|
+
return FocusManager.instance.isAnyDescendantFocused(id);
|
|
258
|
+
} catch (_e) {
|
|
259
|
+
return false;
|
|
260
|
+
}
|
|
261
|
+
})();
|
|
262
|
+
|
|
263
|
+
if (isFocused || isDescendantFocused) {
|
|
254
264
|
// Move focus to next one
|
|
255
265
|
const nextFocus = FocusManager.instance.focused?.props
|
|
256
266
|
?.nextFocusDown as string;
|
package/focusManager/index.ts
CHANGED
|
@@ -8,6 +8,8 @@ import { isNilOrEmpty } from "../reactUtils/helpers";
|
|
|
8
8
|
import { isAndroidTVPlatform } from "../reactUtils";
|
|
9
9
|
import { noop } from "../functionUtils";
|
|
10
10
|
|
|
11
|
+
const EMPTY_GROUP_COMPONENT = "empty_group_component";
|
|
12
|
+
|
|
11
13
|
export function useFocusManager() {
|
|
12
14
|
return focusManager;
|
|
13
15
|
}
|
|
@@ -74,6 +76,14 @@ export function useFocusEffect(onFocus, focusWatcher) {
|
|
|
74
76
|
}, focusWatcher);
|
|
75
77
|
}
|
|
76
78
|
|
|
79
|
+
export function shouldShowEmptyGroup(component) {
|
|
80
|
+
return (
|
|
81
|
+
component?.ui_components?.filter(
|
|
82
|
+
({ component_type }) => component_type === EMPTY_GROUP_COMPONENT
|
|
83
|
+
)?.length > 0
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
77
87
|
export const isFocusable = (component: ZappEntry | ZappUIComponent | any) =>
|
|
78
88
|
R.tryCatch(
|
|
79
89
|
R.compose(
|
|
@@ -101,15 +111,42 @@ export const useIsFocusable = (props: Props): void => {
|
|
|
101
111
|
component.rules.hide_component_if_data_is_empty
|
|
102
112
|
);
|
|
103
113
|
|
|
114
|
+
const isLoadingData = !!zappPipesData?.loading;
|
|
115
|
+
|
|
104
116
|
const hasNoData =
|
|
105
117
|
isNilOrEmpty(zappPipesData?.data) ||
|
|
106
118
|
isNilOrEmpty(zappPipesData?.data?.entry);
|
|
107
119
|
|
|
120
|
+
const hasDataError = !!zappPipesData?.error;
|
|
121
|
+
|
|
122
|
+
const hasEmptyGroup = shouldShowEmptyGroup(component);
|
|
123
|
+
|
|
124
|
+
const isGallery = component?.component_type === "gallery-qb";
|
|
125
|
+
|
|
126
|
+
const isHiddenDueToEmptyData =
|
|
127
|
+
!isLoadingData && hasNoData && hideIfDataEmpty && !hasEmptyGroup;
|
|
128
|
+
|
|
129
|
+
const defaultFocusableLogic =
|
|
130
|
+
componentCellsFocusable && (!hasNoData || !hideIfDataEmpty);
|
|
131
|
+
|
|
108
132
|
React.useEffect(() => {
|
|
109
133
|
if (isAndroidTVPlatform()) {
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
)
|
|
134
|
+
let isFocusableNew = false;
|
|
135
|
+
|
|
136
|
+
if (isGallery) {
|
|
137
|
+
isFocusableNew = true;
|
|
138
|
+
} else if (isFocusable(component)) {
|
|
139
|
+
isFocusableNew = defaultFocusableLogic;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
setIsFocusable(isFocusableNew);
|
|
113
143
|
}
|
|
114
|
-
}, [
|
|
144
|
+
}, [
|
|
145
|
+
defaultFocusableLogic,
|
|
146
|
+
isHiddenDueToEmptyData,
|
|
147
|
+
isGallery,
|
|
148
|
+
setIsFocusable,
|
|
149
|
+
hasDataError,
|
|
150
|
+
hasEmptyGroup,
|
|
151
|
+
]);
|
|
115
152
|
};
|