-
Notifications
You must be signed in to change notification settings - Fork 43
/
html-template-element.ts
107 lines (90 loc) · 2.89 KB
/
html-template-element.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
import { Node } from "../node.ts";
import { Element } from "../element.ts";
import { Document } from "../document.ts";
import { DocumentFragment } from "../document-fragment.ts";
import { getElementAttributesString, getInnerHtmlFromNodes } from "../utils.ts";
import { fragmentNodesFromString } from "../../deserialize.ts";
import { CTOR_KEY } from "../../constructor-lock.ts";
export class HTMLTemplateElement extends Element {
/**
* This blocks access to the .#contents property when the
* super() constructor is running which invokes (our
* overridden) _setParent() method. Without it, we get
* the following error thrown:
*
* TypeError: Cannot read private member #content from
* an object whose class did not declare it
*
* FIXME: Maybe find a cleaner way to do this
*/
private __contentIsSet = false;
#content: DocumentFragment | null = null;
constructor(
parentNode: Node | null,
attributes: [string, string][],
key: typeof CTOR_KEY,
content: DocumentFragment,
) {
super(
"TEMPLATE",
parentNode,
attributes,
key,
);
this.#content = content;
this.__contentIsSet = true;
}
get content(): DocumentFragment {
return this.#content!;
}
override _setOwnerDocument(document: Document | null) {
super._setOwnerDocument(document);
if (this.__contentIsSet) {
this.content._setOwnerDocument(document);
}
}
override _shallowClone(): HTMLTemplateElement {
const frag = new DocumentFragment();
const attributes = this
.getAttributeNames()
.map((name) => [name, this.getAttribute(name)!] as [string, string]);
return new HTMLTemplateElement(null, attributes, CTOR_KEY, frag);
}
override cloneNode(deep = false): HTMLTemplateElement {
const newNode = super.cloneNode(deep) as HTMLTemplateElement;
if (deep) {
const destContent = newNode.content;
for (const child of this.content.childNodes) {
destContent.appendChild(child.cloneNode(deep));
}
}
return newNode;
}
get innerHTML(): string {
return getInnerHtmlFromNodes(this.content.childNodes, "template");
}
// Replace children in the `.content`
set innerHTML(html: string) {
const content = this.content;
// Remove all children
for (const child of content.childNodes) {
child._setParent(null);
}
const mutator = content._getChildNodesMutator();
mutator.splice(0, content.childNodes.length);
// Parse HTML into new children
if (html.length) {
const parsed = fragmentNodesFromString(html);
mutator.push(...parsed.childNodes[0].childNodes);
for (const child of content.childNodes) {
child._setParent(content);
child._setOwnerDocument(content.ownerDocument);
}
}
}
get outerHTML(): string {
return `<template${
getElementAttributesString(this)
}>${this.innerHTML}</template>`;
}
}