@@ -8,6 +8,7 @@ import { DEVFRAME_REMOTE_ASSETS_ERROR_MESSAGE_TYPE, REMOTE_CONNECTION_KEY } from
88import { computed , nextTick , onMounted , onUnmounted , ref , useTemplateRef , watchEffect } from ' vue'
99import { sharedStateToRef } from ' ../../state/docks'
1010import ViewAssetsError from ' ./ViewAssetsError.vue'
11+ import ViewIframeLoading from ' ./ViewIframeLoading.vue'
1112
1213const props = defineProps <{
1314 context: DocksContext
@@ -52,13 +53,24 @@ const ADDRESS_BAR_HEIGHT = 40
5253
5354const isLoading = ref (true )
5455const isIframeLoading = ref (false )
56+ // Flips true once the pane is mounted so the hide/show effect can run — a plain
57+ // `pane.isMounted` read isn't reactive.
58+ const paneReady = ref (false )
5559
5660// A devframe whose client assets are published as their own npm package
5761// answers with a fallback page when it can reach neither a local install nor
5862// the CDN they live on. That page reports itself over `postMessage`, so the
5963// failure renders as a hub panel — with the install command and a retry —
6064// rather than as a bare page inside the frame.
6165const assetsError = ref <RemoteAssetsErrorMessage | null >(null )
66+
67+ // The blank iframe paints white while its content loads, so a placeholder is
68+ // only useful when the pane steps aside (`pane.hide()`) to reveal it — the same
69+ // layering trick `ViewAssetsError` relies on. Show it during the initial load
70+ // and any hard navigation/refresh, but never on top of the assets-error panel.
71+ const showLoadingPlaceholder = computed (
72+ () => ! assetsError .value && (isLoading .value || isIframeLoading .value ),
73+ )
6274const viewFrame = useTemplateRef <HTMLDivElement >(' viewFrame' )
6375const urlInputRef = useTemplateRef <HTMLInputElement >(' urlInput' )
6476
@@ -245,6 +257,10 @@ onMounted(() => {
245257
246258 if (existed )
247259 updateCurrentUrl ()
260+ else
261+ // A freshly created pane is loading its initial content — reflect it so the
262+ // placeholder covers the first paint, not just later navigations.
263+ isIframeLoading .value = true
248264
249265 // Persist this dock's live route while it is the selected one, so the next
250266 // reload can restore it. Only the selected dock writes, so switching docks
@@ -286,19 +302,23 @@ onMounted(() => {
286302 })
287303
288304 // The iframe lives in its own layer stacked over this view, so the error
289- // panel is only visible once the pane steps aside. `hide()` keeps the frame
290- // alive (and its state intact) for the retry.
305+ // panel and the loading placeholder are only visible once the pane steps
306+ // aside. `hide()` keeps the frame alive (and its state intact) so the content
307+ // keeps loading behind the placeholder and survives a retry.
291308 watchEffect (() => {
292- if (assetsError .value )
309+ if (! paneReady .value )
310+ return
311+ if (assetsError .value || isIframeLoading .value )
293312 pane .hide ()
294- else if ( pane . isMounted )
313+ else
295314 pane .show ()
296315 })
297316
298317 window .addEventListener (' message' , onWindowMessage )
299318
300319 pane .mount (viewFrame .value ! )
301320 isLoading .value = false
321+ paneReady .value = true
302322 nextTick (() => {
303323 pane .update ()
304324 })
@@ -382,9 +402,7 @@ onUnmounted(() => {
382402 ref =" viewFrame"
383403 class =" devframes-view-iframe relative w-full h-full flex-1 items-center justify-center"
384404 >
385- <div v-if =" isLoading" class =" op50 z--1" >
386- Loading iframe...
387- </div >
405+ <ViewIframeLoading v-if =" showLoadingPlaceholder " />
388406 <ViewAssetsError
389407 v-if =" assetsError "
390408 :error =" assetsError "
0 commit comments