diff options
Diffstat (limited to 'packages')
-rw-r--r-- | packages/astro/components/ViewTransitions.astro | 8 | ||||
-rw-r--r-- | packages/astro/src/runtime/server/astro-island.ts | 6 | ||||
-rw-r--r-- | packages/integrations/preact/src/client.ts | 31 | ||||
-rw-r--r-- | packages/integrations/react/client-v17.js | 11 | ||||
-rw-r--r-- | packages/integrations/react/client.js | 10 | ||||
-rw-r--r-- | packages/integrations/solid/src/client.ts | 6 | ||||
-rw-r--r-- | packages/integrations/svelte/client.js | 4 | ||||
-rw-r--r-- | packages/integrations/vue/client.js | 16 |
8 files changed, 49 insertions, 43 deletions
diff --git a/packages/astro/components/ViewTransitions.astro b/packages/astro/components/ViewTransitions.astro index 33741d535..15bad445d 100644 --- a/packages/astro/components/ViewTransitions.astro +++ b/packages/astro/components/ViewTransitions.astro @@ -163,18 +163,20 @@ const { fallback = 'animate' } = Astro.props as Props; // Everything left in the new head is new, append it all. document.head.append(...doc.head.children); - // Move over persist stuff in the body + // Persist elements in the existing body const oldBody = document.body; - document.body.replaceWith(doc.body); for (const el of oldBody.querySelectorAll(`[${PERSIST_ATTR}]`)) { const id = el.getAttribute(PERSIST_ATTR); - const newEl = document.querySelector(`[${PERSIST_ATTR}="${id}"]`); + const newEl = doc.querySelector(`[${PERSIST_ATTR}="${id}"]`); if (newEl) { // The element exists in the new page, replace it with the element // from the old page so that state is preserved. newEl.replaceWith(el); } } + // Only replace the existing body *AFTER* persistent elements are moved over + // This avoids disconnecting `astro-island` nodes multiple times + document.body.replaceWith(doc.body); // Simulate scroll behavior of Safari and // Chromium based browsers (Chrome, Edge, Opera, ...) diff --git a/packages/astro/src/runtime/server/astro-island.ts b/packages/astro/src/runtime/server/astro-island.ts index 7be630d06..e0e09eaec 100644 --- a/packages/astro/src/runtime/server/astro-island.ts +++ b/packages/astro/src/runtime/server/astro-island.ts @@ -51,6 +51,12 @@ declare const Astro: { public Component: any; public hydrator: any; static observedAttributes = ['props']; + disconnectedCallback() { + document.addEventListener('astro:after-swap', () => { + // If element wasn't persisted, fire unmount event + if (!this.isConnected) this.dispatchEvent(new CustomEvent('astro:unmount')) + }, { once: true }) + } connectedCallback() { if (!this.hasAttribute('await-children') || this.firstChild) { this.childrenConnectedCallback(); diff --git a/packages/integrations/preact/src/client.ts b/packages/integrations/preact/src/client.ts index f90614398..ad24e886b 100644 --- a/packages/integrations/preact/src/client.ts +++ b/packages/integrations/preact/src/client.ts @@ -1,6 +1,6 @@ -import { h, render, type JSX } from 'preact'; -import StaticHtml from './static-html.js'; import type { SignalLike } from './types'; +import { h, render, hydrate } from 'preact'; +import StaticHtml from './static-html.js'; const sharedSignalMap = new Map<string, SignalLike>(); @@ -8,7 +8,8 @@ export default (element: HTMLElement) => async ( Component: any, props: Record<string, any>, - { default: children, ...slotted }: Record<string, any> + { default: children, ...slotted }: Record<string, any>, + { client }: Record<string, string> ) => { if (!element.hasAttribute('ssr')) return; for (const [key, value] of Object.entries(slotted)) { @@ -27,23 +28,13 @@ export default (element: HTMLElement) => } } - // eslint-disable-next-line @typescript-eslint/no-shadow - function Wrapper({ children }: { children: JSX.Element }) { - let attrs = Object.fromEntries( - Array.from(element.attributes).map((attr) => [attr.name, attr.value]) - ); - return h(element.localName, attrs, children); - } - - let parent = element.parentNode as Element; + const bootstrap = client !== 'only' ? hydrate : render; - render( - h( - Wrapper, - null, - h(Component, props, children != null ? h(StaticHtml, { value: children }) : children) - ), - parent, - element + bootstrap( + h(Component, props, children != null ? h(StaticHtml, { value: children }) : children), + element, ); + + // Preact has no "unmount" option, but you can use `render(null, element)` + element.addEventListener('astro:unmount', () => render(null, element), { once: true }) }; diff --git a/packages/integrations/react/client-v17.js b/packages/integrations/react/client-v17.js index 443109603..70bddc353 100644 --- a/packages/integrations/react/client-v17.js +++ b/packages/integrations/react/client-v17.js @@ -1,5 +1,5 @@ import { createElement } from 'react'; -import { render, hydrate } from 'react-dom'; +import { render, hydrate, unmountComponentAtNode } from 'react-dom'; import StaticHtml from './static-html.js'; export default (element) => @@ -12,8 +12,9 @@ export default (element) => props, children != null ? createElement(StaticHtml, { value: children }) : children ); - if (client === 'only') { - return render(componentEl, element); - } - return hydrate(componentEl, element); + + const isHydrate = client !== 'only'; + const bootstrap = isHydrate ? hydrate : render; + bootstrap(componentEl, element); + element.addEventListener('astro:unmount', () => unmountComponentAtNode(element), { once: true }); }; diff --git a/packages/integrations/react/client.js b/packages/integrations/react/client.js index d8948e7bb..dbd32c0c5 100644 --- a/packages/integrations/react/client.js +++ b/packages/integrations/react/client.js @@ -31,10 +31,14 @@ export default (element) => } if (client === 'only') { return startTransition(() => { - createRoot(element).render(componentEl); + const root = createRoot(element); + root.render(componentEl); + element.addEventListener('astro:unmount', () => root.unmount(), { once: true }); }); } - return startTransition(() => { - hydrateRoot(element, componentEl, renderOptions); + startTransition(() => { + const root = hydrateRoot(element, componentEl, renderOptions); + root.render(componentEl); + element.addEventListener('astro:unmount', () => root.unmount(), { once: true }); }); }; diff --git a/packages/integrations/solid/src/client.ts b/packages/integrations/solid/src/client.ts index 730db0f51..66b3767ea 100644 --- a/packages/integrations/solid/src/client.ts +++ b/packages/integrations/solid/src/client.ts @@ -9,7 +9,7 @@ export default (element: HTMLElement) => } if (!element.hasAttribute('ssr')) return; - const fn = client === 'only' ? render : hydrate; + const boostrap = client === 'only' ? render : hydrate; let _slots: Record<string, any> = {}; if (Object.keys(slotted).length > 0) { @@ -30,7 +30,7 @@ export default (element: HTMLElement) => const { default: children, ...slots } = _slots; const renderId = element.dataset.solidRenderId; - fn( + const dispose = boostrap( () => createComponent(Component, { ...props, @@ -42,4 +42,6 @@ export default (element: HTMLElement) => renderId, } ); + + element.addEventListener('astro:unmount', () => dispose(), { once: true }) }; diff --git a/packages/integrations/svelte/client.js b/packages/integrations/svelte/client.js index 0d07ff2ba..99612a580 100644 --- a/packages/integrations/svelte/client.js +++ b/packages/integrations/svelte/client.js @@ -14,7 +14,7 @@ export default (target) => { try { if (import.meta.env.DEV) useConsoleFilter(); - new Component({ + const component = new Component({ target, props: { ...props, @@ -24,6 +24,8 @@ export default (target) => { hydrate: client !== 'only', $$inline: true, }); + + element.addEventListener('astro:unmount', () => component.$destroy(), { once: true }) } catch (e) { } finally { if (import.meta.env.DEV) finishUsingConsoleFilter(); diff --git a/packages/integrations/vue/client.js b/packages/integrations/vue/client.js index ca61116b2..8b2a5eede 100644 --- a/packages/integrations/vue/client.js +++ b/packages/integrations/vue/client.js @@ -21,15 +21,13 @@ export default (element) => content = h(Suspense, null, content); } - if (client === 'only') { - const app = createApp({ name, render: () => content }); - await setup(app); - app.mount(element, false); - } else { - const app = createSSRApp({ name, render: () => content }); - await setup(app); - app.mount(element, true); - } + const isHydrate = client !== 'only'; + const boostrap = isHydrate ? createSSRApp : createApp; + const app = boostrap({ name, render: () => content }); + await setup(app); + app.mount(element, isHydrate); + + element.addEventListener('astro:unmount', () => app.unmount(), { once: true }); }; function isAsync(fn) { |