@buoy-gg/bottom-sheet 7.0.41 → 7.0.42
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/lib/commonjs/components/BottomSheet.js +1 -1160
- package/lib/commonjs/components/BottomSheet.web.js +1 -795
- package/lib/commonjs/components/bottomSheetGeometry.js +1 -86
- package/lib/commonjs/components/useMultiTap.js +1 -52
- package/lib/commonjs/index.js +1 -12
- package/lib/commonjs/index.web.js +1 -12
- package/lib/module/components/BottomSheet.js +1 -1157
- package/lib/module/components/BottomSheet.web.js +1 -790
- package/lib/module/components/bottomSheetGeometry.js +1 -79
- package/lib/module/components/useMultiTap.js +1 -47
- package/lib/module/index.js +1 -4
- package/lib/module/index.web.js +1 -6
- package/lib/web/index.mjs +1 -2
- package/package.json +2 -2
- package/lib/typescript/components/BottomSheet.d.ts.map +0 -1
- package/lib/typescript/components/BottomSheet.web.d.ts.map +0 -1
- package/lib/typescript/components/bottomSheetGeometry.d.ts.map +0 -1
- package/lib/typescript/components/useMultiTap.d.ts.map +0 -1
- package/lib/typescript/index.d.ts.map +0 -1
- package/lib/typescript/index.web.d.ts.map +0 -1
|
@@ -1,790 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* BottomSheet — Web renderer (React DOM).
|
|
5
|
-
*
|
|
6
|
-
* Same public API and behavior as the native BottomSheet.tsx:
|
|
7
|
-
* - two modes: "bottomSheet" (full-width, continuous drag-resize via header,
|
|
8
|
-
* fling-down to close) and "floating" (draggable window, 4-corner resize,
|
|
9
|
-
* tap topRight corner = close, tap topLeft = onBack)
|
|
10
|
-
* - double-tap header toggles mode, triple-tap closes
|
|
11
|
-
* - no backdrop; bottom-sheet container lets taps pass through beside it
|
|
12
|
-
*
|
|
13
|
-
* Web adaptations (each annotated inline with the native token it mirrors):
|
|
14
|
-
* - Pointer Events + setPointerCapture replace PanResponder (one code path
|
|
15
|
-
* for mouse and touch; capture gives "never release the responder"
|
|
16
|
-
* semantics). touch-action:none on gesture surfaces or WKWebView/Safari
|
|
17
|
-
* steals drags for page scroll.
|
|
18
|
-
* - During gestures we write styles directly to the DOM node (native rule:
|
|
19
|
-
* "direct setValue, no state updates") and commit to React state on release.
|
|
20
|
-
* - Native spring tension:180/friction:22 ≈ cubic-bezier(0.16,1,0.3,1) 320ms;
|
|
21
|
-
* the 200ms opacity fade is copied as-is.
|
|
22
|
-
* - Safe areas come from env(safe-area-inset-*) via a probe element (the
|
|
23
|
-
* native iPhone-dimension table is deleted). Requires viewport-fit=cover
|
|
24
|
-
* in the host page's <meta name="viewport"> to be non-zero on iOS.
|
|
25
|
-
* - Web-only additive props: `disableScrollWrapper` (tools with virtualized
|
|
26
|
-
* bodies need it — JsModal has it natively) and `zIndex` (AppHost stacking).
|
|
27
|
-
*
|
|
28
|
-
* Intentionally NOT ported: the visibility close animation on prop change —
|
|
29
|
-
* native returns null immediately when visible=false (only the gesture-close
|
|
30
|
-
* animates out), and we mirror that.
|
|
31
|
-
*/
|
|
32
|
-
|
|
33
|
-
import { useState, useRef, useEffect } from 'react';
|
|
34
|
-
// RN component, but safe here: every consumer of the web sheet (desktop
|
|
35
|
-
// renderer, website demos) aliases `react-native` to react-native-web, and
|
|
36
|
-
// the backgrounds primitives handle RNW explicitly.
|
|
37
|
-
import { ToolBackground } from '@buoy-gg/shared-ui';
|
|
38
|
-
import { MIN_HEIGHT, DEFAULT_HEIGHT, FLOATING_WIDTH, FLOATING_HEIGHT, FLOATING_MIN_HEIGHT, floatingMinWidth, resizeCorner, shouldCloseOnRelease } from './bottomSheetGeometry';
|
|
39
|
-
import { useMultiTap } from './useMultiTap';
|
|
40
|
-
|
|
41
|
-
// ============================================================================
|
|
42
|
-
// THEME — same palette as native BottomSheet.tsx defaultTheme
|
|
43
|
-
// ============================================================================
|
|
44
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
45
|
-
const defaultTheme = {
|
|
46
|
-
background: 'rgba(8, 12, 21, 0.98)',
|
|
47
|
-
panel: 'rgba(16, 22, 35, 0.98)',
|
|
48
|
-
border: '#00B8E666',
|
|
49
|
-
primary: '#FFFFFF',
|
|
50
|
-
secondary: '#B8BFC9',
|
|
51
|
-
muted: '#7A8599',
|
|
52
|
-
success: '#4AFF9F',
|
|
53
|
-
error: '#FF5252',
|
|
54
|
-
info: '#00B8E6'
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
// ============================================================================
|
|
58
|
-
// TYPES — identical names to the native module
|
|
59
|
-
// ============================================================================
|
|
60
|
-
|
|
61
|
-
// ============================================================================
|
|
62
|
-
// VIEWPORT + SAFE AREA
|
|
63
|
-
// ============================================================================
|
|
64
|
-
function subscribeToViewport(onChange) {
|
|
65
|
-
window.addEventListener('resize', onChange);
|
|
66
|
-
window.visualViewport?.addEventListener('resize', onChange);
|
|
67
|
-
return () => {
|
|
68
|
-
window.removeEventListener('resize', onChange);
|
|
69
|
-
window.visualViewport?.removeEventListener('resize', onChange);
|
|
70
|
-
};
|
|
71
|
-
}
|
|
72
|
-
function readViewport() {
|
|
73
|
-
return {
|
|
74
|
-
width: window.innerWidth,
|
|
75
|
-
height: window.innerHeight
|
|
76
|
-
};
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/** env(safe-area-inset-*) via a probe element (replaces the native iPhone table). */
|
|
80
|
-
function readSafeAreaInsets() {
|
|
81
|
-
const probe = document.createElement('div');
|
|
82
|
-
probe.style.cssText = 'position:fixed;visibility:hidden;pointer-events:none;' + 'top:env(safe-area-inset-top);bottom:env(safe-area-inset-bottom);' + 'left:env(safe-area-inset-left);right:env(safe-area-inset-right)';
|
|
83
|
-
document.body.appendChild(probe);
|
|
84
|
-
// Computed top/bottom/left/right resolve env() to px offsets from their
|
|
85
|
-
// respective edges — the raw parse IS the inset value.
|
|
86
|
-
const cs = getComputedStyle(probe);
|
|
87
|
-
const insets = {
|
|
88
|
-
top: parseFloat(cs.top) || 0,
|
|
89
|
-
bottom: parseFloat(cs.bottom) || 0,
|
|
90
|
-
left: parseFloat(cs.left) || 0,
|
|
91
|
-
right: parseFloat(cs.right) || 0
|
|
92
|
-
};
|
|
93
|
-
document.body.removeChild(probe);
|
|
94
|
-
return insets;
|
|
95
|
-
}
|
|
96
|
-
function useViewportAndInsets() {
|
|
97
|
-
const [state, setState] = useState(() => ({
|
|
98
|
-
viewport: readViewport(),
|
|
99
|
-
insets: readSafeAreaInsets()
|
|
100
|
-
}));
|
|
101
|
-
useEffect(() => {
|
|
102
|
-
return subscribeToViewport(() => {
|
|
103
|
-
setState({
|
|
104
|
-
viewport: readViewport(),
|
|
105
|
-
insets: readSafeAreaInsets()
|
|
106
|
-
});
|
|
107
|
-
});
|
|
108
|
-
}, []);
|
|
109
|
-
return state;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
// ============================================================================
|
|
113
|
-
// GESTURE PLUMBING — pointer drag with tap detection
|
|
114
|
-
// ============================================================================
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* Shared pointerdown handler factory. Captures the pointer on the target,
|
|
118
|
-
* tracks total deltas + a velocity sample (px/ms, matching RN gestureState.vy
|
|
119
|
-
* units), and reports wasTap when total movement stayed within the native
|
|
120
|
-
* 5px tap threshold (DraggableHeader: dragDistance <= 5).
|
|
121
|
-
*/
|
|
122
|
-
function startPointerDrag(e, callbacks) {
|
|
123
|
-
if (!e.isPrimary) return;
|
|
124
|
-
const target = e.currentTarget;
|
|
125
|
-
const startX = e.clientX;
|
|
126
|
-
const startY = e.clientY;
|
|
127
|
-
let lastY = e.clientY;
|
|
128
|
-
let lastT = performance.now();
|
|
129
|
-
let vy = 0;
|
|
130
|
-
let maxDistance = 0;
|
|
131
|
-
callbacks.onStart?.(e);
|
|
132
|
-
target.setPointerCapture(e.pointerId);
|
|
133
|
-
const handleMove = ev => {
|
|
134
|
-
if (ev.pointerId !== e.pointerId) return;
|
|
135
|
-
const dx = ev.clientX - startX;
|
|
136
|
-
const dy = ev.clientY - startY;
|
|
137
|
-
maxDistance = Math.max(maxDistance, Math.abs(dx) + Math.abs(dy));
|
|
138
|
-
const now = performance.now();
|
|
139
|
-
const dt = now - lastT;
|
|
140
|
-
if (dt > 0) {
|
|
141
|
-
vy = (ev.clientY - lastY) / dt;
|
|
142
|
-
lastY = ev.clientY;
|
|
143
|
-
lastT = now;
|
|
144
|
-
}
|
|
145
|
-
callbacks.onMove(ev, {
|
|
146
|
-
dx,
|
|
147
|
-
dy
|
|
148
|
-
});
|
|
149
|
-
};
|
|
150
|
-
const handleUp = ev => {
|
|
151
|
-
if (ev.pointerId !== e.pointerId) return;
|
|
152
|
-
cleanup();
|
|
153
|
-
callbacks.onEnd(ev, {
|
|
154
|
-
dx: ev.clientX - startX,
|
|
155
|
-
dy: ev.clientY - startY,
|
|
156
|
-
vy,
|
|
157
|
-
wasTap: maxDistance <= 5
|
|
158
|
-
});
|
|
159
|
-
};
|
|
160
|
-
const cleanup = () => {
|
|
161
|
-
target.removeEventListener('pointermove', handleMove);
|
|
162
|
-
target.removeEventListener('pointerup', handleUp);
|
|
163
|
-
target.removeEventListener('pointercancel', handleUp);
|
|
164
|
-
};
|
|
165
|
-
target.addEventListener('pointermove', handleMove);
|
|
166
|
-
target.addEventListener('pointerup', handleUp);
|
|
167
|
-
target.addEventListener('pointercancel', handleUp);
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
// ============================================================================
|
|
171
|
-
// HEADER PIECES
|
|
172
|
-
// ============================================================================
|
|
173
|
-
function DragIndicator({
|
|
174
|
-
isResizing,
|
|
175
|
-
mode,
|
|
176
|
-
hasCustomContent = false,
|
|
177
|
-
theme
|
|
178
|
-
}) {
|
|
179
|
-
return /*#__PURE__*/_jsxs("div", {
|
|
180
|
-
style: {
|
|
181
|
-
display: 'flex',
|
|
182
|
-
flexDirection: 'column',
|
|
183
|
-
alignItems: 'center',
|
|
184
|
-
// native: paddingVertical 8 / custom: 6 top, 2 bottom
|
|
185
|
-
padding: hasCustomContent ? '6px 0 2px' : '8px 0',
|
|
186
|
-
position: 'relative'
|
|
187
|
-
},
|
|
188
|
-
children: [/*#__PURE__*/_jsx("div", {
|
|
189
|
-
style: {
|
|
190
|
-
// native dragIndicator: 40×3 info@99 / floating: 50×5 muted / active: success
|
|
191
|
-
width: isResizing ? 40 : mode === 'floating' ? 50 : 40,
|
|
192
|
-
height: mode === 'floating' ? 5 : 3,
|
|
193
|
-
backgroundColor: isResizing ? theme.success : mode === 'floating' ? theme.muted : theme.info + '99',
|
|
194
|
-
borderRadius: 2,
|
|
195
|
-
boxShadow: `0 0 4px ${theme.info}CC`
|
|
196
|
-
}
|
|
197
|
-
}), isResizing && mode === 'bottomSheet' && /*#__PURE__*/_jsx("div", {
|
|
198
|
-
style: {
|
|
199
|
-
position: 'absolute',
|
|
200
|
-
display: 'flex',
|
|
201
|
-
flexDirection: 'row',
|
|
202
|
-
gap: 2,
|
|
203
|
-
marginTop: 12
|
|
204
|
-
},
|
|
205
|
-
children: [0, 1, 2].map(i => /*#__PURE__*/_jsx("div", {
|
|
206
|
-
style: {
|
|
207
|
-
width: 12,
|
|
208
|
-
height: 1,
|
|
209
|
-
backgroundColor: theme.success,
|
|
210
|
-
opacity: 0.6
|
|
211
|
-
}
|
|
212
|
-
}, i))
|
|
213
|
-
})]
|
|
214
|
-
});
|
|
215
|
-
}
|
|
216
|
-
function HeaderBody({
|
|
217
|
-
header,
|
|
218
|
-
isResizing,
|
|
219
|
-
mode,
|
|
220
|
-
theme
|
|
221
|
-
}) {
|
|
222
|
-
if (header?.customContent) {
|
|
223
|
-
return /*#__PURE__*/_jsxs("div", {
|
|
224
|
-
style: {
|
|
225
|
-
flex: 1,
|
|
226
|
-
display: 'flex',
|
|
227
|
-
flexDirection: 'column',
|
|
228
|
-
justifyContent: 'center'
|
|
229
|
-
},
|
|
230
|
-
children: [/*#__PURE__*/_jsx(DragIndicator, {
|
|
231
|
-
isResizing: isResizing,
|
|
232
|
-
mode: mode,
|
|
233
|
-
hasCustomContent: true,
|
|
234
|
-
theme: theme
|
|
235
|
-
}), header.customContent]
|
|
236
|
-
});
|
|
237
|
-
}
|
|
238
|
-
return /*#__PURE__*/_jsxs("div", {
|
|
239
|
-
style: {
|
|
240
|
-
flex: 1,
|
|
241
|
-
display: 'flex',
|
|
242
|
-
flexDirection: 'column',
|
|
243
|
-
justifyContent: 'center',
|
|
244
|
-
position: 'relative'
|
|
245
|
-
},
|
|
246
|
-
children: [/*#__PURE__*/_jsx(DragIndicator, {
|
|
247
|
-
isResizing: isResizing,
|
|
248
|
-
mode: mode,
|
|
249
|
-
theme: theme
|
|
250
|
-
}), /*#__PURE__*/_jsxs("div", {
|
|
251
|
-
style: {
|
|
252
|
-
padding: '0 16px',
|
|
253
|
-
display: 'flex',
|
|
254
|
-
flexDirection: 'column',
|
|
255
|
-
alignItems: 'center'
|
|
256
|
-
},
|
|
257
|
-
children: [header?.title &&
|
|
258
|
-
/*#__PURE__*/
|
|
259
|
-
// native headerTitle: 16px / 600 / primary
|
|
260
|
-
_jsx("span", {
|
|
261
|
-
style: {
|
|
262
|
-
fontSize: 16,
|
|
263
|
-
fontWeight: 600,
|
|
264
|
-
color: theme.primary
|
|
265
|
-
},
|
|
266
|
-
children: header.title
|
|
267
|
-
}), header?.subtitle &&
|
|
268
|
-
/*#__PURE__*/
|
|
269
|
-
// native headerSubtitle: 12px / secondary / paddingTop 4
|
|
270
|
-
_jsx("span", {
|
|
271
|
-
style: {
|
|
272
|
-
fontSize: 12,
|
|
273
|
-
color: theme.secondary,
|
|
274
|
-
paddingTop: 4
|
|
275
|
-
},
|
|
276
|
-
children: header.subtitle
|
|
277
|
-
})]
|
|
278
|
-
}), /*#__PURE__*/_jsx("div", {
|
|
279
|
-
style: {
|
|
280
|
-
position: 'absolute',
|
|
281
|
-
inset: 0,
|
|
282
|
-
display: 'flex',
|
|
283
|
-
alignItems: 'center',
|
|
284
|
-
justifyContent: 'center',
|
|
285
|
-
pointerEvents: 'none'
|
|
286
|
-
},
|
|
287
|
-
children: /*#__PURE__*/_jsx("span", {
|
|
288
|
-
style: {
|
|
289
|
-
fontSize: 10,
|
|
290
|
-
color: theme.muted,
|
|
291
|
-
fontStyle: 'italic'
|
|
292
|
-
},
|
|
293
|
-
children: "Double tap: Toggle \u2022 Triple tap: Close"
|
|
294
|
-
})
|
|
295
|
-
})]
|
|
296
|
-
});
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
// ============================================================================
|
|
300
|
-
// CONTENT — scroll wrapper + sticky footer (native content/footerContainer)
|
|
301
|
-
// ============================================================================
|
|
302
|
-
function SheetContent({
|
|
303
|
-
children,
|
|
304
|
-
footer,
|
|
305
|
-
footerHeight,
|
|
306
|
-
disableScrollWrapper,
|
|
307
|
-
contentStyle,
|
|
308
|
-
theme,
|
|
309
|
-
background
|
|
310
|
-
}) {
|
|
311
|
-
return /*#__PURE__*/_jsxs("div", {
|
|
312
|
-
style: {
|
|
313
|
-
// native content: flex 1, background, bottom radius 16, overflow hidden
|
|
314
|
-
flex: 1,
|
|
315
|
-
minHeight: 0,
|
|
316
|
-
display: 'flex',
|
|
317
|
-
flexDirection: 'column',
|
|
318
|
-
backgroundColor: theme.background,
|
|
319
|
-
borderBottomLeftRadius: 16,
|
|
320
|
-
borderBottomRightRadius: 16,
|
|
321
|
-
overflow: 'hidden',
|
|
322
|
-
position: 'relative',
|
|
323
|
-
...contentStyle
|
|
324
|
-
},
|
|
325
|
-
children: [background ? /*#__PURE__*/_jsx(ToolBackground, {}) : null, disableScrollWrapper ? children : /*#__PURE__*/_jsx("div", {
|
|
326
|
-
style: {
|
|
327
|
-
flex: 1,
|
|
328
|
-
minHeight: 0,
|
|
329
|
-
overflowY: 'auto',
|
|
330
|
-
WebkitOverflowScrolling: 'touch',
|
|
331
|
-
paddingBottom: footerHeight
|
|
332
|
-
},
|
|
333
|
-
children: children
|
|
334
|
-
}), footer ? /*#__PURE__*/_jsx("div", {
|
|
335
|
-
style: {
|
|
336
|
-
position: 'absolute',
|
|
337
|
-
left: 0,
|
|
338
|
-
right: 0,
|
|
339
|
-
bottom: 0,
|
|
340
|
-
backgroundColor: theme.background,
|
|
341
|
-
borderBottomLeftRadius: 16,
|
|
342
|
-
borderBottomRightRadius: 16
|
|
343
|
-
},
|
|
344
|
-
children: footer
|
|
345
|
-
}) : null]
|
|
346
|
-
});
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
// ============================================================================
|
|
350
|
-
// BOTTOM SHEET MODE
|
|
351
|
-
// ============================================================================
|
|
352
|
-
const SPRING_TRANSITION = 'transform 320ms cubic-bezier(0.16, 1, 0.3, 1)';
|
|
353
|
-
const FADE_TRANSITION = 'opacity 200ms ease';
|
|
354
|
-
function SheetPanel({
|
|
355
|
-
props,
|
|
356
|
-
theme,
|
|
357
|
-
viewport,
|
|
358
|
-
effectiveMaxHeight,
|
|
359
|
-
panelHeight,
|
|
360
|
-
setPanelHeight,
|
|
361
|
-
onHeaderTap
|
|
362
|
-
}) {
|
|
363
|
-
const {
|
|
364
|
-
onClose,
|
|
365
|
-
children,
|
|
366
|
-
header,
|
|
367
|
-
styles: customStyles = {},
|
|
368
|
-
minHeight = MIN_HEIGHT,
|
|
369
|
-
footer,
|
|
370
|
-
footerHeight = 0,
|
|
371
|
-
disableScrollWrapper,
|
|
372
|
-
zIndex = 1000,
|
|
373
|
-
background = true
|
|
374
|
-
} = props;
|
|
375
|
-
const sheetRef = useRef(null);
|
|
376
|
-
const wrapperRef = useRef(null);
|
|
377
|
-
const [isResizing, setIsResizing] = useState(false);
|
|
378
|
-
const [entered, setEntered] = useState(false);
|
|
379
|
-
const closingRef = useRef(false);
|
|
380
|
-
const heightRef = useRef(panelHeight);
|
|
381
|
-
|
|
382
|
-
// Enter animation: translateY 100% → 0 (native spring 180/22) + fade 200ms.
|
|
383
|
-
useEffect(() => {
|
|
384
|
-
const raf = requestAnimationFrame(() => setEntered(true));
|
|
385
|
-
return () => cancelAnimationFrame(raf);
|
|
386
|
-
}, []);
|
|
387
|
-
const handleHeaderPointerDown = e => {
|
|
388
|
-
if (closingRef.current) return;
|
|
389
|
-
// native: headerTouchOffset = locationY (finger position inside header)
|
|
390
|
-
const headerTop = e.currentTarget.getBoundingClientRect().top;
|
|
391
|
-
const touchOffset = e.clientY - headerTop;
|
|
392
|
-
startPointerDrag(e, {
|
|
393
|
-
onStart: () => setIsResizing(true),
|
|
394
|
-
onMove: ev => {
|
|
395
|
-
// native: targetHeight = SCREEN.height - (pageY - headerTouchOffset)
|
|
396
|
-
const sheetTop = ev.clientY - touchOffset;
|
|
397
|
-
const target = Math.max(minHeight, Math.min(viewport.height - sheetTop, effectiveMaxHeight));
|
|
398
|
-
heightRef.current = target;
|
|
399
|
-
if (sheetRef.current) sheetRef.current.style.height = `${target}px`;
|
|
400
|
-
},
|
|
401
|
-
onEnd: (_ev, {
|
|
402
|
-
dy,
|
|
403
|
-
vy,
|
|
404
|
-
wasTap
|
|
405
|
-
}) => {
|
|
406
|
-
setIsResizing(false);
|
|
407
|
-
if (wasTap) {
|
|
408
|
-
onHeaderTap();
|
|
409
|
-
return;
|
|
410
|
-
}
|
|
411
|
-
if (shouldCloseOnRelease(vy, dy, heightRef.current, minHeight)) {
|
|
412
|
-
// Gesture close animates out, then fires onClose (native parity).
|
|
413
|
-
closingRef.current = true;
|
|
414
|
-
const wrapper = wrapperRef.current;
|
|
415
|
-
if (wrapper) {
|
|
416
|
-
wrapper.style.transition = `${SPRING_TRANSITION}, ${FADE_TRANSITION}`;
|
|
417
|
-
wrapper.style.transform = 'translateY(100%)';
|
|
418
|
-
wrapper.style.opacity = '0';
|
|
419
|
-
}
|
|
420
|
-
setTimeout(onClose, 220);
|
|
421
|
-
return;
|
|
422
|
-
}
|
|
423
|
-
setPanelHeight(heightRef.current);
|
|
424
|
-
}
|
|
425
|
-
});
|
|
426
|
-
};
|
|
427
|
-
return /*#__PURE__*/_jsx("div", {
|
|
428
|
-
style: {
|
|
429
|
-
// native fullScreenContainer: absoluteFill + zIndex, box-none touches
|
|
430
|
-
position: 'fixed',
|
|
431
|
-
inset: 0,
|
|
432
|
-
zIndex,
|
|
433
|
-
pointerEvents: 'none'
|
|
434
|
-
},
|
|
435
|
-
children: /*#__PURE__*/_jsx("div", {
|
|
436
|
-
ref: wrapperRef,
|
|
437
|
-
style: {
|
|
438
|
-
// native bottomSheetWrapper: absolute bottom/left/right 0
|
|
439
|
-
position: 'absolute',
|
|
440
|
-
bottom: 0,
|
|
441
|
-
left: 0,
|
|
442
|
-
right: 0,
|
|
443
|
-
pointerEvents: 'auto',
|
|
444
|
-
transform: entered ? 'translateY(0)' : 'translateY(100%)',
|
|
445
|
-
opacity: entered ? 1 : 0,
|
|
446
|
-
transition: `${SPRING_TRANSITION}, ${FADE_TRANSITION}`
|
|
447
|
-
},
|
|
448
|
-
children: /*#__PURE__*/_jsxs("div", {
|
|
449
|
-
ref: sheetRef,
|
|
450
|
-
style: {
|
|
451
|
-
// native bottomSheet: panel bg, top radius 16, 1px border, glow shadow
|
|
452
|
-
display: 'flex',
|
|
453
|
-
flexDirection: 'column',
|
|
454
|
-
height: panelHeight,
|
|
455
|
-
backgroundColor: theme.panel,
|
|
456
|
-
borderTopLeftRadius: 16,
|
|
457
|
-
borderTopRightRadius: 16,
|
|
458
|
-
border: `1px solid ${theme.border}`,
|
|
459
|
-
boxShadow: `0 -4px 12px ${theme.info}4D, 0 -2px 24px rgba(0,0,0,0.4)`,
|
|
460
|
-
...customStyles.container
|
|
461
|
-
},
|
|
462
|
-
children: [/*#__PURE__*/_jsx("div", {
|
|
463
|
-
onPointerDown: handleHeaderPointerDown,
|
|
464
|
-
style: {
|
|
465
|
-
// native header: minHeight 56, top radius, hairline bottom border
|
|
466
|
-
minHeight: 56,
|
|
467
|
-
display: 'flex',
|
|
468
|
-
backgroundColor: theme.panel,
|
|
469
|
-
borderTopLeftRadius: 16,
|
|
470
|
-
borderTopRightRadius: 16,
|
|
471
|
-
borderBottom: '1px solid rgba(255, 255, 255, 0.1)',
|
|
472
|
-
cursor: isResizing ? 'ns-resize' : 'grab',
|
|
473
|
-
touchAction: 'none',
|
|
474
|
-
userSelect: 'none'
|
|
475
|
-
},
|
|
476
|
-
children: /*#__PURE__*/_jsx(HeaderBody, {
|
|
477
|
-
header: header,
|
|
478
|
-
isResizing: isResizing,
|
|
479
|
-
mode: "bottomSheet",
|
|
480
|
-
theme: theme
|
|
481
|
-
})
|
|
482
|
-
}), /*#__PURE__*/_jsx(SheetContent, {
|
|
483
|
-
footer: footer,
|
|
484
|
-
footerHeight: footerHeight,
|
|
485
|
-
disableScrollWrapper: disableScrollWrapper,
|
|
486
|
-
contentStyle: customStyles.content,
|
|
487
|
-
theme: theme,
|
|
488
|
-
background: background,
|
|
489
|
-
children: children
|
|
490
|
-
})]
|
|
491
|
-
})
|
|
492
|
-
})
|
|
493
|
-
});
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
// ============================================================================
|
|
497
|
-
// FLOATING MODE
|
|
498
|
-
// ============================================================================
|
|
499
|
-
function CornerHandleView({
|
|
500
|
-
isActive,
|
|
501
|
-
theme
|
|
502
|
-
}) {
|
|
503
|
-
return /*#__PURE__*/_jsx("div", {
|
|
504
|
-
style: {
|
|
505
|
-
width: 20,
|
|
506
|
-
height: 20,
|
|
507
|
-
borderRadius: 10,
|
|
508
|
-
margin: 5,
|
|
509
|
-
// native handlerActive: success tint + 2px border + glow
|
|
510
|
-
backgroundColor: isActive ? theme.success + '1A' : 'transparent',
|
|
511
|
-
border: isActive ? `2px solid ${theme.success}` : '2px solid transparent',
|
|
512
|
-
boxShadow: isActive ? `0 0 8px ${theme.success}99` : 'none'
|
|
513
|
-
}
|
|
514
|
-
});
|
|
515
|
-
}
|
|
516
|
-
function FloatingWindow({
|
|
517
|
-
props,
|
|
518
|
-
theme,
|
|
519
|
-
viewport,
|
|
520
|
-
insets,
|
|
521
|
-
rect,
|
|
522
|
-
setRect,
|
|
523
|
-
onHeaderTap
|
|
524
|
-
}) {
|
|
525
|
-
const {
|
|
526
|
-
onClose,
|
|
527
|
-
children,
|
|
528
|
-
header,
|
|
529
|
-
styles: customStyles = {},
|
|
530
|
-
footer,
|
|
531
|
-
footerHeight = 0,
|
|
532
|
-
onBack,
|
|
533
|
-
disableScrollWrapper,
|
|
534
|
-
zIndex = 1000,
|
|
535
|
-
background = true
|
|
536
|
-
} = props;
|
|
537
|
-
const windowRef = useRef(null);
|
|
538
|
-
const [isDragging, setIsDragging] = useState(false);
|
|
539
|
-
const [isResizing, setIsResizing] = useState(false);
|
|
540
|
-
const [entered, setEntered] = useState(false);
|
|
541
|
-
const rectRef = useRef(rect);
|
|
542
|
-
rectRef.current = rect;
|
|
543
|
-
const liveRectRef = useRef(rect);
|
|
544
|
-
useEffect(() => {
|
|
545
|
-
const raf = requestAnimationFrame(() => setEntered(true));
|
|
546
|
-
return () => cancelAnimationFrame(raf);
|
|
547
|
-
}, []);
|
|
548
|
-
const applyRect = r => {
|
|
549
|
-
liveRectRef.current = r;
|
|
550
|
-
const el = windowRef.current;
|
|
551
|
-
if (!el) return;
|
|
552
|
-
el.style.transform = `translate(${r.left}px, ${r.top}px)`;
|
|
553
|
-
el.style.width = `${r.width}px`;
|
|
554
|
-
el.style.height = `${r.height}px`;
|
|
555
|
-
};
|
|
556
|
-
|
|
557
|
-
// Header drag: absolute finger anchoring (native DraggableHeader:
|
|
558
|
-
// position = pageX/Y - touch offset inside the element).
|
|
559
|
-
const handleHeaderPointerDown = e => {
|
|
560
|
-
const start = rectRef.current;
|
|
561
|
-
const offsetX = e.clientX - start.left;
|
|
562
|
-
const offsetY = e.clientY - start.top;
|
|
563
|
-
startPointerDrag(e, {
|
|
564
|
-
onMove: (ev, {
|
|
565
|
-
dx,
|
|
566
|
-
dy
|
|
567
|
-
}) => {
|
|
568
|
-
if (Math.abs(dx) + Math.abs(dy) > 5) setIsDragging(true);
|
|
569
|
-
applyRect({
|
|
570
|
-
...liveRectRef.current,
|
|
571
|
-
left: ev.clientX - offsetX,
|
|
572
|
-
top: ev.clientY - offsetY
|
|
573
|
-
});
|
|
574
|
-
},
|
|
575
|
-
onEnd: (_ev, {
|
|
576
|
-
wasTap
|
|
577
|
-
}) => {
|
|
578
|
-
setIsDragging(false);
|
|
579
|
-
if (wasTap) {
|
|
580
|
-
onHeaderTap();
|
|
581
|
-
return;
|
|
582
|
-
}
|
|
583
|
-
// Release clamp keeps the whole window inside the safe area (all
|
|
584
|
-
// four insets — deliberate improvement over native, which only
|
|
585
|
-
// clamps the top so windows could sit under the home indicator).
|
|
586
|
-
const live = liveRectRef.current;
|
|
587
|
-
const clamped = {
|
|
588
|
-
...live,
|
|
589
|
-
left: Math.max(insets.left, Math.min(live.left, viewport.width - insets.right - live.width)),
|
|
590
|
-
top: Math.max(insets.top, Math.min(live.top, viewport.height - insets.bottom - live.height))
|
|
591
|
-
};
|
|
592
|
-
applyRect(clamped);
|
|
593
|
-
setRect(clamped);
|
|
594
|
-
}
|
|
595
|
-
});
|
|
596
|
-
};
|
|
597
|
-
const handleCornerPointerDown = corner => e => {
|
|
598
|
-
e.stopPropagation();
|
|
599
|
-
const start = {
|
|
600
|
-
...rectRef.current
|
|
601
|
-
};
|
|
602
|
-
let didResize = false;
|
|
603
|
-
startPointerDrag(e, {
|
|
604
|
-
onStart: () => setIsResizing(true),
|
|
605
|
-
onMove: (_ev, {
|
|
606
|
-
dx,
|
|
607
|
-
dy
|
|
608
|
-
}) => {
|
|
609
|
-
// native: ignores sub-half-pixel jitter
|
|
610
|
-
if (Math.abs(dx) < 0.5 && Math.abs(dy) < 0.5) return;
|
|
611
|
-
didResize = true;
|
|
612
|
-
applyRect(resizeCorner(corner, start, dx, dy, viewport, insets, floatingMinWidth(viewport.width), FLOATING_MIN_HEIGHT));
|
|
613
|
-
},
|
|
614
|
-
onEnd: () => {
|
|
615
|
-
setIsResizing(false);
|
|
616
|
-
// native: tap (no resize) on topRight = close, topLeft = back
|
|
617
|
-
if (!didResize) {
|
|
618
|
-
if (corner === 'topRight') {
|
|
619
|
-
onClose();
|
|
620
|
-
return;
|
|
621
|
-
}
|
|
622
|
-
if (corner === 'topLeft' && onBack) {
|
|
623
|
-
onBack();
|
|
624
|
-
return;
|
|
625
|
-
}
|
|
626
|
-
}
|
|
627
|
-
setRect(liveRectRef.current);
|
|
628
|
-
}
|
|
629
|
-
});
|
|
630
|
-
};
|
|
631
|
-
const active = isDragging || isResizing;
|
|
632
|
-
const corners = [{
|
|
633
|
-
corner: 'topLeft',
|
|
634
|
-
style: {
|
|
635
|
-
top: 4,
|
|
636
|
-
left: 4
|
|
637
|
-
}
|
|
638
|
-
}, {
|
|
639
|
-
corner: 'topRight',
|
|
640
|
-
style: {
|
|
641
|
-
top: 4,
|
|
642
|
-
right: 4
|
|
643
|
-
}
|
|
644
|
-
}, {
|
|
645
|
-
corner: 'bottomLeft',
|
|
646
|
-
style: {
|
|
647
|
-
bottom: 4,
|
|
648
|
-
left: 4
|
|
649
|
-
}
|
|
650
|
-
}, {
|
|
651
|
-
corner: 'bottomRight',
|
|
652
|
-
style: {
|
|
653
|
-
bottom: 4,
|
|
654
|
-
right: 4
|
|
655
|
-
}
|
|
656
|
-
}];
|
|
657
|
-
return /*#__PURE__*/_jsxs("div", {
|
|
658
|
-
ref: windowRef,
|
|
659
|
-
style: {
|
|
660
|
-
// native floatingModal: absolute, panel bg, radius 16, glow shadow
|
|
661
|
-
position: 'fixed',
|
|
662
|
-
top: 0,
|
|
663
|
-
left: 0,
|
|
664
|
-
transform: `translate(${rect.left}px, ${rect.top}px)`,
|
|
665
|
-
width: rect.width,
|
|
666
|
-
height: rect.height,
|
|
667
|
-
display: 'flex',
|
|
668
|
-
flexDirection: 'column',
|
|
669
|
-
backgroundColor: theme.panel,
|
|
670
|
-
borderRadius: 16,
|
|
671
|
-
// native floatingModalDragging: success border while active
|
|
672
|
-
border: active ? `2px solid ${theme.success}` : `1px solid ${theme.border}`,
|
|
673
|
-
boxShadow: active ? `0 0 12px ${theme.success}99` : `0 0 20px ${theme.info}80, 0 8px 32px rgba(0,0,0,0.5)`,
|
|
674
|
-
zIndex,
|
|
675
|
-
opacity: entered ? 1 : 0,
|
|
676
|
-
transition: FADE_TRANSITION,
|
|
677
|
-
...customStyles.container
|
|
678
|
-
},
|
|
679
|
-
children: [/*#__PURE__*/_jsx("div", {
|
|
680
|
-
onPointerDown: handleHeaderPointerDown,
|
|
681
|
-
style: {
|
|
682
|
-
minHeight: 56,
|
|
683
|
-
display: 'flex',
|
|
684
|
-
backgroundColor: theme.panel,
|
|
685
|
-
borderTopLeftRadius: 14,
|
|
686
|
-
borderTopRightRadius: 14,
|
|
687
|
-
borderBottom: '1px solid rgba(255, 255, 255, 0.1)',
|
|
688
|
-
cursor: isDragging ? 'grabbing' : 'grab',
|
|
689
|
-
touchAction: 'none',
|
|
690
|
-
userSelect: 'none'
|
|
691
|
-
},
|
|
692
|
-
children: /*#__PURE__*/_jsx(HeaderBody, {
|
|
693
|
-
header: header,
|
|
694
|
-
isResizing: active,
|
|
695
|
-
mode: "floating",
|
|
696
|
-
theme: theme
|
|
697
|
-
})
|
|
698
|
-
}), /*#__PURE__*/_jsx(SheetContent, {
|
|
699
|
-
footer: footer,
|
|
700
|
-
footerHeight: footerHeight,
|
|
701
|
-
disableScrollWrapper: disableScrollWrapper,
|
|
702
|
-
contentStyle: customStyles.content,
|
|
703
|
-
theme: theme,
|
|
704
|
-
background: background,
|
|
705
|
-
children: children
|
|
706
|
-
}), corners.map(({
|
|
707
|
-
corner,
|
|
708
|
-
style
|
|
709
|
-
}) => /*#__PURE__*/_jsx("div", {
|
|
710
|
-
onPointerDown: handleCornerPointerDown(corner),
|
|
711
|
-
style: {
|
|
712
|
-
// native cornerHandleWrapper: 30×30 hit area (hitSlop folded in)
|
|
713
|
-
position: 'absolute',
|
|
714
|
-
width: 30,
|
|
715
|
-
height: 30,
|
|
716
|
-
zIndex: 10,
|
|
717
|
-
touchAction: 'none',
|
|
718
|
-
cursor: corner === 'topLeft' || corner === 'bottomRight' ? 'nwse-resize' : 'nesw-resize',
|
|
719
|
-
...style
|
|
720
|
-
},
|
|
721
|
-
children: /*#__PURE__*/_jsx(CornerHandleView, {
|
|
722
|
-
isActive: active,
|
|
723
|
-
theme: theme
|
|
724
|
-
})
|
|
725
|
-
}, corner))]
|
|
726
|
-
});
|
|
727
|
-
}
|
|
728
|
-
|
|
729
|
-
// ============================================================================
|
|
730
|
-
// MAIN COMPONENT — mode router (each mode is its own subtree; rule4 style)
|
|
731
|
-
// ============================================================================
|
|
732
|
-
export const BottomSheet = props => {
|
|
733
|
-
const {
|
|
734
|
-
visible,
|
|
735
|
-
onClose,
|
|
736
|
-
maxHeight,
|
|
737
|
-
initialHeight = DEFAULT_HEIGHT,
|
|
738
|
-
initialMode = 'bottomSheet',
|
|
739
|
-
onModeChange,
|
|
740
|
-
theme: customTheme
|
|
741
|
-
} = props;
|
|
742
|
-
const theme = {
|
|
743
|
-
...defaultTheme,
|
|
744
|
-
...customTheme
|
|
745
|
-
};
|
|
746
|
-
const {
|
|
747
|
-
viewport,
|
|
748
|
-
insets
|
|
749
|
-
} = useViewportAndInsets();
|
|
750
|
-
const [mode, setMode] = useState(initialMode);
|
|
751
|
-
const [panelHeight, setPanelHeight] = useState(initialHeight);
|
|
752
|
-
const [floatingRect, setFloatingRect] = useState(() => ({
|
|
753
|
-
width: FLOATING_WIDTH,
|
|
754
|
-
height: FLOATING_HEIGHT,
|
|
755
|
-
left: (window.innerWidth - FLOATING_WIDTH) / 2,
|
|
756
|
-
top: (window.innerHeight - FLOATING_HEIGHT) / 2
|
|
757
|
-
}));
|
|
758
|
-
|
|
759
|
-
// native: effectiveMaxHeight = maxHeight || SCREEN.height - insets.top
|
|
760
|
-
const effectiveMaxHeight = maxHeight || viewport.height - insets.top;
|
|
761
|
-
const toggleMode = () => {
|
|
762
|
-
const newMode = mode === 'bottomSheet' ? 'floating' : 'bottomSheet';
|
|
763
|
-
setMode(newMode);
|
|
764
|
-
onModeChange?.(newMode);
|
|
765
|
-
};
|
|
766
|
-
const handleHeaderTap = useMultiTap(toggleMode, onClose);
|
|
767
|
-
if (!visible) {
|
|
768
|
-
return null;
|
|
769
|
-
}
|
|
770
|
-
if (mode === 'floating') {
|
|
771
|
-
return /*#__PURE__*/_jsx(FloatingWindow, {
|
|
772
|
-
props: props,
|
|
773
|
-
theme: theme,
|
|
774
|
-
viewport: viewport,
|
|
775
|
-
insets: insets,
|
|
776
|
-
rect: floatingRect,
|
|
777
|
-
setRect: setFloatingRect,
|
|
778
|
-
onHeaderTap: handleHeaderTap
|
|
779
|
-
});
|
|
780
|
-
}
|
|
781
|
-
return /*#__PURE__*/_jsx(SheetPanel, {
|
|
782
|
-
props: props,
|
|
783
|
-
theme: theme,
|
|
784
|
-
viewport: viewport,
|
|
785
|
-
effectiveMaxHeight: effectiveMaxHeight,
|
|
786
|
-
panelHeight: Math.min(panelHeight, effectiveMaxHeight),
|
|
787
|
-
setPanelHeight: setPanelHeight,
|
|
788
|
-
onHeaderTap: handleHeaderTap
|
|
789
|
-
});
|
|
790
|
-
};
|
|
1
|
+
"use strict";import{useState as y,useRef as F,useEffect as N}from"react";import{ToolBackground as tt}from"@buoy-gg/shared-ui";import{MIN_HEIGHT as et,DEFAULT_HEIGHT as ot,FLOATING_WIDTH as X,FLOATING_HEIGHT as V,FLOATING_MIN_HEIGHT as nt,floatingMinWidth as it,resizeCorner as rt,shouldCloseOnRelease as st}from"./bottomSheetGeometry";import{useMultiTap as lt}from"./useMultiTap";import{jsx as r,jsxs as D}from"react/jsx-runtime";const at={background:"rgba(8, 12, 21, 0.98)",panel:"rgba(16, 22, 35, 0.98)",border:"#00B8E666",primary:"#FFFFFF",secondary:"#B8BFC9",muted:"#7A8599",success:"#4AFF9F",error:"#FF5252",info:"#00B8E6"};function ct(t){window.addEventListener("resize",t);window.visualViewport?.addEventListener("resize",t);return()=>{window.removeEventListener("resize",t);window.visualViewport?.removeEventListener("resize",t)}}function q(){return{width:window.innerWidth,height:window.innerHeight}}function U(){const t=document.createElement("div");t.style.cssText="position:fixed;visibility:hidden;pointer-events:none;top:env(safe-area-inset-top);bottom:env(safe-area-inset-bottom);left:env(safe-area-inset-left);right:env(safe-area-inset-right)";document.body.appendChild(t);const e=getComputedStyle(t);const n={top:parseFloat(e.top)||0,bottom:parseFloat(e.bottom)||0,left:parseFloat(e.left)||0,right:parseFloat(e.right)||0};document.body.removeChild(t);return n}function dt(){const[t,e]=y(()=>({viewport:q(),insets:U()}));N(()=>{return ct(()=>{e({viewport:q(),insets:U()})})},[]);return t}function G(t,e){if(!t.isPrimary)return;const n=t.currentTarget;const o=t.clientX;const s=t.clientY;let d=t.clientY;let u=performance.now();let w=0;let p=0;e.onStart?.(t);n.setPointerCapture(t.pointerId);const f=l=>{if(l.pointerId!==t.pointerId)return;const m=l.clientX-o;const v=l.clientY-s;p=Math.max(p,Math.abs(m)+Math.abs(v));const R=performance.now();const T=R-u;if(T>0){w=(l.clientY-d)/T;d=l.clientY;u=R}e.onMove(l,{dx:m,dy:v})};const c=l=>{if(l.pointerId!==t.pointerId)return;g();e.onEnd(l,{dx:l.clientX-o,dy:l.clientY-s,vy:w,wasTap:p<=5})};const g=()=>{n.removeEventListener("pointermove",f);n.removeEventListener("pointerup",c);n.removeEventListener("pointercancel",c)};n.addEventListener("pointermove",f);n.addEventListener("pointerup",c);n.addEventListener("pointercancel",c)}function J({isResizing:t,mode:e,hasCustomContent:n=false,theme:o}){return D("div",{style:{display:"flex",flexDirection:"column",alignItems:"center",padding:n?"6px 0 2px":"8px 0",position:"relative"},children:[r("div",{style:{width:t?40:e==="floating"?50:40,height:e==="floating"?5:3,backgroundColor:t?o.success:e==="floating"?o.muted:o.info+"99",borderRadius:2,boxShadow:`0 0 4px ${o.info}CC`}}),t&&e==="bottomSheet"&&r("div",{style:{position:"absolute",display:"flex",flexDirection:"row",gap:2,marginTop:12},children:[0,1,2].map(s=>r("div",{style:{width:12,height:1,backgroundColor:o.success,opacity:.6}},s))})]})}function K({header:t,isResizing:e,mode:n,theme:o}){if(t?.customContent){return D("div",{style:{flex:1,display:"flex",flexDirection:"column",justifyContent:"center"},children:[r(J,{isResizing:e,mode:n,hasCustomContent:true,theme:o}),t.customContent]})}return D("div",{style:{flex:1,display:"flex",flexDirection:"column",justifyContent:"center",position:"relative"},children:[r(J,{isResizing:e,mode:n,theme:o}),D("div",{style:{padding:"0 16px",display:"flex",flexDirection:"column",alignItems:"center"},children:[t?.title&&r("span",{style:{fontSize:16,fontWeight:600,color:o.primary},children:t.title}),t?.subtitle&&r("span",{style:{fontSize:12,color:o.secondary,paddingTop:4},children:t.subtitle})]}),r("div",{style:{position:"absolute",inset:0,display:"flex",alignItems:"center",justifyContent:"center",pointerEvents:"none"},children:r("span",{style:{fontSize:10,color:o.muted,fontStyle:"italic"},children:"Double tap: Toggle \u2022 Triple tap: Close"})})]})}function Q({children:t,footer:e,footerHeight:n,disableScrollWrapper:o,contentStyle:s,theme:d,background:u}){return D("div",{style:{flex:1,minHeight:0,display:"flex",flexDirection:"column",backgroundColor:d.background,borderBottomLeftRadius:16,borderBottomRightRadius:16,overflow:"hidden",position:"relative",...s},children:[u?r(tt,{}):null,o?t:r("div",{style:{flex:1,minHeight:0,overflowY:"auto",WebkitOverflowScrolling:"touch",paddingBottom:n},children:t}),e?r("div",{style:{position:"absolute",left:0,right:0,bottom:0,backgroundColor:d.background,borderBottomLeftRadius:16,borderBottomRightRadius:16},children:e}):null]})}const Z="transform 320ms cubic-bezier(0.16, 1, 0.3, 1)";const O="opacity 200ms ease";function ut({props:t,theme:e,viewport:n,effectiveMaxHeight:o,panelHeight:s,setPanelHeight:d,onHeaderTap:u}){const{onClose:w,children:p,header:f,styles:c={},minHeight:g=et,footer:l,footerHeight:m=0,disableScrollWrapper:v,zIndex:R=1e3,background:T=true}=t;const b=F(null);const I=F(null);const[S,C]=y(false);const[$,P]=y(false);const k=F(false);const M=F(s);N(()=>{const x=requestAnimationFrame(()=>P(true));return()=>cancelAnimationFrame(x)},[]);const L=x=>{if(k.current)return;const _=x.currentTarget.getBoundingClientRect().top;const W=x.clientY-_;G(x,{onStart:()=>C(true),onMove:E=>{const A=E.clientY-W;const i=Math.max(g,Math.min(n.height-A,o));M.current=i;if(b.current)b.current.style.height=`${i}px`},onEnd:(E,{dy:A,vy:i,wasTap:a})=>{C(false);if(a){u();return}if(st(i,A,M.current,g)){k.current=true;const H=I.current;if(H){H.style.transition=`${Z}, ${O}`;H.style.transform="translateY(100%)";H.style.opacity="0"}setTimeout(w,220);return}d(M.current)}})};return r("div",{style:{position:"fixed",inset:0,zIndex:R,pointerEvents:"none"},children:r("div",{ref:I,style:{position:"absolute",bottom:0,left:0,right:0,pointerEvents:"auto",transform:$?"translateY(0)":"translateY(100%)",opacity:$?1:0,transition:`${Z}, ${O}`},children:D("div",{ref:b,style:{display:"flex",flexDirection:"column",height:s,backgroundColor:e.panel,borderTopLeftRadius:16,borderTopRightRadius:16,border:`1px solid ${e.border}`,boxShadow:`0 -4px 12px ${e.info}4D, 0 -2px 24px rgba(0,0,0,0.4)`,...c.container},children:[r("div",{onPointerDown:L,style:{minHeight:56,display:"flex",backgroundColor:e.panel,borderTopLeftRadius:16,borderTopRightRadius:16,borderBottom:"1px solid rgba(255, 255, 255, 0.1)",cursor:S?"ns-resize":"grab",touchAction:"none",userSelect:"none"},children:r(K,{header:f,isResizing:S,mode:"bottomSheet",theme:e})}),r(Q,{footer:l,footerHeight:m,disableScrollWrapper:v,contentStyle:c.content,theme:e,background:T,children:p})]})})})}function pt({isActive:t,theme:e}){return r("div",{style:{width:20,height:20,borderRadius:10,margin:5,backgroundColor:t?e.success+"1A":"transparent",border:t?`2px solid ${e.success}`:"2px solid transparent",boxShadow:t?`0 0 8px ${e.success}99`:"none"}})}function ft({props:t,theme:e,viewport:n,insets:o,rect:s,setRect:d,onHeaderTap:u}){const{onClose:w,children:p,header:f,styles:c={},footer:g,footerHeight:l=0,onBack:m,disableScrollWrapper:v,zIndex:R=1e3,background:T=true}=t;const b=F(null);const[I,S]=y(false);const[C,$]=y(false);const[P,k]=y(false);const M=F(s);M.current=s;const L=F(s);N(()=>{const i=requestAnimationFrame(()=>k(true));return()=>cancelAnimationFrame(i)},[]);const x=i=>{L.current=i;const a=b.current;if(!a)return;a.style.transform=`translate(${i.left}px, ${i.top}px)`;a.style.width=`${i.width}px`;a.style.height=`${i.height}px`};const _=i=>{const a=M.current;const H=i.clientX-a.left;const Y=i.clientY-a.top;G(i,{onMove:(B,{dx:z,dy:h})=>{if(Math.abs(z)+Math.abs(h)>5)S(true);x({...L.current,left:B.clientX-H,top:B.clientY-Y})},onEnd:(B,{wasTap:z})=>{S(false);if(z){u();return}const h=L.current;const j={...h,left:Math.max(o.left,Math.min(h.left,n.width-o.right-h.width)),top:Math.max(o.top,Math.min(h.top,n.height-o.bottom-h.height))};x(j);d(j)}})};const W=i=>a=>{a.stopPropagation();const H={...M.current};let Y=false;G(a,{onStart:()=>$(true),onMove:(B,{dx:z,dy:h})=>{if(Math.abs(z)<.5&&Math.abs(h)<.5)return;Y=true;x(rt(i,H,z,h,n,o,it(n.width),nt))},onEnd:()=>{$(false);if(!Y){if(i==="topRight"){w();return}if(i==="topLeft"&&m){m();return}}d(L.current)}})};const E=I||C;const A=[{corner:"topLeft",style:{top:4,left:4}},{corner:"topRight",style:{top:4,right:4}},{corner:"bottomLeft",style:{bottom:4,left:4}},{corner:"bottomRight",style:{bottom:4,right:4}}];return D("div",{ref:b,style:{position:"fixed",top:0,left:0,transform:`translate(${s.left}px, ${s.top}px)`,width:s.width,height:s.height,display:"flex",flexDirection:"column",backgroundColor:e.panel,borderRadius:16,border:E?`2px solid ${e.success}`:`1px solid ${e.border}`,boxShadow:E?`0 0 12px ${e.success}99`:`0 0 20px ${e.info}80, 0 8px 32px rgba(0,0,0,0.5)`,zIndex:R,opacity:P?1:0,transition:O,...c.container},children:[r("div",{onPointerDown:_,style:{minHeight:56,display:"flex",backgroundColor:e.panel,borderTopLeftRadius:14,borderTopRightRadius:14,borderBottom:"1px solid rgba(255, 255, 255, 0.1)",cursor:I?"grabbing":"grab",touchAction:"none",userSelect:"none"},children:r(K,{header:f,isResizing:E,mode:"floating",theme:e})}),r(Q,{footer:g,footerHeight:l,disableScrollWrapper:v,contentStyle:c.content,theme:e,background:T,children:p}),A.map(({corner:i,style:a})=>r("div",{onPointerDown:W(i),style:{position:"absolute",width:30,height:30,zIndex:10,touchAction:"none",cursor:i==="topLeft"||i==="bottomRight"?"nwse-resize":"nesw-resize",...a},children:r(pt,{isActive:E,theme:e})},i))]})}export const BottomSheet=t=>{const{visible:e,onClose:n,maxHeight:o,initialHeight:s=ot,initialMode:d="bottomSheet",onModeChange:u,theme:w}=t;const p={...at,...w};const{viewport:f,insets:c}=dt();const[g,l]=y(d);const[m,v]=y(s);const[R,T]=y(()=>({width:X,height:V,left:(window.innerWidth-X)/2,top:(window.innerHeight-V)/2}));const b=o||f.height-c.top;const I=()=>{const C=g==="bottomSheet"?"floating":"bottomSheet";l(C);u?.(C)};const S=lt(I,n);if(!e){return null}if(g==="floating"){return r(ft,{props:t,theme:p,viewport:f,insets:c,rect:R,setRect:T,onHeaderTap:S})}return r(ut,{props:t,theme:p,viewport:f,effectiveMaxHeight:b,panelHeight:Math.min(m,b),setPanelHeight:v,onHeaderTap:S})};
|