@xaui/native 0.0.13 → 0.0.14

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.
@@ -0,0 +1,379 @@
1
+ import {
2
+ Portal,
3
+ useXUITheme
4
+ } from "./chunk-NBRASCX4.js";
5
+
6
+ // src/components/bottom-sheet/bottom-sheet.tsx
7
+ import React from "react";
8
+ import { Animated as Animated3, Pressable, View } from "react-native";
9
+
10
+ // src/components/bottom-sheet/bottom-sheet.hook.ts
11
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
12
+ import { Animated as Animated2, Dimensions, PanResponder } from "react-native";
13
+ import { getSafeThemeColor } from "@xaui/core";
14
+
15
+ // src/components/bottom-sheet/bottom-sheet.animation.ts
16
+ import { Animated, Easing } from "react-native";
17
+ var SPRING_CONFIG = {
18
+ useNativeDriver: true,
19
+ speed: 14,
20
+ bounciness: 4
21
+ };
22
+ var TIMING_CONFIG = {
23
+ duration: 280,
24
+ easing: Easing.bezier(0.2, 0, 0, 1),
25
+ useNativeDriver: true
26
+ };
27
+ var runOpenAnimation = (translateY, backdropOpacity, targetTranslateY) => {
28
+ const animation = Animated.parallel([
29
+ Animated.spring(translateY, {
30
+ ...SPRING_CONFIG,
31
+ toValue: targetTranslateY
32
+ }),
33
+ Animated.timing(backdropOpacity, {
34
+ ...TIMING_CONFIG,
35
+ toValue: 1
36
+ })
37
+ ]);
38
+ animation.start();
39
+ return animation;
40
+ };
41
+ var runCloseAnimation = (translateY, backdropOpacity, screenHeight, onComplete) => {
42
+ const animation = Animated.parallel([
43
+ Animated.timing(translateY, {
44
+ ...TIMING_CONFIG,
45
+ toValue: screenHeight
46
+ }),
47
+ Animated.timing(backdropOpacity, {
48
+ ...TIMING_CONFIG,
49
+ toValue: 0
50
+ })
51
+ ]);
52
+ animation.start(({ finished }) => {
53
+ if (finished && onComplete) {
54
+ onComplete();
55
+ }
56
+ });
57
+ return animation;
58
+ };
59
+ var runSnapAnimation = (translateY, targetTranslateY) => {
60
+ const animation = Animated.spring(translateY, {
61
+ ...SPRING_CONFIG,
62
+ toValue: targetTranslateY
63
+ });
64
+ animation.start();
65
+ return animation;
66
+ };
67
+
68
+ // src/components/bottom-sheet/bottom-sheet.hook.ts
69
+ var DISMISS_VELOCITY_THRESHOLD = 0.5;
70
+ var DISMISS_DISTANCE_FRACTION = 0.3;
71
+ var SCREEN_HEIGHT = Dimensions.get("window").height;
72
+ var getTranslateYForSnap = (snapFraction) => SCREEN_HEIGHT * (1 - snapFraction);
73
+ var useBottomSheetAnimation = ({
74
+ isOpen,
75
+ snapPoints,
76
+ initialSnapIndex,
77
+ enableSwipeToDismiss,
78
+ disableAnimation,
79
+ onClose,
80
+ onSnapChange
81
+ }) => {
82
+ const [shouldRender, setShouldRender] = useState(false);
83
+ const currentSnapIndex = useRef(initialSnapIndex);
84
+ const animationRef = useRef(null);
85
+ const translateY = useRef(new Animated2.Value(SCREEN_HEIGHT)).current;
86
+ const backdropOpacity = useRef(new Animated2.Value(0)).current;
87
+ const sortedSnapPoints = useMemo(
88
+ () => [...snapPoints].sort((a, b) => a - b),
89
+ [snapPoints]
90
+ );
91
+ const snapTranslateValues = useMemo(
92
+ () => sortedSnapPoints.map(getTranslateYForSnap),
93
+ [sortedSnapPoints]
94
+ );
95
+ const open = useCallback(() => {
96
+ setShouldRender(true);
97
+ const targetIndex = Math.min(initialSnapIndex, sortedSnapPoints.length - 1);
98
+ currentSnapIndex.current = targetIndex;
99
+ if (disableAnimation) {
100
+ translateY.setValue(snapTranslateValues[targetIndex]);
101
+ backdropOpacity.setValue(1);
102
+ return;
103
+ }
104
+ translateY.setValue(SCREEN_HEIGHT);
105
+ backdropOpacity.setValue(0);
106
+ animationRef.current?.stop();
107
+ animationRef.current = runOpenAnimation(
108
+ translateY,
109
+ backdropOpacity,
110
+ snapTranslateValues[targetIndex]
111
+ );
112
+ }, [
113
+ initialSnapIndex,
114
+ sortedSnapPoints,
115
+ snapTranslateValues,
116
+ disableAnimation,
117
+ translateY,
118
+ backdropOpacity
119
+ ]);
120
+ const close = useCallback(() => {
121
+ if (disableAnimation) {
122
+ translateY.setValue(SCREEN_HEIGHT);
123
+ backdropOpacity.setValue(0);
124
+ setShouldRender(false);
125
+ onClose?.();
126
+ return;
127
+ }
128
+ animationRef.current?.stop();
129
+ animationRef.current = runCloseAnimation(
130
+ translateY,
131
+ backdropOpacity,
132
+ SCREEN_HEIGHT,
133
+ () => {
134
+ setShouldRender(false);
135
+ onClose?.();
136
+ }
137
+ );
138
+ }, [disableAnimation, translateY, backdropOpacity, onClose]);
139
+ const snapTo = useCallback(
140
+ (index) => {
141
+ const clampedIndex = Math.max(0, Math.min(index, sortedSnapPoints.length - 1));
142
+ currentSnapIndex.current = clampedIndex;
143
+ onSnapChange?.(clampedIndex);
144
+ if (disableAnimation) {
145
+ translateY.setValue(snapTranslateValues[clampedIndex]);
146
+ return;
147
+ }
148
+ animationRef.current?.stop();
149
+ animationRef.current = runSnapAnimation(
150
+ translateY,
151
+ snapTranslateValues[clampedIndex]
152
+ );
153
+ },
154
+ [sortedSnapPoints, snapTranslateValues, disableAnimation, translateY, onSnapChange]
155
+ );
156
+ useEffect(() => {
157
+ if (isOpen) {
158
+ open();
159
+ } else if (shouldRender) {
160
+ close();
161
+ }
162
+ }, [isOpen]);
163
+ const panResponder = useMemo(
164
+ () => PanResponder.create({
165
+ onStartShouldSetPanResponder: () => true,
166
+ onMoveShouldSetPanResponder: (_, gestureState) => Math.abs(gestureState.dy) > 5,
167
+ onPanResponderMove: (_, gestureState) => {
168
+ const currentTranslate = snapTranslateValues[currentSnapIndex.current];
169
+ const newTranslateY = currentTranslate + gestureState.dy;
170
+ const maxExpanded = snapTranslateValues[snapTranslateValues.length - 1];
171
+ const clamped = Math.max(maxExpanded, newTranslateY);
172
+ translateY.setValue(clamped);
173
+ },
174
+ onPanResponderRelease: (_, gestureState) => {
175
+ const currentTranslate = snapTranslateValues[currentSnapIndex.current];
176
+ const finalPosition = currentTranslate + gestureState.dy;
177
+ if (enableSwipeToDismiss && (gestureState.vy > DISMISS_VELOCITY_THRESHOLD || finalPosition > SCREEN_HEIGHT * (1 - sortedSnapPoints[0] * DISMISS_DISTANCE_FRACTION))) {
178
+ close();
179
+ return;
180
+ }
181
+ if (gestureState.vy < -DISMISS_VELOCITY_THRESHOLD) {
182
+ const nextIndex = Math.min(
183
+ currentSnapIndex.current + 1,
184
+ sortedSnapPoints.length - 1
185
+ );
186
+ snapTo(nextIndex);
187
+ return;
188
+ }
189
+ if (gestureState.vy > DISMISS_VELOCITY_THRESHOLD) {
190
+ const prevIndex = Math.max(currentSnapIndex.current - 1, 0);
191
+ if (prevIndex === currentSnapIndex.current && enableSwipeToDismiss) {
192
+ close();
193
+ return;
194
+ }
195
+ snapTo(prevIndex);
196
+ return;
197
+ }
198
+ let closestIndex = 0;
199
+ let minDistance = Infinity;
200
+ snapTranslateValues.forEach((snapVal, index) => {
201
+ const distance = Math.abs(finalPosition - snapVal);
202
+ if (distance < minDistance) {
203
+ minDistance = distance;
204
+ closestIndex = index;
205
+ }
206
+ });
207
+ snapTo(closestIndex);
208
+ }
209
+ }),
210
+ [
211
+ snapTranslateValues,
212
+ sortedSnapPoints,
213
+ enableSwipeToDismiss,
214
+ translateY,
215
+ close,
216
+ snapTo
217
+ ]
218
+ );
219
+ return {
220
+ shouldRender,
221
+ translateY,
222
+ backdropOpacity,
223
+ panResponder,
224
+ close,
225
+ snapTo,
226
+ screenHeight: SCREEN_HEIGHT
227
+ };
228
+ };
229
+ var useBottomSheetStyles = (themeColor, radius) => {
230
+ const theme = useXUITheme();
231
+ const safeThemeColor = getSafeThemeColor(themeColor);
232
+ const colorScheme = theme.colors[safeThemeColor];
233
+ const sheetStyles = useMemo(
234
+ () => ({
235
+ backgroundColor: colorScheme.background ?? theme.colors.background ?? "#ffffff",
236
+ borderTopLeftRadius: theme.borderRadius[radius],
237
+ borderTopRightRadius: theme.borderRadius[radius]
238
+ }),
239
+ [theme, colorScheme, radius]
240
+ );
241
+ const handleIndicatorColor = useMemo(
242
+ () => theme.mode === "dark" ? `${colorScheme.main}60` : `${colorScheme.main}40`,
243
+ [theme, colorScheme]
244
+ );
245
+ return { sheetStyles, handleIndicatorColor };
246
+ };
247
+
248
+ // src/components/bottom-sheet/bottom-sheet.style.ts
249
+ import { StyleSheet } from "react-native";
250
+ var styles = StyleSheet.create({
251
+ backdrop: {
252
+ position: "absolute",
253
+ top: 0,
254
+ left: 0,
255
+ right: 0,
256
+ bottom: 0,
257
+ backgroundColor: "rgba(0, 0, 0, 0.5)"
258
+ },
259
+ container: {
260
+ position: "absolute",
261
+ left: 0,
262
+ right: 0,
263
+ bottom: 0
264
+ },
265
+ sheet: {
266
+ overflow: "hidden"
267
+ },
268
+ handle: {
269
+ alignItems: "center",
270
+ justifyContent: "center",
271
+ paddingVertical: 10
272
+ },
273
+ handleIndicator: {
274
+ width: 36,
275
+ height: 4,
276
+ borderRadius: 2
277
+ },
278
+ content: {
279
+ flex: 1
280
+ }
281
+ });
282
+
283
+ // src/components/bottom-sheet/bottom-sheet.tsx
284
+ var BottomSheet = ({
285
+ children,
286
+ isOpen,
287
+ snapPoints = [0.4, 0.9],
288
+ initialSnapIndex = 0,
289
+ themeColor = "default",
290
+ radius = "lg",
291
+ showBackdrop = true,
292
+ closeOnBackdropPress = true,
293
+ enableSwipeToDismiss = true,
294
+ showHandle = true,
295
+ disableAnimation = false,
296
+ style,
297
+ handleContent,
298
+ onClose,
299
+ onSnapChange
300
+ }) => {
301
+ const {
302
+ shouldRender,
303
+ translateY,
304
+ backdropOpacity,
305
+ panResponder,
306
+ close,
307
+ screenHeight
308
+ } = useBottomSheetAnimation({
309
+ isOpen,
310
+ snapPoints,
311
+ initialSnapIndex,
312
+ enableSwipeToDismiss,
313
+ disableAnimation,
314
+ onClose,
315
+ onSnapChange
316
+ });
317
+ const { sheetStyles, handleIndicatorColor } = useBottomSheetStyles(
318
+ themeColor,
319
+ radius
320
+ );
321
+ if (!shouldRender) {
322
+ return null;
323
+ }
324
+ const handleBackdropPress = () => {
325
+ if (closeOnBackdropPress) {
326
+ close();
327
+ }
328
+ };
329
+ return /* @__PURE__ */ React.createElement(Portal, null, showBackdrop && /* @__PURE__ */ React.createElement(
330
+ Animated3.View,
331
+ {
332
+ style: [styles.backdrop, { opacity: backdropOpacity }]
333
+ },
334
+ /* @__PURE__ */ React.createElement(
335
+ Pressable,
336
+ {
337
+ style: styles.backdrop,
338
+ onPress: handleBackdropPress
339
+ }
340
+ )
341
+ ), /* @__PURE__ */ React.createElement(
342
+ Animated3.View,
343
+ {
344
+ style: [
345
+ styles.container,
346
+ {
347
+ height: screenHeight,
348
+ transform: [{ translateY }]
349
+ }
350
+ ]
351
+ },
352
+ /* @__PURE__ */ React.createElement(
353
+ View,
354
+ {
355
+ style: [
356
+ styles.sheet,
357
+ { height: screenHeight },
358
+ sheetStyles,
359
+ style
360
+ ],
361
+ ...panResponder.panHandlers
362
+ },
363
+ showHandle && /* @__PURE__ */ React.createElement(View, { style: styles.handle }, handleContent ?? /* @__PURE__ */ React.createElement(
364
+ View,
365
+ {
366
+ style: [
367
+ styles.handleIndicator,
368
+ { backgroundColor: handleIndicatorColor }
369
+ ]
370
+ }
371
+ )),
372
+ /* @__PURE__ */ React.createElement(View, { style: styles.content }, children)
373
+ )
374
+ ));
375
+ };
376
+
377
+ export {
378
+ BottomSheet
379
+ };