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

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 (53) 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/utils/__tests__/useCurationAPI.test.js +1 -1
  11. package/Components/GeneralContentScreen/utils/useCurationAPI.ts +22 -6
  12. package/Components/HandlePlayable/utils.ts +31 -0
  13. package/Components/MasterCell/DefaultComponents/SecondaryImage/Image.tsx +40 -39
  14. package/Components/MasterCell/DefaultComponents/SecondaryImage/__tests__/Image.test.tsx +95 -0
  15. package/Components/MasterCell/DefaultComponents/SecondaryImage/__tests__/__snapshots__/Image.test.tsx.snap +86 -0
  16. package/Components/MasterCell/DefaultComponents/SecondaryImage/__tests__/index.test.ts +141 -0
  17. package/Components/MasterCell/DefaultComponents/SecondaryImage/hooks/__tests__/useGetImageDimensions.test.ts +7 -6
  18. package/Components/MasterCell/DefaultComponents/SecondaryImage/index.ts +1 -1
  19. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/index.ts +6 -2
  20. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/utils/__tests__/getPluginIdentifier.test.ts +233 -11
  21. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/utils/index.ts +19 -15
  22. package/Components/MasterCell/hoc/__tests__/withAsyncRender.test.tsx +219 -0
  23. package/Components/MasterCell/hoc/withAsyncRender.tsx +9 -7
  24. package/Components/OfflineHandler/NotificationView/NotificationView.lg.tsx +17 -9
  25. package/Components/OfflineHandler/NotificationView/NotificationView.samsung.tsx +16 -8
  26. package/Components/OfflineHandler/NotificationView/utils.ts +34 -0
  27. package/Components/PlayerContainer/PlayerContainer.tsx +47 -38
  28. package/Components/PlayerContainer/useRestrictMobilePlayback.tsx +131 -0
  29. package/Components/River/ComponentsMap/ComponentsMap.tsx +16 -0
  30. package/Components/River/ComponentsMap/hooks/__tests__/useLoadingState.test.ts +1 -1
  31. package/Components/River/TV/River.tsx +31 -14
  32. package/Components/River/TV/index.tsx +8 -4
  33. package/Components/River/TV/utils/__tests__/toStringOrEmpty.test.ts +30 -0
  34. package/Components/River/TV/utils/index.ts +4 -0
  35. package/Components/River/TV/withFocusableGroupForContent.tsx +71 -0
  36. package/Components/River/__tests__/__snapshots__/componentsMap.test.js.snap +2 -0
  37. package/Components/River/__tests__/componentsMap.test.js +38 -0
  38. package/Components/Screen/orientationHandler.ts +7 -10
  39. package/Components/Tabs/TabContent.tsx +7 -4
  40. package/Components/VideoModal/hooks/__tests__/useDelayedPlayerDetails.test.ts +15 -7
  41. package/Components/Viewport/ViewportAware/index.tsx +16 -7
  42. package/Components/Viewport/ViewportEvents/__tests__/viewportEvents.test.js +1 -1
  43. package/Contexts/ScreenContext/index.tsx +25 -18
  44. package/Contexts/ScreenTrackedViewPositionsContext/__tests__/index.test.tsx +1 -1
  45. package/Contexts/ZappHookModalContext/index.tsx +37 -61
  46. package/Contexts/index.ts +0 -2
  47. package/Decorators/ConfigurationWrapper/__tests__/__snapshots__/withConfigurationProvider.test.tsx.snap +1 -0
  48. package/Decorators/ConfigurationWrapper/const.ts +1 -0
  49. package/Decorators/ZappPipesDataConnector/__tests__/zappPipesDataConnector.test.js +1 -1
  50. package/events/index.ts +3 -0
  51. package/events/scrollEndReached.ts +15 -0
  52. package/package.json +5 -5
  53. package/Components/River/TV/withTVEventHandler.tsx +0 -27
@@ -2,7 +2,8 @@
2
2
 
3
3
  import * as React from "react";
4
4
  import { Text } from "react-native";
5
- import * as R from "ramda";
5
+
6
+ import { mergeRight } from "@applicaster/zapp-react-native-utils/utils";
6
7
 
7
8
  import { GeneralContentScreen } from "../../GeneralContentScreen";
8
9
  import { ScreenResolver } from "@applicaster/zapp-react-native-ui-components/Components/ScreenResolver";
@@ -13,6 +14,8 @@ import {
13
14
  } from "@applicaster/zapp-react-native-utils/reactHooks/screen/useScreenContext";
14
15
  import { useRivers } from "@applicaster/zapp-react-native-utils/reactHooks/state";
15
16
 
17
+ import { toStringOrEmpty } from "./utils";
18
+
16
19
  type Props = {
17
20
  screenId: string;
18
21
  screenData: ZappRiver | ZappEntry;
@@ -24,6 +27,7 @@ type Props = {
24
27
  isInsideContainer?: boolean;
25
28
  extraAnchorPointYOffset: number;
26
29
  river?: ZappRiver | ZappEntry;
30
+ groupId: string;
27
31
  };
28
32
 
29
33
  export const River = (props: Props) => {
@@ -35,6 +39,7 @@ export const River = (props: Props) => {
35
39
  componentsMapExtraProps,
36
40
  isInsideContainer,
37
41
  extraAnchorPointYOffset,
42
+ groupId,
38
43
  } = props;
39
44
 
40
45
  const { title: screenTitle, summary: screenSummary } = useNavbarState();
@@ -51,28 +56,41 @@ export const River = (props: Props) => {
51
56
  [screenId]
52
57
  );
53
58
 
54
- const stringOrEmpty = (value: string | number | undefined): string =>
55
- R.isNil(value) ? "" : String(value);
59
+ const screenResolverData = React.useMemo(() => {
60
+ const extraData = mergeRight(extraProps, screenResolverExtraProps);
61
+
62
+ return {
63
+ extraData,
64
+ screenData: mergeRight(river, { groupId: extraData?.groupId }),
65
+ componentsMapExtraProps: mergeRight(componentsMapExtraProps, { groupId }),
66
+ };
67
+ }, [
68
+ extraProps,
69
+ screenResolverExtraProps,
70
+ river,
71
+ componentsMapExtraProps,
72
+ groupId,
73
+ ]);
56
74
 
57
75
  React.useEffect(() => {
58
76
  if (!isInsideContainer) {
59
- setScreenTitle(stringOrEmpty(screenData?.title));
60
- setScreenSummary(stringOrEmpty(screenData?.summary));
77
+ setScreenTitle(toStringOrEmpty(screenData?.title));
78
+ setScreenSummary(toStringOrEmpty(screenData?.summary));
61
79
  }
62
80
  }, [screenData.id]);
63
81
 
64
82
  React.useEffect(() => {
65
83
  if (feedData && !isInsideContainer) {
66
84
  if (feedData.title && feedData.title !== screenTitle) {
67
- setScreenTitle(stringOrEmpty(feedData.title));
85
+ setScreenTitle(toStringOrEmpty(feedData.title));
68
86
  }
69
87
 
70
88
  if (feedData.summary && feedData.summary !== screenSummary) {
71
- setScreenSummary(stringOrEmpty(feedData.summary));
89
+ setScreenSummary(toStringOrEmpty(feedData.summary));
72
90
  }
73
91
  } else {
74
- setScreenTitle(stringOrEmpty(screenData?.title));
75
- setScreenSummary(stringOrEmpty(screenData?.summary));
92
+ setScreenTitle(toStringOrEmpty(screenData?.title));
93
+ setScreenSummary(toStringOrEmpty(screenData?.summary));
76
94
  }
77
95
  }, [feedData, screenData, screenTitle, screenSummary]);
78
96
 
@@ -86,15 +104,13 @@ export const River = (props: Props) => {
86
104
  }
87
105
 
88
106
  if (river.type !== "general_content") {
89
- const extraData = { ...R.mergeRight(extraProps, screenResolverExtraProps) };
90
-
91
107
  return (
92
108
  <ScreenResolver
93
109
  screenType={river.type}
94
110
  screenId={screenId}
95
- screenData={R.merge(river, { groupId: extraData?.groupId })}
96
- componentsMapExtraProps={componentsMapExtraProps}
97
- {...extraData}
111
+ screenData={screenResolverData.screenData}
112
+ componentsMapExtraProps={screenResolverData.componentsMapExtraProps}
113
+ {...screenResolverData.extraData}
98
114
  />
99
115
  );
100
116
  }
@@ -106,6 +122,7 @@ export const River = (props: Props) => {
106
122
  isScreenWrappedInContainer={isInsideContainer}
107
123
  extraAnchorPointYOffset={extraAnchorPointYOffset}
108
124
  componentsMapExtraProps={componentsMapExtraProps}
125
+ groupId={groupId}
109
126
  />
110
127
  );
111
128
  };
@@ -1,11 +1,15 @@
1
- import { compose } from "ramda";
1
+ import { compose, identity } from "@applicaster/zapp-react-native-utils/utils";
2
+ import { isTvOSPlatform } from "@applicaster/zapp-react-native-utils/reactUtils";
3
+
2
4
  import { River as RiverComponent } from "./River";
3
- import { withTvEventHandler } from "./withTVEventHandler";
4
5
  import { withComponentsMapOffsetContext } from "../../../Contexts/ComponentsMapOffsetContext";
5
6
  import { withRiverDataLoader } from "./withRiverDataLoader";
7
+ import { withFocusableGroupForContent } from "./withFocusableGroupForContent";
8
+
9
+ const isTVOS = isTvOSPlatform();
6
10
 
7
11
  export const River = compose(
8
- withTvEventHandler,
9
12
  withComponentsMapOffsetContext,
10
- withRiverDataLoader
13
+ withRiverDataLoader,
14
+ isTVOS ? withFocusableGroupForContent : identity
11
15
  )(RiverComponent);
@@ -0,0 +1,30 @@
1
+ import { toStringOrEmpty } from "..";
2
+
3
+ describe("toStringOrEmpty", () => {
4
+ test("returns empty string for undefined", () => {
5
+ expect(toStringOrEmpty(undefined)).toBe("");
6
+ });
7
+
8
+ test("returns empty string for null", () => {
9
+ expect(toStringOrEmpty(null)).toBe("");
10
+ });
11
+
12
+ test("converts number to string", () => {
13
+ expect(toStringOrEmpty(0)).toBe("0");
14
+ expect(toStringOrEmpty(123)).toBe("123");
15
+ expect(toStringOrEmpty(-42)).toBe("-42");
16
+ });
17
+
18
+ test("returns string as is", () => {
19
+ expect(toStringOrEmpty("hello")).toBe("hello");
20
+ expect(toStringOrEmpty("")).toBe("");
21
+ });
22
+
23
+ test("works with numeric strings", () => {
24
+ expect(toStringOrEmpty("123")).toBe("123");
25
+ });
26
+
27
+ test("does not throw on falsy values like 0", () => {
28
+ expect(toStringOrEmpty(0)).toBe("0");
29
+ });
30
+ });
@@ -0,0 +1,4 @@
1
+ import { isNil } from "@applicaster/zapp-react-native-utils/utils";
2
+
3
+ export const toStringOrEmpty = (value: unknown): string =>
4
+ isNil(value) ? "" : String(value);
@@ -0,0 +1,71 @@
1
+ import * as React from "react";
2
+ import { View, StyleSheet } from "react-native";
3
+
4
+ import { FocusableGroup } from "@applicaster/zapp-react-native-ui-components/Components/FocusableGroup";
5
+ import { riverFocusManager } from "@applicaster/zapp-react-native-utils/appUtils/RiverFocusManager";
6
+
7
+ import { topMenuLayoutChange$ } from "@applicaster/zapp-react-native-tvos-app/Layout/topMenu";
8
+
9
+ const styles = StyleSheet.create({
10
+ flexOne: {
11
+ flex: 1,
12
+ },
13
+ });
14
+
15
+ export const withFocusableGroupForContent = (Component) => {
16
+ return function WithFocusableGroupForContent(props) {
17
+ const { screenId, isInsideContainer } = props;
18
+
19
+ const [topMenuHeight, setTopMenuHeight] = React.useState(0);
20
+
21
+ React.useEffect(() => {
22
+ const subscription = topMenuLayoutChange$.subscribe((layout) => {
23
+ setTopMenuHeight(layout.height);
24
+ });
25
+
26
+ return () => {
27
+ subscription.unsubscribe();
28
+ };
29
+ }, []);
30
+
31
+ const focusableId = React.useMemo(
32
+ () =>
33
+ riverFocusManager.screenFocusableGroupId({
34
+ screenId,
35
+ isInsideContainer,
36
+ }),
37
+ [screenId, isInsideContainer]
38
+ );
39
+
40
+ if (isInsideContainer) {
41
+ return <Component {...props} />;
42
+ }
43
+
44
+ return (
45
+ <FocusableGroup
46
+ key={focusableId}
47
+ id={focusableId}
48
+ // The top menu is rendered in its own FocusableGroup, anchored at the top of the screen.
49
+ // When the "content" FocusableGroup starts at y = 0 as well, the two groups visually overlap.
50
+ // On TvOS platform this overlap can confuse the focus engine, because the focusable bounds of
51
+ // the top-menu group and the content group intersect, leading to erratic navigation between
52
+ // the menu and the content (e.g. unexpected jumps or focus getting "stuck").
53
+ //
54
+ // To avoid this, we shift the entire content FocusableGroup down by the dynamic top menu
55
+ // height (marginTop: topMenuHeight). This separates the focus regions of the two groups in
56
+ // focus space, so they no longer intersect.
57
+ //
58
+ // The inner <View> below then applies the inverse margin (marginTop: -topMenuHeight) so that
59
+ // the actual visual position of the content on screen does not change; only the focusable
60
+ // bounds of the outer group are offset.
61
+ style={[styles.flexOne, { marginTop: topMenuHeight }]}
62
+ // this group does not have parent
63
+ groupId={undefined}
64
+ >
65
+ <View style={[styles.flexOne, { marginTop: -1 * topMenuHeight }]}>
66
+ <Component {...props} groupId={focusableId} />
67
+ </View>
68
+ </FocusableGroup>
69
+ );
70
+ };
71
+ };
@@ -137,6 +137,7 @@ exports[`componentsMap renders renders components map correctly 1`] = `
137
137
  keyExtractor={[Function]}
138
138
  maxToRenderPerBatch={10}
139
139
  onContentSizeChange={[Function]}
140
+ onEndReached={[Function]}
140
141
  onLayout={[Function]}
141
142
  onMomentumScrollBegin={[Function]}
142
143
  onMomentumScrollEnd={[Function]}
@@ -154,6 +155,7 @@ exports[`componentsMap renders renders components map correctly 1`] = `
154
155
  }
155
156
  }
156
157
  stickyHeaderIndices={[]}
158
+ testID="components-map-flat-list"
157
159
  viewabilityConfigCallbackPairs={[]}
158
160
  windowSize={12}
159
161
  >
@@ -139,7 +139,13 @@ jest.mock(
139
139
  })
140
140
  );
141
141
 
142
+ jest.mock("@applicaster/zapp-react-native-ui-components/events", () => ({
143
+ ...jest.requireActual("@applicaster/zapp-react-native-ui-components/events"),
144
+ emitScrollEndReached: jest.fn(),
145
+ }));
146
+
142
147
  const { View } = require("react-native");
148
+ const events = require("@applicaster/zapp-react-native-ui-components/events");
143
149
  const { ComponentsMap } = require("../ComponentsMap/ComponentsMap");
144
150
  const theme = require("./theme-mock.json");
145
151
 
@@ -190,4 +196,36 @@ describe("componentsMap", () => {
190
196
 
191
197
  expect(toJSON()).toMatchSnapshot();
192
198
  });
199
+
200
+ it("calls emitScrollEndReached when onScroll was called and isScreenWrappedInContainer is false", () => {
201
+ themeSpy = jest
202
+ .spyOn(themeUtils, "useTheme")
203
+ .mockImplementation(() => () => theme);
204
+
205
+ events.emitScrollEndReached.mockClear();
206
+
207
+ const { getByTestId } = render(
208
+ <Provider store={store}>
209
+ <ComponentsMap
210
+ {...props}
211
+ isScreenWrappedInContainer={false}
212
+ feed={{ entry: [] }}
213
+ />
214
+ </Provider>
215
+ );
216
+
217
+ const flatList = getByTestId("components-map-flat-list");
218
+
219
+ flatList.props.onScroll({
220
+ nativeEvent: {
221
+ contentOffset: { y: 0 },
222
+ layoutMeasurement: { height: 100 },
223
+ contentSize: { height: 200 },
224
+ },
225
+ });
226
+
227
+ flatList.props.onEndReached();
228
+
229
+ expect(events.emitScrollEndReached).toHaveBeenCalledTimes(1);
230
+ });
193
231
  });
@@ -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) {
@@ -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,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
  });
@@ -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,24 +65,28 @@ 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
+ );
83
90
 
84
91
  type ScreenContextType = {
85
92
  _navBarStore: ReturnType<typeof createStore>;
@@ -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,