xertica-ui 2.2.1 → 2.4.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/CHANGELOG.md +564 -525
- package/README.md +417 -382
- package/bin/cli.ts +1244 -748
- package/bin/generate-tokens.ts +262 -262
- package/bin/language-config.ts +5 -8
- package/components/assets/xertica-orbe-animation.ts +1162 -1162
- package/components/assistant/code-block/CodeBlock.tsx +268 -268
- package/components/assistant/code-block/code-block.stories.tsx +57 -57
- package/components/assistant/code-block/code-block.test.tsx +44 -44
- package/components/assistant/code-block/index.ts +1 -1
- package/components/assistant/formatted-document/FormattedDocument.tsx +147 -147
- package/components/assistant/formatted-document/formatted-document.stories.tsx +51 -51
- package/components/assistant/formatted-document/formatted-document.test.tsx +42 -42
- package/components/assistant/formatted-document/index.ts +1 -1
- package/components/assistant/index.ts +6 -6
- package/components/assistant/markdown-message/MarkdownMessage.tsx +152 -152
- package/components/assistant/markdown-message/index.ts +1 -1
- package/components/assistant/markdown-message/markdown-message.stories.tsx +50 -50
- package/components/assistant/markdown-message/markdown-message.test.tsx +33 -33
- package/components/assistant/modern-chat-input/ModernChatInput.tsx +17 -7
- package/components/assistant/modern-chat-input/index.ts +1 -1
- package/components/assistant/modern-chat-input/modern-chat-input.stories.tsx +131 -131
- package/components/assistant/modern-chat-input/modern-chat-input.test.tsx +79 -79
- package/components/assistant/xertica-assistant/index.ts +3 -3
- package/components/assistant/xertica-assistant/parts/AssistantCollapsedView.tsx +99 -99
- package/components/assistant/xertica-assistant/parts/AssistantConversationList.tsx +104 -106
- package/components/assistant/xertica-assistant/parts/AssistantDocumentEditor.tsx +81 -81
- package/components/assistant/xertica-assistant/parts/AssistantFeedbackDialog.tsx +88 -78
- package/components/assistant/xertica-assistant/parts/AssistantHeader.tsx +75 -75
- package/components/assistant/xertica-assistant/parts/AssistantMessageBubble.tsx +564 -560
- package/components/assistant/xertica-assistant/parts/AssistantTabBar.tsx +67 -67
- package/components/assistant/xertica-assistant/parts/AssistantTypingIndicator.tsx +41 -41
- package/components/assistant/xertica-assistant/parts/AssistantWelcomeScreen.tsx +103 -103
- package/components/assistant/xertica-assistant/parts/index.ts +16 -16
- package/components/assistant/xertica-assistant/types.ts +134 -134
- package/components/assistant/xertica-assistant/use-assistant.ts +615 -615
- package/components/assistant/xertica-assistant/xertica-assistant.stories.tsx +407 -407
- package/components/assistant/xertica-assistant/xertica-assistant.test.tsx +65 -65
- package/components/assistant/xertica-assistant/xertica-assistant.tsx +611 -613
- package/components/blocks/card-patterns/ActivityCard.tsx +100 -100
- package/components/blocks/card-patterns/FeatureCard.tsx +109 -109
- package/components/blocks/card-patterns/FeatureCardSkeleton.tsx +1 -6
- package/components/blocks/card-patterns/NotificationCard.tsx +140 -140
- package/components/blocks/card-patterns/ProfileCard.tsx +112 -114
- package/components/blocks/card-patterns/ProjectCard.tsx +123 -123
- package/components/blocks/card-patterns/ProjectCardSkeleton.tsx +1 -6
- package/components/blocks/card-patterns/QuickActionCard.tsx +68 -68
- package/components/blocks/card-patterns/card-patterns.mdx +123 -123
- package/components/blocks/card-patterns/card-patterns.stories.tsx +594 -594
- package/components/blocks/card-patterns/index.ts +29 -29
- package/components/blocks/index.ts +1 -1
- package/components/brand/branding/branding.stories.tsx +57 -57
- package/components/brand/index.ts +6 -6
- package/components/brand/language-selector/index.ts +1 -1
- package/components/brand/language-selector/language-selector.mdx +126 -126
- package/components/brand/language-selector/language-selector.stories.tsx +1 -4
- package/components/brand/theme-toggle/ThemeToggle.tsx +74 -70
- package/components/brand/theme-toggle/index.ts +1 -1
- package/components/brand/theme-toggle/theme-toggle.stories.tsx +34 -34
- package/components/brand/theme-toggle/theme-toggle.test.tsx +34 -34
- package/components/brand/xertica-logo/XerticaLogo.stories.tsx +82 -82
- package/components/brand/xertica-logo/XerticaLogo.tsx +104 -104
- package/components/brand/xertica-logo/index.ts +1 -1
- package/components/brand/xertica-logo/xertica-logo.test.tsx +26 -26
- package/components/brand/xertica-orbe/XerticaOrbe.tsx +1927 -1927
- package/components/brand/xertica-orbe/index.ts +1 -1
- package/components/brand/xertica-orbe/xertica-orbe.stories.tsx +40 -40
- package/components/brand/xertica-orbe/xertica-orbe.test.tsx +19 -19
- package/components/brand/xertica-provider/XerticaProvider.tsx +1 -4
- package/components/brand/xertica-provider/index.ts +1 -1
- package/components/brand/xertica-provider/xertica-provider.test.tsx +74 -74
- package/components/brand/xertica-xlogo/XerticaXLogo.stories.tsx +79 -79
- package/components/brand/xertica-xlogo/XerticaXLogo.tsx +65 -65
- package/components/brand/xertica-xlogo/index.ts +1 -1
- package/components/brand/xertica-xlogo/xertica-xlogo.test.tsx +16 -16
- package/components/examples/ApiKeyMapExample.tsx +71 -71
- package/components/examples/DrawingMapExample.tsx +565 -565
- package/components/examples/FilterableMapExample.tsx +393 -393
- package/components/examples/LocationPickerExample.tsx +348 -348
- package/components/examples/MapExamples.tsx +268 -268
- package/components/examples/MapGmpExample.tsx +169 -169
- package/components/examples/MapShowcase.tsx +471 -471
- package/components/examples/RouteMapExamples.tsx +329 -329
- package/components/examples/SidebarLogoExample.tsx +65 -65
- package/components/examples/SimpleFilterableMap.tsx +219 -219
- package/components/examples/index.ts +45 -45
- package/components/figma/ImageWithFallback.tsx +27 -27
- package/components/hooks/index.ts +13 -13
- package/components/hooks/use-layout-shortcuts.ts +43 -43
- package/components/index.ts +86 -90
- package/components/layout/header/header.stories.tsx +204 -204
- package/components/layout/header/header.test.tsx +75 -75
- package/components/layout/header/header.tsx +349 -349
- package/components/layout/header/index.ts +1 -1
- package/components/layout/index.ts +2 -2
- package/components/layout/sidebar/index.ts +3 -3
- package/components/layout/sidebar/sidebar.stories.tsx +586 -586
- package/components/layout/sidebar/sidebar.test.tsx +76 -76
- package/components/layout/sidebar/sidebar.tsx +1079 -1073
- package/components/layout/sidebar/use-sidebar.ts +104 -104
- package/components/media/FloatingMediaWrapper.tsx +371 -371
- package/components/media/audio-player/AudioPlayer.stories.tsx +124 -124
- package/components/media/audio-player/AudioPlayer.test.tsx +106 -106
- package/components/media/audio-player/AudioPlayer.tsx +767 -765
- package/components/media/audio-player/index.ts +1 -1
- package/components/media/audio-player/use-audio-player.ts +312 -312
- package/components/media/index.ts +3 -3
- package/components/media/video-player/VideoPlayer.stories.tsx +98 -98
- package/components/media/video-player/VideoPlayer.test.tsx +73 -73
- package/components/media/video-player/VideoPlayer.tsx +310 -310
- package/components/media/video-player/index.ts +1 -1
- package/components/pages/forgot-password-page/ForgotPasswordPage.stories.tsx +24 -24
- package/components/pages/forgot-password-page/ForgotPasswordPage.tsx +188 -188
- package/components/pages/forgot-password-page/forgot-password-page.test.tsx +45 -45
- package/components/pages/forgot-password-page/index.ts +1 -1
- package/components/pages/home-content/HomeContent.stories.tsx +43 -43
- package/components/pages/home-content/HomeContent.tsx +120 -120
- package/components/pages/home-content/index.ts +1 -1
- package/components/pages/home-page/HomePage.stories.tsx +39 -39
- package/components/pages/home-page/HomePage.tsx +78 -74
- package/components/pages/home-page/home-page.test.tsx +53 -53
- package/components/pages/home-page/index.ts +1 -1
- package/components/pages/index.ts +8 -8
- package/components/pages/login-page/LoginPage.stories.tsx +39 -39
- package/components/pages/login-page/LoginPage.tsx +218 -216
- package/components/pages/login-page/index.ts +1 -1
- package/components/pages/login-page/login-page.test.tsx +63 -63
- package/components/pages/reset-password-page/ResetPasswordPage.stories.tsx +24 -24
- package/components/pages/reset-password-page/ResetPasswordPage.tsx +243 -239
- package/components/pages/reset-password-page/index.ts +1 -1
- package/components/pages/template-content/TemplateContent.stories.tsx +43 -43
- package/components/pages/template-content/TemplateContent.tsx +1354 -1235
- package/components/pages/template-content/index.ts +1 -1
- package/components/pages/template-page/TemplatePage.stories.tsx +39 -39
- package/components/pages/template-page/TemplatePage.tsx +62 -62
- package/components/pages/template-page/index.ts +1 -1
- package/components/pages/template-page/template-page.test.tsx +52 -52
- package/components/pages/verify-email-page/VerifyEmailPage.stories.tsx +41 -41
- package/components/pages/verify-email-page/VerifyEmailPage.tsx +206 -206
- package/components/pages/verify-email-page/index.ts +1 -1
- package/components/public-api-smoke.test.tsx +52 -52
- package/components/shared/CustomTooltipContent.tsx +48 -48
- package/components/shared/assistant-utils.test.ts +16 -16
- package/components/shared/assistant-utils.ts +225 -225
- package/components/shared/error-boundary.stories.tsx +114 -132
- package/components/shared/error-boundary.tsx +150 -154
- package/components/shared/error-fallbacks.tsx +222 -226
- package/components/shared/layout-constants.ts +8 -8
- package/components/shared/navigation.ts +35 -35
- package/components/shared/use-mobile.test.ts +16 -16
- package/components/shared/use-mobile.ts +36 -36
- package/components/shared/utils.test.ts +14 -14
- package/components/shared/utils.ts +6 -6
- package/components/ui/accordion/accordion.stories.tsx +105 -105
- package/components/ui/accordion/accordion.test.tsx +59 -59
- package/components/ui/accordion/accordion.tsx +77 -77
- package/components/ui/accordion/index.ts +1 -1
- package/components/ui/alert/alert.stories.tsx +86 -86
- package/components/ui/alert/alert.test.tsx +53 -53
- package/components/ui/alert/alert.tsx +93 -93
- package/components/ui/alert/index.ts +1 -1
- package/components/ui/alert-dialog/alert-dialog.stories.tsx +84 -84
- package/components/ui/alert-dialog/alert-dialog.test.tsx +70 -70
- package/components/ui/alert-dialog/alert-dialog.tsx +149 -149
- package/components/ui/alert-dialog/index.ts +1 -1
- package/components/ui/aspect-ratio/aspect-ratio.stories.tsx +46 -46
- package/components/ui/aspect-ratio/aspect-ratio.test.tsx +28 -28
- package/components/ui/aspect-ratio/aspect-ratio.tsx +20 -20
- package/components/ui/aspect-ratio/index.ts +1 -1
- package/components/ui/assistant-chart/AssistantChart.tsx +64 -64
- package/components/ui/assistant-chart/assistant-chart.stories.tsx +44 -44
- package/components/ui/assistant-chart/assistant-chart.test.tsx +46 -46
- package/components/ui/assistant-chart/index.ts +1 -1
- package/components/ui/avatar/avatar.stories.tsx +86 -86
- package/components/ui/avatar/avatar.test.tsx +55 -55
- package/components/ui/avatar/avatar.tsx +71 -71
- package/components/ui/avatar/index.ts +1 -1
- package/components/ui/badge/badge.stories.tsx +72 -72
- package/components/ui/badge/badge.test.tsx +40 -40
- package/components/ui/badge/badge.tsx +58 -58
- package/components/ui/badge/index.ts +1 -1
- package/components/ui/breadcrumb/breadcrumb.stories.tsx +123 -123
- package/components/ui/breadcrumb/breadcrumb.test.tsx +70 -70
- package/components/ui/breadcrumb/breadcrumb.tsx +114 -114
- package/components/ui/breadcrumb/index.ts +1 -1
- package/components/ui/button/button.stories.tsx +183 -183
- package/components/ui/button/button.test.tsx +64 -64
- package/components/ui/button/button.tsx +98 -98
- package/components/ui/button/index.ts +1 -1
- package/components/ui/calendar/calendar.stories.tsx +108 -108
- package/components/ui/calendar/calendar.test.tsx +53 -53
- package/components/ui/calendar/calendar.tsx +230 -230
- package/components/ui/calendar/index.ts +1 -1
- package/components/ui/card/card.stories.tsx +301 -301
- package/components/ui/card/card.test.tsx +55 -55
- package/components/ui/card/card.tsx +83 -83
- package/components/ui/card/index.ts +1 -1
- package/components/ui/carousel/carousel.stories.tsx +80 -80
- package/components/ui/carousel/carousel.test.tsx +75 -75
- package/components/ui/carousel/carousel.tsx +242 -242
- package/components/ui/carousel/index.ts +1 -1
- package/components/ui/chart/chart.stories.tsx +1328 -1328
- package/components/ui/chart/chart.test.tsx +178 -178
- package/components/ui/chart/chart.tsx +2232 -2232
- package/components/ui/chart/index.ts +1 -1
- package/components/ui/checkbox/checkbox.stories.tsx +109 -109
- package/components/ui/checkbox/checkbox.test.tsx +49 -49
- package/components/ui/checkbox/checkbox.tsx +68 -68
- package/components/ui/checkbox/index.ts +1 -1
- package/components/ui/collapsible/collapsible.stories.tsx +45 -45
- package/components/ui/collapsible/collapsible.test.tsx +51 -51
- package/components/ui/collapsible/collapsible.tsx +32 -32
- package/components/ui/collapsible/index.ts +1 -1
- package/components/ui/command/command.stories.tsx +134 -134
- package/components/ui/command/command.test.tsx +48 -48
- package/components/ui/command/command.tsx +163 -163
- package/components/ui/command/index.ts +1 -1
- package/components/ui/context-menu/context-menu.stories.tsx +76 -76
- package/components/ui/context-menu/context-menu.test.tsx +61 -61
- package/components/ui/context-menu/context-menu.tsx +236 -236
- package/components/ui/context-menu/index.ts +1 -1
- package/components/ui/dialog/dialog.stories.tsx +174 -174
- package/components/ui/dialog/dialog.test.tsx +78 -78
- package/components/ui/dialog/dialog.tsx +189 -189
- package/components/ui/dialog/index.ts +1 -1
- package/components/ui/drawer/drawer.stories.tsx +71 -71
- package/components/ui/drawer/drawer.test.tsx +67 -67
- package/components/ui/drawer/drawer.tsx +146 -146
- package/components/ui/drawer/index.ts +1 -1
- package/components/ui/dropdown-menu/dropdown-menu.stories.tsx +156 -156
- package/components/ui/dropdown-menu/dropdown-menu.test.tsx +62 -62
- package/components/ui/dropdown-menu/dropdown-menu.tsx +240 -240
- package/components/ui/dropdown-menu/index.ts +1 -1
- package/components/ui/empty/empty.stories.tsx +85 -85
- package/components/ui/empty/empty.test.tsx +31 -31
- package/components/ui/empty/empty.tsx +88 -88
- package/components/ui/empty/index.ts +1 -1
- package/components/ui/file-upload/file-upload.stories.tsx +144 -144
- package/components/ui/file-upload/file-upload.test.tsx +65 -65
- package/components/ui/file-upload/file-upload.tsx +142 -142
- package/components/ui/file-upload/index.ts +2 -2
- package/components/ui/file-upload/use-file-upload.ts +177 -177
- package/components/ui/form/form.stories.tsx +85 -85
- package/components/ui/form/form.test.tsx +75 -75
- package/components/ui/form/form.tsx +163 -163
- package/components/ui/form/index.ts +1 -1
- package/components/ui/google-maps-loader/google-maps-loader.test.tsx +35 -35
- package/components/ui/google-maps-loader/google-maps-loader.tsx +465 -465
- package/components/ui/google-maps-loader/index.ts +1 -1
- package/components/ui/hover-card/hover-card.stories.tsx +61 -61
- package/components/ui/hover-card/hover-card.test.tsx +48 -48
- package/components/ui/hover-card/hover-card.tsx +50 -50
- package/components/ui/hover-card/index.ts +1 -1
- package/components/ui/index.ts +400 -400
- package/components/ui/input/index.ts +1 -1
- package/components/ui/input/input.stories.tsx +153 -153
- package/components/ui/input/input.test.tsx +47 -47
- package/components/ui/input/input.tsx +57 -57
- package/components/ui/input-otp/index.ts +1 -1
- package/components/ui/input-otp/input-otp.stories.tsx +120 -120
- package/components/ui/input-otp/input-otp.test.tsx +74 -74
- package/components/ui/input-otp/input-otp.tsx +101 -101
- package/components/ui/label/index.ts +1 -1
- package/components/ui/label/label.stories.tsx +74 -74
- package/components/ui/label/label.test.tsx +45 -45
- package/components/ui/label/label.tsx +53 -53
- package/components/ui/map/index.ts +1 -1
- package/components/ui/map/map.stories.tsx +86 -86
- package/components/ui/map/map.test.tsx +82 -82
- package/components/ui/map/map.tsx +506 -506
- package/components/ui/map/mock.test.tsx +13 -13
- package/components/ui/map-config/index.ts +1 -1
- package/components/ui/map-config/map-config.ts +18 -18
- package/components/ui/map-layers/index.ts +1 -1
- package/components/ui/map-layers/map-layers.test.tsx +48 -48
- package/components/ui/map-layers/map-layers.tsx +126 -126
- package/components/ui/map.exports/index.ts +1 -1
- package/components/ui/map.exports/map.exports.ts +31 -31
- package/components/ui/menubar/index.ts +1 -1
- package/components/ui/menubar/menubar.stories.tsx +130 -130
- package/components/ui/menubar/menubar.test.tsx +53 -53
- package/components/ui/menubar/menubar.tsx +265 -265
- package/components/ui/navigation-menu/index.ts +1 -1
- package/components/ui/navigation-menu/navigation-menu.stories.tsx +126 -126
- package/components/ui/navigation-menu/navigation-menu.test.tsx +47 -47
- package/components/ui/navigation-menu/navigation-menu.tsx +165 -165
- package/components/ui/notification-badge/index.ts +1 -1
- package/components/ui/notification-badge/notification-badge.stories.tsx +66 -66
- package/components/ui/notification-badge/notification-badge.test.tsx +61 -61
- package/components/ui/notification-badge/notification-badge.tsx +91 -91
- package/components/ui/page-header/index.ts +1 -1
- package/components/ui/page-header/page-header.stories.tsx +69 -69
- package/components/ui/page-header/page-header.test.tsx +37 -37
- package/components/ui/page-header/page-header.tsx +124 -124
- package/components/ui/pagination/index.ts +3 -3
- package/components/ui/pagination/pagination.stories.tsx +210 -210
- package/components/ui/pagination/pagination.test.tsx +63 -63
- package/components/ui/pagination/pagination.tsx +140 -140
- package/components/ui/pagination/use-pagination.ts +173 -173
- package/components/ui/popover/index.ts +1 -1
- package/components/ui/popover/popover.stories.tsx +73 -73
- package/components/ui/popover/popover.test.tsx +48 -48
- package/components/ui/popover/popover.tsx +54 -54
- package/components/ui/progress/index.ts +1 -1
- package/components/ui/progress/progress.stories.tsx +55 -55
- package/components/ui/progress/progress.test.tsx +23 -23
- package/components/ui/progress/progress.tsx +68 -68
- package/components/ui/radio-group/index.ts +1 -1
- package/components/ui/radio-group/radio-group.stories.tsx +114 -114
- package/components/ui/radio-group/radio-group.test.tsx +78 -78
- package/components/ui/radio-group/radio-group.tsx +93 -93
- package/components/ui/rating/index.ts +1 -1
- package/components/ui/rating/rating.stories.tsx +50 -50
- package/components/ui/rating/rating.test.tsx +48 -48
- package/components/ui/rating/rating.tsx +145 -145
- package/components/ui/resizable/index.ts +1 -1
- package/components/ui/resizable/resizable.stories.tsx +88 -88
- package/components/ui/resizable/resizable.test.tsx +61 -61
- package/components/ui/resizable/resizable.tsx +452 -452
- package/components/ui/rich-text-editor/index.ts +7 -7
- package/components/ui/rich-text-editor/rich-text-editor.stories.tsx +290 -290
- package/components/ui/rich-text-editor/rich-text-editor.test.tsx +86 -86
- package/components/ui/rich-text-editor/rich-text-editor.tsx +634 -634
- package/components/ui/rich-text-editor/use-rich-text-editor.ts +453 -453
- package/components/ui/route-map/index.ts +1 -1
- package/components/ui/route-map/route-map.stories.tsx +48 -48
- package/components/ui/route-map/route-map.test.tsx +108 -108
- package/components/ui/route-map/route-map.tsx +349 -349
- package/components/ui/scroll-area/index.ts +1 -1
- package/components/ui/scroll-area/scroll-area.stories.tsx +31 -31
- package/components/ui/scroll-area/scroll-area.test.tsx +27 -27
- package/components/ui/scroll-area/scroll-area.tsx +70 -70
- package/components/ui/search/index.ts +1 -1
- package/components/ui/search/search.stories.tsx +107 -107
- package/components/ui/search/search.test.tsx +67 -67
- package/components/ui/search/search.tsx +141 -141
- package/components/ui/select/index.ts +1 -1
- package/components/ui/select/select.stories.tsx +163 -163
- package/components/ui/select/select.test.tsx +99 -99
- package/components/ui/select/select.tsx +195 -195
- package/components/ui/separator/index.ts +1 -1
- package/components/ui/separator/separator.stories.tsx +55 -55
- package/components/ui/separator/separator.test.tsx +23 -23
- package/components/ui/separator/separator.tsx +39 -39
- package/components/ui/sheet/index.ts +1 -1
- package/components/ui/sheet/sheet.stories.tsx +93 -93
- package/components/ui/sheet/sheet.test.tsx +62 -62
- package/components/ui/sheet/sheet.tsx +149 -149
- package/components/ui/simple-map/index.ts +1 -1
- package/components/ui/simple-map/simple-map.stories.tsx +44 -44
- package/components/ui/simple-map/simple-map.test.tsx +36 -36
- package/components/ui/simple-map/simple-map.tsx +92 -92
- package/components/ui/skeleton/index.ts +1 -1
- package/components/ui/skeleton/skeleton.stories.tsx +36 -36
- package/components/ui/skeleton/skeleton.test.tsx +19 -19
- package/components/ui/skeleton/skeleton.tsx +25 -25
- package/components/ui/slider/index.ts +1 -1
- package/components/ui/slider/slider.stories.tsx +44 -44
- package/components/ui/slider/slider.test.tsx +25 -25
- package/components/ui/slider/slider.tsx +66 -66
- package/components/ui/sonner/index.ts +1 -1
- package/components/ui/sonner/sonner.stories.tsx +41 -41
- package/components/ui/sonner/sonner.test.tsx +24 -24
- package/components/ui/sonner/sonner.tsx +74 -74
- package/components/ui/stats-card/index.ts +2 -2
- package/components/ui/stats-card/stats-card-skeleton.tsx +1 -3
- package/components/ui/stats-card/stats-card.stories.tsx +99 -99
- package/components/ui/stats-card/stats-card.test.tsx +34 -34
- package/components/ui/stats-card/stats-card.tsx +93 -93
- package/components/ui/stepper/index.ts +3 -3
- package/components/ui/stepper/stepper.stories.tsx +171 -171
- package/components/ui/stepper/stepper.test.tsx +47 -47
- package/components/ui/stepper/stepper.tsx +190 -190
- package/components/ui/stepper/use-stepper.ts +139 -139
- package/components/ui/switch/index.ts +1 -1
- package/components/ui/switch/switch.stories.tsx +93 -93
- package/components/ui/switch/switch.test.tsx +44 -44
- package/components/ui/switch/switch.tsx +70 -70
- package/components/ui/table/index.ts +1 -1
- package/components/ui/table/table.stories.tsx +114 -114
- package/components/ui/table/table.test.tsx +43 -43
- package/components/ui/table/table.tsx +104 -104
- package/components/ui/tabs/index.ts +1 -1
- package/components/ui/tabs/tabs.stories.tsx +140 -140
- package/components/ui/tabs/tabs.test.tsx +50 -50
- package/components/ui/tabs/tabs.tsx +66 -66
- package/components/ui/textarea/index.ts +1 -1
- package/components/ui/textarea/textarea.stories.tsx +69 -69
- package/components/ui/textarea/textarea.test.tsx +41 -41
- package/components/ui/textarea/textarea.tsx +61 -61
- package/components/ui/timeline/index.ts +1 -1
- package/components/ui/timeline/timeline.stories.tsx +97 -97
- package/components/ui/timeline/timeline.test.tsx +53 -53
- package/components/ui/timeline/timeline.tsx +124 -124
- package/components/ui/toggle/index.ts +1 -1
- package/components/ui/toggle/toggle.stories.tsx +56 -56
- package/components/ui/toggle/toggle.test.tsx +32 -32
- package/components/ui/toggle/toggle.tsx +55 -55
- package/components/ui/toggle-group/index.ts +1 -1
- package/components/ui/toggle-group/toggle-group.stories.tsx +66 -66
- package/components/ui/toggle-group/toggle-group.test.tsx +47 -47
- package/components/ui/toggle-group/toggle-group.tsx +79 -79
- package/components/ui/tooltip/index.ts +1 -1
- package/components/ui/tooltip/tooltip.stories.tsx +83 -83
- package/components/ui/tooltip/tooltip.test.tsx +39 -39
- package/components/ui/tooltip/tooltip.tsx +69 -69
- package/components/ui/tree-view/index.ts +4 -4
- package/components/ui/tree-view/tree-view.stories.tsx +154 -154
- package/components/ui/tree-view/tree-view.test.tsx +58 -58
- package/components/ui/tree-view/tree-view.tsx +171 -171
- package/components/ui/tree-view/use-tree-view.ts +237 -237
- package/components.json +892 -892
- package/contexts/ApiKeyContext.test.tsx +26 -26
- package/contexts/ApiKeyContext.tsx +196 -196
- package/contexts/AssistenteContext.test.tsx +17 -17
- package/contexts/AssistenteContext.tsx +113 -113
- package/contexts/AuthContext.tsx +121 -118
- package/contexts/BrandColorsContext.test.tsx +21 -21
- package/contexts/BrandColorsContext.tsx +251 -251
- package/contexts/LanguageContext.tsx +1 -2
- package/contexts/LayoutContext.test.tsx +29 -29
- package/contexts/LayoutContext.tsx +140 -140
- package/contexts/ThemeContext.test.tsx +38 -38
- package/contexts/ThemeContext.tsx +111 -111
- package/contexts/index.ts +8 -8
- package/contexts/theme-data.ts +340 -340
- package/dist/AssistantChart-COGiOV-g.cjs +3541 -0
- package/dist/AssistantChart-CWX1OWNM.js +3373 -0
- package/dist/AudioPlayer-9psiEucT.cjs +1282 -0
- package/dist/AudioPlayer-Dp2bD1Gk.js +1278 -0
- package/dist/BrandColorsContext-DZT7JjeD.js +659 -0
- package/dist/BrandColorsContext-awnBCmC4.cjs +666 -0
- package/dist/CodeBlock-DYkTfR0f.js +221 -0
- package/dist/CodeBlock-EOvp9cVu.cjs +223 -0
- package/dist/CustomTooltipContent-BhdIeBEg.cjs +54 -0
- package/dist/CustomTooltipContent-CNbVB2NS.js +33 -0
- package/dist/FeatureCard-BZ4CYxFf.cjs +497 -0
- package/dist/FeatureCard-DNycVGwT.js +485 -0
- package/dist/FeatureCardSkeleton-DZqc96mt.js +27 -0
- package/dist/FeatureCardSkeleton-pTa0YNKP.cjs +29 -0
- package/dist/LayoutContext-BEq_-n98.cjs +96 -0
- package/dist/LayoutContext-DNl1xSoX.js +92 -0
- package/dist/ThemeContext-CMD3z2Dz.cjs +1930 -0
- package/dist/ThemeContext-x_F2zsnv.js +1923 -0
- package/dist/VerifyEmailPage-BJjAMUTW.js +3223 -0
- package/dist/VerifyEmailPage-Bv8Ah_TK.cjs +3235 -0
- package/dist/VerifyEmailPage-CkBYfsNy.cjs +3232 -0
- package/dist/VerifyEmailPage-Cyl55sJb.js +3226 -0
- package/dist/VerifyEmailPage-X14vhdyl.js +3296 -0
- package/dist/VerifyEmailPage-u_Dn7t1U.cjs +3305 -0
- package/dist/XerticaOrbe-Uk2JML1-.cjs +1927 -0
- package/dist/XerticaOrbe-jA5T2iOk.js +1925 -0
- package/dist/XerticaProvider-BErr83Bg.js +42 -0
- package/dist/XerticaProvider-CwOkHxiT.cjs +44 -0
- package/dist/XerticaProvider-DUOJg9iX.js +49 -0
- package/dist/XerticaProvider-Dl_b72_l.cjs +51 -0
- package/dist/XerticaXLogo-BX3ueACh.js +255 -0
- package/dist/XerticaXLogo-mqjoBiLI.js +252 -0
- package/dist/XerticaXLogo-qBPhwK3g.cjs +260 -0
- package/dist/XerticaXLogo-uQgwns_E.cjs +257 -0
- package/dist/alert-dialog-DhwPioBa.cjs +885 -0
- package/dist/alert-dialog-DqlRW_An.js +831 -0
- package/dist/assistant.cjs.js +8 -4
- package/dist/assistant.es.js +5 -11
- package/dist/avatar-3kO2Anrp.js +54 -0
- package/dist/avatar-BCM7YQRC.cjs +77 -0
- package/dist/blocks.cjs.js +9 -4
- package/dist/blocks.es.js +2 -16
- package/dist/brand.cjs.js +10 -5
- package/dist/brand.es.js +3 -11
- package/dist/breadcrumb-BKtHF4gk.cjs +98 -0
- package/dist/breadcrumb-ifNsA7Zl.js +90 -0
- package/dist/button-0BlA47It.cjs +85 -0
- package/dist/button-DZHzN1Gd.js +62 -0
- package/dist/cli.js +471 -93
- package/dist/components/brand/theme-toggle/ThemeToggle.d.ts +1 -1
- package/dist/components/index.d.ts +1 -1
- package/dist/dropdown-menu-BMcykFDf.cjs +225 -0
- package/dist/dropdown-menu-Dn_eV2Xb.js +190 -0
- package/dist/google-maps-loader-BCe58h9D.js +308 -0
- package/dist/google-maps-loader-casMyxlo.cjs +316 -0
- package/dist/hooks.cjs.js +12 -8
- package/dist/hooks.es.js +10 -27
- package/dist/index-9GWd0qxq.cjs +12 -0
- package/dist/index-BabBx2pa.js +6 -0
- package/dist/index.cjs.js +37 -32
- package/dist/index.es.js +30 -363
- package/dist/input-C_UiS2Py.cjs +152 -0
- package/dist/input-cc-PTD4R.js +123 -0
- package/dist/layout.cjs.js +10 -6
- package/dist/layout.es.js +7 -9
- package/dist/media.cjs.js +8 -3
- package/dist/media.es.js +1 -6
- package/dist/pages.cjs.js +8 -3
- package/dist/pages.es.js +1 -11
- package/dist/progress-C7Lti5wo.js +80 -0
- package/dist/progress-Cqwxbqs1.cjs +103 -0
- package/dist/rich-text-editor-DqLICivI.js +2832 -0
- package/dist/rich-text-editor-DxO1Hz3a.cjs +2903 -0
- package/dist/select-CH6v_KcQ.cjs +161 -0
- package/dist/select-D-xvCZK2.js +130 -0
- package/dist/sidebar-3XyzjVBw.js +792 -0
- package/dist/sidebar-B4ZWaMrE.js +792 -0
- package/dist/sidebar-BS1p2V7t.cjs +795 -0
- package/dist/sidebar-DyYvgyBj.cjs +795 -0
- package/dist/skeleton-DjiHerJn.cjs +87 -0
- package/dist/skeleton-DtR5tkYe.js +78 -0
- package/dist/slider-B00b9SVK.cjs +78 -0
- package/dist/slider-DQCNUUMj.js +56 -0
- package/dist/sonner-B-jWlik1.cjs +68 -0
- package/dist/sonner-C9tiqj4f.js +47 -0
- package/dist/tooltip-D8n9UYoU.cjs +72 -0
- package/dist/tooltip-RtbSmPYJ.js +48 -0
- package/dist/ui.cjs.js +23 -18
- package/dist/ui.es.js +16 -303
- package/dist/use-audio-player-B78fd2ct.js +188 -0
- package/dist/use-audio-player-DGvhPrgR.cjs +190 -0
- package/dist/use-mobile-BdXTRb0Z.cjs +51 -0
- package/dist/use-mobile-Ce2cBAQe.js +29 -0
- package/dist/xertica-assistant-B1NaSFFj.js +2173 -0
- package/dist/xertica-assistant-B687qEPU.js +2165 -0
- package/dist/xertica-assistant-CIaUlbIt.cjs +2180 -0
- package/dist/xertica-assistant-sOHwTgIP.cjs +2172 -0
- package/dist/xertica-ui.css +1 -1
- package/docs/ai-usage.md +195 -195
- package/docs/architecture-improvements.md +456 -456
- package/docs/architecture.md +312 -306
- package/docs/components/accordion.md +109 -109
- package/docs/components/alert-dialog.md +127 -127
- package/docs/components/alert.md +106 -106
- package/docs/components/aspect-ratio.md +58 -58
- package/docs/components/assistant-chart.md +47 -47
- package/docs/components/assistant.md +428 -426
- package/docs/components/audio-player.md +167 -167
- package/docs/components/avatar.md +101 -101
- package/docs/components/badge.md +84 -84
- package/docs/components/branding.md +252 -252
- package/docs/components/breadcrumb.md +104 -104
- package/docs/components/button.md +156 -156
- package/docs/components/calendar.md +141 -141
- package/docs/components/card-patterns.md +447 -445
- package/docs/components/card.md +245 -245
- package/docs/components/carousel.md +100 -100
- package/docs/components/chart.md +638 -638
- package/docs/components/checkbox.md +88 -88
- package/docs/components/code-block.md +105 -105
- package/docs/components/collapsible.md +86 -86
- package/docs/components/command.md +113 -113
- package/docs/components/context-menu.md +81 -81
- package/docs/components/dialog.md +198 -198
- package/docs/components/drawer.md +105 -105
- package/docs/components/dropdown-menu.md +127 -127
- package/docs/components/empty.md +127 -127
- package/docs/components/error-boundary.md +201 -191
- package/docs/components/file-upload.md +189 -189
- package/docs/components/floating-media-wrapper.md +63 -63
- package/docs/components/form.md +177 -177
- package/docs/components/formatted-document.md +105 -105
- package/docs/components/google-maps-loader.md +44 -44
- package/docs/components/header.md +177 -177
- package/docs/components/hooks.md +432 -430
- package/docs/components/hover-card.md +86 -86
- package/docs/components/image-with-fallback.md +107 -107
- package/docs/components/input-otp.md +95 -95
- package/docs/components/input.md +130 -130
- package/docs/components/label.md +69 -69
- package/docs/components/language-selector.md +20 -16
- package/docs/components/map-layers.md +138 -138
- package/docs/components/map.md +84 -84
- package/docs/components/markdown-message.md +47 -47
- package/docs/components/menubar.md +89 -89
- package/docs/components/modern-chat-input.md +164 -164
- package/docs/components/navigation-menu.md +83 -83
- package/docs/components/notification-badge.md +78 -78
- package/docs/components/page-header.md +93 -93
- package/docs/components/pages.md +323 -309
- package/docs/components/pagination.md +334 -334
- package/docs/components/popover.md +116 -116
- package/docs/components/progress.md +103 -103
- package/docs/components/radio-group.md +133 -133
- package/docs/components/rating.md +77 -77
- package/docs/components/resizable.md +84 -84
- package/docs/components/rich-text-editor.md +255 -255
- package/docs/components/route-map.md +124 -124
- package/docs/components/scroll-area.md +58 -58
- package/docs/components/search.md +87 -87
- package/docs/components/select.md +144 -144
- package/docs/components/separator.md +58 -58
- package/docs/components/sheet.md +122 -122
- package/docs/components/sidebar.md +314 -314
- package/docs/components/simple-map.md +51 -51
- package/docs/components/skeleton.md +99 -99
- package/docs/components/slider.md +84 -84
- package/docs/components/sonner.md +115 -115
- package/docs/components/stats-card.md +120 -120
- package/docs/components/stepper.md +268 -268
- package/docs/components/switch.md +106 -106
- package/docs/components/table.md +138 -138
- package/docs/components/tabs.md +117 -117
- package/docs/components/textarea.md +86 -86
- package/docs/components/theme-toggle.md +73 -73
- package/docs/components/timeline.md +121 -121
- package/docs/components/toggle-group.md +68 -68
- package/docs/components/toggle.md +62 -62
- package/docs/components/tooltip.md +116 -116
- package/docs/components/tree-view.md +238 -238
- package/docs/components/use-mobile.md +96 -96
- package/docs/components/video-player.md +68 -68
- package/docs/components/xertica-logo.md +36 -36
- package/docs/components/xertica-orbe.md +35 -35
- package/docs/components/xertica-provider.md +65 -65
- package/docs/components/xertica-xlogo.md +35 -35
- package/docs/decision-tree.md +293 -293
- package/docs/doc-audit.md +244 -243
- package/docs/form-sizing.md +162 -162
- package/docs/getting-started.md +616 -591
- package/docs/guidelines.md +330 -328
- package/docs/i18n.md +61 -57
- package/docs/installation.md +268 -267
- package/docs/layout.md +143 -143
- package/docs/llms.md +295 -295
- package/docs/patterns/analytics.md +194 -194
- package/docs/patterns/crud.md +149 -149
- package/docs/patterns/dashboard.md +138 -138
- package/docs/patterns/detail-page.md +296 -296
- package/docs/patterns/form.md +241 -241
- package/docs/patterns/login.md +156 -156
- package/docs/patterns/settings.md +368 -368
- package/docs/patterns/wizard.md +213 -213
- package/docs/state-management.md +289 -289
- package/guidelines/Guidelines.md +409 -406
- package/hooks/useTheme.test.tsx +16 -16
- package/hooks/useTheme.ts +4 -4
- package/imports/Podcast.tsx +540 -540
- package/imports/XerticaAi.tsx +46 -46
- package/imports/XerticaX.tsx +15 -15
- package/imports/svg-aueiaqngck.ts +20 -20
- package/imports/svg-v9krss1ozd.ts +23 -23
- package/imports/svg-vhrdofe3qe.ts +6 -6
- package/llms-compact.txt +2 -1
- package/llms.txt +2 -1
- package/mcp/resources.json +22 -22
- package/mcp/tools.json +35 -35
- package/package.json +219 -213
- package/scripts/ai-validator.ts +91 -91
- package/scripts/cleanup-case-dupes.ts +62 -62
- package/scripts/generate-ai-manifests.ts +107 -107
- package/styles/globals.css +13 -13
- package/styles/xertica/app-overrides/chat.css +61 -61
- package/styles/xertica/app-overrides/scrollbar.css +33 -33
- package/styles/xertica/base.css +90 -71
- package/styles/xertica/integrations/google-maps.css +76 -76
- package/styles/xertica/integrations/sonner.css +73 -73
- package/styles/xertica/theme-map.css +102 -99
- package/styles/xertica/tokens.css +240 -236
- package/templates/CLAUDE.md +16 -1
- package/templates/eslint.config.js +26 -26
- package/templates/guidelines/Guidelines.md +577 -553
- package/templates/package.json +69 -69
- package/templates/postcss.config.js +6 -6
- package/templates/src/app/App.tsx +46 -46
- package/templates/src/app/components/AppLayout.tsx +55 -55
- package/templates/src/app/components/AuthGuard.tsx +131 -82
- package/templates/src/app/context/AuthContext.tsx +108 -108
- package/templates/src/features/assistant/index.ts +5 -5
- package/templates/src/features/auth/index.ts +4 -4
- package/templates/src/features/auth/ui/AuthPageShell.tsx +32 -32
- package/templates/src/features/auth/ui/ForgotPasswordContent.tsx +70 -72
- package/templates/src/features/auth/ui/LoginContent.tsx +92 -92
- package/templates/src/features/auth/ui/ResetPasswordContent.tsx +6 -2
- package/templates/src/features/auth/ui/SocialLoginButtons.tsx +78 -78
- package/templates/src/features/auth/ui/VerifyEmailContent.tsx +2 -6
- package/templates/src/features/home/data/mock.ts +41 -35
- package/templates/src/features/home/index.ts +11 -11
- package/templates/src/features/home/store/dashboardStore.ts +25 -25
- package/templates/src/features/home/ui/HomeContent.tsx +117 -119
- package/templates/src/features/template/index.ts +5 -5
- package/templates/src/features/template/ui/CrudTemplate.tsx +1 -4
- package/templates/src/features/template/ui/LoginTemplate.tsx +1 -1
- package/templates/src/features/template/ui/TemplateContent.tsx +29 -21
- package/templates/src/locales/en/pages/templates.json +17 -17
- package/templates/src/locales/es/pages/templates.json +17 -17
- package/templates/src/locales/pt-BR/pages/templates.json +17 -17
- package/templates/src/main.tsx +11 -11
- package/templates/src/pages/AssistantPage.tsx +26 -20
- package/templates/src/pages/ForgotPasswordPage.tsx +6 -6
- package/templates/src/pages/HomePage.tsx +53 -49
- package/templates/src/pages/LoginPage.tsx +10 -10
- package/templates/src/pages/ResetPasswordPage.tsx +6 -6
- package/templates/src/pages/TemplatePage.tsx +28 -28
- package/templates/src/pages/VerifyEmailPage.tsx +6 -6
- package/templates/src/shared/config/navigation.ts +19 -19
- package/templates/src/shared/error-boundary.tsx +150 -154
- package/templates/src/shared/error-fallbacks.tsx +222 -226
- package/templates/src/shared/lib/auth.ts +20 -20
- package/templates/src/shared/types/auth.ts +3 -3
- package/templates/src/styles/index.css +95 -95
- package/templates/src/styles/xertica/tokens.css +240 -236
- package/templates/tsconfig.json +25 -25
- package/templates/tsconfig.node.json +12 -12
- package/templates/vite-env.d.ts +1 -1
- package/templates/vite.config.js +20 -20
- package/templates/vite.config.ts +54 -51
- package/utils/color-utils.ts +72 -72
- package/utils/demo-responses.test.ts +10 -10
- package/utils/demo-responses.ts +151 -151
- package/utils/gemini.test.ts +25 -25
- package/utils/gemini.ts +155 -155
|
@@ -1,465 +1,465 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import React, { createContext, useContext, ReactNode, useState, useEffect, useRef } from 'react';
|
|
4
|
-
import { GOOGLE_MAPS_LIBRARIES, GOOGLE_MAPS_ID } from '../map-config';
|
|
5
|
-
|
|
6
|
-
interface GoogleMapsContextType {
|
|
7
|
-
isLoaded: boolean;
|
|
8
|
-
loadError: Error | undefined;
|
|
9
|
-
load: (apiKey: string) => Promise<void>;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
const GoogleMapsContext = createContext<GoogleMapsContextType>({
|
|
13
|
-
isLoaded: false,
|
|
14
|
-
loadError: undefined,
|
|
15
|
-
load: () => Promise.resolve(),
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
// Singleton global para prevenir múltiplos carregamentos
|
|
19
|
-
declare global {
|
|
20
|
-
interface Window {
|
|
21
|
-
__XERTICA_GOOGLE_MAPS_LOADER__?: {
|
|
22
|
-
isLoaded: boolean;
|
|
23
|
-
loadError: Error | undefined;
|
|
24
|
-
listeners: Set<(state: GoogleMapsContextType) => void>;
|
|
25
|
-
scriptElement?: HTMLScriptElement;
|
|
26
|
-
};
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Helper that synchronously reads the Google Maps API key from the environment or localStorage
|
|
32
|
-
*/
|
|
33
|
-
function getInitialApiKey(): string | undefined {
|
|
34
|
-
// 1. Check Environment Variable (Vite)
|
|
35
|
-
if (
|
|
36
|
-
typeof import.meta !== 'undefined' &&
|
|
37
|
-
import.meta.env &&
|
|
38
|
-
import.meta.env.VITE_GOOGLE_MAPS_API_KEY
|
|
39
|
-
) {
|
|
40
|
-
return import.meta.env.VITE_GOOGLE_MAPS_API_KEY as string;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
if (typeof window === 'undefined') return undefined;
|
|
44
|
-
|
|
45
|
-
const savedKey = window.localStorage?.getItem('xertica-googlemaps-api-key');
|
|
46
|
-
|
|
47
|
-
if (savedKey && savedKey.trim().length > 0) {
|
|
48
|
-
return savedKey;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
return undefined;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Removes the existing Google Maps script tag and clears the global Maps object
|
|
56
|
-
*/
|
|
57
|
-
function removeExistingScript(): void {
|
|
58
|
-
if (typeof window === 'undefined') return;
|
|
59
|
-
|
|
60
|
-
// Remover callback global se existir
|
|
61
|
-
if ((window as any).__googleMapsCallback) {
|
|
62
|
-
delete (window as any).__googleMapsCallback;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
// Remover script existente
|
|
66
|
-
const existingScript = document.querySelector(`script[src*="maps.googleapis.com/maps/api/js"]`);
|
|
67
|
-
if (existingScript) {
|
|
68
|
-
existingScript.remove();
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// Limpar Google Maps do window
|
|
72
|
-
if ((window as any).google?.maps) {
|
|
73
|
-
delete (window as any).google.maps;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
// Limpar singleton para permitir novo carregamento
|
|
77
|
-
if (window.__XERTICA_GOOGLE_MAPS_LOADER__) {
|
|
78
|
-
window.__XERTICA_GOOGLE_MAPS_LOADER__.isLoaded = false;
|
|
79
|
-
window.__XERTICA_GOOGLE_MAPS_LOADER__.loadError = undefined;
|
|
80
|
-
window.__XERTICA_GOOGLE_MAPS_LOADER__.scriptElement = undefined;
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* Returns true if the Google Maps JS API has already been loaded
|
|
86
|
-
*/
|
|
87
|
-
function isGoogleMapsAlreadyLoaded(): boolean {
|
|
88
|
-
if (typeof window === 'undefined') return false;
|
|
89
|
-
return window.google?.maps?.version !== undefined;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* Returns true if the AdvancedMarkerElement marker library is available
|
|
94
|
-
*/
|
|
95
|
-
function isMarkerLibraryLoaded(): boolean {
|
|
96
|
-
if (typeof window === 'undefined') return false;
|
|
97
|
-
return window.google?.maps?.marker?.AdvancedMarkerElement !== undefined;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* Dynamically injects the Google Maps JS script tag and resolves when all libraries are loaded
|
|
102
|
-
*/
|
|
103
|
-
function loadGoogleMapsScript(apiKey?: string): Promise<void> {
|
|
104
|
-
return new Promise((resolve, reject) => {
|
|
105
|
-
if (typeof window === 'undefined') {
|
|
106
|
-
reject(new Error('Window is undefined'));
|
|
107
|
-
return;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
// Validar API key antes de carregar
|
|
111
|
-
if (!apiKey || apiKey.length < 10) {
|
|
112
|
-
reject(
|
|
113
|
-
new Error(
|
|
114
|
-
'Invalid or missing Google Maps API key. Please configure your API key in Settings.'
|
|
115
|
-
)
|
|
116
|
-
);
|
|
117
|
-
return;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
// Se já está carregado com a mesma key, resolver imediatamente
|
|
121
|
-
if (isGoogleMapsAlreadyLoaded() && isMarkerLibraryLoaded()) {
|
|
122
|
-
// Verificar se a API key atual é a mesma
|
|
123
|
-
const existingScript = document.querySelector(
|
|
124
|
-
`script[src*="maps.googleapis.com/maps/api/js"]`
|
|
125
|
-
) as HTMLScriptElement;
|
|
126
|
-
if (existingScript && existingScript.src.includes(`key=${apiKey}`)) {
|
|
127
|
-
resolve();
|
|
128
|
-
return;
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
// Verificar se o script já existe
|
|
133
|
-
const existing = document.querySelector(
|
|
134
|
-
`script[src*="maps.googleapis.com/maps/api/js"]`
|
|
135
|
-
) as HTMLScriptElement;
|
|
136
|
-
if (existing) {
|
|
137
|
-
// Se existe mas com chave diferente, logar aviso. Não podemos recarregar com SPA se componentes já foram definidos.
|
|
138
|
-
if (!existing.src.includes(`key=${apiKey}`)) {
|
|
139
|
-
console.warn(
|
|
140
|
-
'[GoogleMapsLoader] API key changed, but Google Maps cannot be reloaded without a full page refresh because custom elements are already defined.'
|
|
141
|
-
);
|
|
142
|
-
// Don't reject, just resolve to keep the app working with the old key until refresh
|
|
143
|
-
resolve();
|
|
144
|
-
return;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
// Aguardar o script carregar
|
|
148
|
-
if (isGoogleMapsAlreadyLoaded() && isMarkerLibraryLoaded()) {
|
|
149
|
-
resolve();
|
|
150
|
-
} else {
|
|
151
|
-
existing.addEventListener('load', () => {
|
|
152
|
-
setTimeout(() => {
|
|
153
|
-
if (isMarkerLibraryLoaded()) {
|
|
154
|
-
resolve();
|
|
155
|
-
} else {
|
|
156
|
-
reject(new Error('Marker library failed to load'));
|
|
157
|
-
}
|
|
158
|
-
}, 100);
|
|
159
|
-
});
|
|
160
|
-
existing.addEventListener('error', () => reject(new Error('Failed to load Google Maps')));
|
|
161
|
-
}
|
|
162
|
-
return;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
// Prevenir reinjeção se customElements já tem gmp-map (garantia final)
|
|
166
|
-
if (typeof customElements !== 'undefined' && customElements.get('gmp-map')) {
|
|
167
|
-
console.warn(
|
|
168
|
-
'[GoogleMapsLoader] gmp-map is already defined in customElements. Skipping script injection.'
|
|
169
|
-
);
|
|
170
|
-
resolve();
|
|
171
|
-
return;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
// Criar novo script
|
|
175
|
-
const script = document.createElement('script');
|
|
176
|
-
const params = new URLSearchParams({
|
|
177
|
-
key: apiKey, // SEMPRE incluir a API key
|
|
178
|
-
callback: '__googleMapsCallback',
|
|
179
|
-
libraries: GOOGLE_MAPS_LIBRARIES.join(','),
|
|
180
|
-
v: 'quarterly', // Usar 'quarterly' para versão estável ao invés de 'weekly'
|
|
181
|
-
loading: 'async',
|
|
182
|
-
});
|
|
183
|
-
|
|
184
|
-
script.src = `https://maps.googleapis.com/maps/api/js?${params.toString()}`;
|
|
185
|
-
script.async = true;
|
|
186
|
-
script.defer = true;
|
|
187
|
-
script.id = GOOGLE_MAPS_ID;
|
|
188
|
-
|
|
189
|
-
// Callback global
|
|
190
|
-
(window as any).__googleMapsCallback = () => {
|
|
191
|
-
delete (window as any).__googleMapsCallback;
|
|
192
|
-
// Aguardar um pouco para garantir que todas as bibliotecas foram carregadas
|
|
193
|
-
setTimeout(() => {
|
|
194
|
-
if (isMarkerLibraryLoaded()) {
|
|
195
|
-
resolve();
|
|
196
|
-
} else {
|
|
197
|
-
reject(new Error('Marker library failed to load'));
|
|
198
|
-
}
|
|
199
|
-
}, 100);
|
|
200
|
-
};
|
|
201
|
-
|
|
202
|
-
script.addEventListener('error', () => {
|
|
203
|
-
delete (window as any).__googleMapsCallback;
|
|
204
|
-
reject(
|
|
205
|
-
new Error(
|
|
206
|
-
'Failed to load Google Maps script. Please check your API key and ensure Maps JavaScript API is enabled.'
|
|
207
|
-
)
|
|
208
|
-
);
|
|
209
|
-
});
|
|
210
|
-
|
|
211
|
-
document.head.appendChild(script);
|
|
212
|
-
|
|
213
|
-
// Salvar referência ao script
|
|
214
|
-
const singleton = getOrCreateSingleton();
|
|
215
|
-
if (singleton) {
|
|
216
|
-
singleton.scriptElement = script;
|
|
217
|
-
}
|
|
218
|
-
});
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
/**
|
|
222
|
-
* Inicializa o singleton global
|
|
223
|
-
*/
|
|
224
|
-
function getOrCreateSingleton() {
|
|
225
|
-
if (typeof window === 'undefined') return null;
|
|
226
|
-
|
|
227
|
-
if (!window.__XERTICA_GOOGLE_MAPS_LOADER__) {
|
|
228
|
-
const isPreloaded = isGoogleMapsAlreadyLoaded();
|
|
229
|
-
|
|
230
|
-
window.__XERTICA_GOOGLE_MAPS_LOADER__ = {
|
|
231
|
-
isLoaded: isPreloaded,
|
|
232
|
-
loadError: undefined,
|
|
233
|
-
listeners: new Set(),
|
|
234
|
-
scriptElement: undefined,
|
|
235
|
-
};
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
return window.__XERTICA_GOOGLE_MAPS_LOADER__;
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
/**
|
|
242
|
-
* Atualiza o estado do singleton e notifica listeners
|
|
243
|
-
*/
|
|
244
|
-
function updateSingleton(state: Partial<GoogleMapsContextType>) {
|
|
245
|
-
const singleton = getOrCreateSingleton();
|
|
246
|
-
if (!singleton) return;
|
|
247
|
-
|
|
248
|
-
if (state.isLoaded !== undefined) singleton.isLoaded = state.isLoaded;
|
|
249
|
-
if (state.loadError !== undefined) singleton.loadError = state.loadError;
|
|
250
|
-
|
|
251
|
-
// Prepare safe state to notify
|
|
252
|
-
const newState: GoogleMapsContextType = {
|
|
253
|
-
isLoaded: singleton.isLoaded,
|
|
254
|
-
loadError: singleton.loadError,
|
|
255
|
-
load: async (apiKey: string) => {
|
|
256
|
-
// Allow manual loading call via stored function in context if needed,
|
|
257
|
-
// though typically we use the direct export or loadGoogleMapsScript
|
|
258
|
-
if (singleton.isLoaded) return;
|
|
259
|
-
try {
|
|
260
|
-
await loadGoogleMapsScript(apiKey);
|
|
261
|
-
updateSingleton({ isLoaded: true, loadError: undefined });
|
|
262
|
-
} catch (error: any) {
|
|
263
|
-
updateSingleton({ isLoaded: false, loadError: error });
|
|
264
|
-
throw error;
|
|
265
|
-
}
|
|
266
|
-
},
|
|
267
|
-
};
|
|
268
|
-
|
|
269
|
-
singleton.listeners.forEach(listener => listener(newState));
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
/**
|
|
273
|
-
* Internal component that subscribes to the singleton loader state
|
|
274
|
-
*/
|
|
275
|
-
const SingletonLoaderWrapper = ({ children }: { children: ReactNode }) => {
|
|
276
|
-
const [state, setState] = useState<GoogleMapsContextType>(() => {
|
|
277
|
-
const singleton = getOrCreateSingleton();
|
|
278
|
-
|
|
279
|
-
// Default load function that triggers the script load
|
|
280
|
-
const loadFn = async (apiKey: string) => {
|
|
281
|
-
if (singleton?.isLoaded) return;
|
|
282
|
-
try {
|
|
283
|
-
await loadGoogleMapsScript(apiKey);
|
|
284
|
-
updateSingleton({ isLoaded: true, loadError: undefined });
|
|
285
|
-
} catch (error: any) {
|
|
286
|
-
updateSingleton({ isLoaded: false, loadError: error });
|
|
287
|
-
throw error;
|
|
288
|
-
}
|
|
289
|
-
};
|
|
290
|
-
|
|
291
|
-
if (!singleton) return { isLoaded: false, loadError: undefined, load: loadFn };
|
|
292
|
-
|
|
293
|
-
return {
|
|
294
|
-
isLoaded: singleton.isLoaded,
|
|
295
|
-
loadError: singleton.loadError,
|
|
296
|
-
load: loadFn,
|
|
297
|
-
};
|
|
298
|
-
});
|
|
299
|
-
|
|
300
|
-
useEffect(() => {
|
|
301
|
-
const singleton = getOrCreateSingleton();
|
|
302
|
-
if (!singleton) return;
|
|
303
|
-
|
|
304
|
-
const listener = (newState: GoogleMapsContextType) => {
|
|
305
|
-
setState(newState);
|
|
306
|
-
};
|
|
307
|
-
|
|
308
|
-
singleton.listeners.add(listener);
|
|
309
|
-
|
|
310
|
-
// Sincronizar estado inicial e função de load
|
|
311
|
-
listener({
|
|
312
|
-
isLoaded: singleton.isLoaded,
|
|
313
|
-
loadError: singleton.loadError,
|
|
314
|
-
load: state.load,
|
|
315
|
-
});
|
|
316
|
-
|
|
317
|
-
return () => {
|
|
318
|
-
singleton.listeners.delete(listener);
|
|
319
|
-
};
|
|
320
|
-
}, []);
|
|
321
|
-
|
|
322
|
-
return <GoogleMapsContext.Provider value={state}>{children}</GoogleMapsContext.Provider>;
|
|
323
|
-
};
|
|
324
|
-
|
|
325
|
-
/**
|
|
326
|
-
* Internal component that manually triggers the Google Maps script load
|
|
327
|
-
*/
|
|
328
|
-
const LoaderInitializer = ({ apiKey }: { apiKey?: string }) => {
|
|
329
|
-
const hasInitializedRef = useRef(false);
|
|
330
|
-
|
|
331
|
-
useEffect(() => {
|
|
332
|
-
// Prevenir múltiplas execuções
|
|
333
|
-
if (hasInitializedRef.current) {
|
|
334
|
-
return;
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
const singleton = getOrCreateSingleton();
|
|
338
|
-
if (!singleton) return;
|
|
339
|
-
|
|
340
|
-
// Se já está carregado, atualizar estado
|
|
341
|
-
if (isGoogleMapsAlreadyLoaded() && isMarkerLibraryLoaded()) {
|
|
342
|
-
updateSingleton({ isLoaded: true, loadError: undefined });
|
|
343
|
-
hasInitializedRef.current = true;
|
|
344
|
-
return;
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
hasInitializedRef.current = true;
|
|
348
|
-
|
|
349
|
-
// Use prop key OR get from storage
|
|
350
|
-
const keyToUse = apiKey || getInitialApiKey();
|
|
351
|
-
|
|
352
|
-
// Se não houver API key, apenas marcar como não carregado (sem erro)
|
|
353
|
-
if (!keyToUse) {
|
|
354
|
-
updateSingleton({
|
|
355
|
-
isLoaded: false,
|
|
356
|
-
loadError: undefined, // Não definir erro quando não há API key
|
|
357
|
-
});
|
|
358
|
-
return;
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
loadGoogleMapsScript(keyToUse)
|
|
362
|
-
.then(() => {
|
|
363
|
-
updateSingleton({ isLoaded: true, loadError: undefined });
|
|
364
|
-
})
|
|
365
|
-
.catch(error => {
|
|
366
|
-
updateSingleton({ isLoaded: false, loadError: error });
|
|
367
|
-
});
|
|
368
|
-
}, [apiKey]);
|
|
369
|
-
|
|
370
|
-
return null;
|
|
371
|
-
};
|
|
372
|
-
|
|
373
|
-
interface GoogleMapsLoaderProviderProps {
|
|
374
|
-
children: ReactNode;
|
|
375
|
-
apiKey?: string;
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
/**
|
|
379
|
-
* GoogleMapsLoaderProvider
|
|
380
|
-
*
|
|
381
|
-
* Provider global que gerencia o carregamento da API do Google Maps.
|
|
382
|
-
* Usa carregamento manual do script para evitar conflitos com custom elements.
|
|
383
|
-
*/
|
|
384
|
-
export const GoogleMapsLoaderProvider = ({ children, apiKey }: GoogleMapsLoaderProviderProps) => {
|
|
385
|
-
const [shouldInitialize] = useState(() => {
|
|
386
|
-
const singleton = getOrCreateSingleton();
|
|
387
|
-
if (!singleton) return false;
|
|
388
|
-
|
|
389
|
-
// Se já está carregado, não inicializar
|
|
390
|
-
if (singleton.isLoaded || isGoogleMapsAlreadyLoaded() || isMarkerLibraryLoaded()) {
|
|
391
|
-
singleton.isLoaded = true;
|
|
392
|
-
return false;
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
return true;
|
|
396
|
-
});
|
|
397
|
-
|
|
398
|
-
return (
|
|
399
|
-
<>
|
|
400
|
-
{shouldInitialize && <LoaderInitializer apiKey={apiKey} />}
|
|
401
|
-
<SingletonLoaderWrapper>{children}</SingletonLoaderWrapper>
|
|
402
|
-
</>
|
|
403
|
-
);
|
|
404
|
-
};
|
|
405
|
-
|
|
406
|
-
export const useGoogleMapsLoader = () => useContext(GoogleMapsContext);
|
|
407
|
-
|
|
408
|
-
/**
|
|
409
|
-
* Recarrega o Google Maps com uma nova API key
|
|
410
|
-
*/
|
|
411
|
-
export function reloadGoogleMaps(newApiKey: string): Promise<void> {
|
|
412
|
-
return new Promise((resolve, reject) => {
|
|
413
|
-
if (typeof window === 'undefined') {
|
|
414
|
-
reject(new Error('Window is undefined'));
|
|
415
|
-
return;
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
// Validar API key
|
|
419
|
-
if (!newApiKey || newApiKey.length < 10) {
|
|
420
|
-
reject(new Error('Invalid or missing Google Maps API key'));
|
|
421
|
-
return;
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
// Verificar se a key atual é a mesma
|
|
425
|
-
const existingScript = document.querySelector(
|
|
426
|
-
`script[src*="maps.googleapis.com/maps/api/js"]`
|
|
427
|
-
) as HTMLScriptElement;
|
|
428
|
-
if (existingScript && existingScript.src.includes(`key=${newApiKey}`)) {
|
|
429
|
-
// Mesma key, apenas resolver (ou esperar carregar)
|
|
430
|
-
resolve();
|
|
431
|
-
return;
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
// Se a chave for diferente, NÃO PODEMOS remover o script e adicionar outro
|
|
435
|
-
// se o Google Maps já definiu custom elements. Isso causa o erro:
|
|
436
|
-
// "Element with name 'gmp-...' already defined"
|
|
437
|
-
if (typeof customElements !== 'undefined' && customElements.get('gmp-map')) {
|
|
438
|
-
console.warn(
|
|
439
|
-
'[GoogleMapsLoader] Cannot reload map API dynamically because custom elements (gmp-map) are already registered. A full page reload is required to apply the new API key.'
|
|
440
|
-
);
|
|
441
|
-
resolve(); // Resolver para não quebrar a UI
|
|
442
|
-
return;
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
// Se chegou aqui e não tem custom elements, podemos tentar recarregar
|
|
446
|
-
removeExistingScript();
|
|
447
|
-
|
|
448
|
-
// Atualizar singleton
|
|
449
|
-
updateSingleton({ isLoaded: false, loadError: undefined });
|
|
450
|
-
|
|
451
|
-
// Carregar novo script
|
|
452
|
-
loadGoogleMapsScript(newApiKey)
|
|
453
|
-
.then(() => {
|
|
454
|
-
updateSingleton({ isLoaded: true, loadError: undefined });
|
|
455
|
-
resolve();
|
|
456
|
-
})
|
|
457
|
-
.catch(error => {
|
|
458
|
-
updateSingleton({ isLoaded: false, loadError: error });
|
|
459
|
-
reject(error);
|
|
460
|
-
});
|
|
461
|
-
});
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
// Alias for backwards compatibility
|
|
465
|
-
export const GoogleMapsProvider = GoogleMapsLoaderProvider;
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import React, { createContext, useContext, ReactNode, useState, useEffect, useRef } from 'react';
|
|
4
|
+
import { GOOGLE_MAPS_LIBRARIES, GOOGLE_MAPS_ID } from '../map-config';
|
|
5
|
+
|
|
6
|
+
interface GoogleMapsContextType {
|
|
7
|
+
isLoaded: boolean;
|
|
8
|
+
loadError: Error | undefined;
|
|
9
|
+
load: (apiKey: string) => Promise<void>;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const GoogleMapsContext = createContext<GoogleMapsContextType>({
|
|
13
|
+
isLoaded: false,
|
|
14
|
+
loadError: undefined,
|
|
15
|
+
load: () => Promise.resolve(),
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
// Singleton global para prevenir múltiplos carregamentos
|
|
19
|
+
declare global {
|
|
20
|
+
interface Window {
|
|
21
|
+
__XERTICA_GOOGLE_MAPS_LOADER__?: {
|
|
22
|
+
isLoaded: boolean;
|
|
23
|
+
loadError: Error | undefined;
|
|
24
|
+
listeners: Set<(state: GoogleMapsContextType) => void>;
|
|
25
|
+
scriptElement?: HTMLScriptElement;
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Helper that synchronously reads the Google Maps API key from the environment or localStorage
|
|
32
|
+
*/
|
|
33
|
+
function getInitialApiKey(): string | undefined {
|
|
34
|
+
// 1. Check Environment Variable (Vite)
|
|
35
|
+
if (
|
|
36
|
+
typeof import.meta !== 'undefined' &&
|
|
37
|
+
import.meta.env &&
|
|
38
|
+
import.meta.env.VITE_GOOGLE_MAPS_API_KEY
|
|
39
|
+
) {
|
|
40
|
+
return import.meta.env.VITE_GOOGLE_MAPS_API_KEY as string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (typeof window === 'undefined') return undefined;
|
|
44
|
+
|
|
45
|
+
const savedKey = window.localStorage?.getItem('xertica-googlemaps-api-key');
|
|
46
|
+
|
|
47
|
+
if (savedKey && savedKey.trim().length > 0) {
|
|
48
|
+
return savedKey;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Removes the existing Google Maps script tag and clears the global Maps object
|
|
56
|
+
*/
|
|
57
|
+
function removeExistingScript(): void {
|
|
58
|
+
if (typeof window === 'undefined') return;
|
|
59
|
+
|
|
60
|
+
// Remover callback global se existir
|
|
61
|
+
if ((window as any).__googleMapsCallback) {
|
|
62
|
+
delete (window as any).__googleMapsCallback;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Remover script existente
|
|
66
|
+
const existingScript = document.querySelector(`script[src*="maps.googleapis.com/maps/api/js"]`);
|
|
67
|
+
if (existingScript) {
|
|
68
|
+
existingScript.remove();
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Limpar Google Maps do window
|
|
72
|
+
if ((window as any).google?.maps) {
|
|
73
|
+
delete (window as any).google.maps;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Limpar singleton para permitir novo carregamento
|
|
77
|
+
if (window.__XERTICA_GOOGLE_MAPS_LOADER__) {
|
|
78
|
+
window.__XERTICA_GOOGLE_MAPS_LOADER__.isLoaded = false;
|
|
79
|
+
window.__XERTICA_GOOGLE_MAPS_LOADER__.loadError = undefined;
|
|
80
|
+
window.__XERTICA_GOOGLE_MAPS_LOADER__.scriptElement = undefined;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Returns true if the Google Maps JS API has already been loaded
|
|
86
|
+
*/
|
|
87
|
+
function isGoogleMapsAlreadyLoaded(): boolean {
|
|
88
|
+
if (typeof window === 'undefined') return false;
|
|
89
|
+
return window.google?.maps?.version !== undefined;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Returns true if the AdvancedMarkerElement marker library is available
|
|
94
|
+
*/
|
|
95
|
+
function isMarkerLibraryLoaded(): boolean {
|
|
96
|
+
if (typeof window === 'undefined') return false;
|
|
97
|
+
return window.google?.maps?.marker?.AdvancedMarkerElement !== undefined;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Dynamically injects the Google Maps JS script tag and resolves when all libraries are loaded
|
|
102
|
+
*/
|
|
103
|
+
function loadGoogleMapsScript(apiKey?: string): Promise<void> {
|
|
104
|
+
return new Promise((resolve, reject) => {
|
|
105
|
+
if (typeof window === 'undefined') {
|
|
106
|
+
reject(new Error('Window is undefined'));
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Validar API key antes de carregar
|
|
111
|
+
if (!apiKey || apiKey.length < 10) {
|
|
112
|
+
reject(
|
|
113
|
+
new Error(
|
|
114
|
+
'Invalid or missing Google Maps API key. Please configure your API key in Settings.'
|
|
115
|
+
)
|
|
116
|
+
);
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Se já está carregado com a mesma key, resolver imediatamente
|
|
121
|
+
if (isGoogleMapsAlreadyLoaded() && isMarkerLibraryLoaded()) {
|
|
122
|
+
// Verificar se a API key atual é a mesma
|
|
123
|
+
const existingScript = document.querySelector(
|
|
124
|
+
`script[src*="maps.googleapis.com/maps/api/js"]`
|
|
125
|
+
) as HTMLScriptElement;
|
|
126
|
+
if (existingScript && existingScript.src.includes(`key=${apiKey}`)) {
|
|
127
|
+
resolve();
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Verificar se o script já existe
|
|
133
|
+
const existing = document.querySelector(
|
|
134
|
+
`script[src*="maps.googleapis.com/maps/api/js"]`
|
|
135
|
+
) as HTMLScriptElement;
|
|
136
|
+
if (existing) {
|
|
137
|
+
// Se existe mas com chave diferente, logar aviso. Não podemos recarregar com SPA se componentes já foram definidos.
|
|
138
|
+
if (!existing.src.includes(`key=${apiKey}`)) {
|
|
139
|
+
console.warn(
|
|
140
|
+
'[GoogleMapsLoader] API key changed, but Google Maps cannot be reloaded without a full page refresh because custom elements are already defined.'
|
|
141
|
+
);
|
|
142
|
+
// Don't reject, just resolve to keep the app working with the old key until refresh
|
|
143
|
+
resolve();
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Aguardar o script carregar
|
|
148
|
+
if (isGoogleMapsAlreadyLoaded() && isMarkerLibraryLoaded()) {
|
|
149
|
+
resolve();
|
|
150
|
+
} else {
|
|
151
|
+
existing.addEventListener('load', () => {
|
|
152
|
+
setTimeout(() => {
|
|
153
|
+
if (isMarkerLibraryLoaded()) {
|
|
154
|
+
resolve();
|
|
155
|
+
} else {
|
|
156
|
+
reject(new Error('Marker library failed to load'));
|
|
157
|
+
}
|
|
158
|
+
}, 100);
|
|
159
|
+
});
|
|
160
|
+
existing.addEventListener('error', () => reject(new Error('Failed to load Google Maps')));
|
|
161
|
+
}
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Prevenir reinjeção se customElements já tem gmp-map (garantia final)
|
|
166
|
+
if (typeof customElements !== 'undefined' && customElements.get('gmp-map')) {
|
|
167
|
+
console.warn(
|
|
168
|
+
'[GoogleMapsLoader] gmp-map is already defined in customElements. Skipping script injection.'
|
|
169
|
+
);
|
|
170
|
+
resolve();
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Criar novo script
|
|
175
|
+
const script = document.createElement('script');
|
|
176
|
+
const params = new URLSearchParams({
|
|
177
|
+
key: apiKey, // SEMPRE incluir a API key
|
|
178
|
+
callback: '__googleMapsCallback',
|
|
179
|
+
libraries: GOOGLE_MAPS_LIBRARIES.join(','),
|
|
180
|
+
v: 'quarterly', // Usar 'quarterly' para versão estável ao invés de 'weekly'
|
|
181
|
+
loading: 'async',
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
script.src = `https://maps.googleapis.com/maps/api/js?${params.toString()}`;
|
|
185
|
+
script.async = true;
|
|
186
|
+
script.defer = true;
|
|
187
|
+
script.id = GOOGLE_MAPS_ID;
|
|
188
|
+
|
|
189
|
+
// Callback global
|
|
190
|
+
(window as any).__googleMapsCallback = () => {
|
|
191
|
+
delete (window as any).__googleMapsCallback;
|
|
192
|
+
// Aguardar um pouco para garantir que todas as bibliotecas foram carregadas
|
|
193
|
+
setTimeout(() => {
|
|
194
|
+
if (isMarkerLibraryLoaded()) {
|
|
195
|
+
resolve();
|
|
196
|
+
} else {
|
|
197
|
+
reject(new Error('Marker library failed to load'));
|
|
198
|
+
}
|
|
199
|
+
}, 100);
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
script.addEventListener('error', () => {
|
|
203
|
+
delete (window as any).__googleMapsCallback;
|
|
204
|
+
reject(
|
|
205
|
+
new Error(
|
|
206
|
+
'Failed to load Google Maps script. Please check your API key and ensure Maps JavaScript API is enabled.'
|
|
207
|
+
)
|
|
208
|
+
);
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
document.head.appendChild(script);
|
|
212
|
+
|
|
213
|
+
// Salvar referência ao script
|
|
214
|
+
const singleton = getOrCreateSingleton();
|
|
215
|
+
if (singleton) {
|
|
216
|
+
singleton.scriptElement = script;
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Inicializa o singleton global
|
|
223
|
+
*/
|
|
224
|
+
function getOrCreateSingleton() {
|
|
225
|
+
if (typeof window === 'undefined') return null;
|
|
226
|
+
|
|
227
|
+
if (!window.__XERTICA_GOOGLE_MAPS_LOADER__) {
|
|
228
|
+
const isPreloaded = isGoogleMapsAlreadyLoaded();
|
|
229
|
+
|
|
230
|
+
window.__XERTICA_GOOGLE_MAPS_LOADER__ = {
|
|
231
|
+
isLoaded: isPreloaded,
|
|
232
|
+
loadError: undefined,
|
|
233
|
+
listeners: new Set(),
|
|
234
|
+
scriptElement: undefined,
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return window.__XERTICA_GOOGLE_MAPS_LOADER__;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Atualiza o estado do singleton e notifica listeners
|
|
243
|
+
*/
|
|
244
|
+
function updateSingleton(state: Partial<GoogleMapsContextType>) {
|
|
245
|
+
const singleton = getOrCreateSingleton();
|
|
246
|
+
if (!singleton) return;
|
|
247
|
+
|
|
248
|
+
if (state.isLoaded !== undefined) singleton.isLoaded = state.isLoaded;
|
|
249
|
+
if (state.loadError !== undefined) singleton.loadError = state.loadError;
|
|
250
|
+
|
|
251
|
+
// Prepare safe state to notify
|
|
252
|
+
const newState: GoogleMapsContextType = {
|
|
253
|
+
isLoaded: singleton.isLoaded,
|
|
254
|
+
loadError: singleton.loadError,
|
|
255
|
+
load: async (apiKey: string) => {
|
|
256
|
+
// Allow manual loading call via stored function in context if needed,
|
|
257
|
+
// though typically we use the direct export or loadGoogleMapsScript
|
|
258
|
+
if (singleton.isLoaded) return;
|
|
259
|
+
try {
|
|
260
|
+
await loadGoogleMapsScript(apiKey);
|
|
261
|
+
updateSingleton({ isLoaded: true, loadError: undefined });
|
|
262
|
+
} catch (error: any) {
|
|
263
|
+
updateSingleton({ isLoaded: false, loadError: error });
|
|
264
|
+
throw error;
|
|
265
|
+
}
|
|
266
|
+
},
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
singleton.listeners.forEach(listener => listener(newState));
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Internal component that subscribes to the singleton loader state
|
|
274
|
+
*/
|
|
275
|
+
const SingletonLoaderWrapper = ({ children }: { children: ReactNode }) => {
|
|
276
|
+
const [state, setState] = useState<GoogleMapsContextType>(() => {
|
|
277
|
+
const singleton = getOrCreateSingleton();
|
|
278
|
+
|
|
279
|
+
// Default load function that triggers the script load
|
|
280
|
+
const loadFn = async (apiKey: string) => {
|
|
281
|
+
if (singleton?.isLoaded) return;
|
|
282
|
+
try {
|
|
283
|
+
await loadGoogleMapsScript(apiKey);
|
|
284
|
+
updateSingleton({ isLoaded: true, loadError: undefined });
|
|
285
|
+
} catch (error: any) {
|
|
286
|
+
updateSingleton({ isLoaded: false, loadError: error });
|
|
287
|
+
throw error;
|
|
288
|
+
}
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
if (!singleton) return { isLoaded: false, loadError: undefined, load: loadFn };
|
|
292
|
+
|
|
293
|
+
return {
|
|
294
|
+
isLoaded: singleton.isLoaded,
|
|
295
|
+
loadError: singleton.loadError,
|
|
296
|
+
load: loadFn,
|
|
297
|
+
};
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
useEffect(() => {
|
|
301
|
+
const singleton = getOrCreateSingleton();
|
|
302
|
+
if (!singleton) return;
|
|
303
|
+
|
|
304
|
+
const listener = (newState: GoogleMapsContextType) => {
|
|
305
|
+
setState(newState);
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
singleton.listeners.add(listener);
|
|
309
|
+
|
|
310
|
+
// Sincronizar estado inicial e função de load
|
|
311
|
+
listener({
|
|
312
|
+
isLoaded: singleton.isLoaded,
|
|
313
|
+
loadError: singleton.loadError,
|
|
314
|
+
load: state.load,
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
return () => {
|
|
318
|
+
singleton.listeners.delete(listener);
|
|
319
|
+
};
|
|
320
|
+
}, []);
|
|
321
|
+
|
|
322
|
+
return <GoogleMapsContext.Provider value={state}>{children}</GoogleMapsContext.Provider>;
|
|
323
|
+
};
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Internal component that manually triggers the Google Maps script load
|
|
327
|
+
*/
|
|
328
|
+
const LoaderInitializer = ({ apiKey }: { apiKey?: string }) => {
|
|
329
|
+
const hasInitializedRef = useRef(false);
|
|
330
|
+
|
|
331
|
+
useEffect(() => {
|
|
332
|
+
// Prevenir múltiplas execuções
|
|
333
|
+
if (hasInitializedRef.current) {
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
const singleton = getOrCreateSingleton();
|
|
338
|
+
if (!singleton) return;
|
|
339
|
+
|
|
340
|
+
// Se já está carregado, atualizar estado
|
|
341
|
+
if (isGoogleMapsAlreadyLoaded() && isMarkerLibraryLoaded()) {
|
|
342
|
+
updateSingleton({ isLoaded: true, loadError: undefined });
|
|
343
|
+
hasInitializedRef.current = true;
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
hasInitializedRef.current = true;
|
|
348
|
+
|
|
349
|
+
// Use prop key OR get from storage
|
|
350
|
+
const keyToUse = apiKey || getInitialApiKey();
|
|
351
|
+
|
|
352
|
+
// Se não houver API key, apenas marcar como não carregado (sem erro)
|
|
353
|
+
if (!keyToUse) {
|
|
354
|
+
updateSingleton({
|
|
355
|
+
isLoaded: false,
|
|
356
|
+
loadError: undefined, // Não definir erro quando não há API key
|
|
357
|
+
});
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
loadGoogleMapsScript(keyToUse)
|
|
362
|
+
.then(() => {
|
|
363
|
+
updateSingleton({ isLoaded: true, loadError: undefined });
|
|
364
|
+
})
|
|
365
|
+
.catch(error => {
|
|
366
|
+
updateSingleton({ isLoaded: false, loadError: error });
|
|
367
|
+
});
|
|
368
|
+
}, [apiKey]);
|
|
369
|
+
|
|
370
|
+
return null;
|
|
371
|
+
};
|
|
372
|
+
|
|
373
|
+
interface GoogleMapsLoaderProviderProps {
|
|
374
|
+
children: ReactNode;
|
|
375
|
+
apiKey?: string;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* GoogleMapsLoaderProvider
|
|
380
|
+
*
|
|
381
|
+
* Provider global que gerencia o carregamento da API do Google Maps.
|
|
382
|
+
* Usa carregamento manual do script para evitar conflitos com custom elements.
|
|
383
|
+
*/
|
|
384
|
+
export const GoogleMapsLoaderProvider = ({ children, apiKey }: GoogleMapsLoaderProviderProps) => {
|
|
385
|
+
const [shouldInitialize] = useState(() => {
|
|
386
|
+
const singleton = getOrCreateSingleton();
|
|
387
|
+
if (!singleton) return false;
|
|
388
|
+
|
|
389
|
+
// Se já está carregado, não inicializar
|
|
390
|
+
if (singleton.isLoaded || isGoogleMapsAlreadyLoaded() || isMarkerLibraryLoaded()) {
|
|
391
|
+
singleton.isLoaded = true;
|
|
392
|
+
return false;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
return true;
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
return (
|
|
399
|
+
<>
|
|
400
|
+
{shouldInitialize && <LoaderInitializer apiKey={apiKey} />}
|
|
401
|
+
<SingletonLoaderWrapper>{children}</SingletonLoaderWrapper>
|
|
402
|
+
</>
|
|
403
|
+
);
|
|
404
|
+
};
|
|
405
|
+
|
|
406
|
+
export const useGoogleMapsLoader = () => useContext(GoogleMapsContext);
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* Recarrega o Google Maps com uma nova API key
|
|
410
|
+
*/
|
|
411
|
+
export function reloadGoogleMaps(newApiKey: string): Promise<void> {
|
|
412
|
+
return new Promise((resolve, reject) => {
|
|
413
|
+
if (typeof window === 'undefined') {
|
|
414
|
+
reject(new Error('Window is undefined'));
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// Validar API key
|
|
419
|
+
if (!newApiKey || newApiKey.length < 10) {
|
|
420
|
+
reject(new Error('Invalid or missing Google Maps API key'));
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// Verificar se a key atual é a mesma
|
|
425
|
+
const existingScript = document.querySelector(
|
|
426
|
+
`script[src*="maps.googleapis.com/maps/api/js"]`
|
|
427
|
+
) as HTMLScriptElement;
|
|
428
|
+
if (existingScript && existingScript.src.includes(`key=${newApiKey}`)) {
|
|
429
|
+
// Mesma key, apenas resolver (ou esperar carregar)
|
|
430
|
+
resolve();
|
|
431
|
+
return;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// Se a chave for diferente, NÃO PODEMOS remover o script e adicionar outro
|
|
435
|
+
// se o Google Maps já definiu custom elements. Isso causa o erro:
|
|
436
|
+
// "Element with name 'gmp-...' already defined"
|
|
437
|
+
if (typeof customElements !== 'undefined' && customElements.get('gmp-map')) {
|
|
438
|
+
console.warn(
|
|
439
|
+
'[GoogleMapsLoader] Cannot reload map API dynamically because custom elements (gmp-map) are already registered. A full page reload is required to apply the new API key.'
|
|
440
|
+
);
|
|
441
|
+
resolve(); // Resolver para não quebrar a UI
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
// Se chegou aqui e não tem custom elements, podemos tentar recarregar
|
|
446
|
+
removeExistingScript();
|
|
447
|
+
|
|
448
|
+
// Atualizar singleton
|
|
449
|
+
updateSingleton({ isLoaded: false, loadError: undefined });
|
|
450
|
+
|
|
451
|
+
// Carregar novo script
|
|
452
|
+
loadGoogleMapsScript(newApiKey)
|
|
453
|
+
.then(() => {
|
|
454
|
+
updateSingleton({ isLoaded: true, loadError: undefined });
|
|
455
|
+
resolve();
|
|
456
|
+
})
|
|
457
|
+
.catch(error => {
|
|
458
|
+
updateSingleton({ isLoaded: false, loadError: error });
|
|
459
|
+
reject(error);
|
|
460
|
+
});
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// Alias for backwards compatibility
|
|
465
|
+
export const GoogleMapsProvider = GoogleMapsLoaderProvider;
|