@applicaster/zapp-react-native-utils 14.0.9-alpha.2354002522 → 14.0.9-alpha.3232321524

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.
@@ -31,4 +31,42 @@ describe("imageSrcFromMediaItem", () => {
31
31
 
32
32
  expect(imageSrcFromMediaItem(badEntry, ["image_base"])).toBeUndefined();
33
33
  });
34
+
35
+ it("returns undefined when fallback is false and key is not found", () => {
36
+ const result = imageSrcFromMediaItem(entry as ZappEntry, [
37
+ "does_not_exist",
38
+ false,
39
+ ]);
40
+
41
+ expect(result).toBeUndefined();
42
+ });
43
+
44
+ it("returns src when fallback is false and key is found", () => {
45
+ const result = imageSrcFromMediaItem(entry as ZappEntry, [
46
+ "logo_thumbnail",
47
+ false,
48
+ ]);
49
+
50
+ expect(result).toEqual(entry.media_group[1].media_item[0].src);
51
+ });
52
+
53
+ it("returns image_base as fallback when fallback is explicitly true and key is not found", () => {
54
+ const result = imageSrcFromMediaItem(entry as ZappEntry, [
55
+ "does_not_exist",
56
+ true,
57
+ ]);
58
+
59
+ const fallback = entry.media_group[0].media_item[0];
60
+ expect(result).toEqual(fallback.src);
61
+ expect(fallback.key).toBe("image_base");
62
+ });
63
+
64
+ it("returns src when fallback is explicitly true and key is found", () => {
65
+ const result = imageSrcFromMediaItem(entry as ZappEntry, [
66
+ "logo_thumbnail",
67
+ true,
68
+ ]);
69
+
70
+ expect(result).toEqual(entry.media_group[1].media_item[0].src);
71
+ });
34
72
  });
@@ -155,9 +155,9 @@ export function getMediaItems(entry: ZappEntry): Option<ZappMediaItem[]> {
155
155
 
156
156
  /**
157
157
  * Retrieves the "src" value from a media item in the entry's media group,
158
- * based on a provided key, with fallback logic.
158
+ * based on a provided key, with optional fallback logic.
159
159
  *
160
- * Fallback order:
160
+ * Fallback order (when enabled):
161
161
  * 1. Attempts to find a media item with the specified key (or "image_base" if none provided).
162
162
  * 2. If not found, attempts to find a media item with the key "image_base".
163
163
  * 3. If still not found, falls back to the first available media item.
@@ -166,15 +166,19 @@ export function getMediaItems(entry: ZappEntry): Option<ZappMediaItem[]> {
166
166
  * since empty URIs are invalid in some platforms (e.g., React Native).
167
167
  *
168
168
  * @param {ZappEntry} entry - The entry object containing a media group.
169
- * @param {string[] | unknown} arg - A single-element array containing the key to look up, or any unknown value.
169
+ * @param {string[] | unknown} arg - Can be an array or any other value (treated as empty array).
170
+ * When an array:
171
+ * - First element: The key to look up. If omitted or undefined, defaults to "image_base".
172
+ * - Second element: Boolean to enable/disable fallback logic. If omitted or undefined, defaults to true.
170
173
  * @returns {?string} The "src" URI from the matched media item, or undefined if not found or empty.
171
174
  */
172
175
  export function imageSrcFromMediaItem(
173
176
  entry: ZappEntry,
174
177
  arg: string[] | unknown
175
178
  ): Option<string> {
176
- const args: unknown = R.unless(Array.isArray, Array)(arg || []);
179
+ const args: any = R.unless(Array.isArray, Array)(arg || []);
177
180
  const imageKey: string = args?.[0] || "image_base"; // always a single key in this function
181
+ const fallback: boolean = args?.[1] !== false;
178
182
 
179
183
  const mediaItems = getMediaItems(entry);
180
184
 
@@ -185,14 +189,16 @@ export function imageSrcFromMediaItem(
185
189
  // Try to find the item with the given key
186
190
  let foundItem = mediaItems.find((item) => item.key === imageKey);
187
191
 
188
- // If not found and key was not "image_base", try to find "image_base"
189
- if (!foundItem && imageKey !== "image_base") {
190
- foundItem = mediaItems.find((item) => item.key === "image_base");
191
- }
192
+ if (fallback) {
193
+ // If not found and key was not "image_base", try to find "image_base"
194
+ if (!foundItem && imageKey !== "image_base") {
195
+ foundItem = mediaItems.find((item) => item.key === "image_base");
196
+ }
192
197
 
193
- // If still not found, default to first item
194
- if (!foundItem) {
195
- foundItem = mediaItems[0];
198
+ // If still not found, default to first item
199
+ if (!foundItem) {
200
+ foundItem = mediaItems[0];
201
+ }
196
202
  }
197
203
 
198
204
  const src = foundItem?.src;
@@ -331,6 +331,34 @@ function getPlayerConfiguration({ platform, version }) {
331
331
  label_tooltip: "Hint for skip intro button accessibility",
332
332
  type: "text_input",
333
333
  },
334
+ {
335
+ type: "text_input",
336
+ label: "Text Tracks label",
337
+ key: "text_label_text",
338
+ initial_value: "Subtitles",
339
+ label_tooltip: "Label for the text tracks list",
340
+ },
341
+ {
342
+ type: "text_input",
343
+ label: "Audio Tracks label",
344
+ key: "audio_label_text",
345
+ initial_value: "Audio Tracks",
346
+ label_tooltip: "Label for the audio tracks list",
347
+ },
348
+ {
349
+ type: "text_input",
350
+ label: "Off label",
351
+ key: "off_label_text",
352
+ initial_value: "Off",
353
+ label_tooltip: "Label for the 'Off' option in text tracks list",
354
+ },
355
+ {
356
+ type: "text_input",
357
+ label: "Default label",
358
+ key: "default_audio_label_text",
359
+ initial_value: "Default",
360
+ label_tooltip: "Label for the 'Default' audio in audio tracks list",
361
+ },
334
362
  ],
335
363
  };
336
364
 
@@ -3424,6 +3452,22 @@ function getPlayerConfiguration({ platform, version }) {
3424
3452
  label_tooltip:
3425
3453
  "This field allows you to override the player configuration / initialization options. i.e. html5: { vhs: { limitRenditionByPlayerDimensions: false } } Please ensure your configuration matches the player you are targeting, and that it is a valid json. See config options here: https://videojs.com/guides/options/",
3426
3454
  },
3455
+ {
3456
+ key: "advanced_player_styles",
3457
+ label: "Advanced Player Styles",
3458
+ type: "text_input",
3459
+ multiline: true,
3460
+ initial_value: `.vjs-text-track-cue {
3461
+ inset: unset !important;
3462
+ top: 94% !important;
3463
+ bottom: 0 !important;
3464
+ left: 0 !important;
3465
+ right: 0 !important;
3466
+ width: 100% !important;
3467
+ }`,
3468
+ label_tooltip:
3469
+ "This field allows you to inject custom CSS styles to override default player styles. The styles will be injected into the document head and can be used to customize the appearance of the player. Leave empty to use only default styles.",
3470
+ },
3427
3471
  {
3428
3472
  key: "use_dashjs_player",
3429
3473
  label: "Use Enhanced Player for DASH streams",
@@ -5,7 +5,8 @@ import { layoutV2TypeMatcher } from "./layoutV2TypeMatcher";
5
5
  import { HOOKS_EVENTS } from "../appUtils/HooksManager/constants";
6
6
  import { HooksManager } from "../appUtils/HooksManager";
7
7
  import { appStore } from "@applicaster/zapp-react-native-redux/AppStore";
8
- import { HookModalContextT } from "@applicaster/zapp-react-native-ui-components/Contexts/ZappHookModalContext";
8
+
9
+ import { zappHookModalStore } from "@applicaster/zapp-react-native-ui-components/Contexts/ZappHookModalContext";
9
10
  import { logger } from "@applicaster/zapp-react-native-utils/logger";
10
11
  import {
11
12
  isGeneralPlugin,
@@ -15,6 +16,8 @@ import {
15
16
  } from "./itemTypeMatchers";
16
17
  import { RootState } from "@applicaster/zapp-react-native-redux/store";
17
18
 
19
+ import { pick } from "@applicaster/zapp-react-native-utils/utils";
20
+
18
21
  type PathAttribute = {
19
22
  screenType: string;
20
23
  screenId: string;
@@ -403,15 +406,22 @@ export const mapContentTypesToRivers = (
403
406
  };
404
407
 
405
408
  export const runZappHooksForEntry = async (
406
- entry: HookPluginProps["payload"],
407
- {
409
+ entry: HookPluginProps["payload"]
410
+ ): Promise<{ success: boolean; payload: ZappEntry }> => {
411
+ const {
408
412
  setState,
409
413
  resetState,
410
414
  setIsHooksExecutionInProgress,
411
415
  setIsPresentingFullScreen,
412
416
  setIsRunningInBackground,
413
- }: Partial<HookModalContextT>
414
- ): Promise<{ success: boolean; payload: ZappEntry }> => {
417
+ } = pick(zappHookModalStore.getState(), [
418
+ "setState",
419
+ "resetState",
420
+ "setIsHooksExecutionInProgress",
421
+ "setIsPresentingFullScreen",
422
+ "setIsRunningInBackground",
423
+ ]);
424
+
415
425
  resetState?.();
416
426
 
417
427
  let success;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@applicaster/zapp-react-native-utils",
3
- "version": "14.0.9-alpha.2354002522",
3
+ "version": "14.0.9-alpha.3232321524",
4
4
  "description": "Applicaster Zapp React Native utilities package",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -27,7 +27,7 @@
27
27
  },
28
28
  "homepage": "https://github.com/applicaster/quickbrick#readme",
29
29
  "dependencies": {
30
- "@applicaster/applicaster-types": "14.0.9-alpha.2354002522",
30
+ "@applicaster/applicaster-types": "14.0.9-alpha.3232321524",
31
31
  "buffer": "^5.2.1",
32
32
  "camelize": "^1.0.0",
33
33
  "dayjs": "^1.11.10",
@@ -1,20 +1,24 @@
1
1
  import { reactHooksLogger } from "../../logger";
2
- import { useZappHooksModalState } from "@applicaster/zapp-react-dom-ui-components/Components/ZappHooksModal/hooks/useZappHooksModalState";
2
+ import { useZappHookModalStore } from "@applicaster/zapp-react-native-ui-components/Contexts/ZappHookModalContext";
3
3
 
4
4
  const logger = reactHooksLogger.addSubsystem("useHookModalScreenData");
5
5
 
6
6
  type VariousScreenData = LegacyNavigationScreenData | ZappRiver | ZappEntry;
7
7
 
8
- export const useHookModalScreenData = (): VariousScreenData | undefined => {
9
- const hookModalState = useZappHooksModalState();
8
+ export const useHookModalScreenData = (
9
+ skipSubscription: boolean
10
+ ): VariousScreenData | undefined => {
11
+ const hookModalState = useZappHookModalStore((state) =>
12
+ skipSubscription ? undefined : state.state
13
+ );
10
14
 
11
- if (!hookModalState.state?.screenData) {
15
+ if (!hookModalState?.screenData) {
12
16
  return;
13
17
  }
14
18
 
15
19
  try {
16
- const screenData = hookModalState.state.screenData?.payload;
17
- const hookPlugin = hookModalState.state.screenData.hookPlugin;
20
+ const screenData = hookModalState.screenData?.payload;
21
+ const hookPlugin = hookModalState.screenData.hookPlugin;
18
22
 
19
23
  if (hookPlugin?.screen_id) {
20
24
  return {
@@ -23,7 +27,7 @@ export const useHookModalScreenData = (): VariousScreenData | undefined => {
23
27
  };
24
28
  }
25
29
 
26
- return hookModalState.state.screenData?.payload;
30
+ return hookModalState.screenData?.payload;
27
31
  } catch (error) {
28
32
  logger.error({
29
33
  message: "Hook modal screen data creation failed",
@@ -34,6 +38,6 @@ export const useHookModalScreenData = (): VariousScreenData | undefined => {
34
38
  jsOnly: true,
35
39
  });
36
40
 
37
- return hookModalState.state.screenData?.payload;
41
+ return hookModalState.screenData?.payload;
38
42
  }
39
43
  };
@@ -5,7 +5,6 @@ import { isNavBarVisible, useRoute } from "../";
5
5
  import { Provider } from "react-redux";
6
6
  import configureMockStore from "redux-mock-store";
7
7
  import { NavigationContext } from "@applicaster/zapp-react-native-ui-components/Contexts/NavigationContext";
8
- import { ZappHookModalContext } from "@applicaster/zapp-react-native-ui-components/Contexts";
9
8
  import { ROUTE_TYPES } from "@applicaster/zapp-react-native-utils/navigationUtils/routeTypes";
10
9
  import { ScreenDataContext } from "@applicaster/zapp-react-native-ui-components/Contexts/ScreenDataContext";
11
10
 
@@ -83,9 +82,45 @@ const hookModalContextState = {
83
82
  },
84
83
  setState: jest.fn(),
85
84
  resetState: jest.fn(),
85
+ hookPresentationMode: null,
86
86
  };
87
87
 
88
88
  const hooksModalPathname = `${ROUTE_TYPES.HOOKS_MODAL}/${hookModalContextState.state.screenData.payload.id}`;
89
+
90
+ jest.mock(
91
+ "@applicaster/zapp-react-native-ui-components/Contexts/ZappHookModalContext",
92
+ () => {
93
+ // Define state inside mock factory to avoid hoisting issues
94
+ const mockState = {
95
+ isRunningInBackground: false,
96
+ setIsRunningInBackground: jest.fn(),
97
+ setIsPresentingFullScreen: jest.fn(),
98
+ isPresentationFullScreen: true,
99
+ isHooksExecutionInProgress: true,
100
+ setIsHooksExecutionInProgress: jest.fn(),
101
+ state: {
102
+ path: "/home",
103
+ screenData: { payload: { id: "hookExecutedInModal" } },
104
+ },
105
+ setState: jest.fn(),
106
+ resetState: jest.fn(),
107
+ hookPresentationMode: null,
108
+ };
109
+
110
+ const mockHook = jest.fn((selector) => {
111
+ return selector ? selector(mockState) : mockState;
112
+ }) as jest.MockedFunction<any> & { getState: () => typeof mockState };
113
+
114
+ // Add getState method to the mock hook (Zustand stores have this)
115
+ mockHook.getState = jest.fn(() => mockState);
116
+
117
+ return {
118
+ useZappHookModalStore: mockHook,
119
+ zappHookModalStore: mockHook,
120
+ };
121
+ }
122
+ );
123
+
89
124
  const videoModalPathname = `${ROUTE_TYPES.VIDEO_MODAL}/${videoModalNavigator.videoModalState.item.id}`;
90
125
  const mockStore = configureMockStore();
91
126
 
@@ -125,13 +160,11 @@ const videoModalWrapper = ({ children }) => (
125
160
 
126
161
  const hookModalWrapper = ({ children }) => (
127
162
  <Provider store={store}>
128
- <ZappHookModalContext.ReactContext.Provider value={hookModalContextState}>
129
- <NavigationContext.Provider value={hookModalNavigator}>
130
- <PathnameContext.Provider value={hooksModalPathname}>
131
- {children}
132
- </PathnameContext.Provider>
133
- </NavigationContext.Provider>
134
- </ZappHookModalContext.ReactContext.Provider>
163
+ <NavigationContext.Provider value={hookModalNavigator}>
164
+ <PathnameContext.Provider value={hooksModalPathname}>
165
+ {children}
166
+ </PathnameContext.Provider>
167
+ </NavigationContext.Provider>
135
168
  </Provider>
136
169
  );
137
170
 
@@ -1,5 +1,6 @@
1
- import { useContext, useEffect, useMemo, useRef } from "react";
1
+ import { useEffect, useMemo, useRef } from "react";
2
2
  import { BackHandler } from "react-native";
3
+ import { shallow } from "zustand/shallow";
3
4
 
4
5
  import {
5
6
  useContentTypes,
@@ -8,13 +9,13 @@ import {
8
9
  import { HooksManager } from "@applicaster/zapp-react-native-utils/appUtils/HooksManager";
9
10
 
10
11
  import { LONG_KEY_PRESS_TIMEOUT } from "@applicaster/quick-brick-core/const";
11
- import { ZappHookModalContext } from "@applicaster/zapp-react-native-ui-components/Contexts";
12
- import { HookModalContextT } from "@applicaster/zapp-react-native-ui-components/Contexts/ZappHookModalContext";
12
+ import { useZappHookModalStore } from "@applicaster/zapp-react-native-ui-components/Contexts/ZappHookModalContext";
13
13
  import { HOOKS_EVENTS } from "../../appUtils/HooksManager/constants";
14
14
  import { getRiverFromRoute, getTargetRoute } from "../../navigationUtils";
15
15
  import { useConnectionInfo } from "../connection";
16
16
 
17
17
  import { isTV, isWeb } from "@applicaster/zapp-react-native-utils/reactUtils";
18
+ import { pick } from "@applicaster/zapp-react-native-utils/utils";
18
19
  import { useNavbarState } from "../screen";
19
20
 
20
21
  export { useNavigation } from "./useNavigation";
@@ -166,7 +167,17 @@ export const useZappHooksForEntry = (
166
167
  setIsHooksExecutionInProgress,
167
168
  setIsPresentingFullScreen,
168
169
  setIsRunningInBackground,
169
- }: HookModalContextT = useContext(ZappHookModalContext.ReactContext);
170
+ } = useZappHookModalStore(
171
+ (state) =>
172
+ pick(state, [
173
+ "setState",
174
+ "resetState",
175
+ "setIsHooksExecutionInProgress",
176
+ "setIsPresentingFullScreen",
177
+ "setIsRunningInBackground",
178
+ ]),
179
+ shallow
180
+ );
170
181
 
171
182
  const {
172
183
  appData: { layoutVersion },
@@ -52,7 +52,9 @@ export const useRoute = (
52
52
 
53
53
  const modalScreenData = modalState.screen;
54
54
 
55
- const hookModalScreenData = useHookModalScreenData();
55
+ const hookModalScreenData = useHookModalScreenData(
56
+ !isHookModalPathname(pathname)
57
+ );
56
58
 
57
59
  const videoModalScreenData =
58
60
  navigator?.videoModalState?.item &&
@@ -17,7 +17,7 @@ export const resolveStorageContextKey = (storageKey: string) => {
17
17
  const key = splitKey[1];
18
18
 
19
19
  if (!key) {
20
- return getFromSessionOrLocalStorage(key);
20
+ return getFromSessionOrLocalStorage(namespaceOrKey);
21
21
  } else {
22
22
  return getFromSessionOrLocalStorage(key, namespaceOrKey);
23
23
  }