···11import type { Displayable, Renderable } from 'dhtml'
22-import { assert, is_html, is_iterable, is_renderable, single_part_template } from '../shared.ts'
22+import { assert, is_html, is_iterable, is_renderable, single_part_template, type ToString } from '../shared.ts'
33import { delete_controller, get_controller, get_key } from './controller.ts'
44import { create_root, create_root_after, type Root } from './root.ts'
55import { create_span, delete_contents, extract_contents, insert_node, type Span } from './span.ts'
···5353 span = create_span(child)
5454 }
55555656- return function update(value) {
5656+ function render_renderable(renderable: Renderable): Displayable {
5757+ const controller = get_controller(renderable)
5858+5959+ controller._invalidate ??= () => {
6060+ assert(current_renderable === renderable, 'could not invalidate an outdated renderable')
6161+ update(renderable)
6262+ }
6363+ controller._parent_node = span._parent
6464+6565+ try {
6666+ return renderable.render()
6767+ } catch (thrown) {
6868+ if (is_html(thrown)) {
6969+ return thrown
7070+ } else {
7171+ throw thrown
7272+ }
7373+ }
7474+ }
7575+7676+ function handle_iterable(values: Iterable<Displayable>) {
7777+ if (!roots) {
7878+ // we previously rendered a single value, so we need to clear it.
7979+ disconnect_root()
8080+ delete_contents(span)
8181+8282+ roots = []
8383+ }
8484+8585+ // create or update a root for every item.
8686+ let i = 0
8787+ let end = span._start
8888+ for (const item of values) {
8989+ const key = get_key(item)
9090+ let root = (roots[i] ??= create_root_after(end))
9191+9292+ if (key !== undefined && root._key !== key) {
9393+ const j = roots.findIndex(r => r._key === key)
9494+ root._key = key
9595+ if (j !== -1) {
9696+ const root1 = root
9797+ const root2 = roots[j]
9898+9999+ // swap the contents of the spans
100100+ const tmp_content = extract_contents(root1._span)
101101+ insert_node(root1._span, extract_contents(root2._span))
102102+ insert_node(root2._span, tmp_content)
103103+104104+ // swap the spans back
105105+ const tmp_span = root1._span
106106+ root1._span = root2._span
107107+ root2._span = tmp_span
108108+109109+ // swap the roots
110110+ roots[j] = root1
111111+ root = roots[i] = root2
112112+ }
113113+ }
114114+115115+ root.render(item)
116116+ end = root._span._end
117117+ i++
118118+ }
119119+120120+ // and now remove excess roots if the iterable has shrunk.
121121+ while (roots.length > i) {
122122+ const root = roots.pop()
123123+ assert(root)
124124+ root.detach()
125125+ delete_contents(root._span)
126126+ }
127127+128128+ span._end = end
129129+ }
130130+131131+ function handle_single_value(value: Node | ToString | null) {
132132+ if (old_value != null && value !== null && !(old_value instanceof Node) && !(value instanceof Node)) {
133133+ // we previously rendered a string, and we're rendering a string again.
134134+ assert(span._start === span._end && span._start instanceof Text)
135135+ span._start.data = '' + value
136136+ } else {
137137+ delete_contents(span)
138138+ if (value !== null) insert_node(span, value instanceof Node ? value : new Text(value satisfies ToString as string))
139139+ }
140140+ }
141141+142142+ function update(value_: unknown) {
143143+ let value = value_ as Displayable
144144+57145 assert(span)
58146 const ends_were_equal = span._parent === parent_span._parent && span._end === parent_span._end
5914760148 if (is_renderable(value)) {
61149 switch_renderable(value)
621506363- const renderable = value
6464- const controller = get_controller(renderable)
6565-6666- controller._invalidate ??= () => {
6767- assert(current_renderable === renderable, 'could not invalidate an outdated renderable')
6868- update(renderable)
6969- }
7070- controller._parent_node = span._parent
7171-7272- try {
7373- value = renderable.render()
7474- } catch (thrown) {
7575- if (is_html(thrown)) {
7676- value = thrown
7777- } else {
7878- throw thrown
7979- }
8080- }
151151+ value = render_renderable(value)
8115282153 // if render returned another renderable, we want to track/cache both renderables individually.
83154 // wrap it in a nested ChildPart so that each can be tracked without ChildPart having to handle multiple renderables.
84155 if (is_renderable(value)) value = single_part_template(value)
8585- } else switch_renderable(null)
156156+ } else {
157157+ switch_renderable(null)
158158+ }
8615987160 // if it's undefined, swap the value for null.
88161 // this means if the initial value is undefined,
···93166 // NOTE: we're explicitly not caching/diffing the value when it's an iterable,
94167 // given it can yield different values but have the same identity. (e.g. arrays)
95168 if (is_iterable(value)) {
9696- if (!roots) {
9797- // we previously rendered a single value, so we need to clear it.
9898- disconnect_root()
9999- delete_contents(span)
100100-101101- roots = []
169169+ handle_iterable(value as Iterable<Displayable>)
170170+ } else {
171171+ if (roots) {
172172+ for (const root of roots) root.detach()
173173+ roots = undefined
102174 }
103175104104- // create or update a root for every item.
105105- let i = 0
106106- let end = span._start
107107- for (const item of value) {
108108- const key = get_key(item)
109109- let root = (roots[i] ??= create_root_after(end))
110110-111111- if (key !== undefined && root._key !== key) {
112112- const j = roots.findIndex(r => r._key === key)
113113- root._key = key
114114- if (j !== -1) {
115115- const root1 = root
116116- const root2 = roots[j]
176176+ // only continue if the value hasn't changed.
177177+ if (!Object.is(old_value, value)) {
178178+ if (is_html(value)) {
179179+ root ??= create_root(span)
180180+ root.render(value) // root.render will detach the previous tree if the template has changed.
181181+ } else {
182182+ // if we previously rendered a tree that might contain renderables,
183183+ // and the template has changed (or we're not even rendering a template anymore),
184184+ // we need to clear the old renderables.
185185+ disconnect_root()
117186118118- // swap the contents of the spans
119119- const tmp_content = extract_contents(root1._span)
120120- insert_node(root1._span, extract_contents(root2._span))
121121- insert_node(root2._span, tmp_content)
122122-123123- // swap the spans back
124124- const tmp_span = root1._span
125125- root1._span = root2._span
126126- root2._span = tmp_span
127127-128128- // swap the roots
129129- roots[j] = root1
130130- root = roots[i] = root2
131131- }
187187+ handle_single_value(value)
132188 }
133189134134- root.render(item as Displayable)
135135- end = root._span._end
136136- i++
137137- }
138138-139139- // and now remove excess roots if the iterable has shrunk.
140140- while (roots.length > i) {
141141- const root = roots.pop()
142142- assert(root)
143143- root.detach()
144144- delete_contents(root._span)
145145- }
146146-147147- span._end = end
148148-149149- if (current_renderable) {
150150- const controller = get_controller(current_renderable)
151151- controller._mount_callbacks?.forEach(callback => controller._unmount_callbacks.add(callback?.()))
152152- delete controller._mount_callbacks
153153- }
154154-155155- if (ends_were_equal) parent_span._end = span._end
156156-157157- return
158158- } else if (roots) {
159159- for (const root of roots) root.detach()
160160- roots = undefined
161161- }
162162-163163- // now early return if the value hasn't changed.
164164- if (Object.is(old_value, value)) return
165165-166166- if (is_html(value)) {
167167- root ??= create_root(span)
168168- root.render(value) // root.render will detach the previous tree if the template has changed.
169169- } else {
170170- // if we previously rendered a tree that might contain renderables,
171171- // and the template has changed (or we're not even rendering a template anymore),
172172- // we need to clear the old renderables.
173173- disconnect_root()
174174-175175- if (old_value != null && value !== null && !(old_value instanceof Node) && !(value instanceof Node)) {
176176- // we previously rendered a string, and we're rendering a string again.
177177- assert(span._start === span._end && span._start instanceof Text)
178178- span._start.data = '' + value
179179- } else {
180180- delete_contents(span)
181181- if (value !== null) insert_node(span, value instanceof Node ? value : new Text('' + value))
190190+ old_value = value
182191 }
183192 }
184184-185185- old_value = value
186193187194 if (current_renderable) {
188195 const controller = get_controller(current_renderable)
···192199193200 if (ends_were_equal) parent_span._end = span._end
194201 }
202202+203203+ return update
195204}
196205197206export function create_property_part(node: Node, name: string): Part {
+1-5
src/index.ts
···11-import { html_tag } from './shared.ts'
22-33-interface ToString {
44- toString(): string
55-}
11+import { html_tag, type ToString } from './shared.ts'
6273export type Displayable = null | undefined | ToString | Node | Renderable | Iterable<Displayable> | HTML
84export interface Renderable {
+4
src/shared.ts
···2525export function single_part_template(part: Displayable): HTML {
2626 return html`${part}`
2727}
2828+2929+export interface ToString {
3030+ toString(): string
3131+}