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

use child part for list items, not root

tombl.dev fb40985d 6408419a

verified
+52 -60
+52 -60
src/client/parts.ts
··· 15 15 export type Part = (value: unknown) => void 16 16 17 17 export function create_child_part(parent_node: Node | Span, child_index: number): Part { 18 + let child: ChildNode | null 19 + 20 + if (parent_node instanceof Node) { 21 + child = parent_node.childNodes[child_index] 22 + assert(child) 23 + } else { 24 + child = parent_node._start.nextSibling 25 + assert(child) 26 + for (let i = 0; i < child_index; i++) { 27 + child = child.nextSibling 28 + assert(child !== null, 'expected more siblings') 29 + assert(child !== parent_node._end, 'ran out of siblings before the end') 30 + } 31 + } 32 + 33 + return create_child_part_inner(() => create_span(child)) 34 + } 35 + 36 + function create_child_part_inner(get_span: () => Span): Part { 18 37 let span: Span | undefined 19 38 20 39 // for when we're rendering a renderable: ··· 25 44 let root: Root | undefined 26 45 27 46 // for when we're rendering multiple values: 28 - let spans: Span[] | undefined 29 - let roots: Root[] | undefined 30 - let keys: Key[] | undefined 47 + let entries: Array<{ _span: Span; _part: Part; _key: Key }> | undefined 31 48 32 49 // for when we're rendering a string/single dom node: 33 50 // undefined means no previous value, because a user-specified undefined is remapped to null ··· 55 72 root = undefined 56 73 } 57 74 58 - let child: ChildNode | null 59 - if (parent_node instanceof Node) { 60 - child = parent_node.childNodes[child_index] 61 - assert(child) 62 - } else { 63 - child = parent_node._start.nextSibling 64 - assert(child) 65 - for (let i = 0; i < child_index; i++) { 66 - child = child.nextSibling 67 - assert(child !== null, 'expected more siblings') 68 - assert(child !== parent_node._end, 'ran out of siblings before the end') 69 - } 70 - } 71 - 72 75 return function update(value) { 73 - span ??= create_span(child) 76 + span ??= get_span() 74 77 75 78 if (is_renderable(value)) { 76 79 if (!needs_revalidate && value === current_renderable) return ··· 114 117 // NOTE: we're explicitly not caching/diffing the value when it's an iterable, 115 118 // given it can yield different values but have the same identity. (e.g. arrays) 116 119 if (is_iterable(value)) { 117 - if (!roots) { 120 + if (!entries) { 118 121 // we previously rendered a single value, so we need to clear it. 119 122 disconnect_root() 120 123 delete_contents(span) 121 - 122 - spans = [] 123 - roots = [] 124 - keys = [] 124 + entries = [] 125 125 } 126 - assert(spans) 127 - assert(keys) 128 126 129 127 // create or update a root for every item. 130 128 let i = 0 131 129 let end = span._start 132 130 for (const item of value) { 133 - const key = get_key(item) 134 - let span = (spans[i] ??= create_span_after(end)) 135 - let root = (roots[i] ??= create_root(span)) 131 + const key = get_key(item) as Key 132 + if (entries.length <= i) { 133 + const span = create_span_after(end) 134 + entries[i] = { _span: span, _part: create_child_part_inner(() => span), _key: key } 135 + } 136 136 137 - if (key !== undefined && keys[i] !== key) { 138 - for (let j = i; j < roots.length; j++) { 139 - const root1 = root 140 - const root2 = roots[j] 141 - const span1 = spans[i] 142 - const span2 = spans[j] 137 + if (key !== undefined && entries[i]._key !== key) { 138 + for (let j = i + 1; j < entries.length; j++) { 139 + const entry1 = entries[i] 140 + const entry2 = entries[j] 143 141 144 - if (keys[j] === key) { 142 + if (entry2._key === key) { 145 143 // swap the contents of the spans 146 - const tmp_content = extract_contents(span1) 147 - insert_node(span1, extract_contents(span2)) 148 - insert_node(span2, tmp_content) 144 + const tmp_content = extract_contents(entry1._span) 145 + insert_node(entry1._span, extract_contents(entry2._span)) 146 + insert_node(entry2._span, tmp_content) 149 147 150 148 // swap the spans back 151 - const tmp_span = { ...span1 } 152 - Object.assign(span1, span2) 153 - Object.assign(span2, tmp_span) 149 + const tmp_span = { ...entry1._span } 150 + Object.assign(entry1._span, entry2._span) 151 + Object.assign(entry2._span, tmp_span) 154 152 155 153 // swap the roots 156 - spans[j] = span1 157 - span = spans[i] = span2 158 - roots[j] = root1 159 - root = roots[i] = root2 160 - keys[j] = keys[i] 161 - keys[i] = key 154 + entries[j] = entry1 155 + entries[i] = entry2 162 156 163 157 break 164 158 } 165 159 } 166 160 167 - keys[i] = key 161 + entries[i]._key = key 168 162 } 169 163 170 - root.render(item as Displayable) 171 - end = span._end 164 + entries[i]._part(item as Displayable) 165 + end = entries[i]._span._end 172 166 i++ 173 167 } 174 168 175 - // and now remove excess roots if the iterable has shrunk. 176 - while (roots.length > i) { 177 - const root = roots.pop() 178 - assert(root) 179 - root.render(null) 169 + // and now remove excess parts if the iterable has shrunk. 170 + while (entries.length > i) { 171 + const entry = entries.pop() 172 + assert(entry) 173 + entry._part(null) 180 174 } 181 175 182 176 return 183 - } else if (roots) { 184 - for (const root of roots) root.render(null) 185 - spans = undefined 186 - roots = undefined 187 - keys = undefined 177 + } else if (entries) { 178 + for (const entry of entries) entry._part(null) 179 + entries = undefined 188 180 } 189 181 190 182 // now early return if the value hasn't changed.