@applicaster/zapp-react-native-ui-components 16.0.0-rc.12 → 16.0.0-rc.121

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 (166) hide show
  1. package/Components/AudioPlayer/tv/__tests__/audioPlayer.test.js +19 -0
  2. package/Components/AudioPlayer/tv/__tests__/index.test.tsx +120 -0
  3. package/Components/AudioPlayer/tv/index.tsx +5 -1
  4. package/Components/Cell/Cell.tsx +3 -6
  5. package/Components/Cell/TvOSCellComponent.tsx +0 -1
  6. package/Components/CellRendererResolver/index.ts +1 -1
  7. package/Components/FocusableGroup/FocusableTvOS.tsx +11 -7
  8. package/Components/GeneralContentScreen/GeneralContentScreenHookAdapter.tsx +54 -0
  9. package/Components/GeneralContentScreen/__tests__/GeneralContentScreenHookAdapter.test.tsx +64 -0
  10. package/Components/GeneralContentScreen/__tests__/HookContentFocusGroup.web.test.tsx +91 -0
  11. package/Components/GeneralContentScreen/hookAdapter/__tests__/networkService.test.ts +74 -0
  12. package/Components/GeneralContentScreen/hookAdapter/__tests__/runInBackground.test.ts +139 -0
  13. package/Components/GeneralContentScreen/hookAdapter/__tests__/skipExpression.test.ts +294 -0
  14. package/Components/GeneralContentScreen/hookAdapter/__tests__/validationHelper.test.ts +188 -0
  15. package/Components/GeneralContentScreen/hookAdapter/logger.ts +6 -0
  16. package/Components/GeneralContentScreen/hookAdapter/networkService.ts +53 -0
  17. package/Components/GeneralContentScreen/hookAdapter/runInBackground.ts +48 -0
  18. package/Components/GeneralContentScreen/hookAdapter/skipExpression.ts +252 -0
  19. package/Components/GeneralContentScreen/hookAdapter/validationHelper.ts +90 -0
  20. package/Components/GeneralContentScreen/hookFocus/index.tsx +13 -0
  21. package/Components/GeneralContentScreen/hookFocus/index.web.tsx +69 -0
  22. package/Components/GeneralContentScreen/index.ts +2 -0
  23. package/Components/Layout/FullWidthRow.tsx +38 -0
  24. package/Components/LinkHandler/LinkHandler.tsx +2 -7
  25. package/Components/MasterCell/DefaultComponents/ActionButton.tsx +5 -1
  26. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/__tests__/placement.test.ts +47 -21
  27. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/components/ActionButtonController.tsx +19 -4
  28. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/components/__tests__/ActionButtonController.test.tsx +43 -1
  29. package/Components/MasterCell/DefaultComponents/ActionButtonsCore/placement.ts +34 -6
  30. package/Components/MasterCell/DefaultComponents/Button.tsx +1 -0
  31. package/Components/MasterCell/DefaultComponents/Image/hoc/utils/__tests__/withDimensions.test.ts +420 -125
  32. package/Components/MasterCell/DefaultComponents/Image/hoc/utils/index.ts +114 -19
  33. package/Components/MasterCell/DefaultComponents/Image/hoc/withDimensions.tsx +19 -8
  34. package/Components/MasterCell/DefaultComponents/ImageContainer/index.tsx +5 -3
  35. package/Components/MasterCell/DefaultComponents/LiveImage/executePreloadHooks.ts +2 -4
  36. package/Components/MasterCell/DefaultComponents/LiveImage/index.tsx +14 -6
  37. package/Components/MasterCell/DefaultComponents/PressableView.tsx +29 -9
  38. package/Components/MasterCell/DefaultComponents/Text/hooks/useText.ts +4 -0
  39. package/Components/MasterCell/DefaultComponents/Toggle.tsx +36 -14
  40. package/Components/MasterCell/DefaultComponents/__tests__/Toggle.test.tsx +167 -0
  41. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/ActionButton.tsx +154 -100
  42. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/AssetComponent.tsx +15 -4
  43. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/Button.ts +56 -22
  44. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/Spacer.ts +6 -4
  45. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/TextLabelsContainer.ts +3 -1
  46. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/__tests__/PressableView.test.tsx +34 -21
  47. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/__tests__/index.test.ts +15 -4
  48. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/index.ts +23 -16
  49. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/utils/__tests__/insertButtons.test.ts +8 -3
  50. package/Components/MasterCell/DefaultComponents/mobile/MobileActionButtons/utils/index.ts +2 -2
  51. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/utils/__tests__/insertButtonsBetweenLabels.test.ts +45 -13
  52. package/Components/MasterCell/DefaultComponents/tv/TvActionButtons/utils/index.ts +2 -2
  53. package/Components/MasterCell/SharedUI/CollapsibleTextContainer/__tests__/factory.test.ts +32 -0
  54. package/Components/MasterCell/__tests__/masterCell.test.js +60 -0
  55. package/Components/MasterCell/contexts/PressedStateContext.ts +3 -0
  56. package/Components/MasterCell/dataAdapter.ts +15 -5
  57. package/Components/MasterCell/hooks/index.ts +2 -0
  58. package/Components/MasterCell/hooks/usePressedState.ts +4 -0
  59. package/Components/MasterCell/index.tsx +57 -8
  60. package/Components/MasterCell/utils/__tests__/resolveColor.test.js +169 -132
  61. package/Components/MasterCell/utils/__tests__/resolveColorForProp.test.js +92 -0
  62. package/Components/MasterCell/utils/__tests__/withoutFocusedState.test.js +20 -0
  63. package/Components/MasterCell/utils/index.ts +103 -46
  64. package/Components/ModalComponent/ActionsBottomSheet/ActionButton.tsx +56 -0
  65. package/Components/ModalComponent/ActionsBottomSheet/__tests__/openActionsBottomSheet.test.ts +38 -0
  66. package/Components/ModalComponent/ActionsBottomSheet/boundActionButton.tsx +24 -0
  67. package/Components/ModalComponent/ActionsBottomSheet/index.ts +9 -0
  68. package/Components/ModalComponent/ActionsBottomSheet/openActionsBottomSheet.ts +61 -0
  69. package/Components/ModalComponent/AudioPlayer/Components/Button.tsx +392 -0
  70. package/Components/ModalComponent/AudioPlayer/Components/Header.tsx +772 -0
  71. package/Components/ModalComponent/AudioPlayer/Components/Input.tsx +728 -0
  72. package/Components/ModalComponent/AudioPlayer/Components/Item.tsx +1028 -0
  73. package/Components/ModalComponent/AudioPlayer/Components/NowPlayingHeaderSection.tsx +205 -0
  74. package/Components/ModalComponent/AudioPlayer/Components/__tests__/Item.test.tsx +164 -0
  75. package/Components/ModalComponent/AudioPlayer/Components/__tests__/NowPlayingHeaderSection.test.tsx +36 -0
  76. package/Components/ModalComponent/AudioPlayer/Components/index.ts +9 -0
  77. package/Components/ModalComponent/AudioPlayer/utils/__tests__/mergeStyles.test.ts +433 -0
  78. package/Components/ModalComponent/AudioPlayer/utils/mergeStyles.ts +563 -0
  79. package/Components/ModalComponent/BottomSheetDragArea.tsx +33 -0
  80. package/Components/ModalComponent/BottomSheetModalContent.tsx +440 -50
  81. package/Components/ModalComponent/Button/index.tsx +5 -3
  82. package/Components/ModalComponent/Header/CloseButton.tsx +12 -0
  83. package/Components/ModalComponent/Header/__tests__/CloseButton.test.tsx +72 -0
  84. package/Components/ModalComponent/Header/index.tsx +20 -7
  85. package/Components/ModalComponent/Header/utils.ts +1 -1
  86. package/Components/ModalComponent/ModalBlocksStyleContext.tsx +10 -0
  87. package/Components/ModalComponent/SortableList.tsx +156 -0
  88. package/Components/ModalComponent/SortableList.web.tsx +34 -0
  89. package/Components/ModalComponent/TextInputContent.tsx +135 -0
  90. package/Components/ModalComponent/__tests__/BottomSheetDragArea.test.tsx +74 -0
  91. package/Components/ModalComponent/__tests__/BottomSheetModalContent.test.tsx +343 -0
  92. package/Components/ModalComponent/__tests__/SortableList.web.test.tsx +16 -0
  93. package/Components/ModalComponent/__tests__/TextInputContent.test.tsx +169 -0
  94. package/Components/ModalComponent/__tests__/showTextInput.test.ts +120 -0
  95. package/Components/ModalComponent/index.tsx +18 -9
  96. package/Components/ModalComponent/presets/__tests__/getCell.test.ts +90 -0
  97. package/Components/ModalComponent/presets/dynamicCollectionCell.tsx +78 -0
  98. package/Components/ModalComponent/presets/index.ts +27 -0
  99. package/Components/ModalComponent/presets/selectableCell.tsx +28 -0
  100. package/Components/ModalComponent/presets/standardCell.tsx +41 -0
  101. package/Components/ModalComponent/presets/types.ts +35 -0
  102. package/Components/ModalComponent/showTextInput.ts +54 -0
  103. package/Components/ModalComponent/utils.ts +82 -21
  104. package/Components/OfflineHandler/__tests__/__snapshots__/index.test.tsx.snap +4 -85
  105. package/Components/OfflineHandler/__tests__/index.test.tsx +186 -17
  106. package/Components/OfflineHandler/hooks.ts +111 -0
  107. package/Components/OfflineHandler/index.tsx +91 -75
  108. package/Components/OfflineHandler/utils/index.ts +1 -13
  109. package/Components/PlayerContainer/PlayerContainer.tsx +28 -11
  110. package/Components/PlayerContainer/ProgramInfo/index.tsx +3 -4
  111. package/Components/PlayerContainer/__tests__/PlayerContainer.test.tsx +337 -0
  112. package/Components/PreloaderWrapper/index.tsx +1 -1
  113. package/Components/River/ComponentsMap/ComponentsMap.tsx +91 -62
  114. package/Components/River/River.tsx +4 -0
  115. package/Components/River/__tests__/__snapshots__/componentsMap.test.js.snap +142 -89
  116. package/Components/River/__tests__/componentsMap.test.js +8 -3
  117. package/Components/River/__tests__/resultCallback.test.tsx +47 -0
  118. package/Components/Screen/__tests__/Screen.test.tsx +24 -0
  119. package/Components/Screen/__tests__/__snapshots__/Screen.test.tsx.snap +2 -0
  120. package/Components/Screen/index.tsx +13 -4
  121. package/Components/Screen/navigationHandler.ts +8 -8
  122. package/Components/ScreenFeedLoader/ScreenFeedLoader.tsx +5 -3
  123. package/Components/ScreenResolverFeedProvider/ScreenResolverFeedProvider.tsx +1 -1
  124. package/Components/ScreenRevealManager/ScreenRevealManager.ts +18 -2
  125. package/Components/ScreenRevealManager/__tests__/ScreenRevealManager.test.ts +36 -0
  126. package/Components/ScreenRevealManager/__tests__/withScreenRevealManager.test.tsx +56 -0
  127. package/Components/ScreenRevealManager/withScreenRevealManager.tsx +21 -4
  128. package/Components/TopCutoffOverlay/index.tsx +2 -2
  129. package/Components/Transitioner/AnimationManager.js +1 -1
  130. package/Components/Transitioner/Scene.tsx +4 -3
  131. package/Components/Transitioner/Transitioner.tsx +98 -49
  132. package/Components/Transitioner/__tests__/transitioner.test.js +372 -3
  133. package/Components/VideoLive/LiveImageManager.ts +9 -3
  134. package/Components/VideoLive/__tests__/LiveImageManager.test.ts +59 -0
  135. package/Components/VideoModal/PlayerWrapper.tsx +4 -1
  136. package/Components/VideoModal/__tests__/PlayerWrapper.test.tsx +1 -0
  137. package/Components/VideoModal/__tests__/playerWrapperStyle.test.ts +58 -0
  138. package/Components/VideoModal/__tests__/playerWrapperUtils.test.ts +16 -0
  139. package/Components/VideoModal/playerWrapperStyle.ts +21 -3
  140. package/Components/VideoModal/playerWrapperUtils.ts +7 -1
  141. package/Components/ZappFrameworkComponents/BarView/BarView.tsx +12 -5
  142. package/Components/ZappFrameworkComponents/BarView/__tests__/BarView.test.tsx +2 -2
  143. package/Contexts/CachedDimensionsContext/__tests__/index.test.ts +154 -0
  144. package/Contexts/CachedDimensionsContext/index.ts +17 -2
  145. package/Contexts/ModalNavigationContext/__tests__/index.test.tsx +15 -0
  146. package/Contexts/ModalNavigationContext/index.tsx +2 -2
  147. package/Decorators/ZappPipesDataConnector/__tests__/screenToolbox.test.ts +347 -0
  148. package/Decorators/ZappPipesDataConnector/__tests__/screenToolboxUnavailable.test.ts +53 -0
  149. package/Decorators/ZappPipesDataConnector/index.tsx +19 -4
  150. package/Decorators/ZappPipesDataConnector/screenToolbox.ts +384 -0
  151. package/Helpers/ComponentCellSelectionHelper/index.js +0 -6
  152. package/Helpers/index.js +0 -39
  153. package/Hooks/useEntryActionsExpander.ts +63 -0
  154. package/assets/empty-Info.png +0 -0
  155. package/package.json +6 -5
  156. package/Components/OfflineHandler/NotificationView/NotificationView.lg.tsx +0 -112
  157. package/Components/OfflineHandler/NotificationView/NotificationView.samsung.tsx +0 -107
  158. package/Components/OfflineHandler/NotificationView/NotificationView.tsx +0 -153
  159. package/Components/OfflineHandler/NotificationView/__tests__/index.test.tsx +0 -66
  160. package/Components/OfflineHandler/NotificationView/utils.ts +0 -34
  161. package/Components/Screen/utils.ts +0 -16
  162. package/Helpers/Analytics/index.js +0 -95
  163. /package/Components/BackgroundImage/{BackgroundImage.tv.android.tsx → BackgroundImage.android.tv.tsx} +0 -0
  164. /package/Components/BackgroundImage/{BackgroundImage.tv.ios.tsx → BackgroundImage.ios.tv.tsx} +0 -0
  165. /package/Components/MasterCell/DefaultComponents/ButtonContainerView/{index.tv.android.tsx → index.android.tv.tsx} +0 -0
  166. /package/Decorators/ZappPipesDataConnector/__tests__/{Hero.js → Hero.tsx} +0 -0
@@ -0,0 +1,53 @@
1
+ import { act, renderHook } from "@testing-library/react-native";
2
+
3
+ // Fails the screen toolbox as a build without the X-Ray bridge does. Others (player, loaded with
4
+ // the toolbox's imports) register, or the helper would never let the screen toolbox try.
5
+ const mockRegister = jest.fn((toolbox) =>
6
+ toolbox.name === "screen"
7
+ ? Promise.reject(new Error("no bridge"))
8
+ : Promise.resolve()
9
+ );
10
+
11
+ // The X-Ray mock (__mocks__/@applicaster/quick-brick-xray) has no toolbox API: give it one.
12
+ require("@applicaster/quick-brick-xray").registerReactToolbox = mockRegister;
13
+
14
+ jest.mock("@applicaster/zapp-react-native-redux/AppStore", () => ({
15
+ appStore: { get: () => undefined },
16
+ }));
17
+
18
+ const { useScreenToolboxRecord } = require("../screenToolbox");
19
+
20
+ const mount = (id: string) =>
21
+ renderHook(() =>
22
+ useScreenToolboxRecord({ id }, "home", 0, undefined, "/river/home")
23
+ );
24
+
25
+ const mounted = () => {
26
+ // Other toolboxes (player) may register first.
27
+ const { tools } = mockRegister.mock.calls.find(
28
+ ([toolbox]) => toolbox.name === "screen"
29
+ )[0];
30
+
31
+ return tools
32
+ .find((tool) => tool.name === "current")
33
+ .handler({ pathname: "/river/home" }).mounted;
34
+ };
35
+
36
+ describe("screenToolbox without the X-Ray bridge", () => {
37
+ it("drops its records and stops recording", async () => {
38
+ mount("c1");
39
+
40
+ // Let the failed registration settle.
41
+ await act(async () => {});
42
+
43
+ expect(mounted()).toEqual([]);
44
+
45
+ mount("c2");
46
+
47
+ expect(mounted()).toEqual([]);
48
+
49
+ expect(
50
+ mockRegister.mock.calls.filter(([toolbox]) => toolbox.name === "screen")
51
+ ).toHaveLength(1);
52
+ });
53
+ });
@@ -5,7 +5,9 @@ import { useRoute } from "@applicaster/zapp-react-native-utils/reactHooks/naviga
5
5
  import { usePlugins } from "@applicaster/zapp-react-native-redux/hooks";
6
6
  import { ZappPipesSearchContext } from "@applicaster/zapp-react-native-ui-components/Contexts";
7
7
  import { useScreenContext } from "@applicaster/zapp-react-native-utils/reactHooks/screen/useScreenContext";
8
+ import { useUIComponentContext } from "@applicaster/zapp-react-native-ui-components/Contexts/UIComponentContext";
8
9
  import { ResolverSelector } from "./ResolverSelector";
10
+ import { useScreenToolboxRecord } from "./screenToolbox";
9
11
  import { ComponentDataSourceContext } from "./types";
10
12
 
11
13
  type Props = {
@@ -22,7 +24,7 @@ export function zappPipesDataConnector(
22
24
  Component: React.FC<any> | React.ComponentClass<any>
23
25
  ) {
24
26
  return function WrappedWithZappPipesData(props: Props) {
25
- const { screenData } = useRoute();
27
+ const { screenData, pathname } = useRoute();
26
28
  const plugins = usePlugins();
27
29
  const screenContextData = useScreenContext();
28
30
 
@@ -53,6 +55,16 @@ export function zappPipesDataConnector(
53
55
 
54
56
  const [searchContext] = ZappPipesSearchContext.useZappPipesContext();
55
57
 
58
+ const parent = useUIComponentContext() as { id?: string } | null;
59
+
60
+ const toolboxRecord = useScreenToolboxRecord(
61
+ component,
62
+ riverId,
63
+ componentIndex,
64
+ parent?.id,
65
+ pathname
66
+ );
67
+
56
68
  // Prepare context for resolvers
57
69
  const resolverProps: ComponentDataSourceContext = {
58
70
  component,
@@ -71,9 +83,12 @@ export function zappPipesDataConnector(
71
83
 
72
84
  return (
73
85
  <ResolverSelector {...resolverProps}>
74
- {(zappPipesDataProps) => (
75
- <Component {...props} {...zappPipesDataProps} />
76
- )}
86
+ {(zappPipesDataProps) => {
87
+ // A plain field on the toolbox record, read only when the agent asks: no render.
88
+ toolboxRecord.feed = zappPipesDataProps.zappPipesData;
89
+
90
+ return <Component {...props} {...zappPipesDataProps} />;
91
+ }}
77
92
  </ResolverSelector>
78
93
  );
79
94
  };
@@ -0,0 +1,384 @@
1
+ import { useEffect, useMemo } from "react";
2
+ import { Platform } from "react-native";
3
+ import * as R from "ramda";
4
+ import { appStore } from "@applicaster/zapp-react-native-redux/AppStore";
5
+ import {
6
+ registerXrayToolbox,
7
+ XrayToolSpec,
8
+ } from "@applicaster/zapp-react-native-utils/logger/xrayToolbox";
9
+ import { modalStore } from "@applicaster/zapp-react-native-utils/modalState";
10
+ import { ROUTE_TYPES } from "@applicaster/zapp-react-native-utils/navigationUtils/routeTypes";
11
+ import { getXrayNavigator } from "@applicaster/zapp-react-native-utils/navigationUtils/xrayNavigator";
12
+ import { isTV } from "@applicaster/zapp-react-native-utils/reactUtils";
13
+ import { zappHookModalStore } from "@applicaster/zapp-react-native-ui-components/Contexts/ZappHookModalContext";
14
+
15
+ import { ZappPipesData } from "./types";
16
+
17
+ /**
18
+ * X-Ray `screen.*` toolbox: what the current screen shows — its river components in order, the
19
+ * feed each one loaded and the entries it displays. Every river component, on every platform,
20
+ * goes through the zappPipes decorator, which writes a record here on mount and drops it on
21
+ * unmount: a plain Set, nothing subscribes to it, so it never renders anything. Records are
22
+ * dropped for good once X-Ray turns out to have no toolbox bridge (release builds).
23
+ *
24
+ * Screens that are not rivers (screen plugins, hooks, the player) report what renders them,
25
+ * read from navigation and the modal stores; a plugin with state of its own registers its own
26
+ * toolbox, as the player does.
27
+ *
28
+ * Mounted is not visible (lists are virtualised): which rows and cells are on screen comes from
29
+ * the native `ui.inspect`. River rows carry testID `component-<componentId>` (mobile, tvOS,
30
+ * Android TV) and cells carry their entry id in their testID.
31
+ */
32
+
33
+ type ComponentRecord = {
34
+ component: ZappUIComponent;
35
+ riverId: string;
36
+ index?: number;
37
+ parentId?: string;
38
+ pathname: string;
39
+ /** Written on each render: the data the component was given, after item filter and limit. */
40
+ feed?: ZappPipesData;
41
+ };
42
+
43
+ /** What a route shows; `screenType` is how ScreenResolver renders it. */
44
+ type RouteState = {
45
+ screenType?: string;
46
+ screen?: any;
47
+ entry?: Partial<ZappEntry>;
48
+ nested?: { screen?: ZappRiver };
49
+ };
50
+
51
+ /** A screen presented over the navigation stack. `covers` is false when it is out of the way. */
52
+ type Layer = RouteState & { pathname: string; covers: boolean };
53
+
54
+ const records = new Set<ComponentRecord>();
55
+
56
+ /** Set once registration failed: X-Ray has no toolbox bridge, so nothing will ever read records. */
57
+ let unavailable = false;
58
+
59
+ const DEFAULT_ENTRIES_LIMIT = 10;
60
+
61
+ const toLimit = (value: unknown) =>
62
+ typeof value === "number" && value >= 0 ? value : DEFAULT_ENTRIES_LIMIT;
63
+
64
+ /** The hooks modal, with the pathname ZappHooksModal gives it; hidden while it runs in the background. */
65
+ const hooksModalLayer = (): Layer | undefined => {
66
+ const { state, isHooksExecutionInProgress, isRunningInBackground } =
67
+ zappHookModalStore.getState();
68
+
69
+ if (!isHooksExecutionInProgress || !state.path) return undefined;
70
+
71
+ const hookPlugin = state.screenData?.hookPlugin;
72
+
73
+ return {
74
+ pathname: `${ROUTE_TYPES.HOOKS_MODAL}/${hookPlugin ? hookPlugin.name : ""}`,
75
+ covers:
76
+ !isRunningInBackground && hookPlugin?.module?.presentFullScreen !== false,
77
+ screenType: "hooks",
78
+ screen: hookPlugin,
79
+ entry: state.screenData?.payload as ZappEntry,
80
+ };
81
+ };
82
+
83
+ /** The modal screen or bottom sheet, with the pathname and props ModalProvider gives it. */
84
+ const modalLayer = (): Layer | undefined => {
85
+ const { visible, screen, props } = modalStore.getState().modalState;
86
+
87
+ if (!visible || !screen) return undefined;
88
+
89
+ const { screenId, screenType, entry, screenData } = {
90
+ screenId: screen.id,
91
+ screenType: "river",
92
+ ...props,
93
+ } as Record<string, any>;
94
+
95
+ // A modal hook (and the ad interstitial) presents a { hookPlugin, payload } wrapper.
96
+ const isHook = screenType === "hooks";
97
+
98
+ return {
99
+ pathname: `${ROUTE_TYPES.MODAL}/${screenId}`,
100
+ covers: true,
101
+ screenType,
102
+ screen: isHook ? (screen.hookPlugin ?? screenData?.hookPlugin) : screen,
103
+ entry: isHook ? screen.payload : entry,
104
+ };
105
+ };
106
+
107
+ const videoModalLayer = (): Layer | undefined => {
108
+ const videoModal = getXrayNavigator()?.videoModalState;
109
+
110
+ if (!videoModal?.visible) return undefined;
111
+
112
+ return {
113
+ pathname: `${ROUTE_TYPES.VIDEO_MODAL}/${videoModal.item?.id}`,
114
+ covers: videoModal.mode !== "MINIMIZED",
115
+ screenType: "playable",
116
+ entry: videoModal.item,
117
+ };
118
+ };
119
+
120
+ /**
121
+ * Topmost first: the hooks modal and modals are native RN Modals, drawn over the video modal.
122
+ * Only layers this platform renders: the mobile layout renders the hooks and video modals, and
123
+ * ModalProvider renders nothing on web.
124
+ */
125
+ const presentedLayers = () => {
126
+ const web = Platform.OS === "web";
127
+ const mobile = !web && !isTV();
128
+
129
+ return [
130
+ mobile && hooksModalLayer(),
131
+ !web && modalLayer(),
132
+ mobile && videoModalLayer(),
133
+ ].filter((layer): layer is Layer => Boolean(layer));
134
+ };
135
+
136
+ /** The pathname of what is on top: a covering presented layer, else the stack top. */
137
+ export const getTopPathname = (): string | undefined =>
138
+ presentedLayers().find((layer) => layer.covers)?.pathname ??
139
+ getXrayNavigator()?.getPathname();
140
+
141
+ /** What the route at `pathname` shows. Presented layers are not on the stack: their stores hold them. */
142
+ const routeState = (pathname: string): RouteState => {
143
+ const layer = presentedLayers().find((item) => item.pathname === pathname);
144
+
145
+ if (layer) return layer;
146
+
147
+ // Not presented any more (a stale modal pathname): nothing to describe.
148
+ if (!pathname.startsWith("/")) return {};
149
+
150
+ const state = getXrayNavigator()?.getStackForPathname(pathname)?.state;
151
+
152
+ // A stack route ends in <screenType>/<screenId>.
153
+ const screenType = pathname.split("/").slice(-2)[0];
154
+
155
+ return {
156
+ screenType,
157
+ ...state,
158
+ // A hook screen's entry is the { payload } wrapper it was pushed with.
159
+ entry:
160
+ screenType === "hooks"
161
+ ? ((state?.entry as any)?.payload ?? state?.entry)
162
+ : state?.entry,
163
+ };
164
+ };
165
+
166
+ /** Screen types ScreenResolver renders itself rather than through a screen plugin. */
167
+ const BUILT_IN_KINDS = {
168
+ playable: "player",
169
+ link: "link",
170
+ favorites: "favorites",
171
+ };
172
+
173
+ /**
174
+ * What renders the screen, as ScreenResolver picks it: a river of components, a built-in screen,
175
+ * a hook plugin, or a screen plugin (a river whose type is a plugin identifier, or a route whose
176
+ * screen type is one).
177
+ */
178
+ const describeKind = ({
179
+ screenType,
180
+ screen,
181
+ }: RouteState): { kind: string; identifier?: string } => {
182
+ if (!screenType) return { kind: "none" };
183
+
184
+ if (screen?.modalBottomSheetContentProps) return { kind: "bottomSheet" };
185
+
186
+ if (screenType === "hooks") {
187
+ return { kind: "hook", identifier: screen?.identifier };
188
+ }
189
+
190
+ if (BUILT_IN_KINDS[screenType]) return { kind: BUILT_IN_KINDS[screenType] };
191
+
192
+ if (screenType !== "river") return { kind: "plugin", identifier: screenType };
193
+
194
+ return screen?.type === "general_content"
195
+ ? { kind: "river" }
196
+ : { kind: "plugin", identifier: screen?.type };
197
+ };
198
+
199
+ const describePlugin = (identifier?: string) => {
200
+ if (!identifier) return null;
201
+
202
+ const plugins = (appStore.get("plugins") ?? []) as QuickBrickPlugin[];
203
+
204
+ return {
205
+ identifier,
206
+ name: plugins.find((plugin) => plugin.identifier === identifier)?.name,
207
+ };
208
+ };
209
+
210
+ const describeFeed = (feed?: ZappPipesData) => {
211
+ if (!feed?.url) return null;
212
+
213
+ const error = feed.error as { message?: string } | undefined;
214
+
215
+ return {
216
+ url: feed.url,
217
+ state: error ? "error" : feed.loading ? "loading" : "loaded",
218
+ ...(error && { error: error.message ?? String(error) }),
219
+ count: feed.data?.entry?.length ?? 0,
220
+ };
221
+ };
222
+
223
+ const byIndex = (a: ComponentRecord, b: ComponentRecord) =>
224
+ (a.index ?? 0) - (b.index ?? 0);
225
+
226
+ /** Components in order, a group's children nested under it (they share the river and its indexes). */
227
+ const describeComponents = (
228
+ mounted: ComponentRecord[],
229
+ belongs: (record: ComponentRecord) => boolean,
230
+ limit: number
231
+ ) =>
232
+ mounted
233
+ .filter(belongs)
234
+ .sort(byIndex)
235
+ .map((record) => {
236
+ const children = describeComponents(
237
+ mounted,
238
+ (child) => child.parentId === record.component.id,
239
+ limit
240
+ );
241
+
242
+ return {
243
+ index: record.index,
244
+ id: record.component.id,
245
+ type: record.component.component_type,
246
+ name: record.component.name,
247
+ feed: describeFeed(record.feed),
248
+ entries: (record.feed?.data?.entry ?? [])
249
+ .slice(0, limit)
250
+ .map((entry, index) => ({ index, id: entry.id, title: entry.title })),
251
+ ...(children.length > 0 && { children }),
252
+ };
253
+ });
254
+
255
+ const describeRiverComponents = (mounted: ComponentRecord[], limit: number) => {
256
+ const ids = new Set(mounted.map((record) => record.component.id));
257
+
258
+ return describeComponents(
259
+ mounted,
260
+ (record) => !record.parentId || !ids.has(record.parentId),
261
+ limit
262
+ );
263
+ };
264
+
265
+ const describeScreen = (pathname: string, limit: number) => {
266
+ const state = routeState(pathname);
267
+ const { screen, entry, nested } = state;
268
+ const { kind, identifier } = describeKind(state);
269
+ const plugin = describePlugin(identifier);
270
+
271
+ const mounted = Array.from(records).filter(
272
+ (record) => record.pathname === pathname
273
+ );
274
+
275
+ const riverIds = R.uniq(mounted.map((record) => record.riverId));
276
+ const rivers = appStore.get("rivers") as Record<string, ZappRiver>;
277
+
278
+ return {
279
+ pathname,
280
+ screen: {
281
+ kind,
282
+ ...(screen && { id: screen.id, name: screen.name, type: screen.type }),
283
+ ...(plugin && { plugin }),
284
+ },
285
+ ...(nested?.screen && {
286
+ nested: {
287
+ id: nested.screen.id,
288
+ name: nested.screen.name,
289
+ type: nested.screen.type,
290
+ },
291
+ }),
292
+ entry: entry ? { id: entry.id, title: entry.title } : null,
293
+ rivers: riverIds.map((riverId) => ({
294
+ id: riverId,
295
+ name: rivers?.[riverId]?.name,
296
+ type: rivers?.[riverId]?.type,
297
+ components: describeRiverComponents(
298
+ mounted.filter((record) => record.riverId === riverId),
299
+ limit
300
+ ),
301
+ })),
302
+ };
303
+ };
304
+
305
+ const tools: XrayToolSpec[] = [
306
+ {
307
+ name: "current",
308
+ description:
309
+ "The screen on top (hooks modal, modal or bottom sheet, video modal unless docked, else stack top) " +
310
+ "or the one at `pathname`: what renders it (kind river, plugin, hook, player, link, favorites, " +
311
+ "bottomSheet, and the plugin), its river components in order (a group's children nested) with " +
312
+ "their feed (url, state, count) and first entries. " +
313
+ "Lists mount ahead of the viewport, so a component here may be off screen: use the native " +
314
+ "ui.inspect for what is visible (row testID component-<id>; cell testIDs contain the entry id).",
315
+ parameters: [
316
+ {
317
+ name: "pathname",
318
+ type: "string",
319
+ description:
320
+ "A mounted screen's route (see mounted); defaults to the top one.",
321
+ },
322
+ {
323
+ name: "limit",
324
+ type: "number",
325
+ description: `Entries per component, default ${DEFAULT_ENTRIES_LIMIT}.`,
326
+ },
327
+ ],
328
+ handler: ({ pathname, limit }) => {
329
+ const target =
330
+ typeof pathname === "string" && pathname ? pathname : getTopPathname();
331
+
332
+ if (!target) throw new Error("Navigation is not available yet");
333
+
334
+ return {
335
+ ...describeScreen(target, toLimit(limit)),
336
+ mounted: R.uniq(Array.from(records, (record) => record.pathname)),
337
+ };
338
+ },
339
+ },
340
+ ];
341
+
342
+ const registerScreenToolbox = () =>
343
+ registerXrayToolbox({
344
+ name: "screen",
345
+ description:
346
+ "What the current screen shows: what renders it, river components, their feeds and entries.",
347
+ tools,
348
+ });
349
+
350
+ /**
351
+ * Keeps a record of a mounted river component for the toolbox. Returns the record so the caller
352
+ * can attach the feed data it renders with.
353
+ */
354
+ export const useScreenToolboxRecord = (
355
+ component: ZappUIComponent,
356
+ riverId: string,
357
+ index: number | undefined,
358
+ parentId: string | undefined,
359
+ pathname: string
360
+ ): ComponentRecord => {
361
+ const record = useMemo(
362
+ () => ({ component, riverId, index, parentId, pathname }),
363
+ [component, riverId, index, parentId, pathname]
364
+ );
365
+
366
+ useEffect(() => {
367
+ if (unavailable) return;
368
+
369
+ registerScreenToolbox().then((registered) => {
370
+ if (!registered) {
371
+ unavailable = true;
372
+ records.clear();
373
+ }
374
+ });
375
+
376
+ records.add(record);
377
+
378
+ return () => {
379
+ records.delete(record);
380
+ };
381
+ }, [record]);
382
+
383
+ return record;
384
+ };
@@ -1,4 +1,3 @@
1
- import { sendTapCellEvent, sendTapMenuItem } from "../Analytics";
2
1
  import { getItemType } from "@applicaster/zapp-react-native-utils/navigationUtils";
3
2
  import { SCREEN_TYPES } from "@applicaster/zapp-react-native-utils/navigationUtils/itemTypes";
4
3
 
@@ -12,7 +11,6 @@ export function onCellPress(
12
11
  itemIndex,
13
12
  layoutVersion = "v1"
14
13
  ) {
15
- sendTapCellEvent(entry, component, headerTitle, itemIndex);
16
14
  const componentScreenType = screenTypeFromComponent(component);
17
15
  const entryScreenType = screenTypeFromEntry(entry);
18
16
  const itemType = getItemType(entry, layoutVersion);
@@ -37,7 +35,3 @@ function screenTypeFromComponent(component) {
37
35
  function screenTypeFromEntry(entry) {
38
36
  return entry && entry.screen_type;
39
37
  }
40
-
41
- export function onMenuItemFocus({ item, index, isHome }) {
42
- sendTapMenuItem({ item, index, isHome });
43
- }
package/Helpers/index.js CHANGED
@@ -7,42 +7,3 @@ export const ASSETS = {
7
7
  amazon: "tv_app_background",
8
8
  }),
9
9
  };
10
-
11
- export const ANALYTICS_CORE_EVENTS = {
12
- TAP_CELL: "Tap Cell",
13
- PLAY_VOD_ITEM: "Play VOD Item",
14
- VOD_ITEM_PLAY_WAS_TRIGGERED: "VOD Item: Play Was Triggered",
15
- CHANNEL_ITEM_PLAY_WAS_TRIGGERED: "Channel Item: Play Was Triggered",
16
- PROGRAM_ITEM_PLAY_WAS_TRIGGERED: "Program Item: Play Was Triggered",
17
- TAP_MENU_ICON: "Tap Menu Item",
18
- HOME_SCREEN_VIEWED: "Home Screen: Viewed",
19
- ITEM_NOT_AVAILABLE: "N/A",
20
- };
21
-
22
- export const ANALYTICS_ENTRY_EVENTS = {
23
- ITEM_TYPE: "Item Type",
24
- ITEM_TITLE: "Item Name",
25
- ITEM_ID: "Item ID",
26
- ITEM_LINK: "Item Link",
27
- ITEM_INDEX: "Item Number",
28
- ITEM_TIME_PLAYED: "Item Total Time Played",
29
- ITEM_TOTAL_TIME_PLAYED: "Item Total Time Played",
30
- ITEM_PLAY_COMPLETED: "Completed",
31
- ITEM_CUSTOM_PROPERTY: "Custom Property",
32
- };
33
-
34
- export const ANALYTICS_COMPONENT_EVENTS = {
35
- COMPONENT_ID: "Component ID",
36
- COMPONENT_TYPE: "Component Type",
37
- CELL_STYLE: "Cell Style",
38
- HEADER_TITLE: "Header Name",
39
- COMPONENT_SOURCE: "Component Source",
40
- };
41
-
42
- export const ANALYTICS_MENU_ITEM_EVENTS = {
43
- ITEM_TYPE: "Item Type",
44
- ITEM_TITLE: "Item Name",
45
- ITEM_ID: "Item ID",
46
- ITEM_HOME_SCREEN: "Home Screen",
47
- ITEM_INDEX: "Item Number",
48
- };
@@ -0,0 +1,63 @@
1
+ import * as React from "react";
2
+
3
+ import { ActionExecutorContext } from "@applicaster/zapp-react-native-utils/actionsExecutor/ActionExecutorContext";
4
+ import {
5
+ buildEntryActions,
6
+ EntryActionDeps,
7
+ } from "@applicaster/zapp-react-native-utils/entryActions";
8
+ import { useRoute } from "@applicaster/zapp-react-native-utils/reactHooks/navigation";
9
+ import { useScreenStateStore } from "@applicaster/zapp-react-native-utils/reactHooks/navigation/useScreenStateStore";
10
+ import {
11
+ useCurrentScreenData,
12
+ useScreenContext,
13
+ } from "@applicaster/zapp-react-native-utils/reactHooks/screen";
14
+ import { RegisteredAction } from "@applicaster/zapp-react-native-utils/uiActionsRegistrator";
15
+
16
+ /**
17
+ * Expands `entry.extensions.entry_action` into invokable actions, against the
18
+ * screen this hook is called from.
19
+ *
20
+ * Entry actions are data on the entry, not an installed capability, so there is
21
+ * nothing to look up by identifier: the surface showing the entry expands them.
22
+ * That also decides which screen they execute against — the one whose tree this
23
+ * hook runs in, rather than whichever screen happened to be current when a
24
+ * global provider was registered.
25
+ *
26
+ * Pass the result to `resolveActionIdentifiers` / `resolvePlayerActions` as
27
+ * `expandEntryActions`.
28
+ */
29
+ export function useEntryActionsExpander(): (
30
+ entry: ZappEntry | ZappFeed
31
+ ) => RegisteredAction[] {
32
+ const { pathname } = useRoute();
33
+
34
+ const screenContext = useScreenContext();
35
+ const screenData = useCurrentScreenData();
36
+ const screenState = screenContext?.options;
37
+ const screenStateStore = useScreenStateStore();
38
+ const actionExecutor = React.useContext(ActionExecutorContext);
39
+
40
+ // Read through a ref so the returned expander keeps a stable identity: it
41
+ // feeds `useMemo` dependency lists on surfaces that re-resolve their actions
42
+ // on every registry change.
43
+ const depsRef = React.useRef<EntryActionDeps>();
44
+
45
+ depsRef.current = {
46
+ actionExecutor,
47
+ actionContext: {
48
+ screenData,
49
+ screenState,
50
+ screenRoute: pathname,
51
+ screenStateStore,
52
+ // From the screen context, the way `useCellClick` builds the same bag.
53
+ // The ZappPipes entry context is a route-keyed map, so reading `.data`
54
+ // off it was always undefined.
55
+ screenEntry: screenContext?.entry,
56
+ },
57
+ };
58
+
59
+ return React.useCallback(
60
+ (entry: ZappEntry | ZappFeed) => buildEntryActions(entry, depsRef.current),
61
+ []
62
+ );
63
+ }
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@applicaster/zapp-react-native-ui-components",
3
- "version": "16.0.0-rc.12",
3
+ "version": "16.0.0-rc.121",
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",
@@ -28,12 +28,13 @@
28
28
  },
29
29
  "homepage": "https://github.com/applicaster/quickbrick#readme",
30
30
  "dependencies": {
31
- "@applicaster/applicaster-types": "16.0.0-rc.12",
32
- "@applicaster/zapp-react-native-bridge": "16.0.0-rc.12",
33
- "@applicaster/zapp-react-native-redux": "16.0.0-rc.12",
34
- "@applicaster/zapp-react-native-utils": "16.0.0-rc.12",
31
+ "@applicaster/applicaster-types": "16.0.0-rc.121",
32
+ "@applicaster/zapp-react-native-bridge": "16.0.0-rc.121",
33
+ "@applicaster/zapp-react-native-redux": "16.0.0-rc.121",
34
+ "@applicaster/zapp-react-native-utils": "16.0.0-rc.121",
35
35
  "fast-json-stable-stringify": "^2.1.0",
36
36
  "promise": "^8.3.0",
37
+ "react-native-sortables": "1.7.1",
37
38
  "url": "^0.11.0",
38
39
  "uuid": "^3.3.2"
39
40
  },