@applicaster/zapp-react-native-ui-components 15.0.0-rc.129 → 15.0.0-rc.131
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/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/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/PressableView.tsx +196 -0
- package/Components/MasterCell/DefaultComponents/index.ts +2 -0
- package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/Asset.ts +46 -0
- package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/Button.ts +126 -0
- package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/ButtonContainerView.ts +23 -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 +32 -0
- package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/__tests__/PressableView.test.tsx +191 -0
- package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/__tests__/builders.test.ts +140 -0
- package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/__tests__/index.test.ts +222 -0
- package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/helpers.ts +105 -0
- package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/index.ts +104 -0
- package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/utils/__tests__/insertButtons.test.ts +118 -0
- package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/utils/index.ts +73 -0
- package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/__tests__/index.test.ts +86 -0
- package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/index.ts +35 -52
- package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/utils/__tests__/getPluginIdentifier.test.ts +35 -171
- package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/utils/index.ts +36 -145
- package/Components/MasterCell/elementMapper.tsx +1 -0
- package/package.json +5 -5
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { buildActionButtonsModel } from "../model";
|
|
2
|
+
|
|
3
|
+
describe("buildActionButtonsModel", () => {
|
|
4
|
+
it("returns null when the container is disabled", () => {
|
|
5
|
+
const configuration = {
|
|
6
|
+
mobile_buttons_container_buttons_enabled: false,
|
|
7
|
+
mobile_button_1_button_enabled: true,
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
const value = (key) => configuration[key];
|
|
11
|
+
|
|
12
|
+
expect(
|
|
13
|
+
buildActionButtonsModel({
|
|
14
|
+
configuration,
|
|
15
|
+
value,
|
|
16
|
+
containerPrefix: "mobile_buttons_container",
|
|
17
|
+
buttonPrefix: "mobile_button",
|
|
18
|
+
})
|
|
19
|
+
).toBeNull();
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("returns explicit enabled slots and semantic container data", () => {
|
|
23
|
+
const configuration = {
|
|
24
|
+
mobile_buttons_container_buttons_enabled: true,
|
|
25
|
+
mobile_buttons_container_align: "right",
|
|
26
|
+
mobile_buttons_container_margin_top: 1,
|
|
27
|
+
mobile_buttons_container_margin_right: 2,
|
|
28
|
+
mobile_buttons_container_margin_bottom: 3,
|
|
29
|
+
mobile_buttons_container_margin_left: 4,
|
|
30
|
+
mobile_buttons_container_stacking: "vertical",
|
|
31
|
+
mobile_buttons_container_horizontal_gutter: 8,
|
|
32
|
+
mobile_buttons_container_vertical_gutter: 12,
|
|
33
|
+
mobile_buttons_container_independent_styles: false,
|
|
34
|
+
mobile_button_1_button_enabled: true,
|
|
35
|
+
mobile_button_2_button_enabled: false,
|
|
36
|
+
mobile_button_3_button_enabled: true,
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const value = (key) => configuration[key];
|
|
40
|
+
|
|
41
|
+
expect(
|
|
42
|
+
buildActionButtonsModel({
|
|
43
|
+
configuration,
|
|
44
|
+
value,
|
|
45
|
+
containerPrefix: "mobile_buttons_container",
|
|
46
|
+
buttonPrefix: "mobile_button",
|
|
47
|
+
})
|
|
48
|
+
).toEqual({
|
|
49
|
+
enabledSlots: [1, 3],
|
|
50
|
+
buttonsCount: 2,
|
|
51
|
+
container: {
|
|
52
|
+
horizontalAlign: "flex-end",
|
|
53
|
+
margins: {
|
|
54
|
+
top: 1,
|
|
55
|
+
right: 2,
|
|
56
|
+
bottom: 3,
|
|
57
|
+
left: 4,
|
|
58
|
+
},
|
|
59
|
+
stacking: "vertical",
|
|
60
|
+
horizontalGutter: 8,
|
|
61
|
+
verticalGutter: 12,
|
|
62
|
+
independentStyles: false,
|
|
63
|
+
},
|
|
64
|
+
buttons: [
|
|
65
|
+
{
|
|
66
|
+
slot: 1,
|
|
67
|
+
renderIndex: 0,
|
|
68
|
+
specificPrefix: "mobile_button_1",
|
|
69
|
+
stylePrefix: "mobile_button_1",
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
slot: 3,
|
|
73
|
+
renderIndex: 1,
|
|
74
|
+
specificPrefix: "mobile_button_3",
|
|
75
|
+
stylePrefix: "mobile_button_1",
|
|
76
|
+
},
|
|
77
|
+
],
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
});
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import {
|
|
2
|
+
insertBetweenLabelContainers,
|
|
3
|
+
insertBetweenLabels,
|
|
4
|
+
} from "../placement";
|
|
5
|
+
|
|
6
|
+
describe("ActionButtonsCore placement", () => {
|
|
7
|
+
const buttons = { type: "View", name: "buttons" };
|
|
8
|
+
|
|
9
|
+
const above_labels = [
|
|
10
|
+
{ name: "above_label_1" },
|
|
11
|
+
{ name: "above_label_2" },
|
|
12
|
+
{ name: "above_label_3" },
|
|
13
|
+
];
|
|
14
|
+
|
|
15
|
+
const below_labels = [
|
|
16
|
+
{ name: "below_label_1" },
|
|
17
|
+
{ name: "below_label_2" },
|
|
18
|
+
{ name: "below_label_3" },
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
it("inserts buttons after the matching label", () => {
|
|
22
|
+
expect(
|
|
23
|
+
insertBetweenLabels({ position: "below_label_2" }, buttons, below_labels)
|
|
24
|
+
).toEqual([below_labels[0], below_labels[1], buttons, below_labels[2]]);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("inserts buttons before the matching label", () => {
|
|
28
|
+
expect(
|
|
29
|
+
insertBetweenLabels({ position: "above_label_2" }, buttons, above_labels)
|
|
30
|
+
).toEqual([above_labels[0], buttons, above_labels[1], above_labels[2]]);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("prepends buttons only when on_top is allowed", () => {
|
|
34
|
+
expect(
|
|
35
|
+
insertBetweenLabels(
|
|
36
|
+
{ position: "on_top", allowOnTop: true },
|
|
37
|
+
buttons,
|
|
38
|
+
below_labels
|
|
39
|
+
)
|
|
40
|
+
).toEqual([buttons, ...below_labels]);
|
|
41
|
+
|
|
42
|
+
expect(
|
|
43
|
+
insertBetweenLabels(
|
|
44
|
+
{ position: "on_top", allowOnTop: false },
|
|
45
|
+
buttons,
|
|
46
|
+
below_labels
|
|
47
|
+
)
|
|
48
|
+
).toEqual(below_labels);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("appends buttons when appendWhenMissing is enabled", () => {
|
|
52
|
+
expect(
|
|
53
|
+
insertBetweenLabels(
|
|
54
|
+
{ position: "unknown", appendWhenMissing: true },
|
|
55
|
+
buttons,
|
|
56
|
+
below_labels
|
|
57
|
+
)
|
|
58
|
+
).toEqual([...below_labels, buttons]);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("returns labels unchanged when appendWhenMissing is disabled", () => {
|
|
62
|
+
expect(
|
|
63
|
+
insertBetweenLabels(
|
|
64
|
+
{ position: "unknown", appendWhenMissing: false },
|
|
65
|
+
buttons,
|
|
66
|
+
below_labels
|
|
67
|
+
)
|
|
68
|
+
).toEqual(below_labels);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
const labelContainers = [
|
|
72
|
+
{
|
|
73
|
+
elements: [
|
|
74
|
+
{
|
|
75
|
+
elements: [{ name: "top_label_1" }, { name: "top_label_2" }],
|
|
76
|
+
},
|
|
77
|
+
],
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
elements: [
|
|
81
|
+
{
|
|
82
|
+
elements: [{ name: "bottom_label_1" }],
|
|
83
|
+
},
|
|
84
|
+
],
|
|
85
|
+
},
|
|
86
|
+
];
|
|
87
|
+
|
|
88
|
+
it("inserts buttons after the matching label in a nested container", () => {
|
|
89
|
+
expect(
|
|
90
|
+
insertBetweenLabelContainers(
|
|
91
|
+
{ position: "below_top_label_2" },
|
|
92
|
+
buttons,
|
|
93
|
+
labelContainers
|
|
94
|
+
)
|
|
95
|
+
).toEqual([
|
|
96
|
+
{
|
|
97
|
+
elements: [
|
|
98
|
+
{
|
|
99
|
+
elements: [
|
|
100
|
+
{ name: "top_label_1" },
|
|
101
|
+
{ name: "top_label_2" },
|
|
102
|
+
buttons,
|
|
103
|
+
],
|
|
104
|
+
},
|
|
105
|
+
],
|
|
106
|
+
},
|
|
107
|
+
labelContainers[1],
|
|
108
|
+
]);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it("inserts buttons before the matching label in a nested container", () => {
|
|
112
|
+
expect(
|
|
113
|
+
insertBetweenLabelContainers(
|
|
114
|
+
{ position: "above_top_label_2" },
|
|
115
|
+
buttons,
|
|
116
|
+
labelContainers
|
|
117
|
+
)
|
|
118
|
+
).toEqual([
|
|
119
|
+
{
|
|
120
|
+
elements: [
|
|
121
|
+
{
|
|
122
|
+
elements: [
|
|
123
|
+
{ name: "top_label_1" },
|
|
124
|
+
buttons,
|
|
125
|
+
{ name: "top_label_2" },
|
|
126
|
+
],
|
|
127
|
+
},
|
|
128
|
+
],
|
|
129
|
+
},
|
|
130
|
+
labelContainers[1],
|
|
131
|
+
]);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it("prepends buttons into the first container only when on_top is allowed", () => {
|
|
135
|
+
expect(
|
|
136
|
+
insertBetweenLabelContainers(
|
|
137
|
+
{ position: "on_top", allowOnTop: true },
|
|
138
|
+
buttons,
|
|
139
|
+
labelContainers
|
|
140
|
+
)
|
|
141
|
+
).toEqual([
|
|
142
|
+
{
|
|
143
|
+
elements: [buttons, ...labelContainers[0].elements],
|
|
144
|
+
},
|
|
145
|
+
labelContainers[1],
|
|
146
|
+
]);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it("appends buttons into the last container when configured", () => {
|
|
150
|
+
expect(
|
|
151
|
+
insertBetweenLabelContainers(
|
|
152
|
+
{ position: "unknown", appendWhenMissing: true },
|
|
153
|
+
buttons,
|
|
154
|
+
labelContainers
|
|
155
|
+
)
|
|
156
|
+
).toEqual([
|
|
157
|
+
labelContainers[0],
|
|
158
|
+
{
|
|
159
|
+
elements: [...labelContainers[1].elements, buttons],
|
|
160
|
+
},
|
|
161
|
+
]);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it("inserts buttons after a matching label in a direct two-level container", () => {
|
|
165
|
+
const directLabelContainers = [
|
|
166
|
+
{
|
|
167
|
+
elements: [{ name: "top_label_1" }, { name: "top_label_2" }],
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
elements: [{ name: "bottom_label_1" }],
|
|
171
|
+
},
|
|
172
|
+
];
|
|
173
|
+
|
|
174
|
+
expect(
|
|
175
|
+
insertBetweenLabelContainers(
|
|
176
|
+
{ position: "below_top_label_2" },
|
|
177
|
+
buttons,
|
|
178
|
+
directLabelContainers
|
|
179
|
+
)
|
|
180
|
+
).toEqual([
|
|
181
|
+
{
|
|
182
|
+
elements: [{ name: "top_label_1" }, { name: "top_label_2" }, buttons],
|
|
183
|
+
},
|
|
184
|
+
directLabelContainers[1],
|
|
185
|
+
]);
|
|
186
|
+
});
|
|
187
|
+
});
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getButtonSlotPrefix,
|
|
3
|
+
getEnabledButtonSlots,
|
|
4
|
+
getStylePrefix,
|
|
5
|
+
} from "../selectors";
|
|
6
|
+
|
|
7
|
+
describe("ActionButtonsCore selectors", () => {
|
|
8
|
+
it("returns explicit enabled button slots without collapsing sparse slots", () => {
|
|
9
|
+
const configuration = {
|
|
10
|
+
mobile_button_1_button_enabled: true,
|
|
11
|
+
mobile_button_2_button_enabled: false,
|
|
12
|
+
mobile_button_3_button_enabled: true,
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
expect(getEnabledButtonSlots(configuration, "mobile_button")).toEqual([
|
|
16
|
+
1, 3,
|
|
17
|
+
]);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it("returns slot-based prefixes", () => {
|
|
21
|
+
expect(getButtonSlotPrefix("tv_buttons_button", 3)).toBe(
|
|
22
|
+
"tv_buttons_button_3"
|
|
23
|
+
);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("reuses button 1 styles when independent styles are disabled", () => {
|
|
27
|
+
expect(
|
|
28
|
+
getStylePrefix({
|
|
29
|
+
slot: 3,
|
|
30
|
+
independentStyles: false,
|
|
31
|
+
buttonPrefix: "mobile_button",
|
|
32
|
+
})
|
|
33
|
+
).toBe("mobile_button_1");
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("uses the explicit slot prefix when independent styles are enabled", () => {
|
|
37
|
+
expect(
|
|
38
|
+
getStylePrefix({
|
|
39
|
+
slot: 3,
|
|
40
|
+
independentStyles: true,
|
|
41
|
+
buttonPrefix: "mobile_button",
|
|
42
|
+
})
|
|
43
|
+
).toBe("mobile_button_3");
|
|
44
|
+
});
|
|
45
|
+
});
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { buildContainerLayout, getContainerMargins } from "../style";
|
|
2
|
+
|
|
3
|
+
describe("ActionButtonsCore style helpers", () => {
|
|
4
|
+
it("returns numeric margins with zero defaults", () => {
|
|
5
|
+
const configuration = {
|
|
6
|
+
mobile_buttons_container_margin_top: "4",
|
|
7
|
+
mobile_buttons_container_margin_left: 6,
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
const value = (key) => configuration[key];
|
|
11
|
+
|
|
12
|
+
expect(getContainerMargins(value, "mobile_buttons_container")).toEqual({
|
|
13
|
+
top: 4,
|
|
14
|
+
right: 0,
|
|
15
|
+
bottom: 0,
|
|
16
|
+
left: 6,
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it("returns semantic layout data using shared alignment mapping", () => {
|
|
21
|
+
const configuration = {
|
|
22
|
+
tv_buttons_container_align: "middle",
|
|
23
|
+
tv_buttons_container_margin_top: 1,
|
|
24
|
+
tv_buttons_container_margin_right: 2,
|
|
25
|
+
tv_buttons_container_margin_bottom: 3,
|
|
26
|
+
tv_buttons_container_margin_left: 4,
|
|
27
|
+
tv_buttons_container_stacking: "vertical",
|
|
28
|
+
tv_buttons_container_horizontal_gutter: 8,
|
|
29
|
+
tv_buttons_container_vertical_gutter: 12,
|
|
30
|
+
tv_buttons_container_independent_styles: true,
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const value = (key) => configuration[key];
|
|
34
|
+
|
|
35
|
+
expect(buildContainerLayout(value, "tv_buttons_container")).toEqual({
|
|
36
|
+
horizontalAlign: "center",
|
|
37
|
+
margins: {
|
|
38
|
+
top: 1,
|
|
39
|
+
right: 2,
|
|
40
|
+
bottom: 3,
|
|
41
|
+
left: 4,
|
|
42
|
+
},
|
|
43
|
+
stacking: "vertical",
|
|
44
|
+
horizontalGutter: 8,
|
|
45
|
+
verticalGutter: 12,
|
|
46
|
+
independentStyles: true,
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
});
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getButtonSlotPrefix,
|
|
3
|
+
getEnabledButtonSlots,
|
|
4
|
+
getStylePrefix,
|
|
5
|
+
} from "./selectors";
|
|
6
|
+
import { buildContainerLayout } from "./style";
|
|
7
|
+
import { ActionButtonsModel, BuildActionButtonsModelOptions } from "./types";
|
|
8
|
+
|
|
9
|
+
export const buildActionButtonsModel = ({
|
|
10
|
+
configuration,
|
|
11
|
+
value,
|
|
12
|
+
containerPrefix,
|
|
13
|
+
buttonPrefix,
|
|
14
|
+
maxButtons = 3,
|
|
15
|
+
}: BuildActionButtonsModelOptions): ActionButtonsModel | null => {
|
|
16
|
+
if (!value(`${containerPrefix}_buttons_enabled`)) {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const enabledSlots = getEnabledButtonSlots(
|
|
21
|
+
configuration,
|
|
22
|
+
buttonPrefix,
|
|
23
|
+
maxButtons
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
if (enabledSlots.length === 0) {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const container = buildContainerLayout(value, containerPrefix);
|
|
31
|
+
|
|
32
|
+
return {
|
|
33
|
+
enabledSlots,
|
|
34
|
+
buttonsCount: enabledSlots.length,
|
|
35
|
+
container,
|
|
36
|
+
buttons: enabledSlots.map((slot, renderIndex) => ({
|
|
37
|
+
slot,
|
|
38
|
+
renderIndex,
|
|
39
|
+
specificPrefix: getButtonSlotPrefix(buttonPrefix, slot),
|
|
40
|
+
stylePrefix: getStylePrefix({
|
|
41
|
+
slot,
|
|
42
|
+
independentStyles: container.independentStyles,
|
|
43
|
+
buttonPrefix,
|
|
44
|
+
}),
|
|
45
|
+
})),
|
|
46
|
+
};
|
|
47
|
+
};
|
|
@@ -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
|
+
};
|