Make if and each blocks reactive on the client
This commit is contained in:
@@ -1611,24 +1611,21 @@ export const REACTIVE_RUNTIME = String.raw`
|
||||
if (
|
||||
attribute.name === "data-text"
|
||||
) {
|
||||
try {
|
||||
var textValue =
|
||||
itemEval(attribute.value);
|
||||
|
||||
node.textContent =
|
||||
textValue == null
|
||||
? ""
|
||||
: String(textValue);
|
||||
} catch (error) {
|
||||
console.error(
|
||||
"[wrnexus] data-for text binding failed for '" +
|
||||
attribute.value +
|
||||
"'",
|
||||
error,
|
||||
);
|
||||
|
||||
node.textContent = "";
|
||||
}
|
||||
(function (textNode, textExpression) {
|
||||
var runText = reactive(function () {
|
||||
try {
|
||||
var textValue = itemEval(textExpression);
|
||||
textNode.textContent = textValue == null ? "" : String(textValue);
|
||||
} catch (error) {
|
||||
console.error(
|
||||
"[wrnexus] data-for text binding failed for '" + textExpression + "'",
|
||||
error,
|
||||
);
|
||||
textNode.textContent = "";
|
||||
}
|
||||
});
|
||||
runText();
|
||||
})(node, attribute.value);
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -1954,6 +1951,129 @@ export const REACTIVE_RUNTIME = String.raw`
|
||||
}
|
||||
}
|
||||
|
||||
// Compiled if/each blocks keep their SSR result in the custom
|
||||
// element and carry an inert, base64-encoded template for later browser
|
||||
// updates. The first reactive pass only subscribes to dependencies, so
|
||||
// hydration does not throw away server DOM. Subsequent state changes
|
||||
// materialize the appropriate branch/rows and hydrate their bindings.
|
||||
function decodeControlDefinition(value) {
|
||||
try {
|
||||
var binary = window.atob(value || "");
|
||||
var bytes = new Uint8Array(binary.length);
|
||||
for (var index = 0; index < binary.length; index++) {
|
||||
bytes[index] = binary.charCodeAt(index);
|
||||
}
|
||||
return JSON.parse(new TextDecoder("utf-8").decode(bytes));
|
||||
} catch (error) {
|
||||
reportDiagnostic("WRN-CONTROL-DECODE", "Failed to decode a client control block.", el, error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function setupControlBlock(block, outerLocals) {
|
||||
if (!block || block.__wrnexusControl || (!outerLocals && !owns(block))) return;
|
||||
block.__wrnexusControl = true;
|
||||
var inherited = outerLocals || decodeLoopLocals(block);
|
||||
var rangeEnd = null;
|
||||
if (block.tagName && block.tagName.toLowerCase() === "template") {
|
||||
var depth = 0;
|
||||
for (var sibling = block.nextSibling; sibling; sibling = sibling.nextSibling) {
|
||||
if (sibling.nodeType !== 1 || sibling.tagName.toLowerCase() !== "template") continue;
|
||||
if (sibling.hasAttribute("data-wrn-if") || sibling.hasAttribute("data-wrn-each")) depth++;
|
||||
if (sibling.hasAttribute("data-wrn-control-end")) {
|
||||
if (depth === 0) { rangeEnd = sibling; break; }
|
||||
depth--;
|
||||
}
|
||||
}
|
||||
if (!rangeEnd) return;
|
||||
}
|
||||
var ifDefinition = block.hasAttribute("data-wrn-if")
|
||||
? decodeControlDefinition(block.getAttribute("data-wrn-if"))
|
||||
: null;
|
||||
var eachDefinition = block.hasAttribute("data-wrn-each")
|
||||
? decodeControlDefinition(block.getAttribute("data-wrn-each"))
|
||||
: null;
|
||||
var firstRun = true;
|
||||
|
||||
function controlRead(name) {
|
||||
return Object.prototype.hasOwnProperty.call(inherited, name) ? inherited[name] : readScope(name);
|
||||
}
|
||||
|
||||
function controlEval(expression, locals) {
|
||||
return evaluateExpression(expression, function (name) {
|
||||
return locals && Object.prototype.hasOwnProperty.call(locals, name)
|
||||
? locals[name]
|
||||
: controlRead(name);
|
||||
});
|
||||
}
|
||||
|
||||
function clearControlContent() {
|
||||
if (!rangeEnd) { block.innerHTML = ""; return; }
|
||||
while (block.nextSibling && block.nextSibling !== rangeEnd) {
|
||||
block.parentNode.removeChild(block.nextSibling);
|
||||
}
|
||||
}
|
||||
|
||||
function appendControlContent(markup, locals) {
|
||||
var template = document.createElement("template");
|
||||
template.innerHTML = markup || "";
|
||||
var fragment = template.content;
|
||||
var elements = Array.prototype.slice.call(fragment.childNodes).filter(function (node) {
|
||||
return node.nodeType === 1;
|
||||
});
|
||||
if (rangeEnd) block.parentNode.insertBefore(fragment, rangeEnd);
|
||||
else block.appendChild(fragment);
|
||||
elements.forEach(function (node) { hydrateItem(node, locals || inherited); });
|
||||
elements.forEach(function (node) {
|
||||
var controls = [];
|
||||
if (node.matches && node.matches("[data-wrn-if],[data-wrn-each]")) controls.push(node);
|
||||
if (node.querySelectorAll) Array.prototype.push.apply(controls, node.querySelectorAll("[data-wrn-if],[data-wrn-each]"));
|
||||
controls.forEach(function (nested) {
|
||||
if (!nested.__wrnexusControl) setupControlBlock(nested, locals || inherited);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
reactive(function () {
|
||||
if (ifDefinition) {
|
||||
var selected = null;
|
||||
for (var branchIndex = 0; branchIndex < ifDefinition.length; branchIndex++) {
|
||||
var branch = ifDefinition[branchIndex];
|
||||
if (branch.cond === null || !!controlEval(branch.cond, inherited)) {
|
||||
selected = branch;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (firstRun) { firstRun = false; return; }
|
||||
clearControlContent();
|
||||
appendControlContent(selected ? selected.body : "", inherited);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!eachDefinition) return;
|
||||
var list = controlEval(eachDefinition.list, inherited);
|
||||
if (!Array.isArray(list)) list = [];
|
||||
if (firstRun) { firstRun = false; return; }
|
||||
clearControlContent();
|
||||
if (list.length === 0) {
|
||||
appendControlContent(eachDefinition.empty || "", inherited);
|
||||
return;
|
||||
}
|
||||
for (var itemIndex = 0; itemIndex < list.length; itemIndex++) {
|
||||
var rowLocals = {};
|
||||
Object.keys(inherited).forEach(function (name) { rowLocals[name] = inherited[name]; });
|
||||
rowLocals[eachDefinition.item] = list[itemIndex];
|
||||
if (eachDefinition.index) rowLocals[eachDefinition.index] = itemIndex;
|
||||
appendControlContent(eachDefinition.body || "", rowLocals);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Array.prototype.slice.call(el.querySelectorAll("[data-wrn-if],[data-wrn-each]")).forEach(function (block) {
|
||||
if (block.parentElement && block.parentElement.closest("[data-wrn-if],[data-wrn-each]")) return;
|
||||
setupControlBlock(block, null);
|
||||
});
|
||||
|
||||
/*
|
||||
* Set up one [data-for] template. Extracted from an inline forEach so it
|
||||
* can recurse: hydrateItem calls it for every loop nested inside a rendered
|
||||
|
||||
Reference in New Issue
Block a user