@applicaster/zapp-react-native-ui-components 13.0.0-alpha.7120331332 → 13.0.0-alpha.7122212380
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/BaseFocusable/index.ios.ts +1 -1
- package/Components/BaseFocusable/index.tsx +66 -29
- package/Components/Cell/Cell.tsx +7 -3
- package/Components/Cell/TvOSCellComponent.tsx +0 -4
- package/Components/Focusable/Focusable.tsx +0 -54
- package/Components/Focusable/FocusableTvOS.tsx +1 -1
- package/Components/Focusable/Touchable.tsx +19 -20
- package/Components/FocusableList/index.tsx +0 -1
- package/Components/MasterCell/DefaultComponents/BorderContainerView/__tests__/index.test.tsx +66 -0
- package/Components/MasterCell/DefaultComponents/BorderContainerView/index.tsx +4 -1
- package/Components/MasterCell/DefaultComponents/Image/Image.ios.tsx +6 -12
- package/Components/MasterCell/DefaultComponents/ImageBorderContainer/__tests__/index.test.ts +93 -0
- package/Components/MasterCell/DefaultComponents/SecondaryImage/utils.ts +1 -1
- package/Components/MasterCell/DefaultComponents/__tests__/image.test.js +1 -1
- package/Components/MasterCell/dataAdapter.ts +10 -3
- package/Components/MasterCell/hooks/useAsyncRendering/index.ts +2 -2
- package/Components/MasterCell/utils/index.ts +21 -7
- package/Components/PlayerContainer/PlayerContainer.tsx +3 -7
- package/Components/PlayerContainer/PlayerContainerContext.tsx +9 -0
- package/Components/River/__tests__/componentsMap.test.js +14 -1
- package/Components/Tabs/Tab.tsx +6 -6
- package/Components/TextInputTv/index.tsx +2 -2
- package/Components/Transitioner/AnimationManager.js +8 -8
- package/Components/VideoLive/LiveImageManager.ts +27 -1
- package/Components/VideoLive/PlayerLiveImageComponent.tsx +18 -16
- package/Components/VideoLive/__tests__/PlayerLiveImageComponent.test.tsx +51 -1
- package/Components/VideoLive/__tests__/__snapshots__/PlayerLiveImageComponent.test.tsx.snap +0 -5
- package/Components/VideoModal/ModalAnimation/AnimatedScrollModal.tsx +5 -1
- package/Components/VideoModal/ModalAnimation/AnimatedVideoPlayerComponent.tsx +5 -1
- package/Components/VideoModal/ModalAnimation/AnimationComponent.tsx +8 -7
- package/Components/VideoModal/ModalAnimation/utils.ts +2 -2
- package/Components/VideoModal/OpaqueLayer.tsx +33 -0
- package/Components/VideoModal/PlayerWrapper.tsx +16 -35
- package/Components/VideoModal/VideoModal.tsx +14 -23
- package/Components/VideoModal/__tests__/PlayerWrapper.test.tsx +1 -1
- package/Components/VideoModal/__tests__/__snapshots__/PlayerWrapper.test.tsx.snap +0 -90
- package/Components/VideoModal/hooks/__tests__/useDelayedPlayerDetails.test.ts +89 -0
- package/Components/VideoModal/hooks/index.ts +7 -0
- package/Components/VideoModal/hooks/useDelayedPlayerDetails.ts +49 -0
- package/Components/VideoModal/hooks/utils/__tests__/showDetails.test.ts +91 -0
- package/Components/VideoModal/hooks/utils/index.ts +33 -0
- package/Components/VideoModal/utils.ts +1 -1
- package/Components/default-cell-renderer/__tests__/defaultCellRenderer.test.tsx +7 -3
- package/Components/default-cell-renderer/viewTrees/tv/DefaultCell/__tests__/DefaultCell.test.tsx +5 -1
- package/Contexts/ScreenContext/index.tsx +3 -2
- package/Decorators/ZappPipesDataConnector/__tests__/Hero.js +1 -1
- package/Decorators/ZappPipesDataConnector/index.tsx +31 -1
- package/package.json +12 -6
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
+
import * as R from "ramda";
|
|
2
3
|
|
|
3
4
|
import {
|
|
4
5
|
BaseFocusable as BaseFocusableInterface,
|
|
@@ -7,7 +8,6 @@ import {
|
|
|
7
8
|
|
|
8
9
|
import { createReactRef } from "@applicaster/zapp-react-native-utils/reactUtils";
|
|
9
10
|
import { focusManager } from "@applicaster/zapp-react-native-utils/appUtils/focusManager";
|
|
10
|
-
import * as FOCUS_EVENTS from "@applicaster/zapp-react-native-utils/appUtils/focusManager/events";
|
|
11
11
|
import { noop } from "@applicaster/zapp-react-native-utils/functionUtils";
|
|
12
12
|
|
|
13
13
|
type ScrollDirection = FocusManager.IOS.Direction | FocusManager.Web.Direction;
|
|
@@ -35,7 +35,7 @@ type Props = {
|
|
|
35
35
|
};
|
|
36
36
|
|
|
37
37
|
export class BaseFocusable<
|
|
38
|
-
T extends Props = Props
|
|
38
|
+
T extends Props = Props,
|
|
39
39
|
> extends BaseFocusableInterface<T> {
|
|
40
40
|
constructor(props) {
|
|
41
41
|
super(props);
|
|
@@ -131,12 +131,6 @@ export class BaseFocusable<
|
|
|
131
131
|
willLoseFocus: FocusManager.FocusEventCB = (focusable, scrollDirection) => {
|
|
132
132
|
const { willLoseFocus = noop } = this.props;
|
|
133
133
|
willLoseFocus(focusable, scrollDirection);
|
|
134
|
-
|
|
135
|
-
focusManager.invokeHandler?.(
|
|
136
|
-
FOCUS_EVENTS.WILL_LOSE_FOCUS,
|
|
137
|
-
this,
|
|
138
|
-
scrollDirection
|
|
139
|
-
);
|
|
140
134
|
};
|
|
141
135
|
|
|
142
136
|
willReceiveFocus: FocusManager.FocusEventCB = (
|
|
@@ -145,12 +139,6 @@ export class BaseFocusable<
|
|
|
145
139
|
) => {
|
|
146
140
|
const { willReceiveFocus = noop } = this.props;
|
|
147
141
|
willReceiveFocus(focusable, scrollDirection);
|
|
148
|
-
|
|
149
|
-
focusManager.invokeHandler?.(
|
|
150
|
-
FOCUS_EVENTS.WILL_RECEIVE_FOCUS,
|
|
151
|
-
this,
|
|
152
|
-
scrollDirection
|
|
153
|
-
);
|
|
154
142
|
};
|
|
155
143
|
|
|
156
144
|
/**
|
|
@@ -163,8 +151,6 @@ export class BaseFocusable<
|
|
|
163
151
|
const { onFocus = noop } = this.props;
|
|
164
152
|
this.setFocusedState(true);
|
|
165
153
|
onFocus(focusable, scrollDirection);
|
|
166
|
-
|
|
167
|
-
focusManager.invokeHandler?.(FOCUS_EVENTS.FOCUS, this, scrollDirection);
|
|
168
154
|
};
|
|
169
155
|
|
|
170
156
|
/**
|
|
@@ -179,12 +165,6 @@ export class BaseFocusable<
|
|
|
179
165
|
) => {
|
|
180
166
|
const { hasReceivedFocus = noop } = this.props;
|
|
181
167
|
hasReceivedFocus(focusable, scrollDirection);
|
|
182
|
-
|
|
183
|
-
focusManager.invokeHandler?.(
|
|
184
|
-
FOCUS_EVENTS.HAS_RECEIVED_FOCUS,
|
|
185
|
-
this,
|
|
186
|
-
scrollDirection
|
|
187
|
-
);
|
|
188
168
|
};
|
|
189
169
|
|
|
190
170
|
/**
|
|
@@ -196,12 +176,6 @@ export class BaseFocusable<
|
|
|
196
176
|
hasLostFocus: FocusManager.FocusEventCB = (focusable, scrollDirection) => {
|
|
197
177
|
const { hasLostFocus = noop } = this.props;
|
|
198
178
|
hasLostFocus(focusable, scrollDirection);
|
|
199
|
-
|
|
200
|
-
focusManager.invokeHandler?.(
|
|
201
|
-
FOCUS_EVENTS.HAS_LOST_FOCUS,
|
|
202
|
-
this,
|
|
203
|
-
scrollDirection
|
|
204
|
-
);
|
|
205
179
|
};
|
|
206
180
|
|
|
207
181
|
/**
|
|
@@ -228,7 +202,6 @@ export class BaseFocusable<
|
|
|
228
202
|
const { onBlur = noop } = this.props;
|
|
229
203
|
this.setFocusedState(false);
|
|
230
204
|
onBlur(focusable, scrollDirection);
|
|
231
|
-
focusManager.invokeHandler?.(FOCUS_EVENTS.BLUR, this, scrollDirection);
|
|
232
205
|
};
|
|
233
206
|
|
|
234
207
|
onPress = (keyEvent?: Nullable<React.MouseEvent>) => {
|
|
@@ -269,4 +242,68 @@ export class BaseFocusable<
|
|
|
269
242
|
this.setState({ focused });
|
|
270
243
|
}
|
|
271
244
|
};
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* will invoke the underlying component's focus method
|
|
248
|
+
* @param {Object} scrollDirection
|
|
249
|
+
* @returns {Promise}
|
|
250
|
+
*/
|
|
251
|
+
focus(_, scrollDirection) {
|
|
252
|
+
return this.onFocus(this, scrollDirection); // invokeComponentMethod(this, "onFocus", scrollDirection);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* will invoke the underlying component's blur method
|
|
257
|
+
* @param {Object} scrollDirection
|
|
258
|
+
* @returns {Promise}
|
|
259
|
+
*/
|
|
260
|
+
blur(
|
|
261
|
+
_,
|
|
262
|
+
scrollDirection?: FocusManager.Web.Direction | FocusManager.IOS.Direction
|
|
263
|
+
) {
|
|
264
|
+
return this.onBlur(this, scrollDirection);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Sets the focus on this item. Will trigger sequentially a sequence of
|
|
269
|
+
* functions (willReceiveFocus, focus, hasReceivedFocus). If these functions (defined in the Focusable
|
|
270
|
+
* Item underlying component) return promises, execution will wait before it proceeds to the next. This
|
|
271
|
+
* is useful for triggering sequential operations that specifically require to fully run before or after
|
|
272
|
+
* focus is actually set on that item.
|
|
273
|
+
* @param {string} scrollDirection string representation of the direction of the navigation which landed
|
|
274
|
+
* to this item being focused
|
|
275
|
+
*/
|
|
276
|
+
_executeFocusSequence(methodNames, scrollDirection) {
|
|
277
|
+
return R.reduce(
|
|
278
|
+
(sequence, methodName) => {
|
|
279
|
+
const method = this[methodName]; // Access the method by name
|
|
280
|
+
|
|
281
|
+
if (typeof method !== "function") {
|
|
282
|
+
throw new Error(
|
|
283
|
+
`Method '${methodName}' not found or not a function.`
|
|
284
|
+
);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
return sequence
|
|
288
|
+
.then(() => method.call(this, scrollDirection))
|
|
289
|
+
.catch((e) => {
|
|
290
|
+
throw e; // Re-throw for consistent error handling
|
|
291
|
+
});
|
|
292
|
+
},
|
|
293
|
+
Promise.resolve(),
|
|
294
|
+
methodNames
|
|
295
|
+
);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
setFocus(scrollDirection) {
|
|
299
|
+
const focusMethods = ["willReceiveFocus", "focus", "hasReceivedFocus"];
|
|
300
|
+
|
|
301
|
+
return this._executeFocusSequence(focusMethods, scrollDirection);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
setBlur(scrollDirection) {
|
|
305
|
+
const blurMethods = ["willLoseFocus", "blur", "hasLostFocus"];
|
|
306
|
+
|
|
307
|
+
return this._executeFocusSequence(blurMethods, scrollDirection);
|
|
308
|
+
}
|
|
272
309
|
}
|
package/Components/Cell/Cell.tsx
CHANGED
|
@@ -273,10 +273,14 @@ export class CellComponent extends React.Component<Props, State> {
|
|
|
273
273
|
if (isFocused) {
|
|
274
274
|
const accessibilityManager = AccessibilityManager.getInstance();
|
|
275
275
|
|
|
276
|
+
const accessibilityTitle =
|
|
277
|
+
item?.extensions?.accessibility?.label || item?.title || "";
|
|
278
|
+
|
|
279
|
+
const accessibilityHint =
|
|
280
|
+
item?.extensions?.accessibility?.hint || "";
|
|
281
|
+
|
|
276
282
|
accessibilityManager.readText({
|
|
277
|
-
text:
|
|
278
|
-
item.extensions?.accessibility?.label || item.title
|
|
279
|
-
),
|
|
283
|
+
text: `${accessibilityTitle} ${accessibilityHint}`,
|
|
280
284
|
});
|
|
281
285
|
}
|
|
282
286
|
|
|
@@ -10,10 +10,6 @@ import { focusManager } from "@applicaster/zapp-react-native-utils/appUtils/focu
|
|
|
10
10
|
import { sendSelectCellEvent } from "@applicaster/zapp-react-native-utils/analyticsUtils";
|
|
11
11
|
import { noop } from "@applicaster/zapp-react-native-utils/functionUtils";
|
|
12
12
|
import { CellWithFocusable } from "./CellWithFocusable";
|
|
13
|
-
import { getCellState } from "./utils";
|
|
14
|
-
|
|
15
|
-
const _getCellState = (focused, selected) =>
|
|
16
|
-
getCellState({ focused, selected });
|
|
17
13
|
|
|
18
14
|
type Props = {
|
|
19
15
|
item: ZappEntry;
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
-
import * as R from "ramda";
|
|
3
2
|
|
|
4
3
|
import { BaseFocusable } from "../BaseFocusable";
|
|
5
4
|
import { focusManager } from "@applicaster/zapp-react-native-utils/appUtils/focusManager";
|
|
@@ -42,8 +41,6 @@ class Focusable extends BaseFocusable<Props> {
|
|
|
42
41
|
this.resetLongPressTimeout = this.resetLongPressTimeout.bind(this);
|
|
43
42
|
this.longPress = this.longPress.bind(this);
|
|
44
43
|
this.press = this.press.bind(this);
|
|
45
|
-
this.focus = this.focus.bind(this);
|
|
46
|
-
this.blur = this.blur.bind(this);
|
|
47
44
|
}
|
|
48
45
|
|
|
49
46
|
/**
|
|
@@ -55,37 +52,6 @@ class Focusable extends BaseFocusable<Props> {
|
|
|
55
52
|
return this.preferredFocus();
|
|
56
53
|
}
|
|
57
54
|
|
|
58
|
-
/**
|
|
59
|
-
* Sets the focus on this item. Will trigger sequentially a sequence of
|
|
60
|
-
* functions (willReceiveFocus, focus, hasReceivedFocus). If these functions (defined in the Focusable
|
|
61
|
-
* Item underlying component) return promises, execution will wait before it proceeds to the next. This
|
|
62
|
-
* is useful for triggering sequential operations that specifically require to fully run before or after
|
|
63
|
-
* focus is actually set on that item.
|
|
64
|
-
* @param {string} scrollDirection string representation of the direction of the navigation which landed
|
|
65
|
-
* to this item being focused
|
|
66
|
-
*/
|
|
67
|
-
setFocus(scrollDirection) {
|
|
68
|
-
const focusMethods = [
|
|
69
|
-
this.willReceiveFocus,
|
|
70
|
-
this.focus,
|
|
71
|
-
this.hasReceivedFocus,
|
|
72
|
-
];
|
|
73
|
-
|
|
74
|
-
const self = this;
|
|
75
|
-
|
|
76
|
-
return R.reduce(
|
|
77
|
-
(sequence, method) => {
|
|
78
|
-
return sequence
|
|
79
|
-
.then(() => method.apply(self, [scrollDirection]))
|
|
80
|
-
.catch((e) => {
|
|
81
|
-
throw e;
|
|
82
|
-
});
|
|
83
|
-
},
|
|
84
|
-
Promise.resolve(),
|
|
85
|
-
focusMethods
|
|
86
|
-
);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
55
|
startLongPressTimeout() {
|
|
90
56
|
this.longPressTimeout = setTimeout(() => {
|
|
91
57
|
this.longPress(null);
|
|
@@ -102,26 +68,6 @@ class Focusable extends BaseFocusable<Props> {
|
|
|
102
68
|
}
|
|
103
69
|
}
|
|
104
70
|
|
|
105
|
-
/**
|
|
106
|
-
* will invoke the underlying component's focus method
|
|
107
|
-
* @param {Object} scrollDirection
|
|
108
|
-
* @returns {Promise}
|
|
109
|
-
*/
|
|
110
|
-
focus(scrollDirection) {
|
|
111
|
-
return this.onFocus(this, scrollDirection); // invokeComponentMethod(this, "onFocus", scrollDirection);
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
/**
|
|
115
|
-
* will invoke the underlying component's blur method
|
|
116
|
-
* @param {Object} scrollDirection
|
|
117
|
-
* @returns {Promise}
|
|
118
|
-
*/
|
|
119
|
-
blur(
|
|
120
|
-
scrollDirection?: FocusManager.Web.Direction | FocusManager.IOS.Direction
|
|
121
|
-
) {
|
|
122
|
-
return this.onBlur(this, scrollDirection);
|
|
123
|
-
}
|
|
124
|
-
|
|
125
71
|
/**
|
|
126
72
|
* will invoke the underlying component's press method
|
|
127
73
|
* @param {Object} keyEvent
|
|
@@ -90,7 +90,7 @@ export class Focusable extends BaseFocusable<Props> {
|
|
|
90
90
|
onBlur(nativeEvent);
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
-
setFocus(
|
|
93
|
+
setFocus(_direction, callback) {
|
|
94
94
|
const focusMethods = [
|
|
95
95
|
{ method: this.willReceiveFocus },
|
|
96
96
|
{ method: this.focus, args: [callback] },
|
|
@@ -1,25 +1,24 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
2
|
|
|
3
|
-
type Props =
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
} & Partial<ParentFocus>;
|
|
3
|
+
type Props = {
|
|
4
|
+
id: string;
|
|
5
|
+
children: React.ReactNode;
|
|
6
|
+
onPress?: (ref: FocusManager.TouchableRef) => void;
|
|
7
|
+
onPressIn?: (ref: FocusManager.TouchableRef) => void;
|
|
8
|
+
onPressOut?: (ref: FocusManager.TouchableRef) => void;
|
|
9
|
+
onLongPress?: (ref: FocusManager.TouchableRef) => void;
|
|
10
|
+
onFocus?: (
|
|
11
|
+
ref: FocusManager.TouchableRef,
|
|
12
|
+
options: FocusManager.Android.CallbackOptions
|
|
13
|
+
) => void;
|
|
14
|
+
onBlur?: (
|
|
15
|
+
ref: FocusManager.TouchableRef,
|
|
16
|
+
options: FocusManager.Android.CallbackOptions
|
|
17
|
+
) => void;
|
|
18
|
+
|
|
19
|
+
disableFocus?: boolean;
|
|
20
|
+
blockFocus?: boolean;
|
|
21
|
+
} & Partial<ParentFocus>;
|
|
23
22
|
|
|
24
23
|
export class Touchable extends React.Component<Props> {
|
|
25
24
|
onPress(focusableRef: FocusManager.TouchableRef): void {
|
|
@@ -87,7 +87,6 @@ function FocusableListComponent<ItemT>(props: Props<ItemT>, ref) {
|
|
|
87
87
|
initialFocusDirection = "down",
|
|
88
88
|
data = [],
|
|
89
89
|
// eslint-disable-next-line unused-imports/no-unused-vars
|
|
90
|
-
onAllComponentsLoaded, // TODO: re-implement it
|
|
91
90
|
omitPropsPropagation = [],
|
|
92
91
|
} = props;
|
|
93
92
|
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BorderContainerView,
|
|
3
|
+
getBorderPadding, // Export for testing (using a double underscore prefix is a common convention)
|
|
4
|
+
} from "../index";
|
|
5
|
+
import * as React from "react";
|
|
6
|
+
import { render } from "@testing-library/react-native";
|
|
7
|
+
import { toNumberWithDefaultZero } from "@applicaster/zapp-react-native-utils/numberUtils";
|
|
8
|
+
import { View } from "react-native";
|
|
9
|
+
|
|
10
|
+
jest.mock("@applicaster/zapp-react-native-utils/numberUtils", () => ({
|
|
11
|
+
toNumberWithDefaultZero: jest.fn((value) => Number(value) || 0),
|
|
12
|
+
}));
|
|
13
|
+
|
|
14
|
+
describe("BorderContainerView", () => {
|
|
15
|
+
describe("getBorderPadding", () => {
|
|
16
|
+
it("returns 0 for inside", () => {
|
|
17
|
+
expect(getBorderPadding("inside", 10)).toBe(0);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it("returns borderWidth / 2 for center", () => {
|
|
21
|
+
expect(getBorderPadding("center", 10)).toBe(5);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("returns borderWidth for outside", () => {
|
|
25
|
+
expect(getBorderPadding("outside", 10)).toBe(10);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("returns borderWidth for invalid position", () => {
|
|
29
|
+
// @ts-ignore
|
|
30
|
+
expect(getBorderPadding("other_value", 10)).toBe(10);
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("Border component renders null if no borderPosition", () => {
|
|
35
|
+
const style = { borderWidth: 5, borderRadius: 10, borderColor: "red" };
|
|
36
|
+
|
|
37
|
+
const padding = {
|
|
38
|
+
paddingTop: 2,
|
|
39
|
+
paddingRight: 3,
|
|
40
|
+
paddingBottom: 4,
|
|
41
|
+
paddingLeft: 5,
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const borderPosition = null;
|
|
45
|
+
|
|
46
|
+
const { queryByTestId } = render(
|
|
47
|
+
<BorderContainerView
|
|
48
|
+
style={style}
|
|
49
|
+
testID="border-container"
|
|
50
|
+
borderPosition={borderPosition}
|
|
51
|
+
borderPaddingTop={toNumberWithDefaultZero(padding.paddingTop)}
|
|
52
|
+
borderPaddingRight={toNumberWithDefaultZero(padding.paddingRight)}
|
|
53
|
+
borderPaddingBottom={toNumberWithDefaultZero(padding.paddingBottom)}
|
|
54
|
+
borderPaddingLeft={toNumberWithDefaultZero(padding.paddingLeft)}
|
|
55
|
+
>
|
|
56
|
+
<View testID="child" />
|
|
57
|
+
</BorderContainerView>
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
const children = queryByTestId("border-container").children;
|
|
61
|
+
|
|
62
|
+
expect(children[1].props.testID).toBe("child");
|
|
63
|
+
|
|
64
|
+
expect(children[0].children.length).toBe(0);
|
|
65
|
+
});
|
|
66
|
+
});
|
|
@@ -28,7 +28,10 @@ const styles = StyleSheet.create({
|
|
|
28
28
|
},
|
|
29
29
|
});
|
|
30
30
|
|
|
31
|
-
const getBorderPadding = (
|
|
31
|
+
export const getBorderPadding = (
|
|
32
|
+
borderPosition: BorderPosition,
|
|
33
|
+
borderWidth: number
|
|
34
|
+
) => {
|
|
32
35
|
switch (borderPosition) {
|
|
33
36
|
case "inside":
|
|
34
37
|
return 0;
|
|
@@ -38,22 +38,16 @@ function Image({
|
|
|
38
38
|
|
|
39
39
|
const shouldRenderImg = source && !error;
|
|
40
40
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
const defaultPlaceHolderImage = React.useMemo(
|
|
45
|
-
() => ({ uri: placeholderImage || "" }),
|
|
46
|
-
[source, error]
|
|
47
|
-
);
|
|
41
|
+
const _source = shouldRenderImg
|
|
42
|
+
? withDimensions(source)
|
|
43
|
+
: { uri: placeholderImage };
|
|
48
44
|
|
|
49
45
|
return (
|
|
50
|
-
// @ts-ignore
|
|
51
46
|
<MemoizedImage
|
|
52
47
|
style={style as ImageStyle}
|
|
53
|
-
onError={() => setErrorState(true)}
|
|
54
|
-
source
|
|
55
|
-
|
|
56
|
-
}
|
|
48
|
+
onError={React.useCallback(() => setErrorState(true), [])}
|
|
49
|
+
// as we have defaults as "" for placeholder image, we need to pass undefined to source to not throw warnings
|
|
50
|
+
source={_source?.uri ? _source : undefined}
|
|
57
51
|
{...R.omit(["source"], otherProps)}
|
|
58
52
|
/>
|
|
59
53
|
);
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { ImageBorderContainer } from "../index";
|
|
2
|
+
import { getImageContainerMarginStyles } from "@applicaster/zapp-react-native-utils/cellUtils";
|
|
3
|
+
|
|
4
|
+
const mockValue = (key) => {
|
|
5
|
+
const values = {
|
|
6
|
+
cell_padding_top: 1,
|
|
7
|
+
cell_padding_left: 2,
|
|
8
|
+
cell_padding_right: 3,
|
|
9
|
+
cell_padding_bottom: 4,
|
|
10
|
+
image_border_size: 5,
|
|
11
|
+
image_focused_border_color: "red",
|
|
12
|
+
image_selected_border_color: "blue",
|
|
13
|
+
image_focused_selected_border_color: "green",
|
|
14
|
+
image_border_color: "black",
|
|
15
|
+
image_corner_radius: 10,
|
|
16
|
+
image_border_position: "outside",
|
|
17
|
+
image_border_padding_top: 6,
|
|
18
|
+
image_border_padding_right: 7,
|
|
19
|
+
image_border_padding_bottom: 8,
|
|
20
|
+
image_border_padding_left: 9,
|
|
21
|
+
image_margin_top: 11,
|
|
22
|
+
image_margin_left: 12,
|
|
23
|
+
image_margin_right: 13,
|
|
24
|
+
image_margin_bottom: 14,
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
return values[key] || 0;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
describe("ImageBorderContainer", () => {
|
|
31
|
+
it("calculates style properties correctly", () => {
|
|
32
|
+
const marginStyles = getImageContainerMarginStyles({ value: mockValue });
|
|
33
|
+
|
|
34
|
+
expect(marginStyles.marginTop).toBe(11);
|
|
35
|
+
expect(marginStyles.marginLeft).toBe(12);
|
|
36
|
+
expect(marginStyles.marginRight).toBe(13);
|
|
37
|
+
expect(marginStyles.marginBottom).toBe(14);
|
|
38
|
+
|
|
39
|
+
const props = {
|
|
40
|
+
value: mockValue,
|
|
41
|
+
state: "default",
|
|
42
|
+
imageStyles: {},
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const result = ImageBorderContainer(props);
|
|
46
|
+
|
|
47
|
+
expect(result.style.marginTop).toBe(12); // 11 + 1
|
|
48
|
+
expect(result.style.marginLeft).toBe(14); // 12 + 2
|
|
49
|
+
expect(result.style.marginRight).toBe(16); // 13 + 3
|
|
50
|
+
expect(result.style.marginBottom).toBe(18); // 14 + 4
|
|
51
|
+
expect(result.style.borderWidth).toBe(5);
|
|
52
|
+
expect(result.style.borderColor).toBe("black");
|
|
53
|
+
expect(result.style.borderRadius).toBe(10);
|
|
54
|
+
expect(result.additionalProps.borderPosition).toBe("outside");
|
|
55
|
+
expect(result.additionalProps.borderPaddingTop).toBe(6);
|
|
56
|
+
expect(result.additionalProps.borderPaddingRight).toBe(7);
|
|
57
|
+
expect(result.additionalProps.borderPaddingBottom).toBe(8);
|
|
58
|
+
expect(result.additionalProps.borderPaddingLeft).toBe(9);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("handles focused state correctly", () => {
|
|
62
|
+
const props = {
|
|
63
|
+
value: mockValue,
|
|
64
|
+
state: "focused",
|
|
65
|
+
imageStyles: {},
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const result = ImageBorderContainer(props);
|
|
69
|
+
expect(result.style.borderColor).toBe("red");
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("handles selected state correctly", () => {
|
|
73
|
+
const props = {
|
|
74
|
+
value: mockValue,
|
|
75
|
+
state: "selected",
|
|
76
|
+
imageStyles: {},
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const result = ImageBorderContainer(props);
|
|
80
|
+
expect(result.style.borderColor).toBe("blue");
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it("handles focused and selected state correctly", () => {
|
|
84
|
+
const props = {
|
|
85
|
+
value: mockValue,
|
|
86
|
+
state: "focused_selected",
|
|
87
|
+
imageStyles: {},
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const result = ImageBorderContainer(props);
|
|
91
|
+
expect(result.style.borderColor).toBe("green");
|
|
92
|
+
});
|
|
93
|
+
});
|
|
@@ -17,7 +17,7 @@ const SECONDARY_IMAGE_PREFIX = "secondary_image";
|
|
|
17
17
|
|
|
18
18
|
type ImageSizing = typeof IMAGE_SIZING_FIT | typeof IMAGE_SIZING_FILL;
|
|
19
19
|
|
|
20
|
-
type ImagePosition = typeof IMAGE_POSITION[keyof typeof IMAGE_POSITION];
|
|
20
|
+
type ImagePosition = (typeof IMAGE_POSITION)[keyof typeof IMAGE_POSITION];
|
|
21
21
|
|
|
22
22
|
export type DisplayMode =
|
|
23
23
|
| typeof DISPLAY_MODE_FIXED
|
|
@@ -35,7 +35,6 @@ function retrieveData(entry, func, args) {
|
|
|
35
35
|
* @param {?[Object]} element.elements Optional array of nested elements to render within the current node
|
|
36
36
|
* @returns {Object} inflated configuration, ready to be rendered by the master cell's element mapper
|
|
37
37
|
*/
|
|
38
|
-
|
|
39
38
|
export function configInflater(
|
|
40
39
|
entry: any,
|
|
41
40
|
{
|
|
@@ -65,7 +64,7 @@ export function configInflater(
|
|
|
65
64
|
|
|
66
65
|
if (Array.isArray(elements)) {
|
|
67
66
|
adjustedElements = elements.map((element) =>
|
|
68
|
-
configInflater(entry, element
|
|
67
|
+
configInflater(entry, element)
|
|
69
68
|
);
|
|
70
69
|
}
|
|
71
70
|
|
|
@@ -77,6 +76,14 @@ export function configInflater(
|
|
|
77
76
|
};
|
|
78
77
|
}
|
|
79
78
|
|
|
79
|
+
/**
|
|
80
|
+
* Memoized version of configInflater to cache results based on entry and masterCellConfig
|
|
81
|
+
*/
|
|
82
|
+
const memoizedConfigInflater = R.memoizeWith(
|
|
83
|
+
(entry, config) => `${entry.id}-${JSON.stringify(config)}`,
|
|
84
|
+
configInflater
|
|
85
|
+
);
|
|
86
|
+
|
|
80
87
|
/**
|
|
81
88
|
* Return the proper view tree according to entry's content type and UI state.
|
|
82
89
|
* Falls back to "default" content type or state.
|
|
@@ -97,7 +104,7 @@ function resolveElementsNode(entry, state, elements) {
|
|
|
97
104
|
export function defaultDataAdapter(elements) {
|
|
98
105
|
return function elementsBuilder({ entry, state = "default" }) {
|
|
99
106
|
return resolveElementsNode(entry, state, elements).map((element) =>
|
|
100
|
-
|
|
107
|
+
memoizedConfigInflater(entry, element)
|
|
101
108
|
);
|
|
102
109
|
};
|
|
103
110
|
}
|
|
@@ -8,7 +8,7 @@ import { MasterCellAsyncRenderManager } from "./MasterCellAsyncRenderManager";
|
|
|
8
8
|
const layoutMeasure = (viewRef, onMeasure) => {
|
|
9
9
|
viewRef?.measureLayout?.(
|
|
10
10
|
findNodeHandle(viewRef),
|
|
11
|
-
(
|
|
11
|
+
(_x, _y, width, height) => {
|
|
12
12
|
onMeasure({ width, height });
|
|
13
13
|
},
|
|
14
14
|
noop
|
|
@@ -16,7 +16,7 @@ const layoutMeasure = (viewRef, onMeasure) => {
|
|
|
16
16
|
};
|
|
17
17
|
|
|
18
18
|
const regularMeasure = (viewRef, onMeasure) => {
|
|
19
|
-
viewRef?.measure?.((
|
|
19
|
+
viewRef?.measure?.((_x, _y, width, height) => {
|
|
20
20
|
onMeasure({ width, height });
|
|
21
21
|
});
|
|
22
22
|
};
|
|
@@ -78,13 +78,17 @@ export function resolveColor(entry, style) {
|
|
|
78
78
|
return style;
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
-
//
|
|
82
|
-
|
|
83
|
-
const
|
|
81
|
+
// Iterate through style object keys and resolve any color properties
|
|
82
|
+
// non-color properties are passed through unchanged
|
|
83
|
+
const resolvedStyle = Object.keys(style).reduce((acc, key) => {
|
|
84
|
+
if (key.includes("color") || key.includes("Color")) {
|
|
85
|
+
return { ...acc, ...resolveColorForProp(entry, acc, key) };
|
|
86
|
+
}
|
|
84
87
|
|
|
85
|
-
|
|
86
|
-
return { ...style, ...resolveColorForProp(entry, acc, value) };
|
|
88
|
+
return acc;
|
|
87
89
|
}, style);
|
|
90
|
+
|
|
91
|
+
return resolvedStyle;
|
|
88
92
|
}
|
|
89
93
|
|
|
90
94
|
export function isVideoPreviewEnabled({
|
|
@@ -103,7 +107,7 @@ export const useFilterChildren = <
|
|
|
103
107
|
item: any;
|
|
104
108
|
pluginIdentifier: string;
|
|
105
109
|
};
|
|
106
|
-
}
|
|
110
|
+
},
|
|
107
111
|
>(
|
|
108
112
|
children: T[]
|
|
109
113
|
): T[] => {
|
|
@@ -339,13 +343,23 @@ export const useCellState = ({
|
|
|
339
343
|
return getCellState({ focused, selected: _isSelected });
|
|
340
344
|
};
|
|
341
345
|
|
|
346
|
+
const cache = new Map<string, any>();
|
|
347
|
+
|
|
342
348
|
export const hasFocusableInsideBuilder = (elementsBuilder) => (item) => {
|
|
343
349
|
const elements = elementsBuilder({ entry: item });
|
|
344
350
|
|
|
345
|
-
|
|
351
|
+
if (cache.has(item.id)) {
|
|
352
|
+
return cache.get(item.id);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
const result = R.anyPass([
|
|
346
356
|
hasElementsSpecificViewType("ButtonContainerView"),
|
|
347
357
|
hasElementsSpecificViewType("FocusableView"),
|
|
348
358
|
])(elements);
|
|
359
|
+
|
|
360
|
+
cache.set(item.id, result);
|
|
361
|
+
|
|
362
|
+
return result;
|
|
349
363
|
};
|
|
350
364
|
|
|
351
365
|
export function getEntryState(state, selected) {
|