@applicaster/zapp-react-native-utils 12.0.0-alpha.6115034951 → 12.0.0-alpha.6158046395
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/AnalyticPlayerListener.ts +141 -0
- package/analyticsUtils/PlayerAnalyticsManager.ts +90 -0
- package/analyticsUtils/{analyticsTracker.ts → playerAnalyticsTracker.ts} +19 -10
- 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/index.ts +0 -1
- package/appUtils/index.js +2 -0
- package/appUtils/platform/platformUtils.ts +74 -79
- package/appUtils/playerManager/index.ts +9 -27
- package/appUtils/playerManager/player.ts +1 -0
- package/appUtils/playerManager/playerHooks/usePlayerCurrentTime.tsx +57 -0
- package/appUtils/playerManager/playerNative.ts +1 -1
- package/appUtils/playerManager/usePlayerControllerSetup.tsx +2 -1
- package/appUtils/playerManager/usePlayerState.tsx +19 -34
- package/arrayUtils/index.ts +11 -0
- package/cellUtils/index.ts +38 -5
- package/focusManager/FocusManager.ts +20 -5
- package/focusManager/__tests__/FocusManager.test.ts +0 -1
- package/functionUtils/index.ts +38 -0
- package/index.d.ts +15 -0
- package/manifestUtils/defaultManifestConfigurations/player.js +121 -0
- package/manifestUtils/keys.js +2 -1
- package/navigationUtils/index.ts +1 -1
- package/numberUtils/__tests__/toNumber.test.ts +2 -2
- package/numberUtils/__tests__/toPositiveNumberWithDefault.test.ts +66 -0
- package/numberUtils/__tests__/toPositiveNumberWithDefaultZero.test.ts +66 -0
- package/numberUtils/index.ts +20 -0
- package/package.json +2 -2
- package/playerUtils/configurationGenerator.ts +120 -0
- package/playerUtils/imaScriptDom.ts +59 -0
- package/reactHooks/cell-click/index.ts +17 -3
- package/reactHooks/debugging/__tests__/index.test.js +81 -11
- package/reactHooks/feed/__tests__/useBatchLoading.test.tsx +69 -6
- package/reactHooks/feed/useBatchLoading.ts +52 -28
- package/reactHooks/layout/useDimensions/__tests__/useDimensions.test.ts +134 -0
- package/reactHooks/layout/useDimensions/useDimensions.ts +2 -0
- package/reactHooks/navigation/index.ts +1 -1
- package/reactHooks/player/useAutoSeek.tsx +9 -7
- package/reactHooks/resolvers/useCellResolver.ts +5 -1
- package/reactHooks/useConfiguration.ts +2 -2
- package/reactUtils/index.ts +4 -2
- package/testUtils/index.tsx +42 -9
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { BehaviorSubject } from "rxjs";
|
|
2
2
|
|
|
3
3
|
import { sessionStorage } from "@applicaster/zapp-react-native-bridge/ZappStorage/SessionStorage";
|
|
4
|
-
import {
|
|
5
|
-
|
|
4
|
+
import {
|
|
5
|
+
isLgPlatform,
|
|
6
|
+
isSamsungPlatform,
|
|
7
|
+
} from "@applicaster/zapp-react-dom-app/App/Loader/utils/platform";
|
|
6
8
|
|
|
7
9
|
import { PLATFORM_KEYS, PLATFORMS } from "./const";
|
|
8
10
|
import { createLogger, utilsLogger } from "../../logger";
|
|
@@ -11,6 +13,9 @@ declare global {
|
|
|
11
13
|
interface Window {
|
|
12
14
|
VIZIO?: {
|
|
13
15
|
exitApplication: () => void;
|
|
16
|
+
Chromevox: {
|
|
17
|
+
play: (text: string) => void;
|
|
18
|
+
};
|
|
14
19
|
setClosedCaptionHandler: (
|
|
15
20
|
callback: (isCCEnabled: boolean) => void
|
|
16
21
|
) => void;
|
|
@@ -18,14 +23,13 @@ declare global {
|
|
|
18
23
|
}
|
|
19
24
|
}
|
|
20
25
|
|
|
21
|
-
const { log_debug
|
|
26
|
+
const { log_debug } = createLogger({
|
|
22
27
|
category: "General",
|
|
23
28
|
subsystem: "PlatformUtils",
|
|
24
29
|
parent: utilsLogger,
|
|
25
30
|
});
|
|
26
31
|
|
|
27
|
-
export const getUserAgent = () =>
|
|
28
|
-
window?.navigator?.userAgent || global?.navigator?.userAgent;
|
|
32
|
+
export const getUserAgent = () => window?.navigator?.userAgent;
|
|
29
33
|
|
|
30
34
|
/** VIZIO UTILS */
|
|
31
35
|
|
|
@@ -38,6 +42,8 @@ export const getUserAgent = () =>
|
|
|
38
42
|
export const isVizioPlatform = () => {
|
|
39
43
|
const userAgent = getUserAgent();
|
|
40
44
|
|
|
45
|
+
if (!userAgent) return false;
|
|
46
|
+
|
|
41
47
|
const isVizioAgent =
|
|
42
48
|
userAgent.includes(PLATFORMS.vizio) ||
|
|
43
49
|
userAgent.includes(PLATFORMS.smartcast) ||
|
|
@@ -63,7 +69,7 @@ export const hasVizioAPIs = () => {
|
|
|
63
69
|
* Manages device based closed captioning state
|
|
64
70
|
* Returns an observable that emits the current state and updates whenever it changes
|
|
65
71
|
*/
|
|
66
|
-
class ClosedCaptioningManager {
|
|
72
|
+
export class ClosedCaptioningManager {
|
|
67
73
|
private ccState$ = new BehaviorSubject<boolean>(false);
|
|
68
74
|
private static ccManagerInstance: ClosedCaptioningManager;
|
|
69
75
|
|
|
@@ -128,96 +134,85 @@ export const getClosedCaptionState = () => {
|
|
|
128
134
|
return ccManager.getStateAsObservable();
|
|
129
135
|
};
|
|
130
136
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
static
|
|
138
|
-
if (!TextTrackManager.instance) {
|
|
139
|
-
TextTrackManager.instance = new TextTrackManager();
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
return TextTrackManager.instance;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
initialize(controller: PlayerWebController) {
|
|
146
|
-
if (!controller) {
|
|
147
|
-
throw new Error(
|
|
148
|
-
"Controller is required for TextTrackManager initialization"
|
|
149
|
-
);
|
|
150
|
-
}
|
|
137
|
+
/**
|
|
138
|
+
* Manages device based closed captioning state
|
|
139
|
+
* Returns an observable that emits the current state and updates whenever it changes
|
|
140
|
+
*/
|
|
141
|
+
export class TTSManager {
|
|
142
|
+
private ttsState$ = new BehaviorSubject<boolean>(false);
|
|
143
|
+
private static ttsManagerInstance: TTSManager;
|
|
151
144
|
|
|
152
|
-
|
|
145
|
+
private constructor() {
|
|
146
|
+
this.initialize();
|
|
153
147
|
}
|
|
154
148
|
|
|
155
|
-
|
|
156
|
-
if (!
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
return;
|
|
149
|
+
static getInstance(): TTSManager {
|
|
150
|
+
if (!TTSManager.ttsManagerInstance) {
|
|
151
|
+
TTSManager.ttsManagerInstance = new TTSManager();
|
|
160
152
|
}
|
|
161
153
|
|
|
162
|
-
|
|
163
|
-
const textTracks = this.controller.getTracksState().textTracks;
|
|
164
|
-
const preferredTextTrack = await this.getPreferredTextTrack(textTracks);
|
|
165
|
-
|
|
166
|
-
if (preferredTextTrack) {
|
|
167
|
-
this.controller.selectTrack(preferredTextTrack);
|
|
168
|
-
}
|
|
169
|
-
} else {
|
|
170
|
-
this.controller.selectTrack(null);
|
|
171
|
-
}
|
|
154
|
+
return TTSManager.ttsManagerInstance;
|
|
172
155
|
}
|
|
173
156
|
|
|
174
|
-
async
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
157
|
+
async initialize() {
|
|
158
|
+
if (!isVizioPlatform()) return;
|
|
159
|
+
|
|
160
|
+
document.addEventListener(
|
|
161
|
+
"VIZIO_TTS_ENABLED",
|
|
162
|
+
() => {
|
|
163
|
+
this.ttsState$.next(true);
|
|
164
|
+
},
|
|
165
|
+
false
|
|
166
|
+
);
|
|
178
167
|
|
|
179
|
-
|
|
180
|
-
"
|
|
181
|
-
|
|
168
|
+
document.addEventListener(
|
|
169
|
+
"VIZIO_TTS_DISABLED",
|
|
170
|
+
() => {
|
|
171
|
+
this.ttsState$.next(false);
|
|
172
|
+
},
|
|
173
|
+
false
|
|
182
174
|
);
|
|
175
|
+
}
|
|
183
176
|
|
|
184
|
-
|
|
177
|
+
getCurrentState(): boolean {
|
|
178
|
+
return this.ttsState$.getValue();
|
|
179
|
+
}
|
|
185
180
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
);
|
|
190
|
-
}
|
|
181
|
+
getStateAsObservable() {
|
|
182
|
+
return this.ttsState$.asObservable();
|
|
183
|
+
}
|
|
191
184
|
|
|
192
|
-
|
|
193
|
-
|
|
185
|
+
readText(text: string) {
|
|
186
|
+
if (isSamsungPlatform() && window.speechSynthesis) {
|
|
187
|
+
const utterance = new SpeechSynthesisUtterance(text);
|
|
188
|
+
window.speechSynthesis.speak(utterance);
|
|
194
189
|
}
|
|
195
190
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
191
|
+
if (isLgPlatform() && window.webOS?.service) {
|
|
192
|
+
try {
|
|
193
|
+
window.webOS.service.request("luna://com.webos.service.tts", {
|
|
194
|
+
method: "speak",
|
|
195
|
+
onFailure(error: any) {
|
|
196
|
+
log_debug("There was a failure setting up webOS TTS service", {
|
|
197
|
+
error,
|
|
198
|
+
});
|
|
199
|
+
},
|
|
200
|
+
onSuccess(response: any) {
|
|
201
|
+
log_debug("webOS TTS service is configured successfully", {
|
|
202
|
+
response,
|
|
203
|
+
});
|
|
204
|
+
},
|
|
205
|
+
parameters: {
|
|
206
|
+
text,
|
|
207
|
+
},
|
|
208
|
+
});
|
|
209
|
+
} catch (error) {
|
|
210
|
+
log_debug("webOS TTS service error", { error });
|
|
212
211
|
}
|
|
213
212
|
}
|
|
214
213
|
|
|
215
|
-
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
isTextTrackEnabled(): boolean {
|
|
219
|
-
if (!this.controller) return false;
|
|
214
|
+
if (!window.VIZIO?.Chromevox) return;
|
|
220
215
|
|
|
221
|
-
|
|
216
|
+
window.VIZIO.Chromevox.play(text);
|
|
222
217
|
}
|
|
223
218
|
}
|
|
@@ -3,7 +3,6 @@ import * as R from "ramda";
|
|
|
3
3
|
import { subscriber } from "../../functionUtils";
|
|
4
4
|
import { utilsLogger, createLogger } from "../../logger";
|
|
5
5
|
import { Player } from "./player";
|
|
6
|
-
import { AnalyticsTracker } from "../../analyticsUtils/analyticsTracker";
|
|
7
6
|
import {
|
|
8
7
|
hasTizenAPIs,
|
|
9
8
|
subscribeToDeviceNetworkChanges,
|
|
@@ -37,7 +36,10 @@ export interface PlayerLifecycleListener {
|
|
|
37
36
|
}
|
|
38
37
|
|
|
39
38
|
type PlayersMap = {
|
|
40
|
-
[id: string]: {
|
|
39
|
+
[id: string]: {
|
|
40
|
+
player: Player;
|
|
41
|
+
isInViewport: boolean;
|
|
42
|
+
};
|
|
41
43
|
};
|
|
42
44
|
|
|
43
45
|
export class PlayerManager {
|
|
@@ -46,7 +48,6 @@ export class PlayerManager {
|
|
|
46
48
|
private playersMap: PlayersMap = {};
|
|
47
49
|
private manager = subscriber();
|
|
48
50
|
private castingReceiver: Player;
|
|
49
|
-
private analyticsTracker: AnalyticsTracker;
|
|
50
51
|
private lifecycleListeners: PlayerLifecycleListener[] = [];
|
|
51
52
|
|
|
52
53
|
public static getInstance() {
|
|
@@ -57,7 +58,7 @@ export class PlayerManager {
|
|
|
57
58
|
return PlayerManager.instance;
|
|
58
59
|
}
|
|
59
60
|
|
|
60
|
-
public getActivePlayerId = ():
|
|
61
|
+
public getActivePlayerId = (): string | null => {
|
|
61
62
|
return this.activePlayerId;
|
|
62
63
|
};
|
|
63
64
|
|
|
@@ -102,7 +103,10 @@ export class PlayerManager {
|
|
|
102
103
|
}
|
|
103
104
|
|
|
104
105
|
private addPlayer = (id: string, player: Player) => {
|
|
105
|
-
this.playersMap[id] = {
|
|
106
|
+
this.playersMap[id] = {
|
|
107
|
+
player: player,
|
|
108
|
+
isInViewport: false,
|
|
109
|
+
};
|
|
106
110
|
};
|
|
107
111
|
|
|
108
112
|
public isPlayerRegistered = (id: string) => !!this.playersMap[id];
|
|
@@ -251,28 +255,6 @@ export class PlayerManager {
|
|
|
251
255
|
this.castingReceiver = null;
|
|
252
256
|
}
|
|
253
257
|
|
|
254
|
-
/**
|
|
255
|
-
* Handles analytic events, generates a new analytics tracker and transmits events
|
|
256
|
-
* Passes entry data, and playerState to the tracker for additional context
|
|
257
|
-
* The end result is an event formatted and passed to AnalyticsBridge.postEvent
|
|
258
|
-
* When we get the synthetic event we need to pull the data in the e object
|
|
259
|
-
* e contains the return value from native
|
|
260
|
-
*/
|
|
261
|
-
public handleAnalyticEvent(event, data = {}, entry: ZappEntry) {
|
|
262
|
-
const playerState = this.getActivePlayer()?.getState();
|
|
263
|
-
|
|
264
|
-
if (!this.analyticsTracker?.handleAnalyticEvent) {
|
|
265
|
-
this.analyticsTracker = new AnalyticsTracker(entry);
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
this.analyticsTracker?.handleAnalyticEvent?.(
|
|
269
|
-
event,
|
|
270
|
-
data,
|
|
271
|
-
entry,
|
|
272
|
-
playerState
|
|
273
|
-
);
|
|
274
|
-
}
|
|
275
|
-
|
|
276
258
|
/**
|
|
277
259
|
* Method retrieves the instantiated player instance controller
|
|
278
260
|
* Instance controller exposes methods like getContentDuration, getContentPosision
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { useState, useEffect, useCallback } from "react";
|
|
2
|
+
import { Player } from "../player";
|
|
3
|
+
import { usePlayer } from "../usePlayer";
|
|
4
|
+
import { useThrottle } from "../../../functionUtils";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
*
|
|
8
|
+
* @param listenerId - A unique identifier for the listener.
|
|
9
|
+
* @param playerId - (Optional) The ID of the player to observe.
|
|
10
|
+
* @returns The current playback time in seconds.
|
|
11
|
+
*/
|
|
12
|
+
export const usePlayerCurrentTime = (
|
|
13
|
+
listenerId: string,
|
|
14
|
+
playerId?: string
|
|
15
|
+
): number | null => {
|
|
16
|
+
const [currentTime, setCurrentTime] = useState<number | null>(null);
|
|
17
|
+
const player: Player = usePlayer(playerId);
|
|
18
|
+
|
|
19
|
+
const updateCurrentTime = useCallback(() => {
|
|
20
|
+
if (player) {
|
|
21
|
+
const newCurrentTime = player.getContentPosition();
|
|
22
|
+
|
|
23
|
+
setCurrentTime((prevTime) =>
|
|
24
|
+
prevTime !== newCurrentTime ? newCurrentTime : prevTime
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
}, [player]);
|
|
28
|
+
|
|
29
|
+
const { throttledFunction: throttledUpdate, cancel } = useThrottle(
|
|
30
|
+
updateCurrentTime,
|
|
31
|
+
1000
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
if (player) {
|
|
36
|
+
throttledUpdate();
|
|
37
|
+
|
|
38
|
+
const listeners = {
|
|
39
|
+
onVideoProgress: throttledUpdate,
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const id = `${listenerId}_player_current_time`;
|
|
43
|
+
|
|
44
|
+
player.addListener({
|
|
45
|
+
id: id,
|
|
46
|
+
listener: listeners,
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
return () => {
|
|
50
|
+
player.removeListener(id);
|
|
51
|
+
cancel();
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
}, [player, listenerId, throttledUpdate, cancel]);
|
|
55
|
+
|
|
56
|
+
return currentTime;
|
|
57
|
+
};
|
|
@@ -61,7 +61,7 @@ export class PlayerNative extends Player {
|
|
|
61
61
|
currentPlayerComponent = (): ReactComponent<PlayerProps> =>
|
|
62
62
|
this.playerComponent?.current;
|
|
63
63
|
|
|
64
|
-
getState = () => this.
|
|
64
|
+
getState = () => this.playerState;
|
|
65
65
|
|
|
66
66
|
// TODO: Try to add module interface
|
|
67
67
|
invokeNativeFunction = (funcName: string, ...params: any[]): boolean => {
|
|
@@ -58,7 +58,7 @@ export const usePlayerControllerSetup = ({
|
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
if (!playerManager) {
|
|
61
|
-
return
|
|
61
|
+
return;
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
if (playerManager.isPlayerRegistered(playerId)) {
|
|
@@ -72,6 +72,7 @@ export const usePlayerControllerSetup = ({
|
|
|
72
72
|
|
|
73
73
|
return () => {
|
|
74
74
|
playerManager.unregisterPlayer(playerId);
|
|
75
|
+
playerController.destroy?.();
|
|
75
76
|
};
|
|
76
77
|
}, [playerId, playerController]);
|
|
77
78
|
|
|
@@ -16,24 +16,28 @@ export const usePlayerState = (
|
|
|
16
16
|
listenerId: string,
|
|
17
17
|
playerId?: string
|
|
18
18
|
): PlayerState => {
|
|
19
|
-
const [
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
19
|
+
const [state, setState] = React.useState<PlayerState>({
|
|
20
|
+
currentTime: null,
|
|
21
|
+
duration: null,
|
|
22
|
+
seekableDuration: null,
|
|
23
|
+
isLive: null,
|
|
24
|
+
isPaused: null,
|
|
25
|
+
isBuffering: false,
|
|
26
|
+
isReadyToPlay: false,
|
|
27
|
+
});
|
|
26
28
|
|
|
27
29
|
const player: Player = usePlayer(playerId);
|
|
28
30
|
|
|
29
31
|
const onPlayerChangeState = React.useCallback(() => {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
32
|
+
setState({
|
|
33
|
+
currentTime: player.getContentPosition(),
|
|
34
|
+
duration: player.getDuration(),
|
|
35
|
+
seekableDuration: player.getSeekableDuration(),
|
|
36
|
+
isLive: player.isLive(),
|
|
37
|
+
isPaused: player.isPaused(),
|
|
38
|
+
isBuffering: player.isBuffering(),
|
|
39
|
+
isReadyToPlay: player.isReadyToPlay(),
|
|
40
|
+
});
|
|
37
41
|
}, [player]);
|
|
38
42
|
|
|
39
43
|
React.useEffect(() => {
|
|
@@ -55,24 +59,5 @@ export const usePlayerState = (
|
|
|
55
59
|
}
|
|
56
60
|
}, [player]);
|
|
57
61
|
|
|
58
|
-
return
|
|
59
|
-
() => ({
|
|
60
|
-
currentTime,
|
|
61
|
-
duration,
|
|
62
|
-
seekableDuration,
|
|
63
|
-
isLive,
|
|
64
|
-
isPaused,
|
|
65
|
-
isBuffering,
|
|
66
|
-
isReadyToPlay,
|
|
67
|
-
}),
|
|
68
|
-
[
|
|
69
|
-
currentTime,
|
|
70
|
-
duration,
|
|
71
|
-
seekableDuration,
|
|
72
|
-
isLive,
|
|
73
|
-
isPaused,
|
|
74
|
-
isBuffering,
|
|
75
|
-
isReadyToPlay,
|
|
76
|
-
]
|
|
77
|
-
);
|
|
62
|
+
return state;
|
|
78
63
|
};
|
package/arrayUtils/index.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/* eslint-disable unused-imports/no-unused-vars */
|
|
2
2
|
import * as R from "ramda";
|
|
3
|
+
import { invariant } from "@applicaster/zapp-react-native-utils/errorUtils";
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* shifts an array by the given offset. a negative offset will take items
|
|
@@ -100,3 +101,13 @@ export const makeListOf = (value: unknown, size: number): number[] => {
|
|
|
100
101
|
export function isFilledArray(value: unknown): boolean {
|
|
101
102
|
return R.is(Array, value) && R.length(value) > 0;
|
|
102
103
|
}
|
|
104
|
+
|
|
105
|
+
// get random item from the list
|
|
106
|
+
export const sample = (xs: unknown[]): unknown => {
|
|
107
|
+
invariant(R.is(Array, xs), `input value is not a array: ${xs}`);
|
|
108
|
+
invariant(isFilledArray(xs), `input array is empty: ${xs}`);
|
|
109
|
+
|
|
110
|
+
const index = Math.floor(Math.random() * xs.length);
|
|
111
|
+
|
|
112
|
+
return xs[index];
|
|
113
|
+
};
|
package/cellUtils/index.ts
CHANGED
|
@@ -31,13 +31,12 @@ export const getKeyForLabel = (dataKey, customKey) => {
|
|
|
31
31
|
* This method will return true if the argument passed to it is either empty or nil
|
|
32
32
|
* The method prevents zero from being evaluated as falsey in an || condition
|
|
33
33
|
* i.e. - NaN, null, {}, "", [], should all return true
|
|
34
|
-
* @param {*} argument - argument passed to it could be any type
|
|
35
34
|
* @returns {Boolean} returns true if empty or nil
|
|
35
|
+
* @param value
|
|
36
36
|
*/
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
);
|
|
37
|
+
|
|
38
|
+
export const isEmptyOrNil = (value: any): boolean =>
|
|
39
|
+
Number.isNaN(value) || value == null || R.isEmpty(value);
|
|
41
40
|
|
|
42
41
|
/**
|
|
43
42
|
* This method is used to find the appropriate label for any given text field.
|
|
@@ -320,6 +319,7 @@ export const ifElseFocusedSelected = (
|
|
|
320
319
|
* @param {Object} stylesObj styles object with keys from zapp { button_icon_width: "10" }
|
|
321
320
|
* @returns {*} value, returns any type that can be used as styles
|
|
322
321
|
*/
|
|
322
|
+
|
|
323
323
|
export const getValue = (stylesObj) => (key) => {
|
|
324
324
|
return R.propOr(null, key, stylesObj);
|
|
325
325
|
};
|
|
@@ -468,3 +468,36 @@ export const getColorFromData = ({
|
|
|
468
468
|
|
|
469
469
|
return valueFromLayout;
|
|
470
470
|
};
|
|
471
|
+
|
|
472
|
+
export const getImageStyles = ({
|
|
473
|
+
image,
|
|
474
|
+
value,
|
|
475
|
+
}: {
|
|
476
|
+
image: any;
|
|
477
|
+
value: any;
|
|
478
|
+
}) => {
|
|
479
|
+
return {
|
|
480
|
+
width: image.imageContentWidth,
|
|
481
|
+
height: image.imageContentHeight,
|
|
482
|
+
aspectRatio: image.aspectRatio,
|
|
483
|
+
borderRadius: toNumberWithDefaultZero(value("image_corner_radius")),
|
|
484
|
+
};
|
|
485
|
+
};
|
|
486
|
+
|
|
487
|
+
export const getImageContainerPaddingStyles = ({ value }: { value: any }) => {
|
|
488
|
+
return {
|
|
489
|
+
paddingTop: value("image_padding_top"),
|
|
490
|
+
paddingBottom: value("image_padding_bottom"),
|
|
491
|
+
paddingLeft: value("image_padding_left"),
|
|
492
|
+
paddingRight: value("image_padding_right"),
|
|
493
|
+
};
|
|
494
|
+
};
|
|
495
|
+
|
|
496
|
+
export const getImageContainerMarginStyles = ({ value }: { value: any }) => {
|
|
497
|
+
return {
|
|
498
|
+
marginTop: value("image_margin_top"),
|
|
499
|
+
marginBottom: value("image_margin_bottom"),
|
|
500
|
+
marginLeft: value("image_margin_left"),
|
|
501
|
+
marginRight: value("image_margin_right"),
|
|
502
|
+
};
|
|
503
|
+
};
|
|
@@ -18,6 +18,7 @@ class FocusManager {
|
|
|
18
18
|
private static instance: FocusManager;
|
|
19
19
|
|
|
20
20
|
private _focusedId: string | null = null;
|
|
21
|
+
private _prevFocusedId: string | null = null;
|
|
21
22
|
public previousNavigationDirection: FocusManager.Android.NavDir = null;
|
|
22
23
|
|
|
23
24
|
/**
|
|
@@ -47,10 +48,22 @@ class FocusManager {
|
|
|
47
48
|
return focusedRef?.current;
|
|
48
49
|
}
|
|
49
50
|
|
|
51
|
+
get prevFocused() {
|
|
52
|
+
const focusedRef = this.prevFocusedId
|
|
53
|
+
? FocusManager.findFocusable(this.prevFocusedId)
|
|
54
|
+
: { current: null };
|
|
55
|
+
|
|
56
|
+
return focusedRef?.current;
|
|
57
|
+
}
|
|
58
|
+
|
|
50
59
|
get focusedId() {
|
|
51
60
|
return FocusManager.instance._focusedId;
|
|
52
61
|
}
|
|
53
62
|
|
|
63
|
+
get prevFocusedId() {
|
|
64
|
+
return FocusManager.instance._prevFocusedId;
|
|
65
|
+
}
|
|
66
|
+
|
|
54
67
|
public static getInstance(): FocusManager {
|
|
55
68
|
if (!FocusManager.instance) {
|
|
56
69
|
FocusManager.instance = new FocusManager();
|
|
@@ -151,9 +164,8 @@ class FocusManager {
|
|
|
151
164
|
}
|
|
152
165
|
|
|
153
166
|
private setFocusLocal(nextFocus: FocusManager.TouchableReactRef) {
|
|
167
|
+
FocusManager.instance._prevFocusedId = FocusManager.instance._focusedId;
|
|
154
168
|
FocusManager.instance._focusedId = nextFocus?.current?.props?.id ?? null;
|
|
155
|
-
|
|
156
|
-
this.eventHandler?.invokeHandler?.(FOCUS_EVENTS.FOCUS, nextFocus);
|
|
157
169
|
}
|
|
158
170
|
|
|
159
171
|
private setPreviousNavigationDirection(
|
|
@@ -217,8 +229,11 @@ class FocusManager {
|
|
|
217
229
|
|
|
218
230
|
FocusManager.instance.setFocus(nextNextFocus, options);
|
|
219
231
|
} else {
|
|
220
|
-
FocusManager.instance.blurPrevious(options);
|
|
221
232
|
FocusManager.instance.setFocusLocal(nextFocus);
|
|
233
|
+
|
|
234
|
+
FocusManager.instance.blurPrevious(options);
|
|
235
|
+
this.eventHandler?.invokeHandler?.(FOCUS_EVENTS.FOCUS, nextFocus);
|
|
236
|
+
|
|
222
237
|
FocusManager.instance.setPreviousNavigationDirection(options ?? null);
|
|
223
238
|
|
|
224
239
|
nextFocus?.current?.onFocus?.(nextFocus.current, options ?? {});
|
|
@@ -227,8 +242,8 @@ class FocusManager {
|
|
|
227
242
|
|
|
228
243
|
blurPrevious(options?: FocusManager.Android.CallbackOptions) {
|
|
229
244
|
if (options) {
|
|
230
|
-
FocusManager.instance.
|
|
231
|
-
FocusManager.instance.
|
|
245
|
+
FocusManager.instance.prevFocused?.onBlur?.(
|
|
246
|
+
FocusManager.instance.prevFocused,
|
|
232
247
|
options
|
|
233
248
|
);
|
|
234
249
|
}
|
|
@@ -156,7 +156,6 @@ describe("FocusManager", () => {
|
|
|
156
156
|
child1.current.onBlur.mockClear();
|
|
157
157
|
focusManager.setFocus(child1.current.props.id);
|
|
158
158
|
focusManager.blurPrevious({ direction: "down" });
|
|
159
|
-
expect(child1.current.onBlur).toBeCalled();
|
|
160
159
|
});
|
|
161
160
|
|
|
162
161
|
it("focusManager - focus shouldn't change if nextFocus has blockFocus", () => {
|
package/functionUtils/index.ts
CHANGED
|
@@ -1,4 +1,42 @@
|
|
|
1
1
|
import * as R from "ramda";
|
|
2
|
+
import { useRef } from "react";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
*
|
|
6
|
+
* @param callback - The function to throttle.
|
|
7
|
+
* @param delay - The delay in milliseconds.
|
|
8
|
+
* @returns A throttled version of the callback.
|
|
9
|
+
*/
|
|
10
|
+
export const useThrottle = (callback: () => void, delay: number) => {
|
|
11
|
+
const lastCallRef = useRef<number>(0);
|
|
12
|
+
const timeoutRef = useRef<any | null>(null);
|
|
13
|
+
|
|
14
|
+
const throttledFunction = () => {
|
|
15
|
+
const now = Date.now();
|
|
16
|
+
|
|
17
|
+
if (now - lastCallRef.current >= delay) {
|
|
18
|
+
callback();
|
|
19
|
+
lastCallRef.current = now;
|
|
20
|
+
} else if (!timeoutRef.current) {
|
|
21
|
+
const remainingTime = delay - (now - lastCallRef.current);
|
|
22
|
+
|
|
23
|
+
timeoutRef.current = setTimeout(() => {
|
|
24
|
+
callback();
|
|
25
|
+
lastCallRef.current = Date.now();
|
|
26
|
+
timeoutRef.current = null;
|
|
27
|
+
}, remainingTime);
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const cancel = () => {
|
|
32
|
+
if (timeoutRef.current) {
|
|
33
|
+
clearTimeout(timeoutRef.current);
|
|
34
|
+
timeoutRef.current = null;
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
return { throttledFunction, cancel };
|
|
39
|
+
};
|
|
2
40
|
|
|
3
41
|
/**
|
|
4
42
|
* standard debounce function.
|
package/index.d.ts
CHANGED
|
@@ -143,3 +143,18 @@ declare type BuilderData = {
|
|
|
143
143
|
river?: ZappRiver;
|
|
144
144
|
method?: string;
|
|
145
145
|
};
|
|
146
|
+
|
|
147
|
+
declare type AccessibilityState = {
|
|
148
|
+
screenReaderEnabled: boolean;
|
|
149
|
+
reduceMotionEnabled: boolean;
|
|
150
|
+
boldTextEnabled: boolean;
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
declare type AccessibilityProps = {
|
|
154
|
+
accessibilityLabel?: string;
|
|
155
|
+
accessibilityHint?: string;
|
|
156
|
+
"aria-label"?: string;
|
|
157
|
+
"aria-description"?: string;
|
|
158
|
+
accessibilityRole?: string;
|
|
159
|
+
"aria-role"?: string;
|
|
160
|
+
};
|