@@ -86,6 +86,29 @@ export const exportMapToHTML = (options, version = KEPLER_GL_VERSION) => {
8686 // native loader (which fails under file://).
8787 window.esmsInitOptions = {shimMode: true};
8888 </script>
89+ <script>
90+ // Replace the loading overlay with an error message.
91+ // Safe to call at any time — once React has mounted and removed
92+ // #kepler-load-screen this becomes a no-op.
93+ window._keplerShowLoadError = function(msg) {
94+ var loadScreen = document.getElementById('kepler-load-screen');
95+ if (!loadScreen) return;
96+ var spinner = loadScreen.querySelector('.kepler-spinner');
97+ var label = loadScreen.querySelector('.kepler-load-label');
98+ if (spinner) spinner.style.display = 'none';
99+ if (label) label.style.display = 'none';
100+ var errEl = document.createElement('div');
101+ errEl.className = 'kepler-load-error';
102+ errEl.textContent = msg || 'Failed to load the map. Check your internet connection and reload the page.';
103+ loadScreen.appendChild(errEl);
104+ };
105+ // Catch unhandled promise rejections (e.g. ES module imports failing
106+ // inside the module-shim script before loadScript is ever called).
107+ window.addEventListener('unhandledrejection', function(event) {
108+ console.error('kepler.gl: unhandled rejection during map load', event.reason);
109+ window._keplerShowLoadError('Failed to load the map. Check your internet connection and reload the page.');
110+ });
111+ </script>
89112 <script src="https://unpkg.com/es-module-shims@${ ES_MODULE_SHIMS_VERSION } /dist/es-module-shims.js" crossorigin="anonymous"></script>
90113
91114 <script type="importmap-shim">
@@ -155,10 +178,44 @@ export const exportMapToHTML = (options, version = KEPLER_GL_VERSION) => {
155178 })
156179 .catch(function(error) {
157180 console.error('kepler.gl: failed to load UMD bundle', error);
181+ window._keplerShowLoadError('Failed to load the map bundle. Check your internet connection and reload the page.');
158182 });
159183 </script>
160184 <style type="text/css">
161185 body {margin: 0; padding: 0; overflow: hidden;}
186+ #kepler-load-screen {
187+ position: fixed;
188+ top: 0; left: 0;
189+ width: 100%; height: 100%;
190+ background: #29323c;
191+ display: flex;
192+ flex-direction: column;
193+ align-items: center;
194+ justify-content: center;
195+ gap: 20px;
196+ z-index: 9999;
197+ font-family: ff-clan-web-pro, 'Helvetica Neue', Helvetica, sans-serif;
198+ color: #a0a7b4;
199+ }
200+ #kepler-load-screen .kepler-spinner {
201+ width: 48px; height: 48px;
202+ border: 4px solid rgba(255,255,255,0.08);
203+ border-top-color: #1fbad6;
204+ border-radius: 50%;
205+ animation: kepler-spin 0.8s linear infinite;
206+ }
207+ #kepler-load-screen .kepler-load-label {
208+ font-size: 13px;
209+ letter-spacing: 0.5px;
210+ }
211+ #kepler-load-screen .kepler-load-error {
212+ font-size: 13px;
213+ color: #f05e45;
214+ max-width: 360px;
215+ text-align: center;
216+ line-height: 1.5;
217+ }
218+ @keyframes kepler-spin { to { transform: rotate(360deg); } }
162219 </style>
163220
164221 <!--MapBox token-->
@@ -199,7 +256,10 @@ export const exportMapToHTML = (options, version = KEPLER_GL_VERSION) => {
199256 <body>
200257 <!-- We will put our React component inside this div. -->
201258 <div id="app">
202- <!-- Kepler.gl map will be placed here-->
259+ <div id="kepler-load-screen">
260+ <div class="kepler-spinner"></div>
261+ <div class="kepler-load-label">Loading map…</div>
262+ </div>
203263 </div>
204264
205265 <!--
@@ -363,6 +423,13 @@ export const exportMapToHTML = (options, version = KEPLER_GL_VERSION) => {
363423 window.addEventListener('resize', handleResize);
364424 return function() {window.removeEventListener('resize', handleResize);};
365425 }, []);
426+ // Remove the loading overlay after the first render is painted.
427+ // useEffect fires post-commit, so this is safe under React 19's
428+ // concurrent renderer — the map is visible before the overlay goes away.
429+ react.useEffect(function removeLoadScreen() {
430+ var loadScreen = document.getElementById('kepler-load-screen');
431+ if (loadScreen) loadScreen.remove();
432+ }, []);
366433 return react.createElement(
367434 'div',
368435 {style: {position: 'absolute', left: 0, width: '100vw', height: '100vh'}},
0 commit comments