@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
|
|
132
|
-
context?.component?.
|
|
133
|
-
|
|
134
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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 [
|
|
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
|
|