WIP transitions

blockless
Rich Harris 2 years ago
parent 30aa5667fc
commit 032b739e98

@ -265,26 +265,73 @@ export function derived_safe_equal(fn) {
* @param {() => void} done * @param {() => void} done
*/ */
export function pause_effect(effect, done) { export function pause_effect(effect, done) {
if (effect.r) { const transitions = [];
for (const child of effect.r) {
pause_effect(child, noop); pause_children(effect, transitions);
let remaining = transitions.length;
if (remaining > 0) {
const check = () => {
if (!--remaining) {
destroy_effect(effect);
done();
} }
};
for (const transition of transitions) {
transition.to(0, check);
}
} else {
destroy_effect(effect);
done();
} }
}
/**
* @param {import('../types.js').ComputationSignal} effect
* @param {TODO[]} transitions
*/
function pause_children(effect, transitions) {
effect.f |= INERT; effect.f |= INERT;
if (effect.out) {
transitions.push(...effect.out); // TODO differentiate between global and local
}
if (effect.r) {
for (const child of effect.r) {
pause_children(child, transitions);
}
}
}
/**
* @param {import('../types.js').ComputationSignal} effect
*/
function destroy_effect(effect) {
// TODO distinguish between 'block effects' (?) which own their own DOM // TODO distinguish between 'block effects' (?) which own their own DOM
// and other render effects // and other render effects
if (effect.dom) { if (effect.dom) {
remove(effect.dom); remove(effect.dom);
} }
done(); // TODO defer until transitions have completed if (effect.r) {
for (const child of effect.r) {
destroy_effect(child);
}
}
} }
/** /**
* @param {import('../types.js').ComputationSignal} signal * @param {import('../types.js').ComputationSignal} effect
*/ */
export function resume_effect(signal) { export function resume_effect(effect) {
// TODO if (effect.r) {
for (const child of effect.r) {
resume_effect(child);
}
}
effect.f ^= INERT;
} }

@ -495,133 +495,68 @@ function is_transition_block(block) {
/** /**
* @template P * @template P
* @param {HTMLElement} dom * @param {HTMLElement} element
* @param {() => import('./types.js').TransitionFn<P | undefined> | import('./types.js').AnimateFn<P | undefined>} get_transition_fn * @param {() => import('./types.js').TransitionFn<P | undefined>} get_fn
* @param {(() => P) | null} props_fn * @param {(() => P) | null} get_params
* @param {'in' | 'out' | 'both' | 'key'} direction * @param {'in' | 'out' | 'both'} direction
* @param {boolean} global * @param {boolean} global
* @returns {void} * @returns {void}
*/ */
export function bind_transition(dom, get_transition_fn, props_fn, direction, global) { export function bind_transition(element, get_fn, get_params, direction, global) {
const transition_effect = /** @type {import('./types.js').EffectSignal} */ (current_effect); const effect = /** @type {import('./types.js').EffectSignal} */ (current_effect);
const block = current_block;
const is_keyed_transition = direction === 'key';
let can_show_intro_on_mount = true; let p = direction === 'out' ? 1 : 0;
let can_apply_lazy_transitions = false;
if (is_keyed_transition) { /** @type {Animation | null} */
// @ts-ignore let current_animation;
dom.__animate = true;
}
/** @type {import('./types.js').Block | null} */
let transition_block = block;
main: while (transition_block !== null) {
if (is_transition_block(transition_block)) {
if (transition_block.t === EACH_ITEM_BLOCK) {
// Lazily apply the each block transition
transition_block.r = each_item_transition;
transition_block.a = each_item_animate;
transition_block = transition_block.p;
} else if (transition_block.t === AWAIT_BLOCK && transition_block.n /* pending */) {
can_show_intro_on_mount = true;
} else if (transition_block.t === IF_BLOCK) {
transition_block.r = if_block_transition;
if (can_show_intro_on_mount) {
/** @type {import('./types.js').Block | null} */
let if_block = transition_block;
while (if_block.t === IF_BLOCK) {
// If we have an if block parent that is currently falsy then
// we can show the intro on mount as long as that block is mounted
if (if_block.e !== null && !if_block.v) {
can_show_intro_on_mount = true;
break main;
}
if_block = if_block.p;
}
}
}
if (!can_apply_lazy_transitions && can_show_intro_on_mount) {
can_show_intro_on_mount = transition_block.e !== null;
}
if (can_show_intro_on_mount || !global) {
can_apply_lazy_transitions = true;
}
} else if (transition_block.t === ROOT_BLOCK && !can_apply_lazy_transitions) {
can_show_intro_on_mount = transition_block.e !== null || transition_block.i;
}
transition_block = transition_block.p;
}
/** @type {import('./types.js').Transition} */ /** @type {TODO} */
let transition; let current_options;
effect(() => { const transition = {
let already_mounted = false; global,
if (transition !== undefined) { to(target, callback) {
already_mounted = true; if (current_animation) {
// Destroy any existing transitions first // TODO get `p` from current_animation?
transition.x(); current_animation.cancel();
} }
const transition_fn = get_transition_fn();
/** @param {DOMRect} [from] */
const init = (from) =>
untrack(() => {
const props = props_fn === null ? {} : props_fn();
return is_keyed_transition
? /** @type {import('./types.js').AnimateFn<any>} */ (transition_fn)(
dom,
{ from: /** @type {DOMRect} */ (from), to: dom.getBoundingClientRect() },
props,
{}
)
: /** @type {import('./types.js').TransitionFn<any>} */ (transition_fn)(dom, props, {
direction
});
});
transition = create_transition(dom, init, direction, transition_effect);
const is_intro = direction === 'in';
const show_intro = can_show_intro_on_mount && (is_intro || direction === 'both');
if (show_intro && !already_mounted) { current_options ??= get_fn()(element, get_params?.(), { direction });
transition.p = transition.i();
}
const effect = managed_pre_effect(() => { if (current_options.css) {
destroy_signal(effect); // WAAPI
dom.inert = false; const keyframes = [];
const n = current_options.duration / (1000 / 60);
if (show_intro && !already_mounted) { for (let i = 0; i <= n; i += 1) {
transition.in(); const t = current_options.easing(p + ((target - p) * i) / n);
const css = current_options.css(t);
keyframes.push(css_to_keyframe(css));
} }
/** @type {import('./types.js').Block | null} */ current_animation = element.animate(keyframes, {
let transition_block = block; duration: current_options.duration,
while (!is_intro && transition_block !== null) { easing: 'linear'
const parent = transition_block.p; });
if (is_transition_block(transition_block)) {
if (transition_block.r !== null) { current_animation.finished.then(() => {
transition_block.r(transition); console.log('done');
} current_animation = null;
if ( callback();
parent === null || });
(!global && (transition_block.t !== IF_BLOCK || parent.t !== IF_BLOCK || parent.v)) } else {
) { // TODO timer
break;
} }
} }
transition_block = parent; };
// TODO don't pass strings around like this, it's silly
if (direction === 'in' || direction === 'both') {
(effect.in ??= []).push(transition);
} }
}, false);
});
if (direction === 'key') { if (direction === 'out' || direction === 'both') {
effect(() => { (effect.out ??= []).push(transition);
return () => {
transition.x();
};
});
} }
} }

@ -37,6 +37,7 @@ class Animation {
#reversed; #reversed;
#target; #target;
#paused; #paused;
#finished;
/** /**
* @param {HTMLElement} target * @param {HTMLElement} target
@ -59,6 +60,10 @@ class Animation {
this.#keyframes = keyframes; this.#keyframes = keyframes;
} }
}; };
this.finished = new Promise((fulfil) => {
this.#finished = fulfil;
});
} }
play() { play() {
@ -78,8 +83,13 @@ class Animation {
} else { } else {
this.currentTime = raf.time - this.#timeline_offset; 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) {
this.#finished();
}
} }
/** /**

@ -11,6 +11,6 @@ export default test({
btn1.click(); btn1.click();
}); });
assert.htmlEqual(target.innerHTML, `<button>hide</button><div style="opacity: 0;">hello</div>`); assert.htmlEqual(target.innerHTML, `<button>hide</button><div>hello</div>`);
} }
}); });

Loading…
Cancel
Save