tempest-react-sdk 0.38.3 → 0.39.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/_virtual/_rolldown/runtime.cjs +1 -1
- package/dist/access/access-control-context.cjs.map +1 -1
- package/dist/access/use-can.cjs.map +1 -1
- package/dist/app/AppProviders.cjs.map +1 -1
- package/dist/app/AppProviders.js.map +1 -1
- package/dist/audio/use-audio-recorder.cjs.map +1 -1
- package/dist/audio/use-audio.cjs.map +1 -1
- package/dist/audio/use-microphone.cjs.map +1 -1
- package/dist/auth/create-auth-store.cjs.map +1 -1
- package/dist/auth/lazy-with-retry.cjs.map +1 -1
- package/dist/auth/use-passkey.cjs.map +1 -1
- package/dist/br/BrazilMap.cjs +1 -1
- package/dist/br/BrazilMap.cjs.map +1 -1
- package/dist/br/BrazilMap.js +2 -2
- package/dist/br/BrazilMap.js.map +1 -1
- package/dist/br/BrazilStateCitySelect.cjs.map +1 -1
- package/dist/br/BrazilStateCitySelect.js.map +1 -1
- package/dist/br/BrazilStateMap.cjs +1 -1
- package/dist/br/BrazilStateMap.cjs.map +1 -1
- package/dist/br/BrazilStateMap.js +2 -2
- package/dist/br/BrazilStateMap.js.map +1 -1
- package/dist/br/MapLegend.cjs.map +1 -1
- package/dist/br/MapLegend.js.map +1 -1
- package/dist/br/MapMarkers.cjs.map +1 -1
- package/dist/br/MapMarkers.js.map +1 -1
- package/dist/br/MunicipalitySearch.cjs.map +1 -1
- package/dist/br/MunicipalitySearch.js.map +1 -1
- package/dist/br/PixQRCode.cjs.map +1 -1
- package/dist/br/PixQRCode.js.map +1 -1
- package/dist/br/use-map-hover.cjs.map +1 -1
- package/dist/br/use-map-zoom.cjs.map +1 -1
- package/dist/capture/media-recorder.cjs +1 -1
- package/dist/capture/media-recorder.js +2 -2
- package/dist/capture/use-barcode-scanner.cjs +1 -1
- package/dist/capture/use-barcode-scanner.cjs.map +1 -1
- package/dist/capture/use-barcode-scanner.js +2 -2
- package/dist/capture/use-barcode-scanner.js.map +1 -1
- package/dist/capture/use-screen-capture.cjs.map +1 -1
- package/dist/capture/use-speech-recognition.cjs.map +1 -1
- package/dist/capture/use-torch.cjs.map +1 -1
- package/dist/capture/use-video-recorder.cjs.map +1 -1
- package/dist/charts/AreaChart.cjs.map +1 -1
- package/dist/charts/AreaChart.js.map +1 -1
- package/dist/charts/BarChart.cjs.map +1 -1
- package/dist/charts/BarChart.js.map +1 -1
- package/dist/charts/LineChart.cjs.map +1 -1
- package/dist/charts/LineChart.js.map +1 -1
- package/dist/charts/PieChart.cjs.map +1 -1
- package/dist/charts/PieChart.js.map +1 -1
- package/dist/charts/RadarChart.cjs.map +1 -1
- package/dist/charts/RadarChart.js.map +1 -1
- package/dist/charts/use-chart-colors.cjs.map +1 -1
- package/dist/components/AIChat/AIChat.cjs.map +1 -1
- package/dist/components/AIChat/AIChat.js.map +1 -1
- package/dist/components/AIChat/AIChatComposer.cjs.map +1 -1
- package/dist/components/AIChat/AIChatComposer.js.map +1 -1
- package/dist/components/AIChat/AIChatTurn.cjs.map +1 -1
- package/dist/components/AIChat/AIChatTurn.js.map +1 -1
- package/dist/components/Accordion/Accordion.cjs.map +1 -1
- package/dist/components/Accordion/Accordion.js.map +1 -1
- package/dist/components/Alert/Alert.cjs.map +1 -1
- package/dist/components/Alert/Alert.js.map +1 -1
- package/dist/components/AppBar/AppBar.cjs.map +1 -1
- package/dist/components/AppBar/AppBar.js.map +1 -1
- package/dist/components/AppShell/AppShell.cjs.map +1 -1
- package/dist/components/AppShell/AppShell.js.map +1 -1
- package/dist/components/AudioPlayer/AudioPlayer.cjs.map +1 -1
- package/dist/components/AudioPlayer/AudioPlayer.js.map +1 -1
- package/dist/components/AudioRecorder/AudioRecorder.cjs.map +1 -1
- package/dist/components/AudioRecorder/AudioRecorder.js.map +1 -1
- package/dist/components/Avatar/Avatar.cjs.map +1 -1
- package/dist/components/Avatar/Avatar.js.map +1 -1
- package/dist/components/AvatarGroup/AvatarGroup.cjs.map +1 -1
- package/dist/components/AvatarGroup/AvatarGroup.js.map +1 -1
- package/dist/components/Badge/Badge.cjs.map +1 -1
- package/dist/components/Badge/Badge.js.map +1 -1
- package/dist/components/Banner/Banner.cjs.map +1 -1
- package/dist/components/Banner/Banner.js.map +1 -1
- package/dist/components/BarcodeScanner/BarcodeScanner.cjs.map +1 -1
- package/dist/components/BarcodeScanner/BarcodeScanner.js.map +1 -1
- package/dist/components/BottomNavigation/BottomNavigation.cjs.map +1 -1
- package/dist/components/BottomNavigation/BottomNavigation.js.map +1 -1
- package/dist/components/BottomSheet/BottomSheet.cjs.map +1 -1
- package/dist/components/BottomSheet/BottomSheet.js.map +1 -1
- package/dist/components/Breadcrumbs/Breadcrumbs.cjs.map +1 -1
- package/dist/components/Breadcrumbs/Breadcrumbs.js.map +1 -1
- package/dist/components/Button/Button.cjs.map +1 -1
- package/dist/components/Button/Button.js.map +1 -1
- package/dist/components/Calendar/Calendar.cjs +1 -1
- package/dist/components/Calendar/Calendar.cjs.map +1 -1
- package/dist/components/Calendar/Calendar.js +38 -41
- package/dist/components/Calendar/Calendar.js.map +1 -1
- package/dist/components/Card/Card.cjs.map +1 -1
- package/dist/components/Card/Card.js.map +1 -1
- package/dist/components/Carousel/Carousel.cjs.map +1 -1
- package/dist/components/Carousel/Carousel.js.map +1 -1
- package/dist/components/Chat/Chat.cjs.map +1 -1
- package/dist/components/Chat/Chat.js.map +1 -1
- package/dist/components/Chat/ChatComposer.cjs.map +1 -1
- package/dist/components/Chat/ChatComposer.js.map +1 -1
- package/dist/components/Chat/chat-groups.cjs +1 -1
- package/dist/components/Chat/chat-groups.cjs.map +1 -1
- package/dist/components/Chat/chat-groups.js +2 -2
- package/dist/components/Chat/chat-groups.js.map +1 -1
- package/dist/components/Checkbox/Checkbox.cjs.map +1 -1
- package/dist/components/Checkbox/Checkbox.js.map +1 -1
- package/dist/components/ChipInput/ChipInput.cjs.map +1 -1
- package/dist/components/ChipInput/ChipInput.js.map +1 -1
- package/dist/components/ClickOutside/ClickOutside.cjs.map +1 -1
- package/dist/components/CodeBlock/CodeBlock.cjs.map +1 -1
- package/dist/components/CodeBlock/CodeBlock.js.map +1 -1
- package/dist/components/Collapsible/Collapsible.cjs.map +1 -1
- package/dist/components/Collapsible/Collapsible.js.map +1 -1
- package/dist/components/Combobox/Combobox.cjs.map +1 -1
- package/dist/components/Combobox/Combobox.js.map +1 -1
- package/dist/components/Command/Command.cjs.map +1 -1
- package/dist/components/Command/Command.js.map +1 -1
- package/dist/components/ConfirmDialog/ConfirmDialog.cjs.map +1 -1
- package/dist/components/ConfirmDialog/ConfirmDialog.js.map +1 -1
- package/dist/components/ContextMenu/ContextMenu.cjs.map +1 -1
- package/dist/components/ContextMenu/ContextMenu.js.map +1 -1
- package/dist/components/CopyButton/CopyButton.cjs.map +1 -1
- package/dist/components/CopyButton/CopyButton.js.map +1 -1
- package/dist/components/DataList/DataList.cjs.map +1 -1
- package/dist/components/DataList/DataList.js.map +1 -1
- package/dist/components/DataTable/DataTable.cjs +1 -1
- package/dist/components/DataTable/DataTable.cjs.map +1 -1
- package/dist/components/DataTable/DataTable.js +2 -2
- package/dist/components/DataTable/DataTable.js.map +1 -1
- package/dist/components/DataTable/EditableCell.cjs.map +1 -1
- package/dist/components/DataTable/EditableCell.js.map +1 -1
- package/dist/components/DatePicker/DatePicker.cjs.map +1 -1
- package/dist/components/DateRangePicker/DateRangePicker.cjs.map +1 -1
- package/dist/components/DateRangePicker/DateRangePicker.js.map +1 -1
- package/dist/components/DescriptionList/DescriptionList.cjs.map +1 -1
- package/dist/components/DescriptionList/DescriptionList.js.map +1 -1
- package/dist/components/Divider/Divider.cjs.map +1 -1
- package/dist/components/Divider/Divider.js.map +1 -1
- package/dist/components/Drawer/Drawer.cjs.map +1 -1
- package/dist/components/Drawer/Drawer.js.map +1 -1
- package/dist/components/DropdownMenu/DropdownMenu.cjs.map +1 -1
- package/dist/components/DropdownMenu/DropdownMenu.js.map +1 -1
- package/dist/components/Dropzone/Dropzone.cjs.map +1 -1
- package/dist/components/Dropzone/Dropzone.js.map +1 -1
- package/dist/components/EmptyState/EmptyState.cjs.map +1 -1
- package/dist/components/EmptyState/EmptyState.js.map +1 -1
- package/dist/components/ErrorState/ErrorState.cjs.map +1 -1
- package/dist/components/ErrorState/ErrorState.js.map +1 -1
- package/dist/components/FileUpload/FileUpload.cjs +1 -1
- package/dist/components/FileUpload/FileUpload.cjs.map +1 -1
- package/dist/components/FileUpload/FileUpload.js +1 -1
- package/dist/components/FileUpload/FileUpload.js.map +1 -1
- package/dist/components/FilterBar/FilterBar.cjs.map +1 -1
- package/dist/components/FilterBar/FilterBar.js.map +1 -1
- package/dist/components/FloatingActionButton/FloatingActionButton.cjs.map +1 -1
- package/dist/components/FloatingActionButton/FloatingActionButton.js.map +1 -1
- package/dist/components/Form/Form.cjs.map +1 -1
- package/dist/components/Form/Form.js.map +1 -1
- package/dist/components/HoverCard/HoverCard.cjs.map +1 -1
- package/dist/components/HoverCard/HoverCard.js.map +1 -1
- package/dist/components/Image/Image.cjs.map +1 -1
- package/dist/components/ImageCropper/ImageCropper.cjs +1 -1
- package/dist/components/ImageCropper/ImageCropper.cjs.map +1 -1
- package/dist/components/ImageCropper/ImageCropper.js +1 -1
- package/dist/components/ImageCropper/ImageCropper.js.map +1 -1
- package/dist/components/Input/Input.cjs.map +1 -1
- package/dist/components/Input/Input.js.map +1 -1
- package/dist/components/InstallBanner/InstallBanner.cjs.map +1 -1
- package/dist/components/InstallBanner/InstallBanner.js.map +1 -1
- package/dist/components/InstallButton/InstallButton.cjs.map +1 -1
- package/dist/components/InstallButton/InstallButton.js.map +1 -1
- package/dist/components/Kanban/Kanban.cjs.map +1 -1
- package/dist/components/Kanban/Kanban.js.map +1 -1
- package/dist/components/Label/Label.cjs.map +1 -1
- package/dist/components/Label/Label.js.map +1 -1
- package/dist/components/Lightbox/Lightbox.cjs.map +1 -1
- package/dist/components/Lightbox/Lightbox.js.map +1 -1
- package/dist/components/ListTile/ListTile.cjs.map +1 -1
- package/dist/components/ListTile/ListTile.js.map +1 -1
- package/dist/components/Markdown/Markdown.cjs +1 -1
- package/dist/components/Markdown/Markdown.cjs.map +1 -1
- package/dist/components/Markdown/Markdown.js +2 -2
- package/dist/components/Markdown/Markdown.js.map +1 -1
- package/dist/components/Masonry/Masonry.cjs.map +1 -1
- package/dist/components/Masonry/Masonry.js.map +1 -1
- package/dist/components/Menubar/Menubar.cjs.map +1 -1
- package/dist/components/Menubar/Menubar.js.map +1 -1
- package/dist/components/Modal/Modal.cjs.map +1 -1
- package/dist/components/Modal/Modal.js.map +1 -1
- package/dist/components/ModalsManager/ModalsManager.cjs.map +1 -1
- package/dist/components/ModalsManager/ModalsManager.js.map +1 -1
- package/dist/components/Money/Money.cjs.map +1 -1
- package/dist/components/Money/Money.js.map +1 -1
- package/dist/components/MultiSelect/MultiSelect.cjs.map +1 -1
- package/dist/components/MultiSelect/MultiSelect.js.map +1 -1
- package/dist/components/NProgress/NProgress.cjs.map +1 -1
- package/dist/components/Navbar/Navbar.cjs.map +1 -1
- package/dist/components/Navbar/Navbar.js.map +1 -1
- package/dist/components/NavigationMenu/NavigationMenu.cjs.map +1 -1
- package/dist/components/NavigationMenu/NavigationMenu.js.map +1 -1
- package/dist/components/NavigationRail/NavigationRail.cjs.map +1 -1
- package/dist/components/NavigationRail/NavigationRail.js.map +1 -1
- package/dist/components/NotificationCenter/NotificationCenter.cjs.map +1 -1
- package/dist/components/NotificationCenter/NotificationCenter.js.map +1 -1
- package/dist/components/NotificationCenter/use-notification-inbox.cjs.map +1 -1
- package/dist/components/OfflineIndicator/OfflineIndicator.cjs.map +1 -1
- package/dist/components/OfflineIndicator/OfflineIndicator.js.map +1 -1
- package/dist/components/Page/Page.cjs.map +1 -1
- package/dist/components/Page/Page.js.map +1 -1
- package/dist/components/Pagination/Pagination.cjs.map +1 -1
- package/dist/components/Pagination/Pagination.js.map +1 -1
- package/dist/components/PasswordInput/PasswordInput.cjs.map +1 -1
- package/dist/components/PasswordInput/PasswordInput.js.map +1 -1
- package/dist/components/PinInput/PinInput.cjs.map +1 -1
- package/dist/components/PinInput/PinInput.js.map +1 -1
- package/dist/components/Popover/Popover.cjs.map +1 -1
- package/dist/components/Popover/Popover.js.map +1 -1
- package/dist/components/Portal/Portal.cjs.map +1 -1
- package/dist/components/Progress/Progress.cjs.map +1 -1
- package/dist/components/Progress/Progress.js.map +1 -1
- package/dist/components/QRCode/QRCode.cjs.map +1 -1
- package/dist/components/QRCode/QRCode.js.map +1 -1
- package/dist/components/Radio/Radio.cjs.map +1 -1
- package/dist/components/Radio/Radio.js.map +1 -1
- package/dist/components/RangeSlider/RangeSlider.cjs.map +1 -1
- package/dist/components/RangeSlider/RangeSlider.js.map +1 -1
- package/dist/components/RatingStars/RatingStars.cjs.map +1 -1
- package/dist/components/RatingStars/RatingStars.js.map +1 -1
- package/dist/components/RefreshIndicator/RefreshIndicator.cjs.map +1 -1
- package/dist/components/RefreshIndicator/RefreshIndicator.js.map +1 -1
- package/dist/components/RelativeTime/RelativeTime.cjs.map +1 -1
- package/dist/components/RelativeTime/RelativeTime.js.map +1 -1
- package/dist/components/Resizable/Resizable.cjs.map +1 -1
- package/dist/components/Resizable/Resizable.js.map +1 -1
- package/dist/components/Scheduler/Scheduler.cjs.map +1 -1
- package/dist/components/Scheduler/Scheduler.js.map +1 -1
- package/dist/components/ScrollArea/ScrollArea.cjs.map +1 -1
- package/dist/components/SearchBar/SearchBar.cjs.map +1 -1
- package/dist/components/SearchBar/SearchBar.js.map +1 -1
- package/dist/components/SegmentedControl/SegmentedControl.cjs.map +1 -1
- package/dist/components/SegmentedControl/SegmentedControl.js.map +1 -1
- package/dist/components/Select/Select.cjs.map +1 -1
- package/dist/components/Select/Select.js.map +1 -1
- package/dist/components/Sidebar/Sidebar.cjs.map +1 -1
- package/dist/components/Sidebar/Sidebar.js.map +1 -1
- package/dist/components/SignaturePad/SignaturePad.cjs.map +1 -1
- package/dist/components/SignaturePad/SignaturePad.js.map +1 -1
- package/dist/components/Slider/Slider.cjs.map +1 -1
- package/dist/components/Slider/Slider.js.map +1 -1
- package/dist/components/Sparkline/Sparkline.cjs.map +1 -1
- package/dist/components/Sparkline/Sparkline.js.map +1 -1
- package/dist/components/Spinner/Spinner.cjs.map +1 -1
- package/dist/components/Spinner/Spinner.js.map +1 -1
- package/dist/components/Stat/Stat.cjs.map +1 -1
- package/dist/components/Stat/Stat.js.map +1 -1
- package/dist/components/Stepper/Stepper.cjs.map +1 -1
- package/dist/components/Stepper/Stepper.js.map +1 -1
- package/dist/components/StepperInput/StepperInput.cjs.map +1 -1
- package/dist/components/StepperInput/StepperInput.js.map +1 -1
- package/dist/components/Switch/Switch.cjs.map +1 -1
- package/dist/components/Switch/Switch.js.map +1 -1
- package/dist/components/SyncStatusBadge/SyncStatusBadge.cjs.map +1 -1
- package/dist/components/SyncStatusBadge/SyncStatusBadge.js.map +1 -1
- package/dist/components/Table/Table.cjs.map +1 -1
- package/dist/components/Table/Table.js.map +1 -1
- package/dist/components/Tabs/Tabs.cjs.map +1 -1
- package/dist/components/Tabs/Tabs.js.map +1 -1
- package/dist/components/Tag/Tag.cjs.map +1 -1
- package/dist/components/Tag/Tag.js.map +1 -1
- package/dist/components/Textarea/Textarea.cjs.map +1 -1
- package/dist/components/Textarea/Textarea.js.map +1 -1
- package/dist/components/TimePicker/TimePicker.cjs.map +1 -1
- package/dist/components/TimePicker/TimePicker.js.map +1 -1
- package/dist/components/Timeline/Timeline.cjs.map +1 -1
- package/dist/components/Timeline/Timeline.js.map +1 -1
- package/dist/components/Toast/ToastProvider.cjs.map +1 -1
- package/dist/components/Toast/ToastProvider.js.map +1 -1
- package/dist/components/Toggle/Toggle.cjs.map +1 -1
- package/dist/components/ToggleGroup/ToggleGroup.cjs.map +1 -1
- package/dist/components/ToggleGroup/ToggleGroup.js.map +1 -1
- package/dist/components/Tooltip/Tooltip.cjs.map +1 -1
- package/dist/components/Tooltip/Tooltip.js.map +1 -1
- package/dist/components/Tour/Tour.cjs.map +1 -1
- package/dist/components/Tour/Tour.js.map +1 -1
- package/dist/components/Transfer/Transfer.cjs.map +1 -1
- package/dist/components/Transfer/Transfer.js.map +1 -1
- package/dist/components/TreeView/TreeView.cjs +1 -1
- package/dist/components/TreeView/TreeView.cjs.map +1 -1
- package/dist/components/TreeView/TreeView.js +1 -4
- package/dist/components/TreeView/TreeView.js.map +1 -1
- package/dist/components/UpdatePrompt/UpdatePrompt.cjs.map +1 -1
- package/dist/components/UpdatePrompt/UpdatePrompt.js.map +1 -1
- package/dist/components/VirtualList/VirtualList.cjs.map +1 -1
- package/dist/components/VirtualList/VirtualList.js.map +1 -1
- package/dist/components/VirtualTable/VirtualTable.cjs.map +1 -1
- package/dist/components/VirtualTable/VirtualTable.js.map +1 -1
- package/dist/components/VisuallyHidden/VisuallyHidden.cjs.map +1 -1
- package/dist/components/Wizard/Wizard.cjs.map +1 -1
- package/dist/components/Wizard/Wizard.js.map +1 -1
- package/dist/data/data-provider-context.cjs.map +1 -1
- package/dist/data/use-resource.cjs.map +1 -1
- package/dist/editor/RichTextEditor.cjs.map +1 -1
- package/dist/editor/RichTextEditor.js.map +1 -1
- package/dist/error-boundary/use-error-handler.cjs.map +1 -1
- package/dist/feature-flags/FeatureFlagsProvider.cjs.map +1 -1
- package/dist/forms/FormField.cjs.map +1 -1
- package/dist/forms/masked-inputs.cjs.map +1 -1
- package/dist/forms/use-viacep.cjs.map +1 -1
- package/dist/forms/use-zod-form.cjs.map +1 -1
- package/dist/geo/TrajectoryMap.cjs.map +1 -1
- package/dist/geo/TrajectoryMap.js.map +1 -1
- package/dist/geo/leaflet-map.cjs.map +1 -1
- package/dist/geo/leaflet-map.js.map +1 -1
- package/dist/geo/use-position-tracker.cjs.map +1 -1
- package/dist/hooks/use-announce.cjs.map +1 -1
- package/dist/hooks/use-async.cjs.map +1 -1
- package/dist/hooks/use-before-install-prompt.cjs.map +1 -1
- package/dist/hooks/use-breakpoint.cjs.map +1 -1
- package/dist/hooks/use-click-outside.cjs.map +1 -1
- package/dist/hooks/use-client-filter.cjs.map +1 -1
- package/dist/hooks/use-clipboard.cjs.map +1 -1
- package/dist/hooks/use-counter.cjs.map +1 -1
- package/dist/hooks/use-debounce.cjs.map +1 -1
- package/dist/hooks/use-deep-memo.cjs.map +1 -1
- package/dist/hooks/use-disclosure.cjs.map +1 -1
- package/dist/hooks/use-document-visibility.cjs.map +1 -1
- package/dist/hooks/use-event-listener.cjs.map +1 -1
- package/dist/hooks/use-geolocation.cjs.map +1 -1
- package/dist/hooks/use-hover.cjs.map +1 -1
- package/dist/hooks/use-idle.cjs.map +1 -1
- package/dist/hooks/use-install-prompt.cjs +1 -1
- package/dist/hooks/use-install-prompt.cjs.map +1 -1
- package/dist/hooks/use-install-prompt.js +1 -1
- package/dist/hooks/use-install-prompt.js.map +1 -1
- package/dist/hooks/use-intersection-observer.cjs.map +1 -1
- package/dist/hooks/use-interval.cjs.map +1 -1
- package/dist/hooks/use-is-first-render.cjs.map +1 -1
- package/dist/hooks/use-keyboard-shortcut.cjs +1 -1
- package/dist/hooks/use-keyboard-shortcut.cjs.map +1 -1
- package/dist/hooks/use-keyboard-shortcut.js +1 -1
- package/dist/hooks/use-keyboard-shortcut.js.map +1 -1
- package/dist/hooks/use-list-state.cjs.map +1 -1
- package/dist/hooks/use-local-storage.cjs.map +1 -1
- package/dist/hooks/use-long-press-handlers.cjs.map +1 -1
- package/dist/hooks/use-long-press.cjs.map +1 -1
- package/dist/hooks/use-map.cjs.map +1 -1
- package/dist/hooks/use-media-devices.cjs.map +1 -1
- package/dist/hooks/use-media-permission.cjs.map +1 -1
- package/dist/hooks/use-media-query.cjs.map +1 -1
- package/dist/hooks/use-object-url.cjs.map +1 -1
- package/dist/hooks/use-online.cjs.map +1 -1
- package/dist/hooks/use-pagination.cjs.map +1 -1
- package/dist/hooks/use-previous.cjs.map +1 -1
- package/dist/hooks/use-queue.cjs.map +1 -1
- package/dist/hooks/use-resize-observer.cjs.map +1 -1
- package/dist/hooks/use-scroll-overflow.cjs.map +1 -1
- package/dist/hooks/use-service-worker-update.cjs.map +1 -1
- package/dist/hooks/use-set.cjs.map +1 -1
- package/dist/hooks/use-sortable.cjs +1 -1
- package/dist/hooks/use-sortable.cjs.map +1 -1
- package/dist/hooks/use-sortable.js +0 -2
- package/dist/hooks/use-sortable.js.map +1 -1
- package/dist/hooks/use-stable-callback.cjs.map +1 -1
- package/dist/hooks/use-storage-estimate.cjs.map +1 -1
- package/dist/hooks/use-throttle.cjs.map +1 -1
- package/dist/hooks/use-timeout.cjs.map +1 -1
- package/dist/hooks/use-toggle.cjs.map +1 -1
- package/dist/hooks/use-window-size.cjs.map +1 -1
- package/dist/http/api-client.cjs +1 -1
- package/dist/http/api-client.cjs.map +1 -1
- package/dist/http/api-client.js +7 -5
- package/dist/http/api-client.js.map +1 -1
- package/dist/http/resumable-upload.cjs +1 -1
- package/dist/http/resumable-upload.cjs.map +1 -1
- package/dist/http/resumable-upload.js +1 -1
- package/dist/http/resumable-upload.js.map +1 -1
- package/dist/http/use-poll.cjs.map +1 -1
- package/dist/i18n/I18nProvider.cjs.map +1 -1
- package/dist/icons/Icon.cjs.map +1 -1
- package/dist/icons/IconProvider.cjs.map +1 -1
- package/dist/icons/icon-context.cjs.map +1 -1
- package/dist/icons/use-icon.cjs.map +1 -1
- package/dist/imaging/use-image-processing.cjs.map +1 -1
- package/dist/oauth/GoogleSignIn.cjs.map +1 -1
- package/dist/oauth/GoogleSignIn.js.map +1 -1
- package/dist/oauth/use-oauth-callback.cjs.map +1 -1
- package/dist/offline/use-offline-sync.cjs.map +1 -1
- package/dist/push/use-push-subscription.cjs.map +1 -1
- package/dist/query/QueryProvider.cjs.map +1 -1
- package/dist/query/constants.cjs +1 -1
- package/dist/query/constants.cjs.map +1 -1
- package/dist/query/constants.js +10 -10
- package/dist/query/constants.js.map +1 -1
- package/dist/query/create-query-keys.cjs +1 -1
- package/dist/query/create-query-keys.cjs.map +1 -1
- package/dist/query/create-query-keys.js +1 -1
- package/dist/query/create-query-keys.js.map +1 -1
- package/dist/query/offline-persistence.cjs.map +1 -1
- package/dist/query/use-cursor-query.cjs.map +1 -1
- package/dist/query/use-offline-mutation.cjs.map +1 -1
- package/dist/query/use-paginated-query.cjs.map +1 -1
- package/dist/router/AppRouter.cjs +1 -1
- package/dist/router/AppRouter.cjs.map +1 -1
- package/dist/router/AppRouter.js +7 -2
- package/dist/router/AppRouter.js.map +1 -1
- package/dist/sse/use-event-stream.cjs.map +1 -1
- package/dist/store/create-store.cjs.map +1 -1
- package/dist/tabular/use-tabular-predictor.cjs.map +1 -1
- package/dist/telemetry/TelemetryProvider.cjs.map +1 -1
- package/dist/theme/ThemeProvider.cjs.map +1 -1
- package/dist/theme/color.cjs +1 -1
- package/dist/theme/color.cjs.map +1 -1
- package/dist/theme/color.js +1 -1
- package/dist/theme/color.js.map +1 -1
- package/dist/utils/cn.cjs +1 -1
- package/dist/utils/cn.cjs.map +1 -1
- package/dist/utils/cn.js +1 -1
- package/dist/utils/cn.js.map +1 -1
- package/dist/utils/objects.cjs +1 -1
- package/dist/utils/objects.cjs.map +1 -1
- package/dist/utils/objects.js +1 -1
- package/dist/utils/objects.js.map +1 -1
- package/dist/utils/xlsx.cjs +1 -1
- package/dist/utils/xlsx.cjs.map +1 -1
- package/dist/utils/xlsx.js +3 -3
- package/dist/utils/xlsx.js.map +1 -1
- package/dist/vision/core/canvas.cjs +1 -1
- package/dist/vision/core/canvas.cjs.map +1 -1
- package/dist/vision/core/canvas.js +4 -4
- package/dist/vision/core/canvas.js.map +1 -1
- package/dist/vision/core/exceptions.cjs +1 -1
- package/dist/vision/core/exceptions.cjs.map +1 -1
- package/dist/vision/core/exceptions.js +2 -2
- package/dist/vision/core/exceptions.js.map +1 -1
- package/dist/vision/core/metadata.cjs +2 -2
- package/dist/vision/core/metadata.cjs.map +1 -1
- package/dist/vision/core/metadata.js +11 -8
- package/dist/vision/core/metadata.js.map +1 -1
- package/dist/vision/core/session.cjs +1 -1
- package/dist/vision/core/session.cjs.map +1 -1
- package/dist/vision/core/session.js +8 -5
- package/dist/vision/core/session.js.map +1 -1
- package/dist/vision/fusion.cjs +2 -0
- package/dist/vision/fusion.cjs.map +1 -0
- package/dist/vision/fusion.js +42 -0
- package/dist/vision/fusion.js.map +1 -0
- package/dist/vision/index.cjs +1 -1
- package/dist/vision/index.cjs.map +1 -1
- package/dist/vision/index.js +23 -20
- package/dist/vision/index.js.map +1 -1
- package/dist/vision/io/image.cjs +1 -1
- package/dist/vision/io/image.cjs.map +1 -1
- package/dist/vision/io/image.js +4 -2
- package/dist/vision/io/image.js.map +1 -1
- package/dist/vision/labels.cjs +1 -1
- package/dist/vision/labels.cjs.map +1 -1
- package/dist/vision/labels.js +7 -4
- package/dist/vision/labels.js.map +1 -1
- package/dist/vision/postprocess/detection.cjs +1 -1
- package/dist/vision/postprocess/detection.cjs.map +1 -1
- package/dist/vision/postprocess/detection.js +3 -10
- package/dist/vision/postprocess/detection.js.map +1 -1
- package/dist/vision/postprocess/segmentation.cjs +1 -1
- package/dist/vision/postprocess/segmentation.cjs.map +1 -1
- package/dist/vision/postprocess/segmentation.js +11 -15
- package/dist/vision/postprocess/segmentation.js.map +1 -1
- package/dist/vision/preprocess/image.cjs +1 -1
- package/dist/vision/preprocess/image.cjs.map +1 -1
- package/dist/vision/preprocess/image.js +4 -2
- package/dist/vision/preprocess/image.js.map +1 -1
- package/dist/vision/preprocess/pipeline.cjs +2 -0
- package/dist/vision/preprocess/pipeline.cjs.map +1 -0
- package/dist/vision/preprocess/pipeline.js +61 -0
- package/dist/vision/preprocess/pipeline.js.map +1 -0
- package/dist/vision/results.cjs +1 -1
- package/dist/vision/results.cjs.map +1 -1
- package/dist/vision/results.js +23 -2
- package/dist/vision/results.js.map +1 -1
- package/dist/vision/tasks/base.cjs +1 -1
- package/dist/vision/tasks/base.cjs.map +1 -1
- package/dist/vision/tasks/base.js +11 -2
- package/dist/vision/tasks/base.js.map +1 -1
- package/dist/vision/tasks/classifier.cjs +1 -1
- package/dist/vision/tasks/classifier.cjs.map +1 -1
- package/dist/vision/tasks/classifier.js +2 -2
- package/dist/vision/tasks/classifier.js.map +1 -1
- package/dist/vision/tasks/detectClassify.cjs +2 -0
- package/dist/vision/tasks/detectClassify.cjs.map +1 -0
- package/dist/vision/tasks/detectClassify.js +222 -0
- package/dist/vision/tasks/detectClassify.js.map +1 -0
- package/dist/vision/tasks/detector.cjs +1 -1
- package/dist/vision/tasks/detector.cjs.map +1 -1
- package/dist/vision/tasks/detector.js +54 -29
- package/dist/vision/tasks/detector.js.map +1 -1
- package/dist/vision/tasks/segmenter.cjs +1 -1
- package/dist/vision/tasks/segmenter.cjs.map +1 -1
- package/dist/vision/tasks/segmenter.js +53 -28
- package/dist/vision/tasks/segmenter.js.map +1 -1
- package/dist/vision/types.cjs.map +1 -1
- package/dist/vision/types.js.map +1 -1
- package/dist/vision/use-camera-stream.cjs.map +1 -1
- package/dist/vision/use-live-luminance.cjs.map +1 -1
- package/dist/vision.cjs +1 -1
- package/dist/vision.d.ts +1517 -962
- package/dist/vision.js +25 -22
- package/dist/vite/create-vite-config.cjs +1 -1
- package/dist/vite/create-vite-config.cjs.map +1 -1
- package/dist/vite/create-vite-config.js +3 -3
- package/dist/vite/create-vite-config.js.map +1 -1
- package/dist/vite/tempest-icons.cjs.map +1 -1
- package/dist/vite/tempest-pwa-dev-sw.cjs.map +1 -1
- package/dist/vite/tempest-pwa-icons.cjs.map +1 -1
- package/dist/ws/use-web-socket.cjs +1 -1
- package/dist/ws/use-web-socket.cjs.map +1 -1
- package/dist/ws/use-web-socket.js +5 -3
- package/dist/ws/use-web-socket.js.map +1 -1
- package/package.json +2 -2
- package/template/package.json +1 -1
package/README.md
CHANGED
|
@@ -279,7 +279,7 @@ Every module is re-exported from the package root — `import { Button, useDebou
|
|
|
279
279
|
| `push` | `WebPushClient`, `WebPushUnsupportedError`, `WebPushPermissionDeniedError`, `usePushSubscription`, `urlBase64ToUint8Array`, `isPushSupported` |
|
|
280
280
|
| `sw` _(also subpath `tempest-react-sdk/sw`)_ | `registerServiceWorker` (opt-in `autoUpdate` poll + reload, no `vite-plugin-pwa`), `skipWaiting`, `unregisterAllServiceWorkers`, `installPushHandler`, `installNotificationClickHandler`, `installSkipWaitingListener`, `installPrecache` (app-shell offline + Navigation Preload), `installRuntimeCache` (per-route caching, incl. `rangeRequests`), `createPartialResponse` (206 range slicing), `installBackgroundSync` (offline mutation queue + `periodicsync`), `registerPeriodicSync` (main-thread periodic sync), `inspectCaches`/`clearCaches` (cache observability) — the React-free `tempest-react-sdk/sw` subpath is ideal for bundling into your own `sw.ts` |
|
|
281
281
|
| `editor` _(subpath `tempest-react-sdk/editor`, opt. peers `@tiptap/react`+`@tiptap/starter-kit`)_ | `RichTextEditor` — themed tiptap v3 rich-text editor with toolbar (tiptap externalized; install it only if you use the editor) |
|
|
282
|
-
| `vision` _(subpath `tempest-react-sdk/vision`, opt. peer `onnxruntime-web`)_ | `Classifier`, `Detector`, `Segmenter` + results/labels/preprocess — on-device ONNX vision inference, vendored from `@mauriciobenjamin700/ort-vision-sdk-web` (onnxruntime-web externalized; install it only if you use vision). Plus browser capture primitives: `useCameraStream` (rear-camera `getUserMedia` + classified errors + `retry()`), `computeImageLuminance`/`isLuminanceAcceptable`/`LowLuminanceError`/`LUMINANCE_SAMPLE_MAX_EDGE` (BT.709 brightness), `useLiveLuminance` (live rAF sampling)
|
|
282
|
+
| `vision` _(subpath `tempest-react-sdk/vision`, opt. peer `onnxruntime-web`)_ | `Classifier`, `Detector`, `Segmenter`, `DetectClassify` (fused detect→classify pipeline) + `warmup()`, `raiseOnEmpty`/`NoDetectionsError`, `LetterboxPipeline`, `readFusionSpec` and results/labels/preprocess — on-device ONNX vision inference, vendored from `@mauriciobenjamin700/ort-vision-sdk-web` (onnxruntime-web externalized; install it only if you use vision). Plus browser capture primitives: `useCameraStream` (rear-camera `getUserMedia` + classified errors + `retry()`), `computeImageLuminance`/`isLuminanceAcceptable`/`LowLuminanceError`/`LUMINANCE_SAMPLE_MAX_EDGE` (BT.709 brightness), `useLiveLuminance` (live rAF sampling) |
|
|
283
283
|
| `tabular` _(subpath `tempest-react-sdk/tabular`, opt. peer `onnxruntime-web`)_ | `TabularPredictor`, `useTabularPredictor` — scikit-learn models exported by `tempest-fastapi-sdk`'s `export_sklearn_to_onnx`, running in the browser with no inference server. Offline by design: `fetchModelBytes`/`isModelCached`/`cacheModelBytes`/`clearModelCache` (Cache Storage, cache-first) plus `configureOrtAssets`/`ortAssetUrls`/`ORT_WASM_ASSETS` for the runtime's WebAssembly binaries, which ONNX Runtime Web fetches at session creation. Import the default `onnxruntime-web` entry — the WebGPU build has no `ai.onnx.ml` kernels |
|
|
284
284
|
| `imaging` _(subpath `tempest-react-sdk/imaging`, no deps)_ | `resizeImage`, `cropImage`, `rotateImage`, `flipImage`, `compressToTarget` (binary-searches quality into a byte budget), `createThumbnails` (several sizes from one decode), `decodeImage`/`readImageInfo`, `encodeImage`, `supportsImageType`/`bestSupportedType`, plus `useImagePreview`/`useImageProcessing`. Canvas only — decodes with EXIF orientation applied (phone photos arrive upright), reports the format actually produced (asking for AVIF where there is no encoder silently returns PNG), and re-encoding drops EXIF, which is the privacy step as much as the bandwidth one |
|
|
285
285
|
| `audio` | `createAudioPlayer`, `playAudio`, `stopAudio`, `useAudio` |
|
|
@@ -1 +1 @@
|
|
|
1
|
-
var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},s=(n,r,
|
|
1
|
+
var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},s=(n,r,s)=>(s=n==null?{}:e(i(n)),o(r||!n||!n.__esModule||!a.call(n,`default`)?t(s,`default`,{value:n,enumerable:!0}):s,n));exports.__toESM=s;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"access-control-context.cjs","names":[],"sources":["../../src/access/access-control-context.tsx"],"sourcesContent":["import { createContext, useContext } from \"react\";\nimport type { ReactNode } from \"react\";\nimport type { AccessControl } from \"./types\";\n\nconst AccessControlContext = createContext<AccessControl | null>(null);\n\nexport interface AccessControlProviderProps {\n /** The access-control strategy made available to descendants. */\n control: AccessControl;\n children: ReactNode;\n}\n\n/**\n * Provide an {@link AccessControl} strategy to the React tree. Components such\n * as `<Can>` and the `useCan` hook read it from context.\n *\n * @example\n * ```tsx\n * <AccessControlProvider control={createRoleAccessControl({ role: \"admin\", roles: { admin: [\"*\"] } })}>\n * <App />\n * </AccessControlProvider>\n * ```\n */\nexport function AccessControlProvider({ control, children }: AccessControlProviderProps) {\n return (\n <AccessControlContext.Provider value={control}>{children}</AccessControlContext.Provider>\n );\n}\n\n/**\n * Read the current {@link AccessControl} from context.\n *\n * Returns `null` when no {@link AccessControlProvider} is present. Callers MUST\n * treat the absence of a provider as **\"allow all\"** — i.e. when this returns\n * `null`, access checks should default to permitted. This keeps the SDK\n * opt-in: dropping in a provider adds enforcement, removing it disables it.\n */\nexport function useAccessControl(): AccessControl | null {\n return useContext(AccessControlContext);\n}\n"],"mappings":"sDAIA,IAAM,GAAA,
|
|
1
|
+
{"version":3,"file":"access-control-context.cjs","names":[],"sources":["../../src/access/access-control-context.tsx"],"sourcesContent":["import { createContext, useContext } from \"react\";\nimport type { ReactNode } from \"react\";\nimport type { AccessControl } from \"./types\";\n\nconst AccessControlContext = createContext<AccessControl | null>(null);\n\nexport interface AccessControlProviderProps {\n /** The access-control strategy made available to descendants. */\n control: AccessControl;\n children: ReactNode;\n}\n\n/**\n * Provide an {@link AccessControl} strategy to the React tree. Components such\n * as `<Can>` and the `useCan` hook read it from context.\n *\n * @example\n * ```tsx\n * <AccessControlProvider control={createRoleAccessControl({ role: \"admin\", roles: { admin: [\"*\"] } })}>\n * <App />\n * </AccessControlProvider>\n * ```\n */\nexport function AccessControlProvider({ control, children }: AccessControlProviderProps) {\n return (\n <AccessControlContext.Provider value={control}>{children}</AccessControlContext.Provider>\n );\n}\n\n/**\n * Read the current {@link AccessControl} from context.\n *\n * Returns `null` when no {@link AccessControlProvider} is present. Callers MUST\n * treat the absence of a provider as **\"allow all\"** — i.e. when this returns\n * `null`, access checks should default to permitted. This keeps the SDK\n * opt-in: dropping in a provider adds enforcement, removing it disables it.\n */\nexport function useAccessControl(): AccessControl | null {\n return useContext(AccessControlContext);\n}\n"],"mappings":"sDAIA,IAAM,GAAA,EAAuB,EAAA,cAAA,CAAoC,IAAI,EAmBrE,SAAgB,EAAsB,CAAE,UAAS,YAAwC,CACrF,OACI,EAAA,EAAA,IAAA,CAAC,EAAqB,SAAtB,CAA+B,MAAO,EAAU,UAAwC,CAAA,CAEhG,CAUA,SAAgB,GAAyC,CACrD,OAAA,EAAO,EAAA,WAAA,CAAW,CAAoB,CAC1C"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-can.cjs","names":[],"sources":["../../src/access/use-can.ts"],"sourcesContent":["import { useEffect, useState } from \"react\";\nimport { useAccessControl } from \"./access-control-context\";\nimport type { CanParams, CanResult } from \"./types\";\n\nexport interface UseCanResult {\n /** Whether the action is permitted. Defaults to `true` while loading is not the concern of the caller. */\n allowed: boolean;\n /** `true` while an async `can` check is pending. */\n isLoading: boolean;\n /** Optional explanation, typically present when `allowed` is `false`. */\n reason?: string;\n}\n\nfunction normalize(result: boolean | CanResult): CanResult {\n return typeof result === \"boolean\" ? { can: result } : result;\n}\n\n/**\n * Resolve an access check against the {@link AccessControl} in context.\n *\n * Handles sync booleans, sync {@link CanResult}, and promises of either. When\n * no provider is present, access is allowed (see `useAccessControl`). The check\n * re-runs whenever the params change.\n *\n * @param params - The action/resource being checked.\n */\nexport function useCan(params: CanParams): UseCanResult {\n const control = useAccessControl();\n const paramsKey = JSON.stringify(params);\n\n const [state, setState] = useState<UseCanResult>(() =>\n control ? { allowed: false, isLoading: true } : { allowed: true, isLoading: false },\n );\n\n useEffect(() => {\n if (!control) {\n setState({ allowed: true, isLoading: false });\n return;\n }\n\n let cancelled = false;\n setState((prev) => ({ ...prev, isLoading: true }));\n\n Promise.resolve(control.can(params))\n .then((raw) => {\n if (cancelled) return;\n const result = normalize(raw);\n setState({ allowed: result.can, isLoading: false, reason: result.reason });\n })\n .catch((error: unknown) => {\n if (cancelled) return;\n setState({\n allowed: false,\n isLoading: false,\n reason: error instanceof Error ? error.message : \"access check failed\",\n });\n });\n\n return () => {\n cancelled = true;\n };\n // `paramsKey` is the stable stringified form of `params`.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [control, paramsKey]);\n\n return state;\n}\n"],"mappings":"uEAaA,SAAS,EAAU,EAAwC,CACvD,OAAO,OAAO,GAAW,UAAY,CAAE,IAAK,CAAO,EAAI,CAC3D,CAWA,SAAgB,EAAO,EAAiC,CACpD,IAAM,EAAU,EAAA,iBAAiB,EAC3B,EAAY,KAAK,UAAU,CAAM,EAEjC,CAAC,EAAO,IAAA,
|
|
1
|
+
{"version":3,"file":"use-can.cjs","names":[],"sources":["../../src/access/use-can.ts"],"sourcesContent":["import { useEffect, useState } from \"react\";\nimport { useAccessControl } from \"./access-control-context\";\nimport type { CanParams, CanResult } from \"./types\";\n\nexport interface UseCanResult {\n /** Whether the action is permitted. Defaults to `true` while loading is not the concern of the caller. */\n allowed: boolean;\n /** `true` while an async `can` check is pending. */\n isLoading: boolean;\n /** Optional explanation, typically present when `allowed` is `false`. */\n reason?: string;\n}\n\nfunction normalize(result: boolean | CanResult): CanResult {\n return typeof result === \"boolean\" ? { can: result } : result;\n}\n\n/**\n * Resolve an access check against the {@link AccessControl} in context.\n *\n * Handles sync booleans, sync {@link CanResult}, and promises of either. When\n * no provider is present, access is allowed (see `useAccessControl`). The check\n * re-runs whenever the params change.\n *\n * @param params - The action/resource being checked.\n */\nexport function useCan(params: CanParams): UseCanResult {\n const control = useAccessControl();\n const paramsKey = JSON.stringify(params);\n\n const [state, setState] = useState<UseCanResult>(() =>\n control ? { allowed: false, isLoading: true } : { allowed: true, isLoading: false },\n );\n\n useEffect(() => {\n if (!control) {\n setState({ allowed: true, isLoading: false });\n return;\n }\n\n let cancelled = false;\n setState((prev) => ({ ...prev, isLoading: true }));\n\n Promise.resolve(control.can(params))\n .then((raw) => {\n if (cancelled) return;\n const result = normalize(raw);\n setState({ allowed: result.can, isLoading: false, reason: result.reason });\n })\n .catch((error: unknown) => {\n if (cancelled) return;\n setState({\n allowed: false,\n isLoading: false,\n reason: error instanceof Error ? error.message : \"access check failed\",\n });\n });\n\n return () => {\n cancelled = true;\n };\n // `paramsKey` is the stable stringified form of `params`.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [control, paramsKey]);\n\n return state;\n}\n"],"mappings":"uEAaA,SAAS,EAAU,EAAwC,CACvD,OAAO,OAAO,GAAW,UAAY,CAAE,IAAK,CAAO,EAAI,CAC3D,CAWA,SAAgB,EAAO,EAAiC,CACpD,IAAM,EAAU,EAAA,iBAAiB,EAC3B,EAAY,KAAK,UAAU,CAAM,EAEjC,CAAC,EAAO,IAAA,EAAY,EAAA,SAAA,KACtB,EAAU,CAAE,QAAS,GAAO,UAAW,EAAK,EAAI,CAAE,QAAS,GAAM,UAAW,EAAM,CACtF,EAiCA,OA/BA,EAAA,EAAA,UAAA,KAAgB,CACZ,GAAI,CAAC,EAAS,CACV,EAAS,CAAE,QAAS,GAAM,UAAW,EAAM,CAAC,EAC5C,MACJ,CAEA,IAAI,EAAY,GAkBhB,OAjBA,EAAU,IAAU,CAAE,GAAG,EAAM,UAAW,EAAK,EAAE,EAEjD,QAAQ,QAAQ,EAAQ,IAAI,CAAM,CAAC,CAAC,CAC/B,KAAM,GAAQ,CACX,GAAI,EAAW,OACf,IAAM,EAAS,EAAU,CAAG,EAC5B,EAAS,CAAE,QAAS,EAAO,IAAK,UAAW,GAAO,OAAQ,EAAO,MAAO,CAAC,CAC7E,CAAC,CAAC,CACD,MAAO,GAAmB,CACnB,GACJ,EAAS,CACL,QAAS,GACT,UAAW,GACX,OAAQ,aAAiB,MAAQ,EAAM,QAAU,qBACrD,CAAC,CACL,CAAC,MAEQ,CACT,EAAY,EAChB,CAGJ,EAAG,CAAC,EAAS,CAAS,CAAC,EAEhB,CACX"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AppProviders.cjs","names":[],"sources":["../../src/app/AppProviders.tsx"],"sourcesContent":["import type { ReactNode } from \"react\";\nimport { QueryProvider } from \"../query/QueryProvider\";\nimport type { QueryProviderProps } from \"../query/QueryProvider\";\nimport { ThemeProvider } from \"../theme/ThemeProvider\";\nimport type { ThemeProviderProps } from \"../theme/ThemeProvider\";\nimport { I18nProvider } from \"../i18n/I18nProvider\";\nimport type { I18nProviderProps } from \"../i18n/I18nProvider\";\nimport { ErrorBoundary } from \"../error-boundary/ErrorBoundary\";\nimport type { ErrorBoundaryProps } from \"../error-boundary/ErrorBoundary\";\n\nexport interface AppProvidersProps {\n children: ReactNode;\n /**\n * TanStack Query config. Enabled by default with SDK defaults; pass `false`\n * to opt out (e.g. the app already mounts its own `QueryProvider`).\n */\n query?: Omit<QueryProviderProps, \"children\"> | false;\n /**\n * Theme config. Enabled by default; pass `false` to opt out.\n */\n theme?: Omit<ThemeProviderProps, \"children\"> | false;\n /**\n * i18n config. Opt-in: provide `locale` + `messages` to mount the provider.\n */\n i18n?: Omit<I18nProviderProps, \"children\">;\n /**\n * Top-level error boundary. Opt-in: provide a `fallback` to wrap the tree.\n */\n errorBoundary?: Omit<ErrorBoundaryProps, \"children\">;\n}\n\n/**\n * Compose the Tempest app-wide providers in one place: error boundary →\n * TanStack Query → theme → i18n. Query and theme are on by default; i18n and\n * the error boundary mount only when configured. Each provider can be disabled\n * or tuned independently, so an app's `main.tsx` stays a single declarative\n * block instead of a hand-nested provider pyramid.\n *\n * @example\n * <AppProviders\n * errorBoundary={{ fallback: <Crash /> }}\n * i18n={{ locale: \"pt-BR\", messages }}\n * >\n * <AppRouter routes={routes} />\n * </AppProviders>\n */\nexport function AppProviders({ children, query, theme, i18n, errorBoundary }: AppProvidersProps) {\n let tree: ReactNode = children;\n\n if (i18n) {\n tree = <I18nProvider {...i18n}>{tree}</I18nProvider>;\n }\n\n if (theme !== false) {\n tree = <ThemeProvider {...(theme || {})}>{tree}</ThemeProvider>;\n }\n\n if (query !== false) {\n tree = <QueryProvider {...(query || {})}>{tree}</QueryProvider>;\n }\n\n if (errorBoundary) {\n tree = <ErrorBoundary {...errorBoundary}>{tree}</ErrorBoundary>;\n }\n\n return <>{tree}</>;\n}\n"],"mappings":"gNA8CA,SAAgB,EAAa,CAAE,WAAU,QAAO,QAAO,OAAM,iBAAoC,CAC7F,IAAI,EAAkB,EAkBtB,OAhBI,IACA,GAAO,EAAA,EAAA,IAAA,CAAC,EAAA,aAAD,CAAc,GAAI,
|
|
1
|
+
{"version":3,"file":"AppProviders.cjs","names":[],"sources":["../../src/app/AppProviders.tsx"],"sourcesContent":["import type { ReactNode } from \"react\";\nimport { QueryProvider } from \"../query/QueryProvider\";\nimport type { QueryProviderProps } from \"../query/QueryProvider\";\nimport { ThemeProvider } from \"../theme/ThemeProvider\";\nimport type { ThemeProviderProps } from \"../theme/ThemeProvider\";\nimport { I18nProvider } from \"../i18n/I18nProvider\";\nimport type { I18nProviderProps } from \"../i18n/I18nProvider\";\nimport { ErrorBoundary } from \"../error-boundary/ErrorBoundary\";\nimport type { ErrorBoundaryProps } from \"../error-boundary/ErrorBoundary\";\n\nexport interface AppProvidersProps {\n children: ReactNode;\n /**\n * TanStack Query config. Enabled by default with SDK defaults; pass `false`\n * to opt out (e.g. the app already mounts its own `QueryProvider`).\n */\n query?: Omit<QueryProviderProps, \"children\"> | false;\n /**\n * Theme config. Enabled by default; pass `false` to opt out.\n */\n theme?: Omit<ThemeProviderProps, \"children\"> | false;\n /**\n * i18n config. Opt-in: provide `locale` + `messages` to mount the provider.\n */\n i18n?: Omit<I18nProviderProps, \"children\">;\n /**\n * Top-level error boundary. Opt-in: provide a `fallback` to wrap the tree.\n */\n errorBoundary?: Omit<ErrorBoundaryProps, \"children\">;\n}\n\n/**\n * Compose the Tempest app-wide providers in one place: error boundary →\n * TanStack Query → theme → i18n. Query and theme are on by default; i18n and\n * the error boundary mount only when configured. Each provider can be disabled\n * or tuned independently, so an app's `main.tsx` stays a single declarative\n * block instead of a hand-nested provider pyramid.\n *\n * @example\n * <AppProviders\n * errorBoundary={{ fallback: <Crash /> }}\n * i18n={{ locale: \"pt-BR\", messages }}\n * >\n * <AppRouter routes={routes} />\n * </AppProviders>\n */\nexport function AppProviders({ children, query, theme, i18n, errorBoundary }: AppProvidersProps) {\n let tree: ReactNode = children;\n\n if (i18n) {\n tree = <I18nProvider {...i18n}>{tree}</I18nProvider>;\n }\n\n if (theme !== false) {\n tree = <ThemeProvider {...(theme || {})}>{tree}</ThemeProvider>;\n }\n\n if (query !== false) {\n tree = <QueryProvider {...(query || {})}>{tree}</QueryProvider>;\n }\n\n if (errorBoundary) {\n tree = <ErrorBoundary {...errorBoundary}>{tree}</ErrorBoundary>;\n }\n\n return <>{tree}</>;\n}\n"],"mappings":"gNA8CA,SAAgB,EAAa,CAAE,WAAU,QAAO,QAAO,OAAM,iBAAoC,CAC7F,IAAI,EAAkB,EAkBtB,OAhBI,IACA,GAAO,EAAA,EAAA,IAAA,CAAC,EAAA,aAAD,CAAc,GAAI,EAAO,SAAA,CAAmB,CAAA,GAGnD,IAAU,KACV,GAAO,EAAA,EAAA,IAAA,CAAC,EAAA,cAAD,CAAe,GAAK,GAAS,CAAC,EAAK,SAAA,CAAoB,CAAA,GAG9D,IAAU,KACV,GAAO,EAAA,EAAA,IAAA,CAAC,EAAA,cAAD,CAAe,GAAK,GAAS,CAAC,EAAK,SAAA,CAAoB,CAAA,GAG9D,IACA,GAAO,EAAA,EAAA,IAAA,CAAC,EAAA,cAAD,CAAe,GAAI,EAAgB,SAAA,CAAoB,CAAA,IAG3D,EAAA,EAAA,IAAA,CAAA,EAAA,SAAA,CAAA,SAAG,CAAO,CAAA,CACrB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AppProviders.js","names":[],"sources":["../../src/app/AppProviders.tsx"],"sourcesContent":["import type { ReactNode } from \"react\";\nimport { QueryProvider } from \"../query/QueryProvider\";\nimport type { QueryProviderProps } from \"../query/QueryProvider\";\nimport { ThemeProvider } from \"../theme/ThemeProvider\";\nimport type { ThemeProviderProps } from \"../theme/ThemeProvider\";\nimport { I18nProvider } from \"../i18n/I18nProvider\";\nimport type { I18nProviderProps } from \"../i18n/I18nProvider\";\nimport { ErrorBoundary } from \"../error-boundary/ErrorBoundary\";\nimport type { ErrorBoundaryProps } from \"../error-boundary/ErrorBoundary\";\n\nexport interface AppProvidersProps {\n children: ReactNode;\n /**\n * TanStack Query config. Enabled by default with SDK defaults; pass `false`\n * to opt out (e.g. the app already mounts its own `QueryProvider`).\n */\n query?: Omit<QueryProviderProps, \"children\"> | false;\n /**\n * Theme config. Enabled by default; pass `false` to opt out.\n */\n theme?: Omit<ThemeProviderProps, \"children\"> | false;\n /**\n * i18n config. Opt-in: provide `locale` + `messages` to mount the provider.\n */\n i18n?: Omit<I18nProviderProps, \"children\">;\n /**\n * Top-level error boundary. Opt-in: provide a `fallback` to wrap the tree.\n */\n errorBoundary?: Omit<ErrorBoundaryProps, \"children\">;\n}\n\n/**\n * Compose the Tempest app-wide providers in one place: error boundary →\n * TanStack Query → theme → i18n. Query and theme are on by default; i18n and\n * the error boundary mount only when configured. Each provider can be disabled\n * or tuned independently, so an app's `main.tsx` stays a single declarative\n * block instead of a hand-nested provider pyramid.\n *\n * @example\n * <AppProviders\n * errorBoundary={{ fallback: <Crash /> }}\n * i18n={{ locale: \"pt-BR\", messages }}\n * >\n * <AppRouter routes={routes} />\n * </AppProviders>\n */\nexport function AppProviders({ children, query, theme, i18n, errorBoundary }: AppProvidersProps) {\n let tree: ReactNode = children;\n\n if (i18n) {\n tree = <I18nProvider {...i18n}>{tree}</I18nProvider>;\n }\n\n if (theme !== false) {\n tree = <ThemeProvider {...(theme || {})}>{tree}</ThemeProvider>;\n }\n\n if (query !== false) {\n tree = <QueryProvider {...(query || {})}>{tree}</QueryProvider>;\n }\n\n if (errorBoundary) {\n tree = <ErrorBoundary {...errorBoundary}>{tree}</ErrorBoundary>;\n }\n\n return <>{tree}</>;\n}\n"],"mappings":";;;;;;AA8CA,SAAgB,EAAa,EAAE,aAAU,UAAO,UAAO,SAAM,oBAAoC;CAC7F,IAAI,IAAkB;CAkBtB,OAhBI,MACA,IAAO,kBAAC,GAAD;EAAc,GAAI;
|
|
1
|
+
{"version":3,"file":"AppProviders.js","names":[],"sources":["../../src/app/AppProviders.tsx"],"sourcesContent":["import type { ReactNode } from \"react\";\nimport { QueryProvider } from \"../query/QueryProvider\";\nimport type { QueryProviderProps } from \"../query/QueryProvider\";\nimport { ThemeProvider } from \"../theme/ThemeProvider\";\nimport type { ThemeProviderProps } from \"../theme/ThemeProvider\";\nimport { I18nProvider } from \"../i18n/I18nProvider\";\nimport type { I18nProviderProps } from \"../i18n/I18nProvider\";\nimport { ErrorBoundary } from \"../error-boundary/ErrorBoundary\";\nimport type { ErrorBoundaryProps } from \"../error-boundary/ErrorBoundary\";\n\nexport interface AppProvidersProps {\n children: ReactNode;\n /**\n * TanStack Query config. Enabled by default with SDK defaults; pass `false`\n * to opt out (e.g. the app already mounts its own `QueryProvider`).\n */\n query?: Omit<QueryProviderProps, \"children\"> | false;\n /**\n * Theme config. Enabled by default; pass `false` to opt out.\n */\n theme?: Omit<ThemeProviderProps, \"children\"> | false;\n /**\n * i18n config. Opt-in: provide `locale` + `messages` to mount the provider.\n */\n i18n?: Omit<I18nProviderProps, \"children\">;\n /**\n * Top-level error boundary. Opt-in: provide a `fallback` to wrap the tree.\n */\n errorBoundary?: Omit<ErrorBoundaryProps, \"children\">;\n}\n\n/**\n * Compose the Tempest app-wide providers in one place: error boundary →\n * TanStack Query → theme → i18n. Query and theme are on by default; i18n and\n * the error boundary mount only when configured. Each provider can be disabled\n * or tuned independently, so an app's `main.tsx` stays a single declarative\n * block instead of a hand-nested provider pyramid.\n *\n * @example\n * <AppProviders\n * errorBoundary={{ fallback: <Crash /> }}\n * i18n={{ locale: \"pt-BR\", messages }}\n * >\n * <AppRouter routes={routes} />\n * </AppProviders>\n */\nexport function AppProviders({ children, query, theme, i18n, errorBoundary }: AppProvidersProps) {\n let tree: ReactNode = children;\n\n if (i18n) {\n tree = <I18nProvider {...i18n}>{tree}</I18nProvider>;\n }\n\n if (theme !== false) {\n tree = <ThemeProvider {...(theme || {})}>{tree}</ThemeProvider>;\n }\n\n if (query !== false) {\n tree = <QueryProvider {...(query || {})}>{tree}</QueryProvider>;\n }\n\n if (errorBoundary) {\n tree = <ErrorBoundary {...errorBoundary}>{tree}</ErrorBoundary>;\n }\n\n return <>{tree}</>;\n}\n"],"mappings":";;;;;;AA8CA,SAAgB,EAAa,EAAE,aAAU,UAAO,UAAO,SAAM,oBAAoC;CAC7F,IAAI,IAAkB;CAkBtB,OAhBI,MACA,IAAO,kBAAC,GAAD;EAAc,GAAI;EAAO,UAAA;CAAmB,CAAA,IAGnD,MAAU,OACV,IAAO,kBAAC,GAAD;EAAe,GAAK,KAAS,CAAC;EAAK,UAAA;CAAoB,CAAA,IAG9D,MAAU,OACV,IAAO,kBAAC,GAAD;EAAe,GAAK,KAAS,CAAC;EAAK,UAAA;CAAoB,CAAA,IAG9D,MACA,IAAO,kBAAC,GAAD;EAAe,GAAI;EAAgB,UAAA;CAAoB,CAAA,IAG3D,kBAAA,GAAA,EAAA,UAAG,EAAO,CAAA;AACrB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-audio-recorder.cjs","names":[],"sources":["../../src/audio/use-audio-recorder.ts"],"sourcesContent":["/**\n * @tempest-limits hook-lines — the hook is the recorder's lifecycle seen from React:\n * permission, the recorder handle, the clock, the level meter and the auto-stop\n * timer, all of which have to be torn down together on unmount. A second hook would\n * own half the teardown.\n */\nimport { useCallback, useEffect, useRef, useState } from \"react\";\n\nimport { useStableCallback } from \"@/hooks/use-stable-callback\";\n\nimport {\n createAudioRecorder,\n type AudioRecorderHandle,\n type AudioRecorderOptions,\n type AudioRecorderStatus,\n type AudioRecording,\n} from \"./audio-recorder\";\nimport { createLevelMeter, type LevelMeter } from \"./level-meter\";\n\n/** Options for {@link useAudioRecorder}. */\nexport interface UseAudioRecorderOptions extends AudioRecorderOptions {\n /**\n * Stop automatically after this many milliseconds.\n *\n * Worth setting on anything user-facing: a recording left running by accident is\n * a memory buffer that grows until the tab dies.\n */\n maxDurationMs?: number;\n /** Called once the recording is assembled, whether by `stop()` or `maxDurationMs`. */\n onRecorded?: (recording: AudioRecording) => void;\n /** How often the clock and the level are published, in ms. Default 100. */\n tickMs?: number;\n /** Skip the level meter — it costs an `AudioContext`. Default `false`. */\n disableLevelMeter?: boolean;\n}\n\n/** Value returned by {@link useAudioRecorder}. */\nexport interface UseAudioRecorderResult {\n status: AudioRecorderStatus;\n /** Recorded length so far, excluding paused time. */\n durationMs: number;\n /** Loudness 0–1, or `0` when the meter is off or nothing is recording. */\n level: number;\n /** The finished recording, or `null` before the first `stop()`. */\n recording: AudioRecording | null;\n /** Recorder-level error (encoder failure, device pulled mid-recording). */\n error: unknown;\n /** `false` when there is no stream yet, or the browser cannot record. */\n ready: boolean;\n start: () => void;\n pause: () => void;\n resume: () => void;\n /** Stop and resolve with the recording. Also published on `recording`. */\n stop: () => Promise<AudioRecording | null>;\n /** Stop and throw the audio away. */\n cancel: () => void;\n}\n\n/**\n * Record the given stream, with a clock, a level meter and a state machine.\n *\n * Pass the stream from {@link useMicrophone}; the hook stays `ready: false` until\n * there is one, so a page can render the whole recorder UI before permission is\n * granted and simply have it disabled.\n *\n * The clock and the level are published on an interval (`tickMs`, default 100 ms)\n * rather than per animation frame. A hook that set state 60 times a second would\n * re-render everything the recorder is inside; 10 Hz is smooth enough for a timer and\n * a meter, and the underlying `createLevelMeter` is still available for anyone who\n * wants a frame-accurate bar written straight to the DOM.\n *\n * @param stream - The stream to record, or `null` while permission is pending.\n * @param options - See {@link UseAudioRecorderOptions}.\n *\n * @example\n * const mic = useMicrophone();\n * const rec = useAudioRecorder(mic.stream, { maxDurationMs: 120_000 });\n * <button disabled={!rec.ready} onClick={rec.start}>Gravar</button>\n */\nexport function useAudioRecorder(\n stream: MediaStream | null,\n options: UseAudioRecorderOptions = {},\n): UseAudioRecorderResult {\n const {\n maxDurationMs,\n onRecorded,\n onChunk,\n onError,\n mimeType,\n audioBitsPerSecond,\n timesliceMs,\n tickMs = 100,\n disableLevelMeter = false,\n } = options;\n\n const [status, setStatus] = useState<AudioRecorderStatus>(\"idle\");\n const [durationMs, setDurationMs] = useState(0);\n const [level, setLevel] = useState(0);\n const [recording, setRecording] = useState<AudioRecording | null>(null);\n const [error, setError] = useState<unknown>(null);\n const [ready, setReady] = useState(false);\n\n const recorderRef = useRef<AudioRecorderHandle | null>(null);\n const meterRef = useRef<LevelMeter | null>(null);\n\n const emitRecorded = useStableCallback((result: AudioRecording) => onRecorded?.(result));\n const emitChunk = useStableCallback((chunk: Blob) => onChunk?.(chunk));\n const emitError = useStableCallback((err: unknown) => {\n setError(err);\n onError?.(err);\n });\n\n /**\n * Build the recorder and the meter for a stream, and tear both down when it\n * changes.\n *\n * The recorder is rebuilt per stream because `MediaRecorder` is bound to the\n * stream it was constructed with — swapping microphones mid-session hands us a\n * new `MediaStream`, and reusing the old recorder would keep recording the device\n * the user just switched away from. It is rebuilt on a container or bitrate change\n * for the same reason: both are constructor arguments.\n */\n useEffect(() => {\n if (!stream) {\n setReady(false);\n return;\n }\n let recorder: AudioRecorderHandle;\n try {\n recorder = createAudioRecorder(stream, {\n mimeType,\n audioBitsPerSecond,\n timesliceMs,\n onChunk: emitChunk,\n onError: emitError,\n });\n } catch (err) {\n setError(err);\n setReady(false);\n return;\n }\n recorderRef.current = recorder;\n meterRef.current = disableLevelMeter ? null : createLevelMeter(stream);\n setReady(true);\n setStatus(\"idle\");\n\n return () => {\n if (recorder.status() === \"recording\" || recorder.status() === \"paused\") {\n recorder.cancel();\n }\n meterRef.current?.stop();\n meterRef.current = null;\n recorderRef.current = null;\n setReady(false);\n };\n }, [\n stream,\n disableLevelMeter,\n mimeType,\n audioBitsPerSecond,\n timesliceMs,\n emitChunk,\n emitError,\n ]);\n\n const stop = useCallback(async (): Promise<AudioRecording | null> => {\n const recorder = recorderRef.current;\n if (!recorder) return null;\n const current = recorder.status();\n if (current !== \"recording\" && current !== \"paused\") return null;\n const result = await recorder.stop();\n setStatus(\"stopped\");\n setDurationMs(result.durationMs);\n setLevel(0);\n setRecording(result);\n emitRecorded(result);\n return result;\n }, [emitRecorded]);\n\n /**\n * Publish the clock and the level while something is running, and enforce\n * `maxDurationMs`.\n *\n * The cap is checked on the same tick rather than with a `setTimeout` armed at\n * `start()`, because a paused recording must not keep counting toward it — a\n * timeout would fire while the user is paused mid-sentence.\n */\n useEffect(() => {\n if (status !== \"recording\" && status !== \"paused\") {\n setLevel(0);\n return;\n }\n const id = setInterval(() => {\n const recorder = recorderRef.current;\n if (!recorder) return;\n const elapsed = recorder.durationMs();\n setDurationMs(elapsed);\n setLevel(status === \"recording\" ? (meterRef.current?.level() ?? 0) : 0);\n if (maxDurationMs !== undefined && elapsed >= maxDurationMs) void stop();\n }, tickMs);\n return () => clearInterval(id);\n }, [status, tickMs, maxDurationMs, stop]);\n\n const start = useCallback((): void => {\n const recorder = recorderRef.current;\n if (!recorder) return;\n setRecording(null);\n setError(null);\n setDurationMs(0);\n recorder.start();\n setStatus(recorder.status());\n }, []);\n\n const pause = useCallback((): void => {\n const recorder = recorderRef.current;\n if (!recorder) return;\n recorder.pause();\n setDurationMs(recorder.durationMs());\n setStatus(recorder.status());\n }, []);\n\n const resume = useCallback((): void => {\n const recorder = recorderRef.current;\n if (!recorder) return;\n recorder.resume();\n setStatus(recorder.status());\n }, []);\n\n const cancel = useCallback((): void => {\n const recorder = recorderRef.current;\n if (!recorder) return;\n recorder.cancel();\n setStatus(\"idle\");\n setDurationMs(0);\n setLevel(0);\n setRecording(null);\n }, []);\n\n return {\n status,\n durationMs,\n level,\n recording,\n error,\n ready,\n start,\n pause,\n resume,\n stop,\n cancel,\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"use-audio-recorder.cjs","names":[],"sources":["../../src/audio/use-audio-recorder.ts"],"sourcesContent":["/**\n * @tempest-limits hook-lines — the hook is the recorder's lifecycle seen from React:\n * permission, the recorder handle, the clock, the level meter and the auto-stop\n * timer, all of which have to be torn down together on unmount. A second hook would\n * own half the teardown.\n */\nimport { useCallback, useEffect, useRef, useState } from \"react\";\n\nimport { useStableCallback } from \"@/hooks/use-stable-callback\";\n\nimport {\n createAudioRecorder,\n type AudioRecorderHandle,\n type AudioRecorderOptions,\n type AudioRecorderStatus,\n type AudioRecording,\n} from \"./audio-recorder\";\nimport { createLevelMeter, type LevelMeter } from \"./level-meter\";\n\n/** Options for {@link useAudioRecorder}. */\nexport interface UseAudioRecorderOptions extends AudioRecorderOptions {\n /**\n * Stop automatically after this many milliseconds.\n *\n * Worth setting on anything user-facing: a recording left running by accident is\n * a memory buffer that grows until the tab dies.\n */\n maxDurationMs?: number;\n /** Called once the recording is assembled, whether by `stop()` or `maxDurationMs`. */\n onRecorded?: (recording: AudioRecording) => void;\n /** How often the clock and the level are published, in ms. Default 100. */\n tickMs?: number;\n /** Skip the level meter — it costs an `AudioContext`. Default `false`. */\n disableLevelMeter?: boolean;\n}\n\n/** Value returned by {@link useAudioRecorder}. */\nexport interface UseAudioRecorderResult {\n status: AudioRecorderStatus;\n /** Recorded length so far, excluding paused time. */\n durationMs: number;\n /** Loudness 0–1, or `0` when the meter is off or nothing is recording. */\n level: number;\n /** The finished recording, or `null` before the first `stop()`. */\n recording: AudioRecording | null;\n /** Recorder-level error (encoder failure, device pulled mid-recording). */\n error: unknown;\n /** `false` when there is no stream yet, or the browser cannot record. */\n ready: boolean;\n start: () => void;\n pause: () => void;\n resume: () => void;\n /** Stop and resolve with the recording. Also published on `recording`. */\n stop: () => Promise<AudioRecording | null>;\n /** Stop and throw the audio away. */\n cancel: () => void;\n}\n\n/**\n * Record the given stream, with a clock, a level meter and a state machine.\n *\n * Pass the stream from {@link useMicrophone}; the hook stays `ready: false` until\n * there is one, so a page can render the whole recorder UI before permission is\n * granted and simply have it disabled.\n *\n * The clock and the level are published on an interval (`tickMs`, default 100 ms)\n * rather than per animation frame. A hook that set state 60 times a second would\n * re-render everything the recorder is inside; 10 Hz is smooth enough for a timer and\n * a meter, and the underlying `createLevelMeter` is still available for anyone who\n * wants a frame-accurate bar written straight to the DOM.\n *\n * @param stream - The stream to record, or `null` while permission is pending.\n * @param options - See {@link UseAudioRecorderOptions}.\n *\n * @example\n * const mic = useMicrophone();\n * const rec = useAudioRecorder(mic.stream, { maxDurationMs: 120_000 });\n * <button disabled={!rec.ready} onClick={rec.start}>Gravar</button>\n */\nexport function useAudioRecorder(\n stream: MediaStream | null,\n options: UseAudioRecorderOptions = {},\n): UseAudioRecorderResult {\n const {\n maxDurationMs,\n onRecorded,\n onChunk,\n onError,\n mimeType,\n audioBitsPerSecond,\n timesliceMs,\n tickMs = 100,\n disableLevelMeter = false,\n } = options;\n\n const [status, setStatus] = useState<AudioRecorderStatus>(\"idle\");\n const [durationMs, setDurationMs] = useState(0);\n const [level, setLevel] = useState(0);\n const [recording, setRecording] = useState<AudioRecording | null>(null);\n const [error, setError] = useState<unknown>(null);\n const [ready, setReady] = useState(false);\n\n const recorderRef = useRef<AudioRecorderHandle | null>(null);\n const meterRef = useRef<LevelMeter | null>(null);\n\n const emitRecorded = useStableCallback((result: AudioRecording) => onRecorded?.(result));\n const emitChunk = useStableCallback((chunk: Blob) => onChunk?.(chunk));\n const emitError = useStableCallback((err: unknown) => {\n setError(err);\n onError?.(err);\n });\n\n /**\n * Build the recorder and the meter for a stream, and tear both down when it\n * changes.\n *\n * The recorder is rebuilt per stream because `MediaRecorder` is bound to the\n * stream it was constructed with — swapping microphones mid-session hands us a\n * new `MediaStream`, and reusing the old recorder would keep recording the device\n * the user just switched away from. It is rebuilt on a container or bitrate change\n * for the same reason: both are constructor arguments.\n */\n useEffect(() => {\n if (!stream) {\n setReady(false);\n return;\n }\n let recorder: AudioRecorderHandle;\n try {\n recorder = createAudioRecorder(stream, {\n mimeType,\n audioBitsPerSecond,\n timesliceMs,\n onChunk: emitChunk,\n onError: emitError,\n });\n } catch (err) {\n setError(err);\n setReady(false);\n return;\n }\n recorderRef.current = recorder;\n meterRef.current = disableLevelMeter ? null : createLevelMeter(stream);\n setReady(true);\n setStatus(\"idle\");\n\n return () => {\n if (recorder.status() === \"recording\" || recorder.status() === \"paused\") {\n recorder.cancel();\n }\n meterRef.current?.stop();\n meterRef.current = null;\n recorderRef.current = null;\n setReady(false);\n };\n }, [\n stream,\n disableLevelMeter,\n mimeType,\n audioBitsPerSecond,\n timesliceMs,\n emitChunk,\n emitError,\n ]);\n\n const stop = useCallback(async (): Promise<AudioRecording | null> => {\n const recorder = recorderRef.current;\n if (!recorder) return null;\n const current = recorder.status();\n if (current !== \"recording\" && current !== \"paused\") return null;\n const result = await recorder.stop();\n setStatus(\"stopped\");\n setDurationMs(result.durationMs);\n setLevel(0);\n setRecording(result);\n emitRecorded(result);\n return result;\n }, [emitRecorded]);\n\n /**\n * Publish the clock and the level while something is running, and enforce\n * `maxDurationMs`.\n *\n * The cap is checked on the same tick rather than with a `setTimeout` armed at\n * `start()`, because a paused recording must not keep counting toward it — a\n * timeout would fire while the user is paused mid-sentence.\n */\n useEffect(() => {\n if (status !== \"recording\" && status !== \"paused\") {\n setLevel(0);\n return;\n }\n const id = setInterval(() => {\n const recorder = recorderRef.current;\n if (!recorder) return;\n const elapsed = recorder.durationMs();\n setDurationMs(elapsed);\n setLevel(status === \"recording\" ? (meterRef.current?.level() ?? 0) : 0);\n if (maxDurationMs !== undefined && elapsed >= maxDurationMs) void stop();\n }, tickMs);\n return () => clearInterval(id);\n }, [status, tickMs, maxDurationMs, stop]);\n\n const start = useCallback((): void => {\n const recorder = recorderRef.current;\n if (!recorder) return;\n setRecording(null);\n setError(null);\n setDurationMs(0);\n recorder.start();\n setStatus(recorder.status());\n }, []);\n\n const pause = useCallback((): void => {\n const recorder = recorderRef.current;\n if (!recorder) return;\n recorder.pause();\n setDurationMs(recorder.durationMs());\n setStatus(recorder.status());\n }, []);\n\n const resume = useCallback((): void => {\n const recorder = recorderRef.current;\n if (!recorder) return;\n recorder.resume();\n setStatus(recorder.status());\n }, []);\n\n const cancel = useCallback((): void => {\n const recorder = recorderRef.current;\n if (!recorder) return;\n recorder.cancel();\n setStatus(\"idle\");\n setDurationMs(0);\n setLevel(0);\n setRecording(null);\n }, []);\n\n return {\n status,\n durationMs,\n level,\n recording,\n error,\n ready,\n start,\n pause,\n resume,\n stop,\n cancel,\n };\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-audio.cjs","names":[],"sources":["../../src/audio/use-audio.ts"],"sourcesContent":["import { useCallback, useEffect, useState } from \"react\";\nimport { createAudioPlayer, type AudioPlayerHandle, type PlayAudioOptions } from \"./audio-player\";\n\nexport interface UseAudioResult {\n /** Play `src` on the hook's private player. */\n play: (src: string, options?: PlayAudioOptions) => Promise<void>;\n /** Stop the current clip. */\n stop: () => void;\n /**\n * Whether the browser's autoplay policy has been satisfied. Becomes\n * `true` after the first successful `play()`.\n */\n unlocked: boolean;\n}\n\n/**\n * The player lives in a state initializer, not in a lazily-written ref: the\n * initializer runs exactly once and is not a side effect during render, which is\n * what the ref version was. The player identity never changes, so state holds it\n * fine and the value is non-null from the first render.\n *\n * Hook-managed audio player. Each component instance gets its own\n * {@link AudioPlayerHandle}, so unmounting cleanly stops playback. Useful for\n * notification chimes, UI feedback sounds, and per-component soundtracks.\n */\nexport function useAudio(): UseAudioResult {\n const [player] = useState<AudioPlayerHandle>(() => createAudioPlayer());\n const [unlocked, setUnlocked] = useState<boolean>(false);\n\n useEffect(() => {\n return () => {\n player.stop();\n };\n }, [player]);\n\n const play = useCallback(\n async (src: string, options?: PlayAudioOptions): Promise<void> => {\n const result = await player.play(src, options);\n if (result) setUnlocked(true);\n },\n [player],\n );\n\n const stop = useCallback((): void => {\n player.stop();\n }, [player]);\n\n return { play, stop, unlocked };\n}\n"],"mappings":"6DAyBA,SAAgB,GAA2B,CACvC,GAAM,CAAC,IAAA,
|
|
1
|
+
{"version":3,"file":"use-audio.cjs","names":[],"sources":["../../src/audio/use-audio.ts"],"sourcesContent":["import { useCallback, useEffect, useState } from \"react\";\nimport { createAudioPlayer, type AudioPlayerHandle, type PlayAudioOptions } from \"./audio-player\";\n\nexport interface UseAudioResult {\n /** Play `src` on the hook's private player. */\n play: (src: string, options?: PlayAudioOptions) => Promise<void>;\n /** Stop the current clip. */\n stop: () => void;\n /**\n * Whether the browser's autoplay policy has been satisfied. Becomes\n * `true` after the first successful `play()`.\n */\n unlocked: boolean;\n}\n\n/**\n * The player lives in a state initializer, not in a lazily-written ref: the\n * initializer runs exactly once and is not a side effect during render, which is\n * what the ref version was. The player identity never changes, so state holds it\n * fine and the value is non-null from the first render.\n *\n * Hook-managed audio player. Each component instance gets its own\n * {@link AudioPlayerHandle}, so unmounting cleanly stops playback. Useful for\n * notification chimes, UI feedback sounds, and per-component soundtracks.\n */\nexport function useAudio(): UseAudioResult {\n const [player] = useState<AudioPlayerHandle>(() => createAudioPlayer());\n const [unlocked, setUnlocked] = useState<boolean>(false);\n\n useEffect(() => {\n return () => {\n player.stop();\n };\n }, [player]);\n\n const play = useCallback(\n async (src: string, options?: PlayAudioOptions): Promise<void> => {\n const result = await player.play(src, options);\n if (result) setUnlocked(true);\n },\n [player],\n );\n\n const stop = useCallback((): void => {\n player.stop();\n }, [player]);\n\n return { play, stop, unlocked };\n}\n"],"mappings":"6DAyBA,SAAgB,GAA2B,CACvC,GAAM,CAAC,IAAA,EAAU,EAAA,SAAA,KAAkC,EAAA,kBAAkB,CAAC,EAChE,CAAC,EAAU,IAAA,EAAe,EAAA,SAAA,CAAkB,EAAK,EAoBvD,OAlBA,EAAA,EAAA,UAAA,SACiB,CACT,EAAO,KAAK,CAChB,EACD,CAAC,CAAM,CAAC,EAcJ,CAAE,MAAA,EAZI,EAAA,YAAA,CACT,MAAO,EAAa,IAA8C,CAE1D,MADiB,EAAO,KAAK,EAAK,CAAO,GACjC,EAAY,EAAI,CAChC,EACA,CAAC,CAAM,CAOF,EAAM,MAAA,EAJF,EAAA,YAAA,KAAwB,CACjC,EAAO,KAAK,CAChB,EAAG,CAAC,CAAM,CAEK,EAAM,UAAS,CAClC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-microphone.cjs","names":[],"sources":["../../src/audio/use-microphone.ts"],"sourcesContent":["import { useCallback, useEffect, useRef, useState } from \"react\";\n\nimport {\n classifyMediaError,\n isMediaCaptureSupported,\n missingCaptureApiError,\n type MediaAccessError,\n} from \"./media-access\";\n\n/** Lifecycle of a microphone stream. */\nexport type MicrophoneStatus = \"idle\" | \"requesting\" | \"ready\" | \"error\";\n\n/** Options for {@link useMicrophone}. */\nexport interface UseMicrophoneOptions {\n /**\n * Open the stream on mount instead of waiting for `start()`. Default `false`.\n *\n * Left off on purpose. Opening on mount means prompting on mount, and a\n * permission prompt the user did not provoke is the most reliable way to earn a\n * permanent \"Block\" — after which `getUserMedia` rejects without ever prompting\n * again. Wire `start()` to the button that needs the microphone.\n */\n autoStart?: boolean;\n /** Specific microphone, from `useMediaDevices().audioInputs`. */\n deviceId?: string;\n /**\n * Browser voice processing. All three default to `true`, which is what speech\n * wants; turn them off for music, where a gate chewing on a decaying note is\n * worse than the room noise it removes.\n */\n echoCancellation?: boolean;\n noiseSuppression?: boolean;\n autoGainControl?: boolean;\n /** Escape hatch: full constraints, replacing everything above. */\n constraints?: MediaStreamConstraints;\n}\n\n/** Value returned by {@link useMicrophone}. */\nexport interface UseMicrophoneResult {\n status: MicrophoneStatus;\n /** The live stream, or `null`. Feed it to {@link useAudioRecorder} or an analyser. */\n stream: MediaStream | null;\n /** Classified error, or `null` outside the `error` status. */\n error: MediaAccessError | null;\n /** Open the stream. Safe to call when already open — it is a no-op. */\n start: () => void;\n /** Release the microphone. The browser's recording indicator only clears here. */\n stop: () => void;\n}\n\n/**\n * Acquire a microphone `MediaStream`, classified errors included, and release it\n * properly.\n *\n * Releasing matters more than it looks. Every track has to be stopped by hand:\n * dropping the last reference to a `MediaStream` does **not** turn off the\n * microphone, so the browser keeps showing its recording indicator and the OS keeps\n * the device busy — which then makes the *next* `getUserMedia` fail with\n * `NotReadableError` in another tab. The hook stops tracks on `stop()`, on unmount,\n * and before re-opening.\n *\n * @param options - See {@link UseMicrophoneOptions}.\n * @returns The stream, its status, a classified error, and `start`/`stop`.\n *\n * @example\n * const mic = useMicrophone();\n * const recorder = useAudioRecorder(mic.stream);\n * <button onClick={mic.start}>Liberar microfone</button>\n */\nexport function useMicrophone(options: UseMicrophoneOptions = {}): UseMicrophoneResult {\n const {\n autoStart = false,\n deviceId,\n echoCancellation = true,\n noiseSuppression = true,\n autoGainControl = true,\n constraints,\n } = options;\n\n const [status, setStatus] = useState<MicrophoneStatus>(\"idle\");\n const [stream, setStream] = useState<MediaStream | null>(null);\n const [error, setError] = useState<MediaAccessError | null>(null);\n const streamRef = useRef<MediaStream | null>(null);\n const generation = useRef(0);\n\n const release = useCallback((): void => {\n streamRef.current?.getTracks().forEach((track) => track.stop());\n streamRef.current = null;\n }, []);\n\n const stop = useCallback((): void => {\n generation.current += 1;\n release();\n setStream(null);\n setStatus(\"idle\");\n setError(null);\n }, [release]);\n\n const start = useCallback((): void => {\n if (streamRef.current) return;\n const run = (generation.current += 1);\n setStatus(\"requesting\");\n setError(null);\n\n if (!isMediaCaptureSupported()) {\n setError(missingCaptureApiError(\"microphone\"));\n setStatus(\"error\");\n return;\n }\n\n const request: MediaStreamConstraints = constraints ?? {\n audio: {\n echoCancellation,\n noiseSuppression,\n autoGainControl,\n ...(deviceId ? { deviceId: { exact: deviceId } } : {}),\n },\n video: false,\n };\n\n void navigator.mediaDevices\n .getUserMedia(request)\n .then((next) => {\n if (run !== generation.current) {\n next.getTracks().forEach((track) => track.stop());\n return;\n }\n streamRef.current = next;\n setStream(next);\n setStatus(\"ready\");\n })\n .catch((err: unknown) => {\n if (run !== generation.current) return;\n setError(classifyMediaError(err, \"microphone\"));\n setStatus(\"error\");\n });\n }, [constraints, deviceId, echoCancellation, noiseSuppression, autoGainControl]);\n\n useEffect(() => {\n if (autoStart) start();\n // `start` is stable per constraint set; re-running on every identity change\n // would reopen the device on each render.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [autoStart]);\n\n useEffect(() => release, [release]);\n\n return { status, stream, error, start, stop };\n}\n"],"mappings":"6DAqEA,SAAgB,EAAc,EAAgC,CAAC,EAAwB,CACnF,GAAM,CACF,YAAY,GACZ,WACA,mBAAmB,GACnB,mBAAmB,GACnB,kBAAkB,GAClB,eACA,EAEE,CAAC,EAAQ,IAAA,
|
|
1
|
+
{"version":3,"file":"use-microphone.cjs","names":[],"sources":["../../src/audio/use-microphone.ts"],"sourcesContent":["import { useCallback, useEffect, useRef, useState } from \"react\";\n\nimport {\n classifyMediaError,\n isMediaCaptureSupported,\n missingCaptureApiError,\n type MediaAccessError,\n} from \"./media-access\";\n\n/** Lifecycle of a microphone stream. */\nexport type MicrophoneStatus = \"idle\" | \"requesting\" | \"ready\" | \"error\";\n\n/** Options for {@link useMicrophone}. */\nexport interface UseMicrophoneOptions {\n /**\n * Open the stream on mount instead of waiting for `start()`. Default `false`.\n *\n * Left off on purpose. Opening on mount means prompting on mount, and a\n * permission prompt the user did not provoke is the most reliable way to earn a\n * permanent \"Block\" — after which `getUserMedia` rejects without ever prompting\n * again. Wire `start()` to the button that needs the microphone.\n */\n autoStart?: boolean;\n /** Specific microphone, from `useMediaDevices().audioInputs`. */\n deviceId?: string;\n /**\n * Browser voice processing. All three default to `true`, which is what speech\n * wants; turn them off for music, where a gate chewing on a decaying note is\n * worse than the room noise it removes.\n */\n echoCancellation?: boolean;\n noiseSuppression?: boolean;\n autoGainControl?: boolean;\n /** Escape hatch: full constraints, replacing everything above. */\n constraints?: MediaStreamConstraints;\n}\n\n/** Value returned by {@link useMicrophone}. */\nexport interface UseMicrophoneResult {\n status: MicrophoneStatus;\n /** The live stream, or `null`. Feed it to {@link useAudioRecorder} or an analyser. */\n stream: MediaStream | null;\n /** Classified error, or `null` outside the `error` status. */\n error: MediaAccessError | null;\n /** Open the stream. Safe to call when already open — it is a no-op. */\n start: () => void;\n /** Release the microphone. The browser's recording indicator only clears here. */\n stop: () => void;\n}\n\n/**\n * Acquire a microphone `MediaStream`, classified errors included, and release it\n * properly.\n *\n * Releasing matters more than it looks. Every track has to be stopped by hand:\n * dropping the last reference to a `MediaStream` does **not** turn off the\n * microphone, so the browser keeps showing its recording indicator and the OS keeps\n * the device busy — which then makes the *next* `getUserMedia` fail with\n * `NotReadableError` in another tab. The hook stops tracks on `stop()`, on unmount,\n * and before re-opening.\n *\n * @param options - See {@link UseMicrophoneOptions}.\n * @returns The stream, its status, a classified error, and `start`/`stop`.\n *\n * @example\n * const mic = useMicrophone();\n * const recorder = useAudioRecorder(mic.stream);\n * <button onClick={mic.start}>Liberar microfone</button>\n */\nexport function useMicrophone(options: UseMicrophoneOptions = {}): UseMicrophoneResult {\n const {\n autoStart = false,\n deviceId,\n echoCancellation = true,\n noiseSuppression = true,\n autoGainControl = true,\n constraints,\n } = options;\n\n const [status, setStatus] = useState<MicrophoneStatus>(\"idle\");\n const [stream, setStream] = useState<MediaStream | null>(null);\n const [error, setError] = useState<MediaAccessError | null>(null);\n const streamRef = useRef<MediaStream | null>(null);\n const generation = useRef(0);\n\n const release = useCallback((): void => {\n streamRef.current?.getTracks().forEach((track) => track.stop());\n streamRef.current = null;\n }, []);\n\n const stop = useCallback((): void => {\n generation.current += 1;\n release();\n setStream(null);\n setStatus(\"idle\");\n setError(null);\n }, [release]);\n\n const start = useCallback((): void => {\n if (streamRef.current) return;\n const run = (generation.current += 1);\n setStatus(\"requesting\");\n setError(null);\n\n if (!isMediaCaptureSupported()) {\n setError(missingCaptureApiError(\"microphone\"));\n setStatus(\"error\");\n return;\n }\n\n const request: MediaStreamConstraints = constraints ?? {\n audio: {\n echoCancellation,\n noiseSuppression,\n autoGainControl,\n ...(deviceId ? { deviceId: { exact: deviceId } } : {}),\n },\n video: false,\n };\n\n void navigator.mediaDevices\n .getUserMedia(request)\n .then((next) => {\n if (run !== generation.current) {\n next.getTracks().forEach((track) => track.stop());\n return;\n }\n streamRef.current = next;\n setStream(next);\n setStatus(\"ready\");\n })\n .catch((err: unknown) => {\n if (run !== generation.current) return;\n setError(classifyMediaError(err, \"microphone\"));\n setStatus(\"error\");\n });\n }, [constraints, deviceId, echoCancellation, noiseSuppression, autoGainControl]);\n\n useEffect(() => {\n if (autoStart) start();\n // `start` is stable per constraint set; re-running on every identity change\n // would reopen the device on each render.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [autoStart]);\n\n useEffect(() => release, [release]);\n\n return { status, stream, error, start, stop };\n}\n"],"mappings":"6DAqEA,SAAgB,EAAc,EAAgC,CAAC,EAAwB,CACnF,GAAM,CACF,YAAY,GACZ,WACA,mBAAmB,GACnB,mBAAmB,GACnB,kBAAkB,GAClB,eACA,EAEE,CAAC,EAAQ,IAAA,EAAa,EAAA,SAAA,CAA2B,MAAM,EACvD,CAAC,EAAQ,IAAA,EAAa,EAAA,SAAA,CAA6B,IAAI,EACvD,CAAC,EAAO,IAAA,EAAY,EAAA,SAAA,CAAkC,IAAI,EAC1D,GAAA,EAAY,EAAA,OAAA,CAA2B,IAAI,EAC3C,GAAA,EAAa,EAAA,OAAA,CAAO,CAAC,EAErB,GAAA,EAAU,EAAA,YAAA,KAAwB,CACpC,EAAU,SAAS,UAAU,CAAC,CAAC,QAAS,GAAU,EAAM,KAAK,CAAC,EAC9D,EAAU,QAAU,IACxB,EAAG,CAAC,CAAC,EAEC,GAAA,EAAO,EAAA,YAAA,KAAwB,CACjC,EAAW,SAAW,EACtB,EAAQ,EACR,EAAU,IAAI,EACd,EAAU,MAAM,EAChB,EAAS,IAAI,CACjB,EAAG,CAAC,CAAO,CAAC,EAEN,GAAA,EAAQ,EAAA,YAAA,KAAwB,CAClC,GAAI,EAAU,QAAS,OACvB,IAAM,EAAO,EAAW,SAAW,EAInC,GAHA,EAAU,YAAY,EACtB,EAAS,IAAI,EAET,CAAC,EAAA,wBAAwB,EAAG,CAC5B,EAAS,EAAA,uBAAuB,YAAY,CAAC,EAC7C,EAAU,OAAO,EACjB,MACJ,CAEA,IAAM,EAAkC,GAAe,CACnD,MAAO,CACH,mBACA,mBACA,kBACA,GAAI,EAAW,CAAE,SAAU,CAAE,MAAO,CAAS,CAAE,EAAI,CAAC,CACxD,EACA,MAAO,EACX,EAEA,UAAe,aACV,aAAa,CAAO,CAAC,CACrB,KAAM,GAAS,CACZ,GAAI,IAAQ,EAAW,QAAS,CAC5B,EAAK,UAAU,CAAC,CAAC,QAAS,GAAU,EAAM,KAAK,CAAC,EAChD,MACJ,CACA,EAAU,QAAU,EACpB,EAAU,CAAI,EACd,EAAU,OAAO,CACrB,CAAC,CAAC,CACD,MAAO,GAAiB,CACjB,IAAQ,EAAW,UACvB,EAAS,EAAA,mBAAmB,EAAK,YAAY,CAAC,EAC9C,EAAU,OAAO,EACrB,CAAC,CACT,EAAG,CAAC,EAAa,EAAU,EAAkB,EAAkB,CAAe,CAAC,EAW/E,OATA,EAAA,EAAA,UAAA,KAAgB,CACR,GAAW,EAAM,CAIzB,EAAG,CAAC,CAAS,CAAC,GAEd,EAAA,EAAA,UAAA,KAAgB,EAAS,CAAC,CAAO,CAAC,EAE3B,CAAE,SAAQ,SAAQ,QAAO,QAAO,MAAK,CAChD"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-auth-store.cjs","names":[],"sources":["../../src/auth/create-auth-store.ts"],"sourcesContent":["import { create } from \"zustand\";\nimport { persist, createJSONStorage } from \"zustand/middleware\";\n\nexport interface AuthState<TUser> {\n user: TUser | null;\n token: string | null;\n isAuthenticated: boolean;\n setSession: (session: { user: TUser; token: string }) => void;\n setUser: (user: TUser | null) => void;\n setToken: (token: string | null) => void;\n logout: () => void;\n}\n\nexport interface CreateAuthStoreOptions<TUser> {\n /** Persist key used in storage (default: \"tempest-auth\"). */\n name?: string;\n /** Which storage to persist into (default: localStorage). */\n storage?: \"local\" | \"session\";\n /** Initial user (useful for SSR hydration). */\n initialUser?: TUser | null;\n /** Initial token. */\n initialToken?: string | null;\n}\n\n/**\n * Build a typed Zustand auth store with `persist` middleware. Each app passes\n * its own `TUser` shape so the SDK does not own the user model.\n *\n * @example\n * const useAuthStore = createAuthStore<UserResponse>();\n * const { user, token, logout } = useAuthStore();\n */\nexport function createAuthStore<TUser>(options: CreateAuthStoreOptions<TUser> = {}) {\n const name = options.name ?? \"tempest-auth\";\n const storageImpl = options.storage === \"session\" ? () => sessionStorage : () => localStorage;\n\n return create<AuthState<TUser>>()(\n persist(\n (set) => ({\n user: options.initialUser ?? null,\n token: options.initialToken ?? null,\n isAuthenticated: !!options.initialToken,\n setSession: ({ user, token }) => set({ user, token, isAuthenticated: true }),\n setUser: (user) => set({ user }),\n setToken: (token) => set({ token, isAuthenticated: !!token }),\n logout: () => set({ user: null, token: null, isAuthenticated: false }),\n }),\n {\n name,\n storage: createJSONStorage(storageImpl),\n partialize: (state) => ({ user: state.user, token: state.token }),\n onRehydrateStorage: () => (state) => {\n if (state) state.isAuthenticated = !!state.token;\n },\n },\n ),\n );\n}\n"],"mappings":"yDAgCA,SAAgB,EAAuB,EAAyC,CAAC,EAAG,CAChF,IAAM,EAAO,EAAQ,MAAQ,eACvB,EAAc,EAAQ,UAAY,cAAkB,mBAAuB,aAEjF,OAAA,
|
|
1
|
+
{"version":3,"file":"create-auth-store.cjs","names":[],"sources":["../../src/auth/create-auth-store.ts"],"sourcesContent":["import { create } from \"zustand\";\nimport { persist, createJSONStorage } from \"zustand/middleware\";\n\nexport interface AuthState<TUser> {\n user: TUser | null;\n token: string | null;\n isAuthenticated: boolean;\n setSession: (session: { user: TUser; token: string }) => void;\n setUser: (user: TUser | null) => void;\n setToken: (token: string | null) => void;\n logout: () => void;\n}\n\nexport interface CreateAuthStoreOptions<TUser> {\n /** Persist key used in storage (default: \"tempest-auth\"). */\n name?: string;\n /** Which storage to persist into (default: localStorage). */\n storage?: \"local\" | \"session\";\n /** Initial user (useful for SSR hydration). */\n initialUser?: TUser | null;\n /** Initial token. */\n initialToken?: string | null;\n}\n\n/**\n * Build a typed Zustand auth store with `persist` middleware. Each app passes\n * its own `TUser` shape so the SDK does not own the user model.\n *\n * @example\n * const useAuthStore = createAuthStore<UserResponse>();\n * const { user, token, logout } = useAuthStore();\n */\nexport function createAuthStore<TUser>(options: CreateAuthStoreOptions<TUser> = {}) {\n const name = options.name ?? \"tempest-auth\";\n const storageImpl = options.storage === \"session\" ? () => sessionStorage : () => localStorage;\n\n return create<AuthState<TUser>>()(\n persist(\n (set) => ({\n user: options.initialUser ?? null,\n token: options.initialToken ?? null,\n isAuthenticated: !!options.initialToken,\n setSession: ({ user, token }) => set({ user, token, isAuthenticated: true }),\n setUser: (user) => set({ user }),\n setToken: (token) => set({ token, isAuthenticated: !!token }),\n logout: () => set({ user: null, token: null, isAuthenticated: false }),\n }),\n {\n name,\n storage: createJSONStorage(storageImpl),\n partialize: (state) => ({ user: state.user, token: state.token }),\n onRehydrateStorage: () => (state) => {\n if (state) state.isAuthenticated = !!state.token;\n },\n },\n ),\n );\n}\n"],"mappings":"yDAgCA,SAAgB,EAAuB,EAAyC,CAAC,EAAG,CAChF,IAAM,EAAO,EAAQ,MAAQ,eACvB,EAAc,EAAQ,UAAY,cAAkB,mBAAuB,aAEjF,OAAA,EAAO,EAAA,OAAA,CAAyB,CAAC,EAAA,EAC7B,EAAA,QAAA,CACK,IAAS,CACN,KAAM,EAAQ,aAAe,KAC7B,MAAO,EAAQ,cAAgB,KAC/B,gBAAiB,CAAC,CAAC,EAAQ,aAC3B,YAAa,CAAE,OAAM,WAAY,EAAI,CAAE,OAAM,QAAO,gBAAiB,EAAK,CAAC,EAC3E,QAAU,GAAS,EAAI,CAAE,MAAK,CAAC,EAC/B,SAAW,GAAU,EAAI,CAAE,QAAO,gBAAiB,CAAC,CAAC,CAAM,CAAC,EAC5D,WAAc,EAAI,CAAE,KAAM,KAAM,MAAO,KAAM,gBAAiB,EAAM,CAAC,CACzE,GACA,CACI,OACA,SAAA,EAAS,EAAA,kBAAA,CAAkB,CAAW,EACtC,WAAa,IAAW,CAAE,KAAM,EAAM,KAAM,MAAO,EAAM,KAAM,GAC/D,uBAA2B,GAAU,CAC7B,IAAO,EAAM,gBAAkB,CAAC,CAAC,EAAM,MAC/C,CACJ,CACJ,CACJ,CACJ"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lazy-with-retry.cjs","names":[],"sources":["../../src/auth/lazy-with-retry.ts"],"sourcesContent":["import { lazy } from \"react\";\nimport type { ComponentType } from \"react\";\n\nexport interface LazyWithRetryOptions {\n /** Max attempts. Default: 3. */\n retries?: number;\n /** Initial delay (ms) before retrying. Default: 400. */\n initialDelay?: number;\n /**\n * Reload the page after every retry fails. Helps when the stale chunk\n * error is caused by an old `index.html` referencing deleted bundles.\n * Default: true.\n */\n reloadOnFinalFailure?: boolean;\n}\n\n/**\n * Wrap `React.lazy` with automatic retry. Common cause of failure:\n * deployed-then-cached `index.html` references chunk filenames that no\n * longer exist. Retrying after a short delay typically picks up the new\n * bundle; a final `location.reload()` recovers from stale `index.html`.\n *\n * @example\n * const Settings = lazyWithRetry(() => import(\"./Settings\"));\n */\nexport function lazyWithRetry<T extends ComponentType<unknown>>(\n factory: () => Promise<{ default: T }>,\n options: LazyWithRetryOptions = {},\n): ReturnType<typeof lazy<T>> {\n const { retries = 3, initialDelay = 400, reloadOnFinalFailure = true } = options;\n\n async function load(attempt = 1): Promise<{ default: T }> {\n try {\n return await factory();\n } catch (error) {\n if (attempt >= retries) {\n if (reloadOnFinalFailure && typeof window !== \"undefined\") {\n window.location.reload();\n }\n throw error;\n }\n await new Promise((resolve) => setTimeout(resolve, initialDelay * 2 ** (attempt - 1)));\n return load(attempt + 1);\n }\n }\n\n return lazy(load) as ReturnType<typeof lazy<T>>;\n}\n"],"mappings":"uBAyBA,SAAgB,EACZ,EACA,EAAgC,CAAC,EACP,CAC1B,GAAM,CAAE,UAAU,EAAG,eAAe,IAAK,uBAAuB,IAAS,EAEzE,eAAe,EAAK,EAAU,EAA4B,CACtD,GAAI,CACA,OAAO,MAAM,EAAQ,CACzB,OAAS,EAAO,CACZ,GAAI,GAAW,EAIX,MAHI,GAAwB,OAAO,OAAW,KAC1C,OAAO,SAAS,OAAO,EAErB,EAGV,OADA,MAAM,IAAI,QAAS,GAAY,WAAW,EAAS,EAAe,IAAM,EAAU,EAAE,CAAC,EAC9E,EAAK,EAAU,CAAC,CAC3B,CACJ,CAEA,OAAA,
|
|
1
|
+
{"version":3,"file":"lazy-with-retry.cjs","names":[],"sources":["../../src/auth/lazy-with-retry.ts"],"sourcesContent":["import { lazy } from \"react\";\nimport type { ComponentType } from \"react\";\n\nexport interface LazyWithRetryOptions {\n /** Max attempts. Default: 3. */\n retries?: number;\n /** Initial delay (ms) before retrying. Default: 400. */\n initialDelay?: number;\n /**\n * Reload the page after every retry fails. Helps when the stale chunk\n * error is caused by an old `index.html` referencing deleted bundles.\n * Default: true.\n */\n reloadOnFinalFailure?: boolean;\n}\n\n/**\n * Wrap `React.lazy` with automatic retry. Common cause of failure:\n * deployed-then-cached `index.html` references chunk filenames that no\n * longer exist. Retrying after a short delay typically picks up the new\n * bundle; a final `location.reload()` recovers from stale `index.html`.\n *\n * @example\n * const Settings = lazyWithRetry(() => import(\"./Settings\"));\n */\nexport function lazyWithRetry<T extends ComponentType<unknown>>(\n factory: () => Promise<{ default: T }>,\n options: LazyWithRetryOptions = {},\n): ReturnType<typeof lazy<T>> {\n const { retries = 3, initialDelay = 400, reloadOnFinalFailure = true } = options;\n\n async function load(attempt = 1): Promise<{ default: T }> {\n try {\n return await factory();\n } catch (error) {\n if (attempt >= retries) {\n if (reloadOnFinalFailure && typeof window !== \"undefined\") {\n window.location.reload();\n }\n throw error;\n }\n await new Promise((resolve) => setTimeout(resolve, initialDelay * 2 ** (attempt - 1)));\n return load(attempt + 1);\n }\n }\n\n return lazy(load) as ReturnType<typeof lazy<T>>;\n}\n"],"mappings":"uBAyBA,SAAgB,EACZ,EACA,EAAgC,CAAC,EACP,CAC1B,GAAM,CAAE,UAAU,EAAG,eAAe,IAAK,uBAAuB,IAAS,EAEzE,eAAe,EAAK,EAAU,EAA4B,CACtD,GAAI,CACA,OAAO,MAAM,EAAQ,CACzB,OAAS,EAAO,CACZ,GAAI,GAAW,EAIX,MAHI,GAAwB,OAAO,OAAW,KAC1C,OAAO,SAAS,OAAO,EAErB,EAGV,OADA,MAAM,IAAI,QAAS,GAAY,WAAW,EAAS,EAAe,IAAM,EAAU,EAAE,CAAC,EAC9E,EAAK,EAAU,CAAC,CAC3B,CACJ,CAEA,OAAA,EAAO,EAAA,KAAA,CAAK,CAAI,CACpB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-passkey.cjs","names":[],"sources":["../../src/auth/use-passkey.ts"],"sourcesContent":["/**\n * @tempest-limits file-lines — the hook wraps both ceremonies with the status\n * machine the UI needs (`prompting` and `verifying` are separate because they need\n * different copy) and the capability probes that decide whether to offer the button\n * at all.\n */\nimport { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\n\nimport { useStableCallback } from \"../hooks/use-stable-callback\";\nimport {\n classifyPasskeyError,\n createPasskeyClient,\n type PasskeyAuthenticationJSON,\n type PasskeyClient,\n type PasskeyCreationOptionsJSON,\n type PasskeyError,\n type PasskeyRegistrationJSON,\n type PasskeyRequestOptionsJSON,\n} from \"./passkey\";\n\n/**\n * Where a passkey ceremony is.\n *\n * `\"prompting\"` and `\"verifying\"` are split because they need different copy:\n * while prompting, the browser sheet is up and the user must touch a sensor;\n * while verifying, your backend is checking the signature and the user should\n * just wait.\n */\nexport type PasskeyStatus = \"idle\" | \"prompting\" | \"verifying\" | \"success\" | \"error\";\n\n/** Capability probes shared by both passkey hooks. */\nexport interface PasskeyCapabilities {\n /** WebAuthn exists in this browser. Known synchronously. */\n supported: boolean;\n /**\n * This device has a built-in authenticator. `null` until the async probe\n * settles — render nothing passkey-related while it is `null`.\n */\n platformAvailable: boolean | null;\n /** Autofill-driven sign-in is available. `null` until the probe settles. */\n conditionalAvailable: boolean | null;\n}\n\nlet sharedClient: PasskeyClient | undefined;\n\n/**\n * Lazily built default client, shared by every hook that was not given one.\n *\n * Lazy rather than a module-level constant so that importing the hooks never runs\n * a call the bundler cannot prove side-effect-free — that alone would pin the\n * module into every consumer's bundle.\n */\nfunction defaultClient(): PasskeyClient {\n sharedClient ??= createPasskeyClient();\n return sharedClient;\n}\n\n/**\n * Probe what this device can do, once per mount.\n *\n * @param client - Client whose probes to call.\n * @returns The three capability answers, two of them async.\n */\nexport function usePasskeyCapabilities(client?: PasskeyClient): PasskeyCapabilities {\n const resolved = useMemo(() => client ?? defaultClient(), [client]);\n const [supported] = useState<boolean>(() => resolved.isSupported());\n const [platformAvailable, setPlatformAvailable] = useState<boolean | null>(null);\n const [conditionalAvailable, setConditionalAvailable] = useState<boolean | null>(null);\n\n useEffect(() => {\n let cancelled = false;\n void resolved.isPlatformAuthenticatorAvailable().then((value) => {\n if (!cancelled) setPlatformAvailable(value);\n });\n void resolved.isConditionalMediationAvailable().then((value) => {\n if (!cancelled) setConditionalAvailable(value);\n });\n return () => {\n cancelled = true;\n };\n }, [resolved]);\n\n return { supported, platformAvailable, conditionalAvailable };\n}\n\n/** Options for {@link usePasskeyRegistration}. */\nexport interface UsePasskeyRegistrationOptions<TResult> {\n /** Fetch server options — your `POST /webauthn/register/begin`. */\n getOptions: () => Promise<PasskeyCreationOptionsJSON>;\n /** Send the credential to be verified — your `POST /webauthn/register/finish`. */\n verify: (credential: PasskeyRegistrationJSON) => Promise<TResult>;\n /** Client to use. Defaults to a shared `createPasskeyClient()`. */\n client?: PasskeyClient;\n /** Called with whatever `verify` resolved to. */\n onSuccess?: (result: TResult) => void;\n /** Called with the classified failure. */\n onError?: (error: PasskeyError) => void;\n}\n\n/** Value returned by {@link usePasskeyRegistration}. */\nexport interface UsePasskeyRegistrationResult<TResult> extends PasskeyCapabilities {\n /** Run the ceremony. Resolves `null` on failure — read `error` for the reason. */\n register: () => Promise<TResult | null>;\n /** Current phase. */\n status: PasskeyStatus;\n /** Last failure, cleared when a new attempt starts. */\n error: PasskeyError | null;\n /** Whatever `verify` resolved to, on success. */\n data: TResult | null;\n /** Close the browser sheet from your own UI. */\n cancel: () => void;\n /** Back to `\"idle\"`, clearing `error` and `data`. */\n reset: () => void;\n}\n\n/**\n * Register a passkey: fetch options, run `navigator.credentials.create`, hand the\n * credential to your backend.\n *\n * `register()` **resolves** rather than rejects on failure, because every failure\n * here is already state the UI has to render (`error.kind`, `status`). Forcing a\n * `try/catch` around a button handler that also has to set state would only\n * duplicate what the hook holds.\n *\n * @param options - Transport callbacks plus optional client and listeners.\n * @returns Ceremony state, the `register` action, and the capability probes.\n *\n * @example\n * const passkey = usePasskeyRegistration({\n * getOptions: () => api.post(\"/webauthn/register/begin\"),\n * verify: (credential) => api.post(\"/webauthn/register/finish\", { body: credential }),\n * });\n *\n * if (passkey.platformAvailable === false) return null;\n * return (\n * <button onClick={() => void passkey.register()} disabled={passkey.status === \"prompting\"}>\n * Criar passkey\n * </button>\n * );\n */\nexport function usePasskeyRegistration<TResult = unknown>(\n options: UsePasskeyRegistrationOptions<TResult>,\n): UsePasskeyRegistrationResult<TResult> {\n const client = useMemo(() => options.client ?? defaultClient(), [options.client]);\n const capabilities = usePasskeyCapabilities(client);\n const [status, setStatus] = useState<PasskeyStatus>(\"idle\");\n const [error, setError] = useState<PasskeyError | null>(null);\n const [data, setData] = useState<TResult | null>(null);\n const controller = useRef<AbortController | null>(null);\n\n const getOptions = useStableCallback(options.getOptions);\n const verify = useStableCallback(options.verify);\n const onSuccess = useStableCallback((result: TResult) => options.onSuccess?.(result));\n const onError = useStableCallback((failure: PasskeyError) => options.onError?.(failure));\n\n const register = useCallback(async (): Promise<TResult | null> => {\n setStatus(\"prompting\");\n setError(null);\n const abort = new AbortController();\n controller.current = abort;\n try {\n const serverOptions = await getOptions();\n const credential = await client.register(serverOptions, { signal: abort.signal });\n setStatus(\"verifying\");\n const result = await verify(credential);\n setData(result);\n setStatus(\"success\");\n onSuccess(result);\n return result;\n } catch (thrown) {\n const failure = classifyPasskeyError(thrown, \"register\");\n setError(failure);\n setStatus(\"error\");\n onError(failure);\n return null;\n } finally {\n controller.current = null;\n }\n }, [client, getOptions, verify, onSuccess, onError]);\n\n const cancel = useCallback((): void => {\n controller.current?.abort();\n }, []);\n\n const reset = useCallback((): void => {\n setStatus(\"idle\");\n setError(null);\n setData(null);\n }, []);\n\n return { ...capabilities, register, status, error, data, cancel, reset };\n}\n\n/** Options for {@link usePasskeySignIn}. */\nexport interface UsePasskeySignInOptions<TResult> {\n /** Fetch server options — your `POST /webauthn/signin/begin`. */\n getOptions: () => Promise<PasskeyRequestOptionsJSON>;\n /** Send the assertion to be verified — your `POST /webauthn/signin/finish`. */\n verify: (assertion: PasskeyAuthenticationJSON) => Promise<TResult>;\n /** Client to use. Defaults to a shared `createPasskeyClient()`. */\n client?: PasskeyClient;\n /**\n * Arm the autofill flow on mount (`mediation: \"conditional\"`).\n *\n * The request stays pending, invisibly, until the user picks a passkey from\n * the browser's autofill list — so the field it should appear on needs\n * `autocomplete=\"username webauthn\"`. Only one conditional request may be live\n * per page, and it is aborted on unmount.\n */\n conditional?: boolean;\n /** Called with whatever `verify` resolved to. */\n onSuccess?: (result: TResult) => void;\n /** Called with the classified failure. */\n onError?: (error: PasskeyError) => void;\n}\n\n/** Value returned by {@link usePasskeySignIn}. */\nexport interface UsePasskeySignInResult<TResult> extends PasskeyCapabilities {\n /** Run the modal ceremony. Resolves `null` on failure. */\n signIn: () => Promise<TResult | null>;\n /** Current phase. */\n status: PasskeyStatus;\n /** Last failure, cleared when a new attempt starts. */\n error: PasskeyError | null;\n /** Whatever `verify` resolved to, on success. */\n data: TResult | null;\n /** True while an armed conditional (autofill) request is waiting. */\n conditionalPending: boolean;\n /** Close the browser sheet from your own UI. */\n cancel: () => void;\n /** Back to `\"idle\"`, clearing `error` and `data`. */\n reset: () => void;\n}\n\n/**\n * Sign in with a passkey, with or without the autofill flow.\n *\n * Two ways in, and a good login page wires both. `signIn()` is the explicit\n * button. `conditional: true` arms the pleasant one: the browser lists the user's\n * passkeys inside the username field itself, so signing in is one tap and no\n * password was ever typed. The field must carry\n * `autocomplete=\"username webauthn\"` or the list never appears — the hook cannot\n * do that part for you.\n *\n * A conditional request that is aborted (unmount, or the user typing a password\n * instead) is **not** surfaced as an error: nothing failed, the user chose\n * another door.\n *\n * @param options - Transport callbacks, `conditional`, optional client and listeners.\n * @returns Ceremony state, the `signIn` action, and the capability probes.\n *\n * @example\n * const passkey = usePasskeySignIn({\n * conditional: true,\n * getOptions: () => api.post(\"/webauthn/signin/begin\"),\n * verify: (assertion) => api.post(\"/webauthn/signin/finish\", { body: assertion }),\n * onSuccess: (session) => auth.setToken(session.access_token),\n * });\n *\n * <input name=\"email\" autoComplete=\"username webauthn\" />\n * <button onClick={() => void passkey.signIn()}>Entrar com passkey</button>\n */\nexport function usePasskeySignIn<TResult = unknown>(\n options: UsePasskeySignInOptions<TResult>,\n): UsePasskeySignInResult<TResult> {\n const client = useMemo(() => options.client ?? defaultClient(), [options.client]);\n const capabilities = usePasskeyCapabilities(client);\n const [status, setStatus] = useState<PasskeyStatus>(\"idle\");\n const [error, setError] = useState<PasskeyError | null>(null);\n const [data, setData] = useState<TResult | null>(null);\n const [conditionalPending, setConditionalPending] = useState(false);\n const controller = useRef<AbortController | null>(null);\n const conditional = options.conditional ?? false;\n\n const getOptions = useStableCallback(options.getOptions);\n const verify = useStableCallback(options.verify);\n const onSuccess = useStableCallback((result: TResult) => options.onSuccess?.(result));\n const onError = useStableCallback((failure: PasskeyError) => options.onError?.(failure));\n\n const run = useCallback(\n async (\n mediation: \"optional\" | \"conditional\",\n abort: AbortController = new AbortController(),\n ): Promise<TResult | null> => {\n controller.current = abort;\n if (mediation === \"optional\") {\n setStatus(\"prompting\");\n setError(null);\n }\n try {\n const serverOptions = await getOptions();\n const assertion = await client.authenticate(serverOptions, {\n mediation,\n signal: abort.signal,\n });\n setStatus(\"verifying\");\n const result = await verify(assertion);\n setData(result);\n setStatus(\"success\");\n onSuccess(result);\n return result;\n } catch (thrown) {\n const failure = classifyPasskeyError(thrown, \"authenticate\");\n if (failure.kind === \"aborted\" && mediation === \"conditional\") return null;\n setError(failure);\n setStatus(\"error\");\n onError(failure);\n return null;\n } finally {\n controller.current = null;\n }\n },\n [client, getOptions, verify, onSuccess, onError],\n );\n\n const signIn = useCallback(() => run(\"optional\"), [run]);\n\n useEffect(() => {\n if (!conditional) return;\n let cancelled = false;\n let abort: AbortController | null = null;\n\n async function arm(): Promise<void> {\n if (!(await client.isConditionalMediationAvailable())) return;\n if (cancelled) return;\n setConditionalPending(true);\n abort = new AbortController();\n await run(\"conditional\", abort);\n if (!cancelled) setConditionalPending(false);\n }\n\n void arm();\n\n return () => {\n cancelled = true;\n abort?.abort();\n };\n }, [conditional, client, run]);\n\n const cancel = useCallback((): void => {\n controller.current?.abort();\n }, []);\n\n const reset = useCallback((): void => {\n setStatus(\"idle\");\n setError(null);\n setData(null);\n }, []);\n\n return {\n ...capabilities,\n signIn,\n status,\n error,\n data,\n conditionalPending,\n cancel,\n reset,\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"use-passkey.cjs","names":[],"sources":["../../src/auth/use-passkey.ts"],"sourcesContent":["/**\n * @tempest-limits file-lines — the hook wraps both ceremonies with the status\n * machine the UI needs (`prompting` and `verifying` are separate because they need\n * different copy) and the capability probes that decide whether to offer the button\n * at all.\n */\nimport { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\n\nimport { useStableCallback } from \"../hooks/use-stable-callback\";\nimport {\n classifyPasskeyError,\n createPasskeyClient,\n type PasskeyAuthenticationJSON,\n type PasskeyClient,\n type PasskeyCreationOptionsJSON,\n type PasskeyError,\n type PasskeyRegistrationJSON,\n type PasskeyRequestOptionsJSON,\n} from \"./passkey\";\n\n/**\n * Where a passkey ceremony is.\n *\n * `\"prompting\"` and `\"verifying\"` are split because they need different copy:\n * while prompting, the browser sheet is up and the user must touch a sensor;\n * while verifying, your backend is checking the signature and the user should\n * just wait.\n */\nexport type PasskeyStatus = \"idle\" | \"prompting\" | \"verifying\" | \"success\" | \"error\";\n\n/** Capability probes shared by both passkey hooks. */\nexport interface PasskeyCapabilities {\n /** WebAuthn exists in this browser. Known synchronously. */\n supported: boolean;\n /**\n * This device has a built-in authenticator. `null` until the async probe\n * settles — render nothing passkey-related while it is `null`.\n */\n platformAvailable: boolean | null;\n /** Autofill-driven sign-in is available. `null` until the probe settles. */\n conditionalAvailable: boolean | null;\n}\n\nlet sharedClient: PasskeyClient | undefined;\n\n/**\n * Lazily built default client, shared by every hook that was not given one.\n *\n * Lazy rather than a module-level constant so that importing the hooks never runs\n * a call the bundler cannot prove side-effect-free — that alone would pin the\n * module into every consumer's bundle.\n */\nfunction defaultClient(): PasskeyClient {\n sharedClient ??= createPasskeyClient();\n return sharedClient;\n}\n\n/**\n * Probe what this device can do, once per mount.\n *\n * @param client - Client whose probes to call.\n * @returns The three capability answers, two of them async.\n */\nexport function usePasskeyCapabilities(client?: PasskeyClient): PasskeyCapabilities {\n const resolved = useMemo(() => client ?? defaultClient(), [client]);\n const [supported] = useState<boolean>(() => resolved.isSupported());\n const [platformAvailable, setPlatformAvailable] = useState<boolean | null>(null);\n const [conditionalAvailable, setConditionalAvailable] = useState<boolean | null>(null);\n\n useEffect(() => {\n let cancelled = false;\n void resolved.isPlatformAuthenticatorAvailable().then((value) => {\n if (!cancelled) setPlatformAvailable(value);\n });\n void resolved.isConditionalMediationAvailable().then((value) => {\n if (!cancelled) setConditionalAvailable(value);\n });\n return () => {\n cancelled = true;\n };\n }, [resolved]);\n\n return { supported, platformAvailable, conditionalAvailable };\n}\n\n/** Options for {@link usePasskeyRegistration}. */\nexport interface UsePasskeyRegistrationOptions<TResult> {\n /** Fetch server options — your `POST /webauthn/register/begin`. */\n getOptions: () => Promise<PasskeyCreationOptionsJSON>;\n /** Send the credential to be verified — your `POST /webauthn/register/finish`. */\n verify: (credential: PasskeyRegistrationJSON) => Promise<TResult>;\n /** Client to use. Defaults to a shared `createPasskeyClient()`. */\n client?: PasskeyClient;\n /** Called with whatever `verify` resolved to. */\n onSuccess?: (result: TResult) => void;\n /** Called with the classified failure. */\n onError?: (error: PasskeyError) => void;\n}\n\n/** Value returned by {@link usePasskeyRegistration}. */\nexport interface UsePasskeyRegistrationResult<TResult> extends PasskeyCapabilities {\n /** Run the ceremony. Resolves `null` on failure — read `error` for the reason. */\n register: () => Promise<TResult | null>;\n /** Current phase. */\n status: PasskeyStatus;\n /** Last failure, cleared when a new attempt starts. */\n error: PasskeyError | null;\n /** Whatever `verify` resolved to, on success. */\n data: TResult | null;\n /** Close the browser sheet from your own UI. */\n cancel: () => void;\n /** Back to `\"idle\"`, clearing `error` and `data`. */\n reset: () => void;\n}\n\n/**\n * Register a passkey: fetch options, run `navigator.credentials.create`, hand the\n * credential to your backend.\n *\n * `register()` **resolves** rather than rejects on failure, because every failure\n * here is already state the UI has to render (`error.kind`, `status`). Forcing a\n * `try/catch` around a button handler that also has to set state would only\n * duplicate what the hook holds.\n *\n * @param options - Transport callbacks plus optional client and listeners.\n * @returns Ceremony state, the `register` action, and the capability probes.\n *\n * @example\n * const passkey = usePasskeyRegistration({\n * getOptions: () => api.post(\"/webauthn/register/begin\"),\n * verify: (credential) => api.post(\"/webauthn/register/finish\", { body: credential }),\n * });\n *\n * if (passkey.platformAvailable === false) return null;\n * return (\n * <button onClick={() => void passkey.register()} disabled={passkey.status === \"prompting\"}>\n * Criar passkey\n * </button>\n * );\n */\nexport function usePasskeyRegistration<TResult = unknown>(\n options: UsePasskeyRegistrationOptions<TResult>,\n): UsePasskeyRegistrationResult<TResult> {\n const client = useMemo(() => options.client ?? defaultClient(), [options.client]);\n const capabilities = usePasskeyCapabilities(client);\n const [status, setStatus] = useState<PasskeyStatus>(\"idle\");\n const [error, setError] = useState<PasskeyError | null>(null);\n const [data, setData] = useState<TResult | null>(null);\n const controller = useRef<AbortController | null>(null);\n\n const getOptions = useStableCallback(options.getOptions);\n const verify = useStableCallback(options.verify);\n const onSuccess = useStableCallback((result: TResult) => options.onSuccess?.(result));\n const onError = useStableCallback((failure: PasskeyError) => options.onError?.(failure));\n\n const register = useCallback(async (): Promise<TResult | null> => {\n setStatus(\"prompting\");\n setError(null);\n const abort = new AbortController();\n controller.current = abort;\n try {\n const serverOptions = await getOptions();\n const credential = await client.register(serverOptions, { signal: abort.signal });\n setStatus(\"verifying\");\n const result = await verify(credential);\n setData(result);\n setStatus(\"success\");\n onSuccess(result);\n return result;\n } catch (thrown) {\n const failure = classifyPasskeyError(thrown, \"register\");\n setError(failure);\n setStatus(\"error\");\n onError(failure);\n return null;\n } finally {\n controller.current = null;\n }\n }, [client, getOptions, verify, onSuccess, onError]);\n\n const cancel = useCallback((): void => {\n controller.current?.abort();\n }, []);\n\n const reset = useCallback((): void => {\n setStatus(\"idle\");\n setError(null);\n setData(null);\n }, []);\n\n return { ...capabilities, register, status, error, data, cancel, reset };\n}\n\n/** Options for {@link usePasskeySignIn}. */\nexport interface UsePasskeySignInOptions<TResult> {\n /** Fetch server options — your `POST /webauthn/signin/begin`. */\n getOptions: () => Promise<PasskeyRequestOptionsJSON>;\n /** Send the assertion to be verified — your `POST /webauthn/signin/finish`. */\n verify: (assertion: PasskeyAuthenticationJSON) => Promise<TResult>;\n /** Client to use. Defaults to a shared `createPasskeyClient()`. */\n client?: PasskeyClient;\n /**\n * Arm the autofill flow on mount (`mediation: \"conditional\"`).\n *\n * The request stays pending, invisibly, until the user picks a passkey from\n * the browser's autofill list — so the field it should appear on needs\n * `autocomplete=\"username webauthn\"`. Only one conditional request may be live\n * per page, and it is aborted on unmount.\n */\n conditional?: boolean;\n /** Called with whatever `verify` resolved to. */\n onSuccess?: (result: TResult) => void;\n /** Called with the classified failure. */\n onError?: (error: PasskeyError) => void;\n}\n\n/** Value returned by {@link usePasskeySignIn}. */\nexport interface UsePasskeySignInResult<TResult> extends PasskeyCapabilities {\n /** Run the modal ceremony. Resolves `null` on failure. */\n signIn: () => Promise<TResult | null>;\n /** Current phase. */\n status: PasskeyStatus;\n /** Last failure, cleared when a new attempt starts. */\n error: PasskeyError | null;\n /** Whatever `verify` resolved to, on success. */\n data: TResult | null;\n /** True while an armed conditional (autofill) request is waiting. */\n conditionalPending: boolean;\n /** Close the browser sheet from your own UI. */\n cancel: () => void;\n /** Back to `\"idle\"`, clearing `error` and `data`. */\n reset: () => void;\n}\n\n/**\n * Sign in with a passkey, with or without the autofill flow.\n *\n * Two ways in, and a good login page wires both. `signIn()` is the explicit\n * button. `conditional: true` arms the pleasant one: the browser lists the user's\n * passkeys inside the username field itself, so signing in is one tap and no\n * password was ever typed. The field must carry\n * `autocomplete=\"username webauthn\"` or the list never appears — the hook cannot\n * do that part for you.\n *\n * A conditional request that is aborted (unmount, or the user typing a password\n * instead) is **not** surfaced as an error: nothing failed, the user chose\n * another door.\n *\n * @param options - Transport callbacks, `conditional`, optional client and listeners.\n * @returns Ceremony state, the `signIn` action, and the capability probes.\n *\n * @example\n * const passkey = usePasskeySignIn({\n * conditional: true,\n * getOptions: () => api.post(\"/webauthn/signin/begin\"),\n * verify: (assertion) => api.post(\"/webauthn/signin/finish\", { body: assertion }),\n * onSuccess: (session) => auth.setToken(session.access_token),\n * });\n *\n * <input name=\"email\" autoComplete=\"username webauthn\" />\n * <button onClick={() => void passkey.signIn()}>Entrar com passkey</button>\n */\nexport function usePasskeySignIn<TResult = unknown>(\n options: UsePasskeySignInOptions<TResult>,\n): UsePasskeySignInResult<TResult> {\n const client = useMemo(() => options.client ?? defaultClient(), [options.client]);\n const capabilities = usePasskeyCapabilities(client);\n const [status, setStatus] = useState<PasskeyStatus>(\"idle\");\n const [error, setError] = useState<PasskeyError | null>(null);\n const [data, setData] = useState<TResult | null>(null);\n const [conditionalPending, setConditionalPending] = useState(false);\n const controller = useRef<AbortController | null>(null);\n const conditional = options.conditional ?? false;\n\n const getOptions = useStableCallback(options.getOptions);\n const verify = useStableCallback(options.verify);\n const onSuccess = useStableCallback((result: TResult) => options.onSuccess?.(result));\n const onError = useStableCallback((failure: PasskeyError) => options.onError?.(failure));\n\n const run = useCallback(\n async (\n mediation: \"optional\" | \"conditional\",\n abort: AbortController = new AbortController(),\n ): Promise<TResult | null> => {\n controller.current = abort;\n if (mediation === \"optional\") {\n setStatus(\"prompting\");\n setError(null);\n }\n try {\n const serverOptions = await getOptions();\n const assertion = await client.authenticate(serverOptions, {\n mediation,\n signal: abort.signal,\n });\n setStatus(\"verifying\");\n const result = await verify(assertion);\n setData(result);\n setStatus(\"success\");\n onSuccess(result);\n return result;\n } catch (thrown) {\n const failure = classifyPasskeyError(thrown, \"authenticate\");\n if (failure.kind === \"aborted\" && mediation === \"conditional\") return null;\n setError(failure);\n setStatus(\"error\");\n onError(failure);\n return null;\n } finally {\n controller.current = null;\n }\n },\n [client, getOptions, verify, onSuccess, onError],\n );\n\n const signIn = useCallback(() => run(\"optional\"), [run]);\n\n useEffect(() => {\n if (!conditional) return;\n let cancelled = false;\n let abort: AbortController | null = null;\n\n async function arm(): Promise<void> {\n if (!(await client.isConditionalMediationAvailable())) return;\n if (cancelled) return;\n setConditionalPending(true);\n abort = new AbortController();\n await run(\"conditional\", abort);\n if (!cancelled) setConditionalPending(false);\n }\n\n void arm();\n\n return () => {\n cancelled = true;\n abort?.abort();\n };\n }, [conditional, client, run]);\n\n const cancel = useCallback((): void => {\n controller.current?.abort();\n }, []);\n\n const reset = useCallback((): void => {\n setStatus(\"idle\");\n setError(null);\n setData(null);\n }, []);\n\n return {\n ...capabilities,\n signIn,\n status,\n error,\n data,\n conditionalPending,\n cancel,\n reset,\n };\n}\n"],"mappings":"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"}
|
package/dist/br/BrazilMap.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const e=require("../utils/cn.cjs"),t=require("../geo/projection.cjs"),n=require("./locations.cjs"),r=require("./BrazilMap.module.cjs"),i=require("./MapMarkers.cjs"),a=require("./MapTooltip.cjs"),o=require("./regions.cjs"),s=require("./use-map-zoom.cjs"),c=require("./svg-utils.cjs"),l=require("./use-map-hover.cjs");let u=require("react"),d=require("react/jsx-runtime");function f({selected:n,onSelect:f,values:m,minColor:h=`#dbeafe`,maxColor:g=`#2563eb`,colorScale:_,colorByRegion:v=!1,height:y=440,padding:b=12,showLabels:x=!0,label:S=`Mapa do Brasil por estado`,loadingContent:C,showTooltip:w=!0,renderTooltip:T,markers:E,onMarkerClick:D,zoomable:O=!1,className:k,style:A,...j}){let M=(0,u.useRef)(null),[N,P]=(0,u.useState)(600),[F,I]=(0,u.useState)(null),{hover:L,onMove:R,onLeave:z}=l.useMapHover(M),B=s.useMapZoom(O);(0,u.useEffect)(()=>{let e=!0;return Promise.resolve().then(()=>require("./br-geo.cjs")).then(t=>{e&&I(t.BR_UF_GEOJSON)}),()=>{e=!1}},[]),(0,u.useEffect)(()=>{let e=M.current;if(!e||typeof ResizeObserver>`u`)return;let t=new ResizeObserver(e=>{let t=e[0]?.contentRect.width;t&&t>0&&P(t)});return t.observe(e),()=>t.disconnect()},[]);let V=(0,u.useMemo)(()=>n?new Set(Array.isArray(n)?n:[n]):new Set,[n]),H=(0,u.useMemo)(()=>{if(!m)return null;let e=Object.values(m).filter(e=>typeof e==`number`);return e.length===0?null:[Math.min(...e),Math.max(...e)]},[m]),U=(0,u.useMemo)(()=>{if(!F)return null;let e=t.fitProjection(c.geometriesBounds(F.features.map(e=>e.geometry)),N,y,{padding:b});return{projection:e,items:F.features.map(t=>({uf:t.properties.uf,name:t.properties.name,region:t.properties.region,d:c.geometryPath(t.geometry,e),centroid:c.geometryCentroid(t.geometry,e)}))}},[F,N,y,b]);function W(e){let t=m?.[e];if(typeof t!=`number`)return;if(_)return _(t);if(!H)return;let[n,r]=H
|
|
1
|
+
const e=require("../utils/cn.cjs"),t=require("../geo/projection.cjs"),n=require("./locations.cjs"),r=require("./BrazilMap.module.cjs"),i=require("./MapMarkers.cjs"),a=require("./MapTooltip.cjs"),o=require("./regions.cjs"),s=require("./use-map-zoom.cjs"),c=require("./svg-utils.cjs"),l=require("./use-map-hover.cjs");let u=require("react"),d=require("react/jsx-runtime");function f({selected:n,onSelect:f,values:m,minColor:h=`#dbeafe`,maxColor:g=`#2563eb`,colorScale:_,colorByRegion:v=!1,height:y=440,padding:b=12,showLabels:x=!0,label:S=`Mapa do Brasil por estado`,loadingContent:C,showTooltip:w=!0,renderTooltip:T,markers:E,onMarkerClick:D,zoomable:O=!1,className:k,style:A,...j}){let M=(0,u.useRef)(null),[N,P]=(0,u.useState)(600),[F,I]=(0,u.useState)(null),{hover:L,onMove:R,onLeave:z}=l.useMapHover(M),B=s.useMapZoom(O);(0,u.useEffect)(()=>{let e=!0;return Promise.resolve().then(()=>require("./br-geo.cjs")).then(t=>{e&&I(t.BR_UF_GEOJSON)}),()=>{e=!1}},[]),(0,u.useEffect)(()=>{let e=M.current;if(!e||typeof ResizeObserver>`u`)return;let t=new ResizeObserver(e=>{let t=e[0]?.contentRect.width;t&&t>0&&P(t)});return t.observe(e),()=>t.disconnect()},[]);let V=(0,u.useMemo)(()=>n?new Set(Array.isArray(n)?n:[n]):new Set,[n]),H=(0,u.useMemo)(()=>{if(!m)return null;let e=Object.values(m).filter(e=>typeof e==`number`);return e.length===0?null:[Math.min(...e),Math.max(...e)]},[m]),U=(0,u.useMemo)(()=>{if(!F)return null;let e=t.fitProjection(c.geometriesBounds(F.features.map(e=>e.geometry)),N,y,{padding:b});return{projection:e,items:F.features.map(t=>({uf:t.properties.uf,name:t.properties.name,region:t.properties.region,d:c.geometryPath(t.geometry,e),centroid:c.geometryCentroid(t.geometry,e)}))}},[F,N,y,b]);function W(e){let t=m?.[e];if(typeof t!=`number`)return;if(_)return _(t);if(!H)return;let[n,r]=H,i=r===n?1:(t-n)/(r-n);return c.lerpColor(h,g,i)}let G=(0,u.useMemo)(()=>U?U.items.map(t=>{let n=V.has(t.uf),i=n?void 0:v?o.REGION_COLORS[t.region]:W(t.uf);return(0,d.jsx)(`path`,{className:e.cn(r.default.state,n&&r.default.selected),d:t.d,fillRule:`evenodd`,style:i?{fill:i}:void 0,"data-uf":t.uf,tabIndex:f?0:void 0,role:f?`button`:void 0,"aria-label":t.name,"aria-pressed":f?n:void 0,onClick:f?()=>f(t.uf):void 0,onMouseMove:w?e=>R({uf:t.uf,name:t.name},e):void 0,onMouseLeave:w?z:void 0,onKeyDown:f?e=>{(e.key===`Enter`||e.key===` `)&&(e.preventDefault(),f(t.uf))}:void 0},t.uf)}):null,[U,V,m,_,v,h,g,f,w,R,z]);return(0,d.jsxs)(`div`,{ref:M,className:e.cn(r.default.map,k),style:{height:y,...A},role:`group`,"aria-label":S,...j,children:[U?(0,d.jsx)(`svg`,{className:r.default.svg,width:`100%`,height:y,viewBox:`0 0 ${N} ${y}`,preserveAspectRatio:`xMidYMid meet`,...B.handlers,children:(0,d.jsxs)(`g`,{transform:B.transform||void 0,children:[G,x&&U.items.map(e=>(0,d.jsx)(`text`,{className:r.default.label,x:e.centroid.x,y:e.centroid.y,textAnchor:`middle`,dominantBaseline:`central`,children:e.uf},`label-${e.uf}`)),E&&E.length>0&&(0,d.jsx)(i.MapMarkers,{projection:U.projection,markers:E,onMarkerClick:D})]})}):(0,d.jsx)(`div`,{className:r.default.loading,children:C??`Carregando mapa…`}),B.isTransformed&&(0,d.jsx)(`button`,{type:`button`,className:r.default.zoomReset,onClick:B.reset,children:`Reset`}),w&&L&&(0,d.jsx)(a.MapTooltip,{x:L.x,y:L.y,children:T?T({uf:L.item.uf,name:L.item.name,value:m?.[L.item.uf]}):(0,d.jsx)(p,{uf:L.item.uf,name:L.item.name,value:m?.[L.item.uf]})})]})}function p({uf:e,name:t,value:i}){let a=n.getState(e);return(0,d.jsxs)(d.Fragment,{children:[(0,d.jsxs)(`div`,{className:r.default.tooltipTitle,children:[t,` (`,e,`)`]}),(0,d.jsxs)(`div`,{className:r.default.tooltipMeta,children:[a?`${a.region} · ${a.cities.length} cidades`:e,typeof i==`number`?` · ${i}`:``]})]})}exports.BrazilMap=f;
|
|
2
2
|
//# sourceMappingURL=BrazilMap.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BrazilMap.cjs","names":[],"sources":["../../src/br/BrazilMap.tsx"],"sourcesContent":["/**\n * @tempest-limits file-lines, props-count, function-lines — the 17 props are five\n * independent axes of one map — choropleth (values, minColor, maxColor, colorScale,\n * colorByRegion), selection (selected, onSelect), overlay (markers, onMarkerClick,\n * zoomable), framing (height, padding, showLabels, label, loadingContent) and\n * tooltip (showTooltip, renderTooltip). None of them is mutually exclusive with\n * another, so there is no second component hiding here; the body is long for the\n * same reason it cannot be split, since the projected paths, the hit-testing and the\n * zoom transform all read the same viewBox state.\n */\nimport { useEffect, useMemo, useRef, useState, type HTMLAttributes, type ReactNode } from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport { fitProjection } from \"@/geo/projection\";\nimport type { GeoMarker } from \"@/geo/types\";\nimport type { BrUfFeatureCollection } from \"./br-geo\";\nimport { getState, type UF } from \"./locations\";\nimport { MapMarkers } from \"./MapMarkers\";\nimport { MapTooltip } from \"./MapTooltip\";\nimport { REGION_COLORS } from \"./regions\";\nimport type { ColorScale } from \"./scales\";\nimport { useMapZoom } from \"./use-map-zoom\";\nimport { geometriesBounds, geometryCentroid, geometryPath, lerpColor } from \"./svg-utils\";\nimport { useMapHover } from \"./use-map-hover\";\nimport styles from \"./BrazilMap.module.css\";\n\n/** Data passed to a {@link BrazilMapProps.renderTooltip} callback. */\nexport interface BrazilMapTooltipData {\n uf: UF;\n name: string;\n /** Choropleth value for this UF, if `values` was provided. */\n value?: number;\n}\n\nexport interface BrazilMapProps extends Omit<HTMLAttributes<HTMLDivElement>, \"onSelect\"> {\n /** Currently selected UF(s) — highlighted. Accepts one or many. */\n selected?: UF | readonly UF[] | null;\n /** Fired when a state is clicked. */\n onSelect?: (uf: UF) => void;\n /**\n * Optional choropleth values per UF. When set, each state is tinted between\n * `minColor` and `maxColor` by its value (linear). States without a value\n * use the base surface color.\n */\n values?: Partial<Record<UF, number>>;\n /** Choropleth low-end color (2-color linear ramp). Default: a light primary tint. */\n minColor?: string;\n /** Choropleth high-end color (2-color linear ramp). Default: the primary token. */\n maxColor?: string;\n /**\n * Custom value→color scale (e.g. from `sequentialScale`/`quantizeScale`).\n * Takes precedence over `minColor`/`maxColor`. Pair with a `<MapLegend>`.\n */\n colorScale?: ColorScale;\n /**\n * Tint each state by its macro-region (categorical). Overrides\n * `values`/`colorScale`. Pair with `<MapLegend items={regionLegendItems()} />`.\n */\n colorByRegion?: boolean;\n /** Viewport height in pixels. Default: `440`. */\n height?: number;\n /** Inner padding in pixels. Default: `12`. */\n padding?: number;\n /** Render the UF acronym at each state centroid. Default: `true`. */\n showLabels?: boolean;\n /** Accessible label for the map region. Default: `\"Mapa do Brasil por estado\"`. */\n label?: string;\n /** Custom content when the geometry is still loading. */\n loadingContent?: ReactNode;\n /** Show a floating tooltip (name + region + city count + value) on hover. Default: `true`. */\n showTooltip?: boolean;\n /** Override the default tooltip content. */\n renderTooltip?: (data: BrazilMapTooltipData) => ReactNode;\n /** Point markers to overlay on the map (e.g. capitals, stores). */\n markers?: readonly GeoMarker[];\n /** Fired when a marker is clicked. */\n onMarkerClick?: (marker: GeoMarker, index: number) => void;\n /** Enable wheel-zoom + drag-pan (double-click resets). Default: `false`. */\n zoomable?: boolean;\n}\n\n/**\n * Clickable choropleth map of Brazil's 27 federative units. Renders the bundled\n * simplified UF GeoJSON as SVG paths — **no external tiles or paid API**. Click\n * a state to fire `onSelect(uf)`; pass `selected` to highlight and `values` to\n * tint states by a metric.\n *\n * The GeoJSON (~36 KB gzip) is loaded lazily, so importing this component does\n * not pull the geometry until it actually mounts.\n *\n * @example\n * const [uf, setUf] = useState<UF | null>(null);\n * <BrazilMap selected={uf} onSelect={setUf} />\n *\n * @example\n * // Choropleth by a metric per state\n * <BrazilMap values={{ SP: 120, RJ: 90, MG: 60 }} />\n */\nexport function BrazilMap({\n selected,\n onSelect,\n values,\n minColor = \"#dbeafe\",\n maxColor = \"#2563eb\",\n colorScale,\n colorByRegion = false,\n height = 440,\n padding = 12,\n showLabels = true,\n label = \"Mapa do Brasil por estado\",\n loadingContent,\n showTooltip = true,\n renderTooltip,\n markers,\n onMarkerClick,\n zoomable = false,\n className,\n style,\n ...rest\n}: BrazilMapProps) {\n const containerRef = useRef<HTMLDivElement>(null);\n const [width, setWidth] = useState<number>(600);\n const [collection, setCollection] = useState<BrUfFeatureCollection | null>(null);\n const { hover, onMove, onLeave } = useMapHover<{ uf: UF; name: string }>(containerRef);\n const mapZoom = useMapZoom(zoomable);\n\n useEffect(() => {\n let active = true;\n void import(\"./br-geo\").then((m) => {\n if (active) setCollection(m.BR_UF_GEOJSON);\n });\n return () => {\n active = false;\n };\n }, []);\n\n useEffect(() => {\n const node = containerRef.current;\n if (!node || typeof ResizeObserver === \"undefined\") return;\n const observer = new ResizeObserver((entries) => {\n const measured = entries[0]?.contentRect.width;\n if (measured && measured > 0) setWidth(measured);\n });\n observer.observe(node);\n return () => observer.disconnect();\n }, []);\n\n const selectedSet = useMemo<Set<UF>>(() => {\n if (!selected) return new Set();\n return new Set(Array.isArray(selected) ? selected : [selected as UF]);\n }, [selected]);\n\n const valueRange = useMemo<[number, number] | null>(() => {\n if (!values) return null;\n const nums = Object.values(values).filter((v): v is number => typeof v === \"number\");\n if (nums.length === 0) return null;\n return [Math.min(...nums), Math.max(...nums)];\n }, [values]);\n\n const shapes = useMemo(() => {\n if (!collection) return null;\n const projection = fitProjection(\n geometriesBounds(collection.features.map((f) => f.geometry)),\n width,\n height,\n { padding },\n );\n const items = collection.features.map((feature) => ({\n uf: feature.properties.uf,\n name: feature.properties.name,\n region: feature.properties.region,\n d: geometryPath(feature.geometry, projection),\n centroid: geometryCentroid(feature.geometry, projection),\n }));\n return { projection, items };\n }, [collection, width, height, padding]);\n\n function fillFor(uf: UF): string | undefined {\n const v = values?.[uf];\n if (typeof v !== \"number\") return undefined;\n if (colorScale) return colorScale(v);\n if (!valueRange) return undefined;\n const [min, max] = valueRange;\n const t = max === min ? 1 : (v - min) / (max - min);\n return lerpColor(minColor, maxColor, t);\n }\n\n // Memoized so frequent re-renders (hover tooltip) don't rebuild all 27 paths.\n const stateEls = useMemo(() => {\n if (!shapes) return null;\n return shapes.items.map((shape) => {\n const isSelected = selectedSet.has(shape.uf);\n const fillColor = isSelected\n ? undefined\n : colorByRegion\n ? REGION_COLORS[shape.region]\n : fillFor(shape.uf);\n return (\n <path\n key={shape.uf}\n className={cn(styles.state, isSelected && styles.selected)}\n d={shape.d}\n fillRule=\"evenodd\"\n style={fillColor ? { fill: fillColor } : undefined}\n data-uf={shape.uf}\n tabIndex={onSelect ? 0 : undefined}\n role={onSelect ? \"button\" : undefined}\n aria-label={shape.name}\n aria-pressed={onSelect ? isSelected : undefined}\n onClick={onSelect ? () => onSelect(shape.uf) : undefined}\n onMouseMove={\n showTooltip\n ? (e) => onMove({ uf: shape.uf, name: shape.name }, e)\n : undefined\n }\n onMouseLeave={showTooltip ? onLeave : undefined}\n onKeyDown={\n onSelect\n ? (e) => {\n if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n onSelect(shape.uf);\n }\n }\n : undefined\n }\n />\n );\n });\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [\n shapes,\n selectedSet,\n values,\n colorScale,\n colorByRegion,\n minColor,\n maxColor,\n onSelect,\n showTooltip,\n onMove,\n onLeave,\n ]);\n\n return (\n <div\n ref={containerRef}\n className={cn(styles.map, className)}\n style={{ height, ...style }}\n role=\"group\"\n aria-label={label}\n {...rest}\n >\n {!shapes ? (\n <div className={styles.loading}>{loadingContent ?? \"Carregando mapa…\"}</div>\n ) : (\n <svg\n className={styles.svg}\n width=\"100%\"\n height={height}\n viewBox={`0 0 ${width} ${height}`}\n preserveAspectRatio=\"xMidYMid meet\"\n {...mapZoom.handlers}\n >\n <g transform={mapZoom.transform || undefined}>\n {stateEls}\n\n {showLabels &&\n shapes.items.map((shape) => (\n <text\n key={`label-${shape.uf}`}\n className={styles.label}\n x={shape.centroid.x}\n y={shape.centroid.y}\n textAnchor=\"middle\"\n dominantBaseline=\"central\"\n >\n {shape.uf}\n </text>\n ))}\n\n {markers && markers.length > 0 && (\n <MapMarkers\n projection={shapes.projection}\n markers={markers}\n onMarkerClick={onMarkerClick}\n />\n )}\n </g>\n </svg>\n )}\n\n {mapZoom.isTransformed && (\n <button type=\"button\" className={styles.zoomReset} onClick={mapZoom.reset}>\n Reset\n </button>\n )}\n\n {showTooltip && hover && (\n <MapTooltip x={hover.x} y={hover.y}>\n {renderTooltip ? (\n renderTooltip({\n uf: hover.item.uf,\n name: hover.item.name,\n value: values?.[hover.item.uf],\n })\n ) : (\n <DefaultUfTooltip\n uf={hover.item.uf}\n name={hover.item.name}\n value={values?.[hover.item.uf]}\n />\n )}\n </MapTooltip>\n )}\n </div>\n );\n}\n\n/** Default UF tooltip: name (UF) + region + city count + optional value. */\nfunction DefaultUfTooltip({ uf, name, value }: { uf: UF; name: string; value?: number }) {\n const state = getState(uf);\n return (\n <>\n <div className={styles.tooltipTitle}>\n {name} ({uf})\n </div>\n <div className={styles.tooltipMeta}>\n {state ? `${state.region} · ${state.cities.length} cidades` : uf}\n {typeof value === \"number\" ? ` · ${value}` : \"\"}\n </div>\n </>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"BrazilMap.cjs","names":[],"sources":["../../src/br/BrazilMap.tsx"],"sourcesContent":["/**\n * @tempest-limits file-lines, props-count, function-lines — the 17 props are five\n * independent axes of one map — choropleth (values, minColor, maxColor, colorScale,\n * colorByRegion), selection (selected, onSelect), overlay (markers, onMarkerClick,\n * zoomable), framing (height, padding, showLabels, label, loadingContent) and\n * tooltip (showTooltip, renderTooltip). None of them is mutually exclusive with\n * another, so there is no second component hiding here; the body is long for the\n * same reason it cannot be split, since the projected paths, the hit-testing and the\n * zoom transform all read the same viewBox state.\n */\nimport { useEffect, useMemo, useRef, useState, type HTMLAttributes, type ReactNode } from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport { fitProjection } from \"@/geo/projection\";\nimport type { GeoMarker } from \"@/geo/types\";\nimport type { BrUfFeatureCollection } from \"./br-geo\";\nimport { getState, type UF } from \"./locations\";\nimport { MapMarkers } from \"./MapMarkers\";\nimport { MapTooltip } from \"./MapTooltip\";\nimport { REGION_COLORS } from \"./regions\";\nimport type { ColorScale } from \"./scales\";\nimport { useMapZoom } from \"./use-map-zoom\";\nimport { geometriesBounds, geometryCentroid, geometryPath, lerpColor } from \"./svg-utils\";\nimport { useMapHover } from \"./use-map-hover\";\nimport styles from \"./BrazilMap.module.css\";\n\n/** Data passed to a {@link BrazilMapProps.renderTooltip} callback. */\nexport interface BrazilMapTooltipData {\n uf: UF;\n name: string;\n /** Choropleth value for this UF, if `values` was provided. */\n value?: number;\n}\n\nexport interface BrazilMapProps extends Omit<HTMLAttributes<HTMLDivElement>, \"onSelect\"> {\n /** Currently selected UF(s) — highlighted. Accepts one or many. */\n selected?: UF | readonly UF[] | null;\n /** Fired when a state is clicked. */\n onSelect?: (uf: UF) => void;\n /**\n * Optional choropleth values per UF. When set, each state is tinted between\n * `minColor` and `maxColor` by its value (linear). States without a value\n * use the base surface color.\n */\n values?: Partial<Record<UF, number>>;\n /** Choropleth low-end color (2-color linear ramp). Default: a light primary tint. */\n minColor?: string;\n /** Choropleth high-end color (2-color linear ramp). Default: the primary token. */\n maxColor?: string;\n /**\n * Custom value→color scale (e.g. from `sequentialScale`/`quantizeScale`).\n * Takes precedence over `minColor`/`maxColor`. Pair with a `<MapLegend>`.\n */\n colorScale?: ColorScale;\n /**\n * Tint each state by its macro-region (categorical). Overrides\n * `values`/`colorScale`. Pair with `<MapLegend items={regionLegendItems()} />`.\n */\n colorByRegion?: boolean;\n /** Viewport height in pixels. Default: `440`. */\n height?: number;\n /** Inner padding in pixels. Default: `12`. */\n padding?: number;\n /** Render the UF acronym at each state centroid. Default: `true`. */\n showLabels?: boolean;\n /** Accessible label for the map region. Default: `\"Mapa do Brasil por estado\"`. */\n label?: string;\n /** Custom content when the geometry is still loading. */\n loadingContent?: ReactNode;\n /** Show a floating tooltip (name + region + city count + value) on hover. Default: `true`. */\n showTooltip?: boolean;\n /** Override the default tooltip content. */\n renderTooltip?: (data: BrazilMapTooltipData) => ReactNode;\n /** Point markers to overlay on the map (e.g. capitals, stores). */\n markers?: readonly GeoMarker[];\n /** Fired when a marker is clicked. */\n onMarkerClick?: (marker: GeoMarker, index: number) => void;\n /** Enable wheel-zoom + drag-pan (double-click resets). Default: `false`. */\n zoomable?: boolean;\n}\n\n/**\n * Clickable choropleth map of Brazil's 27 federative units. Renders the bundled\n * simplified UF GeoJSON as SVG paths — **no external tiles or paid API**. Click\n * a state to fire `onSelect(uf)`; pass `selected` to highlight and `values` to\n * tint states by a metric.\n *\n * The GeoJSON (~36 KB gzip) is loaded lazily, so importing this component does\n * not pull the geometry until it actually mounts.\n *\n * @example\n * const [uf, setUf] = useState<UF | null>(null);\n * <BrazilMap selected={uf} onSelect={setUf} />\n *\n * @example\n * // Choropleth by a metric per state\n * <BrazilMap values={{ SP: 120, RJ: 90, MG: 60 }} />\n */\nexport function BrazilMap({\n selected,\n onSelect,\n values,\n minColor = \"#dbeafe\",\n maxColor = \"#2563eb\",\n colorScale,\n colorByRegion = false,\n height = 440,\n padding = 12,\n showLabels = true,\n label = \"Mapa do Brasil por estado\",\n loadingContent,\n showTooltip = true,\n renderTooltip,\n markers,\n onMarkerClick,\n zoomable = false,\n className,\n style,\n ...rest\n}: BrazilMapProps) {\n const containerRef = useRef<HTMLDivElement>(null);\n const [width, setWidth] = useState<number>(600);\n const [collection, setCollection] = useState<BrUfFeatureCollection | null>(null);\n const { hover, onMove, onLeave } = useMapHover<{ uf: UF; name: string }>(containerRef);\n const mapZoom = useMapZoom(zoomable);\n\n useEffect(() => {\n let active = true;\n void import(\"./br-geo\").then((m) => {\n if (active) setCollection(m.BR_UF_GEOJSON);\n });\n return () => {\n active = false;\n };\n }, []);\n\n useEffect(() => {\n const node = containerRef.current;\n if (!node || typeof ResizeObserver === \"undefined\") return;\n const observer = new ResizeObserver((entries) => {\n const measured = entries[0]?.contentRect.width;\n if (measured && measured > 0) setWidth(measured);\n });\n observer.observe(node);\n return () => observer.disconnect();\n }, []);\n\n const selectedSet = useMemo<Set<UF>>(() => {\n if (!selected) return new Set();\n return new Set(Array.isArray(selected) ? selected : [selected as UF]);\n }, [selected]);\n\n const valueRange = useMemo<[number, number] | null>(() => {\n if (!values) return null;\n const nums = Object.values(values).filter((v): v is number => typeof v === \"number\");\n if (nums.length === 0) return null;\n return [Math.min(...nums), Math.max(...nums)];\n }, [values]);\n\n const shapes = useMemo(() => {\n if (!collection) return null;\n const projection = fitProjection(\n geometriesBounds(collection.features.map((f) => f.geometry)),\n width,\n height,\n { padding },\n );\n const items = collection.features.map((feature) => ({\n uf: feature.properties.uf,\n name: feature.properties.name,\n region: feature.properties.region,\n d: geometryPath(feature.geometry, projection),\n centroid: geometryCentroid(feature.geometry, projection),\n }));\n return { projection, items };\n }, [collection, width, height, padding]);\n\n function fillFor(uf: UF): string | undefined {\n const v = values?.[uf];\n if (typeof v !== \"number\") return undefined;\n if (colorScale) return colorScale(v);\n if (!valueRange) return undefined;\n const [min, max] = valueRange;\n const t = max === min ? 1 : (v - min) / (max - min);\n return lerpColor(minColor, maxColor, t);\n }\n\n // Memoized so frequent re-renders (hover tooltip) don't rebuild all 27 paths.\n const stateEls = useMemo(() => {\n if (!shapes) return null;\n return shapes.items.map((shape) => {\n const isSelected = selectedSet.has(shape.uf);\n const fillColor = isSelected\n ? undefined\n : colorByRegion\n ? REGION_COLORS[shape.region]\n : fillFor(shape.uf);\n return (\n <path\n key={shape.uf}\n className={cn(styles.state, isSelected && styles.selected)}\n d={shape.d}\n fillRule=\"evenodd\"\n style={fillColor ? { fill: fillColor } : undefined}\n data-uf={shape.uf}\n tabIndex={onSelect ? 0 : undefined}\n role={onSelect ? \"button\" : undefined}\n aria-label={shape.name}\n aria-pressed={onSelect ? isSelected : undefined}\n onClick={onSelect ? () => onSelect(shape.uf) : undefined}\n onMouseMove={\n showTooltip\n ? (e) => onMove({ uf: shape.uf, name: shape.name }, e)\n : undefined\n }\n onMouseLeave={showTooltip ? onLeave : undefined}\n onKeyDown={\n onSelect\n ? (e) => {\n if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n onSelect(shape.uf);\n }\n }\n : undefined\n }\n />\n );\n });\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [\n shapes,\n selectedSet,\n values,\n colorScale,\n colorByRegion,\n minColor,\n maxColor,\n onSelect,\n showTooltip,\n onMove,\n onLeave,\n ]);\n\n return (\n <div\n ref={containerRef}\n className={cn(styles.map, className)}\n style={{ height, ...style }}\n role=\"group\"\n aria-label={label}\n {...rest}\n >\n {!shapes ? (\n <div className={styles.loading}>{loadingContent ?? \"Carregando mapa…\"}</div>\n ) : (\n <svg\n className={styles.svg}\n width=\"100%\"\n height={height}\n viewBox={`0 0 ${width} ${height}`}\n preserveAspectRatio=\"xMidYMid meet\"\n {...mapZoom.handlers}\n >\n <g transform={mapZoom.transform || undefined}>\n {stateEls}\n\n {showLabels &&\n shapes.items.map((shape) => (\n <text\n key={`label-${shape.uf}`}\n className={styles.label}\n x={shape.centroid.x}\n y={shape.centroid.y}\n textAnchor=\"middle\"\n dominantBaseline=\"central\"\n >\n {shape.uf}\n </text>\n ))}\n\n {markers && markers.length > 0 && (\n <MapMarkers\n projection={shapes.projection}\n markers={markers}\n onMarkerClick={onMarkerClick}\n />\n )}\n </g>\n </svg>\n )}\n\n {mapZoom.isTransformed && (\n <button type=\"button\" className={styles.zoomReset} onClick={mapZoom.reset}>\n Reset\n </button>\n )}\n\n {showTooltip && hover && (\n <MapTooltip x={hover.x} y={hover.y}>\n {renderTooltip ? (\n renderTooltip({\n uf: hover.item.uf,\n name: hover.item.name,\n value: values?.[hover.item.uf],\n })\n ) : (\n <DefaultUfTooltip\n uf={hover.item.uf}\n name={hover.item.name}\n value={values?.[hover.item.uf]}\n />\n )}\n </MapTooltip>\n )}\n </div>\n );\n}\n\n/** Default UF tooltip: name (UF) + region + city count + optional value. */\nfunction DefaultUfTooltip({ uf, name, value }: { uf: UF; name: string; value?: number }) {\n const state = getState(uf);\n return (\n <>\n <div className={styles.tooltipTitle}>\n {name} ({uf})\n </div>\n <div className={styles.tooltipMeta}>\n {state ? `${state.region} · ${state.cities.length} cidades` : uf}\n {typeof value === \"number\" ? ` · ${value}` : \"\"}\n </div>\n </>\n );\n}\n"],"mappings":"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"}
|
package/dist/br/BrazilMap.js
CHANGED
|
@@ -57,8 +57,8 @@ function b({ selected: n, onSelect: _, values: b, minColor: S = "#dbeafe", maxCo
|
|
|
57
57
|
if (typeof t != "number") return;
|
|
58
58
|
if (w) return w(t);
|
|
59
59
|
if (!Y) return;
|
|
60
|
-
let [n, r] = Y;
|
|
61
|
-
return d(S, C,
|
|
60
|
+
let [n, r] = Y, i = r === n ? 1 : (t - n) / (r - n);
|
|
61
|
+
return d(S, C, i);
|
|
62
62
|
}
|
|
63
63
|
let Q = m(() => X ? X.items.map((t) => {
|
|
64
64
|
let n = J.has(t.uf), i = n ? void 0 : T ? o[t.region] : Z(t.uf);
|
package/dist/br/BrazilMap.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BrazilMap.js","names":[],"sources":["../../src/br/BrazilMap.tsx"],"sourcesContent":["/**\n * @tempest-limits file-lines, props-count, function-lines — the 17 props are five\n * independent axes of one map — choropleth (values, minColor, maxColor, colorScale,\n * colorByRegion), selection (selected, onSelect), overlay (markers, onMarkerClick,\n * zoomable), framing (height, padding, showLabels, label, loadingContent) and\n * tooltip (showTooltip, renderTooltip). None of them is mutually exclusive with\n * another, so there is no second component hiding here; the body is long for the\n * same reason it cannot be split, since the projected paths, the hit-testing and the\n * zoom transform all read the same viewBox state.\n */\nimport { useEffect, useMemo, useRef, useState, type HTMLAttributes, type ReactNode } from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport { fitProjection } from \"@/geo/projection\";\nimport type { GeoMarker } from \"@/geo/types\";\nimport type { BrUfFeatureCollection } from \"./br-geo\";\nimport { getState, type UF } from \"./locations\";\nimport { MapMarkers } from \"./MapMarkers\";\nimport { MapTooltip } from \"./MapTooltip\";\nimport { REGION_COLORS } from \"./regions\";\nimport type { ColorScale } from \"./scales\";\nimport { useMapZoom } from \"./use-map-zoom\";\nimport { geometriesBounds, geometryCentroid, geometryPath, lerpColor } from \"./svg-utils\";\nimport { useMapHover } from \"./use-map-hover\";\nimport styles from \"./BrazilMap.module.css\";\n\n/** Data passed to a {@link BrazilMapProps.renderTooltip} callback. */\nexport interface BrazilMapTooltipData {\n uf: UF;\n name: string;\n /** Choropleth value for this UF, if `values` was provided. */\n value?: number;\n}\n\nexport interface BrazilMapProps extends Omit<HTMLAttributes<HTMLDivElement>, \"onSelect\"> {\n /** Currently selected UF(s) — highlighted. Accepts one or many. */\n selected?: UF | readonly UF[] | null;\n /** Fired when a state is clicked. */\n onSelect?: (uf: UF) => void;\n /**\n * Optional choropleth values per UF. When set, each state is tinted between\n * `minColor` and `maxColor` by its value (linear). States without a value\n * use the base surface color.\n */\n values?: Partial<Record<UF, number>>;\n /** Choropleth low-end color (2-color linear ramp). Default: a light primary tint. */\n minColor?: string;\n /** Choropleth high-end color (2-color linear ramp). Default: the primary token. */\n maxColor?: string;\n /**\n * Custom value→color scale (e.g. from `sequentialScale`/`quantizeScale`).\n * Takes precedence over `minColor`/`maxColor`. Pair with a `<MapLegend>`.\n */\n colorScale?: ColorScale;\n /**\n * Tint each state by its macro-region (categorical). Overrides\n * `values`/`colorScale`. Pair with `<MapLegend items={regionLegendItems()} />`.\n */\n colorByRegion?: boolean;\n /** Viewport height in pixels. Default: `440`. */\n height?: number;\n /** Inner padding in pixels. Default: `12`. */\n padding?: number;\n /** Render the UF acronym at each state centroid. Default: `true`. */\n showLabels?: boolean;\n /** Accessible label for the map region. Default: `\"Mapa do Brasil por estado\"`. */\n label?: string;\n /** Custom content when the geometry is still loading. */\n loadingContent?: ReactNode;\n /** Show a floating tooltip (name + region + city count + value) on hover. Default: `true`. */\n showTooltip?: boolean;\n /** Override the default tooltip content. */\n renderTooltip?: (data: BrazilMapTooltipData) => ReactNode;\n /** Point markers to overlay on the map (e.g. capitals, stores). */\n markers?: readonly GeoMarker[];\n /** Fired when a marker is clicked. */\n onMarkerClick?: (marker: GeoMarker, index: number) => void;\n /** Enable wheel-zoom + drag-pan (double-click resets). Default: `false`. */\n zoomable?: boolean;\n}\n\n/**\n * Clickable choropleth map of Brazil's 27 federative units. Renders the bundled\n * simplified UF GeoJSON as SVG paths — **no external tiles or paid API**. Click\n * a state to fire `onSelect(uf)`; pass `selected` to highlight and `values` to\n * tint states by a metric.\n *\n * The GeoJSON (~36 KB gzip) is loaded lazily, so importing this component does\n * not pull the geometry until it actually mounts.\n *\n * @example\n * const [uf, setUf] = useState<UF | null>(null);\n * <BrazilMap selected={uf} onSelect={setUf} />\n *\n * @example\n * // Choropleth by a metric per state\n * <BrazilMap values={{ SP: 120, RJ: 90, MG: 60 }} />\n */\nexport function BrazilMap({\n selected,\n onSelect,\n values,\n minColor = \"#dbeafe\",\n maxColor = \"#2563eb\",\n colorScale,\n colorByRegion = false,\n height = 440,\n padding = 12,\n showLabels = true,\n label = \"Mapa do Brasil por estado\",\n loadingContent,\n showTooltip = true,\n renderTooltip,\n markers,\n onMarkerClick,\n zoomable = false,\n className,\n style,\n ...rest\n}: BrazilMapProps) {\n const containerRef = useRef<HTMLDivElement>(null);\n const [width, setWidth] = useState<number>(600);\n const [collection, setCollection] = useState<BrUfFeatureCollection | null>(null);\n const { hover, onMove, onLeave } = useMapHover<{ uf: UF; name: string }>(containerRef);\n const mapZoom = useMapZoom(zoomable);\n\n useEffect(() => {\n let active = true;\n void import(\"./br-geo\").then((m) => {\n if (active) setCollection(m.BR_UF_GEOJSON);\n });\n return () => {\n active = false;\n };\n }, []);\n\n useEffect(() => {\n const node = containerRef.current;\n if (!node || typeof ResizeObserver === \"undefined\") return;\n const observer = new ResizeObserver((entries) => {\n const measured = entries[0]?.contentRect.width;\n if (measured && measured > 0) setWidth(measured);\n });\n observer.observe(node);\n return () => observer.disconnect();\n }, []);\n\n const selectedSet = useMemo<Set<UF>>(() => {\n if (!selected) return new Set();\n return new Set(Array.isArray(selected) ? selected : [selected as UF]);\n }, [selected]);\n\n const valueRange = useMemo<[number, number] | null>(() => {\n if (!values) return null;\n const nums = Object.values(values).filter((v): v is number => typeof v === \"number\");\n if (nums.length === 0) return null;\n return [Math.min(...nums), Math.max(...nums)];\n }, [values]);\n\n const shapes = useMemo(() => {\n if (!collection) return null;\n const projection = fitProjection(\n geometriesBounds(collection.features.map((f) => f.geometry)),\n width,\n height,\n { padding },\n );\n const items = collection.features.map((feature) => ({\n uf: feature.properties.uf,\n name: feature.properties.name,\n region: feature.properties.region,\n d: geometryPath(feature.geometry, projection),\n centroid: geometryCentroid(feature.geometry, projection),\n }));\n return { projection, items };\n }, [collection, width, height, padding]);\n\n function fillFor(uf: UF): string | undefined {\n const v = values?.[uf];\n if (typeof v !== \"number\") return undefined;\n if (colorScale) return colorScale(v);\n if (!valueRange) return undefined;\n const [min, max] = valueRange;\n const t = max === min ? 1 : (v - min) / (max - min);\n return lerpColor(minColor, maxColor, t);\n }\n\n // Memoized so frequent re-renders (hover tooltip) don't rebuild all 27 paths.\n const stateEls = useMemo(() => {\n if (!shapes) return null;\n return shapes.items.map((shape) => {\n const isSelected = selectedSet.has(shape.uf);\n const fillColor = isSelected\n ? undefined\n : colorByRegion\n ? REGION_COLORS[shape.region]\n : fillFor(shape.uf);\n return (\n <path\n key={shape.uf}\n className={cn(styles.state, isSelected && styles.selected)}\n d={shape.d}\n fillRule=\"evenodd\"\n style={fillColor ? { fill: fillColor } : undefined}\n data-uf={shape.uf}\n tabIndex={onSelect ? 0 : undefined}\n role={onSelect ? \"button\" : undefined}\n aria-label={shape.name}\n aria-pressed={onSelect ? isSelected : undefined}\n onClick={onSelect ? () => onSelect(shape.uf) : undefined}\n onMouseMove={\n showTooltip\n ? (e) => onMove({ uf: shape.uf, name: shape.name }, e)\n : undefined\n }\n onMouseLeave={showTooltip ? onLeave : undefined}\n onKeyDown={\n onSelect\n ? (e) => {\n if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n onSelect(shape.uf);\n }\n }\n : undefined\n }\n />\n );\n });\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [\n shapes,\n selectedSet,\n values,\n colorScale,\n colorByRegion,\n minColor,\n maxColor,\n onSelect,\n showTooltip,\n onMove,\n onLeave,\n ]);\n\n return (\n <div\n ref={containerRef}\n className={cn(styles.map, className)}\n style={{ height, ...style }}\n role=\"group\"\n aria-label={label}\n {...rest}\n >\n {!shapes ? (\n <div className={styles.loading}>{loadingContent ?? \"Carregando mapa…\"}</div>\n ) : (\n <svg\n className={styles.svg}\n width=\"100%\"\n height={height}\n viewBox={`0 0 ${width} ${height}`}\n preserveAspectRatio=\"xMidYMid meet\"\n {...mapZoom.handlers}\n >\n <g transform={mapZoom.transform || undefined}>\n {stateEls}\n\n {showLabels &&\n shapes.items.map((shape) => (\n <text\n key={`label-${shape.uf}`}\n className={styles.label}\n x={shape.centroid.x}\n y={shape.centroid.y}\n textAnchor=\"middle\"\n dominantBaseline=\"central\"\n >\n {shape.uf}\n </text>\n ))}\n\n {markers && markers.length > 0 && (\n <MapMarkers\n projection={shapes.projection}\n markers={markers}\n onMarkerClick={onMarkerClick}\n />\n )}\n </g>\n </svg>\n )}\n\n {mapZoom.isTransformed && (\n <button type=\"button\" className={styles.zoomReset} onClick={mapZoom.reset}>\n Reset\n </button>\n )}\n\n {showTooltip && hover && (\n <MapTooltip x={hover.x} y={hover.y}>\n {renderTooltip ? (\n renderTooltip({\n uf: hover.item.uf,\n name: hover.item.name,\n value: values?.[hover.item.uf],\n })\n ) : (\n <DefaultUfTooltip\n uf={hover.item.uf}\n name={hover.item.name}\n value={values?.[hover.item.uf]}\n />\n )}\n </MapTooltip>\n )}\n </div>\n );\n}\n\n/** Default UF tooltip: name (UF) + region + city count + optional value. */\nfunction DefaultUfTooltip({ uf, name, value }: { uf: UF; name: string; value?: number }) {\n const state = getState(uf);\n return (\n <>\n <div className={styles.tooltipTitle}>\n {name} ({uf})\n </div>\n <div className={styles.tooltipMeta}>\n {state ? `${state.region} · ${state.cities.length} cidades` : uf}\n {typeof value === \"number\" ? ` · ${value}` : \"\"}\n </div>\n </>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"BrazilMap.js","names":[],"sources":["../../src/br/BrazilMap.tsx"],"sourcesContent":["/**\n * @tempest-limits file-lines, props-count, function-lines — the 17 props are five\n * independent axes of one map — choropleth (values, minColor, maxColor, colorScale,\n * colorByRegion), selection (selected, onSelect), overlay (markers, onMarkerClick,\n * zoomable), framing (height, padding, showLabels, label, loadingContent) and\n * tooltip (showTooltip, renderTooltip). None of them is mutually exclusive with\n * another, so there is no second component hiding here; the body is long for the\n * same reason it cannot be split, since the projected paths, the hit-testing and the\n * zoom transform all read the same viewBox state.\n */\nimport { useEffect, useMemo, useRef, useState, type HTMLAttributes, type ReactNode } from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport { fitProjection } from \"@/geo/projection\";\nimport type { GeoMarker } from \"@/geo/types\";\nimport type { BrUfFeatureCollection } from \"./br-geo\";\nimport { getState, type UF } from \"./locations\";\nimport { MapMarkers } from \"./MapMarkers\";\nimport { MapTooltip } from \"./MapTooltip\";\nimport { REGION_COLORS } from \"./regions\";\nimport type { ColorScale } from \"./scales\";\nimport { useMapZoom } from \"./use-map-zoom\";\nimport { geometriesBounds, geometryCentroid, geometryPath, lerpColor } from \"./svg-utils\";\nimport { useMapHover } from \"./use-map-hover\";\nimport styles from \"./BrazilMap.module.css\";\n\n/** Data passed to a {@link BrazilMapProps.renderTooltip} callback. */\nexport interface BrazilMapTooltipData {\n uf: UF;\n name: string;\n /** Choropleth value for this UF, if `values` was provided. */\n value?: number;\n}\n\nexport interface BrazilMapProps extends Omit<HTMLAttributes<HTMLDivElement>, \"onSelect\"> {\n /** Currently selected UF(s) — highlighted. Accepts one or many. */\n selected?: UF | readonly UF[] | null;\n /** Fired when a state is clicked. */\n onSelect?: (uf: UF) => void;\n /**\n * Optional choropleth values per UF. When set, each state is tinted between\n * `minColor` and `maxColor` by its value (linear). States without a value\n * use the base surface color.\n */\n values?: Partial<Record<UF, number>>;\n /** Choropleth low-end color (2-color linear ramp). Default: a light primary tint. */\n minColor?: string;\n /** Choropleth high-end color (2-color linear ramp). Default: the primary token. */\n maxColor?: string;\n /**\n * Custom value→color scale (e.g. from `sequentialScale`/`quantizeScale`).\n * Takes precedence over `minColor`/`maxColor`. Pair with a `<MapLegend>`.\n */\n colorScale?: ColorScale;\n /**\n * Tint each state by its macro-region (categorical). Overrides\n * `values`/`colorScale`. Pair with `<MapLegend items={regionLegendItems()} />`.\n */\n colorByRegion?: boolean;\n /** Viewport height in pixels. Default: `440`. */\n height?: number;\n /** Inner padding in pixels. Default: `12`. */\n padding?: number;\n /** Render the UF acronym at each state centroid. Default: `true`. */\n showLabels?: boolean;\n /** Accessible label for the map region. Default: `\"Mapa do Brasil por estado\"`. */\n label?: string;\n /** Custom content when the geometry is still loading. */\n loadingContent?: ReactNode;\n /** Show a floating tooltip (name + region + city count + value) on hover. Default: `true`. */\n showTooltip?: boolean;\n /** Override the default tooltip content. */\n renderTooltip?: (data: BrazilMapTooltipData) => ReactNode;\n /** Point markers to overlay on the map (e.g. capitals, stores). */\n markers?: readonly GeoMarker[];\n /** Fired when a marker is clicked. */\n onMarkerClick?: (marker: GeoMarker, index: number) => void;\n /** Enable wheel-zoom + drag-pan (double-click resets). Default: `false`. */\n zoomable?: boolean;\n}\n\n/**\n * Clickable choropleth map of Brazil's 27 federative units. Renders the bundled\n * simplified UF GeoJSON as SVG paths — **no external tiles or paid API**. Click\n * a state to fire `onSelect(uf)`; pass `selected` to highlight and `values` to\n * tint states by a metric.\n *\n * The GeoJSON (~36 KB gzip) is loaded lazily, so importing this component does\n * not pull the geometry until it actually mounts.\n *\n * @example\n * const [uf, setUf] = useState<UF | null>(null);\n * <BrazilMap selected={uf} onSelect={setUf} />\n *\n * @example\n * // Choropleth by a metric per state\n * <BrazilMap values={{ SP: 120, RJ: 90, MG: 60 }} />\n */\nexport function BrazilMap({\n selected,\n onSelect,\n values,\n minColor = \"#dbeafe\",\n maxColor = \"#2563eb\",\n colorScale,\n colorByRegion = false,\n height = 440,\n padding = 12,\n showLabels = true,\n label = \"Mapa do Brasil por estado\",\n loadingContent,\n showTooltip = true,\n renderTooltip,\n markers,\n onMarkerClick,\n zoomable = false,\n className,\n style,\n ...rest\n}: BrazilMapProps) {\n const containerRef = useRef<HTMLDivElement>(null);\n const [width, setWidth] = useState<number>(600);\n const [collection, setCollection] = useState<BrUfFeatureCollection | null>(null);\n const { hover, onMove, onLeave } = useMapHover<{ uf: UF; name: string }>(containerRef);\n const mapZoom = useMapZoom(zoomable);\n\n useEffect(() => {\n let active = true;\n void import(\"./br-geo\").then((m) => {\n if (active) setCollection(m.BR_UF_GEOJSON);\n });\n return () => {\n active = false;\n };\n }, []);\n\n useEffect(() => {\n const node = containerRef.current;\n if (!node || typeof ResizeObserver === \"undefined\") return;\n const observer = new ResizeObserver((entries) => {\n const measured = entries[0]?.contentRect.width;\n if (measured && measured > 0) setWidth(measured);\n });\n observer.observe(node);\n return () => observer.disconnect();\n }, []);\n\n const selectedSet = useMemo<Set<UF>>(() => {\n if (!selected) return new Set();\n return new Set(Array.isArray(selected) ? selected : [selected as UF]);\n }, [selected]);\n\n const valueRange = useMemo<[number, number] | null>(() => {\n if (!values) return null;\n const nums = Object.values(values).filter((v): v is number => typeof v === \"number\");\n if (nums.length === 0) return null;\n return [Math.min(...nums), Math.max(...nums)];\n }, [values]);\n\n const shapes = useMemo(() => {\n if (!collection) return null;\n const projection = fitProjection(\n geometriesBounds(collection.features.map((f) => f.geometry)),\n width,\n height,\n { padding },\n );\n const items = collection.features.map((feature) => ({\n uf: feature.properties.uf,\n name: feature.properties.name,\n region: feature.properties.region,\n d: geometryPath(feature.geometry, projection),\n centroid: geometryCentroid(feature.geometry, projection),\n }));\n return { projection, items };\n }, [collection, width, height, padding]);\n\n function fillFor(uf: UF): string | undefined {\n const v = values?.[uf];\n if (typeof v !== \"number\") return undefined;\n if (colorScale) return colorScale(v);\n if (!valueRange) return undefined;\n const [min, max] = valueRange;\n const t = max === min ? 1 : (v - min) / (max - min);\n return lerpColor(minColor, maxColor, t);\n }\n\n // Memoized so frequent re-renders (hover tooltip) don't rebuild all 27 paths.\n const stateEls = useMemo(() => {\n if (!shapes) return null;\n return shapes.items.map((shape) => {\n const isSelected = selectedSet.has(shape.uf);\n const fillColor = isSelected\n ? undefined\n : colorByRegion\n ? REGION_COLORS[shape.region]\n : fillFor(shape.uf);\n return (\n <path\n key={shape.uf}\n className={cn(styles.state, isSelected && styles.selected)}\n d={shape.d}\n fillRule=\"evenodd\"\n style={fillColor ? { fill: fillColor } : undefined}\n data-uf={shape.uf}\n tabIndex={onSelect ? 0 : undefined}\n role={onSelect ? \"button\" : undefined}\n aria-label={shape.name}\n aria-pressed={onSelect ? isSelected : undefined}\n onClick={onSelect ? () => onSelect(shape.uf) : undefined}\n onMouseMove={\n showTooltip\n ? (e) => onMove({ uf: shape.uf, name: shape.name }, e)\n : undefined\n }\n onMouseLeave={showTooltip ? onLeave : undefined}\n onKeyDown={\n onSelect\n ? (e) => {\n if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n onSelect(shape.uf);\n }\n }\n : undefined\n }\n />\n );\n });\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [\n shapes,\n selectedSet,\n values,\n colorScale,\n colorByRegion,\n minColor,\n maxColor,\n onSelect,\n showTooltip,\n onMove,\n onLeave,\n ]);\n\n return (\n <div\n ref={containerRef}\n className={cn(styles.map, className)}\n style={{ height, ...style }}\n role=\"group\"\n aria-label={label}\n {...rest}\n >\n {!shapes ? (\n <div className={styles.loading}>{loadingContent ?? \"Carregando mapa…\"}</div>\n ) : (\n <svg\n className={styles.svg}\n width=\"100%\"\n height={height}\n viewBox={`0 0 ${width} ${height}`}\n preserveAspectRatio=\"xMidYMid meet\"\n {...mapZoom.handlers}\n >\n <g transform={mapZoom.transform || undefined}>\n {stateEls}\n\n {showLabels &&\n shapes.items.map((shape) => (\n <text\n key={`label-${shape.uf}`}\n className={styles.label}\n x={shape.centroid.x}\n y={shape.centroid.y}\n textAnchor=\"middle\"\n dominantBaseline=\"central\"\n >\n {shape.uf}\n </text>\n ))}\n\n {markers && markers.length > 0 && (\n <MapMarkers\n projection={shapes.projection}\n markers={markers}\n onMarkerClick={onMarkerClick}\n />\n )}\n </g>\n </svg>\n )}\n\n {mapZoom.isTransformed && (\n <button type=\"button\" className={styles.zoomReset} onClick={mapZoom.reset}>\n Reset\n </button>\n )}\n\n {showTooltip && hover && (\n <MapTooltip x={hover.x} y={hover.y}>\n {renderTooltip ? (\n renderTooltip({\n uf: hover.item.uf,\n name: hover.item.name,\n value: values?.[hover.item.uf],\n })\n ) : (\n <DefaultUfTooltip\n uf={hover.item.uf}\n name={hover.item.name}\n value={values?.[hover.item.uf]}\n />\n )}\n </MapTooltip>\n )}\n </div>\n );\n}\n\n/** Default UF tooltip: name (UF) + region + city count + optional value. */\nfunction DefaultUfTooltip({ uf, name, value }: { uf: UF; name: string; value?: number }) {\n const state = getState(uf);\n return (\n <>\n <div className={styles.tooltipTitle}>\n {name} ({uf})\n </div>\n <div className={styles.tooltipMeta}>\n {state ? `${state.region} · ${state.cities.length} cidades` : uf}\n {typeof value === \"number\" ? ` · ${value}` : \"\"}\n </div>\n </>\n );\n}\n"],"mappings":"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"}
|