@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.
Files changed (64) hide show
  1. package/actionsExecutor/ActionExecutorContext.tsx +3 -6
  2. package/actionsExecutor/feedDecorator.ts +6 -6
  3. package/adsUtils/index.ts +2 -2
  4. package/analyticsUtils/AnalyticPlayerListener.ts +5 -2
  5. package/analyticsUtils/README.md +1 -1
  6. package/appUtils/HooksManager/index.ts +10 -10
  7. package/appUtils/accessibilityManager/const.ts +4 -0
  8. package/appUtils/accessibilityManager/hooks.ts +20 -13
  9. package/appUtils/accessibilityManager/index.ts +28 -1
  10. package/appUtils/accessibilityManager/utils.ts +36 -5
  11. package/appUtils/focusManager/__tests__/__snapshots__/focusManager.test.js.snap +1 -0
  12. package/appUtils/focusManager/index.ios.ts +18 -1
  13. package/appUtils/focusManagerAux/utils/index.ts +18 -0
  14. package/appUtils/focusManagerAux/utils/utils.ios.ts +35 -0
  15. package/appUtils/keyCodes/keys/keys.web.ts +1 -4
  16. package/appUtils/orientationHelper.ts +2 -4
  17. package/appUtils/platform/platformUtils.ts +117 -18
  18. package/appUtils/playerManager/OverlayObserver/OverlaysObserver.ts +94 -4
  19. package/appUtils/playerManager/OverlayObserver/utils.ts +32 -20
  20. package/appUtils/playerManager/conts.ts +21 -0
  21. package/appUtils/playerManager/player.ts +4 -0
  22. package/appUtils/playerManager/playerNative.ts +29 -16
  23. package/appUtils/playerManager/usePlayerState.tsx +14 -2
  24. package/arrayUtils/__tests__/allTruthy.test.ts +24 -0
  25. package/arrayUtils/__tests__/anyThruthy.test.ts +24 -0
  26. package/arrayUtils/index.ts +5 -0
  27. package/cellUtils/index.ts +32 -0
  28. package/configurationUtils/__tests__/manifestKeyParser.test.ts +26 -26
  29. package/manifestUtils/defaultManifestConfigurations/player.js +75 -1
  30. package/manifestUtils/keys.js +21 -0
  31. package/manifestUtils/sharedConfiguration/screenPicker/utils.js +1 -0
  32. package/manifestUtils/tvAction/container/index.js +1 -1
  33. package/navigationUtils/index.ts +19 -16
  34. package/package.json +2 -2
  35. package/playerUtils/usePlayerTTS.ts +8 -3
  36. package/pluginUtils/index.ts +4 -0
  37. package/reactHooks/advertising/index.ts +2 -2
  38. package/reactHooks/debugging/__tests__/index.test.js +4 -4
  39. package/reactHooks/device/useMemoizedIsTablet.ts +3 -3
  40. package/reactHooks/feed/__tests__/useEntryScreenId.test.tsx +3 -0
  41. package/reactHooks/feed/useBatchLoading.ts +7 -1
  42. package/reactHooks/feed/useEntryScreenId.ts +2 -2
  43. package/reactHooks/feed/usePipesCacheReset.ts +3 -1
  44. package/reactHooks/flatList/useLoadNextPageIfNeeded.ts +13 -16
  45. package/reactHooks/layout/index.ts +1 -1
  46. package/reactHooks/layout/useDimensions/__tests__/{useDimensions.test.ts → useDimensions.test.tsx} +105 -25
  47. package/reactHooks/layout/useDimensions/useDimensions.ts +2 -2
  48. package/reactHooks/navigation/index.ts +7 -6
  49. package/reactHooks/navigation/useRoute.ts +8 -6
  50. package/reactHooks/player/TVSeekControlller/TVSeekController.ts +27 -10
  51. package/reactHooks/resolvers/useCellResolver.ts +6 -2
  52. package/reactHooks/resolvers/useComponentResolver.ts +8 -2
  53. package/reactHooks/screen/__tests__/useTargetScreenData.test.tsx +10 -2
  54. package/reactHooks/screen/useTargetScreenData.ts +4 -2
  55. package/reactHooks/state/useRivers.ts +1 -1
  56. package/reactHooks/usePluginConfiguration.ts +2 -2
  57. package/testUtils/index.tsx +29 -20
  58. package/utils/__tests__/mapAccum.test.ts +73 -0
  59. package/utils/__tests__/selectors.test.ts +124 -0
  60. package/utils/index.ts +10 -0
  61. package/utils/mapAccum.ts +23 -0
  62. package/utils/selectors.ts +46 -0
  63. package/zappFrameworkUtils/HookCallback/callbackNavigationAction.ts +49 -12
  64. package/zappFrameworkUtils/HookCallback/hookCallbackManifestExtensions.config.js +1 -1
@@ -0,0 +1,124 @@
1
+ import {
2
+ createPluginsByPathSelector,
3
+ createPluginsByModuleSelector,
4
+ combinePluginSelectors,
5
+ } from "../selectors";
6
+
7
+ describe("Plugin Selectors", () => {
8
+ const mockPlugins = [
9
+ {
10
+ identifier: "plugin1",
11
+ module: {
12
+ urlScheme: { host: "test" },
13
+ player: { type: "default" },
14
+ },
15
+ customField: true,
16
+ },
17
+ {
18
+ identifier: "plugin2",
19
+ module: {
20
+ urlScheme: { host: "other" },
21
+ },
22
+ },
23
+ {
24
+ identifier: "plugin3",
25
+ module: {
26
+ player: { type: "custom" },
27
+ },
28
+ },
29
+ ];
30
+
31
+ const mockState = {
32
+ plugins: mockPlugins,
33
+ };
34
+
35
+ describe("createPluginsByPathSelector", () => {
36
+ it("filters plugins by string path", () => {
37
+ const selector = createPluginsByPathSelector("customField");
38
+ const result = selector(mockState);
39
+
40
+ expect(result).toHaveLength(1);
41
+ expect(result[0].identifier).toBe("plugin1");
42
+ });
43
+
44
+ it("filters plugins by array path", () => {
45
+ const selector = createPluginsByPathSelector(["module", "urlScheme"]);
46
+ const result = selector(mockState);
47
+
48
+ expect(result).toHaveLength(2);
49
+ expect(result.map((p) => p.identifier)).toEqual(["plugin1", "plugin2"]);
50
+ });
51
+
52
+ it("handles missing plugins array", () => {
53
+ const selector = createPluginsByPathSelector("customField");
54
+ const result = selector({});
55
+
56
+ expect(result).toEqual([]);
57
+ });
58
+
59
+ it("handles non-existent path", () => {
60
+ const selector = createPluginsByPathSelector("nonexistent");
61
+ const result = selector(mockState);
62
+
63
+ expect(result).toEqual([]);
64
+ });
65
+ });
66
+
67
+ describe("createPluginsByModuleSelector", () => {
68
+ it("filters plugins by module path", () => {
69
+ const selector = createPluginsByModuleSelector("player");
70
+ const result = selector(mockState);
71
+
72
+ expect(result).toHaveLength(2);
73
+ expect(result.map((p) => p.identifier)).toEqual(["plugin1", "plugin3"]);
74
+ });
75
+
76
+ it("handles non-existent module", () => {
77
+ const selector = createPluginsByModuleSelector("nonexistent");
78
+ const result = selector(mockState);
79
+
80
+ expect(result).toEqual([]);
81
+ });
82
+ });
83
+
84
+ describe("combinePluginSelectors", () => {
85
+ it("combines multiple selectors with AND logic", () => {
86
+ const urlSchemeSelector = createPluginsByModuleSelector("urlScheme");
87
+ const playerSelector = createPluginsByModuleSelector("player");
88
+
89
+ const combinedSelector = combinePluginSelectors(
90
+ urlSchemeSelector,
91
+ playerSelector
92
+ );
93
+
94
+ const result = combinedSelector(mockState);
95
+
96
+ expect(result).toHaveLength(1);
97
+ expect(result[0].identifier).toBe("plugin1");
98
+ });
99
+
100
+ it("returns empty array when no plugins match all conditions", () => {
101
+ const urlSchemeSelector = createPluginsByModuleSelector("urlScheme");
102
+ const customSelector = createPluginsByPathSelector("nonexistent");
103
+
104
+ const combinedSelector = combinePluginSelectors(
105
+ urlSchemeSelector,
106
+ customSelector
107
+ );
108
+
109
+ const result = combinedSelector(mockState);
110
+
111
+ expect(result).toEqual([]);
112
+ });
113
+
114
+ it("handles empty state", () => {
115
+ const selector = combinePluginSelectors(
116
+ createPluginsByModuleSelector("urlScheme")
117
+ );
118
+
119
+ const result = selector({});
120
+
121
+ expect(result).toEqual([]);
122
+ });
123
+ });
124
+ });
package/utils/index.ts CHANGED
@@ -16,6 +16,8 @@ export { endsWith } from "./endsWith";
16
16
 
17
17
  export { take } from "./take";
18
18
 
19
+ export { mapAccum } from "./mapAccum";
20
+
19
21
  export {
20
22
  cloneDeep as clone,
21
23
  flatten,
@@ -34,4 +36,12 @@ export {
34
36
  last,
35
37
  toLower,
36
38
  isEqual as equals,
39
+ uniq,
40
+ uniqWith,
41
+ flowRight as compose,
42
+ partial,
43
+ clamp,
44
+ reverse,
45
+ set,
46
+ compact,
37
47
  } from "lodash";
@@ -0,0 +1,23 @@
1
+ import { curry } from "lodash";
2
+
3
+ /**
4
+ * A native reimplementation of Ramda's mapAccum.
5
+ *
6
+ * @template A, B, C
7
+ * @param {(acc: A, value: B) => [A, C]} fn - Function returning [newAcc, mappedValue]
8
+ * @param {A} acc - Initial accumulator
9
+ * @param {B[]} list - List to process
10
+ * @returns {[A, C[]]} - Tuple of [final accumulator, mapped array]
11
+ */
12
+ export const mapAccum = curry((fn, acc, list) => {
13
+ const result = [];
14
+ let currentAcc = acc;
15
+
16
+ for (let i = 0; i < list.length; i++) {
17
+ const [nextAcc, mapped] = fn(currentAcc, list[i]);
18
+ currentAcc = nextAcc;
19
+ result.push(mapped);
20
+ }
21
+
22
+ return [currentAcc, result];
23
+ });
@@ -0,0 +1,46 @@
1
+ import { get } from "lodash";
2
+
3
+ export type Selector<T> = (state: any) => T;
4
+
5
+ /**
6
+ * Creates a selector for filtering plugins by path
7
+ * @param path - Path to check in plugin. Can be a dot notation string or array
8
+ * @returns A selector function that returns matching plugins
9
+ */
10
+ export const createPluginsByPathSelector = (
11
+ path: string | string[]
12
+ ): Selector<any[]> => {
13
+ return (state: any) => {
14
+ const plugins = state.plugins || [];
15
+
16
+ return plugins.filter((plugin: any) => get(plugin, path));
17
+ };
18
+ };
19
+
20
+ /**
21
+ * Creates a selector for filtering plugins by module path
22
+ * @param modulePath - Module path to check in plugin (e.g., "urlScheme", "player")
23
+ * @returns A selector function that returns plugins with specified module
24
+ */
25
+ export const createPluginsByModuleSelector = (
26
+ modulePath: string
27
+ ): Selector<any[]> => {
28
+ return createPluginsByPathSelector(["module", modulePath]);
29
+ };
30
+
31
+ /**
32
+ * Creates a selector that combines multiple plugin selectors
33
+ * @param selectors - Array of plugin selectors to combine
34
+ * @returns A selector function that returns plugins matching all conditions
35
+ */
36
+ export const combinePluginSelectors = (
37
+ ...selectors: Selector<any[]>[]
38
+ ): Selector<any[]> => {
39
+ return (state: any) => {
40
+ return selectors.reduce((filtered, selector) => {
41
+ const selected = selector(state);
42
+
43
+ return filtered.filter((plugin) => selected.includes(plugin));
44
+ }, state.plugins || []);
45
+ };
46
+ };
@@ -1,6 +1,6 @@
1
1
  import { useNavigation, useRivers, useScreenContext } from "../../reactHooks";
2
2
  import { createLogger } from "../../logger";
3
- import { useCallback, useMemo } from "react";
3
+ import { useCallback, useMemo, useRef, useEffect } from "react";
4
4
 
5
5
  export enum NavigationCallbackOptions {
6
6
  DEFAULT = "default",
@@ -17,6 +17,7 @@ export enum ResultType {
17
17
  export type CallbackResult = hookCallbackArgs & {
18
18
  options?: {
19
19
  resultType?: ResultType;
20
+ navigator?: QuickBrickAppNavigator;
20
21
  };
21
22
  };
22
23
 
@@ -45,7 +46,7 @@ const legacyMappingKeys = {
45
46
  actionType: "login_completion_action",
46
47
  targetScreen: "navigate_to_login_screen",
47
48
  },
48
- "quick-brick-user-account-ui-component": {
49
+ "quick-brick-user-account-ui-component.login": {
49
50
  actionType: "callbackAction",
50
51
  },
51
52
  "quick-brick-login-multi-login-providers.login": {
@@ -56,9 +57,23 @@ const legacyMappingKeys = {
56
57
  actionType: "logout_completion_action",
57
58
  targetScreen: "navigate_to_logout_screen",
58
59
  },
60
+ "quick-brick-storefront": {
61
+ actionType: "purchase_completion_action",
62
+ targetScreen: "navigate_to_screen_after_purchase",
63
+ },
64
+ "zapp_login_plugin_oauth_tv_2_0.login": {
65
+ actionType: "login_completion_action",
66
+ targetScreen: "navigate_to_login_screen",
67
+ },
68
+ "zapp_login_plugin_oauth_tv_2_0.logout": {
69
+ actionType: "logout_completion_action",
70
+ targetScreen: "navigate_to_logout_screen",
71
+ },
59
72
  };
60
73
 
61
- const NAV_ACTIONS = Object.values(NavigationCallbackOptions) as string[];
74
+ const NAV_ACTIONS = (
75
+ Object.values(NavigationCallbackOptions) as string[]
76
+ ).filter((value) => value !== NavigationCallbackOptions.DEFAULT);
62
77
 
63
78
  const isNavAction = (v: unknown): v is NavigationCallbackOptions =>
64
79
  typeof v === "string" && NAV_ACTIONS.includes(v);
@@ -69,7 +84,8 @@ export const getNavigationKeys = (
69
84
  ): NavKeys => {
70
85
  const general = (item?.general ?? {}) as General;
71
86
 
72
- const pluginIdentifier = (item as any).identifier ?? item?.type ?? "";
87
+ const pluginIdentifier =
88
+ (item as any).identifier ?? item?.type ?? item?.component_type ?? "";
73
89
 
74
90
  const legacy =
75
91
  legacyMappingKeys[`${pluginIdentifier}.${resultType}`] ??
@@ -121,6 +137,12 @@ export const useCallbackNavigationAction = (
121
137
  const rivers = useRivers();
122
138
  const screenContext = useScreenContext();
123
139
 
140
+ const navigationRef = useRef(navigation);
141
+
142
+ useEffect(() => {
143
+ navigationRef.current = navigation;
144
+ }, [navigation]);
145
+
124
146
  const overrideCallbackFromComponent = useMemo(() => {
125
147
  log_verbose(`${LogPrefix}: overridden callbackAction by component`);
126
148
 
@@ -154,20 +176,35 @@ export const useCallbackNavigationAction = (
154
176
  return;
155
177
  }
156
178
 
157
- const data = getNavigationKeys(item, args.options?.resultType ?? null);
179
+ let data = getNavigationKeys(item, args.options?.resultType ?? null);
158
180
 
159
181
  if (!data) {
160
- hookCallback?.(args);
182
+ const isScreen = !hookCallback;
161
183
 
162
- return;
184
+ if (isScreen && args.options?.resultType === ResultType.login) {
185
+ log_debug(
186
+ `${LogPrefix} no navigation data found, applying GO BACK for login screen`
187
+ );
188
+
189
+ data = {
190
+ action: NavigationCallbackOptions.GO_BACK,
191
+ targetScreenId: null,
192
+ };
193
+ } else {
194
+ hookCallback?.(args);
195
+
196
+ return;
197
+ }
163
198
  }
164
199
 
165
200
  hookCallback?.({ ...args, success: false, cancelled: true });
201
+ const currentNavigation = navigationRef.current;
166
202
 
167
203
  switch (data.action) {
168
204
  case NavigationCallbackOptions.GO_BACK: {
169
- if (navigation.canGoBack()) {
170
- navigation.goBack();
205
+ if (currentNavigation.canGoBack()) {
206
+ currentNavigation.goBack();
207
+
171
208
  log_info(`${LogPrefix} performing 'GO BACK' action`);
172
209
  } else {
173
210
  log_info(`${LogPrefix} cannot perform 'GO BACK' action — ignoring`);
@@ -177,7 +214,7 @@ export const useCallbackNavigationAction = (
177
214
  }
178
215
 
179
216
  case NavigationCallbackOptions.GO_HOME: {
180
- navigation.goHome();
217
+ currentNavigation.goHome();
181
218
  log_info(`${LogPrefix} performing 'GO HOME' action`);
182
219
  break;
183
220
  }
@@ -193,7 +230,7 @@ export const useCallbackNavigationAction = (
193
230
  const screen = rivers[screenId];
194
231
 
195
232
  if (screen) {
196
- navigation.replace(screen);
233
+ currentNavigation.replace(screen);
197
234
 
198
235
  log_info(
199
236
  `${LogPrefix} performing 'GO TO SCREEN' action to screen: ${screenId}`
@@ -210,7 +247,7 @@ export const useCallbackNavigationAction = (
210
247
  }
211
248
  }
212
249
  },
213
- [item, navigation, rivers]
250
+ [item, rivers]
214
251
  );
215
252
 
216
253
  return overrideCallbackFromComponent || callbackAction;
@@ -20,7 +20,7 @@ const callbackKeyPrefix = (key, prefix, keySeparator = "_") =>
20
20
 
21
21
  const extendManifestWithHookCallback = (prefix = null) => ({
22
22
  group: true,
23
- label: "CallBack Navigation",
23
+ label: `CallBack Navigation: ${prefix}`,
24
24
  folded: true,
25
25
  fields: [
26
26
  {