@applicaster/zapp-react-native-ui-components 16.0.0-rc.21 → 16.0.0-rc.23
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/Layout/FullWidthRow.tsx +38 -0
- package/Components/MasterCell/dataAdapter.ts +15 -5
- package/Components/MasterCell/index.tsx +10 -1
- package/Components/MasterCell/utils/__tests__/resolveColor.test.js +169 -132
- package/Components/MasterCell/utils/__tests__/resolveColorForProp.test.js +92 -0
- package/Components/MasterCell/utils/index.ts +85 -46
- package/Components/River/ComponentsMap/ComponentsMap.tsx +2 -2
- package/Components/River/__tests__/__snapshots__/componentsMap.test.js.snap +1 -15
- package/Components/Screen/index.tsx +2 -0
- package/Components/ZappFrameworkComponents/BarView/BarView.tsx +10 -5
- package/Contexts/CachedDimensionsContext/__tests__/index.test.ts +154 -0
- package/Contexts/CachedDimensionsContext/index.ts +17 -2
- package/package.json +5 -5
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { StyleSheet, View } from "react-native";
|
|
3
|
+
|
|
4
|
+
const styles = StyleSheet.create({
|
|
5
|
+
row: { flexDirection: "row" },
|
|
6
|
+
basis: {
|
|
7
|
+
maxWidth: "100%",
|
|
8
|
+
flexBasis: "100%",
|
|
9
|
+
},
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
type Props = {
|
|
13
|
+
children: React.ReactNode;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Constrains its child to exactly the available parent width.
|
|
18
|
+
*
|
|
19
|
+
* UI components such as Hero and List render their items inside a parent
|
|
20
|
+
* `ScrollView` (e.g. when wrapped by tabs). A child placed directly in a
|
|
21
|
+
* scroll container can size itself to its own content rather than to the
|
|
22
|
+
* viewport, which causes items to be measured at a stale width and resize
|
|
23
|
+
* unexpectedly after layout changes such as device rotation.
|
|
24
|
+
*
|
|
25
|
+
* Wrapping the content in a `flexDirection: "row"` parent with a
|
|
26
|
+
* `flexBasis: "100%"` / `maxWidth: "100%"` child forces the child to
|
|
27
|
+
* re-derive its width from the parent on every layout pass instead of
|
|
28
|
+
* caching a measured pixel width, keeping it full-width and stable.
|
|
29
|
+
*
|
|
30
|
+
* @param children - The content to render at full parent width.
|
|
31
|
+
*/
|
|
32
|
+
export function FullWidthRow({ children }: Props) {
|
|
33
|
+
return (
|
|
34
|
+
<View style={styles.row}>
|
|
35
|
+
<View style={styles.basis}>{children}</View>
|
|
36
|
+
</View>
|
|
37
|
+
);
|
|
38
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as R from "ramda";
|
|
2
2
|
import { isFunction } from "@applicaster/zapp-react-native-utils/functionUtils";
|
|
3
|
+
import { toBooleanWithDefaultFalse } from "@applicaster/zapp-react-native-utils/booleanUtils";
|
|
3
4
|
|
|
4
5
|
import { functionForName } from "./MappingFunctions";
|
|
5
6
|
import { resolveColor } from "./utils";
|
|
@@ -52,7 +53,8 @@ export function configInflater(
|
|
|
52
53
|
additionalProps?: Record<string, any>;
|
|
53
54
|
data?: Array<{ propName: string; func: Function; args: any[] }>;
|
|
54
55
|
elements?: any[];
|
|
55
|
-
}
|
|
56
|
+
},
|
|
57
|
+
allowDynamicColorsOutsideExtensions: boolean
|
|
56
58
|
) {
|
|
57
59
|
const props = data.reduce(
|
|
58
60
|
(acc, curr) => {
|
|
@@ -68,13 +70,13 @@ export function configInflater(
|
|
|
68
70
|
|
|
69
71
|
if (Array.isArray(elements)) {
|
|
70
72
|
adjustedElements = elements.map((element) =>
|
|
71
|
-
configInflater(entry, element as any)
|
|
73
|
+
configInflater(entry, element as any, allowDynamicColorsOutsideExtensions)
|
|
72
74
|
);
|
|
73
75
|
}
|
|
74
76
|
|
|
75
77
|
return {
|
|
76
78
|
type,
|
|
77
|
-
style: resolveColor(entry, style),
|
|
79
|
+
style: resolveColor(entry, style, allowDynamicColorsOutsideExtensions),
|
|
78
80
|
props,
|
|
79
81
|
elements: adjustedElements,
|
|
80
82
|
};
|
|
@@ -98,9 +100,17 @@ function resolveElementsNode(entry, state, elements) {
|
|
|
98
100
|
}
|
|
99
101
|
|
|
100
102
|
export function defaultDataAdapter(elements) {
|
|
101
|
-
return function elementsBuilder({
|
|
103
|
+
return function elementsBuilder({
|
|
104
|
+
entry,
|
|
105
|
+
state = "default",
|
|
106
|
+
allowDynamicColorsOutsideExtensions,
|
|
107
|
+
}) {
|
|
102
108
|
return resolveElementsNode(entry, state, elements).map((element) =>
|
|
103
|
-
configInflater(
|
|
109
|
+
configInflater(
|
|
110
|
+
entry,
|
|
111
|
+
element,
|
|
112
|
+
toBooleanWithDefaultFalse(allowDynamicColorsOutsideExtensions)
|
|
113
|
+
)
|
|
104
114
|
);
|
|
105
115
|
};
|
|
106
116
|
}
|
|
@@ -3,6 +3,7 @@ import * as React from "react";
|
|
|
3
3
|
import { v4 as uuid } from "uuid";
|
|
4
4
|
|
|
5
5
|
import { useRoute } from "@applicaster/zapp-react-native-utils/reactHooks/navigation";
|
|
6
|
+
import { useTheme } from "@applicaster/zapp-react-native-utils/theme";
|
|
6
7
|
import { useScreenState } from "@applicaster/zapp-react-native-utils/screenState";
|
|
7
8
|
import { defaultComponents } from "./DefaultComponents";
|
|
8
9
|
import { defaultDataAdapter } from "./dataAdapter";
|
|
@@ -65,6 +66,7 @@ export function masterCellBuilder({
|
|
|
65
66
|
*/
|
|
66
67
|
function MasterCell({ item, state, ...otherProps }: Props) {
|
|
67
68
|
const { screenData } = useRoute();
|
|
69
|
+
const theme = useTheme();
|
|
68
70
|
|
|
69
71
|
const screenId =
|
|
70
72
|
screenData && "targetScreen" in screenData
|
|
@@ -85,8 +87,15 @@ export function masterCellBuilder({
|
|
|
85
87
|
elementsBuilder({
|
|
86
88
|
entry: item,
|
|
87
89
|
state: getEntryState(state, entryIsSelected),
|
|
90
|
+
allowDynamicColorsOutsideExtensions:
|
|
91
|
+
theme?.allow_dynamic_colors_outside_extensions,
|
|
88
92
|
}),
|
|
89
|
-
[
|
|
93
|
+
[
|
|
94
|
+
state,
|
|
95
|
+
item,
|
|
96
|
+
entryIsSelected,
|
|
97
|
+
theme?.allow_dynamic_colors_outside_extensions,
|
|
98
|
+
] // Assuming that item won't mutate
|
|
90
99
|
);
|
|
91
100
|
|
|
92
101
|
const wrapperRef = React.useRef(null);
|
|
@@ -1,194 +1,231 @@
|
|
|
1
1
|
import { resolveColor } from "..";
|
|
2
|
-
import { masterCellLogger } from "../../logger";
|
|
3
|
-
|
|
4
|
-
const loggerSpy = jest
|
|
5
|
-
.spyOn(masterCellLogger, "warn")
|
|
6
|
-
.mockImplementation(() => {});
|
|
7
2
|
|
|
8
3
|
describe("resolveColor", () => {
|
|
9
4
|
const entry = {
|
|
10
5
|
extensions: {
|
|
11
6
|
color: "red",
|
|
12
7
|
green_color: "green",
|
|
8
|
+
float_alpha_color: "rgba(239,239,239,1.0)",
|
|
9
|
+
invalid_color: "not_a_color",
|
|
13
10
|
},
|
|
11
|
+
background_color: "#123456",
|
|
14
12
|
};
|
|
15
13
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
const style = {
|
|
22
|
-
color: "extensions.color",
|
|
23
|
-
};
|
|
14
|
+
describe("default behavior (resolves extensions paths via pathOr, passes through literals)", () => {
|
|
15
|
+
it("resolves color from extensions data mapping path", () => {
|
|
16
|
+
const style = {
|
|
17
|
+
color: "extensions.color",
|
|
18
|
+
};
|
|
24
19
|
|
|
25
|
-
|
|
26
|
-
|
|
20
|
+
expect(resolveColor(entry, style)).toEqual({
|
|
21
|
+
color: entry.extensions.color,
|
|
22
|
+
});
|
|
27
23
|
});
|
|
28
|
-
});
|
|
29
24
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
25
|
+
it("passes through non-extensions values as raw color strings", () => {
|
|
26
|
+
const style = {
|
|
27
|
+
color: "invalid_path",
|
|
28
|
+
};
|
|
34
29
|
|
|
35
|
-
|
|
36
|
-
|
|
30
|
+
expect(resolveColor(entry, style)).toEqual({
|
|
31
|
+
color: "invalid_path",
|
|
32
|
+
});
|
|
37
33
|
});
|
|
38
34
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
colorFromProp: "invalid_path",
|
|
44
|
-
configurationValue: "invalid_path",
|
|
45
|
-
},
|
|
46
|
-
})
|
|
47
|
-
);
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
it("resolves any style prop with containing color", () => {
|
|
51
|
-
const style = {
|
|
52
|
-
backgroundColor: "extensions.color",
|
|
53
|
-
width: "100%",
|
|
54
|
-
borderColor: "extensions.green_color",
|
|
55
|
-
};
|
|
35
|
+
it("passes through entry path keys outside extensions prefix as raw color strings", () => {
|
|
36
|
+
const style = {
|
|
37
|
+
color: "background_color",
|
|
38
|
+
};
|
|
56
39
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
width: "100%",
|
|
40
|
+
expect(resolveColor(entry, style)).toEqual({
|
|
41
|
+
color: "background_color",
|
|
42
|
+
});
|
|
61
43
|
});
|
|
62
|
-
});
|
|
63
44
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
45
|
+
it("resolves all color-related style props from extensions paths", () => {
|
|
46
|
+
const style = {
|
|
47
|
+
backgroundColor: "extensions.color",
|
|
48
|
+
width: "100%",
|
|
49
|
+
borderColor: "extensions.green_color",
|
|
50
|
+
};
|
|
68
51
|
|
|
69
|
-
|
|
70
|
-
|
|
52
|
+
expect(resolveColor(entry, style)).toEqual({
|
|
53
|
+
backgroundColor: "red",
|
|
54
|
+
borderColor: "green",
|
|
55
|
+
width: "100%",
|
|
56
|
+
});
|
|
57
|
+
});
|
|
71
58
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
59
|
+
it("passes through literal hex color values unchanged", () => {
|
|
60
|
+
const style = {
|
|
61
|
+
color: "#000000",
|
|
62
|
+
};
|
|
76
63
|
|
|
77
|
-
|
|
78
|
-
|
|
64
|
+
expect(resolveColor(entry, style)).toEqual(style);
|
|
65
|
+
});
|
|
79
66
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
67
|
+
it("passes through literal rgba color values unchanged", () => {
|
|
68
|
+
const style = {
|
|
69
|
+
color: "rgba(0,0,0,0)",
|
|
70
|
+
};
|
|
84
71
|
|
|
85
|
-
|
|
86
|
-
|
|
72
|
+
expect(resolveColor(entry, style)).toEqual(style);
|
|
73
|
+
});
|
|
87
74
|
|
|
88
|
-
|
|
89
|
-
|
|
75
|
+
it("passes through literal rgba values with float alpha unchanged", () => {
|
|
76
|
+
const style = {
|
|
77
|
+
color: "rgba(239,239,239,1.0)",
|
|
78
|
+
};
|
|
90
79
|
|
|
91
|
-
|
|
92
|
-
|
|
80
|
+
expect(resolveColor(entry, style)).toEqual(style);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it("passes through transparent as a literal color value", () => {
|
|
84
|
+
const style = {
|
|
85
|
+
color: "transparent",
|
|
86
|
+
};
|
|
93
87
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
test: 1,
|
|
97
|
-
};
|
|
88
|
+
expect(resolveColor(entry, style)).toEqual(style);
|
|
89
|
+
});
|
|
98
90
|
|
|
99
|
-
|
|
100
|
-
|
|
91
|
+
it("passes through named color values as literal colors", () => {
|
|
92
|
+
const style = {
|
|
93
|
+
color: "black",
|
|
94
|
+
};
|
|
101
95
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
color: "not.exist.path",
|
|
105
|
-
};
|
|
96
|
+
expect(resolveColor(entry, style)).toEqual(style);
|
|
97
|
+
});
|
|
106
98
|
|
|
107
|
-
|
|
108
|
-
|
|
99
|
+
it("returns style unchanged when style is undefined", () => {
|
|
100
|
+
expect(resolveColor(entry, undefined)).toBeUndefined();
|
|
109
101
|
});
|
|
110
|
-
});
|
|
111
102
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
};
|
|
103
|
+
it("returns style unchanged when style is null", () => {
|
|
104
|
+
expect(resolveColor(entry, null)).toBeNull();
|
|
105
|
+
});
|
|
116
106
|
|
|
117
|
-
|
|
118
|
-
|
|
107
|
+
it("returns style unchanged when it has no color-related props", () => {
|
|
108
|
+
const style = {
|
|
109
|
+
test: 1,
|
|
110
|
+
};
|
|
119
111
|
|
|
120
|
-
|
|
121
|
-
beforeEach(() => {
|
|
122
|
-
// Clear memoization cache before each test
|
|
123
|
-
resolveColor.clear && resolveColor.clear();
|
|
112
|
+
expect(resolveColor(entry, style)).toEqual(style);
|
|
124
113
|
});
|
|
125
114
|
|
|
126
|
-
it("
|
|
127
|
-
const style = {
|
|
115
|
+
it("returns null when extensions path is missing from entry", () => {
|
|
116
|
+
const style = {
|
|
117
|
+
color: "extensions.missing.path",
|
|
118
|
+
};
|
|
128
119
|
|
|
129
|
-
|
|
130
|
-
|
|
120
|
+
expect(resolveColor(entry, style)).toEqual({
|
|
121
|
+
color: null,
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it("returns resolved extensions path value without validating color", () => {
|
|
126
|
+
const style = {
|
|
127
|
+
color: "extensions.invalid_color",
|
|
128
|
+
};
|
|
131
129
|
|
|
132
|
-
expect(
|
|
130
|
+
expect(resolveColor(entry, style)).toEqual({
|
|
131
|
+
color: "not_a_color",
|
|
132
|
+
});
|
|
133
133
|
});
|
|
134
134
|
|
|
135
|
-
it("
|
|
136
|
-
const
|
|
137
|
-
|
|
138
|
-
color: "red",
|
|
139
|
-
green_color: "green",
|
|
140
|
-
},
|
|
135
|
+
it("passes through empty string color values unchanged", () => {
|
|
136
|
+
const style = {
|
|
137
|
+
color: "",
|
|
141
138
|
};
|
|
142
139
|
|
|
143
|
-
|
|
144
|
-
|
|
140
|
+
expect(resolveColor(entry, style)).toEqual({
|
|
141
|
+
color: "",
|
|
142
|
+
});
|
|
143
|
+
});
|
|
145
144
|
|
|
146
|
-
|
|
147
|
-
const
|
|
145
|
+
it("resolves rgba color values with float alpha from extensions path", () => {
|
|
146
|
+
const style = {
|
|
147
|
+
color: "extensions.float_alpha_color",
|
|
148
|
+
};
|
|
148
149
|
|
|
149
|
-
expect(
|
|
150
|
+
expect(resolveColor(entry, style)).toEqual({
|
|
151
|
+
color: "rgba(239,239,239,1.0)",
|
|
152
|
+
});
|
|
150
153
|
});
|
|
154
|
+
});
|
|
151
155
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
const style = {
|
|
156
|
+
describe("when allowDynamicColorsOutsideExtensions is enabled (resolves via resolveColorForProp with validation)", () => {
|
|
157
|
+
it("resolves entry paths outside the extensions prefix", () => {
|
|
158
|
+
const style = {
|
|
159
|
+
color: "background_color",
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
expect(resolveColor(entry, style, true)).toEqual({
|
|
163
|
+
color: "#123456",
|
|
164
|
+
});
|
|
165
|
+
});
|
|
155
166
|
|
|
156
|
-
|
|
157
|
-
const
|
|
167
|
+
it("resolves extensions paths through resolveColorForProp", () => {
|
|
168
|
+
const style = {
|
|
169
|
+
color: "extensions.color",
|
|
170
|
+
};
|
|
158
171
|
|
|
159
|
-
expect(
|
|
172
|
+
expect(resolveColor(entry, style, true)).toEqual({
|
|
173
|
+
color: "red",
|
|
174
|
+
});
|
|
160
175
|
});
|
|
161
176
|
|
|
162
|
-
it("
|
|
163
|
-
const
|
|
164
|
-
|
|
165
|
-
color: "red",
|
|
166
|
-
green_color: "green",
|
|
167
|
-
},
|
|
177
|
+
it("returns null for invalid entry paths that cannot be resolved", () => {
|
|
178
|
+
const style = {
|
|
179
|
+
color: "invalid_path",
|
|
168
180
|
};
|
|
169
181
|
|
|
170
|
-
|
|
182
|
+
expect(resolveColor(entry, style, true)).toEqual({
|
|
183
|
+
color: null,
|
|
184
|
+
});
|
|
185
|
+
});
|
|
171
186
|
|
|
172
|
-
|
|
187
|
+
it("returns null when extensions path cannot be resolved to a valid color", () => {
|
|
188
|
+
const style = {
|
|
189
|
+
color: "extensions.missing.path",
|
|
190
|
+
};
|
|
173
191
|
|
|
174
|
-
|
|
175
|
-
|
|
192
|
+
expect(resolveColor(entry, style, true)).toEqual({
|
|
193
|
+
color: null,
|
|
194
|
+
});
|
|
195
|
+
});
|
|
176
196
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
197
|
+
it("passes through literal color values unchanged", () => {
|
|
198
|
+
const style = {
|
|
199
|
+
color: "#000000",
|
|
200
|
+
backgroundColor: "rgba(239,239,239,1.0)",
|
|
201
|
+
borderColor: "transparent",
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
expect(resolveColor(entry, style, true)).toEqual(style);
|
|
180
205
|
});
|
|
181
206
|
|
|
182
|
-
it("
|
|
183
|
-
const
|
|
184
|
-
|
|
207
|
+
it("passes through empty string color values unchanged", () => {
|
|
208
|
+
const style = {
|
|
209
|
+
color: "",
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
expect(resolveColor(entry, style, true)).toEqual({
|
|
213
|
+
color: "",
|
|
214
|
+
});
|
|
215
|
+
});
|
|
185
216
|
|
|
186
|
-
|
|
187
|
-
const
|
|
217
|
+
it("resolves multiple color-related style props from entry paths", () => {
|
|
218
|
+
const style = {
|
|
219
|
+
backgroundColor: "background_color",
|
|
220
|
+
borderColor: "extensions.green_color",
|
|
221
|
+
width: "100%",
|
|
222
|
+
};
|
|
188
223
|
|
|
189
|
-
expect(
|
|
190
|
-
|
|
191
|
-
|
|
224
|
+
expect(resolveColor(entry, style, true)).toEqual({
|
|
225
|
+
backgroundColor: "#123456",
|
|
226
|
+
borderColor: "green",
|
|
227
|
+
width: "100%",
|
|
228
|
+
});
|
|
192
229
|
});
|
|
193
230
|
});
|
|
194
231
|
});
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { resolveColorForProp } from "..";
|
|
2
|
+
|
|
3
|
+
jest.mock("../../logger", () => ({
|
|
4
|
+
masterCellLogger: {
|
|
5
|
+
warn: jest.fn(),
|
|
6
|
+
},
|
|
7
|
+
}));
|
|
8
|
+
|
|
9
|
+
import { masterCellLogger } from "../../logger";
|
|
10
|
+
|
|
11
|
+
describe("resolveColorForProp", () => {
|
|
12
|
+
const entry = {
|
|
13
|
+
extensions: {
|
|
14
|
+
color: "red",
|
|
15
|
+
green_color: "green",
|
|
16
|
+
float_alpha_color: "rgba(239,239,239,1.0)",
|
|
17
|
+
},
|
|
18
|
+
background_color: "#123456",
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
beforeEach(() => {
|
|
22
|
+
jest.clearAllMocks();
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it("returns undefined when colorFromProp is undefined", () => {
|
|
26
|
+
expect(resolveColorForProp(entry, undefined)).toBeUndefined();
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("returns undefined when colorFromProp is empty string", () => {
|
|
30
|
+
expect(resolveColorForProp(entry, "")).toBeUndefined();
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("resolves color from extensions data mapping path", () => {
|
|
34
|
+
expect(resolveColorForProp(entry, "extensions.color")).toBe("red");
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("resolves color from nested entry path", () => {
|
|
38
|
+
expect(resolveColorForProp(entry, "background_color")).toBe("#123456");
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("returns valid hex color values", () => {
|
|
42
|
+
expect(resolveColorForProp(entry, "#000000")).toBe("#000000");
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("returns valid rgba color values", () => {
|
|
46
|
+
expect(resolveColorForProp(entry, "rgba(0,0,0,0)")).toBe("rgba(0,0,0,0)");
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it("returns valid named color values", () => {
|
|
50
|
+
expect(resolveColorForProp(entry, "transparent")).toBe("transparent");
|
|
51
|
+
expect(resolveColorForProp(entry, "black")).toBe("black");
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("returns undefined for invalid non-entry color values", () => {
|
|
55
|
+
expect(resolveColorForProp(entry, "invalid_path")).toBeUndefined();
|
|
56
|
+
|
|
57
|
+
expect(masterCellLogger.warn).toHaveBeenCalledWith({
|
|
58
|
+
message: "Cannot resolve property invalid_path from the entry.",
|
|
59
|
+
data: {
|
|
60
|
+
configurationValue: "invalid_path",
|
|
61
|
+
colorFromProp: "invalid_path",
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it("returns undefined when extensions path is missing", () => {
|
|
67
|
+
expect(
|
|
68
|
+
resolveColorForProp(entry, "extensions.missing.path")
|
|
69
|
+
).toBeUndefined();
|
|
70
|
+
|
|
71
|
+
expect(masterCellLogger.warn).toHaveBeenCalledWith({
|
|
72
|
+
message:
|
|
73
|
+
"Cannot resolve property extensions.missing.path from the entry.",
|
|
74
|
+
data: {
|
|
75
|
+
configurationValue: "extensions.missing.path",
|
|
76
|
+
colorFromProp: "extensions.missing.path",
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it("resolves rgba color values with float alpha from entry path", () => {
|
|
82
|
+
expect(resolveColorForProp(entry, "extensions.float_alpha_color")).toBe(
|
|
83
|
+
"rgba(239,239,239,1.0)"
|
|
84
|
+
);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it("returns valid rgba color values with float alpha", () => {
|
|
88
|
+
expect(resolveColorForProp(entry, "rgba(239,239,239,1.0)")).toBe(
|
|
89
|
+
"rgba(239,239,239,1.0)"
|
|
90
|
+
);
|
|
91
|
+
});
|
|
92
|
+
});
|
|
@@ -1,22 +1,23 @@
|
|
|
1
1
|
import React, { useMemo } from "react";
|
|
2
2
|
import * as R from "ramda";
|
|
3
|
-
import
|
|
3
|
+
import memoizee from "memoizee";
|
|
4
|
+
|
|
4
5
|
import { platformSelect } from "@applicaster/zapp-react-native-utils/reactUtils";
|
|
5
6
|
import { useActions } from "@applicaster/zapp-react-native-utils/reactHooks/actions";
|
|
6
|
-
|
|
7
|
-
import { masterCellLogger } from "../logger";
|
|
8
|
-
import { getCellState } from "../../Cell/utils";
|
|
7
|
+
import { isValidColor } from "@applicaster/zapp-react-native-utils/colorUtils";
|
|
9
8
|
import { getColorFromData } from "@applicaster/zapp-react-native-utils/cellUtils";
|
|
10
|
-
import { get } from "@applicaster/zapp-react-native-utils/utils";
|
|
11
|
-
import {
|
|
9
|
+
import { pathOr, get, isNil } from "@applicaster/zapp-react-native-utils/utils";
|
|
10
|
+
import { isNilOrEmpty } from "@applicaster/zapp-react-native-utils/reactUtils/helpers";
|
|
12
11
|
import { useRoute } from "@applicaster/zapp-react-native-utils/reactHooks";
|
|
13
12
|
import { useScreenStateStore } from "@applicaster/zapp-react-native-utils/reactHooks/navigation/useScreenStateStore";
|
|
14
|
-
import
|
|
15
|
-
import
|
|
13
|
+
import { isNotEmptyString } from "@applicaster/zapp-react-native-utils/stringUtils";
|
|
14
|
+
import { masterCellLogger } from "../logger";
|
|
15
|
+
import { getCellState } from "../../Cell/utils";
|
|
16
|
+
import { isCellSelected, useBehaviorUpdate } from "./behaviorProvider";
|
|
16
17
|
import { DataProvider } from "../DefaultComponents/DataProvider";
|
|
17
18
|
|
|
18
19
|
const hasElementSpecificViewType = (viewType) => (element) => {
|
|
19
|
-
if (
|
|
20
|
+
if (isNil(element)) {
|
|
20
21
|
return false;
|
|
21
22
|
}
|
|
22
23
|
|
|
@@ -28,12 +29,20 @@ const hasElementSpecificViewType = (viewType) => (element) => {
|
|
|
28
29
|
return hasElementsSpecificViewType(viewType)(element.elements);
|
|
29
30
|
};
|
|
30
31
|
|
|
32
|
+
export const hasElementsSpecificViewType = (viewType) => (elements) => {
|
|
33
|
+
if (isNilOrEmpty(elements)) {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return R.any(hasElementSpecificViewType(viewType))(elements);
|
|
38
|
+
};
|
|
39
|
+
|
|
31
40
|
const logWarning = (
|
|
32
41
|
colorValueFromCellStyle,
|
|
33
42
|
colorFromProp,
|
|
34
43
|
colorValueFromEntry
|
|
35
44
|
) => {
|
|
36
|
-
if (
|
|
45
|
+
if (isNil(colorValueFromEntry)) {
|
|
37
46
|
masterCellLogger.warn({
|
|
38
47
|
message: `Cannot resolve property ${colorValueFromCellStyle} from the entry.`,
|
|
39
48
|
data: {
|
|
@@ -44,15 +53,10 @@ const logWarning = (
|
|
|
44
53
|
}
|
|
45
54
|
};
|
|
46
55
|
|
|
47
|
-
export
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
return R.any(hasElementSpecificViewType(viewType))(elements);
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
function resolveColorForProp(entry: any, colorFromProp: string | undefined) {
|
|
56
|
+
export function resolveColorForProp(
|
|
57
|
+
entry: any,
|
|
58
|
+
colorFromProp: string | undefined
|
|
59
|
+
) {
|
|
56
60
|
if (!colorFromProp) {
|
|
57
61
|
return undefined;
|
|
58
62
|
}
|
|
@@ -62,9 +66,7 @@ function resolveColorForProp(entry: any, colorFromProp: string | undefined) {
|
|
|
62
66
|
colorFromProp.split(".")
|
|
63
67
|
);
|
|
64
68
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
if (nestedEntryValue === undefined && !validateColor(color)) {
|
|
69
|
+
if (nestedEntryValue === undefined && !isValidColor(colorFromProp)) {
|
|
68
70
|
logWarning(colorFromProp, colorFromProp, nestedEntryValue);
|
|
69
71
|
|
|
70
72
|
return undefined;
|
|
@@ -86,36 +88,73 @@ function resolveColorForProp(entry: any, colorFromProp: string | undefined) {
|
|
|
86
88
|
|
|
87
89
|
const getColorKeys = memoizee((style) => {
|
|
88
90
|
const styleKeys = Object.keys(style);
|
|
89
|
-
const colorKeys = styleKeys.filter((key) => /color/i.test(key));
|
|
90
91
|
|
|
91
|
-
return
|
|
92
|
+
return styleKeys.filter((key) => /color/i.test(key));
|
|
92
93
|
});
|
|
93
94
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
95
|
+
/**
|
|
96
|
+
* A color style value is either a literal color (e.g. "#FFFFFF") or a
|
|
97
|
+
* data-mapping path into the entry. Data mappings always point at the entry
|
|
98
|
+
* `extensions` object (e.g. "extensions.brandColor"), so we only need to do a
|
|
99
|
+
* path lookup when the value starts with "extensions" — everything else is a
|
|
100
|
+
* literal color and can be returned as-is.
|
|
101
|
+
*
|
|
102
|
+
* This avoids the previous, very expensive approach that ran `validateColor`
|
|
103
|
+
* on every value and memoized on a full `fast-json-stable-stringify` of the
|
|
104
|
+
* entire entry.
|
|
105
|
+
*/
|
|
106
|
+
const EXTENSIONS_PREFIX = "extensions";
|
|
107
|
+
|
|
108
|
+
const isDataMappingPath = (value: string): boolean =>
|
|
109
|
+
typeof value === "string" && value.startsWith(EXTENSIONS_PREFIX);
|
|
110
|
+
|
|
111
|
+
export const resolveColor = (
|
|
112
|
+
entry,
|
|
113
|
+
style,
|
|
114
|
+
allowDynamicColorsOutsideExtensions
|
|
115
|
+
) => {
|
|
116
|
+
if (style === null || style === undefined) {
|
|
117
|
+
return style;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const colorKeys = getColorKeys(style);
|
|
121
|
+
|
|
122
|
+
if (colorKeys.length === 0) {
|
|
123
|
+
return style;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return colorKeys.reduce(
|
|
127
|
+
(acc, key) => {
|
|
128
|
+
const value = acc[key];
|
|
99
129
|
|
|
100
|
-
|
|
101
|
-
(
|
|
102
|
-
|
|
103
|
-
const colorStyle = resolveColorForProp(entry, acc[value]);
|
|
130
|
+
// 1. The Expensive Edge-Case (Only for the app with root mappings)
|
|
131
|
+
if (allowDynamicColorsOutsideExtensions && isNotEmptyString(value)) {
|
|
132
|
+
const possibleColor = resolveColorForProp(entry, value);
|
|
104
133
|
|
|
105
|
-
|
|
106
|
-
}
|
|
134
|
+
acc[key] = isValidColor(possibleColor) ? possibleColor : null;
|
|
107
135
|
|
|
108
136
|
return acc;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// 2. The Fast Path (For 99% of apps)
|
|
140
|
+
if (isDataMappingPath(value)) {
|
|
141
|
+
// resolve the mapped color from the entry; fall back to null
|
|
142
|
+
// (RN ignores null style values) when the path is missing
|
|
143
|
+
const possibleColor = pathOr(null, value.split("."), entry);
|
|
144
|
+
|
|
145
|
+
acc[key] = possibleColor;
|
|
146
|
+
|
|
147
|
+
return acc;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// 3. Default Case: Treat as a raw color string
|
|
151
|
+
acc[key] = value;
|
|
152
|
+
|
|
153
|
+
return acc;
|
|
116
154
|
},
|
|
117
|
-
|
|
118
|
-
);
|
|
155
|
+
{ ...style }
|
|
156
|
+
);
|
|
157
|
+
};
|
|
119
158
|
|
|
120
159
|
export function isVideoPreviewEnabled({
|
|
121
160
|
enable_video_preview = false,
|
|
@@ -238,7 +277,7 @@ const recursiveCloneElement = (focused: boolean) => (element) => {
|
|
|
238
277
|
};
|
|
239
278
|
|
|
240
279
|
export const recursiveCloneElementsWithState = (focused: boolean, children) => {
|
|
241
|
-
if (
|
|
280
|
+
if (isNilOrEmpty(children)) {
|
|
242
281
|
return undefined;
|
|
243
282
|
}
|
|
244
283
|
|
|
@@ -252,7 +291,7 @@ export const cloneChildrenWithIds = (
|
|
|
252
291
|
ids: string[],
|
|
253
292
|
children: React.ReactElement[]
|
|
254
293
|
) => {
|
|
255
|
-
if (
|
|
294
|
+
if (isNilOrEmpty(children)) {
|
|
256
295
|
return undefined;
|
|
257
296
|
}
|
|
258
297
|
|
|
@@ -9,7 +9,7 @@ import { useScreenConfiguration } from "../useScreenConfiguration";
|
|
|
9
9
|
import { RefreshControl } from "../RefreshControl";
|
|
10
10
|
import { ifEmptyUseFallback } from "@applicaster/zapp-react-native-utils/cellUtils";
|
|
11
11
|
import {
|
|
12
|
-
|
|
12
|
+
useMarkPipesDataStale,
|
|
13
13
|
useProfilerLogging,
|
|
14
14
|
} from "@applicaster/zapp-react-native-utils/reactHooks";
|
|
15
15
|
import { useLoadingState } from "./hooks/useLoadingState";
|
|
@@ -154,7 +154,7 @@ function ComponentsMapComponent(props: Props) {
|
|
|
154
154
|
[flatListHeight]
|
|
155
155
|
);
|
|
156
156
|
|
|
157
|
-
|
|
157
|
+
useMarkPipesDataStale(riverId, riverComponents);
|
|
158
158
|
|
|
159
159
|
const refreshControl = React.useMemo(
|
|
160
160
|
() => (pullToRefreshEnabled ? <RefreshControl /> : null),
|
|
@@ -10,11 +10,7 @@ exports[`componentsMap renders renders components map correctly 1`] = `
|
|
|
10
10
|
>
|
|
11
11
|
<View
|
|
12
12
|
onLayout={[Function]}
|
|
13
|
-
style={
|
|
14
|
-
{
|
|
15
|
-
"flex": 1,
|
|
16
|
-
}
|
|
17
|
-
}
|
|
13
|
+
style={{}}
|
|
18
14
|
>
|
|
19
15
|
<RCTScrollView
|
|
20
16
|
ListFooterComponent={
|
|
@@ -167,11 +163,6 @@ exports[`componentsMap renders renders components map correctly 1`] = `
|
|
|
167
163
|
>
|
|
168
164
|
<View
|
|
169
165
|
onLayout={[Function]}
|
|
170
|
-
style={
|
|
171
|
-
{
|
|
172
|
-
"flex": 1,
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
166
|
>
|
|
176
167
|
<View />
|
|
177
168
|
</View>
|
|
@@ -183,11 +174,6 @@ exports[`componentsMap renders renders components map correctly 1`] = `
|
|
|
183
174
|
>
|
|
184
175
|
<View
|
|
185
176
|
onLayout={[Function]}
|
|
186
|
-
style={
|
|
187
|
-
{
|
|
188
|
-
"flex": 1,
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
177
|
/>
|
|
192
178
|
</View>
|
|
193
179
|
<View
|
|
@@ -82,6 +82,8 @@ export function Screen(_props: Props) {
|
|
|
82
82
|
const style = React.useMemo(
|
|
83
83
|
() => ({
|
|
84
84
|
...screenStyles,
|
|
85
|
+
// FIXME: add logic to use backgroundColor if it valid_color and non-transparent, otherwise use theme.app_background_color.
|
|
86
|
+
// Current implementation treats "red" color as invalid color.
|
|
85
87
|
backgroundColor: isValidColor(backgroundColor)
|
|
86
88
|
? backgroundColor
|
|
87
89
|
: theme.app_background_color,
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import { StyleSheet } from "react-native";
|
|
3
|
-
import { SafeAreaView } from "
|
|
4
|
-
|
|
2
|
+
import { StyleSheet, SafeAreaView as RNSafeAreaView } from "react-native";
|
|
3
|
+
import { SafeAreaView } from "react-native-safe-area-context";
|
|
5
4
|
import { useIsRTL } from "@applicaster/zapp-react-native-utils/localizationUtils";
|
|
6
5
|
import CloseButton from "./Buttons/CloseButton";
|
|
7
6
|
import BackButton from "./Buttons/BackButton";
|
|
8
7
|
import BarTitle from "./BarTitle";
|
|
8
|
+
import { platformSelect } from "@applicaster/zapp-react-native-utils/reactUtils";
|
|
9
9
|
|
|
10
10
|
type Props = {
|
|
11
11
|
screenStyles: {
|
|
@@ -24,6 +24,11 @@ const defaultState: NavBarState = {
|
|
|
24
24
|
title: "",
|
|
25
25
|
};
|
|
26
26
|
|
|
27
|
+
const SafeAreaViewPlatform = platformSelect({
|
|
28
|
+
ios: RNSafeAreaView,
|
|
29
|
+
android: SafeAreaView,
|
|
30
|
+
});
|
|
31
|
+
|
|
27
32
|
const BarView = ({ screenStyles, barState = defaultState }: Props) => {
|
|
28
33
|
const isRTL = useIsRTL();
|
|
29
34
|
|
|
@@ -45,7 +50,7 @@ const BarView = ({ screenStyles, barState = defaultState }: Props) => {
|
|
|
45
50
|
);
|
|
46
51
|
|
|
47
52
|
return (
|
|
48
|
-
<
|
|
53
|
+
<SafeAreaViewPlatform
|
|
49
54
|
edges={["top", "left", "right"]}
|
|
50
55
|
style={[style.view, isRTL ? style.rtlStyle : {}]}
|
|
51
56
|
testID="BarView-safeAreaView"
|
|
@@ -53,7 +58,7 @@ const BarView = ({ screenStyles, barState = defaultState }: Props) => {
|
|
|
53
58
|
{backButton}
|
|
54
59
|
<BarTitle title={barState.title} screenStyles={screenStyles} />
|
|
55
60
|
{closeButton}
|
|
56
|
-
</
|
|
61
|
+
</SafeAreaViewPlatform>
|
|
57
62
|
);
|
|
58
63
|
};
|
|
59
64
|
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { renderHook, act } from "@testing-library/react-native";
|
|
2
|
+
|
|
3
|
+
type DimensionsStore = {
|
|
4
|
+
setState: (
|
|
5
|
+
partial: { dimensions: Record<string, unknown>; ids: string[] },
|
|
6
|
+
replace?: boolean
|
|
7
|
+
) => void;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
const createdStores: DimensionsStore[] = [];
|
|
11
|
+
|
|
12
|
+
jest.mock("zustand", () => {
|
|
13
|
+
const actual = jest.requireActual<typeof import("zustand")>("zustand");
|
|
14
|
+
|
|
15
|
+
return {
|
|
16
|
+
...actual,
|
|
17
|
+
create: ((initializer: Parameters<typeof actual.create>[0]) => {
|
|
18
|
+
const store = actual.create(initializer);
|
|
19
|
+
createdStores.push(store);
|
|
20
|
+
|
|
21
|
+
return store;
|
|
22
|
+
}) as typeof actual.create,
|
|
23
|
+
};
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
import { useCachedDimensions } from "../index";
|
|
27
|
+
|
|
28
|
+
const resetStore = () => {
|
|
29
|
+
const store = createdStores[createdStores.length - 1];
|
|
30
|
+
|
|
31
|
+
act(() => {
|
|
32
|
+
store?.setState({ dimensions: {}, ids: [] });
|
|
33
|
+
});
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
describe("useCachedDimensions", () => {
|
|
37
|
+
beforeEach(() => {
|
|
38
|
+
resetStore();
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
describe("getCachedDimensions", () => {
|
|
42
|
+
it("returns default dimensions when id has no cached value", () => {
|
|
43
|
+
const { result } = renderHook(() => useCachedDimensions("component-1"));
|
|
44
|
+
|
|
45
|
+
expect(result.current.getCachedDimensions()).toEqual({
|
|
46
|
+
width: undefined,
|
|
47
|
+
height: undefined,
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("returns cached dimensions after setCachedDimensions", () => {
|
|
52
|
+
const { result } = renderHook(() => useCachedDimensions("component-1"));
|
|
53
|
+
const dimensions = { width: 100, height: 200 };
|
|
54
|
+
|
|
55
|
+
act(() => {
|
|
56
|
+
result.current.setCachedDimensions(dimensions);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
expect(result.current.getCachedDimensions()).toEqual(dimensions);
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
describe("setCachedDimensions", () => {
|
|
64
|
+
it("updates dimensions for the hook id", () => {
|
|
65
|
+
const { result } = renderHook(() => useCachedDimensions("component-1"));
|
|
66
|
+
|
|
67
|
+
act(() => {
|
|
68
|
+
result.current.setCachedDimensions({ width: 50, height: 75 });
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
expect(result.current.getCachedDimensions()).toEqual({
|
|
72
|
+
width: 50,
|
|
73
|
+
height: 75,
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it("shares cached dimensions between hook instances with the same id", () => {
|
|
78
|
+
const { result: first } = renderHook(() =>
|
|
79
|
+
useCachedDimensions("shared-id")
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
const { result: second } = renderHook(() =>
|
|
83
|
+
useCachedDimensions("shared-id")
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
act(() => {
|
|
87
|
+
first.current.setCachedDimensions({ width: 10, height: 20 });
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
expect(second.current.getCachedDimensions()).toEqual({
|
|
91
|
+
width: 10,
|
|
92
|
+
height: 20,
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
describe("getLastCachedDimensions", () => {
|
|
98
|
+
it("returns undefined when no dimensions have been cached", () => {
|
|
99
|
+
const { result } = renderHook(() => useCachedDimensions("component-1"));
|
|
100
|
+
|
|
101
|
+
expect(result.current.getLastCachedDimensions()).toBeUndefined();
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it("returns the most recently cached dimensions across ids", () => {
|
|
105
|
+
const { result: first } = renderHook(() => useCachedDimensions("first"));
|
|
106
|
+
|
|
107
|
+
const { result: second } = renderHook(() =>
|
|
108
|
+
useCachedDimensions("second")
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
act(() => {
|
|
112
|
+
first.current.setCachedDimensions({ width: 100, height: 100 });
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
act(() => {
|
|
116
|
+
second.current.setCachedDimensions({ width: 200, height: 200 });
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
expect(first.current.getLastCachedDimensions()).toEqual({
|
|
120
|
+
width: 200,
|
|
121
|
+
height: 200,
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it("returns the latest dimensions when the same id is updated multiple times", () => {
|
|
126
|
+
const { result } = renderHook(() => useCachedDimensions("component-1"));
|
|
127
|
+
|
|
128
|
+
act(() => {
|
|
129
|
+
result.current.setCachedDimensions({ width: 100, height: 100 });
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
act(() => {
|
|
133
|
+
result.current.setCachedDimensions({ width: 300, height: 400 });
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
expect(result.current.getLastCachedDimensions()).toEqual({
|
|
137
|
+
width: 300,
|
|
138
|
+
height: 400,
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
describe("return value", () => {
|
|
144
|
+
it("exposes getCachedDimensions, setCachedDimensions, and getLastCachedDimensions", () => {
|
|
145
|
+
const { result } = renderHook(() => useCachedDimensions("component-1"));
|
|
146
|
+
|
|
147
|
+
expect(result.current).toEqual({
|
|
148
|
+
getCachedDimensions: expect.any(Function),
|
|
149
|
+
setCachedDimensions: expect.any(Function),
|
|
150
|
+
getLastCachedDimensions: expect.any(Function),
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
});
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { create, useStore } from "zustand";
|
|
2
2
|
import { produce } from "immer";
|
|
3
3
|
import { useCallback, useMemo } from "react";
|
|
4
|
+
import { last } from "@applicaster/zapp-react-native-utils/utils";
|
|
4
5
|
|
|
5
6
|
type Dimensions = {
|
|
6
7
|
width: Option<number>;
|
|
@@ -10,14 +11,17 @@ type Dimensions = {
|
|
|
10
11
|
type DimensionsState = {
|
|
11
12
|
dimensions: Record<string, Dimensions>;
|
|
12
13
|
setCachedDimensions: (id: string, dimensions: Dimensions) => void;
|
|
14
|
+
ids: string[];
|
|
13
15
|
};
|
|
14
16
|
|
|
15
17
|
const dimensionsStore = create<DimensionsState>((set) => ({
|
|
16
18
|
dimensions: {},
|
|
19
|
+
ids: [],
|
|
17
20
|
setCachedDimensions: (id, dimensions) =>
|
|
18
21
|
set(
|
|
19
22
|
produce((draft) => {
|
|
20
23
|
draft.dimensions[id] = dimensions;
|
|
24
|
+
draft.ids.push(id);
|
|
21
25
|
})
|
|
22
26
|
),
|
|
23
27
|
}));
|
|
@@ -28,7 +32,7 @@ const initialDimensions = () => ({
|
|
|
28
32
|
});
|
|
29
33
|
|
|
30
34
|
export const useCachedDimensions = (id: string) => {
|
|
31
|
-
const { dimensions, setCachedDimensions } = useStore(
|
|
35
|
+
const { dimensions, setCachedDimensions, ids } = useStore(
|
|
32
36
|
dimensionsStore
|
|
33
37
|
) as DimensionsState;
|
|
34
38
|
|
|
@@ -40,11 +44,22 @@ export const useCachedDimensions = (id: string) => {
|
|
|
40
44
|
setCachedDimensions(id, newDimensions);
|
|
41
45
|
}, []);
|
|
42
46
|
|
|
47
|
+
const getLastCachedDimensions = useCallback(() => {
|
|
48
|
+
const previousId = last(ids);
|
|
49
|
+
|
|
50
|
+
if (!previousId) {
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return dimensions[previousId];
|
|
55
|
+
}, [dimensions, ids]);
|
|
56
|
+
|
|
43
57
|
return useMemo(
|
|
44
58
|
() => ({
|
|
45
59
|
getCachedDimensions,
|
|
46
60
|
setCachedDimensions: handleSetCachedDimensions,
|
|
61
|
+
getLastCachedDimensions,
|
|
47
62
|
}),
|
|
48
|
-
[getCachedDimensions, handleSetCachedDimensions]
|
|
63
|
+
[getCachedDimensions, handleSetCachedDimensions, getLastCachedDimensions]
|
|
49
64
|
);
|
|
50
65
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@applicaster/zapp-react-native-ui-components",
|
|
3
|
-
"version": "16.0.0-rc.
|
|
3
|
+
"version": "16.0.0-rc.23",
|
|
4
4
|
"description": "Applicaster Zapp React Native ui components for the Quick Brick App",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
@@ -28,10 +28,10 @@
|
|
|
28
28
|
},
|
|
29
29
|
"homepage": "https://github.com/applicaster/quickbrick#readme",
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@applicaster/applicaster-types": "16.0.0-rc.
|
|
32
|
-
"@applicaster/zapp-react-native-bridge": "16.0.0-rc.
|
|
33
|
-
"@applicaster/zapp-react-native-redux": "16.0.0-rc.
|
|
34
|
-
"@applicaster/zapp-react-native-utils": "16.0.0-rc.
|
|
31
|
+
"@applicaster/applicaster-types": "16.0.0-rc.23",
|
|
32
|
+
"@applicaster/zapp-react-native-bridge": "16.0.0-rc.23",
|
|
33
|
+
"@applicaster/zapp-react-native-redux": "16.0.0-rc.23",
|
|
34
|
+
"@applicaster/zapp-react-native-utils": "16.0.0-rc.23",
|
|
35
35
|
"fast-json-stable-stringify": "^2.1.0",
|
|
36
36
|
"promise": "^8.3.0",
|
|
37
37
|
"url": "^0.11.0",
|