@applicaster/zapp-react-native-ui-components 15.1.0-rc.8 → 16.0.0-alpha.1833861900

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 (157) hide show
  1. package/Components/BackgroundImage/BackgroundImage.tsx +42 -0
  2. package/Components/BackgroundImage/BackgroundImage.tv.android.tsx +28 -0
  3. package/Components/BackgroundImage/BackgroundImage.tv.ios.tsx +24 -0
  4. package/Components/BackgroundImage/index.ts +1 -0
  5. package/Components/BaseFocusable/index.ios.ts +12 -2
  6. package/Components/Cell/FocusableWrapper.tsx +3 -0
  7. package/Components/Cell/TvOSCellComponent.tsx +6 -3
  8. package/Components/CellRendererResolver/index.ts +1 -1
  9. package/Components/ComponentResolver/__tests__/componentResolver.test.js +1 -1
  10. package/Components/Focusable/Focusable.tsx +4 -2
  11. package/Components/Focusable/FocusableTvOS.tsx +18 -1
  12. package/Components/Focusable/__tests__/__snapshots__/FocusableTvOS.test.tsx.snap +1 -0
  13. package/Components/FocusableGroup/FocusableTvOS.tsx +41 -8
  14. package/Components/GeneralContentScreen/utils/__tests__/useCurationAPI.test.js +1 -1
  15. package/Components/HandlePlayable/HandlePlayable.tsx +13 -8
  16. package/Components/Layout/TV/NavBarContainer.tsx +1 -10
  17. package/Components/Layout/TV/ScreenContainer.tsx +2 -6
  18. package/Components/Layout/TV/__tests__/__snapshots__/NavBarContainer.test.tsx.snap +7 -12
  19. package/Components/Layout/TV/__tests__/__snapshots__/ScreenContainer.test.tsx.snap +7 -12
  20. package/Components/Layout/TV/__tests__/__snapshots__/index.test.tsx.snap +0 -1
  21. package/Components/Layout/TV/index.tsx +5 -7
  22. package/Components/Layout/TV/index.web.tsx +5 -7
  23. package/Components/LinkHandler/LinkHandler.tsx +2 -2
  24. package/Components/MasterCell/CONFIG_BUILDER_TO_REACT_COMPONENT.md +144 -0
  25. package/Components/MasterCell/DefaultComponents/ActionButton.tsx +16 -5
  26. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/__tests__/model.test.ts +80 -0
  27. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/__tests__/placement.test.ts +187 -0
  28. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/__tests__/selectors.test.ts +45 -0
  29. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/__tests__/style.test.ts +49 -0
  30. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/components/ActionButtonController.tsx +165 -0
  31. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/components/__tests__/ActionButtonController.test.tsx +405 -0
  32. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/components/index.ts +1 -0
  33. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/model.ts +47 -0
  34. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/placement.ts +170 -0
  35. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/selectors.ts +26 -0
  36. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/style.ts +29 -0
  37. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/types.ts +37 -0
  38. package/Components/MasterCell/DefaultComponents/BorderContainerView/index.tsx +4 -10
  39. package/Components/MasterCell/DefaultComponents/Button.tsx +0 -15
  40. package/Components/MasterCell/DefaultComponents/ButtonContainerView/components/HorizontalSeparator.tsx +8 -0
  41. package/Components/MasterCell/DefaultComponents/ButtonContainerView/index.tsx +15 -0
  42. package/Components/MasterCell/DefaultComponents/ButtonContainerView/index.tv.android.tsx +58 -0
  43. package/Components/MasterCell/DefaultComponents/{tv/ButtonContainerView/index.tsx → ButtonContainerView/index.tv.tsx} +3 -11
  44. package/Components/MasterCell/DefaultComponents/ButtonContainerView/index.web.ts +1 -0
  45. package/Components/MasterCell/DefaultComponents/ButtonContainerView/types.ts +40 -0
  46. package/Components/MasterCell/DefaultComponents/DataProvider/index.tsx +163 -0
  47. package/Components/MasterCell/DefaultComponents/FocusableView/index.android.tsx +2 -23
  48. package/Components/MasterCell/DefaultComponents/FocusableView/index.tsx +4 -22
  49. package/Components/MasterCell/DefaultComponents/Image/Image.android.tsx +8 -2
  50. package/Components/MasterCell/DefaultComponents/Image/Image.ios.tsx +11 -3
  51. package/Components/MasterCell/DefaultComponents/Image/Image.web.tsx +9 -1
  52. package/Components/MasterCell/DefaultComponents/Image/hooks/useImage.ts +15 -14
  53. package/Components/MasterCell/DefaultComponents/LiveImage/index.tsx +1 -2
  54. package/Components/MasterCell/DefaultComponents/PressableView.tsx +34 -0
  55. package/Components/MasterCell/DefaultComponents/SecondaryImage/hooks/__tests__/useGetImageDimensions.test.ts +7 -6
  56. package/Components/MasterCell/DefaultComponents/Text/hooks/useText.ts +11 -0
  57. package/Components/MasterCell/DefaultComponents/__tests__/DataProvider.test.tsx +141 -0
  58. package/Components/MasterCell/DefaultComponents/index.ts +9 -3
  59. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/ActionButton.tsx +135 -0
  60. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/Asset.ts +33 -0
  61. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/AssetComponent.tsx +22 -0
  62. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/Button.ts +125 -0
  63. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/Spacer.ts +16 -0
  64. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/TextLabel.ts +67 -0
  65. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/TextLabelsContainer.ts +37 -0
  66. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/__tests__/PressableView.test.tsx +393 -0
  67. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/__tests__/builders.test.ts +141 -0
  68. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/__tests__/index.test.ts +343 -0
  69. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/helpers.ts +105 -0
  70. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/index.ts +122 -0
  71. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/utils/__tests__/insertButtons.test.ts +118 -0
  72. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/utils/index.ts +238 -0
  73. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/Asset.ts +4 -18
  74. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/Button.ts +24 -73
  75. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/TextLabelsContainer.ts +37 -18
  76. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/TvActionButton.tsx +27 -0
  77. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/__tests__/index.test.ts +89 -0
  78. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/__tests__/renderedTree.test.tsx +231 -0
  79. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/index.ts +47 -48
  80. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/utils/__tests__/getPluginIdentifier.test.ts +115 -29
  81. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/utils/index.ts +101 -144
  82. package/Components/MasterCell/MappingFunctions/index.js +3 -2
  83. package/Components/MasterCell/README.md +4 -0
  84. package/Components/MasterCell/__tests__/__snapshots__/dataAdapter.test.js.snap +24 -0
  85. package/Components/MasterCell/__tests__/configInflater.test.js +1 -0
  86. package/Components/MasterCell/__tests__/elementMapper.test.js +46 -0
  87. package/Components/MasterCell/dataAdapter.ts +4 -1
  88. package/Components/MasterCell/elementMapper.tsx +52 -7
  89. package/Components/MasterCell/utils/__tests__/cloneChildrenWithIds.test.tsx +43 -0
  90. package/Components/MasterCell/utils/__tests__/useFilterChildren.test.tsx +80 -0
  91. package/Components/MasterCell/utils/index.ts +85 -15
  92. package/Components/OfflineHandler/NotificationView/NotificationView.tsx +2 -2
  93. package/Components/OfflineHandler/NotificationView/__tests__/index.test.tsx +17 -18
  94. package/Components/OfflineHandler/__tests__/index.test.tsx +27 -18
  95. package/Components/PlayerContainer/PlayerContainer.tsx +15 -14
  96. package/Components/PlayerContainer/__tests__/PlayerContainer.test.tsx +284 -0
  97. package/Components/River/ComponentsMap/ComponentsMap.tsx +6 -19
  98. package/Components/River/ComponentsMap/hooks/__tests__/useLoadingState.test.ts +1 -1
  99. package/Components/River/RefreshControl.tsx +19 -88
  100. package/Components/River/River.tsx +9 -82
  101. package/Components/River/TV/River.tsx +31 -14
  102. package/Components/River/TV/index.tsx +8 -4
  103. package/Components/River/TV/utils/__tests__/toStringOrEmpty.test.ts +30 -0
  104. package/Components/River/TV/utils/index.ts +4 -0
  105. package/Components/River/TV/withFocusableGroupForContent.tsx +71 -0
  106. package/Components/River/__tests__/__snapshots__/componentsMap.test.js.snap +1 -0
  107. package/Components/River/__tests__/componentsMap.test.js +38 -0
  108. package/Components/River/hooks/__tests__/usePullToRefresh.test.ts +132 -0
  109. package/Components/River/hooks/index.ts +1 -0
  110. package/Components/River/hooks/usePullToRefresh.ts +51 -0
  111. package/Components/Screen/TV/index.web.tsx +4 -2
  112. package/Components/Screen/__tests__/Screen.test.tsx +65 -42
  113. package/Components/Screen/__tests__/__snapshots__/Screen.test.tsx.snap +68 -44
  114. package/Components/Screen/hooks.ts +2 -3
  115. package/Components/Screen/index.tsx +2 -3
  116. package/Components/Screen/orientationHandler.ts +3 -3
  117. package/Components/ScreenResolver/index.tsx +9 -5
  118. package/Components/ScreenRevealManager/Overlay.tsx +34 -0
  119. package/Components/ScreenRevealManager/ScreenRevealManager.ts +40 -8
  120. package/Components/ScreenRevealManager/__tests__/Overlay.test.tsx +88 -0
  121. package/Components/ScreenRevealManager/__tests__/ScreenRevealManager.test.ts +86 -69
  122. package/Components/ScreenRevealManager/withScreenRevealManager.tsx +9 -20
  123. package/Components/Tabs/TabContent.tsx +7 -4
  124. package/Components/TopCutoffOverlay/__tests__/TopCutoffOverlay.test.tsx +201 -0
  125. package/Components/TopCutoffOverlay/hooks/__tests__/useMarginTop.test.ts +130 -0
  126. package/Components/TopCutoffOverlay/hooks/index.ts +1 -0
  127. package/Components/TopCutoffOverlay/hooks/useMarginTop.ts +59 -0
  128. package/Components/TopCutoffOverlay/index.tsx +55 -0
  129. package/Components/Transitioner/index.js +3 -3
  130. package/Components/VideoModal/ModalAnimation/ModalAnimationContext.tsx +5 -5
  131. package/Components/VideoModal/hooks/__tests__/useDelayedPlayerDetails.test.ts +15 -7
  132. package/Components/VideoModal/utils.ts +18 -10
  133. package/Components/Viewport/ViewportAware/__tests__/viewportAware.test.js +0 -2
  134. package/Components/Viewport/ViewportAware/index.tsx +16 -7
  135. package/Components/Viewport/ViewportEvents/__tests__/viewportEvents.test.js +1 -1
  136. package/Components/ZappFrameworkComponents/BarView/BarView.tsx +4 -6
  137. package/Components/ZappFrameworkComponents/BarView/__tests__/BarView.test.tsx +2 -2
  138. package/Components/default-cell-renderer/viewTrees/mobile/index.ts +0 -3
  139. package/Contexts/ScreenContext/index.tsx +25 -18
  140. package/Contexts/ScreenTrackedViewPositionsContext/__tests__/index.test.tsx +1 -1
  141. package/Decorators/Analytics/index.tsx +6 -5
  142. package/Decorators/ConfigurationWrapper/__tests__/__snapshots__/withConfigurationProvider.test.tsx.snap +1 -0
  143. package/Decorators/ConfigurationWrapper/const.ts +1 -0
  144. package/Decorators/ZappPipesDataConnector/__tests__/UrlFeedResolver.test.tsx +39 -21
  145. package/Decorators/ZappPipesDataConnector/__tests__/zappPipesDataConnector.test.js +1 -1
  146. package/Decorators/ZappPipesDataConnector/index.tsx +2 -2
  147. package/Decorators/ZappPipesDataConnector/resolvers/StaticFeedResolver.tsx +1 -1
  148. package/Decorators/ZappPipesDataConnector/resolvers/UrlFeedResolver.tsx +18 -7
  149. package/Helpers/DataSourceHelper/__tests__/itemLimitForData.test.ts +80 -0
  150. package/Helpers/DataSourceHelper/index.ts +19 -0
  151. package/Helpers/index.js +7 -1
  152. package/package.json +5 -5
  153. package/Components/Layout/TV/LayoutBackground.tsx +0 -28
  154. package/Components/MasterCell/DefaultComponents/tv/ButtonContainerView/index.android.tsx +0 -135
  155. package/Components/MasterCell/DefaultComponents/tv/ButtonContainerView/types.ts +0 -25
  156. package/Components/River/TV/withTVEventHandler.tsx +0 -36
  157. package/Helpers/DataSourceHelper/index.js +0 -19
@@ -0,0 +1,170 @@
1
+ type Label = {
2
+ name?: string;
3
+ elements?: Array<Label | unknown>;
4
+ };
5
+
6
+ type LabelContainer = {
7
+ elements?: Array<Label | unknown>;
8
+ };
9
+
10
+ type LabelExtraContainer = {
11
+ elements?: Array<LabelContainer | unknown>;
12
+ };
13
+
14
+ type InsertOptions = {
15
+ position?: string;
16
+ allowOnTop?: boolean;
17
+ appendWhenMissing?: boolean;
18
+ };
19
+
20
+ const hasLabelName = (value: unknown): value is Label =>
21
+ !!value &&
22
+ typeof value === "object" &&
23
+ typeof (value as Label).name === "string" &&
24
+ (value as Label).name !== "";
25
+
26
+ const withButtons = (
27
+ labelName: string | undefined,
28
+ buttons: unknown,
29
+ labels: Label[]
30
+ ) =>
31
+ labels.reduce<Array<Label | unknown>>((acc, label) => {
32
+ if (!!label?.name && labelName?.includes(label.name)) {
33
+ // handle above_*
34
+ if (labelName?.startsWith("above_")) {
35
+ return [...acc, buttons, label];
36
+ }
37
+
38
+ // handle below_* or plain exact match (default: insert after (covers below_* AND plain names))
39
+ return [...acc, label, buttons];
40
+ }
41
+
42
+ return [...acc, label];
43
+ }, []);
44
+
45
+ const withButtonsInNestedViews = (
46
+ labelName: string | undefined,
47
+ buttons: unknown,
48
+ views: Array<LabelContainer | unknown>
49
+ ) => {
50
+ const hasDirectLabels = views.some((view) => hasLabelName(view));
51
+
52
+ if (hasDirectLabels) {
53
+ return withButtons(labelName, buttons, views as Label[]);
54
+ }
55
+
56
+ return views.map((view) => {
57
+ if (
58
+ !view ||
59
+ typeof view !== "object" ||
60
+ !Array.isArray((view as LabelContainer).elements)
61
+ ) {
62
+ return view;
63
+ }
64
+
65
+ return {
66
+ ...(view as LabelContainer),
67
+ elements: withButtons(
68
+ labelName,
69
+ buttons,
70
+ (view as LabelContainer).elements || []
71
+ ),
72
+ };
73
+ });
74
+ };
75
+
76
+ const containsNestedLabel = (
77
+ labelName: string | undefined,
78
+ labelContainers: LabelExtraContainer[]
79
+ ) =>
80
+ labelContainers.some((labelContainer) =>
81
+ (labelContainer.elements || []).some((view) => {
82
+ if (hasLabelName(view)) {
83
+ return labelName?.includes(view.name) ?? false;
84
+ }
85
+
86
+ if (
87
+ !view ||
88
+ typeof view !== "object" ||
89
+ !Array.isArray((view as LabelContainer).elements)
90
+ ) {
91
+ return false;
92
+ }
93
+
94
+ return ((view as LabelContainer).elements || []).some((label) =>
95
+ hasLabelName(label) ? (labelName?.includes(label.name) ?? false) : false
96
+ );
97
+ })
98
+ );
99
+
100
+ export const insertBetweenLabels = (
101
+ { position, allowOnTop = false, appendWhenMissing = false }: InsertOptions,
102
+ buttons: unknown,
103
+ labels: Label[] = []
104
+ ) => {
105
+ if (buttons == null) {
106
+ return labels;
107
+ }
108
+
109
+ if (allowOnTop && position === "on_top") {
110
+ return [buttons, ...labels];
111
+ }
112
+
113
+ const labelsWithButtons = withButtons(position, buttons, labels);
114
+ const inserted = labelsWithButtons.length !== labels.length;
115
+
116
+ if (!inserted && appendWhenMissing) {
117
+ return [...labels, buttons];
118
+ }
119
+
120
+ return inserted ? labelsWithButtons : [...labels];
121
+ };
122
+
123
+ export const insertBetweenLabelContainers = (
124
+ { position, allowOnTop = false, appendWhenMissing = false }: InsertOptions,
125
+ buttons: unknown,
126
+ labelContainers: LabelExtraContainer[] = []
127
+ ) => {
128
+ if (buttons == null) {
129
+ return labelContainers;
130
+ }
131
+
132
+ if (labelContainers.length === 0) {
133
+ return [buttons];
134
+ }
135
+
136
+ if (allowOnTop && position === "on_top") {
137
+ return labelContainers.map((labelContainer, index) =>
138
+ index === 0
139
+ ? {
140
+ ...labelContainer,
141
+ elements: [buttons, ...(labelContainer.elements || [])],
142
+ }
143
+ : labelContainer
144
+ );
145
+ }
146
+
147
+ const hasMatchingLabel = containsNestedLabel(position, labelContainers);
148
+
149
+ const labelContainersWithButtons = labelContainers.map((labelContainer) => ({
150
+ ...labelContainer,
151
+ elements: withButtonsInNestedViews(
152
+ position,
153
+ buttons,
154
+ labelContainer.elements || []
155
+ ),
156
+ }));
157
+
158
+ if (!hasMatchingLabel && appendWhenMissing) {
159
+ return labelContainers.map((labelContainer, index) =>
160
+ index === labelContainers.length - 1
161
+ ? {
162
+ ...labelContainer,
163
+ elements: [...(labelContainer.elements || []), buttons],
164
+ }
165
+ : labelContainer
166
+ );
167
+ }
168
+
169
+ return hasMatchingLabel ? labelContainersWithButtons : labelContainers;
170
+ };
@@ -0,0 +1,26 @@
1
+ export const getButtonSlotPrefix = (buttonPrefix: string, slot: number) =>
2
+ `${buttonPrefix}_${slot}`;
3
+
4
+ export const getEnabledButtonSlots = (
5
+ configuration: Record<string, unknown>,
6
+ buttonPrefix: string,
7
+ maxButtons = 3
8
+ ): number[] =>
9
+ Array.from({ length: maxButtons }, (_, index) => index + 1).filter((slot) =>
10
+ Boolean(
11
+ configuration[`${getButtonSlotPrefix(buttonPrefix, slot)}_button_enabled`]
12
+ )
13
+ );
14
+
15
+ export const getStylePrefix = ({
16
+ slot,
17
+ independentStyles,
18
+ buttonPrefix,
19
+ }: {
20
+ slot: number;
21
+ independentStyles: boolean;
22
+ buttonPrefix: string;
23
+ }) =>
24
+ independentStyles
25
+ ? getButtonSlotPrefix(buttonPrefix, slot)
26
+ : getButtonSlotPrefix(buttonPrefix, 1);
@@ -0,0 +1,29 @@
1
+ import { mapSelfAlignment } from "@applicaster/zapp-react-native-utils/cellUtils";
2
+ import { toNumberWithDefaultZero } from "@applicaster/zapp-react-native-utils/numberUtils";
3
+
4
+ import { ActionButtonsContainerLayout } from "./types";
5
+
6
+ export const getContainerMargins = (
7
+ value: (key: string) => unknown,
8
+ prefix: string
9
+ ) => ({
10
+ top: toNumberWithDefaultZero(value(`${prefix}_margin_top`)),
11
+ right: toNumberWithDefaultZero(value(`${prefix}_margin_right`)),
12
+ bottom: toNumberWithDefaultZero(value(`${prefix}_margin_bottom`)),
13
+ left: toNumberWithDefaultZero(value(`${prefix}_margin_left`)),
14
+ });
15
+
16
+ export const buildContainerLayout = (
17
+ value: (key: string) => unknown,
18
+ prefix: string
19
+ ): ActionButtonsContainerLayout => ({
20
+ horizontalAlign: mapSelfAlignment(value(`${prefix}_align`)),
21
+ margins: getContainerMargins(value, prefix),
22
+ stacking:
23
+ value(`${prefix}_stacking`) === "vertical" ? "vertical" : "horizontal",
24
+ horizontalGutter: toNumberWithDefaultZero(
25
+ value(`${prefix}_horizontal_gutter`)
26
+ ),
27
+ verticalGutter: toNumberWithDefaultZero(value(`${prefix}_vertical_gutter`)),
28
+ independentStyles: Boolean(value(`${prefix}_independent_styles`)),
29
+ });
@@ -0,0 +1,37 @@
1
+ export type ActionButtonSlot = number;
2
+
3
+ export type ActionButtonDescriptor = {
4
+ slot: ActionButtonSlot;
5
+ renderIndex: number;
6
+ specificPrefix: string;
7
+ stylePrefix: string;
8
+ };
9
+
10
+ export type ActionButtonsContainerLayout = {
11
+ horizontalAlign: string;
12
+ margins: {
13
+ top: number;
14
+ right: number;
15
+ bottom: number;
16
+ left: number;
17
+ };
18
+ stacking: "horizontal" | "vertical";
19
+ horizontalGutter: number;
20
+ verticalGutter: number;
21
+ independentStyles: boolean;
22
+ };
23
+
24
+ export type ActionButtonsModel = {
25
+ enabledSlots: ActionButtonSlot[];
26
+ buttonsCount: number;
27
+ container: ActionButtonsContainerLayout;
28
+ buttons: ActionButtonDescriptor[];
29
+ };
30
+
31
+ export type BuildActionButtonsModelOptions = {
32
+ configuration: Record<string, unknown>;
33
+ value: (key: string) => unknown;
34
+ containerPrefix: string;
35
+ buttonPrefix: string;
36
+ maxButtons?: number;
37
+ };
@@ -135,17 +135,11 @@ export const BorderContainerView = (props: Props) => {
135
135
 
136
136
  const isImageOnlyCell = useMemo(
137
137
  () =>
138
- !hasFocusableInside(entry) &&
139
- !hasTextLabels &&
140
138
  state === "focused" &&
141
- !isMeasurement?.measuringInProgress,
142
- [
143
- hasFocusableInside,
144
- entry,
145
- hasTextLabels,
146
- state,
147
- isMeasurement?.measuringInProgress,
148
- ]
139
+ !hasTextLabels &&
140
+ !isMeasurement?.measuringInProgress &&
141
+ !hasFocusableInside(entry),
142
+ [entry, hasTextLabels, state, isMeasurement?.measuringInProgress]
149
143
  );
150
144
 
151
145
  useEffect(() => {
@@ -2,7 +2,6 @@ import * as React from "react";
2
2
  import * as R from "ramda";
3
3
 
4
4
  import { TouchableOpacity } from "react-native";
5
- // import { SvgUri } from "react-native-svg";
6
5
 
7
6
  import { connectToStore } from "@applicaster/zapp-react-native-redux/utils/connectToStore";
8
7
  import { useActions } from "@applicaster/zapp-react-native-utils/reactHooks/actions";
@@ -35,20 +34,6 @@ type Props = {
35
34
  cellUUID?: string;
36
35
  };
37
36
 
38
- // function Svg({
39
- // uri,
40
- // style,
41
- // ...props
42
- // }: {
43
- // uri: {},
44
- // style: { width: Number, height: number },
45
- // }) {
46
- // const width = style?.width;
47
- // const height = style?.height;
48
-
49
- // return <SvgUri width={width} height={height} uri={uri} />;
50
- // }
51
-
52
37
  const components = {
53
38
  Image,
54
39
  };
@@ -0,0 +1,8 @@
1
+ import React from "react";
2
+ import { View } from "react-native";
3
+
4
+ import type { HorizontalSeparatorProps } from "../types";
5
+
6
+ export const HorizontalSeparator = ({ width }: HorizontalSeparatorProps) => (
7
+ <View style={{ width }} />
8
+ );
@@ -0,0 +1,15 @@
1
+ import React from "react";
2
+ import { View } from "react-native";
3
+ import { ContainerProps } from "./types";
4
+
5
+ export function ButtonContainerView({
6
+ style,
7
+ contentStyle,
8
+ children,
9
+ }: ContainerProps) {
10
+ return (
11
+ <View style={style} pointerEvents="box-none">
12
+ <View style={contentStyle}>{children}</View>
13
+ </View>
14
+ );
15
+ }
@@ -0,0 +1,58 @@
1
+ import React from "react";
2
+ import { View } from "react-native";
3
+ import * as R from "ramda";
4
+ import { useInitialFocus } from "@applicaster/zapp-react-native-utils/focusManager";
5
+ import {
6
+ useIsRTL,
7
+ applyRTLStylesIfNeeded,
8
+ } from "@applicaster/zapp-react-native-utils/localizationUtils";
9
+
10
+ import {
11
+ cloneChildrenWithIds,
12
+ insertBetween,
13
+ unwrapDataProviderChild,
14
+ useFilterChildren,
15
+ } from "../../utils";
16
+
17
+ import type { ContainerProps, ContainerChildren } from "./types";
18
+ import { HorizontalSeparator } from "./components/HorizontalSeparator";
19
+
20
+ const generateId = (cellUUID, suffixId) => `${cellUUID}--${suffixId}`;
21
+
22
+ export function ButtonContainerView({
23
+ style,
24
+ children,
25
+ ...otherProps
26
+ }: ContainerProps) {
27
+ const isRTL = useIsRTL();
28
+
29
+ const horizontalGutter = R.pathOr(0, ["horizontalGutter"], otherProps);
30
+ const filteredChildren = useFilterChildren<ContainerChildren>(children);
31
+
32
+ const buttonIds = filteredChildren.map((child) => {
33
+ const wrappedChild = unwrapDataProviderChild(child) as ContainerChildren;
34
+ const { cellUUID, suffixId } = wrappedChild.props;
35
+
36
+ return generateId(cellUUID, suffixId);
37
+ });
38
+
39
+ useInitialFocus(otherProps.state === "focused", R.head(buttonIds));
40
+
41
+ if (R.isEmpty(filteredChildren)) {
42
+ return null;
43
+ }
44
+
45
+ return (
46
+ <View style={applyRTLStylesIfNeeded(style, isRTL)}>
47
+ {insertBetween(
48
+ (index) => (
49
+ <HorizontalSeparator
50
+ key={`separator_${index}`}
51
+ width={horizontalGutter}
52
+ />
53
+ ),
54
+ cloneChildrenWithIds(buttonIds, filteredChildren)
55
+ )}
56
+ </View>
57
+ );
58
+ }
@@ -1,20 +1,13 @@
1
1
  import React from "react";
2
2
  import { View } from "react-native";
3
3
  import * as R from "ramda";
4
- import { useFilterChildren, insertBetween } from "../../../utils";
5
- import type {
6
- HorizontalSeparatorProps,
7
- ContainerProps,
8
- ContainerChildren,
9
- } from "./types";
4
+ import { insertBetween, useFilterChildren } from "../../utils";
5
+ import type { ContainerProps, ContainerChildren } from "./types";
10
6
  import {
11
7
  useIsRTL,
12
8
  applyRTLStylesIfNeeded,
13
9
  } from "@applicaster/zapp-react-native-utils/localizationUtils";
14
-
15
- const HorizontalSeparator = ({ width }: HorizontalSeparatorProps) => (
16
- <View style={{ width }} />
17
- );
10
+ import { HorizontalSeparator } from "./components/HorizontalSeparator";
18
11
 
19
12
  export function ButtonContainerView({
20
13
  style,
@@ -23,7 +16,6 @@ export function ButtonContainerView({
23
16
  }: ContainerProps) {
24
17
  const isRTL = useIsRTL();
25
18
  const horizontalGutter = R.pathOr(0, ["horizontalGutter"], otherProps);
26
-
27
19
  const filteredChildren = useFilterChildren<ContainerChildren>(children);
28
20
 
29
21
  if (R.isEmpty(filteredChildren)) {
@@ -0,0 +1 @@
1
+ export { ButtonContainerView } from "./index.tv";
@@ -0,0 +1,40 @@
1
+ import { ReactElement, ReactNode } from "react";
2
+ import { ViewStyle } from "react-native";
3
+
4
+ export type ContainerChildProps = {
5
+ item: any;
6
+ pluginIdentifier: string;
7
+ cellUUID: string;
8
+ suffixId: string;
9
+ nextFocusLeft?: string;
10
+ nextFocusRight?: string;
11
+ };
12
+
13
+ export type ContainerProps = Record<string, any> & {
14
+ buttonsToggleEnabled: boolean;
15
+ skipButtons: boolean;
16
+ style: ViewStyle;
17
+ contentStyle?: ViewStyle;
18
+ children: ContainerChildren[];
19
+ };
20
+
21
+ export type ButtonProps = Record<string, any> & {
22
+ style: ViewStyle;
23
+ children?: ReactNode;
24
+ item: any;
25
+ cellUUID: string;
26
+ groupId?: string;
27
+ suffixId: string;
28
+ normalStyles?: ViewStyle;
29
+ focusedStyles?: ViewStyle;
30
+ nextFocusLeft?: string;
31
+ nextFocusRight?: string;
32
+ pluginIdentifier: string;
33
+ disableFocus?: boolean;
34
+ };
35
+
36
+ export type HorizontalSeparatorProps = {
37
+ width: number;
38
+ };
39
+
40
+ export type ContainerChildren = ReactElement<ContainerChildProps>;
@@ -0,0 +1,163 @@
1
+ import React from "react";
2
+ import { View } from "react-native";
3
+
4
+ type Props = Record<string, any> & {
5
+ children?: React.ReactNode;
6
+ };
7
+
8
+ /**
9
+ * Creates the provider payload from the resolved runtime props.
10
+ * `children` is structural and is therefore excluded. `_dataKey` is produced
11
+ * by `configInflater` from the data mapping propName and identifies the prop
12
+ * that carries the resolved entry value.
13
+ */
14
+ const createDataProviderProps = ({ children: _children, ...props }: Props) => ({
15
+ _dataKey: props._dataKey,
16
+ [props._dataKey as string]: props[props._dataKey as string],
17
+ });
18
+
19
+ /**
20
+ * Merges provider values into an element's existing `dataProviderProps`
21
+ * without overwriting keys that were supplied explicitly.
22
+ */
23
+ const mergeDataProviderProps = (
24
+ targetProps: Record<string, unknown>,
25
+ forwardedProps: Record<string, unknown>
26
+ ) => {
27
+ const dataProviderProps = Object.keys(forwardedProps).reduce(
28
+ (acc, key) => {
29
+ if (typeof acc[key] === "undefined") {
30
+ acc[key] = forwardedProps[key];
31
+ }
32
+
33
+ return acc;
34
+ },
35
+ { ...(targetProps.dataProviderProps as Record<string, unknown>) } as Record<
36
+ string,
37
+ unknown
38
+ >
39
+ );
40
+
41
+ return {
42
+ dataProviderProps: dataProviderProps,
43
+ ...(targetProps[dataProviderProps._dataKey as string] === undefined
44
+ ? {
45
+ [dataProviderProps._dataKey as string]:
46
+ forwardedProps[dataProviderProps._dataKey as string],
47
+ }
48
+ : {}),
49
+ };
50
+ };
51
+
52
+ /**
53
+ * Applies the provider props to the wrapped child's direct children only.
54
+ * The shallow stop is intentional: descendants below this level must receive
55
+ * data explicitly from their parent component instead of implicit propagation.
56
+ */
57
+ const cloneSecondLevel = (
58
+ nodes: React.ReactNode,
59
+ forwardedProps: Record<string, unknown>
60
+ ) =>
61
+ React.Children.map(nodes, (child) => {
62
+ if (!React.isValidElement(child)) {
63
+ return child;
64
+ }
65
+
66
+ if (child.type === View) {
67
+ return child;
68
+ }
69
+
70
+ return React.cloneElement(
71
+ child,
72
+ mergeDataProviderProps(
73
+ child.props as Record<string, unknown>,
74
+ forwardedProps
75
+ ) as never
76
+ );
77
+ });
78
+
79
+ /**
80
+ * Specification:
81
+ * `DataProvider` bridges MasterCell node-tree `data` mappings into runtime
82
+ * React props for a shallow wrapped subtree under `dataProviderProps`.
83
+ *
84
+ * Purpose:
85
+ * Use `DataProvider` when a subtree root and that root's direct children must
86
+ * receive the same resolved runtime prop without propagating that prop deeper
87
+ * into the tree.
88
+ *
89
+ * Input contract:
90
+ * - `DataProvider` receives runtime props from normal MasterCell inflation.
91
+ * - The values under `dataProviderProps` are derived from the resolved props
92
+ * it receives.
93
+ * - The inner keys are not hardcoded and may include `entry`, `item`, or any
94
+ * other prop name produced by the node-tree `data` mapping.
95
+ *
96
+ * Forwarding contract:
97
+ * - inject the mapped value as a direct prop on the wrapped child
98
+ * - inject `dataProviderProps` into the wrapped child
99
+ * - inject both the direct prop and `dataProviderProps` into that child's
100
+ * direct children
101
+ * - stop at that level
102
+ * - do not overwrite explicit keys that already exist inside
103
+ * `dataProviderProps` on wrapped elements
104
+ *
105
+ * Non-forwarded props:
106
+ * - `children`
107
+ *
108
+ * Example:
109
+ *
110
+ * ```ts
111
+ * {
112
+ * type: "DataProvider",
113
+ * data: [
114
+ * {
115
+ * func: "identity",
116
+ * args: [],
117
+ * propName: "entry",
118
+ * },
119
+ * ],
120
+ * elements: [
121
+ * {
122
+ * type: "ButtonContainerView",
123
+ * elements: [Button({ ... })],
124
+ * },
125
+ * ],
126
+ * }
127
+ * ```
128
+ *
129
+ * Expected runtime result:
130
+ * - `ButtonContainerView` receives `entry` and `dataProviderProps.entry`
131
+ * - each direct `Button` child receives `entry` and `dataProviderProps.entry`
132
+ * - deeper descendants such as `Asset` or `TextLabel` do not receive
133
+ * `entry` or `dataProviderProps.entry` unless their parent passes it
134
+ * explicitly
135
+ */
136
+ export const DataProvider = ({ children, ...props }: Props) => {
137
+ const forwardedProps = createDataProviderProps(props);
138
+
139
+ return (
140
+ <>
141
+ {React.Children.map(children, (child) => {
142
+ if (!React.isValidElement(child)) {
143
+ return child;
144
+ }
145
+
146
+ const injectedChildren = cloneSecondLevel(
147
+ child.props.children,
148
+ forwardedProps
149
+ );
150
+
151
+ return React.cloneElement(child, {
152
+ ...mergeDataProviderProps(
153
+ child.props as Record<string, unknown>,
154
+ forwardedProps
155
+ ),
156
+ ...(typeof injectedChildren !== "undefined"
157
+ ? { children: injectedChildren }
158
+ : {}),
159
+ });
160
+ })}
161
+ </>
162
+ );
163
+ };