@applicaster/zapp-react-native-ui-components 14.0.33 → 14.0.34-alpha.1413073222

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.
@@ -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";
@@ -50,7 +51,8 @@ export function configInflater(
50
51
  additionalProps?: Record<string, any>;
51
52
  data?: Array<{ propName: string; func: Function; args: any[] }>;
52
53
  elements?: any[];
53
- }
54
+ },
55
+ allowDynamicColorsOutsideExtensions: boolean
54
56
  ) {
55
57
  const props = data.reduce(
56
58
  (acc, curr) => {
@@ -65,13 +67,13 @@ export function configInflater(
65
67
 
66
68
  if (Array.isArray(elements)) {
67
69
  adjustedElements = elements.map((element) =>
68
- configInflater(entry, element as any)
70
+ configInflater(entry, element as any, allowDynamicColorsOutsideExtensions)
69
71
  );
70
72
  }
71
73
 
72
74
  return {
73
75
  type,
74
- style: resolveColor(entry, style),
76
+ style: resolveColor(entry, style, allowDynamicColorsOutsideExtensions),
75
77
  props,
76
78
  elements: adjustedElements,
77
79
  };
@@ -95,9 +97,17 @@ function resolveElementsNode(entry, state, elements) {
95
97
  }
96
98
 
97
99
  export function defaultDataAdapter(elements) {
98
- return function elementsBuilder({ entry, state = "default" }) {
100
+ return function elementsBuilder({
101
+ entry,
102
+ state = "default",
103
+ allowDynamicColorsOutsideExtensions,
104
+ }) {
99
105
  return resolveElementsNode(entry, state, elements).map((element) =>
100
- configInflater(entry, element)
106
+ configInflater(
107
+ entry,
108
+ element,
109
+ toBooleanWithDefaultFalse(allowDynamicColorsOutsideExtensions)
110
+ )
101
111
  );
102
112
  };
103
113
  }
@@ -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
- [state, item, entryIsSelected] // Assuming that item won't mutate
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
- beforeEach(() => {
17
- loggerSpy.mockClear();
18
- });
19
-
20
- it("resolve color with path", () => {
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
- expect(resolveColor(entry, style)).toEqual({
26
- color: entry.extensions.color,
20
+ expect(resolveColor(entry, style)).toEqual({
21
+ color: entry.extensions.color,
22
+ });
27
23
  });
28
- });
29
24
 
30
- it("logs a warning when path is undefined", () => {
31
- const style = {
32
- color: "invalid_path",
33
- };
25
+ it("passes through non-extensions values as raw color strings", () => {
26
+ const style = {
27
+ color: "invalid_path",
28
+ };
34
29
 
35
- expect(resolveColor(entry, style)).toEqual({
36
- color: undefined,
30
+ expect(resolveColor(entry, style)).toEqual({
31
+ color: "invalid_path",
32
+ });
37
33
  });
38
34
 
39
- expect(loggerSpy).toHaveBeenCalledWith(
40
- expect.objectContaining({
41
- message: "Cannot resolve property invalid_path from the entry.",
42
- data: {
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
- expect(resolveColor(entry, style)).toEqual({
58
- backgroundColor: "red",
59
- borderColor: "green",
60
- width: "100%",
40
+ expect(resolveColor(entry, style)).toEqual({
41
+ color: "background_color",
42
+ });
61
43
  });
62
- });
63
44
 
64
- it("not replace color with in hex format", () => {
65
- const style = {
66
- color: "#000000",
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
- expect(resolveColor(entry, style)).toEqual(style);
70
- });
52
+ expect(resolveColor(entry, style)).toEqual({
53
+ backgroundColor: "red",
54
+ borderColor: "green",
55
+ width: "100%",
56
+ });
57
+ });
71
58
 
72
- it("not replace color with in rgda format", () => {
73
- const style = {
74
- color: "rgba(0,0,0,0)",
75
- };
59
+ it("passes through literal hex color values unchanged", () => {
60
+ const style = {
61
+ color: "#000000",
62
+ };
76
63
 
77
- expect(resolveColor(entry, style)).toEqual(style);
78
- });
64
+ expect(resolveColor(entry, style)).toEqual(style);
65
+ });
79
66
 
80
- it("doesn't replace transparent as a color", () => {
81
- const style = {
82
- color: "transparent",
83
- };
67
+ it("passes through literal rgba color values unchanged", () => {
68
+ const style = {
69
+ color: "rgba(0,0,0,0)",
70
+ };
84
71
 
85
- expect(resolveColor(entry, style)).toEqual(style);
86
- });
72
+ expect(resolveColor(entry, style)).toEqual(style);
73
+ });
87
74
 
88
- it("return nil if style is nil", () => {
89
- const style = undefined;
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
- expect(resolveColor(entry, style)).toBeUndefined();
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
- it("not modify style without color prop", () => {
95
- const style = {
96
- test: 1,
97
- };
88
+ expect(resolveColor(entry, style)).toEqual(style);
89
+ });
98
90
 
99
- expect(resolveColor(entry, style)).toEqual(style);
100
- });
91
+ it("passes through named color values as literal colors", () => {
92
+ const style = {
93
+ color: "black",
94
+ };
101
95
 
102
- it("not modify style with not existing path", () => {
103
- const style = {
104
- color: "not.exist.path",
105
- };
96
+ expect(resolveColor(entry, style)).toEqual(style);
97
+ });
106
98
 
107
- expect(resolveColor(entry, style)).toEqual({
108
- color: undefined,
99
+ it("returns style unchanged when style is undefined", () => {
100
+ expect(resolveColor(entry, undefined)).toBeUndefined();
109
101
  });
110
- });
111
102
 
112
- it("not modify style with empty path", () => {
113
- const style = {
114
- color: "",
115
- };
103
+ it("returns style unchanged when style is null", () => {
104
+ expect(resolveColor(entry, null)).toBeNull();
105
+ });
116
106
 
117
- expect(resolveColor(entry, style)).toEqual(style);
118
- });
107
+ it("returns style unchanged when it has no color-related props", () => {
108
+ const style = {
109
+ test: 1,
110
+ };
119
111
 
120
- describe("memoization", () => {
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("hits cache with same entry and style references", () => {
127
- const style = { color: "extensions.color" };
115
+ it("returns null when extensions path is missing from entry", () => {
116
+ const style = {
117
+ color: "extensions.missing.path",
118
+ };
128
119
 
129
- const result1 = resolveColor(entry, style);
130
- const result2 = resolveColor(entry, style);
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(result1).toBe(result2); // Same object reference
130
+ expect(resolveColor(entry, style)).toEqual({
131
+ color: "not_a_color",
132
+ });
133
133
  });
134
134
 
135
- it("hits cache with new references but equal entry/style values", () => {
136
- const entryClone = {
137
- extensions: {
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
- const style = { color: "extensions.color" };
144
- const styleClone = { color: "extensions.color" };
140
+ expect(resolveColor(entry, style)).toEqual({
141
+ color: "",
142
+ });
143
+ });
145
144
 
146
- const result1 = resolveColor(entry, style);
147
- const result2 = resolveColor(entryClone, styleClone);
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(result1).toBe(result2);
150
+ expect(resolveColor(entry, style)).toEqual({
151
+ color: "rgba(239,239,239,1.0)",
152
+ });
150
153
  });
154
+ });
151
155
 
152
- it("misses cache when entry is new object", () => {
153
- const entry2 = { extensions: { color: "blue" } }; // Same values, different object
154
- const style = { color: "extensions.color" };
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
- const result1 = resolveColor(entry, style);
157
- const result2 = resolveColor(entry2, style);
167
+ it("resolves extensions paths through resolveColorForProp", () => {
168
+ const style = {
169
+ color: "extensions.color",
170
+ };
158
171
 
159
- expect(result1).not.toBe(result2); // Different object references
172
+ expect(resolveColor(entry, style, true)).toEqual({
173
+ color: "red",
174
+ });
160
175
  });
161
176
 
162
- it("misses cache when entry property changes", () => {
163
- const myEntry = {
164
- extensions: {
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
- const style = { color: "extensions.color" };
182
+ expect(resolveColor(entry, style, true)).toEqual({
183
+ color: null,
184
+ });
185
+ });
171
186
 
172
- const result1 = resolveColor(myEntry, style);
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
- myEntry.extensions.color = "blue"; // Change property
175
- const result2 = resolveColor(myEntry, style);
192
+ expect(resolveColor(entry, style, true)).toEqual({
193
+ color: null,
194
+ });
195
+ });
176
196
 
177
- expect(result1).toEqual({ color: "red" });
178
- expect(result2).toEqual({ color: "blue" });
179
- expect(result1).not.toBe(result2);
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("misses cache when style changes", () => {
183
- const style1 = { color: "extensions.color" };
184
- const style2 = { backgroundColor: "extensions.color" };
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
- const result1 = resolveColor(entry, style1);
187
- const result2 = resolveColor(entry, style2);
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(result1).toEqual({ color: "red" });
190
- expect(result2).toEqual({ backgroundColor: "red" });
191
- expect(result1).not.toBe(result2);
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,21 +1,23 @@
1
1
  import React, { useMemo } from "react";
2
2
  import * as R from "ramda";
3
- import validateColor from "validate-color";
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";
7
+ import { isValidColor } from "@applicaster/zapp-react-native-utils/colorUtils";
8
+ import { getColorFromData } from "@applicaster/zapp-react-native-utils/cellUtils";
9
+ import { pathOr, get, isNil } from "@applicaster/zapp-react-native-utils/utils";
10
+ import { isNilOrEmpty } from "@applicaster/zapp-react-native-utils/reactUtils/helpers";
11
+ import { useRoute } from "@applicaster/zapp-react-native-utils/reactHooks";
12
+ import { useScreenStateStore } from "@applicaster/zapp-react-native-utils/reactHooks/navigation/useScreenStateStore";
6
13
 
14
+ import { isNotEmptyString } from "@applicaster/zapp-react-native-utils/stringUtils";
7
15
  import { masterCellLogger } from "../logger";
8
16
  import { getCellState } from "../../Cell/utils";
9
- import { getColorFromData } from "@applicaster/zapp-react-native-utils/cellUtils";
10
- import { get } from "@applicaster/zapp-react-native-utils/utils";
11
17
  import { isCellSelected, useBehaviorUpdate } from "./behaviorProvider";
12
- import { useRoute } from "@applicaster/zapp-react-native-utils/reactHooks";
13
- import { useScreenStateStore } from "@applicaster/zapp-react-native-utils/reactHooks/navigation/useScreenStateStore";
14
- import memoizee from "memoizee";
15
- import stringify from "fast-json-stable-stringify";
16
18
 
17
19
  const hasElementSpecificViewType = (viewType) => (element) => {
18
- if (R.isNil(element)) {
20
+ if (isNil(element)) {
19
21
  return false;
20
22
  }
21
23
 
@@ -27,12 +29,20 @@ const hasElementSpecificViewType = (viewType) => (element) => {
27
29
  return hasElementsSpecificViewType(viewType)(element.elements);
28
30
  };
29
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
+
30
40
  const logWarning = (
31
41
  colorValueFromCellStyle,
32
42
  colorFromProp,
33
43
  colorValueFromEntry
34
44
  ) => {
35
- if (R.isNil(colorValueFromEntry)) {
45
+ if (isNil(colorValueFromEntry)) {
36
46
  masterCellLogger.warn({
37
47
  message: `Cannot resolve property ${colorValueFromCellStyle} from the entry.`,
38
48
  data: {
@@ -43,15 +53,10 @@ const logWarning = (
43
53
  }
44
54
  };
45
55
 
46
- export const hasElementsSpecificViewType = (viewType) => (elements) => {
47
- if (R.isNil(elements) || R.isEmpty(elements)) {
48
- return false;
49
- }
50
-
51
- return R.any(hasElementSpecificViewType(viewType))(elements);
52
- };
53
-
54
- function resolveColorForProp(entry: any, colorFromProp: string | undefined) {
56
+ export function resolveColorForProp(
57
+ entry: any,
58
+ colorFromProp: string | undefined
59
+ ) {
55
60
  if (!colorFromProp) {
56
61
  return undefined;
57
62
  }
@@ -61,9 +66,7 @@ function resolveColorForProp(entry: any, colorFromProp: string | undefined) {
61
66
  colorFromProp.split(".")
62
67
  );
63
68
 
64
- const color = colorFromProp.replace(".00", "").replace(".0", ""); // https://github.com/dreamyguy/validate-color/issues/44
65
-
66
- if (nestedEntryValue === undefined && !validateColor(color)) {
69
+ if (nestedEntryValue === undefined && !isValidColor(colorFromProp)) {
67
70
  logWarning(colorFromProp, colorFromProp, nestedEntryValue);
68
71
 
69
72
  return undefined;
@@ -85,36 +88,73 @@ function resolveColorForProp(entry: any, colorFromProp: string | undefined) {
85
88
 
86
89
  const getColorKeys = memoizee((style) => {
87
90
  const styleKeys = Object.keys(style);
88
- const colorKeys = styleKeys.filter((key) => /color/i.test(key));
89
91
 
90
- return colorKeys;
92
+ return styleKeys.filter((key) => /color/i.test(key));
91
93
  });
92
94
 
93
- export const resolveColor = memoizee(
94
- (entry, style) => {
95
- if (style === null || style === undefined) {
96
- return style;
97
- }
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
+ }
98
119
 
99
- return getColorKeys(style).reduce(
100
- (acc, value) => {
101
- if (acc[value] && typeof acc[value] === "string") {
102
- const colorStyle = resolveColorForProp(entry, acc[value]);
120
+ const colorKeys = getColorKeys(style);
103
121
 
104
- acc[value] = colorStyle;
105
- }
122
+ if (colorKeys.length === 0) {
123
+ return style;
124
+ }
125
+
126
+ return colorKeys.reduce(
127
+ (acc, key) => {
128
+ const value = acc[key];
129
+
130
+ // 1. The Expensive Edge-Case (Only for the app with root mappings)
131
+ if (allowDynamicColorsOutsideExtensions && isNotEmptyString(value)) {
132
+ const possibleColor = resolveColorForProp(entry, value);
133
+
134
+ acc[key] = isValidColor(possibleColor) ? possibleColor : null;
106
135
 
107
136
  return acc;
108
- },
109
- { ...style }
110
- );
111
- },
112
- {
113
- normalizer: (args) => {
114
- return [stringify(args[0]), stringify(args[1])].join("|");
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;
115
154
  },
116
- }
117
- );
155
+ { ...style }
156
+ );
157
+ };
118
158
 
119
159
  export function isVideoPreviewEnabled({
120
160
  enable_video_preview = false,
@@ -192,7 +232,7 @@ const recursiveCloneElement = (focused: boolean) => (element) => {
192
232
  };
193
233
 
194
234
  export const recursiveCloneElementsWithState = (focused: boolean, children) => {
195
- if (R.isNil(children) || R.isEmpty(children)) {
235
+ if (isNilOrEmpty(children)) {
196
236
  return undefined;
197
237
  }
198
238
 
@@ -202,8 +242,11 @@ export const recursiveCloneElementsWithState = (focused: boolean, children) => {
202
242
  const next = (currentIndex, items) => items[currentIndex + 1];
203
243
  const previous = (currentIndex, items) => items[currentIndex - 1];
204
244
 
205
- export const cloneElementsWithIds = (ids, children) => {
206
- if (R.isNil(children) || R.isEmpty(children)) {
245
+ export const cloneElementsWithIds = (
246
+ ids: string[],
247
+ children: React.ReactElement[]
248
+ ) => {
249
+ if (isNilOrEmpty(children)) {
207
250
  return undefined;
208
251
  }
209
252
 
@@ -338,11 +338,15 @@ const PlayerContainerComponent = (props: Props) => {
338
338
  setIsLoadingNextVideo(false);
339
339
 
340
340
  const resumeTime = Number(item?.extensions?.resumeTime);
341
+ const activePlayer = playerManager.getActivePlayer();
341
342
 
342
- // Сhecking that the player itself knows where to start playing, and there is no need to call seekTo after returning from the Сhromecast
343
+ // Сhecking that the player itself knows where to start playing, and there is no need to call seekTo after returning from the Сhromecast.
344
+ // Live streams and a missing/zero resume position are skipped: seeking to 0 on
345
+ // a live stream pulls playback off the live edge into a buffer-starvation seek loop.
343
346
  if (
344
- !playerManager.getActivePlayer()?.hasResumePosition() &&
345
- !isNaN(resumeTime)
347
+ !activePlayer?.hasResumePosition() &&
348
+ resumeTime > 0 &&
349
+ !activePlayer?.isLive()
346
350
  ) {
347
351
  player?.seekTo(resumeTime);
348
352
  }
@@ -19,10 +19,13 @@ import {
19
19
  useScreenData,
20
20
  } from "@applicaster/zapp-react-native-utils/reactHooks";
21
21
  import { getNavigationPluginModule } from "@applicaster/zapp-react-native-app/App/Layout/layoutHelpers";
22
+ import {
23
+ isValidColor,
24
+ isTransparentColor,
25
+ } from "@applicaster/zapp-react-native-utils/colorUtils";
22
26
 
23
27
  import { RouteManager } from "../RouteManager";
24
28
  import { useScreenConfiguration } from "../River/useScreenConfiguration";
25
- import { isValidColor } from "./utils";
26
29
  import { useWaitForValidOrientation } from "./hooks";
27
30
 
28
31
  const screenStyles = {
@@ -83,9 +86,10 @@ export function Screen(_props: Props) {
83
86
  const style = React.useMemo(
84
87
  () => ({
85
88
  ...screenStyles,
86
- backgroundColor: isValidColor(backgroundColor)
87
- ? backgroundColor
88
- : theme.app_background_color,
89
+ backgroundColor:
90
+ isValidColor(backgroundColor) && !isTransparentColor(backgroundColor)
91
+ ? backgroundColor
92
+ : theme.app_background_color,
89
93
  }),
90
94
  [theme.app_background_color, backgroundColor]
91
95
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@applicaster/zapp-react-native-ui-components",
3
- "version": "14.0.33",
3
+ "version": "14.0.34-alpha.1413073222",
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": "14.0.33",
32
- "@applicaster/zapp-react-native-bridge": "14.0.33",
33
- "@applicaster/zapp-react-native-redux": "14.0.33",
34
- "@applicaster/zapp-react-native-utils": "14.0.33",
31
+ "@applicaster/applicaster-types": "14.0.34-alpha.1413073222",
32
+ "@applicaster/zapp-react-native-bridge": "14.0.34-alpha.1413073222",
33
+ "@applicaster/zapp-react-native-redux": "14.0.34-alpha.1413073222",
34
+ "@applicaster/zapp-react-native-utils": "14.0.34-alpha.1413073222",
35
35
  "fast-json-stable-stringify": "^2.1.0",
36
36
  "promise": "^8.3.0",
37
37
  "url": "^0.11.0",
@@ -1,16 +0,0 @@
1
- import { map, split, replace, compose, trim } from "ramda";
2
-
3
- function colorIsNotTransparent(color) {
4
- const layers = compose(
5
- map(trim),
6
- split(","),
7
- replace(")", ""),
8
- replace("rgba(", "")
9
- )(color);
10
-
11
- return Number(layers?.[3]) > 0;
12
- }
13
-
14
- export function isValidColor(string) {
15
- return string && string !== "transparent" && colorIsNotTransparent(string);
16
- }