@12-apps/ui 6.6.0 → 6.8.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/{chunk-QLBYAYUG.js → chunk-KR2BP7NK.js} +4 -4
- package/dist/chunk-KR2BP7NK.js.map +1 -0
- package/dist/{chunk-DFVRZ3UK.js → chunk-KXARS46L.js} +3 -3
- package/dist/chunk-KXARS46L.js.map +1 -0
- package/dist/{chunk-JP55QWVR.js → chunk-L7L6SX64.js} +5 -2
- package/dist/chunk-L7L6SX64.js.map +1 -0
- package/dist/{chunk-QZZVHY6U.js → chunk-PVLDKXKR.js} +7 -3
- package/dist/chunk-PVLDKXKR.js.map +1 -0
- package/dist/{chunk-3KWD5Y5O.js → chunk-RXE5CUUE.js} +8 -4
- package/dist/chunk-RXE5CUUE.js.map +1 -0
- package/dist/{chunk-H7UAGTVW.js → chunk-TSG5C7MP.js} +4 -3
- package/dist/chunk-TSG5C7MP.js.map +1 -0
- package/dist/{chunk-NUCLEOHF.js → chunk-UNM7KFDX.js} +44 -14
- package/dist/chunk-UNM7KFDX.js.map +1 -0
- package/dist/{chunk-CEKPLUQV.js → chunk-UY7UF7IN.js} +19 -13
- package/dist/chunk-UY7UF7IN.js.map +1 -0
- package/dist/data-display/Alert.js +1 -1
- package/dist/data-display/AsyncStateContainer.js +2 -1
- package/dist/data-display/AsyncStateContainer.js.map +1 -1
- package/dist/data-display/Banner.js +4 -2
- package/dist/data-display/Banner.js.map +1 -1
- package/dist/data-display/CardKit.js +3 -1
- package/dist/data-display/CardKit.js.map +1 -1
- package/dist/data-display/Carousel.js +5 -2
- package/dist/data-display/Carousel.js.map +1 -1
- package/dist/data-display/DataGrid.js +1 -1
- package/dist/data-display/DataViews.js +90 -66
- package/dist/data-display/DataViews.js.map +1 -1
- package/dist/data-display/HoverCard.js +4 -1
- package/dist/data-display/HoverCard.js.map +1 -1
- package/dist/data-display/Lightbox.js +147 -117
- package/dist/data-display/Lightbox.js.map +1 -1
- package/dist/data-display/MapPreview.js +14 -10
- package/dist/data-display/MapPreview.js.map +1 -1
- package/dist/data-display/Table.js +4 -2
- package/dist/data-display/Table.js.map +1 -1
- package/dist/data-display/TimingDiagram.js +3 -2
- package/dist/data-display/TimingDiagram.js.map +1 -1
- package/dist/feedback/Sonner.js +5 -4
- package/dist/feedback/Sonner.js.map +1 -1
- package/dist/feedback/StackedModal.js +1 -1
- package/dist/feedback/TutorialOverlay.js +19 -5
- package/dist/feedback/TutorialOverlay.js.map +1 -1
- package/dist/form/AddressAutocomplete.js +16 -8
- package/dist/form/AddressAutocomplete.js.map +1 -1
- package/dist/form/Autocomplete.js +9 -7
- package/dist/form/Autocomplete.js.map +1 -1
- package/dist/form/Calendar.js +1 -1
- package/dist/form/CodeEditor.js +14 -9
- package/dist/form/CodeEditor.js.map +1 -1
- package/dist/form/DateRangePicker.js +10 -3
- package/dist/form/DateRangePicker.js.map +1 -1
- package/dist/form/Menubar.js +4 -1
- package/dist/form/Menubar.js.map +1 -1
- package/dist/form/PasswordStrength.js +48 -28
- package/dist/form/PasswordStrength.js.map +1 -1
- package/dist/form/PhoneInput.js +11 -5
- package/dist/form/PhoneInput.js.map +1 -1
- package/dist/form/SearchPalette.js +4 -1
- package/dist/form/SearchPalette.js.map +1 -1
- package/dist/form/Textarea.js +21 -19
- package/dist/form/Textarea.js.map +1 -1
- package/dist/form/UploadButton.js +16 -11
- package/dist/form/UploadButton.js.map +1 -1
- package/dist/layout/ContentToolbar.js +1 -1
- package/dist/layout/Dashboard.js +2 -1
- package/dist/layout/Dashboard.js.map +1 -1
- package/dist/layout/ScrollArea.js +9 -3
- package/dist/layout/ScrollArea.js.map +1 -1
- package/dist/layout/TableFilter.js +1 -1
- package/dist/navigation/Breadcrumbs.js +9 -3
- package/dist/navigation/Breadcrumbs.js.map +1 -1
- package/dist/navigation/CommandPalette.js +20 -7
- package/dist/navigation/CommandPalette.js.map +1 -1
- package/dist/navigation/Pagination.js +1 -1
- package/dist/navigation/Tabs.js +7 -3
- package/dist/navigation/Tabs.js.map +1 -1
- package/dist/pt-BR.js +192 -5
- package/dist/pt-BR.js.map +1 -1
- package/dist/types/components/data-display/Alert/Alert.d.ts +9 -2
- package/dist/types/components/data-display/Alert/Alert.d.ts.map +1 -1
- package/dist/types/components/data-display/Alert/Alert.types.d.ts +25 -9
- package/dist/types/components/data-display/Alert/Alert.types.d.ts.map +1 -1
- package/dist/types/components/data-display/AsyncStateContainer/AsyncStateContainer.d.ts.map +1 -1
- package/dist/types/components/data-display/AsyncStateContainer/AsyncStateContainer.types.d.ts +6 -0
- package/dist/types/components/data-display/AsyncStateContainer/AsyncStateContainer.types.d.ts.map +1 -1
- package/dist/types/components/data-display/Banner/Banner.d.ts.map +1 -1
- package/dist/types/components/data-display/Banner/Banner.types.d.ts +2 -0
- package/dist/types/components/data-display/Banner/Banner.types.d.ts.map +1 -1
- package/dist/types/components/data-display/CardKit/card-actions-context.d.ts +3 -1
- package/dist/types/components/data-display/CardKit/card-actions-context.d.ts.map +1 -1
- package/dist/types/components/data-display/Carousel/Carousel.chrome.d.ts +3 -0
- package/dist/types/components/data-display/Carousel/Carousel.chrome.d.ts.map +1 -1
- package/dist/types/components/data-display/Carousel/Carousel.parts.d.ts.map +1 -1
- package/dist/types/components/data-display/Carousel/Carousel.types.d.ts +7 -0
- package/dist/types/components/data-display/Carousel/Carousel.types.d.ts.map +1 -1
- package/dist/types/components/data-display/DataGrid/DataGrid.states.d.ts +1 -0
- package/dist/types/components/data-display/DataGrid/DataGrid.states.d.ts.map +1 -1
- package/dist/types/components/data-display/DataGrid/DataGrid.types.d.ts +5 -0
- package/dist/types/components/data-display/DataGrid/DataGrid.types.d.ts.map +1 -1
- package/dist/types/components/data-display/DataViews/data-views-category-pill.d.ts.map +1 -1
- package/dist/types/components/data-display/DataViews/data-views-copy.d.ts +29 -1
- package/dist/types/components/data-display/DataViews/data-views-copy.d.ts.map +1 -1
- package/dist/types/components/data-display/DataViews/data-views-day-input.d.ts +12 -1
- package/dist/types/components/data-display/DataViews/data-views-day-input.d.ts.map +1 -1
- package/dist/types/components/data-display/DataViews/data-views-filter-panel.d.ts.map +1 -1
- package/dist/types/components/data-display/DataViews/data-views-grid-bodies.d.ts.map +1 -1
- package/dist/types/components/data-display/DataViews/data-views-grid-parts.d.ts.map +1 -1
- package/dist/types/components/data-display/DataViews/data-views-more-fields.d.ts.map +1 -1
- package/dist/types/components/data-display/DataViews/data-views-pagination.d.ts.map +1 -1
- package/dist/types/components/data-display/DataViews/data-views-range-pill.d.ts.map +1 -1
- package/dist/types/components/data-display/DataViews/data-views-table-parts.d.ts.map +1 -1
- package/dist/types/components/data-display/DataViews/data-views-toolbar.d.ts.map +1 -1
- package/dist/types/components/data-display/HoverCard/HoverCard.content.d.ts +1 -1
- package/dist/types/components/data-display/HoverCard/HoverCard.content.d.ts.map +1 -1
- package/dist/types/components/data-display/HoverCard/HoverCard.d.ts.map +1 -1
- package/dist/types/components/data-display/HoverCard/HoverCard.types.d.ts +2 -0
- package/dist/types/components/data-display/HoverCard/HoverCard.types.d.ts.map +1 -1
- package/dist/types/components/data-display/Lightbox/Lightbox.d.ts.map +1 -1
- package/dist/types/components/data-display/Lightbox/Lightbox.types.d.ts +9 -0
- package/dist/types/components/data-display/Lightbox/Lightbox.types.d.ts.map +1 -1
- package/dist/types/components/data-display/Lightbox/LightboxControls.d.ts.map +1 -1
- package/dist/types/components/data-display/Lightbox/lightbox-copy-context.d.ts +17 -0
- package/dist/types/components/data-display/Lightbox/lightbox-copy-context.d.ts.map +1 -0
- package/dist/types/components/data-display/MapPreview/MapControlBar.d.ts +7 -0
- package/dist/types/components/data-display/MapPreview/MapControlBar.d.ts.map +1 -1
- package/dist/types/components/data-display/MapPreview/MapLayers.d.ts.map +1 -1
- package/dist/types/components/data-display/MapPreview/MapPreview.types.d.ts +9 -0
- package/dist/types/components/data-display/MapPreview/MapPreview.types.d.ts.map +1 -1
- package/dist/types/components/data-display/MapPreview/MapSearchBar.d.ts +2 -0
- package/dist/types/components/data-display/MapPreview/MapSearchBar.d.ts.map +1 -1
- package/dist/types/components/data-display/Table/Table.d.ts.map +1 -1
- package/dist/types/components/data-display/Table/Table.types.d.ts +5 -0
- package/dist/types/components/data-display/Table/Table.types.d.ts.map +1 -1
- package/dist/types/components/data-display/Table/TableStates.d.ts +3 -1
- package/dist/types/components/data-display/Table/TableStates.d.ts.map +1 -1
- package/dist/types/components/data-display/TimingDiagram/TimingDiagram.d.ts.map +1 -1
- package/dist/types/components/data-display/TimingDiagram/TimingDiagram.types.d.ts +3 -0
- package/dist/types/components/data-display/TimingDiagram/TimingDiagram.types.d.ts.map +1 -1
- package/dist/types/components/feedback/Sonner/Sonner.d.ts +8 -0
- package/dist/types/components/feedback/Sonner/Sonner.d.ts.map +1 -1
- package/dist/types/components/feedback/Sonner/Sonner.types.d.ts +2 -0
- package/dist/types/components/feedback/Sonner/Sonner.types.d.ts.map +1 -1
- package/dist/types/components/feedback/Sonner/SonnerToast.d.ts.map +1 -1
- package/dist/types/components/feedback/StackedModal/StackedModal.d.ts.map +1 -1
- package/dist/types/components/feedback/StackedModal/StackedModal.parts.d.ts +2 -0
- package/dist/types/components/feedback/StackedModal/StackedModal.parts.d.ts.map +1 -1
- package/dist/types/components/feedback/StackedModal/StackedModal.types.d.ts +2 -0
- package/dist/types/components/feedback/StackedModal/StackedModal.types.d.ts.map +1 -1
- package/dist/types/components/feedback/TutorialOverlay/TutorialOverlay.d.ts.map +1 -1
- package/dist/types/components/feedback/TutorialOverlay/TutorialOverlay.types.d.ts +3 -0
- package/dist/types/components/feedback/TutorialOverlay/TutorialOverlay.types.d.ts.map +1 -1
- package/dist/types/components/feedback/TutorialOverlay/TutorialStepBody.d.ts +2 -0
- package/dist/types/components/feedback/TutorialOverlay/TutorialStepBody.d.ts.map +1 -1
- package/dist/types/components/form/AddressAutocomplete/AddressAutocomplete.types.d.ts +7 -0
- package/dist/types/components/form/AddressAutocomplete/AddressAutocomplete.types.d.ts.map +1 -1
- package/dist/types/components/form/AddressAutocomplete/AddressAutocompleteField.d.ts.map +1 -1
- package/dist/types/components/form/AddressAutocomplete/AddressFallbackField.d.ts +1 -0
- package/dist/types/components/form/AddressAutocomplete/AddressFallbackField.d.ts.map +1 -1
- package/dist/types/components/form/AddressAutocomplete/AddressInputField.d.ts +1 -0
- package/dist/types/components/form/AddressAutocomplete/AddressInputField.d.ts.map +1 -1
- package/dist/types/components/form/Autocomplete/Autocomplete.d.ts.map +1 -1
- package/dist/types/components/form/Autocomplete/Autocomplete.parts.d.ts +9 -3
- package/dist/types/components/form/Autocomplete/Autocomplete.parts.d.ts.map +1 -1
- package/dist/types/components/form/Autocomplete/Autocomplete.types.d.ts +7 -0
- package/dist/types/components/form/Autocomplete/Autocomplete.types.d.ts.map +1 -1
- package/dist/types/components/form/Calendar/Calendar.types.d.ts +6 -0
- package/dist/types/components/form/Calendar/Calendar.types.d.ts.map +1 -1
- package/dist/types/components/form/CodeEditor/CodeEditor.d.ts.map +1 -1
- package/dist/types/components/form/CodeEditor/CodeEditor.types.d.ts +7 -0
- package/dist/types/components/form/CodeEditor/CodeEditor.types.d.ts.map +1 -1
- package/dist/types/components/form/CodeEditor/CodeEditorToolbar.d.ts +2 -0
- package/dist/types/components/form/CodeEditor/CodeEditorToolbar.d.ts.map +1 -1
- package/dist/types/components/form/DateRangePicker/DateRangePicker.d.ts.map +1 -1
- package/dist/types/components/form/DateRangePicker/DateRangePicker.range.d.ts.map +1 -1
- package/dist/types/components/form/DateRangePicker/DateRangePicker.types.d.ts +11 -0
- package/dist/types/components/form/DateRangePicker/DateRangePicker.types.d.ts.map +1 -1
- package/dist/types/components/form/DateRangePicker/DateRangePickerFields.d.ts.map +1 -1
- package/dist/types/components/form/Menubar/Menubar.d.ts.map +1 -1
- package/dist/types/components/form/Menubar/Menubar.toolbar.d.ts +1 -0
- package/dist/types/components/form/Menubar/Menubar.toolbar.d.ts.map +1 -1
- package/dist/types/components/form/Menubar/Menubar.types.d.ts +5 -0
- package/dist/types/components/form/Menubar/Menubar.types.d.ts.map +1 -1
- package/dist/types/components/form/PasswordStrength/PasswordStrength.d.ts.map +1 -1
- package/dist/types/components/form/PasswordStrength/PasswordStrength.helpers.d.ts +4 -2
- package/dist/types/components/form/PasswordStrength/PasswordStrength.helpers.d.ts.map +1 -1
- package/dist/types/components/form/PasswordStrength/PasswordStrength.types.d.ts +8 -0
- package/dist/types/components/form/PasswordStrength/PasswordStrength.types.d.ts.map +1 -1
- package/dist/types/components/form/PhoneInput/PhoneInput.d.ts.map +1 -1
- package/dist/types/components/form/PhoneInput/PhoneInput.types.d.ts +7 -0
- package/dist/types/components/form/PhoneInput/PhoneInput.types.d.ts.map +1 -1
- package/dist/types/components/form/SearchPalette/SearchPalette.d.ts.map +1 -1
- package/dist/types/components/form/SearchPalette/SearchPalette.parts.d.ts +2 -0
- package/dist/types/components/form/SearchPalette/SearchPalette.parts.d.ts.map +1 -1
- package/dist/types/components/form/SearchPalette/SearchPalette.types.d.ts +6 -0
- package/dist/types/components/form/SearchPalette/SearchPalette.types.d.ts.map +1 -1
- package/dist/types/components/form/Textarea/Textarea.d.ts.map +1 -1
- package/dist/types/components/form/Textarea/Textarea.types.d.ts +16 -1
- package/dist/types/components/form/Textarea/Textarea.types.d.ts.map +1 -1
- package/dist/types/components/form/Textarea/TextareaRichEditor.d.ts +7 -0
- package/dist/types/components/form/Textarea/TextareaRichEditor.d.ts.map +1 -1
- package/dist/types/components/form/UploadButton/UploadButton.d.ts.map +1 -1
- package/dist/types/components/form/UploadButton/UploadButton.parts.d.ts +6 -3
- package/dist/types/components/form/UploadButton/UploadButton.parts.d.ts.map +1 -1
- package/dist/types/components/form/UploadButton/UploadButton.types.d.ts +7 -0
- package/dist/types/components/form/UploadButton/UploadButton.types.d.ts.map +1 -1
- package/dist/types/components/layout/ContentToolbar/ContentToolbar.d.ts +1 -1
- package/dist/types/components/layout/ContentToolbar/ContentToolbar.d.ts.map +1 -1
- package/dist/types/components/layout/ContentToolbar/ContentToolbar.types.d.ts +12 -0
- package/dist/types/components/layout/ContentToolbar/ContentToolbar.types.d.ts.map +1 -1
- package/dist/types/components/layout/ContentToolbar/MultiSelectDropdown.d.ts +1 -1
- package/dist/types/components/layout/ContentToolbar/MultiSelectDropdown.d.ts.map +1 -1
- package/dist/types/components/layout/ContentToolbar/SortByDropdown.d.ts +1 -1
- package/dist/types/components/layout/ContentToolbar/SortByDropdown.d.ts.map +1 -1
- package/dist/types/components/layout/ContentToolbar/ViewSelector.d.ts +1 -1
- package/dist/types/components/layout/ContentToolbar/ViewSelector.d.ts.map +1 -1
- package/dist/types/components/layout/Dashboard/Dashboard.d.ts +1 -1
- package/dist/types/components/layout/Dashboard/Dashboard.types.d.ts +4 -0
- package/dist/types/components/layout/Dashboard/Dashboard.types.d.ts.map +1 -1
- package/dist/types/components/layout/Dashboard/DashboardParts.d.ts +1 -1
- package/dist/types/components/layout/Dashboard/DashboardParts.d.ts.map +1 -1
- package/dist/types/components/layout/ScrollArea/ScrollArea.d.ts.map +1 -1
- package/dist/types/components/layout/ScrollArea/ScrollArea.types.d.ts +21 -3
- package/dist/types/components/layout/ScrollArea/ScrollArea.types.d.ts.map +1 -1
- package/dist/types/components/layout/ScrollArea/ScrollViewport.d.ts +2 -0
- package/dist/types/components/layout/ScrollArea/ScrollViewport.d.ts.map +1 -1
- package/dist/types/components/layout/TableFilter/TableFilter.d.ts +1 -1
- package/dist/types/components/layout/TableFilter/TableFilter.d.ts.map +1 -1
- package/dist/types/components/layout/TableFilter/TableFilter.types.d.ts +7 -0
- package/dist/types/components/layout/TableFilter/TableFilter.types.d.ts.map +1 -1
- package/dist/types/components/layout/TableFilter/TableFilterContext.d.ts +9 -0
- package/dist/types/components/layout/TableFilter/TableFilterContext.d.ts.map +1 -1
- package/dist/types/components/navigation/Breadcrumbs/BreadcrumbEntry.d.ts +2 -0
- package/dist/types/components/navigation/Breadcrumbs/BreadcrumbEntry.d.ts.map +1 -1
- package/dist/types/components/navigation/Breadcrumbs/Breadcrumbs.d.ts.map +1 -1
- package/dist/types/components/navigation/Breadcrumbs/Breadcrumbs.types.d.ts +3 -0
- package/dist/types/components/navigation/Breadcrumbs/Breadcrumbs.types.d.ts.map +1 -1
- package/dist/types/components/navigation/CommandPalette/CommandPalette.d.ts.map +1 -1
- package/dist/types/components/navigation/CommandPalette/CommandPalette.types.d.ts +3 -0
- package/dist/types/components/navigation/CommandPalette/CommandPalette.types.d.ts.map +1 -1
- package/dist/types/components/navigation/CommandPalette/CommandPaletteResults.d.ts +2 -0
- package/dist/types/components/navigation/CommandPalette/CommandPaletteResults.d.ts.map +1 -1
- package/dist/types/components/navigation/Pagination/Pagination.d.ts.map +1 -1
- package/dist/types/components/navigation/Pagination/Pagination.types.d.ts +2 -0
- package/dist/types/components/navigation/Pagination/Pagination.types.d.ts.map +1 -1
- package/dist/types/components/navigation/Pagination/PaginationParts.d.ts +2 -0
- package/dist/types/components/navigation/Pagination/PaginationParts.d.ts.map +1 -1
- package/dist/types/components/navigation/Tabs/Tabs.d.ts.map +1 -1
- package/dist/types/components/navigation/Tabs/Tabs.types.d.ts +2 -0
- package/dist/types/components/navigation/Tabs/Tabs.types.d.ts.map +1 -1
- package/dist/types/components/utility/InfiniteScroll/InfiniteScroll.d.ts.map +1 -1
- package/dist/types/components/utility/InfiniteScroll/InfiniteScroll.parts.d.ts +6 -2
- package/dist/types/components/utility/InfiniteScroll/InfiniteScroll.parts.d.ts.map +1 -1
- package/dist/types/components/utility/InfiniteScroll/InfiniteScroll.types.d.ts +6 -0
- package/dist/types/components/utility/InfiniteScroll/InfiniteScroll.types.d.ts.map +1 -1
- package/dist/types/components/utility/InstallPrompt/InstallPrompt.d.ts +4 -2
- package/dist/types/components/utility/InstallPrompt/InstallPrompt.d.ts.map +1 -1
- package/dist/types/components/utility/InstallPrompt/InstallPrompt.types.d.ts +8 -7
- package/dist/types/components/utility/InstallPrompt/InstallPrompt.types.d.ts.map +1 -1
- package/dist/types/copy/data-display.d.ts +56 -0
- package/dist/types/copy/data-display.d.ts.map +1 -0
- package/dist/types/copy/feedback.d.ts +38 -0
- package/dist/types/copy/feedback.d.ts.map +1 -0
- package/dist/types/copy/form.d.ts +146 -0
- package/dist/types/copy/form.d.ts.map +1 -0
- package/dist/types/copy/layout.d.ts +18 -0
- package/dist/types/copy/layout.d.ts.map +1 -0
- package/dist/types/copy/navigation.d.ts +24 -0
- package/dist/types/copy/navigation.d.ts.map +1 -0
- package/dist/types/copy/shared.d.ts +119 -0
- package/dist/types/copy/shared.d.ts.map +1 -0
- package/dist/types/copy/utility.d.ts +35 -0
- package/dist/types/copy/utility.d.ts.map +1 -0
- package/dist/types/copy.d.ts +13 -113
- package/dist/types/copy.d.ts.map +1 -1
- package/dist/types/pt-BR.d.ts +15 -18
- package/dist/types/pt-BR.d.ts.map +1 -1
- package/dist/types/pt-BR.data-display.d.ts +13 -0
- package/dist/types/pt-BR.data-display.d.ts.map +1 -0
- package/dist/types/pt-BR.feedback.d.ts +8 -0
- package/dist/types/pt-BR.feedback.d.ts.map +1 -0
- package/dist/types/pt-BR.form.d.ts +13 -0
- package/dist/types/pt-BR.form.d.ts.map +1 -0
- package/dist/types/pt-BR.layout.d.ts +7 -0
- package/dist/types/pt-BR.layout.d.ts.map +1 -0
- package/dist/types/pt-BR.navigation.d.ts +8 -0
- package/dist/types/pt-BR.navigation.d.ts.map +1 -0
- package/dist/types/pt-BR.shared.d.ts +21 -0
- package/dist/types/pt-BR.shared.d.ts.map +1 -0
- package/dist/types/pt-BR.utility.d.ts +8 -0
- package/dist/types/pt-BR.utility.d.ts.map +1 -0
- package/dist/types/user-avatar/user-avatar.d.ts +7 -1
- package/dist/types/user-avatar/user-avatar.d.ts.map +1 -1
- package/dist/user-avatar.js +2 -2
- package/dist/user-avatar.js.map +1 -1
- package/dist/utility/InfiniteScroll.js +7 -5
- package/dist/utility/InfiniteScroll.js.map +1 -1
- package/dist/utility/InstallPrompt.js +14 -13
- package/dist/utility/InstallPrompt.js.map +1 -1
- package/package.json +1 -1
- package/src/components/data-display/Alert/Alert.tsx +22 -5
- package/src/components/data-display/Alert/Alert.types.ts +20 -10
- package/src/components/data-display/AsyncStateContainer/AsyncStateContainer.tsx +2 -1
- package/src/components/data-display/AsyncStateContainer/AsyncStateContainer.types.ts +6 -0
- package/src/components/data-display/Banner/Banner.tsx +5 -2
- package/src/components/data-display/Banner/Banner.types.ts +2 -0
- package/src/components/data-display/CardKit/card-actions-context.tsx +4 -0
- package/src/components/data-display/Carousel/Carousel.chrome.tsx +5 -0
- package/src/components/data-display/Carousel/Carousel.parts.tsx +4 -3
- package/src/components/data-display/Carousel/Carousel.types.ts +7 -0
- package/src/components/data-display/DataGrid/DataGrid.states.tsx +3 -3
- package/src/components/data-display/DataGrid/DataGrid.tsx +1 -1
- package/src/components/data-display/DataGrid/DataGrid.types.ts +5 -0
- package/src/components/data-display/DataViews/data-views-category-pill.tsx +2 -0
- package/src/components/data-display/DataViews/data-views-copy.ts +29 -1
- package/src/components/data-display/DataViews/data-views-day-input.tsx +14 -3
- package/src/components/data-display/DataViews/data-views-filter-panel.tsx +2 -1
- package/src/components/data-display/DataViews/data-views-grid-bodies.tsx +3 -0
- package/src/components/data-display/DataViews/data-views-grid-parts.tsx +9 -10
- package/src/components/data-display/DataViews/data-views-more-fields.tsx +3 -1
- package/src/components/data-display/DataViews/data-views-pagination.tsx +3 -0
- package/src/components/data-display/DataViews/data-views-range-pill.tsx +3 -1
- package/src/components/data-display/DataViews/data-views-table-parts.tsx +1 -0
- package/src/components/data-display/DataViews/data-views-toolbar.tsx +2 -0
- package/src/components/data-display/HoverCard/HoverCard.content.tsx +10 -2
- package/src/components/data-display/HoverCard/HoverCard.tsx +2 -3
- package/src/components/data-display/HoverCard/HoverCard.types.ts +2 -0
- package/src/components/data-display/Lightbox/Lightbox.tsx +3 -0
- package/src/components/data-display/Lightbox/Lightbox.types.ts +9 -0
- package/src/components/data-display/Lightbox/LightboxControls.tsx +28 -15
- package/src/components/data-display/Lightbox/lightbox-copy-context.tsx +42 -0
- package/src/components/data-display/MapPreview/MapControlBar.tsx +17 -9
- package/src/components/data-display/MapPreview/MapLayers.tsx +2 -0
- package/src/components/data-display/MapPreview/MapPreview.types.ts +10 -0
- package/src/components/data-display/MapPreview/MapSearchBar.tsx +4 -1
- package/src/components/data-display/Table/Table.tsx +7 -2
- package/src/components/data-display/Table/Table.types.ts +5 -0
- package/src/components/data-display/Table/TableStates.tsx +2 -2
- package/src/components/data-display/TimingDiagram/TimingDiagram.tsx +3 -2
- package/src/components/data-display/TimingDiagram/TimingDiagram.types.ts +3 -0
- package/src/components/feedback/Sonner/Sonner.tsx +15 -3
- package/src/components/feedback/Sonner/Sonner.types.ts +2 -0
- package/src/components/feedback/Sonner/SonnerToast.tsx +2 -1
- package/src/components/feedback/StackedModal/StackedModal.parts.tsx +10 -3
- package/src/components/feedback/StackedModal/StackedModal.tsx +2 -1
- package/src/components/feedback/StackedModal/StackedModal.types.ts +2 -0
- package/src/components/feedback/TutorialOverlay/TutorialOverlay.tsx +13 -1
- package/src/components/feedback/TutorialOverlay/TutorialOverlay.types.ts +3 -0
- package/src/components/feedback/TutorialOverlay/TutorialStepBody.tsx +10 -4
- package/src/components/form/AddressAutocomplete/AddressAutocomplete.types.ts +7 -0
- package/src/components/form/AddressAutocomplete/AddressAutocompleteField.tsx +2 -0
- package/src/components/form/AddressAutocomplete/AddressFallbackField.tsx +4 -2
- package/src/components/form/AddressAutocomplete/AddressInputField.tsx +13 -6
- package/src/components/form/Autocomplete/Autocomplete.parts.tsx +9 -6
- package/src/components/form/Autocomplete/Autocomplete.tsx +2 -1
- package/src/components/form/Autocomplete/Autocomplete.types.ts +7 -0
- package/src/components/form/Calendar/Calendar.tsx +2 -2
- package/src/components/form/Calendar/Calendar.types.ts +7 -0
- package/src/components/form/CodeEditor/CodeEditor.tsx +6 -4
- package/src/components/form/CodeEditor/CodeEditor.types.ts +7 -0
- package/src/components/form/CodeEditor/CodeEditorToolbar.tsx +12 -5
- package/src/components/form/DateRangePicker/DateRangePicker.range.ts +2 -0
- package/src/components/form/DateRangePicker/DateRangePicker.tsx +4 -0
- package/src/components/form/DateRangePicker/DateRangePicker.types.ts +11 -0
- package/src/components/form/DateRangePicker/DateRangePickerFields.tsx +2 -0
- package/src/components/form/Menubar/Menubar.toolbar.tsx +3 -1
- package/src/components/form/Menubar/Menubar.tsx +2 -1
- package/src/components/form/Menubar/Menubar.types.ts +5 -0
- package/src/components/form/PasswordStrength/PasswordStrength.helpers.tsx +33 -17
- package/src/components/form/PasswordStrength/PasswordStrength.tsx +29 -13
- package/src/components/form/PasswordStrength/PasswordStrength.types.ts +9 -0
- package/src/components/form/PhoneInput/PhoneInput.tsx +15 -5
- package/src/components/form/PhoneInput/PhoneInput.types.ts +7 -0
- package/src/components/form/SearchPalette/SearchPalette.parts.tsx +4 -1
- package/src/components/form/SearchPalette/SearchPalette.tsx +4 -1
- package/src/components/form/SearchPalette/SearchPalette.types.ts +6 -0
- package/src/components/form/Textarea/Textarea.tsx +6 -1
- package/src/components/form/Textarea/Textarea.types.ts +14 -1
- package/src/components/form/Textarea/TextareaRichEditor.tsx +28 -18
- package/src/components/form/UploadButton/UploadButton.parts.tsx +16 -7
- package/src/components/form/UploadButton/UploadButton.tsx +4 -1
- package/src/components/form/UploadButton/UploadButton.types.ts +8 -0
- package/src/components/layout/ContentToolbar/ContentToolbar.tsx +10 -2
- package/src/components/layout/ContentToolbar/ContentToolbar.types.ts +12 -0
- package/src/components/layout/ContentToolbar/MultiSelectDropdown.tsx +15 -4
- package/src/components/layout/ContentToolbar/SortByDropdown.tsx +12 -3
- package/src/components/layout/ContentToolbar/ViewSelector.tsx +2 -1
- package/src/components/layout/Dashboard/Dashboard.types.ts +4 -0
- package/src/components/layout/Dashboard/DashboardParts.tsx +2 -1
- package/src/components/layout/ScrollArea/ScrollArea.tsx +16 -3
- package/src/components/layout/ScrollArea/ScrollArea.types.ts +19 -4
- package/src/components/layout/ScrollArea/ScrollViewport.tsx +4 -1
- package/src/components/layout/TableFilter/TableFilter.tsx +19 -11
- package/src/components/layout/TableFilter/TableFilter.types.ts +8 -0
- package/src/components/layout/TableFilter/TableFilterContext.tsx +10 -0
- package/src/components/navigation/Breadcrumbs/BreadcrumbEntry.tsx +11 -4
- package/src/components/navigation/Breadcrumbs/Breadcrumbs.tsx +2 -0
- package/src/components/navigation/Breadcrumbs/Breadcrumbs.types.ts +3 -0
- package/src/components/navigation/CommandPalette/CommandPalette.tsx +15 -5
- package/src/components/navigation/CommandPalette/CommandPalette.types.ts +3 -0
- package/src/components/navigation/CommandPalette/CommandPaletteResults.tsx +5 -2
- package/src/components/navigation/Pagination/Pagination.tsx +2 -0
- package/src/components/navigation/Pagination/Pagination.types.ts +2 -0
- package/src/components/navigation/Pagination/PaginationParts.tsx +4 -1
- package/src/components/navigation/Tabs/Tabs.tsx +8 -3
- package/src/components/navigation/Tabs/Tabs.types.ts +2 -0
- package/src/components/utility/InfiniteScroll/InfiniteScroll.parts.tsx +4 -4
- package/src/components/utility/InfiniteScroll/InfiniteScroll.tsx +4 -2
- package/src/components/utility/InfiniteScroll/InfiniteScroll.types.ts +6 -0
- package/src/components/utility/InstallPrompt/InstallPrompt.tsx +18 -13
- package/src/components/utility/InstallPrompt/InstallPrompt.types.ts +9 -7
- package/src/copy/data-display.ts +60 -0
- package/src/copy/feedback.ts +39 -0
- package/src/copy/form.ts +154 -0
- package/src/copy/layout.ts +18 -0
- package/src/copy/navigation.ts +25 -0
- package/src/copy/shared.ts +122 -0
- package/src/copy/utility.ts +36 -0
- package/src/copy.ts +13 -117
- package/src/pt-BR.data-display.ts +236 -0
- package/src/pt-BR.feedback.ts +25 -0
- package/src/pt-BR.form.ts +96 -0
- package/src/pt-BR.layout.ts +15 -0
- package/src/pt-BR.navigation.ts +21 -0
- package/src/pt-BR.shared.ts +94 -0
- package/src/pt-BR.ts +15 -267
- package/src/pt-BR.utility.ts +21 -0
- package/src/user-avatar/user-avatar.tsx +8 -2
- package/dist/chunk-3KWD5Y5O.js.map +0 -1
- package/dist/chunk-CEKPLUQV.js.map +0 -1
- package/dist/chunk-DFVRZ3UK.js.map +0 -1
- package/dist/chunk-H7UAGTVW.js.map +0 -1
- package/dist/chunk-JP55QWVR.js.map +0 -1
- package/dist/chunk-NUCLEOHF.js.map +0 -1
- package/dist/chunk-QLBYAYUG.js.map +0 -1
- package/dist/chunk-QZZVHY6U.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/utility/InfiniteScroll/InfiniteScroll.tsx","../../src/components/utility/InfiniteScroll/InfiniteScroll.hooks.ts","../../src/components/utility/InfiniteScroll/InfiniteScroll.parts.tsx"],"sourcesContent":["import { Box } from '@mui/material';\nimport React from 'react';\n\nimport { useLoadMore, useSentinel } from './InfiniteScroll.hooks';\nimport {\n containerStyles,\n DefaultEndMessage,\n DefaultError,\n DefaultLoader,\n} from './InfiniteScroll.parts';\nimport type { InfiniteScrollProps } from './InfiniteScroll.types';\n\nexport const InfiniteScroll: React.FC<InfiniteScrollProps> = ({\n children,\n variant = 'default',\n hasMore,\n loading,\n threshold = 150,\n loadMore,\n loader,\n endMessage,\n error,\n errorComponent,\n onError,\n className,\n style,\n width,\n scrollableTarget,\n testMode = false,\n testTriggerRef,\n}) => {\n const { sentinelRef, isIntersecting } = useSentinel({ testMode, threshold, scrollableTarget });\n\n useLoadMore({\n hasMore,\n loading,\n error,\n loadMore,\n onError,\n isIntersecting,\n testMode,\n testTriggerRef,\n });\n\n // The tail of the list is one of four things: the end message, an error, a\n // spinner, or the sentinel that asks for the next page.\n const renderSentinel = () => {\n if (!hasMore) return endMessage || <DefaultEndMessage />;\n if (error) return errorComponent || <DefaultError error={error} />;\n if (loading) return loader || <DefaultLoader />;\n\n return (\n <div\n ref={sentinelRef}\n data-testid=\"infinite-scroll-sentinel\"\n style={{\n height: variant === 'horizontal' ? '100%' : '1px',\n width: variant === 'horizontal' ? '1px' : '100%',\n }}\n />\n );\n };\n\n return (\n <Box\n className={className}\n sx={{\n ...containerStyles(variant, width),\n ...style,\n }}\n >\n {variant === 'reverse' && renderSentinel()}\n\n {variant === 'horizontal' ? (\n <Box sx={{ display: 'flex', flexDirection: 'row' }}>{children}</Box>\n ) : (\n children\n )}\n\n {variant !== 'reverse' && renderSentinel()}\n\n {variant === 'horizontal' && renderSentinel()}\n </Box>\n );\n};\n","import { useCallback, useEffect, useRef, useState } from 'react';\n\nimport type { InfiniteScrollProps } from './InfiniteScroll.types';\n\ntype ScrollableTarget = InfiniteScrollProps['scrollableTarget'];\n\n/** A string target is looked up by id first, then as a selector. */\nconst resolveScrollable = (scrollableTarget: ScrollableTarget): Element | null => {\n if (!scrollableTarget) return null;\n\n if (typeof scrollableTarget === 'string') {\n return (\n document.getElementById(scrollableTarget) || document.querySelector(scrollableTarget)\n );\n }\n\n return scrollableTarget;\n};\n\ninterface ObserverArgs {\n testMode: boolean;\n threshold: number;\n scrollableTarget: ScrollableTarget;\n}\n\n/**\n * Watches a zero-size sentinel at the end of the list. `rootMargin` is what makes\n * it fire before the sentinel is actually on screen, so the next page is already\n * loading by the time the user reaches the bottom.\n */\nexport const useSentinel = ({ testMode, threshold, scrollableTarget }: ObserverArgs) => {\n const sentinelRef = useRef<HTMLDivElement>(null);\n const [isIntersecting, setIsIntersecting] = useState(false);\n\n useEffect(() => {\n if (testMode || !sentinelRef.current) return;\n\n const observer = new window.IntersectionObserver(\n ([entry]) => {\n if (entry) {\n setIsIntersecting(entry.isIntersecting);\n }\n },\n {\n root: resolveScrollable(scrollableTarget) || undefined,\n rootMargin: `${threshold}px`,\n threshold: 0,\n },\n );\n\n observer.observe(sentinelRef.current);\n\n return () => observer.disconnect();\n }, [testMode, threshold, scrollableTarget]);\n\n return { sentinelRef, isIntersecting };\n};\n\ntype LoadArgs = Pick<\n InfiniteScrollProps,\n 'hasMore' | 'loading' | 'error' | 'loadMore' | 'onError' | 'testTriggerRef'\n> & {\n isIntersecting: boolean;\n testMode: boolean;\n};\n\n/**\n * A ref rather than state guards re-entry, so a second intersection landing while\n * the first `loadMore` is still in flight is dropped rather than queued.\n */\nexport const useLoadMore = ({\n hasMore,\n loading,\n error,\n loadMore,\n onError,\n isIntersecting,\n testMode,\n testTriggerRef,\n}: LoadArgs) => {\n const loadingRef = useRef(false);\n\n const handleLoadMore = useCallback(async () => {\n if (loadingRef.current || !hasMore || error) {\n return;\n }\n\n loadingRef.current = true;\n\n try {\n await loadMore();\n } catch (err) {\n const errorObj = err instanceof Error ? err : new Error('Unknown error');\n onError?.(errorObj);\n } finally {\n loadingRef.current = false;\n }\n }, [hasMore, error, loadMore, onError]);\n\n // Expose test trigger for test mode\n useEffect(() => {\n if (testMode && testTriggerRef) {\n testTriggerRef.current = handleLoadMore;\n }\n\n return () => {\n if (testMode && testTriggerRef) {\n testTriggerRef.current = undefined;\n }\n };\n }, [testMode, testTriggerRef, handleLoadMore]);\n\n useEffect(() => {\n if (isIntersecting && hasMore && !loading && !error) {\n handleLoadMore();\n }\n }, [isIntersecting, hasMore, loading, error, handleLoadMore]);\n};\n","import { Alert, Box, CircularProgress, Typography } from '@mui/material';\nimport React from 'react';\n\nimport type { InfiniteScrollProps } from './InfiniteScroll.types';\n\ntype Variant = NonNullable<InfiniteScrollProps['variant']>;\n\nconst CENTERED = {\n display: 'flex',\n justifyContent: 'center',\n alignItems: 'center',\n p: 2,\n} as const;\n\nexport const DefaultLoader: React.FC = () => (\n <Box sx={CENTERED}>\n <CircularProgress size={24} />\n <Typography variant=\"body2\" sx={{ ml: 1 }}>\n Loading...\n </Typography>\n </Box>\n);\n\nexport const DefaultEndMessage: React.FC = () => (\n <Box sx={CENTERED}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n No more items to load\n </Typography>\n </Box>\n);\n\nexport const DefaultError: React.FC<{ error: Error }> = ({ error }) => (\n <Alert severity=\"error\" sx={{ m: 2 }}>\n {error.message || 'An error occurred while loading more items'}\n </Alert>\n);\n\n/**\n * `horizontal` scrolls along a row; `reverse` stacks bottom-up, for a chat log\n * that loads older messages as you scroll away from the newest.\n */\nexport const containerStyles = (variant: Variant, width?: number | string) => {\n const horizontal = variant === 'horizontal';\n const base = {\n width: horizontal ? width || '100%' : '100%',\n height: horizontal ? '100%' : 'auto',\n overflowX: horizontal ? 'auto' : 'hidden',\n overflowY: horizontal ? 'hidden' : 'visible',\n display: 'flex',\n flexDirection: horizontal ? 'row' : 'column',\n };\n\n return variant === 'reverse' ? { ...base, flexDirection: 'column-reverse' } : base;\n};\n"],"mappings":"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","names":["Box","jsx","jsxs","Box"]}
|
|
1
|
+
{"version":3,"sources":["../../src/components/utility/InfiniteScroll/InfiniteScroll.tsx","../../src/components/utility/InfiniteScroll/InfiniteScroll.hooks.ts","../../src/components/utility/InfiniteScroll/InfiniteScroll.parts.tsx"],"sourcesContent":["import { Box } from '@mui/material';\nimport React from 'react';\n\nimport { useLoadMore, useSentinel } from './InfiniteScroll.hooks';\nimport {\n containerStyles,\n DefaultEndMessage,\n DefaultError,\n DefaultLoader,\n} from './InfiniteScroll.parts';\nimport type { InfiniteScrollProps } from './InfiniteScroll.types';\n\nexport const InfiniteScroll: React.FC<InfiniteScrollProps> = ({\n children,\n variant = 'default',\n hasMore,\n loading,\n threshold = 150,\n loadMore,\n loader,\n loadingText,\n endText,\n endMessage,\n error,\n errorComponent,\n onError,\n className,\n style,\n width,\n scrollableTarget,\n testMode = false,\n testTriggerRef,\n}) => {\n const { sentinelRef, isIntersecting } = useSentinel({ testMode, threshold, scrollableTarget });\n\n useLoadMore({\n hasMore,\n loading,\n error,\n loadMore,\n onError,\n isIntersecting,\n testMode,\n testTriggerRef,\n });\n\n // The tail of the list is one of four things: the end message, an error, a\n // spinner, or the sentinel that asks for the next page.\n const renderSentinel = () => {\n if (!hasMore) return endMessage || <DefaultEndMessage endText={endText} />;\n if (error) return errorComponent || <DefaultError error={error} />;\n if (loading) return loader || <DefaultLoader loadingText={loadingText} />;\n\n return (\n <div\n ref={sentinelRef}\n data-testid=\"infinite-scroll-sentinel\"\n style={{\n height: variant === 'horizontal' ? '100%' : '1px',\n width: variant === 'horizontal' ? '1px' : '100%',\n }}\n />\n );\n };\n\n return (\n <Box\n className={className}\n sx={{\n ...containerStyles(variant, width),\n ...style,\n }}\n >\n {variant === 'reverse' && renderSentinel()}\n\n {variant === 'horizontal' ? (\n <Box sx={{ display: 'flex', flexDirection: 'row' }}>{children}</Box>\n ) : (\n children\n )}\n\n {variant !== 'reverse' && renderSentinel()}\n\n {variant === 'horizontal' && renderSentinel()}\n </Box>\n );\n};\n","import { useCallback, useEffect, useRef, useState } from 'react';\n\nimport type { InfiniteScrollProps } from './InfiniteScroll.types';\n\ntype ScrollableTarget = InfiniteScrollProps['scrollableTarget'];\n\n/** A string target is looked up by id first, then as a selector. */\nconst resolveScrollable = (scrollableTarget: ScrollableTarget): Element | null => {\n if (!scrollableTarget) return null;\n\n if (typeof scrollableTarget === 'string') {\n return (\n document.getElementById(scrollableTarget) || document.querySelector(scrollableTarget)\n );\n }\n\n return scrollableTarget;\n};\n\ninterface ObserverArgs {\n testMode: boolean;\n threshold: number;\n scrollableTarget: ScrollableTarget;\n}\n\n/**\n * Watches a zero-size sentinel at the end of the list. `rootMargin` is what makes\n * it fire before the sentinel is actually on screen, so the next page is already\n * loading by the time the user reaches the bottom.\n */\nexport const useSentinel = ({ testMode, threshold, scrollableTarget }: ObserverArgs) => {\n const sentinelRef = useRef<HTMLDivElement>(null);\n const [isIntersecting, setIsIntersecting] = useState(false);\n\n useEffect(() => {\n if (testMode || !sentinelRef.current) return;\n\n const observer = new window.IntersectionObserver(\n ([entry]) => {\n if (entry) {\n setIsIntersecting(entry.isIntersecting);\n }\n },\n {\n root: resolveScrollable(scrollableTarget) || undefined,\n rootMargin: `${threshold}px`,\n threshold: 0,\n },\n );\n\n observer.observe(sentinelRef.current);\n\n return () => observer.disconnect();\n }, [testMode, threshold, scrollableTarget]);\n\n return { sentinelRef, isIntersecting };\n};\n\ntype LoadArgs = Pick<\n InfiniteScrollProps,\n 'hasMore' | 'loading' | 'error' | 'loadMore' | 'onError' | 'testTriggerRef'\n> & {\n isIntersecting: boolean;\n testMode: boolean;\n};\n\n/**\n * A ref rather than state guards re-entry, so a second intersection landing while\n * the first `loadMore` is still in flight is dropped rather than queued.\n */\nexport const useLoadMore = ({\n hasMore,\n loading,\n error,\n loadMore,\n onError,\n isIntersecting,\n testMode,\n testTriggerRef,\n}: LoadArgs) => {\n const loadingRef = useRef(false);\n\n const handleLoadMore = useCallback(async () => {\n if (loadingRef.current || !hasMore || error) {\n return;\n }\n\n loadingRef.current = true;\n\n try {\n await loadMore();\n } catch (err) {\n const errorObj = err instanceof Error ? err : new Error('Unknown error');\n onError?.(errorObj);\n } finally {\n loadingRef.current = false;\n }\n }, [hasMore, error, loadMore, onError]);\n\n // Expose test trigger for test mode\n useEffect(() => {\n if (testMode && testTriggerRef) {\n testTriggerRef.current = handleLoadMore;\n }\n\n return () => {\n if (testMode && testTriggerRef) {\n testTriggerRef.current = undefined;\n }\n };\n }, [testMode, testTriggerRef, handleLoadMore]);\n\n useEffect(() => {\n if (isIntersecting && hasMore && !loading && !error) {\n handleLoadMore();\n }\n }, [isIntersecting, hasMore, loading, error, handleLoadMore]);\n};\n","import { Alert, Box, CircularProgress, Typography } from '@mui/material';\nimport React from 'react';\n\nimport type { InfiniteScrollProps } from './InfiniteScroll.types';\n\ntype Variant = NonNullable<InfiniteScrollProps['variant']>;\n\nconst CENTERED = {\n display: 'flex',\n justifyContent: 'center',\n alignItems: 'center',\n p: 2,\n} as const;\n\nexport const DefaultLoader: React.FC<{ loadingText: string }> = ({ loadingText }) => (\n <Box sx={CENTERED}>\n <CircularProgress size={24} />\n <Typography variant=\"body2\" sx={{ ml: 1 }}>\n {loadingText}\n </Typography>\n </Box>\n);\n\nexport const DefaultEndMessage: React.FC<{ endText: string }> = ({ endText }) => (\n <Box sx={CENTERED}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {endText}\n </Typography>\n </Box>\n);\n\nexport const DefaultError: React.FC<{ error: Error }> = ({ error }) => (\n <Alert severity=\"error\" sx={{ m: 2 }}>\n {error.message || 'An error occurred while loading more items'}\n </Alert>\n);\n\n/**\n * `horizontal` scrolls along a row; `reverse` stacks bottom-up, for a chat log\n * that loads older messages as you scroll away from the newest.\n */\nexport const containerStyles = (variant: Variant, width?: number | string) => {\n const horizontal = variant === 'horizontal';\n const base = {\n width: horizontal ? width || '100%' : '100%',\n height: horizontal ? '100%' : 'auto',\n overflowX: horizontal ? 'auto' : 'hidden',\n overflowY: horizontal ? 'hidden' : 'visible',\n display: 'flex',\n flexDirection: horizontal ? 'row' : 'column',\n };\n\n return variant === 'reverse' ? { ...base, flexDirection: 'column-reverse' } : base;\n};\n"],"mappings":"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","names":["Box","jsx","jsxs","Box"]}
|
|
@@ -250,7 +250,7 @@ var usePwaInstall = (options = {}) => {
|
|
|
250
250
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
251
251
|
var StyledInstallPrompt = styled(Box)(({ theme }) => installPromptStyles(theme));
|
|
252
252
|
var testIdFor = (base, suffix) => base ? `${base}-${suffix}` : `install-prompt-${suffix}`;
|
|
253
|
-
var IosInstructions = ({ instructions, dataTestId }) => /* @__PURE__ */ jsx(
|
|
253
|
+
var IosInstructions = ({ copy, instructions, dataTestId }) => /* @__PURE__ */ jsx(
|
|
254
254
|
Typography,
|
|
255
255
|
{
|
|
256
256
|
className: "install-prompt-description",
|
|
@@ -259,13 +259,15 @@ var IosInstructions = ({ instructions, dataTestId }) => /* @__PURE__ */ jsx(
|
|
|
259
259
|
"data-testid": testIdFor(dataTestId, "ios-instructions"),
|
|
260
260
|
sx: { display: "flex", alignItems: "center", gap: 0.5, flexWrap: "wrap" },
|
|
261
261
|
children: instructions ?? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
262
|
+
copy.iosTapBefore,
|
|
263
|
+
" ",
|
|
264
|
+
/* @__PURE__ */ jsx(IosShare, { fontSize: "small", "aria-label": copy.shareLabel }),
|
|
265
|
+
" ",
|
|
266
|
+
copy.iosTapAfter
|
|
265
267
|
] })
|
|
266
268
|
}
|
|
267
269
|
);
|
|
268
|
-
var InstallPromptBody = ({ icon, title, description, iosInstructions, platform, dataTestId }) => /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
270
|
+
var InstallPromptBody = ({ copy, icon, title, description, iosInstructions, platform, dataTestId }) => /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
269
271
|
/* @__PURE__ */ jsx(Box, { className: "install-prompt-icon", "aria-hidden": "true", children: icon ?? /* @__PURE__ */ jsx(InstallMobile, {}) }),
|
|
270
272
|
/* @__PURE__ */ jsxs(Box, { className: "install-prompt-content", children: [
|
|
271
273
|
/* @__PURE__ */ jsx(
|
|
@@ -288,7 +290,7 @@ var InstallPromptBody = ({ icon, title, description, iosInstructions, platform,
|
|
|
288
290
|
children: description
|
|
289
291
|
}
|
|
290
292
|
),
|
|
291
|
-
platform === "ios" && /* @__PURE__ */ jsx(IosInstructions, { instructions: iosInstructions, dataTestId })
|
|
293
|
+
platform === "ios" && /* @__PURE__ */ jsx(IosInstructions, { copy, instructions: iosInstructions, dataTestId })
|
|
292
294
|
] })
|
|
293
295
|
] });
|
|
294
296
|
var InstallPromptActions = ({ platform, installLabel, dismissLabel, onInstallClick, onDismissClick, dataTestId }) => /* @__PURE__ */ jsxs(Box, { className: "install-prompt-actions", children: [
|
|
@@ -315,11 +317,9 @@ var InstallPromptActions = ({ platform, installLabel, dismissLabel, onInstallCli
|
|
|
315
317
|
] });
|
|
316
318
|
var InstallPrompt = React.forwardRef(
|
|
317
319
|
({
|
|
318
|
-
|
|
320
|
+
copy,
|
|
319
321
|
description,
|
|
320
|
-
installLabel = "Install",
|
|
321
322
|
iosInstructions,
|
|
322
|
-
dismissLabel = "Dismiss install prompt",
|
|
323
323
|
icon,
|
|
324
324
|
onInstall,
|
|
325
325
|
onDismiss,
|
|
@@ -349,7 +349,7 @@ var InstallPrompt = React.forwardRef(
|
|
|
349
349
|
ref,
|
|
350
350
|
className,
|
|
351
351
|
role: "region",
|
|
352
|
-
"aria-label": title,
|
|
352
|
+
"aria-label": copy.title,
|
|
353
353
|
"data-testid": dataTestId,
|
|
354
354
|
...props,
|
|
355
355
|
children: [
|
|
@@ -357,7 +357,8 @@ var InstallPrompt = React.forwardRef(
|
|
|
357
357
|
InstallPromptBody,
|
|
358
358
|
{
|
|
359
359
|
icon,
|
|
360
|
-
|
|
360
|
+
copy,
|
|
361
|
+
title: copy.title,
|
|
361
362
|
description,
|
|
362
363
|
iosInstructions,
|
|
363
364
|
platform,
|
|
@@ -368,8 +369,8 @@ var InstallPrompt = React.forwardRef(
|
|
|
368
369
|
InstallPromptActions,
|
|
369
370
|
{
|
|
370
371
|
platform,
|
|
371
|
-
installLabel,
|
|
372
|
-
dismissLabel,
|
|
372
|
+
installLabel: copy.installLabel,
|
|
373
|
+
dismissLabel: copy.dismissLabel,
|
|
373
374
|
onInstallClick: handleInstall,
|
|
374
375
|
onDismissClick: handleDismiss,
|
|
375
376
|
dataTestId
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/utility/InstallPrompt/InstallPrompt.tsx","../../src/components/utility/InstallPrompt/InstallPrompt.styles.ts","../../src/components/utility/InstallPrompt/usePwaInstall.ts","../../src/components/utility/InstallPrompt/InstallPrompt.earlyCapture.ts","../../src/components/utility/InstallPrompt/InstallPrompt.helpers.ts"],"sourcesContent":["import Close from '@mui/icons-material/Close';\nimport InstallMobile from '@mui/icons-material/InstallMobile';\nimport IosShare from '@mui/icons-material/IosShare';\nimport { Box, Button, IconButton, Typography } from '@mui/material';\nimport { styled } from '@mui/material';\nimport React from 'react';\n\nimport { installPromptStyles } from './InstallPrompt.styles';\nimport type { InstallPlatform, InstallPromptProps } from './InstallPrompt.types';\nimport { usePwaInstall } from './usePwaInstall';\n\nconst StyledInstallPrompt = styled(Box)(({ theme }) => installPromptStyles(theme));\n\nconst testIdFor = (base: string | undefined, suffix: string) =>\n base ? `${base}-${suffix}` : `install-prompt-${suffix}`;\n\n/**\n * The iOS branch renders instructions rather than a button, because Safari\n * exposes no API to open its own Add to Home Screen sheet. A button here would\n * be a control that does nothing when pressed.\n */\nconst IosInstructions: React.FC<{\n instructions?: React.ReactNode;\n dataTestId?: string;\n}> = ({ instructions, dataTestId }) => (\n <Typography\n className=\"install-prompt-description\"\n variant=\"body2\"\n component=\"div\"\n data-testid={testIdFor(dataTestId, 'ios-instructions')}\n sx={{ display: 'flex', alignItems: 'center', gap: 0.5, flexWrap: 'wrap' }}\n >\n {instructions ?? (\n <>\n Tap <IosShare fontSize=\"small\" aria-label=\"Share\" /> then "Add to Home Screen"\n </>\n )}\n </Typography>\n);\n\nconst InstallPromptBody: React.FC<{\n icon?: React.ReactNode;\n title: string;\n description?: string;\n iosInstructions?: React.ReactNode;\n platform: InstallPlatform;\n dataTestId?: string;\n}> = ({ icon, title, description, iosInstructions, platform, dataTestId }) => (\n <>\n <Box className=\"install-prompt-icon\" aria-hidden=\"true\">\n {icon ?? <InstallMobile />}\n </Box>\n\n <Box className=\"install-prompt-content\">\n <Typography\n className=\"install-prompt-title\"\n variant=\"subtitle2\"\n component=\"div\"\n data-testid={testIdFor(dataTestId, 'title')}\n >\n {title}\n </Typography>\n\n {description && (\n <Typography\n className=\"install-prompt-description\"\n variant=\"body2\"\n component=\"div\"\n data-testid={testIdFor(dataTestId, 'description')}\n >\n {description}\n </Typography>\n )}\n\n {platform === 'ios' && (\n <IosInstructions instructions={iosInstructions} dataTestId={dataTestId} />\n )}\n </Box>\n </>\n);\n\nconst InstallPromptActions: React.FC<{\n platform: InstallPlatform;\n installLabel: string;\n dismissLabel: string;\n onInstallClick: () => void;\n onDismissClick: () => void;\n dataTestId?: string;\n}> = ({ platform, installLabel, dismissLabel, onInstallClick, onDismissClick, dataTestId }) => (\n <Box className=\"install-prompt-actions\">\n {platform === 'prompt' && (\n <Button\n size=\"small\"\n variant=\"contained\"\n onClick={onInstallClick}\n data-testid={testIdFor(dataTestId, 'install')}\n >\n {installLabel}\n </Button>\n )}\n\n <IconButton\n size=\"small\"\n onClick={onDismissClick}\n aria-label={dismissLabel}\n data-testid={testIdFor(dataTestId, 'dismiss')}\n >\n <Close fontSize=\"small\" />\n </IconButton>\n </Box>\n);\n\n/**\n * A dismissible invitation to install the app, rendered only when the browser\n * is actually able to install it and the user has not recently said no.\n *\n * Returns `null` in every other case — already installed, dismissed, or a\n * browser with no install route at all — so it is safe to mount unconditionally\n * in a layout rather than gating it at the call site.\n *\n * The copy defaults to English and every string is a prop: this package stays\n * locale-neutral and the consuming app supplies its own wording.\n */\nexport const InstallPrompt = React.forwardRef<HTMLDivElement, InstallPromptProps>(\n (\n {\n title = 'Install this app',\n description,\n installLabel = 'Install',\n iosInstructions,\n dismissLabel = 'Dismiss install prompt',\n icon,\n onInstall,\n onDismiss,\n storageKey,\n dismissForDays,\n className,\n 'data-testid': dataTestId = 'install-prompt',\n ...props\n },\n ref,\n ) => {\n const { canInstall, platform, promptInstall, dismiss } = usePwaInstall({\n storageKey,\n dismissForDays,\n });\n\n const handleInstall = React.useCallback(() => {\n void promptInstall().then((outcome) => onInstall?.(outcome));\n }, [promptInstall, onInstall]);\n\n const handleDismiss = React.useCallback(() => {\n dismiss();\n onDismiss?.();\n }, [dismiss, onDismiss]);\n\n if (!canInstall) {\n return null;\n }\n\n return (\n <StyledInstallPrompt\n ref={ref}\n className={className}\n role=\"region\"\n aria-label={title}\n data-testid={dataTestId}\n {...props}\n >\n <InstallPromptBody\n icon={icon}\n title={title}\n description={description}\n iosInstructions={iosInstructions}\n platform={platform}\n dataTestId={dataTestId}\n />\n\n <InstallPromptActions\n platform={platform}\n installLabel={installLabel}\n dismissLabel={dismissLabel}\n onInstallClick={handleInstall}\n onDismissClick={handleDismiss}\n dataTestId={dataTestId}\n />\n </StyledInstallPrompt>\n );\n },\n);\n\nInstallPrompt.displayName = 'InstallPrompt';\n","import { alpha } from '@mui/material';\nimport type { CSSObject, Theme } from '@mui/material';\n\nexport const installPromptStyles = (theme: Theme): CSSObject => ({\n display: 'flex',\n alignItems: 'flex-start',\n gap: theme.spacing(1.5),\n padding: theme.spacing(1.5, 2),\n borderRadius: theme.spacing(1),\n backgroundColor: alpha(theme.palette.primary.main, 0.08),\n border: `1px solid ${alpha(theme.palette.primary.main, 0.2)}`,\n\n '.install-prompt-icon': {\n flexShrink: 0,\n marginTop: theme.spacing(0.25),\n display: 'flex',\n color: theme.palette.primary.main,\n },\n\n '.install-prompt-content': {\n flex: 1,\n minWidth: 0,\n },\n\n '.install-prompt-title': {\n fontWeight: 600,\n },\n\n '.install-prompt-description': {\n opacity: 0.9,\n },\n\n '.install-prompt-actions': {\n display: 'flex',\n flexShrink: 0,\n alignItems: 'center',\n gap: theme.spacing(1),\n },\n\n // The actions drop to their own full-width row on a phone, where this\n // component does most of its work.\n [theme.breakpoints.down('sm')]: {\n flexWrap: 'wrap',\n\n '.install-prompt-actions': {\n width: '100%',\n marginTop: theme.spacing(1),\n },\n },\n});\n","import { useCallback, useEffect, useRef, useState } from 'react';\n\nimport { PWA_INSTALL_AVAILABLE_EVENT, readPwaInstallStash } from './InstallPrompt.earlyCapture';\nimport {\n DEFAULT_DISMISS_DAYS,\n DEFAULT_STORAGE_KEY,\n isDismissalActive,\n isStandaloneDisplay,\n readDismissedAt,\n resolveInstallPlatform,\n writeDismissedAt,\n} from './InstallPrompt.helpers';\nimport type {\n BeforeInstallPromptEvent,\n InstallOutcome,\n InstallPlatform,\n PwaInstallState,\n UsePwaInstallOptions,\n} from './InstallPrompt.types';\n\n/**\n * Headless PWA install machinery.\n *\n * A manifest and a service worker make a site installABLE; they do not make it\n * install. Chromium announces its willingness by firing `beforeinstallprompt`\n * and then does nothing further on its own beyond a small address-bar icon most\n * users never notice. Unless a page calls `preventDefault()` on that event and\n * keeps it, the handle is gone and the app can never ask.\n *\n * That is the whole reason this hook exists. It captures the event, tracks the\n * three states that should suppress an install invitation (already installed,\n * recently dismissed, browser cannot install), and hands back a\n * gesture-callable `promptInstall`.\n *\n * Every environment probe runs in an effect rather than in a state initialiser:\n * touching `window` during render breaks SSR and desynchronises hydration, so\n * the first paint deliberately reports `canInstall: false` and corrects itself\n * on mount.\n */\n\n/** Tracks whether the page is running as an installed app. */\nconst useInstalledState = (onInstalled?: () => void) => {\n const [isInstalled, setIsInstalled] = useState(false);\n\n // Held in a ref, and refreshed in its own effect rather than during render, so\n // that a caller passing an inline callback does not re-subscribe the listener\n // on every render.\n const onInstalledRef = useRef(onInstalled);\n useEffect(() => {\n onInstalledRef.current = onInstalled;\n }, [onInstalled]);\n\n useEffect(() => {\n // `appinstalled` can also land before the app mounts, so the stash is\n // consulted for the same reason the deferred event is.\n setIsInstalled(isStandaloneDisplay() || readPwaInstallStash()?.installedAt != null);\n\n const markInstalled = () => {\n setIsInstalled(true);\n onInstalledRef.current?.();\n };\n\n window.addEventListener('appinstalled', markInstalled);\n return () => window.removeEventListener('appinstalled', markInstalled);\n }, []);\n\n return isInstalled;\n};\n\n/**\n * Holds the deferred `beforeinstallprompt` event.\n *\n * The stash written by `capturePwaInstallEvent` is the PRIMARY source, not a\n * fallback. Chromium fires the event once, during initial page load; a\n * listener attached in an effect runs after hydration and, when the component\n * is code-split, after a download that may not happen until a later route. In\n * a real browser it essentially never catches the event, and the event is not\n * reissued on request — so a hook that relies on its own listener reports\n * \"cannot install\" forever on the one platform that can.\n *\n * The live listeners below therefore cover only the narrow case they can\n * actually serve: a browser re-evaluating installability while the app is\n * already running, and a host that never wired the capture at all.\n */\nconst useDeferredPrompt = () => {\n const deferredRef = useRef<BeforeInstallPromptEvent | null>(null);\n const [promptReady, setPromptReady] = useState(false);\n\n useEffect(() => {\n const hold = (event: BeforeInstallPromptEvent) => {\n deferredRef.current = event;\n setPromptReady(true);\n };\n\n // Whatever fired before this component existed.\n const held = readPwaInstallStash()?.event;\n if (held) hold(held);\n\n // The capture rang the bell after mount: adopt what it is holding.\n const adopt = () => {\n const fresh = readPwaInstallStash()?.event;\n if (fresh) hold(fresh);\n };\n\n const capture = (event: Event) => {\n // Without preventDefault the browser owns the event and the handle dies\n // with it. The capture has normally done this already; repeating it is\n // harmless and keeps the hook correct on a host that skipped wiring.\n event.preventDefault();\n hold(event as BeforeInstallPromptEvent);\n };\n\n window.addEventListener(PWA_INSTALL_AVAILABLE_EVENT, adopt);\n window.addEventListener('beforeinstallprompt', capture);\n return () => {\n window.removeEventListener(PWA_INSTALL_AVAILABLE_EVENT, adopt);\n window.removeEventListener('beforeinstallprompt', capture);\n };\n }, []);\n\n const clearPrompt = useCallback(() => {\n deferredRef.current = null;\n setPromptReady(false);\n }, []);\n\n return { deferredRef, promptReady, clearPrompt };\n};\n\n/** Reads and writes the \"don't ask again for a while\" record. */\nconst useDismissal = (storageKey: string, dismissForDays: number) => {\n const [dismissed, setDismissed] = useState(false);\n\n useEffect(() => {\n const dismissedAt = readDismissedAt(storageKey);\n setDismissed(isDismissalActive(dismissedAt, dismissForDays, Date.now()));\n }, [storageKey, dismissForDays]);\n\n const persistDismissal = useCallback(() => {\n writeDismissedAt(storageKey, Date.now());\n setDismissed(true);\n }, [storageKey]);\n\n return { dismissed, persistDismissal };\n};\n\n/** Resolves the install route once the deferred-prompt state is known. */\nconst useInstallPlatform = (promptReady: boolean): InstallPlatform => {\n const [platform, setPlatform] = useState<InstallPlatform>('unsupported');\n\n useEffect(() => {\n setPlatform(resolveInstallPlatform(promptReady));\n }, [promptReady]);\n\n return platform;\n};\n\nexport const usePwaInstall = (options: UsePwaInstallOptions = {}): PwaInstallState => {\n const {\n storageKey = DEFAULT_STORAGE_KEY,\n dismissForDays = DEFAULT_DISMISS_DAYS,\n onInstalled,\n } = options;\n\n const isInstalled = useInstalledState(onInstalled);\n const { deferredRef, promptReady, clearPrompt } = useDeferredPrompt();\n const { dismissed, persistDismissal } = useDismissal(storageKey, dismissForDays);\n const platform = useInstallPlatform(promptReady);\n\n const promptInstall = useCallback(async (): Promise<InstallOutcome> => {\n const deferred = deferredRef.current;\n if (!deferred) return 'unavailable';\n\n await deferred.prompt();\n const { outcome } = await deferred.userChoice;\n\n // Single-use handle: spent whichever way the user answered. Chromium emits a\n // fresh event if the app is still installable, so holding this one would\n // only mean calling prompt() twice on a dead event.\n clearPrompt();\n\n // Declining the browser's own dialog is a real \"no\" — record it, or the\n // affordance reappears on the next render and badgers the user.\n if (outcome === 'dismissed') persistDismissal();\n\n return outcome;\n }, [deferredRef, clearPrompt, persistDismissal]);\n\n // iOS has no event to wait for, so its affordance is gated on the platform\n // probe alone; everywhere else a live captured prompt is the prerequisite.\n const canInstall = !isInstalled && !dismissed && (platform === 'ios' || promptReady);\n\n return {\n canInstall,\n platform,\n isInstalled,\n promptInstall,\n dismiss: persistDismissal,\n };\n};\n","import type { BeforeInstallPromptEvent, PwaInstallStash } from './InstallPrompt.types';\n\n/**\n * Holds `beforeinstallprompt` from before the app exists.\n *\n * `usePwaInstall` cannot attach its own listener early enough, and no React\n * hook can. Chromium fires the event once, during initial page load, the\n * moment it has a manifest and a registered worker — which in a real app is\n * before hydration, and often before the chunk containing the component has\n * even been downloaded. A listener registered in an effect is not late by\n * milliseconds; it can be late by entire navigations. Nothing calls\n * `preventDefault()` in time, the browser keeps the event, and the page can\n * never ask again.\n *\n * So the capture has to live outside React, and the host has to install it as\n * the very first thing the document does. Two supported ways, in order of\n * reliability:\n *\n * 1. An inline `<script>` in `<head>`, which beats a deferred module bundle:\n *\n * <script>\n * window.addEventListener('beforeinstallprompt', function (e) {\n * e.preventDefault();\n * var s = (window.__pwaInstall = window.__pwaInstall || {});\n * s.event = e; s.firedAt = Date.now();\n * window.dispatchEvent(new Event('pwa-install-available'));\n * });\n * </script>\n *\n * 2. `capturePwaInstallEvent()` as the first statement of the entry module,\n * before `createRoot`. Simpler to wire and correct in most apps, but a\n * module is still deferred, so a very early event can outrun it.\n *\n * Both write the same shape, so the hook does not care which was used.\n */\n\n/** Property on `window` holding the captured event. Shared with the inline snippet. */\nexport const PWA_INSTALL_STASH_KEY = '__pwaInstall';\n\n/** Fired after a capture, so an app already running can adopt the event. */\nexport const PWA_INSTALL_AVAILABLE_EVENT = 'pwa-install-available';\n\ntype StashHost = Window & { [PWA_INSTALL_STASH_KEY]?: PwaInstallStash };\n\n/** The stash, or `null` when no capture ever ran (SSR, or a host that skipped wiring). */\nexport const readPwaInstallStash = (): PwaInstallStash | null => {\n if (typeof window === 'undefined') return null;\n return (window as StashHost)[PWA_INSTALL_STASH_KEY] ?? null;\n};\n\nconst ensureStash = (): PwaInstallStash => {\n const host = window as StashHost;\n host[PWA_INSTALL_STASH_KEY] ??= { event: null, firedAt: null, installedAt: null };\n return host[PWA_INSTALL_STASH_KEY];\n};\n\n/**\n * The teardown for the capture currently running, so `resetPwaInstallStash`\n * can unwind completely. Held here rather than on the stash because it must\n * survive the stash being deleted, and because it must never be serialised\n * into the diagnostic payload.\n */\nlet activeTeardown: (() => void) | null = null;\n\n/**\n * Starts capturing. Returns a teardown, and is safe to call twice — a second\n * call is a no-op rather than a duplicate listener, so a host that wires both\n * the inline snippet and this function does not double-handle the event.\n */\nexport const capturePwaInstallEvent = (): (() => void) => {\n if (typeof window === 'undefined') return () => undefined;\n\n const stash = ensureStash();\n if (stash.capturing === true) return () => undefined;\n stash.capturing = true;\n\n const onBeforeInstall = (event: Event): void => {\n // The line the whole component depends on. Without it the browser owns the\n // event, shows at most its own mini-infobar, and discards the handle.\n event.preventDefault();\n stash.event = event as BeforeInstallPromptEvent;\n stash.firedAt = Date.now();\n window.dispatchEvent(new Event(PWA_INSTALL_AVAILABLE_EVENT));\n };\n\n const onInstalled = (): void => {\n stash.installedAt = Date.now();\n stash.event = null;\n };\n\n window.addEventListener('beforeinstallprompt', onBeforeInstall);\n window.addEventListener('appinstalled', onInstalled);\n\n const teardown = () => {\n window.removeEventListener('beforeinstallprompt', onBeforeInstall);\n window.removeEventListener('appinstalled', onInstalled);\n stash.capturing = false;\n if (activeTeardown === teardown) activeTeardown = null;\n };\n\n activeTeardown = teardown;\n return teardown;\n};\n\n/**\n * Returns the page to its pre-capture state: listeners removed, stash gone.\n *\n * Exists for tests and stories, which need a clean page per case. It unwinds\n * the listeners as well as the data, because a leaked capture from a previous\n * case would go on suppressing events that the next case expects to observe.\n */\nexport const resetPwaInstallStash = (): void => {\n if (typeof window === 'undefined') return;\n activeTeardown?.();\n activeTeardown = null;\n delete (window as StashHost)[PWA_INSTALL_STASH_KEY];\n};\n","import type { InstallPlatform } from './InstallPrompt.types';\n\n/**\n * Pure environment probes and dismissal storage for `usePwaInstall`.\n *\n * Everything here is a plain function taking its clock and its storage from the\n * caller where that matters, so the hook's behaviour is testable without\n * faking timers or a browser.\n */\n\nexport const DEFAULT_STORAGE_KEY = 'pwa-install-dismissed';\nexport const DEFAULT_DISMISS_DAYS = 30;\n\nconst MS_PER_DAY = 86_400_000;\nconst STANDALONE_QUERY = '(display-mode: standalone)';\n\n/** iOS Safari predates `display-mode` and exposes this instead. */\ninterface IosNavigator extends Navigator {\n standalone?: boolean;\n}\n\n/** Guard for SSR and for jsdom runs where `matchMedia` is not implemented. */\nconst hasWindow = (): boolean => typeof window !== 'undefined';\n\n/**\n * True when the page is already running as an installed app.\n *\n * Both checks are needed: `display-mode: standalone` is the standard signal,\n * but iOS never implemented it for home-screen launches and reports\n * `navigator.standalone` instead. Miss the iOS branch and the installed app\n * nags its own users to install it again.\n */\nexport const isStandaloneDisplay = (): boolean => {\n if (!hasWindow()) return false;\n if ((window.navigator as IosNavigator).standalone === true) return true;\n return window.matchMedia?.(STANDALONE_QUERY).matches === true;\n};\n\n/**\n * True for Safari on iOS/iPadOS — the one browser that can install but cannot\n * be asked programmatically.\n *\n * Two traps handled here. iPadOS 13+ reports a Macintosh user agent, so the\n * touch-point count is what separates an iPad from a desktop; and Chrome,\n * Firefox and Edge on iOS are Safari underneath but offer no Add to Home\n * Screen at all, so telling their users to tap Share is a dead end.\n *\n * Kept parameterised so the agent table can be tested without reassigning\n * `navigator`, which leaks across test files when it is not restored.\n */\nexport const isIosSafariAgent = (userAgent: string, maxTouchPoints: number): boolean => {\n const isIosDevice =\n /iPad|iPhone|iPod/.test(userAgent) ||\n (userAgent.includes('Macintosh') && maxTouchPoints > 1);\n\n if (!isIosDevice) return false;\n\n return !/CriOS|FxiOS|EdgiOS|OPiOS/.test(userAgent);\n};\n\n/** `isIosSafariAgent` against the live browser. Internal — callers use `resolveInstallPlatform`. */\nconst isIosSafari = (): boolean => {\n if (!hasWindow()) return false;\n\n const { userAgent, maxTouchPoints } = window.navigator;\n return isIosSafariAgent(userAgent, maxTouchPoints);\n};\n\n/**\n * Reads the stored dismissal timestamp.\n *\n * Storage access is wrapped because it THROWS rather than returning null in\n * Safari private mode and wherever cookies are blocked — an uncaught throw\n * here would take down the storefront on the very browsers this component\n * exists to serve.\n */\nexport const readDismissedAt = (storageKey: string): number | null => {\n if (!hasWindow()) return null;\n\n try {\n const raw = window.localStorage.getItem(storageKey);\n if (!raw) return null;\n\n const parsed = Number.parseInt(raw, 10);\n return Number.isFinite(parsed) ? parsed : null;\n } catch {\n return null;\n }\n};\n\n/** Persists a dismissal. Silently a no-op when storage is unavailable. */\nexport const writeDismissedAt = (storageKey: string, at: number): void => {\n if (!hasWindow()) return;\n\n try {\n window.localStorage.setItem(storageKey, String(at));\n } catch {\n // Storage blocked — the dismissal lasts for this page view only.\n }\n};\n\n/**\n * Whether a stored dismissal still suppresses the prompt.\n *\n * `dismissForDays: 0` means forever. A timestamp in the future (a device whose\n * clock was wound back) is treated as active rather than expired, so a bad\n * clock cannot turn into a prompt on every page view.\n */\nexport const isDismissalActive = (\n dismissedAt: number | null,\n dismissForDays: number,\n now: number,\n): boolean => {\n if (dismissedAt === null) return false;\n if (dismissForDays <= 0) return true;\n\n return now - dismissedAt < dismissForDays * MS_PER_DAY;\n};\n\n/** Resolves which install route this browser offers. */\nexport const resolveInstallPlatform = (hasDeferredPrompt: boolean): InstallPlatform => {\n if (hasDeferredPrompt) return 'prompt';\n return isIosSafari() ? 'ios' : 'unsupported';\n};\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/components/utility/InstallPrompt/InstallPrompt.tsx","../../src/components/utility/InstallPrompt/InstallPrompt.styles.ts","../../src/components/utility/InstallPrompt/usePwaInstall.ts","../../src/components/utility/InstallPrompt/InstallPrompt.earlyCapture.ts","../../src/components/utility/InstallPrompt/InstallPrompt.helpers.ts"],"sourcesContent":["import Close from '@mui/icons-material/Close';\nimport InstallMobile from '@mui/icons-material/InstallMobile';\nimport IosShare from '@mui/icons-material/IosShare';\nimport { Box, Button, IconButton, Typography } from '@mui/material';\nimport { styled } from '@mui/material';\nimport React from 'react';\n\nimport { installPromptStyles } from './InstallPrompt.styles';\nimport type { InstallPlatform, InstallPromptProps } from './InstallPrompt.types';\nimport { usePwaInstall } from './usePwaInstall';\nimport type { InstallPromptCopy } from '../../../copy';\n\nconst StyledInstallPrompt = styled(Box)(({ theme }) => installPromptStyles(theme));\n\nconst testIdFor = (base: string | undefined, suffix: string) =>\n base ? `${base}-${suffix}` : `install-prompt-${suffix}`;\n\n/**\n * The iOS branch renders instructions rather than a button, because Safari\n * exposes no API to open its own Add to Home Screen sheet. A button here would\n * be a control that does nothing when pressed.\n */\nconst IosInstructions: React.FC<{\n copy: InstallPromptCopy;\n instructions?: React.ReactNode;\n dataTestId?: string;\n}> = ({ copy, instructions, dataTestId }) => (\n <Typography\n className=\"install-prompt-description\"\n variant=\"body2\"\n component=\"div\"\n data-testid={testIdFor(dataTestId, 'ios-instructions')}\n sx={{ display: 'flex', alignItems: 'center', gap: 0.5, flexWrap: 'wrap' }}\n >\n {instructions ?? (\n <>\n {copy.iosTapBefore} <IosShare fontSize=\"small\" aria-label={copy.shareLabel} />{' '}\n {copy.iosTapAfter}\n </>\n )}\n </Typography>\n);\n\nconst InstallPromptBody: React.FC<{\n copy: InstallPromptCopy;\n icon?: React.ReactNode;\n title: string;\n description?: string;\n iosInstructions?: React.ReactNode;\n platform: InstallPlatform;\n dataTestId?: string;\n}> = ({ copy, icon, title, description, iosInstructions, platform, dataTestId }) => (\n <>\n <Box className=\"install-prompt-icon\" aria-hidden=\"true\">\n {icon ?? <InstallMobile />}\n </Box>\n\n <Box className=\"install-prompt-content\">\n <Typography\n className=\"install-prompt-title\"\n variant=\"subtitle2\"\n component=\"div\"\n data-testid={testIdFor(dataTestId, 'title')}\n >\n {title}\n </Typography>\n\n {description && (\n <Typography\n className=\"install-prompt-description\"\n variant=\"body2\"\n component=\"div\"\n data-testid={testIdFor(dataTestId, 'description')}\n >\n {description}\n </Typography>\n )}\n\n {platform === 'ios' && (\n <IosInstructions copy={copy} instructions={iosInstructions} dataTestId={dataTestId} />\n )}\n </Box>\n </>\n);\n\nconst InstallPromptActions: React.FC<{\n platform: InstallPlatform;\n installLabel: string;\n dismissLabel: string;\n onInstallClick: () => void;\n onDismissClick: () => void;\n dataTestId?: string;\n}> = ({ platform, installLabel, dismissLabel, onInstallClick, onDismissClick, dataTestId }) => (\n <Box className=\"install-prompt-actions\">\n {platform === 'prompt' && (\n <Button\n size=\"small\"\n variant=\"contained\"\n onClick={onInstallClick}\n data-testid={testIdFor(dataTestId, 'install')}\n >\n {installLabel}\n </Button>\n )}\n\n <IconButton\n size=\"small\"\n onClick={onDismissClick}\n aria-label={dismissLabel}\n data-testid={testIdFor(dataTestId, 'dismiss')}\n >\n <Close fontSize=\"small\" />\n </IconButton>\n </Box>\n);\n\n/**\n * A dismissible invitation to install the app, rendered only when the browser\n * is actually able to install it and the user has not recently said no.\n *\n * Returns `null` in every other case — already installed, dismissed, or a\n * browser with no install route at all — so it is safe to mount unconditionally\n * in a layout rather than gating it at the call site.\n *\n * Every string is REQUIRED host config (`copy`): this package stays\n * locale-neutral and the consuming app supplies its own wording. There is no\n * English default to fall through to, so a missing word is a typecheck failure\n * rather than an English sentence in a pt-BR store.\n */\nexport const InstallPrompt = React.forwardRef<HTMLDivElement, InstallPromptProps>(\n (\n {\n copy,\n description,\n iosInstructions,\n icon,\n onInstall,\n onDismiss,\n storageKey,\n dismissForDays,\n className,\n 'data-testid': dataTestId = 'install-prompt',\n ...props\n },\n ref,\n ) => {\n const { canInstall, platform, promptInstall, dismiss } = usePwaInstall({\n storageKey,\n dismissForDays,\n });\n\n const handleInstall = React.useCallback(() => {\n void promptInstall().then((outcome) => onInstall?.(outcome));\n }, [promptInstall, onInstall]);\n\n const handleDismiss = React.useCallback(() => {\n dismiss();\n onDismiss?.();\n }, [dismiss, onDismiss]);\n\n if (!canInstall) {\n return null;\n }\n\n return (\n <StyledInstallPrompt\n ref={ref}\n className={className}\n role=\"region\"\n aria-label={copy.title}\n data-testid={dataTestId}\n {...props}\n >\n <InstallPromptBody\n icon={icon}\n copy={copy}\n title={copy.title}\n description={description}\n iosInstructions={iosInstructions}\n platform={platform}\n dataTestId={dataTestId}\n />\n\n <InstallPromptActions\n platform={platform}\n installLabel={copy.installLabel}\n dismissLabel={copy.dismissLabel}\n onInstallClick={handleInstall}\n onDismissClick={handleDismiss}\n dataTestId={dataTestId}\n />\n </StyledInstallPrompt>\n );\n },\n);\n\nInstallPrompt.displayName = 'InstallPrompt';\n","import { alpha } from '@mui/material';\nimport type { CSSObject, Theme } from '@mui/material';\n\nexport const installPromptStyles = (theme: Theme): CSSObject => ({\n display: 'flex',\n alignItems: 'flex-start',\n gap: theme.spacing(1.5),\n padding: theme.spacing(1.5, 2),\n borderRadius: theme.spacing(1),\n backgroundColor: alpha(theme.palette.primary.main, 0.08),\n border: `1px solid ${alpha(theme.palette.primary.main, 0.2)}`,\n\n '.install-prompt-icon': {\n flexShrink: 0,\n marginTop: theme.spacing(0.25),\n display: 'flex',\n color: theme.palette.primary.main,\n },\n\n '.install-prompt-content': {\n flex: 1,\n minWidth: 0,\n },\n\n '.install-prompt-title': {\n fontWeight: 600,\n },\n\n '.install-prompt-description': {\n opacity: 0.9,\n },\n\n '.install-prompt-actions': {\n display: 'flex',\n flexShrink: 0,\n alignItems: 'center',\n gap: theme.spacing(1),\n },\n\n // The actions drop to their own full-width row on a phone, where this\n // component does most of its work.\n [theme.breakpoints.down('sm')]: {\n flexWrap: 'wrap',\n\n '.install-prompt-actions': {\n width: '100%',\n marginTop: theme.spacing(1),\n },\n },\n});\n","import { useCallback, useEffect, useRef, useState } from 'react';\n\nimport { PWA_INSTALL_AVAILABLE_EVENT, readPwaInstallStash } from './InstallPrompt.earlyCapture';\nimport {\n DEFAULT_DISMISS_DAYS,\n DEFAULT_STORAGE_KEY,\n isDismissalActive,\n isStandaloneDisplay,\n readDismissedAt,\n resolveInstallPlatform,\n writeDismissedAt,\n} from './InstallPrompt.helpers';\nimport type {\n BeforeInstallPromptEvent,\n InstallOutcome,\n InstallPlatform,\n PwaInstallState,\n UsePwaInstallOptions,\n} from './InstallPrompt.types';\n\n/**\n * Headless PWA install machinery.\n *\n * A manifest and a service worker make a site installABLE; they do not make it\n * install. Chromium announces its willingness by firing `beforeinstallprompt`\n * and then does nothing further on its own beyond a small address-bar icon most\n * users never notice. Unless a page calls `preventDefault()` on that event and\n * keeps it, the handle is gone and the app can never ask.\n *\n * That is the whole reason this hook exists. It captures the event, tracks the\n * three states that should suppress an install invitation (already installed,\n * recently dismissed, browser cannot install), and hands back a\n * gesture-callable `promptInstall`.\n *\n * Every environment probe runs in an effect rather than in a state initialiser:\n * touching `window` during render breaks SSR and desynchronises hydration, so\n * the first paint deliberately reports `canInstall: false` and corrects itself\n * on mount.\n */\n\n/** Tracks whether the page is running as an installed app. */\nconst useInstalledState = (onInstalled?: () => void) => {\n const [isInstalled, setIsInstalled] = useState(false);\n\n // Held in a ref, and refreshed in its own effect rather than during render, so\n // that a caller passing an inline callback does not re-subscribe the listener\n // on every render.\n const onInstalledRef = useRef(onInstalled);\n useEffect(() => {\n onInstalledRef.current = onInstalled;\n }, [onInstalled]);\n\n useEffect(() => {\n // `appinstalled` can also land before the app mounts, so the stash is\n // consulted for the same reason the deferred event is.\n setIsInstalled(isStandaloneDisplay() || readPwaInstallStash()?.installedAt != null);\n\n const markInstalled = () => {\n setIsInstalled(true);\n onInstalledRef.current?.();\n };\n\n window.addEventListener('appinstalled', markInstalled);\n return () => window.removeEventListener('appinstalled', markInstalled);\n }, []);\n\n return isInstalled;\n};\n\n/**\n * Holds the deferred `beforeinstallprompt` event.\n *\n * The stash written by `capturePwaInstallEvent` is the PRIMARY source, not a\n * fallback. Chromium fires the event once, during initial page load; a\n * listener attached in an effect runs after hydration and, when the component\n * is code-split, after a download that may not happen until a later route. In\n * a real browser it essentially never catches the event, and the event is not\n * reissued on request — so a hook that relies on its own listener reports\n * \"cannot install\" forever on the one platform that can.\n *\n * The live listeners below therefore cover only the narrow case they can\n * actually serve: a browser re-evaluating installability while the app is\n * already running, and a host that never wired the capture at all.\n */\nconst useDeferredPrompt = () => {\n const deferredRef = useRef<BeforeInstallPromptEvent | null>(null);\n const [promptReady, setPromptReady] = useState(false);\n\n useEffect(() => {\n const hold = (event: BeforeInstallPromptEvent) => {\n deferredRef.current = event;\n setPromptReady(true);\n };\n\n // Whatever fired before this component existed.\n const held = readPwaInstallStash()?.event;\n if (held) hold(held);\n\n // The capture rang the bell after mount: adopt what it is holding.\n const adopt = () => {\n const fresh = readPwaInstallStash()?.event;\n if (fresh) hold(fresh);\n };\n\n const capture = (event: Event) => {\n // Without preventDefault the browser owns the event and the handle dies\n // with it. The capture has normally done this already; repeating it is\n // harmless and keeps the hook correct on a host that skipped wiring.\n event.preventDefault();\n hold(event as BeforeInstallPromptEvent);\n };\n\n window.addEventListener(PWA_INSTALL_AVAILABLE_EVENT, adopt);\n window.addEventListener('beforeinstallprompt', capture);\n return () => {\n window.removeEventListener(PWA_INSTALL_AVAILABLE_EVENT, adopt);\n window.removeEventListener('beforeinstallprompt', capture);\n };\n }, []);\n\n const clearPrompt = useCallback(() => {\n deferredRef.current = null;\n setPromptReady(false);\n }, []);\n\n return { deferredRef, promptReady, clearPrompt };\n};\n\n/** Reads and writes the \"don't ask again for a while\" record. */\nconst useDismissal = (storageKey: string, dismissForDays: number) => {\n const [dismissed, setDismissed] = useState(false);\n\n useEffect(() => {\n const dismissedAt = readDismissedAt(storageKey);\n setDismissed(isDismissalActive(dismissedAt, dismissForDays, Date.now()));\n }, [storageKey, dismissForDays]);\n\n const persistDismissal = useCallback(() => {\n writeDismissedAt(storageKey, Date.now());\n setDismissed(true);\n }, [storageKey]);\n\n return { dismissed, persistDismissal };\n};\n\n/** Resolves the install route once the deferred-prompt state is known. */\nconst useInstallPlatform = (promptReady: boolean): InstallPlatform => {\n const [platform, setPlatform] = useState<InstallPlatform>('unsupported');\n\n useEffect(() => {\n setPlatform(resolveInstallPlatform(promptReady));\n }, [promptReady]);\n\n return platform;\n};\n\nexport const usePwaInstall = (options: UsePwaInstallOptions = {}): PwaInstallState => {\n const {\n storageKey = DEFAULT_STORAGE_KEY,\n dismissForDays = DEFAULT_DISMISS_DAYS,\n onInstalled,\n } = options;\n\n const isInstalled = useInstalledState(onInstalled);\n const { deferredRef, promptReady, clearPrompt } = useDeferredPrompt();\n const { dismissed, persistDismissal } = useDismissal(storageKey, dismissForDays);\n const platform = useInstallPlatform(promptReady);\n\n const promptInstall = useCallback(async (): Promise<InstallOutcome> => {\n const deferred = deferredRef.current;\n if (!deferred) return 'unavailable';\n\n await deferred.prompt();\n const { outcome } = await deferred.userChoice;\n\n // Single-use handle: spent whichever way the user answered. Chromium emits a\n // fresh event if the app is still installable, so holding this one would\n // only mean calling prompt() twice on a dead event.\n clearPrompt();\n\n // Declining the browser's own dialog is a real \"no\" — record it, or the\n // affordance reappears on the next render and badgers the user.\n if (outcome === 'dismissed') persistDismissal();\n\n return outcome;\n }, [deferredRef, clearPrompt, persistDismissal]);\n\n // iOS has no event to wait for, so its affordance is gated on the platform\n // probe alone; everywhere else a live captured prompt is the prerequisite.\n const canInstall = !isInstalled && !dismissed && (platform === 'ios' || promptReady);\n\n return {\n canInstall,\n platform,\n isInstalled,\n promptInstall,\n dismiss: persistDismissal,\n };\n};\n","import type { BeforeInstallPromptEvent, PwaInstallStash } from './InstallPrompt.types';\n\n/**\n * Holds `beforeinstallprompt` from before the app exists.\n *\n * `usePwaInstall` cannot attach its own listener early enough, and no React\n * hook can. Chromium fires the event once, during initial page load, the\n * moment it has a manifest and a registered worker — which in a real app is\n * before hydration, and often before the chunk containing the component has\n * even been downloaded. A listener registered in an effect is not late by\n * milliseconds; it can be late by entire navigations. Nothing calls\n * `preventDefault()` in time, the browser keeps the event, and the page can\n * never ask again.\n *\n * So the capture has to live outside React, and the host has to install it as\n * the very first thing the document does. Two supported ways, in order of\n * reliability:\n *\n * 1. An inline `<script>` in `<head>`, which beats a deferred module bundle:\n *\n * <script>\n * window.addEventListener('beforeinstallprompt', function (e) {\n * e.preventDefault();\n * var s = (window.__pwaInstall = window.__pwaInstall || {});\n * s.event = e; s.firedAt = Date.now();\n * window.dispatchEvent(new Event('pwa-install-available'));\n * });\n * </script>\n *\n * 2. `capturePwaInstallEvent()` as the first statement of the entry module,\n * before `createRoot`. Simpler to wire and correct in most apps, but a\n * module is still deferred, so a very early event can outrun it.\n *\n * Both write the same shape, so the hook does not care which was used.\n */\n\n/** Property on `window` holding the captured event. Shared with the inline snippet. */\nexport const PWA_INSTALL_STASH_KEY = '__pwaInstall';\n\n/** Fired after a capture, so an app already running can adopt the event. */\nexport const PWA_INSTALL_AVAILABLE_EVENT = 'pwa-install-available';\n\ntype StashHost = Window & { [PWA_INSTALL_STASH_KEY]?: PwaInstallStash };\n\n/** The stash, or `null` when no capture ever ran (SSR, or a host that skipped wiring). */\nexport const readPwaInstallStash = (): PwaInstallStash | null => {\n if (typeof window === 'undefined') return null;\n return (window as StashHost)[PWA_INSTALL_STASH_KEY] ?? null;\n};\n\nconst ensureStash = (): PwaInstallStash => {\n const host = window as StashHost;\n host[PWA_INSTALL_STASH_KEY] ??= { event: null, firedAt: null, installedAt: null };\n return host[PWA_INSTALL_STASH_KEY];\n};\n\n/**\n * The teardown for the capture currently running, so `resetPwaInstallStash`\n * can unwind completely. Held here rather than on the stash because it must\n * survive the stash being deleted, and because it must never be serialised\n * into the diagnostic payload.\n */\nlet activeTeardown: (() => void) | null = null;\n\n/**\n * Starts capturing. Returns a teardown, and is safe to call twice — a second\n * call is a no-op rather than a duplicate listener, so a host that wires both\n * the inline snippet and this function does not double-handle the event.\n */\nexport const capturePwaInstallEvent = (): (() => void) => {\n if (typeof window === 'undefined') return () => undefined;\n\n const stash = ensureStash();\n if (stash.capturing === true) return () => undefined;\n stash.capturing = true;\n\n const onBeforeInstall = (event: Event): void => {\n // The line the whole component depends on. Without it the browser owns the\n // event, shows at most its own mini-infobar, and discards the handle.\n event.preventDefault();\n stash.event = event as BeforeInstallPromptEvent;\n stash.firedAt = Date.now();\n window.dispatchEvent(new Event(PWA_INSTALL_AVAILABLE_EVENT));\n };\n\n const onInstalled = (): void => {\n stash.installedAt = Date.now();\n stash.event = null;\n };\n\n window.addEventListener('beforeinstallprompt', onBeforeInstall);\n window.addEventListener('appinstalled', onInstalled);\n\n const teardown = () => {\n window.removeEventListener('beforeinstallprompt', onBeforeInstall);\n window.removeEventListener('appinstalled', onInstalled);\n stash.capturing = false;\n if (activeTeardown === teardown) activeTeardown = null;\n };\n\n activeTeardown = teardown;\n return teardown;\n};\n\n/**\n * Returns the page to its pre-capture state: listeners removed, stash gone.\n *\n * Exists for tests and stories, which need a clean page per case. It unwinds\n * the listeners as well as the data, because a leaked capture from a previous\n * case would go on suppressing events that the next case expects to observe.\n */\nexport const resetPwaInstallStash = (): void => {\n if (typeof window === 'undefined') return;\n activeTeardown?.();\n activeTeardown = null;\n delete (window as StashHost)[PWA_INSTALL_STASH_KEY];\n};\n","import type { InstallPlatform } from './InstallPrompt.types';\n\n/**\n * Pure environment probes and dismissal storage for `usePwaInstall`.\n *\n * Everything here is a plain function taking its clock and its storage from the\n * caller where that matters, so the hook's behaviour is testable without\n * faking timers or a browser.\n */\n\nexport const DEFAULT_STORAGE_KEY = 'pwa-install-dismissed';\nexport const DEFAULT_DISMISS_DAYS = 30;\n\nconst MS_PER_DAY = 86_400_000;\nconst STANDALONE_QUERY = '(display-mode: standalone)';\n\n/** iOS Safari predates `display-mode` and exposes this instead. */\ninterface IosNavigator extends Navigator {\n standalone?: boolean;\n}\n\n/** Guard for SSR and for jsdom runs where `matchMedia` is not implemented. */\nconst hasWindow = (): boolean => typeof window !== 'undefined';\n\n/**\n * True when the page is already running as an installed app.\n *\n * Both checks are needed: `display-mode: standalone` is the standard signal,\n * but iOS never implemented it for home-screen launches and reports\n * `navigator.standalone` instead. Miss the iOS branch and the installed app\n * nags its own users to install it again.\n */\nexport const isStandaloneDisplay = (): boolean => {\n if (!hasWindow()) return false;\n if ((window.navigator as IosNavigator).standalone === true) return true;\n return window.matchMedia?.(STANDALONE_QUERY).matches === true;\n};\n\n/**\n * True for Safari on iOS/iPadOS — the one browser that can install but cannot\n * be asked programmatically.\n *\n * Two traps handled here. iPadOS 13+ reports a Macintosh user agent, so the\n * touch-point count is what separates an iPad from a desktop; and Chrome,\n * Firefox and Edge on iOS are Safari underneath but offer no Add to Home\n * Screen at all, so telling their users to tap Share is a dead end.\n *\n * Kept parameterised so the agent table can be tested without reassigning\n * `navigator`, which leaks across test files when it is not restored.\n */\nexport const isIosSafariAgent = (userAgent: string, maxTouchPoints: number): boolean => {\n const isIosDevice =\n /iPad|iPhone|iPod/.test(userAgent) ||\n (userAgent.includes('Macintosh') && maxTouchPoints > 1);\n\n if (!isIosDevice) return false;\n\n return !/CriOS|FxiOS|EdgiOS|OPiOS/.test(userAgent);\n};\n\n/** `isIosSafariAgent` against the live browser. Internal — callers use `resolveInstallPlatform`. */\nconst isIosSafari = (): boolean => {\n if (!hasWindow()) return false;\n\n const { userAgent, maxTouchPoints } = window.navigator;\n return isIosSafariAgent(userAgent, maxTouchPoints);\n};\n\n/**\n * Reads the stored dismissal timestamp.\n *\n * Storage access is wrapped because it THROWS rather than returning null in\n * Safari private mode and wherever cookies are blocked — an uncaught throw\n * here would take down the storefront on the very browsers this component\n * exists to serve.\n */\nexport const readDismissedAt = (storageKey: string): number | null => {\n if (!hasWindow()) return null;\n\n try {\n const raw = window.localStorage.getItem(storageKey);\n if (!raw) return null;\n\n const parsed = Number.parseInt(raw, 10);\n return Number.isFinite(parsed) ? parsed : null;\n } catch {\n return null;\n }\n};\n\n/** Persists a dismissal. Silently a no-op when storage is unavailable. */\nexport const writeDismissedAt = (storageKey: string, at: number): void => {\n if (!hasWindow()) return;\n\n try {\n window.localStorage.setItem(storageKey, String(at));\n } catch {\n // Storage blocked — the dismissal lasts for this page view only.\n }\n};\n\n/**\n * Whether a stored dismissal still suppresses the prompt.\n *\n * `dismissForDays: 0` means forever. A timestamp in the future (a device whose\n * clock was wound back) is treated as active rather than expired, so a bad\n * clock cannot turn into a prompt on every page view.\n */\nexport const isDismissalActive = (\n dismissedAt: number | null,\n dismissForDays: number,\n now: number,\n): boolean => {\n if (dismissedAt === null) return false;\n if (dismissForDays <= 0) return true;\n\n return now - dismissedAt < dismissForDays * MS_PER_DAY;\n};\n\n/** Resolves which install route this browser offers. */\nexport const resolveInstallPlatform = (hasDeferredPrompt: boolean): InstallPlatform => {\n if (hasDeferredPrompt) return 'prompt';\n return isIosSafari() ? 'ios' : 'unsupported';\n};\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -139,12 +139,13 @@ const AlertContent: React.FC<{
|
|
|
139
139
|
</>
|
|
140
140
|
);
|
|
141
141
|
|
|
142
|
-
const AlertCloseButton: React.FC<{ dataTestId?: string; onClose: () => void }> = ({
|
|
142
|
+
const AlertCloseButton: React.FC<{ dataTestId?: string; label: string; onClose: () => void }> = ({
|
|
143
143
|
dataTestId,
|
|
144
|
+
label,
|
|
144
145
|
onClose }) => (
|
|
145
146
|
<IconButton
|
|
146
147
|
data-testid={testIdFor(dataTestId, 'close')}
|
|
147
|
-
aria-label=
|
|
148
|
+
aria-label={label}
|
|
148
149
|
color="inherit"
|
|
149
150
|
size="small"
|
|
150
151
|
onClick={onClose}
|
|
@@ -164,6 +165,24 @@ const AlertCloseButton: React.FC<{ dataTestId?: string; onClose: () => void }> =
|
|
|
164
165
|
</IconButton>
|
|
165
166
|
);
|
|
166
167
|
|
|
168
|
+
/**
|
|
169
|
+
* The dismiss button, or nothing.
|
|
170
|
+
*
|
|
171
|
+
* Reads `closeLabel` straight off the props rather than through the
|
|
172
|
+
* `definedProps` merge, which would widen it to `string | undefined` and hand
|
|
173
|
+
* the button an empty name. The UNION in `AlertProps` guarantees the label
|
|
174
|
+
* wherever `closable` is true — that narrowing is gone inside the component,
|
|
175
|
+
* which is why the fallback below exists and why it is unreachable.
|
|
176
|
+
*/
|
|
177
|
+
function dismissButton(
|
|
178
|
+
props: AlertProps,
|
|
179
|
+
dataTestId: string | undefined,
|
|
180
|
+
onClose: () => void,
|
|
181
|
+
): React.JSX.Element | null {
|
|
182
|
+
if (!props.closable) return null;
|
|
183
|
+
return <AlertCloseButton dataTestId={dataTestId} label={props.closeLabel} onClose={onClose} />;
|
|
184
|
+
}
|
|
185
|
+
|
|
167
186
|
export const Alert = React.forwardRef<HTMLDivElement, AlertProps>(
|
|
168
187
|
(
|
|
169
188
|
alertProps,
|
|
@@ -176,7 +195,6 @@ export const Alert = React.forwardRef<HTMLDivElement, AlertProps>(
|
|
|
176
195
|
pulse,
|
|
177
196
|
icon,
|
|
178
197
|
showIcon,
|
|
179
|
-
closable,
|
|
180
198
|
onClose,
|
|
181
199
|
title,
|
|
182
200
|
description,
|
|
@@ -190,7 +208,6 @@ export const Alert = React.forwardRef<HTMLDivElement, AlertProps>(
|
|
|
190
208
|
|
|
191
209
|
// Depends on `variant`, so it cannot live in the static defaults above.
|
|
192
210
|
const ariaLive = alertProps['aria-live'] ?? (variant === 'danger' ? 'assertive' : 'polite');
|
|
193
|
-
|
|
194
211
|
const [open, setOpen] = React.useState(true);
|
|
195
212
|
const [isClosing, setIsClosing] = React.useState(false);
|
|
196
213
|
|
|
@@ -232,7 +249,7 @@ export const Alert = React.forwardRef<HTMLDivElement, AlertProps>(
|
|
|
232
249
|
aria-live={ariaLive}
|
|
233
250
|
aria-atomic={ariaAtomic}
|
|
234
251
|
tabIndex={0}
|
|
235
|
-
action={
|
|
252
|
+
action={dismissButton(alertProps, dataTestId, handleClose)}
|
|
236
253
|
{...props}
|
|
237
254
|
>
|
|
238
255
|
{content}
|
|
@@ -2,7 +2,26 @@ import type { ColorValue } from '../../../tokens/scales';
|
|
|
2
2
|
import type { AlertProps as MuiAlertProps } from '@mui/material';
|
|
3
3
|
import type React from 'react';
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
/**
|
|
6
|
+
* The dismissible half, as a UNION rather than one more optional field.
|
|
7
|
+
*
|
|
8
|
+
* `closeLabel` is the dismiss button's only accessible name — it renders a
|
|
9
|
+
* glyph and nothing else — so it must be REQUIRED wherever that button exists.
|
|
10
|
+
* But most alerts are not closable, and making it required on all of them
|
|
11
|
+
* would ask a dozen call sites across five packages to name a control they
|
|
12
|
+
* never render. A word invented to satisfy a type is worse than no word: the
|
|
13
|
+
* next reader cannot tell which button it belongs to.
|
|
14
|
+
*
|
|
15
|
+
* So the type says what is actually true — the label is required exactly when
|
|
16
|
+
* the button is.
|
|
17
|
+
*/
|
|
18
|
+
type AlertDismiss =
|
|
19
|
+
| { closable?: false; onClose?: () => void; closeLabel?: never }
|
|
20
|
+
| { closable: true; onClose?: () => void; closeLabel: string };
|
|
21
|
+
|
|
22
|
+
export type AlertProps = AlertBase & AlertDismiss;
|
|
23
|
+
|
|
24
|
+
export interface AlertBase extends Omit<MuiAlertProps, 'variant' | 'color' | 'role'> {
|
|
6
25
|
/**
|
|
7
26
|
* The variant of the alert
|
|
8
27
|
*/
|
|
@@ -33,15 +52,6 @@ export interface AlertProps extends Omit<MuiAlertProps, 'variant' | 'color' | 'r
|
|
|
33
52
|
*/
|
|
34
53
|
showIcon?: boolean;
|
|
35
54
|
|
|
36
|
-
/**
|
|
37
|
-
* Whether the alert can be closed
|
|
38
|
-
*/
|
|
39
|
-
closable?: boolean;
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Callback when alert is closed
|
|
43
|
-
*/
|
|
44
|
-
onClose?: () => void;
|
|
45
55
|
|
|
46
56
|
/**
|
|
47
57
|
* Title for the alert
|
|
@@ -80,6 +80,7 @@ export const AsyncStateContainer: React.FC<AsyncStateContainerProps> = React.mem
|
|
|
80
80
|
errorComponent,
|
|
81
81
|
renderError,
|
|
82
82
|
emptyComponent,
|
|
83
|
+
emptyTitle,
|
|
83
84
|
renderEmpty,
|
|
84
85
|
className,
|
|
85
86
|
dataTestId,
|
|
@@ -107,7 +108,7 @@ export const AsyncStateContainer: React.FC<AsyncStateContainerProps> = React.mem
|
|
|
107
108
|
if (isEmpty) {
|
|
108
109
|
return (
|
|
109
110
|
<StateBox state="empty" {...box}>
|
|
110
|
-
{pickNode(emptyComponent, renderEmpty, <EmptyState title=
|
|
111
|
+
{pickNode(emptyComponent, renderEmpty, <EmptyState title={emptyTitle} />)}
|
|
111
112
|
</StateBox>
|
|
112
113
|
);
|
|
113
114
|
}
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import type React from 'react';
|
|
2
2
|
|
|
3
3
|
export interface AsyncStateContainerProps {
|
|
4
|
+
/**
|
|
5
|
+
* What an EMPTY result reads as, when the caller supplies no
|
|
6
|
+
* `emptyComponent` of its own. REQUIRED — the fallback was the English
|
|
7
|
+
* "No data available", on the package every host renders.
|
|
8
|
+
*/
|
|
9
|
+
emptyTitle: string;
|
|
4
10
|
/**
|
|
5
11
|
* Whether the container is in a loading state
|
|
6
12
|
* @default false
|
|
@@ -202,8 +202,9 @@ const BannerTrailing: React.FC<{
|
|
|
202
202
|
variant: BannerVariant;
|
|
203
203
|
dismissible: boolean;
|
|
204
204
|
onDismissClick: () => void;
|
|
205
|
+
dismissLabel: string;
|
|
205
206
|
dataTestId?: string;
|
|
206
|
-
}> = ({ actions, variant, dismissible, onDismissClick, dataTestId }) => (
|
|
207
|
+
}> = ({ actions, variant, dismissible, onDismissClick, dismissLabel, dataTestId }) => (
|
|
207
208
|
<Box display="flex" alignItems="center" gap={1}>
|
|
208
209
|
{actions && actions.length > 0 && (
|
|
209
210
|
<Box className="banner-actions">
|
|
@@ -223,7 +224,7 @@ const BannerTrailing: React.FC<{
|
|
|
223
224
|
className="banner-dismiss"
|
|
224
225
|
size="small"
|
|
225
226
|
onClick={onDismissClick}
|
|
226
|
-
aria-label=
|
|
227
|
+
aria-label={dismissLabel}
|
|
227
228
|
data-testid={testIdFor(dataTestId, 'close')}
|
|
228
229
|
sx={{ transition: 'all 0.2s ease' }}
|
|
229
230
|
>
|
|
@@ -241,6 +242,7 @@ export const Banner = React.forwardRef<HTMLDivElement, BannerProps>(
|
|
|
241
242
|
description,
|
|
242
243
|
icon,
|
|
243
244
|
dismissible = false,
|
|
245
|
+
dismissLabel,
|
|
244
246
|
onDismiss,
|
|
245
247
|
actions,
|
|
246
248
|
sticky = false,
|
|
@@ -295,6 +297,7 @@ export const Banner = React.forwardRef<HTMLDivElement, BannerProps>(
|
|
|
295
297
|
variant={variant}
|
|
296
298
|
dismissible={dismissible}
|
|
297
299
|
onDismissClick={handleDismiss}
|
|
300
|
+
dismissLabel={dismissLabel}
|
|
298
301
|
dataTestId={dataTestId}
|
|
299
302
|
/>
|
|
300
303
|
</StyledBanner>
|
|
@@ -14,6 +14,8 @@ export interface BannerAction {
|
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
export interface BannerProps {
|
|
17
|
+
/** The dismiss button's accessible name — it carries a glyph only. REQUIRED. */
|
|
18
|
+
dismissLabel: string;
|
|
17
19
|
/** Message severity type */
|
|
18
20
|
variant?: BannerVariant;
|
|
19
21
|
/** Main message title text */
|
|
@@ -37,6 +37,7 @@ export function CardActionsProvider({
|
|
|
37
37
|
tenantSlug,
|
|
38
38
|
onRefresh,
|
|
39
39
|
errorTitle,
|
|
40
|
+
errorDismissLabel,
|
|
40
41
|
autoHideMs = 6_000,
|
|
41
42
|
children,
|
|
42
43
|
}: {
|
|
@@ -50,6 +51,8 @@ export function CardActionsProvider({
|
|
|
50
51
|
* MESSAGE beside it comes from whatever failed, which is already the host's.
|
|
51
52
|
*/
|
|
52
53
|
errorTitle: string;
|
|
54
|
+
/** The dismiss on that alert, which carries a glyph only. REQUIRED. */
|
|
55
|
+
errorDismissLabel: string;
|
|
53
56
|
/** How long the snackbar stays up. */
|
|
54
57
|
autoHideMs?: number;
|
|
55
58
|
children: ReactNode;
|
|
@@ -72,6 +75,7 @@ export function CardActionsProvider({
|
|
|
72
75
|
title={errorTitle}
|
|
73
76
|
description={error ?? ''}
|
|
74
77
|
closable
|
|
78
|
+
closeLabel={errorDismissLabel}
|
|
75
79
|
onClose={() => setError(null)}
|
|
76
80
|
data-testid="card-action-error"
|
|
77
81
|
/>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
|
|
3
3
|
import { CarouselArrows, CarouselIndicators, CarouselThumbnails } from './Carousel.parts';
|
|
4
|
+
import type { CarouselCopy } from '../../../copy';
|
|
4
5
|
import type {
|
|
5
6
|
CarouselArrowsProps,
|
|
6
7
|
CarouselIndicatorsProps,
|
|
@@ -12,6 +13,8 @@ type IndicatorPosition = NonNullable<CarouselIndicatorsProps['position']>;
|
|
|
12
13
|
type Color = NonNullable<CarouselIndicatorsProps['color']>;
|
|
13
14
|
|
|
14
15
|
export interface CarouselChromeProps {
|
|
16
|
+
/** The two arrows' accessible names, from the mount. */
|
|
17
|
+
copy: CarouselCopy;
|
|
15
18
|
items: CarouselItem[];
|
|
16
19
|
activeIndex: number;
|
|
17
20
|
loop: boolean;
|
|
@@ -34,6 +37,7 @@ export interface CarouselChromeProps {
|
|
|
34
37
|
* as three conditional blocks inside the carousel's own render.
|
|
35
38
|
*/
|
|
36
39
|
export const CarouselChrome: React.FC<CarouselChromeProps> = ({
|
|
40
|
+
copy,
|
|
37
41
|
items,
|
|
38
42
|
activeIndex,
|
|
39
43
|
loop,
|
|
@@ -52,6 +56,7 @@ export const CarouselChrome: React.FC<CarouselChromeProps> = ({
|
|
|
52
56
|
<>
|
|
53
57
|
{showArrows && !disabled && (
|
|
54
58
|
<CarouselArrows
|
|
59
|
+
copy={copy}
|
|
55
60
|
onPrev={onPrev}
|
|
56
61
|
onNext={onNext}
|
|
57
62
|
position={arrowPosition}
|
|
@@ -212,7 +212,8 @@ const arrowSx = (theme: Theme, color: Color, isOverlay: boolean): CSSObject => (
|
|
|
212
212
|
|
|
213
213
|
export const CarouselArrows: React.FC<CarouselArrowsProps> = (props) => {
|
|
214
214
|
const {
|
|
215
|
-
onPrev, onNext, position, disabled, disablePrev, disableNext, color, size, className,
|
|
215
|
+
copy, onPrev, onNext, position, disabled, disablePrev, disableNext, color, size, className,
|
|
216
|
+
style,
|
|
216
217
|
} = withDefaults(props, ARROWS_DEFAULTS) as CarouselArrowsProps &
|
|
217
218
|
Required<Pick<CarouselArrowsProps, keyof typeof ARROWS_DEFAULTS>>;
|
|
218
219
|
|
|
@@ -227,7 +228,7 @@ export const CarouselArrows: React.FC<CarouselArrowsProps> = (props) => {
|
|
|
227
228
|
disabled={disabled || disablePrev}
|
|
228
229
|
size={muiSize(size)}
|
|
229
230
|
className={className}
|
|
230
|
-
aria-label=
|
|
231
|
+
aria-label={copy.previous}
|
|
231
232
|
data-testid="carousel-prev-button"
|
|
232
233
|
sx={{ ...sx, left: offset, ...style }}
|
|
233
234
|
>
|
|
@@ -237,7 +238,7 @@ export const CarouselArrows: React.FC<CarouselArrowsProps> = (props) => {
|
|
|
237
238
|
<IconButton
|
|
238
239
|
onClick={onNext}
|
|
239
240
|
disabled={disabled || disableNext}
|
|
240
|
-
aria-label=
|
|
241
|
+
aria-label={copy.next}
|
|
241
242
|
size={muiSize(size)}
|
|
242
243
|
className={className}
|
|
243
244
|
data-testid="carousel-next-button"
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ColorValue, SizeValue } from '../../../tokens/scales';
|
|
2
2
|
import type { CSSProperties, ReactNode } from 'react';
|
|
3
|
+
import type { CarouselCopy } from '../../../copy';
|
|
3
4
|
|
|
4
5
|
export interface CarouselItem {
|
|
5
6
|
id: string;
|
|
@@ -11,6 +12,11 @@ export interface CarouselItem {
|
|
|
11
12
|
}
|
|
12
13
|
|
|
13
14
|
export interface CarouselProps {
|
|
15
|
+
/**
|
|
16
|
+
* The two arrows' accessible names. They render a glyph and nothing else,
|
|
17
|
+
* so this pair IS what a screen reader announces for them. REQUIRED.
|
|
18
|
+
*/
|
|
19
|
+
copy: CarouselCopy;
|
|
14
20
|
items: CarouselItem[];
|
|
15
21
|
variant?: 'default' | 'glass' | 'gradient' | 'elevated' | 'minimal' | 'cards';
|
|
16
22
|
size?: SizeValue;
|
|
@@ -58,6 +64,7 @@ export interface CarouselIndicatorsProps {
|
|
|
58
64
|
}
|
|
59
65
|
|
|
60
66
|
export interface CarouselArrowsProps {
|
|
67
|
+
copy: CarouselCopy;
|
|
61
68
|
onPrev: () => void;
|
|
62
69
|
onNext: () => void;
|
|
63
70
|
position?: 'inside' | 'outside' | 'overlay';
|
|
@@ -87,14 +87,14 @@ export function DataGridError(
|
|
|
87
87
|
}
|
|
88
88
|
|
|
89
89
|
export function DataGridEmpty(
|
|
90
|
-
props: StateProps & { emptyState?: React.ReactNode },
|
|
90
|
+
props: StateProps & { emptyState?: React.ReactNode; emptyText: string },
|
|
91
91
|
): React.JSX.Element {
|
|
92
|
-
const { emptyState, ...rest } = props;
|
|
92
|
+
const { emptyState, emptyText, ...rest } = props;
|
|
93
93
|
return (
|
|
94
94
|
<Placeholder {...rest} flag="data-empty">
|
|
95
95
|
{emptyState ?? (
|
|
96
96
|
<Typography variant="body1" color="text.secondary">
|
|
97
|
-
|
|
97
|
+
{emptyText}
|
|
98
98
|
</Typography>
|
|
99
99
|
)}
|
|
100
100
|
</Placeholder>
|
|
@@ -68,7 +68,7 @@ export const DataGrid = <T extends Record<string, unknown> = Record<string, unkn
|
|
|
68
68
|
if (props.loading) return <DataGridLoading {...placeholder} />;
|
|
69
69
|
if (props.error) return <DataGridError {...placeholder} error={props.error} />;
|
|
70
70
|
if (model.processedRows.length === 0) {
|
|
71
|
-
return <DataGridEmpty {...placeholder} emptyState={props.emptyState} />;
|
|
71
|
+
return <DataGridEmpty {...placeholder} emptyState={props.emptyState} emptyText={props.emptyText} />;
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
const leadingColumns = (props.selection?.mode ?? 'none') !== 'none' ? 1 : 0;
|