react-native-nitro-modal 0.1.4 → 0.2.0
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/README.md +19 -7
- package/android/src/main/java/com/margelo/nitro/nitromodal/BottomSheetPresenter.kt +72 -21
- package/android/src/main/java/com/margelo/nitro/nitromodal/HybridNitroModal.kt +17 -3
- package/android/src/main/java/com/margelo/nitro/nitromodal/ModalPresenter.kt +121 -12
- package/android/src/main/java/com/margelo/nitro/nitromodal/PopupPresenter.kt +92 -31
- package/lib/module/NitroModal.js +1 -2
- package/lib/module/NitroModal.js.map +1 -1
- package/lib/module/NitroModalView.js +4 -3
- package/lib/module/NitroModalView.js.map +1 -1
- package/lib/module/NitroModalView.native.js +3 -0
- package/lib/module/NitroModalView.native.js.map +1 -1
- package/lib/module/NitroModalView.web.js +129 -0
- package/lib/module/NitroModalView.web.js.map +1 -0
- package/lib/module/web/AnimatedValue.js +123 -0
- package/lib/module/web/AnimatedValue.js.map +1 -0
- package/lib/module/web/ModalConfig.js +26 -0
- package/lib/module/web/ModalConfig.js.map +1 -0
- package/lib/module/web/ModalController.js +279 -0
- package/lib/module/web/ModalController.js.map +1 -0
- package/lib/module/web/ModalPresenter.js +245 -0
- package/lib/module/web/ModalPresenter.js.map +1 -0
- package/lib/module/web/PopupPresenter.js +117 -0
- package/lib/module/web/PopupPresenter.js.map +1 -0
- package/lib/module/web/SheetPresenter.js +469 -0
- package/lib/module/web/SheetPresenter.js.map +1 -0
- package/lib/module/web/dom.js +125 -0
- package/lib/module/web/dom.js.map +1 -0
- package/lib/typescript/src/NitroModal.d.ts +2 -2
- package/lib/typescript/src/NitroModal.d.ts.map +1 -1
- package/lib/typescript/src/NitroModalView.d.ts +2 -1
- package/lib/typescript/src/NitroModalView.d.ts.map +1 -1
- package/lib/typescript/src/NitroModalView.native.d.ts +1 -0
- package/lib/typescript/src/NitroModalView.native.d.ts.map +1 -1
- package/lib/typescript/src/NitroModalView.web.d.ts +6 -0
- package/lib/typescript/src/NitroModalView.web.d.ts.map +1 -0
- package/lib/typescript/src/web/AnimatedValue.d.ts +48 -0
- package/lib/typescript/src/web/AnimatedValue.d.ts.map +1 -0
- package/lib/typescript/src/web/ModalConfig.d.ts +24 -0
- package/lib/typescript/src/web/ModalConfig.d.ts.map +1 -0
- package/lib/typescript/src/web/ModalController.d.ts +65 -0
- package/lib/typescript/src/web/ModalController.d.ts.map +1 -0
- package/lib/typescript/src/web/ModalPresenter.d.ts +98 -0
- package/lib/typescript/src/web/ModalPresenter.d.ts.map +1 -0
- package/lib/typescript/src/web/PopupPresenter.d.ts +20 -0
- package/lib/typescript/src/web/PopupPresenter.d.ts.map +1 -0
- package/lib/typescript/src/web/SheetPresenter.d.ts +65 -0
- package/lib/typescript/src/web/SheetPresenter.d.ts.map +1 -0
- package/lib/typescript/src/web/dom.d.ts +40 -0
- package/lib/typescript/src/web/dom.d.ts.map +1 -0
- package/package.json +3 -2
- package/src/NitroModal.tsx +3 -4
- package/src/NitroModalView.native.tsx +4 -0
- package/src/NitroModalView.tsx +9 -4
- package/src/NitroModalView.web.tsx +138 -0
- package/src/web/AnimatedValue.ts +189 -0
- package/src/web/ModalConfig.ts +57 -0
- package/src/web/ModalController.ts +305 -0
- package/src/web/ModalPresenter.ts +298 -0
- package/src/web/PopupPresenter.ts +129 -0
- package/src/web/SheetPresenter.ts +582 -0
- package/src/web/dom.ts +154 -0
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/// <reference lib="dom" />
|
|
4
|
+
|
|
5
|
+
import { canUseDOM, estimateViewport, readSafeAreaInsets } from "./dom.js";
|
|
6
|
+
import { isSameConfig, makeConfig } from "./ModalConfig.js";
|
|
7
|
+
import { popupContentArea, PopupPresenter } from "./PopupPresenter.js";
|
|
8
|
+
import { sheetContentArea, SheetPresenter } from "./SheetPresenter.js";
|
|
9
|
+
/**
|
|
10
|
+
* The web counterpart of the native `HybridNitroModal`: receives the host
|
|
11
|
+
* view's props and drives one presenter per presentation.
|
|
12
|
+
*
|
|
13
|
+
* Presentation is a small state machine so that rapid `isOpen` toggles, user
|
|
14
|
+
* dismissals and unmounts always settle on a consistent state, and every
|
|
15
|
+
* accepted present request ends with exactly one `onDismiss`. Transitions are
|
|
16
|
+
* interrupted rather than queued: closing mid-enter turns the enter around,
|
|
17
|
+
* and opening mid-exit turns the exit around (that session carries on, so it
|
|
18
|
+
* gets no `onDismiss` and at most one `onPresent`).
|
|
19
|
+
*/
|
|
20
|
+
export class ModalController {
|
|
21
|
+
methods = {
|
|
22
|
+
present: () => this.requestPresent(),
|
|
23
|
+
dismiss: () => this.requestDismiss('programmatic'),
|
|
24
|
+
snapToDetent: index => {
|
|
25
|
+
if (Number.isFinite(index)) {
|
|
26
|
+
this.presenter?.snapToDetent(Math.trunc(index));
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
props = null;
|
|
31
|
+
config = null;
|
|
32
|
+
isOpen = false;
|
|
33
|
+
phase = 'idle';
|
|
34
|
+
/** What React (or an imperative call) asked for. */
|
|
35
|
+
wantsOpen = false;
|
|
36
|
+
/** A present request was accepted and its `onDismiss` is still owed. */
|
|
37
|
+
sessionActive = false;
|
|
38
|
+
/** `onPresent` was sent for the current session. */
|
|
39
|
+
presentSent = false;
|
|
40
|
+
dismissReason = 'programmatic';
|
|
41
|
+
reconcileScheduled = false;
|
|
42
|
+
isAttached = false;
|
|
43
|
+
presenter = null;
|
|
44
|
+
elements = null;
|
|
45
|
+
lastContentArea = null;
|
|
46
|
+
|
|
47
|
+
/** `setMounted` renders (or removes) the modal's elements. */
|
|
48
|
+
constructor(setMounted) {
|
|
49
|
+
this.setMounted = setMounted;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Host view lifecycle
|
|
53
|
+
|
|
54
|
+
attach() {
|
|
55
|
+
this.isAttached = true;
|
|
56
|
+
window.addEventListener('resize', this.handleWindowResize);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** The host view unmounted. Also runs for React's strict-mode remount, so `attach` must work again. */
|
|
60
|
+
detach() {
|
|
61
|
+
this.isAttached = false;
|
|
62
|
+
window.removeEventListener('resize', this.handleWindowResize);
|
|
63
|
+
this.presenter?.destroy();
|
|
64
|
+
this.presenter = null;
|
|
65
|
+
this.phase = 'idle';
|
|
66
|
+
this.wantsOpen = false;
|
|
67
|
+
this.sessionActive = false;
|
|
68
|
+
this.presentSent = false;
|
|
69
|
+
this.dismissReason = 'programmatic';
|
|
70
|
+
// Applied again from the props on the next `update`.
|
|
71
|
+
this.isOpen = false;
|
|
72
|
+
this.config = null;
|
|
73
|
+
this.lastContentArea = null;
|
|
74
|
+
this.setMounted(false);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** The host view rendered with new props. */
|
|
78
|
+
update(props) {
|
|
79
|
+
this.props = props;
|
|
80
|
+
const next = makeConfig(props);
|
|
81
|
+
if (!this.config || !isSameConfig(next, this.config)) {
|
|
82
|
+
this.config = next;
|
|
83
|
+
if (this.presenter) {
|
|
84
|
+
this.presenter.update(next);
|
|
85
|
+
} else {
|
|
86
|
+
this.reportEstimatedContentArea();
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
if (props.isOpen !== this.isOpen) {
|
|
90
|
+
this.isOpen = props.isOpen;
|
|
91
|
+
if (props.isOpen) {
|
|
92
|
+
this.requestPresent();
|
|
93
|
+
} else {
|
|
94
|
+
this.requestDismiss('programmatic');
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
elementsDidMount(elements) {
|
|
99
|
+
this.elements = elements;
|
|
100
|
+
if (this.presenter && !this.presenter.isShown) {
|
|
101
|
+
this.presenter.show(elements);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
elementsWillUnmount() {
|
|
105
|
+
this.elements = null;
|
|
106
|
+
}
|
|
107
|
+
contentDidRender() {
|
|
108
|
+
this.presenter?.contentDidRender();
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** Escape key, routed by React Native Web's `Modal` to the topmost modal. */
|
|
112
|
+
handleBackPress = () => {
|
|
113
|
+
// Already on its way out: the press is spent.
|
|
114
|
+
if (!this.isVisible) return;
|
|
115
|
+
this.props?.onBackButtonPress?.();
|
|
116
|
+
if (!this.config?.dismissOnBackButton) return;
|
|
117
|
+
this.requestDismiss('backButton');
|
|
118
|
+
this.reconcile();
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
// State machine
|
|
122
|
+
|
|
123
|
+
get isVisible() {
|
|
124
|
+
return this.phase === 'presenting' || this.phase === 'presented';
|
|
125
|
+
}
|
|
126
|
+
requestPresent() {
|
|
127
|
+
if (!this.isAttached) return;
|
|
128
|
+
this.wantsOpen = true;
|
|
129
|
+
this.sessionActive = true;
|
|
130
|
+
this.scheduleReconcile();
|
|
131
|
+
}
|
|
132
|
+
requestDismiss(reason) {
|
|
133
|
+
if (this.wantsOpen) {
|
|
134
|
+
this.dismissReason = reason;
|
|
135
|
+
}
|
|
136
|
+
this.wantsOpen = false;
|
|
137
|
+
this.scheduleReconcile();
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** Coalesces the requests made in one task, e.g. an open and a close in the same handler. */
|
|
141
|
+
scheduleReconcile() {
|
|
142
|
+
if (this.reconcileScheduled) return;
|
|
143
|
+
this.reconcileScheduled = true;
|
|
144
|
+
queueMicrotask(() => {
|
|
145
|
+
this.reconcileScheduled = false;
|
|
146
|
+
if (this.isAttached) {
|
|
147
|
+
this.reconcile();
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
reconcile() {
|
|
152
|
+
switch (this.phase) {
|
|
153
|
+
case 'presenting':
|
|
154
|
+
case 'presented':
|
|
155
|
+
if (!this.wantsOpen) {
|
|
156
|
+
this.startDismiss();
|
|
157
|
+
}
|
|
158
|
+
break;
|
|
159
|
+
case 'dismissing':
|
|
160
|
+
if (this.wantsOpen && this.presenter?.cancelDismiss()) {
|
|
161
|
+
this.phase = 'presenting';
|
|
162
|
+
this.dismissReason = 'programmatic';
|
|
163
|
+
}
|
|
164
|
+
break;
|
|
165
|
+
case 'idle':
|
|
166
|
+
if (this.wantsOpen) {
|
|
167
|
+
this.startPresent();
|
|
168
|
+
} else if (this.sessionActive) {
|
|
169
|
+
this.endSession(); // Cancelled before anything appeared.
|
|
170
|
+
}
|
|
171
|
+
break;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
startPresent() {
|
|
175
|
+
const {
|
|
176
|
+
config
|
|
177
|
+
} = this;
|
|
178
|
+
if (!canUseDOM || !config) return;
|
|
179
|
+
const presenter = config.mode === 'popup' ? new PopupPresenter(config, this) : new SheetPresenter(config, this);
|
|
180
|
+
this.presenter = presenter;
|
|
181
|
+
this.phase = 'presenting';
|
|
182
|
+
this.setMounted(true);
|
|
183
|
+
// Still mounted from a presentation that just ended.
|
|
184
|
+
if (this.elements) {
|
|
185
|
+
presenter.show(this.elements);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
startDismiss() {
|
|
189
|
+
const {
|
|
190
|
+
presenter
|
|
191
|
+
} = this;
|
|
192
|
+
if (!presenter?.isShown) {
|
|
193
|
+
// Its elements never mounted.
|
|
194
|
+
presenter?.destroy();
|
|
195
|
+
this.finishDismiss();
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
this.phase = 'dismissing';
|
|
199
|
+
presenter.dismiss();
|
|
200
|
+
}
|
|
201
|
+
finishDismiss() {
|
|
202
|
+
this.phase = 'idle';
|
|
203
|
+
this.presenter = null;
|
|
204
|
+
this.setMounted(false);
|
|
205
|
+
this.endSession();
|
|
206
|
+
this.reconcile();
|
|
207
|
+
}
|
|
208
|
+
endSession() {
|
|
209
|
+
if (!this.sessionActive) return;
|
|
210
|
+
const reason = this.dismissReason;
|
|
211
|
+
this.dismissReason = 'programmatic';
|
|
212
|
+
this.presentSent = false;
|
|
213
|
+
// A present requested while we were dismissing starts a new session.
|
|
214
|
+
this.sessionActive = this.wantsOpen;
|
|
215
|
+
if (this.isAttached) {
|
|
216
|
+
this.props?.onDismiss?.(reason);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// PresenterListener
|
|
221
|
+
|
|
222
|
+
onPresented() {
|
|
223
|
+
if (this.phase !== 'presenting') return;
|
|
224
|
+
this.phase = 'presented';
|
|
225
|
+
if (!this.presentSent) {
|
|
226
|
+
this.presentSent = true;
|
|
227
|
+
this.props?.onPresent?.();
|
|
228
|
+
}
|
|
229
|
+
this.reconcile();
|
|
230
|
+
}
|
|
231
|
+
onDismissed() {
|
|
232
|
+
this.finishDismiss();
|
|
233
|
+
}
|
|
234
|
+
onBackdropPress() {
|
|
235
|
+
this.props?.onBackdropPress?.();
|
|
236
|
+
if (!this.config?.dismissOnBackdropPress || !this.isVisible) return;
|
|
237
|
+
this.requestDismiss('backdrop');
|
|
238
|
+
this.reconcile();
|
|
239
|
+
}
|
|
240
|
+
onSwipeDismiss() {
|
|
241
|
+
if (!this.config?.dismissOnSwipe || !this.isVisible) return;
|
|
242
|
+
this.requestDismiss('swipe');
|
|
243
|
+
this.reconcile();
|
|
244
|
+
}
|
|
245
|
+
onDetentChange(index) {
|
|
246
|
+
this.props?.onDetentChange?.(index);
|
|
247
|
+
}
|
|
248
|
+
onContentAreaChange(width, height) {
|
|
249
|
+
const area = {
|
|
250
|
+
width: Math.floor(width),
|
|
251
|
+
height: Math.floor(height)
|
|
252
|
+
};
|
|
253
|
+
const last = this.lastContentArea;
|
|
254
|
+
if (area.width <= 0 || area.height <= 0) return;
|
|
255
|
+
if (last?.width === area.width && last.height === area.height) return;
|
|
256
|
+
this.lastContentArea = area;
|
|
257
|
+
this.props?.onContentAreaChange?.(area);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Helpers
|
|
261
|
+
|
|
262
|
+
/** Lets React size the content before the first presentation. */
|
|
263
|
+
reportEstimatedContentArea() {
|
|
264
|
+
const {
|
|
265
|
+
config
|
|
266
|
+
} = this;
|
|
267
|
+
if (!canUseDOM || !this.isAttached || !config) return;
|
|
268
|
+
const viewport = estimateViewport();
|
|
269
|
+
const insets = readSafeAreaInsets();
|
|
270
|
+
const area = config.mode === 'popup' ? popupContentArea(viewport, insets) : sheetContentArea(config.detents, viewport, insets);
|
|
271
|
+
this.onContentAreaChange(area.width, area.height);
|
|
272
|
+
}
|
|
273
|
+
handleWindowResize = () => {
|
|
274
|
+
if (!this.presenter) {
|
|
275
|
+
this.reportEstimatedContentArea();
|
|
276
|
+
}
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
//# sourceMappingURL=ModalController.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["canUseDOM","estimateViewport","readSafeAreaInsets","isSameConfig","makeConfig","popupContentArea","PopupPresenter","sheetContentArea","SheetPresenter","ModalController","methods","present","requestPresent","dismiss","requestDismiss","snapToDetent","index","Number","isFinite","presenter","Math","trunc","props","config","isOpen","phase","wantsOpen","sessionActive","presentSent","dismissReason","reconcileScheduled","isAttached","elements","lastContentArea","constructor","setMounted","attach","window","addEventListener","handleWindowResize","detach","removeEventListener","destroy","update","next","reportEstimatedContentArea","elementsDidMount","isShown","show","elementsWillUnmount","contentDidRender","handleBackPress","isVisible","onBackButtonPress","dismissOnBackButton","reconcile","scheduleReconcile","reason","queueMicrotask","startDismiss","cancelDismiss","startPresent","endSession","mode","finishDismiss","onDismiss","onPresented","onPresent","onDismissed","onBackdropPress","dismissOnBackdropPress","onSwipeDismiss","dismissOnSwipe","onDetentChange","onContentAreaChange","width","height","area","floor","last","viewport","insets","detents"],"sourceRoot":"../../../src","sources":["web/ModalController.ts"],"mappings":"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","ignoreList":[]}
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/// <reference lib="dom" />
|
|
4
|
+
|
|
5
|
+
import { canScrollWithin, clipOverflow, readKeyboardHeight, readSafeAreaInsets, toCSSColor, ZERO_INSETS } from "./dom.js";
|
|
6
|
+
|
|
7
|
+
/** DOM rendered by the web host view for one presentation. */
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Owns one presentation from show to dismiss. Subclasses lay out the surface
|
|
11
|
+
* and drive the enter/exit animations; this class tracks the viewport, the
|
|
12
|
+
* on-screen keyboard and the content size, and handles the backdrop and
|
|
13
|
+
* teardown.
|
|
14
|
+
*
|
|
15
|
+
* Transitions are interruptible: dismissing mid-enter turns the enter around,
|
|
16
|
+
* and `cancelDismiss` turns an exit back around. Once an exit starts the modal
|
|
17
|
+
* stops taking input.
|
|
18
|
+
*/
|
|
19
|
+
export class ModalPresenter {
|
|
20
|
+
elements = null;
|
|
21
|
+
viewport = {
|
|
22
|
+
width: 0,
|
|
23
|
+
height: 0
|
|
24
|
+
};
|
|
25
|
+
insets = ZERO_INSETS;
|
|
26
|
+
/** Part of the viewport covered by the on-screen keyboard, from the bottom. */
|
|
27
|
+
keyboardHeight = 0;
|
|
28
|
+
/** Measured size of the React content. */
|
|
29
|
+
contentSize = {
|
|
30
|
+
width: 0,
|
|
31
|
+
height: 0
|
|
32
|
+
};
|
|
33
|
+
entering = false;
|
|
34
|
+
isDismissing = false;
|
|
35
|
+
finished = false;
|
|
36
|
+
observedContent = null;
|
|
37
|
+
resizeObserver = null;
|
|
38
|
+
constructor(config, listener) {
|
|
39
|
+
this.config = config;
|
|
40
|
+
this.listener = listener;
|
|
41
|
+
}
|
|
42
|
+
get isShown() {
|
|
43
|
+
return this.elements != null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Takes over the rendered elements and starts the enter transition. */
|
|
47
|
+
show(elements) {
|
|
48
|
+
if (this.elements || this.finished) return;
|
|
49
|
+
this.elements = elements;
|
|
50
|
+
// The elements can outlive a previous presentation (an immediate re-present).
|
|
51
|
+
elements.overlay.style.pointerEvents = '';
|
|
52
|
+
elements.surface.style.overflow = clipOverflow();
|
|
53
|
+
elements.surface.style.opacity = '';
|
|
54
|
+
elements.surface.style.transform = '';
|
|
55
|
+
window.addEventListener('resize', this.handleResize);
|
|
56
|
+
window.visualViewport?.addEventListener('resize', this.handleKeyboard);
|
|
57
|
+
window.visualViewport?.addEventListener('scroll', this.handleKeyboard);
|
|
58
|
+
elements.backdrop.addEventListener('click', this.handleBackdropClick);
|
|
59
|
+
elements.overlay.addEventListener('wheel', this.handleWheel, {
|
|
60
|
+
passive: false
|
|
61
|
+
});
|
|
62
|
+
if (typeof ResizeObserver !== 'undefined') {
|
|
63
|
+
this.resizeObserver = new ResizeObserver(this.handleContentResize);
|
|
64
|
+
}
|
|
65
|
+
this.measureViewport();
|
|
66
|
+
this.observeContent();
|
|
67
|
+
this.applyBackdrop();
|
|
68
|
+
this.didShow();
|
|
69
|
+
this.entering = true;
|
|
70
|
+
this.animateIn();
|
|
71
|
+
}
|
|
72
|
+
update(config) {
|
|
73
|
+
const previous = this.config;
|
|
74
|
+
this.config = config;
|
|
75
|
+
if (!this.elements) return;
|
|
76
|
+
this.applyBackdrop();
|
|
77
|
+
this.configDidChange(previous);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** The React content re-rendered; its root element may have changed. */
|
|
81
|
+
contentDidRender() {
|
|
82
|
+
if (this.elements && this.observeContent()) {
|
|
83
|
+
this.contentSizeDidChange();
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Animated dismissal, also from mid-enter. `onDismissed` follows when it
|
|
89
|
+
* completes, unless `cancelDismiss` turns it around first.
|
|
90
|
+
*/
|
|
91
|
+
dismiss() {
|
|
92
|
+
const elements = this.elements;
|
|
93
|
+
if (!elements || this.isDismissing || this.finished) return;
|
|
94
|
+
this.isDismissing = true;
|
|
95
|
+
this.entering = false;
|
|
96
|
+
elements.overlay.style.pointerEvents = 'none';
|
|
97
|
+
// Also hides the on-screen keyboard.
|
|
98
|
+
const focused = document.activeElement;
|
|
99
|
+
if (focused instanceof HTMLElement && elements.overlay.contains(focused)) {
|
|
100
|
+
focused.blur();
|
|
101
|
+
}
|
|
102
|
+
this.animateOut(() => this.finish());
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Reverses a running exit from wherever it is. `onPresented` follows once
|
|
107
|
+
* it settles. Returns false when there is no exit to reverse.
|
|
108
|
+
*/
|
|
109
|
+
cancelDismiss() {
|
|
110
|
+
if (!this.elements || !this.isDismissing || this.finished) return false;
|
|
111
|
+
this.isDismissing = false;
|
|
112
|
+
this.elements.overlay.style.pointerEvents = '';
|
|
113
|
+
this.entering = true;
|
|
114
|
+
this.animateIn();
|
|
115
|
+
return true;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** Tears down without animating or notifying the listener. */
|
|
119
|
+
destroy() {
|
|
120
|
+
if (this.finished) return;
|
|
121
|
+
this.finished = true;
|
|
122
|
+
this.teardown();
|
|
123
|
+
}
|
|
124
|
+
snapToDetent(_index) {}
|
|
125
|
+
|
|
126
|
+
/** The elements are in place; lay out the hidden state. */
|
|
127
|
+
|
|
128
|
+
/** Animates from the current state to presented, then calls `notifyPresented`. */
|
|
129
|
+
|
|
130
|
+
/** Animates from the current state (possibly mid-enter) to gone, then calls `onEnd`. */
|
|
131
|
+
|
|
132
|
+
/** The viewport size, safe area or keyboard height changed. */
|
|
133
|
+
|
|
134
|
+
/** For subclasses: the running enter animation settled. */
|
|
135
|
+
notifyPresented() {
|
|
136
|
+
if (!this.entering || this.isDismissing || this.finished) return;
|
|
137
|
+
this.entering = false;
|
|
138
|
+
this.listener.onPresented();
|
|
139
|
+
}
|
|
140
|
+
reportContentArea(width, height) {
|
|
141
|
+
if (width > 0 && height > 0) {
|
|
142
|
+
this.listener.onContentAreaChange(width, height);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
teardown() {
|
|
146
|
+
this.stopAnimations();
|
|
147
|
+
window.removeEventListener('resize', this.handleResize);
|
|
148
|
+
window.visualViewport?.removeEventListener('resize', this.handleKeyboard);
|
|
149
|
+
window.visualViewport?.removeEventListener('scroll', this.handleKeyboard);
|
|
150
|
+
this.elements?.backdrop.removeEventListener('click', this.handleBackdropClick);
|
|
151
|
+
this.elements?.overlay.removeEventListener('wheel', this.handleWheel);
|
|
152
|
+
this.resizeObserver?.disconnect();
|
|
153
|
+
this.resizeObserver = null;
|
|
154
|
+
this.observedContent = null;
|
|
155
|
+
}
|
|
156
|
+
finish() {
|
|
157
|
+
if (this.finished) return;
|
|
158
|
+
this.finished = true;
|
|
159
|
+
this.teardown();
|
|
160
|
+
this.listener.onDismissed();
|
|
161
|
+
}
|
|
162
|
+
applyBackdrop() {
|
|
163
|
+
const {
|
|
164
|
+
backdrop,
|
|
165
|
+
surface
|
|
166
|
+
} = this.elements;
|
|
167
|
+
const {
|
|
168
|
+
config
|
|
169
|
+
} = this;
|
|
170
|
+
backdrop.style.backgroundColor = toCSSColor(config.backdropColor, config.backdropOpacity);
|
|
171
|
+
const blur = config.backdropBlurRadius > 0 ? `blur(${config.backdropBlurRadius}px)` : '';
|
|
172
|
+
backdrop.style.setProperty('backdrop-filter', blur);
|
|
173
|
+
backdrop.style.setProperty('-webkit-backdrop-filter', blur);
|
|
174
|
+
// `Canvas` follows the page's `color-scheme`, like a system background.
|
|
175
|
+
surface.style.backgroundColor = config.contentBackgroundColor != null ? toCSSColor(config.contentBackgroundColor) : 'Canvas';
|
|
176
|
+
}
|
|
177
|
+
measureViewport() {
|
|
178
|
+
const {
|
|
179
|
+
overlay
|
|
180
|
+
} = this.elements;
|
|
181
|
+
this.viewport = {
|
|
182
|
+
width: overlay.clientWidth,
|
|
183
|
+
height: overlay.clientHeight
|
|
184
|
+
};
|
|
185
|
+
this.insets = readSafeAreaInsets();
|
|
186
|
+
this.keyboardHeight = readKeyboardHeight(this.viewport.height);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/** Re-targets the observer at the content's root element. Returns whether its size changed. */
|
|
190
|
+
observeContent() {
|
|
191
|
+
const root = this.elements?.content.firstElementChild;
|
|
192
|
+
const element = root instanceof HTMLElement ? root : null;
|
|
193
|
+
if (element !== this.observedContent) {
|
|
194
|
+
if (this.observedContent) {
|
|
195
|
+
this.resizeObserver?.unobserve(this.observedContent);
|
|
196
|
+
}
|
|
197
|
+
this.observedContent = element;
|
|
198
|
+
if (element) {
|
|
199
|
+
this.resizeObserver?.observe(element);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
return this.measureContent();
|
|
203
|
+
}
|
|
204
|
+
measureContent() {
|
|
205
|
+
// Layout sizes, unaffected by the transition's transforms.
|
|
206
|
+
const width = this.observedContent?.offsetWidth ?? 0;
|
|
207
|
+
const height = this.observedContent?.offsetHeight ?? 0;
|
|
208
|
+
if (width === this.contentSize.width && height === this.contentSize.height) return false;
|
|
209
|
+
this.contentSize = {
|
|
210
|
+
width,
|
|
211
|
+
height
|
|
212
|
+
};
|
|
213
|
+
return true;
|
|
214
|
+
}
|
|
215
|
+
handleContentResize = () => {
|
|
216
|
+
if (this.measureContent()) {
|
|
217
|
+
this.contentSizeDidChange();
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
handleResize = () => {
|
|
221
|
+
if (!this.elements) return;
|
|
222
|
+
this.measureViewport();
|
|
223
|
+
this.viewportDidChange();
|
|
224
|
+
};
|
|
225
|
+
handleKeyboard = () => {
|
|
226
|
+
const height = readKeyboardHeight(this.viewport.height);
|
|
227
|
+
if (height === this.keyboardHeight) return;
|
|
228
|
+
this.keyboardHeight = height;
|
|
229
|
+
this.viewportDidChange();
|
|
230
|
+
};
|
|
231
|
+
handleBackdropClick = () => {
|
|
232
|
+
if (!this.isDismissing && !this.finished) {
|
|
233
|
+
this.listener.onBackdropPress();
|
|
234
|
+
}
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
/** Keeps the page behind the modal from scrolling; scrollable content still scrolls. */
|
|
238
|
+
handleWheel = event => {
|
|
239
|
+
if (event.ctrlKey || Math.abs(event.deltaX) > Math.abs(event.deltaY)) return;
|
|
240
|
+
if (!canScrollWithin(event.target, this.elements.overlay, event.deltaY)) {
|
|
241
|
+
event.preventDefault();
|
|
242
|
+
}
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
//# sourceMappingURL=ModalPresenter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["canScrollWithin","clipOverflow","readKeyboardHeight","readSafeAreaInsets","toCSSColor","ZERO_INSETS","ModalPresenter","elements","viewport","width","height","insets","keyboardHeight","contentSize","entering","isDismissing","finished","observedContent","resizeObserver","constructor","config","listener","isShown","show","overlay","style","pointerEvents","surface","overflow","opacity","transform","window","addEventListener","handleResize","visualViewport","handleKeyboard","backdrop","handleBackdropClick","handleWheel","passive","ResizeObserver","handleContentResize","measureViewport","observeContent","applyBackdrop","didShow","animateIn","update","previous","configDidChange","contentDidRender","contentSizeDidChange","dismiss","focused","document","activeElement","HTMLElement","contains","blur","animateOut","finish","cancelDismiss","destroy","teardown","snapToDetent","_index","notifyPresented","onPresented","reportContentArea","onContentAreaChange","stopAnimations","removeEventListener","disconnect","onDismissed","backgroundColor","backdropColor","backdropOpacity","backdropBlurRadius","setProperty","contentBackgroundColor","clientWidth","clientHeight","root","content","firstElementChild","element","unobserve","observe","measureContent","offsetWidth","offsetHeight","viewportDidChange","onBackdropPress","event","ctrlKey","Math","abs","deltaX","deltaY","target","preventDefault"],"sourceRoot":"../../../src","sources":["web/ModalPresenter.ts"],"mappings":"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","ignoreList":[]}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/// <reference lib="dom" />
|
|
4
|
+
|
|
5
|
+
import { AnimatedValue, INSTANT } from "./AnimatedValue.js";
|
|
6
|
+
import { prefersReducedMotion } from "./dom.js";
|
|
7
|
+
import { ModalPresenter } from "./ModalPresenter.js";
|
|
8
|
+
const MARGIN = 16;
|
|
9
|
+
const DEFAULT_CORNER_RADIUS = 16;
|
|
10
|
+
const ENTER_SCALE = 0.9;
|
|
11
|
+
const ENTER = {
|
|
12
|
+
type: 'spring',
|
|
13
|
+
response: 0.3,
|
|
14
|
+
dampingRatio: 0.82
|
|
15
|
+
};
|
|
16
|
+
const EXIT = {
|
|
17
|
+
type: 'timing',
|
|
18
|
+
duration: 200,
|
|
19
|
+
easing: t => t * t
|
|
20
|
+
};
|
|
21
|
+
/** Area a card may occupy, clear of the safe area and the part of the keyboard above it. */
|
|
22
|
+
function availableRect(viewport, insets, keyboardHeight) {
|
|
23
|
+
const covered = Math.max(0, keyboardHeight - insets.bottom);
|
|
24
|
+
return {
|
|
25
|
+
x: insets.left + MARGIN,
|
|
26
|
+
y: insets.top + MARGIN,
|
|
27
|
+
width: Math.max(0, viewport.width - insets.left - insets.right - 2 * MARGIN),
|
|
28
|
+
height: Math.max(0, viewport.height - insets.top - insets.bottom - 2 * MARGIN - covered)
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Best guess of the content area before the popup is on screen. */
|
|
33
|
+
export function popupContentArea(viewport, insets) {
|
|
34
|
+
const {
|
|
35
|
+
width,
|
|
36
|
+
height
|
|
37
|
+
} = availableRect(viewport, insets, 0);
|
|
38
|
+
return {
|
|
39
|
+
width,
|
|
40
|
+
height
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** `popup` mode: a centered card over the backdrop with a fade/scale transition. */
|
|
45
|
+
export class PopupPresenter extends ModalPresenter {
|
|
46
|
+
/** 0 hidden, 1 presented. */
|
|
47
|
+
progress = new AnimatedValue(0, 0.001, value => this.render(value));
|
|
48
|
+
didShow() {
|
|
49
|
+
this.layout();
|
|
50
|
+
this.progress.set(0);
|
|
51
|
+
}
|
|
52
|
+
animateIn() {
|
|
53
|
+
this.progress.animateTo(1, this.isAnimated ? ENTER : INSTANT, {
|
|
54
|
+
onEnd: () => this.notifyPresented()
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
animateOut(onEnd) {
|
|
58
|
+
this.progress.animateTo(0, this.isAnimated ? EXIT : INSTANT, {
|
|
59
|
+
onEnd
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
configDidChange() {
|
|
63
|
+
this.layout();
|
|
64
|
+
this.render(this.progress.value);
|
|
65
|
+
}
|
|
66
|
+
viewportDidChange() {
|
|
67
|
+
this.layout();
|
|
68
|
+
}
|
|
69
|
+
contentSizeDidChange() {
|
|
70
|
+
this.layout();
|
|
71
|
+
}
|
|
72
|
+
stopAnimations() {
|
|
73
|
+
this.progress.stop();
|
|
74
|
+
}
|
|
75
|
+
get isAnimated() {
|
|
76
|
+
return this.config.popupAnimation !== 'none' && !prefersReducedMotion();
|
|
77
|
+
}
|
|
78
|
+
layout() {
|
|
79
|
+
const {
|
|
80
|
+
surface,
|
|
81
|
+
content,
|
|
82
|
+
grabber
|
|
83
|
+
} = this.elements;
|
|
84
|
+
const behavior = this.config.keyboardBehavior;
|
|
85
|
+
const available = availableRect(this.viewport, this.insets, behavior === 'none' ? 0 : this.keyboardHeight);
|
|
86
|
+
const width = Math.min(this.contentSize.width, available.width);
|
|
87
|
+
const height = Math.min(this.contentSize.height, available.height);
|
|
88
|
+
Object.assign(surface.style, {
|
|
89
|
+
left: `${available.x + (available.width - width) / 2}px`,
|
|
90
|
+
top: `${available.y + (available.height - height) / 2}px`,
|
|
91
|
+
width: `${width}px`,
|
|
92
|
+
height: `${height}px`,
|
|
93
|
+
borderRadius: `${this.config.cornerRadius ?? DEFAULT_CORNER_RADIUS}px`
|
|
94
|
+
});
|
|
95
|
+
grabber.style.display = 'none';
|
|
96
|
+
|
|
97
|
+
// The content host also bounds the content's own (shrink-to-fit) width.
|
|
98
|
+
const area = behavior === 'resize' ? available : availableRect(this.viewport, this.insets, 0);
|
|
99
|
+
content.style.width = `${area.width}px`;
|
|
100
|
+
content.style.height = `${area.height}px`;
|
|
101
|
+
this.reportContentArea(area.width, area.height);
|
|
102
|
+
}
|
|
103
|
+
render(progress) {
|
|
104
|
+
if (!this.elements) return;
|
|
105
|
+
const {
|
|
106
|
+
surface,
|
|
107
|
+
backdrop
|
|
108
|
+
} = this.elements;
|
|
109
|
+
const visible = Math.min(Math.max(progress, 0), 1);
|
|
110
|
+
const animation = this.config.popupAnimation;
|
|
111
|
+
backdrop.style.opacity = String(visible);
|
|
112
|
+
surface.style.opacity = animation === 'none' ? '1' : String(visible);
|
|
113
|
+
const scale = animation === 'scale' ? ENTER_SCALE + (1 - ENTER_SCALE) * progress : 1;
|
|
114
|
+
surface.style.transform = `scale(${scale})`;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
//# sourceMappingURL=PopupPresenter.js.map
|