@tamagui/toast 2.0.0-rc.9 → 2.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 (413) hide show
  1. package/dist/cjs/Toast.cjs +149 -141
  2. package/dist/cjs/Toast.native.js +149 -142
  3. package/dist/cjs/Toast.native.js.map +1 -1
  4. package/dist/cjs/ToastAnnounce.cjs +78 -72
  5. package/dist/cjs/ToastAnnounce.native.js +85 -79
  6. package/dist/cjs/ToastAnnounce.native.js.map +1 -1
  7. package/dist/cjs/ToastComposable.cjs +780 -591
  8. package/dist/cjs/ToastComposable.native.js +863 -706
  9. package/dist/cjs/ToastComposable.native.js.map +1 -1
  10. package/dist/cjs/ToastImperative.cjs +99 -80
  11. package/dist/cjs/ToastImperative.native.js +104 -96
  12. package/dist/cjs/ToastImperative.native.js.map +1 -1
  13. package/dist/cjs/ToastImpl.cjs +300 -238
  14. package/dist/cjs/ToastImpl.native.js +309 -271
  15. package/dist/cjs/ToastImpl.native.js.map +1 -1
  16. package/dist/cjs/ToastItemFrame.cjs +143 -0
  17. package/dist/cjs/ToastItemFrame.native.js +148 -0
  18. package/dist/cjs/ToastItemFrame.native.js.map +1 -0
  19. package/dist/cjs/ToastPortal.cjs +23 -18
  20. package/dist/cjs/ToastPortal.native.js +27 -22
  21. package/dist/cjs/ToastPortal.native.js.map +1 -1
  22. package/dist/cjs/ToastProvider.cjs +102 -98
  23. package/dist/cjs/ToastProvider.native.js +108 -106
  24. package/dist/cjs/ToastProvider.native.js.map +1 -1
  25. package/dist/cjs/ToastState.cjs +218 -155
  26. package/dist/cjs/ToastState.native.js +270 -203
  27. package/dist/cjs/ToastState.native.js.map +1 -1
  28. package/dist/cjs/ToastViewport.cjs +274 -233
  29. package/dist/cjs/ToastViewport.native.js +301 -273
  30. package/dist/cjs/ToastViewport.native.js.map +1 -1
  31. package/dist/cjs/Toaster.cjs +71 -233
  32. package/dist/cjs/Toaster.native.js +72 -289
  33. package/dist/cjs/Toaster.native.js.map +1 -1
  34. package/dist/cjs/constants.cjs +14 -12
  35. package/dist/cjs/constants.native.js +14 -12
  36. package/dist/cjs/constants.native.js.map +1 -1
  37. package/dist/cjs/createNativeToast.cjs +43 -35
  38. package/dist/cjs/createNativeToast.native.js +42 -30
  39. package/dist/cjs/createNativeToast.native.js.map +1 -1
  40. package/dist/cjs/dispatchNativeToast.cjs +47 -0
  41. package/dist/cjs/dispatchNativeToast.native.js +52 -0
  42. package/dist/cjs/dispatchNativeToast.native.js.map +1 -0
  43. package/dist/cjs/index.cjs +7 -5
  44. package/dist/cjs/index.native.js +7 -5
  45. package/dist/cjs/index.native.js.map +1 -1
  46. package/dist/cjs/types.cjs +7 -5
  47. package/dist/cjs/types.native.js +7 -5
  48. package/dist/cjs/types.native.js.map +1 -1
  49. package/dist/cjs/useAnimatedDragGesture.cjs +184 -83
  50. package/dist/cjs/useAnimatedDragGesture.native.js +193 -79
  51. package/dist/cjs/useAnimatedDragGesture.native.js.map +1 -1
  52. package/dist/cjs/useReducedMotion.cjs +44 -30
  53. package/dist/cjs/useReducedMotion.native.js +52 -43
  54. package/dist/cjs/useReducedMotion.native.js.map +1 -1
  55. package/dist/cjs/useToastAnimations.cjs +233 -155
  56. package/dist/cjs/useToastAnimations.native.js +246 -170
  57. package/dist/cjs/useToastAnimations.native.js.map +1 -1
  58. package/dist/cjs/v2.cjs +36 -0
  59. package/dist/cjs/v2.native.js +39 -0
  60. package/dist/cjs/v2.native.js.map +1 -0
  61. package/dist/esm/Toast.mjs +112 -106
  62. package/dist/esm/Toast.mjs.map +1 -1
  63. package/dist/esm/Toast.native.js +112 -107
  64. package/dist/esm/Toast.native.js.map +1 -1
  65. package/dist/esm/ToastAnnounce.mjs +46 -42
  66. package/dist/esm/ToastAnnounce.mjs.map +1 -1
  67. package/dist/esm/ToastAnnounce.native.js +52 -48
  68. package/dist/esm/ToastAnnounce.native.js.map +1 -1
  69. package/dist/esm/ToastComposable.mjs +742 -555
  70. package/dist/esm/ToastComposable.mjs.map +1 -1
  71. package/dist/esm/ToastComposable.native.js +825 -670
  72. package/dist/esm/ToastComposable.native.js.map +1 -1
  73. package/dist/esm/ToastImperative.mjs +71 -54
  74. package/dist/esm/ToastImperative.mjs.map +1 -1
  75. package/dist/esm/ToastImperative.native.js +76 -70
  76. package/dist/esm/ToastImperative.native.js.map +1 -1
  77. package/dist/esm/ToastImpl.mjs +261 -201
  78. package/dist/esm/ToastImpl.mjs.map +1 -1
  79. package/dist/esm/ToastImpl.native.js +270 -234
  80. package/dist/esm/ToastImpl.native.js.map +1 -1
  81. package/dist/esm/ToastItemFrame.mjs +114 -0
  82. package/dist/esm/ToastItemFrame.mjs.map +1 -0
  83. package/dist/esm/ToastItemFrame.native.js +116 -0
  84. package/dist/esm/ToastItemFrame.native.js.map +1 -0
  85. package/dist/esm/ToastPortal.mjs +8 -5
  86. package/dist/esm/ToastPortal.mjs.map +1 -1
  87. package/dist/esm/ToastPortal.native.js +12 -9
  88. package/dist/esm/ToastPortal.native.js.map +1 -1
  89. package/dist/esm/ToastProvider.mjs +71 -69
  90. package/dist/esm/ToastProvider.mjs.map +1 -1
  91. package/dist/esm/ToastProvider.native.js +77 -77
  92. package/dist/esm/ToastProvider.native.js.map +1 -1
  93. package/dist/esm/ToastState.mjs +205 -144
  94. package/dist/esm/ToastState.mjs.map +1 -1
  95. package/dist/esm/ToastState.native.js +258 -193
  96. package/dist/esm/ToastState.native.js.map +1 -1
  97. package/dist/esm/ToastViewport.mjs +238 -199
  98. package/dist/esm/ToastViewport.mjs.map +1 -1
  99. package/dist/esm/ToastViewport.native.js +265 -239
  100. package/dist/esm/ToastViewport.native.js.map +1 -1
  101. package/dist/esm/Toaster.mjs +45 -209
  102. package/dist/esm/Toaster.mjs.map +1 -1
  103. package/dist/esm/Toaster.native.js +46 -265
  104. package/dist/esm/Toaster.native.js.map +1 -1
  105. package/dist/esm/constants.mjs +2 -2
  106. package/dist/esm/constants.mjs.map +1 -1
  107. package/dist/esm/constants.native.js +2 -2
  108. package/dist/esm/constants.native.js.map +1 -1
  109. package/dist/esm/createNativeToast.mjs +29 -24
  110. package/dist/esm/createNativeToast.mjs.map +1 -1
  111. package/dist/esm/createNativeToast.native.js +27 -18
  112. package/dist/esm/createNativeToast.native.js.map +1 -1
  113. package/dist/esm/dispatchNativeToast.mjs +22 -0
  114. package/dist/esm/dispatchNativeToast.mjs.map +1 -0
  115. package/dist/esm/dispatchNativeToast.native.js +24 -0
  116. package/dist/esm/dispatchNativeToast.native.js.map +1 -0
  117. package/dist/esm/index.js +1 -1
  118. package/dist/esm/index.js.map +1 -6
  119. package/dist/esm/useAnimatedDragGesture.mjs +160 -61
  120. package/dist/esm/useAnimatedDragGesture.mjs.map +1 -1
  121. package/dist/esm/useAnimatedDragGesture.native.js +167 -55
  122. package/dist/esm/useAnimatedDragGesture.native.js.map +1 -1
  123. package/dist/esm/useReducedMotion.mjs +18 -6
  124. package/dist/esm/useReducedMotion.mjs.map +1 -1
  125. package/dist/esm/useReducedMotion.native.js +25 -18
  126. package/dist/esm/useReducedMotion.native.js.map +1 -1
  127. package/dist/esm/useToastAnimations.mjs +206 -130
  128. package/dist/esm/useToastAnimations.mjs.map +1 -1
  129. package/dist/esm/useToastAnimations.native.js +218 -144
  130. package/dist/esm/useToastAnimations.native.js.map +1 -1
  131. package/dist/esm/v2.mjs +6 -0
  132. package/dist/esm/v2.mjs.map +1 -0
  133. package/dist/esm/v2.native.js +6 -0
  134. package/dist/esm/v2.native.js.map +1 -0
  135. package/dist/jsx/Toast.mjs +112 -106
  136. package/dist/jsx/Toast.mjs.map +1 -1
  137. package/dist/jsx/Toast.native.js +149 -142
  138. package/dist/jsx/Toast.native.js.map +1 -1
  139. package/dist/jsx/ToastAnnounce.mjs +46 -42
  140. package/dist/jsx/ToastAnnounce.mjs.map +1 -1
  141. package/dist/jsx/ToastAnnounce.native.js +85 -79
  142. package/dist/jsx/ToastAnnounce.native.js.map +1 -1
  143. package/dist/jsx/ToastComposable.mjs +742 -555
  144. package/dist/jsx/ToastComposable.mjs.map +1 -1
  145. package/dist/jsx/ToastComposable.native.js +863 -706
  146. package/dist/jsx/ToastComposable.native.js.map +1 -1
  147. package/dist/jsx/ToastImperative.mjs +71 -54
  148. package/dist/jsx/ToastImperative.mjs.map +1 -1
  149. package/dist/jsx/ToastImperative.native.js +104 -96
  150. package/dist/jsx/ToastImperative.native.js.map +1 -1
  151. package/dist/jsx/ToastImpl.mjs +261 -201
  152. package/dist/jsx/ToastImpl.mjs.map +1 -1
  153. package/dist/jsx/ToastImpl.native.js +309 -271
  154. package/dist/jsx/ToastImpl.native.js.map +1 -1
  155. package/dist/jsx/ToastItemFrame.mjs +114 -0
  156. package/dist/jsx/ToastItemFrame.mjs.map +1 -0
  157. package/dist/jsx/ToastItemFrame.native.js +148 -0
  158. package/dist/jsx/ToastItemFrame.native.js.map +1 -0
  159. package/dist/jsx/ToastPortal.mjs +8 -5
  160. package/dist/jsx/ToastPortal.mjs.map +1 -1
  161. package/dist/jsx/ToastPortal.native.js +27 -22
  162. package/dist/jsx/ToastPortal.native.js.map +1 -1
  163. package/dist/jsx/ToastProvider.mjs +71 -69
  164. package/dist/jsx/ToastProvider.mjs.map +1 -1
  165. package/dist/jsx/ToastProvider.native.js +108 -106
  166. package/dist/jsx/ToastProvider.native.js.map +1 -1
  167. package/dist/jsx/ToastState.mjs +205 -144
  168. package/dist/jsx/ToastState.mjs.map +1 -1
  169. package/dist/jsx/ToastState.native.js +270 -203
  170. package/dist/jsx/ToastState.native.js.map +1 -1
  171. package/dist/jsx/ToastViewport.mjs +238 -199
  172. package/dist/jsx/ToastViewport.mjs.map +1 -1
  173. package/dist/jsx/ToastViewport.native.js +301 -273
  174. package/dist/jsx/ToastViewport.native.js.map +1 -1
  175. package/dist/jsx/Toaster.mjs +45 -209
  176. package/dist/jsx/Toaster.mjs.map +1 -1
  177. package/dist/jsx/Toaster.native.js +72 -289
  178. package/dist/jsx/Toaster.native.js.map +1 -1
  179. package/dist/jsx/constants.mjs +2 -2
  180. package/dist/jsx/constants.mjs.map +1 -1
  181. package/dist/jsx/constants.native.js +14 -12
  182. package/dist/jsx/constants.native.js.map +1 -1
  183. package/dist/jsx/createNativeToast.mjs +29 -24
  184. package/dist/jsx/createNativeToast.mjs.map +1 -1
  185. package/dist/jsx/createNativeToast.native.js +42 -30
  186. package/dist/jsx/createNativeToast.native.js.map +1 -1
  187. package/dist/jsx/dispatchNativeToast.mjs +22 -0
  188. package/dist/jsx/dispatchNativeToast.mjs.map +1 -0
  189. package/dist/jsx/dispatchNativeToast.native.js +52 -0
  190. package/dist/jsx/dispatchNativeToast.native.js.map +1 -0
  191. package/dist/jsx/index.js +1 -1
  192. package/dist/jsx/index.js.map +1 -6
  193. package/dist/jsx/index.native.js +7 -5
  194. package/dist/jsx/types.native.js +7 -5
  195. package/dist/jsx/useAnimatedDragGesture.mjs +160 -61
  196. package/dist/jsx/useAnimatedDragGesture.mjs.map +1 -1
  197. package/dist/jsx/useAnimatedDragGesture.native.js +193 -79
  198. package/dist/jsx/useAnimatedDragGesture.native.js.map +1 -1
  199. package/dist/jsx/useReducedMotion.mjs +18 -6
  200. package/dist/jsx/useReducedMotion.mjs.map +1 -1
  201. package/dist/jsx/useReducedMotion.native.js +52 -43
  202. package/dist/jsx/useReducedMotion.native.js.map +1 -1
  203. package/dist/jsx/useToastAnimations.mjs +206 -130
  204. package/dist/jsx/useToastAnimations.mjs.map +1 -1
  205. package/dist/jsx/useToastAnimations.native.js +246 -170
  206. package/dist/jsx/useToastAnimations.native.js.map +1 -1
  207. package/dist/jsx/v2.mjs +6 -0
  208. package/dist/jsx/v2.mjs.map +1 -0
  209. package/dist/jsx/v2.native.js +39 -0
  210. package/dist/jsx/v2.native.js.map +1 -0
  211. package/package.json +41 -24
  212. package/src/ToastComposable.tsx +1380 -0
  213. package/src/ToastImpl.tsx +14 -2
  214. package/src/ToastItemFrame.tsx +136 -0
  215. package/src/ToastPortal.tsx +2 -2
  216. package/src/ToastProvider.tsx +8 -1
  217. package/src/ToastState.ts +398 -0
  218. package/src/ToastViewport.tsx +4 -3
  219. package/src/Toaster.tsx +181 -0
  220. package/src/createNativeToast.native.tsx +4 -0
  221. package/src/createNativeToast.tsx +18 -24
  222. package/src/dispatchNativeToast.ts +43 -0
  223. package/src/useAnimatedDragGesture.native.ts +255 -0
  224. package/src/useAnimatedDragGesture.ts +319 -0
  225. package/src/useReducedMotion.ts +59 -0
  226. package/src/useToastAnimations.ts +372 -0
  227. package/src/v2.ts +31 -0
  228. package/types/ToastComposable.d.ts +199 -0
  229. package/types/ToastComposable.d.ts.map +1 -1
  230. package/types/ToastImpl.d.ts.map +1 -1
  231. package/types/ToastItemFrame.d.ts +25 -0
  232. package/types/ToastItemFrame.d.ts.map +1 -0
  233. package/types/ToastPortal.d.ts.map +1 -1
  234. package/types/ToastProvider.d.ts +1 -1
  235. package/types/ToastProvider.d.ts.map +1 -1
  236. package/types/ToastState.d.ts +179 -0
  237. package/types/ToastState.d.ts.map +1 -1
  238. package/types/ToastViewport.d.ts.map +1 -1
  239. package/types/Toaster.d.ts +112 -0
  240. package/types/Toaster.d.ts.map +1 -1
  241. package/types/createNativeToast.d.ts +5 -0
  242. package/types/createNativeToast.d.ts.map +1 -1
  243. package/types/createNativeToast.native.d.ts +1 -0
  244. package/types/createNativeToast.native.d.ts.map +1 -1
  245. package/types/dispatchNativeToast.d.ts +12 -0
  246. package/types/dispatchNativeToast.d.ts.map +1 -0
  247. package/types/useAnimatedDragGesture.d.ts +33 -0
  248. package/types/useAnimatedDragGesture.d.ts.map +1 -1
  249. package/types/useAnimatedDragGesture.native.d.ts +33 -0
  250. package/types/useAnimatedDragGesture.native.d.ts.map +1 -1
  251. package/types/useReducedMotion.d.ts +6 -0
  252. package/types/useToastAnimations.d.ts +50 -0
  253. package/types/useToastAnimations.d.ts.map +1 -1
  254. package/types/v2.d.ts +10 -0
  255. package/types/v2.d.ts.map +1 -0
  256. package/v2/index.cjs +2 -0
  257. package/v2/index.js +2 -0
  258. package/v2/index.native.cjs +2 -0
  259. package/v2/index.native.js +2 -0
  260. package/LICENSE +0 -21
  261. package/dist/cjs/Toast.js +0 -119
  262. package/dist/cjs/Toast.js.map +0 -6
  263. package/dist/cjs/ToastAnnounce.js +0 -72
  264. package/dist/cjs/ToastAnnounce.js.map +0 -6
  265. package/dist/cjs/ToastComposable.js +0 -548
  266. package/dist/cjs/ToastComposable.js.map +0 -6
  267. package/dist/cjs/ToastImperative.js +0 -71
  268. package/dist/cjs/ToastImperative.js.map +0 -6
  269. package/dist/cjs/ToastImpl.js +0 -227
  270. package/dist/cjs/ToastImpl.js.map +0 -6
  271. package/dist/cjs/ToastItem.cjs +0 -526
  272. package/dist/cjs/ToastItem.js +0 -409
  273. package/dist/cjs/ToastItem.js.map +0 -6
  274. package/dist/cjs/ToastItem.native.js +0 -614
  275. package/dist/cjs/ToastItem.native.js.map +0 -1
  276. package/dist/cjs/ToastPortal.js +0 -26
  277. package/dist/cjs/ToastPortal.js.map +0 -6
  278. package/dist/cjs/ToastProvider.js +0 -105
  279. package/dist/cjs/ToastProvider.js.map +0 -6
  280. package/dist/cjs/ToastState.js +0 -160
  281. package/dist/cjs/ToastState.js.map +0 -6
  282. package/dist/cjs/ToastViewport.js +0 -263
  283. package/dist/cjs/ToastViewport.js.map +0 -6
  284. package/dist/cjs/Toaster.js +0 -204
  285. package/dist/cjs/Toaster.js.map +0 -6
  286. package/dist/cjs/constants.js +0 -22
  287. package/dist/cjs/constants.js.map +0 -6
  288. package/dist/cjs/createNativeToast.js +0 -44
  289. package/dist/cjs/createNativeToast.js.map +0 -6
  290. package/dist/cjs/index.js +0 -15
  291. package/dist/cjs/index.js.map +0 -6
  292. package/dist/cjs/types.js +0 -14
  293. package/dist/cjs/types.js.map +0 -6
  294. package/dist/cjs/useAnimatedDragGesture.js +0 -97
  295. package/dist/cjs/useAnimatedDragGesture.js.map +0 -6
  296. package/dist/cjs/useDragGesture.cjs +0 -129
  297. package/dist/cjs/useDragGesture.js +0 -100
  298. package/dist/cjs/useDragGesture.js.map +0 -6
  299. package/dist/cjs/useDragGesture.native.js +0 -146
  300. package/dist/cjs/useDragGesture.native.js.map +0 -1
  301. package/dist/cjs/useReducedMotion.js +0 -53
  302. package/dist/cjs/useReducedMotion.js.map +0 -6
  303. package/dist/cjs/useToastAnimations.js +0 -144
  304. package/dist/cjs/useToastAnimations.js.map +0 -6
  305. package/dist/cjs/v1.cjs +0 -31
  306. package/dist/cjs/v1.js +0 -26
  307. package/dist/cjs/v1.js.map +0 -6
  308. package/dist/cjs/v1.native.js +0 -34
  309. package/dist/cjs/v1.native.js.map +0 -1
  310. package/dist/esm/Toast.js +0 -107
  311. package/dist/esm/Toast.js.map +0 -6
  312. package/dist/esm/ToastAnnounce.js +0 -55
  313. package/dist/esm/ToastAnnounce.js.map +0 -6
  314. package/dist/esm/ToastComposable.js +0 -543
  315. package/dist/esm/ToastComposable.js.map +0 -6
  316. package/dist/esm/ToastImperative.js +0 -50
  317. package/dist/esm/ToastImperative.js.map +0 -6
  318. package/dist/esm/ToastImpl.js +0 -225
  319. package/dist/esm/ToastImpl.js.map +0 -6
  320. package/dist/esm/ToastItem.js +0 -393
  321. package/dist/esm/ToastItem.js.map +0 -6
  322. package/dist/esm/ToastItem.mjs +0 -492
  323. package/dist/esm/ToastItem.mjs.map +0 -1
  324. package/dist/esm/ToastItem.native.js +0 -577
  325. package/dist/esm/ToastItem.native.js.map +0 -1
  326. package/dist/esm/ToastPortal.js +0 -13
  327. package/dist/esm/ToastPortal.js.map +0 -6
  328. package/dist/esm/ToastProvider.js +0 -87
  329. package/dist/esm/ToastProvider.js.map +0 -6
  330. package/dist/esm/ToastState.js +0 -144
  331. package/dist/esm/ToastState.js.map +0 -6
  332. package/dist/esm/ToastViewport.js +0 -250
  333. package/dist/esm/ToastViewport.js.map +0 -6
  334. package/dist/esm/Toaster.js +0 -188
  335. package/dist/esm/Toaster.js.map +0 -6
  336. package/dist/esm/constants.js +0 -6
  337. package/dist/esm/constants.js.map +0 -6
  338. package/dist/esm/createNativeToast.js +0 -28
  339. package/dist/esm/createNativeToast.js.map +0 -6
  340. package/dist/esm/types.js +0 -1
  341. package/dist/esm/types.js.map +0 -6
  342. package/dist/esm/useAnimatedDragGesture.js +0 -73
  343. package/dist/esm/useAnimatedDragGesture.js.map +0 -6
  344. package/dist/esm/useDragGesture.js +0 -76
  345. package/dist/esm/useDragGesture.js.map +0 -6
  346. package/dist/esm/useDragGesture.mjs +0 -95
  347. package/dist/esm/useDragGesture.mjs.map +0 -1
  348. package/dist/esm/useDragGesture.native.js +0 -109
  349. package/dist/esm/useDragGesture.native.js.map +0 -1
  350. package/dist/esm/useReducedMotion.js +0 -30
  351. package/dist/esm/useReducedMotion.js.map +0 -6
  352. package/dist/esm/useToastAnimations.js +0 -122
  353. package/dist/esm/useToastAnimations.js.map +0 -6
  354. package/dist/esm/v1.js +0 -17
  355. package/dist/esm/v1.js.map +0 -6
  356. package/dist/esm/v1.mjs +0 -3
  357. package/dist/esm/v1.mjs.map +0 -1
  358. package/dist/esm/v1.native.js +0 -3
  359. package/dist/esm/v1.native.js.map +0 -1
  360. package/dist/jsx/Toast.js +0 -107
  361. package/dist/jsx/Toast.js.map +0 -6
  362. package/dist/jsx/ToastAnnounce.js +0 -55
  363. package/dist/jsx/ToastAnnounce.js.map +0 -6
  364. package/dist/jsx/ToastComposable.js +0 -543
  365. package/dist/jsx/ToastComposable.js.map +0 -6
  366. package/dist/jsx/ToastImperative.js +0 -50
  367. package/dist/jsx/ToastImperative.js.map +0 -6
  368. package/dist/jsx/ToastImpl.js +0 -225
  369. package/dist/jsx/ToastImpl.js.map +0 -6
  370. package/dist/jsx/ToastItem.js +0 -393
  371. package/dist/jsx/ToastItem.js.map +0 -6
  372. package/dist/jsx/ToastItem.mjs +0 -492
  373. package/dist/jsx/ToastItem.mjs.map +0 -1
  374. package/dist/jsx/ToastItem.native.js +0 -614
  375. package/dist/jsx/ToastItem.native.js.map +0 -1
  376. package/dist/jsx/ToastPortal.js +0 -13
  377. package/dist/jsx/ToastPortal.js.map +0 -6
  378. package/dist/jsx/ToastProvider.js +0 -87
  379. package/dist/jsx/ToastProvider.js.map +0 -6
  380. package/dist/jsx/ToastState.js +0 -144
  381. package/dist/jsx/ToastState.js.map +0 -6
  382. package/dist/jsx/ToastViewport.js +0 -250
  383. package/dist/jsx/ToastViewport.js.map +0 -6
  384. package/dist/jsx/Toaster.js +0 -188
  385. package/dist/jsx/Toaster.js.map +0 -6
  386. package/dist/jsx/constants.js +0 -6
  387. package/dist/jsx/constants.js.map +0 -6
  388. package/dist/jsx/createNativeToast.js +0 -28
  389. package/dist/jsx/createNativeToast.js.map +0 -6
  390. package/dist/jsx/types.js +0 -1
  391. package/dist/jsx/types.js.map +0 -6
  392. package/dist/jsx/useAnimatedDragGesture.js +0 -73
  393. package/dist/jsx/useAnimatedDragGesture.js.map +0 -6
  394. package/dist/jsx/useDragGesture.js +0 -76
  395. package/dist/jsx/useDragGesture.js.map +0 -6
  396. package/dist/jsx/useDragGesture.mjs +0 -95
  397. package/dist/jsx/useDragGesture.mjs.map +0 -1
  398. package/dist/jsx/useDragGesture.native.js +0 -146
  399. package/dist/jsx/useDragGesture.native.js.map +0 -1
  400. package/dist/jsx/useReducedMotion.js +0 -30
  401. package/dist/jsx/useReducedMotion.js.map +0 -6
  402. package/dist/jsx/useToastAnimations.js +0 -122
  403. package/dist/jsx/useToastAnimations.js.map +0 -6
  404. package/dist/jsx/v1.js +0 -17
  405. package/dist/jsx/v1.js.map +0 -6
  406. package/dist/jsx/v1.mjs +0 -3
  407. package/dist/jsx/v1.mjs.map +0 -1
  408. package/dist/jsx/v1.native.js +0 -34
  409. package/dist/jsx/v1.native.js.map +0 -1
  410. package/types/ToastItem.d.ts.map +0 -1
  411. package/types/useDragGesture.d.ts.map +0 -1
  412. package/types/useDragGesture.native.d.ts.map +0 -1
  413. package/types/v1.d.ts.map +0 -1
@@ -0,0 +1,1380 @@
1
+ import { AnimatePresence } from '@tamagui/animate-presence'
2
+ import { isWeb } from '@tamagui/constants'
3
+ import { getGestureHandler } from '@tamagui/native'
4
+ import type { GetProps, TamaguiElement } from '@tamagui/core'
5
+ import {
6
+ createStyledContext,
7
+ styled,
8
+ Theme,
9
+ useConfiguration,
10
+ useEvent,
11
+ useThemeName,
12
+ View,
13
+ } from '@tamagui/core'
14
+ import { withStaticProperties } from '@tamagui/helpers'
15
+ import { Portal } from '@tamagui/portal'
16
+ import { XStack, YStack } from '@tamagui/stacks'
17
+ import { SizableText } from '@tamagui/text'
18
+ import * as React from 'react'
19
+ import type { SwipeDirection } from './ToastProvider'
20
+ import type { ExternalToast, ToastT, ToastToDismiss, ToastType } from './ToastState'
21
+ import { ToastState } from './ToastState'
22
+ import type { BurntToastOptions } from './types'
23
+ import { dispatchNativeToast } from './dispatchNativeToast'
24
+ import { useAnimatedDragGesture } from './useAnimatedDragGesture'
25
+ import { useToastAnimations } from './useToastAnimations'
26
+ import { useReducedMotion } from './useReducedMotion'
27
+ import {
28
+ DefaultCloseIcon,
29
+ ToastActionFrame,
30
+ ToastCloseFrame,
31
+ ToastItemFrame,
32
+ ToastPositionWrapper,
33
+ } from './ToastItemFrame'
34
+
35
+ // defaults
36
+ const VISIBLE_TOASTS_AMOUNT = 4
37
+ const VIEWPORT_OFFSET = 16
38
+ const TOAST_GAP = 14
39
+ const TOAST_LIFETIME = 4000
40
+ const FIXED_TOAST_HEIGHT = 72
41
+ const TIME_BEFORE_UNMOUNT = 200
42
+ const DEFAULT_HOTKEY: string[] = ['altKey', 'KeyT']
43
+
44
+ export type ToastPosition =
45
+ | 'top-left'
46
+ | 'top-center'
47
+ | 'top-right'
48
+ | 'bottom-left'
49
+ | 'bottom-center'
50
+ | 'bottom-right'
51
+
52
+ /* -------------------------------------------------------------------------------------------------
53
+ * Context
54
+ * -----------------------------------------------------------------------------------------------*/
55
+
56
+ // Map of toastId -> height (keyed storage prevents ordering drift)
57
+ type HeightsMap = Record<string | number, number>
58
+
59
+ interface ToastContextValue {
60
+ toasts: ToastT[]
61
+ heights: HeightsMap
62
+ setToastHeight: (toastId: string | number, height: number) => void
63
+ removeToastHeight: (toastId: string | number) => void
64
+ expanded: boolean
65
+ setExpanded: React.Dispatch<React.SetStateAction<boolean>>
66
+ interacting: boolean
67
+ setInteracting: React.Dispatch<React.SetStateAction<boolean>>
68
+ /** Trigger cooldown period after dismiss - prevents collapse during stack rebalance */
69
+ triggerDismissCooldown: () => void
70
+ /** Check if currently in dismiss cooldown */
71
+ isInDismissCooldown: () => boolean
72
+ removeToast: (toast: ToastT) => void
73
+ position: ToastPosition
74
+ duration: number
75
+ gap: number
76
+ visibleToasts: number
77
+ swipeDirection: SwipeDirection
78
+ swipeThreshold: number
79
+ closeButton: boolean
80
+ reducedMotion: boolean
81
+ toastHeight: number
82
+ native: boolean
83
+ burntOptions?: Omit<BurntToastOptions, 'title' | 'message' | 'duration'>
84
+ notificationOptions?: NotificationOptions
85
+ icons?: ToastIcons
86
+ }
87
+
88
+ const ToastContext = createStyledContext<ToastContextValue>(
89
+ {} as ToastContextValue,
90
+ 'Toast__'
91
+ )
92
+
93
+ const useToastContext = ToastContext.useStyledContext
94
+
95
+ /* -------------------------------------------------------------------------------------------------
96
+ * ToastItemContext - for auto-wiring Toast.Close (web only)
97
+ * -----------------------------------------------------------------------------------------------*/
98
+
99
+ interface ToastItemContextValue {
100
+ toast: ToastT
101
+ handleClose: () => void
102
+ }
103
+
104
+ const ToastItemContext = React.createContext<ToastItemContextValue | null>(null)
105
+
106
+ function useToastItemContext() {
107
+ const ctx = React.useContext(ToastItemContext)
108
+ if (!ctx) {
109
+ throw new Error('useToastItemContext must be used within Toast.Item or Toast.List')
110
+ }
111
+ return ctx
112
+ }
113
+
114
+ /* -------------------------------------------------------------------------------------------------
115
+ * Icons
116
+ * -----------------------------------------------------------------------------------------------*/
117
+
118
+ export interface ToastIcons {
119
+ success?: React.ReactNode
120
+ error?: React.ReactNode
121
+ warning?: React.ReactNode
122
+ info?: React.ReactNode
123
+ loading?: React.ReactNode
124
+ close?: React.ReactNode
125
+ }
126
+
127
+ /* Icons - users provide their own via icons prop, no built-in defaults */
128
+
129
+ /* -------------------------------------------------------------------------------------------------
130
+ * Toast (Root)
131
+ * -----------------------------------------------------------------------------------------------*/
132
+
133
+ export interface ToastRootProps {
134
+ children: React.ReactNode
135
+ /**
136
+ * Position of the toasts on screen
137
+ * @default 'bottom-right'
138
+ */
139
+ position?: ToastPosition
140
+ /**
141
+ * Default duration for toasts in ms
142
+ * @default 4000
143
+ */
144
+ duration?: number
145
+ /**
146
+ * Gap between toasts in pixels
147
+ * @default 14
148
+ */
149
+ gap?: number
150
+ /**
151
+ * Number of toasts visible at once
152
+ * @default 4
153
+ */
154
+ visibleToasts?: number
155
+ /**
156
+ * Direction toasts can be swiped to dismiss
157
+ * @default 'auto'
158
+ */
159
+ swipeDirection?: SwipeDirection
160
+ /**
161
+ * Distance in pixels swipe must pass to dismiss
162
+ * @default 50
163
+ */
164
+ swipeThreshold?: number
165
+ /**
166
+ * Fixed toast height in pixels for native stacking calculations.
167
+ * On web, heights are measured dynamically.
168
+ * @default 56
169
+ */
170
+ toastHeight?: number
171
+ /**
172
+ * Show close button on toasts
173
+ * @default false
174
+ */
175
+ closeButton?: boolean
176
+ /**
177
+ * When true, toasts are always expanded (fanned out) instead of stacked.
178
+ * @default false
179
+ */
180
+ expand?: boolean
181
+ /**
182
+ * Theme for toasts
183
+ */
184
+ theme?: 'light' | 'dark' | 'system'
185
+ /**
186
+ * Force reduced motion mode
187
+ */
188
+ reducedMotion?: boolean
189
+ /**
190
+ * When true, uses burnt native OS toasts on mobile instead of RN views.
191
+ * @default false
192
+ */
193
+ native?: boolean
194
+ /**
195
+ * Options for burnt native toasts on mobile
196
+ */
197
+ burntOptions?: Omit<BurntToastOptions, 'title' | 'message' | 'duration'>
198
+ /**
199
+ * Options for web Notification API when native is true on web
200
+ */
201
+ notificationOptions?: NotificationOptions
202
+ /**
203
+ * Custom icons for toast types
204
+ */
205
+ icons?: ToastIcons
206
+ }
207
+
208
+ function resolveSwipeDirection(
209
+ direction: SwipeDirection,
210
+ position: ToastPosition
211
+ ): Exclude<SwipeDirection, 'auto'> {
212
+ if (direction !== 'auto') return direction
213
+ const [yPosition, xPosition] = position.split('-') as [
214
+ 'top' | 'bottom',
215
+ 'left' | 'center' | 'right',
216
+ ]
217
+ if (!isWeb) {
218
+ // on native, always use vertical swipe to avoid conflicting with
219
+ // iOS/Android navigation back gesture (horizontal edge swipe)
220
+ return yPosition === 'top' ? 'up' : 'down'
221
+ }
222
+ if (xPosition === 'left') return 'left'
223
+ if (xPosition === 'right') return 'right'
224
+ // center positions: horizontal swipe feels most natural
225
+ return 'horizontal'
226
+ }
227
+
228
+ const ToastRoot = React.forwardRef<TamaguiElement, ToastRootProps>(
229
+ function ToastRoot(props, _ref) {
230
+ const {
231
+ children,
232
+ position = 'bottom-right',
233
+ duration = TOAST_LIFETIME,
234
+ gap = TOAST_GAP,
235
+ visibleToasts = VISIBLE_TOASTS_AMOUNT,
236
+ swipeDirection: swipeDirectionProp = 'auto',
237
+ swipeThreshold = 50,
238
+ toastHeight = FIXED_TOAST_HEIGHT,
239
+ closeButton = false,
240
+ expand = false,
241
+ theme: themeProp,
242
+ reducedMotion: reducedMotionProp,
243
+ native = false,
244
+ burntOptions,
245
+ notificationOptions,
246
+ icons,
247
+ } = props
248
+
249
+ const reducedMotion = useReducedMotion(reducedMotionProp)
250
+ const [toasts, setToasts] = React.useState<ToastT[]>([])
251
+ const [heights, setHeights] = React.useState<HeightsMap>({})
252
+ const [localExpanded, setExpanded] = React.useState(false)
253
+ const expanded = expand || localExpanded
254
+ const [interacting, setInteracting] = React.useState(false)
255
+
256
+ // Lock height updates during expand/collapse CSS transition to prevent
257
+ // font-loading onLayout corrections from restarting the animation mid-flight.
258
+ // useLayoutEffect fires before paint, so the lock is set before any onLayout callbacks.
259
+ const heightsLockedRef = React.useRef(false)
260
+ const prevExpandedRef = React.useRef(expanded)
261
+
262
+ React.useLayoutEffect(() => {
263
+ if (prevExpandedRef.current !== expanded) {
264
+ heightsLockedRef.current = true
265
+ prevExpandedRef.current = expanded
266
+ }
267
+ const timer = setTimeout(() => {
268
+ heightsLockedRef.current = false
269
+ }, 350)
270
+ return () => clearTimeout(timer)
271
+ }, [expanded])
272
+
273
+ // Round + skip small changes to prevent cascading re-renders from
274
+ // sub-pixel onLayout jitter during font loading or CSS transitions
275
+ const setToastHeight = React.useCallback(
276
+ (toastId: string | number, height: number) => {
277
+ if (heightsLockedRef.current) return
278
+ const rounded = Math.round(height)
279
+ setHeights((prev) => {
280
+ const existing = prev[toastId]
281
+ if (existing != null && Math.abs(existing - rounded) <= 2) return prev
282
+ return { ...prev, [toastId]: rounded }
283
+ })
284
+ },
285
+ []
286
+ )
287
+
288
+ const removeToastHeight = React.useCallback((toastId: string | number) => {
289
+ setHeights((prev) => {
290
+ if (!(toastId in prev)) return prev
291
+ const next = { ...prev }
292
+ delete next[toastId]
293
+ return next
294
+ })
295
+ }, [])
296
+
297
+ // Cooldown after dismiss - prevents collapse while stack rebalances
298
+ const dismissCooldownRef = React.useRef(false)
299
+ const dismissCooldownTimerRef = React.useRef<ReturnType<typeof setTimeout> | null>(
300
+ null
301
+ )
302
+
303
+ const triggerDismissCooldown = React.useCallback(() => {
304
+ dismissCooldownRef.current = true
305
+ if (dismissCooldownTimerRef.current) {
306
+ clearTimeout(dismissCooldownTimerRef.current)
307
+ }
308
+ dismissCooldownTimerRef.current = setTimeout(() => {
309
+ dismissCooldownRef.current = false
310
+ }, 800)
311
+ }, [])
312
+
313
+ const isInDismissCooldown = React.useCallback(() => dismissCooldownRef.current, [])
314
+
315
+ // Store object props in refs so the subscription effect doesn't
316
+ // re-subscribe on every render when consumers pass inline objects.
317
+ const burntOptionsRef = React.useRef(burntOptions)
318
+ const notificationOptionsRef = React.useRef(notificationOptions)
319
+ React.useEffect(() => {
320
+ burntOptionsRef.current = burntOptions
321
+ }, [burntOptions])
322
+ React.useEffect(() => {
323
+ notificationOptionsRef.current = notificationOptions
324
+ }, [notificationOptions])
325
+
326
+ // subscribe to toast state
327
+ React.useEffect(() => {
328
+ return ToastState.subscribe((toast) => {
329
+ if ((toast as ToastToDismiss).dismiss) {
330
+ setToasts((toasts) =>
331
+ toasts.map((t) => (t.id === toast.id ? { ...t, delete: true } : t))
332
+ )
333
+ return
334
+ }
335
+
336
+ // Native dispatch: intercept before entering state so no in-app toast renders.
337
+ // On failure (e.g. permission denied), falls through to in-app.
338
+ if (native) {
339
+ const handled = dispatchNativeToast(toast as ToastT, {
340
+ duration,
341
+ burntOptions: burntOptionsRef.current,
342
+ notificationOptions: notificationOptionsRef.current,
343
+ })
344
+ if (handled) return
345
+ }
346
+
347
+ setToasts((toasts) => {
348
+ const idx = toasts.findIndex((t) => t.id === toast.id)
349
+ if (idx !== -1) {
350
+ return [
351
+ ...toasts.slice(0, idx),
352
+ { ...toasts[idx], ...toast },
353
+ ...toasts.slice(idx + 1),
354
+ ]
355
+ }
356
+ return [toast as ToastT, ...toasts]
357
+ })
358
+ })
359
+ }, [native, duration])
360
+
361
+ // collapse when 1 toast left, or when a new toast is added while expanded
362
+ const prevToastCountRef = React.useRef(toasts.length)
363
+ React.useEffect(() => {
364
+ const prevCount = prevToastCountRef.current
365
+ prevToastCountRef.current = toasts.length
366
+
367
+ if (toasts.length <= 1 && !dismissCooldownRef.current) {
368
+ setExpanded(false)
369
+ } else if (toasts.length > prevCount && expanded) {
370
+ // new toast added while expanded — collapse to show the new front toast
371
+ setExpanded(false)
372
+ }
373
+ }, [toasts.length, expanded])
374
+
375
+ const removeToast = React.useCallback((toastToRemove: ToastT) => {
376
+ setToasts((toasts) => {
377
+ if (!toasts.find((t) => t.id === toastToRemove.id)?.delete) {
378
+ ToastState.dismiss(toastToRemove.id)
379
+ }
380
+ return toasts.filter(({ id }) => id !== toastToRemove.id)
381
+ })
382
+ }, [])
383
+
384
+ const swipeDirection = resolveSwipeDirection(swipeDirectionProp, position)
385
+
386
+ const currentTheme = useThemeName()
387
+ const resolvedTheme =
388
+ themeProp === 'system' || !themeProp
389
+ ? currentTheme?.includes('dark')
390
+ ? 'dark'
391
+ : 'light'
392
+ : themeProp
393
+
394
+ const contextValue: ToastContextValue = {
395
+ toasts,
396
+ heights,
397
+ setToastHeight,
398
+ removeToastHeight,
399
+ expanded,
400
+ setExpanded,
401
+ interacting,
402
+ setInteracting,
403
+ triggerDismissCooldown,
404
+ isInDismissCooldown,
405
+ removeToast,
406
+ position,
407
+ duration,
408
+ gap,
409
+ visibleToasts,
410
+ swipeDirection,
411
+ swipeThreshold,
412
+ toastHeight,
413
+ closeButton,
414
+ reducedMotion,
415
+ native,
416
+ burntOptions,
417
+ notificationOptions,
418
+ icons,
419
+ }
420
+
421
+ return (
422
+ <ToastContext.Provider {...contextValue}>
423
+ <Theme name={resolvedTheme as any}>{children}</Theme>
424
+ </ToastContext.Provider>
425
+ )
426
+ }
427
+ )
428
+
429
+ /* -------------------------------------------------------------------------------------------------
430
+ * ToastViewport
431
+ * -----------------------------------------------------------------------------------------------*/
432
+
433
+ const ToastViewportFrame = styled(View, {
434
+ name: 'ToastViewport',
435
+
436
+ variants: {
437
+ unstyled: {
438
+ false: {
439
+ position: isWeb ? ('fixed' as any) : 'absolute',
440
+ zIndex: 100000,
441
+ pointerEvents: 'box-none',
442
+ maxWidth: '100%',
443
+ ...(isWeb && { width: 356 }),
444
+ minHeight: 1,
445
+ },
446
+ },
447
+ } as const,
448
+
449
+ defaultVariants: {
450
+ unstyled: process.env.TAMAGUI_HEADLESS === '1',
451
+ },
452
+ })
453
+
454
+ export interface ToastViewportProps extends GetProps<typeof ToastViewportFrame> {
455
+ /**
456
+ * Offset from screen edge
457
+ * @default 24
458
+ */
459
+ offset?: number | { top?: number; right?: number; bottom?: number; left?: number }
460
+ /**
461
+ * Hotkey to focus viewport
462
+ */
463
+ hotkey?: string[]
464
+ /**
465
+ * Aria label
466
+ * @default 'Notifications'
467
+ */
468
+ label?: string
469
+ /**
470
+ * Portal to root
471
+ * @default true
472
+ */
473
+ portalToRoot?: boolean
474
+ /**
475
+ * z-index for the portal container when portalToRoot is true
476
+ * @default Number.MAX_SAFE_INTEGER
477
+ */
478
+ portalZIndex?: number
479
+ }
480
+
481
+ const ToastViewport = ToastViewportFrame.styleable<ToastViewportProps>(
482
+ function ToastViewport(props, ref) {
483
+ const {
484
+ offset = VIEWPORT_OFFSET,
485
+ hotkey = DEFAULT_HOTKEY,
486
+ label = 'Notifications',
487
+ portalToRoot = true,
488
+ portalZIndex = Number.MAX_SAFE_INTEGER,
489
+ children,
490
+ ...rest
491
+ } = props
492
+
493
+ const ctx = useToastContext()
494
+ const listRef = React.useRef<TamaguiElement>(null)
495
+ const hoverTimeoutRef = React.useRef<ReturnType<typeof setTimeout> | null>(null)
496
+ const hoverCooldownRef = React.useRef(false)
497
+ const deferredCollapseRef = React.useRef<ReturnType<typeof setTimeout> | null>(null)
498
+ const mouseInsideRef = React.useRef(false)
499
+
500
+ const [yPosition, xPosition] = ctx.position.split('-') as [
501
+ 'top' | 'bottom',
502
+ 'left' | 'center' | 'right',
503
+ ]
504
+
505
+ // offset styles
506
+ // on native, get safe area insets to avoid status bar / Dynamic Island / home indicator
507
+ // use insets from TamaguiProvider (passed via useConfiguration)
508
+ // same pattern as Slider — works on native when TamaguiProvider has insets prop
509
+ const { insets: safeInsets } = useConfiguration()
510
+
511
+ const offsetStyles = React.useMemo(() => {
512
+ const styles: any = {}
513
+ const defaultOffset = typeof offset === 'number' ? offset : VIEWPORT_OFFSET
514
+ const offsetObj =
515
+ typeof offset === 'object'
516
+ ? offset
517
+ : {
518
+ top: defaultOffset,
519
+ right: defaultOffset,
520
+ bottom: defaultOffset,
521
+ left: defaultOffset,
522
+ }
523
+
524
+ const safeTop = safeInsets?.top ?? 0
525
+ const safeBottom = safeInsets?.bottom ?? 0
526
+
527
+ // if safe area already provides spacing, skip the offset to avoid double padding
528
+ const topOffset = safeTop > 0 ? safeTop : (offsetObj.top ?? defaultOffset)
529
+ const bottomOffset =
530
+ safeBottom > 0 ? safeBottom : (offsetObj.bottom ?? defaultOffset)
531
+
532
+ if (yPosition === 'top') styles.top = topOffset
533
+ else styles.bottom = bottomOffset
534
+
535
+ if (isWeb) {
536
+ if (xPosition === 'left') styles.left = offsetObj.left ?? defaultOffset
537
+ else if (xPosition === 'right') styles.right = offsetObj.right ?? defaultOffset
538
+ else {
539
+ styles.left = '50%'
540
+ styles.transform = 'translateX(-50%)'
541
+ }
542
+ } else {
543
+ // native: always set both left + right so viewport fills screen
544
+ // (no fixed width on native — left/right offsets define the width)
545
+ styles.left = offsetObj.left ?? defaultOffset
546
+ styles.right = offsetObj.right ?? defaultOffset
547
+ }
548
+
549
+ return styles
550
+ }, [offset, yPosition, xPosition])
551
+
552
+ // hotkey
553
+ React.useEffect(() => {
554
+ if (!isWeb) return
555
+ const handleKeyDown = (event: KeyboardEvent) => {
556
+ const isHotkeyPressed =
557
+ hotkey.length > 0 &&
558
+ hotkey.every((key) => (event as any)[key] || event.code === key)
559
+ if (isHotkeyPressed) {
560
+ ctx.setExpanded(true)
561
+ ;(listRef.current as HTMLElement)?.focus()
562
+ }
563
+ // Escape is handled by individual toast items via onKeyDown
564
+ // which dismisses the focused toast with cooldown (keeps stack expanded)
565
+ }
566
+ document.addEventListener('keydown', handleKeyDown)
567
+ return () => document.removeEventListener('keydown', handleKeyDown)
568
+ }, [hotkey])
569
+
570
+ if (ctx.toasts.length === 0) return null
571
+
572
+ const hotkeyLabel = hotkey.join('+').replace(/Key/g, '').replace(/Digit/g, '')
573
+
574
+ const content = (
575
+ <ToastViewportFrame
576
+ ref={listRef}
577
+ aria-label={`${label} ${hotkeyLabel}`}
578
+ tabIndex={-1}
579
+ aria-live="polite"
580
+ style={offsetStyles}
581
+ data-y-position={yPosition}
582
+ data-x-position={xPosition}
583
+ {...(isWeb
584
+ ? {
585
+ onMouseEnter: () => {
586
+ mouseInsideRef.current = true
587
+ if (deferredCollapseRef.current) {
588
+ clearTimeout(deferredCollapseRef.current)
589
+ deferredCollapseRef.current = null
590
+ }
591
+ if (
592
+ ctx.toasts.length > 1 &&
593
+ !ctx.interacting &&
594
+ !hoverCooldownRef.current
595
+ ) {
596
+ hoverTimeoutRef.current = setTimeout(() => ctx.setExpanded(true), 50)
597
+ }
598
+ },
599
+ onMouseLeave: () => {
600
+ mouseInsideRef.current = false
601
+ if (hoverTimeoutRef.current) {
602
+ clearTimeout(hoverTimeoutRef.current)
603
+ hoverTimeoutRef.current = null
604
+ }
605
+ if (!ctx.interacting && !ctx.isInDismissCooldown()) {
606
+ ctx.setExpanded(false)
607
+ } else if (ctx.isInDismissCooldown()) {
608
+ // During dismiss cooldown, defer collapse until well after
609
+ // the exit animation completes to prevent mid-animation bounce.
610
+ // The cooldown is 800ms, spring exit is ~500ms — 1200ms covers both.
611
+ if (deferredCollapseRef.current) {
612
+ clearTimeout(deferredCollapseRef.current)
613
+ }
614
+ deferredCollapseRef.current = setTimeout(() => {
615
+ deferredCollapseRef.current = null
616
+ if (!mouseInsideRef.current) {
617
+ ctx.setExpanded(false)
618
+ }
619
+ }, 1200)
620
+ }
621
+ },
622
+ onPointerDown: () => {
623
+ if (hoverTimeoutRef.current) {
624
+ clearTimeout(hoverTimeoutRef.current)
625
+ hoverTimeoutRef.current = null
626
+ }
627
+ ctx.setInteracting(true)
628
+ },
629
+ onPointerUp: () => ctx.setInteracting(false),
630
+ onPointerCancel: () => ctx.setInteracting(false),
631
+ }
632
+ : {
633
+ onPress: () => {
634
+ if (ctx.toasts.length > 1) {
635
+ ctx.setExpanded((prev) => !prev)
636
+ }
637
+ },
638
+ })}
639
+ {...(isWeb && {
640
+ onFocus: (event: React.FocusEvent) => {
641
+ // keyboard focus entered — expand stack and pause timers
642
+ if (
643
+ !(event.currentTarget as HTMLElement).contains(
644
+ event.relatedTarget as HTMLElement
645
+ )
646
+ ) {
647
+ if (ctx.toasts.length > 1) {
648
+ ctx.setExpanded(true)
649
+ }
650
+ ctx.setInteracting(true)
651
+ }
652
+ },
653
+ onBlur: (event: React.FocusEvent) => {
654
+ // focus left the toaster — collapse and resume timers
655
+ if (
656
+ !(event.currentTarget as HTMLElement).contains(
657
+ event.relatedTarget as HTMLElement
658
+ )
659
+ ) {
660
+ ctx.setInteracting(false)
661
+ if (!ctx.isInDismissCooldown()) {
662
+ ctx.setExpanded(false)
663
+ }
664
+ }
665
+ },
666
+ })}
667
+ {...rest}
668
+ >
669
+ {children}
670
+ </ToastViewportFrame>
671
+ )
672
+
673
+ if (portalToRoot) {
674
+ return <Portal zIndex={portalZIndex}>{content}</Portal>
675
+ }
676
+
677
+ return content
678
+ }
679
+ )
680
+
681
+ /* -------------------------------------------------------------------------------------------------
682
+ * ToastList - handles iteration and AnimatePresence
683
+ * -----------------------------------------------------------------------------------------------*/
684
+
685
+ export interface ToastItemRenderProps {
686
+ toast: ToastT
687
+ index: number
688
+ handleClose: () => void
689
+ }
690
+
691
+ export interface ToastListProps {
692
+ /**
693
+ * Custom render function for each toast item
694
+ */
695
+ renderItem?: (props: ToastItemRenderProps) => React.ReactNode
696
+ }
697
+
698
+ function ToastList({ renderItem }: ToastListProps) {
699
+ const ctx = useToastContext()
700
+
701
+ // render all toasts — hidden ones have opacity 0 but stay mounted
702
+ // so they smoothly transition when visible toasts are dismissed
703
+ const maxRender = ctx.toasts.length
704
+
705
+ return (
706
+ <AnimatePresence>
707
+ {ctx.toasts.slice(0, maxRender).map((toast, index) => {
708
+ const handleClose = () => {
709
+ if (toast.dismissible === false) return
710
+ toast.onDismiss?.(toast)
711
+ ctx.removeToast(toast)
712
+ }
713
+
714
+ const itemContextValue: ToastItemContextValue = {
715
+ toast,
716
+ handleClose,
717
+ }
718
+
719
+ if (!renderItem) {
720
+ return (
721
+ <ToastItemContext.Provider key={toast.id} value={itemContextValue}>
722
+ <ToastItemInner toast={toast} index={index}>
723
+ <DefaultToastContent toast={toast} />
724
+ </ToastItemInner>
725
+ </ToastItemContext.Provider>
726
+ )
727
+ }
728
+
729
+ return (
730
+ <ToastItemContext.Provider key={toast.id} value={itemContextValue}>
731
+ {renderItem({ toast, index, handleClose })}
732
+ </ToastItemContext.Provider>
733
+ )
734
+ })}
735
+ </AnimatePresence>
736
+ )
737
+ }
738
+
739
+ /* -------------------------------------------------------------------------------------------------
740
+ * DefaultToastContent - default rendering for toast items
741
+ * -----------------------------------------------------------------------------------------------*/
742
+
743
+ function DefaultToastContent({ toast }: { toast: ToastT }) {
744
+ const ctx = useToastContext()
745
+ const { handleClose } = useToastItemContext()
746
+ const toastType = toast.type ?? 'default'
747
+ const dismissible = toast.dismissible !== false
748
+
749
+ const title = typeof toast.title === 'function' ? toast.title() : toast.title
750
+ const description =
751
+ typeof toast.description === 'function' ? toast.description() : toast.description
752
+
753
+ return (
754
+ <XStack alignItems="flex-start" gap="$3">
755
+ <ToastIcon />
756
+
757
+ <YStack flex={1} gap="$1">
758
+ {title && <ToastTitle>{title}</ToastTitle>}
759
+ {description && <ToastDescription>{description}</ToastDescription>}
760
+
761
+ {(toast.action || toast.cancel) && (
762
+ <XStack gap="$2" marginTop="$2">
763
+ {toast.cancel && (
764
+ <ToastActionFrame
765
+ backgroundColor="transparent"
766
+ onPress={(e: any) => {
767
+ toast.cancel?.onClick?.(e)
768
+ handleClose()
769
+ }}
770
+ >
771
+ <SizableText size="$2" color="$color11">
772
+ {toast.cancel.label}
773
+ </SizableText>
774
+ </ToastActionFrame>
775
+ )}
776
+ {toast.action && (
777
+ <ToastActionFrame
778
+ backgroundColor="$color12"
779
+ hoverStyle={{ backgroundColor: '$color11' }}
780
+ pressStyle={{ backgroundColor: '$color10' }}
781
+ onPress={(e: any) => {
782
+ toast.action?.onClick?.(e)
783
+ if (!(e as any).defaultPrevented) {
784
+ handleClose()
785
+ }
786
+ }}
787
+ >
788
+ <SizableText size="$2" fontWeight="600" color="$background">
789
+ {toast.action.label}
790
+ </SizableText>
791
+ </ToastActionFrame>
792
+ )}
793
+ </XStack>
794
+ )}
795
+ </YStack>
796
+
797
+ {ctx.closeButton && dismissible && <ToastClose />}
798
+ </XStack>
799
+ )
800
+ }
801
+
802
+ /* ToastPositionWrapper, ToastItemFrame imported from ./ToastItemFrame */
803
+
804
+ /* -------------------------------------------------------------------------------------------------
805
+ * DragWrapper - handles drag gestures with proper event handling
806
+ * -----------------------------------------------------------------------------------------------*/
807
+
808
+ interface DragWrapperProps {
809
+ animatedStyle: any
810
+ gestureHandlers: any
811
+ gesture: any
812
+ AnimatedView: any
813
+ dragRef: React.RefObject<HTMLDivElement | null>
814
+ children: React.ReactNode
815
+ }
816
+
817
+ function DragWrapper({
818
+ animatedStyle,
819
+ gestureHandlers,
820
+ gesture,
821
+ AnimatedView,
822
+ dragRef,
823
+ children,
824
+ }: DragWrapperProps) {
825
+ if (isWeb) {
826
+ return (
827
+ <div
828
+ ref={dragRef}
829
+ style={{
830
+ flex: 1,
831
+ display: 'flex',
832
+ flexDirection: 'column',
833
+ userSelect: 'none',
834
+ WebkitUserSelect: 'none',
835
+ touchAction: 'none',
836
+ cursor: 'default',
837
+ }}
838
+ {...gestureHandlers}
839
+ >
840
+ {children}
841
+ </div>
842
+ )
843
+ }
844
+
845
+ // when RNGH gesture is available, wrap with GestureDetector + plain View
846
+ // (GestureDetector needs a native View to attach handlers to)
847
+ if (gesture) {
848
+ const gh = getGestureHandler()
849
+ const GestureDetector = gh.state.GestureDetector
850
+ if (GestureDetector) {
851
+ return (
852
+ <GestureDetector gesture={gesture}>
853
+ <View style={{ flex: 1 }} {...({ collapsable: false } as any)}>
854
+ <AnimatedView style={[{ flex: 1 }, animatedStyle]}>{children}</AnimatedView>
855
+ </View>
856
+ </GestureDetector>
857
+ )
858
+ }
859
+ }
860
+
861
+ // fallback: PanResponder handlers
862
+ return (
863
+ <AnimatedView style={[{ flex: 1 }, animatedStyle]} {...gestureHandlers}>
864
+ {children}
865
+ </AnimatedView>
866
+ )
867
+ }
868
+
869
+ /* -------------------------------------------------------------------------------------------------
870
+ * ToastItem (the wrapper with stacking/drag)
871
+ * -----------------------------------------------------------------------------------------------*/
872
+
873
+ export interface ToastItemProps extends GetProps<typeof ToastItemFrame> {
874
+ toast: ToastT
875
+ index: number
876
+ children: React.ReactNode
877
+ }
878
+
879
+ const ToastItemInner = ToastItemFrame.styleable<ToastItemProps>(
880
+ function ToastItem(props, ref) {
881
+ const { toast, index, children, ...rest } = props
882
+ const ctx = useToastContext()
883
+
884
+ const [mounted, setMounted] = React.useState(false)
885
+ const [removed, setRemoved] = React.useState(false)
886
+ const [swipeOut, setSwipeOut] = React.useState(false)
887
+ // Freeze the Y offset when dismiss starts so the exiting toast doesn't jump
888
+ // as other toasts rebalance
889
+ const [offsetBeforeRemove, setOffsetBeforeRemove] = React.useState(0)
890
+ // Freeze stackY at swipe time — context re-renders recalculate stackY
891
+ // but the exiting toast should stay at its pre-removal position
892
+ const swipeExitYRef = React.useRef<number | null>(null)
893
+
894
+ const closeTimerRef = React.useRef<ReturnType<typeof setTimeout> | null>(null)
895
+ const closeTimerStartRef = React.useRef(0)
896
+ const lastPauseTimeRef = React.useRef(0)
897
+ const remainingTimeRef = React.useRef(toast.duration ?? ctx.duration)
898
+
899
+ const isFront = index === 0
900
+ const isVisible = index < ctx.visibleToasts
901
+ const toastType = toast.type ?? 'default'
902
+ const dismissible = toast.dismissible !== false
903
+ const duration = toast.duration ?? ctx.duration
904
+
905
+ const [yPosition] = ctx.position.split('-') as ['top' | 'bottom', string]
906
+ const isTop = yPosition === 'top'
907
+
908
+ // web: dynamic heights (CSS transitions run off main thread, no FPS concern)
909
+ // native: fixed height (avoids React state re-render cascade on JS thread)
910
+ const expandedOffset = isWeb
911
+ ? (() => {
912
+ let totalHeight = 0
913
+ let activeCount = 0
914
+ for (let i = 0; i < index; i++) {
915
+ const toastId = ctx.toasts[i]?.id
916
+ if (toastId == null) continue
917
+ const h = ctx.heights[toastId]
918
+ if (h === 0) continue
919
+ totalHeight += h ?? ctx.toastHeight
920
+ activeCount++
921
+ }
922
+ return totalHeight + activeCount * ctx.gap
923
+ })()
924
+ : index * (ctx.toastHeight + ctx.gap)
925
+
926
+ // Refs for stable access in callbacks — avoids putting expandedOffset/expanded
927
+ // in deps which would cause timer restarts on every height measurement
928
+ const expandedOffsetRef = React.useRef(expandedOffset)
929
+ expandedOffsetRef.current = expandedOffset
930
+ const isExpandedRef = React.useRef(ctx.expanded)
931
+ isExpandedRef.current = ctx.expanded
932
+
933
+ // timer — no height-zeroing needed here because auto-dismiss only fires
934
+ // when not expanded/interacting (timer is paused during hover)
935
+ const startTimer = React.useCallback(() => {
936
+ if (duration === Number.POSITIVE_INFINITY || toastType === 'loading') return
937
+ closeTimerStartRef.current = Date.now()
938
+ closeTimerRef.current = setTimeout(() => {
939
+ toast.onAutoClose?.(toast)
940
+ setRemoved(true)
941
+ setTimeout(() => ctx.removeToast(toast), TIME_BEFORE_UNMOUNT)
942
+ }, remainingTimeRef.current)
943
+ }, [duration, toastType, toast, ctx.removeToast])
944
+
945
+ const pauseTimer = useEvent(() => {
946
+ if (closeTimerRef.current) {
947
+ clearTimeout(closeTimerRef.current)
948
+ }
949
+ if (lastPauseTimeRef.current < closeTimerStartRef.current) {
950
+ const elapsed = Date.now() - closeTimerStartRef.current
951
+ remainingTimeRef.current = Math.max(0, remainingTimeRef.current - elapsed)
952
+ }
953
+ lastPauseTimeRef.current = Date.now()
954
+ })
955
+
956
+ const resumeTimer = useEvent(() => {
957
+ if (ctx.expanded || ctx.interacting) return
958
+ remainingTimeRef.current = duration
959
+ startTimer()
960
+ })
961
+
962
+ React.useEffect(() => {
963
+ setMounted(true)
964
+ }, [])
965
+
966
+ // handle deletion — only zero height when expanded (Sonner rebalance)
967
+ React.useEffect(() => {
968
+ if (toast.delete) {
969
+ setRemoved(true)
970
+ if (isExpandedRef.current) {
971
+ setOffsetBeforeRemove(expandedOffsetRef.current)
972
+ }
973
+ setTimeout(() => ctx.removeToast(toast), TIME_BEFORE_UNMOUNT)
974
+ }
975
+ }, [toast.delete, toast, ctx.removeToast])
976
+
977
+ React.useEffect(() => {
978
+ // all toasts have independent timers (same as Sonner)
979
+ // stagger comes from creation time differences
980
+ if (ctx.expanded || ctx.interacting) {
981
+ pauseTimer()
982
+ } else {
983
+ startTimer()
984
+ }
985
+ return () => {
986
+ if (closeTimerRef.current) clearTimeout(closeTimerRef.current)
987
+ }
988
+ }, [ctx.expanded, ctx.interacting, startTimer])
989
+
990
+ // reset remaining time when duration changes
991
+ React.useEffect(() => {
992
+ remainingTimeRef.current = duration
993
+ }, [duration])
994
+
995
+ // animations
996
+ const {
997
+ setDragOffset,
998
+ springBack,
999
+ animateOut,
1000
+ animatedStyle,
1001
+ AnimatedView,
1002
+ dragRef,
1003
+ } = useToastAnimations({
1004
+ reducedMotion: ctx.reducedMotion,
1005
+ swipeAxis:
1006
+ ctx.swipeDirection === 'up' ||
1007
+ ctx.swipeDirection === 'down' ||
1008
+ ctx.swipeDirection === 'vertical'
1009
+ ? 'vertical'
1010
+ : 'horizontal',
1011
+ })
1012
+
1013
+ const { isDragging, gestureHandlers, gesture } = useAnimatedDragGesture({
1014
+ direction: ctx.swipeDirection,
1015
+ threshold: ctx.swipeThreshold,
1016
+ disabled: !dismissible || toastType === 'loading',
1017
+ expanded: ctx.expanded,
1018
+ onDragStart: pauseTimer,
1019
+ onDragMove: setDragOffset,
1020
+ onDismiss: (exitDirection, velocity) => {
1021
+ // Trigger cooldown to prevent collapse while stack rebalances
1022
+ ctx.triggerDismissCooldown()
1023
+ setSwipeOut(true)
1024
+ toast.onDismiss?.(toast)
1025
+ // freeze stackY at swipe time — after removeToast, context re-renders
1026
+ // recalculate expandedOffset with the wrong toast array
1027
+ swipeExitYRef.current = isExpandedRef.current
1028
+ ? isTop
1029
+ ? expandedOffsetRef.current
1030
+ : -expandedOffsetRef.current
1031
+ : isFront
1032
+ ? 0
1033
+ : isTop
1034
+ ? ctx.gap * index
1035
+ : -ctx.gap * index
1036
+ setRemoved(true)
1037
+ ctx.removeToast(toast)
1038
+ animateOut(exitDirection, velocity)
1039
+ },
1040
+ onCancel: () => {
1041
+ springBack(() => {
1042
+ resumeTimer()
1043
+ })
1044
+ },
1045
+ })
1046
+
1047
+ // measure height (web only — native uses fixed height)
1048
+ const handleLayout = React.useCallback(
1049
+ (event: any) => {
1050
+ if (!isWeb) return
1051
+ if (removed) return
1052
+ if (!ctx.expanded && index !== 0) return
1053
+ const { height } = event.nativeEvent.layout
1054
+ ctx.setToastHeight(toast.id, height)
1055
+ },
1056
+ [toast.id, ctx.setToastHeight, index, ctx.expanded, removed]
1057
+ )
1058
+
1059
+ // remove height on unmount (web only)
1060
+ React.useEffect(() => {
1061
+ if (!isWeb) return
1062
+ return () => {
1063
+ ctx.removeToastHeight(toast.id)
1064
+ }
1065
+ }, [toast.id, ctx.removeToastHeight])
1066
+
1067
+ const handleClose = React.useCallback(() => {
1068
+ if (!dismissible) return
1069
+ ctx.triggerDismissCooldown()
1070
+ toast.onDismiss?.(toast)
1071
+ setRemoved(true)
1072
+ if (isExpandedRef.current) {
1073
+ setOffsetBeforeRemove(expandedOffsetRef.current)
1074
+ }
1075
+ setTimeout(() => ctx.removeToast(toast), TIME_BEFORE_UNMOUNT)
1076
+ }, [dismissible, toast, ctx.removeToast, ctx.triggerDismissCooldown])
1077
+
1078
+ const itemContextValue = React.useMemo<ToastItemContextValue>(
1079
+ () => ({ toast, handleClose }),
1080
+ [toast, handleClose]
1081
+ )
1082
+
1083
+ // front toast height for collapsed stacking (web only)
1084
+ let frontToastHeight = -1
1085
+ if (isWeb) {
1086
+ for (const t of ctx.toasts) {
1087
+ const h = ctx.heights[t.id]
1088
+ if (h != null && h > 0) {
1089
+ frontToastHeight = h
1090
+ break
1091
+ }
1092
+ }
1093
+ }
1094
+
1095
+ const stackScale = !ctx.expanded && !isFront ? 1 - index * 0.05 : 1
1096
+
1097
+ // When removed, freeze Y at the saved offset so the exiting toast doesn't jump
1098
+ // as other toasts rebalance (Sonner: --offset uses offsetBeforeRemove when removed)
1099
+ const activeExpandedOffset = removed ? offsetBeforeRemove : expandedOffset
1100
+
1101
+ const stackY = ctx.expanded
1102
+ ? isTop
1103
+ ? activeExpandedOffset
1104
+ : -activeExpandedOffset
1105
+ : isFront
1106
+ ? 0
1107
+ : isTop
1108
+ ? ctx.gap * index
1109
+ : -ctx.gap * index
1110
+
1111
+ const computedOpacity = removed && !swipeOut ? 0 : index >= ctx.visibleToasts ? 0 : 1
1112
+ const computedZIndex = removed ? 0 : ctx.visibleToasts - index + 1
1113
+ // web: use measured height for smooth expand/collapse transitions
1114
+ // native: fixed height, no constraint needed
1115
+ const computedHeight = isWeb
1116
+ ? ctx.expanded
1117
+ ? ctx.heights[toast.id] || undefined
1118
+ : !isFront && frontToastHeight > 0
1119
+ ? frontToastHeight
1120
+ : undefined
1121
+ : undefined
1122
+ const computedPointerEvents = index >= ctx.visibleToasts ? 'none' : 'auto'
1123
+
1124
+ // gap filler for hover stability
1125
+ const gapFillerHeight = ctx.expanded ? ctx.gap + 1 : 0
1126
+
1127
+ // data attributes
1128
+ const dataAttributes = {
1129
+ 'data-mounted': mounted ? 'true' : 'false',
1130
+ 'data-removed': removed ? 'true' : 'false',
1131
+ 'data-swipe-out': swipeOut ? 'true' : 'false',
1132
+ 'data-visible': isVisible ? 'true' : 'false',
1133
+ 'data-front': isFront ? 'true' : 'false',
1134
+ 'data-index': String(index),
1135
+ 'data-type': toastType,
1136
+ 'data-expanded': ctx.expanded ? 'true' : 'false',
1137
+ }
1138
+
1139
+ return (
1140
+ <ToastPositionWrapper
1141
+ ref={ref}
1142
+ testID={rest.testID}
1143
+ accessibilityLabel={rest.accessibilityLabel}
1144
+ {...dataAttributes}
1145
+ transition={
1146
+ isDragging || ctx.reducedMotion ? undefined : removed ? '200ms' : '400ms'
1147
+ }
1148
+ animateOnly={
1149
+ isWeb ? ['transform', 'opacity', 'height'] : ['transform', 'opacity']
1150
+ }
1151
+ y={stackY}
1152
+ scale={stackScale}
1153
+ opacity={computedOpacity}
1154
+ zIndex={computedZIndex}
1155
+ height={computedHeight}
1156
+ overflow="visible"
1157
+ pointerEvents={computedPointerEvents as any}
1158
+ top={isTop ? 0 : undefined}
1159
+ bottom={isTop ? undefined : 0}
1160
+ {...(isWeb &&
1161
+ !isFront && {
1162
+ style: { transformOrigin: isTop ? 'top center' : 'bottom center' },
1163
+ })}
1164
+ enterStyle={
1165
+ ctx.reducedMotion ? { opacity: 0 } : { opacity: 0, y: isTop ? -80 : 80 }
1166
+ }
1167
+ exitStyle={
1168
+ ctx.reducedMotion
1169
+ ? { opacity: 0 }
1170
+ : swipeOut
1171
+ ? { opacity: 0, y: swipeExitYRef.current ?? stackY, scale: stackScale }
1172
+ : { opacity: 0, y: stackY, scale: stackScale }
1173
+ }
1174
+ >
1175
+ <DragWrapper
1176
+ animatedStyle={animatedStyle}
1177
+ gestureHandlers={gestureHandlers}
1178
+ gesture={gesture}
1179
+ AnimatedView={AnimatedView}
1180
+ dragRef={dragRef}
1181
+ >
1182
+ <ToastItemFrame
1183
+ role="status"
1184
+ aria-live="polite"
1185
+ aria-atomic
1186
+ tabIndex={0}
1187
+ onLayout={handleLayout}
1188
+ {...(isWeb && {
1189
+ onKeyDown: (event: React.KeyboardEvent) => {
1190
+ if (event.key === 'Escape' && dismissible) {
1191
+ // move focus to the next toast before dismissing
1192
+ const current = event.currentTarget as HTMLElement
1193
+ const container = current.closest('[aria-label]') as HTMLElement
1194
+ if (container) {
1195
+ const focusables = container.querySelectorAll('[tabindex="0"]')
1196
+ const arr = Array.from(focusables)
1197
+ const idx = arr.indexOf(current)
1198
+ const next = arr[idx + 1] || arr[idx - 1]
1199
+ ;(next as HTMLElement)?.focus()
1200
+ }
1201
+ handleClose()
1202
+ }
1203
+ },
1204
+ })}
1205
+ {...rest}
1206
+ >
1207
+ {/* gap filler to prevent hover flicker */}
1208
+ {ctx.expanded && gapFillerHeight > 0 && (
1209
+ <View
1210
+ position="absolute"
1211
+ left={0}
1212
+ right={0}
1213
+ height={gapFillerHeight}
1214
+ pointerEvents="auto"
1215
+ {...(isTop ? { top: '100%' } : { bottom: '100%' })}
1216
+ />
1217
+ )}
1218
+ <ToastItemContext.Provider value={itemContextValue}>
1219
+ {children}
1220
+ </ToastItemContext.Provider>
1221
+ </ToastItemFrame>
1222
+ </DragWrapper>
1223
+ </ToastPositionWrapper>
1224
+ )
1225
+ }
1226
+ )
1227
+
1228
+ /* -------------------------------------------------------------------------------------------------
1229
+ * ToastTitle
1230
+ * -----------------------------------------------------------------------------------------------*/
1231
+
1232
+ const ToastTitle = styled(SizableText, {
1233
+ name: 'ToastTitle',
1234
+
1235
+ variants: {
1236
+ unstyled: {
1237
+ false: {
1238
+ color: '$color',
1239
+ fontWeight: '600',
1240
+ size: '$4',
1241
+ },
1242
+ },
1243
+ } as const,
1244
+
1245
+ defaultVariants: {
1246
+ unstyled: process.env.TAMAGUI_HEADLESS === '1',
1247
+ },
1248
+ })
1249
+
1250
+ /* -------------------------------------------------------------------------------------------------
1251
+ * ToastDescription
1252
+ * -----------------------------------------------------------------------------------------------*/
1253
+
1254
+ const ToastDescription = styled(SizableText, {
1255
+ name: 'ToastDescription',
1256
+
1257
+ variants: {
1258
+ unstyled: {
1259
+ false: {
1260
+ color: '$color11',
1261
+ size: '$2',
1262
+ },
1263
+ },
1264
+ } as const,
1265
+
1266
+ defaultVariants: {
1267
+ unstyled: process.env.TAMAGUI_HEADLESS === '1',
1268
+ },
1269
+ })
1270
+
1271
+ /* -------------------------------------------------------------------------------------------------
1272
+ * ToastClose - auto-wired to dismiss current toast
1273
+ * -----------------------------------------------------------------------------------------------*/
1274
+
1275
+ const ToastClose = ToastCloseFrame.styleable(function ToastClose(props, ref) {
1276
+ // try to get handleClose from context, but allow manual override
1277
+ let handleClose: (() => void) | undefined
1278
+ try {
1279
+ const itemCtx = useToastItemContext()
1280
+ handleClose = itemCtx.handleClose
1281
+ } catch {
1282
+ // not inside a Toast.Item context, require manual onPress
1283
+ }
1284
+
1285
+ const ctx = useToastContext()
1286
+
1287
+ return (
1288
+ <ToastCloseFrame ref={ref} aria-label="Close toast" onPress={handleClose} {...props}>
1289
+ {props.children ?? ctx.icons?.close ?? <DefaultCloseIcon />}
1290
+ </ToastCloseFrame>
1291
+ )
1292
+ })
1293
+
1294
+ /* -------------------------------------------------------------------------------------------------
1295
+ * ToastAction
1296
+ * -----------------------------------------------------------------------------------------------*/
1297
+
1298
+ const ToastAction = ToastActionFrame.styleable(function ToastAction(props, ref) {
1299
+ return <ToastActionFrame ref={ref} {...props} />
1300
+ })
1301
+
1302
+ /* -------------------------------------------------------------------------------------------------
1303
+ * ToastIcon - renders icon based on toast type
1304
+ * -----------------------------------------------------------------------------------------------*/
1305
+
1306
+ function ToastIcon(props: { children?: React.ReactNode }) {
1307
+ const ctx = useToastContext()
1308
+ let toast: ToastT | undefined
1309
+
1310
+ try {
1311
+ const itemCtx = useToastItemContext()
1312
+ toast = itemCtx.toast
1313
+ } catch {
1314
+ // not inside a Toast.Item context
1315
+ return null
1316
+ }
1317
+
1318
+ if (!toast) return null
1319
+
1320
+ // if custom icon provided on toast, use it
1321
+ if (toast.icon !== undefined) {
1322
+ return (
1323
+ <View flexShrink={0} marginTop="$0.5">
1324
+ {toast.icon}
1325
+ </View>
1326
+ )
1327
+ }
1328
+
1329
+ const toastType = toast.type ?? 'default'
1330
+
1331
+ // only show icons if explicitly provided via icons prop (no built-in defaults)
1332
+ const icon = ctx.icons?.[toastType] ?? null
1333
+ if (!icon) return null
1334
+
1335
+ return (
1336
+ <View flexShrink={0} marginTop="$0.5">
1337
+ {icon}
1338
+ </View>
1339
+ )
1340
+ }
1341
+
1342
+ /* -------------------------------------------------------------------------------------------------
1343
+ * useToasts hook for rendering
1344
+ * -----------------------------------------------------------------------------------------------*/
1345
+
1346
+ export function useToasts() {
1347
+ const ctx = useToastContext()
1348
+ return {
1349
+ toasts: ctx.toasts,
1350
+ expanded: ctx.expanded,
1351
+ position: ctx.position,
1352
+ }
1353
+ }
1354
+
1355
+ /* -------------------------------------------------------------------------------------------------
1356
+ * useToastItem hook for accessing current toast in custom content
1357
+ * -----------------------------------------------------------------------------------------------*/
1358
+
1359
+ export function useToastItem() {
1360
+ return useToastItemContext()
1361
+ }
1362
+
1363
+ /* -------------------------------------------------------------------------------------------------
1364
+ * Export
1365
+ * -----------------------------------------------------------------------------------------------*/
1366
+
1367
+ ToastRoot.displayName = 'Toast'
1368
+
1369
+ export const Toast = withStaticProperties(ToastRoot, {
1370
+ Viewport: ToastViewport,
1371
+ List: ToastList,
1372
+ Item: ToastItemInner,
1373
+ Title: ToastTitle,
1374
+ Description: ToastDescription,
1375
+ Close: ToastClose,
1376
+ Action: ToastAction,
1377
+ Icon: ToastIcon,
1378
+ })
1379
+
1380
+ export type { ToastT, ExternalToast }