/** * 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-="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: * * {item.title} */ 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();