a post-component library for building user-interfaces on the web.

fix a bug related to wrong ChildPart spans (#40)

+ add failing test

in html`${a}|${b}`,
a's slot's childIndex is 0
b's slot's childIndex is 2

however, where ${a} results in 2+ nodes:
by the time ${b} is created, it's no longer at the same
index in the parent.

by reversing the order in which the parts are created,
the slots are always in the expected position.

authored by tombl.dev and committed by

GitHub e05d2ab6 3ccda0d5

+14 -9
+5 -8
src/html.js
··· 204 204 span._deleteContents() 205 205 span._insertNode(doc) 206 206 207 - let prev 208 207 this._parts = template._parts.map(([dynamicIdx, createPart], elementIdx) => { 209 - const part = createPart(prev, span) 208 + const part = createPart(span) 210 209 part.create(nodeByPart[elementIdx], dynamics[dynamicIdx]) 211 - prev = part 212 210 return /** @type {const} */ ([dynamicIdx, part]) 213 211 }) 214 212 } ··· 256 254 while ((nextPart < compiled._parts.length || DEV) && walker.nextNode()) { 257 255 const node = /** @type {Text | Element | Comment} */ (walker.currentNode) 258 256 if (isText(node)) { 257 + // reverse the order because we'll be supplying ChildPart with its index in the parent node. 258 + // and if we apply the parts forwards, indicies will be wrong if some prior part renders more than one node. 259 + // also reverse it because that's the correct order for splitting. 259 260 const nodes = [...node.data.matchAll(DYNAMIC_GLOBAL)].reverse().map(match => { 260 261 node.splitText(match.index + match[0].length) 261 262 const dyn = new Comment() ··· 263 264 return /** @type {const} */ ([dyn, parseInt(match[1])]) 264 265 }) 265 266 266 - // put them back in order, inverting the effect of the reverse above. 267 - // not relevant for behavior, but it satisfies the warning when parts are used out of order. 268 - nodes.reverse() 269 - 270 267 if (nodes.length) { 271 268 DEV: assert(node.parentNode !== null, 'all text nodes should have a parent node') 272 269 let siblings = [...node.parentNode.childNodes] 273 270 for (const [node, idx] of nodes) { 274 271 const child = siblings.indexOf(node) 275 - patch(node.parentNode, idx, (_prev, span) => new ChildPart(child, span)) 272 + patch(node.parentNode, idx, span => new ChildPart(child, span)) 276 273 } 277 274 } 278 275 } else if (DEV && isComment(node)) {
+1 -1
src/types.ts
··· 33 33 34 34 export interface CompiledTemplate { 35 35 _content: DocumentFragment 36 - _parts: [idx: number, createPart: (prev: Part, span: Span) => Part][] 36 + _parts: [idx: number, createPart: (span: Span) => Part][] 37 37 _rootParts: number[] 38 38 }
+8
test/basic.test.ts
··· 110 110 expect(el.firstChild).toBeInstanceOf(Text) 111 111 expect((el.firstChild as Text).data).toBe('abc') 112 112 }) 113 + 114 + it('shifting ChildPart index', () => { 115 + const { root, el } = setup() 116 + 117 + root.render(html`${html`A<!--x-->`}B${'C'}`) 118 + 119 + expect(el.innerHTML).toBe('A<!--x-->BC') 120 + }) 113 121 }) 114 122 115 123 describe('errors', () => {