@applicaster/zapp-react-native-ui-components 15.0.0-alpha.8680244503 → 15.0.0-alpha.9102777840

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 (68) hide show
  1. package/Components/BaseFocusable/index.ios.ts +12 -2
  2. package/Components/Cell/Cell.tsx +6 -0
  3. package/Components/Cell/CellWithFocusable.tsx +9 -0
  4. package/Components/Cell/FocusableWrapper.tsx +3 -0
  5. package/Components/Cell/TvOSCellComponent.tsx +5 -0
  6. package/Components/Focusable/Focusable.tsx +4 -2
  7. package/Components/Focusable/FocusableTvOS.tsx +18 -1
  8. package/Components/Focusable/__tests__/__snapshots__/FocusableTvOS.test.tsx.snap +1 -0
  9. package/Components/FocusableGroup/FocusableTvOS.tsx +32 -1
  10. package/Components/GeneralContentScreen/GeneralContentScreen.tsx +3 -8
  11. package/Components/GeneralContentScreen/utils/__tests__/useCurationAPI.test.js +1 -1
  12. package/Components/GeneralContentScreen/utils/useCurationAPI.ts +22 -6
  13. package/Components/HandlePlayable/HandlePlayable.tsx +16 -29
  14. package/Components/HandlePlayable/utils.ts +31 -0
  15. package/Components/MasterCell/DefaultComponents/SecondaryImage/Image.tsx +40 -39
  16. package/Components/MasterCell/DefaultComponents/SecondaryImage/__tests__/Image.test.tsx +95 -0
  17. package/Components/MasterCell/DefaultComponents/SecondaryImage/__tests__/__snapshots__/Image.test.tsx.snap +86 -0
  18. package/Components/MasterCell/DefaultComponents/SecondaryImage/__tests__/index.test.ts +141 -0
  19. package/Components/MasterCell/DefaultComponents/SecondaryImage/hooks/__tests__/useGetImageDimensions.test.ts +7 -6
  20. package/Components/MasterCell/DefaultComponents/SecondaryImage/index.ts +1 -1
  21. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/index.ts +6 -2
  22. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/utils/__tests__/getPluginIdentifier.test.ts +233 -11
  23. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/utils/index.ts +19 -15
  24. package/Components/MasterCell/hoc/__tests__/withAsyncRender.test.tsx +219 -0
  25. package/Components/MasterCell/hoc/withAsyncRender.tsx +9 -7
  26. package/Components/OfflineHandler/NotificationView/NotificationView.lg.tsx +17 -9
  27. package/Components/OfflineHandler/NotificationView/NotificationView.samsung.tsx +16 -8
  28. package/Components/OfflineHandler/NotificationView/utils.ts +34 -0
  29. package/Components/PlayerContainer/PlayerContainer.tsx +40 -47
  30. package/Components/River/ComponentsMap/ComponentsMap.tsx +16 -0
  31. package/Components/River/ComponentsMap/hooks/__tests__/useLoadingState.test.ts +1 -1
  32. package/Components/River/RefreshControl.tsx +9 -3
  33. package/Components/River/TV/River.tsx +31 -14
  34. package/Components/River/TV/index.tsx +8 -4
  35. package/Components/River/TV/utils/__tests__/toStringOrEmpty.test.ts +30 -0
  36. package/Components/River/TV/utils/index.ts +4 -0
  37. package/Components/River/TV/withFocusableGroupForContent.tsx +71 -0
  38. package/Components/River/__tests__/__snapshots__/componentsMap.test.js.snap +2 -0
  39. package/Components/River/__tests__/componentsMap.test.js +38 -0
  40. package/Components/Screen/__tests__/Screen.test.tsx +1 -0
  41. package/Components/Screen/hooks.ts +73 -3
  42. package/Components/Screen/index.tsx +7 -1
  43. package/Components/Screen/orientationHandler.ts +7 -10
  44. package/Components/ScreenResolver/index.tsx +14 -10
  45. package/Components/Tabs/TabContent.tsx +7 -4
  46. package/Components/Transitioner/Scene.tsx +9 -15
  47. package/Components/VideoModal/hooks/__tests__/useDelayedPlayerDetails.test.ts +15 -7
  48. package/Components/Viewport/ViewportAware/__tests__/viewportAware.test.js +0 -2
  49. package/Components/Viewport/ViewportAware/index.tsx +16 -7
  50. package/Components/Viewport/ViewportEvents/__tests__/viewportEvents.test.js +1 -1
  51. package/Contexts/ScreenContext/index.tsx +54 -19
  52. package/Contexts/ScreenTrackedViewPositionsContext/__tests__/index.test.tsx +1 -1
  53. package/Contexts/ZappHookModalContext/index.tsx +37 -61
  54. package/Contexts/ZappPipesContext/ZappPipesContextFactory.tsx +18 -7
  55. package/Contexts/index.ts +0 -2
  56. package/Decorators/ConfigurationWrapper/__tests__/__snapshots__/withConfigurationProvider.test.tsx.snap +1 -0
  57. package/Decorators/ConfigurationWrapper/const.ts +1 -0
  58. package/Decorators/ZappPipesDataConnector/ResolverSelector.tsx +25 -7
  59. package/Decorators/ZappPipesDataConnector/__tests__/ResolverSelector.test.tsx +212 -5
  60. package/Decorators/ZappPipesDataConnector/__tests__/UrlFeedResolver.test.tsx +39 -21
  61. package/Decorators/ZappPipesDataConnector/__tests__/zappPipesDataConnector.test.js +1 -1
  62. package/Decorators/ZappPipesDataConnector/resolvers/UrlFeedResolver.tsx +18 -7
  63. package/events/index.ts +3 -0
  64. package/events/scrollEndReached.ts +15 -0
  65. package/package.json +5 -5
  66. package/Components/PlayerContainer/ErrorDisplay/ErrorDisplay.tsx +0 -57
  67. package/Components/PlayerContainer/ErrorDisplay/index.ts +0 -9
  68. package/Components/River/TV/withTVEventHandler.tsx +0 -27
@@ -1,18 +1,16 @@
1
- import React, { useLayoutEffect } from "react";
1
+ import { useLayoutEffect } from "react";
2
2
 
3
3
  import {
4
4
  allowedOrientationsForScreen,
5
- ORIENTATIONS,
6
5
  getOrientation,
6
+ ORIENTATIONS,
7
7
  useGetScreenOrientation,
8
8
  } from "@applicaster/zapp-react-native-utils/appUtils/orientationHelper";
9
9
  import { usePrevious } from "@applicaster/zapp-react-native-utils/reactHooks/utils";
10
10
  import { usePlugins, useAppData } from "@applicaster/zapp-react-native-redux";
11
11
  import { findPluginByType } from "@applicaster/zapp-react-native-utils/pluginUtils";
12
12
  import { useIsTablet } from "@applicaster/zapp-react-native-utils/reactHooks";
13
-
14
- import { ZappHookModalContext } from "../../Contexts";
15
- import { HookModalContextT } from "../../Contexts/ZappHookModalContext";
13
+ import { zappHookModalStore } from "../../Contexts/ZappHookModalContext";
16
14
 
17
15
  /**
18
16
  * This function calls the native module needed to set orientation
@@ -89,10 +87,6 @@ type Props = {
89
87
  };
90
88
 
91
89
  export function useScreenOrientationHandler({ screenData, isActive }: Props) {
92
- const { isHooksExecutionInProgress } = React.useContext<HookModalContextT>(
93
- ZappHookModalContext.ReactContext
94
- );
95
-
96
90
  const prevIsActive = usePrevious(isActive);
97
91
 
98
92
  const newOrientation = useNewOrientationForScreenData({
@@ -105,6 +99,9 @@ export function useScreenOrientationHandler({ screenData, isActive }: Props) {
105
99
  return;
106
100
  }
107
101
 
102
+ // TODO: make sure it can be static getter and subscription is not needed
103
+ const { isHooksExecutionInProgress } = zappHookModalStore.getState();
104
+
108
105
  // If modal hook presented we need to skip
109
106
  // Change orientation for presenter screen
110
107
  if (isHooksExecutionInProgress) {
@@ -116,7 +113,7 @@ export function useScreenOrientationHandler({ screenData, isActive }: Props) {
116
113
 
117
114
  setOrientation(newOrientation);
118
115
  }
119
- }, [newOrientation, isHooksExecutionInProgress, prevIsActive, isActive]);
116
+ }, [newOrientation, prevIsActive, isActive]);
120
117
  }
121
118
 
122
119
  export function useOrientationHandler({ screenData }: OrientationHookArgs) {
@@ -57,7 +57,6 @@ export function ScreenResolverComponent(props: Props) {
57
57
  const { hookPlugin } = screenData || {};
58
58
 
59
59
  const plugins = usePlugins();
60
- const rivers = useRivers();
61
60
 
62
61
  const components = useAppSelector(selectComponents);
63
62
 
@@ -65,12 +64,6 @@ export function ScreenResolverComponent(props: Props) {
65
64
  videoModalState: { mode },
66
65
  } = useNavigation();
67
66
 
68
- const [, setScreenContext] = ZappPipesScreenContext.useZappPipesContext();
69
-
70
- React.useEffect(() => {
71
- setScreenContext(rivers[screenId]);
72
- }, [rivers, screenId, setScreenContext]);
73
-
74
67
  const parentCallback = props.resultCallback;
75
68
 
76
69
  const screenAction = useCallbackActions(
@@ -150,6 +143,17 @@ export function ScreenResolverComponent(props: Props) {
150
143
  return null;
151
144
  }
152
145
 
153
- export const ScreenResolver = ZappPipesScreenContext.withProvider(
154
- ScreenResolverComponent
155
- );
146
+ function withDefaultScreenContext(Component: React.ComponentType<any>) {
147
+ return function WithDefaultScreenContext(props: any) {
148
+ const screenId = props.screenId;
149
+ const rivers = useRivers();
150
+
151
+ return (
152
+ <ZappPipesScreenContext.Provider initialContextValue={rivers[screenId]}>
153
+ <Component {...props} />
154
+ </ZappPipesScreenContext.Provider>
155
+ );
156
+ };
157
+ }
158
+
159
+ export const ScreenResolver = withDefaultScreenContext(ScreenResolverComponent);
@@ -1,11 +1,10 @@
1
1
  import React from "react";
2
- import { View, ViewStyle } from "react-native";
2
+ import { View, StyleSheet } from "react-native";
3
3
 
4
4
  import { River } from "@applicaster/zapp-react-native-ui-components/Components";
5
5
  import { withNestedNavigationContextProvider } from "@applicaster/zapp-react-native-ui-components/Contexts/NestedNavigationContext";
6
6
 
7
7
  type Props = {
8
- styles: Record<string, ViewStyle>;
9
8
  minHeight: number;
10
9
  changingTab: boolean;
11
10
  feedUrl: string;
@@ -14,9 +13,14 @@ type Props = {
14
13
  backgroundColor: string;
15
14
  };
16
15
 
16
+ const styles = StyleSheet.create({
17
+ riverWrapper: {
18
+ flex: 1,
19
+ },
20
+ });
21
+
17
22
  function TabContentComponent(props: Props) {
18
23
  const {
19
- styles,
20
24
  minHeight,
21
25
  backgroundColor,
22
26
  changingTab,
@@ -29,7 +33,6 @@ function TabContentComponent(props: Props) {
29
33
  <View
30
34
  style={[
31
35
  styles.riverWrapper,
32
-
33
36
  {
34
37
  backgroundColor,
35
38
  minHeight,
@@ -1,9 +1,9 @@
1
- import React, { useEffect } from "react";
1
+ import React from "react";
2
2
  import { equals } from "ramda";
3
3
  import { Animated, ViewProps, ViewStyle } from "react-native";
4
- import { useSafeAreaFrame } from "react-native-safe-area-context";
5
4
 
6
5
  import { useScreenOrientationHandler } from "@applicaster/zapp-react-native-ui-components/Components/Screen/orientationHandler";
6
+ import { useMemoizedSafeAreaFrameWithActiveState } from "@applicaster/zapp-react-native-ui-components/Components/Screen/hooks";
7
7
 
8
8
  import { PathnameContext } from "../../Contexts/PathnameContext";
9
9
  import { ScreenDataContext } from "../../Contexts/ScreenDataContext";
@@ -94,19 +94,13 @@ function SceneComponent({
94
94
  isActive,
95
95
  });
96
96
 
97
- const frame = useSafeAreaFrame();
98
-
99
- const [memoFrame, setMemoFrame] = React.useState(frame);
100
-
101
- useEffect(() => {
102
- if (isActive) {
103
- setMemoFrame((oldFrame) =>
104
- oldFrame.width === frame.width && oldFrame.height === frame.height
105
- ? oldFrame
106
- : frame
107
- );
108
- }
109
- }, [isActive, frame.width, frame.height]);
97
+ // Use shared memoized frame hook - synchronized with useWaitForValidOrientation
98
+ // to prevent race conditions during orientation changes
99
+ // Pass isActive from props since Scene knows its active state from Transitioner
100
+ const memoFrame = useMemoizedSafeAreaFrameWithActiveState({
101
+ updateForInactiveScreens: false,
102
+ isActive,
103
+ });
110
104
 
111
105
  const isAnimating = animating && overlayStyle;
112
106
 
@@ -1,4 +1,4 @@
1
- import { renderHook } from "@testing-library/react-hooks";
1
+ import { act, renderHook, waitFor } from "@testing-library/react-native";
2
2
  import { useDelayedPlayerDetails } from "../useDelayedPlayerDetails";
3
3
  import { useIsTablet } from "@applicaster/zapp-react-native-utils/reactHooks";
4
4
 
@@ -13,16 +13,20 @@ describe("useDelayedPlayerDetails", () => {
13
13
  jest.clearAllMocks();
14
14
  });
15
15
 
16
- it("should return false initially, that changes after 1 second", () => {
16
+ it("should return false initially, that changes after 1 second", async () => {
17
17
  const { result } = renderHook(() =>
18
18
  useDelayedPlayerDetails({ isInline: true, isDocked: false, isPip: false })
19
19
  );
20
20
 
21
21
  expect(result.current).toBe(false);
22
22
 
23
- jest.advanceTimersByTime(1000);
23
+ act(() => {
24
+ jest.advanceTimersByTime(1000);
25
+ });
24
26
 
25
- expect(result.current).toBe(true);
27
+ await waitFor(() => {
28
+ expect(result.current).toBe(true);
29
+ });
26
30
  });
27
31
 
28
32
  it("should return false when isPip is true", () => {
@@ -41,7 +45,7 @@ describe("useDelayedPlayerDetails", () => {
41
45
  expect(result.current).toBe(false);
42
46
  });
43
47
 
44
- it("should return true for tablet regardless of other flags", () => {
48
+ it("should return true for tablet regardless of other flags", async () => {
45
49
  (useIsTablet as jest.Mock).mockReturnValue(true);
46
50
 
47
51
  const { result } = renderHook(() =>
@@ -52,8 +56,12 @@ describe("useDelayedPlayerDetails", () => {
52
56
  })
53
57
  );
54
58
 
55
- jest.advanceTimersByTime(1000);
59
+ act(() => {
60
+ jest.advanceTimersByTime(1000);
61
+ });
56
62
 
57
- expect(result.current).toBe(true);
63
+ await waitFor(() => {
64
+ expect(result.current).toBe(true);
65
+ });
58
66
  });
59
67
  });
@@ -22,8 +22,6 @@ ReactNative.UIManager.measureLayout = jest.fn(
22
22
  }
23
23
  );
24
24
 
25
- ReactNative.findNodeHandle = () => 1234;
26
-
27
25
  const viewportEventsManager = new ViewportEvents(true);
28
26
 
29
27
  const TestComponent = (props) => {
@@ -32,7 +32,7 @@ function getTestDimensions(testDimensions) {
32
32
  return (process.env.NODE_ENV === "test" && testDimensions) || null;
33
33
  }
34
34
 
35
- function ViewportAwareComponent(props: Props, ref) {
35
+ function ViewportAwareComponent(props: Props, forwardedRef) {
36
36
  const viewportEvents = useViewportEventsContext();
37
37
 
38
38
  const {
@@ -47,13 +47,22 @@ function ViewportAwareComponent(props: Props, ref) {
47
47
  getTestDimensions(testDimensions) || initialDimensions
48
48
  );
49
49
 
50
+ const localRef = React.useRef(null);
51
+
50
52
  const [viewportChangeEvent, setViewportChangeEvent] = React.useState(null);
51
53
 
52
- function assignRef(_ref) {
53
- if (_ref && !ref) {
54
- ref = _ref;
55
- }
56
- }
54
+ const assignRef = React.useCallback(
55
+ (_ref) => {
56
+ localRef.current = _ref;
57
+
58
+ if (typeof forwardedRef === "function") {
59
+ forwardedRef(_ref);
60
+ } else if (forwardedRef) {
61
+ forwardedRef.current = _ref;
62
+ }
63
+ },
64
+ [forwardedRef]
65
+ );
57
66
 
58
67
  const checkInViewport = (viewportChangeEvent, layoutEvent) => {
59
68
  const inVerticalViewport = Utils.isInViewport(
@@ -94,7 +103,7 @@ function ViewportAwareComponent(props: Props, ref) {
94
103
  const onViewportChange = (viewportChangeEvent) => {
95
104
  setViewportChangeEvent(viewportChangeEvent);
96
105
 
97
- const nodeHandle = findNodeHandle(ref);
106
+ const nodeHandle = findNodeHandle(localRef.current);
98
107
 
99
108
  if (!nodeHandle) {
100
109
  return;
@@ -1,5 +1,5 @@
1
1
  import * as React from "react";
2
- import { renderHook } from "@testing-library/react-hooks";
2
+ import { renderHook } from "@testing-library/react-native";
3
3
  import {
4
4
  ViewportEvents,
5
5
  useViewportEventsContext,
@@ -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,30 +65,52 @@ 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>>((_) => ({})));
83
93
 
84
94
  type ScreenContextType = {
85
95
  _navBarStore: ReturnType<typeof createStore>;
86
96
  _stateStore: ReturnType<typeof createStateStore>;
87
97
  navBar: NavBarState;
88
98
  legacyFormatScreenData: LegacyNavigationScreenData | null;
99
+ /**
100
+ * Zustand store for component-level state within a screen.
101
+ *
102
+ * **Purpose:** Persists state across component mount/unmount cycles (e.g., during virtualization)
103
+ * and enables state sharing between components using the same key within the same screen.
104
+ *
105
+ * **Lifecycle:** Tied to the screen/route — recreated on each route change.
106
+ *
107
+ * @example
108
+ * // Used by useComponentScreenState hook:
109
+ * const store = useScreenContextV2()._componentStateStore;
110
+ * store.setState({ 'my-key': value });
111
+ * const value = store.getState()['my-key'];
112
+ */
113
+ _componentStateStore: ReturnType<typeof createScreenComponentsStore>;
89
114
  };
90
115
 
91
116
  export const ScreenContext = createContext<ScreenContextType>({
@@ -100,6 +125,7 @@ export const ScreenContext = createContext<ScreenContextType>({
100
125
  setSummary: (_subtitle) => void 0,
101
126
  },
102
127
  legacyFormatScreenData: {} as LegacyNavigationScreenData,
128
+ _componentStateStore: createScreenComponentsStore(),
103
129
  });
104
130
 
105
131
  export function ScreenContextProvider({
@@ -153,6 +179,14 @@ export function ScreenContextProvider({
153
179
  return navBarState;
154
180
  }, []);
155
181
 
182
+ // Component state store - recreated when pathname changes (route change).
183
+ // Unlike _navBarStore and _stateStore (cached via refs), this store
184
+ // resets only when pathname changes to provide a clean state for the new route.
185
+ const componentStateStore = useMemo(
186
+ () => createScreenComponentsStore(),
187
+ [pathname]
188
+ );
189
+
156
190
  const screenNavBarState = getScreenNavBarState()(
157
191
  useShallow((state) => ({
158
192
  visible: state.visible,
@@ -205,8 +239,9 @@ export function ScreenContextProvider({
205
239
  _stateStore: getScreenState(),
206
240
  navBar: navBarState,
207
241
  legacyFormatScreenData: routeScreenData,
242
+ _componentStateStore: componentStateStore,
208
243
  }),
209
- [navBarState, screenData, routeScreenData]
244
+ [navBarState, screenData, routeScreenData, componentStateStore]
210
245
  )}
211
246
  >
212
247
  {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";
@@ -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
  }