33 * @typedef {import('./types.ts').Displayable } Displayable
44 * @typedef {import('./types.ts').Renderable } Renderable
55 * @typedef {import('./types.ts').CompiledTemplate } CompiledTemplate
6- * @_typedef {import('./types.ts').Key} Key
6+ * @typedef {import('./types.ts').Key } Key
77 * @typedef {import('./types.ts').CustomPartConstructor } CustomPartConstructor
88 * @typedef {import('./types.ts').CustomPartInstance } CustomPartInstance
99 */
@@ -136,7 +136,7 @@ class BoundTemplateInstance {
136136}
137137
138138export class Root {
139- // / ** @type {Key | undefined } */ _key
139+ /** @type {Key | undefined } */ _key
140140
141141 constructor ( span ) {
142142 this . span = span
@@ -381,6 +381,13 @@ export function onUnmount(renderable, callback) {
381381 }
382382}
383383
384+ const keys = new WeakMap ( )
385+ export function keyed ( renderable , key ) {
386+ if ( keys . has ( renderable ) ) throw new Error ( 'renderable already has a key' )
387+ keys . set ( renderable , key )
388+ return renderable
389+ }
390+
384391/** @implements {Part} */
385392class ChildPart {
386393 #childIndex
@@ -476,82 +483,60 @@ class ChildPart {
476483 let i = 0
477484 let offset = this . #span. end
478485 for ( const item of value ) {
479- const root = ( this . #roots[ i ] ??= new Root ( new Span ( this . #span. parentNode , offset , offset ) ) )
486+ // @ts -expect-error -- WeakMap lookups of non-objects always return undefined, which is fine
487+ const key = keys . get ( item ) ?? item
488+
489+ if ( key !== undefined ) {
490+ let first = this . #roots[ i ]
491+ let i1 = i
492+ if ( first ?. _key !== key ) {
493+ let i2 = this . #roots. findIndex ( root => root ?. _key === key )
494+ 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 )
504+
505+ // swap the contents of the spans
506+ const content1 = second . span . extractContents ( )
507+ const content2 = first . span . extractContents ( )
508+ second . span . insertNode ( content2 )
509+ first . span . insertNode ( content1 )
510+
511+ // swap the spans back
512+ ; [ first . span , second . span ] = [ second . span , first . span ]
513+
514+ // swap the roots
515+ this . #roots[ i1 ] = second
516+ 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+ }
523+ }
524+ }
525+ }
526+
527+ const root = ( this . #roots[ i ++ ] ??= new Root ( new Span ( this . #span. parentNode , offset , offset ) ) )
480528 root . render ( item )
529+ // console.log(offset, root.span.end)
481530 offset = root . span . end
482- i ++
483-
484- // @_ts -expect-error -- WeakMap lookups of non-objects always return undefined, which is fine
485- // const key = keys.get(item) ?? item
486-
487- // if (key !== undefined) {
488- // let first = this.#roots[i]
489- // let i1 = i
490- // if (first?._key !== key) {
491- // let i2 = this.#roots.findIndex(root => root?._key === key)
492- // if (i2 !== -1) {
493- // let second = this.#roots[i2]
494-
495- // if (second.span.start < first.span.start) {
496- // // first must refer to the lower index.
497- // ;[first, second] = [second, first]
498- // ;[i1, i2] = [i2, i1]
499- // }
500-
501- // // swap the contents of the spans
502- // const content1 = second.span.extractContents()
503- // const content2 = first.span.extractContents()
504- // second.span.insertNode(content2)
505- // first.span.insertNode(content1)
506-
507- // // swap the spans back
508- // ;[first.span, second.span] = [second.span, first.span]
509-
510- // // swap the roots
511- // this.#roots[i1] = second
512- // this.#roots[i2] = first
513-
514- // const difference = second.span.length - first.span.length
515- // for (let j = i1 + 1; j <= i2; j++) {
516- // this.#roots[j].span.start += difference
517- // this.#roots[j].span.end += difference
518- // }
519- // }
520- // }
521- // }
522-
523- // const root = (this.#roots[i++] ??= new Root(new Span(this.#span.parentNode, offset, offset)))
524- // root.render(item)
525- // console.log(offset, root.span.end)
526- // offset = root.span.end
527-
528- // // TODO: make this a weak relationship, because if key is collected, the comparison will always be false.
529- // if (key !== undefined) root._key = key
530- // }
531-
532- // // and now remove excess roots if the iterable has shrunk.
533- // console.log([...this.#roots])
534- // const extra = this.#roots.splice(i)
535- // this.#roots.length = i
536- // // extra.sort((a, b) => b.span.start - a.span.start)
537- // extra.reverse()
538- // for (const root of extra) {
539- // console.log(
540- // 'detach',
541- // [...root.span.parentNode.childNodes],
542- // root.span.start,
543- // root.span.end,
544- // root._instance?.template._content.textContent,
545- // [...root.span],
546- // )
547- // root.detach()
548- // root.span.deleteContents()
549- // console.log('after detach', [...root.span.parentNode.childNodes], root.span.start, root.span.end)
531+
532+ // TODO: make this a weak relationship, because if key is collected, the comparison will always be false.
533+ if ( key !== undefined ) root . _key = key
550534 }
551535
552536 // and now remove excess roots if the iterable has shrunk.
553537 while ( this . #roots. length > i ) {
554- const root = /** @type {Root } */ ( this . #roots. pop ( ) )
538+ const root = this . #roots. pop ( )
539+ assert ( root )
555540 root . detach ( )
556541 root . span . deleteContents ( )
557542 }
@@ -578,6 +563,8 @@ class ChildPart {
578563
579564 if ( this . #value != null && value !== null && ! ( this . #value instanceof Node ) && ! ( value instanceof Node ) ) {
580565 // we previously rendered a string, and we're rendering a string again.
566+ assert ( this . #span. length === 1 )
567+ assert ( this . #span. parentNode . childNodes [ this . #span. start ] instanceof Text )
581568 this . #span. parentNode . childNodes [ this . #span. start ] . data = value
582569 } else {
583570 this . #span. deleteContents ( )
0 commit comments