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; 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 { export class HtmlFragmentBuilder {
template: HTMLTemplateElement = document.createElement('template'); template: HTMLTemplateElement = document.createElement('template');
placeholderActions: Array<(variableParts: HtmlFragment[], topNodes: ChildNode[]) => void> = []; placeholderActions: PlaceholderAction[] = [];
constructor(constantParts: TemplateStringsArray) { constructor(constantParts: TemplateStringsArray) {
const pieces: string[] = []; const pieces: string[] = [];
@ -81,9 +131,13 @@ export class HtmlFragmentBuilder {
const n = nextN; const n = nextN;
switch (n.nodeType) { switch (n.nodeType) {
case Node.TEXT_NODE: { case Node.TEXT_NODE: {
const { constantParts, placeholders } = splitByPlaceholders(n.textContent ?? ''); const { constantParts, placeholders } =
splitByPlaceholders(n.textContent ?? '');
constantParts.forEach((c, i) => { 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); n.parentNode?.insertBefore(document.createTextNode(c), n);
}); });
nextN = n.nextSibling; nextN = n.nextSibling;
@ -91,22 +145,7 @@ export class HtmlFragmentBuilder {
placeholders.forEach((n, i) => { placeholders.forEach((n, i) => {
const currentPath = path.slice(); const currentPath = path.slice();
currentPath[currentPath.length - 1] = i * 2 + 1; currentPath[currentPath.length - 1] = i * 2 + 1;
this.placeholderActions.push((vs, topNodes) => { this.placeholderActions.push(nodeInserter(n, currentPath));
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);
});
}); });
bump(constantParts.length + placeholders.length); bump(constantParts.length + placeholders.length);
break; break;
@ -123,25 +162,16 @@ export class HtmlFragmentBuilder {
e.removeAttributeNode(attr); e.removeAttributeNode(attr);
i--; i--;
const n = parseInt(nameIsPlaceholder[1], 10); const n = parseInt(nameIsPlaceholder[1], 10);
this.placeholderActions.push((vs, topNodes) => { this.placeholderActions.push(attributesInserter(n, currentPath));
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));
});
} else { } else {
const { constantParts, placeholders } = splitByPlaceholders(attr.value); const { constantParts, placeholders } =
splitByPlaceholders(attr.value);
if (constantParts.length !== 1) { if (constantParts.length !== 1) {
this.placeholderActions.push((vs, topNodes) => { this.placeholderActions.push(attributeValueInserter(
const node = followPath(topNodes, currentPath); attrName,
const pieces = [constantParts[0]]; constantParts,
placeholders.forEach((n, i) => { placeholders,
pieces.push(...renderFragment(vs[n], false)); currentPath));
pieces.push(constantParts[i + 1]);
});
(node as Element).setAttribute(attrName, pieces.join(''));
});
} }
} }
} }