···55 const idMap: IdMap = new Map();
6677 if (isParentNode(node) && isParentNode(guide)) {
88- populateIdMapForNode(node, idMap);
99- populateIdMapForNode(guide, idMap);
88+ populateIdSets(node, idMap);
99+ populateIdSets(guide, idMap);
1010 }
11111212 morphNodes(node, guide, idMap);
1313}
14141515-// For each node with an ID, push that ID into the IDSet on the IDMap, for each of its parent elements.
1616-function populateIdMapForNode(node: ParentNode, idMap: IdMap): void {
1515+// For each node with an ID, push that ID into the IdSet on the IdMap, for each of its parent elements.
1616+function populateIdSets(node: ParentNode, idMap: IdMap): void {
1717 const elementsWithIds: NodeListOf<Element> = node.querySelectorAll("[id]");
18181919 for (const elementWithId of elementsWithIds) {
2020 const id = elementWithId.id;
2121+2222+ // Ignore empty IDs
2123 if (id === "") continue;
2424+2225 let current: Element | null = elementWithId;
23262427 while (current) {
···3538 // TODO: We should extract this into a separate function.
3639 if (parent && insertBefore && insertBefore !== node) parent.insertBefore(guide, insertBefore);
37403838- if (isText(node) && isText(guide)) {
3939- if (node.textContent !== guide.textContent) node.textContent = guide.textContent;
4040- } else if (isElement(node) && isElement(guide) && node.tagName === guide.tagName) {
4141- if (node.hasAttributes() || guide.hasAttributes()) morphAttributes(node, guide);
4242- if (node.hasChildNodes() || guide.hasChildNodes()) morphChildNodes(node, guide, idMap);
4343- } else node.replaceWith(guide.cloneNode(true));
4141+ if (isElement(node) && isElement(guide) && node.tagName === guide.tagName) {
4242+ // We need to check if the element is an input, option, or textarea here, because they have
4343+ // special attributes not covered by the isEqualNode check.
4444+ if (!isInput(node) && !isOption(node) && !isTextArea(node) && node.isEqualNode(guide)) return;
4545+ else {
4646+ if (node.hasAttributes() || guide.hasAttributes()) morphAttributes(node, guide);
4747+ if (node.hasChildNodes() || guide.hasChildNodes()) morphChildNodes(node, guide, idMap);
4848+ }
4949+ } else {
5050+ if (node.isEqualNode(guide)) return;
5151+ else if (isText(node) && isText(guide)) {
5252+ if (node.textContent !== guide.textContent) node.textContent = guide.textContent;
5353+ } else if (isComment(node) && isComment(guide)) {
5454+ if (node.nodeValue !== guide.nodeValue) node.nodeValue = guide.nodeValue;
5555+ } else node.replaceWith(guide.cloneNode(true));
5656+ }
4457}
45584659function morphAttributes(elem: Element, guide: Element): void {
···5063 // Copy attributes from the guide to the element, if they don’t already match.
5164 for (const { name, value } of guide.attributes) elem.getAttribute(name) === value || elem.setAttribute(name, value);
52656666+ elem.nodeValue;
6767+5368 // For certain types of elements, we need to do some extra work to ensure the element’s state matches the guide’s state.
5454- if (isInput(elem) && isInput(guide) && elem.value !== guide.value) elem.value = guide.value;
5555- else if (isOption(elem) && isOption(guide) && elem.selected !== guide.selected) elem.selected = guide.selected;
5656- else if (isTextArea(elem) && isTextArea(guide) && elem.value !== guide.value) elem.value = guide.value;
6969+ if (isInput(elem) && isInput(guide) && elem.type !== "file") {
7070+ if (elem.value !== guide.value) elem.value = guide.value;
7171+ if (elem.checked !== guide.checked) elem.checked = guide.checked;
7272+ if (elem.disabled !== guide.disabled) elem.disabled = guide.disabled;
7373+ } else if (isOption(elem) && isOption(guide) && elem.selected !== guide.selected) elem.selected = guide.selected;
7474+ else if (isTextArea(elem) && isTextArea(guide)) {
7575+ if (elem.value !== guide.value) elem.value = guide.value;
7676+7777+ const text = elem.firstChild as Text | null;
7878+ if (text && text.textContent !== guide.value) text.textContent = guide.value;
7979+ }
5780}
58815982// Iterates over the child nodes of the guide element, morphing the main element’s child nodes to match.
···117140// the necessary checks at runtime.
118141119142function isText(node: Node): node is Text {
120120- return node.nodeType === Node.TEXT_NODE;
143143+ return node.nodeType === 3;
144144+}
145145+146146+function isComment(node: Node): node is Comment {
147147+ return node.nodeType === 8;
121148}
122149123150function isElement(node: Node): node is Element {
124124- return node.nodeType === Node.ELEMENT_NODE;
151151+ return node.nodeType === 1;
125152}
126153127154function isInput(element: Element): element is HTMLInputElement {
···137164}
138165139166function isParentNode(node: Node): node is ParentNode {
140140- return (
141141- node.nodeType === Node.ELEMENT_NODE ||
142142- node.nodeType === Node.DOCUMENT_NODE ||
143143- node.nodeType === Node.DOCUMENT_FRAGMENT_NODE
144144- );
167167+ return node.nodeType === 1 || node.nodeType === 9 || node.nodeType === 11;
145168}