@applicaster/zapp-react-native-ui-components 15.0.0-rc.14 → 15.0.0-rc.140

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 (197) hide show
  1. package/Components/AnimatedInOut/index.tsx +69 -26
  2. package/Components/BaseFocusable/index.ios.ts +12 -2
  3. package/Components/Cell/Cell.tsx +14 -3
  4. package/Components/Cell/CellWithFocusable.tsx +9 -0
  5. package/Components/Cell/FocusableWrapper.tsx +3 -0
  6. package/Components/Cell/TvOSCellComponent.tsx +25 -6
  7. package/Components/Focusable/Focusable.tsx +4 -2
  8. package/Components/Focusable/FocusableTvOS.tsx +18 -1
  9. package/Components/Focusable/__tests__/__snapshots__/FocusableTvOS.test.tsx.snap +1 -0
  10. package/Components/FocusableGroup/FocusableTvOS.tsx +32 -1
  11. package/Components/GeneralContentScreen/GeneralContentScreen.tsx +39 -28
  12. package/Components/GeneralContentScreen/__tests__/GeneralContentScreen.test.tsx +104 -0
  13. package/Components/GeneralContentScreen/utils/__tests__/getScreenDataSource.test.ts +19 -0
  14. package/Components/GeneralContentScreen/utils/__tests__/useCurationAPI.test.js +1 -1
  15. package/Components/GeneralContentScreen/utils/getScreenDataSource.ts +9 -0
  16. package/Components/GeneralContentScreen/utils/useCurationAPI.ts +22 -6
  17. package/Components/HandlePlayable/HandlePlayable.tsx +33 -94
  18. package/Components/HandlePlayable/const.ts +3 -0
  19. package/Components/HandlePlayable/utils.ts +105 -0
  20. package/Components/HookRenderer/HookRenderer.tsx +40 -10
  21. package/Components/HookRenderer/__tests__/HookRenderer.test.tsx +60 -0
  22. package/Components/Layout/TV/LayoutBackground.tsx +5 -2
  23. package/Components/Layout/TV/NavBarContainer.tsx +1 -10
  24. package/Components/Layout/TV/ScreenContainer.tsx +2 -6
  25. package/Components/Layout/TV/__tests__/__snapshots__/NavBarContainer.test.tsx.snap +7 -12
  26. package/Components/Layout/TV/__tests__/__snapshots__/ScreenContainer.test.tsx.snap +7 -12
  27. package/Components/Layout/TV/index.tsx +3 -4
  28. package/Components/Layout/TV/index.web.tsx +3 -4
  29. package/Components/LinkHandler/LinkHandler.tsx +2 -2
  30. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/__tests__/model.test.ts +80 -0
  31. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/__tests__/placement.test.ts +187 -0
  32. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/__tests__/selectors.test.ts +45 -0
  33. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/__tests__/style.test.ts +49 -0
  34. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/model.ts +47 -0
  35. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/placement.ts +170 -0
  36. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/selectors.ts +26 -0
  37. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/style.ts +29 -0
  38. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/types.ts +37 -0
  39. package/Components/MasterCell/DefaultComponents/BorderContainerView/__tests__/index.test.tsx +16 -1
  40. package/Components/MasterCell/DefaultComponents/BorderContainerView/index.tsx +30 -2
  41. package/Components/MasterCell/DefaultComponents/Button.tsx +0 -15
  42. package/Components/MasterCell/DefaultComponents/Image/Image.android.tsx +5 -1
  43. package/Components/MasterCell/DefaultComponents/Image/Image.ios.tsx +11 -3
  44. package/Components/MasterCell/DefaultComponents/Image/Image.web.tsx +9 -1
  45. package/Components/MasterCell/DefaultComponents/Image/hooks/useImage.ts +15 -14
  46. package/Components/MasterCell/DefaultComponents/LiveImage/__tests__/prepareEntry.test.ts +352 -0
  47. package/Components/MasterCell/DefaultComponents/LiveImage/executePreloadHooks.ts +136 -0
  48. package/Components/MasterCell/DefaultComponents/LiveImage/index.tsx +43 -22
  49. package/Components/MasterCell/DefaultComponents/PressableView.tsx +261 -0
  50. package/Components/MasterCell/DefaultComponents/SecondaryImage/Image.tsx +40 -39
  51. package/Components/MasterCell/DefaultComponents/SecondaryImage/__tests__/Image.test.tsx +95 -0
  52. package/Components/MasterCell/DefaultComponents/SecondaryImage/__tests__/__snapshots__/Image.test.tsx.snap +86 -0
  53. package/Components/MasterCell/DefaultComponents/SecondaryImage/__tests__/index.test.ts +141 -0
  54. package/Components/MasterCell/DefaultComponents/SecondaryImage/hooks/__tests__/useGetImageDimensions.test.ts +7 -6
  55. package/Components/MasterCell/DefaultComponents/SecondaryImage/index.ts +1 -1
  56. package/Components/MasterCell/DefaultComponents/Text/index.tsx +10 -14
  57. package/Components/MasterCell/DefaultComponents/index.ts +2 -0
  58. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/Asset.ts +42 -0
  59. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/Button.ts +127 -0
  60. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/ButtonContainerView.ts +23 -0
  61. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/Spacer.ts +16 -0
  62. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/TextLabel.ts +67 -0
  63. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/TextLabelsContainer.ts +32 -0
  64. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/__tests__/PressableView.test.tsx +195 -0
  65. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/__tests__/builders.test.ts +140 -0
  66. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/__tests__/index.test.ts +222 -0
  67. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/helpers.ts +105 -0
  68. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/index.ts +104 -0
  69. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/utils/__tests__/insertButtons.test.ts +118 -0
  70. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/utils/index.ts +73 -0
  71. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/__tests__/index.test.ts +86 -0
  72. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/index.ts +35 -48
  73. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/utils/__tests__/getPluginIdentifier.test.ts +115 -29
  74. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/utils/index.ts +39 -144
  75. package/Components/MasterCell/elementMapper.tsx +1 -0
  76. package/Components/MasterCell/hoc/__tests__/withAsyncRender.test.tsx +219 -0
  77. package/Components/MasterCell/hoc/withAsyncRender.tsx +9 -7
  78. package/Components/MasterCell/index.tsx +2 -0
  79. package/Components/MasterCell/utils/__tests__/resolveColor.test.js +82 -3
  80. package/Components/MasterCell/utils/index.ts +61 -31
  81. package/Components/MeasurmentsPortal/MeasurementsPortal.tsx +102 -87
  82. package/Components/MeasurmentsPortal/__tests__/MeasurementsPortal.test.tsx +355 -0
  83. package/Components/OfflineHandler/NotificationView/NotificationView.lg.tsx +17 -9
  84. package/Components/OfflineHandler/NotificationView/NotificationView.samsung.tsx +16 -8
  85. package/Components/OfflineHandler/NotificationView/NotificationView.tsx +2 -2
  86. package/Components/OfflineHandler/NotificationView/__tests__/index.test.tsx +17 -18
  87. package/Components/OfflineHandler/NotificationView/utils.ts +34 -0
  88. package/Components/OfflineHandler/__tests__/index.test.tsx +27 -18
  89. package/Components/PlayerContainer/PlayerContainer.tsx +43 -64
  90. package/Components/PlayerImageBackground/index.tsx +3 -22
  91. package/Components/PreloaderWrapper/__tests__/index.test.tsx +26 -0
  92. package/Components/PreloaderWrapper/index.tsx +15 -0
  93. package/Components/River/ComponentsMap/ComponentsMap.tsx +18 -16
  94. package/Components/River/ComponentsMap/hooks/__tests__/useLoadingState.test.ts +1 -1
  95. package/Components/River/RefreshControl.tsx +19 -82
  96. package/Components/River/River.tsx +9 -82
  97. package/Components/River/RiverItem.tsx +26 -20
  98. package/Components/River/TV/River.tsx +31 -14
  99. package/Components/River/TV/index.tsx +8 -4
  100. package/Components/River/TV/utils/__tests__/toStringOrEmpty.test.ts +30 -0
  101. package/Components/River/TV/utils/index.ts +4 -0
  102. package/Components/River/TV/withFocusableGroupForContent.tsx +71 -0
  103. package/Components/River/__tests__/__snapshots__/componentsMap.test.js.snap +2 -0
  104. package/Components/River/__tests__/componentsMap.test.js +38 -0
  105. package/Components/River/hooks/__tests__/usePullToRefresh.test.ts +132 -0
  106. package/Components/River/hooks/index.ts +1 -0
  107. package/Components/River/hooks/usePullToRefresh.ts +51 -0
  108. package/Components/Screen/TV/index.web.tsx +4 -2
  109. package/Components/Screen/__tests__/Screen.test.tsx +66 -42
  110. package/Components/Screen/__tests__/__snapshots__/Screen.test.tsx.snap +68 -44
  111. package/Components/Screen/hooks.ts +75 -6
  112. package/Components/Screen/index.tsx +9 -4
  113. package/Components/Screen/navigationHandler.ts +49 -24
  114. package/Components/Screen/orientationHandler.ts +10 -13
  115. package/Components/ScreenFeedLoader/ScreenFeedLoader.tsx +46 -0
  116. package/Components/ScreenFeedLoader/__tests__/ScreenFeedLoader.test.tsx +94 -0
  117. package/Components/ScreenFeedLoader/index.ts +1 -0
  118. package/Components/ScreenResolver/__tests__/screenResolver.test.js +24 -0
  119. package/Components/ScreenResolver/hooks/index.ts +3 -0
  120. package/Components/ScreenResolver/hooks/useGetComponent.ts +15 -0
  121. package/Components/ScreenResolver/hooks/useScreenComponentResolver.tsx +90 -0
  122. package/Components/ScreenResolver/index.tsx +15 -111
  123. package/Components/ScreenResolver/utils/__tests__/getScreenTypeProps.test.ts +45 -0
  124. package/Components/ScreenResolver/utils/getScreenTypeProps.ts +43 -0
  125. package/Components/ScreenResolver/utils/index.ts +1 -0
  126. package/Components/ScreenResolver/withDefaultScreenContext.tsx +16 -0
  127. package/Components/ScreenResolverFeedProvider/ScreenResolverFeedProvider.tsx +25 -0
  128. package/Components/ScreenResolverFeedProvider/__tests__/ScreenResolverFeedProvider.test.tsx +44 -0
  129. package/Components/ScreenResolverFeedProvider/index.ts +1 -0
  130. package/Components/ScreenRevealManager/ScreenRevealManager.ts +40 -8
  131. package/Components/ScreenRevealManager/__tests__/ScreenRevealManager.test.ts +86 -69
  132. package/Components/ScreenRevealManager/withScreenRevealManager.tsx +44 -26
  133. package/Components/Tabs/TV/Tabs.tsx +20 -3
  134. package/Components/Tabs/TabContent.tsx +7 -4
  135. package/Components/TopCutoffOverlay/hooks/__tests__/useMarginTop.test.ts +130 -0
  136. package/Components/TopCutoffOverlay/hooks/index.ts +1 -0
  137. package/Components/TopCutoffOverlay/hooks/useMarginTop.ts +59 -0
  138. package/Components/TopCutoffOverlay/index.tsx +55 -0
  139. package/Components/Transitioner/Scene.tsx +10 -3
  140. package/Components/Transitioner/index.js +3 -3
  141. package/Components/VideoLive/LiveImageManager.ts +199 -54
  142. package/Components/VideoLive/PlayerLiveImageComponent.tsx +31 -33
  143. package/Components/VideoLive/__tests__/PlayerLiveImageComponent.test.tsx +2 -17
  144. package/Components/VideoLive/__tests__/__snapshots__/PlayerLiveImageComponent.test.tsx.snap +1 -0
  145. package/Components/VideoModal/ModalAnimation/ModalAnimationContext.tsx +118 -171
  146. package/Components/VideoModal/ModalAnimation/index.ts +2 -13
  147. package/Components/VideoModal/ModalAnimation/utils.ts +1 -327
  148. package/Components/VideoModal/PlayerWrapper.tsx +14 -88
  149. package/Components/VideoModal/VideoModal.tsx +1 -5
  150. package/Components/VideoModal/__tests__/PlayerWrapper.test.tsx +1 -0
  151. package/Components/VideoModal/hooks/__tests__/useDelayedPlayerDetails.test.ts +15 -7
  152. package/Components/VideoModal/hooks/useModalSize.ts +10 -5
  153. package/Components/VideoModal/playerWrapperStyle.ts +70 -0
  154. package/Components/VideoModal/playerWrapperUtils.ts +91 -0
  155. package/Components/VideoModal/utils.ts +19 -9
  156. package/Components/Viewport/ViewportAware/__tests__/viewportAware.test.js +0 -2
  157. package/Components/Viewport/ViewportAware/index.tsx +16 -7
  158. package/Components/Viewport/ViewportEvents/__tests__/viewportEvents.test.js +1 -1
  159. package/Components/ZappUIComponent/index.tsx +12 -6
  160. package/Components/default-cell-renderer/viewTrees/mobile/index.ts +0 -3
  161. package/Components/index.js +1 -1
  162. package/Contexts/ScreenContext/__tests__/index.test.tsx +57 -0
  163. package/Contexts/ScreenContext/index.tsx +71 -19
  164. package/Contexts/ScreenTrackedViewPositionsContext/__tests__/index.test.tsx +1 -1
  165. package/Contexts/ZappHookModalContext/index.tsx +37 -61
  166. package/Contexts/ZappPipesContext/ZappPipesContextFactory.tsx +18 -7
  167. package/Contexts/index.ts +0 -2
  168. package/Decorators/Analytics/index.tsx +6 -5
  169. package/Decorators/ConfigurationWrapper/__tests__/__snapshots__/withConfigurationProvider.test.tsx.snap +1 -0
  170. package/Decorators/ConfigurationWrapper/const.ts +1 -0
  171. package/Decorators/ZappPipesDataConnector/ResolverSelector.tsx +25 -7
  172. package/Decorators/ZappPipesDataConnector/__tests__/ResolverSelector.test.tsx +212 -5
  173. package/Decorators/ZappPipesDataConnector/__tests__/UrlFeedResolver.test.tsx +39 -21
  174. package/Decorators/ZappPipesDataConnector/__tests__/zappPipesDataConnector.test.js +1 -1
  175. package/Decorators/ZappPipesDataConnector/index.tsx +2 -2
  176. package/Decorators/ZappPipesDataConnector/resolvers/StaticFeedResolver.tsx +1 -1
  177. package/Decorators/ZappPipesDataConnector/resolvers/UrlFeedResolver.tsx +18 -7
  178. package/Helpers/DataSourceHelper/__tests__/itemLimitForData.test.ts +80 -0
  179. package/Helpers/DataSourceHelper/index.ts +19 -0
  180. package/events/index.ts +3 -0
  181. package/events/scrollEndReached.ts +15 -0
  182. package/index.d.ts +7 -0
  183. package/package.json +6 -5
  184. package/Components/MasterCell/DefaultComponents/Text/utils/__tests__/withAdjustedLineHeight.test.ts +0 -46
  185. package/Components/MasterCell/DefaultComponents/Text/utils/index.ts +0 -21
  186. package/Components/PlayerContainer/ErrorDisplay/ErrorDisplay.tsx +0 -57
  187. package/Components/PlayerContainer/ErrorDisplay/index.ts +0 -9
  188. package/Components/River/TV/withTVEventHandler.tsx +0 -27
  189. package/Components/VideoModal/ModalAnimation/AnimatedPlayerModalWrapper.tsx +0 -60
  190. package/Components/VideoModal/ModalAnimation/AnimatedScrollModal.tsx +0 -417
  191. package/Components/VideoModal/ModalAnimation/AnimatedScrollModal.web.tsx +0 -294
  192. package/Components/VideoModal/ModalAnimation/AnimatedVideoPlayerComponent.tsx +0 -176
  193. package/Components/VideoModal/ModalAnimation/AnimatedVideoPlayerComponent.web.tsx +0 -93
  194. package/Components/VideoModal/ModalAnimation/AnimationComponent.tsx +0 -500
  195. package/Components/VideoModal/ModalAnimation/__tests__/getMoveUpValue.test.ts +0 -108
  196. package/Helpers/DataSourceHelper/index.js +0 -19
  197. /package/Components/HookRenderer/{index.tsx → index.ts} +0 -0
@@ -16,6 +16,7 @@ import { useNestedNavigationContext } from "@applicaster/zapp-react-native-ui-co
16
16
  import { create } from "zustand";
17
17
  import { subscribeWithSelector } from "zustand/middleware";
18
18
  import { useShallow } from "zustand/react/shallow";
19
+
19
20
  import { Animated } from "react-native";
20
21
 
21
22
  interface NavBarStoreState {
@@ -29,6 +30,8 @@ interface NavBarStoreState {
29
30
  scrollState: number;
30
31
  contentPosition: string;
31
32
  scrollYAnimated: Animated.Value;
33
+ hideOnScrollAnimated: Animated.Value;
34
+ hideOnScroll: boolean;
32
35
  }
33
36
 
34
37
  interface NavBarState {
@@ -62,35 +65,62 @@ const createStateStore = () =>
62
65
  );
63
66
 
64
67
  const createStore = () =>
65
- create<NavBarStoreState>((set) => ({
66
- title: "",
67
- summary: "",
68
- visible: true,
69
- height: 0,
70
- scrollState: 0,
71
- contentPosition: "",
72
- scrollYAnimated: new Animated.Value(0),
73
- setTitle(title) {
74
- set({ title });
75
- },
76
- setSummary(summary) {
77
- set({ summary });
78
- },
79
- setVisible(visible) {
80
- set({ visible });
81
- },
82
- }));
68
+ create(
69
+ subscribeWithSelector<NavBarStoreState>((set) => ({
70
+ title: "",
71
+ summary: "",
72
+ visible: true,
73
+ height: 0,
74
+ scrollState: 0,
75
+ contentPosition: "",
76
+ scrollYAnimated: new Animated.Value(0),
77
+ hideOnScrollAnimated: new Animated.Value(0),
78
+ hideOnScroll: false,
79
+ setTitle(title) {
80
+ set({ title });
81
+ },
82
+ setSummary(summary) {
83
+ set({ summary });
84
+ },
85
+ setVisible(visible) {
86
+ set({ visible });
87
+ },
88
+ }))
89
+ );
90
+
91
+ const createScreenComponentsStore = () =>
92
+ create(subscribeWithSelector<Record<string, unknown>>((_) => ({})));
93
+
94
+ const createFeedStore = () =>
95
+ create(subscribeWithSelector<Record<string, unknown>>((_) => ({})));
83
96
 
84
97
  type ScreenContextType = {
85
98
  _navBarStore: ReturnType<typeof createStore>;
86
99
  _stateStore: ReturnType<typeof createStateStore>;
100
+ _feedStore: ReturnType<typeof createFeedStore>;
87
101
  navBar: NavBarState;
88
102
  legacyFormatScreenData: LegacyNavigationScreenData | null;
103
+ /**
104
+ * Zustand store for component-level state within a screen.
105
+ *
106
+ * **Purpose:** Persists state across component mount/unmount cycles (e.g., during virtualization)
107
+ * and enables state sharing between components using the same key within the same screen.
108
+ *
109
+ * **Lifecycle:** Tied to the screen/route — recreated on each route change.
110
+ *
111
+ * @example
112
+ * // Used by useComponentScreenState hook:
113
+ * const store = useScreenContextV2()._componentStateStore;
114
+ * store.setState({ 'my-key': value });
115
+ * const value = store.getState()['my-key'];
116
+ */
117
+ _componentStateStore: ReturnType<typeof createScreenComponentsStore>;
89
118
  };
90
119
 
91
120
  export const ScreenContext = createContext<ScreenContextType>({
92
121
  _stateStore: createStateStore(),
93
122
  _navBarStore: createStore(),
123
+ _feedStore: createFeedStore(),
94
124
  navBar: {
95
125
  visible: true,
96
126
  title: "",
@@ -100,6 +130,7 @@ export const ScreenContext = createContext<ScreenContextType>({
100
130
  setSummary: (_subtitle) => void 0,
101
131
  },
102
132
  legacyFormatScreenData: {} as LegacyNavigationScreenData,
133
+ _componentStateStore: createScreenComponentsStore(),
103
134
  });
104
135
 
105
136
  export function ScreenContextProvider({
@@ -131,6 +162,10 @@ export function ScreenContextProvider({
131
162
  null
132
163
  );
133
164
 
165
+ const screenFeedStoreRef = useRef<null | ReturnType<typeof createFeedStore>>(
166
+ null
167
+ );
168
+
134
169
  const getScreenState = useCallback(() => {
135
170
  if (screenStateRef.current !== null) {
136
171
  return screenStateRef.current;
@@ -153,6 +188,21 @@ export function ScreenContextProvider({
153
188
  return navBarState;
154
189
  }, []);
155
190
 
191
+ // Assign feed store to ref to persist it across re-renders, but recreate on pathname change
192
+ const screenFeedStore = useMemo(() => createFeedStore(), [pathname]);
193
+
194
+ if (screenFeedStoreRef.current !== screenFeedStore) {
195
+ screenFeedStoreRef.current = screenFeedStore;
196
+ }
197
+
198
+ // Component state store - recreated when pathname changes (route change).
199
+ // Unlike _navBarStore and _stateStore (cached via refs), this store
200
+ // resets only when pathname changes to provide a clean state for the new route.
201
+ const componentStateStore = useMemo(
202
+ () => createScreenComponentsStore(),
203
+ [pathname]
204
+ );
205
+
156
206
  const screenNavBarState = getScreenNavBarState()(
157
207
  useShallow((state) => ({
158
208
  visible: state.visible,
@@ -203,10 +253,12 @@ export function ScreenContextProvider({
203
253
  () => ({
204
254
  _navBarStore: getScreenNavBarState(),
205
255
  _stateStore: getScreenState(),
256
+ _feedStore: screenFeedStoreRef.current,
206
257
  navBar: navBarState,
207
258
  legacyFormatScreenData: routeScreenData,
259
+ _componentStateStore: componentStateStore,
208
260
  }),
209
- [navBarState, screenData, routeScreenData]
261
+ [navBarState, screenData, routeScreenData, componentStateStore]
210
262
  )}
211
263
  >
212
264
  {children}
@@ -1,4 +1,4 @@
1
- import { renderHook } from "@testing-library/react-hooks";
1
+ import { renderHook } from "@testing-library/react-native";
2
2
  import {
3
3
  useScreenTrackedViewPositionsContext,
4
4
  ScreenTrackedViewPositionsContext,
@@ -1,4 +1,4 @@
1
- import React, { useCallback } from "react";
1
+ import { create } from "zustand";
2
2
 
3
3
  type HookModalState = {
4
4
  path: string;
@@ -10,11 +10,12 @@ export type HookModalContextT = {
10
10
  setIsHooksExecutionInProgress: (hookExecutionState?: boolean) => void;
11
11
  isRunningInBackground: boolean;
12
12
  isPresentationFullScreen: boolean;
13
- setIsRunningInBackground: (hookBackgroundProcessState?: boolean) => void;
14
- setIsPresentingFullScreen: (hookBackgroundProcessState?: boolean) => void;
13
+ setIsRunningInBackground: () => void;
14
+ setIsPresentingFullScreen: () => void;
15
15
  state: HookModalState;
16
16
  setState: (state: HookModalState) => void;
17
17
  resetState: () => void;
18
+ hookPresentationMode: HookPresentationMode;
18
19
  };
19
20
 
20
21
  const initialState = {
@@ -24,68 +25,43 @@ const initialState = {
24
25
 
25
26
  type HookPresentationMode = "background" | "fullScreen";
26
27
 
27
- const ReactContext = React.createContext<HookModalContextT>({
28
- isHooksExecutionInProgress: false,
29
- setIsHooksExecutionInProgress: () => {},
30
- isRunningInBackground: null,
31
- setIsRunningInBackground: () => {},
32
- setIsPresentingFullScreen: () => {},
33
- isPresentationFullScreen: null,
28
+ // Use useZappHookModalStore() in React components (hooks)
29
+ // Use zappHookModalStore.getState() in non-React functions (utility functions, etc.)
30
+ export const useZappHookModalStore = create<HookModalContextT>()((set) => ({
34
31
  state: initialState,
35
- setState: () => null,
36
- resetState: () => null,
37
- });
38
-
39
- const Provider = ({ children }: { children: React.ReactNode }) => {
40
- const [state, setState] = React.useState<HookModalState>(initialState);
41
-
42
- const [hookPresentationMode, setHookPresentationMode] =
43
- React.useState<HookPresentationMode>(null);
44
-
45
- const resetState = useCallback(() => {
46
- setState(initialState);
47
- }, [setState]);
48
-
49
- const [isHooksExecutionInProgress, setIsHooksExecutionInProgress] =
50
- React.useState(false);
32
+ isHooksExecutionInProgress: false,
33
+ hookPresentationMode: null as HookPresentationMode,
34
+ isRunningInBackground: false,
35
+ isPresentationFullScreen: false,
51
36
 
52
- const setIsRunningInBackground = () => {
53
- setHookPresentationMode("background");
54
- };
37
+ setState: (newState: HookModalState) => {
38
+ set({ state: newState });
39
+ },
55
40
 
56
- const setIsPresentingFullScreen = () => {
57
- setHookPresentationMode("fullScreen");
58
- };
41
+ setIsHooksExecutionInProgress: (hookExecutionState?: boolean) => {
42
+ set({ isHooksExecutionInProgress: hookExecutionState ?? false });
43
+ },
59
44
 
60
- return (
61
- <ReactContext.Provider
62
- value={{
63
- isRunningInBackground: hookPresentationMode === "background",
64
- setIsRunningInBackground,
65
- setIsPresentingFullScreen,
66
- isPresentationFullScreen: hookPresentationMode === "fullScreen",
67
- isHooksExecutionInProgress,
68
- setIsHooksExecutionInProgress,
69
- state,
70
- setState,
71
- resetState,
72
- }}
73
- >
74
- {children}
75
- </ReactContext.Provider>
76
- );
77
- };
45
+ setIsRunningInBackground: () => {
46
+ set({
47
+ hookPresentationMode: "background",
48
+ isRunningInBackground: true,
49
+ isPresentationFullScreen: false,
50
+ });
51
+ },
78
52
 
79
- const withProvider = (Component) => {
80
- const WithProvider = (props) => {
81
- return (
82
- <Provider>
83
- <Component {...props} />
84
- </Provider>
85
- );
86
- };
53
+ setIsPresentingFullScreen: () => {
54
+ set({
55
+ hookPresentationMode: "fullScreen",
56
+ isRunningInBackground: false,
57
+ isPresentationFullScreen: true,
58
+ });
59
+ },
87
60
 
88
- return WithProvider;
89
- };
61
+ resetState: () => {
62
+ set({ state: initialState });
63
+ },
64
+ }));
90
65
 
91
- export const ZappHookModalContext = { withProvider, ReactContext };
66
+ // Export an alias for clearer non-React usage (utility functions, etc.)
67
+ export const zappHookModalStore = useZappHookModalStore;
@@ -5,10 +5,10 @@ import React, {
5
5
  useMemo,
6
6
  useState,
7
7
  } from "react";
8
- import * as R from "ramda";
9
8
 
10
- type ProviderProps = {
9
+ type ProviderProps<S> = {
11
10
  children: React.ReactChild;
11
+ initialContextValue?: S;
12
12
  };
13
13
 
14
14
  type ContextType<T> = {
@@ -27,21 +27,29 @@ export function createZappPipesContext<T, S = T>(
27
27
  ) {
28
28
  const Context = createContext<ContextType<S>>(initialContext);
29
29
 
30
- const { selector = R.identity, prepareContext = R.identity } = options || {};
30
+ const defaultSelector = (c: any) => c;
31
+ const defaultPrepareContext = (n: any) => n;
32
+ const joinArgs = (args: any[]) => args.join("-");
33
+
34
+ const { selector = defaultSelector, prepareContext = defaultPrepareContext } =
35
+ options || {};
31
36
 
32
37
  function useZappPipesContext(...hookArgs: any[]): [T, (T) => void] {
33
38
  const { context, setContext } = useContext(Context);
39
+ const joinedArgs = joinArgs(hookArgs);
34
40
 
35
41
  const contextValue = useMemo(
36
42
  () => selector(context, ...hookArgs),
37
- [context, R.join("-", hookArgs)]
43
+ // eslint-disable-next-line @wogns3623/better-exhaustive-deps/exhaustive-deps
44
+ [context, joinedArgs]
38
45
  );
39
46
 
40
47
  const contextSetter = useCallback(
41
48
  (newContext: T) => {
42
49
  setContext(prepareContext(newContext, context, ...hookArgs));
43
50
  },
44
- [context, setContext, R.join("-", hookArgs)]
51
+ // eslint-disable-next-line @wogns3623/better-exhaustive-deps/exhaustive-deps
52
+ [context, joinedArgs]
45
53
  );
46
54
 
47
55
  return useMemo(
@@ -50,8 +58,11 @@ export function createZappPipesContext<T, S = T>(
50
58
  );
51
59
  }
52
60
 
53
- function Provider({ children }: ProviderProps) {
54
- const [context, setContext] = useState<S>(initialContext.context);
61
+ /** Provider accepts `initialContextValue` prop to set the initial context value */
62
+ function Provider({ children, initialContextValue }: ProviderProps<S>) {
63
+ const [context, setContext] = useState<S>(
64
+ initialContextValue ?? initialContext.context
65
+ );
55
66
 
56
67
  return (
57
68
  <Context.Provider value={{ context, setContext }}>
package/Contexts/index.ts CHANGED
@@ -12,8 +12,6 @@ export { HorizontalScrollContext } from "./HorizontalScrollContext";
12
12
 
13
13
  export { RiverOffsetContext } from "./RiverOffsetContext";
14
14
 
15
- export { ZappHookModalContext } from "./ZappHookModalContext";
16
-
17
15
  export { MeasurementContext } from "./MeasurementContext";
18
16
 
19
17
  export * from "./ZappPipesContext";
@@ -1,7 +1,6 @@
1
1
  import React from "react";
2
2
 
3
3
  import { getAnalyticsFunctions } from "@applicaster/zapp-react-native-utils/analyticsUtils";
4
- import { usePickFromState } from "@applicaster/zapp-react-native-redux/hooks";
5
4
 
6
5
  type ComponentProps = {
7
6
  component: {
@@ -21,11 +20,13 @@ type ComponentProps = {
21
20
 
22
21
  function withAnalytics(Component) {
23
22
  return function WithAnalytics(props: ComponentProps) {
24
- const { appData } = usePickFromState(["appData"]);
25
-
26
23
  const analyticsFunctions = React.useMemo(
27
- () => getAnalyticsFunctions({ props, appData }),
28
- [props, appData]
24
+ () =>
25
+ getAnalyticsFunctions({
26
+ component: props.component,
27
+ zappPipesData: props.zappPipesData,
28
+ } as any),
29
+ [props.component, props.zappPipesData]
29
30
  );
30
31
 
31
32
  return (
@@ -88,6 +88,7 @@ exports[`withConfigurationProvider correctly passes all the configuration keys c
88
88
  tab_cell_padding_right={10}
89
89
  tab_cell_padding_top={14}
90
90
  tablet_theme={false}
91
+ tabs_screen_background_color="transparent"
91
92
  target={false}
92
93
  target_screen_switch={false}
93
94
  text_label_active_font_color="rgba(239, 239, 239, 0.5)"
@@ -208,4 +208,5 @@ export const keysMap: Record<string, Function> = {
208
208
  tab_bar_item_margin_right: castOrDefaultValue(Number, 0),
209
209
  tab_bar_item_margin_bottom: castOrDefaultValue(Number, 0),
210
210
  tab_bar_item_margin_left: castOrDefaultValue(Number, 0),
211
+ tabs_screen_background_color: castOrDefaultValue(R.identity, "transparent"),
211
212
  };
@@ -1,4 +1,3 @@
1
- // ResolverSelector.tsx
2
1
  import React from "react";
3
2
  import { ComponentDataSourceContext, ZappPipesDataProps } from "./types";
4
3
  import { StaticFeedResolver } from "./resolvers/StaticFeedResolver";
@@ -12,14 +11,33 @@ type ResolverSelectorProps = ComponentDataSourceContext & {
12
11
  export function ResolverSelector(props: ResolverSelectorProps) {
13
12
  const { getStaticComponentFeed, component, feedUrl, children } = props;
14
13
 
15
- // Determine which resolver to use
14
+ const renderDefaultResolver = (
15
+ fallbackChildren: (dataProps: ZappPipesDataProps) => React.ReactNode
16
+ ) => {
17
+ if (feedUrl || component?.data?.source) {
18
+ return <UrlFeedResolver {...props}>{fallbackChildren}</UrlFeedResolver>;
19
+ }
20
+
21
+ return <NullFeedResolver>{fallbackChildren}</NullFeedResolver>;
22
+ };
23
+
16
24
  if (getStaticComponentFeed) {
17
- return <StaticFeedResolver {...props}>{children}</StaticFeedResolver>;
18
- }
25
+ return (
26
+ <StaticFeedResolver {...props}>
27
+ {(zappPipesDataProps) => {
28
+ const { data, loading, error } = zappPipesDataProps.zappPipesData;
29
+
30
+ const shouldFallback = !loading && data === null && !error;
31
+
32
+ if (shouldFallback) {
33
+ return renderDefaultResolver(children);
34
+ }
19
35
 
20
- if (feedUrl || component?.data?.source) {
21
- return <UrlFeedResolver {...props}>{children}</UrlFeedResolver>;
36
+ return children(zappPipesDataProps);
37
+ }}
38
+ </StaticFeedResolver>
39
+ );
22
40
  }
23
41
 
24
- return <NullFeedResolver>{children}</NullFeedResolver>;
42
+ return renderDefaultResolver(children);
25
43
  }
@@ -7,15 +7,30 @@ import { UrlFeedResolver } from "../resolvers/UrlFeedResolver";
7
7
  import { NullFeedResolver } from "../resolvers/NullFeedResolver";
8
8
 
9
9
  jest.mock("../resolvers/StaticFeedResolver", () => ({
10
- StaticFeedResolver: jest.fn(() => null),
10
+ StaticFeedResolver: jest.fn(({ children }) =>
11
+ children({
12
+ zappPipesData: { loading: true, data: null, error: null },
13
+ reloadData: jest.fn(),
14
+ })
15
+ ),
11
16
  }));
12
17
 
13
18
  jest.mock("../resolvers/UrlFeedResolver", () => ({
14
- UrlFeedResolver: jest.fn(() => null),
19
+ UrlFeedResolver: jest.fn(({ children }) =>
20
+ children({
21
+ zappPipesData: { loading: true, data: null, error: null },
22
+ reloadData: jest.fn(),
23
+ })
24
+ ),
15
25
  }));
16
26
 
17
27
  jest.mock("../resolvers/NullFeedResolver", () => ({
18
- NullFeedResolver: jest.fn(() => null),
28
+ NullFeedResolver: jest.fn(({ children }) =>
29
+ children({
30
+ zappPipesData: { loading: false, data: null, error: null },
31
+ reloadData: jest.fn(),
32
+ })
33
+ ),
19
34
  }));
20
35
 
21
36
  const testPlugin = {
@@ -52,7 +67,7 @@ describe("ResolverSelector", () => {
52
67
  expect.objectContaining({
53
68
  getStaticComponentFeed: props.getStaticComponentFeed,
54
69
  component: mockComponent,
55
- children: mockChildren,
70
+ children: expect.any(Function),
56
71
  }),
57
72
  expect.anything()
58
73
  );
@@ -151,7 +166,10 @@ describe("ResolverSelector", () => {
151
166
  render(<ResolverSelector {...props} />);
152
167
 
153
168
  expect(StaticFeedResolver).toHaveBeenCalledWith(
154
- expect.objectContaining(props),
169
+ expect.objectContaining({
170
+ ...props,
171
+ children: expect.any(Function),
172
+ }),
155
173
  expect.anything()
156
174
  );
157
175
  });
@@ -202,4 +220,193 @@ describe("ResolverSelector", () => {
202
220
  expect(UrlFeedResolver).not.toHaveBeenCalled();
203
221
  expect(NullFeedResolver).not.toHaveBeenCalled();
204
222
  });
223
+
224
+ describe("Fallback Logic", () => {
225
+ it("should fallback to UrlFeedResolver when StaticFeedResolver returns null and feedUrl is present", () => {
226
+ const props = {
227
+ getStaticComponentFeed: jest.fn(),
228
+ feedUrl: "https://example.com/feed",
229
+ component: mockComponent,
230
+ children: mockChildren,
231
+ riverId: "test-river",
232
+ };
233
+
234
+ (StaticFeedResolver as jest.Mock).mockImplementation(({ children }) =>
235
+ children({
236
+ zappPipesData: { loading: false, data: null, error: null },
237
+ reloadData: jest.fn(),
238
+ })
239
+ );
240
+
241
+ render(<ResolverSelector {...props} />);
242
+
243
+ expect(StaticFeedResolver).toHaveBeenCalled();
244
+
245
+ expect(UrlFeedResolver).toHaveBeenCalledWith(
246
+ expect.objectContaining({
247
+ feedUrl: props.feedUrl,
248
+ component: mockComponent,
249
+ children: mockChildren,
250
+ }),
251
+ expect.anything()
252
+ );
253
+ });
254
+
255
+ it("should fallback to UrlFeedResolver when StaticFeedResolver returns null and component.data.source is present", () => {
256
+ const componentWithSource = {
257
+ ...mockComponent,
258
+ data: {
259
+ source: "data-source",
260
+ },
261
+ };
262
+
263
+ const props = {
264
+ getStaticComponentFeed: jest.fn(),
265
+ component: componentWithSource,
266
+ children: mockChildren,
267
+ riverId: "test-river",
268
+ };
269
+
270
+ (StaticFeedResolver as jest.Mock).mockImplementation(({ children }) =>
271
+ children({
272
+ zappPipesData: { loading: false, data: null, error: null },
273
+ reloadData: jest.fn(),
274
+ })
275
+ );
276
+
277
+ render(<ResolverSelector {...props} />);
278
+
279
+ expect(StaticFeedResolver).toHaveBeenCalled();
280
+
281
+ expect(UrlFeedResolver).toHaveBeenCalledWith(
282
+ expect.objectContaining({
283
+ component: componentWithSource,
284
+ children: mockChildren,
285
+ }),
286
+ expect.anything()
287
+ );
288
+ });
289
+
290
+ it("should fallback to NullFeedResolver when StaticFeedResolver returns null and no feedUrl/source is present", () => {
291
+ const props = {
292
+ getStaticComponentFeed: jest.fn(),
293
+ component: mockComponent,
294
+ children: mockChildren,
295
+ riverId: "test-river",
296
+ };
297
+
298
+ (StaticFeedResolver as jest.Mock).mockImplementation(({ children }) =>
299
+ children({
300
+ zappPipesData: { loading: false, data: null, error: null },
301
+ reloadData: jest.fn(),
302
+ })
303
+ );
304
+
305
+ render(<ResolverSelector {...props} />);
306
+
307
+ expect(StaticFeedResolver).toHaveBeenCalled();
308
+ expect(UrlFeedResolver).not.toHaveBeenCalled();
309
+
310
+ expect(NullFeedResolver).toHaveBeenCalledWith(
311
+ expect.objectContaining({
312
+ children: mockChildren,
313
+ }),
314
+ expect.anything()
315
+ );
316
+ });
317
+
318
+ it("should NOT fallback and call children with static data when StaticFeedResolver returns data", () => {
319
+ const staticData = { entry: [{ id: "1" }] };
320
+
321
+ const props = {
322
+ getStaticComponentFeed: jest.fn(),
323
+ feedUrl: "https://example.com/feed",
324
+ component: mockComponent,
325
+ children: mockChildren,
326
+ riverId: "test-river",
327
+ };
328
+
329
+ (StaticFeedResolver as jest.Mock).mockImplementation(({ children }) =>
330
+ children({
331
+ zappPipesData: { loading: false, data: staticData, error: null },
332
+ reloadData: jest.fn(),
333
+ })
334
+ );
335
+
336
+ render(<ResolverSelector {...props} />);
337
+
338
+ expect(StaticFeedResolver).toHaveBeenCalled();
339
+ expect(UrlFeedResolver).not.toHaveBeenCalled();
340
+
341
+ expect(mockChildren).toHaveBeenCalledWith(
342
+ expect.objectContaining({
343
+ zappPipesData: expect.objectContaining({
344
+ data: staticData,
345
+ }),
346
+ })
347
+ );
348
+ });
349
+
350
+ it("should NOT fallback and call children with loading state when StaticFeedResolver is loading", () => {
351
+ const props = {
352
+ getStaticComponentFeed: jest.fn(),
353
+ feedUrl: "https://example.com/feed",
354
+ component: mockComponent,
355
+ children: mockChildren,
356
+ riverId: "test-river",
357
+ };
358
+
359
+ (StaticFeedResolver as jest.Mock).mockImplementation(({ children }) =>
360
+ children({
361
+ zappPipesData: { loading: true, data: null, error: null },
362
+ reloadData: jest.fn(),
363
+ })
364
+ );
365
+
366
+ render(<ResolverSelector {...props} />);
367
+
368
+ expect(StaticFeedResolver).toHaveBeenCalled();
369
+ expect(UrlFeedResolver).not.toHaveBeenCalled();
370
+
371
+ expect(mockChildren).toHaveBeenCalledWith(
372
+ expect.objectContaining({
373
+ zappPipesData: expect.objectContaining({
374
+ loading: true,
375
+ }),
376
+ })
377
+ );
378
+ });
379
+
380
+ it("should NOT fallback and call children with error when StaticFeedResolver returns error", () => {
381
+ const error = new Error("Static error");
382
+
383
+ const props = {
384
+ getStaticComponentFeed: jest.fn(),
385
+ feedUrl: "https://example.com/feed",
386
+ component: mockComponent,
387
+ children: mockChildren,
388
+ riverId: "test-river",
389
+ };
390
+
391
+ (StaticFeedResolver as jest.Mock).mockImplementation(({ children }) =>
392
+ children({
393
+ zappPipesData: { loading: false, data: null, error },
394
+ reloadData: jest.fn(),
395
+ })
396
+ );
397
+
398
+ render(<ResolverSelector {...props} />);
399
+
400
+ expect(StaticFeedResolver).toHaveBeenCalled();
401
+ expect(UrlFeedResolver).not.toHaveBeenCalled();
402
+
403
+ expect(mockChildren).toHaveBeenCalledWith(
404
+ expect.objectContaining({
405
+ zappPipesData: expect.objectContaining({
406
+ error,
407
+ }),
408
+ })
409
+ );
410
+ });
411
+ });
205
412
  });