@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.
Files changed (64) hide show
  1. package/Components/AnimatedInOut/index.tsx +69 -26
  2. package/Components/Cell/Cell.tsx +91 -64
  3. package/Components/Cell/CellWithFocusable.tsx +3 -0
  4. package/Components/Cell/FocusableWrapper.tsx +44 -0
  5. package/Components/Cell/TvOSCellComponent.tsx +80 -14
  6. package/Components/FeedLoader/FeedLoader.tsx +1 -1
  7. package/Components/Focusable/Focusable.tsx +10 -3
  8. package/Components/Focusable/FocusableTvOS.tsx +2 -2
  9. package/Components/Focusable/Touchable.tsx +5 -3
  10. package/Components/Focusable/index.android.tsx +8 -4
  11. package/Components/FocusableGroup/FocusableTvOS.tsx +1 -1
  12. package/Components/FocusableList/FocusableItem.tsx +4 -3
  13. package/Components/FocusableList/FocusableListItemWrapper.tsx +2 -1
  14. package/Components/FocusableList/hooks/useCellState.android.ts +13 -3
  15. package/Components/FocusableList/index.tsx +16 -9
  16. package/Components/GeneralContentScreen/utils/useCurationAPI.ts +9 -11
  17. package/Components/HandlePlayable/HandlePlayable.tsx +14 -65
  18. package/Components/HandlePlayable/const.ts +3 -0
  19. package/Components/HandlePlayable/utils.ts +74 -0
  20. package/Components/MasterCell/DefaultComponents/FocusableView/index.tsx +0 -12
  21. package/Components/MasterCell/DefaultComponents/SecondaryImage/Image.tsx +65 -17
  22. package/Components/MasterCell/DefaultComponents/SecondaryImage/__tests__/Image.test.tsx +21 -3
  23. package/Components/MasterCell/DefaultComponents/SecondaryImage/__tests__/__snapshots__/Image.test.tsx.snap +6 -3
  24. package/Components/MasterCell/DefaultComponents/Text/index.tsx +26 -6
  25. package/Components/PlayerContainer/PlayerContainer.tsx +5 -17
  26. package/Components/PlayerImageBackground/index.tsx +3 -22
  27. package/Components/River/ComponentsMap/ComponentsMap.tsx +49 -42
  28. package/Components/River/ComponentsMap/ContextProviders/ComponentsMapHeightContext.ts +8 -0
  29. package/Components/River/ComponentsMap/ContextProviders/ComponentsMapRefContext.ts +8 -0
  30. package/Components/Screen/TV/hooks/useInitialFocus.ts +14 -4
  31. package/Components/Screen/__tests__/__snapshots__/Screen.test.tsx.snap +2 -0
  32. package/Components/Screen/__tests__/navigationHandler.test.ts +133 -22
  33. package/Components/Screen/index.tsx +22 -5
  34. package/Components/Screen/navigationHandler.ts +20 -2
  35. package/Components/ScreenRevealManager/__tests__/ScreenRevealManager.test.ts +2 -2
  36. package/Components/ScreenRevealManager/utils/index.ts +23 -0
  37. package/Components/ScreenRevealManager/withScreenRevealManager.tsx +54 -24
  38. package/Components/Tabs/TV/Tabs.android.tsx +1 -1
  39. package/Components/TextInputTv/__tests__/__snapshots__/TextInputTv.test.js.snap +13 -0
  40. package/Components/TextInputTv/index.tsx +11 -0
  41. package/Components/VideoLive/__tests__/__snapshots__/PlayerLiveImageComponent.test.tsx.snap +1 -0
  42. package/Components/VideoModal/ModalAnimation/ModalAnimationContext.tsx +117 -157
  43. package/Components/VideoModal/ModalAnimation/index.ts +2 -13
  44. package/Components/VideoModal/ModalAnimation/utils.ts +1 -327
  45. package/Components/VideoModal/PlayerDetails.tsx +5 -5
  46. package/Components/VideoModal/PlayerWrapper.tsx +14 -88
  47. package/Components/VideoModal/VideoModal.tsx +1 -5
  48. package/Components/VideoModal/__tests__/PlayerWrapper.test.tsx +1 -0
  49. package/Components/VideoModal/hooks/__tests__/useDelayedPlayerDetails.test.ts +9 -1
  50. package/Components/VideoModal/hooks/useDelayedPlayerDetails.ts +40 -15
  51. package/Components/VideoModal/hooks/useModalSize.ts +10 -5
  52. package/Components/VideoModal/hooks/utils/__tests__/showDetails.test.ts +2 -2
  53. package/Components/VideoModal/hooks/utils/index.ts +4 -0
  54. package/Components/VideoModal/playerWrapperStyle.ts +70 -0
  55. package/Components/VideoModal/playerWrapperUtils.ts +91 -0
  56. package/Contexts/CellFocusedStateContext/index.tsx +27 -0
  57. package/Decorators/RiverFeedLoader/utils/getDatasourceUrl.ts +6 -10
  58. package/events/index.ts +2 -0
  59. package/package.json +5 -5
  60. package/Components/VideoModal/ModalAnimation/AnimatedPlayerModalWrapper.tsx +0 -60
  61. package/Components/VideoModal/ModalAnimation/AnimatedScrollModal.tsx +0 -415
  62. package/Components/VideoModal/ModalAnimation/AnimatedVideoPlayerComponent.tsx +0 -176
  63. package/Components/VideoModal/ModalAnimation/AnimationComponent.tsx +0 -500
  64. 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 [animating, setAnimating] = React.useState(undefined);
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
- function startAnimation(toValue, config) {
53
- if (animating) {
54
- animating.reset();
55
- }
56
-
57
- const { duration, easing, delay = 0, onAnimationEnd = noop } = config;
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
- const compositeAnimation = Animated.timing(animatedValue, {
60
- duration,
61
- toValue,
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
- setAnimating(compositeAnimation);
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
- }, [visible, previousVisible]);
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={animating}
132
+ renderToHardwareTextureAndroid={!!animationRef.current}
90
133
  style={[styles(animatedValue), staticStyles]}
91
134
  >
92
135
  {children}
@@ -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
- <CellWithFocusable
227
- CellRenderer={CellRenderer}
228
- item={item}
229
- id={focusableId}
230
- groupId={groupId || component?.id}
231
- onFocus={handleFocus}
232
- index={index}
233
- scrollTo={this.scrollToIndex()}
234
- isFocusable={isFocusable}
235
- skipFocusManagerRegistration={skipFocusManagerRegistration}
236
- behavior={behavior}
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
- <View
243
- testID={`${component?.id}-${id}`}
244
- accessible={false}
245
- style={styles.touchableCell}
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
- {(focused, event) => {
262
- const isFocused = this.isCellFocused(focused);
263
-
264
- if (isFocused) {
265
- const accessibilityManager = AccessibilityManager.getInstance();
266
-
267
- const accessibilityTitle =
268
- item?.extensions?.accessibility?.label || item?.title || "";
269
-
270
- const accessibilityHint =
271
- item?.extensions?.accessibility?.hint || "";
272
-
273
- accessibilityManager.readText({
274
- text: `${accessibilityTitle} ${accessibilityHint}`,
275
- });
276
- }
277
-
278
- return (
279
- <FocusableCell
280
- {...{
281
- index,
282
- CellRenderer,
283
- item,
284
- focused: isFocused,
285
- scrollTo: this.scrollToIndex(event),
286
- behavior,
287
- }}
288
- />
289
- );
290
- }}
291
- </Focusable>
292
- </View>
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
- export class TvOSCellComponent extends React.Component<Props, State> {
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
- <CellWithFocusable
263
- CellRenderer={CellRenderer}
264
- item={item}
271
+ <FocusableWrapper
265
272
  id={focusableId}
266
- groupId={(groupId || component?.id).toString()}
273
+ groupId={String(groupId || component?.id)}
274
+ isParallaxDisabled={this.layout?.width > 1740}
267
275
  onFocus={handleFocus}
268
- index={index}
269
- scrollTo={this.scrollTo}
270
- preferredFocus={preferredFocus}
271
- focused={this.props.focused}
272
- behavior={behavior}
273
- isFocusable={isFocusable}
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.ComponentType<any>;
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.ComponentType<any>;
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, ...otherProps } = this.props;
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
- {...otherProps}
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
- {R.is(Function, children) ? children(focused) : children}
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(