Skip to content

feat: add outro option to unmount #14540

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Dec 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/sour-jeans-talk.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'svelte': patch
---

feat: add `outro` option to `unmount`
11 changes: 7 additions & 4 deletions documentation/docs/06-runtime/04-imperative-component-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,19 +33,22 @@ Note that unlike calling `new App(...)` in Svelte 4, things like effects (includ

## `unmount`

Unmounts a component created with [`mount`](#mount) or [`hydrate`](#hydrate):
Unmounts a component that was previously created with [`mount`](#mount) or [`hydrate`](#hydrate).

If `options.outro` is `true`, [transitions](transition) will play before the component is removed from the DOM:

```js
// @errors: 1109
import { mount, unmount } from 'svelte';
import App from './App.svelte';

const app = mount(App, {...});
const app = mount(App, { target: document.body });

// later
unmount(app);
unmount(app, { outro: true });
```

Returns a `Promise` that resolves after transitions have completed if `options.outro` is true, or immediately otherwise.

## `render`

Only available on the server and when compiling with the `server` option. Takes a component and returns an object with `body` and `head` properties on it, which you can use to populate the HTML when server-rendering your app:
Expand Down
24 changes: 24 additions & 0 deletions packages/svelte/src/internal/client/reactivity/effects.js
Original file line number Diff line number Diff line change
Expand Up @@ -248,11 +248,35 @@ export function inspect_effect(fn) {
*/
export function effect_root(fn) {
const effect = create_effect(ROOT_EFFECT, fn, true);

return () => {
destroy_effect(effect);
};
}

/**
* An effect root whose children can transition out
* @param {() => void} fn
* @returns {(options?: { outro?: boolean }) => Promise<void>}
*/
export function component_root(fn) {
const effect = create_effect(ROOT_EFFECT, fn, true);

return (options = {}) => {
return new Promise((fulfil) => {
if (options.outro) {
pause_effect(effect, () => {
destroy_effect(effect);
fulfil(undefined);
});
} else {
destroy_effect(effect);
fulfil(undefined);
}
});
};
}

/**
* @param {() => void | (() => void)} fn
* @returns {Effect}
Expand Down
33 changes: 27 additions & 6 deletions packages/svelte/src/internal/client/render.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
} from './dom/operations.js';
import { HYDRATION_END, HYDRATION_ERROR, HYDRATION_START } from '../../constants.js';
import { push, pop, component_context, active_effect } from './runtime.js';
import { effect_root, branch } from './reactivity/effects.js';
import { component_root, branch } from './reactivity/effects.js';
import {
hydrate_next,
hydrate_node,
Expand Down Expand Up @@ -204,7 +204,7 @@ function _mount(Component, { target, anchor, props = {}, events, context, intro
// @ts-expect-error will be defined because the render effect runs synchronously
var component = undefined;

var unmount = effect_root(() => {
var unmount = component_root(() => {
var anchor_node = anchor ?? target.appendChild(create_text());

branch(() => {
Expand Down Expand Up @@ -252,7 +252,7 @@ function _mount(Component, { target, anchor, props = {}, events, context, intro
}

root_event_handles.delete(event_handle);
mounted_components.delete(component);

if (anchor_node !== anchor) {
anchor_node.parentNode?.removeChild(anchor_node);
}
Expand All @@ -271,14 +271,35 @@ let mounted_components = new WeakMap();

/**
* Unmounts a component that was previously mounted using `mount` or `hydrate`.
*
* If `options.outro` is `true`, [transitions](https://svelte.dev/docs/svelte/transition) will play before the component is removed from the DOM.
*
* Returns a `Promise` that resolves after transitions have completed if `options.outro` is true, or immediately otherwise.
*
* ```js
* import { mount, unmount } from 'svelte';
* import App from './App.svelte';
*
* const app = mount(App, { target: document.body });
*
* // later...
* unmount(app, { outro: true });
* ```
* @param {Record<string, any>} component
* @param {{ outro?: boolean }} [options]
* @returns {Promise<void>}
*/
export function unmount(component) {
export function unmount(component, options) {
const fn = mounted_components.get(component);

if (fn) {
fn();
} else if (DEV) {
mounted_components.delete(component);
return fn(options);
}

if (DEV) {
w.lifecycle_double_unmount();
}

return Promise.resolve();
}
18 changes: 17 additions & 1 deletion packages/svelte/types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -448,8 +448,24 @@ declare module 'svelte' {
}): Exports;
/**
* Unmounts a component that was previously mounted using `mount` or `hydrate`.
*
* If `options.outro` is `true`, [transitions](https://svelte.dev/docs/svelte/transition) will play before the component is removed from the DOM.
*
* Returns a `Promise` that resolves after transitions have completed if `options.outro` is true, or immediately otherwise.
*
* ```js
* import { mount, unmount } from 'svelte';
* import App from './App.svelte';
*
* const app = mount(App, { target: document.body });
*
* // later...
* unmount(app, { outro: true });
* ```
* */
export function unmount(component: Record<string, any>): void;
export function unmount(component: Record<string, any>, options?: {
outro?: boolean;
} | undefined): Promise<void>;
/**
* Returns a promise that resolves once any pending state changes have been applied.
* */
Expand Down
Loading