almost everything working

pull/10798/head
Rich Harris 2 years ago
parent fa346378b2
commit 628df85977

@ -28,6 +28,7 @@ import {
AttributeAliases, AttributeAliases,
DOMBooleanAttributes, DOMBooleanAttributes,
EACH_INDEX_REACTIVE, EACH_INDEX_REACTIVE,
EACH_IS_ANIMATED,
EACH_IS_CONTROLLED, EACH_IS_CONTROLLED,
EACH_IS_STRICT_EQUALS, EACH_IS_STRICT_EQUALS,
EACH_ITEM_REACTIVE, EACH_ITEM_REACTIVE,
@ -2337,6 +2338,16 @@ export const template_visitors = {
each_type |= EACH_ITEM_REACTIVE; each_type |= EACH_ITEM_REACTIVE;
} }
if (
node.key &&
node.body.nodes.some((child) => {
if (child.type !== 'RegularElement' && child.type !== 'SvelteElement') return false;
return child.attributes.some((attr) => attr.type === 'AnimateDirective');
})
) {
each_type |= EACH_IS_ANIMATED;
}
if (each_node_meta.is_controlled) { if (each_node_meta.is_controlled) {
each_type |= EACH_IS_CONTROLLED; each_type |= EACH_IS_CONTROLLED;
} }

@ -15,12 +15,13 @@ import {
} from '../hydration.js'; } from '../hydration.js';
import { empty } from '../operations.js'; import { empty } from '../operations.js';
import { insert, remove } from '../reconciler.js'; import { insert, remove } from '../reconciler.js';
import { current_block } from '../../runtime.js'; import { current_block, untrack } from '../../runtime.js';
import { import {
destroy_effect, destroy_effect,
pause_effect, pause_effect,
render_effect, render_effect,
resume_effect resume_effect,
user_effect
} from '../../reactivity/effects.js'; } from '../../reactivity/effects.js';
import { source, mutable_source, set } from '../../reactivity/sources.js'; import { source, mutable_source, set } from '../../reactivity/sources.js';
import { is_array, is_frozen, map_get, map_set } from '../../utils.js'; import { is_array, is_frozen, map_get, map_set } from '../../utils.js';
@ -29,6 +30,14 @@ import { STATE_SYMBOL } from '../../constants.js';
var NEW_BLOCK = -1; var NEW_BLOCK = -1;
var LIS_BLOCK = -2; var LIS_BLOCK = -2;
/** @type {import('#client').EachItemBlock | null} */
export let current_each_item_block = null;
/** @param {import('#client').EachItemBlock | null} block */
export function set_current_each_item_block(block) {
current_each_item_block = block;
}
/** /**
* @template V * @template V
* @param {Element | Comment} anchor The next sibling node, or the parent node if this is a 'controlled' block * @param {Element | Comment} anchor The next sibling node, or the parent node if this is a 'controlled' block
@ -316,13 +325,23 @@ function reconcile_tracked_array(array, each_block, anchor, render_fn, flags, ke
var b_blocks = Array(b); var b_blocks = Array(b);
var is_animated = (flags & EACH_IS_ANIMATED) !== 0; var is_animated = (flags & EACH_IS_ANIMATED) !== 0;
var should_update = is_animated || (flags & (EACH_ITEM_REACTIVE | EACH_INDEX_REACTIVE)) !== 0; var should_update = (flags & (EACH_ITEM_REACTIVE | EACH_INDEX_REACTIVE)) !== 0;
var start = 0; var start = 0;
var block; var block;
/** @type {Array<import('#client').EachItemBlock>} */ /** @type {Array<import('#client').EachItemBlock>} */
var to_destroy = []; var to_destroy = [];
/** @type {Array<import('#client').EachItemBlock>} */
var to_animate = [];
if (is_animated) {
for (block of a_blocks) {
// TODO can we avoid measuring blocks that will be destroyed?
block.a?.measure();
}
}
// Step 1 — trim common suffix // Step 1 — trim common suffix
while (a > 0 && b > 0 && a_blocks[a - 1].k === keys[b - 1]) { while (a > 0 && b > 0 && a_blocks[a - 1].k === keys[b - 1]) {
block = b_blocks[--b] = a_blocks[--a]; block = b_blocks[--b] = a_blocks[--a];
@ -392,8 +411,7 @@ function reconcile_tracked_array(array, each_block, anchor, render_fn, flags, ke
b_blocks[index] = block; b_blocks[index] = block;
if (is_animated) { if (is_animated) {
// If keys are animated, we need to do updates before actual moves to_animate.push(block);
update_block(block, array[index], index, flags);
} }
} }
} }
@ -424,6 +442,16 @@ function reconcile_tracked_array(array, each_block, anchor, render_fn, flags, ke
} }
} }
if (to_animate.length > 0) {
user_effect(() => {
untrack(() => {
for (block of to_animate) {
block.a?.apply();
}
});
});
}
var remaining = to_destroy.length; var remaining = to_destroy.length;
if (remaining > 0) { if (remaining > 0) {
var clear = () => { var clear = () => {
@ -602,6 +630,7 @@ function create_block(item, key, index, render_fn, flags) {
/** @type {import('#client').EachItemBlock} */ /** @type {import('#client').EachItemBlock} */
var block = { var block = {
a: null,
// dom // dom
d: null, d: null,
// effect // effect
@ -615,6 +644,11 @@ function create_block(item, key, index, render_fn, flags) {
v: item_value v: item_value
}; };
var previous_each_item_block = current_each_item_block;
try {
current_each_item_block = block;
block.e = render_effect( block.e = render_effect(
() => { () => {
render_fn(null, block.v, block.i); render_fn(null, block.v, block.i);
@ -624,4 +658,7 @@ function create_block(item, key, index, render_fn, flags) {
); );
return block; return block;
} finally {
current_each_item_block = previous_each_item_block;
}
} }

@ -11,6 +11,7 @@ import { remove } from '../reconciler.js';
import { current_block } from '../../runtime.js'; import { current_block } from '../../runtime.js';
import { is_array } from '../../utils.js'; import { is_array } from '../../utils.js';
import { set_run_transitions } from '../../render.js'; import { set_run_transitions } from '../../render.js';
import { current_each_item_block, set_current_each_item_block } from './each.js';
/** /**
* @param {import('#client').Block} block * @param {import('#client').Block} block
@ -64,10 +65,15 @@ export function element(anchor_node, tag_fn, is_svg, render_fn) {
/** @type {import('#client').Effect | null} */ /** @type {import('#client').Effect | null} */
let effect; let effect;
let each_item_block = current_each_item_block;
const wrapper = render_effect(() => { const wrapper = render_effect(() => {
const next_tag = tag_fn() || null; const next_tag = tag_fn() || null;
if (next_tag === tag) return; if (next_tag === tag) return;
var previous_each_item_block = current_each_item_block;
set_current_each_item_block(each_item_block);
// We try our best infering the namespace in case it's not possible to determine statically, // We try our best infering the namespace in case it's not possible to determine statically,
// but on the first render on the client (without hydration) the parent will be undefined, // but on the first render on the client (without hydration) the parent will be undefined,
// since the anchor is not attached to its parent / the dom yet. // since the anchor is not attached to its parent / the dom yet.
@ -134,6 +140,8 @@ export function element(anchor_node, tag_fn, is_svg, render_fn) {
tag = next_tag; tag = next_tag;
if (tag) current_tag = tag; if (tag) current_tag = tag;
set_run_transitions(true); set_run_transitions(true);
set_current_each_item_block(previous_each_item_block);
}, block); }, block);
wrapper.ondestroy = () => { wrapper.ondestroy = () => {

@ -1,11 +1,12 @@
import { noop } from '../../../common.js'; import { noop } from '../../../common.js';
import { user_effect } from '../../reactivity/effects.js'; import { user_effect } from '../../reactivity/effects.js';
import { current_effect, untrack } from '../../runtime.js'; import { current_block, current_effect, untrack } from '../../runtime.js';
import { raf } from '../../timing.js'; import { raf } from '../../timing.js';
import { loop } from '../../loop.js'; import { loop } from '../../loop.js';
import { run_transitions } from '../../render.js'; import { run_transitions } from '../../render.js';
import { TRANSITION_GLOBAL, TRANSITION_IN, TRANSITION_OUT } from '../../constants.js'; import { TRANSITION_GLOBAL, TRANSITION_IN, TRANSITION_OUT } from '../../constants.js';
import { is_function } from '../../utils.js'; import { is_function } from '../../utils.js';
import { current_each_item_block } from '../blocks/each.js';
/** /**
* @template T * @template T
@ -66,8 +67,42 @@ function css_to_keyframe(css) {
/** @param {number} t */ /** @param {number} t */
const linear = (t) => t; const linear = (t) => t;
export function animation() { /**
// TODO * @template P
* @param {Element} element
* @param {() => import('#client').AnimateFn<P | undefined>} get_fn
* @param {(() => P) | null} get_params
*/
export function animation(element, get_fn, get_params) {
/** @type {DOMRect} */
let from;
/** @type {DOMRect} */
let to;
/** @type {import('#client').Animation | undefined} */
let animation;
/** @type {import('#client').AnimationManager} */
const manager = {
measure() {
from = element.getBoundingClientRect();
},
apply() {
to = element.getBoundingClientRect();
const options = get_fn()(element, { from, to }, get_params?.(), {}); // TODO what is the last argument?
animation?.abort();
// TODO bail if `from` and `to` match
animation = animate(element, options, undefined, 1, () => {
animation = undefined;
});
}
};
/** @type {import('#client').EachItemBlock} */ (current_each_item_block).a = manager;
} }
/** /**
@ -195,7 +230,7 @@ function animate(element, options, counterpart, t2, callback) {
}; };
} }
dispatch_event(element, t2 === 1 ? 'introstart' : 'outrostart'); dispatch_event(element, t2 === 1 ? 'introstart' : 'outrostart'); // TODO not for `animate:`
var { delay = 0, duration, css, tick, easing = linear } = options; var { delay = 0, duration, css, tick, easing = linear } = options;

@ -123,6 +123,8 @@ export type EachBlock = {
}; };
export type EachItemBlock = { export type EachItemBlock = {
/** animation manager */
a: AnimationManager | null;
/** dom */ /** dom */
d: null | TemplateNode | Array<TemplateNode>; d: null | TemplateNode | Array<TemplateNode>;
/** effect */ /** effect */
@ -162,6 +164,11 @@ export interface TransitionManager {
stop: () => void; stop: () => void;
} }
export interface AnimationManager {
measure: () => void;
apply: () => void;
}
export interface Animation { export interface Animation {
abort: () => void; abort: () => void;
neuter: () => void; neuter: () => void;

@ -23,7 +23,6 @@ export default test({
`, `,
test({ assert, component, target, raf }) { test({ assert, component, target, raf }) {
raf.tick(0);
component.tag = 'p'; component.tag = 'p';
assert.equal(target.querySelectorAll('p').length, 5); assert.equal(target.querySelectorAll('p').length, 5);
@ -52,11 +51,11 @@ export default test({
]; ];
divs = target.querySelectorAll('div'); divs = target.querySelectorAll('div');
assert.ok(~divs[0].style.transform); assert.equal(divs[0].style.transform, 'translate(0px, 120px)');
assert.equal(divs[1].style.transform, 'translate(1px, 0px)'); assert.equal(divs[1].style.transform, 'translate(0px, 0px)');
assert.equal(divs[2].style.transform, 'translate(1px, 0px)'); assert.equal(divs[2].style.transform, 'translate(0px, 0px)');
assert.equal(divs[3].style.transform, 'translate(1px, 0px)'); assert.equal(divs[3].style.transform, 'translate(0px, 0px)');
assert.ok(~divs[4].style.transform); assert.equal(divs[4].style.transform, 'translate(0px, -120px)');
raf.tick(100); raf.tick(100);
assert.deepEqual([divs[0].style.transform, divs[4].style.transform], ['', '']); assert.deepEqual([divs[0].style.transform, divs[4].style.transform], ['', '']);

@ -8,7 +8,7 @@
return { return {
duration: 100, duration: 100,
css: (t, u) => `transform: translate(${u + dx}px, ${u * dy}px)` css: (t, u) => `transform: translate(${u * dx}px, ${u * dy}px)`
}; };
} }
</script> </script>

Loading…
Cancel
Save