From c599807ef9df66dc25391f44590f8a1f498d7e66 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Fri, 9 May 2025 13:33:08 +0200 Subject: [PATCH] implement `settled` --- packages/svelte/src/index-client.js | 2 +- packages/svelte/src/index-server.js | 2 ++ packages/svelte/src/internal/client/reactivity/batch.js | 7 +++++-- packages/svelte/src/internal/client/runtime.js | 9 +++++++++ packages/svelte/types/index.d.ts | 5 +++++ 5 files changed, 22 insertions(+), 3 deletions(-) diff --git a/packages/svelte/src/index-client.js b/packages/svelte/src/index-client.js index d843426ce0..1ee59f7209 100644 --- a/packages/svelte/src/index-client.js +++ b/packages/svelte/src/index-client.js @@ -241,5 +241,5 @@ function init_update_callbacks(context) { export { flushSync } from './internal/client/runtime.js'; export { getContext, getAllContexts, hasContext, setContext } from './internal/client/context.js'; export { hydrate, mount, unmount } from './internal/client/render.js'; -export { tick, untrack } from './internal/client/runtime.js'; +export { tick, untrack, settled } from './internal/client/runtime.js'; export { createRawSnippet } from './internal/client/dom/blocks/snippet.js'; diff --git a/packages/svelte/src/index-server.js b/packages/svelte/src/index-server.js index f4cb6f8c41..219bcfb360 100644 --- a/packages/svelte/src/index-server.js +++ b/packages/svelte/src/index-server.js @@ -35,6 +35,8 @@ export function unmount() { export async function tick() {} +export async function settled() {} + /** @type {AbortController | null} */ let controller = null; diff --git a/packages/svelte/src/internal/client/reactivity/batch.js b/packages/svelte/src/internal/client/reactivity/batch.js index bf1b0ea203..138c59ef86 100644 --- a/packages/svelte/src/internal/client/reactivity/batch.js +++ b/packages/svelte/src/internal/client/reactivity/batch.js @@ -39,6 +39,9 @@ export class Batch { #pending = 0; + /** @type {PromiseWithResolvers | null} */ + deferred = null; + /** @type {Effect[]} */ async_effects = []; @@ -51,8 +54,6 @@ export class Batch { /** @type {Set} */ skipped_effects = new Set(); - apply() {} - /** * * @param {Effect[]} root_effects @@ -93,6 +94,8 @@ export class Batch { flush_queued_effects(render_effects); flush_queued_effects(effects); + + this.deferred?.resolve(); } else { for (const e of this.render_effects) set_signal_status(e, CLEAN); for (const e of this.effects) set_signal_status(e, CLEAN); diff --git a/packages/svelte/src/internal/client/runtime.js b/packages/svelte/src/internal/client/runtime.js index 085c1fa850..eed6550b93 100644 --- a/packages/svelte/src/internal/client/runtime.js +++ b/packages/svelte/src/internal/client/runtime.js @@ -884,6 +884,15 @@ export async function tick() { flushSync(); } +/** + * Returns a promise that resolves once any state changes, and asynchronous work resulting from them, + * have resolved and the DOM has been updated + * @returns {Promise} + */ +export function settled() { + return (Batch.ensure().deferred ??= Promise.withResolvers()).promise; +} + /** * @template V * @param {Value} signal diff --git a/packages/svelte/types/index.d.ts b/packages/svelte/types/index.d.ts index 63e2328101..bd936e9248 100644 --- a/packages/svelte/types/index.d.ts +++ b/packages/svelte/types/index.d.ts @@ -452,6 +452,11 @@ declare module 'svelte' { * Returns a promise that resolves once any pending state changes have been applied. * */ export function tick(): Promise; + /** + * Returns a promise that resolves once any state changes, and asynchronous work resulting from them, + * have resolved and the DOM has been updated + * */ + export function settled(): Promise; /** * When used inside a [`$derived`](https://svelte.dev/docs/svelte/$derived) or [`$effect`](https://svelte.dev/docs/svelte/$effect), * any state read inside `fn` will not be treated as a dependency.