@btndprojects/loam-ui-native 0.0.0-stage → 0.1.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 (269) hide show
  1. package/LICENSE +21 -0
  2. package/dist/accordion/index.js +3 -0
  3. package/dist/adaptive/index.js +2 -0
  4. package/dist/alert/index.js +3 -0
  5. package/dist/analyzer/index.js +6 -0
  6. package/dist/announcement/index.js +6 -0
  7. package/dist/audio-player/index.js +3 -0
  8. package/dist/avatar/index.js +3 -0
  9. package/dist/avatar-group/index.js +3 -0
  10. package/dist/badge/index.js +5 -0
  11. package/dist/banner/index.js +2 -0
  12. package/dist/board/index.js +7 -0
  13. package/dist/button/index.js +5 -0
  14. package/dist/camera-permission/index.js +13 -0
  15. package/dist/capture-result/index.js +18 -0
  16. package/dist/card/index.js +4 -0
  17. package/dist/card-form/index.js +10 -0
  18. package/dist/carousel/index.js +2 -0
  19. package/dist/chart/index.js +6 -0
  20. package/dist/charts/index.js +7 -0
  21. package/dist/checkbox/index.js +7 -0
  22. package/dist/checklist/index.js +10 -0
  23. package/dist/checkout/index.js +13 -0
  24. package/dist/chip/index.js +7 -0
  25. package/dist/chunk-223Q2T2Q.js +229 -0
  26. package/dist/chunk-2A5NIQAK.js +254 -0
  27. package/dist/chunk-2GK6PHGE.js +363 -0
  28. package/dist/chunk-2WUTXHMU.js +1 -0
  29. package/dist/chunk-2ZZ7TDJ4.js +324 -0
  30. package/dist/chunk-35OFI3JG.js +499 -0
  31. package/dist/chunk-3ARE4TES.js +195 -0
  32. package/dist/chunk-3ECPOSKO.js +1 -0
  33. package/dist/chunk-3ENTMPQY.js +229 -0
  34. package/dist/chunk-3HJH6FF4.js +98 -0
  35. package/dist/chunk-3NMD74ZU.js +4 -0
  36. package/dist/chunk-3WPW4KUN.js +182 -0
  37. package/dist/chunk-47FWHGUN.js +394 -0
  38. package/dist/chunk-4BXMXBJO.js +141 -0
  39. package/dist/chunk-4DS5ZWVC.js +83 -0
  40. package/dist/chunk-4GBOGOTV.js +295 -0
  41. package/dist/chunk-4SZYY6AN.js +99 -0
  42. package/dist/chunk-4YIP3CWS.js +237 -0
  43. package/dist/chunk-4YQOR7JM.js +204 -0
  44. package/dist/chunk-4ZAHCY44.js +177 -0
  45. package/dist/chunk-52X2VYTD.js +228 -0
  46. package/dist/chunk-5P26LBDL.js +1 -0
  47. package/dist/chunk-5XLRYDAX.js +1 -0
  48. package/dist/chunk-5YNTNN5A.js +177 -0
  49. package/dist/chunk-66SG2H23.js +129 -0
  50. package/dist/chunk-6HT2LP62.js +504 -0
  51. package/dist/chunk-6N33LFQA.js +35 -0
  52. package/dist/chunk-6X6PQ443.js +432 -0
  53. package/dist/chunk-7CFF4HXS.js +126 -0
  54. package/dist/chunk-7O2FMUPT.js +755 -0
  55. package/dist/chunk-AI4DWZCD.js +162 -0
  56. package/dist/chunk-AWJOCGFZ.js +213 -0
  57. package/dist/chunk-AZRX6SAC.js +285 -0
  58. package/dist/chunk-B54QE2FN.js +1 -0
  59. package/dist/chunk-BC475SJH.js +1 -0
  60. package/dist/chunk-BEDBFJND.js +202 -0
  61. package/dist/chunk-BTTFCMKG.js +166 -0
  62. package/dist/chunk-BV3B2HYZ.js +1635 -0
  63. package/dist/chunk-C2NTXVWM.js +98 -0
  64. package/dist/chunk-CDRZHT42.js +424 -0
  65. package/dist/chunk-CJ5EOYZE.js +62 -0
  66. package/dist/chunk-CUMUNPXY.js +128 -0
  67. package/dist/chunk-CUS3ZSCU.js +1 -0
  68. package/dist/chunk-CYRBHQQZ.js +411 -0
  69. package/dist/chunk-DHWADYZX.js +144 -0
  70. package/dist/chunk-DKCSPATL.js +1 -0
  71. package/dist/chunk-DM5QUKKO.js +326 -0
  72. package/dist/chunk-DSJBXLUG.js +845 -0
  73. package/dist/chunk-DYBH2I6W.js +1 -0
  74. package/dist/chunk-DZ255O2F.js +1 -0
  75. package/dist/chunk-E35BPSGW.js +1 -0
  76. package/dist/chunk-EJBDRVIH.js +166 -0
  77. package/dist/chunk-F43NP42L.js +1 -0
  78. package/dist/chunk-F5XNPWS7.js +1024 -0
  79. package/dist/chunk-FIF2M24N.js +1521 -0
  80. package/dist/chunk-FOKG57CR.js +993 -0
  81. package/dist/chunk-G3HOYSF7.js +1 -0
  82. package/dist/chunk-GDKZTW34.js +395 -0
  83. package/dist/chunk-GL7B2DOW.js +134 -0
  84. package/dist/chunk-GNNJ4GCC.js +471 -0
  85. package/dist/chunk-GNZ56EUI.js +1 -0
  86. package/dist/chunk-H6NIOQHK.js +346 -0
  87. package/dist/chunk-H6PC7RDA.js +1 -0
  88. package/dist/chunk-HPPW424V.js +334 -0
  89. package/dist/chunk-HQOQIBJI.js +1 -0
  90. package/dist/chunk-HU2EQNRZ.js +44 -0
  91. package/dist/chunk-IIQTAY3J.js +715 -0
  92. package/dist/chunk-ITTYJZWX.js +271 -0
  93. package/dist/chunk-IVNRBPGC.js +1018 -0
  94. package/dist/chunk-J4KLDGI4.js +1 -0
  95. package/dist/chunk-J7Q7VCVC.js +254 -0
  96. package/dist/chunk-JDEOITQ2.js +1 -0
  97. package/dist/chunk-JNH2TYIN.js +96 -0
  98. package/dist/chunk-JRLTGLN4.js +1 -0
  99. package/dist/chunk-JTRJJLKL.js +1 -0
  100. package/dist/chunk-JUAMLEHX.js +379 -0
  101. package/dist/chunk-JXJB2X6M.js +131 -0
  102. package/dist/chunk-KMI5ML5I.js +262 -0
  103. package/dist/chunk-KTAC4MGE.js +4 -0
  104. package/dist/chunk-LAECSR4H.js +178 -0
  105. package/dist/chunk-LK4BCSGX.js +1 -0
  106. package/dist/chunk-LOE5YNN3.js +470 -0
  107. package/dist/chunk-LRJNDEIA.js +302 -0
  108. package/dist/chunk-LSS54GLJ.js +116 -0
  109. package/dist/chunk-LZLHELAG.js +137 -0
  110. package/dist/chunk-M2LUXGKZ.js +1 -0
  111. package/dist/chunk-MOUJOP55.js +425 -0
  112. package/dist/chunk-N32RGOA5.js +159 -0
  113. package/dist/chunk-N5ZNE2YE.js +1 -0
  114. package/dist/chunk-NBNCBML6.js +1 -0
  115. package/dist/chunk-NKB7BZYK.js +167 -0
  116. package/dist/chunk-NQYBUO3K.js +1795 -0
  117. package/dist/chunk-NY2U3PBI.js +59 -0
  118. package/dist/chunk-O4AEHWII.js +464 -0
  119. package/dist/chunk-O7AE6DAF.js +146 -0
  120. package/dist/chunk-OETCUXQP.js +134 -0
  121. package/dist/chunk-OOPPQH27.js +179 -0
  122. package/dist/chunk-OPXFZ4Y5.js +204 -0
  123. package/dist/chunk-P23EQNRQ.js +338 -0
  124. package/dist/chunk-PJXUJYS6.js +248 -0
  125. package/dist/chunk-PNWUWOBF.js +1 -0
  126. package/dist/chunk-PUPM2YGF.js +376 -0
  127. package/dist/chunk-PZXZJBMP.js +620 -0
  128. package/dist/chunk-Q72LJOVE.js +169 -0
  129. package/dist/chunk-QCDXZBC5.js +247 -0
  130. package/dist/chunk-QDYDBCFP.js +197 -0
  131. package/dist/chunk-QJJ6MADV.js +274 -0
  132. package/dist/chunk-QKXCPTFS.js +349 -0
  133. package/dist/chunk-QWCMU5B2.js +860 -0
  134. package/dist/chunk-RGQWWKLA.js +634 -0
  135. package/dist/chunk-RIBDXGUG.js +108 -0
  136. package/dist/chunk-RJQOOHT2.js +214 -0
  137. package/dist/chunk-RNGLIHFA.js +191 -0
  138. package/dist/chunk-RQ3HASCC.js +6 -0
  139. package/dist/chunk-RSOJT5YW.js +124 -0
  140. package/dist/chunk-RTIKSY6A.js +1 -0
  141. package/dist/chunk-RUZHTGKB.js +237 -0
  142. package/dist/chunk-RW6ZL2FQ.js +284 -0
  143. package/dist/chunk-S7F6XLTK.js +540 -0
  144. package/dist/chunk-SBMNWWBV.js +155 -0
  145. package/dist/chunk-SDTQPRRB.js +1 -0
  146. package/dist/chunk-SYNOJOQO.js +377 -0
  147. package/dist/chunk-T753XT36.js +1 -0
  148. package/dist/chunk-TKQYWZ5V.js +196 -0
  149. package/dist/chunk-TKVFVF5D.js +95 -0
  150. package/dist/chunk-TOXAGRDQ.js +144 -0
  151. package/dist/chunk-TUTI5HOZ.js +112 -0
  152. package/dist/chunk-U7TFM2JA.js +140 -0
  153. package/dist/chunk-UC2WGUP3.js +627 -0
  154. package/dist/chunk-UWFLYMAR.js +1126 -0
  155. package/dist/chunk-UWSZB7BJ.js +1 -0
  156. package/dist/chunk-UYG5L34B.js +87 -0
  157. package/dist/chunk-VBOXAEPI.js +336 -0
  158. package/dist/chunk-VCDBXW5H.js +188 -0
  159. package/dist/chunk-VCXPHHDS.js +1 -0
  160. package/dist/chunk-VEAYTRPX.js +414 -0
  161. package/dist/chunk-VFCUIDRT.js +1 -0
  162. package/dist/chunk-VQHPQEUH.js +273 -0
  163. package/dist/chunk-VXDZFJGP.js +874 -0
  164. package/dist/chunk-WS56BL3D.js +1 -0
  165. package/dist/chunk-WSW6ZFAQ.js +98 -0
  166. package/dist/chunk-WVKKJA6U.js +870 -0
  167. package/dist/chunk-XNUVB5OL.js +375 -0
  168. package/dist/chunk-XOSCY2CW.js +242 -0
  169. package/dist/chunk-XXXEFXY5.js +99 -0
  170. package/dist/chunk-Y5CSSPV4.js +1 -0
  171. package/dist/chunk-Y6DU4J47.js +1 -0
  172. package/dist/chunk-YC6WORFL.js +535 -0
  173. package/dist/chunk-YDGVJETP.js +60 -0
  174. package/dist/chunk-YF4NSSMT.js +366 -0
  175. package/dist/chunk-YG3ZJHGU.js +223 -0
  176. package/dist/chunk-YIJ55TBC.js +1 -0
  177. package/dist/chunk-YKZ4RZZF.js +1 -0
  178. package/dist/chunk-YLLCXTMC.js +37 -0
  179. package/dist/chunk-YTA74UZR.js +1 -0
  180. package/dist/chunk-Z7H7VSFZ.js +323 -0
  181. package/dist/chunk-ZK2H4O5P.js +1 -0
  182. package/dist/chunk-ZVFE5QGU.js +524 -0
  183. package/dist/coachmark/index.js +4 -0
  184. package/dist/code-block/index.js +4 -0
  185. package/dist/color-field/index.js +7 -0
  186. package/dist/commerce/index.js +20 -0
  187. package/dist/connectivity/index.js +2 -0
  188. package/dist/context-menu/index.js +5 -0
  189. package/dist/copy-button/index.js +3 -0
  190. package/dist/data-grid/index.js +24 -0
  191. package/dist/date-range-picker/index.js +13 -0
  192. package/dist/description-list/index.js +2 -0
  193. package/dist/dialog/index.js +3 -0
  194. package/dist/empty-state/index.js +3 -0
  195. package/dist/error-summary/index.js +3 -0
  196. package/dist/fab/index.js +2 -0
  197. package/dist/file-upload/index.js +9 -0
  198. package/dist/filter-bar/index.js +8 -0
  199. package/dist/flow-run/index.js +14 -0
  200. package/dist/form/index.js +3 -0
  201. package/dist/gallery/index.js +2 -0
  202. package/dist/header/index.js +5 -0
  203. package/dist/hold-to-confirm/index.js +4 -0
  204. package/dist/index.d.ts +22883 -0
  205. package/dist/index.js +158 -0
  206. package/dist/infinite-list/index.js +2 -0
  207. package/dist/inline-edit/index.js +5 -0
  208. package/dist/input/index.js +5 -0
  209. package/dist/interactions/index.js +2 -0
  210. package/dist/invoice/index.js +10 -0
  211. package/dist/kbd/index.js +1 -0
  212. package/dist/keyboard/index.js +3 -0
  213. package/dist/list-row/index.js +7 -0
  214. package/dist/log-viewer/index.js +2 -0
  215. package/dist/map-view/index.js +3 -0
  216. package/dist/markdown/index.js +6 -0
  217. package/dist/metric/index.js +4 -0
  218. package/dist/money/index.js +3 -0
  219. package/dist/nav-drawer/index.js +2 -0
  220. package/dist/navigation/index.js +2 -0
  221. package/dist/notification-center/index.js +16 -0
  222. package/dist/number-roll/index.js +3 -0
  223. package/dist/one-handed/index.js +5 -0
  224. package/dist/os-surfaces/index.js +3 -0
  225. package/dist/otp/index.js +7 -0
  226. package/dist/page-header/index.js +1 -0
  227. package/dist/payment-method/index.js +12 -0
  228. package/dist/payment-status/index.js +16 -0
  229. package/dist/permission/index.js +2 -0
  230. package/dist/picker/index.js +7 -0
  231. package/dist/progress/index.js +5 -0
  232. package/dist/pull-to-refresh/index.js +4 -0
  233. package/dist/radio-group/index.js +6 -0
  234. package/dist/rating/index.js +2 -0
  235. package/dist/receivables/index.js +20 -0
  236. package/dist/refund/index.js +16 -0
  237. package/dist/save-status/index.js +3 -0
  238. package/dist/scroll-area/index.js +1 -0
  239. package/dist/search-bar/index.js +5 -0
  240. package/dist/segmented-control/index.js +5 -0
  241. package/dist/sheet/index.js +4 -0
  242. package/dist/skeleton/index.js +2 -0
  243. package/dist/slider/index.js +5 -0
  244. package/dist/spinner/index.js +2 -0
  245. package/dist/split-button/index.js +7 -0
  246. package/dist/spotlight/index.js +2 -0
  247. package/dist/stage-timeline/index.js +4 -0
  248. package/dist/status-dot/index.js +2 -0
  249. package/dist/stepper/index.js +6 -0
  250. package/dist/streaming-text/index.js +3 -0
  251. package/dist/survey/index.js +19 -0
  252. package/dist/switch/index.js +5 -0
  253. package/dist/system-state/index.js +4 -0
  254. package/dist/tab-bar/index.js +2 -0
  255. package/dist/tabs/index.js +2 -0
  256. package/dist/textarea/index.js +6 -0
  257. package/dist/thinking/index.js +2 -0
  258. package/dist/timeline/index.js +4 -0
  259. package/dist/toast/index.js +2 -0
  260. package/dist/toolbar/index.js +2 -0
  261. package/dist/tour/index.js +11 -0
  262. package/dist/truncation-notice/index.js +2 -0
  263. package/dist/update-notice/index.js +11 -0
  264. package/dist/video-player/index.js +2 -0
  265. package/dist/viewfinder/index.js +5 -0
  266. package/dist/widget/index.js +4 -0
  267. package/dist/wizard/index.js +6 -0
  268. package/package.json +76 -3
  269. package/README.md +0 -3
@@ -0,0 +1,470 @@
1
+ import { overscrollIntent, bandIndicator, overscrollOffset, GESTURE_SLOP_PX, SCROLL_TOP_SLOP_PX, isReachExitArmed, REACH_ARM_PX } from './chunk-HU2EQNRZ.js';
2
+ import { expandedProps } from './chunk-RQ3HASCC.js';
3
+ import { haptics } from './chunk-NY2U3PBI.js';
4
+ import { Icon } from './chunk-TKVFVF5D.js';
5
+ import * as React from 'react';
6
+ import { useWindowDimensions, Animated, Easing, AccessibilityInfo, PanResponder, View, Pressable, ScrollView } from 'react-native';
7
+ import { createStyleSheet, useStyles, useLoamTheme } from '@btndprojects/loam-theme-native';
8
+ import { jsxs, jsx } from 'react/jsx-runtime';
9
+
10
+ var DROPLET_STRETCH = 0.45;
11
+ var DROPLET_SQUISH = 0.18;
12
+ var DROPLET_TAUT_Y = 1.02;
13
+ var DROPLET_TAUT_X = 1.05;
14
+ var SPIN_DURATION_MS = 1100;
15
+ var DROPLET_EXIT_SCALE = 0.4;
16
+ var EXIT_BAR_MIN_HEIGHT = 48;
17
+ var REFERENCE_SHIFT = 232;
18
+ var SLOW_SPRING = { speed: 8, bounciness: 7 };
19
+ function dropletGeometry(offset, size, clearance, owners = {}) {
20
+ const band = offset > 0 ? offset : 0;
21
+ const intent = overscrollIntent(band, owners);
22
+ const armed = intent !== "idle";
23
+ const progress = Math.min(1, band / REACH_ARM_PX);
24
+ const stretch = armed ? DROPLET_TAUT_Y : 1 + DROPLET_STRETCH * progress;
25
+ const squish = armed ? DROPLET_TAUT_X : 1 - DROPLET_SQUISH * progress;
26
+ const fitted = bandIndicator(band, size * stretch, clearance);
27
+ return {
28
+ opacity: fitted.opacity,
29
+ translateY: band / 2 - size / 2,
30
+ scaleX: fitted.scale * squish,
31
+ scaleY: fitted.scale * stretch,
32
+ bottom: band / 2 + size * fitted.scale * stretch / 2,
33
+ intent,
34
+ armed
35
+ };
36
+ }
37
+ function reachShift(windowHeight, reachZone = 0.62) {
38
+ if (!Number.isFinite(windowHeight) || windowHeight <= 0) return REFERENCE_SHIFT;
39
+ return Math.round(windowHeight * (1 - reachZone));
40
+ }
41
+ function OneHanded({
42
+ active,
43
+ onActiveChange,
44
+ onRefresh,
45
+ refreshing,
46
+ shift,
47
+ exitLabel = "Exit one-handed reach",
48
+ exitHint = "Drag up or tap to move the content back",
49
+ indicatorLabel = "Pull down to refresh, keep pulling for one-handed reach",
50
+ refreshingLabel = "Refreshing",
51
+ enterAnnouncement = "One-handed reach on. Content moved down.",
52
+ exitAnnouncement = "One-handed reach off.",
53
+ children,
54
+ ...rest
55
+ }) {
56
+ const { styles } = useStyles(sheet);
57
+ const theme = useLoamTheme();
58
+ const { height: windowHeight } = useWindowDimensions();
59
+ const dropDistance = shift ?? reachShift(windowHeight, theme.layout.reachZone);
60
+ const bandHeight = Math.max(dropDistance, EXIT_BAR_MIN_HEIGHT);
61
+ const dropletSize = theme.icon.lg + theme.space.s1;
62
+ const clearance = theme.space.s3;
63
+ const [ownActive, setOwnActive] = React.useState(false);
64
+ const isActive = active ?? ownActive;
65
+ const [ownRefreshing, setOwnRefreshing] = React.useState(false);
66
+ const isRefreshing = refreshing ?? ownRefreshing;
67
+ const [intent, setIntent] = React.useState("idle");
68
+ const [bandMounted, setBandMounted] = React.useState(isActive);
69
+ const contentY = React.useRef(new Animated.Value(isActive ? dropDistance : 0)).current;
70
+ const dropOpacity = React.useRef(new Animated.Value(0)).current;
71
+ const dropY = React.useRef(new Animated.Value(0)).current;
72
+ const dropScaleX = React.useRef(new Animated.Value(1)).current;
73
+ const dropScaleY = React.useRef(new Animated.Value(1)).current;
74
+ const spinnerOpacity = React.useRef(new Animated.Value(0)).current;
75
+ const spin = React.useRef(new Animated.Value(0)).current;
76
+ const bandOpacity = React.useRef(new Animated.Value(isActive ? 1 : 0)).current;
77
+ const enterEasing = React.useMemo(
78
+ () => Easing.bezier(...theme.motion.enter.easing),
79
+ [theme.motion.enter.easing]
80
+ );
81
+ const exitEasing = React.useMemo(
82
+ () => Easing.bezier(...theme.motion.exit.easing),
83
+ [theme.motion.exit.easing]
84
+ );
85
+ const springTo = React.useCallback(
86
+ (value, toValue) => theme.reduceMotion ? Animated.timing(value, {
87
+ toValue,
88
+ duration: theme.motion.spring.duration,
89
+ easing: Easing.bezier(...theme.motion.spring.easing),
90
+ useNativeDriver: true
91
+ }) : Animated.spring(value, { toValue, useNativeDriver: true, ...SLOW_SPRING }),
92
+ [theme.reduceMotion, theme.motion.spring]
93
+ );
94
+ const owners = React.useMemo(
95
+ () => ({ refresh: onRefresh != null, reach: true }),
96
+ [onRefresh]
97
+ );
98
+ const ownersRef = React.useRef(owners);
99
+ ownersRef.current = owners;
100
+ const paintDroplet = React.useCallback(
101
+ (offset) => {
102
+ const g = dropletGeometry(offset, dropletSize, clearance, ownersRef.current);
103
+ dropOpacity.setValue(g.opacity);
104
+ dropY.setValue(g.translateY);
105
+ dropScaleX.setValue(g.scaleX);
106
+ dropScaleY.setValue(g.scaleY);
107
+ return g;
108
+ },
109
+ [dropOpacity, dropY, dropScaleX, dropScaleY, dropletSize, clearance]
110
+ );
111
+ const settleDroplet = React.useCallback(() => {
112
+ Animated.parallel([
113
+ Animated.timing(dropOpacity, {
114
+ toValue: 0,
115
+ duration: theme.motion.exit.duration,
116
+ easing: exitEasing,
117
+ useNativeDriver: true
118
+ }),
119
+ springTo(dropY, 0),
120
+ springTo(dropScaleX, 1),
121
+ springTo(dropScaleY, 1)
122
+ ]).start();
123
+ }, [dropOpacity, dropY, dropScaleX, dropScaleY, springTo, exitEasing, theme.motion.exit.duration]);
124
+ const onRefreshRef = React.useRef(onRefresh);
125
+ onRefreshRef.current = onRefresh;
126
+ const controlledRefreshing = refreshing != null;
127
+ const parkedY = React.useMemo(
128
+ () => dropletGeometry(
129
+ dropletSize * DROPLET_TAUT_Y + clearance * 2,
130
+ dropletSize,
131
+ clearance,
132
+ ownersRef.current
133
+ ).translateY,
134
+ [dropletSize, clearance]
135
+ );
136
+ const wasRefreshing = React.useRef(isRefreshing);
137
+ React.useEffect(() => {
138
+ const changed = wasRefreshing.current !== isRefreshing;
139
+ wasRefreshing.current = isRefreshing;
140
+ if (isRefreshing) {
141
+ spinnerOpacity.setValue(1);
142
+ Animated.parallel([
143
+ Animated.timing(dropOpacity, {
144
+ toValue: 1,
145
+ duration: theme.motion.enter.duration,
146
+ easing: enterEasing,
147
+ useNativeDriver: true
148
+ }),
149
+ springTo(dropY, parkedY),
150
+ springTo(dropScaleX, 1),
151
+ springTo(dropScaleY, 1)
152
+ ]).start();
153
+ if (theme.reduceMotion) {
154
+ spin.setValue(0);
155
+ return;
156
+ }
157
+ const loop = Animated.loop(
158
+ Animated.sequence([
159
+ Animated.timing(spin, {
160
+ toValue: 1,
161
+ duration: theme.loop.spin,
162
+ easing: Easing.linear,
163
+ useNativeDriver: true
164
+ })
165
+ ])
166
+ );
167
+ loop.start();
168
+ return () => loop.stop();
169
+ }
170
+ if (!changed) return;
171
+ spinnerOpacity.setValue(0);
172
+ spin.setValue(0);
173
+ Animated.parallel([
174
+ Animated.timing(dropOpacity, {
175
+ toValue: 0,
176
+ duration: theme.motion.exit.duration,
177
+ easing: exitEasing,
178
+ useNativeDriver: true
179
+ }),
180
+ springTo(dropY, 0),
181
+ springTo(dropScaleX, DROPLET_EXIT_SCALE),
182
+ springTo(dropScaleY, DROPLET_EXIT_SCALE)
183
+ ]).start();
184
+ }, [isRefreshing, parkedY]);
185
+ const refreshTimer = React.useRef(null);
186
+ const runRefresh = React.useCallback(() => {
187
+ haptics.impact("medium");
188
+ onRefreshRef.current?.();
189
+ if (controlledRefreshing) return;
190
+ setOwnRefreshing(true);
191
+ if (refreshTimer.current) clearTimeout(refreshTimer.current);
192
+ refreshTimer.current = setTimeout(() => {
193
+ refreshTimer.current = null;
194
+ setOwnRefreshing(false);
195
+ }, SPIN_DURATION_MS);
196
+ }, [controlledRefreshing]);
197
+ React.useEffect(
198
+ () => () => {
199
+ if (refreshTimer.current) clearTimeout(refreshTimer.current);
200
+ },
201
+ []
202
+ );
203
+ const requestActive = React.useCallback(
204
+ (next) => {
205
+ if (active == null) setOwnActive(next);
206
+ onActiveChange?.(next);
207
+ },
208
+ [active, onActiveChange]
209
+ );
210
+ const wasActive = React.useRef(isActive);
211
+ React.useEffect(() => {
212
+ const changed = wasActive.current !== isActive;
213
+ wasActive.current = isActive;
214
+ if (isActive) {
215
+ if (changed) {
216
+ setBandMounted(true);
217
+ haptics.impact("heavy");
218
+ AccessibilityInfo.announceForAccessibility(enterAnnouncement);
219
+ }
220
+ Animated.parallel([
221
+ springTo(contentY, dropDistance),
222
+ Animated.timing(bandOpacity, {
223
+ toValue: 1,
224
+ duration: theme.motion.enter.duration,
225
+ easing: enterEasing,
226
+ useNativeDriver: true
227
+ })
228
+ ]).start();
229
+ return;
230
+ }
231
+ if (!changed) return;
232
+ AccessibilityInfo.announceForAccessibility(exitAnnouncement);
233
+ springTo(contentY, 0).start();
234
+ Animated.timing(bandOpacity, {
235
+ toValue: 0,
236
+ duration: theme.motion.exit.duration,
237
+ easing: exitEasing,
238
+ useNativeDriver: true
239
+ }).start(({ finished }) => {
240
+ if (finished) setBandMounted(false);
241
+ });
242
+ }, [isActive, dropDistance]);
243
+ const scrollTop = React.useRef(0);
244
+ const startedAtTop = React.useRef(false);
245
+ const offsetPx = React.useRef(0);
246
+ const liftPx = React.useRef(0);
247
+ const isActiveRef = React.useRef(isActive);
248
+ isActiveRef.current = isActive;
249
+ const reconcileTimer = React.useRef(null);
250
+ const reconcile = React.useCallback(() => {
251
+ if (reconcileTimer.current) clearTimeout(reconcileTimer.current);
252
+ reconcileTimer.current = setTimeout(() => {
253
+ reconcileTimer.current = null;
254
+ springTo(contentY, isActiveRef.current ? dropDistance : 0).start();
255
+ }, 0);
256
+ }, [contentY, dropDistance, springTo]);
257
+ React.useEffect(
258
+ () => () => {
259
+ if (reconcileTimer.current) clearTimeout(reconcileTimer.current);
260
+ },
261
+ []
262
+ );
263
+ const overscroll = React.useRef({
264
+ down: () => {
265
+ },
266
+ canStart: (_dy, _dx) => false,
267
+ move: (_dy) => {
268
+ },
269
+ release: () => {
270
+ }
271
+ });
272
+ overscroll.current = {
273
+ down: () => {
274
+ startedAtTop.current = scrollTop.current <= SCROLL_TOP_SLOP_PX;
275
+ },
276
+ canStart: (dy, dx) => !isActive && !isRefreshing && startedAtTop.current && dy > GESTURE_SLOP_PX && dy > Math.abs(dx),
277
+ move: (dy) => {
278
+ const offset = overscrollOffset(dy);
279
+ offsetPx.current = offset;
280
+ contentY.setValue(offset);
281
+ setIntent(paintDroplet(offset).intent);
282
+ },
283
+ release: () => {
284
+ const offset = offsetPx.current;
285
+ offsetPx.current = 0;
286
+ const resolved = overscrollIntent(offset, ownersRef.current);
287
+ setIntent("idle");
288
+ if (resolved === "reach") {
289
+ paintDroplet(0);
290
+ requestActive(true);
291
+ reconcile();
292
+ return;
293
+ }
294
+ springTo(contentY, 0).start();
295
+ if (resolved === "refresh") runRefresh();
296
+ else settleDroplet();
297
+ }
298
+ };
299
+ const exit = React.useRef({
300
+ canStart: (_dy, _dx) => false,
301
+ move: (_dy) => {
302
+ },
303
+ release: () => {
304
+ }
305
+ });
306
+ exit.current = {
307
+ canStart: (dy, dx) => isActive && dy < -GESTURE_SLOP_PX && Math.abs(dy) > Math.abs(dx),
308
+ move: (dy) => {
309
+ liftPx.current = dy;
310
+ contentY.setValue(dropDistance + Math.min(0, Math.max(-dropDistance, dy)));
311
+ },
312
+ release: () => {
313
+ const lift = liftPx.current;
314
+ liftPx.current = 0;
315
+ if (isReachExitArmed(lift)) {
316
+ requestActive(false);
317
+ reconcile();
318
+ return;
319
+ }
320
+ springTo(contentY, dropDistance).start();
321
+ }
322
+ };
323
+ const pan = React.useRef(
324
+ PanResponder.create({
325
+ // Snapshot the scroll position, but never claim the responder on touch-down.
326
+ onStartShouldSetPanResponderCapture: () => {
327
+ overscroll.current.down();
328
+ return false;
329
+ },
330
+ // Capture, because at the top of a scroller a downward drag is an
331
+ // over-scroll the ScrollView has nothing to do with.
332
+ onMoveShouldSetPanResponderCapture: (_e, g) => overscroll.current.canStart(g.dy, g.dx),
333
+ onPanResponderMove: (_e, g) => overscroll.current.move(g.dy),
334
+ onPanResponderRelease: () => overscroll.current.release(),
335
+ onPanResponderTerminate: () => overscroll.current.release(),
336
+ onPanResponderTerminationRequest: () => false
337
+ })
338
+ ).current;
339
+ const exitPan = React.useRef(
340
+ PanResponder.create({
341
+ // Only a real upward drag on the freed band; a tap falls through to the
342
+ // Pressable inside it.
343
+ onMoveShouldSetPanResponderCapture: (_e, g) => exit.current.canStart(g.dy, g.dx),
344
+ onPanResponderMove: (_e, g) => exit.current.move(g.dy),
345
+ onPanResponderRelease: () => exit.current.release(),
346
+ onPanResponderTerminate: () => exit.current.release(),
347
+ onPanResponderTerminationRequest: () => false
348
+ })
349
+ ).current;
350
+ const onAccent = isRefreshing || intent !== "reach";
351
+ const dropletFill = onAccent ? theme.color.accent : theme.color.good;
352
+ const spinnerInk = onAccent ? theme.color.accentInk : theme.color.goodInk;
353
+ const rotate = spin.interpolate({ inputRange: [0, 1], outputRange: ["0deg", "360deg"] });
354
+ return /* @__PURE__ */ jsxs(View, { style: styles.root, ...pan.panHandlers, children: [
355
+ bandMounted ? /* @__PURE__ */ jsx(
356
+ Animated.View,
357
+ {
358
+ style: [
359
+ styles.exitBand,
360
+ { height: bandHeight, opacity: bandOpacity, pointerEvents: isActive ? "auto" : "none" }
361
+ ],
362
+ ...exitPan.panHandlers,
363
+ children: /* @__PURE__ */ jsx(
364
+ Pressable,
365
+ {
366
+ accessibilityRole: "button",
367
+ accessibilityLabel: exitLabel,
368
+ accessibilityHint: exitHint,
369
+ accessibilityState: { expanded: isActive },
370
+ ...expandedProps(isActive),
371
+ onPress: () => {
372
+ requestActive(false);
373
+ reconcile();
374
+ },
375
+ style: styles.exitPress,
376
+ children: /* @__PURE__ */ jsx(Icon, { name: "chevron-up", size: theme.icon.lg, color: theme.color.text3 })
377
+ }
378
+ )
379
+ }
380
+ ) : null,
381
+ /* @__PURE__ */ jsx(
382
+ Animated.View,
383
+ {
384
+ accessibilityRole: "progressbar",
385
+ accessibilityLabel: isRefreshing ? refreshingLabel : indicatorLabel,
386
+ style: [
387
+ styles.droplet,
388
+ {
389
+ width: dropletSize,
390
+ height: dropletSize,
391
+ backgroundColor: dropletFill,
392
+ opacity: dropOpacity,
393
+ transform: [{ translateY: dropY }, { scaleX: dropScaleX }, { scaleY: dropScaleY }]
394
+ }
395
+ ],
396
+ children: /* @__PURE__ */ jsx(
397
+ Animated.View,
398
+ {
399
+ style: [
400
+ styles.spinner,
401
+ {
402
+ borderWidth: theme.icon.stroke,
403
+ borderTopColor: spinnerInk,
404
+ opacity: spinnerOpacity,
405
+ transform: [{ rotate }]
406
+ }
407
+ ]
408
+ }
409
+ )
410
+ }
411
+ ),
412
+ /* @__PURE__ */ jsx(Animated.View, { style: [styles.content, { transform: [{ translateY: contentY }] }], children: /* @__PURE__ */ jsx(
413
+ ScrollView,
414
+ {
415
+ ...rest,
416
+ focusable: true,
417
+ scrollEventThrottle: 16,
418
+ contentContainerStyle: [
419
+ rest.contentContainerStyle,
420
+ bandMounted ? { paddingBottom: dropDistance } : null
421
+ ],
422
+ onScroll: (e) => {
423
+ scrollTop.current = e.nativeEvent.contentOffset.y;
424
+ rest.onScroll?.(e);
425
+ },
426
+ children
427
+ }
428
+ ) })
429
+ ] });
430
+ }
431
+ var sheet = createStyleSheet((theme) => ({
432
+ root: { flex: 1, overflow: "hidden" },
433
+ content: { flex: 1 },
434
+ // Anchored at the very top of the freed band; `dropletGeometry` does the placing.
435
+ droplet: {
436
+ position: "absolute",
437
+ top: 0,
438
+ alignSelf: "center",
439
+ borderRadius: theme.radius.full,
440
+ // Decorative and untouchable — the gesture lives on the root.
441
+ pointerEvents: "none",
442
+ // Above the exit band, both above the content.
443
+ zIndex: theme.z.sticky
444
+ },
445
+ spinner: {
446
+ position: "absolute",
447
+ top: 0,
448
+ bottom: 0,
449
+ start: 0,
450
+ end: 0,
451
+ borderRadius: theme.radius.full,
452
+ borderColor: "transparent"
453
+ },
454
+ // The whole freed band is the way out: tap it, or drag it up.
455
+ exitBand: {
456
+ position: "absolute",
457
+ top: 0,
458
+ start: 0,
459
+ end: 0,
460
+ zIndex: theme.z.raised
461
+ },
462
+ exitPress: {
463
+ flex: 1,
464
+ alignItems: "center",
465
+ justifyContent: "flex-end",
466
+ paddingBottom: theme.space.s3
467
+ }
468
+ }));
469
+
470
+ export { DROPLET_EXIT_SCALE, DROPLET_SQUISH, DROPLET_STRETCH, DROPLET_TAUT_X, DROPLET_TAUT_Y, EXIT_BAR_MIN_HEIGHT, OneHanded, REFERENCE_SHIFT, SPIN_DURATION_MS, dropletGeometry, reachShift };