@applicaster/zapp-react-native-utils 15.0.0-rc.14 → 15.0.0-rc.140

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 (177) hide show
  1. package/README.md +0 -6
  2. package/actionsExecutor/ActionExecutorContext.tsx +86 -12
  3. package/actionsExecutor/feedDecorator.ts +6 -6
  4. package/adsUtils/__tests__/createVMAP.test.ts +419 -0
  5. package/adsUtils/index.ts +2 -2
  6. package/analyticsUtils/README.md +1 -1
  7. package/analyticsUtils/analyticsMapper.ts +10 -2
  8. package/appDataUtils/__tests__/urlScheme.test.ts +678 -0
  9. package/appUtils/HooksManager/__tests__/__snapshots__/hooksManager.test.js.snap +0 -188
  10. package/appUtils/HooksManager/__tests__/hooksManager.test.js +16 -2
  11. package/appUtils/HooksManager/index.ts +45 -10
  12. package/appUtils/RiverFocusManager/{index.js → index.ts} +25 -18
  13. package/appUtils/accessibilityManager/__tests__/utils.test.ts +360 -0
  14. package/appUtils/accessibilityManager/const.ts +4 -0
  15. package/appUtils/accessibilityManager/hooks.ts +20 -13
  16. package/appUtils/accessibilityManager/index.ts +28 -1
  17. package/appUtils/accessibilityManager/utils.ts +59 -8
  18. package/appUtils/contextKeysManager/__tests__/getKeys/failure.test.ts +7 -2
  19. package/appUtils/contextKeysManager/__tests__/getKeys/success.test.ts +48 -0
  20. package/appUtils/contextKeysManager/contextResolver.ts +51 -22
  21. package/appUtils/contextKeysManager/index.ts +65 -10
  22. package/appUtils/focusManager/__tests__/__snapshots__/focusManager.test.js.snap +3 -0
  23. package/appUtils/focusManager/index.ios.ts +43 -4
  24. package/appUtils/focusManager/treeDataStructure/Tree/__tests__/Tree.test.js +46 -0
  25. package/appUtils/focusManager/treeDataStructure/Tree/index.js +18 -18
  26. package/appUtils/focusManagerAux/utils/index.ios.ts +122 -0
  27. package/appUtils/focusManagerAux/utils/index.ts +13 -7
  28. package/appUtils/focusManagerAux/utils/utils.ios.ts +202 -3
  29. package/appUtils/keyCodes/keys/keys.web.ts +1 -4
  30. package/appUtils/localizationsHelper.ts +4 -0
  31. package/appUtils/orientationHelper.ts +2 -4
  32. package/appUtils/platform/platformUtils.ts +117 -18
  33. package/appUtils/playerManager/OverlayObserver/OverlaysObserver.ts +94 -4
  34. package/appUtils/playerManager/OverlayObserver/utils.ts +32 -20
  35. package/appUtils/playerManager/index.ts +9 -0
  36. package/appUtils/playerManager/player.ts +5 -1
  37. package/appUtils/playerManager/playerNative.ts +31 -17
  38. package/appUtils/playerManager/usePlayer.tsx +5 -3
  39. package/appUtils/playerManager/usePlayerState.tsx +14 -2
  40. package/arrayUtils/__tests__/allTruthy.test.ts +24 -0
  41. package/arrayUtils/__tests__/anyThruthy.test.ts +24 -0
  42. package/arrayUtils/index.ts +5 -0
  43. package/cellUtils/__tests__/cellUtils.test.ts +39 -0
  44. package/cellUtils/index.ts +43 -1
  45. package/cloudEventsUtils/__tests__/index.test.ts +529 -0
  46. package/cloudEventsUtils/index.ts +65 -1
  47. package/componentsUtils/index.ts +8 -0
  48. package/configurationUtils/__tests__/imageSrcFromMediaItem.test.ts +38 -0
  49. package/configurationUtils/__tests__/manifestKeyParser.test.ts +26 -26
  50. package/configurationUtils/index.ts +17 -11
  51. package/dateUtils/__tests__/dayjs.test.ts +327 -0
  52. package/dateUtils/index.ts +2 -0
  53. package/enumUtils/__tests__/getEnumKeyByEnumValue.test.ts +207 -0
  54. package/errorUtils/__tests__/GeneralError.test.ts +97 -0
  55. package/errorUtils/__tests__/HttpStatusCode.test.ts +344 -0
  56. package/errorUtils/__tests__/MissingPluginError.test.ts +113 -0
  57. package/errorUtils/__tests__/NetworkError.test.ts +202 -0
  58. package/errorUtils/__tests__/getParsedResponse.test.ts +188 -0
  59. package/errorUtils/__tests__/invariant.test.ts +112 -0
  60. package/focusManager/aux/index.ts +1 -1
  61. package/headersUtils/__tests__/headersUtils.test.js +11 -1
  62. package/headersUtils/index.ts +2 -1
  63. package/manifestUtils/_internals/__tests__/index.test.js +41 -0
  64. package/manifestUtils/_internals/index.js +33 -0
  65. package/manifestUtils/defaultManifestConfigurations/player.js +115 -11
  66. package/manifestUtils/fieldUtils/__tests__/fieldUtils.test.js +49 -0
  67. package/manifestUtils/fieldUtils/index.js +54 -0
  68. package/manifestUtils/index.js +2 -0
  69. package/manifestUtils/keys.js +249 -0
  70. package/manifestUtils/mobileAction/button/__tests__/mobileActionButton.test.js +168 -0
  71. package/manifestUtils/mobileAction/button/index.js +140 -0
  72. package/manifestUtils/mobileAction/container/__tests__/mobileActionButtonsContainer.test.js +102 -0
  73. package/manifestUtils/mobileAction/container/index.js +73 -0
  74. package/manifestUtils/mobileAction/groups/__tests__/buildMobileActionButtonGroups.test.js +127 -0
  75. package/manifestUtils/mobileAction/groups/defaults.js +76 -0
  76. package/manifestUtils/mobileAction/groups/index.js +80 -0
  77. package/manifestUtils/platformIsTV.js +13 -0
  78. package/manifestUtils/sharedConfiguration/screenPicker/utils.js +1 -0
  79. package/manifestUtils/tvAction/container/index.js +1 -1
  80. package/navigationUtils/index.ts +15 -5
  81. package/numberUtils/__tests__/toNumber.test.ts +27 -0
  82. package/numberUtils/__tests__/toPositiveNumber.test.ts +193 -0
  83. package/numberUtils/index.ts +23 -1
  84. package/package.json +4 -4
  85. package/playerUtils/usePlayerTTS.ts +8 -3
  86. package/pluginUtils/index.ts +4 -0
  87. package/reactHooks/advertising/index.ts +2 -2
  88. package/reactHooks/analytics/__tests__/useSendAnalyticsOnPress.test.ts +537 -0
  89. package/reactHooks/app/__tests__/useAppState.test.ts +1 -1
  90. package/reactHooks/autoscrolling/__tests__/useTrackCurrentAutoScrollingElement.test.ts +1 -1
  91. package/reactHooks/autoscrolling/__tests__/useTrackedView.test.tsx +1 -2
  92. package/reactHooks/cell-click/__tests__/index.test.js +1 -3
  93. package/reactHooks/cell-click/index.ts +2 -1
  94. package/reactHooks/configuration/__tests__/index.test.tsx +1 -1
  95. package/reactHooks/connection/__tests__/index.test.js +1 -1
  96. package/reactHooks/debugging/__tests__/index.test.js +4 -4
  97. package/reactHooks/dev/__tests__/useReRenderLog.test.ts +188 -0
  98. package/reactHooks/device/useIsTablet.tsx +14 -19
  99. package/reactHooks/device/useMemoizedIsTablet.ts +3 -3
  100. package/reactHooks/events/index.ts +20 -0
  101. package/reactHooks/feed/__tests__/useBatchLoading.test.tsx +32 -23
  102. package/reactHooks/feed/__tests__/useBuildPipesUrl.test.tsx +19 -19
  103. package/reactHooks/feed/__tests__/useEntryScreenId.test.tsx +4 -1
  104. package/reactHooks/feed/__tests__/useFeedLoader.test.tsx +42 -30
  105. package/reactHooks/feed/__tests__/{useInflatedUrl.test.ts → useInflatedUrl.test.tsx} +62 -7
  106. package/reactHooks/feed/index.ts +0 -2
  107. package/reactHooks/feed/useBatchLoading.ts +7 -1
  108. package/reactHooks/feed/useEntryScreenId.ts +2 -2
  109. package/reactHooks/feed/useInflatedUrl.ts +44 -18
  110. package/reactHooks/feed/usePipesCacheReset.ts +3 -1
  111. package/reactHooks/flatList/useLoadNextPageIfNeeded.ts +13 -16
  112. package/reactHooks/hookModal/hooks/useHookModalScreenData.ts +12 -8
  113. package/reactHooks/index.ts +2 -0
  114. package/reactHooks/layout/__tests__/index.test.tsx +1 -1
  115. package/reactHooks/layout/__tests__/useLayoutVersion.test.tsx +1 -1
  116. package/reactHooks/layout/index.ts +1 -1
  117. package/reactHooks/layout/useDimensions/__tests__/{useDimensions.test.ts → useDimensions.test.tsx} +105 -25
  118. package/reactHooks/layout/useDimensions/useDimensions.ts +2 -2
  119. package/reactHooks/navigation/__tests__/index.test.tsx +40 -9
  120. package/reactHooks/navigation/index.ts +27 -11
  121. package/reactHooks/navigation/useRoute.ts +11 -7
  122. package/reactHooks/player/TVSeekControlller/TVSeekController.ts +27 -10
  123. package/reactHooks/player/__tests__/useAutoSeek._test.tsx +1 -1
  124. package/reactHooks/player/__tests__/useTapSeek._test.ts +1 -1
  125. package/reactHooks/resolvers/__tests__/useCellResolver.test.tsx +1 -1
  126. package/reactHooks/resolvers/__tests__/useComponentResolver.test.tsx +1 -1
  127. package/reactHooks/resolvers/useCellResolver.ts +6 -2
  128. package/reactHooks/resolvers/useComponentResolver.ts +19 -3
  129. package/reactHooks/screen/__tests__/useCurrentScreenData.test.tsx +2 -2
  130. package/reactHooks/screen/__tests__/useScreenBackgroundColor.test.tsx +1 -1
  131. package/reactHooks/screen/__tests__/useScreenData.test.tsx +1 -1
  132. package/reactHooks/screen/__tests__/useTargetScreenData.test.tsx +12 -4
  133. package/reactHooks/screen/useTargetScreenData.ts +4 -2
  134. package/reactHooks/state/__tests__/useComponentScreenState.test.ts +246 -0
  135. package/reactHooks/state/index.ts +2 -0
  136. package/reactHooks/state/useComponentScreenState.ts +45 -0
  137. package/reactHooks/state/useRefWithInitialValue.ts +10 -0
  138. package/reactHooks/state/useRivers.ts +1 -1
  139. package/reactHooks/ui/__tests__/useFadeOutWhenBlurred.test.ts +580 -0
  140. package/reactHooks/usePluginConfiguration.ts +2 -2
  141. package/reactHooks/utils/__tests__/index.test.js +1 -1
  142. package/rectUtils/__tests__/index.test.ts +549 -0
  143. package/rectUtils/index.ts +2 -2
  144. package/refreshUtils/RefreshCoordinator/__tests__/refreshCoordinator.test.ts +206 -0
  145. package/refreshUtils/RefreshCoordinator/index.ts +245 -0
  146. package/refreshUtils/RefreshCoordinator/utils/__tests__/getDataRefreshConfig.test.ts +104 -0
  147. package/refreshUtils/RefreshCoordinator/utils/index.ts +29 -0
  148. package/screenPickerUtils/__tests__/index.test.ts +333 -0
  149. package/screenPickerUtils/index.ts +5 -0
  150. package/screenState/__tests__/index.test.ts +1 -1
  151. package/screenUtils/index.ts +3 -0
  152. package/searchUtils/const.ts +7 -0
  153. package/searchUtils/index.ts +3 -0
  154. package/services/storageServiceSync.web.ts +1 -1
  155. package/stringUtils/index.ts +1 -1
  156. package/testUtils/index.tsx +30 -21
  157. package/time/__tests__/BackgroundTimer.test.ts +156 -0
  158. package/time/__tests__/Timer.test.ts +236 -0
  159. package/typeGuards/__tests__/isString.test.ts +21 -0
  160. package/typeGuards/index.ts +4 -0
  161. package/utils/__tests__/clone.test.ts +158 -0
  162. package/utils/__tests__/mapAccum.test.ts +73 -0
  163. package/utils/__tests__/mergeRight.test.ts +48 -0
  164. package/utils/__tests__/path.test.ts +7 -0
  165. package/utils/__tests__/selectors.test.ts +124 -0
  166. package/utils/clone.ts +7 -0
  167. package/utils/index.ts +21 -1
  168. package/utils/mapAccum.ts +23 -0
  169. package/utils/mergeRight.ts +5 -0
  170. package/utils/path.ts +6 -3
  171. package/utils/pathOr.ts +5 -1
  172. package/utils/selectors.ts +46 -0
  173. package/zappFrameworkUtils/HookCallback/callbackNavigationAction.ts +49 -12
  174. package/zappFrameworkUtils/HookCallback/hookCallbackManifestExtensions.config.js +1 -1
  175. package/reactHooks/componentsMap/index.ts +0 -55
  176. package/reactHooks/feed/__tests__/useFeedRefresh.test.tsx +0 -75
  177. package/reactHooks/feed/useFeedRefresh.tsx +0 -65
@@ -0,0 +1,168 @@
1
+ import { mobileActionButton } from "..";
2
+
3
+ describe("mobileActionButton", () => {
4
+ const defaults = {
5
+ buttonEnabled: true,
6
+ assignAction: "navigation_action",
7
+ displayMode: "dynamic",
8
+ width: 140,
9
+ contentsAlignment: "center",
10
+ backgroundColor: "rgba(1,1,1,1)",
11
+ focusedBackgroundColor: "rgba(2,2,2,1)",
12
+ borderColor: "rgba(0,0,0,0)",
13
+ focusedBorderColor: "rgba(0,0,0,0)",
14
+ borderSize: 0,
15
+ cornerRadius: 8,
16
+ paddingTop: 14,
17
+ paddingRight: 24,
18
+ paddingBottom: 14,
19
+ paddingLeft: 16,
20
+ assetEnabled: true,
21
+ actionAssetFlavour: "flavour_1",
22
+ assetAlignment: "left",
23
+ assetHeight: 24,
24
+ assetWidth: 24,
25
+ assetMarginTop: 0,
26
+ assetMarginRight: 6,
27
+ assetMarginBottom: 0,
28
+ assetMarginLeft: 0,
29
+ labelEnabled: true,
30
+ fontColor: "rgba(239,239,239,1)",
31
+ focusedFontColor: "rgba(239,239,239,1)",
32
+ iosFontFamily: "Ubuntu-Bold",
33
+ androidFontFamily: "Ubuntu-Bold",
34
+ fontSize: 15,
35
+ lineHeight: 24,
36
+ iosLetterSpacing: -0.2,
37
+ androidLetterSpacing: -0.2,
38
+ textTransform: "default",
39
+ marginTop: 0,
40
+ marginRight: 0,
41
+ marginBottom: 0,
42
+ marginLeft: 0,
43
+ };
44
+
45
+ it("generates button fields with inferred conditional rules", () => {
46
+ const result = mobileActionButton({
47
+ label: "Mobile Button 2",
48
+ description: "button 2",
49
+ defaults,
50
+ isFirstButton: false,
51
+ });
52
+
53
+ expect(result.group).toBe(true);
54
+
55
+ const enabledField = result.fields.find(
56
+ (field) => field.key === "mobile_button_2_button_enabled"
57
+ );
58
+
59
+ expect(enabledField).toBeTruthy();
60
+
61
+ expect(enabledField.conditional_fields).toEqual([
62
+ {
63
+ key: "styles/mobile_buttons_container_buttons_enabled",
64
+ condition_value: true,
65
+ },
66
+ ]);
67
+
68
+ const assignActionField = result.fields.find(
69
+ (field) => field.key === "mobile_button_2_assign_action"
70
+ );
71
+
72
+ expect(assignActionField).toBeTruthy();
73
+
74
+ expect(assignActionField.conditional_fields).toEqual([
75
+ {
76
+ key: "styles/mobile_button_2_button_enabled",
77
+ condition_value: true,
78
+ },
79
+ ]);
80
+
81
+ const widthField = result.fields.find(
82
+ (field) => field.key === "mobile_button_2_width"
83
+ );
84
+
85
+ expect(widthField.rules).toBe("all_conditions");
86
+
87
+ expect(widthField.conditional_fields).toEqual([
88
+ {
89
+ key: "styles/mobile_button_2_button_enabled",
90
+ condition_value: true,
91
+ },
92
+ {
93
+ key: "styles/mobile_buttons_container_independent_styles",
94
+ condition_value: true,
95
+ },
96
+ {
97
+ key: "styles/mobile_button_2_display_mode",
98
+ condition_value: "fixed",
99
+ },
100
+ ]);
101
+
102
+ const actionAssetFlavourField = result.fields.find(
103
+ (field) => field.key === "mobile_button_2_action_asset_flavour"
104
+ );
105
+
106
+ expect(actionAssetFlavourField.rules).toBe("all_conditions");
107
+
108
+ expect(actionAssetFlavourField.conditional_fields).toEqual([
109
+ {
110
+ key: "styles/mobile_button_2_button_enabled",
111
+ condition_value: true,
112
+ },
113
+ {
114
+ key: "styles/mobile_buttons_container_independent_styles",
115
+ condition_value: true,
116
+ },
117
+ {
118
+ key: "styles/mobile_button_2_asset_enabled",
119
+ condition_value: true,
120
+ },
121
+ ]);
122
+
123
+ const contentsAlignmentField = result.fields.find(
124
+ (field) => field.key === "mobile_button_2_contents_alignment"
125
+ );
126
+
127
+ expect(contentsAlignmentField.rules).toBe("all_conditions");
128
+
129
+ expect(contentsAlignmentField.conditional_fields).toEqual([
130
+ {
131
+ key: "styles/mobile_button_2_button_enabled",
132
+ condition_value: true,
133
+ },
134
+ {
135
+ key: "styles/mobile_buttons_container_independent_styles",
136
+ condition_value: true,
137
+ },
138
+ {
139
+ key: "styles/mobile_button_2_display_mode",
140
+ condition_value: ["fixed", "fill"],
141
+ },
142
+ ]);
143
+ });
144
+
145
+ it("keeps first-button fields independent-style agnostic", () => {
146
+ const result = mobileActionButton({
147
+ label: "Mobile Button 1",
148
+ description: "button 1",
149
+ defaults,
150
+ isFirstButton: true,
151
+ });
152
+
153
+ const widthField = result.fields.find(
154
+ (field) => field.key === "mobile_button_1_width"
155
+ );
156
+
157
+ expect(widthField.conditional_fields).toEqual([
158
+ {
159
+ key: "styles/mobile_button_1_button_enabled",
160
+ condition_value: true,
161
+ },
162
+ {
163
+ key: "styles/mobile_button_1_display_mode",
164
+ condition_value: "fixed",
165
+ },
166
+ ]);
167
+ });
168
+ });
@@ -0,0 +1,140 @@
1
+ const { MOBILE_ACTION_BUTTON_FIELDS } = require("../../keys");
2
+
3
+ const {
4
+ isKeyHasSuffix,
5
+ toSnakeCase,
6
+ generateFieldsFromDefaultsWithoutPrefixedLabel,
7
+ getKeyWithPrefixGenerator,
8
+ isKeyHasAnyOfSuffixes,
9
+ } = require("../../_internals");
10
+
11
+ const { withConditional, createConditionalField } = require("../../fieldUtils");
12
+
13
+ const { fieldsGroup } = require("../../utils");
14
+
15
+ /**
16
+ * Builds a manifest group for a single mobile action button and applies the
17
+ * conditional visibility rules between container, button, asset, and label fields.
18
+ *
19
+ * @param {object} options
20
+ * @param {string} options.label
21
+ * @param {string} options.description
22
+ * @param {object} options.defaults
23
+ * @param {boolean} options.isFirstButton
24
+ * @returns {object}
25
+ */
26
+ function mobileActionButton({ label, description, defaults, isFirstButton }) {
27
+ const buttonKeyFromLabel = toSnakeCase(label); // "Mobile Button 1" -> "mobile_button_1"
28
+
29
+ const generatedFields = generateFieldsFromDefaultsWithoutPrefixedLabel(
30
+ label,
31
+ defaults,
32
+ MOBILE_ACTION_BUTTON_FIELDS
33
+ );
34
+
35
+ const keyPrefixGenerator = getKeyWithPrefixGenerator(buttonKeyFromLabel);
36
+
37
+ const containerKeyPrefixGenerator = getKeyWithPrefixGenerator(
38
+ "mobile_buttons_container"
39
+ );
40
+
41
+ const containerEnabledKey = containerKeyPrefixGenerator("buttons_enabled");
42
+
43
+ const independentStylesKey =
44
+ containerKeyPrefixGenerator("independent_styles");
45
+
46
+ const buttonEnabledKey = keyPrefixGenerator("button_enabled");
47
+ const displayModeKey = keyPrefixGenerator("display_mode");
48
+ const assetEnabledKey = keyPrefixGenerator("asset_enabled");
49
+ const labelEnabledKey = keyPrefixGenerator("label_enabled");
50
+
51
+ const defaultButtonConditions = [
52
+ createConditionalField(buttonEnabledKey, true),
53
+ ];
54
+
55
+ const fields = generatedFields.map((field) => {
56
+ const key = field.key;
57
+
58
+ // button_enabled has to be always visible when the container is enabled, as it's the main toggle for the button
59
+ if (isKeyHasSuffix("button_enabled", key)) {
60
+ return withConditional([
61
+ createConditionalField(containerEnabledKey, true),
62
+ ])(field);
63
+ }
64
+
65
+ // for button at index 1(isFirstButton) return `defaultButtonsConditions`,
66
+ // the rest buttons lookup depends on independent styles toggle
67
+ const stylesConditions = isFirstButton
68
+ ? defaultButtonConditions
69
+ : [
70
+ ...defaultButtonConditions,
71
+ createConditionalField(independentStylesKey, true),
72
+ ];
73
+
74
+ // assign_action depends only on button_enabled
75
+ if (isKeyHasSuffix("assign_action", key)) {
76
+ return withConditional(defaultButtonConditions)(field);
77
+ }
78
+
79
+ const conditions = [...stylesConditions];
80
+
81
+ // width depends on [display_mode: fixed]
82
+ if (isKeyHasSuffix("width", key)) {
83
+ conditions.push(createConditionalField(displayModeKey, "fixed"));
84
+ }
85
+
86
+ // contents_alignment depends on [display_mode: fixed or display_mode: fill]
87
+ if (isKeyHasSuffix("contents_alignment", key)) {
88
+ conditions.push(
89
+ createConditionalField(displayModeKey, ["fixed", "fill"])
90
+ );
91
+ }
92
+
93
+ // asset styling fields depend on [asset_enabled: true]
94
+ if (
95
+ isKeyHasAnyOfSuffixes(
96
+ [
97
+ "action_asset_flavour",
98
+ "asset_alignment",
99
+ "asset_height",
100
+ "asset_width",
101
+ "asset_margin_top",
102
+ "asset_margin_right",
103
+ "asset_margin_bottom",
104
+ "asset_margin_left",
105
+ ],
106
+ key
107
+ )
108
+ ) {
109
+ conditions.push(createConditionalField(assetEnabledKey, true));
110
+ }
111
+
112
+ // label styling fields depend on [label_enabled: true]
113
+ if (
114
+ isKeyHasAnyOfSuffixes(
115
+ [
116
+ "font_color",
117
+ "focused_font_color",
118
+ "ios_font_family",
119
+ "android_font_family",
120
+ "font_size",
121
+ "line_height",
122
+ "ios_letter_spacing",
123
+ "android_letter_spacing",
124
+ "text_transform",
125
+ ],
126
+ key
127
+ )
128
+ ) {
129
+ conditions.push(createConditionalField(labelEnabledKey, true));
130
+ }
131
+
132
+ return withConditional(conditions)(field);
133
+ });
134
+
135
+ return fieldsGroup(label, description, fields);
136
+ }
137
+
138
+ module.exports = {
139
+ mobileActionButton,
140
+ };
@@ -0,0 +1,102 @@
1
+ import { mobileActionButtonsContainer } from "..";
2
+
3
+ describe("mobileActionButtonsContainer", () => {
4
+ it("generates container fields with inferred conditionals", () => {
5
+ const result = mobileActionButtonsContainer({
6
+ label: "Mobile Buttons Container",
7
+ description: "container",
8
+ defaults: {
9
+ buttonsEnabled: false,
10
+ position: ["over_image", "text_label_1", "text_label_2"],
11
+ align: "left",
12
+ overImagePosition: "bottom_left",
13
+ marginTop: 0,
14
+ marginRight: 20,
15
+ marginBottom: 0,
16
+ marginLeft: 20,
17
+ stacking: "horizontal",
18
+ horizontalGutter: 8,
19
+ verticalGutter: 8,
20
+ independentStyles: true,
21
+ },
22
+ });
23
+
24
+ expect(result.group).toBe(true);
25
+ expect(result.label).toBe("Mobile Buttons Container");
26
+
27
+ const enabledField = result.fields.find(
28
+ (field) => field.key === "mobile_buttons_container_buttons_enabled"
29
+ );
30
+
31
+ expect(enabledField).toBeTruthy();
32
+ expect(enabledField.type).toBe("switch");
33
+
34
+ const positionField = result.fields.find(
35
+ (field) => field.key === "mobile_buttons_container_position"
36
+ );
37
+
38
+ expect(positionField.options).toEqual([
39
+ "over_image",
40
+ "text_label_1",
41
+ "text_label_2",
42
+ ]);
43
+
44
+ expect(positionField.conditional_fields).toEqual([
45
+ {
46
+ key: "styles/mobile_buttons_container_buttons_enabled",
47
+ condition_value: true,
48
+ },
49
+ ]);
50
+
51
+ const overImagePositionField = result.fields.find(
52
+ (field) => field.key === "mobile_buttons_container_over_image_position"
53
+ );
54
+
55
+ expect(overImagePositionField.rules).toBe("all_conditions");
56
+
57
+ expect(overImagePositionField.conditional_fields).toEqual([
58
+ {
59
+ key: "styles/mobile_buttons_container_buttons_enabled",
60
+ condition_value: true,
61
+ },
62
+ {
63
+ key: "styles/mobile_buttons_container_position",
64
+ condition_value: "over_image",
65
+ },
66
+ ]);
67
+
68
+ const horizontalGutterField = result.fields.find(
69
+ (field) => field.key === "mobile_buttons_container_horizontal_gutter"
70
+ );
71
+
72
+ expect(horizontalGutterField.rules).toBe("all_conditions");
73
+
74
+ expect(horizontalGutterField.conditional_fields).toEqual([
75
+ {
76
+ key: "styles/mobile_buttons_container_buttons_enabled",
77
+ condition_value: true,
78
+ },
79
+ {
80
+ key: "styles/mobile_buttons_container_stacking",
81
+ condition_value: "horizontal",
82
+ },
83
+ ]);
84
+
85
+ const verticalGutterField = result.fields.find(
86
+ (field) => field.key === "mobile_buttons_container_vertical_gutter"
87
+ );
88
+
89
+ expect(verticalGutterField.rules).toBe("all_conditions");
90
+
91
+ expect(verticalGutterField.conditional_fields).toEqual([
92
+ {
93
+ key: "styles/mobile_buttons_container_buttons_enabled",
94
+ condition_value: true,
95
+ },
96
+ {
97
+ key: "styles/mobile_buttons_container_stacking",
98
+ condition_value: "vertical",
99
+ },
100
+ ]);
101
+ });
102
+ });
@@ -0,0 +1,73 @@
1
+ const { mobileActionButtonContainerFields } = require("../../keys");
2
+
3
+ const {
4
+ isKeyHasSuffix,
5
+ toSnakeCase,
6
+ generateFieldsFromDefaultsWithoutPrefixedLabel,
7
+ getKeyWithPrefixGenerator,
8
+ } = require("../../_internals");
9
+
10
+ const { withConditional, createConditionalField } = require("../../fieldUtils");
11
+
12
+ const { fieldsGroup } = require("../../utils");
13
+
14
+ /**
15
+ * Builds the manifest group for the mobile action buttons container and wires
16
+ * child field visibility to the container-level toggles and layout selections.
17
+ *
18
+ * @param {object} options
19
+ * @param {string} options.label
20
+ * @param {string} options.description
21
+ * @param {object} options.defaults
22
+ * @returns {object}
23
+ */
24
+ function mobileActionButtonsContainer({ label, description, defaults }) {
25
+ const containerKeyFromLabel = toSnakeCase(label); // "Mobile Buttons Container" -> "mobile_buttons_container"
26
+
27
+ const generatedFields = generateFieldsFromDefaultsWithoutPrefixedLabel(
28
+ label,
29
+ defaults,
30
+ mobileActionButtonContainerFields(defaults.position)
31
+ );
32
+
33
+ const keyPrefixGenerator = getKeyWithPrefixGenerator(containerKeyFromLabel);
34
+
35
+ const enabledKey = keyPrefixGenerator("buttons_enabled");
36
+ const positionKey = keyPrefixGenerator("position");
37
+ const stackingKey = keyPrefixGenerator("stacking");
38
+
39
+ const defaultConditions = [createConditionalField(enabledKey, true)];
40
+
41
+ const fields = generatedFields.map((field) => {
42
+ const key = field.key;
43
+
44
+ if (isKeyHasSuffix("buttons_enabled", key)) {
45
+ return field;
46
+ }
47
+
48
+ const conditions = [...defaultConditions];
49
+
50
+ // over_image_position depends on [positionKey: over_image]
51
+ if (isKeyHasSuffix("over_image_position", key)) {
52
+ conditions.push(createConditionalField(positionKey, "over_image"));
53
+ }
54
+
55
+ // horizontal_gutter depends on [stackingKey: horizontal]
56
+ if (isKeyHasSuffix("horizontal_gutter", key)) {
57
+ conditions.push(createConditionalField(stackingKey, "horizontal"));
58
+ }
59
+
60
+ // vertical_gutter depends on [stackingKey: vertical]
61
+ if (isKeyHasSuffix("vertical_gutter", key)) {
62
+ conditions.push(createConditionalField(stackingKey, "vertical"));
63
+ }
64
+
65
+ return withConditional(conditions)(field);
66
+ });
67
+
68
+ return fieldsGroup(label, description, fields);
69
+ }
70
+
71
+ module.exports = {
72
+ mobileActionButtonsContainer,
73
+ };
@@ -0,0 +1,127 @@
1
+ import { buildMobileActionButtonGroups } from "..";
2
+
3
+ describe("buildMobileActionButtonGroups", () => {
4
+ it("builds the container and three button groups with shared defaults", () => {
5
+ const groups = buildMobileActionButtonGroups({
6
+ containerDefaults: {
7
+ position: ["over_image", "text_label_1", "text_label_2"],
8
+ },
9
+ });
10
+
11
+ expect(groups).toHaveLength(4);
12
+
13
+ expect(groups.map((group) => group.label)).toEqual([
14
+ "Mobile Buttons Container",
15
+ "Mobile Button 1",
16
+ "Mobile Button 2",
17
+ "Mobile Button 3",
18
+ ]);
19
+
20
+ const containerFields = groups[0].fields;
21
+
22
+ const positionField = containerFields.find(
23
+ (field) => field.key === "mobile_buttons_container_position"
24
+ );
25
+
26
+ const buttonsEnabledField = containerFields.find(
27
+ (field) => field.key === "mobile_buttons_container_buttons_enabled"
28
+ );
29
+
30
+ expect(positionField.options).toEqual([
31
+ "over_image",
32
+ "text_label_1",
33
+ "text_label_2",
34
+ ]);
35
+
36
+ expect(buttonsEnabledField.initial_value).toBe(false);
37
+
38
+ const button1Fields = groups[1].fields;
39
+
40
+ const button1EnabledField = button1Fields.find(
41
+ (field) => field.key === "mobile_button_1_button_enabled"
42
+ );
43
+
44
+ const button1AssignActionField = button1Fields.find(
45
+ (field) => field.key === "mobile_button_1_assign_action"
46
+ );
47
+
48
+ const button1BackgroundColorField = button1Fields.find(
49
+ (field) => field.key === "mobile_button_1_background_color"
50
+ );
51
+
52
+ expect(button1EnabledField.initial_value).toBe(true);
53
+ expect(button1AssignActionField.initial_value).toBe("navigation_action");
54
+
55
+ expect(button1BackgroundColorField.initial_value).toBe(
56
+ "rgba(254, 20, 72, 1)"
57
+ );
58
+
59
+ const button2Fields = groups[2].fields;
60
+
61
+ const button2EnabledField = button2Fields.find(
62
+ (field) => field.key === "mobile_button_2_button_enabled"
63
+ );
64
+
65
+ const button2AssignActionField = button2Fields.find(
66
+ (field) => field.key === "mobile_button_2_assign_action"
67
+ );
68
+
69
+ expect(button2EnabledField.initial_value).toBe(false);
70
+ expect(button2AssignActionField.initial_value).toBe("secondary_navigation");
71
+
72
+ const button3Fields = groups[3].fields;
73
+
74
+ const button3EnabledField = button3Fields.find(
75
+ (field) => field.key === "mobile_button_3_button_enabled"
76
+ );
77
+
78
+ const button3AssignActionField = button3Fields.find(
79
+ (field) => field.key === "mobile_button_3_assign_action"
80
+ );
81
+
82
+ expect(button3EnabledField.initial_value).toBe(false);
83
+
84
+ expect(button3AssignActionField.initial_value).toBe(
85
+ "local_storage_favourites_action"
86
+ );
87
+ });
88
+
89
+ it("merges container, shared button, and per-button overrides", () => {
90
+ const groups = buildMobileActionButtonGroups({
91
+ containerDefaults: {
92
+ align: "center",
93
+ position: ["over_image"],
94
+ },
95
+ sharedButtonDefaults: {
96
+ assetAlignment: "right",
97
+ },
98
+ buttonOverrides: {
99
+ 2: {
100
+ buttonEnabled: true,
101
+ assignAction: "downloads",
102
+ },
103
+ },
104
+ });
105
+
106
+ const alignField = groups[0].fields.find(
107
+ (field) => field.key === "mobile_buttons_container_align"
108
+ );
109
+
110
+ const button1AssetAlignmentField = groups[1].fields.find(
111
+ (field) => field.key === "mobile_button_1_asset_alignment"
112
+ );
113
+
114
+ const button2EnabledField = groups[2].fields.find(
115
+ (field) => field.key === "mobile_button_2_button_enabled"
116
+ );
117
+
118
+ const button2AssignActionField = groups[2].fields.find(
119
+ (field) => field.key === "mobile_button_2_assign_action"
120
+ );
121
+
122
+ expect(alignField.initial_value).toBe("center");
123
+ expect(button1AssetAlignmentField.initial_value).toBe("right");
124
+ expect(button2EnabledField.initial_value).toBe(true);
125
+ expect(button2AssignActionField.initial_value).toBe("downloads");
126
+ });
127
+ });
@@ -0,0 +1,76 @@
1
+ const DEFAULT_MOBILE_ACTION_BUTTONS_CONTAINER_DEFAULTS = {
2
+ buttonsEnabled: false,
3
+
4
+ align: "left",
5
+ overImagePosition: "bottom_left",
6
+ marginTop: 0,
7
+ marginRight: 20,
8
+ marginBottom: 0,
9
+ marginLeft: 20,
10
+ stacking: "horizontal",
11
+ horizontalGutter: 8,
12
+ verticalGutter: 8,
13
+ independentStyles: true,
14
+ };
15
+
16
+ const DEFAULT_MOBILE_ACTION_BUTTON_SHARED_DEFAULTS = {
17
+ displayMode: "dynamic",
18
+ width: 140,
19
+ contentsAlignment: "center",
20
+ backgroundColor: "rgba(62, 62, 62, 1)",
21
+ focusedBackgroundColor: "rgba(46, 46, 46, 1)",
22
+ borderColor: "rgba(0, 0, 0, 0)",
23
+ focusedBorderColor: "rgba(0, 0, 0, 0)",
24
+ borderSize: 0,
25
+ cornerRadius: 8,
26
+ paddingTop: 14,
27
+ paddingRight: 24,
28
+ paddingBottom: 14,
29
+ paddingLeft: 16,
30
+ assetEnabled: true,
31
+ actionAssetFlavour: "flavour_1",
32
+ assetAlignment: "left",
33
+ assetHeight: 24,
34
+ assetWidth: 24,
35
+ assetMarginTop: 0,
36
+ assetMarginRight: 6,
37
+ assetMarginBottom: 0,
38
+ assetMarginLeft: 0,
39
+ labelEnabled: true,
40
+ fontColor: "rgba(239, 239, 239, 1)",
41
+ focusedFontColor: "rgba(239, 239, 239, 1)",
42
+ iosFontFamily: "Ubuntu-Bold",
43
+ androidFontFamily: "Ubuntu-Bold",
44
+ fontSize: 15,
45
+ lineHeight: 24,
46
+ iosLetterSpacing: -0.2,
47
+ androidLetterSpacing: -0.2,
48
+ textTransform: "default",
49
+ marginTop: 0,
50
+ marginRight: 0,
51
+ marginBottom: 0,
52
+ marginLeft: 0,
53
+ };
54
+
55
+ const DEFAULT_MOBILE_ACTION_BUTTON_PRESETS = {
56
+ 1: {
57
+ buttonEnabled: true,
58
+ assignAction: "navigation_action",
59
+ backgroundColor: "rgba(254, 20, 72, 1)",
60
+ focusedBackgroundColor: "rgba(213, 8, 54, 1)",
61
+ },
62
+ 2: {
63
+ buttonEnabled: false,
64
+ assignAction: "secondary_navigation",
65
+ },
66
+ 3: {
67
+ buttonEnabled: false,
68
+ assignAction: "local_storage_favourites_action",
69
+ },
70
+ };
71
+
72
+ module.exports = {
73
+ DEFAULT_MOBILE_ACTION_BUTTONS_CONTAINER_DEFAULTS,
74
+ DEFAULT_MOBILE_ACTION_BUTTON_SHARED_DEFAULTS,
75
+ DEFAULT_MOBILE_ACTION_BUTTON_PRESETS,
76
+ };