@applicaster/zapp-react-native-utils 15.1.0 → 15.1.1

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.
@@ -25,6 +25,10 @@ import {
25
25
  resolveObjectValues,
26
26
  } from "../appUtils/contextKeysManager/contextResolver";
27
27
  import { useNavigation } from "../reactHooks";
28
+ import {
29
+ getInflatedDataSourceUrl,
30
+ getSearchContext,
31
+ } from "../reactHooks/feed/useInflatedUrl";
28
32
 
29
33
  import {
30
34
  useContentTypes,
@@ -128,12 +132,42 @@ const prepareDefaultActions = (actionExecutor) => {
128
132
  async (_action, context) => {
129
133
  const dispatch = appStore.getDispatch();
130
134
 
131
- const dataSource =
132
- context?.component?.data?.source ||
133
- findParentComponent(context?.component.id, context?.screenData)?.data
134
- ?.source;
135
+ const parentComponent = findParentComponent(
136
+ context?.component?.id,
137
+ context?.screenData
138
+ );
139
+
140
+ const componentSource = context?.component?.data?.source;
141
+
142
+ const componentData = componentSource
143
+ ? context.component.data
144
+ : parentComponent?.data;
145
+
146
+ const source = componentData?.source;
147
+ const mapping = componentData?.mapping;
135
148
 
136
- log_info(`handleAction: refreshComponent for dataSource:${dataSource}`);
149
+ let dataSource = source;
150
+
151
+ if (source && mapping) {
152
+ dataSource =
153
+ getInflatedDataSourceUrl({
154
+ source,
155
+ contexts: {
156
+ entry: context?.entryContext,
157
+ screen: context?.screenData,
158
+ search: getSearchContext(null, mapping),
159
+ },
160
+ mapping,
161
+ }) || source;
162
+ }
163
+
164
+ log_info(`handleAction: refreshComponent for dataSource:${dataSource}`, {
165
+ source,
166
+ inflatedUrl: dataSource,
167
+ mapping,
168
+ entryContextId: context?.entryContext?.id,
169
+ entryId: context?.entry?.id,
170
+ });
137
171
 
138
172
  // TODO: In theory we should wait callback to complete, before completing the action, but now it's not needed
139
173
  // TODO: handle focused item removal
@@ -157,6 +157,10 @@ function generateFieldsFromDefaultsWithoutPrefixedLabel(
157
157
 
158
158
  if (conditions) {
159
159
  generatedField.conditional_fields = conditions.map((condition) => {
160
+ if (condition.section === null) {
161
+ return { key: condition.key, condition_value: condition.value };
162
+ }
163
+
160
164
  const section = condition.section ? `${condition.section}/` : "";
161
165
 
162
166
  return {
@@ -164,6 +168,8 @@ function generateFieldsFromDefaultsWithoutPrefixedLabel(
164
168
  condition_value: condition.value,
165
169
  };
166
170
  });
171
+
172
+ generatedField.rules = "all_conditions";
167
173
  }
168
174
 
169
175
  return generatedField;
@@ -0,0 +1,133 @@
1
+ const {
2
+ parseShorthand,
3
+ margin,
4
+ padding,
5
+ withConditional,
6
+ getConditionalKey,
7
+ createConditionalField,
8
+ } = require("..");
9
+
10
+ describe("parseShorthand", () => {
11
+ it("1 value → all four sides equal", () => {
12
+ expect(parseShorthand("8")).toEqual([8, 8, 8, 8]);
13
+ });
14
+
15
+ it("2 values → top/bottom, right/left", () => {
16
+ expect(parseShorthand("0 20")).toEqual([0, 20, 0, 20]);
17
+ });
18
+
19
+ it("3 values → top, right/left, bottom", () => {
20
+ expect(parseShorthand("0 0 20")).toEqual([0, 0, 20, 0]);
21
+ });
22
+
23
+ it("4 values → top right bottom left", () => {
24
+ expect(parseShorthand("1 2 3 4")).toEqual([1, 2, 3, 4]);
25
+ });
26
+
27
+ it("throws on invalid count", () => {
28
+ expect(() => parseShorthand("1 2 3 4 5")).toThrow();
29
+ });
30
+ });
31
+
32
+ describe("margin", () => {
33
+ it("returns 4 field descriptors with correct suffixes", () => {
34
+ const fields = margin("margin", { mobile: "0" });
35
+
36
+ expect(fields.map((f) => f.suffix)).toEqual([
37
+ "margin top",
38
+ "margin right",
39
+ "margin bottom",
40
+ "margin left",
41
+ ]);
42
+ });
43
+
44
+ it("sets type to number_input", () => {
45
+ margin("margin", { mobile: "0" }).forEach((f) => {
46
+ expect(f.type).toBe("number_input");
47
+ });
48
+ });
49
+
50
+ it("expands shorthand per platform", () => {
51
+ const fields = margin("margin", { mobile: "0 0 20", tv: "0 0 90" });
52
+ const byKey = Object.fromEntries(fields.map((f) => [f.suffix, f]));
53
+ expect(byKey["margin top"].initialValue).toEqual({ mobile: 0, tv: 0 });
54
+ expect(byKey["margin bottom"].initialValue).toEqual({ mobile: 20, tv: 90 });
55
+ expect(byKey["margin left"].initialValue).toEqual({ mobile: 0, tv: 0 });
56
+ });
57
+
58
+ it("supports a custom suffix prefix", () => {
59
+ const fields = margin("input margin", { mobile: "4" });
60
+ expect(fields[0].suffix).toBe("input margin top");
61
+ });
62
+ });
63
+
64
+ describe("padding", () => {
65
+ it("returns 4 field descriptors with correct suffixes", () => {
66
+ const fields = padding("padding", { mobile: "0" });
67
+
68
+ expect(fields.map((f) => f.suffix)).toEqual([
69
+ "padding top",
70
+ "padding right",
71
+ "padding bottom",
72
+ "padding left",
73
+ ]);
74
+ });
75
+
76
+ it("expands 2-value shorthand symmetrically", () => {
77
+ const fields = padding("padding", { mobile: "0 20", tv: "0 124" });
78
+ const byKey = Object.fromEntries(fields.map((f) => [f.suffix, f]));
79
+
80
+ expect(byKey["padding right"].initialValue).toEqual({
81
+ mobile: 20,
82
+ tv: 124,
83
+ });
84
+
85
+ expect(byKey["padding left"].initialValue).toEqual({ mobile: 20, tv: 124 });
86
+ expect(byKey["padding top"].initialValue).toEqual({ mobile: 0, tv: 0 });
87
+ expect(byKey["padding bottom"].initialValue).toEqual({ mobile: 0, tv: 0 });
88
+ });
89
+ });
90
+
91
+ describe("manifestUtils/fieldUtils", () => {
92
+ it("appends conditions and adds all_conditions when there is more than one condition", () => {
93
+ const config = {
94
+ key: "mobile_button_1_width",
95
+ conditional_fields: [
96
+ { key: "styles/mobile_button_1_button_enabled", condition_value: true },
97
+ ],
98
+ };
99
+
100
+ const result = withConditional([
101
+ { key: "styles/mobile_button_1_display_mode", condition_value: "fixed" },
102
+ ])(config);
103
+
104
+ expect(result.conditional_fields).toEqual([
105
+ { key: "styles/mobile_button_1_button_enabled", condition_value: true },
106
+ { key: "styles/mobile_button_1_display_mode", condition_value: "fixed" },
107
+ ]);
108
+
109
+ expect(result.rules).toBe("all_conditions");
110
+ });
111
+
112
+ it("creates category-prefixed conditional key", () => {
113
+ expect(
114
+ getConditionalKey("mobile_buttons_container_position", "styles")
115
+ ).toBe("styles/mobile_buttons_container_position");
116
+ });
117
+
118
+ it("creates conditional fields with default category and with raw key", () => {
119
+ expect(
120
+ createConditionalField("mobile_button_1_asset_enabled", true)
121
+ ).toEqual({
122
+ key: "styles/mobile_button_1_asset_enabled",
123
+ condition_value: true,
124
+ });
125
+
126
+ expect(
127
+ createConditionalField("mobile_button_1_asset_enabled", true, null)
128
+ ).toEqual({
129
+ key: "mobile_button_1_asset_enabled",
130
+ condition_value: true,
131
+ });
132
+ });
133
+ });
@@ -0,0 +1,179 @@
1
+ const { ZAPPIFEST_FIELDS } = require("../keys");
2
+ const { toCamelCase } = require("../_internals");
3
+
4
+ const SIDES = ["top", "right", "bottom", "left"];
5
+
6
+ /**
7
+ * Parses a CSS margin/padding shorthand string into [top, right, bottom, left].
8
+ *
9
+ * 1 value → all four sides
10
+ * 2 values → top/bottom, right/left
11
+ * 3 values → top, right/left, bottom
12
+ * 4 values → top, right, bottom, left
13
+ *
14
+ * @param {string} str
15
+ * @returns {[number, number, number, number]}
16
+ */
17
+ function parseShorthand(str) {
18
+ const parts = String(str).trim().split(/\s+/).map(Number);
19
+
20
+ switch (parts.length) {
21
+ case 1:
22
+ return [parts[0], parts[0], parts[0], parts[0]];
23
+ case 2:
24
+ return [parts[0], parts[1], parts[0], parts[1]];
25
+ case 3:
26
+ return [parts[0], parts[1], parts[2], parts[1]];
27
+ case 4:
28
+ return parts;
29
+ default:
30
+ throw new Error(
31
+ `Shorthand expects 1–4 space-separated values, got ${parts.length}: "${str}"`
32
+ );
33
+ }
34
+ }
35
+
36
+ function spacingFields(prefix, initialValues) {
37
+ const platforms = Object.keys(initialValues);
38
+
39
+ const parsed = Object.fromEntries(
40
+ platforms.map((p) => [p, parseShorthand(initialValues[p])])
41
+ );
42
+
43
+ return SIDES.map((side, i) => ({
44
+ suffix: `${prefix} ${side}`,
45
+ type: ZAPPIFEST_FIELDS.number_input,
46
+ initialValue: Object.fromEntries(platforms.map((p) => [p, parsed[p][i]])),
47
+ }));
48
+ }
49
+
50
+ /**
51
+ * Generates four margin field descriptors (top/right/bottom/left) from a
52
+ * per-platform CSS shorthand string.
53
+ *
54
+ * @param {string} suffix - prefix word for generated field names, e.g. "margin"
55
+ * @param {Record<string, string>} initialValues - e.g. { mobile: "0 0 20", tv: "0 0 90" }
56
+ * @returns {object[]}
57
+ */
58
+ function margin(suffix, initialValues) {
59
+ return spacingFields(suffix, initialValues);
60
+ }
61
+
62
+ /**
63
+ * Generates four padding field descriptors (top/right/bottom/left) from a
64
+ * per-platform CSS shorthand string.
65
+ *
66
+ * @param {string} suffix - prefix word for generated field names, e.g. "padding"
67
+ * @param {Record<string, string>} initialValues - e.g. { mobile: "0 20", tv: "0 124" }
68
+ * @returns {object[]}
69
+ */
70
+ function padding(suffix, initialValues) {
71
+ return spacingFields(suffix, initialValues);
72
+ }
73
+
74
+ /**
75
+ * Returns a flat camelCase defaults object for a spacing shorthand.
76
+ * e.g. marginDefaults("margin", "0 0 20") → { marginTop: 0, marginRight: 0, marginBottom: 20, marginLeft: 0 }
77
+ *
78
+ * @param {string} suffix - e.g. "margin" or "padding"
79
+ * @param {string} shorthand - CSS-like shorthand string
80
+ * @returns {Record<string, number>}
81
+ */
82
+ function spacingDefaults(suffix, shorthand) {
83
+ const values = parseShorthand(shorthand);
84
+
85
+ return Object.fromEntries(
86
+ SIDES.map((side, i) => [toCamelCase(`${suffix} ${side}`), values[i]])
87
+ );
88
+ }
89
+
90
+ function marginDefaults(suffix, shorthand) {
91
+ return spacingDefaults(suffix, shorthand);
92
+ }
93
+
94
+ function paddingDefaults(suffix, shorthand) {
95
+ return spacingDefaults(suffix, shorthand);
96
+ }
97
+
98
+ /**
99
+ * Returns four field descriptors (top/right/bottom/left) with only suffix and type — no initialValue.
100
+ * Used when defaults are stored separately in the subgroup spec.
101
+ *
102
+ * @param {string} prefix - e.g. "margin" or "padding"
103
+ * @returns {object[]}
104
+ */
105
+ function marginFields(prefix) {
106
+ return SIDES.map((side) => ({
107
+ suffix: `${prefix} ${side}`,
108
+ type: ZAPPIFEST_FIELDS.number_input,
109
+ }));
110
+ }
111
+
112
+ function paddingFields(prefix) {
113
+ return SIDES.map((side) => ({
114
+ suffix: `${prefix} ${side}`,
115
+ type: ZAPPIFEST_FIELDS.number_input,
116
+ }));
117
+ }
118
+
119
+ /**
120
+ * Appends new conditional_fields from conditions array
121
+ * to config.conditional_fields
122
+ * if there are more than 1 condition, sets rules to "all_conditions"
123
+ *
124
+ * @param {Array<{key: string, condition_value: unknown}>} conditions
125
+ * @returns {(config: object) => object}
126
+ */
127
+ const withConditional = (conditions) => (config) => {
128
+ const conditional_fields = [
129
+ ...(config.conditional_fields || []),
130
+ ...conditions,
131
+ ];
132
+
133
+ const next = { ...config, conditional_fields };
134
+
135
+ if (conditional_fields.length > 1) {
136
+ return { ...next, rules: "all_conditions" };
137
+ }
138
+
139
+ return next;
140
+ };
141
+
142
+ /**
143
+ * Builds key for conditional fields prepending category, e.g. "styles/mobile_buttons_container_position"
144
+ *
145
+ * @param {string} key
146
+ * @param {string} category
147
+ * @returns {string}
148
+ */
149
+ function getConditionalKey(key, category) {
150
+ return `${category}/${key}`;
151
+ }
152
+
153
+ /**
154
+ * Returns a conditional field object for manifest visibility rules.
155
+ *
156
+ * @param {string} key
157
+ * @param {unknown} condition_value
158
+ * @param {string|null} [category="styles"] Pass `null` to use the key as-is.
159
+ * @returns {{key: string, condition_value: unknown}}
160
+ */
161
+ function createConditionalField(key, condition_value, category = "styles") {
162
+ return {
163
+ key: category ? getConditionalKey(key, category) : key,
164
+ condition_value,
165
+ };
166
+ }
167
+
168
+ module.exports = {
169
+ parseShorthand,
170
+ margin,
171
+ padding,
172
+ marginDefaults,
173
+ paddingDefaults,
174
+ marginFields,
175
+ paddingFields,
176
+ withConditional,
177
+ getConditionalKey,
178
+ createConditionalField,
179
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@applicaster/zapp-react-native-utils",
3
- "version": "15.1.0",
3
+ "version": "15.1.1",
4
4
  "description": "Applicaster Zapp React Native utilities package",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -27,7 +27,7 @@
27
27
  },
28
28
  "homepage": "https://github.com/applicaster/quickbrick#readme",
29
29
  "dependencies": {
30
- "@applicaster/applicaster-types": "15.1.0",
30
+ "@applicaster/applicaster-types": "15.1.1",
31
31
  "buffer": "^5.2.1",
32
32
  "camelize": "^1.0.0",
33
33
  "dayjs": "^1.11.10",
@@ -54,7 +54,7 @@ export const useCellClick = ({
54
54
  component?.rules?.component_cells_selectable
55
55
  );
56
56
 
57
- const [__, setEntryContext] =
57
+ const [entryContext, setEntryContext] =
58
58
  ZappPipesEntryContext.useZappPipesContext(pathname);
59
59
 
60
60
  const logTimestamp = useProfilerLogging();
@@ -89,6 +89,7 @@ export const useCellClick = ({
89
89
  screenState,
90
90
  screenRoute: pathname,
91
91
  screenStateStore,
92
+ entryContext,
92
93
  });
93
94
  }
94
95