@@ -56,25 +56,31 @@ const flash =
5656 : undefined
5757
5858class Span {
59+ /**
60+ * @param {Node } parentNode
61+ * @param {Node | null } start
62+ * @param {Node | null } end
63+ */
5964 constructor ( parentNode , start , end ) {
65+ assert ( start === null || start . parentNode === parentNode )
66+ assert ( end === null || end . parentNode === parentNode )
67+
6068 this . parentNode = parentNode
6169 this . start = start
6270 this . end = end
6371 }
72+
6473 deleteContents ( ) {
6574 // optimization for clearing when we own the entire parent.
66- if ( this . start === 0 && this . end >= this . parentNode . childNodes . length ) {
67- if ( DEV && this . end !== this . parentNode . childNodes . length ) console . warn ( 'end is past the end of the parent' )
75+ if ( this . parentNode . firstChild === this . start && this . end === null ) {
6876 this . parentNode . textContent = ''
69- this . end = 0
77+ this . start = null
7078 return
7179 }
72- while ( this . end > this . start ) {
73- const node = this . parentNode . childNodes [ -- this . end ]
74- if ( flash && node . getBoundingClientRect ) {
80+ for ( const node of this ) {
81+ if ( flash && isElement ( node ) ) {
7582 const box = node . getBoundingClientRect ( )
76- const cloned = node . cloneNode ( true )
77- node . remove ( )
83+ const cloned = /** @type {HTMLElement } */ ( node . cloneNode ( true ) )
7884 Object . assign ( cloned . style , {
7985 position : 'absolute' ,
8086 top : box . top + 'px' ,
@@ -85,35 +91,37 @@ class Span {
8591 } )
8692 document . body . appendChild ( cloned )
8793 Promise . resolve ( flash ( cloned , 255 , 0 , 0 ) ) . then ( ( ) => cloned . remove ( ) )
88- } else {
89- node . remove ( )
9094 }
95+ this . parentNode . removeChild ( node )
9196 }
97+ this . start = this . end = this . start ?. previousSibling ?? null
9298 }
9399 insertNode ( node ) {
94- const length = isDocumentFragment ( node ) ? node . childNodes . length : 1
95- this . parentNode . insertBefore ( node , this . parentNode . childNodes [ this . end ] ?? null )
96- this . end += length
100+ if ( isDocumentFragment ( node ) && node . childNodes . length === 0 ) return
101+ this . start ??= isDocumentFragment ( node ) ? node . firstChild : node
102+ this . parentNode . insertBefore ( node , this . end )
97103 if ( flash ) for ( const node of this ) flash ( node , 0 , 255 , 0 )
98104 }
99105 * [ Symbol . iterator ] ( ) {
100- for ( let i = this . start ; i < this . end ; i ++ )
101- yield ( console . log ( this . parentNode . childNodes [ i ] ) , this . parentNode . childNodes [ i ] )
106+ for ( let node = this . start ; node !== this . end && node !== null ; node = node . nextSibling ) yield node
102107 }
103108 extractContents ( ) {
104109 const fragment = document . createDocumentFragment ( )
105- while ( this . end > this . start ) fragment . prepend ( this . parentNode . childNodes [ -- this . end ] )
110+ for ( const node of this ) fragment . appendChild ( node )
111+ this . start = this . end = null
106112 return fragment
107113 }
108114 get length ( ) {
109- return this . end - this . start
115+ let length = 0
116+ for ( const _ of this ) length ++
117+ return length
110118 }
111119}
112120
113121if ( DEV ) {
114122 Span . prototype . toString = function ( ) {
115123 let result = ''
116- for ( const node of this ) result += node . outerHTML
124+ for ( const node of this ) result += /** @type { Element } */ ( node ) . outerHTML ?? node
117125 return result
118126 }
119127}
@@ -143,12 +151,11 @@ export class Root {
143151 }
144152
145153 static appendInto ( parent ) {
146- return new Root ( new Span ( parent , parent . childNodes . length , parent . childNodes . length ) )
154+ return new Root ( new Span ( parent , parent . lastChild , null ) )
147155 }
148156
149157 static replace ( node ) {
150- const index = [ ...node . parentNode . childNodes ] . indexOf ( node )
151- return new Root ( new Span ( node . parentNode , index , index + 1 ) )
158+ return new Root ( new Span ( node . parentNode , node , node . nextSibling ) )
152159 }
153160
154161 render ( value ) {
@@ -271,7 +278,7 @@ function compileTemplate(statics) {
271278 // by splitting the text starting from the end, we only have to split the original node.
272279 for ( const match of [ ...node . data . matchAll ( DYNAMIC_GLOBAL ) ] . reverse ( ) ) {
273280 node . splitText ( match . index + match [ 0 ] . length )
274- const dyn = new Comment ( )
281+ const dyn = DEV ? new Comment ( `dyn-$ ${ match [ 1 ] } ` ) : new Comment ( )
275282 node . splitText ( match . index ) . replaceWith ( dyn )
276283 nodes . push ( [ dyn , parseInt ( match [ 1 ] ) ] )
277284 }
@@ -399,10 +406,21 @@ class ChildPart {
399406
400407 #span
401408 create ( node , value ) {
402- this . #span =
403- node instanceof Span
404- ? new Span ( node . parentNode , node . start + this . #childIndex, node . start + this . #childIndex + 1 )
405- : new Span ( node , this . #childIndex, this . #childIndex + 1 )
409+ assert ( this . #childIndex !== undefined )
410+
411+ if ( node instanceof Span ) {
412+ let child = node . start
413+ assert ( child , 'expected a start node' )
414+ for ( let i = 0 ; i < this . #childIndex; i ++ ) {
415+ assert ( child . nextSibling !== null )
416+ assert ( child . nextSibling !== node . end )
417+ child = child . nextSibling
418+ }
419+ this . #span = new Span ( node . parentNode , child , child . nextSibling )
420+ } else {
421+ const child = node . childNodes [ this . #childIndex]
422+ this . #span = new Span ( node , child , child . nextSibling )
423+ }
406424
407425 this . #childIndex = undefined // we only need this once.
408426
@@ -490,17 +508,9 @@ class ChildPart {
490508 let first = this . #roots[ i ]
491509 let i1 = i
492510 if ( first ?. _key !== key ) {
493- let i2 = this . #roots. findIndex ( root => root ?. _key === key )
511+ const i2 = this . #roots. findIndex ( root => root ?. _key === key )
494512 if ( i2 !== - 1 ) {
495- let second = this . #roots[ i2 ]
496-
497- if ( second . span . start < first . span . start ) {
498- // first must refer to the lower index.
499- ; [ first , second ] = [ second , first ]
500- ; [ i1 , i2 ] = [ i2 , i1 ]
501- }
502- assert ( first . span . start < second . span . start )
503- assert ( i1 < i2 )
513+ const second = this . #roots[ i2 ]
504514
505515 // swap the contents of the spans
506516 const content1 = second . span . extractContents ( )
@@ -514,19 +524,12 @@ class ChildPart {
514524 // swap the roots
515525 this . #roots[ i1 ] = second
516526 this . #roots[ i2 ] = first
517-
518- const difference = second . span . length - first . span . length
519- for ( let j = i1 + 1 ; j <= i2 ; j ++ ) {
520- this . #roots[ j ] . span . start += difference
521- this . #roots[ j ] . span . end += difference
522- }
523527 }
524528 }
525529 }
526530
527- const root = ( this . #roots[ i ++ ] ??= new Root ( new Span ( this . #span. parentNode , offset , offset ) ) )
531+ const root = ( this . #roots[ i ++ ] ??= new Root ( new Span ( this . #span. parentNodeNode , offset , offset ) ) )
528532 root . render ( item )
529- // console.log(offset, root.span.end)
530533 offset = root . span . end
531534
532535 // TODO: make this a weak relationship, because if key is collected, the comparison will always be false.
@@ -564,8 +567,8 @@ class ChildPart {
564567 if ( this . #value != null && value !== null && ! ( this . #value instanceof Node ) && ! ( value instanceof Node ) ) {
565568 // we previously rendered a string, and we're rendering a string again.
566569 assert ( this . #span. length === 1 )
567- assert ( this . #span. parentNode . childNodes [ this . #span . start ] instanceof Text )
568- this . #span. parentNode . childNodes [ this . #span . start ] . data = value
570+ assert ( this . #span. start instanceof Text )
571+ this . #span. start . data = value
569572 } else {
570573 this . #span. deleteContents ( )
571574 if ( value !== null ) this . #span. insertNode ( value instanceof Node ? value : new Text ( value . toString ( ) ) )
0 commit comments