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