|
1 | 1 | <script setup lang="ts"> |
2 | 2 | import type { DevToolsViewIframe } from '@vitejs/devtools-kit' |
3 | 3 | import type { DocksContext } from '@vitejs/devtools-kit/client' |
| 4 | +import type { IframePanes } from 'iframe-pane' |
4 | 5 | import type { CSSProperties } from 'vue' |
5 | | -import type { PersistedDomViewsManager } from '../../utils/PersistedDomViewsManager' |
6 | 6 | import { REMOTE_CONNECTION_KEY } from '@vitejs/devtools-kit/constants' |
7 | | -import { computed, nextTick, onMounted, onUnmounted, ref, useTemplateRef, watch, watchEffect } from 'vue' |
| 7 | +import { computed, nextTick, onMounted, onUnmounted, ref, useTemplateRef, watchEffect } from 'vue' |
8 | 8 | import { sharedStateToRef } from '../../state/docks' |
9 | 9 |
|
10 | 10 | const props = defineProps<{ |
11 | 11 | context: DocksContext |
12 | 12 | entry: DevToolsViewIframe |
13 | | - persistedDoms: PersistedDomViewsManager |
| 13 | + panes: IframePanes |
14 | 14 | iframeStyle?: CSSProperties |
15 | 15 | }>() |
16 | 16 |
|
@@ -58,7 +58,7 @@ const editingUrl = ref(props.entry.url) |
58 | 58 | const isEditing = ref(false) |
59 | 59 |
|
60 | 60 | const iframeElement = computed(() => { |
61 | | - return props.persistedDoms.getHolder(props.entry.id, 'iframe')?.element |
| 61 | + return props.panes.get(props.entry.id)?.iframe |
62 | 62 | }) |
63 | 63 |
|
64 | 64 | // Get current page's origin for comparison |
@@ -183,72 +183,63 @@ function refresh() { |
183 | 183 | iframe.src = src |
184 | 184 | } |
185 | 185 |
|
| 186 | +let onIframeLoad: (() => void) | undefined |
| 187 | +
|
186 | 188 | onMounted(() => { |
187 | | - if (props.persistedDoms.getHolder(props.entry.id, 'iframe')) { |
188 | | - updateCurrentUrl() |
189 | | - } |
190 | | - const holder = props.persistedDoms.getOrCreateHolder(props.entry.id, 'iframe') |
191 | | - holder.element.style.boxShadow = 'none' |
192 | | - holder.element.style.outline = 'none' |
| 189 | + const existed = props.panes.has(props.entry.id) |
| 190 | + // `src` is only assigned when the pane is first created, so re-mounting an |
| 191 | + // existing iframe (tab switch) preserves its navigation/scroll/JS state. |
| 192 | + const pane = props.panes.ensure(props.entry.id, { |
| 193 | + src: props.entry.url, |
| 194 | + style: { boxShadow: 'none', outline: 'none' }, |
| 195 | + }) |
| 196 | + const iframe = pane.iframe |
193 | 197 |
|
194 | | - if (!holder.element.src) |
195 | | - holder.element.src = props.entry.url |
| 198 | + if (existed) |
| 199 | + updateCurrentUrl() |
196 | 200 |
|
197 | 201 | // Listen for iframe load events |
198 | | - holder.element.addEventListener('load', () => { |
| 202 | + onIframeLoad = () => { |
199 | 203 | isIframeLoading.value = false |
200 | 204 | updateCurrentUrl() |
201 | | - }) |
| 205 | + } |
| 206 | + iframe.addEventListener('load', onIframeLoad) |
202 | 207 |
|
203 | 208 | const entryState = props.context.docks.getStateById(props.entry.id) |
204 | 209 | if (entryState) |
205 | | - entryState.domElements.iframe = holder.element |
| 210 | + entryState.domElements.iframe = iframe |
206 | 211 |
|
| 212 | + // iframe-pane positions the iframe exactly over the mount target (the view |
| 213 | + // frame below the address bar), so no manual offset is needed — only the |
| 214 | + // cosmetic borders differ between edge/float and address-bar states. |
207 | 215 | watchEffect(() => { |
208 | | - Object.assign(holder.element.style, props.iframeStyle) |
209 | | - if (showAddressBar.value) { |
210 | | - holder.element.style.marginTop = `${ADDRESS_BAR_HEIGHT}px` |
211 | | - if (!isEdgeMode.value) { |
212 | | - holder.element.style.borderTopLeftRadius = '0px' |
213 | | - holder.element.style.borderTopRightRadius = '0px' |
214 | | - } |
| 216 | + Object.assign(iframe.style, props.iframeStyle) |
| 217 | + if (showAddressBar.value && !isEdgeMode.value) { |
| 218 | + iframe.style.borderTopLeftRadius = '0px' |
| 219 | + iframe.style.borderTopRightRadius = '0px' |
215 | 220 | } |
216 | 221 | else { |
217 | | - holder.element.style.marginTop = '0px' |
218 | | - holder.element.style.borderTopLeftRadius = '' |
219 | | - holder.element.style.borderTopRightRadius = '' |
| 222 | + iframe.style.borderTopLeftRadius = '' |
| 223 | + iframe.style.borderTopRightRadius = '' |
220 | 224 | } |
221 | 225 | if (isEdgeMode.value) { |
222 | | - holder.element.style.borderRadius = '0px' |
223 | | - holder.element.style.border = 'none' |
| 226 | + iframe.style.borderRadius = '0px' |
| 227 | + iframe.style.border = 'none' |
224 | 228 | } |
225 | 229 | }) |
226 | 230 |
|
227 | | - watch( |
228 | | - () => props.context.panel, |
229 | | - () => { |
230 | | - holder.update() |
231 | | - }, |
232 | | - { deep: true }, |
233 | | - ) |
234 | | -
|
235 | | - watchEffect( |
236 | | - () => { |
237 | | - holder.element.style.pointerEvents = (props.context.panel.isDragging || props.context.panel.isResizing) ? 'none' : 'auto' |
238 | | - }, |
239 | | - { flush: 'sync' }, |
240 | | - ) |
241 | | -
|
242 | | - holder.mount(viewFrame.value!) |
| 231 | + pane.mount(viewFrame.value!) |
243 | 232 | isLoading.value = false |
244 | 233 | nextTick(() => { |
245 | | - holder.update() |
| 234 | + pane.update() |
246 | 235 | }) |
247 | 236 | }) |
248 | 237 |
|
249 | 238 | onUnmounted(() => { |
250 | | - const holder = props.persistedDoms.getHolder(props.entry.id, 'iframe') |
251 | | - holder?.unmount() |
| 239 | + const pane = props.panes.get(props.entry.id) |
| 240 | + if (pane && onIframeLoad) |
| 241 | + pane.iframe?.removeEventListener('load', onIframeLoad) |
| 242 | + pane?.unmount() |
252 | 243 | }) |
253 | 244 | </script> |
254 | 245 |
|
|
0 commit comments