diff --git a/packages/svelte/src/internal/client/render.js b/packages/svelte/src/internal/client/render.js index da46e3e318..6f7450e7d1 100644 --- a/packages/svelte/src/internal/client/render.js +++ b/packages/svelte/src/internal/client/render.js @@ -58,7 +58,8 @@ import { managed_effect, derived, pre_effect, - user_effect + user_effect, + pause_effect } from './reactivity/computations.js'; import { current_hydration_fragment, @@ -1651,115 +1652,28 @@ export function element(anchor_node, tag_fn, is_svg, render_fn) { * @returns {void} */ export function component(anchor_node, component_fn, render_fn) { - const block = create_dynamic_component_block(); - - /** @type {null | import('./types.js').Render} */ - let current_render = null; hydrate_block_anchor(anchor_node); - /** @type {null | ((props: P) => void)} */ - let component = null; - block.r = - /** - * @param {import('./types.js').Transition} transition - * @returns {void} - */ - (transition) => { - const render = /** @type {import('./types.js').Render} */ (current_render); - const transitions = render.s; - transitions.add(transition); - transition.f(() => { - transitions.delete(transition); - if (transitions.size === 0) { - // If the current render has changed since, then we can remove the old render - // effect as it's stale. - if (current_render !== render && render.e !== null) { - if (render.d !== null) { - remove(render.d); - render.d = null; - } - destroy_signal(render.e); - render.e = null; - } - } + /** @type {TODO} */ + let Component = null; + + /** @type {import('./types.js').ComputationSignal | null} */ + let effect = null; + + render_effect(() => { + if (Component === (Component = component_fn() ?? null)) return; + + if (effect) { + const e = effect; + pause_effect(effect, () => { + if (e === effect) effect = null; }); - }; - const create_render_effect = () => { - /** @type {import('./types.js').Render} */ - const render = { - d: null, - e: null, - s: new Set(), - p: current_render - }; - // Managed effect - const effect = render_effect( - () => { - const current = block.d; - if (current !== null) { - remove(current); - block.d = null; - } - if (component) { - render_fn(component); - } - render.d = block.d; - block.d = null; - }, - block, - true - ); - render.e = effect; - current_render = render; - }; - const render = () => { - const render = current_render; - if (render === null) { - create_render_effect(); - return; - } - const transitions = render.s; - if (transitions.size === 0) { - if (render.d !== null) { - remove(render.d); - render.d = null; - } - if (render.e) { - execute_effect(render.e); - } else { - create_render_effect(); - } - } else { - create_render_effect(); - trigger_transitions(transitions, 'out'); } - }; - const component_effect = render_effect( - () => { - const next_component = component_fn(); - if (component !== next_component) { - component = next_component; - render(); - } - }, - block, - false - ); - push_destroy_fn(component_effect, () => { - let render = current_render; - while (render !== null) { - const dom = render.d; - if (dom !== null) { - remove(dom); - } - const effect = render.e; - if (effect !== null) { - destroy_signal(effect); - } - render = render.p; + + if (Component) { + effect = render_effect(() => render_fn(Component), {}, true); } }); - block.e = component_effect; } /** diff --git a/packages/svelte/src/internal/client/transitions.js b/packages/svelte/src/internal/client/transitions.js index 1bdb3b28fe..962eb2acec 100644 --- a/packages/svelte/src/internal/client/transitions.js +++ b/packages/svelte/src/internal/client/transitions.js @@ -546,7 +546,7 @@ export function bind_transition(element, get_fn, get_params, direction, global) for (let i = 0; i <= n; i += 1) { const t = current_options.easing(p + (current_delta * i) / n); - const css = current_options.css(t); + const css = current_options.css(t, 1 - t); keyframes.push(css_to_keyframe(css)); } @@ -578,6 +578,8 @@ export function bind_transition(element, get_fn, get_params, direction, global) user_effect(() => { untrack(() => transition.to(1)); }); + } else { + p = 1; } } diff --git a/packages/svelte/tests/animation-helpers.js b/packages/svelte/tests/animation-helpers.js index 51d1f6bae3..6de42e1874 100644 --- a/packages/svelte/tests/animation-helpers.js +++ b/packages/svelte/tests/animation-helpers.js @@ -33,11 +33,9 @@ function tick(time) { class Animation { #keyframes; #duration; - #timeline_offset; - #reversed; #target; - #paused; #finished; + #cancelled; /** * @param {HTMLElement} target @@ -48,47 +46,39 @@ class Animation { this.#target = target; this.#keyframes = keyframes; this.#duration = options.duration || 0; - this.#timeline_offset = 0; - this.#reversed = false; - this.#paused = false; - this.onfinish = () => {}; - this.pending = true; this.currentTime = 0; - this.playState = 'running'; - this.effect = { - setKeyframes: (/** @type {Keyframe[]} */ keyframes) => { - this.#keyframes = keyframes; + + // Promise-like semantics, but call callbacks immediately on raf.tick + this.finished = { + then: (callback) => { + this.#finished = callback; + + return { + catch: (callback) => { + this.#cancelled = callback; + } + }; } }; - - this.finished = new Promise((fulfil) => { - this.#finished = fulfil; - }); } - play() { - this.#paused = false; - raf.animations.add(this); - this.playState = 'running'; - this._update(); + cancel() { + if (this.currentTime > 0 && this.currentTime < this.#duration) { + this._applyKeyFrame(0); + } + + this.#cancelled(); } _update() { - if (this.#reversed) { - if (this.#timeline_offset === 0) { - this.currentTime = this.#duration - raf.time; - } else { - this.currentTime = this.#timeline_offset + (this.#timeline_offset - raf.time); - } - } else { - this.currentTime = raf.time - this.#timeline_offset; - } + this.currentTime = raf.time; const target_frame = this.currentTime / this.#duration; this._applyKeyFrame(target_frame); if (this.currentTime >= this.#duration) { this.#finished(); + raf.animations.delete(this); } } @@ -103,53 +93,13 @@ class Animation { // @ts-ignore this.#target.style[prop] = frame[prop]; } - if (this.#reversed) { - if (this.currentTime <= 0) { - this.finish(); - for (let prop in frame) { - // @ts-ignore - this.#target.style[prop] = null; - } - } - } else { - if (this.currentTime >= this.#duration) { - this.finish(); - for (let prop in frame) { - // @ts-ignore - this.#target.style[prop] = null; - } - } - } - } - finish() { - this.onfinish(); - this.currentTime = this.#reversed ? 0 : this.#duration; - if (this.#reversed) { - raf.animations.delete(this); - } - this.playState = 'idle'; - } - - cancel() { - this.#paused = true; - if (this.currentTime > 0 && this.currentTime < this.#duration) { - this._applyKeyFrame(this.#reversed ? this.#keyframes.length - 1 : 0); - } - } - - pause() { - this.#paused = true; - this.playState = 'paused'; - } - - reverse() { - if (this.#paused && !raf.animations.has(this)) { - raf.animations.add(this); + if (this.currentTime >= this.#duration) { + for (let prop in frame) { + // @ts-ignore + this.#target.style[prop] = null; + } } - this.#timeline_offset = this.currentTime; - this.#reversed = !this.#reversed; - this.playState = 'running'; } } @@ -160,6 +110,7 @@ class Animation { */ HTMLElement.prototype.animate = function (keyframes, options) { const animation = new Animation(this, keyframes, options); + raf.animations.add(animation); // @ts-ignore return animation; };