This commit is contained in:
Tony Garnock-Jones 2023-12-03 23:22:51 +01:00
parent a9ea553ca1
commit b336faff25
1 changed files with 66 additions and 36 deletions

View File

@ -57,9 +57,59 @@ function followPath(topNodes: ChildNode[], path: number[]): Node {
return n;
}
type PlaceholderAction = (variableParts: HtmlFragment[], topNodes: ChildNode[]) => void;
function nodeInserter(n: number, path: number[]): PlaceholderAction {
return (vs, topNodes) => {
const node = followPath(topNodes, path);
function walk(f: HtmlFragment) {
if (Array.isArray(f)) {
f.forEach(walk);
} else {
let newNode: Node;
switch (typeof f) {
case 'string': newNode = document.createTextNode(f); break;
case 'number': newNode = document.createTextNode('' + f); break;
default: newNode = f; break;
}
node.parentNode?.insertBefore(newNode, node);
}
}
walk(vs[n]);
node.parentNode?.removeChild(node);
};
}
function attributesInserter(n: number, path: number[]): PlaceholderAction {
return (vs, topNodes) => {
const node = followPath(topNodes, path);
const e = document.createElement('template');
e.innerHTML = `<x-dummy ${renderFragment(vs[n], true).join('')}></x-dummy>`;
Array.from(e.attributes).forEach(a =>
(node as Element).setAttribute(a.name, a.value));
};
}
function attributeValueInserter(
attrName: string,
constantParts: string[],
placeholders: number[],
path: number[],
): PlaceholderAction {
return (vs, topNodes) => {
const node = followPath(topNodes, path);
const pieces = [constantParts[0]];
placeholders.forEach((n, i) => {
pieces.push(...renderFragment(vs[n], false));
pieces.push(constantParts[i + 1]);
});
(node as Element).setAttribute(attrName, pieces.join(''));
};
}
export class HtmlFragmentBuilder {
template: HTMLTemplateElement = document.createElement('template');
placeholderActions: Array<(variableParts: HtmlFragment[], topNodes: ChildNode[]) => void> = [];
placeholderActions: PlaceholderAction[] = [];
constructor(constantParts: TemplateStringsArray) {
const pieces: string[] = [];
@ -81,9 +131,13 @@ export class HtmlFragmentBuilder {
const n = nextN;
switch (n.nodeType) {
case Node.TEXT_NODE: {
const { constantParts, placeholders } = splitByPlaceholders(n.textContent ?? '');
const { constantParts, placeholders } =
splitByPlaceholders(n.textContent ?? '');
constantParts.forEach((c, i) => {
if (i > 0) n.parentNode?.insertBefore(document.createElement('placeholder'), n);
if (i > 0) {
const placeholder = document.createElement('x-placeholder');
n.parentNode?.insertBefore(placeholder, n);
}
n.parentNode?.insertBefore(document.createTextNode(c), n);
});
nextN = n.nextSibling;
@ -91,22 +145,7 @@ export class HtmlFragmentBuilder {
placeholders.forEach((n, i) => {
const currentPath = path.slice();
currentPath[currentPath.length - 1] = i * 2 + 1;
this.placeholderActions.push((vs, topNodes) => {
const node = followPath(topNodes, currentPath);
function walk(f: HtmlFragment) {
if (Array.isArray(f)) {
f.forEach(walk);
} else {
switch (typeof f) {
case 'string': node.parentNode?.insertBefore(document.createTextNode(f), node); break;
case 'number': node.parentNode?.insertBefore(document.createTextNode('' + f), node); break;
default: node.parentNode?.insertBefore(f, node); break;
}
}
}
walk(vs[n]);
node.parentNode?.removeChild(node);
});
this.placeholderActions.push(nodeInserter(n, currentPath));
});
bump(constantParts.length + placeholders.length);
break;
@ -123,25 +162,16 @@ export class HtmlFragmentBuilder {
e.removeAttributeNode(attr);
i--;
const n = parseInt(nameIsPlaceholder[1], 10);
this.placeholderActions.push((vs, topNodes) => {
const node = followPath(topNodes, currentPath);
const e = document.createElement('template');
e.innerHTML = `<dummy ${renderFragment(vs[n], true).join('')}></dummy>`;
Array.from(e.attributes).forEach(a =>
(node as Element).setAttribute(a.name, a.value));
});
this.placeholderActions.push(attributesInserter(n, currentPath));
} else {
const { constantParts, placeholders } = splitByPlaceholders(attr.value);
const { constantParts, placeholders } =
splitByPlaceholders(attr.value);
if (constantParts.length !== 1) {
this.placeholderActions.push((vs, topNodes) => {
const node = followPath(topNodes, currentPath);
const pieces = [constantParts[0]];
placeholders.forEach((n, i) => {
pieces.push(...renderFragment(vs[n], false));
pieces.push(constantParts[i + 1]);
});
(node as Element).setAttribute(attrName, pieces.join(''));
});
this.placeholderActions.push(attributeValueInserter(
attrName,
constantParts,
placeholders,
currentPath));
}
}
}