@buoy-gg/bottom-sheet 7.0.40 → 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.
@@ -1,1160 +1 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.BottomSheet = void 0;
7
- var _react = require("react");
8
- var _reactNative = require("react-native");
9
- var _sharedUi = require("@buoy-gg/shared-ui");
10
- var _jsxRuntime = require("react/jsx-runtime");
11
- /**
12
- * BottomSheet - Ultra-optimized modal component for true 60FPS performance
13
- *
14
- * Achieves 60FPS by following key principles:
15
- * 1. ALWAYS use native driver (useNativeDriver: true)
16
- * 2. Use transforms instead of layout properties (translateY instead of height)
17
- * 3. Use interpolation for all calculations (no JS thread math)
18
- * 4. Minimize PanResponder JS work (direct setValue, no state updates)
19
- *
20
- * Based on the proven JsModal component from @buoy-gg/shared-ui
21
- */
22
-
23
- // ============================================================================
24
- // CONSTANTS - Modal dimensions and configuration
25
- // ============================================================================
26
- const SCREEN = _reactNative.Dimensions.get("window");
27
- const MIN_HEIGHT = 100;
28
- const DEFAULT_HEIGHT = 400;
29
- const FLOATING_WIDTH = 380;
30
- const FLOATING_HEIGHT = 500;
31
- const FLOATING_MIN_WIDTH = SCREEN.width * 0.25; // 1/4 of screen width
32
- const FLOATING_MIN_HEIGHT = 80; // Just a bit more than header height (60px header + 20px content)
33
-
34
- // ============================================================================
35
- // SAFE AREA INSETS - Simplified version for standalone package
36
- // ============================================================================
37
-
38
- // Device detection map for iOS
39
- const iPhoneDimensionMap = {
40
- // iPhone 14 Pro, 14 Pro Max, 15, 15 Plus, 15 Pro, 15 Pro Max, 16 series (Dynamic Island)
41
- "393,852": {
42
- top: 59,
43
- bottom: 34
44
- },
45
- // 14 Pro, 15, 15 Pro, 16, 16 Pro
46
- "430,932": {
47
- top: 59,
48
- bottom: 34
49
- },
50
- // 14 Pro Max, 15 Plus, 15 Pro Max, 16 Plus, 16 Pro Max
51
- // iPhone 12, 12 Pro, 13, 13 Pro, 14
52
- "390,844": {
53
- top: 47,
54
- bottom: 34
55
- },
56
- // iPhone 12 Pro Max, 13 Pro Max, 14 Plus
57
- "428,926": {
58
- top: 47,
59
- bottom: 34
60
- },
61
- // iPhone 12 mini, 13 mini
62
- "375,812": {
63
- top: 50,
64
- bottom: 34
65
- },
66
- // iPhone XR, 11
67
- "414,896": {
68
- top: 48,
69
- bottom: 34
70
- }
71
- };
72
- const getPureJSSafeAreaInsets = () => {
73
- if (_reactNative.Platform.OS === "android") {
74
- const androidVersion = _reactNative.Platform.Version;
75
- const statusBarHeight = _reactNative.StatusBar.currentHeight || 0;
76
- const hasGestureNav = androidVersion >= 29;
77
- return {
78
- top: statusBarHeight,
79
- bottom: hasGestureNav ? 20 : 0,
80
- left: 0,
81
- right: 0
82
- };
83
- }
84
-
85
- // iOS
86
- const {
87
- width,
88
- height
89
- } = _reactNative.Dimensions.get("window");
90
- const dimensionKey = `${width},${height}`;
91
- const deviceInsets = iPhoneDimensionMap[dimensionKey];
92
- if (deviceInsets) {
93
- return {
94
- ...deviceInsets,
95
- left: 0,
96
- right: 0
97
- };
98
- }
99
-
100
- // Default for older iPhones without notch
101
- return {
102
- top: 20,
103
- // Standard status bar
104
- bottom: 0,
105
- left: 0,
106
- right: 0
107
- };
108
- };
109
- const useSafeAreaInsets = () => {
110
- const [insets, setInsets] = (0, _react.useState)(() => getPureJSSafeAreaInsets());
111
- (0, _react.useEffect)(() => {
112
- const updateInsets = () => {
113
- setInsets(getPureJSSafeAreaInsets());
114
- };
115
- const subscription = _reactNative.Dimensions.addEventListener("change", updateInsets);
116
- return () => {
117
- subscription?.remove();
118
- };
119
- }, []);
120
- return insets;
121
- };
122
-
123
- // ============================================================================
124
- // DEFAULT THEME - Simple, customizable color palette
125
- // ============================================================================
126
- const defaultTheme = {
127
- background: "rgba(8, 12, 21, 0.98)",
128
- panel: "rgba(16, 22, 35, 0.98)",
129
- border: "#00B8E666",
130
- primary: "#FFFFFF",
131
- secondary: "#B8BFC9",
132
- muted: "#7A8599",
133
- success: "#4AFF9F",
134
- error: "#FF5252",
135
- info: "#00B8E6"
136
- };
137
-
138
- // ============================================================================
139
- // TYPE DEFINITIONS - Interface contracts for the modal
140
- // ============================================================================
141
-
142
- // ============================================================================
143
- // ICON COMPONENTS - Visual indicators for modal controls
144
- // ============================================================================
145
-
146
- /**
147
- * DragIndicator - Visual feedback for draggable areas
148
- */
149
- const DragIndicator = /*#__PURE__*/(0, _react.memo)(function DragIndicator({
150
- isResizing,
151
- mode,
152
- hasCustomContent = false,
153
- theme = defaultTheme
154
- }) {
155
- const styles = createDynamicStyles(theme);
156
- return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
157
- style: [styles.dragIndicatorContainer, hasCustomContent && styles.dragIndicatorContainerCustom],
158
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
159
- style: [styles.dragIndicator, mode === "floating" && styles.floatingDragIndicator, isResizing && styles.dragIndicatorActive]
160
- }), isResizing && mode === "bottomSheet" && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
161
- style: styles.resizeGripContainer,
162
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
163
- style: styles.resizeGripLine
164
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
165
- style: styles.resizeGripLine
166
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
167
- style: styles.resizeGripLine
168
- })]
169
- })]
170
- });
171
- });
172
-
173
- /**
174
- * CornerHandle - Resize handle for floating mode corners
175
- */
176
- const CornerHandle = /*#__PURE__*/(0, _react.memo)(function CornerHandle({
177
- isActive,
178
- theme = defaultTheme
179
- }) {
180
- const styles = createDynamicStyles(theme);
181
- return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
182
- style: [styles.cornerHandle],
183
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
184
- style: [styles.handler, isActive && styles.handlerActive]
185
- })
186
- });
187
- });
188
-
189
- /**
190
- * ModalHeader - Header bar with title, controls, and drag area
191
- */
192
-
193
- const ModalHeader = /*#__PURE__*/(0, _react.memo)(function ModalHeader({
194
- header,
195
- onClose,
196
- onToggleMode,
197
- isResizing,
198
- mode,
199
- panHandlers,
200
- theme = defaultTheme
201
- }) {
202
- const styles = createDynamicStyles(theme);
203
- const lastTapRef = (0, _react.useRef)(0);
204
- const tapCountRef = (0, _react.useRef)(0);
205
- const tapTimeoutRef = (0, _react.useRef)(null);
206
- const handleHeaderTap = (0, _react.useCallback)(() => {
207
- const now = Date.now();
208
- const timeSinceLastTap = now - lastTapRef.current;
209
- if (timeSinceLastTap > 500) {
210
- tapCountRef.current = 0;
211
- }
212
- tapCountRef.current++;
213
- lastTapRef.current = now;
214
- if (tapTimeoutRef.current) {
215
- clearTimeout(tapTimeoutRef.current);
216
- }
217
- tapTimeoutRef.current = setTimeout(() => {
218
- if (tapCountRef.current === 2) {
219
- onToggleMode();
220
- } else if (tapCountRef.current >= 3) {
221
- onClose();
222
- }
223
- tapCountRef.current = 0;
224
- }, 300);
225
- }, [onToggleMode, onClose]);
226
- (0, _react.useEffect)(() => {
227
- return () => {
228
- if (tapTimeoutRef.current) {
229
- clearTimeout(tapTimeoutRef.current);
230
- }
231
- };
232
- }, []);
233
- const headerProps = panHandlers ? panHandlers : {};
234
- const shouldHandleTap = !!panHandlers;
235
-
236
- // If custom content is provided and it's a complete replacement
237
- if (header?.customContent) {
238
- const headerContent = /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
239
- style: styles.headerInner,
240
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(DragIndicator, {
241
- isResizing: isResizing,
242
- mode: mode,
243
- hasCustomContent: true,
244
- theme: theme
245
- }), header.customContent]
246
- });
247
- return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
248
- style: styles.header,
249
- ...headerProps,
250
- children: shouldHandleTap ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableWithoutFeedback, {
251
- onPress: handleHeaderTap,
252
- children: headerContent
253
- }) : headerContent
254
- });
255
- }
256
- const headerContent = /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
257
- style: styles.headerInner,
258
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(DragIndicator, {
259
- isResizing: isResizing,
260
- mode: mode,
261
- theme: theme
262
- }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
263
- style: styles.headerContent,
264
- children: [header?.title && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
265
- style: styles.headerTitle,
266
- children: header.title
267
- }), header?.subtitle && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
268
- style: styles.headerSubtitle,
269
- children: header.subtitle
270
- })]
271
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
272
- style: styles.headerHintText,
273
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
274
- style: styles.hintText,
275
- children: "Double tap: Toggle \u2022 Triple tap: Close"
276
- })
277
- })]
278
- });
279
- return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
280
- style: [styles.header, mode === "floating" && styles.floatingModeHeader],
281
- ...headerProps,
282
- children: shouldHandleTap ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableWithoutFeedback, {
283
- onPress: handleHeaderTap,
284
- children: headerContent
285
- }) : headerContent
286
- });
287
- });
288
-
289
- // ============================================================================
290
- // DRAGGABLE HEADER - For floating mode
291
- // ============================================================================
292
-
293
- const DraggableHeader = /*#__PURE__*/(0, _react.memo)(function DraggableHeader({
294
- children,
295
- position,
296
- onDragStart,
297
- onDragEnd,
298
- onTap,
299
- containerBounds = _reactNative.Dimensions.get("window"),
300
- elementSize = {
301
- width: 100,
302
- height: 50
303
- },
304
- minPosition = {
305
- x: 0,
306
- y: 0
307
- },
308
- style,
309
- enabled = true
310
- }) {
311
- const isDraggingRef = (0, _react.useRef)(false);
312
- const dragDistanceRef = (0, _react.useRef)(0);
313
- const touchOffsetRef = (0, _react.useRef)({
314
- x: 0,
315
- y: 0
316
- });
317
- const panResponder = (0, _react.useMemo)(() => _reactNative.PanResponder.create({
318
- onStartShouldSetPanResponder: () => enabled,
319
- onMoveShouldSetPanResponder: (_, g) => enabled && (Math.abs(g.dx) > 1 || Math.abs(g.dy) > 1),
320
- onPanResponderTerminationRequest: () => false,
321
- onPanResponderGrant: evt => {
322
- isDraggingRef.current = false;
323
- dragDistanceRef.current = 0;
324
- touchOffsetRef.current = {
325
- x: evt.nativeEvent.locationX,
326
- y: evt.nativeEvent.locationY
327
- };
328
- position.stopAnimation(({
329
- x,
330
- y
331
- }) => {
332
- position.setOffset({
333
- x,
334
- y
335
- });
336
- position.setValue({
337
- x: 0,
338
- y: 0
339
- });
340
- });
341
- },
342
- onPanResponderMove: (evt, gestureState) => {
343
- const totalDistance = Math.abs(gestureState.dx) + Math.abs(gestureState.dy);
344
- dragDistanceRef.current = totalDistance;
345
- if (totalDistance > 5 && !isDraggingRef.current) {
346
- isDraggingRef.current = true;
347
- onDragStart?.();
348
- }
349
- const x = evt.nativeEvent.pageX - touchOffsetRef.current.x;
350
- const y = evt.nativeEvent.pageY - touchOffsetRef.current.y;
351
- position.setOffset({
352
- x: 0,
353
- y: 0
354
- });
355
- position.setValue({
356
- x,
357
- y
358
- });
359
- },
360
- onPanResponderRelease: () => {
361
- const currentX = Number(JSON.stringify(position.x));
362
- const currentY = Number(JSON.stringify(position.y));
363
- if (dragDistanceRef.current <= 5 && !isDraggingRef.current) {
364
- position.setOffset({
365
- x: 0,
366
- y: 0
367
- });
368
- position.setValue({
369
- x: currentX,
370
- y: currentY
371
- });
372
- onTap?.();
373
- return;
374
- }
375
- const clampedX = Math.max(minPosition.x, Math.min(currentX, containerBounds.width - elementSize.width));
376
- const clampedY = Math.max(minPosition.y, Math.min(currentY, containerBounds.height - elementSize.height));
377
- position.setValue({
378
- x: clampedX,
379
- y: clampedY
380
- });
381
- onDragEnd?.({
382
- x: clampedX,
383
- y: clampedY
384
- });
385
- isDraggingRef.current = false;
386
- },
387
- onPanResponderTerminate: () => {
388
- isDraggingRef.current = false;
389
- }
390
- }), [enabled, position, onDragStart, onDragEnd, onTap, containerBounds, elementSize, minPosition]);
391
- return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
392
- style: style,
393
- ...panResponder.panHandlers,
394
- children: children
395
- });
396
- });
397
-
398
- // ============================================================================
399
- // MAIN COMPONENT - Optimized for 60FPS with transforms and interpolation
400
- // ============================================================================
401
- const BottomSheetComponent = ({
402
- visible,
403
- onClose,
404
- children,
405
- header,
406
- styles: customStyles = {},
407
- minHeight = MIN_HEIGHT,
408
- maxHeight,
409
- initialHeight = DEFAULT_HEIGHT,
410
- initialMode = "bottomSheet",
411
- onModeChange,
412
- footer,
413
- footerHeight = 0,
414
- onBack,
415
- theme: customTheme,
416
- background = true
417
- }) => {
418
- const theme = {
419
- ...defaultTheme,
420
- ...customTheme
421
- };
422
- const styles = (0, _react.useMemo)(() => createDynamicStyles(theme), [theme]);
423
- const insets = useSafeAreaInsets();
424
- const [mode, setMode] = (0, _react.useState)(initialMode);
425
- const [isResizing, setIsResizing] = (0, _react.useState)(false);
426
- const [isDragging, setIsDragging] = (0, _react.useState)(false);
427
- const [panelHeight, setPanelHeight] = (0, _react.useState)(initialHeight);
428
- const [dimensions, setDimensions] = (0, _react.useState)({
429
- width: FLOATING_WIDTH,
430
- height: FLOATING_HEIGHT,
431
- top: (SCREEN.height - FLOATING_HEIGHT) / 2,
432
- left: (SCREEN.width - FLOATING_WIDTH) / 2
433
- });
434
- const [containerBounds] = (0, _react.useState)({
435
- width: SCREEN.width,
436
- height: SCREEN.height
437
- });
438
-
439
- // ============================================================================
440
- // ANIMATED VALUES - All using native driver
441
- // ============================================================================
442
- const visibilityProgress = (0, _react.useRef)(new _reactNative.Animated.Value(0)).current;
443
- const bottomSheetTranslateY = (0, _react.useRef)(new _reactNative.Animated.Value(SCREEN.height)).current;
444
- const animatedBottomPosition = (0, _react.useRef)(new _reactNative.Animated.Value(initialHeight)).current;
445
- const currentHeightRef = (0, _react.useRef)(initialHeight);
446
-
447
- // Floating mode animations
448
- const floatingPosition = (0, _react.useRef)(new _reactNative.Animated.ValueXY({
449
- x: (SCREEN.width - FLOATING_WIDTH) / 2,
450
- y: (SCREEN.height - FLOATING_HEIGHT) / 2
451
- })).current;
452
- const animatedWidth = (0, _react.useRef)(new _reactNative.Animated.Value(FLOATING_WIDTH)).current;
453
- const animatedFloatingHeight = (0, _react.useRef)(new _reactNative.Animated.Value(FLOATING_HEIGHT)).current;
454
-
455
- // Refs for resize handles
456
- const currentDimensionsRef = (0, _react.useRef)(dimensions);
457
- const startDimensionsRef = (0, _react.useRef)(dimensions);
458
-
459
- // Update refs when dimensions change
460
- (0, _react.useEffect)(() => {
461
- currentDimensionsRef.current = dimensions;
462
- }, [dimensions]);
463
-
464
- // Cleanup on unmount
465
- (0, _react.useEffect)(() => {
466
- return () => {
467
- visibilityProgress.stopAnimation();
468
- bottomSheetTranslateY.stopAnimation();
469
- animatedBottomPosition.stopAnimation();
470
- floatingPosition.stopAnimation();
471
- animatedWidth.stopAnimation();
472
- animatedFloatingHeight.stopAnimation();
473
- visibilityProgress.setValue(0);
474
- bottomSheetTranslateY.setValue(SCREEN.height);
475
- animatedBottomPosition.setValue(initialHeight);
476
- currentHeightRef.current = initialHeight;
477
- };
478
- }, []);
479
-
480
- // ============================================================================
481
- // INTERPOLATIONS - All math done natively!
482
- // ============================================================================
483
- const modalOpacity = visibilityProgress.interpolate({
484
- inputRange: [0, 1],
485
- outputRange: [0, 1],
486
- extrapolate: "clamp"
487
- });
488
- const effectiveMaxHeight = maxHeight || SCREEN.height - insets.top;
489
-
490
- // Mode toggle handler
491
- const toggleMode = (0, _react.useCallback)(() => {
492
- setIsDragging(false);
493
- setIsResizing(false);
494
- const newMode = mode === "bottomSheet" ? "floating" : "bottomSheet";
495
- setMode(newMode);
496
- onModeChange?.(newMode);
497
- }, [mode, onModeChange]);
498
- (0, _react.useEffect)(() => {
499
- setIsDragging(false);
500
- setIsResizing(false);
501
- }, [mode]);
502
-
503
- // ============================================================================
504
- // EFFECT: Visibility Animations - All using native driver!
505
- // ============================================================================
506
- (0, _react.useEffect)(() => {
507
- let openAnimation = null;
508
- let closeAnimation = null;
509
- if (visible) {
510
- bottomSheetTranslateY.setValue(SCREEN.height);
511
- visibilityProgress.setValue(0);
512
- if (mode === "bottomSheet") {
513
- openAnimation = _reactNative.Animated.parallel([_reactNative.Animated.spring(bottomSheetTranslateY, {
514
- toValue: 0,
515
- tension: 180,
516
- friction: 22,
517
- useNativeDriver: true
518
- }), _reactNative.Animated.timing(visibilityProgress, {
519
- toValue: 1,
520
- duration: 200,
521
- useNativeDriver: true
522
- })]);
523
- openAnimation.start();
524
- } else {
525
- openAnimation = _reactNative.Animated.timing(visibilityProgress, {
526
- toValue: 1,
527
- duration: 200,
528
- useNativeDriver: true
529
- });
530
- openAnimation.start();
531
- }
532
- } else {
533
- if (mode === "bottomSheet") {
534
- closeAnimation = _reactNative.Animated.parallel([_reactNative.Animated.spring(bottomSheetTranslateY, {
535
- toValue: SCREEN.height,
536
- tension: 180,
537
- friction: 22,
538
- useNativeDriver: true
539
- }), _reactNative.Animated.timing(visibilityProgress, {
540
- toValue: 0,
541
- duration: 200,
542
- useNativeDriver: true
543
- })]);
544
- closeAnimation.start();
545
- } else {
546
- closeAnimation = _reactNative.Animated.timing(visibilityProgress, {
547
- toValue: 0,
548
- duration: 200,
549
- useNativeDriver: true
550
- });
551
- closeAnimation.start();
552
- }
553
- }
554
- return () => {
555
- if (openAnimation) openAnimation.stop();
556
- if (closeAnimation) closeAnimation.stop();
557
- };
558
- }, [visible, mode, visibilityProgress, bottomSheetTranslateY]);
559
-
560
- // ============================================================================
561
- // OPTIMIZED PAN RESPONDER: Bottom Sheet Resize
562
- // ============================================================================
563
- const headerTouchOffsetRef = (0, _react.useRef)(0);
564
- const bottomSheetPanResponder = (0, _react.useMemo)(() => _reactNative.PanResponder.create({
565
- onStartShouldSetPanResponder: () => mode === "bottomSheet",
566
- onMoveShouldSetPanResponder: (evt, gestureState) => mode === "bottomSheet" && Math.abs(gestureState.dy) > 3,
567
- onPanResponderTerminationRequest: () => false,
568
- onPanResponderGrant: evt => {
569
- setIsResizing(true);
570
- headerTouchOffsetRef.current = evt.nativeEvent.locationY || 0;
571
- animatedBottomPosition.stopAnimation(val => {
572
- currentHeightRef.current = val;
573
- });
574
- bottomSheetTranslateY.stopAnimation();
575
- },
576
- onPanResponderMove: evt => {
577
- const sheetTop = evt.nativeEvent.pageY - headerTouchOffsetRef.current;
578
- let targetHeight = SCREEN.height - sheetTop;
579
- targetHeight = Math.max(minHeight, Math.min(targetHeight, effectiveMaxHeight));
580
- animatedBottomPosition.setValue(targetHeight);
581
- currentHeightRef.current = targetHeight;
582
- },
583
- onPanResponderRelease: (evt, gestureState) => {
584
- setIsResizing(false);
585
- const finalHeight = currentHeightRef.current;
586
- const shouldClose = gestureState.vy > 0.8 && gestureState.dy > 50 || gestureState.dy > 150 && finalHeight <= minHeight;
587
- if (shouldClose) {
588
- _reactNative.Animated.parallel([_reactNative.Animated.timing(visibilityProgress, {
589
- toValue: 0,
590
- duration: 200,
591
- useNativeDriver: true
592
- }), _reactNative.Animated.spring(bottomSheetTranslateY, {
593
- toValue: SCREEN.height,
594
- tension: 180,
595
- friction: 22,
596
- useNativeDriver: true
597
- })]).start(() => onClose());
598
- return;
599
- }
600
- setPanelHeight(finalHeight);
601
- },
602
- onPanResponderTerminate: () => {
603
- setIsResizing(false);
604
- }
605
- }), [mode, minHeight, effectiveMaxHeight, animatedBottomPosition, bottomSheetTranslateY, visibilityProgress, onClose]);
606
-
607
- // ============================================================================
608
- // CREATE RESIZE HANDLER: For 4-corner resize in floating mode
609
- // ============================================================================
610
- const createResizeHandler = (0, _react.useCallback)(corner => {
611
- let didResize = false;
612
- return _reactNative.PanResponder.create({
613
- onStartShouldSetPanResponder: () => mode === "floating",
614
- onMoveShouldSetPanResponder: () => mode === "floating",
615
- onPanResponderGrant: () => {
616
- didResize = false;
617
- const currentDims = currentDimensionsRef.current;
618
- floatingPosition.stopAnimation(({
619
- x,
620
- y
621
- }) => {
622
- floatingPosition.setValue({
623
- x,
624
- y
625
- });
626
- });
627
- setIsResizing(true);
628
- startDimensionsRef.current = {
629
- ...currentDims
630
- };
631
- },
632
- onPanResponderMove: (_evt, gestureState) => {
633
- const {
634
- dx,
635
- dy
636
- } = gestureState;
637
- if (Math.abs(dx) < 0.5 && Math.abs(dy) < 0.5) return;
638
- didResize = true;
639
- const minLeft = Math.max(0, insets.left || 0);
640
- const maxRight = containerBounds.width - Math.max(0, insets.right || 0);
641
- const minTop = Math.max(0, insets.top || 0);
642
- const maxBottom = containerBounds.height - Math.max(0, insets.bottom || 0);
643
- const start = startDimensionsRef.current;
644
- const startRight = start.left + start.width;
645
- const startBottom = start.top + start.height;
646
- let left = start.left;
647
- let top = start.top;
648
- let right = startRight;
649
- let bottom = startBottom;
650
- switch (corner) {
651
- case "topLeft":
652
- {
653
- const newLeft = Math.max(minLeft, Math.min(start.left + dx, startRight - FLOATING_MIN_WIDTH));
654
- const newTop = Math.max(minTop, Math.min(start.top + dy, startBottom - FLOATING_MIN_HEIGHT));
655
- left = newLeft;
656
- top = newTop;
657
- right = startRight;
658
- bottom = startBottom;
659
- break;
660
- }
661
- case "topRight":
662
- {
663
- const newRight = Math.min(maxRight, Math.max(startRight + dx, start.left + FLOATING_MIN_WIDTH));
664
- const newTop = Math.max(minTop, Math.min(start.top + dy, startBottom - FLOATING_MIN_HEIGHT));
665
- left = start.left;
666
- top = newTop;
667
- right = newRight;
668
- bottom = startBottom;
669
- break;
670
- }
671
- case "bottomLeft":
672
- {
673
- const newLeft = Math.max(minLeft, Math.min(start.left + dx, startRight - FLOATING_MIN_WIDTH));
674
- const newBottom = Math.min(maxBottom, Math.max(startBottom + dy, start.top + FLOATING_MIN_HEIGHT));
675
- left = newLeft;
676
- top = start.top;
677
- right = startRight;
678
- bottom = newBottom;
679
- break;
680
- }
681
- case "bottomRight":
682
- {
683
- const newRight = Math.min(maxRight, Math.max(startRight + dx, start.left + FLOATING_MIN_WIDTH));
684
- const newBottom = Math.min(maxBottom, Math.max(startBottom + dy, start.top + FLOATING_MIN_HEIGHT));
685
- left = start.left;
686
- top = start.top;
687
- right = newRight;
688
- bottom = newBottom;
689
- break;
690
- }
691
- }
692
- const updatedWidth = Math.max(FLOATING_MIN_WIDTH, right - left);
693
- const updatedHeight = Math.max(FLOATING_MIN_HEIGHT, bottom - top);
694
- setDimensions({
695
- width: updatedWidth,
696
- height: updatedHeight,
697
- left,
698
- top
699
- });
700
- animatedWidth.setValue(updatedWidth);
701
- animatedFloatingHeight.setValue(updatedHeight);
702
- floatingPosition.setValue({
703
- x: left,
704
- y: top
705
- });
706
- currentDimensionsRef.current = {
707
- width: updatedWidth,
708
- height: updatedHeight,
709
- left,
710
- top
711
- };
712
- },
713
- onPanResponderRelease: () => {
714
- setIsResizing(false);
715
- if (corner === "topRight" && !didResize) {
716
- onClose();
717
- return;
718
- }
719
- if (corner === "topLeft" && !didResize && onBack) {
720
- onBack();
721
- return;
722
- }
723
- didResize = false;
724
- setDimensions(currentDimensionsRef.current);
725
- },
726
- onPanResponderTerminate: () => {
727
- setIsResizing(false);
728
- didResize = false;
729
- }
730
- });
731
- }, [mode, containerBounds, insets.left, insets.right, insets.top, insets.bottom, floatingPosition, animatedWidth, animatedFloatingHeight, onClose, onBack]);
732
- const resizeHandlers = (0, _react.useMemo)(() => {
733
- return {
734
- topLeft: createResizeHandler("topLeft"),
735
- topRight: createResizeHandler("topRight"),
736
- bottomLeft: createResizeHandler("bottomLeft"),
737
- bottomRight: createResizeHandler("bottomRight")
738
- };
739
- }, [createResizeHandler]);
740
-
741
- // ============================================================================
742
- // Floating Mode Drag Handlers
743
- // ============================================================================
744
- const handleFloatingDragStart = (0, _react.useCallback)(() => {
745
- setIsDragging(true);
746
- }, []);
747
- const handleFloatingDragEnd = (0, _react.useCallback)(finalPosition => {
748
- setIsDragging(false);
749
- const currentDims = currentDimensionsRef.current;
750
- const newDimensions = {
751
- ...currentDims,
752
- left: finalPosition.x,
753
- top: finalPosition.y
754
- };
755
- setDimensions(newDimensions);
756
- }, []);
757
- const lastTapRef = (0, _react.useRef)(0);
758
- const tapCountRef = (0, _react.useRef)(0);
759
- const tapTimeoutRef = (0, _react.useRef)(null);
760
- const handleFloatingTap = (0, _react.useCallback)(() => {
761
- const now = Date.now();
762
- const timeSinceLastTap = now - lastTapRef.current;
763
- if (timeSinceLastTap > 500) {
764
- tapCountRef.current = 0;
765
- }
766
- tapCountRef.current++;
767
- lastTapRef.current = now;
768
- if (tapTimeoutRef.current) {
769
- clearTimeout(tapTimeoutRef.current);
770
- }
771
- tapTimeoutRef.current = setTimeout(() => {
772
- if (tapCountRef.current === 2) {
773
- toggleMode();
774
- } else if (tapCountRef.current >= 3) {
775
- onClose();
776
- }
777
- tapCountRef.current = 0;
778
- }, 300);
779
- }, [toggleMode, onClose]);
780
- (0, _react.useEffect)(() => {
781
- return () => {
782
- if (tapTimeoutRef.current) {
783
- clearTimeout(tapTimeoutRef.current);
784
- }
785
- };
786
- }, []);
787
-
788
- // ============================================================================
789
- // RENDER: Modal UI with transform-based animations
790
- // ============================================================================
791
-
792
- if (!visible) {
793
- return null;
794
- }
795
-
796
- // Render floating mode
797
- if (mode === "floating") {
798
- return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Animated.View, {
799
- style: [styles.floatingModal, {
800
- width: dimensions.width,
801
- height: dimensions.height,
802
- opacity: modalOpacity,
803
- transform: [{
804
- translateX: floatingPosition.x
805
- }, {
806
- translateY: floatingPosition.y
807
- }]
808
- }, (isDragging || isResizing) && styles.floatingModalDragging, customStyles.container],
809
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(DraggableHeader, {
810
- position: floatingPosition,
811
- onDragStart: handleFloatingDragStart,
812
- onDragEnd: handleFloatingDragEnd,
813
- onTap: handleFloatingTap,
814
- containerBounds: containerBounds,
815
- elementSize: dimensions,
816
- minPosition: {
817
- x: 0,
818
- y: insets.top
819
- },
820
- style: styles.floatingHeader,
821
- enabled: mode === "floating" && !isResizing,
822
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(ModalHeader, {
823
- header: header,
824
- onClose: onClose,
825
- onToggleMode: toggleMode,
826
- isResizing: isDragging || isResizing,
827
- mode: mode,
828
- theme: theme
829
- })
830
- }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
831
- style: [styles.content, customStyles.content],
832
- children: [background ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ToolBackground, {}) : null, /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.ScrollView, {
833
- style: {
834
- flex: 1
835
- },
836
- contentContainerStyle: {
837
- flexGrow: 1,
838
- paddingBottom: footerHeight
839
- },
840
- showsVerticalScrollIndicator: true,
841
- nestedScrollEnabled: true,
842
- children: children
843
- }), footer ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
844
- style: styles.footerContainer,
845
- children: footer
846
- }) : null]
847
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
848
- ...resizeHandlers.topLeft.panHandlers,
849
- style: [styles.cornerHandleWrapper, {
850
- top: 4,
851
- left: 4
852
- }],
853
- hitSlop: {
854
- top: 8,
855
- left: 8,
856
- right: 8,
857
- bottom: 8
858
- },
859
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(CornerHandle, {
860
- isActive: isDragging || isResizing,
861
- theme: theme
862
- })
863
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
864
- ...resizeHandlers.topRight.panHandlers,
865
- style: [styles.cornerHandleWrapper, {
866
- top: 4,
867
- right: 4
868
- }],
869
- hitSlop: {
870
- top: 8,
871
- left: 8,
872
- right: 8,
873
- bottom: 8
874
- },
875
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(CornerHandle, {
876
- isActive: isDragging || isResizing,
877
- theme: theme
878
- })
879
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
880
- ...resizeHandlers.bottomLeft.panHandlers,
881
- style: [styles.cornerHandleWrapper, {
882
- bottom: 4,
883
- left: 4
884
- }],
885
- hitSlop: {
886
- top: 8,
887
- left: 8,
888
- right: 8,
889
- bottom: 8
890
- },
891
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(CornerHandle, {
892
- isActive: isDragging || isResizing,
893
- theme: theme
894
- })
895
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
896
- ...resizeHandlers.bottomRight.panHandlers,
897
- style: [styles.cornerHandleWrapper, {
898
- bottom: 4,
899
- right: 4
900
- }],
901
- hitSlop: {
902
- top: 8,
903
- left: 8,
904
- right: 8,
905
- bottom: 8
906
- },
907
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(CornerHandle, {
908
- isActive: isDragging || isResizing,
909
- theme: theme
910
- })
911
- })]
912
- });
913
- }
914
-
915
- // Render bottom sheet mode
916
- return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
917
- style: styles.fullScreenContainer,
918
- pointerEvents: "box-none",
919
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Animated.View, {
920
- style: [styles.bottomSheetWrapper, {
921
- opacity: modalOpacity,
922
- transform: [{
923
- translateY: bottomSheetTranslateY
924
- }]
925
- }],
926
- children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Animated.View, {
927
- style: [styles.bottomSheet, customStyles.container, {
928
- height: animatedBottomPosition
929
- }],
930
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(ModalHeader, {
931
- header: header,
932
- onClose: onClose,
933
- onToggleMode: toggleMode,
934
- isResizing: isResizing,
935
- mode: mode,
936
- panHandlers: bottomSheetPanResponder.panHandlers,
937
- theme: theme
938
- }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
939
- style: [styles.content, customStyles.content],
940
- children: [background ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ToolBackground, {}) : null, /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.ScrollView, {
941
- style: {
942
- flex: 1
943
- },
944
- contentContainerStyle: {
945
- flexGrow: 1,
946
- paddingBottom: footerHeight
947
- },
948
- showsVerticalScrollIndicator: true,
949
- nestedScrollEnabled: true,
950
- children: children
951
- }), footer ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
952
- style: styles.footerContainer,
953
- children: footer
954
- }) : null]
955
- })]
956
- })
957
- })
958
- });
959
- };
960
-
961
- // ============================================================================
962
- // DYNAMIC STYLES - Creates styles based on theme
963
- // ============================================================================
964
- const createDynamicStyles = theme => _reactNative.StyleSheet.create({
965
- fullScreenContainer: {
966
- ..._sharedUi.absoluteFill,
967
- zIndex: 1000
968
- },
969
- bottomSheetWrapper: {
970
- position: "absolute",
971
- bottom: 0,
972
- left: 0,
973
- right: 0
974
- },
975
- bottomSheet: {
976
- backgroundColor: theme.panel,
977
- borderTopLeftRadius: 16,
978
- borderTopRightRadius: 16,
979
- borderWidth: 1,
980
- borderColor: theme.border,
981
- shadowColor: theme.info,
982
- shadowOffset: {
983
- width: 0,
984
- height: -4
985
- },
986
- shadowOpacity: 0.3,
987
- shadowRadius: 12,
988
- elevation: 20
989
- },
990
- floatingModal: {
991
- position: "absolute",
992
- backgroundColor: theme.panel,
993
- borderRadius: 16,
994
- borderWidth: 1,
995
- borderColor: theme.border,
996
- shadowColor: theme.info,
997
- shadowOffset: {
998
- width: 0,
999
- height: 0
1000
- },
1001
- shadowOpacity: 0.5,
1002
- shadowRadius: 20,
1003
- elevation: 24,
1004
- zIndex: 1000,
1005
- width: FLOATING_WIDTH,
1006
- height: FLOATING_HEIGHT
1007
- },
1008
- floatingModalDragging: {
1009
- borderColor: theme.success,
1010
- borderWidth: 2,
1011
- shadowColor: theme.success + "99",
1012
- shadowOpacity: 0.8,
1013
- shadowRadius: 12
1014
- },
1015
- header: {
1016
- borderTopLeftRadius: 16,
1017
- borderTopRightRadius: 16,
1018
- backgroundColor: theme.panel,
1019
- minHeight: 56,
1020
- borderWidth: 1,
1021
- borderColor: theme.border,
1022
- borderBottomWidth: 1,
1023
- borderBottomColor: "rgba(255, 255, 255, 0.1)"
1024
- },
1025
- floatingHeader: {
1026
- borderTopLeftRadius: 14,
1027
- borderTopRightRadius: 14
1028
- },
1029
- floatingModeHeader: {
1030
- borderTopLeftRadius: 14,
1031
- borderTopRightRadius: 14
1032
- },
1033
- headerInner: {
1034
- flex: 1,
1035
- justifyContent: "center"
1036
- },
1037
- dragIndicatorContainer: {
1038
- alignItems: "center",
1039
- paddingVertical: 8,
1040
- backgroundColor: "transparent"
1041
- },
1042
- dragIndicatorContainerCustom: {
1043
- paddingTop: 6,
1044
- paddingBottom: 2,
1045
- backgroundColor: "transparent"
1046
- },
1047
- dragIndicator: {
1048
- width: 40,
1049
- height: 3,
1050
- backgroundColor: theme.info + "99",
1051
- borderRadius: 2,
1052
- shadowColor: theme.info,
1053
- shadowOffset: {
1054
- width: 0,
1055
- height: 0
1056
- },
1057
- shadowOpacity: 0.8,
1058
- shadowRadius: 4
1059
- },
1060
- floatingDragIndicator: {
1061
- width: 50,
1062
- height: 5,
1063
- backgroundColor: theme.muted
1064
- },
1065
- dragIndicatorActive: {
1066
- backgroundColor: theme.success,
1067
- width: 40
1068
- },
1069
- resizeGripContainer: {
1070
- position: "absolute",
1071
- flexDirection: "row",
1072
- gap: 2,
1073
- marginTop: 12
1074
- },
1075
- resizeGripLine: {
1076
- width: 12,
1077
- height: 1,
1078
- backgroundColor: theme.success,
1079
- opacity: 0.6
1080
- },
1081
- headerContent: {
1082
- paddingHorizontal: 16,
1083
- alignItems: "center"
1084
- },
1085
- headerTitle: {
1086
- fontSize: 16,
1087
- fontWeight: "600",
1088
- color: theme.primary
1089
- },
1090
- headerSubtitle: {
1091
- fontSize: 12,
1092
- color: theme.secondary,
1093
- paddingTop: 4
1094
- },
1095
- headerHintText: {
1096
- position: "absolute",
1097
- top: 0,
1098
- left: 0,
1099
- right: 0,
1100
- bottom: 0,
1101
- justifyContent: "center",
1102
- alignItems: "center"
1103
- },
1104
- hintText: {
1105
- fontSize: 10,
1106
- color: theme.muted,
1107
- fontStyle: "italic"
1108
- },
1109
- content: {
1110
- flex: 1,
1111
- backgroundColor: theme.background,
1112
- borderBottomLeftRadius: 16,
1113
- borderBottomRightRadius: 16,
1114
- overflow: "hidden"
1115
- },
1116
- cornerHandle: {
1117
- position: "absolute",
1118
- zIndex: 1
1119
- },
1120
- cornerHandleWrapper: {
1121
- position: "absolute",
1122
- width: 30,
1123
- height: 30,
1124
- zIndex: 1000
1125
- },
1126
- handler: {
1127
- width: 20,
1128
- height: 20,
1129
- backgroundColor: "transparent",
1130
- borderRadius: 10,
1131
- borderWidth: 0,
1132
- borderColor: "transparent"
1133
- },
1134
- handlerActive: {
1135
- backgroundColor: theme.success + "1A",
1136
- borderColor: theme.success,
1137
- borderWidth: 2,
1138
- shadowColor: theme.success + "99",
1139
- shadowOffset: {
1140
- width: 0,
1141
- height: 0
1142
- },
1143
- shadowOpacity: 1,
1144
- shadowRadius: 8
1145
- },
1146
- footerContainer: {
1147
- position: "absolute",
1148
- left: 0,
1149
- right: 0,
1150
- bottom: 0,
1151
- backgroundColor: theme.background,
1152
- borderBottomLeftRadius: 16,
1153
- borderBottomRightRadius: 16
1154
- }
1155
- });
1156
-
1157
- // ============================================================================
1158
- // EXPORT - Memoized modal component for optimal performance
1159
- // ============================================================================
1160
- const BottomSheet = exports.BottomSheet = /*#__PURE__*/(0, _react.memo)(BottomSheetComponent);
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.BottomSheet=void 0;var _react=require("react");var _reactNative=require("react-native");var _sharedUi=require("@buoy-gg/shared-ui");var _jsxRuntime=require("react/jsx-runtime");const SCREEN=_reactNative.Dimensions.get("window");const MIN_HEIGHT=100;const DEFAULT_HEIGHT=400;const FLOATING_WIDTH=380;const FLOATING_HEIGHT=500;const FLOATING_MIN_WIDTH=SCREEN.width*.25;const FLOATING_MIN_HEIGHT=80;const iPhoneDimensionMap={"393,852":{top:59,bottom:34},"430,932":{top:59,bottom:34},"390,844":{top:47,bottom:34},"428,926":{top:47,bottom:34},"375,812":{top:50,bottom:34},"414,896":{top:48,bottom:34}};const getPureJSSafeAreaInsets=()=>{if(_reactNative.Platform.OS==="android"){const p=_reactNative.Platform.Version;const s=_reactNative.StatusBar.currentHeight||0;const x=p>=29;return{top:s,bottom:x?20:0,left:0,right:0}}const{width:e,height:o}=_reactNative.Dimensions.get("window");const r=`${e},${o}`;const c=iPhoneDimensionMap[r];if(c){return{...c,left:0,right:0}}return{top:20,bottom:0,left:0,right:0}};const useSafeAreaInsets=()=>{const[e,o]=(0,_react.useState)(()=>getPureJSSafeAreaInsets());(0,_react.useEffect)(()=>{const r=()=>{o(getPureJSSafeAreaInsets())};const c=_reactNative.Dimensions.addEventListener("change",r);return()=>{c?.remove()}},[]);return e};const defaultTheme={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"};const DragIndicator=(0,_react.memo)(function e({isResizing:o,mode:r,hasCustomContent:c=false,theme:p=defaultTheme}){const s=createDynamicStyles(p);return(0,_jsxRuntime.jsxs)(_reactNative.View,{style:[s.dragIndicatorContainer,c&&s.dragIndicatorContainerCustom],children:[(0,_jsxRuntime.jsx)(_reactNative.View,{style:[s.dragIndicator,r==="floating"&&s.floatingDragIndicator,o&&s.dragIndicatorActive]}),o&&r==="bottomSheet"&&(0,_jsxRuntime.jsxs)(_reactNative.View,{style:s.resizeGripContainer,children:[(0,_jsxRuntime.jsx)(_reactNative.View,{style:s.resizeGripLine}),(0,_jsxRuntime.jsx)(_reactNative.View,{style:s.resizeGripLine}),(0,_jsxRuntime.jsx)(_reactNative.View,{style:s.resizeGripLine})]})]})});const CornerHandle=(0,_react.memo)(function e({isActive:o,theme:r=defaultTheme}){const c=createDynamicStyles(r);return(0,_jsxRuntime.jsx)(_reactNative.View,{style:[c.cornerHandle],children:(0,_jsxRuntime.jsx)(_reactNative.View,{style:[c.handler,o&&c.handlerActive]})})});const ModalHeader=(0,_react.memo)(function e({header:o,onClose:r,onToggleMode:c,isResizing:p,mode:s,panHandlers:x,theme:m=defaultTheme}){const h=createDynamicStyles(m);const j=(0,_react.useRef)(0);const g=(0,_react.useRef)(0);const f=(0,_react.useRef)(null);const w=(0,_react.useCallback)(()=>{const t=Date.now();const d=t-j.current;if(d>500){g.current=0}g.current++;j.current=t;if(f.current){clearTimeout(f.current)}f.current=setTimeout(()=>{if(g.current===2){c()}else if(g.current>=3){r()}g.current=0},300)},[c,r]);(0,_react.useEffect)(()=>{return()=>{if(f.current){clearTimeout(f.current)}}},[]);const S=x?x:{};const A=!!x;if(o?.customContent){const t=(0,_jsxRuntime.jsxs)(_reactNative.View,{style:h.headerInner,children:[(0,_jsxRuntime.jsx)(DragIndicator,{isResizing:p,mode:s,hasCustomContent:true,theme:m}),o.customContent]});return(0,_jsxRuntime.jsx)(_reactNative.View,{style:h.header,...S,children:A?(0,_jsxRuntime.jsx)(_reactNative.TouchableWithoutFeedback,{onPress:w,children:t}):t})}const i=(0,_jsxRuntime.jsxs)(_reactNative.View,{style:h.headerInner,children:[(0,_jsxRuntime.jsx)(DragIndicator,{isResizing:p,mode:s,theme:m}),(0,_jsxRuntime.jsxs)(_reactNative.View,{style:h.headerContent,children:[o?.title&&(0,_jsxRuntime.jsx)(_reactNative.Text,{style:h.headerTitle,children:o.title}),o?.subtitle&&(0,_jsxRuntime.jsx)(_reactNative.Text,{style:h.headerSubtitle,children:o.subtitle})]}),(0,_jsxRuntime.jsx)(_reactNative.View,{style:h.headerHintText,children:(0,_jsxRuntime.jsx)(_reactNative.Text,{style:h.hintText,children:"Double tap: Toggle \u2022 Triple tap: Close"})})]});return(0,_jsxRuntime.jsx)(_reactNative.View,{style:[h.header,s==="floating"&&h.floatingModeHeader],...S,children:A?(0,_jsxRuntime.jsx)(_reactNative.TouchableWithoutFeedback,{onPress:w,children:i}):i})});const DraggableHeader=(0,_react.memo)(function e({children:o,position:r,onDragStart:c,onDragEnd:p,onTap:s,containerBounds:x=_reactNative.Dimensions.get("window"),elementSize:m={width:100,height:50},minPosition:h={x:0,y:0},style:j,enabled:g=true}){const f=(0,_react.useRef)(false);const w=(0,_react.useRef)(0);const S=(0,_react.useRef)({x:0,y:0});const A=(0,_react.useMemo)(()=>_reactNative.PanResponder.create({onStartShouldSetPanResponder:()=>g,onMoveShouldSetPanResponder:(i,t)=>g&&(Math.abs(t.dx)>1||Math.abs(t.dy)>1),onPanResponderTerminationRequest:()=>false,onPanResponderGrant:i=>{f.current=false;w.current=0;S.current={x:i.nativeEvent.locationX,y:i.nativeEvent.locationY};r.stopAnimation(({x:t,y:d})=>{r.setOffset({x:t,y:d});r.setValue({x:0,y:0})})},onPanResponderMove:(i,t)=>{const d=Math.abs(t.dx)+Math.abs(t.dy);w.current=d;if(d>5&&!f.current){f.current=true;c?.()}const l=i.nativeEvent.pageX-S.current.x;const K=i.nativeEvent.pageY-S.current.y;r.setOffset({x:0,y:0});r.setValue({x:l,y:K})},onPanResponderRelease:()=>{const i=Number(JSON.stringify(r.x));const t=Number(JSON.stringify(r.y));if(w.current<=5&&!f.current){r.setOffset({x:0,y:0});r.setValue({x:i,y:t});s?.();return}const d=Math.max(h.x,Math.min(i,x.width-m.width));const l=Math.max(h.y,Math.min(t,x.height-m.height));r.setValue({x:d,y:l});p?.({x:d,y:l});f.current=false},onPanResponderTerminate:()=>{f.current=false}}),[g,r,c,p,s,x,m,h]);return(0,_jsxRuntime.jsx)(_reactNative.View,{style:j,...A.panHandlers,children:o})});const BottomSheetComponent=({visible:e,onClose:o,children:r,header:c,styles:p={},minHeight:s=MIN_HEIGHT,maxHeight:x,initialHeight:m=DEFAULT_HEIGHT,initialMode:h="bottomSheet",onModeChange:j,footer:g,footerHeight:f=0,onBack:w,theme:S,background:A=true})=>{const i={...defaultTheme,...S};const t=(0,_react.useMemo)(()=>createDynamicStyles(i),[i]);const d=useSafeAreaInsets();const[l,K]=(0,_react.useState)(h);const[T,M]=(0,_react.useState)(false);const[E,q]=(0,_react.useState)(false);const[be,he]=(0,_react.useState)(m);const[I,Q]=(0,_react.useState)({width:FLOATING_WIDTH,height:FLOATING_HEIGHT,top:(SCREEN.height-FLOATING_HEIGHT)/2,left:(SCREEN.width-FLOATING_WIDTH)/2});const[X]=(0,_react.useState)({width:SCREEN.width,height:SCREEN.height});const R=(0,_react.useRef)(new _reactNative.Animated.Value(0)).current;const y=(0,_react.useRef)(new _reactNative.Animated.Value(SCREEN.height)).current;const B=(0,_react.useRef)(new _reactNative.Animated.Value(m)).current;const J=(0,_react.useRef)(m);const V=(0,_react.useRef)(new _reactNative.Animated.ValueXY({x:(SCREEN.width-FLOATING_WIDTH)/2,y:(SCREEN.height-FLOATING_HEIGHT)/2})).current;const Z=(0,_react.useRef)(new _reactNative.Animated.Value(FLOATING_WIDTH)).current;const ee=(0,_react.useRef)(new _reactNative.Animated.Value(FLOATING_HEIGHT)).current;const F=(0,_react.useRef)(I);const oe=(0,_react.useRef)(I);(0,_react.useEffect)(()=>{F.current=I},[I]);(0,_react.useEffect)(()=>{return()=>{R.stopAnimation();y.stopAnimation();B.stopAnimation();V.stopAnimation();Z.stopAnimation();ee.stopAnimation();R.setValue(0);y.setValue(SCREEN.height);B.setValue(m);J.current=m}},[]);const re=R.interpolate({inputRange:[0,1],outputRange:[0,1],extrapolate:"clamp"});const ae=x||SCREEN.height-d.top;const U=(0,_react.useCallback)(()=>{q(false);M(false);const a=l==="bottomSheet"?"floating":"bottomSheet";K(a);j?.(a)},[l,j]);(0,_react.useEffect)(()=>{q(false);M(false)},[l]);(0,_react.useEffect)(()=>{let a=null;let n=null;if(e){y.setValue(SCREEN.height);R.setValue(0);if(l==="bottomSheet"){a=_reactNative.Animated.parallel([_reactNative.Animated.spring(y,{toValue:0,tension:180,friction:22,useNativeDriver:true}),_reactNative.Animated.timing(R,{toValue:1,duration:200,useNativeDriver:true})]);a.start()}else{a=_reactNative.Animated.timing(R,{toValue:1,duration:200,useNativeDriver:true});a.start()}}else{if(l==="bottomSheet"){n=_reactNative.Animated.parallel([_reactNative.Animated.spring(y,{toValue:SCREEN.height,tension:180,friction:22,useNativeDriver:true}),_reactNative.Animated.timing(R,{toValue:0,duration:200,useNativeDriver:true})]);n.start()}else{n=_reactNative.Animated.timing(R,{toValue:0,duration:200,useNativeDriver:true});n.start()}}return()=>{if(a)a.stop();if(n)n.stop()}},[e,l,R,y]);const se=(0,_react.useRef)(0);const fe=(0,_react.useMemo)(()=>_reactNative.PanResponder.create({onStartShouldSetPanResponder:()=>l==="bottomSheet",onMoveShouldSetPanResponder:(a,n)=>l==="bottomSheet"&&Math.abs(n.dy)>3,onPanResponderTerminationRequest:()=>false,onPanResponderGrant:a=>{M(true);se.current=a.nativeEvent.locationY||0;B.stopAnimation(n=>{J.current=n});y.stopAnimation()},onPanResponderMove:a=>{const n=a.nativeEvent.pageY-se.current;let b=SCREEN.height-n;b=Math.max(s,Math.min(b,ae));B.setValue(b);J.current=b},onPanResponderRelease:(a,n)=>{M(false);const b=J.current;const z=n.vy>.8&&n.dy>50||n.dy>150&&b<=s;if(z){_reactNative.Animated.parallel([_reactNative.Animated.timing(R,{toValue:0,duration:200,useNativeDriver:true}),_reactNative.Animated.spring(y,{toValue:SCREEN.height,tension:180,friction:22,useNativeDriver:true})]).start(()=>o());return}he(b)},onPanResponderTerminate:()=>{M(false)}}),[l,s,ae,B,y,R,o]);const O=(0,_react.useCallback)(a=>{let n=false;return _reactNative.PanResponder.create({onStartShouldSetPanResponder:()=>l==="floating",onMoveShouldSetPanResponder:()=>l==="floating",onPanResponderGrant:()=>{n=false;const b=F.current;V.stopAnimation(({x:z,y:H})=>{V.setValue({x:z,y:H})});M(true);oe.current={...b}},onPanResponderMove:(b,z)=>{const{dx:H,dy:G}=z;if(Math.abs(H)<.5&&Math.abs(G)<.5)return;n=true;const le=Math.max(0,d.left||0);const de=X.width-Math.max(0,d.right||0);const ce=Math.max(0,d.top||0);const ue=X.height-Math.max(0,d.bottom||0);const u=oe.current;const D=u.left+u.width;const P=u.top+u.height;let v=u.left;let C=u.top;let _=D;let Y=P;switch(a){case"topLeft":{const L=Math.max(le,Math.min(u.left+H,D-FLOATING_MIN_WIDTH));const k=Math.max(ce,Math.min(u.top+G,P-FLOATING_MIN_HEIGHT));v=L;C=k;_=D;Y=P;break}case"topRight":{const L=Math.min(de,Math.max(D+H,u.left+FLOATING_MIN_WIDTH));const k=Math.max(ce,Math.min(u.top+G,P-FLOATING_MIN_HEIGHT));v=u.left;C=k;_=L;Y=P;break}case"bottomLeft":{const L=Math.max(le,Math.min(u.left+H,D-FLOATING_MIN_WIDTH));const k=Math.min(ue,Math.max(P+G,u.top+FLOATING_MIN_HEIGHT));v=L;C=u.top;_=D;Y=k;break}case"bottomRight":{const L=Math.min(de,Math.max(D+H,u.left+FLOATING_MIN_WIDTH));const k=Math.min(ue,Math.max(P+G,u.top+FLOATING_MIN_HEIGHT));v=u.left;C=u.top;_=L;Y=k;break}}const te=Math.max(FLOATING_MIN_WIDTH,_-v);const ne=Math.max(FLOATING_MIN_HEIGHT,Y-C);Q({width:te,height:ne,left:v,top:C});Z.setValue(te);ee.setValue(ne);V.setValue({x:v,y:C});F.current={width:te,height:ne,left:v,top:C}},onPanResponderRelease:()=>{M(false);if(a==="topRight"&&!n){o();return}if(a==="topLeft"&&!n&&w){w();return}n=false;Q(F.current)},onPanResponderTerminate:()=>{M(false);n=false}})},[l,X,d.left,d.right,d.top,d.bottom,V,Z,ee,o,w]);const $=(0,_react.useMemo)(()=>{return{topLeft:O("topLeft"),topRight:O("topRight"),bottomLeft:O("bottomLeft"),bottomRight:O("bottomRight")}},[O]);const pe=(0,_react.useCallback)(()=>{q(true)},[]);const ge=(0,_react.useCallback)(a=>{q(false);const n=F.current;const b={...n,left:a.x,top:a.y};Q(b)},[]);const ie=(0,_react.useRef)(0);const N=(0,_react.useRef)(0);const W=(0,_react.useRef)(null);const me=(0,_react.useCallback)(()=>{const a=Date.now();const n=a-ie.current;if(n>500){N.current=0}N.current++;ie.current=a;if(W.current){clearTimeout(W.current)}W.current=setTimeout(()=>{if(N.current===2){U()}else if(N.current>=3){o()}N.current=0},300)},[U,o]);(0,_react.useEffect)(()=>{return()=>{if(W.current){clearTimeout(W.current)}}},[]);if(!e){return null}if(l==="floating"){return(0,_jsxRuntime.jsxs)(_reactNative.Animated.View,{style:[t.floatingModal,{width:I.width,height:I.height,opacity:re,transform:[{translateX:V.x},{translateY:V.y}]},(E||T)&&t.floatingModalDragging,p.container],children:[(0,_jsxRuntime.jsx)(DraggableHeader,{position:V,onDragStart:pe,onDragEnd:ge,onTap:me,containerBounds:X,elementSize:I,minPosition:{x:0,y:d.top},style:t.floatingHeader,enabled:l==="floating"&&!T,children:(0,_jsxRuntime.jsx)(ModalHeader,{header:c,onClose:o,onToggleMode:U,isResizing:E||T,mode:l,theme:i})}),(0,_jsxRuntime.jsxs)(_reactNative.View,{style:[t.content,p.content],children:[A?(0,_jsxRuntime.jsx)(_sharedUi.ToolBackground,{}):null,(0,_jsxRuntime.jsx)(_reactNative.ScrollView,{style:{flex:1},contentContainerStyle:{flexGrow:1,paddingBottom:f},showsVerticalScrollIndicator:true,nestedScrollEnabled:true,children:r}),g?(0,_jsxRuntime.jsx)(_reactNative.View,{style:t.footerContainer,children:g}):null]}),(0,_jsxRuntime.jsx)(_reactNative.View,{...$.topLeft.panHandlers,style:[t.cornerHandleWrapper,{top:4,left:4}],hitSlop:{top:8,left:8,right:8,bottom:8},children:(0,_jsxRuntime.jsx)(CornerHandle,{isActive:E||T,theme:i})}),(0,_jsxRuntime.jsx)(_reactNative.View,{...$.topRight.panHandlers,style:[t.cornerHandleWrapper,{top:4,right:4}],hitSlop:{top:8,left:8,right:8,bottom:8},children:(0,_jsxRuntime.jsx)(CornerHandle,{isActive:E||T,theme:i})}),(0,_jsxRuntime.jsx)(_reactNative.View,{...$.bottomLeft.panHandlers,style:[t.cornerHandleWrapper,{bottom:4,left:4}],hitSlop:{top:8,left:8,right:8,bottom:8},children:(0,_jsxRuntime.jsx)(CornerHandle,{isActive:E||T,theme:i})}),(0,_jsxRuntime.jsx)(_reactNative.View,{...$.bottomRight.panHandlers,style:[t.cornerHandleWrapper,{bottom:4,right:4}],hitSlop:{top:8,left:8,right:8,bottom:8},children:(0,_jsxRuntime.jsx)(CornerHandle,{isActive:E||T,theme:i})})]})}return(0,_jsxRuntime.jsx)(_reactNative.View,{style:t.fullScreenContainer,pointerEvents:"box-none",children:(0,_jsxRuntime.jsx)(_reactNative.Animated.View,{style:[t.bottomSheetWrapper,{opacity:re,transform:[{translateY:y}]}],children:(0,_jsxRuntime.jsxs)(_reactNative.Animated.View,{style:[t.bottomSheet,p.container,{height:B}],children:[(0,_jsxRuntime.jsx)(ModalHeader,{header:c,onClose:o,onToggleMode:U,isResizing:T,mode:l,panHandlers:fe.panHandlers,theme:i}),(0,_jsxRuntime.jsxs)(_reactNative.View,{style:[t.content,p.content],children:[A?(0,_jsxRuntime.jsx)(_sharedUi.ToolBackground,{}):null,(0,_jsxRuntime.jsx)(_reactNative.ScrollView,{style:{flex:1},contentContainerStyle:{flexGrow:1,paddingBottom:f},showsVerticalScrollIndicator:true,nestedScrollEnabled:true,children:r}),g?(0,_jsxRuntime.jsx)(_reactNative.View,{style:t.footerContainer,children:g}):null]})]})})})};const createDynamicStyles=e=>_reactNative.StyleSheet.create({fullScreenContainer:{..._sharedUi.absoluteFill,zIndex:1e3},bottomSheetWrapper:{position:"absolute",bottom:0,left:0,right:0},bottomSheet:{backgroundColor:e.panel,borderTopLeftRadius:16,borderTopRightRadius:16,borderWidth:1,borderColor:e.border,shadowColor:e.info,shadowOffset:{width:0,height:-4},shadowOpacity:.3,shadowRadius:12,elevation:20},floatingModal:{position:"absolute",backgroundColor:e.panel,borderRadius:16,borderWidth:1,borderColor:e.border,shadowColor:e.info,shadowOffset:{width:0,height:0},shadowOpacity:.5,shadowRadius:20,elevation:24,zIndex:1e3,width:FLOATING_WIDTH,height:FLOATING_HEIGHT},floatingModalDragging:{borderColor:e.success,borderWidth:2,shadowColor:e.success+"99",shadowOpacity:.8,shadowRadius:12},header:{borderTopLeftRadius:16,borderTopRightRadius:16,backgroundColor:e.panel,minHeight:56,borderWidth:1,borderColor:e.border,borderBottomWidth:1,borderBottomColor:"rgba(255, 255, 255, 0.1)"},floatingHeader:{borderTopLeftRadius:14,borderTopRightRadius:14},floatingModeHeader:{borderTopLeftRadius:14,borderTopRightRadius:14},headerInner:{flex:1,justifyContent:"center"},dragIndicatorContainer:{alignItems:"center",paddingVertical:8,backgroundColor:"transparent"},dragIndicatorContainerCustom:{paddingTop:6,paddingBottom:2,backgroundColor:"transparent"},dragIndicator:{width:40,height:3,backgroundColor:e.info+"99",borderRadius:2,shadowColor:e.info,shadowOffset:{width:0,height:0},shadowOpacity:.8,shadowRadius:4},floatingDragIndicator:{width:50,height:5,backgroundColor:e.muted},dragIndicatorActive:{backgroundColor:e.success,width:40},resizeGripContainer:{position:"absolute",flexDirection:"row",gap:2,marginTop:12},resizeGripLine:{width:12,height:1,backgroundColor:e.success,opacity:.6},headerContent:{paddingHorizontal:16,alignItems:"center"},headerTitle:{fontSize:16,fontWeight:"600",color:e.primary},headerSubtitle:{fontSize:12,color:e.secondary,paddingTop:4},headerHintText:{position:"absolute",top:0,left:0,right:0,bottom:0,justifyContent:"center",alignItems:"center"},hintText:{fontSize:10,color:e.muted,fontStyle:"italic"},content:{flex:1,backgroundColor:e.background,borderBottomLeftRadius:16,borderBottomRightRadius:16,overflow:"hidden"},cornerHandle:{position:"absolute",zIndex:1},cornerHandleWrapper:{position:"absolute",width:30,height:30,zIndex:1e3},handler:{width:20,height:20,backgroundColor:"transparent",borderRadius:10,borderWidth:0,borderColor:"transparent"},handlerActive:{backgroundColor:e.success+"1A",borderColor:e.success,borderWidth:2,shadowColor:e.success+"99",shadowOffset:{width:0,height:0},shadowOpacity:1,shadowRadius:8},footerContainer:{position:"absolute",left:0,right:0,bottom:0,backgroundColor:e.background,borderBottomLeftRadius:16,borderBottomRightRadius:16}});const BottomSheet=exports.BottomSheet=(0,_react.memo)(BottomSheetComponent);