@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.
- package/App/ModalProvider/ModalContent.tsx +3 -16
- package/App/ModalProvider/__tests__/ModalContent.test.tsx +4 -9
- package/App/ModalProvider/__tests__/index.test.tsx +24 -0
- package/App/ModalProvider/index.tsx +10 -3
- package/App/NavigationProvider/NavigationProvider.tsx +6 -4
- package/App/NavigationProvider/__tests__/navigationProvider.test.tsx +56 -0
- package/package.json +8 -8
|
@@ -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.
|
|
38
|
-
//
|
|
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("
|
|
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
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
-
<
|
|
66
|
-
<
|
|
67
|
-
|
|
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.
|
|
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.
|
|
32
|
-
"@applicaster/quick-brick-core-plugins": "16.0.0-alpha.
|
|
33
|
-
"@applicaster/zapp-pipes-v2-client": "16.0.0-alpha.
|
|
34
|
-
"@applicaster/zapp-react-native-bridge": "16.0.0-alpha.
|
|
35
|
-
"@applicaster/zapp-react-native-redux": "16.0.0-alpha.
|
|
36
|
-
"@applicaster/zapp-react-native-ui-components": "16.0.0-alpha.
|
|
37
|
-
"@applicaster/zapp-react-native-utils": "16.0.0-alpha.
|
|
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",
|