zkit-ui 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (267) hide show
  1. package/README.md +267 -0
  2. package/dist/ComponentLibProvider.d.ts +15 -0
  3. package/dist/ComponentLibProvider.d.ts.map +1 -0
  4. package/dist/ComponentLibProvider.js +18 -0
  5. package/dist/assets/animations/spinner.json +1 -0
  6. package/dist/assets/icons/check.svg +4 -0
  7. package/dist/assets/icons/toast/error.webp +0 -0
  8. package/dist/assets/icons/toast/success.webp +0 -0
  9. package/dist/assets/icons/toast/warning.webp +0 -0
  10. package/dist/config.d.ts +16 -0
  11. package/dist/config.d.ts.map +1 -0
  12. package/dist/config.js +42 -0
  13. package/dist/i18n/I18nProvider.d.ts +11 -0
  14. package/dist/i18n/I18nProvider.d.ts.map +1 -0
  15. package/dist/i18n/I18nProvider.js +84 -0
  16. package/dist/i18n/locale.d.ts +7 -0
  17. package/dist/i18n/locale.d.ts.map +1 -0
  18. package/dist/i18n/locale.js +87 -0
  19. package/dist/i18n/locales/de.d.ts +51 -0
  20. package/dist/i18n/locales/de.d.ts.map +1 -0
  21. package/dist/i18n/locales/de.js +62 -0
  22. package/dist/i18n/locales/en-US.d.ts +51 -0
  23. package/dist/i18n/locales/en-US.d.ts.map +1 -0
  24. package/dist/i18n/locales/en-US.js +62 -0
  25. package/dist/i18n/locales/index.d.ts +9 -0
  26. package/dist/i18n/locales/index.d.ts.map +1 -0
  27. package/dist/i18n/locales/index.js +23 -0
  28. package/dist/i18n/locales/ja.d.ts +51 -0
  29. package/dist/i18n/locales/ja.d.ts.map +1 -0
  30. package/dist/i18n/locales/ja.js +62 -0
  31. package/dist/i18n/locales/zh-CN.d.ts +51 -0
  32. package/dist/i18n/locales/zh-CN.d.ts.map +1 -0
  33. package/dist/i18n/locales/zh-CN.js +62 -0
  34. package/dist/i18n/locales/zh-TW.d.ts +51 -0
  35. package/dist/i18n/locales/zh-TW.d.ts.map +1 -0
  36. package/dist/i18n/locales/zh-TW.js +62 -0
  37. package/dist/i18n/types.d.ts +11 -0
  38. package/dist/i18n/types.d.ts.map +1 -0
  39. package/dist/i18n/types.js +2 -0
  40. package/dist/i18n/useI18n.d.ts +2 -0
  41. package/dist/i18n/useI18n.d.ts.map +1 -0
  42. package/dist/i18n/useI18n.js +41 -0
  43. package/dist/index.d.ts +57 -0
  44. package/dist/index.d.ts.map +1 -0
  45. package/dist/index.js +90 -0
  46. package/dist/services/ActionDialogService/ActionDialog.d.ts +4 -0
  47. package/dist/services/ActionDialogService/ActionDialog.d.ts.map +1 -0
  48. package/dist/services/ActionDialogService/ActionDialog.js +531 -0
  49. package/dist/services/ActionDialogService/index.d.ts +4 -0
  50. package/dist/services/ActionDialogService/index.d.ts.map +1 -0
  51. package/dist/services/ActionDialogService/index.js +8 -0
  52. package/dist/services/ActionDialogService/service.d.ts +7 -0
  53. package/dist/services/ActionDialogService/service.d.ts.map +1 -0
  54. package/dist/services/ActionDialogService/service.js +309 -0
  55. package/dist/services/ActionDialogService/shared.d.ts +17 -0
  56. package/dist/services/ActionDialogService/shared.d.ts.map +1 -0
  57. package/dist/services/ActionDialogService/shared.js +167 -0
  58. package/dist/services/ActionDialogService/types.d.ts +160 -0
  59. package/dist/services/ActionDialogService/types.d.ts.map +1 -0
  60. package/dist/services/ActionDialogService/types.js +2 -0
  61. package/dist/services/CardToastService/index.d.ts +115 -0
  62. package/dist/services/CardToastService/index.d.ts.map +1 -0
  63. package/dist/services/CardToastService/index.js +899 -0
  64. package/dist/services/ImagePreviewService/index.d.ts +179 -0
  65. package/dist/services/ImagePreviewService/index.d.ts.map +1 -0
  66. package/dist/services/ImagePreviewService/index.js +1383 -0
  67. package/dist/services/LoadingService/index.d.ts +146 -0
  68. package/dist/services/LoadingService/index.d.ts.map +1 -0
  69. package/dist/services/LoadingService/index.js +827 -0
  70. package/dist/services/PermissionPurposeDialogService/index.d.ts +99 -0
  71. package/dist/services/PermissionPurposeDialogService/index.d.ts.map +1 -0
  72. package/dist/services/PermissionPurposeDialogService/index.js +360 -0
  73. package/dist/services/PickerService/index.d.ts +128 -0
  74. package/dist/services/PickerService/index.d.ts.map +1 -0
  75. package/dist/services/PickerService/index.js +250 -0
  76. package/dist/theme/ThemeProvider.d.ts +15 -0
  77. package/dist/theme/ThemeProvider.d.ts.map +1 -0
  78. package/dist/theme/ThemeProvider.js +61 -0
  79. package/dist/theme/defaultTheme.d.ts +3 -0
  80. package/dist/theme/defaultTheme.d.ts.map +1 -0
  81. package/dist/theme/defaultTheme.js +28 -0
  82. package/dist/theme/mergeTheme.d.ts +3 -0
  83. package/dist/theme/mergeTheme.d.ts.map +1 -0
  84. package/dist/theme/mergeTheme.js +19 -0
  85. package/dist/theme/types.d.ts +17 -0
  86. package/dist/theme/types.d.ts.map +1 -0
  87. package/dist/theme/types.js +2 -0
  88. package/dist/theme/useTheme.d.ts +2 -0
  89. package/dist/theme/useTheme.d.ts.map +1 -0
  90. package/dist/theme/useTheme.js +46 -0
  91. package/dist/ui/Accordion/index.d.ts +99 -0
  92. package/dist/ui/Accordion/index.d.ts.map +1 -0
  93. package/dist/ui/Accordion/index.js +777 -0
  94. package/dist/ui/AddressCascader/index.d.ts +58 -0
  95. package/dist/ui/AddressCascader/index.d.ts.map +1 -0
  96. package/dist/ui/AddressCascader/index.js +710 -0
  97. package/dist/ui/BetweenTime/index.d.ts +82 -0
  98. package/dist/ui/BetweenTime/index.d.ts.map +1 -0
  99. package/dist/ui/BetweenTime/index.js +702 -0
  100. package/dist/ui/Button/index.d.ts +93 -0
  101. package/dist/ui/Button/index.d.ts.map +1 -0
  102. package/dist/ui/Button/index.js +832 -0
  103. package/dist/ui/Checkbox/index.d.ts +103 -0
  104. package/dist/ui/Checkbox/index.d.ts.map +1 -0
  105. package/dist/ui/Checkbox/index.js +694 -0
  106. package/dist/ui/DatePicker/index.d.ts +5 -0
  107. package/dist/ui/DatePicker/index.d.ts.map +1 -0
  108. package/dist/ui/DatePicker/index.js +157 -0
  109. package/dist/ui/DatePicker/types.d.ts +99 -0
  110. package/dist/ui/DatePicker/types.d.ts.map +1 -0
  111. package/dist/ui/DatePicker/types.js +2 -0
  112. package/dist/ui/DatePicker/utils.d.ts +32 -0
  113. package/dist/ui/DatePicker/utils.d.ts.map +1 -0
  114. package/dist/ui/DatePicker/utils.js +316 -0
  115. package/dist/ui/LinkedScroll/index.d.ts +78 -0
  116. package/dist/ui/LinkedScroll/index.d.ts.map +1 -0
  117. package/dist/ui/LinkedScroll/index.js +470 -0
  118. package/dist/ui/LoadingSpinner/index.d.ts +8 -0
  119. package/dist/ui/LoadingSpinner/index.d.ts.map +1 -0
  120. package/dist/ui/LoadingSpinner/index.js +122 -0
  121. package/dist/ui/Picker/actionBar.d.ts +14 -0
  122. package/dist/ui/Picker/actionBar.d.ts.map +1 -0
  123. package/dist/ui/Picker/actionBar.js +28 -0
  124. package/dist/ui/Picker/index.d.ts +5 -0
  125. package/dist/ui/Picker/index.d.ts.map +1 -0
  126. package/dist/ui/Picker/index.js +412 -0
  127. package/dist/ui/Picker/types.d.ts +80 -0
  128. package/dist/ui/Picker/types.d.ts.map +1 -0
  129. package/dist/ui/Picker/types.js +2 -0
  130. package/dist/ui/Picker/utils.d.ts +27 -0
  131. package/dist/ui/Picker/utils.d.ts.map +1 -0
  132. package/dist/ui/Picker/utils.js +182 -0
  133. package/dist/ui/Radio/index.d.ts +95 -0
  134. package/dist/ui/Radio/index.d.ts.map +1 -0
  135. package/dist/ui/Radio/index.js +633 -0
  136. package/dist/ui/Sheet/index.d.ts +12 -0
  137. package/dist/ui/Sheet/index.d.ts.map +1 -0
  138. package/dist/ui/Sheet/index.js +832 -0
  139. package/dist/ui/Sheet/nativeBottom.d.ts +52 -0
  140. package/dist/ui/Sheet/nativeBottom.d.ts.map +1 -0
  141. package/dist/ui/Sheet/nativeBottom.js +442 -0
  142. package/dist/ui/Sheet/nativeBottom.web.d.ts +18 -0
  143. package/dist/ui/Sheet/nativeBottom.web.d.ts.map +1 -0
  144. package/dist/ui/Sheet/nativeBottom.web.js +46 -0
  145. package/dist/ui/Sheet/slots.d.ts +5 -0
  146. package/dist/ui/Sheet/slots.d.ts.map +1 -0
  147. package/dist/ui/Sheet/slots.js +40 -0
  148. package/dist/ui/Sheet/types.d.ts +131 -0
  149. package/dist/ui/Sheet/types.d.ts.map +1 -0
  150. package/dist/ui/Sheet/types.js +2 -0
  151. package/dist/ui/SliderCaptcha/index.d.ts +57 -0
  152. package/dist/ui/SliderCaptcha/index.d.ts.map +1 -0
  153. package/dist/ui/SliderCaptcha/index.js +586 -0
  154. package/dist/ui/Switch/index.d.ts +72 -0
  155. package/dist/ui/Switch/index.d.ts.map +1 -0
  156. package/dist/ui/Switch/index.js +671 -0
  157. package/dist/ui/Text/index.d.ts +41 -0
  158. package/dist/ui/Text/index.d.ts.map +1 -0
  159. package/dist/ui/Text/index.js +325 -0
  160. package/dist/ui/TextInput/TextInputPrimitive.d.ts +8 -0
  161. package/dist/ui/TextInput/TextInputPrimitive.d.ts.map +1 -0
  162. package/dist/ui/TextInput/TextInputPrimitive.js +54 -0
  163. package/dist/ui/TextInput/index.d.ts +89 -0
  164. package/dist/ui/TextInput/index.d.ts.map +1 -0
  165. package/dist/ui/TextInput/index.js +545 -0
  166. package/dist/ui/WheelColumn/ZKitWheelPickerNativeComponent.d.ts +39 -0
  167. package/dist/ui/WheelColumn/ZKitWheelPickerNativeComponent.d.ts.map +1 -0
  168. package/dist/ui/WheelColumn/ZKitWheelPickerNativeComponent.js +60 -0
  169. package/dist/ui/WheelColumn/constants.d.ts +2 -0
  170. package/dist/ui/WheelColumn/constants.d.ts.map +1 -0
  171. package/dist/ui/WheelColumn/constants.js +4 -0
  172. package/dist/ui/WheelColumn/index.d.ts +59 -0
  173. package/dist/ui/WheelColumn/index.d.ts.map +1 -0
  174. package/dist/ui/WheelColumn/index.js +547 -0
  175. package/ios/ZKitWheelPicker.h +18 -0
  176. package/ios/ZKitWheelPicker.mm +230 -0
  177. package/ios/ZKitWheelPickerLabel.h +10 -0
  178. package/ios/ZKitWheelPickerLabel.mm +31 -0
  179. package/ios/ZKitWheelPickerManager.h +5 -0
  180. package/ios/ZKitWheelPickerManager.mm +53 -0
  181. package/package.json +75 -0
  182. package/react-native.config.js +10 -0
  183. package/src/ComponentLibProvider.tsx +56 -0
  184. package/src/assets/animations/spinner.json +1 -0
  185. package/src/assets/icons/check.svg +4 -0
  186. package/src/assets/icons/toast/error.webp +0 -0
  187. package/src/assets/icons/toast/success.webp +0 -0
  188. package/src/assets/icons/toast/warning.webp +0 -0
  189. package/src/config.ts +65 -0
  190. package/src/i18n/I18nProvider.tsx +69 -0
  191. package/src/i18n/locale.ts +108 -0
  192. package/src/i18n/locales/de.ts +67 -0
  193. package/src/i18n/locales/en-US.ts +67 -0
  194. package/src/i18n/locales/index.ts +19 -0
  195. package/src/i18n/locales/ja.ts +67 -0
  196. package/src/i18n/locales/zh-CN.ts +67 -0
  197. package/src/i18n/locales/zh-TW.ts +67 -0
  198. package/src/i18n/types.ts +21 -0
  199. package/src/i18n/useI18n.ts +7 -0
  200. package/src/index.ts +395 -0
  201. package/src/services/ActionDialogService/ActionDialog.tsx +756 -0
  202. package/src/services/ActionDialogService/README.md +146 -0
  203. package/src/services/ActionDialogService/index.tsx +39 -0
  204. package/src/services/ActionDialogService/service.tsx +355 -0
  205. package/src/services/ActionDialogService/shared.ts +204 -0
  206. package/src/services/ActionDialogService/types.ts +197 -0
  207. package/src/services/CardToastService/README.md +128 -0
  208. package/src/services/CardToastService/index.tsx +1284 -0
  209. package/src/services/ImagePreviewService/README.md +67 -0
  210. package/src/services/ImagePreviewService/index.tsx +2148 -0
  211. package/src/services/LoadingService/README.md +128 -0
  212. package/src/services/LoadingService/index.tsx +1113 -0
  213. package/src/services/PermissionPurposeDialogService/README.md +80 -0
  214. package/src/services/PermissionPurposeDialogService/index.tsx +449 -0
  215. package/src/services/PickerService/README.md +154 -0
  216. package/src/services/PickerService/index.tsx +410 -0
  217. package/src/theme/ThemeProvider.tsx +40 -0
  218. package/src/theme/defaultTheme.ts +30 -0
  219. package/src/theme/mergeTheme.ts +16 -0
  220. package/src/theme/types.ts +36 -0
  221. package/src/theme/useTheme.ts +10 -0
  222. package/src/types/svg.d.ts +9 -0
  223. package/src/types/vant-area-data.d.ts +19 -0
  224. package/src/ui/Accordion/README.md +67 -0
  225. package/src/ui/Accordion/index.tsx +1221 -0
  226. package/src/ui/AddressCascader/README.md +144 -0
  227. package/src/ui/AddressCascader/index.tsx +1084 -0
  228. package/src/ui/BetweenTime/README.md +490 -0
  229. package/src/ui/BetweenTime/index.tsx +1001 -0
  230. package/src/ui/Button/README.md +186 -0
  231. package/src/ui/Button/index.tsx +1356 -0
  232. package/src/ui/Checkbox/README.md +211 -0
  233. package/src/ui/Checkbox/index.tsx +1231 -0
  234. package/src/ui/DatePicker/README.md +117 -0
  235. package/src/ui/DatePicker/index.tsx +295 -0
  236. package/src/ui/DatePicker/types.ts +115 -0
  237. package/src/ui/DatePicker/utils.ts +405 -0
  238. package/src/ui/LinkedScroll/README.md +55 -0
  239. package/src/ui/LinkedScroll/index.tsx +809 -0
  240. package/src/ui/LoadingSpinner/index.tsx +104 -0
  241. package/src/ui/Picker/README.md +190 -0
  242. package/src/ui/Picker/actionBar.tsx +70 -0
  243. package/src/ui/Picker/index.tsx +694 -0
  244. package/src/ui/Picker/types.ts +94 -0
  245. package/src/ui/Picker/utils.ts +239 -0
  246. package/src/ui/Radio/README.md +207 -0
  247. package/src/ui/Radio/index.tsx +1117 -0
  248. package/src/ui/Sheet/README.md +82 -0
  249. package/src/ui/Sheet/index.tsx +1163 -0
  250. package/src/ui/Sheet/nativeBottom.tsx +683 -0
  251. package/src/ui/Sheet/nativeBottom.web.tsx +40 -0
  252. package/src/ui/Sheet/slots.tsx +75 -0
  253. package/src/ui/Sheet/types.ts +195 -0
  254. package/src/ui/SliderCaptcha/README.md +61 -0
  255. package/src/ui/SliderCaptcha/index.tsx +816 -0
  256. package/src/ui/Switch/README.md +143 -0
  257. package/src/ui/Switch/index.tsx +1092 -0
  258. package/src/ui/Text/README.md +157 -0
  259. package/src/ui/Text/index.tsx +450 -0
  260. package/src/ui/TextInput/README.md +197 -0
  261. package/src/ui/TextInput/TextInputPrimitive.tsx +44 -0
  262. package/src/ui/TextInput/index.tsx +942 -0
  263. package/src/ui/WheelColumn/README.md +104 -0
  264. package/src/ui/WheelColumn/ZKitWheelPickerNativeComponent.tsx +68 -0
  265. package/src/ui/WheelColumn/constants.ts +1 -0
  266. package/src/ui/WheelColumn/index.tsx +826 -0
  267. package/zkit-ui.podspec +19 -0
@@ -0,0 +1,1163 @@
1
+ import * as React from 'react';
2
+ import {
3
+ Modal,
4
+ Platform,
5
+ Pressable,
6
+ StyleSheet,
7
+ View,
8
+ useWindowDimensions,
9
+ type ViewStyle,
10
+ } from 'react-native';
11
+ import { Gesture, GestureDetector, GestureHandlerRootView } from 'react-native-gesture-handler';
12
+ import Animated, {
13
+ Easing,
14
+ ReduceMotion,
15
+ useAnimatedStyle,
16
+ useSharedValue,
17
+ withTiming,
18
+ } from 'react-native-reanimated';
19
+ import type { WithTimingConfig } from 'react-native-reanimated';
20
+ import { useSafeAreaInsets } from 'react-native-safe-area-context';
21
+ import { scheduleOnRN } from 'react-native-worklets';
22
+ import { wp } from 'zkit-tools';
23
+ import { useTheme } from '../../theme/useTheme';
24
+ import { NativeBottomSheet, type NativeBottomSheetRef } from './nativeBottom';
25
+ import { SheetContent, SheetFooter, SheetHeader } from './slots';
26
+ import type {
27
+ SheetAnimationConfig,
28
+ SheetBackdropConfig,
29
+ SheetCloseOptions,
30
+ SheetCloseReason,
31
+ SheetDetent,
32
+ SheetHandleConfig,
33
+ SheetOpenChangeReason,
34
+ SheetOpenOptions,
35
+ SheetPlacement,
36
+ SheetProps,
37
+ SheetRef,
38
+ SheetRenderContext,
39
+ SheetSafeAreaEdges,
40
+ SheetSize,
41
+ SheetSlot,
42
+ SheetState,
43
+ } from './types';
44
+
45
+ export { SheetContent, SheetFooter, SheetHeader } from './slots';
46
+ export type {
47
+ SheetAnimationConfig,
48
+ SheetBackdropConfig,
49
+ SheetCloseCompleteDetails,
50
+ SheetCloseOptions,
51
+ SheetCloseReason,
52
+ SheetContentProps,
53
+ SheetDetent,
54
+ SheetDetentChangePayload,
55
+ SheetFooterProps,
56
+ SheetHandleConfig,
57
+ SheetHeaderProps,
58
+ SheetNativeProps,
59
+ SheetOpenChangeDetails,
60
+ SheetOpenChangeReason,
61
+ SheetOpenCompleteDetails,
62
+ SheetOpenOptions,
63
+ SheetOpenReason,
64
+ SheetPlacement,
65
+ SheetProps,
66
+ SheetRef,
67
+ SheetRenderContext,
68
+ SheetSafeAreaEdges,
69
+ SheetSize,
70
+ SheetState,
71
+ } from './types';
72
+
73
+ type ResolvedAnimation = {
74
+ openTiming: WithTimingConfig;
75
+ closeTiming: WithTimingConfig;
76
+ };
77
+
78
+ type ResolvedBackdrop = Required<SheetBackdropConfig>;
79
+
80
+ const DEFAULT_BACKDROP_OPACITY = 0.42;
81
+ const DEFAULT_DISMISSIBLE = true;
82
+ const DEFAULT_DRAGGABLE = true;
83
+ const DEFAULT_HANDLE_VISIBLE = true;
84
+ const DEFAULT_OPEN_DURATION = 260;
85
+ const DEFAULT_CLOSE_DURATION = 210;
86
+ const DEFAULT_EASING = Easing.out(Easing.cubic);
87
+ const DEFAULT_SIDE_SIZE_RATIO = 0.86;
88
+ const DEFAULT_TOP_MAX_RATIO = 0.82;
89
+ const DEFAULT_DRAG_VELOCITY = 900;
90
+ const BOTTOM_HANDLE_RESERVE = wp(36);
91
+ const TOP_HANDLE_RESERVE = wp(20);
92
+ const SIDE_HANDLE_RESERVE = wp(24);
93
+ const SIDE_HANDLE_INSET = wp(4);
94
+ const SIDE_HANDLE_WIDTH = wp(16);
95
+
96
+ function clampNumber(value: number, min: number, max: number) {
97
+ if (!Number.isFinite(value)) return min;
98
+ return Math.max(min, Math.min(max, value));
99
+ }
100
+
101
+ function resolveDuration(duration: number | undefined, fallback: number) {
102
+ if (duration == null || !Number.isFinite(duration)) return fallback;
103
+ return Math.max(0, duration);
104
+ }
105
+
106
+ function resolveAnimation(animation: SheetAnimationConfig | false | undefined): ResolvedAnimation {
107
+ if (animation === false) {
108
+ return {
109
+ openTiming: {
110
+ duration: 0,
111
+ easing: DEFAULT_EASING,
112
+ reduceMotion: ReduceMotion.Always,
113
+ },
114
+ closeTiming: {
115
+ duration: 0,
116
+ easing: DEFAULT_EASING,
117
+ reduceMotion: ReduceMotion.Always,
118
+ },
119
+ };
120
+ }
121
+
122
+ const easing = animation?.easing ?? DEFAULT_EASING;
123
+ const reduceMotion = animation?.reduceMotion ?? ReduceMotion.System;
124
+ const duration = animation?.duration;
125
+
126
+ return {
127
+ openTiming: {
128
+ duration: resolveDuration(animation?.openDuration ?? duration, DEFAULT_OPEN_DURATION),
129
+ easing,
130
+ reduceMotion,
131
+ },
132
+ closeTiming: {
133
+ duration: resolveDuration(animation?.closeDuration ?? duration, DEFAULT_CLOSE_DURATION),
134
+ easing,
135
+ reduceMotion,
136
+ },
137
+ };
138
+ }
139
+
140
+ function resolveBackdrop(backdrop: SheetProps['backdrop']): ResolvedBackdrop {
141
+ if (backdrop === false) {
142
+ return {
143
+ visible: false,
144
+ dismissOnPress: false,
145
+ color: '#000000',
146
+ opacity: 0,
147
+ };
148
+ }
149
+
150
+ if (backdrop && typeof backdrop === 'object') {
151
+ return {
152
+ visible: backdrop.visible !== false,
153
+ dismissOnPress: backdrop.dismissOnPress !== false,
154
+ color: backdrop.color ?? '#000000',
155
+ opacity: backdrop.opacity ?? DEFAULT_BACKDROP_OPACITY,
156
+ };
157
+ }
158
+
159
+ return {
160
+ visible: true,
161
+ dismissOnPress: true,
162
+ color: '#000000',
163
+ opacity: DEFAULT_BACKDROP_OPACITY,
164
+ };
165
+ }
166
+
167
+ function renderSlot(slot: SheetSlot | undefined, context: SheetRenderContext) {
168
+ if (typeof slot === 'function') {
169
+ return (slot as (context: SheetRenderContext) => React.ReactNode)(context);
170
+ }
171
+ return slot;
172
+ }
173
+
174
+ function normalizeDetentIndex(index: number | undefined, length: number) {
175
+ if (!Number.isFinite(index)) return 0;
176
+ return clampNumber(Math.round(index ?? 0), 0, Math.max(0, length - 1));
177
+ }
178
+
179
+ function detentsFromSize(size: SheetSize | undefined) {
180
+ if (size == null || size === 'auto' || typeof size === 'number') return undefined;
181
+ if (size === 'sm') return ['content'] as const;
182
+ if (size === 'md') return ['medium'] as const;
183
+ if (size === 'lg') return ['large'] as const;
184
+ if (size === 'full') return ['full'] as const;
185
+ return [size] as const;
186
+ }
187
+
188
+ function parsePercent(input: string) {
189
+ if (!input.endsWith('%')) return undefined;
190
+ const percent = Number.parseFloat(input.slice(0, -1));
191
+ if (!Number.isFinite(percent) || percent <= 0) return undefined;
192
+ return clampNumber(percent / 100, 0.1, 1);
193
+ }
194
+
195
+ function resolveAxisSize(
196
+ size: SheetSize | undefined,
197
+ axisLength: number,
198
+ fallback: number,
199
+ maxSize: number | undefined
200
+ ) {
201
+ const resolvedMax = Math.max(wp(64), maxSize ?? axisLength);
202
+ if (typeof size === 'number') return clampNumber(size, wp(64), resolvedMax);
203
+ if (typeof size === 'string') {
204
+ const percent = parsePercent(size);
205
+ if (percent != null) return clampNumber(axisLength * percent, wp(64), resolvedMax);
206
+ if (size === 'sm') return clampNumber(wp(280), wp(64), resolvedMax);
207
+ if (size === 'md') return clampNumber(wp(320), wp(64), resolvedMax);
208
+ if (size === 'lg') return clampNumber(wp(380), wp(64), resolvedMax);
209
+ if (size === 'full') return resolvedMax;
210
+ }
211
+ return clampNumber(fallback, wp(64), resolvedMax);
212
+ }
213
+
214
+ function resolveTopHeight(
215
+ size: SheetSize | undefined,
216
+ windowHeight: number,
217
+ maxHeight: number | undefined
218
+ ) {
219
+ if (size == null || size === 'auto') return undefined;
220
+ return resolveAxisSize(size, windowHeight, windowHeight * DEFAULT_TOP_MAX_RATIO, maxHeight);
221
+ }
222
+
223
+ function resolveBottomHeight(
224
+ detents: readonly SheetDetent[] | undefined,
225
+ detentIndex: number,
226
+ windowHeight: number,
227
+ maxHeight: number | undefined
228
+ ) {
229
+ const detent = detents?.[normalizeDetentIndex(detentIndex, detents.length)] ?? 'auto';
230
+ if (detent === 'content' || detent === 'auto') return undefined;
231
+
232
+ const resolvedMax = Math.max(wp(64), maxHeight ?? windowHeight);
233
+ if (detent === 'medium') return clampNumber(windowHeight * 0.5, wp(64), resolvedMax);
234
+ if (detent === 'large') return clampNumber(windowHeight * 0.9, wp(64), resolvedMax);
235
+ if (detent === 'full') return resolvedMax;
236
+
237
+ if (typeof detent === 'string') {
238
+ const percent = parsePercent(detent);
239
+ if (percent != null) return clampNumber(windowHeight * percent, wp(64), resolvedMax);
240
+ }
241
+
242
+ if (typeof detent === 'number' && Number.isFinite(detent) && detent > 0) {
243
+ return clampNumber(windowHeight * clampNumber(detent, 0.1, 1), wp(64), resolvedMax);
244
+ }
245
+
246
+ return undefined;
247
+ }
248
+
249
+ function getCloseDirection(placement: SheetPlacement) {
250
+ if (placement === 'left' || placement === 'top') return -1;
251
+ return 1;
252
+ }
253
+
254
+ function getClosedOffset(placement: SheetPlacement, width: number, height: number) {
255
+ if (placement === 'left') return -width;
256
+ if (placement === 'right') return width;
257
+ if (placement === 'top') return -height;
258
+ return height;
259
+ }
260
+
261
+ function resolveSafeAreaEdges(safeArea: SheetProps['safeArea'], placement: SheetPlacement): SheetSafeAreaEdges {
262
+ if (safeArea === false) return {};
263
+ if (safeArea && typeof safeArea === 'object') return safeArea;
264
+ if (placement === 'top') return { top: true, left: true, right: true };
265
+ if (placement === 'left') return { top: true, left: true, bottom: true };
266
+ if (placement === 'right') return { top: true, right: true, bottom: true };
267
+ return { left: true, right: true, bottom: true };
268
+ }
269
+
270
+ function toCloseReason(reason: SheetOpenChangeReason): SheetCloseReason {
271
+ return reason === 'api' ? 'api' : reason;
272
+ }
273
+
274
+ function resolveDetentIndexForDetails(placement: SheetPlacement, detentIndex: number) {
275
+ return placement === 'bottom' ? detentIndex : null;
276
+ }
277
+
278
+ function getDocument() {
279
+ return (
280
+ globalThis as unknown as {
281
+ document?: {
282
+ addEventListener: (type: string, listener: (event: { key?: string; preventDefault?: () => void }) => void) => void;
283
+ removeEventListener: (type: string, listener: (event: { key?: string; preventDefault?: () => void }) => void) => void;
284
+ };
285
+ }
286
+ ).document;
287
+ }
288
+
289
+ const SheetRoot = React.forwardRef<SheetRef, SheetProps>(function Sheet(
290
+ {
291
+ placement = 'bottom',
292
+ open: openProp,
293
+ defaultOpen = false,
294
+ onOpenChange,
295
+ onOpenComplete,
296
+ onCloseComplete,
297
+ disabled = false,
298
+ dismissible = DEFAULT_DISMISSIBLE,
299
+ draggable = DEFAULT_DRAGGABLE,
300
+ backdrop,
301
+ title,
302
+ description,
303
+ header,
304
+ footer,
305
+ children,
306
+ size,
307
+ safeArea = true,
308
+ detents,
309
+ detentIndex,
310
+ defaultDetentIndex,
311
+ onDetentChange,
312
+ nativeProps,
313
+ backgroundColor,
314
+ cornerRadius,
315
+ maxHeight,
316
+ maxWidth,
317
+ handle = DEFAULT_HANDLE_VISIBLE,
318
+ animation,
319
+ dragCloseThreshold,
320
+ dragVelocityThreshold = DEFAULT_DRAG_VELOCITY,
321
+ style,
322
+ headerStyle,
323
+ footerStyle,
324
+ testID,
325
+ accessibilityLabel,
326
+ backdropAccessibilityLabel = 'Close sheet',
327
+ },
328
+ ref
329
+ ) {
330
+ const theme = useTheme();
331
+ const insets = useSafeAreaInsets();
332
+ const windowSize = useWindowDimensions();
333
+ const isBottomPlacement = placement === 'bottom';
334
+ const usesNativeBottom = isBottomPlacement && Platform.OS !== 'web';
335
+ const nativeBottomRef = React.useRef<NativeBottomSheetRef>(null);
336
+ const isOpenControlled = openProp !== undefined;
337
+ const [innerOpen, setInnerOpen] = React.useState(defaultOpen);
338
+ const isOpen = isOpenControlled ? !!openProp : innerOpen;
339
+ const [sheetState, setSheetState] = React.useState<SheetState>(isOpen ? 'opening' : 'closed');
340
+ const [shellMounted, setShellMounted] = React.useState(isOpen);
341
+ const [surfaceReady, setSurfaceReady] = React.useState(false);
342
+ const [layoutSize, setLayoutSize] = React.useState({ width: 0, height: 0 });
343
+ const bottomDetents = React.useMemo(
344
+ () => detents ?? detentsFromSize(size),
345
+ [detents, size]
346
+ );
347
+ const bottomDetentCount = bottomDetents?.length ?? 1;
348
+ const initialDetentIndex = normalizeDetentIndex(
349
+ detentIndex ?? defaultDetentIndex,
350
+ bottomDetentCount
351
+ );
352
+ const [customDetentIndex, setCustomDetentIndex] = React.useState(initialDetentIndex);
353
+
354
+ const progress = useSharedValue(0);
355
+ const gestureClosePending = useSharedValue(false);
356
+ const isOpenRef = React.useRef(isOpen);
357
+ const previousOpenRef = React.useRef(isOpen);
358
+ const closeIntentRef = React.useRef(false);
359
+ const activeLifecycleRef = React.useRef(isOpen);
360
+ const stateRef = React.useRef<SheetState>(sheetState);
361
+ const requestedDetentIndexRef = React.useRef(initialDetentIndex);
362
+ const animationRef = React.useRef(resolveAnimation(animation));
363
+ const closeReasonRef = React.useRef<SheetCloseReason>('system');
364
+ const presentAnimatedRef = React.useRef(true);
365
+ const dismissAnimatedRef = React.useRef(true);
366
+ const openAnimationFrameRef = React.useRef<number | null>(null);
367
+ const openAnimationStartedRef = React.useRef(false);
368
+ const controlledGestureResetFrameRef = React.useRef<number | null>(null);
369
+ const backdropConfig = React.useMemo(() => resolveBackdrop(backdrop), [backdrop]);
370
+ const resolvedAnimation = React.useMemo(() => resolveAnimation(animation), [animation]);
371
+
372
+ const cancelOpenAnimationFrame = React.useCallback(() => {
373
+ if (openAnimationFrameRef.current == null) return;
374
+ cancelAnimationFrame(openAnimationFrameRef.current);
375
+ openAnimationFrameRef.current = null;
376
+ }, []);
377
+
378
+ React.useEffect(() => {
379
+ animationRef.current = resolvedAnimation;
380
+ }, [resolvedAnimation]);
381
+
382
+ React.useEffect(() => {
383
+ isOpenRef.current = isOpen;
384
+ }, [isOpen]);
385
+
386
+ React.useEffect(() => {
387
+ stateRef.current = sheetState;
388
+ }, [sheetState]);
389
+
390
+ React.useEffect(() => {
391
+ const wasOpen = previousOpenRef.current;
392
+
393
+ if (wasOpen && !isOpen) {
394
+ if (!closeIntentRef.current) {
395
+ closeReasonRef.current = 'system';
396
+ }
397
+ closeIntentRef.current = false;
398
+ }
399
+
400
+ if (!wasOpen && isOpen) {
401
+ closeIntentRef.current = false;
402
+ }
403
+
404
+ previousOpenRef.current = isOpen;
405
+ }, [isOpen]);
406
+
407
+ React.useEffect(() => {
408
+ if (detentIndex == null) return;
409
+ const nextIndex = normalizeDetentIndex(detentIndex, bottomDetentCount);
410
+ requestedDetentIndexRef.current = nextIndex;
411
+ setCustomDetentIndex(nextIndex);
412
+ }, [bottomDetentCount, detentIndex]);
413
+
414
+ React.useEffect(() => {
415
+ setCustomDetentIndex((currentIndex) => {
416
+ const nextIndex = normalizeDetentIndex(currentIndex, bottomDetentCount);
417
+ requestedDetentIndexRef.current = nextIndex;
418
+ return nextIndex;
419
+ });
420
+ }, [bottomDetentCount]);
421
+
422
+ React.useEffect(
423
+ () => () => {
424
+ cancelOpenAnimationFrame();
425
+
426
+ if (controlledGestureResetFrameRef.current != null) {
427
+ cancelAnimationFrame(controlledGestureResetFrameRef.current);
428
+ controlledGestureResetFrameRef.current = null;
429
+ }
430
+ },
431
+ [cancelOpenAnimationFrame]
432
+ );
433
+
434
+ const emitOpenChange = React.useCallback(
435
+ (nextOpen: boolean, reason: SheetOpenChangeReason, detentIndex?: number) => {
436
+ onOpenChange?.(nextOpen, {
437
+ reason,
438
+ placement,
439
+ detentIndex: resolveDetentIndexForDetails(
440
+ placement,
441
+ normalizeDetentIndex(detentIndex ?? requestedDetentIndexRef.current, bottomDetentCount)
442
+ ),
443
+ });
444
+ },
445
+ [bottomDetentCount, onOpenChange, placement]
446
+ );
447
+
448
+ const requestOpenChange = React.useCallback(
449
+ (nextOpen: boolean, reason: SheetOpenChangeReason, detentIndex?: number) => {
450
+ if (disabled && nextOpen) return;
451
+
452
+ if (detentIndex != null) {
453
+ const nextIndex = normalizeDetentIndex(detentIndex, bottomDetentCount);
454
+ requestedDetentIndexRef.current = nextIndex;
455
+ if (!usesNativeBottom) setCustomDetentIndex(nextIndex);
456
+ }
457
+
458
+ if (nextOpen === isOpenRef.current) return;
459
+
460
+ if (nextOpen) {
461
+ activeLifecycleRef.current = true;
462
+ presentAnimatedRef.current = true;
463
+ } else {
464
+ closeReasonRef.current = toCloseReason(reason);
465
+ closeIntentRef.current = true;
466
+ dismissAnimatedRef.current = true;
467
+ }
468
+
469
+ if (!isOpenControlled) {
470
+ setInnerOpen(nextOpen);
471
+ }
472
+
473
+ emitOpenChange(nextOpen, reason, requestedDetentIndexRef.current);
474
+ },
475
+ [bottomDetentCount, disabled, emitOpenChange, isOpenControlled, usesNativeBottom]
476
+ );
477
+
478
+ const finishClosedLifecycle = React.useCallback(() => {
479
+ if (isOpenRef.current) return;
480
+
481
+ setSheetState('closed');
482
+ progress.value = 0;
483
+ gestureClosePending.value = false;
484
+ presentAnimatedRef.current = true;
485
+ dismissAnimatedRef.current = true;
486
+ openAnimationStartedRef.current = false;
487
+ setSurfaceReady(false);
488
+ setShellMounted(false);
489
+
490
+ if (activeLifecycleRef.current) {
491
+ activeLifecycleRef.current = false;
492
+ onCloseComplete?.({
493
+ reason: closeReasonRef.current,
494
+ placement,
495
+ detentIndex: resolveDetentIndexForDetails(placement, requestedDetentIndexRef.current),
496
+ });
497
+ }
498
+ }, [gestureClosePending, onCloseComplete, placement, progress]);
499
+
500
+ const finishOpenLifecycle = React.useCallback(() => {
501
+ if (!isOpenRef.current) return;
502
+
503
+ setSheetState('open');
504
+ gestureClosePending.value = false;
505
+ onOpenComplete?.({
506
+ placement,
507
+ detentIndex: resolveDetentIndexForDetails(placement, requestedDetentIndexRef.current),
508
+ });
509
+ }, [gestureClosePending, onOpenComplete, placement]);
510
+
511
+ const openSheet = React.useCallback(
512
+ async (options?: SheetOpenOptions) => {
513
+ const targetIndex = normalizeDetentIndex(
514
+ options?.detentIndex ?? requestedDetentIndexRef.current,
515
+ bottomDetentCount
516
+ );
517
+ requestedDetentIndexRef.current = targetIndex;
518
+ presentAnimatedRef.current = options?.animated !== false;
519
+
520
+ if (usesNativeBottom) {
521
+ const bottomRef = nativeBottomRef.current;
522
+ if (bottomRef) {
523
+ await bottomRef.open({ detentIndex: targetIndex, animated: options?.animated });
524
+ return;
525
+ }
526
+ }
527
+
528
+ setCustomDetentIndex(targetIndex);
529
+ requestOpenChange(true, 'api', targetIndex);
530
+ },
531
+ [bottomDetentCount, requestOpenChange, usesNativeBottom]
532
+ );
533
+
534
+ const closeSheet = React.useCallback(
535
+ async (options?: SheetCloseOptions) => {
536
+ dismissAnimatedRef.current = options?.animated !== false;
537
+
538
+ if (usesNativeBottom) {
539
+ const bottomRef = nativeBottomRef.current;
540
+ if (bottomRef) {
541
+ await bottomRef.close({ animated: options?.animated });
542
+ return;
543
+ }
544
+ }
545
+
546
+ requestOpenChange(false, 'api', requestedDetentIndexRef.current);
547
+ },
548
+ [requestOpenChange, usesNativeBottom]
549
+ );
550
+
551
+ const snapTo = React.useCallback(
552
+ async (detentIndex: number) => {
553
+ const targetIndex = normalizeDetentIndex(detentIndex, bottomDetentCount);
554
+ requestedDetentIndexRef.current = targetIndex;
555
+
556
+ setCustomDetentIndex(targetIndex);
557
+
558
+ if (usesNativeBottom) {
559
+ await nativeBottomRef.current?.snapTo(targetIndex);
560
+ }
561
+ },
562
+ [bottomDetentCount, usesNativeBottom]
563
+ );
564
+
565
+ React.useImperativeHandle(
566
+ ref,
567
+ () => ({
568
+ open: openSheet,
569
+ close: closeSheet,
570
+ snapTo,
571
+ getState: () => (usesNativeBottom ? nativeBottomRef.current?.getState() ?? stateRef.current : stateRef.current),
572
+ }),
573
+ [closeSheet, openSheet, snapTo, usesNativeBottom]
574
+ );
575
+
576
+ React.useEffect(() => {
577
+ if (usesNativeBottom || !isOpen) return undefined;
578
+
579
+ activeLifecycleRef.current = true;
580
+ openAnimationStartedRef.current = false;
581
+ cancelOpenAnimationFrame();
582
+ progress.value = 0;
583
+ gestureClosePending.value = false;
584
+ setLayoutSize({ width: 0, height: 0 });
585
+ setSurfaceReady(false);
586
+ setShellMounted(true);
587
+ setSheetState('opening');
588
+
589
+ return undefined;
590
+ }, [cancelOpenAnimationFrame, gestureClosePending, isOpen, progress, usesNativeBottom]);
591
+
592
+ React.useEffect(() => {
593
+ if (usesNativeBottom || !isOpen || !shellMounted || !surfaceReady || openAnimationStartedRef.current) {
594
+ return undefined;
595
+ }
596
+
597
+ const frame = requestAnimationFrame(() => {
598
+ openAnimationFrameRef.current = null;
599
+ if (!isOpenRef.current) return;
600
+
601
+ openAnimationStartedRef.current = true;
602
+ progress.value = withTiming(
603
+ 1,
604
+ presentAnimatedRef.current ? animationRef.current.openTiming : { ...animationRef.current.openTiming, duration: 0 },
605
+ (finished) => {
606
+ if (finished) scheduleOnRN(finishOpenLifecycle);
607
+ }
608
+ );
609
+ });
610
+
611
+ openAnimationFrameRef.current = frame;
612
+
613
+ return () => {
614
+ if (openAnimationFrameRef.current !== frame) return;
615
+ cancelAnimationFrame(frame);
616
+ openAnimationFrameRef.current = null;
617
+ };
618
+ }, [finishOpenLifecycle, isOpen, progress, shellMounted, surfaceReady, usesNativeBottom]);
619
+
620
+ React.useEffect(() => {
621
+ if (usesNativeBottom || isOpen) return undefined;
622
+ if (!shellMounted || stateRef.current === 'closed') return undefined;
623
+
624
+ cancelOpenAnimationFrame();
625
+ openAnimationStartedRef.current = false;
626
+ setSurfaceReady(false);
627
+ setSheetState('closing');
628
+ progress.value = withTiming(
629
+ 0,
630
+ dismissAnimatedRef.current ? animationRef.current.closeTiming : { ...animationRef.current.closeTiming, duration: 0 },
631
+ (finished) => {
632
+ if (finished) scheduleOnRN(finishClosedLifecycle);
633
+ }
634
+ );
635
+
636
+ return undefined;
637
+ }, [cancelOpenAnimationFrame, finishClosedLifecycle, isOpen, progress, shellMounted, usesNativeBottom]);
638
+
639
+ const requestBackdropClose = React.useCallback(() => {
640
+ if (disabled || !dismissible || !backdropConfig.dismissOnPress || !isOpenRef.current) return;
641
+ requestOpenChange(false, 'backdrop', requestedDetentIndexRef.current);
642
+ }, [backdropConfig.dismissOnPress, disabled, dismissible, requestOpenChange]);
643
+
644
+ const requestBackClose = React.useCallback(() => {
645
+ if (disabled || !dismissible || !isOpenRef.current) return;
646
+ requestOpenChange(false, 'back', requestedDetentIndexRef.current);
647
+ }, [disabled, dismissible, requestOpenChange]);
648
+
649
+ const requestGestureClose = React.useCallback(() => {
650
+ if (disabled || !dismissible || !draggable || !isOpenRef.current) return;
651
+ requestOpenChange(false, 'gesture', requestedDetentIndexRef.current);
652
+
653
+ if (!isOpenControlled) return;
654
+
655
+ if (controlledGestureResetFrameRef.current != null) {
656
+ cancelAnimationFrame(controlledGestureResetFrameRef.current);
657
+ }
658
+
659
+ controlledGestureResetFrameRef.current = requestAnimationFrame(() => {
660
+ controlledGestureResetFrameRef.current = null;
661
+ if (!isOpenRef.current) return;
662
+
663
+ gestureClosePending.value = false;
664
+ progress.value = withTiming(1, animationRef.current.openTiming);
665
+ });
666
+ }, [disabled, dismissible, draggable, gestureClosePending, isOpenControlled, progress, requestOpenChange]);
667
+
668
+ React.useEffect(() => {
669
+ if (usesNativeBottom || Platform.OS !== 'web' || !isOpen || disabled || !dismissible) return undefined;
670
+
671
+ const documentRef = getDocument();
672
+ if (!documentRef) return undefined;
673
+
674
+ const handleKeyDown = (event: { key?: string; preventDefault?: () => void }) => {
675
+ if (event.key !== 'Escape') return;
676
+ event.preventDefault?.();
677
+ requestBackClose();
678
+ };
679
+
680
+ documentRef.addEventListener('keydown', handleKeyDown);
681
+ return () => documentRef.removeEventListener('keydown', handleKeyDown);
682
+ }, [disabled, dismissible, isOpen, requestBackClose, usesNativeBottom]);
683
+
684
+ const context = React.useMemo<SheetRenderContext>(
685
+ () => ({
686
+ isOpen,
687
+ state: sheetState,
688
+ placement,
689
+ detentIndex: resolveDetentIndexForDetails(placement, customDetentIndex),
690
+ open: openSheet,
691
+ close: closeSheet,
692
+ snapTo,
693
+ }),
694
+ [closeSheet, customDetentIndex, isOpen, openSheet, placement, sheetState, snapTo]
695
+ );
696
+
697
+ const handleBottomOpenChange = React.useCallback(
698
+ (nextOpen: boolean, meta: { reason: SheetOpenChangeReason; detentIndex: number }) => {
699
+ const reason = meta.reason;
700
+ requestedDetentIndexRef.current = normalizeDetentIndex(meta.detentIndex, bottomDetentCount);
701
+
702
+ if (!nextOpen) {
703
+ closeReasonRef.current = toCloseReason(reason);
704
+ closeIntentRef.current = true;
705
+ } else {
706
+ activeLifecycleRef.current = true;
707
+ }
708
+
709
+ if (!isOpenControlled) {
710
+ setInnerOpen(nextOpen);
711
+ }
712
+
713
+ emitOpenChange(nextOpen, reason, requestedDetentIndexRef.current);
714
+ },
715
+ [bottomDetentCount, emitOpenChange, isOpenControlled]
716
+ );
717
+
718
+ const horizontal = placement === 'left' || placement === 'right';
719
+ const fallbackWidth = resolveAxisSize(
720
+ size,
721
+ windowSize.width,
722
+ Math.min(wp(360), windowSize.width * DEFAULT_SIDE_SIZE_RATIO),
723
+ maxWidth
724
+ );
725
+ const resolvedHeight = isBottomPlacement
726
+ ? resolveBottomHeight(bottomDetents, customDetentIndex, windowSize.height, maxHeight)
727
+ : resolveTopHeight(size, windowSize.height, maxHeight);
728
+ const fallbackHeight = resolvedHeight ??
729
+ (isBottomPlacement
730
+ ? Math.min(windowSize.height, maxHeight ?? windowSize.height)
731
+ : Math.min(windowSize.height * DEFAULT_TOP_MAX_RATIO, maxHeight ?? windowSize.height));
732
+ const animatedWidth = horizontal ? fallbackWidth : windowSize.width;
733
+ const animatedHeight = horizontal ? windowSize.height : layoutSize.height || fallbackHeight;
734
+ const closedOffset = getClosedOffset(placement, animatedWidth, animatedHeight);
735
+ const closedDistance = Math.abs(closedOffset);
736
+ const closeDirection = getCloseDirection(placement);
737
+ const closeThreshold = dragCloseThreshold ?? Math.min(closedDistance * 0.36, wp(156));
738
+ const safeAreaEdges = resolveSafeAreaEdges(safeArea, placement);
739
+ const handleConfig: SheetHandleConfig = handle && typeof handle === 'object' ? handle : {};
740
+ const handleVisible = handle !== false;
741
+ const handleCanDrag = handleVisible && draggable && dismissible && !disabled;
742
+ const usesBottomInlineHandle = handleVisible && isBottomPlacement;
743
+ const usesTopInlineHandle = handleVisible && placement === 'top';
744
+ const usesOverlayHandle = handleVisible && horizontal;
745
+ const topHandleReserve = usesTopInlineHandle ? TOP_HANDLE_RESERVE : 0;
746
+ const dragResetTiming = resolvedAnimation.openTiming;
747
+ const dragCloseTiming = resolvedAnimation.closeTiming;
748
+ const surfaceColor = backgroundColor ?? theme.colors.surface;
749
+ const resolvedCornerRadius = cornerRadius ?? wp(22);
750
+ const topMaxHeight = Math.min(
751
+ windowSize.height,
752
+ (maxHeight ?? windowSize.height * DEFAULT_TOP_MAX_RATIO) + topHandleReserve
753
+ );
754
+ const surfaceLayoutStyle: ViewStyle = horizontal
755
+ ? {
756
+ bottom: 0,
757
+ top: 0,
758
+ width: fallbackWidth,
759
+ maxWidth,
760
+ [placement]: -animatedWidth,
761
+ }
762
+ : isBottomPlacement
763
+ ? {
764
+ bottom: -animatedHeight,
765
+ left: 0,
766
+ right: 0,
767
+ height: resolvedHeight,
768
+ maxHeight: maxHeight ?? windowSize.height,
769
+ }
770
+ : {
771
+ left: 0,
772
+ right: 0,
773
+ top: -animatedHeight,
774
+ height: resolvedHeight,
775
+ maxHeight: topMaxHeight,
776
+ };
777
+ const surfaceInsetStyle: ViewStyle = {
778
+ paddingTop:
779
+ (safeAreaEdges.top ? insets.top : 0) ||
780
+ undefined,
781
+ paddingRight:
782
+ (safeAreaEdges.right ? insets.right : 0) +
783
+ (placement === 'left' && usesOverlayHandle ? SIDE_HANDLE_RESERVE : 0) ||
784
+ undefined,
785
+ paddingBottom:
786
+ (safeAreaEdges.bottom ? insets.bottom : 0) ||
787
+ undefined,
788
+ paddingLeft:
789
+ (safeAreaEdges.left ? insets.left : 0) +
790
+ (placement === 'right' && usesOverlayHandle ? SIDE_HANDLE_RESERVE : 0) ||
791
+ undefined,
792
+ };
793
+ const cornerStyle: ViewStyle =
794
+ placement === 'left'
795
+ ? {
796
+ borderTopRightRadius: resolvedCornerRadius,
797
+ borderBottomRightRadius: resolvedCornerRadius,
798
+ }
799
+ : placement === 'right'
800
+ ? {
801
+ borderTopLeftRadius: resolvedCornerRadius,
802
+ borderBottomLeftRadius: resolvedCornerRadius,
803
+ }
804
+ : placement === 'bottom'
805
+ ? {
806
+ borderTopLeftRadius: resolvedCornerRadius,
807
+ borderTopRightRadius: resolvedCornerRadius,
808
+ }
809
+ : {
810
+ borderBottomLeftRadius: resolvedCornerRadius,
811
+ borderBottomRightRadius: resolvedCornerRadius,
812
+ };
813
+
814
+ const animatedSurfaceStyle = useAnimatedStyle(() => {
815
+ const translate = -closedOffset * progress.value;
816
+ if (horizontal) return { transform: [{ translateX: translate }] };
817
+ return { transform: [{ translateY: translate }] };
818
+ }, [closedOffset, horizontal]);
819
+
820
+ const animatedBackdropStyle = useAnimatedStyle(() => ({
821
+ opacity: backdropConfig.opacity * progress.value,
822
+ }), [backdropConfig.opacity]);
823
+
824
+ const panGesture = React.useMemo(() => {
825
+ if (!handleCanDrag) return Gesture.Pan().enabled(false);
826
+
827
+ const baseGesture = Gesture.Pan().enabled(handleCanDrag);
828
+ const directionalGesture = horizontal
829
+ ? baseGesture.activeOffsetX([-wp(8), wp(8)])
830
+ : baseGesture.activeOffsetY([-wp(8), wp(8)]);
831
+
832
+ return directionalGesture
833
+ .onBegin(() => {
834
+ 'worklet';
835
+ gestureClosePending.value = false;
836
+ })
837
+ .onUpdate((event) => {
838
+ 'worklet';
839
+ const rawDrag =
840
+ placement === 'left'
841
+ ? Math.min(event.translationX, 0)
842
+ : placement === 'right'
843
+ ? Math.max(event.translationX, 0)
844
+ : placement === 'top'
845
+ ? Math.min(event.translationY, 0)
846
+ : Math.max(event.translationY, 0);
847
+ const boundedDrag =
848
+ closeDirection > 0
849
+ ? Math.min(rawDrag, closedDistance)
850
+ : Math.max(rawDrag, -closedDistance);
851
+ progress.value = closedDistance <= 0 ? 1 : 1 - Math.abs(boundedDrag) / closedDistance;
852
+ })
853
+ .onEnd((event) => {
854
+ 'worklet';
855
+ const velocity = horizontal ? event.velocityX : event.velocityY;
856
+ const distance = closedDistance * (1 - progress.value);
857
+ const shouldClose =
858
+ distance >= closeThreshold ||
859
+ velocity * closeDirection >= dragVelocityThreshold;
860
+
861
+ if (shouldClose) {
862
+ gestureClosePending.value = true;
863
+ progress.value = withTiming(0, dragCloseTiming);
864
+ scheduleOnRN(requestGestureClose);
865
+ }
866
+ })
867
+ .onFinalize(() => {
868
+ 'worklet';
869
+ if (gestureClosePending.value) return;
870
+ progress.value = withTiming(1, dragResetTiming);
871
+ });
872
+ }, [
873
+ closeDirection,
874
+ closedDistance,
875
+ closeThreshold,
876
+ dragCloseTiming,
877
+ dragResetTiming,
878
+ dragVelocityThreshold,
879
+ gestureClosePending,
880
+ handleCanDrag,
881
+ horizontal,
882
+ placement,
883
+ progress,
884
+ requestGestureClose,
885
+ ]);
886
+
887
+ const handleIndicator = handleVisible ? (
888
+ <View
889
+ style={[
890
+ horizontal ? styles.verticalHandle : styles.horizontalHandle,
891
+ {
892
+ backgroundColor: handleConfig.color ?? theme.colors.border,
893
+ borderRadius: handleConfig.radius ?? wp(2),
894
+ height: horizontal ? handleConfig.width ?? wp(36) : handleConfig.height ?? wp(4),
895
+ width: horizontal ? handleConfig.height ?? wp(4) : handleConfig.width ?? wp(36),
896
+ },
897
+ ]}
898
+ />
899
+ ) : null;
900
+
901
+ const inlineHandle = usesBottomInlineHandle ? (
902
+ <GestureDetector gesture={panGesture}>
903
+ <View
904
+ accessibilityRole={handleCanDrag ? 'adjustable' : undefined}
905
+ style={styles.inlineHandleHost}
906
+ >
907
+ {handleIndicator}
908
+ </View>
909
+ </GestureDetector>
910
+ ) : null;
911
+
912
+ const topInlineHandle = usesTopInlineHandle ? (
913
+ <GestureDetector gesture={panGesture}>
914
+ <View
915
+ accessibilityRole={handleCanDrag ? 'adjustable' : undefined}
916
+ style={styles.topHandleHost}
917
+ >
918
+ {handleIndicator}
919
+ </View>
920
+ </GestureDetector>
921
+ ) : null;
922
+
923
+ const overlayHandle = usesOverlayHandle ? (
924
+ <GestureDetector gesture={panGesture}>
925
+ <View
926
+ accessibilityRole={handleCanDrag ? 'adjustable' : undefined}
927
+ style={[
928
+ styles.handleHost,
929
+ placement === 'left' ? styles.handleHostRight : styles.handleHostLeft,
930
+ ]}
931
+ >
932
+ {handleIndicator}
933
+ </View>
934
+ </GestureDetector>
935
+ ) : null;
936
+
937
+ if (usesNativeBottom) {
938
+ return (
939
+ <NativeBottomSheet
940
+ ref={nativeBottomRef}
941
+ open={isOpen}
942
+ onOpenChange={handleBottomOpenChange}
943
+ onOpenComplete={(payload) => {
944
+ requestedDetentIndexRef.current = payload.index;
945
+ onOpenComplete?.({
946
+ placement: 'bottom',
947
+ detentIndex: payload.index,
948
+ });
949
+ }}
950
+ onCloseComplete={(details) => {
951
+ closeReasonRef.current = details.reason;
952
+ requestedDetentIndexRef.current = details.detentIndex;
953
+ onCloseComplete?.({
954
+ reason: details.reason,
955
+ placement: 'bottom',
956
+ detentIndex: details.detentIndex,
957
+ });
958
+ }}
959
+ detents={bottomDetents}
960
+ detentIndex={detentIndex}
961
+ defaultDetentIndex={defaultDetentIndex}
962
+ onDetentChange={(index, payload) => {
963
+ requestedDetentIndexRef.current = index;
964
+ onDetentChange?.(index, payload);
965
+ }}
966
+ disabled={disabled}
967
+ dismissible={dismissible}
968
+ draggable={draggable}
969
+ backdrop={backdrop}
970
+ title={title}
971
+ description={description}
972
+ header={
973
+ header === undefined
974
+ ? undefined
975
+ : (bottomContext) => renderSlot(header, bottomContext)
976
+ }
977
+ footer={
978
+ footer === undefined
979
+ ? undefined
980
+ : (bottomContext) => renderSlot(footer, bottomContext)
981
+ }
982
+ backgroundColor={backgroundColor}
983
+ cornerRadius={cornerRadius}
984
+ maxHeight={maxHeight}
985
+ maxWidth={maxWidth}
986
+ handle={handle}
987
+ style={style}
988
+ headerStyle={headerStyle}
989
+ footerStyle={footerStyle}
990
+ testID={testID}
991
+ accessibilityLabel={accessibilityLabel}
992
+ backdropAccessibilityLabel={backdropAccessibilityLabel}
993
+ nativeProps={nativeProps}
994
+ >
995
+ {children === undefined
996
+ ? undefined
997
+ : (bottomContext) => renderSlot(children, bottomContext)}
998
+ </NativeBottomSheet>
999
+ );
1000
+ }
1001
+
1002
+ if (!shellMounted) return null;
1003
+
1004
+ const resolvedHeader =
1005
+ header !== undefined || title != null || description != null ? (
1006
+ header !== undefined ? (
1007
+ renderSlot(header, context)
1008
+ ) : (
1009
+ <SheetHeader title={title} description={description} style={headerStyle} />
1010
+ )
1011
+ ) : null;
1012
+ const resolvedFooter = footer !== undefined ? renderSlot(footer, context) : null;
1013
+ const resolvedChildren = renderSlot(children, context);
1014
+
1015
+ return (
1016
+ <Modal
1017
+ visible
1018
+ transparent
1019
+ animationType="none"
1020
+ statusBarTranslucent
1021
+ presentationStyle="overFullScreen"
1022
+ onRequestClose={requestBackClose}
1023
+ >
1024
+ <GestureHandlerRootView style={styles.modalRoot}>
1025
+ <View style={styles.modalRoot} pointerEvents="box-none">
1026
+ {backdropConfig.visible ? (
1027
+ <Pressable
1028
+ accessibilityRole="button"
1029
+ accessibilityLabel={backdropAccessibilityLabel}
1030
+ disabled={disabled || !dismissible || !backdropConfig.dismissOnPress}
1031
+ onPress={requestBackdropClose}
1032
+ style={StyleSheet.absoluteFill}
1033
+ >
1034
+ <Animated.View
1035
+ pointerEvents="none"
1036
+ style={[
1037
+ StyleSheet.absoluteFill,
1038
+ { backgroundColor: backdropConfig.color },
1039
+ styles.closedBackdrop,
1040
+ animatedBackdropStyle,
1041
+ ]}
1042
+ />
1043
+ </Pressable>
1044
+ ) : null}
1045
+
1046
+ <Animated.View
1047
+ testID={testID}
1048
+ accessibilityLabel={accessibilityLabel}
1049
+ accessibilityViewIsModal
1050
+ importantForAccessibility="yes"
1051
+ onLayout={(event) => {
1052
+ const { width, height } = event.nativeEvent.layout;
1053
+ const sizeChanged = width !== layoutSize.width || height !== layoutSize.height;
1054
+ if (sizeChanged) setLayoutSize({ width, height });
1055
+ if (!surfaceReady) setSurfaceReady(true);
1056
+ }}
1057
+ style={[
1058
+ styles.surface,
1059
+ surfaceLayoutStyle,
1060
+ cornerStyle,
1061
+ surfaceInsetStyle,
1062
+ { backgroundColor: surfaceColor, borderColor: theme.colors.border },
1063
+ style,
1064
+ animatedSurfaceStyle,
1065
+ ]}
1066
+ >
1067
+ {usesTopInlineHandle ? (
1068
+ <>
1069
+ <View style={styles.topContentHost}>
1070
+ {resolvedHeader}
1071
+ {resolvedChildren}
1072
+ {resolvedFooter != null ? <View style={footerStyle}>{resolvedFooter}</View> : null}
1073
+ </View>
1074
+ {topInlineHandle}
1075
+ </>
1076
+ ) : (
1077
+ <>
1078
+ {inlineHandle}
1079
+ {overlayHandle}
1080
+ {resolvedHeader}
1081
+ {resolvedChildren}
1082
+ {resolvedFooter != null ? <View style={footerStyle}>{resolvedFooter}</View> : null}
1083
+ </>
1084
+ )}
1085
+ </Animated.View>
1086
+ </View>
1087
+ </GestureHandlerRootView>
1088
+ </Modal>
1089
+ );
1090
+ });
1091
+
1092
+ type SheetComponent = React.ForwardRefExoticComponent<
1093
+ SheetProps & React.RefAttributes<SheetRef>
1094
+ > & {
1095
+ Header: typeof SheetHeader;
1096
+ Content: typeof SheetContent;
1097
+ Footer: typeof SheetFooter;
1098
+ };
1099
+
1100
+ export const Sheet = Object.assign(SheetRoot, {
1101
+ Header: SheetHeader,
1102
+ Content: SheetContent,
1103
+ Footer: SheetFooter,
1104
+ }) as SheetComponent;
1105
+
1106
+ const styles = StyleSheet.create({
1107
+ modalRoot: {
1108
+ ...StyleSheet.absoluteFillObject,
1109
+ },
1110
+ surface: {
1111
+ borderWidth: StyleSheet.hairlineWidth,
1112
+ overflow: 'hidden',
1113
+ position: 'absolute',
1114
+ },
1115
+ closedBackdrop: {
1116
+ opacity: 0,
1117
+ },
1118
+ handleHost: {
1119
+ alignItems: 'center',
1120
+ justifyContent: 'center',
1121
+ position: 'absolute',
1122
+ zIndex: 2,
1123
+ },
1124
+ handleHostLeft: {
1125
+ bottom: 0,
1126
+ left: SIDE_HANDLE_INSET,
1127
+ top: 0,
1128
+ width: SIDE_HANDLE_WIDTH,
1129
+ },
1130
+ handleHostRight: {
1131
+ bottom: 0,
1132
+ right: SIDE_HANDLE_INSET,
1133
+ top: 0,
1134
+ width: SIDE_HANDLE_WIDTH,
1135
+ },
1136
+ inlineHandleHost: {
1137
+ alignItems: 'center',
1138
+ flexShrink: 0,
1139
+ height: BOTTOM_HANDLE_RESERVE,
1140
+ justifyContent: 'center',
1141
+ },
1142
+ topContentHost: {
1143
+ flexShrink: 1,
1144
+ marginBottom: TOP_HANDLE_RESERVE,
1145
+ overflow: 'hidden',
1146
+ },
1147
+ topHandleHost: {
1148
+ alignItems: 'center',
1149
+ bottom: 0,
1150
+ height: TOP_HANDLE_RESERVE,
1151
+ justifyContent: 'center',
1152
+ left: 0,
1153
+ position: 'absolute',
1154
+ right: 0,
1155
+ zIndex: 2,
1156
+ },
1157
+ horizontalHandle: {
1158
+ flexShrink: 0,
1159
+ },
1160
+ verticalHandle: {
1161
+ flexShrink: 0,
1162
+ },
1163
+ });