@applicaster/zapp-react-native-ui-components 12.0.0-alpha.3977160850 → 12.0.0-alpha.4337072244

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 (82) hide show
  1. package/Components/BaseFocusable/index.tsx +1 -1
  2. package/Components/Cell/Cell.tsx +7 -7
  3. package/Components/Cell/CellWithFocusable.tsx +38 -19
  4. package/Components/Cell/TvOSCellComponent.tsx +2 -2
  5. package/Components/ErrorScreen/index.tsx +1 -1
  6. package/Components/FocusableCell/index.tsx +7 -7
  7. package/Components/FocusableGroup/index.tsx +1 -0
  8. package/Components/GeneralContentScreen/utils/__tests__/useCurationAPI.test.js +13 -6
  9. package/Components/GeneralContentScreen/utils/useCurationAPI.ts +1 -1
  10. package/Components/HandlePlayable/HandlePlayable.tsx +3 -0
  11. package/Components/HookRenderer/index.tsx +1 -3
  12. package/Components/Image/Image.android.tsx +3 -25
  13. package/Components/Image/Image.tsx +18 -2
  14. package/Components/Image/utils/index.ts +39 -0
  15. package/Components/Layout/TV/ScreenLayoutContextProvider.tsx +2 -2
  16. package/Components/Layout/TV/__tests__/__snapshots__/index.test.tsx.snap +10 -10
  17. package/Components/Layout/TV/__tests__/index.test.tsx +18 -86
  18. package/Components/Layout/TV/index.tsx +43 -102
  19. package/Components/MasterCell/DefaultComponents/ActionButton.tsx +2 -0
  20. package/Components/MasterCell/DefaultComponents/Image/Image.android.tsx +9 -26
  21. package/Components/MasterCell/DefaultComponents/Image/Image.ios.tsx +5 -3
  22. package/Components/MasterCell/DefaultComponents/Image/hooks/useImageSize.ts +65 -0
  23. package/Components/MasterCell/DefaultComponents/ImageContainer/index.tsx +5 -1
  24. package/Components/MasterCell/DefaultComponents/LiveImage/index.tsx +17 -2
  25. package/Components/MasterCell/DefaultComponents/SecondaryImage/Image.tsx +3 -2
  26. package/Components/MasterCell/DefaultComponents/Text/index.tsx +14 -2
  27. package/Components/MasterCell/DefaultComponents/Toggle.tsx +5 -1
  28. package/Components/MasterCell/DefaultComponents/__tests__/image.test.js +1 -1
  29. package/Components/MasterCell/DefaultComponents/__tests__/text.test.tsx +24 -9
  30. package/Components/MasterCell/DefaultComponents/tv/ButtonContainerView/index.android.tsx +4 -1
  31. package/Components/MasterCell/DefaultComponents/tv/ButtonContainerView/index.tsx +4 -1
  32. package/Components/MasterCell/SharedUI/CollapsibleTextContainer/index.ts +1 -3
  33. package/Components/MasterCell/hooks/useAsyncRendering/index.ts +1 -1
  34. package/Components/MasterCell/utils/index.ts +18 -7
  35. package/Components/MeasurmentsPortal/BasicMeasurementsPortal.tsx +11 -7
  36. package/Components/MeasurmentsPortal/MeasurementsPortal.tsx +10 -1
  37. package/Components/PlayerContainer/PlayerContainer.tsx +64 -31
  38. package/Components/PlayerContainer/ProgramInfo/index.tsx +3 -1
  39. package/Components/PlayerImageBackground/index.tsx +34 -8
  40. package/Components/River/ComponentsMap/ComponentsMap.tsx +86 -22
  41. package/Components/River/RefreshControl.tsx +55 -1
  42. package/Components/River/TV/River.tsx +0 -23
  43. package/Components/River/__tests__/componentsMap.test.js +7 -0
  44. package/Components/RouteManager/index.tsx +2 -2
  45. package/Components/Screen/TV/hooks/index.ts +3 -0
  46. package/Components/Screen/TV/hooks/useInitialFocus.ts +67 -0
  47. package/Components/Screen/TV/hooks/useRestoreFocus.ts +22 -0
  48. package/Components/Screen/TV/index.web.tsx +174 -0
  49. package/Components/Screen/__tests__/Screen.test.tsx +5 -1
  50. package/Components/Screen/__tests__/__snapshots__/Screen.test.tsx.snap +14 -0
  51. package/Components/Screen/index.tsx +48 -28
  52. package/Components/ScreenResolver/index.tsx +3 -1
  53. package/Components/Tabs/TV/styles.ts +1 -1
  54. package/Components/VideoLive/PlayerLiveImageComponent.tsx +31 -28
  55. package/Components/VideoLive/__tests__/PlayerLiveImageComponent.test.tsx +45 -3
  56. package/Components/VideoLive/__tests__/__snapshots__/PlayerLiveImageComponent.test.tsx.snap +18 -28
  57. package/Components/VideoModal/ModalAnimation/AnimatedPlayerModalWrapper.tsx +78 -0
  58. package/Components/VideoModal/ModalAnimation/AnimatedScrollModal.tsx +342 -0
  59. package/Components/VideoModal/ModalAnimation/AnimatedVideoPlayerComponent.tsx +151 -0
  60. package/Components/VideoModal/ModalAnimation/AnimationComponent.tsx +438 -0
  61. package/Components/VideoModal/ModalAnimation/ModalAnimationContext.tsx +154 -0
  62. package/Components/VideoModal/ModalAnimation/index.ts +15 -0
  63. package/Components/VideoModal/ModalAnimation/utils.ts +299 -0
  64. package/Components/VideoModal/PlayerWrapper.tsx +66 -43
  65. package/Components/VideoModal/VideoModal.tsx +25 -42
  66. package/Components/VideoModal/__tests__/PlayerWrapper.test.tsx +6 -1
  67. package/Components/VideoModal/__tests__/__snapshots__/PlayerWrapper.test.tsx.snap +246 -166
  68. package/Components/VideoModal/utils.ts +3 -3
  69. package/Components/Viewport/ViewportTracker/ViewportTracker.tsx +1 -1
  70. package/Components/Viewport/ViewportTracker/index.tsx +1 -0
  71. package/Components/Viewport/utils.js +1 -1
  72. package/Contexts/MenuToggleContext/{index.js → index.ts} +16 -0
  73. package/Contexts/ScreenContext/__tests__/__snapshots__/index.test.tsx.snap +3 -0
  74. package/Contexts/ScreenContext/__tests__/index.test.tsx +27 -0
  75. package/Contexts/ScreenContext/index.tsx +94 -25
  76. package/Contexts/ZappPipesContext/ZappPipesEntryContext.ts +1 -1
  77. package/Decorators/ComponentsMapWrapper/withComponentsMapProvider.tsx +7 -2
  78. package/events/index.ts +1 -0
  79. package/package.json +5 -5
  80. package/Components/Layout/TV/TransitionOverlay.tsx +0 -64
  81. package/Components/VideoModal/hooks/usePosition.ts +0 -50
  82. /package/Contexts/{index.js → index.ts} +0 -0
@@ -1,128 +1,69 @@
1
1
  import * as React from "react";
2
- import * as R from "ramda";
2
+ import { isEmpty, pathOr } from "ramda";
3
3
  import { usePickFromState } from "@applicaster/zapp-react-native-redux/hooks";
4
4
  import { useNavigation } from "@applicaster/zapp-react-native-utils/reactHooks/navigation";
5
- import { useTheme } from "@applicaster/zapp-react-native-utils/theme";
6
-
7
- import {
8
- getNavigationProps,
9
- resolveNavigationPlugin,
10
- routeIsPlayerScreen,
11
- } from "@applicaster/zapp-react-native-utils/navigationUtils";
12
-
13
- import { getBackgroundImageUrl } from "../utils";
14
- import { LayoutContainer } from "./LayoutContainer";
15
- import { NavBarContainer } from "./NavBarContainer";
16
- import { ScreenContainer } from "./ScreenContainer";
17
- import { TransitionOverlay } from "./TransitionOverlay";
5
+ import { getPathAttributes } from "@applicaster/zapp-react-native-utils/navigationUtils";
18
6
 
19
7
  import { ScreenLayoutContextProvider } from "./ScreenLayoutContextProvider";
20
- import { isApplePlatform } from "@applicaster/zapp-react-native-utils/reactUtils";
21
8
  import { PathnameContext } from "../../../Contexts/PathnameContext";
22
- import { useNavbarState } from "@applicaster/zapp-react-native-utils/reactHooks/screen/useScreenContext";
9
+ import { FocusableGroup } from "../../FocusableGroup";
10
+ import { Screen } from "../../Screen/TV/index.web";
11
+
12
+ export const ScreenDataContext = React.createContext(null);
23
13
 
24
14
  type Components = {
25
15
  NavBar: React.ComponentType<any>;
26
16
  Background: React.ComponentType<any>;
27
17
  };
28
18
 
29
- type ComponentsExtraProps = {
30
- Background?: Record<string, any>;
31
- NavBar?: Record<string, any>;
32
- };
33
-
34
19
  type Props = {
35
20
  Components: Components;
36
- ComponentsExtraProps?: ComponentsExtraProps;
37
- children: React.ReactNode;
38
21
  };
39
22
 
40
- const Layout = ({ Components, ComponentsExtraProps, children }: Props) => {
41
- const navigator = useNavigation();
42
- const theme = useTheme();
43
- const { title, visible } = useNavbarState();
44
- const { activeRiver, currentRoute } = navigator;
45
-
46
- const {
47
- appState: { appReady = false } = {},
48
- remoteConfigurations,
49
- plugins = [],
50
- } = usePickFromState(["appState", "remoteConfigurations", "plugins"]);
51
-
52
- const navigationProps = getNavigationProps({
53
- navigator,
54
- title,
55
- category: "menu",
56
- });
57
-
58
- const backgroundColor = React.useMemo(() => theme.app_background_color, []);
59
-
60
- const backgroundImageUrl = React.useMemo(
61
- () => getBackgroundImageUrl(remoteConfigurations),
62
- [remoteConfigurations]
63
- );
64
-
65
- const menuPlugin = React.useMemo(
66
- () =>
67
- resolveNavigationPlugin({
68
- category: "menu",
69
- navigations: activeRiver?.navigations,
70
- plugins,
71
- }) as QuickBrickPlugin,
72
- []
73
- );
74
-
75
- const NavBar = React.useMemo(
76
- () => Components.NavBar || (menuPlugin.module as React.ComponentType<any>),
77
- []
78
- );
79
-
80
- const [navBarReady, setNavBarReady] = React.useState(false);
81
-
82
- const navBarContainer = (
83
- <NavBarContainer isVisible={visible} onReady={setNavBarReady}>
84
- <NavBar
85
- ref={ComponentsExtraProps?.NavBar?.ref}
86
- isVisible={visible && !routeIsPlayerScreen(currentRoute)}
87
- menuPlugin={menuPlugin}
88
- navigationProps={navigationProps}
89
- {...R.omit(["ref"])(ComponentsExtraProps?.NavBar)}
90
- />
91
- </NavBarContainer>
92
- );
23
+ const StackNavigator = ({ Components }: Props) => {
24
+ // @ts-ignore
25
+ const { mainStack, currentRoute } = useNavigation();
26
+
27
+ const renderScreen = ({ route, state }) => {
28
+ const pathAttributes = getPathAttributes({ pathname: route });
29
+
30
+ if (isEmpty(pathAttributes)) {
31
+ return null;
32
+ }
33
+
34
+ const id = `${route}`;
35
+
36
+ return (
37
+ <FocusableGroup
38
+ id={id}
39
+ preferredFocus={route === currentRoute}
40
+ excludeFromFocusSearching
41
+ key={id}
42
+ >
43
+ <ScreenDataContext.Provider value={state}>
44
+ <PathnameContext.Provider value={route}>
45
+ <Screen route={route} Components={Components} />
46
+ </PathnameContext.Provider>
47
+ </ScreenDataContext.Provider>
48
+ </FocusableGroup>
49
+ );
50
+ };
51
+
52
+ return mainStack.map(renderScreen);
53
+ };
93
54
 
94
- const isTVOS = isApplePlatform();
55
+ const Layout = ({ Components }: Props) => {
56
+ const { appState } = usePickFromState(["appState"]);
57
+ const appReady = pathOr(false, ["appReady"], appState);
95
58
 
96
59
  if (!appReady) {
97
60
  return null;
98
61
  }
99
62
 
100
63
  return (
101
- <LayoutContainer>
102
- <ScreenLayoutContextProvider>
103
- <Components.Background
104
- backgroundColor={backgroundColor}
105
- backgroundImageUrl={backgroundImageUrl}
106
- {...R.omit(["ref"])(ComponentsExtraProps?.Background)}
107
- >
108
- {!isTVOS ? navBarContainer : null}
109
- {navBarReady ? (
110
- /**
111
- Need to re-mount this component on each navigation event
112
- It's a hack to work around the fact that we don't support
113
- correct screen rendering in our navigator (shared currentRoute state)
114
- **/
115
- <PathnameContext.Provider value={navigator.currentRoute}>
116
- <ScreenContainer>{children}</ScreenContainer>
117
- </PathnameContext.Provider>
118
- ) : null}
119
- {/** Render NavBarContainer over ScreenContainer to let tvOS native
120
- focus manager works correctly to focus nav bar items first **/}
121
- {!isTVOS ? navBarContainer : null}
122
- <TransitionOverlay />
123
- </Components.Background>
124
- </ScreenLayoutContextProvider>
125
- </LayoutContainer>
64
+ <ScreenLayoutContextProvider>
65
+ <StackNavigator Components={Components} />
66
+ </ScreenLayoutContextProvider>
126
67
  );
127
68
  };
128
69
 
@@ -25,6 +25,7 @@ type Props = {
25
25
  testID?: string;
26
26
  accessibilityLabel?: string;
27
27
  cellUUID?: string;
28
+ extraProps?: Record<string, any>;
28
29
  };
29
30
 
30
31
  function isStringAsset(asset) {
@@ -108,6 +109,7 @@ export function ActionButton(props: Props) {
108
109
  flavour={flavour}
109
110
  asset={getAssetValue(asset, flavour)}
110
111
  cellUUID={cellUUID}
112
+ {...(props?.extraProps ?? {})}
111
113
  />
112
114
  )}
113
115
  </TouchableOpacity>
@@ -1,11 +1,11 @@
1
1
  import * as React from "react";
2
2
  import { ImageStyle, View } from "react-native";
3
3
  import * as R from "ramda";
4
- import ImageComponent from "@applicaster/zapp-react-native-ui-components/Components/FastImage";
4
+ import { QBImage } from "@applicaster/zapp-react-native-ui-components/Components/Image";
5
5
  import { toBooleanWithDefaultFalse } from "@applicaster/zapp-react-native-utils/booleanUtils";
6
- import { toFiniteNumberWithDefault } from "@applicaster/zapp-react-native-utils/numberUtils";
7
6
 
8
7
  import { useImageSourceWithDefault, useCheckFailedUri } from "./hooks";
8
+ import useImageSize from "./hooks/useImageSize";
9
9
 
10
10
  type Source = {
11
11
  uri: string;
@@ -19,32 +19,13 @@ type Props = Record<string, any> & {
19
19
  withDimensions: (source: Source) => Source;
20
20
  };
21
21
 
22
- const MemoizedImage = React.memo(ImageComponent, R.equals);
23
-
24
- const normalize = Math.ceil;
22
+ const MemoizedImage = React.memo(QBImage, R.equals);
25
23
 
26
24
  const isValidExternalSource = (source): boolean =>
27
25
  source?.uri?.startsWith("http") ||
28
26
  source?.uri?.startsWith("data:image") ||
29
27
  source?.uri?.startsWith("file://");
30
28
 
31
- const isValidSize = (size: unknown): boolean =>
32
- toFiniteNumberWithDefault(0, size) > 0;
33
-
34
- const getSourceWithMaxSizes = (applyMaxSize, style, source) => {
35
- const { width, height } = style;
36
-
37
- if (!applyMaxSize || !isValidSize(width) || !isValidSize(height)) {
38
- return source;
39
- }
40
-
41
- return {
42
- ...source,
43
- maxWidth: normalize(width),
44
- maxHeight: normalize(height),
45
- };
46
- };
47
-
48
29
  export default function Image({
49
30
  style,
50
31
  uri,
@@ -82,10 +63,12 @@ export default function Image({
82
63
 
83
64
  const applyMaxSize = toBooleanWithDefaultFalse(otherProps?.applyMaxSize);
84
65
 
85
- const sourceWithMaxSizes = React.useMemo(
86
- () => withDimensions(getSourceWithMaxSizes(applyMaxSize, style, source)),
87
- [applyMaxSize, style?.height, style?.width, source?.uri, withDimensions]
88
- );
66
+ const sourceWithMaxSizes = useImageSize({
67
+ style,
68
+ applyMaxSize,
69
+ source,
70
+ withDimensions,
71
+ });
89
72
 
90
73
  if (isFailedToLoadUri(uri) || !isValidExternalSource(source)) {
91
74
  return <View style={style} />;
@@ -43,14 +43,16 @@ function Image({
43
43
  [source, error]
44
44
  );
45
45
 
46
+ const _source = shouldRenderImg
47
+ ? withDimensions(source)
48
+ : defaultPlaceHolderImage;
49
+
46
50
  return (
47
51
  // @ts-ignore
48
52
  <MemoizedImage
49
53
  style={style as ImageStyle}
50
54
  onError={() => setErrorState(true)}
51
- source={
52
- shouldRenderImg ? withDimensions(source) : defaultPlaceHolderImage
53
- }
55
+ source={_source?.uri ? _source : undefined}
54
56
  {...R.omit(["source"], otherProps)}
55
57
  />
56
58
  );
@@ -0,0 +1,65 @@
1
+ import * as React from "react";
2
+ import { ImageStyle } from "react-native";
3
+ import { toFiniteNumberWithDefault } from "@applicaster/zapp-react-native-utils/numberUtils";
4
+
5
+ type Source = {
6
+ uri: string;
7
+ };
8
+
9
+ type Props = Record<string, any> & {
10
+ style: ImageStyle;
11
+ uri?: string;
12
+ placeholderImage?: string;
13
+ entry?: ZappEntry | ZappFeed;
14
+ withDimensions: (source: Source) => Source;
15
+ };
16
+
17
+ const normalize = Math.ceil;
18
+
19
+ const isValidSize = (size: unknown): boolean =>
20
+ toFiniteNumberWithDefault(0, size) > 0;
21
+
22
+ type WidthDimensions = { aspectRatio?: number; width: number; height?: number };
23
+ type HeightDimensions = {
24
+ aspectRatio?: number;
25
+ width?: number;
26
+ height: number;
27
+ };
28
+ type FullDimensions = { aspectRatio?: number; width: number; height: number };
29
+ type Dimensions = WidthDimensions | HeightDimensions | FullDimensions;
30
+
31
+ const calculateDimensions = ({ aspectRatio, width, height }: Dimensions) => {
32
+ if (aspectRatio) {
33
+ return {
34
+ width: width || height * aspectRatio,
35
+ height: height || width / aspectRatio,
36
+ };
37
+ }
38
+
39
+ return { width, height };
40
+ };
41
+
42
+ const getSourceWithMaxSizes = (applyMaxSize, style, source) => {
43
+ const { width, height } = calculateDimensions(style);
44
+
45
+ if (!applyMaxSize || !isValidSize(width) || !isValidSize(height)) {
46
+ return source;
47
+ }
48
+
49
+ return {
50
+ ...source,
51
+ maxWidth: normalize(width),
52
+ maxHeight: normalize(height),
53
+ };
54
+ };
55
+
56
+ export default function useImageSize(props: Props) {
57
+ const { style, applyMaxSize, source, withDimensions } = props;
58
+
59
+ const sourceWithMaxSizes = React.useMemo(
60
+ () => withDimensions(getSourceWithMaxSizes(applyMaxSize, style, source)),
61
+ [applyMaxSize, style?.height, style?.width, source?.uri, withDimensions]
62
+ );
63
+
64
+ return sourceWithMaxSizes;
65
+ }
@@ -2,6 +2,7 @@ import React from "react";
2
2
  import { isVideoPreviewEnabled } from "@applicaster/zapp-react-native-ui-components/Components/MasterCell/utils";
3
3
  import { LiveImage } from "../LiveImage";
4
4
  import PureImage from "../Image";
5
+ import { useIsScreenActive } from "@applicaster/zapp-react-native-utils/reactHooks";
5
6
 
6
7
  type Props = {
7
8
  enable_video_preview: boolean;
@@ -9,7 +10,10 @@ type Props = {
9
10
  };
10
11
 
11
12
  export const ImageContainer = (props: Props) => {
12
- const Component = isVideoPreviewEnabled(props) ? LiveImage : PureImage;
13
+ const isActive = useIsScreenActive();
14
+
15
+ const Component =
16
+ isVideoPreviewEnabled(props) && isActive ? LiveImage : PureImage;
13
17
 
14
18
  return <Component {...props} />;
15
19
  };
@@ -18,6 +18,7 @@ type LiveImageProps = {
18
18
  imageKey?: string;
19
19
  state: "focused" | "unfocused" | "selected";
20
20
  audio_muted_by_default?: boolean;
21
+ uri: string; // cover image url
21
22
  };
22
23
 
23
24
  const DEFAULT_PLAYER_IDENTIFIER = "QuickBrickPlayerPlugin";
@@ -176,10 +177,23 @@ const getPlayerConfig = (player_screen_id, actionIdentifier) => {
176
177
  const positionAbsolute: ViewStyle = { position: "absolute" };
177
178
 
178
179
  const LiveImageComponent = (props: LiveImageProps) => {
179
- const { item, style, cellUUID, imageKey, actionIdentifier, state } = props;
180
+ const {
181
+ item,
182
+ style: componentStyle,
183
+ cellUUID,
184
+ imageKey,
185
+ actionIdentifier,
186
+ state,
187
+ } = props;
180
188
 
181
189
  const component = useUIComponentContext();
182
190
 
191
+ // Fix for blinking on state change
192
+ const style = React.useMemo(
193
+ () => ({ ...componentStyle, backgroundColor: "transparent" }),
194
+ [componentStyle]
195
+ );
196
+
183
197
  const componentType = React.useMemo(
184
198
  () => getPropComponentType(component),
185
199
  [component?.id]
@@ -209,10 +223,11 @@ const LiveImageComponent = (props: LiveImageProps) => {
209
223
 
210
224
  return (
211
225
  <>
212
- <Image {...props} />
226
+ <Image {...props} applyMaxSize />
213
227
  {isShouldRender && (
214
228
  <View style={[style, positionAbsolute]}>
215
229
  <PlayerLiveImage
230
+ uri={props.uri}
216
231
  playerId={cellUUID}
217
232
  playerPluginId={playerPluginId}
218
233
  screenConfig={screenConfig}
@@ -1,11 +1,12 @@
1
1
  import * as React from "react";
2
2
  import { isNil } from "ramda";
3
- import { Image as RNImage, ImageStyle, View } from "react-native";
3
+ import { ImageStyle, View } from "react-native";
4
4
  import {
5
5
  FIT_POSITION,
6
6
  IMAGE_SIZING_FIT,
7
7
  IMAGE_SIZING_FILL,
8
8
  } from "@applicaster/zapp-react-native-utils/manifestUtils/secondaryImage";
9
+ import { QBImage as Image } from "@applicaster/zapp-react-native-ui-components/Components/Image";
9
10
 
10
11
  import { useGetImageDimensions } from "./hooks";
11
12
  import {
@@ -59,7 +60,7 @@ const SecondaryImageComponent = (props: Props) => {
59
60
 
60
61
  return (
61
62
  <View style={style} onLayout={onAsyncRender}>
62
- <RNImage
63
+ <Image
63
64
  {...props}
64
65
  source={{ uri }}
65
66
  style={{
@@ -1,7 +1,6 @@
1
1
  import * as React from "react";
2
2
  import { Text } from "react-native";
3
3
  import { omit } from "ramda";
4
- import { isNilOrEmpty } from "@applicaster/zapp-react-native-utils/reactUtils/helpers";
5
4
  import {
6
5
  textTransform,
7
6
  dateFormat,
@@ -9,6 +8,9 @@ import {
9
8
 
10
9
  import { useTextLabel, withFocusedStyles } from "./hooks";
11
10
  import { withScaledLineHeight } from "./utils";
11
+ import { toNumber } from "@applicaster/zapp-react-native-utils/numberUtils";
12
+ import { MeasurementPortalContext } from "../../../MeasurmentsPortal";
13
+ import { isNilOrEmpty } from "@applicaster/zapp-react-native-utils/reactUtils/helpers";
12
14
 
13
15
  type Props = {
14
16
  style: any;
@@ -32,14 +34,24 @@ const _Text = ({
32
34
  ...otherProps
33
35
  }: Props) => {
34
36
  const _label = useTextLabel({ label, entry });
37
+ const isMeasurement = React.useContext(MeasurementPortalContext);
35
38
 
36
39
  if (isNilOrEmpty(_label)) {
37
40
  return null;
38
41
  }
39
42
 
43
+ // set maximum possible height for the text in case of measurement
44
+ const height =
45
+ isMeasurement && otherProps.numberOfLines && style.lineHeight
46
+ ? toNumber(otherProps.numberOfLines) * toNumber(style.lineHeight)
47
+ : undefined;
48
+
40
49
  return (
41
50
  <Text
42
- style={withScaledLineHeight(withFocusedStyles({ style, otherProps }))}
51
+ style={[
52
+ withScaledLineHeight(withFocusedStyles({ style, otherProps })),
53
+ { height },
54
+ ]}
43
55
  {...withoutLabel(otherProps)}
44
56
  >
45
57
  {dateTransformEnabled
@@ -23,7 +23,11 @@ export const Toggle = (props: Props) => {
23
23
  iosBackgroundColor,
24
24
  } = style;
25
25
 
26
- const isSelected = React.useMemo(() => (cellState === "selected"), [item?.id, cellState]);
26
+ const isSelected = React.useMemo(
27
+ () => cellState === "selected",
28
+ [item?.id, cellState]
29
+ );
30
+
27
31
  const actionContext = useActions(action?.identifier);
28
32
 
29
33
  const [actionState, setActionState] = useState(
@@ -39,6 +39,6 @@ describe("image with no source", () => {
39
39
  );
40
40
 
41
41
  const testInstance = testRenderer.root;
42
- expect(testInstance.findByType(Image).props.source).toEqual({ uri: "" });
42
+ expect(testInstance.findByType(Image).props.source).toBeUndefined();
43
43
  });
44
44
  });
@@ -1,11 +1,23 @@
1
1
  import React from "react";
2
- import { Text } from "react-native";
2
+ import RN from "react-native";
3
3
  import { Provider } from "react-redux";
4
4
  import TestRenderer from "react-test-renderer";
5
- import configureStore from "redux-mock-store";
6
- import CustomText from "../Text";
5
+ import configureStoreFn from "redux-mock-store";
7
6
 
8
- const mockStore = configureStore();
7
+ const mockUseIsRTL = jest.fn(() => true);
8
+ const mockGetIsRTL = jest.fn(() => true);
9
+ const mockUseLocalizedStrings = jest.fn(() => ({}));
10
+ const mockGetUILanguage = jest.fn(() => "he-HE");
11
+
12
+ jest.mock("@applicaster/zapp-react-native-utils/localizationUtils", () => ({
13
+ useIsRTL: mockUseIsRTL,
14
+ getIsRTL: mockGetIsRTL,
15
+ useLocalizedStrings: mockUseLocalizedStrings,
16
+ getUILanguage: mockGetUILanguage,
17
+ }));
18
+
19
+ const CustomText = require("../Text").default;
20
+ const mockStore = configureStoreFn();
9
21
 
10
22
  const defaultProps = {
11
23
  entry: {},
@@ -22,7 +34,7 @@ const getRenderedText = (label: string, store: any) => {
22
34
  </Provider>
23
35
  );
24
36
 
25
- return testRenderer.root.findByType(Text).props.children;
37
+ return testRenderer.root.findByType(RN.Text).props.children;
26
38
  };
27
39
 
28
40
  describe("RTL app: Hebrew text contains english word", () => {
@@ -34,7 +46,7 @@ describe("RTL app: Hebrew text contains english word", () => {
34
46
  remoteConfigurations: { localizations: { he: {} } },
35
47
  appData: {
36
48
  languageCode: "he",
37
- countryCode: "HE",
49
+ countryCode: "IL",
38
50
  },
39
51
  });
40
52
 
@@ -47,18 +59,22 @@ describe("RTL app: Hebrew text contains english word", () => {
47
59
 
48
60
  it("Hebrew text contains not first english word", () => {
49
61
  const renderedText = getRenderedText(textWithNotFirstEnglishWord, store);
50
-
51
62
  expect(renderedText).toEqual(textWithNotFirstEnglishWord);
52
63
  });
53
64
 
54
65
  it("Hebrew text doesn't contain english word", () => {
55
66
  const renderedText = getRenderedText(textWithoutEnglishWord, store);
56
-
57
67
  expect(renderedText).toEqual(textWithoutEnglishWord);
58
68
  });
59
69
  });
60
70
 
61
71
  describe("LTR app: English text", () => {
72
+ beforeAll(() => {
73
+ mockUseIsRTL.mockReturnValue(false);
74
+ mockGetIsRTL.mockReturnValue(false);
75
+ mockGetUILanguage.mockReturnValue("en-US");
76
+ });
77
+
62
78
  const englishText = "Test sentence";
63
79
 
64
80
  const store = mockStore({
@@ -71,7 +87,6 @@ describe("LTR app: English text", () => {
71
87
 
72
88
  it("English text", () => {
73
89
  const renderedText = getRenderedText(englishText, store);
74
-
75
90
  expect(renderedText).toEqual(englishText);
76
91
  });
77
92
  });
@@ -61,7 +61,10 @@ export function ButtonContainerView({
61
61
  <View style={applyRTLStylesIfNeeded(style, isRTL)}>
62
62
  {insertBetween(
63
63
  (index) => (
64
- <HorizontalSeparator key={index} width={horizontalGutter} />
64
+ <HorizontalSeparator
65
+ key={`separator_${index}`}
66
+ width={horizontalGutter}
67
+ />
65
68
  ),
66
69
  cloneElementsWithIds(buttonIds, filteredChildren)
67
70
  )}
@@ -29,7 +29,10 @@ export function ButtonContainerView({
29
29
  <View style={style}>
30
30
  {insertBetween(
31
31
  (index) => (
32
- <HorizontalSeparator key={index} width={horizontalGutter} />
32
+ <HorizontalSeparator
33
+ key={`separator_${index}`}
34
+ width={horizontalGutter}
35
+ />
33
36
  ),
34
37
  filteredChildren
35
38
  )}
@@ -51,9 +51,7 @@ export const CollapsibleTextContainer = ({
51
51
  func: (data) => {
52
52
  return getColorFromData({
53
53
  data,
54
- getValue,
55
- key: "background_color",
56
- defaultColor: additionalStyles.backgroundColor,
54
+ valueFromLayout: additionalStyles.backgroundColor,
57
55
  });
58
56
  },
59
57
  propName: "backgroundColor",
@@ -53,7 +53,7 @@ export const useAsyncRendering = (viewRef: React.RefObject<View>): Return => {
53
53
  measure(viewRef?.current, ({ width, height }) => {
54
54
  setDimensions({
55
55
  width,
56
- height,
56
+ height: Math.ceil(height),
57
57
  });
58
58
  });
59
59
  })
@@ -125,13 +125,24 @@ export const useFilterChildren = <
125
125
 
126
126
  const filteredChildren = children.filter((child) => {
127
127
  const item = child.props.item;
128
- const action = actions.actions[child.props.pluginIdentifier];
128
+ const actionIdentifier = child.props.pluginIdentifier;
129
+ const action = actions.actions[actionIdentifier];
130
+
129
131
  // context value of specific plugin
130
- const currentValue = action.module.context._currentValue;
132
+ if (action?.module && action.module.context) {
133
+ const currentValue = action.module.context._currentValue;
134
+
135
+ return currentValue?.isActionAvailable
136
+ ? currentValue.isActionAvailable(item)
137
+ : true;
138
+ }
139
+
140
+ masterCellLogger.error({
141
+ message: `Action plugin for ${actionIdentifier} could not be found, check the configuration of your action button`,
142
+ data: { item, action: child.props.pluginIdentifier },
143
+ });
131
144
 
132
- return currentValue?.isActionAvailable
133
- ? currentValue.isActionAvailable(item)
134
- : true;
145
+ return false;
135
146
  });
136
147
 
137
148
  return filteredChildren;
@@ -285,8 +296,8 @@ export const useBehaviorUpdate = (behavior: Behavior) => {
285
296
  }
286
297
  }
287
298
  }, [behavior]);
288
-
289
- useEffect(() => {
299
+
300
+ useEffect(() => {
290
301
  if (!behavior) {
291
302
  return;
292
303
  }