tangled
alpha
login
or
join now
tombl.dev
/
dhtml
1
fork
atom
a post-component library for building user-interfaces on the web.
1
fork
atom
overview
issues
pulls
pipelines
trim some bytes (#29)
authored by
tombl.dev
and committed by
GitHub
1 year ago
1499b1cc
8400e7c8
+40
-62
2 changed files
expand all
collapse all
unified
split
src
html.js
types.ts
+40
-61
src/html.js
reviewed
···
20
20
/** @return {node is Text} */
21
21
const isText = node => node.nodeType === /** @satisfies {typeof Node.TEXT_NODE} */ (3)
22
22
23
23
-
/** @return {node is Comment} */
24
24
-
const isComment = node => node.nodeType === /** @satisfies {typeof Node.COMMENT_NODE} */ (8)
25
25
-
26
23
/** @return {node is DocumentFragment} */
27
24
const isDocumentFragment = node => node.nodeType === /** @satisfies {typeof Node.DOCUMENT_FRAGMENT_NODE} */ (11)
28
28
-
29
29
-
export const html = (statics, ...dynamics) => new BoundTemplateInstance(statics, dynamics)
30
30
-
31
31
-
const singlePartTemplate = part => html`${part}`
32
25
33
26
/** @return {value is Renderable} */
34
27
const isRenderable = value => typeof value === 'object' && value !== null && 'render' in value
···
36
29
/** @return {value is Iterable<unknown>} */
37
30
const isIterable = value => typeof value === 'object' && value !== null && Symbol.iterator in value
38
31
32
32
+
export const html = (statics, ...dynamics) => new BoundTemplateInstance(statics, dynamics)
33
33
+
34
34
+
const singlePartTemplate = part => html`${part}`
35
35
+
39
36
/** @return {asserts value} */
40
37
const assert = (value, message = 'assertion failed') => {
41
38
if (!DEV) return
···
45
42
/** @implements {SpanInstance} */
46
43
class Span {
47
44
/**
48
48
-
* @param {ParentNode} parentNode
49
49
-
* @param {Node} start the first node in the span
50
50
-
* @param {Node} end the last node in the span
45
45
+
* @param {Node} node the only node in the span
51
46
*/
52
52
-
constructor(parentNode, start, end) {
53
53
-
DEV: {
54
54
-
assert(parentNode != null)
55
55
-
assert(start.parentNode === parentNode)
56
56
-
assert(end.parentNode === parentNode)
57
57
-
}
58
58
-
59
59
-
this.parentNode = parentNode
60
60
-
this._start = start
61
61
-
this._end = end
47
47
+
constructor(node) {
48
48
+
DEV: assert(node.parentNode !== null)
49
49
+
this._parentNode = node.parentNode
50
50
+
this._start = this._end = node
62
51
}
63
52
64
53
_deleteContents() {
65
54
const marker = new Text()
66
66
-
this.parentNode.insertBefore(marker, this._start)
55
55
+
this._parentNode.insertBefore(marker, this._start)
67
56
68
68
-
for (const node of this) this.parentNode.removeChild(node)
57
57
+
for (const node of this) this._parentNode.removeChild(node)
69
58
70
59
this._start = this._end = marker
71
60
}
···
74
63
_insertNode(node) {
75
64
const end = isDocumentFragment(node) ? node.lastChild : node
76
65
if (end === null) return // empty fragment
77
77
-
this.parentNode.insertBefore(node, this._end.nextSibling)
66
66
+
this._parentNode.insertBefore(node, this._end.nextSibling)
78
67
this._end = end
79
68
80
69
if (isText(this._start) && this._start.data === '') {
81
81
-
assert(this._start.nextSibling)
82
70
const marker = this._start
71
71
+
72
72
+
DEV: assert(this._start.nextSibling)
83
73
this._start = this._start.nextSibling
84
84
-
this.parentNode.removeChild(marker)
74
74
+
75
75
+
this._parentNode.removeChild(marker)
85
76
}
86
77
}
87
78
*[Symbol.iterator]() {
88
79
let node = this._start
89
80
for (;;) {
90
90
-
const { nextSibling } = node
81
81
+
const next = node.nextSibling
91
82
yield node
92
83
if (node === this._end) return
93
93
-
assert(nextSibling, 'expected more siblings')
94
94
-
node = nextSibling
84
84
+
assert(next, 'expected more siblings')
85
85
+
node = next
95
86
}
96
87
}
97
88
_extractContents() {
98
89
const marker = new Text()
99
99
-
this.parentNode.insertBefore(marker, this._start)
90
90
+
this._parentNode.insertBefore(marker, this._start)
100
91
101
92
const fragment = document.createDocumentFragment()
102
93
for (const node of this) fragment.appendChild(node)
···
104
95
this._start = this._end = marker
105
96
return fragment
106
97
}
107
107
-
get length() {
108
108
-
let length = 0
109
109
-
for (const _ of this) length++
110
110
-
return length
111
111
-
}
112
98
}
113
99
114
100
if (DEV) {
···
151
137
static appendInto(parent) {
152
138
const marker = new Text()
153
139
parent.appendChild(marker)
154
154
-
return new Root(new Span(parent, marker, marker))
140
140
+
return new Root(new Span(marker))
155
141
}
156
142
157
143
/** @param {Node} node */
158
144
static insertAfter(node) {
159
159
-
assert(node.parentNode, 'expected a parent node')
145
145
+
DEV: assert(node.parentNode, 'expected a parent node')
160
146
const marker = new Text()
161
147
node.parentNode.insertBefore(marker, node.nextSibling)
162
162
-
return new Root(new Span(node.parentNode, marker, marker))
148
148
+
return new Root(new Span(marker))
163
149
}
164
150
165
151
/** @param {Node} node */
166
152
static replace(node) {
167
167
-
assert(node.parentNode, 'expected a parent node')
168
168
-
return new Root(new Span(node.parentNode, node, node))
153
153
+
return new Root(new Span(node))
169
154
}
170
155
171
156
render(value) {
···
409
394
/** @type {Span | undefined} */
410
395
#span
411
396
create(node, value) {
412
412
-
assert(this.#childIndex !== undefined)
413
413
-
414
397
if (node instanceof Span) {
415
398
let child = node._start
416
416
-
assert(child, 'expected a start node')
417
399
for (let i = 0; i < this.#childIndex; i++) {
418
418
-
assert(child.nextSibling !== null, 'expected more siblings')
419
419
-
assert(child.nextSibling !== node._end, 'ran out of siblings before the end')
400
400
+
DEV: {
401
401
+
assert(child.nextSibling !== null, 'expected more siblings')
402
402
+
assert(child.nextSibling !== node._end, 'ran out of siblings before the end')
403
403
+
}
420
404
child = child.nextSibling
421
405
}
422
422
-
this.#span = new Span(node.parentNode, child, child)
406
406
+
this.#span = new Span(child)
423
407
} else {
424
408
const child = node.childNodes[this.#childIndex]
425
425
-
this.#span = new Span(node, child, child)
409
409
+
this.#span = new Span(child)
426
410
}
427
427
-
428
428
-
this.#childIndex = undefined // we only need this once.
429
411
430
412
this.update(value)
431
413
}
···
461
443
462
444
/** @param {Displayable} value */
463
445
update(value) {
464
464
-
assert(this.#span)
446
446
+
DEV: assert(this.#span)
465
447
const endsWereEqual =
466
466
-
this.#span.parentNode === this.#parentSpan.parentNode && this.#span._end === this.#parentSpan._end
448
448
+
this.#span._parentNode === this.#parentSpan.parentNode && this.#span._end === this.#parentSpan._end
467
449
468
450
if (isRenderable(value)) {
469
451
this.#switchRenderable(value)
···
474
456
controllers.set(renderable, {
475
457
_invalidateQueued: null,
476
458
_invalidate: () => {
477
477
-
assert(this.#renderable === renderable, 'could not invalidate an outdated renderable')
459
459
+
DEV: assert(this.#renderable === renderable, 'could not invalidate an outdated renderable')
478
460
this.update(renderable)
479
461
},
480
462
_unmountCallbacks: null, // will be upgraded to a Set if needed.
481
481
-
_parentNode: this.#span.parentNode,
463
463
+
_parentNode: this.#span._parentNode,
482
464
})
483
465
484
466
value = renderable.render()
···
507
489
508
490
// create or update a root for every item.
509
491
let i = 0
510
510
-
let end = this.#span._end
511
492
for (const item of value) {
512
493
// @ts-expect-error -- WeakMap lookups of non-objects always return undefined, which is fine
513
494
const key = keys.get(item) ?? item
514
514
-
let root = (this.#roots[i] ??= Root.insertAfter(end))
495
495
+
let root = (this.#roots[i] ??= Root.insertAfter(this.#span._end))
515
496
516
497
if (key !== undefined && root._key !== key) {
517
498
const j = this.#roots.findIndex(r => r._key === key)
···
531
512
root2._span = tmpSpan
532
513
533
514
// swap the roots
534
534
-
root = this.#roots[i] = root2
535
515
this.#roots[j] = root1
516
516
+
root = this.#roots[i] = root2
536
517
}
537
518
}
538
519
539
520
root.render(item)
540
540
-
end = root._span._end
521
521
+
this.#span._end = root._span._end
541
522
542
523
i++
543
524
}
···
550
531
root._span._deleteContents()
551
532
}
552
533
553
553
-
this.#span._end = end
554
534
if (endsWereEqual) this.#parentSpan._end = this.#span._end
555
535
556
536
return
···
573
553
574
554
if (this.#value != null && value !== null && !(this.#value instanceof Node) && !(value instanceof Node)) {
575
555
// we previously rendered a string, and we're rendering a string again.
576
576
-
assert(this.#span.length === 1, `expected a single node, got ${this.#span.length}`)
577
577
-
assert(this.#span._start instanceof Text)
578
578
-
this.#span._start.data = value.toString()
556
556
+
DEV: assert(this.#span._start === this.#span._end && this.#span._start instanceof Text)
557
557
+
this.#span._start.data = '' + value
579
558
} else {
580
559
this.#span._deleteContents()
581
581
-
if (value !== null) this.#span._insertNode(value instanceof Node ? value : new Text(value.toString()))
560
560
+
if (value !== null) this.#span._insertNode(value instanceof Node ? value : new Text('' + value))
582
561
}
583
562
}
584
563
-1
src/types.ts
reviewed
···
18
18
export type Key = string | number | bigint | boolean | symbol | object | null
19
19
20
20
export declare class Span {
21
21
-
parentNode: ParentNode
22
21
_start: Node | null
23
22
_end: Node | null
24
23
}