tempest-react-sdk 0.39.0 → 0.40.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 (475) hide show
  1. package/dist/_virtual/_rolldown/runtime.cjs +1 -1
  2. package/dist/access/access-control-context.cjs.map +1 -1
  3. package/dist/access/use-can.cjs.map +1 -1
  4. package/dist/app/AppProviders.cjs.map +1 -1
  5. package/dist/app/AppProviders.js.map +1 -1
  6. package/dist/audio/use-audio-recorder.cjs.map +1 -1
  7. package/dist/audio/use-audio.cjs.map +1 -1
  8. package/dist/audio/use-microphone.cjs.map +1 -1
  9. package/dist/auth/create-auth-store.cjs.map +1 -1
  10. package/dist/auth/lazy-with-retry.cjs.map +1 -1
  11. package/dist/auth/passkey.cjs.map +1 -1
  12. package/dist/auth/passkey.js.map +1 -1
  13. package/dist/auth/use-passkey.cjs.map +1 -1
  14. package/dist/br/BrazilMap.cjs +1 -1
  15. package/dist/br/BrazilMap.cjs.map +1 -1
  16. package/dist/br/BrazilMap.js +2 -2
  17. package/dist/br/BrazilMap.js.map +1 -1
  18. package/dist/br/BrazilStateCitySelect.cjs.map +1 -1
  19. package/dist/br/BrazilStateCitySelect.js.map +1 -1
  20. package/dist/br/BrazilStateMap.cjs +1 -1
  21. package/dist/br/BrazilStateMap.cjs.map +1 -1
  22. package/dist/br/BrazilStateMap.js +2 -2
  23. package/dist/br/BrazilStateMap.js.map +1 -1
  24. package/dist/br/MapLegend.cjs.map +1 -1
  25. package/dist/br/MapLegend.js.map +1 -1
  26. package/dist/br/MapMarkers.cjs.map +1 -1
  27. package/dist/br/MapMarkers.js.map +1 -1
  28. package/dist/br/MunicipalitySearch.cjs.map +1 -1
  29. package/dist/br/MunicipalitySearch.js.map +1 -1
  30. package/dist/br/PixQRCode.cjs.map +1 -1
  31. package/dist/br/PixQRCode.js.map +1 -1
  32. package/dist/br/use-map-hover.cjs.map +1 -1
  33. package/dist/br/use-map-zoom.cjs.map +1 -1
  34. package/dist/capture/media-recorder.cjs +1 -1
  35. package/dist/capture/media-recorder.js +2 -2
  36. package/dist/capture/use-barcode-scanner.cjs +1 -1
  37. package/dist/capture/use-barcode-scanner.cjs.map +1 -1
  38. package/dist/capture/use-barcode-scanner.js +2 -2
  39. package/dist/capture/use-barcode-scanner.js.map +1 -1
  40. package/dist/capture/use-screen-capture.cjs.map +1 -1
  41. package/dist/capture/use-speech-recognition.cjs.map +1 -1
  42. package/dist/capture/use-torch.cjs.map +1 -1
  43. package/dist/capture/use-video-recorder.cjs.map +1 -1
  44. package/dist/charts/AreaChart.cjs.map +1 -1
  45. package/dist/charts/AreaChart.js.map +1 -1
  46. package/dist/charts/BarChart.cjs.map +1 -1
  47. package/dist/charts/BarChart.js.map +1 -1
  48. package/dist/charts/LineChart.cjs.map +1 -1
  49. package/dist/charts/LineChart.js.map +1 -1
  50. package/dist/charts/PieChart.cjs.map +1 -1
  51. package/dist/charts/PieChart.js.map +1 -1
  52. package/dist/charts/RadarChart.cjs.map +1 -1
  53. package/dist/charts/RadarChart.js.map +1 -1
  54. package/dist/charts/use-chart-colors.cjs.map +1 -1
  55. package/dist/components/AIChat/AIChat.cjs.map +1 -1
  56. package/dist/components/AIChat/AIChat.js.map +1 -1
  57. package/dist/components/AIChat/AIChatComposer.cjs.map +1 -1
  58. package/dist/components/AIChat/AIChatComposer.js.map +1 -1
  59. package/dist/components/AIChat/AIChatTurn.cjs.map +1 -1
  60. package/dist/components/AIChat/AIChatTurn.js.map +1 -1
  61. package/dist/components/Accordion/Accordion.cjs.map +1 -1
  62. package/dist/components/Accordion/Accordion.js.map +1 -1
  63. package/dist/components/Alert/Alert.cjs.map +1 -1
  64. package/dist/components/Alert/Alert.js.map +1 -1
  65. package/dist/components/AppBar/AppBar.cjs.map +1 -1
  66. package/dist/components/AppBar/AppBar.js.map +1 -1
  67. package/dist/components/AppShell/AppShell.cjs.map +1 -1
  68. package/dist/components/AppShell/AppShell.js.map +1 -1
  69. package/dist/components/AudioPlayer/AudioPlayer.cjs.map +1 -1
  70. package/dist/components/AudioPlayer/AudioPlayer.js.map +1 -1
  71. package/dist/components/AudioRecorder/AudioRecorder.cjs.map +1 -1
  72. package/dist/components/AudioRecorder/AudioRecorder.js.map +1 -1
  73. package/dist/components/Avatar/Avatar.cjs.map +1 -1
  74. package/dist/components/Avatar/Avatar.js.map +1 -1
  75. package/dist/components/AvatarGroup/AvatarGroup.cjs.map +1 -1
  76. package/dist/components/AvatarGroup/AvatarGroup.js.map +1 -1
  77. package/dist/components/Badge/Badge.cjs.map +1 -1
  78. package/dist/components/Badge/Badge.js.map +1 -1
  79. package/dist/components/Banner/Banner.cjs.map +1 -1
  80. package/dist/components/Banner/Banner.js.map +1 -1
  81. package/dist/components/BarcodeScanner/BarcodeScanner.cjs.map +1 -1
  82. package/dist/components/BarcodeScanner/BarcodeScanner.js.map +1 -1
  83. package/dist/components/BottomNavigation/BottomNavigation.cjs.map +1 -1
  84. package/dist/components/BottomNavigation/BottomNavigation.js.map +1 -1
  85. package/dist/components/BottomSheet/BottomSheet.cjs.map +1 -1
  86. package/dist/components/BottomSheet/BottomSheet.js.map +1 -1
  87. package/dist/components/Breadcrumbs/Breadcrumbs.cjs.map +1 -1
  88. package/dist/components/Breadcrumbs/Breadcrumbs.js.map +1 -1
  89. package/dist/components/Button/Button.cjs.map +1 -1
  90. package/dist/components/Button/Button.js.map +1 -1
  91. package/dist/components/Calendar/Calendar.cjs +1 -1
  92. package/dist/components/Calendar/Calendar.cjs.map +1 -1
  93. package/dist/components/Calendar/Calendar.js +38 -41
  94. package/dist/components/Calendar/Calendar.js.map +1 -1
  95. package/dist/components/Card/Card.cjs.map +1 -1
  96. package/dist/components/Card/Card.js.map +1 -1
  97. package/dist/components/Carousel/Carousel.cjs.map +1 -1
  98. package/dist/components/Carousel/Carousel.js.map +1 -1
  99. package/dist/components/Chat/Chat.cjs.map +1 -1
  100. package/dist/components/Chat/Chat.js.map +1 -1
  101. package/dist/components/Chat/ChatComposer.cjs.map +1 -1
  102. package/dist/components/Chat/ChatComposer.js.map +1 -1
  103. package/dist/components/Chat/chat-groups.cjs +1 -1
  104. package/dist/components/Chat/chat-groups.cjs.map +1 -1
  105. package/dist/components/Chat/chat-groups.js +2 -2
  106. package/dist/components/Chat/chat-groups.js.map +1 -1
  107. package/dist/components/Checkbox/Checkbox.cjs.map +1 -1
  108. package/dist/components/Checkbox/Checkbox.js.map +1 -1
  109. package/dist/components/ChipInput/ChipInput.cjs.map +1 -1
  110. package/dist/components/ChipInput/ChipInput.js.map +1 -1
  111. package/dist/components/ClickOutside/ClickOutside.cjs.map +1 -1
  112. package/dist/components/CodeBlock/CodeBlock.cjs.map +1 -1
  113. package/dist/components/CodeBlock/CodeBlock.js.map +1 -1
  114. package/dist/components/Collapsible/Collapsible.cjs.map +1 -1
  115. package/dist/components/Collapsible/Collapsible.js.map +1 -1
  116. package/dist/components/Combobox/Combobox.cjs.map +1 -1
  117. package/dist/components/Combobox/Combobox.js.map +1 -1
  118. package/dist/components/Command/Command.cjs.map +1 -1
  119. package/dist/components/Command/Command.js.map +1 -1
  120. package/dist/components/ConfirmDialog/ConfirmDialog.cjs.map +1 -1
  121. package/dist/components/ConfirmDialog/ConfirmDialog.js.map +1 -1
  122. package/dist/components/ContextMenu/ContextMenu.cjs.map +1 -1
  123. package/dist/components/ContextMenu/ContextMenu.js.map +1 -1
  124. package/dist/components/CopyButton/CopyButton.cjs.map +1 -1
  125. package/dist/components/CopyButton/CopyButton.js.map +1 -1
  126. package/dist/components/DataList/DataList.cjs.map +1 -1
  127. package/dist/components/DataList/DataList.js.map +1 -1
  128. package/dist/components/DataTable/DataTable.cjs +1 -1
  129. package/dist/components/DataTable/DataTable.cjs.map +1 -1
  130. package/dist/components/DataTable/DataTable.js +2 -2
  131. package/dist/components/DataTable/DataTable.js.map +1 -1
  132. package/dist/components/DataTable/EditableCell.cjs.map +1 -1
  133. package/dist/components/DataTable/EditableCell.js.map +1 -1
  134. package/dist/components/DatePicker/DatePicker.cjs.map +1 -1
  135. package/dist/components/DateRangePicker/DateRangePicker.cjs.map +1 -1
  136. package/dist/components/DateRangePicker/DateRangePicker.js.map +1 -1
  137. package/dist/components/DescriptionList/DescriptionList.cjs.map +1 -1
  138. package/dist/components/DescriptionList/DescriptionList.js.map +1 -1
  139. package/dist/components/Divider/Divider.cjs.map +1 -1
  140. package/dist/components/Divider/Divider.js.map +1 -1
  141. package/dist/components/Drawer/Drawer.cjs.map +1 -1
  142. package/dist/components/Drawer/Drawer.js.map +1 -1
  143. package/dist/components/DropdownMenu/DropdownMenu.cjs.map +1 -1
  144. package/dist/components/DropdownMenu/DropdownMenu.js.map +1 -1
  145. package/dist/components/Dropzone/Dropzone.cjs.map +1 -1
  146. package/dist/components/Dropzone/Dropzone.js.map +1 -1
  147. package/dist/components/EmptyState/EmptyState.cjs.map +1 -1
  148. package/dist/components/EmptyState/EmptyState.js.map +1 -1
  149. package/dist/components/ErrorState/ErrorState.cjs.map +1 -1
  150. package/dist/components/ErrorState/ErrorState.js.map +1 -1
  151. package/dist/components/FileUpload/FileUpload.cjs +1 -1
  152. package/dist/components/FileUpload/FileUpload.cjs.map +1 -1
  153. package/dist/components/FileUpload/FileUpload.js +1 -1
  154. package/dist/components/FileUpload/FileUpload.js.map +1 -1
  155. package/dist/components/FilterBar/FilterBar.cjs.map +1 -1
  156. package/dist/components/FilterBar/FilterBar.js.map +1 -1
  157. package/dist/components/FloatingActionButton/FloatingActionButton.cjs.map +1 -1
  158. package/dist/components/FloatingActionButton/FloatingActionButton.js.map +1 -1
  159. package/dist/components/Form/Form.cjs.map +1 -1
  160. package/dist/components/Form/Form.js.map +1 -1
  161. package/dist/components/HoverCard/HoverCard.cjs.map +1 -1
  162. package/dist/components/HoverCard/HoverCard.js.map +1 -1
  163. package/dist/components/Image/Image.cjs.map +1 -1
  164. package/dist/components/ImageCropper/ImageCropper.cjs +1 -1
  165. package/dist/components/ImageCropper/ImageCropper.cjs.map +1 -1
  166. package/dist/components/ImageCropper/ImageCropper.js +1 -1
  167. package/dist/components/ImageCropper/ImageCropper.js.map +1 -1
  168. package/dist/components/Input/Input.cjs.map +1 -1
  169. package/dist/components/Input/Input.js.map +1 -1
  170. package/dist/components/InstallBanner/InstallBanner.cjs.map +1 -1
  171. package/dist/components/InstallBanner/InstallBanner.js.map +1 -1
  172. package/dist/components/InstallButton/InstallButton.cjs.map +1 -1
  173. package/dist/components/InstallButton/InstallButton.js.map +1 -1
  174. package/dist/components/Kanban/Kanban.cjs.map +1 -1
  175. package/dist/components/Kanban/Kanban.js.map +1 -1
  176. package/dist/components/Label/Label.cjs.map +1 -1
  177. package/dist/components/Label/Label.js.map +1 -1
  178. package/dist/components/Lightbox/Lightbox.cjs.map +1 -1
  179. package/dist/components/Lightbox/Lightbox.js.map +1 -1
  180. package/dist/components/ListTile/ListTile.cjs.map +1 -1
  181. package/dist/components/ListTile/ListTile.js.map +1 -1
  182. package/dist/components/Markdown/Markdown.cjs +1 -1
  183. package/dist/components/Markdown/Markdown.cjs.map +1 -1
  184. package/dist/components/Markdown/Markdown.js +2 -2
  185. package/dist/components/Markdown/Markdown.js.map +1 -1
  186. package/dist/components/Masonry/Masonry.cjs.map +1 -1
  187. package/dist/components/Masonry/Masonry.js.map +1 -1
  188. package/dist/components/Menubar/Menubar.cjs.map +1 -1
  189. package/dist/components/Menubar/Menubar.js.map +1 -1
  190. package/dist/components/Modal/Modal.cjs.map +1 -1
  191. package/dist/components/Modal/Modal.js.map +1 -1
  192. package/dist/components/ModalsManager/ModalsManager.cjs.map +1 -1
  193. package/dist/components/ModalsManager/ModalsManager.js.map +1 -1
  194. package/dist/components/Money/Money.cjs.map +1 -1
  195. package/dist/components/Money/Money.js.map +1 -1
  196. package/dist/components/MultiSelect/MultiSelect.cjs.map +1 -1
  197. package/dist/components/MultiSelect/MultiSelect.js.map +1 -1
  198. package/dist/components/NProgress/NProgress.cjs.map +1 -1
  199. package/dist/components/Navbar/Navbar.cjs.map +1 -1
  200. package/dist/components/Navbar/Navbar.js.map +1 -1
  201. package/dist/components/NavigationMenu/NavigationMenu.cjs.map +1 -1
  202. package/dist/components/NavigationMenu/NavigationMenu.js.map +1 -1
  203. package/dist/components/NavigationRail/NavigationRail.cjs.map +1 -1
  204. package/dist/components/NavigationRail/NavigationRail.js.map +1 -1
  205. package/dist/components/NotificationCenter/NotificationCenter.cjs.map +1 -1
  206. package/dist/components/NotificationCenter/NotificationCenter.js.map +1 -1
  207. package/dist/components/NotificationCenter/use-notification-inbox.cjs.map +1 -1
  208. package/dist/components/OfflineIndicator/OfflineIndicator.cjs.map +1 -1
  209. package/dist/components/OfflineIndicator/OfflineIndicator.js.map +1 -1
  210. package/dist/components/Page/Page.cjs.map +1 -1
  211. package/dist/components/Page/Page.js.map +1 -1
  212. package/dist/components/Pagination/Pagination.cjs.map +1 -1
  213. package/dist/components/Pagination/Pagination.js.map +1 -1
  214. package/dist/components/PasswordInput/PasswordInput.cjs.map +1 -1
  215. package/dist/components/PasswordInput/PasswordInput.js.map +1 -1
  216. package/dist/components/PinInput/PinInput.cjs.map +1 -1
  217. package/dist/components/PinInput/PinInput.js.map +1 -1
  218. package/dist/components/Popover/Popover.cjs.map +1 -1
  219. package/dist/components/Popover/Popover.js.map +1 -1
  220. package/dist/components/Portal/Portal.cjs.map +1 -1
  221. package/dist/components/Progress/Progress.cjs.map +1 -1
  222. package/dist/components/Progress/Progress.js.map +1 -1
  223. package/dist/components/QRCode/QRCode.cjs.map +1 -1
  224. package/dist/components/QRCode/QRCode.js.map +1 -1
  225. package/dist/components/Radio/Radio.cjs.map +1 -1
  226. package/dist/components/Radio/Radio.js.map +1 -1
  227. package/dist/components/RangeSlider/RangeSlider.cjs.map +1 -1
  228. package/dist/components/RangeSlider/RangeSlider.js.map +1 -1
  229. package/dist/components/RatingStars/RatingStars.cjs.map +1 -1
  230. package/dist/components/RatingStars/RatingStars.js.map +1 -1
  231. package/dist/components/RefreshIndicator/RefreshIndicator.cjs.map +1 -1
  232. package/dist/components/RefreshIndicator/RefreshIndicator.js.map +1 -1
  233. package/dist/components/RelativeTime/RelativeTime.cjs.map +1 -1
  234. package/dist/components/RelativeTime/RelativeTime.js.map +1 -1
  235. package/dist/components/Resizable/Resizable.cjs.map +1 -1
  236. package/dist/components/Resizable/Resizable.js.map +1 -1
  237. package/dist/components/Scheduler/Scheduler.cjs.map +1 -1
  238. package/dist/components/Scheduler/Scheduler.js.map +1 -1
  239. package/dist/components/ScrollArea/ScrollArea.cjs.map +1 -1
  240. package/dist/components/SearchBar/SearchBar.cjs.map +1 -1
  241. package/dist/components/SearchBar/SearchBar.js.map +1 -1
  242. package/dist/components/SegmentedControl/SegmentedControl.cjs.map +1 -1
  243. package/dist/components/SegmentedControl/SegmentedControl.js.map +1 -1
  244. package/dist/components/Select/Select.cjs.map +1 -1
  245. package/dist/components/Select/Select.js.map +1 -1
  246. package/dist/components/Sidebar/Sidebar.cjs.map +1 -1
  247. package/dist/components/Sidebar/Sidebar.js.map +1 -1
  248. package/dist/components/SignaturePad/SignaturePad.cjs.map +1 -1
  249. package/dist/components/SignaturePad/SignaturePad.js.map +1 -1
  250. package/dist/components/Slider/Slider.cjs.map +1 -1
  251. package/dist/components/Slider/Slider.js.map +1 -1
  252. package/dist/components/Sparkline/Sparkline.cjs.map +1 -1
  253. package/dist/components/Sparkline/Sparkline.js.map +1 -1
  254. package/dist/components/Spinner/Spinner.cjs.map +1 -1
  255. package/dist/components/Spinner/Spinner.js.map +1 -1
  256. package/dist/components/Stat/Stat.cjs.map +1 -1
  257. package/dist/components/Stat/Stat.js.map +1 -1
  258. package/dist/components/Stepper/Stepper.cjs.map +1 -1
  259. package/dist/components/Stepper/Stepper.js.map +1 -1
  260. package/dist/components/StepperInput/StepperInput.cjs.map +1 -1
  261. package/dist/components/StepperInput/StepperInput.js.map +1 -1
  262. package/dist/components/Switch/Switch.cjs.map +1 -1
  263. package/dist/components/Switch/Switch.js.map +1 -1
  264. package/dist/components/SyncStatusBadge/SyncStatusBadge.cjs.map +1 -1
  265. package/dist/components/SyncStatusBadge/SyncStatusBadge.js.map +1 -1
  266. package/dist/components/Table/Table.cjs.map +1 -1
  267. package/dist/components/Table/Table.js.map +1 -1
  268. package/dist/components/Tabs/Tabs.cjs.map +1 -1
  269. package/dist/components/Tabs/Tabs.js.map +1 -1
  270. package/dist/components/Tag/Tag.cjs.map +1 -1
  271. package/dist/components/Tag/Tag.js.map +1 -1
  272. package/dist/components/Textarea/Textarea.cjs.map +1 -1
  273. package/dist/components/Textarea/Textarea.js.map +1 -1
  274. package/dist/components/TimePicker/TimePicker.cjs.map +1 -1
  275. package/dist/components/TimePicker/TimePicker.js.map +1 -1
  276. package/dist/components/Timeline/Timeline.cjs.map +1 -1
  277. package/dist/components/Timeline/Timeline.js.map +1 -1
  278. package/dist/components/Toast/ToastProvider.cjs.map +1 -1
  279. package/dist/components/Toast/ToastProvider.js.map +1 -1
  280. package/dist/components/Toggle/Toggle.cjs.map +1 -1
  281. package/dist/components/ToggleGroup/ToggleGroup.cjs.map +1 -1
  282. package/dist/components/ToggleGroup/ToggleGroup.js.map +1 -1
  283. package/dist/components/Tooltip/Tooltip.cjs.map +1 -1
  284. package/dist/components/Tooltip/Tooltip.js.map +1 -1
  285. package/dist/components/Tour/Tour.cjs.map +1 -1
  286. package/dist/components/Tour/Tour.js.map +1 -1
  287. package/dist/components/Transfer/Transfer.cjs.map +1 -1
  288. package/dist/components/Transfer/Transfer.js.map +1 -1
  289. package/dist/components/TreeView/TreeView.cjs +1 -1
  290. package/dist/components/TreeView/TreeView.cjs.map +1 -1
  291. package/dist/components/TreeView/TreeView.js +1 -4
  292. package/dist/components/TreeView/TreeView.js.map +1 -1
  293. package/dist/components/UpdatePrompt/UpdatePrompt.cjs.map +1 -1
  294. package/dist/components/UpdatePrompt/UpdatePrompt.js.map +1 -1
  295. package/dist/components/VirtualList/VirtualList.cjs.map +1 -1
  296. package/dist/components/VirtualList/VirtualList.js.map +1 -1
  297. package/dist/components/VirtualTable/VirtualTable.cjs.map +1 -1
  298. package/dist/components/VirtualTable/VirtualTable.js.map +1 -1
  299. package/dist/components/VisuallyHidden/VisuallyHidden.cjs.map +1 -1
  300. package/dist/components/Wizard/Wizard.cjs.map +1 -1
  301. package/dist/components/Wizard/Wizard.js.map +1 -1
  302. package/dist/data/data-provider-context.cjs.map +1 -1
  303. package/dist/data/use-resource.cjs.map +1 -1
  304. package/dist/editor/RichTextEditor.cjs.map +1 -1
  305. package/dist/editor/RichTextEditor.js.map +1 -1
  306. package/dist/error-boundary/use-error-handler.cjs.map +1 -1
  307. package/dist/feature-flags/FeatureFlagsProvider.cjs.map +1 -1
  308. package/dist/forms/FormField.cjs.map +1 -1
  309. package/dist/forms/masked-inputs.cjs +1 -1
  310. package/dist/forms/masked-inputs.cjs.map +1 -1
  311. package/dist/forms/masked-inputs.js +1 -1
  312. package/dist/forms/masked-inputs.js.map +1 -1
  313. package/dist/forms/use-viacep.cjs.map +1 -1
  314. package/dist/forms/use-zod-form.cjs.map +1 -1
  315. package/dist/forms/zod-resolver.cjs.map +1 -1
  316. package/dist/forms/zod-resolver.js.map +1 -1
  317. package/dist/geo/TrajectoryMap.cjs.map +1 -1
  318. package/dist/geo/TrajectoryMap.js.map +1 -1
  319. package/dist/geo/leaflet-map.cjs.map +1 -1
  320. package/dist/geo/leaflet-map.js.map +1 -1
  321. package/dist/geo/use-position-tracker.cjs.map +1 -1
  322. package/dist/hooks/use-announce.cjs.map +1 -1
  323. package/dist/hooks/use-async.cjs.map +1 -1
  324. package/dist/hooks/use-before-install-prompt.cjs.map +1 -1
  325. package/dist/hooks/use-breakpoint.cjs.map +1 -1
  326. package/dist/hooks/use-click-outside.cjs.map +1 -1
  327. package/dist/hooks/use-client-filter.cjs.map +1 -1
  328. package/dist/hooks/use-clipboard.cjs.map +1 -1
  329. package/dist/hooks/use-counter.cjs.map +1 -1
  330. package/dist/hooks/use-debounce.cjs.map +1 -1
  331. package/dist/hooks/use-deep-memo.cjs.map +1 -1
  332. package/dist/hooks/use-disclosure.cjs.map +1 -1
  333. package/dist/hooks/use-document-visibility.cjs.map +1 -1
  334. package/dist/hooks/use-event-listener.cjs.map +1 -1
  335. package/dist/hooks/use-geolocation.cjs.map +1 -1
  336. package/dist/hooks/use-hover.cjs.map +1 -1
  337. package/dist/hooks/use-idle.cjs.map +1 -1
  338. package/dist/hooks/use-install-prompt.cjs +1 -1
  339. package/dist/hooks/use-install-prompt.cjs.map +1 -1
  340. package/dist/hooks/use-install-prompt.js +1 -1
  341. package/dist/hooks/use-install-prompt.js.map +1 -1
  342. package/dist/hooks/use-intersection-observer.cjs.map +1 -1
  343. package/dist/hooks/use-interval.cjs.map +1 -1
  344. package/dist/hooks/use-is-first-render.cjs.map +1 -1
  345. package/dist/hooks/use-keyboard-shortcut.cjs +1 -1
  346. package/dist/hooks/use-keyboard-shortcut.cjs.map +1 -1
  347. package/dist/hooks/use-keyboard-shortcut.js +1 -1
  348. package/dist/hooks/use-keyboard-shortcut.js.map +1 -1
  349. package/dist/hooks/use-list-state.cjs.map +1 -1
  350. package/dist/hooks/use-local-storage.cjs.map +1 -1
  351. package/dist/hooks/use-long-press-handlers.cjs.map +1 -1
  352. package/dist/hooks/use-long-press.cjs.map +1 -1
  353. package/dist/hooks/use-map.cjs.map +1 -1
  354. package/dist/hooks/use-media-devices.cjs.map +1 -1
  355. package/dist/hooks/use-media-permission.cjs.map +1 -1
  356. package/dist/hooks/use-media-query.cjs.map +1 -1
  357. package/dist/hooks/use-object-url.cjs.map +1 -1
  358. package/dist/hooks/use-online.cjs.map +1 -1
  359. package/dist/hooks/use-pagination.cjs.map +1 -1
  360. package/dist/hooks/use-previous.cjs.map +1 -1
  361. package/dist/hooks/use-queue.cjs.map +1 -1
  362. package/dist/hooks/use-resize-observer.cjs.map +1 -1
  363. package/dist/hooks/use-scroll-overflow.cjs.map +1 -1
  364. package/dist/hooks/use-service-worker-update.cjs.map +1 -1
  365. package/dist/hooks/use-set.cjs.map +1 -1
  366. package/dist/hooks/use-sortable.cjs +1 -1
  367. package/dist/hooks/use-sortable.cjs.map +1 -1
  368. package/dist/hooks/use-sortable.js +0 -2
  369. package/dist/hooks/use-sortable.js.map +1 -1
  370. package/dist/hooks/use-stable-callback.cjs.map +1 -1
  371. package/dist/hooks/use-storage-estimate.cjs.map +1 -1
  372. package/dist/hooks/use-throttle.cjs.map +1 -1
  373. package/dist/hooks/use-timeout.cjs.map +1 -1
  374. package/dist/hooks/use-toggle.cjs.map +1 -1
  375. package/dist/hooks/use-window-size.cjs.map +1 -1
  376. package/dist/http/api-client.cjs +1 -1
  377. package/dist/http/api-client.cjs.map +1 -1
  378. package/dist/http/api-client.js +7 -5
  379. package/dist/http/api-client.js.map +1 -1
  380. package/dist/http/resumable-upload.cjs +1 -1
  381. package/dist/http/resumable-upload.cjs.map +1 -1
  382. package/dist/http/resumable-upload.js +1 -1
  383. package/dist/http/resumable-upload.js.map +1 -1
  384. package/dist/http/use-poll.cjs.map +1 -1
  385. package/dist/i18n/I18nProvider.cjs.map +1 -1
  386. package/dist/icons/Icon.cjs.map +1 -1
  387. package/dist/icons/IconProvider.cjs.map +1 -1
  388. package/dist/icons/icon-context.cjs.map +1 -1
  389. package/dist/icons/use-icon.cjs.map +1 -1
  390. package/dist/imaging/use-image-processing.cjs.map +1 -1
  391. package/dist/oauth/GoogleSignIn.cjs.map +1 -1
  392. package/dist/oauth/GoogleSignIn.js.map +1 -1
  393. package/dist/oauth/use-oauth-callback.cjs.map +1 -1
  394. package/dist/offline/use-offline-sync.cjs.map +1 -1
  395. package/dist/push/use-push-subscription.cjs.map +1 -1
  396. package/dist/query/QueryProvider.cjs.map +1 -1
  397. package/dist/query/constants.cjs +1 -1
  398. package/dist/query/constants.cjs.map +1 -1
  399. package/dist/query/constants.js +10 -10
  400. package/dist/query/constants.js.map +1 -1
  401. package/dist/query/create-query-keys.cjs +1 -1
  402. package/dist/query/create-query-keys.cjs.map +1 -1
  403. package/dist/query/create-query-keys.js +1 -1
  404. package/dist/query/create-query-keys.js.map +1 -1
  405. package/dist/query/offline-persistence.cjs.map +1 -1
  406. package/dist/query/use-cursor-query.cjs.map +1 -1
  407. package/dist/query/use-offline-mutation.cjs.map +1 -1
  408. package/dist/query/use-paginated-query.cjs.map +1 -1
  409. package/dist/router/AppRouter.cjs +1 -1
  410. package/dist/router/AppRouter.cjs.map +1 -1
  411. package/dist/router/AppRouter.js +7 -2
  412. package/dist/router/AppRouter.js.map +1 -1
  413. package/dist/sse/use-event-stream.cjs.map +1 -1
  414. package/dist/store/create-store.cjs.map +1 -1
  415. package/dist/tabular/use-tabular-predictor.cjs.map +1 -1
  416. package/dist/telemetry/TelemetryProvider.cjs.map +1 -1
  417. package/dist/tempest-react-sdk.d.ts +25 -26
  418. package/dist/theme/ThemeProvider.cjs.map +1 -1
  419. package/dist/theme/color.cjs +1 -1
  420. package/dist/theme/color.cjs.map +1 -1
  421. package/dist/theme/color.js +1 -1
  422. package/dist/theme/color.js.map +1 -1
  423. package/dist/utils/cn.cjs +1 -1
  424. package/dist/utils/cn.cjs.map +1 -1
  425. package/dist/utils/cn.js +1 -1
  426. package/dist/utils/cn.js.map +1 -1
  427. package/dist/utils/objects.cjs +1 -1
  428. package/dist/utils/objects.cjs.map +1 -1
  429. package/dist/utils/objects.js +1 -1
  430. package/dist/utils/objects.js.map +1 -1
  431. package/dist/utils/xlsx.cjs +1 -1
  432. package/dist/utils/xlsx.cjs.map +1 -1
  433. package/dist/utils/xlsx.js +3 -3
  434. package/dist/utils/xlsx.js.map +1 -1
  435. package/dist/vision/core/exceptions.cjs.map +1 -1
  436. package/dist/vision/core/exceptions.js.map +1 -1
  437. package/dist/vision/core/metadata.cjs +1 -1
  438. package/dist/vision/core/metadata.cjs.map +1 -1
  439. package/dist/vision/core/metadata.js +1 -1
  440. package/dist/vision/core/metadata.js.map +1 -1
  441. package/dist/vision/io/image.cjs +1 -1
  442. package/dist/vision/io/image.cjs.map +1 -1
  443. package/dist/vision/io/image.js +4 -2
  444. package/dist/vision/io/image.js.map +1 -1
  445. package/dist/vision/preprocess/image.cjs +1 -1
  446. package/dist/vision/preprocess/image.cjs.map +1 -1
  447. package/dist/vision/preprocess/image.js +4 -2
  448. package/dist/vision/preprocess/image.js.map +1 -1
  449. package/dist/vision/tasks/base.cjs +1 -1
  450. package/dist/vision/tasks/base.cjs.map +1 -1
  451. package/dist/vision/tasks/base.js +3 -1
  452. package/dist/vision/tasks/base.js.map +1 -1
  453. package/dist/vision/tasks/classifier.cjs +1 -1
  454. package/dist/vision/tasks/classifier.cjs.map +1 -1
  455. package/dist/vision/tasks/classifier.js +2 -2
  456. package/dist/vision/tasks/classifier.js.map +1 -1
  457. package/dist/vision/tasks/detectClassify.cjs +1 -1
  458. package/dist/vision/tasks/detectClassify.cjs.map +1 -1
  459. package/dist/vision/tasks/detectClassify.js +16 -15
  460. package/dist/vision/tasks/detectClassify.js.map +1 -1
  461. package/dist/vision/use-camera-stream.cjs.map +1 -1
  462. package/dist/vision/use-live-luminance.cjs.map +1 -1
  463. package/dist/vite/create-vite-config.cjs +1 -1
  464. package/dist/vite/create-vite-config.cjs.map +1 -1
  465. package/dist/vite/create-vite-config.js +3 -3
  466. package/dist/vite/create-vite-config.js.map +1 -1
  467. package/dist/vite/tempest-icons.cjs.map +1 -1
  468. package/dist/vite/tempest-pwa-dev-sw.cjs.map +1 -1
  469. package/dist/vite/tempest-pwa-icons.cjs.map +1 -1
  470. package/dist/ws/use-web-socket.cjs +1 -1
  471. package/dist/ws/use-web-socket.cjs.map +1 -1
  472. package/dist/ws/use-web-socket.js +5 -3
  473. package/dist/ws/use-web-socket.js.map +1 -1
  474. package/package.json +2 -2
  475. package/template/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"use-position-tracker.cjs","names":[],"sources":["../../src/geo/use-position-tracker.ts"],"sourcesContent":["import { useCallback, useEffect, useRef, useState } from \"react\";\nimport {\n createPositionTracker,\n type CreatePositionTrackerOptions,\n type PositionTracker,\n type TrackerStatus,\n} from \"./create-position-tracker\";\nimport type { TrackPoint } from \"./types\";\n\nexport interface UsePositionTrackerOptions extends Omit<\n CreatePositionTrackerOptions,\n \"onUpdate\" | \"onStatusChange\"\n> {\n /** Start tracking automatically on mount. Default: `false`. */\n autoStart?: boolean;\n}\n\nexport interface UsePositionTrackerResult {\n /** Recorded trajectory (newest last). */\n points: readonly TrackPoint[];\n /** Most recent point, or `null` before the first fix. */\n lastPoint: TrackPoint | null;\n /** Total distance of the trajectory in kilometers. */\n distanceKm: number;\n /** Current tracker status. */\n status: TrackerStatus;\n /** True while `status === \"tracking\"`. */\n isTracking: boolean;\n /** Begin watching position. */\n start: () => void;\n /** Stop watching, keeping the trajectory. */\n stop: () => void;\n /** Stop and discard the trajectory. */\n clear: () => void;\n}\n\n/**\n * React hook wrapping {@link createPositionTracker}. Records a live GPS\n * trajectory tied to the component lifecycle — the watch is torn down on\n * unmount. Fully browser-side; no external service.\n *\n * @example\n * const { points, distanceKm, isTracking, start, stop } = usePositionTracker({\n * minDistanceKm: 0.01,\n * });\n * return (\n * <>\n * <button onClick={isTracking ? stop : start}>{isTracking ? \"Parar\" : \"Rastrear\"}</button>\n * <TrajectoryMap points={points} />\n * <span>{distanceKm.toFixed(2)} km</span>\n * </>\n * );\n */\nexport function usePositionTracker(\n options: UsePositionTrackerOptions = {},\n): UsePositionTrackerResult {\n const { autoStart = false, onError, ...trackerOptions } = options;\n\n const [points, setPoints] = useState<readonly TrackPoint[]>([]);\n const [distanceKm, setDistanceKm] = useState<number>(0);\n const [status, setStatus] = useState<TrackerStatus>(\"idle\");\n\n const trackerRef = useRef<PositionTracker | null>(null);\n const onErrorRef = useRef(onError);\n useEffect(() => {\n onErrorRef.current = onError;\n }, [onError]);\n\n useEffect(() => {\n const tracker = createPositionTracker({\n ...trackerOptions,\n onUpdate: (next) => {\n setPoints([...next]);\n setDistanceKm(tracker.distanceKm);\n },\n onStatusChange: setStatus,\n onError: (error) => onErrorRef.current?.(error),\n });\n trackerRef.current = tracker;\n if (autoStart) tracker.start();\n\n return () => {\n tracker.stop();\n trackerRef.current = null;\n };\n // Tracker options are read once at creation; changing them mid-track\n // would drop the recorded trajectory, so they are intentionally omitted.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n const start = useCallback((): void => trackerRef.current?.start(), []);\n const stop = useCallback((): void => trackerRef.current?.stop(), []);\n const clear = useCallback((): void => {\n trackerRef.current?.clear();\n setDistanceKm(0);\n }, []);\n\n return {\n points,\n lastPoint: points.length > 0 ? points[points.length - 1] : null,\n distanceKm,\n status,\n isTracking: status === \"tracking\",\n start,\n stop,\n clear,\n };\n}\n"],"mappings":"wEAqDA,SAAgB,EACZ,EAAqC,CAAC,EACd,CACxB,GAAM,CAAE,YAAY,GAAO,UAAS,GAAG,GAAmB,EAEpD,CAAC,EAAQ,IAAA,EAAA,EAAA,SAAA,CAA6C,CAAC,CAAC,EACxD,CAAC,EAAY,IAAA,EAAA,EAAA,SAAA,CAAkC,CAAC,EAChD,CAAC,EAAQ,IAAA,EAAA,EAAA,SAAA,CAAqC,MAAM,EAEpD,GAAA,EAAA,EAAA,OAAA,CAA4C,IAAI,EAChD,GAAA,EAAA,EAAA,OAAA,CAAoB,CAAO,GACjC,EAAA,EAAA,UAAA,KAAgB,CACZ,EAAW,QAAU,CACzB,EAAG,CAAC,CAAO,CAAC,GAEZ,EAAA,EAAA,UAAA,KAAgB,CACZ,IAAM,EAAU,EAAA,sBAAsB,CAClC,GAAG,EACH,SAAW,GAAS,CAChB,EAAU,CAAC,GAAG,CAAI,CAAC,EACnB,EAAc,EAAQ,UAAU,CACpC,EACA,eAAgB,EAChB,QAAU,GAAU,EAAW,UAAU,CAAK,CAClD,CAAC,EAID,MAHA,GAAW,QAAU,EACjB,GAAW,EAAQ,MAAM,MAEhB,CACT,EAAQ,KAAK,EACb,EAAW,QAAU,IACzB,CAIJ,EAAG,CAAC,CAAC,EAEL,IAAM,GAAA,EAAA,EAAA,YAAA,KAAgC,EAAW,SAAS,MAAM,EAAG,CAAC,CAAC,EAC/D,GAAA,EAAA,EAAA,YAAA,KAA+B,EAAW,SAAS,KAAK,EAAG,CAAC,CAAC,EAC7D,GAAA,EAAA,EAAA,YAAA,KAAgC,CAClC,EAAW,SAAS,MAAM,EAC1B,EAAc,CAAC,CACnB,EAAG,CAAC,CAAC,EAEL,MAAO,CACH,SACA,UAAW,EAAO,OAAS,EAAI,EAAO,EAAO,OAAS,GAAK,KAC3D,aACA,SACA,WAAY,IAAW,WACvB,QACA,OACA,OACJ,CACJ"}
1
+ {"version":3,"file":"use-position-tracker.cjs","names":[],"sources":["../../src/geo/use-position-tracker.ts"],"sourcesContent":["import { useCallback, useEffect, useRef, useState } from \"react\";\nimport {\n createPositionTracker,\n type CreatePositionTrackerOptions,\n type PositionTracker,\n type TrackerStatus,\n} from \"./create-position-tracker\";\nimport type { TrackPoint } from \"./types\";\n\nexport interface UsePositionTrackerOptions extends Omit<\n CreatePositionTrackerOptions,\n \"onUpdate\" | \"onStatusChange\"\n> {\n /** Start tracking automatically on mount. Default: `false`. */\n autoStart?: boolean;\n}\n\nexport interface UsePositionTrackerResult {\n /** Recorded trajectory (newest last). */\n points: readonly TrackPoint[];\n /** Most recent point, or `null` before the first fix. */\n lastPoint: TrackPoint | null;\n /** Total distance of the trajectory in kilometers. */\n distanceKm: number;\n /** Current tracker status. */\n status: TrackerStatus;\n /** True while `status === \"tracking\"`. */\n isTracking: boolean;\n /** Begin watching position. */\n start: () => void;\n /** Stop watching, keeping the trajectory. */\n stop: () => void;\n /** Stop and discard the trajectory. */\n clear: () => void;\n}\n\n/**\n * React hook wrapping {@link createPositionTracker}. Records a live GPS\n * trajectory tied to the component lifecycle — the watch is torn down on\n * unmount. Fully browser-side; no external service.\n *\n * @example\n * const { points, distanceKm, isTracking, start, stop } = usePositionTracker({\n * minDistanceKm: 0.01,\n * });\n * return (\n * <>\n * <button onClick={isTracking ? stop : start}>{isTracking ? \"Parar\" : \"Rastrear\"}</button>\n * <TrajectoryMap points={points} />\n * <span>{distanceKm.toFixed(2)} km</span>\n * </>\n * );\n */\nexport function usePositionTracker(\n options: UsePositionTrackerOptions = {},\n): UsePositionTrackerResult {\n const { autoStart = false, onError, ...trackerOptions } = options;\n\n const [points, setPoints] = useState<readonly TrackPoint[]>([]);\n const [distanceKm, setDistanceKm] = useState<number>(0);\n const [status, setStatus] = useState<TrackerStatus>(\"idle\");\n\n const trackerRef = useRef<PositionTracker | null>(null);\n const onErrorRef = useRef(onError);\n useEffect(() => {\n onErrorRef.current = onError;\n }, [onError]);\n\n useEffect(() => {\n const tracker = createPositionTracker({\n ...trackerOptions,\n onUpdate: (next) => {\n setPoints([...next]);\n setDistanceKm(tracker.distanceKm);\n },\n onStatusChange: setStatus,\n onError: (error) => onErrorRef.current?.(error),\n });\n trackerRef.current = tracker;\n if (autoStart) tracker.start();\n\n return () => {\n tracker.stop();\n trackerRef.current = null;\n };\n // Tracker options are read once at creation; changing them mid-track\n // would drop the recorded trajectory, so they are intentionally omitted.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n const start = useCallback((): void => trackerRef.current?.start(), []);\n const stop = useCallback((): void => trackerRef.current?.stop(), []);\n const clear = useCallback((): void => {\n trackerRef.current?.clear();\n setDistanceKm(0);\n }, []);\n\n return {\n points,\n lastPoint: points.length > 0 ? points[points.length - 1] : null,\n distanceKm,\n status,\n isTracking: status === \"tracking\",\n start,\n stop,\n clear,\n };\n}\n"],"mappings":"wEAqDA,SAAgB,EACZ,EAAqC,CAAC,EACd,CACxB,GAAM,CAAE,YAAY,GAAO,UAAS,GAAG,GAAmB,EAEpD,CAAC,EAAQ,IAAA,EAAa,EAAA,SAAA,CAAgC,CAAC,CAAC,EACxD,CAAC,EAAY,IAAA,EAAiB,EAAA,SAAA,CAAiB,CAAC,EAChD,CAAC,EAAQ,IAAA,EAAa,EAAA,SAAA,CAAwB,MAAM,EAEpD,GAAA,EAAa,EAAA,OAAA,CAA+B,IAAI,EAChD,GAAA,EAAa,EAAA,OAAA,CAAO,CAAO,GACjC,EAAA,EAAA,UAAA,KAAgB,CACZ,EAAW,QAAU,CACzB,EAAG,CAAC,CAAO,CAAC,GAEZ,EAAA,EAAA,UAAA,KAAgB,CACZ,IAAM,EAAU,EAAA,sBAAsB,CAClC,GAAG,EACH,SAAW,GAAS,CAChB,EAAU,CAAC,GAAG,CAAI,CAAC,EACnB,EAAc,EAAQ,UAAU,CACpC,EACA,eAAgB,EAChB,QAAU,GAAU,EAAW,UAAU,CAAK,CAClD,CAAC,EAID,MAHA,GAAW,QAAU,EACjB,GAAW,EAAQ,MAAM,MAEhB,CACT,EAAQ,KAAK,EACb,EAAW,QAAU,IACzB,CAIJ,EAAG,CAAC,CAAC,EAEL,IAAM,GAAA,EAAQ,EAAA,YAAA,KAAwB,EAAW,SAAS,MAAM,EAAG,CAAC,CAAC,EAC/D,GAAA,EAAO,EAAA,YAAA,KAAwB,EAAW,SAAS,KAAK,EAAG,CAAC,CAAC,EAC7D,GAAA,EAAQ,EAAA,YAAA,KAAwB,CAClC,EAAW,SAAS,MAAM,EAC1B,EAAc,CAAC,CACnB,EAAG,CAAC,CAAC,EAEL,MAAO,CACH,SACA,UAAW,EAAO,OAAS,EAAI,EAAO,EAAO,OAAS,GAAK,KAC3D,aACA,SACA,WAAY,IAAW,WACvB,QACA,OACA,OACJ,CACJ"}
@@ -1 +1 @@
1
- {"version":3,"file":"use-announce.cjs","names":[],"sources":["../../src/hooks/use-announce.ts"],"sourcesContent":["import { useCallback, useEffect } from \"react\";\n\n/**\n * How urgently a screen reader should interrupt.\n *\n * `\"polite\"` waits for a pause in what is being read. `\"assertive\"` cuts in\n * immediately, which is right for an error the user must act on and wrong for\n * everything else — an assertive announcement can truncate the sentence the user\n * was in the middle of.\n */\nexport type AnnouncePoliteness = \"polite\" | \"assertive\";\n\n/** How long an announcement stays in the DOM before it is cleaned up. */\nconst CLEAR_AFTER_MS = 7_000;\n\n/**\n * Inline styles that hide an element visually while leaving it readable.\n *\n * Inline and not a CSS module on purpose: these nodes are created imperatively by\n * a hook, and a hook that only works when the app remembered to import\n * `tempest-react-sdk/styles.css` would drop two visible, unstyled paragraphs into\n * the page — a visual bug caused by an accessibility feature.\n */\nconst HIDDEN_STYLE: Partial<CSSStyleDeclaration> = {\n position: \"absolute\",\n width: \"1px\",\n height: \"1px\",\n margin: \"-1px\",\n padding: \"0\",\n border: \"0\",\n overflow: \"hidden\",\n clip: \"rect(0 0 0 0)\",\n clipPath: \"inset(50%)\",\n whiteSpace: \"nowrap\",\n};\n\ninterface Regions {\n polite: HTMLElement;\n assertive: HTMLElement;\n}\n\nlet regions: Regions | null = null;\nconst timers = new Set<ReturnType<typeof setTimeout>>();\n\n/**\n * Build one live region.\n *\n * The polite region carries `role=\"status\"`; the assertive one carries only\n * `aria-live=\"assertive\"`. `role=\"alert\"` is deliberately **not** used: these\n * regions live in the document for the page's whole life, and an empty element\n * claiming to be an alert is both a lie about its content and a trap for every\n * `getByRole(\"alert\")` in a consuming app's test suite, which would suddenly match\n * two nodes. A bare `aria-live` region is announced by every screen reader that\n * supports live regions at all.\n *\n * @param politeness - Which region to build.\n * @returns The region, already appended to `<body>`.\n */\nfunction createRegion(politeness: AnnouncePoliteness): HTMLElement {\n const node = document.createElement(\"div\");\n if (politeness === \"polite\") node.setAttribute(\"role\", \"status\");\n node.setAttribute(\"aria-live\", politeness);\n node.setAttribute(\"aria-atomic\", \"true\");\n node.setAttribute(\"data-tempest-announcer\", politeness);\n Object.assign(node.style, HIDDEN_STYLE);\n document.body.appendChild(node);\n return node;\n}\n\n/**\n * Get the two shared live regions, creating them on first use.\n *\n * **Two regions, not one with a switchable `aria-live`.** Politeness is a property\n * of the region, read when the assistive technology first registers it — flipping\n * the attribute later is honoured by some screen readers, ignored by others, and\n * in the worst case drops the announcement entirely. Two regions that never change\n * are the only version that behaves the same everywhere.\n *\n * **Shared, not one per component.** Every live region on the page is polled;\n * several of them mutating at once is how announcements get dropped or doubled.\n * One pair per document keeps the order deterministic.\n *\n * A pair is replaced as a **pair**, discarding the old one: if only one region was\n * torn out of the DOM (a router that replaced `<body>`, a micro-frontend unmount),\n * keeping its still-attached sibling would leave two regions of that politeness on\n * the page — the exact duplication the shared pair exists to prevent.\n *\n * @returns The polite and assertive regions, or `null` outside a document.\n */\nfunction ensureRegions(): Regions | null {\n if (typeof document === \"undefined\") return null;\n if (regions && regions.polite.isConnected && regions.assertive.isConnected) return regions;\n regions?.polite.remove();\n regions?.assertive.remove();\n regions = { polite: createRegion(\"polite\"), assertive: createRegion(\"assertive\") };\n return regions;\n}\n\n/**\n * Announce a message to screen readers, from anywhere — a hook, an event handler,\n * a plain function outside React.\n *\n * ## Why the same string announces twice\n *\n * Screen readers announce a live region when its **content changes**. Writing the\n * same text again is not a change, so \"Item removido\" twice in a row is read once —\n * the classic reason these announcers are quietly broken. Instead of mutating text,\n * every call replaces the region's child with a **new element**. The DOM mutation is\n * real even when the string is identical, so the second announcement happens, and\n * the reader hears the exact message with no padding characters bolted on.\n *\n * @param message - Text to read out. Empty strings are ignored.\n * @param politeness - `\"polite\"` (default) or `\"assertive\"`.\n *\n * @example\n * announce(`${count} pedidos encontrados`);\n * announce(\"Falha ao salvar\", \"assertive\");\n */\nexport function announce(message: string, politeness: AnnouncePoliteness = \"polite\"): void {\n if (!message) return;\n const pair = ensureRegions();\n if (!pair) return;\n\n const region = politeness === \"assertive\" ? pair.assertive : pair.polite;\n region.replaceChildren();\n const item = document.createElement(\"div\");\n item.textContent = message;\n region.appendChild(item);\n\n const timer = setTimeout(() => {\n timers.delete(timer);\n if (item.isConnected) item.remove();\n }, CLEAR_AFTER_MS);\n timers.add(timer);\n}\n\n/**\n * Remove the shared regions and cancel pending cleanups.\n *\n * For test teardown and for a micro-frontend being unmounted from a page it does\n * not own. Regular apps never need it — two empty hidden `div`s cost nothing, and\n * tearing them down while another component still announces would lose messages.\n */\nexport function clearAnnouncer(): void {\n for (const timer of timers) clearTimeout(timer);\n timers.clear();\n regions?.polite.remove();\n regions?.assertive.remove();\n regions = null;\n}\n\n/**\n * Announce transient messages to screen readers through one shared live region\n * pair.\n *\n * Reach for this when something happened that a sighted user can see and a screen\n * reader user cannot: a filter narrowed a list, a row saved, a copy succeeded, an\n * upload failed. It is **not** for content that is already on screen inside a\n * region with a role — a status pill, a toast that renders as `role=\"status\"`,\n * a form error tied to its input — announcing those again reads them twice.\n *\n * !!! warning \"Never wrap streaming text in a live region\"\n * A live region over text that grows token by token makes the reader start the\n * whole answer again on every token. Announce the **edges** instead — \"gerando\n * resposta\" and \"resposta concluída\" — and leave the transcript in a plain\n * `role=\"log\"` the user reads at their own pace. `AIChat` does exactly that.\n *\n * Mounting the hook creates the regions even before the first message. That is not\n * tidiness: a live region inserted into the DOM in the same frame as its first\n * content routinely loses that announcement, because the assistive technology has\n * to have registered the region before it can notice a change inside it.\n *\n * @returns A stable `announce(message, politeness?)` function.\n *\n * @example\n * const announce = useAnnounce();\n *\n * function onFilter(rows: Row[]) {\n * announce(`${rows.length} resultados`);\n * }\n */\nexport function useAnnounce(): (message: string, politeness?: AnnouncePoliteness) => void {\n useEffect(() => {\n ensureRegions();\n }, []);\n\n return useCallback(\n (message: string, politeness: AnnouncePoliteness = \"polite\") =>\n announce(message, politeness),\n [],\n );\n}\n"],"mappings":"uBAaA,IAAM,EAAiB,IAUjB,EAA6C,CAC/C,SAAU,WACV,MAAO,MACP,OAAQ,MACR,OAAQ,OACR,QAAS,IACT,OAAQ,IACR,SAAU,SACV,KAAM,gBACN,SAAU,aACV,WAAY,QAChB,EAOI,EAA0B,KACxB,EAAS,IAAI,IAgBnB,SAAS,EAAa,EAA6C,CAC/D,IAAM,EAAO,SAAS,cAAc,KAAK,EAOzC,OANI,IAAe,UAAU,EAAK,aAAa,OAAQ,QAAQ,EAC/D,EAAK,aAAa,YAAa,CAAU,EACzC,EAAK,aAAa,cAAe,MAAM,EACvC,EAAK,aAAa,yBAA0B,CAAU,EACtD,OAAO,OAAO,EAAK,MAAO,CAAY,EACtC,SAAS,KAAK,YAAY,CAAI,EACvB,CACX,CAsBA,SAAS,GAAgC,CAMrC,OALI,OAAO,SAAa,IAAoB,KACxC,GAAW,EAAQ,OAAO,aAAe,EAAQ,UAAU,YAAoB,GACnF,GAAS,OAAO,OAAO,EACvB,GAAS,UAAU,OAAO,EAC1B,EAAU,CAAE,OAAQ,EAAa,QAAQ,EAAG,UAAW,EAAa,WAAW,CAAE,EAC1E,EACX,CAsBA,SAAgB,EAAS,EAAiB,EAAiC,SAAgB,CACvF,GAAI,CAAC,EAAS,OACd,IAAM,EAAO,EAAc,EAC3B,GAAI,CAAC,EAAM,OAEX,IAAM,EAAS,IAAe,YAAc,EAAK,UAAY,EAAK,OAClE,EAAO,gBAAgB,EACvB,IAAM,EAAO,SAAS,cAAc,KAAK,EACzC,EAAK,YAAc,EACnB,EAAO,YAAY,CAAI,EAEvB,IAAM,EAAQ,eAAiB,CAC3B,EAAO,OAAO,CAAK,EACf,EAAK,aAAa,EAAK,OAAO,CACtC,EAAG,CAAc,EACjB,EAAO,IAAI,CAAK,CACpB,CASA,SAAgB,GAAuB,CACnC,IAAK,IAAM,KAAS,EAAQ,aAAa,CAAK,EAC9C,EAAO,MAAM,EACb,GAAS,OAAO,OAAO,EACvB,GAAS,UAAU,OAAO,EAC1B,EAAU,IACd,CAgCA,SAAgB,GAA0E,CAKtF,OAJA,EAAA,EAAA,UAAA,KAAgB,CACZ,EAAc,CAClB,EAAG,CAAC,CAAC,GAEL,EAAA,EAAA,YAAA,EACK,EAAiB,EAAiC,WAC/C,EAAS,EAAS,CAAU,EAChC,CAAC,CACL,CACJ"}
1
+ {"version":3,"file":"use-announce.cjs","names":[],"sources":["../../src/hooks/use-announce.ts"],"sourcesContent":["import { useCallback, useEffect } from \"react\";\n\n/**\n * How urgently a screen reader should interrupt.\n *\n * `\"polite\"` waits for a pause in what is being read. `\"assertive\"` cuts in\n * immediately, which is right for an error the user must act on and wrong for\n * everything else — an assertive announcement can truncate the sentence the user\n * was in the middle of.\n */\nexport type AnnouncePoliteness = \"polite\" | \"assertive\";\n\n/** How long an announcement stays in the DOM before it is cleaned up. */\nconst CLEAR_AFTER_MS = 7_000;\n\n/**\n * Inline styles that hide an element visually while leaving it readable.\n *\n * Inline and not a CSS module on purpose: these nodes are created imperatively by\n * a hook, and a hook that only works when the app remembered to import\n * `tempest-react-sdk/styles.css` would drop two visible, unstyled paragraphs into\n * the page — a visual bug caused by an accessibility feature.\n */\nconst HIDDEN_STYLE: Partial<CSSStyleDeclaration> = {\n position: \"absolute\",\n width: \"1px\",\n height: \"1px\",\n margin: \"-1px\",\n padding: \"0\",\n border: \"0\",\n overflow: \"hidden\",\n clip: \"rect(0 0 0 0)\",\n clipPath: \"inset(50%)\",\n whiteSpace: \"nowrap\",\n};\n\ninterface Regions {\n polite: HTMLElement;\n assertive: HTMLElement;\n}\n\nlet regions: Regions | null = null;\nconst timers = new Set<ReturnType<typeof setTimeout>>();\n\n/**\n * Build one live region.\n *\n * The polite region carries `role=\"status\"`; the assertive one carries only\n * `aria-live=\"assertive\"`. `role=\"alert\"` is deliberately **not** used: these\n * regions live in the document for the page's whole life, and an empty element\n * claiming to be an alert is both a lie about its content and a trap for every\n * `getByRole(\"alert\")` in a consuming app's test suite, which would suddenly match\n * two nodes. A bare `aria-live` region is announced by every screen reader that\n * supports live regions at all.\n *\n * @param politeness - Which region to build.\n * @returns The region, already appended to `<body>`.\n */\nfunction createRegion(politeness: AnnouncePoliteness): HTMLElement {\n const node = document.createElement(\"div\");\n if (politeness === \"polite\") node.setAttribute(\"role\", \"status\");\n node.setAttribute(\"aria-live\", politeness);\n node.setAttribute(\"aria-atomic\", \"true\");\n node.setAttribute(\"data-tempest-announcer\", politeness);\n Object.assign(node.style, HIDDEN_STYLE);\n document.body.appendChild(node);\n return node;\n}\n\n/**\n * Get the two shared live regions, creating them on first use.\n *\n * **Two regions, not one with a switchable `aria-live`.** Politeness is a property\n * of the region, read when the assistive technology first registers it — flipping\n * the attribute later is honoured by some screen readers, ignored by others, and\n * in the worst case drops the announcement entirely. Two regions that never change\n * are the only version that behaves the same everywhere.\n *\n * **Shared, not one per component.** Every live region on the page is polled;\n * several of them mutating at once is how announcements get dropped or doubled.\n * One pair per document keeps the order deterministic.\n *\n * A pair is replaced as a **pair**, discarding the old one: if only one region was\n * torn out of the DOM (a router that replaced `<body>`, a micro-frontend unmount),\n * keeping its still-attached sibling would leave two regions of that politeness on\n * the page — the exact duplication the shared pair exists to prevent.\n *\n * @returns The polite and assertive regions, or `null` outside a document.\n */\nfunction ensureRegions(): Regions | null {\n if (typeof document === \"undefined\") return null;\n if (regions && regions.polite.isConnected && regions.assertive.isConnected) return regions;\n regions?.polite.remove();\n regions?.assertive.remove();\n regions = { polite: createRegion(\"polite\"), assertive: createRegion(\"assertive\") };\n return regions;\n}\n\n/**\n * Announce a message to screen readers, from anywhere — a hook, an event handler,\n * a plain function outside React.\n *\n * ## Why the same string announces twice\n *\n * Screen readers announce a live region when its **content changes**. Writing the\n * same text again is not a change, so \"Item removido\" twice in a row is read once —\n * the classic reason these announcers are quietly broken. Instead of mutating text,\n * every call replaces the region's child with a **new element**. The DOM mutation is\n * real even when the string is identical, so the second announcement happens, and\n * the reader hears the exact message with no padding characters bolted on.\n *\n * @param message - Text to read out. Empty strings are ignored.\n * @param politeness - `\"polite\"` (default) or `\"assertive\"`.\n *\n * @example\n * announce(`${count} pedidos encontrados`);\n * announce(\"Falha ao salvar\", \"assertive\");\n */\nexport function announce(message: string, politeness: AnnouncePoliteness = \"polite\"): void {\n if (!message) return;\n const pair = ensureRegions();\n if (!pair) return;\n\n const region = politeness === \"assertive\" ? pair.assertive : pair.polite;\n region.replaceChildren();\n const item = document.createElement(\"div\");\n item.textContent = message;\n region.appendChild(item);\n\n const timer = setTimeout(() => {\n timers.delete(timer);\n if (item.isConnected) item.remove();\n }, CLEAR_AFTER_MS);\n timers.add(timer);\n}\n\n/**\n * Remove the shared regions and cancel pending cleanups.\n *\n * For test teardown and for a micro-frontend being unmounted from a page it does\n * not own. Regular apps never need it — two empty hidden `div`s cost nothing, and\n * tearing them down while another component still announces would lose messages.\n */\nexport function clearAnnouncer(): void {\n for (const timer of timers) clearTimeout(timer);\n timers.clear();\n regions?.polite.remove();\n regions?.assertive.remove();\n regions = null;\n}\n\n/**\n * Announce transient messages to screen readers through one shared live region\n * pair.\n *\n * Reach for this when something happened that a sighted user can see and a screen\n * reader user cannot: a filter narrowed a list, a row saved, a copy succeeded, an\n * upload failed. It is **not** for content that is already on screen inside a\n * region with a role — a status pill, a toast that renders as `role=\"status\"`,\n * a form error tied to its input — announcing those again reads them twice.\n *\n * !!! warning \"Never wrap streaming text in a live region\"\n * A live region over text that grows token by token makes the reader start the\n * whole answer again on every token. Announce the **edges** instead — \"gerando\n * resposta\" and \"resposta concluída\" — and leave the transcript in a plain\n * `role=\"log\"` the user reads at their own pace. `AIChat` does exactly that.\n *\n * Mounting the hook creates the regions even before the first message. That is not\n * tidiness: a live region inserted into the DOM in the same frame as its first\n * content routinely loses that announcement, because the assistive technology has\n * to have registered the region before it can notice a change inside it.\n *\n * @returns A stable `announce(message, politeness?)` function.\n *\n * @example\n * const announce = useAnnounce();\n *\n * function onFilter(rows: Row[]) {\n * announce(`${rows.length} resultados`);\n * }\n */\nexport function useAnnounce(): (message: string, politeness?: AnnouncePoliteness) => void {\n useEffect(() => {\n ensureRegions();\n }, []);\n\n return useCallback(\n (message: string, politeness: AnnouncePoliteness = \"polite\") =>\n announce(message, politeness),\n [],\n );\n}\n"],"mappings":"uBAaA,IAAM,EAAiB,IAUjB,EAA6C,CAC/C,SAAU,WACV,MAAO,MACP,OAAQ,MACR,OAAQ,OACR,QAAS,IACT,OAAQ,IACR,SAAU,SACV,KAAM,gBACN,SAAU,aACV,WAAY,QAChB,EAOI,EAA0B,KACxB,EAAS,IAAI,IAgBnB,SAAS,EAAa,EAA6C,CAC/D,IAAM,EAAO,SAAS,cAAc,KAAK,EAOzC,OANI,IAAe,UAAU,EAAK,aAAa,OAAQ,QAAQ,EAC/D,EAAK,aAAa,YAAa,CAAU,EACzC,EAAK,aAAa,cAAe,MAAM,EACvC,EAAK,aAAa,yBAA0B,CAAU,EACtD,OAAO,OAAO,EAAK,MAAO,CAAY,EACtC,SAAS,KAAK,YAAY,CAAI,EACvB,CACX,CAsBA,SAAS,GAAgC,CAMrC,OALI,OAAO,SAAa,IAAoB,KACxC,GAAW,EAAQ,OAAO,aAAe,EAAQ,UAAU,YAAoB,GACnF,GAAS,OAAO,OAAO,EACvB,GAAS,UAAU,OAAO,EAC1B,EAAU,CAAE,OAAQ,EAAa,QAAQ,EAAG,UAAW,EAAa,WAAW,CAAE,EAC1E,EACX,CAsBA,SAAgB,EAAS,EAAiB,EAAiC,SAAgB,CACvF,GAAI,CAAC,EAAS,OACd,IAAM,EAAO,EAAc,EAC3B,GAAI,CAAC,EAAM,OAEX,IAAM,EAAS,IAAe,YAAc,EAAK,UAAY,EAAK,OAClE,EAAO,gBAAgB,EACvB,IAAM,EAAO,SAAS,cAAc,KAAK,EACzC,EAAK,YAAc,EACnB,EAAO,YAAY,CAAI,EAEvB,IAAM,EAAQ,eAAiB,CAC3B,EAAO,OAAO,CAAK,EACf,EAAK,aAAa,EAAK,OAAO,CACtC,EAAG,CAAc,EACjB,EAAO,IAAI,CAAK,CACpB,CASA,SAAgB,GAAuB,CACnC,IAAK,IAAM,KAAS,EAAQ,aAAa,CAAK,EAC9C,EAAO,MAAM,EACb,GAAS,OAAO,OAAO,EACvB,GAAS,UAAU,OAAO,EAC1B,EAAU,IACd,CAgCA,SAAgB,GAA0E,CAKtF,OAJA,EAAA,EAAA,UAAA,KAAgB,CACZ,EAAc,CAClB,EAAG,CAAC,CAAC,GAEL,EAAO,EAAA,YAAA,EACF,EAAiB,EAAiC,WAC/C,EAAS,EAAS,CAAU,EAChC,CAAC,CACL,CACJ"}
@@ -1 +1 @@
1
- {"version":3,"file":"use-async.cjs","names":[],"sources":["../../src/hooks/use-async.ts"],"sourcesContent":["import { useCallback, useEffect, useRef, useState } from \"react\";\n\nexport type AsyncStatus = \"idle\" | \"pending\" | \"success\" | \"error\";\n\nexport interface UseAsyncResult<T> {\n status: AsyncStatus;\n data: T | undefined;\n error: unknown;\n isPending: boolean;\n isSuccess: boolean;\n isError: boolean;\n /** Trigger the async function. Resolves with the new data or rejects. */\n run: () => Promise<T>;\n /** Reset state back to idle. */\n reset: () => void;\n}\n\n/**\n * Run an async function and track its `idle/pending/success/error` state.\n *\n * - Discards results from stale runs (race-condition safe).\n * - `immediate` (default `false`) triggers the function on mount and when\n * `deps` change. With `false`, call `run()` manually.\n * - Returns a stable object so callers can destructure or pass around safely.\n *\n * For server data with caching, prefer React Query — `useAsync` is the\n * minimal one-shot primitive without dependencies.\n */\nexport function useAsync<T>(\n asyncFn: () => Promise<T>,\n deps: ReadonlyArray<unknown> = [],\n options: { immediate?: boolean } = {},\n): UseAsyncResult<T> {\n const { immediate = false } = options;\n const [state, setState] = useState<{\n status: AsyncStatus;\n data: T | undefined;\n error: unknown;\n }>({ status: \"idle\", data: undefined, error: undefined });\n\n const fnRef = useRef(asyncFn);\n useEffect(() => {\n fnRef.current = asyncFn;\n }, [asyncFn]);\n const callIdRef = useRef<number>(0);\n const mountedRef = useRef<boolean>(true);\n\n useEffect(() => {\n mountedRef.current = true;\n return () => {\n mountedRef.current = false;\n };\n }, []);\n\n const run = useCallback(async (): Promise<T> => {\n const callId = ++callIdRef.current;\n setState((current) => ({ ...current, status: \"pending\", error: undefined }));\n try {\n const data = await fnRef.current();\n if (mountedRef.current && callIdRef.current === callId) {\n setState({ status: \"success\", data, error: undefined });\n }\n return data;\n } catch (error) {\n if (mountedRef.current && callIdRef.current === callId) {\n setState({ status: \"error\", data: undefined, error });\n }\n throw error;\n }\n }, []);\n\n const reset = useCallback((): void => {\n callIdRef.current++;\n setState({ status: \"idle\", data: undefined, error: undefined });\n }, []);\n\n useEffect(() => {\n if (!immediate) return;\n run().catch(() => {\n /* error already captured in state */\n });\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [immediate, ...deps]);\n\n return {\n status: state.status,\n data: state.data,\n error: state.error,\n isPending: state.status === \"pending\",\n isSuccess: state.status === \"success\",\n isError: state.status === \"error\",\n run,\n reset,\n };\n}\n"],"mappings":"uBA4BA,SAAgB,EACZ,EACA,EAA+B,CAAC,EAChC,EAAmC,CAAC,EACnB,CACjB,GAAM,CAAE,YAAY,IAAU,EACxB,CAAC,EAAO,IAAA,EAAA,EAAA,SAAA,CAIX,CAAE,OAAQ,OAAQ,KAAM,IAAA,GAAW,MAAO,IAAA,EAAU,CAAC,EAElD,GAAA,EAAA,EAAA,OAAA,CAAe,CAAO,GAC5B,EAAA,EAAA,UAAA,KAAgB,CACZ,EAAM,QAAU,CACpB,EAAG,CAAC,CAAO,CAAC,EACZ,IAAM,GAAA,EAAA,EAAA,OAAA,CAA2B,CAAC,EAC5B,GAAA,EAAA,EAAA,OAAA,CAA6B,EAAI,GAEvC,EAAA,EAAA,UAAA,MACI,EAAW,QAAU,OACR,CACT,EAAW,QAAU,EACzB,GACD,CAAC,CAAC,EAEL,IAAM,GAAA,EAAA,EAAA,YAAA,CAAkB,SAAwB,CAC5C,IAAM,EAAS,EAAE,EAAU,QAC3B,EAAU,IAAa,CAAE,GAAG,EAAS,OAAQ,UAAW,MAAO,IAAA,EAAU,EAAE,EAC3E,GAAI,CACA,IAAM,EAAO,MAAM,EAAM,QAAQ,EAIjC,OAHI,EAAW,SAAW,EAAU,UAAY,GAC5C,EAAS,CAAE,OAAQ,UAAW,OAAM,MAAO,IAAA,EAAU,CAAC,EAEnD,CACX,OAAS,EAAO,CAIZ,MAHI,EAAW,SAAW,EAAU,UAAY,GAC5C,EAAS,CAAE,OAAQ,QAAS,KAAM,IAAA,GAAW,OAAM,CAAC,EAElD,CACV,CACJ,EAAG,CAAC,CAAC,EAEC,GAAA,EAAA,EAAA,YAAA,KAAgC,CAClC,EAAU,UACV,EAAS,CAAE,OAAQ,OAAQ,KAAM,IAAA,GAAW,MAAO,IAAA,EAAU,CAAC,CAClE,EAAG,CAAC,CAAC,EAUL,OARA,EAAA,EAAA,UAAA,KAAgB,CACP,GACL,EAAI,CAAC,CAAC,UAAY,CAElB,CAAC,CAEL,EAAG,CAAC,EAAW,GAAG,CAAI,CAAC,EAEhB,CACH,OAAQ,EAAM,OACd,KAAM,EAAM,KACZ,MAAO,EAAM,MACb,UAAW,EAAM,SAAW,UAC5B,UAAW,EAAM,SAAW,UAC5B,QAAS,EAAM,SAAW,QAC1B,MACA,OACJ,CACJ"}
1
+ {"version":3,"file":"use-async.cjs","names":[],"sources":["../../src/hooks/use-async.ts"],"sourcesContent":["import { useCallback, useEffect, useRef, useState } from \"react\";\n\nexport type AsyncStatus = \"idle\" | \"pending\" | \"success\" | \"error\";\n\nexport interface UseAsyncResult<T> {\n status: AsyncStatus;\n data: T | undefined;\n error: unknown;\n isPending: boolean;\n isSuccess: boolean;\n isError: boolean;\n /** Trigger the async function. Resolves with the new data or rejects. */\n run: () => Promise<T>;\n /** Reset state back to idle. */\n reset: () => void;\n}\n\n/**\n * Run an async function and track its `idle/pending/success/error` state.\n *\n * - Discards results from stale runs (race-condition safe).\n * - `immediate` (default `false`) triggers the function on mount and when\n * `deps` change. With `false`, call `run()` manually.\n * - Returns a stable object so callers can destructure or pass around safely.\n *\n * For server data with caching, prefer React Query — `useAsync` is the\n * minimal one-shot primitive without dependencies.\n */\nexport function useAsync<T>(\n asyncFn: () => Promise<T>,\n deps: ReadonlyArray<unknown> = [],\n options: { immediate?: boolean } = {},\n): UseAsyncResult<T> {\n const { immediate = false } = options;\n const [state, setState] = useState<{\n status: AsyncStatus;\n data: T | undefined;\n error: unknown;\n }>({ status: \"idle\", data: undefined, error: undefined });\n\n const fnRef = useRef(asyncFn);\n useEffect(() => {\n fnRef.current = asyncFn;\n }, [asyncFn]);\n const callIdRef = useRef<number>(0);\n const mountedRef = useRef<boolean>(true);\n\n useEffect(() => {\n mountedRef.current = true;\n return () => {\n mountedRef.current = false;\n };\n }, []);\n\n const run = useCallback(async (): Promise<T> => {\n const callId = ++callIdRef.current;\n setState((current) => ({ ...current, status: \"pending\", error: undefined }));\n try {\n const data = await fnRef.current();\n if (mountedRef.current && callIdRef.current === callId) {\n setState({ status: \"success\", data, error: undefined });\n }\n return data;\n } catch (error) {\n if (mountedRef.current && callIdRef.current === callId) {\n setState({ status: \"error\", data: undefined, error });\n }\n throw error;\n }\n }, []);\n\n const reset = useCallback((): void => {\n callIdRef.current++;\n setState({ status: \"idle\", data: undefined, error: undefined });\n }, []);\n\n useEffect(() => {\n if (!immediate) return;\n run().catch(() => {\n /* error already captured in state */\n });\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [immediate, ...deps]);\n\n return {\n status: state.status,\n data: state.data,\n error: state.error,\n isPending: state.status === \"pending\",\n isSuccess: state.status === \"success\",\n isError: state.status === \"error\",\n run,\n reset,\n };\n}\n"],"mappings":"uBA4BA,SAAgB,EACZ,EACA,EAA+B,CAAC,EAChC,EAAmC,CAAC,EACnB,CACjB,GAAM,CAAE,YAAY,IAAU,EACxB,CAAC,EAAO,IAAA,EAAY,EAAA,SAAA,CAIvB,CAAE,OAAQ,OAAQ,KAAM,IAAA,GAAW,MAAO,IAAA,EAAU,CAAC,EAElD,GAAA,EAAQ,EAAA,OAAA,CAAO,CAAO,GAC5B,EAAA,EAAA,UAAA,KAAgB,CACZ,EAAM,QAAU,CACpB,EAAG,CAAC,CAAO,CAAC,EACZ,IAAM,GAAA,EAAY,EAAA,OAAA,CAAe,CAAC,EAC5B,GAAA,EAAa,EAAA,OAAA,CAAgB,EAAI,GAEvC,EAAA,EAAA,UAAA,MACI,EAAW,QAAU,OACR,CACT,EAAW,QAAU,EACzB,GACD,CAAC,CAAC,EAEL,IAAM,GAAA,EAAM,EAAA,YAAA,CAAY,SAAwB,CAC5C,IAAM,EAAS,EAAE,EAAU,QAC3B,EAAU,IAAa,CAAE,GAAG,EAAS,OAAQ,UAAW,MAAO,IAAA,EAAU,EAAE,EAC3E,GAAI,CACA,IAAM,EAAO,MAAM,EAAM,QAAQ,EAIjC,OAHI,EAAW,SAAW,EAAU,UAAY,GAC5C,EAAS,CAAE,OAAQ,UAAW,OAAM,MAAO,IAAA,EAAU,CAAC,EAEnD,CACX,OAAS,EAAO,CAIZ,MAHI,EAAW,SAAW,EAAU,UAAY,GAC5C,EAAS,CAAE,OAAQ,QAAS,KAAM,IAAA,GAAW,OAAM,CAAC,EAElD,CACV,CACJ,EAAG,CAAC,CAAC,EAEC,GAAA,EAAQ,EAAA,YAAA,KAAwB,CAClC,EAAU,UACV,EAAS,CAAE,OAAQ,OAAQ,KAAM,IAAA,GAAW,MAAO,IAAA,EAAU,CAAC,CAClE,EAAG,CAAC,CAAC,EAUL,OARA,EAAA,EAAA,UAAA,KAAgB,CACP,GACL,EAAI,CAAC,CAAC,UAAY,CAElB,CAAC,CAEL,EAAG,CAAC,EAAW,GAAG,CAAI,CAAC,EAEhB,CACH,OAAQ,EAAM,OACd,KAAM,EAAM,KACZ,MAAO,EAAM,MACb,UAAW,EAAM,SAAW,UAC5B,UAAW,EAAM,SAAW,UAC5B,QAAS,EAAM,SAAW,QAC1B,MACA,OACJ,CACJ"}
@@ -1 +1 @@
1
- {"version":3,"file":"use-before-install-prompt.cjs","names":[],"sources":["../../src/hooks/use-before-install-prompt.ts"],"sourcesContent":["import { useCallback, useEffect, useState } from \"react\";\n\ninterface BeforeInstallPromptEvent extends Event {\n readonly platforms: readonly string[];\n readonly userChoice: Promise<{ outcome: \"accepted\" | \"dismissed\"; platform: string }>;\n prompt(): Promise<void>;\n}\n\nexport interface UseBeforeInstallPromptResult {\n /** True when the browser fired `beforeinstallprompt` and the user has not yet decided. */\n installable: boolean;\n /** True after the user accepts the install prompt. */\n installed: boolean;\n /**\n * True when the app is already running as an installed PWA (display-mode\n * `standalone`/`fullscreen`/`minimal-ui`, or iOS `navigator.standalone`).\n * Use it to hide install affordances for users who already installed.\n */\n isStandalone: boolean;\n /** Show the install prompt. Resolves with the user's choice. */\n prompt: () => Promise<\"accepted\" | \"dismissed\" | \"unsupported\">;\n}\n\n/** Detect whether the document is running inside an installed PWA window. */\nfunction detectStandalone(): boolean {\n if (typeof window === \"undefined\") return false;\n const displayModes = [\"standalone\", \"fullscreen\", \"minimal-ui\"];\n const matchesDisplayMode = displayModes.some(\n (mode) => window.matchMedia?.(`(display-mode: ${mode})`)?.matches,\n );\n // iOS Safari exposes the legacy non-standard `navigator.standalone`.\n const iosStandalone =\n (window.navigator as Navigator & { standalone?: boolean }).standalone === true;\n return matchesDisplayMode || iosStandalone;\n}\n\n/**\n * React hook for the PWA install prompt. Captures the `beforeinstallprompt`\n * event so you can defer the install UI to a moment that fits your UX.\n */\nexport function useBeforeInstallPrompt(): UseBeforeInstallPromptResult {\n const [deferred, setDeferred] = useState<BeforeInstallPromptEvent | null>(null);\n const [installed, setInstalled] = useState<boolean>(false);\n const [isStandalone, setIsStandalone] = useState<boolean>(detectStandalone);\n\n useEffect(() => {\n if (typeof window === \"undefined\") return;\n const handler = (event: Event): void => {\n event.preventDefault();\n setDeferred(event as BeforeInstallPromptEvent);\n };\n const installedHandler = (): void => {\n setDeferred(null);\n setInstalled(true);\n setIsStandalone(true);\n };\n const displayModeQuery = window.matchMedia?.(\"(display-mode: standalone)\");\n const displayModeHandler = (): void => setIsStandalone(detectStandalone());\n window.addEventListener(\"beforeinstallprompt\", handler);\n window.addEventListener(\"appinstalled\", installedHandler);\n displayModeQuery?.addEventListener(\"change\", displayModeHandler);\n return () => {\n window.removeEventListener(\"beforeinstallprompt\", handler);\n window.removeEventListener(\"appinstalled\", installedHandler);\n displayModeQuery?.removeEventListener(\"change\", displayModeHandler);\n };\n }, []);\n\n const prompt = useCallback(async (): Promise<\"accepted\" | \"dismissed\" | \"unsupported\"> => {\n if (!deferred) return \"unsupported\";\n await deferred.prompt();\n const result = await deferred.userChoice;\n setDeferred(null);\n if (result.outcome === \"accepted\") setInstalled(true);\n return result.outcome;\n }, [deferred]);\n\n return {\n installable: !!deferred,\n installed,\n isStandalone,\n prompt,\n };\n}\n"],"mappings":"uBAwBA,SAAS,GAA4B,CACjC,GAAI,OAAO,OAAW,IAAa,MAAO,GAE1C,IAAM,EAAqB,CADL,aAAc,aAAc,YACvB,CAAA,CAAa,KACnC,GAAS,OAAO,aAAa,kBAAkB,EAAK,EAAE,CAAC,EAAE,OAC9D,EAEM,EACD,OAAO,UAAmD,aAAe,GAC9E,OAAO,GAAsB,CACjC,CAMA,SAAgB,GAAuD,CACnE,GAAM,CAAC,EAAU,IAAA,EAAA,EAAA,SAAA,CAAyD,IAAI,EACxE,CAAC,EAAW,IAAA,EAAA,EAAA,SAAA,CAAkC,EAAK,EACnD,CAAC,EAAc,IAAA,EAAA,EAAA,SAAA,CAAqC,CAAgB,GAE1E,EAAA,EAAA,UAAA,KAAgB,CACZ,GAAI,OAAO,OAAW,IAAa,OACnC,IAAM,EAAW,GAAuB,CACpC,EAAM,eAAe,EACrB,EAAY,CAAiC,CACjD,EACM,MAA+B,CACjC,EAAY,IAAI,EAChB,EAAa,EAAI,EACjB,EAAgB,EAAI,CACxB,EACM,EAAmB,OAAO,aAAa,4BAA4B,EACnE,MAAiC,EAAgB,EAAiB,CAAC,EAIzE,OAHA,OAAO,iBAAiB,sBAAuB,CAAO,EACtD,OAAO,iBAAiB,eAAgB,CAAgB,EACxD,GAAkB,iBAAiB,SAAU,CAAkB,MAClD,CACT,OAAO,oBAAoB,sBAAuB,CAAO,EACzD,OAAO,oBAAoB,eAAgB,CAAgB,EAC3D,GAAkB,oBAAoB,SAAU,CAAkB,CACtE,CACJ,EAAG,CAAC,CAAC,EAEL,IAAM,GAAA,EAAA,EAAA,YAAA,CAAqB,SAA+D,CACtF,GAAI,CAAC,EAAU,MAAO,cACtB,MAAM,EAAS,OAAO,EACtB,IAAM,EAAS,MAAM,EAAS,WAG9B,OAFA,EAAY,IAAI,EACZ,EAAO,UAAY,YAAY,EAAa,EAAI,EAC7C,EAAO,OAClB,EAAG,CAAC,CAAQ,CAAC,EAEb,MAAO,CACH,YAAa,CAAC,CAAC,EACf,YACA,eACA,QACJ,CACJ"}
1
+ {"version":3,"file":"use-before-install-prompt.cjs","names":[],"sources":["../../src/hooks/use-before-install-prompt.ts"],"sourcesContent":["import { useCallback, useEffect, useState } from \"react\";\n\ninterface BeforeInstallPromptEvent extends Event {\n readonly platforms: readonly string[];\n readonly userChoice: Promise<{ outcome: \"accepted\" | \"dismissed\"; platform: string }>;\n prompt(): Promise<void>;\n}\n\nexport interface UseBeforeInstallPromptResult {\n /** True when the browser fired `beforeinstallprompt` and the user has not yet decided. */\n installable: boolean;\n /** True after the user accepts the install prompt. */\n installed: boolean;\n /**\n * True when the app is already running as an installed PWA (display-mode\n * `standalone`/`fullscreen`/`minimal-ui`, or iOS `navigator.standalone`).\n * Use it to hide install affordances for users who already installed.\n */\n isStandalone: boolean;\n /** Show the install prompt. Resolves with the user's choice. */\n prompt: () => Promise<\"accepted\" | \"dismissed\" | \"unsupported\">;\n}\n\n/** Detect whether the document is running inside an installed PWA window. */\nfunction detectStandalone(): boolean {\n if (typeof window === \"undefined\") return false;\n const displayModes = [\"standalone\", \"fullscreen\", \"minimal-ui\"];\n const matchesDisplayMode = displayModes.some(\n (mode) => window.matchMedia?.(`(display-mode: ${mode})`)?.matches,\n );\n // iOS Safari exposes the legacy non-standard `navigator.standalone`.\n const iosStandalone =\n (window.navigator as Navigator & { standalone?: boolean }).standalone === true;\n return matchesDisplayMode || iosStandalone;\n}\n\n/**\n * React hook for the PWA install prompt. Captures the `beforeinstallprompt`\n * event so you can defer the install UI to a moment that fits your UX.\n */\nexport function useBeforeInstallPrompt(): UseBeforeInstallPromptResult {\n const [deferred, setDeferred] = useState<BeforeInstallPromptEvent | null>(null);\n const [installed, setInstalled] = useState<boolean>(false);\n const [isStandalone, setIsStandalone] = useState<boolean>(detectStandalone);\n\n useEffect(() => {\n if (typeof window === \"undefined\") return;\n const handler = (event: Event): void => {\n event.preventDefault();\n setDeferred(event as BeforeInstallPromptEvent);\n };\n const installedHandler = (): void => {\n setDeferred(null);\n setInstalled(true);\n setIsStandalone(true);\n };\n const displayModeQuery = window.matchMedia?.(\"(display-mode: standalone)\");\n const displayModeHandler = (): void => setIsStandalone(detectStandalone());\n window.addEventListener(\"beforeinstallprompt\", handler);\n window.addEventListener(\"appinstalled\", installedHandler);\n displayModeQuery?.addEventListener(\"change\", displayModeHandler);\n return () => {\n window.removeEventListener(\"beforeinstallprompt\", handler);\n window.removeEventListener(\"appinstalled\", installedHandler);\n displayModeQuery?.removeEventListener(\"change\", displayModeHandler);\n };\n }, []);\n\n const prompt = useCallback(async (): Promise<\"accepted\" | \"dismissed\" | \"unsupported\"> => {\n if (!deferred) return \"unsupported\";\n await deferred.prompt();\n const result = await deferred.userChoice;\n setDeferred(null);\n if (result.outcome === \"accepted\") setInstalled(true);\n return result.outcome;\n }, [deferred]);\n\n return {\n installable: !!deferred,\n installed,\n isStandalone,\n prompt,\n };\n}\n"],"mappings":"uBAwBA,SAAS,GAA4B,CACjC,GAAI,OAAO,OAAW,IAAa,MAAO,GAE1C,IAAM,EAAqB,CADL,aAAc,aAAc,YACvB,CAAA,CAAa,KACnC,GAAS,OAAO,aAAa,kBAAkB,EAAK,EAAE,CAAC,EAAE,OAC9D,EAEM,EACD,OAAO,UAAmD,aAAe,GAC9E,OAAO,GAAsB,CACjC,CAMA,SAAgB,GAAuD,CACnE,GAAM,CAAC,EAAU,IAAA,EAAe,EAAA,SAAA,CAA0C,IAAI,EACxE,CAAC,EAAW,IAAA,EAAgB,EAAA,SAAA,CAAkB,EAAK,EACnD,CAAC,EAAc,IAAA,EAAmB,EAAA,SAAA,CAAkB,CAAgB,GAE1E,EAAA,EAAA,UAAA,KAAgB,CACZ,GAAI,OAAO,OAAW,IAAa,OACnC,IAAM,EAAW,GAAuB,CACpC,EAAM,eAAe,EACrB,EAAY,CAAiC,CACjD,EACM,MAA+B,CACjC,EAAY,IAAI,EAChB,EAAa,EAAI,EACjB,EAAgB,EAAI,CACxB,EACM,EAAmB,OAAO,aAAa,4BAA4B,EACnE,MAAiC,EAAgB,EAAiB,CAAC,EAIzE,OAHA,OAAO,iBAAiB,sBAAuB,CAAO,EACtD,OAAO,iBAAiB,eAAgB,CAAgB,EACxD,GAAkB,iBAAiB,SAAU,CAAkB,MAClD,CACT,OAAO,oBAAoB,sBAAuB,CAAO,EACzD,OAAO,oBAAoB,eAAgB,CAAgB,EAC3D,GAAkB,oBAAoB,SAAU,CAAkB,CACtE,CACJ,EAAG,CAAC,CAAC,EAEL,IAAM,GAAA,EAAS,EAAA,YAAA,CAAY,SAA+D,CACtF,GAAI,CAAC,EAAU,MAAO,cACtB,MAAM,EAAS,OAAO,EACtB,IAAM,EAAS,MAAM,EAAS,WAG9B,OAFA,EAAY,IAAI,EACZ,EAAO,UAAY,YAAY,EAAa,EAAI,EAC7C,EAAO,OAClB,EAAG,CAAC,CAAQ,CAAC,EAEb,MAAO,CACH,YAAa,CAAC,CAAC,EACf,YACA,eACA,QACJ,CACJ"}
@@ -1 +1 @@
1
- {"version":3,"file":"use-breakpoint.cjs","names":[],"sources":["../../src/hooks/use-breakpoint.ts"],"sourcesContent":["import { useEffect, useState } from \"react\";\n\n/**\n * Breakpoint keys exposed by the SDK. Values mirror the CSS tokens\n * `--tempest-bp-xs|sm|md|lg|xl|2xl` defined in `styles/colors.css`.\n */\nexport type Breakpoint = \"xs\" | \"sm\" | \"md\" | \"lg\" | \"xl\" | \"2xl\";\n\n/** Pixel value paired with each breakpoint key. */\nexport const BREAKPOINTS: Record<Breakpoint, number> = {\n xs: 480,\n sm: 640,\n md: 768,\n lg: 1024,\n xl: 1280,\n \"2xl\": 1536,\n};\n\nconst ORDER: readonly Breakpoint[] = [\"xs\", \"sm\", \"md\", \"lg\", \"xl\", \"2xl\"] as const;\n\nfunction resolveCurrent(width: number): Breakpoint {\n let current: Breakpoint = \"xs\";\n for (const key of ORDER) {\n if (width >= BREAKPOINTS[key]) {\n current = key;\n }\n }\n return current;\n}\n\nexport interface BreakpointHelpers {\n /** Current breakpoint key (the largest one whose min-width is matched). */\n current: Breakpoint;\n /** Window width in pixels at last update. `0` when SSR. */\n width: number;\n /** True when viewport width is `>=` the given breakpoint. */\n above: (bp: Breakpoint) => boolean;\n /** True when viewport width is `<` the given breakpoint. */\n below: (bp: Breakpoint) => boolean;\n /** Convenience flags mapping to typical device shapes. */\n isMobile: boolean;\n isTablet: boolean;\n isDesktop: boolean;\n}\n\n/**\n * Reactive viewport-breakpoint hook.\n *\n * Returns the current breakpoint key plus `above` / `below` helpers and\n * `isMobile` / `isTablet` / `isDesktop` flags. SSR-safe — returns `xs` /\n * `width: 0` on the server, then updates after mount.\n */\nexport function useBreakpoint(): BreakpointHelpers {\n const [width, setWidth] = useState<number>(() =>\n typeof window === \"undefined\" ? 0 : window.innerWidth,\n );\n\n useEffect(() => {\n if (typeof window === \"undefined\") return;\n const onResize = (): void => setWidth(window.innerWidth);\n onResize();\n window.addEventListener(\"resize\", onResize, { passive: true });\n return () => window.removeEventListener(\"resize\", onResize);\n }, []);\n\n const current = resolveCurrent(width);\n const above = (bp: Breakpoint): boolean => width >= BREAKPOINTS[bp];\n const below = (bp: Breakpoint): boolean => width < BREAKPOINTS[bp];\n\n return {\n current,\n width,\n above,\n below,\n isMobile: below(\"md\"),\n isTablet: above(\"md\") && below(\"lg\"),\n isDesktop: above(\"lg\"),\n };\n}\n"],"mappings":"uBASA,IAAa,EAA0C,CACnD,GAAI,IACJ,GAAI,IACJ,GAAI,IACJ,GAAI,KACJ,GAAI,KACJ,MAAO,IACX,EAEM,EAA+B,CAAC,KAAM,KAAM,KAAM,KAAM,KAAM,KAAK,EAEzE,SAAS,EAAe,EAA2B,CAC/C,IAAI,EAAsB,KAC1B,IAAK,IAAM,KAAO,EACV,GAAS,EAAY,KACrB,EAAU,GAGlB,OAAO,CACX,CAwBA,SAAgB,GAAmC,CAC/C,GAAM,CAAC,EAAO,IAAA,EAAA,EAAA,SAAA,KACV,OAAO,OAAW,IAAc,EAAI,OAAO,UAC/C,GAEA,EAAA,EAAA,UAAA,KAAgB,CACZ,GAAI,OAAO,OAAW,IAAa,OACnC,IAAM,MAAuB,EAAS,OAAO,UAAU,EAGvD,OAFA,EAAS,EACT,OAAO,iBAAiB,SAAU,EAAU,CAAE,QAAS,EAAK,CAAC,MAChD,OAAO,oBAAoB,SAAU,CAAQ,CAC9D,EAAG,CAAC,CAAC,EAEL,IAAM,EAAU,EAAe,CAAK,EAC9B,EAAS,GAA4B,GAAS,EAAY,GAC1D,EAAS,GAA4B,EAAQ,EAAY,GAE/D,MAAO,CACH,UACA,QACA,QACA,QACA,SAAU,EAAM,IAAI,EACpB,SAAU,EAAM,IAAI,GAAK,EAAM,IAAI,EACnC,UAAW,EAAM,IAAI,CACzB,CACJ"}
1
+ {"version":3,"file":"use-breakpoint.cjs","names":[],"sources":["../../src/hooks/use-breakpoint.ts"],"sourcesContent":["import { useEffect, useState } from \"react\";\n\n/**\n * Breakpoint keys exposed by the SDK. Values mirror the CSS tokens\n * `--tempest-bp-xs|sm|md|lg|xl|2xl` defined in `styles/colors.css`.\n */\nexport type Breakpoint = \"xs\" | \"sm\" | \"md\" | \"lg\" | \"xl\" | \"2xl\";\n\n/** Pixel value paired with each breakpoint key. */\nexport const BREAKPOINTS: Record<Breakpoint, number> = {\n xs: 480,\n sm: 640,\n md: 768,\n lg: 1024,\n xl: 1280,\n \"2xl\": 1536,\n};\n\nconst ORDER: readonly Breakpoint[] = [\"xs\", \"sm\", \"md\", \"lg\", \"xl\", \"2xl\"] as const;\n\nfunction resolveCurrent(width: number): Breakpoint {\n let current: Breakpoint = \"xs\";\n for (const key of ORDER) {\n if (width >= BREAKPOINTS[key]) {\n current = key;\n }\n }\n return current;\n}\n\nexport interface BreakpointHelpers {\n /** Current breakpoint key (the largest one whose min-width is matched). */\n current: Breakpoint;\n /** Window width in pixels at last update. `0` when SSR. */\n width: number;\n /** True when viewport width is `>=` the given breakpoint. */\n above: (bp: Breakpoint) => boolean;\n /** True when viewport width is `<` the given breakpoint. */\n below: (bp: Breakpoint) => boolean;\n /** Convenience flags mapping to typical device shapes. */\n isMobile: boolean;\n isTablet: boolean;\n isDesktop: boolean;\n}\n\n/**\n * Reactive viewport-breakpoint hook.\n *\n * Returns the current breakpoint key plus `above` / `below` helpers and\n * `isMobile` / `isTablet` / `isDesktop` flags. SSR-safe — returns `xs` /\n * `width: 0` on the server, then updates after mount.\n */\nexport function useBreakpoint(): BreakpointHelpers {\n const [width, setWidth] = useState<number>(() =>\n typeof window === \"undefined\" ? 0 : window.innerWidth,\n );\n\n useEffect(() => {\n if (typeof window === \"undefined\") return;\n const onResize = (): void => setWidth(window.innerWidth);\n onResize();\n window.addEventListener(\"resize\", onResize, { passive: true });\n return () => window.removeEventListener(\"resize\", onResize);\n }, []);\n\n const current = resolveCurrent(width);\n const above = (bp: Breakpoint): boolean => width >= BREAKPOINTS[bp];\n const below = (bp: Breakpoint): boolean => width < BREAKPOINTS[bp];\n\n return {\n current,\n width,\n above,\n below,\n isMobile: below(\"md\"),\n isTablet: above(\"md\") && below(\"lg\"),\n isDesktop: above(\"lg\"),\n };\n}\n"],"mappings":"uBASA,IAAa,EAA0C,CACnD,GAAI,IACJ,GAAI,IACJ,GAAI,IACJ,GAAI,KACJ,GAAI,KACJ,MAAO,IACX,EAEM,EAA+B,CAAC,KAAM,KAAM,KAAM,KAAM,KAAM,KAAK,EAEzE,SAAS,EAAe,EAA2B,CAC/C,IAAI,EAAsB,KAC1B,IAAK,IAAM,KAAO,EACV,GAAS,EAAY,KACrB,EAAU,GAGlB,OAAO,CACX,CAwBA,SAAgB,GAAmC,CAC/C,GAAM,CAAC,EAAO,IAAA,EAAY,EAAA,SAAA,KACtB,OAAO,OAAW,IAAc,EAAI,OAAO,UAC/C,GAEA,EAAA,EAAA,UAAA,KAAgB,CACZ,GAAI,OAAO,OAAW,IAAa,OACnC,IAAM,MAAuB,EAAS,OAAO,UAAU,EAGvD,OAFA,EAAS,EACT,OAAO,iBAAiB,SAAU,EAAU,CAAE,QAAS,EAAK,CAAC,MAChD,OAAO,oBAAoB,SAAU,CAAQ,CAC9D,EAAG,CAAC,CAAC,EAEL,IAAM,EAAU,EAAe,CAAK,EAC9B,EAAS,GAA4B,GAAS,EAAY,GAC1D,EAAS,GAA4B,EAAQ,EAAY,GAE/D,MAAO,CACH,UACA,QACA,QACA,QACA,SAAU,EAAM,IAAI,EACpB,SAAU,EAAM,IAAI,GAAK,EAAM,IAAI,EACnC,UAAW,EAAM,IAAI,CACzB,CACJ"}
@@ -1 +1 @@
1
- {"version":3,"file":"use-click-outside.cjs","names":[],"sources":["../../src/hooks/use-click-outside.ts"],"sourcesContent":["import { useEffect, useRef } from \"react\";\nimport type { RefObject } from \"react\";\n\n/**\n * Call `handler` when a `mousedown` / `touchstart` occurs outside the returned\n * ref's element. SSR-safe — listeners are only attached in the browser.\n *\n * @typeParam T - element type to attach the ref to.\n * @param handler - invoked on an outside interaction.\n * @returns A ref to attach to the element treated as \"inside\".\n */\nexport function useClickOutside<T extends HTMLElement = HTMLElement>(\n handler: () => void,\n): RefObject<T | null> {\n const ref = useRef<T>(null);\n const handlerRef = useRef(handler);\n useEffect(() => {\n handlerRef.current = handler;\n }, [handler]);\n\n useEffect(() => {\n if (typeof document === \"undefined\") return;\n\n const listener = (event: MouseEvent | TouchEvent): void => {\n const target = event.target as Node | null;\n if (!ref.current || (target && ref.current.contains(target))) return;\n handlerRef.current();\n };\n\n document.addEventListener(\"mousedown\", listener);\n document.addEventListener(\"touchstart\", listener);\n return () => {\n document.removeEventListener(\"mousedown\", listener);\n document.removeEventListener(\"touchstart\", listener);\n };\n }, []);\n\n return ref;\n}\n"],"mappings":"uBAWA,SAAgB,EACZ,EACmB,CACnB,IAAM,GAAA,EAAA,EAAA,OAAA,CAAgB,IAAI,EACpB,GAAA,EAAA,EAAA,OAAA,CAAoB,CAAO,EAsBjC,OArBA,EAAA,EAAA,UAAA,KAAgB,CACZ,EAAW,QAAU,CACzB,EAAG,CAAC,CAAO,CAAC,GAEZ,EAAA,EAAA,UAAA,KAAgB,CACZ,GAAI,OAAO,SAAa,IAAa,OAErC,IAAM,EAAY,GAAyC,CACvD,IAAM,EAAS,EAAM,OACjB,CAAC,EAAI,SAAY,GAAU,EAAI,QAAQ,SAAS,CAAM,GAC1D,EAAW,QAAQ,CACvB,EAIA,OAFA,SAAS,iBAAiB,YAAa,CAAQ,EAC/C,SAAS,iBAAiB,aAAc,CAAQ,MACnC,CACT,SAAS,oBAAoB,YAAa,CAAQ,EAClD,SAAS,oBAAoB,aAAc,CAAQ,CACvD,CACJ,EAAG,CAAC,CAAC,EAEE,CACX"}
1
+ {"version":3,"file":"use-click-outside.cjs","names":[],"sources":["../../src/hooks/use-click-outside.ts"],"sourcesContent":["import { useEffect, useRef } from \"react\";\nimport type { RefObject } from \"react\";\n\n/**\n * Call `handler` when a `mousedown` / `touchstart` occurs outside the returned\n * ref's element. SSR-safe — listeners are only attached in the browser.\n *\n * @typeParam T - element type to attach the ref to.\n * @param handler - invoked on an outside interaction.\n * @returns A ref to attach to the element treated as \"inside\".\n */\nexport function useClickOutside<T extends HTMLElement = HTMLElement>(\n handler: () => void,\n): RefObject<T | null> {\n const ref = useRef<T>(null);\n const handlerRef = useRef(handler);\n useEffect(() => {\n handlerRef.current = handler;\n }, [handler]);\n\n useEffect(() => {\n if (typeof document === \"undefined\") return;\n\n const listener = (event: MouseEvent | TouchEvent): void => {\n const target = event.target as Node | null;\n if (!ref.current || (target && ref.current.contains(target))) return;\n handlerRef.current();\n };\n\n document.addEventListener(\"mousedown\", listener);\n document.addEventListener(\"touchstart\", listener);\n return () => {\n document.removeEventListener(\"mousedown\", listener);\n document.removeEventListener(\"touchstart\", listener);\n };\n }, []);\n\n return ref;\n}\n"],"mappings":"uBAWA,SAAgB,EACZ,EACmB,CACnB,IAAM,GAAA,EAAM,EAAA,OAAA,CAAU,IAAI,EACpB,GAAA,EAAa,EAAA,OAAA,CAAO,CAAO,EAsBjC,OArBA,EAAA,EAAA,UAAA,KAAgB,CACZ,EAAW,QAAU,CACzB,EAAG,CAAC,CAAO,CAAC,GAEZ,EAAA,EAAA,UAAA,KAAgB,CACZ,GAAI,OAAO,SAAa,IAAa,OAErC,IAAM,EAAY,GAAyC,CACvD,IAAM,EAAS,EAAM,OACjB,CAAC,EAAI,SAAY,GAAU,EAAI,QAAQ,SAAS,CAAM,GAC1D,EAAW,QAAQ,CACvB,EAIA,OAFA,SAAS,iBAAiB,YAAa,CAAQ,EAC/C,SAAS,iBAAiB,aAAc,CAAQ,MACnC,CACT,SAAS,oBAAoB,YAAa,CAAQ,EAClD,SAAS,oBAAoB,aAAc,CAAQ,CACvD,CACJ,EAAG,CAAC,CAAC,EAEE,CACX"}
@@ -1 +1 @@
1
- {"version":3,"file":"use-client-filter.cjs","names":[],"sources":["../../src/hooks/use-client-filter.ts"],"sourcesContent":["import { useMemo } from \"react\";\n\nexport type FilterPredicate<T> = (item: T, search: string) => boolean;\n\n/**\n * Client-side filter helper. Performs a case-insensitive match on the listed\n * keys when no custom predicate is provided.\n *\n * @param items - Source list.\n * @param search - Search string.\n * @param keysOrPredicate - Either a list of keys to match against or a custom predicate.\n * @returns Filtered list (referential identity preserved when search is empty).\n */\nexport function useClientFilter<T extends Record<string, unknown>>(\n items: T[],\n search: string,\n keysOrPredicate: (keyof T)[] | FilterPredicate<T>,\n): T[] {\n return useMemo(() => {\n const term = search.trim().toLowerCase();\n if (!term) return items;\n if (typeof keysOrPredicate === \"function\") {\n return items.filter((item) => keysOrPredicate(item, term));\n }\n return items.filter((item) =>\n keysOrPredicate.some((key) => {\n const value = item[key];\n return value != null && String(value).toLowerCase().includes(term);\n }),\n );\n }, [items, search, keysOrPredicate]);\n}\n"],"mappings":"uBAaA,SAAgB,EACZ,EACA,EACA,EACG,CACH,OAAA,EAAA,EAAA,QAAA,KAAqB,CACjB,IAAM,EAAO,EAAO,KAAK,CAAC,CAAC,YAAY,EAKvC,OAJK,EACD,OAAO,GAAoB,WACpB,EAAM,OAAQ,GAAS,EAAgB,EAAM,CAAI,CAAC,EAEtD,EAAM,OAAQ,GACjB,EAAgB,KAAM,GAAQ,CAC1B,IAAM,EAAQ,EAAK,GACnB,OAAO,GAAS,MAAQ,OAAO,CAAK,CAAC,CAAC,YAAY,CAAC,CAAC,SAAS,CAAI,CACrE,CAAC,CACL,EATkB,CAUtB,EAAG,CAAC,EAAO,EAAQ,CAAe,CAAC,CACvC"}
1
+ {"version":3,"file":"use-client-filter.cjs","names":[],"sources":["../../src/hooks/use-client-filter.ts"],"sourcesContent":["import { useMemo } from \"react\";\n\nexport type FilterPredicate<T> = (item: T, search: string) => boolean;\n\n/**\n * Client-side filter helper. Performs a case-insensitive match on the listed\n * keys when no custom predicate is provided.\n *\n * @param items - Source list.\n * @param search - Search string.\n * @param keysOrPredicate - Either a list of keys to match against or a custom predicate.\n * @returns Filtered list (referential identity preserved when search is empty).\n */\nexport function useClientFilter<T extends Record<string, unknown>>(\n items: T[],\n search: string,\n keysOrPredicate: (keyof T)[] | FilterPredicate<T>,\n): T[] {\n return useMemo(() => {\n const term = search.trim().toLowerCase();\n if (!term) return items;\n if (typeof keysOrPredicate === \"function\") {\n return items.filter((item) => keysOrPredicate(item, term));\n }\n return items.filter((item) =>\n keysOrPredicate.some((key) => {\n const value = item[key];\n return value != null && String(value).toLowerCase().includes(term);\n }),\n );\n }, [items, search, keysOrPredicate]);\n}\n"],"mappings":"uBAaA,SAAgB,EACZ,EACA,EACA,EACG,CACH,OAAA,EAAO,EAAA,QAAA,KAAc,CACjB,IAAM,EAAO,EAAO,KAAK,CAAC,CAAC,YAAY,EAKvC,OAJK,EACD,OAAO,GAAoB,WACpB,EAAM,OAAQ,GAAS,EAAgB,EAAM,CAAI,CAAC,EAEtD,EAAM,OAAQ,GACjB,EAAgB,KAAM,GAAQ,CAC1B,IAAM,EAAQ,EAAK,GACnB,OAAO,GAAS,MAAQ,OAAO,CAAK,CAAC,CAAC,YAAY,CAAC,CAAC,SAAS,CAAI,CACrE,CAAC,CACL,EATkB,CAUtB,EAAG,CAAC,EAAO,EAAQ,CAAe,CAAC,CACvC"}
@@ -1 +1 @@
1
- {"version":3,"file":"use-clipboard.cjs","names":[],"sources":["../../src/hooks/use-clipboard.ts"],"sourcesContent":["import { useCallback, useEffect, useRef, useState } from \"react\";\n\nexport interface UseClipboardOptions {\n /** Time (ms) before the `copied` flag resets back to false. Default: 1500. */\n resetAfter?: number;\n}\n\nexport interface UseClipboardResult {\n /** True briefly after a successful copy. */\n copied: boolean;\n /** Copy a string to the clipboard. Returns `true` on success. */\n copy: (text: string) => Promise<boolean>;\n /** Manually reset the `copied` flag. */\n reset: () => void;\n}\n\n/**\n * Wrapper around `navigator.clipboard.writeText` with a transient `copied`\n * flag for UI feedback (\"Copied!\" toast). Falls back gracefully when the\n * Clipboard API is unavailable.\n */\nexport function useClipboard(options: UseClipboardOptions = {}): UseClipboardResult {\n const { resetAfter = 1500 } = options;\n const [copied, setCopied] = useState<boolean>(false);\n const timer = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n const reset = useCallback((): void => {\n if (timer.current) {\n clearTimeout(timer.current);\n timer.current = null;\n }\n setCopied(false);\n }, []);\n\n useEffect(() => () => reset(), [reset]);\n\n const copy = useCallback(\n async (text: string): Promise<boolean> => {\n try {\n if (typeof navigator !== \"undefined\" && navigator.clipboard?.writeText) {\n await navigator.clipboard.writeText(text);\n } else if (typeof document !== \"undefined\") {\n const textarea = document.createElement(\"textarea\");\n textarea.value = text;\n textarea.style.position = \"fixed\";\n textarea.style.opacity = \"0\";\n document.body.appendChild(textarea);\n textarea.select();\n document.execCommand(\"copy\");\n document.body.removeChild(textarea);\n } else {\n return false;\n }\n setCopied(true);\n if (timer.current) clearTimeout(timer.current);\n timer.current = setTimeout(() => setCopied(false), resetAfter);\n return true;\n } catch {\n return false;\n }\n },\n [resetAfter],\n );\n\n return { copied, copy, reset };\n}\n"],"mappings":"uBAqBA,SAAgB,EAAa,EAA+B,CAAC,EAAuB,CAChF,GAAM,CAAE,aAAa,MAAS,EACxB,CAAC,EAAQ,IAAA,EAAA,EAAA,SAAA,CAA+B,EAAK,EAC7C,GAAA,EAAA,EAAA,OAAA,CAAqD,IAAI,EAEzD,GAAA,EAAA,EAAA,YAAA,KAAgC,CAClC,AAEI,EAAM,WADN,aAAa,EAAM,OAAO,EACV,MAEpB,EAAU,EAAK,CACnB,EAAG,CAAC,CAAC,EAgCL,OA9BA,EAAA,EAAA,UAAA,SAAsB,EAAM,EAAG,CAAC,CAAK,CAAC,EA8B/B,CAAE,SAAQ,MAAA,EAAA,EAAA,YAAA,CA3Bb,KAAO,IAAmC,CACtC,GAAI,CACA,GAAI,OAAO,UAAc,KAAe,UAAU,WAAW,UACzD,MAAM,UAAU,UAAU,UAAU,CAAI,OACrC,GAAI,OAAO,SAAa,IAAa,CACxC,IAAM,EAAW,SAAS,cAAc,UAAU,EAClD,EAAS,MAAQ,EACjB,EAAS,MAAM,SAAW,QAC1B,EAAS,MAAM,QAAU,IACzB,SAAS,KAAK,YAAY,CAAQ,EAClC,EAAS,OAAO,EAChB,SAAS,YAAY,MAAM,EAC3B,SAAS,KAAK,YAAY,CAAQ,CACtC,MACI,MAAO,GAKX,OAHA,EAAU,EAAI,EACV,EAAM,SAAS,aAAa,EAAM,OAAO,EAC7C,EAAM,QAAU,eAAiB,EAAU,EAAK,EAAG,CAAU,EACtD,EACX,MAAQ,CACJ,MAAO,EACX,CACJ,EACA,CAAC,CAAU,CAGE,EAAM,OAAM,CACjC"}
1
+ {"version":3,"file":"use-clipboard.cjs","names":[],"sources":["../../src/hooks/use-clipboard.ts"],"sourcesContent":["import { useCallback, useEffect, useRef, useState } from \"react\";\n\nexport interface UseClipboardOptions {\n /** Time (ms) before the `copied` flag resets back to false. Default: 1500. */\n resetAfter?: number;\n}\n\nexport interface UseClipboardResult {\n /** True briefly after a successful copy. */\n copied: boolean;\n /** Copy a string to the clipboard. Returns `true` on success. */\n copy: (text: string) => Promise<boolean>;\n /** Manually reset the `copied` flag. */\n reset: () => void;\n}\n\n/**\n * Wrapper around `navigator.clipboard.writeText` with a transient `copied`\n * flag for UI feedback (\"Copied!\" toast). Falls back gracefully when the\n * Clipboard API is unavailable.\n */\nexport function useClipboard(options: UseClipboardOptions = {}): UseClipboardResult {\n const { resetAfter = 1500 } = options;\n const [copied, setCopied] = useState<boolean>(false);\n const timer = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n const reset = useCallback((): void => {\n if (timer.current) {\n clearTimeout(timer.current);\n timer.current = null;\n }\n setCopied(false);\n }, []);\n\n useEffect(() => () => reset(), [reset]);\n\n const copy = useCallback(\n async (text: string): Promise<boolean> => {\n try {\n if (typeof navigator !== \"undefined\" && navigator.clipboard?.writeText) {\n await navigator.clipboard.writeText(text);\n } else if (typeof document !== \"undefined\") {\n const textarea = document.createElement(\"textarea\");\n textarea.value = text;\n textarea.style.position = \"fixed\";\n textarea.style.opacity = \"0\";\n document.body.appendChild(textarea);\n textarea.select();\n document.execCommand(\"copy\");\n document.body.removeChild(textarea);\n } else {\n return false;\n }\n setCopied(true);\n if (timer.current) clearTimeout(timer.current);\n timer.current = setTimeout(() => setCopied(false), resetAfter);\n return true;\n } catch {\n return false;\n }\n },\n [resetAfter],\n );\n\n return { copied, copy, reset };\n}\n"],"mappings":"uBAqBA,SAAgB,EAAa,EAA+B,CAAC,EAAuB,CAChF,GAAM,CAAE,aAAa,MAAS,EACxB,CAAC,EAAQ,IAAA,EAAa,EAAA,SAAA,CAAkB,EAAK,EAC7C,GAAA,EAAQ,EAAA,OAAA,CAA6C,IAAI,EAEzD,GAAA,EAAQ,EAAA,YAAA,KAAwB,CAClC,AAEI,EAAM,WADN,aAAa,EAAM,OAAO,EACV,MAEpB,EAAU,EAAK,CACnB,EAAG,CAAC,CAAC,EAgCL,OA9BA,EAAA,EAAA,UAAA,SAAsB,EAAM,EAAG,CAAC,CAAK,CAAC,EA8B/B,CAAE,SAAQ,MAAA,EA5BJ,EAAA,YAAA,CACT,KAAO,IAAmC,CACtC,GAAI,CACA,GAAI,OAAO,UAAc,KAAe,UAAU,WAAW,UACzD,MAAM,UAAU,UAAU,UAAU,CAAI,OACrC,GAAI,OAAO,SAAa,IAAa,CACxC,IAAM,EAAW,SAAS,cAAc,UAAU,EAClD,EAAS,MAAQ,EACjB,EAAS,MAAM,SAAW,QAC1B,EAAS,MAAM,QAAU,IACzB,SAAS,KAAK,YAAY,CAAQ,EAClC,EAAS,OAAO,EAChB,SAAS,YAAY,MAAM,EAC3B,SAAS,KAAK,YAAY,CAAQ,CACtC,MACI,MAAO,GAKX,OAHA,EAAU,EAAI,EACV,EAAM,SAAS,aAAa,EAAM,OAAO,EAC7C,EAAM,QAAU,eAAiB,EAAU,EAAK,EAAG,CAAU,EACtD,EACX,MAAQ,CACJ,MAAO,EACX,CACJ,EACA,CAAC,CAAU,CAGE,EAAM,OAAM,CACjC"}
@@ -1 +1 @@
1
- {"version":3,"file":"use-counter.cjs","names":[],"sources":["../../src/hooks/use-counter.ts"],"sourcesContent":["import { useCallback, useMemo, useState } from \"react\";\n\nexport interface UseCounterOptions {\n min?: number;\n max?: number;\n}\n\nexport interface CounterHandlers {\n increment: () => void;\n decrement: () => void;\n set: (value: number) => void;\n reset: () => void;\n}\n\nconst clamp = (value: number, min: number | undefined, max: number | undefined): number => {\n let next = value;\n if (typeof min === \"number\") next = Math.max(min, next);\n if (typeof max === \"number\") next = Math.min(max, next);\n return next;\n};\n\n/**\n * Numeric counter clamped to an optional `[min, max]` range.\n *\n * @param initial - initial value (default `0`), clamped to the range.\n * @param options - optional `min` / `max` bounds.\n * @returns Tuple `[count, { increment, decrement, set, reset }]`.\n */\nexport function useCounter(\n initial = 0,\n options: UseCounterOptions = {},\n): [number, CounterHandlers] {\n const { min, max } = options;\n const [count, setCount] = useState<number>(() => clamp(initial, min, max));\n\n const increment = useCallback((): void => {\n setCount((current) => clamp(current + 1, min, max));\n }, [min, max]);\n\n const decrement = useCallback((): void => {\n setCount((current) => clamp(current - 1, min, max));\n }, [min, max]);\n\n const set = useCallback(\n (value: number): void => {\n setCount(clamp(value, min, max));\n },\n [min, max],\n );\n\n const reset = useCallback((): void => {\n setCount(clamp(initial, min, max));\n }, [initial, min, max]);\n\n const handlers = useMemo<CounterHandlers>(\n () => ({ increment, decrement, set, reset }),\n [increment, decrement, set, reset],\n );\n\n return [count, handlers];\n}\n"],"mappings":"uBAcA,IAAM,GAAS,EAAe,EAAyB,IAAoC,CACvF,IAAI,EAAO,EAGX,OAFI,OAAO,GAAQ,WAAU,EAAO,KAAK,IAAI,EAAK,CAAI,GAClD,OAAO,GAAQ,WAAU,EAAO,KAAK,IAAI,EAAK,CAAI,GAC/C,CACX,EASA,SAAgB,EACZ,EAAU,EACV,EAA6B,CAAC,EACL,CACzB,GAAM,CAAE,MAAK,OAAQ,EACf,CAAC,EAAO,IAAA,EAAA,EAAA,SAAA,KAAmC,EAAM,EAAS,EAAK,CAAG,CAAC,EAEnE,GAAA,EAAA,EAAA,YAAA,KAAoC,CACtC,EAAU,GAAY,EAAM,EAAU,EAAG,EAAK,CAAG,CAAC,CACtD,EAAG,CAAC,EAAK,CAAG,CAAC,EAEP,GAAA,EAAA,EAAA,YAAA,KAAoC,CACtC,EAAU,GAAY,EAAM,EAAU,EAAG,EAAK,CAAG,CAAC,CACtD,EAAG,CAAC,EAAK,CAAG,CAAC,EAEP,GAAA,EAAA,EAAA,YAAA,CACD,GAAwB,CACrB,EAAS,EAAM,EAAO,EAAK,CAAG,CAAC,CACnC,EACA,CAAC,EAAK,CAAG,CACb,EAEM,GAAA,EAAA,EAAA,YAAA,KAAgC,CAClC,EAAS,EAAM,EAAS,EAAK,CAAG,CAAC,CACrC,EAAG,CAAC,EAAS,EAAK,CAAG,CAAC,EAOtB,MAAO,CAAC,GAAA,EAAA,EAAA,QAAA,MAJG,CAAE,YAAW,YAAW,MAAK,OAAM,GAC1C,CAAC,EAAW,EAAW,EAAK,CAAK,CAGtB,CAAQ,CAC3B"}
1
+ {"version":3,"file":"use-counter.cjs","names":[],"sources":["../../src/hooks/use-counter.ts"],"sourcesContent":["import { useCallback, useMemo, useState } from \"react\";\n\nexport interface UseCounterOptions {\n min?: number;\n max?: number;\n}\n\nexport interface CounterHandlers {\n increment: () => void;\n decrement: () => void;\n set: (value: number) => void;\n reset: () => void;\n}\n\nconst clamp = (value: number, min: number | undefined, max: number | undefined): number => {\n let next = value;\n if (typeof min === \"number\") next = Math.max(min, next);\n if (typeof max === \"number\") next = Math.min(max, next);\n return next;\n};\n\n/**\n * Numeric counter clamped to an optional `[min, max]` range.\n *\n * @param initial - initial value (default `0`), clamped to the range.\n * @param options - optional `min` / `max` bounds.\n * @returns Tuple `[count, { increment, decrement, set, reset }]`.\n */\nexport function useCounter(\n initial = 0,\n options: UseCounterOptions = {},\n): [number, CounterHandlers] {\n const { min, max } = options;\n const [count, setCount] = useState<number>(() => clamp(initial, min, max));\n\n const increment = useCallback((): void => {\n setCount((current) => clamp(current + 1, min, max));\n }, [min, max]);\n\n const decrement = useCallback((): void => {\n setCount((current) => clamp(current - 1, min, max));\n }, [min, max]);\n\n const set = useCallback(\n (value: number): void => {\n setCount(clamp(value, min, max));\n },\n [min, max],\n );\n\n const reset = useCallback((): void => {\n setCount(clamp(initial, min, max));\n }, [initial, min, max]);\n\n const handlers = useMemo<CounterHandlers>(\n () => ({ increment, decrement, set, reset }),\n [increment, decrement, set, reset],\n );\n\n return [count, handlers];\n}\n"],"mappings":"uBAcA,IAAM,GAAS,EAAe,EAAyB,IAAoC,CACvF,IAAI,EAAO,EAGX,OAFI,OAAO,GAAQ,WAAU,EAAO,KAAK,IAAI,EAAK,CAAI,GAClD,OAAO,GAAQ,WAAU,EAAO,KAAK,IAAI,EAAK,CAAI,GAC/C,CACX,EASA,SAAgB,EACZ,EAAU,EACV,EAA6B,CAAC,EACL,CACzB,GAAM,CAAE,MAAK,OAAQ,EACf,CAAC,EAAO,IAAA,EAAY,EAAA,SAAA,KAAuB,EAAM,EAAS,EAAK,CAAG,CAAC,EAEnE,GAAA,EAAY,EAAA,YAAA,KAAwB,CACtC,EAAU,GAAY,EAAM,EAAU,EAAG,EAAK,CAAG,CAAC,CACtD,EAAG,CAAC,EAAK,CAAG,CAAC,EAEP,GAAA,EAAY,EAAA,YAAA,KAAwB,CACtC,EAAU,GAAY,EAAM,EAAU,EAAG,EAAK,CAAG,CAAC,CACtD,EAAG,CAAC,EAAK,CAAG,CAAC,EAEP,GAAA,EAAM,EAAA,YAAA,CACP,GAAwB,CACrB,EAAS,EAAM,EAAO,EAAK,CAAG,CAAC,CACnC,EACA,CAAC,EAAK,CAAG,CACb,EAEM,GAAA,EAAQ,EAAA,YAAA,KAAwB,CAClC,EAAS,EAAM,EAAS,EAAK,CAAG,CAAC,CACrC,EAAG,CAAC,EAAS,EAAK,CAAG,CAAC,EAOtB,MAAO,CAAC,GAAA,EALS,EAAA,QAAA,MACN,CAAE,YAAW,YAAW,MAAK,OAAM,GAC1C,CAAC,EAAW,EAAW,EAAK,CAAK,CAGtB,CAAQ,CAC3B"}
@@ -1 +1 @@
1
- {"version":3,"file":"use-debounce.cjs","names":[],"sources":["../../src/hooks/use-debounce.ts"],"sourcesContent":["import { useEffect, useState } from \"react\";\n\n/**\n * Debounce a fast-changing value. Returns the latest value once `delay` ms\n * have elapsed without further changes.\n *\n * @param value - The value to debounce.\n * @param delay - Delay in milliseconds (default 300).\n * @returns The debounced value.\n */\nexport function useDebounce<T>(value: T, delay = 300): T {\n const [debouncedValue, setDebouncedValue] = useState<T>(value);\n\n useEffect(() => {\n const timer = setTimeout(() => setDebouncedValue(value), delay);\n return () => clearTimeout(timer);\n }, [value, delay]);\n\n return debouncedValue;\n}\n"],"mappings":"uBAUA,SAAgB,EAAe,EAAU,EAAQ,IAAQ,CACrD,GAAM,CAAC,EAAgB,IAAA,EAAA,EAAA,SAAA,CAAiC,CAAK,EAO7D,OALA,EAAA,EAAA,UAAA,KAAgB,CACZ,IAAM,EAAQ,eAAiB,EAAkB,CAAK,EAAG,CAAK,EAC9D,UAAa,aAAa,CAAK,CACnC,EAAG,CAAC,EAAO,CAAK,CAAC,EAEV,CACX"}
1
+ {"version":3,"file":"use-debounce.cjs","names":[],"sources":["../../src/hooks/use-debounce.ts"],"sourcesContent":["import { useEffect, useState } from \"react\";\n\n/**\n * Debounce a fast-changing value. Returns the latest value once `delay` ms\n * have elapsed without further changes.\n *\n * @param value - The value to debounce.\n * @param delay - Delay in milliseconds (default 300).\n * @returns The debounced value.\n */\nexport function useDebounce<T>(value: T, delay = 300): T {\n const [debouncedValue, setDebouncedValue] = useState<T>(value);\n\n useEffect(() => {\n const timer = setTimeout(() => setDebouncedValue(value), delay);\n return () => clearTimeout(timer);\n }, [value, delay]);\n\n return debouncedValue;\n}\n"],"mappings":"uBAUA,SAAgB,EAAe,EAAU,EAAQ,IAAQ,CACrD,GAAM,CAAC,EAAgB,IAAA,EAAqB,EAAA,SAAA,CAAY,CAAK,EAO7D,OALA,EAAA,EAAA,UAAA,KAAgB,CACZ,IAAM,EAAQ,eAAiB,EAAkB,CAAK,EAAG,CAAK,EAC9D,UAAa,aAAa,CAAK,CACnC,EAAG,CAAC,EAAO,CAAK,CAAC,EAEV,CACX"}
@@ -1 +1 @@
1
- {"version":3,"file":"use-deep-memo.cjs","names":[],"sources":["../../src/hooks/use-deep-memo.ts"],"sourcesContent":["/* eslint-disable react-hooks/refs -- the structural cache is read and written during render by design (see the docstring) */\nimport { useRef } from \"react\";\n\nfunction deepEqual(a: unknown, b: unknown): boolean {\n if (Object.is(a, b)) return true;\n if (typeof a !== \"object\" || typeof b !== \"object\" || a === null || b === null) return false;\n if (Array.isArray(a) !== Array.isArray(b)) return false;\n if (Array.isArray(a) && Array.isArray(b)) {\n if (a.length !== b.length) return false;\n return a.every((value, index) => deepEqual(value, b[index]));\n }\n const aKeys = Object.keys(a as object);\n const bKeys = Object.keys(b as object);\n if (aKeys.length !== bKeys.length) return false;\n return aKeys.every((key) =>\n deepEqual((a as Record<string, unknown>)[key], (b as Record<string, unknown>)[key]),\n );\n}\n\n/**\n * Memoize a value with a structural equality check. Use when an object/array\n * created during render is fed to `useEffect` dependencies and you want to\n * avoid spurious effect runs when only the reference changes.\n *\n * The cache is a ref read and written during render. That is flagged by the React\n * Compiler rules and is nonetheless correct here: the write is idempotent — a\n * discarded render either wrote a structurally equal value or nothing at all — so\n * replaying the render cannot change the result.\n */\nexport function useDeepMemo<T>(value: T): T {\n const ref = useRef<T>(value);\n if (!deepEqual(ref.current, value)) {\n ref.current = value;\n }\n return ref.current;\n}\n"],"mappings":"uBAGA,SAAS,EAAU,EAAY,EAAqB,CAChD,GAAI,OAAO,GAAG,EAAG,CAAC,EAAG,MAAO,GAE5B,GADI,OAAO,GAAM,UAAY,OAAO,GAAM,UAAY,IAAM,MAAQ,IAAM,MACtE,MAAM,QAAQ,CAAC,IAAM,MAAM,QAAQ,CAAC,EAAG,MAAO,GAClD,GAAI,MAAM,QAAQ,CAAC,GAAK,MAAM,QAAQ,CAAC,EAEnC,OADI,EAAE,SAAW,EAAE,QACZ,EAAE,OAAO,EAAO,IAAU,EAAU,EAAO,EAAE,EAAM,CAAC,EAE/D,IAAM,EAAQ,OAAO,KAAK,CAAW,EAC/B,EAAQ,OAAO,KAAK,CAAW,EAErC,OADI,EAAM,SAAW,EAAM,QACpB,EAAM,MAAO,GAChB,EAAW,EAA8B,GAAO,EAA8B,EAAI,CACtF,CACJ,CAYA,SAAgB,EAAe,EAAa,CACxC,IAAM,GAAA,EAAA,EAAA,OAAA,CAAgB,CAAK,EAI3B,OAHK,EAAU,EAAI,QAAS,CAAK,IAC7B,EAAI,QAAU,GAEX,EAAI,OACf"}
1
+ {"version":3,"file":"use-deep-memo.cjs","names":[],"sources":["../../src/hooks/use-deep-memo.ts"],"sourcesContent":["/* eslint-disable react-hooks/refs -- the structural cache is read and written during render by design (see the docstring) */\nimport { useRef } from \"react\";\n\nfunction deepEqual(a: unknown, b: unknown): boolean {\n if (Object.is(a, b)) return true;\n if (typeof a !== \"object\" || typeof b !== \"object\" || a === null || b === null) return false;\n if (Array.isArray(a) !== Array.isArray(b)) return false;\n if (Array.isArray(a) && Array.isArray(b)) {\n if (a.length !== b.length) return false;\n return a.every((value, index) => deepEqual(value, b[index]));\n }\n const aKeys = Object.keys(a as object);\n const bKeys = Object.keys(b as object);\n if (aKeys.length !== bKeys.length) return false;\n return aKeys.every((key) =>\n deepEqual((a as Record<string, unknown>)[key], (b as Record<string, unknown>)[key]),\n );\n}\n\n/**\n * Memoize a value with a structural equality check. Use when an object/array\n * created during render is fed to `useEffect` dependencies and you want to\n * avoid spurious effect runs when only the reference changes.\n *\n * The cache is a ref read and written during render. That is flagged by the React\n * Compiler rules and is nonetheless correct here: the write is idempotent — a\n * discarded render either wrote a structurally equal value or nothing at all — so\n * replaying the render cannot change the result.\n */\nexport function useDeepMemo<T>(value: T): T {\n const ref = useRef<T>(value);\n if (!deepEqual(ref.current, value)) {\n ref.current = value;\n }\n return ref.current;\n}\n"],"mappings":"uBAGA,SAAS,EAAU,EAAY,EAAqB,CAChD,GAAI,OAAO,GAAG,EAAG,CAAC,EAAG,MAAO,GAE5B,GADI,OAAO,GAAM,UAAY,OAAO,GAAM,UAAY,IAAM,MAAQ,IAAM,MACtE,MAAM,QAAQ,CAAC,IAAM,MAAM,QAAQ,CAAC,EAAG,MAAO,GAClD,GAAI,MAAM,QAAQ,CAAC,GAAK,MAAM,QAAQ,CAAC,EAEnC,OADI,EAAE,SAAW,EAAE,QACZ,EAAE,OAAO,EAAO,IAAU,EAAU,EAAO,EAAE,EAAM,CAAC,EAE/D,IAAM,EAAQ,OAAO,KAAK,CAAW,EAC/B,EAAQ,OAAO,KAAK,CAAW,EAErC,OADI,EAAM,SAAW,EAAM,QACpB,EAAM,MAAO,GAChB,EAAW,EAA8B,GAAO,EAA8B,EAAI,CACtF,CACJ,CAYA,SAAgB,EAAe,EAAa,CACxC,IAAM,GAAA,EAAM,EAAA,OAAA,CAAU,CAAK,EAI3B,OAHK,EAAU,EAAI,QAAS,CAAK,IAC7B,EAAI,QAAU,GAEX,EAAI,OACf"}
@@ -1 +1 @@
1
- {"version":3,"file":"use-disclosure.cjs","names":[],"sources":["../../src/hooks/use-disclosure.ts"],"sourcesContent":["import { useCallback, useMemo, useState } from \"react\";\n\nexport interface DisclosureHandlers {\n open: () => void;\n close: () => void;\n toggle: () => void;\n}\n\n/**\n * Manage open/closed boolean state with stable `open`/`close`/`toggle` handlers.\n *\n * Richer than {@link useToggle} for UI elements like modals, drawers and\n * popovers: the handlers are referentially stable across renders.\n *\n * @param initial - initial opened state (default `false`).\n * @returns Tuple `[opened, { open, close, toggle }]`.\n */\nexport function useDisclosure(initial = false): [boolean, DisclosureHandlers] {\n const [opened, setOpened] = useState<boolean>(initial);\n\n const open = useCallback((): void => setOpened(true), []);\n const close = useCallback((): void => setOpened(false), []);\n const toggle = useCallback((): void => setOpened((current) => !current), []);\n\n const handlers = useMemo<DisclosureHandlers>(\n () => ({ open, close, toggle }),\n [open, close, toggle],\n );\n\n return [opened, handlers];\n}\n"],"mappings":"uBAiBA,SAAgB,EAAc,EAAU,GAAsC,CAC1E,GAAM,CAAC,EAAQ,IAAA,EAAA,EAAA,SAAA,CAA+B,CAAO,EAE/C,GAAA,EAAA,EAAA,YAAA,KAA+B,EAAU,EAAI,EAAG,CAAC,CAAC,EAClD,GAAA,EAAA,EAAA,YAAA,KAAgC,EAAU,EAAK,EAAG,CAAC,CAAC,EACpD,GAAA,EAAA,EAAA,YAAA,KAAiC,EAAW,GAAY,CAAC,CAAO,EAAG,CAAC,CAAC,EAO3E,MAAO,CAAC,GAAA,EAAA,EAAA,QAAA,MAJG,CAAE,OAAM,QAAO,QAAO,GAC7B,CAAC,EAAM,EAAO,CAAM,CAGR,CAAQ,CAC5B"}
1
+ {"version":3,"file":"use-disclosure.cjs","names":[],"sources":["../../src/hooks/use-disclosure.ts"],"sourcesContent":["import { useCallback, useMemo, useState } from \"react\";\n\nexport interface DisclosureHandlers {\n open: () => void;\n close: () => void;\n toggle: () => void;\n}\n\n/**\n * Manage open/closed boolean state with stable `open`/`close`/`toggle` handlers.\n *\n * Richer than {@link useToggle} for UI elements like modals, drawers and\n * popovers: the handlers are referentially stable across renders.\n *\n * @param initial - initial opened state (default `false`).\n * @returns Tuple `[opened, { open, close, toggle }]`.\n */\nexport function useDisclosure(initial = false): [boolean, DisclosureHandlers] {\n const [opened, setOpened] = useState<boolean>(initial);\n\n const open = useCallback((): void => setOpened(true), []);\n const close = useCallback((): void => setOpened(false), []);\n const toggle = useCallback((): void => setOpened((current) => !current), []);\n\n const handlers = useMemo<DisclosureHandlers>(\n () => ({ open, close, toggle }),\n [open, close, toggle],\n );\n\n return [opened, handlers];\n}\n"],"mappings":"uBAiBA,SAAgB,EAAc,EAAU,GAAsC,CAC1E,GAAM,CAAC,EAAQ,IAAA,EAAa,EAAA,SAAA,CAAkB,CAAO,EAE/C,GAAA,EAAO,EAAA,YAAA,KAAwB,EAAU,EAAI,EAAG,CAAC,CAAC,EAClD,GAAA,EAAQ,EAAA,YAAA,KAAwB,EAAU,EAAK,EAAG,CAAC,CAAC,EACpD,GAAA,EAAS,EAAA,YAAA,KAAwB,EAAW,GAAY,CAAC,CAAO,EAAG,CAAC,CAAC,EAO3E,MAAO,CAAC,GAAA,EALS,EAAA,QAAA,MACN,CAAE,OAAM,QAAO,QAAO,GAC7B,CAAC,EAAM,EAAO,CAAM,CAGR,CAAQ,CAC5B"}
@@ -1 +1 @@
1
- {"version":3,"file":"use-document-visibility.cjs","names":[],"sources":["../../src/hooks/use-document-visibility.ts"],"sourcesContent":["import { useEffect, useState } from \"react\";\n\nexport type DocumentVisibility = \"visible\" | \"hidden\";\n\n/** Subscribe to `document.visibilityState`. Returns `\"visible\"` during SSR. */\nexport function useDocumentVisibility(): DocumentVisibility {\n const [state, setState] = useState<DocumentVisibility>(() =>\n typeof document === \"undefined\"\n ? \"visible\"\n : (document.visibilityState as DocumentVisibility),\n );\n\n useEffect(() => {\n if (typeof document === \"undefined\") return;\n const handler = (): void => setState(document.visibilityState as DocumentVisibility);\n document.addEventListener(\"visibilitychange\", handler);\n return () => document.removeEventListener(\"visibilitychange\", handler);\n }, []);\n\n return state;\n}\n"],"mappings":"uBAKA,SAAgB,GAA4C,CACxD,GAAM,CAAC,EAAO,IAAA,EAAA,EAAA,SAAA,KACV,OAAO,SAAa,IACd,UACC,SAAS,eACpB,EASA,OAPA,EAAA,EAAA,UAAA,KAAgB,CACZ,GAAI,OAAO,SAAa,IAAa,OACrC,IAAM,MAAsB,EAAS,SAAS,eAAqC,EAEnF,OADA,SAAS,iBAAiB,mBAAoB,CAAO,MACxC,SAAS,oBAAoB,mBAAoB,CAAO,CACzE,EAAG,CAAC,CAAC,EAEE,CACX"}
1
+ {"version":3,"file":"use-document-visibility.cjs","names":[],"sources":["../../src/hooks/use-document-visibility.ts"],"sourcesContent":["import { useEffect, useState } from \"react\";\n\nexport type DocumentVisibility = \"visible\" | \"hidden\";\n\n/** Subscribe to `document.visibilityState`. Returns `\"visible\"` during SSR. */\nexport function useDocumentVisibility(): DocumentVisibility {\n const [state, setState] = useState<DocumentVisibility>(() =>\n typeof document === \"undefined\"\n ? \"visible\"\n : (document.visibilityState as DocumentVisibility),\n );\n\n useEffect(() => {\n if (typeof document === \"undefined\") return;\n const handler = (): void => setState(document.visibilityState as DocumentVisibility);\n document.addEventListener(\"visibilitychange\", handler);\n return () => document.removeEventListener(\"visibilitychange\", handler);\n }, []);\n\n return state;\n}\n"],"mappings":"uBAKA,SAAgB,GAA4C,CACxD,GAAM,CAAC,EAAO,IAAA,EAAY,EAAA,SAAA,KACtB,OAAO,SAAa,IACd,UACC,SAAS,eACpB,EASA,OAPA,EAAA,EAAA,UAAA,KAAgB,CACZ,GAAI,OAAO,SAAa,IAAa,OACrC,IAAM,MAAsB,EAAS,SAAS,eAAqC,EAEnF,OADA,SAAS,iBAAiB,mBAAoB,CAAO,MACxC,SAAS,oBAAoB,mBAAoB,CAAO,CACzE,EAAG,CAAC,CAAC,EAEE,CACX"}
@@ -1 +1 @@
1
- {"version":3,"file":"use-event-listener.cjs","names":[],"sources":["../../src/hooks/use-event-listener.ts"],"sourcesContent":["import { useEffect, useRef } from \"react\";\n\ntype AnyEventTarget = EventTarget | { current: EventTarget | null } | null | undefined;\n\n/**\n * Subscribe to a DOM event with React-friendly semantics.\n *\n * - Handler is stored in a ref so callers can pass inline functions without\n * resubscribing on every render.\n * - `target` defaults to `window`. Accepts a raw `EventTarget` (window, document,\n * element) OR a ref pointing at one.\n * - Returns nothing — cleanup is automatic on unmount or when `eventName`/`target` change.\n *\n * @tempest-limits param-count — deliberately the same positional shape as\n * `addEventListener(type, listener, options)`, with the target inserted where React\n * needs it. A hook that mirrors a DOM API is read by everyone who already knows the\n * DOM API; a bespoke options bag would make the familiar call unfamiliar.\n */\nexport function useEventListener<K extends keyof WindowEventMap>(\n eventName: K,\n handler: (event: WindowEventMap[K]) => void,\n target?: AnyEventTarget,\n options?: AddEventListenerOptions | boolean,\n): void;\nexport function useEventListener<K extends keyof DocumentEventMap>(\n eventName: K,\n handler: (event: DocumentEventMap[K]) => void,\n target?: AnyEventTarget,\n options?: AddEventListenerOptions | boolean,\n): void;\nexport function useEventListener<K extends keyof HTMLElementEventMap>(\n eventName: K,\n handler: (event: HTMLElementEventMap[K]) => void,\n target?: AnyEventTarget,\n options?: AddEventListenerOptions | boolean,\n): void;\nexport function useEventListener(\n eventName: string,\n handler: (event: Event) => void,\n target?: AnyEventTarget,\n options?: AddEventListenerOptions | boolean,\n): void {\n const handlerRef = useRef(handler);\n useEffect(() => {\n handlerRef.current = handler;\n }, [handler]);\n\n useEffect(() => {\n const resolvedTarget: EventTarget | null =\n target === undefined\n ? typeof window === \"undefined\"\n ? null\n : window\n : \"current\" in (target as { current: unknown })\n ? ((target as { current: EventTarget | null }).current ?? null)\n : (target as EventTarget | null);\n\n if (!resolvedTarget?.addEventListener) return;\n\n const listener: EventListener = (event) => handlerRef.current(event);\n resolvedTarget.addEventListener(eventName, listener, options);\n return () => resolvedTarget.removeEventListener(eventName, listener, options);\n }, [eventName, target, options]);\n}\n"],"mappings":"uBAoCA,SAAgB,EACZ,EACA,EACA,EACA,EACI,CACJ,IAAM,GAAA,EAAA,EAAA,OAAA,CAAoB,CAAO,GACjC,EAAA,EAAA,UAAA,KAAgB,CACZ,EAAW,QAAU,CACzB,EAAG,CAAC,CAAO,CAAC,GAEZ,EAAA,EAAA,UAAA,KAAgB,CACZ,IAAM,EACF,IAAW,IAAA,GACL,OAAO,OAAW,IACd,KACA,OACJ,YAAc,EACV,EAA2C,SAAW,KACvD,EAEb,GAAI,CAAC,GAAgB,iBAAkB,OAEvC,IAAM,EAA2B,GAAU,EAAW,QAAQ,CAAK,EAEnE,OADA,EAAe,iBAAiB,EAAW,EAAU,CAAO,MAC/C,EAAe,oBAAoB,EAAW,EAAU,CAAO,CAChF,EAAG,CAAC,EAAW,EAAQ,CAAO,CAAC,CACnC"}
1
+ {"version":3,"file":"use-event-listener.cjs","names":[],"sources":["../../src/hooks/use-event-listener.ts"],"sourcesContent":["import { useEffect, useRef } from \"react\";\n\ntype AnyEventTarget = EventTarget | { current: EventTarget | null } | null | undefined;\n\n/**\n * Subscribe to a DOM event with React-friendly semantics.\n *\n * - Handler is stored in a ref so callers can pass inline functions without\n * resubscribing on every render.\n * - `target` defaults to `window`. Accepts a raw `EventTarget` (window, document,\n * element) OR a ref pointing at one.\n * - Returns nothing — cleanup is automatic on unmount or when `eventName`/`target` change.\n *\n * @tempest-limits param-count — deliberately the same positional shape as\n * `addEventListener(type, listener, options)`, with the target inserted where React\n * needs it. A hook that mirrors a DOM API is read by everyone who already knows the\n * DOM API; a bespoke options bag would make the familiar call unfamiliar.\n */\nexport function useEventListener<K extends keyof WindowEventMap>(\n eventName: K,\n handler: (event: WindowEventMap[K]) => void,\n target?: AnyEventTarget,\n options?: AddEventListenerOptions | boolean,\n): void;\nexport function useEventListener<K extends keyof DocumentEventMap>(\n eventName: K,\n handler: (event: DocumentEventMap[K]) => void,\n target?: AnyEventTarget,\n options?: AddEventListenerOptions | boolean,\n): void;\nexport function useEventListener<K extends keyof HTMLElementEventMap>(\n eventName: K,\n handler: (event: HTMLElementEventMap[K]) => void,\n target?: AnyEventTarget,\n options?: AddEventListenerOptions | boolean,\n): void;\nexport function useEventListener(\n eventName: string,\n handler: (event: Event) => void,\n target?: AnyEventTarget,\n options?: AddEventListenerOptions | boolean,\n): void {\n const handlerRef = useRef(handler);\n useEffect(() => {\n handlerRef.current = handler;\n }, [handler]);\n\n useEffect(() => {\n const resolvedTarget: EventTarget | null =\n target === undefined\n ? typeof window === \"undefined\"\n ? null\n : window\n : \"current\" in (target as { current: unknown })\n ? ((target as { current: EventTarget | null }).current ?? null)\n : (target as EventTarget | null);\n\n if (!resolvedTarget?.addEventListener) return;\n\n const listener: EventListener = (event) => handlerRef.current(event);\n resolvedTarget.addEventListener(eventName, listener, options);\n return () => resolvedTarget.removeEventListener(eventName, listener, options);\n }, [eventName, target, options]);\n}\n"],"mappings":"uBAoCA,SAAgB,EACZ,EACA,EACA,EACA,EACI,CACJ,IAAM,GAAA,EAAa,EAAA,OAAA,CAAO,CAAO,GACjC,EAAA,EAAA,UAAA,KAAgB,CACZ,EAAW,QAAU,CACzB,EAAG,CAAC,CAAO,CAAC,GAEZ,EAAA,EAAA,UAAA,KAAgB,CACZ,IAAM,EACF,IAAW,IAAA,GACL,OAAO,OAAW,IACd,KACA,OACJ,YAAc,EACV,EAA2C,SAAW,KACvD,EAEb,GAAI,CAAC,GAAgB,iBAAkB,OAEvC,IAAM,EAA2B,GAAU,EAAW,QAAQ,CAAK,EAEnE,OADA,EAAe,iBAAiB,EAAW,EAAU,CAAO,MAC/C,EAAe,oBAAoB,EAAW,EAAU,CAAO,CAChF,EAAG,CAAC,EAAW,EAAQ,CAAO,CAAC,CACnC"}
@@ -1 +1 @@
1
- {"version":3,"file":"use-geolocation.cjs","names":[],"sources":["../../src/hooks/use-geolocation.ts"],"sourcesContent":["import { useEffect, useState } from \"react\";\n\nexport interface GeolocationState {\n loading: boolean;\n error: GeolocationPositionError | null;\n coords: GeolocationCoordinates | null;\n timestamp: number | null;\n}\n\nexport interface UseGeolocationOptions extends PositionOptions {\n /** Use `watchPosition` instead of one-shot `getCurrentPosition`. Default: false. */\n watch?: boolean;\n /** Disable the hook without unmounting. Default: false. */\n disabled?: boolean;\n}\n\n/**\n * React hook around the Geolocation API.\n *\n * The effect depends on the individual `PositionOptions` fields instead of the\n * rest object, which is a new reference on every render and would restart the\n * watch continuously. `exhaustive-deps` is silenced there because those fields\n * are the whole of what the effect reads from it.\n *\n * @param options - Geolocation options plus `watch` and `disabled` switches.\n * @returns The latest position, error and loading state.\n */\nexport function useGeolocation(options: UseGeolocationOptions = {}): GeolocationState {\n const { watch = false, disabled = false, ...positionOptions } = options;\n const [state, setState] = useState<GeolocationState>({\n loading: !disabled,\n error: null,\n coords: null,\n timestamp: null,\n });\n\n useEffect(() => {\n if (disabled || typeof navigator === \"undefined\" || !navigator.geolocation) {\n setState((prev) => ({ ...prev, loading: false }));\n return;\n }\n\n const onSuccess = (position: GeolocationPosition): void => {\n setState({\n loading: false,\n error: null,\n coords: position.coords,\n timestamp: position.timestamp,\n });\n };\n const onError = (error: GeolocationPositionError): void => {\n setState((prev) => ({ ...prev, loading: false, error }));\n };\n\n if (watch) {\n const watchId = navigator.geolocation.watchPosition(\n onSuccess,\n onError,\n positionOptions,\n );\n return () => navigator.geolocation.clearWatch(watchId);\n }\n\n navigator.geolocation.getCurrentPosition(onSuccess, onError, positionOptions);\n return undefined;\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [\n disabled,\n watch,\n positionOptions.enableHighAccuracy,\n positionOptions.maximumAge,\n positionOptions.timeout,\n ]);\n\n return state;\n}\n"],"mappings":"uBA2BA,SAAgB,EAAe,EAAiC,CAAC,EAAqB,CAClF,GAAM,CAAE,QAAQ,GAAO,WAAW,GAAO,GAAG,GAAoB,EAC1D,CAAC,EAAO,IAAA,EAAA,EAAA,SAAA,CAAuC,CACjD,QAAS,CAAC,EACV,MAAO,KACP,OAAQ,KACR,UAAW,IACf,CAAC,EAwCD,OAtCA,EAAA,EAAA,UAAA,KAAgB,CACZ,GAAI,GAAY,OAAO,UAAc,KAAe,CAAC,UAAU,YAAa,CACxE,EAAU,IAAU,CAAE,GAAG,EAAM,QAAS,EAAM,EAAE,EAChD,MACJ,CAEA,IAAM,EAAa,GAAwC,CACvD,EAAS,CACL,QAAS,GACT,MAAO,KACP,OAAQ,EAAS,OACjB,UAAW,EAAS,SACxB,CAAC,CACL,EACM,EAAW,GAA0C,CACvD,EAAU,IAAU,CAAE,GAAG,EAAM,QAAS,GAAO,OAAM,EAAE,CAC3D,EAEA,GAAI,EAAO,CACP,IAAM,EAAU,UAAU,YAAY,cAClC,EACA,EACA,CACJ,EACA,UAAa,UAAU,YAAY,WAAW,CAAO,CACzD,CAEA,UAAU,YAAY,mBAAmB,EAAW,EAAS,CAAe,CAGhF,EAAG,CACC,EACA,EACA,EAAgB,mBAChB,EAAgB,WAChB,EAAgB,OACpB,CAAC,EAEM,CACX"}
1
+ {"version":3,"file":"use-geolocation.cjs","names":[],"sources":["../../src/hooks/use-geolocation.ts"],"sourcesContent":["import { useEffect, useState } from \"react\";\n\nexport interface GeolocationState {\n loading: boolean;\n error: GeolocationPositionError | null;\n coords: GeolocationCoordinates | null;\n timestamp: number | null;\n}\n\nexport interface UseGeolocationOptions extends PositionOptions {\n /** Use `watchPosition` instead of one-shot `getCurrentPosition`. Default: false. */\n watch?: boolean;\n /** Disable the hook without unmounting. Default: false. */\n disabled?: boolean;\n}\n\n/**\n * React hook around the Geolocation API.\n *\n * The effect depends on the individual `PositionOptions` fields instead of the\n * rest object, which is a new reference on every render and would restart the\n * watch continuously. `exhaustive-deps` is silenced there because those fields\n * are the whole of what the effect reads from it.\n *\n * @param options - Geolocation options plus `watch` and `disabled` switches.\n * @returns The latest position, error and loading state.\n */\nexport function useGeolocation(options: UseGeolocationOptions = {}): GeolocationState {\n const { watch = false, disabled = false, ...positionOptions } = options;\n const [state, setState] = useState<GeolocationState>({\n loading: !disabled,\n error: null,\n coords: null,\n timestamp: null,\n });\n\n useEffect(() => {\n if (disabled || typeof navigator === \"undefined\" || !navigator.geolocation) {\n setState((prev) => ({ ...prev, loading: false }));\n return;\n }\n\n const onSuccess = (position: GeolocationPosition): void => {\n setState({\n loading: false,\n error: null,\n coords: position.coords,\n timestamp: position.timestamp,\n });\n };\n const onError = (error: GeolocationPositionError): void => {\n setState((prev) => ({ ...prev, loading: false, error }));\n };\n\n if (watch) {\n const watchId = navigator.geolocation.watchPosition(\n onSuccess,\n onError,\n positionOptions,\n );\n return () => navigator.geolocation.clearWatch(watchId);\n }\n\n navigator.geolocation.getCurrentPosition(onSuccess, onError, positionOptions);\n return undefined;\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [\n disabled,\n watch,\n positionOptions.enableHighAccuracy,\n positionOptions.maximumAge,\n positionOptions.timeout,\n ]);\n\n return state;\n}\n"],"mappings":"uBA2BA,SAAgB,EAAe,EAAiC,CAAC,EAAqB,CAClF,GAAM,CAAE,QAAQ,GAAO,WAAW,GAAO,GAAG,GAAoB,EAC1D,CAAC,EAAO,IAAA,EAAY,EAAA,SAAA,CAA2B,CACjD,QAAS,CAAC,EACV,MAAO,KACP,OAAQ,KACR,UAAW,IACf,CAAC,EAwCD,OAtCA,EAAA,EAAA,UAAA,KAAgB,CACZ,GAAI,GAAY,OAAO,UAAc,KAAe,CAAC,UAAU,YAAa,CACxE,EAAU,IAAU,CAAE,GAAG,EAAM,QAAS,EAAM,EAAE,EAChD,MACJ,CAEA,IAAM,EAAa,GAAwC,CACvD,EAAS,CACL,QAAS,GACT,MAAO,KACP,OAAQ,EAAS,OACjB,UAAW,EAAS,SACxB,CAAC,CACL,EACM,EAAW,GAA0C,CACvD,EAAU,IAAU,CAAE,GAAG,EAAM,QAAS,GAAO,OAAM,EAAE,CAC3D,EAEA,GAAI,EAAO,CACP,IAAM,EAAU,UAAU,YAAY,cAClC,EACA,EACA,CACJ,EACA,UAAa,UAAU,YAAY,WAAW,CAAO,CACzD,CAEA,UAAU,YAAY,mBAAmB,EAAW,EAAS,CAAe,CAGhF,EAAG,CACC,EACA,EACA,EAAgB,mBAChB,EAAgB,WAChB,EAAgB,OACpB,CAAC,EAEM,CACX"}
@@ -1 +1 @@
1
- {"version":3,"file":"use-hover.cjs","names":[],"sources":["../../src/hooks/use-hover.ts"],"sourcesContent":["import { useEffect, useState } from \"react\";\nimport type { RefObject } from \"react\";\n\n/**\n * Track whether the pointer is hovering over the referenced element.\n * Returns `false` on touch-only devices (no pointer hover).\n *\n * @example\n * const ref = useRef<HTMLDivElement>(null);\n * const hovered = useHover(ref);\n * return <div ref={ref}>{hovered ? \"✨\" : \"\"}</div>;\n */\nexport function useHover<T extends HTMLElement>(ref: RefObject<T | null>): boolean {\n const [hovered, setHovered] = useState<boolean>(false);\n\n useEffect(() => {\n const node = ref.current;\n if (!node) return;\n const onEnter = (): void => setHovered(true);\n const onLeave = (): void => setHovered(false);\n node.addEventListener(\"mouseenter\", onEnter);\n node.addEventListener(\"mouseleave\", onLeave);\n return () => {\n node.removeEventListener(\"mouseenter\", onEnter);\n node.removeEventListener(\"mouseleave\", onLeave);\n };\n }, [ref]);\n\n return hovered;\n}\n"],"mappings":"uBAYA,SAAgB,EAAgC,EAAmC,CAC/E,GAAM,CAAC,EAAS,IAAA,EAAA,EAAA,SAAA,CAAgC,EAAK,EAerD,OAbA,EAAA,EAAA,UAAA,KAAgB,CACZ,IAAM,EAAO,EAAI,QACjB,GAAI,CAAC,EAAM,OACX,IAAM,MAAsB,EAAW,EAAI,EACrC,MAAsB,EAAW,EAAK,EAG5C,OAFA,EAAK,iBAAiB,aAAc,CAAO,EAC3C,EAAK,iBAAiB,aAAc,CAAO,MAC9B,CACT,EAAK,oBAAoB,aAAc,CAAO,EAC9C,EAAK,oBAAoB,aAAc,CAAO,CAClD,CACJ,EAAG,CAAC,CAAG,CAAC,EAED,CACX"}
1
+ {"version":3,"file":"use-hover.cjs","names":[],"sources":["../../src/hooks/use-hover.ts"],"sourcesContent":["import { useEffect, useState } from \"react\";\nimport type { RefObject } from \"react\";\n\n/**\n * Track whether the pointer is hovering over the referenced element.\n * Returns `false` on touch-only devices (no pointer hover).\n *\n * @example\n * const ref = useRef<HTMLDivElement>(null);\n * const hovered = useHover(ref);\n * return <div ref={ref}>{hovered ? \"✨\" : \"\"}</div>;\n */\nexport function useHover<T extends HTMLElement>(ref: RefObject<T | null>): boolean {\n const [hovered, setHovered] = useState<boolean>(false);\n\n useEffect(() => {\n const node = ref.current;\n if (!node) return;\n const onEnter = (): void => setHovered(true);\n const onLeave = (): void => setHovered(false);\n node.addEventListener(\"mouseenter\", onEnter);\n node.addEventListener(\"mouseleave\", onLeave);\n return () => {\n node.removeEventListener(\"mouseenter\", onEnter);\n node.removeEventListener(\"mouseleave\", onLeave);\n };\n }, [ref]);\n\n return hovered;\n}\n"],"mappings":"uBAYA,SAAgB,EAAgC,EAAmC,CAC/E,GAAM,CAAC,EAAS,IAAA,EAAc,EAAA,SAAA,CAAkB,EAAK,EAerD,OAbA,EAAA,EAAA,UAAA,KAAgB,CACZ,IAAM,EAAO,EAAI,QACjB,GAAI,CAAC,EAAM,OACX,IAAM,MAAsB,EAAW,EAAI,EACrC,MAAsB,EAAW,EAAK,EAG5C,OAFA,EAAK,iBAAiB,aAAc,CAAO,EAC3C,EAAK,iBAAiB,aAAc,CAAO,MAC9B,CACT,EAAK,oBAAoB,aAAc,CAAO,EAC9C,EAAK,oBAAoB,aAAc,CAAO,CAClD,CACJ,EAAG,CAAC,CAAG,CAAC,EAED,CACX"}
@@ -1 +1 @@
1
- {"version":3,"file":"use-idle.cjs","names":[],"sources":["../../src/hooks/use-idle.ts"],"sourcesContent":["import { useEffect, useState } from \"react\";\n\nconst DEFAULT_EVENTS: (keyof WindowEventMap)[] = [\n \"mousemove\",\n \"mousedown\",\n \"keydown\",\n \"touchstart\",\n \"wheel\",\n \"scroll\",\n];\n\n/**\n * Detect when the user has been idle for `timeout` ms (no interaction).\n * Returns `true` once the threshold elapses and back to `false` on activity.\n */\nexport function useIdle(timeout = 60_000): boolean {\n const [idle, setIdle] = useState<boolean>(false);\n\n useEffect(() => {\n if (typeof window === \"undefined\") return;\n let timer = setTimeout(() => setIdle(true), timeout);\n\n function reset(): void {\n setIdle(false);\n clearTimeout(timer);\n timer = setTimeout(() => setIdle(true), timeout);\n }\n\n for (const event of DEFAULT_EVENTS) {\n window.addEventListener(event, reset, { passive: true });\n }\n return () => {\n clearTimeout(timer);\n for (const event of DEFAULT_EVENTS) {\n window.removeEventListener(event, reset);\n }\n };\n }, [timeout]);\n\n return idle;\n}\n"],"mappings":"uBAEA,IAAM,EAA2C,CAC7C,YACA,YACA,UACA,aACA,QACA,QACJ,EAMA,SAAgB,EAAQ,EAAU,IAAiB,CAC/C,GAAM,CAAC,EAAM,IAAA,EAAA,EAAA,SAAA,CAA6B,EAAK,EAuB/C,OArBA,EAAA,EAAA,UAAA,KAAgB,CACZ,GAAI,OAAO,OAAW,IAAa,OACnC,IAAI,EAAQ,eAAiB,EAAQ,EAAI,EAAG,CAAO,EAEnD,SAAS,GAAc,CACnB,EAAQ,EAAK,EACb,aAAa,CAAK,EAClB,EAAQ,eAAiB,EAAQ,EAAI,EAAG,CAAO,CACnD,CAEA,IAAK,IAAM,KAAS,EAChB,OAAO,iBAAiB,EAAO,EAAO,CAAE,QAAS,EAAK,CAAC,EAE3D,UAAa,CACT,aAAa,CAAK,EAClB,IAAK,IAAM,KAAS,EAChB,OAAO,oBAAoB,EAAO,CAAK,CAE/C,CACJ,EAAG,CAAC,CAAO,CAAC,EAEL,CACX"}
1
+ {"version":3,"file":"use-idle.cjs","names":[],"sources":["../../src/hooks/use-idle.ts"],"sourcesContent":["import { useEffect, useState } from \"react\";\n\nconst DEFAULT_EVENTS: (keyof WindowEventMap)[] = [\n \"mousemove\",\n \"mousedown\",\n \"keydown\",\n \"touchstart\",\n \"wheel\",\n \"scroll\",\n];\n\n/**\n * Detect when the user has been idle for `timeout` ms (no interaction).\n * Returns `true` once the threshold elapses and back to `false` on activity.\n */\nexport function useIdle(timeout = 60_000): boolean {\n const [idle, setIdle] = useState<boolean>(false);\n\n useEffect(() => {\n if (typeof window === \"undefined\") return;\n let timer = setTimeout(() => setIdle(true), timeout);\n\n function reset(): void {\n setIdle(false);\n clearTimeout(timer);\n timer = setTimeout(() => setIdle(true), timeout);\n }\n\n for (const event of DEFAULT_EVENTS) {\n window.addEventListener(event, reset, { passive: true });\n }\n return () => {\n clearTimeout(timer);\n for (const event of DEFAULT_EVENTS) {\n window.removeEventListener(event, reset);\n }\n };\n }, [timeout]);\n\n return idle;\n}\n"],"mappings":"uBAEA,IAAM,EAA2C,CAC7C,YACA,YACA,UACA,aACA,QACA,QACJ,EAMA,SAAgB,EAAQ,EAAU,IAAiB,CAC/C,GAAM,CAAC,EAAM,IAAA,EAAW,EAAA,SAAA,CAAkB,EAAK,EAuB/C,OArBA,EAAA,EAAA,UAAA,KAAgB,CACZ,GAAI,OAAO,OAAW,IAAa,OACnC,IAAI,EAAQ,eAAiB,EAAQ,EAAI,EAAG,CAAO,EAEnD,SAAS,GAAc,CACnB,EAAQ,EAAK,EACb,aAAa,CAAK,EAClB,EAAQ,eAAiB,EAAQ,EAAI,EAAG,CAAO,CACnD,CAEA,IAAK,IAAM,KAAS,EAChB,OAAO,iBAAiB,EAAO,EAAO,CAAE,QAAS,EAAK,CAAC,EAE3D,UAAa,CACT,aAAa,CAAK,EAClB,IAAK,IAAM,KAAS,EAChB,OAAO,oBAAoB,EAAO,CAAK,CAE/C,CACJ,EAAG,CAAC,CAAO,CAAC,EAEL,CACX"}
@@ -1,2 +1,2 @@
1
- const e=require("./pwa-env.cjs");let t=require("react");var n=10080*60*1e3,r=3e3,i=`tempest:install-declined-at`,a=null,o=new Set;typeof window<`u`&&(window.addEventListener(`beforeinstallprompt`,e=>{e.preventDefault(),a=e,o.forEach(e=>e(a))}),window.addEventListener(`appinstalled`,()=>{a=null,o.forEach(e=>e(null))}));function s(e,t){if(typeof window>`u`)return!1;let n=window.localStorage.getItem(e);if(!n)return!1;let r=Number(n);return Number.isFinite(r)?Date.now()-r<t:!1}function c(c={}){let{declineStorageKey:l=i,declineCooldownMs:u=n,manualFallbackDelayMs:d=r}=c,[f,p]=(0,t.useState)(a),[m,h]=(0,t.useState)(e.isStandalone()),[g,_]=(0,t.useState)(!1);(0,t.useEffect)(()=>(o.add(p),()=>{o.delete(p)}),[]),(0,t.useEffect)(()=>{if(typeof window>`u`)return;let t=window.matchMedia?.(`(display-mode: standalone)`),n=()=>h(e.isStandalone());return t?.addEventListener?.(`change`,n),window.addEventListener(`appinstalled`,n),()=>{t?.removeEventListener?.(`change`,n),window.removeEventListener(`appinstalled`,n)}},[]),(0,t.useEffect)(()=>{if(f){_(!1);return}let e=window.setTimeout(()=>{a||_(!0)},d);return()=>window.clearTimeout(e)},[f,d]);let v=e.isIOS(),y=e.isAndroidWithoutPromptApi(),b=s(l,u),x=`none`;!m&&!b&&(f?x=`native`:v?x=`ios`:(y||g)&&(x=`manual`));let S=()=>{typeof window>`u`||window.localStorage.setItem(l,String(Date.now()))};return{deferredPrompt:f,canInstall:x!==`none`,isIOS:v,isStandalone:m,isManualAndroid:y,promptTimedOut:g,method:x,openInChromeIntent:e.buildOpenInChromeIntent(),install:async()=>{if(!f)return!1;await f.prompt();let e=await f.userChoice;return e.outcome===`dismissed`&&S(),a=null,o.forEach(e=>e(null)),e.outcome===`accepted`},recordDecline:S}}exports.useInstallPrompt=c;
1
+ const e=require("./pwa-env.cjs");let t=require("react");var n=6048e5,r=3e3,i=`tempest:install-declined-at`,a=null,o=new Set;typeof window<`u`&&(window.addEventListener(`beforeinstallprompt`,e=>{e.preventDefault(),a=e,o.forEach(e=>e(a))}),window.addEventListener(`appinstalled`,()=>{a=null,o.forEach(e=>e(null))}));function s(e,t){if(typeof window>`u`)return!1;let n=window.localStorage.getItem(e);if(!n)return!1;let r=Number(n);return Number.isFinite(r)?Date.now()-r<t:!1}function c(c={}){let{declineStorageKey:l=i,declineCooldownMs:u=n,manualFallbackDelayMs:d=r}=c,[f,p]=(0,t.useState)(a),[m,h]=(0,t.useState)(e.isStandalone()),[g,_]=(0,t.useState)(!1);(0,t.useEffect)(()=>(o.add(p),()=>{o.delete(p)}),[]),(0,t.useEffect)(()=>{if(typeof window>`u`)return;let t=window.matchMedia?.(`(display-mode: standalone)`),n=()=>h(e.isStandalone());return t?.addEventListener?.(`change`,n),window.addEventListener(`appinstalled`,n),()=>{t?.removeEventListener?.(`change`,n),window.removeEventListener(`appinstalled`,n)}},[]),(0,t.useEffect)(()=>{if(f){_(!1);return}let e=window.setTimeout(()=>{a||_(!0)},d);return()=>window.clearTimeout(e)},[f,d]);let v=e.isIOS(),y=e.isAndroidWithoutPromptApi(),b=s(l,u),x=`none`;!m&&!b&&(f?x=`native`:v?x=`ios`:(y||g)&&(x=`manual`));let S=()=>{typeof window>`u`||window.localStorage.setItem(l,String(Date.now()))};return{deferredPrompt:f,canInstall:x!==`none`,isIOS:v,isStandalone:m,isManualAndroid:y,promptTimedOut:g,method:x,openInChromeIntent:e.buildOpenInChromeIntent(),install:async()=>{if(!f)return!1;await f.prompt();let e=await f.userChoice;return e.outcome===`dismissed`&&S(),a=null,o.forEach(e=>e(null)),e.outcome===`accepted`},recordDecline:S}}exports.useInstallPrompt=c;
2
2
  //# sourceMappingURL=use-install-prompt.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"use-install-prompt.cjs","names":[],"sources":["../../src/hooks/use-install-prompt.ts"],"sourcesContent":["import { useEffect, useState } from \"react\";\nimport {\n buildOpenInChromeIntent,\n isAndroidWithoutPromptApi,\n isIOS,\n isStandalone,\n type BeforeInstallPromptEvent,\n} from \"./pwa-env\";\n\nconst DEFAULT_DECLINE_COOLDOWN_MS = 7 * 24 * 60 * 60 * 1000;\nconst DEFAULT_MANUAL_FALLBACK_DELAY_MS = 3000;\nconst DEFAULT_DECLINE_STORAGE_KEY = \"tempest:install-declined-at\";\n\nlet cachedEvent: BeforeInstallPromptEvent | null = null;\nconst eventSubscribers = new Set<(event: BeforeInstallPromptEvent | null) => void>();\n\nif (typeof window !== \"undefined\") {\n window.addEventListener(\"beforeinstallprompt\", (event: Event) => {\n event.preventDefault();\n cachedEvent = event as BeforeInstallPromptEvent;\n eventSubscribers.forEach((fn) => fn(cachedEvent));\n });\n window.addEventListener(\"appinstalled\", () => {\n cachedEvent = null;\n eventSubscribers.forEach((fn) => fn(null));\n });\n}\n\n/**\n * Strategy the UI should use to install the app:\n *\n * - `\"native\"`: trigger `BeforeInstallPromptEvent.prompt()`.\n * - `\"ios\"`: show iOS Safari Share → Add to Home Screen instructions.\n * - `\"manual\"`: show generic browser-menu instructions (Chromium forks that\n * strip the prompt API, plus the timeout fallback when no event arrives).\n * - `\"none\"`: nothing to offer (already installed or unsupported runtime).\n */\nexport type InstallMethod = \"native\" | \"ios\" | \"manual\" | \"none\";\n\n/** Options for {@link useInstallPrompt}. */\nexport interface UseInstallPromptOptions {\n /**\n * `localStorage` key used to persist the decline timestamp. Defaults to\n * `\"tempest:install-declined-at\"`.\n */\n declineStorageKey?: string;\n /**\n * How long, in ms, the install CTA stays hidden after the user declines.\n * Defaults to 7 days.\n */\n declineCooldownMs?: number;\n /**\n * How long, in ms, to wait for a `beforeinstallprompt` event before\n * resolving to the `\"manual\"` method. Defaults to `3000`.\n */\n manualFallbackDelayMs?: number;\n}\n\n/** State returned by {@link useInstallPrompt}. */\nexport interface UseInstallPromptResult {\n /** The cached `beforeinstallprompt` event, or `null` when unavailable. */\n deferredPrompt: BeforeInstallPromptEvent | null;\n /** True when the app can be installed through any supported method. */\n canInstall: boolean;\n /** True when running on iOS/iPadOS Safari. */\n isIOS: boolean;\n /** True when already running as an installed PWA. */\n isStandalone: boolean;\n /** True when the browser is an Android Chromium fork with no prompt API. */\n isManualAndroid: boolean;\n /** True when no `beforeinstallprompt` arrived within the timeout window. */\n promptTimedOut: boolean;\n /** The resolved install strategy the UI should follow. */\n method: InstallMethod;\n /** An `intent://` URL to re-open the page in Chrome on Android, else `null`. */\n openInChromeIntent: string | null;\n /**\n * Triggers the native prompt.\n *\n * @returns `true` when the user accepted the install, `false` otherwise.\n */\n install: () => Promise<boolean>;\n /** Records that the user declined the CTA, starting the cooldown window. */\n recordDecline: () => void;\n}\n\nfunction readDecline(key: string, cooldownMs: number): boolean {\n if (typeof window === \"undefined\") return false;\n const raw = window.localStorage.getItem(key);\n if (!raw) return false;\n const ts = Number(raw);\n if (!Number.isFinite(ts)) return false;\n return Date.now() - ts < cooldownMs;\n}\n\n/**\n * React hook that resolves how (and whether) to offer PWA installation.\n *\n * It caches the `beforeinstallprompt` event, detects iOS/iPadOS, detects\n * Android Chromium forks that lack the prompt API, detects standalone display\n * mode, and applies a decline cooldown persisted in `localStorage`. The\n * resulting `method` tells the UI which install affordance to render.\n *\n * The decline persistence is pluggable through\n * {@link UseInstallPromptOptions.declineStorageKey} and\n * {@link UseInstallPromptOptions.declineCooldownMs} — no app-specific storage\n * layer is required, and it is SSR-guarded.\n *\n * @param options - Optional storage key, cooldown, and fallback-delay tuning.\n * @returns The install state plus `install()` and `recordDecline()` actions.\n *\n * @example\n * const { method, install } = useInstallPrompt();\n * if (method === \"native\") return <button onClick={install}>Install</button>;\n */\nexport function useInstallPrompt(options: UseInstallPromptOptions = {}): UseInstallPromptResult {\n const {\n declineStorageKey = DEFAULT_DECLINE_STORAGE_KEY,\n declineCooldownMs = DEFAULT_DECLINE_COOLDOWN_MS,\n manualFallbackDelayMs = DEFAULT_MANUAL_FALLBACK_DELAY_MS,\n } = options;\n\n const [event, setEvent] = useState<BeforeInstallPromptEvent | null>(cachedEvent);\n const [standalone, setStandalone] = useState<boolean>(isStandalone());\n const [promptTimedOut, setPromptTimedOut] = useState<boolean>(false);\n\n useEffect(() => {\n eventSubscribers.add(setEvent);\n return () => {\n eventSubscribers.delete(setEvent);\n };\n }, []);\n\n useEffect(() => {\n if (typeof window === \"undefined\") return;\n const mq = window.matchMedia?.(\"(display-mode: standalone)\");\n const handler = (): void => setStandalone(isStandalone());\n mq?.addEventListener?.(\"change\", handler);\n window.addEventListener(\"appinstalled\", handler);\n return () => {\n mq?.removeEventListener?.(\"change\", handler);\n window.removeEventListener(\"appinstalled\", handler);\n };\n }, []);\n\n useEffect(() => {\n if (event) {\n setPromptTimedOut(false);\n return;\n }\n const timer = window.setTimeout(() => {\n if (!cachedEvent) setPromptTimedOut(true);\n }, manualFallbackDelayMs);\n return () => window.clearTimeout(timer);\n }, [event, manualFallbackDelayMs]);\n\n const ios = isIOS();\n const manualAndroid = isAndroidWithoutPromptApi();\n\n const declineActive = readDecline(declineStorageKey, declineCooldownMs);\n\n let method: InstallMethod = \"none\";\n if (!standalone && !declineActive) {\n if (event) method = \"native\";\n else if (ios) method = \"ios\";\n else if (manualAndroid || promptTimedOut) method = \"manual\";\n }\n\n const recordDecline = (): void => {\n if (typeof window === \"undefined\") return;\n window.localStorage.setItem(declineStorageKey, String(Date.now()));\n };\n\n const install = async (): Promise<boolean> => {\n if (!event) return false;\n await event.prompt();\n const choice = await event.userChoice;\n if (choice.outcome === \"dismissed\") recordDecline();\n cachedEvent = null;\n eventSubscribers.forEach((fn) => fn(null));\n return choice.outcome === \"accepted\";\n };\n\n return {\n deferredPrompt: event,\n canInstall: method !== \"none\",\n isIOS: ios,\n isStandalone: standalone,\n isManualAndroid: manualAndroid,\n promptTimedOut,\n method,\n openInChromeIntent: buildOpenInChromeIntent(),\n install,\n recordDecline,\n };\n}\n"],"mappings":"wDASA,IAAM,EAA8B,MAAc,GAAK,IACjD,EAAmC,IACnC,EAA8B,8BAEhC,EAA+C,KAC7C,EAAmB,IAAI,IAEzB,OAAO,OAAW,MAClB,OAAO,iBAAiB,sBAAwB,GAAiB,CAC7D,EAAM,eAAe,EACrB,EAAc,EACd,EAAiB,QAAS,GAAO,EAAG,CAAW,CAAC,CACpD,CAAC,EACD,OAAO,iBAAiB,mBAAsB,CAC1C,EAAc,KACd,EAAiB,QAAS,GAAO,EAAG,IAAI,CAAC,CAC7C,CAAC,GA6DL,SAAS,EAAY,EAAa,EAA6B,CAC3D,GAAI,OAAO,OAAW,IAAa,MAAO,GAC1C,IAAM,EAAM,OAAO,aAAa,QAAQ,CAAG,EAC3C,GAAI,CAAC,EAAK,MAAO,GACjB,IAAM,EAAK,OAAO,CAAG,EAErB,OADK,OAAO,SAAS,CAAE,EAChB,KAAK,IAAI,EAAI,EAAK,EADQ,EAErC,CAsBA,SAAgB,EAAiB,EAAmC,CAAC,EAA2B,CAC5F,GAAM,CACF,oBAAoB,EACpB,oBAAoB,EACpB,wBAAwB,GACxB,EAEE,CAAC,EAAO,IAAA,EAAA,EAAA,SAAA,CAAsD,CAAW,EACzE,CAAC,EAAY,IAAA,EAAA,EAAA,SAAA,CAAmC,EAAA,aAAa,CAAC,EAC9D,CAAC,EAAgB,IAAA,EAAA,EAAA,SAAA,CAAuC,EAAK,GAEnE,EAAA,EAAA,UAAA,MACI,EAAiB,IAAI,CAAQ,MAChB,CACT,EAAiB,OAAO,CAAQ,CACpC,GACD,CAAC,CAAC,GAEL,EAAA,EAAA,UAAA,KAAgB,CACZ,GAAI,OAAO,OAAW,IAAa,OACnC,IAAM,EAAK,OAAO,aAAa,4BAA4B,EACrD,MAAsB,EAAc,EAAA,aAAa,CAAC,EAGxD,OAFA,GAAI,mBAAmB,SAAU,CAAO,EACxC,OAAO,iBAAiB,eAAgB,CAAO,MAClC,CACT,GAAI,sBAAsB,SAAU,CAAO,EAC3C,OAAO,oBAAoB,eAAgB,CAAO,CACtD,CACJ,EAAG,CAAC,CAAC,GAEL,EAAA,EAAA,UAAA,KAAgB,CACZ,GAAI,EAAO,CACP,EAAkB,EAAK,EACvB,MACJ,CACA,IAAM,EAAQ,OAAO,eAAiB,CAC7B,GAAa,EAAkB,EAAI,CAC5C,EAAG,CAAqB,EACxB,UAAa,OAAO,aAAa,CAAK,CAC1C,EAAG,CAAC,EAAO,CAAqB,CAAC,EAEjC,IAAM,EAAM,EAAA,MAAM,EACZ,EAAgB,EAAA,0BAA0B,EAE1C,EAAgB,EAAY,EAAmB,CAAiB,EAElE,EAAwB,OACxB,CAAC,GAAc,CAAC,IACZ,EAAO,EAAS,SACX,EAAK,EAAS,OACd,GAAiB,KAAgB,EAAS,WAGvD,IAAM,MAA4B,CAC1B,OAAO,OAAW,KACtB,OAAO,aAAa,QAAQ,EAAmB,OAAO,KAAK,IAAI,CAAC,CAAC,CACrE,EAYA,MAAO,CACH,eAAgB,EAChB,WAAY,IAAW,OACvB,MAAO,EACP,aAAc,EACd,gBAAiB,EACjB,iBACA,SACA,mBAAoB,EAAA,wBAAwB,EAC5C,iBAnB0C,CAC1C,GAAI,CAAC,EAAO,MAAO,GACnB,MAAM,EAAM,OAAO,EACnB,IAAM,EAAS,MAAM,EAAM,WAI3B,OAHI,EAAO,UAAY,aAAa,EAAc,EAClD,EAAc,KACd,EAAiB,QAAS,GAAO,EAAG,IAAI,CAAC,EAClC,EAAO,UAAY,UAC9B,EAYI,eACJ,CACJ"}
1
+ {"version":3,"file":"use-install-prompt.cjs","names":[],"sources":["../../src/hooks/use-install-prompt.ts"],"sourcesContent":["import { useEffect, useState } from \"react\";\nimport {\n buildOpenInChromeIntent,\n isAndroidWithoutPromptApi,\n isIOS,\n isStandalone,\n type BeforeInstallPromptEvent,\n} from \"./pwa-env\";\n\nconst DEFAULT_DECLINE_COOLDOWN_MS = 7 * 24 * 60 * 60 * 1000;\nconst DEFAULT_MANUAL_FALLBACK_DELAY_MS = 3000;\nconst DEFAULT_DECLINE_STORAGE_KEY = \"tempest:install-declined-at\";\n\nlet cachedEvent: BeforeInstallPromptEvent | null = null;\nconst eventSubscribers = new Set<(event: BeforeInstallPromptEvent | null) => void>();\n\nif (typeof window !== \"undefined\") {\n window.addEventListener(\"beforeinstallprompt\", (event: Event) => {\n event.preventDefault();\n cachedEvent = event as BeforeInstallPromptEvent;\n eventSubscribers.forEach((fn) => fn(cachedEvent));\n });\n window.addEventListener(\"appinstalled\", () => {\n cachedEvent = null;\n eventSubscribers.forEach((fn) => fn(null));\n });\n}\n\n/**\n * Strategy the UI should use to install the app:\n *\n * - `\"native\"`: trigger `BeforeInstallPromptEvent.prompt()`.\n * - `\"ios\"`: show iOS Safari Share → Add to Home Screen instructions.\n * - `\"manual\"`: show generic browser-menu instructions (Chromium forks that\n * strip the prompt API, plus the timeout fallback when no event arrives).\n * - `\"none\"`: nothing to offer (already installed or unsupported runtime).\n */\nexport type InstallMethod = \"native\" | \"ios\" | \"manual\" | \"none\";\n\n/** Options for {@link useInstallPrompt}. */\nexport interface UseInstallPromptOptions {\n /**\n * `localStorage` key used to persist the decline timestamp. Defaults to\n * `\"tempest:install-declined-at\"`.\n */\n declineStorageKey?: string;\n /**\n * How long, in ms, the install CTA stays hidden after the user declines.\n * Defaults to 7 days.\n */\n declineCooldownMs?: number;\n /**\n * How long, in ms, to wait for a `beforeinstallprompt` event before\n * resolving to the `\"manual\"` method. Defaults to `3000`.\n */\n manualFallbackDelayMs?: number;\n}\n\n/** State returned by {@link useInstallPrompt}. */\nexport interface UseInstallPromptResult {\n /** The cached `beforeinstallprompt` event, or `null` when unavailable. */\n deferredPrompt: BeforeInstallPromptEvent | null;\n /** True when the app can be installed through any supported method. */\n canInstall: boolean;\n /** True when running on iOS/iPadOS Safari. */\n isIOS: boolean;\n /** True when already running as an installed PWA. */\n isStandalone: boolean;\n /** True when the browser is an Android Chromium fork with no prompt API. */\n isManualAndroid: boolean;\n /** True when no `beforeinstallprompt` arrived within the timeout window. */\n promptTimedOut: boolean;\n /** The resolved install strategy the UI should follow. */\n method: InstallMethod;\n /** An `intent://` URL to re-open the page in Chrome on Android, else `null`. */\n openInChromeIntent: string | null;\n /**\n * Triggers the native prompt.\n *\n * @returns `true` when the user accepted the install, `false` otherwise.\n */\n install: () => Promise<boolean>;\n /** Records that the user declined the CTA, starting the cooldown window. */\n recordDecline: () => void;\n}\n\nfunction readDecline(key: string, cooldownMs: number): boolean {\n if (typeof window === \"undefined\") return false;\n const raw = window.localStorage.getItem(key);\n if (!raw) return false;\n const ts = Number(raw);\n if (!Number.isFinite(ts)) return false;\n return Date.now() - ts < cooldownMs;\n}\n\n/**\n * React hook that resolves how (and whether) to offer PWA installation.\n *\n * It caches the `beforeinstallprompt` event, detects iOS/iPadOS, detects\n * Android Chromium forks that lack the prompt API, detects standalone display\n * mode, and applies a decline cooldown persisted in `localStorage`. The\n * resulting `method` tells the UI which install affordance to render.\n *\n * The decline persistence is pluggable through\n * {@link UseInstallPromptOptions.declineStorageKey} and\n * {@link UseInstallPromptOptions.declineCooldownMs} — no app-specific storage\n * layer is required, and it is SSR-guarded.\n *\n * @param options - Optional storage key, cooldown, and fallback-delay tuning.\n * @returns The install state plus `install()` and `recordDecline()` actions.\n *\n * @example\n * const { method, install } = useInstallPrompt();\n * if (method === \"native\") return <button onClick={install}>Install</button>;\n */\nexport function useInstallPrompt(options: UseInstallPromptOptions = {}): UseInstallPromptResult {\n const {\n declineStorageKey = DEFAULT_DECLINE_STORAGE_KEY,\n declineCooldownMs = DEFAULT_DECLINE_COOLDOWN_MS,\n manualFallbackDelayMs = DEFAULT_MANUAL_FALLBACK_DELAY_MS,\n } = options;\n\n const [event, setEvent] = useState<BeforeInstallPromptEvent | null>(cachedEvent);\n const [standalone, setStandalone] = useState<boolean>(isStandalone());\n const [promptTimedOut, setPromptTimedOut] = useState<boolean>(false);\n\n useEffect(() => {\n eventSubscribers.add(setEvent);\n return () => {\n eventSubscribers.delete(setEvent);\n };\n }, []);\n\n useEffect(() => {\n if (typeof window === \"undefined\") return;\n const mq = window.matchMedia?.(\"(display-mode: standalone)\");\n const handler = (): void => setStandalone(isStandalone());\n mq?.addEventListener?.(\"change\", handler);\n window.addEventListener(\"appinstalled\", handler);\n return () => {\n mq?.removeEventListener?.(\"change\", handler);\n window.removeEventListener(\"appinstalled\", handler);\n };\n }, []);\n\n useEffect(() => {\n if (event) {\n setPromptTimedOut(false);\n return;\n }\n const timer = window.setTimeout(() => {\n if (!cachedEvent) setPromptTimedOut(true);\n }, manualFallbackDelayMs);\n return () => window.clearTimeout(timer);\n }, [event, manualFallbackDelayMs]);\n\n const ios = isIOS();\n const manualAndroid = isAndroidWithoutPromptApi();\n\n const declineActive = readDecline(declineStorageKey, declineCooldownMs);\n\n let method: InstallMethod = \"none\";\n if (!standalone && !declineActive) {\n if (event) method = \"native\";\n else if (ios) method = \"ios\";\n else if (manualAndroid || promptTimedOut) method = \"manual\";\n }\n\n const recordDecline = (): void => {\n if (typeof window === \"undefined\") return;\n window.localStorage.setItem(declineStorageKey, String(Date.now()));\n };\n\n const install = async (): Promise<boolean> => {\n if (!event) return false;\n await event.prompt();\n const choice = await event.userChoice;\n if (choice.outcome === \"dismissed\") recordDecline();\n cachedEvent = null;\n eventSubscribers.forEach((fn) => fn(null));\n return choice.outcome === \"accepted\";\n };\n\n return {\n deferredPrompt: event,\n canInstall: method !== \"none\",\n isIOS: ios,\n isStandalone: standalone,\n isManualAndroid: manualAndroid,\n promptTimedOut,\n method,\n openInChromeIntent: buildOpenInChromeIntent(),\n install,\n recordDecline,\n };\n}\n"],"mappings":"wDASA,IAAM,EAA8B,OAC9B,EAAmC,IACnC,EAA8B,8BAEhC,EAA+C,KAC7C,EAAmB,IAAI,IAEzB,OAAO,OAAW,MAClB,OAAO,iBAAiB,sBAAwB,GAAiB,CAC7D,EAAM,eAAe,EACrB,EAAc,EACd,EAAiB,QAAS,GAAO,EAAG,CAAW,CAAC,CACpD,CAAC,EACD,OAAO,iBAAiB,mBAAsB,CAC1C,EAAc,KACd,EAAiB,QAAS,GAAO,EAAG,IAAI,CAAC,CAC7C,CAAC,GA6DL,SAAS,EAAY,EAAa,EAA6B,CAC3D,GAAI,OAAO,OAAW,IAAa,MAAO,GAC1C,IAAM,EAAM,OAAO,aAAa,QAAQ,CAAG,EAC3C,GAAI,CAAC,EAAK,MAAO,GACjB,IAAM,EAAK,OAAO,CAAG,EAErB,OADK,OAAO,SAAS,CAAE,EAChB,KAAK,IAAI,EAAI,EAAK,EADQ,EAErC,CAsBA,SAAgB,EAAiB,EAAmC,CAAC,EAA2B,CAC5F,GAAM,CACF,oBAAoB,EACpB,oBAAoB,EACpB,wBAAwB,GACxB,EAEE,CAAC,EAAO,IAAA,EAAY,EAAA,SAAA,CAA0C,CAAW,EACzE,CAAC,EAAY,IAAA,EAAiB,EAAA,SAAA,CAAkB,EAAA,aAAa,CAAC,EAC9D,CAAC,EAAgB,IAAA,EAAqB,EAAA,SAAA,CAAkB,EAAK,GAEnE,EAAA,EAAA,UAAA,MACI,EAAiB,IAAI,CAAQ,MAChB,CACT,EAAiB,OAAO,CAAQ,CACpC,GACD,CAAC,CAAC,GAEL,EAAA,EAAA,UAAA,KAAgB,CACZ,GAAI,OAAO,OAAW,IAAa,OACnC,IAAM,EAAK,OAAO,aAAa,4BAA4B,EACrD,MAAsB,EAAc,EAAA,aAAa,CAAC,EAGxD,OAFA,GAAI,mBAAmB,SAAU,CAAO,EACxC,OAAO,iBAAiB,eAAgB,CAAO,MAClC,CACT,GAAI,sBAAsB,SAAU,CAAO,EAC3C,OAAO,oBAAoB,eAAgB,CAAO,CACtD,CACJ,EAAG,CAAC,CAAC,GAEL,EAAA,EAAA,UAAA,KAAgB,CACZ,GAAI,EAAO,CACP,EAAkB,EAAK,EACvB,MACJ,CACA,IAAM,EAAQ,OAAO,eAAiB,CAC7B,GAAa,EAAkB,EAAI,CAC5C,EAAG,CAAqB,EACxB,UAAa,OAAO,aAAa,CAAK,CAC1C,EAAG,CAAC,EAAO,CAAqB,CAAC,EAEjC,IAAM,EAAM,EAAA,MAAM,EACZ,EAAgB,EAAA,0BAA0B,EAE1C,EAAgB,EAAY,EAAmB,CAAiB,EAElE,EAAwB,OACxB,CAAC,GAAc,CAAC,IACZ,EAAO,EAAS,SACX,EAAK,EAAS,OACd,GAAiB,KAAgB,EAAS,WAGvD,IAAM,MAA4B,CAC1B,OAAO,OAAW,KACtB,OAAO,aAAa,QAAQ,EAAmB,OAAO,KAAK,IAAI,CAAC,CAAC,CACrE,EAYA,MAAO,CACH,eAAgB,EAChB,WAAY,IAAW,OACvB,MAAO,EACP,aAAc,EACd,gBAAiB,EACjB,iBACA,SACA,mBAAoB,EAAA,wBAAwB,EAC5C,iBAnB0C,CAC1C,GAAI,CAAC,EAAO,MAAO,GACnB,MAAM,EAAM,OAAO,EACnB,IAAM,EAAS,MAAM,EAAM,WAI3B,OAHI,EAAO,UAAY,aAAa,EAAc,EAClD,EAAc,KACd,EAAiB,QAAS,GAAO,EAAG,IAAI,CAAC,EAClC,EAAO,UAAY,UAC9B,EAYI,eACJ,CACJ"}
@@ -1,7 +1,7 @@
1
1
  import { buildOpenInChromeIntent as e, isAndroidWithoutPromptApi as t, isIOS as n, isStandalone as r } from "./pwa-env.js";
2
2
  import { useEffect as i, useState as a } from "react";
3
3
  //#region src/hooks/use-install-prompt.ts
4
- var o = 10080 * 60 * 1e3, s = 3e3, c = "tempest:install-declined-at", l = null, u = /* @__PURE__ */ new Set();
4
+ var o = 6048e5, s = 3e3, c = "tempest:install-declined-at", l = null, u = /* @__PURE__ */ new Set();
5
5
  typeof window < "u" && (window.addEventListener("beforeinstallprompt", (e) => {
6
6
  e.preventDefault(), l = e, u.forEach((e) => e(l));
7
7
  }), window.addEventListener("appinstalled", () => {
@@ -1 +1 @@
1
- {"version":3,"file":"use-install-prompt.js","names":[],"sources":["../../src/hooks/use-install-prompt.ts"],"sourcesContent":["import { useEffect, useState } from \"react\";\nimport {\n buildOpenInChromeIntent,\n isAndroidWithoutPromptApi,\n isIOS,\n isStandalone,\n type BeforeInstallPromptEvent,\n} from \"./pwa-env\";\n\nconst DEFAULT_DECLINE_COOLDOWN_MS = 7 * 24 * 60 * 60 * 1000;\nconst DEFAULT_MANUAL_FALLBACK_DELAY_MS = 3000;\nconst DEFAULT_DECLINE_STORAGE_KEY = \"tempest:install-declined-at\";\n\nlet cachedEvent: BeforeInstallPromptEvent | null = null;\nconst eventSubscribers = new Set<(event: BeforeInstallPromptEvent | null) => void>();\n\nif (typeof window !== \"undefined\") {\n window.addEventListener(\"beforeinstallprompt\", (event: Event) => {\n event.preventDefault();\n cachedEvent = event as BeforeInstallPromptEvent;\n eventSubscribers.forEach((fn) => fn(cachedEvent));\n });\n window.addEventListener(\"appinstalled\", () => {\n cachedEvent = null;\n eventSubscribers.forEach((fn) => fn(null));\n });\n}\n\n/**\n * Strategy the UI should use to install the app:\n *\n * - `\"native\"`: trigger `BeforeInstallPromptEvent.prompt()`.\n * - `\"ios\"`: show iOS Safari Share → Add to Home Screen instructions.\n * - `\"manual\"`: show generic browser-menu instructions (Chromium forks that\n * strip the prompt API, plus the timeout fallback when no event arrives).\n * - `\"none\"`: nothing to offer (already installed or unsupported runtime).\n */\nexport type InstallMethod = \"native\" | \"ios\" | \"manual\" | \"none\";\n\n/** Options for {@link useInstallPrompt}. */\nexport interface UseInstallPromptOptions {\n /**\n * `localStorage` key used to persist the decline timestamp. Defaults to\n * `\"tempest:install-declined-at\"`.\n */\n declineStorageKey?: string;\n /**\n * How long, in ms, the install CTA stays hidden after the user declines.\n * Defaults to 7 days.\n */\n declineCooldownMs?: number;\n /**\n * How long, in ms, to wait for a `beforeinstallprompt` event before\n * resolving to the `\"manual\"` method. Defaults to `3000`.\n */\n manualFallbackDelayMs?: number;\n}\n\n/** State returned by {@link useInstallPrompt}. */\nexport interface UseInstallPromptResult {\n /** The cached `beforeinstallprompt` event, or `null` when unavailable. */\n deferredPrompt: BeforeInstallPromptEvent | null;\n /** True when the app can be installed through any supported method. */\n canInstall: boolean;\n /** True when running on iOS/iPadOS Safari. */\n isIOS: boolean;\n /** True when already running as an installed PWA. */\n isStandalone: boolean;\n /** True when the browser is an Android Chromium fork with no prompt API. */\n isManualAndroid: boolean;\n /** True when no `beforeinstallprompt` arrived within the timeout window. */\n promptTimedOut: boolean;\n /** The resolved install strategy the UI should follow. */\n method: InstallMethod;\n /** An `intent://` URL to re-open the page in Chrome on Android, else `null`. */\n openInChromeIntent: string | null;\n /**\n * Triggers the native prompt.\n *\n * @returns `true` when the user accepted the install, `false` otherwise.\n */\n install: () => Promise<boolean>;\n /** Records that the user declined the CTA, starting the cooldown window. */\n recordDecline: () => void;\n}\n\nfunction readDecline(key: string, cooldownMs: number): boolean {\n if (typeof window === \"undefined\") return false;\n const raw = window.localStorage.getItem(key);\n if (!raw) return false;\n const ts = Number(raw);\n if (!Number.isFinite(ts)) return false;\n return Date.now() - ts < cooldownMs;\n}\n\n/**\n * React hook that resolves how (and whether) to offer PWA installation.\n *\n * It caches the `beforeinstallprompt` event, detects iOS/iPadOS, detects\n * Android Chromium forks that lack the prompt API, detects standalone display\n * mode, and applies a decline cooldown persisted in `localStorage`. The\n * resulting `method` tells the UI which install affordance to render.\n *\n * The decline persistence is pluggable through\n * {@link UseInstallPromptOptions.declineStorageKey} and\n * {@link UseInstallPromptOptions.declineCooldownMs} — no app-specific storage\n * layer is required, and it is SSR-guarded.\n *\n * @param options - Optional storage key, cooldown, and fallback-delay tuning.\n * @returns The install state plus `install()` and `recordDecline()` actions.\n *\n * @example\n * const { method, install } = useInstallPrompt();\n * if (method === \"native\") return <button onClick={install}>Install</button>;\n */\nexport function useInstallPrompt(options: UseInstallPromptOptions = {}): UseInstallPromptResult {\n const {\n declineStorageKey = DEFAULT_DECLINE_STORAGE_KEY,\n declineCooldownMs = DEFAULT_DECLINE_COOLDOWN_MS,\n manualFallbackDelayMs = DEFAULT_MANUAL_FALLBACK_DELAY_MS,\n } = options;\n\n const [event, setEvent] = useState<BeforeInstallPromptEvent | null>(cachedEvent);\n const [standalone, setStandalone] = useState<boolean>(isStandalone());\n const [promptTimedOut, setPromptTimedOut] = useState<boolean>(false);\n\n useEffect(() => {\n eventSubscribers.add(setEvent);\n return () => {\n eventSubscribers.delete(setEvent);\n };\n }, []);\n\n useEffect(() => {\n if (typeof window === \"undefined\") return;\n const mq = window.matchMedia?.(\"(display-mode: standalone)\");\n const handler = (): void => setStandalone(isStandalone());\n mq?.addEventListener?.(\"change\", handler);\n window.addEventListener(\"appinstalled\", handler);\n return () => {\n mq?.removeEventListener?.(\"change\", handler);\n window.removeEventListener(\"appinstalled\", handler);\n };\n }, []);\n\n useEffect(() => {\n if (event) {\n setPromptTimedOut(false);\n return;\n }\n const timer = window.setTimeout(() => {\n if (!cachedEvent) setPromptTimedOut(true);\n }, manualFallbackDelayMs);\n return () => window.clearTimeout(timer);\n }, [event, manualFallbackDelayMs]);\n\n const ios = isIOS();\n const manualAndroid = isAndroidWithoutPromptApi();\n\n const declineActive = readDecline(declineStorageKey, declineCooldownMs);\n\n let method: InstallMethod = \"none\";\n if (!standalone && !declineActive) {\n if (event) method = \"native\";\n else if (ios) method = \"ios\";\n else if (manualAndroid || promptTimedOut) method = \"manual\";\n }\n\n const recordDecline = (): void => {\n if (typeof window === \"undefined\") return;\n window.localStorage.setItem(declineStorageKey, String(Date.now()));\n };\n\n const install = async (): Promise<boolean> => {\n if (!event) return false;\n await event.prompt();\n const choice = await event.userChoice;\n if (choice.outcome === \"dismissed\") recordDecline();\n cachedEvent = null;\n eventSubscribers.forEach((fn) => fn(null));\n return choice.outcome === \"accepted\";\n };\n\n return {\n deferredPrompt: event,\n canInstall: method !== \"none\",\n isIOS: ios,\n isStandalone: standalone,\n isManualAndroid: manualAndroid,\n promptTimedOut,\n method,\n openInChromeIntent: buildOpenInChromeIntent(),\n install,\n recordDecline,\n };\n}\n"],"mappings":";;;AASA,IAAM,IAA8B,QAAc,KAAK,KACjD,IAAmC,KACnC,IAA8B,+BAEhC,IAA+C,MAC7C,oBAAmB,IAAI,IAAsD;AAE/E,OAAO,SAAW,QAClB,OAAO,iBAAiB,wBAAwB,MAAiB;CAG7D,AAFA,EAAM,eAAe,GACrB,IAAc,GACd,EAAiB,SAAS,MAAO,EAAG,CAAW,CAAC;AACpD,CAAC,GACD,OAAO,iBAAiB,sBAAsB;CAE1C,AADA,IAAc,MACd,EAAiB,SAAS,MAAO,EAAG,IAAI,CAAC;AAC7C,CAAC;AA6DL,SAAS,EAAY,GAAa,GAA6B;CAC3D,IAAI,OAAO,SAAW,KAAa,OAAO;CAC1C,IAAM,IAAM,OAAO,aAAa,QAAQ,CAAG;CAC3C,IAAI,CAAC,GAAK,OAAO;CACjB,IAAM,IAAK,OAAO,CAAG;CAErB,OADK,OAAO,SAAS,CAAE,IAChB,KAAK,IAAI,IAAI,IAAK,IADQ;AAErC;AAsBA,SAAgB,EAAiB,IAAmC,CAAC,GAA2B;CAC5F,IAAM,EACF,uBAAoB,GACpB,uBAAoB,GACpB,2BAAwB,MACxB,GAEE,CAAC,GAAO,KAAY,EAA0C,CAAW,GACzE,CAAC,GAAY,KAAiB,EAAkB,EAAa,CAAC,GAC9D,CAAC,GAAgB,KAAqB,EAAkB,EAAK;CAqBnE,AAnBA,SACI,EAAiB,IAAI,CAAQ,SAChB;EACT,EAAiB,OAAO,CAAQ;CACpC,IACD,CAAC,CAAC,GAEL,QAAgB;EACZ,IAAI,OAAO,SAAW,KAAa;EACnC,IAAM,IAAK,OAAO,aAAa,4BAA4B,GACrD,UAAsB,EAAc,EAAa,CAAC;EAGxD,OAFA,GAAI,mBAAmB,UAAU,CAAO,GACxC,OAAO,iBAAiB,gBAAgB,CAAO,SAClC;GAET,AADA,GAAI,sBAAsB,UAAU,CAAO,GAC3C,OAAO,oBAAoB,gBAAgB,CAAO;EACtD;CACJ,GAAG,CAAC,CAAC,GAEL,QAAgB;EACZ,IAAI,GAAO;GACP,EAAkB,EAAK;GACvB;EACJ;EACA,IAAM,IAAQ,OAAO,iBAAiB;GAClC,AAAK,KAAa,EAAkB,EAAI;EAC5C,GAAG,CAAqB;EACxB,aAAa,OAAO,aAAa,CAAK;CAC1C,GAAG,CAAC,GAAO,CAAqB,CAAC;CAEjC,IAAM,IAAM,EAAM,GACZ,IAAgB,EAA0B,GAE1C,IAAgB,EAAY,GAAmB,CAAiB,GAElE,IAAwB;CAC5B,AAAI,CAAC,KAAc,CAAC,MACZ,IAAO,IAAS,WACX,IAAK,IAAS,SACd,KAAiB,OAAgB,IAAS;CAGvD,IAAM,UAA4B;EAC1B,OAAO,SAAW,OACtB,OAAO,aAAa,QAAQ,GAAmB,OAAO,KAAK,IAAI,CAAC,CAAC;CACrE;CAYA,OAAO;EACH,gBAAgB;EAChB,YAAY,MAAW;EACvB,OAAO;EACP,cAAc;EACd,iBAAiB;EACjB;EACA;EACA,oBAAoB,EAAwB;EAC5C,qBAnB0C;GAC1C,IAAI,CAAC,GAAO,OAAO;GACnB,MAAM,EAAM,OAAO;GACnB,IAAM,IAAS,MAAM,EAAM;GAI3B,OAHI,EAAO,YAAY,eAAa,EAAc,GAClD,IAAc,MACd,EAAiB,SAAS,MAAO,EAAG,IAAI,CAAC,GAClC,EAAO,YAAY;EAC9B;EAYI;CACJ;AACJ"}
1
+ {"version":3,"file":"use-install-prompt.js","names":[],"sources":["../../src/hooks/use-install-prompt.ts"],"sourcesContent":["import { useEffect, useState } from \"react\";\nimport {\n buildOpenInChromeIntent,\n isAndroidWithoutPromptApi,\n isIOS,\n isStandalone,\n type BeforeInstallPromptEvent,\n} from \"./pwa-env\";\n\nconst DEFAULT_DECLINE_COOLDOWN_MS = 7 * 24 * 60 * 60 * 1000;\nconst DEFAULT_MANUAL_FALLBACK_DELAY_MS = 3000;\nconst DEFAULT_DECLINE_STORAGE_KEY = \"tempest:install-declined-at\";\n\nlet cachedEvent: BeforeInstallPromptEvent | null = null;\nconst eventSubscribers = new Set<(event: BeforeInstallPromptEvent | null) => void>();\n\nif (typeof window !== \"undefined\") {\n window.addEventListener(\"beforeinstallprompt\", (event: Event) => {\n event.preventDefault();\n cachedEvent = event as BeforeInstallPromptEvent;\n eventSubscribers.forEach((fn) => fn(cachedEvent));\n });\n window.addEventListener(\"appinstalled\", () => {\n cachedEvent = null;\n eventSubscribers.forEach((fn) => fn(null));\n });\n}\n\n/**\n * Strategy the UI should use to install the app:\n *\n * - `\"native\"`: trigger `BeforeInstallPromptEvent.prompt()`.\n * - `\"ios\"`: show iOS Safari Share → Add to Home Screen instructions.\n * - `\"manual\"`: show generic browser-menu instructions (Chromium forks that\n * strip the prompt API, plus the timeout fallback when no event arrives).\n * - `\"none\"`: nothing to offer (already installed or unsupported runtime).\n */\nexport type InstallMethod = \"native\" | \"ios\" | \"manual\" | \"none\";\n\n/** Options for {@link useInstallPrompt}. */\nexport interface UseInstallPromptOptions {\n /**\n * `localStorage` key used to persist the decline timestamp. Defaults to\n * `\"tempest:install-declined-at\"`.\n */\n declineStorageKey?: string;\n /**\n * How long, in ms, the install CTA stays hidden after the user declines.\n * Defaults to 7 days.\n */\n declineCooldownMs?: number;\n /**\n * How long, in ms, to wait for a `beforeinstallprompt` event before\n * resolving to the `\"manual\"` method. Defaults to `3000`.\n */\n manualFallbackDelayMs?: number;\n}\n\n/** State returned by {@link useInstallPrompt}. */\nexport interface UseInstallPromptResult {\n /** The cached `beforeinstallprompt` event, or `null` when unavailable. */\n deferredPrompt: BeforeInstallPromptEvent | null;\n /** True when the app can be installed through any supported method. */\n canInstall: boolean;\n /** True when running on iOS/iPadOS Safari. */\n isIOS: boolean;\n /** True when already running as an installed PWA. */\n isStandalone: boolean;\n /** True when the browser is an Android Chromium fork with no prompt API. */\n isManualAndroid: boolean;\n /** True when no `beforeinstallprompt` arrived within the timeout window. */\n promptTimedOut: boolean;\n /** The resolved install strategy the UI should follow. */\n method: InstallMethod;\n /** An `intent://` URL to re-open the page in Chrome on Android, else `null`. */\n openInChromeIntent: string | null;\n /**\n * Triggers the native prompt.\n *\n * @returns `true` when the user accepted the install, `false` otherwise.\n */\n install: () => Promise<boolean>;\n /** Records that the user declined the CTA, starting the cooldown window. */\n recordDecline: () => void;\n}\n\nfunction readDecline(key: string, cooldownMs: number): boolean {\n if (typeof window === \"undefined\") return false;\n const raw = window.localStorage.getItem(key);\n if (!raw) return false;\n const ts = Number(raw);\n if (!Number.isFinite(ts)) return false;\n return Date.now() - ts < cooldownMs;\n}\n\n/**\n * React hook that resolves how (and whether) to offer PWA installation.\n *\n * It caches the `beforeinstallprompt` event, detects iOS/iPadOS, detects\n * Android Chromium forks that lack the prompt API, detects standalone display\n * mode, and applies a decline cooldown persisted in `localStorage`. The\n * resulting `method` tells the UI which install affordance to render.\n *\n * The decline persistence is pluggable through\n * {@link UseInstallPromptOptions.declineStorageKey} and\n * {@link UseInstallPromptOptions.declineCooldownMs} — no app-specific storage\n * layer is required, and it is SSR-guarded.\n *\n * @param options - Optional storage key, cooldown, and fallback-delay tuning.\n * @returns The install state plus `install()` and `recordDecline()` actions.\n *\n * @example\n * const { method, install } = useInstallPrompt();\n * if (method === \"native\") return <button onClick={install}>Install</button>;\n */\nexport function useInstallPrompt(options: UseInstallPromptOptions = {}): UseInstallPromptResult {\n const {\n declineStorageKey = DEFAULT_DECLINE_STORAGE_KEY,\n declineCooldownMs = DEFAULT_DECLINE_COOLDOWN_MS,\n manualFallbackDelayMs = DEFAULT_MANUAL_FALLBACK_DELAY_MS,\n } = options;\n\n const [event, setEvent] = useState<BeforeInstallPromptEvent | null>(cachedEvent);\n const [standalone, setStandalone] = useState<boolean>(isStandalone());\n const [promptTimedOut, setPromptTimedOut] = useState<boolean>(false);\n\n useEffect(() => {\n eventSubscribers.add(setEvent);\n return () => {\n eventSubscribers.delete(setEvent);\n };\n }, []);\n\n useEffect(() => {\n if (typeof window === \"undefined\") return;\n const mq = window.matchMedia?.(\"(display-mode: standalone)\");\n const handler = (): void => setStandalone(isStandalone());\n mq?.addEventListener?.(\"change\", handler);\n window.addEventListener(\"appinstalled\", handler);\n return () => {\n mq?.removeEventListener?.(\"change\", handler);\n window.removeEventListener(\"appinstalled\", handler);\n };\n }, []);\n\n useEffect(() => {\n if (event) {\n setPromptTimedOut(false);\n return;\n }\n const timer = window.setTimeout(() => {\n if (!cachedEvent) setPromptTimedOut(true);\n }, manualFallbackDelayMs);\n return () => window.clearTimeout(timer);\n }, [event, manualFallbackDelayMs]);\n\n const ios = isIOS();\n const manualAndroid = isAndroidWithoutPromptApi();\n\n const declineActive = readDecline(declineStorageKey, declineCooldownMs);\n\n let method: InstallMethod = \"none\";\n if (!standalone && !declineActive) {\n if (event) method = \"native\";\n else if (ios) method = \"ios\";\n else if (manualAndroid || promptTimedOut) method = \"manual\";\n }\n\n const recordDecline = (): void => {\n if (typeof window === \"undefined\") return;\n window.localStorage.setItem(declineStorageKey, String(Date.now()));\n };\n\n const install = async (): Promise<boolean> => {\n if (!event) return false;\n await event.prompt();\n const choice = await event.userChoice;\n if (choice.outcome === \"dismissed\") recordDecline();\n cachedEvent = null;\n eventSubscribers.forEach((fn) => fn(null));\n return choice.outcome === \"accepted\";\n };\n\n return {\n deferredPrompt: event,\n canInstall: method !== \"none\",\n isIOS: ios,\n isStandalone: standalone,\n isManualAndroid: manualAndroid,\n promptTimedOut,\n method,\n openInChromeIntent: buildOpenInChromeIntent(),\n install,\n recordDecline,\n };\n}\n"],"mappings":";;;AASA,IAAM,IAA8B,QAC9B,IAAmC,KACnC,IAA8B,+BAEhC,IAA+C,MAC7C,oBAAmB,IAAI,IAAsD;AAE/E,OAAO,SAAW,QAClB,OAAO,iBAAiB,wBAAwB,MAAiB;CAG7D,AAFA,EAAM,eAAe,GACrB,IAAc,GACd,EAAiB,SAAS,MAAO,EAAG,CAAW,CAAC;AACpD,CAAC,GACD,OAAO,iBAAiB,sBAAsB;CAE1C,AADA,IAAc,MACd,EAAiB,SAAS,MAAO,EAAG,IAAI,CAAC;AAC7C,CAAC;AA6DL,SAAS,EAAY,GAAa,GAA6B;CAC3D,IAAI,OAAO,SAAW,KAAa,OAAO;CAC1C,IAAM,IAAM,OAAO,aAAa,QAAQ,CAAG;CAC3C,IAAI,CAAC,GAAK,OAAO;CACjB,IAAM,IAAK,OAAO,CAAG;CAErB,OADK,OAAO,SAAS,CAAE,IAChB,KAAK,IAAI,IAAI,IAAK,IADQ;AAErC;AAsBA,SAAgB,EAAiB,IAAmC,CAAC,GAA2B;CAC5F,IAAM,EACF,uBAAoB,GACpB,uBAAoB,GACpB,2BAAwB,MACxB,GAEE,CAAC,GAAO,KAAY,EAA0C,CAAW,GACzE,CAAC,GAAY,KAAiB,EAAkB,EAAa,CAAC,GAC9D,CAAC,GAAgB,KAAqB,EAAkB,EAAK;CAqBnE,AAnBA,SACI,EAAiB,IAAI,CAAQ,SAChB;EACT,EAAiB,OAAO,CAAQ;CACpC,IACD,CAAC,CAAC,GAEL,QAAgB;EACZ,IAAI,OAAO,SAAW,KAAa;EACnC,IAAM,IAAK,OAAO,aAAa,4BAA4B,GACrD,UAAsB,EAAc,EAAa,CAAC;EAGxD,OAFA,GAAI,mBAAmB,UAAU,CAAO,GACxC,OAAO,iBAAiB,gBAAgB,CAAO,SAClC;GAET,AADA,GAAI,sBAAsB,UAAU,CAAO,GAC3C,OAAO,oBAAoB,gBAAgB,CAAO;EACtD;CACJ,GAAG,CAAC,CAAC,GAEL,QAAgB;EACZ,IAAI,GAAO;GACP,EAAkB,EAAK;GACvB;EACJ;EACA,IAAM,IAAQ,OAAO,iBAAiB;GAClC,AAAK,KAAa,EAAkB,EAAI;EAC5C,GAAG,CAAqB;EACxB,aAAa,OAAO,aAAa,CAAK;CAC1C,GAAG,CAAC,GAAO,CAAqB,CAAC;CAEjC,IAAM,IAAM,EAAM,GACZ,IAAgB,EAA0B,GAE1C,IAAgB,EAAY,GAAmB,CAAiB,GAElE,IAAwB;CAC5B,AAAI,CAAC,KAAc,CAAC,MACZ,IAAO,IAAS,WACX,IAAK,IAAS,SACd,KAAiB,OAAgB,IAAS;CAGvD,IAAM,UAA4B;EAC1B,OAAO,SAAW,OACtB,OAAO,aAAa,QAAQ,GAAmB,OAAO,KAAK,IAAI,CAAC,CAAC;CACrE;CAYA,OAAO;EACH,gBAAgB;EAChB,YAAY,MAAW;EACvB,OAAO;EACP,cAAc;EACd,iBAAiB;EACjB;EACA;EACA,oBAAoB,EAAwB;EAC5C,qBAnB0C;GAC1C,IAAI,CAAC,GAAO,OAAO;GACnB,MAAM,EAAM,OAAO;GACnB,IAAM,IAAS,MAAM,EAAM;GAI3B,OAHI,EAAO,YAAY,eAAa,EAAc,GAClD,IAAc,MACd,EAAiB,SAAS,MAAO,EAAG,IAAI,CAAC,GAClC,EAAO,YAAY;EAC9B;EAYI;CACJ;AACJ"}
@@ -1 +1 @@
1
- {"version":3,"file":"use-intersection-observer.cjs","names":[],"sources":["../../src/hooks/use-intersection-observer.ts"],"sourcesContent":["import { useEffect, useState } from \"react\";\nimport type { RefObject } from \"react\";\n\nexport interface UseIntersectionObserverOptions extends IntersectionObserverInit {\n /** Stop observing after the first intersection (one-shot). Default: false. */\n once?: boolean;\n}\n\n/**\n * Track whether the referenced element intersects the viewport. Useful for\n * lazy-loading images, \"load more\" sentinels, and animation triggers.\n *\n * @returns `IntersectionObserverEntry | null`. `null` until the first observation.\n */\nexport function useIntersectionObserver(\n ref: RefObject<Element | null>,\n options: UseIntersectionObserverOptions = {},\n): IntersectionObserverEntry | null {\n const [entry, setEntry] = useState<IntersectionObserverEntry | null>(null);\n const { once = false, root, rootMargin, threshold } = options;\n\n useEffect(() => {\n const target = ref.current;\n if (!target || typeof IntersectionObserver === \"undefined\") return;\n\n const observer = new IntersectionObserver(\n ([nextEntry]) => {\n if (!nextEntry) return;\n setEntry(nextEntry);\n if (once && nextEntry.isIntersecting) observer.unobserve(target);\n },\n { root, rootMargin, threshold },\n );\n\n observer.observe(target);\n return () => observer.disconnect();\n }, [ref, once, root, rootMargin, threshold]);\n\n return entry;\n}\n"],"mappings":"uBAcA,SAAgB,EACZ,EACA,EAA0C,CAAC,EACX,CAChC,GAAM,CAAC,EAAO,IAAA,EAAA,EAAA,SAAA,CAAuD,IAAI,EACnE,CAAE,OAAO,GAAO,OAAM,aAAY,aAAc,EAmBtD,OAjBA,EAAA,EAAA,UAAA,KAAgB,CACZ,IAAM,EAAS,EAAI,QACnB,GAAI,CAAC,GAAU,OAAO,qBAAyB,IAAa,OAE5D,IAAM,EAAW,IAAI,sBAChB,CAAC,KAAe,CACR,IACL,EAAS,CAAS,EACd,GAAQ,EAAU,gBAAgB,EAAS,UAAU,CAAM,EACnE,EACA,CAAE,OAAM,aAAY,WAAU,CAClC,EAGA,OADA,EAAS,QAAQ,CAAM,MACV,EAAS,WAAW,CACrC,EAAG,CAAC,EAAK,EAAM,EAAM,EAAY,CAAS,CAAC,EAEpC,CACX"}
1
+ {"version":3,"file":"use-intersection-observer.cjs","names":[],"sources":["../../src/hooks/use-intersection-observer.ts"],"sourcesContent":["import { useEffect, useState } from \"react\";\nimport type { RefObject } from \"react\";\n\nexport interface UseIntersectionObserverOptions extends IntersectionObserverInit {\n /** Stop observing after the first intersection (one-shot). Default: false. */\n once?: boolean;\n}\n\n/**\n * Track whether the referenced element intersects the viewport. Useful for\n * lazy-loading images, \"load more\" sentinels, and animation triggers.\n *\n * @returns `IntersectionObserverEntry | null`. `null` until the first observation.\n */\nexport function useIntersectionObserver(\n ref: RefObject<Element | null>,\n options: UseIntersectionObserverOptions = {},\n): IntersectionObserverEntry | null {\n const [entry, setEntry] = useState<IntersectionObserverEntry | null>(null);\n const { once = false, root, rootMargin, threshold } = options;\n\n useEffect(() => {\n const target = ref.current;\n if (!target || typeof IntersectionObserver === \"undefined\") return;\n\n const observer = new IntersectionObserver(\n ([nextEntry]) => {\n if (!nextEntry) return;\n setEntry(nextEntry);\n if (once && nextEntry.isIntersecting) observer.unobserve(target);\n },\n { root, rootMargin, threshold },\n );\n\n observer.observe(target);\n return () => observer.disconnect();\n }, [ref, once, root, rootMargin, threshold]);\n\n return entry;\n}\n"],"mappings":"uBAcA,SAAgB,EACZ,EACA,EAA0C,CAAC,EACX,CAChC,GAAM,CAAC,EAAO,IAAA,EAAY,EAAA,SAAA,CAA2C,IAAI,EACnE,CAAE,OAAO,GAAO,OAAM,aAAY,aAAc,EAmBtD,OAjBA,EAAA,EAAA,UAAA,KAAgB,CACZ,IAAM,EAAS,EAAI,QACnB,GAAI,CAAC,GAAU,OAAO,qBAAyB,IAAa,OAE5D,IAAM,EAAW,IAAI,sBAChB,CAAC,KAAe,CACR,IACL,EAAS,CAAS,EACd,GAAQ,EAAU,gBAAgB,EAAS,UAAU,CAAM,EACnE,EACA,CAAE,OAAM,aAAY,WAAU,CAClC,EAGA,OADA,EAAS,QAAQ,CAAM,MACV,EAAS,WAAW,CACrC,EAAG,CAAC,EAAK,EAAM,EAAM,EAAY,CAAS,CAAC,EAEpC,CACX"}
@@ -1 +1 @@
1
- {"version":3,"file":"use-interval.cjs","names":[],"sources":["../../src/hooks/use-interval.ts"],"sourcesContent":["import { useEffect, useRef } from \"react\";\n\n/**\n * Reactive `setInterval`. Pass `null` as `delay` to pause. `fn` is stored in\n * a ref so inline callbacks don't reset the interval on every render.\n *\n * @example\n * useInterval(() => poll(), enabled ? 5000 : null);\n */\nexport function useInterval(fn: () => void, delay: number | null): void {\n const fnRef = useRef(fn);\n useEffect(() => {\n fnRef.current = fn;\n }, [fn]);\n\n useEffect(() => {\n if (delay === null) return;\n const id = setInterval(() => fnRef.current(), delay);\n return () => clearInterval(id);\n }, [delay]);\n}\n"],"mappings":"uBASA,SAAgB,EAAY,EAAgB,EAA4B,CACpE,IAAM,GAAA,EAAA,EAAA,OAAA,CAAe,CAAE,GACvB,EAAA,EAAA,UAAA,KAAgB,CACZ,EAAM,QAAU,CACpB,EAAG,CAAC,CAAE,CAAC,GAEP,EAAA,EAAA,UAAA,KAAgB,CACZ,GAAI,IAAU,KAAM,OACpB,IAAM,EAAK,gBAAkB,EAAM,QAAQ,EAAG,CAAK,EACnD,UAAa,cAAc,CAAE,CACjC,EAAG,CAAC,CAAK,CAAC,CACd"}
1
+ {"version":3,"file":"use-interval.cjs","names":[],"sources":["../../src/hooks/use-interval.ts"],"sourcesContent":["import { useEffect, useRef } from \"react\";\n\n/**\n * Reactive `setInterval`. Pass `null` as `delay` to pause. `fn` is stored in\n * a ref so inline callbacks don't reset the interval on every render.\n *\n * @example\n * useInterval(() => poll(), enabled ? 5000 : null);\n */\nexport function useInterval(fn: () => void, delay: number | null): void {\n const fnRef = useRef(fn);\n useEffect(() => {\n fnRef.current = fn;\n }, [fn]);\n\n useEffect(() => {\n if (delay === null) return;\n const id = setInterval(() => fnRef.current(), delay);\n return () => clearInterval(id);\n }, [delay]);\n}\n"],"mappings":"uBASA,SAAgB,EAAY,EAAgB,EAA4B,CACpE,IAAM,GAAA,EAAQ,EAAA,OAAA,CAAO,CAAE,GACvB,EAAA,EAAA,UAAA,KAAgB,CACZ,EAAM,QAAU,CACpB,EAAG,CAAC,CAAE,CAAC,GAEP,EAAA,EAAA,UAAA,KAAgB,CACZ,GAAI,IAAU,KAAM,OACpB,IAAM,EAAK,gBAAkB,EAAM,QAAQ,EAAG,CAAK,EACnD,UAAa,cAAc,CAAE,CACjC,EAAG,CAAC,CAAK,CAAC,CACd"}
@@ -1 +1 @@
1
- {"version":3,"file":"use-is-first-render.cjs","names":[],"sources":["../../src/hooks/use-is-first-render.ts"],"sourcesContent":["/* eslint-disable react-hooks/refs -- flipping the flag during render IS this hook */\nimport { useRef } from \"react\";\n\n/**\n * Return `true` on the first render of the component and `false` thereafter.\n *\n * Reads *and* flips a ref during render, which the React Compiler rules flag on\n * sight — here it is the entire hook. Under StrictMode's double render in dev the\n * second pass already sees `false`, so treat the result as \"first committed\n * render\" and not as a strict render counter.\n *\n * @returns Whether the current render is the first one.\n */\nexport function useIsFirstRender(): boolean {\n const isFirst = useRef<boolean>(true);\n if (isFirst.current) {\n isFirst.current = false;\n return true;\n }\n return false;\n}\n"],"mappings":"uBAaA,SAAgB,GAA4B,CACxC,IAAM,GAAA,EAAA,EAAA,OAAA,CAA0B,EAAI,EAKpC,OAJI,EAAQ,SACR,EAAQ,QAAU,GACX,IAEJ,EACX"}
1
+ {"version":3,"file":"use-is-first-render.cjs","names":[],"sources":["../../src/hooks/use-is-first-render.ts"],"sourcesContent":["/* eslint-disable react-hooks/refs -- flipping the flag during render IS this hook */\nimport { useRef } from \"react\";\n\n/**\n * Return `true` on the first render of the component and `false` thereafter.\n *\n * Reads *and* flips a ref during render, which the React Compiler rules flag on\n * sight — here it is the entire hook. Under StrictMode's double render in dev the\n * second pass already sees `false`, so treat the result as \"first committed\n * render\" and not as a strict render counter.\n *\n * @returns Whether the current render is the first one.\n */\nexport function useIsFirstRender(): boolean {\n const isFirst = useRef<boolean>(true);\n if (isFirst.current) {\n isFirst.current = false;\n return true;\n }\n return false;\n}\n"],"mappings":"uBAaA,SAAgB,GAA4B,CACxC,IAAM,GAAA,EAAU,EAAA,OAAA,CAAgB,EAAI,EAKpC,OAJI,EAAQ,SACR,EAAQ,QAAU,GACX,IAEJ,EACX"}
@@ -1,2 +1,2 @@
1
- let e=require("react");function t(e,t){if(e.key.toLowerCase()!==t.key.toLowerCase())return!1;let n=!!t.ctrl,r=!!t.meta,i=!!t.shift,a=!!t.alt;if(t.mod){if(!e.ctrlKey&&!e.metaKey)return!1}else if(e.ctrlKey!==n||e.metaKey!==r)return!1;return!(e.shiftKey!==i||e.altKey!==a)}function n(e){if(!(e instanceof HTMLElement))return!1;let t=e.tagName.toLowerCase();return t===`input`||t===`textarea`||t===`select`||e.isContentEditable}function r(r,i,a={}){let{disabled:o=!1,ignoreInput:s=!0}=a;(0,e.useEffect)(()=>{if(o||typeof window>`u`)return;let e=e=>{s&&n(e.target)||t(e,r)&&i(e)};return window.addEventListener(`keydown`,e),()=>window.removeEventListener(`keydown`,e)},[o,s,r.key,r.ctrl,r.meta,r.shift,r.alt,r.mod,i])}exports.useKeyboardShortcut=r;
1
+ let e=require("react");function t(e,t){if(e.key.toLowerCase()!==t.key.toLowerCase())return!1;let n=!!t.ctrl,r=!!t.meta,i=!!t.shift,a=!!t.alt;if(t.mod){if(!e.ctrlKey&&!e.metaKey)return!1}else if(e.ctrlKey!==n||e.metaKey!==r)return!1;return e.shiftKey===i&&e.altKey===a}function n(e){if(!(e instanceof HTMLElement))return!1;let t=e.tagName.toLowerCase();return t===`input`||t===`textarea`||t===`select`||e.isContentEditable}function r(r,i,a={}){let{disabled:o=!1,ignoreInput:s=!0}=a;(0,e.useEffect)(()=>{if(o||typeof window>`u`)return;let e=e=>{s&&n(e.target)||t(e,r)&&i(e)};return window.addEventListener(`keydown`,e),()=>window.removeEventListener(`keydown`,e)},[o,s,r.key,r.ctrl,r.meta,r.shift,r.alt,r.mod,i])}exports.useKeyboardShortcut=r;
2
2
  //# sourceMappingURL=use-keyboard-shortcut.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"use-keyboard-shortcut.cjs","names":[],"sources":["../../src/hooks/use-keyboard-shortcut.ts"],"sourcesContent":["import { useEffect } from \"react\";\n\nexport interface KeyboardShortcut {\n /** Key name (`\"k\"`, `\"Enter\"`, `\"Escape\"`, `\"ArrowDown\"`, etc.). Case-insensitive. */\n key: string;\n ctrl?: boolean;\n meta?: boolean;\n shift?: boolean;\n alt?: boolean;\n /** Match either Ctrl or Cmd. Useful for cross-OS shortcuts. */\n mod?: boolean;\n}\n\nexport interface UseKeyboardShortcutOptions {\n /** Disable the shortcut without unmounting. Default: false. */\n disabled?: boolean;\n /** Listen to the entire window. Pass `false` to scope to a specific element via the handler. Default: true. */\n global?: boolean;\n /** Suppress the event from firing inside `<input>`/`<textarea>`/`[contenteditable]`. Default: true. */\n ignoreInput?: boolean;\n}\n\nfunction matches(event: KeyboardEvent, shortcut: KeyboardShortcut): boolean {\n if (event.key.toLowerCase() !== shortcut.key.toLowerCase()) return false;\n const ctrl = !!shortcut.ctrl;\n const meta = !!shortcut.meta;\n const shift = !!shortcut.shift;\n const alt = !!shortcut.alt;\n if (shortcut.mod) {\n if (!event.ctrlKey && !event.metaKey) return false;\n } else {\n if (event.ctrlKey !== ctrl) return false;\n if (event.metaKey !== meta) return false;\n }\n if (event.shiftKey !== shift) return false;\n if (event.altKey !== alt) return false;\n return true;\n}\n\nfunction isEditable(target: EventTarget | null): boolean {\n if (!(target instanceof HTMLElement)) return false;\n const tag = target.tagName.toLowerCase();\n if (tag === \"input\" || tag === \"textarea\" || tag === \"select\") return true;\n return target.isContentEditable;\n}\n\n/**\n * Bind a global keyboard shortcut. Supports modifier combinations and a\n * cross-OS `mod` key (Ctrl on Windows/Linux, Cmd on macOS).\n *\n * The effect depends on the individual `shortcut` fields rather than on the\n * object itself: callers pass an inline literal (`{ key: \"k\", mod: true }`),\n * which is a fresh reference on every render and would otherwise tear down and\n * re-add the listener each time. `exhaustive-deps` is silenced for that line\n * because the destructured fields are the complete dependency set.\n *\n * @example\n * useKeyboardShortcut({ key: \"k\", mod: true }, () => openSearch());\n */\nexport function useKeyboardShortcut(\n shortcut: KeyboardShortcut,\n handler: (event: KeyboardEvent) => void,\n options: UseKeyboardShortcutOptions = {},\n): void {\n const { disabled = false, ignoreInput = true } = options;\n\n useEffect(() => {\n if (disabled || typeof window === \"undefined\") return;\n const listener = (event: KeyboardEvent): void => {\n if (ignoreInput && isEditable(event.target)) return;\n if (matches(event, shortcut)) handler(event);\n };\n window.addEventListener(\"keydown\", listener);\n return () => window.removeEventListener(\"keydown\", listener);\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [\n disabled,\n ignoreInput,\n shortcut.key,\n shortcut.ctrl,\n shortcut.meta,\n shortcut.shift,\n shortcut.alt,\n shortcut.mod,\n handler,\n ]);\n}\n"],"mappings":"uBAsBA,SAAS,EAAQ,EAAsB,EAAqC,CACxE,GAAI,EAAM,IAAI,YAAY,IAAM,EAAS,IAAI,YAAY,EAAG,MAAO,GACnE,IAAM,EAAO,CAAC,CAAC,EAAS,KAClB,EAAO,CAAC,CAAC,EAAS,KAClB,EAAQ,CAAC,CAAC,EAAS,MACnB,EAAM,CAAC,CAAC,EAAS,IACvB,GAAI,EAAS,QACL,CAAC,EAAM,SAAW,CAAC,EAAM,QAAS,MAAO,EAAA,MAG7C,GADI,EAAM,UAAY,GAClB,EAAM,UAAY,EAAM,MAAO,GAIvC,MADA,EADI,EAAM,WAAa,GACnB,EAAM,SAAW,EAEzB,CAEA,SAAS,EAAW,EAAqC,CACrD,GAAI,EAAE,aAAkB,aAAc,MAAO,GAC7C,IAAM,EAAM,EAAO,QAAQ,YAAY,EAEvC,OADI,IAAQ,SAAW,IAAQ,YAAc,IAAQ,UAC9C,EAAO,iBAClB,CAeA,SAAgB,EACZ,EACA,EACA,EAAsC,CAAC,EACnC,CACJ,GAAM,CAAE,WAAW,GAAO,cAAc,IAAS,GAEjD,EAAA,EAAA,UAAA,KAAgB,CACZ,GAAI,GAAY,OAAO,OAAW,IAAa,OAC/C,IAAM,EAAY,GAA+B,CACzC,GAAe,EAAW,EAAM,MAAM,GACtC,EAAQ,EAAO,CAAQ,GAAG,EAAQ,CAAK,CAC/C,EAEA,OADA,OAAO,iBAAiB,UAAW,CAAQ,MAC9B,OAAO,oBAAoB,UAAW,CAAQ,CAE/D,EAAG,CACC,EACA,EACA,EAAS,IACT,EAAS,KACT,EAAS,KACT,EAAS,MACT,EAAS,IACT,EAAS,IACT,CACJ,CAAC,CACL"}
1
+ {"version":3,"file":"use-keyboard-shortcut.cjs","names":[],"sources":["../../src/hooks/use-keyboard-shortcut.ts"],"sourcesContent":["import { useEffect } from \"react\";\n\nexport interface KeyboardShortcut {\n /** Key name (`\"k\"`, `\"Enter\"`, `\"Escape\"`, `\"ArrowDown\"`, etc.). Case-insensitive. */\n key: string;\n ctrl?: boolean;\n meta?: boolean;\n shift?: boolean;\n alt?: boolean;\n /** Match either Ctrl or Cmd. Useful for cross-OS shortcuts. */\n mod?: boolean;\n}\n\nexport interface UseKeyboardShortcutOptions {\n /** Disable the shortcut without unmounting. Default: false. */\n disabled?: boolean;\n /** Listen to the entire window. Pass `false` to scope to a specific element via the handler. Default: true. */\n global?: boolean;\n /** Suppress the event from firing inside `<input>`/`<textarea>`/`[contenteditable]`. Default: true. */\n ignoreInput?: boolean;\n}\n\nfunction matches(event: KeyboardEvent, shortcut: KeyboardShortcut): boolean {\n if (event.key.toLowerCase() !== shortcut.key.toLowerCase()) return false;\n const ctrl = !!shortcut.ctrl;\n const meta = !!shortcut.meta;\n const shift = !!shortcut.shift;\n const alt = !!shortcut.alt;\n if (shortcut.mod) {\n if (!event.ctrlKey && !event.metaKey) return false;\n } else {\n if (event.ctrlKey !== ctrl) return false;\n if (event.metaKey !== meta) return false;\n }\n if (event.shiftKey !== shift) return false;\n if (event.altKey !== alt) return false;\n return true;\n}\n\nfunction isEditable(target: EventTarget | null): boolean {\n if (!(target instanceof HTMLElement)) return false;\n const tag = target.tagName.toLowerCase();\n if (tag === \"input\" || tag === \"textarea\" || tag === \"select\") return true;\n return target.isContentEditable;\n}\n\n/**\n * Bind a global keyboard shortcut. Supports modifier combinations and a\n * cross-OS `mod` key (Ctrl on Windows/Linux, Cmd on macOS).\n *\n * The effect depends on the individual `shortcut` fields rather than on the\n * object itself: callers pass an inline literal (`{ key: \"k\", mod: true }`),\n * which is a fresh reference on every render and would otherwise tear down and\n * re-add the listener each time. `exhaustive-deps` is silenced for that line\n * because the destructured fields are the complete dependency set.\n *\n * @example\n * useKeyboardShortcut({ key: \"k\", mod: true }, () => openSearch());\n */\nexport function useKeyboardShortcut(\n shortcut: KeyboardShortcut,\n handler: (event: KeyboardEvent) => void,\n options: UseKeyboardShortcutOptions = {},\n): void {\n const { disabled = false, ignoreInput = true } = options;\n\n useEffect(() => {\n if (disabled || typeof window === \"undefined\") return;\n const listener = (event: KeyboardEvent): void => {\n if (ignoreInput && isEditable(event.target)) return;\n if (matches(event, shortcut)) handler(event);\n };\n window.addEventListener(\"keydown\", listener);\n return () => window.removeEventListener(\"keydown\", listener);\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [\n disabled,\n ignoreInput,\n shortcut.key,\n shortcut.ctrl,\n shortcut.meta,\n shortcut.shift,\n shortcut.alt,\n shortcut.mod,\n handler,\n ]);\n}\n"],"mappings":"uBAsBA,SAAS,EAAQ,EAAsB,EAAqC,CACxE,GAAI,EAAM,IAAI,YAAY,IAAM,EAAS,IAAI,YAAY,EAAG,MAAO,GACnE,IAAM,EAAO,CAAC,CAAC,EAAS,KAClB,EAAO,CAAC,CAAC,EAAS,KAClB,EAAQ,CAAC,CAAC,EAAS,MACnB,EAAM,CAAC,CAAC,EAAS,IACvB,GAAI,EAAS,IACL,IAAA,CAAC,EAAM,SAAW,CAAC,EAAM,QAAS,MAAO,EAAA,MAG7C,GADI,EAAM,UAAY,GAClB,EAAM,UAAY,EAAM,MAAO,GAIvC,OAFI,EAAM,WAAa,GACnB,EAAM,SAAW,CAEzB,CAEA,SAAS,EAAW,EAAqC,CACrD,GAAI,EAAE,aAAkB,aAAc,MAAO,GAC7C,IAAM,EAAM,EAAO,QAAQ,YAAY,EAEvC,OADI,IAAQ,SAAW,IAAQ,YAAc,IAAQ,UAC9C,EAAO,iBAClB,CAeA,SAAgB,EACZ,EACA,EACA,EAAsC,CAAC,EACnC,CACJ,GAAM,CAAE,WAAW,GAAO,cAAc,IAAS,GAEjD,EAAA,EAAA,UAAA,KAAgB,CACZ,GAAI,GAAY,OAAO,OAAW,IAAa,OAC/C,IAAM,EAAY,GAA+B,CACzC,GAAe,EAAW,EAAM,MAAM,GACtC,EAAQ,EAAO,CAAQ,GAAG,EAAQ,CAAK,CAC/C,EAEA,OADA,OAAO,iBAAiB,UAAW,CAAQ,MAC9B,OAAO,oBAAoB,UAAW,CAAQ,CAE/D,EAAG,CACC,EACA,EACA,EAAS,IACT,EAAS,KACT,EAAS,KACT,EAAS,MACT,EAAS,IACT,EAAS,IACT,CACJ,CAAC,CACL"}
@@ -6,7 +6,7 @@ function t(e, t) {
6
6
  if (t.mod) {
7
7
  if (!e.ctrlKey && !e.metaKey) return !1;
8
8
  } else if (e.ctrlKey !== n || e.metaKey !== r) return !1;
9
- return !(e.shiftKey !== i || e.altKey !== a);
9
+ return e.shiftKey === i && e.altKey === a;
10
10
  }
11
11
  function n(e) {
12
12
  if (!(e instanceof HTMLElement)) return !1;
@@ -1 +1 @@
1
- {"version":3,"file":"use-keyboard-shortcut.js","names":[],"sources":["../../src/hooks/use-keyboard-shortcut.ts"],"sourcesContent":["import { useEffect } from \"react\";\n\nexport interface KeyboardShortcut {\n /** Key name (`\"k\"`, `\"Enter\"`, `\"Escape\"`, `\"ArrowDown\"`, etc.). Case-insensitive. */\n key: string;\n ctrl?: boolean;\n meta?: boolean;\n shift?: boolean;\n alt?: boolean;\n /** Match either Ctrl or Cmd. Useful for cross-OS shortcuts. */\n mod?: boolean;\n}\n\nexport interface UseKeyboardShortcutOptions {\n /** Disable the shortcut without unmounting. Default: false. */\n disabled?: boolean;\n /** Listen to the entire window. Pass `false` to scope to a specific element via the handler. Default: true. */\n global?: boolean;\n /** Suppress the event from firing inside `<input>`/`<textarea>`/`[contenteditable]`. Default: true. */\n ignoreInput?: boolean;\n}\n\nfunction matches(event: KeyboardEvent, shortcut: KeyboardShortcut): boolean {\n if (event.key.toLowerCase() !== shortcut.key.toLowerCase()) return false;\n const ctrl = !!shortcut.ctrl;\n const meta = !!shortcut.meta;\n const shift = !!shortcut.shift;\n const alt = !!shortcut.alt;\n if (shortcut.mod) {\n if (!event.ctrlKey && !event.metaKey) return false;\n } else {\n if (event.ctrlKey !== ctrl) return false;\n if (event.metaKey !== meta) return false;\n }\n if (event.shiftKey !== shift) return false;\n if (event.altKey !== alt) return false;\n return true;\n}\n\nfunction isEditable(target: EventTarget | null): boolean {\n if (!(target instanceof HTMLElement)) return false;\n const tag = target.tagName.toLowerCase();\n if (tag === \"input\" || tag === \"textarea\" || tag === \"select\") return true;\n return target.isContentEditable;\n}\n\n/**\n * Bind a global keyboard shortcut. Supports modifier combinations and a\n * cross-OS `mod` key (Ctrl on Windows/Linux, Cmd on macOS).\n *\n * The effect depends on the individual `shortcut` fields rather than on the\n * object itself: callers pass an inline literal (`{ key: \"k\", mod: true }`),\n * which is a fresh reference on every render and would otherwise tear down and\n * re-add the listener each time. `exhaustive-deps` is silenced for that line\n * because the destructured fields are the complete dependency set.\n *\n * @example\n * useKeyboardShortcut({ key: \"k\", mod: true }, () => openSearch());\n */\nexport function useKeyboardShortcut(\n shortcut: KeyboardShortcut,\n handler: (event: KeyboardEvent) => void,\n options: UseKeyboardShortcutOptions = {},\n): void {\n const { disabled = false, ignoreInput = true } = options;\n\n useEffect(() => {\n if (disabled || typeof window === \"undefined\") return;\n const listener = (event: KeyboardEvent): void => {\n if (ignoreInput && isEditable(event.target)) return;\n if (matches(event, shortcut)) handler(event);\n };\n window.addEventListener(\"keydown\", listener);\n return () => window.removeEventListener(\"keydown\", listener);\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [\n disabled,\n ignoreInput,\n shortcut.key,\n shortcut.ctrl,\n shortcut.meta,\n shortcut.shift,\n shortcut.alt,\n shortcut.mod,\n handler,\n ]);\n}\n"],"mappings":";;AAsBA,SAAS,EAAQ,GAAsB,GAAqC;CACxE,IAAI,EAAM,IAAI,YAAY,MAAM,EAAS,IAAI,YAAY,GAAG,OAAO;CACnE,IAAM,IAAO,CAAC,CAAC,EAAS,MAClB,IAAO,CAAC,CAAC,EAAS,MAClB,IAAQ,CAAC,CAAC,EAAS,OACnB,IAAM,CAAC,CAAC,EAAS;CACvB,IAAI,EAAS;MACL,CAAC,EAAM,WAAW,CAAC,EAAM,SAAS,OAAO;CAAA,OAG7C,IADI,EAAM,YAAY,KAClB,EAAM,YAAY,GAAM,OAAO;CAIvC,OADA,EADI,EAAM,aAAa,KACnB,EAAM,WAAW;AAEzB;AAEA,SAAS,EAAW,GAAqC;CACrD,IAAI,EAAE,aAAkB,cAAc,OAAO;CAC7C,IAAM,IAAM,EAAO,QAAQ,YAAY;CAEvC,OADI,MAAQ,WAAW,MAAQ,cAAc,MAAQ,YAC9C,EAAO;AAClB;AAeA,SAAgB,EACZ,GACA,GACA,IAAsC,CAAC,GACnC;CACJ,IAAM,EAAE,cAAW,IAAO,iBAAc,OAAS;CAEjD,QAAgB;EACZ,IAAI,KAAY,OAAO,SAAW,KAAa;EAC/C,IAAM,KAAY,MAA+B;GACzC,KAAe,EAAW,EAAM,MAAM,KACtC,EAAQ,GAAO,CAAQ,KAAG,EAAQ,CAAK;EAC/C;EAEA,OADA,OAAO,iBAAiB,WAAW,CAAQ,SAC9B,OAAO,oBAAoB,WAAW,CAAQ;CAE/D,GAAG;EACC;EACA;EACA,EAAS;EACT,EAAS;EACT,EAAS;EACT,EAAS;EACT,EAAS;EACT,EAAS;EACT;CACJ,CAAC;AACL"}
1
+ {"version":3,"file":"use-keyboard-shortcut.js","names":[],"sources":["../../src/hooks/use-keyboard-shortcut.ts"],"sourcesContent":["import { useEffect } from \"react\";\n\nexport interface KeyboardShortcut {\n /** Key name (`\"k\"`, `\"Enter\"`, `\"Escape\"`, `\"ArrowDown\"`, etc.). Case-insensitive. */\n key: string;\n ctrl?: boolean;\n meta?: boolean;\n shift?: boolean;\n alt?: boolean;\n /** Match either Ctrl or Cmd. Useful for cross-OS shortcuts. */\n mod?: boolean;\n}\n\nexport interface UseKeyboardShortcutOptions {\n /** Disable the shortcut without unmounting. Default: false. */\n disabled?: boolean;\n /** Listen to the entire window. Pass `false` to scope to a specific element via the handler. Default: true. */\n global?: boolean;\n /** Suppress the event from firing inside `<input>`/`<textarea>`/`[contenteditable]`. Default: true. */\n ignoreInput?: boolean;\n}\n\nfunction matches(event: KeyboardEvent, shortcut: KeyboardShortcut): boolean {\n if (event.key.toLowerCase() !== shortcut.key.toLowerCase()) return false;\n const ctrl = !!shortcut.ctrl;\n const meta = !!shortcut.meta;\n const shift = !!shortcut.shift;\n const alt = !!shortcut.alt;\n if (shortcut.mod) {\n if (!event.ctrlKey && !event.metaKey) return false;\n } else {\n if (event.ctrlKey !== ctrl) return false;\n if (event.metaKey !== meta) return false;\n }\n if (event.shiftKey !== shift) return false;\n if (event.altKey !== alt) return false;\n return true;\n}\n\nfunction isEditable(target: EventTarget | null): boolean {\n if (!(target instanceof HTMLElement)) return false;\n const tag = target.tagName.toLowerCase();\n if (tag === \"input\" || tag === \"textarea\" || tag === \"select\") return true;\n return target.isContentEditable;\n}\n\n/**\n * Bind a global keyboard shortcut. Supports modifier combinations and a\n * cross-OS `mod` key (Ctrl on Windows/Linux, Cmd on macOS).\n *\n * The effect depends on the individual `shortcut` fields rather than on the\n * object itself: callers pass an inline literal (`{ key: \"k\", mod: true }`),\n * which is a fresh reference on every render and would otherwise tear down and\n * re-add the listener each time. `exhaustive-deps` is silenced for that line\n * because the destructured fields are the complete dependency set.\n *\n * @example\n * useKeyboardShortcut({ key: \"k\", mod: true }, () => openSearch());\n */\nexport function useKeyboardShortcut(\n shortcut: KeyboardShortcut,\n handler: (event: KeyboardEvent) => void,\n options: UseKeyboardShortcutOptions = {},\n): void {\n const { disabled = false, ignoreInput = true } = options;\n\n useEffect(() => {\n if (disabled || typeof window === \"undefined\") return;\n const listener = (event: KeyboardEvent): void => {\n if (ignoreInput && isEditable(event.target)) return;\n if (matches(event, shortcut)) handler(event);\n };\n window.addEventListener(\"keydown\", listener);\n return () => window.removeEventListener(\"keydown\", listener);\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [\n disabled,\n ignoreInput,\n shortcut.key,\n shortcut.ctrl,\n shortcut.meta,\n shortcut.shift,\n shortcut.alt,\n shortcut.mod,\n handler,\n ]);\n}\n"],"mappings":";;AAsBA,SAAS,EAAQ,GAAsB,GAAqC;CACxE,IAAI,EAAM,IAAI,YAAY,MAAM,EAAS,IAAI,YAAY,GAAG,OAAO;CACnE,IAAM,IAAO,CAAC,CAAC,EAAS,MAClB,IAAO,CAAC,CAAC,EAAS,MAClB,IAAQ,CAAC,CAAC,EAAS,OACnB,IAAM,CAAC,CAAC,EAAS;CACvB,IAAI,EAAS,KACL;MAAA,CAAC,EAAM,WAAW,CAAC,EAAM,SAAS,OAAO;CAAA,OAG7C,IADI,EAAM,YAAY,KAClB,EAAM,YAAY,GAAM,OAAO;CAIvC,OAFI,EAAM,aAAa,KACnB,EAAM,WAAW;AAEzB;AAEA,SAAS,EAAW,GAAqC;CACrD,IAAI,EAAE,aAAkB,cAAc,OAAO;CAC7C,IAAM,IAAM,EAAO,QAAQ,YAAY;CAEvC,OADI,MAAQ,WAAW,MAAQ,cAAc,MAAQ,YAC9C,EAAO;AAClB;AAeA,SAAgB,EACZ,GACA,GACA,IAAsC,CAAC,GACnC;CACJ,IAAM,EAAE,cAAW,IAAO,iBAAc,OAAS;CAEjD,QAAgB;EACZ,IAAI,KAAY,OAAO,SAAW,KAAa;EAC/C,IAAM,KAAY,MAA+B;GACzC,KAAe,EAAW,EAAM,MAAM,KACtC,EAAQ,GAAO,CAAQ,KAAG,EAAQ,CAAK;EAC/C;EAEA,OADA,OAAO,iBAAiB,WAAW,CAAQ,SAC9B,OAAO,oBAAoB,WAAW,CAAQ;CAE/D,GAAG;EACC;EACA;EACA,EAAS;EACT,EAAS;EACT,EAAS;EACT,EAAS;EACT,EAAS;EACT,EAAS;EACT;CACJ,CAAC;AACL"}