tempest-react-sdk 0.38.3 → 0.39.1

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 (519) hide show
  1. package/README.md +1 -1
  2. package/dist/_virtual/_rolldown/runtime.cjs +1 -1
  3. package/dist/access/access-control-context.cjs.map +1 -1
  4. package/dist/access/use-can.cjs.map +1 -1
  5. package/dist/app/AppProviders.cjs.map +1 -1
  6. package/dist/app/AppProviders.js.map +1 -1
  7. package/dist/audio/use-audio-recorder.cjs.map +1 -1
  8. package/dist/audio/use-audio.cjs.map +1 -1
  9. package/dist/audio/use-microphone.cjs.map +1 -1
  10. package/dist/auth/create-auth-store.cjs.map +1 -1
  11. package/dist/auth/lazy-with-retry.cjs.map +1 -1
  12. package/dist/auth/use-passkey.cjs.map +1 -1
  13. package/dist/br/BrazilMap.cjs +1 -1
  14. package/dist/br/BrazilMap.cjs.map +1 -1
  15. package/dist/br/BrazilMap.js +2 -2
  16. package/dist/br/BrazilMap.js.map +1 -1
  17. package/dist/br/BrazilStateCitySelect.cjs.map +1 -1
  18. package/dist/br/BrazilStateCitySelect.js.map +1 -1
  19. package/dist/br/BrazilStateMap.cjs +1 -1
  20. package/dist/br/BrazilStateMap.cjs.map +1 -1
  21. package/dist/br/BrazilStateMap.js +2 -2
  22. package/dist/br/BrazilStateMap.js.map +1 -1
  23. package/dist/br/MapLegend.cjs.map +1 -1
  24. package/dist/br/MapLegend.js.map +1 -1
  25. package/dist/br/MapMarkers.cjs.map +1 -1
  26. package/dist/br/MapMarkers.js.map +1 -1
  27. package/dist/br/MunicipalitySearch.cjs.map +1 -1
  28. package/dist/br/MunicipalitySearch.js.map +1 -1
  29. package/dist/br/PixQRCode.cjs.map +1 -1
  30. package/dist/br/PixQRCode.js.map +1 -1
  31. package/dist/br/use-map-hover.cjs.map +1 -1
  32. package/dist/br/use-map-zoom.cjs.map +1 -1
  33. package/dist/capture/media-recorder.cjs +1 -1
  34. package/dist/capture/media-recorder.js +2 -2
  35. package/dist/capture/use-barcode-scanner.cjs +1 -1
  36. package/dist/capture/use-barcode-scanner.cjs.map +1 -1
  37. package/dist/capture/use-barcode-scanner.js +2 -2
  38. package/dist/capture/use-barcode-scanner.js.map +1 -1
  39. package/dist/capture/use-screen-capture.cjs.map +1 -1
  40. package/dist/capture/use-speech-recognition.cjs.map +1 -1
  41. package/dist/capture/use-torch.cjs.map +1 -1
  42. package/dist/capture/use-video-recorder.cjs.map +1 -1
  43. package/dist/charts/AreaChart.cjs.map +1 -1
  44. package/dist/charts/AreaChart.js.map +1 -1
  45. package/dist/charts/BarChart.cjs.map +1 -1
  46. package/dist/charts/BarChart.js.map +1 -1
  47. package/dist/charts/LineChart.cjs.map +1 -1
  48. package/dist/charts/LineChart.js.map +1 -1
  49. package/dist/charts/PieChart.cjs.map +1 -1
  50. package/dist/charts/PieChart.js.map +1 -1
  51. package/dist/charts/RadarChart.cjs.map +1 -1
  52. package/dist/charts/RadarChart.js.map +1 -1
  53. package/dist/charts/use-chart-colors.cjs.map +1 -1
  54. package/dist/components/AIChat/AIChat.cjs.map +1 -1
  55. package/dist/components/AIChat/AIChat.js.map +1 -1
  56. package/dist/components/AIChat/AIChatComposer.cjs.map +1 -1
  57. package/dist/components/AIChat/AIChatComposer.js.map +1 -1
  58. package/dist/components/AIChat/AIChatTurn.cjs.map +1 -1
  59. package/dist/components/AIChat/AIChatTurn.js.map +1 -1
  60. package/dist/components/Accordion/Accordion.cjs.map +1 -1
  61. package/dist/components/Accordion/Accordion.js.map +1 -1
  62. package/dist/components/Alert/Alert.cjs.map +1 -1
  63. package/dist/components/Alert/Alert.js.map +1 -1
  64. package/dist/components/AppBar/AppBar.cjs.map +1 -1
  65. package/dist/components/AppBar/AppBar.js.map +1 -1
  66. package/dist/components/AppShell/AppShell.cjs.map +1 -1
  67. package/dist/components/AppShell/AppShell.js.map +1 -1
  68. package/dist/components/AudioPlayer/AudioPlayer.cjs.map +1 -1
  69. package/dist/components/AudioPlayer/AudioPlayer.js.map +1 -1
  70. package/dist/components/AudioRecorder/AudioRecorder.cjs.map +1 -1
  71. package/dist/components/AudioRecorder/AudioRecorder.js.map +1 -1
  72. package/dist/components/Avatar/Avatar.cjs.map +1 -1
  73. package/dist/components/Avatar/Avatar.js.map +1 -1
  74. package/dist/components/AvatarGroup/AvatarGroup.cjs.map +1 -1
  75. package/dist/components/AvatarGroup/AvatarGroup.js.map +1 -1
  76. package/dist/components/Badge/Badge.cjs.map +1 -1
  77. package/dist/components/Badge/Badge.js.map +1 -1
  78. package/dist/components/Banner/Banner.cjs.map +1 -1
  79. package/dist/components/Banner/Banner.js.map +1 -1
  80. package/dist/components/BarcodeScanner/BarcodeScanner.cjs.map +1 -1
  81. package/dist/components/BarcodeScanner/BarcodeScanner.js.map +1 -1
  82. package/dist/components/BottomNavigation/BottomNavigation.cjs.map +1 -1
  83. package/dist/components/BottomNavigation/BottomNavigation.js.map +1 -1
  84. package/dist/components/BottomSheet/BottomSheet.cjs.map +1 -1
  85. package/dist/components/BottomSheet/BottomSheet.js.map +1 -1
  86. package/dist/components/Breadcrumbs/Breadcrumbs.cjs.map +1 -1
  87. package/dist/components/Breadcrumbs/Breadcrumbs.js.map +1 -1
  88. package/dist/components/Button/Button.cjs.map +1 -1
  89. package/dist/components/Button/Button.js.map +1 -1
  90. package/dist/components/Calendar/Calendar.cjs +1 -1
  91. package/dist/components/Calendar/Calendar.cjs.map +1 -1
  92. package/dist/components/Calendar/Calendar.js +38 -41
  93. package/dist/components/Calendar/Calendar.js.map +1 -1
  94. package/dist/components/Card/Card.cjs.map +1 -1
  95. package/dist/components/Card/Card.js.map +1 -1
  96. package/dist/components/Carousel/Carousel.cjs.map +1 -1
  97. package/dist/components/Carousel/Carousel.js.map +1 -1
  98. package/dist/components/Chat/Chat.cjs.map +1 -1
  99. package/dist/components/Chat/Chat.js.map +1 -1
  100. package/dist/components/Chat/ChatComposer.cjs.map +1 -1
  101. package/dist/components/Chat/ChatComposer.js.map +1 -1
  102. package/dist/components/Chat/chat-groups.cjs +1 -1
  103. package/dist/components/Chat/chat-groups.cjs.map +1 -1
  104. package/dist/components/Chat/chat-groups.js +2 -2
  105. package/dist/components/Chat/chat-groups.js.map +1 -1
  106. package/dist/components/Checkbox/Checkbox.cjs.map +1 -1
  107. package/dist/components/Checkbox/Checkbox.js.map +1 -1
  108. package/dist/components/ChipInput/ChipInput.cjs.map +1 -1
  109. package/dist/components/ChipInput/ChipInput.js.map +1 -1
  110. package/dist/components/ClickOutside/ClickOutside.cjs.map +1 -1
  111. package/dist/components/CodeBlock/CodeBlock.cjs.map +1 -1
  112. package/dist/components/CodeBlock/CodeBlock.js.map +1 -1
  113. package/dist/components/Collapsible/Collapsible.cjs.map +1 -1
  114. package/dist/components/Collapsible/Collapsible.js.map +1 -1
  115. package/dist/components/Combobox/Combobox.cjs.map +1 -1
  116. package/dist/components/Combobox/Combobox.js.map +1 -1
  117. package/dist/components/Command/Command.cjs.map +1 -1
  118. package/dist/components/Command/Command.js.map +1 -1
  119. package/dist/components/ConfirmDialog/ConfirmDialog.cjs.map +1 -1
  120. package/dist/components/ConfirmDialog/ConfirmDialog.js.map +1 -1
  121. package/dist/components/ContextMenu/ContextMenu.cjs.map +1 -1
  122. package/dist/components/ContextMenu/ContextMenu.js.map +1 -1
  123. package/dist/components/CopyButton/CopyButton.cjs.map +1 -1
  124. package/dist/components/CopyButton/CopyButton.js.map +1 -1
  125. package/dist/components/DataList/DataList.cjs.map +1 -1
  126. package/dist/components/DataList/DataList.js.map +1 -1
  127. package/dist/components/DataTable/DataTable.cjs +1 -1
  128. package/dist/components/DataTable/DataTable.cjs.map +1 -1
  129. package/dist/components/DataTable/DataTable.js +2 -2
  130. package/dist/components/DataTable/DataTable.js.map +1 -1
  131. package/dist/components/DataTable/EditableCell.cjs.map +1 -1
  132. package/dist/components/DataTable/EditableCell.js.map +1 -1
  133. package/dist/components/DatePicker/DatePicker.cjs.map +1 -1
  134. package/dist/components/DateRangePicker/DateRangePicker.cjs.map +1 -1
  135. package/dist/components/DateRangePicker/DateRangePicker.js.map +1 -1
  136. package/dist/components/DescriptionList/DescriptionList.cjs.map +1 -1
  137. package/dist/components/DescriptionList/DescriptionList.js.map +1 -1
  138. package/dist/components/Divider/Divider.cjs.map +1 -1
  139. package/dist/components/Divider/Divider.js.map +1 -1
  140. package/dist/components/Drawer/Drawer.cjs.map +1 -1
  141. package/dist/components/Drawer/Drawer.js.map +1 -1
  142. package/dist/components/DropdownMenu/DropdownMenu.cjs.map +1 -1
  143. package/dist/components/DropdownMenu/DropdownMenu.js.map +1 -1
  144. package/dist/components/Dropzone/Dropzone.cjs.map +1 -1
  145. package/dist/components/Dropzone/Dropzone.js.map +1 -1
  146. package/dist/components/EmptyState/EmptyState.cjs.map +1 -1
  147. package/dist/components/EmptyState/EmptyState.js.map +1 -1
  148. package/dist/components/ErrorState/ErrorState.cjs.map +1 -1
  149. package/dist/components/ErrorState/ErrorState.js.map +1 -1
  150. package/dist/components/FileUpload/FileUpload.cjs +1 -1
  151. package/dist/components/FileUpload/FileUpload.cjs.map +1 -1
  152. package/dist/components/FileUpload/FileUpload.js +1 -1
  153. package/dist/components/FileUpload/FileUpload.js.map +1 -1
  154. package/dist/components/FilterBar/FilterBar.cjs.map +1 -1
  155. package/dist/components/FilterBar/FilterBar.js.map +1 -1
  156. package/dist/components/FloatingActionButton/FloatingActionButton.cjs.map +1 -1
  157. package/dist/components/FloatingActionButton/FloatingActionButton.js.map +1 -1
  158. package/dist/components/Form/Form.cjs.map +1 -1
  159. package/dist/components/Form/Form.js.map +1 -1
  160. package/dist/components/HoverCard/HoverCard.cjs.map +1 -1
  161. package/dist/components/HoverCard/HoverCard.js.map +1 -1
  162. package/dist/components/Image/Image.cjs.map +1 -1
  163. package/dist/components/ImageCropper/ImageCropper.cjs +1 -1
  164. package/dist/components/ImageCropper/ImageCropper.cjs.map +1 -1
  165. package/dist/components/ImageCropper/ImageCropper.js +1 -1
  166. package/dist/components/ImageCropper/ImageCropper.js.map +1 -1
  167. package/dist/components/Input/Input.cjs.map +1 -1
  168. package/dist/components/Input/Input.js.map +1 -1
  169. package/dist/components/InstallBanner/InstallBanner.cjs.map +1 -1
  170. package/dist/components/InstallBanner/InstallBanner.js.map +1 -1
  171. package/dist/components/InstallButton/InstallButton.cjs.map +1 -1
  172. package/dist/components/InstallButton/InstallButton.js.map +1 -1
  173. package/dist/components/Kanban/Kanban.cjs.map +1 -1
  174. package/dist/components/Kanban/Kanban.js.map +1 -1
  175. package/dist/components/Label/Label.cjs.map +1 -1
  176. package/dist/components/Label/Label.js.map +1 -1
  177. package/dist/components/Lightbox/Lightbox.cjs.map +1 -1
  178. package/dist/components/Lightbox/Lightbox.js.map +1 -1
  179. package/dist/components/ListTile/ListTile.cjs.map +1 -1
  180. package/dist/components/ListTile/ListTile.js.map +1 -1
  181. package/dist/components/Markdown/Markdown.cjs +1 -1
  182. package/dist/components/Markdown/Markdown.cjs.map +1 -1
  183. package/dist/components/Markdown/Markdown.js +2 -2
  184. package/dist/components/Markdown/Markdown.js.map +1 -1
  185. package/dist/components/Masonry/Masonry.cjs.map +1 -1
  186. package/dist/components/Masonry/Masonry.js.map +1 -1
  187. package/dist/components/Menubar/Menubar.cjs.map +1 -1
  188. package/dist/components/Menubar/Menubar.js.map +1 -1
  189. package/dist/components/Modal/Modal.cjs.map +1 -1
  190. package/dist/components/Modal/Modal.js.map +1 -1
  191. package/dist/components/ModalsManager/ModalsManager.cjs.map +1 -1
  192. package/dist/components/ModalsManager/ModalsManager.js.map +1 -1
  193. package/dist/components/Money/Money.cjs.map +1 -1
  194. package/dist/components/Money/Money.js.map +1 -1
  195. package/dist/components/MultiSelect/MultiSelect.cjs.map +1 -1
  196. package/dist/components/MultiSelect/MultiSelect.js.map +1 -1
  197. package/dist/components/NProgress/NProgress.cjs.map +1 -1
  198. package/dist/components/Navbar/Navbar.cjs.map +1 -1
  199. package/dist/components/Navbar/Navbar.js.map +1 -1
  200. package/dist/components/NavigationMenu/NavigationMenu.cjs.map +1 -1
  201. package/dist/components/NavigationMenu/NavigationMenu.js.map +1 -1
  202. package/dist/components/NavigationRail/NavigationRail.cjs.map +1 -1
  203. package/dist/components/NavigationRail/NavigationRail.js.map +1 -1
  204. package/dist/components/NotificationCenter/NotificationCenter.cjs.map +1 -1
  205. package/dist/components/NotificationCenter/NotificationCenter.js.map +1 -1
  206. package/dist/components/NotificationCenter/use-notification-inbox.cjs.map +1 -1
  207. package/dist/components/OfflineIndicator/OfflineIndicator.cjs.map +1 -1
  208. package/dist/components/OfflineIndicator/OfflineIndicator.js.map +1 -1
  209. package/dist/components/Page/Page.cjs.map +1 -1
  210. package/dist/components/Page/Page.js.map +1 -1
  211. package/dist/components/Pagination/Pagination.cjs.map +1 -1
  212. package/dist/components/Pagination/Pagination.js.map +1 -1
  213. package/dist/components/PasswordInput/PasswordInput.cjs.map +1 -1
  214. package/dist/components/PasswordInput/PasswordInput.js.map +1 -1
  215. package/dist/components/PinInput/PinInput.cjs.map +1 -1
  216. package/dist/components/PinInput/PinInput.js.map +1 -1
  217. package/dist/components/Popover/Popover.cjs.map +1 -1
  218. package/dist/components/Popover/Popover.js.map +1 -1
  219. package/dist/components/Portal/Portal.cjs.map +1 -1
  220. package/dist/components/Progress/Progress.cjs.map +1 -1
  221. package/dist/components/Progress/Progress.js.map +1 -1
  222. package/dist/components/QRCode/QRCode.cjs.map +1 -1
  223. package/dist/components/QRCode/QRCode.js.map +1 -1
  224. package/dist/components/Radio/Radio.cjs.map +1 -1
  225. package/dist/components/Radio/Radio.js.map +1 -1
  226. package/dist/components/RangeSlider/RangeSlider.cjs.map +1 -1
  227. package/dist/components/RangeSlider/RangeSlider.js.map +1 -1
  228. package/dist/components/RatingStars/RatingStars.cjs.map +1 -1
  229. package/dist/components/RatingStars/RatingStars.js.map +1 -1
  230. package/dist/components/RefreshIndicator/RefreshIndicator.cjs.map +1 -1
  231. package/dist/components/RefreshIndicator/RefreshIndicator.js.map +1 -1
  232. package/dist/components/RelativeTime/RelativeTime.cjs.map +1 -1
  233. package/dist/components/RelativeTime/RelativeTime.js.map +1 -1
  234. package/dist/components/Resizable/Resizable.cjs.map +1 -1
  235. package/dist/components/Resizable/Resizable.js.map +1 -1
  236. package/dist/components/Scheduler/Scheduler.cjs.map +1 -1
  237. package/dist/components/Scheduler/Scheduler.js.map +1 -1
  238. package/dist/components/ScrollArea/ScrollArea.cjs.map +1 -1
  239. package/dist/components/SearchBar/SearchBar.cjs.map +1 -1
  240. package/dist/components/SearchBar/SearchBar.js.map +1 -1
  241. package/dist/components/SegmentedControl/SegmentedControl.cjs.map +1 -1
  242. package/dist/components/SegmentedControl/SegmentedControl.js.map +1 -1
  243. package/dist/components/Select/Select.cjs.map +1 -1
  244. package/dist/components/Select/Select.js.map +1 -1
  245. package/dist/components/Sidebar/Sidebar.cjs.map +1 -1
  246. package/dist/components/Sidebar/Sidebar.js.map +1 -1
  247. package/dist/components/SignaturePad/SignaturePad.cjs.map +1 -1
  248. package/dist/components/SignaturePad/SignaturePad.js.map +1 -1
  249. package/dist/components/Slider/Slider.cjs.map +1 -1
  250. package/dist/components/Slider/Slider.js.map +1 -1
  251. package/dist/components/Sparkline/Sparkline.cjs.map +1 -1
  252. package/dist/components/Sparkline/Sparkline.js.map +1 -1
  253. package/dist/components/Spinner/Spinner.cjs.map +1 -1
  254. package/dist/components/Spinner/Spinner.js.map +1 -1
  255. package/dist/components/Stat/Stat.cjs.map +1 -1
  256. package/dist/components/Stat/Stat.js.map +1 -1
  257. package/dist/components/Stepper/Stepper.cjs.map +1 -1
  258. package/dist/components/Stepper/Stepper.js.map +1 -1
  259. package/dist/components/StepperInput/StepperInput.cjs.map +1 -1
  260. package/dist/components/StepperInput/StepperInput.js.map +1 -1
  261. package/dist/components/Switch/Switch.cjs.map +1 -1
  262. package/dist/components/Switch/Switch.js.map +1 -1
  263. package/dist/components/SyncStatusBadge/SyncStatusBadge.cjs.map +1 -1
  264. package/dist/components/SyncStatusBadge/SyncStatusBadge.js.map +1 -1
  265. package/dist/components/Table/Table.cjs.map +1 -1
  266. package/dist/components/Table/Table.js.map +1 -1
  267. package/dist/components/Tabs/Tabs.cjs.map +1 -1
  268. package/dist/components/Tabs/Tabs.js.map +1 -1
  269. package/dist/components/Tag/Tag.cjs.map +1 -1
  270. package/dist/components/Tag/Tag.js.map +1 -1
  271. package/dist/components/Textarea/Textarea.cjs.map +1 -1
  272. package/dist/components/Textarea/Textarea.js.map +1 -1
  273. package/dist/components/TimePicker/TimePicker.cjs.map +1 -1
  274. package/dist/components/TimePicker/TimePicker.js.map +1 -1
  275. package/dist/components/Timeline/Timeline.cjs.map +1 -1
  276. package/dist/components/Timeline/Timeline.js.map +1 -1
  277. package/dist/components/Toast/ToastProvider.cjs.map +1 -1
  278. package/dist/components/Toast/ToastProvider.js.map +1 -1
  279. package/dist/components/Toggle/Toggle.cjs.map +1 -1
  280. package/dist/components/ToggleGroup/ToggleGroup.cjs.map +1 -1
  281. package/dist/components/ToggleGroup/ToggleGroup.js.map +1 -1
  282. package/dist/components/Tooltip/Tooltip.cjs.map +1 -1
  283. package/dist/components/Tooltip/Tooltip.js.map +1 -1
  284. package/dist/components/Tour/Tour.cjs.map +1 -1
  285. package/dist/components/Tour/Tour.js.map +1 -1
  286. package/dist/components/Transfer/Transfer.cjs.map +1 -1
  287. package/dist/components/Transfer/Transfer.js.map +1 -1
  288. package/dist/components/TreeView/TreeView.cjs +1 -1
  289. package/dist/components/TreeView/TreeView.cjs.map +1 -1
  290. package/dist/components/TreeView/TreeView.js +1 -4
  291. package/dist/components/TreeView/TreeView.js.map +1 -1
  292. package/dist/components/UpdatePrompt/UpdatePrompt.cjs.map +1 -1
  293. package/dist/components/UpdatePrompt/UpdatePrompt.js.map +1 -1
  294. package/dist/components/VirtualList/VirtualList.cjs.map +1 -1
  295. package/dist/components/VirtualList/VirtualList.js.map +1 -1
  296. package/dist/components/VirtualTable/VirtualTable.cjs.map +1 -1
  297. package/dist/components/VirtualTable/VirtualTable.js.map +1 -1
  298. package/dist/components/VisuallyHidden/VisuallyHidden.cjs.map +1 -1
  299. package/dist/components/Wizard/Wizard.cjs.map +1 -1
  300. package/dist/components/Wizard/Wizard.js.map +1 -1
  301. package/dist/data/data-provider-context.cjs.map +1 -1
  302. package/dist/data/use-resource.cjs.map +1 -1
  303. package/dist/editor/RichTextEditor.cjs.map +1 -1
  304. package/dist/editor/RichTextEditor.js.map +1 -1
  305. package/dist/error-boundary/use-error-handler.cjs.map +1 -1
  306. package/dist/feature-flags/FeatureFlagsProvider.cjs.map +1 -1
  307. package/dist/forms/FormField.cjs.map +1 -1
  308. package/dist/forms/masked-inputs.cjs.map +1 -1
  309. package/dist/forms/use-viacep.cjs.map +1 -1
  310. package/dist/forms/use-zod-form.cjs.map +1 -1
  311. package/dist/geo/TrajectoryMap.cjs.map +1 -1
  312. package/dist/geo/TrajectoryMap.js.map +1 -1
  313. package/dist/geo/leaflet-map.cjs.map +1 -1
  314. package/dist/geo/leaflet-map.js.map +1 -1
  315. package/dist/geo/use-position-tracker.cjs.map +1 -1
  316. package/dist/hooks/use-announce.cjs.map +1 -1
  317. package/dist/hooks/use-async.cjs.map +1 -1
  318. package/dist/hooks/use-before-install-prompt.cjs.map +1 -1
  319. package/dist/hooks/use-breakpoint.cjs.map +1 -1
  320. package/dist/hooks/use-click-outside.cjs.map +1 -1
  321. package/dist/hooks/use-client-filter.cjs.map +1 -1
  322. package/dist/hooks/use-clipboard.cjs.map +1 -1
  323. package/dist/hooks/use-counter.cjs.map +1 -1
  324. package/dist/hooks/use-debounce.cjs.map +1 -1
  325. package/dist/hooks/use-deep-memo.cjs.map +1 -1
  326. package/dist/hooks/use-disclosure.cjs.map +1 -1
  327. package/dist/hooks/use-document-visibility.cjs.map +1 -1
  328. package/dist/hooks/use-event-listener.cjs.map +1 -1
  329. package/dist/hooks/use-geolocation.cjs.map +1 -1
  330. package/dist/hooks/use-hover.cjs.map +1 -1
  331. package/dist/hooks/use-idle.cjs.map +1 -1
  332. package/dist/hooks/use-install-prompt.cjs +1 -1
  333. package/dist/hooks/use-install-prompt.cjs.map +1 -1
  334. package/dist/hooks/use-install-prompt.js +1 -1
  335. package/dist/hooks/use-install-prompt.js.map +1 -1
  336. package/dist/hooks/use-intersection-observer.cjs.map +1 -1
  337. package/dist/hooks/use-interval.cjs.map +1 -1
  338. package/dist/hooks/use-is-first-render.cjs.map +1 -1
  339. package/dist/hooks/use-keyboard-shortcut.cjs +1 -1
  340. package/dist/hooks/use-keyboard-shortcut.cjs.map +1 -1
  341. package/dist/hooks/use-keyboard-shortcut.js +1 -1
  342. package/dist/hooks/use-keyboard-shortcut.js.map +1 -1
  343. package/dist/hooks/use-list-state.cjs.map +1 -1
  344. package/dist/hooks/use-local-storage.cjs.map +1 -1
  345. package/dist/hooks/use-long-press-handlers.cjs.map +1 -1
  346. package/dist/hooks/use-long-press.cjs.map +1 -1
  347. package/dist/hooks/use-map.cjs.map +1 -1
  348. package/dist/hooks/use-media-devices.cjs.map +1 -1
  349. package/dist/hooks/use-media-permission.cjs.map +1 -1
  350. package/dist/hooks/use-media-query.cjs.map +1 -1
  351. package/dist/hooks/use-object-url.cjs.map +1 -1
  352. package/dist/hooks/use-online.cjs.map +1 -1
  353. package/dist/hooks/use-pagination.cjs.map +1 -1
  354. package/dist/hooks/use-previous.cjs.map +1 -1
  355. package/dist/hooks/use-queue.cjs.map +1 -1
  356. package/dist/hooks/use-resize-observer.cjs.map +1 -1
  357. package/dist/hooks/use-scroll-overflow.cjs.map +1 -1
  358. package/dist/hooks/use-service-worker-update.cjs.map +1 -1
  359. package/dist/hooks/use-set.cjs.map +1 -1
  360. package/dist/hooks/use-sortable.cjs +1 -1
  361. package/dist/hooks/use-sortable.cjs.map +1 -1
  362. package/dist/hooks/use-sortable.js +0 -2
  363. package/dist/hooks/use-sortable.js.map +1 -1
  364. package/dist/hooks/use-stable-callback.cjs.map +1 -1
  365. package/dist/hooks/use-storage-estimate.cjs.map +1 -1
  366. package/dist/hooks/use-throttle.cjs.map +1 -1
  367. package/dist/hooks/use-timeout.cjs.map +1 -1
  368. package/dist/hooks/use-toggle.cjs.map +1 -1
  369. package/dist/hooks/use-window-size.cjs.map +1 -1
  370. package/dist/http/api-client.cjs +1 -1
  371. package/dist/http/api-client.cjs.map +1 -1
  372. package/dist/http/api-client.js +7 -5
  373. package/dist/http/api-client.js.map +1 -1
  374. package/dist/http/resumable-upload.cjs +1 -1
  375. package/dist/http/resumable-upload.cjs.map +1 -1
  376. package/dist/http/resumable-upload.js +1 -1
  377. package/dist/http/resumable-upload.js.map +1 -1
  378. package/dist/http/use-poll.cjs.map +1 -1
  379. package/dist/i18n/I18nProvider.cjs.map +1 -1
  380. package/dist/icons/Icon.cjs.map +1 -1
  381. package/dist/icons/IconProvider.cjs.map +1 -1
  382. package/dist/icons/icon-context.cjs.map +1 -1
  383. package/dist/icons/use-icon.cjs.map +1 -1
  384. package/dist/imaging/use-image-processing.cjs.map +1 -1
  385. package/dist/oauth/GoogleSignIn.cjs.map +1 -1
  386. package/dist/oauth/GoogleSignIn.js.map +1 -1
  387. package/dist/oauth/use-oauth-callback.cjs.map +1 -1
  388. package/dist/offline/use-offline-sync.cjs.map +1 -1
  389. package/dist/push/use-push-subscription.cjs.map +1 -1
  390. package/dist/query/QueryProvider.cjs.map +1 -1
  391. package/dist/query/constants.cjs +1 -1
  392. package/dist/query/constants.cjs.map +1 -1
  393. package/dist/query/constants.js +10 -10
  394. package/dist/query/constants.js.map +1 -1
  395. package/dist/query/create-query-keys.cjs +1 -1
  396. package/dist/query/create-query-keys.cjs.map +1 -1
  397. package/dist/query/create-query-keys.js +1 -1
  398. package/dist/query/create-query-keys.js.map +1 -1
  399. package/dist/query/offline-persistence.cjs.map +1 -1
  400. package/dist/query/use-cursor-query.cjs.map +1 -1
  401. package/dist/query/use-offline-mutation.cjs.map +1 -1
  402. package/dist/query/use-paginated-query.cjs.map +1 -1
  403. package/dist/router/AppRouter.cjs +1 -1
  404. package/dist/router/AppRouter.cjs.map +1 -1
  405. package/dist/router/AppRouter.js +7 -2
  406. package/dist/router/AppRouter.js.map +1 -1
  407. package/dist/sse/use-event-stream.cjs.map +1 -1
  408. package/dist/store/create-store.cjs.map +1 -1
  409. package/dist/tabular/use-tabular-predictor.cjs.map +1 -1
  410. package/dist/telemetry/TelemetryProvider.cjs.map +1 -1
  411. package/dist/theme/ThemeProvider.cjs.map +1 -1
  412. package/dist/theme/color.cjs +1 -1
  413. package/dist/theme/color.cjs.map +1 -1
  414. package/dist/theme/color.js +1 -1
  415. package/dist/theme/color.js.map +1 -1
  416. package/dist/utils/cn.cjs +1 -1
  417. package/dist/utils/cn.cjs.map +1 -1
  418. package/dist/utils/cn.js +1 -1
  419. package/dist/utils/cn.js.map +1 -1
  420. package/dist/utils/objects.cjs +1 -1
  421. package/dist/utils/objects.cjs.map +1 -1
  422. package/dist/utils/objects.js +1 -1
  423. package/dist/utils/objects.js.map +1 -1
  424. package/dist/utils/xlsx.cjs +1 -1
  425. package/dist/utils/xlsx.cjs.map +1 -1
  426. package/dist/utils/xlsx.js +3 -3
  427. package/dist/utils/xlsx.js.map +1 -1
  428. package/dist/vision/core/canvas.cjs +1 -1
  429. package/dist/vision/core/canvas.cjs.map +1 -1
  430. package/dist/vision/core/canvas.js +4 -4
  431. package/dist/vision/core/canvas.js.map +1 -1
  432. package/dist/vision/core/exceptions.cjs +1 -1
  433. package/dist/vision/core/exceptions.cjs.map +1 -1
  434. package/dist/vision/core/exceptions.js +2 -2
  435. package/dist/vision/core/exceptions.js.map +1 -1
  436. package/dist/vision/core/metadata.cjs +2 -2
  437. package/dist/vision/core/metadata.cjs.map +1 -1
  438. package/dist/vision/core/metadata.js +11 -8
  439. package/dist/vision/core/metadata.js.map +1 -1
  440. package/dist/vision/core/session.cjs +1 -1
  441. package/dist/vision/core/session.cjs.map +1 -1
  442. package/dist/vision/core/session.js +8 -5
  443. package/dist/vision/core/session.js.map +1 -1
  444. package/dist/vision/fusion.cjs +2 -0
  445. package/dist/vision/fusion.cjs.map +1 -0
  446. package/dist/vision/fusion.js +42 -0
  447. package/dist/vision/fusion.js.map +1 -0
  448. package/dist/vision/index.cjs +1 -1
  449. package/dist/vision/index.cjs.map +1 -1
  450. package/dist/vision/index.js +23 -20
  451. package/dist/vision/index.js.map +1 -1
  452. package/dist/vision/io/image.cjs +1 -1
  453. package/dist/vision/io/image.cjs.map +1 -1
  454. package/dist/vision/io/image.js +4 -2
  455. package/dist/vision/io/image.js.map +1 -1
  456. package/dist/vision/labels.cjs +1 -1
  457. package/dist/vision/labels.cjs.map +1 -1
  458. package/dist/vision/labels.js +7 -4
  459. package/dist/vision/labels.js.map +1 -1
  460. package/dist/vision/postprocess/detection.cjs +1 -1
  461. package/dist/vision/postprocess/detection.cjs.map +1 -1
  462. package/dist/vision/postprocess/detection.js +3 -10
  463. package/dist/vision/postprocess/detection.js.map +1 -1
  464. package/dist/vision/postprocess/segmentation.cjs +1 -1
  465. package/dist/vision/postprocess/segmentation.cjs.map +1 -1
  466. package/dist/vision/postprocess/segmentation.js +11 -15
  467. package/dist/vision/postprocess/segmentation.js.map +1 -1
  468. package/dist/vision/preprocess/image.cjs +1 -1
  469. package/dist/vision/preprocess/image.cjs.map +1 -1
  470. package/dist/vision/preprocess/image.js +4 -2
  471. package/dist/vision/preprocess/image.js.map +1 -1
  472. package/dist/vision/preprocess/pipeline.cjs +2 -0
  473. package/dist/vision/preprocess/pipeline.cjs.map +1 -0
  474. package/dist/vision/preprocess/pipeline.js +61 -0
  475. package/dist/vision/preprocess/pipeline.js.map +1 -0
  476. package/dist/vision/results.cjs +1 -1
  477. package/dist/vision/results.cjs.map +1 -1
  478. package/dist/vision/results.js +23 -2
  479. package/dist/vision/results.js.map +1 -1
  480. package/dist/vision/tasks/base.cjs +1 -1
  481. package/dist/vision/tasks/base.cjs.map +1 -1
  482. package/dist/vision/tasks/base.js +11 -2
  483. package/dist/vision/tasks/base.js.map +1 -1
  484. package/dist/vision/tasks/classifier.cjs +1 -1
  485. package/dist/vision/tasks/classifier.cjs.map +1 -1
  486. package/dist/vision/tasks/classifier.js +2 -2
  487. package/dist/vision/tasks/classifier.js.map +1 -1
  488. package/dist/vision/tasks/detectClassify.cjs +2 -0
  489. package/dist/vision/tasks/detectClassify.cjs.map +1 -0
  490. package/dist/vision/tasks/detectClassify.js +222 -0
  491. package/dist/vision/tasks/detectClassify.js.map +1 -0
  492. package/dist/vision/tasks/detector.cjs +1 -1
  493. package/dist/vision/tasks/detector.cjs.map +1 -1
  494. package/dist/vision/tasks/detector.js +54 -29
  495. package/dist/vision/tasks/detector.js.map +1 -1
  496. package/dist/vision/tasks/segmenter.cjs +1 -1
  497. package/dist/vision/tasks/segmenter.cjs.map +1 -1
  498. package/dist/vision/tasks/segmenter.js +53 -28
  499. package/dist/vision/tasks/segmenter.js.map +1 -1
  500. package/dist/vision/types.cjs.map +1 -1
  501. package/dist/vision/types.js.map +1 -1
  502. package/dist/vision/use-camera-stream.cjs.map +1 -1
  503. package/dist/vision/use-live-luminance.cjs.map +1 -1
  504. package/dist/vision.cjs +1 -1
  505. package/dist/vision.d.ts +1517 -962
  506. package/dist/vision.js +25 -22
  507. package/dist/vite/create-vite-config.cjs +1 -1
  508. package/dist/vite/create-vite-config.cjs.map +1 -1
  509. package/dist/vite/create-vite-config.js +3 -3
  510. package/dist/vite/create-vite-config.js.map +1 -1
  511. package/dist/vite/tempest-icons.cjs.map +1 -1
  512. package/dist/vite/tempest-pwa-dev-sw.cjs.map +1 -1
  513. package/dist/vite/tempest-pwa-icons.cjs.map +1 -1
  514. package/dist/ws/use-web-socket.cjs +1 -1
  515. package/dist/ws/use-web-socket.cjs.map +1 -1
  516. package/dist/ws/use-web-socket.js +5 -3
  517. package/dist/ws/use-web-socket.js.map +1 -1
  518. package/package.json +2 -2
  519. package/template/package.json +1 -1
@@ -43,57 +43,54 @@ function h(e, t) {
43
43
  for (let e = 0; e < u; e += 1) a.push(d(i.getFullYear(), i.getMonth(), i.getDate() + e));
44
44
  return a;
45
45
  }
46
- function g({ value: g, defaultValue: _, onChange: v, month: y, onMonthChange: b, minDate: x, maxDate: S, weekStartsOn: C = 0, className: w, ...T }) {
47
- let E = g !== void 0, [D, O] = a(_), k = E ? g : D, A = y !== void 0, [j, M] = a(() => {
48
- let e = y ?? g ?? _ ?? /* @__PURE__ */ new Date();
46
+ function g({ value: u, defaultValue: g, onChange: _, month: v, onMonthChange: y, minDate: b, maxDate: x, weekStartsOn: S = 0, className: C, ...w }) {
47
+ let T = u !== void 0, [E, D] = a(g), O = T ? u : E, k = v !== void 0, [A, j] = a(() => {
48
+ let e = v ?? u ?? g ?? /* @__PURE__ */ new Date();
49
49
  return d(e.getFullYear(), e.getMonth(), 1);
50
- }), N = A ? d(y.getFullYear(), y.getMonth(), 1) : j, [P, F] = a(null), I = i(null), L = r(() => f(/* @__PURE__ */ new Date()), []), R = r(() => h(N, C), [N, C]), z = r(() => Array.from({ length: 7 }, (e, t) => c[(t + C) % 7]), [C]), B = n((e) => {
51
- let t = d(N.getFullYear(), N.getMonth() + e, 1);
52
- b && b(t), A || M(t);
50
+ }), M = k ? d(v.getFullYear(), v.getMonth(), 1) : A, [N, P] = a(null), F = i(null), I = r(() => f(/* @__PURE__ */ new Date()), []), L = r(() => h(M, S), [M, S]), R = r(() => Array.from({ length: 7 }, (e, t) => c[(t + S) % 7]), [S]), z = n((e) => {
51
+ let t = d(M.getFullYear(), M.getMonth() + e, 1);
52
+ y && y(t), k || j(t);
53
53
  }, [
54
- N,
55
- b,
56
- A
57
- ]), V = n((e) => {
58
- m(e, x, S) || (v && v(e), E || O(e));
54
+ M,
55
+ y,
56
+ k
57
+ ]), B = n((e) => {
58
+ m(e, b, x) || (_ && _(e), T || D(e));
59
59
  }, [
60
+ b,
60
61
  x,
61
- S,
62
- v,
63
- E
64
- ]), H = n((e) => {
65
- let t = Math.max(0, Math.min(u - 1, e));
66
- F(t);
67
- let n = I.current;
62
+ _,
63
+ T
64
+ ]), V = n((e) => {
65
+ let t = Math.max(0, Math.min(41, e));
66
+ P(t);
67
+ let n = F.current;
68
68
  n && n.querySelectorAll("button[data-day]")[t]?.focus();
69
- }, []), U = n((e, t) => {
69
+ }, []), H = n((e, t) => {
70
70
  switch (e.key) {
71
71
  case "ArrowLeft":
72
- e.preventDefault(), H(t - 1);
72
+ e.preventDefault(), V(t - 1);
73
73
  break;
74
74
  case "ArrowRight":
75
- e.preventDefault(), H(t + 1);
75
+ e.preventDefault(), V(t + 1);
76
76
  break;
77
77
  case "ArrowUp":
78
- e.preventDefault(), H(t - 7);
78
+ e.preventDefault(), V(t - 7);
79
79
  break;
80
80
  case "ArrowDown":
81
- e.preventDefault(), H(t + 7);
81
+ e.preventDefault(), V(t + 7);
82
82
  break;
83
83
  case "Enter":
84
- case " ":
85
- e.preventDefault(), V(R[t]);
86
- break;
87
- default: break;
84
+ case " ": e.preventDefault(), B(L[t]);
88
85
  }
89
86
  }, [
90
- H,
91
87
  V,
92
- R
88
+ B,
89
+ L
93
90
  ]);
94
91
  return /* @__PURE__ */ s("div", {
95
- className: e(t.root, w),
96
- ...T,
92
+ className: e(t.root, C),
93
+ ...w,
97
94
  children: [
98
95
  /* @__PURE__ */ s("div", {
99
96
  className: t.header,
@@ -102,23 +99,23 @@ function g({ value: g, defaultValue: _, onChange: v, month: y, onMonthChange: b,
102
99
  type: "button",
103
100
  className: t.nav,
104
101
  "aria-label": "Previous month",
105
- onClick: () => B(-1),
102
+ onClick: () => z(-1),
106
103
  children: "‹"
107
104
  }),
108
105
  /* @__PURE__ */ s("span", {
109
106
  className: t.title,
110
107
  "aria-live": "polite",
111
108
  children: [
112
- l[N.getMonth()],
109
+ l[M.getMonth()],
113
110
  " ",
114
- N.getFullYear()
111
+ M.getFullYear()
115
112
  ]
116
113
  }),
117
114
  /* @__PURE__ */ o("button", {
118
115
  type: "button",
119
116
  className: t.nav,
120
117
  "aria-label": "Next month",
121
- onClick: () => B(1),
118
+ onClick: () => z(1),
122
119
  children: "›"
123
120
  })
124
121
  ]
@@ -126,7 +123,7 @@ function g({ value: g, defaultValue: _, onChange: v, month: y, onMonthChange: b,
126
123
  /* @__PURE__ */ o("div", {
127
124
  className: t.weekdays,
128
125
  role: "row",
129
- children: z.map((e) => /* @__PURE__ */ o("span", {
126
+ children: R.map((e) => /* @__PURE__ */ o("span", {
130
127
  className: t.weekday,
131
128
  role: "columnheader",
132
129
  children: e
@@ -135,9 +132,9 @@ function g({ value: g, defaultValue: _, onChange: v, month: y, onMonthChange: b,
135
132
  /* @__PURE__ */ o("div", {
136
133
  className: t.grid,
137
134
  role: "grid",
138
- ref: I,
139
- children: R.map((n, r) => {
140
- let i = n.getMonth() === N.getMonth(), a = k ? p(n, k) : !1, s = p(n, L), c = m(n, x, S), l = P === null ? i && n.getDate() === 1 : P === r;
135
+ ref: F,
136
+ children: L.map((n, r) => {
137
+ let i = n.getMonth() === M.getMonth(), a = O ? p(n, O) : !1, s = p(n, I), c = m(n, b, x), l = N === null ? i && n.getDate() === 1 : N === r;
141
138
  return /* @__PURE__ */ o("button", {
142
139
  type: "button",
143
140
  "data-day": !0,
@@ -148,8 +145,8 @@ function g({ value: g, defaultValue: _, onChange: v, month: y, onMonthChange: b,
148
145
  "aria-current": s ? "date" : void 0,
149
146
  disabled: c,
150
147
  tabIndex: l ? 0 : -1,
151
- onClick: () => V(n),
152
- onKeyDown: (e) => U(e, r),
148
+ onClick: () => B(n),
149
+ onKeyDown: (e) => H(e, r),
153
150
  children: n.getDate()
154
151
  }, r);
155
152
  })
@@ -1 +1 @@
1
- {"version":3,"file":"Calendar.js","names":[],"sources":["../../../src/components/Calendar/Calendar.tsx"],"sourcesContent":["/**\n * @tempest-limits file-lines, props-count, function-lines — a date grid is\n * controlled two ways at once — the selected day (value, defaultValue, onChange) and\n * the visible month (month, onMonthChange) — inside the bounds (minDate, maxDate,\n * weekStartsOn). The body builds the grid and owns roving focus and keyboard\n * navigation, which read the same cursor date.\n */\nimport { useCallback, useMemo, useRef, useState } from \"react\";\nimport type { HTMLAttributes, KeyboardEvent } from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport styles from \"./Calendar.module.css\";\n\nexport type WeekStart = 0 | 1;\n\nexport interface CalendarProps extends Omit<\n HTMLAttributes<HTMLDivElement>,\n \"onChange\" | \"defaultValue\"\n> {\n /** Controlled selected date. */\n value?: Date;\n /** Initial selected date for the uncontrolled case. */\n defaultValue?: Date;\n /** Called with the newly selected date. */\n onChange?: (date: Date) => void;\n /** Controlled visible month (any day within it). */\n month?: Date;\n /** Called when the visible month changes (prev/next). */\n onMonthChange?: (month: Date) => void;\n /** Earliest selectable date (inclusive). */\n minDate?: Date;\n /** Latest selectable date (inclusive). */\n maxDate?: Date;\n /** First column of the week — `0` Sunday (default) or `1` Monday. */\n weekStartsOn?: WeekStart;\n}\n\nconst WEEKDAY_LABELS = [\"Sun\", \"Mon\", \"Tue\", \"Wed\", \"Thu\", \"Fri\", \"Sat\"];\nconst MONTH_LABELS = [\n \"January\",\n \"February\",\n \"March\",\n \"April\",\n \"May\",\n \"June\",\n \"July\",\n \"August\",\n \"September\",\n \"October\",\n \"November\",\n \"December\",\n];\n\nconst DAYS_IN_GRID = 42;\n\n/**\n * Build a `Date` at midnight local time for the given year/month/day.\n *\n * @param year - Full year.\n * @param month - Zero-based month index.\n * @param day - Day of the month.\n * @returns A normalized `Date`.\n */\nfunction makeDate(year: number, month: number, day: number): Date {\n return new Date(year, month, day);\n}\n\n/**\n * Strip the time portion so two dates can be compared by calendar day.\n *\n * @param date - The date to normalize.\n * @returns A new `Date` at local midnight.\n */\nfunction startOfDay(date: Date): Date {\n return new Date(date.getFullYear(), date.getMonth(), date.getDate());\n}\n\n/**\n * Whether two dates fall on the same calendar day.\n *\n * @param a - First date.\n * @param b - Second date.\n * @returns `true` when year, month and day match.\n */\nfunction isSameDay(a: Date, b: Date): boolean {\n return (\n a.getFullYear() === b.getFullYear() &&\n a.getMonth() === b.getMonth() &&\n a.getDate() === b.getDate()\n );\n}\n\n/**\n * Whether a date is outside the inclusive `[minDate, maxDate]` window.\n *\n * @param date - The candidate day.\n * @param minDate - Optional lower bound.\n * @param maxDate - Optional upper bound.\n * @returns `true` when the date is disabled.\n */\nfunction isOutOfRange(date: Date, minDate?: Date, maxDate?: Date): boolean {\n const day = startOfDay(date).getTime();\n if (minDate && day < startOfDay(minDate).getTime()) return true;\n if (maxDate && day > startOfDay(maxDate).getTime()) return true;\n return false;\n}\n\n/**\n * Compute the 42-cell (6x7) grid of dates covering the given month, padded with\n * leading/trailing days from the adjacent months.\n *\n * @param month - Any date within the target month.\n * @param weekStartsOn - First column of the week.\n * @returns An array of 42 `Date` objects.\n */\nfunction buildGrid(month: Date, weekStartsOn: WeekStart): Date[] {\n const year = month.getFullYear();\n const monthIndex = month.getMonth();\n const firstOfMonth = makeDate(year, monthIndex, 1);\n const firstWeekday = firstOfMonth.getDay();\n const leading = (firstWeekday - weekStartsOn + 7) % 7;\n const start = makeDate(year, monthIndex, 1 - leading);\n\n const cells: Date[] = [];\n for (let i = 0; i < DAYS_IN_GRID; i += 1) {\n cells.push(makeDate(start.getFullYear(), start.getMonth(), start.getDate() + i));\n }\n return cells;\n}\n\n/**\n * Standalone month-grid date picker. Renders a header with the month/year and\n * prev/next buttons, a weekday row, and a 6x7 grid of day buttons. Selection and\n * the visible month can each be controlled or uncontrolled. Grid arithmetic uses\n * plain `Date` math — no external date libraries.\n *\n * Keyboard: arrow keys move focus by day (left/right) or week (up/down), and\n * Enter/Space selects the focused day.\n */\nexport function Calendar({\n value,\n defaultValue,\n onChange,\n month,\n onMonthChange,\n minDate,\n maxDate,\n weekStartsOn = 0,\n className,\n ...props\n}: CalendarProps) {\n const isSelectionControlled = value !== undefined;\n const [internalSelected, setInternalSelected] = useState<Date | undefined>(defaultValue);\n const selected = isSelectionControlled ? value : internalSelected;\n\n const isMonthControlled = month !== undefined;\n const [internalMonth, setInternalMonth] = useState<Date>(() => {\n const base = month ?? value ?? defaultValue ?? new Date();\n return makeDate(base.getFullYear(), base.getMonth(), 1);\n });\n const visibleMonth = isMonthControlled\n ? makeDate(month.getFullYear(), month.getMonth(), 1)\n : internalMonth;\n\n const [focusedDay, setFocusedDay] = useState<number | null>(null);\n const gridRef = useRef<HTMLDivElement | null>(null);\n\n const today = useMemo(() => startOfDay(new Date()), []);\n const cells = useMemo(\n () => buildGrid(visibleMonth, weekStartsOn),\n [visibleMonth, weekStartsOn],\n );\n\n const weekdayLabels = useMemo(() => {\n return Array.from({ length: 7 }, (_, i) => WEEKDAY_LABELS[(i + weekStartsOn) % 7]);\n }, [weekStartsOn]);\n\n const changeMonth = useCallback(\n (offset: number): void => {\n const next = makeDate(visibleMonth.getFullYear(), visibleMonth.getMonth() + offset, 1);\n if (onMonthChange) onMonthChange(next);\n if (!isMonthControlled) setInternalMonth(next);\n },\n [visibleMonth, onMonthChange, isMonthControlled],\n );\n\n const selectDate = useCallback(\n (date: Date): void => {\n if (isOutOfRange(date, minDate, maxDate)) return;\n if (onChange) onChange(date);\n if (!isSelectionControlled) setInternalSelected(date);\n },\n [minDate, maxDate, onChange, isSelectionControlled],\n );\n\n const focusCell = useCallback((index: number): void => {\n const clamped = Math.max(0, Math.min(DAYS_IN_GRID - 1, index));\n setFocusedDay(clamped);\n const grid = gridRef.current;\n if (!grid) return;\n const buttons = grid.querySelectorAll<HTMLButtonElement>(\"button[data-day]\");\n buttons[clamped]?.focus();\n }, []);\n\n const handleKeyDown = useCallback(\n (event: KeyboardEvent<HTMLButtonElement>, index: number): void => {\n switch (event.key) {\n case \"ArrowLeft\":\n event.preventDefault();\n focusCell(index - 1);\n break;\n case \"ArrowRight\":\n event.preventDefault();\n focusCell(index + 1);\n break;\n case \"ArrowUp\":\n event.preventDefault();\n focusCell(index - 7);\n break;\n case \"ArrowDown\":\n event.preventDefault();\n focusCell(index + 7);\n break;\n case \"Enter\":\n case \" \":\n event.preventDefault();\n selectDate(cells[index]);\n break;\n default:\n break;\n }\n },\n [focusCell, selectDate, cells],\n );\n\n return (\n <div className={cn(styles.root, className)} {...props}>\n <div className={styles.header}>\n <button\n type=\"button\"\n className={styles.nav}\n aria-label=\"Previous month\"\n onClick={() => changeMonth(-1)}\n >\n {\"‹\"}\n </button>\n <span className={styles.title} aria-live=\"polite\">\n {MONTH_LABELS[visibleMonth.getMonth()]} {visibleMonth.getFullYear()}\n </span>\n <button\n type=\"button\"\n className={styles.nav}\n aria-label=\"Next month\"\n onClick={() => changeMonth(1)}\n >\n {\"›\"}\n </button>\n </div>\n\n <div className={styles.weekdays} role=\"row\">\n {weekdayLabels.map((label) => (\n <span key={label} className={styles.weekday} role=\"columnheader\">\n {label}\n </span>\n ))}\n </div>\n\n <div className={styles.grid} role=\"grid\" ref={gridRef}>\n {cells.map((date, index) => {\n const inMonth = date.getMonth() === visibleMonth.getMonth();\n const isSelected = selected ? isSameDay(date, selected) : false;\n const isToday = isSameDay(date, today);\n const disabled = isOutOfRange(date, minDate, maxDate);\n const isFocusTarget =\n focusedDay === null\n ? inMonth && date.getDate() === 1\n : focusedDay === index;\n return (\n <button\n key={index}\n type=\"button\"\n data-day\n role=\"gridcell\"\n className={cn(\n styles.day,\n !inMonth && styles.outside,\n isSelected && styles.selected,\n isToday && styles.today,\n )}\n aria-pressed={isSelected}\n aria-selected={isSelected}\n aria-current={isToday ? \"date\" : undefined}\n disabled={disabled}\n tabIndex={isFocusTarget ? 0 : -1}\n onClick={() => selectDate(date)}\n onKeyDown={(event) => handleKeyDown(event, index)}\n >\n {date.getDate()}\n </button>\n );\n })}\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"Calendar.js","names":[],"sources":["../../../src/components/Calendar/Calendar.tsx"],"sourcesContent":["/**\n * @tempest-limits file-lines, props-count, function-lines — a date grid is\n * controlled two ways at once — the selected day (value, defaultValue, onChange) and\n * the visible month (month, onMonthChange) — inside the bounds (minDate, maxDate,\n * weekStartsOn). The body builds the grid and owns roving focus and keyboard\n * navigation, which read the same cursor date.\n */\nimport { useCallback, useMemo, useRef, useState } from \"react\";\nimport type { HTMLAttributes, KeyboardEvent } from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport styles from \"./Calendar.module.css\";\n\nexport type WeekStart = 0 | 1;\n\nexport interface CalendarProps extends Omit<\n HTMLAttributes<HTMLDivElement>,\n \"onChange\" | \"defaultValue\"\n> {\n /** Controlled selected date. */\n value?: Date;\n /** Initial selected date for the uncontrolled case. */\n defaultValue?: Date;\n /** Called with the newly selected date. */\n onChange?: (date: Date) => void;\n /** Controlled visible month (any day within it). */\n month?: Date;\n /** Called when the visible month changes (prev/next). */\n onMonthChange?: (month: Date) => void;\n /** Earliest selectable date (inclusive). */\n minDate?: Date;\n /** Latest selectable date (inclusive). */\n maxDate?: Date;\n /** First column of the week — `0` Sunday (default) or `1` Monday. */\n weekStartsOn?: WeekStart;\n}\n\nconst WEEKDAY_LABELS = [\"Sun\", \"Mon\", \"Tue\", \"Wed\", \"Thu\", \"Fri\", \"Sat\"];\nconst MONTH_LABELS = [\n \"January\",\n \"February\",\n \"March\",\n \"April\",\n \"May\",\n \"June\",\n \"July\",\n \"August\",\n \"September\",\n \"October\",\n \"November\",\n \"December\",\n];\n\nconst DAYS_IN_GRID = 42;\n\n/**\n * Build a `Date` at midnight local time for the given year/month/day.\n *\n * @param year - Full year.\n * @param month - Zero-based month index.\n * @param day - Day of the month.\n * @returns A normalized `Date`.\n */\nfunction makeDate(year: number, month: number, day: number): Date {\n return new Date(year, month, day);\n}\n\n/**\n * Strip the time portion so two dates can be compared by calendar day.\n *\n * @param date - The date to normalize.\n * @returns A new `Date` at local midnight.\n */\nfunction startOfDay(date: Date): Date {\n return new Date(date.getFullYear(), date.getMonth(), date.getDate());\n}\n\n/**\n * Whether two dates fall on the same calendar day.\n *\n * @param a - First date.\n * @param b - Second date.\n * @returns `true` when year, month and day match.\n */\nfunction isSameDay(a: Date, b: Date): boolean {\n return (\n a.getFullYear() === b.getFullYear() &&\n a.getMonth() === b.getMonth() &&\n a.getDate() === b.getDate()\n );\n}\n\n/**\n * Whether a date is outside the inclusive `[minDate, maxDate]` window.\n *\n * @param date - The candidate day.\n * @param minDate - Optional lower bound.\n * @param maxDate - Optional upper bound.\n * @returns `true` when the date is disabled.\n */\nfunction isOutOfRange(date: Date, minDate?: Date, maxDate?: Date): boolean {\n const day = startOfDay(date).getTime();\n if (minDate && day < startOfDay(minDate).getTime()) return true;\n if (maxDate && day > startOfDay(maxDate).getTime()) return true;\n return false;\n}\n\n/**\n * Compute the 42-cell (6x7) grid of dates covering the given month, padded with\n * leading/trailing days from the adjacent months.\n *\n * @param month - Any date within the target month.\n * @param weekStartsOn - First column of the week.\n * @returns An array of 42 `Date` objects.\n */\nfunction buildGrid(month: Date, weekStartsOn: WeekStart): Date[] {\n const year = month.getFullYear();\n const monthIndex = month.getMonth();\n const firstOfMonth = makeDate(year, monthIndex, 1);\n const firstWeekday = firstOfMonth.getDay();\n const leading = (firstWeekday - weekStartsOn + 7) % 7;\n const start = makeDate(year, monthIndex, 1 - leading);\n\n const cells: Date[] = [];\n for (let i = 0; i < DAYS_IN_GRID; i += 1) {\n cells.push(makeDate(start.getFullYear(), start.getMonth(), start.getDate() + i));\n }\n return cells;\n}\n\n/**\n * Standalone month-grid date picker. Renders a header with the month/year and\n * prev/next buttons, a weekday row, and a 6x7 grid of day buttons. Selection and\n * the visible month can each be controlled or uncontrolled. Grid arithmetic uses\n * plain `Date` math — no external date libraries.\n *\n * Keyboard: arrow keys move focus by day (left/right) or week (up/down), and\n * Enter/Space selects the focused day.\n */\nexport function Calendar({\n value,\n defaultValue,\n onChange,\n month,\n onMonthChange,\n minDate,\n maxDate,\n weekStartsOn = 0,\n className,\n ...props\n}: CalendarProps) {\n const isSelectionControlled = value !== undefined;\n const [internalSelected, setInternalSelected] = useState<Date | undefined>(defaultValue);\n const selected = isSelectionControlled ? value : internalSelected;\n\n const isMonthControlled = month !== undefined;\n const [internalMonth, setInternalMonth] = useState<Date>(() => {\n const base = month ?? value ?? defaultValue ?? new Date();\n return makeDate(base.getFullYear(), base.getMonth(), 1);\n });\n const visibleMonth = isMonthControlled\n ? makeDate(month.getFullYear(), month.getMonth(), 1)\n : internalMonth;\n\n const [focusedDay, setFocusedDay] = useState<number | null>(null);\n const gridRef = useRef<HTMLDivElement | null>(null);\n\n const today = useMemo(() => startOfDay(new Date()), []);\n const cells = useMemo(\n () => buildGrid(visibleMonth, weekStartsOn),\n [visibleMonth, weekStartsOn],\n );\n\n const weekdayLabels = useMemo(() => {\n return Array.from({ length: 7 }, (_, i) => WEEKDAY_LABELS[(i + weekStartsOn) % 7]);\n }, [weekStartsOn]);\n\n const changeMonth = useCallback(\n (offset: number): void => {\n const next = makeDate(visibleMonth.getFullYear(), visibleMonth.getMonth() + offset, 1);\n if (onMonthChange) onMonthChange(next);\n if (!isMonthControlled) setInternalMonth(next);\n },\n [visibleMonth, onMonthChange, isMonthControlled],\n );\n\n const selectDate = useCallback(\n (date: Date): void => {\n if (isOutOfRange(date, minDate, maxDate)) return;\n if (onChange) onChange(date);\n if (!isSelectionControlled) setInternalSelected(date);\n },\n [minDate, maxDate, onChange, isSelectionControlled],\n );\n\n const focusCell = useCallback((index: number): void => {\n const clamped = Math.max(0, Math.min(DAYS_IN_GRID - 1, index));\n setFocusedDay(clamped);\n const grid = gridRef.current;\n if (!grid) return;\n const buttons = grid.querySelectorAll<HTMLButtonElement>(\"button[data-day]\");\n buttons[clamped]?.focus();\n }, []);\n\n const handleKeyDown = useCallback(\n (event: KeyboardEvent<HTMLButtonElement>, index: number): void => {\n switch (event.key) {\n case \"ArrowLeft\":\n event.preventDefault();\n focusCell(index - 1);\n break;\n case \"ArrowRight\":\n event.preventDefault();\n focusCell(index + 1);\n break;\n case \"ArrowUp\":\n event.preventDefault();\n focusCell(index - 7);\n break;\n case \"ArrowDown\":\n event.preventDefault();\n focusCell(index + 7);\n break;\n case \"Enter\":\n case \" \":\n event.preventDefault();\n selectDate(cells[index]);\n break;\n default:\n break;\n }\n },\n [focusCell, selectDate, cells],\n );\n\n return (\n <div className={cn(styles.root, className)} {...props}>\n <div className={styles.header}>\n <button\n type=\"button\"\n className={styles.nav}\n aria-label=\"Previous month\"\n onClick={() => changeMonth(-1)}\n >\n {\"‹\"}\n </button>\n <span className={styles.title} aria-live=\"polite\">\n {MONTH_LABELS[visibleMonth.getMonth()]} {visibleMonth.getFullYear()}\n </span>\n <button\n type=\"button\"\n className={styles.nav}\n aria-label=\"Next month\"\n onClick={() => changeMonth(1)}\n >\n {\"›\"}\n </button>\n </div>\n\n <div className={styles.weekdays} role=\"row\">\n {weekdayLabels.map((label) => (\n <span key={label} className={styles.weekday} role=\"columnheader\">\n {label}\n </span>\n ))}\n </div>\n\n <div className={styles.grid} role=\"grid\" ref={gridRef}>\n {cells.map((date, index) => {\n const inMonth = date.getMonth() === visibleMonth.getMonth();\n const isSelected = selected ? isSameDay(date, selected) : false;\n const isToday = isSameDay(date, today);\n const disabled = isOutOfRange(date, minDate, maxDate);\n const isFocusTarget =\n focusedDay === null\n ? inMonth && date.getDate() === 1\n : focusedDay === index;\n return (\n <button\n key={index}\n type=\"button\"\n data-day\n role=\"gridcell\"\n className={cn(\n styles.day,\n !inMonth && styles.outside,\n isSelected && styles.selected,\n isToday && styles.today,\n )}\n aria-pressed={isSelected}\n aria-selected={isSelected}\n aria-current={isToday ? \"date\" : undefined}\n disabled={disabled}\n tabIndex={isFocusTarget ? 0 : -1}\n onClick={() => selectDate(date)}\n onKeyDown={(event) => handleKeyDown(event, index)}\n >\n {date.getDate()}\n </button>\n );\n })}\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"Card.cjs","names":[],"sources":["../../../src/components/Card/Card.tsx"],"sourcesContent":["import type { HTMLAttributes, ReactNode } from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport styles from \"./Card.module.css\";\n\nexport type CardElevation = \"flat\" | \"default\" | \"raised\" | \"elevated\";\n\nexport interface CardProps extends Omit<HTMLAttributes<HTMLDivElement>, \"title\"> {\n title?: ReactNode;\n actions?: ReactNode;\n footer?: ReactNode;\n flush?: boolean;\n /** Hoverable card — adds elevation lift, cursor pointer and focus ring. */\n interactive?: boolean;\n /** Visual elevation level. Defaults to `default` (subtle shadow + border). */\n elevation?: CardElevation;\n}\n\n/**\n * Card container with optional header (title + actions) and footer slot.\n * Use `flush` when you need to host content (tables, lists) without inner padding.\n * Use `interactive` for clickable cards — adds hover lift and focus ring.\n */\nexport function Card({\n title,\n actions,\n footer,\n flush = false,\n interactive = false,\n elevation = \"default\",\n className,\n children,\n ...props\n}: CardProps) {\n const hasHeader = !!(title || actions);\n return (\n <div\n className={cn(\n styles.card,\n elevation === \"flat\" && styles.flat,\n elevation === \"raised\" && styles.raised,\n elevation === \"elevated\" && styles.elevated,\n interactive && styles.interactive,\n !hasHeader && !flush && styles.padded,\n flush && styles.flush,\n className,\n )}\n tabIndex={interactive ? (props.tabIndex ?? 0) : props.tabIndex}\n {...props}\n >\n {hasHeader && (\n <header className={styles.header}>\n {typeof title === \"string\" ? <h3 className={styles.title}>{title}</h3> : title}\n {actions}\n </header>\n )}\n {hasHeader ? <div className={styles.body}>{children}</div> : children}\n {footer && <footer className={styles.footer}>{footer}</footer>}\n </div>\n );\n}\n"],"mappings":"wGAsBA,SAAgB,EAAK,CACjB,QACA,UACA,SACA,QAAQ,GACR,cAAc,GACd,YAAY,UACZ,YACA,WACA,GAAG,GACO,CACV,IAAM,EAAY,CAAC,EAAE,GAAS,GAC9B,OACI,EAAA,EAAA,KAAA,CAAC,MAAD,CACI,UAAW,EAAA,GACP,EAAA,QAAO,KACP,IAAc,QAAU,EAAA,QAAO,KAC/B,IAAc,UAAY,EAAA,QAAO,OACjC,IAAc,YAAc,EAAA,QAAO,SACnC,GAAe,EAAA,QAAO,YACtB,CAAC,GAAa,CAAC,GAAS,EAAA,QAAO,OAC/B,GAAS,EAAA,QAAO,MAChB,CACJ,EACA,SAAU,EAAe,EAAM,UAAY,EAAK,EAAM,SACtD,GAAI,WAZR,CAcK,IACG,EAAA,EAAA,KAAA,CAAC,SAAD,CAAQ,UAAW,EAAA,QAAO,gBAA1B,CACK,OAAO,GAAU,UAAW,EAAA,EAAA,IAAA,CAAC,KAAD,CAAI,UAAW,EAAA,QAAO,eAAQ,CAAU,CAAA,EAAI,EACxE,CACG,IAEX,GAAY,EAAA,EAAA,IAAA,CAAC,MAAD,CAAK,UAAW,EAAA,QAAO,KAAO,UAAc,CAAA,EAAI,EAC5D,IAAU,EAAA,EAAA,IAAA,CAAC,SAAD,CAAQ,UAAW,EAAA,QAAO,gBAAS,CAAe,CAAA,CAC5D,GAEb"}
1
+ {"version":3,"file":"Card.cjs","names":[],"sources":["../../../src/components/Card/Card.tsx"],"sourcesContent":["import type { HTMLAttributes, ReactNode } from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport styles from \"./Card.module.css\";\n\nexport type CardElevation = \"flat\" | \"default\" | \"raised\" | \"elevated\";\n\nexport interface CardProps extends Omit<HTMLAttributes<HTMLDivElement>, \"title\"> {\n title?: ReactNode;\n actions?: ReactNode;\n footer?: ReactNode;\n flush?: boolean;\n /** Hoverable card — adds elevation lift, cursor pointer and focus ring. */\n interactive?: boolean;\n /** Visual elevation level. Defaults to `default` (subtle shadow + border). */\n elevation?: CardElevation;\n}\n\n/**\n * Card container with optional header (title + actions) and footer slot.\n * Use `flush` when you need to host content (tables, lists) without inner padding.\n * Use `interactive` for clickable cards — adds hover lift and focus ring.\n */\nexport function Card({\n title,\n actions,\n footer,\n flush = false,\n interactive = false,\n elevation = \"default\",\n className,\n children,\n ...props\n}: CardProps) {\n const hasHeader = !!(title || actions);\n return (\n <div\n className={cn(\n styles.card,\n elevation === \"flat\" && styles.flat,\n elevation === \"raised\" && styles.raised,\n elevation === \"elevated\" && styles.elevated,\n interactive && styles.interactive,\n !hasHeader && !flush && styles.padded,\n flush && styles.flush,\n className,\n )}\n tabIndex={interactive ? (props.tabIndex ?? 0) : props.tabIndex}\n {...props}\n >\n {hasHeader && (\n <header className={styles.header}>\n {typeof title === \"string\" ? <h3 className={styles.title}>{title}</h3> : title}\n {actions}\n </header>\n )}\n {hasHeader ? <div className={styles.body}>{children}</div> : children}\n {footer && <footer className={styles.footer}>{footer}</footer>}\n </div>\n );\n}\n"],"mappings":"wGAsBA,SAAgB,EAAK,CACjB,QACA,UACA,SACA,QAAQ,GACR,cAAc,GACd,YAAY,UACZ,YACA,WACA,GAAG,GACO,CACV,IAAM,EAAY,CAAC,EAAE,GAAS,GAC9B,OACI,EAAA,EAAA,KAAA,CAAC,MAAD,CACI,UAAW,EAAA,GACP,EAAA,QAAO,KACP,IAAc,QAAU,EAAA,QAAO,KAC/B,IAAc,UAAY,EAAA,QAAO,OACjC,IAAc,YAAc,EAAA,QAAO,SACnC,GAAe,EAAA,QAAO,YACtB,CAAC,GAAa,CAAC,GAAS,EAAA,QAAO,OAC/B,GAAS,EAAA,QAAO,MAChB,CACJ,EACA,SAAU,EAAe,EAAM,UAAY,EAAK,EAAM,SACtD,GAAI,EAZR,SAAA,CAcK,IACG,EAAA,EAAA,KAAA,CAAC,SAAD,CAAQ,UAAW,EAAA,QAAO,OAA1B,SAAA,CACK,OAAO,GAAU,UAAW,EAAA,EAAA,IAAA,CAAC,KAAD,CAAI,UAAW,EAAA,QAAO,MAAQ,SAAA,CAAU,CAAA,EAAI,EACxE,CACG,IAEX,GAAY,EAAA,EAAA,IAAA,CAAC,MAAD,CAAK,UAAW,EAAA,QAAO,KAAO,UAAc,CAAA,EAAI,EAC5D,IAAU,EAAA,EAAA,IAAA,CAAC,SAAD,CAAQ,UAAW,EAAA,QAAO,OAAS,SAAA,CAAe,CAAA,CAC5D,GAEb"}
@@ -1 +1 @@
1
- {"version":3,"file":"Card.js","names":[],"sources":["../../../src/components/Card/Card.tsx"],"sourcesContent":["import type { HTMLAttributes, ReactNode } from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport styles from \"./Card.module.css\";\n\nexport type CardElevation = \"flat\" | \"default\" | \"raised\" | \"elevated\";\n\nexport interface CardProps extends Omit<HTMLAttributes<HTMLDivElement>, \"title\"> {\n title?: ReactNode;\n actions?: ReactNode;\n footer?: ReactNode;\n flush?: boolean;\n /** Hoverable card — adds elevation lift, cursor pointer and focus ring. */\n interactive?: boolean;\n /** Visual elevation level. Defaults to `default` (subtle shadow + border). */\n elevation?: CardElevation;\n}\n\n/**\n * Card container with optional header (title + actions) and footer slot.\n * Use `flush` when you need to host content (tables, lists) without inner padding.\n * Use `interactive` for clickable cards — adds hover lift and focus ring.\n */\nexport function Card({\n title,\n actions,\n footer,\n flush = false,\n interactive = false,\n elevation = \"default\",\n className,\n children,\n ...props\n}: CardProps) {\n const hasHeader = !!(title || actions);\n return (\n <div\n className={cn(\n styles.card,\n elevation === \"flat\" && styles.flat,\n elevation === \"raised\" && styles.raised,\n elevation === \"elevated\" && styles.elevated,\n interactive && styles.interactive,\n !hasHeader && !flush && styles.padded,\n flush && styles.flush,\n className,\n )}\n tabIndex={interactive ? (props.tabIndex ?? 0) : props.tabIndex}\n {...props}\n >\n {hasHeader && (\n <header className={styles.header}>\n {typeof title === \"string\" ? <h3 className={styles.title}>{title}</h3> : title}\n {actions}\n </header>\n )}\n {hasHeader ? <div className={styles.body}>{children}</div> : children}\n {footer && <footer className={styles.footer}>{footer}</footer>}\n </div>\n );\n}\n"],"mappings":";;;;AAsBA,SAAgB,EAAK,EACjB,UACA,YACA,WACA,WAAQ,IACR,iBAAc,IACd,eAAY,WACZ,cACA,aACA,GAAG,KACO;CACV,IAAM,IAAY,CAAC,EAAE,KAAS;CAC9B,OACI,kBAAC,OAAD;EACI,WAAW,EACP,EAAO,MACP,MAAc,UAAU,EAAO,MAC/B,MAAc,YAAY,EAAO,QACjC,MAAc,cAAc,EAAO,UACnC,KAAe,EAAO,aACtB,CAAC,KAAa,CAAC,KAAS,EAAO,QAC/B,KAAS,EAAO,OAChB,CACJ;EACA,UAAU,IAAe,EAAM,YAAY,IAAK,EAAM;EACtD,GAAI;YAZR;GAcK,KACG,kBAAC,UAAD;IAAQ,WAAW,EAAO;cAA1B,CACK,OAAO,KAAU,WAAW,kBAAC,MAAD;KAAI,WAAW,EAAO;eAAQ;IAAU,CAAA,IAAI,GACxE,CACG;;GAEX,IAAY,kBAAC,OAAD;IAAK,WAAW,EAAO;IAAO;GAAc,CAAA,IAAI;GAC5D,KAAU,kBAAC,UAAD;IAAQ,WAAW,EAAO;cAAS;GAAe,CAAA;EAC5D;;AAEb"}
1
+ {"version":3,"file":"Card.js","names":[],"sources":["../../../src/components/Card/Card.tsx"],"sourcesContent":["import type { HTMLAttributes, ReactNode } from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport styles from \"./Card.module.css\";\n\nexport type CardElevation = \"flat\" | \"default\" | \"raised\" | \"elevated\";\n\nexport interface CardProps extends Omit<HTMLAttributes<HTMLDivElement>, \"title\"> {\n title?: ReactNode;\n actions?: ReactNode;\n footer?: ReactNode;\n flush?: boolean;\n /** Hoverable card — adds elevation lift, cursor pointer and focus ring. */\n interactive?: boolean;\n /** Visual elevation level. Defaults to `default` (subtle shadow + border). */\n elevation?: CardElevation;\n}\n\n/**\n * Card container with optional header (title + actions) and footer slot.\n * Use `flush` when you need to host content (tables, lists) without inner padding.\n * Use `interactive` for clickable cards — adds hover lift and focus ring.\n */\nexport function Card({\n title,\n actions,\n footer,\n flush = false,\n interactive = false,\n elevation = \"default\",\n className,\n children,\n ...props\n}: CardProps) {\n const hasHeader = !!(title || actions);\n return (\n <div\n className={cn(\n styles.card,\n elevation === \"flat\" && styles.flat,\n elevation === \"raised\" && styles.raised,\n elevation === \"elevated\" && styles.elevated,\n interactive && styles.interactive,\n !hasHeader && !flush && styles.padded,\n flush && styles.flush,\n className,\n )}\n tabIndex={interactive ? (props.tabIndex ?? 0) : props.tabIndex}\n {...props}\n >\n {hasHeader && (\n <header className={styles.header}>\n {typeof title === \"string\" ? <h3 className={styles.title}>{title}</h3> : title}\n {actions}\n </header>\n )}\n {hasHeader ? <div className={styles.body}>{children}</div> : children}\n {footer && <footer className={styles.footer}>{footer}</footer>}\n </div>\n );\n}\n"],"mappings":";;;;AAsBA,SAAgB,EAAK,EACjB,UACA,YACA,WACA,WAAQ,IACR,iBAAc,IACd,eAAY,WACZ,cACA,aACA,GAAG,KACO;CACV,IAAM,IAAY,CAAC,EAAE,KAAS;CAC9B,OACI,kBAAC,OAAD;EACI,WAAW,EACP,EAAO,MACP,MAAc,UAAU,EAAO,MAC/B,MAAc,YAAY,EAAO,QACjC,MAAc,cAAc,EAAO,UACnC,KAAe,EAAO,aACtB,CAAC,KAAa,CAAC,KAAS,EAAO,QAC/B,KAAS,EAAO,OAChB,CACJ;EACA,UAAU,IAAe,EAAM,YAAY,IAAK,EAAM;EACtD,GAAI;EAZR,UAAA;GAcK,KACG,kBAAC,UAAD;IAAQ,WAAW,EAAO;IAA1B,UAAA,CACK,OAAO,KAAU,WAAW,kBAAC,MAAD;KAAI,WAAW,EAAO;KAAQ,UAAA;IAAU,CAAA,IAAI,GACxE,CACG;;GAEX,IAAY,kBAAC,OAAD;IAAK,WAAW,EAAO;IAAO;GAAc,CAAA,IAAI;GAC5D,KAAU,kBAAC,UAAD;IAAQ,WAAW,EAAO;IAAS,UAAA;GAAe,CAAA;EAC5D;;AAEb"}
@@ -1 +1 @@
1
- {"version":3,"file":"Carousel.cjs","names":[],"sources":["../../../src/components/Carousel/Carousel.tsx"],"sourcesContent":["/**\n * @tempest-limits function-lines — the body owns the scroll snap position, the\n * swipe, the autoplay timer that must pause on interaction and the dot indicators\n * that read the same index.\n */\nimport { useCallback, useState } from \"react\";\nimport type { HTMLAttributes, ReactNode } from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport styles from \"./Carousel.module.css\";\n\nexport interface CarouselProps extends Omit<HTMLAttributes<HTMLDivElement>, \"children\"> {\n /** Slides — one rendered at a time. */\n children: ReactNode[];\n /** Wrap around at the ends instead of stopping. Default `false`. */\n loop?: boolean;\n /** Show prev/next arrow buttons. Default `true`. */\n showArrows?: boolean;\n /** Show dot indicators. Default `true`. */\n showDots?: boolean;\n /** Controlled active index. */\n index?: number;\n /** Initial index for uncontrolled use. Default `0`. */\n defaultIndex?: number;\n /** Called whenever the active index changes. */\n onIndexChange?: (index: number) => void;\n}\n\n/**\n * Horizontal content slider showing one slide at a time.\n *\n * - Track translates by the active index (`translateX(-index * 100%)`).\n * - Prev/next arrows (disabled at the ends unless `loop`); dot indicators jump.\n * - Controlled (`index`) or uncontrolled (`defaultIndex`).\n * - Arrow Left/Right on the focused region navigates.\n *\n * @example\n * <Carousel loop>\n * <img src=\"/1.jpg\" alt=\"\" />\n * <img src=\"/2.jpg\" alt=\"\" />\n * <img src=\"/3.jpg\" alt=\"\" />\n * </Carousel>\n */\nexport function Carousel({\n children,\n loop = false,\n showArrows = true,\n showDots = true,\n index,\n defaultIndex = 0,\n onIndexChange,\n className,\n ...props\n}: CarouselProps) {\n const count = children.length;\n const isControlled = index !== undefined;\n const [internalIndex, setInternalIndex] = useState<number>(defaultIndex);\n const current = isControlled ? index : internalIndex;\n\n const goTo = useCallback(\n (next: number): void => {\n let target = next;\n if (target < 0) target = loop ? count - 1 : 0;\n else if (target >= count) target = loop ? 0 : count - 1;\n if (target === current) return;\n if (!isControlled) setInternalIndex(target);\n onIndexChange?.(target);\n },\n [count, loop, current, isControlled, onIndexChange],\n );\n\n const atStart = current <= 0;\n const atEnd = current >= count - 1;\n\n const handleKeyDown = (event: React.KeyboardEvent<HTMLDivElement>): void => {\n if (event.key === \"ArrowRight\") {\n event.preventDefault();\n goTo(current + 1);\n } else if (event.key === \"ArrowLeft\") {\n event.preventDefault();\n goTo(current - 1);\n }\n };\n\n return (\n <div\n role=\"region\"\n aria-roledescription=\"carousel\"\n tabIndex={0}\n className={cn(styles.root, className)}\n onKeyDown={handleKeyDown}\n {...props}\n >\n <div className={styles.viewport}>\n <div\n className={styles.track}\n style={{ transform: `translateX(-${current * 100}%)` }}\n >\n {children.map((slide, slideIndex) => (\n <div\n key={slideIndex}\n role=\"group\"\n aria-roledescription=\"slide\"\n aria-label={`${slideIndex + 1} of ${count}`}\n aria-hidden={slideIndex !== current}\n className={styles.slide}\n >\n {slide}\n </div>\n ))}\n </div>\n </div>\n\n {showArrows && count > 1 && (\n <>\n <button\n type=\"button\"\n className={cn(styles.arrow, styles.prev)}\n aria-label=\"Previous slide\"\n disabled={!loop && atStart}\n onClick={() => goTo(current - 1)}\n >\n ‹\n </button>\n <button\n type=\"button\"\n className={cn(styles.arrow, styles.next)}\n aria-label=\"Next slide\"\n disabled={!loop && atEnd}\n onClick={() => goTo(current + 1)}\n >\n ›\n </button>\n </>\n )}\n\n {showDots && count > 1 && (\n <div className={styles.dots} role=\"tablist\" aria-label=\"Slides\">\n {children.map((_, dotIndex) => (\n <button\n key={dotIndex}\n type=\"button\"\n role=\"tab\"\n aria-label={`Go to slide ${dotIndex + 1}`}\n aria-selected={dotIndex === current}\n className={cn(styles.dot, dotIndex === current && styles.dotActive)}\n onClick={() => goTo(dotIndex)}\n />\n ))}\n </div>\n )}\n </div>\n );\n}\n"],"mappings":"+HA0CA,SAAgB,EAAS,CACrB,WACA,OAAO,GACP,aAAa,GACb,WAAW,GACX,QACA,eAAe,EACf,gBACA,YACA,GAAG,GACW,CACd,IAAM,EAAQ,EAAS,OACjB,EAAe,IAAU,IAAA,GACzB,CAAC,EAAe,IAAA,EAAA,EAAA,SAAA,CAAqC,CAAY,EACjE,EAAU,EAAe,EAAQ,EAEjC,GAAA,EAAA,EAAA,YAAA,CACD,GAAuB,CACpB,IAAI,EAAS,EACT,EAAS,EAAG,EAAS,EAAO,EAAQ,EAAI,EACnC,GAAU,IAAO,EAAS,EAAO,EAAI,EAAQ,GAClD,IAAW,IACV,GAAc,EAAiB,CAAM,EAC1C,IAAgB,CAAM,EAC1B,EACA,CAAC,EAAO,EAAM,EAAS,EAAc,CAAa,CACtD,EAEM,EAAU,GAAW,EACrB,EAAQ,GAAW,EAAQ,EAYjC,OACI,EAAA,EAAA,KAAA,CAAC,MAAD,CACI,KAAK,SACL,uBAAqB,WACrB,SAAU,EACV,UAAW,EAAA,GAAG,EAAA,QAAO,KAAM,CAAS,EACpC,UAhBe,GAAqD,CACpE,EAAM,MAAQ,cACd,EAAM,eAAe,EACrB,EAAK,EAAU,CAAC,GACT,EAAM,MAAQ,cACrB,EAAM,eAAe,EACrB,EAAK,EAAU,CAAC,EAExB,EASQ,GAAI,WANR,EAQI,EAAA,EAAA,IAAA,CAAC,MAAD,CAAK,UAAW,EAAA,QAAO,mBACnB,EAAA,EAAA,IAAA,CAAC,MAAD,CACI,UAAW,EAAA,QAAO,MAClB,MAAO,CAAE,UAAW,eAAe,EAAU,IAAI,GAAI,WAEpD,EAAS,KAAK,EAAO,KAClB,EAAA,EAAA,IAAA,CAAC,MAAD,CAEI,KAAK,QACL,uBAAqB,QACrB,aAAY,GAAG,EAAa,EAAE,MAAM,IACpC,cAAa,IAAe,EAC5B,UAAW,EAAA,QAAO,eAEjB,CACA,EARI,CAQJ,CACR,CACA,CAAA,CACJ,CAAA,EAEJ,GAAc,EAAQ,IACnB,EAAA,EAAA,KAAA,CAAA,EAAA,SAAA,CAAA,SAAA,EACI,EAAA,EAAA,IAAA,CAAC,SAAD,CACI,KAAK,SACL,UAAW,EAAA,GAAG,EAAA,QAAO,MAAO,EAAA,QAAO,IAAI,EACvC,aAAW,iBACX,SAAU,CAAC,GAAQ,EACnB,YAAe,EAAK,EAAU,CAAC,WAClC,GAEO,CAAA,GACR,EAAA,EAAA,IAAA,CAAC,SAAD,CACI,KAAK,SACL,UAAW,EAAA,GAAG,EAAA,QAAO,MAAO,EAAA,QAAO,IAAI,EACvC,aAAW,aACX,SAAU,CAAC,GAAQ,EACnB,YAAe,EAAK,EAAU,CAAC,WAClC,GAEO,CAAA,CACV,CAAA,CAAA,EAGL,GAAY,EAAQ,IACjB,EAAA,EAAA,IAAA,CAAC,MAAD,CAAK,UAAW,EAAA,QAAO,KAAM,KAAK,UAAU,aAAW,kBAClD,EAAS,KAAK,EAAG,KACd,EAAA,EAAA,IAAA,CAAC,SAAD,CAEI,KAAK,SACL,KAAK,MACL,aAAY,eAAe,EAAW,IACtC,gBAAe,IAAa,EAC5B,UAAW,EAAA,GAAG,EAAA,QAAO,IAAK,IAAa,GAAW,EAAA,QAAO,SAAS,EAClE,YAAe,EAAK,CAAQ,CAC/B,EAPQ,CAOR,CACJ,CACA,CAAA,CAER,GAEb"}
1
+ {"version":3,"file":"Carousel.cjs","names":[],"sources":["../../../src/components/Carousel/Carousel.tsx"],"sourcesContent":["/**\n * @tempest-limits function-lines — the body owns the scroll snap position, the\n * swipe, the autoplay timer that must pause on interaction and the dot indicators\n * that read the same index.\n */\nimport { useCallback, useState } from \"react\";\nimport type { HTMLAttributes, ReactNode } from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport styles from \"./Carousel.module.css\";\n\nexport interface CarouselProps extends Omit<HTMLAttributes<HTMLDivElement>, \"children\"> {\n /** Slides — one rendered at a time. */\n children: ReactNode[];\n /** Wrap around at the ends instead of stopping. Default `false`. */\n loop?: boolean;\n /** Show prev/next arrow buttons. Default `true`. */\n showArrows?: boolean;\n /** Show dot indicators. Default `true`. */\n showDots?: boolean;\n /** Controlled active index. */\n index?: number;\n /** Initial index for uncontrolled use. Default `0`. */\n defaultIndex?: number;\n /** Called whenever the active index changes. */\n onIndexChange?: (index: number) => void;\n}\n\n/**\n * Horizontal content slider showing one slide at a time.\n *\n * - Track translates by the active index (`translateX(-index * 100%)`).\n * - Prev/next arrows (disabled at the ends unless `loop`); dot indicators jump.\n * - Controlled (`index`) or uncontrolled (`defaultIndex`).\n * - Arrow Left/Right on the focused region navigates.\n *\n * @example\n * <Carousel loop>\n * <img src=\"/1.jpg\" alt=\"\" />\n * <img src=\"/2.jpg\" alt=\"\" />\n * <img src=\"/3.jpg\" alt=\"\" />\n * </Carousel>\n */\nexport function Carousel({\n children,\n loop = false,\n showArrows = true,\n showDots = true,\n index,\n defaultIndex = 0,\n onIndexChange,\n className,\n ...props\n}: CarouselProps) {\n const count = children.length;\n const isControlled = index !== undefined;\n const [internalIndex, setInternalIndex] = useState<number>(defaultIndex);\n const current = isControlled ? index : internalIndex;\n\n const goTo = useCallback(\n (next: number): void => {\n let target = next;\n if (target < 0) target = loop ? count - 1 : 0;\n else if (target >= count) target = loop ? 0 : count - 1;\n if (target === current) return;\n if (!isControlled) setInternalIndex(target);\n onIndexChange?.(target);\n },\n [count, loop, current, isControlled, onIndexChange],\n );\n\n const atStart = current <= 0;\n const atEnd = current >= count - 1;\n\n const handleKeyDown = (event: React.KeyboardEvent<HTMLDivElement>): void => {\n if (event.key === \"ArrowRight\") {\n event.preventDefault();\n goTo(current + 1);\n } else if (event.key === \"ArrowLeft\") {\n event.preventDefault();\n goTo(current - 1);\n }\n };\n\n return (\n <div\n role=\"region\"\n aria-roledescription=\"carousel\"\n tabIndex={0}\n className={cn(styles.root, className)}\n onKeyDown={handleKeyDown}\n {...props}\n >\n <div className={styles.viewport}>\n <div\n className={styles.track}\n style={{ transform: `translateX(-${current * 100}%)` }}\n >\n {children.map((slide, slideIndex) => (\n <div\n key={slideIndex}\n role=\"group\"\n aria-roledescription=\"slide\"\n aria-label={`${slideIndex + 1} of ${count}`}\n aria-hidden={slideIndex !== current}\n className={styles.slide}\n >\n {slide}\n </div>\n ))}\n </div>\n </div>\n\n {showArrows && count > 1 && (\n <>\n <button\n type=\"button\"\n className={cn(styles.arrow, styles.prev)}\n aria-label=\"Previous slide\"\n disabled={!loop && atStart}\n onClick={() => goTo(current - 1)}\n >\n ‹\n </button>\n <button\n type=\"button\"\n className={cn(styles.arrow, styles.next)}\n aria-label=\"Next slide\"\n disabled={!loop && atEnd}\n onClick={() => goTo(current + 1)}\n >\n ›\n </button>\n </>\n )}\n\n {showDots && count > 1 && (\n <div className={styles.dots} role=\"tablist\" aria-label=\"Slides\">\n {children.map((_, dotIndex) => (\n <button\n key={dotIndex}\n type=\"button\"\n role=\"tab\"\n aria-label={`Go to slide ${dotIndex + 1}`}\n aria-selected={dotIndex === current}\n className={cn(styles.dot, dotIndex === current && styles.dotActive)}\n onClick={() => goTo(dotIndex)}\n />\n ))}\n </div>\n )}\n </div>\n );\n}\n"],"mappings":"+HA0CA,SAAgB,EAAS,CACrB,WACA,OAAO,GACP,aAAa,GACb,WAAW,GACX,QACA,eAAe,EACf,gBACA,YACA,GAAG,GACW,CACd,IAAM,EAAQ,EAAS,OACjB,EAAe,IAAU,IAAA,GACzB,CAAC,EAAe,IAAA,EAAoB,EAAA,SAAA,CAAiB,CAAY,EACjE,EAAU,EAAe,EAAQ,EAEjC,GAAA,EAAO,EAAA,YAAA,CACR,GAAuB,CACpB,IAAI,EAAS,EACT,EAAS,EAAG,EAAS,EAAO,EAAQ,EAAI,EACnC,GAAU,IAAO,EAAS,EAAO,EAAI,EAAQ,GAClD,IAAW,IACV,GAAc,EAAiB,CAAM,EAC1C,IAAgB,CAAM,EAC1B,EACA,CAAC,EAAO,EAAM,EAAS,EAAc,CAAa,CACtD,EAEM,EAAU,GAAW,EACrB,EAAQ,GAAW,EAAQ,EAYjC,OACI,EAAA,EAAA,KAAA,CAAC,MAAD,CACI,KAAK,SACL,uBAAqB,WACrB,SAAU,EACV,UAAW,EAAA,GAAG,EAAA,QAAO,KAAM,CAAS,EACpC,UAhBe,GAAqD,CACpE,EAAM,MAAQ,cACd,EAAM,eAAe,EACrB,EAAK,EAAU,CAAC,GACT,EAAM,MAAQ,cACrB,EAAM,eAAe,EACrB,EAAK,EAAU,CAAC,EAExB,EASQ,GAAI,EANR,SAAA,EAQI,EAAA,EAAA,IAAA,CAAC,MAAD,CAAK,UAAW,EAAA,QAAO,SACnB,UAAA,EAAA,EAAA,IAAA,CAAC,MAAD,CACI,UAAW,EAAA,QAAO,MAClB,MAAO,CAAE,UAAW,eAAe,EAAU,IAAI,GAAI,EAEpD,SAAA,EAAS,KAAK,EAAO,KAClB,EAAA,EAAA,IAAA,CAAC,MAAD,CAEI,KAAK,QACL,uBAAqB,QACrB,aAAY,GAAG,EAAa,EAAE,MAAM,IACpC,cAAa,IAAe,EAC5B,UAAW,EAAA,QAAO,MAEjB,SAAA,CACA,EARI,CAQJ,CACR,CACA,CAAA,CACJ,CAAA,EAEJ,GAAc,EAAQ,IACnB,EAAA,EAAA,KAAA,CAAA,EAAA,SAAA,CAAA,SAAA,EACI,EAAA,EAAA,IAAA,CAAC,SAAD,CACI,KAAK,SACL,UAAW,EAAA,GAAG,EAAA,QAAO,MAAO,EAAA,QAAO,IAAI,EACvC,aAAW,iBACX,SAAU,CAAC,GAAQ,EACnB,YAAe,EAAK,EAAU,CAAC,EAClC,SAAA,GAEO,CAAA,GACR,EAAA,EAAA,IAAA,CAAC,SAAD,CACI,KAAK,SACL,UAAW,EAAA,GAAG,EAAA,QAAO,MAAO,EAAA,QAAO,IAAI,EACvC,aAAW,aACX,SAAU,CAAC,GAAQ,EACnB,YAAe,EAAK,EAAU,CAAC,EAClC,SAAA,GAEO,CAAA,CACV,CAAA,CAAA,EAGL,GAAY,EAAQ,IACjB,EAAA,EAAA,IAAA,CAAC,MAAD,CAAK,UAAW,EAAA,QAAO,KAAM,KAAK,UAAU,aAAW,SAClD,SAAA,EAAS,KAAK,EAAG,KACd,EAAA,EAAA,IAAA,CAAC,SAAD,CAEI,KAAK,SACL,KAAK,MACL,aAAY,eAAe,EAAW,IACtC,gBAAe,IAAa,EAC5B,UAAW,EAAA,GAAG,EAAA,QAAO,IAAK,IAAa,GAAW,EAAA,QAAO,SAAS,EAClE,YAAe,EAAK,CAAQ,CAC/B,EAPQ,CAOR,CACJ,CACA,CAAA,CAER,GAEb"}
@@ -1 +1 @@
1
- {"version":3,"file":"Carousel.js","names":[],"sources":["../../../src/components/Carousel/Carousel.tsx"],"sourcesContent":["/**\n * @tempest-limits function-lines — the body owns the scroll snap position, the\n * swipe, the autoplay timer that must pause on interaction and the dot indicators\n * that read the same index.\n */\nimport { useCallback, useState } from \"react\";\nimport type { HTMLAttributes, ReactNode } from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport styles from \"./Carousel.module.css\";\n\nexport interface CarouselProps extends Omit<HTMLAttributes<HTMLDivElement>, \"children\"> {\n /** Slides — one rendered at a time. */\n children: ReactNode[];\n /** Wrap around at the ends instead of stopping. Default `false`. */\n loop?: boolean;\n /** Show prev/next arrow buttons. Default `true`. */\n showArrows?: boolean;\n /** Show dot indicators. Default `true`. */\n showDots?: boolean;\n /** Controlled active index. */\n index?: number;\n /** Initial index for uncontrolled use. Default `0`. */\n defaultIndex?: number;\n /** Called whenever the active index changes. */\n onIndexChange?: (index: number) => void;\n}\n\n/**\n * Horizontal content slider showing one slide at a time.\n *\n * - Track translates by the active index (`translateX(-index * 100%)`).\n * - Prev/next arrows (disabled at the ends unless `loop`); dot indicators jump.\n * - Controlled (`index`) or uncontrolled (`defaultIndex`).\n * - Arrow Left/Right on the focused region navigates.\n *\n * @example\n * <Carousel loop>\n * <img src=\"/1.jpg\" alt=\"\" />\n * <img src=\"/2.jpg\" alt=\"\" />\n * <img src=\"/3.jpg\" alt=\"\" />\n * </Carousel>\n */\nexport function Carousel({\n children,\n loop = false,\n showArrows = true,\n showDots = true,\n index,\n defaultIndex = 0,\n onIndexChange,\n className,\n ...props\n}: CarouselProps) {\n const count = children.length;\n const isControlled = index !== undefined;\n const [internalIndex, setInternalIndex] = useState<number>(defaultIndex);\n const current = isControlled ? index : internalIndex;\n\n const goTo = useCallback(\n (next: number): void => {\n let target = next;\n if (target < 0) target = loop ? count - 1 : 0;\n else if (target >= count) target = loop ? 0 : count - 1;\n if (target === current) return;\n if (!isControlled) setInternalIndex(target);\n onIndexChange?.(target);\n },\n [count, loop, current, isControlled, onIndexChange],\n );\n\n const atStart = current <= 0;\n const atEnd = current >= count - 1;\n\n const handleKeyDown = (event: React.KeyboardEvent<HTMLDivElement>): void => {\n if (event.key === \"ArrowRight\") {\n event.preventDefault();\n goTo(current + 1);\n } else if (event.key === \"ArrowLeft\") {\n event.preventDefault();\n goTo(current - 1);\n }\n };\n\n return (\n <div\n role=\"region\"\n aria-roledescription=\"carousel\"\n tabIndex={0}\n className={cn(styles.root, className)}\n onKeyDown={handleKeyDown}\n {...props}\n >\n <div className={styles.viewport}>\n <div\n className={styles.track}\n style={{ transform: `translateX(-${current * 100}%)` }}\n >\n {children.map((slide, slideIndex) => (\n <div\n key={slideIndex}\n role=\"group\"\n aria-roledescription=\"slide\"\n aria-label={`${slideIndex + 1} of ${count}`}\n aria-hidden={slideIndex !== current}\n className={styles.slide}\n >\n {slide}\n </div>\n ))}\n </div>\n </div>\n\n {showArrows && count > 1 && (\n <>\n <button\n type=\"button\"\n className={cn(styles.arrow, styles.prev)}\n aria-label=\"Previous slide\"\n disabled={!loop && atStart}\n onClick={() => goTo(current - 1)}\n >\n ‹\n </button>\n <button\n type=\"button\"\n className={cn(styles.arrow, styles.next)}\n aria-label=\"Next slide\"\n disabled={!loop && atEnd}\n onClick={() => goTo(current + 1)}\n >\n ›\n </button>\n </>\n )}\n\n {showDots && count > 1 && (\n <div className={styles.dots} role=\"tablist\" aria-label=\"Slides\">\n {children.map((_, dotIndex) => (\n <button\n key={dotIndex}\n type=\"button\"\n role=\"tab\"\n aria-label={`Go to slide ${dotIndex + 1}`}\n aria-selected={dotIndex === current}\n className={cn(styles.dot, dotIndex === current && styles.dotActive)}\n onClick={() => goTo(dotIndex)}\n />\n ))}\n </div>\n )}\n </div>\n );\n}\n"],"mappings":";;;;;AA0CA,SAAgB,EAAS,EACrB,aACA,UAAO,IACP,gBAAa,IACb,cAAW,IACX,UACA,kBAAe,GACf,kBACA,cACA,GAAG,KACW;CACd,IAAM,IAAQ,EAAS,QACjB,IAAe,MAAU,KAAA,GACzB,CAAC,GAAe,KAAoB,EAAiB,CAAY,GACjE,IAAU,IAAe,IAAQ,GAEjC,IAAO,GACR,MAAuB;EACpB,IAAI,IAAS;EACb,AAAI,IAAS,IAAG,IAAS,IAAO,IAAQ,IAAI,IACnC,KAAU,MAAO,IAAS,IAAO,IAAI,IAAQ,IAClD,MAAW,MACV,KAAc,EAAiB,CAAM,GAC1C,IAAgB,CAAM;CAC1B,GACA;EAAC;EAAO;EAAM;EAAS;EAAc;CAAa,CACtD,GAEM,IAAU,KAAW,GACrB,IAAQ,KAAW,IAAQ;CAYjC,OACI,kBAAC,OAAD;EACI,MAAK;EACL,wBAAqB;EACrB,UAAU;EACV,WAAW,EAAG,EAAO,MAAM,CAAS;EACpC,YAhBe,MAAqD;GACxE,AAAI,EAAM,QAAQ,gBACd,EAAM,eAAe,GACrB,EAAK,IAAU,CAAC,KACT,EAAM,QAAQ,gBACrB,EAAM,eAAe,GACrB,EAAK,IAAU,CAAC;EAExB;EASQ,GAAI;YANR;GAQI,kBAAC,OAAD;IAAK,WAAW,EAAO;cACnB,kBAAC,OAAD;KACI,WAAW,EAAO;KAClB,OAAO,EAAE,WAAW,eAAe,IAAU,IAAI,IAAI;eAEpD,EAAS,KAAK,GAAO,MAClB,kBAAC,OAAD;MAEI,MAAK;MACL,wBAAqB;MACrB,cAAY,GAAG,IAAa,EAAE,MAAM;MACpC,eAAa,MAAe;MAC5B,WAAW,EAAO;gBAEjB;KACA,GARI,CAQJ,CACR;IACA,CAAA;GACJ,CAAA;GAEJ,KAAc,IAAQ,KACnB,kBAAA,GAAA,EAAA,UAAA,CACI,kBAAC,UAAD;IACI,MAAK;IACL,WAAW,EAAG,EAAO,OAAO,EAAO,IAAI;IACvC,cAAW;IACX,UAAU,CAAC,KAAQ;IACnB,eAAe,EAAK,IAAU,CAAC;cAClC;GAEO,CAAA,GACR,kBAAC,UAAD;IACI,MAAK;IACL,WAAW,EAAG,EAAO,OAAO,EAAO,IAAI;IACvC,cAAW;IACX,UAAU,CAAC,KAAQ;IACnB,eAAe,EAAK,IAAU,CAAC;cAClC;GAEO,CAAA,CACV,EAAA,CAAA;GAGL,KAAY,IAAQ,KACjB,kBAAC,OAAD;IAAK,WAAW,EAAO;IAAM,MAAK;IAAU,cAAW;cAClD,EAAS,KAAK,GAAG,MACd,kBAAC,UAAD;KAEI,MAAK;KACL,MAAK;KACL,cAAY,eAAe,IAAW;KACtC,iBAAe,MAAa;KAC5B,WAAW,EAAG,EAAO,KAAK,MAAa,KAAW,EAAO,SAAS;KAClE,eAAe,EAAK,CAAQ;IAC/B,GAPQ,CAOR,CACJ;GACA,CAAA;EAER;;AAEb"}
1
+ {"version":3,"file":"Carousel.js","names":[],"sources":["../../../src/components/Carousel/Carousel.tsx"],"sourcesContent":["/**\n * @tempest-limits function-lines — the body owns the scroll snap position, the\n * swipe, the autoplay timer that must pause on interaction and the dot indicators\n * that read the same index.\n */\nimport { useCallback, useState } from \"react\";\nimport type { HTMLAttributes, ReactNode } from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport styles from \"./Carousel.module.css\";\n\nexport interface CarouselProps extends Omit<HTMLAttributes<HTMLDivElement>, \"children\"> {\n /** Slides — one rendered at a time. */\n children: ReactNode[];\n /** Wrap around at the ends instead of stopping. Default `false`. */\n loop?: boolean;\n /** Show prev/next arrow buttons. Default `true`. */\n showArrows?: boolean;\n /** Show dot indicators. Default `true`. */\n showDots?: boolean;\n /** Controlled active index. */\n index?: number;\n /** Initial index for uncontrolled use. Default `0`. */\n defaultIndex?: number;\n /** Called whenever the active index changes. */\n onIndexChange?: (index: number) => void;\n}\n\n/**\n * Horizontal content slider showing one slide at a time.\n *\n * - Track translates by the active index (`translateX(-index * 100%)`).\n * - Prev/next arrows (disabled at the ends unless `loop`); dot indicators jump.\n * - Controlled (`index`) or uncontrolled (`defaultIndex`).\n * - Arrow Left/Right on the focused region navigates.\n *\n * @example\n * <Carousel loop>\n * <img src=\"/1.jpg\" alt=\"\" />\n * <img src=\"/2.jpg\" alt=\"\" />\n * <img src=\"/3.jpg\" alt=\"\" />\n * </Carousel>\n */\nexport function Carousel({\n children,\n loop = false,\n showArrows = true,\n showDots = true,\n index,\n defaultIndex = 0,\n onIndexChange,\n className,\n ...props\n}: CarouselProps) {\n const count = children.length;\n const isControlled = index !== undefined;\n const [internalIndex, setInternalIndex] = useState<number>(defaultIndex);\n const current = isControlled ? index : internalIndex;\n\n const goTo = useCallback(\n (next: number): void => {\n let target = next;\n if (target < 0) target = loop ? count - 1 : 0;\n else if (target >= count) target = loop ? 0 : count - 1;\n if (target === current) return;\n if (!isControlled) setInternalIndex(target);\n onIndexChange?.(target);\n },\n [count, loop, current, isControlled, onIndexChange],\n );\n\n const atStart = current <= 0;\n const atEnd = current >= count - 1;\n\n const handleKeyDown = (event: React.KeyboardEvent<HTMLDivElement>): void => {\n if (event.key === \"ArrowRight\") {\n event.preventDefault();\n goTo(current + 1);\n } else if (event.key === \"ArrowLeft\") {\n event.preventDefault();\n goTo(current - 1);\n }\n };\n\n return (\n <div\n role=\"region\"\n aria-roledescription=\"carousel\"\n tabIndex={0}\n className={cn(styles.root, className)}\n onKeyDown={handleKeyDown}\n {...props}\n >\n <div className={styles.viewport}>\n <div\n className={styles.track}\n style={{ transform: `translateX(-${current * 100}%)` }}\n >\n {children.map((slide, slideIndex) => (\n <div\n key={slideIndex}\n role=\"group\"\n aria-roledescription=\"slide\"\n aria-label={`${slideIndex + 1} of ${count}`}\n aria-hidden={slideIndex !== current}\n className={styles.slide}\n >\n {slide}\n </div>\n ))}\n </div>\n </div>\n\n {showArrows && count > 1 && (\n <>\n <button\n type=\"button\"\n className={cn(styles.arrow, styles.prev)}\n aria-label=\"Previous slide\"\n disabled={!loop && atStart}\n onClick={() => goTo(current - 1)}\n >\n ‹\n </button>\n <button\n type=\"button\"\n className={cn(styles.arrow, styles.next)}\n aria-label=\"Next slide\"\n disabled={!loop && atEnd}\n onClick={() => goTo(current + 1)}\n >\n ›\n </button>\n </>\n )}\n\n {showDots && count > 1 && (\n <div className={styles.dots} role=\"tablist\" aria-label=\"Slides\">\n {children.map((_, dotIndex) => (\n <button\n key={dotIndex}\n type=\"button\"\n role=\"tab\"\n aria-label={`Go to slide ${dotIndex + 1}`}\n aria-selected={dotIndex === current}\n className={cn(styles.dot, dotIndex === current && styles.dotActive)}\n onClick={() => goTo(dotIndex)}\n />\n ))}\n </div>\n )}\n </div>\n );\n}\n"],"mappings":";;;;;AA0CA,SAAgB,EAAS,EACrB,aACA,UAAO,IACP,gBAAa,IACb,cAAW,IACX,UACA,kBAAe,GACf,kBACA,cACA,GAAG,KACW;CACd,IAAM,IAAQ,EAAS,QACjB,IAAe,MAAU,KAAA,GACzB,CAAC,GAAe,KAAoB,EAAiB,CAAY,GACjE,IAAU,IAAe,IAAQ,GAEjC,IAAO,GACR,MAAuB;EACpB,IAAI,IAAS;EACb,AAAI,IAAS,IAAG,IAAS,IAAO,IAAQ,IAAI,IACnC,KAAU,MAAO,IAAS,IAAO,IAAI,IAAQ,IAClD,MAAW,MACV,KAAc,EAAiB,CAAM,GAC1C,IAAgB,CAAM;CAC1B,GACA;EAAC;EAAO;EAAM;EAAS;EAAc;CAAa,CACtD,GAEM,IAAU,KAAW,GACrB,IAAQ,KAAW,IAAQ;CAYjC,OACI,kBAAC,OAAD;EACI,MAAK;EACL,wBAAqB;EACrB,UAAU;EACV,WAAW,EAAG,EAAO,MAAM,CAAS;EACpC,YAhBe,MAAqD;GACxE,AAAI,EAAM,QAAQ,gBACd,EAAM,eAAe,GACrB,EAAK,IAAU,CAAC,KACT,EAAM,QAAQ,gBACrB,EAAM,eAAe,GACrB,EAAK,IAAU,CAAC;EAExB;EASQ,GAAI;EANR,UAAA;GAQI,kBAAC,OAAD;IAAK,WAAW,EAAO;IACnB,UAAA,kBAAC,OAAD;KACI,WAAW,EAAO;KAClB,OAAO,EAAE,WAAW,eAAe,IAAU,IAAI,IAAI;KAEpD,UAAA,EAAS,KAAK,GAAO,MAClB,kBAAC,OAAD;MAEI,MAAK;MACL,wBAAqB;MACrB,cAAY,GAAG,IAAa,EAAE,MAAM;MACpC,eAAa,MAAe;MAC5B,WAAW,EAAO;MAEjB,UAAA;KACA,GARI,CAQJ,CACR;IACA,CAAA;GACJ,CAAA;GAEJ,KAAc,IAAQ,KACnB,kBAAA,GAAA,EAAA,UAAA,CACI,kBAAC,UAAD;IACI,MAAK;IACL,WAAW,EAAG,EAAO,OAAO,EAAO,IAAI;IACvC,cAAW;IACX,UAAU,CAAC,KAAQ;IACnB,eAAe,EAAK,IAAU,CAAC;IAClC,UAAA;GAEO,CAAA,GACR,kBAAC,UAAD;IACI,MAAK;IACL,WAAW,EAAG,EAAO,OAAO,EAAO,IAAI;IACvC,cAAW;IACX,UAAU,CAAC,KAAQ;IACnB,eAAe,EAAK,IAAU,CAAC;IAClC,UAAA;GAEO,CAAA,CACV,EAAA,CAAA;GAGL,KAAY,IAAQ,KACjB,kBAAC,OAAD;IAAK,WAAW,EAAO;IAAM,MAAK;IAAU,cAAW;IAClD,UAAA,EAAS,KAAK,GAAG,MACd,kBAAC,UAAD;KAEI,MAAK;KACL,MAAK;KACL,cAAY,eAAe,IAAW;KACtC,iBAAe,MAAa;KAC5B,WAAW,EAAG,EAAO,KAAK,MAAa,KAAW,EAAO,SAAS;KAClE,eAAe,EAAK,CAAQ;IAC/B,GAPQ,CAOR,CACJ;GACA,CAAA;EAER;;AAEb"}
@@ -1 +1 @@
1
- {"version":3,"file":"Chat.cjs","names":[],"sources":["../../../src/components/Chat/Chat.tsx"],"sourcesContent":["/**\n * @tempest-limits file-lines, props-count, function-lines — message grouping needs\n * more context than a list does: currentUserId to decide sides, groupWindowMs and\n * now to decide where a bubble group breaks, typing for the indicator. The remaining\n * props are the composer's (placeholder, composerActions, composerDisabled, onSend,\n * onSendError) and the slots (renderAvatar, header, emptyState).\n */\nimport { useEffect, useLayoutEffect, useRef, type HTMLAttributes, type ReactNode } from \"react\";\n\nimport { cn } from \"@/utils/cn\";\n\nimport { EmptyState } from \"../EmptyState\";\nimport { VisuallyHidden } from \"../VisuallyHidden\";\nimport { ChatComposer, type ChatComposerHandle } from \"./ChatComposer\";\nimport {\n chatStrings,\n dayLabel,\n groupMessages,\n timeLabel,\n typingLabel,\n type ChatMessage,\n} from \"./chat-groups\";\nimport styles from \"./Chat.module.css\";\n\n/** DOM attributes this component redefines. */\ntype OverriddenDomProps = \"children\" | \"onSubmit\";\n\nexport interface ChatProps extends Omit<HTMLAttributes<HTMLDivElement>, OverriddenDomProps> {\n /** The thread, **oldest first**. Never reordered by the component. */\n messages: readonly ChatMessage[];\n /** Author id treated as \"own\" — decides side, colour and status ticks. */\n currentUserId?: string;\n /** Renders the composer when given. Receives the trimmed text. */\n onSend?: (text: string) => void | Promise<void>;\n /** Enables the retry control on a `\"failed\"` message. */\n onRetry?: (message: ChatMessage) => void;\n /** Names currently typing. One, two or a count is phrased for you. */\n typing?: readonly string[];\n /** Avatar for the first message of a run — an `<Avatar>`, an `<Icon>`. */\n renderAvatar?: (message: ChatMessage) => ReactNode;\n /** Rendered above the thread, inside the panel. */\n header?: ReactNode;\n /** Shown when there are no messages. */\n emptyState?: ReactNode;\n /** Gap that still keeps consecutive messages in one run. Default 5 min. */\n groupWindowMs?: number;\n /** Locale for labels. Default `\"pt-BR\"`. */\n locale?: \"pt-BR\" | \"en\";\n /** Reference instant for \"Hoje\"/\"Ontem\". Default: now, at render time. */\n now?: number;\n /** Placeholder for the composer. */\n placeholder?: string;\n /** Extra controls inside the composer, before the send button. */\n composerActions?: ReactNode;\n /** Disable the composer — no permission, thread archived, offline. */\n composerDisabled?: boolean;\n /** Called when `onSend` rejects. The draft stays in the field either way. */\n onSendError?: (error: unknown) => void;\n}\n\n/** Status glyph and label for an outgoing message. */\nconst STATUS_GLYPH: Record<NonNullable<ChatMessage[\"status\"]>, string> = {\n sending: \"◌\",\n sent: \"✓\",\n read: \"✓✓\",\n failed: \"!\",\n};\n\n/**\n * A message thread: grouped by author and by day, own messages on one side, with\n * delivery state, a typing indicator and an optional composer.\n *\n * Presentational and controlled, like the rest of the SDK: it takes a list and\n * emits intent (`onSend`, `onRetry`). Where messages come from — REST, the SDK's\n * `createWebSocket`, an SSE stream — and how an optimistic insert is done stay with\n * the app, because those differ per backend and a component that assumed one would\n * be wrong for most.\n *\n * It works as a comment thread too: that is the same component with\n * `currentUserId` set and no `typing`.\n *\n * @example\n * <Chat\n * messages={messages}\n * currentUserId={me.id}\n * typing={typingNames}\n * onSend={(text) => send({ text })}\n * onRetry={(message) => resend(message.id)}\n * />\n */\nexport function Chat({\n messages,\n currentUserId,\n onSend,\n onRetry,\n typing = [],\n renderAvatar,\n header,\n emptyState,\n groupWindowMs,\n locale = \"pt-BR\",\n now,\n placeholder,\n composerActions,\n composerDisabled,\n onSendError,\n className,\n ...rest\n}: ChatProps) {\n const strings = chatStrings(locale);\n const sections = groupMessages({ messages, currentUserId, windowMs: groupWindowMs });\n const composer = useRef<ChatComposerHandle | null>(null);\n const thread = useRef<HTMLDivElement | null>(null);\n const stuckToBottom = useRef(true);\n\n /**\n * Remember whether the reader is at the bottom, before the next batch lands.\n *\n * A thread that always scrolls to the newest message yanks somebody out of the\n * history they were reading, every time anyone types. So the jump only happens\n * when they were already at the bottom — the same rule every chat app converges\n * on. The 48px slack covers a partially visible last row.\n */\n const trackPosition = (): void => {\n const node = thread.current;\n if (!node) return;\n const distance = node.scrollHeight - node.scrollTop - node.clientHeight;\n stuckToBottom.current = distance < 48;\n };\n\n useLayoutEffect(() => {\n const node = thread.current;\n if (!node || !stuckToBottom.current) return;\n node.scrollTop = node.scrollHeight;\n }, [messages, typing]);\n\n useEffect(() => {\n const node = thread.current;\n if (!node) return;\n node.scrollTop = node.scrollHeight;\n // Mount lands on the newest message; from then on `trackPosition` decides.\n }, []);\n\n const typingText = typingLabel(typing, locale);\n\n return (\n <div className={cn(styles.panel, className)} {...rest}>\n {header && <header className={styles.header}>{header}</header>}\n\n <div\n ref={thread}\n className={styles.thread}\n onScroll={trackPosition}\n role=\"log\"\n aria-live=\"polite\"\n aria-relevant=\"additions text\"\n tabIndex={0}\n aria-label={strings.thread}\n >\n {messages.length === 0\n ? (emptyState ?? <EmptyState title={strings.empty} />)\n : sections.map((section) =>\n section.kind === \"day\" ? (\n <div key={section.key} className={styles.day}>\n <span className={styles.dayLabel}>\n {dayLabel(section.date, { locale, now })}\n </span>\n </div>\n ) : (\n <div\n key={section.key}\n className={cn(styles.run, section.own && styles.ownRun)}\n >\n {renderAvatar && (\n <div className={styles.avatar} aria-hidden=\"true\">\n {renderAvatar(section.messages[0])}\n </div>\n )}\n <div className={styles.runBody}>\n <span className={styles.author}>\n {section.own\n ? strings.you\n : (section.authorName ?? section.authorId)}\n </span>\n <ul className={styles.bubbles}>\n {section.messages.map((message) => (\n <li key={message.id} className={styles.bubbleRow}>\n <div\n className={cn(\n styles.bubble,\n section.own && styles.ownBubble,\n message.status === \"failed\" &&\n styles.failedBubble,\n )}\n >\n <div className={styles.body}>\n {message.body}\n </div>\n <div className={styles.meta}>\n <time\n dateTime={new Date(\n message.sentAt,\n ).toISOString()}\n >\n {timeLabel(message.sentAt, locale)}\n </time>\n {section.own && message.status && (\n <span\n className={cn(\n styles.status,\n message.status === \"failed\" &&\n styles.statusFailed,\n )}\n title={strings[message.status]}\n >\n <span aria-hidden=\"true\">\n {STATUS_GLYPH[message.status]}\n </span>\n <VisuallyHidden>\n {strings[message.status]}\n </VisuallyHidden>\n </span>\n )}\n </div>\n </div>\n {message.status === \"failed\" && onRetry && (\n <button\n type=\"button\"\n className={styles.retry}\n onClick={() => onRetry(message)}\n >\n {strings.retry}\n </button>\n )}\n </li>\n ))}\n </ul>\n </div>\n </div>\n ),\n )}\n </div>\n\n {typingText && (\n <p className={styles.typing} aria-live=\"polite\">\n {typingText}\n </p>\n )}\n\n {onSend && (\n <ChatComposer\n ref={composer}\n onSend={onSend}\n locale={locale}\n placeholder={placeholder}\n actions={composerActions}\n disabled={composerDisabled}\n onError={onSendError}\n />\n )}\n </div>\n );\n}\n"],"mappings":"sRA6DA,IAAM,EAAmE,CACrE,QAAS,IACT,KAAM,IACN,KAAM,KACN,OAAQ,GACZ,EAwBA,SAAgB,EAAK,CACjB,WACA,gBACA,SACA,UACA,SAAS,CAAC,EACV,eACA,SACA,aACA,gBACA,SAAS,QACT,MACA,cACA,kBACA,mBACA,cACA,YACA,GAAG,GACO,CACV,IAAM,EAAU,EAAA,YAAY,CAAM,EAC5B,EAAW,EAAA,cAAc,CAAE,WAAU,gBAAe,SAAU,CAAc,CAAC,EAC7E,GAAA,EAAA,EAAA,OAAA,CAA6C,IAAI,EACjD,GAAA,EAAA,EAAA,OAAA,CAAuC,IAAI,EAC3C,GAAA,EAAA,EAAA,OAAA,CAAuB,EAAI,EAU3B,MAA4B,CAC9B,IAAM,EAAO,EAAO,QACpB,GAAI,CAAC,EAAM,OACX,IAAM,EAAW,EAAK,aAAe,EAAK,UAAY,EAAK,aAC3D,EAAc,QAAU,EAAW,EACvC,GAEA,EAAA,EAAA,gBAAA,KAAsB,CAClB,IAAM,EAAO,EAAO,QAChB,CAAC,GAAQ,CAAC,EAAc,UAC5B,EAAK,UAAY,EAAK,aAC1B,EAAG,CAAC,EAAU,CAAM,CAAC,GAErB,EAAA,EAAA,UAAA,KAAgB,CACZ,IAAM,EAAO,EAAO,QACf,IACL,EAAK,UAAY,EAAK,aAE1B,EAAG,CAAC,CAAC,EAEL,IAAM,EAAa,EAAA,YAAY,EAAQ,CAAM,EAE7C,OACI,EAAA,EAAA,KAAA,CAAC,MAAD,CAAK,UAAW,EAAA,GAAG,EAAA,QAAO,MAAO,CAAS,EAAG,GAAI,WAAjD,CACK,IAAU,EAAA,EAAA,IAAA,CAAC,SAAD,CAAQ,UAAW,EAAA,QAAO,gBAAS,CAAe,CAAA,GAE7D,EAAA,EAAA,IAAA,CAAC,MAAD,CACI,IAAK,EACL,UAAW,EAAA,QAAO,OAClB,SAAU,EACV,KAAK,MACL,YAAU,SACV,gBAAc,iBACd,SAAU,EACV,aAAY,EAAQ,gBAEnB,EAAS,SAAW,EACd,IAAc,EAAA,EAAA,IAAA,CAAC,EAAA,WAAD,CAAY,MAAO,EAAQ,KAAQ,CAAA,EAClD,EAAS,IAAK,GACV,EAAQ,OAAS,OACb,EAAA,EAAA,IAAA,CAAC,MAAD,CAAuB,UAAW,EAAA,QAAO,cACrC,EAAA,EAAA,IAAA,CAAC,OAAD,CAAM,UAAW,EAAA,QAAO,kBACnB,EAAA,SAAS,EAAQ,KAAM,CAAE,SAAQ,KAAI,CAAC,CACrC,CAAA,CACL,EAJK,EAAQ,GAIb,GAEL,EAAA,EAAA,KAAA,CAAC,MAAD,CAEI,UAAW,EAAA,GAAG,EAAA,QAAO,IAAK,EAAQ,KAAO,EAAA,QAAO,MAAM,WAF1D,CAIK,IACG,EAAA,EAAA,IAAA,CAAC,MAAD,CAAK,UAAW,EAAA,QAAO,OAAQ,cAAY,gBACtC,EAAa,EAAQ,SAAS,EAAE,CAChC,CAAA,GAET,EAAA,EAAA,KAAA,CAAC,MAAD,CAAK,UAAW,EAAA,QAAO,iBAAvB,EACI,EAAA,EAAA,IAAA,CAAC,OAAD,CAAM,UAAW,EAAA,QAAO,gBACnB,EAAQ,IACH,EAAQ,IACP,EAAQ,YAAc,EAAQ,QACnC,CAAA,GACN,EAAA,EAAA,IAAA,CAAC,KAAD,CAAI,UAAW,EAAA,QAAO,iBACjB,EAAQ,SAAS,IAAK,IACnB,EAAA,EAAA,KAAA,CAAC,KAAD,CAAqB,UAAW,EAAA,QAAO,mBAAvC,EACI,EAAA,EAAA,KAAA,CAAC,MAAD,CACI,UAAW,EAAA,GACP,EAAA,QAAO,OACP,EAAQ,KAAO,EAAA,QAAO,UACtB,EAAQ,SAAW,UACf,EAAA,QAAO,YACf,WANJ,EAQI,EAAA,EAAA,IAAA,CAAC,MAAD,CAAK,UAAW,EAAA,QAAO,cAClB,EAAQ,IACR,CAAA,GACL,EAAA,EAAA,KAAA,CAAC,MAAD,CAAK,UAAW,EAAA,QAAO,cAAvB,EACI,EAAA,EAAA,IAAA,CAAC,OAAD,CACI,SAAU,IAAI,KACV,EAAQ,MACZ,CAAC,CAAC,YAAY,WAEb,EAAA,UAAU,EAAQ,OAAQ,CAAM,CAC/B,CAAA,EACL,EAAQ,KAAO,EAAQ,SACpB,EAAA,EAAA,KAAA,CAAC,OAAD,CACI,UAAW,EAAA,GACP,EAAA,QAAO,OACP,EAAQ,SAAW,UACf,EAAA,QAAO,YACf,EACA,MAAO,EAAQ,EAAQ,iBAN3B,EAQI,EAAA,EAAA,IAAA,CAAC,OAAD,CAAM,cAAY,gBACb,EAAa,EAAQ,OACpB,CAAA,GACN,EAAA,EAAA,IAAA,CAAC,EAAA,eAAD,CAAA,SACK,EAAQ,EAAQ,OACL,CAAA,CACd,GAET,GACJ,IACJ,EAAQ,SAAW,UAAY,IAC5B,EAAA,EAAA,IAAA,CAAC,SAAD,CACI,KAAK,SACL,UAAW,EAAA,QAAO,MAClB,YAAe,EAAQ,CAAO,WAE7B,EAAQ,KACL,CAAA,CAEZ,GAhDK,EAAQ,EAgDb,CACP,CACD,CAAA,CACH,GACJ,GApEI,EAAQ,GAoEZ,CAEb,CACL,CAAA,EAEJ,IACG,EAAA,EAAA,IAAA,CAAC,IAAD,CAAG,UAAW,EAAA,QAAO,OAAQ,YAAU,kBAClC,CACF,CAAA,EAGN,IACG,EAAA,EAAA,IAAA,CAAC,EAAA,aAAD,CACI,IAAK,EACG,SACA,SACK,cACb,QAAS,EACT,SAAU,EACV,QAAS,CACZ,CAAA,CAEJ,GAEb"}
1
+ {"version":3,"file":"Chat.cjs","names":[],"sources":["../../../src/components/Chat/Chat.tsx"],"sourcesContent":["/**\n * @tempest-limits file-lines, props-count, function-lines — message grouping needs\n * more context than a list does: currentUserId to decide sides, groupWindowMs and\n * now to decide where a bubble group breaks, typing for the indicator. The remaining\n * props are the composer's (placeholder, composerActions, composerDisabled, onSend,\n * onSendError) and the slots (renderAvatar, header, emptyState).\n */\nimport { useEffect, useLayoutEffect, useRef, type HTMLAttributes, type ReactNode } from \"react\";\n\nimport { cn } from \"@/utils/cn\";\n\nimport { EmptyState } from \"../EmptyState\";\nimport { VisuallyHidden } from \"../VisuallyHidden\";\nimport { ChatComposer, type ChatComposerHandle } from \"./ChatComposer\";\nimport {\n chatStrings,\n dayLabel,\n groupMessages,\n timeLabel,\n typingLabel,\n type ChatMessage,\n} from \"./chat-groups\";\nimport styles from \"./Chat.module.css\";\n\n/** DOM attributes this component redefines. */\ntype OverriddenDomProps = \"children\" | \"onSubmit\";\n\nexport interface ChatProps extends Omit<HTMLAttributes<HTMLDivElement>, OverriddenDomProps> {\n /** The thread, **oldest first**. Never reordered by the component. */\n messages: readonly ChatMessage[];\n /** Author id treated as \"own\" — decides side, colour and status ticks. */\n currentUserId?: string;\n /** Renders the composer when given. Receives the trimmed text. */\n onSend?: (text: string) => void | Promise<void>;\n /** Enables the retry control on a `\"failed\"` message. */\n onRetry?: (message: ChatMessage) => void;\n /** Names currently typing. One, two or a count is phrased for you. */\n typing?: readonly string[];\n /** Avatar for the first message of a run — an `<Avatar>`, an `<Icon>`. */\n renderAvatar?: (message: ChatMessage) => ReactNode;\n /** Rendered above the thread, inside the panel. */\n header?: ReactNode;\n /** Shown when there are no messages. */\n emptyState?: ReactNode;\n /** Gap that still keeps consecutive messages in one run. Default 5 min. */\n groupWindowMs?: number;\n /** Locale for labels. Default `\"pt-BR\"`. */\n locale?: \"pt-BR\" | \"en\";\n /** Reference instant for \"Hoje\"/\"Ontem\". Default: now, at render time. */\n now?: number;\n /** Placeholder for the composer. */\n placeholder?: string;\n /** Extra controls inside the composer, before the send button. */\n composerActions?: ReactNode;\n /** Disable the composer — no permission, thread archived, offline. */\n composerDisabled?: boolean;\n /** Called when `onSend` rejects. The draft stays in the field either way. */\n onSendError?: (error: unknown) => void;\n}\n\n/** Status glyph and label for an outgoing message. */\nconst STATUS_GLYPH: Record<NonNullable<ChatMessage[\"status\"]>, string> = {\n sending: \"◌\",\n sent: \"✓\",\n read: \"✓✓\",\n failed: \"!\",\n};\n\n/**\n * A message thread: grouped by author and by day, own messages on one side, with\n * delivery state, a typing indicator and an optional composer.\n *\n * Presentational and controlled, like the rest of the SDK: it takes a list and\n * emits intent (`onSend`, `onRetry`). Where messages come from — REST, the SDK's\n * `createWebSocket`, an SSE stream — and how an optimistic insert is done stay with\n * the app, because those differ per backend and a component that assumed one would\n * be wrong for most.\n *\n * It works as a comment thread too: that is the same component with\n * `currentUserId` set and no `typing`.\n *\n * @example\n * <Chat\n * messages={messages}\n * currentUserId={me.id}\n * typing={typingNames}\n * onSend={(text) => send({ text })}\n * onRetry={(message) => resend(message.id)}\n * />\n */\nexport function Chat({\n messages,\n currentUserId,\n onSend,\n onRetry,\n typing = [],\n renderAvatar,\n header,\n emptyState,\n groupWindowMs,\n locale = \"pt-BR\",\n now,\n placeholder,\n composerActions,\n composerDisabled,\n onSendError,\n className,\n ...rest\n}: ChatProps) {\n const strings = chatStrings(locale);\n const sections = groupMessages({ messages, currentUserId, windowMs: groupWindowMs });\n const composer = useRef<ChatComposerHandle | null>(null);\n const thread = useRef<HTMLDivElement | null>(null);\n const stuckToBottom = useRef(true);\n\n /**\n * Remember whether the reader is at the bottom, before the next batch lands.\n *\n * A thread that always scrolls to the newest message yanks somebody out of the\n * history they were reading, every time anyone types. So the jump only happens\n * when they were already at the bottom — the same rule every chat app converges\n * on. The 48px slack covers a partially visible last row.\n */\n const trackPosition = (): void => {\n const node = thread.current;\n if (!node) return;\n const distance = node.scrollHeight - node.scrollTop - node.clientHeight;\n stuckToBottom.current = distance < 48;\n };\n\n useLayoutEffect(() => {\n const node = thread.current;\n if (!node || !stuckToBottom.current) return;\n node.scrollTop = node.scrollHeight;\n }, [messages, typing]);\n\n useEffect(() => {\n const node = thread.current;\n if (!node) return;\n node.scrollTop = node.scrollHeight;\n // Mount lands on the newest message; from then on `trackPosition` decides.\n }, []);\n\n const typingText = typingLabel(typing, locale);\n\n return (\n <div className={cn(styles.panel, className)} {...rest}>\n {header && <header className={styles.header}>{header}</header>}\n\n <div\n ref={thread}\n className={styles.thread}\n onScroll={trackPosition}\n role=\"log\"\n aria-live=\"polite\"\n aria-relevant=\"additions text\"\n tabIndex={0}\n aria-label={strings.thread}\n >\n {messages.length === 0\n ? (emptyState ?? <EmptyState title={strings.empty} />)\n : sections.map((section) =>\n section.kind === \"day\" ? (\n <div key={section.key} className={styles.day}>\n <span className={styles.dayLabel}>\n {dayLabel(section.date, { locale, now })}\n </span>\n </div>\n ) : (\n <div\n key={section.key}\n className={cn(styles.run, section.own && styles.ownRun)}\n >\n {renderAvatar && (\n <div className={styles.avatar} aria-hidden=\"true\">\n {renderAvatar(section.messages[0])}\n </div>\n )}\n <div className={styles.runBody}>\n <span className={styles.author}>\n {section.own\n ? strings.you\n : (section.authorName ?? section.authorId)}\n </span>\n <ul className={styles.bubbles}>\n {section.messages.map((message) => (\n <li key={message.id} className={styles.bubbleRow}>\n <div\n className={cn(\n styles.bubble,\n section.own && styles.ownBubble,\n message.status === \"failed\" &&\n styles.failedBubble,\n )}\n >\n <div className={styles.body}>\n {message.body}\n </div>\n <div className={styles.meta}>\n <time\n dateTime={new Date(\n message.sentAt,\n ).toISOString()}\n >\n {timeLabel(message.sentAt, locale)}\n </time>\n {section.own && message.status && (\n <span\n className={cn(\n styles.status,\n message.status === \"failed\" &&\n styles.statusFailed,\n )}\n title={strings[message.status]}\n >\n <span aria-hidden=\"true\">\n {STATUS_GLYPH[message.status]}\n </span>\n <VisuallyHidden>\n {strings[message.status]}\n </VisuallyHidden>\n </span>\n )}\n </div>\n </div>\n {message.status === \"failed\" && onRetry && (\n <button\n type=\"button\"\n className={styles.retry}\n onClick={() => onRetry(message)}\n >\n {strings.retry}\n </button>\n )}\n </li>\n ))}\n </ul>\n </div>\n </div>\n ),\n )}\n </div>\n\n {typingText && (\n <p className={styles.typing} aria-live=\"polite\">\n {typingText}\n </p>\n )}\n\n {onSend && (\n <ChatComposer\n ref={composer}\n onSend={onSend}\n locale={locale}\n placeholder={placeholder}\n actions={composerActions}\n disabled={composerDisabled}\n onError={onSendError}\n />\n )}\n </div>\n );\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"Chat.js","names":[],"sources":["../../../src/components/Chat/Chat.tsx"],"sourcesContent":["/**\n * @tempest-limits file-lines, props-count, function-lines — message grouping needs\n * more context than a list does: currentUserId to decide sides, groupWindowMs and\n * now to decide where a bubble group breaks, typing for the indicator. The remaining\n * props are the composer's (placeholder, composerActions, composerDisabled, onSend,\n * onSendError) and the slots (renderAvatar, header, emptyState).\n */\nimport { useEffect, useLayoutEffect, useRef, type HTMLAttributes, type ReactNode } from \"react\";\n\nimport { cn } from \"@/utils/cn\";\n\nimport { EmptyState } from \"../EmptyState\";\nimport { VisuallyHidden } from \"../VisuallyHidden\";\nimport { ChatComposer, type ChatComposerHandle } from \"./ChatComposer\";\nimport {\n chatStrings,\n dayLabel,\n groupMessages,\n timeLabel,\n typingLabel,\n type ChatMessage,\n} from \"./chat-groups\";\nimport styles from \"./Chat.module.css\";\n\n/** DOM attributes this component redefines. */\ntype OverriddenDomProps = \"children\" | \"onSubmit\";\n\nexport interface ChatProps extends Omit<HTMLAttributes<HTMLDivElement>, OverriddenDomProps> {\n /** The thread, **oldest first**. Never reordered by the component. */\n messages: readonly ChatMessage[];\n /** Author id treated as \"own\" — decides side, colour and status ticks. */\n currentUserId?: string;\n /** Renders the composer when given. Receives the trimmed text. */\n onSend?: (text: string) => void | Promise<void>;\n /** Enables the retry control on a `\"failed\"` message. */\n onRetry?: (message: ChatMessage) => void;\n /** Names currently typing. One, two or a count is phrased for you. */\n typing?: readonly string[];\n /** Avatar for the first message of a run — an `<Avatar>`, an `<Icon>`. */\n renderAvatar?: (message: ChatMessage) => ReactNode;\n /** Rendered above the thread, inside the panel. */\n header?: ReactNode;\n /** Shown when there are no messages. */\n emptyState?: ReactNode;\n /** Gap that still keeps consecutive messages in one run. Default 5 min. */\n groupWindowMs?: number;\n /** Locale for labels. Default `\"pt-BR\"`. */\n locale?: \"pt-BR\" | \"en\";\n /** Reference instant for \"Hoje\"/\"Ontem\". Default: now, at render time. */\n now?: number;\n /** Placeholder for the composer. */\n placeholder?: string;\n /** Extra controls inside the composer, before the send button. */\n composerActions?: ReactNode;\n /** Disable the composer — no permission, thread archived, offline. */\n composerDisabled?: boolean;\n /** Called when `onSend` rejects. The draft stays in the field either way. */\n onSendError?: (error: unknown) => void;\n}\n\n/** Status glyph and label for an outgoing message. */\nconst STATUS_GLYPH: Record<NonNullable<ChatMessage[\"status\"]>, string> = {\n sending: \"◌\",\n sent: \"✓\",\n read: \"✓✓\",\n failed: \"!\",\n};\n\n/**\n * A message thread: grouped by author and by day, own messages on one side, with\n * delivery state, a typing indicator and an optional composer.\n *\n * Presentational and controlled, like the rest of the SDK: it takes a list and\n * emits intent (`onSend`, `onRetry`). Where messages come from — REST, the SDK's\n * `createWebSocket`, an SSE stream — and how an optimistic insert is done stay with\n * the app, because those differ per backend and a component that assumed one would\n * be wrong for most.\n *\n * It works as a comment thread too: that is the same component with\n * `currentUserId` set and no `typing`.\n *\n * @example\n * <Chat\n * messages={messages}\n * currentUserId={me.id}\n * typing={typingNames}\n * onSend={(text) => send({ text })}\n * onRetry={(message) => resend(message.id)}\n * />\n */\nexport function Chat({\n messages,\n currentUserId,\n onSend,\n onRetry,\n typing = [],\n renderAvatar,\n header,\n emptyState,\n groupWindowMs,\n locale = \"pt-BR\",\n now,\n placeholder,\n composerActions,\n composerDisabled,\n onSendError,\n className,\n ...rest\n}: ChatProps) {\n const strings = chatStrings(locale);\n const sections = groupMessages({ messages, currentUserId, windowMs: groupWindowMs });\n const composer = useRef<ChatComposerHandle | null>(null);\n const thread = useRef<HTMLDivElement | null>(null);\n const stuckToBottom = useRef(true);\n\n /**\n * Remember whether the reader is at the bottom, before the next batch lands.\n *\n * A thread that always scrolls to the newest message yanks somebody out of the\n * history they were reading, every time anyone types. So the jump only happens\n * when they were already at the bottom — the same rule every chat app converges\n * on. The 48px slack covers a partially visible last row.\n */\n const trackPosition = (): void => {\n const node = thread.current;\n if (!node) return;\n const distance = node.scrollHeight - node.scrollTop - node.clientHeight;\n stuckToBottom.current = distance < 48;\n };\n\n useLayoutEffect(() => {\n const node = thread.current;\n if (!node || !stuckToBottom.current) return;\n node.scrollTop = node.scrollHeight;\n }, [messages, typing]);\n\n useEffect(() => {\n const node = thread.current;\n if (!node) return;\n node.scrollTop = node.scrollHeight;\n // Mount lands on the newest message; from then on `trackPosition` decides.\n }, []);\n\n const typingText = typingLabel(typing, locale);\n\n return (\n <div className={cn(styles.panel, className)} {...rest}>\n {header && <header className={styles.header}>{header}</header>}\n\n <div\n ref={thread}\n className={styles.thread}\n onScroll={trackPosition}\n role=\"log\"\n aria-live=\"polite\"\n aria-relevant=\"additions text\"\n tabIndex={0}\n aria-label={strings.thread}\n >\n {messages.length === 0\n ? (emptyState ?? <EmptyState title={strings.empty} />)\n : sections.map((section) =>\n section.kind === \"day\" ? (\n <div key={section.key} className={styles.day}>\n <span className={styles.dayLabel}>\n {dayLabel(section.date, { locale, now })}\n </span>\n </div>\n ) : (\n <div\n key={section.key}\n className={cn(styles.run, section.own && styles.ownRun)}\n >\n {renderAvatar && (\n <div className={styles.avatar} aria-hidden=\"true\">\n {renderAvatar(section.messages[0])}\n </div>\n )}\n <div className={styles.runBody}>\n <span className={styles.author}>\n {section.own\n ? strings.you\n : (section.authorName ?? section.authorId)}\n </span>\n <ul className={styles.bubbles}>\n {section.messages.map((message) => (\n <li key={message.id} className={styles.bubbleRow}>\n <div\n className={cn(\n styles.bubble,\n section.own && styles.ownBubble,\n message.status === \"failed\" &&\n styles.failedBubble,\n )}\n >\n <div className={styles.body}>\n {message.body}\n </div>\n <div className={styles.meta}>\n <time\n dateTime={new Date(\n message.sentAt,\n ).toISOString()}\n >\n {timeLabel(message.sentAt, locale)}\n </time>\n {section.own && message.status && (\n <span\n className={cn(\n styles.status,\n message.status === \"failed\" &&\n styles.statusFailed,\n )}\n title={strings[message.status]}\n >\n <span aria-hidden=\"true\">\n {STATUS_GLYPH[message.status]}\n </span>\n <VisuallyHidden>\n {strings[message.status]}\n </VisuallyHidden>\n </span>\n )}\n </div>\n </div>\n {message.status === \"failed\" && onRetry && (\n <button\n type=\"button\"\n className={styles.retry}\n onClick={() => onRetry(message)}\n >\n {strings.retry}\n </button>\n )}\n </li>\n ))}\n </ul>\n </div>\n </div>\n ),\n )}\n </div>\n\n {typingText && (\n <p className={styles.typing} aria-live=\"polite\">\n {typingText}\n </p>\n )}\n\n {onSend && (\n <ChatComposer\n ref={composer}\n onSend={onSend}\n locale={locale}\n placeholder={placeholder}\n actions={composerActions}\n disabled={composerDisabled}\n onError={onSendError}\n />\n )}\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"Chat.js","names":[],"sources":["../../../src/components/Chat/Chat.tsx"],"sourcesContent":["/**\n * @tempest-limits file-lines, props-count, function-lines — message grouping needs\n * more context than a list does: currentUserId to decide sides, groupWindowMs and\n * now to decide where a bubble group breaks, typing for the indicator. The remaining\n * props are the composer's (placeholder, composerActions, composerDisabled, onSend,\n * onSendError) and the slots (renderAvatar, header, emptyState).\n */\nimport { useEffect, useLayoutEffect, useRef, type HTMLAttributes, type ReactNode } from \"react\";\n\nimport { cn } from \"@/utils/cn\";\n\nimport { EmptyState } from \"../EmptyState\";\nimport { VisuallyHidden } from \"../VisuallyHidden\";\nimport { ChatComposer, type ChatComposerHandle } from \"./ChatComposer\";\nimport {\n chatStrings,\n dayLabel,\n groupMessages,\n timeLabel,\n typingLabel,\n type ChatMessage,\n} from \"./chat-groups\";\nimport styles from \"./Chat.module.css\";\n\n/** DOM attributes this component redefines. */\ntype OverriddenDomProps = \"children\" | \"onSubmit\";\n\nexport interface ChatProps extends Omit<HTMLAttributes<HTMLDivElement>, OverriddenDomProps> {\n /** The thread, **oldest first**. Never reordered by the component. */\n messages: readonly ChatMessage[];\n /** Author id treated as \"own\" — decides side, colour and status ticks. */\n currentUserId?: string;\n /** Renders the composer when given. Receives the trimmed text. */\n onSend?: (text: string) => void | Promise<void>;\n /** Enables the retry control on a `\"failed\"` message. */\n onRetry?: (message: ChatMessage) => void;\n /** Names currently typing. One, two or a count is phrased for you. */\n typing?: readonly string[];\n /** Avatar for the first message of a run — an `<Avatar>`, an `<Icon>`. */\n renderAvatar?: (message: ChatMessage) => ReactNode;\n /** Rendered above the thread, inside the panel. */\n header?: ReactNode;\n /** Shown when there are no messages. */\n emptyState?: ReactNode;\n /** Gap that still keeps consecutive messages in one run. Default 5 min. */\n groupWindowMs?: number;\n /** Locale for labels. Default `\"pt-BR\"`. */\n locale?: \"pt-BR\" | \"en\";\n /** Reference instant for \"Hoje\"/\"Ontem\". Default: now, at render time. */\n now?: number;\n /** Placeholder for the composer. */\n placeholder?: string;\n /** Extra controls inside the composer, before the send button. */\n composerActions?: ReactNode;\n /** Disable the composer — no permission, thread archived, offline. */\n composerDisabled?: boolean;\n /** Called when `onSend` rejects. The draft stays in the field either way. */\n onSendError?: (error: unknown) => void;\n}\n\n/** Status glyph and label for an outgoing message. */\nconst STATUS_GLYPH: Record<NonNullable<ChatMessage[\"status\"]>, string> = {\n sending: \"◌\",\n sent: \"✓\",\n read: \"✓✓\",\n failed: \"!\",\n};\n\n/**\n * A message thread: grouped by author and by day, own messages on one side, with\n * delivery state, a typing indicator and an optional composer.\n *\n * Presentational and controlled, like the rest of the SDK: it takes a list and\n * emits intent (`onSend`, `onRetry`). Where messages come from — REST, the SDK's\n * `createWebSocket`, an SSE stream — and how an optimistic insert is done stay with\n * the app, because those differ per backend and a component that assumed one would\n * be wrong for most.\n *\n * It works as a comment thread too: that is the same component with\n * `currentUserId` set and no `typing`.\n *\n * @example\n * <Chat\n * messages={messages}\n * currentUserId={me.id}\n * typing={typingNames}\n * onSend={(text) => send({ text })}\n * onRetry={(message) => resend(message.id)}\n * />\n */\nexport function Chat({\n messages,\n currentUserId,\n onSend,\n onRetry,\n typing = [],\n renderAvatar,\n header,\n emptyState,\n groupWindowMs,\n locale = \"pt-BR\",\n now,\n placeholder,\n composerActions,\n composerDisabled,\n onSendError,\n className,\n ...rest\n}: ChatProps) {\n const strings = chatStrings(locale);\n const sections = groupMessages({ messages, currentUserId, windowMs: groupWindowMs });\n const composer = useRef<ChatComposerHandle | null>(null);\n const thread = useRef<HTMLDivElement | null>(null);\n const stuckToBottom = useRef(true);\n\n /**\n * Remember whether the reader is at the bottom, before the next batch lands.\n *\n * A thread that always scrolls to the newest message yanks somebody out of the\n * history they were reading, every time anyone types. So the jump only happens\n * when they were already at the bottom — the same rule every chat app converges\n * on. The 48px slack covers a partially visible last row.\n */\n const trackPosition = (): void => {\n const node = thread.current;\n if (!node) return;\n const distance = node.scrollHeight - node.scrollTop - node.clientHeight;\n stuckToBottom.current = distance < 48;\n };\n\n useLayoutEffect(() => {\n const node = thread.current;\n if (!node || !stuckToBottom.current) return;\n node.scrollTop = node.scrollHeight;\n }, [messages, typing]);\n\n useEffect(() => {\n const node = thread.current;\n if (!node) return;\n node.scrollTop = node.scrollHeight;\n // Mount lands on the newest message; from then on `trackPosition` decides.\n }, []);\n\n const typingText = typingLabel(typing, locale);\n\n return (\n <div className={cn(styles.panel, className)} {...rest}>\n {header && <header className={styles.header}>{header}</header>}\n\n <div\n ref={thread}\n className={styles.thread}\n onScroll={trackPosition}\n role=\"log\"\n aria-live=\"polite\"\n aria-relevant=\"additions text\"\n tabIndex={0}\n aria-label={strings.thread}\n >\n {messages.length === 0\n ? (emptyState ?? <EmptyState title={strings.empty} />)\n : sections.map((section) =>\n section.kind === \"day\" ? (\n <div key={section.key} className={styles.day}>\n <span className={styles.dayLabel}>\n {dayLabel(section.date, { locale, now })}\n </span>\n </div>\n ) : (\n <div\n key={section.key}\n className={cn(styles.run, section.own && styles.ownRun)}\n >\n {renderAvatar && (\n <div className={styles.avatar} aria-hidden=\"true\">\n {renderAvatar(section.messages[0])}\n </div>\n )}\n <div className={styles.runBody}>\n <span className={styles.author}>\n {section.own\n ? strings.you\n : (section.authorName ?? section.authorId)}\n </span>\n <ul className={styles.bubbles}>\n {section.messages.map((message) => (\n <li key={message.id} className={styles.bubbleRow}>\n <div\n className={cn(\n styles.bubble,\n section.own && styles.ownBubble,\n message.status === \"failed\" &&\n styles.failedBubble,\n )}\n >\n <div className={styles.body}>\n {message.body}\n </div>\n <div className={styles.meta}>\n <time\n dateTime={new Date(\n message.sentAt,\n ).toISOString()}\n >\n {timeLabel(message.sentAt, locale)}\n </time>\n {section.own && message.status && (\n <span\n className={cn(\n styles.status,\n message.status === \"failed\" &&\n styles.statusFailed,\n )}\n title={strings[message.status]}\n >\n <span aria-hidden=\"true\">\n {STATUS_GLYPH[message.status]}\n </span>\n <VisuallyHidden>\n {strings[message.status]}\n </VisuallyHidden>\n </span>\n )}\n </div>\n </div>\n {message.status === \"failed\" && onRetry && (\n <button\n type=\"button\"\n className={styles.retry}\n onClick={() => onRetry(message)}\n >\n {strings.retry}\n </button>\n )}\n </li>\n ))}\n </ul>\n </div>\n </div>\n ),\n )}\n </div>\n\n {typingText && (\n <p className={styles.typing} aria-live=\"polite\">\n {typingText}\n </p>\n )}\n\n {onSend && (\n <ChatComposer\n ref={composer}\n onSend={onSend}\n locale={locale}\n placeholder={placeholder}\n actions={composerActions}\n disabled={composerDisabled}\n onError={onSendError}\n />\n )}\n </div>\n );\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"ChatComposer.cjs","names":[],"sources":["../../../src/components/Chat/ChatComposer.tsx"],"sourcesContent":["import {\n forwardRef,\n useImperativeHandle,\n useLayoutEffect,\n useRef,\n useState,\n type FormEvent,\n type KeyboardEvent,\n type ReactNode,\n type TextareaHTMLAttributes,\n} from \"react\";\n\nimport { cn } from \"@/utils/cn\";\n\nimport { chatStrings } from \"./chat-groups\";\nimport styles from \"./Chat.module.css\";\n\n/** DOM attributes the composer redefines. */\ntype OverriddenDomProps = \"onSubmit\" | \"value\" | \"defaultValue\" | \"rows\";\n\nexport interface ChatComposerProps extends Omit<\n TextareaHTMLAttributes<HTMLTextAreaElement>,\n OverriddenDomProps\n> {\n /** Called with the trimmed text. The field clears only when this does not throw. */\n onSend: (text: string) => void | Promise<void>;\n /** Locale for the placeholder and the send label. Default `\"pt-BR\"`. */\n locale?: \"pt-BR\" | \"en\";\n /** Rendered to the left of the send button — an attach button, an emoji picker. */\n actions?: ReactNode;\n /** Largest height the field grows to, in lines. Default 6. */\n maxRows?: number;\n /** Send label. Defaults to the locale string. */\n sendLabel?: string;\n /**\n * Called when `onSend` rejects. The draft is kept either way.\n *\n * Without it the rejection is swallowed after the draft is preserved, because\n * the alternative is worse: re-throwing out of a DOM event handler surfaces as\n * an unhandled promise rejection — console noise for the developer, a hit in\n * whatever crash reporter the app runs, and nothing the user can act on. The\n * visible signal is the text still sitting in the field; wire this to a toast\n * to say why.\n */\n onError?: (error: unknown) => void;\n}\n\n/** Imperative handle, so a thread can focus the field after retrying a message. */\nexport interface ChatComposerHandle {\n focus: () => void;\n /** Replace the draft — used to put a failed message back in the field. */\n setValue: (text: string) => void;\n}\n\n/**\n * The message field of a thread: a textarea that grows with its content, sends on\n * `Enter` and keeps `Shift+Enter` for a newline.\n *\n * Uncontrolled on purpose. A chat draft changes on every keystroke, and lifting\n * that into app state re-renders the whole thread per character — the one place\n * where \"controlled by default\" costs something visible. Apps that need the draft\n * (a persisted composer, a slash-command menu) read it from `onChange` or drive it\n * through the ref.\n *\n * @example\n * <ChatComposer onSend={(text) => api.post(\"/messages\", { body: { text } })} />\n */\nexport const ChatComposer = forwardRef<ChatComposerHandle, ChatComposerProps>(function ChatComposer(\n {\n onSend,\n locale = \"pt-BR\",\n actions,\n maxRows = 6,\n sendLabel,\n onError,\n className,\n disabled,\n placeholder,\n onKeyDown,\n onChange,\n ...rest\n },\n ref,\n) {\n const strings = chatStrings(locale);\n const textarea = useRef<HTMLTextAreaElement | null>(null);\n const [value, setValue] = useState(\"\");\n const [busy, setBusy] = useState(false);\n\n useImperativeHandle(ref, () => ({\n focus: () => textarea.current?.focus(),\n setValue: (text: string) => {\n setValue(text);\n textarea.current?.focus();\n },\n }));\n\n /**\n * Grow the field to fit its content, up to `maxRows`.\n *\n * Measured from `scrollHeight` after resetting the height, because\n * `scrollHeight` on an element that is already tall enough reports the\n * current height and the field would never shrink back.\n */\n useLayoutEffect(() => {\n const node = textarea.current;\n if (!node) return;\n node.style.height = \"auto\";\n const lineHeight = Number.parseFloat(getComputedStyle(node).lineHeight) || 20;\n const max = lineHeight * maxRows;\n node.style.height = `${Math.min(node.scrollHeight, max)}px`;\n node.style.overflowY = node.scrollHeight > max ? \"auto\" : \"hidden\";\n }, [value, maxRows]);\n\n const submit = async (): Promise<void> => {\n const text = value.trim();\n if (!text || busy || disabled) return;\n setBusy(true);\n try {\n await onSend(text);\n setValue(\"\");\n } catch (error) {\n onError?.(error);\n } finally {\n setBusy(false);\n }\n };\n\n const handleSubmit = (event: FormEvent): void => {\n event.preventDefault();\n void submit();\n };\n\n /**\n * `Enter` sends, `Shift+Enter` breaks the line.\n *\n * The IME check is not optional: while composing Japanese or Korean, `Enter`\n * confirms the candidate word and `keyCode === 229` marks that keystroke.\n * Sending there would post half a word and eat the confirmation.\n */\n const handleKeyDown = (event: KeyboardEvent<HTMLTextAreaElement>): void => {\n onKeyDown?.(event);\n if (event.defaultPrevented) return;\n if (event.key !== \"Enter\" || event.shiftKey) return;\n if (event.nativeEvent.isComposing || event.keyCode === 229) return;\n event.preventDefault();\n void submit();\n };\n\n return (\n <form className={cn(styles.composer, className)} onSubmit={handleSubmit}>\n <textarea\n {...rest}\n ref={textarea}\n className={styles.field}\n rows={1}\n value={value}\n disabled={disabled}\n placeholder={placeholder ?? strings.placeholder}\n onChange={(event) => {\n setValue(event.target.value);\n onChange?.(event);\n }}\n onKeyDown={handleKeyDown}\n />\n <div className={styles.composerActions}>\n {actions}\n <button\n type=\"submit\"\n className={styles.send}\n disabled={disabled || busy || value.trim() === \"\"}\n >\n {sendLabel ?? strings.send}\n </button>\n </div>\n </form>\n );\n});\n"],"mappings":"0JAmEA,IAAa,GAAA,EAAA,EAAA,WAAA,CAAiE,SAC1E,CACI,SACA,SAAS,QACT,UACA,UAAU,EACV,YACA,UACA,YACA,WACA,cACA,YACA,WACA,GAAG,GAEP,EACF,CACE,IAAM,EAAU,EAAA,YAAY,CAAM,EAC5B,GAAA,EAAA,EAAA,OAAA,CAA8C,IAAI,EAClD,CAAC,EAAO,IAAA,EAAA,EAAA,SAAA,CAAqB,EAAE,EAC/B,CAAC,EAAM,IAAA,EAAA,EAAA,SAAA,CAAoB,EAAK,GAEtC,EAAA,EAAA,oBAAA,CAAoB,OAAY,CAC5B,UAAa,EAAS,SAAS,MAAM,EACrC,SAAW,GAAiB,CACxB,EAAS,CAAI,EACb,EAAS,SAAS,MAAM,CAC5B,CACJ,EAAE,GASF,EAAA,EAAA,gBAAA,KAAsB,CAClB,IAAM,EAAO,EAAS,QACtB,GAAI,CAAC,EAAM,OACX,EAAK,MAAM,OAAS,OAEpB,IAAM,GADa,OAAO,WAAW,iBAAiB,CAAI,CAAC,CAAC,UAAU,GAAK,IAClD,EACzB,EAAK,MAAM,OAAS,GAAG,KAAK,IAAI,EAAK,aAAc,CAAG,EAAE,IACxD,EAAK,MAAM,UAAY,EAAK,aAAe,EAAM,OAAS,QAC9D,EAAG,CAAC,EAAO,CAAO,CAAC,EAEnB,IAAM,EAAS,SAA2B,CACtC,IAAM,EAAO,EAAM,KAAK,EACpB,MAAC,GAAQ,GAAQ,GACrB,GAAQ,EAAI,EACZ,GAAI,CACA,MAAM,EAAO,CAAI,EACjB,EAAS,EAAE,CACf,OAAS,EAAO,CACZ,IAAU,CAAK,CACnB,QAAU,CACN,EAAQ,EAAK,CACjB,CARY,CAShB,EAEM,EAAgB,GAA2B,CAC7C,EAAM,eAAe,EACrB,EAAY,CAChB,EASM,EAAiB,GAAoD,CACvE,IAAY,CAAK,EACb,GAAM,mBACN,EAAM,MAAQ,SAAW,EAAM,UAC/B,EAAM,YAAY,aAAe,EAAM,UAAY,MACvD,EAAM,eAAe,EACrB,EAAY,GAChB,EAEA,OACI,EAAA,EAAA,KAAA,CAAC,OAAD,CAAM,UAAW,EAAA,GAAG,EAAA,QAAO,SAAU,CAAS,EAAG,SAAU,WAA3D,EACI,EAAA,EAAA,IAAA,CAAC,WAAD,CACI,GAAI,EACJ,IAAK,EACL,UAAW,EAAA,QAAO,MAClB,KAAM,EACC,QACG,WACV,YAAa,GAAe,EAAQ,YACpC,SAAW,GAAU,CACjB,EAAS,EAAM,OAAO,KAAK,EAC3B,IAAW,CAAK,CACpB,EACA,UAAW,CACd,CAAA,GACD,EAAA,EAAA,KAAA,CAAC,MAAD,CAAK,UAAW,EAAA,QAAO,yBAAvB,CACK,GACD,EAAA,EAAA,IAAA,CAAC,SAAD,CACI,KAAK,SACL,UAAW,EAAA,QAAO,KAClB,SAAU,GAAY,GAAQ,EAAM,KAAK,IAAM,YAE9C,GAAa,EAAQ,IAClB,CAAA,CACP,GACH,GAEd,CAAC"}
1
+ {"version":3,"file":"ChatComposer.cjs","names":[],"sources":["../../../src/components/Chat/ChatComposer.tsx"],"sourcesContent":["import {\n forwardRef,\n useImperativeHandle,\n useLayoutEffect,\n useRef,\n useState,\n type FormEvent,\n type KeyboardEvent,\n type ReactNode,\n type TextareaHTMLAttributes,\n} from \"react\";\n\nimport { cn } from \"@/utils/cn\";\n\nimport { chatStrings } from \"./chat-groups\";\nimport styles from \"./Chat.module.css\";\n\n/** DOM attributes the composer redefines. */\ntype OverriddenDomProps = \"onSubmit\" | \"value\" | \"defaultValue\" | \"rows\";\n\nexport interface ChatComposerProps extends Omit<\n TextareaHTMLAttributes<HTMLTextAreaElement>,\n OverriddenDomProps\n> {\n /** Called with the trimmed text. The field clears only when this does not throw. */\n onSend: (text: string) => void | Promise<void>;\n /** Locale for the placeholder and the send label. Default `\"pt-BR\"`. */\n locale?: \"pt-BR\" | \"en\";\n /** Rendered to the left of the send button — an attach button, an emoji picker. */\n actions?: ReactNode;\n /** Largest height the field grows to, in lines. Default 6. */\n maxRows?: number;\n /** Send label. Defaults to the locale string. */\n sendLabel?: string;\n /**\n * Called when `onSend` rejects. The draft is kept either way.\n *\n * Without it the rejection is swallowed after the draft is preserved, because\n * the alternative is worse: re-throwing out of a DOM event handler surfaces as\n * an unhandled promise rejection — console noise for the developer, a hit in\n * whatever crash reporter the app runs, and nothing the user can act on. The\n * visible signal is the text still sitting in the field; wire this to a toast\n * to say why.\n */\n onError?: (error: unknown) => void;\n}\n\n/** Imperative handle, so a thread can focus the field after retrying a message. */\nexport interface ChatComposerHandle {\n focus: () => void;\n /** Replace the draft — used to put a failed message back in the field. */\n setValue: (text: string) => void;\n}\n\n/**\n * The message field of a thread: a textarea that grows with its content, sends on\n * `Enter` and keeps `Shift+Enter` for a newline.\n *\n * Uncontrolled on purpose. A chat draft changes on every keystroke, and lifting\n * that into app state re-renders the whole thread per character — the one place\n * where \"controlled by default\" costs something visible. Apps that need the draft\n * (a persisted composer, a slash-command menu) read it from `onChange` or drive it\n * through the ref.\n *\n * @example\n * <ChatComposer onSend={(text) => api.post(\"/messages\", { body: { text } })} />\n */\nexport const ChatComposer = forwardRef<ChatComposerHandle, ChatComposerProps>(function ChatComposer(\n {\n onSend,\n locale = \"pt-BR\",\n actions,\n maxRows = 6,\n sendLabel,\n onError,\n className,\n disabled,\n placeholder,\n onKeyDown,\n onChange,\n ...rest\n },\n ref,\n) {\n const strings = chatStrings(locale);\n const textarea = useRef<HTMLTextAreaElement | null>(null);\n const [value, setValue] = useState(\"\");\n const [busy, setBusy] = useState(false);\n\n useImperativeHandle(ref, () => ({\n focus: () => textarea.current?.focus(),\n setValue: (text: string) => {\n setValue(text);\n textarea.current?.focus();\n },\n }));\n\n /**\n * Grow the field to fit its content, up to `maxRows`.\n *\n * Measured from `scrollHeight` after resetting the height, because\n * `scrollHeight` on an element that is already tall enough reports the\n * current height and the field would never shrink back.\n */\n useLayoutEffect(() => {\n const node = textarea.current;\n if (!node) return;\n node.style.height = \"auto\";\n const lineHeight = Number.parseFloat(getComputedStyle(node).lineHeight) || 20;\n const max = lineHeight * maxRows;\n node.style.height = `${Math.min(node.scrollHeight, max)}px`;\n node.style.overflowY = node.scrollHeight > max ? \"auto\" : \"hidden\";\n }, [value, maxRows]);\n\n const submit = async (): Promise<void> => {\n const text = value.trim();\n if (!text || busy || disabled) return;\n setBusy(true);\n try {\n await onSend(text);\n setValue(\"\");\n } catch (error) {\n onError?.(error);\n } finally {\n setBusy(false);\n }\n };\n\n const handleSubmit = (event: FormEvent): void => {\n event.preventDefault();\n void submit();\n };\n\n /**\n * `Enter` sends, `Shift+Enter` breaks the line.\n *\n * The IME check is not optional: while composing Japanese or Korean, `Enter`\n * confirms the candidate word and `keyCode === 229` marks that keystroke.\n * Sending there would post half a word and eat the confirmation.\n */\n const handleKeyDown = (event: KeyboardEvent<HTMLTextAreaElement>): void => {\n onKeyDown?.(event);\n if (event.defaultPrevented) return;\n if (event.key !== \"Enter\" || event.shiftKey) return;\n if (event.nativeEvent.isComposing || event.keyCode === 229) return;\n event.preventDefault();\n void submit();\n };\n\n return (\n <form className={cn(styles.composer, className)} onSubmit={handleSubmit}>\n <textarea\n {...rest}\n ref={textarea}\n className={styles.field}\n rows={1}\n value={value}\n disabled={disabled}\n placeholder={placeholder ?? strings.placeholder}\n onChange={(event) => {\n setValue(event.target.value);\n onChange?.(event);\n }}\n onKeyDown={handleKeyDown}\n />\n <div className={styles.composerActions}>\n {actions}\n <button\n type=\"submit\"\n className={styles.send}\n disabled={disabled || busy || value.trim() === \"\"}\n >\n {sendLabel ?? strings.send}\n </button>\n </div>\n </form>\n );\n});\n"],"mappings":"0JAmEA,IAAa,GAAA,EAAe,EAAA,WAAA,CAAkD,SAC1E,CACI,SACA,SAAS,QACT,UACA,UAAU,EACV,YACA,UACA,YACA,WACA,cACA,YACA,WACA,GAAG,GAEP,EACF,CACE,IAAM,EAAU,EAAA,YAAY,CAAM,EAC5B,GAAA,EAAW,EAAA,OAAA,CAAmC,IAAI,EAClD,CAAC,EAAO,IAAA,EAAY,EAAA,SAAA,CAAS,EAAE,EAC/B,CAAC,EAAM,IAAA,EAAW,EAAA,SAAA,CAAS,EAAK,GAEtC,EAAA,EAAA,oBAAA,CAAoB,OAAY,CAC5B,UAAa,EAAS,SAAS,MAAM,EACrC,SAAW,GAAiB,CACxB,EAAS,CAAI,EACb,EAAS,SAAS,MAAM,CAC5B,CACJ,EAAE,GASF,EAAA,EAAA,gBAAA,KAAsB,CAClB,IAAM,EAAO,EAAS,QACtB,GAAI,CAAC,EAAM,OACX,EAAK,MAAM,OAAS,OAEpB,IAAM,GADa,OAAO,WAAW,iBAAiB,CAAI,CAAC,CAAC,UAAU,GAAK,IAClD,EACzB,EAAK,MAAM,OAAS,GAAG,KAAK,IAAI,EAAK,aAAc,CAAG,EAAE,IACxD,EAAK,MAAM,UAAY,EAAK,aAAe,EAAM,OAAS,QAC9D,EAAG,CAAC,EAAO,CAAO,CAAC,EAEnB,IAAM,EAAS,SAA2B,CACtC,IAAM,EAAO,EAAM,KAAK,EACpB,MAAC,GAAQ,GAAQ,GACrB,GAAQ,EAAI,EACZ,GAAI,CACA,MAAM,EAAO,CAAI,EACjB,EAAS,EAAE,CACf,OAAS,EAAO,CACZ,IAAU,CAAK,CACnB,QAAU,CACN,EAAQ,EAAK,CACjB,CARY,CAShB,EAEM,EAAgB,GAA2B,CAC7C,EAAM,eAAe,EACrB,EAAY,CAChB,EASM,EAAiB,GAAoD,CACvE,IAAY,CAAK,EACb,GAAM,mBACN,EAAM,MAAQ,SAAW,EAAM,UAC/B,EAAM,YAAY,aAAe,EAAM,UAAY,MACvD,EAAM,eAAe,EACrB,EAAY,GAChB,EAEA,OACI,EAAA,EAAA,KAAA,CAAC,OAAD,CAAM,UAAW,EAAA,GAAG,EAAA,QAAO,SAAU,CAAS,EAAG,SAAU,EAA3D,SAAA,EACI,EAAA,EAAA,IAAA,CAAC,WAAD,CACI,GAAI,EACJ,IAAK,EACL,UAAW,EAAA,QAAO,MAClB,KAAM,EACC,QACG,WACV,YAAa,GAAe,EAAQ,YACpC,SAAW,GAAU,CACjB,EAAS,EAAM,OAAO,KAAK,EAC3B,IAAW,CAAK,CACpB,EACA,UAAW,CACd,CAAA,GACD,EAAA,EAAA,KAAA,CAAC,MAAD,CAAK,UAAW,EAAA,QAAO,gBAAvB,SAAA,CACK,GACD,EAAA,EAAA,IAAA,CAAC,SAAD,CACI,KAAK,SACL,UAAW,EAAA,QAAO,KAClB,SAAU,GAAY,GAAQ,EAAM,KAAK,IAAM,GAE9C,SAAA,GAAa,EAAQ,IAClB,CAAA,CACP,CACH,CAAA,CAAA,GAEd,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"ChatComposer.js","names":[],"sources":["../../../src/components/Chat/ChatComposer.tsx"],"sourcesContent":["import {\n forwardRef,\n useImperativeHandle,\n useLayoutEffect,\n useRef,\n useState,\n type FormEvent,\n type KeyboardEvent,\n type ReactNode,\n type TextareaHTMLAttributes,\n} from \"react\";\n\nimport { cn } from \"@/utils/cn\";\n\nimport { chatStrings } from \"./chat-groups\";\nimport styles from \"./Chat.module.css\";\n\n/** DOM attributes the composer redefines. */\ntype OverriddenDomProps = \"onSubmit\" | \"value\" | \"defaultValue\" | \"rows\";\n\nexport interface ChatComposerProps extends Omit<\n TextareaHTMLAttributes<HTMLTextAreaElement>,\n OverriddenDomProps\n> {\n /** Called with the trimmed text. The field clears only when this does not throw. */\n onSend: (text: string) => void | Promise<void>;\n /** Locale for the placeholder and the send label. Default `\"pt-BR\"`. */\n locale?: \"pt-BR\" | \"en\";\n /** Rendered to the left of the send button — an attach button, an emoji picker. */\n actions?: ReactNode;\n /** Largest height the field grows to, in lines. Default 6. */\n maxRows?: number;\n /** Send label. Defaults to the locale string. */\n sendLabel?: string;\n /**\n * Called when `onSend` rejects. The draft is kept either way.\n *\n * Without it the rejection is swallowed after the draft is preserved, because\n * the alternative is worse: re-throwing out of a DOM event handler surfaces as\n * an unhandled promise rejection — console noise for the developer, a hit in\n * whatever crash reporter the app runs, and nothing the user can act on. The\n * visible signal is the text still sitting in the field; wire this to a toast\n * to say why.\n */\n onError?: (error: unknown) => void;\n}\n\n/** Imperative handle, so a thread can focus the field after retrying a message. */\nexport interface ChatComposerHandle {\n focus: () => void;\n /** Replace the draft — used to put a failed message back in the field. */\n setValue: (text: string) => void;\n}\n\n/**\n * The message field of a thread: a textarea that grows with its content, sends on\n * `Enter` and keeps `Shift+Enter` for a newline.\n *\n * Uncontrolled on purpose. A chat draft changes on every keystroke, and lifting\n * that into app state re-renders the whole thread per character — the one place\n * where \"controlled by default\" costs something visible. Apps that need the draft\n * (a persisted composer, a slash-command menu) read it from `onChange` or drive it\n * through the ref.\n *\n * @example\n * <ChatComposer onSend={(text) => api.post(\"/messages\", { body: { text } })} />\n */\nexport const ChatComposer = forwardRef<ChatComposerHandle, ChatComposerProps>(function ChatComposer(\n {\n onSend,\n locale = \"pt-BR\",\n actions,\n maxRows = 6,\n sendLabel,\n onError,\n className,\n disabled,\n placeholder,\n onKeyDown,\n onChange,\n ...rest\n },\n ref,\n) {\n const strings = chatStrings(locale);\n const textarea = useRef<HTMLTextAreaElement | null>(null);\n const [value, setValue] = useState(\"\");\n const [busy, setBusy] = useState(false);\n\n useImperativeHandle(ref, () => ({\n focus: () => textarea.current?.focus(),\n setValue: (text: string) => {\n setValue(text);\n textarea.current?.focus();\n },\n }));\n\n /**\n * Grow the field to fit its content, up to `maxRows`.\n *\n * Measured from `scrollHeight` after resetting the height, because\n * `scrollHeight` on an element that is already tall enough reports the\n * current height and the field would never shrink back.\n */\n useLayoutEffect(() => {\n const node = textarea.current;\n if (!node) return;\n node.style.height = \"auto\";\n const lineHeight = Number.parseFloat(getComputedStyle(node).lineHeight) || 20;\n const max = lineHeight * maxRows;\n node.style.height = `${Math.min(node.scrollHeight, max)}px`;\n node.style.overflowY = node.scrollHeight > max ? \"auto\" : \"hidden\";\n }, [value, maxRows]);\n\n const submit = async (): Promise<void> => {\n const text = value.trim();\n if (!text || busy || disabled) return;\n setBusy(true);\n try {\n await onSend(text);\n setValue(\"\");\n } catch (error) {\n onError?.(error);\n } finally {\n setBusy(false);\n }\n };\n\n const handleSubmit = (event: FormEvent): void => {\n event.preventDefault();\n void submit();\n };\n\n /**\n * `Enter` sends, `Shift+Enter` breaks the line.\n *\n * The IME check is not optional: while composing Japanese or Korean, `Enter`\n * confirms the candidate word and `keyCode === 229` marks that keystroke.\n * Sending there would post half a word and eat the confirmation.\n */\n const handleKeyDown = (event: KeyboardEvent<HTMLTextAreaElement>): void => {\n onKeyDown?.(event);\n if (event.defaultPrevented) return;\n if (event.key !== \"Enter\" || event.shiftKey) return;\n if (event.nativeEvent.isComposing || event.keyCode === 229) return;\n event.preventDefault();\n void submit();\n };\n\n return (\n <form className={cn(styles.composer, className)} onSubmit={handleSubmit}>\n <textarea\n {...rest}\n ref={textarea}\n className={styles.field}\n rows={1}\n value={value}\n disabled={disabled}\n placeholder={placeholder ?? strings.placeholder}\n onChange={(event) => {\n setValue(event.target.value);\n onChange?.(event);\n }}\n onKeyDown={handleKeyDown}\n />\n <div className={styles.composerActions}>\n {actions}\n <button\n type=\"submit\"\n className={styles.send}\n disabled={disabled || busy || value.trim() === \"\"}\n >\n {sendLabel ?? strings.send}\n </button>\n </div>\n </form>\n );\n});\n"],"mappings":";;;;;;AAmEA,IAAa,IAAe,EAAkD,SAC1E,EACI,WACA,YAAS,SACT,YACA,aAAU,GACV,cACA,YACA,cACA,aACA,gBACA,cACA,aACA,GAAG,KAEP,GACF;CACE,IAAM,IAAU,EAAY,CAAM,GAC5B,IAAW,EAAmC,IAAI,GAClD,CAAC,GAAO,KAAY,EAAS,EAAE,GAC/B,CAAC,GAAM,KAAW,EAAS,EAAK;CAiBtC,AAfA,EAAoB,UAAY;EAC5B,aAAa,EAAS,SAAS,MAAM;EACrC,WAAW,MAAiB;GAExB,AADA,EAAS,CAAI,GACb,EAAS,SAAS,MAAM;EAC5B;CACJ,EAAE,GASF,QAAsB;EAClB,IAAM,IAAO,EAAS;EACtB,IAAI,CAAC,GAAM;EACX,EAAK,MAAM,SAAS;EAEpB,IAAM,KADa,OAAO,WAAW,iBAAiB,CAAI,CAAC,CAAC,UAAU,KAAK,MAClD;EAEzB,AADA,EAAK,MAAM,SAAS,GAAG,KAAK,IAAI,EAAK,cAAc,CAAG,EAAE,KACxD,EAAK,MAAM,YAAY,EAAK,eAAe,IAAM,SAAS;CAC9D,GAAG,CAAC,GAAO,CAAO,CAAC;CAEnB,IAAM,IAAS,YAA2B;EACtC,IAAM,IAAO,EAAM,KAAK;EACpB,OAAC,KAAQ,KAAQ,IACrB;KAAQ,EAAI;GACZ,IAAI;IAEA,AADA,MAAM,EAAO,CAAI,GACjB,EAAS,EAAE;GACf,SAAS,GAAO;IACZ,IAAU,CAAK;GACnB,UAAU;IACN,EAAQ,EAAK;GACjB;EARY;CAShB,GAEM,KAAgB,MAA2B;EAE7C,AADA,EAAM,eAAe,GACrB,EAAY;CAChB,GASM,KAAiB,MAAoD;EACvE,IAAY,CAAK,GACb,GAAM,qBACN,EAAM,QAAQ,WAAW,EAAM,YAC/B,EAAM,YAAY,eAAe,EAAM,YAAY,QACvD,EAAM,eAAe,GACrB,EAAY;CAChB;CAEA,OACI,kBAAC,QAAD;EAAM,WAAW,EAAG,EAAO,UAAU,CAAS;EAAG,UAAU;YAA3D,CACI,kBAAC,YAAD;GACI,GAAI;GACJ,KAAK;GACL,WAAW,EAAO;GAClB,MAAM;GACC;GACG;GACV,aAAa,KAAe,EAAQ;GACpC,WAAW,MAAU;IAEjB,AADA,EAAS,EAAM,OAAO,KAAK,GAC3B,IAAW,CAAK;GACpB;GACA,WAAW;EACd,CAAA,GACD,kBAAC,OAAD;GAAK,WAAW,EAAO;aAAvB,CACK,GACD,kBAAC,UAAD;IACI,MAAK;IACL,WAAW,EAAO;IAClB,UAAU,KAAY,KAAQ,EAAM,KAAK,MAAM;cAE9C,KAAa,EAAQ;GAClB,CAAA,CACP;IACH;;AAEd,CAAC"}
1
+ {"version":3,"file":"ChatComposer.js","names":[],"sources":["../../../src/components/Chat/ChatComposer.tsx"],"sourcesContent":["import {\n forwardRef,\n useImperativeHandle,\n useLayoutEffect,\n useRef,\n useState,\n type FormEvent,\n type KeyboardEvent,\n type ReactNode,\n type TextareaHTMLAttributes,\n} from \"react\";\n\nimport { cn } from \"@/utils/cn\";\n\nimport { chatStrings } from \"./chat-groups\";\nimport styles from \"./Chat.module.css\";\n\n/** DOM attributes the composer redefines. */\ntype OverriddenDomProps = \"onSubmit\" | \"value\" | \"defaultValue\" | \"rows\";\n\nexport interface ChatComposerProps extends Omit<\n TextareaHTMLAttributes<HTMLTextAreaElement>,\n OverriddenDomProps\n> {\n /** Called with the trimmed text. The field clears only when this does not throw. */\n onSend: (text: string) => void | Promise<void>;\n /** Locale for the placeholder and the send label. Default `\"pt-BR\"`. */\n locale?: \"pt-BR\" | \"en\";\n /** Rendered to the left of the send button — an attach button, an emoji picker. */\n actions?: ReactNode;\n /** Largest height the field grows to, in lines. Default 6. */\n maxRows?: number;\n /** Send label. Defaults to the locale string. */\n sendLabel?: string;\n /**\n * Called when `onSend` rejects. The draft is kept either way.\n *\n * Without it the rejection is swallowed after the draft is preserved, because\n * the alternative is worse: re-throwing out of a DOM event handler surfaces as\n * an unhandled promise rejection — console noise for the developer, a hit in\n * whatever crash reporter the app runs, and nothing the user can act on. The\n * visible signal is the text still sitting in the field; wire this to a toast\n * to say why.\n */\n onError?: (error: unknown) => void;\n}\n\n/** Imperative handle, so a thread can focus the field after retrying a message. */\nexport interface ChatComposerHandle {\n focus: () => void;\n /** Replace the draft — used to put a failed message back in the field. */\n setValue: (text: string) => void;\n}\n\n/**\n * The message field of a thread: a textarea that grows with its content, sends on\n * `Enter` and keeps `Shift+Enter` for a newline.\n *\n * Uncontrolled on purpose. A chat draft changes on every keystroke, and lifting\n * that into app state re-renders the whole thread per character — the one place\n * where \"controlled by default\" costs something visible. Apps that need the draft\n * (a persisted composer, a slash-command menu) read it from `onChange` or drive it\n * through the ref.\n *\n * @example\n * <ChatComposer onSend={(text) => api.post(\"/messages\", { body: { text } })} />\n */\nexport const ChatComposer = forwardRef<ChatComposerHandle, ChatComposerProps>(function ChatComposer(\n {\n onSend,\n locale = \"pt-BR\",\n actions,\n maxRows = 6,\n sendLabel,\n onError,\n className,\n disabled,\n placeholder,\n onKeyDown,\n onChange,\n ...rest\n },\n ref,\n) {\n const strings = chatStrings(locale);\n const textarea = useRef<HTMLTextAreaElement | null>(null);\n const [value, setValue] = useState(\"\");\n const [busy, setBusy] = useState(false);\n\n useImperativeHandle(ref, () => ({\n focus: () => textarea.current?.focus(),\n setValue: (text: string) => {\n setValue(text);\n textarea.current?.focus();\n },\n }));\n\n /**\n * Grow the field to fit its content, up to `maxRows`.\n *\n * Measured from `scrollHeight` after resetting the height, because\n * `scrollHeight` on an element that is already tall enough reports the\n * current height and the field would never shrink back.\n */\n useLayoutEffect(() => {\n const node = textarea.current;\n if (!node) return;\n node.style.height = \"auto\";\n const lineHeight = Number.parseFloat(getComputedStyle(node).lineHeight) || 20;\n const max = lineHeight * maxRows;\n node.style.height = `${Math.min(node.scrollHeight, max)}px`;\n node.style.overflowY = node.scrollHeight > max ? \"auto\" : \"hidden\";\n }, [value, maxRows]);\n\n const submit = async (): Promise<void> => {\n const text = value.trim();\n if (!text || busy || disabled) return;\n setBusy(true);\n try {\n await onSend(text);\n setValue(\"\");\n } catch (error) {\n onError?.(error);\n } finally {\n setBusy(false);\n }\n };\n\n const handleSubmit = (event: FormEvent): void => {\n event.preventDefault();\n void submit();\n };\n\n /**\n * `Enter` sends, `Shift+Enter` breaks the line.\n *\n * The IME check is not optional: while composing Japanese or Korean, `Enter`\n * confirms the candidate word and `keyCode === 229` marks that keystroke.\n * Sending there would post half a word and eat the confirmation.\n */\n const handleKeyDown = (event: KeyboardEvent<HTMLTextAreaElement>): void => {\n onKeyDown?.(event);\n if (event.defaultPrevented) return;\n if (event.key !== \"Enter\" || event.shiftKey) return;\n if (event.nativeEvent.isComposing || event.keyCode === 229) return;\n event.preventDefault();\n void submit();\n };\n\n return (\n <form className={cn(styles.composer, className)} onSubmit={handleSubmit}>\n <textarea\n {...rest}\n ref={textarea}\n className={styles.field}\n rows={1}\n value={value}\n disabled={disabled}\n placeholder={placeholder ?? strings.placeholder}\n onChange={(event) => {\n setValue(event.target.value);\n onChange?.(event);\n }}\n onKeyDown={handleKeyDown}\n />\n <div className={styles.composerActions}>\n {actions}\n <button\n type=\"submit\"\n className={styles.send}\n disabled={disabled || busy || value.trim() === \"\"}\n >\n {sendLabel ?? strings.send}\n </button>\n </div>\n </form>\n );\n});\n"],"mappings":";;;;;;AAmEA,IAAa,IAAe,EAAkD,SAC1E,EACI,WACA,YAAS,SACT,YACA,aAAU,GACV,cACA,YACA,cACA,aACA,gBACA,cACA,aACA,GAAG,KAEP,GACF;CACE,IAAM,IAAU,EAAY,CAAM,GAC5B,IAAW,EAAmC,IAAI,GAClD,CAAC,GAAO,KAAY,EAAS,EAAE,GAC/B,CAAC,GAAM,KAAW,EAAS,EAAK;CAiBtC,AAfA,EAAoB,UAAY;EAC5B,aAAa,EAAS,SAAS,MAAM;EACrC,WAAW,MAAiB;GAExB,AADA,EAAS,CAAI,GACb,EAAS,SAAS,MAAM;EAC5B;CACJ,EAAE,GASF,QAAsB;EAClB,IAAM,IAAO,EAAS;EACtB,IAAI,CAAC,GAAM;EACX,EAAK,MAAM,SAAS;EAEpB,IAAM,KADa,OAAO,WAAW,iBAAiB,CAAI,CAAC,CAAC,UAAU,KAAK,MAClD;EAEzB,AADA,EAAK,MAAM,SAAS,GAAG,KAAK,IAAI,EAAK,cAAc,CAAG,EAAE,KACxD,EAAK,MAAM,YAAY,EAAK,eAAe,IAAM,SAAS;CAC9D,GAAG,CAAC,GAAO,CAAO,CAAC;CAEnB,IAAM,IAAS,YAA2B;EACtC,IAAM,IAAO,EAAM,KAAK;EACpB,OAAC,KAAQ,KAAQ,IACrB;KAAQ,EAAI;GACZ,IAAI;IAEA,AADA,MAAM,EAAO,CAAI,GACjB,EAAS,EAAE;GACf,SAAS,GAAO;IACZ,IAAU,CAAK;GACnB,UAAU;IACN,EAAQ,EAAK;GACjB;EARY;CAShB,GAEM,KAAgB,MAA2B;EAE7C,AADA,EAAM,eAAe,GACrB,EAAY;CAChB,GASM,KAAiB,MAAoD;EACvE,IAAY,CAAK,GACb,GAAM,qBACN,EAAM,QAAQ,WAAW,EAAM,YAC/B,EAAM,YAAY,eAAe,EAAM,YAAY,QACvD,EAAM,eAAe,GACrB,EAAY;CAChB;CAEA,OACI,kBAAC,QAAD;EAAM,WAAW,EAAG,EAAO,UAAU,CAAS;EAAG,UAAU;EAA3D,UAAA,CACI,kBAAC,YAAD;GACI,GAAI;GACJ,KAAK;GACL,WAAW,EAAO;GAClB,MAAM;GACC;GACG;GACV,aAAa,KAAe,EAAQ;GACpC,WAAW,MAAU;IAEjB,AADA,EAAS,EAAM,OAAO,KAAK,GAC3B,IAAW,CAAK;GACpB;GACA,WAAW;EACd,CAAA,GACD,kBAAC,OAAD;GAAK,WAAW,EAAO;GAAvB,UAAA,CACK,GACD,kBAAC,UAAD;IACI,MAAK;IACL,WAAW,EAAO;IAClB,UAAU,KAAY,KAAQ,EAAM,KAAK,MAAM;IAE9C,UAAA,KAAa,EAAQ;GAClB,CAAA,CACP;EACH,CAAA,CAAA;;AAEd,CAAC"}
@@ -1,2 +1,2 @@
1
- var e=300*1e3;function t(e){let t=new Date(e);return t.setHours(0,0,0,0),t.getTime()}function n({messages:n,currentUserId:r,windowMs:i=e}){let a=[],o=null,s=null;for(let e of n){let n=t(e.sentAt);n!==o&&(o=n,s=null,a.push({kind:`day`,key:`day-${n}`,date:n}));let c=s?.messages[s.messages.length-1];if(s!==null&&c!==void 0&&s.authorId===e.authorId&&e.sentAt-c.sentAt<=i&&s){s.messages.push(e);continue}s={kind:`run`,key:`${e.authorId}-${e.id}`,authorId:e.authorId,authorName:e.authorName,own:r!==void 0&&e.authorId===r,messages:[e]},a.push(s)}return a}var r={thread:`Conversa`,today:`Hoje`,yesterday:`Ontem`,you:`Você`,typingOne:e=>`${e} está digitando…`,typingTwo:(e,t)=>`${e} e ${t} estão digitando…`,typingMany:e=>`${e} pessoas estão digitando…`,sending:`Enviando`,sent:`Enviada`,read:`Lida`,failed:`Falhou ao enviar`,retry:`Tentar de novo`,empty:`Nenhuma mensagem ainda`,placeholder:`Escreva uma mensagem`,send:`Enviar`},i={thread:`Conversation`,today:`Today`,yesterday:`Yesterday`,you:`You`,typingOne:e=>`${e} is typing…`,typingTwo:(e,t)=>`${e} and ${t} are typing…`,typingMany:e=>`${e} people are typing…`,sending:`Sending`,sent:`Sent`,read:`Read`,failed:`Failed to send`,retry:`Try again`,empty:`No messages yet`,placeholder:`Write a message`,send:`Send`};function a(e){return e===`en`?i:r}function o(e,{locale:n=`pt-BR`,now:r}={}){let i=a(n),o=t(r??Date.now()),s=1440*60*1e3;return e===o?i.today:e===o-s?i.yesterday:new Date(e).toLocaleDateString(n===`en`?`en-US`:`pt-BR`,{day:`2-digit`,month:`short`,year:e<o-300*s?`numeric`:void 0})}function s(e,t=`pt-BR`){return new Date(e).toLocaleTimeString(t===`en`?`en-US`:`pt-BR`,{hour:`2-digit`,minute:`2-digit`})}function c(e,t=`pt-BR`){let n=a(t);return e.length===0?null:e.length===1?n.typingOne(e[0]):e.length===2?n.typingTwo(e[0],e[1]):n.typingMany(e.length)}exports.DEFAULT_GROUP_WINDOW_MS=e,exports.chatStrings=a,exports.dayLabel=o,exports.groupMessages=n,exports.timeLabel=s,exports.typingLabel=c;
1
+ var e=3e5;function t(e){let t=new Date(e);return t.setHours(0,0,0,0),t.getTime()}function n({messages:n,currentUserId:r,windowMs:i=e}){let a=[],o=null,s=null;for(let e of n){let n=t(e.sentAt);n!==o&&(o=n,s=null,a.push({kind:`day`,key:`day-${n}`,date:n}));let c=s?.messages[s.messages.length-1];if(s!==null&&c!==void 0&&s.authorId===e.authorId&&e.sentAt-c.sentAt<=i&&s){s.messages.push(e);continue}s={kind:`run`,key:`${e.authorId}-${e.id}`,authorId:e.authorId,authorName:e.authorName,own:r!==void 0&&e.authorId===r,messages:[e]},a.push(s)}return a}var r={thread:`Conversa`,today:`Hoje`,yesterday:`Ontem`,you:`Você`,typingOne:e=>`${e} está digitando…`,typingTwo:(e,t)=>`${e} e ${t} estão digitando…`,typingMany:e=>`${e} pessoas estão digitando…`,sending:`Enviando`,sent:`Enviada`,read:`Lida`,failed:`Falhou ao enviar`,retry:`Tentar de novo`,empty:`Nenhuma mensagem ainda`,placeholder:`Escreva uma mensagem`,send:`Enviar`},i={thread:`Conversation`,today:`Today`,yesterday:`Yesterday`,you:`You`,typingOne:e=>`${e} is typing…`,typingTwo:(e,t)=>`${e} and ${t} are typing…`,typingMany:e=>`${e} people are typing…`,sending:`Sending`,sent:`Sent`,read:`Read`,failed:`Failed to send`,retry:`Try again`,empty:`No messages yet`,placeholder:`Write a message`,send:`Send`};function a(e){return e===`en`?i:r}function o(e,{locale:n=`pt-BR`,now:r}={}){let i=a(n),o=t(r??Date.now()),s=864e5;return e===o?i.today:e===o-s?i.yesterday:new Date(e).toLocaleDateString(n===`en`?`en-US`:`pt-BR`,{day:`2-digit`,month:`short`,year:e<o-300*s?`numeric`:void 0})}function s(e,t=`pt-BR`){return new Date(e).toLocaleTimeString(t===`en`?`en-US`:`pt-BR`,{hour:`2-digit`,minute:`2-digit`})}function c(e,t=`pt-BR`){let n=a(t);return e.length===0?null:e.length===1?n.typingOne(e[0]):e.length===2?n.typingTwo(e[0],e[1]):n.typingMany(e.length)}exports.DEFAULT_GROUP_WINDOW_MS=e,exports.chatStrings=a,exports.dayLabel=o,exports.groupMessages=n,exports.timeLabel=s,exports.typingLabel=c;
2
2
  //# sourceMappingURL=chat-groups.cjs.map