@applicaster/quick-brick-core 16.0.0-alpha.1593888345 → 16.0.0-alpha.1701179921

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.
@@ -1,7 +1,6 @@
1
1
  import React from "react";
2
2
 
3
3
  import { View, StyleSheet } from "react-native";
4
- import { useSafeAreaInsets } from "react-native-safe-area-context";
5
4
 
6
5
  import { ScreenResolver } from "@applicaster/zapp-react-native-ui-components/Components/ScreenResolver";
7
6
 
@@ -18,8 +17,6 @@ const styles = StyleSheet.create({
18
17
  export function ModalContent(props: Props) {
19
18
  const { modalScreenProps } = props;
20
19
 
21
- const insets = useSafeAreaInsets();
22
-
23
20
  if (modalScreenProps?.screenData?.modalBottomSheetContentProps) {
24
21
  const { ModalBottomSheetContent, modalBottomSheetContentProps } =
25
22
  modalScreenProps?.screenData || {};
@@ -34,19 +31,9 @@ export function ModalContent(props: Props) {
34
31
 
35
32
  return (
36
33
  // A presented screen fills the window, including the status bar and home
37
- // indicator. Inset it so chrome it draws at its own edges — a close button,
38
- // for example — stays inside the tappable area.
39
- <View
40
- style={[
41
- styles.container,
42
- {
43
- paddingTop: insets.top,
44
- paddingBottom: insets.bottom,
45
- paddingLeft: insets.left,
46
- paddingRight: insets.right,
47
- },
48
- ]}
49
- >
34
+ // indicator. The modal wraps it in its own SafeAreaProvider, so the screen
35
+ // reads the real insets itself and places its own chrome inside them.
36
+ <View style={styles.container}>
50
37
  <ScreenResolver {...modalScreenProps} />
51
38
  </View>
52
39
  );
@@ -6,10 +6,6 @@ import { ModalContent } from "../ModalContent";
6
6
 
7
7
  jest.mock("../ModalBottomSheet", () => ({ ModalBottomSheet: () => null }));
8
8
 
9
- jest.mock("react-native-safe-area-context", () => ({
10
- useSafeAreaInsets: () => ({ top: 59, bottom: 34, left: 0, right: 0 }),
11
- }));
12
-
13
9
  jest.mock(
14
10
  "@applicaster/zapp-react-native-ui-components/Components/ScreenResolver",
15
11
  () => ({ ScreenResolver: () => null })
@@ -18,15 +14,14 @@ jest.mock(
18
14
  const flatStyle = (tree: any) => StyleSheet.flatten(tree.props.style);
19
15
 
20
16
  describe("ModalContent", () => {
21
- it("insets a presented screen by the safe area so its chrome stays tappable", () => {
17
+ it("lets a presented screen fill the modal, leaving the safe area to the screen", () => {
22
18
  const tree = render(
23
19
  <ModalContent modalScreenProps={{ screenId: "river-1" }} />
24
20
  ).toJSON();
25
21
 
26
- expect(flatStyle(tree)).toMatchObject({
27
- paddingTop: 59,
28
- paddingBottom: 34,
29
- });
22
+ // The modal provides a SafeAreaProvider instead, so the presented screen
23
+ // reads the real insets itself rather than being padded from the outside.
24
+ expect(flatStyle(tree)).toEqual({ flex: 1 });
30
25
  });
31
26
 
32
27
  it("leaves a bottom sheet full-bleed", () => {
@@ -11,6 +11,9 @@ import { ModalProvider } from "../";
11
11
  */
12
12
  const mockSeenScreenData: any[] = [];
13
13
 
14
+ /** Records the `initialMetrics` each SafeAreaProvider the modal renders got. */
15
+ const mockSafeAreaProviderMetrics: any[] = [];
16
+
14
17
  jest.mock(
15
18
  "@applicaster/zapp-react-native-ui-components/Components/ScreenResolver",
16
19
  () => {
@@ -43,6 +46,14 @@ jest.mock("../ModalBottomSheet", () => ({ ModalBottomSheet: () => null }));
43
46
 
44
47
  jest.mock("react-native-safe-area-context", () => ({
45
48
  useSafeAreaInsets: () => ({ top: 0, bottom: 0, left: 0, right: 0 }),
49
+ // A modal renders in its own native window, so it needs its own provider
50
+ // seeded with the window metrics; without them insets read as zero inside.
51
+ initialWindowMetrics: { insets: { top: 59, bottom: 34, left: 0, right: 0 } },
52
+ SafeAreaProvider: ({ children, initialMetrics }: any) => {
53
+ mockSafeAreaProviderMetrics.push(initialMetrics);
54
+
55
+ return children;
56
+ },
46
57
  }));
47
58
 
48
59
  const river = { id: "river-parent-lock", type: "parent_lock" } as any;
@@ -54,6 +65,7 @@ const seen = () => mockSeenScreenData[mockSeenScreenData.length - 1];
54
65
  describe("ModalProvider", () => {
55
66
  beforeEach(() => {
56
67
  mockSeenScreenData.length = 0;
68
+ mockSafeAreaProviderMetrics.length = 0;
57
69
  modalStore.getState().dismissModal();
58
70
  });
59
71
 
@@ -72,4 +84,16 @@ describe("ModalProvider", () => {
72
84
 
73
85
  expect(seen()).toMatchObject({ screen: river });
74
86
  });
87
+
88
+ it("seeds the presented screen's safe area with the window metrics", () => {
89
+ const { initialWindowMetrics } = require("react-native-safe-area-context");
90
+
91
+ modalStore.getState().openModal({ item: river });
92
+
93
+ render(<ModalProvider />);
94
+
95
+ expect(mockSafeAreaProviderMetrics).toEqual(
96
+ expect.arrayContaining([initialWindowMetrics])
97
+ );
98
+ });
75
99
  });
@@ -1,5 +1,10 @@
1
1
  import * as React from "react";
2
2
 
3
+ import {
4
+ initialWindowMetrics,
5
+ SafeAreaProvider,
6
+ } from "react-native-safe-area-context";
7
+
3
8
  import { ModalPresenter } from "./ModalPresenter";
4
9
  import { ModalContent } from "./ModalContent";
5
10
  import { ModalChildrenWrapper } from "./ModalChildrenWrapper";
@@ -62,9 +67,11 @@ export function ModalProvider() {
62
67
  {...modalState?.options}
63
68
  statusBarTranslucent
64
69
  >
65
- <ModalChildrenWrapper>
66
- <ModalContent modalScreenProps={modalScreenProps} />
67
- </ModalChildrenWrapper>
70
+ <SafeAreaProvider initialMetrics={initialWindowMetrics}>
71
+ <ModalChildrenWrapper>
72
+ <ModalContent modalScreenProps={modalScreenProps} />
73
+ </ModalChildrenWrapper>
74
+ </SafeAreaProvider>
68
75
  </ModalPresenter>
69
76
  </ScreenContextProvider>
70
77
  </ScreenDataContext.Provider>
@@ -243,6 +243,10 @@ export function NavigationProvider({ children }: Props) {
243
243
  playNextOverlayState.current = state;
244
244
  };
245
245
 
246
+ // The play-next overlay owns remote focus while it is visible, so key
247
+ // handling (InteractionManager) needs to be able to ask for it.
248
+ const getPlayNextOverlay = () => playNextOverlayState.current ?? null;
249
+
246
250
  const setNestedScreenContent = (entry: ZappEntry, screenId: string) => {
247
251
  const screen = rivers[screenId];
248
252
  const parent = findParent(context, navigator.currentRoute);
@@ -349,10 +353,7 @@ export function NavigationProvider({ children }: Props) {
349
353
  state: firstRoute.state,
350
354
  };
351
355
 
352
- if (
353
- stateForBack?.state?.entry &&
354
- "home" in stateForBack?.state?.entry
355
- ) {
356
+ if (stateForBack?.state?.entry?.home) {
356
357
  goHome();
357
358
  } else {
358
359
  dispatch(back(stateForBack));
@@ -777,6 +778,7 @@ export function NavigationProvider({ children }: Props) {
777
778
  replace: replaceItem,
778
779
  setNestedScreenContent,
779
780
  setPlayNextOverlay,
781
+ getPlayNextOverlay,
780
782
  goHome,
781
783
  canGoBack,
782
784
  goBack,
@@ -410,6 +410,62 @@ describe("<NavigationProvider />", () => {
410
410
  });
411
411
  });
412
412
 
413
+ describe("when backToTop is requested from a non-home tab", () => {
414
+ const listenRiver = {
415
+ id: "LISTEN",
416
+ home: false,
417
+ navigations: [],
418
+ ui_components: [],
419
+ };
420
+
421
+ beforeEach(() => {
422
+ wrapper = renderContextConsumer({
423
+ ...store,
424
+ rivers: { ...rivers, LISTEN: listenRiver },
425
+ });
426
+
427
+ act(() => {
428
+ getNavigatorValues(wrapper).navigator.replace(listenRiver);
429
+ });
430
+ });
431
+
432
+ it("returns to the tab root from the second level", () => {
433
+ act(() => {
434
+ getNavigatorValues(wrapper).navigator.push(rivers.A1234);
435
+ });
436
+
437
+ act(() => {
438
+ getNavigatorValues(wrapper).navigator.goBack(false, false, true);
439
+ });
440
+
441
+ const { navigator, state } = getNavigatorValues(wrapper);
442
+
443
+ expect(navigator.currentRoute).toEqual("/river/LISTEN");
444
+ expect(navigator.activeRiver).toEqual(listenRiver);
445
+ expect(state.stack.mainStack).toHaveLength(1);
446
+ });
447
+
448
+ it("returns to the tab root from the third level", () => {
449
+ act(() => {
450
+ getNavigatorValues(wrapper).navigator.push(rivers.A1234);
451
+ });
452
+
453
+ act(() => {
454
+ getNavigatorValues(wrapper).navigator.push(rivers.C0987);
455
+ });
456
+
457
+ act(() => {
458
+ getNavigatorValues(wrapper).navigator.goBack(false, false, true);
459
+ });
460
+
461
+ const { navigator, state } = getNavigatorValues(wrapper);
462
+
463
+ expect(navigator.currentRoute).toEqual("/river/LISTEN");
464
+ expect(navigator.activeRiver).toEqual(listenRiver);
465
+ expect(state.stack.mainStack).toHaveLength(1);
466
+ });
467
+ });
468
+
413
469
  describe("when there is a previous route and there are hooks", () => {
414
470
  beforeEach(() => {
415
471
  wrapper = renderContextConsumer();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@applicaster/quick-brick-core",
3
- "version": "16.0.0-alpha.1593888345",
3
+ "version": "16.0.0-alpha.1701179921",
4
4
  "description": "Core package for Applicaster's Quick Brick App",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -28,13 +28,13 @@
28
28
  },
29
29
  "homepage": "https://github.com/applicaster/quickbrick#readme",
30
30
  "dependencies": {
31
- "@applicaster/applicaster-types": "16.0.0-alpha.1593888345",
32
- "@applicaster/quick-brick-core-plugins": "16.0.0-alpha.1593888345",
33
- "@applicaster/zapp-pipes-v2-client": "16.0.0-alpha.1593888345",
34
- "@applicaster/zapp-react-native-bridge": "16.0.0-alpha.1593888345",
35
- "@applicaster/zapp-react-native-redux": "16.0.0-alpha.1593888345",
36
- "@applicaster/zapp-react-native-ui-components": "16.0.0-alpha.1593888345",
37
- "@applicaster/zapp-react-native-utils": "16.0.0-alpha.1593888345",
31
+ "@applicaster/applicaster-types": "16.0.0-alpha.1701179921",
32
+ "@applicaster/quick-brick-core-plugins": "16.0.0-alpha.1701179921",
33
+ "@applicaster/zapp-pipes-v2-client": "16.0.0-alpha.1701179921",
34
+ "@applicaster/zapp-react-native-bridge": "16.0.0-alpha.1701179921",
35
+ "@applicaster/zapp-react-native-redux": "16.0.0-alpha.1701179921",
36
+ "@applicaster/zapp-react-native-ui-components": "16.0.0-alpha.1701179921",
37
+ "@applicaster/zapp-react-native-utils": "16.0.0-alpha.1701179921",
38
38
  "atob": "^2.1.2",
39
39
  "axios": "^0.28.0",
40
40
  "btoa": "^1.2.1",