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,2 +1,2 @@
1
- const e=require("../../utils/cn.cjs"),t=require("../../hooks/use-pagination.cjs"),n=require("../../hooks/use-announce.cjs"),r=require("../Pagination/Pagination.cjs"),i=require("../SearchBar/SearchBar.cjs"),a=require("../Table/Table.cjs"),o=require("../../utils/compare-values.cjs"),s=require("./DataTable.module.cjs"),c=require("./EditableCell.cjs"),l=require("./edit-labels.cjs");let u=require("react"),d=require("react/jsx-runtime");function f(e,t){return`${String(e)}::${String(t)}`}function p(e){return typeof e.header==`string`?e.header:String(e.key)}function m({data:m,columns:h,pageSize:g=10,searchable:_=!1,searchKeys:v,initialSort:y,rowKey:b=(e,t)=>t,emptyMessage:ee,onCellChange:x,editLabels:S,className:te,...ne}){let[C,w]=(0,u.useState)(``),[T,E]=(0,u.useState)(y??null),{page:D,setPage:O}=t.usePagination(1,g),k=n.useAnnounce(),[A,j]=(0,u.useState)(null),[M,N]=(0,u.useState)(null),[P,F]=(0,u.useState)({}),[I,L]=(0,u.useState)({}),[R,z]=(0,u.useState)({}),B=(0,u.useMemo)(()=>({...l.DEFAULT_EDIT_LABELS,...S}),[S]),V=x!==void 0&&h.some(e=>e.editable),H=(0,u.useMemo)(()=>v&&v.length>0?v:h.filter(e=>{let t=m.find(t=>t[e.key]!=null),n=t?t[e.key]:void 0;return typeof n==`string`||typeof n==`number`}).map(e=>e.key),[v,h,m]),U=(0,u.useMemo)(()=>{let e=C.trim().toLowerCase();return!e||!_?m:m.filter(t=>H.some(n=>{let r=t[n];return r!=null&&String(r).toLowerCase().includes(e)}))},[m,C,_,H]),W=(0,u.useMemo)(()=>{if(!T)return U;let e=T.direction===`asc`?1:-1;return[...U].sort((t,n)=>o.compareValues(t[T.key],n[T.key])*e)},[U,T]),G=Math.max(1,Math.ceil(W.length/g));(0,u.useEffect)(()=>{D>G&&O(G)},[D,G,O]);let K=Math.min(D,G),q=(0,u.useMemo)(()=>{let e=(K-1)*g;return W.slice(e,e+g)},[W,K,g]);function re(e){E(t=>!t||t.key!==e?{key:e,direction:`asc`}:t.direction===`asc`?{key:e,direction:`desc`}:null)}let J=(0,u.useCallback)(e=>(K-1)*g+e,[K,g]),Y=(0,u.useMemo)(()=>{if(!V)return[];let e=[];return q.forEach((t,n)=>{let r=b(t,J(n));for(let t of h)t.editable&&e.push(f(r,t.key))}),e},[V,q,h,J]),X=(0,u.useCallback)((e,t,n)=>n in P?P[n]:e[t.key],[P]),Z=(0,u.useCallback)(async(e,t,n)=>{z(t=>({...t,[e]:!0}));try{await x?.(n),k(B.saved(p(t)))}catch(n){F(t=>{let n={...t};return delete n[e],n});let r=n instanceof Error&&n.message?n.message:B.saveFailed(p(t));L(t=>({...t,[e]:r}))}finally{z(t=>{let n={...t};return delete n[e],n})}},[x,k,B]),Q=(0,u.useCallback)((e,t)=>{if(t===`none`){j(null),N(e);return}let n=Y.indexOf(e),r=Y[n+(t===`next`?1:-1)];if(r===void 0){j(null),N(e);return}j(r),N(null)},[Y]),$=(0,u.useCallback)((e,t,n,r,i,a)=>{let o=X(e,t,n);if(i===(t.formatEdit?t.formatEdit(e):String(o??``))){Q(n,a);return}let s=t.parse?t.parse(i,e):t.editorType===`number`?Number(i):i.trim(),c=t.validate?.(s,e)??null;if(c){L(e=>({...e,[n]:c}));return}L(e=>{let t={...e};return delete t[n],t}),F(e=>({...e,[n]:s})),Q(n,a),Z(n,t,{row:e,key:t.key,value:s,previous:o,rowIndex:J(r)})},[X,Q,Z,J]),ie=(0,u.useMemo)(()=>h.map(e=>{let t=T?.key===e.key?T?.direction===`asc`?` ▲`:` ▼`:``,n=e.sortable?(0,d.jsxs)(`button`,{type:`button`,className:s.default.sortButton,onClick:()=>re(e.key),"aria-label":`Ordenar por ${p(e)}`,children:[e.header,(0,d.jsx)(`span`,{className:s.default.sortIndicator,"aria-hidden":!0,children:t})]}):e.header,r=t=>e.render?e.render(t):t[e.key]??null,i=(t,n)=>{let r={...t,[e.key]:n};return e.render?e.render(r):r[e.key]??null},a=V&&e.editable===!0;return{key:String(e.key),header:n,align:e.align,priority:e.priority,width:e.width,render:a?(t,n)=>{let r=f(b(t,J(n)),e.key),a=X(t,e,r);return(0,d.jsx)(c.EditableCell,{text:e.formatEdit?e.formatEdit(t):String(a??``),columnLabel:p(e),rowNumber:n+1,inputType:e.editorType??`text`,editing:A===r,refocus:M===r,saving:R[r]===!0,error:I[r]??null,errorId:`tempest-cell-error-${r.replace(/[^\w-]/g,`_`)}`,labels:B,onOpen:()=>{j(r),N(null)},onCommit:(i,a)=>$(t,e,r,n,i,a),onCancel:()=>{j(null),N(r),L(e=>{let t={...e};return delete t[r],t})},children:i(t,a)})}:r}}),[h,T,V,A,M,R,I,B,P,J,$]);return(0,d.jsxs)(`div`,{className:e.cn(s.default.wrapper,te),...ne,children:[_&&(0,d.jsx)(i.SearchBar,{value:C,onChange:e=>{w(e),O(1)},wrapperClassName:s.default.search}),(0,d.jsx)(a.Table,{columns:ie,data:q,rowKey:(e,t)=>b(e,J(t)),emptyMessage:ee}),G>1&&(0,d.jsx)(r.Pagination,{page:K,totalPages:G,onPageChange:O,totalItems:W.length})]})}exports.DataTable=m;
1
+ const e=require("../../utils/cn.cjs"),t=require("../../hooks/use-pagination.cjs"),n=require("../../hooks/use-announce.cjs"),r=require("../Pagination/Pagination.cjs"),i=require("../SearchBar/SearchBar.cjs"),a=require("../Table/Table.cjs"),o=require("../../utils/compare-values.cjs"),s=require("./DataTable.module.cjs"),c=require("./EditableCell.cjs"),l=require("./edit-labels.cjs");let u=require("react"),d=require("react/jsx-runtime");function f(e,t){return`${String(e)}::${String(t)}`}function p(e){return typeof e.header==`string`?e.header:String(e.key)}function m({data:m,columns:h,pageSize:g=10,searchable:_=!1,searchKeys:v,initialSort:ee,rowKey:y=(e,t)=>t,emptyMessage:b,onCellChange:x,editLabels:S,className:C,...te}){let[w,ne]=(0,u.useState)(``),[T,E]=(0,u.useState)(ee??null),{page:D,setPage:O}=t.usePagination(1,g),k=n.useAnnounce(),[A,j]=(0,u.useState)(null),[M,N]=(0,u.useState)(null),[P,F]=(0,u.useState)({}),[I,L]=(0,u.useState)({}),[R,z]=(0,u.useState)({}),B=(0,u.useMemo)(()=>({...l.DEFAULT_EDIT_LABELS,...S}),[S]),V=x!==void 0&&h.some(e=>e.editable),H=(0,u.useMemo)(()=>v&&v.length>0?v:h.filter(e=>{let t=m.find(t=>t[e.key]!=null),n=t?t[e.key]:void 0;return typeof n==`string`||typeof n==`number`}).map(e=>e.key),[v,h,m]),U=(0,u.useMemo)(()=>{let e=w.trim().toLowerCase();return!e||!_?m:m.filter(t=>H.some(n=>{let r=t[n];return r!=null&&String(r).toLowerCase().includes(e)}))},[m,w,_,H]),W=(0,u.useMemo)(()=>{if(!T)return U;let e=T.direction===`asc`?1:-1;return[...U].sort((t,n)=>o.compareValues(t[T.key],n[T.key])*e)},[U,T]),G=Math.max(1,Math.ceil(W.length/g));(0,u.useEffect)(()=>{D>G&&O(G)},[D,G,O]);let K=Math.min(D,G),q=(0,u.useMemo)(()=>{let e=(K-1)*g;return W.slice(e,e+g)},[W,K,g]);function re(e){E(t=>!t||t.key!==e?{key:e,direction:`asc`}:t.direction===`asc`?{key:e,direction:`desc`}:null)}let J=(0,u.useCallback)(e=>(K-1)*g+e,[K,g]),Y=(0,u.useMemo)(()=>{if(!V)return[];let e=[];return q.forEach((t,n)=>{let r=y(t,J(n));for(let t of h)t.editable&&e.push(f(r,t.key))}),e},[V,q,h,J]),X=(0,u.useCallback)((e,t,n)=>n in P?P[n]:e[t.key],[P]),Z=(0,u.useCallback)(async(e,t,n)=>{z(t=>({...t,[e]:!0}));try{await x?.(n),k(B.saved(p(t)))}catch(n){F(t=>{let n={...t};return delete n[e],n});let r=n instanceof Error&&n.message?n.message:B.saveFailed(p(t));L(t=>({...t,[e]:r}))}finally{z(t=>{let n={...t};return delete n[e],n})}},[x,k,B]),Q=(0,u.useCallback)((e,t)=>{if(t===`none`){j(null),N(e);return}let n=Y.indexOf(e),r=Y[n+(t===`next`?1:-1)];if(r===void 0){j(null),N(e);return}j(r),N(null)},[Y]),$=(0,u.useCallback)((e,t,n,r,i,a)=>{let o=X(e,t,n);if(i===(t.formatEdit?t.formatEdit(e):String(o??``))){Q(n,a);return}let s=t.parse?t.parse(i,e):t.editorType===`number`?Number(i):i.trim(),c=t.validate?.(s,e)??null;if(c){L(e=>({...e,[n]:c}));return}L(e=>{let t={...e};return delete t[n],t}),F(e=>({...e,[n]:s})),Q(n,a),Z(n,t,{row:e,key:t.key,value:s,previous:o,rowIndex:J(r)})},[X,Q,Z,J]),ie=(0,u.useMemo)(()=>h.map(e=>{let t=T?.key===e.key?T?.direction===`asc`?` ▲`:` ▼`:``,n=e.sortable?(0,d.jsxs)(`button`,{type:`button`,className:s.default.sortButton,onClick:()=>re(e.key),"aria-label":`Ordenar por ${p(e)}`,children:[e.header,(0,d.jsx)(`span`,{className:s.default.sortIndicator,"aria-hidden":!0,children:t})]}):e.header,r=t=>e.render?e.render(t):t[e.key]??null,i=(t,n)=>{let r={...t,[e.key]:n};return e.render?e.render(r):r[e.key]??null},a=V&&e.editable===!0;return{key:String(e.key),header:n,align:e.align,priority:e.priority,width:e.width,render:a?(t,n)=>{let r=f(y(t,J(n)),e.key),a=X(t,e,r),o=e.formatEdit?e.formatEdit(t):String(a??``);return(0,d.jsx)(c.EditableCell,{text:o,columnLabel:p(e),rowNumber:n+1,inputType:e.editorType??`text`,editing:A===r,refocus:M===r,saving:R[r]===!0,error:I[r]??null,errorId:`tempest-cell-error-${r.replace(/[^\w-]/g,`_`)}`,labels:B,onOpen:()=>{j(r),N(null)},onCommit:(i,a)=>$(t,e,r,n,i,a),onCancel:()=>{j(null),N(r),L(e=>{let t={...e};return delete t[r],t})},children:i(t,a)})}:r}}),[h,T,V,A,M,R,I,B,P,J,$]);return(0,d.jsxs)(`div`,{className:e.cn(s.default.wrapper,C),...te,children:[_&&(0,d.jsx)(i.SearchBar,{value:w,onChange:e=>{ne(e),O(1)},wrapperClassName:s.default.search}),(0,d.jsx)(a.Table,{columns:ie,data:q,rowKey:(e,t)=>y(e,J(t)),emptyMessage:b}),G>1&&(0,d.jsx)(r.Pagination,{page:K,totalPages:G,onPageChange:O,totalItems:W.length})]})}exports.DataTable=m;
2
2
  //# sourceMappingURL=DataTable.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"DataTable.cjs","names":[],"sources":["../../../src/components/DataTable/DataTable.tsx"],"sourcesContent":["/**\n * @tempest-limits file-lines, props-count, function-lines — the table does four jobs\n * a caller turns on independently — paging (pageSize), search (searchable,\n * searchKeys), sort (initialSort) and inline edit (onCellChange, editLabels) — over\n * one row model (data, columns, rowKey, emptyMessage). The body is long because\n * those four share the derived-rows pipeline: filter, then sort, then page, then map\n * to cells, in that order and off the same memo.\n */\nimport { useCallback, useEffect, useMemo, useState } from \"react\";\nimport type { HTMLAttributes, ReactNode } from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport { compareValues } from \"@/utils/compare-values\";\nimport { usePagination } from \"@/hooks\";\nimport { useAnnounce } from \"@/hooks/use-announce\";\nimport { Table, type TableAlign, type TableColumn, type TablePriority } from \"../Table\";\nimport { Pagination } from \"../Pagination\";\nimport { SearchBar } from \"../SearchBar\";\nimport { EditableCell } from \"./EditableCell\";\nimport { DEFAULT_EDIT_LABELS, type CellCommitMove, type DataTableEditLabels } from \"./edit-labels\";\nimport styles from \"./DataTable.module.css\";\n\nexport type SortDirection = \"asc\" | \"desc\";\n\nexport interface DataTableSort<T> {\n key: keyof T;\n direction: SortDirection;\n}\n\n/** Input types an editable column can use. */\nexport type DataTableEditorType = \"text\" | \"number\" | \"date\" | \"email\" | \"tel\" | \"url\";\n\n/** One accepted cell edit, handed to `onCellChange`. */\nexport interface DataTableCellChange<T> {\n /** The row as it was before the edit. */\n row: T;\n /** Which column changed. */\n key: keyof T;\n /** The parsed new value. */\n value: unknown;\n /** The value that was displayed before the edit. */\n previous: unknown;\n /** Index of the row in the full `data` array. */\n rowIndex: number;\n}\n\n/**\n * Column definition for {@link DataTable}. Extends the headless {@link Table}\n * column shape with a typed `key`, opt-in sorting, opt-in inline editing, and the\n * visual options that are forwarded to the underlying Table cell.\n */\nexport interface DataTableColumn<T> {\n /** Property of the row this column reads from. Doubles as the cell key. */\n key: keyof T;\n /** Column heading. */\n header: ReactNode;\n /** Custom cell renderer. Defaults to `String(row[key])`. */\n render?: (row: T) => ReactNode;\n /** Enable click-to-sort on this column's header. */\n sortable?: boolean;\n /** Text alignment forwarded to the Table cell. */\n align?: TableAlign;\n /** Responsive visibility priority forwarded to the Table cell. */\n priority?: TablePriority;\n /** Fixed column width forwarded to the Table cell. */\n width?: string | number;\n /**\n * Let cells in this column be edited in place. Requires `onCellChange` on the\n * table; without it the column stays read-only.\n */\n editable?: boolean;\n /** Editor input type. Default `\"text\"`. */\n editorType?: DataTableEditorType;\n /** Text the editor opens with. Defaults to `String(value ?? \"\")`. */\n formatEdit?: (row: T) => string;\n /**\n * Turn the typed string into the stored value. Defaults to the trimmed string,\n * or `Number(raw)` when `editorType` is `\"number\"`.\n */\n parse?: (raw: string, row: T) => unknown;\n /** Return a message to reject the edit, or `null` to accept it. */\n validate?: (value: unknown, row: T) => string | null;\n}\n\nexport interface DataTableProps<T> extends HTMLAttributes<HTMLDivElement> {\n /** Full, unfiltered dataset. Sorting/filtering/pagination happen client-side. */\n data: T[];\n /** Column definitions. */\n columns: DataTableColumn<T>[];\n /** Rows per page. Default 10. */\n pageSize?: number;\n /** Render a search input above the table. Default false. */\n searchable?: boolean;\n /**\n * Keys to match the search term against. When omitted, every column whose\n * value is a string or number is searched.\n */\n searchKeys?: (keyof T)[];\n /** Initial sort applied before any header interaction. */\n initialSort?: DataTableSort<T>;\n /** Stable key extractor for rows. Defaults to the row index. */\n rowKey?: (row: T, index: number) => string | number;\n /** Content shown when no rows match. */\n emptyMessage?: ReactNode;\n /**\n * Persist an accepted cell edit. Return a promise: while it is pending the cell\n * already shows the new value, and a rejection rolls that back and surfaces the\n * error in the cell. Without this prop no column is editable.\n */\n onCellChange?: (change: DataTableCellChange<T>) => void | Promise<void>;\n /** Override the PT-BR copy of the editing affordances. */\n editLabels?: Partial<DataTableEditLabels>;\n}\n\n/** Identity of one cell, stable across re-renders and pagination. */\nfunction cellId(rowKeyValue: string | number, columnKey: PropertyKey): string {\n return `${String(rowKeyValue)}::${String(columnKey)}`;\n}\n\nfunction headerText<T>(column: DataTableColumn<T>): string {\n return typeof column.header === \"string\" ? column.header : String(column.key);\n}\n\n/**\n * Stateful, headless data table built on top of {@link Table}. Adds\n * client-side searching, click-to-sort columns, pagination and opt-in inline\n * editing while delegating all table markup to the underlying Table component.\n *\n * - Clicking a sortable header cycles asc → desc → unsorted.\n * - Search matches a case-insensitive substring across `searchKeys`\n * (or every string/number column when not provided).\n * - Pagination is hidden when the result fits on a single page.\n * - A column with `editable` renders a button that opens an inline editor;\n * `Enter` commits, `Escape` discards, `Tab` walks to the next editable cell.\n *\n * Editing is strictly opt-in: with no `editable` column (or no `onCellChange`) the\n * rendered markup is byte-for-byte what it was before the feature existed, which\n * matters because the component is published.\n *\n * ## Optimistic, with a visible rollback\n *\n * An accepted edit is shown immediately and `onCellChange` runs in the background.\n * If it rejects, the cell returns to the old value **and** shows the reason as a\n * `role=\"alert\"` tied to the cell. A silent revert is worse than no optimistic\n * update at all: the user watched their edit appear and has no reason to doubt it.\n *\n * The header memo depends on `columns`, `sort` and the editing state only:\n * `toggleSort` and the commit callbacks are recreated each render but always close\n * over the same setters, so including them would rebuild every header on every\n * render without changing behaviour. That is why `exhaustive-deps` is silenced on\n * that dependency array.\n */\nexport function DataTable<T>({\n data,\n columns,\n pageSize = 10,\n searchable = false,\n searchKeys,\n initialSort,\n rowKey = (_row, index) => index,\n emptyMessage,\n onCellChange,\n editLabels,\n className,\n ...rest\n}: DataTableProps<T>) {\n const [search, setSearch] = useState<string>(\"\");\n const [sort, setSort] = useState<DataTableSort<T> | null>(initialSort ?? null);\n const { page, setPage } = usePagination(1, pageSize);\n const announce = useAnnounce();\n\n const [editing, setEditing] = useState<string | null>(null);\n const [refocus, setRefocus] = useState<string | null>(null);\n const [overrides, setOverrides] = useState<Record<string, unknown>>({});\n const [errors, setErrors] = useState<Record<string, string>>({});\n const [saving, setSaving] = useState<Record<string, boolean>>({});\n\n const labels = useMemo<DataTableEditLabels>(\n () => ({ ...DEFAULT_EDIT_LABELS, ...editLabels }),\n [editLabels],\n );\n const editingEnabled = onCellChange !== undefined && columns.some((column) => column.editable);\n\n const effectiveSearchKeys = useMemo<(keyof T)[]>(() => {\n if (searchKeys && searchKeys.length > 0) return searchKeys;\n return columns\n .filter((column) => {\n const sample = data.find((row) => row[column.key] != null);\n const value = sample ? sample[column.key] : undefined;\n return typeof value === \"string\" || typeof value === \"number\";\n })\n .map((column) => column.key);\n }, [searchKeys, columns, data]);\n\n const filtered = useMemo<T[]>(() => {\n const term = search.trim().toLowerCase();\n if (!term || !searchable) return data;\n return data.filter((row) =>\n effectiveSearchKeys.some((key) => {\n const value = row[key];\n return value != null && String(value).toLowerCase().includes(term);\n }),\n );\n }, [data, search, searchable, effectiveSearchKeys]);\n\n const sorted = useMemo<T[]>(() => {\n if (!sort) return filtered;\n const factor = sort.direction === \"asc\" ? 1 : -1;\n return [...filtered].sort((a, b) => compareValues(a[sort.key], b[sort.key]) * factor);\n }, [filtered, sort]);\n\n const totalPages = Math.max(1, Math.ceil(sorted.length / pageSize));\n\n // Clamp the current page when the dataset shrinks (e.g. after filtering).\n useEffect(() => {\n if (page > totalPages) setPage(totalPages);\n }, [page, totalPages, setPage]);\n\n const safePage = Math.min(page, totalPages);\n const pageRows = useMemo<T[]>(() => {\n const start = (safePage - 1) * pageSize;\n return sorted.slice(start, start + pageSize);\n }, [sorted, safePage, pageSize]);\n\n function toggleSort(key: keyof T): void {\n setSort((current) => {\n if (!current || current.key !== key) return { key, direction: \"asc\" };\n if (current.direction === \"asc\") return { key, direction: \"desc\" };\n return null;\n });\n }\n\n const absoluteIndex = useCallback(\n (pageIndex: number) => (safePage - 1) * pageSize + pageIndex,\n [safePage, pageSize],\n );\n\n /**\n * Every editable cell on the page, row-major — the order `Tab` walks.\n *\n * Row-major and not column-major because a row is the record a user is\n * correcting; walking down a column would make them re-find their place on\n * every keystroke.\n */\n const editableCellIds = useMemo<string[]>(() => {\n if (!editingEnabled) return [];\n const ids: string[] = [];\n pageRows.forEach((row, index) => {\n const key = rowKey(row, absoluteIndex(index));\n for (const column of columns) {\n if (column.editable) ids.push(cellId(key, column.key));\n }\n });\n return ids;\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [editingEnabled, pageRows, columns, absoluteIndex]);\n\n const displayed = useCallback(\n (row: T, column: DataTableColumn<T>, id: string): unknown =>\n id in overrides ? overrides[id] : row[column.key],\n [overrides],\n );\n\n /**\n * Run the caller's `onCellChange` behind the optimistic update.\n *\n * Only the *success* is announced. The failure already renders as a\n * `role=\"alert\"` inside the cell, which screen readers read on insertion —\n * announcing it again from the shared region would read it twice and put the\n * same text in the document twice.\n */\n const persist = useCallback(\n async (id: string, column: DataTableColumn<T>, change: DataTableCellChange<T>) => {\n setSaving((current) => ({ ...current, [id]: true }));\n try {\n await onCellChange?.(change);\n announce(labels.saved(headerText(column)));\n } catch (error) {\n setOverrides((current) => {\n const next = { ...current };\n delete next[id];\n return next;\n });\n const message =\n error instanceof Error && error.message\n ? error.message\n : labels.saveFailed(headerText(column));\n setErrors((current) => ({ ...current, [id]: message }));\n } finally {\n setSaving((current) => {\n const next = { ...current };\n delete next[id];\n return next;\n });\n }\n },\n [onCellChange, announce, labels],\n );\n\n const moveFrom = useCallback(\n (id: string, move: CellCommitMove): void => {\n if (move === \"none\") {\n setEditing(null);\n setRefocus(id);\n return;\n }\n const index = editableCellIds.indexOf(id);\n const target = editableCellIds[index + (move === \"next\" ? 1 : -1)];\n if (target === undefined) {\n setEditing(null);\n setRefocus(id);\n return;\n }\n setEditing(target);\n setRefocus(null);\n },\n [editableCellIds],\n );\n\n /**\n * Parse, validate, stage optimistically, then save in the background.\n *\n * Validation runs before anything is staged, and a rejection leaves the editor\n * open with the message attached to the input — the user has to be able to fix\n * what they typed without retyping it.\n */\n const commit = useCallback(\n (\n row: T,\n column: DataTableColumn<T>,\n id: string,\n pageIndex: number,\n raw: string,\n move: CellCommitMove,\n ): void => {\n const previous = displayed(row, column, id);\n const currentText = column.formatEdit ? column.formatEdit(row) : String(previous ?? \"\");\n if (raw === currentText) {\n moveFrom(id, move);\n return;\n }\n\n const value = column.parse\n ? column.parse(raw, row)\n : column.editorType === \"number\"\n ? Number(raw)\n : raw.trim();\n\n const invalid = column.validate?.(value, row) ?? null;\n if (invalid) {\n setErrors((current) => ({ ...current, [id]: invalid }));\n return;\n }\n\n setErrors((current) => {\n const next = { ...current };\n delete next[id];\n return next;\n });\n setOverrides((current) => ({ ...current, [id]: value }));\n moveFrom(id, move);\n void persist(id, column, {\n row,\n key: column.key,\n value,\n previous,\n rowIndex: absoluteIndex(pageIndex),\n });\n },\n [displayed, moveFrom, persist, absoluteIndex],\n );\n\n const tableColumns = useMemo<TableColumn<T>[]>(\n () =>\n columns.map((column) => {\n const isSorted = sort?.key === column.key;\n const indicator = isSorted ? (sort?.direction === \"asc\" ? \" ▲\" : \" ▼\") : \"\";\n const header = column.sortable ? (\n <button\n type=\"button\"\n className={styles.sortButton}\n onClick={() => toggleSort(column.key)}\n aria-label={`Ordenar por ${headerText(column)}`}\n >\n {column.header}\n <span className={styles.sortIndicator} aria-hidden>\n {indicator}\n </span>\n </button>\n ) : (\n column.header\n );\n\n const plainCell = (row: T): ReactNode => {\n if (column.render) return column.render(row);\n return (row[column.key] as ReactNode) ?? null;\n };\n\n /**\n * Render an editable cell's content against the optimistic value.\n *\n * The row is shallow-patched rather than the value passed alongside it,\n * because a column with a custom `render` (a `<Money>`, a badge) reads\n * the row — handing it the stale row would show the old number under a\n * cell the user just changed.\n */\n const patchedCell = (row: T, value: unknown): ReactNode => {\n const patched = { ...row, [column.key]: value } as T;\n if (column.render) return column.render(patched);\n return (patched[column.key] as ReactNode) ?? null;\n };\n\n const editable = editingEnabled && column.editable === true;\n\n return {\n key: String(column.key),\n header,\n align: column.align,\n priority: column.priority,\n width: column.width,\n render: editable\n ? (row: T, index: number) => {\n const id = cellId(rowKey(row, absoluteIndex(index)), column.key);\n const value = displayed(row, column, id);\n const text = column.formatEdit\n ? column.formatEdit(row)\n : String(value ?? \"\");\n return (\n <EditableCell\n text={text}\n columnLabel={headerText(column)}\n rowNumber={index + 1}\n inputType={column.editorType ?? \"text\"}\n editing={editing === id}\n refocus={refocus === id}\n saving={saving[id] === true}\n error={errors[id] ?? null}\n errorId={`tempest-cell-error-${id.replace(/[^\\w-]/g, \"_\")}`}\n labels={labels}\n onOpen={() => {\n setEditing(id);\n setRefocus(null);\n }}\n onCommit={(raw, move) =>\n commit(row, column, id, index, raw, move)\n }\n onCancel={() => {\n setEditing(null);\n setRefocus(id);\n setErrors((current) => {\n const next = { ...current };\n delete next[id];\n return next;\n });\n }}\n >\n {patchedCell(row, value)}\n </EditableCell>\n );\n }\n : plainCell,\n };\n }),\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [\n columns,\n sort,\n editingEnabled,\n editing,\n refocus,\n saving,\n errors,\n labels,\n overrides,\n absoluteIndex,\n commit,\n ],\n );\n\n return (\n <div className={cn(styles.wrapper, className)} {...rest}>\n {searchable && (\n <SearchBar\n value={search}\n onChange={(value) => {\n setSearch(value);\n setPage(1);\n }}\n wrapperClassName={styles.search}\n />\n )}\n <Table\n columns={tableColumns}\n data={pageRows}\n rowKey={(row, index) => rowKey(row, absoluteIndex(index))}\n emptyMessage={emptyMessage}\n />\n {totalPages > 1 && (\n <Pagination\n page={safePage}\n totalPages={totalPages}\n onPageChange={setPage}\n totalItems={sorted.length}\n />\n )}\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"DataTable.cjs","names":[],"sources":["../../../src/components/DataTable/DataTable.tsx"],"sourcesContent":["/**\n * @tempest-limits file-lines, props-count, function-lines — the table does four jobs\n * a caller turns on independently — paging (pageSize), search (searchable,\n * searchKeys), sort (initialSort) and inline edit (onCellChange, editLabels) — over\n * one row model (data, columns, rowKey, emptyMessage). The body is long because\n * those four share the derived-rows pipeline: filter, then sort, then page, then map\n * to cells, in that order and off the same memo.\n */\nimport { useCallback, useEffect, useMemo, useState } from \"react\";\nimport type { HTMLAttributes, ReactNode } from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport { compareValues } from \"@/utils/compare-values\";\nimport { usePagination } from \"@/hooks\";\nimport { useAnnounce } from \"@/hooks/use-announce\";\nimport { Table, type TableAlign, type TableColumn, type TablePriority } from \"../Table\";\nimport { Pagination } from \"../Pagination\";\nimport { SearchBar } from \"../SearchBar\";\nimport { EditableCell } from \"./EditableCell\";\nimport { DEFAULT_EDIT_LABELS, type CellCommitMove, type DataTableEditLabels } from \"./edit-labels\";\nimport styles from \"./DataTable.module.css\";\n\nexport type SortDirection = \"asc\" | \"desc\";\n\nexport interface DataTableSort<T> {\n key: keyof T;\n direction: SortDirection;\n}\n\n/** Input types an editable column can use. */\nexport type DataTableEditorType = \"text\" | \"number\" | \"date\" | \"email\" | \"tel\" | \"url\";\n\n/** One accepted cell edit, handed to `onCellChange`. */\nexport interface DataTableCellChange<T> {\n /** The row as it was before the edit. */\n row: T;\n /** Which column changed. */\n key: keyof T;\n /** The parsed new value. */\n value: unknown;\n /** The value that was displayed before the edit. */\n previous: unknown;\n /** Index of the row in the full `data` array. */\n rowIndex: number;\n}\n\n/**\n * Column definition for {@link DataTable}. Extends the headless {@link Table}\n * column shape with a typed `key`, opt-in sorting, opt-in inline editing, and the\n * visual options that are forwarded to the underlying Table cell.\n */\nexport interface DataTableColumn<T> {\n /** Property of the row this column reads from. Doubles as the cell key. */\n key: keyof T;\n /** Column heading. */\n header: ReactNode;\n /** Custom cell renderer. Defaults to `String(row[key])`. */\n render?: (row: T) => ReactNode;\n /** Enable click-to-sort on this column's header. */\n sortable?: boolean;\n /** Text alignment forwarded to the Table cell. */\n align?: TableAlign;\n /** Responsive visibility priority forwarded to the Table cell. */\n priority?: TablePriority;\n /** Fixed column width forwarded to the Table cell. */\n width?: string | number;\n /**\n * Let cells in this column be edited in place. Requires `onCellChange` on the\n * table; without it the column stays read-only.\n */\n editable?: boolean;\n /** Editor input type. Default `\"text\"`. */\n editorType?: DataTableEditorType;\n /** Text the editor opens with. Defaults to `String(value ?? \"\")`. */\n formatEdit?: (row: T) => string;\n /**\n * Turn the typed string into the stored value. Defaults to the trimmed string,\n * or `Number(raw)` when `editorType` is `\"number\"`.\n */\n parse?: (raw: string, row: T) => unknown;\n /** Return a message to reject the edit, or `null` to accept it. */\n validate?: (value: unknown, row: T) => string | null;\n}\n\nexport interface DataTableProps<T> extends HTMLAttributes<HTMLDivElement> {\n /** Full, unfiltered dataset. Sorting/filtering/pagination happen client-side. */\n data: T[];\n /** Column definitions. */\n columns: DataTableColumn<T>[];\n /** Rows per page. Default 10. */\n pageSize?: number;\n /** Render a search input above the table. Default false. */\n searchable?: boolean;\n /**\n * Keys to match the search term against. When omitted, every column whose\n * value is a string or number is searched.\n */\n searchKeys?: (keyof T)[];\n /** Initial sort applied before any header interaction. */\n initialSort?: DataTableSort<T>;\n /** Stable key extractor for rows. Defaults to the row index. */\n rowKey?: (row: T, index: number) => string | number;\n /** Content shown when no rows match. */\n emptyMessage?: ReactNode;\n /**\n * Persist an accepted cell edit. Return a promise: while it is pending the cell\n * already shows the new value, and a rejection rolls that back and surfaces the\n * error in the cell. Without this prop no column is editable.\n */\n onCellChange?: (change: DataTableCellChange<T>) => void | Promise<void>;\n /** Override the PT-BR copy of the editing affordances. */\n editLabels?: Partial<DataTableEditLabels>;\n}\n\n/** Identity of one cell, stable across re-renders and pagination. */\nfunction cellId(rowKeyValue: string | number, columnKey: PropertyKey): string {\n return `${String(rowKeyValue)}::${String(columnKey)}`;\n}\n\nfunction headerText<T>(column: DataTableColumn<T>): string {\n return typeof column.header === \"string\" ? column.header : String(column.key);\n}\n\n/**\n * Stateful, headless data table built on top of {@link Table}. Adds\n * client-side searching, click-to-sort columns, pagination and opt-in inline\n * editing while delegating all table markup to the underlying Table component.\n *\n * - Clicking a sortable header cycles asc → desc → unsorted.\n * - Search matches a case-insensitive substring across `searchKeys`\n * (or every string/number column when not provided).\n * - Pagination is hidden when the result fits on a single page.\n * - A column with `editable` renders a button that opens an inline editor;\n * `Enter` commits, `Escape` discards, `Tab` walks to the next editable cell.\n *\n * Editing is strictly opt-in: with no `editable` column (or no `onCellChange`) the\n * rendered markup is byte-for-byte what it was before the feature existed, which\n * matters because the component is published.\n *\n * ## Optimistic, with a visible rollback\n *\n * An accepted edit is shown immediately and `onCellChange` runs in the background.\n * If it rejects, the cell returns to the old value **and** shows the reason as a\n * `role=\"alert\"` tied to the cell. A silent revert is worse than no optimistic\n * update at all: the user watched their edit appear and has no reason to doubt it.\n *\n * The header memo depends on `columns`, `sort` and the editing state only:\n * `toggleSort` and the commit callbacks are recreated each render but always close\n * over the same setters, so including them would rebuild every header on every\n * render without changing behaviour. That is why `exhaustive-deps` is silenced on\n * that dependency array.\n */\nexport function DataTable<T>({\n data,\n columns,\n pageSize = 10,\n searchable = false,\n searchKeys,\n initialSort,\n rowKey = (_row, index) => index,\n emptyMessage,\n onCellChange,\n editLabels,\n className,\n ...rest\n}: DataTableProps<T>) {\n const [search, setSearch] = useState<string>(\"\");\n const [sort, setSort] = useState<DataTableSort<T> | null>(initialSort ?? null);\n const { page, setPage } = usePagination(1, pageSize);\n const announce = useAnnounce();\n\n const [editing, setEditing] = useState<string | null>(null);\n const [refocus, setRefocus] = useState<string | null>(null);\n const [overrides, setOverrides] = useState<Record<string, unknown>>({});\n const [errors, setErrors] = useState<Record<string, string>>({});\n const [saving, setSaving] = useState<Record<string, boolean>>({});\n\n const labels = useMemo<DataTableEditLabels>(\n () => ({ ...DEFAULT_EDIT_LABELS, ...editLabels }),\n [editLabels],\n );\n const editingEnabled = onCellChange !== undefined && columns.some((column) => column.editable);\n\n const effectiveSearchKeys = useMemo<(keyof T)[]>(() => {\n if (searchKeys && searchKeys.length > 0) return searchKeys;\n return columns\n .filter((column) => {\n const sample = data.find((row) => row[column.key] != null);\n const value = sample ? sample[column.key] : undefined;\n return typeof value === \"string\" || typeof value === \"number\";\n })\n .map((column) => column.key);\n }, [searchKeys, columns, data]);\n\n const filtered = useMemo<T[]>(() => {\n const term = search.trim().toLowerCase();\n if (!term || !searchable) return data;\n return data.filter((row) =>\n effectiveSearchKeys.some((key) => {\n const value = row[key];\n return value != null && String(value).toLowerCase().includes(term);\n }),\n );\n }, [data, search, searchable, effectiveSearchKeys]);\n\n const sorted = useMemo<T[]>(() => {\n if (!sort) return filtered;\n const factor = sort.direction === \"asc\" ? 1 : -1;\n return [...filtered].sort((a, b) => compareValues(a[sort.key], b[sort.key]) * factor);\n }, [filtered, sort]);\n\n const totalPages = Math.max(1, Math.ceil(sorted.length / pageSize));\n\n // Clamp the current page when the dataset shrinks (e.g. after filtering).\n useEffect(() => {\n if (page > totalPages) setPage(totalPages);\n }, [page, totalPages, setPage]);\n\n const safePage = Math.min(page, totalPages);\n const pageRows = useMemo<T[]>(() => {\n const start = (safePage - 1) * pageSize;\n return sorted.slice(start, start + pageSize);\n }, [sorted, safePage, pageSize]);\n\n function toggleSort(key: keyof T): void {\n setSort((current) => {\n if (!current || current.key !== key) return { key, direction: \"asc\" };\n if (current.direction === \"asc\") return { key, direction: \"desc\" };\n return null;\n });\n }\n\n const absoluteIndex = useCallback(\n (pageIndex: number) => (safePage - 1) * pageSize + pageIndex,\n [safePage, pageSize],\n );\n\n /**\n * Every editable cell on the page, row-major — the order `Tab` walks.\n *\n * Row-major and not column-major because a row is the record a user is\n * correcting; walking down a column would make them re-find their place on\n * every keystroke.\n */\n const editableCellIds = useMemo<string[]>(() => {\n if (!editingEnabled) return [];\n const ids: string[] = [];\n pageRows.forEach((row, index) => {\n const key = rowKey(row, absoluteIndex(index));\n for (const column of columns) {\n if (column.editable) ids.push(cellId(key, column.key));\n }\n });\n return ids;\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [editingEnabled, pageRows, columns, absoluteIndex]);\n\n const displayed = useCallback(\n (row: T, column: DataTableColumn<T>, id: string): unknown =>\n id in overrides ? overrides[id] : row[column.key],\n [overrides],\n );\n\n /**\n * Run the caller's `onCellChange` behind the optimistic update.\n *\n * Only the *success* is announced. The failure already renders as a\n * `role=\"alert\"` inside the cell, which screen readers read on insertion —\n * announcing it again from the shared region would read it twice and put the\n * same text in the document twice.\n */\n const persist = useCallback(\n async (id: string, column: DataTableColumn<T>, change: DataTableCellChange<T>) => {\n setSaving((current) => ({ ...current, [id]: true }));\n try {\n await onCellChange?.(change);\n announce(labels.saved(headerText(column)));\n } catch (error) {\n setOverrides((current) => {\n const next = { ...current };\n delete next[id];\n return next;\n });\n const message =\n error instanceof Error && error.message\n ? error.message\n : labels.saveFailed(headerText(column));\n setErrors((current) => ({ ...current, [id]: message }));\n } finally {\n setSaving((current) => {\n const next = { ...current };\n delete next[id];\n return next;\n });\n }\n },\n [onCellChange, announce, labels],\n );\n\n const moveFrom = useCallback(\n (id: string, move: CellCommitMove): void => {\n if (move === \"none\") {\n setEditing(null);\n setRefocus(id);\n return;\n }\n const index = editableCellIds.indexOf(id);\n const target = editableCellIds[index + (move === \"next\" ? 1 : -1)];\n if (target === undefined) {\n setEditing(null);\n setRefocus(id);\n return;\n }\n setEditing(target);\n setRefocus(null);\n },\n [editableCellIds],\n );\n\n /**\n * Parse, validate, stage optimistically, then save in the background.\n *\n * Validation runs before anything is staged, and a rejection leaves the editor\n * open with the message attached to the input — the user has to be able to fix\n * what they typed without retyping it.\n */\n const commit = useCallback(\n (\n row: T,\n column: DataTableColumn<T>,\n id: string,\n pageIndex: number,\n raw: string,\n move: CellCommitMove,\n ): void => {\n const previous = displayed(row, column, id);\n const currentText = column.formatEdit ? column.formatEdit(row) : String(previous ?? \"\");\n if (raw === currentText) {\n moveFrom(id, move);\n return;\n }\n\n const value = column.parse\n ? column.parse(raw, row)\n : column.editorType === \"number\"\n ? Number(raw)\n : raw.trim();\n\n const invalid = column.validate?.(value, row) ?? null;\n if (invalid) {\n setErrors((current) => ({ ...current, [id]: invalid }));\n return;\n }\n\n setErrors((current) => {\n const next = { ...current };\n delete next[id];\n return next;\n });\n setOverrides((current) => ({ ...current, [id]: value }));\n moveFrom(id, move);\n void persist(id, column, {\n row,\n key: column.key,\n value,\n previous,\n rowIndex: absoluteIndex(pageIndex),\n });\n },\n [displayed, moveFrom, persist, absoluteIndex],\n );\n\n const tableColumns = useMemo<TableColumn<T>[]>(\n () =>\n columns.map((column) => {\n const isSorted = sort?.key === column.key;\n const indicator = isSorted ? (sort?.direction === \"asc\" ? \" ▲\" : \" ▼\") : \"\";\n const header = column.sortable ? (\n <button\n type=\"button\"\n className={styles.sortButton}\n onClick={() => toggleSort(column.key)}\n aria-label={`Ordenar por ${headerText(column)}`}\n >\n {column.header}\n <span className={styles.sortIndicator} aria-hidden>\n {indicator}\n </span>\n </button>\n ) : (\n column.header\n );\n\n const plainCell = (row: T): ReactNode => {\n if (column.render) return column.render(row);\n return (row[column.key] as ReactNode) ?? null;\n };\n\n /**\n * Render an editable cell's content against the optimistic value.\n *\n * The row is shallow-patched rather than the value passed alongside it,\n * because a column with a custom `render` (a `<Money>`, a badge) reads\n * the row — handing it the stale row would show the old number under a\n * cell the user just changed.\n */\n const patchedCell = (row: T, value: unknown): ReactNode => {\n const patched = { ...row, [column.key]: value } as T;\n if (column.render) return column.render(patched);\n return (patched[column.key] as ReactNode) ?? null;\n };\n\n const editable = editingEnabled && column.editable === true;\n\n return {\n key: String(column.key),\n header,\n align: column.align,\n priority: column.priority,\n width: column.width,\n render: editable\n ? (row: T, index: number) => {\n const id = cellId(rowKey(row, absoluteIndex(index)), column.key);\n const value = displayed(row, column, id);\n const text = column.formatEdit\n ? column.formatEdit(row)\n : String(value ?? \"\");\n return (\n <EditableCell\n text={text}\n columnLabel={headerText(column)}\n rowNumber={index + 1}\n inputType={column.editorType ?? \"text\"}\n editing={editing === id}\n refocus={refocus === id}\n saving={saving[id] === true}\n error={errors[id] ?? null}\n errorId={`tempest-cell-error-${id.replace(/[^\\w-]/g, \"_\")}`}\n labels={labels}\n onOpen={() => {\n setEditing(id);\n setRefocus(null);\n }}\n onCommit={(raw, move) =>\n commit(row, column, id, index, raw, move)\n }\n onCancel={() => {\n setEditing(null);\n setRefocus(id);\n setErrors((current) => {\n const next = { ...current };\n delete next[id];\n return next;\n });\n }}\n >\n {patchedCell(row, value)}\n </EditableCell>\n );\n }\n : plainCell,\n };\n }),\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [\n columns,\n sort,\n editingEnabled,\n editing,\n refocus,\n saving,\n errors,\n labels,\n overrides,\n absoluteIndex,\n commit,\n ],\n );\n\n return (\n <div className={cn(styles.wrapper, className)} {...rest}>\n {searchable && (\n <SearchBar\n value={search}\n onChange={(value) => {\n setSearch(value);\n setPage(1);\n }}\n wrapperClassName={styles.search}\n />\n )}\n <Table\n columns={tableColumns}\n data={pageRows}\n rowKey={(row, index) => rowKey(row, absoluteIndex(index))}\n emptyMessage={emptyMessage}\n />\n {totalPages > 1 && (\n <Pagination\n page={safePage}\n totalPages={totalPages}\n onPageChange={setPage}\n totalItems={sorted.length}\n />\n )}\n </div>\n );\n}\n"],"mappings":"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"}
@@ -175,9 +175,9 @@ function g({ data: g, columns: _, pageSize: v = 10, searchable: y = !1, searchKe
175
175
  priority: e.priority,
176
176
  width: e.width,
177
177
  render: a ? (t, n) => {
178
- let r = m(x(t, J(n)), e.key), a = X(t, e, r);
178
+ let r = m(x(t, J(n)), e.key), a = X(t, e, r), o = e.formatEdit ? e.formatEdit(t) : String(a ?? "");
179
179
  return /* @__PURE__ */ f(c, {
180
- text: e.formatEdit ? e.formatEdit(t) : String(a ?? ""),
180
+ text: o,
181
181
  columnLabel: h(e),
182
182
  rowNumber: n + 1,
183
183
  inputType: e.editorType ?? "text",
@@ -1 +1 @@
1
- {"version":3,"file":"DataTable.js","names":[],"sources":["../../../src/components/DataTable/DataTable.tsx"],"sourcesContent":["/**\n * @tempest-limits file-lines, props-count, function-lines — the table does four jobs\n * a caller turns on independently — paging (pageSize), search (searchable,\n * searchKeys), sort (initialSort) and inline edit (onCellChange, editLabels) — over\n * one row model (data, columns, rowKey, emptyMessage). The body is long because\n * those four share the derived-rows pipeline: filter, then sort, then page, then map\n * to cells, in that order and off the same memo.\n */\nimport { useCallback, useEffect, useMemo, useState } from \"react\";\nimport type { HTMLAttributes, ReactNode } from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport { compareValues } from \"@/utils/compare-values\";\nimport { usePagination } from \"@/hooks\";\nimport { useAnnounce } from \"@/hooks/use-announce\";\nimport { Table, type TableAlign, type TableColumn, type TablePriority } from \"../Table\";\nimport { Pagination } from \"../Pagination\";\nimport { SearchBar } from \"../SearchBar\";\nimport { EditableCell } from \"./EditableCell\";\nimport { DEFAULT_EDIT_LABELS, type CellCommitMove, type DataTableEditLabels } from \"./edit-labels\";\nimport styles from \"./DataTable.module.css\";\n\nexport type SortDirection = \"asc\" | \"desc\";\n\nexport interface DataTableSort<T> {\n key: keyof T;\n direction: SortDirection;\n}\n\n/** Input types an editable column can use. */\nexport type DataTableEditorType = \"text\" | \"number\" | \"date\" | \"email\" | \"tel\" | \"url\";\n\n/** One accepted cell edit, handed to `onCellChange`. */\nexport interface DataTableCellChange<T> {\n /** The row as it was before the edit. */\n row: T;\n /** Which column changed. */\n key: keyof T;\n /** The parsed new value. */\n value: unknown;\n /** The value that was displayed before the edit. */\n previous: unknown;\n /** Index of the row in the full `data` array. */\n rowIndex: number;\n}\n\n/**\n * Column definition for {@link DataTable}. Extends the headless {@link Table}\n * column shape with a typed `key`, opt-in sorting, opt-in inline editing, and the\n * visual options that are forwarded to the underlying Table cell.\n */\nexport interface DataTableColumn<T> {\n /** Property of the row this column reads from. Doubles as the cell key. */\n key: keyof T;\n /** Column heading. */\n header: ReactNode;\n /** Custom cell renderer. Defaults to `String(row[key])`. */\n render?: (row: T) => ReactNode;\n /** Enable click-to-sort on this column's header. */\n sortable?: boolean;\n /** Text alignment forwarded to the Table cell. */\n align?: TableAlign;\n /** Responsive visibility priority forwarded to the Table cell. */\n priority?: TablePriority;\n /** Fixed column width forwarded to the Table cell. */\n width?: string | number;\n /**\n * Let cells in this column be edited in place. Requires `onCellChange` on the\n * table; without it the column stays read-only.\n */\n editable?: boolean;\n /** Editor input type. Default `\"text\"`. */\n editorType?: DataTableEditorType;\n /** Text the editor opens with. Defaults to `String(value ?? \"\")`. */\n formatEdit?: (row: T) => string;\n /**\n * Turn the typed string into the stored value. Defaults to the trimmed string,\n * or `Number(raw)` when `editorType` is `\"number\"`.\n */\n parse?: (raw: string, row: T) => unknown;\n /** Return a message to reject the edit, or `null` to accept it. */\n validate?: (value: unknown, row: T) => string | null;\n}\n\nexport interface DataTableProps<T> extends HTMLAttributes<HTMLDivElement> {\n /** Full, unfiltered dataset. Sorting/filtering/pagination happen client-side. */\n data: T[];\n /** Column definitions. */\n columns: DataTableColumn<T>[];\n /** Rows per page. Default 10. */\n pageSize?: number;\n /** Render a search input above the table. Default false. */\n searchable?: boolean;\n /**\n * Keys to match the search term against. When omitted, every column whose\n * value is a string or number is searched.\n */\n searchKeys?: (keyof T)[];\n /** Initial sort applied before any header interaction. */\n initialSort?: DataTableSort<T>;\n /** Stable key extractor for rows. Defaults to the row index. */\n rowKey?: (row: T, index: number) => string | number;\n /** Content shown when no rows match. */\n emptyMessage?: ReactNode;\n /**\n * Persist an accepted cell edit. Return a promise: while it is pending the cell\n * already shows the new value, and a rejection rolls that back and surfaces the\n * error in the cell. Without this prop no column is editable.\n */\n onCellChange?: (change: DataTableCellChange<T>) => void | Promise<void>;\n /** Override the PT-BR copy of the editing affordances. */\n editLabels?: Partial<DataTableEditLabels>;\n}\n\n/** Identity of one cell, stable across re-renders and pagination. */\nfunction cellId(rowKeyValue: string | number, columnKey: PropertyKey): string {\n return `${String(rowKeyValue)}::${String(columnKey)}`;\n}\n\nfunction headerText<T>(column: DataTableColumn<T>): string {\n return typeof column.header === \"string\" ? column.header : String(column.key);\n}\n\n/**\n * Stateful, headless data table built on top of {@link Table}. Adds\n * client-side searching, click-to-sort columns, pagination and opt-in inline\n * editing while delegating all table markup to the underlying Table component.\n *\n * - Clicking a sortable header cycles asc → desc → unsorted.\n * - Search matches a case-insensitive substring across `searchKeys`\n * (or every string/number column when not provided).\n * - Pagination is hidden when the result fits on a single page.\n * - A column with `editable` renders a button that opens an inline editor;\n * `Enter` commits, `Escape` discards, `Tab` walks to the next editable cell.\n *\n * Editing is strictly opt-in: with no `editable` column (or no `onCellChange`) the\n * rendered markup is byte-for-byte what it was before the feature existed, which\n * matters because the component is published.\n *\n * ## Optimistic, with a visible rollback\n *\n * An accepted edit is shown immediately and `onCellChange` runs in the background.\n * If it rejects, the cell returns to the old value **and** shows the reason as a\n * `role=\"alert\"` tied to the cell. A silent revert is worse than no optimistic\n * update at all: the user watched their edit appear and has no reason to doubt it.\n *\n * The header memo depends on `columns`, `sort` and the editing state only:\n * `toggleSort` and the commit callbacks are recreated each render but always close\n * over the same setters, so including them would rebuild every header on every\n * render without changing behaviour. That is why `exhaustive-deps` is silenced on\n * that dependency array.\n */\nexport function DataTable<T>({\n data,\n columns,\n pageSize = 10,\n searchable = false,\n searchKeys,\n initialSort,\n rowKey = (_row, index) => index,\n emptyMessage,\n onCellChange,\n editLabels,\n className,\n ...rest\n}: DataTableProps<T>) {\n const [search, setSearch] = useState<string>(\"\");\n const [sort, setSort] = useState<DataTableSort<T> | null>(initialSort ?? null);\n const { page, setPage } = usePagination(1, pageSize);\n const announce = useAnnounce();\n\n const [editing, setEditing] = useState<string | null>(null);\n const [refocus, setRefocus] = useState<string | null>(null);\n const [overrides, setOverrides] = useState<Record<string, unknown>>({});\n const [errors, setErrors] = useState<Record<string, string>>({});\n const [saving, setSaving] = useState<Record<string, boolean>>({});\n\n const labels = useMemo<DataTableEditLabels>(\n () => ({ ...DEFAULT_EDIT_LABELS, ...editLabels }),\n [editLabels],\n );\n const editingEnabled = onCellChange !== undefined && columns.some((column) => column.editable);\n\n const effectiveSearchKeys = useMemo<(keyof T)[]>(() => {\n if (searchKeys && searchKeys.length > 0) return searchKeys;\n return columns\n .filter((column) => {\n const sample = data.find((row) => row[column.key] != null);\n const value = sample ? sample[column.key] : undefined;\n return typeof value === \"string\" || typeof value === \"number\";\n })\n .map((column) => column.key);\n }, [searchKeys, columns, data]);\n\n const filtered = useMemo<T[]>(() => {\n const term = search.trim().toLowerCase();\n if (!term || !searchable) return data;\n return data.filter((row) =>\n effectiveSearchKeys.some((key) => {\n const value = row[key];\n return value != null && String(value).toLowerCase().includes(term);\n }),\n );\n }, [data, search, searchable, effectiveSearchKeys]);\n\n const sorted = useMemo<T[]>(() => {\n if (!sort) return filtered;\n const factor = sort.direction === \"asc\" ? 1 : -1;\n return [...filtered].sort((a, b) => compareValues(a[sort.key], b[sort.key]) * factor);\n }, [filtered, sort]);\n\n const totalPages = Math.max(1, Math.ceil(sorted.length / pageSize));\n\n // Clamp the current page when the dataset shrinks (e.g. after filtering).\n useEffect(() => {\n if (page > totalPages) setPage(totalPages);\n }, [page, totalPages, setPage]);\n\n const safePage = Math.min(page, totalPages);\n const pageRows = useMemo<T[]>(() => {\n const start = (safePage - 1) * pageSize;\n return sorted.slice(start, start + pageSize);\n }, [sorted, safePage, pageSize]);\n\n function toggleSort(key: keyof T): void {\n setSort((current) => {\n if (!current || current.key !== key) return { key, direction: \"asc\" };\n if (current.direction === \"asc\") return { key, direction: \"desc\" };\n return null;\n });\n }\n\n const absoluteIndex = useCallback(\n (pageIndex: number) => (safePage - 1) * pageSize + pageIndex,\n [safePage, pageSize],\n );\n\n /**\n * Every editable cell on the page, row-major — the order `Tab` walks.\n *\n * Row-major and not column-major because a row is the record a user is\n * correcting; walking down a column would make them re-find their place on\n * every keystroke.\n */\n const editableCellIds = useMemo<string[]>(() => {\n if (!editingEnabled) return [];\n const ids: string[] = [];\n pageRows.forEach((row, index) => {\n const key = rowKey(row, absoluteIndex(index));\n for (const column of columns) {\n if (column.editable) ids.push(cellId(key, column.key));\n }\n });\n return ids;\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [editingEnabled, pageRows, columns, absoluteIndex]);\n\n const displayed = useCallback(\n (row: T, column: DataTableColumn<T>, id: string): unknown =>\n id in overrides ? overrides[id] : row[column.key],\n [overrides],\n );\n\n /**\n * Run the caller's `onCellChange` behind the optimistic update.\n *\n * Only the *success* is announced. The failure already renders as a\n * `role=\"alert\"` inside the cell, which screen readers read on insertion —\n * announcing it again from the shared region would read it twice and put the\n * same text in the document twice.\n */\n const persist = useCallback(\n async (id: string, column: DataTableColumn<T>, change: DataTableCellChange<T>) => {\n setSaving((current) => ({ ...current, [id]: true }));\n try {\n await onCellChange?.(change);\n announce(labels.saved(headerText(column)));\n } catch (error) {\n setOverrides((current) => {\n const next = { ...current };\n delete next[id];\n return next;\n });\n const message =\n error instanceof Error && error.message\n ? error.message\n : labels.saveFailed(headerText(column));\n setErrors((current) => ({ ...current, [id]: message }));\n } finally {\n setSaving((current) => {\n const next = { ...current };\n delete next[id];\n return next;\n });\n }\n },\n [onCellChange, announce, labels],\n );\n\n const moveFrom = useCallback(\n (id: string, move: CellCommitMove): void => {\n if (move === \"none\") {\n setEditing(null);\n setRefocus(id);\n return;\n }\n const index = editableCellIds.indexOf(id);\n const target = editableCellIds[index + (move === \"next\" ? 1 : -1)];\n if (target === undefined) {\n setEditing(null);\n setRefocus(id);\n return;\n }\n setEditing(target);\n setRefocus(null);\n },\n [editableCellIds],\n );\n\n /**\n * Parse, validate, stage optimistically, then save in the background.\n *\n * Validation runs before anything is staged, and a rejection leaves the editor\n * open with the message attached to the input — the user has to be able to fix\n * what they typed without retyping it.\n */\n const commit = useCallback(\n (\n row: T,\n column: DataTableColumn<T>,\n id: string,\n pageIndex: number,\n raw: string,\n move: CellCommitMove,\n ): void => {\n const previous = displayed(row, column, id);\n const currentText = column.formatEdit ? column.formatEdit(row) : String(previous ?? \"\");\n if (raw === currentText) {\n moveFrom(id, move);\n return;\n }\n\n const value = column.parse\n ? column.parse(raw, row)\n : column.editorType === \"number\"\n ? Number(raw)\n : raw.trim();\n\n const invalid = column.validate?.(value, row) ?? null;\n if (invalid) {\n setErrors((current) => ({ ...current, [id]: invalid }));\n return;\n }\n\n setErrors((current) => {\n const next = { ...current };\n delete next[id];\n return next;\n });\n setOverrides((current) => ({ ...current, [id]: value }));\n moveFrom(id, move);\n void persist(id, column, {\n row,\n key: column.key,\n value,\n previous,\n rowIndex: absoluteIndex(pageIndex),\n });\n },\n [displayed, moveFrom, persist, absoluteIndex],\n );\n\n const tableColumns = useMemo<TableColumn<T>[]>(\n () =>\n columns.map((column) => {\n const isSorted = sort?.key === column.key;\n const indicator = isSorted ? (sort?.direction === \"asc\" ? \" ▲\" : \" ▼\") : \"\";\n const header = column.sortable ? (\n <button\n type=\"button\"\n className={styles.sortButton}\n onClick={() => toggleSort(column.key)}\n aria-label={`Ordenar por ${headerText(column)}`}\n >\n {column.header}\n <span className={styles.sortIndicator} aria-hidden>\n {indicator}\n </span>\n </button>\n ) : (\n column.header\n );\n\n const plainCell = (row: T): ReactNode => {\n if (column.render) return column.render(row);\n return (row[column.key] as ReactNode) ?? null;\n };\n\n /**\n * Render an editable cell's content against the optimistic value.\n *\n * The row is shallow-patched rather than the value passed alongside it,\n * because a column with a custom `render` (a `<Money>`, a badge) reads\n * the row — handing it the stale row would show the old number under a\n * cell the user just changed.\n */\n const patchedCell = (row: T, value: unknown): ReactNode => {\n const patched = { ...row, [column.key]: value } as T;\n if (column.render) return column.render(patched);\n return (patched[column.key] as ReactNode) ?? null;\n };\n\n const editable = editingEnabled && column.editable === true;\n\n return {\n key: String(column.key),\n header,\n align: column.align,\n priority: column.priority,\n width: column.width,\n render: editable\n ? (row: T, index: number) => {\n const id = cellId(rowKey(row, absoluteIndex(index)), column.key);\n const value = displayed(row, column, id);\n const text = column.formatEdit\n ? column.formatEdit(row)\n : String(value ?? \"\");\n return (\n <EditableCell\n text={text}\n columnLabel={headerText(column)}\n rowNumber={index + 1}\n inputType={column.editorType ?? \"text\"}\n editing={editing === id}\n refocus={refocus === id}\n saving={saving[id] === true}\n error={errors[id] ?? null}\n errorId={`tempest-cell-error-${id.replace(/[^\\w-]/g, \"_\")}`}\n labels={labels}\n onOpen={() => {\n setEditing(id);\n setRefocus(null);\n }}\n onCommit={(raw, move) =>\n commit(row, column, id, index, raw, move)\n }\n onCancel={() => {\n setEditing(null);\n setRefocus(id);\n setErrors((current) => {\n const next = { ...current };\n delete next[id];\n return next;\n });\n }}\n >\n {patchedCell(row, value)}\n </EditableCell>\n );\n }\n : plainCell,\n };\n }),\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [\n columns,\n sort,\n editingEnabled,\n editing,\n refocus,\n saving,\n errors,\n labels,\n overrides,\n absoluteIndex,\n commit,\n ],\n );\n\n return (\n <div className={cn(styles.wrapper, className)} {...rest}>\n {searchable && (\n <SearchBar\n value={search}\n onChange={(value) => {\n setSearch(value);\n setPage(1);\n }}\n wrapperClassName={styles.search}\n />\n )}\n <Table\n columns={tableColumns}\n data={pageRows}\n rowKey={(row, index) => rowKey(row, absoluteIndex(index))}\n emptyMessage={emptyMessage}\n />\n {totalPages > 1 && (\n <Pagination\n page={safePage}\n totalPages={totalPages}\n onPageChange={setPage}\n totalItems={sorted.length}\n />\n )}\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"DataTable.js","names":[],"sources":["../../../src/components/DataTable/DataTable.tsx"],"sourcesContent":["/**\n * @tempest-limits file-lines, props-count, function-lines — the table does four jobs\n * a caller turns on independently — paging (pageSize), search (searchable,\n * searchKeys), sort (initialSort) and inline edit (onCellChange, editLabels) — over\n * one row model (data, columns, rowKey, emptyMessage). The body is long because\n * those four share the derived-rows pipeline: filter, then sort, then page, then map\n * to cells, in that order and off the same memo.\n */\nimport { useCallback, useEffect, useMemo, useState } from \"react\";\nimport type { HTMLAttributes, ReactNode } from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport { compareValues } from \"@/utils/compare-values\";\nimport { usePagination } from \"@/hooks\";\nimport { useAnnounce } from \"@/hooks/use-announce\";\nimport { Table, type TableAlign, type TableColumn, type TablePriority } from \"../Table\";\nimport { Pagination } from \"../Pagination\";\nimport { SearchBar } from \"../SearchBar\";\nimport { EditableCell } from \"./EditableCell\";\nimport { DEFAULT_EDIT_LABELS, type CellCommitMove, type DataTableEditLabels } from \"./edit-labels\";\nimport styles from \"./DataTable.module.css\";\n\nexport type SortDirection = \"asc\" | \"desc\";\n\nexport interface DataTableSort<T> {\n key: keyof T;\n direction: SortDirection;\n}\n\n/** Input types an editable column can use. */\nexport type DataTableEditorType = \"text\" | \"number\" | \"date\" | \"email\" | \"tel\" | \"url\";\n\n/** One accepted cell edit, handed to `onCellChange`. */\nexport interface DataTableCellChange<T> {\n /** The row as it was before the edit. */\n row: T;\n /** Which column changed. */\n key: keyof T;\n /** The parsed new value. */\n value: unknown;\n /** The value that was displayed before the edit. */\n previous: unknown;\n /** Index of the row in the full `data` array. */\n rowIndex: number;\n}\n\n/**\n * Column definition for {@link DataTable}. Extends the headless {@link Table}\n * column shape with a typed `key`, opt-in sorting, opt-in inline editing, and the\n * visual options that are forwarded to the underlying Table cell.\n */\nexport interface DataTableColumn<T> {\n /** Property of the row this column reads from. Doubles as the cell key. */\n key: keyof T;\n /** Column heading. */\n header: ReactNode;\n /** Custom cell renderer. Defaults to `String(row[key])`. */\n render?: (row: T) => ReactNode;\n /** Enable click-to-sort on this column's header. */\n sortable?: boolean;\n /** Text alignment forwarded to the Table cell. */\n align?: TableAlign;\n /** Responsive visibility priority forwarded to the Table cell. */\n priority?: TablePriority;\n /** Fixed column width forwarded to the Table cell. */\n width?: string | number;\n /**\n * Let cells in this column be edited in place. Requires `onCellChange` on the\n * table; without it the column stays read-only.\n */\n editable?: boolean;\n /** Editor input type. Default `\"text\"`. */\n editorType?: DataTableEditorType;\n /** Text the editor opens with. Defaults to `String(value ?? \"\")`. */\n formatEdit?: (row: T) => string;\n /**\n * Turn the typed string into the stored value. Defaults to the trimmed string,\n * or `Number(raw)` when `editorType` is `\"number\"`.\n */\n parse?: (raw: string, row: T) => unknown;\n /** Return a message to reject the edit, or `null` to accept it. */\n validate?: (value: unknown, row: T) => string | null;\n}\n\nexport interface DataTableProps<T> extends HTMLAttributes<HTMLDivElement> {\n /** Full, unfiltered dataset. Sorting/filtering/pagination happen client-side. */\n data: T[];\n /** Column definitions. */\n columns: DataTableColumn<T>[];\n /** Rows per page. Default 10. */\n pageSize?: number;\n /** Render a search input above the table. Default false. */\n searchable?: boolean;\n /**\n * Keys to match the search term against. When omitted, every column whose\n * value is a string or number is searched.\n */\n searchKeys?: (keyof T)[];\n /** Initial sort applied before any header interaction. */\n initialSort?: DataTableSort<T>;\n /** Stable key extractor for rows. Defaults to the row index. */\n rowKey?: (row: T, index: number) => string | number;\n /** Content shown when no rows match. */\n emptyMessage?: ReactNode;\n /**\n * Persist an accepted cell edit. Return a promise: while it is pending the cell\n * already shows the new value, and a rejection rolls that back and surfaces the\n * error in the cell. Without this prop no column is editable.\n */\n onCellChange?: (change: DataTableCellChange<T>) => void | Promise<void>;\n /** Override the PT-BR copy of the editing affordances. */\n editLabels?: Partial<DataTableEditLabels>;\n}\n\n/** Identity of one cell, stable across re-renders and pagination. */\nfunction cellId(rowKeyValue: string | number, columnKey: PropertyKey): string {\n return `${String(rowKeyValue)}::${String(columnKey)}`;\n}\n\nfunction headerText<T>(column: DataTableColumn<T>): string {\n return typeof column.header === \"string\" ? column.header : String(column.key);\n}\n\n/**\n * Stateful, headless data table built on top of {@link Table}. Adds\n * client-side searching, click-to-sort columns, pagination and opt-in inline\n * editing while delegating all table markup to the underlying Table component.\n *\n * - Clicking a sortable header cycles asc → desc → unsorted.\n * - Search matches a case-insensitive substring across `searchKeys`\n * (or every string/number column when not provided).\n * - Pagination is hidden when the result fits on a single page.\n * - A column with `editable` renders a button that opens an inline editor;\n * `Enter` commits, `Escape` discards, `Tab` walks to the next editable cell.\n *\n * Editing is strictly opt-in: with no `editable` column (or no `onCellChange`) the\n * rendered markup is byte-for-byte what it was before the feature existed, which\n * matters because the component is published.\n *\n * ## Optimistic, with a visible rollback\n *\n * An accepted edit is shown immediately and `onCellChange` runs in the background.\n * If it rejects, the cell returns to the old value **and** shows the reason as a\n * `role=\"alert\"` tied to the cell. A silent revert is worse than no optimistic\n * update at all: the user watched their edit appear and has no reason to doubt it.\n *\n * The header memo depends on `columns`, `sort` and the editing state only:\n * `toggleSort` and the commit callbacks are recreated each render but always close\n * over the same setters, so including them would rebuild every header on every\n * render without changing behaviour. That is why `exhaustive-deps` is silenced on\n * that dependency array.\n */\nexport function DataTable<T>({\n data,\n columns,\n pageSize = 10,\n searchable = false,\n searchKeys,\n initialSort,\n rowKey = (_row, index) => index,\n emptyMessage,\n onCellChange,\n editLabels,\n className,\n ...rest\n}: DataTableProps<T>) {\n const [search, setSearch] = useState<string>(\"\");\n const [sort, setSort] = useState<DataTableSort<T> | null>(initialSort ?? null);\n const { page, setPage } = usePagination(1, pageSize);\n const announce = useAnnounce();\n\n const [editing, setEditing] = useState<string | null>(null);\n const [refocus, setRefocus] = useState<string | null>(null);\n const [overrides, setOverrides] = useState<Record<string, unknown>>({});\n const [errors, setErrors] = useState<Record<string, string>>({});\n const [saving, setSaving] = useState<Record<string, boolean>>({});\n\n const labels = useMemo<DataTableEditLabels>(\n () => ({ ...DEFAULT_EDIT_LABELS, ...editLabels }),\n [editLabels],\n );\n const editingEnabled = onCellChange !== undefined && columns.some((column) => column.editable);\n\n const effectiveSearchKeys = useMemo<(keyof T)[]>(() => {\n if (searchKeys && searchKeys.length > 0) return searchKeys;\n return columns\n .filter((column) => {\n const sample = data.find((row) => row[column.key] != null);\n const value = sample ? sample[column.key] : undefined;\n return typeof value === \"string\" || typeof value === \"number\";\n })\n .map((column) => column.key);\n }, [searchKeys, columns, data]);\n\n const filtered = useMemo<T[]>(() => {\n const term = search.trim().toLowerCase();\n if (!term || !searchable) return data;\n return data.filter((row) =>\n effectiveSearchKeys.some((key) => {\n const value = row[key];\n return value != null && String(value).toLowerCase().includes(term);\n }),\n );\n }, [data, search, searchable, effectiveSearchKeys]);\n\n const sorted = useMemo<T[]>(() => {\n if (!sort) return filtered;\n const factor = sort.direction === \"asc\" ? 1 : -1;\n return [...filtered].sort((a, b) => compareValues(a[sort.key], b[sort.key]) * factor);\n }, [filtered, sort]);\n\n const totalPages = Math.max(1, Math.ceil(sorted.length / pageSize));\n\n // Clamp the current page when the dataset shrinks (e.g. after filtering).\n useEffect(() => {\n if (page > totalPages) setPage(totalPages);\n }, [page, totalPages, setPage]);\n\n const safePage = Math.min(page, totalPages);\n const pageRows = useMemo<T[]>(() => {\n const start = (safePage - 1) * pageSize;\n return sorted.slice(start, start + pageSize);\n }, [sorted, safePage, pageSize]);\n\n function toggleSort(key: keyof T): void {\n setSort((current) => {\n if (!current || current.key !== key) return { key, direction: \"asc\" };\n if (current.direction === \"asc\") return { key, direction: \"desc\" };\n return null;\n });\n }\n\n const absoluteIndex = useCallback(\n (pageIndex: number) => (safePage - 1) * pageSize + pageIndex,\n [safePage, pageSize],\n );\n\n /**\n * Every editable cell on the page, row-major — the order `Tab` walks.\n *\n * Row-major and not column-major because a row is the record a user is\n * correcting; walking down a column would make them re-find their place on\n * every keystroke.\n */\n const editableCellIds = useMemo<string[]>(() => {\n if (!editingEnabled) return [];\n const ids: string[] = [];\n pageRows.forEach((row, index) => {\n const key = rowKey(row, absoluteIndex(index));\n for (const column of columns) {\n if (column.editable) ids.push(cellId(key, column.key));\n }\n });\n return ids;\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [editingEnabled, pageRows, columns, absoluteIndex]);\n\n const displayed = useCallback(\n (row: T, column: DataTableColumn<T>, id: string): unknown =>\n id in overrides ? overrides[id] : row[column.key],\n [overrides],\n );\n\n /**\n * Run the caller's `onCellChange` behind the optimistic update.\n *\n * Only the *success* is announced. The failure already renders as a\n * `role=\"alert\"` inside the cell, which screen readers read on insertion —\n * announcing it again from the shared region would read it twice and put the\n * same text in the document twice.\n */\n const persist = useCallback(\n async (id: string, column: DataTableColumn<T>, change: DataTableCellChange<T>) => {\n setSaving((current) => ({ ...current, [id]: true }));\n try {\n await onCellChange?.(change);\n announce(labels.saved(headerText(column)));\n } catch (error) {\n setOverrides((current) => {\n const next = { ...current };\n delete next[id];\n return next;\n });\n const message =\n error instanceof Error && error.message\n ? error.message\n : labels.saveFailed(headerText(column));\n setErrors((current) => ({ ...current, [id]: message }));\n } finally {\n setSaving((current) => {\n const next = { ...current };\n delete next[id];\n return next;\n });\n }\n },\n [onCellChange, announce, labels],\n );\n\n const moveFrom = useCallback(\n (id: string, move: CellCommitMove): void => {\n if (move === \"none\") {\n setEditing(null);\n setRefocus(id);\n return;\n }\n const index = editableCellIds.indexOf(id);\n const target = editableCellIds[index + (move === \"next\" ? 1 : -1)];\n if (target === undefined) {\n setEditing(null);\n setRefocus(id);\n return;\n }\n setEditing(target);\n setRefocus(null);\n },\n [editableCellIds],\n );\n\n /**\n * Parse, validate, stage optimistically, then save in the background.\n *\n * Validation runs before anything is staged, and a rejection leaves the editor\n * open with the message attached to the input — the user has to be able to fix\n * what they typed without retyping it.\n */\n const commit = useCallback(\n (\n row: T,\n column: DataTableColumn<T>,\n id: string,\n pageIndex: number,\n raw: string,\n move: CellCommitMove,\n ): void => {\n const previous = displayed(row, column, id);\n const currentText = column.formatEdit ? column.formatEdit(row) : String(previous ?? \"\");\n if (raw === currentText) {\n moveFrom(id, move);\n return;\n }\n\n const value = column.parse\n ? column.parse(raw, row)\n : column.editorType === \"number\"\n ? Number(raw)\n : raw.trim();\n\n const invalid = column.validate?.(value, row) ?? null;\n if (invalid) {\n setErrors((current) => ({ ...current, [id]: invalid }));\n return;\n }\n\n setErrors((current) => {\n const next = { ...current };\n delete next[id];\n return next;\n });\n setOverrides((current) => ({ ...current, [id]: value }));\n moveFrom(id, move);\n void persist(id, column, {\n row,\n key: column.key,\n value,\n previous,\n rowIndex: absoluteIndex(pageIndex),\n });\n },\n [displayed, moveFrom, persist, absoluteIndex],\n );\n\n const tableColumns = useMemo<TableColumn<T>[]>(\n () =>\n columns.map((column) => {\n const isSorted = sort?.key === column.key;\n const indicator = isSorted ? (sort?.direction === \"asc\" ? \" ▲\" : \" ▼\") : \"\";\n const header = column.sortable ? (\n <button\n type=\"button\"\n className={styles.sortButton}\n onClick={() => toggleSort(column.key)}\n aria-label={`Ordenar por ${headerText(column)}`}\n >\n {column.header}\n <span className={styles.sortIndicator} aria-hidden>\n {indicator}\n </span>\n </button>\n ) : (\n column.header\n );\n\n const plainCell = (row: T): ReactNode => {\n if (column.render) return column.render(row);\n return (row[column.key] as ReactNode) ?? null;\n };\n\n /**\n * Render an editable cell's content against the optimistic value.\n *\n * The row is shallow-patched rather than the value passed alongside it,\n * because a column with a custom `render` (a `<Money>`, a badge) reads\n * the row — handing it the stale row would show the old number under a\n * cell the user just changed.\n */\n const patchedCell = (row: T, value: unknown): ReactNode => {\n const patched = { ...row, [column.key]: value } as T;\n if (column.render) return column.render(patched);\n return (patched[column.key] as ReactNode) ?? null;\n };\n\n const editable = editingEnabled && column.editable === true;\n\n return {\n key: String(column.key),\n header,\n align: column.align,\n priority: column.priority,\n width: column.width,\n render: editable\n ? (row: T, index: number) => {\n const id = cellId(rowKey(row, absoluteIndex(index)), column.key);\n const value = displayed(row, column, id);\n const text = column.formatEdit\n ? column.formatEdit(row)\n : String(value ?? \"\");\n return (\n <EditableCell\n text={text}\n columnLabel={headerText(column)}\n rowNumber={index + 1}\n inputType={column.editorType ?? \"text\"}\n editing={editing === id}\n refocus={refocus === id}\n saving={saving[id] === true}\n error={errors[id] ?? null}\n errorId={`tempest-cell-error-${id.replace(/[^\\w-]/g, \"_\")}`}\n labels={labels}\n onOpen={() => {\n setEditing(id);\n setRefocus(null);\n }}\n onCommit={(raw, move) =>\n commit(row, column, id, index, raw, move)\n }\n onCancel={() => {\n setEditing(null);\n setRefocus(id);\n setErrors((current) => {\n const next = { ...current };\n delete next[id];\n return next;\n });\n }}\n >\n {patchedCell(row, value)}\n </EditableCell>\n );\n }\n : plainCell,\n };\n }),\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [\n columns,\n sort,\n editingEnabled,\n editing,\n refocus,\n saving,\n errors,\n labels,\n overrides,\n absoluteIndex,\n commit,\n ],\n );\n\n return (\n <div className={cn(styles.wrapper, className)} {...rest}>\n {searchable && (\n <SearchBar\n value={search}\n onChange={(value) => {\n setSearch(value);\n setPage(1);\n }}\n wrapperClassName={styles.search}\n />\n )}\n <Table\n columns={tableColumns}\n data={pageRows}\n rowKey={(row, index) => rowKey(row, absoluteIndex(index))}\n emptyMessage={emptyMessage}\n />\n {totalPages > 1 && (\n <Pagination\n page={safePage}\n totalPages={totalPages}\n onPageChange={setPage}\n totalItems={sorted.length}\n />\n )}\n </div>\n );\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"EditableCell.cjs","names":[],"sources":["../../../src/components/DataTable/EditableCell.tsx"],"sourcesContent":["/**\n * @tempest-limits props-count, function-lines — an editable cell has to be announced\n * as well as rendered: columnLabel and rowNumber build the accessible name, labels\n * holds the button copy, and error/errorId wire the message to the input. The rest\n * is the edit lifecycle (editing, refocus, saving, onOpen, onCommit, onCancel) that\n * DataTable drives from outside.\n */\nimport { useEffect, useRef, useState } from \"react\";\nimport type { KeyboardEvent, ReactNode } from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport { VisuallyHidden } from \"../VisuallyHidden\";\nimport type { CellCommitMove, DataTableEditLabels } from \"./edit-labels\";\nimport styles from \"./DataTable.module.css\";\n\ninterface CellEditorProps {\n /** Text the editor opens with. */\n initial: string;\n /** `<input type>` for the editor. */\n type: string;\n /** Accessible name. */\n label: string;\n /** Id of the message element, when there is one. */\n describedBy?: string;\n /** Whether the current draft was rejected by validation. */\n invalid: boolean;\n /** Commit the draft. The parent decides whether the editor closes. */\n onCommit: (raw: string, move: CellCommitMove) => void;\n /** Discard the draft. */\n onCancel: () => void;\n}\n\n/**\n * The `<input>` half of an editable cell.\n *\n * A separate component so it **mounts fresh** every time a cell opens: the draft\n * then starts from the current value through `useState`'s initialiser, with no\n * effect syncing props into state and no render where the draft is stale.\n *\n * `Tab` is intercepted rather than left to the browser. Default tab order would\n * walk into the next row's trigger button, which is one keystroke away from the\n * next *editor* — and in a table the reader is editing, moving cell to cell is the\n * expected behaviour. `Escape` discards; `Enter` commits and closes; blur commits,\n * because clicking away from a half-typed cell and losing the typing is a data-loss\n * bug users report as \"the table ate my edit\".\n *\n * `autoFocus` is safe here in a way it is not on page load: the editor only exists\n * because the user just clicked the cell or pressed `Tab` into it, so focus is\n * following the interaction rather than stealing it.\n *\n * The `settled` flag stops a keyboard commit from being repeated by the blur that\n * follows it. Typing clears the flag again, because after a commit the parent\n * rejected — the only case where the editor is still open and focused — the draft is\n * live once more, and dropping *that* edit on blur would be the same data loss the\n * blur-commits rule exists to prevent.\n */\nfunction CellEditor({\n initial,\n type,\n label,\n describedBy,\n invalid,\n onCommit,\n onCancel,\n}: CellEditorProps) {\n const [draft, setDraft] = useState<string>(initial);\n const settled = useRef(false);\n\n function finish(move: CellCommitMove): void {\n settled.current = true;\n onCommit(draft, move);\n }\n\n function handleKeyDown(event: KeyboardEvent<HTMLInputElement>): void {\n if (event.key === \"Enter\") {\n event.preventDefault();\n finish(\"none\");\n return;\n }\n if (event.key === \"Escape\") {\n event.preventDefault();\n settled.current = true;\n onCancel();\n return;\n }\n if (event.key === \"Tab\") {\n event.preventDefault();\n finish(event.shiftKey ? \"previous\" : \"next\");\n }\n }\n\n return (\n <input\n autoFocus\n className={cn(styles.cellEditor, invalid && styles.cellEditorInvalid)}\n type={type}\n value={draft}\n aria-label={label}\n aria-invalid={invalid || undefined}\n aria-describedby={describedBy}\n onChange={(event) => {\n settled.current = false;\n setDraft(event.target.value);\n }}\n onKeyDown={handleKeyDown}\n onBlur={() => {\n if (settled.current) return;\n finish(\"none\");\n }}\n />\n );\n}\n\nexport interface EditableCellProps {\n /** Rendered value, used when the cell is closed. */\n children: ReactNode;\n /** Plain-text value, for the editor's initial draft and the trigger's name. */\n text: string;\n /** Header text of this column, for accessible names. */\n columnLabel: string;\n /** 1-based row number on the current page. */\n rowNumber: number;\n /** `<input type>` for the editor. */\n inputType: string;\n /** Whether this cell owns the open editor. */\n editing: boolean;\n /** Return focus to the trigger when the editor closes (Enter/Escape, not Tab). */\n refocus: boolean;\n /** A save is in flight for this cell. */\n saving: boolean;\n /** Validation or save error to surface, or null. */\n error: string | null;\n /** Stable id used for `aria-describedby` on the input and the trigger. */\n errorId: string;\n /** Copy for the affordances. */\n labels: DataTableEditLabels;\n /** Open the editor. */\n onOpen: () => void;\n /** Commit a draft. */\n onCommit: (raw: string, move: CellCommitMove) => void;\n /** Close without committing. */\n onCancel: () => void;\n}\n\n/**\n * One cell of a {@link DataTable} column marked `editable`.\n *\n * Closed, it is a button carrying the value — a plain `<td>` with a click handler\n * would be invisible to a keyboard and unnamed to a screen reader, and a\n * `tabIndex`-only cell announces no role. Open, it is an `<input>`.\n *\n * The button's name comes from its **contents**: a visually hidden \"Editar {coluna}:\"\n * in front of whatever the column rendered. An `aria-label` built from the raw value\n * would read \"850000\" over a cell showing `R$ 8.500,00`, which fails WCAG 2.5.3 (Label\n * in Name) and leaves voice control unable to address the cell by what it says.\n *\n * The error message is a `role=\"alert\"` tied to the input (and to the trigger once\n * the editor closes) through `aria-describedby`, so a rejected save is announced and\n * then still reachable — an optimistic update that rolls back silently leaves the\n * user believing the edit stuck.\n */\nexport function EditableCell({\n children,\n text,\n columnLabel,\n rowNumber,\n inputType,\n editing,\n refocus,\n saving,\n error,\n errorId,\n labels,\n onOpen,\n onCommit,\n onCancel,\n}: EditableCellProps) {\n const trigger = useRef<HTMLButtonElement | null>(null);\n const wasEditing = useRef(false);\n\n useEffect(() => {\n if (!editing && wasEditing.current && refocus) trigger.current?.focus();\n wasEditing.current = editing;\n }, [editing, refocus]);\n\n return (\n <span className={styles.cellWrap}>\n {editing ? (\n <CellEditor\n initial={text}\n type={inputType}\n label={labels.editor(columnLabel, rowNumber)}\n describedBy={error ? errorId : undefined}\n invalid={error !== null}\n onCommit={onCommit}\n onCancel={onCancel}\n />\n ) : (\n <button\n ref={trigger}\n type=\"button\"\n className={cn(styles.cellButton, error && styles.cellButtonInvalid)}\n aria-describedby={error ? errorId : undefined}\n aria-busy={saving || undefined}\n onClick={onOpen}\n >\n <VisuallyHidden>{labels.editCell(columnLabel)}</VisuallyHidden> {children}\n </button>\n )}\n {error && (\n <span className={styles.cellError} id={errorId} role=\"alert\">\n {error}\n </span>\n )}\n </span>\n );\n}\n"],"mappings":"kLAuDA,SAAS,EAAW,CAChB,UACA,OACA,QACA,cACA,UACA,WACA,YACgB,CAChB,GAAM,CAAC,EAAO,IAAA,EAAA,EAAA,SAAA,CAA6B,CAAO,EAC5C,GAAA,EAAA,EAAA,OAAA,CAAiB,EAAK,EAE5B,SAAS,EAAO,EAA4B,CACxC,EAAQ,QAAU,GAClB,EAAS,EAAO,CAAI,CACxB,CAEA,SAAS,EAAc,EAA8C,CACjE,GAAI,EAAM,MAAQ,QAAS,CACvB,EAAM,eAAe,EACrB,EAAO,MAAM,EACb,MACJ,CACA,GAAI,EAAM,MAAQ,SAAU,CACxB,EAAM,eAAe,EACrB,EAAQ,QAAU,GAClB,EAAS,EACT,MACJ,CACI,EAAM,MAAQ,QACd,EAAM,eAAe,EACrB,EAAO,EAAM,SAAW,WAAa,MAAM,EAEnD,CAEA,OACI,EAAA,EAAA,IAAA,CAAC,QAAD,CACI,UAAA,GACA,UAAW,EAAA,GAAG,EAAA,QAAO,WAAY,GAAW,EAAA,QAAO,iBAAiB,EAC9D,OACN,MAAO,EACP,aAAY,EACZ,eAAc,GAAW,IAAA,GACzB,mBAAkB,EAClB,SAAW,GAAU,CACjB,EAAQ,QAAU,GAClB,EAAS,EAAM,OAAO,KAAK,CAC/B,EACA,UAAW,EACX,WAAc,CACN,EAAQ,SACZ,EAAO,MAAM,CACjB,CACH,CAAA,CAET,CAkDA,SAAgB,EAAa,CACzB,WACA,OACA,cACA,YACA,YACA,UACA,UACA,SACA,QACA,UACA,SACA,SACA,WACA,YACkB,CAClB,IAAM,GAAA,EAAA,EAAA,OAAA,CAA2C,IAAI,EAC/C,GAAA,EAAA,EAAA,OAAA,CAAoB,EAAK,EAO/B,OALA,EAAA,EAAA,UAAA,KAAgB,CACR,CAAC,GAAW,EAAW,SAAW,GAAS,EAAQ,SAAS,MAAM,EACtE,EAAW,QAAU,CACzB,EAAG,CAAC,EAAS,CAAO,CAAC,GAGjB,EAAA,EAAA,KAAA,CAAC,OAAD,CAAM,UAAW,EAAA,QAAO,kBAAxB,CACK,GACG,EAAA,EAAA,IAAA,CAAC,EAAD,CACI,QAAS,EACT,KAAM,EACN,MAAO,EAAO,OAAO,EAAa,CAAS,EAC3C,YAAa,EAAQ,EAAU,IAAA,GAC/B,QAAS,IAAU,KACT,WACA,UACb,CAAA,GAED,EAAA,EAAA,KAAA,CAAC,SAAD,CACI,IAAK,EACL,KAAK,SACL,UAAW,EAAA,GAAG,EAAA,QAAO,WAAY,GAAS,EAAA,QAAO,iBAAiB,EAClE,mBAAkB,EAAQ,EAAU,IAAA,GACpC,YAAW,GAAU,IAAA,GACrB,QAAS,WANb,EAQI,EAAA,EAAA,IAAA,CAAC,EAAA,eAAD,CAAA,SAAiB,EAAO,SAAS,CAAW,CAAkB,CAAA,EAAC,IAAE,CAC7D,IAEX,IACG,EAAA,EAAA,IAAA,CAAC,OAAD,CAAM,UAAW,EAAA,QAAO,UAAW,GAAI,EAAS,KAAK,iBAChD,CACC,CAAA,CAER,GAEd"}
1
+ {"version":3,"file":"EditableCell.cjs","names":[],"sources":["../../../src/components/DataTable/EditableCell.tsx"],"sourcesContent":["/**\n * @tempest-limits props-count, function-lines — an editable cell has to be announced\n * as well as rendered: columnLabel and rowNumber build the accessible name, labels\n * holds the button copy, and error/errorId wire the message to the input. The rest\n * is the edit lifecycle (editing, refocus, saving, onOpen, onCommit, onCancel) that\n * DataTable drives from outside.\n */\nimport { useEffect, useRef, useState } from \"react\";\nimport type { KeyboardEvent, ReactNode } from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport { VisuallyHidden } from \"../VisuallyHidden\";\nimport type { CellCommitMove, DataTableEditLabels } from \"./edit-labels\";\nimport styles from \"./DataTable.module.css\";\n\ninterface CellEditorProps {\n /** Text the editor opens with. */\n initial: string;\n /** `<input type>` for the editor. */\n type: string;\n /** Accessible name. */\n label: string;\n /** Id of the message element, when there is one. */\n describedBy?: string;\n /** Whether the current draft was rejected by validation. */\n invalid: boolean;\n /** Commit the draft. The parent decides whether the editor closes. */\n onCommit: (raw: string, move: CellCommitMove) => void;\n /** Discard the draft. */\n onCancel: () => void;\n}\n\n/**\n * The `<input>` half of an editable cell.\n *\n * A separate component so it **mounts fresh** every time a cell opens: the draft\n * then starts from the current value through `useState`'s initialiser, with no\n * effect syncing props into state and no render where the draft is stale.\n *\n * `Tab` is intercepted rather than left to the browser. Default tab order would\n * walk into the next row's trigger button, which is one keystroke away from the\n * next *editor* — and in a table the reader is editing, moving cell to cell is the\n * expected behaviour. `Escape` discards; `Enter` commits and closes; blur commits,\n * because clicking away from a half-typed cell and losing the typing is a data-loss\n * bug users report as \"the table ate my edit\".\n *\n * `autoFocus` is safe here in a way it is not on page load: the editor only exists\n * because the user just clicked the cell or pressed `Tab` into it, so focus is\n * following the interaction rather than stealing it.\n *\n * The `settled` flag stops a keyboard commit from being repeated by the blur that\n * follows it. Typing clears the flag again, because after a commit the parent\n * rejected — the only case where the editor is still open and focused — the draft is\n * live once more, and dropping *that* edit on blur would be the same data loss the\n * blur-commits rule exists to prevent.\n */\nfunction CellEditor({\n initial,\n type,\n label,\n describedBy,\n invalid,\n onCommit,\n onCancel,\n}: CellEditorProps) {\n const [draft, setDraft] = useState<string>(initial);\n const settled = useRef(false);\n\n function finish(move: CellCommitMove): void {\n settled.current = true;\n onCommit(draft, move);\n }\n\n function handleKeyDown(event: KeyboardEvent<HTMLInputElement>): void {\n if (event.key === \"Enter\") {\n event.preventDefault();\n finish(\"none\");\n return;\n }\n if (event.key === \"Escape\") {\n event.preventDefault();\n settled.current = true;\n onCancel();\n return;\n }\n if (event.key === \"Tab\") {\n event.preventDefault();\n finish(event.shiftKey ? \"previous\" : \"next\");\n }\n }\n\n return (\n <input\n autoFocus\n className={cn(styles.cellEditor, invalid && styles.cellEditorInvalid)}\n type={type}\n value={draft}\n aria-label={label}\n aria-invalid={invalid || undefined}\n aria-describedby={describedBy}\n onChange={(event) => {\n settled.current = false;\n setDraft(event.target.value);\n }}\n onKeyDown={handleKeyDown}\n onBlur={() => {\n if (settled.current) return;\n finish(\"none\");\n }}\n />\n );\n}\n\nexport interface EditableCellProps {\n /** Rendered value, used when the cell is closed. */\n children: ReactNode;\n /** Plain-text value, for the editor's initial draft and the trigger's name. */\n text: string;\n /** Header text of this column, for accessible names. */\n columnLabel: string;\n /** 1-based row number on the current page. */\n rowNumber: number;\n /** `<input type>` for the editor. */\n inputType: string;\n /** Whether this cell owns the open editor. */\n editing: boolean;\n /** Return focus to the trigger when the editor closes (Enter/Escape, not Tab). */\n refocus: boolean;\n /** A save is in flight for this cell. */\n saving: boolean;\n /** Validation or save error to surface, or null. */\n error: string | null;\n /** Stable id used for `aria-describedby` on the input and the trigger. */\n errorId: string;\n /** Copy for the affordances. */\n labels: DataTableEditLabels;\n /** Open the editor. */\n onOpen: () => void;\n /** Commit a draft. */\n onCommit: (raw: string, move: CellCommitMove) => void;\n /** Close without committing. */\n onCancel: () => void;\n}\n\n/**\n * One cell of a {@link DataTable} column marked `editable`.\n *\n * Closed, it is a button carrying the value — a plain `<td>` with a click handler\n * would be invisible to a keyboard and unnamed to a screen reader, and a\n * `tabIndex`-only cell announces no role. Open, it is an `<input>`.\n *\n * The button's name comes from its **contents**: a visually hidden \"Editar {coluna}:\"\n * in front of whatever the column rendered. An `aria-label` built from the raw value\n * would read \"850000\" over a cell showing `R$ 8.500,00`, which fails WCAG 2.5.3 (Label\n * in Name) and leaves voice control unable to address the cell by what it says.\n *\n * The error message is a `role=\"alert\"` tied to the input (and to the trigger once\n * the editor closes) through `aria-describedby`, so a rejected save is announced and\n * then still reachable — an optimistic update that rolls back silently leaves the\n * user believing the edit stuck.\n */\nexport function EditableCell({\n children,\n text,\n columnLabel,\n rowNumber,\n inputType,\n editing,\n refocus,\n saving,\n error,\n errorId,\n labels,\n onOpen,\n onCommit,\n onCancel,\n}: EditableCellProps) {\n const trigger = useRef<HTMLButtonElement | null>(null);\n const wasEditing = useRef(false);\n\n useEffect(() => {\n if (!editing && wasEditing.current && refocus) trigger.current?.focus();\n wasEditing.current = editing;\n }, [editing, refocus]);\n\n return (\n <span className={styles.cellWrap}>\n {editing ? (\n <CellEditor\n initial={text}\n type={inputType}\n label={labels.editor(columnLabel, rowNumber)}\n describedBy={error ? errorId : undefined}\n invalid={error !== null}\n onCommit={onCommit}\n onCancel={onCancel}\n />\n ) : (\n <button\n ref={trigger}\n type=\"button\"\n className={cn(styles.cellButton, error && styles.cellButtonInvalid)}\n aria-describedby={error ? errorId : undefined}\n aria-busy={saving || undefined}\n onClick={onOpen}\n >\n <VisuallyHidden>{labels.editCell(columnLabel)}</VisuallyHidden> {children}\n </button>\n )}\n {error && (\n <span className={styles.cellError} id={errorId} role=\"alert\">\n {error}\n </span>\n )}\n </span>\n );\n}\n"],"mappings":"kLAuDA,SAAS,EAAW,CAChB,UACA,OACA,QACA,cACA,UACA,WACA,YACgB,CAChB,GAAM,CAAC,EAAO,IAAA,EAAY,EAAA,SAAA,CAAiB,CAAO,EAC5C,GAAA,EAAU,EAAA,OAAA,CAAO,EAAK,EAE5B,SAAS,EAAO,EAA4B,CACxC,EAAQ,QAAU,GAClB,EAAS,EAAO,CAAI,CACxB,CAEA,SAAS,EAAc,EAA8C,CACjE,GAAI,EAAM,MAAQ,QAAS,CACvB,EAAM,eAAe,EACrB,EAAO,MAAM,EACb,MACJ,CACA,GAAI,EAAM,MAAQ,SAAU,CACxB,EAAM,eAAe,EACrB,EAAQ,QAAU,GAClB,EAAS,EACT,MACJ,CACI,EAAM,MAAQ,QACd,EAAM,eAAe,EACrB,EAAO,EAAM,SAAW,WAAa,MAAM,EAEnD,CAEA,OACI,EAAA,EAAA,IAAA,CAAC,QAAD,CACI,UAAA,GACA,UAAW,EAAA,GAAG,EAAA,QAAO,WAAY,GAAW,EAAA,QAAO,iBAAiB,EAC9D,OACN,MAAO,EACP,aAAY,EACZ,eAAc,GAAW,IAAA,GACzB,mBAAkB,EAClB,SAAW,GAAU,CACjB,EAAQ,QAAU,GAClB,EAAS,EAAM,OAAO,KAAK,CAC/B,EACA,UAAW,EACX,WAAc,CACN,EAAQ,SACZ,EAAO,MAAM,CACjB,CACH,CAAA,CAET,CAkDA,SAAgB,EAAa,CACzB,WACA,OACA,cACA,YACA,YACA,UACA,UACA,SACA,QACA,UACA,SACA,SACA,WACA,YACkB,CAClB,IAAM,GAAA,EAAU,EAAA,OAAA,CAAiC,IAAI,EAC/C,GAAA,EAAa,EAAA,OAAA,CAAO,EAAK,EAO/B,OALA,EAAA,EAAA,UAAA,KAAgB,CACR,CAAC,GAAW,EAAW,SAAW,GAAS,EAAQ,SAAS,MAAM,EACtE,EAAW,QAAU,CACzB,EAAG,CAAC,EAAS,CAAO,CAAC,GAGjB,EAAA,EAAA,KAAA,CAAC,OAAD,CAAM,UAAW,EAAA,QAAO,SAAxB,SAAA,CACK,GACG,EAAA,EAAA,IAAA,CAAC,EAAD,CACI,QAAS,EACT,KAAM,EACN,MAAO,EAAO,OAAO,EAAa,CAAS,EAC3C,YAAa,EAAQ,EAAU,IAAA,GAC/B,QAAS,IAAU,KACT,WACA,UACb,CAAA,GAED,EAAA,EAAA,KAAA,CAAC,SAAD,CACI,IAAK,EACL,KAAK,SACL,UAAW,EAAA,GAAG,EAAA,QAAO,WAAY,GAAS,EAAA,QAAO,iBAAiB,EAClE,mBAAkB,EAAQ,EAAU,IAAA,GACpC,YAAW,GAAU,IAAA,GACrB,QAAS,EANb,SAAA,EAQI,EAAA,EAAA,IAAA,CAAC,EAAA,eAAD,CAAA,SAAiB,EAAO,SAAS,CAAW,CAAkB,CAAA,EAAC,IAAE,CAC7D,CAEX,CAAA,EAAA,IACG,EAAA,EAAA,IAAA,CAAC,OAAD,CAAM,UAAW,EAAA,QAAO,UAAW,GAAI,EAAS,KAAK,QAChD,SAAA,CACC,CAAA,CAER,GAEd"}
@@ -1 +1 @@
1
- {"version":3,"file":"EditableCell.js","names":[],"sources":["../../../src/components/DataTable/EditableCell.tsx"],"sourcesContent":["/**\n * @tempest-limits props-count, function-lines — an editable cell has to be announced\n * as well as rendered: columnLabel and rowNumber build the accessible name, labels\n * holds the button copy, and error/errorId wire the message to the input. The rest\n * is the edit lifecycle (editing, refocus, saving, onOpen, onCommit, onCancel) that\n * DataTable drives from outside.\n */\nimport { useEffect, useRef, useState } from \"react\";\nimport type { KeyboardEvent, ReactNode } from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport { VisuallyHidden } from \"../VisuallyHidden\";\nimport type { CellCommitMove, DataTableEditLabels } from \"./edit-labels\";\nimport styles from \"./DataTable.module.css\";\n\ninterface CellEditorProps {\n /** Text the editor opens with. */\n initial: string;\n /** `<input type>` for the editor. */\n type: string;\n /** Accessible name. */\n label: string;\n /** Id of the message element, when there is one. */\n describedBy?: string;\n /** Whether the current draft was rejected by validation. */\n invalid: boolean;\n /** Commit the draft. The parent decides whether the editor closes. */\n onCommit: (raw: string, move: CellCommitMove) => void;\n /** Discard the draft. */\n onCancel: () => void;\n}\n\n/**\n * The `<input>` half of an editable cell.\n *\n * A separate component so it **mounts fresh** every time a cell opens: the draft\n * then starts from the current value through `useState`'s initialiser, with no\n * effect syncing props into state and no render where the draft is stale.\n *\n * `Tab` is intercepted rather than left to the browser. Default tab order would\n * walk into the next row's trigger button, which is one keystroke away from the\n * next *editor* — and in a table the reader is editing, moving cell to cell is the\n * expected behaviour. `Escape` discards; `Enter` commits and closes; blur commits,\n * because clicking away from a half-typed cell and losing the typing is a data-loss\n * bug users report as \"the table ate my edit\".\n *\n * `autoFocus` is safe here in a way it is not on page load: the editor only exists\n * because the user just clicked the cell or pressed `Tab` into it, so focus is\n * following the interaction rather than stealing it.\n *\n * The `settled` flag stops a keyboard commit from being repeated by the blur that\n * follows it. Typing clears the flag again, because after a commit the parent\n * rejected — the only case where the editor is still open and focused — the draft is\n * live once more, and dropping *that* edit on blur would be the same data loss the\n * blur-commits rule exists to prevent.\n */\nfunction CellEditor({\n initial,\n type,\n label,\n describedBy,\n invalid,\n onCommit,\n onCancel,\n}: CellEditorProps) {\n const [draft, setDraft] = useState<string>(initial);\n const settled = useRef(false);\n\n function finish(move: CellCommitMove): void {\n settled.current = true;\n onCommit(draft, move);\n }\n\n function handleKeyDown(event: KeyboardEvent<HTMLInputElement>): void {\n if (event.key === \"Enter\") {\n event.preventDefault();\n finish(\"none\");\n return;\n }\n if (event.key === \"Escape\") {\n event.preventDefault();\n settled.current = true;\n onCancel();\n return;\n }\n if (event.key === \"Tab\") {\n event.preventDefault();\n finish(event.shiftKey ? \"previous\" : \"next\");\n }\n }\n\n return (\n <input\n autoFocus\n className={cn(styles.cellEditor, invalid && styles.cellEditorInvalid)}\n type={type}\n value={draft}\n aria-label={label}\n aria-invalid={invalid || undefined}\n aria-describedby={describedBy}\n onChange={(event) => {\n settled.current = false;\n setDraft(event.target.value);\n }}\n onKeyDown={handleKeyDown}\n onBlur={() => {\n if (settled.current) return;\n finish(\"none\");\n }}\n />\n );\n}\n\nexport interface EditableCellProps {\n /** Rendered value, used when the cell is closed. */\n children: ReactNode;\n /** Plain-text value, for the editor's initial draft and the trigger's name. */\n text: string;\n /** Header text of this column, for accessible names. */\n columnLabel: string;\n /** 1-based row number on the current page. */\n rowNumber: number;\n /** `<input type>` for the editor. */\n inputType: string;\n /** Whether this cell owns the open editor. */\n editing: boolean;\n /** Return focus to the trigger when the editor closes (Enter/Escape, not Tab). */\n refocus: boolean;\n /** A save is in flight for this cell. */\n saving: boolean;\n /** Validation or save error to surface, or null. */\n error: string | null;\n /** Stable id used for `aria-describedby` on the input and the trigger. */\n errorId: string;\n /** Copy for the affordances. */\n labels: DataTableEditLabels;\n /** Open the editor. */\n onOpen: () => void;\n /** Commit a draft. */\n onCommit: (raw: string, move: CellCommitMove) => void;\n /** Close without committing. */\n onCancel: () => void;\n}\n\n/**\n * One cell of a {@link DataTable} column marked `editable`.\n *\n * Closed, it is a button carrying the value — a plain `<td>` with a click handler\n * would be invisible to a keyboard and unnamed to a screen reader, and a\n * `tabIndex`-only cell announces no role. Open, it is an `<input>`.\n *\n * The button's name comes from its **contents**: a visually hidden \"Editar {coluna}:\"\n * in front of whatever the column rendered. An `aria-label` built from the raw value\n * would read \"850000\" over a cell showing `R$ 8.500,00`, which fails WCAG 2.5.3 (Label\n * in Name) and leaves voice control unable to address the cell by what it says.\n *\n * The error message is a `role=\"alert\"` tied to the input (and to the trigger once\n * the editor closes) through `aria-describedby`, so a rejected save is announced and\n * then still reachable — an optimistic update that rolls back silently leaves the\n * user believing the edit stuck.\n */\nexport function EditableCell({\n children,\n text,\n columnLabel,\n rowNumber,\n inputType,\n editing,\n refocus,\n saving,\n error,\n errorId,\n labels,\n onOpen,\n onCommit,\n onCancel,\n}: EditableCellProps) {\n const trigger = useRef<HTMLButtonElement | null>(null);\n const wasEditing = useRef(false);\n\n useEffect(() => {\n if (!editing && wasEditing.current && refocus) trigger.current?.focus();\n wasEditing.current = editing;\n }, [editing, refocus]);\n\n return (\n <span className={styles.cellWrap}>\n {editing ? (\n <CellEditor\n initial={text}\n type={inputType}\n label={labels.editor(columnLabel, rowNumber)}\n describedBy={error ? errorId : undefined}\n invalid={error !== null}\n onCommit={onCommit}\n onCancel={onCancel}\n />\n ) : (\n <button\n ref={trigger}\n type=\"button\"\n className={cn(styles.cellButton, error && styles.cellButtonInvalid)}\n aria-describedby={error ? errorId : undefined}\n aria-busy={saving || undefined}\n onClick={onOpen}\n >\n <VisuallyHidden>{labels.editCell(columnLabel)}</VisuallyHidden> {children}\n </button>\n )}\n {error && (\n <span className={styles.cellError} id={errorId} role=\"alert\">\n {error}\n </span>\n )}\n </span>\n );\n}\n"],"mappings":";;;;;;AAuDA,SAAS,EAAW,EAChB,YACA,SACA,UACA,gBACA,YACA,aACA,eACgB;CAChB,IAAM,CAAC,GAAO,KAAY,EAAiB,CAAO,GAC5C,IAAU,EAAO,EAAK;CAE5B,SAAS,EAAO,GAA4B;EAExC,AADA,EAAQ,UAAU,IAClB,EAAS,GAAO,CAAI;CACxB;CAEA,SAAS,EAAc,GAA8C;EACjE,IAAI,EAAM,QAAQ,SAAS;GAEvB,AADA,EAAM,eAAe,GACrB,EAAO,MAAM;GACb;EACJ;EACA,IAAI,EAAM,QAAQ,UAAU;GAGxB,AAFA,EAAM,eAAe,GACrB,EAAQ,UAAU,IAClB,EAAS;GACT;EACJ;EACA,AAAI,EAAM,QAAQ,UACd,EAAM,eAAe,GACrB,EAAO,EAAM,WAAW,aAAa,MAAM;CAEnD;CAEA,OACI,kBAAC,SAAD;EACI,WAAA;EACA,WAAW,EAAG,EAAO,YAAY,KAAW,EAAO,iBAAiB;EAC9D;EACN,OAAO;EACP,cAAY;EACZ,gBAAc,KAAW,KAAA;EACzB,oBAAkB;EAClB,WAAW,MAAU;GAEjB,AADA,EAAQ,UAAU,IAClB,EAAS,EAAM,OAAO,KAAK;EAC/B;EACA,WAAW;EACX,cAAc;GACN,EAAQ,WACZ,EAAO,MAAM;EACjB;CACH,CAAA;AAET;AAkDA,SAAgB,EAAa,EACzB,aACA,SACA,gBACA,cACA,cACA,YACA,YACA,WACA,UACA,YACA,WACA,WACA,aACA,eACkB;CAClB,IAAM,IAAU,EAAiC,IAAI,GAC/C,IAAa,EAAO,EAAK;CAO/B,OALA,QAAgB;EAEZ,AADI,CAAC,KAAW,EAAW,WAAW,KAAS,EAAQ,SAAS,MAAM,GACtE,EAAW,UAAU;CACzB,GAAG,CAAC,GAAS,CAAO,CAAC,GAGjB,kBAAC,QAAD;EAAM,WAAW,EAAO;YAAxB,CACK,IACG,kBAAC,GAAD;GACI,SAAS;GACT,MAAM;GACN,OAAO,EAAO,OAAO,GAAa,CAAS;GAC3C,aAAa,IAAQ,IAAU,KAAA;GAC/B,SAAS,MAAU;GACT;GACA;EACb,CAAA,IAED,kBAAC,UAAD;GACI,KAAK;GACL,MAAK;GACL,WAAW,EAAG,EAAO,YAAY,KAAS,EAAO,iBAAiB;GAClE,oBAAkB,IAAQ,IAAU,KAAA;GACpC,aAAW,KAAU,KAAA;GACrB,SAAS;aANb;IAQI,kBAAC,GAAD,EAAA,UAAiB,EAAO,SAAS,CAAW,EAAkB,CAAA;IAAC;IAAE;GAC7D;MAEX,KACG,kBAAC,QAAD;GAAM,WAAW,EAAO;GAAW,IAAI;GAAS,MAAK;aAChD;EACC,CAAA,CAER;;AAEd"}
1
+ {"version":3,"file":"EditableCell.js","names":[],"sources":["../../../src/components/DataTable/EditableCell.tsx"],"sourcesContent":["/**\n * @tempest-limits props-count, function-lines — an editable cell has to be announced\n * as well as rendered: columnLabel and rowNumber build the accessible name, labels\n * holds the button copy, and error/errorId wire the message to the input. The rest\n * is the edit lifecycle (editing, refocus, saving, onOpen, onCommit, onCancel) that\n * DataTable drives from outside.\n */\nimport { useEffect, useRef, useState } from \"react\";\nimport type { KeyboardEvent, ReactNode } from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport { VisuallyHidden } from \"../VisuallyHidden\";\nimport type { CellCommitMove, DataTableEditLabels } from \"./edit-labels\";\nimport styles from \"./DataTable.module.css\";\n\ninterface CellEditorProps {\n /** Text the editor opens with. */\n initial: string;\n /** `<input type>` for the editor. */\n type: string;\n /** Accessible name. */\n label: string;\n /** Id of the message element, when there is one. */\n describedBy?: string;\n /** Whether the current draft was rejected by validation. */\n invalid: boolean;\n /** Commit the draft. The parent decides whether the editor closes. */\n onCommit: (raw: string, move: CellCommitMove) => void;\n /** Discard the draft. */\n onCancel: () => void;\n}\n\n/**\n * The `<input>` half of an editable cell.\n *\n * A separate component so it **mounts fresh** every time a cell opens: the draft\n * then starts from the current value through `useState`'s initialiser, with no\n * effect syncing props into state and no render where the draft is stale.\n *\n * `Tab` is intercepted rather than left to the browser. Default tab order would\n * walk into the next row's trigger button, which is one keystroke away from the\n * next *editor* — and in a table the reader is editing, moving cell to cell is the\n * expected behaviour. `Escape` discards; `Enter` commits and closes; blur commits,\n * because clicking away from a half-typed cell and losing the typing is a data-loss\n * bug users report as \"the table ate my edit\".\n *\n * `autoFocus` is safe here in a way it is not on page load: the editor only exists\n * because the user just clicked the cell or pressed `Tab` into it, so focus is\n * following the interaction rather than stealing it.\n *\n * The `settled` flag stops a keyboard commit from being repeated by the blur that\n * follows it. Typing clears the flag again, because after a commit the parent\n * rejected — the only case where the editor is still open and focused — the draft is\n * live once more, and dropping *that* edit on blur would be the same data loss the\n * blur-commits rule exists to prevent.\n */\nfunction CellEditor({\n initial,\n type,\n label,\n describedBy,\n invalid,\n onCommit,\n onCancel,\n}: CellEditorProps) {\n const [draft, setDraft] = useState<string>(initial);\n const settled = useRef(false);\n\n function finish(move: CellCommitMove): void {\n settled.current = true;\n onCommit(draft, move);\n }\n\n function handleKeyDown(event: KeyboardEvent<HTMLInputElement>): void {\n if (event.key === \"Enter\") {\n event.preventDefault();\n finish(\"none\");\n return;\n }\n if (event.key === \"Escape\") {\n event.preventDefault();\n settled.current = true;\n onCancel();\n return;\n }\n if (event.key === \"Tab\") {\n event.preventDefault();\n finish(event.shiftKey ? \"previous\" : \"next\");\n }\n }\n\n return (\n <input\n autoFocus\n className={cn(styles.cellEditor, invalid && styles.cellEditorInvalid)}\n type={type}\n value={draft}\n aria-label={label}\n aria-invalid={invalid || undefined}\n aria-describedby={describedBy}\n onChange={(event) => {\n settled.current = false;\n setDraft(event.target.value);\n }}\n onKeyDown={handleKeyDown}\n onBlur={() => {\n if (settled.current) return;\n finish(\"none\");\n }}\n />\n );\n}\n\nexport interface EditableCellProps {\n /** Rendered value, used when the cell is closed. */\n children: ReactNode;\n /** Plain-text value, for the editor's initial draft and the trigger's name. */\n text: string;\n /** Header text of this column, for accessible names. */\n columnLabel: string;\n /** 1-based row number on the current page. */\n rowNumber: number;\n /** `<input type>` for the editor. */\n inputType: string;\n /** Whether this cell owns the open editor. */\n editing: boolean;\n /** Return focus to the trigger when the editor closes (Enter/Escape, not Tab). */\n refocus: boolean;\n /** A save is in flight for this cell. */\n saving: boolean;\n /** Validation or save error to surface, or null. */\n error: string | null;\n /** Stable id used for `aria-describedby` on the input and the trigger. */\n errorId: string;\n /** Copy for the affordances. */\n labels: DataTableEditLabels;\n /** Open the editor. */\n onOpen: () => void;\n /** Commit a draft. */\n onCommit: (raw: string, move: CellCommitMove) => void;\n /** Close without committing. */\n onCancel: () => void;\n}\n\n/**\n * One cell of a {@link DataTable} column marked `editable`.\n *\n * Closed, it is a button carrying the value — a plain `<td>` with a click handler\n * would be invisible to a keyboard and unnamed to a screen reader, and a\n * `tabIndex`-only cell announces no role. Open, it is an `<input>`.\n *\n * The button's name comes from its **contents**: a visually hidden \"Editar {coluna}:\"\n * in front of whatever the column rendered. An `aria-label` built from the raw value\n * would read \"850000\" over a cell showing `R$ 8.500,00`, which fails WCAG 2.5.3 (Label\n * in Name) and leaves voice control unable to address the cell by what it says.\n *\n * The error message is a `role=\"alert\"` tied to the input (and to the trigger once\n * the editor closes) through `aria-describedby`, so a rejected save is announced and\n * then still reachable — an optimistic update that rolls back silently leaves the\n * user believing the edit stuck.\n */\nexport function EditableCell({\n children,\n text,\n columnLabel,\n rowNumber,\n inputType,\n editing,\n refocus,\n saving,\n error,\n errorId,\n labels,\n onOpen,\n onCommit,\n onCancel,\n}: EditableCellProps) {\n const trigger = useRef<HTMLButtonElement | null>(null);\n const wasEditing = useRef(false);\n\n useEffect(() => {\n if (!editing && wasEditing.current && refocus) trigger.current?.focus();\n wasEditing.current = editing;\n }, [editing, refocus]);\n\n return (\n <span className={styles.cellWrap}>\n {editing ? (\n <CellEditor\n initial={text}\n type={inputType}\n label={labels.editor(columnLabel, rowNumber)}\n describedBy={error ? errorId : undefined}\n invalid={error !== null}\n onCommit={onCommit}\n onCancel={onCancel}\n />\n ) : (\n <button\n ref={trigger}\n type=\"button\"\n className={cn(styles.cellButton, error && styles.cellButtonInvalid)}\n aria-describedby={error ? errorId : undefined}\n aria-busy={saving || undefined}\n onClick={onOpen}\n >\n <VisuallyHidden>{labels.editCell(columnLabel)}</VisuallyHidden> {children}\n </button>\n )}\n {error && (\n <span className={styles.cellError} id={errorId} role=\"alert\">\n {error}\n </span>\n )}\n </span>\n );\n}\n"],"mappings":";;;;;;AAuDA,SAAS,EAAW,EAChB,YACA,SACA,UACA,gBACA,YACA,aACA,eACgB;CAChB,IAAM,CAAC,GAAO,KAAY,EAAiB,CAAO,GAC5C,IAAU,EAAO,EAAK;CAE5B,SAAS,EAAO,GAA4B;EAExC,AADA,EAAQ,UAAU,IAClB,EAAS,GAAO,CAAI;CACxB;CAEA,SAAS,EAAc,GAA8C;EACjE,IAAI,EAAM,QAAQ,SAAS;GAEvB,AADA,EAAM,eAAe,GACrB,EAAO,MAAM;GACb;EACJ;EACA,IAAI,EAAM,QAAQ,UAAU;GAGxB,AAFA,EAAM,eAAe,GACrB,EAAQ,UAAU,IAClB,EAAS;GACT;EACJ;EACA,AAAI,EAAM,QAAQ,UACd,EAAM,eAAe,GACrB,EAAO,EAAM,WAAW,aAAa,MAAM;CAEnD;CAEA,OACI,kBAAC,SAAD;EACI,WAAA;EACA,WAAW,EAAG,EAAO,YAAY,KAAW,EAAO,iBAAiB;EAC9D;EACN,OAAO;EACP,cAAY;EACZ,gBAAc,KAAW,KAAA;EACzB,oBAAkB;EAClB,WAAW,MAAU;GAEjB,AADA,EAAQ,UAAU,IAClB,EAAS,EAAM,OAAO,KAAK;EAC/B;EACA,WAAW;EACX,cAAc;GACN,EAAQ,WACZ,EAAO,MAAM;EACjB;CACH,CAAA;AAET;AAkDA,SAAgB,EAAa,EACzB,aACA,SACA,gBACA,cACA,cACA,YACA,YACA,WACA,UACA,YACA,WACA,WACA,aACA,eACkB;CAClB,IAAM,IAAU,EAAiC,IAAI,GAC/C,IAAa,EAAO,EAAK;CAO/B,OALA,QAAgB;EAEZ,AADI,CAAC,KAAW,EAAW,WAAW,KAAS,EAAQ,SAAS,MAAM,GACtE,EAAW,UAAU;CACzB,GAAG,CAAC,GAAS,CAAO,CAAC,GAGjB,kBAAC,QAAD;EAAM,WAAW,EAAO;EAAxB,UAAA,CACK,IACG,kBAAC,GAAD;GACI,SAAS;GACT,MAAM;GACN,OAAO,EAAO,OAAO,GAAa,CAAS;GAC3C,aAAa,IAAQ,IAAU,KAAA;GAC/B,SAAS,MAAU;GACT;GACA;EACb,CAAA,IAED,kBAAC,UAAD;GACI,KAAK;GACL,MAAK;GACL,WAAW,EAAG,EAAO,YAAY,KAAS,EAAO,iBAAiB;GAClE,oBAAkB,IAAQ,IAAU,KAAA;GACpC,aAAW,KAAU,KAAA;GACrB,SAAS;GANb,UAAA;IAQI,kBAAC,GAAD,EAAA,UAAiB,EAAO,SAAS,CAAW,EAAkB,CAAA;IAAC;IAAE;GAC7D;EAEX,CAAA,GAAA,KACG,kBAAC,QAAD;GAAM,WAAW,EAAO;GAAW,IAAI;GAAS,MAAK;GAChD,UAAA;EACC,CAAA,CAER;;AAEd"}
@@ -1 +1 @@
1
- {"version":3,"file":"DatePicker.cjs","names":[],"sources":["../../../src/components/DatePicker/DatePicker.tsx"],"sourcesContent":["/**\n * @tempest-limits props-count — label, helperText and error are the three states of\n * one message slot that every input in this SDK shares; removing them pushes the\n * scaffolding back into the app. DatePicker adds min, max and mode on top, which are\n * the field's own semantics.\n */\nimport { forwardRef } from \"react\";\nimport type { InputHTMLAttributes } from \"react\";\nimport { Input } from \"@/components/Input\";\n\nexport interface DatePickerProps extends Omit<\n InputHTMLAttributes<HTMLInputElement>,\n \"type\" | \"value\" | \"onChange\" | \"size\"\n> {\n /** ISO date string (`YYYY-MM-DD`) or empty. */\n value: string;\n onChange: (value: string) => void;\n label?: string;\n helperText?: string;\n error?: string;\n /** Lower bound (`YYYY-MM-DD`). */\n min?: string;\n /** Upper bound (`YYYY-MM-DD`). */\n max?: string;\n /** Mode. `date` (default), `datetime-local`, `time`, or `month`. */\n mode?: \"date\" | \"datetime-local\" | \"time\" | \"month\";\n wrapperClassName?: string;\n}\n\n/**\n * Thin wrapper around the native `<input type=\"date\">` (or `datetime-local`,\n * `time`, `month`). For richer pickers, pair with `react-datepicker` directly\n * — this primitive keeps the SDK dep-free.\n */\nexport const DatePicker = forwardRef<HTMLInputElement, DatePickerProps>(function DatePicker(\n { value, onChange, mode = \"date\", ...props },\n ref,\n) {\n return (\n <Input\n {...props}\n ref={ref}\n type={mode}\n value={value}\n onChange={(event) => onChange(event.target.value)}\n />\n );\n});\n"],"mappings":"4FAkCA,IAAa,GAAA,EAAA,EAAA,WAAA,CAA2D,SACpE,CAAE,QAAO,WAAU,OAAO,OAAQ,GAAG,GACrC,EACF,CACE,OACI,EAAA,EAAA,IAAA,CAAC,EAAA,MAAD,CACI,GAAI,EACC,MACL,KAAM,EACC,QACP,SAAW,GAAU,EAAS,EAAM,OAAO,KAAK,CACnD,CAAA,CAET,CAAC"}
1
+ {"version":3,"file":"DatePicker.cjs","names":[],"sources":["../../../src/components/DatePicker/DatePicker.tsx"],"sourcesContent":["/**\n * @tempest-limits props-count — label, helperText and error are the three states of\n * one message slot that every input in this SDK shares; removing them pushes the\n * scaffolding back into the app. DatePicker adds min, max and mode on top, which are\n * the field's own semantics.\n */\nimport { forwardRef } from \"react\";\nimport type { InputHTMLAttributes } from \"react\";\nimport { Input } from \"@/components/Input\";\n\nexport interface DatePickerProps extends Omit<\n InputHTMLAttributes<HTMLInputElement>,\n \"type\" | \"value\" | \"onChange\" | \"size\"\n> {\n /** ISO date string (`YYYY-MM-DD`) or empty. */\n value: string;\n onChange: (value: string) => void;\n label?: string;\n helperText?: string;\n error?: string;\n /** Lower bound (`YYYY-MM-DD`). */\n min?: string;\n /** Upper bound (`YYYY-MM-DD`). */\n max?: string;\n /** Mode. `date` (default), `datetime-local`, `time`, or `month`. */\n mode?: \"date\" | \"datetime-local\" | \"time\" | \"month\";\n wrapperClassName?: string;\n}\n\n/**\n * Thin wrapper around the native `<input type=\"date\">` (or `datetime-local`,\n * `time`, `month`). For richer pickers, pair with `react-datepicker` directly\n * — this primitive keeps the SDK dep-free.\n */\nexport const DatePicker = forwardRef<HTMLInputElement, DatePickerProps>(function DatePicker(\n { value, onChange, mode = \"date\", ...props },\n ref,\n) {\n return (\n <Input\n {...props}\n ref={ref}\n type={mode}\n value={value}\n onChange={(event) => onChange(event.target.value)}\n />\n );\n});\n"],"mappings":"4FAkCA,IAAa,GAAA,EAAa,EAAA,WAAA,CAA8C,SACpE,CAAE,QAAO,WAAU,OAAO,OAAQ,GAAG,GACrC,EACF,CACE,OACI,EAAA,EAAA,IAAA,CAAC,EAAA,MAAD,CACI,GAAI,EACC,MACL,KAAM,EACC,QACP,SAAW,GAAU,EAAS,EAAM,OAAO,KAAK,CACnD,CAAA,CAET,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"DateRangePicker.cjs","names":[],"sources":["../../../src/components/DateRangePicker/DateRangePicker.tsx"],"sourcesContent":["/**\n * @tempest-limits file-lines, function-lines — a range is two dates whose validity\n * depends on each other, so the body keeps both ends, the hovered preview end and\n * the open panel in one place; splitting the halves would mean each one reaching\n * into the other's state to clamp itself.\n */\nimport { useCallback, useMemo, useState } from \"react\";\nimport type { HTMLAttributes } from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport styles from \"./DateRangePicker.module.css\";\n\nexport type WeekStart = 0 | 1;\n\n/** A selected range. Either bound may be `null` while picking. */\nexport interface DateRange {\n start: Date | null;\n end: Date | null;\n}\n\nexport interface DateRangePickerProps extends Omit<\n HTMLAttributes<HTMLDivElement>,\n \"onChange\" | \"defaultValue\"\n> {\n /** Controlled selected range. */\n value: DateRange;\n /** Called with the new range as the user picks start then end. */\n onChange: (range: DateRange) => void;\n /** How many month grids to show side by side. Default `2`. */\n numberOfMonths?: number;\n /** Initial visible month (uncontrolled). Defaults to the range start or today. */\n defaultMonth?: Date;\n minDate?: Date;\n maxDate?: Date;\n /** First column of the week — `0` Sunday (default) or `1` Monday. */\n weekStartsOn?: WeekStart;\n}\n\nconst WEEKDAY_LABELS = [\"Sun\", \"Mon\", \"Tue\", \"Wed\", \"Thu\", \"Fri\", \"Sat\"];\nconst MONTH_LABELS = [\n \"January\",\n \"February\",\n \"March\",\n \"April\",\n \"May\",\n \"June\",\n \"July\",\n \"August\",\n \"September\",\n \"October\",\n \"November\",\n \"December\",\n];\nconst DAYS_IN_GRID = 42;\n\nfunction makeDate(year: number, month: number, day: number): Date {\n return new Date(year, month, day);\n}\n\nfunction startOfDay(date: Date): Date {\n return new Date(date.getFullYear(), date.getMonth(), date.getDate());\n}\n\nfunction addMonths(date: Date, offset: number): Date {\n return makeDate(date.getFullYear(), date.getMonth() + offset, 1);\n}\n\nfunction isSameDay(a: Date, b: Date): boolean {\n return (\n a.getFullYear() === b.getFullYear() &&\n a.getMonth() === b.getMonth() &&\n a.getDate() === b.getDate()\n );\n}\n\nfunction isOutOfRange(date: Date, minDate?: Date, maxDate?: Date): boolean {\n const day = startOfDay(date).getTime();\n if (minDate && day < startOfDay(minDate).getTime()) return true;\n if (maxDate && day > startOfDay(maxDate).getTime()) return true;\n return false;\n}\n\n/** Inclusive-exclusive-agnostic \"strictly between a and b\" (a/b order-independent). */\nfunction isBetween(date: Date, a: Date, b: Date): boolean {\n const t = startOfDay(date).getTime();\n const lo = Math.min(startOfDay(a).getTime(), startOfDay(b).getTime());\n const hi = Math.max(startOfDay(a).getTime(), startOfDay(b).getTime());\n return t > lo && t < hi;\n}\n\nfunction buildGrid(month: Date, weekStartsOn: WeekStart): Date[] {\n const year = month.getFullYear();\n const monthIndex = month.getMonth();\n const firstWeekday = makeDate(year, monthIndex, 1).getDay();\n const leading = (firstWeekday - weekStartsOn + 7) % 7;\n const start = makeDate(year, monthIndex, 1 - leading);\n return Array.from({ length: DAYS_IN_GRID }, (_, i) =>\n makeDate(start.getFullYear(), start.getMonth(), start.getDate() + i),\n );\n}\n\n/**\n * Date-range picker: pick a start day, then an end day, across one or more\n * month grids. The hovered day previews the range before the end is committed.\n * Pure `Date` math — no date libraries. For a single date, use `Calendar`.\n *\n * Selection: first click sets `start` (clears `end`); the next click sets `end`\n * (auto-ordered if you click earlier than the start); a third click starts over.\n */\nexport function DateRangePicker({\n value,\n onChange,\n numberOfMonths = 2,\n defaultMonth,\n minDate,\n maxDate,\n weekStartsOn = 0,\n className,\n ...props\n}: DateRangePickerProps) {\n const [visibleMonth, setVisibleMonth] = useState<Date>(() => {\n const base = defaultMonth ?? value.start ?? new Date();\n return makeDate(base.getFullYear(), base.getMonth(), 1);\n });\n const [hovered, setHovered] = useState<Date | null>(null);\n\n const today = useMemo(() => startOfDay(new Date()), []);\n const weekdayLabels = useMemo(\n () => Array.from({ length: 7 }, (_, i) => WEEKDAY_LABELS[(i + weekStartsOn) % 7]),\n [weekStartsOn],\n );\n\n const selectDate = useCallback(\n (date: Date): void => {\n if (isOutOfRange(date, minDate, maxDate)) return;\n const { start, end } = value;\n if (!start || end) {\n onChange({ start: date, end: null });\n } else if (startOfDay(date).getTime() < startOfDay(start).getTime()) {\n onChange({ start: date, end: start });\n } else {\n onChange({ start, end: date });\n }\n },\n [value, onChange, minDate, maxDate],\n );\n\n const changeMonth = useCallback((offset: number): void => {\n setVisibleMonth((current) => addMonths(current, offset));\n }, []);\n\n // Effective end used for preview highlighting (hovered day while picking).\n const previewEnd = value.start && !value.end ? hovered : value.end;\n\n function dayState(date: Date): {\n selected: boolean;\n inRange: boolean;\n isStart: boolean;\n isEnd: boolean;\n } {\n const { start } = value;\n const end = previewEnd;\n const isStart = start ? isSameDay(date, start) : false;\n const isEnd = end ? isSameDay(date, end) : false;\n const inRange = start && end ? isBetween(date, start, end) : false;\n return { selected: isStart || isEnd, inRange, isStart, isEnd };\n }\n\n return (\n <div className={cn(styles.root, className)} {...props}>\n <div className={styles.header}>\n <button\n type=\"button\"\n className={styles.nav}\n aria-label=\"Previous month\"\n onClick={() => changeMonth(-1)}\n >\n {\"‹\"}\n </button>\n <span className={styles.headerSpacer} aria-hidden />\n <button\n type=\"button\"\n className={styles.nav}\n aria-label=\"Next month\"\n onClick={() => changeMonth(1)}\n >\n {\"›\"}\n </button>\n </div>\n\n <div className={styles.months} onMouseLeave={() => setHovered(null)}>\n {Array.from({ length: Math.max(1, numberOfMonths) }, (_, monthOffset) => {\n const month = addMonths(visibleMonth, monthOffset);\n const cells = buildGrid(month, weekStartsOn);\n return (\n <div key={monthOffset} className={styles.month}>\n <div className={styles.title} aria-live=\"polite\">\n {MONTH_LABELS[month.getMonth()]} {month.getFullYear()}\n </div>\n <div className={styles.weekdays} role=\"row\">\n {weekdayLabels.map((label) => (\n <span\n key={label}\n className={styles.weekday}\n role=\"columnheader\"\n >\n {label}\n </span>\n ))}\n </div>\n <div className={styles.grid} role=\"grid\">\n {cells.map((date, index) => {\n const inMonth = date.getMonth() === month.getMonth();\n const disabled = isOutOfRange(date, minDate, maxDate);\n const { selected, inRange, isStart, isEnd } = dayState(date);\n const isToday = isSameDay(date, today);\n return (\n <button\n key={index}\n type=\"button\"\n role=\"gridcell\"\n className={cn(\n styles.day,\n !inMonth && styles.outside,\n inRange && styles.inRange,\n selected && styles.selected,\n isStart && styles.rangeStart,\n isEnd && styles.rangeEnd,\n isToday && styles.today,\n )}\n aria-label={`${MONTH_LABELS[date.getMonth()]} ${date.getDate()}, ${date.getFullYear()}`}\n aria-selected={selected}\n aria-current={isToday ? \"date\" : undefined}\n disabled={disabled}\n onClick={() => selectDate(date)}\n onMouseEnter={() => setHovered(date)}\n >\n {date.getDate()}\n </button>\n );\n })}\n </div>\n </div>\n );\n })}\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"DateRangePicker.cjs","names":[],"sources":["../../../src/components/DateRangePicker/DateRangePicker.tsx"],"sourcesContent":["/**\n * @tempest-limits file-lines, function-lines — a range is two dates whose validity\n * depends on each other, so the body keeps both ends, the hovered preview end and\n * the open panel in one place; splitting the halves would mean each one reaching\n * into the other's state to clamp itself.\n */\nimport { useCallback, useMemo, useState } from \"react\";\nimport type { HTMLAttributes } from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport styles from \"./DateRangePicker.module.css\";\n\nexport type WeekStart = 0 | 1;\n\n/** A selected range. Either bound may be `null` while picking. */\nexport interface DateRange {\n start: Date | null;\n end: Date | null;\n}\n\nexport interface DateRangePickerProps extends Omit<\n HTMLAttributes<HTMLDivElement>,\n \"onChange\" | \"defaultValue\"\n> {\n /** Controlled selected range. */\n value: DateRange;\n /** Called with the new range as the user picks start then end. */\n onChange: (range: DateRange) => void;\n /** How many month grids to show side by side. Default `2`. */\n numberOfMonths?: number;\n /** Initial visible month (uncontrolled). Defaults to the range start or today. */\n defaultMonth?: Date;\n minDate?: Date;\n maxDate?: Date;\n /** First column of the week — `0` Sunday (default) or `1` Monday. */\n weekStartsOn?: WeekStart;\n}\n\nconst WEEKDAY_LABELS = [\"Sun\", \"Mon\", \"Tue\", \"Wed\", \"Thu\", \"Fri\", \"Sat\"];\nconst MONTH_LABELS = [\n \"January\",\n \"February\",\n \"March\",\n \"April\",\n \"May\",\n \"June\",\n \"July\",\n \"August\",\n \"September\",\n \"October\",\n \"November\",\n \"December\",\n];\nconst DAYS_IN_GRID = 42;\n\nfunction makeDate(year: number, month: number, day: number): Date {\n return new Date(year, month, day);\n}\n\nfunction startOfDay(date: Date): Date {\n return new Date(date.getFullYear(), date.getMonth(), date.getDate());\n}\n\nfunction addMonths(date: Date, offset: number): Date {\n return makeDate(date.getFullYear(), date.getMonth() + offset, 1);\n}\n\nfunction isSameDay(a: Date, b: Date): boolean {\n return (\n a.getFullYear() === b.getFullYear() &&\n a.getMonth() === b.getMonth() &&\n a.getDate() === b.getDate()\n );\n}\n\nfunction isOutOfRange(date: Date, minDate?: Date, maxDate?: Date): boolean {\n const day = startOfDay(date).getTime();\n if (minDate && day < startOfDay(minDate).getTime()) return true;\n if (maxDate && day > startOfDay(maxDate).getTime()) return true;\n return false;\n}\n\n/** Inclusive-exclusive-agnostic \"strictly between a and b\" (a/b order-independent). */\nfunction isBetween(date: Date, a: Date, b: Date): boolean {\n const t = startOfDay(date).getTime();\n const lo = Math.min(startOfDay(a).getTime(), startOfDay(b).getTime());\n const hi = Math.max(startOfDay(a).getTime(), startOfDay(b).getTime());\n return t > lo && t < hi;\n}\n\nfunction buildGrid(month: Date, weekStartsOn: WeekStart): Date[] {\n const year = month.getFullYear();\n const monthIndex = month.getMonth();\n const firstWeekday = makeDate(year, monthIndex, 1).getDay();\n const leading = (firstWeekday - weekStartsOn + 7) % 7;\n const start = makeDate(year, monthIndex, 1 - leading);\n return Array.from({ length: DAYS_IN_GRID }, (_, i) =>\n makeDate(start.getFullYear(), start.getMonth(), start.getDate() + i),\n );\n}\n\n/**\n * Date-range picker: pick a start day, then an end day, across one or more\n * month grids. The hovered day previews the range before the end is committed.\n * Pure `Date` math — no date libraries. For a single date, use `Calendar`.\n *\n * Selection: first click sets `start` (clears `end`); the next click sets `end`\n * (auto-ordered if you click earlier than the start); a third click starts over.\n */\nexport function DateRangePicker({\n value,\n onChange,\n numberOfMonths = 2,\n defaultMonth,\n minDate,\n maxDate,\n weekStartsOn = 0,\n className,\n ...props\n}: DateRangePickerProps) {\n const [visibleMonth, setVisibleMonth] = useState<Date>(() => {\n const base = defaultMonth ?? value.start ?? new Date();\n return makeDate(base.getFullYear(), base.getMonth(), 1);\n });\n const [hovered, setHovered] = useState<Date | null>(null);\n\n const today = useMemo(() => startOfDay(new Date()), []);\n const weekdayLabels = useMemo(\n () => Array.from({ length: 7 }, (_, i) => WEEKDAY_LABELS[(i + weekStartsOn) % 7]),\n [weekStartsOn],\n );\n\n const selectDate = useCallback(\n (date: Date): void => {\n if (isOutOfRange(date, minDate, maxDate)) return;\n const { start, end } = value;\n if (!start || end) {\n onChange({ start: date, end: null });\n } else if (startOfDay(date).getTime() < startOfDay(start).getTime()) {\n onChange({ start: date, end: start });\n } else {\n onChange({ start, end: date });\n }\n },\n [value, onChange, minDate, maxDate],\n );\n\n const changeMonth = useCallback((offset: number): void => {\n setVisibleMonth((current) => addMonths(current, offset));\n }, []);\n\n // Effective end used for preview highlighting (hovered day while picking).\n const previewEnd = value.start && !value.end ? hovered : value.end;\n\n function dayState(date: Date): {\n selected: boolean;\n inRange: boolean;\n isStart: boolean;\n isEnd: boolean;\n } {\n const { start } = value;\n const end = previewEnd;\n const isStart = start ? isSameDay(date, start) : false;\n const isEnd = end ? isSameDay(date, end) : false;\n const inRange = start && end ? isBetween(date, start, end) : false;\n return { selected: isStart || isEnd, inRange, isStart, isEnd };\n }\n\n return (\n <div className={cn(styles.root, className)} {...props}>\n <div className={styles.header}>\n <button\n type=\"button\"\n className={styles.nav}\n aria-label=\"Previous month\"\n onClick={() => changeMonth(-1)}\n >\n {\"‹\"}\n </button>\n <span className={styles.headerSpacer} aria-hidden />\n <button\n type=\"button\"\n className={styles.nav}\n aria-label=\"Next month\"\n onClick={() => changeMonth(1)}\n >\n {\"›\"}\n </button>\n </div>\n\n <div className={styles.months} onMouseLeave={() => setHovered(null)}>\n {Array.from({ length: Math.max(1, numberOfMonths) }, (_, monthOffset) => {\n const month = addMonths(visibleMonth, monthOffset);\n const cells = buildGrid(month, weekStartsOn);\n return (\n <div key={monthOffset} className={styles.month}>\n <div className={styles.title} aria-live=\"polite\">\n {MONTH_LABELS[month.getMonth()]} {month.getFullYear()}\n </div>\n <div className={styles.weekdays} role=\"row\">\n {weekdayLabels.map((label) => (\n <span\n key={label}\n className={styles.weekday}\n role=\"columnheader\"\n >\n {label}\n </span>\n ))}\n </div>\n <div className={styles.grid} role=\"grid\">\n {cells.map((date, index) => {\n const inMonth = date.getMonth() === month.getMonth();\n const disabled = isOutOfRange(date, minDate, maxDate);\n const { selected, inRange, isStart, isEnd } = dayState(date);\n const isToday = isSameDay(date, today);\n return (\n <button\n key={index}\n type=\"button\"\n role=\"gridcell\"\n className={cn(\n styles.day,\n !inMonth && styles.outside,\n inRange && styles.inRange,\n selected && styles.selected,\n isStart && styles.rangeStart,\n isEnd && styles.rangeEnd,\n isToday && styles.today,\n )}\n aria-label={`${MONTH_LABELS[date.getMonth()]} ${date.getDate()}, ${date.getFullYear()}`}\n aria-selected={selected}\n aria-current={isToday ? \"date\" : undefined}\n disabled={disabled}\n onClick={() => selectDate(date)}\n onMouseEnter={() => setHovered(date)}\n >\n {date.getDate()}\n </button>\n );\n })}\n </div>\n </div>\n );\n })}\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"DateRangePicker.js","names":[],"sources":["../../../src/components/DateRangePicker/DateRangePicker.tsx"],"sourcesContent":["/**\n * @tempest-limits file-lines, function-lines — a range is two dates whose validity\n * depends on each other, so the body keeps both ends, the hovered preview end and\n * the open panel in one place; splitting the halves would mean each one reaching\n * into the other's state to clamp itself.\n */\nimport { useCallback, useMemo, useState } from \"react\";\nimport type { HTMLAttributes } from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport styles from \"./DateRangePicker.module.css\";\n\nexport type WeekStart = 0 | 1;\n\n/** A selected range. Either bound may be `null` while picking. */\nexport interface DateRange {\n start: Date | null;\n end: Date | null;\n}\n\nexport interface DateRangePickerProps extends Omit<\n HTMLAttributes<HTMLDivElement>,\n \"onChange\" | \"defaultValue\"\n> {\n /** Controlled selected range. */\n value: DateRange;\n /** Called with the new range as the user picks start then end. */\n onChange: (range: DateRange) => void;\n /** How many month grids to show side by side. Default `2`. */\n numberOfMonths?: number;\n /** Initial visible month (uncontrolled). Defaults to the range start or today. */\n defaultMonth?: Date;\n minDate?: Date;\n maxDate?: Date;\n /** First column of the week — `0` Sunday (default) or `1` Monday. */\n weekStartsOn?: WeekStart;\n}\n\nconst WEEKDAY_LABELS = [\"Sun\", \"Mon\", \"Tue\", \"Wed\", \"Thu\", \"Fri\", \"Sat\"];\nconst MONTH_LABELS = [\n \"January\",\n \"February\",\n \"March\",\n \"April\",\n \"May\",\n \"June\",\n \"July\",\n \"August\",\n \"September\",\n \"October\",\n \"November\",\n \"December\",\n];\nconst DAYS_IN_GRID = 42;\n\nfunction makeDate(year: number, month: number, day: number): Date {\n return new Date(year, month, day);\n}\n\nfunction startOfDay(date: Date): Date {\n return new Date(date.getFullYear(), date.getMonth(), date.getDate());\n}\n\nfunction addMonths(date: Date, offset: number): Date {\n return makeDate(date.getFullYear(), date.getMonth() + offset, 1);\n}\n\nfunction isSameDay(a: Date, b: Date): boolean {\n return (\n a.getFullYear() === b.getFullYear() &&\n a.getMonth() === b.getMonth() &&\n a.getDate() === b.getDate()\n );\n}\n\nfunction isOutOfRange(date: Date, minDate?: Date, maxDate?: Date): boolean {\n const day = startOfDay(date).getTime();\n if (minDate && day < startOfDay(minDate).getTime()) return true;\n if (maxDate && day > startOfDay(maxDate).getTime()) return true;\n return false;\n}\n\n/** Inclusive-exclusive-agnostic \"strictly between a and b\" (a/b order-independent). */\nfunction isBetween(date: Date, a: Date, b: Date): boolean {\n const t = startOfDay(date).getTime();\n const lo = Math.min(startOfDay(a).getTime(), startOfDay(b).getTime());\n const hi = Math.max(startOfDay(a).getTime(), startOfDay(b).getTime());\n return t > lo && t < hi;\n}\n\nfunction buildGrid(month: Date, weekStartsOn: WeekStart): Date[] {\n const year = month.getFullYear();\n const monthIndex = month.getMonth();\n const firstWeekday = makeDate(year, monthIndex, 1).getDay();\n const leading = (firstWeekday - weekStartsOn + 7) % 7;\n const start = makeDate(year, monthIndex, 1 - leading);\n return Array.from({ length: DAYS_IN_GRID }, (_, i) =>\n makeDate(start.getFullYear(), start.getMonth(), start.getDate() + i),\n );\n}\n\n/**\n * Date-range picker: pick a start day, then an end day, across one or more\n * month grids. The hovered day previews the range before the end is committed.\n * Pure `Date` math — no date libraries. For a single date, use `Calendar`.\n *\n * Selection: first click sets `start` (clears `end`); the next click sets `end`\n * (auto-ordered if you click earlier than the start); a third click starts over.\n */\nexport function DateRangePicker({\n value,\n onChange,\n numberOfMonths = 2,\n defaultMonth,\n minDate,\n maxDate,\n weekStartsOn = 0,\n className,\n ...props\n}: DateRangePickerProps) {\n const [visibleMonth, setVisibleMonth] = useState<Date>(() => {\n const base = defaultMonth ?? value.start ?? new Date();\n return makeDate(base.getFullYear(), base.getMonth(), 1);\n });\n const [hovered, setHovered] = useState<Date | null>(null);\n\n const today = useMemo(() => startOfDay(new Date()), []);\n const weekdayLabels = useMemo(\n () => Array.from({ length: 7 }, (_, i) => WEEKDAY_LABELS[(i + weekStartsOn) % 7]),\n [weekStartsOn],\n );\n\n const selectDate = useCallback(\n (date: Date): void => {\n if (isOutOfRange(date, minDate, maxDate)) return;\n const { start, end } = value;\n if (!start || end) {\n onChange({ start: date, end: null });\n } else if (startOfDay(date).getTime() < startOfDay(start).getTime()) {\n onChange({ start: date, end: start });\n } else {\n onChange({ start, end: date });\n }\n },\n [value, onChange, minDate, maxDate],\n );\n\n const changeMonth = useCallback((offset: number): void => {\n setVisibleMonth((current) => addMonths(current, offset));\n }, []);\n\n // Effective end used for preview highlighting (hovered day while picking).\n const previewEnd = value.start && !value.end ? hovered : value.end;\n\n function dayState(date: Date): {\n selected: boolean;\n inRange: boolean;\n isStart: boolean;\n isEnd: boolean;\n } {\n const { start } = value;\n const end = previewEnd;\n const isStart = start ? isSameDay(date, start) : false;\n const isEnd = end ? isSameDay(date, end) : false;\n const inRange = start && end ? isBetween(date, start, end) : false;\n return { selected: isStart || isEnd, inRange, isStart, isEnd };\n }\n\n return (\n <div className={cn(styles.root, className)} {...props}>\n <div className={styles.header}>\n <button\n type=\"button\"\n className={styles.nav}\n aria-label=\"Previous month\"\n onClick={() => changeMonth(-1)}\n >\n {\"‹\"}\n </button>\n <span className={styles.headerSpacer} aria-hidden />\n <button\n type=\"button\"\n className={styles.nav}\n aria-label=\"Next month\"\n onClick={() => changeMonth(1)}\n >\n {\"›\"}\n </button>\n </div>\n\n <div className={styles.months} onMouseLeave={() => setHovered(null)}>\n {Array.from({ length: Math.max(1, numberOfMonths) }, (_, monthOffset) => {\n const month = addMonths(visibleMonth, monthOffset);\n const cells = buildGrid(month, weekStartsOn);\n return (\n <div key={monthOffset} className={styles.month}>\n <div className={styles.title} aria-live=\"polite\">\n {MONTH_LABELS[month.getMonth()]} {month.getFullYear()}\n </div>\n <div className={styles.weekdays} role=\"row\">\n {weekdayLabels.map((label) => (\n <span\n key={label}\n className={styles.weekday}\n role=\"columnheader\"\n >\n {label}\n </span>\n ))}\n </div>\n <div className={styles.grid} role=\"grid\">\n {cells.map((date, index) => {\n const inMonth = date.getMonth() === month.getMonth();\n const disabled = isOutOfRange(date, minDate, maxDate);\n const { selected, inRange, isStart, isEnd } = dayState(date);\n const isToday = isSameDay(date, today);\n return (\n <button\n key={index}\n type=\"button\"\n role=\"gridcell\"\n className={cn(\n styles.day,\n !inMonth && styles.outside,\n inRange && styles.inRange,\n selected && styles.selected,\n isStart && styles.rangeStart,\n isEnd && styles.rangeEnd,\n isToday && styles.today,\n )}\n aria-label={`${MONTH_LABELS[date.getMonth()]} ${date.getDate()}, ${date.getFullYear()}`}\n aria-selected={selected}\n aria-current={isToday ? \"date\" : undefined}\n disabled={disabled}\n onClick={() => selectDate(date)}\n onMouseEnter={() => setHovered(date)}\n >\n {date.getDate()}\n </button>\n );\n })}\n </div>\n </div>\n );\n })}\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"DateRangePicker.js","names":[],"sources":["../../../src/components/DateRangePicker/DateRangePicker.tsx"],"sourcesContent":["/**\n * @tempest-limits file-lines, function-lines — a range is two dates whose validity\n * depends on each other, so the body keeps both ends, the hovered preview end and\n * the open panel in one place; splitting the halves would mean each one reaching\n * into the other's state to clamp itself.\n */\nimport { useCallback, useMemo, useState } from \"react\";\nimport type { HTMLAttributes } from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport styles from \"./DateRangePicker.module.css\";\n\nexport type WeekStart = 0 | 1;\n\n/** A selected range. Either bound may be `null` while picking. */\nexport interface DateRange {\n start: Date | null;\n end: Date | null;\n}\n\nexport interface DateRangePickerProps extends Omit<\n HTMLAttributes<HTMLDivElement>,\n \"onChange\" | \"defaultValue\"\n> {\n /** Controlled selected range. */\n value: DateRange;\n /** Called with the new range as the user picks start then end. */\n onChange: (range: DateRange) => void;\n /** How many month grids to show side by side. Default `2`. */\n numberOfMonths?: number;\n /** Initial visible month (uncontrolled). Defaults to the range start or today. */\n defaultMonth?: Date;\n minDate?: Date;\n maxDate?: Date;\n /** First column of the week — `0` Sunday (default) or `1` Monday. */\n weekStartsOn?: WeekStart;\n}\n\nconst WEEKDAY_LABELS = [\"Sun\", \"Mon\", \"Tue\", \"Wed\", \"Thu\", \"Fri\", \"Sat\"];\nconst MONTH_LABELS = [\n \"January\",\n \"February\",\n \"March\",\n \"April\",\n \"May\",\n \"June\",\n \"July\",\n \"August\",\n \"September\",\n \"October\",\n \"November\",\n \"December\",\n];\nconst DAYS_IN_GRID = 42;\n\nfunction makeDate(year: number, month: number, day: number): Date {\n return new Date(year, month, day);\n}\n\nfunction startOfDay(date: Date): Date {\n return new Date(date.getFullYear(), date.getMonth(), date.getDate());\n}\n\nfunction addMonths(date: Date, offset: number): Date {\n return makeDate(date.getFullYear(), date.getMonth() + offset, 1);\n}\n\nfunction isSameDay(a: Date, b: Date): boolean {\n return (\n a.getFullYear() === b.getFullYear() &&\n a.getMonth() === b.getMonth() &&\n a.getDate() === b.getDate()\n );\n}\n\nfunction isOutOfRange(date: Date, minDate?: Date, maxDate?: Date): boolean {\n const day = startOfDay(date).getTime();\n if (minDate && day < startOfDay(minDate).getTime()) return true;\n if (maxDate && day > startOfDay(maxDate).getTime()) return true;\n return false;\n}\n\n/** Inclusive-exclusive-agnostic \"strictly between a and b\" (a/b order-independent). */\nfunction isBetween(date: Date, a: Date, b: Date): boolean {\n const t = startOfDay(date).getTime();\n const lo = Math.min(startOfDay(a).getTime(), startOfDay(b).getTime());\n const hi = Math.max(startOfDay(a).getTime(), startOfDay(b).getTime());\n return t > lo && t < hi;\n}\n\nfunction buildGrid(month: Date, weekStartsOn: WeekStart): Date[] {\n const year = month.getFullYear();\n const monthIndex = month.getMonth();\n const firstWeekday = makeDate(year, monthIndex, 1).getDay();\n const leading = (firstWeekday - weekStartsOn + 7) % 7;\n const start = makeDate(year, monthIndex, 1 - leading);\n return Array.from({ length: DAYS_IN_GRID }, (_, i) =>\n makeDate(start.getFullYear(), start.getMonth(), start.getDate() + i),\n );\n}\n\n/**\n * Date-range picker: pick a start day, then an end day, across one or more\n * month grids. The hovered day previews the range before the end is committed.\n * Pure `Date` math — no date libraries. For a single date, use `Calendar`.\n *\n * Selection: first click sets `start` (clears `end`); the next click sets `end`\n * (auto-ordered if you click earlier than the start); a third click starts over.\n */\nexport function DateRangePicker({\n value,\n onChange,\n numberOfMonths = 2,\n defaultMonth,\n minDate,\n maxDate,\n weekStartsOn = 0,\n className,\n ...props\n}: DateRangePickerProps) {\n const [visibleMonth, setVisibleMonth] = useState<Date>(() => {\n const base = defaultMonth ?? value.start ?? new Date();\n return makeDate(base.getFullYear(), base.getMonth(), 1);\n });\n const [hovered, setHovered] = useState<Date | null>(null);\n\n const today = useMemo(() => startOfDay(new Date()), []);\n const weekdayLabels = useMemo(\n () => Array.from({ length: 7 }, (_, i) => WEEKDAY_LABELS[(i + weekStartsOn) % 7]),\n [weekStartsOn],\n );\n\n const selectDate = useCallback(\n (date: Date): void => {\n if (isOutOfRange(date, minDate, maxDate)) return;\n const { start, end } = value;\n if (!start || end) {\n onChange({ start: date, end: null });\n } else if (startOfDay(date).getTime() < startOfDay(start).getTime()) {\n onChange({ start: date, end: start });\n } else {\n onChange({ start, end: date });\n }\n },\n [value, onChange, minDate, maxDate],\n );\n\n const changeMonth = useCallback((offset: number): void => {\n setVisibleMonth((current) => addMonths(current, offset));\n }, []);\n\n // Effective end used for preview highlighting (hovered day while picking).\n const previewEnd = value.start && !value.end ? hovered : value.end;\n\n function dayState(date: Date): {\n selected: boolean;\n inRange: boolean;\n isStart: boolean;\n isEnd: boolean;\n } {\n const { start } = value;\n const end = previewEnd;\n const isStart = start ? isSameDay(date, start) : false;\n const isEnd = end ? isSameDay(date, end) : false;\n const inRange = start && end ? isBetween(date, start, end) : false;\n return { selected: isStart || isEnd, inRange, isStart, isEnd };\n }\n\n return (\n <div className={cn(styles.root, className)} {...props}>\n <div className={styles.header}>\n <button\n type=\"button\"\n className={styles.nav}\n aria-label=\"Previous month\"\n onClick={() => changeMonth(-1)}\n >\n {\"‹\"}\n </button>\n <span className={styles.headerSpacer} aria-hidden />\n <button\n type=\"button\"\n className={styles.nav}\n aria-label=\"Next month\"\n onClick={() => changeMonth(1)}\n >\n {\"›\"}\n </button>\n </div>\n\n <div className={styles.months} onMouseLeave={() => setHovered(null)}>\n {Array.from({ length: Math.max(1, numberOfMonths) }, (_, monthOffset) => {\n const month = addMonths(visibleMonth, monthOffset);\n const cells = buildGrid(month, weekStartsOn);\n return (\n <div key={monthOffset} className={styles.month}>\n <div className={styles.title} aria-live=\"polite\">\n {MONTH_LABELS[month.getMonth()]} {month.getFullYear()}\n </div>\n <div className={styles.weekdays} role=\"row\">\n {weekdayLabels.map((label) => (\n <span\n key={label}\n className={styles.weekday}\n role=\"columnheader\"\n >\n {label}\n </span>\n ))}\n </div>\n <div className={styles.grid} role=\"grid\">\n {cells.map((date, index) => {\n const inMonth = date.getMonth() === month.getMonth();\n const disabled = isOutOfRange(date, minDate, maxDate);\n const { selected, inRange, isStart, isEnd } = dayState(date);\n const isToday = isSameDay(date, today);\n return (\n <button\n key={index}\n type=\"button\"\n role=\"gridcell\"\n className={cn(\n styles.day,\n !inMonth && styles.outside,\n inRange && styles.inRange,\n selected && styles.selected,\n isStart && styles.rangeStart,\n isEnd && styles.rangeEnd,\n isToday && styles.today,\n )}\n aria-label={`${MONTH_LABELS[date.getMonth()]} ${date.getDate()}, ${date.getFullYear()}`}\n aria-selected={selected}\n aria-current={isToday ? \"date\" : undefined}\n disabled={disabled}\n onClick={() => selectDate(date)}\n onMouseEnter={() => setHovered(date)}\n >\n {date.getDate()}\n </button>\n );\n })}\n </div>\n </div>\n );\n })}\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"DescriptionList.cjs","names":[],"sources":["../../../src/components/DescriptionList/DescriptionList.tsx"],"sourcesContent":["import type { HTMLAttributes, ReactNode } from \"react\";\nimport { Fragment } from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport styles from \"./DescriptionList.module.css\";\n\nexport interface DescriptionListItem {\n /** The key/label rendered inside a `<dt>`. */\n term: ReactNode;\n /** The value rendered inside a `<dd>`. */\n description: ReactNode;\n}\n\nexport interface DescriptionListProps extends HTMLAttributes<HTMLDListElement> {\n /** Term/description pairs rendered as `<dt>`/`<dd>` rows. */\n items: DescriptionListItem[];\n}\n\n/**\n * Renders a semantic `<dl>` of term/description pairs with minimal\n * token-based key/value styling.\n *\n * @param items - The term/description pairs to render.\n * @returns The rendered description list.\n */\nexport function DescriptionList({ items, className, ...props }: DescriptionListProps) {\n return (\n <dl className={cn(styles.list, className)} {...props}>\n {items.map((item, index) => (\n <Fragment key={index}>\n <dt className={styles.term}>{item.term}</dt>\n <dd className={styles.description}>{item.description}</dd>\n </Fragment>\n ))}\n </dl>\n );\n}\n"],"mappings":"sIAwBA,SAAgB,EAAgB,CAAE,QAAO,YAAW,GAAG,GAA+B,CAClF,OACI,EAAA,EAAA,IAAA,CAAC,KAAD,CAAI,UAAW,EAAA,GAAG,EAAA,QAAO,KAAM,CAAS,EAAG,GAAI,WAC1C,EAAM,KAAK,EAAM,KACd,EAAA,EAAA,KAAA,CAAC,EAAA,SAAD,CAAA,SAAA,EACI,EAAA,EAAA,IAAA,CAAC,KAAD,CAAI,UAAW,EAAA,QAAO,cAAO,EAAK,IAAS,CAAA,GAC3C,EAAA,EAAA,IAAA,CAAC,KAAD,CAAI,UAAW,EAAA,QAAO,qBAAc,EAAK,WAAgB,CAAA,CACnD,CAAA,EAHK,CAGL,CACb,CACD,CAAA,CAEZ"}
1
+ {"version":3,"file":"DescriptionList.cjs","names":[],"sources":["../../../src/components/DescriptionList/DescriptionList.tsx"],"sourcesContent":["import type { HTMLAttributes, ReactNode } from \"react\";\nimport { Fragment } from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport styles from \"./DescriptionList.module.css\";\n\nexport interface DescriptionListItem {\n /** The key/label rendered inside a `<dt>`. */\n term: ReactNode;\n /** The value rendered inside a `<dd>`. */\n description: ReactNode;\n}\n\nexport interface DescriptionListProps extends HTMLAttributes<HTMLDListElement> {\n /** Term/description pairs rendered as `<dt>`/`<dd>` rows. */\n items: DescriptionListItem[];\n}\n\n/**\n * Renders a semantic `<dl>` of term/description pairs with minimal\n * token-based key/value styling.\n *\n * @param items - The term/description pairs to render.\n * @returns The rendered description list.\n */\nexport function DescriptionList({ items, className, ...props }: DescriptionListProps) {\n return (\n <dl className={cn(styles.list, className)} {...props}>\n {items.map((item, index) => (\n <Fragment key={index}>\n <dt className={styles.term}>{item.term}</dt>\n <dd className={styles.description}>{item.description}</dd>\n </Fragment>\n ))}\n </dl>\n );\n}\n"],"mappings":"sIAwBA,SAAgB,EAAgB,CAAE,QAAO,YAAW,GAAG,GAA+B,CAClF,OACI,EAAA,EAAA,IAAA,CAAC,KAAD,CAAI,UAAW,EAAA,GAAG,EAAA,QAAO,KAAM,CAAS,EAAG,GAAI,EAC1C,SAAA,EAAM,KAAK,EAAM,KACd,EAAA,EAAA,KAAA,CAAC,EAAA,SAAD,CAAA,SAAA,EACI,EAAA,EAAA,IAAA,CAAC,KAAD,CAAI,UAAW,EAAA,QAAO,KAAO,SAAA,EAAK,IAAS,CAAA,GAC3C,EAAA,EAAA,IAAA,CAAC,KAAD,CAAI,UAAW,EAAA,QAAO,YAAc,SAAA,EAAK,WAAgB,CAAA,CACnD,CAAA,EAHK,CAGL,CACb,CACD,CAAA,CAEZ"}