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
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TrajectoryMap.cjs","names":[],"sources":["../../src/geo/TrajectoryMap.tsx"],"sourcesContent":["/**\n * @tempest-limits file-lines, props-count, function-lines — the SVG plot (points,\n * current, height, padding, showGrid, showScale, strokeColor, label), the optional\n * leaflet tile layer (tileUrl, tileAttribution) and the overlay (markers,\n * onMarkerClick). The body projects the path, fits the bounds and hands the same\n * bounds to the tile layer when there is one.\n */\nimport { lazy, Suspense, useEffect, useMemo, useRef, useState, type HTMLAttributes } from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport { boundingBox, expandBounds } from \"./bounds\";\nimport { haversineKm } from \"./distance\";\nimport { fitProjection, type PixelPoint } from \"./projection\";\nimport type { Coordinate, GeoMarker } from \"./types\";\nimport styles from \"./TrajectoryMap.module.css\";\n\n// Leaflet layer is loaded on demand — only when a `tileUrl` is supplied, so\n// apps that stick to the tile-free SVG never pull leaflet into their bundle.\nconst LeafletTrajectory = lazy(() =>\n import(\"./leaflet-map\").then((m) => ({ default: m.LeafletTrajectory })),\n);\n\nexport interface TrajectoryMapProps extends Omit<HTMLAttributes<HTMLDivElement>, \"children\"> {\n /** Ordered coordinates forming the trajectory. */\n points: readonly Coordinate[];\n /** Optional \"current position\" marker, drawn on top of the path. */\n current?: Coordinate | null;\n /** Viewport height in pixels. Default: `320`. */\n height?: number;\n /** Inner padding in pixels kept clear on every edge. Default: `24`. */\n padding?: number;\n /** Draw a light reference grid behind the path (SVG mode). Default: `true`. */\n showGrid?: boolean;\n /** Draw a distance scale bar (SVG mode). Default: `true`. */\n showScale?: boolean;\n /** Path stroke color. Default: the primary token. */\n strokeColor?: string;\n /**\n * Tile URL template (`{z}/{x}/{y}`) of a map server **you host**. When set,\n * the map upgrades to a real Leaflet tile layer (requires `leaflet` to be\n * installed). Omit for the zero-dependency, zero-network SVG plot.\n */\n tileUrl?: string;\n /** Attribution text shown over the tile layer. */\n tileAttribution?: string;\n /** Accessible label for the map region. Default: `\"Trajetória\"`. */\n label?: string;\n /** Point markers to overlay (e.g. stops, POIs). Included in the auto-fit. */\n markers?: readonly GeoMarker[];\n /** Fired when a marker is clicked. */\n onMarkerClick?: (marker: GeoMarker, index: number) => void;\n}\n\nconst NICE_KM_STEPS = [0.05, 0.1, 0.25, 0.5, 1, 2, 5, 10, 25, 50, 100, 250, 500, 1000] as const;\n\n/** Pick the largest \"nice\" distance whose bar fits within `maxPixels`. */\nfunction pickScaleKm(kmPerPixel: number, maxPixels: number): { km: number; pixels: number } {\n const maxKm = kmPerPixel * maxPixels;\n let chosen: number = NICE_KM_STEPS[0];\n for (const step of NICE_KM_STEPS) {\n if (step <= maxKm) chosen = step;\n }\n return { km: chosen, pixels: chosen / kmPerPixel };\n}\n\n/** Format a distance in km for the scale bar (meters below 1 km). */\nfunction formatScale(km: number): string {\n return km < 1 ? `${Math.round(km * 1000)} m` : `${km % 1 === 0 ? km : km.toFixed(1)} km`;\n}\n\n/**\n * Plot a GPS trajectory. By default it renders a **tile-free SVG** map (Web\n * Mercator projection, auto-fit to the points, optional grid + scale bar) —\n * 100% self-contained, no external tiles or paid API. Pass `tileUrl` pointing\n * at a map server **you host** to upgrade to a real Leaflet tile layer.\n *\n * @example\n * // Zero-dependency SVG plot\n * <TrajectoryMap points={points} current={lastPoint} height={360} />\n *\n * @example\n * // Real tiles from your own server (requires `leaflet` installed)\n * <TrajectoryMap points={points} tileUrl=\"https://tiles.internal/{z}/{x}/{y}.png\" />\n */\nexport function TrajectoryMap({\n points,\n current,\n height = 320,\n padding = 24,\n showGrid = true,\n showScale = true,\n strokeColor,\n tileUrl,\n tileAttribution,\n label = \"Trajetória\",\n markers,\n onMarkerClick,\n className,\n style,\n ...rest\n}: TrajectoryMapProps) {\n const containerRef = useRef<HTMLDivElement>(null);\n const [width, setWidth] = useState<number>(600);\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 allPoints = useMemo<Coordinate[]>(\n () => [...points, ...(current ? [current] : []), ...(markers ?? [])],\n [points, current, markers],\n );\n\n const svg = useMemo(() => {\n const box = boundingBox(allPoints);\n if (!box) return null;\n\n const projection = fitProjection(expandBounds(box, 0.12), width, height, { padding });\n const pixels: PixelPoint[] = points.map((p) => projection.project(p));\n const polyline = pixels.map((p) => `${p.x.toFixed(1)},${p.y.toFixed(1)}`).join(\" \");\n\n const startPixel = pixels[0] ?? null;\n const endPixel = pixels.length > 0 ? pixels[pixels.length - 1] : null;\n const currentPixel = current ? projection.project(current) : null;\n\n // Distance-per-pixel at the map center, for the scale bar: measure the\n // center → east-edge span in both km (haversine) and pixels (projected).\n const centerCoord = {\n latitude: (box.minLatitude + box.maxLatitude) / 2,\n longitude: (box.minLongitude + box.maxLongitude) / 2,\n };\n const center = projection.project(centerCoord);\n const eastRef = projection.project({ ...centerCoord, longitude: box.maxLongitude });\n const edgeKm = haversineKm(centerCoord, { ...centerCoord, longitude: box.maxLongitude });\n const edgePixels = Math.abs(eastRef.x - center.x) || 1;\n const kmPerPixel = edgeKm / edgePixels || 0;\n\n const markerPixels = (markers ?? []).map((m) => ({ marker: m, p: projection.project(m) }));\n\n return { polyline, startPixel, endPixel, currentPixel, kmPerPixel, markerPixels };\n }, [allPoints, points, current, markers, width, height, padding]);\n\n // ── Leaflet tile mode ────────────────────────────────────────────────────\n if (tileUrl) {\n return (\n <div\n ref={containerRef}\n className={cn(styles.map, className)}\n style={{ height, ...style }}\n role=\"img\"\n aria-label={label}\n {...rest}\n >\n <Suspense fallback={<div className={styles.loading}>Carregando mapa…</div>}>\n <LeafletTrajectory\n points={points}\n current={current}\n tileUrl={tileUrl}\n tileAttribution={tileAttribution}\n strokeColor={strokeColor}\n />\n </Suspense>\n </div>\n );\n }\n\n // ── Tile-free SVG mode ─────────────────────────────────────────────────────\n const scale =\n showScale && svg && svg.kmPerPixel > 0\n ? pickScaleKm(svg.kmPerPixel, Math.min(120, width * 0.35))\n : null;\n\n return (\n <div\n ref={containerRef}\n className={cn(styles.map, className)}\n style={{ height, ...style }}\n role=\"img\"\n aria-label={label}\n {...rest}\n >\n {!svg ? (\n <div className={styles.empty}>Sem pontos de trajetória ainda.</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 >\n {showGrid && (\n <g className={styles.grid}>\n {[0.25, 0.5, 0.75].map((f) => (\n <line\n key={`h${f}`}\n x1={0}\n y1={height * f}\n x2={width}\n y2={height * f}\n />\n ))}\n {[0.25, 0.5, 0.75].map((f) => (\n <line\n key={`v${f}`}\n x1={width * f}\n y1={0}\n x2={width * f}\n y2={height}\n />\n ))}\n </g>\n )}\n\n {svg.polyline && (\n <polyline\n className={styles.path}\n points={svg.polyline}\n fill=\"none\"\n stroke={strokeColor}\n data-testid=\"trajectory-path\"\n />\n )}\n\n {svg.startPixel && (\n <circle\n className={styles.start}\n cx={svg.startPixel.x}\n cy={svg.startPixel.y}\n r={5}\n />\n )}\n {svg.endPixel && !current && (\n <circle\n className={styles.end}\n cx={svg.endPixel.x}\n cy={svg.endPixel.y}\n r={5}\n />\n )}\n {svg.currentPixel && (\n <>\n <circle\n className={styles.pulse}\n cx={svg.currentPixel.x}\n cy={svg.currentPixel.y}\n r={10}\n />\n <circle\n className={styles.current}\n cx={svg.currentPixel.x}\n cy={svg.currentPixel.y}\n r={5}\n />\n </>\n )}\n\n {svg.markerPixels.map(({ marker, p }, i) => {\n const interactive = Boolean(onMarkerClick);\n return (\n <circle\n key={marker.id ?? i}\n className={styles.marker}\n cx={p.x}\n cy={p.y}\n r={marker.radius ?? 6}\n style={marker.color ? { fill: marker.color } : undefined}\n data-marker-id={marker.id ?? i}\n tabIndex={interactive ? 0 : undefined}\n role={interactive ? \"button\" : undefined}\n aria-label={marker.label}\n onClick={interactive ? () => onMarkerClick?.(marker, i) : undefined}\n onKeyDown={\n interactive\n ? (e) => {\n if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n onMarkerClick?.(marker, i);\n }\n }\n : undefined\n }\n >\n {marker.label ? <title>{marker.label}</title> : null}\n </circle>\n );\n })}\n\n {scale && (\n <g\n className={styles.scale}\n transform={`translate(${padding}, ${height - padding})`}\n >\n <line x1={0} y1={0} x2={scale.pixels} y2={0} />\n <line x1={0} y1={-4} x2={0} y2={4} />\n <line x1={scale.pixels} y1={-4} x2={scale.pixels} y2={4} />\n <text x={scale.pixels / 2} y={-8} textAnchor=\"middle\">\n {formatScale(scale.km)}\n </text>\n </g>\n )}\n </svg>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"TrajectoryMap.cjs","names":[],"sources":["../../src/geo/TrajectoryMap.tsx"],"sourcesContent":["/**\n * @tempest-limits file-lines, props-count, function-lines — the SVG plot (points,\n * current, height, padding, showGrid, showScale, strokeColor, label), the optional\n * leaflet tile layer (tileUrl, tileAttribution) and the overlay (markers,\n * onMarkerClick). The body projects the path, fits the bounds and hands the same\n * bounds to the tile layer when there is one.\n */\nimport { lazy, Suspense, useEffect, useMemo, useRef, useState, type HTMLAttributes } from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport { boundingBox, expandBounds } from \"./bounds\";\nimport { haversineKm } from \"./distance\";\nimport { fitProjection, type PixelPoint } from \"./projection\";\nimport type { Coordinate, GeoMarker } from \"./types\";\nimport styles from \"./TrajectoryMap.module.css\";\n\n// Leaflet layer is loaded on demand — only when a `tileUrl` is supplied, so\n// apps that stick to the tile-free SVG never pull leaflet into their bundle.\nconst LeafletTrajectory = lazy(() =>\n import(\"./leaflet-map\").then((m) => ({ default: m.LeafletTrajectory })),\n);\n\nexport interface TrajectoryMapProps extends Omit<HTMLAttributes<HTMLDivElement>, \"children\"> {\n /** Ordered coordinates forming the trajectory. */\n points: readonly Coordinate[];\n /** Optional \"current position\" marker, drawn on top of the path. */\n current?: Coordinate | null;\n /** Viewport height in pixels. Default: `320`. */\n height?: number;\n /** Inner padding in pixels kept clear on every edge. Default: `24`. */\n padding?: number;\n /** Draw a light reference grid behind the path (SVG mode). Default: `true`. */\n showGrid?: boolean;\n /** Draw a distance scale bar (SVG mode). Default: `true`. */\n showScale?: boolean;\n /** Path stroke color. Default: the primary token. */\n strokeColor?: string;\n /**\n * Tile URL template (`{z}/{x}/{y}`) of a map server **you host**. When set,\n * the map upgrades to a real Leaflet tile layer (requires `leaflet` to be\n * installed). Omit for the zero-dependency, zero-network SVG plot.\n */\n tileUrl?: string;\n /** Attribution text shown over the tile layer. */\n tileAttribution?: string;\n /** Accessible label for the map region. Default: `\"Trajetória\"`. */\n label?: string;\n /** Point markers to overlay (e.g. stops, POIs). Included in the auto-fit. */\n markers?: readonly GeoMarker[];\n /** Fired when a marker is clicked. */\n onMarkerClick?: (marker: GeoMarker, index: number) => void;\n}\n\nconst NICE_KM_STEPS = [0.05, 0.1, 0.25, 0.5, 1, 2, 5, 10, 25, 50, 100, 250, 500, 1000] as const;\n\n/** Pick the largest \"nice\" distance whose bar fits within `maxPixels`. */\nfunction pickScaleKm(kmPerPixel: number, maxPixels: number): { km: number; pixels: number } {\n const maxKm = kmPerPixel * maxPixels;\n let chosen: number = NICE_KM_STEPS[0];\n for (const step of NICE_KM_STEPS) {\n if (step <= maxKm) chosen = step;\n }\n return { km: chosen, pixels: chosen / kmPerPixel };\n}\n\n/** Format a distance in km for the scale bar (meters below 1 km). */\nfunction formatScale(km: number): string {\n return km < 1 ? `${Math.round(km * 1000)} m` : `${km % 1 === 0 ? km : km.toFixed(1)} km`;\n}\n\n/**\n * Plot a GPS trajectory. By default it renders a **tile-free SVG** map (Web\n * Mercator projection, auto-fit to the points, optional grid + scale bar) —\n * 100% self-contained, no external tiles or paid API. Pass `tileUrl` pointing\n * at a map server **you host** to upgrade to a real Leaflet tile layer.\n *\n * @example\n * // Zero-dependency SVG plot\n * <TrajectoryMap points={points} current={lastPoint} height={360} />\n *\n * @example\n * // Real tiles from your own server (requires `leaflet` installed)\n * <TrajectoryMap points={points} tileUrl=\"https://tiles.internal/{z}/{x}/{y}.png\" />\n */\nexport function TrajectoryMap({\n points,\n current,\n height = 320,\n padding = 24,\n showGrid = true,\n showScale = true,\n strokeColor,\n tileUrl,\n tileAttribution,\n label = \"Trajetória\",\n markers,\n onMarkerClick,\n className,\n style,\n ...rest\n}: TrajectoryMapProps) {\n const containerRef = useRef<HTMLDivElement>(null);\n const [width, setWidth] = useState<number>(600);\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 allPoints = useMemo<Coordinate[]>(\n () => [...points, ...(current ? [current] : []), ...(markers ?? [])],\n [points, current, markers],\n );\n\n const svg = useMemo(() => {\n const box = boundingBox(allPoints);\n if (!box) return null;\n\n const projection = fitProjection(expandBounds(box, 0.12), width, height, { padding });\n const pixels: PixelPoint[] = points.map((p) => projection.project(p));\n const polyline = pixels.map((p) => `${p.x.toFixed(1)},${p.y.toFixed(1)}`).join(\" \");\n\n const startPixel = pixels[0] ?? null;\n const endPixel = pixels.length > 0 ? pixels[pixels.length - 1] : null;\n const currentPixel = current ? projection.project(current) : null;\n\n // Distance-per-pixel at the map center, for the scale bar: measure the\n // center → east-edge span in both km (haversine) and pixels (projected).\n const centerCoord = {\n latitude: (box.minLatitude + box.maxLatitude) / 2,\n longitude: (box.minLongitude + box.maxLongitude) / 2,\n };\n const center = projection.project(centerCoord);\n const eastRef = projection.project({ ...centerCoord, longitude: box.maxLongitude });\n const edgeKm = haversineKm(centerCoord, { ...centerCoord, longitude: box.maxLongitude });\n const edgePixels = Math.abs(eastRef.x - center.x) || 1;\n const kmPerPixel = edgeKm / edgePixels || 0;\n\n const markerPixels = (markers ?? []).map((m) => ({ marker: m, p: projection.project(m) }));\n\n return { polyline, startPixel, endPixel, currentPixel, kmPerPixel, markerPixels };\n }, [allPoints, points, current, markers, width, height, padding]);\n\n // ── Leaflet tile mode ────────────────────────────────────────────────────\n if (tileUrl) {\n return (\n <div\n ref={containerRef}\n className={cn(styles.map, className)}\n style={{ height, ...style }}\n role=\"img\"\n aria-label={label}\n {...rest}\n >\n <Suspense fallback={<div className={styles.loading}>Carregando mapa…</div>}>\n <LeafletTrajectory\n points={points}\n current={current}\n tileUrl={tileUrl}\n tileAttribution={tileAttribution}\n strokeColor={strokeColor}\n />\n </Suspense>\n </div>\n );\n }\n\n // ── Tile-free SVG mode ─────────────────────────────────────────────────────\n const scale =\n showScale && svg && svg.kmPerPixel > 0\n ? pickScaleKm(svg.kmPerPixel, Math.min(120, width * 0.35))\n : null;\n\n return (\n <div\n ref={containerRef}\n className={cn(styles.map, className)}\n style={{ height, ...style }}\n role=\"img\"\n aria-label={label}\n {...rest}\n >\n {!svg ? (\n <div className={styles.empty}>Sem pontos de trajetória ainda.</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 >\n {showGrid && (\n <g className={styles.grid}>\n {[0.25, 0.5, 0.75].map((f) => (\n <line\n key={`h${f}`}\n x1={0}\n y1={height * f}\n x2={width}\n y2={height * f}\n />\n ))}\n {[0.25, 0.5, 0.75].map((f) => (\n <line\n key={`v${f}`}\n x1={width * f}\n y1={0}\n x2={width * f}\n y2={height}\n />\n ))}\n </g>\n )}\n\n {svg.polyline && (\n <polyline\n className={styles.path}\n points={svg.polyline}\n fill=\"none\"\n stroke={strokeColor}\n data-testid=\"trajectory-path\"\n />\n )}\n\n {svg.startPixel && (\n <circle\n className={styles.start}\n cx={svg.startPixel.x}\n cy={svg.startPixel.y}\n r={5}\n />\n )}\n {svg.endPixel && !current && (\n <circle\n className={styles.end}\n cx={svg.endPixel.x}\n cy={svg.endPixel.y}\n r={5}\n />\n )}\n {svg.currentPixel && (\n <>\n <circle\n className={styles.pulse}\n cx={svg.currentPixel.x}\n cy={svg.currentPixel.y}\n r={10}\n />\n <circle\n className={styles.current}\n cx={svg.currentPixel.x}\n cy={svg.currentPixel.y}\n r={5}\n />\n </>\n )}\n\n {svg.markerPixels.map(({ marker, p }, i) => {\n const interactive = Boolean(onMarkerClick);\n return (\n <circle\n key={marker.id ?? i}\n className={styles.marker}\n cx={p.x}\n cy={p.y}\n r={marker.radius ?? 6}\n style={marker.color ? { fill: marker.color } : undefined}\n data-marker-id={marker.id ?? i}\n tabIndex={interactive ? 0 : undefined}\n role={interactive ? \"button\" : undefined}\n aria-label={marker.label}\n onClick={interactive ? () => onMarkerClick?.(marker, i) : undefined}\n onKeyDown={\n interactive\n ? (e) => {\n if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n onMarkerClick?.(marker, i);\n }\n }\n : undefined\n }\n >\n {marker.label ? <title>{marker.label}</title> : null}\n </circle>\n );\n })}\n\n {scale && (\n <g\n className={styles.scale}\n transform={`translate(${padding}, ${height - padding})`}\n >\n <line x1={0} y1={0} x2={scale.pixels} y2={0} />\n <line x1={0} y1={-4} x2={0} y2={4} />\n <line x1={scale.pixels} y1={-4} x2={scale.pixels} y2={4} />\n <text x={scale.pixels / 2} y={-8} textAnchor=\"middle\">\n {formatScale(scale.km)}\n </text>\n </g>\n )}\n </svg>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TrajectoryMap.js","names":[],"sources":["../../src/geo/TrajectoryMap.tsx"],"sourcesContent":["/**\n * @tempest-limits file-lines, props-count, function-lines — the SVG plot (points,\n * current, height, padding, showGrid, showScale, strokeColor, label), the optional\n * leaflet tile layer (tileUrl, tileAttribution) and the overlay (markers,\n * onMarkerClick). The body projects the path, fits the bounds and hands the same\n * bounds to the tile layer when there is one.\n */\nimport { lazy, Suspense, useEffect, useMemo, useRef, useState, type HTMLAttributes } from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport { boundingBox, expandBounds } from \"./bounds\";\nimport { haversineKm } from \"./distance\";\nimport { fitProjection, type PixelPoint } from \"./projection\";\nimport type { Coordinate, GeoMarker } from \"./types\";\nimport styles from \"./TrajectoryMap.module.css\";\n\n// Leaflet layer is loaded on demand — only when a `tileUrl` is supplied, so\n// apps that stick to the tile-free SVG never pull leaflet into their bundle.\nconst LeafletTrajectory = lazy(() =>\n import(\"./leaflet-map\").then((m) => ({ default: m.LeafletTrajectory })),\n);\n\nexport interface TrajectoryMapProps extends Omit<HTMLAttributes<HTMLDivElement>, \"children\"> {\n /** Ordered coordinates forming the trajectory. */\n points: readonly Coordinate[];\n /** Optional \"current position\" marker, drawn on top of the path. */\n current?: Coordinate | null;\n /** Viewport height in pixels. Default: `320`. */\n height?: number;\n /** Inner padding in pixels kept clear on every edge. Default: `24`. */\n padding?: number;\n /** Draw a light reference grid behind the path (SVG mode). Default: `true`. */\n showGrid?: boolean;\n /** Draw a distance scale bar (SVG mode). Default: `true`. */\n showScale?: boolean;\n /** Path stroke color. Default: the primary token. */\n strokeColor?: string;\n /**\n * Tile URL template (`{z}/{x}/{y}`) of a map server **you host**. When set,\n * the map upgrades to a real Leaflet tile layer (requires `leaflet` to be\n * installed). Omit for the zero-dependency, zero-network SVG plot.\n */\n tileUrl?: string;\n /** Attribution text shown over the tile layer. */\n tileAttribution?: string;\n /** Accessible label for the map region. Default: `\"Trajetória\"`. */\n label?: string;\n /** Point markers to overlay (e.g. stops, POIs). Included in the auto-fit. */\n markers?: readonly GeoMarker[];\n /** Fired when a marker is clicked. */\n onMarkerClick?: (marker: GeoMarker, index: number) => void;\n}\n\nconst NICE_KM_STEPS = [0.05, 0.1, 0.25, 0.5, 1, 2, 5, 10, 25, 50, 100, 250, 500, 1000] as const;\n\n/** Pick the largest \"nice\" distance whose bar fits within `maxPixels`. */\nfunction pickScaleKm(kmPerPixel: number, maxPixels: number): { km: number; pixels: number } {\n const maxKm = kmPerPixel * maxPixels;\n let chosen: number = NICE_KM_STEPS[0];\n for (const step of NICE_KM_STEPS) {\n if (step <= maxKm) chosen = step;\n }\n return { km: chosen, pixels: chosen / kmPerPixel };\n}\n\n/** Format a distance in km for the scale bar (meters below 1 km). */\nfunction formatScale(km: number): string {\n return km < 1 ? `${Math.round(km * 1000)} m` : `${km % 1 === 0 ? km : km.toFixed(1)} km`;\n}\n\n/**\n * Plot a GPS trajectory. By default it renders a **tile-free SVG** map (Web\n * Mercator projection, auto-fit to the points, optional grid + scale bar) —\n * 100% self-contained, no external tiles or paid API. Pass `tileUrl` pointing\n * at a map server **you host** to upgrade to a real Leaflet tile layer.\n *\n * @example\n * // Zero-dependency SVG plot\n * <TrajectoryMap points={points} current={lastPoint} height={360} />\n *\n * @example\n * // Real tiles from your own server (requires `leaflet` installed)\n * <TrajectoryMap points={points} tileUrl=\"https://tiles.internal/{z}/{x}/{y}.png\" />\n */\nexport function TrajectoryMap({\n points,\n current,\n height = 320,\n padding = 24,\n showGrid = true,\n showScale = true,\n strokeColor,\n tileUrl,\n tileAttribution,\n label = \"Trajetória\",\n markers,\n onMarkerClick,\n className,\n style,\n ...rest\n}: TrajectoryMapProps) {\n const containerRef = useRef<HTMLDivElement>(null);\n const [width, setWidth] = useState<number>(600);\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 allPoints = useMemo<Coordinate[]>(\n () => [...points, ...(current ? [current] : []), ...(markers ?? [])],\n [points, current, markers],\n );\n\n const svg = useMemo(() => {\n const box = boundingBox(allPoints);\n if (!box) return null;\n\n const projection = fitProjection(expandBounds(box, 0.12), width, height, { padding });\n const pixels: PixelPoint[] = points.map((p) => projection.project(p));\n const polyline = pixels.map((p) => `${p.x.toFixed(1)},${p.y.toFixed(1)}`).join(\" \");\n\n const startPixel = pixels[0] ?? null;\n const endPixel = pixels.length > 0 ? pixels[pixels.length - 1] : null;\n const currentPixel = current ? projection.project(current) : null;\n\n // Distance-per-pixel at the map center, for the scale bar: measure the\n // center → east-edge span in both km (haversine) and pixels (projected).\n const centerCoord = {\n latitude: (box.minLatitude + box.maxLatitude) / 2,\n longitude: (box.minLongitude + box.maxLongitude) / 2,\n };\n const center = projection.project(centerCoord);\n const eastRef = projection.project({ ...centerCoord, longitude: box.maxLongitude });\n const edgeKm = haversineKm(centerCoord, { ...centerCoord, longitude: box.maxLongitude });\n const edgePixels = Math.abs(eastRef.x - center.x) || 1;\n const kmPerPixel = edgeKm / edgePixels || 0;\n\n const markerPixels = (markers ?? []).map((m) => ({ marker: m, p: projection.project(m) }));\n\n return { polyline, startPixel, endPixel, currentPixel, kmPerPixel, markerPixels };\n }, [allPoints, points, current, markers, width, height, padding]);\n\n // ── Leaflet tile mode ────────────────────────────────────────────────────\n if (tileUrl) {\n return (\n <div\n ref={containerRef}\n className={cn(styles.map, className)}\n style={{ height, ...style }}\n role=\"img\"\n aria-label={label}\n {...rest}\n >\n <Suspense fallback={<div className={styles.loading}>Carregando mapa…</div>}>\n <LeafletTrajectory\n points={points}\n current={current}\n tileUrl={tileUrl}\n tileAttribution={tileAttribution}\n strokeColor={strokeColor}\n />\n </Suspense>\n </div>\n );\n }\n\n // ── Tile-free SVG mode ─────────────────────────────────────────────────────\n const scale =\n showScale && svg && svg.kmPerPixel > 0\n ? pickScaleKm(svg.kmPerPixel, Math.min(120, width * 0.35))\n : null;\n\n return (\n <div\n ref={containerRef}\n className={cn(styles.map, className)}\n style={{ height, ...style }}\n role=\"img\"\n aria-label={label}\n {...rest}\n >\n {!svg ? (\n <div className={styles.empty}>Sem pontos de trajetória ainda.</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 >\n {showGrid && (\n <g className={styles.grid}>\n {[0.25, 0.5, 0.75].map((f) => (\n <line\n key={`h${f}`}\n x1={0}\n y1={height * f}\n x2={width}\n y2={height * f}\n />\n ))}\n {[0.25, 0.5, 0.75].map((f) => (\n <line\n key={`v${f}`}\n x1={width * f}\n y1={0}\n x2={width * f}\n y2={height}\n />\n ))}\n </g>\n )}\n\n {svg.polyline && (\n <polyline\n className={styles.path}\n points={svg.polyline}\n fill=\"none\"\n stroke={strokeColor}\n data-testid=\"trajectory-path\"\n />\n )}\n\n {svg.startPixel && (\n <circle\n className={styles.start}\n cx={svg.startPixel.x}\n cy={svg.startPixel.y}\n r={5}\n />\n )}\n {svg.endPixel && !current && (\n <circle\n className={styles.end}\n cx={svg.endPixel.x}\n cy={svg.endPixel.y}\n r={5}\n />\n )}\n {svg.currentPixel && (\n <>\n <circle\n className={styles.pulse}\n cx={svg.currentPixel.x}\n cy={svg.currentPixel.y}\n r={10}\n />\n <circle\n className={styles.current}\n cx={svg.currentPixel.x}\n cy={svg.currentPixel.y}\n r={5}\n />\n </>\n )}\n\n {svg.markerPixels.map(({ marker, p }, i) => {\n const interactive = Boolean(onMarkerClick);\n return (\n <circle\n key={marker.id ?? i}\n className={styles.marker}\n cx={p.x}\n cy={p.y}\n r={marker.radius ?? 6}\n style={marker.color ? { fill: marker.color } : undefined}\n data-marker-id={marker.id ?? i}\n tabIndex={interactive ? 0 : undefined}\n role={interactive ? \"button\" : undefined}\n aria-label={marker.label}\n onClick={interactive ? () => onMarkerClick?.(marker, i) : undefined}\n onKeyDown={\n interactive\n ? (e) => {\n if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n onMarkerClick?.(marker, i);\n }\n }\n : undefined\n }\n >\n {marker.label ? <title>{marker.label}</title> : null}\n </circle>\n );\n })}\n\n {scale && (\n <g\n className={styles.scale}\n transform={`translate(${padding}, ${height - padding})`}\n >\n <line x1={0} y1={0} x2={scale.pixels} y2={0} />\n <line x1={0} y1={-4} x2={0} y2={4} />\n <line x1={scale.pixels} y1={-4} x2={scale.pixels} y2={4} />\n <text x={scale.pixels / 2} y={-8} textAnchor=\"middle\">\n {formatScale(scale.km)}\n </text>\n </g>\n )}\n </svg>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"TrajectoryMap.js","names":[],"sources":["../../src/geo/TrajectoryMap.tsx"],"sourcesContent":["/**\n * @tempest-limits file-lines, props-count, function-lines — the SVG plot (points,\n * current, height, padding, showGrid, showScale, strokeColor, label), the optional\n * leaflet tile layer (tileUrl, tileAttribution) and the overlay (markers,\n * onMarkerClick). The body projects the path, fits the bounds and hands the same\n * bounds to the tile layer when there is one.\n */\nimport { lazy, Suspense, useEffect, useMemo, useRef, useState, type HTMLAttributes } from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport { boundingBox, expandBounds } from \"./bounds\";\nimport { haversineKm } from \"./distance\";\nimport { fitProjection, type PixelPoint } from \"./projection\";\nimport type { Coordinate, GeoMarker } from \"./types\";\nimport styles from \"./TrajectoryMap.module.css\";\n\n// Leaflet layer is loaded on demand — only when a `tileUrl` is supplied, so\n// apps that stick to the tile-free SVG never pull leaflet into their bundle.\nconst LeafletTrajectory = lazy(() =>\n import(\"./leaflet-map\").then((m) => ({ default: m.LeafletTrajectory })),\n);\n\nexport interface TrajectoryMapProps extends Omit<HTMLAttributes<HTMLDivElement>, \"children\"> {\n /** Ordered coordinates forming the trajectory. */\n points: readonly Coordinate[];\n /** Optional \"current position\" marker, drawn on top of the path. */\n current?: Coordinate | null;\n /** Viewport height in pixels. Default: `320`. */\n height?: number;\n /** Inner padding in pixels kept clear on every edge. Default: `24`. */\n padding?: number;\n /** Draw a light reference grid behind the path (SVG mode). Default: `true`. */\n showGrid?: boolean;\n /** Draw a distance scale bar (SVG mode). Default: `true`. */\n showScale?: boolean;\n /** Path stroke color. Default: the primary token. */\n strokeColor?: string;\n /**\n * Tile URL template (`{z}/{x}/{y}`) of a map server **you host**. When set,\n * the map upgrades to a real Leaflet tile layer (requires `leaflet` to be\n * installed). Omit for the zero-dependency, zero-network SVG plot.\n */\n tileUrl?: string;\n /** Attribution text shown over the tile layer. */\n tileAttribution?: string;\n /** Accessible label for the map region. Default: `\"Trajetória\"`. */\n label?: string;\n /** Point markers to overlay (e.g. stops, POIs). Included in the auto-fit. */\n markers?: readonly GeoMarker[];\n /** Fired when a marker is clicked. */\n onMarkerClick?: (marker: GeoMarker, index: number) => void;\n}\n\nconst NICE_KM_STEPS = [0.05, 0.1, 0.25, 0.5, 1, 2, 5, 10, 25, 50, 100, 250, 500, 1000] as const;\n\n/** Pick the largest \"nice\" distance whose bar fits within `maxPixels`. */\nfunction pickScaleKm(kmPerPixel: number, maxPixels: number): { km: number; pixels: number } {\n const maxKm = kmPerPixel * maxPixels;\n let chosen: number = NICE_KM_STEPS[0];\n for (const step of NICE_KM_STEPS) {\n if (step <= maxKm) chosen = step;\n }\n return { km: chosen, pixels: chosen / kmPerPixel };\n}\n\n/** Format a distance in km for the scale bar (meters below 1 km). */\nfunction formatScale(km: number): string {\n return km < 1 ? `${Math.round(km * 1000)} m` : `${km % 1 === 0 ? km : km.toFixed(1)} km`;\n}\n\n/**\n * Plot a GPS trajectory. By default it renders a **tile-free SVG** map (Web\n * Mercator projection, auto-fit to the points, optional grid + scale bar) —\n * 100% self-contained, no external tiles or paid API. Pass `tileUrl` pointing\n * at a map server **you host** to upgrade to a real Leaflet tile layer.\n *\n * @example\n * // Zero-dependency SVG plot\n * <TrajectoryMap points={points} current={lastPoint} height={360} />\n *\n * @example\n * // Real tiles from your own server (requires `leaflet` installed)\n * <TrajectoryMap points={points} tileUrl=\"https://tiles.internal/{z}/{x}/{y}.png\" />\n */\nexport function TrajectoryMap({\n points,\n current,\n height = 320,\n padding = 24,\n showGrid = true,\n showScale = true,\n strokeColor,\n tileUrl,\n tileAttribution,\n label = \"Trajetória\",\n markers,\n onMarkerClick,\n className,\n style,\n ...rest\n}: TrajectoryMapProps) {\n const containerRef = useRef<HTMLDivElement>(null);\n const [width, setWidth] = useState<number>(600);\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 allPoints = useMemo<Coordinate[]>(\n () => [...points, ...(current ? [current] : []), ...(markers ?? [])],\n [points, current, markers],\n );\n\n const svg = useMemo(() => {\n const box = boundingBox(allPoints);\n if (!box) return null;\n\n const projection = fitProjection(expandBounds(box, 0.12), width, height, { padding });\n const pixels: PixelPoint[] = points.map((p) => projection.project(p));\n const polyline = pixels.map((p) => `${p.x.toFixed(1)},${p.y.toFixed(1)}`).join(\" \");\n\n const startPixel = pixels[0] ?? null;\n const endPixel = pixels.length > 0 ? pixels[pixels.length - 1] : null;\n const currentPixel = current ? projection.project(current) : null;\n\n // Distance-per-pixel at the map center, for the scale bar: measure the\n // center → east-edge span in both km (haversine) and pixels (projected).\n const centerCoord = {\n latitude: (box.minLatitude + box.maxLatitude) / 2,\n longitude: (box.minLongitude + box.maxLongitude) / 2,\n };\n const center = projection.project(centerCoord);\n const eastRef = projection.project({ ...centerCoord, longitude: box.maxLongitude });\n const edgeKm = haversineKm(centerCoord, { ...centerCoord, longitude: box.maxLongitude });\n const edgePixels = Math.abs(eastRef.x - center.x) || 1;\n const kmPerPixel = edgeKm / edgePixels || 0;\n\n const markerPixels = (markers ?? []).map((m) => ({ marker: m, p: projection.project(m) }));\n\n return { polyline, startPixel, endPixel, currentPixel, kmPerPixel, markerPixels };\n }, [allPoints, points, current, markers, width, height, padding]);\n\n // ── Leaflet tile mode ────────────────────────────────────────────────────\n if (tileUrl) {\n return (\n <div\n ref={containerRef}\n className={cn(styles.map, className)}\n style={{ height, ...style }}\n role=\"img\"\n aria-label={label}\n {...rest}\n >\n <Suspense fallback={<div className={styles.loading}>Carregando mapa…</div>}>\n <LeafletTrajectory\n points={points}\n current={current}\n tileUrl={tileUrl}\n tileAttribution={tileAttribution}\n strokeColor={strokeColor}\n />\n </Suspense>\n </div>\n );\n }\n\n // ── Tile-free SVG mode ─────────────────────────────────────────────────────\n const scale =\n showScale && svg && svg.kmPerPixel > 0\n ? pickScaleKm(svg.kmPerPixel, Math.min(120, width * 0.35))\n : null;\n\n return (\n <div\n ref={containerRef}\n className={cn(styles.map, className)}\n style={{ height, ...style }}\n role=\"img\"\n aria-label={label}\n {...rest}\n >\n {!svg ? (\n <div className={styles.empty}>Sem pontos de trajetória ainda.</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 >\n {showGrid && (\n <g className={styles.grid}>\n {[0.25, 0.5, 0.75].map((f) => (\n <line\n key={`h${f}`}\n x1={0}\n y1={height * f}\n x2={width}\n y2={height * f}\n />\n ))}\n {[0.25, 0.5, 0.75].map((f) => (\n <line\n key={`v${f}`}\n x1={width * f}\n y1={0}\n x2={width * f}\n y2={height}\n />\n ))}\n </g>\n )}\n\n {svg.polyline && (\n <polyline\n className={styles.path}\n points={svg.polyline}\n fill=\"none\"\n stroke={strokeColor}\n data-testid=\"trajectory-path\"\n />\n )}\n\n {svg.startPixel && (\n <circle\n className={styles.start}\n cx={svg.startPixel.x}\n cy={svg.startPixel.y}\n r={5}\n />\n )}\n {svg.endPixel && !current && (\n <circle\n className={styles.end}\n cx={svg.endPixel.x}\n cy={svg.endPixel.y}\n r={5}\n />\n )}\n {svg.currentPixel && (\n <>\n <circle\n className={styles.pulse}\n cx={svg.currentPixel.x}\n cy={svg.currentPixel.y}\n r={10}\n />\n <circle\n className={styles.current}\n cx={svg.currentPixel.x}\n cy={svg.currentPixel.y}\n r={5}\n />\n </>\n )}\n\n {svg.markerPixels.map(({ marker, p }, i) => {\n const interactive = Boolean(onMarkerClick);\n return (\n <circle\n key={marker.id ?? i}\n className={styles.marker}\n cx={p.x}\n cy={p.y}\n r={marker.radius ?? 6}\n style={marker.color ? { fill: marker.color } : undefined}\n data-marker-id={marker.id ?? i}\n tabIndex={interactive ? 0 : undefined}\n role={interactive ? \"button\" : undefined}\n aria-label={marker.label}\n onClick={interactive ? () => onMarkerClick?.(marker, i) : undefined}\n onKeyDown={\n interactive\n ? (e) => {\n if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n onMarkerClick?.(marker, i);\n }\n }\n : undefined\n }\n >\n {marker.label ? <title>{marker.label}</title> : null}\n </circle>\n );\n })}\n\n {scale && (\n <g\n className={styles.scale}\n transform={`translate(${padding}, ${height - padding})`}\n >\n <line x1={0} y1={0} x2={scale.pixels} y2={0} />\n <line x1={0} y1={-4} x2={0} y2={4} />\n <line x1={scale.pixels} y1={-4} x2={scale.pixels} y2={4} />\n <text x={scale.pixels / 2} y={-8} textAnchor=\"middle\">\n {formatScale(scale.km)}\n </text>\n </g>\n )}\n </svg>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"leaflet-map.cjs","names":[],"sources":["../../src/geo/leaflet-map.tsx"],"sourcesContent":["import { useEffect, useRef, useState } from \"react\";\nimport { boundingBox } from \"./bounds\";\nimport type { Coordinate } from \"./types\";\n\n/**\n * Minimal structural typing for the slice of the Leaflet API this layer uses.\n * Declared locally so the SDK type-checks and builds even when `leaflet` (an\n * optional peer) is not installed — the real module is loaded at runtime.\n */\ninterface LeafletMapInstance {\n setView: (center: [number, number], zoom: number) => LeafletMapInstance;\n fitBounds: (bounds: [[number, number], [number, number]], options?: unknown) => void;\n remove: () => void;\n}\ninterface LeafletLayer {\n addTo: (map: LeafletMapInstance) => LeafletLayer;\n}\ninterface LeafletModule {\n map: (el: HTMLElement, options?: unknown) => LeafletMapInstance;\n tileLayer: (url: string, options?: unknown) => LeafletLayer;\n polyline: (latlngs: [number, number][], options?: unknown) => LeafletLayer;\n circleMarker: (latlng: [number, number], options?: unknown) => LeafletLayer;\n}\n\nexport interface LeafletTrajectoryProps {\n points: readonly Coordinate[];\n current?: Coordinate | null;\n tileUrl: string;\n tileAttribution?: string;\n strokeColor?: string;\n}\n\n/**\n * Leaflet-backed tile layer for {@link TrajectoryMap}. Loaded lazily and only\n * when a `tileUrl` is provided. Requires `leaflet` to be installed by the app\n * (optional peer dependency) plus its stylesheet imported once:\n * `import \"leaflet/dist/leaflet.css\"`.\n */\nexport function LeafletTrajectory({\n points,\n current,\n tileUrl,\n tileAttribution,\n strokeColor,\n}: LeafletTrajectoryProps) {\n const nodeRef = useRef<HTMLDivElement>(null);\n const [error, setError] = useState<string | null>(null);\n\n useEffect(() => {\n let map: LeafletMapInstance | null = null;\n let cancelled = false;\n\n const specifier = \"leaflet\";\n import(/* @vite-ignore */ specifier)\n .then((mod: { default?: LeafletModule } & LeafletModule) => {\n if (cancelled || !nodeRef.current) return;\n const L: LeafletModule = mod.default ?? mod;\n\n map = L.map(nodeRef.current, { attributionControl: true });\n L.tileLayer(tileUrl, {\n attribution: tileAttribution ?? \"\",\n maxZoom: 19,\n }).addTo(map);\n\n const latlngs = points.map((p): [number, number] => [p.latitude, p.longitude]);\n if (latlngs.length > 0) {\n L.polyline(latlngs, {\n color: strokeColor ?? \"#2563eb\",\n weight: 4,\n }).addTo(map);\n L.circleMarker(latlngs[0], { radius: 6, color: \"#16a34a\" }).addTo(map);\n }\n\n const marker = current ?? points[points.length - 1];\n if (marker) {\n L.circleMarker([marker.latitude, marker.longitude], {\n radius: 6,\n color: strokeColor ?? \"#2563eb\",\n }).addTo(map);\n }\n\n const box = boundingBox(current ? [...points, current] : [...points]);\n if (box) {\n map.fitBounds(\n [\n [box.minLatitude, box.minLongitude],\n [box.maxLatitude, box.maxLongitude],\n ],\n { padding: [24, 24] },\n );\n } else {\n map.setView([0, 0], 2);\n }\n })\n .catch(() => {\n if (!cancelled) {\n setError(\n \"Leaflet não encontrado. Instale `leaflet` para usar tiles, ou remova `tileUrl` para o plot SVG.\",\n );\n }\n });\n\n return () => {\n cancelled = true;\n map?.remove();\n };\n }, [points, current, tileUrl, tileAttribution, strokeColor]);\n\n if (error) {\n return (\n <div style={{ padding: 16, fontSize: 13, color: \"var(--tempest-danger-fg)\" }}>\n {error}\n </div>\n );\n }\n\n return <div ref={nodeRef} style={{ width: \"100%\", height: \"100%\" }} />;\n}\n"],"mappings":"sFAsCA,SAAgB,EAAkB,CAC9B,SACA,UACA,UACA,kBACA,eACuB,CACvB,IAAM,GAAA,
|
|
1
|
+
{"version":3,"file":"leaflet-map.cjs","names":[],"sources":["../../src/geo/leaflet-map.tsx"],"sourcesContent":["import { useEffect, useRef, useState } from \"react\";\nimport { boundingBox } from \"./bounds\";\nimport type { Coordinate } from \"./types\";\n\n/**\n * Minimal structural typing for the slice of the Leaflet API this layer uses.\n * Declared locally so the SDK type-checks and builds even when `leaflet` (an\n * optional peer) is not installed — the real module is loaded at runtime.\n */\ninterface LeafletMapInstance {\n setView: (center: [number, number], zoom: number) => LeafletMapInstance;\n fitBounds: (bounds: [[number, number], [number, number]], options?: unknown) => void;\n remove: () => void;\n}\ninterface LeafletLayer {\n addTo: (map: LeafletMapInstance) => LeafletLayer;\n}\ninterface LeafletModule {\n map: (el: HTMLElement, options?: unknown) => LeafletMapInstance;\n tileLayer: (url: string, options?: unknown) => LeafletLayer;\n polyline: (latlngs: [number, number][], options?: unknown) => LeafletLayer;\n circleMarker: (latlng: [number, number], options?: unknown) => LeafletLayer;\n}\n\nexport interface LeafletTrajectoryProps {\n points: readonly Coordinate[];\n current?: Coordinate | null;\n tileUrl: string;\n tileAttribution?: string;\n strokeColor?: string;\n}\n\n/**\n * Leaflet-backed tile layer for {@link TrajectoryMap}. Loaded lazily and only\n * when a `tileUrl` is provided. Requires `leaflet` to be installed by the app\n * (optional peer dependency) plus its stylesheet imported once:\n * `import \"leaflet/dist/leaflet.css\"`.\n */\nexport function LeafletTrajectory({\n points,\n current,\n tileUrl,\n tileAttribution,\n strokeColor,\n}: LeafletTrajectoryProps) {\n const nodeRef = useRef<HTMLDivElement>(null);\n const [error, setError] = useState<string | null>(null);\n\n useEffect(() => {\n let map: LeafletMapInstance | null = null;\n let cancelled = false;\n\n const specifier = \"leaflet\";\n import(/* @vite-ignore */ specifier)\n .then((mod: { default?: LeafletModule } & LeafletModule) => {\n if (cancelled || !nodeRef.current) return;\n const L: LeafletModule = mod.default ?? mod;\n\n map = L.map(nodeRef.current, { attributionControl: true });\n L.tileLayer(tileUrl, {\n attribution: tileAttribution ?? \"\",\n maxZoom: 19,\n }).addTo(map);\n\n const latlngs = points.map((p): [number, number] => [p.latitude, p.longitude]);\n if (latlngs.length > 0) {\n L.polyline(latlngs, {\n color: strokeColor ?? \"#2563eb\",\n weight: 4,\n }).addTo(map);\n L.circleMarker(latlngs[0], { radius: 6, color: \"#16a34a\" }).addTo(map);\n }\n\n const marker = current ?? points[points.length - 1];\n if (marker) {\n L.circleMarker([marker.latitude, marker.longitude], {\n radius: 6,\n color: strokeColor ?? \"#2563eb\",\n }).addTo(map);\n }\n\n const box = boundingBox(current ? [...points, current] : [...points]);\n if (box) {\n map.fitBounds(\n [\n [box.minLatitude, box.minLongitude],\n [box.maxLatitude, box.maxLongitude],\n ],\n { padding: [24, 24] },\n );\n } else {\n map.setView([0, 0], 2);\n }\n })\n .catch(() => {\n if (!cancelled) {\n setError(\n \"Leaflet não encontrado. Instale `leaflet` para usar tiles, ou remova `tileUrl` para o plot SVG.\",\n );\n }\n });\n\n return () => {\n cancelled = true;\n map?.remove();\n };\n }, [points, current, tileUrl, tileAttribution, strokeColor]);\n\n if (error) {\n return (\n <div style={{ padding: 16, fontSize: 13, color: \"var(--tempest-danger-fg)\" }}>\n {error}\n </div>\n );\n }\n\n return <div ref={nodeRef} style={{ width: \"100%\", height: \"100%\" }} />;\n}\n"],"mappings":"sFAsCA,SAAgB,EAAkB,CAC9B,SACA,UACA,UACA,kBACA,eACuB,CACvB,IAAM,GAAA,EAAU,EAAA,OAAA,CAAuB,IAAI,EACrC,CAAC,EAAO,IAAA,EAAY,EAAA,SAAA,CAAwB,IAAI,EAsEtD,OApEA,EAAA,EAAA,UAAA,KAAgB,CACZ,IAAI,EAAiC,KACjC,EAAY,GAoDhB,OAjDA,OAA0B,UAAU,CAC/B,KAAM,GAAqD,CACxD,GAAI,GAAa,CAAC,EAAQ,QAAS,OACnC,IAAM,EAAmB,EAAI,SAAW,EAExC,EAAM,EAAE,IAAI,EAAQ,QAAS,CAAE,mBAAoB,EAAK,CAAC,EACzD,EAAE,UAAU,EAAS,CACjB,YAAa,GAAmB,GAChC,QAAS,EACb,CAAC,CAAC,CAAC,MAAM,CAAG,EAEZ,IAAM,EAAU,EAAO,IAAK,GAAwB,CAAC,EAAE,SAAU,EAAE,SAAS,CAAC,EACzE,EAAQ,OAAS,IACjB,EAAE,SAAS,EAAS,CAChB,MAAO,GAAe,UACtB,OAAQ,CACZ,CAAC,CAAC,CAAC,MAAM,CAAG,EACZ,EAAE,aAAa,EAAQ,GAAI,CAAE,OAAQ,EAAG,MAAO,SAAU,CAAC,CAAC,CAAC,MAAM,CAAG,GAGzE,IAAM,EAAS,GAAW,EAAO,EAAO,OAAS,GAC7C,GACA,EAAE,aAAa,CAAC,EAAO,SAAU,EAAO,SAAS,EAAG,CAChD,OAAQ,EACR,MAAO,GAAe,SAC1B,CAAC,CAAC,CAAC,MAAM,CAAG,EAGhB,IAAM,EAAM,EAAA,YAAY,EAAU,CAAC,GAAG,EAAQ,CAAO,EAAI,CAAC,GAAG,CAAM,CAAC,EAChE,EACA,EAAI,UACA,CACI,CAAC,EAAI,YAAa,EAAI,YAAY,EAClC,CAAC,EAAI,YAAa,EAAI,YAAY,CACtC,EACA,CAAE,QAAS,CAAC,GAAI,EAAE,CAAE,CACxB,EAEA,EAAI,QAAQ,CAAC,EAAG,CAAC,EAAG,CAAC,CAE7B,CAAC,CAAC,CACD,UAAY,CACJ,GACD,EACI,iGACJ,CAER,CAAC,MAEQ,CACT,EAAY,GACZ,GAAK,OAAO,CAChB,CACJ,EAAG,CAAC,EAAQ,EAAS,EAAS,EAAiB,CAAW,CAAC,EAEvD,GAEI,EAAA,EAAA,IAAA,CAAC,MAAD,CAAK,MAAO,CAAE,QAAS,GAAI,SAAU,GAAI,MAAO,0BAA2B,EACtE,SAAA,CACA,CAAA,GAIN,EAAA,EAAA,IAAA,CAAC,MAAD,CAAK,IAAK,EAAS,MAAO,CAAE,MAAO,OAAQ,OAAQ,MAAO,CAAI,CAAA,CACzE"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"leaflet-map.js","names":[],"sources":["../../src/geo/leaflet-map.tsx"],"sourcesContent":["import { useEffect, useRef, useState } from \"react\";\nimport { boundingBox } from \"./bounds\";\nimport type { Coordinate } from \"./types\";\n\n/**\n * Minimal structural typing for the slice of the Leaflet API this layer uses.\n * Declared locally so the SDK type-checks and builds even when `leaflet` (an\n * optional peer) is not installed — the real module is loaded at runtime.\n */\ninterface LeafletMapInstance {\n setView: (center: [number, number], zoom: number) => LeafletMapInstance;\n fitBounds: (bounds: [[number, number], [number, number]], options?: unknown) => void;\n remove: () => void;\n}\ninterface LeafletLayer {\n addTo: (map: LeafletMapInstance) => LeafletLayer;\n}\ninterface LeafletModule {\n map: (el: HTMLElement, options?: unknown) => LeafletMapInstance;\n tileLayer: (url: string, options?: unknown) => LeafletLayer;\n polyline: (latlngs: [number, number][], options?: unknown) => LeafletLayer;\n circleMarker: (latlng: [number, number], options?: unknown) => LeafletLayer;\n}\n\nexport interface LeafletTrajectoryProps {\n points: readonly Coordinate[];\n current?: Coordinate | null;\n tileUrl: string;\n tileAttribution?: string;\n strokeColor?: string;\n}\n\n/**\n * Leaflet-backed tile layer for {@link TrajectoryMap}. Loaded lazily and only\n * when a `tileUrl` is provided. Requires `leaflet` to be installed by the app\n * (optional peer dependency) plus its stylesheet imported once:\n * `import \"leaflet/dist/leaflet.css\"`.\n */\nexport function LeafletTrajectory({\n points,\n current,\n tileUrl,\n tileAttribution,\n strokeColor,\n}: LeafletTrajectoryProps) {\n const nodeRef = useRef<HTMLDivElement>(null);\n const [error, setError] = useState<string | null>(null);\n\n useEffect(() => {\n let map: LeafletMapInstance | null = null;\n let cancelled = false;\n\n const specifier = \"leaflet\";\n import(/* @vite-ignore */ specifier)\n .then((mod: { default?: LeafletModule } & LeafletModule) => {\n if (cancelled || !nodeRef.current) return;\n const L: LeafletModule = mod.default ?? mod;\n\n map = L.map(nodeRef.current, { attributionControl: true });\n L.tileLayer(tileUrl, {\n attribution: tileAttribution ?? \"\",\n maxZoom: 19,\n }).addTo(map);\n\n const latlngs = points.map((p): [number, number] => [p.latitude, p.longitude]);\n if (latlngs.length > 0) {\n L.polyline(latlngs, {\n color: strokeColor ?? \"#2563eb\",\n weight: 4,\n }).addTo(map);\n L.circleMarker(latlngs[0], { radius: 6, color: \"#16a34a\" }).addTo(map);\n }\n\n const marker = current ?? points[points.length - 1];\n if (marker) {\n L.circleMarker([marker.latitude, marker.longitude], {\n radius: 6,\n color: strokeColor ?? \"#2563eb\",\n }).addTo(map);\n }\n\n const box = boundingBox(current ? [...points, current] : [...points]);\n if (box) {\n map.fitBounds(\n [\n [box.minLatitude, box.minLongitude],\n [box.maxLatitude, box.maxLongitude],\n ],\n { padding: [24, 24] },\n );\n } else {\n map.setView([0, 0], 2);\n }\n })\n .catch(() => {\n if (!cancelled) {\n setError(\n \"Leaflet não encontrado. Instale `leaflet` para usar tiles, ou remova `tileUrl` para o plot SVG.\",\n );\n }\n });\n\n return () => {\n cancelled = true;\n map?.remove();\n };\n }, [points, current, tileUrl, tileAttribution, strokeColor]);\n\n if (error) {\n return (\n <div style={{ padding: 16, fontSize: 13, color: \"var(--tempest-danger-fg)\" }}>\n {error}\n </div>\n );\n }\n\n return <div ref={nodeRef} style={{ width: \"100%\", height: \"100%\" }} />;\n}\n"],"mappings":";;;;AAsCA,SAAgB,EAAkB,EAC9B,WACA,YACA,YACA,oBACA,kBACuB;CACvB,IAAM,IAAU,EAAuB,IAAI,GACrC,CAAC,GAAO,KAAY,EAAwB,IAAI;CAsEtD,OApEA,QAAgB;EACZ,IAAI,IAAiC,MACjC,IAAY;EAoDhB,OAjDA;;GAA0B;CAAU,CAC/B,MAAM,MAAqD;GACxD,IAAI,KAAa,CAAC,EAAQ,SAAS;GACnC,IAAM,IAAmB,EAAI,WAAW;GAGxC,AADA,IAAM,EAAE,IAAI,EAAQ,SAAS,EAAE,oBAAoB,GAAK,CAAC,GACzD,EAAE,UAAU,GAAS;IACjB,aAAa,KAAmB;IAChC,SAAS;GACb,CAAC,CAAC,CAAC,MAAM,CAAG;GAEZ,IAAM,IAAU,EAAO,KAAK,MAAwB,CAAC,EAAE,UAAU,EAAE,SAAS,CAAC;GAC7E,AAAI,EAAQ,SAAS,MACjB,EAAE,SAAS,GAAS;IAChB,OAAO,KAAe;IACtB,QAAQ;GACZ,CAAC,CAAC,CAAC,MAAM,CAAG,GACZ,EAAE,aAAa,EAAQ,IAAI;IAAE,QAAQ;IAAG,OAAO;GAAU,CAAC,CAAC,CAAC,MAAM,CAAG;GAGzE,IAAM,IAAS,KAAW,EAAO,EAAO,SAAS;GACjD,AAAI,KACA,EAAE,aAAa,CAAC,EAAO,UAAU,EAAO,SAAS,GAAG;IAChD,QAAQ;IACR,OAAO,KAAe;GAC1B,CAAC,CAAC,CAAC,MAAM,CAAG;GAGhB,IAAM,IAAM,EAAY,IAAU,CAAC,GAAG,GAAQ,CAAO,IAAI,CAAC,GAAG,CAAM,CAAC;GACpE,AAAI,IACA,EAAI,UACA,CACI,CAAC,EAAI,aAAa,EAAI,YAAY,GAClC,CAAC,EAAI,aAAa,EAAI,YAAY,CACtC,GACA,EAAE,SAAS,CAAC,IAAI,EAAE,EAAE,CACxB,IAEA,EAAI,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC;EAE7B,CAAC,CAAC,CACD,YAAY;GACT,AAAK,KACD,EACI,iGACJ;EAER,CAAC,SAEQ;GAET,AADA,IAAY,IACZ,GAAK,OAAO;EAChB;CACJ,GAAG;EAAC;EAAQ;EAAS;EAAS;EAAiB;CAAW,CAAC,GAEvD,IAEI,kBAAC,OAAD;EAAK,OAAO;GAAE,SAAS;GAAI,UAAU;GAAI,OAAO;EAA2B;
|
|
1
|
+
{"version":3,"file":"leaflet-map.js","names":[],"sources":["../../src/geo/leaflet-map.tsx"],"sourcesContent":["import { useEffect, useRef, useState } from \"react\";\nimport { boundingBox } from \"./bounds\";\nimport type { Coordinate } from \"./types\";\n\n/**\n * Minimal structural typing for the slice of the Leaflet API this layer uses.\n * Declared locally so the SDK type-checks and builds even when `leaflet` (an\n * optional peer) is not installed — the real module is loaded at runtime.\n */\ninterface LeafletMapInstance {\n setView: (center: [number, number], zoom: number) => LeafletMapInstance;\n fitBounds: (bounds: [[number, number], [number, number]], options?: unknown) => void;\n remove: () => void;\n}\ninterface LeafletLayer {\n addTo: (map: LeafletMapInstance) => LeafletLayer;\n}\ninterface LeafletModule {\n map: (el: HTMLElement, options?: unknown) => LeafletMapInstance;\n tileLayer: (url: string, options?: unknown) => LeafletLayer;\n polyline: (latlngs: [number, number][], options?: unknown) => LeafletLayer;\n circleMarker: (latlng: [number, number], options?: unknown) => LeafletLayer;\n}\n\nexport interface LeafletTrajectoryProps {\n points: readonly Coordinate[];\n current?: Coordinate | null;\n tileUrl: string;\n tileAttribution?: string;\n strokeColor?: string;\n}\n\n/**\n * Leaflet-backed tile layer for {@link TrajectoryMap}. Loaded lazily and only\n * when a `tileUrl` is provided. Requires `leaflet` to be installed by the app\n * (optional peer dependency) plus its stylesheet imported once:\n * `import \"leaflet/dist/leaflet.css\"`.\n */\nexport function LeafletTrajectory({\n points,\n current,\n tileUrl,\n tileAttribution,\n strokeColor,\n}: LeafletTrajectoryProps) {\n const nodeRef = useRef<HTMLDivElement>(null);\n const [error, setError] = useState<string | null>(null);\n\n useEffect(() => {\n let map: LeafletMapInstance | null = null;\n let cancelled = false;\n\n const specifier = \"leaflet\";\n import(/* @vite-ignore */ specifier)\n .then((mod: { default?: LeafletModule } & LeafletModule) => {\n if (cancelled || !nodeRef.current) return;\n const L: LeafletModule = mod.default ?? mod;\n\n map = L.map(nodeRef.current, { attributionControl: true });\n L.tileLayer(tileUrl, {\n attribution: tileAttribution ?? \"\",\n maxZoom: 19,\n }).addTo(map);\n\n const latlngs = points.map((p): [number, number] => [p.latitude, p.longitude]);\n if (latlngs.length > 0) {\n L.polyline(latlngs, {\n color: strokeColor ?? \"#2563eb\",\n weight: 4,\n }).addTo(map);\n L.circleMarker(latlngs[0], { radius: 6, color: \"#16a34a\" }).addTo(map);\n }\n\n const marker = current ?? points[points.length - 1];\n if (marker) {\n L.circleMarker([marker.latitude, marker.longitude], {\n radius: 6,\n color: strokeColor ?? \"#2563eb\",\n }).addTo(map);\n }\n\n const box = boundingBox(current ? [...points, current] : [...points]);\n if (box) {\n map.fitBounds(\n [\n [box.minLatitude, box.minLongitude],\n [box.maxLatitude, box.maxLongitude],\n ],\n { padding: [24, 24] },\n );\n } else {\n map.setView([0, 0], 2);\n }\n })\n .catch(() => {\n if (!cancelled) {\n setError(\n \"Leaflet não encontrado. Instale `leaflet` para usar tiles, ou remova `tileUrl` para o plot SVG.\",\n );\n }\n });\n\n return () => {\n cancelled = true;\n map?.remove();\n };\n }, [points, current, tileUrl, tileAttribution, strokeColor]);\n\n if (error) {\n return (\n <div style={{ padding: 16, fontSize: 13, color: \"var(--tempest-danger-fg)\" }}>\n {error}\n </div>\n );\n }\n\n return <div ref={nodeRef} style={{ width: \"100%\", height: \"100%\" }} />;\n}\n"],"mappings":";;;;AAsCA,SAAgB,EAAkB,EAC9B,WACA,YACA,YACA,oBACA,kBACuB;CACvB,IAAM,IAAU,EAAuB,IAAI,GACrC,CAAC,GAAO,KAAY,EAAwB,IAAI;CAsEtD,OApEA,QAAgB;EACZ,IAAI,IAAiC,MACjC,IAAY;EAoDhB,OAjDA;;GAA0B;CAAU,CAC/B,MAAM,MAAqD;GACxD,IAAI,KAAa,CAAC,EAAQ,SAAS;GACnC,IAAM,IAAmB,EAAI,WAAW;GAGxC,AADA,IAAM,EAAE,IAAI,EAAQ,SAAS,EAAE,oBAAoB,GAAK,CAAC,GACzD,EAAE,UAAU,GAAS;IACjB,aAAa,KAAmB;IAChC,SAAS;GACb,CAAC,CAAC,CAAC,MAAM,CAAG;GAEZ,IAAM,IAAU,EAAO,KAAK,MAAwB,CAAC,EAAE,UAAU,EAAE,SAAS,CAAC;GAC7E,AAAI,EAAQ,SAAS,MACjB,EAAE,SAAS,GAAS;IAChB,OAAO,KAAe;IACtB,QAAQ;GACZ,CAAC,CAAC,CAAC,MAAM,CAAG,GACZ,EAAE,aAAa,EAAQ,IAAI;IAAE,QAAQ;IAAG,OAAO;GAAU,CAAC,CAAC,CAAC,MAAM,CAAG;GAGzE,IAAM,IAAS,KAAW,EAAO,EAAO,SAAS;GACjD,AAAI,KACA,EAAE,aAAa,CAAC,EAAO,UAAU,EAAO,SAAS,GAAG;IAChD,QAAQ;IACR,OAAO,KAAe;GAC1B,CAAC,CAAC,CAAC,MAAM,CAAG;GAGhB,IAAM,IAAM,EAAY,IAAU,CAAC,GAAG,GAAQ,CAAO,IAAI,CAAC,GAAG,CAAM,CAAC;GACpE,AAAI,IACA,EAAI,UACA,CACI,CAAC,EAAI,aAAa,EAAI,YAAY,GAClC,CAAC,EAAI,aAAa,EAAI,YAAY,CACtC,GACA,EAAE,SAAS,CAAC,IAAI,EAAE,EAAE,CACxB,IAEA,EAAI,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC;EAE7B,CAAC,CAAC,CACD,YAAY;GACT,AAAK,KACD,EACI,iGACJ;EAER,CAAC,SAEQ;GAET,AADA,IAAY,IACZ,GAAK,OAAO;EAChB;CACJ,GAAG;EAAC;EAAQ;EAAS;EAAS;EAAiB;CAAW,CAAC,GAEvD,IAEI,kBAAC,OAAD;EAAK,OAAO;GAAE,SAAS;GAAI,UAAU;GAAI,OAAO;EAA2B;EACtE,UAAA;CACA,CAAA,IAIN,kBAAC,OAAD;EAAK,KAAK;EAAS,OAAO;GAAE,OAAO;GAAQ,QAAQ;EAAO;CAAI,CAAA;AACzE"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-position-tracker.cjs","names":[],"sources":["../../src/geo/use-position-tracker.ts"],"sourcesContent":["import { useCallback, useEffect, useRef, useState } from \"react\";\nimport {\n createPositionTracker,\n type CreatePositionTrackerOptions,\n type PositionTracker,\n type TrackerStatus,\n} from \"./create-position-tracker\";\nimport type { TrackPoint } from \"./types\";\n\nexport interface UsePositionTrackerOptions extends Omit<\n CreatePositionTrackerOptions,\n \"onUpdate\" | \"onStatusChange\"\n> {\n /** Start tracking automatically on mount. Default: `false`. */\n autoStart?: boolean;\n}\n\nexport interface UsePositionTrackerResult {\n /** Recorded trajectory (newest last). */\n points: readonly TrackPoint[];\n /** Most recent point, or `null` before the first fix. */\n lastPoint: TrackPoint | null;\n /** Total distance of the trajectory in kilometers. */\n distanceKm: number;\n /** Current tracker status. */\n status: TrackerStatus;\n /** True while `status === \"tracking\"`. */\n isTracking: boolean;\n /** Begin watching position. */\n start: () => void;\n /** Stop watching, keeping the trajectory. */\n stop: () => void;\n /** Stop and discard the trajectory. */\n clear: () => void;\n}\n\n/**\n * React hook wrapping {@link createPositionTracker}. Records a live GPS\n * trajectory tied to the component lifecycle — the watch is torn down on\n * unmount. Fully browser-side; no external service.\n *\n * @example\n * const { points, distanceKm, isTracking, start, stop } = usePositionTracker({\n * minDistanceKm: 0.01,\n * });\n * return (\n * <>\n * <button onClick={isTracking ? stop : start}>{isTracking ? \"Parar\" : \"Rastrear\"}</button>\n * <TrajectoryMap points={points} />\n * <span>{distanceKm.toFixed(2)} km</span>\n * </>\n * );\n */\nexport function usePositionTracker(\n options: UsePositionTrackerOptions = {},\n): UsePositionTrackerResult {\n const { autoStart = false, onError, ...trackerOptions } = options;\n\n const [points, setPoints] = useState<readonly TrackPoint[]>([]);\n const [distanceKm, setDistanceKm] = useState<number>(0);\n const [status, setStatus] = useState<TrackerStatus>(\"idle\");\n\n const trackerRef = useRef<PositionTracker | null>(null);\n const onErrorRef = useRef(onError);\n useEffect(() => {\n onErrorRef.current = onError;\n }, [onError]);\n\n useEffect(() => {\n const tracker = createPositionTracker({\n ...trackerOptions,\n onUpdate: (next) => {\n setPoints([...next]);\n setDistanceKm(tracker.distanceKm);\n },\n onStatusChange: setStatus,\n onError: (error) => onErrorRef.current?.(error),\n });\n trackerRef.current = tracker;\n if (autoStart) tracker.start();\n\n return () => {\n tracker.stop();\n trackerRef.current = null;\n };\n // Tracker options are read once at creation; changing them mid-track\n // would drop the recorded trajectory, so they are intentionally omitted.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n const start = useCallback((): void => trackerRef.current?.start(), []);\n const stop = useCallback((): void => trackerRef.current?.stop(), []);\n const clear = useCallback((): void => {\n trackerRef.current?.clear();\n setDistanceKm(0);\n }, []);\n\n return {\n points,\n lastPoint: points.length > 0 ? points[points.length - 1] : null,\n distanceKm,\n status,\n isTracking: status === \"tracking\",\n start,\n stop,\n clear,\n };\n}\n"],"mappings":"wEAqDA,SAAgB,EACZ,EAAqC,CAAC,EACd,CACxB,GAAM,CAAE,YAAY,GAAO,UAAS,GAAG,GAAmB,EAEpD,CAAC,EAAQ,IAAA,
|
|
1
|
+
{"version":3,"file":"use-position-tracker.cjs","names":[],"sources":["../../src/geo/use-position-tracker.ts"],"sourcesContent":["import { useCallback, useEffect, useRef, useState } from \"react\";\nimport {\n createPositionTracker,\n type CreatePositionTrackerOptions,\n type PositionTracker,\n type TrackerStatus,\n} from \"./create-position-tracker\";\nimport type { TrackPoint } from \"./types\";\n\nexport interface UsePositionTrackerOptions extends Omit<\n CreatePositionTrackerOptions,\n \"onUpdate\" | \"onStatusChange\"\n> {\n /** Start tracking automatically on mount. Default: `false`. */\n autoStart?: boolean;\n}\n\nexport interface UsePositionTrackerResult {\n /** Recorded trajectory (newest last). */\n points: readonly TrackPoint[];\n /** Most recent point, or `null` before the first fix. */\n lastPoint: TrackPoint | null;\n /** Total distance of the trajectory in kilometers. */\n distanceKm: number;\n /** Current tracker status. */\n status: TrackerStatus;\n /** True while `status === \"tracking\"`. */\n isTracking: boolean;\n /** Begin watching position. */\n start: () => void;\n /** Stop watching, keeping the trajectory. */\n stop: () => void;\n /** Stop and discard the trajectory. */\n clear: () => void;\n}\n\n/**\n * React hook wrapping {@link createPositionTracker}. Records a live GPS\n * trajectory tied to the component lifecycle — the watch is torn down on\n * unmount. Fully browser-side; no external service.\n *\n * @example\n * const { points, distanceKm, isTracking, start, stop } = usePositionTracker({\n * minDistanceKm: 0.01,\n * });\n * return (\n * <>\n * <button onClick={isTracking ? stop : start}>{isTracking ? \"Parar\" : \"Rastrear\"}</button>\n * <TrajectoryMap points={points} />\n * <span>{distanceKm.toFixed(2)} km</span>\n * </>\n * );\n */\nexport function usePositionTracker(\n options: UsePositionTrackerOptions = {},\n): UsePositionTrackerResult {\n const { autoStart = false, onError, ...trackerOptions } = options;\n\n const [points, setPoints] = useState<readonly TrackPoint[]>([]);\n const [distanceKm, setDistanceKm] = useState<number>(0);\n const [status, setStatus] = useState<TrackerStatus>(\"idle\");\n\n const trackerRef = useRef<PositionTracker | null>(null);\n const onErrorRef = useRef(onError);\n useEffect(() => {\n onErrorRef.current = onError;\n }, [onError]);\n\n useEffect(() => {\n const tracker = createPositionTracker({\n ...trackerOptions,\n onUpdate: (next) => {\n setPoints([...next]);\n setDistanceKm(tracker.distanceKm);\n },\n onStatusChange: setStatus,\n onError: (error) => onErrorRef.current?.(error),\n });\n trackerRef.current = tracker;\n if (autoStart) tracker.start();\n\n return () => {\n tracker.stop();\n trackerRef.current = null;\n };\n // Tracker options are read once at creation; changing them mid-track\n // would drop the recorded trajectory, so they are intentionally omitted.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n const start = useCallback((): void => trackerRef.current?.start(), []);\n const stop = useCallback((): void => trackerRef.current?.stop(), []);\n const clear = useCallback((): void => {\n trackerRef.current?.clear();\n setDistanceKm(0);\n }, []);\n\n return {\n points,\n lastPoint: points.length > 0 ? points[points.length - 1] : null,\n distanceKm,\n status,\n isTracking: status === \"tracking\",\n start,\n stop,\n clear,\n };\n}\n"],"mappings":"wEAqDA,SAAgB,EACZ,EAAqC,CAAC,EACd,CACxB,GAAM,CAAE,YAAY,GAAO,UAAS,GAAG,GAAmB,EAEpD,CAAC,EAAQ,IAAA,EAAa,EAAA,SAAA,CAAgC,CAAC,CAAC,EACxD,CAAC,EAAY,IAAA,EAAiB,EAAA,SAAA,CAAiB,CAAC,EAChD,CAAC,EAAQ,IAAA,EAAa,EAAA,SAAA,CAAwB,MAAM,EAEpD,GAAA,EAAa,EAAA,OAAA,CAA+B,IAAI,EAChD,GAAA,EAAa,EAAA,OAAA,CAAO,CAAO,GACjC,EAAA,EAAA,UAAA,KAAgB,CACZ,EAAW,QAAU,CACzB,EAAG,CAAC,CAAO,CAAC,GAEZ,EAAA,EAAA,UAAA,KAAgB,CACZ,IAAM,EAAU,EAAA,sBAAsB,CAClC,GAAG,EACH,SAAW,GAAS,CAChB,EAAU,CAAC,GAAG,CAAI,CAAC,EACnB,EAAc,EAAQ,UAAU,CACpC,EACA,eAAgB,EAChB,QAAU,GAAU,EAAW,UAAU,CAAK,CAClD,CAAC,EAID,MAHA,GAAW,QAAU,EACjB,GAAW,EAAQ,MAAM,MAEhB,CACT,EAAQ,KAAK,EACb,EAAW,QAAU,IACzB,CAIJ,EAAG,CAAC,CAAC,EAEL,IAAM,GAAA,EAAQ,EAAA,YAAA,KAAwB,EAAW,SAAS,MAAM,EAAG,CAAC,CAAC,EAC/D,GAAA,EAAO,EAAA,YAAA,KAAwB,EAAW,SAAS,KAAK,EAAG,CAAC,CAAC,EAC7D,GAAA,EAAQ,EAAA,YAAA,KAAwB,CAClC,EAAW,SAAS,MAAM,EAC1B,EAAc,CAAC,CACnB,EAAG,CAAC,CAAC,EAEL,MAAO,CACH,SACA,UAAW,EAAO,OAAS,EAAI,EAAO,EAAO,OAAS,GAAK,KAC3D,aACA,SACA,WAAY,IAAW,WACvB,QACA,OACA,OACJ,CACJ"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-announce.cjs","names":[],"sources":["../../src/hooks/use-announce.ts"],"sourcesContent":["import { useCallback, useEffect } from \"react\";\n\n/**\n * How urgently a screen reader should interrupt.\n *\n * `\"polite\"` waits for a pause in what is being read. `\"assertive\"` cuts in\n * immediately, which is right for an error the user must act on and wrong for\n * everything else — an assertive announcement can truncate the sentence the user\n * was in the middle of.\n */\nexport type AnnouncePoliteness = \"polite\" | \"assertive\";\n\n/** How long an announcement stays in the DOM before it is cleaned up. */\nconst CLEAR_AFTER_MS = 7_000;\n\n/**\n * Inline styles that hide an element visually while leaving it readable.\n *\n * Inline and not a CSS module on purpose: these nodes are created imperatively by\n * a hook, and a hook that only works when the app remembered to import\n * `tempest-react-sdk/styles.css` would drop two visible, unstyled paragraphs into\n * the page — a visual bug caused by an accessibility feature.\n */\nconst HIDDEN_STYLE: Partial<CSSStyleDeclaration> = {\n position: \"absolute\",\n width: \"1px\",\n height: \"1px\",\n margin: \"-1px\",\n padding: \"0\",\n border: \"0\",\n overflow: \"hidden\",\n clip: \"rect(0 0 0 0)\",\n clipPath: \"inset(50%)\",\n whiteSpace: \"nowrap\",\n};\n\ninterface Regions {\n polite: HTMLElement;\n assertive: HTMLElement;\n}\n\nlet regions: Regions | null = null;\nconst timers = new Set<ReturnType<typeof setTimeout>>();\n\n/**\n * Build one live region.\n *\n * The polite region carries `role=\"status\"`; the assertive one carries only\n * `aria-live=\"assertive\"`. `role=\"alert\"` is deliberately **not** used: these\n * regions live in the document for the page's whole life, and an empty element\n * claiming to be an alert is both a lie about its content and a trap for every\n * `getByRole(\"alert\")` in a consuming app's test suite, which would suddenly match\n * two nodes. A bare `aria-live` region is announced by every screen reader that\n * supports live regions at all.\n *\n * @param politeness - Which region to build.\n * @returns The region, already appended to `<body>`.\n */\nfunction createRegion(politeness: AnnouncePoliteness): HTMLElement {\n const node = document.createElement(\"div\");\n if (politeness === \"polite\") node.setAttribute(\"role\", \"status\");\n node.setAttribute(\"aria-live\", politeness);\n node.setAttribute(\"aria-atomic\", \"true\");\n node.setAttribute(\"data-tempest-announcer\", politeness);\n Object.assign(node.style, HIDDEN_STYLE);\n document.body.appendChild(node);\n return node;\n}\n\n/**\n * Get the two shared live regions, creating them on first use.\n *\n * **Two regions, not one with a switchable `aria-live`.** Politeness is a property\n * of the region, read when the assistive technology first registers it — flipping\n * the attribute later is honoured by some screen readers, ignored by others, and\n * in the worst case drops the announcement entirely. Two regions that never change\n * are the only version that behaves the same everywhere.\n *\n * **Shared, not one per component.** Every live region on the page is polled;\n * several of them mutating at once is how announcements get dropped or doubled.\n * One pair per document keeps the order deterministic.\n *\n * A pair is replaced as a **pair**, discarding the old one: if only one region was\n * torn out of the DOM (a router that replaced `<body>`, a micro-frontend unmount),\n * keeping its still-attached sibling would leave two regions of that politeness on\n * the page — the exact duplication the shared pair exists to prevent.\n *\n * @returns The polite and assertive regions, or `null` outside a document.\n */\nfunction ensureRegions(): Regions | null {\n if (typeof document === \"undefined\") return null;\n if (regions && regions.polite.isConnected && regions.assertive.isConnected) return regions;\n regions?.polite.remove();\n regions?.assertive.remove();\n regions = { polite: createRegion(\"polite\"), assertive: createRegion(\"assertive\") };\n return regions;\n}\n\n/**\n * Announce a message to screen readers, from anywhere — a hook, an event handler,\n * a plain function outside React.\n *\n * ## Why the same string announces twice\n *\n * Screen readers announce a live region when its **content changes**. Writing the\n * same text again is not a change, so \"Item removido\" twice in a row is read once —\n * the classic reason these announcers are quietly broken. Instead of mutating text,\n * every call replaces the region's child with a **new element**. The DOM mutation is\n * real even when the string is identical, so the second announcement happens, and\n * the reader hears the exact message with no padding characters bolted on.\n *\n * @param message - Text to read out. Empty strings are ignored.\n * @param politeness - `\"polite\"` (default) or `\"assertive\"`.\n *\n * @example\n * announce(`${count} pedidos encontrados`);\n * announce(\"Falha ao salvar\", \"assertive\");\n */\nexport function announce(message: string, politeness: AnnouncePoliteness = \"polite\"): void {\n if (!message) return;\n const pair = ensureRegions();\n if (!pair) return;\n\n const region = politeness === \"assertive\" ? pair.assertive : pair.polite;\n region.replaceChildren();\n const item = document.createElement(\"div\");\n item.textContent = message;\n region.appendChild(item);\n\n const timer = setTimeout(() => {\n timers.delete(timer);\n if (item.isConnected) item.remove();\n }, CLEAR_AFTER_MS);\n timers.add(timer);\n}\n\n/**\n * Remove the shared regions and cancel pending cleanups.\n *\n * For test teardown and for a micro-frontend being unmounted from a page it does\n * not own. Regular apps never need it — two empty hidden `div`s cost nothing, and\n * tearing them down while another component still announces would lose messages.\n */\nexport function clearAnnouncer(): void {\n for (const timer of timers) clearTimeout(timer);\n timers.clear();\n regions?.polite.remove();\n regions?.assertive.remove();\n regions = null;\n}\n\n/**\n * Announce transient messages to screen readers through one shared live region\n * pair.\n *\n * Reach for this when something happened that a sighted user can see and a screen\n * reader user cannot: a filter narrowed a list, a row saved, a copy succeeded, an\n * upload failed. It is **not** for content that is already on screen inside a\n * region with a role — a status pill, a toast that renders as `role=\"status\"`,\n * a form error tied to its input — announcing those again reads them twice.\n *\n * !!! warning \"Never wrap streaming text in a live region\"\n * A live region over text that grows token by token makes the reader start the\n * whole answer again on every token. Announce the **edges** instead — \"gerando\n * resposta\" and \"resposta concluída\" — and leave the transcript in a plain\n * `role=\"log\"` the user reads at their own pace. `AIChat` does exactly that.\n *\n * Mounting the hook creates the regions even before the first message. That is not\n * tidiness: a live region inserted into the DOM in the same frame as its first\n * content routinely loses that announcement, because the assistive technology has\n * to have registered the region before it can notice a change inside it.\n *\n * @returns A stable `announce(message, politeness?)` function.\n *\n * @example\n * const announce = useAnnounce();\n *\n * function onFilter(rows: Row[]) {\n * announce(`${rows.length} resultados`);\n * }\n */\nexport function useAnnounce(): (message: string, politeness?: AnnouncePoliteness) => void {\n useEffect(() => {\n ensureRegions();\n }, []);\n\n return useCallback(\n (message: string, politeness: AnnouncePoliteness = \"polite\") =>\n announce(message, politeness),\n [],\n );\n}\n"],"mappings":"uBAaA,IAAM,EAAiB,IAUjB,EAA6C,CAC/C,SAAU,WACV,MAAO,MACP,OAAQ,MACR,OAAQ,OACR,QAAS,IACT,OAAQ,IACR,SAAU,SACV,KAAM,gBACN,SAAU,aACV,WAAY,QAChB,EAOI,EAA0B,KACxB,EAAS,IAAI,IAgBnB,SAAS,EAAa,EAA6C,CAC/D,IAAM,EAAO,SAAS,cAAc,KAAK,EAOzC,OANI,IAAe,UAAU,EAAK,aAAa,OAAQ,QAAQ,EAC/D,EAAK,aAAa,YAAa,CAAU,EACzC,EAAK,aAAa,cAAe,MAAM,EACvC,EAAK,aAAa,yBAA0B,CAAU,EACtD,OAAO,OAAO,EAAK,MAAO,CAAY,EACtC,SAAS,KAAK,YAAY,CAAI,EACvB,CACX,CAsBA,SAAS,GAAgC,CAMrC,OALI,OAAO,SAAa,IAAoB,KACxC,GAAW,EAAQ,OAAO,aAAe,EAAQ,UAAU,YAAoB,GACnF,GAAS,OAAO,OAAO,EACvB,GAAS,UAAU,OAAO,EAC1B,EAAU,CAAE,OAAQ,EAAa,QAAQ,EAAG,UAAW,EAAa,WAAW,CAAE,EAC1E,EACX,CAsBA,SAAgB,EAAS,EAAiB,EAAiC,SAAgB,CACvF,GAAI,CAAC,EAAS,OACd,IAAM,EAAO,EAAc,EAC3B,GAAI,CAAC,EAAM,OAEX,IAAM,EAAS,IAAe,YAAc,EAAK,UAAY,EAAK,OAClE,EAAO,gBAAgB,EACvB,IAAM,EAAO,SAAS,cAAc,KAAK,EACzC,EAAK,YAAc,EACnB,EAAO,YAAY,CAAI,EAEvB,IAAM,EAAQ,eAAiB,CAC3B,EAAO,OAAO,CAAK,EACf,EAAK,aAAa,EAAK,OAAO,CACtC,EAAG,CAAc,EACjB,EAAO,IAAI,CAAK,CACpB,CASA,SAAgB,GAAuB,CACnC,IAAK,IAAM,KAAS,EAAQ,aAAa,CAAK,EAC9C,EAAO,MAAM,EACb,GAAS,OAAO,OAAO,EACvB,GAAS,UAAU,OAAO,EAC1B,EAAU,IACd,CAgCA,SAAgB,GAA0E,CAKtF,OAJA,EAAA,EAAA,UAAA,KAAgB,CACZ,EAAc,CAClB,EAAG,CAAC,CAAC,GAEL,
|
|
1
|
+
{"version":3,"file":"use-announce.cjs","names":[],"sources":["../../src/hooks/use-announce.ts"],"sourcesContent":["import { useCallback, useEffect } from \"react\";\n\n/**\n * How urgently a screen reader should interrupt.\n *\n * `\"polite\"` waits for a pause in what is being read. `\"assertive\"` cuts in\n * immediately, which is right for an error the user must act on and wrong for\n * everything else — an assertive announcement can truncate the sentence the user\n * was in the middle of.\n */\nexport type AnnouncePoliteness = \"polite\" | \"assertive\";\n\n/** How long an announcement stays in the DOM before it is cleaned up. */\nconst CLEAR_AFTER_MS = 7_000;\n\n/**\n * Inline styles that hide an element visually while leaving it readable.\n *\n * Inline and not a CSS module on purpose: these nodes are created imperatively by\n * a hook, and a hook that only works when the app remembered to import\n * `tempest-react-sdk/styles.css` would drop two visible, unstyled paragraphs into\n * the page — a visual bug caused by an accessibility feature.\n */\nconst HIDDEN_STYLE: Partial<CSSStyleDeclaration> = {\n position: \"absolute\",\n width: \"1px\",\n height: \"1px\",\n margin: \"-1px\",\n padding: \"0\",\n border: \"0\",\n overflow: \"hidden\",\n clip: \"rect(0 0 0 0)\",\n clipPath: \"inset(50%)\",\n whiteSpace: \"nowrap\",\n};\n\ninterface Regions {\n polite: HTMLElement;\n assertive: HTMLElement;\n}\n\nlet regions: Regions | null = null;\nconst timers = new Set<ReturnType<typeof setTimeout>>();\n\n/**\n * Build one live region.\n *\n * The polite region carries `role=\"status\"`; the assertive one carries only\n * `aria-live=\"assertive\"`. `role=\"alert\"` is deliberately **not** used: these\n * regions live in the document for the page's whole life, and an empty element\n * claiming to be an alert is both a lie about its content and a trap for every\n * `getByRole(\"alert\")` in a consuming app's test suite, which would suddenly match\n * two nodes. A bare `aria-live` region is announced by every screen reader that\n * supports live regions at all.\n *\n * @param politeness - Which region to build.\n * @returns The region, already appended to `<body>`.\n */\nfunction createRegion(politeness: AnnouncePoliteness): HTMLElement {\n const node = document.createElement(\"div\");\n if (politeness === \"polite\") node.setAttribute(\"role\", \"status\");\n node.setAttribute(\"aria-live\", politeness);\n node.setAttribute(\"aria-atomic\", \"true\");\n node.setAttribute(\"data-tempest-announcer\", politeness);\n Object.assign(node.style, HIDDEN_STYLE);\n document.body.appendChild(node);\n return node;\n}\n\n/**\n * Get the two shared live regions, creating them on first use.\n *\n * **Two regions, not one with a switchable `aria-live`.** Politeness is a property\n * of the region, read when the assistive technology first registers it — flipping\n * the attribute later is honoured by some screen readers, ignored by others, and\n * in the worst case drops the announcement entirely. Two regions that never change\n * are the only version that behaves the same everywhere.\n *\n * **Shared, not one per component.** Every live region on the page is polled;\n * several of them mutating at once is how announcements get dropped or doubled.\n * One pair per document keeps the order deterministic.\n *\n * A pair is replaced as a **pair**, discarding the old one: if only one region was\n * torn out of the DOM (a router that replaced `<body>`, a micro-frontend unmount),\n * keeping its still-attached sibling would leave two regions of that politeness on\n * the page — the exact duplication the shared pair exists to prevent.\n *\n * @returns The polite and assertive regions, or `null` outside a document.\n */\nfunction ensureRegions(): Regions | null {\n if (typeof document === \"undefined\") return null;\n if (regions && regions.polite.isConnected && regions.assertive.isConnected) return regions;\n regions?.polite.remove();\n regions?.assertive.remove();\n regions = { polite: createRegion(\"polite\"), assertive: createRegion(\"assertive\") };\n return regions;\n}\n\n/**\n * Announce a message to screen readers, from anywhere — a hook, an event handler,\n * a plain function outside React.\n *\n * ## Why the same string announces twice\n *\n * Screen readers announce a live region when its **content changes**. Writing the\n * same text again is not a change, so \"Item removido\" twice in a row is read once —\n * the classic reason these announcers are quietly broken. Instead of mutating text,\n * every call replaces the region's child with a **new element**. The DOM mutation is\n * real even when the string is identical, so the second announcement happens, and\n * the reader hears the exact message with no padding characters bolted on.\n *\n * @param message - Text to read out. Empty strings are ignored.\n * @param politeness - `\"polite\"` (default) or `\"assertive\"`.\n *\n * @example\n * announce(`${count} pedidos encontrados`);\n * announce(\"Falha ao salvar\", \"assertive\");\n */\nexport function announce(message: string, politeness: AnnouncePoliteness = \"polite\"): void {\n if (!message) return;\n const pair = ensureRegions();\n if (!pair) return;\n\n const region = politeness === \"assertive\" ? pair.assertive : pair.polite;\n region.replaceChildren();\n const item = document.createElement(\"div\");\n item.textContent = message;\n region.appendChild(item);\n\n const timer = setTimeout(() => {\n timers.delete(timer);\n if (item.isConnected) item.remove();\n }, CLEAR_AFTER_MS);\n timers.add(timer);\n}\n\n/**\n * Remove the shared regions and cancel pending cleanups.\n *\n * For test teardown and for a micro-frontend being unmounted from a page it does\n * not own. Regular apps never need it — two empty hidden `div`s cost nothing, and\n * tearing them down while another component still announces would lose messages.\n */\nexport function clearAnnouncer(): void {\n for (const timer of timers) clearTimeout(timer);\n timers.clear();\n regions?.polite.remove();\n regions?.assertive.remove();\n regions = null;\n}\n\n/**\n * Announce transient messages to screen readers through one shared live region\n * pair.\n *\n * Reach for this when something happened that a sighted user can see and a screen\n * reader user cannot: a filter narrowed a list, a row saved, a copy succeeded, an\n * upload failed. It is **not** for content that is already on screen inside a\n * region with a role — a status pill, a toast that renders as `role=\"status\"`,\n * a form error tied to its input — announcing those again reads them twice.\n *\n * !!! warning \"Never wrap streaming text in a live region\"\n * A live region over text that grows token by token makes the reader start the\n * whole answer again on every token. Announce the **edges** instead — \"gerando\n * resposta\" and \"resposta concluída\" — and leave the transcript in a plain\n * `role=\"log\"` the user reads at their own pace. `AIChat` does exactly that.\n *\n * Mounting the hook creates the regions even before the first message. That is not\n * tidiness: a live region inserted into the DOM in the same frame as its first\n * content routinely loses that announcement, because the assistive technology has\n * to have registered the region before it can notice a change inside it.\n *\n * @returns A stable `announce(message, politeness?)` function.\n *\n * @example\n * const announce = useAnnounce();\n *\n * function onFilter(rows: Row[]) {\n * announce(`${rows.length} resultados`);\n * }\n */\nexport function useAnnounce(): (message: string, politeness?: AnnouncePoliteness) => void {\n useEffect(() => {\n ensureRegions();\n }, []);\n\n return useCallback(\n (message: string, politeness: AnnouncePoliteness = \"polite\") =>\n announce(message, politeness),\n [],\n );\n}\n"],"mappings":"uBAaA,IAAM,EAAiB,IAUjB,EAA6C,CAC/C,SAAU,WACV,MAAO,MACP,OAAQ,MACR,OAAQ,OACR,QAAS,IACT,OAAQ,IACR,SAAU,SACV,KAAM,gBACN,SAAU,aACV,WAAY,QAChB,EAOI,EAA0B,KACxB,EAAS,IAAI,IAgBnB,SAAS,EAAa,EAA6C,CAC/D,IAAM,EAAO,SAAS,cAAc,KAAK,EAOzC,OANI,IAAe,UAAU,EAAK,aAAa,OAAQ,QAAQ,EAC/D,EAAK,aAAa,YAAa,CAAU,EACzC,EAAK,aAAa,cAAe,MAAM,EACvC,EAAK,aAAa,yBAA0B,CAAU,EACtD,OAAO,OAAO,EAAK,MAAO,CAAY,EACtC,SAAS,KAAK,YAAY,CAAI,EACvB,CACX,CAsBA,SAAS,GAAgC,CAMrC,OALI,OAAO,SAAa,IAAoB,KACxC,GAAW,EAAQ,OAAO,aAAe,EAAQ,UAAU,YAAoB,GACnF,GAAS,OAAO,OAAO,EACvB,GAAS,UAAU,OAAO,EAC1B,EAAU,CAAE,OAAQ,EAAa,QAAQ,EAAG,UAAW,EAAa,WAAW,CAAE,EAC1E,EACX,CAsBA,SAAgB,EAAS,EAAiB,EAAiC,SAAgB,CACvF,GAAI,CAAC,EAAS,OACd,IAAM,EAAO,EAAc,EAC3B,GAAI,CAAC,EAAM,OAEX,IAAM,EAAS,IAAe,YAAc,EAAK,UAAY,EAAK,OAClE,EAAO,gBAAgB,EACvB,IAAM,EAAO,SAAS,cAAc,KAAK,EACzC,EAAK,YAAc,EACnB,EAAO,YAAY,CAAI,EAEvB,IAAM,EAAQ,eAAiB,CAC3B,EAAO,OAAO,CAAK,EACf,EAAK,aAAa,EAAK,OAAO,CACtC,EAAG,CAAc,EACjB,EAAO,IAAI,CAAK,CACpB,CASA,SAAgB,GAAuB,CACnC,IAAK,IAAM,KAAS,EAAQ,aAAa,CAAK,EAC9C,EAAO,MAAM,EACb,GAAS,OAAO,OAAO,EACvB,GAAS,UAAU,OAAO,EAC1B,EAAU,IACd,CAgCA,SAAgB,GAA0E,CAKtF,OAJA,EAAA,EAAA,UAAA,KAAgB,CACZ,EAAc,CAClB,EAAG,CAAC,CAAC,GAEL,EAAO,EAAA,YAAA,EACF,EAAiB,EAAiC,WAC/C,EAAS,EAAS,CAAU,EAChC,CAAC,CACL,CACJ"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-async.cjs","names":[],"sources":["../../src/hooks/use-async.ts"],"sourcesContent":["import { useCallback, useEffect, useRef, useState } from \"react\";\n\nexport type AsyncStatus = \"idle\" | \"pending\" | \"success\" | \"error\";\n\nexport interface UseAsyncResult<T> {\n status: AsyncStatus;\n data: T | undefined;\n error: unknown;\n isPending: boolean;\n isSuccess: boolean;\n isError: boolean;\n /** Trigger the async function. Resolves with the new data or rejects. */\n run: () => Promise<T>;\n /** Reset state back to idle. */\n reset: () => void;\n}\n\n/**\n * Run an async function and track its `idle/pending/success/error` state.\n *\n * - Discards results from stale runs (race-condition safe).\n * - `immediate` (default `false`) triggers the function on mount and when\n * `deps` change. With `false`, call `run()` manually.\n * - Returns a stable object so callers can destructure or pass around safely.\n *\n * For server data with caching, prefer React Query — `useAsync` is the\n * minimal one-shot primitive without dependencies.\n */\nexport function useAsync<T>(\n asyncFn: () => Promise<T>,\n deps: ReadonlyArray<unknown> = [],\n options: { immediate?: boolean } = {},\n): UseAsyncResult<T> {\n const { immediate = false } = options;\n const [state, setState] = useState<{\n status: AsyncStatus;\n data: T | undefined;\n error: unknown;\n }>({ status: \"idle\", data: undefined, error: undefined });\n\n const fnRef = useRef(asyncFn);\n useEffect(() => {\n fnRef.current = asyncFn;\n }, [asyncFn]);\n const callIdRef = useRef<number>(0);\n const mountedRef = useRef<boolean>(true);\n\n useEffect(() => {\n mountedRef.current = true;\n return () => {\n mountedRef.current = false;\n };\n }, []);\n\n const run = useCallback(async (): Promise<T> => {\n const callId = ++callIdRef.current;\n setState((current) => ({ ...current, status: \"pending\", error: undefined }));\n try {\n const data = await fnRef.current();\n if (mountedRef.current && callIdRef.current === callId) {\n setState({ status: \"success\", data, error: undefined });\n }\n return data;\n } catch (error) {\n if (mountedRef.current && callIdRef.current === callId) {\n setState({ status: \"error\", data: undefined, error });\n }\n throw error;\n }\n }, []);\n\n const reset = useCallback((): void => {\n callIdRef.current++;\n setState({ status: \"idle\", data: undefined, error: undefined });\n }, []);\n\n useEffect(() => {\n if (!immediate) return;\n run().catch(() => {\n /* error already captured in state */\n });\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [immediate, ...deps]);\n\n return {\n status: state.status,\n data: state.data,\n error: state.error,\n isPending: state.status === \"pending\",\n isSuccess: state.status === \"success\",\n isError: state.status === \"error\",\n run,\n reset,\n };\n}\n"],"mappings":"uBA4BA,SAAgB,EACZ,EACA,EAA+B,CAAC,EAChC,EAAmC,CAAC,EACnB,CACjB,GAAM,CAAE,YAAY,IAAU,EACxB,CAAC,EAAO,IAAA,
|
|
1
|
+
{"version":3,"file":"use-async.cjs","names":[],"sources":["../../src/hooks/use-async.ts"],"sourcesContent":["import { useCallback, useEffect, useRef, useState } from \"react\";\n\nexport type AsyncStatus = \"idle\" | \"pending\" | \"success\" | \"error\";\n\nexport interface UseAsyncResult<T> {\n status: AsyncStatus;\n data: T | undefined;\n error: unknown;\n isPending: boolean;\n isSuccess: boolean;\n isError: boolean;\n /** Trigger the async function. Resolves with the new data or rejects. */\n run: () => Promise<T>;\n /** Reset state back to idle. */\n reset: () => void;\n}\n\n/**\n * Run an async function and track its `idle/pending/success/error` state.\n *\n * - Discards results from stale runs (race-condition safe).\n * - `immediate` (default `false`) triggers the function on mount and when\n * `deps` change. With `false`, call `run()` manually.\n * - Returns a stable object so callers can destructure or pass around safely.\n *\n * For server data with caching, prefer React Query — `useAsync` is the\n * minimal one-shot primitive without dependencies.\n */\nexport function useAsync<T>(\n asyncFn: () => Promise<T>,\n deps: ReadonlyArray<unknown> = [],\n options: { immediate?: boolean } = {},\n): UseAsyncResult<T> {\n const { immediate = false } = options;\n const [state, setState] = useState<{\n status: AsyncStatus;\n data: T | undefined;\n error: unknown;\n }>({ status: \"idle\", data: undefined, error: undefined });\n\n const fnRef = useRef(asyncFn);\n useEffect(() => {\n fnRef.current = asyncFn;\n }, [asyncFn]);\n const callIdRef = useRef<number>(0);\n const mountedRef = useRef<boolean>(true);\n\n useEffect(() => {\n mountedRef.current = true;\n return () => {\n mountedRef.current = false;\n };\n }, []);\n\n const run = useCallback(async (): Promise<T> => {\n const callId = ++callIdRef.current;\n setState((current) => ({ ...current, status: \"pending\", error: undefined }));\n try {\n const data = await fnRef.current();\n if (mountedRef.current && callIdRef.current === callId) {\n setState({ status: \"success\", data, error: undefined });\n }\n return data;\n } catch (error) {\n if (mountedRef.current && callIdRef.current === callId) {\n setState({ status: \"error\", data: undefined, error });\n }\n throw error;\n }\n }, []);\n\n const reset = useCallback((): void => {\n callIdRef.current++;\n setState({ status: \"idle\", data: undefined, error: undefined });\n }, []);\n\n useEffect(() => {\n if (!immediate) return;\n run().catch(() => {\n /* error already captured in state */\n });\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [immediate, ...deps]);\n\n return {\n status: state.status,\n data: state.data,\n error: state.error,\n isPending: state.status === \"pending\",\n isSuccess: state.status === \"success\",\n isError: state.status === \"error\",\n run,\n reset,\n };\n}\n"],"mappings":"uBA4BA,SAAgB,EACZ,EACA,EAA+B,CAAC,EAChC,EAAmC,CAAC,EACnB,CACjB,GAAM,CAAE,YAAY,IAAU,EACxB,CAAC,EAAO,IAAA,EAAY,EAAA,SAAA,CAIvB,CAAE,OAAQ,OAAQ,KAAM,IAAA,GAAW,MAAO,IAAA,EAAU,CAAC,EAElD,GAAA,EAAQ,EAAA,OAAA,CAAO,CAAO,GAC5B,EAAA,EAAA,UAAA,KAAgB,CACZ,EAAM,QAAU,CACpB,EAAG,CAAC,CAAO,CAAC,EACZ,IAAM,GAAA,EAAY,EAAA,OAAA,CAAe,CAAC,EAC5B,GAAA,EAAa,EAAA,OAAA,CAAgB,EAAI,GAEvC,EAAA,EAAA,UAAA,MACI,EAAW,QAAU,OACR,CACT,EAAW,QAAU,EACzB,GACD,CAAC,CAAC,EAEL,IAAM,GAAA,EAAM,EAAA,YAAA,CAAY,SAAwB,CAC5C,IAAM,EAAS,EAAE,EAAU,QAC3B,EAAU,IAAa,CAAE,GAAG,EAAS,OAAQ,UAAW,MAAO,IAAA,EAAU,EAAE,EAC3E,GAAI,CACA,IAAM,EAAO,MAAM,EAAM,QAAQ,EAIjC,OAHI,EAAW,SAAW,EAAU,UAAY,GAC5C,EAAS,CAAE,OAAQ,UAAW,OAAM,MAAO,IAAA,EAAU,CAAC,EAEnD,CACX,OAAS,EAAO,CAIZ,MAHI,EAAW,SAAW,EAAU,UAAY,GAC5C,EAAS,CAAE,OAAQ,QAAS,KAAM,IAAA,GAAW,OAAM,CAAC,EAElD,CACV,CACJ,EAAG,CAAC,CAAC,EAEC,GAAA,EAAQ,EAAA,YAAA,KAAwB,CAClC,EAAU,UACV,EAAS,CAAE,OAAQ,OAAQ,KAAM,IAAA,GAAW,MAAO,IAAA,EAAU,CAAC,CAClE,EAAG,CAAC,CAAC,EAUL,OARA,EAAA,EAAA,UAAA,KAAgB,CACP,GACL,EAAI,CAAC,CAAC,UAAY,CAElB,CAAC,CAEL,EAAG,CAAC,EAAW,GAAG,CAAI,CAAC,EAEhB,CACH,OAAQ,EAAM,OACd,KAAM,EAAM,KACZ,MAAO,EAAM,MACb,UAAW,EAAM,SAAW,UAC5B,UAAW,EAAM,SAAW,UAC5B,QAAS,EAAM,SAAW,QAC1B,MACA,OACJ,CACJ"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-before-install-prompt.cjs","names":[],"sources":["../../src/hooks/use-before-install-prompt.ts"],"sourcesContent":["import { useCallback, useEffect, useState } from \"react\";\n\ninterface BeforeInstallPromptEvent extends Event {\n readonly platforms: readonly string[];\n readonly userChoice: Promise<{ outcome: \"accepted\" | \"dismissed\"; platform: string }>;\n prompt(): Promise<void>;\n}\n\nexport interface UseBeforeInstallPromptResult {\n /** True when the browser fired `beforeinstallprompt` and the user has not yet decided. */\n installable: boolean;\n /** True after the user accepts the install prompt. */\n installed: boolean;\n /**\n * True when the app is already running as an installed PWA (display-mode\n * `standalone`/`fullscreen`/`minimal-ui`, or iOS `navigator.standalone`).\n * Use it to hide install affordances for users who already installed.\n */\n isStandalone: boolean;\n /** Show the install prompt. Resolves with the user's choice. */\n prompt: () => Promise<\"accepted\" | \"dismissed\" | \"unsupported\">;\n}\n\n/** Detect whether the document is running inside an installed PWA window. */\nfunction detectStandalone(): boolean {\n if (typeof window === \"undefined\") return false;\n const displayModes = [\"standalone\", \"fullscreen\", \"minimal-ui\"];\n const matchesDisplayMode = displayModes.some(\n (mode) => window.matchMedia?.(`(display-mode: ${mode})`)?.matches,\n );\n // iOS Safari exposes the legacy non-standard `navigator.standalone`.\n const iosStandalone =\n (window.navigator as Navigator & { standalone?: boolean }).standalone === true;\n return matchesDisplayMode || iosStandalone;\n}\n\n/**\n * React hook for the PWA install prompt. Captures the `beforeinstallprompt`\n * event so you can defer the install UI to a moment that fits your UX.\n */\nexport function useBeforeInstallPrompt(): UseBeforeInstallPromptResult {\n const [deferred, setDeferred] = useState<BeforeInstallPromptEvent | null>(null);\n const [installed, setInstalled] = useState<boolean>(false);\n const [isStandalone, setIsStandalone] = useState<boolean>(detectStandalone);\n\n useEffect(() => {\n if (typeof window === \"undefined\") return;\n const handler = (event: Event): void => {\n event.preventDefault();\n setDeferred(event as BeforeInstallPromptEvent);\n };\n const installedHandler = (): void => {\n setDeferred(null);\n setInstalled(true);\n setIsStandalone(true);\n };\n const displayModeQuery = window.matchMedia?.(\"(display-mode: standalone)\");\n const displayModeHandler = (): void => setIsStandalone(detectStandalone());\n window.addEventListener(\"beforeinstallprompt\", handler);\n window.addEventListener(\"appinstalled\", installedHandler);\n displayModeQuery?.addEventListener(\"change\", displayModeHandler);\n return () => {\n window.removeEventListener(\"beforeinstallprompt\", handler);\n window.removeEventListener(\"appinstalled\", installedHandler);\n displayModeQuery?.removeEventListener(\"change\", displayModeHandler);\n };\n }, []);\n\n const prompt = useCallback(async (): Promise<\"accepted\" | \"dismissed\" | \"unsupported\"> => {\n if (!deferred) return \"unsupported\";\n await deferred.prompt();\n const result = await deferred.userChoice;\n setDeferred(null);\n if (result.outcome === \"accepted\") setInstalled(true);\n return result.outcome;\n }, [deferred]);\n\n return {\n installable: !!deferred,\n installed,\n isStandalone,\n prompt,\n };\n}\n"],"mappings":"uBAwBA,SAAS,GAA4B,CACjC,GAAI,OAAO,OAAW,IAAa,MAAO,GAE1C,IAAM,EAAqB,CADL,aAAc,aAAc,YACvB,CAAA,CAAa,KACnC,GAAS,OAAO,aAAa,kBAAkB,EAAK,EAAE,CAAC,EAAE,OAC9D,EAEM,EACD,OAAO,UAAmD,aAAe,GAC9E,OAAO,GAAsB,CACjC,CAMA,SAAgB,GAAuD,CACnE,GAAM,CAAC,EAAU,IAAA,
|
|
1
|
+
{"version":3,"file":"use-before-install-prompt.cjs","names":[],"sources":["../../src/hooks/use-before-install-prompt.ts"],"sourcesContent":["import { useCallback, useEffect, useState } from \"react\";\n\ninterface BeforeInstallPromptEvent extends Event {\n readonly platforms: readonly string[];\n readonly userChoice: Promise<{ outcome: \"accepted\" | \"dismissed\"; platform: string }>;\n prompt(): Promise<void>;\n}\n\nexport interface UseBeforeInstallPromptResult {\n /** True when the browser fired `beforeinstallprompt` and the user has not yet decided. */\n installable: boolean;\n /** True after the user accepts the install prompt. */\n installed: boolean;\n /**\n * True when the app is already running as an installed PWA (display-mode\n * `standalone`/`fullscreen`/`minimal-ui`, or iOS `navigator.standalone`).\n * Use it to hide install affordances for users who already installed.\n */\n isStandalone: boolean;\n /** Show the install prompt. Resolves with the user's choice. */\n prompt: () => Promise<\"accepted\" | \"dismissed\" | \"unsupported\">;\n}\n\n/** Detect whether the document is running inside an installed PWA window. */\nfunction detectStandalone(): boolean {\n if (typeof window === \"undefined\") return false;\n const displayModes = [\"standalone\", \"fullscreen\", \"minimal-ui\"];\n const matchesDisplayMode = displayModes.some(\n (mode) => window.matchMedia?.(`(display-mode: ${mode})`)?.matches,\n );\n // iOS Safari exposes the legacy non-standard `navigator.standalone`.\n const iosStandalone =\n (window.navigator as Navigator & { standalone?: boolean }).standalone === true;\n return matchesDisplayMode || iosStandalone;\n}\n\n/**\n * React hook for the PWA install prompt. Captures the `beforeinstallprompt`\n * event so you can defer the install UI to a moment that fits your UX.\n */\nexport function useBeforeInstallPrompt(): UseBeforeInstallPromptResult {\n const [deferred, setDeferred] = useState<BeforeInstallPromptEvent | null>(null);\n const [installed, setInstalled] = useState<boolean>(false);\n const [isStandalone, setIsStandalone] = useState<boolean>(detectStandalone);\n\n useEffect(() => {\n if (typeof window === \"undefined\") return;\n const handler = (event: Event): void => {\n event.preventDefault();\n setDeferred(event as BeforeInstallPromptEvent);\n };\n const installedHandler = (): void => {\n setDeferred(null);\n setInstalled(true);\n setIsStandalone(true);\n };\n const displayModeQuery = window.matchMedia?.(\"(display-mode: standalone)\");\n const displayModeHandler = (): void => setIsStandalone(detectStandalone());\n window.addEventListener(\"beforeinstallprompt\", handler);\n window.addEventListener(\"appinstalled\", installedHandler);\n displayModeQuery?.addEventListener(\"change\", displayModeHandler);\n return () => {\n window.removeEventListener(\"beforeinstallprompt\", handler);\n window.removeEventListener(\"appinstalled\", installedHandler);\n displayModeQuery?.removeEventListener(\"change\", displayModeHandler);\n };\n }, []);\n\n const prompt = useCallback(async (): Promise<\"accepted\" | \"dismissed\" | \"unsupported\"> => {\n if (!deferred) return \"unsupported\";\n await deferred.prompt();\n const result = await deferred.userChoice;\n setDeferred(null);\n if (result.outcome === \"accepted\") setInstalled(true);\n return result.outcome;\n }, [deferred]);\n\n return {\n installable: !!deferred,\n installed,\n isStandalone,\n prompt,\n };\n}\n"],"mappings":"uBAwBA,SAAS,GAA4B,CACjC,GAAI,OAAO,OAAW,IAAa,MAAO,GAE1C,IAAM,EAAqB,CADL,aAAc,aAAc,YACvB,CAAA,CAAa,KACnC,GAAS,OAAO,aAAa,kBAAkB,EAAK,EAAE,CAAC,EAAE,OAC9D,EAEM,EACD,OAAO,UAAmD,aAAe,GAC9E,OAAO,GAAsB,CACjC,CAMA,SAAgB,GAAuD,CACnE,GAAM,CAAC,EAAU,IAAA,EAAe,EAAA,SAAA,CAA0C,IAAI,EACxE,CAAC,EAAW,IAAA,EAAgB,EAAA,SAAA,CAAkB,EAAK,EACnD,CAAC,EAAc,IAAA,EAAmB,EAAA,SAAA,CAAkB,CAAgB,GAE1E,EAAA,EAAA,UAAA,KAAgB,CACZ,GAAI,OAAO,OAAW,IAAa,OACnC,IAAM,EAAW,GAAuB,CACpC,EAAM,eAAe,EACrB,EAAY,CAAiC,CACjD,EACM,MAA+B,CACjC,EAAY,IAAI,EAChB,EAAa,EAAI,EACjB,EAAgB,EAAI,CACxB,EACM,EAAmB,OAAO,aAAa,4BAA4B,EACnE,MAAiC,EAAgB,EAAiB,CAAC,EAIzE,OAHA,OAAO,iBAAiB,sBAAuB,CAAO,EACtD,OAAO,iBAAiB,eAAgB,CAAgB,EACxD,GAAkB,iBAAiB,SAAU,CAAkB,MAClD,CACT,OAAO,oBAAoB,sBAAuB,CAAO,EACzD,OAAO,oBAAoB,eAAgB,CAAgB,EAC3D,GAAkB,oBAAoB,SAAU,CAAkB,CACtE,CACJ,EAAG,CAAC,CAAC,EAEL,IAAM,GAAA,EAAS,EAAA,YAAA,CAAY,SAA+D,CACtF,GAAI,CAAC,EAAU,MAAO,cACtB,MAAM,EAAS,OAAO,EACtB,IAAM,EAAS,MAAM,EAAS,WAG9B,OAFA,EAAY,IAAI,EACZ,EAAO,UAAY,YAAY,EAAa,EAAI,EAC7C,EAAO,OAClB,EAAG,CAAC,CAAQ,CAAC,EAEb,MAAO,CACH,YAAa,CAAC,CAAC,EACf,YACA,eACA,QACJ,CACJ"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-breakpoint.cjs","names":[],"sources":["../../src/hooks/use-breakpoint.ts"],"sourcesContent":["import { useEffect, useState } from \"react\";\n\n/**\n * Breakpoint keys exposed by the SDK. Values mirror the CSS tokens\n * `--tempest-bp-xs|sm|md|lg|xl|2xl` defined in `styles/colors.css`.\n */\nexport type Breakpoint = \"xs\" | \"sm\" | \"md\" | \"lg\" | \"xl\" | \"2xl\";\n\n/** Pixel value paired with each breakpoint key. */\nexport const BREAKPOINTS: Record<Breakpoint, number> = {\n xs: 480,\n sm: 640,\n md: 768,\n lg: 1024,\n xl: 1280,\n \"2xl\": 1536,\n};\n\nconst ORDER: readonly Breakpoint[] = [\"xs\", \"sm\", \"md\", \"lg\", \"xl\", \"2xl\"] as const;\n\nfunction resolveCurrent(width: number): Breakpoint {\n let current: Breakpoint = \"xs\";\n for (const key of ORDER) {\n if (width >= BREAKPOINTS[key]) {\n current = key;\n }\n }\n return current;\n}\n\nexport interface BreakpointHelpers {\n /** Current breakpoint key (the largest one whose min-width is matched). */\n current: Breakpoint;\n /** Window width in pixels at last update. `0` when SSR. */\n width: number;\n /** True when viewport width is `>=` the given breakpoint. */\n above: (bp: Breakpoint) => boolean;\n /** True when viewport width is `<` the given breakpoint. */\n below: (bp: Breakpoint) => boolean;\n /** Convenience flags mapping to typical device shapes. */\n isMobile: boolean;\n isTablet: boolean;\n isDesktop: boolean;\n}\n\n/**\n * Reactive viewport-breakpoint hook.\n *\n * Returns the current breakpoint key plus `above` / `below` helpers and\n * `isMobile` / `isTablet` / `isDesktop` flags. SSR-safe — returns `xs` /\n * `width: 0` on the server, then updates after mount.\n */\nexport function useBreakpoint(): BreakpointHelpers {\n const [width, setWidth] = useState<number>(() =>\n typeof window === \"undefined\" ? 0 : window.innerWidth,\n );\n\n useEffect(() => {\n if (typeof window === \"undefined\") return;\n const onResize = (): void => setWidth(window.innerWidth);\n onResize();\n window.addEventListener(\"resize\", onResize, { passive: true });\n return () => window.removeEventListener(\"resize\", onResize);\n }, []);\n\n const current = resolveCurrent(width);\n const above = (bp: Breakpoint): boolean => width >= BREAKPOINTS[bp];\n const below = (bp: Breakpoint): boolean => width < BREAKPOINTS[bp];\n\n return {\n current,\n width,\n above,\n below,\n isMobile: below(\"md\"),\n isTablet: above(\"md\") && below(\"lg\"),\n isDesktop: above(\"lg\"),\n };\n}\n"],"mappings":"uBASA,IAAa,EAA0C,CACnD,GAAI,IACJ,GAAI,IACJ,GAAI,IACJ,GAAI,KACJ,GAAI,KACJ,MAAO,IACX,EAEM,EAA+B,CAAC,KAAM,KAAM,KAAM,KAAM,KAAM,KAAK,EAEzE,SAAS,EAAe,EAA2B,CAC/C,IAAI,EAAsB,KAC1B,IAAK,IAAM,KAAO,EACV,GAAS,EAAY,KACrB,EAAU,GAGlB,OAAO,CACX,CAwBA,SAAgB,GAAmC,CAC/C,GAAM,CAAC,EAAO,IAAA,
|
|
1
|
+
{"version":3,"file":"use-breakpoint.cjs","names":[],"sources":["../../src/hooks/use-breakpoint.ts"],"sourcesContent":["import { useEffect, useState } from \"react\";\n\n/**\n * Breakpoint keys exposed by the SDK. Values mirror the CSS tokens\n * `--tempest-bp-xs|sm|md|lg|xl|2xl` defined in `styles/colors.css`.\n */\nexport type Breakpoint = \"xs\" | \"sm\" | \"md\" | \"lg\" | \"xl\" | \"2xl\";\n\n/** Pixel value paired with each breakpoint key. */\nexport const BREAKPOINTS: Record<Breakpoint, number> = {\n xs: 480,\n sm: 640,\n md: 768,\n lg: 1024,\n xl: 1280,\n \"2xl\": 1536,\n};\n\nconst ORDER: readonly Breakpoint[] = [\"xs\", \"sm\", \"md\", \"lg\", \"xl\", \"2xl\"] as const;\n\nfunction resolveCurrent(width: number): Breakpoint {\n let current: Breakpoint = \"xs\";\n for (const key of ORDER) {\n if (width >= BREAKPOINTS[key]) {\n current = key;\n }\n }\n return current;\n}\n\nexport interface BreakpointHelpers {\n /** Current breakpoint key (the largest one whose min-width is matched). */\n current: Breakpoint;\n /** Window width in pixels at last update. `0` when SSR. */\n width: number;\n /** True when viewport width is `>=` the given breakpoint. */\n above: (bp: Breakpoint) => boolean;\n /** True when viewport width is `<` the given breakpoint. */\n below: (bp: Breakpoint) => boolean;\n /** Convenience flags mapping to typical device shapes. */\n isMobile: boolean;\n isTablet: boolean;\n isDesktop: boolean;\n}\n\n/**\n * Reactive viewport-breakpoint hook.\n *\n * Returns the current breakpoint key plus `above` / `below` helpers and\n * `isMobile` / `isTablet` / `isDesktop` flags. SSR-safe — returns `xs` /\n * `width: 0` on the server, then updates after mount.\n */\nexport function useBreakpoint(): BreakpointHelpers {\n const [width, setWidth] = useState<number>(() =>\n typeof window === \"undefined\" ? 0 : window.innerWidth,\n );\n\n useEffect(() => {\n if (typeof window === \"undefined\") return;\n const onResize = (): void => setWidth(window.innerWidth);\n onResize();\n window.addEventListener(\"resize\", onResize, { passive: true });\n return () => window.removeEventListener(\"resize\", onResize);\n }, []);\n\n const current = resolveCurrent(width);\n const above = (bp: Breakpoint): boolean => width >= BREAKPOINTS[bp];\n const below = (bp: Breakpoint): boolean => width < BREAKPOINTS[bp];\n\n return {\n current,\n width,\n above,\n below,\n isMobile: below(\"md\"),\n isTablet: above(\"md\") && below(\"lg\"),\n isDesktop: above(\"lg\"),\n };\n}\n"],"mappings":"uBASA,IAAa,EAA0C,CACnD,GAAI,IACJ,GAAI,IACJ,GAAI,IACJ,GAAI,KACJ,GAAI,KACJ,MAAO,IACX,EAEM,EAA+B,CAAC,KAAM,KAAM,KAAM,KAAM,KAAM,KAAK,EAEzE,SAAS,EAAe,EAA2B,CAC/C,IAAI,EAAsB,KAC1B,IAAK,IAAM,KAAO,EACV,GAAS,EAAY,KACrB,EAAU,GAGlB,OAAO,CACX,CAwBA,SAAgB,GAAmC,CAC/C,GAAM,CAAC,EAAO,IAAA,EAAY,EAAA,SAAA,KACtB,OAAO,OAAW,IAAc,EAAI,OAAO,UAC/C,GAEA,EAAA,EAAA,UAAA,KAAgB,CACZ,GAAI,OAAO,OAAW,IAAa,OACnC,IAAM,MAAuB,EAAS,OAAO,UAAU,EAGvD,OAFA,EAAS,EACT,OAAO,iBAAiB,SAAU,EAAU,CAAE,QAAS,EAAK,CAAC,MAChD,OAAO,oBAAoB,SAAU,CAAQ,CAC9D,EAAG,CAAC,CAAC,EAEL,IAAM,EAAU,EAAe,CAAK,EAC9B,EAAS,GAA4B,GAAS,EAAY,GAC1D,EAAS,GAA4B,EAAQ,EAAY,GAE/D,MAAO,CACH,UACA,QACA,QACA,QACA,SAAU,EAAM,IAAI,EACpB,SAAU,EAAM,IAAI,GAAK,EAAM,IAAI,EACnC,UAAW,EAAM,IAAI,CACzB,CACJ"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-click-outside.cjs","names":[],"sources":["../../src/hooks/use-click-outside.ts"],"sourcesContent":["import { useEffect, useRef } from \"react\";\nimport type { RefObject } from \"react\";\n\n/**\n * Call `handler` when a `mousedown` / `touchstart` occurs outside the returned\n * ref's element. SSR-safe — listeners are only attached in the browser.\n *\n * @typeParam T - element type to attach the ref to.\n * @param handler - invoked on an outside interaction.\n * @returns A ref to attach to the element treated as \"inside\".\n */\nexport function useClickOutside<T extends HTMLElement = HTMLElement>(\n handler: () => void,\n): RefObject<T | null> {\n const ref = useRef<T>(null);\n const handlerRef = useRef(handler);\n useEffect(() => {\n handlerRef.current = handler;\n }, [handler]);\n\n useEffect(() => {\n if (typeof document === \"undefined\") return;\n\n const listener = (event: MouseEvent | TouchEvent): void => {\n const target = event.target as Node | null;\n if (!ref.current || (target && ref.current.contains(target))) return;\n handlerRef.current();\n };\n\n document.addEventListener(\"mousedown\", listener);\n document.addEventListener(\"touchstart\", listener);\n return () => {\n document.removeEventListener(\"mousedown\", listener);\n document.removeEventListener(\"touchstart\", listener);\n };\n }, []);\n\n return ref;\n}\n"],"mappings":"uBAWA,SAAgB,EACZ,EACmB,CACnB,IAAM,GAAA,
|
|
1
|
+
{"version":3,"file":"use-click-outside.cjs","names":[],"sources":["../../src/hooks/use-click-outside.ts"],"sourcesContent":["import { useEffect, useRef } from \"react\";\nimport type { RefObject } from \"react\";\n\n/**\n * Call `handler` when a `mousedown` / `touchstart` occurs outside the returned\n * ref's element. SSR-safe — listeners are only attached in the browser.\n *\n * @typeParam T - element type to attach the ref to.\n * @param handler - invoked on an outside interaction.\n * @returns A ref to attach to the element treated as \"inside\".\n */\nexport function useClickOutside<T extends HTMLElement = HTMLElement>(\n handler: () => void,\n): RefObject<T | null> {\n const ref = useRef<T>(null);\n const handlerRef = useRef(handler);\n useEffect(() => {\n handlerRef.current = handler;\n }, [handler]);\n\n useEffect(() => {\n if (typeof document === \"undefined\") return;\n\n const listener = (event: MouseEvent | TouchEvent): void => {\n const target = event.target as Node | null;\n if (!ref.current || (target && ref.current.contains(target))) return;\n handlerRef.current();\n };\n\n document.addEventListener(\"mousedown\", listener);\n document.addEventListener(\"touchstart\", listener);\n return () => {\n document.removeEventListener(\"mousedown\", listener);\n document.removeEventListener(\"touchstart\", listener);\n };\n }, []);\n\n return ref;\n}\n"],"mappings":"uBAWA,SAAgB,EACZ,EACmB,CACnB,IAAM,GAAA,EAAM,EAAA,OAAA,CAAU,IAAI,EACpB,GAAA,EAAa,EAAA,OAAA,CAAO,CAAO,EAsBjC,OArBA,EAAA,EAAA,UAAA,KAAgB,CACZ,EAAW,QAAU,CACzB,EAAG,CAAC,CAAO,CAAC,GAEZ,EAAA,EAAA,UAAA,KAAgB,CACZ,GAAI,OAAO,SAAa,IAAa,OAErC,IAAM,EAAY,GAAyC,CACvD,IAAM,EAAS,EAAM,OACjB,CAAC,EAAI,SAAY,GAAU,EAAI,QAAQ,SAAS,CAAM,GAC1D,EAAW,QAAQ,CACvB,EAIA,OAFA,SAAS,iBAAiB,YAAa,CAAQ,EAC/C,SAAS,iBAAiB,aAAc,CAAQ,MACnC,CACT,SAAS,oBAAoB,YAAa,CAAQ,EAClD,SAAS,oBAAoB,aAAc,CAAQ,CACvD,CACJ,EAAG,CAAC,CAAC,EAEE,CACX"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-client-filter.cjs","names":[],"sources":["../../src/hooks/use-client-filter.ts"],"sourcesContent":["import { useMemo } from \"react\";\n\nexport type FilterPredicate<T> = (item: T, search: string) => boolean;\n\n/**\n * Client-side filter helper. Performs a case-insensitive match on the listed\n * keys when no custom predicate is provided.\n *\n * @param items - Source list.\n * @param search - Search string.\n * @param keysOrPredicate - Either a list of keys to match against or a custom predicate.\n * @returns Filtered list (referential identity preserved when search is empty).\n */\nexport function useClientFilter<T extends Record<string, unknown>>(\n items: T[],\n search: string,\n keysOrPredicate: (keyof T)[] | FilterPredicate<T>,\n): T[] {\n return useMemo(() => {\n const term = search.trim().toLowerCase();\n if (!term) return items;\n if (typeof keysOrPredicate === \"function\") {\n return items.filter((item) => keysOrPredicate(item, term));\n }\n return items.filter((item) =>\n keysOrPredicate.some((key) => {\n const value = item[key];\n return value != null && String(value).toLowerCase().includes(term);\n }),\n );\n }, [items, search, keysOrPredicate]);\n}\n"],"mappings":"uBAaA,SAAgB,EACZ,EACA,EACA,EACG,CACH,OAAA,
|
|
1
|
+
{"version":3,"file":"use-client-filter.cjs","names":[],"sources":["../../src/hooks/use-client-filter.ts"],"sourcesContent":["import { useMemo } from \"react\";\n\nexport type FilterPredicate<T> = (item: T, search: string) => boolean;\n\n/**\n * Client-side filter helper. Performs a case-insensitive match on the listed\n * keys when no custom predicate is provided.\n *\n * @param items - Source list.\n * @param search - Search string.\n * @param keysOrPredicate - Either a list of keys to match against or a custom predicate.\n * @returns Filtered list (referential identity preserved when search is empty).\n */\nexport function useClientFilter<T extends Record<string, unknown>>(\n items: T[],\n search: string,\n keysOrPredicate: (keyof T)[] | FilterPredicate<T>,\n): T[] {\n return useMemo(() => {\n const term = search.trim().toLowerCase();\n if (!term) return items;\n if (typeof keysOrPredicate === \"function\") {\n return items.filter((item) => keysOrPredicate(item, term));\n }\n return items.filter((item) =>\n keysOrPredicate.some((key) => {\n const value = item[key];\n return value != null && String(value).toLowerCase().includes(term);\n }),\n );\n }, [items, search, keysOrPredicate]);\n}\n"],"mappings":"uBAaA,SAAgB,EACZ,EACA,EACA,EACG,CACH,OAAA,EAAO,EAAA,QAAA,KAAc,CACjB,IAAM,EAAO,EAAO,KAAK,CAAC,CAAC,YAAY,EAKvC,OAJK,EACD,OAAO,GAAoB,WACpB,EAAM,OAAQ,GAAS,EAAgB,EAAM,CAAI,CAAC,EAEtD,EAAM,OAAQ,GACjB,EAAgB,KAAM,GAAQ,CAC1B,IAAM,EAAQ,EAAK,GACnB,OAAO,GAAS,MAAQ,OAAO,CAAK,CAAC,CAAC,YAAY,CAAC,CAAC,SAAS,CAAI,CACrE,CAAC,CACL,EATkB,CAUtB,EAAG,CAAC,EAAO,EAAQ,CAAe,CAAC,CACvC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-clipboard.cjs","names":[],"sources":["../../src/hooks/use-clipboard.ts"],"sourcesContent":["import { useCallback, useEffect, useRef, useState } from \"react\";\n\nexport interface UseClipboardOptions {\n /** Time (ms) before the `copied` flag resets back to false. Default: 1500. */\n resetAfter?: number;\n}\n\nexport interface UseClipboardResult {\n /** True briefly after a successful copy. */\n copied: boolean;\n /** Copy a string to the clipboard. Returns `true` on success. */\n copy: (text: string) => Promise<boolean>;\n /** Manually reset the `copied` flag. */\n reset: () => void;\n}\n\n/**\n * Wrapper around `navigator.clipboard.writeText` with a transient `copied`\n * flag for UI feedback (\"Copied!\" toast). Falls back gracefully when the\n * Clipboard API is unavailable.\n */\nexport function useClipboard(options: UseClipboardOptions = {}): UseClipboardResult {\n const { resetAfter = 1500 } = options;\n const [copied, setCopied] = useState<boolean>(false);\n const timer = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n const reset = useCallback((): void => {\n if (timer.current) {\n clearTimeout(timer.current);\n timer.current = null;\n }\n setCopied(false);\n }, []);\n\n useEffect(() => () => reset(), [reset]);\n\n const copy = useCallback(\n async (text: string): Promise<boolean> => {\n try {\n if (typeof navigator !== \"undefined\" && navigator.clipboard?.writeText) {\n await navigator.clipboard.writeText(text);\n } else if (typeof document !== \"undefined\") {\n const textarea = document.createElement(\"textarea\");\n textarea.value = text;\n textarea.style.position = \"fixed\";\n textarea.style.opacity = \"0\";\n document.body.appendChild(textarea);\n textarea.select();\n document.execCommand(\"copy\");\n document.body.removeChild(textarea);\n } else {\n return false;\n }\n setCopied(true);\n if (timer.current) clearTimeout(timer.current);\n timer.current = setTimeout(() => setCopied(false), resetAfter);\n return true;\n } catch {\n return false;\n }\n },\n [resetAfter],\n );\n\n return { copied, copy, reset };\n}\n"],"mappings":"uBAqBA,SAAgB,EAAa,EAA+B,CAAC,EAAuB,CAChF,GAAM,CAAE,aAAa,MAAS,EACxB,CAAC,EAAQ,IAAA,
|
|
1
|
+
{"version":3,"file":"use-clipboard.cjs","names":[],"sources":["../../src/hooks/use-clipboard.ts"],"sourcesContent":["import { useCallback, useEffect, useRef, useState } from \"react\";\n\nexport interface UseClipboardOptions {\n /** Time (ms) before the `copied` flag resets back to false. Default: 1500. */\n resetAfter?: number;\n}\n\nexport interface UseClipboardResult {\n /** True briefly after a successful copy. */\n copied: boolean;\n /** Copy a string to the clipboard. Returns `true` on success. */\n copy: (text: string) => Promise<boolean>;\n /** Manually reset the `copied` flag. */\n reset: () => void;\n}\n\n/**\n * Wrapper around `navigator.clipboard.writeText` with a transient `copied`\n * flag for UI feedback (\"Copied!\" toast). Falls back gracefully when the\n * Clipboard API is unavailable.\n */\nexport function useClipboard(options: UseClipboardOptions = {}): UseClipboardResult {\n const { resetAfter = 1500 } = options;\n const [copied, setCopied] = useState<boolean>(false);\n const timer = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n const reset = useCallback((): void => {\n if (timer.current) {\n clearTimeout(timer.current);\n timer.current = null;\n }\n setCopied(false);\n }, []);\n\n useEffect(() => () => reset(), [reset]);\n\n const copy = useCallback(\n async (text: string): Promise<boolean> => {\n try {\n if (typeof navigator !== \"undefined\" && navigator.clipboard?.writeText) {\n await navigator.clipboard.writeText(text);\n } else if (typeof document !== \"undefined\") {\n const textarea = document.createElement(\"textarea\");\n textarea.value = text;\n textarea.style.position = \"fixed\";\n textarea.style.opacity = \"0\";\n document.body.appendChild(textarea);\n textarea.select();\n document.execCommand(\"copy\");\n document.body.removeChild(textarea);\n } else {\n return false;\n }\n setCopied(true);\n if (timer.current) clearTimeout(timer.current);\n timer.current = setTimeout(() => setCopied(false), resetAfter);\n return true;\n } catch {\n return false;\n }\n },\n [resetAfter],\n );\n\n return { copied, copy, reset };\n}\n"],"mappings":"uBAqBA,SAAgB,EAAa,EAA+B,CAAC,EAAuB,CAChF,GAAM,CAAE,aAAa,MAAS,EACxB,CAAC,EAAQ,IAAA,EAAa,EAAA,SAAA,CAAkB,EAAK,EAC7C,GAAA,EAAQ,EAAA,OAAA,CAA6C,IAAI,EAEzD,GAAA,EAAQ,EAAA,YAAA,KAAwB,CAClC,AAEI,EAAM,WADN,aAAa,EAAM,OAAO,EACV,MAEpB,EAAU,EAAK,CACnB,EAAG,CAAC,CAAC,EAgCL,OA9BA,EAAA,EAAA,UAAA,SAAsB,EAAM,EAAG,CAAC,CAAK,CAAC,EA8B/B,CAAE,SAAQ,MAAA,EA5BJ,EAAA,YAAA,CACT,KAAO,IAAmC,CACtC,GAAI,CACA,GAAI,OAAO,UAAc,KAAe,UAAU,WAAW,UACzD,MAAM,UAAU,UAAU,UAAU,CAAI,OACrC,GAAI,OAAO,SAAa,IAAa,CACxC,IAAM,EAAW,SAAS,cAAc,UAAU,EAClD,EAAS,MAAQ,EACjB,EAAS,MAAM,SAAW,QAC1B,EAAS,MAAM,QAAU,IACzB,SAAS,KAAK,YAAY,CAAQ,EAClC,EAAS,OAAO,EAChB,SAAS,YAAY,MAAM,EAC3B,SAAS,KAAK,YAAY,CAAQ,CACtC,MACI,MAAO,GAKX,OAHA,EAAU,EAAI,EACV,EAAM,SAAS,aAAa,EAAM,OAAO,EAC7C,EAAM,QAAU,eAAiB,EAAU,EAAK,EAAG,CAAU,EACtD,EACX,MAAQ,CACJ,MAAO,EACX,CACJ,EACA,CAAC,CAAU,CAGE,EAAM,OAAM,CACjC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-counter.cjs","names":[],"sources":["../../src/hooks/use-counter.ts"],"sourcesContent":["import { useCallback, useMemo, useState } from \"react\";\n\nexport interface UseCounterOptions {\n min?: number;\n max?: number;\n}\n\nexport interface CounterHandlers {\n increment: () => void;\n decrement: () => void;\n set: (value: number) => void;\n reset: () => void;\n}\n\nconst clamp = (value: number, min: number | undefined, max: number | undefined): number => {\n let next = value;\n if (typeof min === \"number\") next = Math.max(min, next);\n if (typeof max === \"number\") next = Math.min(max, next);\n return next;\n};\n\n/**\n * Numeric counter clamped to an optional `[min, max]` range.\n *\n * @param initial - initial value (default `0`), clamped to the range.\n * @param options - optional `min` / `max` bounds.\n * @returns Tuple `[count, { increment, decrement, set, reset }]`.\n */\nexport function useCounter(\n initial = 0,\n options: UseCounterOptions = {},\n): [number, CounterHandlers] {\n const { min, max } = options;\n const [count, setCount] = useState<number>(() => clamp(initial, min, max));\n\n const increment = useCallback((): void => {\n setCount((current) => clamp(current + 1, min, max));\n }, [min, max]);\n\n const decrement = useCallback((): void => {\n setCount((current) => clamp(current - 1, min, max));\n }, [min, max]);\n\n const set = useCallback(\n (value: number): void => {\n setCount(clamp(value, min, max));\n },\n [min, max],\n );\n\n const reset = useCallback((): void => {\n setCount(clamp(initial, min, max));\n }, [initial, min, max]);\n\n const handlers = useMemo<CounterHandlers>(\n () => ({ increment, decrement, set, reset }),\n [increment, decrement, set, reset],\n );\n\n return [count, handlers];\n}\n"],"mappings":"uBAcA,IAAM,GAAS,EAAe,EAAyB,IAAoC,CACvF,IAAI,EAAO,EAGX,OAFI,OAAO,GAAQ,WAAU,EAAO,KAAK,IAAI,EAAK,CAAI,GAClD,OAAO,GAAQ,WAAU,EAAO,KAAK,IAAI,EAAK,CAAI,GAC/C,CACX,EASA,SAAgB,EACZ,EAAU,EACV,EAA6B,CAAC,EACL,CACzB,GAAM,CAAE,MAAK,OAAQ,EACf,CAAC,EAAO,IAAA,
|
|
1
|
+
{"version":3,"file":"use-counter.cjs","names":[],"sources":["../../src/hooks/use-counter.ts"],"sourcesContent":["import { useCallback, useMemo, useState } from \"react\";\n\nexport interface UseCounterOptions {\n min?: number;\n max?: number;\n}\n\nexport interface CounterHandlers {\n increment: () => void;\n decrement: () => void;\n set: (value: number) => void;\n reset: () => void;\n}\n\nconst clamp = (value: number, min: number | undefined, max: number | undefined): number => {\n let next = value;\n if (typeof min === \"number\") next = Math.max(min, next);\n if (typeof max === \"number\") next = Math.min(max, next);\n return next;\n};\n\n/**\n * Numeric counter clamped to an optional `[min, max]` range.\n *\n * @param initial - initial value (default `0`), clamped to the range.\n * @param options - optional `min` / `max` bounds.\n * @returns Tuple `[count, { increment, decrement, set, reset }]`.\n */\nexport function useCounter(\n initial = 0,\n options: UseCounterOptions = {},\n): [number, CounterHandlers] {\n const { min, max } = options;\n const [count, setCount] = useState<number>(() => clamp(initial, min, max));\n\n const increment = useCallback((): void => {\n setCount((current) => clamp(current + 1, min, max));\n }, [min, max]);\n\n const decrement = useCallback((): void => {\n setCount((current) => clamp(current - 1, min, max));\n }, [min, max]);\n\n const set = useCallback(\n (value: number): void => {\n setCount(clamp(value, min, max));\n },\n [min, max],\n );\n\n const reset = useCallback((): void => {\n setCount(clamp(initial, min, max));\n }, [initial, min, max]);\n\n const handlers = useMemo<CounterHandlers>(\n () => ({ increment, decrement, set, reset }),\n [increment, decrement, set, reset],\n );\n\n return [count, handlers];\n}\n"],"mappings":"uBAcA,IAAM,GAAS,EAAe,EAAyB,IAAoC,CACvF,IAAI,EAAO,EAGX,OAFI,OAAO,GAAQ,WAAU,EAAO,KAAK,IAAI,EAAK,CAAI,GAClD,OAAO,GAAQ,WAAU,EAAO,KAAK,IAAI,EAAK,CAAI,GAC/C,CACX,EASA,SAAgB,EACZ,EAAU,EACV,EAA6B,CAAC,EACL,CACzB,GAAM,CAAE,MAAK,OAAQ,EACf,CAAC,EAAO,IAAA,EAAY,EAAA,SAAA,KAAuB,EAAM,EAAS,EAAK,CAAG,CAAC,EAEnE,GAAA,EAAY,EAAA,YAAA,KAAwB,CACtC,EAAU,GAAY,EAAM,EAAU,EAAG,EAAK,CAAG,CAAC,CACtD,EAAG,CAAC,EAAK,CAAG,CAAC,EAEP,GAAA,EAAY,EAAA,YAAA,KAAwB,CACtC,EAAU,GAAY,EAAM,EAAU,EAAG,EAAK,CAAG,CAAC,CACtD,EAAG,CAAC,EAAK,CAAG,CAAC,EAEP,GAAA,EAAM,EAAA,YAAA,CACP,GAAwB,CACrB,EAAS,EAAM,EAAO,EAAK,CAAG,CAAC,CACnC,EACA,CAAC,EAAK,CAAG,CACb,EAEM,GAAA,EAAQ,EAAA,YAAA,KAAwB,CAClC,EAAS,EAAM,EAAS,EAAK,CAAG,CAAC,CACrC,EAAG,CAAC,EAAS,EAAK,CAAG,CAAC,EAOtB,MAAO,CAAC,GAAA,EALS,EAAA,QAAA,MACN,CAAE,YAAW,YAAW,MAAK,OAAM,GAC1C,CAAC,EAAW,EAAW,EAAK,CAAK,CAGtB,CAAQ,CAC3B"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-debounce.cjs","names":[],"sources":["../../src/hooks/use-debounce.ts"],"sourcesContent":["import { useEffect, useState } from \"react\";\n\n/**\n * Debounce a fast-changing value. Returns the latest value once `delay` ms\n * have elapsed without further changes.\n *\n * @param value - The value to debounce.\n * @param delay - Delay in milliseconds (default 300).\n * @returns The debounced value.\n */\nexport function useDebounce<T>(value: T, delay = 300): T {\n const [debouncedValue, setDebouncedValue] = useState<T>(value);\n\n useEffect(() => {\n const timer = setTimeout(() => setDebouncedValue(value), delay);\n return () => clearTimeout(timer);\n }, [value, delay]);\n\n return debouncedValue;\n}\n"],"mappings":"uBAUA,SAAgB,EAAe,EAAU,EAAQ,IAAQ,CACrD,GAAM,CAAC,EAAgB,IAAA,
|
|
1
|
+
{"version":3,"file":"use-debounce.cjs","names":[],"sources":["../../src/hooks/use-debounce.ts"],"sourcesContent":["import { useEffect, useState } from \"react\";\n\n/**\n * Debounce a fast-changing value. Returns the latest value once `delay` ms\n * have elapsed without further changes.\n *\n * @param value - The value to debounce.\n * @param delay - Delay in milliseconds (default 300).\n * @returns The debounced value.\n */\nexport function useDebounce<T>(value: T, delay = 300): T {\n const [debouncedValue, setDebouncedValue] = useState<T>(value);\n\n useEffect(() => {\n const timer = setTimeout(() => setDebouncedValue(value), delay);\n return () => clearTimeout(timer);\n }, [value, delay]);\n\n return debouncedValue;\n}\n"],"mappings":"uBAUA,SAAgB,EAAe,EAAU,EAAQ,IAAQ,CACrD,GAAM,CAAC,EAAgB,IAAA,EAAqB,EAAA,SAAA,CAAY,CAAK,EAO7D,OALA,EAAA,EAAA,UAAA,KAAgB,CACZ,IAAM,EAAQ,eAAiB,EAAkB,CAAK,EAAG,CAAK,EAC9D,UAAa,aAAa,CAAK,CACnC,EAAG,CAAC,EAAO,CAAK,CAAC,EAEV,CACX"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-deep-memo.cjs","names":[],"sources":["../../src/hooks/use-deep-memo.ts"],"sourcesContent":["/* eslint-disable react-hooks/refs -- the structural cache is read and written during render by design (see the docstring) */\nimport { useRef } from \"react\";\n\nfunction deepEqual(a: unknown, b: unknown): boolean {\n if (Object.is(a, b)) return true;\n if (typeof a !== \"object\" || typeof b !== \"object\" || a === null || b === null) return false;\n if (Array.isArray(a) !== Array.isArray(b)) return false;\n if (Array.isArray(a) && Array.isArray(b)) {\n if (a.length !== b.length) return false;\n return a.every((value, index) => deepEqual(value, b[index]));\n }\n const aKeys = Object.keys(a as object);\n const bKeys = Object.keys(b as object);\n if (aKeys.length !== bKeys.length) return false;\n return aKeys.every((key) =>\n deepEqual((a as Record<string, unknown>)[key], (b as Record<string, unknown>)[key]),\n );\n}\n\n/**\n * Memoize a value with a structural equality check. Use when an object/array\n * created during render is fed to `useEffect` dependencies and you want to\n * avoid spurious effect runs when only the reference changes.\n *\n * The cache is a ref read and written during render. That is flagged by the React\n * Compiler rules and is nonetheless correct here: the write is idempotent — a\n * discarded render either wrote a structurally equal value or nothing at all — so\n * replaying the render cannot change the result.\n */\nexport function useDeepMemo<T>(value: T): T {\n const ref = useRef<T>(value);\n if (!deepEqual(ref.current, value)) {\n ref.current = value;\n }\n return ref.current;\n}\n"],"mappings":"uBAGA,SAAS,EAAU,EAAY,EAAqB,CAChD,GAAI,OAAO,GAAG,EAAG,CAAC,EAAG,MAAO,GAE5B,GADI,OAAO,GAAM,UAAY,OAAO,GAAM,UAAY,IAAM,MAAQ,IAAM,MACtE,MAAM,QAAQ,CAAC,IAAM,MAAM,QAAQ,CAAC,EAAG,MAAO,GAClD,GAAI,MAAM,QAAQ,CAAC,GAAK,MAAM,QAAQ,CAAC,EAEnC,OADI,EAAE,SAAW,EAAE,QACZ,EAAE,OAAO,EAAO,IAAU,EAAU,EAAO,EAAE,EAAM,CAAC,EAE/D,IAAM,EAAQ,OAAO,KAAK,CAAW,EAC/B,EAAQ,OAAO,KAAK,CAAW,EAErC,OADI,EAAM,SAAW,EAAM,QACpB,EAAM,MAAO,GAChB,EAAW,EAA8B,GAAO,EAA8B,EAAI,CACtF,CACJ,CAYA,SAAgB,EAAe,EAAa,CACxC,IAAM,GAAA,
|
|
1
|
+
{"version":3,"file":"use-deep-memo.cjs","names":[],"sources":["../../src/hooks/use-deep-memo.ts"],"sourcesContent":["/* eslint-disable react-hooks/refs -- the structural cache is read and written during render by design (see the docstring) */\nimport { useRef } from \"react\";\n\nfunction deepEqual(a: unknown, b: unknown): boolean {\n if (Object.is(a, b)) return true;\n if (typeof a !== \"object\" || typeof b !== \"object\" || a === null || b === null) return false;\n if (Array.isArray(a) !== Array.isArray(b)) return false;\n if (Array.isArray(a) && Array.isArray(b)) {\n if (a.length !== b.length) return false;\n return a.every((value, index) => deepEqual(value, b[index]));\n }\n const aKeys = Object.keys(a as object);\n const bKeys = Object.keys(b as object);\n if (aKeys.length !== bKeys.length) return false;\n return aKeys.every((key) =>\n deepEqual((a as Record<string, unknown>)[key], (b as Record<string, unknown>)[key]),\n );\n}\n\n/**\n * Memoize a value with a structural equality check. Use when an object/array\n * created during render is fed to `useEffect` dependencies and you want to\n * avoid spurious effect runs when only the reference changes.\n *\n * The cache is a ref read and written during render. That is flagged by the React\n * Compiler rules and is nonetheless correct here: the write is idempotent — a\n * discarded render either wrote a structurally equal value or nothing at all — so\n * replaying the render cannot change the result.\n */\nexport function useDeepMemo<T>(value: T): T {\n const ref = useRef<T>(value);\n if (!deepEqual(ref.current, value)) {\n ref.current = value;\n }\n return ref.current;\n}\n"],"mappings":"uBAGA,SAAS,EAAU,EAAY,EAAqB,CAChD,GAAI,OAAO,GAAG,EAAG,CAAC,EAAG,MAAO,GAE5B,GADI,OAAO,GAAM,UAAY,OAAO,GAAM,UAAY,IAAM,MAAQ,IAAM,MACtE,MAAM,QAAQ,CAAC,IAAM,MAAM,QAAQ,CAAC,EAAG,MAAO,GAClD,GAAI,MAAM,QAAQ,CAAC,GAAK,MAAM,QAAQ,CAAC,EAEnC,OADI,EAAE,SAAW,EAAE,QACZ,EAAE,OAAO,EAAO,IAAU,EAAU,EAAO,EAAE,EAAM,CAAC,EAE/D,IAAM,EAAQ,OAAO,KAAK,CAAW,EAC/B,EAAQ,OAAO,KAAK,CAAW,EAErC,OADI,EAAM,SAAW,EAAM,QACpB,EAAM,MAAO,GAChB,EAAW,EAA8B,GAAO,EAA8B,EAAI,CACtF,CACJ,CAYA,SAAgB,EAAe,EAAa,CACxC,IAAM,GAAA,EAAM,EAAA,OAAA,CAAU,CAAK,EAI3B,OAHK,EAAU,EAAI,QAAS,CAAK,IAC7B,EAAI,QAAU,GAEX,EAAI,OACf"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-disclosure.cjs","names":[],"sources":["../../src/hooks/use-disclosure.ts"],"sourcesContent":["import { useCallback, useMemo, useState } from \"react\";\n\nexport interface DisclosureHandlers {\n open: () => void;\n close: () => void;\n toggle: () => void;\n}\n\n/**\n * Manage open/closed boolean state with stable `open`/`close`/`toggle` handlers.\n *\n * Richer than {@link useToggle} for UI elements like modals, drawers and\n * popovers: the handlers are referentially stable across renders.\n *\n * @param initial - initial opened state (default `false`).\n * @returns Tuple `[opened, { open, close, toggle }]`.\n */\nexport function useDisclosure(initial = false): [boolean, DisclosureHandlers] {\n const [opened, setOpened] = useState<boolean>(initial);\n\n const open = useCallback((): void => setOpened(true), []);\n const close = useCallback((): void => setOpened(false), []);\n const toggle = useCallback((): void => setOpened((current) => !current), []);\n\n const handlers = useMemo<DisclosureHandlers>(\n () => ({ open, close, toggle }),\n [open, close, toggle],\n );\n\n return [opened, handlers];\n}\n"],"mappings":"uBAiBA,SAAgB,EAAc,EAAU,GAAsC,CAC1E,GAAM,CAAC,EAAQ,IAAA,
|
|
1
|
+
{"version":3,"file":"use-disclosure.cjs","names":[],"sources":["../../src/hooks/use-disclosure.ts"],"sourcesContent":["import { useCallback, useMemo, useState } from \"react\";\n\nexport interface DisclosureHandlers {\n open: () => void;\n close: () => void;\n toggle: () => void;\n}\n\n/**\n * Manage open/closed boolean state with stable `open`/`close`/`toggle` handlers.\n *\n * Richer than {@link useToggle} for UI elements like modals, drawers and\n * popovers: the handlers are referentially stable across renders.\n *\n * @param initial - initial opened state (default `false`).\n * @returns Tuple `[opened, { open, close, toggle }]`.\n */\nexport function useDisclosure(initial = false): [boolean, DisclosureHandlers] {\n const [opened, setOpened] = useState<boolean>(initial);\n\n const open = useCallback((): void => setOpened(true), []);\n const close = useCallback((): void => setOpened(false), []);\n const toggle = useCallback((): void => setOpened((current) => !current), []);\n\n const handlers = useMemo<DisclosureHandlers>(\n () => ({ open, close, toggle }),\n [open, close, toggle],\n );\n\n return [opened, handlers];\n}\n"],"mappings":"uBAiBA,SAAgB,EAAc,EAAU,GAAsC,CAC1E,GAAM,CAAC,EAAQ,IAAA,EAAa,EAAA,SAAA,CAAkB,CAAO,EAE/C,GAAA,EAAO,EAAA,YAAA,KAAwB,EAAU,EAAI,EAAG,CAAC,CAAC,EAClD,GAAA,EAAQ,EAAA,YAAA,KAAwB,EAAU,EAAK,EAAG,CAAC,CAAC,EACpD,GAAA,EAAS,EAAA,YAAA,KAAwB,EAAW,GAAY,CAAC,CAAO,EAAG,CAAC,CAAC,EAO3E,MAAO,CAAC,GAAA,EALS,EAAA,QAAA,MACN,CAAE,OAAM,QAAO,QAAO,GAC7B,CAAC,EAAM,EAAO,CAAM,CAGR,CAAQ,CAC5B"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-document-visibility.cjs","names":[],"sources":["../../src/hooks/use-document-visibility.ts"],"sourcesContent":["import { useEffect, useState } from \"react\";\n\nexport type DocumentVisibility = \"visible\" | \"hidden\";\n\n/** Subscribe to `document.visibilityState`. Returns `\"visible\"` during SSR. */\nexport function useDocumentVisibility(): DocumentVisibility {\n const [state, setState] = useState<DocumentVisibility>(() =>\n typeof document === \"undefined\"\n ? \"visible\"\n : (document.visibilityState as DocumentVisibility),\n );\n\n useEffect(() => {\n if (typeof document === \"undefined\") return;\n const handler = (): void => setState(document.visibilityState as DocumentVisibility);\n document.addEventListener(\"visibilitychange\", handler);\n return () => document.removeEventListener(\"visibilitychange\", handler);\n }, []);\n\n return state;\n}\n"],"mappings":"uBAKA,SAAgB,GAA4C,CACxD,GAAM,CAAC,EAAO,IAAA,
|
|
1
|
+
{"version":3,"file":"use-document-visibility.cjs","names":[],"sources":["../../src/hooks/use-document-visibility.ts"],"sourcesContent":["import { useEffect, useState } from \"react\";\n\nexport type DocumentVisibility = \"visible\" | \"hidden\";\n\n/** Subscribe to `document.visibilityState`. Returns `\"visible\"` during SSR. */\nexport function useDocumentVisibility(): DocumentVisibility {\n const [state, setState] = useState<DocumentVisibility>(() =>\n typeof document === \"undefined\"\n ? \"visible\"\n : (document.visibilityState as DocumentVisibility),\n );\n\n useEffect(() => {\n if (typeof document === \"undefined\") return;\n const handler = (): void => setState(document.visibilityState as DocumentVisibility);\n document.addEventListener(\"visibilitychange\", handler);\n return () => document.removeEventListener(\"visibilitychange\", handler);\n }, []);\n\n return state;\n}\n"],"mappings":"uBAKA,SAAgB,GAA4C,CACxD,GAAM,CAAC,EAAO,IAAA,EAAY,EAAA,SAAA,KACtB,OAAO,SAAa,IACd,UACC,SAAS,eACpB,EASA,OAPA,EAAA,EAAA,UAAA,KAAgB,CACZ,GAAI,OAAO,SAAa,IAAa,OACrC,IAAM,MAAsB,EAAS,SAAS,eAAqC,EAEnF,OADA,SAAS,iBAAiB,mBAAoB,CAAO,MACxC,SAAS,oBAAoB,mBAAoB,CAAO,CACzE,EAAG,CAAC,CAAC,EAEE,CACX"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-event-listener.cjs","names":[],"sources":["../../src/hooks/use-event-listener.ts"],"sourcesContent":["import { useEffect, useRef } from \"react\";\n\ntype AnyEventTarget = EventTarget | { current: EventTarget | null } | null | undefined;\n\n/**\n * Subscribe to a DOM event with React-friendly semantics.\n *\n * - Handler is stored in a ref so callers can pass inline functions without\n * resubscribing on every render.\n * - `target` defaults to `window`. Accepts a raw `EventTarget` (window, document,\n * element) OR a ref pointing at one.\n * - Returns nothing — cleanup is automatic on unmount or when `eventName`/`target` change.\n *\n * @tempest-limits param-count — deliberately the same positional shape as\n * `addEventListener(type, listener, options)`, with the target inserted where React\n * needs it. A hook that mirrors a DOM API is read by everyone who already knows the\n * DOM API; a bespoke options bag would make the familiar call unfamiliar.\n */\nexport function useEventListener<K extends keyof WindowEventMap>(\n eventName: K,\n handler: (event: WindowEventMap[K]) => void,\n target?: AnyEventTarget,\n options?: AddEventListenerOptions | boolean,\n): void;\nexport function useEventListener<K extends keyof DocumentEventMap>(\n eventName: K,\n handler: (event: DocumentEventMap[K]) => void,\n target?: AnyEventTarget,\n options?: AddEventListenerOptions | boolean,\n): void;\nexport function useEventListener<K extends keyof HTMLElementEventMap>(\n eventName: K,\n handler: (event: HTMLElementEventMap[K]) => void,\n target?: AnyEventTarget,\n options?: AddEventListenerOptions | boolean,\n): void;\nexport function useEventListener(\n eventName: string,\n handler: (event: Event) => void,\n target?: AnyEventTarget,\n options?: AddEventListenerOptions | boolean,\n): void {\n const handlerRef = useRef(handler);\n useEffect(() => {\n handlerRef.current = handler;\n }, [handler]);\n\n useEffect(() => {\n const resolvedTarget: EventTarget | null =\n target === undefined\n ? typeof window === \"undefined\"\n ? null\n : window\n : \"current\" in (target as { current: unknown })\n ? ((target as { current: EventTarget | null }).current ?? null)\n : (target as EventTarget | null);\n\n if (!resolvedTarget?.addEventListener) return;\n\n const listener: EventListener = (event) => handlerRef.current(event);\n resolvedTarget.addEventListener(eventName, listener, options);\n return () => resolvedTarget.removeEventListener(eventName, listener, options);\n }, [eventName, target, options]);\n}\n"],"mappings":"uBAoCA,SAAgB,EACZ,EACA,EACA,EACA,EACI,CACJ,IAAM,GAAA,
|
|
1
|
+
{"version":3,"file":"use-event-listener.cjs","names":[],"sources":["../../src/hooks/use-event-listener.ts"],"sourcesContent":["import { useEffect, useRef } from \"react\";\n\ntype AnyEventTarget = EventTarget | { current: EventTarget | null } | null | undefined;\n\n/**\n * Subscribe to a DOM event with React-friendly semantics.\n *\n * - Handler is stored in a ref so callers can pass inline functions without\n * resubscribing on every render.\n * - `target` defaults to `window`. Accepts a raw `EventTarget` (window, document,\n * element) OR a ref pointing at one.\n * - Returns nothing — cleanup is automatic on unmount or when `eventName`/`target` change.\n *\n * @tempest-limits param-count — deliberately the same positional shape as\n * `addEventListener(type, listener, options)`, with the target inserted where React\n * needs it. A hook that mirrors a DOM API is read by everyone who already knows the\n * DOM API; a bespoke options bag would make the familiar call unfamiliar.\n */\nexport function useEventListener<K extends keyof WindowEventMap>(\n eventName: K,\n handler: (event: WindowEventMap[K]) => void,\n target?: AnyEventTarget,\n options?: AddEventListenerOptions | boolean,\n): void;\nexport function useEventListener<K extends keyof DocumentEventMap>(\n eventName: K,\n handler: (event: DocumentEventMap[K]) => void,\n target?: AnyEventTarget,\n options?: AddEventListenerOptions | boolean,\n): void;\nexport function useEventListener<K extends keyof HTMLElementEventMap>(\n eventName: K,\n handler: (event: HTMLElementEventMap[K]) => void,\n target?: AnyEventTarget,\n options?: AddEventListenerOptions | boolean,\n): void;\nexport function useEventListener(\n eventName: string,\n handler: (event: Event) => void,\n target?: AnyEventTarget,\n options?: AddEventListenerOptions | boolean,\n): void {\n const handlerRef = useRef(handler);\n useEffect(() => {\n handlerRef.current = handler;\n }, [handler]);\n\n useEffect(() => {\n const resolvedTarget: EventTarget | null =\n target === undefined\n ? typeof window === \"undefined\"\n ? null\n : window\n : \"current\" in (target as { current: unknown })\n ? ((target as { current: EventTarget | null }).current ?? null)\n : (target as EventTarget | null);\n\n if (!resolvedTarget?.addEventListener) return;\n\n const listener: EventListener = (event) => handlerRef.current(event);\n resolvedTarget.addEventListener(eventName, listener, options);\n return () => resolvedTarget.removeEventListener(eventName, listener, options);\n }, [eventName, target, options]);\n}\n"],"mappings":"uBAoCA,SAAgB,EACZ,EACA,EACA,EACA,EACI,CACJ,IAAM,GAAA,EAAa,EAAA,OAAA,CAAO,CAAO,GACjC,EAAA,EAAA,UAAA,KAAgB,CACZ,EAAW,QAAU,CACzB,EAAG,CAAC,CAAO,CAAC,GAEZ,EAAA,EAAA,UAAA,KAAgB,CACZ,IAAM,EACF,IAAW,IAAA,GACL,OAAO,OAAW,IACd,KACA,OACJ,YAAc,EACV,EAA2C,SAAW,KACvD,EAEb,GAAI,CAAC,GAAgB,iBAAkB,OAEvC,IAAM,EAA2B,GAAU,EAAW,QAAQ,CAAK,EAEnE,OADA,EAAe,iBAAiB,EAAW,EAAU,CAAO,MAC/C,EAAe,oBAAoB,EAAW,EAAU,CAAO,CAChF,EAAG,CAAC,EAAW,EAAQ,CAAO,CAAC,CACnC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-geolocation.cjs","names":[],"sources":["../../src/hooks/use-geolocation.ts"],"sourcesContent":["import { useEffect, useState } from \"react\";\n\nexport interface GeolocationState {\n loading: boolean;\n error: GeolocationPositionError | null;\n coords: GeolocationCoordinates | null;\n timestamp: number | null;\n}\n\nexport interface UseGeolocationOptions extends PositionOptions {\n /** Use `watchPosition` instead of one-shot `getCurrentPosition`. Default: false. */\n watch?: boolean;\n /** Disable the hook without unmounting. Default: false. */\n disabled?: boolean;\n}\n\n/**\n * React hook around the Geolocation API.\n *\n * The effect depends on the individual `PositionOptions` fields instead of the\n * rest object, which is a new reference on every render and would restart the\n * watch continuously. `exhaustive-deps` is silenced there because those fields\n * are the whole of what the effect reads from it.\n *\n * @param options - Geolocation options plus `watch` and `disabled` switches.\n * @returns The latest position, error and loading state.\n */\nexport function useGeolocation(options: UseGeolocationOptions = {}): GeolocationState {\n const { watch = false, disabled = false, ...positionOptions } = options;\n const [state, setState] = useState<GeolocationState>({\n loading: !disabled,\n error: null,\n coords: null,\n timestamp: null,\n });\n\n useEffect(() => {\n if (disabled || typeof navigator === \"undefined\" || !navigator.geolocation) {\n setState((prev) => ({ ...prev, loading: false }));\n return;\n }\n\n const onSuccess = (position: GeolocationPosition): void => {\n setState({\n loading: false,\n error: null,\n coords: position.coords,\n timestamp: position.timestamp,\n });\n };\n const onError = (error: GeolocationPositionError): void => {\n setState((prev) => ({ ...prev, loading: false, error }));\n };\n\n if (watch) {\n const watchId = navigator.geolocation.watchPosition(\n onSuccess,\n onError,\n positionOptions,\n );\n return () => navigator.geolocation.clearWatch(watchId);\n }\n\n navigator.geolocation.getCurrentPosition(onSuccess, onError, positionOptions);\n return undefined;\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [\n disabled,\n watch,\n positionOptions.enableHighAccuracy,\n positionOptions.maximumAge,\n positionOptions.timeout,\n ]);\n\n return state;\n}\n"],"mappings":"uBA2BA,SAAgB,EAAe,EAAiC,CAAC,EAAqB,CAClF,GAAM,CAAE,QAAQ,GAAO,WAAW,GAAO,GAAG,GAAoB,EAC1D,CAAC,EAAO,IAAA,
|
|
1
|
+
{"version":3,"file":"use-geolocation.cjs","names":[],"sources":["../../src/hooks/use-geolocation.ts"],"sourcesContent":["import { useEffect, useState } from \"react\";\n\nexport interface GeolocationState {\n loading: boolean;\n error: GeolocationPositionError | null;\n coords: GeolocationCoordinates | null;\n timestamp: number | null;\n}\n\nexport interface UseGeolocationOptions extends PositionOptions {\n /** Use `watchPosition` instead of one-shot `getCurrentPosition`. Default: false. */\n watch?: boolean;\n /** Disable the hook without unmounting. Default: false. */\n disabled?: boolean;\n}\n\n/**\n * React hook around the Geolocation API.\n *\n * The effect depends on the individual `PositionOptions` fields instead of the\n * rest object, which is a new reference on every render and would restart the\n * watch continuously. `exhaustive-deps` is silenced there because those fields\n * are the whole of what the effect reads from it.\n *\n * @param options - Geolocation options plus `watch` and `disabled` switches.\n * @returns The latest position, error and loading state.\n */\nexport function useGeolocation(options: UseGeolocationOptions = {}): GeolocationState {\n const { watch = false, disabled = false, ...positionOptions } = options;\n const [state, setState] = useState<GeolocationState>({\n loading: !disabled,\n error: null,\n coords: null,\n timestamp: null,\n });\n\n useEffect(() => {\n if (disabled || typeof navigator === \"undefined\" || !navigator.geolocation) {\n setState((prev) => ({ ...prev, loading: false }));\n return;\n }\n\n const onSuccess = (position: GeolocationPosition): void => {\n setState({\n loading: false,\n error: null,\n coords: position.coords,\n timestamp: position.timestamp,\n });\n };\n const onError = (error: GeolocationPositionError): void => {\n setState((prev) => ({ ...prev, loading: false, error }));\n };\n\n if (watch) {\n const watchId = navigator.geolocation.watchPosition(\n onSuccess,\n onError,\n positionOptions,\n );\n return () => navigator.geolocation.clearWatch(watchId);\n }\n\n navigator.geolocation.getCurrentPosition(onSuccess, onError, positionOptions);\n return undefined;\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [\n disabled,\n watch,\n positionOptions.enableHighAccuracy,\n positionOptions.maximumAge,\n positionOptions.timeout,\n ]);\n\n return state;\n}\n"],"mappings":"uBA2BA,SAAgB,EAAe,EAAiC,CAAC,EAAqB,CAClF,GAAM,CAAE,QAAQ,GAAO,WAAW,GAAO,GAAG,GAAoB,EAC1D,CAAC,EAAO,IAAA,EAAY,EAAA,SAAA,CAA2B,CACjD,QAAS,CAAC,EACV,MAAO,KACP,OAAQ,KACR,UAAW,IACf,CAAC,EAwCD,OAtCA,EAAA,EAAA,UAAA,KAAgB,CACZ,GAAI,GAAY,OAAO,UAAc,KAAe,CAAC,UAAU,YAAa,CACxE,EAAU,IAAU,CAAE,GAAG,EAAM,QAAS,EAAM,EAAE,EAChD,MACJ,CAEA,IAAM,EAAa,GAAwC,CACvD,EAAS,CACL,QAAS,GACT,MAAO,KACP,OAAQ,EAAS,OACjB,UAAW,EAAS,SACxB,CAAC,CACL,EACM,EAAW,GAA0C,CACvD,EAAU,IAAU,CAAE,GAAG,EAAM,QAAS,GAAO,OAAM,EAAE,CAC3D,EAEA,GAAI,EAAO,CACP,IAAM,EAAU,UAAU,YAAY,cAClC,EACA,EACA,CACJ,EACA,UAAa,UAAU,YAAY,WAAW,CAAO,CACzD,CAEA,UAAU,YAAY,mBAAmB,EAAW,EAAS,CAAe,CAGhF,EAAG,CACC,EACA,EACA,EAAgB,mBAChB,EAAgB,WAChB,EAAgB,OACpB,CAAC,EAEM,CACX"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-hover.cjs","names":[],"sources":["../../src/hooks/use-hover.ts"],"sourcesContent":["import { useEffect, useState } from \"react\";\nimport type { RefObject } from \"react\";\n\n/**\n * Track whether the pointer is hovering over the referenced element.\n * Returns `false` on touch-only devices (no pointer hover).\n *\n * @example\n * const ref = useRef<HTMLDivElement>(null);\n * const hovered = useHover(ref);\n * return <div ref={ref}>{hovered ? \"✨\" : \"\"}</div>;\n */\nexport function useHover<T extends HTMLElement>(ref: RefObject<T | null>): boolean {\n const [hovered, setHovered] = useState<boolean>(false);\n\n useEffect(() => {\n const node = ref.current;\n if (!node) return;\n const onEnter = (): void => setHovered(true);\n const onLeave = (): void => setHovered(false);\n node.addEventListener(\"mouseenter\", onEnter);\n node.addEventListener(\"mouseleave\", onLeave);\n return () => {\n node.removeEventListener(\"mouseenter\", onEnter);\n node.removeEventListener(\"mouseleave\", onLeave);\n };\n }, [ref]);\n\n return hovered;\n}\n"],"mappings":"uBAYA,SAAgB,EAAgC,EAAmC,CAC/E,GAAM,CAAC,EAAS,IAAA,
|
|
1
|
+
{"version":3,"file":"use-hover.cjs","names":[],"sources":["../../src/hooks/use-hover.ts"],"sourcesContent":["import { useEffect, useState } from \"react\";\nimport type { RefObject } from \"react\";\n\n/**\n * Track whether the pointer is hovering over the referenced element.\n * Returns `false` on touch-only devices (no pointer hover).\n *\n * @example\n * const ref = useRef<HTMLDivElement>(null);\n * const hovered = useHover(ref);\n * return <div ref={ref}>{hovered ? \"✨\" : \"\"}</div>;\n */\nexport function useHover<T extends HTMLElement>(ref: RefObject<T | null>): boolean {\n const [hovered, setHovered] = useState<boolean>(false);\n\n useEffect(() => {\n const node = ref.current;\n if (!node) return;\n const onEnter = (): void => setHovered(true);\n const onLeave = (): void => setHovered(false);\n node.addEventListener(\"mouseenter\", onEnter);\n node.addEventListener(\"mouseleave\", onLeave);\n return () => {\n node.removeEventListener(\"mouseenter\", onEnter);\n node.removeEventListener(\"mouseleave\", onLeave);\n };\n }, [ref]);\n\n return hovered;\n}\n"],"mappings":"uBAYA,SAAgB,EAAgC,EAAmC,CAC/E,GAAM,CAAC,EAAS,IAAA,EAAc,EAAA,SAAA,CAAkB,EAAK,EAerD,OAbA,EAAA,EAAA,UAAA,KAAgB,CACZ,IAAM,EAAO,EAAI,QACjB,GAAI,CAAC,EAAM,OACX,IAAM,MAAsB,EAAW,EAAI,EACrC,MAAsB,EAAW,EAAK,EAG5C,OAFA,EAAK,iBAAiB,aAAc,CAAO,EAC3C,EAAK,iBAAiB,aAAc,CAAO,MAC9B,CACT,EAAK,oBAAoB,aAAc,CAAO,EAC9C,EAAK,oBAAoB,aAAc,CAAO,CAClD,CACJ,EAAG,CAAC,CAAG,CAAC,EAED,CACX"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-idle.cjs","names":[],"sources":["../../src/hooks/use-idle.ts"],"sourcesContent":["import { useEffect, useState } from \"react\";\n\nconst DEFAULT_EVENTS: (keyof WindowEventMap)[] = [\n \"mousemove\",\n \"mousedown\",\n \"keydown\",\n \"touchstart\",\n \"wheel\",\n \"scroll\",\n];\n\n/**\n * Detect when the user has been idle for `timeout` ms (no interaction).\n * Returns `true` once the threshold elapses and back to `false` on activity.\n */\nexport function useIdle(timeout = 60_000): boolean {\n const [idle, setIdle] = useState<boolean>(false);\n\n useEffect(() => {\n if (typeof window === \"undefined\") return;\n let timer = setTimeout(() => setIdle(true), timeout);\n\n function reset(): void {\n setIdle(false);\n clearTimeout(timer);\n timer = setTimeout(() => setIdle(true), timeout);\n }\n\n for (const event of DEFAULT_EVENTS) {\n window.addEventListener(event, reset, { passive: true });\n }\n return () => {\n clearTimeout(timer);\n for (const event of DEFAULT_EVENTS) {\n window.removeEventListener(event, reset);\n }\n };\n }, [timeout]);\n\n return idle;\n}\n"],"mappings":"uBAEA,IAAM,EAA2C,CAC7C,YACA,YACA,UACA,aACA,QACA,QACJ,EAMA,SAAgB,EAAQ,EAAU,IAAiB,CAC/C,GAAM,CAAC,EAAM,IAAA,
|
|
1
|
+
{"version":3,"file":"use-idle.cjs","names":[],"sources":["../../src/hooks/use-idle.ts"],"sourcesContent":["import { useEffect, useState } from \"react\";\n\nconst DEFAULT_EVENTS: (keyof WindowEventMap)[] = [\n \"mousemove\",\n \"mousedown\",\n \"keydown\",\n \"touchstart\",\n \"wheel\",\n \"scroll\",\n];\n\n/**\n * Detect when the user has been idle for `timeout` ms (no interaction).\n * Returns `true` once the threshold elapses and back to `false` on activity.\n */\nexport function useIdle(timeout = 60_000): boolean {\n const [idle, setIdle] = useState<boolean>(false);\n\n useEffect(() => {\n if (typeof window === \"undefined\") return;\n let timer = setTimeout(() => setIdle(true), timeout);\n\n function reset(): void {\n setIdle(false);\n clearTimeout(timer);\n timer = setTimeout(() => setIdle(true), timeout);\n }\n\n for (const event of DEFAULT_EVENTS) {\n window.addEventListener(event, reset, { passive: true });\n }\n return () => {\n clearTimeout(timer);\n for (const event of DEFAULT_EVENTS) {\n window.removeEventListener(event, reset);\n }\n };\n }, [timeout]);\n\n return idle;\n}\n"],"mappings":"uBAEA,IAAM,EAA2C,CAC7C,YACA,YACA,UACA,aACA,QACA,QACJ,EAMA,SAAgB,EAAQ,EAAU,IAAiB,CAC/C,GAAM,CAAC,EAAM,IAAA,EAAW,EAAA,SAAA,CAAkB,EAAK,EAuB/C,OArBA,EAAA,EAAA,UAAA,KAAgB,CACZ,GAAI,OAAO,OAAW,IAAa,OACnC,IAAI,EAAQ,eAAiB,EAAQ,EAAI,EAAG,CAAO,EAEnD,SAAS,GAAc,CACnB,EAAQ,EAAK,EACb,aAAa,CAAK,EAClB,EAAQ,eAAiB,EAAQ,EAAI,EAAG,CAAO,CACnD,CAEA,IAAK,IAAM,KAAS,EAChB,OAAO,iBAAiB,EAAO,EAAO,CAAE,QAAS,EAAK,CAAC,EAE3D,UAAa,CACT,aAAa,CAAK,EAClB,IAAK,IAAM,KAAS,EAChB,OAAO,oBAAoB,EAAO,CAAK,CAE/C,CACJ,EAAG,CAAC,CAAO,CAAC,EAEL,CACX"}
|