@applicaster/zapp-react-native-ui-components 14.0.0-alpha.2482261241 → 14.0.0-alpha.2802628909
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/AnimatedInOut/index.tsx +69 -26
- package/Components/Cell/Cell.tsx +91 -64
- package/Components/Cell/CellWithFocusable.tsx +3 -0
- package/Components/Cell/FocusableWrapper.tsx +44 -0
- package/Components/Cell/TvOSCellComponent.tsx +80 -14
- package/Components/FeedLoader/FeedLoader.tsx +1 -1
- package/Components/Focusable/Focusable.tsx +10 -3
- package/Components/Focusable/FocusableTvOS.tsx +2 -2
- package/Components/Focusable/Touchable.tsx +5 -3
- package/Components/Focusable/index.android.tsx +8 -4
- package/Components/FocusableGroup/FocusableTvOS.tsx +1 -1
- package/Components/FocusableList/FocusableItem.tsx +4 -3
- package/Components/FocusableList/FocusableListItemWrapper.tsx +2 -1
- package/Components/FocusableList/hooks/useCellState.android.ts +13 -3
- package/Components/FocusableList/index.tsx +16 -9
- package/Components/GeneralContentScreen/utils/useCurationAPI.ts +9 -11
- package/Components/HandlePlayable/HandlePlayable.tsx +14 -65
- package/Components/HandlePlayable/const.ts +3 -0
- package/Components/HandlePlayable/utils.ts +74 -0
- package/Components/MasterCell/DefaultComponents/FocusableView/index.tsx +0 -12
- package/Components/MasterCell/DefaultComponents/SecondaryImage/Image.tsx +65 -17
- package/Components/MasterCell/DefaultComponents/SecondaryImage/__tests__/Image.test.tsx +21 -3
- package/Components/MasterCell/DefaultComponents/SecondaryImage/__tests__/__snapshots__/Image.test.tsx.snap +6 -3
- package/Components/MasterCell/DefaultComponents/Text/index.tsx +26 -6
- package/Components/PlayerContainer/PlayerContainer.tsx +5 -17
- package/Components/PlayerImageBackground/index.tsx +3 -22
- package/Components/River/ComponentsMap/ComponentsMap.tsx +49 -42
- package/Components/River/ComponentsMap/ContextProviders/ComponentsMapHeightContext.ts +8 -0
- package/Components/River/ComponentsMap/ContextProviders/ComponentsMapRefContext.ts +8 -0
- package/Components/Screen/TV/hooks/useInitialFocus.ts +14 -4
- package/Components/Screen/__tests__/__snapshots__/Screen.test.tsx.snap +2 -0
- package/Components/Screen/__tests__/navigationHandler.test.ts +133 -22
- package/Components/Screen/index.tsx +22 -5
- package/Components/Screen/navigationHandler.ts +20 -2
- package/Components/ScreenRevealManager/__tests__/ScreenRevealManager.test.ts +2 -2
- package/Components/ScreenRevealManager/utils/index.ts +23 -0
- package/Components/ScreenRevealManager/withScreenRevealManager.tsx +54 -24
- package/Components/Tabs/TV/Tabs.android.tsx +1 -1
- package/Components/TextInputTv/__tests__/__snapshots__/TextInputTv.test.js.snap +13 -0
- package/Components/TextInputTv/index.tsx +11 -0
- package/Components/VideoLive/__tests__/__snapshots__/PlayerLiveImageComponent.test.tsx.snap +1 -0
- package/Components/VideoModal/ModalAnimation/ModalAnimationContext.tsx +117 -157
- package/Components/VideoModal/ModalAnimation/index.ts +2 -13
- package/Components/VideoModal/ModalAnimation/utils.ts +1 -327
- package/Components/VideoModal/PlayerDetails.tsx +5 -5
- package/Components/VideoModal/PlayerWrapper.tsx +14 -88
- package/Components/VideoModal/VideoModal.tsx +1 -5
- package/Components/VideoModal/__tests__/PlayerWrapper.test.tsx +1 -0
- package/Components/VideoModal/hooks/__tests__/useDelayedPlayerDetails.test.ts +9 -1
- package/Components/VideoModal/hooks/useDelayedPlayerDetails.ts +40 -15
- package/Components/VideoModal/hooks/useModalSize.ts +10 -5
- package/Components/VideoModal/hooks/utils/__tests__/showDetails.test.ts +2 -2
- package/Components/VideoModal/hooks/utils/index.ts +4 -0
- package/Components/VideoModal/playerWrapperStyle.ts +70 -0
- package/Components/VideoModal/playerWrapperUtils.ts +91 -0
- package/Contexts/CellFocusedStateContext/index.tsx +27 -0
- package/Decorators/RiverFeedLoader/utils/getDatasourceUrl.ts +6 -10
- package/events/index.ts +2 -0
- package/package.json +5 -5
- package/Components/VideoModal/ModalAnimation/AnimatedPlayerModalWrapper.tsx +0 -60
- package/Components/VideoModal/ModalAnimation/AnimatedScrollModal.tsx +0 -415
- package/Components/VideoModal/ModalAnimation/AnimatedVideoPlayerComponent.tsx +0 -176
- package/Components/VideoModal/ModalAnimation/AnimationComponent.tsx +0 -500
- package/Components/VideoModal/ModalAnimation/__tests__/getMoveUpValue.test.ts +0 -108
|
@@ -6,10 +6,6 @@ import { noop } from "@applicaster/zapp-react-native-utils/functionUtils";
|
|
|
6
6
|
|
|
7
7
|
type AnimatedInterpolatedStyle = any;
|
|
8
8
|
|
|
9
|
-
// type AnimatedInterpolatedStyle =
|
|
10
|
-
// | Animated.AnimatedInterpolation
|
|
11
|
-
// | [{ [Key: string]: Animated.AnimatedInterpolation }];
|
|
12
|
-
|
|
13
9
|
type AnimationConfig = {
|
|
14
10
|
duration: number;
|
|
15
11
|
easing: EasingFunction;
|
|
@@ -45,32 +41,57 @@ export function AnimatedInOut({
|
|
|
45
41
|
children,
|
|
46
42
|
}: Props) {
|
|
47
43
|
const [animatedValue] = React.useState(new Animated.Value(visible ? 1 : 0));
|
|
48
|
-
const
|
|
44
|
+
const animationRef = React.useRef<Animated.CompositeAnimation | null>(null);
|
|
45
|
+
const delayTimerRef = React.useRef<NodeJS.Timeout | null>(null);
|
|
49
46
|
|
|
50
47
|
const previousVisible = usePrevious(toBooleanWithDefaultFalse(visible));
|
|
51
48
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
49
|
+
const startAnimation = React.useCallback(
|
|
50
|
+
(toValue: number, config: AnimationConfig) => {
|
|
51
|
+
if (delayTimerRef.current) {
|
|
52
|
+
clearTimeout(delayTimerRef.current);
|
|
53
|
+
delayTimerRef.current = null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (animationRef.current) {
|
|
57
|
+
animationRef.current.stop();
|
|
58
|
+
animationRef.current = null;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const { duration, easing, delay = 0, onAnimationEnd = noop } = config;
|
|
62
|
+
|
|
63
|
+
const runAnimation = () => {
|
|
64
|
+
animationRef.current = Animated.timing(animatedValue, {
|
|
65
|
+
duration,
|
|
66
|
+
toValue,
|
|
67
|
+
easing,
|
|
68
|
+
useNativeDriver: true,
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
animationRef.current.start(({ finished }) => {
|
|
72
|
+
if (finished) {
|
|
73
|
+
animationRef.current = null;
|
|
74
|
+
onAnimationEnd();
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
if (delay > 0) {
|
|
80
|
+
delayTimerRef.current = setTimeout(runAnimation, delay);
|
|
81
|
+
} else {
|
|
82
|
+
runAnimation();
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
[animatedValue]
|
|
86
|
+
);
|
|
58
87
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
easing,
|
|
63
|
-
delay,
|
|
64
|
-
useNativeDriver: true,
|
|
65
|
-
}).start(() => {
|
|
66
|
-
setAnimating(undefined);
|
|
67
|
-
onAnimationEnd();
|
|
68
|
-
});
|
|
88
|
+
React.useEffect(() => {
|
|
89
|
+
if (previousVisible === undefined) {
|
|
90
|
+
animatedValue.setValue(visible ? 1 : 0);
|
|
69
91
|
|
|
70
|
-
|
|
71
|
-
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
72
94
|
|
|
73
|
-
React.useEffect(() => {
|
|
74
95
|
if (!previousVisible && visible) {
|
|
75
96
|
startAnimation(1.0, getAnimation(animationConfig, "in"));
|
|
76
97
|
}
|
|
@@ -78,7 +99,29 @@ export function AnimatedInOut({
|
|
|
78
99
|
if (previousVisible && !visible) {
|
|
79
100
|
startAnimation(0.0, getAnimation(animationConfig, "out"));
|
|
80
101
|
}
|
|
81
|
-
}, [
|
|
102
|
+
}, [
|
|
103
|
+
visible,
|
|
104
|
+
previousVisible,
|
|
105
|
+
animatedValue,
|
|
106
|
+
startAnimation,
|
|
107
|
+
animationConfig,
|
|
108
|
+
]);
|
|
109
|
+
|
|
110
|
+
React.useEffect(() => {
|
|
111
|
+
return () => {
|
|
112
|
+
if (delayTimerRef.current) {
|
|
113
|
+
clearTimeout(delayTimerRef.current);
|
|
114
|
+
delayTimerRef.current = null;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (animationRef.current) {
|
|
118
|
+
animationRef.current.stop();
|
|
119
|
+
animationRef.current = null;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
animatedValue.stopAnimation();
|
|
123
|
+
};
|
|
124
|
+
}, [animatedValue]);
|
|
82
125
|
|
|
83
126
|
const styles = visible
|
|
84
127
|
? getAnimation(animationConfig, "in").styles
|
|
@@ -86,7 +129,7 @@ export function AnimatedInOut({
|
|
|
86
129
|
|
|
87
130
|
return (
|
|
88
131
|
<Animated.View
|
|
89
|
-
renderToHardwareTextureAndroid={
|
|
132
|
+
renderToHardwareTextureAndroid={!!animationRef.current}
|
|
90
133
|
style={[styles(animatedValue), staticStyles]}
|
|
91
134
|
>
|
|
92
135
|
{children}
|
package/Components/Cell/Cell.tsx
CHANGED
|
@@ -8,6 +8,7 @@ import { getItemType } from "@applicaster/zapp-react-native-utils/navigationUtil
|
|
|
8
8
|
import { SCREEN_TYPES } from "@applicaster/zapp-react-native-utils/navigationUtils/itemTypes";
|
|
9
9
|
import { sendSelectCellEvent } from "@applicaster/zapp-react-native-utils/analyticsUtils";
|
|
10
10
|
import { noop } from "@applicaster/zapp-react-native-utils/functionUtils";
|
|
11
|
+
import { CellFocusedStateContextProvider } from "@applicaster/zapp-react-native-ui-components/Contexts/CellFocusedStateContext";
|
|
11
12
|
|
|
12
13
|
import { CellWithFocusable } from "./CellWithFocusable";
|
|
13
14
|
import { BaseFocusable } from "../BaseFocusable";
|
|
@@ -15,6 +16,7 @@ import { AccessibilityManager } from "@applicaster/zapp-react-native-utils/appUt
|
|
|
15
16
|
import { styles } from "./styles";
|
|
16
17
|
|
|
17
18
|
type Props = {
|
|
19
|
+
dataLength: number;
|
|
18
20
|
item: ZappEntry;
|
|
19
21
|
index: number;
|
|
20
22
|
shouldScrollHorizontally: (arg1: [any]) => boolean | null | undefined;
|
|
@@ -67,9 +69,12 @@ type Props = {
|
|
|
67
69
|
|
|
68
70
|
type State = {
|
|
69
71
|
hasFocusableInside: boolean;
|
|
72
|
+
cellFocused: boolean;
|
|
70
73
|
};
|
|
71
74
|
|
|
72
75
|
export class CellComponent extends React.Component<Props, State> {
|
|
76
|
+
accessibilityManager: AccessibilityManager;
|
|
77
|
+
|
|
73
78
|
constructor(props) {
|
|
74
79
|
super(props);
|
|
75
80
|
this.onPress = this.onPress.bind(this);
|
|
@@ -79,10 +84,14 @@ export class CellComponent extends React.Component<Props, State> {
|
|
|
79
84
|
this.hasReceivedFocus = this.hasReceivedFocus.bind(this);
|
|
80
85
|
this.scrollVertically = this.scrollVertically.bind(this);
|
|
81
86
|
this.scrollToIndex = this.scrollToIndex.bind(this);
|
|
87
|
+
this.handleAccessibilityFocus = this.handleAccessibilityFocus.bind(this);
|
|
82
88
|
|
|
83
89
|
this.state = {
|
|
84
90
|
hasFocusableInside: props.CellRenderer.hasFocusableInside?.(props.item),
|
|
91
|
+
cellFocused: false,
|
|
85
92
|
};
|
|
93
|
+
|
|
94
|
+
this.accessibilityManager = AccessibilityManager.getInstance();
|
|
86
95
|
}
|
|
87
96
|
|
|
88
97
|
setScreenLayout(componentAnchorPointY, screenLayout) {
|
|
@@ -130,6 +139,8 @@ export class CellComponent extends React.Component<Props, State> {
|
|
|
130
139
|
} = this.props;
|
|
131
140
|
|
|
132
141
|
if (isFocusable) {
|
|
142
|
+
this.setState({ cellFocused: true });
|
|
143
|
+
|
|
133
144
|
if (
|
|
134
145
|
shouldUpdate &&
|
|
135
146
|
shouldScrollVertically?.(mouse, focusable, id, title)
|
|
@@ -139,7 +150,9 @@ export class CellComponent extends React.Component<Props, State> {
|
|
|
139
150
|
}
|
|
140
151
|
}
|
|
141
152
|
|
|
142
|
-
onBlur() {
|
|
153
|
+
onBlur() {
|
|
154
|
+
this.setState({ cellFocused: false });
|
|
155
|
+
}
|
|
143
156
|
|
|
144
157
|
willReceiveFocus() {}
|
|
145
158
|
|
|
@@ -183,6 +196,25 @@ export class CellComponent extends React.Component<Props, State> {
|
|
|
183
196
|
return !isFocusable ? false : focused || focusableFocused;
|
|
184
197
|
}
|
|
185
198
|
|
|
199
|
+
handleAccessibilityFocus(index, dataLength) {
|
|
200
|
+
// For loop scrolling, calculate the correct logical index
|
|
201
|
+
const logicalIndex = dataLength ? index % dataLength : index;
|
|
202
|
+
|
|
203
|
+
const positionLabel = dataLength
|
|
204
|
+
? `item ${logicalIndex + 1} of ${dataLength}`
|
|
205
|
+
: "";
|
|
206
|
+
|
|
207
|
+
if (this.state.hasFocusableInside) {
|
|
208
|
+
this.accessibilityManager.readText({
|
|
209
|
+
text: " ",
|
|
210
|
+
});
|
|
211
|
+
} else {
|
|
212
|
+
this.accessibilityManager.readText({
|
|
213
|
+
text: `${positionLabel}`,
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
186
218
|
componentDidUpdate(prevProps: Readonly<Props>) {
|
|
187
219
|
if (prevProps.item !== this.props.item) {
|
|
188
220
|
this.setState({
|
|
@@ -191,6 +223,8 @@ export class CellComponent extends React.Component<Props, State> {
|
|
|
191
223
|
),
|
|
192
224
|
});
|
|
193
225
|
}
|
|
226
|
+
|
|
227
|
+
this.handleAccessibilityFocus(this.props.index, this.props.dataLength);
|
|
194
228
|
}
|
|
195
229
|
|
|
196
230
|
render() {
|
|
@@ -212,7 +246,6 @@ export class CellComponent extends React.Component<Props, State> {
|
|
|
212
246
|
} = this.props;
|
|
213
247
|
|
|
214
248
|
const { id } = item;
|
|
215
|
-
|
|
216
249
|
const focusableId = join("-", [component?.id, id, index]);
|
|
217
250
|
|
|
218
251
|
const handleFocus = (focusable, mouse) => {
|
|
@@ -223,73 +256,67 @@ export class CellComponent extends React.Component<Props, State> {
|
|
|
223
256
|
|
|
224
257
|
if (this.state.hasFocusableInside) {
|
|
225
258
|
return (
|
|
226
|
-
<
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
259
|
+
<CellFocusedStateContextProvider cellFocused={this.state.cellFocused}>
|
|
260
|
+
<CellWithFocusable
|
|
261
|
+
CellRenderer={CellRenderer}
|
|
262
|
+
item={item}
|
|
263
|
+
id={focusableId}
|
|
264
|
+
groupId={groupId || component?.id}
|
|
265
|
+
onFocus={handleFocus}
|
|
266
|
+
onBlur={onBlur || this.onBlur}
|
|
267
|
+
index={index}
|
|
268
|
+
scrollTo={this.scrollToIndex()}
|
|
269
|
+
isFocusable={isFocusable}
|
|
270
|
+
skipFocusManagerRegistration={skipFocusManagerRegistration}
|
|
271
|
+
behavior={behavior}
|
|
272
|
+
/>
|
|
273
|
+
</CellFocusedStateContextProvider>
|
|
238
274
|
);
|
|
239
275
|
}
|
|
240
276
|
|
|
241
277
|
return (
|
|
242
|
-
<
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
<Focusable
|
|
248
|
-
id={focusableId}
|
|
249
|
-
groupId={groupId || component?.id}
|
|
250
|
-
onFocus={handleFocus}
|
|
251
|
-
onBlur={onBlur || this.onBlur}
|
|
252
|
-
onPress={this.onPress}
|
|
253
|
-
willReceiveFocus={willReceiveFocus || this.willReceiveFocus}
|
|
254
|
-
hasReceivedFocus={hasReceivedFocus || this.hasReceivedFocus}
|
|
255
|
-
preferredFocus={preferredFocus}
|
|
256
|
-
offsetUpdater={offsetUpdater}
|
|
257
|
-
style={styles.baseCell}
|
|
258
|
-
isFocusable={isFocusable}
|
|
259
|
-
skipFocusManagerRegistration={skipFocusManagerRegistration}
|
|
278
|
+
<CellFocusedStateContextProvider cellFocused={this.state.cellFocused}>
|
|
279
|
+
<View
|
|
280
|
+
testID={`${component?.id}-${id}`}
|
|
281
|
+
accessible={false}
|
|
282
|
+
style={styles.touchableCell}
|
|
260
283
|
>
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
284
|
+
<Focusable
|
|
285
|
+
id={focusableId}
|
|
286
|
+
groupId={groupId || component?.id}
|
|
287
|
+
onFocus={handleFocus}
|
|
288
|
+
onBlur={onBlur || this.onBlur}
|
|
289
|
+
onPress={this.onPress}
|
|
290
|
+
willReceiveFocus={willReceiveFocus || this.willReceiveFocus}
|
|
291
|
+
hasReceivedFocus={hasReceivedFocus || this.hasReceivedFocus}
|
|
292
|
+
preferredFocus={preferredFocus}
|
|
293
|
+
offsetUpdater={offsetUpdater}
|
|
294
|
+
style={styles.baseCell}
|
|
295
|
+
isFocusable={isFocusable}
|
|
296
|
+
skipFocusManagerRegistration={skipFocusManagerRegistration}
|
|
297
|
+
{...this.accessibilityManager.getButtonAccessibilityProps(
|
|
298
|
+
item?.extensions?.accessibility?.label || item?.title
|
|
299
|
+
)}
|
|
300
|
+
>
|
|
301
|
+
{(focused, event) => {
|
|
302
|
+
const isFocused = this.isCellFocused(focused);
|
|
303
|
+
|
|
304
|
+
return (
|
|
305
|
+
<FocusableCell
|
|
306
|
+
{...{
|
|
307
|
+
index,
|
|
308
|
+
CellRenderer,
|
|
309
|
+
item,
|
|
310
|
+
focused: isFocused,
|
|
311
|
+
scrollTo: this.scrollToIndex(event),
|
|
312
|
+
behavior,
|
|
313
|
+
}}
|
|
314
|
+
/>
|
|
315
|
+
);
|
|
316
|
+
}}
|
|
317
|
+
</Focusable>
|
|
318
|
+
</View>
|
|
319
|
+
</CellFocusedStateContextProvider>
|
|
293
320
|
);
|
|
294
321
|
}
|
|
295
322
|
}
|
|
@@ -14,6 +14,7 @@ type Props = {
|
|
|
14
14
|
id: string;
|
|
15
15
|
groupId: string;
|
|
16
16
|
onFocus: Function;
|
|
17
|
+
onBlur?: Function;
|
|
17
18
|
index: number;
|
|
18
19
|
scrollTo: Function;
|
|
19
20
|
preferredFocus?: boolean;
|
|
@@ -33,6 +34,7 @@ export function CellWithFocusable(props: Props) {
|
|
|
33
34
|
id,
|
|
34
35
|
groupId,
|
|
35
36
|
onFocus,
|
|
37
|
+
onBlur = noop,
|
|
36
38
|
scrollTo = noop,
|
|
37
39
|
preferredFocus,
|
|
38
40
|
skipFocusManagerRegistration,
|
|
@@ -78,6 +80,7 @@ export function CellWithFocusable(props: Props) {
|
|
|
78
80
|
const onGroupBlur = React.useCallback(() => {
|
|
79
81
|
if (!skipFocusManagerRegistration) {
|
|
80
82
|
setIsFocused(false);
|
|
83
|
+
onBlur?.();
|
|
81
84
|
}
|
|
82
85
|
}, [skipFocusManagerRegistration]);
|
|
83
86
|
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { Focusable } from "@applicaster/zapp-react-native-ui-components/Components/Focusable/FocusableTvOS";
|
|
3
|
+
import { noop } from "@applicaster/zapp-react-native-utils/functionUtils";
|
|
4
|
+
|
|
5
|
+
type Props = {
|
|
6
|
+
id: string;
|
|
7
|
+
groupId: string;
|
|
8
|
+
isParallaxDisabled: boolean;
|
|
9
|
+
applyWrapper: boolean;
|
|
10
|
+
children: (focused: boolean) => React.ReactNode;
|
|
11
|
+
onFocus: (arg1: any, index?: number) => void;
|
|
12
|
+
onBlur: Callback;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export const FocusableWrapper = ({
|
|
16
|
+
id,
|
|
17
|
+
groupId,
|
|
18
|
+
isParallaxDisabled,
|
|
19
|
+
children,
|
|
20
|
+
applyWrapper,
|
|
21
|
+
onFocus,
|
|
22
|
+
onBlur,
|
|
23
|
+
}: Props) => {
|
|
24
|
+
if (applyWrapper) {
|
|
25
|
+
return (
|
|
26
|
+
<Focusable
|
|
27
|
+
id={id}
|
|
28
|
+
groupId={groupId}
|
|
29
|
+
isParallaxDisabled={isParallaxDisabled}
|
|
30
|
+
onFocus={onFocus}
|
|
31
|
+
onBlur={onBlur}
|
|
32
|
+
willReceiveFocus={noop}
|
|
33
|
+
hasReceivedFocus={noop}
|
|
34
|
+
// @ts-ignore
|
|
35
|
+
offsetUpdater={noop}
|
|
36
|
+
isFocusable
|
|
37
|
+
>
|
|
38
|
+
{(focused) => children(focused)}
|
|
39
|
+
</Focusable>
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return <>{children(false)}</>;
|
|
44
|
+
};
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
2
|
import * as R from "ramda";
|
|
3
3
|
import { View, StyleSheet } from "react-native";
|
|
4
|
+
import { first, filter } from "rxjs/operators";
|
|
5
|
+
|
|
6
|
+
import { compose } from "@applicaster/zapp-react-native-utils/utils";
|
|
4
7
|
|
|
5
8
|
import { Focusable } from "@applicaster/zapp-react-native-ui-components/Components/Focusable/FocusableTvOS";
|
|
6
9
|
import { FocusableCell } from "@applicaster/zapp-react-native-ui-components/Components/FocusableCell";
|
|
@@ -9,7 +12,11 @@ import { SCREEN_TYPES } from "@applicaster/zapp-react-native-utils/navigationUti
|
|
|
9
12
|
import { focusManager } from "@applicaster/zapp-react-native-utils/appUtils/focusManager";
|
|
10
13
|
import { sendSelectCellEvent } from "@applicaster/zapp-react-native-utils/analyticsUtils";
|
|
11
14
|
import { noop } from "@applicaster/zapp-react-native-utils/functionUtils";
|
|
15
|
+
import { toBooleanWithDefaultTrue } from "@applicaster/zapp-react-native-utils/booleanUtils";
|
|
12
16
|
import { CellWithFocusable } from "./CellWithFocusable";
|
|
17
|
+
import { FocusableWrapper } from "./FocusableWrapper";
|
|
18
|
+
|
|
19
|
+
import { focusableButtonsRegistration$ } from "@applicaster/zapp-react-native-utils/appUtils/focusManagerAux/utils/utils.ios";
|
|
13
20
|
|
|
14
21
|
type Props = {
|
|
15
22
|
item: ZappEntry;
|
|
@@ -66,6 +73,8 @@ type Props = {
|
|
|
66
73
|
shouldUpdate: boolean;
|
|
67
74
|
behavior: Behavior;
|
|
68
75
|
componentsMapOffset: number;
|
|
76
|
+
applyFocusableWrapper: boolean;
|
|
77
|
+
hasFocusableInside: boolean;
|
|
69
78
|
};
|
|
70
79
|
|
|
71
80
|
type State = {
|
|
@@ -82,7 +91,7 @@ const baseCellStyles = {
|
|
|
82
91
|
flex: 1,
|
|
83
92
|
} as const;
|
|
84
93
|
|
|
85
|
-
|
|
94
|
+
class TvOSCell extends React.Component<Props, State> {
|
|
86
95
|
cell: any;
|
|
87
96
|
target: any;
|
|
88
97
|
layout: any;
|
|
@@ -239,6 +248,8 @@ export class TvOSCellComponent extends React.Component<Props, State> {
|
|
|
239
248
|
groupId,
|
|
240
249
|
isFocusable,
|
|
241
250
|
behavior,
|
|
251
|
+
applyFocusableWrapper,
|
|
252
|
+
hasFocusableInside,
|
|
242
253
|
} = this.props;
|
|
243
254
|
|
|
244
255
|
const { id } = item;
|
|
@@ -254,24 +265,33 @@ export class TvOSCellComponent extends React.Component<Props, State> {
|
|
|
254
265
|
this.onFocus(arg1, index);
|
|
255
266
|
};
|
|
256
267
|
|
|
257
|
-
const hasFocusableInside = CellRenderer.hasFocusableInside?.(item);
|
|
258
|
-
|
|
259
268
|
if (hasFocusableInside) {
|
|
260
269
|
return (
|
|
261
270
|
<View onLayout={this.onLayout}>
|
|
262
|
-
<
|
|
263
|
-
CellRenderer={CellRenderer}
|
|
264
|
-
item={item}
|
|
271
|
+
<FocusableWrapper
|
|
265
272
|
id={focusableId}
|
|
266
|
-
groupId={(groupId || component?.id)
|
|
273
|
+
groupId={String(groupId || component?.id)}
|
|
274
|
+
isParallaxDisabled={this.layout?.width > 1740}
|
|
267
275
|
onFocus={handleFocus}
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
276
|
+
onBlur={onBlur || this.onBlur}
|
|
277
|
+
applyWrapper={applyFocusableWrapper}
|
|
278
|
+
>
|
|
279
|
+
{(focused) => (
|
|
280
|
+
<CellWithFocusable
|
|
281
|
+
CellRenderer={CellRenderer}
|
|
282
|
+
item={item}
|
|
283
|
+
id={focusableId}
|
|
284
|
+
groupId={(groupId || component?.id).toString()}
|
|
285
|
+
onFocus={handleFocus}
|
|
286
|
+
index={index}
|
|
287
|
+
scrollTo={this.scrollTo}
|
|
288
|
+
preferredFocus={preferredFocus}
|
|
289
|
+
focused={focused || this.props.focused}
|
|
290
|
+
behavior={behavior}
|
|
291
|
+
isFocusable={isFocusable}
|
|
292
|
+
/>
|
|
293
|
+
)}
|
|
294
|
+
</FocusableWrapper>
|
|
275
295
|
</View>
|
|
276
296
|
);
|
|
277
297
|
}
|
|
@@ -309,3 +329,49 @@ export class TvOSCellComponent extends React.Component<Props, State> {
|
|
|
309
329
|
);
|
|
310
330
|
}
|
|
311
331
|
}
|
|
332
|
+
|
|
333
|
+
export function withFocusableWrapperHOC(Component) {
|
|
334
|
+
return function WrappedComponent(props) {
|
|
335
|
+
const [focusableViewIsRendered, setFocusableViewIsRendered] =
|
|
336
|
+
React.useState(false);
|
|
337
|
+
|
|
338
|
+
const { CellRenderer, item, groupId, component } = props;
|
|
339
|
+
|
|
340
|
+
const isFocusable = toBooleanWithDefaultTrue(props?.isFocusable);
|
|
341
|
+
|
|
342
|
+
const focusableGroupId = String(groupId || component?.id);
|
|
343
|
+
|
|
344
|
+
const hasFocusableInside = CellRenderer.hasFocusableInside?.(item);
|
|
345
|
+
|
|
346
|
+
React.useEffect(() => {
|
|
347
|
+
// start waiting any first registration of FocusableButton inside this focusableGroup
|
|
348
|
+
// after it we could get rid of applying focusable-wrapper
|
|
349
|
+
const subscription = focusableButtonsRegistration$(focusableGroupId)
|
|
350
|
+
.pipe(
|
|
351
|
+
filter(() => isFocusable),
|
|
352
|
+
first()
|
|
353
|
+
)
|
|
354
|
+
.subscribe(() => {
|
|
355
|
+
setFocusableViewIsRendered(true);
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
return () => {
|
|
359
|
+
subscription.unsubscribe();
|
|
360
|
+
};
|
|
361
|
+
}, [isFocusable, focusableGroupId]);
|
|
362
|
+
|
|
363
|
+
const applyFocusableWrapper = React.useMemo(() => {
|
|
364
|
+
return isFocusable && hasFocusableInside && !focusableViewIsRendered;
|
|
365
|
+
}, [isFocusable, hasFocusableInside, focusableViewIsRendered]);
|
|
366
|
+
|
|
367
|
+
return (
|
|
368
|
+
<Component
|
|
369
|
+
{...props}
|
|
370
|
+
applyFocusableWrapper={applyFocusableWrapper}
|
|
371
|
+
hasFocusableInside={hasFocusableInside}
|
|
372
|
+
/>
|
|
373
|
+
);
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
export const TvOSCellComponent = compose(withFocusableWrapperHOC)(TvOSCell);
|
|
@@ -8,7 +8,7 @@ type Props = {
|
|
|
8
8
|
typeof import("@applicaster/zapp-react-native-utils/reactHooks/feed").useLoadPipesDataDispatch
|
|
9
9
|
>;
|
|
10
10
|
feedUrl: string;
|
|
11
|
-
children: (feed: ZappFeed) => React.
|
|
11
|
+
children: (feed: ZappFeed) => React.ReactNode;
|
|
12
12
|
onFeedLoaded: (feed: ZappFeed) => {};
|
|
13
13
|
onError: (error: ZappPipesData["error"]) => {};
|
|
14
14
|
refreshing: boolean;
|
|
@@ -6,6 +6,7 @@ import { focusManager } from "@applicaster/zapp-react-native-utils/appUtils/focu
|
|
|
6
6
|
import { LONG_KEY_PRESS_TIMEOUT } from "@applicaster/quick-brick-core/const";
|
|
7
7
|
import { withFocusableContext } from "../../Contexts/FocusableGroupContext/withFocusableContext";
|
|
8
8
|
import { StyleSheet, ViewStyle } from "react-native";
|
|
9
|
+
import { AccessibilityManager } from "@applicaster/zapp-react-native-utils/appUtils/accessibilityManager";
|
|
9
10
|
|
|
10
11
|
type Props = {
|
|
11
12
|
initialFocus?: boolean;
|
|
@@ -20,7 +21,7 @@ type Props = {
|
|
|
20
21
|
onPressOut?: () => void;
|
|
21
22
|
onLongPress?: () => void;
|
|
22
23
|
handleFocus?: ({ mouse }: { mouse: boolean }) => void;
|
|
23
|
-
children: (boolean, string) => React.
|
|
24
|
+
children: (boolean, string) => React.ReactNode;
|
|
24
25
|
selected?: boolean;
|
|
25
26
|
style?: ViewStyle[] | ViewStyle;
|
|
26
27
|
};
|
|
@@ -29,6 +30,7 @@ class Focusable extends BaseFocusable<Props> {
|
|
|
29
30
|
isGroup: boolean;
|
|
30
31
|
mouse: boolean;
|
|
31
32
|
longPressTimeout = null;
|
|
33
|
+
accessibilityManager: AccessibilityManager;
|
|
32
34
|
|
|
33
35
|
constructor(props) {
|
|
34
36
|
super(props);
|
|
@@ -43,6 +45,8 @@ class Focusable extends BaseFocusable<Props> {
|
|
|
43
45
|
this.resetLongPressTimeout = this.resetLongPressTimeout.bind(this);
|
|
44
46
|
this.longPress = this.longPress.bind(this);
|
|
45
47
|
this.press = this.press.bind(this);
|
|
48
|
+
|
|
49
|
+
this.accessibilityManager = AccessibilityManager.getInstance();
|
|
46
50
|
}
|
|
47
51
|
|
|
48
52
|
/**
|
|
@@ -123,12 +127,15 @@ class Focusable extends BaseFocusable<Props> {
|
|
|
123
127
|
}
|
|
124
128
|
|
|
125
129
|
render() {
|
|
126
|
-
const { children, style
|
|
130
|
+
const { children, style } = this.props;
|
|
127
131
|
const { focused } = this.state;
|
|
128
132
|
|
|
129
133
|
const id = this.getId();
|
|
130
134
|
const focusableId = `focusable-${id}`;
|
|
131
135
|
|
|
136
|
+
const accessibilityProps =
|
|
137
|
+
this.accessibilityManager.getWebAccessibilityProps(this.props);
|
|
138
|
+
|
|
132
139
|
return (
|
|
133
140
|
<div
|
|
134
141
|
id={focusableId}
|
|
@@ -141,7 +148,7 @@ class Focusable extends BaseFocusable<Props> {
|
|
|
141
148
|
data-testid={focusableId}
|
|
142
149
|
focused-teststate={focused ? "focused" : "default"}
|
|
143
150
|
style={StyleSheet.flatten(style) as any as React.CSSProperties}
|
|
144
|
-
{...
|
|
151
|
+
{...accessibilityProps}
|
|
145
152
|
>
|
|
146
153
|
{children(focused, { mouse: this.mouse })}
|
|
147
154
|
</div>
|
|
@@ -19,7 +19,7 @@ type Props = {
|
|
|
19
19
|
onPress?: (nativeEvent: any) => void;
|
|
20
20
|
onFocus?: (nativeEvent: any) => void;
|
|
21
21
|
onBlur?: (nativeEvent: any) => void;
|
|
22
|
-
children: (focused?: boolean) => React.ReactNode;
|
|
22
|
+
children: ((focused?: boolean) => React.ReactNode) | React.ReactNode;
|
|
23
23
|
isParallaxDisabled: boolean;
|
|
24
24
|
preferredFocus?: boolean;
|
|
25
25
|
selected?: boolean;
|
|
@@ -204,7 +204,7 @@ export class Focusable extends BaseFocusable<Props> {
|
|
|
204
204
|
{...this.nextFocusableReactTags}
|
|
205
205
|
{...otherProps}
|
|
206
206
|
>
|
|
207
|
-
{
|
|
207
|
+
{typeof children === "function" ? children(focused) : children}
|
|
208
208
|
</FocusableItemNative>
|
|
209
209
|
);
|
|
210
210
|
}
|
|
@@ -9,7 +9,8 @@ type Props = {
|
|
|
9
9
|
onLongPress?: (ref: FocusManager.TouchableRef) => void;
|
|
10
10
|
onFocus?: (
|
|
11
11
|
ref: FocusManager.TouchableRef,
|
|
12
|
-
options: FocusManager.Android.CallbackOptions
|
|
12
|
+
options: FocusManager.Android.CallbackOptions,
|
|
13
|
+
context: Option<FocusManager.FocusContext>
|
|
13
14
|
) => void;
|
|
14
15
|
onBlur?: (
|
|
15
16
|
ref: FocusManager.TouchableRef,
|
|
@@ -39,9 +40,10 @@ export class Touchable extends React.Component<Props> {
|
|
|
39
40
|
|
|
40
41
|
onFocus(
|
|
41
42
|
focusableRef: FocusManager.TouchableRef,
|
|
42
|
-
options: FocusManager.Android.CallbackOptions
|
|
43
|
+
options: FocusManager.Android.CallbackOptions,
|
|
44
|
+
context: Option<FocusManager.FocusContext>
|
|
43
45
|
): void {
|
|
44
|
-
this.props?.onFocus?.(focusableRef, options);
|
|
46
|
+
this.props?.onFocus?.(focusableRef, options, context);
|
|
45
47
|
}
|
|
46
48
|
|
|
47
49
|
onBlur(
|