@ -26,9 +26,6 @@ import { source, mutable_source, set } from '../../reactivity/sources.js';
import { is _array , is _frozen , map _get , map _set } from '../../utils.js' ;
import { STATE _SYMBOL } from '../../constants.js' ;
var NEW _ITEM = - 1 ;
var LIS _ITEM = - 2 ;
/ * *
* The row of a keyed each block that is currently updating . We track this
* so that ` animate: ` directives have something to attach themselves to
@ -41,25 +38,33 @@ export function set_current_each_item(item) {
current _each _item = item ;
}
/ * *
* @ param { any } _
* @ param { number } i
* /
export function index ( _ , i ) {
return i ;
}
/ * *
* Pause multiple effects simultaneously , and coordinate their
* subsequent destruction . Used in each blocks
* @ param { import ( '#client' ) . Effect [ ] } effects
* @ param { import ( '#client' ) . E achItem[ ] } item s
* @ param { null | Node } controlled _anchor
* @ param { ( ) => void } [ callback ]
* /
function pause _effects ( effects , controlled _anchor , callback ) {
function pause _effects ( item s, controlled _anchor , callback ) {
/** @type {import('#client').TransitionManager[]} */
var transitions = [ ] ;
var length = effect s. length ;
var length = item s. length ;
for ( var i = 0 ; i < length ; i ++ ) {
pause _children ( effects[ i ] , transitions , true ) ;
pause _children ( items[ i ] . e , transitions , true ) ;
}
// If we have a controlled anchor, it means that the each block is inside a single
// DOM element, so we can apply a fast-path for clearing the contents of the element.
if ( effects. length > 0 && transitions . length === 0 && controlled _anchor !== null ) {
if ( length > 0 && transitions . length === 0 && controlled _anchor !== null ) {
var parent _node = /** @type {Element} */ ( controlled _anchor . parentNode ) ;
parent _node . textContent = '' ;
parent _node . append ( controlled _anchor ) ;
@ -67,7 +72,7 @@ function pause_effects(effects, controlled_anchor, callback) {
run _out _transitions ( transitions , ( ) => {
for ( var i = 0 ; i < length ; i ++ ) {
destroy _effect ( effects[ i ] ) ;
destroy _effect ( items[ i ] . e ) ;
}
if ( callback !== undefined ) callback ( ) ;
@ -79,15 +84,14 @@ function pause_effects(effects, controlled_anchor, callback) {
* @ param { Element | Comment } anchor The next sibling node , or the parent node if this is a 'controlled' block
* @ param { number } flags
* @ param { ( ) => V [ ] } get _collection
* @ param { null | ( ( item : V ) => string ) } get _key
* @ param { ( value : V , index : number ) => any } get _key
* @ param { ( anchor : Node , item : import ( '#client' ) . MaybeSource < V > , index : import ( '#client' ) . MaybeSource < number > ) => void } render _fn
* @ param { null | ( ( anchor : Node ) => void ) } fallback _fn
* @ param { typeof reconcile _indexed _array | reconcile _tracked _array } reconcile _fn
* @ returns { void }
* /
function each ( anchor , flags , get _collection , get _key , render _fn , fallback _fn , reconcile _fn ) {
export function each ( anchor , flags , get _collection , get _key , render _fn , fallback _fn = null ) {
/** @type {import('#client').EachState} */
var state = { flags , items : [ ] } ;
var state = { flags , items : new Map ( ) , next : null } ;
var is _controlled = ( flags & EACH _IS _CONTROLLED ) !== 0 ;
@ -113,8 +117,6 @@ function each(anchor, flags, get_collection, get_key, render_fn, fallback_fn, re
? [ ]
: Array . from ( collection ) ;
var keys = get _key === null ? array : array . map ( get _key ) ;
var length = array . length ;
// If we are working with an array that isn't proxied or frozen, then remove strict equality and ensure the items
@ -145,11 +147,15 @@ function each(anchor, flags, get_collection, get_key, render_fn, fallback_fn, re
// this is separate to the previous block because `hydrating` might change
if ( hydrating ) {
var b _items = [ ] ;
/** @type {Node} */
var child _anchor = hydrate _nodes [ 0 ] ;
/** @type {import('#client').EachItem | import('#client').EachState} */
var prev = state ;
/** @type {import('#client').EachItem} */
var item ;
for ( var i = 0 ; i < length ; i ++ ) {
if (
child _anchor . nodeType !== 8 ||
@ -163,8 +169,13 @@ function each(anchor, flags, get_collection, get_key, render_fn, fallback_fn, re
}
child _anchor = hydrate _anchor ( child _anchor ) ;
b _items [ i ] = create _item ( child _anchor , array [ i ] , keys ? . [ i ] , i , render _fn , flags ) ;
var value = array [ i ] ;
var key = get _key ( value , i ) ;
item = create _item ( child _anchor , prev , null , value , key , i , render _fn , flags ) ;
state . items . set ( key , item ) ;
child _anchor = /** @type {Comment} */ ( child _anchor . nextSibling ) ;
prev = item ;
}
// remove excess nodes
@ -175,12 +186,10 @@ function each(anchor, flags, get_collection, get_key, render_fn, fallback_fn, re
child _anchor = next ;
}
}
state . items = b _items ;
}
if ( ! hydrating ) {
reconcile _fn ( array , state , anchor , render _fn , flags , keys ) ;
reconcile ( array , state , anchor , render _fn , flags , get_ key) ;
}
if ( fallback _fn !== null ) {
@ -204,33 +213,6 @@ function each(anchor, flags, get_collection, get_key, render_fn, fallback_fn, re
} ) ;
}
/ * *
* @ template V
* @ param { Element | Comment } anchor
* @ param { number } flags
* @ param { ( ) => V [ ] } get _collection
* @ param { null | ( ( item : V ) => string ) } get _key
* @ param { ( anchor : Node , item : import ( '#client' ) . MaybeSource < V > , index : import ( '#client' ) . MaybeSource < number > ) => void } render _fn
* @ param { null | ( ( anchor : Node ) => void ) } [ fallback _fn ]
* @ returns { void }
* /
export function each _keyed ( anchor , flags , get _collection , get _key , render _fn , fallback _fn = null ) {
each ( anchor , flags , get _collection , get _key , render _fn , fallback _fn , reconcile _tracked _array ) ;
}
/ * *
* @ template V
* @ param { Element | Comment } anchor
* @ param { number } flags
* @ param { ( ) => V [ ] } get _collection
* @ param { ( anchor : Node , item : import ( '#client' ) . MaybeSource < V > , index : import ( '#client' ) . MaybeSource < number > ) => void } render _fn
* @ param { null | ( ( anchor : Node ) => void ) } [ fallback _fn ]
* @ returns { void }
* /
export function each _indexed ( anchor , flags , get _collection , render _fn , fallback _fn = null ) {
each ( anchor , flags , get _collection , null , render _fn , fallback _fn , reconcile _indexed _array ) ;
}
/ * *
* @ template V
* @ param { Array < V > } array
@ -238,308 +220,182 @@ export function each_indexed(anchor, flags, get_collection, render_fn, fallback_
* @ param { Element | Comment | Text } anchor
* @ param { ( anchor : Node , item : import ( '#client' ) . MaybeSource < V > , index : number | import ( '#client' ) . Source < number > ) => void } render _fn
* @ param { number } flags
* @ param { ( value : V , index : number ) => any } get _key
* @ returns { void }
* /
function reconcile _indexed _array ( array , state , anchor , render _fn , flags ) {
var a _items = state . items ;
function reconcile ( array , state , anchor , render _fn , flags , get _key ) {
var is _animated = ( flags & EACH _IS _ANIMATED ) !== 0 ;
var should _update = ( flags & ( EACH _ITEM _REACTIVE | EACH _INDEX _REACTIVE ) ) !== 0 ;
var a = a _items . length ;
var b = array . length ;
var min = Math . min ( a , b ) ;
var length = array . length ;
var items = state . items ;
var first = state . next ;
var current = first ;
/** @type { typeof a_items } */
var b_items = Array ( b ) ;
/** @type { Set<import('#client').EachItem> } */
var seen = new Set ( ) ;
var item ;
var value ;
// update items
for ( var i = 0 ; i < min ; i += 1 ) {
value = array [ i ] ;
item = a _items [ i ] ;
b _items [ i ] = item ;
update _item ( item , value , i , flags ) ;
resume _effect ( item . e ) ;
}
/** @type {import('#client').EachState | import('#client').EachItem} */
var prev = state ;
if ( b > a ) {
// add items
for ( ; i < b ; i += 1 ) {
value = array [ i ] ;
item = create _item ( anchor , value , null , i , render _fn , flags ) ;
b _items [ i ] = item ;
}
state . items = b _items ;
} else if ( a > b ) {
// remove items
var effects = [ ] ;
for ( i = b ; i < a ; i += 1 ) {
effects . push ( a _items [ i ] . e ) ;
}
var controlled _anchor = ( flags & EACH _IS _CONTROLLED ) !== 0 && b === 0 ? anchor : null ;
/** @type {import('#client').EachItem[]} */
var to _animate = [ ] ;
pause _effects ( effects , controlled _anchor , ( ) => {
state . items . length = b ;
} ) ;
}
}
/** @type {import('#client').EachItem[]} */
var matched = [ ] ;
/ * *
* Reconcile arrays by the equality of the elements in the array . This algorithm
* is based on Ivi ' s reconcilation logic :
* https : //github.com/localvoid/ivi/blob/9f1bd0918f487da5b131941228604763c5d8ef56/packages/ivi/src/client/core.ts#L968
* @ template V
* @ param { Array < V > } array
* @ param { import ( '#client' ) . EachState } state
* @ param { Element | Comment | Text } anchor
* @ param { ( anchor : Node , item : import ( '#client' ) . MaybeSource < V > , index : number | import ( '#client' ) . Source < number > ) => void } render _fn
* @ param { number } flags
* @ param { any [ ] } keys
* @ returns { void }
* /
function reconcile _tracked _array ( array , state , anchor , render _fn , flags , keys ) {
var a _items = state . items ;
/** @type {import('#client').EachItem[]} */
var stashed = [ ] ;
var a = a _items . length ;
var b = array . length ;
/** @type {V} */
var value ;
/** @type { Array<import('#client').EachItem> } */
var b_items = Array ( b ) ;
/** @type {any} */
var key ;
var is _animated = ( flags & EACH _IS _ANIMATED ) !== 0 ;
var should _update = ( flags & ( EACH _ITEM _REACTIVE | EACH _INDEX _REACTIVE ) ) !== 0 ;
var is _controlled = ( flags & EACH _IS _CONTROLLED ) !== 0 ;
var start = 0 ;
/** @type {import('#client').EachItem | undefined} */
var item ;
/** @type {import('#client').Effect[]} */
var to _destroy = [ ] ;
/** @type {Array<import('#client').EachItem>} */
var to _animate = [ ] ;
// Step 1 — trim common suffix
while ( a > 0 && b > 0 && a _items [ a - 1 ] . k === keys [ b - 1 ] ) {
item = b _items [ -- b ] = a _items [ -- a ] ;
anchor = get _first _child ( item ) ;
/** @type {number} */
var i ;
resume _effect ( item . e ) ;
if ( is _animated ) {
for ( i = 0 ; i < length ; i += 1 ) {
value = array [ i ] ;
key = get _key ( value , i ) ;
item = items . get ( key ) ;
if ( should _update ) {
update _item ( item , array [ b ] , b , flags ) ;
}
if ( is _animated ) {
item . a ? . measure ( ) ;
to _animate . push ( item ) ;
if ( item !== undefined ) {
item . a ? . measure ( ) ;
to _animate . push ( item ) ;
}
}
}
// Step 2 — trim common prefix
while ( start < a && start < b && a _items [ start ] . k === keys [ start ] ) {
item = b _items [ start ] = a _items [ start ] ;
resume _effect ( item . e ) ;
for ( i = 0 ; i < length ; i += 1 ) {
value = array [ i ] ;
key = get _key ( value , i ) ;
item = items . get ( key ) ;
if ( item === undefined ) {
prev = create _item (
current ? get _first _child ( current ) : anchor ,
prev ,
prev . next ,
value ,
key ,
i ,
render _fn ,
flags
) ;
items . set ( key , prev ) ;
matched = [ ] ;
stashed = [ ] ;
current = prev . next ;
continue ;
}
if ( should _update ) {
update _item ( item , array [ start ] , start , flags ) ;
update _item ( item , value, i , flags ) ;
}
if ( is _animated ) {
item . a ? . measure ( ) ;
to _animate . push ( item ) ;
}
start += 1 ;
}
// Step 3 — update
if ( start === a ) {
// add only
while ( start < b ) {
item = create _item ( anchor , array [ start ] , keys [ start ] , start , render _fn , flags ) ;
b _items [ start ++ ] = item ;
}
} else if ( start === b ) {
// remove only
while ( start < a ) {
to _destroy . push ( a _items [ start ++ ] . e ) ;
}
} else {
// reconcile
var moved = false ;
var sources = new Int32Array ( b - start ) ;
var indexes = new Map ( ) ;
var i ;
var index ;
var last _item ;
// store the indexes of each item in the new world
for ( i = start ; i < b ; i += 1 ) {
sources [ i - start ] = NEW _ITEM ;
map _set ( indexes , keys [ i ] , i ) ;
}
resume _effect ( item . e ) ;
if ( is _animated ) {
// for all items that were in both the old and the new list,
// measure them and store them in `to_animate` so we can
// apply animations once the DOM has been updated
for ( i = 0 ; i < a _items . length ; i += 1 ) {
item = a _items [ i ] ;
if ( indexes . has ( item . k ) ) {
item . a ? . measure ( ) ;
to _animate . push ( item ) ;
}
}
}
if ( item !== current ) {
if ( seen . has ( item ) ) {
if ( matched . length < stashed . length ) {
// more efficient to move later items to the front
var start = stashed [ 0 ] ;
var local _anchor = get _first _child ( start ) ;
var j ;
// populate the `sources` array for each old item with
// its new index, so that we can calculate moves
for ( i = start ; i < a ; i += 1 ) {
item = a _items [ i ] ;
index = map _get ( indexes , item . k ) ;
prev = start . prev ;
resume _effect ( item . e ) ;
var a = matched [ 0 ] ;
var b = matched [ matched . length - 1 ] ;
if ( index === undefined ) {
to _destroy . push ( item . e ) ;
} else {
moved = true ;
sources [ index - start ] = i ;
b _items [ index ] = item ;
link ( a . prev , b . next ) ;
link ( prev , a ) ;
link ( b , start ) ;
if ( is _animated ) {
to _animate . push ( item ) ;
}
}
}
for ( j = 0 ; j < matched . length ; j += 1 ) {
move ( matched [ j ] , local _anchor ) ;
}
// if we need to move items (as opposed to just adding/removing),
// figure out how to do so efficiently (I would be lying if I said
// I fully understand this part)
if ( moved ) {
mark _lis ( sources ) ;
} else if ( is _controlled && to _destroy . length === a _items . length ) {
// We can optimize the case in which all items are replaced —
// destroy everything first, then append new items
pause _effects ( to _destroy , anchor ) ;
to _destroy = [ ] ;
}
for ( j = 0 ; j < stashed . length ; j += 1 ) {
seen . delete ( stashed [ j ] ) ;
}
// working from the back, insert new or moved items
while ( b -- > start ) {
index = sources [ b - start ] ;
var should _insert = index === NEW _ITEM ;
if ( should _insert ) {
if ( last _item !== undefined ) anchor = get _first _child ( last _item ) ;
item = create _item ( anchor , array [ b ] , keys [ b ] , b , render _fn , flags ) ;
} else {
item = b _items [ b ] ;
if ( should _update ) {
update _item ( item , array [ b ] , b , flags ) ;
}
current = start ;
prev = b ;
i -= 1 ;
if ( moved && index !== LIS _ITEM ) {
if ( last _item !== undefined ) anchor = get _first _child ( last _item ) ;
move ( /** @type {import('#client').Dom} */ ( item . e . dom ) , anchor ) ;
}
}
matched = [ ] ;
stashed = [ ] ;
} else {
// more efficient to move earlier items to the back
seen . delete ( item ) ;
move ( item , current ? get _first _child ( current ) : anchor ) ;
last _item = b _items [ b ] = item ;
}
}
link ( item . prev , item . next ) ;
link ( item , prev . next ) ;
link ( prev , item ) ;
if ( to _animate . length > 0 ) {
// TODO we need to briefly take any outroing elements out of the flow, so that
// we can figure out the eventual destination of the animating elements
// - https://github.com/sveltejs/svelte/pull/10798#issuecomment-2013681778
// - https://svelte.dev/repl/6e891305e9644a7ca7065fa95c79d2d2?version=4.2.9
effect ( ( ) => {
untrack ( ( ) => {
for ( item of to _animate ) {
item . a ? . apply ( ) ;
prev = item ;
}
} ) ;
} ) ;
}
var controlled _anchor = is _controlled && b _items . length === 0 ? anchor : null ;
continue ;
}
pause _effects ( to _destroy , controlled _anchor , ( ) => {
state . items = b _items ;
} ) ;
}
matched = [ ] ;
stashed = [ ] ;
/ * *
* Longest Increased Subsequence algorithm
* @ param { Int32Array } a
* @ returns { void }
* /
function mark _lis ( a ) {
var length = a . length ;
var parent = new Int32Array ( length ) ;
var index = new Int32Array ( length ) ;
var index _length = 0 ;
var i = 0 ;
/** @type {number} */
var j ;
/** @type {number} */
var k ;
while ( current !== null && current . k !== key ) {
seen . add ( current ) ;
stashed . push ( current ) ;
current = current . next ;
}
/** @type {number} */
var lo ;
if ( current === null ) {
continue ;
}
/** @type {number} */
var hi ;
item = current ;
}
// Skip -1 values at the start of the input array `a`.
for ( ; a [ i ] === NEW _ITEM ; ++ i ) {
/**/
matched . push ( item ) ;
prev = item ;
current = item . next ;
}
index [ 0 ] = i ++ ;
for ( ; i < length ; ++ i ) {
k = a [ i ] ;
if ( k !== NEW _ITEM ) {
// Ignore -1 values.
j = index [ index _length ] ;
const to _destroy = Array . from ( seen ) ;
if ( a [ j ] < k ) {
parent [ i ] = j ;
index [ ++ index _length ] = i ;
} else {
lo = 0 ;
hi = index _length ;
while ( current ) {
to _destroy . push ( current ) ;
current = current . next ;
}
while ( lo < hi ) {
j = ( lo + hi ) >> 1 ;
if ( a [ index [ j ] ] < k ) {
lo = j + 1 ;
} else {
hi = j ;
}
}
var controlled _anchor = ( flags & EACH _IS _CONTROLLED ) !== 0 && length === 0 ? anchor : null ;
if ( k < a [ index [ lo ] ] ) {
if ( lo > 0 ) {
parent [ i ] = index [ lo - 1 ] ;
}
index [ lo ] = i ;
}
}
pause _effects ( to _destroy , controlled _anchor , ( ) => {
for ( var i = 0 ; i < to _destroy . length ; i += 1 ) {
var item = to _destroy [ i ] ;
items . delete ( item . k ) ;
link ( item . prev , item . next ) ;
}
}
// Mutate input array `a` and assign -2 value to all nodes that are part of LIS.
j = index [ index _length ] ;
} ) ;
while ( index _length -- >= 0 ) {
a [ j ] = LIS _ITEM ;
j = parent [ j ] ;
if ( is _animated ) {
effect ( ( ) => {
untrack ( ( ) => {
for ( item of to _animate ) {
item . a ? . apply ( ) ;
}
} ) ;
} ) ;
}
}
@ -579,6 +435,8 @@ function update_item(item, value, index, type) {
/ * *
* @ template V
* @ param { Node } anchor
* @ param { import ( '#client' ) . EachItem | import ( '#client' ) . EachState } prev
* @ param { import ( '#client' ) . EachItem | null } next
* @ param { V } value
* @ param { unknown } key
* @ param { number } index
@ -586,7 +444,7 @@ function update_item(item, value, index, type) {
* @ param { number } flags
* @ returns { import ( '#client' ) . EachItem }
* /
function create _item ( anchor , value, key , index , render _fn , flags ) {
function create _item ( anchor , prev, next , value, key , index , render _fn , flags ) {
var previous _each _item = current _each _item ;
try {
@ -603,9 +461,14 @@ function create_item(anchor, value, key, index, render_fn, flags) {
k : key ,
a : null ,
// @ts-expect-error
e : null
e : null ,
prev ,
next
} ;
prev . next = item ;
if ( next !== null ) next . prev = item ;
current _each _item = item ;
item . e = branch ( ( ) => render _fn ( anchor , v , i ) ) ;
@ -616,15 +479,29 @@ function create_item(anchor, value, key, index, render_fn, flags) {
}
/ * *
* @ param { import ( '#client' ) . Dom} current
* @ param { import ( '#client' ) . EachItem} item
* @ param { Text | Element | Comment } anchor
* /
function move ( current , anchor ) {
if ( is _array ( current ) ) {
for ( var i = 0 ; i < current . length ; i ++ ) {
anchor . before ( current [ i ] ) ;
function move ( item , anchor ) {
var dom = item . e . dom ;
if ( dom !== null ) {
if ( is _array ( dom ) ) {
for ( var i = 0 ; i < dom . length ; i ++ ) {
anchor . before ( dom [ i ] ) ;
}
} else {
anchor . before ( dom ) ;
}
} else {
anchor . before ( current ) ;
}
}
/ * *
*
* @ param { import ( '#client' ) . EachItem | import ( '#client' ) . EachState } prev
* @ param { import ( '#client' ) . EachItem | null } next
* /
function link ( prev , next ) {
prev . next = next ;
if ( next !== null ) next . prev = prev ;
}