@gabvdl/ui 0.0.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (330) hide show
  1. package/README.md +95 -0
  2. package/dist/components/button/button.d.ts +39 -0
  3. package/dist/components/button/button.d.ts.map +1 -0
  4. package/dist/components/button/button.js +72 -0
  5. package/dist/components/button/button.js.map +1 -0
  6. package/dist/components/button/index.d.ts +5 -0
  7. package/dist/components/button/index.d.ts.map +1 -0
  8. package/dist/components/button/tooltip.d.ts +31 -0
  9. package/dist/components/button/tooltip.d.ts.map +1 -0
  10. package/dist/components/button/tooltip.js +83 -0
  11. package/dist/components/button/tooltip.js.map +1 -0
  12. package/dist/components/changelog/changelog.d.ts +52 -0
  13. package/dist/components/changelog/changelog.d.ts.map +1 -0
  14. package/dist/components/changelog/changelog.js +164 -0
  15. package/dist/components/changelog/changelog.js.map +1 -0
  16. package/dist/components/changelog/index.d.ts +3 -0
  17. package/dist/components/changelog/index.d.ts.map +1 -0
  18. package/dist/components/copy-button/copy-button.d.ts +16 -0
  19. package/dist/components/copy-button/copy-button.d.ts.map +1 -0
  20. package/dist/components/copy-button/copy-button.js +40 -0
  21. package/dist/components/copy-button/copy-button.js.map +1 -0
  22. package/dist/components/copy-button/index.d.ts +3 -0
  23. package/dist/components/copy-button/index.d.ts.map +1 -0
  24. package/dist/components/drop-zone/drop-zone.d.ts +20 -0
  25. package/dist/components/drop-zone/drop-zone.d.ts.map +1 -0
  26. package/dist/components/drop-zone/drop-zone.js +54 -0
  27. package/dist/components/drop-zone/drop-zone.js.map +1 -0
  28. package/dist/components/drop-zone/index.d.ts +5 -0
  29. package/dist/components/drop-zone/index.d.ts.map +1 -0
  30. package/dist/components/drop-zone/use-file-drop.d.ts +45 -0
  31. package/dist/components/drop-zone/use-file-drop.d.ts.map +1 -0
  32. package/dist/components/drop-zone/use-file-drop.js +97 -0
  33. package/dist/components/drop-zone/use-file-drop.js.map +1 -0
  34. package/dist/components/element-picker/element-picker-field.d.ts +30 -0
  35. package/dist/components/element-picker/element-picker-field.d.ts.map +1 -0
  36. package/dist/components/element-picker/element-picker-field.js +273 -0
  37. package/dist/components/element-picker/element-picker-field.js.map +1 -0
  38. package/dist/components/element-picker/element-picker.d.ts +45 -0
  39. package/dist/components/element-picker/element-picker.d.ts.map +1 -0
  40. package/dist/components/element-picker/element-picker.js +179 -0
  41. package/dist/components/element-picker/element-picker.js.map +1 -0
  42. package/dist/components/element-picker/element-preview.d.ts +23 -0
  43. package/dist/components/element-picker/element-preview.d.ts.map +1 -0
  44. package/dist/components/element-picker/element-preview.js +72 -0
  45. package/dist/components/element-picker/element-preview.js.map +1 -0
  46. package/dist/components/element-picker/index.d.ts +12 -0
  47. package/dist/components/element-picker/index.d.ts.map +1 -0
  48. package/dist/components/element-picker/parse.d.ts +32 -0
  49. package/dist/components/element-picker/parse.d.ts.map +1 -0
  50. package/dist/components/element-picker/parse.js +230 -0
  51. package/dist/components/element-picker/parse.js.map +1 -0
  52. package/dist/components/element-picker/types.d.ts +72 -0
  53. package/dist/components/element-picker/types.d.ts.map +1 -0
  54. package/dist/components/element-picker/use-element-picker.d.ts +56 -0
  55. package/dist/components/element-picker/use-element-picker.d.ts.map +1 -0
  56. package/dist/components/element-picker/use-element-picker.js +201 -0
  57. package/dist/components/element-picker/use-element-picker.js.map +1 -0
  58. package/dist/components/empty-state/empty-state.d.ts +16 -0
  59. package/dist/components/empty-state/empty-state.d.ts.map +1 -0
  60. package/dist/components/empty-state/empty-state.js +41 -0
  61. package/dist/components/empty-state/empty-state.js.map +1 -0
  62. package/dist/components/empty-state/index.d.ts +3 -0
  63. package/dist/components/empty-state/index.d.ts.map +1 -0
  64. package/dist/components/floating-panel/dock-context.d.ts +66 -0
  65. package/dist/components/floating-panel/dock-context.d.ts.map +1 -0
  66. package/dist/components/floating-panel/dock-context.js +96 -0
  67. package/dist/components/floating-panel/dock-context.js.map +1 -0
  68. package/dist/components/floating-panel/dock.d.ts +26 -0
  69. package/dist/components/floating-panel/dock.d.ts.map +1 -0
  70. package/dist/components/floating-panel/dock.js +46 -0
  71. package/dist/components/floating-panel/dock.js.map +1 -0
  72. package/dist/components/floating-panel/floating-panel.d.ts +69 -0
  73. package/dist/components/floating-panel/floating-panel.d.ts.map +1 -0
  74. package/dist/components/floating-panel/floating-panel.js +285 -0
  75. package/dist/components/floating-panel/floating-panel.js.map +1 -0
  76. package/dist/components/floating-panel/index.d.ts +7 -0
  77. package/dist/components/floating-panel/index.d.ts.map +1 -0
  78. package/dist/components/fuzzy-list/fuzzy-list.d.ts +96 -0
  79. package/dist/components/fuzzy-list/fuzzy-list.d.ts.map +1 -0
  80. package/dist/components/fuzzy-list/fuzzy-list.js +127 -0
  81. package/dist/components/fuzzy-list/fuzzy-list.js.map +1 -0
  82. package/dist/components/fuzzy-list/index.d.ts +4 -0
  83. package/dist/components/fuzzy-list/index.d.ts.map +1 -0
  84. package/dist/components/global-search/global-search.d.ts +78 -0
  85. package/dist/components/global-search/global-search.d.ts.map +1 -0
  86. package/dist/components/global-search/global-search.js +181 -0
  87. package/dist/components/global-search/global-search.js.map +1 -0
  88. package/dist/components/global-search/hotkey.d.ts +23 -0
  89. package/dist/components/global-search/hotkey.d.ts.map +1 -0
  90. package/dist/components/global-search/hotkey.js +41 -0
  91. package/dist/components/global-search/hotkey.js.map +1 -0
  92. package/dist/components/global-search/index.d.ts +5 -0
  93. package/dist/components/global-search/index.d.ts.map +1 -0
  94. package/dist/components/iframe-preview/iframe-preview.d.ts +64 -0
  95. package/dist/components/iframe-preview/iframe-preview.d.ts.map +1 -0
  96. package/dist/components/iframe-preview/iframe-preview.js +257 -0
  97. package/dist/components/iframe-preview/iframe-preview.js.map +1 -0
  98. package/dist/components/iframe-preview/index.d.ts +3 -0
  99. package/dist/components/iframe-preview/index.d.ts.map +1 -0
  100. package/dist/components/image-viewer/image-viewer.d.ts +30 -0
  101. package/dist/components/image-viewer/image-viewer.d.ts.map +1 -0
  102. package/dist/components/image-viewer/image-viewer.js +226 -0
  103. package/dist/components/image-viewer/image-viewer.js.map +1 -0
  104. package/dist/components/image-viewer/index.d.ts +4 -0
  105. package/dist/components/image-viewer/index.d.ts.map +1 -0
  106. package/dist/components/image-viewer/progressive-image.d.ts +20 -0
  107. package/dist/components/image-viewer/progressive-image.d.ts.map +1 -0
  108. package/dist/components/image-viewer/progressive-image.js +62 -0
  109. package/dist/components/image-viewer/progressive-image.js.map +1 -0
  110. package/dist/components/image-viewer/viewable-image.d.ts +17 -0
  111. package/dist/components/image-viewer/viewable-image.d.ts.map +1 -0
  112. package/dist/components/image-viewer/viewable-image.js +38 -0
  113. package/dist/components/image-viewer/viewable-image.js.map +1 -0
  114. package/dist/components/input/index.d.ts +3 -0
  115. package/dist/components/input/index.d.ts.map +1 -0
  116. package/dist/components/input/input.d.ts +15 -0
  117. package/dist/components/input/input.d.ts.map +1 -0
  118. package/dist/components/input/input.js +56 -0
  119. package/dist/components/input/input.js.map +1 -0
  120. package/dist/components/modal/index.d.ts +5 -0
  121. package/dist/components/modal/index.d.ts.map +1 -0
  122. package/dist/components/modal/modal-context.d.ts +37 -0
  123. package/dist/components/modal/modal-context.d.ts.map +1 -0
  124. package/dist/components/modal/modal-context.js +81 -0
  125. package/dist/components/modal/modal-context.js.map +1 -0
  126. package/dist/components/modal/modal.d.ts +28 -0
  127. package/dist/components/modal/modal.d.ts.map +1 -0
  128. package/dist/components/modal/modal.js +94 -0
  129. package/dist/components/modal/modal.js.map +1 -0
  130. package/dist/components/nav-2d/index.d.ts +3 -0
  131. package/dist/components/nav-2d/index.d.ts.map +1 -0
  132. package/dist/components/nav-2d/nav-2d.d.ts +59 -0
  133. package/dist/components/nav-2d/nav-2d.d.ts.map +1 -0
  134. package/dist/components/nav-2d/nav-2d.js +290 -0
  135. package/dist/components/nav-2d/nav-2d.js.map +1 -0
  136. package/dist/components/phone-preview/index.d.ts +3 -0
  137. package/dist/components/phone-preview/index.d.ts.map +1 -0
  138. package/dist/components/phone-preview/phone-preview.d.ts +45 -0
  139. package/dist/components/phone-preview/phone-preview.d.ts.map +1 -0
  140. package/dist/components/phone-preview/phone-preview.js +117 -0
  141. package/dist/components/phone-preview/phone-preview.js.map +1 -0
  142. package/dist/components/progressive-bash/index.d.ts +7 -0
  143. package/dist/components/progressive-bash/index.d.ts.map +1 -0
  144. package/dist/components/progressive-bash/parse.d.ts +88 -0
  145. package/dist/components/progressive-bash/parse.d.ts.map +1 -0
  146. package/dist/components/progressive-bash/parse.js +120 -0
  147. package/dist/components/progressive-bash/parse.js.map +1 -0
  148. package/dist/components/progressive-bash/playback.d.ts +50 -0
  149. package/dist/components/progressive-bash/playback.d.ts.map +1 -0
  150. package/dist/components/progressive-bash/playback.js +19 -0
  151. package/dist/components/progressive-bash/playback.js.map +1 -0
  152. package/dist/components/progressive-bash/progressive-bash.d.ts +97 -0
  153. package/dist/components/progressive-bash/progressive-bash.d.ts.map +1 -0
  154. package/dist/components/progressive-bash/progressive-bash.js +377 -0
  155. package/dist/components/progressive-bash/progressive-bash.js.map +1 -0
  156. package/dist/components/progressive-list/index.d.ts +3 -0
  157. package/dist/components/progressive-list/index.d.ts.map +1 -0
  158. package/dist/components/progressive-list/progressive-list.d.ts +55 -0
  159. package/dist/components/progressive-list/progressive-list.d.ts.map +1 -0
  160. package/dist/components/progressive-list/progressive-list.js +83 -0
  161. package/dist/components/progressive-list/progressive-list.js.map +1 -0
  162. package/dist/components/progressive-table/index.d.ts +3 -0
  163. package/dist/components/progressive-table/index.d.ts.map +1 -0
  164. package/dist/components/progressive-table/progressive-table.d.ts +58 -0
  165. package/dist/components/progressive-table/progressive-table.d.ts.map +1 -0
  166. package/dist/components/progressive-table/progressive-table.js +86 -0
  167. package/dist/components/progressive-table/progressive-table.js.map +1 -0
  168. package/dist/components/progressive-text/index.d.ts +3 -0
  169. package/dist/components/progressive-text/index.d.ts.map +1 -0
  170. package/dist/components/progressive-text/progressive-text.d.ts +54 -0
  171. package/dist/components/progressive-text/progressive-text.d.ts.map +1 -0
  172. package/dist/components/progressive-text/progressive-text.js +97 -0
  173. package/dist/components/progressive-text/progressive-text.js.map +1 -0
  174. package/dist/components/progressive-timeline/index.d.ts +3 -0
  175. package/dist/components/progressive-timeline/index.d.ts.map +1 -0
  176. package/dist/components/progressive-timeline/progressive-timeline.d.ts +46 -0
  177. package/dist/components/progressive-timeline/progressive-timeline.d.ts.map +1 -0
  178. package/dist/components/progressive-timeline/progressive-timeline.js +62 -0
  179. package/dist/components/progressive-timeline/progressive-timeline.js.map +1 -0
  180. package/dist/components/relative-time/index.d.ts +3 -0
  181. package/dist/components/relative-time/index.d.ts.map +1 -0
  182. package/dist/components/relative-time/relative-time.d.ts +14 -0
  183. package/dist/components/relative-time/relative-time.d.ts.map +1 -0
  184. package/dist/components/relative-time/relative-time.js +15 -0
  185. package/dist/components/relative-time/relative-time.js.map +1 -0
  186. package/dist/components/resizable-layout/index.d.ts +3 -0
  187. package/dist/components/resizable-layout/index.d.ts.map +1 -0
  188. package/dist/components/resizable-layout/resizable-layout.d.ts +113 -0
  189. package/dist/components/resizable-layout/resizable-layout.d.ts.map +1 -0
  190. package/dist/components/resizable-layout/resizable-layout.js +398 -0
  191. package/dist/components/resizable-layout/resizable-layout.js.map +1 -0
  192. package/dist/components/rich-input/compose.d.ts +13 -0
  193. package/dist/components/rich-input/compose.d.ts.map +1 -0
  194. package/dist/components/rich-input/compose.js +28 -0
  195. package/dist/components/rich-input/compose.js.map +1 -0
  196. package/dist/components/rich-input/index.d.ts +5 -0
  197. package/dist/components/rich-input/index.d.ts.map +1 -0
  198. package/dist/components/rich-input/parts.d.ts +53 -0
  199. package/dist/components/rich-input/parts.d.ts.map +1 -0
  200. package/dist/components/rich-input/parts.js +285 -0
  201. package/dist/components/rich-input/parts.js.map +1 -0
  202. package/dist/components/rich-input/rich-input.d.ts +64 -0
  203. package/dist/components/rich-input/rich-input.d.ts.map +1 -0
  204. package/dist/components/rich-input/rich-input.js +351 -0
  205. package/dist/components/rich-input/rich-input.js.map +1 -0
  206. package/dist/components/rich-input/types.d.ts +92 -0
  207. package/dist/components/rich-input/types.d.ts.map +1 -0
  208. package/dist/components/rich-input/use-draft.d.ts +9 -0
  209. package/dist/components/rich-input/use-draft.d.ts.map +1 -0
  210. package/dist/components/rich-input/use-draft.js +47 -0
  211. package/dist/components/rich-input/use-draft.js.map +1 -0
  212. package/dist/components/rich-input/use-file-upload.d.ts +18 -0
  213. package/dist/components/rich-input/use-file-upload.d.ts.map +1 -0
  214. package/dist/components/rich-input/use-file-upload.js +88 -0
  215. package/dist/components/rich-input/use-file-upload.js.map +1 -0
  216. package/dist/components/rich-input/use-guidelines.d.ts +16 -0
  217. package/dist/components/rich-input/use-guidelines.d.ts.map +1 -0
  218. package/dist/components/rich-input/use-guidelines.js +85 -0
  219. package/dist/components/rich-input/use-guidelines.js.map +1 -0
  220. package/dist/components/rich-input/use-input-history.d.ts +22 -0
  221. package/dist/components/rich-input/use-input-history.d.ts.map +1 -0
  222. package/dist/components/rich-input/use-input-history.js +61 -0
  223. package/dist/components/rich-input/use-input-history.js.map +1 -0
  224. package/dist/components/rich-input/use-mention.d.ts +24 -0
  225. package/dist/components/rich-input/use-mention.d.ts.map +1 -0
  226. package/dist/components/rich-input/use-mention.js +54 -0
  227. package/dist/components/rich-input/use-mention.js.map +1 -0
  228. package/dist/components/search-input/index.d.ts +3 -0
  229. package/dist/components/search-input/index.d.ts.map +1 -0
  230. package/dist/components/search-input/search-input.d.ts +14 -0
  231. package/dist/components/search-input/search-input.d.ts.map +1 -0
  232. package/dist/components/search-input/search-input.js +63 -0
  233. package/dist/components/search-input/search-input.js.map +1 -0
  234. package/dist/components/skeleton/index.d.ts +3 -0
  235. package/dist/components/skeleton/index.d.ts.map +1 -0
  236. package/dist/components/skeleton/skeleton.d.ts +19 -0
  237. package/dist/components/skeleton/skeleton.d.ts.map +1 -0
  238. package/dist/components/skeleton/skeleton.js +29 -0
  239. package/dist/components/skeleton/skeleton.js.map +1 -0
  240. package/dist/components/spinner/index.d.ts +3 -0
  241. package/dist/components/spinner/index.d.ts.map +1 -0
  242. package/dist/components/spinner/spinner.d.ts +11 -0
  243. package/dist/components/spinner/spinner.d.ts.map +1 -0
  244. package/dist/components/spinner/spinner.js +32 -0
  245. package/dist/components/spinner/spinner.js.map +1 -0
  246. package/dist/components/status-badge/index.d.ts +3 -0
  247. package/dist/components/status-badge/index.d.ts.map +1 -0
  248. package/dist/components/status-badge/status-badge.d.ts +31 -0
  249. package/dist/components/status-badge/status-badge.d.ts.map +1 -0
  250. package/dist/components/status-badge/status-badge.js +53 -0
  251. package/dist/components/status-badge/status-badge.js.map +1 -0
  252. package/dist/components/theme/index.d.ts +5 -0
  253. package/dist/components/theme/index.d.ts.map +1 -0
  254. package/dist/components/theme/theme-toggle.d.ts +11 -0
  255. package/dist/components/theme/theme-toggle.d.ts.map +1 -0
  256. package/dist/components/theme/theme-toggle.js +57 -0
  257. package/dist/components/theme/theme-toggle.js.map +1 -0
  258. package/dist/components/theme/theme.d.ts +29 -0
  259. package/dist/components/theme/theme.d.ts.map +1 -0
  260. package/dist/components/theme/theme.js +70 -0
  261. package/dist/components/theme/theme.js.map +1 -0
  262. package/dist/components/toast/index.d.ts +3 -0
  263. package/dist/components/toast/index.d.ts.map +1 -0
  264. package/dist/components/toast/toast.d.ts +64 -0
  265. package/dist/components/toast/toast.d.ts.map +1 -0
  266. package/dist/components/toast/toast.js +155 -0
  267. package/dist/components/toast/toast.js.map +1 -0
  268. package/dist/components/virtual-list/index.d.ts +3 -0
  269. package/dist/components/virtual-list/index.d.ts.map +1 -0
  270. package/dist/components/virtual-list/virtual-list.d.ts +71 -0
  271. package/dist/components/virtual-list/virtual-list.d.ts.map +1 -0
  272. package/dist/components/virtual-list/virtual-list.js +65 -0
  273. package/dist/components/virtual-list/virtual-list.js.map +1 -0
  274. package/dist/hooks/index.d.ts +12 -0
  275. package/dist/hooks/index.d.ts.map +1 -0
  276. package/dist/hooks/use-copy-to-clipboard.d.ts +26 -0
  277. package/dist/hooks/use-copy-to-clipboard.d.ts.map +1 -0
  278. package/dist/hooks/use-copy-to-clipboard.js +36 -0
  279. package/dist/hooks/use-copy-to-clipboard.js.map +1 -0
  280. package/dist/hooks/use-debounced-value.d.ts +23 -0
  281. package/dist/hooks/use-debounced-value.d.ts.map +1 -0
  282. package/dist/hooks/use-debounced-value.js +19 -0
  283. package/dist/hooks/use-debounced-value.js.map +1 -0
  284. package/dist/hooks/use-intersection.d.ts +25 -0
  285. package/dist/hooks/use-intersection.d.ts.map +1 -0
  286. package/dist/hooks/use-intersection.js +38 -0
  287. package/dist/hooks/use-intersection.js.map +1 -0
  288. package/dist/hooks/use-local-storage.d.ts +12 -0
  289. package/dist/hooks/use-local-storage.d.ts.map +1 -0
  290. package/dist/hooks/use-local-storage.js +43 -0
  291. package/dist/hooks/use-local-storage.js.map +1 -0
  292. package/dist/hooks/use-long-press.d.ts +37 -0
  293. package/dist/hooks/use-long-press.d.ts.map +1 -0
  294. package/dist/hooks/use-long-press.js +39 -0
  295. package/dist/hooks/use-long-press.js.map +1 -0
  296. package/dist/hooks/use-media-query.d.ts +17 -0
  297. package/dist/hooks/use-media-query.d.ts.map +1 -0
  298. package/dist/hooks/use-media-query.js +37 -0
  299. package/dist/hooks/use-media-query.js.map +1 -0
  300. package/dist/hooks/use-overlay.d.ts +20 -0
  301. package/dist/hooks/use-overlay.d.ts.map +1 -0
  302. package/dist/hooks/use-overlay.js +42 -0
  303. package/dist/hooks/use-overlay.js.map +1 -0
  304. package/dist/image-viewer.css +152 -0
  305. package/dist/index.d.ts +67 -0
  306. package/dist/index.d.ts.map +1 -0
  307. package/dist/index.js +161 -0
  308. package/dist/index.js.map +1 -0
  309. package/dist/lib/format.d.ts +19 -0
  310. package/dist/lib/format.d.ts.map +1 -0
  311. package/dist/lib/format.js +62 -0
  312. package/dist/lib/format.js.map +1 -0
  313. package/dist/lib/highlight.d.ts +15 -0
  314. package/dist/lib/highlight.d.ts.map +1 -0
  315. package/dist/lib/highlight.js +39 -0
  316. package/dist/lib/highlight.js.map +1 -0
  317. package/dist/lib/search.d.ts +40 -0
  318. package/dist/lib/search.d.ts.map +1 -0
  319. package/dist/lib/search.js +91 -0
  320. package/dist/lib/search.js.map +1 -0
  321. package/dist/lib/utils.d.ts +4 -0
  322. package/dist/lib/utils.d.ts.map +1 -0
  323. package/dist/lib/utils.js +9 -0
  324. package/dist/lib/utils.js.map +1 -0
  325. package/dist/overlay.css +78 -0
  326. package/dist/styles.css +6 -0
  327. package/dist/theme.css +81 -0
  328. package/dist/tooltip.css +53 -0
  329. package/dist/virtual-list.css +29 -0
  330. package/package.json +77 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"toast.js","sources":["../../../src/components/toast/toast.tsx"],"sourcesContent":["import * as React from 'react';\nimport { createPortal } from 'react-dom';\nimport { AlertTriangle, CheckCircle2, Info, Loader2, X, XCircle } from 'lucide-react';\n\nimport { cn } from '../../lib/utils';\n\nexport type ToastType = 'success' | 'error' | 'warning' | 'info' | 'loading';\n\nexport type ToastPosition =\n | 'top-left'\n | 'top-center'\n | 'top-right'\n | 'bottom-left'\n | 'bottom-center'\n | 'bottom-right';\n\nexport interface ToastAction {\n label: string;\n onClick?: () => void;\n /** Renders the action as a link instead of a button. */\n href?: string;\n}\n\nexport interface ToastOptions {\n /** Bold first line above the message. */\n title?: string;\n type?: ToastType;\n /** ms before it self-dismisses. `0` (or a `loading` toast) pins it open. */\n duration?: number;\n action?: ToastAction;\n}\n\nexport interface Toast extends ToastOptions {\n id: string;\n message: React.ReactNode;\n type: ToastType;\n}\n\n/** Callable: `toast('Saved')`, plus one shorthand per type. */\nexport interface ToastFn {\n (message: React.ReactNode, options?: ToastOptions): string;\n success: (message: React.ReactNode, options?: ToastOptions) => string;\n error: (message: React.ReactNode, options?: ToastOptions) => string;\n warning: (message: React.ReactNode, options?: ToastOptions) => string;\n info: (message: React.ReactNode, options?: ToastOptions) => string;\n /** Pinned open with a spinner — settle it with `toast.update(id, …)`. */\n loading: (message: React.ReactNode, options?: ToastOptions) => string;\n /** Turn a pending toast into its outcome, in place. */\n update: (id: string, message: React.ReactNode, options?: ToastOptions) => void;\n dismiss: (id?: string) => void;\n}\n\nconst ToastCtx = React.createContext<ToastFn | null>(null);\n\n/**\n * The toast queue.\n *\n * ```tsx\n * <ToastProvider> <App /> </ToastProvider>\n *\n * const toast = useToast()\n * toast.success('Deployed', { action: { label: 'Open', href: url } })\n * const id = toast.loading('Uploading…')\n * toast.update(id, 'Uploaded', { type: 'success' })\n * ```\n */\nexport function ToastProvider({\n children,\n position = 'bottom-right',\n duration = 4000,\n max = 4,\n}: {\n children: React.ReactNode;\n position?: ToastPosition;\n /** Default lifetime. Errors get 1.75× this, since they carry more to read. */\n duration?: number;\n /** Oldest toasts beyond this are dropped, so a burst can't cover the page. */\n max?: number;\n}) {\n const [toasts, setToasts] = React.useState<Toast[]>([]);\n const timers = React.useRef(new Map<string, ReturnType<typeof setTimeout>>());\n const seq = React.useRef(0);\n\n const dismiss = React.useCallback((id?: string) => {\n if (id == null) {\n timers.current.forEach(clearTimeout);\n timers.current.clear();\n setToasts([]);\n return;\n }\n clearTimeout(timers.current.get(id));\n timers.current.delete(id);\n setToasts((prev) => prev.filter((t) => t.id !== id));\n }, []);\n\n const arm = React.useCallback(\n (t: Toast) => {\n clearTimeout(timers.current.get(t.id));\n const ttl = t.duration ?? (t.type === 'loading' ? 0 : t.type === 'error' ? duration * 1.75 : duration);\n if (ttl > 0) timers.current.set(t.id, setTimeout(() => dismiss(t.id), ttl));\n },\n [dismiss, duration],\n );\n\n const push = React.useCallback(\n (message: React.ReactNode, { type = 'info', ...rest }: ToastOptions = {}) => {\n const id = `t${(seq.current += 1)}`;\n const toast: Toast = { id, message, type, ...rest };\n setToasts((prev) => [...prev, toast].slice(-max));\n arm(toast);\n return id;\n },\n [arm, max],\n );\n\n const update = React.useCallback(\n (id: string, message: React.ReactNode, options: ToastOptions = {}) => {\n setToasts((prev) =>\n prev.map((t) => {\n if (t.id !== id) return t;\n const next: Toast = { ...t, message, ...options, type: options.type ?? t.type };\n arm(next);\n return next;\n }),\n );\n },\n [arm],\n );\n\n const toast = React.useMemo<ToastFn>(() => {\n const fn = ((message, options) => push(message, options)) as ToastFn;\n fn.success = (m, o) => push(m, { ...o, type: 'success' });\n fn.error = (m, o) => push(m, { ...o, type: 'error' });\n fn.warning = (m, o) => push(m, { ...o, type: 'warning' });\n fn.info = (m, o) => push(m, { ...o, type: 'info' });\n fn.loading = (m, o) => push(m, { ...o, type: 'loading' });\n fn.update = update;\n fn.dismiss = dismiss;\n return fn;\n }, [push, update, dismiss]);\n\n React.useEffect(() => {\n const pending = timers.current;\n return () => pending.forEach(clearTimeout);\n }, []);\n\n return (\n <ToastCtx.Provider value={toast}>\n {children}\n <Toaster toasts={toasts} position={position} onDismiss={dismiss} />\n </ToastCtx.Provider>\n );\n}\n\n/** The toast function. Throws outside a `<ToastProvider>` — a silent no-op toast is worse. */\nexport function useToast(): ToastFn {\n const ctx = React.useContext(ToastCtx);\n if (!ctx) throw new Error('useToast must be used inside a <ToastProvider>');\n return ctx;\n}\n\nconst META: Record<ToastType, { Icon: typeof Info; tone: string }> = {\n success: { Icon: CheckCircle2, tone: 'text-emerald-500' },\n error: { Icon: XCircle, tone: 'text-destructive' },\n warning: { Icon: AlertTriangle, tone: 'text-amber-500' },\n info: { Icon: Info, tone: 'text-sky-500' },\n loading: { Icon: Loader2, tone: 'text-muted-foreground' },\n};\n\nconst PLACEMENT: Record<ToastPosition, string> = {\n 'top-left': 'top-0 left-0 items-start',\n 'top-center': 'top-0 left-1/2 -translate-x-1/2 items-center',\n 'top-right': 'top-0 right-0 items-end',\n 'bottom-left': 'bottom-0 left-0 items-start',\n 'bottom-center': 'bottom-0 left-1/2 -translate-x-1/2 items-center',\n 'bottom-right': 'bottom-0 right-0 items-end',\n};\n\n/** The rendered stack. `ToastProvider` mounts one; you rarely need it directly. */\nexport function Toaster({\n toasts,\n position = 'bottom-right',\n onDismiss,\n}: {\n toasts: Toast[];\n position?: ToastPosition;\n onDismiss: (id: string) => void;\n}) {\n if (typeof document === 'undefined') return null;\n\n const fromTop = position.startsWith('top');\n\n return createPortal(\n <div\n className={cn(\n 'pointer-events-none fixed z-[100] flex w-full max-w-sm flex-col gap-2 p-4',\n fromTop ? 'flex-col' : 'flex-col-reverse',\n PLACEMENT[position],\n )}\n role=\"region\"\n aria-label=\"Notifications\"\n >\n {toasts.map((t) => {\n const { Icon, tone } = META[t.type];\n return (\n <div\n key={t.id}\n role={t.type === 'error' ? 'alert' : 'status'}\n aria-live={t.type === 'error' ? 'assertive' : 'polite'}\n className={cn(\n 'ds-toast pointer-events-auto flex w-full items-start gap-3 rounded-xl border border-border bg-popover p-3 pr-2 text-popover-foreground shadow-lg',\n fromTop ? 'ds-toast--top' : 'ds-toast--bottom',\n )}\n >\n <Icon className={cn('mt-0.5 size-4 shrink-0', tone, t.type === 'loading' && 'animate-spin')} />\n <div className=\"min-w-0 flex-1\">\n {t.title && <p className=\"text-sm font-medium\">{t.title}</p>}\n <div className=\"text-sm text-muted-foreground [overflow-wrap:anywhere]\">{t.message}</div>\n {t.action &&\n (t.action.href ? (\n <a\n href={t.action.href}\n target=\"_blank\"\n rel=\"noreferrer\"\n className=\"mt-1.5 inline-block text-xs font-medium text-foreground underline underline-offset-2\"\n >\n {t.action.label}\n </a>\n ) : (\n <button\n type=\"button\"\n onClick={() => {\n t.action?.onClick?.();\n onDismiss(t.id);\n }}\n className=\"mt-1.5 text-xs font-medium text-foreground underline underline-offset-2\"\n >\n {t.action.label}\n </button>\n ))}\n </div>\n <button\n type=\"button\"\n onClick={() => onDismiss(t.id)}\n aria-label=\"Dismiss\"\n className=\"rounded-md p-1 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground\"\n >\n <X className=\"size-3.5\" />\n </button>\n </div>\n );\n })}\n </div>,\n document.body,\n );\n}\n"],"names":["ToastCtx","React","ToastProvider","children","position","duration","max","toasts","setToasts","timers","seq","dismiss","id","prev","t","arm","ttl","push","message","type","rest","toast","update","options","next","fn","m","pending","jsxs","jsx","Toaster","useToast","ctx","META","CheckCircle2","XCircle","AlertTriangle","Info","Loader2","PLACEMENT","onDismiss","fromTop","createPortal","cn","Icon","tone","_b","_a","X"],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ export { VirtualList } from './virtual-list';
2
+ export type { VirtualListProps, VirtualListHandle } from './virtual-list';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/virtual-list/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC7C,YAAY,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC"}
@@ -0,0 +1,71 @@
1
+ import { CSSProperties, Key, ReactNode, Ref } from 'react';
2
+ /** Imperative handle exposed via {@link VirtualListProps.apiRef}. */
3
+ export interface VirtualListHandle {
4
+ scrollToIndex: (index: number, opts?: {
5
+ align?: 'auto' | 'start' | 'center' | 'end';
6
+ }) => void;
7
+ scrollToTop: () => void;
8
+ }
9
+ export interface VirtualListProps<T> {
10
+ /** The rows. Only the ones near the viewport are ever in the DOM. */
11
+ items: T[];
12
+ /** Render one row. */
13
+ renderItem: (item: T, index: number) => ReactNode;
14
+ /** Stable React key per row (defaults to the index). */
15
+ getItemKey?: (item: T, index: number) => Key;
16
+ /** First-paint height guess in px; real heights are then measured. */
17
+ estimateSize?: number;
18
+ /** Off-screen rows kept mounted as a scroll buffer. */
19
+ overscan?: number;
20
+ /**
21
+ * Called when the last rows scroll into range — append more items here for an
22
+ * infinite list. Guarded by `hasMore` and `loading`.
23
+ */
24
+ onEndReached?: () => void;
25
+ /** Whether more pages remain (guards `onEndReached`). */
26
+ hasMore?: boolean;
27
+ /** Whether a load is in flight (guards `onEndReached`, shows the indicator). */
28
+ loading?: boolean;
29
+ /** Rows-from-the-end that trip `onEndReached`. Default 4. */
30
+ endThreshold?: number;
31
+ /** Shown at the bottom while `loading`. */
32
+ loadingIndicator?: ReactNode;
33
+ /** Shown when `items` is empty and not loading. */
34
+ emptyState?: ReactNode;
35
+ /**
36
+ * Animate rows sliding to their new position when the list is re-sorted (e.g.
37
+ * an auto-sort by "updated" date whose value changed), so a reorder glides
38
+ * instead of teleporting. Only animates while the list is idle — scrolling and
39
+ * lazy height-measurement never animate. Requires the stylesheet
40
+ * `@gabvdl/ui/virtual-list.css` (bundled in `styles.css`) and a stable
41
+ * `getItemKey` so React keeps each row's DOM node across the reorder. Honors
42
+ * `prefers-reduced-motion`.
43
+ */
44
+ smooth?: boolean;
45
+ /** The scroll container — MUST be given a bounded height (via `className`). */
46
+ className?: string;
47
+ style?: CSSProperties;
48
+ /** Escape hatch to drive scrolling (e.g. keyboard navigation). */
49
+ apiRef?: Ref<VirtualListHandle>;
50
+ }
51
+ /**
52
+ * A virtualized, optionally infinite list. Only the rows near the viewport are
53
+ * mounted (windowing), heights are measured so rows may vary, and `onEndReached`
54
+ * fires as the tail comes into view so you can lazily append more. The scroll
55
+ * container needs a bounded height — set one with `className` (e.g. `h-96` or
56
+ * `flex-1 min-h-0`).
57
+ *
58
+ * ```tsx
59
+ * <VirtualList
60
+ * items={rows}
61
+ * className="h-96"
62
+ * estimateSize={56}
63
+ * hasMore={hasMore}
64
+ * loading={loading}
65
+ * onEndReached={loadNextPage}
66
+ * renderItem={(row) => <Row {...row} />}
67
+ * />
68
+ * ```
69
+ */
70
+ export declare function VirtualList<T>({ items, renderItem, getItemKey, estimateSize, overscan, onEndReached, hasMore, loading, endThreshold, loadingIndicator, emptyState, smooth, className, style, apiRef, }: VirtualListProps<T>): import("react").JSX.Element;
71
+ //# sourceMappingURL=virtual-list.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"virtual-list.d.ts","sourceRoot":"","sources":["../../../src/components/virtual-list/virtual-list.tsx"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,aAAa,EAClB,KAAK,GAAG,EACR,KAAK,SAAS,EACd,KAAK,GAAG,EACT,MAAM,OAAO,CAAC;AAKf,qEAAqE;AACrE,MAAM,WAAW,iBAAiB;IAChC,aAAa,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,QAAQ,GAAG,KAAK,CAAA;KAAE,KAAK,IAAI,CAAC;IAC/F,WAAW,EAAE,MAAM,IAAI,CAAC;CACzB;AAED,MAAM,WAAW,gBAAgB,CAAC,CAAC;IACjC,qEAAqE;IACrE,KAAK,EAAE,CAAC,EAAE,CAAC;IACX,sBAAsB;IACtB,UAAU,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,SAAS,CAAC;IAClD,wDAAwD;IACxD,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,GAAG,CAAC;IAC7C,sEAAsE;IACtE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,uDAAuD;IACvD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;IAC1B,yDAAyD;IACzD,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,gFAAgF;IAChF,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,6DAA6D;IAC7D,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,2CAA2C;IAC3C,gBAAgB,CAAC,EAAE,SAAS,CAAC;IAC7B,mDAAmD;IACnD,UAAU,CAAC,EAAE,SAAS,CAAC;IACvB;;;;;;;;OAQG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,+EAA+E;IAC/E,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB,kEAAkE;IAClE,MAAM,CAAC,EAAE,GAAG,CAAC,iBAAiB,CAAC,CAAC;CACjC;AAQD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,WAAW,CAAC,CAAC,EAAE,EAC7B,KAAK,EACL,UAAU,EACV,UAAU,EACV,YAAiB,EACjB,QAAY,EACZ,YAAY,EACZ,OAAe,EACf,OAAe,EACf,YAAgB,EAChB,gBAAiC,EACjC,UAAiB,EACjB,MAAc,EACd,SAAS,EACT,KAAK,EACL,MAAM,GACP,EAAE,gBAAgB,CAAC,CAAC,CAAC,+BAmErB"}
@@ -0,0 +1,65 @@
1
+ import { jsx as o, jsxs as z } from "react/jsx-runtime";
2
+ import { useRef as S, useImperativeHandle as L, useEffect as N } from "react";
3
+ import { useVirtualizer as V } from "@tanstack/react-virtual";
4
+ import { cn as u } from "../../lib/utils.js";
5
+ const j = /* @__PURE__ */ o("div", { className: "py-3 text-center mono text-[11px] uppercase tracking-[0.2em] text-muted-foreground", children: "Loading…" });
6
+ function H({
7
+ items: r,
8
+ renderItem: g,
9
+ getItemKey: c,
10
+ estimateSize: p = 56,
11
+ overscan: h = 6,
12
+ onEndReached: n,
13
+ hasMore: f = !1,
14
+ loading: l = !1,
15
+ endThreshold: m = 4,
16
+ loadingIndicator: v = j,
17
+ emptyState: w = null,
18
+ smooth: y = !1,
19
+ className: d,
20
+ style: x,
21
+ apiRef: I
22
+ }) {
23
+ const a = S(null), t = V({
24
+ count: r.length,
25
+ getScrollElement: () => a.current,
26
+ estimateSize: () => p,
27
+ overscan: h,
28
+ getItemKey: c ? (e) => c(r[e], e) : void 0
29
+ }), i = t.getVirtualItems(), T = y && !t.isScrolling;
30
+ return L(
31
+ I,
32
+ () => ({
33
+ scrollToIndex: (e, s) => t.scrollToIndex(e, { align: (s == null ? void 0 : s.align) ?? "auto" }),
34
+ scrollToTop: () => t.scrollToOffset(0)
35
+ }),
36
+ [t]
37
+ ), N(() => {
38
+ if (!n || !f || l) return;
39
+ const e = i[i.length - 1];
40
+ e && e.index >= r.length - 1 - m && n();
41
+ }, [i, r.length, f, l, m, n]), r.length === 0 && !l ? /* @__PURE__ */ o("div", { ref: a, className: u("overflow-y-auto", d), style: x, children: w }) : /* @__PURE__ */ z("div", { ref: a, className: u("overflow-y-auto", d), style: x, children: [
42
+ /* @__PURE__ */ o("div", { style: { position: "relative", width: "100%", height: t.getTotalSize() }, children: i.map((e) => /* @__PURE__ */ o(
43
+ "div",
44
+ {
45
+ "data-index": e.index,
46
+ ref: t.measureElement,
47
+ className: u("ds-virtual-row", T && "ds-virtual-row--smooth"),
48
+ style: {
49
+ position: "absolute",
50
+ top: 0,
51
+ left: 0,
52
+ width: "100%",
53
+ transform: `translateY(${e.start}px)`
54
+ },
55
+ children: g(r[e.index], e.index)
56
+ },
57
+ e.key
58
+ )) }),
59
+ l && v
60
+ ] });
61
+ }
62
+ export {
63
+ H as VirtualList
64
+ };
65
+ //# sourceMappingURL=virtual-list.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"virtual-list.js","sources":["../../../src/components/virtual-list/virtual-list.tsx"],"sourcesContent":["import {\n useEffect,\n useImperativeHandle,\n useRef,\n type CSSProperties,\n type Key,\n type ReactNode,\n type Ref,\n} from 'react';\nimport { useVirtualizer } from '@tanstack/react-virtual';\n\nimport { cn } from '../../lib/utils';\n\n/** Imperative handle exposed via {@link VirtualListProps.apiRef}. */\nexport interface VirtualListHandle {\n scrollToIndex: (index: number, opts?: { align?: 'auto' | 'start' | 'center' | 'end' }) => void;\n scrollToTop: () => void;\n}\n\nexport interface VirtualListProps<T> {\n /** The rows. Only the ones near the viewport are ever in the DOM. */\n items: T[];\n /** Render one row. */\n renderItem: (item: T, index: number) => ReactNode;\n /** Stable React key per row (defaults to the index). */\n getItemKey?: (item: T, index: number) => Key;\n /** First-paint height guess in px; real heights are then measured. */\n estimateSize?: number;\n /** Off-screen rows kept mounted as a scroll buffer. */\n overscan?: number;\n /**\n * Called when the last rows scroll into range — append more items here for an\n * infinite list. Guarded by `hasMore` and `loading`.\n */\n onEndReached?: () => void;\n /** Whether more pages remain (guards `onEndReached`). */\n hasMore?: boolean;\n /** Whether a load is in flight (guards `onEndReached`, shows the indicator). */\n loading?: boolean;\n /** Rows-from-the-end that trip `onEndReached`. Default 4. */\n endThreshold?: number;\n /** Shown at the bottom while `loading`. */\n loadingIndicator?: ReactNode;\n /** Shown when `items` is empty and not loading. */\n emptyState?: ReactNode;\n /**\n * Animate rows sliding to their new position when the list is re-sorted (e.g.\n * an auto-sort by \"updated\" date whose value changed), so a reorder glides\n * instead of teleporting. Only animates while the list is idle — scrolling and\n * lazy height-measurement never animate. Requires the stylesheet\n * `@gabvdl/ui/virtual-list.css` (bundled in `styles.css`) and a stable\n * `getItemKey` so React keeps each row's DOM node across the reorder. Honors\n * `prefers-reduced-motion`.\n */\n smooth?: boolean;\n /** The scroll container — MUST be given a bounded height (via `className`). */\n className?: string;\n style?: CSSProperties;\n /** Escape hatch to drive scrolling (e.g. keyboard navigation). */\n apiRef?: Ref<VirtualListHandle>;\n}\n\nconst DefaultLoading = (\n <div className=\"py-3 text-center mono text-[11px] uppercase tracking-[0.2em] text-muted-foreground\">\n Loading…\n </div>\n);\n\n/**\n * A virtualized, optionally infinite list. Only the rows near the viewport are\n * mounted (windowing), heights are measured so rows may vary, and `onEndReached`\n * fires as the tail comes into view so you can lazily append more. The scroll\n * container needs a bounded height — set one with `className` (e.g. `h-96` or\n * `flex-1 min-h-0`).\n *\n * ```tsx\n * <VirtualList\n * items={rows}\n * className=\"h-96\"\n * estimateSize={56}\n * hasMore={hasMore}\n * loading={loading}\n * onEndReached={loadNextPage}\n * renderItem={(row) => <Row {...row} />}\n * />\n * ```\n */\nexport function VirtualList<T>({\n items,\n renderItem,\n getItemKey,\n estimateSize = 56,\n overscan = 6,\n onEndReached,\n hasMore = false,\n loading = false,\n endThreshold = 4,\n loadingIndicator = DefaultLoading,\n emptyState = null,\n smooth = false,\n className,\n style,\n apiRef,\n}: VirtualListProps<T>) {\n const parentRef = useRef<HTMLDivElement>(null);\n\n const virtualizer = useVirtualizer({\n count: items.length,\n getScrollElement: () => parentRef.current,\n estimateSize: () => estimateSize,\n overscan,\n getItemKey: getItemKey ? (index) => getItemKey(items[index], index) : undefined,\n });\n\n const virtualItems = virtualizer.getVirtualItems();\n\n // Only glide rows when the list is idle: during a scroll the same reflow that\n // repositions rows (and lazy height-measurement) would otherwise animate too,\n // reading as lag. On a reorder the component re-renders while not scrolling,\n // so the target offsets change and the transition plays.\n const animate = smooth && !virtualizer.isScrolling;\n\n useImperativeHandle(\n apiRef,\n () => ({\n scrollToIndex: (index, opts) => virtualizer.scrollToIndex(index, { align: opts?.align ?? 'auto' }),\n scrollToTop: () => virtualizer.scrollToOffset(0),\n }),\n [virtualizer],\n );\n\n // Infinite load: pull the next page as the last rows enter range.\n useEffect(() => {\n if (!onEndReached || !hasMore || loading) return;\n const last = virtualItems[virtualItems.length - 1];\n if (last && last.index >= items.length - 1 - endThreshold) onEndReached();\n }, [virtualItems, items.length, hasMore, loading, endThreshold, onEndReached]);\n\n if (items.length === 0 && !loading) {\n return (\n <div ref={parentRef} className={cn('overflow-y-auto', className)} style={style}>\n {emptyState}\n </div>\n );\n }\n\n return (\n <div ref={parentRef} className={cn('overflow-y-auto', className)} style={style}>\n <div style={{ position: 'relative', width: '100%', height: virtualizer.getTotalSize() }}>\n {virtualItems.map((v) => (\n <div\n key={v.key}\n data-index={v.index}\n ref={virtualizer.measureElement}\n className={cn('ds-virtual-row', animate && 'ds-virtual-row--smooth')}\n style={{\n position: 'absolute',\n top: 0,\n left: 0,\n width: '100%',\n transform: `translateY(${v.start}px)`,\n }}\n >\n {renderItem(items[v.index], v.index)}\n </div>\n ))}\n </div>\n {loading && loadingIndicator}\n </div>\n );\n}\n"],"names":["DefaultLoading","jsx","VirtualList","items","renderItem","getItemKey","estimateSize","overscan","onEndReached","hasMore","loading","endThreshold","loadingIndicator","emptyState","smooth","className","style","apiRef","parentRef","useRef","virtualizer","useVirtualizer","index","virtualItems","animate","useImperativeHandle","opts","useEffect","last","cn","jsxs","v"],"mappings":";;;;AA8DA,MAAMA,IACJ,gBAAAC,EAAC,OAAA,EAAI,WAAU,sFAAqF,UAAA,YAEpG;AAsBK,SAASC,EAAe;AAAA,EAC7B,OAAAC;AAAA,EACA,YAAAC;AAAA,EACA,YAAAC;AAAA,EACA,cAAAC,IAAe;AAAA,EACf,UAAAC,IAAW;AAAA,EACX,cAAAC;AAAA,EACA,SAAAC,IAAU;AAAA,EACV,SAAAC,IAAU;AAAA,EACV,cAAAC,IAAe;AAAA,EACf,kBAAAC,IAAmBZ;AAAA,EACnB,YAAAa,IAAa;AAAA,EACb,QAAAC,IAAS;AAAA,EACT,WAAAC;AAAA,EACA,OAAAC;AAAA,EACA,QAAAC;AACF,GAAwB;AACtB,QAAMC,IAAYC,EAAuB,IAAI,GAEvCC,IAAcC,EAAe;AAAA,IACjC,OAAOlB,EAAM;AAAA,IACb,kBAAkB,MAAMe,EAAU;AAAA,IAClC,cAAc,MAAMZ;AAAA,IACpB,UAAAC;AAAA,IACA,YAAYF,IAAa,CAACiB,MAAUjB,EAAWF,EAAMmB,CAAK,GAAGA,CAAK,IAAI;AAAA,EAAA,CACvE,GAEKC,IAAeH,EAAY,gBAAA,GAM3BI,IAAUV,KAAU,CAACM,EAAY;AAkBvC,SAhBAK;AAAA,IACER;AAAA,IACA,OAAO;AAAA,MACL,eAAe,CAACK,GAAOI,MAASN,EAAY,cAAcE,GAAO,EAAE,QAAOI,KAAA,gBAAAA,EAAM,UAAS,OAAA,CAAQ;AAAA,MACjG,aAAa,MAAMN,EAAY,eAAe,CAAC;AAAA,IAAA;AAAA,IAEjD,CAACA,CAAW;AAAA,EAAA,GAIdO,EAAU,MAAM;AACd,QAAI,CAACnB,KAAgB,CAACC,KAAWC,EAAS;AAC1C,UAAMkB,IAAOL,EAAaA,EAAa,SAAS,CAAC;AACjD,IAAIK,KAAQA,EAAK,SAASzB,EAAM,SAAS,IAAIQ,KAAcH,EAAA;AAAA,EAC7D,GAAG,CAACe,GAAcpB,EAAM,QAAQM,GAASC,GAASC,GAAcH,CAAY,CAAC,GAEzEL,EAAM,WAAW,KAAK,CAACO,IAEvB,gBAAAT,EAAC,OAAA,EAAI,KAAKiB,GAAW,WAAWW,EAAG,mBAAmBd,CAAS,GAAG,OAAAC,GAC/D,UAAAH,EAAA,CACH,IAKF,gBAAAiB,EAAC,SAAI,KAAKZ,GAAW,WAAWW,EAAG,mBAAmBd,CAAS,GAAG,OAAAC,GAChE,UAAA;AAAA,IAAA,gBAAAf,EAAC,OAAA,EAAI,OAAO,EAAE,UAAU,YAAY,OAAO,QAAQ,QAAQmB,EAAY,kBACpE,UAAAG,EAAa,IAAI,CAACQ,MACjB,gBAAA9B;AAAA,MAAC;AAAA,MAAA;AAAA,QAEC,cAAY8B,EAAE;AAAA,QACd,KAAKX,EAAY;AAAA,QACjB,WAAWS,EAAG,kBAAkBL,KAAW,wBAAwB;AAAA,QACnE,OAAO;AAAA,UACL,UAAU;AAAA,UACV,KAAK;AAAA,UACL,MAAM;AAAA,UACN,OAAO;AAAA,UACP,WAAW,cAAcO,EAAE,KAAK;AAAA,QAAA;AAAA,QAGjC,YAAW5B,EAAM4B,EAAE,KAAK,GAAGA,EAAE,KAAK;AAAA,MAAA;AAAA,MAZ9BA,EAAE;AAAA,IAAA,CAcV,GACH;AAAA,IACCrB,KAAWE;AAAA,EAAA,GACd;AAEJ;"}
@@ -0,0 +1,12 @@
1
+ export { useCopyToClipboard } from './use-copy-to-clipboard';
2
+ export type { UseCopyOptions } from './use-copy-to-clipboard';
3
+ export { useDebouncedValue } from './use-debounced-value';
4
+ export type { UseDebouncedValueOptions } from './use-debounced-value';
5
+ export { useInfiniteScroll, useIntersection } from './use-intersection';
6
+ export type { UseInfiniteScrollOptions } from './use-intersection';
7
+ export { useLocalStorage } from './use-local-storage';
8
+ export { useLongPress } from './use-long-press';
9
+ export type { LongPressPoint, UseLongPressOptions } from './use-long-press';
10
+ export { useIsMobile, useIsTouch, useMediaQuery, usePrefersDark, usePrefersReducedMotion, } from './use-media-query';
11
+ export { useEscape, useOutsideClick, useScrollLock } from './use-overlay';
12
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/hooks/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAC7D,YAAY,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAC9D,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAC1D,YAAY,EAAE,wBAAwB,EAAE,MAAM,uBAAuB,CAAC;AACtE,OAAO,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AACxE,YAAY,EAAE,wBAAwB,EAAE,MAAM,oBAAoB,CAAC;AACnE,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AACtD,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAChD,YAAY,EAAE,cAAc,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAC5E,OAAO,EACL,WAAW,EACX,UAAU,EACV,aAAa,EACb,cAAc,EACd,uBAAuB,GACxB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,SAAS,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC"}
@@ -0,0 +1,26 @@
1
+ export interface UseCopyOptions {
2
+ /** How long `copied` stays true, in ms. */
3
+ timeout?: number;
4
+ /**
5
+ * On touch devices, offer the native share sheet first (falls back to the
6
+ * clipboard when the user dismisses it, or when sharing is unavailable).
7
+ */
8
+ share?: boolean | {
9
+ title?: string;
10
+ text?: string;
11
+ };
12
+ }
13
+ /**
14
+ * Copy text to the clipboard and flag it for a moment, so a button can swap its
15
+ * icon to a check.
16
+ *
17
+ * Handles the two ways `navigator.clipboard` is unavailable — insecure origins
18
+ * and old WebViews — by falling back to a hidden `<textarea>` + `execCommand`,
19
+ * so it works over plain HTTP on the LAN too.
20
+ */
21
+ export declare function useCopyToClipboard({ timeout, share }?: UseCopyOptions): {
22
+ copy: (text: string) => Promise<boolean>;
23
+ copied: boolean;
24
+ error: Error | null;
25
+ };
26
+ //# sourceMappingURL=use-copy-to-clipboard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-copy-to-clipboard.d.ts","sourceRoot":"","sources":["../../src/hooks/use-copy-to-clipboard.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,cAAc;IAC7B,2CAA2C;IAC3C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;OAGG;IACH,KAAK,CAAC,EAAE,OAAO,GAAG;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CACrD;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,EAAE,OAAc,EAAE,KAAa,EAAE,GAAE,cAAmB;iBAQxE,MAAM,KAAG,OAAO,CAAC,OAAO,CAAC;;;EA2BzC"}
@@ -0,0 +1,36 @@
1
+ import * as o from "react";
2
+ function f({ timeout: r = 1500, share: e = !1 } = {}) {
3
+ const [t, a] = o.useState(!1), [s, c] = o.useState(null), n = o.useRef(void 0);
4
+ return o.useEffect(() => () => clearTimeout(n.current), []), { copy: o.useCallback(
5
+ async (i) => {
6
+ if (e && typeof navigator < "u" && navigator.share)
7
+ try {
8
+ const u = typeof e == "object" ? e : {};
9
+ return await navigator.share({ ...u, text: u.text ?? i }), !0;
10
+ } catch {
11
+ }
12
+ return await l(i) ? (c(null), a(!0), clearTimeout(n.current), n.current = setTimeout(() => a(!1), r), !0) : (c(new Error("clipboard unavailable")), !1);
13
+ },
14
+ [e, r]
15
+ ), copied: t, error: s };
16
+ }
17
+ async function l(r) {
18
+ var e;
19
+ try {
20
+ if ((e = navigator.clipboard) != null && e.writeText)
21
+ return await navigator.clipboard.writeText(r), !0;
22
+ } catch {
23
+ }
24
+ try {
25
+ const t = document.createElement("textarea");
26
+ t.value = r, t.setAttribute("readonly", ""), t.style.cssText = "position:fixed;top:0;left:-9999px;opacity:0", document.body.appendChild(t), t.select();
27
+ const a = document.execCommand("copy");
28
+ return t.remove(), a;
29
+ } catch {
30
+ return !1;
31
+ }
32
+ }
33
+ export {
34
+ f as useCopyToClipboard
35
+ };
36
+ //# sourceMappingURL=use-copy-to-clipboard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-copy-to-clipboard.js","sources":["../../src/hooks/use-copy-to-clipboard.ts"],"sourcesContent":["import * as React from 'react';\n\nexport interface UseCopyOptions {\n /** How long `copied` stays true, in ms. */\n timeout?: number;\n /**\n * On touch devices, offer the native share sheet first (falls back to the\n * clipboard when the user dismisses it, or when sharing is unavailable).\n */\n share?: boolean | { title?: string; text?: string };\n}\n\n/**\n * Copy text to the clipboard and flag it for a moment, so a button can swap its\n * icon to a check.\n *\n * Handles the two ways `navigator.clipboard` is unavailable — insecure origins\n * and old WebViews — by falling back to a hidden `<textarea>` + `execCommand`,\n * so it works over plain HTTP on the LAN too.\n */\nexport function useCopyToClipboard({ timeout = 1500, share = false }: UseCopyOptions = {}) {\n const [copied, setCopied] = React.useState(false);\n const [error, setError] = React.useState<Error | null>(null);\n const timer = React.useRef<ReturnType<typeof setTimeout> | undefined>(undefined);\n\n React.useEffect(() => () => clearTimeout(timer.current), []);\n\n const copy = React.useCallback(\n async (text: string): Promise<boolean> => {\n if (share && typeof navigator !== 'undefined' && navigator.share) {\n try {\n const meta = typeof share === 'object' ? share : {};\n await navigator.share({ ...meta, text: meta.text ?? text });\n return true; // shared, not copied — no check mark to show\n } catch {\n /* dismissed or unsupported — fall through to the clipboard */\n }\n }\n\n const ok = await writeClipboard(text);\n if (!ok) {\n setError(new Error('clipboard unavailable'));\n return false;\n }\n\n setError(null);\n setCopied(true);\n clearTimeout(timer.current);\n timer.current = setTimeout(() => setCopied(false), timeout);\n return true;\n },\n [share, timeout],\n );\n\n return { copy, copied, error };\n}\n\nasync function writeClipboard(text: string): Promise<boolean> {\n try {\n if (navigator.clipboard?.writeText) {\n await navigator.clipboard.writeText(text);\n return true;\n }\n } catch {\n /* blocked (insecure origin, permissions) — try the legacy path */\n }\n\n try {\n const ta = document.createElement('textarea');\n ta.value = text;\n ta.setAttribute('readonly', '');\n ta.style.cssText = 'position:fixed;top:0;left:-9999px;opacity:0';\n document.body.appendChild(ta);\n ta.select();\n const ok = document.execCommand('copy');\n ta.remove();\n return ok;\n } catch {\n return false;\n }\n}\n"],"names":["useCopyToClipboard","timeout","share","copied","setCopied","React","error","setError","timer","text","meta","writeClipboard","_a","ta","ok"],"mappings":";AAoBO,SAASA,EAAmB,EAAE,SAAAC,IAAU,MAAM,OAAAC,IAAQ,GAAA,IAA0B,IAAI;AACzF,QAAM,CAACC,GAAQC,CAAS,IAAIC,EAAM,SAAS,EAAK,GAC1C,CAACC,GAAOC,CAAQ,IAAIF,EAAM,SAAuB,IAAI,GACrDG,IAAQH,EAAM,OAAkD,MAAS;AAE/E,SAAAA,EAAM,UAAU,MAAM,MAAM,aAAaG,EAAM,OAAO,GAAG,EAAE,GA6BpD,EAAE,MA3BIH,EAAM;AAAA,IACjB,OAAOI,MAAmC;AACxC,UAAIP,KAAS,OAAO,YAAc,OAAe,UAAU;AACzD,YAAI;AACF,gBAAMQ,IAAO,OAAOR,KAAU,WAAWA,IAAQ,CAAA;AACjD,uBAAM,UAAU,MAAM,EAAE,GAAGQ,GAAM,MAAMA,EAAK,QAAQD,GAAM,GACnD;AAAA,QACT,QAAQ;AAAA,QAER;AAIF,aADW,MAAME,EAAeF,CAAI,KAMpCF,EAAS,IAAI,GACbH,EAAU,EAAI,GACd,aAAaI,EAAM,OAAO,GAC1BA,EAAM,UAAU,WAAW,MAAMJ,EAAU,EAAK,GAAGH,CAAO,GACnD,OARLM,EAAS,IAAI,MAAM,uBAAuB,CAAC,GACpC;AAAA,IAQX;AAAA,IACA,CAACL,GAAOD,CAAO;AAAA,EAAA,GAGF,QAAAE,GAAQ,OAAAG,EAAA;AACzB;AAEA,eAAeK,EAAeF,GAAgC;;AAC5D,MAAI;AACF,SAAIG,IAAA,UAAU,cAAV,QAAAA,EAAqB;AACvB,mBAAM,UAAU,UAAU,UAAUH,CAAI,GACjC;AAAA,EAEX,QAAQ;AAAA,EAER;AAEA,MAAI;AACF,UAAMI,IAAK,SAAS,cAAc,UAAU;AAC5C,IAAAA,EAAG,QAAQJ,GACXI,EAAG,aAAa,YAAY,EAAE,GAC9BA,EAAG,MAAM,UAAU,+CACnB,SAAS,KAAK,YAAYA,CAAE,GAC5BA,EAAG,OAAA;AACH,UAAMC,IAAK,SAAS,YAAY,MAAM;AACtC,WAAAD,EAAG,OAAA,GACIC;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;"}
@@ -0,0 +1,23 @@
1
+ export interface UseDebouncedValueOptions {
2
+ /**
3
+ * Skip the delay when the value becomes "empty" (empty string / null /
4
+ * undefined / empty array), so clearing a search box repaints the full list
5
+ * immediately instead of after the debounce window. Default true.
6
+ */
7
+ leadingOnEmpty?: boolean;
8
+ }
9
+ /**
10
+ * Trail a fast-changing value (a search query, a slider) by `delay` ms so the
11
+ * expensive consumer downstream only sees it once the user pauses.
12
+ *
13
+ * Returns the debounced value plus `pending` — true while the live value has
14
+ * moved on but the debounced one hasn't caught up yet, which is what a search
15
+ * box hangs its spinner on.
16
+ *
17
+ * ```tsx
18
+ * const [debounced, pending] = useDebouncedValue(query, 400);
19
+ * const results = useMemo(() => search(debounced), [debounced]);
20
+ * ```
21
+ */
22
+ export declare function useDebouncedValue<T>(value: T, delay?: number, { leadingOnEmpty }?: UseDebouncedValueOptions): [T, boolean];
23
+ //# sourceMappingURL=use-debounced-value.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-debounced-value.d.ts","sourceRoot":"","sources":["../../src/hooks/use-debounced-value.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,wBAAwB;IACvC;;;;OAIG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AASD;;;;;;;;;;;;GAYG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,EACjC,KAAK,EAAE,CAAC,EACR,KAAK,SAAM,EACX,EAAE,cAAqB,EAAE,GAAE,wBAA6B,GACvD,CAAC,CAAC,EAAE,OAAO,CAAC,CAiBd"}
@@ -0,0 +1,19 @@
1
+ import { useState as c, useRef as i, useEffect as f } from "react";
2
+ function m(t) {
3
+ return t == null ? !0 : typeof t == "string" ? t.trim() === "" : Array.isArray(t) ? t.length === 0 : !1;
4
+ }
5
+ function p(t, r = 400, { leadingOnEmpty: e = !0 } = {}) {
6
+ const [n, s] = c(t), o = i(t);
7
+ return o.current = t, f(() => {
8
+ if (r <= 0 || e && m(t)) {
9
+ s(t);
10
+ return;
11
+ }
12
+ const u = setTimeout(() => s(o.current), r);
13
+ return () => clearTimeout(u);
14
+ }, [t, r, e]), [n, !Object.is(n, t)];
15
+ }
16
+ export {
17
+ p as useDebouncedValue
18
+ };
19
+ //# sourceMappingURL=use-debounced-value.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-debounced-value.js","sources":["../../src/hooks/use-debounced-value.ts"],"sourcesContent":["import { useEffect, useRef, useState } from 'react';\n\nexport interface UseDebouncedValueOptions {\n /**\n * Skip the delay when the value becomes \"empty\" (empty string / null /\n * undefined / empty array), so clearing a search box repaints the full list\n * immediately instead of after the debounce window. Default true.\n */\n leadingOnEmpty?: boolean;\n}\n\nfunction isEmpty(value: unknown): boolean {\n if (value == null) return true;\n if (typeof value === 'string') return value.trim() === '';\n if (Array.isArray(value)) return value.length === 0;\n return false;\n}\n\n/**\n * Trail a fast-changing value (a search query, a slider) by `delay` ms so the\n * expensive consumer downstream only sees it once the user pauses.\n *\n * Returns the debounced value plus `pending` — true while the live value has\n * moved on but the debounced one hasn't caught up yet, which is what a search\n * box hangs its spinner on.\n *\n * ```tsx\n * const [debounced, pending] = useDebouncedValue(query, 400);\n * const results = useMemo(() => search(debounced), [debounced]);\n * ```\n */\nexport function useDebouncedValue<T>(\n value: T,\n delay = 400,\n { leadingOnEmpty = true }: UseDebouncedValueOptions = {},\n): [T, boolean] {\n const [debounced, setDebounced] = useState(value);\n // Keep the latest value in a ref so the flush on unmount/delay-change can't\n // resurrect a stale one.\n const latest = useRef(value);\n latest.current = value;\n\n useEffect(() => {\n if (delay <= 0 || (leadingOnEmpty && isEmpty(value))) {\n setDebounced(value);\n return;\n }\n const t = setTimeout(() => setDebounced(latest.current), delay);\n return () => clearTimeout(t);\n }, [value, delay, leadingOnEmpty]);\n\n return [debounced, !Object.is(debounced, value)];\n}\n"],"names":["isEmpty","value","useDebouncedValue","delay","leadingOnEmpty","debounced","setDebounced","useState","latest","useRef","useEffect","t"],"mappings":";AAWA,SAASA,EAAQC,GAAyB;AACxC,SAAIA,KAAS,OAAa,KACtB,OAAOA,KAAU,WAAiBA,EAAM,WAAW,KACnD,MAAM,QAAQA,CAAK,IAAUA,EAAM,WAAW,IAC3C;AACT;AAeO,SAASC,EACdD,GACAE,IAAQ,KACR,EAAE,gBAAAC,IAAiB,GAAA,IAAmC,IACxC;AACd,QAAM,CAACC,GAAWC,CAAY,IAAIC,EAASN,CAAK,GAG1CO,IAASC,EAAOR,CAAK;AAC3B,SAAAO,EAAO,UAAUP,GAEjBS,EAAU,MAAM;AACd,QAAIP,KAAS,KAAMC,KAAkBJ,EAAQC,CAAK,GAAI;AACpD,MAAAK,EAAaL,CAAK;AAClB;AAAA,IACF;AACA,UAAMU,IAAI,WAAW,MAAML,EAAaE,EAAO,OAAO,GAAGL,CAAK;AAC9D,WAAO,MAAM,aAAaQ,CAAC;AAAA,EAC7B,GAAG,CAACV,GAAOE,GAAOC,CAAc,CAAC,GAE1B,CAACC,GAAW,CAAC,OAAO,GAAGA,GAAWJ,CAAK,CAAC;AACjD;"}
@@ -0,0 +1,25 @@
1
+ import * as React from 'react';
2
+ /**
3
+ * Fire `onIntersect` while a sentinel element is (near) the viewport.
4
+ *
5
+ * The paged-reveal counterpart to `VirtualList`'s `onEndReached`: put the
6
+ * returned ref on a div at the bottom of a plain grid and it loads the next
7
+ * page before the user reaches it (`rootMargin` looks ahead by default).
8
+ */
9
+ export declare function useIntersection<T extends HTMLElement = HTMLDivElement>(onIntersect: () => void, { enabled, rootMargin, threshold, }?: {
10
+ enabled?: boolean;
11
+ rootMargin?: string;
12
+ threshold?: number;
13
+ }): React.RefObject<T | null>;
14
+ export interface UseInfiniteScrollOptions {
15
+ hasMore: boolean;
16
+ loading?: boolean;
17
+ onLoadMore: () => void;
18
+ rootMargin?: string;
19
+ }
20
+ /**
21
+ * `useIntersection` wired for pagination: it stops observing once there is
22
+ * nothing more to load, and never re-enters while a page is in flight.
23
+ */
24
+ export declare function useInfiniteScroll<T extends HTMLElement = HTMLDivElement>({ hasMore, loading, onLoadMore, rootMargin, }: UseInfiniteScrollOptions): React.RefObject<T | null>;
25
+ //# sourceMappingURL=use-intersection.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-intersection.d.ts","sourceRoot":"","sources":["../../src/hooks/use-intersection.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,CAAC,SAAS,WAAW,GAAG,cAAc,EACpE,WAAW,EAAE,MAAM,IAAI,EACvB,EACE,OAAc,EACd,UAAoB,EACpB,SAAa,GACd,GAAE;IAAE,OAAO,CAAC,EAAE,OAAO,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAO,6BAoBvE;AAED,MAAM,WAAW,wBAAwB;IACvC,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,UAAU,EAAE,MAAM,IAAI,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,SAAS,WAAW,GAAG,cAAc,EAAE,EACxE,OAAO,EACP,OAAe,EACf,UAAU,EACV,UAAU,GACX,EAAE,wBAAwB,6BAO1B"}
@@ -0,0 +1,38 @@
1
+ import * as c from "react";
2
+ function l(e, {
3
+ enabled: n = !0,
4
+ rootMargin: t = "600px",
5
+ threshold: r = 0
6
+ } = {}) {
7
+ const s = c.useRef(null), u = c.useRef(e);
8
+ return u.current = e, c.useEffect(() => {
9
+ const f = s.current;
10
+ if (!f || !n || typeof IntersectionObserver > "u") return;
11
+ const i = new IntersectionObserver(
12
+ (b) => {
13
+ var o;
14
+ (o = b[0]) != null && o.isIntersecting && u.current();
15
+ },
16
+ { rootMargin: t, threshold: r }
17
+ );
18
+ return i.observe(f), () => i.disconnect();
19
+ }, [n, t, r]), s;
20
+ }
21
+ function p({
22
+ hasMore: e,
23
+ loading: n = !1,
24
+ onLoadMore: t,
25
+ rootMargin: r
26
+ }) {
27
+ return l(
28
+ () => {
29
+ e && !n && t();
30
+ },
31
+ { enabled: e && !n, rootMargin: r }
32
+ );
33
+ }
34
+ export {
35
+ p as useInfiniteScroll,
36
+ l as useIntersection
37
+ };
38
+ //# sourceMappingURL=use-intersection.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-intersection.js","sources":["../../src/hooks/use-intersection.ts"],"sourcesContent":["import * as React from 'react';\n\n/**\n * Fire `onIntersect` while a sentinel element is (near) the viewport.\n *\n * The paged-reveal counterpart to `VirtualList`'s `onEndReached`: put the\n * returned ref on a div at the bottom of a plain grid and it loads the next\n * page before the user reaches it (`rootMargin` looks ahead by default).\n */\nexport function useIntersection<T extends HTMLElement = HTMLDivElement>(\n onIntersect: () => void,\n {\n enabled = true,\n rootMargin = '600px',\n threshold = 0,\n }: { enabled?: boolean; rootMargin?: string; threshold?: number } = {},\n) {\n const ref = React.useRef<T | null>(null);\n const cb = React.useRef(onIntersect);\n cb.current = onIntersect;\n\n React.useEffect(() => {\n const el = ref.current;\n if (!el || !enabled || typeof IntersectionObserver === 'undefined') return;\n const io = new IntersectionObserver(\n (entries) => {\n if (entries[0]?.isIntersecting) cb.current();\n },\n { rootMargin, threshold },\n );\n io.observe(el);\n return () => io.disconnect();\n }, [enabled, rootMargin, threshold]);\n\n return ref;\n}\n\nexport interface UseInfiniteScrollOptions {\n hasMore: boolean;\n loading?: boolean;\n onLoadMore: () => void;\n rootMargin?: string;\n}\n\n/**\n * `useIntersection` wired for pagination: it stops observing once there is\n * nothing more to load, and never re-enters while a page is in flight.\n */\nexport function useInfiniteScroll<T extends HTMLElement = HTMLDivElement>({\n hasMore,\n loading = false,\n onLoadMore,\n rootMargin,\n}: UseInfiniteScrollOptions) {\n return useIntersection<T>(\n () => {\n if (hasMore && !loading) onLoadMore();\n },\n { enabled: hasMore && !loading, rootMargin },\n );\n}\n"],"names":["useIntersection","onIntersect","enabled","rootMargin","threshold","ref","React","cb","el","io","entries","_a","useInfiniteScroll","hasMore","loading","onLoadMore"],"mappings":";AASO,SAASA,EACdC,GACA;AAAA,EACE,SAAAC,IAAU;AAAA,EACV,YAAAC,IAAa;AAAA,EACb,WAAAC,IAAY;AACd,IAAoE,IACpE;AACA,QAAMC,IAAMC,EAAM,OAAiB,IAAI,GACjCC,IAAKD,EAAM,OAAOL,CAAW;AACnC,SAAAM,EAAG,UAAUN,GAEbK,EAAM,UAAU,MAAM;AACpB,UAAME,IAAKH,EAAI;AACf,QAAI,CAACG,KAAM,CAACN,KAAW,OAAO,uBAAyB,IAAa;AACpE,UAAMO,IAAK,IAAI;AAAA,MACb,CAACC,MAAY;;AACX,SAAIC,IAAAD,EAAQ,CAAC,MAAT,QAAAC,EAAY,oBAAmB,QAAA;AAAA,MACrC;AAAA,MACA,EAAE,YAAAR,GAAY,WAAAC,EAAA;AAAA,IAAU;AAE1B,WAAAK,EAAG,QAAQD,CAAE,GACN,MAAMC,EAAG,WAAA;AAAA,EAClB,GAAG,CAACP,GAASC,GAAYC,CAAS,CAAC,GAE5BC;AACT;AAaO,SAASO,EAA0D;AAAA,EACxE,SAAAC;AAAA,EACA,SAAAC,IAAU;AAAA,EACV,YAAAC;AAAA,EACA,YAAAZ;AACF,GAA6B;AAC3B,SAAOH;AAAA,IACL,MAAM;AACJ,MAAIa,KAAW,CAACC,KAASC,EAAA;AAAA,IAC3B;AAAA,IACA,EAAE,SAASF,KAAW,CAACC,GAAS,YAAAX,EAAA;AAAA,EAAW;AAE/C;"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * `useState` backed by `localStorage` (or `sessionStorage`).
3
+ *
4
+ * Reads once on mount, writes on every change, and swallows quota/private-mode
5
+ * errors so a full disk can never crash the app. Tabs stay in sync: a `storage`
6
+ * event from another tab, or a write from another hook instance on the same
7
+ * key, updates every subscriber.
8
+ */
9
+ export declare function useLocalStorage<T>(key: string, initial: T, { session }?: {
10
+ session?: boolean;
11
+ }): [T, (value: T | ((prev: T) => T)) => void];
12
+ //# sourceMappingURL=use-local-storage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-local-storage.d.ts","sourceRoot":"","sources":["../../src/hooks/use-local-storage.ts"],"names":[],"mappings":"AAEA;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAC/B,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,CAAC,EACV,EAAE,OAAe,EAAE,GAAE;IAAE,OAAO,CAAC,EAAE,OAAO,CAAA;CAAO,GAC9C,CAAC,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,KAAK,IAAI,CAAC,CAkD5C"}
@@ -0,0 +1,43 @@
1
+ import * as s from "react";
2
+ function v(e, i, { session: c = !1 } = {}) {
3
+ const l = () => typeof window > "u" ? null : c ? window.sessionStorage : window.localStorage, r = s.useCallback(() => {
4
+ var t;
5
+ try {
6
+ const n = (t = l()) == null ? void 0 : t.getItem(e);
7
+ return n == null ? i : JSON.parse(n);
8
+ } catch {
9
+ return i;
10
+ }
11
+ }, [e, c]), [w, a] = s.useState(r);
12
+ s.useEffect(() => {
13
+ a(r());
14
+ const t = (o) => {
15
+ (o.key === e || o.key === null) && a(r());
16
+ }, n = (o) => {
17
+ o.detail === e && a(r());
18
+ };
19
+ return window.addEventListener("storage", t), window.addEventListener(d, n), () => {
20
+ window.removeEventListener("storage", t), window.removeEventListener(d, n);
21
+ };
22
+ }, [e, r]);
23
+ const f = s.useCallback(
24
+ (t) => {
25
+ a((n) => {
26
+ var u;
27
+ const o = typeof t == "function" ? t(n) : t;
28
+ try {
29
+ (u = l()) == null || u.setItem(e, JSON.stringify(o)), window.dispatchEvent(new CustomEvent(d, { detail: e }));
30
+ } catch {
31
+ }
32
+ return o;
33
+ });
34
+ },
35
+ [e, c]
36
+ );
37
+ return [w, f];
38
+ }
39
+ const d = "gabvdl-ui:storage";
40
+ export {
41
+ v as useLocalStorage
42
+ };
43
+ //# sourceMappingURL=use-local-storage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-local-storage.js","sources":["../../src/hooks/use-local-storage.ts"],"sourcesContent":["import * as React from 'react';\n\n/**\n * `useState` backed by `localStorage` (or `sessionStorage`).\n *\n * Reads once on mount, writes on every change, and swallows quota/private-mode\n * errors so a full disk can never crash the app. Tabs stay in sync: a `storage`\n * event from another tab, or a write from another hook instance on the same\n * key, updates every subscriber.\n */\nexport function useLocalStorage<T>(\n key: string,\n initial: T,\n { session = false }: { session?: boolean } = {},\n): [T, (value: T | ((prev: T) => T)) => void] {\n const store = () =>\n typeof window === 'undefined' ? null : session ? window.sessionStorage : window.localStorage;\n\n const read = React.useCallback((): T => {\n try {\n const raw = store()?.getItem(key);\n return raw == null ? initial : (JSON.parse(raw) as T);\n } catch {\n return initial;\n }\n // `initial` is intentionally not a dep: it is the mount-time default only.\n }, [key, session]);\n\n const [value, setValue] = React.useState<T>(read);\n\n // A key change (or a cross-tab write) re-reads from storage.\n React.useEffect(() => {\n setValue(read());\n const onStorage = (e: StorageEvent) => {\n if (e.key === key || e.key === null) setValue(read());\n };\n const onLocal = (e: Event) => {\n if ((e as CustomEvent<string>).detail === key) setValue(read());\n };\n window.addEventListener('storage', onStorage);\n window.addEventListener(SYNC_EVENT, onLocal);\n return () => {\n window.removeEventListener('storage', onStorage);\n window.removeEventListener(SYNC_EVENT, onLocal);\n };\n }, [key, read]);\n\n const set = React.useCallback(\n (next: T | ((prev: T) => T)) => {\n setValue((prev) => {\n const resolved = typeof next === 'function' ? (next as (p: T) => T)(prev) : next;\n try {\n store()?.setItem(key, JSON.stringify(resolved));\n window.dispatchEvent(new CustomEvent(SYNC_EVENT, { detail: key }));\n } catch {\n /* quota exceeded / storage disabled — keep the in-memory value */\n }\n return resolved;\n });\n },\n [key, session],\n );\n\n return [value, set];\n}\n\nconst SYNC_EVENT = 'gabvdl-ui:storage';\n"],"names":["useLocalStorage","key","initial","session","store","read","React","raw","_a","value","setValue","onStorage","e","onLocal","SYNC_EVENT","set","next","prev","resolved"],"mappings":";AAUO,SAASA,EACdC,GACAC,GACA,EAAE,SAAAC,IAAU,GAAA,IAAiC,IACD;AAC5C,QAAMC,IAAQ,MACZ,OAAO,SAAW,MAAc,OAAOD,IAAU,OAAO,iBAAiB,OAAO,cAE5EE,IAAOC,EAAM,YAAY,MAAS;;AACtC,QAAI;AACF,YAAMC,KAAMC,IAAAJ,QAAA,gBAAAI,EAAS,QAAQP;AAC7B,aAAOM,KAAO,OAAOL,IAAW,KAAK,MAAMK,CAAG;AAAA,IAChD,QAAQ;AACN,aAAOL;AAAA,IACT;AAAA,EAEF,GAAG,CAACD,GAAKE,CAAO,CAAC,GAEX,CAACM,GAAOC,CAAQ,IAAIJ,EAAM,SAAYD,CAAI;AAGhD,EAAAC,EAAM,UAAU,MAAM;AACpB,IAAAI,EAASL,GAAM;AACf,UAAMM,IAAY,CAACC,MAAoB;AACrC,OAAIA,EAAE,QAAQX,KAAOW,EAAE,QAAQ,SAAMF,EAASL,GAAM;AAAA,IACtD,GACMQ,IAAU,CAACD,MAAa;AAC5B,MAAKA,EAA0B,WAAWX,KAAKS,EAASL,GAAM;AAAA,IAChE;AACA,kBAAO,iBAAiB,WAAWM,CAAS,GAC5C,OAAO,iBAAiBG,GAAYD,CAAO,GACpC,MAAM;AACX,aAAO,oBAAoB,WAAWF,CAAS,GAC/C,OAAO,oBAAoBG,GAAYD,CAAO;AAAA,IAChD;AAAA,EACF,GAAG,CAACZ,GAAKI,CAAI,CAAC;AAEd,QAAMU,IAAMT,EAAM;AAAA,IAChB,CAACU,MAA+B;AAC9B,MAAAN,EAAS,CAACO,MAAS;;AACjB,cAAMC,IAAW,OAAOF,KAAS,aAAcA,EAAqBC,CAAI,IAAID;AAC5E,YAAI;AACF,WAAAR,IAAAJ,EAAA,MAAA,QAAAI,EAAS,QAAQP,GAAK,KAAK,UAAUiB,CAAQ,IAC7C,OAAO,cAAc,IAAI,YAAYJ,GAAY,EAAE,QAAQb,EAAA,CAAK,CAAC;AAAA,QACnE,QAAQ;AAAA,QAER;AACA,eAAOiB;AAAA,MACT,CAAC;AAAA,IACH;AAAA,IACA,CAACjB,GAAKE,CAAO;AAAA,EAAA;AAGf,SAAO,CAACM,GAAOM,CAAG;AACpB;AAEA,MAAMD,IAAa;"}
@@ -0,0 +1,37 @@
1
+ import * as React from 'react';
2
+ export interface LongPressPoint {
3
+ x: number;
4
+ y: number;
5
+ }
6
+ export interface UseLongPressOptions {
7
+ /** Hold duration before it fires, in ms. */
8
+ delay?: number;
9
+ /** Movement (px) that cancels the press — a scroll, not a hold. */
10
+ moveTolerance?: number;
11
+ /** Also fire on a desktop right-click (contextmenu). Default true. */
12
+ contextMenu?: boolean;
13
+ /** Fired on a plain tap/click that never became a long press. */
14
+ onClick?: (e: React.MouseEvent) => void;
15
+ }
16
+ /**
17
+ * Long-press (and right-click) as a gesture, with the press coordinates — so a
18
+ * context menu can be anchored where the finger actually landed.
19
+ *
20
+ * Spread the returned props onto any element. After the gesture fires, the
21
+ * ensuing `click` is swallowed once: without that, a long press on a link both
22
+ * opens the menu and follows the link.
23
+ */
24
+ export declare function useLongPress(onLongPress: (point: LongPressPoint) => void, { delay, moveTolerance, contextMenu, onClick }?: UseLongPressOptions): {
25
+ onPointerDown: (e: React.PointerEvent) => void;
26
+ onPointerMove: (e: React.PointerEvent) => void;
27
+ onPointerUp: () => void;
28
+ onPointerLeave: () => void;
29
+ onPointerCancel: () => void;
30
+ onContextMenu: (e: React.MouseEvent) => void;
31
+ onClick: (e: React.MouseEvent) => void;
32
+ style: {
33
+ WebkitTouchCallout: "none";
34
+ touchAction: "manipulation";
35
+ };
36
+ };
37
+ //# sourceMappingURL=use-long-press.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-long-press.d.ts","sourceRoot":"","sources":["../../src/hooks/use-long-press.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,MAAM,WAAW,cAAc;IAC7B,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;CACX;AAED,MAAM,WAAW,mBAAmB;IAClC,4CAA4C;IAC5C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,mEAAmE;IACnE,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,sEAAsE;IACtE,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,iEAAiE;IACjE,OAAO,CAAC,EAAE,CAAC,CAAC,EAAE,KAAK,CAAC,UAAU,KAAK,IAAI,CAAC;CACzC;AAED;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAC1B,WAAW,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,IAAI,EAC5C,EAAE,KAAW,EAAE,aAAkB,EAAE,WAAkB,EAAE,OAAO,EAAE,GAAE,mBAAwB;uBAyBrE,KAAK,CAAC,YAAY;uBAIlB,KAAK,CAAC,YAAY;;;;uBAQlB,KAAK,CAAC,UAAU;iBAOtB,KAAK,CAAC,UAAU;;;;;EAYhC"}
@@ -0,0 +1,39 @@
1
+ import * as r from "react";
2
+ function m(l, { delay: a = 450, moveTolerance: s = 10, contextMenu: f = !0, onClick: o } = {}) {
3
+ const c = r.useRef(void 0), u = r.useRef(null), n = r.useRef(!1), e = r.useCallback(() => {
4
+ clearTimeout(c.current), u.current = null;
5
+ }, []);
6
+ r.useEffect(() => e, [e]);
7
+ const p = (t) => {
8
+ n.current = !1, u.current = t, clearTimeout(c.current), c.current = setTimeout(() => {
9
+ n.current = !0, u.current = null, l(t);
10
+ }, a);
11
+ };
12
+ return {
13
+ onPointerDown: (t) => {
14
+ t.pointerType === "mouse" && t.button !== 0 || p({ x: t.clientX, y: t.clientY });
15
+ },
16
+ onPointerMove: (t) => {
17
+ const i = u.current;
18
+ i && Math.hypot(t.clientX - i.x, t.clientY - i.y) > s && e();
19
+ },
20
+ onPointerUp: e,
21
+ onPointerLeave: e,
22
+ onPointerCancel: e,
23
+ onContextMenu: (t) => {
24
+ f && (t.preventDefault(), e(), n.current = !0, l({ x: t.clientX, y: t.clientY }));
25
+ },
26
+ onClick: (t) => {
27
+ if (n.current) {
28
+ t.preventDefault(), t.stopPropagation(), n.current = !1;
29
+ return;
30
+ }
31
+ o == null || o(t);
32
+ },
33
+ style: { WebkitTouchCallout: "none", touchAction: "manipulation" }
34
+ };
35
+ }
36
+ export {
37
+ m as useLongPress
38
+ };
39
+ //# sourceMappingURL=use-long-press.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-long-press.js","sources":["../../src/hooks/use-long-press.ts"],"sourcesContent":["import * as React from 'react';\n\nexport interface LongPressPoint {\n x: number;\n y: number;\n}\n\nexport interface UseLongPressOptions {\n /** Hold duration before it fires, in ms. */\n delay?: number;\n /** Movement (px) that cancels the press — a scroll, not a hold. */\n moveTolerance?: number;\n /** Also fire on a desktop right-click (contextmenu). Default true. */\n contextMenu?: boolean;\n /** Fired on a plain tap/click that never became a long press. */\n onClick?: (e: React.MouseEvent) => void;\n}\n\n/**\n * Long-press (and right-click) as a gesture, with the press coordinates — so a\n * context menu can be anchored where the finger actually landed.\n *\n * Spread the returned props onto any element. After the gesture fires, the\n * ensuing `click` is swallowed once: without that, a long press on a link both\n * opens the menu and follows the link.\n */\nexport function useLongPress(\n onLongPress: (point: LongPressPoint) => void,\n { delay = 450, moveTolerance = 10, contextMenu = true, onClick }: UseLongPressOptions = {},\n) {\n const timer = React.useRef<ReturnType<typeof setTimeout> | undefined>(undefined);\n const start = React.useRef<LongPressPoint | null>(null);\n const fired = React.useRef(false);\n\n const cancel = React.useCallback(() => {\n clearTimeout(timer.current);\n start.current = null;\n }, []);\n\n React.useEffect(() => cancel, [cancel]);\n\n const begin = (point: LongPressPoint) => {\n fired.current = false;\n start.current = point;\n clearTimeout(timer.current);\n timer.current = setTimeout(() => {\n fired.current = true;\n start.current = null;\n onLongPress(point);\n }, delay);\n };\n\n return {\n onPointerDown: (e: React.PointerEvent) => {\n if (e.pointerType === 'mouse' && e.button !== 0) return; // right-click has its own path\n begin({ x: e.clientX, y: e.clientY });\n },\n onPointerMove: (e: React.PointerEvent) => {\n const from = start.current;\n if (!from) return;\n if (Math.hypot(e.clientX - from.x, e.clientY - from.y) > moveTolerance) cancel();\n },\n onPointerUp: cancel,\n onPointerLeave: cancel,\n onPointerCancel: cancel,\n onContextMenu: (e: React.MouseEvent) => {\n if (!contextMenu) return;\n e.preventDefault();\n cancel();\n fired.current = true;\n onLongPress({ x: e.clientX, y: e.clientY });\n },\n onClick: (e: React.MouseEvent) => {\n if (fired.current) {\n // The gesture already handled this press — don't also navigate.\n e.preventDefault();\n e.stopPropagation();\n fired.current = false;\n return;\n }\n onClick?.(e);\n },\n style: { WebkitTouchCallout: 'none' as const, touchAction: 'manipulation' as const },\n };\n}\n"],"names":["useLongPress","onLongPress","delay","moveTolerance","contextMenu","onClick","timer","React","start","fired","cancel","begin","point","e","from"],"mappings":";AA0BO,SAASA,EACdC,GACA,EAAE,OAAAC,IAAQ,KAAK,eAAAC,IAAgB,IAAI,aAAAC,IAAc,IAAM,SAAAC,EAAA,IAAiC,CAAA,GACxF;AACA,QAAMC,IAAQC,EAAM,OAAkD,MAAS,GACzEC,IAAQD,EAAM,OAA8B,IAAI,GAChDE,IAAQF,EAAM,OAAO,EAAK,GAE1BG,IAASH,EAAM,YAAY,MAAM;AACrC,iBAAaD,EAAM,OAAO,GAC1BE,EAAM,UAAU;AAAA,EAClB,GAAG,CAAA,CAAE;AAEL,EAAAD,EAAM,UAAU,MAAMG,GAAQ,CAACA,CAAM,CAAC;AAEtC,QAAMC,IAAQ,CAACC,MAA0B;AACvC,IAAAH,EAAM,UAAU,IAChBD,EAAM,UAAUI,GAChB,aAAaN,EAAM,OAAO,GAC1BA,EAAM,UAAU,WAAW,MAAM;AAC/B,MAAAG,EAAM,UAAU,IAChBD,EAAM,UAAU,MAChBP,EAAYW,CAAK;AAAA,IACnB,GAAGV,CAAK;AAAA,EACV;AAEA,SAAO;AAAA,IACL,eAAe,CAACW,MAA0B;AACxC,MAAIA,EAAE,gBAAgB,WAAWA,EAAE,WAAW,KAC9CF,EAAM,EAAE,GAAGE,EAAE,SAAS,GAAGA,EAAE,SAAS;AAAA,IACtC;AAAA,IACA,eAAe,CAACA,MAA0B;AACxC,YAAMC,IAAON,EAAM;AACnB,MAAKM,KACD,KAAK,MAAMD,EAAE,UAAUC,EAAK,GAAGD,EAAE,UAAUC,EAAK,CAAC,IAAIX,KAAeO,EAAA;AAAA,IAC1E;AAAA,IACA,aAAaA;AAAA,IACb,gBAAgBA;AAAA,IAChB,iBAAiBA;AAAA,IACjB,eAAe,CAACG,MAAwB;AACtC,MAAKT,MACLS,EAAE,eAAA,GACFH,EAAA,GACAD,EAAM,UAAU,IAChBR,EAAY,EAAE,GAAGY,EAAE,SAAS,GAAGA,EAAE,SAAS;AAAA,IAC5C;AAAA,IACA,SAAS,CAACA,MAAwB;AAChC,UAAIJ,EAAM,SAAS;AAEjB,QAAAI,EAAE,eAAA,GACFA,EAAE,gBAAA,GACFJ,EAAM,UAAU;AAChB;AAAA,MACF;AACA,MAAAJ,KAAA,QAAAA,EAAUQ;AAAA,IACZ;AAAA,IACA,OAAO,EAAE,oBAAoB,QAAiB,aAAa,eAAA;AAAA,EAAwB;AAEvF;"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Subscribe to a CSS media query.
3
+ *
4
+ * SSR-safe (`false` on the server, resolved on hydration) and driven by
5
+ * `useSyncExternalStore`, so several components watching the same query share
6
+ * one listener and never tear.
7
+ */
8
+ export declare function useMediaQuery(query: string): boolean;
9
+ /** True below the `md` breakpoint (768px) — the phone/desktop split. */
10
+ export declare function useIsMobile(breakpoint?: number): boolean;
11
+ /** True on a coarse pointer (finger) — use for tap targets, not for width. */
12
+ export declare function useIsTouch(): boolean;
13
+ /** The OS colour-scheme preference. See `useTheme` for the resolved theme. */
14
+ export declare function usePrefersDark(): boolean;
15
+ /** True when the user asked for less motion — gate animations on it. */
16
+ export declare function usePrefersReducedMotion(): boolean;
17
+ //# sourceMappingURL=use-media-query.d.ts.map