@applicaster/zapp-react-native-ui-components 13.0.0-alpha.8532113155 → 13.0.0-alpha.9103242840

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 (63) hide show
  1. package/Components/BaseFocusable/index.ios.ts +1 -1
  2. package/Components/BaseFocusable/index.tsx +1 -1
  3. package/Components/Cell/Cell.tsx +4 -7
  4. package/Components/Cell/CellWithFocusable.tsx +59 -43
  5. package/Components/Cell/TvOSCellComponent.tsx +20 -22
  6. package/Components/Cell/__tests__/CellWIthFocusable.test.js +6 -13
  7. package/Components/Cell/index.js +3 -1
  8. package/Components/Focusable/FocusableTvOS.tsx +5 -1
  9. package/Components/Focusable/Touchable.tsx +20 -19
  10. package/Components/FocusableList/index.tsx +26 -9
  11. package/Components/FocusableScrollView/index.tsx +39 -12
  12. package/Components/Layout/TV/NavBarContainer.tsx +8 -7
  13. package/Components/Layout/TV/__tests__/__snapshots__/NavBarContainer.test.tsx.snap +1 -1
  14. package/Components/Layout/TV/__tests__/__snapshots__/ScreenContainer.test.tsx.snap +1 -1
  15. package/Components/MasterCell/DefaultComponents/BorderContainerView/index.tsx +1 -4
  16. package/Components/MasterCell/DefaultComponents/Image/Image.ios.tsx +11 -5
  17. package/Components/MasterCell/DefaultComponents/SecondaryImage/utils.ts +1 -1
  18. package/Components/MasterCell/DefaultComponents/__tests__/image.test.js +1 -1
  19. package/Components/MasterCell/hooks/useAsyncRendering/MasterCellAsyncRenderManager.ts +2 -2
  20. package/Components/MasterCell/utils/index.ts +1 -1
  21. package/Components/ModalComponent/Header/index.tsx +3 -3
  22. package/Components/River/ComponentsMap/ComponentsMap.tsx +65 -39
  23. package/Components/River/ComponentsMap/hooks/useLoadingState.ts +51 -78
  24. package/Components/River/RiverFooter.tsx +9 -39
  25. package/Components/River/RiverItem.tsx +2 -37
  26. package/Components/River/TV/index.tsx +6 -1
  27. package/Components/River/__tests__/__snapshots__/componentsMap.test.js.snap +31 -100
  28. package/Components/River/__tests__/componentsMap.test.js +5 -17
  29. package/Components/Screen/index.tsx +39 -13
  30. package/Components/Tabs/Tab.tsx +6 -6
  31. package/Components/TextInputTv/index.tsx +2 -2
  32. package/Components/Transitioner/AnimationManager.js +8 -8
  33. package/Components/Transitioner/Scene.tsx +23 -52
  34. package/Components/Transitioner/__tests__/__snapshots__/Scene.test.js.snap +43 -59
  35. package/Components/Transitioner/__tests__/__snapshots__/transitioner.test.js.snap +2 -2
  36. package/Components/Transitioner/index.js +4 -8
  37. package/Components/VideoLive/LiveImageManager.ts +1 -27
  38. package/Components/VideoLive/PlayerLiveImageComponent.tsx +21 -29
  39. package/Components/VideoLive/__tests__/PlayerLiveImageComponent.test.tsx +1 -51
  40. package/Components/VideoLive/__tests__/__snapshots__/PlayerLiveImageComponent.test.tsx.snap +5 -0
  41. package/Components/VideoModal/ModalAnimation/AnimationComponent.tsx +6 -7
  42. package/Components/VideoModal/ModalAnimation/ModalAnimationContext.tsx +3 -2
  43. package/Components/VideoModal/ModalAnimation/utils.ts +2 -2
  44. package/Components/VideoModal/PlayerWrapper.tsx +35 -16
  45. package/Components/VideoModal/VideoModal.tsx +23 -14
  46. package/Components/VideoModal/__tests__/PlayerWrapper.test.tsx +1 -1
  47. package/Components/VideoModal/__tests__/__snapshots__/PlayerWrapper.test.tsx.snap +90 -0
  48. package/Components/VideoModal/hooks/utils/index.ts +0 -33
  49. package/Components/VideoModal/utils.ts +1 -1
  50. package/Contexts/ComponentsMapOffsetContext/index.tsx +46 -0
  51. package/Contexts/ScreenContext/index.tsx +2 -3
  52. package/Decorators/ZappPipesDataConnector/__tests__/Hero.js +1 -1
  53. package/Decorators/ZappPipesDataConnector/index.tsx +1 -31
  54. package/package.json +5 -5
  55. package/Components/Cell/CellWithFocusable.ios.tsx +0 -126
  56. package/Components/MasterCell/DefaultComponents/BorderContainerView/__tests__/index.test.tsx +0 -66
  57. package/Components/MasterCell/DefaultComponents/ImageBorderContainer/__tests__/index.test.ts +0 -93
  58. package/Components/Screen/hooks.ts +0 -56
  59. package/Components/VideoModal/OpaqueLayer.tsx +0 -33
  60. package/Components/VideoModal/hooks/__tests__/useDelayedPlayerDetails.test.ts +0 -89
  61. package/Components/VideoModal/hooks/index.ts +0 -7
  62. package/Components/VideoModal/hooks/useDelayedPlayerDetails.ts +0 -49
  63. package/Components/VideoModal/hooks/utils/__tests__/showDetails.test.ts +0 -91
@@ -25,7 +25,7 @@ type Props = {
25
25
  };
26
26
 
27
27
  export class BaseFocusable<
28
- T extends Props = Props,
28
+ T extends Props = Props
29
29
  > extends BaseFocusableInterface<T> {
30
30
  constructor(props) {
31
31
  super(props);
@@ -35,7 +35,7 @@ type Props = {
35
35
  };
36
36
 
37
37
  export class BaseFocusable<
38
- T extends Props = Props,
38
+ T extends Props = Props
39
39
  > extends BaseFocusableInterface<T> {
40
40
  constructor(props) {
41
41
  super(props);
@@ -61,6 +61,7 @@ type Props = {
61
61
  skipFocusManagerRegistration?: boolean;
62
62
  shouldUpdate: boolean;
63
63
  behavior: Behavior;
64
+ componentsMapOffset: number;
64
65
  };
65
66
 
66
67
  type State = {
@@ -273,14 +274,10 @@ export class CellComponent extends React.Component<Props, State> {
273
274
  if (isFocused) {
274
275
  const accessibilityManager = AccessibilityManager.getInstance();
275
276
 
276
- const accessibilityTitle =
277
- item?.extensions?.accessibility?.label || item?.title || "";
278
-
279
- const accessibilityHint =
280
- item?.extensions?.accessibility?.hint || "";
281
-
282
277
  accessibilityManager.readText({
283
- text: `${accessibilityTitle} ${accessibilityHint}`,
278
+ text: String(
279
+ item.extensions?.accessibility?.label || item.title
280
+ ),
284
281
  });
285
282
  }
286
283
 
@@ -1,10 +1,48 @@
1
1
  import * as React from "react";
2
2
 
3
+ import { focusManager } from "@applicaster/zapp-react-native-utils/appUtils/focusManager";
4
+ import * as FOCUS_EVENTS from "@applicaster/zapp-react-native-utils/appUtils/focusManager/events";
3
5
  import { noop } from "@applicaster/zapp-react-native-utils/functionUtils";
4
6
  import { toBooleanWithDefaultFalse } from "@applicaster/zapp-react-native-utils/booleanUtils";
5
7
 
8
+ import { isAppleTV } from "../../Helpers/Platform";
6
9
  import { useCellState } from "../MasterCell/utils";
7
- import { FocusableGroup } from "../FocusableGroup";
10
+
11
+ const useCellFocusedState = (
12
+ skipFocusManagerRegistration: boolean,
13
+ groupId: string,
14
+ id: string
15
+ ) => {
16
+ const [currentCellFocused, setCurrentCellFocused] = React.useState(false);
17
+
18
+ React.useEffect(() => {
19
+ const isGroupItemFocused = () => {
20
+ if (!skipFocusManagerRegistration) {
21
+ const isFocused = focusManager.isGroupItemFocused(groupId, id);
22
+ setCurrentCellFocused(isFocused);
23
+ }
24
+ };
25
+
26
+ const handler = () => {
27
+ // tvOS hack for properly checking focus
28
+ if (isAppleTV()) {
29
+ setTimeout(() => {
30
+ isGroupItemFocused();
31
+ }, 0);
32
+ } else {
33
+ isGroupItemFocused();
34
+ }
35
+ };
36
+
37
+ focusManager.on(FOCUS_EVENTS.FOCUS, handler);
38
+
39
+ return () => {
40
+ focusManager.removeHandler(FOCUS_EVENTS.FOCUS, handler);
41
+ };
42
+ }, [groupId, skipFocusManagerRegistration]);
43
+
44
+ return currentCellFocused;
45
+ };
8
46
 
9
47
  type Props = {
10
48
  item: ZappEntry;
@@ -37,7 +75,11 @@ export function CellWithFocusable(props: Props) {
37
75
  focused,
38
76
  } = props;
39
77
 
40
- const [isFocused, setIsFocused] = React.useState(false);
78
+ const isFocused = useCellFocusedState(
79
+ skipFocusManagerRegistration,
80
+ groupId,
81
+ id
82
+ );
41
83
 
42
84
  const state = useCellState({
43
85
  id: item.id,
@@ -54,52 +96,26 @@ export function CellWithFocusable(props: Props) {
54
96
  }
55
97
  }, [focusedButtonId]);
56
98
 
57
- const handleToggleFocus = React.useCallback(
58
- (value) => {
59
- setFocusedButtonId(value.focusedButtonId);
60
-
61
- if (value.focusable) {
62
- onFocus(value.focusable, value.mouse);
63
- }
64
- },
65
- [onFocus]
66
- );
67
-
68
- const onGroupFocus = React.useCallback(() => {
69
- if (!skipFocusManagerRegistration) {
70
- setIsFocused(true);
71
- }
72
- }, [skipFocusManagerRegistration]);
99
+ const handleToggleFocus = (value) => {
100
+ setFocusedButtonId(value.focusedButtonId);
73
101
 
74
- const onGroupBlur = React.useCallback(() => {
75
- if (!skipFocusManagerRegistration) {
76
- setIsFocused(false);
102
+ if (value.focusable) {
103
+ onFocus(value.focusable, value.mouse);
77
104
  }
78
- }, [skipFocusManagerRegistration]);
105
+ };
79
106
 
80
107
  return (
81
- <FocusableGroup
82
- id={`focusable-cell-wrapper-${id}`}
83
- testID={"cell-with-focusable-cell-renderer-focusable-group"}
108
+ <CellRenderer
109
+ item={item}
84
110
  groupId={groupId}
111
+ onToggleFocus={handleToggleFocus}
112
+ state={state}
113
+ prefixId={id}
114
+ focusedButtonId={focusedButtonId}
85
115
  preferredFocus={preferredFocus}
86
- shouldUsePreferredFocus
87
- onFocus={onGroupFocus}
88
- onBlur={onGroupBlur}
89
- >
90
- <CellRenderer
91
- testID={"cell-with-focusable-cell-renderer"}
92
- item={item}
93
- groupId={`focusable-cell-wrapper-${id}`}
94
- onToggleFocus={handleToggleFocus}
95
- state={state}
96
- prefixId={id}
97
- focusedButtonId={focusedButtonId}
98
- preferredFocus={true}
99
- skipFocusManagerRegistration={skipFocusManagerRegistration}
100
- isFocusable={isFocusable}
101
- focused={focused}
102
- />
103
- </FocusableGroup>
116
+ skipFocusManagerRegistration={skipFocusManagerRegistration}
117
+ isFocusable={isFocusable}
118
+ focused={focused}
119
+ />
104
120
  );
105
121
  }
@@ -2,7 +2,7 @@ import * as React from "react";
2
2
  import * as R from "ramda";
3
3
  import { View, StyleSheet } from "react-native";
4
4
 
5
- import { Focusable } from "@applicaster/zapp-react-native-ui-components/Components/Focusable";
5
+ import { Focusable } from "@applicaster/zapp-react-native-ui-components/Components/Focusable/FocusableTvOS";
6
6
  import { FocusableCell } from "@applicaster/zapp-react-native-ui-components/Components/FocusableCell";
7
7
  import { getItemType } from "@applicaster/zapp-react-native-utils/navigationUtils";
8
8
  import { SCREEN_TYPES } from "@applicaster/zapp-react-native-utils/navigationUtils/itemTypes";
@@ -65,6 +65,7 @@ type Props = {
65
65
  isFocusable: boolean;
66
66
  shouldUpdate: boolean;
67
67
  behavior: Behavior;
68
+ componentsMapOffset: number;
68
69
  };
69
70
 
70
71
  type State = {
@@ -179,6 +180,7 @@ export class TvOSCellComponent extends React.Component<Props, State> {
179
180
  groupId,
180
181
  component,
181
182
  index,
183
+ componentsMapOffset,
182
184
  } = this.props;
183
185
 
184
186
  this.setScreenLayout(componentAnchorPointY, screenLayout);
@@ -193,13 +195,11 @@ export class TvOSCellComponent extends React.Component<Props, State> {
193
195
  const extraAnchorPointYOffset =
194
196
  screenLayout?.extraAnchorPointYOffset || 0;
195
197
 
196
- const marginTop = screenLayout?.screenMarginTop || 0;
197
-
198
198
  const totalOffset =
199
199
  headerOffset +
200
- (componentAnchorPointY || 0) +
201
- extraAnchorPointYOffset -
202
- marginTop;
200
+ (componentAnchorPointY || 0) +
201
+ extraAnchorPointYOffset -
202
+ componentsMapOffset || 0;
203
203
 
204
204
  mainOffsetUpdater?.(
205
205
  { tag: this.target },
@@ -247,7 +247,7 @@ export class TvOSCellComponent extends React.Component<Props, State> {
247
247
 
248
248
  const focusableId = R.join("-", [component?.id, id, index]);
249
249
 
250
- const handleFocus = (arg1: any, index: number) => {
250
+ const handleFocus = (arg1: any, index?: number) => {
251
251
  const focusFn = onFocus || noop;
252
252
  focusFn(arg1, index);
253
253
 
@@ -280,7 +280,7 @@ export class TvOSCellComponent extends React.Component<Props, State> {
280
280
  <View onLayout={this.onLayout} style={styles.container}>
281
281
  <Focusable
282
282
  id={focusableId}
283
- groupId={groupId || component?.id}
283
+ groupId={String(groupId || component?.id)}
284
284
  onFocus={handleFocus}
285
285
  onBlur={onBlur || this.onBlur}
286
286
  isParallaxDisabled={this.layout?.width > 1740}
@@ -292,20 +292,18 @@ export class TvOSCellComponent extends React.Component<Props, State> {
292
292
  style={baseCellStyles}
293
293
  isFocusable={isFocusable}
294
294
  >
295
- {(focused) => {
296
- return (
297
- <FocusableCell
298
- {...{
299
- index,
300
- CellRenderer,
301
- item,
302
- focused: this.props.focused || selectedCell || focused,
303
- scrollTo: this.scrollTo,
304
- behavior,
305
- }}
306
- />
307
- );
308
- }}
295
+ {(focused) => (
296
+ <FocusableCell
297
+ {...{
298
+ index,
299
+ CellRenderer,
300
+ item,
301
+ focused: !!(this.props.focused || selectedCell || focused),
302
+ scrollTo: this.scrollTo,
303
+ behavior,
304
+ }}
305
+ />
306
+ )}
309
307
  </Focusable>
310
308
  </View>
311
309
  );
@@ -1,7 +1,7 @@
1
1
  import { View } from "react-native";
2
2
  import React from "react";
3
3
  import { act, render } from "@testing-library/react-native";
4
- import { CellWithFocusable } from "../CellWithFocusable.tsx";
4
+ import { CellWithFocusable } from "../CellWithFocusable";
5
5
 
6
6
  import { focusManager } from "@applicaster/zapp-react-native-utils/appUtils/focusManager";
7
7
 
@@ -23,9 +23,7 @@ describe("CellWithFocusable", () => {
23
23
 
24
24
  const wrapper = renderWith(props);
25
25
 
26
- const element = wrapper.getByTestId("cell-with-focusable-cell-renderer");
27
-
28
- expect(element.props.state).toBe("default");
26
+ expect(wrapper.UNSAFE_getByType("View").props.state).toBe("default");
29
27
  });
30
28
 
31
29
  it("should render in default state", () => {
@@ -42,9 +40,8 @@ describe("CellWithFocusable", () => {
42
40
  focusManager.isGroupItemFocused = jest.fn(() => true);
43
41
 
44
42
  const wrapper = renderWith(props);
45
- const element = wrapper.getByTestId("cell-with-focusable-cell-renderer");
46
43
 
47
- expect(element.props.state).toBe("default");
44
+ expect(wrapper.UNSAFE_getByType("View").props.state).toBe("default");
48
45
  });
49
46
 
50
47
  it("should render in focused state", () => {
@@ -58,17 +55,13 @@ describe("CellWithFocusable", () => {
58
55
  scrollTo: jest.fn(),
59
56
  };
60
57
 
58
+ focusManager.isGroupItemFocused = jest.fn(() => true);
61
59
  const wrapper = renderWith(props);
62
60
 
63
- const focusableGroupComponent = wrapper.getByTestId(
64
- "cell-with-focusable-cell-renderer-focusable-group"
65
- );
66
-
67
61
  act(() => {
68
- focusableGroupComponent.props.onFocus();
62
+ focusManager.on.mock.calls[focusManager.on.mock.calls.length - 1][1]();
69
63
  });
70
64
 
71
- const element = wrapper.getByTestId("cell-with-focusable-cell-renderer");
72
- expect(element.props.state).toBe("focused");
65
+ expect(wrapper.UNSAFE_getByType("View").props.state).toBe("focused");
73
66
  });
74
67
  });
@@ -11,6 +11,7 @@ import { ScreenScrollingContext } from "../../Contexts/ScreenScrollingContext";
11
11
 
12
12
  import { ScreenLayoutContextConsumer } from "../../Contexts/ScreenLayoutContext";
13
13
  import { createContext } from "@applicaster/zapp-react-native-utils/reactUtils/createContext";
14
+ import { withComponentsMapOffsetContextConsumer } from "../../Contexts/ComponentsMapOffsetContext";
14
15
 
15
16
  // TODO | Gallery QB | Extract this
16
17
  export const ScrollInterceptorContext = createContext(
@@ -30,5 +31,6 @@ export const Cell = R.compose(
30
31
  RiverOffsetContext.withConsumer,
31
32
  HorizontalScrollContext.withConsumer,
32
33
  withConsumer,
33
- ScreenLayoutContextConsumer
34
+ ScreenLayoutContextConsumer,
35
+ withComponentsMapOffsetContextConsumer
34
36
  )(Component);
@@ -9,7 +9,7 @@ import {
9
9
  focusManager,
10
10
  forceFocusableFocus,
11
11
  } from "@applicaster/zapp-react-native-utils/appUtils/focusManager/index.ios";
12
- import { findNodeHandle } from "react-native";
12
+ import { findNodeHandle, ViewStyle } from "react-native";
13
13
 
14
14
  function noop() {}
15
15
 
@@ -35,6 +35,10 @@ type Props = {
35
35
  forceFocus?: boolean;
36
36
  initialFocus?: boolean;
37
37
  onLayout?: (e: any) => void;
38
+ willReceiveFocus: () => void;
39
+ hasReceivedFocus: () => void;
40
+ offsetUpdater: (arg1: string, arg2: number) => number;
41
+ style: ViewStyle;
38
42
  };
39
43
 
40
44
  export class Focusable extends BaseFocusable<Props> {
@@ -1,24 +1,25 @@
1
1
  import * as React from "react";
2
2
 
3
- type Props = {
4
- id: string;
5
- children: React.ReactNode;
6
- onPress?: (ref: FocusManager.TouchableRef) => void;
7
- onPressIn?: (ref: FocusManager.TouchableRef) => void;
8
- onPressOut?: (ref: FocusManager.TouchableRef) => void;
9
- onLongPress?: (ref: FocusManager.TouchableRef) => void;
10
- onFocus?: (
11
- ref: FocusManager.TouchableRef,
12
- options: FocusManager.Android.CallbackOptions
13
- ) => void;
14
- onBlur?: (
15
- ref: FocusManager.TouchableRef,
16
- options: FocusManager.Android.CallbackOptions
17
- ) => void;
18
-
19
- disableFocus?: boolean;
20
- blockFocus?: boolean;
21
- } & Partial<ParentFocus>;
3
+ type Props =
4
+ | {
5
+ id: string;
6
+ children: React.ReactNode;
7
+ onPress?: (ref: FocusManager.TouchableRef) => void;
8
+ onPressIn?: (ref: FocusManager.TouchableRef) => void;
9
+ onPressOut?: (ref: FocusManager.TouchableRef) => void;
10
+ onLongPress?: (ref: FocusManager.TouchableRef) => void;
11
+ onFocus?: (
12
+ ref: FocusManager.TouchableRef,
13
+ options: FocusManager.Android.CallbackOptions
14
+ ) => void;
15
+ onBlur?: (
16
+ ref: FocusManager.TouchableRef,
17
+ options: FocusManager.Android.CallbackOptions
18
+ ) => void;
19
+
20
+ disableFocus?: boolean;
21
+ blockFocus?: boolean;
22
+ } & Partial<ParentFocus>;
22
23
 
23
24
  export class Touchable extends React.Component<Props> {
24
25
  onPress(focusableRef: FocusManager.TouchableRef): void {
@@ -15,6 +15,7 @@ import { noop } from "@applicaster/zapp-react-native-utils/functionUtils";
15
15
 
16
16
  import { useParentFocus, useCheckItemIdsForUnique } from "./hooks";
17
17
  import { FocusableListItemWrapper } from "./FocusableListItemWrapper";
18
+ import { FocusableScrollView } from "../FocusableScrollView";
18
19
 
19
20
  const mapIndexed = R.addIndex(R.map);
20
21
 
@@ -54,6 +55,7 @@ export type Props<ItemT> = FlatListProps<ItemT> & {
54
55
  disableNextFocusUp?: boolean;
55
56
  disableNextFocusLeft?: boolean;
56
57
  disableNextFocusRight?: boolean;
58
+ useScrollView?: boolean;
57
59
  } & ParentFocus;
58
60
 
59
61
  function FocusableListComponent<ItemT>(props: Props<ItemT>, ref) {
@@ -88,6 +90,7 @@ function FocusableListComponent<ItemT>(props: Props<ItemT>, ref) {
88
90
  data = [],
89
91
  // eslint-disable-next-line unused-imports/no-unused-vars
90
92
  omitPropsPropagation = [],
93
+ useScrollView = false,
91
94
  } = props;
92
95
 
93
96
  useCheckItemIdsForUnique({ componentId: props.id, items: data });
@@ -273,6 +276,7 @@ function FocusableListComponent<ItemT>(props: Props<ItemT>, ref) {
273
276
  "initialFocusDirection",
274
277
  "withStateMemory",
275
278
  "useSequentialLoading",
279
+ "useScrollView",
276
280
  ...omitPropsPropagation,
277
281
  ],
278
282
  R.__
@@ -295,15 +299,28 @@ function FocusableListComponent<ItemT>(props: Props<ItemT>, ref) {
295
299
  return (
296
300
  // @ts-ignore
297
301
  <ChildrenFocusDeactivatorView flex={1}>
298
- <FlatList
299
- focusable={false}
300
- scrollEnabled={false}
301
- ref={ref}
302
- {...getFlatListProps(props)}
303
- renderItem={renderItem}
304
- onEndReached={onEndReached}
305
- initialNumToRender={initialNumToRender}
306
- />
302
+ {useScrollView ? (
303
+ <FocusableScrollView
304
+ ref={ref}
305
+ {...getFlatListProps(props)}
306
+ onEndReached={onEndReached}
307
+ initialNumToRender={initialNumToRender}
308
+ renderItem={renderItem}
309
+ focused={focused}
310
+ data={data}
311
+ />
312
+ ) : (
313
+ <FlatList
314
+ testID="focusable-list-flat-list"
315
+ focusable={false}
316
+ scrollEnabled={false}
317
+ ref={ref}
318
+ {...getFlatListProps(props)}
319
+ renderItem={renderItem}
320
+ onEndReached={onEndReached}
321
+ initialNumToRender={initialNumToRender}
322
+ />
323
+ )}
307
324
  </ChildrenFocusDeactivatorView>
308
325
  );
309
326
  }
@@ -11,6 +11,13 @@ import {
11
11
  useInitialFocus,
12
12
  } from "@applicaster/zapp-react-native-utils/focusManager";
13
13
 
14
+ export type FocusableScrollViewRenderItem = (info: {
15
+ item: ZappUIComponent;
16
+ index: number;
17
+ focused: boolean;
18
+ parentFocus: ParentFocus;
19
+ }) => React.ReactElement;
20
+
14
21
  export type Props = ScrollViewProps & {
15
22
  id?: number | string;
16
23
  horizontal?: boolean;
@@ -32,17 +39,27 @@ export type Props = ScrollViewProps & {
32
39
  initialFocusDirection?: FocusManager.Android.FocusNavigationDirections;
33
40
  onAllComponentsLoaded?: () => void;
34
41
  omitPropsPropagation?: Array<keyof FlatListProps<any>>;
35
- renderItem: (info: {
36
- item: ZappUIComponent;
37
- index: number;
38
- focused: boolean;
39
- parentFocus: ParentFocus;
40
- }) => React.ReactElement;
42
+ renderItem: FocusableScrollViewRenderItem;
41
43
  } & ParentFocus;
42
44
 
43
45
  type State = boolean[];
44
46
  type Action = { index?: number; isFocusable: boolean; type?: string };
45
47
 
48
+ const unwrapArray = (maybeArray) =>
49
+ Array.isArray(maybeArray) ? maybeArray[0] : maybeArray;
50
+
51
+ function getEntry<T>(data: T | T[], index: unknown): T {
52
+ let entry;
53
+
54
+ if (R.isNil(index)) {
55
+ entry = unwrapArray(data);
56
+ } else if (typeof index === "number") {
57
+ entry = unwrapArray(data[index]);
58
+ }
59
+
60
+ return entry;
61
+ }
62
+
46
63
  const focusableStateReducer = (
47
64
  state: State,
48
65
  { index = null, isFocusable, type = null }: Action
@@ -73,7 +90,7 @@ const extendScrollViewRef = (_ref, childCompsMeasurementsMap) => {
73
90
  };
74
91
 
75
92
  _ref.scrollToIndex = (params) => {
76
- const { index, viewOffset } = params;
93
+ const { index, viewOffset = 0 } = params;
77
94
 
78
95
  const itemByIndex = R.find(R.propEq("index", index))(
79
96
  R.values(childCompsMeasurementsMap)
@@ -129,8 +146,11 @@ function FocusableScrollViewComponent(props: Props, ref) {
129
146
  );
130
147
 
131
148
  const getFocusableEntryId = React.useCallback(
132
- (_entry: ZappEntry, index?: number) => {
133
- const entry = R.isNil(index) ? _entry : data[index];
149
+ (_entry: ZappEntry | null, index?: number) => {
150
+ const entry: { id?: string } = getEntry(
151
+ R.isNil(index) ? _entry : data,
152
+ index
153
+ );
134
154
 
135
155
  return entry ? `${props.id}--${entry?.id}` : undefined;
136
156
  },
@@ -273,11 +293,18 @@ function FocusableScrollViewComponent(props: Props, ref) {
273
293
  const renderItem = React.useCallback(
274
294
  (item, index) => {
275
295
  const renderArgs = { item, index };
276
- const id = `${props.id}--${renderArgs?.item?.id}`;
277
- const nextFocus = getNextFocus(renderArgs?.index);
296
+
297
+ const id = getFocusableEntryId(
298
+ Array.isArray(item) ? item[0] : item,
299
+ index
300
+ );
301
+
302
+ const nextFocus = getNextFocus(index);
278
303
 
279
304
  const onItemLayout = (event) => {
280
- childCompsMeasurementsMap.current[item.id] = {
305
+ childCompsMeasurementsMap.current[
306
+ Array.isArray(item) ? index : item.id
307
+ ] = {
281
308
  index,
282
309
  layout: event.nativeEvent.layout,
283
310
  };
@@ -1,7 +1,5 @@
1
1
  import * as React from "react";
2
2
  import { View, StyleSheet } from "react-native";
3
- import { ScreenLayoutContext } from "@applicaster/zapp-react-native-ui-components/Contexts/ScreenLayoutContext";
4
- import { ifEmptyUseFallback } from "@applicaster/zapp-react-native-utils/cellUtils";
5
3
  import { useCurrentScreenData } from "@applicaster/zapp-react-native-utils/reactHooks/screen";
6
4
  import { useScreenConfiguration } from "../../River/useScreenConfiguration";
7
5
 
@@ -12,12 +10,15 @@ type Props = {
12
10
  };
13
11
 
14
12
  const styles = StyleSheet.create({
15
- container: { position: "absolute", top: 0, zIndex: 10, width: 1920 },
13
+ container: {
14
+ position: "absolute",
15
+ top: 0,
16
+ zIndex: 10,
17
+ width: 1920,
18
+ },
16
19
  });
17
20
 
18
21
  export const NavBarContainer = ({ children, isVisible, onReady }: Props) => {
19
- const screenLayout = React.useContext(ScreenLayoutContext);
20
- const screenMarginTop = screenLayout?.screenMarginTop || 0;
21
22
  const screen = useCurrentScreenData();
22
23
  const screenConfig = useScreenConfiguration(screen?.id);
23
24
 
@@ -26,9 +27,9 @@ export const NavBarContainer = ({ children, isVisible, onReady }: Props) => {
26
27
  // limits the height of the focusable container of the TopMenuBarTV component
27
28
  // to prevent it from being overlapped by the screen content,
28
29
  // as it makes TopMenuBarTV unfocusable on tvOS
29
- maxHeight: ifEmptyUseFallback(screenConfig?.marginTop, screenMarginTop),
30
+ maxHeight: 1,
30
31
  }),
31
- [screenLayout, screenConfig]
32
+ [screenConfig]
32
33
  );
33
34
 
34
35
  React.useEffect(() => {
@@ -11,7 +11,7 @@ exports[`NavBarContainer renders 1`] = `
11
11
  "zIndex": 10,
12
12
  },
13
13
  {
14
- "maxHeight": 10,
14
+ "maxHeight": 1,
15
15
  },
16
16
  ]
17
17
  }
@@ -22,7 +22,7 @@ exports[`ScreenContainer renders 1`] = `
22
22
  "zIndex": 10,
23
23
  },
24
24
  {
25
- "maxHeight": 10,
25
+ "maxHeight": 1,
26
26
  },
27
27
  ]
28
28
  }
@@ -28,10 +28,7 @@ const styles = StyleSheet.create({
28
28
  },
29
29
  });
30
30
 
31
- export const getBorderPadding = (
32
- borderPosition: BorderPosition,
33
- borderWidth: number
34
- ) => {
31
+ const getBorderPadding = (borderPosition: BorderPosition, borderWidth) => {
35
32
  switch (borderPosition) {
36
33
  case "inside":
37
34
  return 0;
@@ -38,16 +38,22 @@ function Image({
38
38
 
39
39
  const shouldRenderImg = source && !error;
40
40
 
41
- const _source = shouldRenderImg
42
- ? withDimensions(source)
43
- : { uri: placeholderImage };
41
+ // Default placeholder image fallback is empty string because it won't rerender
42
+ // in case of undefined or {uri: undefined} source
43
+ // https://github.com/facebook/react-native/issues/9195
44
+ const defaultPlaceHolderImage = React.useMemo(
45
+ () => ({ uri: placeholderImage || "" }),
46
+ [source, error]
47
+ );
44
48
 
45
49
  return (
50
+ // @ts-ignore
46
51
  <MemoizedImage
47
52
  style={style as ImageStyle}
48
53
  onError={React.useCallback(() => setErrorState(true), [])}
49
- // as we have defaults as "" for placeholder image, we need to pass undefined to source to not throw warnings
50
- source={_source?.uri ? _source : undefined}
54
+ source={
55
+ shouldRenderImg ? withDimensions(source) : defaultPlaceHolderImage
56
+ }
51
57
  {...R.omit(["source"], otherProps)}
52
58
  />
53
59
  );
@@ -17,7 +17,7 @@ const SECONDARY_IMAGE_PREFIX = "secondary_image";
17
17
 
18
18
  type ImageSizing = typeof IMAGE_SIZING_FIT | typeof IMAGE_SIZING_FILL;
19
19
 
20
- type ImagePosition = (typeof IMAGE_POSITION)[keyof typeof IMAGE_POSITION];
20
+ type ImagePosition = typeof IMAGE_POSITION[keyof typeof IMAGE_POSITION];
21
21
 
22
22
  export type DisplayMode =
23
23
  | typeof DISPLAY_MODE_FIXED