@applicaster/zapp-react-native-ui-components 15.0.0-alpha.7591121530 → 15.0.0-alpha.7607942912

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 (54) hide show
  1. package/Components/BaseFocusable/index.ios.ts +12 -2
  2. package/Components/Cell/Cell.tsx +8 -3
  3. package/Components/Cell/FocusableWrapper.tsx +3 -0
  4. package/Components/Cell/TvOSCellComponent.tsx +26 -5
  5. package/Components/Focusable/FocusableTvOS.tsx +12 -2
  6. package/Components/Focusable/__tests__/__snapshots__/FocusableTvOS.test.tsx.snap +1 -0
  7. package/Components/FocusableGroup/FocusableTvOS.tsx +11 -0
  8. package/Components/HandlePlayable/HandlePlayable.tsx +10 -7
  9. package/Components/Layout/TV/LayoutBackground.tsx +5 -2
  10. package/Components/Layout/TV/ScreenContainer.tsx +2 -6
  11. package/Components/Layout/TV/index.tsx +3 -4
  12. package/Components/Layout/TV/index.web.tsx +3 -4
  13. package/Components/LinkHandler/LinkHandler.tsx +2 -2
  14. package/Components/MasterCell/DefaultComponents/BorderContainerView/__tests__/index.test.tsx +16 -1
  15. package/Components/MasterCell/DefaultComponents/BorderContainerView/index.tsx +30 -2
  16. package/Components/MasterCell/DefaultComponents/Image/Image.android.tsx +5 -1
  17. package/Components/MasterCell/DefaultComponents/Image/Image.ios.tsx +11 -3
  18. package/Components/MasterCell/DefaultComponents/Image/Image.web.tsx +9 -1
  19. package/Components/MasterCell/DefaultComponents/Image/hooks/useImage.ts +15 -14
  20. package/Components/MasterCell/DefaultComponents/LiveImage/index.tsx +10 -6
  21. package/Components/MasterCell/DefaultComponents/Text/index.tsx +8 -8
  22. package/Components/MasterCell/index.tsx +2 -0
  23. package/Components/MasterCell/utils/__tests__/resolveColor.test.js +82 -3
  24. package/Components/MasterCell/utils/index.ts +61 -31
  25. package/Components/MeasurmentsPortal/MeasurementsPortal.tsx +102 -87
  26. package/Components/MeasurmentsPortal/__tests__/MeasurementsPortal.test.tsx +355 -0
  27. package/Components/OfflineHandler/NotificationView/NotificationView.tsx +2 -2
  28. package/Components/OfflineHandler/NotificationView/__tests__/index.test.tsx +17 -18
  29. package/Components/OfflineHandler/__tests__/index.test.tsx +27 -18
  30. package/Components/PlayerContainer/PlayerContainer.tsx +4 -3
  31. package/Components/Screen/TV/index.web.tsx +4 -2
  32. package/Components/Screen/__tests__/Screen.test.tsx +65 -42
  33. package/Components/Screen/__tests__/__snapshots__/Screen.test.tsx.snap +68 -44
  34. package/Components/Screen/hooks.ts +2 -3
  35. package/Components/Screen/index.tsx +2 -3
  36. package/Components/Screen/navigationHandler.ts +49 -24
  37. package/Components/Screen/orientationHandler.ts +3 -3
  38. package/Components/ScreenResolver/index.tsx +13 -7
  39. package/Components/ScreenRevealManager/ScreenRevealManager.ts +40 -8
  40. package/Components/ScreenRevealManager/__tests__/ScreenRevealManager.test.ts +86 -69
  41. package/Components/Tabs/TV/Tabs.tsx +20 -3
  42. package/Components/Transitioner/Scene.tsx +15 -2
  43. package/Components/Transitioner/index.js +3 -3
  44. package/Components/VideoModal/ModalAnimation/ModalAnimationContext.tsx +124 -27
  45. package/Components/VideoModal/VideoModal.tsx +1 -5
  46. package/Components/VideoModal/utils.ts +19 -9
  47. package/Decorators/Analytics/index.tsx +6 -5
  48. package/Decorators/ZappPipesDataConnector/index.tsx +2 -2
  49. package/Decorators/ZappPipesDataConnector/resolvers/StaticFeedResolver.tsx +1 -1
  50. package/Helpers/DataSourceHelper/__tests__/itemLimitForData.test.ts +80 -0
  51. package/Helpers/DataSourceHelper/index.ts +19 -0
  52. package/index.d.ts +7 -0
  53. package/package.json +6 -5
  54. package/Helpers/DataSourceHelper/index.js +0 -19
@@ -22,6 +22,7 @@ type Props = {
22
22
  onFocus?: FocusManager.FocusEventCB;
23
23
  onBlur?: FocusManager.FocusEventCB;
24
24
  selected?: boolean;
25
+ skipFocusManagerRegistration?: boolean;
25
26
  };
26
27
 
27
28
  export class BaseFocusable<
@@ -61,10 +62,14 @@ export class BaseFocusable<
61
62
  }
62
63
 
63
64
  componentDidMount() {
64
- const { id } = this.props;
65
+ const { id, skipFocusManagerRegistration } = this.props;
65
66
  const component = this;
66
67
  this.node = this.ref.current;
67
68
 
69
+ if (skipFocusManagerRegistration) {
70
+ return;
71
+ }
72
+
68
73
  focusManager.register({
69
74
  id,
70
75
  component: component,
@@ -118,7 +123,12 @@ export class BaseFocusable<
118
123
 
119
124
  componentWillUnmount() {
120
125
  this._isMounted = false;
121
- const { id } = this.props;
126
+ const { id, skipFocusManagerRegistration } = this.props;
127
+
128
+ if (skipFocusManagerRegistration) {
129
+ return;
130
+ }
131
+
122
132
  focusManager.unregister(id, { group: this.isGroup || false });
123
133
  }
124
134
 
@@ -208,14 +208,14 @@ export class CellComponent extends React.Component<Props, State> {
208
208
  this.accessibilityManager.readText({
209
209
  text: " ",
210
210
  });
211
- } else {
211
+ } else if (this.state.cellFocused) {
212
212
  this.accessibilityManager.readText({
213
213
  text: `${positionLabel}`,
214
214
  });
215
215
  }
216
216
  }
217
217
 
218
- componentDidUpdate(prevProps: Readonly<Props>) {
218
+ componentDidUpdate(prevProps: Readonly<Props>, prevState: Readonly<State>) {
219
219
  if (prevProps.item !== this.props.item) {
220
220
  this.setState({
221
221
  hasFocusableInside: this.props.CellRenderer.hasFocusableInside?.(
@@ -224,7 +224,12 @@ export class CellComponent extends React.Component<Props, State> {
224
224
  });
225
225
  }
226
226
 
227
- this.handleAccessibilityFocus(this.props.index, this.props.dataLength);
227
+ if (
228
+ prevState.cellFocused !== this.state.cellFocused ||
229
+ this.state.hasFocusableInside
230
+ ) {
231
+ this.handleAccessibilityFocus(this.props.index, this.props.dataLength);
232
+ }
228
233
  }
229
234
 
230
235
  render() {
@@ -10,6 +10,7 @@ type Props = {
10
10
  children: (focused: boolean) => React.ReactNode;
11
11
  onFocus: (arg1: any, index?: number) => void;
12
12
  onBlur: Callback;
13
+ skipFocusManagerRegistration?: boolean;
13
14
  };
14
15
 
15
16
  export const FocusableWrapper = ({
@@ -20,6 +21,7 @@ export const FocusableWrapper = ({
20
21
  applyWrapper,
21
22
  onFocus,
22
23
  onBlur,
24
+ skipFocusManagerRegistration,
23
25
  }: Props) => {
24
26
  if (applyWrapper) {
25
27
  return (
@@ -34,6 +36,7 @@ export const FocusableWrapper = ({
34
36
  // @ts-ignore
35
37
  offsetUpdater={noop}
36
38
  isFocusable
39
+ skipFocusManagerRegistration={skipFocusManagerRegistration}
37
40
  >
38
41
  {(focused) => children(focused)}
39
42
  </Focusable>
@@ -17,6 +17,7 @@ import { CellWithFocusable } from "./CellWithFocusable";
17
17
  import { FocusableWrapper } from "./FocusableWrapper";
18
18
 
19
19
  import { focusableButtonsRegistration$ } from "@applicaster/zapp-react-native-utils/appUtils/focusManagerAux/utils/utils.ios";
20
+ import { toNumberWithDefaultZero } from "@applicaster/zapp-react-native-utils/numberUtils";
20
21
 
21
22
  type Props = {
22
23
  item: ZappEntry;
@@ -37,6 +38,10 @@ type Props = {
37
38
  component: {
38
39
  id: number | string;
39
40
  component_type: string;
41
+ styles?: {
42
+ component_margin_top?: number;
43
+ component_padding_top?: number;
44
+ };
40
45
  };
41
46
  selected: boolean;
42
47
  CellRenderer: React.FunctionComponent<any> & {
@@ -75,6 +80,7 @@ type Props = {
75
80
  componentsMapOffset: number;
76
81
  applyFocusableWrapper: boolean;
77
82
  hasFocusableInside: boolean;
83
+ skipFocusManagerRegistration?: boolean;
78
84
  };
79
85
 
80
86
  type State = {
@@ -201,14 +207,25 @@ class TvOSCell extends React.Component<Props, State> {
201
207
  ) {
202
208
  const { headerOffset } = getHeaderOffset();
203
209
 
204
- const extraAnchorPointYOffset =
205
- screenLayout?.extraAnchorPointYOffset || 0;
210
+ const extraAnchorPointYOffset = toNumberWithDefaultZero(
211
+ screenLayout?.extraAnchorPointYOffset
212
+ );
213
+
214
+ const componentMarginTop = toNumberWithDefaultZero(
215
+ component?.styles?.component_margin_top
216
+ );
217
+
218
+ const componentPaddingTop = toNumberWithDefaultZero(
219
+ component?.styles?.component_padding_top
220
+ );
206
221
 
207
222
  const totalOffset =
208
223
  headerOffset +
209
- (componentAnchorPointY || 0) +
210
- extraAnchorPointYOffset -
211
- componentsMapOffset || 0;
224
+ toNumberWithDefaultZero(componentAnchorPointY) +
225
+ extraAnchorPointYOffset -
226
+ toNumberWithDefaultZero(componentsMapOffset) +
227
+ componentMarginTop +
228
+ componentPaddingTop;
212
229
 
213
230
  mainOffsetUpdater?.(
214
231
  { tag: this.target },
@@ -250,6 +267,7 @@ class TvOSCell extends React.Component<Props, State> {
250
267
  behavior,
251
268
  applyFocusableWrapper,
252
269
  hasFocusableInside,
270
+ skipFocusManagerRegistration,
253
271
  } = this.props;
254
272
 
255
273
  const { id } = item;
@@ -275,6 +293,7 @@ class TvOSCell extends React.Component<Props, State> {
275
293
  onFocus={handleFocus}
276
294
  onBlur={onBlur || this.onBlur}
277
295
  applyWrapper={applyFocusableWrapper}
296
+ skipFocusManagerRegistration={skipFocusManagerRegistration}
278
297
  >
279
298
  {(focused) => (
280
299
  <CellWithFocusable
@@ -289,6 +308,7 @@ class TvOSCell extends React.Component<Props, State> {
289
308
  focused={focused || this.props.focused}
290
309
  behavior={behavior}
291
310
  isFocusable={isFocusable}
311
+ skipFocusManagerRegistration={skipFocusManagerRegistration}
292
312
  />
293
313
  )}
294
314
  </FocusableWrapper>
@@ -311,6 +331,7 @@ class TvOSCell extends React.Component<Props, State> {
311
331
  offsetUpdater={offsetUpdater}
312
332
  style={baseCellStyles}
313
333
  isFocusable={isFocusable}
334
+ skipFocusManagerRegistration={skipFocusManagerRegistration}
314
335
  >
315
336
  {(focused) => (
316
337
  <FocusableCell
@@ -10,8 +10,8 @@ import {
10
10
  forceFocusableFocus,
11
11
  } from "@applicaster/zapp-react-native-utils/appUtils/focusManager/index.ios";
12
12
  import { findNodeHandle, ViewStyle } from "react-native";
13
-
14
- function noop() {}
13
+ import { emitNativeRegistered } from "@applicaster/zapp-react-native-utils/appUtils/focusManagerAux/utils/utils.ios";
14
+ import { noop } from "@applicaster/zapp-react-native-utils/functionUtils";
15
15
 
16
16
  type Props = {
17
17
  id: string;
@@ -39,6 +39,7 @@ type Props = {
39
39
  hasReceivedFocus: () => void;
40
40
  offsetUpdater: (arg1: string, arg2: number) => number;
41
41
  style: ViewStyle;
42
+ skipFocusManagerRegistration?: boolean;
42
43
  };
43
44
 
44
45
  export class Focusable extends BaseFocusable<Props> {
@@ -53,6 +54,7 @@ export class Focusable extends BaseFocusable<Props> {
53
54
  this.nextFocusableReactTags = {};
54
55
  this.preferredFocus = this.preferredFocus.bind(this);
55
56
  this.measureView = this.measureView.bind(this);
57
+ this.onRegistered = this.onRegistered.bind(this);
56
58
  }
57
59
 
58
60
  /**
@@ -169,6 +171,13 @@ export class Focusable extends BaseFocusable<Props> {
169
171
  });
170
172
  }
171
173
 
174
+ onRegistered({ nativeEvent }) {
175
+ const groupId = nativeEvent?.groupId;
176
+ const id = nativeEvent?.itemId;
177
+
178
+ emitNativeRegistered({ id, groupId, isGroup: false });
179
+ }
180
+
172
181
  render() {
173
182
  const {
174
183
  children,
@@ -203,6 +212,7 @@ export class Focusable extends BaseFocusable<Props> {
203
212
  focusable={isFocusable}
204
213
  {...this.nextFocusableReactTags}
205
214
  {...otherProps}
215
+ onRegistered={this.onRegistered}
206
216
  >
207
217
  {typeof children === "function" ? children(focused) : children}
208
218
  </FocusableItemNative>
@@ -5,6 +5,7 @@ exports[`FocusableTvOS should render correctly 1`] = `
5
5
  groupId={null}
6
6
  itemId={null}
7
7
  onLayout={[Function]}
8
+ onRegistered={[Function]}
8
9
  onViewBlur={[Function]}
9
10
  onViewFocus={[Function]}
10
11
  onViewPress={[Function]}
@@ -2,6 +2,7 @@ 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
4
  import { createLogger } from "@applicaster/zapp-react-native-utils/logger";
5
+ import { emitNativeRegistered } from "@applicaster/zapp-react-native-utils/appUtils/focusManagerAux/utils/utils.ios";
5
6
 
6
7
  const { log_verbose } = createLogger({
7
8
  subsystem: "General",
@@ -34,6 +35,15 @@ type Props = {
34
35
  };
35
36
 
36
37
  export class FocusableGroup extends BaseFocusable<Props> {
38
+ public readonly isGroup: boolean = true;
39
+
40
+ onRegistered = ({ nativeEvent }) => {
41
+ const groupId = nativeEvent?.groupId;
42
+ const id = nativeEvent?.itemId;
43
+
44
+ emitNativeRegistered({ id, groupId, isGroup: true });
45
+ };
46
+
37
47
  render() {
38
48
  const {
39
49
  children,
@@ -66,6 +76,7 @@ export class FocusableGroup extends BaseFocusable<Props> {
66
76
  onGroupBlur={onGroupBlur}
67
77
  style={style}
68
78
  {...otherProps}
79
+ onRegistered={this.onRegistered}
69
80
  >
70
81
  {children}
71
82
  </FocusableGroupNative>
@@ -1,9 +1,14 @@
1
1
  import * as React from "react";
2
- import { usePickFromState } from "@applicaster/zapp-react-native-redux/hooks";
2
+ import {
3
+ useAppData,
4
+ useContentTypes,
5
+ usePlugins,
6
+ } from "@applicaster/zapp-react-native-redux/hooks";
3
7
  import {
4
8
  useDimensions,
5
9
  useIsTablet as isTablet,
6
10
  useNavigation,
11
+ useRivers,
7
12
  } from "@applicaster/zapp-react-native-utils/reactHooks";
8
13
 
9
14
  import { BufferAnimation } from "../PlayerContainer/BufferAnimation";
@@ -39,12 +44,10 @@ export function HandlePlayable({
39
44
  mode,
40
45
  groupId,
41
46
  }: Props): React.ReactElement | null {
42
- const { plugins, contentTypes, rivers, appData } = usePickFromState([
43
- "plugins",
44
- "contentTypes",
45
- "rivers",
46
- "appData",
47
- ]);
47
+ const plugins = usePlugins();
48
+ const contentTypes = useContentTypes();
49
+ const rivers = useRivers();
50
+ const appData = useAppData();
48
51
 
49
52
  const { closeVideoModal } = useNavigation();
50
53
 
@@ -1,7 +1,10 @@
1
1
  import React from "react";
2
- import { usePickFromState } from "@applicaster/zapp-react-native-redux/hooks";
3
2
  import { getBackgroundImageUrl } from "../utils";
4
3
  import { useTheme } from "@applicaster/zapp-react-native-utils/theme";
4
+ import {
5
+ selectRemoteConfigurations,
6
+ useAppSelector,
7
+ } from "@applicaster/zapp-react-native-redux";
5
8
 
6
9
  export const LayoutBackground = ({
7
10
  Background,
@@ -12,7 +15,7 @@ export const LayoutBackground = ({
12
15
  }) => {
13
16
  const theme = useTheme();
14
17
 
15
- const { remoteConfigurations } = usePickFromState(["remoteConfigurations"]);
18
+ const remoteConfigurations = useAppSelector(selectRemoteConfigurations);
16
19
 
17
20
  const backgroundColor = theme.app_background_color;
18
21
  const backgroundImageUrl = getBackgroundImageUrl(remoteConfigurations);
@@ -18,7 +18,7 @@ import {
18
18
  routeIsPlayerScreen,
19
19
  } from "@applicaster/zapp-react-native-utils/navigationUtils";
20
20
  import { isApplePlatform } from "@applicaster/zapp-react-native-utils/reactUtils";
21
- import { usePickFromState } from "@applicaster/zapp-react-native-redux/hooks";
21
+ import { usePlugins } from "@applicaster/zapp-react-native-redux/hooks";
22
22
  import { NavBarContainer } from "./NavBarContainer";
23
23
 
24
24
  type ComponentsExtraProps = {
@@ -111,11 +111,7 @@ export const ScreenContainer = React.memo(function ScreenContainer({
111
111
  const { activeRiver } = navigator;
112
112
  const { title, visible } = useNavbarState();
113
113
 
114
- const { plugins = [] } = usePickFromState([
115
- "appState",
116
- "remoteConfigurations",
117
- "plugins",
118
- ]);
114
+ const plugins = usePlugins();
119
115
 
120
116
  const navigationProps = getNavigationProps({
121
117
  navigator,
@@ -1,5 +1,5 @@
1
1
  import * as React from "react";
2
- import { usePickFromState } from "@applicaster/zapp-react-native-redux/hooks";
2
+ import { useAppSelector } from "@applicaster/zapp-react-native-redux/hooks";
3
3
  import { useNavigation } from "@applicaster/zapp-react-native-utils/reactHooks/navigation";
4
4
 
5
5
  import { LayoutContainer } from "./LayoutContainer";
@@ -10,6 +10,7 @@ import { PathnameContext } from "../../../Contexts/PathnameContext";
10
10
  import { ScreenDataContext } from "../../../Contexts/ScreenDataContext";
11
11
  import { ScreenContextProvider } from "../../../Contexts/ScreenContext";
12
12
  import { LayoutBackground } from "./LayoutBackground";
13
+ import { selectAppReady } from "@applicaster/zapp-react-native-redux";
13
14
 
14
15
  type Components = {
15
16
  NavBar: React.ComponentType<any>;
@@ -29,9 +30,7 @@ type Props = {
29
30
  const Layout = ({ Components, ComponentsExtraProps, children }: Props) => {
30
31
  const navigator = useNavigation();
31
32
 
32
- const { appState: { appReady = false } = {} } = usePickFromState([
33
- "appState",
34
- ]);
33
+ const appReady = useAppSelector(selectAppReady);
35
34
 
36
35
  if (!appReady) {
37
36
  return null;
@@ -1,11 +1,11 @@
1
1
  import * as React from "react";
2
- import { pathOr } from "ramda";
3
2
 
4
- import { usePickFromState } from "@applicaster/zapp-react-native-redux/hooks";
3
+ import { useAppSelector } from "@applicaster/zapp-react-native-redux/hooks";
5
4
 
6
5
  import { ScreenLayoutContextProvider } from "./ScreenLayoutContextProvider";
7
6
  import { StackNavigator } from "../../Navigator";
8
7
  import { LayoutBackground } from "./LayoutBackground";
8
+ import { selectAppReady } from "@applicaster/zapp-react-native-redux";
9
9
 
10
10
  type Components = {
11
11
  NavBar: React.ComponentType<any>;
@@ -17,8 +17,7 @@ type Props = {
17
17
  };
18
18
 
19
19
  const Layout = ({ Components }: Props) => {
20
- const { appState } = usePickFromState(["appState"]);
21
- const appReady = pathOr(false, ["appReady"], appState);
20
+ const appReady = useAppSelector(selectAppReady);
22
21
 
23
22
  if (!appReady) {
24
23
  return null;
@@ -2,7 +2,7 @@ import * as React from "react";
2
2
  import * as R from "ramda";
3
3
 
4
4
  import { findPluginByIdentifier } from "@applicaster/zapp-react-native-utils/pluginUtils";
5
- import { usePickFromState } from "@applicaster/zapp-react-native-redux/hooks";
5
+ import { usePlugins } from "@applicaster/zapp-react-native-redux/hooks";
6
6
  import {
7
7
  inflateString,
8
8
  objectToReadableString,
@@ -40,7 +40,7 @@ export async function inflateUrl(url) {
40
40
 
41
41
  export function LinkHandler(props: Props) {
42
42
  const screenData = props?.screenData;
43
- const { plugins } = usePickFromState(["rivers", "plugins"]);
43
+ const plugins = usePlugins();
44
44
 
45
45
  const ScreenPlugin = findPluginByIdentifier(
46
46
  WEBVIEW_SCREEN_IDENTIFIER,
@@ -1,8 +1,8 @@
1
+ import * as React from "react";
1
2
  import {
2
3
  BorderContainerView,
3
4
  getBorderPadding, // Export for testing (using a double underscore prefix is a common convention)
4
5
  } from "../index";
5
- import * as React from "react";
6
6
  import { render } from "@testing-library/react-native";
7
7
  import { toNumberWithDefaultZero } from "@applicaster/zapp-react-native-utils/numberUtils";
8
8
  import { View } from "react-native";
@@ -11,6 +11,15 @@ jest.mock("@applicaster/zapp-react-native-utils/numberUtils", () => ({
11
11
  toNumberWithDefaultZero: jest.fn((value) => Number(value) || 0),
12
12
  }));
13
13
 
14
+ jest.mock(
15
+ "@applicaster/zapp-react-native-utils/appUtils/accessibilityManager/hooks",
16
+ () => ({
17
+ useAccessibilityManager: jest.fn(() => ({
18
+ addHeading: jest.fn(),
19
+ })),
20
+ })
21
+ );
22
+
14
23
  describe("BorderContainerView", () => {
15
24
  describe("getBorderPadding", () => {
16
25
  it("returns 0 for inside", () => {
@@ -42,6 +51,8 @@ describe("BorderContainerView", () => {
42
51
  };
43
52
 
44
53
  const borderPosition = null;
54
+ const mockEntry = { id: "test-entry" } as ZappEntry;
55
+ const mockHasFocusableInside = jest.fn(() => false);
45
56
 
46
57
  const { queryByTestId } = render(
47
58
  <BorderContainerView
@@ -52,6 +63,10 @@ describe("BorderContainerView", () => {
52
63
  borderPaddingRight={toNumberWithDefaultZero(padding.paddingRight)}
53
64
  borderPaddingBottom={toNumberWithDefaultZero(padding.paddingBottom)}
54
65
  borderPaddingLeft={toNumberWithDefaultZero(padding.paddingLeft)}
66
+ hasFocusableInside={mockHasFocusableInside}
67
+ entry={mockEntry}
68
+ state="focused"
69
+ hasTextLabels={false}
55
70
  >
56
71
  <View testID="child" />
57
72
  </BorderContainerView>
@@ -1,10 +1,16 @@
1
- import { toNumberWithDefaultZero } from "@applicaster/zapp-react-native-utils/numberUtils";
2
- import * as React from "react";
1
+ import React, { useMemo, useContext, useEffect } from "react";
3
2
  import { ImageStyle, StyleSheet, View, ViewStyle } from "react-native";
3
+ import { useAccessibilityManager } from "@applicaster/zapp-react-native-utils/appUtils/accessibilityManager/hooks";
4
+ import { toNumberWithDefaultZero } from "@applicaster/zapp-react-native-utils/numberUtils";
5
+ import { MeasurementPortalContext } from "../../../MeasurmentsPortal/MeasurementsPortal";
4
6
 
5
7
  type BorderPosition = "inside" | "outside" | "center";
6
8
 
7
9
  interface Props {
10
+ hasFocusableInside: (entry: ZappEntry) => boolean;
11
+ entry: ZappEntry;
12
+ state: CellState;
13
+ hasTextLabels: boolean;
8
14
  style: ImageStyle | ViewStyle;
9
15
  borderPosition: BorderPosition;
10
16
  borderPaddingTop: number;
@@ -118,8 +124,30 @@ export const BorderContainerView = (props: Props) => {
118
124
  borderPaddingLeft,
119
125
  style,
120
126
  children,
127
+ hasFocusableInside,
128
+ entry,
129
+ state,
130
+ hasTextLabels,
121
131
  } = props;
122
132
 
133
+ const accessibilityManager = useAccessibilityManager();
134
+ const isMeasurement = useContext(MeasurementPortalContext);
135
+
136
+ const isImageOnlyCell = useMemo(
137
+ () =>
138
+ state === "focused" &&
139
+ !hasTextLabels &&
140
+ !isMeasurement?.measuringInProgress &&
141
+ !hasFocusableInside(entry),
142
+ [entry, hasTextLabels, state, isMeasurement?.measuringInProgress]
143
+ );
144
+
145
+ useEffect(() => {
146
+ if (isImageOnlyCell && entry?.title) {
147
+ accessibilityManager.addHeading(String(entry.title));
148
+ }
149
+ }, [isImageOnlyCell, entry?.title]);
150
+
123
151
  const padding =
124
152
  borderPosition === "outside"
125
153
  ? {
@@ -32,6 +32,7 @@ export default function Image({
32
32
  placeholderImage,
33
33
  entry,
34
34
  withDimensions,
35
+ source: sourceProp,
35
36
  ...otherProps
36
37
  }: Props) {
37
38
  const [showDefault, setShowDefault] = React.useState(false);
@@ -48,7 +49,10 @@ export default function Image({
48
49
  entry,
49
50
  showDefault,
50
51
  placeholderImage: placeholderImage || "",
51
- otherProps,
52
+ otherProps: {
53
+ source: sourceProp,
54
+ state: otherProps.state,
55
+ },
52
56
  });
53
57
 
54
58
  const onError = React.useCallback(() => {
@@ -1,8 +1,8 @@
1
1
  import * as React from "react";
2
2
  import { Image as RnImage, ImageStyle } from "react-native";
3
- import { equals, omit } from "ramda";
4
3
 
5
4
  import { useImageSource } from "./hooks";
5
+ import { equals } from "@applicaster/zapp-react-native-utils/utils";
6
6
 
7
7
  type Source = {
8
8
  uri: string;
@@ -25,11 +25,19 @@ function Image({
25
25
  placeholderImage,
26
26
  entry,
27
27
  withDimensions,
28
+ source: sourceProp,
28
29
  ...otherProps
29
30
  }: Props) {
30
31
  const [error, setErrorState] = React.useState(null);
31
32
 
32
- const source = useImageSource({ uri, entry, otherProps });
33
+ const source = useImageSource({
34
+ uri,
35
+ entry,
36
+ otherProps: {
37
+ source: sourceProp,
38
+ state: otherProps.state,
39
+ },
40
+ });
33
41
 
34
42
  React.useEffect(() => {
35
43
  // reset error state on URI change as the error is referencing previous uri
@@ -49,7 +57,7 @@ function Image({
49
57
  onError={React.useCallback(() => setErrorState(true), [])}
50
58
  // as we have defaults as "" for placeholder image, we need to pass undefined to source to not throw warnings
51
59
  source={_source?.uri ? _source : undefined}
52
- {...omit(["source"], otherProps)}
60
+ {...otherProps}
53
61
  />
54
62
  );
55
63
  }
@@ -23,9 +23,17 @@ function Image({
23
23
  placeholderImage,
24
24
  entry,
25
25
  withDimensions,
26
+ source: sourceProp,
26
27
  ...otherProps
27
28
  }: Props) {
28
- const source = useImageSource({ uri, entry, otherProps });
29
+ const source = useImageSource({
30
+ uri,
31
+ entry,
32
+ otherProps: {
33
+ source: sourceProp,
34
+ state: otherProps.state,
35
+ },
36
+ });
29
37
 
30
38
  const updatedSource = source ? withDimensions(source) : { uri: "" };
31
39
 
@@ -1,19 +1,21 @@
1
1
  import * as React from "react";
2
- import { path } from "ramda";
3
2
 
4
3
  import { isTV } from "@applicaster/zapp-react-native-utils/reactUtils";
5
4
  import { useActions } from "@applicaster/zapp-react-native-utils/reactHooks/actions";
6
5
  import { extractAsset } from "./utils";
7
6
 
8
7
  type Return = { uri: string } | null;
8
+ type Source = { context?: string; uri?: string } | null | undefined;
9
9
 
10
- const getSourceContext = path(["source", "context"]);
11
- const getSourceUri = path(["source", "uri"]);
12
- const getState = path(["state"]);
10
+ const getSourceContext = (source: Source) => source?.context;
11
+ const getSourceUri = (source: Source) => source?.uri;
13
12
 
14
- export const useImageSource = ({ uri, entry, otherProps }): Return => {
15
- const uriContext = getSourceContext(otherProps);
16
- const uriState = getState(otherProps);
13
+ export const useImageSource = ({
14
+ uri,
15
+ entry,
16
+ otherProps: { source, state: uriState },
17
+ }): Return => {
18
+ const uriContext = getSourceContext(source);
17
19
 
18
20
  const action = useActions(uriContext);
19
21
 
@@ -38,7 +40,7 @@ export const useImageSource = ({ uri, entry, otherProps }): Return => {
38
40
  return { uri };
39
41
  }
40
42
 
41
- const uriFromSource = getSourceUri(otherProps);
43
+ const uriFromSource = getSourceUri(source);
42
44
 
43
45
  if (uriFromSource) {
44
46
  return { uri: uriFromSource };
@@ -47,7 +49,7 @@ export const useImageSource = ({ uri, entry, otherProps }): Return => {
47
49
  return null;
48
50
  };
49
51
 
50
- const getSource = (uri, showDefault, placeholderImage, otherProps) => {
52
+ const getSource = (uri, showDefault, placeholderImage, source) => {
51
53
  const placeholderName = placeholderImage || "";
52
54
 
53
55
  const defaultPath = {
@@ -60,7 +62,7 @@ const getSource = (uri, showDefault, placeholderImage, otherProps) => {
60
62
  return { uri };
61
63
  }
62
64
 
63
- const uriFromSource = getSourceUri(otherProps);
65
+ const uriFromSource = getSourceUri(source);
64
66
 
65
67
  if (uriFromSource) {
66
68
  return { uri: uriFromSource };
@@ -74,10 +76,9 @@ export const useImageSourceWithDefault = ({
74
76
  entry,
75
77
  showDefault,
76
78
  placeholderImage,
77
- otherProps,
79
+ otherProps: { state: uriState, source },
78
80
  }): Return => {
79
- const uriContext = getSourceContext(otherProps);
80
- const uriState = getState(otherProps);
81
+ const uriContext = getSourceContext(source);
81
82
 
82
83
  const action = useActions(uriContext);
83
84
 
@@ -98,5 +99,5 @@ export const useImageSourceWithDefault = ({
98
99
  return extractAsset(!isTV(), entryStateLocal.asset, uriState);
99
100
  }
100
101
 
101
- return getSource(uri, showDefault, placeholderImage, otherProps);
102
+ return getSource(uri, showDefault, placeholderImage, source);
102
103
  };