@ -19,6 +19,7 @@ import { untrack } from '../../runtime.js';
import {
destroy _effect ,
pause _effect ,
pause _effects ,
render _effect ,
resume _effect ,
user _effect
@ -26,21 +27,20 @@ import {
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' ;
import { create _block } from './utils.js' ;
var NEW _ BLOCK = - 1 ;
var LIS _ BLOCK = - 2 ;
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
* @ type { import ( '#client' ) . EachItem | null }
* /
export let current _each _item _block = null ;
export let current _each _item = null ;
/** @param {import('#client').EachItem | null} block */
export function set _current _each _item _block ( block ) {
current _each _item _block = block ;
/** @param {import('#client').EachItem | null} item */
export function set _current _each _item ( item ) {
current _each _item = item ;
}
/ * *
@ -55,8 +55,6 @@ export function set_current_each_item_block(block) {
* @ returns { void }
* /
function each ( anchor , get _collection , flags , get _key , render _fn , fallback _fn , reconcile _fn ) {
var block = create _block ( ) ;
/** @type {import('#client').EachState} */
var state = { flags , items : [ ] } ;
@ -71,132 +69,122 @@ function each(anchor, get_collection, flags, get_key, render_fn, fallback_fn, re
/** @type {import('#client').Effect | null} */
var fallback = null ;
var effect = render _effect (
( ) => {
var collection = get _collection ( ) ;
var effect = render _effect ( ( ) => {
var collection = get _collection ( ) ;
var array = is _array ( collection )
? collection
: collection == null
? [ ]
: Array . from ( collection ) ;
var array = is _array ( collection )
? collection
: collection == null
? [ ]
: Array . from ( collection ) ;
var keys = get _key === null ? array : array . map ( get _key ) ;
var keys = get _key === null ? array : array . map ( get _key ) ;
var length = array . length ;
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
// are treated as reactive, so they get wrapped in a signal.
var flags = state . flags ;
if ( ( flags & EACH _IS _STRICT _EQUALS ) !== 0 && ! is _frozen ( array ) && ! ( STATE _SYMBOL in array ) ) {
flags ^= EACH _IS _STRICT _EQUALS ;
// If we are working with an array that isn't proxied or frozen, then remove strict equality and ensure the items
// are treated as reactive, so they get wrapped in a signal.
var flags = state . flags ;
if ( ( flags & EACH _IS _STRICT _EQUALS ) !== 0 && ! is _frozen ( array ) && ! ( STATE _SYMBOL in array ) ) {
flags ^= EACH _IS _STRICT _EQUALS ;
// Additionally if we're in an keyed each block, we'll need ensure the items are all wrapped in signals.
if ( ( flags & EACH _KEYED ) !== 0 && ( flags & EACH _ITEM _REACTIVE ) === 0 ) {
flags ^= EACH _ITEM _REACTIVE ;
}
// Additionally if we're in an keyed each block, we'll need ensure the items are all wrapped in signals.
if ( ( flags & EACH _KEYED ) !== 0 && ( flags & EACH _ITEM _REACTIVE ) === 0 ) {
flags ^= EACH _ITEM _REACTIVE ;
}
}
/** `true` if there was a hydration mismatch. Needs to be a `let` or else it isn't treeshaken out */
let mismatch = false ;
if ( hydrating ) {
var is _else =
/** @type {Comment} */ ( current _hydration _fragment ? . [ 0 ] ) ? . data === 'ssr:each_else' ;
/** `true` if there was a hydration mismatch. Needs to be a `let` or else it isn't treeshaken out */
let mismatch = false ;
if ( hydrating ) {
var is _else =
/** @type {Comment} */ ( current _hydration _fragment ? . [ 0 ] ) ? . data === 'ssr:each_else' ;
if ( is _else !== ( length === 0 ) ) {
// hydration mismatch — remove the server-rendered DOM and start over
remove ( current _hydration _fragment ) ;
set _current _hydration _fragment ( null ) ;
mismatch = true ;
} else if ( is _else ) {
// Remove the each_else comment node or else it will confuse the subsequent hydration algorithm
/** @type {import('#client').TemplateNode[]} */ ( current _hydration _fragment ) . shift ( ) ;
}
}
if ( is _else !== ( length === 0 ) ) {
// hydration mismatch — remove the server-rendered DOM and start over
remove ( current _hydration _fragment ) ;
set _current _hydration _fragment ( null ) ;
// this is separate to the previous block because `hydrating` might change
if ( hydrating ) {
var b _items = [ ] ;
// Hydrate block
var hydration _list = /** @type {import('#client').TemplateNode[]} */ (
current _hydration _fragment
) ;
var hydrating _node = hydration _list [ 0 ] ;
for ( var i = 0 ; i < length ; i ++ ) {
var fragment = get _hydration _fragment ( hydrating _node ) ;
set _current _hydration _fragment ( fragment ) ;
if ( ! fragment ) {
// If fragment is null, then that means that the server rendered less items than what
// the client code specifies -> break out and continue with client-side node creation
mismatch = true ;
} else if ( is _else ) {
// Remove the each_else comment node or else it will confuse the subsequent hydration algorithm
/** @type {import('#client').TemplateNode[]} */ ( current _hydration _fragment ) . shift ( ) ;
break ;
}
}
// this is separate to the previous block because `hydrating` might change
if ( hydrating ) {
var b _blocks = [ ] ;
b _items [ i ] = create _item ( array [ i ] , keys ? . [ i ] , i , render _fn , flags ) ;
// Hydrate block
var hydration _list = /** @type {import('#client').TemplateNode[]} */ (
current _hydration _fragment
// TODO helperise this
hydrating _node = /** @type {import('#client').TemplateNode} */ (
/** @type {Node} */ (
/** @type {Node} */ ( fragment [ fragment . length - 1 ] || hydrating _node ) . nextSibling
) . nextSibling
) ;
var hydrating _node = hydration _list [ 0 ] ;
for ( var i = 0 ; i < length ; i ++ ) {
var fragment = get _hydration _fragment ( hydrating _node ) ;
set _current _hydration _fragment ( fragment ) ;
if ( ! fragment ) {
// If fragment is null, then that means that the server rendered less items than what
// the client code specifies -> break out and continue with client-side node creation
mismatch = true ;
break ;
}
b _blocks [ i ] = create _item ( array [ i ] , keys ? . [ i ] , i , render _fn , flags ) ;
// TODO helperise this
hydrating _node = /** @type {import('#client').TemplateNode} */ (
/** @type {Node} */ (
/** @type {Node} */ ( fragment [ fragment . length - 1 ] || hydrating _node ) . nextSibling
) . nextSibling
) ;
}
}
remove _excess _hydration _nodes ( hydration _list , hydrating _node ) ;
remove _excess _hydration _nodes ( hydration _list , hydrating _node ) ;
state . items = b _ block s;
}
state . items = b _items ;
}
if ( ! hydrating ) {
// TODO add 'empty controlled block' optimisation here
reconcile _fn ( array , state , anchor , render _fn , flags , keys ) ;
}
if ( ! hydrating ) {
// TODO add 'empty controlled block' optimisation here
reconcile _fn ( array , state , anchor , render _fn , flags , keys ) ;
}
if ( fallback _fn !== null ) {
if ( length === 0 ) {
if ( fallback ) {
resume _effect ( fallback ) ;
} else {
fallback = render _effect (
( ) => {
fallback _fn ( anchor ) ;
var dom = block . d ; // TODO would be nice if this was just returned from the managed effect function...
return ( ) => {
if ( dom !== null ) {
remove ( dom ) ;
dom = null ;
}
} ;
} ,
block ,
true
) ;
}
} else if ( fallback !== null ) {
pause _effect ( fallback , ( ) => {
fallback = null ;
} ) ;
if ( fallback _fn !== null ) {
if ( length === 0 ) {
if ( fallback ) {
resume _effect ( fallback ) ;
} else {
fallback = render _effect ( ( ) => {
var dom = fallback _fn ( anchor ) ;
return ( ) => {
if ( dom !== undefined ) {
remove ( dom ) ;
}
} ;
} , true ) ;
}
} else if ( fallback !== null ) {
pause _effect ( fallback , ( ) => {
fallback = null ;
} ) ;
}
}
if ( mismatch ) {
// Set a fragment so that Svelte continues to operate in hydration mode
set _current _hydration _fragment ( [ ] ) ;
}
} ,
block ,
false
) ;
if ( mismatch ) {
// Set a fragment so that Svelte continues to operate in hydration mode
set _current _hydration _fragment ( [ ] ) ;
}
} ) ;
effect . ondestroy = ( ) => {
for ( var item of state . items ) {
if ( item . d !== null ) {
if ( item . e . dom !== null ) {
remove ( item . e . dom ) ;
destroy _effect ( item . e ) ;
remove ( item . d ) ;
}
}
@ -274,26 +262,14 @@ function reconcile_indexed_array(array, state, anchor, render_fn, flags) {
state . items = b _items ;
} else if ( a > b ) {
// remove items
var remaining = a - b ;
var clear = ( ) => {
for ( var i = b ; i < a ; i += 1 ) {
var block = a _items [ i ] ;
if ( block . d ) remove ( block . d ) ;
}
var effects = [ ] ;
for ( i = b ; i < a ; i += 1 ) {
effects . push ( a _items [ i ] . e ) ;
}
pause _effects ( effects , ( ) => {
state . items . length = b ;
} ;
var check = ( ) => {
if ( -- remaining === 0 ) {
clear ( ) ;
}
} ;
for ( ; i < a ; i += 1 ) {
pause _effect ( a _items [ i ] . e , check ) ;
}
} ) ;
}
}
@ -311,42 +287,42 @@ function reconcile_indexed_array(array, state, anchor, render_fn, flags) {
* @ returns { void }
* /
function reconcile _tracked _array ( array , state , anchor , render _fn , flags , keys ) {
var a _ block s = state . items ;
var a _ item s = state . items ;
var a = a _ block s. length ;
var a = a _ item s. length ;
var b = array . length ;
/** @type {Array<import('#client').EachItem>} */
var b _ block s = Array ( b ) ;
var b _ item s = Array ( b ) ;
var is _animated = ( flags & EACH _IS _ANIMATED ) !== 0 ;
var should _update = ( flags & ( EACH _ITEM _REACTIVE | EACH _INDEX _REACTIVE ) ) !== 0 ;
var start = 0 ;
var block ;
var item ;
/** @type { Array<import('#client').EachItem> } */
/** @type { import('#client').Effect[] } */
var to _destroy = [ ] ;
// Step 1 — trim common suffix
while ( a > 0 && b > 0 && a _ block s[ a - 1 ] . k === keys [ b - 1 ] ) {
block = b _blocks [ -- b ] = a _block s[ -- a ] ;
anchor = get _first _child ( block ) ;
while ( a > 0 && b > 0 && a _ item s[ a - 1 ] . k === keys [ b - 1 ] ) {
item = b _items [ -- b ] = a _item s[ -- a ] ;
anchor = get _first _child ( item ) ;
resume _effect ( block . e ) ;
resume _effect ( item . e ) ;
if ( should _update ) {
update _item ( block , array [ b ] , b , flags ) ;
update _item ( item , array [ b ] , b , flags ) ;
}
}
// Step 2 — trim common prefix
while ( start < a && start < b && a _ block s[ start ] . k === keys [ start ] ) {
block = b _blocks [ start ] = a _block s[ start ] ;
while ( start < a && start < b && a _ item s[ start ] . k === keys [ start ] ) {
item = b _items [ start ] = a _item s[ start ] ;
resume _effect ( block . e ) ;
resume _effect ( item . e ) ;
if ( should _update ) {
update _item ( block , array [ start ] , start , flags ) ;
update _item ( item , array [ start ] , start , flags ) ;
}
start += 1 ;
@ -356,14 +332,14 @@ function reconcile_tracked_array(array, state, anchor, render_fn, flags, keys) {
if ( start === a ) {
// add only
while ( start < b ) {
block = create _item ( array [ start ] , keys [ start ] , start , render _fn , flags ) ;
b _ blocks[ start ++ ] = block ;
insert _item ( block , anchor ) ;
item = create _item ( array [ start ] , keys [ start ] , start , render _fn , flags ) ;
b _ items[ start ++ ] = item ;
insert _item ( item , anchor ) ;
}
} else if ( start === b ) {
// remove only
while ( start < a ) {
to _destroy . push ( a _ blocks[ start ++ ] ) ;
to _destroy . push ( a _ items[ start ++ ] . e ) ;
}
} else {
// reconcile
@ -372,78 +348,78 @@ function reconcile_tracked_array(array, state, anchor, render_fn, flags, keys) {
var indexes = new Map ( ) ;
var i ;
var index ;
var last _ block ;
var last _ item ;
var last _sibling ;
// store the indexes of each block in the new world
// store the indexes of each item in the new world
for ( i = start ; i < b ; i += 1 ) {
sources [ i - start ] = NEW _ BLOCK ;
sources [ i - start ] = NEW _ ITEM ;
map _set ( indexes , keys [ i ] , i ) ;
}
/** @type {Array<import('#client').EachItem>} */
var to _animate = [ ] ;
if ( is _animated ) {
// for all block s that were in both the old and the new list,
// for all item s 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 _ block s. length ; i += 1 ) {
block = a _block s[ i ] ;
if ( indexes . has ( block . k ) ) {
block . a ? . measure ( ) ;
to _animate . push ( block ) ;
for ( i = 0 ; i < a _ item s. length ; i += 1 ) {
item = a _item s[ i ] ;
if ( indexes . has ( item . k ) ) {
item . a ? . measure ( ) ;
to _animate . push ( item ) ;
}
}
}
// populate the `sources` array for each old block with
// 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 ) {
block = a _block s[ i ] ;
index = map _get ( indexes , block . k ) ;
item = a _item s[ i ] ;
index = map _get ( indexes , item . k ) ;
resume _effect ( block . e ) ;
resume _effect ( item . e ) ;
if ( index === undefined ) {
to _destroy . push ( block ) ;
to _destroy . push ( item. e ) ;
} else {
moved = true ;
sources [ index - start ] = i ;
b _ blocks[ index ] = block ;
b _ items[ index ] = item ;
if ( is _animated ) {
to _animate . push ( block ) ;
to _animate . push ( item ) ;
}
}
}
// if we need to move block s (as opposed to just adding/removing),
// if we need to move item s (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 ) ;
}
// working from the back, insert new or moved block s
// working from the back, insert new or moved item s
while ( b -- > start ) {
index = sources [ b - start ] ;
var insert = index === NEW _ BLOCK ;
var insert = index === NEW _ ITEM ;
if ( insert ) {
block = create _item ( array [ b ] , keys [ b ] , b , render _fn , flags ) ;
item = create _item ( array [ b ] , keys [ b ] , b , render _fn , flags ) ;
} else {
block = b _block s[ b ] ;
item = b _item s[ b ] ;
if ( should _update ) {
update _item ( block , array [ b ] , b , flags ) ;
update _item ( item , array [ b ] , b , flags ) ;
}
}
if ( insert || ( moved && index !== LIS _ BLOCK ) ) {
last _sibling = last _ block === undefined ? anchor : get _first _child ( last _ block ) ;
anchor = insert _item ( block , last _sibling ) ;
if ( insert || ( moved && index !== LIS _ ITEM ) ) {
last _sibling = last _ item === undefined ? anchor : get _first _child ( last _ item ) ;
anchor = insert _item ( item , last _sibling ) ;
}
last _ block = b _blocks [ b ] = block ;
last _ item = b _items [ b ] = item ;
}
if ( to _animate . length > 0 ) {
@ -453,36 +429,17 @@ function reconcile_tracked_array(array, state, anchor, render_fn, flags, keys) {
// - https://svelte.dev/repl/6e891305e9644a7ca7065fa95c79d2d2?version=4.2.9
user _effect ( ( ) => {
untrack ( ( ) => {
for ( block of to _animate ) {
block . a ? . apply ( ) ;
for ( item of to _animate ) {
item . a ? . apply ( ) ;
}
} ) ;
} ) ;
}
}
var remaining = to _destroy . length ;
if ( remaining > 0 ) {
var clear = ( ) => {
for ( block of to _destroy ) {
if ( block . d ) remove ( block . d ) ;
}
state . items = b _blocks ;
} ;
var check = ( ) => {
if ( -- remaining === 0 ) {
clear ( ) ;
}
} ;
for ( block of to _destroy ) {
pause _effect ( block . e , check ) ;
}
} else {
state . items = b _blocks ;
}
pause _effects ( to _destroy , ( ) => {
state . items = b _items ;
} ) ;
}
/ * *
@ -524,7 +481,7 @@ function mark_lis(a) {
var hi ;
// Skip -1 values at the start of the input array `a`.
for ( ; a [ i ] === NEW _ BLOCK ; ++ i ) {
for ( ; a [ i ] === NEW _ ITEM ; ++ i ) {
/**/
}
@ -533,7 +490,7 @@ function mark_lis(a) {
for ( ; i < length ; ++ i ) {
k = a [ i ] ;
if ( k !== NEW _ BLOCK ) {
if ( k !== NEW _ ITEM ) {
// Ignore -1 values.
j = index [ index _length ] ;
@ -567,27 +524,27 @@ function mark_lis(a) {
j = index [ index _length ] ;
while ( index _length -- >= 0 ) {
a [ j ] = LIS _ BLOCK ;
a [ j ] = LIS _ ITEM ;
j = parent [ j ] ;
}
}
/ * *
* @ param { import ( '#client' ) . EachItem } block
* @ param { Text | Element | Comment } sibling
* @ param { import ( '#client' ) . EachItem } item
* @ param { Text | Element | Comment } anchor
* @ returns { Text | Element | Comment }
* /
function insert _item ( block, sibling ) {
var current = /** @type {import('#client'). TemplateNode} */ ( block . d ) ;
return insert ( current , sibling ) ;
function insert _item ( item, anchor ) {
var current = /** @type {import('#client'). Dom} */ ( item . e . dom ) ;
return insert ( current , anchor ) ;
}
/ * *
* @ param { import ( '#client' ) . EachItem } block
* @ param { import ( '#client' ) . EachItem } item
* @ returns { Text | Element | Comment }
* /
function get _first _child ( block ) {
var current = block. d ;
function get _first _child ( item ) {
var current = item. e . dom ;
if ( is _array ( current ) ) {
return /** @type {Text | Element | Comment} */ ( current [ 0 ] ) ;
@ -597,48 +554,40 @@ function get_first_child(block) {
}
/ * *
* @ param { import ( '#client' ) . EachItem } block
* @ param { any } item
* @ param { import ( '#client' ) . EachItem } item
* @ param { any } value
* @ param { number } index
* @ param { number } type
* @ returns { void }
* /
function update _item ( block, item , index , type ) {
function update _item ( item, value , index , type ) {
if ( ( type & EACH _ITEM _REACTIVE ) !== 0 ) {
set ( block. v , item ) ;
set ( item. v , value ) ;
}
if ( ( type & EACH _INDEX _REACTIVE ) !== 0 ) {
set ( /** @type {import('#client').Value<number>} */ ( block . i ) , index ) ;
set ( /** @type {import('#client').Value<number>} */ ( item . i ) , index ) ;
} else {
block . i = index ;
item . i = index ;
}
}
/ * *
* @ template V
* @ param { V } item
* @ param { V } value
* @ param { unknown } key
* @ param { number } index
* @ param { ( anchor : null , item : V , index : number | import ( '#client' ) . Value < number > ) => void } render _fn
* @ param { number } flags
* @ returns { import ( '#client' ) . EachItem }
* /
function create _item ( item , key , index , render _fn , flags ) {
function create _item ( value , key , index , render _fn , flags ) {
var each _item _not _reactive = ( flags & EACH _ITEM _REACTIVE ) === 0 ;
var item _value = each _item _not _reactive
? item
: ( flags & EACH _IS _STRICT _EQUALS ) !== 0
? source ( item )
: mutable _source ( item ) ;
/** @type {import('#client').EachItem} */
var block = {
var item = {
a : null ,
// dom
d : null ,
// effect
// @ts-expect-error
e : null ,
// index
@ -646,24 +595,30 @@ function create_item(item, key, index, render_fn, flags) {
// key
k : key ,
// item
v : item _value
v : each _item _not _reactive
? value
: ( flags & EACH _IS _STRICT _EQUALS ) !== 0
? source ( value )
: mutable _source ( value )
} ;
var previous _each _item _block = current _each _item _block ;
var previous _each _item = current _each _item ;
try {
current _each _item _block = block ;
current _each _item = item ;
item . e = render _effect ( ( ) => {
var dom = render _fn ( null , item . v , item . i ) ;
block . e = render _effect (
( ) => {
render _fn ( null , block . v , block . i ) ;
} ,
block ,
true
) ;
return ( ) => {
if ( dom !== undefined ) {
remove ( dom ) ;
}
} ;
} , true ) ;
return block ;
return item ;
} finally {
current _each _item _block = previous _each _item _block ;
current _each _item = previous _each _item ;
}
}