tempest-react-sdk 0.39.0 → 0.40.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (475) hide show
  1. package/dist/_virtual/_rolldown/runtime.cjs +1 -1
  2. package/dist/access/access-control-context.cjs.map +1 -1
  3. package/dist/access/use-can.cjs.map +1 -1
  4. package/dist/app/AppProviders.cjs.map +1 -1
  5. package/dist/app/AppProviders.js.map +1 -1
  6. package/dist/audio/use-audio-recorder.cjs.map +1 -1
  7. package/dist/audio/use-audio.cjs.map +1 -1
  8. package/dist/audio/use-microphone.cjs.map +1 -1
  9. package/dist/auth/create-auth-store.cjs.map +1 -1
  10. package/dist/auth/lazy-with-retry.cjs.map +1 -1
  11. package/dist/auth/passkey.cjs.map +1 -1
  12. package/dist/auth/passkey.js.map +1 -1
  13. package/dist/auth/use-passkey.cjs.map +1 -1
  14. package/dist/br/BrazilMap.cjs +1 -1
  15. package/dist/br/BrazilMap.cjs.map +1 -1
  16. package/dist/br/BrazilMap.js +2 -2
  17. package/dist/br/BrazilMap.js.map +1 -1
  18. package/dist/br/BrazilStateCitySelect.cjs.map +1 -1
  19. package/dist/br/BrazilStateCitySelect.js.map +1 -1
  20. package/dist/br/BrazilStateMap.cjs +1 -1
  21. package/dist/br/BrazilStateMap.cjs.map +1 -1
  22. package/dist/br/BrazilStateMap.js +2 -2
  23. package/dist/br/BrazilStateMap.js.map +1 -1
  24. package/dist/br/MapLegend.cjs.map +1 -1
  25. package/dist/br/MapLegend.js.map +1 -1
  26. package/dist/br/MapMarkers.cjs.map +1 -1
  27. package/dist/br/MapMarkers.js.map +1 -1
  28. package/dist/br/MunicipalitySearch.cjs.map +1 -1
  29. package/dist/br/MunicipalitySearch.js.map +1 -1
  30. package/dist/br/PixQRCode.cjs.map +1 -1
  31. package/dist/br/PixQRCode.js.map +1 -1
  32. package/dist/br/use-map-hover.cjs.map +1 -1
  33. package/dist/br/use-map-zoom.cjs.map +1 -1
  34. package/dist/capture/media-recorder.cjs +1 -1
  35. package/dist/capture/media-recorder.js +2 -2
  36. package/dist/capture/use-barcode-scanner.cjs +1 -1
  37. package/dist/capture/use-barcode-scanner.cjs.map +1 -1
  38. package/dist/capture/use-barcode-scanner.js +2 -2
  39. package/dist/capture/use-barcode-scanner.js.map +1 -1
  40. package/dist/capture/use-screen-capture.cjs.map +1 -1
  41. package/dist/capture/use-speech-recognition.cjs.map +1 -1
  42. package/dist/capture/use-torch.cjs.map +1 -1
  43. package/dist/capture/use-video-recorder.cjs.map +1 -1
  44. package/dist/charts/AreaChart.cjs.map +1 -1
  45. package/dist/charts/AreaChart.js.map +1 -1
  46. package/dist/charts/BarChart.cjs.map +1 -1
  47. package/dist/charts/BarChart.js.map +1 -1
  48. package/dist/charts/LineChart.cjs.map +1 -1
  49. package/dist/charts/LineChart.js.map +1 -1
  50. package/dist/charts/PieChart.cjs.map +1 -1
  51. package/dist/charts/PieChart.js.map +1 -1
  52. package/dist/charts/RadarChart.cjs.map +1 -1
  53. package/dist/charts/RadarChart.js.map +1 -1
  54. package/dist/charts/use-chart-colors.cjs.map +1 -1
  55. package/dist/components/AIChat/AIChat.cjs.map +1 -1
  56. package/dist/components/AIChat/AIChat.js.map +1 -1
  57. package/dist/components/AIChat/AIChatComposer.cjs.map +1 -1
  58. package/dist/components/AIChat/AIChatComposer.js.map +1 -1
  59. package/dist/components/AIChat/AIChatTurn.cjs.map +1 -1
  60. package/dist/components/AIChat/AIChatTurn.js.map +1 -1
  61. package/dist/components/Accordion/Accordion.cjs.map +1 -1
  62. package/dist/components/Accordion/Accordion.js.map +1 -1
  63. package/dist/components/Alert/Alert.cjs.map +1 -1
  64. package/dist/components/Alert/Alert.js.map +1 -1
  65. package/dist/components/AppBar/AppBar.cjs.map +1 -1
  66. package/dist/components/AppBar/AppBar.js.map +1 -1
  67. package/dist/components/AppShell/AppShell.cjs.map +1 -1
  68. package/dist/components/AppShell/AppShell.js.map +1 -1
  69. package/dist/components/AudioPlayer/AudioPlayer.cjs.map +1 -1
  70. package/dist/components/AudioPlayer/AudioPlayer.js.map +1 -1
  71. package/dist/components/AudioRecorder/AudioRecorder.cjs.map +1 -1
  72. package/dist/components/AudioRecorder/AudioRecorder.js.map +1 -1
  73. package/dist/components/Avatar/Avatar.cjs.map +1 -1
  74. package/dist/components/Avatar/Avatar.js.map +1 -1
  75. package/dist/components/AvatarGroup/AvatarGroup.cjs.map +1 -1
  76. package/dist/components/AvatarGroup/AvatarGroup.js.map +1 -1
  77. package/dist/components/Badge/Badge.cjs.map +1 -1
  78. package/dist/components/Badge/Badge.js.map +1 -1
  79. package/dist/components/Banner/Banner.cjs.map +1 -1
  80. package/dist/components/Banner/Banner.js.map +1 -1
  81. package/dist/components/BarcodeScanner/BarcodeScanner.cjs.map +1 -1
  82. package/dist/components/BarcodeScanner/BarcodeScanner.js.map +1 -1
  83. package/dist/components/BottomNavigation/BottomNavigation.cjs.map +1 -1
  84. package/dist/components/BottomNavigation/BottomNavigation.js.map +1 -1
  85. package/dist/components/BottomSheet/BottomSheet.cjs.map +1 -1
  86. package/dist/components/BottomSheet/BottomSheet.js.map +1 -1
  87. package/dist/components/Breadcrumbs/Breadcrumbs.cjs.map +1 -1
  88. package/dist/components/Breadcrumbs/Breadcrumbs.js.map +1 -1
  89. package/dist/components/Button/Button.cjs.map +1 -1
  90. package/dist/components/Button/Button.js.map +1 -1
  91. package/dist/components/Calendar/Calendar.cjs +1 -1
  92. package/dist/components/Calendar/Calendar.cjs.map +1 -1
  93. package/dist/components/Calendar/Calendar.js +38 -41
  94. package/dist/components/Calendar/Calendar.js.map +1 -1
  95. package/dist/components/Card/Card.cjs.map +1 -1
  96. package/dist/components/Card/Card.js.map +1 -1
  97. package/dist/components/Carousel/Carousel.cjs.map +1 -1
  98. package/dist/components/Carousel/Carousel.js.map +1 -1
  99. package/dist/components/Chat/Chat.cjs.map +1 -1
  100. package/dist/components/Chat/Chat.js.map +1 -1
  101. package/dist/components/Chat/ChatComposer.cjs.map +1 -1
  102. package/dist/components/Chat/ChatComposer.js.map +1 -1
  103. package/dist/components/Chat/chat-groups.cjs +1 -1
  104. package/dist/components/Chat/chat-groups.cjs.map +1 -1
  105. package/dist/components/Chat/chat-groups.js +2 -2
  106. package/dist/components/Chat/chat-groups.js.map +1 -1
  107. package/dist/components/Checkbox/Checkbox.cjs.map +1 -1
  108. package/dist/components/Checkbox/Checkbox.js.map +1 -1
  109. package/dist/components/ChipInput/ChipInput.cjs.map +1 -1
  110. package/dist/components/ChipInput/ChipInput.js.map +1 -1
  111. package/dist/components/ClickOutside/ClickOutside.cjs.map +1 -1
  112. package/dist/components/CodeBlock/CodeBlock.cjs.map +1 -1
  113. package/dist/components/CodeBlock/CodeBlock.js.map +1 -1
  114. package/dist/components/Collapsible/Collapsible.cjs.map +1 -1
  115. package/dist/components/Collapsible/Collapsible.js.map +1 -1
  116. package/dist/components/Combobox/Combobox.cjs.map +1 -1
  117. package/dist/components/Combobox/Combobox.js.map +1 -1
  118. package/dist/components/Command/Command.cjs.map +1 -1
  119. package/dist/components/Command/Command.js.map +1 -1
  120. package/dist/components/ConfirmDialog/ConfirmDialog.cjs.map +1 -1
  121. package/dist/components/ConfirmDialog/ConfirmDialog.js.map +1 -1
  122. package/dist/components/ContextMenu/ContextMenu.cjs.map +1 -1
  123. package/dist/components/ContextMenu/ContextMenu.js.map +1 -1
  124. package/dist/components/CopyButton/CopyButton.cjs.map +1 -1
  125. package/dist/components/CopyButton/CopyButton.js.map +1 -1
  126. package/dist/components/DataList/DataList.cjs.map +1 -1
  127. package/dist/components/DataList/DataList.js.map +1 -1
  128. package/dist/components/DataTable/DataTable.cjs +1 -1
  129. package/dist/components/DataTable/DataTable.cjs.map +1 -1
  130. package/dist/components/DataTable/DataTable.js +2 -2
  131. package/dist/components/DataTable/DataTable.js.map +1 -1
  132. package/dist/components/DataTable/EditableCell.cjs.map +1 -1
  133. package/dist/components/DataTable/EditableCell.js.map +1 -1
  134. package/dist/components/DatePicker/DatePicker.cjs.map +1 -1
  135. package/dist/components/DateRangePicker/DateRangePicker.cjs.map +1 -1
  136. package/dist/components/DateRangePicker/DateRangePicker.js.map +1 -1
  137. package/dist/components/DescriptionList/DescriptionList.cjs.map +1 -1
  138. package/dist/components/DescriptionList/DescriptionList.js.map +1 -1
  139. package/dist/components/Divider/Divider.cjs.map +1 -1
  140. package/dist/components/Divider/Divider.js.map +1 -1
  141. package/dist/components/Drawer/Drawer.cjs.map +1 -1
  142. package/dist/components/Drawer/Drawer.js.map +1 -1
  143. package/dist/components/DropdownMenu/DropdownMenu.cjs.map +1 -1
  144. package/dist/components/DropdownMenu/DropdownMenu.js.map +1 -1
  145. package/dist/components/Dropzone/Dropzone.cjs.map +1 -1
  146. package/dist/components/Dropzone/Dropzone.js.map +1 -1
  147. package/dist/components/EmptyState/EmptyState.cjs.map +1 -1
  148. package/dist/components/EmptyState/EmptyState.js.map +1 -1
  149. package/dist/components/ErrorState/ErrorState.cjs.map +1 -1
  150. package/dist/components/ErrorState/ErrorState.js.map +1 -1
  151. package/dist/components/FileUpload/FileUpload.cjs +1 -1
  152. package/dist/components/FileUpload/FileUpload.cjs.map +1 -1
  153. package/dist/components/FileUpload/FileUpload.js +1 -1
  154. package/dist/components/FileUpload/FileUpload.js.map +1 -1
  155. package/dist/components/FilterBar/FilterBar.cjs.map +1 -1
  156. package/dist/components/FilterBar/FilterBar.js.map +1 -1
  157. package/dist/components/FloatingActionButton/FloatingActionButton.cjs.map +1 -1
  158. package/dist/components/FloatingActionButton/FloatingActionButton.js.map +1 -1
  159. package/dist/components/Form/Form.cjs.map +1 -1
  160. package/dist/components/Form/Form.js.map +1 -1
  161. package/dist/components/HoverCard/HoverCard.cjs.map +1 -1
  162. package/dist/components/HoverCard/HoverCard.js.map +1 -1
  163. package/dist/components/Image/Image.cjs.map +1 -1
  164. package/dist/components/ImageCropper/ImageCropper.cjs +1 -1
  165. package/dist/components/ImageCropper/ImageCropper.cjs.map +1 -1
  166. package/dist/components/ImageCropper/ImageCropper.js +1 -1
  167. package/dist/components/ImageCropper/ImageCropper.js.map +1 -1
  168. package/dist/components/Input/Input.cjs.map +1 -1
  169. package/dist/components/Input/Input.js.map +1 -1
  170. package/dist/components/InstallBanner/InstallBanner.cjs.map +1 -1
  171. package/dist/components/InstallBanner/InstallBanner.js.map +1 -1
  172. package/dist/components/InstallButton/InstallButton.cjs.map +1 -1
  173. package/dist/components/InstallButton/InstallButton.js.map +1 -1
  174. package/dist/components/Kanban/Kanban.cjs.map +1 -1
  175. package/dist/components/Kanban/Kanban.js.map +1 -1
  176. package/dist/components/Label/Label.cjs.map +1 -1
  177. package/dist/components/Label/Label.js.map +1 -1
  178. package/dist/components/Lightbox/Lightbox.cjs.map +1 -1
  179. package/dist/components/Lightbox/Lightbox.js.map +1 -1
  180. package/dist/components/ListTile/ListTile.cjs.map +1 -1
  181. package/dist/components/ListTile/ListTile.js.map +1 -1
  182. package/dist/components/Markdown/Markdown.cjs +1 -1
  183. package/dist/components/Markdown/Markdown.cjs.map +1 -1
  184. package/dist/components/Markdown/Markdown.js +2 -2
  185. package/dist/components/Markdown/Markdown.js.map +1 -1
  186. package/dist/components/Masonry/Masonry.cjs.map +1 -1
  187. package/dist/components/Masonry/Masonry.js.map +1 -1
  188. package/dist/components/Menubar/Menubar.cjs.map +1 -1
  189. package/dist/components/Menubar/Menubar.js.map +1 -1
  190. package/dist/components/Modal/Modal.cjs.map +1 -1
  191. package/dist/components/Modal/Modal.js.map +1 -1
  192. package/dist/components/ModalsManager/ModalsManager.cjs.map +1 -1
  193. package/dist/components/ModalsManager/ModalsManager.js.map +1 -1
  194. package/dist/components/Money/Money.cjs.map +1 -1
  195. package/dist/components/Money/Money.js.map +1 -1
  196. package/dist/components/MultiSelect/MultiSelect.cjs.map +1 -1
  197. package/dist/components/MultiSelect/MultiSelect.js.map +1 -1
  198. package/dist/components/NProgress/NProgress.cjs.map +1 -1
  199. package/dist/components/Navbar/Navbar.cjs.map +1 -1
  200. package/dist/components/Navbar/Navbar.js.map +1 -1
  201. package/dist/components/NavigationMenu/NavigationMenu.cjs.map +1 -1
  202. package/dist/components/NavigationMenu/NavigationMenu.js.map +1 -1
  203. package/dist/components/NavigationRail/NavigationRail.cjs.map +1 -1
  204. package/dist/components/NavigationRail/NavigationRail.js.map +1 -1
  205. package/dist/components/NotificationCenter/NotificationCenter.cjs.map +1 -1
  206. package/dist/components/NotificationCenter/NotificationCenter.js.map +1 -1
  207. package/dist/components/NotificationCenter/use-notification-inbox.cjs.map +1 -1
  208. package/dist/components/OfflineIndicator/OfflineIndicator.cjs.map +1 -1
  209. package/dist/components/OfflineIndicator/OfflineIndicator.js.map +1 -1
  210. package/dist/components/Page/Page.cjs.map +1 -1
  211. package/dist/components/Page/Page.js.map +1 -1
  212. package/dist/components/Pagination/Pagination.cjs.map +1 -1
  213. package/dist/components/Pagination/Pagination.js.map +1 -1
  214. package/dist/components/PasswordInput/PasswordInput.cjs.map +1 -1
  215. package/dist/components/PasswordInput/PasswordInput.js.map +1 -1
  216. package/dist/components/PinInput/PinInput.cjs.map +1 -1
  217. package/dist/components/PinInput/PinInput.js.map +1 -1
  218. package/dist/components/Popover/Popover.cjs.map +1 -1
  219. package/dist/components/Popover/Popover.js.map +1 -1
  220. package/dist/components/Portal/Portal.cjs.map +1 -1
  221. package/dist/components/Progress/Progress.cjs.map +1 -1
  222. package/dist/components/Progress/Progress.js.map +1 -1
  223. package/dist/components/QRCode/QRCode.cjs.map +1 -1
  224. package/dist/components/QRCode/QRCode.js.map +1 -1
  225. package/dist/components/Radio/Radio.cjs.map +1 -1
  226. package/dist/components/Radio/Radio.js.map +1 -1
  227. package/dist/components/RangeSlider/RangeSlider.cjs.map +1 -1
  228. package/dist/components/RangeSlider/RangeSlider.js.map +1 -1
  229. package/dist/components/RatingStars/RatingStars.cjs.map +1 -1
  230. package/dist/components/RatingStars/RatingStars.js.map +1 -1
  231. package/dist/components/RefreshIndicator/RefreshIndicator.cjs.map +1 -1
  232. package/dist/components/RefreshIndicator/RefreshIndicator.js.map +1 -1
  233. package/dist/components/RelativeTime/RelativeTime.cjs.map +1 -1
  234. package/dist/components/RelativeTime/RelativeTime.js.map +1 -1
  235. package/dist/components/Resizable/Resizable.cjs.map +1 -1
  236. package/dist/components/Resizable/Resizable.js.map +1 -1
  237. package/dist/components/Scheduler/Scheduler.cjs.map +1 -1
  238. package/dist/components/Scheduler/Scheduler.js.map +1 -1
  239. package/dist/components/ScrollArea/ScrollArea.cjs.map +1 -1
  240. package/dist/components/SearchBar/SearchBar.cjs.map +1 -1
  241. package/dist/components/SearchBar/SearchBar.js.map +1 -1
  242. package/dist/components/SegmentedControl/SegmentedControl.cjs.map +1 -1
  243. package/dist/components/SegmentedControl/SegmentedControl.js.map +1 -1
  244. package/dist/components/Select/Select.cjs.map +1 -1
  245. package/dist/components/Select/Select.js.map +1 -1
  246. package/dist/components/Sidebar/Sidebar.cjs.map +1 -1
  247. package/dist/components/Sidebar/Sidebar.js.map +1 -1
  248. package/dist/components/SignaturePad/SignaturePad.cjs.map +1 -1
  249. package/dist/components/SignaturePad/SignaturePad.js.map +1 -1
  250. package/dist/components/Slider/Slider.cjs.map +1 -1
  251. package/dist/components/Slider/Slider.js.map +1 -1
  252. package/dist/components/Sparkline/Sparkline.cjs.map +1 -1
  253. package/dist/components/Sparkline/Sparkline.js.map +1 -1
  254. package/dist/components/Spinner/Spinner.cjs.map +1 -1
  255. package/dist/components/Spinner/Spinner.js.map +1 -1
  256. package/dist/components/Stat/Stat.cjs.map +1 -1
  257. package/dist/components/Stat/Stat.js.map +1 -1
  258. package/dist/components/Stepper/Stepper.cjs.map +1 -1
  259. package/dist/components/Stepper/Stepper.js.map +1 -1
  260. package/dist/components/StepperInput/StepperInput.cjs.map +1 -1
  261. package/dist/components/StepperInput/StepperInput.js.map +1 -1
  262. package/dist/components/Switch/Switch.cjs.map +1 -1
  263. package/dist/components/Switch/Switch.js.map +1 -1
  264. package/dist/components/SyncStatusBadge/SyncStatusBadge.cjs.map +1 -1
  265. package/dist/components/SyncStatusBadge/SyncStatusBadge.js.map +1 -1
  266. package/dist/components/Table/Table.cjs.map +1 -1
  267. package/dist/components/Table/Table.js.map +1 -1
  268. package/dist/components/Tabs/Tabs.cjs.map +1 -1
  269. package/dist/components/Tabs/Tabs.js.map +1 -1
  270. package/dist/components/Tag/Tag.cjs.map +1 -1
  271. package/dist/components/Tag/Tag.js.map +1 -1
  272. package/dist/components/Textarea/Textarea.cjs.map +1 -1
  273. package/dist/components/Textarea/Textarea.js.map +1 -1
  274. package/dist/components/TimePicker/TimePicker.cjs.map +1 -1
  275. package/dist/components/TimePicker/TimePicker.js.map +1 -1
  276. package/dist/components/Timeline/Timeline.cjs.map +1 -1
  277. package/dist/components/Timeline/Timeline.js.map +1 -1
  278. package/dist/components/Toast/ToastProvider.cjs.map +1 -1
  279. package/dist/components/Toast/ToastProvider.js.map +1 -1
  280. package/dist/components/Toggle/Toggle.cjs.map +1 -1
  281. package/dist/components/ToggleGroup/ToggleGroup.cjs.map +1 -1
  282. package/dist/components/ToggleGroup/ToggleGroup.js.map +1 -1
  283. package/dist/components/Tooltip/Tooltip.cjs.map +1 -1
  284. package/dist/components/Tooltip/Tooltip.js.map +1 -1
  285. package/dist/components/Tour/Tour.cjs.map +1 -1
  286. package/dist/components/Tour/Tour.js.map +1 -1
  287. package/dist/components/Transfer/Transfer.cjs.map +1 -1
  288. package/dist/components/Transfer/Transfer.js.map +1 -1
  289. package/dist/components/TreeView/TreeView.cjs +1 -1
  290. package/dist/components/TreeView/TreeView.cjs.map +1 -1
  291. package/dist/components/TreeView/TreeView.js +1 -4
  292. package/dist/components/TreeView/TreeView.js.map +1 -1
  293. package/dist/components/UpdatePrompt/UpdatePrompt.cjs.map +1 -1
  294. package/dist/components/UpdatePrompt/UpdatePrompt.js.map +1 -1
  295. package/dist/components/VirtualList/VirtualList.cjs.map +1 -1
  296. package/dist/components/VirtualList/VirtualList.js.map +1 -1
  297. package/dist/components/VirtualTable/VirtualTable.cjs.map +1 -1
  298. package/dist/components/VirtualTable/VirtualTable.js.map +1 -1
  299. package/dist/components/VisuallyHidden/VisuallyHidden.cjs.map +1 -1
  300. package/dist/components/Wizard/Wizard.cjs.map +1 -1
  301. package/dist/components/Wizard/Wizard.js.map +1 -1
  302. package/dist/data/data-provider-context.cjs.map +1 -1
  303. package/dist/data/use-resource.cjs.map +1 -1
  304. package/dist/editor/RichTextEditor.cjs.map +1 -1
  305. package/dist/editor/RichTextEditor.js.map +1 -1
  306. package/dist/error-boundary/use-error-handler.cjs.map +1 -1
  307. package/dist/feature-flags/FeatureFlagsProvider.cjs.map +1 -1
  308. package/dist/forms/FormField.cjs.map +1 -1
  309. package/dist/forms/masked-inputs.cjs +1 -1
  310. package/dist/forms/masked-inputs.cjs.map +1 -1
  311. package/dist/forms/masked-inputs.js +1 -1
  312. package/dist/forms/masked-inputs.js.map +1 -1
  313. package/dist/forms/use-viacep.cjs.map +1 -1
  314. package/dist/forms/use-zod-form.cjs.map +1 -1
  315. package/dist/forms/zod-resolver.cjs.map +1 -1
  316. package/dist/forms/zod-resolver.js.map +1 -1
  317. package/dist/geo/TrajectoryMap.cjs.map +1 -1
  318. package/dist/geo/TrajectoryMap.js.map +1 -1
  319. package/dist/geo/leaflet-map.cjs.map +1 -1
  320. package/dist/geo/leaflet-map.js.map +1 -1
  321. package/dist/geo/use-position-tracker.cjs.map +1 -1
  322. package/dist/hooks/use-announce.cjs.map +1 -1
  323. package/dist/hooks/use-async.cjs.map +1 -1
  324. package/dist/hooks/use-before-install-prompt.cjs.map +1 -1
  325. package/dist/hooks/use-breakpoint.cjs.map +1 -1
  326. package/dist/hooks/use-click-outside.cjs.map +1 -1
  327. package/dist/hooks/use-client-filter.cjs.map +1 -1
  328. package/dist/hooks/use-clipboard.cjs.map +1 -1
  329. package/dist/hooks/use-counter.cjs.map +1 -1
  330. package/dist/hooks/use-debounce.cjs.map +1 -1
  331. package/dist/hooks/use-deep-memo.cjs.map +1 -1
  332. package/dist/hooks/use-disclosure.cjs.map +1 -1
  333. package/dist/hooks/use-document-visibility.cjs.map +1 -1
  334. package/dist/hooks/use-event-listener.cjs.map +1 -1
  335. package/dist/hooks/use-geolocation.cjs.map +1 -1
  336. package/dist/hooks/use-hover.cjs.map +1 -1
  337. package/dist/hooks/use-idle.cjs.map +1 -1
  338. package/dist/hooks/use-install-prompt.cjs +1 -1
  339. package/dist/hooks/use-install-prompt.cjs.map +1 -1
  340. package/dist/hooks/use-install-prompt.js +1 -1
  341. package/dist/hooks/use-install-prompt.js.map +1 -1
  342. package/dist/hooks/use-intersection-observer.cjs.map +1 -1
  343. package/dist/hooks/use-interval.cjs.map +1 -1
  344. package/dist/hooks/use-is-first-render.cjs.map +1 -1
  345. package/dist/hooks/use-keyboard-shortcut.cjs +1 -1
  346. package/dist/hooks/use-keyboard-shortcut.cjs.map +1 -1
  347. package/dist/hooks/use-keyboard-shortcut.js +1 -1
  348. package/dist/hooks/use-keyboard-shortcut.js.map +1 -1
  349. package/dist/hooks/use-list-state.cjs.map +1 -1
  350. package/dist/hooks/use-local-storage.cjs.map +1 -1
  351. package/dist/hooks/use-long-press-handlers.cjs.map +1 -1
  352. package/dist/hooks/use-long-press.cjs.map +1 -1
  353. package/dist/hooks/use-map.cjs.map +1 -1
  354. package/dist/hooks/use-media-devices.cjs.map +1 -1
  355. package/dist/hooks/use-media-permission.cjs.map +1 -1
  356. package/dist/hooks/use-media-query.cjs.map +1 -1
  357. package/dist/hooks/use-object-url.cjs.map +1 -1
  358. package/dist/hooks/use-online.cjs.map +1 -1
  359. package/dist/hooks/use-pagination.cjs.map +1 -1
  360. package/dist/hooks/use-previous.cjs.map +1 -1
  361. package/dist/hooks/use-queue.cjs.map +1 -1
  362. package/dist/hooks/use-resize-observer.cjs.map +1 -1
  363. package/dist/hooks/use-scroll-overflow.cjs.map +1 -1
  364. package/dist/hooks/use-service-worker-update.cjs.map +1 -1
  365. package/dist/hooks/use-set.cjs.map +1 -1
  366. package/dist/hooks/use-sortable.cjs +1 -1
  367. package/dist/hooks/use-sortable.cjs.map +1 -1
  368. package/dist/hooks/use-sortable.js +0 -2
  369. package/dist/hooks/use-sortable.js.map +1 -1
  370. package/dist/hooks/use-stable-callback.cjs.map +1 -1
  371. package/dist/hooks/use-storage-estimate.cjs.map +1 -1
  372. package/dist/hooks/use-throttle.cjs.map +1 -1
  373. package/dist/hooks/use-timeout.cjs.map +1 -1
  374. package/dist/hooks/use-toggle.cjs.map +1 -1
  375. package/dist/hooks/use-window-size.cjs.map +1 -1
  376. package/dist/http/api-client.cjs +1 -1
  377. package/dist/http/api-client.cjs.map +1 -1
  378. package/dist/http/api-client.js +7 -5
  379. package/dist/http/api-client.js.map +1 -1
  380. package/dist/http/resumable-upload.cjs +1 -1
  381. package/dist/http/resumable-upload.cjs.map +1 -1
  382. package/dist/http/resumable-upload.js +1 -1
  383. package/dist/http/resumable-upload.js.map +1 -1
  384. package/dist/http/use-poll.cjs.map +1 -1
  385. package/dist/i18n/I18nProvider.cjs.map +1 -1
  386. package/dist/icons/Icon.cjs.map +1 -1
  387. package/dist/icons/IconProvider.cjs.map +1 -1
  388. package/dist/icons/icon-context.cjs.map +1 -1
  389. package/dist/icons/use-icon.cjs.map +1 -1
  390. package/dist/imaging/use-image-processing.cjs.map +1 -1
  391. package/dist/oauth/GoogleSignIn.cjs.map +1 -1
  392. package/dist/oauth/GoogleSignIn.js.map +1 -1
  393. package/dist/oauth/use-oauth-callback.cjs.map +1 -1
  394. package/dist/offline/use-offline-sync.cjs.map +1 -1
  395. package/dist/push/use-push-subscription.cjs.map +1 -1
  396. package/dist/query/QueryProvider.cjs.map +1 -1
  397. package/dist/query/constants.cjs +1 -1
  398. package/dist/query/constants.cjs.map +1 -1
  399. package/dist/query/constants.js +10 -10
  400. package/dist/query/constants.js.map +1 -1
  401. package/dist/query/create-query-keys.cjs +1 -1
  402. package/dist/query/create-query-keys.cjs.map +1 -1
  403. package/dist/query/create-query-keys.js +1 -1
  404. package/dist/query/create-query-keys.js.map +1 -1
  405. package/dist/query/offline-persistence.cjs.map +1 -1
  406. package/dist/query/use-cursor-query.cjs.map +1 -1
  407. package/dist/query/use-offline-mutation.cjs.map +1 -1
  408. package/dist/query/use-paginated-query.cjs.map +1 -1
  409. package/dist/router/AppRouter.cjs +1 -1
  410. package/dist/router/AppRouter.cjs.map +1 -1
  411. package/dist/router/AppRouter.js +7 -2
  412. package/dist/router/AppRouter.js.map +1 -1
  413. package/dist/sse/use-event-stream.cjs.map +1 -1
  414. package/dist/store/create-store.cjs.map +1 -1
  415. package/dist/tabular/use-tabular-predictor.cjs.map +1 -1
  416. package/dist/telemetry/TelemetryProvider.cjs.map +1 -1
  417. package/dist/tempest-react-sdk.d.ts +25 -26
  418. package/dist/theme/ThemeProvider.cjs.map +1 -1
  419. package/dist/theme/color.cjs +1 -1
  420. package/dist/theme/color.cjs.map +1 -1
  421. package/dist/theme/color.js +1 -1
  422. package/dist/theme/color.js.map +1 -1
  423. package/dist/utils/cn.cjs +1 -1
  424. package/dist/utils/cn.cjs.map +1 -1
  425. package/dist/utils/cn.js +1 -1
  426. package/dist/utils/cn.js.map +1 -1
  427. package/dist/utils/objects.cjs +1 -1
  428. package/dist/utils/objects.cjs.map +1 -1
  429. package/dist/utils/objects.js +1 -1
  430. package/dist/utils/objects.js.map +1 -1
  431. package/dist/utils/xlsx.cjs +1 -1
  432. package/dist/utils/xlsx.cjs.map +1 -1
  433. package/dist/utils/xlsx.js +3 -3
  434. package/dist/utils/xlsx.js.map +1 -1
  435. package/dist/vision/core/exceptions.cjs.map +1 -1
  436. package/dist/vision/core/exceptions.js.map +1 -1
  437. package/dist/vision/core/metadata.cjs +1 -1
  438. package/dist/vision/core/metadata.cjs.map +1 -1
  439. package/dist/vision/core/metadata.js +1 -1
  440. package/dist/vision/core/metadata.js.map +1 -1
  441. package/dist/vision/io/image.cjs +1 -1
  442. package/dist/vision/io/image.cjs.map +1 -1
  443. package/dist/vision/io/image.js +4 -2
  444. package/dist/vision/io/image.js.map +1 -1
  445. package/dist/vision/preprocess/image.cjs +1 -1
  446. package/dist/vision/preprocess/image.cjs.map +1 -1
  447. package/dist/vision/preprocess/image.js +4 -2
  448. package/dist/vision/preprocess/image.js.map +1 -1
  449. package/dist/vision/tasks/base.cjs +1 -1
  450. package/dist/vision/tasks/base.cjs.map +1 -1
  451. package/dist/vision/tasks/base.js +3 -1
  452. package/dist/vision/tasks/base.js.map +1 -1
  453. package/dist/vision/tasks/classifier.cjs +1 -1
  454. package/dist/vision/tasks/classifier.cjs.map +1 -1
  455. package/dist/vision/tasks/classifier.js +2 -2
  456. package/dist/vision/tasks/classifier.js.map +1 -1
  457. package/dist/vision/tasks/detectClassify.cjs +1 -1
  458. package/dist/vision/tasks/detectClassify.cjs.map +1 -1
  459. package/dist/vision/tasks/detectClassify.js +16 -15
  460. package/dist/vision/tasks/detectClassify.js.map +1 -1
  461. package/dist/vision/use-camera-stream.cjs.map +1 -1
  462. package/dist/vision/use-live-luminance.cjs.map +1 -1
  463. package/dist/vite/create-vite-config.cjs +1 -1
  464. package/dist/vite/create-vite-config.cjs.map +1 -1
  465. package/dist/vite/create-vite-config.js +3 -3
  466. package/dist/vite/create-vite-config.js.map +1 -1
  467. package/dist/vite/tempest-icons.cjs.map +1 -1
  468. package/dist/vite/tempest-pwa-dev-sw.cjs.map +1 -1
  469. package/dist/vite/tempest-pwa-icons.cjs.map +1 -1
  470. package/dist/ws/use-web-socket.cjs +1 -1
  471. package/dist/ws/use-web-socket.cjs.map +1 -1
  472. package/dist/ws/use-web-socket.js +5 -3
  473. package/dist/ws/use-web-socket.js.map +1 -1
  474. package/package.json +2 -2
  475. package/template/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"use-passkey.cjs","names":[],"sources":["../../src/auth/use-passkey.ts"],"sourcesContent":["/**\n * @tempest-limits file-lines — the hook wraps both ceremonies with the status\n * machine the UI needs (`prompting` and `verifying` are separate because they need\n * different copy) and the capability probes that decide whether to offer the button\n * at all.\n */\nimport { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\n\nimport { useStableCallback } from \"../hooks/use-stable-callback\";\nimport {\n classifyPasskeyError,\n createPasskeyClient,\n type PasskeyAuthenticationJSON,\n type PasskeyClient,\n type PasskeyCreationOptionsJSON,\n type PasskeyError,\n type PasskeyRegistrationJSON,\n type PasskeyRequestOptionsJSON,\n} from \"./passkey\";\n\n/**\n * Where a passkey ceremony is.\n *\n * `\"prompting\"` and `\"verifying\"` are split because they need different copy:\n * while prompting, the browser sheet is up and the user must touch a sensor;\n * while verifying, your backend is checking the signature and the user should\n * just wait.\n */\nexport type PasskeyStatus = \"idle\" | \"prompting\" | \"verifying\" | \"success\" | \"error\";\n\n/** Capability probes shared by both passkey hooks. */\nexport interface PasskeyCapabilities {\n /** WebAuthn exists in this browser. Known synchronously. */\n supported: boolean;\n /**\n * This device has a built-in authenticator. `null` until the async probe\n * settles — render nothing passkey-related while it is `null`.\n */\n platformAvailable: boolean | null;\n /** Autofill-driven sign-in is available. `null` until the probe settles. */\n conditionalAvailable: boolean | null;\n}\n\nlet sharedClient: PasskeyClient | undefined;\n\n/**\n * Lazily built default client, shared by every hook that was not given one.\n *\n * Lazy rather than a module-level constant so that importing the hooks never runs\n * a call the bundler cannot prove side-effect-free — that alone would pin the\n * module into every consumer's bundle.\n */\nfunction defaultClient(): PasskeyClient {\n sharedClient ??= createPasskeyClient();\n return sharedClient;\n}\n\n/**\n * Probe what this device can do, once per mount.\n *\n * @param client - Client whose probes to call.\n * @returns The three capability answers, two of them async.\n */\nexport function usePasskeyCapabilities(client?: PasskeyClient): PasskeyCapabilities {\n const resolved = useMemo(() => client ?? defaultClient(), [client]);\n const [supported] = useState<boolean>(() => resolved.isSupported());\n const [platformAvailable, setPlatformAvailable] = useState<boolean | null>(null);\n const [conditionalAvailable, setConditionalAvailable] = useState<boolean | null>(null);\n\n useEffect(() => {\n let cancelled = false;\n void resolved.isPlatformAuthenticatorAvailable().then((value) => {\n if (!cancelled) setPlatformAvailable(value);\n });\n void resolved.isConditionalMediationAvailable().then((value) => {\n if (!cancelled) setConditionalAvailable(value);\n });\n return () => {\n cancelled = true;\n };\n }, [resolved]);\n\n return { supported, platformAvailable, conditionalAvailable };\n}\n\n/** Options for {@link usePasskeyRegistration}. */\nexport interface UsePasskeyRegistrationOptions<TResult> {\n /** Fetch server options — your `POST /webauthn/register/begin`. */\n getOptions: () => Promise<PasskeyCreationOptionsJSON>;\n /** Send the credential to be verified — your `POST /webauthn/register/finish`. */\n verify: (credential: PasskeyRegistrationJSON) => Promise<TResult>;\n /** Client to use. Defaults to a shared `createPasskeyClient()`. */\n client?: PasskeyClient;\n /** Called with whatever `verify` resolved to. */\n onSuccess?: (result: TResult) => void;\n /** Called with the classified failure. */\n onError?: (error: PasskeyError) => void;\n}\n\n/** Value returned by {@link usePasskeyRegistration}. */\nexport interface UsePasskeyRegistrationResult<TResult> extends PasskeyCapabilities {\n /** Run the ceremony. Resolves `null` on failure — read `error` for the reason. */\n register: () => Promise<TResult | null>;\n /** Current phase. */\n status: PasskeyStatus;\n /** Last failure, cleared when a new attempt starts. */\n error: PasskeyError | null;\n /** Whatever `verify` resolved to, on success. */\n data: TResult | null;\n /** Close the browser sheet from your own UI. */\n cancel: () => void;\n /** Back to `\"idle\"`, clearing `error` and `data`. */\n reset: () => void;\n}\n\n/**\n * Register a passkey: fetch options, run `navigator.credentials.create`, hand the\n * credential to your backend.\n *\n * `register()` **resolves** rather than rejects on failure, because every failure\n * here is already state the UI has to render (`error.kind`, `status`). Forcing a\n * `try/catch` around a button handler that also has to set state would only\n * duplicate what the hook holds.\n *\n * @param options - Transport callbacks plus optional client and listeners.\n * @returns Ceremony state, the `register` action, and the capability probes.\n *\n * @example\n * const passkey = usePasskeyRegistration({\n * getOptions: () => api.post(\"/webauthn/register/begin\"),\n * verify: (credential) => api.post(\"/webauthn/register/finish\", { body: credential }),\n * });\n *\n * if (passkey.platformAvailable === false) return null;\n * return (\n * <button onClick={() => void passkey.register()} disabled={passkey.status === \"prompting\"}>\n * Criar passkey\n * </button>\n * );\n */\nexport function usePasskeyRegistration<TResult = unknown>(\n options: UsePasskeyRegistrationOptions<TResult>,\n): UsePasskeyRegistrationResult<TResult> {\n const client = useMemo(() => options.client ?? defaultClient(), [options.client]);\n const capabilities = usePasskeyCapabilities(client);\n const [status, setStatus] = useState<PasskeyStatus>(\"idle\");\n const [error, setError] = useState<PasskeyError | null>(null);\n const [data, setData] = useState<TResult | null>(null);\n const controller = useRef<AbortController | null>(null);\n\n const getOptions = useStableCallback(options.getOptions);\n const verify = useStableCallback(options.verify);\n const onSuccess = useStableCallback((result: TResult) => options.onSuccess?.(result));\n const onError = useStableCallback((failure: PasskeyError) => options.onError?.(failure));\n\n const register = useCallback(async (): Promise<TResult | null> => {\n setStatus(\"prompting\");\n setError(null);\n const abort = new AbortController();\n controller.current = abort;\n try {\n const serverOptions = await getOptions();\n const credential = await client.register(serverOptions, { signal: abort.signal });\n setStatus(\"verifying\");\n const result = await verify(credential);\n setData(result);\n setStatus(\"success\");\n onSuccess(result);\n return result;\n } catch (thrown) {\n const failure = classifyPasskeyError(thrown, \"register\");\n setError(failure);\n setStatus(\"error\");\n onError(failure);\n return null;\n } finally {\n controller.current = null;\n }\n }, [client, getOptions, verify, onSuccess, onError]);\n\n const cancel = useCallback((): void => {\n controller.current?.abort();\n }, []);\n\n const reset = useCallback((): void => {\n setStatus(\"idle\");\n setError(null);\n setData(null);\n }, []);\n\n return { ...capabilities, register, status, error, data, cancel, reset };\n}\n\n/** Options for {@link usePasskeySignIn}. */\nexport interface UsePasskeySignInOptions<TResult> {\n /** Fetch server options — your `POST /webauthn/signin/begin`. */\n getOptions: () => Promise<PasskeyRequestOptionsJSON>;\n /** Send the assertion to be verified — your `POST /webauthn/signin/finish`. */\n verify: (assertion: PasskeyAuthenticationJSON) => Promise<TResult>;\n /** Client to use. Defaults to a shared `createPasskeyClient()`. */\n client?: PasskeyClient;\n /**\n * Arm the autofill flow on mount (`mediation: \"conditional\"`).\n *\n * The request stays pending, invisibly, until the user picks a passkey from\n * the browser's autofill list — so the field it should appear on needs\n * `autocomplete=\"username webauthn\"`. Only one conditional request may be live\n * per page, and it is aborted on unmount.\n */\n conditional?: boolean;\n /** Called with whatever `verify` resolved to. */\n onSuccess?: (result: TResult) => void;\n /** Called with the classified failure. */\n onError?: (error: PasskeyError) => void;\n}\n\n/** Value returned by {@link usePasskeySignIn}. */\nexport interface UsePasskeySignInResult<TResult> extends PasskeyCapabilities {\n /** Run the modal ceremony. Resolves `null` on failure. */\n signIn: () => Promise<TResult | null>;\n /** Current phase. */\n status: PasskeyStatus;\n /** Last failure, cleared when a new attempt starts. */\n error: PasskeyError | null;\n /** Whatever `verify` resolved to, on success. */\n data: TResult | null;\n /** True while an armed conditional (autofill) request is waiting. */\n conditionalPending: boolean;\n /** Close the browser sheet from your own UI. */\n cancel: () => void;\n /** Back to `\"idle\"`, clearing `error` and `data`. */\n reset: () => void;\n}\n\n/**\n * Sign in with a passkey, with or without the autofill flow.\n *\n * Two ways in, and a good login page wires both. `signIn()` is the explicit\n * button. `conditional: true` arms the pleasant one: the browser lists the user's\n * passkeys inside the username field itself, so signing in is one tap and no\n * password was ever typed. The field must carry\n * `autocomplete=\"username webauthn\"` or the list never appears — the hook cannot\n * do that part for you.\n *\n * A conditional request that is aborted (unmount, or the user typing a password\n * instead) is **not** surfaced as an error: nothing failed, the user chose\n * another door.\n *\n * @param options - Transport callbacks, `conditional`, optional client and listeners.\n * @returns Ceremony state, the `signIn` action, and the capability probes.\n *\n * @example\n * const passkey = usePasskeySignIn({\n * conditional: true,\n * getOptions: () => api.post(\"/webauthn/signin/begin\"),\n * verify: (assertion) => api.post(\"/webauthn/signin/finish\", { body: assertion }),\n * onSuccess: (session) => auth.setToken(session.access_token),\n * });\n *\n * <input name=\"email\" autoComplete=\"username webauthn\" />\n * <button onClick={() => void passkey.signIn()}>Entrar com passkey</button>\n */\nexport function usePasskeySignIn<TResult = unknown>(\n options: UsePasskeySignInOptions<TResult>,\n): UsePasskeySignInResult<TResult> {\n const client = useMemo(() => options.client ?? defaultClient(), [options.client]);\n const capabilities = usePasskeyCapabilities(client);\n const [status, setStatus] = useState<PasskeyStatus>(\"idle\");\n const [error, setError] = useState<PasskeyError | null>(null);\n const [data, setData] = useState<TResult | null>(null);\n const [conditionalPending, setConditionalPending] = useState(false);\n const controller = useRef<AbortController | null>(null);\n const conditional = options.conditional ?? false;\n\n const getOptions = useStableCallback(options.getOptions);\n const verify = useStableCallback(options.verify);\n const onSuccess = useStableCallback((result: TResult) => options.onSuccess?.(result));\n const onError = useStableCallback((failure: PasskeyError) => options.onError?.(failure));\n\n const run = useCallback(\n async (\n mediation: \"optional\" | \"conditional\",\n abort: AbortController = new AbortController(),\n ): Promise<TResult | null> => {\n controller.current = abort;\n if (mediation === \"optional\") {\n setStatus(\"prompting\");\n setError(null);\n }\n try {\n const serverOptions = await getOptions();\n const assertion = await client.authenticate(serverOptions, {\n mediation,\n signal: abort.signal,\n });\n setStatus(\"verifying\");\n const result = await verify(assertion);\n setData(result);\n setStatus(\"success\");\n onSuccess(result);\n return result;\n } catch (thrown) {\n const failure = classifyPasskeyError(thrown, \"authenticate\");\n if (failure.kind === \"aborted\" && mediation === \"conditional\") return null;\n setError(failure);\n setStatus(\"error\");\n onError(failure);\n return null;\n } finally {\n controller.current = null;\n }\n },\n [client, getOptions, verify, onSuccess, onError],\n );\n\n const signIn = useCallback(() => run(\"optional\"), [run]);\n\n useEffect(() => {\n if (!conditional) return;\n let cancelled = false;\n let abort: AbortController | null = null;\n\n async function arm(): Promise<void> {\n if (!(await client.isConditionalMediationAvailable())) return;\n if (cancelled) return;\n setConditionalPending(true);\n abort = new AbortController();\n await run(\"conditional\", abort);\n if (!cancelled) setConditionalPending(false);\n }\n\n void arm();\n\n return () => {\n cancelled = true;\n abort?.abort();\n };\n }, [conditional, client, run]);\n\n const cancel = useCallback((): void => {\n controller.current?.abort();\n }, []);\n\n const reset = useCallback((): void => {\n setStatus(\"idle\");\n setError(null);\n setData(null);\n }, []);\n\n return {\n ...capabilities,\n signIn,\n status,\n error,\n data,\n conditionalPending,\n cancel,\n reset,\n };\n}\n"],"mappings":"sGA2CA,IAAI,EASJ,SAAS,GAA+B,CAEpC,MADA,KAAiB,EAAA,oBAAoB,EAC9B,CACX,CAQA,SAAgB,EAAuB,EAA6C,CAChF,IAAM,GAAA,EAAA,EAAA,QAAA,KAAyB,GAAU,EAAc,EAAG,CAAC,CAAM,CAAC,EAC5D,CAAC,IAAA,EAAA,EAAA,SAAA,KAAqC,EAAS,YAAY,CAAC,EAC5D,CAAC,EAAmB,IAAA,EAAA,EAAA,SAAA,CAAiD,IAAI,EACzE,CAAC,EAAsB,IAAA,EAAA,EAAA,SAAA,CAAoD,IAAI,EAerF,OAbA,EAAA,EAAA,UAAA,KAAgB,CACZ,IAAI,EAAY,GAOhB,OANA,EAAc,iCAAiC,CAAC,CAAC,KAAM,GAAU,CACxD,GAAW,EAAqB,CAAK,CAC9C,CAAC,EACD,EAAc,gCAAgC,CAAC,CAAC,KAAM,GAAU,CACvD,GAAW,EAAwB,CAAK,CACjD,CAAC,MACY,CACT,EAAY,EAChB,CACJ,EAAG,CAAC,CAAQ,CAAC,EAEN,CAAE,YAAW,oBAAmB,sBAAqB,CAChE,CAyDA,SAAgB,EACZ,EACqC,CACrC,IAAM,GAAA,EAAA,EAAA,QAAA,KAAuB,EAAQ,QAAU,EAAc,EAAG,CAAC,EAAQ,MAAM,CAAC,EAC1E,EAAe,EAAuB,CAAM,EAC5C,CAAC,EAAQ,IAAA,EAAA,EAAA,SAAA,CAAqC,MAAM,EACpD,CAAC,EAAO,IAAA,EAAA,EAAA,SAAA,CAA0C,IAAI,EACtD,CAAC,EAAM,IAAA,EAAA,EAAA,SAAA,CAAoC,IAAI,EAC/C,GAAA,EAAA,EAAA,OAAA,CAA4C,IAAI,EAEhD,EAAa,EAAA,kBAAkB,EAAQ,UAAU,EACjD,EAAS,EAAA,kBAAkB,EAAQ,MAAM,EACzC,EAAY,EAAA,kBAAmB,GAAoB,EAAQ,YAAY,CAAM,CAAC,EAC9E,EAAU,EAAA,kBAAmB,GAA0B,EAAQ,UAAU,CAAO,CAAC,EAEjF,GAAA,EAAA,EAAA,YAAA,CAAuB,SAAqC,CAC9D,EAAU,WAAW,EACrB,EAAS,IAAI,EACb,IAAM,EAAQ,IAAI,gBAClB,EAAW,QAAU,EACrB,GAAI,CACA,IAAM,EAAgB,MAAM,EAAW,EACjC,EAAa,MAAM,EAAO,SAAS,EAAe,CAAE,OAAQ,EAAM,MAAO,CAAC,EAChF,EAAU,WAAW,EACrB,IAAM,EAAS,MAAM,EAAO,CAAU,EAItC,OAHA,EAAQ,CAAM,EACd,EAAU,SAAS,EACnB,EAAU,CAAM,EACT,CACX,OAAS,EAAQ,CACb,IAAM,EAAU,EAAA,qBAAqB,EAAQ,UAAU,EAIvD,OAHA,EAAS,CAAO,EAChB,EAAU,OAAO,EACjB,EAAQ,CAAO,EACR,IACX,QAAU,CACN,EAAW,QAAU,IACzB,CACJ,EAAG,CAAC,EAAQ,EAAY,EAAQ,EAAW,CAAO,CAAC,EAE7C,GAAA,EAAA,EAAA,YAAA,KAAiC,CACnC,EAAW,SAAS,MAAM,CAC9B,EAAG,CAAC,CAAC,EAEC,GAAA,EAAA,EAAA,YAAA,KAAgC,CAClC,EAAU,MAAM,EAChB,EAAS,IAAI,EACb,EAAQ,IAAI,CAChB,EAAG,CAAC,CAAC,EAEL,MAAO,CAAE,GAAG,EAAc,WAAU,SAAQ,QAAO,OAAM,SAAQ,OAAM,CAC3E,CAuEA,SAAgB,EACZ,EAC+B,CAC/B,IAAM,GAAA,EAAA,EAAA,QAAA,KAAuB,EAAQ,QAAU,EAAc,EAAG,CAAC,EAAQ,MAAM,CAAC,EAC1E,EAAe,EAAuB,CAAM,EAC5C,CAAC,EAAQ,IAAA,EAAA,EAAA,SAAA,CAAqC,MAAM,EACpD,CAAC,EAAO,IAAA,EAAA,EAAA,SAAA,CAA0C,IAAI,EACtD,CAAC,EAAM,IAAA,EAAA,EAAA,SAAA,CAAoC,IAAI,EAC/C,CAAC,EAAoB,IAAA,EAAA,EAAA,SAAA,CAAkC,EAAK,EAC5D,GAAA,EAAA,EAAA,OAAA,CAA4C,IAAI,EAChD,EAAc,EAAQ,aAAe,GAErC,EAAa,EAAA,kBAAkB,EAAQ,UAAU,EACjD,EAAS,EAAA,kBAAkB,EAAQ,MAAM,EACzC,EAAY,EAAA,kBAAmB,GAAoB,EAAQ,YAAY,CAAM,CAAC,EAC9E,EAAU,EAAA,kBAAmB,GAA0B,EAAQ,UAAU,CAAO,CAAC,EAEjF,GAAA,EAAA,EAAA,YAAA,CACF,MACI,EACA,EAAyB,IAAI,kBACH,CAC1B,EAAW,QAAU,EACjB,IAAc,aACd,EAAU,WAAW,EACrB,EAAS,IAAI,GAEjB,GAAI,CACA,IAAM,EAAgB,MAAM,EAAW,EACjC,EAAY,MAAM,EAAO,aAAa,EAAe,CACvD,YACA,OAAQ,EAAM,MAClB,CAAC,EACD,EAAU,WAAW,EACrB,IAAM,EAAS,MAAM,EAAO,CAAS,EAIrC,OAHA,EAAQ,CAAM,EACd,EAAU,SAAS,EACnB,EAAU,CAAM,EACT,CACX,OAAS,EAAQ,CACb,IAAM,EAAU,EAAA,qBAAqB,EAAQ,cAAc,EAK3D,OAJI,EAAQ,OAAS,WAAa,IAAc,cAAsB,MACtE,EAAS,CAAO,EAChB,EAAU,OAAO,EACjB,EAAQ,CAAO,EACR,KACX,QAAU,CACN,EAAW,QAAU,IACzB,CACJ,EACA,CAAC,EAAQ,EAAY,EAAQ,EAAW,CAAO,CACnD,EAEM,GAAA,EAAA,EAAA,YAAA,KAA2B,EAAI,UAAU,EAAG,CAAC,CAAG,CAAC,GAEvD,EAAA,EAAA,UAAA,KAAgB,CACZ,GAAI,CAAC,EAAa,OAClB,IAAI,EAAY,GACZ,EAAgC,KAEpC,eAAe,GAAqB,CAC1B,MAAM,EAAO,gCAAgC,IAC/C,IACJ,EAAsB,EAAI,EAC1B,EAAQ,IAAI,gBACZ,MAAM,EAAI,cAAe,CAAK,EACzB,GAAW,EAAsB,EAAK,GAC/C,CAIA,OAFA,EAAS,MAEI,CACT,EAAY,GACZ,GAAO,MAAM,CACjB,CACJ,EAAG,CAAC,EAAa,EAAQ,CAAG,CAAC,EAE7B,IAAM,GAAA,EAAA,EAAA,YAAA,KAAiC,CACnC,EAAW,SAAS,MAAM,CAC9B,EAAG,CAAC,CAAC,EAEC,GAAA,EAAA,EAAA,YAAA,KAAgC,CAClC,EAAU,MAAM,EAChB,EAAS,IAAI,EACb,EAAQ,IAAI,CAChB,EAAG,CAAC,CAAC,EAEL,MAAO,CACH,GAAG,EACH,SACA,SACA,QACA,OACA,qBACA,SACA,OACJ,CACJ"}
1
+ {"version":3,"file":"use-passkey.cjs","names":[],"sources":["../../src/auth/use-passkey.ts"],"sourcesContent":["/**\n * @tempest-limits file-lines — the hook wraps both ceremonies with the status\n * machine the UI needs (`prompting` and `verifying` are separate because they need\n * different copy) and the capability probes that decide whether to offer the button\n * at all.\n */\nimport { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\n\nimport { useStableCallback } from \"../hooks/use-stable-callback\";\nimport {\n classifyPasskeyError,\n createPasskeyClient,\n type PasskeyAuthenticationJSON,\n type PasskeyClient,\n type PasskeyCreationOptionsJSON,\n type PasskeyError,\n type PasskeyRegistrationJSON,\n type PasskeyRequestOptionsJSON,\n} from \"./passkey\";\n\n/**\n * Where a passkey ceremony is.\n *\n * `\"prompting\"` and `\"verifying\"` are split because they need different copy:\n * while prompting, the browser sheet is up and the user must touch a sensor;\n * while verifying, your backend is checking the signature and the user should\n * just wait.\n */\nexport type PasskeyStatus = \"idle\" | \"prompting\" | \"verifying\" | \"success\" | \"error\";\n\n/** Capability probes shared by both passkey hooks. */\nexport interface PasskeyCapabilities {\n /** WebAuthn exists in this browser. Known synchronously. */\n supported: boolean;\n /**\n * This device has a built-in authenticator. `null` until the async probe\n * settles — render nothing passkey-related while it is `null`.\n */\n platformAvailable: boolean | null;\n /** Autofill-driven sign-in is available. `null` until the probe settles. */\n conditionalAvailable: boolean | null;\n}\n\nlet sharedClient: PasskeyClient | undefined;\n\n/**\n * Lazily built default client, shared by every hook that was not given one.\n *\n * Lazy rather than a module-level constant so that importing the hooks never runs\n * a call the bundler cannot prove side-effect-free — that alone would pin the\n * module into every consumer's bundle.\n */\nfunction defaultClient(): PasskeyClient {\n sharedClient ??= createPasskeyClient();\n return sharedClient;\n}\n\n/**\n * Probe what this device can do, once per mount.\n *\n * @param client - Client whose probes to call.\n * @returns The three capability answers, two of them async.\n */\nexport function usePasskeyCapabilities(client?: PasskeyClient): PasskeyCapabilities {\n const resolved = useMemo(() => client ?? defaultClient(), [client]);\n const [supported] = useState<boolean>(() => resolved.isSupported());\n const [platformAvailable, setPlatformAvailable] = useState<boolean | null>(null);\n const [conditionalAvailable, setConditionalAvailable] = useState<boolean | null>(null);\n\n useEffect(() => {\n let cancelled = false;\n void resolved.isPlatformAuthenticatorAvailable().then((value) => {\n if (!cancelled) setPlatformAvailable(value);\n });\n void resolved.isConditionalMediationAvailable().then((value) => {\n if (!cancelled) setConditionalAvailable(value);\n });\n return () => {\n cancelled = true;\n };\n }, [resolved]);\n\n return { supported, platformAvailable, conditionalAvailable };\n}\n\n/** Options for {@link usePasskeyRegistration}. */\nexport interface UsePasskeyRegistrationOptions<TResult> {\n /** Fetch server options — your `POST /webauthn/register/begin`. */\n getOptions: () => Promise<PasskeyCreationOptionsJSON>;\n /** Send the credential to be verified — your `POST /webauthn/register/finish`. */\n verify: (credential: PasskeyRegistrationJSON) => Promise<TResult>;\n /** Client to use. Defaults to a shared `createPasskeyClient()`. */\n client?: PasskeyClient;\n /** Called with whatever `verify` resolved to. */\n onSuccess?: (result: TResult) => void;\n /** Called with the classified failure. */\n onError?: (error: PasskeyError) => void;\n}\n\n/** Value returned by {@link usePasskeyRegistration}. */\nexport interface UsePasskeyRegistrationResult<TResult> extends PasskeyCapabilities {\n /** Run the ceremony. Resolves `null` on failure — read `error` for the reason. */\n register: () => Promise<TResult | null>;\n /** Current phase. */\n status: PasskeyStatus;\n /** Last failure, cleared when a new attempt starts. */\n error: PasskeyError | null;\n /** Whatever `verify` resolved to, on success. */\n data: TResult | null;\n /** Close the browser sheet from your own UI. */\n cancel: () => void;\n /** Back to `\"idle\"`, clearing `error` and `data`. */\n reset: () => void;\n}\n\n/**\n * Register a passkey: fetch options, run `navigator.credentials.create`, hand the\n * credential to your backend.\n *\n * `register()` **resolves** rather than rejects on failure, because every failure\n * here is already state the UI has to render (`error.kind`, `status`). Forcing a\n * `try/catch` around a button handler that also has to set state would only\n * duplicate what the hook holds.\n *\n * @param options - Transport callbacks plus optional client and listeners.\n * @returns Ceremony state, the `register` action, and the capability probes.\n *\n * @example\n * const passkey = usePasskeyRegistration({\n * getOptions: () => api.post(\"/webauthn/register/begin\"),\n * verify: (credential) => api.post(\"/webauthn/register/finish\", { body: credential }),\n * });\n *\n * if (passkey.platformAvailable === false) return null;\n * return (\n * <button onClick={() => void passkey.register()} disabled={passkey.status === \"prompting\"}>\n * Criar passkey\n * </button>\n * );\n */\nexport function usePasskeyRegistration<TResult = unknown>(\n options: UsePasskeyRegistrationOptions<TResult>,\n): UsePasskeyRegistrationResult<TResult> {\n const client = useMemo(() => options.client ?? defaultClient(), [options.client]);\n const capabilities = usePasskeyCapabilities(client);\n const [status, setStatus] = useState<PasskeyStatus>(\"idle\");\n const [error, setError] = useState<PasskeyError | null>(null);\n const [data, setData] = useState<TResult | null>(null);\n const controller = useRef<AbortController | null>(null);\n\n const getOptions = useStableCallback(options.getOptions);\n const verify = useStableCallback(options.verify);\n const onSuccess = useStableCallback((result: TResult) => options.onSuccess?.(result));\n const onError = useStableCallback((failure: PasskeyError) => options.onError?.(failure));\n\n const register = useCallback(async (): Promise<TResult | null> => {\n setStatus(\"prompting\");\n setError(null);\n const abort = new AbortController();\n controller.current = abort;\n try {\n const serverOptions = await getOptions();\n const credential = await client.register(serverOptions, { signal: abort.signal });\n setStatus(\"verifying\");\n const result = await verify(credential);\n setData(result);\n setStatus(\"success\");\n onSuccess(result);\n return result;\n } catch (thrown) {\n const failure = classifyPasskeyError(thrown, \"register\");\n setError(failure);\n setStatus(\"error\");\n onError(failure);\n return null;\n } finally {\n controller.current = null;\n }\n }, [client, getOptions, verify, onSuccess, onError]);\n\n const cancel = useCallback((): void => {\n controller.current?.abort();\n }, []);\n\n const reset = useCallback((): void => {\n setStatus(\"idle\");\n setError(null);\n setData(null);\n }, []);\n\n return { ...capabilities, register, status, error, data, cancel, reset };\n}\n\n/** Options for {@link usePasskeySignIn}. */\nexport interface UsePasskeySignInOptions<TResult> {\n /** Fetch server options — your `POST /webauthn/signin/begin`. */\n getOptions: () => Promise<PasskeyRequestOptionsJSON>;\n /** Send the assertion to be verified — your `POST /webauthn/signin/finish`. */\n verify: (assertion: PasskeyAuthenticationJSON) => Promise<TResult>;\n /** Client to use. Defaults to a shared `createPasskeyClient()`. */\n client?: PasskeyClient;\n /**\n * Arm the autofill flow on mount (`mediation: \"conditional\"`).\n *\n * The request stays pending, invisibly, until the user picks a passkey from\n * the browser's autofill list — so the field it should appear on needs\n * `autocomplete=\"username webauthn\"`. Only one conditional request may be live\n * per page, and it is aborted on unmount.\n */\n conditional?: boolean;\n /** Called with whatever `verify` resolved to. */\n onSuccess?: (result: TResult) => void;\n /** Called with the classified failure. */\n onError?: (error: PasskeyError) => void;\n}\n\n/** Value returned by {@link usePasskeySignIn}. */\nexport interface UsePasskeySignInResult<TResult> extends PasskeyCapabilities {\n /** Run the modal ceremony. Resolves `null` on failure. */\n signIn: () => Promise<TResult | null>;\n /** Current phase. */\n status: PasskeyStatus;\n /** Last failure, cleared when a new attempt starts. */\n error: PasskeyError | null;\n /** Whatever `verify` resolved to, on success. */\n data: TResult | null;\n /** True while an armed conditional (autofill) request is waiting. */\n conditionalPending: boolean;\n /** Close the browser sheet from your own UI. */\n cancel: () => void;\n /** Back to `\"idle\"`, clearing `error` and `data`. */\n reset: () => void;\n}\n\n/**\n * Sign in with a passkey, with or without the autofill flow.\n *\n * Two ways in, and a good login page wires both. `signIn()` is the explicit\n * button. `conditional: true` arms the pleasant one: the browser lists the user's\n * passkeys inside the username field itself, so signing in is one tap and no\n * password was ever typed. The field must carry\n * `autocomplete=\"username webauthn\"` or the list never appears — the hook cannot\n * do that part for you.\n *\n * A conditional request that is aborted (unmount, or the user typing a password\n * instead) is **not** surfaced as an error: nothing failed, the user chose\n * another door.\n *\n * @param options - Transport callbacks, `conditional`, optional client and listeners.\n * @returns Ceremony state, the `signIn` action, and the capability probes.\n *\n * @example\n * const passkey = usePasskeySignIn({\n * conditional: true,\n * getOptions: () => api.post(\"/webauthn/signin/begin\"),\n * verify: (assertion) => api.post(\"/webauthn/signin/finish\", { body: assertion }),\n * onSuccess: (session) => auth.setToken(session.access_token),\n * });\n *\n * <input name=\"email\" autoComplete=\"username webauthn\" />\n * <button onClick={() => void passkey.signIn()}>Entrar com passkey</button>\n */\nexport function usePasskeySignIn<TResult = unknown>(\n options: UsePasskeySignInOptions<TResult>,\n): UsePasskeySignInResult<TResult> {\n const client = useMemo(() => options.client ?? defaultClient(), [options.client]);\n const capabilities = usePasskeyCapabilities(client);\n const [status, setStatus] = useState<PasskeyStatus>(\"idle\");\n const [error, setError] = useState<PasskeyError | null>(null);\n const [data, setData] = useState<TResult | null>(null);\n const [conditionalPending, setConditionalPending] = useState(false);\n const controller = useRef<AbortController | null>(null);\n const conditional = options.conditional ?? false;\n\n const getOptions = useStableCallback(options.getOptions);\n const verify = useStableCallback(options.verify);\n const onSuccess = useStableCallback((result: TResult) => options.onSuccess?.(result));\n const onError = useStableCallback((failure: PasskeyError) => options.onError?.(failure));\n\n const run = useCallback(\n async (\n mediation: \"optional\" | \"conditional\",\n abort: AbortController = new AbortController(),\n ): Promise<TResult | null> => {\n controller.current = abort;\n if (mediation === \"optional\") {\n setStatus(\"prompting\");\n setError(null);\n }\n try {\n const serverOptions = await getOptions();\n const assertion = await client.authenticate(serverOptions, {\n mediation,\n signal: abort.signal,\n });\n setStatus(\"verifying\");\n const result = await verify(assertion);\n setData(result);\n setStatus(\"success\");\n onSuccess(result);\n return result;\n } catch (thrown) {\n const failure = classifyPasskeyError(thrown, \"authenticate\");\n if (failure.kind === \"aborted\" && mediation === \"conditional\") return null;\n setError(failure);\n setStatus(\"error\");\n onError(failure);\n return null;\n } finally {\n controller.current = null;\n }\n },\n [client, getOptions, verify, onSuccess, onError],\n );\n\n const signIn = useCallback(() => run(\"optional\"), [run]);\n\n useEffect(() => {\n if (!conditional) return;\n let cancelled = false;\n let abort: AbortController | null = null;\n\n async function arm(): Promise<void> {\n if (!(await client.isConditionalMediationAvailable())) return;\n if (cancelled) return;\n setConditionalPending(true);\n abort = new AbortController();\n await run(\"conditional\", abort);\n if (!cancelled) setConditionalPending(false);\n }\n\n void arm();\n\n return () => {\n cancelled = true;\n abort?.abort();\n };\n }, [conditional, client, run]);\n\n const cancel = useCallback((): void => {\n controller.current?.abort();\n }, []);\n\n const reset = useCallback((): void => {\n setStatus(\"idle\");\n setError(null);\n setData(null);\n }, []);\n\n return {\n ...capabilities,\n signIn,\n status,\n error,\n data,\n conditionalPending,\n cancel,\n reset,\n };\n}\n"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- const e=require("../utils/cn.cjs"),t=require("../geo/projection.cjs"),n=require("./locations.cjs"),r=require("./BrazilMap.module.cjs"),i=require("./MapMarkers.cjs"),a=require("./MapTooltip.cjs"),o=require("./regions.cjs"),s=require("./use-map-zoom.cjs"),c=require("./svg-utils.cjs"),l=require("./use-map-hover.cjs");let u=require("react"),d=require("react/jsx-runtime");function f({selected:n,onSelect:f,values:m,minColor:h=`#dbeafe`,maxColor:g=`#2563eb`,colorScale:_,colorByRegion:v=!1,height:y=440,padding:b=12,showLabels:x=!0,label:S=`Mapa do Brasil por estado`,loadingContent:C,showTooltip:w=!0,renderTooltip:T,markers:E,onMarkerClick:D,zoomable:O=!1,className:k,style:A,...j}){let M=(0,u.useRef)(null),[N,P]=(0,u.useState)(600),[F,I]=(0,u.useState)(null),{hover:L,onMove:R,onLeave:z}=l.useMapHover(M),B=s.useMapZoom(O);(0,u.useEffect)(()=>{let e=!0;return Promise.resolve().then(()=>require("./br-geo.cjs")).then(t=>{e&&I(t.BR_UF_GEOJSON)}),()=>{e=!1}},[]),(0,u.useEffect)(()=>{let e=M.current;if(!e||typeof ResizeObserver>`u`)return;let t=new ResizeObserver(e=>{let t=e[0]?.contentRect.width;t&&t>0&&P(t)});return t.observe(e),()=>t.disconnect()},[]);let V=(0,u.useMemo)(()=>n?new Set(Array.isArray(n)?n:[n]):new Set,[n]),H=(0,u.useMemo)(()=>{if(!m)return null;let e=Object.values(m).filter(e=>typeof e==`number`);return e.length===0?null:[Math.min(...e),Math.max(...e)]},[m]),U=(0,u.useMemo)(()=>{if(!F)return null;let e=t.fitProjection(c.geometriesBounds(F.features.map(e=>e.geometry)),N,y,{padding:b});return{projection:e,items:F.features.map(t=>({uf:t.properties.uf,name:t.properties.name,region:t.properties.region,d:c.geometryPath(t.geometry,e),centroid:c.geometryCentroid(t.geometry,e)}))}},[F,N,y,b]);function W(e){let t=m?.[e];if(typeof t!=`number`)return;if(_)return _(t);if(!H)return;let[n,r]=H;return c.lerpColor(h,g,r===n?1:(t-n)/(r-n))}let G=(0,u.useMemo)(()=>U?U.items.map(t=>{let n=V.has(t.uf),i=n?void 0:v?o.REGION_COLORS[t.region]:W(t.uf);return(0,d.jsx)(`path`,{className:e.cn(r.default.state,n&&r.default.selected),d:t.d,fillRule:`evenodd`,style:i?{fill:i}:void 0,"data-uf":t.uf,tabIndex:f?0:void 0,role:f?`button`:void 0,"aria-label":t.name,"aria-pressed":f?n:void 0,onClick:f?()=>f(t.uf):void 0,onMouseMove:w?e=>R({uf:t.uf,name:t.name},e):void 0,onMouseLeave:w?z:void 0,onKeyDown:f?e=>{(e.key===`Enter`||e.key===` `)&&(e.preventDefault(),f(t.uf))}:void 0},t.uf)}):null,[U,V,m,_,v,h,g,f,w,R,z]);return(0,d.jsxs)(`div`,{ref:M,className:e.cn(r.default.map,k),style:{height:y,...A},role:`group`,"aria-label":S,...j,children:[U?(0,d.jsx)(`svg`,{className:r.default.svg,width:`100%`,height:y,viewBox:`0 0 ${N} ${y}`,preserveAspectRatio:`xMidYMid meet`,...B.handlers,children:(0,d.jsxs)(`g`,{transform:B.transform||void 0,children:[G,x&&U.items.map(e=>(0,d.jsx)(`text`,{className:r.default.label,x:e.centroid.x,y:e.centroid.y,textAnchor:`middle`,dominantBaseline:`central`,children:e.uf},`label-${e.uf}`)),E&&E.length>0&&(0,d.jsx)(i.MapMarkers,{projection:U.projection,markers:E,onMarkerClick:D})]})}):(0,d.jsx)(`div`,{className:r.default.loading,children:C??`Carregando mapa…`}),B.isTransformed&&(0,d.jsx)(`button`,{type:`button`,className:r.default.zoomReset,onClick:B.reset,children:`Reset`}),w&&L&&(0,d.jsx)(a.MapTooltip,{x:L.x,y:L.y,children:T?T({uf:L.item.uf,name:L.item.name,value:m?.[L.item.uf]}):(0,d.jsx)(p,{uf:L.item.uf,name:L.item.name,value:m?.[L.item.uf]})})]})}function p({uf:e,name:t,value:i}){let a=n.getState(e);return(0,d.jsxs)(d.Fragment,{children:[(0,d.jsxs)(`div`,{className:r.default.tooltipTitle,children:[t,` (`,e,`)`]}),(0,d.jsxs)(`div`,{className:r.default.tooltipMeta,children:[a?`${a.region} · ${a.cities.length} cidades`:e,typeof i==`number`?` · ${i}`:``]})]})}exports.BrazilMap=f;
1
+ const e=require("../utils/cn.cjs"),t=require("../geo/projection.cjs"),n=require("./locations.cjs"),r=require("./BrazilMap.module.cjs"),i=require("./MapMarkers.cjs"),a=require("./MapTooltip.cjs"),o=require("./regions.cjs"),s=require("./use-map-zoom.cjs"),c=require("./svg-utils.cjs"),l=require("./use-map-hover.cjs");let u=require("react"),d=require("react/jsx-runtime");function f({selected:n,onSelect:f,values:m,minColor:h=`#dbeafe`,maxColor:g=`#2563eb`,colorScale:_,colorByRegion:v=!1,height:y=440,padding:b=12,showLabels:x=!0,label:S=`Mapa do Brasil por estado`,loadingContent:C,showTooltip:w=!0,renderTooltip:T,markers:E,onMarkerClick:D,zoomable:O=!1,className:k,style:A,...j}){let M=(0,u.useRef)(null),[N,P]=(0,u.useState)(600),[F,I]=(0,u.useState)(null),{hover:L,onMove:R,onLeave:z}=l.useMapHover(M),B=s.useMapZoom(O);(0,u.useEffect)(()=>{let e=!0;return Promise.resolve().then(()=>require("./br-geo.cjs")).then(t=>{e&&I(t.BR_UF_GEOJSON)}),()=>{e=!1}},[]),(0,u.useEffect)(()=>{let e=M.current;if(!e||typeof ResizeObserver>`u`)return;let t=new ResizeObserver(e=>{let t=e[0]?.contentRect.width;t&&t>0&&P(t)});return t.observe(e),()=>t.disconnect()},[]);let V=(0,u.useMemo)(()=>n?new Set(Array.isArray(n)?n:[n]):new Set,[n]),H=(0,u.useMemo)(()=>{if(!m)return null;let e=Object.values(m).filter(e=>typeof e==`number`);return e.length===0?null:[Math.min(...e),Math.max(...e)]},[m]),U=(0,u.useMemo)(()=>{if(!F)return null;let e=t.fitProjection(c.geometriesBounds(F.features.map(e=>e.geometry)),N,y,{padding:b});return{projection:e,items:F.features.map(t=>({uf:t.properties.uf,name:t.properties.name,region:t.properties.region,d:c.geometryPath(t.geometry,e),centroid:c.geometryCentroid(t.geometry,e)}))}},[F,N,y,b]);function W(e){let t=m?.[e];if(typeof t!=`number`)return;if(_)return _(t);if(!H)return;let[n,r]=H,i=r===n?1:(t-n)/(r-n);return c.lerpColor(h,g,i)}let G=(0,u.useMemo)(()=>U?U.items.map(t=>{let n=V.has(t.uf),i=n?void 0:v?o.REGION_COLORS[t.region]:W(t.uf);return(0,d.jsx)(`path`,{className:e.cn(r.default.state,n&&r.default.selected),d:t.d,fillRule:`evenodd`,style:i?{fill:i}:void 0,"data-uf":t.uf,tabIndex:f?0:void 0,role:f?`button`:void 0,"aria-label":t.name,"aria-pressed":f?n:void 0,onClick:f?()=>f(t.uf):void 0,onMouseMove:w?e=>R({uf:t.uf,name:t.name},e):void 0,onMouseLeave:w?z:void 0,onKeyDown:f?e=>{(e.key===`Enter`||e.key===` `)&&(e.preventDefault(),f(t.uf))}:void 0},t.uf)}):null,[U,V,m,_,v,h,g,f,w,R,z]);return(0,d.jsxs)(`div`,{ref:M,className:e.cn(r.default.map,k),style:{height:y,...A},role:`group`,"aria-label":S,...j,children:[U?(0,d.jsx)(`svg`,{className:r.default.svg,width:`100%`,height:y,viewBox:`0 0 ${N} ${y}`,preserveAspectRatio:`xMidYMid meet`,...B.handlers,children:(0,d.jsxs)(`g`,{transform:B.transform||void 0,children:[G,x&&U.items.map(e=>(0,d.jsx)(`text`,{className:r.default.label,x:e.centroid.x,y:e.centroid.y,textAnchor:`middle`,dominantBaseline:`central`,children:e.uf},`label-${e.uf}`)),E&&E.length>0&&(0,d.jsx)(i.MapMarkers,{projection:U.projection,markers:E,onMarkerClick:D})]})}):(0,d.jsx)(`div`,{className:r.default.loading,children:C??`Carregando mapa…`}),B.isTransformed&&(0,d.jsx)(`button`,{type:`button`,className:r.default.zoomReset,onClick:B.reset,children:`Reset`}),w&&L&&(0,d.jsx)(a.MapTooltip,{x:L.x,y:L.y,children:T?T({uf:L.item.uf,name:L.item.name,value:m?.[L.item.uf]}):(0,d.jsx)(p,{uf:L.item.uf,name:L.item.name,value:m?.[L.item.uf]})})]})}function p({uf:e,name:t,value:i}){let a=n.getState(e);return(0,d.jsxs)(d.Fragment,{children:[(0,d.jsxs)(`div`,{className:r.default.tooltipTitle,children:[t,` (`,e,`)`]}),(0,d.jsxs)(`div`,{className:r.default.tooltipMeta,children:[a?`${a.region} · ${a.cities.length} cidades`:e,typeof i==`number`?` · ${i}`:``]})]})}exports.BrazilMap=f;
2
2
  //# sourceMappingURL=BrazilMap.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"BrazilMap.cjs","names":[],"sources":["../../src/br/BrazilMap.tsx"],"sourcesContent":["/**\n * @tempest-limits file-lines, props-count, function-lines — the 17 props are five\n * independent axes of one map — choropleth (values, minColor, maxColor, colorScale,\n * colorByRegion), selection (selected, onSelect), overlay (markers, onMarkerClick,\n * zoomable), framing (height, padding, showLabels, label, loadingContent) and\n * tooltip (showTooltip, renderTooltip). None of them is mutually exclusive with\n * another, so there is no second component hiding here; the body is long for the\n * same reason it cannot be split, since the projected paths, the hit-testing and the\n * zoom transform all read the same viewBox state.\n */\nimport { useEffect, useMemo, useRef, useState, type HTMLAttributes, type ReactNode } from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport { fitProjection } from \"@/geo/projection\";\nimport type { GeoMarker } from \"@/geo/types\";\nimport type { BrUfFeatureCollection } from \"./br-geo\";\nimport { getState, type UF } from \"./locations\";\nimport { MapMarkers } from \"./MapMarkers\";\nimport { MapTooltip } from \"./MapTooltip\";\nimport { REGION_COLORS } from \"./regions\";\nimport type { ColorScale } from \"./scales\";\nimport { useMapZoom } from \"./use-map-zoom\";\nimport { geometriesBounds, geometryCentroid, geometryPath, lerpColor } from \"./svg-utils\";\nimport { useMapHover } from \"./use-map-hover\";\nimport styles from \"./BrazilMap.module.css\";\n\n/** Data passed to a {@link BrazilMapProps.renderTooltip} callback. */\nexport interface BrazilMapTooltipData {\n uf: UF;\n name: string;\n /** Choropleth value for this UF, if `values` was provided. */\n value?: number;\n}\n\nexport interface BrazilMapProps extends Omit<HTMLAttributes<HTMLDivElement>, \"onSelect\"> {\n /** Currently selected UF(s) — highlighted. Accepts one or many. */\n selected?: UF | readonly UF[] | null;\n /** Fired when a state is clicked. */\n onSelect?: (uf: UF) => void;\n /**\n * Optional choropleth values per UF. When set, each state is tinted between\n * `minColor` and `maxColor` by its value (linear). States without a value\n * use the base surface color.\n */\n values?: Partial<Record<UF, number>>;\n /** Choropleth low-end color (2-color linear ramp). Default: a light primary tint. */\n minColor?: string;\n /** Choropleth high-end color (2-color linear ramp). Default: the primary token. */\n maxColor?: string;\n /**\n * Custom value→color scale (e.g. from `sequentialScale`/`quantizeScale`).\n * Takes precedence over `minColor`/`maxColor`. Pair with a `<MapLegend>`.\n */\n colorScale?: ColorScale;\n /**\n * Tint each state by its macro-region (categorical). Overrides\n * `values`/`colorScale`. Pair with `<MapLegend items={regionLegendItems()} />`.\n */\n colorByRegion?: boolean;\n /** Viewport height in pixels. Default: `440`. */\n height?: number;\n /** Inner padding in pixels. Default: `12`. */\n padding?: number;\n /** Render the UF acronym at each state centroid. Default: `true`. */\n showLabels?: boolean;\n /** Accessible label for the map region. Default: `\"Mapa do Brasil por estado\"`. */\n label?: string;\n /** Custom content when the geometry is still loading. */\n loadingContent?: ReactNode;\n /** Show a floating tooltip (name + region + city count + value) on hover. Default: `true`. */\n showTooltip?: boolean;\n /** Override the default tooltip content. */\n renderTooltip?: (data: BrazilMapTooltipData) => ReactNode;\n /** Point markers to overlay on the map (e.g. capitals, stores). */\n markers?: readonly GeoMarker[];\n /** Fired when a marker is clicked. */\n onMarkerClick?: (marker: GeoMarker, index: number) => void;\n /** Enable wheel-zoom + drag-pan (double-click resets). Default: `false`. */\n zoomable?: boolean;\n}\n\n/**\n * Clickable choropleth map of Brazil's 27 federative units. Renders the bundled\n * simplified UF GeoJSON as SVG paths — **no external tiles or paid API**. Click\n * a state to fire `onSelect(uf)`; pass `selected` to highlight and `values` to\n * tint states by a metric.\n *\n * The GeoJSON (~36 KB gzip) is loaded lazily, so importing this component does\n * not pull the geometry until it actually mounts.\n *\n * @example\n * const [uf, setUf] = useState<UF | null>(null);\n * <BrazilMap selected={uf} onSelect={setUf} />\n *\n * @example\n * // Choropleth by a metric per state\n * <BrazilMap values={{ SP: 120, RJ: 90, MG: 60 }} />\n */\nexport function BrazilMap({\n selected,\n onSelect,\n values,\n minColor = \"#dbeafe\",\n maxColor = \"#2563eb\",\n colorScale,\n colorByRegion = false,\n height = 440,\n padding = 12,\n showLabels = true,\n label = \"Mapa do Brasil por estado\",\n loadingContent,\n showTooltip = true,\n renderTooltip,\n markers,\n onMarkerClick,\n zoomable = false,\n className,\n style,\n ...rest\n}: BrazilMapProps) {\n const containerRef = useRef<HTMLDivElement>(null);\n const [width, setWidth] = useState<number>(600);\n const [collection, setCollection] = useState<BrUfFeatureCollection | null>(null);\n const { hover, onMove, onLeave } = useMapHover<{ uf: UF; name: string }>(containerRef);\n const mapZoom = useMapZoom(zoomable);\n\n useEffect(() => {\n let active = true;\n void import(\"./br-geo\").then((m) => {\n if (active) setCollection(m.BR_UF_GEOJSON);\n });\n return () => {\n active = false;\n };\n }, []);\n\n useEffect(() => {\n const node = containerRef.current;\n if (!node || typeof ResizeObserver === \"undefined\") return;\n const observer = new ResizeObserver((entries) => {\n const measured = entries[0]?.contentRect.width;\n if (measured && measured > 0) setWidth(measured);\n });\n observer.observe(node);\n return () => observer.disconnect();\n }, []);\n\n const selectedSet = useMemo<Set<UF>>(() => {\n if (!selected) return new Set();\n return new Set(Array.isArray(selected) ? selected : [selected as UF]);\n }, [selected]);\n\n const valueRange = useMemo<[number, number] | null>(() => {\n if (!values) return null;\n const nums = Object.values(values).filter((v): v is number => typeof v === \"number\");\n if (nums.length === 0) return null;\n return [Math.min(...nums), Math.max(...nums)];\n }, [values]);\n\n const shapes = useMemo(() => {\n if (!collection) return null;\n const projection = fitProjection(\n geometriesBounds(collection.features.map((f) => f.geometry)),\n width,\n height,\n { padding },\n );\n const items = collection.features.map((feature) => ({\n uf: feature.properties.uf,\n name: feature.properties.name,\n region: feature.properties.region,\n d: geometryPath(feature.geometry, projection),\n centroid: geometryCentroid(feature.geometry, projection),\n }));\n return { projection, items };\n }, [collection, width, height, padding]);\n\n function fillFor(uf: UF): string | undefined {\n const v = values?.[uf];\n if (typeof v !== \"number\") return undefined;\n if (colorScale) return colorScale(v);\n if (!valueRange) return undefined;\n const [min, max] = valueRange;\n const t = max === min ? 1 : (v - min) / (max - min);\n return lerpColor(minColor, maxColor, t);\n }\n\n // Memoized so frequent re-renders (hover tooltip) don't rebuild all 27 paths.\n const stateEls = useMemo(() => {\n if (!shapes) return null;\n return shapes.items.map((shape) => {\n const isSelected = selectedSet.has(shape.uf);\n const fillColor = isSelected\n ? undefined\n : colorByRegion\n ? REGION_COLORS[shape.region]\n : fillFor(shape.uf);\n return (\n <path\n key={shape.uf}\n className={cn(styles.state, isSelected && styles.selected)}\n d={shape.d}\n fillRule=\"evenodd\"\n style={fillColor ? { fill: fillColor } : undefined}\n data-uf={shape.uf}\n tabIndex={onSelect ? 0 : undefined}\n role={onSelect ? \"button\" : undefined}\n aria-label={shape.name}\n aria-pressed={onSelect ? isSelected : undefined}\n onClick={onSelect ? () => onSelect(shape.uf) : undefined}\n onMouseMove={\n showTooltip\n ? (e) => onMove({ uf: shape.uf, name: shape.name }, e)\n : undefined\n }\n onMouseLeave={showTooltip ? onLeave : undefined}\n onKeyDown={\n onSelect\n ? (e) => {\n if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n onSelect(shape.uf);\n }\n }\n : undefined\n }\n />\n );\n });\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [\n shapes,\n selectedSet,\n values,\n colorScale,\n colorByRegion,\n minColor,\n maxColor,\n onSelect,\n showTooltip,\n onMove,\n onLeave,\n ]);\n\n return (\n <div\n ref={containerRef}\n className={cn(styles.map, className)}\n style={{ height, ...style }}\n role=\"group\"\n aria-label={label}\n {...rest}\n >\n {!shapes ? (\n <div className={styles.loading}>{loadingContent ?? \"Carregando mapa…\"}</div>\n ) : (\n <svg\n className={styles.svg}\n width=\"100%\"\n height={height}\n viewBox={`0 0 ${width} ${height}`}\n preserveAspectRatio=\"xMidYMid meet\"\n {...mapZoom.handlers}\n >\n <g transform={mapZoom.transform || undefined}>\n {stateEls}\n\n {showLabels &&\n shapes.items.map((shape) => (\n <text\n key={`label-${shape.uf}`}\n className={styles.label}\n x={shape.centroid.x}\n y={shape.centroid.y}\n textAnchor=\"middle\"\n dominantBaseline=\"central\"\n >\n {shape.uf}\n </text>\n ))}\n\n {markers && markers.length > 0 && (\n <MapMarkers\n projection={shapes.projection}\n markers={markers}\n onMarkerClick={onMarkerClick}\n />\n )}\n </g>\n </svg>\n )}\n\n {mapZoom.isTransformed && (\n <button type=\"button\" className={styles.zoomReset} onClick={mapZoom.reset}>\n Reset\n </button>\n )}\n\n {showTooltip && hover && (\n <MapTooltip x={hover.x} y={hover.y}>\n {renderTooltip ? (\n renderTooltip({\n uf: hover.item.uf,\n name: hover.item.name,\n value: values?.[hover.item.uf],\n })\n ) : (\n <DefaultUfTooltip\n uf={hover.item.uf}\n name={hover.item.name}\n value={values?.[hover.item.uf]}\n />\n )}\n </MapTooltip>\n )}\n </div>\n );\n}\n\n/** Default UF tooltip: name (UF) + region + city count + optional value. */\nfunction DefaultUfTooltip({ uf, name, value }: { uf: UF; name: string; value?: number }) {\n const state = getState(uf);\n return (\n <>\n <div className={styles.tooltipTitle}>\n {name} ({uf})\n </div>\n <div className={styles.tooltipMeta}>\n {state ? `${state.region} · ${state.cities.length} cidades` : uf}\n {typeof value === \"number\" ? ` · ${value}` : \"\"}\n </div>\n </>\n );\n}\n"],"mappings":"kXAiGA,SAAgB,EAAU,CACtB,WACA,WACA,SACA,WAAW,UACX,WAAW,UACX,aACA,gBAAgB,GAChB,SAAS,IACT,UAAU,GACV,aAAa,GACb,QAAQ,4BACR,iBACA,cAAc,GACd,gBACA,UACA,gBACA,WAAW,GACX,YACA,QACA,GAAG,GACY,CACf,IAAM,GAAA,EAAA,EAAA,OAAA,CAAsC,IAAI,EAC1C,CAAC,EAAO,IAAA,EAAA,EAAA,SAAA,CAA6B,GAAG,EACxC,CAAC,EAAY,IAAA,EAAA,EAAA,SAAA,CAAwD,IAAI,EACzE,CAAE,QAAO,SAAQ,WAAY,EAAA,YAAsC,CAAY,EAC/E,EAAU,EAAA,WAAW,CAAQ,GAEnC,EAAA,EAAA,UAAA,KAAgB,CACZ,IAAI,EAAS,GAIb,OAHA,QAAA,QAAA,CAAA,CAAA,SAAA,QAAK,cAAA,CAAA,CAAA,CAAmB,KAAM,GAAM,CAC5B,GAAQ,EAAc,EAAE,aAAa,CAC7C,CAAC,MACY,CACT,EAAS,EACb,CACJ,EAAG,CAAC,CAAC,GAEL,EAAA,EAAA,UAAA,KAAgB,CACZ,IAAM,EAAO,EAAa,QAC1B,GAAI,CAAC,GAAQ,OAAO,eAAmB,IAAa,OACpD,IAAM,EAAW,IAAI,eAAgB,GAAY,CAC7C,IAAM,EAAW,EAAQ,EAAE,EAAE,YAAY,MACrC,GAAY,EAAW,GAAG,EAAS,CAAQ,CACnD,CAAC,EAED,OADA,EAAS,QAAQ,CAAI,MACR,EAAS,WAAW,CACrC,EAAG,CAAC,CAAC,EAEL,IAAM,GAAA,EAAA,EAAA,QAAA,KACG,EACE,IAAI,IAAI,MAAM,QAAQ,CAAQ,EAAI,EAAW,CAAC,CAAc,CAAC,EAD9C,IAAI,IAE3B,CAAC,CAAQ,CAAC,EAEP,GAAA,EAAA,EAAA,QAAA,KAAoD,CACtD,GAAI,CAAC,EAAQ,OAAO,KACpB,IAAM,EAAO,OAAO,OAAO,CAAM,CAAC,CAAC,OAAQ,GAAmB,OAAO,GAAM,QAAQ,EAEnF,OADI,EAAK,SAAW,EAAU,KACvB,CAAC,KAAK,IAAI,GAAG,CAAI,EAAG,KAAK,IAAI,GAAG,CAAI,CAAC,CAChD,EAAG,CAAC,CAAM,CAAC,EAEL,GAAA,EAAA,EAAA,QAAA,KAAuB,CACzB,GAAI,CAAC,EAAY,OAAO,KACxB,IAAM,EAAa,EAAA,cACf,EAAA,iBAAiB,EAAW,SAAS,IAAK,GAAM,EAAE,QAAQ,CAAC,EAC3D,EACA,EACA,CAAE,SAAQ,CACd,EAQA,MAAO,CAAE,aAAY,MAPP,EAAW,SAAS,IAAK,IAAa,CAChD,GAAI,EAAQ,WAAW,GACvB,KAAM,EAAQ,WAAW,KACzB,OAAQ,EAAQ,WAAW,OAC3B,EAAG,EAAA,aAAa,EAAQ,SAAU,CAAU,EAC5C,SAAU,EAAA,iBAAiB,EAAQ,SAAU,CAAU,CAC3D,EACqB,CAAM,CAC/B,EAAG,CAAC,EAAY,EAAO,EAAQ,CAAO,CAAC,EAEvC,SAAS,EAAQ,EAA4B,CACzC,IAAM,EAAI,IAAS,GACnB,GAAI,OAAO,GAAM,SAAU,OAC3B,GAAI,EAAY,OAAO,EAAW,CAAC,EACnC,GAAI,CAAC,EAAY,OACjB,GAAM,CAAC,EAAK,GAAO,EAEnB,OAAO,EAAA,UAAU,EAAU,EADjB,IAAQ,EAAM,GAAK,EAAI,IAAQ,EAAM,EACT,CAC1C,CAGA,IAAM,GAAA,EAAA,EAAA,QAAA,KACG,EACE,EAAO,MAAM,IAAK,GAAU,CAC/B,IAAM,EAAa,EAAY,IAAI,EAAM,EAAE,EACrC,EAAY,EACZ,IAAA,GACA,EACE,EAAA,cAAc,EAAM,QACpB,EAAQ,EAAM,EAAE,EACxB,OACI,EAAA,EAAA,IAAA,CAAC,OAAD,CAEI,UAAW,EAAA,GAAG,EAAA,QAAO,MAAO,GAAc,EAAA,QAAO,QAAQ,EACzD,EAAG,EAAM,EACT,SAAS,UACT,MAAO,EAAY,CAAE,KAAM,CAAU,EAAI,IAAA,GACzC,UAAS,EAAM,GACf,SAAU,EAAW,EAAI,IAAA,GACzB,KAAM,EAAW,SAAW,IAAA,GAC5B,aAAY,EAAM,KAClB,eAAc,EAAW,EAAa,IAAA,GACtC,QAAS,MAAiB,EAAS,EAAM,EAAE,EAAI,IAAA,GAC/C,YACI,EACO,GAAM,EAAO,CAAE,GAAI,EAAM,GAAI,KAAM,EAAM,IAAK,EAAG,CAAC,EACnD,IAAA,GAEV,aAAc,EAAc,EAAU,IAAA,GACtC,UACI,EACO,GAAM,EACC,EAAE,MAAQ,SAAW,EAAE,MAAQ,OAC/B,EAAE,eAAe,EACjB,EAAS,EAAM,EAAE,EAEzB,EACA,IAAA,EAEb,EA3BQ,EAAM,EA2Bd,CAET,CAAC,EAvCmB,KAyCrB,CACC,EACA,EACA,EACA,EACA,EACA,EACA,EACA,EACA,EACA,EACA,CACJ,CAAC,EAED,OACI,EAAA,EAAA,KAAA,CAAC,MAAD,CACI,IAAK,EACL,UAAW,EAAA,GAAG,EAAA,QAAO,IAAK,CAAS,EACnC,MAAO,CAAE,SAAQ,GAAG,CAAM,EAC1B,KAAK,QACL,aAAY,EACZ,GAAI,WANR,CAQM,GAGE,EAAA,EAAA,IAAA,CAAC,MAAD,CACI,UAAW,EAAA,QAAO,IAClB,MAAM,OACE,SACR,QAAS,OAAO,EAAM,GAAG,IACzB,oBAAoB,gBACpB,GAAI,EAAQ,mBAEZ,EAAA,EAAA,KAAA,CAAC,IAAD,CAAG,UAAW,EAAQ,WAAa,IAAA,YAAnC,CACK,EAEA,GACG,EAAO,MAAM,IAAK,IACd,EAAA,EAAA,IAAA,CAAC,OAAD,CAEI,UAAW,EAAA,QAAO,MAClB,EAAG,EAAM,SAAS,EAClB,EAAG,EAAM,SAAS,EAClB,WAAW,SACX,iBAAiB,mBAEhB,EAAM,EACL,EARG,SAAS,EAAM,IAQlB,CACT,EAEJ,GAAW,EAAQ,OAAS,IACzB,EAAA,EAAA,IAAA,CAAC,EAAA,WAAD,CACI,WAAY,EAAO,WACV,UACM,eAClB,CAAA,CAEN,GACF,CAAA,GAnCL,EAAA,EAAA,IAAA,CAAC,MAAD,CAAK,UAAW,EAAA,QAAO,iBAAU,GAAkB,kBAAwB,CAAA,EAsC9E,EAAQ,gBACL,EAAA,EAAA,IAAA,CAAC,SAAD,CAAQ,KAAK,SAAS,UAAW,EAAA,QAAO,UAAW,QAAS,EAAQ,eAAO,OAEnE,CAAA,EAGX,GAAe,IACZ,EAAA,EAAA,IAAA,CAAC,EAAA,WAAD,CAAY,EAAG,EAAM,EAAG,EAAG,EAAM,WAC5B,EACG,EAAc,CACV,GAAI,EAAM,KAAK,GACf,KAAM,EAAM,KAAK,KACjB,MAAO,IAAS,EAAM,KAAK,GAC/B,CAAC,GAED,EAAA,EAAA,IAAA,CAAC,EAAD,CACI,GAAI,EAAM,KAAK,GACf,KAAM,EAAM,KAAK,KACjB,MAAO,IAAS,EAAM,KAAK,GAC9B,CAAA,CAEG,CAAA,CAEf,GAEb,CAGA,SAAS,EAAiB,CAAE,KAAI,OAAM,SAAmD,CACrF,IAAM,EAAQ,EAAA,SAAS,CAAE,EACzB,OACI,EAAA,EAAA,KAAA,CAAA,EAAA,SAAA,CAAA,SAAA,EACI,EAAA,EAAA,KAAA,CAAC,MAAD,CAAK,UAAW,EAAA,QAAO,sBAAvB,CACK,EAAK,KAAG,EAAG,GACX,KACL,EAAA,EAAA,KAAA,CAAC,MAAD,CAAK,UAAW,EAAA,QAAO,qBAAvB,CACK,EAAQ,GAAG,EAAM,OAAO,KAAK,EAAM,OAAO,OAAO,UAAY,EAC7D,OAAO,GAAU,SAAW,MAAM,IAAU,EAC5C,GACP,CAAA,CAAA,CAEV"}
1
+ {"version":3,"file":"BrazilMap.cjs","names":[],"sources":["../../src/br/BrazilMap.tsx"],"sourcesContent":["/**\n * @tempest-limits file-lines, props-count, function-lines — the 17 props are five\n * independent axes of one map — choropleth (values, minColor, maxColor, colorScale,\n * colorByRegion), selection (selected, onSelect), overlay (markers, onMarkerClick,\n * zoomable), framing (height, padding, showLabels, label, loadingContent) and\n * tooltip (showTooltip, renderTooltip). None of them is mutually exclusive with\n * another, so there is no second component hiding here; the body is long for the\n * same reason it cannot be split, since the projected paths, the hit-testing and the\n * zoom transform all read the same viewBox state.\n */\nimport { useEffect, useMemo, useRef, useState, type HTMLAttributes, type ReactNode } from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport { fitProjection } from \"@/geo/projection\";\nimport type { GeoMarker } from \"@/geo/types\";\nimport type { BrUfFeatureCollection } from \"./br-geo\";\nimport { getState, type UF } from \"./locations\";\nimport { MapMarkers } from \"./MapMarkers\";\nimport { MapTooltip } from \"./MapTooltip\";\nimport { REGION_COLORS } from \"./regions\";\nimport type { ColorScale } from \"./scales\";\nimport { useMapZoom } from \"./use-map-zoom\";\nimport { geometriesBounds, geometryCentroid, geometryPath, lerpColor } from \"./svg-utils\";\nimport { useMapHover } from \"./use-map-hover\";\nimport styles from \"./BrazilMap.module.css\";\n\n/** Data passed to a {@link BrazilMapProps.renderTooltip} callback. */\nexport interface BrazilMapTooltipData {\n uf: UF;\n name: string;\n /** Choropleth value for this UF, if `values` was provided. */\n value?: number;\n}\n\nexport interface BrazilMapProps extends Omit<HTMLAttributes<HTMLDivElement>, \"onSelect\"> {\n /** Currently selected UF(s) — highlighted. Accepts one or many. */\n selected?: UF | readonly UF[] | null;\n /** Fired when a state is clicked. */\n onSelect?: (uf: UF) => void;\n /**\n * Optional choropleth values per UF. When set, each state is tinted between\n * `minColor` and `maxColor` by its value (linear). States without a value\n * use the base surface color.\n */\n values?: Partial<Record<UF, number>>;\n /** Choropleth low-end color (2-color linear ramp). Default: a light primary tint. */\n minColor?: string;\n /** Choropleth high-end color (2-color linear ramp). Default: the primary token. */\n maxColor?: string;\n /**\n * Custom value→color scale (e.g. from `sequentialScale`/`quantizeScale`).\n * Takes precedence over `minColor`/`maxColor`. Pair with a `<MapLegend>`.\n */\n colorScale?: ColorScale;\n /**\n * Tint each state by its macro-region (categorical). Overrides\n * `values`/`colorScale`. Pair with `<MapLegend items={regionLegendItems()} />`.\n */\n colorByRegion?: boolean;\n /** Viewport height in pixels. Default: `440`. */\n height?: number;\n /** Inner padding in pixels. Default: `12`. */\n padding?: number;\n /** Render the UF acronym at each state centroid. Default: `true`. */\n showLabels?: boolean;\n /** Accessible label for the map region. Default: `\"Mapa do Brasil por estado\"`. */\n label?: string;\n /** Custom content when the geometry is still loading. */\n loadingContent?: ReactNode;\n /** Show a floating tooltip (name + region + city count + value) on hover. Default: `true`. */\n showTooltip?: boolean;\n /** Override the default tooltip content. */\n renderTooltip?: (data: BrazilMapTooltipData) => ReactNode;\n /** Point markers to overlay on the map (e.g. capitals, stores). */\n markers?: readonly GeoMarker[];\n /** Fired when a marker is clicked. */\n onMarkerClick?: (marker: GeoMarker, index: number) => void;\n /** Enable wheel-zoom + drag-pan (double-click resets). Default: `false`. */\n zoomable?: boolean;\n}\n\n/**\n * Clickable choropleth map of Brazil's 27 federative units. Renders the bundled\n * simplified UF GeoJSON as SVG paths — **no external tiles or paid API**. Click\n * a state to fire `onSelect(uf)`; pass `selected` to highlight and `values` to\n * tint states by a metric.\n *\n * The GeoJSON (~36 KB gzip) is loaded lazily, so importing this component does\n * not pull the geometry until it actually mounts.\n *\n * @example\n * const [uf, setUf] = useState<UF | null>(null);\n * <BrazilMap selected={uf} onSelect={setUf} />\n *\n * @example\n * // Choropleth by a metric per state\n * <BrazilMap values={{ SP: 120, RJ: 90, MG: 60 }} />\n */\nexport function BrazilMap({\n selected,\n onSelect,\n values,\n minColor = \"#dbeafe\",\n maxColor = \"#2563eb\",\n colorScale,\n colorByRegion = false,\n height = 440,\n padding = 12,\n showLabels = true,\n label = \"Mapa do Brasil por estado\",\n loadingContent,\n showTooltip = true,\n renderTooltip,\n markers,\n onMarkerClick,\n zoomable = false,\n className,\n style,\n ...rest\n}: BrazilMapProps) {\n const containerRef = useRef<HTMLDivElement>(null);\n const [width, setWidth] = useState<number>(600);\n const [collection, setCollection] = useState<BrUfFeatureCollection | null>(null);\n const { hover, onMove, onLeave } = useMapHover<{ uf: UF; name: string }>(containerRef);\n const mapZoom = useMapZoom(zoomable);\n\n useEffect(() => {\n let active = true;\n void import(\"./br-geo\").then((m) => {\n if (active) setCollection(m.BR_UF_GEOJSON);\n });\n return () => {\n active = false;\n };\n }, []);\n\n useEffect(() => {\n const node = containerRef.current;\n if (!node || typeof ResizeObserver === \"undefined\") return;\n const observer = new ResizeObserver((entries) => {\n const measured = entries[0]?.contentRect.width;\n if (measured && measured > 0) setWidth(measured);\n });\n observer.observe(node);\n return () => observer.disconnect();\n }, []);\n\n const selectedSet = useMemo<Set<UF>>(() => {\n if (!selected) return new Set();\n return new Set(Array.isArray(selected) ? selected : [selected as UF]);\n }, [selected]);\n\n const valueRange = useMemo<[number, number] | null>(() => {\n if (!values) return null;\n const nums = Object.values(values).filter((v): v is number => typeof v === \"number\");\n if (nums.length === 0) return null;\n return [Math.min(...nums), Math.max(...nums)];\n }, [values]);\n\n const shapes = useMemo(() => {\n if (!collection) return null;\n const projection = fitProjection(\n geometriesBounds(collection.features.map((f) => f.geometry)),\n width,\n height,\n { padding },\n );\n const items = collection.features.map((feature) => ({\n uf: feature.properties.uf,\n name: feature.properties.name,\n region: feature.properties.region,\n d: geometryPath(feature.geometry, projection),\n centroid: geometryCentroid(feature.geometry, projection),\n }));\n return { projection, items };\n }, [collection, width, height, padding]);\n\n function fillFor(uf: UF): string | undefined {\n const v = values?.[uf];\n if (typeof v !== \"number\") return undefined;\n if (colorScale) return colorScale(v);\n if (!valueRange) return undefined;\n const [min, max] = valueRange;\n const t = max === min ? 1 : (v - min) / (max - min);\n return lerpColor(minColor, maxColor, t);\n }\n\n // Memoized so frequent re-renders (hover tooltip) don't rebuild all 27 paths.\n const stateEls = useMemo(() => {\n if (!shapes) return null;\n return shapes.items.map((shape) => {\n const isSelected = selectedSet.has(shape.uf);\n const fillColor = isSelected\n ? undefined\n : colorByRegion\n ? REGION_COLORS[shape.region]\n : fillFor(shape.uf);\n return (\n <path\n key={shape.uf}\n className={cn(styles.state, isSelected && styles.selected)}\n d={shape.d}\n fillRule=\"evenodd\"\n style={fillColor ? { fill: fillColor } : undefined}\n data-uf={shape.uf}\n tabIndex={onSelect ? 0 : undefined}\n role={onSelect ? \"button\" : undefined}\n aria-label={shape.name}\n aria-pressed={onSelect ? isSelected : undefined}\n onClick={onSelect ? () => onSelect(shape.uf) : undefined}\n onMouseMove={\n showTooltip\n ? (e) => onMove({ uf: shape.uf, name: shape.name }, e)\n : undefined\n }\n onMouseLeave={showTooltip ? onLeave : undefined}\n onKeyDown={\n onSelect\n ? (e) => {\n if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n onSelect(shape.uf);\n }\n }\n : undefined\n }\n />\n );\n });\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [\n shapes,\n selectedSet,\n values,\n colorScale,\n colorByRegion,\n minColor,\n maxColor,\n onSelect,\n showTooltip,\n onMove,\n onLeave,\n ]);\n\n return (\n <div\n ref={containerRef}\n className={cn(styles.map, className)}\n style={{ height, ...style }}\n role=\"group\"\n aria-label={label}\n {...rest}\n >\n {!shapes ? (\n <div className={styles.loading}>{loadingContent ?? \"Carregando mapa…\"}</div>\n ) : (\n <svg\n className={styles.svg}\n width=\"100%\"\n height={height}\n viewBox={`0 0 ${width} ${height}`}\n preserveAspectRatio=\"xMidYMid meet\"\n {...mapZoom.handlers}\n >\n <g transform={mapZoom.transform || undefined}>\n {stateEls}\n\n {showLabels &&\n shapes.items.map((shape) => (\n <text\n key={`label-${shape.uf}`}\n className={styles.label}\n x={shape.centroid.x}\n y={shape.centroid.y}\n textAnchor=\"middle\"\n dominantBaseline=\"central\"\n >\n {shape.uf}\n </text>\n ))}\n\n {markers && markers.length > 0 && (\n <MapMarkers\n projection={shapes.projection}\n markers={markers}\n onMarkerClick={onMarkerClick}\n />\n )}\n </g>\n </svg>\n )}\n\n {mapZoom.isTransformed && (\n <button type=\"button\" className={styles.zoomReset} onClick={mapZoom.reset}>\n Reset\n </button>\n )}\n\n {showTooltip && hover && (\n <MapTooltip x={hover.x} y={hover.y}>\n {renderTooltip ? (\n renderTooltip({\n uf: hover.item.uf,\n name: hover.item.name,\n value: values?.[hover.item.uf],\n })\n ) : (\n <DefaultUfTooltip\n uf={hover.item.uf}\n name={hover.item.name}\n value={values?.[hover.item.uf]}\n />\n )}\n </MapTooltip>\n )}\n </div>\n );\n}\n\n/** Default UF tooltip: name (UF) + region + city count + optional value. */\nfunction DefaultUfTooltip({ uf, name, value }: { uf: UF; name: string; value?: number }) {\n const state = getState(uf);\n return (\n <>\n <div className={styles.tooltipTitle}>\n {name} ({uf})\n </div>\n <div className={styles.tooltipMeta}>\n {state ? `${state.region} · ${state.cities.length} cidades` : uf}\n {typeof value === \"number\" ? ` · ${value}` : \"\"}\n </div>\n </>\n );\n}\n"],"mappings":"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"}
@@ -57,8 +57,8 @@ function b({ selected: n, onSelect: _, values: b, minColor: S = "#dbeafe", maxCo
57
57
  if (typeof t != "number") return;
58
58
  if (w) return w(t);
59
59
  if (!Y) return;
60
- let [n, r] = Y;
61
- return d(S, C, r === n ? 1 : (t - n) / (r - n));
60
+ let [n, r] = Y, i = r === n ? 1 : (t - n) / (r - n);
61
+ return d(S, C, i);
62
62
  }
63
63
  let Q = m(() => X ? X.items.map((t) => {
64
64
  let n = J.has(t.uf), i = n ? void 0 : T ? o[t.region] : Z(t.uf);
@@ -1 +1 @@
1
- {"version":3,"file":"BrazilMap.js","names":[],"sources":["../../src/br/BrazilMap.tsx"],"sourcesContent":["/**\n * @tempest-limits file-lines, props-count, function-lines — the 17 props are five\n * independent axes of one map — choropleth (values, minColor, maxColor, colorScale,\n * colorByRegion), selection (selected, onSelect), overlay (markers, onMarkerClick,\n * zoomable), framing (height, padding, showLabels, label, loadingContent) and\n * tooltip (showTooltip, renderTooltip). None of them is mutually exclusive with\n * another, so there is no second component hiding here; the body is long for the\n * same reason it cannot be split, since the projected paths, the hit-testing and the\n * zoom transform all read the same viewBox state.\n */\nimport { useEffect, useMemo, useRef, useState, type HTMLAttributes, type ReactNode } from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport { fitProjection } from \"@/geo/projection\";\nimport type { GeoMarker } from \"@/geo/types\";\nimport type { BrUfFeatureCollection } from \"./br-geo\";\nimport { getState, type UF } from \"./locations\";\nimport { MapMarkers } from \"./MapMarkers\";\nimport { MapTooltip } from \"./MapTooltip\";\nimport { REGION_COLORS } from \"./regions\";\nimport type { ColorScale } from \"./scales\";\nimport { useMapZoom } from \"./use-map-zoom\";\nimport { geometriesBounds, geometryCentroid, geometryPath, lerpColor } from \"./svg-utils\";\nimport { useMapHover } from \"./use-map-hover\";\nimport styles from \"./BrazilMap.module.css\";\n\n/** Data passed to a {@link BrazilMapProps.renderTooltip} callback. */\nexport interface BrazilMapTooltipData {\n uf: UF;\n name: string;\n /** Choropleth value for this UF, if `values` was provided. */\n value?: number;\n}\n\nexport interface BrazilMapProps extends Omit<HTMLAttributes<HTMLDivElement>, \"onSelect\"> {\n /** Currently selected UF(s) — highlighted. Accepts one or many. */\n selected?: UF | readonly UF[] | null;\n /** Fired when a state is clicked. */\n onSelect?: (uf: UF) => void;\n /**\n * Optional choropleth values per UF. When set, each state is tinted between\n * `minColor` and `maxColor` by its value (linear). States without a value\n * use the base surface color.\n */\n values?: Partial<Record<UF, number>>;\n /** Choropleth low-end color (2-color linear ramp). Default: a light primary tint. */\n minColor?: string;\n /** Choropleth high-end color (2-color linear ramp). Default: the primary token. */\n maxColor?: string;\n /**\n * Custom value→color scale (e.g. from `sequentialScale`/`quantizeScale`).\n * Takes precedence over `minColor`/`maxColor`. Pair with a `<MapLegend>`.\n */\n colorScale?: ColorScale;\n /**\n * Tint each state by its macro-region (categorical). Overrides\n * `values`/`colorScale`. Pair with `<MapLegend items={regionLegendItems()} />`.\n */\n colorByRegion?: boolean;\n /** Viewport height in pixels. Default: `440`. */\n height?: number;\n /** Inner padding in pixels. Default: `12`. */\n padding?: number;\n /** Render the UF acronym at each state centroid. Default: `true`. */\n showLabels?: boolean;\n /** Accessible label for the map region. Default: `\"Mapa do Brasil por estado\"`. */\n label?: string;\n /** Custom content when the geometry is still loading. */\n loadingContent?: ReactNode;\n /** Show a floating tooltip (name + region + city count + value) on hover. Default: `true`. */\n showTooltip?: boolean;\n /** Override the default tooltip content. */\n renderTooltip?: (data: BrazilMapTooltipData) => ReactNode;\n /** Point markers to overlay on the map (e.g. capitals, stores). */\n markers?: readonly GeoMarker[];\n /** Fired when a marker is clicked. */\n onMarkerClick?: (marker: GeoMarker, index: number) => void;\n /** Enable wheel-zoom + drag-pan (double-click resets). Default: `false`. */\n zoomable?: boolean;\n}\n\n/**\n * Clickable choropleth map of Brazil's 27 federative units. Renders the bundled\n * simplified UF GeoJSON as SVG paths — **no external tiles or paid API**. Click\n * a state to fire `onSelect(uf)`; pass `selected` to highlight and `values` to\n * tint states by a metric.\n *\n * The GeoJSON (~36 KB gzip) is loaded lazily, so importing this component does\n * not pull the geometry until it actually mounts.\n *\n * @example\n * const [uf, setUf] = useState<UF | null>(null);\n * <BrazilMap selected={uf} onSelect={setUf} />\n *\n * @example\n * // Choropleth by a metric per state\n * <BrazilMap values={{ SP: 120, RJ: 90, MG: 60 }} />\n */\nexport function BrazilMap({\n selected,\n onSelect,\n values,\n minColor = \"#dbeafe\",\n maxColor = \"#2563eb\",\n colorScale,\n colorByRegion = false,\n height = 440,\n padding = 12,\n showLabels = true,\n label = \"Mapa do Brasil por estado\",\n loadingContent,\n showTooltip = true,\n renderTooltip,\n markers,\n onMarkerClick,\n zoomable = false,\n className,\n style,\n ...rest\n}: BrazilMapProps) {\n const containerRef = useRef<HTMLDivElement>(null);\n const [width, setWidth] = useState<number>(600);\n const [collection, setCollection] = useState<BrUfFeatureCollection | null>(null);\n const { hover, onMove, onLeave } = useMapHover<{ uf: UF; name: string }>(containerRef);\n const mapZoom = useMapZoom(zoomable);\n\n useEffect(() => {\n let active = true;\n void import(\"./br-geo\").then((m) => {\n if (active) setCollection(m.BR_UF_GEOJSON);\n });\n return () => {\n active = false;\n };\n }, []);\n\n useEffect(() => {\n const node = containerRef.current;\n if (!node || typeof ResizeObserver === \"undefined\") return;\n const observer = new ResizeObserver((entries) => {\n const measured = entries[0]?.contentRect.width;\n if (measured && measured > 0) setWidth(measured);\n });\n observer.observe(node);\n return () => observer.disconnect();\n }, []);\n\n const selectedSet = useMemo<Set<UF>>(() => {\n if (!selected) return new Set();\n return new Set(Array.isArray(selected) ? selected : [selected as UF]);\n }, [selected]);\n\n const valueRange = useMemo<[number, number] | null>(() => {\n if (!values) return null;\n const nums = Object.values(values).filter((v): v is number => typeof v === \"number\");\n if (nums.length === 0) return null;\n return [Math.min(...nums), Math.max(...nums)];\n }, [values]);\n\n const shapes = useMemo(() => {\n if (!collection) return null;\n const projection = fitProjection(\n geometriesBounds(collection.features.map((f) => f.geometry)),\n width,\n height,\n { padding },\n );\n const items = collection.features.map((feature) => ({\n uf: feature.properties.uf,\n name: feature.properties.name,\n region: feature.properties.region,\n d: geometryPath(feature.geometry, projection),\n centroid: geometryCentroid(feature.geometry, projection),\n }));\n return { projection, items };\n }, [collection, width, height, padding]);\n\n function fillFor(uf: UF): string | undefined {\n const v = values?.[uf];\n if (typeof v !== \"number\") return undefined;\n if (colorScale) return colorScale(v);\n if (!valueRange) return undefined;\n const [min, max] = valueRange;\n const t = max === min ? 1 : (v - min) / (max - min);\n return lerpColor(minColor, maxColor, t);\n }\n\n // Memoized so frequent re-renders (hover tooltip) don't rebuild all 27 paths.\n const stateEls = useMemo(() => {\n if (!shapes) return null;\n return shapes.items.map((shape) => {\n const isSelected = selectedSet.has(shape.uf);\n const fillColor = isSelected\n ? undefined\n : colorByRegion\n ? REGION_COLORS[shape.region]\n : fillFor(shape.uf);\n return (\n <path\n key={shape.uf}\n className={cn(styles.state, isSelected && styles.selected)}\n d={shape.d}\n fillRule=\"evenodd\"\n style={fillColor ? { fill: fillColor } : undefined}\n data-uf={shape.uf}\n tabIndex={onSelect ? 0 : undefined}\n role={onSelect ? \"button\" : undefined}\n aria-label={shape.name}\n aria-pressed={onSelect ? isSelected : undefined}\n onClick={onSelect ? () => onSelect(shape.uf) : undefined}\n onMouseMove={\n showTooltip\n ? (e) => onMove({ uf: shape.uf, name: shape.name }, e)\n : undefined\n }\n onMouseLeave={showTooltip ? onLeave : undefined}\n onKeyDown={\n onSelect\n ? (e) => {\n if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n onSelect(shape.uf);\n }\n }\n : undefined\n }\n />\n );\n });\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [\n shapes,\n selectedSet,\n values,\n colorScale,\n colorByRegion,\n minColor,\n maxColor,\n onSelect,\n showTooltip,\n onMove,\n onLeave,\n ]);\n\n return (\n <div\n ref={containerRef}\n className={cn(styles.map, className)}\n style={{ height, ...style }}\n role=\"group\"\n aria-label={label}\n {...rest}\n >\n {!shapes ? (\n <div className={styles.loading}>{loadingContent ?? \"Carregando mapa…\"}</div>\n ) : (\n <svg\n className={styles.svg}\n width=\"100%\"\n height={height}\n viewBox={`0 0 ${width} ${height}`}\n preserveAspectRatio=\"xMidYMid meet\"\n {...mapZoom.handlers}\n >\n <g transform={mapZoom.transform || undefined}>\n {stateEls}\n\n {showLabels &&\n shapes.items.map((shape) => (\n <text\n key={`label-${shape.uf}`}\n className={styles.label}\n x={shape.centroid.x}\n y={shape.centroid.y}\n textAnchor=\"middle\"\n dominantBaseline=\"central\"\n >\n {shape.uf}\n </text>\n ))}\n\n {markers && markers.length > 0 && (\n <MapMarkers\n projection={shapes.projection}\n markers={markers}\n onMarkerClick={onMarkerClick}\n />\n )}\n </g>\n </svg>\n )}\n\n {mapZoom.isTransformed && (\n <button type=\"button\" className={styles.zoomReset} onClick={mapZoom.reset}>\n Reset\n </button>\n )}\n\n {showTooltip && hover && (\n <MapTooltip x={hover.x} y={hover.y}>\n {renderTooltip ? (\n renderTooltip({\n uf: hover.item.uf,\n name: hover.item.name,\n value: values?.[hover.item.uf],\n })\n ) : (\n <DefaultUfTooltip\n uf={hover.item.uf}\n name={hover.item.name}\n value={values?.[hover.item.uf]}\n />\n )}\n </MapTooltip>\n )}\n </div>\n );\n}\n\n/** Default UF tooltip: name (UF) + region + city count + optional value. */\nfunction DefaultUfTooltip({ uf, name, value }: { uf: UF; name: string; value?: number }) {\n const state = getState(uf);\n return (\n <>\n <div className={styles.tooltipTitle}>\n {name} ({uf})\n </div>\n <div className={styles.tooltipMeta}>\n {state ? `${state.region} · ${state.cities.length} cidades` : uf}\n {typeof value === \"number\" ? ` · ${value}` : \"\"}\n </div>\n </>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"BrazilMap.js","names":[],"sources":["../../src/br/BrazilMap.tsx"],"sourcesContent":["/**\n * @tempest-limits file-lines, props-count, function-lines — the 17 props are five\n * independent axes of one map — choropleth (values, minColor, maxColor, colorScale,\n * colorByRegion), selection (selected, onSelect), overlay (markers, onMarkerClick,\n * zoomable), framing (height, padding, showLabels, label, loadingContent) and\n * tooltip (showTooltip, renderTooltip). None of them is mutually exclusive with\n * another, so there is no second component hiding here; the body is long for the\n * same reason it cannot be split, since the projected paths, the hit-testing and the\n * zoom transform all read the same viewBox state.\n */\nimport { useEffect, useMemo, useRef, useState, type HTMLAttributes, type ReactNode } from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport { fitProjection } from \"@/geo/projection\";\nimport type { GeoMarker } from \"@/geo/types\";\nimport type { BrUfFeatureCollection } from \"./br-geo\";\nimport { getState, type UF } from \"./locations\";\nimport { MapMarkers } from \"./MapMarkers\";\nimport { MapTooltip } from \"./MapTooltip\";\nimport { REGION_COLORS } from \"./regions\";\nimport type { ColorScale } from \"./scales\";\nimport { useMapZoom } from \"./use-map-zoom\";\nimport { geometriesBounds, geometryCentroid, geometryPath, lerpColor } from \"./svg-utils\";\nimport { useMapHover } from \"./use-map-hover\";\nimport styles from \"./BrazilMap.module.css\";\n\n/** Data passed to a {@link BrazilMapProps.renderTooltip} callback. */\nexport interface BrazilMapTooltipData {\n uf: UF;\n name: string;\n /** Choropleth value for this UF, if `values` was provided. */\n value?: number;\n}\n\nexport interface BrazilMapProps extends Omit<HTMLAttributes<HTMLDivElement>, \"onSelect\"> {\n /** Currently selected UF(s) — highlighted. Accepts one or many. */\n selected?: UF | readonly UF[] | null;\n /** Fired when a state is clicked. */\n onSelect?: (uf: UF) => void;\n /**\n * Optional choropleth values per UF. When set, each state is tinted between\n * `minColor` and `maxColor` by its value (linear). States without a value\n * use the base surface color.\n */\n values?: Partial<Record<UF, number>>;\n /** Choropleth low-end color (2-color linear ramp). Default: a light primary tint. */\n minColor?: string;\n /** Choropleth high-end color (2-color linear ramp). Default: the primary token. */\n maxColor?: string;\n /**\n * Custom value→color scale (e.g. from `sequentialScale`/`quantizeScale`).\n * Takes precedence over `minColor`/`maxColor`. Pair with a `<MapLegend>`.\n */\n colorScale?: ColorScale;\n /**\n * Tint each state by its macro-region (categorical). Overrides\n * `values`/`colorScale`. Pair with `<MapLegend items={regionLegendItems()} />`.\n */\n colorByRegion?: boolean;\n /** Viewport height in pixels. Default: `440`. */\n height?: number;\n /** Inner padding in pixels. Default: `12`. */\n padding?: number;\n /** Render the UF acronym at each state centroid. Default: `true`. */\n showLabels?: boolean;\n /** Accessible label for the map region. Default: `\"Mapa do Brasil por estado\"`. */\n label?: string;\n /** Custom content when the geometry is still loading. */\n loadingContent?: ReactNode;\n /** Show a floating tooltip (name + region + city count + value) on hover. Default: `true`. */\n showTooltip?: boolean;\n /** Override the default tooltip content. */\n renderTooltip?: (data: BrazilMapTooltipData) => ReactNode;\n /** Point markers to overlay on the map (e.g. capitals, stores). */\n markers?: readonly GeoMarker[];\n /** Fired when a marker is clicked. */\n onMarkerClick?: (marker: GeoMarker, index: number) => void;\n /** Enable wheel-zoom + drag-pan (double-click resets). Default: `false`. */\n zoomable?: boolean;\n}\n\n/**\n * Clickable choropleth map of Brazil's 27 federative units. Renders the bundled\n * simplified UF GeoJSON as SVG paths — **no external tiles or paid API**. Click\n * a state to fire `onSelect(uf)`; pass `selected` to highlight and `values` to\n * tint states by a metric.\n *\n * The GeoJSON (~36 KB gzip) is loaded lazily, so importing this component does\n * not pull the geometry until it actually mounts.\n *\n * @example\n * const [uf, setUf] = useState<UF | null>(null);\n * <BrazilMap selected={uf} onSelect={setUf} />\n *\n * @example\n * // Choropleth by a metric per state\n * <BrazilMap values={{ SP: 120, RJ: 90, MG: 60 }} />\n */\nexport function BrazilMap({\n selected,\n onSelect,\n values,\n minColor = \"#dbeafe\",\n maxColor = \"#2563eb\",\n colorScale,\n colorByRegion = false,\n height = 440,\n padding = 12,\n showLabels = true,\n label = \"Mapa do Brasil por estado\",\n loadingContent,\n showTooltip = true,\n renderTooltip,\n markers,\n onMarkerClick,\n zoomable = false,\n className,\n style,\n ...rest\n}: BrazilMapProps) {\n const containerRef = useRef<HTMLDivElement>(null);\n const [width, setWidth] = useState<number>(600);\n const [collection, setCollection] = useState<BrUfFeatureCollection | null>(null);\n const { hover, onMove, onLeave } = useMapHover<{ uf: UF; name: string }>(containerRef);\n const mapZoom = useMapZoom(zoomable);\n\n useEffect(() => {\n let active = true;\n void import(\"./br-geo\").then((m) => {\n if (active) setCollection(m.BR_UF_GEOJSON);\n });\n return () => {\n active = false;\n };\n }, []);\n\n useEffect(() => {\n const node = containerRef.current;\n if (!node || typeof ResizeObserver === \"undefined\") return;\n const observer = new ResizeObserver((entries) => {\n const measured = entries[0]?.contentRect.width;\n if (measured && measured > 0) setWidth(measured);\n });\n observer.observe(node);\n return () => observer.disconnect();\n }, []);\n\n const selectedSet = useMemo<Set<UF>>(() => {\n if (!selected) return new Set();\n return new Set(Array.isArray(selected) ? selected : [selected as UF]);\n }, [selected]);\n\n const valueRange = useMemo<[number, number] | null>(() => {\n if (!values) return null;\n const nums = Object.values(values).filter((v): v is number => typeof v === \"number\");\n if (nums.length === 0) return null;\n return [Math.min(...nums), Math.max(...nums)];\n }, [values]);\n\n const shapes = useMemo(() => {\n if (!collection) return null;\n const projection = fitProjection(\n geometriesBounds(collection.features.map((f) => f.geometry)),\n width,\n height,\n { padding },\n );\n const items = collection.features.map((feature) => ({\n uf: feature.properties.uf,\n name: feature.properties.name,\n region: feature.properties.region,\n d: geometryPath(feature.geometry, projection),\n centroid: geometryCentroid(feature.geometry, projection),\n }));\n return { projection, items };\n }, [collection, width, height, padding]);\n\n function fillFor(uf: UF): string | undefined {\n const v = values?.[uf];\n if (typeof v !== \"number\") return undefined;\n if (colorScale) return colorScale(v);\n if (!valueRange) return undefined;\n const [min, max] = valueRange;\n const t = max === min ? 1 : (v - min) / (max - min);\n return lerpColor(minColor, maxColor, t);\n }\n\n // Memoized so frequent re-renders (hover tooltip) don't rebuild all 27 paths.\n const stateEls = useMemo(() => {\n if (!shapes) return null;\n return shapes.items.map((shape) => {\n const isSelected = selectedSet.has(shape.uf);\n const fillColor = isSelected\n ? undefined\n : colorByRegion\n ? REGION_COLORS[shape.region]\n : fillFor(shape.uf);\n return (\n <path\n key={shape.uf}\n className={cn(styles.state, isSelected && styles.selected)}\n d={shape.d}\n fillRule=\"evenodd\"\n style={fillColor ? { fill: fillColor } : undefined}\n data-uf={shape.uf}\n tabIndex={onSelect ? 0 : undefined}\n role={onSelect ? \"button\" : undefined}\n aria-label={shape.name}\n aria-pressed={onSelect ? isSelected : undefined}\n onClick={onSelect ? () => onSelect(shape.uf) : undefined}\n onMouseMove={\n showTooltip\n ? (e) => onMove({ uf: shape.uf, name: shape.name }, e)\n : undefined\n }\n onMouseLeave={showTooltip ? onLeave : undefined}\n onKeyDown={\n onSelect\n ? (e) => {\n if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n onSelect(shape.uf);\n }\n }\n : undefined\n }\n />\n );\n });\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [\n shapes,\n selectedSet,\n values,\n colorScale,\n colorByRegion,\n minColor,\n maxColor,\n onSelect,\n showTooltip,\n onMove,\n onLeave,\n ]);\n\n return (\n <div\n ref={containerRef}\n className={cn(styles.map, className)}\n style={{ height, ...style }}\n role=\"group\"\n aria-label={label}\n {...rest}\n >\n {!shapes ? (\n <div className={styles.loading}>{loadingContent ?? \"Carregando mapa…\"}</div>\n ) : (\n <svg\n className={styles.svg}\n width=\"100%\"\n height={height}\n viewBox={`0 0 ${width} ${height}`}\n preserveAspectRatio=\"xMidYMid meet\"\n {...mapZoom.handlers}\n >\n <g transform={mapZoom.transform || undefined}>\n {stateEls}\n\n {showLabels &&\n shapes.items.map((shape) => (\n <text\n key={`label-${shape.uf}`}\n className={styles.label}\n x={shape.centroid.x}\n y={shape.centroid.y}\n textAnchor=\"middle\"\n dominantBaseline=\"central\"\n >\n {shape.uf}\n </text>\n ))}\n\n {markers && markers.length > 0 && (\n <MapMarkers\n projection={shapes.projection}\n markers={markers}\n onMarkerClick={onMarkerClick}\n />\n )}\n </g>\n </svg>\n )}\n\n {mapZoom.isTransformed && (\n <button type=\"button\" className={styles.zoomReset} onClick={mapZoom.reset}>\n Reset\n </button>\n )}\n\n {showTooltip && hover && (\n <MapTooltip x={hover.x} y={hover.y}>\n {renderTooltip ? (\n renderTooltip({\n uf: hover.item.uf,\n name: hover.item.name,\n value: values?.[hover.item.uf],\n })\n ) : (\n <DefaultUfTooltip\n uf={hover.item.uf}\n name={hover.item.name}\n value={values?.[hover.item.uf]}\n />\n )}\n </MapTooltip>\n )}\n </div>\n );\n}\n\n/** Default UF tooltip: name (UF) + region + city count + optional value. */\nfunction DefaultUfTooltip({ uf, name, value }: { uf: UF; name: string; value?: number }) {\n const state = getState(uf);\n return (\n <>\n <div className={styles.tooltipTitle}>\n {name} ({uf})\n </div>\n <div className={styles.tooltipMeta}>\n {state ? `${state.region} · ${state.cities.length} cidades` : uf}\n {typeof value === \"number\" ? ` · ${value}` : \"\"}\n </div>\n </>\n );\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"BrazilStateCitySelect.cjs","names":[],"sources":["../../src/br/BrazilStateCitySelect.tsx"],"sourcesContent":["/**\n * @tempest-limits props-count — two dependent selects in one control, so every knob\n * comes in pairs: stateLabel and cityLabel, statePlaceholder and cityPlaceholder,\n * defaultUf and defaultCity. The pairing is the feature — the city list is derived\n * from the chosen UF, which is exactly what a caller wiring two separate selects has\n * to reimplement.\n */\nimport { useMemo, useState, type ReactElement } from \"react\";\nimport { Select } from \"@/components/Select\";\nimport { cityChoices, listStates, normalizeUf, type UF } from \"./locations\";\n\n/** Current selection emitted by {@link BrazilStateCitySelect}. */\nexport interface BrazilStateCitySelection {\n /** Selected federative unit, or `null` when none. */\n uf: UF | null;\n /** Selected city, or `null` when none. */\n city: string | null;\n}\n\nexport interface BrazilStateCitySelectProps {\n /** Pre-selected UF (uncontrolled initial value). */\n defaultUf?: UF;\n /** Pre-selected city (uncontrolled initial value). */\n defaultCity?: string;\n /** Fired whenever the state or city changes. */\n onChange?: (selection: BrazilStateCitySelection) => void;\n /** Label for the state select. Default: `\"Estado\"`. */\n stateLabel?: string;\n /** Label for the city select. Default: `\"Cidade\"`. */\n cityLabel?: string;\n /** Placeholder for the state select. Default: `\"Selecione o estado\"`. */\n statePlaceholder?: string;\n /** Placeholder for the city select. Default: `\"Selecione a cidade\"`. */\n cityPlaceholder?: string;\n /** Disable both selects. */\n disabled?: boolean;\n /** Layout of the two selects. Default: `\"row\"`. */\n layout?: \"row\" | \"column\";\n}\n\n/**\n * Cascading Estado → Cidade selector backed by the bundled BR locations data —\n * pick a state and the city list narrows to that UF's municipalities. No\n * network, no external API. Complements {@link BrazilMap} (wire the map's\n * `onSelect` to drive the same UF).\n *\n * @example\n * <BrazilStateCitySelect onChange={({ uf, city }) => console.log(uf, city)} />\n */\nexport function BrazilStateCitySelect({\n defaultUf,\n defaultCity,\n onChange,\n stateLabel = \"Estado\",\n cityLabel = \"Cidade\",\n statePlaceholder = \"Selecione o estado\",\n cityPlaceholder = \"Selecione a cidade\",\n disabled = false,\n layout = \"row\",\n}: BrazilStateCitySelectProps): ReactElement {\n const [uf, setUf] = useState<UF | null>(defaultUf ?? null);\n const [city, setCity] = useState<string | null>(defaultCity ?? null);\n\n const stateOptions = useMemo(\n () => listStates().map((s) => ({ value: s.uf, label: s.name })),\n [],\n );\n const cityOptions = useMemo(() => (uf ? cityChoices(uf) : []), [uf]);\n\n function handleUf(next: string): void {\n const nextUf = normalizeUf(next);\n setUf(nextUf);\n setCity(null);\n onChange?.({ uf: nextUf, city: null });\n }\n\n function handleCity(next: string): void {\n const nextCity = next || null;\n setCity(nextCity);\n onChange?.({ uf, city: nextCity });\n }\n\n return (\n <div\n style={{\n display: \"flex\",\n flexDirection: layout === \"row\" ? \"row\" : \"column\",\n gap: \"var(--tempest-space-3, 12px)\",\n flexWrap: \"wrap\",\n }}\n >\n <Select\n label={stateLabel}\n placeholder={statePlaceholder}\n options={stateOptions}\n value={uf ?? \"\"}\n disabled={disabled}\n onChange={(e) => handleUf(e.target.value)}\n style={{ minWidth: 0 }}\n />\n <Select\n label={cityLabel}\n placeholder={cityPlaceholder}\n options={cityOptions}\n value={city ?? \"\"}\n disabled={disabled || !uf}\n onChange={(e) => handleCity(e.target.value)}\n style={{ minWidth: 0 }}\n />\n </div>\n );\n}\n"],"mappings":"sIAiDA,SAAgB,EAAsB,CAClC,YACA,cACA,WACA,aAAa,SACb,YAAY,SACZ,mBAAmB,qBACnB,kBAAkB,qBAClB,WAAW,GACX,SAAS,OACgC,CACzC,GAAM,CAAC,EAAI,IAAA,EAAA,EAAA,SAAA,CAA6B,GAAa,IAAI,EACnD,CAAC,EAAM,IAAA,EAAA,EAAA,SAAA,CAAmC,GAAe,IAAI,EAE7D,GAAA,EAAA,EAAA,QAAA,KACI,EAAA,WAAW,CAAC,CAAC,IAAK,IAAO,CAAE,MAAO,EAAE,GAAI,MAAO,EAAE,IAAK,EAAE,EAC9D,CAAC,CACL,EACM,GAAA,EAAA,EAAA,QAAA,KAA6B,EAAK,EAAA,YAAY,CAAE,EAAI,CAAC,EAAI,CAAC,CAAE,CAAC,EAEnE,SAAS,EAAS,EAAoB,CAClC,IAAM,EAAS,EAAA,YAAY,CAAI,EAC/B,EAAM,CAAM,EACZ,EAAQ,IAAI,EACZ,IAAW,CAAE,GAAI,EAAQ,KAAM,IAAK,CAAC,CACzC,CAEA,SAAS,EAAW,EAAoB,CACpC,IAAM,EAAW,GAAQ,KACzB,EAAQ,CAAQ,EAChB,IAAW,CAAE,KAAI,KAAM,CAAS,CAAC,CACrC,CAEA,OACI,EAAA,EAAA,KAAA,CAAC,MAAD,CACI,MAAO,CACH,QAAS,OACT,cAAe,IAAW,MAAQ,MAAQ,SAC1C,IAAK,+BACL,SAAU,MACd,WANJ,EAQI,EAAA,EAAA,IAAA,CAAC,EAAA,OAAD,CACI,MAAO,EACP,YAAa,EACb,QAAS,EACT,MAAO,GAAM,GACH,WACV,SAAW,GAAM,EAAS,EAAE,OAAO,KAAK,EACxC,MAAO,CAAE,SAAU,CAAE,CACxB,CAAA,GACD,EAAA,EAAA,IAAA,CAAC,EAAA,OAAD,CACI,MAAO,EACP,YAAa,EACb,QAAS,EACT,MAAO,GAAQ,GACf,SAAU,GAAY,CAAC,EACvB,SAAW,GAAM,EAAW,EAAE,OAAO,KAAK,EAC1C,MAAO,CAAE,SAAU,CAAE,CACxB,CAAA,CACA,GAEb"}
1
+ {"version":3,"file":"BrazilStateCitySelect.cjs","names":[],"sources":["../../src/br/BrazilStateCitySelect.tsx"],"sourcesContent":["/**\n * @tempest-limits props-count — two dependent selects in one control, so every knob\n * comes in pairs: stateLabel and cityLabel, statePlaceholder and cityPlaceholder,\n * defaultUf and defaultCity. The pairing is the feature — the city list is derived\n * from the chosen UF, which is exactly what a caller wiring two separate selects has\n * to reimplement.\n */\nimport { useMemo, useState, type ReactElement } from \"react\";\nimport { Select } from \"@/components/Select\";\nimport { cityChoices, listStates, normalizeUf, type UF } from \"./locations\";\n\n/** Current selection emitted by {@link BrazilStateCitySelect}. */\nexport interface BrazilStateCitySelection {\n /** Selected federative unit, or `null` when none. */\n uf: UF | null;\n /** Selected city, or `null` when none. */\n city: string | null;\n}\n\nexport interface BrazilStateCitySelectProps {\n /** Pre-selected UF (uncontrolled initial value). */\n defaultUf?: UF;\n /** Pre-selected city (uncontrolled initial value). */\n defaultCity?: string;\n /** Fired whenever the state or city changes. */\n onChange?: (selection: BrazilStateCitySelection) => void;\n /** Label for the state select. Default: `\"Estado\"`. */\n stateLabel?: string;\n /** Label for the city select. Default: `\"Cidade\"`. */\n cityLabel?: string;\n /** Placeholder for the state select. Default: `\"Selecione o estado\"`. */\n statePlaceholder?: string;\n /** Placeholder for the city select. Default: `\"Selecione a cidade\"`. */\n cityPlaceholder?: string;\n /** Disable both selects. */\n disabled?: boolean;\n /** Layout of the two selects. Default: `\"row\"`. */\n layout?: \"row\" | \"column\";\n}\n\n/**\n * Cascading Estado → Cidade selector backed by the bundled BR locations data —\n * pick a state and the city list narrows to that UF's municipalities. No\n * network, no external API. Complements {@link BrazilMap} (wire the map's\n * `onSelect` to drive the same UF).\n *\n * @example\n * <BrazilStateCitySelect onChange={({ uf, city }) => console.log(uf, city)} />\n */\nexport function BrazilStateCitySelect({\n defaultUf,\n defaultCity,\n onChange,\n stateLabel = \"Estado\",\n cityLabel = \"Cidade\",\n statePlaceholder = \"Selecione o estado\",\n cityPlaceholder = \"Selecione a cidade\",\n disabled = false,\n layout = \"row\",\n}: BrazilStateCitySelectProps): ReactElement {\n const [uf, setUf] = useState<UF | null>(defaultUf ?? null);\n const [city, setCity] = useState<string | null>(defaultCity ?? null);\n\n const stateOptions = useMemo(\n () => listStates().map((s) => ({ value: s.uf, label: s.name })),\n [],\n );\n const cityOptions = useMemo(() => (uf ? cityChoices(uf) : []), [uf]);\n\n function handleUf(next: string): void {\n const nextUf = normalizeUf(next);\n setUf(nextUf);\n setCity(null);\n onChange?.({ uf: nextUf, city: null });\n }\n\n function handleCity(next: string): void {\n const nextCity = next || null;\n setCity(nextCity);\n onChange?.({ uf, city: nextCity });\n }\n\n return (\n <div\n style={{\n display: \"flex\",\n flexDirection: layout === \"row\" ? \"row\" : \"column\",\n gap: \"var(--tempest-space-3, 12px)\",\n flexWrap: \"wrap\",\n }}\n >\n <Select\n label={stateLabel}\n placeholder={statePlaceholder}\n options={stateOptions}\n value={uf ?? \"\"}\n disabled={disabled}\n onChange={(e) => handleUf(e.target.value)}\n style={{ minWidth: 0 }}\n />\n <Select\n label={cityLabel}\n placeholder={cityPlaceholder}\n options={cityOptions}\n value={city ?? \"\"}\n disabled={disabled || !uf}\n onChange={(e) => handleCity(e.target.value)}\n style={{ minWidth: 0 }}\n />\n </div>\n );\n}\n"],"mappings":"sIAiDA,SAAgB,EAAsB,CAClC,YACA,cACA,WACA,aAAa,SACb,YAAY,SACZ,mBAAmB,qBACnB,kBAAkB,qBAClB,WAAW,GACX,SAAS,OACgC,CACzC,GAAM,CAAC,EAAI,IAAA,EAAS,EAAA,SAAA,CAAoB,GAAa,IAAI,EACnD,CAAC,EAAM,IAAA,EAAW,EAAA,SAAA,CAAwB,GAAe,IAAI,EAE7D,GAAA,EAAe,EAAA,QAAA,KACX,EAAA,WAAW,CAAC,CAAC,IAAK,IAAO,CAAE,MAAO,EAAE,GAAI,MAAO,EAAE,IAAK,EAAE,EAC9D,CAAC,CACL,EACM,GAAA,EAAc,EAAA,QAAA,KAAe,EAAK,EAAA,YAAY,CAAE,EAAI,CAAC,EAAI,CAAC,CAAE,CAAC,EAEnE,SAAS,EAAS,EAAoB,CAClC,IAAM,EAAS,EAAA,YAAY,CAAI,EAC/B,EAAM,CAAM,EACZ,EAAQ,IAAI,EACZ,IAAW,CAAE,GAAI,EAAQ,KAAM,IAAK,CAAC,CACzC,CAEA,SAAS,EAAW,EAAoB,CACpC,IAAM,EAAW,GAAQ,KACzB,EAAQ,CAAQ,EAChB,IAAW,CAAE,KAAI,KAAM,CAAS,CAAC,CACrC,CAEA,OACI,EAAA,EAAA,KAAA,CAAC,MAAD,CACI,MAAO,CACH,QAAS,OACT,cAAe,IAAW,MAAQ,MAAQ,SAC1C,IAAK,+BACL,SAAU,MACd,EANJ,SAAA,EAQI,EAAA,EAAA,IAAA,CAAC,EAAA,OAAD,CACI,MAAO,EACP,YAAa,EACb,QAAS,EACT,MAAO,GAAM,GACH,WACV,SAAW,GAAM,EAAS,EAAE,OAAO,KAAK,EACxC,MAAO,CAAE,SAAU,CAAE,CACxB,CAAA,GACD,EAAA,EAAA,IAAA,CAAC,EAAA,OAAD,CACI,MAAO,EACP,YAAa,EACb,QAAS,EACT,MAAO,GAAQ,GACf,SAAU,GAAY,CAAC,EACvB,SAAW,GAAM,EAAW,EAAE,OAAO,KAAK,EAC1C,MAAO,CAAE,SAAU,CAAE,CACxB,CAAA,CACA,GAEb"}
@@ -1 +1 @@
1
- {"version":3,"file":"BrazilStateCitySelect.js","names":[],"sources":["../../src/br/BrazilStateCitySelect.tsx"],"sourcesContent":["/**\n * @tempest-limits props-count — two dependent selects in one control, so every knob\n * comes in pairs: stateLabel and cityLabel, statePlaceholder and cityPlaceholder,\n * defaultUf and defaultCity. The pairing is the feature — the city list is derived\n * from the chosen UF, which is exactly what a caller wiring two separate selects has\n * to reimplement.\n */\nimport { useMemo, useState, type ReactElement } from \"react\";\nimport { Select } from \"@/components/Select\";\nimport { cityChoices, listStates, normalizeUf, type UF } from \"./locations\";\n\n/** Current selection emitted by {@link BrazilStateCitySelect}. */\nexport interface BrazilStateCitySelection {\n /** Selected federative unit, or `null` when none. */\n uf: UF | null;\n /** Selected city, or `null` when none. */\n city: string | null;\n}\n\nexport interface BrazilStateCitySelectProps {\n /** Pre-selected UF (uncontrolled initial value). */\n defaultUf?: UF;\n /** Pre-selected city (uncontrolled initial value). */\n defaultCity?: string;\n /** Fired whenever the state or city changes. */\n onChange?: (selection: BrazilStateCitySelection) => void;\n /** Label for the state select. Default: `\"Estado\"`. */\n stateLabel?: string;\n /** Label for the city select. Default: `\"Cidade\"`. */\n cityLabel?: string;\n /** Placeholder for the state select. Default: `\"Selecione o estado\"`. */\n statePlaceholder?: string;\n /** Placeholder for the city select. Default: `\"Selecione a cidade\"`. */\n cityPlaceholder?: string;\n /** Disable both selects. */\n disabled?: boolean;\n /** Layout of the two selects. Default: `\"row\"`. */\n layout?: \"row\" | \"column\";\n}\n\n/**\n * Cascading Estado → Cidade selector backed by the bundled BR locations data —\n * pick a state and the city list narrows to that UF's municipalities. No\n * network, no external API. Complements {@link BrazilMap} (wire the map's\n * `onSelect` to drive the same UF).\n *\n * @example\n * <BrazilStateCitySelect onChange={({ uf, city }) => console.log(uf, city)} />\n */\nexport function BrazilStateCitySelect({\n defaultUf,\n defaultCity,\n onChange,\n stateLabel = \"Estado\",\n cityLabel = \"Cidade\",\n statePlaceholder = \"Selecione o estado\",\n cityPlaceholder = \"Selecione a cidade\",\n disabled = false,\n layout = \"row\",\n}: BrazilStateCitySelectProps): ReactElement {\n const [uf, setUf] = useState<UF | null>(defaultUf ?? null);\n const [city, setCity] = useState<string | null>(defaultCity ?? null);\n\n const stateOptions = useMemo(\n () => listStates().map((s) => ({ value: s.uf, label: s.name })),\n [],\n );\n const cityOptions = useMemo(() => (uf ? cityChoices(uf) : []), [uf]);\n\n function handleUf(next: string): void {\n const nextUf = normalizeUf(next);\n setUf(nextUf);\n setCity(null);\n onChange?.({ uf: nextUf, city: null });\n }\n\n function handleCity(next: string): void {\n const nextCity = next || null;\n setCity(nextCity);\n onChange?.({ uf, city: nextCity });\n }\n\n return (\n <div\n style={{\n display: \"flex\",\n flexDirection: layout === \"row\" ? \"row\" : \"column\",\n gap: \"var(--tempest-space-3, 12px)\",\n flexWrap: \"wrap\",\n }}\n >\n <Select\n label={stateLabel}\n placeholder={statePlaceholder}\n options={stateOptions}\n value={uf ?? \"\"}\n disabled={disabled}\n onChange={(e) => handleUf(e.target.value)}\n style={{ minWidth: 0 }}\n />\n <Select\n label={cityLabel}\n placeholder={cityPlaceholder}\n options={cityOptions}\n value={city ?? \"\"}\n disabled={disabled || !uf}\n onChange={(e) => handleCity(e.target.value)}\n style={{ minWidth: 0 }}\n />\n </div>\n );\n}\n"],"mappings":";;;;;AAiDA,SAAgB,EAAsB,EAClC,cACA,gBACA,aACA,gBAAa,UACb,eAAY,UACZ,sBAAmB,sBACnB,qBAAkB,sBAClB,cAAW,IACX,YAAS,SACgC;CACzC,IAAM,CAAC,GAAI,KAAS,EAAoB,KAAa,IAAI,GACnD,CAAC,GAAM,KAAW,EAAwB,KAAe,IAAI,GAE7D,IAAe,QACX,EAAW,CAAC,CAAC,KAAK,OAAO;EAAE,OAAO,EAAE;EAAI,OAAO,EAAE;CAAK,EAAE,GAC9D,CAAC,CACL,GACM,IAAc,QAAe,IAAK,EAAY,CAAE,IAAI,CAAC,GAAI,CAAC,CAAE,CAAC;CAEnE,SAAS,EAAS,GAAoB;EAClC,IAAM,IAAS,EAAY,CAAI;EAG/B,AAFA,EAAM,CAAM,GACZ,EAAQ,IAAI,GACZ,IAAW;GAAE,IAAI;GAAQ,MAAM;EAAK,CAAC;CACzC;CAEA,SAAS,EAAW,GAAoB;EACpC,IAAM,IAAW,KAAQ;EAEzB,AADA,EAAQ,CAAQ,GAChB,IAAW;GAAE;GAAI,MAAM;EAAS,CAAC;CACrC;CAEA,OACI,kBAAC,OAAD;EACI,OAAO;GACH,SAAS;GACT,eAAe,MAAW,QAAQ,QAAQ;GAC1C,KAAK;GACL,UAAU;EACd;YANJ,CAQI,kBAAC,GAAD;GACI,OAAO;GACP,aAAa;GACb,SAAS;GACT,OAAO,KAAM;GACH;GACV,WAAW,MAAM,EAAS,EAAE,OAAO,KAAK;GACxC,OAAO,EAAE,UAAU,EAAE;EACxB,CAAA,GACD,kBAAC,GAAD;GACI,OAAO;GACP,aAAa;GACb,SAAS;GACT,OAAO,KAAQ;GACf,UAAU,KAAY,CAAC;GACvB,WAAW,MAAM,EAAW,EAAE,OAAO,KAAK;GAC1C,OAAO,EAAE,UAAU,EAAE;EACxB,CAAA,CACA;;AAEb"}
1
+ {"version":3,"file":"BrazilStateCitySelect.js","names":[],"sources":["../../src/br/BrazilStateCitySelect.tsx"],"sourcesContent":["/**\n * @tempest-limits props-count — two dependent selects in one control, so every knob\n * comes in pairs: stateLabel and cityLabel, statePlaceholder and cityPlaceholder,\n * defaultUf and defaultCity. The pairing is the feature — the city list is derived\n * from the chosen UF, which is exactly what a caller wiring two separate selects has\n * to reimplement.\n */\nimport { useMemo, useState, type ReactElement } from \"react\";\nimport { Select } from \"@/components/Select\";\nimport { cityChoices, listStates, normalizeUf, type UF } from \"./locations\";\n\n/** Current selection emitted by {@link BrazilStateCitySelect}. */\nexport interface BrazilStateCitySelection {\n /** Selected federative unit, or `null` when none. */\n uf: UF | null;\n /** Selected city, or `null` when none. */\n city: string | null;\n}\n\nexport interface BrazilStateCitySelectProps {\n /** Pre-selected UF (uncontrolled initial value). */\n defaultUf?: UF;\n /** Pre-selected city (uncontrolled initial value). */\n defaultCity?: string;\n /** Fired whenever the state or city changes. */\n onChange?: (selection: BrazilStateCitySelection) => void;\n /** Label for the state select. Default: `\"Estado\"`. */\n stateLabel?: string;\n /** Label for the city select. Default: `\"Cidade\"`. */\n cityLabel?: string;\n /** Placeholder for the state select. Default: `\"Selecione o estado\"`. */\n statePlaceholder?: string;\n /** Placeholder for the city select. Default: `\"Selecione a cidade\"`. */\n cityPlaceholder?: string;\n /** Disable both selects. */\n disabled?: boolean;\n /** Layout of the two selects. Default: `\"row\"`. */\n layout?: \"row\" | \"column\";\n}\n\n/**\n * Cascading Estado → Cidade selector backed by the bundled BR locations data —\n * pick a state and the city list narrows to that UF's municipalities. No\n * network, no external API. Complements {@link BrazilMap} (wire the map's\n * `onSelect` to drive the same UF).\n *\n * @example\n * <BrazilStateCitySelect onChange={({ uf, city }) => console.log(uf, city)} />\n */\nexport function BrazilStateCitySelect({\n defaultUf,\n defaultCity,\n onChange,\n stateLabel = \"Estado\",\n cityLabel = \"Cidade\",\n statePlaceholder = \"Selecione o estado\",\n cityPlaceholder = \"Selecione a cidade\",\n disabled = false,\n layout = \"row\",\n}: BrazilStateCitySelectProps): ReactElement {\n const [uf, setUf] = useState<UF | null>(defaultUf ?? null);\n const [city, setCity] = useState<string | null>(defaultCity ?? null);\n\n const stateOptions = useMemo(\n () => listStates().map((s) => ({ value: s.uf, label: s.name })),\n [],\n );\n const cityOptions = useMemo(() => (uf ? cityChoices(uf) : []), [uf]);\n\n function handleUf(next: string): void {\n const nextUf = normalizeUf(next);\n setUf(nextUf);\n setCity(null);\n onChange?.({ uf: nextUf, city: null });\n }\n\n function handleCity(next: string): void {\n const nextCity = next || null;\n setCity(nextCity);\n onChange?.({ uf, city: nextCity });\n }\n\n return (\n <div\n style={{\n display: \"flex\",\n flexDirection: layout === \"row\" ? \"row\" : \"column\",\n gap: \"var(--tempest-space-3, 12px)\",\n flexWrap: \"wrap\",\n }}\n >\n <Select\n label={stateLabel}\n placeholder={statePlaceholder}\n options={stateOptions}\n value={uf ?? \"\"}\n disabled={disabled}\n onChange={(e) => handleUf(e.target.value)}\n style={{ minWidth: 0 }}\n />\n <Select\n label={cityLabel}\n placeholder={cityPlaceholder}\n options={cityOptions}\n value={city ?? \"\"}\n disabled={disabled || !uf}\n onChange={(e) => handleCity(e.target.value)}\n style={{ minWidth: 0 }}\n />\n </div>\n );\n}\n"],"mappings":";;;;;AAiDA,SAAgB,EAAsB,EAClC,cACA,gBACA,aACA,gBAAa,UACb,eAAY,UACZ,sBAAmB,sBACnB,qBAAkB,sBAClB,cAAW,IACX,YAAS,SACgC;CACzC,IAAM,CAAC,GAAI,KAAS,EAAoB,KAAa,IAAI,GACnD,CAAC,GAAM,KAAW,EAAwB,KAAe,IAAI,GAE7D,IAAe,QACX,EAAW,CAAC,CAAC,KAAK,OAAO;EAAE,OAAO,EAAE;EAAI,OAAO,EAAE;CAAK,EAAE,GAC9D,CAAC,CACL,GACM,IAAc,QAAe,IAAK,EAAY,CAAE,IAAI,CAAC,GAAI,CAAC,CAAE,CAAC;CAEnE,SAAS,EAAS,GAAoB;EAClC,IAAM,IAAS,EAAY,CAAI;EAG/B,AAFA,EAAM,CAAM,GACZ,EAAQ,IAAI,GACZ,IAAW;GAAE,IAAI;GAAQ,MAAM;EAAK,CAAC;CACzC;CAEA,SAAS,EAAW,GAAoB;EACpC,IAAM,IAAW,KAAQ;EAEzB,AADA,EAAQ,CAAQ,GAChB,IAAW;GAAE;GAAI,MAAM;EAAS,CAAC;CACrC;CAEA,OACI,kBAAC,OAAD;EACI,OAAO;GACH,SAAS;GACT,eAAe,MAAW,QAAQ,QAAQ;GAC1C,KAAK;GACL,UAAU;EACd;EANJ,UAAA,CAQI,kBAAC,GAAD;GACI,OAAO;GACP,aAAa;GACb,SAAS;GACT,OAAO,KAAM;GACH;GACV,WAAW,MAAM,EAAS,EAAE,OAAO,KAAK;GACxC,OAAO,EAAE,UAAU,EAAE;EACxB,CAAA,GACD,kBAAC,GAAD;GACI,OAAO;GACP,aAAa;GACb,SAAS;GACT,OAAO,KAAQ;GACf,UAAU,KAAY,CAAC;GACvB,WAAW,MAAM,EAAW,EAAE,OAAO,KAAK;GAC1C,OAAO,EAAE,UAAU,EAAE;EACxB,CAAA,CACA;;AAEb"}
@@ -1,2 +1,2 @@
1
- const e=require("../utils/cn.cjs"),t=require("../geo/projection.cjs"),n=require("./locations.cjs"),r=require("./state-geo.cjs"),i=require("./BrazilMap.module.cjs"),a=require("./MapMarkers.cjs"),o=require("./MapTooltip.cjs"),s=require("./use-map-zoom.cjs"),c=require("./svg-utils.cjs"),l=require("./use-map-hover.cjs");let u=require("react"),d=require("react/jsx-runtime");function f({uf:f,selected:p,onSelect:m,values:h,minColor:g=`#dbeafe`,maxColor:_=`#2563eb`,colorScale:v,height:y=440,padding:b=12,showLabels:x=!1,label:S,loadingContent:C,showTooltip:w=!0,renderTooltip:T,markers:E,onMarkerClick:D,zoomable:O=!1,className:k,style:A,...j}){let M=(0,u.useRef)(null),[N,P]=(0,u.useState)(600),[F,I]=(0,u.useState)(null),L=s.useMapZoom(O),{hover:R,onMove:z,onLeave:B}=l.useMapHover(M);(0,u.useEffect)(()=>{let e=!0;return I(null),r.loadStateMunicipalities(f).then(t=>{e&&I(t)}),()=>{e=!1}},[f]),(0,u.useEffect)(()=>{let e=M.current;if(!e||typeof ResizeObserver>`u`)return;let t=new ResizeObserver(e=>{let t=e[0]?.contentRect.width;t&&t>0&&P(t)});return t.observe(e),()=>t.disconnect()},[]);let V=(0,u.useMemo)(()=>p?new Set(Array.isArray(p)?p:[p]):new Set,[p]),H=(0,u.useMemo)(()=>{if(!h)return null;let e=Object.values(h).filter(e=>typeof e==`number`);return e.length===0?null:[Math.min(...e),Math.max(...e)]},[h]),U=(0,u.useMemo)(()=>{if(!F)return null;let e=t.fitProjection(c.geometriesBounds(F.features.map(e=>e.geometry)),N,y,{padding:b});return{projection:e,items:F.features.map(t=>({id:t.properties.id,name:t.properties.name,d:c.geometryPath(t.geometry,e),centroid:c.geometryCentroid(t.geometry,e)}))}},[F,N,y,b]);function W(e,t){let n=h?.[e]??h?.[t];if(typeof n!=`number`)return;if(v)return v(n);if(!H)return;let[r,i]=H;return c.lerpColor(g,_,i===r?1:(n-r)/(i-r))}let G=(0,u.useMemo)(()=>U?U.items.map(t=>{let n=V.has(t.id)||V.has(t.name),r=n?void 0:W(t.id,t.name);return(0,d.jsx)(`path`,{className:e.cn(i.default.state,n&&i.default.selected),d:t.d,fillRule:`evenodd`,style:r?{fill:r}:void 0,"data-city-id":t.id,"data-city":t.name,tabIndex:m?0:void 0,role:m?`button`:void 0,"aria-label":t.name,"aria-pressed":m?n:void 0,onClick:m?()=>m({id:t.id,name:t.name}):void 0,onMouseMove:w?e=>z({id:t.id,name:t.name},e):void 0,onMouseLeave:w?B:void 0,onKeyDown:m?e=>{(e.key===`Enter`||e.key===` `)&&(e.preventDefault(),m({id:t.id,name:t.name}))}:void 0},t.id)}):null,[U,V,h,v,g,_,m,w,z,B]),K=S??`Municípios de ${n.getState(f)?.name??f}`;return(0,d.jsxs)(`div`,{ref:M,className:e.cn(i.default.map,k),style:{height:y,...A},role:`group`,"aria-label":K,...j,children:[U?(0,d.jsx)(`svg`,{className:i.default.svg,width:`100%`,height:y,viewBox:`0 0 ${N} ${y}`,preserveAspectRatio:`xMidYMid meet`,...L.handlers,children:(0,d.jsxs)(`g`,{transform:L.transform||void 0,children:[G,x&&U.items.map(e=>(0,d.jsx)(`text`,{className:i.default.label,x:e.centroid.x,y:e.centroid.y,textAnchor:`middle`,dominantBaseline:`central`,children:e.name},`label-${e.id}`)),E&&E.length>0&&(0,d.jsx)(a.MapMarkers,{projection:U.projection,markers:E,onMarkerClick:D})]})}):(0,d.jsx)(`div`,{className:i.default.loading,children:C??`Carregando municípios…`}),L.isTransformed&&(0,d.jsx)(`button`,{type:`button`,className:i.default.zoomReset,onClick:L.reset,children:`Reset`}),w&&R&&(0,d.jsx)(o.MapTooltip,{x:R.x,y:R.y,children:T?T({...R.item,value:h?.[R.item.id]??h?.[R.item.name]}):(0,d.jsxs)(d.Fragment,{children:[(0,d.jsx)(`div`,{className:i.default.tooltipTitle,children:R.item.name}),(0,d.jsxs)(`div`,{className:i.default.tooltipMeta,children:[`IBGE `,R.item.id,(()=>{let e=h?.[R.item.id]??h?.[R.item.name];return typeof e==`number`?` · ${e}`:``})()]})]})})]})}exports.BrazilStateMap=f;
1
+ const e=require("../utils/cn.cjs"),t=require("../geo/projection.cjs"),n=require("./locations.cjs"),r=require("./state-geo.cjs"),i=require("./BrazilMap.module.cjs"),a=require("./MapMarkers.cjs"),o=require("./MapTooltip.cjs"),s=require("./use-map-zoom.cjs"),c=require("./svg-utils.cjs"),l=require("./use-map-hover.cjs");let u=require("react"),d=require("react/jsx-runtime");function f({uf:f,selected:p,onSelect:m,values:h,minColor:g=`#dbeafe`,maxColor:_=`#2563eb`,colorScale:v,height:y=440,padding:b=12,showLabels:x=!1,label:S,loadingContent:C,showTooltip:w=!0,renderTooltip:T,markers:E,onMarkerClick:D,zoomable:O=!1,className:k,style:A,...j}){let M=(0,u.useRef)(null),[N,P]=(0,u.useState)(600),[F,I]=(0,u.useState)(null),L=s.useMapZoom(O),{hover:R,onMove:z,onLeave:B}=l.useMapHover(M);(0,u.useEffect)(()=>{let e=!0;return I(null),r.loadStateMunicipalities(f).then(t=>{e&&I(t)}),()=>{e=!1}},[f]),(0,u.useEffect)(()=>{let e=M.current;if(!e||typeof ResizeObserver>`u`)return;let t=new ResizeObserver(e=>{let t=e[0]?.contentRect.width;t&&t>0&&P(t)});return t.observe(e),()=>t.disconnect()},[]);let V=(0,u.useMemo)(()=>p?new Set(Array.isArray(p)?p:[p]):new Set,[p]),H=(0,u.useMemo)(()=>{if(!h)return null;let e=Object.values(h).filter(e=>typeof e==`number`);return e.length===0?null:[Math.min(...e),Math.max(...e)]},[h]),U=(0,u.useMemo)(()=>{if(!F)return null;let e=t.fitProjection(c.geometriesBounds(F.features.map(e=>e.geometry)),N,y,{padding:b});return{projection:e,items:F.features.map(t=>({id:t.properties.id,name:t.properties.name,d:c.geometryPath(t.geometry,e),centroid:c.geometryCentroid(t.geometry,e)}))}},[F,N,y,b]);function W(e,t){let n=h?.[e]??h?.[t];if(typeof n!=`number`)return;if(v)return v(n);if(!H)return;let[r,i]=H,a=i===r?1:(n-r)/(i-r);return c.lerpColor(g,_,a)}let G=(0,u.useMemo)(()=>U?U.items.map(t=>{let n=V.has(t.id)||V.has(t.name),r=n?void 0:W(t.id,t.name);return(0,d.jsx)(`path`,{className:e.cn(i.default.state,n&&i.default.selected),d:t.d,fillRule:`evenodd`,style:r?{fill:r}:void 0,"data-city-id":t.id,"data-city":t.name,tabIndex:m?0:void 0,role:m?`button`:void 0,"aria-label":t.name,"aria-pressed":m?n:void 0,onClick:m?()=>m({id:t.id,name:t.name}):void 0,onMouseMove:w?e=>z({id:t.id,name:t.name},e):void 0,onMouseLeave:w?B:void 0,onKeyDown:m?e=>{(e.key===`Enter`||e.key===` `)&&(e.preventDefault(),m({id:t.id,name:t.name}))}:void 0},t.id)}):null,[U,V,h,v,g,_,m,w,z,B]),K=S??`Municípios de ${n.getState(f)?.name??f}`;return(0,d.jsxs)(`div`,{ref:M,className:e.cn(i.default.map,k),style:{height:y,...A},role:`group`,"aria-label":K,...j,children:[U?(0,d.jsx)(`svg`,{className:i.default.svg,width:`100%`,height:y,viewBox:`0 0 ${N} ${y}`,preserveAspectRatio:`xMidYMid meet`,...L.handlers,children:(0,d.jsxs)(`g`,{transform:L.transform||void 0,children:[G,x&&U.items.map(e=>(0,d.jsx)(`text`,{className:i.default.label,x:e.centroid.x,y:e.centroid.y,textAnchor:`middle`,dominantBaseline:`central`,children:e.name},`label-${e.id}`)),E&&E.length>0&&(0,d.jsx)(a.MapMarkers,{projection:U.projection,markers:E,onMarkerClick:D})]})}):(0,d.jsx)(`div`,{className:i.default.loading,children:C??`Carregando municípios…`}),L.isTransformed&&(0,d.jsx)(`button`,{type:`button`,className:i.default.zoomReset,onClick:L.reset,children:`Reset`}),w&&R&&(0,d.jsx)(o.MapTooltip,{x:R.x,y:R.y,children:T?T({...R.item,value:h?.[R.item.id]??h?.[R.item.name]}):(0,d.jsxs)(d.Fragment,{children:[(0,d.jsx)(`div`,{className:i.default.tooltipTitle,children:R.item.name}),(0,d.jsxs)(`div`,{className:i.default.tooltipMeta,children:[`IBGE `,R.item.id,(()=>{let e=h?.[R.item.id]??h?.[R.item.name];return typeof e==`number`?` · ${e}`:``})()]})]})})]})}exports.BrazilStateMap=f;
2
2
  //# sourceMappingURL=BrazilStateMap.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"BrazilStateMap.cjs","names":[],"sources":["../../src/br/BrazilStateMap.tsx"],"sourcesContent":["/**\n * @tempest-limits file-lines, props-count, function-lines — the municipality-level\n * twin of BrazilMap, prop for prop, plus the `uf` that selects which state's\n * geometry to load. Splitting it differently from BrazilMap would mean two maps with\n * two shapes for the same job.\n */\nimport { useEffect, useMemo, useRef, useState, type HTMLAttributes, type ReactNode } from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport { fitProjection } from \"@/geo/projection\";\nimport type { GeoMarker } from \"@/geo/types\";\nimport { getState, type UF } from \"./locations\";\nimport { MapMarkers } from \"./MapMarkers\";\nimport { MapTooltip } from \"./MapTooltip\";\nimport type { ColorScale } from \"./scales\";\nimport { useMapZoom } from \"./use-map-zoom\";\nimport { loadStateMunicipalities, type StateMunicipalities } from \"./state-geo\";\nimport { geometriesBounds, geometryCentroid, geometryPath, lerpColor } from \"./svg-utils\";\nimport { useMapHover } from \"./use-map-hover\";\nimport styles from \"./BrazilMap.module.css\";\n\n/** A municipality identified for selection callbacks. */\nexport interface Municipality {\n /** 7-digit IBGE code. */\n id: string;\n /** Municipality name. */\n name: string;\n}\n\n/** Data passed to a {@link BrazilStateMapProps.renderTooltip} callback. */\nexport interface BrazilStateMapTooltipData extends Municipality {\n /** Choropleth value for this municipality, if `values` was provided. */\n value?: number;\n}\n\nexport interface BrazilStateMapProps extends Omit<HTMLAttributes<HTMLDivElement>, \"onSelect\"> {\n /** Federative unit to draw (required). */\n uf: UF;\n /** Selected municipality — matched by IBGE `id` or by `name`. Accepts many. */\n selected?: string | readonly string[] | null;\n /** Fired when a municipality is clicked. */\n onSelect?: (municipality: Municipality) => void;\n /**\n * Choropleth values keyed by municipality `id` **or** `name`. When set, each\n * municipality is tinted between `minColor` and `maxColor`.\n */\n values?: Record<string, number>;\n /** Choropleth low-end color (2-color linear ramp). Default: a light primary tint. */\n minColor?: string;\n /** Choropleth high-end color (2-color linear ramp). Default: the primary token. */\n maxColor?: string;\n /**\n * Custom value→color scale (from `sequentialScale`/`quantizeScale`). Takes\n * precedence over `minColor`/`maxColor`. Pair with a `<MapLegend>`.\n */\n colorScale?: ColorScale;\n /** Viewport height in pixels. Default: `440`. */\n height?: number;\n /** Inner padding in pixels. Default: `12`. */\n padding?: number;\n /**\n * Render each municipality name at its centroid. Off by default — a state\n * can have hundreds of municipalities and labels overlap badly.\n */\n showLabels?: boolean;\n /** Accessible label. Default: derived from the state name. */\n label?: string;\n /** Content while the state geometry is loading. */\n loadingContent?: ReactNode;\n /** Show a floating tooltip (name + IBGE code + value) on hover. Default: `true`. */\n showTooltip?: boolean;\n /** Override the default tooltip content. */\n renderTooltip?: (data: BrazilStateMapTooltipData) => ReactNode;\n /** Point markers to overlay on the state (e.g. addresses, POIs). */\n markers?: readonly GeoMarker[];\n /** Fired when a marker is clicked. */\n onMarkerClick?: (marker: GeoMarker, index: number) => void;\n /** Enable wheel-zoom + drag-pan (double-click resets). Default: `false`. */\n zoomable?: boolean;\n}\n\n/**\n * Clickable submap of a single Brazilian state showing **all its\n * municipalities** as SVG paths — no external tiles or paid API. The state's\n * geometry (~40-70 KB gzip) is loaded lazily per UF, so switching states fetches\n * only what is shown.\n *\n * @example\n * const [city, setCity] = useState<string | null>(null);\n * <BrazilStateMap uf=\"SP\" selected={city} onSelect={(m) => setCity(m.name)} />\n *\n * @example\n * // Choropleth of a metric by municipality name\n * <BrazilStateMap uf=\"RJ\" values={{ \"Rio de Janeiro\": 100, Niterói: 40 }} />\n */\nexport function BrazilStateMap({\n uf,\n selected,\n onSelect,\n values,\n minColor = \"#dbeafe\",\n maxColor = \"#2563eb\",\n colorScale,\n height = 440,\n padding = 12,\n showLabels = false,\n label,\n loadingContent,\n showTooltip = true,\n renderTooltip,\n markers,\n onMarkerClick,\n zoomable = false,\n className,\n style,\n ...rest\n}: BrazilStateMapProps) {\n const containerRef = useRef<HTMLDivElement>(null);\n const [width, setWidth] = useState<number>(600);\n const [collection, setCollection] = useState<StateMunicipalities | null>(null);\n const mapZoom = useMapZoom(zoomable);\n const { hover, onMove, onLeave } = useMapHover<Municipality>(containerRef);\n\n useEffect(() => {\n let active = true;\n setCollection(null);\n void loadStateMunicipalities(uf).then((data) => {\n if (active) setCollection(data);\n });\n return () => {\n active = false;\n };\n }, [uf]);\n\n useEffect(() => {\n const node = containerRef.current;\n if (!node || typeof ResizeObserver === \"undefined\") return;\n const observer = new ResizeObserver((entries) => {\n const measured = entries[0]?.contentRect.width;\n if (measured && measured > 0) setWidth(measured);\n });\n observer.observe(node);\n return () => observer.disconnect();\n }, []);\n\n const selectedSet = useMemo<Set<string>>(() => {\n if (!selected) return new Set();\n return new Set(Array.isArray(selected) ? selected : [selected as string]);\n }, [selected]);\n\n const valueRange = useMemo<[number, number] | null>(() => {\n if (!values) return null;\n const nums = Object.values(values).filter((v): v is number => typeof v === \"number\");\n if (nums.length === 0) return null;\n return [Math.min(...nums), Math.max(...nums)];\n }, [values]);\n\n const shapes = useMemo(() => {\n if (!collection) return null;\n const projection = fitProjection(\n geometriesBounds(collection.features.map((f) => f.geometry)),\n width,\n height,\n { padding },\n );\n const items = collection.features.map((feature) => ({\n id: feature.properties.id,\n name: feature.properties.name,\n d: geometryPath(feature.geometry, projection),\n centroid: geometryCentroid(feature.geometry, projection),\n }));\n return { projection, items };\n }, [collection, width, height, padding]);\n\n function fillFor(id: string, name: string): string | undefined {\n const v = values?.[id] ?? values?.[name];\n if (typeof v !== \"number\") return undefined;\n if (colorScale) return colorScale(v);\n if (!valueRange) return undefined;\n const [min, max] = valueRange;\n const t = max === min ? 1 : (v - min) / (max - min);\n return lerpColor(minColor, maxColor, t);\n }\n\n // Memoized so hover re-renders don't rebuild hundreds of municipality paths.\n const cityEls = useMemo(() => {\n if (!shapes) return null;\n return shapes.items.map((shape) => {\n const isSelected = selectedSet.has(shape.id) || selectedSet.has(shape.name);\n const fillColor = isSelected ? undefined : fillFor(shape.id, shape.name);\n return (\n <path\n key={shape.id}\n className={cn(styles.state, isSelected && styles.selected)}\n d={shape.d}\n fillRule=\"evenodd\"\n style={fillColor ? { fill: fillColor } : undefined}\n data-city-id={shape.id}\n data-city={shape.name}\n tabIndex={onSelect ? 0 : undefined}\n role={onSelect ? \"button\" : undefined}\n aria-label={shape.name}\n aria-pressed={onSelect ? isSelected : undefined}\n onClick={\n onSelect ? () => onSelect({ id: shape.id, name: shape.name }) : undefined\n }\n onMouseMove={\n showTooltip\n ? (e) => onMove({ id: shape.id, name: shape.name }, e)\n : undefined\n }\n onMouseLeave={showTooltip ? onLeave : undefined}\n onKeyDown={\n onSelect\n ? (e) => {\n if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n onSelect({ id: shape.id, name: shape.name });\n }\n }\n : undefined\n }\n />\n );\n });\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [\n shapes,\n selectedSet,\n values,\n colorScale,\n minColor,\n maxColor,\n onSelect,\n showTooltip,\n onMove,\n onLeave,\n ]);\n\n const resolvedLabel = label ?? `Municípios de ${getState(uf)?.name ?? uf}`;\n\n return (\n <div\n ref={containerRef}\n className={cn(styles.map, className)}\n style={{ height, ...style }}\n role=\"group\"\n aria-label={resolvedLabel}\n {...rest}\n >\n {!shapes ? (\n <div className={styles.loading}>{loadingContent ?? \"Carregando municípios…\"}</div>\n ) : (\n <svg\n className={styles.svg}\n width=\"100%\"\n height={height}\n viewBox={`0 0 ${width} ${height}`}\n preserveAspectRatio=\"xMidYMid meet\"\n {...mapZoom.handlers}\n >\n <g transform={mapZoom.transform || undefined}>\n {cityEls}\n\n {showLabels &&\n shapes.items.map((shape) => (\n <text\n key={`label-${shape.id}`}\n className={styles.label}\n x={shape.centroid.x}\n y={shape.centroid.y}\n textAnchor=\"middle\"\n dominantBaseline=\"central\"\n >\n {shape.name}\n </text>\n ))}\n\n {markers && markers.length > 0 && (\n <MapMarkers\n projection={shapes.projection}\n markers={markers}\n onMarkerClick={onMarkerClick}\n />\n )}\n </g>\n </svg>\n )}\n\n {mapZoom.isTransformed && (\n <button type=\"button\" className={styles.zoomReset} onClick={mapZoom.reset}>\n Reset\n </button>\n )}\n\n {showTooltip && hover && (\n <MapTooltip x={hover.x} y={hover.y}>\n {renderTooltip ? (\n renderTooltip({\n ...hover.item,\n value: values?.[hover.item.id] ?? values?.[hover.item.name],\n })\n ) : (\n <>\n <div className={styles.tooltipTitle}>{hover.item.name}</div>\n <div className={styles.tooltipMeta}>\n IBGE {hover.item.id}\n {(() => {\n const v = values?.[hover.item.id] ?? values?.[hover.item.name];\n return typeof v === \"number\" ? ` · ${v}` : \"\";\n })()}\n </div>\n </>\n )}\n </MapTooltip>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"BrazilStateMap.cjs","names":[],"sources":["../../src/br/BrazilStateMap.tsx"],"sourcesContent":["/**\n * @tempest-limits file-lines, props-count, function-lines — the municipality-level\n * twin of BrazilMap, prop for prop, plus the `uf` that selects which state's\n * geometry to load. Splitting it differently from BrazilMap would mean two maps with\n * two shapes for the same job.\n */\nimport { useEffect, useMemo, useRef, useState, type HTMLAttributes, type ReactNode } from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport { fitProjection } from \"@/geo/projection\";\nimport type { GeoMarker } from \"@/geo/types\";\nimport { getState, type UF } from \"./locations\";\nimport { MapMarkers } from \"./MapMarkers\";\nimport { MapTooltip } from \"./MapTooltip\";\nimport type { ColorScale } from \"./scales\";\nimport { useMapZoom } from \"./use-map-zoom\";\nimport { loadStateMunicipalities, type StateMunicipalities } from \"./state-geo\";\nimport { geometriesBounds, geometryCentroid, geometryPath, lerpColor } from \"./svg-utils\";\nimport { useMapHover } from \"./use-map-hover\";\nimport styles from \"./BrazilMap.module.css\";\n\n/** A municipality identified for selection callbacks. */\nexport interface Municipality {\n /** 7-digit IBGE code. */\n id: string;\n /** Municipality name. */\n name: string;\n}\n\n/** Data passed to a {@link BrazilStateMapProps.renderTooltip} callback. */\nexport interface BrazilStateMapTooltipData extends Municipality {\n /** Choropleth value for this municipality, if `values` was provided. */\n value?: number;\n}\n\nexport interface BrazilStateMapProps extends Omit<HTMLAttributes<HTMLDivElement>, \"onSelect\"> {\n /** Federative unit to draw (required). */\n uf: UF;\n /** Selected municipality — matched by IBGE `id` or by `name`. Accepts many. */\n selected?: string | readonly string[] | null;\n /** Fired when a municipality is clicked. */\n onSelect?: (municipality: Municipality) => void;\n /**\n * Choropleth values keyed by municipality `id` **or** `name`. When set, each\n * municipality is tinted between `minColor` and `maxColor`.\n */\n values?: Record<string, number>;\n /** Choropleth low-end color (2-color linear ramp). Default: a light primary tint. */\n minColor?: string;\n /** Choropleth high-end color (2-color linear ramp). Default: the primary token. */\n maxColor?: string;\n /**\n * Custom value→color scale (from `sequentialScale`/`quantizeScale`). Takes\n * precedence over `minColor`/`maxColor`. Pair with a `<MapLegend>`.\n */\n colorScale?: ColorScale;\n /** Viewport height in pixels. Default: `440`. */\n height?: number;\n /** Inner padding in pixels. Default: `12`. */\n padding?: number;\n /**\n * Render each municipality name at its centroid. Off by default — a state\n * can have hundreds of municipalities and labels overlap badly.\n */\n showLabels?: boolean;\n /** Accessible label. Default: derived from the state name. */\n label?: string;\n /** Content while the state geometry is loading. */\n loadingContent?: ReactNode;\n /** Show a floating tooltip (name + IBGE code + value) on hover. Default: `true`. */\n showTooltip?: boolean;\n /** Override the default tooltip content. */\n renderTooltip?: (data: BrazilStateMapTooltipData) => ReactNode;\n /** Point markers to overlay on the state (e.g. addresses, POIs). */\n markers?: readonly GeoMarker[];\n /** Fired when a marker is clicked. */\n onMarkerClick?: (marker: GeoMarker, index: number) => void;\n /** Enable wheel-zoom + drag-pan (double-click resets). Default: `false`. */\n zoomable?: boolean;\n}\n\n/**\n * Clickable submap of a single Brazilian state showing **all its\n * municipalities** as SVG paths — no external tiles or paid API. The state's\n * geometry (~40-70 KB gzip) is loaded lazily per UF, so switching states fetches\n * only what is shown.\n *\n * @example\n * const [city, setCity] = useState<string | null>(null);\n * <BrazilStateMap uf=\"SP\" selected={city} onSelect={(m) => setCity(m.name)} />\n *\n * @example\n * // Choropleth of a metric by municipality name\n * <BrazilStateMap uf=\"RJ\" values={{ \"Rio de Janeiro\": 100, Niterói: 40 }} />\n */\nexport function BrazilStateMap({\n uf,\n selected,\n onSelect,\n values,\n minColor = \"#dbeafe\",\n maxColor = \"#2563eb\",\n colorScale,\n height = 440,\n padding = 12,\n showLabels = false,\n label,\n loadingContent,\n showTooltip = true,\n renderTooltip,\n markers,\n onMarkerClick,\n zoomable = false,\n className,\n style,\n ...rest\n}: BrazilStateMapProps) {\n const containerRef = useRef<HTMLDivElement>(null);\n const [width, setWidth] = useState<number>(600);\n const [collection, setCollection] = useState<StateMunicipalities | null>(null);\n const mapZoom = useMapZoom(zoomable);\n const { hover, onMove, onLeave } = useMapHover<Municipality>(containerRef);\n\n useEffect(() => {\n let active = true;\n setCollection(null);\n void loadStateMunicipalities(uf).then((data) => {\n if (active) setCollection(data);\n });\n return () => {\n active = false;\n };\n }, [uf]);\n\n useEffect(() => {\n const node = containerRef.current;\n if (!node || typeof ResizeObserver === \"undefined\") return;\n const observer = new ResizeObserver((entries) => {\n const measured = entries[0]?.contentRect.width;\n if (measured && measured > 0) setWidth(measured);\n });\n observer.observe(node);\n return () => observer.disconnect();\n }, []);\n\n const selectedSet = useMemo<Set<string>>(() => {\n if (!selected) return new Set();\n return new Set(Array.isArray(selected) ? selected : [selected as string]);\n }, [selected]);\n\n const valueRange = useMemo<[number, number] | null>(() => {\n if (!values) return null;\n const nums = Object.values(values).filter((v): v is number => typeof v === \"number\");\n if (nums.length === 0) return null;\n return [Math.min(...nums), Math.max(...nums)];\n }, [values]);\n\n const shapes = useMemo(() => {\n if (!collection) return null;\n const projection = fitProjection(\n geometriesBounds(collection.features.map((f) => f.geometry)),\n width,\n height,\n { padding },\n );\n const items = collection.features.map((feature) => ({\n id: feature.properties.id,\n name: feature.properties.name,\n d: geometryPath(feature.geometry, projection),\n centroid: geometryCentroid(feature.geometry, projection),\n }));\n return { projection, items };\n }, [collection, width, height, padding]);\n\n function fillFor(id: string, name: string): string | undefined {\n const v = values?.[id] ?? values?.[name];\n if (typeof v !== \"number\") return undefined;\n if (colorScale) return colorScale(v);\n if (!valueRange) return undefined;\n const [min, max] = valueRange;\n const t = max === min ? 1 : (v - min) / (max - min);\n return lerpColor(minColor, maxColor, t);\n }\n\n // Memoized so hover re-renders don't rebuild hundreds of municipality paths.\n const cityEls = useMemo(() => {\n if (!shapes) return null;\n return shapes.items.map((shape) => {\n const isSelected = selectedSet.has(shape.id) || selectedSet.has(shape.name);\n const fillColor = isSelected ? undefined : fillFor(shape.id, shape.name);\n return (\n <path\n key={shape.id}\n className={cn(styles.state, isSelected && styles.selected)}\n d={shape.d}\n fillRule=\"evenodd\"\n style={fillColor ? { fill: fillColor } : undefined}\n data-city-id={shape.id}\n data-city={shape.name}\n tabIndex={onSelect ? 0 : undefined}\n role={onSelect ? \"button\" : undefined}\n aria-label={shape.name}\n aria-pressed={onSelect ? isSelected : undefined}\n onClick={\n onSelect ? () => onSelect({ id: shape.id, name: shape.name }) : undefined\n }\n onMouseMove={\n showTooltip\n ? (e) => onMove({ id: shape.id, name: shape.name }, e)\n : undefined\n }\n onMouseLeave={showTooltip ? onLeave : undefined}\n onKeyDown={\n onSelect\n ? (e) => {\n if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n onSelect({ id: shape.id, name: shape.name });\n }\n }\n : undefined\n }\n />\n );\n });\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [\n shapes,\n selectedSet,\n values,\n colorScale,\n minColor,\n maxColor,\n onSelect,\n showTooltip,\n onMove,\n onLeave,\n ]);\n\n const resolvedLabel = label ?? `Municípios de ${getState(uf)?.name ?? uf}`;\n\n return (\n <div\n ref={containerRef}\n className={cn(styles.map, className)}\n style={{ height, ...style }}\n role=\"group\"\n aria-label={resolvedLabel}\n {...rest}\n >\n {!shapes ? (\n <div className={styles.loading}>{loadingContent ?? \"Carregando municípios…\"}</div>\n ) : (\n <svg\n className={styles.svg}\n width=\"100%\"\n height={height}\n viewBox={`0 0 ${width} ${height}`}\n preserveAspectRatio=\"xMidYMid meet\"\n {...mapZoom.handlers}\n >\n <g transform={mapZoom.transform || undefined}>\n {cityEls}\n\n {showLabels &&\n shapes.items.map((shape) => (\n <text\n key={`label-${shape.id}`}\n className={styles.label}\n x={shape.centroid.x}\n y={shape.centroid.y}\n textAnchor=\"middle\"\n dominantBaseline=\"central\"\n >\n {shape.name}\n </text>\n ))}\n\n {markers && markers.length > 0 && (\n <MapMarkers\n projection={shapes.projection}\n markers={markers}\n onMarkerClick={onMarkerClick}\n />\n )}\n </g>\n </svg>\n )}\n\n {mapZoom.isTransformed && (\n <button type=\"button\" className={styles.zoomReset} onClick={mapZoom.reset}>\n Reset\n </button>\n )}\n\n {showTooltip && hover && (\n <MapTooltip x={hover.x} y={hover.y}>\n {renderTooltip ? (\n renderTooltip({\n ...hover.item,\n value: values?.[hover.item.id] ?? values?.[hover.item.name],\n })\n ) : (\n <>\n <div className={styles.tooltipTitle}>{hover.item.name}</div>\n <div className={styles.tooltipMeta}>\n IBGE {hover.item.id}\n {(() => {\n const v = values?.[hover.item.id] ?? values?.[hover.item.name];\n return typeof v === \"number\" ? ` · ${v}` : \"\";\n })()}\n </div>\n </>\n )}\n </MapTooltip>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
@@ -56,8 +56,8 @@ function b({ uf: b, selected: x, onSelect: S, values: C, minColor: w = "#dbeafe"
56
56
  if (typeof n != "number") return;
57
57
  if (E) return E(n);
58
58
  if (!X) return;
59
- let [r, i] = X;
60
- return d(w, T, i === r ? 1 : (n - r) / (i - r));
59
+ let [r, i] = X, a = i === r ? 1 : (n - r) / (i - r);
60
+ return d(w, T, a);
61
61
  }
62
62
  let $ = m(() => Z ? Z.items.map((t) => {
63
63
  let n = Y.has(t.id) || Y.has(t.name), r = n ? void 0 : Q(t.id, t.name);
@@ -1 +1 @@
1
- {"version":3,"file":"BrazilStateMap.js","names":[],"sources":["../../src/br/BrazilStateMap.tsx"],"sourcesContent":["/**\n * @tempest-limits file-lines, props-count, function-lines — the municipality-level\n * twin of BrazilMap, prop for prop, plus the `uf` that selects which state's\n * geometry to load. Splitting it differently from BrazilMap would mean two maps with\n * two shapes for the same job.\n */\nimport { useEffect, useMemo, useRef, useState, type HTMLAttributes, type ReactNode } from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport { fitProjection } from \"@/geo/projection\";\nimport type { GeoMarker } from \"@/geo/types\";\nimport { getState, type UF } from \"./locations\";\nimport { MapMarkers } from \"./MapMarkers\";\nimport { MapTooltip } from \"./MapTooltip\";\nimport type { ColorScale } from \"./scales\";\nimport { useMapZoom } from \"./use-map-zoom\";\nimport { loadStateMunicipalities, type StateMunicipalities } from \"./state-geo\";\nimport { geometriesBounds, geometryCentroid, geometryPath, lerpColor } from \"./svg-utils\";\nimport { useMapHover } from \"./use-map-hover\";\nimport styles from \"./BrazilMap.module.css\";\n\n/** A municipality identified for selection callbacks. */\nexport interface Municipality {\n /** 7-digit IBGE code. */\n id: string;\n /** Municipality name. */\n name: string;\n}\n\n/** Data passed to a {@link BrazilStateMapProps.renderTooltip} callback. */\nexport interface BrazilStateMapTooltipData extends Municipality {\n /** Choropleth value for this municipality, if `values` was provided. */\n value?: number;\n}\n\nexport interface BrazilStateMapProps extends Omit<HTMLAttributes<HTMLDivElement>, \"onSelect\"> {\n /** Federative unit to draw (required). */\n uf: UF;\n /** Selected municipality — matched by IBGE `id` or by `name`. Accepts many. */\n selected?: string | readonly string[] | null;\n /** Fired when a municipality is clicked. */\n onSelect?: (municipality: Municipality) => void;\n /**\n * Choropleth values keyed by municipality `id` **or** `name`. When set, each\n * municipality is tinted between `minColor` and `maxColor`.\n */\n values?: Record<string, number>;\n /** Choropleth low-end color (2-color linear ramp). Default: a light primary tint. */\n minColor?: string;\n /** Choropleth high-end color (2-color linear ramp). Default: the primary token. */\n maxColor?: string;\n /**\n * Custom value→color scale (from `sequentialScale`/`quantizeScale`). Takes\n * precedence over `minColor`/`maxColor`. Pair with a `<MapLegend>`.\n */\n colorScale?: ColorScale;\n /** Viewport height in pixels. Default: `440`. */\n height?: number;\n /** Inner padding in pixels. Default: `12`. */\n padding?: number;\n /**\n * Render each municipality name at its centroid. Off by default — a state\n * can have hundreds of municipalities and labels overlap badly.\n */\n showLabels?: boolean;\n /** Accessible label. Default: derived from the state name. */\n label?: string;\n /** Content while the state geometry is loading. */\n loadingContent?: ReactNode;\n /** Show a floating tooltip (name + IBGE code + value) on hover. Default: `true`. */\n showTooltip?: boolean;\n /** Override the default tooltip content. */\n renderTooltip?: (data: BrazilStateMapTooltipData) => ReactNode;\n /** Point markers to overlay on the state (e.g. addresses, POIs). */\n markers?: readonly GeoMarker[];\n /** Fired when a marker is clicked. */\n onMarkerClick?: (marker: GeoMarker, index: number) => void;\n /** Enable wheel-zoom + drag-pan (double-click resets). Default: `false`. */\n zoomable?: boolean;\n}\n\n/**\n * Clickable submap of a single Brazilian state showing **all its\n * municipalities** as SVG paths — no external tiles or paid API. The state's\n * geometry (~40-70 KB gzip) is loaded lazily per UF, so switching states fetches\n * only what is shown.\n *\n * @example\n * const [city, setCity] = useState<string | null>(null);\n * <BrazilStateMap uf=\"SP\" selected={city} onSelect={(m) => setCity(m.name)} />\n *\n * @example\n * // Choropleth of a metric by municipality name\n * <BrazilStateMap uf=\"RJ\" values={{ \"Rio de Janeiro\": 100, Niterói: 40 }} />\n */\nexport function BrazilStateMap({\n uf,\n selected,\n onSelect,\n values,\n minColor = \"#dbeafe\",\n maxColor = \"#2563eb\",\n colorScale,\n height = 440,\n padding = 12,\n showLabels = false,\n label,\n loadingContent,\n showTooltip = true,\n renderTooltip,\n markers,\n onMarkerClick,\n zoomable = false,\n className,\n style,\n ...rest\n}: BrazilStateMapProps) {\n const containerRef = useRef<HTMLDivElement>(null);\n const [width, setWidth] = useState<number>(600);\n const [collection, setCollection] = useState<StateMunicipalities | null>(null);\n const mapZoom = useMapZoom(zoomable);\n const { hover, onMove, onLeave } = useMapHover<Municipality>(containerRef);\n\n useEffect(() => {\n let active = true;\n setCollection(null);\n void loadStateMunicipalities(uf).then((data) => {\n if (active) setCollection(data);\n });\n return () => {\n active = false;\n };\n }, [uf]);\n\n useEffect(() => {\n const node = containerRef.current;\n if (!node || typeof ResizeObserver === \"undefined\") return;\n const observer = new ResizeObserver((entries) => {\n const measured = entries[0]?.contentRect.width;\n if (measured && measured > 0) setWidth(measured);\n });\n observer.observe(node);\n return () => observer.disconnect();\n }, []);\n\n const selectedSet = useMemo<Set<string>>(() => {\n if (!selected) return new Set();\n return new Set(Array.isArray(selected) ? selected : [selected as string]);\n }, [selected]);\n\n const valueRange = useMemo<[number, number] | null>(() => {\n if (!values) return null;\n const nums = Object.values(values).filter((v): v is number => typeof v === \"number\");\n if (nums.length === 0) return null;\n return [Math.min(...nums), Math.max(...nums)];\n }, [values]);\n\n const shapes = useMemo(() => {\n if (!collection) return null;\n const projection = fitProjection(\n geometriesBounds(collection.features.map((f) => f.geometry)),\n width,\n height,\n { padding },\n );\n const items = collection.features.map((feature) => ({\n id: feature.properties.id,\n name: feature.properties.name,\n d: geometryPath(feature.geometry, projection),\n centroid: geometryCentroid(feature.geometry, projection),\n }));\n return { projection, items };\n }, [collection, width, height, padding]);\n\n function fillFor(id: string, name: string): string | undefined {\n const v = values?.[id] ?? values?.[name];\n if (typeof v !== \"number\") return undefined;\n if (colorScale) return colorScale(v);\n if (!valueRange) return undefined;\n const [min, max] = valueRange;\n const t = max === min ? 1 : (v - min) / (max - min);\n return lerpColor(minColor, maxColor, t);\n }\n\n // Memoized so hover re-renders don't rebuild hundreds of municipality paths.\n const cityEls = useMemo(() => {\n if (!shapes) return null;\n return shapes.items.map((shape) => {\n const isSelected = selectedSet.has(shape.id) || selectedSet.has(shape.name);\n const fillColor = isSelected ? undefined : fillFor(shape.id, shape.name);\n return (\n <path\n key={shape.id}\n className={cn(styles.state, isSelected && styles.selected)}\n d={shape.d}\n fillRule=\"evenodd\"\n style={fillColor ? { fill: fillColor } : undefined}\n data-city-id={shape.id}\n data-city={shape.name}\n tabIndex={onSelect ? 0 : undefined}\n role={onSelect ? \"button\" : undefined}\n aria-label={shape.name}\n aria-pressed={onSelect ? isSelected : undefined}\n onClick={\n onSelect ? () => onSelect({ id: shape.id, name: shape.name }) : undefined\n }\n onMouseMove={\n showTooltip\n ? (e) => onMove({ id: shape.id, name: shape.name }, e)\n : undefined\n }\n onMouseLeave={showTooltip ? onLeave : undefined}\n onKeyDown={\n onSelect\n ? (e) => {\n if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n onSelect({ id: shape.id, name: shape.name });\n }\n }\n : undefined\n }\n />\n );\n });\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [\n shapes,\n selectedSet,\n values,\n colorScale,\n minColor,\n maxColor,\n onSelect,\n showTooltip,\n onMove,\n onLeave,\n ]);\n\n const resolvedLabel = label ?? `Municípios de ${getState(uf)?.name ?? uf}`;\n\n return (\n <div\n ref={containerRef}\n className={cn(styles.map, className)}\n style={{ height, ...style }}\n role=\"group\"\n aria-label={resolvedLabel}\n {...rest}\n >\n {!shapes ? (\n <div className={styles.loading}>{loadingContent ?? \"Carregando municípios…\"}</div>\n ) : (\n <svg\n className={styles.svg}\n width=\"100%\"\n height={height}\n viewBox={`0 0 ${width} ${height}`}\n preserveAspectRatio=\"xMidYMid meet\"\n {...mapZoom.handlers}\n >\n <g transform={mapZoom.transform || undefined}>\n {cityEls}\n\n {showLabels &&\n shapes.items.map((shape) => (\n <text\n key={`label-${shape.id}`}\n className={styles.label}\n x={shape.centroid.x}\n y={shape.centroid.y}\n textAnchor=\"middle\"\n dominantBaseline=\"central\"\n >\n {shape.name}\n </text>\n ))}\n\n {markers && markers.length > 0 && (\n <MapMarkers\n projection={shapes.projection}\n markers={markers}\n onMarkerClick={onMarkerClick}\n />\n )}\n </g>\n </svg>\n )}\n\n {mapZoom.isTransformed && (\n <button type=\"button\" className={styles.zoomReset} onClick={mapZoom.reset}>\n Reset\n </button>\n )}\n\n {showTooltip && hover && (\n <MapTooltip x={hover.x} y={hover.y}>\n {renderTooltip ? (\n renderTooltip({\n ...hover.item,\n value: values?.[hover.item.id] ?? values?.[hover.item.name],\n })\n ) : (\n <>\n <div className={styles.tooltipTitle}>{hover.item.name}</div>\n <div className={styles.tooltipMeta}>\n IBGE {hover.item.id}\n {(() => {\n const v = values?.[hover.item.id] ?? values?.[hover.item.name];\n return typeof v === \"number\" ? ` · ${v}` : \"\";\n })()}\n </div>\n </>\n )}\n </MapTooltip>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"BrazilStateMap.js","names":[],"sources":["../../src/br/BrazilStateMap.tsx"],"sourcesContent":["/**\n * @tempest-limits file-lines, props-count, function-lines — the municipality-level\n * twin of BrazilMap, prop for prop, plus the `uf` that selects which state's\n * geometry to load. Splitting it differently from BrazilMap would mean two maps with\n * two shapes for the same job.\n */\nimport { useEffect, useMemo, useRef, useState, type HTMLAttributes, type ReactNode } from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport { fitProjection } from \"@/geo/projection\";\nimport type { GeoMarker } from \"@/geo/types\";\nimport { getState, type UF } from \"./locations\";\nimport { MapMarkers } from \"./MapMarkers\";\nimport { MapTooltip } from \"./MapTooltip\";\nimport type { ColorScale } from \"./scales\";\nimport { useMapZoom } from \"./use-map-zoom\";\nimport { loadStateMunicipalities, type StateMunicipalities } from \"./state-geo\";\nimport { geometriesBounds, geometryCentroid, geometryPath, lerpColor } from \"./svg-utils\";\nimport { useMapHover } from \"./use-map-hover\";\nimport styles from \"./BrazilMap.module.css\";\n\n/** A municipality identified for selection callbacks. */\nexport interface Municipality {\n /** 7-digit IBGE code. */\n id: string;\n /** Municipality name. */\n name: string;\n}\n\n/** Data passed to a {@link BrazilStateMapProps.renderTooltip} callback. */\nexport interface BrazilStateMapTooltipData extends Municipality {\n /** Choropleth value for this municipality, if `values` was provided. */\n value?: number;\n}\n\nexport interface BrazilStateMapProps extends Omit<HTMLAttributes<HTMLDivElement>, \"onSelect\"> {\n /** Federative unit to draw (required). */\n uf: UF;\n /** Selected municipality — matched by IBGE `id` or by `name`. Accepts many. */\n selected?: string | readonly string[] | null;\n /** Fired when a municipality is clicked. */\n onSelect?: (municipality: Municipality) => void;\n /**\n * Choropleth values keyed by municipality `id` **or** `name`. When set, each\n * municipality is tinted between `minColor` and `maxColor`.\n */\n values?: Record<string, number>;\n /** Choropleth low-end color (2-color linear ramp). Default: a light primary tint. */\n minColor?: string;\n /** Choropleth high-end color (2-color linear ramp). Default: the primary token. */\n maxColor?: string;\n /**\n * Custom value→color scale (from `sequentialScale`/`quantizeScale`). Takes\n * precedence over `minColor`/`maxColor`. Pair with a `<MapLegend>`.\n */\n colorScale?: ColorScale;\n /** Viewport height in pixels. Default: `440`. */\n height?: number;\n /** Inner padding in pixels. Default: `12`. */\n padding?: number;\n /**\n * Render each municipality name at its centroid. Off by default — a state\n * can have hundreds of municipalities and labels overlap badly.\n */\n showLabels?: boolean;\n /** Accessible label. Default: derived from the state name. */\n label?: string;\n /** Content while the state geometry is loading. */\n loadingContent?: ReactNode;\n /** Show a floating tooltip (name + IBGE code + value) on hover. Default: `true`. */\n showTooltip?: boolean;\n /** Override the default tooltip content. */\n renderTooltip?: (data: BrazilStateMapTooltipData) => ReactNode;\n /** Point markers to overlay on the state (e.g. addresses, POIs). */\n markers?: readonly GeoMarker[];\n /** Fired when a marker is clicked. */\n onMarkerClick?: (marker: GeoMarker, index: number) => void;\n /** Enable wheel-zoom + drag-pan (double-click resets). Default: `false`. */\n zoomable?: boolean;\n}\n\n/**\n * Clickable submap of a single Brazilian state showing **all its\n * municipalities** as SVG paths — no external tiles or paid API. The state's\n * geometry (~40-70 KB gzip) is loaded lazily per UF, so switching states fetches\n * only what is shown.\n *\n * @example\n * const [city, setCity] = useState<string | null>(null);\n * <BrazilStateMap uf=\"SP\" selected={city} onSelect={(m) => setCity(m.name)} />\n *\n * @example\n * // Choropleth of a metric by municipality name\n * <BrazilStateMap uf=\"RJ\" values={{ \"Rio de Janeiro\": 100, Niterói: 40 }} />\n */\nexport function BrazilStateMap({\n uf,\n selected,\n onSelect,\n values,\n minColor = \"#dbeafe\",\n maxColor = \"#2563eb\",\n colorScale,\n height = 440,\n padding = 12,\n showLabels = false,\n label,\n loadingContent,\n showTooltip = true,\n renderTooltip,\n markers,\n onMarkerClick,\n zoomable = false,\n className,\n style,\n ...rest\n}: BrazilStateMapProps) {\n const containerRef = useRef<HTMLDivElement>(null);\n const [width, setWidth] = useState<number>(600);\n const [collection, setCollection] = useState<StateMunicipalities | null>(null);\n const mapZoom = useMapZoom(zoomable);\n const { hover, onMove, onLeave } = useMapHover<Municipality>(containerRef);\n\n useEffect(() => {\n let active = true;\n setCollection(null);\n void loadStateMunicipalities(uf).then((data) => {\n if (active) setCollection(data);\n });\n return () => {\n active = false;\n };\n }, [uf]);\n\n useEffect(() => {\n const node = containerRef.current;\n if (!node || typeof ResizeObserver === \"undefined\") return;\n const observer = new ResizeObserver((entries) => {\n const measured = entries[0]?.contentRect.width;\n if (measured && measured > 0) setWidth(measured);\n });\n observer.observe(node);\n return () => observer.disconnect();\n }, []);\n\n const selectedSet = useMemo<Set<string>>(() => {\n if (!selected) return new Set();\n return new Set(Array.isArray(selected) ? selected : [selected as string]);\n }, [selected]);\n\n const valueRange = useMemo<[number, number] | null>(() => {\n if (!values) return null;\n const nums = Object.values(values).filter((v): v is number => typeof v === \"number\");\n if (nums.length === 0) return null;\n return [Math.min(...nums), Math.max(...nums)];\n }, [values]);\n\n const shapes = useMemo(() => {\n if (!collection) return null;\n const projection = fitProjection(\n geometriesBounds(collection.features.map((f) => f.geometry)),\n width,\n height,\n { padding },\n );\n const items = collection.features.map((feature) => ({\n id: feature.properties.id,\n name: feature.properties.name,\n d: geometryPath(feature.geometry, projection),\n centroid: geometryCentroid(feature.geometry, projection),\n }));\n return { projection, items };\n }, [collection, width, height, padding]);\n\n function fillFor(id: string, name: string): string | undefined {\n const v = values?.[id] ?? values?.[name];\n if (typeof v !== \"number\") return undefined;\n if (colorScale) return colorScale(v);\n if (!valueRange) return undefined;\n const [min, max] = valueRange;\n const t = max === min ? 1 : (v - min) / (max - min);\n return lerpColor(minColor, maxColor, t);\n }\n\n // Memoized so hover re-renders don't rebuild hundreds of municipality paths.\n const cityEls = useMemo(() => {\n if (!shapes) return null;\n return shapes.items.map((shape) => {\n const isSelected = selectedSet.has(shape.id) || selectedSet.has(shape.name);\n const fillColor = isSelected ? undefined : fillFor(shape.id, shape.name);\n return (\n <path\n key={shape.id}\n className={cn(styles.state, isSelected && styles.selected)}\n d={shape.d}\n fillRule=\"evenodd\"\n style={fillColor ? { fill: fillColor } : undefined}\n data-city-id={shape.id}\n data-city={shape.name}\n tabIndex={onSelect ? 0 : undefined}\n role={onSelect ? \"button\" : undefined}\n aria-label={shape.name}\n aria-pressed={onSelect ? isSelected : undefined}\n onClick={\n onSelect ? () => onSelect({ id: shape.id, name: shape.name }) : undefined\n }\n onMouseMove={\n showTooltip\n ? (e) => onMove({ id: shape.id, name: shape.name }, e)\n : undefined\n }\n onMouseLeave={showTooltip ? onLeave : undefined}\n onKeyDown={\n onSelect\n ? (e) => {\n if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n onSelect({ id: shape.id, name: shape.name });\n }\n }\n : undefined\n }\n />\n );\n });\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [\n shapes,\n selectedSet,\n values,\n colorScale,\n minColor,\n maxColor,\n onSelect,\n showTooltip,\n onMove,\n onLeave,\n ]);\n\n const resolvedLabel = label ?? `Municípios de ${getState(uf)?.name ?? uf}`;\n\n return (\n <div\n ref={containerRef}\n className={cn(styles.map, className)}\n style={{ height, ...style }}\n role=\"group\"\n aria-label={resolvedLabel}\n {...rest}\n >\n {!shapes ? (\n <div className={styles.loading}>{loadingContent ?? \"Carregando municípios…\"}</div>\n ) : (\n <svg\n className={styles.svg}\n width=\"100%\"\n height={height}\n viewBox={`0 0 ${width} ${height}`}\n preserveAspectRatio=\"xMidYMid meet\"\n {...mapZoom.handlers}\n >\n <g transform={mapZoom.transform || undefined}>\n {cityEls}\n\n {showLabels &&\n shapes.items.map((shape) => (\n <text\n key={`label-${shape.id}`}\n className={styles.label}\n x={shape.centroid.x}\n y={shape.centroid.y}\n textAnchor=\"middle\"\n dominantBaseline=\"central\"\n >\n {shape.name}\n </text>\n ))}\n\n {markers && markers.length > 0 && (\n <MapMarkers\n projection={shapes.projection}\n markers={markers}\n onMarkerClick={onMarkerClick}\n />\n )}\n </g>\n </svg>\n )}\n\n {mapZoom.isTransformed && (\n <button type=\"button\" className={styles.zoomReset} onClick={mapZoom.reset}>\n Reset\n </button>\n )}\n\n {showTooltip && hover && (\n <MapTooltip x={hover.x} y={hover.y}>\n {renderTooltip ? (\n renderTooltip({\n ...hover.item,\n value: values?.[hover.item.id] ?? values?.[hover.item.name],\n })\n ) : (\n <>\n <div className={styles.tooltipTitle}>{hover.item.name}</div>\n <div className={styles.tooltipMeta}>\n IBGE {hover.item.id}\n {(() => {\n const v = values?.[hover.item.id] ?? values?.[hover.item.name];\n return typeof v === \"number\" ? ` · ${v}` : \"\";\n })()}\n </div>\n </>\n )}\n </MapTooltip>\n )}\n </div>\n );\n}\n"],"mappings":"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"}