@hanzogui/toast 3.0.2 → 7.3.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 (270) 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 +81 -75
  5. package/dist/cjs/ToastAnnounce.native.js +86 -80
  6. package/dist/cjs/ToastAnnounce.native.js.map +1 -1
  7. package/dist/cjs/ToastComposable.cjs +933 -0
  8. package/dist/cjs/ToastComposable.native.js +1025 -0
  9. package/dist/cjs/ToastComposable.native.js.map +1 -0
  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 +303 -241
  14. package/dist/cjs/ToastImpl.native.js +311 -273
  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 +103 -99
  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 +311 -0
  26. package/dist/cjs/ToastState.native.js +324 -0
  27. package/dist/cjs/ToastState.native.js.map +1 -0
  28. package/dist/cjs/ToastViewport.cjs +276 -235
  29. package/dist/cjs/ToastViewport.native.js +302 -274
  30. package/dist/cjs/ToastViewport.native.js.map +1 -1
  31. package/dist/cjs/Toaster.cjs +87 -0
  32. package/dist/cjs/Toaster.native.js +91 -0
  33. package/dist/cjs/Toaster.native.js.map +1 -0
  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 +214 -0
  50. package/dist/cjs/useAnimatedDragGesture.native.js +224 -0
  51. package/dist/cjs/useAnimatedDragGesture.native.js.map +1 -0
  52. package/dist/cjs/useReducedMotion.cjs +74 -0
  53. package/dist/cjs/useReducedMotion.native.js +83 -0
  54. package/dist/cjs/useReducedMotion.native.js.map +1 -0
  55. package/dist/cjs/useToastAnimations.cjs +294 -0
  56. package/dist/cjs/useToastAnimations.native.js +304 -0
  57. package/dist/cjs/useToastAnimations.native.js.map +1 -0
  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 +895 -0
  70. package/dist/esm/ToastComposable.mjs.map +1 -0
  71. package/dist/esm/ToastComposable.native.js +984 -0
  72. package/dist/esm/ToastComposable.native.js.map +1 -0
  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 +285 -0
  94. package/dist/esm/ToastState.mjs.map +1 -0
  95. package/dist/esm/ToastState.native.js +295 -0
  96. package/dist/esm/ToastState.native.js.map +1 -0
  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 +51 -0
  102. package/dist/esm/Toaster.mjs.map +1 -0
  103. package/dist/esm/Toaster.native.js +52 -0
  104. package/dist/esm/Toaster.native.js.map +1 -0
  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/useAnimatedDragGesture.mjs +178 -0
  118. package/dist/esm/useAnimatedDragGesture.mjs.map +1 -0
  119. package/dist/esm/useAnimatedDragGesture.native.js +185 -0
  120. package/dist/esm/useAnimatedDragGesture.native.js.map +1 -0
  121. package/dist/esm/useReducedMotion.mjs +38 -0
  122. package/dist/esm/useReducedMotion.mjs.map +1 -0
  123. package/dist/esm/useReducedMotion.native.js +44 -0
  124. package/dist/esm/useReducedMotion.native.js.map +1 -0
  125. package/dist/esm/useToastAnimations.mjs +258 -0
  126. package/dist/esm/useToastAnimations.mjs.map +1 -0
  127. package/dist/esm/useToastAnimations.native.js +265 -0
  128. package/dist/esm/useToastAnimations.native.js.map +1 -0
  129. package/dist/esm/v2.mjs +6 -0
  130. package/dist/esm/v2.mjs.map +1 -0
  131. package/dist/esm/v2.native.js +6 -0
  132. package/dist/esm/v2.native.js.map +1 -0
  133. package/dist/jsx/Toast.mjs +112 -106
  134. package/dist/jsx/Toast.mjs.map +1 -1
  135. package/dist/jsx/Toast.native.js +149 -142
  136. package/dist/jsx/Toast.native.js.map +1 -1
  137. package/dist/jsx/ToastAnnounce.mjs +46 -42
  138. package/dist/jsx/ToastAnnounce.mjs.map +1 -1
  139. package/dist/jsx/ToastAnnounce.native.js +86 -80
  140. package/dist/jsx/ToastAnnounce.native.js.map +1 -1
  141. package/dist/jsx/ToastComposable.mjs +895 -0
  142. package/dist/jsx/ToastComposable.mjs.map +1 -0
  143. package/dist/jsx/ToastComposable.native.js +1025 -0
  144. package/dist/jsx/ToastComposable.native.js.map +1 -0
  145. package/dist/jsx/ToastImperative.mjs +71 -54
  146. package/dist/jsx/ToastImperative.mjs.map +1 -1
  147. package/dist/jsx/ToastImperative.native.js +104 -96
  148. package/dist/jsx/ToastImperative.native.js.map +1 -1
  149. package/dist/jsx/ToastImpl.mjs +261 -201
  150. package/dist/jsx/ToastImpl.mjs.map +1 -1
  151. package/dist/jsx/ToastImpl.native.js +311 -273
  152. package/dist/jsx/ToastImpl.native.js.map +1 -1
  153. package/dist/jsx/ToastItemFrame.mjs +114 -0
  154. package/dist/jsx/ToastItemFrame.mjs.map +1 -0
  155. package/dist/jsx/ToastItemFrame.native.js +148 -0
  156. package/dist/jsx/ToastItemFrame.native.js.map +1 -0
  157. package/dist/jsx/ToastPortal.mjs +8 -5
  158. package/dist/jsx/ToastPortal.mjs.map +1 -1
  159. package/dist/jsx/ToastPortal.native.js +27 -22
  160. package/dist/jsx/ToastPortal.native.js.map +1 -1
  161. package/dist/jsx/ToastProvider.mjs +71 -69
  162. package/dist/jsx/ToastProvider.mjs.map +1 -1
  163. package/dist/jsx/ToastProvider.native.js +108 -106
  164. package/dist/jsx/ToastProvider.native.js.map +1 -1
  165. package/dist/jsx/ToastState.mjs +285 -0
  166. package/dist/jsx/ToastState.mjs.map +1 -0
  167. package/dist/jsx/ToastState.native.js +324 -0
  168. package/dist/jsx/ToastState.native.js.map +1 -0
  169. package/dist/jsx/ToastViewport.mjs +238 -199
  170. package/dist/jsx/ToastViewport.mjs.map +1 -1
  171. package/dist/jsx/ToastViewport.native.js +302 -274
  172. package/dist/jsx/ToastViewport.native.js.map +1 -1
  173. package/dist/jsx/Toaster.mjs +51 -0
  174. package/dist/jsx/Toaster.mjs.map +1 -0
  175. package/dist/jsx/Toaster.native.js +91 -0
  176. package/dist/jsx/Toaster.native.js.map +1 -0
  177. package/dist/jsx/constants.mjs +2 -2
  178. package/dist/jsx/constants.mjs.map +1 -1
  179. package/dist/jsx/constants.native.js +14 -12
  180. package/dist/jsx/constants.native.js.map +1 -1
  181. package/dist/jsx/createNativeToast.mjs +29 -24
  182. package/dist/jsx/createNativeToast.mjs.map +1 -1
  183. package/dist/jsx/createNativeToast.native.js +42 -30
  184. package/dist/jsx/createNativeToast.native.js.map +1 -1
  185. package/dist/jsx/dispatchNativeToast.mjs +22 -0
  186. package/dist/jsx/dispatchNativeToast.mjs.map +1 -0
  187. package/dist/jsx/dispatchNativeToast.native.js +52 -0
  188. package/dist/jsx/dispatchNativeToast.native.js.map +1 -0
  189. package/dist/jsx/index.native.js +7 -5
  190. package/dist/jsx/types.native.js +7 -5
  191. package/dist/jsx/useAnimatedDragGesture.mjs +178 -0
  192. package/dist/jsx/useAnimatedDragGesture.mjs.map +1 -0
  193. package/dist/jsx/useAnimatedDragGesture.native.js +224 -0
  194. package/dist/jsx/useAnimatedDragGesture.native.js.map +1 -0
  195. package/dist/jsx/useReducedMotion.mjs +38 -0
  196. package/dist/jsx/useReducedMotion.mjs.map +1 -0
  197. package/dist/jsx/useReducedMotion.native.js +83 -0
  198. package/dist/jsx/useReducedMotion.native.js.map +1 -0
  199. package/dist/jsx/useToastAnimations.mjs +258 -0
  200. package/dist/jsx/useToastAnimations.mjs.map +1 -0
  201. package/dist/jsx/useToastAnimations.native.js +304 -0
  202. package/dist/jsx/useToastAnimations.native.js.map +1 -0
  203. package/dist/jsx/v2.mjs +6 -0
  204. package/dist/jsx/v2.mjs.map +1 -0
  205. package/dist/jsx/v2.native.js +39 -0
  206. package/dist/jsx/v2.native.js.map +1 -0
  207. package/package.json +43 -24
  208. package/src/Toast.tsx +2 -2
  209. package/src/ToastComposable.tsx +1380 -0
  210. package/src/ToastImpl.tsx +15 -3
  211. package/src/ToastItemFrame.tsx +136 -0
  212. package/src/ToastPortal.tsx +2 -2
  213. package/src/ToastProvider.tsx +8 -1
  214. package/src/ToastState.ts +398 -0
  215. package/src/ToastViewport.tsx +6 -5
  216. package/src/Toaster.tsx +181 -0
  217. package/src/createNativeToast.native.tsx +4 -0
  218. package/src/createNativeToast.tsx +18 -24
  219. package/src/dispatchNativeToast.ts +43 -0
  220. package/src/useAnimatedDragGesture.native.ts +255 -0
  221. package/src/useAnimatedDragGesture.ts +319 -0
  222. package/src/useReducedMotion.ts +59 -0
  223. package/src/useToastAnimations.ts +377 -0
  224. package/src/v2.ts +31 -0
  225. package/types/Toast.d.ts +34 -34
  226. package/types/Toast.d.ts.map +1 -0
  227. package/types/ToastAnnounce.d.ts +4 -4
  228. package/types/ToastAnnounce.d.ts.map +1 -0
  229. package/types/ToastComposable.d.ts +199 -0
  230. package/types/ToastComposable.d.ts.map +1 -0
  231. package/types/ToastImperative.d.ts +3 -3
  232. package/types/ToastImperative.d.ts.map +1 -0
  233. package/types/ToastImpl.d.ts +11 -11
  234. package/types/ToastImpl.d.ts.map +1 -0
  235. package/types/ToastItemFrame.d.ts +25 -0
  236. package/types/ToastItemFrame.d.ts.map +1 -0
  237. package/types/ToastPortal.d.ts +1 -1
  238. package/types/ToastPortal.d.ts.map +1 -0
  239. package/types/ToastProvider.d.ts +4 -4
  240. package/types/ToastProvider.d.ts.map +1 -0
  241. package/types/ToastState.d.ts +179 -0
  242. package/types/ToastState.d.ts.map +1 -0
  243. package/types/ToastViewport.d.ts +10 -10
  244. package/types/ToastViewport.d.ts.map +1 -0
  245. package/types/Toaster.d.ts +112 -0
  246. package/types/Toaster.d.ts.map +1 -0
  247. package/types/constants.d.ts.map +1 -0
  248. package/types/createNativeToast.d.ts +5 -0
  249. package/types/createNativeToast.d.ts.map +1 -0
  250. package/types/createNativeToast.native.d.ts +1 -0
  251. package/types/createNativeToast.native.d.ts.map +1 -0
  252. package/types/dispatchNativeToast.d.ts +12 -0
  253. package/types/dispatchNativeToast.d.ts.map +1 -0
  254. package/types/index.d.ts.map +1 -0
  255. package/types/types.d.ts.map +1 -0
  256. package/types/useAnimatedDragGesture.d.ts +33 -0
  257. package/types/useAnimatedDragGesture.d.ts.map +1 -0
  258. package/types/useAnimatedDragGesture.native.d.ts +33 -0
  259. package/types/useAnimatedDragGesture.native.d.ts.map +1 -0
  260. package/types/useReducedMotion.d.ts +6 -0
  261. package/types/useReducedMotion.d.ts.map +1 -0
  262. package/types/useToastAnimations.d.ts +50 -0
  263. package/types/useToastAnimations.d.ts.map +1 -0
  264. package/types/v2.d.ts +10 -0
  265. package/types/v2.d.ts.map +1 -0
  266. package/v2/index.cjs +2 -0
  267. package/v2/index.js +2 -0
  268. package/v2/index.native.cjs +2 -0
  269. package/v2/index.native.js +2 -0
  270. package/LICENSE +0 -21
@@ -0,0 +1,181 @@
1
+ /**
2
+ * Toaster — drop-in all-in-one toast component.
3
+ * Thin wrapper around Toast + Toast.Viewport + Toast.List (composable API).
4
+ */
5
+
6
+ import type { GuiElement } from '@hanzogui/core'
7
+ import * as React from 'react'
8
+ import { Toast } from './ToastComposable'
9
+ import type { ToastPosition } from './ToastComposable'
10
+ import type { SwipeDirection } from './ToastProvider'
11
+ import type { ExternalToast } from './ToastState'
12
+ import type { BurntToastOptions } from './types'
13
+
14
+ export type ToasterPosition = ToastPosition
15
+
16
+ export interface ToasterProps {
17
+ /**
18
+ * Position of the toasts on screen
19
+ * @default 'bottom-right'
20
+ */
21
+ position?: ToasterPosition
22
+
23
+ /**
24
+ * Expand toasts on hover to show all
25
+ * @default false
26
+ */
27
+ expand?: boolean
28
+
29
+ /**
30
+ * Number of toasts visible at once
31
+ * @default 4
32
+ */
33
+ visibleToasts?: number
34
+
35
+ /**
36
+ * Gap between toasts in pixels
37
+ * @default 14
38
+ */
39
+ gap?: number
40
+
41
+ /**
42
+ * Default duration for toasts in ms
43
+ * @default 4000
44
+ */
45
+ duration?: number
46
+
47
+ /**
48
+ * Offset from screen edge in pixels
49
+ * @default 24
50
+ */
51
+ offset?: number | { top?: number; right?: number; bottom?: number; left?: number }
52
+
53
+ /**
54
+ * Hotkey to focus toast viewport
55
+ * @default ['altKey', 'KeyT']
56
+ */
57
+ hotkey?: string[]
58
+
59
+ /**
60
+ * Direction(s) toasts can be swiped to dismiss.
61
+ * 'auto' detects based on position (swipe toward nearest edge).
62
+ * @default 'auto'
63
+ */
64
+ swipeDirection?: SwipeDirection
65
+
66
+ /**
67
+ * Distance in pixels swipe must pass to dismiss
68
+ * @default 50
69
+ */
70
+ swipeThreshold?: number
71
+
72
+ /**
73
+ * Show close button on toasts
74
+ * @default false
75
+ */
76
+ closeButton?: boolean
77
+
78
+ /**
79
+ * Theme for toasts (auto-detected if not set)
80
+ */
81
+ theme?: 'light' | 'dark' | 'system'
82
+
83
+ /**
84
+ * Custom icons for toast types
85
+ */
86
+ icons?: {
87
+ success?: React.ReactNode
88
+ error?: React.ReactNode
89
+ warning?: React.ReactNode
90
+ info?: React.ReactNode
91
+ loading?: React.ReactNode
92
+ close?: React.ReactNode
93
+ }
94
+
95
+ /**
96
+ * Default toast options
97
+ */
98
+ toastOptions?: ExternalToast
99
+
100
+ /**
101
+ * Container aria label for screen readers
102
+ * @default 'Notifications'
103
+ */
104
+ containerAriaLabel?: string
105
+
106
+ /**
107
+ * When true, uses burnt native OS toasts on mobile instead of RN views.
108
+ * @default false
109
+ */
110
+ native?: boolean
111
+
112
+ /**
113
+ * Options for burnt native toasts on mobile
114
+ */
115
+ burntOptions?: Omit<BurntToastOptions, 'title' | 'message' | 'duration'>
116
+
117
+ /**
118
+ * Options for web Notification API
119
+ */
120
+ notificationOptions?: NotificationOptions
121
+
122
+ /**
123
+ * Force reduced motion mode (disables animations)
124
+ */
125
+ reducedMotion?: boolean
126
+ }
127
+
128
+ export const Toaster = React.forwardRef<GuiElement, ToasterProps>(
129
+ function Toaster(props, ref) {
130
+ const {
131
+ position = 'bottom-right',
132
+ expand = false,
133
+ visibleToasts,
134
+ gap,
135
+ duration,
136
+ offset,
137
+ hotkey,
138
+ swipeDirection,
139
+ swipeThreshold,
140
+ closeButton,
141
+ theme,
142
+ icons,
143
+ toastOptions,
144
+ containerAriaLabel = 'Notifications',
145
+ native,
146
+ burntOptions,
147
+ notificationOptions,
148
+ reducedMotion,
149
+ } = props
150
+
151
+ return (
152
+ <Toast
153
+ position={position}
154
+ expand={expand}
155
+ visibleToasts={visibleToasts}
156
+ gap={gap}
157
+ duration={toastOptions?.duration ?? duration}
158
+ swipeDirection={swipeDirection}
159
+ swipeThreshold={swipeThreshold}
160
+ closeButton={closeButton}
161
+ theme={theme}
162
+ icons={icons}
163
+ native={native}
164
+ burntOptions={burntOptions}
165
+ notificationOptions={notificationOptions}
166
+ reducedMotion={reducedMotion}
167
+ >
168
+ <Toast.Viewport
169
+ ref={ref}
170
+ offset={offset}
171
+ hotkey={hotkey}
172
+ label={containerAriaLabel}
173
+ >
174
+ <Toast.List />
175
+ </Toast.Viewport>
176
+ </Toast>
177
+ )
178
+ }
179
+ )
180
+
181
+ Toaster.displayName = 'Toaster'
@@ -27,3 +27,7 @@ export const hideNativeToast: HideNativeToastsFn = () => {
27
27
  if (!burnt.isEnabled) return
28
28
  burnt.state.dismissAllAlerts!()
29
29
  }
30
+
31
+ export async function requestNotificationPermission(): Promise<null> {
32
+ return null
33
+ }
@@ -5,40 +5,23 @@ export const createNativeToast: CreateNativeToastsFn = (
5
5
  { message, notificationOptions }
6
6
  ) => {
7
7
  if (!('Notification' in window)) {
8
- console.error('This browser does not support notifications')
9
8
  return false
10
9
  }
11
10
 
12
- if (Notification.permission === 'denied') return false
13
- const showNotification = () => {
14
- const notification = new Notification(title, {
15
- body: message,
16
- ...notificationOptions,
17
- })
11
+ if (Notification.permission !== 'granted') return false
18
12
 
19
- return notification
20
- }
13
+ const notification = new Notification(title, {
14
+ body: message,
15
+ ...notificationOptions,
16
+ })
21
17
 
22
- if (Notification.permission === 'granted') {
23
- const notification = showNotification()
24
- return {
25
- nativeToastRef: notification,
26
- }
18
+ return {
19
+ nativeToastRef: notification,
27
20
  }
28
- Notification.requestPermission().then((permission) => {
29
- if (permission === 'granted') {
30
- const notification = showNotification()
31
- return {
32
- nativeToastRef: notification,
33
- }
34
- }
35
- })
36
- return true
37
21
  }
38
22
 
39
23
  export const hideNativeToast: HideNativeToastsFn = (ref) => {
40
24
  if (!('Notification' in window)) {
41
- console.error('This browser does not support notifications')
42
25
  return
43
26
  }
44
27
 
@@ -46,3 +29,14 @@ export const hideNativeToast: HideNativeToastsFn = (ref) => {
46
29
  ref.close()
47
30
  }
48
31
  }
32
+
33
+ /**
34
+ * Request notification permission from the browser.
35
+ * Must be called from a user gesture (click/tap handler).
36
+ */
37
+ export async function requestNotificationPermission(): Promise<NotificationPermission | null> {
38
+ if (!('Notification' in window)) return null
39
+ if (Notification.permission === 'granted') return 'granted'
40
+ if (Notification.permission === 'denied') return 'denied'
41
+ return Notification.requestPermission()
42
+ }
@@ -0,0 +1,43 @@
1
+ import type { ToastT } from './ToastState'
2
+ import type { BurntToastOptions } from './types'
3
+ import { createNativeToast } from './createNativeToast'
4
+
5
+ /**
6
+ * Attempts to dispatch a toast via native platform API (Burnt on mobile, Notification on web).
7
+ * Returns true if the toast was handled natively, false if it should fall through to in-app.
8
+ */
9
+ export function dispatchNativeToast(
10
+ toast: ToastT,
11
+ opts: {
12
+ duration: number
13
+ burntOptions?: Omit<BurntToastOptions, 'title' | 'message' | 'duration'>
14
+ notificationOptions?: NotificationOptions
15
+ }
16
+ ): boolean {
17
+ const titleText = typeof toast.title === 'function' ? toast.title() : toast.title
18
+ if (typeof titleText !== 'string') return false
19
+
20
+ const descText =
21
+ typeof toast.description === 'function' ? toast.description() : toast.description
22
+ const toastType = toast.type ?? 'default'
23
+
24
+ const preset: BurntToastOptions['preset'] =
25
+ toastType === 'error' ? 'error' : toastType === 'success' ? 'done' : 'none'
26
+ const haptic: BurntToastOptions['haptic'] =
27
+ toastType === 'error'
28
+ ? 'error'
29
+ : toastType === 'success'
30
+ ? 'success'
31
+ : toastType === 'warning'
32
+ ? 'warning'
33
+ : 'none'
34
+
35
+ const result = createNativeToast(titleText, {
36
+ message: typeof descText === 'string' ? descText : undefined,
37
+ duration: toast.duration ?? opts.duration,
38
+ burntOptions: { preset, haptic, ...opts.burntOptions },
39
+ notificationOptions: opts.notificationOptions,
40
+ })
41
+
42
+ return result !== false
43
+ }
@@ -0,0 +1,255 @@
1
+ /**
2
+ * Native drag gesture handling for toast swipe-to-dismiss.
3
+ *
4
+ * Uses react-native-gesture-handler (RNGH) when available for proper gesture
5
+ * coordination with ScrollView and navigation. Falls back to PanResponder.
6
+ *
7
+ * Pattern: same as Sheet — RNGH is accessed through @hanzogui/native global
8
+ * registry, never imported directly. The gesture is created in useMemo and
9
+ * returns null when RNGH is not set up.
10
+ */
11
+
12
+ import { getGestureHandler } from '@hanzogui/native'
13
+ import * as React from 'react'
14
+ import type { PanResponderGestureState } from 'react-native'
15
+ import { PanResponder } from 'react-native'
16
+ import type { SwipeDirection } from './ToastProvider'
17
+
18
+ export interface UseAnimatedDragGestureOptions {
19
+ direction: SwipeDirection
20
+ threshold: number
21
+ disabled?: boolean
22
+ expanded?: boolean
23
+ onDragMove: (x: number, y: number) => void
24
+ onDragStart?: () => void
25
+ onDismiss: (exitDirection: 'left' | 'right' | 'up' | 'down', velocity: number) => void
26
+ onCancel: () => void
27
+ }
28
+
29
+ const VELOCITY_THRESHOLD = 0.11
30
+ const GESTURE_GRANT_THRESHOLD = 10
31
+
32
+ function resisted(delta: number, maxResist = 25): number {
33
+ if (delta >= 0) return delta
34
+ const pastBoundary = Math.abs(delta)
35
+ const resistedDistance = Math.sqrt(pastBoundary) * 2
36
+ return -Math.min(resistedDistance, maxResist)
37
+ }
38
+
39
+ const EXIT_DRAG_CAP = 80
40
+
41
+ function cappedExit(delta: number): number {
42
+ if (Math.abs(delta) <= EXIT_DRAG_CAP) return delta
43
+ const sign = delta > 0 ? 1 : -1
44
+ const overshoot = Math.abs(delta) - EXIT_DRAG_CAP
45
+ return sign * (EXIT_DRAG_CAP + Math.sqrt(overshoot) * 2)
46
+ }
47
+
48
+ function computeOffset(
49
+ direction: SwipeDirection,
50
+ dx: number,
51
+ dy: number
52
+ ): { offsetX: number; offsetY: number } {
53
+ let offsetX = 0
54
+ let offsetY = 0
55
+
56
+ if (direction === 'right') {
57
+ offsetX = dx > 0 ? cappedExit(dx) : resisted(dx)
58
+ } else if (direction === 'left') {
59
+ offsetX = dx < 0 ? cappedExit(dx) : -resisted(-dx)
60
+ } else if (direction === 'down') {
61
+ offsetY = dy > 0 ? cappedExit(dy) : resisted(dy)
62
+ } else if (direction === 'up') {
63
+ offsetY = dy < 0 ? cappedExit(dy) : -resisted(-dy)
64
+ } else if (direction === 'horizontal') {
65
+ offsetX = cappedExit(dx)
66
+ } else if (direction === 'vertical') {
67
+ offsetY = cappedExit(dy)
68
+ }
69
+
70
+ return { offsetX, offsetY }
71
+ }
72
+
73
+ function computeExitDirection(
74
+ direction: SwipeDirection,
75
+ dx: number,
76
+ dy: number
77
+ ): 'left' | 'right' | 'up' | 'down' | null {
78
+ if (direction === 'right' && dx > 0) return 'right'
79
+ if (direction === 'left' && dx < 0) return 'left'
80
+ if (direction === 'horizontal') {
81
+ if (Math.abs(dx) > Math.abs(dy)) return dx > 0 ? 'right' : 'left'
82
+ }
83
+ if (direction === 'down' && dy > 0) return 'down'
84
+ if (direction === 'up' && dy < 0) return 'up'
85
+ if (direction === 'vertical') {
86
+ if (Math.abs(dy) > Math.abs(dx)) return dy > 0 ? 'down' : 'up'
87
+ }
88
+ return null
89
+ }
90
+
91
+ function shouldGrantGestureMove(dir: SwipeDirection, dx: number, dy: number): boolean {
92
+ const absDx = Math.abs(dx)
93
+ const absDy = Math.abs(dy)
94
+ if (
95
+ (dir === 'horizontal' || dir === 'left' || dir === 'right') &&
96
+ absDx > GESTURE_GRANT_THRESHOLD &&
97
+ absDx > absDy
98
+ ) {
99
+ return true
100
+ }
101
+ if (
102
+ (dir === 'vertical' || dir === 'up' || dir === 'down') &&
103
+ absDy > GESTURE_GRANT_THRESHOLD &&
104
+ absDy > absDx
105
+ ) {
106
+ return true
107
+ }
108
+ return false
109
+ }
110
+
111
+ /**
112
+ * Single hook — always calls the same hooks in the same order.
113
+ * Creates RNGH gesture in useMemo (returns null if unavailable).
114
+ * Creates PanResponder in useMemo (returns null if RNGH is used).
115
+ * Consumer checks `gesture` to decide whether to wrap with GestureDetector.
116
+ */
117
+ export function useAnimatedDragGesture(options: UseAnimatedDragGestureOptions) {
118
+ const { direction, threshold, disabled } = options
119
+
120
+ const [isDragging, setIsDragging] = React.useState(false)
121
+
122
+ const isHorizontal =
123
+ direction === 'left' || direction === 'right' || direction === 'horizontal'
124
+
125
+ const gestureRef = React.useRef<any>(null)
126
+
127
+ // store callbacks in refs for stable closures
128
+ const onDragMoveRef = React.useRef(options.onDragMove)
129
+ const onDragStartRef = React.useRef(options.onDragStart)
130
+ const onDismissRef = React.useRef(options.onDismiss)
131
+ const onCancelRef = React.useRef(options.onCancel)
132
+ onDragMoveRef.current = options.onDragMove
133
+ onDragStartRef.current = options.onDragStart
134
+ onDismissRef.current = options.onDismiss
135
+ onCancelRef.current = options.onCancel
136
+
137
+ // check once — RNGH availability is set at app init and never changes
138
+ const rnghEnabled = getGestureHandler().isEnabled
139
+
140
+ // RNGH gesture (null if not available)
141
+ const gesture = React.useMemo(() => {
142
+ if (!rnghEnabled || disabled) return null
143
+
144
+ const { Gesture } = getGestureHandler().state
145
+ if (!Gesture) return null
146
+
147
+ const pan = Gesture.Pan()
148
+ .withRef(gestureRef)
149
+ .shouldCancelWhenOutside(false)
150
+ .runOnJS(true)
151
+
152
+ if (isHorizontal) {
153
+ pan.activeOffsetX([-10, 10])
154
+ pan.failOffsetY([-20, 20])
155
+ } else {
156
+ pan.activeOffsetY([-10, 10])
157
+ pan.failOffsetX([-20, 20])
158
+ }
159
+
160
+ pan
161
+ .onStart(() => {
162
+ setIsDragging(true)
163
+ onDragStartRef.current?.()
164
+ })
165
+ .onChange((event: any) => {
166
+ const { offsetX, offsetY } = computeOffset(
167
+ direction,
168
+ event.translationX,
169
+ event.translationY
170
+ )
171
+ onDragMoveRef.current(offsetX, offsetY)
172
+ })
173
+ .onEnd((event: any) => {
174
+ const dx = event.translationX
175
+ const dy = event.translationY
176
+ const relevantDelta = isHorizontal ? dx : dy
177
+ const relevantVelocity = isHorizontal
178
+ ? Math.abs(event.velocityX / 1000)
179
+ : Math.abs(event.velocityY / 1000)
180
+
181
+ const passedThreshold = Math.abs(relevantDelta) >= threshold
182
+ const hasVelocity = relevantVelocity > VELOCITY_THRESHOLD
183
+ const exitDirection = computeExitDirection(direction, dx, dy)
184
+ const shouldDismiss = exitDirection && (passedThreshold || hasVelocity)
185
+
186
+ setIsDragging(false)
187
+
188
+ if (shouldDismiss && exitDirection) {
189
+ onDismissRef.current(exitDirection, relevantVelocity)
190
+ } else {
191
+ onCancelRef.current()
192
+ }
193
+ })
194
+ .onFinalize(() => {
195
+ setIsDragging(false)
196
+ })
197
+
198
+ return pan
199
+ }, [disabled, direction, threshold, isHorizontal, rnghEnabled])
200
+
201
+ // PanResponder fallback (null if RNGH is used)
202
+ const panResponder = React.useMemo(() => {
203
+ if (rnghEnabled || disabled) return null
204
+
205
+ return PanResponder.create({
206
+ onMoveShouldSetPanResponder: (_e, g) => {
207
+ return shouldGrantGestureMove(direction, g.dx, g.dy)
208
+ },
209
+ onMoveShouldSetPanResponderCapture: (_e, g) => {
210
+ return shouldGrantGestureMove(direction, g.dx, g.dy)
211
+ },
212
+ onPanResponderTerminationRequest: () => false,
213
+
214
+ onPanResponderGrant: () => {
215
+ setIsDragging(true)
216
+ onDragStartRef.current?.()
217
+ },
218
+
219
+ onPanResponderMove: (_e, g: PanResponderGestureState) => {
220
+ const { offsetX, offsetY } = computeOffset(direction, g.dx, g.dy)
221
+ onDragMoveRef.current(offsetX, offsetY)
222
+ },
223
+
224
+ onPanResponderRelease: (_e, g: PanResponderGestureState) => {
225
+ const { dx, dy, vx, vy } = g
226
+ const relevantDelta = isHorizontal ? dx : dy
227
+ const relevantVelocity = isHorizontal ? Math.abs(vx) : Math.abs(vy)
228
+
229
+ const passedThreshold = Math.abs(relevantDelta) >= threshold
230
+ const hasVelocity = relevantVelocity > VELOCITY_THRESHOLD
231
+ const exitDirection = computeExitDirection(direction, dx, dy)
232
+ const shouldDismiss = exitDirection && (passedThreshold || hasVelocity)
233
+
234
+ setIsDragging(false)
235
+
236
+ if (shouldDismiss && exitDirection) {
237
+ onDismissRef.current(exitDirection, relevantVelocity)
238
+ } else {
239
+ onCancelRef.current()
240
+ }
241
+ },
242
+
243
+ onPanResponderTerminate: () => {
244
+ setIsDragging(false)
245
+ onCancelRef.current()
246
+ },
247
+ })
248
+ }, [disabled, direction, threshold, isHorizontal, rnghEnabled])
249
+
250
+ return {
251
+ isDragging,
252
+ gestureHandlers: panResponder?.panHandlers ?? {},
253
+ gesture,
254
+ }
255
+ }