@applicaster/zapp-react-native-ui-components 13.0.0-alpha.8865612391 → 13.0.0-alpha.8865907528

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.
@@ -4,9 +4,11 @@ import { usePrevious } from "@applicaster/zapp-react-native-utils/reactHooks/uti
4
4
  import { toBooleanWithDefaultFalse } from "@applicaster/zapp-react-native-utils/booleanUtils";
5
5
  import { noop } from "@applicaster/zapp-react-native-utils/functionUtils";
6
6
 
7
- type AnimatedInterpolatedStyle =
8
- | Animated.AnimatedInterpolation
9
- | [{ [Key: string]: Animated.AnimatedInterpolation }];
7
+ type AnimatedInterpolatedStyle = any;
8
+
9
+ // type AnimatedInterpolatedStyle =
10
+ // | Animated.AnimatedInterpolation
11
+ // | [{ [Key: string]: Animated.AnimatedInterpolation }];
10
12
 
11
13
  type AnimationConfig = {
12
14
  duration: number;
@@ -1,17 +1,31 @@
1
1
  import * as React from "react";
2
2
  import { FocusableGroupNative } from "@applicaster/zapp-react-native-ui-components/Components/NativeFocusables";
3
3
  import { BaseFocusable } from "@applicaster/zapp-react-native-ui-components/Components/BaseFocusable";
4
-
5
- function noop() {}
4
+ import { createLogger } from "@applicaster/zapp-react-native-utils/logger";
5
+
6
+ const { log_verbose } = createLogger({
7
+ subsystem: "General",
8
+ category: "FocusableGroup",
9
+ });
10
+
11
+ type FocusableGroupNativeEvent = {
12
+ nativeEvent: {
13
+ focusHeading: FocusManager.Direction;
14
+ groupId: string;
15
+ isActive: boolean;
16
+ isFocusDisabled: boolean;
17
+ isFocusingByUser: boolean;
18
+ itemId: string;
19
+ target: number;
20
+ };
21
+ };
6
22
 
7
23
  type Props = {
8
24
  id: string;
9
25
  children: (arg1: boolean) => React.ComponentType<any>;
10
- onWillUpdateFocus: (nativeEvent: any) => void;
11
- onDidUpdateFocus: (nativeEvent: any) => void;
12
26
  isFocusDisabled: boolean;
13
27
  initialItemId: string;
14
- resetFocusToInitialValue: boolean;
28
+ isPreferredFocusDisabled: boolean;
15
29
  focusGroupRef: React.Component;
16
30
  shouldUsePreferredFocus: boolean;
17
31
  groupId: string;
@@ -21,86 +35,27 @@ type Props = {
21
35
  };
22
36
 
23
37
  export class FocusableGroup extends BaseFocusable<Props> {
24
- focusableGroupNativeRef: React.Component | null;
25
- constructor(props) {
26
- super(props);
27
- this.focusableGroupNativeRef = null;
28
-
29
- this.state = {
30
- isActive: false,
31
- };
32
-
33
- this.onWillUpdateFocus = this.onWillUpdateFocus.bind(this);
34
- this.onDidUpdateFocus = this.onDidUpdateFocus.bind(this);
35
- this.isGroup = true;
36
-
37
- this.shouldUsePreferredFocus = this.shouldUsePreferredFocus.bind(this);
38
- this.measureView = this.measureView.bind(this);
39
- }
40
-
41
- connectedScreenId() {
42
- const {
43
- screenData: { screenId, parentScreenId },
44
- } = this.props;
45
-
46
- if (screenId) {
47
- return null;
48
- }
49
-
50
- return parentScreenId ? `${parentScreenId}-${screenId}` : screenId;
51
- }
52
-
53
- /**
54
- * tells whether the group should give focus to a preferred item when it gains focus from another
55
- * group
56
- * @returns {boolean}
57
- */
58
- shouldUsePreferredFocus() {
59
- return this.props.shouldUsePreferredFocus || false;
60
- }
61
-
62
- /**
63
- * indicates whether the underlying component should claim preferred focus
64
- * when navigating into the group of this item
65
- * @returns {boolean}
66
- */
67
- isPreferredFocus() {
68
- return this.props.preferredFocus || false;
69
- }
70
-
71
- onWillUpdateFocus({ nativeEvent }) {
72
- const { isActive } = nativeEvent;
73
- this.setState({ isActive });
74
- const { onWillUpdateFocus = noop } = this.props;
75
- onWillUpdateFocus(nativeEvent);
76
- }
77
-
78
- onDidUpdateFocus({ nativeEvent }) {
79
- const { onDidUpdateFocus = noop } = this.props;
80
- onDidUpdateFocus(nativeEvent);
81
- }
82
-
83
- setFocusDisabled(isFocusDisabled) {
84
- if (this.ref) {
85
- this.ref.current.setNativeProps({
86
- isFocusDisabled,
87
- });
88
- }
89
- }
90
-
91
38
  render() {
92
39
  const {
93
40
  children,
94
41
  id,
95
42
  isFocusDisabled,
96
43
  initialItemId,
97
- resetFocusToInitialValue,
98
- style,
44
+ isPreferredFocusDisabled,
45
+ style = {},
99
46
  groupId,
100
47
  ...otherProps
101
48
  } = this.props;
102
49
 
103
- const focusableStyles = style || {};
50
+ const onGroupFocus = ({ nativeEvent }: FocusableGroupNativeEvent) => {
51
+ log_verbose("FOCUSABLE_GROUP: onGroupFocus", { nativeEvent });
52
+ this.onFocus(this.ref, nativeEvent.focusHeading);
53
+ };
54
+
55
+ const onGroupBlur = ({ nativeEvent }: FocusableGroupNativeEvent) => {
56
+ log_verbose("FOCUSABLE_GROUP: onGroupBlur", { nativeEvent });
57
+ this.onBlur(this.ref, nativeEvent.focusHeading);
58
+ };
104
59
 
105
60
  return (
106
61
  <FocusableGroupNative
@@ -109,15 +64,11 @@ export class FocusableGroup extends BaseFocusable<Props> {
109
64
  ref={this.ref}
110
65
  isFocusDisabled={isFocusDisabled}
111
66
  initialItemId={initialItemId}
112
- resetFocusToInitialValue={resetFocusToInitialValue}
113
- onLayout={this.measureView}
114
- onViewFocus={this.onFocus}
115
- onViewPress={this.onPress}
116
- onViewBlur={this.onBlur}
117
- style={focusableStyles}
67
+ isPreferredFocusDisabled={isPreferredFocusDisabled}
68
+ onGroupFocus={onGroupFocus}
69
+ onGroupBlur={onGroupBlur}
70
+ style={style}
118
71
  {...otherProps}
119
- onWillUpdateFocus={this.onWillUpdateFocus}
120
- onDidUpdateFocus={this.onDidUpdateFocus}
121
72
  >
122
73
  {children}
123
74
  </FocusableGroupNative>
@@ -16,12 +16,10 @@ type Props = {
16
16
  prioritiseFocusOn?: number;
17
17
  groupId?: string;
18
18
  hasTVPreferredFocus?: boolean;
19
- resetFocusToInitialValue?: boolean;
19
+ isPreferredFocusDisabled?: boolean;
20
20
  onFocus?: () => void;
21
21
  onBlur?: () => void;
22
- onWillUpdateFocus?: (event?: any) => void;
23
22
  willReceiveFocus?: (event?: any) => void;
24
- onDidUpdateFocus?: (event?: any) => void;
25
23
  hasLostFocus?: any;
26
24
  nextFocusUp?: any;
27
25
  nextFocusDown?: any;
@@ -55,8 +55,11 @@ export const ScreenContainer = React.memo(function ScreenContainer({
55
55
  ComponentsExtraProps,
56
56
  NavBar,
57
57
  }: Props) {
58
- const { screenMarginBottom } = React.useContext(ScreenLayoutContext);
58
+ const { screenMarginBottom, resetScreenLayout } =
59
+ React.useContext(ScreenLayoutContext);
60
+
59
61
  const { currentRoute, screenData } = useNavigation();
62
+
60
63
  const screen = useCurrentScreenData();
61
64
 
62
65
  const { marginBottom, backgroundColor, paddingTop, paddingBottom } =
@@ -65,6 +68,10 @@ export const ScreenContainer = React.memo(function ScreenContainer({
65
68
  const theme = useTheme();
66
69
  const getThemeValue = React.useCallback(R.propOr(0, R.__, theme), [theme]);
67
70
 
71
+ React.useEffect(() => {
72
+ resetScreenLayout();
73
+ }, [currentRoute]);
74
+
68
75
  const fullscreen = React.useCallback(
69
76
  displayFullScreen({ currentRoute, screenData }),
70
77
  [currentRoute, screenData]
@@ -60,10 +60,15 @@ export function ScreenLayoutContextProvider(props: {
60
60
  initialState
61
61
  );
62
62
 
63
+ const resetScreenLayout = React.useCallback(() => {
64
+ setScreenLayout(initialState);
65
+ }, [initialState]);
66
+
63
67
  const screenLayoutValue = React.useMemo(
64
68
  () => ({
65
69
  ...screenLayout,
66
70
  setScreenLayout,
71
+ resetScreenLayout,
67
72
  }),
68
73
  [screenLayout]
69
74
  );
@@ -56,11 +56,12 @@ describe("ScreenContainer", () => {
56
56
  };
57
57
 
58
58
  const setScreenLayout = () => {};
59
+ const resetScreenLayout = () => {};
59
60
 
60
61
  const wrapper = ({ children }) => (
61
62
  <WrappedWithProviders>
62
63
  <ScreenLayoutContext.Provider
63
- value={{ ...screenLayout, setScreenLayout }}
64
+ value={{ ...screenLayout, setScreenLayout, resetScreenLayout }}
64
65
  >
65
66
  <ScreenContainer NavBar={View}>{children}</ScreenContainer>
66
67
  </ScreenLayoutContext.Provider>
@@ -73,7 +73,6 @@ export function elementMapper(
73
73
  : {};
74
74
 
75
75
  const componentProps = {
76
- key,
77
76
  style,
78
77
  skipButtons: otherProps?.skipButtons,
79
78
  emitAsyncElementRegistrate: otherProps?.emitAsyncElementRegistrate,
@@ -91,7 +90,7 @@ export function elementMapper(
91
90
  const fn = mapElementWithKey(elementMapper(components, otherProps));
92
91
 
93
92
  return (
94
- <Component {...componentProps}>
93
+ <Component key={key} {...componentProps}>
95
94
  {focusableTypes.has(type) && elements.length > 0
96
95
  ? elements.map(fn)
97
96
  : null}
@@ -0,0 +1,133 @@
1
+ import { playerManager } from "@applicaster/zapp-react-native-utils/appUtils";
2
+ import { StorageSingleValueProvider } from "@applicaster/zapp-react-native-bridge/ZappStorage/StorageSingleSelectProvider";
3
+ import { PushTopicManager } from "@applicaster/zapp-react-native-bridge/PushNotifications/PushTopicManager";
4
+ import { StorageMultiSelectProvider } from "@applicaster/zapp-react-native-bridge/ZappStorage/StorageMultiSelectProvider";
5
+ import React, { useEffect } from "react";
6
+ import { usePlayer } from "@applicaster/zapp-react-native-utils/appUtils/playerManager/usePlayer";
7
+ import { BehaviorSubject } from "rxjs";
8
+ import { masterCellLogger } from "../logger";
9
+
10
+ const parseContextKey = (key: string): string | null => {
11
+ if (!key?.startsWith("@{ctx/")) return null;
12
+
13
+ return key.substring("@{ctx/".length, key.length - 1);
14
+ };
15
+
16
+ const getDataSourceProvider = (
17
+ behavior: Behavior
18
+ ): BehaviorSubject<string[] | string> | null => {
19
+ if (!behavior) return null;
20
+
21
+ const selection = String(behavior.current_selection);
22
+ const contextKey = parseContextKey(selection);
23
+
24
+ if (contextKey) {
25
+ if (behavior.select_mode === "multi") {
26
+ return StorageMultiSelectProvider.getProvider(contextKey).getObservable();
27
+ }
28
+
29
+ if (behavior.select_mode === "single") {
30
+ return StorageSingleValueProvider.getProvider(contextKey).getObservable();
31
+ }
32
+ }
33
+
34
+ if (behavior.selection_source === "@{push/topics}") {
35
+ return PushTopicManager.getInstance().getEntryObservable();
36
+ }
37
+
38
+ return null;
39
+ };
40
+
41
+ export const useBehaviorUpdate = (behavior: Behavior) => {
42
+ const [lastUpdate, setLastUpdate] = React.useState<number | null>(null);
43
+ const player = usePlayer();
44
+
45
+ const triggerUpdate = () => setLastUpdate(Date.now());
46
+
47
+ useEffect(() => {
48
+ if (!behavior) return;
49
+
50
+ const dataSource = getDataSourceProvider(behavior);
51
+
52
+ if (dataSource) {
53
+ const subscription = dataSource.subscribe(triggerUpdate);
54
+
55
+ return () => subscription.unsubscribe();
56
+ }
57
+ }, [behavior]);
58
+
59
+ useEffect(() => {
60
+ if (!behavior || !player || behavior.selection_source !== "now_playing") {
61
+ return;
62
+ }
63
+
64
+ const subscription = player.getEntryObservable().subscribe(triggerUpdate);
65
+
66
+ return () => subscription.unsubscribe();
67
+ }, [behavior, player]);
68
+
69
+ return lastUpdate;
70
+ };
71
+
72
+ // We cant use async in this function (its inside render),
73
+ // so we rely on useBehaviorUpdate to update current value and trigger re-render
74
+ export const isCellSelected = (
75
+ id: string | number,
76
+ behavior?: Behavior
77
+ ): boolean => {
78
+ if (!behavior) return false;
79
+
80
+ if (behavior.selection_source === "now_playing") {
81
+ const player = playerManager.getActivePlayer();
82
+
83
+ return player?.entry?.id === id;
84
+ }
85
+
86
+ if (behavior.selection_source === "@{push/topics}") {
87
+ if (behavior.select_mode === "single") {
88
+ masterCellLogger.warning(
89
+ "Unexpected single selection mode for push topics"
90
+ );
91
+ }
92
+
93
+ const tags = PushTopicManager.getInstance().getRegisteredTags();
94
+
95
+ return tags.includes(String(id));
96
+ }
97
+
98
+ const selection = String(behavior.current_selection);
99
+ const contextKey = parseContextKey(selection);
100
+
101
+ if (contextKey) {
102
+ if (behavior.select_mode === "single") {
103
+ const selectedItem =
104
+ StorageSingleValueProvider.getProvider(contextKey)?.getValue();
105
+
106
+ return selectedItem === String(id);
107
+ }
108
+
109
+ if (behavior.select_mode === "multi") {
110
+ const selectedItems =
111
+ StorageMultiSelectProvider.getProvider(contextKey)?.getSelectedItems();
112
+
113
+ return selectedItems?.includes(String(id));
114
+ }
115
+ }
116
+
117
+ if (behavior.select_mode === "single") {
118
+ return behavior.current_selection === id;
119
+ }
120
+
121
+ if (
122
+ behavior.select_mode === "multi" &&
123
+ Array.isArray(behavior.current_selection)
124
+ ) {
125
+ const currentSelection: string[] = behavior.current_selection.map(
126
+ (item): string => String(item)
127
+ );
128
+
129
+ return currentSelection.includes(String(id));
130
+ }
131
+
132
+ return false;
133
+ };
@@ -1,16 +1,13 @@
1
- import React, { useEffect, useMemo } from "react";
1
+ import React, { useMemo } from "react";
2
2
  import * as R from "ramda";
3
3
  import validateColor from "validate-color";
4
4
  import { platformSelect } from "@applicaster/zapp-react-native-utils/reactUtils";
5
5
  import { useActions } from "@applicaster/zapp-react-native-utils/reactHooks/actions";
6
6
 
7
7
  import { masterCellLogger } from "../logger";
8
- import { playerManager } from "@applicaster/zapp-react-native-utils/appUtils";
9
- import { usePlayer } from "@applicaster/zapp-react-native-utils/appUtils/playerManager/usePlayer";
10
- import { PushTopicManager } from "@applicaster/zapp-react-native-bridge/PushNotifications/PushTopicManager";
11
- import { StorageMultiSelectProvider } from "@applicaster/zapp-react-native-bridge/ZappStorage/StorageMultiSelectProvider";
12
8
  import { getCellState } from "../../Cell/utils";
13
9
  import { getColorFromData } from "@applicaster/zapp-react-native-utils/cellUtils";
10
+ import { isCellSelected, useBehaviorUpdate } from "./behaviorProvider";
14
11
 
15
12
  const hasElementSpecificViewType = (viewType) => (element) => {
16
13
  if (R.isNil(element)) {
@@ -194,130 +191,7 @@ export const getFocusedButtonId = (focusable) => {
194
191
  };
195
192
 
196
193
  export const isSelected = (id: string | number, behavior?: Behavior) => {
197
- if (!behavior) {
198
- return false;
199
- }
200
-
201
- if (behavior?.selection_source === "now_playing") {
202
- const player = playerManager.getActivePlayer();
203
-
204
- if (player?.entry?.id === id) {
205
- return true;
206
- }
207
- }
208
-
209
- if (behavior?.select_mode === "single") {
210
- return behavior.current_selection === id;
211
- }
212
-
213
- if (behavior?.select_mode === "multi") {
214
- // TODO: Use generic resolver source
215
-
216
- if (behavior.selection_source === "@{push/topics}") {
217
- const tags = PushTopicManager.getInstance().getRegisteredTags();
218
-
219
- return tags.includes(String(id));
220
- }
221
-
222
- if (Array.isArray(behavior.current_selection)) {
223
- return behavior.current_selection.includes(id);
224
- }
225
-
226
- const currentSelection = String(behavior.current_selection);
227
-
228
- if (currentSelection?.startsWith("@{ctx/")) {
229
- const keyWithoutCtx = currentSelection.substring(
230
- "@{ctx/".length,
231
- currentSelection.length - 1
232
- );
233
-
234
- const selectedItems =
235
- StorageMultiSelectProvider.getProvider(
236
- keyWithoutCtx
237
- )?.getSelectedItems();
238
-
239
- return selectedItems?.includes(String(id));
240
- }
241
- }
242
-
243
- return false;
244
- };
245
-
246
- export const useBehaviorUpdate = (behavior: Behavior) => {
247
- const [lastUpdate, setLastUpdate] = React.useState(null);
248
-
249
- const player = usePlayer();
250
-
251
- const triggerUpdate = () => {
252
- setLastUpdate(Date.now());
253
- };
254
-
255
- // TODO: Create generic RX to state update
256
- useEffect(() => {
257
- // TODO: Use generic resolver source
258
- if (!behavior) {
259
- return;
260
- }
261
-
262
- const currentSelection = String(behavior.current_selection);
263
-
264
- if (currentSelection?.startsWith("@{ctx/")) {
265
- const keyWithoutCtx = currentSelection.substring(
266
- "@{ctx/".length,
267
- currentSelection.length - 1
268
- );
269
-
270
- if (keyWithoutCtx) {
271
- const subscription = StorageMultiSelectProvider.getProvider(
272
- keyWithoutCtx
273
- )
274
- .getObservable()
275
- .subscribe(() => {
276
- triggerUpdate();
277
- });
278
-
279
- return () => {
280
- subscription.unsubscribe();
281
- };
282
- }
283
- }
284
- }, [behavior]);
285
-
286
- useEffect(() => {
287
- if (!behavior) {
288
- return;
289
- }
290
-
291
- if (behavior?.selection_source === "@{push/topics}") {
292
- const subscription = PushTopicManager.getInstance()
293
- .getEntryObservable()
294
- .subscribe(() => {
295
- triggerUpdate();
296
- });
297
-
298
- return () => {
299
- subscription.unsubscribe();
300
- };
301
- }
302
- }, [behavior]);
303
-
304
- useEffect(() => {
305
- if (!behavior) {
306
- return;
307
- }
308
-
309
- if (behavior?.selection_source === "now_playing" && player) {
310
- const subscription = player.getEntryObservable().subscribe(() => {
311
- triggerUpdate();
312
- });
313
-
314
- return () => {
315
- subscription.unsubscribe();
316
- };
317
- }
318
- }, [behavior, player]);
319
-
320
- return lastUpdate;
194
+ return isCellSelected(id, behavior);
321
195
  };
322
196
 
323
197
  export const useCellState = ({
@@ -279,7 +279,6 @@ function ComponentsMapComponent(props: Props) {
279
279
  initialNumToRender={3}
280
280
  maxToRenderPerBatch={10}
281
281
  windowSize={12}
282
- listKey={riverId}
283
282
  keyExtractor={keyExtractor}
284
283
  renderItem={renderRiverItem}
285
284
  data={riverComponents}
@@ -12,6 +12,7 @@ type TrackedViewProps = {
12
12
  onPositionUpdated: (props: { rect?: Record<string, number> }) => void;
13
13
  testId?: string | undefined;
14
14
  groupId?: string;
15
+ clipThreshold?: number;
15
16
  };
16
17
 
17
18
  export const TrackedView = memo(function TrackedView(props: TrackedViewProps) {
@@ -2,21 +2,21 @@ import { Animated } from "react-native";
2
2
 
3
3
  import { NAV_ACTION_PUSH, NAV_ACTION_BACK } from "./Transitioner";
4
4
 
5
- type TransitionConfig = {
6
- duration: number;
7
- easing: any;
8
- from: {
9
- style: any;
10
- };
11
- to: {
12
- style: any;
13
- };
14
- };
5
+ // type TransitionConfig = {
6
+ // duration: number;
7
+ // easing: any;
8
+ // from: {
9
+ // style: any;
10
+ // };
11
+ // to: {
12
+ // style: any;
13
+ // };
14
+ // };
15
15
 
16
- type Props = {
17
- transitionConfig: TransitionConfig;
18
- contentStyle: { [string]: any };
19
- };
16
+ // type Props = {
17
+ // transitionConfig: TransitionConfig;
18
+ // contentStyle: { [string]: any };
19
+ // };
20
20
 
21
21
  /**
22
22
  * Manages animation for the Transitioner,
@@ -25,7 +25,7 @@ type Props = {
25
25
  * which must have a proper structure, see types above ^.
26
26
  */
27
27
  export class AnimationManager {
28
- constructor(props: Props) {
28
+ constructor(props) {
29
29
  this.animatedValue = new Animated.Value(0.0);
30
30
 
31
31
  this.config = props.transitionConfig(
@@ -32,6 +32,9 @@ const { log_error, log_debug } = loggerLiveImageComponent;
32
32
  const isMeasurement = (item: ZappEntry) =>
33
33
  isString(item.id) && item.id.startsWith("pre-measurement-");
34
34
 
35
+ // Pixels by which the view can slightly extend outside the viewport and still be considered fully visible.
36
+ const CLIP_THRESHOLD = 10;
37
+
35
38
  type Props = {
36
39
  item: ZappEntry;
37
40
  style: Record<string, any>;
@@ -328,6 +331,7 @@ const PlayerLiveImageComponent = (props: Props) => {
328
331
  <TrackedView
329
332
  testId={`tracked-view-${playerId}-${item.title}`}
330
333
  onPositionUpdated={onPositionUpdated}
334
+ clipThreshold={CLIP_THRESHOLD}
331
335
  >
332
336
  <View ref={trackViewRef}>
333
337
  {isVideoMode ? (
@@ -2,6 +2,7 @@
2
2
 
3
3
  exports[`PlayerLiveImageComponent should render correctly with default props 1`] = `
4
4
  <TrackedView
5
+ clipThreshold={10}
5
6
  onPositionUpdated={[Function]}
6
7
  testId="tracked-view-player1-Test"
7
8
  >
@@ -1,8 +1,8 @@
1
1
  import { Animated, Easing, EasingFunction, StyleProp } from "react-native";
2
2
 
3
3
  type AnimatedInterpolatedStyle =
4
- | Animated.AnimatedInterpolation
5
- | [{ [Key: string]: Animated.AnimatedInterpolation }];
4
+ | Animated.AnimatedInterpolation<number>
5
+ | [{ [Key: string]: Animated.AnimatedInterpolation<number> }];
6
6
 
7
7
  type AnimationConfig = {
8
8
  duration: number;
@@ -31,7 +31,7 @@ const interpolate = (
31
31
  animatedValue: Animated.Value,
32
32
  from: number = 0,
33
33
  to: number = 1
34
- ): Animated.AnimatedInterpolation =>
34
+ ): Animated.AnimatedInterpolation<number> =>
35
35
  animatedValue.interpolate({
36
36
  inputRange: [0, 1],
37
37
  outputRange: [from, to],
@@ -7,17 +7,11 @@ export const withFocusableContext = (Component) => {
7
7
  { groupId, ...props }: Record<string, any>,
8
8
  ref
9
9
  ) => {
10
- return (
11
- <FocusableGroupContext.Consumer>
12
- {(groupIdContext: string) => {
13
- // eslint-disable-next-line react/display-name
14
- const propsGroupId = groupId || null;
15
- const providedGroupId = propsGroupId || groupIdContext;
10
+ const groupIdContext = React.useContext(FocusableGroupContext);
11
+ const propsGroupId = groupId || null;
12
+ const providedGroupId = propsGroupId || groupIdContext;
16
13
 
17
- return <Component {...props} groupId={providedGroupId} ref={ref} />;
18
- }}
19
- </FocusableGroupContext.Consumer>
20
- );
14
+ return <Component {...props} groupId={providedGroupId} ref={ref} />;
21
15
  };
22
16
 
23
17
  return React.forwardRef(WithFocusableContext);
@@ -13,7 +13,7 @@ type ProviderProps = {
13
13
  };
14
14
  };
15
15
 
16
- const initialHeaderOffsetContext = () => ({
16
+ const initialHeaderOffsetContext: HeaderOffsetContextType = () => ({
17
17
  headerOffset: 0,
18
18
  headersAbove: null,
19
19
  });
@@ -24,11 +24,9 @@ export const HeaderOffsetContext = React.createContext<HeaderOffsetContextType>(
24
24
 
25
25
  export function withConsumer(Component) {
26
26
  return function WithConsumer(props) {
27
- return (
28
- <HeaderOffsetContext.Consumer>
29
- {(context) => <Component getHeaderOffset={context} {...props} />}
30
- </HeaderOffsetContext.Consumer>
31
- );
27
+ const getHeaderOffset = React.useContext(HeaderOffsetContext);
28
+
29
+ return <Component getHeaderOffset={getHeaderOffset} {...props} />;
32
30
  };
33
31
  }
34
32
 
@@ -176,15 +176,8 @@ export function ScreenContextProvider({
176
176
 
177
177
  export function withScreenContext(Component: React.ComponentType<any>) {
178
178
  return function WithScreenContextWrapper(props) {
179
- return (
180
- <ScreenContext.Consumer>
181
- {useCallback(
182
- (value) => (
183
- <Component {...props} screenContext={value} />
184
- ),
185
- [Component, props]
186
- )}
187
- </ScreenContext.Consumer>
188
- );
179
+ const screenContext = React.useContext(ScreenContext);
180
+
181
+ return <Component {...props} screenContext={screenContext} />;
189
182
  };
190
183
  }
@@ -8,10 +8,11 @@ type LayoutContext = {
8
8
  componentAnchorPointY: number | null;
9
9
  componentAvailableWidth: number | null;
10
10
  setScreenLayout?: (properties: {}) => void;
11
+ resetScreenLayout?: () => void;
11
12
  extraAnchorPointYOffset: number;
12
13
  };
13
14
 
14
- const screenLayoutContext: LayoutContext = {
15
+ const initialScreenLayoutContext: LayoutContext = {
15
16
  screenMarginTop: null,
16
17
  screenMarginBottom: null,
17
18
  screenHeight: null,
@@ -21,8 +22,9 @@ const screenLayoutContext: LayoutContext = {
21
22
  extraAnchorPointYOffset: 0,
22
23
  };
23
24
 
24
- export const ScreenLayoutContext =
25
- React.createContext<LayoutContext>(screenLayoutContext);
25
+ export const ScreenLayoutContext = React.createContext<LayoutContext>(
26
+ initialScreenLayoutContext
27
+ );
26
28
 
27
29
  export function ScreenLayoutContextConsumer(Component) {
28
30
  return function WithConsumer(props) {
@@ -21,13 +21,13 @@ const prepareConfiguration = (
21
21
  keys: [string]
22
22
  ) => R.compose(R.evolve(keysMap), R.pickAll(keys))(configuration);
23
23
 
24
+ const configurationKeys = R.keys(keysMap);
25
+
24
26
  export function withConfigurationProvider(Component: React.ComponentType<any>) {
25
27
  return function WithConfigurationProvider(props: Props) {
26
28
  const styles = props?.screenData?.styles;
27
29
  const general = props?.screenData?.general;
28
30
 
29
- const configurationKeys = React.useMemo(() => R.keys(keysMap), []);
30
-
31
31
  const configuration = React.useMemo(
32
32
  () => prepareConfiguration({ ...general, ...styles }, configurationKeys),
33
33
  []
@@ -1,12 +1,10 @@
1
1
  /// <reference types="@applicaster/applicaster-types" />
2
2
  /// <reference types="@applicaster/zapp-react-native-ui-components" />
3
3
  import React, { useEffect, useMemo } from "react";
4
- import { localStorage } from "@applicaster/zapp-react-native-bridge/ZappStorage/LocalStorage";
5
4
 
6
5
  import * as R from "ramda";
7
6
  import { Platform } from "react-native";
8
7
  import Url from "url";
9
- import { ENDPOINT_TAGS } from "@applicaster/zapp-react-native-utils/types";
10
8
  import { favoritesListener } from "@applicaster/zapp-react-native-bridge/Favorites";
11
9
  import { usePickFromState } from "@applicaster/zapp-react-native-redux/hooks";
12
10
  import { useRoute } from "@applicaster/zapp-react-native-utils/reactHooks/navigation";
@@ -21,12 +19,7 @@ import { ZappPipesSearchContext } from "@applicaster/zapp-react-native-ui-compon
21
19
  import { useScreenContext } from "@applicaster/zapp-react-native-utils/reactHooks/screen/useScreenContext";
22
20
 
23
21
  import { isVerticalListOrGrid } from "./utils";
24
- import { appStore } from "@applicaster/zapp-react-native-redux/AppStore";
25
- import {
26
- findEndpointForURL,
27
- HTTP_METHODS,
28
- } from "@applicaster/zapp-pipes-v2-client";
29
- import { getNamespaceAndKey } from "@applicaster/zapp-react-native-utils/appUtils/contextKeysManager/utils";
22
+ import { subscribeForUrlContextKeyChanges } from "@applicaster/zapp-pipes-v2-client";
30
23
 
31
24
  type Props = {
32
25
  component: ZappUIComponent;
@@ -300,27 +293,7 @@ export function zappPipesDataConnector(
300
293
  return addListener(reloadData);
301
294
  }
302
295
  } else {
303
- const pipesEndpoints = appStore.get("pipesEndpoints");
304
-
305
- const endpointURL = findEndpointForURL(
306
- dataSourceUrl,
307
- pipesEndpoints,
308
- HTTP_METHODS.GET
309
- );
310
-
311
- const endpoint = pipesEndpoints?.[endpointURL];
312
-
313
- if (endpoint?.tags?.includes(ENDPOINT_TAGS.observe_storage)) {
314
- const subscriptions: (() => void)[] = endpoint.context_obj.map(
315
- (data: Record<string, any>) => {
316
- const { namespace, key } = getNamespaceAndKey(data.key);
317
-
318
- return localStorage.addListener({ key, namespace }, reloadData);
319
- }
320
- );
321
-
322
- return () => subscriptions.forEach((listener) => listener());
323
- }
296
+ return subscribeForUrlContextKeyChanges(dataSourceUrl, {}, reloadData);
324
297
  }
325
298
  }, [dataSourceUrl, reloadData]);
326
299
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@applicaster/zapp-react-native-ui-components",
3
- "version": "13.0.0-alpha.8865612391",
3
+ "version": "13.0.0-alpha.8865907528",
4
4
  "description": "Applicaster Zapp React Native ui components for the Quick Brick App",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -29,15 +29,13 @@
29
29
  },
30
30
  "homepage": "https://github.com/applicaster/quickbrick#readme",
31
31
  "devDependencies": {
32
- "@types/react": "17.0.2",
33
- "@types/react-native": "0.69.6",
34
32
  "redux-mock-store": "^1.5.3"
35
33
  },
36
34
  "dependencies": {
37
- "@applicaster/applicaster-types": "13.0.0-alpha.8865612391",
38
- "@applicaster/zapp-react-native-bridge": "13.0.0-alpha.8865612391",
39
- "@applicaster/zapp-react-native-redux": "13.0.0-alpha.8865612391",
40
- "@applicaster/zapp-react-native-utils": "13.0.0-alpha.8865612391",
35
+ "@applicaster/applicaster-types": "13.0.0-alpha.8865907528",
36
+ "@applicaster/zapp-react-native-bridge": "13.0.0-alpha.8865907528",
37
+ "@applicaster/zapp-react-native-redux": "13.0.0-alpha.8865907528",
38
+ "@applicaster/zapp-react-native-utils": "13.0.0-alpha.8865907528",
41
39
  "promise": "^8.3.0",
42
40
  "react-router-native": "^5.1.2",
43
41
  "url": "^0.11.0",
@@ -46,7 +44,6 @@
46
44
  "peerDependencies": {
47
45
  "@applicaster/zapp-pipes-v2-client": "*",
48
46
  "@react-native-community/netinfo": "*",
49
- "@types/node": "*",
50
47
  "immer": "*",
51
48
  "react": "*",
52
49
  "react-native": "*",
package/tsconfig.json CHANGED
@@ -2,8 +2,7 @@
2
2
  "extends": "../../tsconfig.base.json",
3
3
  "compilerOptions": {
4
4
  "outDir": "./lib",
5
- "rootDir": "./src"
5
+ "rootDir": "./"
6
6
  },
7
- "exclude": ["src/**/*.test.tsx"],
8
- "include": ["src/**/*"]
7
+ "include": ["**/*"],
9
8
  }
@@ -1,126 +0,0 @@
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
- }