@applicaster/zapp-react-native-utils 15.0.0-rc.5 → 15.0.0-rc.52
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/actionsExecutor/ActionExecutorContext.tsx +3 -6
- package/actionsExecutor/feedDecorator.ts +6 -6
- package/adsUtils/index.ts +2 -2
- package/analyticsUtils/AnalyticPlayerListener.ts +5 -2
- package/analyticsUtils/README.md +1 -1
- package/appUtils/HooksManager/index.ts +10 -10
- package/appUtils/accessibilityManager/const.ts +4 -0
- package/appUtils/accessibilityManager/hooks.ts +20 -13
- package/appUtils/accessibilityManager/index.ts +28 -1
- package/appUtils/accessibilityManager/utils.ts +36 -5
- package/appUtils/focusManager/__tests__/__snapshots__/focusManager.test.js.snap +1 -0
- package/appUtils/focusManager/index.ios.ts +18 -1
- package/appUtils/focusManagerAux/utils/index.ts +18 -0
- package/appUtils/focusManagerAux/utils/utils.ios.ts +35 -0
- package/appUtils/keyCodes/keys/keys.web.ts +1 -4
- package/appUtils/orientationHelper.ts +2 -4
- package/appUtils/platform/platformUtils.ts +117 -18
- package/appUtils/playerManager/OverlayObserver/OverlaysObserver.ts +94 -4
- package/appUtils/playerManager/OverlayObserver/utils.ts +32 -20
- package/appUtils/playerManager/conts.ts +21 -0
- package/appUtils/playerManager/player.ts +4 -0
- package/appUtils/playerManager/playerNative.ts +29 -16
- package/appUtils/playerManager/usePlayerState.tsx +14 -2
- package/arrayUtils/__tests__/allTruthy.test.ts +24 -0
- package/arrayUtils/__tests__/anyThruthy.test.ts +24 -0
- package/arrayUtils/index.ts +5 -0
- package/cellUtils/index.ts +32 -0
- package/configurationUtils/__tests__/manifestKeyParser.test.ts +26 -26
- package/manifestUtils/defaultManifestConfigurations/player.js +75 -1
- package/manifestUtils/keys.js +21 -0
- package/manifestUtils/sharedConfiguration/screenPicker/utils.js +1 -0
- package/manifestUtils/tvAction/container/index.js +1 -1
- package/navigationUtils/index.ts +19 -16
- package/package.json +2 -2
- package/playerUtils/usePlayerTTS.ts +8 -3
- package/pluginUtils/index.ts +4 -0
- package/reactHooks/advertising/index.ts +2 -2
- package/reactHooks/debugging/__tests__/index.test.js +4 -4
- package/reactHooks/device/useMemoizedIsTablet.ts +3 -3
- package/reactHooks/feed/__tests__/useEntryScreenId.test.tsx +3 -0
- package/reactHooks/feed/useBatchLoading.ts +7 -1
- package/reactHooks/feed/useEntryScreenId.ts +2 -2
- package/reactHooks/feed/usePipesCacheReset.ts +3 -1
- package/reactHooks/flatList/useLoadNextPageIfNeeded.ts +13 -16
- package/reactHooks/layout/index.ts +1 -1
- package/reactHooks/layout/useDimensions/__tests__/{useDimensions.test.ts → useDimensions.test.tsx} +105 -25
- package/reactHooks/layout/useDimensions/useDimensions.ts +2 -2
- package/reactHooks/navigation/index.ts +7 -6
- package/reactHooks/navigation/useRoute.ts +8 -6
- package/reactHooks/player/TVSeekControlller/TVSeekController.ts +27 -10
- package/reactHooks/resolvers/useCellResolver.ts +6 -2
- package/reactHooks/resolvers/useComponentResolver.ts +8 -2
- package/reactHooks/screen/__tests__/useTargetScreenData.test.tsx +10 -2
- package/reactHooks/screen/useTargetScreenData.ts +4 -2
- package/reactHooks/state/useRivers.ts +1 -1
- package/reactHooks/usePluginConfiguration.ts +2 -2
- package/testUtils/index.tsx +29 -20
- package/utils/__tests__/mapAccum.test.ts +73 -0
- package/utils/__tests__/selectors.test.ts +124 -0
- package/utils/index.ts +10 -0
- package/utils/mapAccum.ts +23 -0
- package/utils/selectors.ts +46 -0
- package/zappFrameworkUtils/HookCallback/callbackNavigationAction.ts +49 -12
- package/zappFrameworkUtils/HookCallback/hookCallbackManifestExtensions.config.js +1 -1
|
@@ -23,12 +23,9 @@ import {
|
|
|
23
23
|
EntryResolver,
|
|
24
24
|
resolveObjectValues,
|
|
25
25
|
} from "../appUtils/contextKeysManager/contextResolver";
|
|
26
|
-
import { useNavigation } from "../reactHooks";
|
|
26
|
+
import { useNavigation, useRivers } from "../reactHooks";
|
|
27
27
|
|
|
28
|
-
import {
|
|
29
|
-
useContentTypes,
|
|
30
|
-
usePickFromState,
|
|
31
|
-
} from "@applicaster/zapp-react-native-redux/hooks";
|
|
28
|
+
import { useContentTypes } from "@applicaster/zapp-react-native-redux/hooks";
|
|
32
29
|
import { useSubscriberFor } from "../reactHooks/useSubscriberFor";
|
|
33
30
|
import { APP_EVENTS } from "../appUtils/events";
|
|
34
31
|
import {
|
|
@@ -278,7 +275,7 @@ export function withActionExecutor(Component) {
|
|
|
278
275
|
|
|
279
276
|
return function ActionExecutorComponent(props: Props) {
|
|
280
277
|
const navigator = useNavigation();
|
|
281
|
-
const
|
|
278
|
+
const rivers = useRivers();
|
|
282
279
|
const contentTypes = useContentTypes();
|
|
283
280
|
|
|
284
281
|
const handlers = useMemo(() => {
|
|
@@ -27,7 +27,7 @@ function makeMultiSelect(feed: ZappFeed, key, decoratedFeed) {
|
|
|
27
27
|
);
|
|
28
28
|
|
|
29
29
|
const behavior = {
|
|
30
|
-
...feed.extensions?.
|
|
30
|
+
...feed.extensions?.behavior,
|
|
31
31
|
select_mode: "multi",
|
|
32
32
|
current_selection: `@{${scope}/${key}}`,
|
|
33
33
|
};
|
|
@@ -75,7 +75,7 @@ function makeSingleSelect(feed: ZappFeed, key, decoratedFeed) {
|
|
|
75
75
|
);
|
|
76
76
|
|
|
77
77
|
const behavior = {
|
|
78
|
-
...feed.extensions?.
|
|
78
|
+
...feed.extensions?.behavior,
|
|
79
79
|
select_mode: "single",
|
|
80
80
|
current_selection: `@{${scope}/${key}}`,
|
|
81
81
|
};
|
|
@@ -141,11 +141,11 @@ function makeSingleSelect(feed: ZappFeed, key, decoratedFeed) {
|
|
|
141
141
|
}
|
|
142
142
|
|
|
143
143
|
export const decorateFeed = (feed: ZappFeed) => {
|
|
144
|
-
if (!(feed.extensions?.
|
|
144
|
+
if (!(feed.extensions?.role === "preference_editor")) {
|
|
145
145
|
return feed;
|
|
146
146
|
}
|
|
147
147
|
|
|
148
|
-
const key = feed.extensions?.
|
|
148
|
+
const key = feed.extensions?.preference_editor_options?.key;
|
|
149
149
|
|
|
150
150
|
if (!key) {
|
|
151
151
|
log_error(
|
|
@@ -160,8 +160,8 @@ export const decorateFeed = (feed: ZappFeed) => {
|
|
|
160
160
|
const decoratedFeed = R.clone(feed);
|
|
161
161
|
|
|
162
162
|
const isSingleSelect =
|
|
163
|
-
(feed.extensions?.
|
|
164
|
-
feed.extensions?.
|
|
163
|
+
(feed.extensions?.preference_editor_options?.select_mode ||
|
|
164
|
+
feed.extensions?.behavior?.select_mode) === "single";
|
|
165
165
|
|
|
166
166
|
if (isSingleSelect) {
|
|
167
167
|
return makeSingleSelect(feed, key, decoratedFeed);
|
package/adsUtils/index.ts
CHANGED
|
@@ -33,10 +33,10 @@ function convertOffset(offset: any): string {
|
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
function createAdBreak(ad: AdMap): string {
|
|
36
|
-
const offset = ad
|
|
36
|
+
const offset = ad.offset;
|
|
37
37
|
const id = offset.toString();
|
|
38
38
|
const timestamp = convertOffset(offset);
|
|
39
|
-
const url = ad
|
|
39
|
+
const url = ad.ad_url.toString().trim();
|
|
40
40
|
|
|
41
41
|
return `
|
|
42
42
|
<vmap:AdBreak timeOffset="${timestamp}" breakType="linear" breakId="break-${id}">
|
|
@@ -35,8 +35,11 @@ export class AnalyticPlayerListener
|
|
|
35
35
|
this.handleAnalyticEvent(PLAYBACK_EVENT.complete);
|
|
36
36
|
};
|
|
37
37
|
|
|
38
|
-
onError = (err:
|
|
39
|
-
this.handleAnalyticEvent(
|
|
38
|
+
onError = (err: QuickBrickPlayer.PlayerErrorI) => {
|
|
39
|
+
this.handleAnalyticEvent(
|
|
40
|
+
PLAYBACK_EVENT.error,
|
|
41
|
+
err.toObject?.() || { message: err.message }
|
|
42
|
+
);
|
|
40
43
|
};
|
|
41
44
|
|
|
42
45
|
onPlayerPause = (event) => {
|
package/analyticsUtils/README.md
CHANGED
|
@@ -388,7 +388,7 @@ export function AnalyticsProvider(props: ComponentWithChildrenProps) {
|
|
|
388
388
|
|
|
389
389
|
```ts
|
|
390
390
|
export function useAnalytics(props: any): any {
|
|
391
|
-
const
|
|
391
|
+
const appData = useAppData();
|
|
392
392
|
const getAnalyticsFunctions = React.useContext(AnalyticsContext);
|
|
393
393
|
|
|
394
394
|
const analyticsFunctions = React.useMemo(
|
|
@@ -230,7 +230,7 @@ export function HooksManager({
|
|
|
230
230
|
function completeHook(hookPlugin, payload, callback) {
|
|
231
231
|
logHookEvent(
|
|
232
232
|
hooksManagerLogger.info,
|
|
233
|
-
`completeHook: hook sequence completed successfully: ${hookPlugin
|
|
233
|
+
`completeHook: hook sequence completed successfully: ${hookPlugin.identifier}`,
|
|
234
234
|
{
|
|
235
235
|
payload,
|
|
236
236
|
hook: hookPlugin,
|
|
@@ -276,7 +276,7 @@ export function HooksManager({
|
|
|
276
276
|
if (hookPlugin.isCancelled()) {
|
|
277
277
|
logHookEvent(
|
|
278
278
|
hooksManagerLogger.info,
|
|
279
|
-
`hookCallback: hook was cancelled: ${hookPlugin
|
|
279
|
+
`hookCallback: hook was cancelled: ${hookPlugin.identifier}`,
|
|
280
280
|
{}
|
|
281
281
|
);
|
|
282
282
|
|
|
@@ -305,7 +305,7 @@ export function HooksManager({
|
|
|
305
305
|
if (!success) {
|
|
306
306
|
logHookEvent(
|
|
307
307
|
hooksManagerLogger.info,
|
|
308
|
-
`hookCallback: hook was cancelled: ${hookPlugin
|
|
308
|
+
`hookCallback: hook was cancelled: ${hookPlugin.identifier}`,
|
|
309
309
|
{
|
|
310
310
|
payload,
|
|
311
311
|
hook: hookPlugin,
|
|
@@ -334,7 +334,7 @@ export function HooksManager({
|
|
|
334
334
|
if (isHookInHomescreen && isHookFlowBlocker && cancelled) {
|
|
335
335
|
logHookEvent(
|
|
336
336
|
hooksManagerLogger.info,
|
|
337
|
-
`hookCallback: send app to background, cancelled flow blocker hook ${hookPlugin
|
|
337
|
+
`hookCallback: send app to background, cancelled flow blocker hook ${hookPlugin.identifier} on home screen`,
|
|
338
338
|
{
|
|
339
339
|
payload,
|
|
340
340
|
hook: hookPlugin,
|
|
@@ -349,7 +349,7 @@ export function HooksManager({
|
|
|
349
349
|
} else {
|
|
350
350
|
logHookEvent(
|
|
351
351
|
hooksManagerLogger.info,
|
|
352
|
-
`hookCallback: hook successfully finished: ${hookPlugin
|
|
352
|
+
`hookCallback: hook successfully finished: ${hookPlugin.identifier}`,
|
|
353
353
|
{
|
|
354
354
|
payload,
|
|
355
355
|
hook: hookPlugin,
|
|
@@ -359,7 +359,7 @@ export function HooksManager({
|
|
|
359
359
|
if (!callback) {
|
|
360
360
|
logHookEvent(
|
|
361
361
|
hooksManagerLogger.warn,
|
|
362
|
-
`hookCallback: ${hookPlugin
|
|
362
|
+
`hookCallback: ${hookPlugin.identifier} is missing \`callback\`, using hookCallback(default one)`,
|
|
363
363
|
{
|
|
364
364
|
hookPlugin,
|
|
365
365
|
}
|
|
@@ -401,7 +401,7 @@ export function HooksManager({
|
|
|
401
401
|
|
|
402
402
|
logHookEvent(
|
|
403
403
|
hooksManagerLogger.info,
|
|
404
|
-
`presentScreenHook: Presenting screen hook: ${hookPlugin
|
|
404
|
+
`presentScreenHook: Presenting screen hook: ${hookPlugin.identifier}`,
|
|
405
405
|
{
|
|
406
406
|
hook: hookPlugin,
|
|
407
407
|
payload,
|
|
@@ -421,7 +421,7 @@ export function HooksManager({
|
|
|
421
421
|
hooksManager.executeHook = function (hookPlugin, payload, callback) {
|
|
422
422
|
logHookEvent(
|
|
423
423
|
hooksManagerLogger.info,
|
|
424
|
-
`executeHook: ${hookPlugin
|
|
424
|
+
`executeHook: ${hookPlugin.identifier}`,
|
|
425
425
|
{
|
|
426
426
|
hook: hookPlugin,
|
|
427
427
|
payload,
|
|
@@ -433,7 +433,7 @@ export function HooksManager({
|
|
|
433
433
|
} catch (error) {
|
|
434
434
|
logHookEvent(
|
|
435
435
|
hooksManagerLogger.error,
|
|
436
|
-
`executeHook: error executing hook: ${hookPlugin
|
|
436
|
+
`executeHook: error executing hook: ${hookPlugin.identifier} error: ${error.message}`,
|
|
437
437
|
{
|
|
438
438
|
hook: hookPlugin,
|
|
439
439
|
payload,
|
|
@@ -460,7 +460,7 @@ export function HooksManager({
|
|
|
460
460
|
try {
|
|
461
461
|
logHookEvent(
|
|
462
462
|
hooksManagerLogger.info,
|
|
463
|
-
`runInBackground: Executing hook: ${hookPlugin
|
|
463
|
+
`runInBackground: Executing hook: ${hookPlugin.identifier}`,
|
|
464
464
|
{
|
|
465
465
|
hook: hookPlugin,
|
|
466
466
|
payload,
|
|
@@ -31,6 +31,10 @@ export const BUTTON_ACCESSIBILITY_KEYS = {
|
|
|
31
31
|
hint: "accessibility_close_mini_hint",
|
|
32
32
|
},
|
|
33
33
|
},
|
|
34
|
+
back_to_live: {
|
|
35
|
+
label: "back_to_live_label",
|
|
36
|
+
hint: "",
|
|
37
|
+
},
|
|
34
38
|
maximize: {
|
|
35
39
|
label: "accessibility_maximize_label",
|
|
36
40
|
hint: "accessibility_maximize_hint",
|
|
@@ -23,19 +23,6 @@ export const useAccessibilityManager = (
|
|
|
23
23
|
}
|
|
24
24
|
}, [pluginConfiguration, accessibilityManager]);
|
|
25
25
|
|
|
26
|
-
useEffect(() => {
|
|
27
|
-
const subscription = accessibilityManager.getStateAsObservable().subscribe({
|
|
28
|
-
next: () => {
|
|
29
|
-
// TODO: handle accessibility states
|
|
30
|
-
// screenReaderEnabled: false
|
|
31
|
-
// reduceMotionEnabled: false
|
|
32
|
-
// boldTextEnabled: false
|
|
33
|
-
},
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
return () => subscription.unsubscribe();
|
|
37
|
-
}, [accessibilityManager]);
|
|
38
|
-
|
|
39
26
|
return accessibilityManager;
|
|
40
27
|
};
|
|
41
28
|
|
|
@@ -72,3 +59,23 @@ export const useAnnouncementActive = (
|
|
|
72
59
|
|
|
73
60
|
return isActive;
|
|
74
61
|
};
|
|
62
|
+
|
|
63
|
+
export const useAccessibilityState = (
|
|
64
|
+
pluginConfiguration: Record<string, any> = {}
|
|
65
|
+
) => {
|
|
66
|
+
const accessibilityManager = useAccessibilityManager(pluginConfiguration);
|
|
67
|
+
|
|
68
|
+
const [state, setState] = useState<AccessibilityState>(
|
|
69
|
+
accessibilityManager.getState()
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
useEffect(() => {
|
|
73
|
+
const subscription = accessibilityManager
|
|
74
|
+
.getStateAsObservable()
|
|
75
|
+
.subscribe(setState);
|
|
76
|
+
|
|
77
|
+
return () => subscription.unsubscribe();
|
|
78
|
+
}, [accessibilityManager]);
|
|
79
|
+
|
|
80
|
+
return state;
|
|
81
|
+
};
|
|
@@ -36,7 +36,20 @@ export class AccessibilityManager {
|
|
|
36
36
|
false
|
|
37
37
|
);
|
|
38
38
|
|
|
39
|
-
private constructor() {
|
|
39
|
+
private constructor() {
|
|
40
|
+
this.ttsManager
|
|
41
|
+
.getScreenReaderEnabledAsObservable()
|
|
42
|
+
.subscribe((enabled) => {
|
|
43
|
+
const state = this.state$.getValue();
|
|
44
|
+
|
|
45
|
+
if (state.screenReaderEnabled !== enabled) {
|
|
46
|
+
this.state$.next({
|
|
47
|
+
...state,
|
|
48
|
+
screenReaderEnabled: enabled,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
}
|
|
40
53
|
|
|
41
54
|
public static getInstance(): AccessibilityManager {
|
|
42
55
|
if (!AccessibilityManager._instance) {
|
|
@@ -92,8 +105,15 @@ export class AccessibilityManager {
|
|
|
92
105
|
/**
|
|
93
106
|
* Adds a heading to the queue, headings will be read before the next text
|
|
94
107
|
* Each heading will be read once and removed from the queue
|
|
108
|
+
* Does nothing if screen reader is not enabled
|
|
95
109
|
*/
|
|
96
110
|
public addHeading(heading: string) {
|
|
111
|
+
const state = this.state$.getValue();
|
|
112
|
+
|
|
113
|
+
if (!state.screenReaderEnabled) {
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
|
|
97
117
|
if (!this.pendingFocusId) {
|
|
98
118
|
this.pendingFocusId = Date.now().toString();
|
|
99
119
|
}
|
|
@@ -108,6 +128,7 @@ export class AccessibilityManager {
|
|
|
108
128
|
*
|
|
109
129
|
* Implements a delay mechanism to reduce noise during rapid navigation.
|
|
110
130
|
* Only the most recent announcement will be read after the delay period.
|
|
131
|
+
* Does nothing if screen reader is not enabled
|
|
111
132
|
*/
|
|
112
133
|
public readText({
|
|
113
134
|
text,
|
|
@@ -116,6 +137,12 @@ export class AccessibilityManager {
|
|
|
116
137
|
text: string;
|
|
117
138
|
keyOfLocalizedText?: string;
|
|
118
139
|
}) {
|
|
140
|
+
const state = this.state$.getValue();
|
|
141
|
+
|
|
142
|
+
if (!state.screenReaderEnabled) {
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
|
|
119
146
|
let textToRead = text;
|
|
120
147
|
|
|
121
148
|
if (keyOfLocalizedText) {
|
|
@@ -12,13 +12,44 @@ export function calculateReadingTime(
|
|
|
12
12
|
minimumPause: number = 500,
|
|
13
13
|
announcementDelay: number = 700
|
|
14
14
|
): number {
|
|
15
|
-
const
|
|
16
|
-
|
|
15
|
+
const trimmed = text.trim();
|
|
16
|
+
|
|
17
|
+
// Count words (split on whitespace and punctuation, keep alnum boundaries)
|
|
18
|
+
const words = trimmed
|
|
17
19
|
.split(/(?<=\d)(?=[a-zA-Z])|(?<=[a-zA-Z])(?=\d)|[^\w\s]+|\s+/)
|
|
18
20
|
.filter(Boolean).length;
|
|
19
21
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
22
|
+
// Count spaces - multiple consecutive spaces add extra pause time
|
|
23
|
+
const spaceMatches: string[] = trimmed.match(/\s+/g) || [];
|
|
24
|
+
|
|
25
|
+
const totalSpaces = spaceMatches.reduce(
|
|
26
|
+
(sum: number, match: string) => sum + match.length,
|
|
27
|
+
0
|
|
23
28
|
);
|
|
29
|
+
|
|
30
|
+
const extraSpaces = Math.max(0, totalSpaces - (words - 1)); // words-1 is normal spacing
|
|
31
|
+
|
|
32
|
+
// Heuristic: punctuation increases TTS duration beyond word-based WPM.
|
|
33
|
+
// Commas typically introduce short pauses, sentence terminators longer ones.
|
|
34
|
+
const commaCount = (trimmed.match(/,/g) || []).length;
|
|
35
|
+
const semicolonCount = (trimmed.match(/;/g) || []).length;
|
|
36
|
+
const colonCount = (trimmed.match(/:/g) || []).length;
|
|
37
|
+
const dashCount = (trimmed.match(/\u2013|\u2014|-/g) || []).length; // – — -
|
|
38
|
+
const sentenceEndCount = (trimmed.match(/[.!?](?!\d)/g) || []).length;
|
|
39
|
+
|
|
40
|
+
const commaPauseMs = 220; // short prosody pause for ","
|
|
41
|
+
const midPauseMs = 260; // for ";", ":", dashes
|
|
42
|
+
const sentenceEndPauseMs = 420; // for ".", "!", "?"
|
|
43
|
+
const extraSpacePauseMs = 50; // per extra space beyond normal spacing
|
|
44
|
+
|
|
45
|
+
const punctuationPause =
|
|
46
|
+
commaCount * commaPauseMs +
|
|
47
|
+
(semicolonCount + colonCount + dashCount) * midPauseMs +
|
|
48
|
+
sentenceEndCount * sentenceEndPauseMs +
|
|
49
|
+
extraSpaces * extraSpacePauseMs;
|
|
50
|
+
|
|
51
|
+
const baseByWordsMs = (words / wordsPerMinute) * 60 * 1000;
|
|
52
|
+
const estimatedMs = Math.max(minimumPause, baseByWordsMs + punctuationPause);
|
|
53
|
+
|
|
54
|
+
return estimatedMs + announcementDelay;
|
|
24
55
|
}
|
|
@@ -69,6 +69,7 @@ exports[`focusManagerIOS should be defined 1`] = `
|
|
|
69
69
|
"getGroupRootById": [Function],
|
|
70
70
|
"getPreferredFocusChild": [Function],
|
|
71
71
|
"invokeHandler": [Function],
|
|
72
|
+
"isChildOf": [Function],
|
|
72
73
|
"isFocusOn": [Function],
|
|
73
74
|
"isGroupItemFocused": [Function],
|
|
74
75
|
"moveFocus": [Function],
|
|
@@ -1,12 +1,20 @@
|
|
|
1
1
|
import { NativeModules } from "react-native";
|
|
2
2
|
import * as R from "ramda";
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
isCurrentFocusOn,
|
|
6
|
+
isChildOf as isChildOfUtils,
|
|
7
|
+
} from "../focusManagerAux/utils";
|
|
5
8
|
import { Tree } from "./treeDataStructure/Tree";
|
|
6
9
|
import { findFocusableNode } from "./treeDataStructure/Utils";
|
|
7
10
|
import { subscriber } from "../../functionUtils";
|
|
8
11
|
import { findChild } from "./utils";
|
|
9
12
|
|
|
13
|
+
import {
|
|
14
|
+
emitRegistered,
|
|
15
|
+
emitUnregistered,
|
|
16
|
+
} from "../focusManagerAux/utils/utils.ios";
|
|
17
|
+
|
|
10
18
|
const { FocusableManagerModule } = NativeModules;
|
|
11
19
|
|
|
12
20
|
/**
|
|
@@ -180,10 +188,14 @@ export const focusManager = (function () {
|
|
|
180
188
|
function register({ id, component }) {
|
|
181
189
|
const { isGroup = false } = component;
|
|
182
190
|
|
|
191
|
+
emitRegistered(id);
|
|
192
|
+
|
|
183
193
|
return isGroup ? registerGroup(id, component) : registerItem(id, component);
|
|
184
194
|
}
|
|
185
195
|
|
|
186
196
|
function unregister(id, { group = false } = {}) {
|
|
197
|
+
emitUnregistered(id);
|
|
198
|
+
|
|
187
199
|
group ? unregisterGroup(id) : unregisterItem(id);
|
|
188
200
|
}
|
|
189
201
|
|
|
@@ -400,6 +412,10 @@ export const focusManager = (function () {
|
|
|
400
412
|
return id && isCurrentFocusOn(id, currentFocusNode);
|
|
401
413
|
}
|
|
402
414
|
|
|
415
|
+
function isChildOf(childId, parentId): boolean {
|
|
416
|
+
return isChildOfUtils(focusableTree, childId, parentId);
|
|
417
|
+
}
|
|
418
|
+
|
|
403
419
|
return {
|
|
404
420
|
on,
|
|
405
421
|
invokeHandler,
|
|
@@ -422,5 +438,6 @@ export const focusManager = (function () {
|
|
|
422
438
|
isGroupItemFocused,
|
|
423
439
|
getPreferredFocusChild,
|
|
424
440
|
isFocusOn,
|
|
441
|
+
isChildOf,
|
|
425
442
|
};
|
|
426
443
|
})();
|
|
@@ -190,3 +190,21 @@ export const isCurrentFocusOn = (id, node) => {
|
|
|
190
190
|
|
|
191
191
|
return isCurrentFocusOn(id, node.parent);
|
|
192
192
|
};
|
|
193
|
+
|
|
194
|
+
export const isChildOf = (focusableTree, childId, parentId) => {
|
|
195
|
+
if (isNil(childId) || isNil(parentId)) {
|
|
196
|
+
return false;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const childNode = focusableTree.findInTree(childId);
|
|
200
|
+
|
|
201
|
+
if (isNil(childNode)) {
|
|
202
|
+
return false;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (childNode.parent?.id === parentId) {
|
|
206
|
+
return true;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
return isChildOf(focusableTree, childNode.parent?.id, parentId);
|
|
210
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { ReplaySubject } from "rxjs";
|
|
2
|
+
import { filter } from "rxjs/operators";
|
|
3
|
+
import { BUTTON_PREFIX } from "@applicaster/zapp-react-native-ui-components/Components/MasterCell/DefaultComponents/tv/TvActionButtons/const";
|
|
4
|
+
import { focusManager } from "@applicaster/zapp-react-native-utils/appUtils/focusManager/index.ios";
|
|
5
|
+
|
|
6
|
+
type FocusableID = string;
|
|
7
|
+
type RegistrationEvent = {
|
|
8
|
+
id: FocusableID;
|
|
9
|
+
registered: boolean;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const isFocusableButton = (id: Option<FocusableID>): boolean =>
|
|
13
|
+
id && id.includes?.(BUTTON_PREFIX);
|
|
14
|
+
|
|
15
|
+
const registeredSubject$ = new ReplaySubject<RegistrationEvent>(1);
|
|
16
|
+
|
|
17
|
+
export const focusableButtonsRegistration$ = (focusableGroupId: string) =>
|
|
18
|
+
registeredSubject$.pipe(
|
|
19
|
+
filter(
|
|
20
|
+
(value) =>
|
|
21
|
+
value.registered && focusManager.isChildOf(value.id, focusableGroupId)
|
|
22
|
+
)
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
export const emitRegistered = (id: Option<FocusableID>): void => {
|
|
26
|
+
if (isFocusableButton(id)) {
|
|
27
|
+
registeredSubject$.next({ id, registered: true });
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export const emitUnregistered = (id: Option<FocusableID>): void => {
|
|
32
|
+
if (isFocusableButton(id)) {
|
|
33
|
+
registeredSubject$.next({ id, registered: false });
|
|
34
|
+
}
|
|
35
|
+
};
|
|
@@ -10,10 +10,7 @@ import { Platform } from "react-native";
|
|
|
10
10
|
* platformKeys[Platform.OS] should only include keys
|
|
11
11
|
* that are unique to that platform, i.e. Exit: { keyCode: 10182 }
|
|
12
12
|
*/
|
|
13
|
-
export const KEYS = Object.assign(
|
|
14
|
-
platformKeys["web"],
|
|
15
|
-
platformKeys[Platform.OS]
|
|
16
|
-
);
|
|
13
|
+
export const KEYS = Object.assign(platformKeys.web, platformKeys[Platform.OS]);
|
|
17
14
|
|
|
18
15
|
export const ARROW_KEYS = [
|
|
19
16
|
KEYS.ArrowUp,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as ReactNative from "react-native";
|
|
2
|
-
import {
|
|
2
|
+
import { useAppData } from "@applicaster/zapp-react-native-redux/hooks";
|
|
3
3
|
|
|
4
4
|
import { isTV, platformSelect } from "../reactUtils";
|
|
5
5
|
import { useIsTablet } from "../reactHooks";
|
|
@@ -184,9 +184,7 @@ export const getScreenOrientation = ({
|
|
|
184
184
|
|
|
185
185
|
export const useGetScreenOrientation = (screenData) => {
|
|
186
186
|
const isTablet = useIsTablet();
|
|
187
|
-
|
|
188
|
-
const { appData } = usePickFromState(["appData"]);
|
|
189
|
-
const isTabletPortrait = appData?.isTabletPortrait;
|
|
187
|
+
const { isTabletPortrait } = useAppData();
|
|
190
188
|
|
|
191
189
|
return getScreenOrientation({
|
|
192
190
|
screenData,
|