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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (197) hide show
  1. package/Components/AnimatedInOut/index.tsx +69 -26
  2. package/Components/BaseFocusable/index.ios.ts +12 -2
  3. package/Components/Cell/Cell.tsx +14 -3
  4. package/Components/Cell/CellWithFocusable.tsx +9 -0
  5. package/Components/Cell/FocusableWrapper.tsx +3 -0
  6. package/Components/Cell/TvOSCellComponent.tsx +25 -6
  7. package/Components/Focusable/Focusable.tsx +4 -2
  8. package/Components/Focusable/FocusableTvOS.tsx +18 -1
  9. package/Components/Focusable/__tests__/__snapshots__/FocusableTvOS.test.tsx.snap +1 -0
  10. package/Components/FocusableGroup/FocusableTvOS.tsx +32 -1
  11. package/Components/GeneralContentScreen/GeneralContentScreen.tsx +39 -28
  12. package/Components/GeneralContentScreen/__tests__/GeneralContentScreen.test.tsx +104 -0
  13. package/Components/GeneralContentScreen/utils/__tests__/getScreenDataSource.test.ts +19 -0
  14. package/Components/GeneralContentScreen/utils/__tests__/useCurationAPI.test.js +1 -1
  15. package/Components/GeneralContentScreen/utils/getScreenDataSource.ts +9 -0
  16. package/Components/GeneralContentScreen/utils/useCurationAPI.ts +22 -6
  17. package/Components/HandlePlayable/HandlePlayable.tsx +33 -94
  18. package/Components/HandlePlayable/const.ts +3 -0
  19. package/Components/HandlePlayable/utils.ts +105 -0
  20. package/Components/HookRenderer/HookRenderer.tsx +40 -10
  21. package/Components/HookRenderer/__tests__/HookRenderer.test.tsx +60 -0
  22. package/Components/Layout/TV/LayoutBackground.tsx +5 -2
  23. package/Components/Layout/TV/NavBarContainer.tsx +1 -10
  24. package/Components/Layout/TV/ScreenContainer.tsx +2 -6
  25. package/Components/Layout/TV/__tests__/__snapshots__/NavBarContainer.test.tsx.snap +7 -12
  26. package/Components/Layout/TV/__tests__/__snapshots__/ScreenContainer.test.tsx.snap +7 -12
  27. package/Components/Layout/TV/index.tsx +3 -4
  28. package/Components/Layout/TV/index.web.tsx +3 -4
  29. package/Components/LinkHandler/LinkHandler.tsx +2 -2
  30. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/__tests__/model.test.ts +80 -0
  31. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/__tests__/placement.test.ts +187 -0
  32. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/__tests__/selectors.test.ts +45 -0
  33. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/__tests__/style.test.ts +49 -0
  34. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/model.ts +47 -0
  35. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/placement.ts +170 -0
  36. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/selectors.ts +26 -0
  37. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/style.ts +29 -0
  38. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/types.ts +37 -0
  39. package/Components/MasterCell/DefaultComponents/BorderContainerView/__tests__/index.test.tsx +16 -1
  40. package/Components/MasterCell/DefaultComponents/BorderContainerView/index.tsx +30 -2
  41. package/Components/MasterCell/DefaultComponents/Button.tsx +0 -15
  42. package/Components/MasterCell/DefaultComponents/Image/Image.android.tsx +5 -1
  43. package/Components/MasterCell/DefaultComponents/Image/Image.ios.tsx +11 -3
  44. package/Components/MasterCell/DefaultComponents/Image/Image.web.tsx +9 -1
  45. package/Components/MasterCell/DefaultComponents/Image/hooks/useImage.ts +15 -14
  46. package/Components/MasterCell/DefaultComponents/LiveImage/__tests__/prepareEntry.test.ts +352 -0
  47. package/Components/MasterCell/DefaultComponents/LiveImage/executePreloadHooks.ts +136 -0
  48. package/Components/MasterCell/DefaultComponents/LiveImage/index.tsx +43 -22
  49. package/Components/MasterCell/DefaultComponents/PressableView.tsx +261 -0
  50. package/Components/MasterCell/DefaultComponents/SecondaryImage/Image.tsx +40 -39
  51. package/Components/MasterCell/DefaultComponents/SecondaryImage/__tests__/Image.test.tsx +95 -0
  52. package/Components/MasterCell/DefaultComponents/SecondaryImage/__tests__/__snapshots__/Image.test.tsx.snap +86 -0
  53. package/Components/MasterCell/DefaultComponents/SecondaryImage/__tests__/index.test.ts +141 -0
  54. package/Components/MasterCell/DefaultComponents/SecondaryImage/hooks/__tests__/useGetImageDimensions.test.ts +7 -6
  55. package/Components/MasterCell/DefaultComponents/SecondaryImage/index.ts +1 -1
  56. package/Components/MasterCell/DefaultComponents/Text/index.tsx +10 -14
  57. package/Components/MasterCell/DefaultComponents/index.ts +2 -0
  58. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/Asset.ts +42 -0
  59. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/Button.ts +127 -0
  60. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/ButtonContainerView.ts +23 -0
  61. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/Spacer.ts +16 -0
  62. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/TextLabel.ts +67 -0
  63. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/TextLabelsContainer.ts +32 -0
  64. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/__tests__/PressableView.test.tsx +195 -0
  65. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/__tests__/builders.test.ts +140 -0
  66. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/__tests__/index.test.ts +222 -0
  67. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/helpers.ts +105 -0
  68. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/index.ts +104 -0
  69. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/utils/__tests__/insertButtons.test.ts +118 -0
  70. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/utils/index.ts +73 -0
  71. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/__tests__/index.test.ts +86 -0
  72. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/index.ts +35 -48
  73. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/utils/__tests__/getPluginIdentifier.test.ts +115 -29
  74. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/utils/index.ts +39 -144
  75. package/Components/MasterCell/elementMapper.tsx +1 -0
  76. package/Components/MasterCell/hoc/__tests__/withAsyncRender.test.tsx +219 -0
  77. package/Components/MasterCell/hoc/withAsyncRender.tsx +9 -7
  78. package/Components/MasterCell/index.tsx +2 -0
  79. package/Components/MasterCell/utils/__tests__/resolveColor.test.js +82 -3
  80. package/Components/MasterCell/utils/index.ts +61 -31
  81. package/Components/MeasurmentsPortal/MeasurementsPortal.tsx +102 -87
  82. package/Components/MeasurmentsPortal/__tests__/MeasurementsPortal.test.tsx +355 -0
  83. package/Components/OfflineHandler/NotificationView/NotificationView.lg.tsx +17 -9
  84. package/Components/OfflineHandler/NotificationView/NotificationView.samsung.tsx +16 -8
  85. package/Components/OfflineHandler/NotificationView/NotificationView.tsx +2 -2
  86. package/Components/OfflineHandler/NotificationView/__tests__/index.test.tsx +17 -18
  87. package/Components/OfflineHandler/NotificationView/utils.ts +34 -0
  88. package/Components/OfflineHandler/__tests__/index.test.tsx +27 -18
  89. package/Components/PlayerContainer/PlayerContainer.tsx +44 -65
  90. package/Components/PlayerImageBackground/index.tsx +3 -22
  91. package/Components/PreloaderWrapper/__tests__/index.test.tsx +26 -0
  92. package/Components/PreloaderWrapper/index.tsx +15 -0
  93. package/Components/River/ComponentsMap/ComponentsMap.tsx +18 -16
  94. package/Components/River/ComponentsMap/hooks/__tests__/useLoadingState.test.ts +1 -1
  95. package/Components/River/RefreshControl.tsx +19 -82
  96. package/Components/River/River.tsx +9 -82
  97. package/Components/River/RiverItem.tsx +26 -20
  98. package/Components/River/TV/River.tsx +31 -14
  99. package/Components/River/TV/index.tsx +8 -4
  100. package/Components/River/TV/utils/__tests__/toStringOrEmpty.test.ts +30 -0
  101. package/Components/River/TV/utils/index.ts +4 -0
  102. package/Components/River/TV/withFocusableGroupForContent.tsx +71 -0
  103. package/Components/River/__tests__/__snapshots__/componentsMap.test.js.snap +2 -0
  104. package/Components/River/__tests__/componentsMap.test.js +38 -0
  105. package/Components/River/hooks/__tests__/usePullToRefresh.test.ts +132 -0
  106. package/Components/River/hooks/index.ts +1 -0
  107. package/Components/River/hooks/usePullToRefresh.ts +51 -0
  108. package/Components/Screen/TV/index.web.tsx +4 -2
  109. package/Components/Screen/__tests__/Screen.test.tsx +66 -42
  110. package/Components/Screen/__tests__/__snapshots__/Screen.test.tsx.snap +68 -44
  111. package/Components/Screen/hooks.ts +75 -6
  112. package/Components/Screen/index.tsx +9 -4
  113. package/Components/Screen/navigationHandler.ts +49 -24
  114. package/Components/Screen/orientationHandler.ts +10 -13
  115. package/Components/ScreenFeedLoader/ScreenFeedLoader.tsx +46 -0
  116. package/Components/ScreenFeedLoader/__tests__/ScreenFeedLoader.test.tsx +94 -0
  117. package/Components/ScreenFeedLoader/index.ts +1 -0
  118. package/Components/ScreenResolver/__tests__/screenResolver.test.js +24 -0
  119. package/Components/ScreenResolver/hooks/index.ts +3 -0
  120. package/Components/ScreenResolver/hooks/useGetComponent.ts +15 -0
  121. package/Components/ScreenResolver/hooks/useScreenComponentResolver.tsx +90 -0
  122. package/Components/ScreenResolver/index.tsx +15 -111
  123. package/Components/ScreenResolver/utils/__tests__/getScreenTypeProps.test.ts +45 -0
  124. package/Components/ScreenResolver/utils/getScreenTypeProps.ts +43 -0
  125. package/Components/ScreenResolver/utils/index.ts +1 -0
  126. package/Components/ScreenResolver/withDefaultScreenContext.tsx +16 -0
  127. package/Components/ScreenResolverFeedProvider/ScreenResolverFeedProvider.tsx +25 -0
  128. package/Components/ScreenResolverFeedProvider/__tests__/ScreenResolverFeedProvider.test.tsx +44 -0
  129. package/Components/ScreenResolverFeedProvider/index.ts +1 -0
  130. package/Components/ScreenRevealManager/ScreenRevealManager.ts +40 -8
  131. package/Components/ScreenRevealManager/__tests__/ScreenRevealManager.test.ts +86 -69
  132. package/Components/ScreenRevealManager/withScreenRevealManager.tsx +44 -26
  133. package/Components/Tabs/TV/Tabs.tsx +20 -3
  134. package/Components/Tabs/TabContent.tsx +7 -4
  135. package/Components/TopCutoffOverlay/hooks/__tests__/useMarginTop.test.ts +130 -0
  136. package/Components/TopCutoffOverlay/hooks/index.ts +1 -0
  137. package/Components/TopCutoffOverlay/hooks/useMarginTop.ts +59 -0
  138. package/Components/TopCutoffOverlay/index.tsx +55 -0
  139. package/Components/Transitioner/Scene.tsx +10 -3
  140. package/Components/Transitioner/index.js +3 -3
  141. package/Components/VideoLive/LiveImageManager.ts +199 -54
  142. package/Components/VideoLive/PlayerLiveImageComponent.tsx +31 -33
  143. package/Components/VideoLive/__tests__/PlayerLiveImageComponent.test.tsx +2 -17
  144. package/Components/VideoLive/__tests__/__snapshots__/PlayerLiveImageComponent.test.tsx.snap +1 -0
  145. package/Components/VideoModal/ModalAnimation/ModalAnimationContext.tsx +118 -171
  146. package/Components/VideoModal/ModalAnimation/index.ts +2 -13
  147. package/Components/VideoModal/ModalAnimation/utils.ts +1 -327
  148. package/Components/VideoModal/PlayerWrapper.tsx +14 -88
  149. package/Components/VideoModal/VideoModal.tsx +1 -5
  150. package/Components/VideoModal/__tests__/PlayerWrapper.test.tsx +1 -0
  151. package/Components/VideoModal/hooks/__tests__/useDelayedPlayerDetails.test.ts +15 -7
  152. package/Components/VideoModal/hooks/useModalSize.ts +10 -5
  153. package/Components/VideoModal/playerWrapperStyle.ts +70 -0
  154. package/Components/VideoModal/playerWrapperUtils.ts +91 -0
  155. package/Components/VideoModal/utils.ts +19 -9
  156. package/Components/Viewport/ViewportAware/__tests__/viewportAware.test.js +0 -2
  157. package/Components/Viewport/ViewportAware/index.tsx +16 -7
  158. package/Components/Viewport/ViewportEvents/__tests__/viewportEvents.test.js +1 -1
  159. package/Components/ZappUIComponent/index.tsx +12 -6
  160. package/Components/default-cell-renderer/viewTrees/mobile/index.ts +0 -3
  161. package/Components/index.js +1 -1
  162. package/Contexts/ScreenContext/__tests__/index.test.tsx +57 -0
  163. package/Contexts/ScreenContext/index.tsx +71 -19
  164. package/Contexts/ScreenTrackedViewPositionsContext/__tests__/index.test.tsx +1 -1
  165. package/Contexts/ZappHookModalContext/index.tsx +37 -61
  166. package/Contexts/ZappPipesContext/ZappPipesContextFactory.tsx +18 -7
  167. package/Contexts/index.ts +0 -2
  168. package/Decorators/Analytics/index.tsx +6 -5
  169. package/Decorators/ConfigurationWrapper/__tests__/__snapshots__/withConfigurationProvider.test.tsx.snap +1 -0
  170. package/Decorators/ConfigurationWrapper/const.ts +1 -0
  171. package/Decorators/ZappPipesDataConnector/ResolverSelector.tsx +25 -7
  172. package/Decorators/ZappPipesDataConnector/__tests__/ResolverSelector.test.tsx +212 -5
  173. package/Decorators/ZappPipesDataConnector/__tests__/UrlFeedResolver.test.tsx +39 -21
  174. package/Decorators/ZappPipesDataConnector/__tests__/zappPipesDataConnector.test.js +1 -1
  175. package/Decorators/ZappPipesDataConnector/index.tsx +2 -2
  176. package/Decorators/ZappPipesDataConnector/resolvers/StaticFeedResolver.tsx +1 -1
  177. package/Decorators/ZappPipesDataConnector/resolvers/UrlFeedResolver.tsx +18 -7
  178. package/Helpers/DataSourceHelper/__tests__/itemLimitForData.test.ts +80 -0
  179. package/Helpers/DataSourceHelper/index.ts +19 -0
  180. package/events/index.ts +3 -0
  181. package/events/scrollEndReached.ts +15 -0
  182. package/index.d.ts +7 -0
  183. package/package.json +6 -5
  184. package/Components/MasterCell/DefaultComponents/Text/utils/__tests__/withAdjustedLineHeight.test.ts +0 -46
  185. package/Components/MasterCell/DefaultComponents/Text/utils/index.ts +0 -21
  186. package/Components/PlayerContainer/ErrorDisplay/ErrorDisplay.tsx +0 -57
  187. package/Components/PlayerContainer/ErrorDisplay/index.ts +0 -9
  188. package/Components/River/TV/withTVEventHandler.tsx +0 -27
  189. package/Components/VideoModal/ModalAnimation/AnimatedPlayerModalWrapper.tsx +0 -60
  190. package/Components/VideoModal/ModalAnimation/AnimatedScrollModal.tsx +0 -417
  191. package/Components/VideoModal/ModalAnimation/AnimatedScrollModal.web.tsx +0 -294
  192. package/Components/VideoModal/ModalAnimation/AnimatedVideoPlayerComponent.tsx +0 -176
  193. package/Components/VideoModal/ModalAnimation/AnimatedVideoPlayerComponent.web.tsx +0 -93
  194. package/Components/VideoModal/ModalAnimation/AnimationComponent.tsx +0 -500
  195. package/Components/VideoModal/ModalAnimation/__tests__/getMoveUpValue.test.ts +0 -108
  196. package/Helpers/DataSourceHelper/index.js +0 -19
  197. /package/Components/HookRenderer/{index.tsx → index.ts} +0 -0
@@ -0,0 +1,104 @@
1
+ import { compact } from "@applicaster/zapp-react-native-utils/cellUtils";
2
+ import {
3
+ insertButtonsBetweenLabels,
4
+ insertButtonsBetweenLabelContainers,
5
+ mobileOverImagePositionStyles,
6
+ } from "./utils";
7
+ import { Button } from "./Button";
8
+ import { ButtonContainerView } from "./ButtonContainerView";
9
+ import { buildActionButtonsModel } from "../../ActionButtonsCore/model";
10
+
11
+ const CONTAINER_PREFIX = "mobile_buttons_container";
12
+ const BUTTON_PREFIX = "mobile_button";
13
+
14
+ type MobileActionButtonsProps = {
15
+ value: (key: string) => unknown;
16
+ configuration: Record<string, unknown>;
17
+ placement: "labels" | "over_image"; // Indicates where the component was placed, to apply specific display rules
18
+ };
19
+
20
+ export const MobileActionButtons = ({
21
+ value,
22
+ configuration,
23
+ placement,
24
+ }: MobileActionButtonsProps) => {
25
+ const position = value(`${CONTAINER_PREFIX}_position`);
26
+
27
+ if (placement === "labels" && position === "over_image") {
28
+ return null;
29
+ }
30
+
31
+ if (placement === "over_image" && position !== "over_image") {
32
+ return null;
33
+ }
34
+
35
+ const model = buildActionButtonsModel({
36
+ configuration,
37
+ value,
38
+ containerPrefix: CONTAINER_PREFIX,
39
+ buttonPrefix: BUTTON_PREFIX,
40
+ });
41
+
42
+ if (!model) {
43
+ return null;
44
+ }
45
+
46
+ const elements = compact(
47
+ model.buttons.map(({ renderIndex, specificPrefix, stylePrefix }) => {
48
+ const isNotLast = renderIndex < model.buttons.length - 1;
49
+
50
+ const {
51
+ container: { stacking, verticalGutter, horizontalGutter },
52
+ } = model;
53
+
54
+ const isVertical = stacking === "vertical";
55
+ const gutter = isVertical ? verticalGutter : horizontalGutter;
56
+
57
+ const spacingStyle = {
58
+ [isVertical ? "marginBottom" : "marginRight"]: isNotLast ? gutter : 0,
59
+ };
60
+
61
+ return Button({
62
+ index: renderIndex,
63
+ value,
64
+ stylePrefix,
65
+ specificPrefix,
66
+ spacingStyle,
67
+ });
68
+ })
69
+ );
70
+
71
+ return ButtonContainerView({
72
+ style: {
73
+ alignItems: "center",
74
+ ...(placement === "over_image"
75
+ ? {
76
+ position: "absolute",
77
+ zIndex: 10,
78
+ top: 0,
79
+ left: 0,
80
+ right: 0,
81
+ bottom: 0,
82
+ ...mobileOverImagePositionStyles(
83
+ value(`${CONTAINER_PREFIX}_over_image_position`) as string
84
+ ),
85
+ }
86
+ : {}),
87
+ },
88
+ contentStyle: {
89
+ flexDirection: model.container.stacking === "vertical" ? "column" : "row",
90
+ alignSelf:
91
+ placement !== "over_image"
92
+ ? model.container.horizontalAlign
93
+ : undefined,
94
+ alignItems: model.container.horizontalAlign,
95
+ marginTop: model.container.margins.top,
96
+ marginRight: model.container.margins.right,
97
+ marginBottom: model.container.margins.bottom,
98
+ marginLeft: model.container.margins.left,
99
+ },
100
+ elements,
101
+ });
102
+ };
103
+
104
+ export { insertButtonsBetweenLabels, insertButtonsBetweenLabelContainers };
@@ -0,0 +1,118 @@
1
+ import {
2
+ insertButtonsBetweenLabelContainers,
3
+ insertButtonsBetweenLabels,
4
+ mobileOverImagePositionStyles,
5
+ } from "..";
6
+
7
+ describe("mobile action insertion helpers", () => {
8
+ const labels = [{ name: "text_label_1" }, { name: "text_label_2" }];
9
+ const buttons = { type: "View", name: "buttons" };
10
+
11
+ it("inserts label buttons below target label", () => {
12
+ const result = insertButtonsBetweenLabels(
13
+ { mobile_buttons_container_position: "below_text_label_1" },
14
+ buttons,
15
+ labels
16
+ );
17
+
18
+ expect(result).toEqual([labels[0], buttons, labels[1]]);
19
+ });
20
+
21
+ it("returns labels unchanged when the flat-label target is unknown", () => {
22
+ const result = insertButtonsBetweenLabels(
23
+ { mobile_buttons_container_position: "unknown" },
24
+ buttons,
25
+ labels
26
+ );
27
+
28
+ expect(result).toEqual([labels[0], labels[1]]);
29
+ });
30
+
31
+ it("inserts label-container buttons below nested target", () => {
32
+ const labelContainers = [
33
+ {
34
+ elements: [{ elements: [{ name: "top_text_label_1" }] }],
35
+ },
36
+ ];
37
+
38
+ const result = insertButtonsBetweenLabelContainers(
39
+ { mobile_buttons_container_position: "top_text_label_1" },
40
+ buttons,
41
+ labelContainers
42
+ );
43
+
44
+ expect(result).toEqual([
45
+ {
46
+ elements: [{ elements: [{ name: "top_text_label_1" }, buttons] }],
47
+ },
48
+ ]);
49
+ });
50
+
51
+ it("inserts label-container buttons below direct target in two-level structure", () => {
52
+ const labelContainers = [
53
+ {
54
+ elements: [{ name: "top_text_label_1" }],
55
+ },
56
+ ];
57
+
58
+ const result = insertButtonsBetweenLabelContainers(
59
+ { mobile_buttons_container_position: "top_text_label_1" },
60
+ buttons,
61
+ labelContainers
62
+ );
63
+
64
+ expect(result).toEqual([
65
+ {
66
+ elements: [{ name: "top_text_label_1" }, buttons],
67
+ },
68
+ ]);
69
+ });
70
+
71
+ it("appends container buttons into the last container when target is unknown", () => {
72
+ const labelContainers = [
73
+ {
74
+ elements: [{ elements: [{ name: "top_text_label_1" }] }],
75
+ },
76
+ {
77
+ elements: [{ elements: [{ name: "bottom_text_label_1" }] }],
78
+ },
79
+ ];
80
+
81
+ const result = insertButtonsBetweenLabelContainers(
82
+ { mobile_buttons_container_position: "unknown" },
83
+ buttons,
84
+ labelContainers
85
+ );
86
+
87
+ expect(result).toEqual([
88
+ labelContainers[0],
89
+ {
90
+ elements: [...labelContainers[1].elements, buttons],
91
+ },
92
+ ]);
93
+ });
94
+ });
95
+
96
+ describe("mobileOverImagePositionStyles", () => {
97
+ it("maps bottom left anchor to absolute positioning", () => {
98
+ const result = mobileOverImagePositionStyles("bottom_left");
99
+
100
+ expect(result).toEqual({
101
+ justifyContent: "flex-end",
102
+ alignItems: "flex-start",
103
+ });
104
+ });
105
+
106
+ it("maps center anchor to absolute center positioning", () => {
107
+ const result = mobileOverImagePositionStyles("center");
108
+
109
+ expect(result).toEqual({
110
+ justifyContent: "center",
111
+ alignItems: "center",
112
+ top: 0,
113
+ left: 0,
114
+ right: 0,
115
+ bottom: 0,
116
+ });
117
+ });
118
+ });
@@ -0,0 +1,73 @@
1
+ import {
2
+ insertBetweenLabelContainers,
3
+ insertBetweenLabels,
4
+ } from "../../../ActionButtonsCore/placement";
5
+
6
+ export const insertButtonsBetweenLabels = (
7
+ configuration: Record<string, unknown>,
8
+ buttons,
9
+ labels = []
10
+ ) =>
11
+ insertBetweenLabels(
12
+ {
13
+ position: configuration?.mobile_buttons_container_position as
14
+ | string
15
+ | undefined,
16
+ allowOnTop: false,
17
+ appendWhenMissing: false,
18
+ },
19
+ buttons,
20
+ labels // "text_label_1", "text_label_2", "text_label_3", "text_label_4"
21
+ );
22
+
23
+ export const insertButtonsBetweenLabelContainers = (
24
+ configuration: Record<string, unknown>,
25
+ buttons,
26
+ labelContainers = []
27
+ ) =>
28
+ insertBetweenLabelContainers(
29
+ {
30
+ position: configuration?.mobile_buttons_container_position as
31
+ | string
32
+ | undefined,
33
+ allowOnTop: false,
34
+ appendWhenMissing: true,
35
+ },
36
+ buttons,
37
+ labelContainers // top_label_1, top_label_2, bottom_label_1, bottom_label_2, etc.
38
+ );
39
+
40
+ export const mobileOverImagePositionStyles = (position: string) => {
41
+ switch (position) {
42
+ case "top_left":
43
+ return {
44
+ justifyContent: "flex-start",
45
+ alignItems: "flex-start",
46
+ };
47
+ case "top_right":
48
+ return {
49
+ justifyContent: "flex-start",
50
+ alignItems: "flex-end",
51
+ };
52
+ case "bottom_left":
53
+ return {
54
+ justifyContent: "flex-end",
55
+ alignItems: "flex-start",
56
+ };
57
+ case "bottom_right":
58
+ return {
59
+ justifyContent: "flex-end",
60
+ alignItems: "flex-end",
61
+ };
62
+ case "center":
63
+ default:
64
+ return {
65
+ justifyContent: "center",
66
+ alignItems: "center",
67
+ top: 0,
68
+ left: 0,
69
+ right: 0,
70
+ bottom: 0,
71
+ };
72
+ }
73
+ };
@@ -0,0 +1,86 @@
1
+ import { TvActionButtons } from "..";
2
+
3
+ describe("TvActionButtons", () => {
4
+ const baseConfiguration = {
5
+ tv_buttons_container_buttons_enabled: true,
6
+ tv_buttons_container_align: "middle",
7
+ tv_buttons_container_margin_top: 1,
8
+ tv_buttons_container_margin_right: 2,
9
+ tv_buttons_container_margin_bottom: 3,
10
+ tv_buttons_container_margin_left: 4,
11
+ tv_buttons_container_horizontal_gutter: 10,
12
+ tv_buttons_container_independent_styles: false,
13
+ tv_buttons_button_1_button_enabled: true,
14
+ tv_buttons_button_1_assign_action: "action_1",
15
+ tv_buttons_button_1_background_padding_top: 11,
16
+ tv_buttons_button_1_background_padding_right: 12,
17
+ tv_buttons_button_1_background_padding_bottom: 13,
18
+ tv_buttons_button_1_background_padding_left: 14,
19
+ tv_buttons_button_3_button_enabled: true,
20
+ tv_buttons_button_3_assign_action: "action_3",
21
+ tv_buttons_button_3_background_padding_top: 31,
22
+ tv_buttons_button_3_background_padding_right: 32,
23
+ tv_buttons_button_3_background_padding_bottom: 33,
24
+ tv_buttons_button_3_background_padding_left: 34,
25
+ };
26
+
27
+ const platformValue = jest.fn();
28
+
29
+ it("renders sparse enabled slots with slot-based action lookup and ids", () => {
30
+ const value = (key) => baseConfiguration[key];
31
+
32
+ const result = TvActionButtons({
33
+ value,
34
+ platformValue,
35
+ configuration: baseConfiguration,
36
+ state: "focused",
37
+ skipButtons: false,
38
+ });
39
+
40
+ expect(result.style.justifyContent).toBe("center");
41
+ expect(result.additionalProps.buttonsCount).toBe(2);
42
+ expect(result.elements).toHaveLength(2);
43
+
44
+ expect(result.elements[0].additionalProps.pluginIdentifier).toBe(
45
+ "action_1"
46
+ );
47
+
48
+ expect(result.elements[0].additionalProps.suffixId).toBe(
49
+ "tv_buttons_button_1"
50
+ );
51
+
52
+ expect(result.elements[0].additionalProps.preferredFocus).toBe(true);
53
+
54
+ expect(result.elements[1].additionalProps.pluginIdentifier).toBe(
55
+ "action_3"
56
+ );
57
+
58
+ expect(result.elements[1].additionalProps.suffixId).toBe(
59
+ "tv_buttons_button_3"
60
+ );
61
+
62
+ expect(result.elements[1].additionalProps.preferredFocus).toBe(false);
63
+ });
64
+
65
+ it("keeps shared button styles from slot 1 while preserving slot 3 identity", () => {
66
+ const value = (key) => baseConfiguration[key];
67
+
68
+ const result = TvActionButtons({
69
+ value,
70
+ platformValue,
71
+ configuration: baseConfiguration,
72
+ state: "focused",
73
+ skipButtons: false,
74
+ });
75
+
76
+ expect(result.elements[1].style.paddingTop).toBe(11);
77
+
78
+ expect(result.elements[1].additionalProps.pluginIdentifier).toBe(
79
+ "action_3"
80
+ );
81
+
82
+ expect(result.elements[1].additionalProps.suffixId).toBe(
83
+ "tv_buttons_button_3"
84
+ );
85
+ });
86
+ });
@@ -1,12 +1,6 @@
1
- import { times } from "@applicaster/zapp-react-native-utils/utils";
2
- import { toNumberWithDefaultZero } from "@applicaster/zapp-react-native-utils/numberUtils";
3
-
4
1
  import { Button } from "./Button";
5
- import {
6
- getButtonsCount,
7
- getPluginIdentifier,
8
- mapSelfAlignment,
9
- } from "./utils";
2
+ import { memoizedGetPluginIdentifier } from "./utils";
3
+ import { buildActionButtonsModel } from "../../ActionButtonsCore/model";
10
4
 
11
5
  import { compact } from "@applicaster/zapp-react-native-utils/cellUtils";
12
6
  import { PREFIX, BUTTON_PREFIX } from "./const";
@@ -16,11 +10,9 @@ export {
16
10
  insertButtonsBetweenLabelContainers,
17
11
  } from "./utils";
18
12
 
19
- const buttonId = (index: number) => `${BUTTON_PREFIX}_${index}`;
20
-
21
13
  type Props = {
22
- value: Function;
23
- platformValue: Function;
14
+ value: (key: string) => unknown;
15
+ platformValue: (key: string) => unknown;
24
16
  configuration: Record<string, unknown>;
25
17
  state: string;
26
18
  skipButtons: boolean;
@@ -33,58 +25,53 @@ export const TvActionButtons = ({
33
25
  state,
34
26
  skipButtons,
35
27
  }: Props) => {
36
- if (skipButtons || !value(`${PREFIX}_container_buttons_enabled`)) {
28
+ if (skipButtons) {
37
29
  return null;
38
30
  }
39
31
 
40
- const buttonsCount = getButtonsCount(configuration, PREFIX);
32
+ const model = buildActionButtonsModel({
33
+ configuration,
34
+ value,
35
+ containerPrefix: `${PREFIX}_container`,
36
+ buttonPrefix: BUTTON_PREFIX,
37
+ });
41
38
 
42
- if (buttonsCount <= 0) {
39
+ if (!model) {
43
40
  return null;
44
41
  }
45
42
 
46
- const independentStyles = value(`${PREFIX}_container_independent_styles`);
47
-
48
43
  return {
49
44
  type: "ButtonContainerView",
50
45
  style: {
51
46
  flexDirection: "row",
52
-
53
- justifyContent: mapSelfAlignment(value(`${PREFIX}_container_align`)),
54
-
55
- marginTop: toNumberWithDefaultZero(
56
- value(`${PREFIX}_container_margin_top`)
57
- ),
58
- marginRight: toNumberWithDefaultZero(
59
- value(`${PREFIX}_container_margin_right`)
60
- ),
61
- marginBottom: toNumberWithDefaultZero(
62
- value(`${PREFIX}_container_margin_bottom`)
63
- ),
64
- marginLeft: toNumberWithDefaultZero(
65
- value(`${PREFIX}_container_margin_left`)
66
- ),
47
+ justifyContent: model.container.horizontalAlign,
48
+ marginTop: model.container.margins.top,
49
+ marginRight: model.container.margins.right,
50
+ marginBottom: model.container.margins.bottom,
51
+ marginLeft: model.container.margins.left,
67
52
  },
68
53
  additionalProps: {
69
- horizontalGutter: toNumberWithDefaultZero(
70
- value(`${PREFIX}_container_horizontal_gutter`)
71
- ),
54
+ horizontalGutter: model.container.horizontalGutter,
72
55
  state,
73
- buttonsCount,
56
+ buttonsCount: model.buttonsCount,
74
57
  },
75
58
  elements: compact(
76
- times((index) => {
77
- const prefixSpecificButton = buttonId(index + 1);
78
-
79
- return Button({
80
- prefix: independentStyles ? prefixSpecificButton : buttonId(1),
81
- value,
82
- platformValue,
83
- pluginIdentifier: getPluginIdentifier(configuration, PREFIX, index),
84
- suffixId: prefixSpecificButton,
85
- preferredFocus: index === 0,
86
- });
87
- }, buttonsCount)
59
+ model.buttons.map(
60
+ ({ slot, renderIndex, specificPrefix, stylePrefix }) => {
61
+ return Button({
62
+ prefix: stylePrefix,
63
+ value,
64
+ platformValue,
65
+ pluginIdentifier: memoizedGetPluginIdentifier(
66
+ configuration,
67
+ PREFIX,
68
+ slot
69
+ ) as string,
70
+ suffixId: specificPrefix,
71
+ preferredFocus: renderIndex === 0,
72
+ });
73
+ }
74
+ )
88
75
  ),
89
76
  };
90
77
  };
@@ -1,53 +1,123 @@
1
- import { getPluginIdentifier } from "..";
1
+ import { getPluginIdentifier, memoizedGetPluginIdentifier } from "..";
2
2
 
3
3
  describe("getPluginIdentifier", () => {
4
4
  const prefix = "tv_buttons";
5
5
 
6
- it("get first plugin identifier", () => {
6
+ beforeAll(() => {
7
+ memoizedGetPluginIdentifier.clear();
8
+ });
9
+
10
+ it("returns the requested slot action identifier", () => {
11
+ expect.assertions(2);
12
+
7
13
  const configuration = {
8
- tv_buttons_button_1_other: "value",
9
14
  tv_buttons_button_1_assign_action:
10
15
  "tv_buttons_button_1_assign_action_value",
11
- tv_buttons_button_2_assign_action:
12
- "tv_buttons_button_2_assign_action_value",
16
+ tv_buttons_button_3_assign_action:
17
+ "tv_buttons_button_3_assign_action_value",
13
18
  };
14
19
 
15
- const index = 0;
20
+ const slot = 3;
21
+ const result = getPluginIdentifier(configuration, prefix, slot);
16
22
 
17
- const result = getPluginIdentifier(configuration, prefix, index);
23
+ const memoizedResult = memoizedGetPluginIdentifier(
24
+ configuration,
25
+ prefix,
26
+ slot
27
+ );
18
28
 
19
- expect(result).toEqual(configuration.tv_buttons_button_1_assign_action);
29
+ expect(result).toEqual(configuration.tv_buttons_button_3_assign_action);
30
+ expect(memoizedResult).toEqual(result);
20
31
  });
21
32
 
22
- it("get second plugin identifier", () => {
33
+ it("returns undefined for a disabled or missing slot", () => {
34
+ expect.assertions(2);
35
+
23
36
  const configuration = {
24
- tv_buttons_button_1_other: "value_1",
25
- tv_buttons_button_1_assign_action:
26
- "tv_buttons_button_1_assign_action_value",
27
- tv_buttons_button_2_other: "value_2",
28
- tv_buttons_button_2_assign_action:
29
- "tv_buttons_button_2_assign_action_value",
37
+ tv_buttons_button_1_assign_action: "tv_buttons_button_1_assign_action",
30
38
  };
31
39
 
32
- const index = 1;
40
+ const slot = 2;
41
+ const result = getPluginIdentifier(configuration, prefix, slot);
33
42
 
34
- const result = getPluginIdentifier(configuration, prefix, index);
43
+ const memoizedResult = memoizedGetPluginIdentifier(
44
+ configuration,
45
+ prefix,
46
+ slot
47
+ );
35
48
 
36
- expect(result).toEqual(configuration.tv_buttons_button_2_assign_action);
49
+ expect(result).toBeUndefined();
50
+ expect(memoizedResult).toEqual(result);
51
+ });
52
+
53
+ it("returns undefined for empty string values", () => {
54
+ expect.assertions(2);
55
+
56
+ const configuration = {
57
+ tv_buttons_button_2_assign_action: "",
58
+ };
59
+
60
+ const slot = 2;
61
+
62
+ const result = getPluginIdentifier(configuration, prefix, slot);
63
+
64
+ const memoizedResult = memoizedGetPluginIdentifier(
65
+ configuration,
66
+ prefix,
67
+ slot
68
+ );
69
+
70
+ expect(result).toBeUndefined();
71
+ expect(memoizedResult).toBeUndefined();
37
72
  });
38
73
 
39
- it("get undefined if no assign_actions at all", () => {
40
- const configuration = {};
74
+ it("returns undefined for negative slots", () => {
75
+ expect.assertions(2);
76
+
77
+ const configuration = {
78
+ tv_buttons_button_1_assign_action: "a",
79
+ tv_buttons_button_2_assign_action: "b",
80
+ };
41
81
 
42
- const index = 0;
82
+ const slot = -1;
83
+ const result = getPluginIdentifier(configuration, prefix, slot);
43
84
 
44
- const result = getPluginIdentifier(configuration, prefix, index);
85
+ const memoizedResult = memoizedGetPluginIdentifier(
86
+ configuration,
87
+ prefix,
88
+ slot
89
+ );
45
90
 
46
91
  expect(result).toBeUndefined();
92
+ expect(memoizedResult).toBeUndefined();
93
+ });
94
+
95
+ it("handles non-string values by returning the configured slot value", () => {
96
+ expect.assertions(2);
97
+
98
+ const configuration = {
99
+ tv_buttons_button_2_assign_action: true,
100
+ };
101
+
102
+ const slot = 2;
103
+ const result = getPluginIdentifier(configuration, prefix, slot);
104
+
105
+ const memoizedResult = memoizedGetPluginIdentifier(
106
+ configuration,
107
+ prefix,
108
+ slot
109
+ );
110
+
111
+ expect(result).toBe(true);
112
+ expect(memoizedResult).toBe(true);
47
113
  });
48
114
  });
49
115
 
50
- describe("getPluginIdentifier - when configuration has same values for different keys", () => {
116
+ describe("getPluginIdentifier - when configuration has duplicate values", () => {
117
+ beforeAll(() => {
118
+ memoizedGetPluginIdentifier.clear();
119
+ });
120
+
51
121
  const prefix = "tv_buttons";
52
122
 
53
123
  const configuration = {
@@ -55,19 +125,35 @@ describe("getPluginIdentifier - when configuration has same values for different
55
125
  tv_buttons_button_2_assign_action: "navigation_action",
56
126
  };
57
127
 
58
- it("get first plugin identifier", () => {
59
- const index = 0;
128
+ it("returns the requested slot even when values are identical", () => {
129
+ expect.assertions(2);
60
130
 
61
- const result = getPluginIdentifier(configuration, prefix, index);
131
+ const slot = 1;
132
+ const result = getPluginIdentifier(configuration, prefix, slot);
133
+
134
+ const memoizedResult = memoizedGetPluginIdentifier(
135
+ configuration,
136
+ prefix,
137
+ slot
138
+ );
62
139
 
63
140
  expect(result).toEqual(configuration.tv_buttons_button_1_assign_action);
141
+ expect(memoizedResult).toEqual(result);
64
142
  });
65
143
 
66
- it("get second plugin identifier", () => {
67
- const index = 1;
144
+ it("returns the second slot when values are identical", () => {
145
+ expect.assertions(2);
146
+
147
+ const slot = 2;
148
+ const result = getPluginIdentifier(configuration, prefix, slot);
68
149
 
69
- const result = getPluginIdentifier(configuration, prefix, index);
150
+ const memoizedResult = memoizedGetPluginIdentifier(
151
+ configuration,
152
+ prefix,
153
+ slot
154
+ );
70
155
 
71
156
  expect(result).toEqual(configuration.tv_buttons_button_2_assign_action);
157
+ expect(memoizedResult).toEqual(result);
72
158
  });
73
159
  });