blockless
Rich Harris 2 years ago
parent 044c1a1975
commit 32202bdcb1

@ -58,7 +58,8 @@ import {
managed_effect, managed_effect,
derived, derived,
pre_effect, pre_effect,
user_effect user_effect,
pause_effect
} from './reactivity/computations.js'; } from './reactivity/computations.js';
import { import {
current_hydration_fragment, current_hydration_fragment,
@ -1651,115 +1652,28 @@ export function element(anchor_node, tag_fn, is_svg, render_fn) {
* @returns {void} * @returns {void}
*/ */
export function component(anchor_node, component_fn, render_fn) { 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); hydrate_block_anchor(anchor_node);
/** @type {null | ((props: P) => void)} */ /** @type {TODO} */
let component = null; let Component = null;
block.r =
/** /** @type {import('./types.js').ComputationSignal | null} */
* @param {import('./types.js').Transition} transition let effect = null;
* @returns {void}
*/ render_effect(() => {
(transition) => { if (Component === (Component = component_fn() ?? null)) return;
const render = /** @type {import('./types.js').Render} */ (current_render);
const transitions = render.s; if (effect) {
transitions.add(transition); const e = effect;
transition.f(() => { pause_effect(effect, () => {
transitions.delete(transition); if (e === effect) effect = null;
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;
}
}
}); });
};
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; if (Component) {
}, effect = render_effect(() => render_fn(Component), {}, true);
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;
} }
}); });
block.e = component_effect;
} }
/** /**

@ -546,7 +546,7 @@ export function bind_transition(element, get_fn, get_params, direction, global)
for (let i = 0; i <= n; i += 1) { for (let i = 0; i <= n; i += 1) {
const t = current_options.easing(p + (current_delta * i) / n); 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)); keyframes.push(css_to_keyframe(css));
} }
@ -578,6 +578,8 @@ export function bind_transition(element, get_fn, get_params, direction, global)
user_effect(() => { user_effect(() => {
untrack(() => transition.to(1)); untrack(() => transition.to(1));
}); });
} else {
p = 1;
} }
} }

@ -33,11 +33,9 @@ function tick(time) {
class Animation { class Animation {
#keyframes; #keyframes;
#duration; #duration;
#timeline_offset;
#reversed;
#target; #target;
#paused;
#finished; #finished;
#cancelled;
/** /**
* @param {HTMLElement} target * @param {HTMLElement} target
@ -48,47 +46,39 @@ class Animation {
this.#target = target; this.#target = target;
this.#keyframes = keyframes; this.#keyframes = keyframes;
this.#duration = options.duration || 0; this.#duration = options.duration || 0;
this.#timeline_offset = 0;
this.#reversed = false;
this.#paused = false;
this.onfinish = () => {};
this.pending = true;
this.currentTime = 0; this.currentTime = 0;
this.playState = 'running';
this.effect = { // Promise-like semantics, but call callbacks immediately on raf.tick
setKeyframes: (/** @type {Keyframe[]} */ keyframes) => { this.finished = {
this.#keyframes = keyframes; then: (callback) => {
this.#finished = callback;
return {
catch: (callback) => {
this.#cancelled = callback;
}
};
} }
}; };
}
this.finished = new Promise((fulfil) => { cancel() {
this.#finished = fulfil; if (this.currentTime > 0 && this.currentTime < this.#duration) {
}); this._applyKeyFrame(0);
} }
play() { this.#cancelled();
this.#paused = false;
raf.animations.add(this);
this.playState = 'running';
this._update();
} }
_update() { _update() {
if (this.#reversed) { this.currentTime = raf.time;
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;
}
const target_frame = this.currentTime / this.#duration; const target_frame = this.currentTime / this.#duration;
this._applyKeyFrame(target_frame); this._applyKeyFrame(target_frame);
if (this.currentTime >= this.#duration) { if (this.currentTime >= this.#duration) {
this.#finished(); this.#finished();
raf.animations.delete(this);
} }
} }
@ -103,54 +93,14 @@ class Animation {
// @ts-ignore // @ts-ignore
this.#target.style[prop] = frame[prop]; 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) { if (this.currentTime >= this.#duration) {
this.finish();
for (let prop in frame) { for (let prop in frame) {
// @ts-ignore // @ts-ignore
this.#target.style[prop] = null; 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);
}
this.#timeline_offset = this.currentTime;
this.#reversed = !this.#reversed;
this.playState = 'running';
}
} }
/** /**
@ -160,6 +110,7 @@ class Animation {
*/ */
HTMLElement.prototype.animate = function (keyframes, options) { HTMLElement.prototype.animate = function (keyframes, options) {
const animation = new Animation(this, keyframes, options); const animation = new Animation(this, keyframes, options);
raf.animations.add(animation);
// @ts-ignore // @ts-ignore
return animation; return animation;
}; };

Loading…
Cancel
Save