@applicaster/zapp-react-native-ui-components 12.0.0-alpha.3977160850 → 12.0.0-alpha.4023252383

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 (128) hide show
  1. package/Components/BaseFocusable/index.tsx +3 -3
  2. package/Components/Cell/Cell.tsx +32 -11
  3. package/Components/Cell/CellWithFocusable.tsx +40 -19
  4. package/Components/Cell/TvOSCellComponent.tsx +5 -9
  5. package/Components/ErrorScreen/index.tsx +1 -1
  6. package/Components/Focusable/Focusable.tsx +6 -1
  7. package/Components/FocusableCell/index.tsx +7 -7
  8. package/Components/FocusableGroup/index.tsx +11 -2
  9. package/Components/FreezeWithCallback/Freeze.tsx +31 -0
  10. package/Components/FreezeWithCallback/LICENSE +21 -0
  11. package/Components/FreezeWithCallback/Suspender.tsx +17 -0
  12. package/Components/FreezeWithCallback/__tests__/index.test.tsx +170 -0
  13. package/Components/FreezeWithCallback/index.tsx +3 -0
  14. package/Components/GeneralContentScreen/utils/__tests__/useCurationAPI.test.js +13 -6
  15. package/Components/GeneralContentScreen/utils/useCurationAPI.ts +1 -1
  16. package/Components/HandlePlayable/HandlePlayable.tsx +3 -0
  17. package/Components/HookRenderer/index.tsx +1 -3
  18. package/Components/Image/Image.android.tsx +3 -25
  19. package/Components/Image/Image.tsx +18 -2
  20. package/Components/Image/utils/index.ts +39 -0
  21. package/Components/Layout/TV/ScreenContainer.tsx +79 -5
  22. package/Components/Layout/TV/ScreenLayoutContextProvider.tsx +2 -2
  23. package/Components/Layout/TV/__tests__/ScreenContainer.test.tsx +38 -28
  24. package/Components/Layout/TV/__tests__/__snapshots__/ScreenContainer.test.tsx.snap +25 -13
  25. package/Components/Layout/TV/__tests__/__snapshots__/index.test.tsx.snap +10 -10
  26. package/Components/Layout/TV/__tests__/index.test.tsx +20 -87
  27. package/Components/Layout/TV/index.tsx +14 -67
  28. package/Components/Layout/TV/index.web.tsx +33 -0
  29. package/Components/Layout/index.tsx +15 -11
  30. package/Components/MasterCell/DefaultComponents/ActionButton.tsx +4 -1
  31. package/Components/MasterCell/DefaultComponents/BorderContainerView/index.tsx +86 -116
  32. package/Components/MasterCell/DefaultComponents/DynamicBadge.tsx +4 -2
  33. package/Components/MasterCell/DefaultComponents/FocusableView/index.tsx +17 -1
  34. package/Components/MasterCell/DefaultComponents/Image/Image.android.tsx +9 -26
  35. package/Components/MasterCell/DefaultComponents/Image/Image.ios.tsx +5 -3
  36. package/Components/MasterCell/DefaultComponents/Image/hooks/useImageSize.ts +65 -0
  37. package/Components/MasterCell/DefaultComponents/ImageBorderContainer/index.ts +71 -0
  38. package/Components/MasterCell/DefaultComponents/ImageContainer/index.tsx +5 -1
  39. package/Components/MasterCell/DefaultComponents/LiveImage/index.tsx +37 -2
  40. package/Components/MasterCell/DefaultComponents/SecondaryImage/Image.tsx +3 -2
  41. package/Components/MasterCell/DefaultComponents/Text/index.tsx +14 -2
  42. package/Components/MasterCell/DefaultComponents/Toggle.tsx +5 -1
  43. package/Components/MasterCell/DefaultComponents/__tests__/image.test.js +1 -1
  44. package/Components/MasterCell/DefaultComponents/__tests__/text.test.tsx +24 -9
  45. package/Components/MasterCell/DefaultComponents/index.ts +2 -0
  46. package/Components/MasterCell/DefaultComponents/tv/ButtonContainerView/index.android.tsx +4 -1
  47. package/Components/MasterCell/DefaultComponents/tv/ButtonContainerView/index.tsx +10 -2
  48. package/Components/MasterCell/SharedUI/CollapsibleTextContainer/index.ts +1 -3
  49. package/Components/MasterCell/SharedUI/ProgressBar/hooks/useFillInPercent.ts +7 -1
  50. package/Components/MasterCell/dataAdapter.ts +21 -7
  51. package/Components/MasterCell/hooks/useAsyncRendering/index.ts +1 -1
  52. package/Components/MasterCell/index.tsx +5 -3
  53. package/Components/MasterCell/utils/__tests__/insertBetween.test.js +9 -0
  54. package/Components/MasterCell/utils/index.ts +35 -37
  55. package/Components/MeasurmentsPortal/BasicMeasurementsPortal.tsx +11 -7
  56. package/Components/MeasurmentsPortal/MeasurementsPortal.tsx +10 -1
  57. package/Components/Navigator/StackNavigator.tsx +57 -0
  58. package/Components/Navigator/index.ts +1 -0
  59. package/Components/PlayerContainer/PlayerContainer.tsx +77 -34
  60. package/Components/PlayerContainer/ProgramInfo/index.tsx +3 -1
  61. package/Components/PlayerImageBackground/index.tsx +34 -8
  62. package/Components/River/ComponentsMap/ComponentsMap.tsx +92 -22
  63. package/Components/River/RefreshControl.tsx +55 -1
  64. package/Components/River/TV/River.tsx +0 -23
  65. package/Components/River/__tests__/componentsMap.test.js +7 -0
  66. package/Components/Screen/TV/hooks/index.ts +1 -0
  67. package/Components/Screen/TV/hooks/useInitialFocus.ts +70 -0
  68. package/Components/Screen/TV/index.web.tsx +178 -0
  69. package/Components/Screen/__tests__/Screen.test.tsx +8 -50
  70. package/Components/Screen/__tests__/__snapshots__/Screen.test.tsx.snap +14 -0
  71. package/Components/Screen/index.tsx +64 -65
  72. package/Components/Screen/navigationHandler.ts +6 -3
  73. package/Components/Screen/orientationHandler.ts +18 -7
  74. package/Components/Screen/utils.ts +16 -0
  75. package/Components/ScreenResolver/index.tsx +9 -12
  76. package/Components/Tabs/TV/styles.ts +1 -1
  77. package/Components/Transitioner/Scene.tsx +39 -26
  78. package/Components/Transitioner/Transitioner.tsx +56 -48
  79. package/Components/Transitioner/__tests__/Scene.test.js +7 -0
  80. package/Components/Transitioner/__tests__/__snapshots__/Scene.test.js.snap +12 -4
  81. package/Components/Transitioner/__tests__/__snapshots__/transitioner.test.js.snap +2 -0
  82. package/Components/VideoLive/LiveImageManager.ts +28 -1
  83. package/Components/VideoLive/PlayerLiveImageComponent.tsx +79 -48
  84. package/Components/VideoLive/__tests__/PlayerLiveImageComponent.test.tsx +45 -3
  85. package/Components/VideoLive/__tests__/__snapshots__/PlayerLiveImageComponent.test.tsx.snap +21 -29
  86. package/Components/VideoModal/ModalAnimation/AnimatedPlayerModalWrapper.tsx +66 -0
  87. package/Components/VideoModal/ModalAnimation/AnimatedScrollModal.tsx +376 -0
  88. package/Components/VideoModal/ModalAnimation/AnimatedVideoPlayerComponent.tsx +165 -0
  89. package/Components/VideoModal/ModalAnimation/AnimationComponent.tsx +473 -0
  90. package/Components/VideoModal/ModalAnimation/ModalAnimationContext.tsx +182 -0
  91. package/Components/VideoModal/ModalAnimation/index.ts +15 -0
  92. package/Components/VideoModal/ModalAnimation/utils.ts +302 -0
  93. package/Components/VideoModal/PlayerDetails.tsx +19 -5
  94. package/Components/VideoModal/PlayerWrapper.tsx +114 -54
  95. package/Components/VideoModal/VideoModal.tsx +32 -43
  96. package/Components/VideoModal/__tests__/PlayerDetails.test.tsx +7 -0
  97. package/Components/VideoModal/__tests__/PlayerWrapper.test.tsx +13 -1
  98. package/Components/VideoModal/__tests__/__snapshots__/PlayerDetails.test.tsx.snap +2 -0
  99. package/Components/VideoModal/__tests__/__snapshots__/PlayerWrapper.test.tsx.snap +358 -166
  100. package/Components/VideoModal/hooks/useModalSize.ts +10 -12
  101. package/Components/VideoModal/utils.ts +8 -5
  102. package/Components/Viewport/ViewportTracker/ViewportTracker.tsx +31 -40
  103. package/Components/Viewport/ViewportTracker/index.tsx +1 -0
  104. package/Components/Viewport/utils.js +1 -1
  105. package/Components/ZappFrameworkComponents/BarView/BarView.tsx +4 -3
  106. package/Components/default-cell-renderer/__tests__/__snapshots__/defaultCellRenderer.test.tsx.snap +4 -4
  107. package/Contexts/FocusableGroupContext/FocusableGroupContext.ts +3 -0
  108. package/Contexts/FocusableGroupContext/FocusableGroupContextProvider.tsx +16 -0
  109. package/Contexts/FocusableGroupContext/__tests__/FocusableGroupContextProvider.test.tsx +22 -0
  110. package/Contexts/FocusableGroupContext/__tests__/withFocusableContext.test.tsx +26 -0
  111. package/Contexts/FocusableGroupContext/index.ts +7 -0
  112. package/Contexts/FocusableGroupContext/withFocusableContext.tsx +17 -0
  113. package/Contexts/MenuToggleContext/{index.js → index.ts} +16 -0
  114. package/Contexts/ScreenContext/__tests__/__snapshots__/index.test.tsx.snap +3 -0
  115. package/Contexts/ScreenContext/__tests__/index.test.tsx +27 -0
  116. package/Contexts/ScreenContext/index.tsx +94 -25
  117. package/Contexts/ScreenDataContext/index.tsx +20 -0
  118. package/Contexts/ZappPipesContext/ZappPipesEntryContext.ts +1 -1
  119. package/Decorators/ComponentsMapWrapper/withComponentsMapProvider.tsx +7 -2
  120. package/const.ts +6 -0
  121. package/events/index.ts +1 -0
  122. package/index.js +3 -0
  123. package/package.json +5 -5
  124. package/Components/Layout/TV/TransitionOverlay.tsx +0 -64
  125. package/Components/VideoModal/hooks/usePosition.ts +0 -50
  126. /package/Components/FocusableGroup/{index.android.js → index.android.ts} +0 -0
  127. /package/Components/FocusableGroup/{index.ios.js → index.ios.ts} +0 -0
  128. /package/Contexts/{index.js → index.ts} +0 -0
@@ -66,14 +66,14 @@ export class BaseFocusable<
66
66
  const { initialFocus } = this.props;
67
67
 
68
68
  if (initialFocus) {
69
- focusManager.setupInitialFocus(this.getId());
69
+ focusManager.setFocus(this.getId());
70
70
  }
71
71
  }
72
72
 
73
73
  getId(props?: { id: string; groupId: string }) {
74
- const { id, groupId = null } = props || this.props;
74
+ const { id } = props || this.props;
75
75
 
76
- return groupId ? `${groupId}-${id}` : id;
76
+ return id;
77
77
  }
78
78
 
79
79
  getRect() {
@@ -1,5 +1,5 @@
1
1
  import * as React from "react";
2
- import * as R from "ramda";
2
+ import { join, merge, path } from "ramda";
3
3
  import { View } from "react-native";
4
4
 
5
5
  import { Focusable } from "@applicaster/zapp-react-native-ui-components/Components/Focusable";
@@ -11,6 +11,7 @@ import { noop } from "@applicaster/zapp-react-native-utils/functionUtils";
11
11
 
12
12
  import { CellWithFocusable } from "./CellWithFocusable";
13
13
  import { BaseFocusable } from "../BaseFocusable";
14
+ import { AccessibilityManager } from "@applicaster/zapp-react-native-utils/appUtils/accessibilityManager";
14
15
 
15
16
  type Props = {
16
17
  item: {
@@ -66,6 +67,10 @@ type Props = {
66
67
  behavior: Behavior;
67
68
  };
68
69
 
70
+ type State = {
71
+ hasFocusableInside: boolean;
72
+ };
73
+
69
74
  const baseCellStyles = {
70
75
  height: "100%",
71
76
  display: "flex",
@@ -77,7 +82,7 @@ const touchableStyles = {
77
82
  height: "100%",
78
83
  } as const;
79
84
 
80
- export class CellComponent extends React.Component<Props> {
85
+ export class CellComponent extends React.Component<Props, State> {
81
86
  constructor(props) {
82
87
  super(props);
83
88
  this.onPress = this.onPress.bind(this);
@@ -87,6 +92,10 @@ export class CellComponent extends React.Component<Props> {
87
92
  this.hasReceivedFocus = this.hasReceivedFocus.bind(this);
88
93
  this.scrollVertically = this.scrollVertically.bind(this);
89
94
  this.scrollToIndex = this.scrollToIndex.bind(this);
95
+
96
+ this.state = {
97
+ hasFocusableInside: props.CellRenderer.hasFocusableInside?.(props.item),
98
+ };
90
99
  }
91
100
 
92
101
  setScreenLayout(componentAnchorPointY, screenLayout) {
@@ -105,8 +114,8 @@ export class CellComponent extends React.Component<Props> {
105
114
  onPress(item, index);
106
115
  } else {
107
116
  const { item, navigator, component, appData } = this.props;
108
- const itemTargetScreen = R.path(["screen_type"], item);
109
- const componentTargetScreen = R.path(["data", "target"], component);
117
+ const itemTargetScreen = path(["screen_type"], item);
118
+ const componentTargetScreen = path(["data", "target"], component);
110
119
  const itemType = getItemType(item, appData?.layoutVersion);
111
120
 
112
121
  sendSelectCellEvent(item, component, item.title, index);
@@ -116,7 +125,7 @@ export class CellComponent extends React.Component<Props> {
116
125
  componentTargetScreen &&
117
126
  !itemTargetScreen
118
127
  ) {
119
- navigator.push(R.merge(item, { screen_type: componentTargetScreen }));
128
+ navigator.push(merge(item, { screen_type: componentTargetScreen }));
120
129
 
121
130
  return;
122
131
  }
@@ -139,6 +148,10 @@ export class CellComponent extends React.Component<Props> {
139
148
  shouldScrollVertically?.(mouse, focusable, id, title)
140
149
  ) {
141
150
  this.scrollVertically(focusable);
151
+
152
+ const accessibilityManager = AccessibilityManager.getInstance();
153
+ // Text from DSP should be localized already
154
+ accessibilityManager.readText({ text: title });
142
155
  }
143
156
  }
144
157
  }
@@ -187,6 +200,16 @@ export class CellComponent extends React.Component<Props> {
187
200
  return !isFocusable ? false : focused || focusableFocused;
188
201
  }
189
202
 
203
+ componentDidUpdate(prevProps: Readonly<Props>) {
204
+ if (prevProps.item !== this.props.item) {
205
+ this.setState({
206
+ hasFocusableInside: this.props.CellRenderer.hasFocusableInside?.(
207
+ this.props.item
208
+ ),
209
+ });
210
+ }
211
+ }
212
+
190
213
  render() {
191
214
  const {
192
215
  item,
@@ -202,12 +225,12 @@ export class CellComponent extends React.Component<Props> {
202
225
  groupId,
203
226
  isFocusable = true,
204
227
  skipFocusManagerRegistration,
205
- behavior
228
+ behavior,
206
229
  } = this.props;
207
230
 
208
231
  const { id } = item;
209
232
 
210
- const focusableId = R.join("-", [component?.id, id, index]);
233
+ const focusableId = join("-", [component?.id, id, index]);
211
234
 
212
235
  const handleFocus = (focusable, mouse) => {
213
236
  const focusFn = onFocus || noop;
@@ -215,9 +238,7 @@ export class CellComponent extends React.Component<Props> {
215
238
  this.onFocus(focusable, mouse);
216
239
  };
217
240
 
218
- const hasFocusableInside = CellRenderer.hasFocusableInside?.(item);
219
-
220
- if (hasFocusableInside) {
241
+ if (this.state.hasFocusableInside) {
221
242
  return (
222
243
  <CellWithFocusable
223
244
  CellRenderer={CellRenderer}
@@ -263,7 +284,7 @@ export class CellComponent extends React.Component<Props> {
263
284
  item,
264
285
  focused: this.isCellFocused(focused),
265
286
  scrollTo: this.scrollToIndex(event),
266
- behavior
287
+ behavior,
267
288
  }}
268
289
  />
269
290
  );
@@ -3,15 +3,25 @@ import * as React from "react";
3
3
  import { focusManager } from "@applicaster/zapp-react-native-utils/appUtils/focusManager";
4
4
  import * as FOCUS_EVENTS from "@applicaster/zapp-react-native-utils/appUtils/focusManager/events";
5
5
  import { noop } from "@applicaster/zapp-react-native-utils/functionUtils";
6
+ import { toBooleanWithDefaultFalse } from "@applicaster/zapp-react-native-utils/booleanUtils";
7
+
6
8
  import { isAppleTV } from "../../Helpers/Platform";
7
9
  import { useCellState } from "../MasterCell/utils";
8
10
 
9
- const useCellFocusedState = (groupId: string, id: string) => {
11
+ const useCellFocusedState = (
12
+ skipFocusManagerRegistration: boolean,
13
+ groupId: string,
14
+ id: string
15
+ ) => {
10
16
  const [currentCellFocused, setCurrentCellFocused] = React.useState(false);
11
17
 
12
18
  React.useEffect(() => {
13
- const isGroupItemFocused = () =>
14
- setCurrentCellFocused(focusManager.isGroupItemFocused(groupId, id));
19
+ const isGroupItemFocused = () => {
20
+ if (!skipFocusManagerRegistration) {
21
+ const isFocused = focusManager.isGroupItemFocused(groupId, id);
22
+ setCurrentCellFocused(isFocused);
23
+ }
24
+ };
15
25
 
16
26
  const handler = () => {
17
27
  // tvOS hack for properly checking focus
@@ -29,24 +39,24 @@ const useCellFocusedState = (groupId: string, id: string) => {
29
39
  return () => {
30
40
  focusManager.removeHandler(FOCUS_EVENTS.FOCUS, handler);
31
41
  };
32
- }, [groupId]);
42
+ }, [groupId, skipFocusManagerRegistration]);
33
43
 
34
44
  return currentCellFocused;
35
45
  };
36
46
 
37
47
  type Props = {
38
- item: ZappEntry,
39
- CellRenderer: React.ComponentType<{ item: Object, state: string }>,
40
- id: string,
41
- groupId: string,
42
- onFocus: Function,
43
- index: number,
44
- scrollTo: Function,
45
- preferredFocus?: boolean,
46
- skipFocusManagerRegistration?: boolean,
47
- isFocusable?: boolean,
48
- behavior: Behavior,
49
- selected?: boolean,
48
+ item: ZappEntry;
49
+ CellRenderer: React.ComponentType<{ item: Object; state: string }>;
50
+ id: string;
51
+ groupId: string;
52
+ onFocus: Function;
53
+ index: number;
54
+ scrollTo: Function;
55
+ preferredFocus?: boolean;
56
+ skipFocusManagerRegistration?: boolean;
57
+ isFocusable?: boolean;
58
+ behavior: Behavior;
59
+ focused?: boolean;
50
60
  };
51
61
 
52
62
  export function CellWithFocusable(props: Props) {
@@ -62,11 +72,21 @@ export function CellWithFocusable(props: Props) {
62
72
  skipFocusManagerRegistration,
63
73
  isFocusable,
64
74
  behavior,
65
- selected
75
+ focused,
66
76
  } = props;
67
77
 
68
- const isFocused = useCellFocusedState(groupId, id);
69
- const state = useCellState({ id: item.id, behavior, focused: isFocused, selected });
78
+ const isFocused = useCellFocusedState(
79
+ skipFocusManagerRegistration,
80
+ groupId,
81
+ id
82
+ );
83
+
84
+ const state = useCellState({
85
+ id: item.id,
86
+ behavior,
87
+ focused: isFocused || toBooleanWithDefaultFalse(focused),
88
+ });
89
+
70
90
  const [focusedButtonId, setFocusedButtonId] = React.useState(undefined);
71
91
 
72
92
  // for horizontal scrolling
@@ -95,6 +115,7 @@ export function CellWithFocusable(props: Props) {
95
115
  preferredFocus={preferredFocus}
96
116
  skipFocusManagerRegistration={skipFocusManagerRegistration}
97
117
  isFocusable={isFocusable}
118
+ focused={focused}
98
119
  />
99
120
  );
100
121
  }
@@ -226,13 +226,7 @@ export class TvOSCellComponent extends React.Component<Props, State> {
226
226
 
227
227
  hasReceivedFocus() {}
228
228
 
229
- scrollTo(index) {
230
- // Workaround for HL autoscroll/autoloop feature, we scroll manually on tvOS.
231
- // Should be removed after refactoring autoscroll/autoloop feature
232
- if (this.props.component?.component_type !== "horizontal_list_qb") {
233
- this.props?.scrollToIndex?.(index);
234
- }
235
- }
229
+ scrollTo() {}
236
230
 
237
231
  render() {
238
232
  const {
@@ -249,7 +243,7 @@ export class TvOSCellComponent extends React.Component<Props, State> {
249
243
  hasReceivedFocus,
250
244
  groupId,
251
245
  isFocusable,
252
- behavior
246
+ behavior,
253
247
  } = this.props;
254
248
 
255
249
  const { id } = item;
@@ -279,7 +273,9 @@ export class TvOSCellComponent extends React.Component<Props, State> {
279
273
  index={index}
280
274
  scrollTo={this.scrollTo}
281
275
  preferredFocus={preferredFocus}
276
+ focused={this.props.focused}
282
277
  behavior={behavior}
278
+ isFocusable={isFocusable}
283
279
  />
284
280
  </View>
285
281
  );
@@ -310,7 +306,7 @@ export class TvOSCellComponent extends React.Component<Props, State> {
310
306
  item,
311
307
  focused: this.props.focused || selectedCell || focused,
312
308
  scrollTo: this.scrollTo,
313
- behavior
309
+ behavior,
314
310
  }}
315
311
  />
316
312
  );
@@ -3,7 +3,6 @@ import * as R from "ramda";
3
3
  import {
4
4
  View,
5
5
  StyleSheet,
6
- Image,
7
6
  Text,
8
7
  ViewStyle,
9
8
  TextStyle,
@@ -13,6 +12,7 @@ import {
13
12
  import { defaultAsset } from "./defaultAsset";
14
13
  import { platformSelect } from "@applicaster/zapp-react-native-utils/reactUtils";
15
14
  import { useNavigation } from "@applicaster/zapp-react-native-utils/reactHooks";
15
+ import { QBImage as Image } from "@applicaster/zapp-react-native-ui-components/Components/Image";
16
16
  import { Button } from "./Button";
17
17
 
18
18
  type ButtonProp = {
@@ -4,6 +4,7 @@ import * as R from "ramda";
4
4
  import { BaseFocusable } from "../BaseFocusable";
5
5
  import { focusManager } from "@applicaster/zapp-react-native-utils/appUtils/focusManager";
6
6
  import { LONG_KEY_PRESS_TIMEOUT } from "@applicaster/quick-brick-core/const";
7
+ import { withFocusableContext } from "../../Contexts/FocusableGroupContext/withFocusableContext";
7
8
 
8
9
  type Props = {
9
10
  initialFocus?: boolean;
@@ -23,7 +24,7 @@ type Props = {
23
24
  style?: React.CSSProperties;
24
25
  };
25
26
 
26
- export class Focusable extends BaseFocusable<Props> {
27
+ class Focusable extends BaseFocusable<Props> {
27
28
  isGroup: boolean;
28
29
  mouse: boolean;
29
30
  longPressTimeout = null;
@@ -198,3 +199,7 @@ export class Focusable extends BaseFocusable<Props> {
198
199
  );
199
200
  }
200
201
  }
202
+
203
+ const FocusableWithFocusableContext = withFocusableContext(Focusable);
204
+
205
+ export { FocusableWithFocusableContext as Focusable };
@@ -3,12 +3,12 @@ import { Platform } from "react-native";
3
3
  import { useCellState } from "../MasterCell/utils";
4
4
 
5
5
  type Props = {
6
- index: number,
7
- item: ZappEntry,
8
- focused: boolean,
9
- scrollTo?: (index: number) => void,
10
- CellRenderer: React.ComponentType<{ item: Object, state: string }>,
11
- behavior: Behavior
6
+ index: number;
7
+ item: ZappEntry;
8
+ focused: boolean;
9
+ scrollTo?: (index: number) => void;
10
+ CellRenderer: React.ComponentType<{ item: Object; state: string }>;
11
+ behavior: Behavior;
12
12
  };
13
13
 
14
14
  const isAndroid = Platform.OS === "android";
@@ -20,7 +20,7 @@ export function FocusableCell(props: Props) {
20
20
  CellRenderer,
21
21
  focused,
22
22
  scrollTo = null,
23
- behavior
23
+ behavior,
24
24
  } = props;
25
25
 
26
26
  const state = useCellState({ id: item.id, behavior, focused });
@@ -1,6 +1,8 @@
1
1
  import * as React from "react";
2
2
 
3
3
  import { BaseFocusable } from "../BaseFocusable";
4
+ import { FocusableGroupContextProvider } from "../../Contexts/FocusableGroupContext";
5
+ import { withFocusableContext } from "../../Contexts/FocusableGroupContext/withFocusableContext";
4
6
 
5
7
  type Props = {
6
8
  testID?: string;
@@ -26,9 +28,10 @@ type Props = {
26
28
  nextFocusLeft?: any;
27
29
  nextFocusRight?: any;
28
30
  onLayout?: any;
31
+ excludeFromFocusSearching?: boolean;
29
32
  };
30
33
 
31
- export class FocusableGroup extends BaseFocusable<Props> {
34
+ class FocusableGroup extends BaseFocusable<Props> {
32
35
  constructor(props) {
33
36
  super(props);
34
37
  this.isGroup = true;
@@ -81,8 +84,14 @@ export class FocusableGroup extends BaseFocusable<Props> {
81
84
  style={style}
82
85
  ref={this.ref}
83
86
  >
84
- {children}
87
+ <FocusableGroupContextProvider id={id}>
88
+ {children}
89
+ </FocusableGroupContextProvider>
85
90
  </div>
86
91
  );
87
92
  }
88
93
  }
94
+
95
+ const FocusableGroupWithFocusableContext = withFocusableContext(FocusableGroup);
96
+
97
+ export { FocusableGroupWithFocusableContext as FocusableGroup };
@@ -0,0 +1,31 @@
1
+ import { usePrevious } from "@applicaster/zapp-react-native-utils/reactHooks/utils";
2
+ import React, { Suspense, useEffect } from "react";
3
+ import { Suspender } from "./Suspender";
4
+
5
+ interface Props {
6
+ freeze: boolean;
7
+ children: React.ReactNode;
8
+ placeholder?: React.ReactNode;
9
+ onRelease?: () => void;
10
+ }
11
+
12
+ export function Freeze({
13
+ freeze,
14
+ children,
15
+ placeholder = null,
16
+ onRelease,
17
+ }: Props) {
18
+ const prevFreeze = usePrevious(freeze);
19
+
20
+ useEffect(() => {
21
+ if (prevFreeze && !freeze && onRelease) {
22
+ onRelease();
23
+ }
24
+ }, [freeze, onRelease, prevFreeze]);
25
+
26
+ return (
27
+ <Suspense fallback={placeholder}>
28
+ <Suspender freeze={freeze}>{children}</Suspender>
29
+ </Suspense>
30
+ );
31
+ }
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2021 Software Mansion
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,17 @@
1
+ import React, { Fragment } from "react";
2
+
3
+ const infiniteThenable = { then() {} };
4
+
5
+ export const Suspender = ({
6
+ freeze,
7
+ children,
8
+ }: {
9
+ freeze: boolean;
10
+ children: React.ReactNode;
11
+ }) => {
12
+ if (freeze) {
13
+ throw infiniteThenable;
14
+ }
15
+
16
+ return <Fragment>{children}</Fragment>;
17
+ };
@@ -0,0 +1,170 @@
1
+ import React, { Dispatch, useEffect, useState } from "react";
2
+ import {
3
+ act,
4
+ create,
5
+ ReactTestRenderer,
6
+ ReactTestRendererJSON,
7
+ } from "react-test-renderer";
8
+ import { FreezeWithCallback } from "../index";
9
+
10
+ const SimpleContent = () => <div />;
11
+
12
+ interface InnerProps {
13
+ value: number;
14
+ }
15
+ // eslint-disable-next-line unused-imports/no-unused-vars
16
+ const Inner = ({ value }: InnerProps) => <div />;
17
+
18
+ interface TestSubscriber {
19
+ renderCount: number;
20
+ subscription: Dispatch<number>;
21
+ }
22
+
23
+ function createSubscriberComponent(): [React.FC, TestSubscriber] {
24
+ const testState: TestSubscriber = {
25
+ renderCount: 0,
26
+ subscription: () => {},
27
+ };
28
+
29
+ const Subscriber: React.FC = () => {
30
+ const [value, setValue] = useState(0);
31
+
32
+ useEffect(() => {
33
+ testState.subscription = setValue;
34
+ }, []);
35
+
36
+ testState.renderCount += 1;
37
+
38
+ return <Inner value={value} />;
39
+ };
40
+
41
+ return [Subscriber, testState];
42
+ }
43
+
44
+ interface ContainerProps {
45
+ freeze: boolean;
46
+ children: React.ReactNode;
47
+ }
48
+
49
+ const Container = ({ freeze, children }: ContainerProps) => (
50
+ <FreezeWithCallback freeze={freeze}>{children}</FreezeWithCallback>
51
+ );
52
+
53
+ function setupTest(initialFreeze: boolean = false) {
54
+ let testRenderer: ReactTestRenderer | undefined;
55
+ const [Subscriber, subscriberState] = createSubscriberComponent();
56
+
57
+ act(() => {
58
+ testRenderer = create(
59
+ <Container freeze={initialFreeze}>
60
+ <Subscriber />
61
+ </Container>
62
+ );
63
+ });
64
+
65
+ return {
66
+ testRenderer,
67
+ testInstance: testRenderer?.root,
68
+ subscriberState,
69
+ updateFreeze: (freeze: boolean) => {
70
+ act(() =>
71
+ testRenderer?.update(
72
+ <Container freeze={freeze}>
73
+ <Subscriber />
74
+ </Container>
75
+ )
76
+ );
77
+ },
78
+ };
79
+ }
80
+
81
+ describe("FreezeWithCallback", () => {
82
+ test("Renders stuff not frozen", () => {
83
+ const testRenderer = create(
84
+ <Container freeze={false}>
85
+ <SimpleContent />
86
+ </Container>
87
+ );
88
+
89
+ expect(testRenderer.root.findByType(SimpleContent)).toBeTruthy();
90
+ });
91
+
92
+ test("Does not render stuff when frozen", () => {
93
+ const testRenderer = create(
94
+ <Container freeze>
95
+ <SimpleContent />
96
+ </Container>
97
+ );
98
+
99
+ expect(testRenderer.root.findAllByType(SimpleContent)).toHaveLength(0);
100
+ });
101
+
102
+ test("Stuff is gone after freeze", () => {
103
+ const testRenderer = create(
104
+ <Container freeze={false}>
105
+ <SimpleContent />
106
+ </Container>
107
+ );
108
+
109
+ expect(testRenderer.root.findByType(SimpleContent)).toBeTruthy();
110
+
111
+ act(() =>
112
+ testRenderer.update(
113
+ <Container freeze>
114
+ <SimpleContent />
115
+ </Container>
116
+ )
117
+ );
118
+
119
+ expect(testRenderer.toJSON()).toBe(null);
120
+ });
121
+
122
+ test("Updates work when not frozen", () => {
123
+ const { testInstance, subscriberState } = setupTest();
124
+
125
+ expect(testInstance?.findByType(Inner).props.value).toEqual(0);
126
+ act(() => subscriberState.subscription(1));
127
+ expect(testInstance?.findByType(Inner).props.value).toEqual(1);
128
+ expect(subscriberState.renderCount).toBe(2);
129
+ });
130
+
131
+ test("Updates does not propagate when frozen", () => {
132
+ const { testInstance, subscriberState, updateFreeze } = setupTest();
133
+
134
+ expect(testInstance?.findByType(Inner).props.value).toEqual(0);
135
+ updateFreeze(true);
136
+ act(() => subscriberState.subscription(1));
137
+ expect(testInstance?.findByType(Inner).props.value).toEqual(0);
138
+ expect(subscriberState.renderCount).toBe(1);
139
+ });
140
+
141
+ test("State persists after defrost", () => {
142
+ const { testInstance, subscriberState, updateFreeze, testRenderer } =
143
+ setupTest();
144
+
145
+ expect(testInstance?.findByType(Inner).props.value).toEqual(0);
146
+ act(() => subscriberState.subscription(1));
147
+ expect(testInstance?.findByType(Inner).props.value).toEqual(1);
148
+
149
+ updateFreeze(true);
150
+ expect(testRenderer?.toJSON()).toBe(null);
151
+
152
+ updateFreeze(false);
153
+ expect((testRenderer?.toJSON() as ReactTestRendererJSON).type).toBe("div");
154
+ expect(testInstance?.findByType(Inner).props.value).toEqual(1);
155
+ });
156
+
157
+ test("Update propagate after defrost", () => {
158
+ const { testInstance, subscriberState, updateFreeze } = setupTest();
159
+
160
+ updateFreeze(true);
161
+ act(() => subscriberState.subscription(1));
162
+ act(() => subscriberState.subscription(2));
163
+ act(() => subscriberState.subscription(3));
164
+ expect(testInstance?.findByType(Inner).props.value).toEqual(0);
165
+
166
+ updateFreeze(false);
167
+ expect(testInstance?.findByType(Inner).props.value).toEqual(3);
168
+ expect(subscriberState.renderCount).toBe(2);
169
+ });
170
+ });
@@ -0,0 +1,3 @@
1
+ import { Freeze } from "./Freeze";
2
+
3
+ export const FreezeWithCallback = Freeze;