tempest-react-sdk 0.39.0 → 0.40.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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/passkey.cjs.map +1 -1
- package/dist/auth/passkey.js.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 +1 -1
- package/dist/forms/masked-inputs.cjs.map +1 -1
- package/dist/forms/masked-inputs.js +1 -1
- package/dist/forms/masked-inputs.js.map +1 -1
- package/dist/forms/use-viacep.cjs.map +1 -1
- package/dist/forms/use-zod-form.cjs.map +1 -1
- package/dist/forms/zod-resolver.cjs.map +1 -1
- package/dist/forms/zod-resolver.js.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/tempest-react-sdk.d.ts +25 -26
- 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/exceptions.cjs.map +1 -1
- package/dist/vision/core/exceptions.js.map +1 -1
- package/dist/vision/core/metadata.cjs +1 -1
- package/dist/vision/core/metadata.cjs.map +1 -1
- package/dist/vision/core/metadata.js +1 -1
- package/dist/vision/core/metadata.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/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/tasks/base.cjs +1 -1
- package/dist/vision/tasks/base.cjs.map +1 -1
- package/dist/vision/tasks/base.js +3 -1
- 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 +1 -1
- package/dist/vision/tasks/detectClassify.cjs.map +1 -1
- package/dist/vision/tasks/detectClassify.js +16 -15
- package/dist/vision/tasks/detectClassify.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/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":"Carousel.js","names":[],"sources":["../../../src/components/Carousel/Carousel.tsx"],"sourcesContent":["/**\n * @tempest-limits function-lines — the body owns the scroll snap position, the\n * swipe, the autoplay timer that must pause on interaction and the dot indicators\n * that read the same index.\n */\nimport { useCallback, useState } from \"react\";\nimport type { HTMLAttributes, ReactNode } from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport styles from \"./Carousel.module.css\";\n\nexport interface CarouselProps extends Omit<HTMLAttributes<HTMLDivElement>, \"children\"> {\n /** Slides — one rendered at a time. */\n children: ReactNode[];\n /** Wrap around at the ends instead of stopping. Default `false`. */\n loop?: boolean;\n /** Show prev/next arrow buttons. Default `true`. */\n showArrows?: boolean;\n /** Show dot indicators. Default `true`. */\n showDots?: boolean;\n /** Controlled active index. */\n index?: number;\n /** Initial index for uncontrolled use. Default `0`. */\n defaultIndex?: number;\n /** Called whenever the active index changes. */\n onIndexChange?: (index: number) => void;\n}\n\n/**\n * Horizontal content slider showing one slide at a time.\n *\n * - Track translates by the active index (`translateX(-index * 100%)`).\n * - Prev/next arrows (disabled at the ends unless `loop`); dot indicators jump.\n * - Controlled (`index`) or uncontrolled (`defaultIndex`).\n * - Arrow Left/Right on the focused region navigates.\n *\n * @example\n * <Carousel loop>\n * <img src=\"/1.jpg\" alt=\"\" />\n * <img src=\"/2.jpg\" alt=\"\" />\n * <img src=\"/3.jpg\" alt=\"\" />\n * </Carousel>\n */\nexport function Carousel({\n children,\n loop = false,\n showArrows = true,\n showDots = true,\n index,\n defaultIndex = 0,\n onIndexChange,\n className,\n ...props\n}: CarouselProps) {\n const count = children.length;\n const isControlled = index !== undefined;\n const [internalIndex, setInternalIndex] = useState<number>(defaultIndex);\n const current = isControlled ? index : internalIndex;\n\n const goTo = useCallback(\n (next: number): void => {\n let target = next;\n if (target < 0) target = loop ? count - 1 : 0;\n else if (target >= count) target = loop ? 0 : count - 1;\n if (target === current) return;\n if (!isControlled) setInternalIndex(target);\n onIndexChange?.(target);\n },\n [count, loop, current, isControlled, onIndexChange],\n );\n\n const atStart = current <= 0;\n const atEnd = current >= count - 1;\n\n const handleKeyDown = (event: React.KeyboardEvent<HTMLDivElement>): void => {\n if (event.key === \"ArrowRight\") {\n event.preventDefault();\n goTo(current + 1);\n } else if (event.key === \"ArrowLeft\") {\n event.preventDefault();\n goTo(current - 1);\n }\n };\n\n return (\n <div\n role=\"region\"\n aria-roledescription=\"carousel\"\n tabIndex={0}\n className={cn(styles.root, className)}\n onKeyDown={handleKeyDown}\n {...props}\n >\n <div className={styles.viewport}>\n <div\n className={styles.track}\n style={{ transform: `translateX(-${current * 100}%)` }}\n >\n {children.map((slide, slideIndex) => (\n <div\n key={slideIndex}\n role=\"group\"\n aria-roledescription=\"slide\"\n aria-label={`${slideIndex + 1} of ${count}`}\n aria-hidden={slideIndex !== current}\n className={styles.slide}\n >\n {slide}\n </div>\n ))}\n </div>\n </div>\n\n {showArrows && count > 1 && (\n <>\n <button\n type=\"button\"\n className={cn(styles.arrow, styles.prev)}\n aria-label=\"Previous slide\"\n disabled={!loop && atStart}\n onClick={() => goTo(current - 1)}\n >\n ‹\n </button>\n <button\n type=\"button\"\n className={cn(styles.arrow, styles.next)}\n aria-label=\"Next slide\"\n disabled={!loop && atEnd}\n onClick={() => goTo(current + 1)}\n >\n ›\n </button>\n </>\n )}\n\n {showDots && count > 1 && (\n <div className={styles.dots} role=\"tablist\" aria-label=\"Slides\">\n {children.map((_, dotIndex) => (\n <button\n key={dotIndex}\n type=\"button\"\n role=\"tab\"\n aria-label={`Go to slide ${dotIndex + 1}`}\n aria-selected={dotIndex === current}\n className={cn(styles.dot, dotIndex === current && styles.dotActive)}\n onClick={() => goTo(dotIndex)}\n />\n ))}\n </div>\n )}\n </div>\n );\n}\n"],"mappings":";;;;;AA0CA,SAAgB,EAAS,EACrB,aACA,UAAO,IACP,gBAAa,IACb,cAAW,IACX,UACA,kBAAe,GACf,kBACA,cACA,GAAG,KACW;CACd,IAAM,IAAQ,EAAS,QACjB,IAAe,MAAU,KAAA,GACzB,CAAC,GAAe,KAAoB,EAAiB,CAAY,GACjE,IAAU,IAAe,IAAQ,GAEjC,IAAO,GACR,MAAuB;EACpB,IAAI,IAAS;EACb,AAAI,IAAS,IAAG,IAAS,IAAO,IAAQ,IAAI,IACnC,KAAU,MAAO,IAAS,IAAO,IAAI,IAAQ,IAClD,MAAW,MACV,KAAc,EAAiB,CAAM,GAC1C,IAAgB,CAAM;CAC1B,GACA;EAAC;EAAO;EAAM;EAAS;EAAc;CAAa,CACtD,GAEM,IAAU,KAAW,GACrB,IAAQ,KAAW,IAAQ;CAYjC,OACI,kBAAC,OAAD;EACI,MAAK;EACL,wBAAqB;EACrB,UAAU;EACV,WAAW,EAAG,EAAO,MAAM,CAAS;EACpC,YAhBe,MAAqD;GACxE,AAAI,EAAM,QAAQ,gBACd,EAAM,eAAe,GACrB,EAAK,IAAU,CAAC,KACT,EAAM,QAAQ,gBACrB,EAAM,eAAe,GACrB,EAAK,IAAU,CAAC;EAExB;EASQ,GAAI;
|
|
1
|
+
{"version":3,"file":"Carousel.js","names":[],"sources":["../../../src/components/Carousel/Carousel.tsx"],"sourcesContent":["/**\n * @tempest-limits function-lines — the body owns the scroll snap position, the\n * swipe, the autoplay timer that must pause on interaction and the dot indicators\n * that read the same index.\n */\nimport { useCallback, useState } from \"react\";\nimport type { HTMLAttributes, ReactNode } from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport styles from \"./Carousel.module.css\";\n\nexport interface CarouselProps extends Omit<HTMLAttributes<HTMLDivElement>, \"children\"> {\n /** Slides — one rendered at a time. */\n children: ReactNode[];\n /** Wrap around at the ends instead of stopping. Default `false`. */\n loop?: boolean;\n /** Show prev/next arrow buttons. Default `true`. */\n showArrows?: boolean;\n /** Show dot indicators. Default `true`. */\n showDots?: boolean;\n /** Controlled active index. */\n index?: number;\n /** Initial index for uncontrolled use. Default `0`. */\n defaultIndex?: number;\n /** Called whenever the active index changes. */\n onIndexChange?: (index: number) => void;\n}\n\n/**\n * Horizontal content slider showing one slide at a time.\n *\n * - Track translates by the active index (`translateX(-index * 100%)`).\n * - Prev/next arrows (disabled at the ends unless `loop`); dot indicators jump.\n * - Controlled (`index`) or uncontrolled (`defaultIndex`).\n * - Arrow Left/Right on the focused region navigates.\n *\n * @example\n * <Carousel loop>\n * <img src=\"/1.jpg\" alt=\"\" />\n * <img src=\"/2.jpg\" alt=\"\" />\n * <img src=\"/3.jpg\" alt=\"\" />\n * </Carousel>\n */\nexport function Carousel({\n children,\n loop = false,\n showArrows = true,\n showDots = true,\n index,\n defaultIndex = 0,\n onIndexChange,\n className,\n ...props\n}: CarouselProps) {\n const count = children.length;\n const isControlled = index !== undefined;\n const [internalIndex, setInternalIndex] = useState<number>(defaultIndex);\n const current = isControlled ? index : internalIndex;\n\n const goTo = useCallback(\n (next: number): void => {\n let target = next;\n if (target < 0) target = loop ? count - 1 : 0;\n else if (target >= count) target = loop ? 0 : count - 1;\n if (target === current) return;\n if (!isControlled) setInternalIndex(target);\n onIndexChange?.(target);\n },\n [count, loop, current, isControlled, onIndexChange],\n );\n\n const atStart = current <= 0;\n const atEnd = current >= count - 1;\n\n const handleKeyDown = (event: React.KeyboardEvent<HTMLDivElement>): void => {\n if (event.key === \"ArrowRight\") {\n event.preventDefault();\n goTo(current + 1);\n } else if (event.key === \"ArrowLeft\") {\n event.preventDefault();\n goTo(current - 1);\n }\n };\n\n return (\n <div\n role=\"region\"\n aria-roledescription=\"carousel\"\n tabIndex={0}\n className={cn(styles.root, className)}\n onKeyDown={handleKeyDown}\n {...props}\n >\n <div className={styles.viewport}>\n <div\n className={styles.track}\n style={{ transform: `translateX(-${current * 100}%)` }}\n >\n {children.map((slide, slideIndex) => (\n <div\n key={slideIndex}\n role=\"group\"\n aria-roledescription=\"slide\"\n aria-label={`${slideIndex + 1} of ${count}`}\n aria-hidden={slideIndex !== current}\n className={styles.slide}\n >\n {slide}\n </div>\n ))}\n </div>\n </div>\n\n {showArrows && count > 1 && (\n <>\n <button\n type=\"button\"\n className={cn(styles.arrow, styles.prev)}\n aria-label=\"Previous slide\"\n disabled={!loop && atStart}\n onClick={() => goTo(current - 1)}\n >\n ‹\n </button>\n <button\n type=\"button\"\n className={cn(styles.arrow, styles.next)}\n aria-label=\"Next slide\"\n disabled={!loop && atEnd}\n onClick={() => goTo(current + 1)}\n >\n ›\n </button>\n </>\n )}\n\n {showDots && count > 1 && (\n <div className={styles.dots} role=\"tablist\" aria-label=\"Slides\">\n {children.map((_, dotIndex) => (\n <button\n key={dotIndex}\n type=\"button\"\n role=\"tab\"\n aria-label={`Go to slide ${dotIndex + 1}`}\n aria-selected={dotIndex === current}\n className={cn(styles.dot, dotIndex === current && styles.dotActive)}\n onClick={() => goTo(dotIndex)}\n />\n ))}\n </div>\n )}\n </div>\n );\n}\n"],"mappings":";;;;;AA0CA,SAAgB,EAAS,EACrB,aACA,UAAO,IACP,gBAAa,IACb,cAAW,IACX,UACA,kBAAe,GACf,kBACA,cACA,GAAG,KACW;CACd,IAAM,IAAQ,EAAS,QACjB,IAAe,MAAU,KAAA,GACzB,CAAC,GAAe,KAAoB,EAAiB,CAAY,GACjE,IAAU,IAAe,IAAQ,GAEjC,IAAO,GACR,MAAuB;EACpB,IAAI,IAAS;EACb,AAAI,IAAS,IAAG,IAAS,IAAO,IAAQ,IAAI,IACnC,KAAU,MAAO,IAAS,IAAO,IAAI,IAAQ,IAClD,MAAW,MACV,KAAc,EAAiB,CAAM,GAC1C,IAAgB,CAAM;CAC1B,GACA;EAAC;EAAO;EAAM;EAAS;EAAc;CAAa,CACtD,GAEM,IAAU,KAAW,GACrB,IAAQ,KAAW,IAAQ;CAYjC,OACI,kBAAC,OAAD;EACI,MAAK;EACL,wBAAqB;EACrB,UAAU;EACV,WAAW,EAAG,EAAO,MAAM,CAAS;EACpC,YAhBe,MAAqD;GACxE,AAAI,EAAM,QAAQ,gBACd,EAAM,eAAe,GACrB,EAAK,IAAU,CAAC,KACT,EAAM,QAAQ,gBACrB,EAAM,eAAe,GACrB,EAAK,IAAU,CAAC;EAExB;EASQ,GAAI;EANR,UAAA;GAQI,kBAAC,OAAD;IAAK,WAAW,EAAO;IACnB,UAAA,kBAAC,OAAD;KACI,WAAW,EAAO;KAClB,OAAO,EAAE,WAAW,eAAe,IAAU,IAAI,IAAI;KAEpD,UAAA,EAAS,KAAK,GAAO,MAClB,kBAAC,OAAD;MAEI,MAAK;MACL,wBAAqB;MACrB,cAAY,GAAG,IAAa,EAAE,MAAM;MACpC,eAAa,MAAe;MAC5B,WAAW,EAAO;MAEjB,UAAA;KACA,GARI,CAQJ,CACR;IACA,CAAA;GACJ,CAAA;GAEJ,KAAc,IAAQ,KACnB,kBAAA,GAAA,EAAA,UAAA,CACI,kBAAC,UAAD;IACI,MAAK;IACL,WAAW,EAAG,EAAO,OAAO,EAAO,IAAI;IACvC,cAAW;IACX,UAAU,CAAC,KAAQ;IACnB,eAAe,EAAK,IAAU,CAAC;IAClC,UAAA;GAEO,CAAA,GACR,kBAAC,UAAD;IACI,MAAK;IACL,WAAW,EAAG,EAAO,OAAO,EAAO,IAAI;IACvC,cAAW;IACX,UAAU,CAAC,KAAQ;IACnB,eAAe,EAAK,IAAU,CAAC;IAClC,UAAA;GAEO,CAAA,CACV,EAAA,CAAA;GAGL,KAAY,IAAQ,KACjB,kBAAC,OAAD;IAAK,WAAW,EAAO;IAAM,MAAK;IAAU,cAAW;IAClD,UAAA,EAAS,KAAK,GAAG,MACd,kBAAC,UAAD;KAEI,MAAK;KACL,MAAK;KACL,cAAY,eAAe,IAAW;KACtC,iBAAe,MAAa;KAC5B,WAAW,EAAG,EAAO,KAAK,MAAa,KAAW,EAAO,SAAS;KAClE,eAAe,EAAK,CAAQ;IAC/B,GAPQ,CAOR,CACJ;GACA,CAAA;EAER;;AAEb"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Chat.cjs","names":[],"sources":["../../../src/components/Chat/Chat.tsx"],"sourcesContent":["/**\n * @tempest-limits file-lines, props-count, function-lines — message grouping needs\n * more context than a list does: currentUserId to decide sides, groupWindowMs and\n * now to decide where a bubble group breaks, typing for the indicator. The remaining\n * props are the composer's (placeholder, composerActions, composerDisabled, onSend,\n * onSendError) and the slots (renderAvatar, header, emptyState).\n */\nimport { useEffect, useLayoutEffect, useRef, type HTMLAttributes, type ReactNode } from \"react\";\n\nimport { cn } from \"@/utils/cn\";\n\nimport { EmptyState } from \"../EmptyState\";\nimport { VisuallyHidden } from \"../VisuallyHidden\";\nimport { ChatComposer, type ChatComposerHandle } from \"./ChatComposer\";\nimport {\n chatStrings,\n dayLabel,\n groupMessages,\n timeLabel,\n typingLabel,\n type ChatMessage,\n} from \"./chat-groups\";\nimport styles from \"./Chat.module.css\";\n\n/** DOM attributes this component redefines. */\ntype OverriddenDomProps = \"children\" | \"onSubmit\";\n\nexport interface ChatProps extends Omit<HTMLAttributes<HTMLDivElement>, OverriddenDomProps> {\n /** The thread, **oldest first**. Never reordered by the component. */\n messages: readonly ChatMessage[];\n /** Author id treated as \"own\" — decides side, colour and status ticks. */\n currentUserId?: string;\n /** Renders the composer when given. Receives the trimmed text. */\n onSend?: (text: string) => void | Promise<void>;\n /** Enables the retry control on a `\"failed\"` message. */\n onRetry?: (message: ChatMessage) => void;\n /** Names currently typing. One, two or a count is phrased for you. */\n typing?: readonly string[];\n /** Avatar for the first message of a run — an `<Avatar>`, an `<Icon>`. */\n renderAvatar?: (message: ChatMessage) => ReactNode;\n /** Rendered above the thread, inside the panel. */\n header?: ReactNode;\n /** Shown when there are no messages. */\n emptyState?: ReactNode;\n /** Gap that still keeps consecutive messages in one run. Default 5 min. */\n groupWindowMs?: number;\n /** Locale for labels. Default `\"pt-BR\"`. */\n locale?: \"pt-BR\" | \"en\";\n /** Reference instant for \"Hoje\"/\"Ontem\". Default: now, at render time. */\n now?: number;\n /** Placeholder for the composer. */\n placeholder?: string;\n /** Extra controls inside the composer, before the send button. */\n composerActions?: ReactNode;\n /** Disable the composer — no permission, thread archived, offline. */\n composerDisabled?: boolean;\n /** Called when `onSend` rejects. The draft stays in the field either way. */\n onSendError?: (error: unknown) => void;\n}\n\n/** Status glyph and label for an outgoing message. */\nconst STATUS_GLYPH: Record<NonNullable<ChatMessage[\"status\"]>, string> = {\n sending: \"◌\",\n sent: \"✓\",\n read: \"✓✓\",\n failed: \"!\",\n};\n\n/**\n * A message thread: grouped by author and by day, own messages on one side, with\n * delivery state, a typing indicator and an optional composer.\n *\n * Presentational and controlled, like the rest of the SDK: it takes a list and\n * emits intent (`onSend`, `onRetry`). Where messages come from — REST, the SDK's\n * `createWebSocket`, an SSE stream — and how an optimistic insert is done stay with\n * the app, because those differ per backend and a component that assumed one would\n * be wrong for most.\n *\n * It works as a comment thread too: that is the same component with\n * `currentUserId` set and no `typing`.\n *\n * @example\n * <Chat\n * messages={messages}\n * currentUserId={me.id}\n * typing={typingNames}\n * onSend={(text) => send({ text })}\n * onRetry={(message) => resend(message.id)}\n * />\n */\nexport function Chat({\n messages,\n currentUserId,\n onSend,\n onRetry,\n typing = [],\n renderAvatar,\n header,\n emptyState,\n groupWindowMs,\n locale = \"pt-BR\",\n now,\n placeholder,\n composerActions,\n composerDisabled,\n onSendError,\n className,\n ...rest\n}: ChatProps) {\n const strings = chatStrings(locale);\n const sections = groupMessages({ messages, currentUserId, windowMs: groupWindowMs });\n const composer = useRef<ChatComposerHandle | null>(null);\n const thread = useRef<HTMLDivElement | null>(null);\n const stuckToBottom = useRef(true);\n\n /**\n * Remember whether the reader is at the bottom, before the next batch lands.\n *\n * A thread that always scrolls to the newest message yanks somebody out of the\n * history they were reading, every time anyone types. So the jump only happens\n * when they were already at the bottom — the same rule every chat app converges\n * on. The 48px slack covers a partially visible last row.\n */\n const trackPosition = (): void => {\n const node = thread.current;\n if (!node) return;\n const distance = node.scrollHeight - node.scrollTop - node.clientHeight;\n stuckToBottom.current = distance < 48;\n };\n\n useLayoutEffect(() => {\n const node = thread.current;\n if (!node || !stuckToBottom.current) return;\n node.scrollTop = node.scrollHeight;\n }, [messages, typing]);\n\n useEffect(() => {\n const node = thread.current;\n if (!node) return;\n node.scrollTop = node.scrollHeight;\n // Mount lands on the newest message; from then on `trackPosition` decides.\n }, []);\n\n const typingText = typingLabel(typing, locale);\n\n return (\n <div className={cn(styles.panel, className)} {...rest}>\n {header && <header className={styles.header}>{header}</header>}\n\n <div\n ref={thread}\n className={styles.thread}\n onScroll={trackPosition}\n role=\"log\"\n aria-live=\"polite\"\n aria-relevant=\"additions text\"\n tabIndex={0}\n aria-label={strings.thread}\n >\n {messages.length === 0\n ? (emptyState ?? <EmptyState title={strings.empty} />)\n : sections.map((section) =>\n section.kind === \"day\" ? (\n <div key={section.key} className={styles.day}>\n <span className={styles.dayLabel}>\n {dayLabel(section.date, { locale, now })}\n </span>\n </div>\n ) : (\n <div\n key={section.key}\n className={cn(styles.run, section.own && styles.ownRun)}\n >\n {renderAvatar && (\n <div className={styles.avatar} aria-hidden=\"true\">\n {renderAvatar(section.messages[0])}\n </div>\n )}\n <div className={styles.runBody}>\n <span className={styles.author}>\n {section.own\n ? strings.you\n : (section.authorName ?? section.authorId)}\n </span>\n <ul className={styles.bubbles}>\n {section.messages.map((message) => (\n <li key={message.id} className={styles.bubbleRow}>\n <div\n className={cn(\n styles.bubble,\n section.own && styles.ownBubble,\n message.status === \"failed\" &&\n styles.failedBubble,\n )}\n >\n <div className={styles.body}>\n {message.body}\n </div>\n <div className={styles.meta}>\n <time\n dateTime={new Date(\n message.sentAt,\n ).toISOString()}\n >\n {timeLabel(message.sentAt, locale)}\n </time>\n {section.own && message.status && (\n <span\n className={cn(\n styles.status,\n message.status === \"failed\" &&\n styles.statusFailed,\n )}\n title={strings[message.status]}\n >\n <span aria-hidden=\"true\">\n {STATUS_GLYPH[message.status]}\n </span>\n <VisuallyHidden>\n {strings[message.status]}\n </VisuallyHidden>\n </span>\n )}\n </div>\n </div>\n {message.status === \"failed\" && onRetry && (\n <button\n type=\"button\"\n className={styles.retry}\n onClick={() => onRetry(message)}\n >\n {strings.retry}\n </button>\n )}\n </li>\n ))}\n </ul>\n </div>\n </div>\n ),\n )}\n </div>\n\n {typingText && (\n <p className={styles.typing} aria-live=\"polite\">\n {typingText}\n </p>\n )}\n\n {onSend && (\n <ChatComposer\n ref={composer}\n onSend={onSend}\n locale={locale}\n placeholder={placeholder}\n actions={composerActions}\n disabled={composerDisabled}\n onError={onSendError}\n />\n )}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"Chat.cjs","names":[],"sources":["../../../src/components/Chat/Chat.tsx"],"sourcesContent":["/**\n * @tempest-limits file-lines, props-count, function-lines — message grouping needs\n * more context than a list does: currentUserId to decide sides, groupWindowMs and\n * now to decide where a bubble group breaks, typing for the indicator. The remaining\n * props are the composer's (placeholder, composerActions, composerDisabled, onSend,\n * onSendError) and the slots (renderAvatar, header, emptyState).\n */\nimport { useEffect, useLayoutEffect, useRef, type HTMLAttributes, type ReactNode } from \"react\";\n\nimport { cn } from \"@/utils/cn\";\n\nimport { EmptyState } from \"../EmptyState\";\nimport { VisuallyHidden } from \"../VisuallyHidden\";\nimport { ChatComposer, type ChatComposerHandle } from \"./ChatComposer\";\nimport {\n chatStrings,\n dayLabel,\n groupMessages,\n timeLabel,\n typingLabel,\n type ChatMessage,\n} from \"./chat-groups\";\nimport styles from \"./Chat.module.css\";\n\n/** DOM attributes this component redefines. */\ntype OverriddenDomProps = \"children\" | \"onSubmit\";\n\nexport interface ChatProps extends Omit<HTMLAttributes<HTMLDivElement>, OverriddenDomProps> {\n /** The thread, **oldest first**. Never reordered by the component. */\n messages: readonly ChatMessage[];\n /** Author id treated as \"own\" — decides side, colour and status ticks. */\n currentUserId?: string;\n /** Renders the composer when given. Receives the trimmed text. */\n onSend?: (text: string) => void | Promise<void>;\n /** Enables the retry control on a `\"failed\"` message. */\n onRetry?: (message: ChatMessage) => void;\n /** Names currently typing. One, two or a count is phrased for you. */\n typing?: readonly string[];\n /** Avatar for the first message of a run — an `<Avatar>`, an `<Icon>`. */\n renderAvatar?: (message: ChatMessage) => ReactNode;\n /** Rendered above the thread, inside the panel. */\n header?: ReactNode;\n /** Shown when there are no messages. */\n emptyState?: ReactNode;\n /** Gap that still keeps consecutive messages in one run. Default 5 min. */\n groupWindowMs?: number;\n /** Locale for labels. Default `\"pt-BR\"`. */\n locale?: \"pt-BR\" | \"en\";\n /** Reference instant for \"Hoje\"/\"Ontem\". Default: now, at render time. */\n now?: number;\n /** Placeholder for the composer. */\n placeholder?: string;\n /** Extra controls inside the composer, before the send button. */\n composerActions?: ReactNode;\n /** Disable the composer — no permission, thread archived, offline. */\n composerDisabled?: boolean;\n /** Called when `onSend` rejects. The draft stays in the field either way. */\n onSendError?: (error: unknown) => void;\n}\n\n/** Status glyph and label for an outgoing message. */\nconst STATUS_GLYPH: Record<NonNullable<ChatMessage[\"status\"]>, string> = {\n sending: \"◌\",\n sent: \"✓\",\n read: \"✓✓\",\n failed: \"!\",\n};\n\n/**\n * A message thread: grouped by author and by day, own messages on one side, with\n * delivery state, a typing indicator and an optional composer.\n *\n * Presentational and controlled, like the rest of the SDK: it takes a list and\n * emits intent (`onSend`, `onRetry`). Where messages come from — REST, the SDK's\n * `createWebSocket`, an SSE stream — and how an optimistic insert is done stay with\n * the app, because those differ per backend and a component that assumed one would\n * be wrong for most.\n *\n * It works as a comment thread too: that is the same component with\n * `currentUserId` set and no `typing`.\n *\n * @example\n * <Chat\n * messages={messages}\n * currentUserId={me.id}\n * typing={typingNames}\n * onSend={(text) => send({ text })}\n * onRetry={(message) => resend(message.id)}\n * />\n */\nexport function Chat({\n messages,\n currentUserId,\n onSend,\n onRetry,\n typing = [],\n renderAvatar,\n header,\n emptyState,\n groupWindowMs,\n locale = \"pt-BR\",\n now,\n placeholder,\n composerActions,\n composerDisabled,\n onSendError,\n className,\n ...rest\n}: ChatProps) {\n const strings = chatStrings(locale);\n const sections = groupMessages({ messages, currentUserId, windowMs: groupWindowMs });\n const composer = useRef<ChatComposerHandle | null>(null);\n const thread = useRef<HTMLDivElement | null>(null);\n const stuckToBottom = useRef(true);\n\n /**\n * Remember whether the reader is at the bottom, before the next batch lands.\n *\n * A thread that always scrolls to the newest message yanks somebody out of the\n * history they were reading, every time anyone types. So the jump only happens\n * when they were already at the bottom — the same rule every chat app converges\n * on. The 48px slack covers a partially visible last row.\n */\n const trackPosition = (): void => {\n const node = thread.current;\n if (!node) return;\n const distance = node.scrollHeight - node.scrollTop - node.clientHeight;\n stuckToBottom.current = distance < 48;\n };\n\n useLayoutEffect(() => {\n const node = thread.current;\n if (!node || !stuckToBottom.current) return;\n node.scrollTop = node.scrollHeight;\n }, [messages, typing]);\n\n useEffect(() => {\n const node = thread.current;\n if (!node) return;\n node.scrollTop = node.scrollHeight;\n // Mount lands on the newest message; from then on `trackPosition` decides.\n }, []);\n\n const typingText = typingLabel(typing, locale);\n\n return (\n <div className={cn(styles.panel, className)} {...rest}>\n {header && <header className={styles.header}>{header}</header>}\n\n <div\n ref={thread}\n className={styles.thread}\n onScroll={trackPosition}\n role=\"log\"\n aria-live=\"polite\"\n aria-relevant=\"additions text\"\n tabIndex={0}\n aria-label={strings.thread}\n >\n {messages.length === 0\n ? (emptyState ?? <EmptyState title={strings.empty} />)\n : sections.map((section) =>\n section.kind === \"day\" ? (\n <div key={section.key} className={styles.day}>\n <span className={styles.dayLabel}>\n {dayLabel(section.date, { locale, now })}\n </span>\n </div>\n ) : (\n <div\n key={section.key}\n className={cn(styles.run, section.own && styles.ownRun)}\n >\n {renderAvatar && (\n <div className={styles.avatar} aria-hidden=\"true\">\n {renderAvatar(section.messages[0])}\n </div>\n )}\n <div className={styles.runBody}>\n <span className={styles.author}>\n {section.own\n ? strings.you\n : (section.authorName ?? section.authorId)}\n </span>\n <ul className={styles.bubbles}>\n {section.messages.map((message) => (\n <li key={message.id} className={styles.bubbleRow}>\n <div\n className={cn(\n styles.bubble,\n section.own && styles.ownBubble,\n message.status === \"failed\" &&\n styles.failedBubble,\n )}\n >\n <div className={styles.body}>\n {message.body}\n </div>\n <div className={styles.meta}>\n <time\n dateTime={new Date(\n message.sentAt,\n ).toISOString()}\n >\n {timeLabel(message.sentAt, locale)}\n </time>\n {section.own && message.status && (\n <span\n className={cn(\n styles.status,\n message.status === \"failed\" &&\n styles.statusFailed,\n )}\n title={strings[message.status]}\n >\n <span aria-hidden=\"true\">\n {STATUS_GLYPH[message.status]}\n </span>\n <VisuallyHidden>\n {strings[message.status]}\n </VisuallyHidden>\n </span>\n )}\n </div>\n </div>\n {message.status === \"failed\" && onRetry && (\n <button\n type=\"button\"\n className={styles.retry}\n onClick={() => onRetry(message)}\n >\n {strings.retry}\n </button>\n )}\n </li>\n ))}\n </ul>\n </div>\n </div>\n ),\n )}\n </div>\n\n {typingText && (\n <p className={styles.typing} aria-live=\"polite\">\n {typingText}\n </p>\n )}\n\n {onSend && (\n <ChatComposer\n ref={composer}\n onSend={onSend}\n locale={locale}\n placeholder={placeholder}\n actions={composerActions}\n disabled={composerDisabled}\n onError={onSendError}\n />\n )}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Chat.js","names":[],"sources":["../../../src/components/Chat/Chat.tsx"],"sourcesContent":["/**\n * @tempest-limits file-lines, props-count, function-lines — message grouping needs\n * more context than a list does: currentUserId to decide sides, groupWindowMs and\n * now to decide where a bubble group breaks, typing for the indicator. The remaining\n * props are the composer's (placeholder, composerActions, composerDisabled, onSend,\n * onSendError) and the slots (renderAvatar, header, emptyState).\n */\nimport { useEffect, useLayoutEffect, useRef, type HTMLAttributes, type ReactNode } from \"react\";\n\nimport { cn } from \"@/utils/cn\";\n\nimport { EmptyState } from \"../EmptyState\";\nimport { VisuallyHidden } from \"../VisuallyHidden\";\nimport { ChatComposer, type ChatComposerHandle } from \"./ChatComposer\";\nimport {\n chatStrings,\n dayLabel,\n groupMessages,\n timeLabel,\n typingLabel,\n type ChatMessage,\n} from \"./chat-groups\";\nimport styles from \"./Chat.module.css\";\n\n/** DOM attributes this component redefines. */\ntype OverriddenDomProps = \"children\" | \"onSubmit\";\n\nexport interface ChatProps extends Omit<HTMLAttributes<HTMLDivElement>, OverriddenDomProps> {\n /** The thread, **oldest first**. Never reordered by the component. */\n messages: readonly ChatMessage[];\n /** Author id treated as \"own\" — decides side, colour and status ticks. */\n currentUserId?: string;\n /** Renders the composer when given. Receives the trimmed text. */\n onSend?: (text: string) => void | Promise<void>;\n /** Enables the retry control on a `\"failed\"` message. */\n onRetry?: (message: ChatMessage) => void;\n /** Names currently typing. One, two or a count is phrased for you. */\n typing?: readonly string[];\n /** Avatar for the first message of a run — an `<Avatar>`, an `<Icon>`. */\n renderAvatar?: (message: ChatMessage) => ReactNode;\n /** Rendered above the thread, inside the panel. */\n header?: ReactNode;\n /** Shown when there are no messages. */\n emptyState?: ReactNode;\n /** Gap that still keeps consecutive messages in one run. Default 5 min. */\n groupWindowMs?: number;\n /** Locale for labels. Default `\"pt-BR\"`. */\n locale?: \"pt-BR\" | \"en\";\n /** Reference instant for \"Hoje\"/\"Ontem\". Default: now, at render time. */\n now?: number;\n /** Placeholder for the composer. */\n placeholder?: string;\n /** Extra controls inside the composer, before the send button. */\n composerActions?: ReactNode;\n /** Disable the composer — no permission, thread archived, offline. */\n composerDisabled?: boolean;\n /** Called when `onSend` rejects. The draft stays in the field either way. */\n onSendError?: (error: unknown) => void;\n}\n\n/** Status glyph and label for an outgoing message. */\nconst STATUS_GLYPH: Record<NonNullable<ChatMessage[\"status\"]>, string> = {\n sending: \"◌\",\n sent: \"✓\",\n read: \"✓✓\",\n failed: \"!\",\n};\n\n/**\n * A message thread: grouped by author and by day, own messages on one side, with\n * delivery state, a typing indicator and an optional composer.\n *\n * Presentational and controlled, like the rest of the SDK: it takes a list and\n * emits intent (`onSend`, `onRetry`). Where messages come from — REST, the SDK's\n * `createWebSocket`, an SSE stream — and how an optimistic insert is done stay with\n * the app, because those differ per backend and a component that assumed one would\n * be wrong for most.\n *\n * It works as a comment thread too: that is the same component with\n * `currentUserId` set and no `typing`.\n *\n * @example\n * <Chat\n * messages={messages}\n * currentUserId={me.id}\n * typing={typingNames}\n * onSend={(text) => send({ text })}\n * onRetry={(message) => resend(message.id)}\n * />\n */\nexport function Chat({\n messages,\n currentUserId,\n onSend,\n onRetry,\n typing = [],\n renderAvatar,\n header,\n emptyState,\n groupWindowMs,\n locale = \"pt-BR\",\n now,\n placeholder,\n composerActions,\n composerDisabled,\n onSendError,\n className,\n ...rest\n}: ChatProps) {\n const strings = chatStrings(locale);\n const sections = groupMessages({ messages, currentUserId, windowMs: groupWindowMs });\n const composer = useRef<ChatComposerHandle | null>(null);\n const thread = useRef<HTMLDivElement | null>(null);\n const stuckToBottom = useRef(true);\n\n /**\n * Remember whether the reader is at the bottom, before the next batch lands.\n *\n * A thread that always scrolls to the newest message yanks somebody out of the\n * history they were reading, every time anyone types. So the jump only happens\n * when they were already at the bottom — the same rule every chat app converges\n * on. The 48px slack covers a partially visible last row.\n */\n const trackPosition = (): void => {\n const node = thread.current;\n if (!node) return;\n const distance = node.scrollHeight - node.scrollTop - node.clientHeight;\n stuckToBottom.current = distance < 48;\n };\n\n useLayoutEffect(() => {\n const node = thread.current;\n if (!node || !stuckToBottom.current) return;\n node.scrollTop = node.scrollHeight;\n }, [messages, typing]);\n\n useEffect(() => {\n const node = thread.current;\n if (!node) return;\n node.scrollTop = node.scrollHeight;\n // Mount lands on the newest message; from then on `trackPosition` decides.\n }, []);\n\n const typingText = typingLabel(typing, locale);\n\n return (\n <div className={cn(styles.panel, className)} {...rest}>\n {header && <header className={styles.header}>{header}</header>}\n\n <div\n ref={thread}\n className={styles.thread}\n onScroll={trackPosition}\n role=\"log\"\n aria-live=\"polite\"\n aria-relevant=\"additions text\"\n tabIndex={0}\n aria-label={strings.thread}\n >\n {messages.length === 0\n ? (emptyState ?? <EmptyState title={strings.empty} />)\n : sections.map((section) =>\n section.kind === \"day\" ? (\n <div key={section.key} className={styles.day}>\n <span className={styles.dayLabel}>\n {dayLabel(section.date, { locale, now })}\n </span>\n </div>\n ) : (\n <div\n key={section.key}\n className={cn(styles.run, section.own && styles.ownRun)}\n >\n {renderAvatar && (\n <div className={styles.avatar} aria-hidden=\"true\">\n {renderAvatar(section.messages[0])}\n </div>\n )}\n <div className={styles.runBody}>\n <span className={styles.author}>\n {section.own\n ? strings.you\n : (section.authorName ?? section.authorId)}\n </span>\n <ul className={styles.bubbles}>\n {section.messages.map((message) => (\n <li key={message.id} className={styles.bubbleRow}>\n <div\n className={cn(\n styles.bubble,\n section.own && styles.ownBubble,\n message.status === \"failed\" &&\n styles.failedBubble,\n )}\n >\n <div className={styles.body}>\n {message.body}\n </div>\n <div className={styles.meta}>\n <time\n dateTime={new Date(\n message.sentAt,\n ).toISOString()}\n >\n {timeLabel(message.sentAt, locale)}\n </time>\n {section.own && message.status && (\n <span\n className={cn(\n styles.status,\n message.status === \"failed\" &&\n styles.statusFailed,\n )}\n title={strings[message.status]}\n >\n <span aria-hidden=\"true\">\n {STATUS_GLYPH[message.status]}\n </span>\n <VisuallyHidden>\n {strings[message.status]}\n </VisuallyHidden>\n </span>\n )}\n </div>\n </div>\n {message.status === \"failed\" && onRetry && (\n <button\n type=\"button\"\n className={styles.retry}\n onClick={() => onRetry(message)}\n >\n {strings.retry}\n </button>\n )}\n </li>\n ))}\n </ul>\n </div>\n </div>\n ),\n )}\n </div>\n\n {typingText && (\n <p className={styles.typing} aria-live=\"polite\">\n {typingText}\n </p>\n )}\n\n {onSend && (\n <ChatComposer\n ref={composer}\n onSend={onSend}\n locale={locale}\n placeholder={placeholder}\n actions={composerActions}\n disabled={composerDisabled}\n onError={onSendError}\n />\n )}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"Chat.js","names":[],"sources":["../../../src/components/Chat/Chat.tsx"],"sourcesContent":["/**\n * @tempest-limits file-lines, props-count, function-lines — message grouping needs\n * more context than a list does: currentUserId to decide sides, groupWindowMs and\n * now to decide where a bubble group breaks, typing for the indicator. The remaining\n * props are the composer's (placeholder, composerActions, composerDisabled, onSend,\n * onSendError) and the slots (renderAvatar, header, emptyState).\n */\nimport { useEffect, useLayoutEffect, useRef, type HTMLAttributes, type ReactNode } from \"react\";\n\nimport { cn } from \"@/utils/cn\";\n\nimport { EmptyState } from \"../EmptyState\";\nimport { VisuallyHidden } from \"../VisuallyHidden\";\nimport { ChatComposer, type ChatComposerHandle } from \"./ChatComposer\";\nimport {\n chatStrings,\n dayLabel,\n groupMessages,\n timeLabel,\n typingLabel,\n type ChatMessage,\n} from \"./chat-groups\";\nimport styles from \"./Chat.module.css\";\n\n/** DOM attributes this component redefines. */\ntype OverriddenDomProps = \"children\" | \"onSubmit\";\n\nexport interface ChatProps extends Omit<HTMLAttributes<HTMLDivElement>, OverriddenDomProps> {\n /** The thread, **oldest first**. Never reordered by the component. */\n messages: readonly ChatMessage[];\n /** Author id treated as \"own\" — decides side, colour and status ticks. */\n currentUserId?: string;\n /** Renders the composer when given. Receives the trimmed text. */\n onSend?: (text: string) => void | Promise<void>;\n /** Enables the retry control on a `\"failed\"` message. */\n onRetry?: (message: ChatMessage) => void;\n /** Names currently typing. One, two or a count is phrased for you. */\n typing?: readonly string[];\n /** Avatar for the first message of a run — an `<Avatar>`, an `<Icon>`. */\n renderAvatar?: (message: ChatMessage) => ReactNode;\n /** Rendered above the thread, inside the panel. */\n header?: ReactNode;\n /** Shown when there are no messages. */\n emptyState?: ReactNode;\n /** Gap that still keeps consecutive messages in one run. Default 5 min. */\n groupWindowMs?: number;\n /** Locale for labels. Default `\"pt-BR\"`. */\n locale?: \"pt-BR\" | \"en\";\n /** Reference instant for \"Hoje\"/\"Ontem\". Default: now, at render time. */\n now?: number;\n /** Placeholder for the composer. */\n placeholder?: string;\n /** Extra controls inside the composer, before the send button. */\n composerActions?: ReactNode;\n /** Disable the composer — no permission, thread archived, offline. */\n composerDisabled?: boolean;\n /** Called when `onSend` rejects. The draft stays in the field either way. */\n onSendError?: (error: unknown) => void;\n}\n\n/** Status glyph and label for an outgoing message. */\nconst STATUS_GLYPH: Record<NonNullable<ChatMessage[\"status\"]>, string> = {\n sending: \"◌\",\n sent: \"✓\",\n read: \"✓✓\",\n failed: \"!\",\n};\n\n/**\n * A message thread: grouped by author and by day, own messages on one side, with\n * delivery state, a typing indicator and an optional composer.\n *\n * Presentational and controlled, like the rest of the SDK: it takes a list and\n * emits intent (`onSend`, `onRetry`). Where messages come from — REST, the SDK's\n * `createWebSocket`, an SSE stream — and how an optimistic insert is done stay with\n * the app, because those differ per backend and a component that assumed one would\n * be wrong for most.\n *\n * It works as a comment thread too: that is the same component with\n * `currentUserId` set and no `typing`.\n *\n * @example\n * <Chat\n * messages={messages}\n * currentUserId={me.id}\n * typing={typingNames}\n * onSend={(text) => send({ text })}\n * onRetry={(message) => resend(message.id)}\n * />\n */\nexport function Chat({\n messages,\n currentUserId,\n onSend,\n onRetry,\n typing = [],\n renderAvatar,\n header,\n emptyState,\n groupWindowMs,\n locale = \"pt-BR\",\n now,\n placeholder,\n composerActions,\n composerDisabled,\n onSendError,\n className,\n ...rest\n}: ChatProps) {\n const strings = chatStrings(locale);\n const sections = groupMessages({ messages, currentUserId, windowMs: groupWindowMs });\n const composer = useRef<ChatComposerHandle | null>(null);\n const thread = useRef<HTMLDivElement | null>(null);\n const stuckToBottom = useRef(true);\n\n /**\n * Remember whether the reader is at the bottom, before the next batch lands.\n *\n * A thread that always scrolls to the newest message yanks somebody out of the\n * history they were reading, every time anyone types. So the jump only happens\n * when they were already at the bottom — the same rule every chat app converges\n * on. The 48px slack covers a partially visible last row.\n */\n const trackPosition = (): void => {\n const node = thread.current;\n if (!node) return;\n const distance = node.scrollHeight - node.scrollTop - node.clientHeight;\n stuckToBottom.current = distance < 48;\n };\n\n useLayoutEffect(() => {\n const node = thread.current;\n if (!node || !stuckToBottom.current) return;\n node.scrollTop = node.scrollHeight;\n }, [messages, typing]);\n\n useEffect(() => {\n const node = thread.current;\n if (!node) return;\n node.scrollTop = node.scrollHeight;\n // Mount lands on the newest message; from then on `trackPosition` decides.\n }, []);\n\n const typingText = typingLabel(typing, locale);\n\n return (\n <div className={cn(styles.panel, className)} {...rest}>\n {header && <header className={styles.header}>{header}</header>}\n\n <div\n ref={thread}\n className={styles.thread}\n onScroll={trackPosition}\n role=\"log\"\n aria-live=\"polite\"\n aria-relevant=\"additions text\"\n tabIndex={0}\n aria-label={strings.thread}\n >\n {messages.length === 0\n ? (emptyState ?? <EmptyState title={strings.empty} />)\n : sections.map((section) =>\n section.kind === \"day\" ? (\n <div key={section.key} className={styles.day}>\n <span className={styles.dayLabel}>\n {dayLabel(section.date, { locale, now })}\n </span>\n </div>\n ) : (\n <div\n key={section.key}\n className={cn(styles.run, section.own && styles.ownRun)}\n >\n {renderAvatar && (\n <div className={styles.avatar} aria-hidden=\"true\">\n {renderAvatar(section.messages[0])}\n </div>\n )}\n <div className={styles.runBody}>\n <span className={styles.author}>\n {section.own\n ? strings.you\n : (section.authorName ?? section.authorId)}\n </span>\n <ul className={styles.bubbles}>\n {section.messages.map((message) => (\n <li key={message.id} className={styles.bubbleRow}>\n <div\n className={cn(\n styles.bubble,\n section.own && styles.ownBubble,\n message.status === \"failed\" &&\n styles.failedBubble,\n )}\n >\n <div className={styles.body}>\n {message.body}\n </div>\n <div className={styles.meta}>\n <time\n dateTime={new Date(\n message.sentAt,\n ).toISOString()}\n >\n {timeLabel(message.sentAt, locale)}\n </time>\n {section.own && message.status && (\n <span\n className={cn(\n styles.status,\n message.status === \"failed\" &&\n styles.statusFailed,\n )}\n title={strings[message.status]}\n >\n <span aria-hidden=\"true\">\n {STATUS_GLYPH[message.status]}\n </span>\n <VisuallyHidden>\n {strings[message.status]}\n </VisuallyHidden>\n </span>\n )}\n </div>\n </div>\n {message.status === \"failed\" && onRetry && (\n <button\n type=\"button\"\n className={styles.retry}\n onClick={() => onRetry(message)}\n >\n {strings.retry}\n </button>\n )}\n </li>\n ))}\n </ul>\n </div>\n </div>\n ),\n )}\n </div>\n\n {typingText && (\n <p className={styles.typing} aria-live=\"polite\">\n {typingText}\n </p>\n )}\n\n {onSend && (\n <ChatComposer\n ref={composer}\n onSend={onSend}\n locale={locale}\n placeholder={placeholder}\n actions={composerActions}\n disabled={composerDisabled}\n onError={onSendError}\n />\n )}\n </div>\n );\n}\n"],"mappings":";;;;;;;;;AA6DA,IAAM,IAAmE;CACrE,SAAS;CACT,MAAM;CACN,MAAM;CACN,QAAQ;AACZ;AAwBA,SAAgB,EAAK,EACjB,aACA,kBACA,WACA,YACA,YAAS,CAAC,GACV,iBACA,WACA,eACA,kBACA,YAAS,SACT,QACA,gBACA,oBACA,qBACA,gBACA,cACA,GAAG,KACO;CACV,IAAM,IAAU,EAAY,CAAM,GAC5B,IAAW,EAAc;EAAE;EAAU;EAAe,UAAU;CAAc,CAAC,GAC7E,IAAW,EAAkC,IAAI,GACjD,IAAS,EAA8B,IAAI,GAC3C,IAAgB,EAAO,EAAI,GAU3B,UAA4B;EAC9B,IAAM,IAAO,EAAO;EACpB,IAAI,CAAC,GAAM;EACX,IAAM,IAAW,EAAK,eAAe,EAAK,YAAY,EAAK;EAC3D,EAAc,UAAU,IAAW;CACvC;CAQA,AANA,QAAsB;EAClB,IAAM,IAAO,EAAO;EAChB,CAAC,KAAQ,CAAC,EAAc,YAC5B,EAAK,YAAY,EAAK;CAC1B,GAAG,CAAC,GAAU,CAAM,CAAC,GAErB,QAAgB;EACZ,IAAM,IAAO,EAAO;EACf,MACL,EAAK,YAAY,EAAK;CAE1B,GAAG,CAAC,CAAC;CAEL,IAAM,IAAa,EAAY,GAAQ,CAAM;CAE7C,OACI,kBAAC,OAAD;EAAK,WAAW,EAAG,EAAO,OAAO,CAAS;EAAG,GAAI;EAAjD,UAAA;GACK,KAAU,kBAAC,UAAD;IAAQ,WAAW,EAAO;IAAS,UAAA;GAAe,CAAA;GAE7D,kBAAC,OAAD;IACI,KAAK;IACL,WAAW,EAAO;IAClB,UAAU;IACV,MAAK;IACL,aAAU;IACV,iBAAc;IACd,UAAU;IACV,cAAY,EAAQ;IAEnB,UAAA,EAAS,WAAW,IACd,KAAc,kBAAC,GAAD,EAAY,OAAO,EAAQ,MAAQ,CAAA,IAClD,EAAS,KAAK,MACV,EAAQ,SAAS,QACb,kBAAC,OAAD;KAAuB,WAAW,EAAO;KACrC,UAAA,kBAAC,QAAD;MAAM,WAAW,EAAO;MACnB,UAAA,EAAS,EAAQ,MAAM;OAAE;OAAQ;MAAI,CAAC;KACrC,CAAA;IACL,GAJK,EAAQ,GAIb,IAEL,kBAAC,OAAD;KAEI,WAAW,EAAG,EAAO,KAAK,EAAQ,OAAO,EAAO,MAAM;KAF1D,UAAA,CAIK,KACG,kBAAC,OAAD;MAAK,WAAW,EAAO;MAAQ,eAAY;MACtC,UAAA,EAAa,EAAQ,SAAS,EAAE;KAChC,CAAA,GAET,kBAAC,OAAD;MAAK,WAAW,EAAO;MAAvB,UAAA,CACI,kBAAC,QAAD;OAAM,WAAW,EAAO;OACnB,UAAA,EAAQ,MACH,EAAQ,MACP,EAAQ,cAAc,EAAQ;MACnC,CAAA,GACN,kBAAC,MAAD;OAAI,WAAW,EAAO;OACjB,UAAA,EAAQ,SAAS,KAAK,MACnB,kBAAC,MAAD;QAAqB,WAAW,EAAO;QAAvC,UAAA,CACI,kBAAC,OAAD;SACI,WAAW,EACP,EAAO,QACP,EAAQ,OAAO,EAAO,WACtB,EAAQ,WAAW,YACf,EAAO,YACf;SANJ,UAAA,CAQI,kBAAC,OAAD;UAAK,WAAW,EAAO;UAClB,UAAA,EAAQ;SACR,CAAA,GACL,kBAAC,OAAD;UAAK,WAAW,EAAO;UAAvB,UAAA,CACI,kBAAC,QAAD;WACI,UAAU,IAAI,KACV,EAAQ,MACZ,CAAC,CAAC,YAAY;WAEb,UAAA,EAAU,EAAQ,QAAQ,CAAM;UAC/B,CAAA,GACL,EAAQ,OAAO,EAAQ,UACpB,kBAAC,QAAD;WACI,WAAW,EACP,EAAO,QACP,EAAQ,WAAW,YACf,EAAO,YACf;WACA,OAAO,EAAQ,EAAQ;WAN3B,UAAA,CAQI,kBAAC,QAAD;YAAM,eAAY;YACb,UAAA,EAAa,EAAQ;WACpB,CAAA,GACN,kBAAC,GAAD,EAAA,UACK,EAAQ,EAAQ,QACL,CAAA,CACd;UAET,CAAA,CAAA;SACJ,CAAA,CAAA;QACJ,CAAA,GAAA,EAAQ,WAAW,YAAY,KAC5B,kBAAC,UAAD;SACI,MAAK;SACL,WAAW,EAAO;SAClB,eAAe,EAAQ,CAAO;SAE7B,UAAA,EAAQ;QACL,CAAA,CAEZ;OAhDK,GAAA,EAAQ,EAgDb,CACP;MACD,CAAA,CACH;KACJ,CAAA,CAAA;IApEI,GAAA,EAAQ,GAoEZ,CAEb;GACL,CAAA;GAEJ,KACG,kBAAC,KAAD;IAAG,WAAW,EAAO;IAAQ,aAAU;IAClC,UAAA;GACF,CAAA;GAGN,KACG,kBAAC,GAAD;IACI,KAAK;IACG;IACA;IACK;IACb,SAAS;IACT,UAAU;IACV,SAAS;GACZ,CAAA;EAEJ;;AAEb"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ChatComposer.cjs","names":[],"sources":["../../../src/components/Chat/ChatComposer.tsx"],"sourcesContent":["import {\n forwardRef,\n useImperativeHandle,\n useLayoutEffect,\n useRef,\n useState,\n type FormEvent,\n type KeyboardEvent,\n type ReactNode,\n type TextareaHTMLAttributes,\n} from \"react\";\n\nimport { cn } from \"@/utils/cn\";\n\nimport { chatStrings } from \"./chat-groups\";\nimport styles from \"./Chat.module.css\";\n\n/** DOM attributes the composer redefines. */\ntype OverriddenDomProps = \"onSubmit\" | \"value\" | \"defaultValue\" | \"rows\";\n\nexport interface ChatComposerProps extends Omit<\n TextareaHTMLAttributes<HTMLTextAreaElement>,\n OverriddenDomProps\n> {\n /** Called with the trimmed text. The field clears only when this does not throw. */\n onSend: (text: string) => void | Promise<void>;\n /** Locale for the placeholder and the send label. Default `\"pt-BR\"`. */\n locale?: \"pt-BR\" | \"en\";\n /** Rendered to the left of the send button — an attach button, an emoji picker. */\n actions?: ReactNode;\n /** Largest height the field grows to, in lines. Default 6. */\n maxRows?: number;\n /** Send label. Defaults to the locale string. */\n sendLabel?: string;\n /**\n * Called when `onSend` rejects. The draft is kept either way.\n *\n * Without it the rejection is swallowed after the draft is preserved, because\n * the alternative is worse: re-throwing out of a DOM event handler surfaces as\n * an unhandled promise rejection — console noise for the developer, a hit in\n * whatever crash reporter the app runs, and nothing the user can act on. The\n * visible signal is the text still sitting in the field; wire this to a toast\n * to say why.\n */\n onError?: (error: unknown) => void;\n}\n\n/** Imperative handle, so a thread can focus the field after retrying a message. */\nexport interface ChatComposerHandle {\n focus: () => void;\n /** Replace the draft — used to put a failed message back in the field. */\n setValue: (text: string) => void;\n}\n\n/**\n * The message field of a thread: a textarea that grows with its content, sends on\n * `Enter` and keeps `Shift+Enter` for a newline.\n *\n * Uncontrolled on purpose. A chat draft changes on every keystroke, and lifting\n * that into app state re-renders the whole thread per character — the one place\n * where \"controlled by default\" costs something visible. Apps that need the draft\n * (a persisted composer, a slash-command menu) read it from `onChange` or drive it\n * through the ref.\n *\n * @example\n * <ChatComposer onSend={(text) => api.post(\"/messages\", { body: { text } })} />\n */\nexport const ChatComposer = forwardRef<ChatComposerHandle, ChatComposerProps>(function ChatComposer(\n {\n onSend,\n locale = \"pt-BR\",\n actions,\n maxRows = 6,\n sendLabel,\n onError,\n className,\n disabled,\n placeholder,\n onKeyDown,\n onChange,\n ...rest\n },\n ref,\n) {\n const strings = chatStrings(locale);\n const textarea = useRef<HTMLTextAreaElement | null>(null);\n const [value, setValue] = useState(\"\");\n const [busy, setBusy] = useState(false);\n\n useImperativeHandle(ref, () => ({\n focus: () => textarea.current?.focus(),\n setValue: (text: string) => {\n setValue(text);\n textarea.current?.focus();\n },\n }));\n\n /**\n * Grow the field to fit its content, up to `maxRows`.\n *\n * Measured from `scrollHeight` after resetting the height, because\n * `scrollHeight` on an element that is already tall enough reports the\n * current height and the field would never shrink back.\n */\n useLayoutEffect(() => {\n const node = textarea.current;\n if (!node) return;\n node.style.height = \"auto\";\n const lineHeight = Number.parseFloat(getComputedStyle(node).lineHeight) || 20;\n const max = lineHeight * maxRows;\n node.style.height = `${Math.min(node.scrollHeight, max)}px`;\n node.style.overflowY = node.scrollHeight > max ? \"auto\" : \"hidden\";\n }, [value, maxRows]);\n\n const submit = async (): Promise<void> => {\n const text = value.trim();\n if (!text || busy || disabled) return;\n setBusy(true);\n try {\n await onSend(text);\n setValue(\"\");\n } catch (error) {\n onError?.(error);\n } finally {\n setBusy(false);\n }\n };\n\n const handleSubmit = (event: FormEvent): void => {\n event.preventDefault();\n void submit();\n };\n\n /**\n * `Enter` sends, `Shift+Enter` breaks the line.\n *\n * The IME check is not optional: while composing Japanese or Korean, `Enter`\n * confirms the candidate word and `keyCode === 229` marks that keystroke.\n * Sending there would post half a word and eat the confirmation.\n */\n const handleKeyDown = (event: KeyboardEvent<HTMLTextAreaElement>): void => {\n onKeyDown?.(event);\n if (event.defaultPrevented) return;\n if (event.key !== \"Enter\" || event.shiftKey) return;\n if (event.nativeEvent.isComposing || event.keyCode === 229) return;\n event.preventDefault();\n void submit();\n };\n\n return (\n <form className={cn(styles.composer, className)} onSubmit={handleSubmit}>\n <textarea\n {...rest}\n ref={textarea}\n className={styles.field}\n rows={1}\n value={value}\n disabled={disabled}\n placeholder={placeholder ?? strings.placeholder}\n onChange={(event) => {\n setValue(event.target.value);\n onChange?.(event);\n }}\n onKeyDown={handleKeyDown}\n />\n <div className={styles.composerActions}>\n {actions}\n <button\n type=\"submit\"\n className={styles.send}\n disabled={disabled || busy || value.trim() === \"\"}\n >\n {sendLabel ?? strings.send}\n </button>\n </div>\n </form>\n );\n});\n"],"mappings":"0JAmEA,IAAa,GAAA,
|
|
1
|
+
{"version":3,"file":"ChatComposer.cjs","names":[],"sources":["../../../src/components/Chat/ChatComposer.tsx"],"sourcesContent":["import {\n forwardRef,\n useImperativeHandle,\n useLayoutEffect,\n useRef,\n useState,\n type FormEvent,\n type KeyboardEvent,\n type ReactNode,\n type TextareaHTMLAttributes,\n} from \"react\";\n\nimport { cn } from \"@/utils/cn\";\n\nimport { chatStrings } from \"./chat-groups\";\nimport styles from \"./Chat.module.css\";\n\n/** DOM attributes the composer redefines. */\ntype OverriddenDomProps = \"onSubmit\" | \"value\" | \"defaultValue\" | \"rows\";\n\nexport interface ChatComposerProps extends Omit<\n TextareaHTMLAttributes<HTMLTextAreaElement>,\n OverriddenDomProps\n> {\n /** Called with the trimmed text. The field clears only when this does not throw. */\n onSend: (text: string) => void | Promise<void>;\n /** Locale for the placeholder and the send label. Default `\"pt-BR\"`. */\n locale?: \"pt-BR\" | \"en\";\n /** Rendered to the left of the send button — an attach button, an emoji picker. */\n actions?: ReactNode;\n /** Largest height the field grows to, in lines. Default 6. */\n maxRows?: number;\n /** Send label. Defaults to the locale string. */\n sendLabel?: string;\n /**\n * Called when `onSend` rejects. The draft is kept either way.\n *\n * Without it the rejection is swallowed after the draft is preserved, because\n * the alternative is worse: re-throwing out of a DOM event handler surfaces as\n * an unhandled promise rejection — console noise for the developer, a hit in\n * whatever crash reporter the app runs, and nothing the user can act on. The\n * visible signal is the text still sitting in the field; wire this to a toast\n * to say why.\n */\n onError?: (error: unknown) => void;\n}\n\n/** Imperative handle, so a thread can focus the field after retrying a message. */\nexport interface ChatComposerHandle {\n focus: () => void;\n /** Replace the draft — used to put a failed message back in the field. */\n setValue: (text: string) => void;\n}\n\n/**\n * The message field of a thread: a textarea that grows with its content, sends on\n * `Enter` and keeps `Shift+Enter` for a newline.\n *\n * Uncontrolled on purpose. A chat draft changes on every keystroke, and lifting\n * that into app state re-renders the whole thread per character — the one place\n * where \"controlled by default\" costs something visible. Apps that need the draft\n * (a persisted composer, a slash-command menu) read it from `onChange` or drive it\n * through the ref.\n *\n * @example\n * <ChatComposer onSend={(text) => api.post(\"/messages\", { body: { text } })} />\n */\nexport const ChatComposer = forwardRef<ChatComposerHandle, ChatComposerProps>(function ChatComposer(\n {\n onSend,\n locale = \"pt-BR\",\n actions,\n maxRows = 6,\n sendLabel,\n onError,\n className,\n disabled,\n placeholder,\n onKeyDown,\n onChange,\n ...rest\n },\n ref,\n) {\n const strings = chatStrings(locale);\n const textarea = useRef<HTMLTextAreaElement | null>(null);\n const [value, setValue] = useState(\"\");\n const [busy, setBusy] = useState(false);\n\n useImperativeHandle(ref, () => ({\n focus: () => textarea.current?.focus(),\n setValue: (text: string) => {\n setValue(text);\n textarea.current?.focus();\n },\n }));\n\n /**\n * Grow the field to fit its content, up to `maxRows`.\n *\n * Measured from `scrollHeight` after resetting the height, because\n * `scrollHeight` on an element that is already tall enough reports the\n * current height and the field would never shrink back.\n */\n useLayoutEffect(() => {\n const node = textarea.current;\n if (!node) return;\n node.style.height = \"auto\";\n const lineHeight = Number.parseFloat(getComputedStyle(node).lineHeight) || 20;\n const max = lineHeight * maxRows;\n node.style.height = `${Math.min(node.scrollHeight, max)}px`;\n node.style.overflowY = node.scrollHeight > max ? \"auto\" : \"hidden\";\n }, [value, maxRows]);\n\n const submit = async (): Promise<void> => {\n const text = value.trim();\n if (!text || busy || disabled) return;\n setBusy(true);\n try {\n await onSend(text);\n setValue(\"\");\n } catch (error) {\n onError?.(error);\n } finally {\n setBusy(false);\n }\n };\n\n const handleSubmit = (event: FormEvent): void => {\n event.preventDefault();\n void submit();\n };\n\n /**\n * `Enter` sends, `Shift+Enter` breaks the line.\n *\n * The IME check is not optional: while composing Japanese or Korean, `Enter`\n * confirms the candidate word and `keyCode === 229` marks that keystroke.\n * Sending there would post half a word and eat the confirmation.\n */\n const handleKeyDown = (event: KeyboardEvent<HTMLTextAreaElement>): void => {\n onKeyDown?.(event);\n if (event.defaultPrevented) return;\n if (event.key !== \"Enter\" || event.shiftKey) return;\n if (event.nativeEvent.isComposing || event.keyCode === 229) return;\n event.preventDefault();\n void submit();\n };\n\n return (\n <form className={cn(styles.composer, className)} onSubmit={handleSubmit}>\n <textarea\n {...rest}\n ref={textarea}\n className={styles.field}\n rows={1}\n value={value}\n disabled={disabled}\n placeholder={placeholder ?? strings.placeholder}\n onChange={(event) => {\n setValue(event.target.value);\n onChange?.(event);\n }}\n onKeyDown={handleKeyDown}\n />\n <div className={styles.composerActions}>\n {actions}\n <button\n type=\"submit\"\n className={styles.send}\n disabled={disabled || busy || value.trim() === \"\"}\n >\n {sendLabel ?? strings.send}\n </button>\n </div>\n </form>\n );\n});\n"],"mappings":"0JAmEA,IAAa,GAAA,EAAe,EAAA,WAAA,CAAkD,SAC1E,CACI,SACA,SAAS,QACT,UACA,UAAU,EACV,YACA,UACA,YACA,WACA,cACA,YACA,WACA,GAAG,GAEP,EACF,CACE,IAAM,EAAU,EAAA,YAAY,CAAM,EAC5B,GAAA,EAAW,EAAA,OAAA,CAAmC,IAAI,EAClD,CAAC,EAAO,IAAA,EAAY,EAAA,SAAA,CAAS,EAAE,EAC/B,CAAC,EAAM,IAAA,EAAW,EAAA,SAAA,CAAS,EAAK,GAEtC,EAAA,EAAA,oBAAA,CAAoB,OAAY,CAC5B,UAAa,EAAS,SAAS,MAAM,EACrC,SAAW,GAAiB,CACxB,EAAS,CAAI,EACb,EAAS,SAAS,MAAM,CAC5B,CACJ,EAAE,GASF,EAAA,EAAA,gBAAA,KAAsB,CAClB,IAAM,EAAO,EAAS,QACtB,GAAI,CAAC,EAAM,OACX,EAAK,MAAM,OAAS,OAEpB,IAAM,GADa,OAAO,WAAW,iBAAiB,CAAI,CAAC,CAAC,UAAU,GAAK,IAClD,EACzB,EAAK,MAAM,OAAS,GAAG,KAAK,IAAI,EAAK,aAAc,CAAG,EAAE,IACxD,EAAK,MAAM,UAAY,EAAK,aAAe,EAAM,OAAS,QAC9D,EAAG,CAAC,EAAO,CAAO,CAAC,EAEnB,IAAM,EAAS,SAA2B,CACtC,IAAM,EAAO,EAAM,KAAK,EACpB,MAAC,GAAQ,GAAQ,GACrB,GAAQ,EAAI,EACZ,GAAI,CACA,MAAM,EAAO,CAAI,EACjB,EAAS,EAAE,CACf,OAAS,EAAO,CACZ,IAAU,CAAK,CACnB,QAAU,CACN,EAAQ,EAAK,CACjB,CARY,CAShB,EAEM,EAAgB,GAA2B,CAC7C,EAAM,eAAe,EACrB,EAAY,CAChB,EASM,EAAiB,GAAoD,CACvE,IAAY,CAAK,EACb,GAAM,mBACN,EAAM,MAAQ,SAAW,EAAM,UAC/B,EAAM,YAAY,aAAe,EAAM,UAAY,MACvD,EAAM,eAAe,EACrB,EAAY,GAChB,EAEA,OACI,EAAA,EAAA,KAAA,CAAC,OAAD,CAAM,UAAW,EAAA,GAAG,EAAA,QAAO,SAAU,CAAS,EAAG,SAAU,EAA3D,SAAA,EACI,EAAA,EAAA,IAAA,CAAC,WAAD,CACI,GAAI,EACJ,IAAK,EACL,UAAW,EAAA,QAAO,MAClB,KAAM,EACC,QACG,WACV,YAAa,GAAe,EAAQ,YACpC,SAAW,GAAU,CACjB,EAAS,EAAM,OAAO,KAAK,EAC3B,IAAW,CAAK,CACpB,EACA,UAAW,CACd,CAAA,GACD,EAAA,EAAA,KAAA,CAAC,MAAD,CAAK,UAAW,EAAA,QAAO,gBAAvB,SAAA,CACK,GACD,EAAA,EAAA,IAAA,CAAC,SAAD,CACI,KAAK,SACL,UAAW,EAAA,QAAO,KAClB,SAAU,GAAY,GAAQ,EAAM,KAAK,IAAM,GAE9C,SAAA,GAAa,EAAQ,IAClB,CAAA,CACP,CACH,CAAA,CAAA,GAEd,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ChatComposer.js","names":[],"sources":["../../../src/components/Chat/ChatComposer.tsx"],"sourcesContent":["import {\n forwardRef,\n useImperativeHandle,\n useLayoutEffect,\n useRef,\n useState,\n type FormEvent,\n type KeyboardEvent,\n type ReactNode,\n type TextareaHTMLAttributes,\n} from \"react\";\n\nimport { cn } from \"@/utils/cn\";\n\nimport { chatStrings } from \"./chat-groups\";\nimport styles from \"./Chat.module.css\";\n\n/** DOM attributes the composer redefines. */\ntype OverriddenDomProps = \"onSubmit\" | \"value\" | \"defaultValue\" | \"rows\";\n\nexport interface ChatComposerProps extends Omit<\n TextareaHTMLAttributes<HTMLTextAreaElement>,\n OverriddenDomProps\n> {\n /** Called with the trimmed text. The field clears only when this does not throw. */\n onSend: (text: string) => void | Promise<void>;\n /** Locale for the placeholder and the send label. Default `\"pt-BR\"`. */\n locale?: \"pt-BR\" | \"en\";\n /** Rendered to the left of the send button — an attach button, an emoji picker. */\n actions?: ReactNode;\n /** Largest height the field grows to, in lines. Default 6. */\n maxRows?: number;\n /** Send label. Defaults to the locale string. */\n sendLabel?: string;\n /**\n * Called when `onSend` rejects. The draft is kept either way.\n *\n * Without it the rejection is swallowed after the draft is preserved, because\n * the alternative is worse: re-throwing out of a DOM event handler surfaces as\n * an unhandled promise rejection — console noise for the developer, a hit in\n * whatever crash reporter the app runs, and nothing the user can act on. The\n * visible signal is the text still sitting in the field; wire this to a toast\n * to say why.\n */\n onError?: (error: unknown) => void;\n}\n\n/** Imperative handle, so a thread can focus the field after retrying a message. */\nexport interface ChatComposerHandle {\n focus: () => void;\n /** Replace the draft — used to put a failed message back in the field. */\n setValue: (text: string) => void;\n}\n\n/**\n * The message field of a thread: a textarea that grows with its content, sends on\n * `Enter` and keeps `Shift+Enter` for a newline.\n *\n * Uncontrolled on purpose. A chat draft changes on every keystroke, and lifting\n * that into app state re-renders the whole thread per character — the one place\n * where \"controlled by default\" costs something visible. Apps that need the draft\n * (a persisted composer, a slash-command menu) read it from `onChange` or drive it\n * through the ref.\n *\n * @example\n * <ChatComposer onSend={(text) => api.post(\"/messages\", { body: { text } })} />\n */\nexport const ChatComposer = forwardRef<ChatComposerHandle, ChatComposerProps>(function ChatComposer(\n {\n onSend,\n locale = \"pt-BR\",\n actions,\n maxRows = 6,\n sendLabel,\n onError,\n className,\n disabled,\n placeholder,\n onKeyDown,\n onChange,\n ...rest\n },\n ref,\n) {\n const strings = chatStrings(locale);\n const textarea = useRef<HTMLTextAreaElement | null>(null);\n const [value, setValue] = useState(\"\");\n const [busy, setBusy] = useState(false);\n\n useImperativeHandle(ref, () => ({\n focus: () => textarea.current?.focus(),\n setValue: (text: string) => {\n setValue(text);\n textarea.current?.focus();\n },\n }));\n\n /**\n * Grow the field to fit its content, up to `maxRows`.\n *\n * Measured from `scrollHeight` after resetting the height, because\n * `scrollHeight` on an element that is already tall enough reports the\n * current height and the field would never shrink back.\n */\n useLayoutEffect(() => {\n const node = textarea.current;\n if (!node) return;\n node.style.height = \"auto\";\n const lineHeight = Number.parseFloat(getComputedStyle(node).lineHeight) || 20;\n const max = lineHeight * maxRows;\n node.style.height = `${Math.min(node.scrollHeight, max)}px`;\n node.style.overflowY = node.scrollHeight > max ? \"auto\" : \"hidden\";\n }, [value, maxRows]);\n\n const submit = async (): Promise<void> => {\n const text = value.trim();\n if (!text || busy || disabled) return;\n setBusy(true);\n try {\n await onSend(text);\n setValue(\"\");\n } catch (error) {\n onError?.(error);\n } finally {\n setBusy(false);\n }\n };\n\n const handleSubmit = (event: FormEvent): void => {\n event.preventDefault();\n void submit();\n };\n\n /**\n * `Enter` sends, `Shift+Enter` breaks the line.\n *\n * The IME check is not optional: while composing Japanese or Korean, `Enter`\n * confirms the candidate word and `keyCode === 229` marks that keystroke.\n * Sending there would post half a word and eat the confirmation.\n */\n const handleKeyDown = (event: KeyboardEvent<HTMLTextAreaElement>): void => {\n onKeyDown?.(event);\n if (event.defaultPrevented) return;\n if (event.key !== \"Enter\" || event.shiftKey) return;\n if (event.nativeEvent.isComposing || event.keyCode === 229) return;\n event.preventDefault();\n void submit();\n };\n\n return (\n <form className={cn(styles.composer, className)} onSubmit={handleSubmit}>\n <textarea\n {...rest}\n ref={textarea}\n className={styles.field}\n rows={1}\n value={value}\n disabled={disabled}\n placeholder={placeholder ?? strings.placeholder}\n onChange={(event) => {\n setValue(event.target.value);\n onChange?.(event);\n }}\n onKeyDown={handleKeyDown}\n />\n <div className={styles.composerActions}>\n {actions}\n <button\n type=\"submit\"\n className={styles.send}\n disabled={disabled || busy || value.trim() === \"\"}\n >\n {sendLabel ?? strings.send}\n </button>\n </div>\n </form>\n );\n});\n"],"mappings":";;;;;;AAmEA,IAAa,IAAe,EAAkD,SAC1E,EACI,WACA,YAAS,SACT,YACA,aAAU,GACV,cACA,YACA,cACA,aACA,gBACA,cACA,aACA,GAAG,KAEP,GACF;CACE,IAAM,IAAU,EAAY,CAAM,GAC5B,IAAW,EAAmC,IAAI,GAClD,CAAC,GAAO,KAAY,EAAS,EAAE,GAC/B,CAAC,GAAM,KAAW,EAAS,EAAK;CAiBtC,AAfA,EAAoB,UAAY;EAC5B,aAAa,EAAS,SAAS,MAAM;EACrC,WAAW,MAAiB;GAExB,AADA,EAAS,CAAI,GACb,EAAS,SAAS,MAAM;EAC5B;CACJ,EAAE,GASF,QAAsB;EAClB,IAAM,IAAO,EAAS;EACtB,IAAI,CAAC,GAAM;EACX,EAAK,MAAM,SAAS;EAEpB,IAAM,KADa,OAAO,WAAW,iBAAiB,CAAI,CAAC,CAAC,UAAU,KAAK,MAClD;EAEzB,AADA,EAAK,MAAM,SAAS,GAAG,KAAK,IAAI,EAAK,cAAc,CAAG,EAAE,KACxD,EAAK,MAAM,YAAY,EAAK,eAAe,IAAM,SAAS;CAC9D,GAAG,CAAC,GAAO,CAAO,CAAC;CAEnB,IAAM,IAAS,YAA2B;EACtC,IAAM,IAAO,EAAM,KAAK;EACpB,OAAC,KAAQ,KAAQ,IACrB;KAAQ,EAAI;GACZ,IAAI;IAEA,AADA,MAAM,EAAO,CAAI,GACjB,EAAS,EAAE;GACf,SAAS,GAAO;IACZ,IAAU,CAAK;GACnB,UAAU;IACN,EAAQ,EAAK;GACjB;EARY;CAShB,GAEM,KAAgB,MAA2B;EAE7C,AADA,EAAM,eAAe,GACrB,EAAY;CAChB,GASM,KAAiB,MAAoD;EACvE,IAAY,CAAK,GACb,GAAM,qBACN,EAAM,QAAQ,WAAW,EAAM,YAC/B,EAAM,YAAY,eAAe,EAAM,YAAY,QACvD,EAAM,eAAe,GACrB,EAAY;CAChB;CAEA,OACI,kBAAC,QAAD;EAAM,WAAW,EAAG,EAAO,UAAU,CAAS;EAAG,UAAU;
|
|
1
|
+
{"version":3,"file":"ChatComposer.js","names":[],"sources":["../../../src/components/Chat/ChatComposer.tsx"],"sourcesContent":["import {\n forwardRef,\n useImperativeHandle,\n useLayoutEffect,\n useRef,\n useState,\n type FormEvent,\n type KeyboardEvent,\n type ReactNode,\n type TextareaHTMLAttributes,\n} from \"react\";\n\nimport { cn } from \"@/utils/cn\";\n\nimport { chatStrings } from \"./chat-groups\";\nimport styles from \"./Chat.module.css\";\n\n/** DOM attributes the composer redefines. */\ntype OverriddenDomProps = \"onSubmit\" | \"value\" | \"defaultValue\" | \"rows\";\n\nexport interface ChatComposerProps extends Omit<\n TextareaHTMLAttributes<HTMLTextAreaElement>,\n OverriddenDomProps\n> {\n /** Called with the trimmed text. The field clears only when this does not throw. */\n onSend: (text: string) => void | Promise<void>;\n /** Locale for the placeholder and the send label. Default `\"pt-BR\"`. */\n locale?: \"pt-BR\" | \"en\";\n /** Rendered to the left of the send button — an attach button, an emoji picker. */\n actions?: ReactNode;\n /** Largest height the field grows to, in lines. Default 6. */\n maxRows?: number;\n /** Send label. Defaults to the locale string. */\n sendLabel?: string;\n /**\n * Called when `onSend` rejects. The draft is kept either way.\n *\n * Without it the rejection is swallowed after the draft is preserved, because\n * the alternative is worse: re-throwing out of a DOM event handler surfaces as\n * an unhandled promise rejection — console noise for the developer, a hit in\n * whatever crash reporter the app runs, and nothing the user can act on. The\n * visible signal is the text still sitting in the field; wire this to a toast\n * to say why.\n */\n onError?: (error: unknown) => void;\n}\n\n/** Imperative handle, so a thread can focus the field after retrying a message. */\nexport interface ChatComposerHandle {\n focus: () => void;\n /** Replace the draft — used to put a failed message back in the field. */\n setValue: (text: string) => void;\n}\n\n/**\n * The message field of a thread: a textarea that grows with its content, sends on\n * `Enter` and keeps `Shift+Enter` for a newline.\n *\n * Uncontrolled on purpose. A chat draft changes on every keystroke, and lifting\n * that into app state re-renders the whole thread per character — the one place\n * where \"controlled by default\" costs something visible. Apps that need the draft\n * (a persisted composer, a slash-command menu) read it from `onChange` or drive it\n * through the ref.\n *\n * @example\n * <ChatComposer onSend={(text) => api.post(\"/messages\", { body: { text } })} />\n */\nexport const ChatComposer = forwardRef<ChatComposerHandle, ChatComposerProps>(function ChatComposer(\n {\n onSend,\n locale = \"pt-BR\",\n actions,\n maxRows = 6,\n sendLabel,\n onError,\n className,\n disabled,\n placeholder,\n onKeyDown,\n onChange,\n ...rest\n },\n ref,\n) {\n const strings = chatStrings(locale);\n const textarea = useRef<HTMLTextAreaElement | null>(null);\n const [value, setValue] = useState(\"\");\n const [busy, setBusy] = useState(false);\n\n useImperativeHandle(ref, () => ({\n focus: () => textarea.current?.focus(),\n setValue: (text: string) => {\n setValue(text);\n textarea.current?.focus();\n },\n }));\n\n /**\n * Grow the field to fit its content, up to `maxRows`.\n *\n * Measured from `scrollHeight` after resetting the height, because\n * `scrollHeight` on an element that is already tall enough reports the\n * current height and the field would never shrink back.\n */\n useLayoutEffect(() => {\n const node = textarea.current;\n if (!node) return;\n node.style.height = \"auto\";\n const lineHeight = Number.parseFloat(getComputedStyle(node).lineHeight) || 20;\n const max = lineHeight * maxRows;\n node.style.height = `${Math.min(node.scrollHeight, max)}px`;\n node.style.overflowY = node.scrollHeight > max ? \"auto\" : \"hidden\";\n }, [value, maxRows]);\n\n const submit = async (): Promise<void> => {\n const text = value.trim();\n if (!text || busy || disabled) return;\n setBusy(true);\n try {\n await onSend(text);\n setValue(\"\");\n } catch (error) {\n onError?.(error);\n } finally {\n setBusy(false);\n }\n };\n\n const handleSubmit = (event: FormEvent): void => {\n event.preventDefault();\n void submit();\n };\n\n /**\n * `Enter` sends, `Shift+Enter` breaks the line.\n *\n * The IME check is not optional: while composing Japanese or Korean, `Enter`\n * confirms the candidate word and `keyCode === 229` marks that keystroke.\n * Sending there would post half a word and eat the confirmation.\n */\n const handleKeyDown = (event: KeyboardEvent<HTMLTextAreaElement>): void => {\n onKeyDown?.(event);\n if (event.defaultPrevented) return;\n if (event.key !== \"Enter\" || event.shiftKey) return;\n if (event.nativeEvent.isComposing || event.keyCode === 229) return;\n event.preventDefault();\n void submit();\n };\n\n return (\n <form className={cn(styles.composer, className)} onSubmit={handleSubmit}>\n <textarea\n {...rest}\n ref={textarea}\n className={styles.field}\n rows={1}\n value={value}\n disabled={disabled}\n placeholder={placeholder ?? strings.placeholder}\n onChange={(event) => {\n setValue(event.target.value);\n onChange?.(event);\n }}\n onKeyDown={handleKeyDown}\n />\n <div className={styles.composerActions}>\n {actions}\n <button\n type=\"submit\"\n className={styles.send}\n disabled={disabled || busy || value.trim() === \"\"}\n >\n {sendLabel ?? strings.send}\n </button>\n </div>\n </form>\n );\n});\n"],"mappings":";;;;;;AAmEA,IAAa,IAAe,EAAkD,SAC1E,EACI,WACA,YAAS,SACT,YACA,aAAU,GACV,cACA,YACA,cACA,aACA,gBACA,cACA,aACA,GAAG,KAEP,GACF;CACE,IAAM,IAAU,EAAY,CAAM,GAC5B,IAAW,EAAmC,IAAI,GAClD,CAAC,GAAO,KAAY,EAAS,EAAE,GAC/B,CAAC,GAAM,KAAW,EAAS,EAAK;CAiBtC,AAfA,EAAoB,UAAY;EAC5B,aAAa,EAAS,SAAS,MAAM;EACrC,WAAW,MAAiB;GAExB,AADA,EAAS,CAAI,GACb,EAAS,SAAS,MAAM;EAC5B;CACJ,EAAE,GASF,QAAsB;EAClB,IAAM,IAAO,EAAS;EACtB,IAAI,CAAC,GAAM;EACX,EAAK,MAAM,SAAS;EAEpB,IAAM,KADa,OAAO,WAAW,iBAAiB,CAAI,CAAC,CAAC,UAAU,KAAK,MAClD;EAEzB,AADA,EAAK,MAAM,SAAS,GAAG,KAAK,IAAI,EAAK,cAAc,CAAG,EAAE,KACxD,EAAK,MAAM,YAAY,EAAK,eAAe,IAAM,SAAS;CAC9D,GAAG,CAAC,GAAO,CAAO,CAAC;CAEnB,IAAM,IAAS,YAA2B;EACtC,IAAM,IAAO,EAAM,KAAK;EACpB,OAAC,KAAQ,KAAQ,IACrB;KAAQ,EAAI;GACZ,IAAI;IAEA,AADA,MAAM,EAAO,CAAI,GACjB,EAAS,EAAE;GACf,SAAS,GAAO;IACZ,IAAU,CAAK;GACnB,UAAU;IACN,EAAQ,EAAK;GACjB;EARY;CAShB,GAEM,KAAgB,MAA2B;EAE7C,AADA,EAAM,eAAe,GACrB,EAAY;CAChB,GASM,KAAiB,MAAoD;EACvE,IAAY,CAAK,GACb,GAAM,qBACN,EAAM,QAAQ,WAAW,EAAM,YAC/B,EAAM,YAAY,eAAe,EAAM,YAAY,QACvD,EAAM,eAAe,GACrB,EAAY;CAChB;CAEA,OACI,kBAAC,QAAD;EAAM,WAAW,EAAG,EAAO,UAAU,CAAS;EAAG,UAAU;EAA3D,UAAA,CACI,kBAAC,YAAD;GACI,GAAI;GACJ,KAAK;GACL,WAAW,EAAO;GAClB,MAAM;GACC;GACG;GACV,aAAa,KAAe,EAAQ;GACpC,WAAW,MAAU;IAEjB,AADA,EAAS,EAAM,OAAO,KAAK,GAC3B,IAAW,CAAK;GACpB;GACA,WAAW;EACd,CAAA,GACD,kBAAC,OAAD;GAAK,WAAW,EAAO;GAAvB,UAAA,CACK,GACD,kBAAC,UAAD;IACI,MAAK;IACL,WAAW,EAAO;IAClB,UAAU,KAAY,KAAQ,EAAM,KAAK,MAAM;IAE9C,UAAA,KAAa,EAAQ;GAClB,CAAA,CACP;EACH,CAAA,CAAA;;AAEd,CAAC"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
var e=
|
|
1
|
+
var e=3e5;function t(e){let t=new Date(e);return t.setHours(0,0,0,0),t.getTime()}function n({messages:n,currentUserId:r,windowMs:i=e}){let a=[],o=null,s=null;for(let e of n){let n=t(e.sentAt);n!==o&&(o=n,s=null,a.push({kind:`day`,key:`day-${n}`,date:n}));let c=s?.messages[s.messages.length-1];if(s!==null&&c!==void 0&&s.authorId===e.authorId&&e.sentAt-c.sentAt<=i&&s){s.messages.push(e);continue}s={kind:`run`,key:`${e.authorId}-${e.id}`,authorId:e.authorId,authorName:e.authorName,own:r!==void 0&&e.authorId===r,messages:[e]},a.push(s)}return a}var r={thread:`Conversa`,today:`Hoje`,yesterday:`Ontem`,you:`Você`,typingOne:e=>`${e} está digitando…`,typingTwo:(e,t)=>`${e} e ${t} estão digitando…`,typingMany:e=>`${e} pessoas estão digitando…`,sending:`Enviando`,sent:`Enviada`,read:`Lida`,failed:`Falhou ao enviar`,retry:`Tentar de novo`,empty:`Nenhuma mensagem ainda`,placeholder:`Escreva uma mensagem`,send:`Enviar`},i={thread:`Conversation`,today:`Today`,yesterday:`Yesterday`,you:`You`,typingOne:e=>`${e} is typing…`,typingTwo:(e,t)=>`${e} and ${t} are typing…`,typingMany:e=>`${e} people are typing…`,sending:`Sending`,sent:`Sent`,read:`Read`,failed:`Failed to send`,retry:`Try again`,empty:`No messages yet`,placeholder:`Write a message`,send:`Send`};function a(e){return e===`en`?i:r}function o(e,{locale:n=`pt-BR`,now:r}={}){let i=a(n),o=t(r??Date.now()),s=864e5;return e===o?i.today:e===o-s?i.yesterday:new Date(e).toLocaleDateString(n===`en`?`en-US`:`pt-BR`,{day:`2-digit`,month:`short`,year:e<o-300*s?`numeric`:void 0})}function s(e,t=`pt-BR`){return new Date(e).toLocaleTimeString(t===`en`?`en-US`:`pt-BR`,{hour:`2-digit`,minute:`2-digit`})}function c(e,t=`pt-BR`){let n=a(t);return e.length===0?null:e.length===1?n.typingOne(e[0]):e.length===2?n.typingTwo(e[0],e[1]):n.typingMany(e.length)}exports.DEFAULT_GROUP_WINDOW_MS=e,exports.chatStrings=a,exports.dayLabel=o,exports.groupMessages=n,exports.timeLabel=s,exports.typingLabel=c;
|
|
2
2
|
//# sourceMappingURL=chat-groups.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chat-groups.cjs","names":[],"sources":["../../../src/components/Chat/chat-groups.ts"],"sourcesContent":["import type { ReactNode } from \"react\";\n\n/** One entry in a thread. */\nexport interface ChatMessage {\n /** Stable identity. Used as the React key and by `onRetry`. */\n id: string;\n /** What was said. A node, so an app can render a link, an image or a quote. */\n body: ReactNode;\n /** Who said it. Compared against `currentUserId` to decide sides. */\n authorId: string;\n /** Display name. Falls back to `authorId` in the header of a run. */\n authorName?: string;\n /** Epoch milliseconds. */\n sentAt: number;\n /**\n * Delivery state of an outgoing message.\n *\n * `\"failed\"` is the one that matters: without it an app has to invent its own\n * way to say \"this never left\", and the user re-types a message that is\n * sitting right there.\n */\n status?: \"sending\" | \"sent\" | \"read\" | \"failed\";\n /** Anything the app wants to carry through to its own renderers. */\n data?: Record<string, unknown>;\n}\n\n/** A run of consecutive messages from one author, under one day. */\nexport interface ChatRun {\n kind: \"run\";\n /** `${authorId}-${first message id}` — stable across re-renders. */\n key: string;\n authorId: string;\n authorName?: string;\n /** Whether this run belongs to the current user. */\n own: boolean;\n messages: ChatMessage[];\n}\n\n/** A date heading between runs. */\nexport interface ChatDay {\n kind: \"day\";\n key: string;\n /** Midnight of that local day, epoch ms — the label is formatted by the view. */\n date: number;\n}\n\nexport type ChatSection = ChatDay | ChatRun;\n\n/** Default window in which consecutive messages from one author stay in a run. */\nexport const DEFAULT_GROUP_WINDOW_MS = 5 * 60 * 1000;\n\n/** Local midnight of an instant, epoch ms. */\nfunction startOfDay(timestamp: number): number {\n const date = new Date(timestamp);\n date.setHours(0, 0, 0, 0);\n return date.getTime();\n}\n\n/**\n * Turn a flat message list into the sections a thread renders: a date heading\n * whenever the local day changes, and runs of consecutive messages from the same\n * author.\n *\n * Grouping is what makes a thread readable — repeating the avatar and the name on\n * every line of a five-line burst turns a conversation into a list of receipts.\n * The run breaks on a different author, a different day, or a gap longer than\n * `windowMs`: a reply an hour later is a new beat in the conversation even when\n * nobody else spoke, and joining it to the earlier burst would put one timestamp\n * on messages an hour apart.\n *\n * Order is taken as given, oldest first, and never sorted here: a thread that\n * reorders what the server sent would fight optimistic inserts, where the\n * pending message is deliberately last.\n *\n * @param params.messages - Oldest first.\n * @param params.currentUserId - Author id treated as \"own\".\n * @param params.windowMs - Gap that still keeps a run together. Default 5 min.\n * @returns Sections in render order.\n */\nexport function groupMessages({\n messages,\n currentUserId,\n windowMs = DEFAULT_GROUP_WINDOW_MS,\n}: {\n messages: readonly ChatMessage[];\n currentUserId?: string;\n windowMs?: number;\n}): ChatSection[] {\n const sections: ChatSection[] = [];\n let day: number | null = null;\n let run: ChatRun | null = null;\n\n for (const message of messages) {\n const messageDay = startOfDay(message.sentAt);\n if (messageDay !== day) {\n day = messageDay;\n run = null;\n sections.push({ kind: \"day\", key: `day-${messageDay}`, date: messageDay });\n }\n\n const previous = run?.messages[run.messages.length - 1];\n const continues =\n run !== null &&\n previous !== undefined &&\n run.authorId === message.authorId &&\n message.sentAt - previous.sentAt <= windowMs;\n\n if (continues && run) {\n run.messages.push(message);\n continue;\n }\n\n run = {\n kind: \"run\",\n key: `${message.authorId}-${message.id}`,\n authorId: message.authorId,\n authorName: message.authorName,\n own: currentUserId !== undefined && message.authorId === currentUserId,\n messages: [message],\n };\n sections.push(run);\n }\n\n return sections;\n}\n\n/** Labels the thread needs, per locale. */\ninterface ChatStrings {\n thread: string;\n today: string;\n yesterday: string;\n you: string;\n typingOne: (name: string) => string;\n typingTwo: (a: string, b: string) => string;\n typingMany: (n: number) => string;\n sending: string;\n sent: string;\n read: string;\n failed: string;\n retry: string;\n empty: string;\n placeholder: string;\n send: string;\n}\n\nconst PT_BR: ChatStrings = {\n thread: \"Conversa\",\n today: \"Hoje\",\n yesterday: \"Ontem\",\n you: \"Você\",\n typingOne: (name) => `${name} está digitando…`,\n typingTwo: (a, b) => `${a} e ${b} estão digitando…`,\n typingMany: (n) => `${n} pessoas estão digitando…`,\n sending: \"Enviando\",\n sent: \"Enviada\",\n read: \"Lida\",\n failed: \"Falhou ao enviar\",\n retry: \"Tentar de novo\",\n empty: \"Nenhuma mensagem ainda\",\n placeholder: \"Escreva uma mensagem\",\n send: \"Enviar\",\n};\n\nconst EN: ChatStrings = {\n thread: \"Conversation\",\n today: \"Today\",\n yesterday: \"Yesterday\",\n you: \"You\",\n typingOne: (name) => `${name} is typing…`,\n typingTwo: (a, b) => `${a} and ${b} are typing…`,\n typingMany: (n) => `${n} people are typing…`,\n sending: \"Sending\",\n sent: \"Sent\",\n read: \"Read\",\n failed: \"Failed to send\",\n retry: \"Try again\",\n empty: \"No messages yet\",\n placeholder: \"Write a message\",\n send: \"Send\",\n};\n\n/** Locale strings for the thread. */\nexport function chatStrings(locale: \"pt-BR\" | \"en\"): ChatStrings {\n return locale === \"en\" ? EN : PT_BR;\n}\n\n/**\n * Label for a date heading: `\"Hoje\"`, `\"Ontem\"`, or the formatted date.\n *\n * @param date - Local midnight of the day being labelled.\n * @param params.now - Reference instant, so tests and SSR-free renders are stable.\n */\nexport function dayLabel(\n date: number,\n { locale = \"pt-BR\", now }: { locale?: \"pt-BR\" | \"en\"; now?: number } = {},\n): string {\n const strings = chatStrings(locale);\n const today = startOfDay(now ?? Date.now());\n const dayMs = 24 * 60 * 60 * 1000;\n if (date === today) return strings.today;\n if (date === today - dayMs) return strings.yesterday;\n return new Date(date).toLocaleDateString(locale === \"en\" ? \"en-US\" : \"pt-BR\", {\n day: \"2-digit\",\n month: \"short\",\n year: date < today - 300 * dayMs ? \"numeric\" : undefined,\n });\n}\n\n/** Clock label for a single message — the time, not a relative phrase. */\nexport function timeLabel(timestamp: number, locale: \"pt-BR\" | \"en\" = \"pt-BR\"): string {\n return new Date(timestamp).toLocaleTimeString(locale === \"en\" ? \"en-US\" : \"pt-BR\", {\n hour: \"2-digit\",\n minute: \"2-digit\",\n });\n}\n\n/** Sentence for the typing indicator, or `null` when nobody is typing. */\nexport function typingLabel(\n names: readonly string[],\n locale: \"pt-BR\" | \"en\" = \"pt-BR\",\n): string | null {\n const strings = chatStrings(locale);\n if (names.length === 0) return null;\n if (names.length === 1) return strings.typingOne(names[0]);\n if (names.length === 2) return strings.typingTwo(names[0], names[1]);\n return strings.typingMany(names.length);\n}\n"],"mappings":"AAiDA,IAAa,EAA0B,
|
|
1
|
+
{"version":3,"file":"chat-groups.cjs","names":[],"sources":["../../../src/components/Chat/chat-groups.ts"],"sourcesContent":["import type { ReactNode } from \"react\";\n\n/** One entry in a thread. */\nexport interface ChatMessage {\n /** Stable identity. Used as the React key and by `onRetry`. */\n id: string;\n /** What was said. A node, so an app can render a link, an image or a quote. */\n body: ReactNode;\n /** Who said it. Compared against `currentUserId` to decide sides. */\n authorId: string;\n /** Display name. Falls back to `authorId` in the header of a run. */\n authorName?: string;\n /** Epoch milliseconds. */\n sentAt: number;\n /**\n * Delivery state of an outgoing message.\n *\n * `\"failed\"` is the one that matters: without it an app has to invent its own\n * way to say \"this never left\", and the user re-types a message that is\n * sitting right there.\n */\n status?: \"sending\" | \"sent\" | \"read\" | \"failed\";\n /** Anything the app wants to carry through to its own renderers. */\n data?: Record<string, unknown>;\n}\n\n/** A run of consecutive messages from one author, under one day. */\nexport interface ChatRun {\n kind: \"run\";\n /** `${authorId}-${first message id}` — stable across re-renders. */\n key: string;\n authorId: string;\n authorName?: string;\n /** Whether this run belongs to the current user. */\n own: boolean;\n messages: ChatMessage[];\n}\n\n/** A date heading between runs. */\nexport interface ChatDay {\n kind: \"day\";\n key: string;\n /** Midnight of that local day, epoch ms — the label is formatted by the view. */\n date: number;\n}\n\nexport type ChatSection = ChatDay | ChatRun;\n\n/** Default window in which consecutive messages from one author stay in a run. */\nexport const DEFAULT_GROUP_WINDOW_MS = 5 * 60 * 1000;\n\n/** Local midnight of an instant, epoch ms. */\nfunction startOfDay(timestamp: number): number {\n const date = new Date(timestamp);\n date.setHours(0, 0, 0, 0);\n return date.getTime();\n}\n\n/**\n * Turn a flat message list into the sections a thread renders: a date heading\n * whenever the local day changes, and runs of consecutive messages from the same\n * author.\n *\n * Grouping is what makes a thread readable — repeating the avatar and the name on\n * every line of a five-line burst turns a conversation into a list of receipts.\n * The run breaks on a different author, a different day, or a gap longer than\n * `windowMs`: a reply an hour later is a new beat in the conversation even when\n * nobody else spoke, and joining it to the earlier burst would put one timestamp\n * on messages an hour apart.\n *\n * Order is taken as given, oldest first, and never sorted here: a thread that\n * reorders what the server sent would fight optimistic inserts, where the\n * pending message is deliberately last.\n *\n * @param params.messages - Oldest first.\n * @param params.currentUserId - Author id treated as \"own\".\n * @param params.windowMs - Gap that still keeps a run together. Default 5 min.\n * @returns Sections in render order.\n */\nexport function groupMessages({\n messages,\n currentUserId,\n windowMs = DEFAULT_GROUP_WINDOW_MS,\n}: {\n messages: readonly ChatMessage[];\n currentUserId?: string;\n windowMs?: number;\n}): ChatSection[] {\n const sections: ChatSection[] = [];\n let day: number | null = null;\n let run: ChatRun | null = null;\n\n for (const message of messages) {\n const messageDay = startOfDay(message.sentAt);\n if (messageDay !== day) {\n day = messageDay;\n run = null;\n sections.push({ kind: \"day\", key: `day-${messageDay}`, date: messageDay });\n }\n\n const previous = run?.messages[run.messages.length - 1];\n const continues =\n run !== null &&\n previous !== undefined &&\n run.authorId === message.authorId &&\n message.sentAt - previous.sentAt <= windowMs;\n\n if (continues && run) {\n run.messages.push(message);\n continue;\n }\n\n run = {\n kind: \"run\",\n key: `${message.authorId}-${message.id}`,\n authorId: message.authorId,\n authorName: message.authorName,\n own: currentUserId !== undefined && message.authorId === currentUserId,\n messages: [message],\n };\n sections.push(run);\n }\n\n return sections;\n}\n\n/** Labels the thread needs, per locale. */\ninterface ChatStrings {\n thread: string;\n today: string;\n yesterday: string;\n you: string;\n typingOne: (name: string) => string;\n typingTwo: (a: string, b: string) => string;\n typingMany: (n: number) => string;\n sending: string;\n sent: string;\n read: string;\n failed: string;\n retry: string;\n empty: string;\n placeholder: string;\n send: string;\n}\n\nconst PT_BR: ChatStrings = {\n thread: \"Conversa\",\n today: \"Hoje\",\n yesterday: \"Ontem\",\n you: \"Você\",\n typingOne: (name) => `${name} está digitando…`,\n typingTwo: (a, b) => `${a} e ${b} estão digitando…`,\n typingMany: (n) => `${n} pessoas estão digitando…`,\n sending: \"Enviando\",\n sent: \"Enviada\",\n read: \"Lida\",\n failed: \"Falhou ao enviar\",\n retry: \"Tentar de novo\",\n empty: \"Nenhuma mensagem ainda\",\n placeholder: \"Escreva uma mensagem\",\n send: \"Enviar\",\n};\n\nconst EN: ChatStrings = {\n thread: \"Conversation\",\n today: \"Today\",\n yesterday: \"Yesterday\",\n you: \"You\",\n typingOne: (name) => `${name} is typing…`,\n typingTwo: (a, b) => `${a} and ${b} are typing…`,\n typingMany: (n) => `${n} people are typing…`,\n sending: \"Sending\",\n sent: \"Sent\",\n read: \"Read\",\n failed: \"Failed to send\",\n retry: \"Try again\",\n empty: \"No messages yet\",\n placeholder: \"Write a message\",\n send: \"Send\",\n};\n\n/** Locale strings for the thread. */\nexport function chatStrings(locale: \"pt-BR\" | \"en\"): ChatStrings {\n return locale === \"en\" ? EN : PT_BR;\n}\n\n/**\n * Label for a date heading: `\"Hoje\"`, `\"Ontem\"`, or the formatted date.\n *\n * @param date - Local midnight of the day being labelled.\n * @param params.now - Reference instant, so tests and SSR-free renders are stable.\n */\nexport function dayLabel(\n date: number,\n { locale = \"pt-BR\", now }: { locale?: \"pt-BR\" | \"en\"; now?: number } = {},\n): string {\n const strings = chatStrings(locale);\n const today = startOfDay(now ?? Date.now());\n const dayMs = 24 * 60 * 60 * 1000;\n if (date === today) return strings.today;\n if (date === today - dayMs) return strings.yesterday;\n return new Date(date).toLocaleDateString(locale === \"en\" ? \"en-US\" : \"pt-BR\", {\n day: \"2-digit\",\n month: \"short\",\n year: date < today - 300 * dayMs ? \"numeric\" : undefined,\n });\n}\n\n/** Clock label for a single message — the time, not a relative phrase. */\nexport function timeLabel(timestamp: number, locale: \"pt-BR\" | \"en\" = \"pt-BR\"): string {\n return new Date(timestamp).toLocaleTimeString(locale === \"en\" ? \"en-US\" : \"pt-BR\", {\n hour: \"2-digit\",\n minute: \"2-digit\",\n });\n}\n\n/** Sentence for the typing indicator, or `null` when nobody is typing. */\nexport function typingLabel(\n names: readonly string[],\n locale: \"pt-BR\" | \"en\" = \"pt-BR\",\n): string | null {\n const strings = chatStrings(locale);\n if (names.length === 0) return null;\n if (names.length === 1) return strings.typingOne(names[0]);\n if (names.length === 2) return strings.typingTwo(names[0], names[1]);\n return strings.typingMany(names.length);\n}\n"],"mappings":"AAiDA,IAAa,EAA0B,IAGvC,SAAS,EAAW,EAA2B,CAC3C,IAAM,EAAO,IAAI,KAAK,CAAS,EAE/B,OADA,EAAK,SAAS,EAAG,EAAG,EAAG,CAAC,EACjB,EAAK,QAAQ,CACxB,CAuBA,SAAgB,EAAc,CAC1B,WACA,gBACA,WAAW,GAKG,CACd,IAAM,EAA0B,CAAC,EAC7B,EAAqB,KACrB,EAAsB,KAE1B,IAAK,IAAM,KAAW,EAAU,CAC5B,IAAM,EAAa,EAAW,EAAQ,MAAM,EACxC,IAAe,IACf,EAAM,EACN,EAAM,KACN,EAAS,KAAK,CAAE,KAAM,MAAO,IAAK,OAAO,IAAc,KAAM,CAAW,CAAC,GAG7E,IAAM,EAAW,GAAK,SAAS,EAAI,SAAS,OAAS,GAOrD,GALI,IAAQ,MACR,IAAa,IAAA,IACb,EAAI,WAAa,EAAQ,UACzB,EAAQ,OAAS,EAAS,QAAU,GAEvB,EAAK,CAClB,EAAI,SAAS,KAAK,CAAO,EACzB,QACJ,CAEA,EAAM,CACF,KAAM,MACN,IAAK,GAAG,EAAQ,SAAS,GAAG,EAAQ,KACpC,SAAU,EAAQ,SAClB,WAAY,EAAQ,WACpB,IAAK,IAAkB,IAAA,IAAa,EAAQ,WAAa,EACzD,SAAU,CAAC,CAAO,CACtB,EACA,EAAS,KAAK,CAAG,CACrB,CAEA,OAAO,CACX,CAqBA,IAAM,EAAqB,CACvB,OAAQ,WACR,MAAO,OACP,UAAW,QACX,IAAK,OACL,UAAY,GAAS,GAAG,EAAK,kBAC7B,WAAY,EAAG,IAAM,GAAG,EAAE,KAAK,EAAE,mBACjC,WAAa,GAAM,GAAG,EAAE,2BACxB,QAAS,WACT,KAAM,UACN,KAAM,OACN,OAAQ,mBACR,MAAO,iBACP,MAAO,yBACP,YAAa,uBACb,KAAM,QACV,EAEM,EAAkB,CACpB,OAAQ,eACR,MAAO,QACP,UAAW,YACX,IAAK,MACL,UAAY,GAAS,GAAG,EAAK,aAC7B,WAAY,EAAG,IAAM,GAAG,EAAE,OAAO,EAAE,cACnC,WAAa,GAAM,GAAG,EAAE,qBACxB,QAAS,UACT,KAAM,OACN,KAAM,OACN,OAAQ,iBACR,MAAO,YACP,MAAO,kBACP,YAAa,kBACb,KAAM,MACV,EAGA,SAAgB,EAAY,EAAqC,CAC7D,OAAO,IAAW,KAAO,EAAK,CAClC,CAQA,SAAgB,EACZ,EACA,CAAE,SAAS,QAAS,OAAmD,CAAC,EAClE,CACN,IAAM,EAAU,EAAY,CAAM,EAC5B,EAAQ,EAAW,GAAO,KAAK,IAAI,CAAC,EACpC,EAAQ,MAGd,OAFI,IAAS,EAAc,EAAQ,MAC/B,IAAS,EAAQ,EAAc,EAAQ,UACpC,IAAI,KAAK,CAAI,CAAC,CAAC,mBAAmB,IAAW,KAAO,QAAU,QAAS,CAC1E,IAAK,UACL,MAAO,QACP,KAAM,EAAO,EAAQ,IAAM,EAAQ,UAAY,IAAA,EACnD,CAAC,CACL,CAGA,SAAgB,EAAU,EAAmB,EAAyB,QAAiB,CACnF,OAAO,IAAI,KAAK,CAAS,CAAC,CAAC,mBAAmB,IAAW,KAAO,QAAU,QAAS,CAC/E,KAAM,UACN,OAAQ,SACZ,CAAC,CACL,CAGA,SAAgB,EACZ,EACA,EAAyB,QACZ,CACb,IAAM,EAAU,EAAY,CAAM,EAIlC,OAHI,EAAM,SAAW,EAAU,KAC3B,EAAM,SAAW,EAAU,EAAQ,UAAU,EAAM,EAAE,EACrD,EAAM,SAAW,EAAU,EAAQ,UAAU,EAAM,GAAI,EAAM,EAAE,EAC5D,EAAQ,WAAW,EAAM,MAAM,CAC1C"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
//#region src/components/Chat/chat-groups.ts
|
|
2
|
-
var e =
|
|
2
|
+
var e = 3e5;
|
|
3
3
|
function t(e) {
|
|
4
4
|
let t = new Date(e);
|
|
5
5
|
return t.setHours(0, 0, 0, 0), t.getTime();
|
|
@@ -66,7 +66,7 @@ function a(e) {
|
|
|
66
66
|
return e === "en" ? i : r;
|
|
67
67
|
}
|
|
68
68
|
function o(e, { locale: n = "pt-BR", now: r } = {}) {
|
|
69
|
-
let i = a(n), o = t(r ?? Date.now()), s =
|
|
69
|
+
let i = a(n), o = t(r ?? Date.now()), s = 864e5;
|
|
70
70
|
return e === o ? i.today : e === o - s ? i.yesterday : new Date(e).toLocaleDateString(n === "en" ? "en-US" : "pt-BR", {
|
|
71
71
|
day: "2-digit",
|
|
72
72
|
month: "short",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chat-groups.js","names":[],"sources":["../../../src/components/Chat/chat-groups.ts"],"sourcesContent":["import type { ReactNode } from \"react\";\n\n/** One entry in a thread. */\nexport interface ChatMessage {\n /** Stable identity. Used as the React key and by `onRetry`. */\n id: string;\n /** What was said. A node, so an app can render a link, an image or a quote. */\n body: ReactNode;\n /** Who said it. Compared against `currentUserId` to decide sides. */\n authorId: string;\n /** Display name. Falls back to `authorId` in the header of a run. */\n authorName?: string;\n /** Epoch milliseconds. */\n sentAt: number;\n /**\n * Delivery state of an outgoing message.\n *\n * `\"failed\"` is the one that matters: without it an app has to invent its own\n * way to say \"this never left\", and the user re-types a message that is\n * sitting right there.\n */\n status?: \"sending\" | \"sent\" | \"read\" | \"failed\";\n /** Anything the app wants to carry through to its own renderers. */\n data?: Record<string, unknown>;\n}\n\n/** A run of consecutive messages from one author, under one day. */\nexport interface ChatRun {\n kind: \"run\";\n /** `${authorId}-${first message id}` — stable across re-renders. */\n key: string;\n authorId: string;\n authorName?: string;\n /** Whether this run belongs to the current user. */\n own: boolean;\n messages: ChatMessage[];\n}\n\n/** A date heading between runs. */\nexport interface ChatDay {\n kind: \"day\";\n key: string;\n /** Midnight of that local day, epoch ms — the label is formatted by the view. */\n date: number;\n}\n\nexport type ChatSection = ChatDay | ChatRun;\n\n/** Default window in which consecutive messages from one author stay in a run. */\nexport const DEFAULT_GROUP_WINDOW_MS = 5 * 60 * 1000;\n\n/** Local midnight of an instant, epoch ms. */\nfunction startOfDay(timestamp: number): number {\n const date = new Date(timestamp);\n date.setHours(0, 0, 0, 0);\n return date.getTime();\n}\n\n/**\n * Turn a flat message list into the sections a thread renders: a date heading\n * whenever the local day changes, and runs of consecutive messages from the same\n * author.\n *\n * Grouping is what makes a thread readable — repeating the avatar and the name on\n * every line of a five-line burst turns a conversation into a list of receipts.\n * The run breaks on a different author, a different day, or a gap longer than\n * `windowMs`: a reply an hour later is a new beat in the conversation even when\n * nobody else spoke, and joining it to the earlier burst would put one timestamp\n * on messages an hour apart.\n *\n * Order is taken as given, oldest first, and never sorted here: a thread that\n * reorders what the server sent would fight optimistic inserts, where the\n * pending message is deliberately last.\n *\n * @param params.messages - Oldest first.\n * @param params.currentUserId - Author id treated as \"own\".\n * @param params.windowMs - Gap that still keeps a run together. Default 5 min.\n * @returns Sections in render order.\n */\nexport function groupMessages({\n messages,\n currentUserId,\n windowMs = DEFAULT_GROUP_WINDOW_MS,\n}: {\n messages: readonly ChatMessage[];\n currentUserId?: string;\n windowMs?: number;\n}): ChatSection[] {\n const sections: ChatSection[] = [];\n let day: number | null = null;\n let run: ChatRun | null = null;\n\n for (const message of messages) {\n const messageDay = startOfDay(message.sentAt);\n if (messageDay !== day) {\n day = messageDay;\n run = null;\n sections.push({ kind: \"day\", key: `day-${messageDay}`, date: messageDay });\n }\n\n const previous = run?.messages[run.messages.length - 1];\n const continues =\n run !== null &&\n previous !== undefined &&\n run.authorId === message.authorId &&\n message.sentAt - previous.sentAt <= windowMs;\n\n if (continues && run) {\n run.messages.push(message);\n continue;\n }\n\n run = {\n kind: \"run\",\n key: `${message.authorId}-${message.id}`,\n authorId: message.authorId,\n authorName: message.authorName,\n own: currentUserId !== undefined && message.authorId === currentUserId,\n messages: [message],\n };\n sections.push(run);\n }\n\n return sections;\n}\n\n/** Labels the thread needs, per locale. */\ninterface ChatStrings {\n thread: string;\n today: string;\n yesterday: string;\n you: string;\n typingOne: (name: string) => string;\n typingTwo: (a: string, b: string) => string;\n typingMany: (n: number) => string;\n sending: string;\n sent: string;\n read: string;\n failed: string;\n retry: string;\n empty: string;\n placeholder: string;\n send: string;\n}\n\nconst PT_BR: ChatStrings = {\n thread: \"Conversa\",\n today: \"Hoje\",\n yesterday: \"Ontem\",\n you: \"Você\",\n typingOne: (name) => `${name} está digitando…`,\n typingTwo: (a, b) => `${a} e ${b} estão digitando…`,\n typingMany: (n) => `${n} pessoas estão digitando…`,\n sending: \"Enviando\",\n sent: \"Enviada\",\n read: \"Lida\",\n failed: \"Falhou ao enviar\",\n retry: \"Tentar de novo\",\n empty: \"Nenhuma mensagem ainda\",\n placeholder: \"Escreva uma mensagem\",\n send: \"Enviar\",\n};\n\nconst EN: ChatStrings = {\n thread: \"Conversation\",\n today: \"Today\",\n yesterday: \"Yesterday\",\n you: \"You\",\n typingOne: (name) => `${name} is typing…`,\n typingTwo: (a, b) => `${a} and ${b} are typing…`,\n typingMany: (n) => `${n} people are typing…`,\n sending: \"Sending\",\n sent: \"Sent\",\n read: \"Read\",\n failed: \"Failed to send\",\n retry: \"Try again\",\n empty: \"No messages yet\",\n placeholder: \"Write a message\",\n send: \"Send\",\n};\n\n/** Locale strings for the thread. */\nexport function chatStrings(locale: \"pt-BR\" | \"en\"): ChatStrings {\n return locale === \"en\" ? EN : PT_BR;\n}\n\n/**\n * Label for a date heading: `\"Hoje\"`, `\"Ontem\"`, or the formatted date.\n *\n * @param date - Local midnight of the day being labelled.\n * @param params.now - Reference instant, so tests and SSR-free renders are stable.\n */\nexport function dayLabel(\n date: number,\n { locale = \"pt-BR\", now }: { locale?: \"pt-BR\" | \"en\"; now?: number } = {},\n): string {\n const strings = chatStrings(locale);\n const today = startOfDay(now ?? Date.now());\n const dayMs = 24 * 60 * 60 * 1000;\n if (date === today) return strings.today;\n if (date === today - dayMs) return strings.yesterday;\n return new Date(date).toLocaleDateString(locale === \"en\" ? \"en-US\" : \"pt-BR\", {\n day: \"2-digit\",\n month: \"short\",\n year: date < today - 300 * dayMs ? \"numeric\" : undefined,\n });\n}\n\n/** Clock label for a single message — the time, not a relative phrase. */\nexport function timeLabel(timestamp: number, locale: \"pt-BR\" | \"en\" = \"pt-BR\"): string {\n return new Date(timestamp).toLocaleTimeString(locale === \"en\" ? \"en-US\" : \"pt-BR\", {\n hour: \"2-digit\",\n minute: \"2-digit\",\n });\n}\n\n/** Sentence for the typing indicator, or `null` when nobody is typing. */\nexport function typingLabel(\n names: readonly string[],\n locale: \"pt-BR\" | \"en\" = \"pt-BR\",\n): string | null {\n const strings = chatStrings(locale);\n if (names.length === 0) return null;\n if (names.length === 1) return strings.typingOne(names[0]);\n if (names.length === 2) return strings.typingTwo(names[0], names[1]);\n return strings.typingMany(names.length);\n}\n"],"mappings":";AAiDA,IAAa,IAA0B
|
|
1
|
+
{"version":3,"file":"chat-groups.js","names":[],"sources":["../../../src/components/Chat/chat-groups.ts"],"sourcesContent":["import type { ReactNode } from \"react\";\n\n/** One entry in a thread. */\nexport interface ChatMessage {\n /** Stable identity. Used as the React key and by `onRetry`. */\n id: string;\n /** What was said. A node, so an app can render a link, an image or a quote. */\n body: ReactNode;\n /** Who said it. Compared against `currentUserId` to decide sides. */\n authorId: string;\n /** Display name. Falls back to `authorId` in the header of a run. */\n authorName?: string;\n /** Epoch milliseconds. */\n sentAt: number;\n /**\n * Delivery state of an outgoing message.\n *\n * `\"failed\"` is the one that matters: without it an app has to invent its own\n * way to say \"this never left\", and the user re-types a message that is\n * sitting right there.\n */\n status?: \"sending\" | \"sent\" | \"read\" | \"failed\";\n /** Anything the app wants to carry through to its own renderers. */\n data?: Record<string, unknown>;\n}\n\n/** A run of consecutive messages from one author, under one day. */\nexport interface ChatRun {\n kind: \"run\";\n /** `${authorId}-${first message id}` — stable across re-renders. */\n key: string;\n authorId: string;\n authorName?: string;\n /** Whether this run belongs to the current user. */\n own: boolean;\n messages: ChatMessage[];\n}\n\n/** A date heading between runs. */\nexport interface ChatDay {\n kind: \"day\";\n key: string;\n /** Midnight of that local day, epoch ms — the label is formatted by the view. */\n date: number;\n}\n\nexport type ChatSection = ChatDay | ChatRun;\n\n/** Default window in which consecutive messages from one author stay in a run. */\nexport const DEFAULT_GROUP_WINDOW_MS = 5 * 60 * 1000;\n\n/** Local midnight of an instant, epoch ms. */\nfunction startOfDay(timestamp: number): number {\n const date = new Date(timestamp);\n date.setHours(0, 0, 0, 0);\n return date.getTime();\n}\n\n/**\n * Turn a flat message list into the sections a thread renders: a date heading\n * whenever the local day changes, and runs of consecutive messages from the same\n * author.\n *\n * Grouping is what makes a thread readable — repeating the avatar and the name on\n * every line of a five-line burst turns a conversation into a list of receipts.\n * The run breaks on a different author, a different day, or a gap longer than\n * `windowMs`: a reply an hour later is a new beat in the conversation even when\n * nobody else spoke, and joining it to the earlier burst would put one timestamp\n * on messages an hour apart.\n *\n * Order is taken as given, oldest first, and never sorted here: a thread that\n * reorders what the server sent would fight optimistic inserts, where the\n * pending message is deliberately last.\n *\n * @param params.messages - Oldest first.\n * @param params.currentUserId - Author id treated as \"own\".\n * @param params.windowMs - Gap that still keeps a run together. Default 5 min.\n * @returns Sections in render order.\n */\nexport function groupMessages({\n messages,\n currentUserId,\n windowMs = DEFAULT_GROUP_WINDOW_MS,\n}: {\n messages: readonly ChatMessage[];\n currentUserId?: string;\n windowMs?: number;\n}): ChatSection[] {\n const sections: ChatSection[] = [];\n let day: number | null = null;\n let run: ChatRun | null = null;\n\n for (const message of messages) {\n const messageDay = startOfDay(message.sentAt);\n if (messageDay !== day) {\n day = messageDay;\n run = null;\n sections.push({ kind: \"day\", key: `day-${messageDay}`, date: messageDay });\n }\n\n const previous = run?.messages[run.messages.length - 1];\n const continues =\n run !== null &&\n previous !== undefined &&\n run.authorId === message.authorId &&\n message.sentAt - previous.sentAt <= windowMs;\n\n if (continues && run) {\n run.messages.push(message);\n continue;\n }\n\n run = {\n kind: \"run\",\n key: `${message.authorId}-${message.id}`,\n authorId: message.authorId,\n authorName: message.authorName,\n own: currentUserId !== undefined && message.authorId === currentUserId,\n messages: [message],\n };\n sections.push(run);\n }\n\n return sections;\n}\n\n/** Labels the thread needs, per locale. */\ninterface ChatStrings {\n thread: string;\n today: string;\n yesterday: string;\n you: string;\n typingOne: (name: string) => string;\n typingTwo: (a: string, b: string) => string;\n typingMany: (n: number) => string;\n sending: string;\n sent: string;\n read: string;\n failed: string;\n retry: string;\n empty: string;\n placeholder: string;\n send: string;\n}\n\nconst PT_BR: ChatStrings = {\n thread: \"Conversa\",\n today: \"Hoje\",\n yesterday: \"Ontem\",\n you: \"Você\",\n typingOne: (name) => `${name} está digitando…`,\n typingTwo: (a, b) => `${a} e ${b} estão digitando…`,\n typingMany: (n) => `${n} pessoas estão digitando…`,\n sending: \"Enviando\",\n sent: \"Enviada\",\n read: \"Lida\",\n failed: \"Falhou ao enviar\",\n retry: \"Tentar de novo\",\n empty: \"Nenhuma mensagem ainda\",\n placeholder: \"Escreva uma mensagem\",\n send: \"Enviar\",\n};\n\nconst EN: ChatStrings = {\n thread: \"Conversation\",\n today: \"Today\",\n yesterday: \"Yesterday\",\n you: \"You\",\n typingOne: (name) => `${name} is typing…`,\n typingTwo: (a, b) => `${a} and ${b} are typing…`,\n typingMany: (n) => `${n} people are typing…`,\n sending: \"Sending\",\n sent: \"Sent\",\n read: \"Read\",\n failed: \"Failed to send\",\n retry: \"Try again\",\n empty: \"No messages yet\",\n placeholder: \"Write a message\",\n send: \"Send\",\n};\n\n/** Locale strings for the thread. */\nexport function chatStrings(locale: \"pt-BR\" | \"en\"): ChatStrings {\n return locale === \"en\" ? EN : PT_BR;\n}\n\n/**\n * Label for a date heading: `\"Hoje\"`, `\"Ontem\"`, or the formatted date.\n *\n * @param date - Local midnight of the day being labelled.\n * @param params.now - Reference instant, so tests and SSR-free renders are stable.\n */\nexport function dayLabel(\n date: number,\n { locale = \"pt-BR\", now }: { locale?: \"pt-BR\" | \"en\"; now?: number } = {},\n): string {\n const strings = chatStrings(locale);\n const today = startOfDay(now ?? Date.now());\n const dayMs = 24 * 60 * 60 * 1000;\n if (date === today) return strings.today;\n if (date === today - dayMs) return strings.yesterday;\n return new Date(date).toLocaleDateString(locale === \"en\" ? \"en-US\" : \"pt-BR\", {\n day: \"2-digit\",\n month: \"short\",\n year: date < today - 300 * dayMs ? \"numeric\" : undefined,\n });\n}\n\n/** Clock label for a single message — the time, not a relative phrase. */\nexport function timeLabel(timestamp: number, locale: \"pt-BR\" | \"en\" = \"pt-BR\"): string {\n return new Date(timestamp).toLocaleTimeString(locale === \"en\" ? \"en-US\" : \"pt-BR\", {\n hour: \"2-digit\",\n minute: \"2-digit\",\n });\n}\n\n/** Sentence for the typing indicator, or `null` when nobody is typing. */\nexport function typingLabel(\n names: readonly string[],\n locale: \"pt-BR\" | \"en\" = \"pt-BR\",\n): string | null {\n const strings = chatStrings(locale);\n if (names.length === 0) return null;\n if (names.length === 1) return strings.typingOne(names[0]);\n if (names.length === 2) return strings.typingTwo(names[0], names[1]);\n return strings.typingMany(names.length);\n}\n"],"mappings":";AAiDA,IAAa,IAA0B;AAGvC,SAAS,EAAW,GAA2B;CAC3C,IAAM,IAAO,IAAI,KAAK,CAAS;CAE/B,OADA,EAAK,SAAS,GAAG,GAAG,GAAG,CAAC,GACjB,EAAK,QAAQ;AACxB;AAuBA,SAAgB,EAAc,EAC1B,aACA,kBACA,cAAW,KAKG;CACd,IAAM,IAA0B,CAAC,GAC7B,IAAqB,MACrB,IAAsB;CAE1B,KAAK,IAAM,KAAW,GAAU;EAC5B,IAAM,IAAa,EAAW,EAAQ,MAAM;EAC5C,AAAI,MAAe,MACf,IAAM,GACN,IAAM,MACN,EAAS,KAAK;GAAE,MAAM;GAAO,KAAK,OAAO;GAAc,MAAM;EAAW,CAAC;EAG7E,IAAM,IAAW,GAAK,SAAS,EAAI,SAAS,SAAS;EAOrD,IALI,MAAQ,QACR,MAAa,KAAA,KACb,EAAI,aAAa,EAAQ,YACzB,EAAQ,SAAS,EAAS,UAAU,KAEvB,GAAK;GAClB,EAAI,SAAS,KAAK,CAAO;GACzB;EACJ;EAUA,AARA,IAAM;GACF,MAAM;GACN,KAAK,GAAG,EAAQ,SAAS,GAAG,EAAQ;GACpC,UAAU,EAAQ;GAClB,YAAY,EAAQ;GACpB,KAAK,MAAkB,KAAA,KAAa,EAAQ,aAAa;GACzD,UAAU,CAAC,CAAO;EACtB,GACA,EAAS,KAAK,CAAG;CACrB;CAEA,OAAO;AACX;AAqBA,IAAM,IAAqB;CACvB,QAAQ;CACR,OAAO;CACP,WAAW;CACX,KAAK;CACL,YAAY,MAAS,GAAG,EAAK;CAC7B,YAAY,GAAG,MAAM,GAAG,EAAE,KAAK,EAAE;CACjC,aAAa,MAAM,GAAG,EAAE;CACxB,SAAS;CACT,MAAM;CACN,MAAM;CACN,QAAQ;CACR,OAAO;CACP,OAAO;CACP,aAAa;CACb,MAAM;AACV,GAEM,IAAkB;CACpB,QAAQ;CACR,OAAO;CACP,WAAW;CACX,KAAK;CACL,YAAY,MAAS,GAAG,EAAK;CAC7B,YAAY,GAAG,MAAM,GAAG,EAAE,OAAO,EAAE;CACnC,aAAa,MAAM,GAAG,EAAE;CACxB,SAAS;CACT,MAAM;CACN,MAAM;CACN,QAAQ;CACR,OAAO;CACP,OAAO;CACP,aAAa;CACb,MAAM;AACV;AAGA,SAAgB,EAAY,GAAqC;CAC7D,OAAO,MAAW,OAAO,IAAK;AAClC;AAQA,SAAgB,EACZ,GACA,EAAE,YAAS,SAAS,WAAmD,CAAC,GAClE;CACN,IAAM,IAAU,EAAY,CAAM,GAC5B,IAAQ,EAAW,KAAO,KAAK,IAAI,CAAC,GACpC,IAAQ;CAGd,OAFI,MAAS,IAAc,EAAQ,QAC/B,MAAS,IAAQ,IAAc,EAAQ,YACpC,IAAI,KAAK,CAAI,CAAC,CAAC,mBAAmB,MAAW,OAAO,UAAU,SAAS;EAC1E,KAAK;EACL,OAAO;EACP,MAAM,IAAO,IAAQ,MAAM,IAAQ,YAAY,KAAA;CACnD,CAAC;AACL;AAGA,SAAgB,EAAU,GAAmB,IAAyB,SAAiB;CACnF,OAAO,IAAI,KAAK,CAAS,CAAC,CAAC,mBAAmB,MAAW,OAAO,UAAU,SAAS;EAC/E,MAAM;EACN,QAAQ;CACZ,CAAC;AACL;AAGA,SAAgB,EACZ,GACA,IAAyB,SACZ;CACb,IAAM,IAAU,EAAY,CAAM;CAIlC,OAHI,EAAM,WAAW,IAAU,OAC3B,EAAM,WAAW,IAAU,EAAQ,UAAU,EAAM,EAAE,IACrD,EAAM,WAAW,IAAU,EAAQ,UAAU,EAAM,IAAI,EAAM,EAAE,IAC5D,EAAQ,WAAW,EAAM,MAAM;AAC1C"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Checkbox.cjs","names":[],"sources":["../../../src/components/Checkbox/Checkbox.tsx"],"sourcesContent":["import { forwardRef, useEffect, useRef } from \"react\";\nimport type { InputHTMLAttributes, ReactNode } from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport styles from \"./Checkbox.module.css\";\n\nexport interface CheckboxProps extends Omit<InputHTMLAttributes<HTMLInputElement>, \"type\"> {\n label?: ReactNode;\n description?: ReactNode;\n /** Render the box in the indeterminate state regardless of `checked`. */\n indeterminate?: boolean;\n wrapperClassName?: string;\n}\n\n/**\n * Accessible checkbox. Supports a tri-state via `indeterminate` and pairs the\n * input with a label/description column for forms.\n */\nexport const Checkbox = forwardRef<HTMLInputElement, CheckboxProps>(function Checkbox(\n { label, description, indeterminate, disabled, wrapperClassName, className, ...props },\n ref,\n) {\n const innerRef = useRef<HTMLInputElement | null>(null);\n\n useEffect(() => {\n if (innerRef.current) innerRef.current.indeterminate = Boolean(indeterminate);\n }, [indeterminate]);\n\n function setRef(node: HTMLInputElement | null): void {\n innerRef.current = node;\n if (typeof ref === \"function\") ref(node);\n else if (ref) (ref as React.MutableRefObject<HTMLInputElement | null>).current = node;\n }\n\n return (\n <label className={cn(styles.wrapper, disabled && styles.disabled, wrapperClassName)}>\n <input\n ref={setRef}\n type=\"checkbox\"\n disabled={disabled}\n className={cn(styles.input, className)}\n {...props}\n />\n <span className={styles.box} aria-hidden>\n {indeterminate ? <DashIcon /> : <CheckIcon />}\n </span>\n {(label || description) && (\n <span className={styles.labelWrap}>\n {label && <span className={styles.label}>{label}</span>}\n {description && <span className={styles.description}>{description}</span>}\n </span>\n )}\n </label>\n );\n});\n\nfunction CheckIcon() {\n return (\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\">\n <path\n d=\"M5 12l5 5L20 7\"\n stroke=\"currentColor\"\n strokeWidth=\"3\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n </svg>\n );\n}\n\nfunction DashIcon() {\n return (\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\">\n <path d=\"M5 12h14\" stroke=\"currentColor\" strokeWidth=\"3\" strokeLinecap=\"round\" />\n </svg>\n );\n}\n"],"mappings":"+HAiBA,IAAa,GAAA,
|
|
1
|
+
{"version":3,"file":"Checkbox.cjs","names":[],"sources":["../../../src/components/Checkbox/Checkbox.tsx"],"sourcesContent":["import { forwardRef, useEffect, useRef } from \"react\";\nimport type { InputHTMLAttributes, ReactNode } from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport styles from \"./Checkbox.module.css\";\n\nexport interface CheckboxProps extends Omit<InputHTMLAttributes<HTMLInputElement>, \"type\"> {\n label?: ReactNode;\n description?: ReactNode;\n /** Render the box in the indeterminate state regardless of `checked`. */\n indeterminate?: boolean;\n wrapperClassName?: string;\n}\n\n/**\n * Accessible checkbox. Supports a tri-state via `indeterminate` and pairs the\n * input with a label/description column for forms.\n */\nexport const Checkbox = forwardRef<HTMLInputElement, CheckboxProps>(function Checkbox(\n { label, description, indeterminate, disabled, wrapperClassName, className, ...props },\n ref,\n) {\n const innerRef = useRef<HTMLInputElement | null>(null);\n\n useEffect(() => {\n if (innerRef.current) innerRef.current.indeterminate = Boolean(indeterminate);\n }, [indeterminate]);\n\n function setRef(node: HTMLInputElement | null): void {\n innerRef.current = node;\n if (typeof ref === \"function\") ref(node);\n else if (ref) (ref as React.MutableRefObject<HTMLInputElement | null>).current = node;\n }\n\n return (\n <label className={cn(styles.wrapper, disabled && styles.disabled, wrapperClassName)}>\n <input\n ref={setRef}\n type=\"checkbox\"\n disabled={disabled}\n className={cn(styles.input, className)}\n {...props}\n />\n <span className={styles.box} aria-hidden>\n {indeterminate ? <DashIcon /> : <CheckIcon />}\n </span>\n {(label || description) && (\n <span className={styles.labelWrap}>\n {label && <span className={styles.label}>{label}</span>}\n {description && <span className={styles.description}>{description}</span>}\n </span>\n )}\n </label>\n );\n});\n\nfunction CheckIcon() {\n return (\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\">\n <path\n d=\"M5 12l5 5L20 7\"\n stroke=\"currentColor\"\n strokeWidth=\"3\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n </svg>\n );\n}\n\nfunction DashIcon() {\n return (\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\">\n <path d=\"M5 12h14\" stroke=\"currentColor\" strokeWidth=\"3\" strokeLinecap=\"round\" />\n </svg>\n );\n}\n"],"mappings":"+HAiBA,IAAa,GAAA,EAAW,EAAA,WAAA,CAA4C,SAChE,CAAE,QAAO,cAAa,gBAAe,WAAU,mBAAkB,YAAW,GAAG,GAC/E,EACF,CACE,IAAM,GAAA,EAAW,EAAA,OAAA,CAAgC,IAAI,GAErD,EAAA,EAAA,UAAA,KAAgB,CACR,EAAS,UAAS,EAAS,QAAQ,cAAgB,EAAQ,EACnE,EAAG,CAAC,CAAa,CAAC,EAElB,SAAS,EAAO,EAAqC,CACjD,EAAS,QAAU,EACf,OAAO,GAAQ,WAAY,EAAI,CAAI,EAC9B,IAAK,EAAyD,QAAU,EACrF,CAEA,OACI,EAAA,EAAA,KAAA,CAAC,QAAD,CAAO,UAAW,EAAA,GAAG,EAAA,QAAO,QAAS,GAAY,EAAA,QAAO,SAAU,CAAgB,EAAlF,SAAA,EACI,EAAA,EAAA,IAAA,CAAC,QAAD,CACI,IAAK,EACL,KAAK,WACK,WACV,UAAW,EAAA,GAAG,EAAA,QAAO,MAAO,CAAS,EACrC,GAAI,CACP,CAAA,GACD,EAAA,EAAA,IAAA,CAAC,OAAD,CAAM,UAAW,EAAA,QAAO,IAAK,cAAA,GACxB,SAAA,GAAgB,EAAA,EAAA,IAAA,CAAC,EAAD,CAAW,CAAA,GAAI,EAAA,EAAA,IAAA,CAAC,EAAD,CAAY,CAAA,CAC1C,CAAA,GACJ,GAAS,KACP,EAAA,EAAA,KAAA,CAAC,OAAD,CAAM,UAAW,EAAA,QAAO,UAAxB,SAAA,CACK,IAAS,EAAA,EAAA,IAAA,CAAC,OAAD,CAAM,UAAW,EAAA,QAAO,MAAQ,SAAA,CAAY,CAAA,EACrD,IAAe,EAAA,EAAA,IAAA,CAAC,OAAD,CAAM,UAAW,EAAA,QAAO,YAAc,SAAA,CAAkB,CAAA,CACtE,GAEP,GAEf,CAAC,EAED,SAAS,GAAY,CACjB,OACI,EAAA,EAAA,IAAA,CAAC,MAAD,CAAK,MAAM,KAAK,OAAO,KAAK,QAAQ,YAAY,KAAK,OACjD,UAAA,EAAA,EAAA,IAAA,CAAC,OAAD,CACI,EAAE,iBACF,OAAO,eACP,YAAY,IACZ,cAAc,QACd,eAAe,OAClB,CAAA,CACA,CAAA,CAEb,CAEA,SAAS,GAAW,CAChB,OACI,EAAA,EAAA,IAAA,CAAC,MAAD,CAAK,MAAM,KAAK,OAAO,KAAK,QAAQ,YAAY,KAAK,OACjD,UAAA,EAAA,EAAA,IAAA,CAAC,OAAD,CAAM,EAAE,WAAW,OAAO,eAAe,YAAY,IAAI,cAAc,OAAS,CAAA,CAC/E,CAAA,CAEb"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Checkbox.js","names":[],"sources":["../../../src/components/Checkbox/Checkbox.tsx"],"sourcesContent":["import { forwardRef, useEffect, useRef } from \"react\";\nimport type { InputHTMLAttributes, ReactNode } from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport styles from \"./Checkbox.module.css\";\n\nexport interface CheckboxProps extends Omit<InputHTMLAttributes<HTMLInputElement>, \"type\"> {\n label?: ReactNode;\n description?: ReactNode;\n /** Render the box in the indeterminate state regardless of `checked`. */\n indeterminate?: boolean;\n wrapperClassName?: string;\n}\n\n/**\n * Accessible checkbox. Supports a tri-state via `indeterminate` and pairs the\n * input with a label/description column for forms.\n */\nexport const Checkbox = forwardRef<HTMLInputElement, CheckboxProps>(function Checkbox(\n { label, description, indeterminate, disabled, wrapperClassName, className, ...props },\n ref,\n) {\n const innerRef = useRef<HTMLInputElement | null>(null);\n\n useEffect(() => {\n if (innerRef.current) innerRef.current.indeterminate = Boolean(indeterminate);\n }, [indeterminate]);\n\n function setRef(node: HTMLInputElement | null): void {\n innerRef.current = node;\n if (typeof ref === \"function\") ref(node);\n else if (ref) (ref as React.MutableRefObject<HTMLInputElement | null>).current = node;\n }\n\n return (\n <label className={cn(styles.wrapper, disabled && styles.disabled, wrapperClassName)}>\n <input\n ref={setRef}\n type=\"checkbox\"\n disabled={disabled}\n className={cn(styles.input, className)}\n {...props}\n />\n <span className={styles.box} aria-hidden>\n {indeterminate ? <DashIcon /> : <CheckIcon />}\n </span>\n {(label || description) && (\n <span className={styles.labelWrap}>\n {label && <span className={styles.label}>{label}</span>}\n {description && <span className={styles.description}>{description}</span>}\n </span>\n )}\n </label>\n );\n});\n\nfunction CheckIcon() {\n return (\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\">\n <path\n d=\"M5 12l5 5L20 7\"\n stroke=\"currentColor\"\n strokeWidth=\"3\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n </svg>\n );\n}\n\nfunction DashIcon() {\n return (\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\">\n <path d=\"M5 12h14\" stroke=\"currentColor\" strokeWidth=\"3\" strokeLinecap=\"round\" />\n </svg>\n );\n}\n"],"mappings":";;;;;AAiBA,IAAa,IAAW,EAA4C,SAChE,EAAE,UAAO,gBAAa,kBAAe,aAAU,qBAAkB,cAAW,GAAG,KAC/E,GACF;CACE,IAAM,IAAW,EAAgC,IAAI;CAErD,QAAgB;EACZ,AAAI,EAAS,YAAS,EAAS,QAAQ,gBAAgB,EAAQ;CACnE,GAAG,CAAC,CAAa,CAAC;CAElB,SAAS,EAAO,GAAqC;EAEjD,AADA,EAAS,UAAU,GACf,OAAO,KAAQ,aAAY,EAAI,CAAI,IAC9B,MAAK,EAAyD,UAAU;CACrF;CAEA,OACI,kBAAC,SAAD;EAAO,WAAW,EAAG,EAAO,SAAS,KAAY,EAAO,UAAU,CAAgB;
|
|
1
|
+
{"version":3,"file":"Checkbox.js","names":[],"sources":["../../../src/components/Checkbox/Checkbox.tsx"],"sourcesContent":["import { forwardRef, useEffect, useRef } from \"react\";\nimport type { InputHTMLAttributes, ReactNode } from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport styles from \"./Checkbox.module.css\";\n\nexport interface CheckboxProps extends Omit<InputHTMLAttributes<HTMLInputElement>, \"type\"> {\n label?: ReactNode;\n description?: ReactNode;\n /** Render the box in the indeterminate state regardless of `checked`. */\n indeterminate?: boolean;\n wrapperClassName?: string;\n}\n\n/**\n * Accessible checkbox. Supports a tri-state via `indeterminate` and pairs the\n * input with a label/description column for forms.\n */\nexport const Checkbox = forwardRef<HTMLInputElement, CheckboxProps>(function Checkbox(\n { label, description, indeterminate, disabled, wrapperClassName, className, ...props },\n ref,\n) {\n const innerRef = useRef<HTMLInputElement | null>(null);\n\n useEffect(() => {\n if (innerRef.current) innerRef.current.indeterminate = Boolean(indeterminate);\n }, [indeterminate]);\n\n function setRef(node: HTMLInputElement | null): void {\n innerRef.current = node;\n if (typeof ref === \"function\") ref(node);\n else if (ref) (ref as React.MutableRefObject<HTMLInputElement | null>).current = node;\n }\n\n return (\n <label className={cn(styles.wrapper, disabled && styles.disabled, wrapperClassName)}>\n <input\n ref={setRef}\n type=\"checkbox\"\n disabled={disabled}\n className={cn(styles.input, className)}\n {...props}\n />\n <span className={styles.box} aria-hidden>\n {indeterminate ? <DashIcon /> : <CheckIcon />}\n </span>\n {(label || description) && (\n <span className={styles.labelWrap}>\n {label && <span className={styles.label}>{label}</span>}\n {description && <span className={styles.description}>{description}</span>}\n </span>\n )}\n </label>\n );\n});\n\nfunction CheckIcon() {\n return (\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\">\n <path\n d=\"M5 12l5 5L20 7\"\n stroke=\"currentColor\"\n strokeWidth=\"3\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n </svg>\n );\n}\n\nfunction DashIcon() {\n return (\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\">\n <path d=\"M5 12h14\" stroke=\"currentColor\" strokeWidth=\"3\" strokeLinecap=\"round\" />\n </svg>\n );\n}\n"],"mappings":";;;;;AAiBA,IAAa,IAAW,EAA4C,SAChE,EAAE,UAAO,gBAAa,kBAAe,aAAU,qBAAkB,cAAW,GAAG,KAC/E,GACF;CACE,IAAM,IAAW,EAAgC,IAAI;CAErD,QAAgB;EACZ,AAAI,EAAS,YAAS,EAAS,QAAQ,gBAAgB,EAAQ;CACnE,GAAG,CAAC,CAAa,CAAC;CAElB,SAAS,EAAO,GAAqC;EAEjD,AADA,EAAS,UAAU,GACf,OAAO,KAAQ,aAAY,EAAI,CAAI,IAC9B,MAAK,EAAyD,UAAU;CACrF;CAEA,OACI,kBAAC,SAAD;EAAO,WAAW,EAAG,EAAO,SAAS,KAAY,EAAO,UAAU,CAAgB;EAAlF,UAAA;GACI,kBAAC,SAAD;IACI,KAAK;IACL,MAAK;IACK;IACV,WAAW,EAAG,EAAO,OAAO,CAAS;IACrC,GAAI;GACP,CAAA;GACD,kBAAC,QAAD;IAAM,WAAW,EAAO;IAAK,eAAA;IACxB,UAAgB,EAAhB,IAAiB,IAAe,GAAhB,CAAW,CAAgB;GAC1C,CAAA;IACJ,KAAS,MACP,kBAAC,QAAD;IAAM,WAAW,EAAO;IAAxB,UAAA,CACK,KAAS,kBAAC,QAAD;KAAM,WAAW,EAAO;KAAQ,UAAA;IAAY,CAAA,GACrD,KAAe,kBAAC,QAAD;KAAM,WAAW,EAAO;KAAc,UAAA;IAAkB,CAAA,CACtE;;EAEP;;AAEf,CAAC;AAED,SAAS,IAAY;CACjB,OACI,kBAAC,OAAD;EAAK,OAAM;EAAK,QAAO;EAAK,SAAQ;EAAY,MAAK;EACjD,UAAA,kBAAC,QAAD;GACI,GAAE;GACF,QAAO;GACP,aAAY;GACZ,eAAc;GACd,gBAAe;EAClB,CAAA;CACA,CAAA;AAEb;AAEA,SAAS,IAAW;CAChB,OACI,kBAAC,OAAD;EAAK,OAAM;EAAK,QAAO;EAAK,SAAQ;EAAY,MAAK;EACjD,UAAA,kBAAC,QAAD;GAAM,GAAE;GAAW,QAAO;GAAe,aAAY;GAAI,eAAc;EAAS,CAAA;CAC/E,CAAA;AAEb"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ChipInput.cjs","names":[],"sources":["../../../src/components/ChipInput/ChipInput.tsx"],"sourcesContent":["/**\n * @tempest-limits props-count — the eight or so props that repeat across every input\n * in the SDK are one contract, not a bag: label, helperText and error are the three\n * states of the same message slot, and dropping any of them pushes the app back to\n * building the label/help/error scaffolding by hand around a bare control. Beyond\n * those, ChipInput adds only commitKeys and normalize, which are what make it a chip\n * input rather than a text field.\n */\nimport { useId, useRef, useState } from \"react\";\nimport type { KeyboardEvent } from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport styles from \"./ChipInput.module.css\";\n\nexport interface ChipInputProps {\n value: string[];\n onChange: (next: string[]) => void;\n label?: string;\n placeholder?: string;\n helperText?: string;\n error?: string;\n /** Id for the inner input. Generated when omitted. */\n id?: string;\n /**\n * Accessible name for the inner input when there is no visible `label`.\n * Without either, screen readers announce an unnamed text field.\n */\n \"aria-label\"?: string;\n /** Keys that commit the current draft as a chip. Default: Enter, comma, Tab. */\n commitKeys?: string[];\n /** Lowercase + trim each chip + dedupe. Default: true. */\n normalize?: boolean;\n className?: string;\n}\n\n/**\n * Multi-value input. Type a value and press Enter (or comma / Tab) to push a\n * chip. Backspace on empty input removes the last chip.\n *\n * Name the field with `label` (associated through `htmlFor`) or, when the label\n * lives elsewhere in the layout, with `aria-label`. The placeholder is not an\n * accessible name — it disappears as soon as the first chip is added.\n */\nexport function ChipInput({\n value,\n onChange,\n label,\n placeholder = \"Adicionar e pressionar Enter…\",\n helperText,\n error,\n commitKeys = [\"Enter\", \",\", \"Tab\"],\n normalize = true,\n className,\n id,\n \"aria-label\": ariaLabel,\n}: ChipInputProps) {\n const [draft, setDraft] = useState<string>(\"\");\n const inputRef = useRef<HTMLInputElement>(null);\n const generatedId = useId();\n const inputId = id ?? generatedId;\n const describedById = error ? `${inputId}-error` : helperText ? `${inputId}-helper` : undefined;\n\n function commit(): void {\n const next = normalize ? draft.trim().toLowerCase() : draft.trim();\n if (!next) return;\n if (value.includes(next)) {\n setDraft(\"\");\n return;\n }\n onChange([...value, next]);\n setDraft(\"\");\n }\n\n function handleKeyDown(event: KeyboardEvent<HTMLInputElement>): void {\n if (commitKeys.includes(event.key)) {\n event.preventDefault();\n commit();\n return;\n }\n if (event.key === \"Backspace\" && !draft && value.length > 0) {\n onChange(value.slice(0, -1));\n }\n }\n\n function remove(index: number): void {\n const next = value.slice();\n next.splice(index, 1);\n onChange(next);\n }\n\n return (\n <div className={cn(styles.wrapper, error && styles.error, className)}>\n {label && (\n <label htmlFor={inputId} className={styles.label}>\n {label}\n </label>\n )}\n <div className={styles.field} onClick={() => inputRef.current?.focus()}>\n {value.map((chip, index) => (\n <span key={`${chip}-${index}`} className={styles.chip}>\n {chip}\n <button\n type=\"button\"\n className={styles.remove}\n aria-label={`Remover ${chip}`}\n onClick={() => remove(index)}\n >\n ×\n </button>\n </span>\n ))}\n <input\n ref={inputRef}\n id={inputId}\n className={styles.input}\n value={draft}\n placeholder={value.length === 0 ? placeholder : \"\"}\n aria-label={label ? undefined : ariaLabel}\n aria-invalid={!!error}\n aria-describedby={describedById}\n onChange={(event) => setDraft(event.target.value)}\n onKeyDown={handleKeyDown}\n onBlur={commit}\n />\n </div>\n {error ? (\n <span id={`${inputId}-error`} className={styles.errorText}>\n {error}\n </span>\n ) : helperText ? (\n <span id={`${inputId}-helper`} className={styles.helper}>\n {helperText}\n </span>\n ) : null}\n </div>\n );\n}\n"],"mappings":"gIA0CA,SAAgB,EAAU,CACtB,QACA,WACA,QACA,cAAc,gCACd,aACA,QACA,aAAa,CAAC,QAAS,IAAK,KAAK,EACjC,YAAY,GACZ,YACA,KACA,aAAc,GACC,CACf,GAAM,CAAC,EAAO,IAAA,
|
|
1
|
+
{"version":3,"file":"ChipInput.cjs","names":[],"sources":["../../../src/components/ChipInput/ChipInput.tsx"],"sourcesContent":["/**\n * @tempest-limits props-count — the eight or so props that repeat across every input\n * in the SDK are one contract, not a bag: label, helperText and error are the three\n * states of the same message slot, and dropping any of them pushes the app back to\n * building the label/help/error scaffolding by hand around a bare control. Beyond\n * those, ChipInput adds only commitKeys and normalize, which are what make it a chip\n * input rather than a text field.\n */\nimport { useId, useRef, useState } from \"react\";\nimport type { KeyboardEvent } from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport styles from \"./ChipInput.module.css\";\n\nexport interface ChipInputProps {\n value: string[];\n onChange: (next: string[]) => void;\n label?: string;\n placeholder?: string;\n helperText?: string;\n error?: string;\n /** Id for the inner input. Generated when omitted. */\n id?: string;\n /**\n * Accessible name for the inner input when there is no visible `label`.\n * Without either, screen readers announce an unnamed text field.\n */\n \"aria-label\"?: string;\n /** Keys that commit the current draft as a chip. Default: Enter, comma, Tab. */\n commitKeys?: string[];\n /** Lowercase + trim each chip + dedupe. Default: true. */\n normalize?: boolean;\n className?: string;\n}\n\n/**\n * Multi-value input. Type a value and press Enter (or comma / Tab) to push a\n * chip. Backspace on empty input removes the last chip.\n *\n * Name the field with `label` (associated through `htmlFor`) or, when the label\n * lives elsewhere in the layout, with `aria-label`. The placeholder is not an\n * accessible name — it disappears as soon as the first chip is added.\n */\nexport function ChipInput({\n value,\n onChange,\n label,\n placeholder = \"Adicionar e pressionar Enter…\",\n helperText,\n error,\n commitKeys = [\"Enter\", \",\", \"Tab\"],\n normalize = true,\n className,\n id,\n \"aria-label\": ariaLabel,\n}: ChipInputProps) {\n const [draft, setDraft] = useState<string>(\"\");\n const inputRef = useRef<HTMLInputElement>(null);\n const generatedId = useId();\n const inputId = id ?? generatedId;\n const describedById = error ? `${inputId}-error` : helperText ? `${inputId}-helper` : undefined;\n\n function commit(): void {\n const next = normalize ? draft.trim().toLowerCase() : draft.trim();\n if (!next) return;\n if (value.includes(next)) {\n setDraft(\"\");\n return;\n }\n onChange([...value, next]);\n setDraft(\"\");\n }\n\n function handleKeyDown(event: KeyboardEvent<HTMLInputElement>): void {\n if (commitKeys.includes(event.key)) {\n event.preventDefault();\n commit();\n return;\n }\n if (event.key === \"Backspace\" && !draft && value.length > 0) {\n onChange(value.slice(0, -1));\n }\n }\n\n function remove(index: number): void {\n const next = value.slice();\n next.splice(index, 1);\n onChange(next);\n }\n\n return (\n <div className={cn(styles.wrapper, error && styles.error, className)}>\n {label && (\n <label htmlFor={inputId} className={styles.label}>\n {label}\n </label>\n )}\n <div className={styles.field} onClick={() => inputRef.current?.focus()}>\n {value.map((chip, index) => (\n <span key={`${chip}-${index}`} className={styles.chip}>\n {chip}\n <button\n type=\"button\"\n className={styles.remove}\n aria-label={`Remover ${chip}`}\n onClick={() => remove(index)}\n >\n ×\n </button>\n </span>\n ))}\n <input\n ref={inputRef}\n id={inputId}\n className={styles.input}\n value={draft}\n placeholder={value.length === 0 ? placeholder : \"\"}\n aria-label={label ? undefined : ariaLabel}\n aria-invalid={!!error}\n aria-describedby={describedById}\n onChange={(event) => setDraft(event.target.value)}\n onKeyDown={handleKeyDown}\n onBlur={commit}\n />\n </div>\n {error ? (\n <span id={`${inputId}-error`} className={styles.errorText}>\n {error}\n </span>\n ) : helperText ? (\n <span id={`${inputId}-helper`} className={styles.helper}>\n {helperText}\n </span>\n ) : null}\n </div>\n );\n}\n"],"mappings":"gIA0CA,SAAgB,EAAU,CACtB,QACA,WACA,QACA,cAAc,gCACd,aACA,QACA,aAAa,CAAC,QAAS,IAAK,KAAK,EACjC,YAAY,GACZ,YACA,KACA,aAAc,GACC,CACf,GAAM,CAAC,EAAO,IAAA,EAAY,EAAA,SAAA,CAAiB,EAAE,EACvC,GAAA,EAAW,EAAA,OAAA,CAAyB,IAAI,EACxC,GAAA,EAAc,EAAA,MAAA,CAAM,EACpB,EAAU,GAAM,EAChB,EAAgB,EAAQ,GAAG,EAAQ,QAAU,EAAa,GAAG,EAAQ,SAAW,IAAA,GAEtF,SAAS,GAAe,CACpB,IAAM,EAAO,EAAY,EAAM,KAAK,CAAC,CAAC,YAAY,EAAI,EAAM,KAAK,EAC5D,KACL,IAAI,EAAM,SAAS,CAAI,EAAG,CACtB,EAAS,EAAE,EACX,MACJ,CACA,EAAS,CAAC,GAAG,EAAO,CAAI,CAAC,EACzB,EAAS,EAAE,CAFX,CAGJ,CAEA,SAAS,EAAc,EAA8C,CACjE,GAAI,EAAW,SAAS,EAAM,GAAG,EAAG,CAChC,EAAM,eAAe,EACrB,EAAO,EACP,MACJ,CACI,EAAM,MAAQ,aAAe,CAAC,GAAS,EAAM,OAAS,GACtD,EAAS,EAAM,MAAM,EAAG,EAAE,CAAC,CAEnC,CAEA,SAAS,EAAO,EAAqB,CACjC,IAAM,EAAO,EAAM,MAAM,EACzB,EAAK,OAAO,EAAO,CAAC,EACpB,EAAS,CAAI,CACjB,CAEA,OACI,EAAA,EAAA,KAAA,CAAC,MAAD,CAAK,UAAW,EAAA,GAAG,EAAA,QAAO,QAAS,GAAS,EAAA,QAAO,MAAO,CAAS,EAAnE,SAAA,CACK,IACG,EAAA,EAAA,IAAA,CAAC,QAAD,CAAO,QAAS,EAAS,UAAW,EAAA,QAAO,MACtC,SAAA,CACE,CAAA,GAEX,EAAA,EAAA,KAAA,CAAC,MAAD,CAAK,UAAW,EAAA,QAAO,MAAO,YAAe,EAAS,SAAS,MAAM,EAArE,SAAA,CACK,EAAM,KAAK,EAAM,KACd,EAAA,EAAA,KAAA,CAAC,OAAD,CAA+B,UAAW,EAAA,QAAO,KAAjD,SAAA,CACK,GACD,EAAA,EAAA,IAAA,CAAC,SAAD,CACI,KAAK,SACL,UAAW,EAAA,QAAO,OAClB,aAAY,WAAW,IACvB,YAAe,EAAO,CAAK,EAC9B,SAAA,GAEO,CAAA,CACN,CAVK,EAAA,GAAG,EAAK,GAAG,GAUhB,CACT,GACD,EAAA,EAAA,IAAA,CAAC,QAAD,CACI,IAAK,EACL,GAAI,EACJ,UAAW,EAAA,QAAO,MAClB,MAAO,EACP,YAAa,EAAM,SAAW,EAAI,EAAc,GAChD,aAAY,EAAQ,IAAA,GAAY,EAChC,eAAc,CAAC,CAAC,EAChB,mBAAkB,EAClB,SAAW,GAAU,EAAS,EAAM,OAAO,KAAK,EAChD,UAAW,EACX,OAAQ,CACX,CAAA,CACA,IACJ,GACG,EAAA,EAAA,IAAA,CAAC,OAAD,CAAM,GAAI,GAAG,EAAQ,QAAS,UAAW,EAAA,QAAO,UAC3C,SAAA,CACC,CAAA,EACN,GACA,EAAA,EAAA,IAAA,CAAC,OAAD,CAAM,GAAI,GAAG,EAAQ,SAAU,UAAW,EAAA,QAAO,OAC5C,SAAA,CACC,CAAA,EACN,IACH,GAEb"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ChipInput.js","names":[],"sources":["../../../src/components/ChipInput/ChipInput.tsx"],"sourcesContent":["/**\n * @tempest-limits props-count — the eight or so props that repeat across every input\n * in the SDK are one contract, not a bag: label, helperText and error are the three\n * states of the same message slot, and dropping any of them pushes the app back to\n * building the label/help/error scaffolding by hand around a bare control. Beyond\n * those, ChipInput adds only commitKeys and normalize, which are what make it a chip\n * input rather than a text field.\n */\nimport { useId, useRef, useState } from \"react\";\nimport type { KeyboardEvent } from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport styles from \"./ChipInput.module.css\";\n\nexport interface ChipInputProps {\n value: string[];\n onChange: (next: string[]) => void;\n label?: string;\n placeholder?: string;\n helperText?: string;\n error?: string;\n /** Id for the inner input. Generated when omitted. */\n id?: string;\n /**\n * Accessible name for the inner input when there is no visible `label`.\n * Without either, screen readers announce an unnamed text field.\n */\n \"aria-label\"?: string;\n /** Keys that commit the current draft as a chip. Default: Enter, comma, Tab. */\n commitKeys?: string[];\n /** Lowercase + trim each chip + dedupe. Default: true. */\n normalize?: boolean;\n className?: string;\n}\n\n/**\n * Multi-value input. Type a value and press Enter (or comma / Tab) to push a\n * chip. Backspace on empty input removes the last chip.\n *\n * Name the field with `label` (associated through `htmlFor`) or, when the label\n * lives elsewhere in the layout, with `aria-label`. The placeholder is not an\n * accessible name — it disappears as soon as the first chip is added.\n */\nexport function ChipInput({\n value,\n onChange,\n label,\n placeholder = \"Adicionar e pressionar Enter…\",\n helperText,\n error,\n commitKeys = [\"Enter\", \",\", \"Tab\"],\n normalize = true,\n className,\n id,\n \"aria-label\": ariaLabel,\n}: ChipInputProps) {\n const [draft, setDraft] = useState<string>(\"\");\n const inputRef = useRef<HTMLInputElement>(null);\n const generatedId = useId();\n const inputId = id ?? generatedId;\n const describedById = error ? `${inputId}-error` : helperText ? `${inputId}-helper` : undefined;\n\n function commit(): void {\n const next = normalize ? draft.trim().toLowerCase() : draft.trim();\n if (!next) return;\n if (value.includes(next)) {\n setDraft(\"\");\n return;\n }\n onChange([...value, next]);\n setDraft(\"\");\n }\n\n function handleKeyDown(event: KeyboardEvent<HTMLInputElement>): void {\n if (commitKeys.includes(event.key)) {\n event.preventDefault();\n commit();\n return;\n }\n if (event.key === \"Backspace\" && !draft && value.length > 0) {\n onChange(value.slice(0, -1));\n }\n }\n\n function remove(index: number): void {\n const next = value.slice();\n next.splice(index, 1);\n onChange(next);\n }\n\n return (\n <div className={cn(styles.wrapper, error && styles.error, className)}>\n {label && (\n <label htmlFor={inputId} className={styles.label}>\n {label}\n </label>\n )}\n <div className={styles.field} onClick={() => inputRef.current?.focus()}>\n {value.map((chip, index) => (\n <span key={`${chip}-${index}`} className={styles.chip}>\n {chip}\n <button\n type=\"button\"\n className={styles.remove}\n aria-label={`Remover ${chip}`}\n onClick={() => remove(index)}\n >\n ×\n </button>\n </span>\n ))}\n <input\n ref={inputRef}\n id={inputId}\n className={styles.input}\n value={draft}\n placeholder={value.length === 0 ? placeholder : \"\"}\n aria-label={label ? undefined : ariaLabel}\n aria-invalid={!!error}\n aria-describedby={describedById}\n onChange={(event) => setDraft(event.target.value)}\n onKeyDown={handleKeyDown}\n onBlur={commit}\n />\n </div>\n {error ? (\n <span id={`${inputId}-error`} className={styles.errorText}>\n {error}\n </span>\n ) : helperText ? (\n <span id={`${inputId}-helper`} className={styles.helper}>\n {helperText}\n </span>\n ) : null}\n </div>\n );\n}\n"],"mappings":";;;;;AA0CA,SAAgB,EAAU,EACtB,UACA,aACA,UACA,iBAAc,iCACd,eACA,UACA,gBAAa;CAAC;CAAS;CAAK;AAAK,GACjC,eAAY,IACZ,cACA,OACA,cAAc,KACC;CACf,IAAM,CAAC,GAAO,KAAY,EAAiB,EAAE,GACvC,IAAW,EAAyB,IAAI,GACxC,IAAc,EAAM,GACpB,IAAU,KAAM,GAChB,IAAgB,IAAQ,GAAG,EAAQ,UAAU,IAAa,GAAG,EAAQ,WAAW,KAAA;CAEtF,SAAS,IAAe;EACpB,IAAM,IAAO,IAAY,EAAM,KAAK,CAAC,CAAC,YAAY,IAAI,EAAM,KAAK;EAC5D,OACL;OAAI,EAAM,SAAS,CAAI,GAAG;IACtB,EAAS,EAAE;IACX;GACJ;GAEA,AADA,EAAS,CAAC,GAAG,GAAO,CAAI,CAAC,GACzB,EAAS,EAAE;EAFX;CAGJ;CAEA,SAAS,EAAc,GAA8C;EACjE,IAAI,EAAW,SAAS,EAAM,GAAG,GAAG;GAEhC,AADA,EAAM,eAAe,GACrB,EAAO;GACP;EACJ;EACA,AAAI,EAAM,QAAQ,eAAe,CAAC,KAAS,EAAM,SAAS,KACtD,EAAS,EAAM,MAAM,GAAG,EAAE,CAAC;CAEnC;CAEA,SAAS,EAAO,GAAqB;EACjC,IAAM,IAAO,EAAM,MAAM;EAEzB,AADA,EAAK,OAAO,GAAO,CAAC,GACpB,EAAS,CAAI;CACjB;CAEA,OACI,kBAAC,OAAD;EAAK,WAAW,EAAG,EAAO,SAAS,KAAS,EAAO,OAAO,CAAS;
|
|
1
|
+
{"version":3,"file":"ChipInput.js","names":[],"sources":["../../../src/components/ChipInput/ChipInput.tsx"],"sourcesContent":["/**\n * @tempest-limits props-count — the eight or so props that repeat across every input\n * in the SDK are one contract, not a bag: label, helperText and error are the three\n * states of the same message slot, and dropping any of them pushes the app back to\n * building the label/help/error scaffolding by hand around a bare control. Beyond\n * those, ChipInput adds only commitKeys and normalize, which are what make it a chip\n * input rather than a text field.\n */\nimport { useId, useRef, useState } from \"react\";\nimport type { KeyboardEvent } from \"react\";\nimport { cn } from \"@/utils/cn\";\nimport styles from \"./ChipInput.module.css\";\n\nexport interface ChipInputProps {\n value: string[];\n onChange: (next: string[]) => void;\n label?: string;\n placeholder?: string;\n helperText?: string;\n error?: string;\n /** Id for the inner input. Generated when omitted. */\n id?: string;\n /**\n * Accessible name for the inner input when there is no visible `label`.\n * Without either, screen readers announce an unnamed text field.\n */\n \"aria-label\"?: string;\n /** Keys that commit the current draft as a chip. Default: Enter, comma, Tab. */\n commitKeys?: string[];\n /** Lowercase + trim each chip + dedupe. Default: true. */\n normalize?: boolean;\n className?: string;\n}\n\n/**\n * Multi-value input. Type a value and press Enter (or comma / Tab) to push a\n * chip. Backspace on empty input removes the last chip.\n *\n * Name the field with `label` (associated through `htmlFor`) or, when the label\n * lives elsewhere in the layout, with `aria-label`. The placeholder is not an\n * accessible name — it disappears as soon as the first chip is added.\n */\nexport function ChipInput({\n value,\n onChange,\n label,\n placeholder = \"Adicionar e pressionar Enter…\",\n helperText,\n error,\n commitKeys = [\"Enter\", \",\", \"Tab\"],\n normalize = true,\n className,\n id,\n \"aria-label\": ariaLabel,\n}: ChipInputProps) {\n const [draft, setDraft] = useState<string>(\"\");\n const inputRef = useRef<HTMLInputElement>(null);\n const generatedId = useId();\n const inputId = id ?? generatedId;\n const describedById = error ? `${inputId}-error` : helperText ? `${inputId}-helper` : undefined;\n\n function commit(): void {\n const next = normalize ? draft.trim().toLowerCase() : draft.trim();\n if (!next) return;\n if (value.includes(next)) {\n setDraft(\"\");\n return;\n }\n onChange([...value, next]);\n setDraft(\"\");\n }\n\n function handleKeyDown(event: KeyboardEvent<HTMLInputElement>): void {\n if (commitKeys.includes(event.key)) {\n event.preventDefault();\n commit();\n return;\n }\n if (event.key === \"Backspace\" && !draft && value.length > 0) {\n onChange(value.slice(0, -1));\n }\n }\n\n function remove(index: number): void {\n const next = value.slice();\n next.splice(index, 1);\n onChange(next);\n }\n\n return (\n <div className={cn(styles.wrapper, error && styles.error, className)}>\n {label && (\n <label htmlFor={inputId} className={styles.label}>\n {label}\n </label>\n )}\n <div className={styles.field} onClick={() => inputRef.current?.focus()}>\n {value.map((chip, index) => (\n <span key={`${chip}-${index}`} className={styles.chip}>\n {chip}\n <button\n type=\"button\"\n className={styles.remove}\n aria-label={`Remover ${chip}`}\n onClick={() => remove(index)}\n >\n ×\n </button>\n </span>\n ))}\n <input\n ref={inputRef}\n id={inputId}\n className={styles.input}\n value={draft}\n placeholder={value.length === 0 ? placeholder : \"\"}\n aria-label={label ? undefined : ariaLabel}\n aria-invalid={!!error}\n aria-describedby={describedById}\n onChange={(event) => setDraft(event.target.value)}\n onKeyDown={handleKeyDown}\n onBlur={commit}\n />\n </div>\n {error ? (\n <span id={`${inputId}-error`} className={styles.errorText}>\n {error}\n </span>\n ) : helperText ? (\n <span id={`${inputId}-helper`} className={styles.helper}>\n {helperText}\n </span>\n ) : null}\n </div>\n );\n}\n"],"mappings":";;;;;AA0CA,SAAgB,EAAU,EACtB,UACA,aACA,UACA,iBAAc,iCACd,eACA,UACA,gBAAa;CAAC;CAAS;CAAK;AAAK,GACjC,eAAY,IACZ,cACA,OACA,cAAc,KACC;CACf,IAAM,CAAC,GAAO,KAAY,EAAiB,EAAE,GACvC,IAAW,EAAyB,IAAI,GACxC,IAAc,EAAM,GACpB,IAAU,KAAM,GAChB,IAAgB,IAAQ,GAAG,EAAQ,UAAU,IAAa,GAAG,EAAQ,WAAW,KAAA;CAEtF,SAAS,IAAe;EACpB,IAAM,IAAO,IAAY,EAAM,KAAK,CAAC,CAAC,YAAY,IAAI,EAAM,KAAK;EAC5D,OACL;OAAI,EAAM,SAAS,CAAI,GAAG;IACtB,EAAS,EAAE;IACX;GACJ;GAEA,AADA,EAAS,CAAC,GAAG,GAAO,CAAI,CAAC,GACzB,EAAS,EAAE;EAFX;CAGJ;CAEA,SAAS,EAAc,GAA8C;EACjE,IAAI,EAAW,SAAS,EAAM,GAAG,GAAG;GAEhC,AADA,EAAM,eAAe,GACrB,EAAO;GACP;EACJ;EACA,AAAI,EAAM,QAAQ,eAAe,CAAC,KAAS,EAAM,SAAS,KACtD,EAAS,EAAM,MAAM,GAAG,EAAE,CAAC;CAEnC;CAEA,SAAS,EAAO,GAAqB;EACjC,IAAM,IAAO,EAAM,MAAM;EAEzB,AADA,EAAK,OAAO,GAAO,CAAC,GACpB,EAAS,CAAI;CACjB;CAEA,OACI,kBAAC,OAAD;EAAK,WAAW,EAAG,EAAO,SAAS,KAAS,EAAO,OAAO,CAAS;EAAnE,UAAA;GACK,KACG,kBAAC,SAAD;IAAO,SAAS;IAAS,WAAW,EAAO;IACtC,UAAA;GACE,CAAA;GAEX,kBAAC,OAAD;IAAK,WAAW,EAAO;IAAO,eAAe,EAAS,SAAS,MAAM;IAArE,UAAA,CACK,EAAM,KAAK,GAAM,MACd,kBAAC,QAAD;KAA+B,WAAW,EAAO;KAAjD,UAAA,CACK,GACD,kBAAC,UAAD;MACI,MAAK;MACL,WAAW,EAAO;MAClB,cAAY,WAAW;MACvB,eAAe,EAAO,CAAK;MAC9B,UAAA;KAEO,CAAA,CACN;IAVK,GAAA,GAAG,EAAK,GAAG,GAUhB,CACT,GACD,kBAAC,SAAD;KACI,KAAK;KACL,IAAI;KACJ,WAAW,EAAO;KAClB,OAAO;KACP,aAAa,EAAM,WAAW,IAAI,IAAc;KAChD,cAAY,IAAQ,KAAA,IAAY;KAChC,gBAAc,CAAC,CAAC;KAChB,oBAAkB;KAClB,WAAW,MAAU,EAAS,EAAM,OAAO,KAAK;KAChD,WAAW;KACX,QAAQ;IACX,CAAA,CACA;;GACJ,IACG,kBAAC,QAAD;IAAM,IAAI,GAAG,EAAQ;IAAS,WAAW,EAAO;IAC3C,UAAA;GACC,CAAA,IACN,IACA,kBAAC,QAAD;IAAM,IAAI,GAAG,EAAQ;IAAU,WAAW,EAAO;IAC5C,UAAA;GACC,CAAA,IACN;EACH;;AAEb"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ClickOutside.cjs","names":[],"sources":["../../../src/components/ClickOutside/ClickOutside.tsx"],"sourcesContent":["import { useEffect, useRef, type HTMLAttributes, type ReactNode } from \"react\";\n\nexport interface ClickOutsideProps extends HTMLAttributes<HTMLDivElement> {\n /** Called when a pointer event lands outside the wrapped subtree. */\n onOutside: (event: MouseEvent | TouchEvent) => void;\n /** Content wrapped by the outside-click boundary. */\n children: ReactNode;\n}\n\n/**\n * Wraps its children in a `<div>` and invokes `onOutside` whenever a\n * `mousedown` or `touchstart` event occurs outside that subtree.\n *\n * Useful for dismissing popovers, dropdowns and menus on outside interaction.\n * Listeners are attached to `document` on mount and removed on unmount.\n *\n * @param props - The click-outside props, plus any `<div>` attributes.\n * @returns The wrapper element containing the children.\n */\nexport function ClickOutside({ onOutside, children, ...props }: ClickOutsideProps): ReactNode {\n const ref = useRef<HTMLDivElement>(null);\n const handlerRef = useRef(onOutside);\n useEffect(() => {\n handlerRef.current = onOutside;\n }, [onOutside]);\n\n useEffect(() => {\n if (typeof document === \"undefined\") {\n return;\n }\n\n function handle(event: MouseEvent | TouchEvent): void {\n const node = ref.current;\n const target = event.target as Node | null;\n if (node && target && !node.contains(target)) {\n handlerRef.current(event);\n }\n }\n\n document.addEventListener(\"mousedown\", handle);\n document.addEventListener(\"touchstart\", handle);\n return () => {\n document.removeEventListener(\"mousedown\", handle);\n document.removeEventListener(\"touchstart\", handle);\n };\n }, []);\n\n return (\n <div ref={ref} {...props}>\n {children}\n </div>\n );\n}\n"],"mappings":"sDAmBA,SAAgB,EAAa,CAAE,YAAW,WAAU,GAAG,GAAuC,CAC1F,IAAM,GAAA,
|
|
1
|
+
{"version":3,"file":"ClickOutside.cjs","names":[],"sources":["../../../src/components/ClickOutside/ClickOutside.tsx"],"sourcesContent":["import { useEffect, useRef, type HTMLAttributes, type ReactNode } from \"react\";\n\nexport interface ClickOutsideProps extends HTMLAttributes<HTMLDivElement> {\n /** Called when a pointer event lands outside the wrapped subtree. */\n onOutside: (event: MouseEvent | TouchEvent) => void;\n /** Content wrapped by the outside-click boundary. */\n children: ReactNode;\n}\n\n/**\n * Wraps its children in a `<div>` and invokes `onOutside` whenever a\n * `mousedown` or `touchstart` event occurs outside that subtree.\n *\n * Useful for dismissing popovers, dropdowns and menus on outside interaction.\n * Listeners are attached to `document` on mount and removed on unmount.\n *\n * @param props - The click-outside props, plus any `<div>` attributes.\n * @returns The wrapper element containing the children.\n */\nexport function ClickOutside({ onOutside, children, ...props }: ClickOutsideProps): ReactNode {\n const ref = useRef<HTMLDivElement>(null);\n const handlerRef = useRef(onOutside);\n useEffect(() => {\n handlerRef.current = onOutside;\n }, [onOutside]);\n\n useEffect(() => {\n if (typeof document === \"undefined\") {\n return;\n }\n\n function handle(event: MouseEvent | TouchEvent): void {\n const node = ref.current;\n const target = event.target as Node | null;\n if (node && target && !node.contains(target)) {\n handlerRef.current(event);\n }\n }\n\n document.addEventListener(\"mousedown\", handle);\n document.addEventListener(\"touchstart\", handle);\n return () => {\n document.removeEventListener(\"mousedown\", handle);\n document.removeEventListener(\"touchstart\", handle);\n };\n }, []);\n\n return (\n <div ref={ref} {...props}>\n {children}\n </div>\n );\n}\n"],"mappings":"sDAmBA,SAAgB,EAAa,CAAE,YAAW,WAAU,GAAG,GAAuC,CAC1F,IAAM,GAAA,EAAM,EAAA,OAAA,CAAuB,IAAI,EACjC,GAAA,EAAa,EAAA,OAAA,CAAO,CAAS,EA0BnC,OAzBA,EAAA,EAAA,UAAA,KAAgB,CACZ,EAAW,QAAU,CACzB,EAAG,CAAC,CAAS,CAAC,GAEd,EAAA,EAAA,UAAA,KAAgB,CACZ,GAAI,OAAO,SAAa,IACpB,OAGJ,SAAS,EAAO,EAAsC,CAClD,IAAM,EAAO,EAAI,QACX,EAAS,EAAM,OACjB,GAAQ,GAAU,CAAC,EAAK,SAAS,CAAM,GACvC,EAAW,QAAQ,CAAK,CAEhC,CAIA,OAFA,SAAS,iBAAiB,YAAa,CAAM,EAC7C,SAAS,iBAAiB,aAAc,CAAM,MACjC,CACT,SAAS,oBAAoB,YAAa,CAAM,EAChD,SAAS,oBAAoB,aAAc,CAAM,CACrD,CACJ,EAAG,CAAC,CAAC,GAGD,EAAA,EAAA,IAAA,CAAC,MAAD,CAAU,MAAK,GAAI,EACd,UACA,CAAA,CAEb"}
|