Files
WRNexusJS/packages/csr/src/reactive-runtime.ts
T
2026-07-24 12:46:44 +05:30

3519 lines
92 KiB
TypeScript

/**
* Browser reactive runtime (Point 2: reactive directives).
*
* Served verbatim at `/__wrnexus/reactive.js` for any page that contains a
* `data-scope`. It is plain browser JS (no build step) and self-contained: it
* inlines a tiny `signal()` so it has no imports to resolve.
*
* Supported directives (this is exactly what the `.wrn` compiler emits):
* data-scope="count: 0, name: 'x'" declare reactive state on a subtree
* data-on-<event>="count++" run a statement in scope on an event
* data-text="expr" element textContent follows an expression
* data-wrn-loop-locals="base64-json" preserves SSR {#each} item/index values
* data-wrnexus-csr="id" target for generated CSR fetch bindings
* {{expr}} or {expr} interpolation inside text nodes
*
* Expressions are evaluated by a tiny parser instead of `eval`/`new Function`,
* so production can use a strong CSP without `unsafe-eval`.
*/
export const REACTIVE_RUNTIME = String.raw`
(function () {
var behaviorInstances = new WeakMap();
var mountedBehaviorRoots = new Set();
var pendingUpdateHooks = new Map();
var updateHooksScheduled = false;
var behaviorObserver;
function reportDiagnostic(code, message, element, detail) {
var payload = {
code: code,
message: message,
hydrationId: element && element.getAttribute ? element.getAttribute("data-wrn-hydration") : null,
detail: detail || null,
};
console.error("[wrnexus:" + code + "] " + message, detail || "");
try {
window.dispatchEvent(new CustomEvent("wrnexus:diagnostic", { detail: payload }));
} catch (_) {
// CustomEvent can be unavailable in minimal DOM test environments.
}
}
function scheduleUpdateHook(element, callback) {
pendingUpdateHooks.set(element, callback);
if (updateHooksScheduled) return;
updateHooksScheduled = true;
queueMicrotask(function () {
updateHooksScheduled = false;
var pending = Array.from(pendingUpdateHooks.entries());
pendingUpdateHooks.clear();
pending.forEach(function (entry) {
var root = entry[0];
var hook = entry[1];
if (!root.isConnected) return;
try { hook(); } catch (error) {
console.error("[wrnexus] component update hook failed", error);
}
});
});
}
function decodeScopePayload(value) {
var binary = window.atob(value);
var bytes =
new Uint8Array(binary.length);
for (
var index = 0;
index < binary.length;
index++
) {
bytes[index] =
binary.charCodeAt(index);
}
var decoded =
new TextDecoder(
"utf-8",
).decode(bytes);
var parsed =
JSON.parse(decoded);
return parsed &&
typeof parsed === "object"
? parsed
: {};
}
function decodeBehaviorPayload(value) {
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 new TextDecoder("utf-8").decode(bytes);
}
function parseBehavior(element) {
var raw = element.getAttribute(
"data-wrn-behavior",
);
if (!raw) {
return null;
}
try {
var decoded =
decodeBehaviorPayload(raw);
var parsed = JSON.parse(decoded);
if (
!parsed ||
typeof parsed !== "object"
) {
return null;
}
return parsed;
} catch (error) {
console.error(
"[wrnexus] failed to parse component behavior",
error,
);
return null;
}
}
function extractBehaviorFunctions(source) {
var functions = [];
var re = /(?:async\s+)?function\s+([A-Za-z_$][\w$]*)\s*\(([^)]*)\)\s*\{/g;
var match;
while ((match = re.exec(source || ""))) {
var start = re.lastIndex;
var depth = 1;
var quote = "";
var i = start;
for (; i < source.length; i++) {
var ch = source[i];
if (quote) {
if (ch === "\\") i++;
else if (ch === quote) quote = "";
continue;
}
if (ch === '"' || ch === "'" || ch === "\`") {
quote = ch;
continue;
}
if (ch === "{") depth++;
else if (ch === "}" && --depth === 0) break;
}
if (depth !== 0) break;
functions.push({
name: match[1],
args: match[2].split(",").map(function (arg) { return arg.trim(); }).filter(Boolean),
body: source.slice(start, i),
});
re.lastIndex = i + 1;
}
return functions;
}
function splitStatements(input) {
var parts = [];
var start = 0;
var depth = 0;
var quote = "";
for (var i = 0; i < input.length; i++) {
var ch = input[i];
if (quote) {
if (ch === "\\") i++;
else if (ch === quote) quote = "";
continue;
}
if (ch === '"' || ch === "'" || ch === "\`") {
quote = ch;
continue;
}
if (ch === "(" || ch === "[" || ch === "{") depth++;
else if (ch === ")" || ch === "]" || ch === "}") depth--;
else if (
(
ch === ";" ||
ch === "\n" ||
ch === "\r"
) &&
depth === 0
) {
var nextIndex = i + 1;
while (
nextIndex < input.length &&
/\s/.test(input[nextIndex])
) {
nextIndex++;
}
if (
input.slice(
nextIndex,
nextIndex + 4,
) === "else"
) {
continue;
}
var part = input
.slice(start, i)
.trim();
if (part) {
parts.push(part);
}
start = i + 1;
}
}
var tail = input.slice(start).trim();
if (tail) parts.push(tail);
return parts;
}
function signal(initial) {
var value = initial;
var subscribers = new Set();
var notifying = false;
return {
get: function () {
return value;
},
set: function (nextValue) {
if (
Object.is(
nextValue,
value,
)
) {
return;
}
var previous = value;
value = nextValue;
/*
* Prevent nested synchronous notification of the
* same signal.
*/
if (notifying) {
return;
}
notifying = true;
try {
/*
* Always iterate a snapshot. A renderer can read
* this signal and subscribe again while it runs.
*/
var snapshot =
Array.from(subscribers);
for (
var index = 0;
index < snapshot.length;
index++
) {
var subscriber =
snapshot[index];
if (
subscribers.has(
subscriber,
)
) {
subscriber(
value,
previous,
);
}
}
} finally {
notifying = false;
}
},
subscribe: function (
subscriber,
) {
subscribers.add(
subscriber,
);
return function () {
subscribers.delete(
subscriber,
);
};
},
};
}
function applyReactiveAttribute(node, name, value) {
var lowerName = String(name || "").toLowerCase();
if (
lowerName === "value" &&
(
node instanceof HTMLInputElement ||
node instanceof HTMLTextAreaElement ||
node instanceof HTMLSelectElement
)
) {
var nextValue =
value == null ? "" : String(value);
if (node.value !== nextValue) {
node.value = nextValue;
}
if (value == null) {
node.removeAttribute("value");
} else {
node.setAttribute(
"value",
nextValue,
);
}
return;
}
if (
lowerName === "checked" &&
node instanceof HTMLInputElement
) {
var checked = !!value;
if (node.checked !== checked) {
node.checked = checked;
}
if (checked) {
node.setAttribute(
"checked",
"",
);
} else {
node.removeAttribute(
"checked",
);
}
return;
}
if (
lowerName === "selected" &&
node instanceof HTMLOptionElement
) {
var selected = !!value;
node.selected = selected;
if (selected) {
node.setAttribute(
"selected",
"",
);
} else {
node.removeAttribute(
"selected",
);
}
return;
}
if (
lowerName === "disabled" ||
lowerName === "required" ||
lowerName === "readonly" ||
lowerName === "multiple"
) {
var enabled = !!value;
if (lowerName in node) {
try {
node[lowerName] = enabled;
} catch (_) {
// Fall through to attribute handling.
}
}
if (enabled) {
node.setAttribute(
name,
"",
);
} else {
node.removeAttribute(name);
}
return;
}
if (value === false || value == null) {
node.removeAttribute(name);
} else {
node.setAttribute(
name,
value === true ? "" : String(value),
);
}
}
function setupScope(el) {
if (el.__wrnexusScope) return;
el.__wrnexusScope = true;
el.__wrnexusHydrated = true;
var encodedScope =
el.getAttribute(
"data-wrn-scope",
);
var initial;
if (encodedScope) {
try {
initial =
decodeScopePayload(
encodedScope,
);
} catch (error) {
reportDiagnostic(
"WRN-HYDRATE-SCOPE",
"Failed to decode the server-rendered scope payload.",
el,
error,
);
initial = {};
}
} else {
var declaration =
el.getAttribute(
"data-scope",
) || "";
initial =
parseScopeDecl(
declaration,
);
}
var signals = {};
Object.keys(initial).forEach(function (k) { signals[k] = signal(initial[k]); });
var behavior = parseBehavior(el);
var computedDefinitions = {};
var computing = new Set();
if (behavior && Array.isArray(behavior.computed)) {
behavior.computed.forEach(function (entry) {
if (entry && typeof entry.name === "string" && typeof entry.expr === "string") {
computedDefinitions[entry.name] = entry.expr;
}
});
}
var renderers = [];
// Dependency-tracked rendering: while a renderer runs, every signal it reads
// subscribes THAT renderer (not a blanket "re-render everything"). A signal
// change then re-runs only the renderers that actually read it. The Set in
// signal.subscribe dedupes, so re-subscribing each run is cheap and bounded.
var currentRenderer = null;
var pendingRenderers = new Set();
var batchDepth = 0;
var flushingRenderers = false;
function flushRenderers() {
if (flushingRenderers || batchDepth > 0) return;
flushingRenderers = true;
try {
while (pendingRenderers.size > 0) {
var queue = Array.from(pendingRenderers);
pendingRenderers.clear();
queue.forEach(function (run) { run(); });
}
} finally {
flushingRenderers = false;
}
}
function scheduleRenderer(renderer) {
pendingRenderers.add(renderer);
flushRenderers();
}
function batchUpdates(callback) {
batchDepth++;
try {
return callback();
} finally {
batchDepth--;
flushRenderers();
}
}
function reactive(fn) {
var running = false;
function run() {
if (running) {
return;
}
running = true;
var previousRenderer =
currentRenderer;
currentRenderer = schedule;
try {
fn();
} finally {
currentRenderer =
previousRenderer;
running = false;
}
}
function schedule() {
scheduleRenderer(run);
}
renderers.push(run);
return run;
}
function renderAll() { renderers.forEach(function (f) { f(); }); }
function decodeLoopLocals(node) {
if (!node) {
return {};
}
var element =
node.nodeType === 1
? node
: node.parentElement;
var owner =
element &&
element.closest &&
element.closest(
"[data-wrn-loop-locals]",
);
if (!owner) {
return {};
}
var encoded =
owner.getAttribute(
"data-wrn-loop-locals",
);
if (!encoded) {
return {};
}
try {
var binary =
window.atob(encoded);
var bytes =
new Uint8Array(binary.length);
for (
var index = 0;
index < binary.length;
index++
) {
bytes[index] =
binary.charCodeAt(index);
}
var decoded =
new TextDecoder("utf-8").decode(
bytes,
);
var parsed =
JSON.parse(decoded);
return parsed &&
typeof parsed === "object"
? parsed
: {};
} catch (error) {
console.error(
"[wrnexus] failed to decode loop locals",
error,
);
return {};
}
}
var behaviorFunctions = {};
var componentEventTarget = el.querySelector("[data-wrn-events]") || el;
var declaredEvents = new Set(
String(componentEventTarget.getAttribute("data-wrn-events") || "")
.split(",")
.map(function (name) { return name.trim(); })
.filter(Boolean),
);
var stateWatchers = {};
var anyStateListeners = new Set();
var cleanupCallbacks = [];
var disposed = false;
function readGlobal(name) {
if (declaredEvents.has(name)) {
return function (detail) {
return dispatchComponentEvent(componentEventTarget, name, detail);
};
}
if (name === "$emit") {
return function (eventName, detail) {
return dispatchComponentEvent(componentEventTarget, eventName, detail);
};
}
if (name === "window") return window;
if (name === "document") return document;
if (name === "console") return console;
if (name === "Array") return Array;
if (name === "Number") return Number;
if (name === "Math") return Math;
if (name === "JSON") return JSON;
if (name === "Date") return Date;
if (name === "URL") return URL;
if (name === "location") return window.location;
if (name === "history") return window.history;
if (name === "navigator") return window.navigator;
if (name === "setTimeout") return window.setTimeout.bind(window);
if (name === "clearTimeout") return window.clearTimeout.bind(window);
if (name === "requestAnimationFrame") return window.requestAnimationFrame.bind(window);
if (name === "cancelAnimationFrame") return window.cancelAnimationFrame.bind(window);
return undefined;
}
function readScope(name) {
if (Object.prototype.hasOwnProperty.call(computedDefinitions, name)) {
if (computing.has(name)) {
reportDiagnostic("WRN-COMPUTED-CYCLE", "Computed value '" + name + "' has a dependency cycle.", el);
return undefined;
}
computing.add(name);
try {
return evalExpr(computedDefinitions[name]);
} finally {
computing.delete(name);
}
}
var sig = signals[name];
if (sig) {
if (currentRenderer) sig.subscribe(currentRenderer);
return sig.get();
}
if (Object.prototype.hasOwnProperty.call(behaviorFunctions, name)) {
return behaviorFunctions[name];
}
return readGlobal(name);
}
function peekScope(name) {
if (Object.prototype.hasOwnProperty.call(computedDefinitions, name)) {
return readScope(name);
}
if (signals[name]) return signals[name].get();
if (Object.prototype.hasOwnProperty.call(behaviorFunctions, name)) {
return behaviorFunctions[name];
}
return readGlobal(name);
}
function notifyState(name, value, previous) {
var watchers = stateWatchers[name];
if (watchers) {
watchers.slice().forEach(function (watcher) {
try { watcher(value, previous); } catch (error) {
console.error("[wrnexus] watcher for '" + name + "' failed", error);
}
});
}
anyStateListeners.forEach(function (listener) {
try { listener(name, value, previous); } catch (error) {
console.error("[wrnexus] state subscriber failed", error);
}
});
}
function writeScope(
name,
value,
) {
if (!signals[name]) {
signals[name] =
signal(value);
notifyState(
name,
value,
undefined,
);
renderAll();
return;
}
var previous =
signals[name].get();
if (
Object.is(
previous,
value,
)
) {
return;
}
signals[name].set(value);
notifyState(
name,
value,
previous,
);
}
function evalExpr(expr, locals) {
return evaluateExpression(expr, function (name) {
if (locals && Object.prototype.hasOwnProperty.call(locals, name)) {
return locals[name];
}
return readScope(name);
});
}
function runStmt(
source,
locals,
) {
return batchUpdates(function () {
var statements =
splitStatements(source);
for (
var statementIndex = 0;
statementIndex <
statements.length;
statementIndex++
) {
var result = runStatement(
statements[statementIndex],
function (expression) {
return evalExpr(
expression,
locals,
);
},
function (name) {
if (
locals &&
Object.prototype
.hasOwnProperty.call(
locals,
name,
)
) {
return locals[name];
}
return peekScope(name);
},
function (name, value) {
if (
locals &&
Object.prototype
.hasOwnProperty.call(
locals,
name,
)
) {
locals[name] = value;
} else {
writeScope(name, value);
}
},
function (body) {
return runStmt(
body,
locals,
);
},
);
if (result.returned) {
return result;
}
}
return {
returned: false,
value: undefined,
};
});
}
function installBehaviorFunctions(source) {
extractBehaviorFunctions(source || "").forEach(function (definition) {
behaviorFunctions[
definition.name
] = function () {
var locals = {};
for (
var i = 0;
i < definition.args.length;
i++
) {
locals[definition.args[i]] =
arguments[i];
}
var result = runStmt(
definition.body,
locals,
);
return result.returned
? result.value
: undefined;
};
});
}
// A binding belongs to THIS scope only when el is the node's nearest
// [data-scope] ancestor. Otherwise a nested scope owns it and we skip it,
// so an outer scope never clobbers an inner one's values.
function owns(node) {
var host = node.nodeType === 1 ? node : node.parentNode;
return (
!!host &&
host.closest &&
host.closest(
"[data-scope], [data-wrn-scope]",
) === el
);
}
// --- data-for list rendering -------------------------------------------
// Each [data-for="item in list"] element is a per-item template. Add
// data-key="item.id" or a key item.id suffix preserves DOM nodes when
// a list is reordered. Unkeyed loops retain the legacy full-rerender path.
function parseFor(value) {
var m = /^\s*([A-Za-z_$][\w$]*)\s*(?:,\s*([A-Za-z_$][\w$]*)\s*)?\s+in\s+([\s\S]+?)(?:\s+key\s+([\s\S]+?))?\s*$/.exec(
value || "",
);
return m ? { item: m[1], index: m[2], list: m[3].trim(), key: m[4] && m[4].trim() } : null;
}
function unwrapForKey(value) {
var expression = String(value || "").trim();
if (expression.charAt(0) === "{" && expression.charAt(expression.length - 1) === "}") {
expression = expression.slice(1, -1).trim();
}
return expression;
}
function stableForKey(value) {
if (value === null) return "null:";
var type = typeof value;
if (type === "object") {
try { return "object:" + JSON.stringify(value); } catch (_) { return "object:" + String(value); }
}
return type + ":" + String(value);
}
function fillMustache(str, itemEval) {
return str.replace(/\{\{\s*([^}]+?)\s*\}\}|\{([^{}]+)\}/g, function (_, d, s) {
var e = (d || s).trim();
try { return String(itemEval(e)); } catch (err) { return ""; }
});
}
function hydrateItem(
root,
locals,
) {
function localRead(name) {
if (
Object.prototype.hasOwnProperty.call(
locals,
name,
)
) {
return locals[name];
}
return readScope(name);
}
function itemEval(expression) {
return evaluateExpression(
expression,
localRead,
);
}
function applyItemTemplate(
template,
) {
return String(template).replace(
/\{\{\s*([^}]+?)\s*\}\}|\{([^{}]+)\}/g,
function (
_,
doubleExpression,
singleExpression,
) {
var expression = (
doubleExpression ||
singleExpression
).trim();
try {
var value =
itemEval(expression);
return value == null
? ""
: String(value);
} catch (_) {
return "";
}
},
);
}
var elements = [root];
if (root.querySelectorAll) {
Array.prototype.push.apply(
elements,
root.querySelectorAll("*"),
);
}
elements.forEach(function (node) {
if (
!node ||
node.nodeType !== 1
) {
return;
}
var attributes =
Array.prototype.slice.call(
node.attributes,
);
attributes.forEach(function (
attribute,
) {
/*
* data-text must be handled using itemEval(),
* because item/index only exist in loop locals.
*/
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 = "";
}
return;
}
/*
* Conditional classes:
* data-wrn-class-*='["class","expression"]'
*/
if (
attribute.name.indexOf(
"data-wrn-class-",
) === 0
) {
var classBinding;
try {
classBinding = JSON.parse(
attribute.value,
);
} catch (_) {
return;
}
if (
!classBinding ||
classBinding.length !== 2
) {
return;
}
var className =
classBinding[0];
var classExpression =
classBinding[1];
var classEnabled = false;
try {
classEnabled = !!itemEval(
classExpression,
);
} catch (_) {
classEnabled = false;
}
node.classList.toggle(
className,
classEnabled,
);
return;
}
/*
* Reactive ordinary attributes:
* data-wrn-bind-*='["aria-expanded","{isOpen(index)}"]'
*/
if (
attribute.name.indexOf(
"data-wrn-bind-",
) === 0
) {
var binding;
try {
binding = JSON.parse(
attribute.value,
);
} catch (_) {
return;
}
if (
!binding ||
binding.length !== 2
) {
return;
}
var attributeName =
binding[0];
var attributeTemplate =
binding[1];
var exact =
/^\{([^{}]+)\}$/.exec(
attributeTemplate,
);
var rawValue;
if (exact) {
try {
rawValue = itemEval(
exact[1].trim(),
);
} catch (_) {
return;
}
} else {
rawValue =
attributeTemplate.replace(
/\{([^{}]+)\}/g,
function (
_,
expression,
) {
try {
var value =
itemEval(
expression.trim(),
);
return value == null
? ""
: String(value);
} catch (_) {
return "";
}
},
);
}
applyReactiveAttribute(
node,
attributeName,
rawValue,
);
return;
}
/*
* Event handlers inside data-for.
*/
if (
attribute.name.indexOf(
"data-on-",
) === 0
) {
var eventName =
attribute.name.slice(
"data-on-".length,
);
var statement =
attribute.value;
node.addEventListener(
eventName,
function (event) {
var eventLocals = {};
Object.keys(
locals,
).forEach(function (key) {
eventLocals[key] =
locals[key];
});
eventLocals.event = event;
eventLocals.$event = event;
try {
runStmt(
statement,
eventLocals,
);
} catch (error) {
console.error(
"[wrnexus] data-for handler error in '" +
statement +
"'",
error,
);
}
},
);
return;
}
/*
* Static attributes containing interpolation.
*/
if (
attribute.value &&
attribute.value.indexOf("{") !== -1
) {
node.setAttribute(
attribute.name,
applyItemTemplate(
attribute.value,
),
);
}
});
});
/*
* Text interpolation inside loop content:
*
* <span>{item.title}</span>
*/
var walker =
document.createTreeWalker(
root,
NodeFilter.SHOW_TEXT,
null,
);
var textNode;
while (
(textNode = walker.nextNode())
) {
var template =
textNode.nodeValue || "";
if (
template.indexOf("{") === -1
) {
continue;
}
textNode.nodeValue =
applyItemTemplate(template);
}
}
Array.prototype.slice
.call(
el.querySelectorAll(
"[data-for]",
),
)
.forEach(function (tpl) {
if (
!tpl.parentNode ||
!owns(tpl)
) {
return;
}
var spec =
parseFor(
tpl.getAttribute(
"data-for",
),
);
if (!spec) {
return;
}
var template =
tpl.cloneNode(true);
template.removeAttribute(
"data-for",
);
var keyExpression =
spec.key ||
unwrapForKey(
tpl.getAttribute(
"data-key",
),
);
template.removeAttribute(
"data-key",
);
var parent =
tpl.parentNode;
var marker =
document.createComment(
"wrn-for",
);
parent.insertBefore(
marker,
tpl,
);
parent.removeChild(tpl);
var clones = [];
var keyedRecords = new Map();
reactive(function () {
var list =
evalExpr(spec.list);
if (!Array.isArray(list)) {
console.error(
"[wrnexus] data-for expected an array for '" +
spec.list +
"', received",
list,
);
list = [];
}
if (!keyExpression) {
for (
var cloneIndex = 0;
cloneIndex <
clones.length;
cloneIndex++
) {
var existingClone =
clones[cloneIndex];
if (
existingClone.parentNode
) {
existingClone.parentNode
.removeChild(
existingClone,
);
}
}
clones = [];
var fragment =
document.createDocumentFragment();
for (
var itemIndex = 0;
itemIndex < list.length;
itemIndex++
) {
var clone =
template.cloneNode(true);
var locals = {};
locals[spec.item] =
list[itemIndex];
if (spec.index) {
locals[spec.index] =
itemIndex;
}
hydrateItem(
clone,
locals,
);
fragment.appendChild(
clone,
);
clones.push(clone);
}
parent.insertBefore(
fragment,
marker.nextSibling,
);
return;
}
var nextRecords = new Map();
var orderedNodes = [];
for (
var keyedIndex = 0;
keyedIndex < list.length;
keyedIndex++
) {
var keyedItem = list[keyedIndex];
var keyedLocals = {};
keyedLocals[spec.item] = keyedItem;
if (spec.index) keyedLocals[spec.index] = keyedIndex;
var rawKey;
try {
rawKey = evaluateExpression(
keyExpression,
function (name) {
return Object.prototype.hasOwnProperty.call(keyedLocals, name)
? keyedLocals[name]
: readScope(name);
},
);
} catch (error) {
reportDiagnostic(
"WRN-HYDRATE-KEY-001",
"Unable to evaluate data-for key '" + keyExpression + "'.",
el,
error,
);
rawKey = keyedIndex;
}
var normalizedKey = stableForKey(rawKey);
if (nextRecords.has(normalizedKey)) {
reportDiagnostic(
"WRN-HYDRATE-KEY-002",
"Duplicate data-for key '" + String(rawKey) + "'; falling back to its index.",
el,
{ key: rawKey, index: keyedIndex },
);
normalizedKey += ":index:" + keyedIndex;
}
var record = keyedRecords.get(normalizedKey);
if (
!record ||
record.item !== keyedItem ||
(spec.index && record.index !== keyedIndex)
) {
if (record && record.node.parentNode) {
record.node.parentNode.removeChild(record.node);
}
var keyedClone = template.cloneNode(true);
hydrateItem(keyedClone, keyedLocals);
record = {
node: keyedClone,
item: keyedItem,
index: keyedIndex,
};
}
nextRecords.set(normalizedKey, record);
orderedNodes.push(record.node);
}
keyedRecords.forEach(function (record, key) {
if (!nextRecords.has(key) && record.node.parentNode) {
record.node.parentNode.removeChild(record.node);
}
});
var keyedFragment = document.createDocumentFragment();
orderedNodes.forEach(function (node) {
keyedFragment.appendChild(node);
});
parent.insertBefore(
keyedFragment,
marker.nextSibling,
);
keyedRecords = nextRecords;
clones = orderedNodes;
});
});
// data-text bindings. Server-rendered {#each} nodes recover their item/index
// values from the nearest data-wrn-loop-locals marker.
el.querySelectorAll("[data-text]").forEach(function (node) {
if (!owns(node)) return;
var expr = node.getAttribute("data-text");
reactive(function () {
try {
var value = evalExpr(
expr,
decodeLoopLocals(node),
);
node.textContent =
value == null ? "" : String(value);
} catch (_) {
// Keep the server-rendered value when evaluation is unavailable.
}
});
});
// data-show="expr" — toggle visibility on truthiness.
el.querySelectorAll("[data-show]").forEach(function (node) {
if (!owns(node)) return;
var showExpr = node.getAttribute("data-show");
reactive(function () {
var visible = true;
try {
visible = !!evalExpr(
showExpr,
decodeLoopLocals(node),
);
} catch (_) {
// Keep visible when evaluation is unavailable.
}
node.setAttribute(
"data-show",
visible ? "true" : "false",
);
node.style.display = visible ? "" : "none";
});
});
// Conditional class bindings emitted as:
// data-wrn-class-*='["class-name","expression"]'
var classBindNodes = [el].concat(
Array.prototype.slice.call(el.querySelectorAll("*")),
);
classBindNodes.forEach(function (node) {
if (!owns(node)) return;
Array.prototype.slice.call(node.attributes).forEach(function (marker) {
if (marker.name.indexOf("data-wrn-class-") !== 0) return;
var binding;
try {
binding = JSON.parse(marker.value);
} catch (e) {
return;
}
if (!binding || binding.length !== 2) return;
var className = binding[0];
var expression = binding[1];
reactive(function () {
var enabled = false;
try {
enabled = !!evalExpr(
expression,
decodeLoopLocals(node),
);
} catch (error) {
enabled = false;
}
node.classList.toggle(
className,
enabled,
);
});
});
});
// Reactive ordinary attributes emitted by the compiler. Each marker stores
// [attributeName, originalTemplate], preserving an SSR value while allowing
// state changes to update type, aria-*, class, href, and other attributes.
var bindNodes = [el].concat(
Array.prototype.slice.call(
el.querySelectorAll("*"),
),
);
bindNodes.forEach(function (node) {
if (!owns(node)) return;
Array.prototype.slice
.call(node.attributes)
.forEach(function (marker) {
if (
marker.name.indexOf(
"data-wrn-bind-",
) !== 0
) {
return;
}
var binding;
try {
binding = JSON.parse(
marker.value,
);
} catch (error) {
return;
}
if (
!binding ||
binding.length !== 2
) {
return;
}
var name = binding[0];
var template = binding[1];
reactive(function () {
var locals =
decodeLoopLocals(node);
var exact =
/^\{([^{}]+)\}$/.exec(
template,
);
var raw;
if (exact) {
try {
raw = evalExpr(
exact[1].trim(),
locals,
);
} catch (error) {
return;
}
} else {
raw = template.replace(
/\{([^{}]+)\}/g,
function (
_,
expression,
) {
try {
var value =
evalExpr(
expression.trim(),
locals,
);
return value == null
? ""
: String(value);
} catch (error) {
return "";
}
},
);
}
applyReactiveAttribute(
node,
name,
raw,
);
});
});
});
// {{expr}} / {expr} interpolation in text nodes
var walker = document.createTreeWalker(el, NodeFilter.SHOW_TEXT, null);
var textNode;
while ((textNode = walker.nextNode())) {
var template = textNode.nodeValue;
if (template.indexOf("{") === -1) continue;
if (!owns(textNode)) continue;
(function (node, tpl) {
reactive(function () {
node.nodeValue = tpl.replace(/\{\{\s*([^}]+?)\s*\}\}|\{([^{}]+)\}/g, function (_, doubleExpr, singleExpr) {
var expr = doubleExpr || singleExpr;
try {
var value = evalExpr(
expr.trim(),
decodeLoopLocals(
node.parentNode,
),
);
return value == null
? ""
: String(value);
} catch (err) { return ""; }
});
});
})(textNode, template);
}
// Event handlers on elements, window, and document.
var nodes = [el].concat(Array.prototype.slice.call(el.querySelectorAll("*")));
nodes.forEach(function (node) {
if (!owns(node)) return;
Array.prototype.slice.call(node.attributes).forEach(function (attr) {
if (attr.name.indexOf("data-on-") !== 0) return;
var rawName = attr.name.slice("data-on-".length);
var target = node;
var evt = rawName;
if (rawName.indexOf("window-") === 0) {
target = window;
evt = rawName.slice("window-".length);
} else if (rawName.indexOf("document-") === 0) {
target = document;
evt = rawName.slice("document-".length);
}
var stmt = attr.value;
var listener = function (event) {
var locals =
decodeLoopLocals(node);
locals.event = event;
locals.$event = event;
try {
runStmt(
stmt,
locals,
);
} catch (error) {
console.error(
"[wrnexus] handler error in '" +
stmt +
"'",
error,
);
}
};
var options =
target === window && (evt === "scroll" || evt === "touchstart" || evt === "touchmove")
? { passive: true }
: undefined;
target.addEventListener(evt, listener, options);
cleanupCallbacks.push(function () {
target.removeEventListener(evt, listener, options);
});
});
});
if (behavior) {
installBehaviorFunctions(behavior.functions);
var publicScopeApi = {
get: peekScope,
set: writeScope,
call: function (name) {
var fn = behaviorFunctions[name];
if (typeof fn !== "function") return undefined;
return fn.apply(null, Array.prototype.slice.call(arguments, 1));
},
};
el.__wrnexusScopeApi = publicScopeApi;
el.querySelectorAll("[data-wrn-select]").forEach(function (select) {
select.__wrnexusScopeApi = publicScopeApi;
});
(behavior.effects || []).forEach(function (source) {
if (typeof source !== "string" || !source.trim()) return;
reactive(function () {
try { runStmt(source); } catch (error) {
reportDiagnostic("WRN-EFFECT-ERROR", "Reactive effect failed.", el, error);
}
});
});
(behavior.watches || []).forEach(function (watch) {
if (!watch || typeof watch.state !== "string" || typeof watch.body !== "string") return;
if (!stateWatchers[watch.state]) stateWatchers[watch.state] = [];
stateWatchers[watch.state].push(function (value, previous) {
runStmt(watch.body, { value: value, previous: previous });
});
});
var updateSource =
behavior.lifecycle && typeof behavior.lifecycle.update === "string"
? behavior.lifecycle.update.trim()
: "";
if (updateSource) {
anyStateListeners.add(function () {
scheduleUpdateHook(el, function () {
if (!disposed) runStmt(updateSource);
});
});
}
var mountSource =
behavior.lifecycle && typeof behavior.lifecycle.mount === "string"
? behavior.lifecycle.mount.trim()
: "";
var unmountSource =
behavior.lifecycle && typeof behavior.lifecycle.unmount === "string"
? behavior.lifecycle.unmount.trim()
: "";
var instance = {
dispose: function () {
if (disposed) return;
disposed = true;
cleanupCallbacks.splice(0).reverse().forEach(function (cleanup) {
try { cleanup(); } catch (_) { /* ignore cleanup errors */ }
});
pendingUpdateHooks.delete(el);
if (unmountSource) {
try { runStmt(unmountSource); } catch (error) {
console.error("[wrnexus] component unmount hook failed", error);
}
}
mountedBehaviorRoots.delete(el);
behaviorInstances.delete(el);
delete el.__wrnexusScopeApi;
el.querySelectorAll("[data-wrn-select]").forEach(function (select) {
delete select.__wrnexusScopeApi;
});
el.__wrnexusScope = false;
},
};
behaviorInstances.set(el, instance);
mountedBehaviorRoots.add(el);
if (mountSource) {
queueMicrotask(function () {
if (!disposed && el.isConnected) {
try { runStmt(mountSource); } catch (error) {
console.error("[wrnexus] component mount hook failed", error);
}
}
});
}
}
renderAll();
}
function disposeBehaviors(root) {
var elements = [];
if (root && root.nodeType === 1 && root.hasAttribute("data-wrn-behavior")) {
elements.push(root);
}
if (root && root.querySelectorAll) {
Array.prototype.push.apply(elements, root.querySelectorAll("[data-wrn-behavior]"));
}
elements.reverse().forEach(function (element) {
var instance = behaviorInstances.get(element);
if (instance) instance.dispose();
});
}
function ensureBehaviorObserver() {
if (behaviorObserver || typeof MutationObserver === "undefined") return;
behaviorObserver = new MutationObserver(function (records) {
records.forEach(function (record) {
Array.prototype.forEach.call(record.removedNodes, function (node) {
if (node && node.nodeType === 1) disposeBehaviors(node);
});
});
});
behaviorObserver.observe(document.documentElement, { childList: true, subtree: true });
}
function queueScopeHydration(element) {
if (!element || element.__wrnexusScope || element.__wrnexusHydrationQueued) return;
var runtime = element.getAttribute("data-wrn-runtime") || "universal";
var strategy = element.getAttribute("data-wrn-hydrate") || "load";
if (runtime === "server" || strategy === "none") return;
element.__wrnexusHydrationQueued = true;
function hydrate() {
if (element.__wrnexusScope || !element.isConnected) return;
setupScope(element);
}
if (strategy === "load") {
hydrate();
return;
}
if (strategy === "idle") {
var idle = window.requestIdleCallback || function (callback) { return window.setTimeout(callback, 1); };
idle(hydrate);
return;
}
if (strategy === "visible" && typeof IntersectionObserver !== "undefined") {
var observer = new IntersectionObserver(function (entries) {
if (!entries.some(function (entry) { return entry.isIntersecting; })) return;
observer.disconnect();
hydrate();
});
observer.observe(element);
return;
}
if (strategy === "interaction") {
var activate = function () {
element.removeEventListener("pointerdown", activate, true);
element.removeEventListener("keydown", activate, true);
element.removeEventListener("focusin", activate, true);
hydrate();
};
element.addEventListener("pointerdown", activate, true);
element.addEventListener("keydown", activate, true);
element.addEventListener("focusin", activate, true);
return;
}
if (strategy.indexOf("media:") === 0 && typeof window.matchMedia === "function") {
var query = strategy.slice(6);
var media = window.matchMedia(query);
if (media.matches) hydrate();
else {
var onChange = function (event) {
if (!event.matches) return;
if (media.removeEventListener) media.removeEventListener("change", onChange);
else media.removeListener(onChange);
hydrate();
};
if (media.addEventListener) media.addEventListener("change", onChange);
else media.addListener(onChange);
}
return;
}
reportDiagnostic(
"WRN-HYDRATE-STRATEGY",
"Unknown hydration strategy '" + strategy + "'. Falling back to load.",
element,
);
hydrate();
}
function hydrateScopes(root) {
var host = root || document;
var scopeSelector =
"[data-scope], [data-wrn-scope]";
if (
host.nodeType === 1 &&
host.matches &&
host.matches(scopeSelector)
) {
queueScopeHydration(host);
}
if (host.querySelectorAll) {
host
.querySelectorAll(scopeSelector)
.forEach(queueScopeHydration);
}
ensureBehaviorObserver();
hydrateSelectControllers(host);
hydratePinInputControllers(host);
}
var selectOutsideClickBound = false;
function selectScopeApi(root) {
var scope = root;
while (scope) {
if (scope.__wrnexusScopeApi) return scope.__wrnexusScopeApi;
scope = scope.parentElement;
}
return null;
}
function selectPayloadOptions(payload) {
if (Array.isArray(payload)) return payload;
if (!payload || typeof payload !== "object") return [];
if (Array.isArray(payload.options)) return payload.options;
if (Array.isArray(payload.items)) return payload.items;
if (Array.isArray(payload.results)) return payload.results;
if (Array.isArray(payload.data)) return payload.data;
return [];
}
function syncComboboxInput(root) {
if (!root || !root.hasAttribute("data-wrn-combobox")) return;
var input = root.querySelector(".wire-next__combobox-input");
var api = selectScopeApi(root);
if (!input || !api) return;
var value = api.call("inputText") || "";
input.value = String(value);
input.setAttribute("value", String(value));
}
function selectEventDetail(root, extra) {
var api = selectScopeApi(root);
var multiple = !!(api && api.get("multiple"));
var detail = {
component: root.hasAttribute("data-wrn-combobox") ? "ComboBox" : "AdvancedSelect",
query: api ? (api.get("query") || "") : "",
value: api ? (api.get("selectedValue") || "") : "",
values: api && Array.isArray(api.get("selectedValues")) ? api.get("selectedValues") : [],
multiple: multiple,
selectedOptions: api ? (api.call("selectedOptions") || []) : [],
};
if (extra && typeof extra === "object") {
Object.keys(extra).forEach(function (key) { detail[key] = extra[key]; });
}
return detail;
}
function emitSelectEvent(root, name, extra) {
try {
dispatchComponentEvent(root, name, selectEventDetail(root, extra));
} catch (_) {
// CustomEvent can be unavailable in minimal DOM environments.
}
}
function renderRemoteSelectOptions(root, options) {
var list = root.querySelector(".wire-next__select-list");
if (!list) return;
list.querySelectorAll(".wire-next__option-group, .wire-next__select-option, .wire-next__select-message")
.forEach(function (node) { node.remove(); });
options.forEach(function (option) {
if (!option || option.value == null) return;
var button = document.createElement("button");
button.type = "button";
button.className = "wire-next__select-option";
button.setAttribute("role", "option");
button.setAttribute("data-option-value", String(option.value));
button.disabled = !!option.disabled;
if (option.icon) {
var icon = document.createElement("i");
icon.className = String(option.icon);
icon.setAttribute("aria-hidden", "true");
button.appendChild(icon);
} else if (option.avatar) {
var avatar = document.createElement("img");
avatar.src = String(option.avatar);
avatar.alt = "";
button.appendChild(avatar);
} else if (option.color) {
var dot = document.createElement("i");
dot.className = "wire-next__color-dot";
dot.style.setProperty("--option-color", String(option.color));
button.appendChild(dot);
}
var copy = document.createElement("span");
var label = document.createElement("strong");
label.textContent = String(option.label == null ? option.value : option.label);
copy.appendChild(label);
if (option.description) {
var description = document.createElement("small");
description.textContent = String(option.description);
copy.appendChild(description);
}
button.appendChild(copy);
var check = document.createElement("i");
check.className = "wire-next__check icon-[lucide--check]";
check.setAttribute("aria-hidden", "true");
check.style.display = "none";
button.appendChild(check);
button.addEventListener("click", function () {
var api = selectScopeApi(root);
if (!api || button.disabled) return;
api.call("chooseOption", option);
window.setTimeout(function () {
var selected = api.call("isSelected", option);
button.classList.toggle("wire-next__select-option--selected", !!selected);
button.setAttribute("aria-selected", selected ? "true" : "false");
check.style.display = selected ? "" : "none";
syncComboboxInput(root);
}, 0);
});
list.appendChild(button);
});
}
function setupSelectController(root) {
if (!root || root.__wrnexusSelectController) return;
root.__wrnexusSelectController = { attempts: 0 };
var remote = root.getAttribute("data-remote") === "true";
var remoteUrl = root.getAttribute("data-remote-url") || "";
var input = root.querySelector(".wire-next__select-search input, .wire-next__combobox-input");
var list = root.querySelector(".wire-next__select-list");
var loadMore = root.querySelector("[data-wrn-select-load-more]");
var timer = 0;
var requestId = 0;
var loadedOptions = [];
var retainedSelections = [];
var currentPage = Number(root.getAttribute("data-page") || 1);
function setLoading(loading) {
root.setAttribute("data-loading", loading ? "true" : "false");
if (list) list.setAttribute("aria-busy", loading ? "true" : "false");
var api = selectScopeApi(root);
if (api) api.set("loading", loading);
}
function load(append, allowEmptyQuery) {
root.__wrnexusSelectController.attempts++;
if (!remote || !remoteUrl) return;
var api = selectScopeApi(root);
if (!api) return;
var query = input ? input.value : "";
var minimum = Number(api.get("minSearchLength") || 0);
if (!allowEmptyQuery && query.length < minimum) return;
var url;
try {
url = new URL(remoteUrl, window.location.href);
} catch (_) {
return;
}
var parameter = root.getAttribute("data-remote-query-param") || "q";
if (query) url.searchParams.set(parameter, query);
var nextPage = append ? currentPage + 1 : 1;
url.searchParams.set("page", String(nextPage));
var thisRequest = ++requestId;
setLoading(true);
fetch(url.toString(), { headers: { accept: "application/json" } })
.then(function (response) {
if (!response.ok) throw new Error("HTTP " + response.status);
return response.json();
})
.then(function (payload) {
if (thisRequest !== requestId) return;
var nextOptions = selectPayloadOptions(payload);
loadedOptions = append ? loadedOptions.concat(nextOptions) : nextOptions;
currentPage = nextPage;
var selectedValues = api.get("selectedValues");
var selectedValue = api.get("selectedValue");
var selectedKeys = api.get("multiple")
? (Array.isArray(selectedValues) ? selectedValues : [])
: (selectedValue ? [selectedValue] : []);
var previousOptions = api.call("allOptions") || [];
retainedSelections = retainedSelections
.concat(previousOptions)
.concat(loadedOptions)
.filter(function (option, index, list) {
return option && selectedKeys.includes(option.value) &&
list.findIndex(function (item) { return item && item.value === option.value; }) === index;
});
var stateOptions = retainedSelections.concat(loadedOptions).filter(function (option, index, list) {
return option && list.findIndex(function (item) { return item && item.value === option.value; }) === index;
});
api.set("options", stateOptions);
api.set("groups", []);
api.set("page", currentPage);
api.set("hasMore", !!(payload && (payload.hasMore || payload.nextPage || payload.next)));
renderRemoteSelectOptions(root, loadedOptions);
emitSelectEvent(root, "load", {
options: nextOptions,
page: currentPage,
hasMore: !!(payload && (payload.hasMore || payload.nextPage || payload.next)),
append: !!append,
url: url.toString(),
payload: payload,
});
})
.catch(function (error) {
console.error("[wrnexus] advanced select remote request failed", error);
emitSelectEvent(root, "error", {
error: error,
message: error && error.message ? error.message : String(error),
url: url.toString(),
});
})
.then(function () {
if (thisRequest === requestId) setLoading(false);
});
}
if (input) {
input.addEventListener("input", function () {
emitSelectEvent(root, "search", { query: input.value });
if (!remote) return;
window.clearTimeout(timer);
var debounce = Number(root.getAttribute("data-remote-debounce") || 250);
timer = window.setTimeout(function () { load(false); }, debounce);
});
}
if (loadMore) {
loadMore.addEventListener("click", function () { load(true); });
}
if (list && root.getAttribute("data-infinite") === "true") {
list.addEventListener("scroll", function () {
if (list.scrollTop + list.clientHeight < list.scrollHeight - 24) return;
var api = selectScopeApi(root);
if (api && api.get("hasMore") && root.getAttribute("data-loading") !== "true") load(true);
});
}
root.addEventListener("click", function (event) {
if (!remote || !remoteUrl) return;
if (!event.target.closest(".wire-next__select-trigger")) return;
if (loadedOptions.length === 0) load(false);
});
root.__wrnexusSelectController.load = load;
var dropdown = root.querySelector(".wire-next__select-dropdown");
var lastOpen = !!(selectScopeApi(root) && selectScopeApi(root).get("open"));
function emitOpenStateIfChanged() {
var api = selectScopeApi(root);
var nextOpen = !!(api && api.get("open"));
if (nextOpen === lastOpen) return;
lastOpen = nextOpen;
emitSelectEvent(root, nextOpen ? "open" : "close");
}
var ObserverConstructor =
root.ownerDocument &&
root.ownerDocument.defaultView &&
root.ownerDocument.defaultView.MutationObserver
? root.ownerDocument.defaultView.MutationObserver
: (typeof MutationObserver !== "undefined" ? MutationObserver : null);
if (ObserverConstructor) {
new ObserverConstructor(function () {
emitOpenStateIfChanged();
}).observe(dropdown || root, {
attributes: true,
attributeFilter: dropdown ? ["data-show", "style"] : ["class"],
});
}
root.addEventListener("click", function (event) {
var optionButton = event.target.closest(".wire-next__select-option");
if (optionButton) {
window.setTimeout(function () {
var api = selectScopeApi(root);
var optionValue = optionButton.getAttribute("data-option-value");
var options = api ? (api.call("allOptions") || []) : [];
var option = options.find(function (item) {
return item && String(item.value) === String(optionValue);
});
emitSelectEvent(root, "select", { option: option || null });
emitSelectEvent(root, "change", { option: option || null, reason: "select" });
emitOpenStateIfChanged();
}, 0);
}
if (event.target.closest(".wire-next__clear-select")) {
window.setTimeout(function () {
emitSelectEvent(root, "clear");
emitSelectEvent(root, "change", { reason: "clear" });
}, 0);
}
}, true);
root.addEventListener("keydown", function (event) {
if (event.key !== "Enter") return;
var before = selectEventDetail(root);
window.setTimeout(function () {
var after = selectEventDetail(root);
var beforeSelection = before.multiple ? JSON.stringify(before.values) : before.value;
var afterSelection = after.multiple ? JSON.stringify(after.values) : after.value;
if (afterSelection === beforeSelection) return;
syncComboboxInput(root);
emitSelectEvent(root, "select", { source: "keyboard" });
emitSelectEvent(root, "change", { reason: "select", source: "keyboard" });
emitOpenStateIfChanged();
}, 0);
}, true);
if (root.hasAttribute("data-wrn-combobox")) {
root.wrnexusCombobox = {
open: function () {
var api = selectScopeApi(root);
if (api) api.call("openDropdown");
emitOpenStateIfChanged();
},
close: function () {
var api = selectScopeApi(root);
if (api) api.call("closeDropdown");
emitOpenStateIfChanged();
},
clear: function () {
var api = selectScopeApi(root);
if (api) api.call("clearValue");
syncComboboxInput(root);
emitSelectEvent(root, "clear", { source: "method" });
emitSelectEvent(root, "change", { reason: "clear", source: "method" });
},
setValue: function (value) {
var api = selectScopeApi(root);
if (api) api.call("setValue", value);
syncComboboxInput(root);
emitSelectEvent(root, "change", { reason: "setValue", source: "method" });
},
reload: function () {
load(false, true);
},
};
root.addEventListener("click", function (event) {
if (!event.target.closest(".wire-next__select-option, .wire-next__clear-select")) return;
window.setTimeout(function () { syncComboboxInput(root); }, 0);
});
if (input) {
input.addEventListener("keydown", function (event) {
if (event.key === "Enter") {
window.setTimeout(function () { syncComboboxInput(root); }, 0);
}
});
}
}
if (remote && remoteUrl && root.getAttribute("data-remote-auto-load") !== "false") {
load(false, true);
}
}
function hydrateSelectControllers(root) {
var host = root || document;
var selects = [];
if (host.nodeType === 1 && host.matches && host.matches("[data-wrn-select]")) selects.push(host);
if (host.querySelectorAll) {
Array.prototype.push.apply(selects, host.querySelectorAll("[data-wrn-select]"));
}
selects.forEach(setupSelectController);
if (!selectOutsideClickBound) {
selectOutsideClickBound = true;
document.addEventListener("pointerdown", function (event) {
document.querySelectorAll("[data-wrn-select].wire-next--open").forEach(function (select) {
if (select.contains(event.target)) return;
var api = selectScopeApi(select);
if (api) api.set("open", false);
});
});
}
}
function dispatchComponentEvent(root, name, detail) {
if (!root || !name) return null;
var EventConstructor =
root.ownerDocument &&
root.ownerDocument.defaultView &&
root.ownerDocument.defaultView.CustomEvent
? root.ownerDocument.defaultView.CustomEvent
: CustomEvent;
var event = new EventConstructor(String(name), {
bubbles: true,
detail: detail || {},
});
root.dispatchEvent(event);
// Compatibility for applications using the former prefixed contract.
root.dispatchEvent(new EventConstructor("wrnexus:" + String(name), {
bubbles: true,
detail: detail || {},
}));
return event;
}
function emitPinInputEvent(root, name, extra) {
var hidden = root.querySelector("[data-pin-value]");
var cells = Array.prototype.slice.call(root.querySelectorAll("[data-pin-cell]"));
var value = hidden ? hidden.value : "";
var detail = {
component: "PinInput",
value: value,
digits: cells.map(function (cell) { return cell.value; }),
complete: value.length === cells.length,
length: cells.length,
};
if (extra && typeof extra === "object") {
Object.keys(extra).forEach(function (key) { detail[key] = extra[key]; });
}
try {
dispatchComponentEvent(root, name, detail);
} catch (_) {
// CustomEvent can be unavailable in minimal DOM environments.
}
}
function setupPinInputController(root) {
if (!root || root.__wrnexusPinInputController) return;
var cells = Array.prototype.slice.call(root.querySelectorAll("[data-pin-cell]"));
var hidden = root.querySelector("[data-pin-value]");
var clearButton = root.querySelector("[data-pin-clear]");
var patternSource = root.getAttribute("data-pattern") || "[0-9]";
var allowPaste = root.getAttribute("data-allow-paste") !== "false";
var autoSubmit = root.getAttribute("data-auto-submit") === "true";
var matcher;
var exactMatcher;
var NativeEventConstructor =
root.ownerDocument &&
root.ownerDocument.defaultView &&
root.ownerDocument.defaultView.Event
? root.ownerDocument.defaultView.Event
: Event;
try {
exactMatcher = new RegExp("^(?:" + patternSource + ")$");
matcher = new RegExp("^(?:" + patternSource + ")$", "i");
} catch (error) {
matcher = /$a/;
emitPinInputEvent(root, "error", {
error: error,
message: "Invalid PIN pattern: " + patternSource,
});
}
function acceptedCharacters(value) {
return Array.from(String(value || "")).map(function (character) {
matcher.lastIndex = 0;
if (!matcher.test(character)) return "";
exactMatcher.lastIndex = 0;
if (exactMatcher.test(character)) return character;
var upper = character.toUpperCase();
exactMatcher.lastIndex = 0;
if (exactMatcher.test(upper)) return upper;
var lower = character.toLowerCase();
exactMatcher.lastIndex = 0;
return exactMatcher.test(lower) ? lower : character;
}).filter(Boolean);
}
function currentValue() {
return cells.map(function (cell) { return cell.value; }).join("");
}
function updateClearButton(value) {
if (!clearButton) return;
clearButton.style.display = value ? "" : "none";
}
function syncValue(reason, index) {
var previous = hidden ? hidden.value : "";
var value = currentValue();
if (hidden) {
hidden.value = value;
hidden.setAttribute("value", value);
hidden.dispatchEvent(new NativeEventConstructor("input", { bubbles: true }));
hidden.dispatchEvent(new NativeEventConstructor("change", { bubbles: true }));
}
root.setAttribute("data-value", value);
root.setAttribute("data-complete", value.length === cells.length ? "true" : "false");
updateClearButton(value);
emitPinInputEvent(root, "input", { reason: reason, index: index });
if (value !== previous) {
emitPinInputEvent(root, "change", { reason: reason, index: index });
}
if (value.length === cells.length && previous.length !== cells.length) {
emitPinInputEvent(root, "complete", { reason: reason, index: index });
if (autoSubmit) {
var form = root.closest("form");
if (form && typeof form.requestSubmit === "function") form.requestSubmit();
}
}
}
function setValue(value, reason) {
var characters = acceptedCharacters(value).slice(0, cells.length);
cells.forEach(function (cell, index) {
cell.value = characters[index] || "";
cell.setAttribute("value", cell.value);
});
syncValue(reason || "setValue", characters.length ? characters.length - 1 : -1);
}
function focusCell(index) {
var cell = cells[Math.max(0, Math.min(cells.length - 1, index))];
if (cell && !cell.disabled) {
cell.focus();
if (typeof cell.select === "function") cell.select();
}
}
cells.forEach(function (cell, index) {
cell.addEventListener("focus", function () {
if (typeof cell.select === "function") cell.select();
});
cell.addEventListener("input", function () {
var characters = acceptedCharacters(cell.value);
cell.value = characters.length ? characters[characters.length - 1] : "";
cell.setAttribute("value", cell.value);
syncValue("input", index);
if (cell.value && index < cells.length - 1) focusCell(index + 1);
});
cell.addEventListener("keydown", function (event) {
if (event.key === "Backspace" && !cell.value && index > 0) {
event.preventDefault();
cells[index - 1].value = "";
cells[index - 1].setAttribute("value", "");
syncValue("backspace", index - 1);
focusCell(index - 1);
} else if (event.key === "ArrowLeft" && index > 0) {
event.preventDefault();
focusCell(index - 1);
} else if (event.key === "ArrowRight" && index < cells.length - 1) {
event.preventDefault();
focusCell(index + 1);
} else if (event.key === "Home") {
event.preventDefault();
focusCell(0);
} else if (event.key === "End") {
event.preventDefault();
focusCell(cells.length - 1);
}
});
cell.addEventListener("paste", function (event) {
if (!allowPaste) return;
var clipboard = event.clipboardData;
if (!clipboard) return;
event.preventDefault();
var characters = acceptedCharacters(clipboard.getData("text")).slice(0, cells.length - index);
characters.forEach(function (character, offset) {
cells[index + offset].value = character;
cells[index + offset].setAttribute("value", character);
});
syncValue("paste", index);
emitPinInputEvent(root, "paste", { index: index, pasted: characters.join("") });
focusCell(Math.min(index + characters.length, cells.length - 1));
});
});
if (clearButton) {
clearButton.addEventListener("click", function () {
setValue("", "clear");
emitPinInputEvent(root, "clear");
focusCell(0);
});
}
root.wrnexusPinInput = {
focus: function (index) { focusCell(Number(index || 0)); },
clear: function () {
setValue("", "clear");
emitPinInputEvent(root, "clear", { source: "method" });
},
setValue: function (value) { setValue(value, "setValue"); },
getValue: function () { return currentValue(); },
};
root.__wrnexusPinInputController = root.wrnexusPinInput;
setValue(hidden ? hidden.value : root.getAttribute("data-value"), "initial");
}
function hydratePinInputControllers(root) {
var host = root || document;
var inputs = [];
if (host.nodeType === 1 && host.matches && host.matches("[data-wrn-pin-input]")) {
inputs.push(host);
}
if (host.querySelectorAll) {
Array.prototype.push.apply(inputs, host.querySelectorAll("[data-wrn-pin-input]"));
}
inputs.forEach(setupPinInputController);
}
function parseScopeDecl(decl) {
var initial = {};
splitTopLevel(decl, ",").forEach(function (part) {
var idx = findTopLevel(part, ":");
if (idx < 0) return;
var name = part.slice(0, idx).trim();
var expr = part.slice(idx + 1).trim();
if (!/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name)) return;
try {
initial[name] = evaluateExpression(expr, function () { return undefined; });
} catch (e) {
console.error("[wrnexus] invalid data-scope value for '" + name + "'", e);
}
});
return initial;
}
function skipStatementWhitespace(
source,
index,
) {
while (
index < source.length &&
/\s/.test(source[index])
) {
index++;
}
return index;
}
function findClosingDelimiter(
source,
start,
opening,
closing,
) {
if (source[start] !== opening) {
return -1;
}
var depth = 1;
var quote = "";
var escaped = false;
for (
var index = start + 1;
index < source.length;
index++
) {
var character = source[index];
if (quote) {
if (escaped) {
escaped = false;
continue;
}
if (character === "\\") {
escaped = true;
continue;
}
if (character === quote) {
quote = "";
}
continue;
}
if (
character === '"' ||
character === "'" ||
character === "\`"
) {
quote = character;
continue;
}
if (character === opening) {
depth++;
continue;
}
if (character === closing) {
depth--;
if (depth === 0) {
return index;
}
}
}
return -1;
}
function parseIfStatement(source) {
source = String(source || "").trim();
if (
source.slice(0, 2) !== "if" ||
/[A-Za-z0-9_$]/.test(
source.charAt(2),
)
) {
return null;
}
var branches = [];
var index = 0;
var firstBranch = true;
while (index < source.length) {
index = skipStatementWhitespace(
source,
index,
);
var branchType;
if (
firstBranch &&
source.slice(index, index + 2) ===
"if" &&
!/[A-Za-z0-9_$]/.test(
source.charAt(index + 2),
)
) {
branchType = "if";
index += 2;
} else if (
source.slice(index, index + 7) ===
"else if" &&
!/[A-Za-z0-9_$]/.test(
source.charAt(index + 7),
)
) {
branchType = "else-if";
index += 7;
} else if (
source.slice(index, index + 4) ===
"else" &&
!/[A-Za-z0-9_$]/.test(
source.charAt(index + 4),
)
) {
branchType = "else";
index += 4;
} else {
break;
}
firstBranch = false;
index = skipStatementWhitespace(
source,
index,
);
var condition = null;
if (branchType !== "else") {
if (source.charAt(index) !== "(") {
throw new Error(
"Expected '(' after " +
(
branchType === "if"
? "if"
: "else if"
),
);
}
var conditionEnd =
findClosingDelimiter(
source,
index,
"(",
")",
);
if (conditionEnd < 0) {
throw new Error(
"Unclosed " +
(
branchType === "if"
? "if"
: "else if"
) +
" condition",
);
}
condition = source
.slice(
index + 1,
conditionEnd,
)
.trim();
index = skipStatementWhitespace(
source,
conditionEnd + 1,
);
}
if (source.charAt(index) !== "{") {
throw new Error(
"Expected '{' after " +
branchType,
);
}
var bodyEnd =
findClosingDelimiter(
source,
index,
"{",
"}",
);
if (bodyEnd < 0) {
throw new Error(
"Unclosed " +
branchType +
" body",
);
}
branches.push({
condition: condition,
body: source.slice(
index + 1,
bodyEnd,
),
});
index = skipStatementWhitespace(
source,
bodyEnd + 1,
);
if (
source.slice(index, index + 7) ===
"else if" ||
source.slice(index, index + 4) ===
"else"
) {
continue;
}
break;
}
return branches.length > 0
? branches
: null;
}
function runStatement(
stmt,
evalExpr,
read,
write,
runBlock,
) {
stmt = String(stmt || "").trim();
if (!stmt) {
return {
returned: false,
value: undefined,
};
}
var returnMatch =
/^return(?:\s+([\s\S]+))?$/.exec(
stmt,
);
if (returnMatch) {
return {
returned: true,
value: returnMatch[1]
? evalExpr(returnMatch[1])
: undefined,
};
}
var ifBranches =
parseIfStatement(stmt);
if (ifBranches) {
for (
var branchIndex = 0;
branchIndex < ifBranches.length;
branchIndex++
) {
var branch =
ifBranches[branchIndex];
if (
branch.condition === null ||
!!evalExpr(branch.condition)
) {
return runBlock(branch.body);
}
}
return {
returned: false,
value: undefined,
};
}
var increment = stmt.match(
/^([A-Za-z_$][A-Za-z0-9_$]*)\s*(\+\+|--)$/,
);
if (increment) {
write(
increment[1],
Number(read(increment[1]) || 0) +
(increment[2] === "++"
? 1
: -1),
);
return {
returned: false,
value: undefined,
};
}
var assignment = stmt.match(
/^([A-Za-z_$][A-Za-z0-9_$]*)\s*(\+=|-=|\*=|\/=|%=|=)\s*([\s\S]+)$/,
);
if (!assignment) {
evalExpr(stmt);
return {
returned: false,
value: undefined,
};
}
var name = assignment[1];
var operator = assignment[2];
var nextValue =
evalExpr(assignment[3]);
var currentValue = read(name);
if (operator === "+=") {
nextValue =
currentValue + nextValue;
} else if (operator === "-=") {
nextValue =
Number(currentValue || 0) -
Number(nextValue || 0);
} else if (operator === "*=") {
nextValue =
Number(currentValue || 0) *
Number(nextValue || 0);
} else if (operator === "/=") {
nextValue =
Number(currentValue || 0) /
Number(nextValue || 0);
} else if (operator === "%=") {
nextValue =
Number(currentValue || 0) %
Number(nextValue || 0);
}
write(name, nextValue);
return {
returned: false,
value: nextValue,
};
}
function evaluateSpecialExpression(
expression,
read,
) {
var source = String(
expression || "",
).trim();
// Remove an optional trailing semicolon.
if (
source.charAt(
source.length - 1,
) === ";"
) {
source = source
.slice(0, -1)
.trim();
}
/*
* Array literals containing spreads:
*
* [...openIndexes, index]
* [first, ...otherItems]
*/
if (
source.charAt(0) === "[" &&
source.charAt(
source.length - 1,
) === "]" &&
source.indexOf("...") !== -1
) {
var arraySource = source.slice(
1,
-1,
);
var arrayParts = splitTopLevel(
arraySource,
",",
);
var arrayResult = [];
arrayParts.forEach(function (
part,
) {
var partSource =
part.trim();
if (!partSource) {
return;
}
if (
partSource.slice(0, 3) ===
"..."
) {
var spreadResult =
evaluateExpression(
partSource
.slice(3)
.trim(),
read,
);
if (
Array.isArray(
spreadResult,
)
) {
Array.prototype.push.apply(
arrayResult,
spreadResult,
);
}
return;
}
arrayResult.push(
evaluateExpression(
partSource,
read,
),
);
});
return {
matched: true,
value: arrayResult,
};
}
/*
* First recognize the array method call without
* trying to parse the callback in the same regex.
*/
var methodMatch =
/^([\s\S]+)\.(filter|map|flatMap|some|every|find|findIndex)\(\s*([\s\S]*)\s*\)$/.exec(
source,
);
if (!methodMatch) {
return {
matched: false,
value: undefined,
};
}
var collectionSource =
methodMatch[1].trim();
var method =
methodMatch[2];
var callbackSource =
methodMatch[3].trim();
/*
* Supported callbacks:
*
* item => item.active
* (item) => item.active
* (item, index) => index > 0
*/
var callbackMatch =
/^(?:\(\s*([A-Za-z_$][A-Za-z0-9_$]*)\s*(?:,\s*([A-Za-z_$][A-Za-z0-9_$]*)\s*)?\)|([A-Za-z_$][A-Za-z0-9_$]*))\s*=>\s*([\s\S]+)$/.exec(
callbackSource,
);
if (!callbackMatch) {
return {
matched: false,
value: undefined,
};
}
var itemName =
callbackMatch[1] ||
callbackMatch[3];
var indexName =
callbackMatch[2];
var callbackBody =
callbackMatch[4].trim();
/*
* Support expression bodies wrapped in parentheses:
*
* item => (
* item.active
* )
*/
if (
callbackBody.charAt(0) ===
"(" &&
callbackBody.charAt(
callbackBody.length - 1,
) === ")"
) {
callbackBody = callbackBody
.slice(1, -1)
.trim();
}
var collection =
evaluateExpression(
collectionSource,
read,
);
if (!Array.isArray(collection)) {
collection = [];
}
var callback = function (
itemValue,
itemIndex,
) {
return evaluateExpression(
callbackBody,
function (name) {
if (name === itemName) {
return itemValue;
}
if (
indexName &&
name === indexName
) {
return itemIndex;
}
return read(name);
},
);
};
var result;
if (method === "filter") {
result =
collection.filter(callback);
} else if (
method === "map"
) {
result =
collection.map(callback);
} else if (
method === "flatMap"
) {
result =
collection.flatMap(callback);
} else if (
method === "some"
) {
result =
collection.some(callback);
} else if (
method === "every"
) {
result =
collection.every(callback);
} else if (
method === "find"
) {
result =
collection.find(callback);
} else {
result =
collection.findIndex(
callback,
);
}
return {
matched: true,
value: result,
};
}
// A small, eval-free expression evaluator (so a strict CSP needs no
// 'unsafe-eval'). Supports: literals, identifiers, member access (a.b, a[b]),
// function/method calls, arrays, objects, arithmetic, comparison, equality,
// logical (&& ||), unary (! - +), and the ternary operator.
function evaluateExpression(
expr,
read,
) {
expr = String(
expr || "",
).trim();
var special =
evaluateSpecialExpression(
expr,
read,
);
if (special.matched) {
return special.value;
}
var tokens =
tokenizeExpression(expr);
var index = 0;
function peek() { return tokens[index]; }
function next() { return tokens[index++]; }
function is(v) { return peek() && peek().value === v; }
function match(v) { if (is(v)) { index++; return true; } return false; }
function expect(v) {
if (!match(v)) throw new Error("Expected '" + v + "' in '" + expr + "'");
}
function parsePrimary() {
var t = next();
if (!t) throw new Error("Unexpected end of expression in '" + expr + "'");
if (t.type === "number" || t.type === "string") return { value: t.value };
if (t.type === "ident") {
if (t.value === "true") return { value: true };
if (t.value === "false") return { value: false };
if (t.value === "null") return { value: null };
if (t.value === "undefined") return { value: undefined };
return { value: read(t.value) };
}
if (t.value === "(") { var v = parseTernary(); expect(")"); return { value: v }; }
if (t.value === "[") {
var arr = [];
if (!is("]")) { arr.push(parseTernary()); while (match(",")) arr.push(parseTernary()); }
expect("]");
return { value: arr };
}
if (t.value === "{") {
var obj = {};
if (!is("}")) {
do {
var kt = next();
var key = kt.value;
expect(":");
obj[key] = parseTernary();
} while (match(","));
}
expect("}");
return { value: obj };
}
throw new Error("Unexpected token '" + t.value + "'");
}
function parsePostfix() {
var node = parsePrimary();
for (;;) {
if (match(".")) {
var prop = next().value;
node = { value: node.value == null ? undefined : node.value[prop], obj: node.value };
} else if (match("[")) {
var key = parseTernary();
expect("]");
node = { value: node.value == null ? undefined : node.value[key], obj: node.value };
} else if (is("(")) {
next();
var args = [];
if (!is(")")) { args.push(parseTernary()); while (match(",")) args.push(parseTernary()); }
expect(")");
var fn = node.value;
node = { value: typeof fn === "function" ? fn.apply(node.obj, args) : undefined };
} else break;
}
return node;
}
function parseUnary() {
if (match("!")) return !parseUnary();
if (match("-")) return -parseUnary();
if (match("+")) return +parseUnary();
return parsePostfix().value;
}
function parseMul() {
var l = parseUnary();
while (peek() && (is("*") || is("/") || is("%"))) {
var op = next().value, r = parseUnary();
l = op === "*" ? l * r : op === "/" ? l / r : l % r;
}
return l;
}
function parseAdd() {
var l = parseMul();
while (peek() && (is("+") || is("-"))) {
var op = next().value, r = parseMul();
l = op === "+" ? l + r : l - r;
}
return l;
}
function parseCmp() {
var l = parseAdd();
while (peek() && (is("<") || is(">") || is("<=") || is(">="))) {
var op = next().value, r = parseAdd();
l = op === "<" ? l < r : op === ">" ? l > r : op === "<=" ? l <= r : l >= r;
}
return l;
}
function parseEq() {
var l = parseCmp();
while (peek() && (is("==") || is("!=") || is("===") || is("!=="))) {
var op = next().value, r = parseCmp();
l = op === "==" ? l == r : op === "!=" ? l != r : op === "===" ? l === r : l !== r;
}
return l;
}
function parseAnd() {
var l = parseEq();
while (match("&&")) {
var r = parseEq();
l = l && r;
}
return l;
}
function parseOr() {
var l = parseAnd();
while (match("||")) {
var r = parseAnd();
l = l || r;
}
return l;
}
function parseTernary() {
var c = parseOr();
if (match("?")) { var a = parseTernary(); expect(":"); var b = parseTernary(); return c ? a : b; }
return c;
}
var value = parseTernary();
if (index < tokens.length) throw new Error("Unexpected token '" + tokens[index].value + "'");
return value;
}
function tokenizeExpression(input) {
var tokens = [];
var i = 0;
while (i < input.length) {
var ch = input[i];
if (/\s/.test(ch)) { i++; continue; }
if (/[0-9]/.test(ch) || (ch === "." && /[0-9]/.test(input[i + 1]))) {
var start = i++;
while (i < input.length && /[0-9.]/.test(input[i])) i++;
tokens.push({ type: "number", value: Number(input.slice(start, i)) });
continue;
}
if (ch === '"' || ch === "'") {
var quote = ch, value = "";
i++;
while (i < input.length) {
ch = input[i++];
if (ch === quote) break;
if (ch === "\\") {
var esc = input[i++];
value += esc === "n" ? "\n" : esc === "t" ? "\t" : esc || "";
} else value += ch;
}
tokens.push({ type: "string", value: value });
continue;
}
if (/[A-Za-z_$]/.test(ch)) {
var s = i++;
while (i < input.length && /[A-Za-z0-9_$]/.test(input[i])) i++;
tokens.push({ type: "ident", value: input.slice(s, i) });
continue;
}
var three = input.slice(i, i + 3);
if (
three === "===" ||
three === "!=="
) {
tokens.push({
type: "operator",
value: three,
});
i += 3;
continue;
}
var two = input.slice(i, i + 2);
if (
two === "==" ||
two === "!=" ||
two === ">=" ||
two === "<=" ||
two === "&&" ||
two === "||"
) {
tokens.push({
type: "operator",
value: two,
});
i += 2;
continue;
}
if ("()+-*/%!<>.,?:[]{}".indexOf(ch) !== -1) { tokens.push({ type: "op", value: ch }); i++; continue; }
throw new Error("Unexpected character '" + ch + "'");
}
return tokens;
}
function splitTopLevel(input, separator) {
var parts = [];
var start = 0;
var depth = 0;
var quote = "";
for (var i = 0; i < input.length; i++) {
var ch = input[i];
if (quote) {
if (ch === "\\") i++;
else if (ch === quote) quote = "";
continue;
}
if (ch === '"' || ch === "'") {
quote = ch;
continue;
}
if (ch === "(" || ch === "[" || ch === "{") depth++;
else if (ch === ")" || ch === "]" || ch === "}") depth--;
else if (ch === separator && depth === 0) {
parts.push(input.slice(start, i).trim());
start = i + 1;
}
}
parts.push(input.slice(start).trim());
return parts.filter(Boolean);
}
function findTopLevel(input, needle) {
var depth = 0;
var quote = "";
for (var i = 0; i < input.length; i++) {
var ch = input[i];
if (quote) {
if (ch === "\\") i++;
else if (ch === quote) quote = "";
continue;
}
if (ch === '"' || ch === "'") quote = ch;
else if (ch === "(" || ch === "[" || ch === "{") depth++;
else if (ch === ")" || ch === "]" || ch === "}") depth--;
else if (ch === needle && depth === 0) return i;
}
return -1;
}
function safeCsrUrl(id) {
try {
if (!/^[A-Za-z0-9_-]+$/.test(id)) return null;
return "/__wrnexus/csr?route=" + encodeURIComponent(location.pathname) + "&id=" + encodeURIComponent(id);
} catch (_) {
return null;
}
}
function setupCsrFetch(el) {
if (el.__wrnexusCsrFetch) return;
el.__wrnexusCsrFetch = true;
var id = el.getAttribute("data-wrnexus-csr") || "";
var url = safeCsrUrl(id);
if (!url) {
console.error("[wrnexus] blocked unsafe CSR binding '" + id + "'");
return;
}
var headers = { "accept": "text/plain" };
var storage = localStorageSnapshotHeader();
if (storage) headers["x-wrnexus-local-storage"] = storage;
fetch(url, { headers: headers })
.then(function (res) {
if (!res.ok) throw new Error("HTTP " + res.status);
return res.text();
})
.then(function (text) {
el.textContent = text;
})
.catch(function (err) {
console.error("[wrnexus] CSR fetch failed for binding '" + id + "'", err);
el.textContent = "Failed to load";
});
}
function hydrateCsrFetches(root) {
(root || document).querySelectorAll("[data-wrnexus-csr]").forEach(setupCsrFetch);
}
function localStorageSnapshotHeader() {
try {
if (!("localStorage" in window)) return "";
var values = {};
for (var i = 0; i < window.localStorage.length; i++) {
var key = window.localStorage.key(i);
if (!key) continue;
var value = window.localStorage.getItem(key);
if (typeof value === "string") values[key] = value;
}
var encoded = encodeURIComponent(JSON.stringify(values));
return encoded.length <= 12000 ? encoded : "";
} catch (_) {
return "";
}
}
window.__wrnexusHydrateScopes = hydrateScopes;
window.__wrnexusHydrateCsrFetches = hydrateCsrFetches;
window.__wrnexusDisposeBehaviors = disposeBehaviors;
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", function () {
hydrateScopes(document);
hydrateCsrFetches(document);
});
} else {
hydrateScopes(document);
hydrateCsrFetches(document);
}
})();
`.trim();