@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.
- package/Components/BackgroundImage/BackgroundImage.tsx +42 -0
- package/Components/BackgroundImage/BackgroundImage.tv.android.tsx +28 -0
- package/Components/BackgroundImage/BackgroundImage.tv.ios.tsx +24 -0
- package/Components/BackgroundImage/index.ts +1 -0
- package/Components/BaseFocusable/index.ios.ts +12 -2
- package/Components/Cell/FocusableWrapper.tsx +3 -0
- package/Components/Cell/TvOSCellComponent.tsx +6 -3
- package/Components/CellRendererResolver/index.ts +1 -1
- package/Components/ComponentResolver/__tests__/componentResolver.test.js +1 -1
- package/Components/Focusable/Focusable.tsx +4 -2
- package/Components/Focusable/FocusableTvOS.tsx +18 -1
- package/Components/Focusable/__tests__/__snapshots__/FocusableTvOS.test.tsx.snap +1 -0
- package/Components/FocusableGroup/FocusableTvOS.tsx +41 -8
- package/Components/GeneralContentScreen/utils/__tests__/useCurationAPI.test.js +1 -1
- package/Components/HandlePlayable/HandlePlayable.tsx +13 -8
- package/Components/Layout/TV/NavBarContainer.tsx +1 -10
- package/Components/Layout/TV/ScreenContainer.tsx +2 -6
- package/Components/Layout/TV/__tests__/__snapshots__/NavBarContainer.test.tsx.snap +7 -12
- package/Components/Layout/TV/__tests__/__snapshots__/ScreenContainer.test.tsx.snap +7 -12
- package/Components/Layout/TV/__tests__/__snapshots__/index.test.tsx.snap +0 -1
- package/Components/Layout/TV/index.tsx +5 -7
- package/Components/Layout/TV/index.web.tsx +5 -7
- package/Components/LinkHandler/LinkHandler.tsx +2 -2
- package/Components/MasterCell/CONFIG_BUILDER_TO_REACT_COMPONENT.md +144 -0
- package/Components/MasterCell/DefaultComponents/ActionButton.tsx +16 -5
- package/Components/MasterCell/DefaultComponents/ActionButtonsCore/__tests__/model.test.ts +80 -0
- package/Components/MasterCell/DefaultComponents/ActionButtonsCore/__tests__/placement.test.ts +187 -0
- package/Components/MasterCell/DefaultComponents/ActionButtonsCore/__tests__/selectors.test.ts +45 -0
- package/Components/MasterCell/DefaultComponents/ActionButtonsCore/__tests__/style.test.ts +49 -0
- package/Components/MasterCell/DefaultComponents/ActionButtonsCore/components/ActionButtonController.tsx +165 -0
- package/Components/MasterCell/DefaultComponents/ActionButtonsCore/components/__tests__/ActionButtonController.test.tsx +405 -0
- package/Components/MasterCell/DefaultComponents/ActionButtonsCore/components/index.ts +1 -0
- package/Components/MasterCell/DefaultComponents/ActionButtonsCore/model.ts +47 -0
- package/Components/MasterCell/DefaultComponents/ActionButtonsCore/placement.ts +170 -0
- package/Components/MasterCell/DefaultComponents/ActionButtonsCore/selectors.ts +26 -0
- package/Components/MasterCell/DefaultComponents/ActionButtonsCore/style.ts +29 -0
- package/Components/MasterCell/DefaultComponents/ActionButtonsCore/types.ts +37 -0
- package/Components/MasterCell/DefaultComponents/BorderContainerView/index.tsx +4 -10
- package/Components/MasterCell/DefaultComponents/Button.tsx +0 -15
- package/Components/MasterCell/DefaultComponents/ButtonContainerView/components/HorizontalSeparator.tsx +8 -0
- package/Components/MasterCell/DefaultComponents/ButtonContainerView/index.tsx +15 -0
- package/Components/MasterCell/DefaultComponents/ButtonContainerView/index.tv.android.tsx +58 -0
- package/Components/MasterCell/DefaultComponents/{tv/ButtonContainerView/index.tsx → ButtonContainerView/index.tv.tsx} +3 -11
- package/Components/MasterCell/DefaultComponents/ButtonContainerView/index.web.ts +1 -0
- package/Components/MasterCell/DefaultComponents/ButtonContainerView/types.ts +40 -0
- package/Components/MasterCell/DefaultComponents/DataProvider/index.tsx +163 -0
- package/Components/MasterCell/DefaultComponents/FocusableView/index.android.tsx +2 -23
- package/Components/MasterCell/DefaultComponents/FocusableView/index.tsx +4 -22
- package/Components/MasterCell/DefaultComponents/Image/Image.android.tsx +8 -2
- package/Components/MasterCell/DefaultComponents/Image/Image.ios.tsx +11 -3
- package/Components/MasterCell/DefaultComponents/Image/Image.web.tsx +9 -1
- package/Components/MasterCell/DefaultComponents/Image/hooks/useImage.ts +15 -14
- package/Components/MasterCell/DefaultComponents/LiveImage/index.tsx +1 -2
- package/Components/MasterCell/DefaultComponents/PressableView.tsx +34 -0
- package/Components/MasterCell/DefaultComponents/SecondaryImage/hooks/__tests__/useGetImageDimensions.test.ts +7 -6
- package/Components/MasterCell/DefaultComponents/Text/hooks/useText.ts +11 -0
- package/Components/MasterCell/DefaultComponents/__tests__/DataProvider.test.tsx +141 -0
- package/Components/MasterCell/DefaultComponents/index.ts +9 -3
- package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/ActionButton.tsx +135 -0
- package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/Asset.ts +33 -0
- package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/AssetComponent.tsx +22 -0
- package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/Button.ts +125 -0
- package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/Spacer.ts +16 -0
- package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/TextLabel.ts +67 -0
- package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/TextLabelsContainer.ts +37 -0
- package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/__tests__/PressableView.test.tsx +393 -0
- package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/__tests__/builders.test.ts +141 -0
- package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/__tests__/index.test.ts +343 -0
- package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/helpers.ts +105 -0
- package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/index.ts +122 -0
- package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/utils/__tests__/insertButtons.test.ts +118 -0
- package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/utils/index.ts +238 -0
- package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/Asset.ts +4 -18
- package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/Button.ts +24 -73
- package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/TextLabelsContainer.ts +37 -18
- package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/TvActionButton.tsx +27 -0
- package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/__tests__/index.test.ts +89 -0
- package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/__tests__/renderedTree.test.tsx +231 -0
- package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/index.ts +47 -48
- package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/utils/__tests__/getPluginIdentifier.test.ts +115 -29
- package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/utils/index.ts +101 -144
- package/Components/MasterCell/MappingFunctions/index.js +3 -2
- package/Components/MasterCell/README.md +4 -0
- package/Components/MasterCell/__tests__/__snapshots__/dataAdapter.test.js.snap +24 -0
- package/Components/MasterCell/__tests__/configInflater.test.js +1 -0
- package/Components/MasterCell/__tests__/elementMapper.test.js +46 -0
- package/Components/MasterCell/dataAdapter.ts +4 -1
- package/Components/MasterCell/elementMapper.tsx +52 -7
- package/Components/MasterCell/utils/__tests__/cloneChildrenWithIds.test.tsx +43 -0
- package/Components/MasterCell/utils/__tests__/useFilterChildren.test.tsx +80 -0
- package/Components/MasterCell/utils/index.ts +85 -15
- package/Components/OfflineHandler/NotificationView/NotificationView.tsx +2 -2
- package/Components/OfflineHandler/NotificationView/__tests__/index.test.tsx +17 -18
- package/Components/OfflineHandler/__tests__/index.test.tsx +27 -18
- package/Components/PlayerContainer/PlayerContainer.tsx +15 -14
- package/Components/PlayerContainer/__tests__/PlayerContainer.test.tsx +284 -0
- package/Components/River/ComponentsMap/ComponentsMap.tsx +6 -19
- package/Components/River/ComponentsMap/hooks/__tests__/useLoadingState.test.ts +1 -1
- package/Components/River/RefreshControl.tsx +19 -88
- package/Components/River/River.tsx +9 -82
- package/Components/River/TV/River.tsx +31 -14
- package/Components/River/TV/index.tsx +8 -4
- package/Components/River/TV/utils/__tests__/toStringOrEmpty.test.ts +30 -0
- package/Components/River/TV/utils/index.ts +4 -0
- package/Components/River/TV/withFocusableGroupForContent.tsx +71 -0
- package/Components/River/__tests__/__snapshots__/componentsMap.test.js.snap +1 -0
- package/Components/River/__tests__/componentsMap.test.js +38 -0
- package/Components/River/hooks/__tests__/usePullToRefresh.test.ts +132 -0
- package/Components/River/hooks/index.ts +1 -0
- package/Components/River/hooks/usePullToRefresh.ts +51 -0
- package/Components/Screen/TV/index.web.tsx +4 -2
- package/Components/Screen/__tests__/Screen.test.tsx +65 -42
- package/Components/Screen/__tests__/__snapshots__/Screen.test.tsx.snap +68 -44
- package/Components/Screen/hooks.ts +2 -3
- package/Components/Screen/index.tsx +2 -3
- package/Components/Screen/orientationHandler.ts +3 -3
- package/Components/ScreenResolver/index.tsx +9 -5
- package/Components/ScreenRevealManager/Overlay.tsx +34 -0
- package/Components/ScreenRevealManager/ScreenRevealManager.ts +40 -8
- package/Components/ScreenRevealManager/__tests__/Overlay.test.tsx +88 -0
- package/Components/ScreenRevealManager/__tests__/ScreenRevealManager.test.ts +86 -69
- package/Components/ScreenRevealManager/withScreenRevealManager.tsx +9 -20
- package/Components/Tabs/TabContent.tsx +7 -4
- package/Components/TopCutoffOverlay/__tests__/TopCutoffOverlay.test.tsx +201 -0
- package/Components/TopCutoffOverlay/hooks/__tests__/useMarginTop.test.ts +130 -0
- package/Components/TopCutoffOverlay/hooks/index.ts +1 -0
- package/Components/TopCutoffOverlay/hooks/useMarginTop.ts +59 -0
- package/Components/TopCutoffOverlay/index.tsx +55 -0
- package/Components/Transitioner/index.js +3 -3
- package/Components/VideoModal/ModalAnimation/ModalAnimationContext.tsx +5 -5
- package/Components/VideoModal/hooks/__tests__/useDelayedPlayerDetails.test.ts +15 -7
- package/Components/VideoModal/utils.ts +18 -10
- package/Components/Viewport/ViewportAware/__tests__/viewportAware.test.js +0 -2
- package/Components/Viewport/ViewportAware/index.tsx +16 -7
- package/Components/Viewport/ViewportEvents/__tests__/viewportEvents.test.js +1 -1
- package/Components/ZappFrameworkComponents/BarView/BarView.tsx +4 -6
- package/Components/ZappFrameworkComponents/BarView/__tests__/BarView.test.tsx +2 -2
- package/Components/default-cell-renderer/viewTrees/mobile/index.ts +0 -3
- package/Contexts/ScreenContext/index.tsx +25 -18
- package/Contexts/ScreenTrackedViewPositionsContext/__tests__/index.test.tsx +1 -1
- package/Decorators/Analytics/index.tsx +6 -5
- package/Decorators/ConfigurationWrapper/__tests__/__snapshots__/withConfigurationProvider.test.tsx.snap +1 -0
- package/Decorators/ConfigurationWrapper/const.ts +1 -0
- package/Decorators/ZappPipesDataConnector/__tests__/UrlFeedResolver.test.tsx +39 -21
- package/Decorators/ZappPipesDataConnector/__tests__/zappPipesDataConnector.test.js +1 -1
- package/Decorators/ZappPipesDataConnector/index.tsx +2 -2
- package/Decorators/ZappPipesDataConnector/resolvers/StaticFeedResolver.tsx +1 -1
- package/Decorators/ZappPipesDataConnector/resolvers/UrlFeedResolver.tsx +18 -7
- package/Helpers/DataSourceHelper/__tests__/itemLimitForData.test.ts +80 -0
- package/Helpers/DataSourceHelper/index.ts +19 -0
- package/Helpers/index.js +7 -1
- package/package.json +5 -5
- package/Components/Layout/TV/LayoutBackground.tsx +0 -28
- package/Components/MasterCell/DefaultComponents/tv/ButtonContainerView/index.android.tsx +0 -135
- package/Components/MasterCell/DefaultComponents/tv/ButtonContainerView/types.ts +0 -25
- package/Components/River/TV/withTVEventHandler.tsx +0 -36
- 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
|
-
!
|
|
142
|
-
|
|
143
|
-
hasFocusableInside,
|
|
144
|
-
|
|
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,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 {
|
|
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
|
+
};
|