@rn-tools/sheets 0.1.4 → 3.0.2
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/CHANGELOG.md +16 -0
- package/README.md +107 -67
- package/android/src/main/java/expo/modules/sheets/RNToolsSheetsModule.kt +8 -4
- package/android/src/main/java/expo/modules/sheets/RNToolsSheetsView.kt +110 -76
- package/android/src/main/java/expo/modules/sheets/SheetProps.kt +2 -1
- package/ios/RNToolsSheets.podspec +3 -3
- package/ios/RNToolsSheetsModule.swift +27 -10
- package/ios/RNToolsSheetsView.swift +269 -224
- package/ios/Sources/RNToolsTouchHandlerHelper.h +15 -0
- package/ios/Sources/RNToolsTouchHandlerHelper.mm +31 -0
- package/mocks/expo-modules-core.mock.ts +9 -0
- package/package.json +10 -14
- package/src/index.ts +4 -1
- package/src/native-sheets-view.tsx +126 -42
- package/src/sheet-slot.tsx +70 -0
- package/src/sheets-client.test.tsx +239 -0
- package/src/sheets-client.tsx +233 -0
- package/src/sheets-provider.tsx +20 -0
- package/vitest.config.mts +25 -0
- package/ios/Sources/RNTSurfaceTouchHandlerWrapper.h +0 -11
- package/ios/Sources/RNTSurfaceTouchHandlerWrapper.mm +0 -43
package/src/index.ts
CHANGED
|
@@ -6,8 +6,11 @@ import {
|
|
|
6
6
|
ViewStyle,
|
|
7
7
|
Platform,
|
|
8
8
|
LayoutChangeEvent,
|
|
9
|
+
StyleSheet,
|
|
10
|
+
useWindowDimensions,
|
|
9
11
|
} from "react-native";
|
|
10
12
|
import { requireNativeViewManager } from "expo-modules-core";
|
|
13
|
+
import { useSafeAreaInsets } from "@rn-tools/core";
|
|
11
14
|
|
|
12
15
|
type SheetState = "DRAGGING" | "OPEN" | "SETTLING" | "HIDDEN";
|
|
13
16
|
|
|
@@ -17,25 +20,19 @@ type ChangeEvent<T extends SheetState, P = unknown> = {
|
|
|
17
20
|
};
|
|
18
21
|
|
|
19
22
|
type OpenChangeEvent = ChangeEvent<"OPEN", { index: number }>;
|
|
20
|
-
type DraggingChangeEvent = ChangeEvent<"DRAGGING">;
|
|
21
|
-
type SettlingChangeEvent = ChangeEvent<"SETTLING">;
|
|
22
23
|
type HiddenChangeEvent = ChangeEvent<"HIDDEN">;
|
|
23
24
|
|
|
24
|
-
type SheetChangeEvent =
|
|
25
|
-
| OpenChangeEvent
|
|
26
|
-
| DraggingChangeEvent
|
|
27
|
-
| SettlingChangeEvent
|
|
28
|
-
| HiddenChangeEvent;
|
|
25
|
+
export type SheetChangeEvent = OpenChangeEvent | HiddenChangeEvent;
|
|
29
26
|
|
|
30
27
|
type NativeOnChangeEvent = NativeSyntheticEvent<SheetChangeEvent>;
|
|
31
28
|
|
|
32
|
-
type AppearanceIOS = {
|
|
29
|
+
export type AppearanceIOS = {
|
|
33
30
|
grabberVisible?: boolean;
|
|
34
31
|
backgroundColor?: string;
|
|
35
32
|
cornerRadius?: number;
|
|
36
33
|
};
|
|
37
34
|
|
|
38
|
-
type AppearanceAndroid = {
|
|
35
|
+
export type AppearanceAndroid = {
|
|
39
36
|
dimAmount?: number;
|
|
40
37
|
cornerRadius?: number;
|
|
41
38
|
backgroundColor?: string;
|
|
@@ -45,8 +42,10 @@ type NativeSheetViewProps = {
|
|
|
45
42
|
children: React.ReactNode;
|
|
46
43
|
snapPoints?: number[];
|
|
47
44
|
isOpen: boolean;
|
|
48
|
-
|
|
45
|
+
initialIndex: number;
|
|
49
46
|
onDismiss: () => void;
|
|
47
|
+
canDismiss?: boolean;
|
|
48
|
+
onDismissPrevented: () => void;
|
|
50
49
|
onStateChange: (event: NativeOnChangeEvent) => void;
|
|
51
50
|
appearanceAndroid?: AppearanceAndroid;
|
|
52
51
|
appearanceIOS?: AppearanceIOS;
|
|
@@ -60,17 +59,16 @@ export type BottomSheetProps = {
|
|
|
60
59
|
containerStyle?: ViewStyle;
|
|
61
60
|
snapPoints?: number[];
|
|
62
61
|
isOpen: boolean;
|
|
63
|
-
|
|
64
|
-
|
|
62
|
+
initialIndex?: number;
|
|
63
|
+
setIsOpen: (isOpen: boolean) => void;
|
|
64
|
+
onDismissed?: () => void;
|
|
65
|
+
canDismiss?: boolean;
|
|
66
|
+
onDismissPrevented?: () => void;
|
|
65
67
|
onStateChange?: (event: SheetChangeEvent) => void;
|
|
66
68
|
appearanceAndroid?: AppearanceAndroid;
|
|
67
69
|
appearanceIOS?: AppearanceIOS;
|
|
68
70
|
};
|
|
69
71
|
|
|
70
|
-
// TODO:
|
|
71
|
-
// - get sheet container height from native side and clamp maxHeight to that value
|
|
72
|
-
//
|
|
73
|
-
|
|
74
72
|
export function BottomSheet(props: BottomSheetProps) {
|
|
75
73
|
const {
|
|
76
74
|
onStateChange,
|
|
@@ -78,12 +76,22 @@ export function BottomSheet(props: BottomSheetProps) {
|
|
|
78
76
|
snapPoints = [],
|
|
79
77
|
containerStyle,
|
|
80
78
|
isOpen,
|
|
81
|
-
|
|
82
|
-
|
|
79
|
+
initialIndex = 0,
|
|
80
|
+
setIsOpen,
|
|
81
|
+
onDismissed,
|
|
83
82
|
appearanceAndroid,
|
|
84
83
|
appearanceIOS,
|
|
84
|
+
canDismiss = true,
|
|
85
|
+
onDismissPrevented,
|
|
85
86
|
} = props;
|
|
86
87
|
|
|
88
|
+
const { height: windowHeight } = useWindowDimensions();
|
|
89
|
+
const insets = useSafeAreaInsets();
|
|
90
|
+
const maxSheetHeight = React.useMemo(
|
|
91
|
+
() => Math.max(0, windowHeight - insets.top - insets.bottom),
|
|
92
|
+
[windowHeight, insets.top, insets.bottom],
|
|
93
|
+
);
|
|
94
|
+
|
|
87
95
|
const [layout, setLayout] = React.useState<LayoutRectangle>({
|
|
88
96
|
height: 0,
|
|
89
97
|
width: 0,
|
|
@@ -91,13 +99,35 @@ export function BottomSheet(props: BottomSheetProps) {
|
|
|
91
99
|
y: 0,
|
|
92
100
|
});
|
|
93
101
|
|
|
102
|
+
const hasOpened = React.useRef(false);
|
|
103
|
+
|
|
94
104
|
const computedSnapPoints = React.useMemo(() => {
|
|
95
|
-
if (snapPoints.length === 0
|
|
96
|
-
|
|
105
|
+
if (snapPoints.length === 0) {
|
|
106
|
+
if (layout.height === 0) {
|
|
107
|
+
return [];
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return [Math.round(Math.min(layout.height, maxSheetHeight))];
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
let effectiveSnapPoints =
|
|
114
|
+
Platform.OS === "android" ? snapPoints.slice(0, 2) : [...snapPoints];
|
|
115
|
+
|
|
116
|
+
const snapPointsExceedingMaxHeight = snapPoints.filter(
|
|
117
|
+
(snapPoint) => snapPoint >= maxSheetHeight,
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
if (snapPointsExceedingMaxHeight.length > 0) {
|
|
121
|
+
effectiveSnapPoints = [
|
|
122
|
+
...effectiveSnapPoints.filter(
|
|
123
|
+
(snapPoint) => snapPoint < maxSheetHeight,
|
|
124
|
+
),
|
|
125
|
+
maxSheetHeight,
|
|
126
|
+
];
|
|
97
127
|
}
|
|
98
128
|
|
|
99
|
-
return
|
|
100
|
-
}, [
|
|
129
|
+
return effectiveSnapPoints.map((snapPoint) => Math.round(snapPoint));
|
|
130
|
+
}, [layout.height, maxSheetHeight, snapPoints]);
|
|
101
131
|
|
|
102
132
|
const maxHeight = React.useMemo(
|
|
103
133
|
() =>
|
|
@@ -110,38 +140,92 @@ export function BottomSheet(props: BottomSheetProps) {
|
|
|
110
140
|
const style = React.useMemo(() => {
|
|
111
141
|
return {
|
|
112
142
|
height: maxHeight,
|
|
143
|
+
borderTopLeftRadius: 16,
|
|
144
|
+
borderTopRightRadius: 16,
|
|
145
|
+
backgroundColor: "white",
|
|
113
146
|
...containerStyle,
|
|
114
147
|
};
|
|
115
|
-
}, [maxHeight]);
|
|
148
|
+
}, [maxHeight, containerStyle]);
|
|
149
|
+
|
|
150
|
+
const computedIsOpen = React.useMemo(
|
|
151
|
+
() => isOpen && computedSnapPoints.length > 0,
|
|
152
|
+
[isOpen, computedSnapPoints],
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
const notifyDismissed = React.useCallback(
|
|
156
|
+
() => {
|
|
157
|
+
if (hasOpened.current) {
|
|
158
|
+
setIsOpen(false);
|
|
159
|
+
}
|
|
160
|
+
onDismissed?.();
|
|
161
|
+
hasOpened.current = false;
|
|
162
|
+
},
|
|
163
|
+
[setIsOpen, onDismissed],
|
|
164
|
+
);
|
|
116
165
|
|
|
117
166
|
const handleOnDismiss = React.useCallback(() => {
|
|
118
|
-
|
|
119
|
-
}, []);
|
|
167
|
+
notifyDismissed();
|
|
168
|
+
}, [notifyDismissed]);
|
|
120
169
|
|
|
121
170
|
const handleStateChange = React.useCallback(
|
|
122
171
|
(event: NativeOnChangeEvent) => {
|
|
172
|
+
if (event.nativeEvent.type === "OPEN") {
|
|
173
|
+
hasOpened.current = true;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (event.nativeEvent.type === "HIDDEN") {
|
|
177
|
+
notifyDismissed();
|
|
178
|
+
}
|
|
179
|
+
|
|
123
180
|
onStateChange?.(event.nativeEvent);
|
|
124
181
|
},
|
|
125
|
-
[onStateChange],
|
|
182
|
+
[onStateChange, notifyDismissed],
|
|
183
|
+
);
|
|
184
|
+
|
|
185
|
+
const handleLayout = React.useCallback(
|
|
186
|
+
(event: LayoutChangeEvent) => {
|
|
187
|
+
setLayout(event.nativeEvent.layout);
|
|
188
|
+
},
|
|
189
|
+
[],
|
|
190
|
+
);
|
|
191
|
+
|
|
192
|
+
const handleDismissWithChanges = React.useCallback(() => {
|
|
193
|
+
onDismissPrevented?.();
|
|
194
|
+
}, [onDismissPrevented]);
|
|
195
|
+
|
|
196
|
+
const isAutosized = React.useMemo(
|
|
197
|
+
() => snapPoints.length === 0,
|
|
198
|
+
[snapPoints],
|
|
126
199
|
);
|
|
127
200
|
|
|
128
|
-
const
|
|
129
|
-
|
|
130
|
-
}, []);
|
|
201
|
+
const pointerEvents = React.useMemo(() => {
|
|
202
|
+
return isOpen ? "auto" : "none";
|
|
203
|
+
}, [isOpen]);
|
|
204
|
+
|
|
205
|
+
const innerStyle = React.useMemo(
|
|
206
|
+
() => (isAutosized ? undefined : StyleSheet.absoluteFill),
|
|
207
|
+
[isAutosized],
|
|
208
|
+
);
|
|
131
209
|
|
|
132
210
|
return (
|
|
133
|
-
<
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
{
|
|
144
|
-
|
|
145
|
-
|
|
211
|
+
<View style={StyleSheet.absoluteFill} pointerEvents={pointerEvents}>
|
|
212
|
+
<NativeSheetsView
|
|
213
|
+
isOpen={computedIsOpen}
|
|
214
|
+
canDismiss={canDismiss}
|
|
215
|
+
initialIndex={initialIndex}
|
|
216
|
+
onDismiss={handleOnDismiss}
|
|
217
|
+
onStateChange={handleStateChange}
|
|
218
|
+
onDismissPrevented={handleDismissWithChanges}
|
|
219
|
+
snapPoints={computedSnapPoints}
|
|
220
|
+
appearanceAndroid={appearanceAndroid}
|
|
221
|
+
appearanceIOS={appearanceIOS}
|
|
222
|
+
>
|
|
223
|
+
<View style={style} collapsable={false}>
|
|
224
|
+
<View onLayout={handleLayout} style={innerStyle} collapsable={false}>
|
|
225
|
+
{children}
|
|
226
|
+
</View>
|
|
227
|
+
</View>
|
|
228
|
+
</NativeSheetsView>
|
|
229
|
+
</View>
|
|
146
230
|
);
|
|
147
231
|
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { useStore } from "@rn-tools/core";
|
|
3
|
+
import { BottomSheet } from "./native-sheets-view";
|
|
4
|
+
import type { SheetChangeEvent } from "./native-sheets-view";
|
|
5
|
+
import { SheetsContext, SheetsStoreContext } from "./sheets-client";
|
|
6
|
+
import type { SheetEntry } from "./sheets-client";
|
|
7
|
+
|
|
8
|
+
function SheetSlotEntry({ entry }: { entry: SheetEntry }) {
|
|
9
|
+
const sheets = React.useContext(SheetsContext);
|
|
10
|
+
const isOpen = entry.status !== "closing";
|
|
11
|
+
|
|
12
|
+
const handleStateChange = React.useCallback(
|
|
13
|
+
(event: SheetChangeEvent) => {
|
|
14
|
+
if (event.type === "OPEN") {
|
|
15
|
+
sheets?.markDidOpen(entry.key);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (event.type === "HIDDEN") {
|
|
19
|
+
sheets?.markDidDismiss(entry.key);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
entry.options.onStateChange?.(event);
|
|
23
|
+
},
|
|
24
|
+
[sheets, entry.key, entry.options.onStateChange],
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
const handleSetIsOpen = React.useCallback(
|
|
28
|
+
(nextIsOpen: boolean) => {
|
|
29
|
+
if (!nextIsOpen) {
|
|
30
|
+
sheets?.dismiss(entry.key);
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
[sheets, entry.key],
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
const handleDismissed = React.useCallback(() => {
|
|
37
|
+
sheets?.markDidDismiss(entry.key);
|
|
38
|
+
}, [sheets, entry.key]);
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
<BottomSheet
|
|
42
|
+
isOpen={isOpen}
|
|
43
|
+
setIsOpen={handleSetIsOpen}
|
|
44
|
+
onDismissed={handleDismissed}
|
|
45
|
+
snapPoints={entry.options.snapPoints}
|
|
46
|
+
initialIndex={entry.options.initialIndex}
|
|
47
|
+
canDismiss={entry.options.canDismiss}
|
|
48
|
+
onDismissPrevented={entry.options.onDismissPrevented}
|
|
49
|
+
onStateChange={handleStateChange}
|
|
50
|
+
containerStyle={entry.options.containerStyle}
|
|
51
|
+
appearanceAndroid={entry.options.appearanceAndroid}
|
|
52
|
+
appearanceIOS={entry.options.appearanceIOS}
|
|
53
|
+
>
|
|
54
|
+
{entry.element}
|
|
55
|
+
</BottomSheet>
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function SheetSlot() {
|
|
60
|
+
const store = React.useContext(SheetsStoreContext);
|
|
61
|
+
const sheets = useStore(store, (state) => state.sheets);
|
|
62
|
+
|
|
63
|
+
return (
|
|
64
|
+
<>
|
|
65
|
+
{sheets.map((entry) => (
|
|
66
|
+
<SheetSlotEntry key={entry.key} entry={entry} />
|
|
67
|
+
))}
|
|
68
|
+
</>
|
|
69
|
+
);
|
|
70
|
+
}
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { describe, expect, it } from "vitest";
|
|
3
|
+
import { createSheets } from "./sheets-client";
|
|
4
|
+
|
|
5
|
+
describe("createSheets", () => {
|
|
6
|
+
it("returns the expected client API", () => {
|
|
7
|
+
const sheets = createSheets();
|
|
8
|
+
|
|
9
|
+
expect(sheets.store).toBeDefined();
|
|
10
|
+
expect(typeof sheets.present).toBe("function");
|
|
11
|
+
expect(typeof sheets.dismiss).toBe("function");
|
|
12
|
+
expect(typeof sheets.dismissAll).toBe("function");
|
|
13
|
+
expect(typeof sheets.remove).toBe("function");
|
|
14
|
+
expect(typeof sheets.markDidOpen).toBe("function");
|
|
15
|
+
expect(typeof sheets.markDidDismiss).toBe("function");
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it("starts with empty state", () => {
|
|
19
|
+
const sheets = createSheets();
|
|
20
|
+
expect(sheets.store.getState().sheets).toEqual([]);
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
describe("present", () => {
|
|
25
|
+
it("adds a new sheet in opening state", () => {
|
|
26
|
+
const sheets = createSheets();
|
|
27
|
+
const key = sheets.present(<span>hello</span>);
|
|
28
|
+
|
|
29
|
+
const state = sheets.store.getState().sheets;
|
|
30
|
+
expect(typeof key).toBe("string");
|
|
31
|
+
expect(state).toHaveLength(1);
|
|
32
|
+
expect(state[0].key).toBe(key);
|
|
33
|
+
expect(state[0].status).toBe("opening");
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("stores element and options", () => {
|
|
37
|
+
const sheets = createSheets();
|
|
38
|
+
const element = <span>content</span>;
|
|
39
|
+
const options = { id: "edit", snapPoints: [300, 500] };
|
|
40
|
+
|
|
41
|
+
sheets.present(element, options);
|
|
42
|
+
|
|
43
|
+
const entry = sheets.store.getState().sheets[0];
|
|
44
|
+
expect(entry.element).toBe(element);
|
|
45
|
+
expect(entry.options).toBe(options);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("reuses key and replaces entry when id already exists", () => {
|
|
49
|
+
const sheets = createSheets();
|
|
50
|
+
|
|
51
|
+
const key1 = sheets.present(<span>a</span>, { id: "edit", snapPoints: [240] });
|
|
52
|
+
sheets.markDidOpen(key1);
|
|
53
|
+
|
|
54
|
+
const key2 = sheets.present(<span>b</span>, { id: "edit", snapPoints: [320] });
|
|
55
|
+
|
|
56
|
+
const state = sheets.store.getState().sheets;
|
|
57
|
+
expect(key2).toBe(key1);
|
|
58
|
+
expect(state).toHaveLength(1);
|
|
59
|
+
expect(state[0].key).toBe(key1);
|
|
60
|
+
expect(state[0].status).toBe("opening");
|
|
61
|
+
expect(state[0].options.snapPoints).toEqual([320]);
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
describe("markDidOpen", () => {
|
|
66
|
+
it("transitions opening to open", () => {
|
|
67
|
+
const sheets = createSheets();
|
|
68
|
+
const key = sheets.present(<span>a</span>);
|
|
69
|
+
|
|
70
|
+
sheets.markDidOpen(key);
|
|
71
|
+
|
|
72
|
+
expect(sheets.store.getState().sheets[0].status).toBe("open");
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it("is a no-op for closing sheets", () => {
|
|
76
|
+
const sheets = createSheets();
|
|
77
|
+
const key = sheets.present(<span>a</span>);
|
|
78
|
+
sheets.dismiss(key);
|
|
79
|
+
|
|
80
|
+
const before = sheets.store.getState();
|
|
81
|
+
sheets.markDidOpen(key);
|
|
82
|
+
|
|
83
|
+
expect(sheets.store.getState()).toBe(before);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it("is a no-op for unknown key", () => {
|
|
87
|
+
const sheets = createSheets();
|
|
88
|
+
const before = sheets.store.getState();
|
|
89
|
+
|
|
90
|
+
sheets.markDidOpen("missing");
|
|
91
|
+
|
|
92
|
+
expect(sheets.store.getState()).toBe(before);
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
describe("dismiss", () => {
|
|
97
|
+
it("marks the top non-closing sheet as closing", () => {
|
|
98
|
+
const sheets = createSheets();
|
|
99
|
+
const keyA = sheets.present(<span>a</span>);
|
|
100
|
+
const keyB = sheets.present(<span>b</span>);
|
|
101
|
+
sheets.markDidOpen(keyA);
|
|
102
|
+
sheets.markDidOpen(keyB);
|
|
103
|
+
|
|
104
|
+
sheets.dismiss();
|
|
105
|
+
|
|
106
|
+
const state = sheets.store.getState().sheets;
|
|
107
|
+
expect(state[0].status).toBe("open");
|
|
108
|
+
expect(state[1].status).toBe("closing");
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it("can dismiss by id", () => {
|
|
112
|
+
const sheets = createSheets();
|
|
113
|
+
const key = sheets.present(<span>a</span>, { id: "edit" });
|
|
114
|
+
sheets.markDidOpen(key);
|
|
115
|
+
|
|
116
|
+
sheets.dismiss("edit");
|
|
117
|
+
|
|
118
|
+
expect(sheets.store.getState().sheets[0].status).toBe("closing");
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it("can dismiss by key", () => {
|
|
122
|
+
const sheets = createSheets();
|
|
123
|
+
const key = sheets.present(<span>a</span>);
|
|
124
|
+
sheets.markDidOpen(key);
|
|
125
|
+
|
|
126
|
+
sheets.dismiss(key);
|
|
127
|
+
|
|
128
|
+
expect(sheets.store.getState().sheets[0].status).toBe("closing");
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it("is a no-op when empty", () => {
|
|
132
|
+
const sheets = createSheets();
|
|
133
|
+
const before = sheets.store.getState();
|
|
134
|
+
|
|
135
|
+
sheets.dismiss();
|
|
136
|
+
|
|
137
|
+
expect(sheets.store.getState()).toBe(before);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it("is a no-op for unknown id", () => {
|
|
141
|
+
const sheets = createSheets();
|
|
142
|
+
sheets.present(<span>a</span>);
|
|
143
|
+
const before = sheets.store.getState();
|
|
144
|
+
|
|
145
|
+
sheets.dismiss("missing");
|
|
146
|
+
|
|
147
|
+
expect(sheets.store.getState()).toBe(before);
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
describe("markDidDismiss", () => {
|
|
152
|
+
it("removes a closing sheet", () => {
|
|
153
|
+
const sheets = createSheets();
|
|
154
|
+
const key = sheets.present(<span>a</span>);
|
|
155
|
+
sheets.markDidOpen(key);
|
|
156
|
+
sheets.dismiss(key);
|
|
157
|
+
|
|
158
|
+
sheets.markDidDismiss(key);
|
|
159
|
+
|
|
160
|
+
expect(sheets.store.getState().sheets).toHaveLength(0);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it("does not remove opening sheet", () => {
|
|
164
|
+
const sheets = createSheets();
|
|
165
|
+
const key = sheets.present(<span>a</span>);
|
|
166
|
+
const before = sheets.store.getState();
|
|
167
|
+
|
|
168
|
+
sheets.markDidDismiss(key);
|
|
169
|
+
|
|
170
|
+
expect(sheets.store.getState()).toBe(before);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it("is a no-op for unknown key", () => {
|
|
174
|
+
const sheets = createSheets();
|
|
175
|
+
const before = sheets.store.getState();
|
|
176
|
+
|
|
177
|
+
sheets.markDidDismiss("missing");
|
|
178
|
+
|
|
179
|
+
expect(sheets.store.getState()).toBe(before);
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
describe("dismissAll", () => {
|
|
184
|
+
it("marks every non-closing sheet as closing", () => {
|
|
185
|
+
const sheets = createSheets();
|
|
186
|
+
const keyA = sheets.present(<span>a</span>);
|
|
187
|
+
const keyB = sheets.present(<span>b</span>);
|
|
188
|
+
const keyC = sheets.present(<span>c</span>);
|
|
189
|
+
sheets.markDidOpen(keyA);
|
|
190
|
+
sheets.markDidOpen(keyB);
|
|
191
|
+
sheets.markDidOpen(keyC);
|
|
192
|
+
sheets.dismiss(keyB);
|
|
193
|
+
|
|
194
|
+
sheets.dismissAll();
|
|
195
|
+
|
|
196
|
+
const state = sheets.store.getState().sheets;
|
|
197
|
+
expect(state).toHaveLength(3);
|
|
198
|
+
expect(state.every((entry) => entry.status === "closing")).toBe(true);
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it("is a no-op when empty", () => {
|
|
202
|
+
const sheets = createSheets();
|
|
203
|
+
const before = sheets.store.getState();
|
|
204
|
+
|
|
205
|
+
sheets.dismissAll();
|
|
206
|
+
|
|
207
|
+
expect(sheets.store.getState()).toBe(before);
|
|
208
|
+
});
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
describe("remove", () => {
|
|
212
|
+
it("removes by key", () => {
|
|
213
|
+
const sheets = createSheets();
|
|
214
|
+
const key = sheets.present(<span>a</span>);
|
|
215
|
+
|
|
216
|
+
sheets.remove(key);
|
|
217
|
+
|
|
218
|
+
expect(sheets.store.getState().sheets).toHaveLength(0);
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
it("removes by id", () => {
|
|
222
|
+
const sheets = createSheets();
|
|
223
|
+
sheets.present(<span>a</span>, { id: "edit" });
|
|
224
|
+
|
|
225
|
+
sheets.remove("edit");
|
|
226
|
+
|
|
227
|
+
expect(sheets.store.getState().sheets).toHaveLength(0);
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
it("is a no-op when no match", () => {
|
|
231
|
+
const sheets = createSheets();
|
|
232
|
+
sheets.present(<span>a</span>);
|
|
233
|
+
const before = sheets.store.getState();
|
|
234
|
+
|
|
235
|
+
sheets.remove("missing");
|
|
236
|
+
|
|
237
|
+
expect(sheets.store.getState()).toBe(before);
|
|
238
|
+
});
|
|
239
|
+
});
|