@applicaster/zapp-react-native-ui-components 13.0.0-alpha.1488035644 → 13.0.0-alpha.1605643562

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 (24) hide show
  1. package/Components/Cell/CellWithFocusable.ios.tsx +126 -0
  2. package/Components/Cell/CellWithFocusable.tsx +36 -55
  3. package/Components/Cell/__tests__/CellWIthFocusable.test.js +13 -6
  4. package/Components/MasterCell/__tests__/__snapshots__/masterCell.test.js.snap +11 -0
  5. package/Components/MasterCell/elementMapper.tsx +1 -12
  6. package/Components/MasterCell/hooks/useAsyncRendering/MasterCellAsyncRenderManager.ts +2 -2
  7. package/Components/MasterCell/index.tsx +18 -84
  8. package/Components/ModalComponent/Header/index.tsx +3 -3
  9. package/Components/River/ComponentsMap/ComponentsMap.tsx +39 -65
  10. package/Components/River/ComponentsMap/hooks/useLoadingState.ts +78 -51
  11. package/Components/River/RiverFooter.tsx +39 -9
  12. package/Components/River/RiverItem.tsx +37 -2
  13. package/Components/River/__tests__/__snapshots__/componentsMap.test.js.snap +100 -31
  14. package/Components/River/__tests__/componentsMap.test.js +3 -4
  15. package/Components/Screen/hooks.ts +56 -0
  16. package/Components/Screen/index.tsx +13 -39
  17. package/Components/Transitioner/Scene.tsx +52 -23
  18. package/Components/Transitioner/__tests__/__snapshots__/Scene.test.js.snap +59 -43
  19. package/Components/Transitioner/__tests__/__snapshots__/transitioner.test.js.snap +2 -2
  20. package/Components/Transitioner/index.js +8 -4
  21. package/Components/VideoLive/PlayerLiveImageComponent.tsx +11 -5
  22. package/Components/VideoModal/ModalAnimation/ModalAnimationContext.tsx +2 -3
  23. package/Components/default-cell-renderer/__tests__/__snapshots__/defaultCellRenderer.test.tsx.snap +32 -0
  24. package/package.json +5 -5
@@ -0,0 +1,126 @@
1
+ /** TODO: Remove this file when tvos FocusableGroup
2
+ * behaviour is aligned to the web one
3
+ * FocusableGroup should only send onFocus and onBlur events when
4
+ * Focus enters and leaves the Focusables inside the branch
5
+ */
6
+ import * as React from "react";
7
+
8
+ import { focusManager } from "@applicaster/zapp-react-native-utils/appUtils/focusManager";
9
+ import * as FOCUS_EVENTS from "@applicaster/zapp-react-native-utils/appUtils/focusManager/events";
10
+ import { noop } from "@applicaster/zapp-react-native-utils/functionUtils";
11
+ import { toBooleanWithDefaultFalse } from "@applicaster/zapp-react-native-utils/booleanUtils";
12
+
13
+ import { isAppleTV } from "../../Helpers/Platform";
14
+ import { useCellState } from "../MasterCell/utils";
15
+
16
+ const useCellFocusedState = (
17
+ skipFocusManagerRegistration: boolean,
18
+ groupId: string,
19
+ id: string
20
+ ) => {
21
+ const [currentCellFocused, setCurrentCellFocused] = React.useState(false);
22
+
23
+ React.useEffect(() => {
24
+ const isGroupItemFocused = () => {
25
+ if (!skipFocusManagerRegistration) {
26
+ const isFocused = focusManager.isGroupItemFocused(groupId, id);
27
+ setCurrentCellFocused(isFocused);
28
+ }
29
+ };
30
+
31
+ const handler = () => {
32
+ // tvOS hack for properly checking focus
33
+ if (isAppleTV()) {
34
+ setTimeout(() => {
35
+ isGroupItemFocused();
36
+ }, 0);
37
+ } else {
38
+ isGroupItemFocused();
39
+ }
40
+ };
41
+
42
+ focusManager.on(FOCUS_EVENTS.FOCUS, handler);
43
+
44
+ return () => {
45
+ focusManager.removeHandler(FOCUS_EVENTS.FOCUS, handler);
46
+ };
47
+ }, [groupId, skipFocusManagerRegistration]);
48
+
49
+ return currentCellFocused;
50
+ };
51
+
52
+ type Props = {
53
+ item: ZappEntry;
54
+ CellRenderer: React.FunctionComponent<any>;
55
+ id: string;
56
+ groupId: string;
57
+ onFocus: Function;
58
+ index: number;
59
+ scrollTo: Function;
60
+ preferredFocus?: boolean;
61
+ skipFocusManagerRegistration?: boolean;
62
+ isFocusable?: boolean;
63
+ behavior: Behavior;
64
+ focused?: boolean;
65
+ };
66
+
67
+ export function CellWithFocusable(props: Props) {
68
+ const {
69
+ index,
70
+ item,
71
+ CellRenderer,
72
+ id,
73
+ groupId,
74
+ onFocus,
75
+ scrollTo = noop,
76
+ preferredFocus,
77
+ skipFocusManagerRegistration,
78
+ isFocusable,
79
+ behavior,
80
+ focused,
81
+ } = props;
82
+
83
+ const isFocused = useCellFocusedState(
84
+ skipFocusManagerRegistration,
85
+ groupId,
86
+ id
87
+ );
88
+
89
+ const state = useCellState({
90
+ id: item.id,
91
+ behavior,
92
+ focused: isFocused || toBooleanWithDefaultFalse(focused),
93
+ });
94
+
95
+ const [focusedButtonId, setFocusedButtonId] = React.useState(undefined);
96
+
97
+ // for horizontal scrolling
98
+ React.useEffect(() => {
99
+ if (focusedButtonId) {
100
+ scrollTo(index);
101
+ }
102
+ }, [focusedButtonId]);
103
+
104
+ const handleToggleFocus = (value) => {
105
+ setFocusedButtonId(value.focusedButtonId);
106
+
107
+ if (value.focusable) {
108
+ onFocus(value.focusable, value.mouse);
109
+ }
110
+ };
111
+
112
+ return (
113
+ <CellRenderer
114
+ item={item}
115
+ groupId={groupId}
116
+ onToggleFocus={handleToggleFocus}
117
+ state={state}
118
+ prefixId={id}
119
+ focusedButtonId={focusedButtonId}
120
+ preferredFocus={preferredFocus}
121
+ skipFocusManagerRegistration={skipFocusManagerRegistration}
122
+ isFocusable={isFocusable}
123
+ focused={focused}
124
+ />
125
+ );
126
+ }
@@ -1,52 +1,14 @@
1
1
  import * as React from "react";
2
2
 
3
- import { focusManager } from "@applicaster/zapp-react-native-utils/appUtils/focusManager";
4
- import * as FOCUS_EVENTS from "@applicaster/zapp-react-native-utils/appUtils/focusManager/events";
5
3
  import { noop } from "@applicaster/zapp-react-native-utils/functionUtils";
6
4
  import { toBooleanWithDefaultFalse } from "@applicaster/zapp-react-native-utils/booleanUtils";
7
5
 
8
- import { isAppleTV } from "../../Helpers/Platform";
9
6
  import { useCellState } from "../MasterCell/utils";
10
-
11
- const useCellFocusedState = (
12
- skipFocusManagerRegistration: boolean,
13
- groupId: string,
14
- id: string
15
- ) => {
16
- const [currentCellFocused, setCurrentCellFocused] = React.useState(false);
17
-
18
- React.useEffect(() => {
19
- const isGroupItemFocused = () => {
20
- if (!skipFocusManagerRegistration) {
21
- const isFocused = focusManager.isGroupItemFocused(groupId, id);
22
- setCurrentCellFocused(isFocused);
23
- }
24
- };
25
-
26
- const handler = () => {
27
- // tvOS hack for properly checking focus
28
- if (isAppleTV()) {
29
- setTimeout(() => {
30
- isGroupItemFocused();
31
- }, 0);
32
- } else {
33
- isGroupItemFocused();
34
- }
35
- };
36
-
37
- focusManager.on?.(FOCUS_EVENTS.FOCUS, handler);
38
-
39
- return () => {
40
- focusManager.removeHandler?.(FOCUS_EVENTS.FOCUS, handler);
41
- };
42
- }, [groupId, skipFocusManagerRegistration]);
43
-
44
- return currentCellFocused;
45
- };
7
+ import { FocusableGroup } from "../FocusableGroup";
46
8
 
47
9
  type Props = {
48
10
  item: ZappEntry;
49
- CellRenderer: React.ComponentType<ZappNewCellRendererProps>;
11
+ CellRenderer: React.FunctionComponent<any>;
50
12
  id: string;
51
13
  groupId: string;
52
14
  onFocus: Function;
@@ -75,11 +37,7 @@ export function CellWithFocusable(props: Props) {
75
37
  focused,
76
38
  } = props;
77
39
 
78
- const isFocused = useCellFocusedState(
79
- skipFocusManagerRegistration || false,
80
- groupId,
81
- id
82
- );
40
+ const [isFocused, setIsFocused] = React.useState(false);
83
41
 
84
42
  const state = useCellState({
85
43
  id: item.id,
@@ -107,18 +65,41 @@ export function CellWithFocusable(props: Props) {
107
65
  [onFocus]
108
66
  );
109
67
 
68
+ const onGroupFocus = React.useCallback(() => {
69
+ if (!skipFocusManagerRegistration) {
70
+ setIsFocused(true);
71
+ }
72
+ }, [skipFocusManagerRegistration]);
73
+
74
+ const onGroupBlur = React.useCallback(() => {
75
+ if (!skipFocusManagerRegistration) {
76
+ setIsFocused(false);
77
+ }
78
+ }, [skipFocusManagerRegistration]);
79
+
110
80
  return (
111
- <CellRenderer
112
- item={item}
81
+ <FocusableGroup
82
+ id={`focusable-cell-wrapper-${id}`}
83
+ testID={"cell-with-focusable-cell-renderer-focusable-group"}
113
84
  groupId={groupId}
114
- onToggleFocus={handleToggleFocus}
115
- state={state}
116
- prefixId={id}
117
- focusedButtonId={focusedButtonId}
118
85
  preferredFocus={preferredFocus}
119
- skipFocusManagerRegistration={skipFocusManagerRegistration}
120
- isFocusable={isFocusable}
121
- focused={focused}
122
- />
86
+ shouldUsePreferredFocus
87
+ onFocus={onGroupFocus}
88
+ onBlur={onGroupBlur}
89
+ >
90
+ <CellRenderer
91
+ testID={"cell-with-focusable-cell-renderer"}
92
+ item={item}
93
+ groupId={`focusable-cell-wrapper-${id}`}
94
+ onToggleFocus={handleToggleFocus}
95
+ state={state}
96
+ prefixId={id}
97
+ focusedButtonId={focusedButtonId}
98
+ preferredFocus={true}
99
+ skipFocusManagerRegistration={skipFocusManagerRegistration}
100
+ isFocusable={isFocusable}
101
+ focused={focused}
102
+ />
103
+ </FocusableGroup>
123
104
  );
124
105
  }
@@ -1,7 +1,7 @@
1
1
  import { View } from "react-native";
2
2
  import React from "react";
3
3
  import { act, render } from "@testing-library/react-native";
4
- import { CellWithFocusable } from "../CellWithFocusable";
4
+ import { CellWithFocusable } from "../CellWithFocusable.tsx";
5
5
 
6
6
  import { focusManager } from "@applicaster/zapp-react-native-utils/appUtils/focusManager";
7
7
 
@@ -23,7 +23,9 @@ describe("CellWithFocusable", () => {
23
23
 
24
24
  const wrapper = renderWith(props);
25
25
 
26
- expect(wrapper.UNSAFE_getByType("View").props.state).toBe("default");
26
+ const element = wrapper.getByTestId("cell-with-focusable-cell-renderer");
27
+
28
+ expect(element.props.state).toBe("default");
27
29
  });
28
30
 
29
31
  it("should render in default state", () => {
@@ -40,8 +42,9 @@ describe("CellWithFocusable", () => {
40
42
  focusManager.isGroupItemFocused = jest.fn(() => true);
41
43
 
42
44
  const wrapper = renderWith(props);
45
+ const element = wrapper.getByTestId("cell-with-focusable-cell-renderer");
43
46
 
44
- expect(wrapper.UNSAFE_getByType("View").props.state).toBe("default");
47
+ expect(element.props.state).toBe("default");
45
48
  });
46
49
 
47
50
  it("should render in focused state", () => {
@@ -55,13 +58,17 @@ describe("CellWithFocusable", () => {
55
58
  scrollTo: jest.fn(),
56
59
  };
57
60
 
58
- focusManager.isGroupItemFocused = jest.fn(() => true);
59
61
  const wrapper = renderWith(props);
60
62
 
63
+ const focusableGroupComponent = wrapper.getByTestId(
64
+ "cell-with-focusable-cell-renderer-focusable-group"
65
+ );
66
+
61
67
  act(() => {
62
- focusManager.on.mock.calls[focusManager.on.mock.calls.length - 1][1]();
68
+ focusableGroupComponent.props.onFocus();
63
69
  });
64
70
 
65
- expect(wrapper.UNSAFE_getByType("View").props.state).toBe("focused");
71
+ const element = wrapper.getByTestId("cell-with-focusable-cell-renderer");
72
+ expect(element.props.state).toBe("focused");
66
73
  });
67
74
  });
@@ -11,6 +11,7 @@ exports[`masterCellBuilder when using a custom data adapter renders correctly 1`
11
11
  <View
12
12
  cellUUID="mocked-uuid"
13
13
  key="0"
14
+ skipButtons={false}
14
15
  style={
15
16
  {
16
17
  "backgroundColor": "#2F2F2F",
@@ -40,6 +41,7 @@ exports[`masterCellBuilder when using custom containerStyle renders correctly 1`
40
41
  <View
41
42
  cellUUID="mocked-uuid"
42
43
  key="0"
44
+ skipButtons={false}
43
45
  style={{}}
44
46
  wrapperRef={
45
47
  {
@@ -61,6 +63,7 @@ exports[`masterCellBuilder when using custom default components renders correctl
61
63
  <mockConstructor
62
64
  cellUUID="mocked-uuid"
63
65
  key="0"
66
+ skipButtons={false}
64
67
  style={{}}
65
68
  wrapperRef={
66
69
  {
@@ -82,6 +85,7 @@ exports[`masterCellBuilder when using default options can be invoked as a React
82
85
  <View
83
86
  cellUUID="mocked-uuid"
84
87
  key="0"
88
+ skipButtons={false}
85
89
  style={
86
90
  {
87
91
  "backgroundColor": "#2F2F2F",
@@ -107,6 +111,7 @@ exports[`masterCellBuilder when using default options can be invoked as a React
107
111
  cellUUID="mocked-uuid"
108
112
  key="0"
109
113
  numberOfLines={1}
114
+ skipButtons={false}
110
115
  style={
111
116
  {
112
117
  "borderTopLeftRadius": 4,
@@ -120,6 +125,7 @@ exports[`masterCellBuilder when using default options can be invoked as a React
120
125
  <View
121
126
  cellUUID="mocked-uuid"
122
127
  key="1"
128
+ skipButtons={false}
123
129
  style={
124
130
  {
125
131
  "height": 30,
@@ -138,6 +144,7 @@ exports[`masterCellBuilder when using default options can be invoked as a React
138
144
  key="0"
139
145
  label="Test Entry"
140
146
  numberOfLines={1}
147
+ skipButtons={false}
141
148
  style={
142
149
  {
143
150
  "color": "#EFEFEF",
@@ -151,6 +158,7 @@ exports[`masterCellBuilder when using default options can be invoked as a React
151
158
  <View
152
159
  cellUUID="mocked-uuid"
153
160
  key="2"
161
+ skipButtons={false}
154
162
  style={
155
163
  {
156
164
  "height": 60,
@@ -169,6 +177,7 @@ exports[`masterCellBuilder when using default options can be invoked as a React
169
177
  key="0"
170
178
  label="Summary of the test entry"
171
179
  numberOfLines={2}
180
+ skipButtons={false}
172
181
  style={
173
182
  {
174
183
  "color": "#EFEFEF",
@@ -183,6 +192,7 @@ exports[`masterCellBuilder when using default options can be invoked as a React
183
192
  <View
184
193
  cellUUID="mocked-uuid"
185
194
  key="3"
195
+ skipButtons={false}
186
196
  style={
187
197
  {
188
198
  "height": 42,
@@ -199,6 +209,7 @@ exports[`masterCellBuilder when using default options can be invoked as a React
199
209
  <ImageContainer
200
210
  cellUUID="mocked-uuid"
201
211
  key="0"
212
+ skipButtons={false}
202
213
  style={
203
214
  {
204
215
  "height": 42,
@@ -1,5 +1,4 @@
1
1
  import * as React from "react";
2
- import { View } from "react-native";
3
2
 
4
3
  type ElementProps = {
5
4
  type: string;
@@ -45,21 +44,11 @@ const cellPlayerTypes = new Set([
45
44
  export function elementMapper(
46
45
  components,
47
46
  otherProps?: {
48
- preferredFocus?: boolean;
49
47
  skipButtons?: boolean;
50
48
  cellUUID?: string;
49
+ [key: string]: any;
51
50
  emitAsyncElementRegistrate?: () => void;
52
51
  emitAsyncElementLayout?: () => void;
53
- focused?: boolean;
54
- focusedButtonId?: string;
55
- groupId?: string;
56
- isFocusable?: boolean;
57
- onToggleFocus?: (arg0: unknown) => void;
58
- prefixId?: string;
59
- skipFocusManagerRegistration?: boolean;
60
- component?: ZappUIComponent;
61
- componentId?: string;
62
- wrapperRef: React.RefObject<typeof View>;
63
52
  }
64
53
  ) {
65
54
  return function Element({
@@ -16,13 +16,13 @@ export class MasterCellAsyncRenderManager {
16
16
  }
17
17
 
18
18
  public emitAsyncElementRegistrate = () => {
19
- this.asyncElementRegistrate$.next();
19
+ this.asyncElementRegistrate$.next(null);
20
20
 
21
21
  return this.decrementAsyncElementRegistrationCount;
22
22
  };
23
23
 
24
24
  public emitAsyncElementLayout = () => {
25
- this.asyncElementLayout$.next();
25
+ this.asyncElementLayout$.next(null);
26
26
  };
27
27
 
28
28
  public decrementAsyncElementRegistrationCount = () => {
@@ -11,24 +11,7 @@ import { getEntryState, hasFocusableInsideBuilder } from "./utils";
11
11
 
12
12
  type Props = {
13
13
  item: ZappEntry;
14
- state: null | "selected" | "focused" | "default";
15
- emitAsyncElementRegistrate?: () => void;
16
- emitAsyncElementLayout?: () => void;
17
- skipButtons?: boolean;
18
- cellUUID?: string;
19
- // New props from CellWithFocusable
20
- groupId?: string;
21
- onToggleFocus?: (arg0: unknown) => void;
22
- prefixId?: string;
23
- focusedButtonId?: string;
24
- preferredFocus?: boolean;
25
- skipFocusManagerRegistration?: boolean;
26
- isFocusable?: boolean;
27
- focused?: boolean;
28
- // New props from Gallery
29
- component?: ZappUIComponent;
30
- componentId?: string;
31
- screenId: string;
14
+ state: null | "selected" | "focused";
32
15
  };
33
16
 
34
17
  const defaultContainerStyle = { flex: 1 };
@@ -56,24 +39,6 @@ type MasterCellBuilderOptions = {
56
39
  };
57
40
  };
58
41
 
59
- // retrieve screendata from route
60
- const useMasterCellScreen = (screenId?: string) => {
61
- let _screenId = screenId;
62
- const { screenData } = useRoute();
63
-
64
- if (!screenId) {
65
- // get screenId from targetScreen or screenData
66
- _screenId =
67
- screenData && "targetScreen" in screenData
68
- ? screenData?.targetScreen?.id
69
- : screenData?.id;
70
- }
71
-
72
- const { screen } = useScreenState(_screenId);
73
-
74
- return screen;
75
- };
76
-
77
42
  export function masterCellBuilder({
78
43
  components = defaultComponents,
79
44
  cellConfiguration,
@@ -98,8 +63,15 @@ export function masterCellBuilder({
98
63
  * inflated with the data coming from the data source
99
64
  * @param {"selected" | "focused" | null} props.state state of the component
100
65
  */
101
- function MasterCell({ item, state, screenId, ...otherProps }: Props) {
102
- const screen = useMasterCellScreen(screenId);
66
+ function MasterCell({ item, state, ...otherProps }: Props) {
67
+ const { screenData } = useRoute();
68
+
69
+ const screenId =
70
+ screenData && "targetScreen" in screenData
71
+ ? screenData?.targetScreen?.id
72
+ : screenData?.id;
73
+
74
+ const { screen } = useScreenState(screenId);
103
75
 
104
76
  const selectedState = screen?.selectedEntry;
105
77
 
@@ -125,56 +97,18 @@ export function masterCellBuilder({
125
97
  item?.id,
126
98
  ]);
127
99
 
128
- const remapElements = React.useMemo(
129
- () =>
130
- mapElementWithKey(
131
- elementMapper(componentsMap, {
132
- wrapperRef,
133
- emitAsyncElementRegistrate: otherProps.emitAsyncElementRegistrate,
134
- emitAsyncElementLayout: otherProps.emitAsyncElementLayout,
135
- preferredFocus: otherProps.preferredFocus,
136
- cellUUID: cellUUID || otherProps.cellUUID,
137
- skipButtons: skipButtons || otherProps.skipButtons,
138
- // cellWithFocusable
139
- focused: otherProps.focused,
140
- focusedButtonId: otherProps.focusedButtonId,
141
- groupId: otherProps.groupId,
142
- isFocusable: otherProps.isFocusable,
143
- onToggleFocus: otherProps.onToggleFocus,
144
- prefixId: otherProps.prefixId,
145
- skipFocusManagerRegistration:
146
- otherProps.skipFocusManagerRegistration,
147
- // gallery
148
- component: otherProps.component,
149
- componentId: otherProps.componentId,
150
- })
151
- ),
152
- [
153
- componentsMap,
154
- otherProps.emitAsyncElementRegistrate,
155
- otherProps.emitAsyncElementLayout,
156
- cellUUID || otherProps.cellUUID,
157
- skipButtons || otherProps.skipButtons,
158
- otherProps.focused,
159
- otherProps.focusedButtonId,
160
- otherProps.groupId,
161
- otherProps.isFocusable,
162
- otherProps.onToggleFocus,
163
- otherProps.prefixId,
164
- otherProps.skipFocusManagerRegistration,
165
- otherProps.component,
166
- otherProps.componentId,
167
- ]
168
- );
169
-
170
- const renderElements = React.useMemo(
171
- () => elements.map(remapElements),
172
- [remapElements, elements]
100
+ const remapElements = mapElementWithKey(
101
+ elementMapper(componentsMap, {
102
+ ...otherProps,
103
+ wrapperRef,
104
+ cellUUID,
105
+ skipButtons,
106
+ })
173
107
  );
174
108
 
175
109
  return (
176
110
  <View style={containerStyle} ref={wrapperRef}>
177
- {renderElements}
111
+ {elements.map(remapElements)}
178
112
  </View>
179
113
  );
180
114
  }
@@ -1,5 +1,5 @@
1
1
  import React, { useMemo } from "react";
2
- import { View, StyleSheet, LayoutChangeEvent } from "react-native";
2
+ import { LayoutChangeEvent, StyleSheet, View } from "react-native";
3
3
  import { Title, TitleProps } from "./Title";
4
4
  import { CloseButton, CloseButtonProps } from "./CloseButton";
5
5
  import { getCloseButtonProps, getTitleProps } from "./utils";
@@ -60,8 +60,8 @@ export function ModalHeader(props: Props) {
60
60
  width: width - buttonsContainerWidth,
61
61
  }}
62
62
  >
63
- {title && <Title {...titleProps} />}
64
- {summary && <Title {...summaryProps} />}
63
+ {title ? <Title {...titleProps} /> : null}
64
+ {summary ? <Title {...summaryProps} /> : null}
65
65
  </View>
66
66
  <View style={{ width: buttonsContainerWidth }}>
67
67
  <CloseButton {...closeButtonProps} />