react-native-nitro-modal 0.1.5 → 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 +17 -7
- 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,298 @@
|
|
|
1
|
+
/// <reference lib="dom" />
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
canScrollWithin,
|
|
5
|
+
clipOverflow,
|
|
6
|
+
readKeyboardHeight,
|
|
7
|
+
readSafeAreaInsets,
|
|
8
|
+
toCSSColor,
|
|
9
|
+
ZERO_INSETS,
|
|
10
|
+
type Insets,
|
|
11
|
+
type Size,
|
|
12
|
+
} from './dom';
|
|
13
|
+
import type { ModalConfig } from './ModalConfig';
|
|
14
|
+
|
|
15
|
+
/** DOM rendered by the web host view for one presentation. */
|
|
16
|
+
export interface ModalElements {
|
|
17
|
+
/** Full-window layer holding the backdrop and the surface. */
|
|
18
|
+
overlay: HTMLElement;
|
|
19
|
+
backdrop: HTMLElement;
|
|
20
|
+
/** The sheet or the popup card. */
|
|
21
|
+
surface: HTMLElement;
|
|
22
|
+
/** Hosts the React content. */
|
|
23
|
+
content: HTMLElement;
|
|
24
|
+
grabber: HTMLElement;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface PresenterListener {
|
|
28
|
+
/** An enter transition settled: the first one, or one that reversed an exit. */
|
|
29
|
+
onPresented(): void;
|
|
30
|
+
/** The exit transition finished. Called at most once per presenter. */
|
|
31
|
+
onDismissed(): void;
|
|
32
|
+
onBackdropPress(): void;
|
|
33
|
+
/** The user swiped the sheet down far enough to dismiss it. */
|
|
34
|
+
onSwipeDismiss(): void;
|
|
35
|
+
onDetentChange(index: number): void;
|
|
36
|
+
onContentAreaChange(width: number, height: number): void;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Owns one presentation from show to dismiss. Subclasses lay out the surface
|
|
41
|
+
* and drive the enter/exit animations; this class tracks the viewport, the
|
|
42
|
+
* on-screen keyboard and the content size, and handles the backdrop and
|
|
43
|
+
* teardown.
|
|
44
|
+
*
|
|
45
|
+
* Transitions are interruptible: dismissing mid-enter turns the enter around,
|
|
46
|
+
* and `cancelDismiss` turns an exit back around. Once an exit starts the modal
|
|
47
|
+
* stops taking input.
|
|
48
|
+
*/
|
|
49
|
+
export abstract class ModalPresenter {
|
|
50
|
+
protected elements: ModalElements | null = null;
|
|
51
|
+
protected viewport: Size = { width: 0, height: 0 };
|
|
52
|
+
protected insets: Insets = ZERO_INSETS;
|
|
53
|
+
/** Part of the viewport covered by the on-screen keyboard, from the bottom. */
|
|
54
|
+
protected keyboardHeight = 0;
|
|
55
|
+
/** Measured size of the React content. */
|
|
56
|
+
protected contentSize: Size = { width: 0, height: 0 };
|
|
57
|
+
protected entering = false;
|
|
58
|
+
protected isDismissing = false;
|
|
59
|
+
private finished = false;
|
|
60
|
+
private observedContent: HTMLElement | null = null;
|
|
61
|
+
private resizeObserver: ResizeObserver | null = null;
|
|
62
|
+
|
|
63
|
+
constructor(
|
|
64
|
+
protected config: ModalConfig,
|
|
65
|
+
protected readonly listener: PresenterListener
|
|
66
|
+
) {}
|
|
67
|
+
|
|
68
|
+
get isShown(): boolean {
|
|
69
|
+
return this.elements != null;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Takes over the rendered elements and starts the enter transition. */
|
|
73
|
+
show(elements: ModalElements) {
|
|
74
|
+
if (this.elements || this.finished) return;
|
|
75
|
+
this.elements = elements;
|
|
76
|
+
// The elements can outlive a previous presentation (an immediate re-present).
|
|
77
|
+
elements.overlay.style.pointerEvents = '';
|
|
78
|
+
elements.surface.style.overflow = clipOverflow();
|
|
79
|
+
elements.surface.style.opacity = '';
|
|
80
|
+
elements.surface.style.transform = '';
|
|
81
|
+
|
|
82
|
+
window.addEventListener('resize', this.handleResize);
|
|
83
|
+
window.visualViewport?.addEventListener('resize', this.handleKeyboard);
|
|
84
|
+
window.visualViewport?.addEventListener('scroll', this.handleKeyboard);
|
|
85
|
+
elements.backdrop.addEventListener('click', this.handleBackdropClick);
|
|
86
|
+
elements.overlay.addEventListener('wheel', this.handleWheel, {
|
|
87
|
+
passive: false,
|
|
88
|
+
});
|
|
89
|
+
if (typeof ResizeObserver !== 'undefined') {
|
|
90
|
+
this.resizeObserver = new ResizeObserver(this.handleContentResize);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
this.measureViewport();
|
|
94
|
+
this.observeContent();
|
|
95
|
+
this.applyBackdrop();
|
|
96
|
+
this.didShow();
|
|
97
|
+
this.entering = true;
|
|
98
|
+
this.animateIn();
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
update(config: ModalConfig) {
|
|
102
|
+
const previous = this.config;
|
|
103
|
+
this.config = config;
|
|
104
|
+
if (!this.elements) return;
|
|
105
|
+
this.applyBackdrop();
|
|
106
|
+
this.configDidChange(previous);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** The React content re-rendered; its root element may have changed. */
|
|
110
|
+
contentDidRender() {
|
|
111
|
+
if (this.elements && this.observeContent()) {
|
|
112
|
+
this.contentSizeDidChange();
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Animated dismissal, also from mid-enter. `onDismissed` follows when it
|
|
118
|
+
* completes, unless `cancelDismiss` turns it around first.
|
|
119
|
+
*/
|
|
120
|
+
dismiss() {
|
|
121
|
+
const elements = this.elements;
|
|
122
|
+
if (!elements || this.isDismissing || this.finished) return;
|
|
123
|
+
this.isDismissing = true;
|
|
124
|
+
this.entering = false;
|
|
125
|
+
elements.overlay.style.pointerEvents = 'none';
|
|
126
|
+
// Also hides the on-screen keyboard.
|
|
127
|
+
const focused = document.activeElement;
|
|
128
|
+
if (focused instanceof HTMLElement && elements.overlay.contains(focused)) {
|
|
129
|
+
focused.blur();
|
|
130
|
+
}
|
|
131
|
+
this.animateOut(() => this.finish());
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Reverses a running exit from wherever it is. `onPresented` follows once
|
|
136
|
+
* it settles. Returns false when there is no exit to reverse.
|
|
137
|
+
*/
|
|
138
|
+
cancelDismiss(): boolean {
|
|
139
|
+
if (!this.elements || !this.isDismissing || this.finished) return false;
|
|
140
|
+
this.isDismissing = false;
|
|
141
|
+
this.elements.overlay.style.pointerEvents = '';
|
|
142
|
+
this.entering = true;
|
|
143
|
+
this.animateIn();
|
|
144
|
+
return true;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/** Tears down without animating or notifying the listener. */
|
|
148
|
+
destroy() {
|
|
149
|
+
if (this.finished) return;
|
|
150
|
+
this.finished = true;
|
|
151
|
+
this.teardown();
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
snapToDetent(_index: number) {}
|
|
155
|
+
|
|
156
|
+
/** The elements are in place; lay out the hidden state. */
|
|
157
|
+
protected abstract didShow(): void;
|
|
158
|
+
|
|
159
|
+
/** Animates from the current state to presented, then calls `notifyPresented`. */
|
|
160
|
+
protected abstract animateIn(): void;
|
|
161
|
+
|
|
162
|
+
/** Animates from the current state (possibly mid-enter) to gone, then calls `onEnd`. */
|
|
163
|
+
protected abstract animateOut(onEnd: () => void): void;
|
|
164
|
+
|
|
165
|
+
protected abstract configDidChange(previous: ModalConfig): void;
|
|
166
|
+
|
|
167
|
+
/** The viewport size, safe area or keyboard height changed. */
|
|
168
|
+
protected abstract viewportDidChange(): void;
|
|
169
|
+
|
|
170
|
+
protected abstract contentSizeDidChange(): void;
|
|
171
|
+
|
|
172
|
+
protected abstract stopAnimations(): void;
|
|
173
|
+
|
|
174
|
+
/** For subclasses: the running enter animation settled. */
|
|
175
|
+
protected notifyPresented() {
|
|
176
|
+
if (!this.entering || this.isDismissing || this.finished) return;
|
|
177
|
+
this.entering = false;
|
|
178
|
+
this.listener.onPresented();
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
protected reportContentArea(width: number, height: number) {
|
|
182
|
+
if (width > 0 && height > 0) {
|
|
183
|
+
this.listener.onContentAreaChange(width, height);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
protected teardown() {
|
|
188
|
+
this.stopAnimations();
|
|
189
|
+
window.removeEventListener('resize', this.handleResize);
|
|
190
|
+
window.visualViewport?.removeEventListener('resize', this.handleKeyboard);
|
|
191
|
+
window.visualViewport?.removeEventListener('scroll', this.handleKeyboard);
|
|
192
|
+
this.elements?.backdrop.removeEventListener(
|
|
193
|
+
'click',
|
|
194
|
+
this.handleBackdropClick
|
|
195
|
+
);
|
|
196
|
+
this.elements?.overlay.removeEventListener('wheel', this.handleWheel);
|
|
197
|
+
this.resizeObserver?.disconnect();
|
|
198
|
+
this.resizeObserver = null;
|
|
199
|
+
this.observedContent = null;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
private finish() {
|
|
203
|
+
if (this.finished) return;
|
|
204
|
+
this.finished = true;
|
|
205
|
+
this.teardown();
|
|
206
|
+
this.listener.onDismissed();
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
private applyBackdrop() {
|
|
210
|
+
const { backdrop, surface } = this.elements!;
|
|
211
|
+
const { config } = this;
|
|
212
|
+
backdrop.style.backgroundColor = toCSSColor(
|
|
213
|
+
config.backdropColor,
|
|
214
|
+
config.backdropOpacity
|
|
215
|
+
);
|
|
216
|
+
const blur =
|
|
217
|
+
config.backdropBlurRadius > 0
|
|
218
|
+
? `blur(${config.backdropBlurRadius}px)`
|
|
219
|
+
: '';
|
|
220
|
+
backdrop.style.setProperty('backdrop-filter', blur);
|
|
221
|
+
backdrop.style.setProperty('-webkit-backdrop-filter', blur);
|
|
222
|
+
// `Canvas` follows the page's `color-scheme`, like a system background.
|
|
223
|
+
surface.style.backgroundColor =
|
|
224
|
+
config.contentBackgroundColor != null
|
|
225
|
+
? toCSSColor(config.contentBackgroundColor)
|
|
226
|
+
: 'Canvas';
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
private measureViewport() {
|
|
230
|
+
const { overlay } = this.elements!;
|
|
231
|
+
this.viewport = {
|
|
232
|
+
width: overlay.clientWidth,
|
|
233
|
+
height: overlay.clientHeight,
|
|
234
|
+
};
|
|
235
|
+
this.insets = readSafeAreaInsets();
|
|
236
|
+
this.keyboardHeight = readKeyboardHeight(this.viewport.height);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/** Re-targets the observer at the content's root element. Returns whether its size changed. */
|
|
240
|
+
private observeContent(): boolean {
|
|
241
|
+
const root = this.elements?.content.firstElementChild;
|
|
242
|
+
const element = root instanceof HTMLElement ? root : null;
|
|
243
|
+
if (element !== this.observedContent) {
|
|
244
|
+
if (this.observedContent) {
|
|
245
|
+
this.resizeObserver?.unobserve(this.observedContent);
|
|
246
|
+
}
|
|
247
|
+
this.observedContent = element;
|
|
248
|
+
if (element) {
|
|
249
|
+
this.resizeObserver?.observe(element);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
return this.measureContent();
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
private measureContent(): boolean {
|
|
256
|
+
// Layout sizes, unaffected by the transition's transforms.
|
|
257
|
+
const width = this.observedContent?.offsetWidth ?? 0;
|
|
258
|
+
const height = this.observedContent?.offsetHeight ?? 0;
|
|
259
|
+
if (width === this.contentSize.width && height === this.contentSize.height)
|
|
260
|
+
return false;
|
|
261
|
+
this.contentSize = { width, height };
|
|
262
|
+
return true;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
private handleContentResize = () => {
|
|
266
|
+
if (this.measureContent()) {
|
|
267
|
+
this.contentSizeDidChange();
|
|
268
|
+
}
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
private handleResize = () => {
|
|
272
|
+
if (!this.elements) return;
|
|
273
|
+
this.measureViewport();
|
|
274
|
+
this.viewportDidChange();
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
private handleKeyboard = () => {
|
|
278
|
+
const height = readKeyboardHeight(this.viewport.height);
|
|
279
|
+
if (height === this.keyboardHeight) return;
|
|
280
|
+
this.keyboardHeight = height;
|
|
281
|
+
this.viewportDidChange();
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
private handleBackdropClick = () => {
|
|
285
|
+
if (!this.isDismissing && !this.finished) {
|
|
286
|
+
this.listener.onBackdropPress();
|
|
287
|
+
}
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
/** Keeps the page behind the modal from scrolling; scrollable content still scrolls. */
|
|
291
|
+
private handleWheel = (event: WheelEvent) => {
|
|
292
|
+
if (event.ctrlKey || Math.abs(event.deltaX) > Math.abs(event.deltaY))
|
|
293
|
+
return;
|
|
294
|
+
if (!canScrollWithin(event.target, this.elements!.overlay, event.deltaY)) {
|
|
295
|
+
event.preventDefault();
|
|
296
|
+
}
|
|
297
|
+
};
|
|
298
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/// <reference lib="dom" />
|
|
2
|
+
|
|
3
|
+
import { AnimatedValue, INSTANT, type Motion } from './AnimatedValue';
|
|
4
|
+
import { prefersReducedMotion, type Insets, type Size } from './dom';
|
|
5
|
+
import { ModalPresenter } from './ModalPresenter';
|
|
6
|
+
|
|
7
|
+
const MARGIN = 16;
|
|
8
|
+
const DEFAULT_CORNER_RADIUS = 16;
|
|
9
|
+
const ENTER_SCALE = 0.9;
|
|
10
|
+
|
|
11
|
+
const ENTER: Motion = { type: 'spring', response: 0.3, dampingRatio: 0.82 };
|
|
12
|
+
const EXIT: Motion = { type: 'timing', duration: 200, easing: (t) => t * t };
|
|
13
|
+
|
|
14
|
+
interface Rect extends Size {
|
|
15
|
+
x: number;
|
|
16
|
+
y: number;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Area a card may occupy, clear of the safe area and the part of the keyboard above it. */
|
|
20
|
+
function availableRect(
|
|
21
|
+
viewport: Size,
|
|
22
|
+
insets: Insets,
|
|
23
|
+
keyboardHeight: number
|
|
24
|
+
): Rect {
|
|
25
|
+
const covered = Math.max(0, keyboardHeight - insets.bottom);
|
|
26
|
+
return {
|
|
27
|
+
x: insets.left + MARGIN,
|
|
28
|
+
y: insets.top + MARGIN,
|
|
29
|
+
width: Math.max(
|
|
30
|
+
0,
|
|
31
|
+
viewport.width - insets.left - insets.right - 2 * MARGIN
|
|
32
|
+
),
|
|
33
|
+
height: Math.max(
|
|
34
|
+
0,
|
|
35
|
+
viewport.height - insets.top - insets.bottom - 2 * MARGIN - covered
|
|
36
|
+
),
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Best guess of the content area before the popup is on screen. */
|
|
41
|
+
export function popupContentArea(viewport: Size, insets: Insets): Size {
|
|
42
|
+
const { width, height } = availableRect(viewport, insets, 0);
|
|
43
|
+
return { width, height };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** `popup` mode: a centered card over the backdrop with a fade/scale transition. */
|
|
47
|
+
export class PopupPresenter extends ModalPresenter {
|
|
48
|
+
/** 0 hidden, 1 presented. */
|
|
49
|
+
private readonly progress = new AnimatedValue(0, 0.001, (value) =>
|
|
50
|
+
this.render(value)
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
protected didShow() {
|
|
54
|
+
this.layout();
|
|
55
|
+
this.progress.set(0);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
protected animateIn() {
|
|
59
|
+
this.progress.animateTo(1, this.isAnimated ? ENTER : INSTANT, {
|
|
60
|
+
onEnd: () => this.notifyPresented(),
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
protected animateOut(onEnd: () => void) {
|
|
65
|
+
this.progress.animateTo(0, this.isAnimated ? EXIT : INSTANT, { onEnd });
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
protected configDidChange() {
|
|
69
|
+
this.layout();
|
|
70
|
+
this.render(this.progress.value);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
protected viewportDidChange() {
|
|
74
|
+
this.layout();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
protected contentSizeDidChange() {
|
|
78
|
+
this.layout();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
protected stopAnimations() {
|
|
82
|
+
this.progress.stop();
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
private get isAnimated(): boolean {
|
|
86
|
+
return this.config.popupAnimation !== 'none' && !prefersReducedMotion();
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
private layout() {
|
|
90
|
+
const { surface, content, grabber } = this.elements!;
|
|
91
|
+
const behavior = this.config.keyboardBehavior;
|
|
92
|
+
const available = availableRect(
|
|
93
|
+
this.viewport,
|
|
94
|
+
this.insets,
|
|
95
|
+
behavior === 'none' ? 0 : this.keyboardHeight
|
|
96
|
+
);
|
|
97
|
+
const width = Math.min(this.contentSize.width, available.width);
|
|
98
|
+
const height = Math.min(this.contentSize.height, available.height);
|
|
99
|
+
Object.assign(surface.style, {
|
|
100
|
+
left: `${available.x + (available.width - width) / 2}px`,
|
|
101
|
+
top: `${available.y + (available.height - height) / 2}px`,
|
|
102
|
+
width: `${width}px`,
|
|
103
|
+
height: `${height}px`,
|
|
104
|
+
borderRadius: `${this.config.cornerRadius ?? DEFAULT_CORNER_RADIUS}px`,
|
|
105
|
+
});
|
|
106
|
+
grabber.style.display = 'none';
|
|
107
|
+
|
|
108
|
+
// The content host also bounds the content's own (shrink-to-fit) width.
|
|
109
|
+
const area =
|
|
110
|
+
behavior === 'resize'
|
|
111
|
+
? available
|
|
112
|
+
: availableRect(this.viewport, this.insets, 0);
|
|
113
|
+
content.style.width = `${area.width}px`;
|
|
114
|
+
content.style.height = `${area.height}px`;
|
|
115
|
+
this.reportContentArea(area.width, area.height);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
private render(progress: number) {
|
|
119
|
+
if (!this.elements) return;
|
|
120
|
+
const { surface, backdrop } = this.elements;
|
|
121
|
+
const visible = Math.min(Math.max(progress, 0), 1);
|
|
122
|
+
const animation = this.config.popupAnimation;
|
|
123
|
+
backdrop.style.opacity = String(visible);
|
|
124
|
+
surface.style.opacity = animation === 'none' ? '1' : String(visible);
|
|
125
|
+
const scale =
|
|
126
|
+
animation === 'scale' ? ENTER_SCALE + (1 - ENTER_SCALE) * progress : 1;
|
|
127
|
+
surface.style.transform = `scale(${scale})`;
|
|
128
|
+
}
|
|
129
|
+
}
|