UNPKG

lightview

Version:

A reactive UI library with features of Bau, Juris, and HTMX plus safe LLM UI generation

1,386 lines 139 kB
var LightviewCDOM = function(exports) { "use strict"; const helpers = /* @__PURE__ */ new Map(); const helperOptions = /* @__PURE__ */ new Map(); const operators = { prefix: /* @__PURE__ */ new Map(), // e.g., '++' -> { helper: 'increment', precedence: 70 } postfix: /* @__PURE__ */ new Map(), // e.g., '++' -> { helper: 'increment', precedence: 70 } infix: /* @__PURE__ */ new Map() // e.g., '+' -> { helper: 'add', precedence: 50 } }; const DEFAULT_PRECEDENCE = { prefix: 80, postfix: 80, infix: 50 }; const registerHelper = (name, fn, options = {}) => { helpers.set(name, fn); if (globalThis.__LIGHTVIEW_INTERNALS__) { globalThis.__LIGHTVIEW_INTERNALS__.helpers.set(name, fn); } if (options) helperOptions.set(name, options); }; const registerOperator = (helperName, symbol, position, precedence, options = {}) => { var _a; if (!["prefix", "postfix", "infix"].includes(position)) { throw new Error(`Invalid operator position: ${position}. Must be 'prefix', 'postfix', or 'infix'.`); } if (!helpers.has(helperName)) { (_a = globalThis.console) == null ? void 0 : _a.warn(`LightviewCDOM: Operator "${symbol}" registered for helper "${helperName}" which is not yet registered.`); } const prec = precedence ?? DEFAULT_PRECEDENCE[position]; operators[position].set(symbol, { helper: helperName, precedence: prec, options }); }; const getLV = () => globalThis.Lightview || null; const getRegistry = () => { var _a; return ((_a = getLV()) == null ? void 0 : _a.registry) || null; }; class BindingTarget { constructor(parent, key) { this.parent = parent; this.key = key; this.isBindingTarget = true; } get value() { return this.parent[this.key]; } set value(v) { this.parent[this.key] = v; } get __parent__() { return this.parent; } } const unwrapSignal = (val) => { if (val && typeof val === "function" && "value" in val) { return val.value; } if (val && typeof val === "object" && !(globalThis.Node && val instanceof globalThis.Node) && "value" in val) { return val.value; } return val; }; const traverse = (root, segments) => { let current = root; for (const segment of segments) { if (!segment) continue; current = unwrapSignal(current); if (current == null) return void 0; const key = segment.startsWith("[") ? segment.slice(1, -1) : segment; current = current[key]; } return unwrapSignal(current); }; const traverseAsContext = (root, segments) => { let current = root; for (let i = 0; i < segments.length; i++) { const segment = segments[i]; if (!segment) continue; const key = segment.startsWith("[") ? segment.slice(1, -1) : segment; const unwrapped = unwrapSignal(current); if (unwrapped == null) return void 0; if (i === segments.length - 1) { return new BindingTarget(unwrapped, key); } current = unwrapped[key]; } return current; }; const resolvePath = (path, context) => { if (typeof path !== "string") return path; const registry = getRegistry(); if (path === ".") return unwrapSignal(context); if (path.startsWith("=/") || path.startsWith("/")) { const segments = path.startsWith("=/") ? path.slice(2).split("/") : path.slice(1).split("/"); const rootName = segments.shift(); const LV = getLV(); const root = LV ? LV.get(rootName, { scope: (context == null ? void 0 : context.__node__) || context }) : registry == null ? void 0 : registry.get(rootName); if (!root) return void 0; return traverse(root, segments); } if (path.startsWith("./")) { return traverse(context, path.slice(2).split("/")); } if (path.startsWith("../")) { return traverse(context == null ? void 0 : context.__parent__, path.slice(3).split("/")); } if (path.includes("/") || path.includes(".")) { const unwrapped = unwrapSignal(context); if (unwrapped && typeof unwrapped === "object" && path in unwrapped) { return unwrapSignal(unwrapped[path]); } return traverse(context, path.split(/[\/.]/)); } const unwrappedContext = unwrapSignal(context); if (unwrappedContext && typeof unwrappedContext === "object") { if (path in unwrappedContext || unwrappedContext[path] !== void 0) { return traverse(unwrappedContext, [path]); } } return path; }; const resolvePathAsContext = (path, context) => { if (typeof path !== "string") return path; const registry = getRegistry(); if (path === ".") return context; if (path.startsWith("=/") || path.startsWith("/")) { const segments = path.startsWith("=/") ? path.slice(2).split(/[/.]/) : path.slice(1).split(/[/.]/); const rootName = segments.shift(); const LV = getLV(); const root = LV ? LV.get(rootName, { scope: (context == null ? void 0 : context.__node__) || context }) : registry == null ? void 0 : registry.get(rootName); if (!root) return void 0; return traverseAsContext(root, segments); } if (path.startsWith("./")) { return traverseAsContext(context, path.slice(2).split(/[\/.]/)); } if (path.startsWith("../")) { return traverseAsContext(context == null ? void 0 : context.__parent__, path.slice(3).split(/[\/.]/)); } if (path.includes("/") || path.includes(".")) { const unwrapped = unwrapSignal(context); if (unwrapped && typeof unwrapped === "object" && path in unwrapped) { return new BindingTarget(unwrapped, path); } return traverseAsContext(context, path.split(/[\/.]/)); } const unwrappedContext = unwrapSignal(context); if (unwrappedContext && typeof unwrappedContext === "object") { if (/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(path)) { return new BindingTarget(unwrappedContext, path); } } return path; }; class LazyValue { constructor(fn) { this.fn = fn; this.isLazy = true; } resolve(context) { return this.fn(context); } } const isNode = (val) => val && typeof val === "object" && globalThis.Node && val instanceof globalThis.Node; const resolveArgument = (arg, context, globalMode = false) => { if (arg.startsWith("'") && arg.endsWith("'") || arg.startsWith('"') && arg.endsWith('"')) { return { value: arg.slice(1, -1), isLiteral: true }; } if (arg !== "" && !isNaN(Number(arg))) { return { value: Number(arg), isLiteral: true }; } if (arg === "true") return { value: true, isLiteral: true }; if (arg === "false") return { value: false, isLiteral: true }; if (arg === "null") return { value: null, isLiteral: true }; if (arg === "_" || arg.startsWith("_/") || arg.startsWith("_.")) { return { value: new LazyValue((item) => { if (arg === "_") return item; const path = arg.startsWith("_.") ? arg.slice(2) : arg.slice(2); return resolvePath(path, item); }), isLazy: true }; } if (arg === "$this" || arg.startsWith("$this/") || arg.startsWith("$this.")) { return { value: new LazyValue((context2) => { const node = (context2 == null ? void 0 : context2.__node__) || context2; if (arg === "$this") return node; const path = arg.startsWith("$this.") ? arg.slice(6) : arg.slice(6); return resolvePath(path, node); }), isLazy: true }; } if (arg === "$event" || arg.startsWith("$event/") || arg.startsWith("$event.")) { return { value: new LazyValue((context2) => { const event = (context2 == null ? void 0 : context2.$event) || (context2 == null ? void 0 : context2.event) || context2; if (arg === "$event") return event; const path = arg.startsWith("$event.") ? arg.slice(7) : arg.slice(7); return resolvePath(path, event); }), isLazy: true }; } if (arg.startsWith("{") || arg.startsWith("[")) { try { const data = parseJPRX(arg); const resolveTemplate = (node, context2) => { if (typeof node === "string") { if (node.startsWith("=")) { const res = resolveExpression$1(node, context2); const final = res instanceof LazyValue ? res.resolve(context2) : res; return unwrapSignal(final); } if (node === "$this" || node.startsWith("$this/") || node.startsWith("$this.")) { const path = node.startsWith("$this.") || node.startsWith("$this/") ? node.slice(6) : node.slice(6); const ctxNode = (context2 == null ? void 0 : context2.__node__) || context2; const res = node === "$this" ? ctxNode : resolvePath(path, ctxNode); return unwrapSignal(res); } if (node === "$event" || node.startsWith("$event/") || node.startsWith("$event.")) { const path = node.startsWith("$event.") || node.startsWith("$event/") ? node.slice(7) : node.slice(7); const event = (context2 == null ? void 0 : context2.$event) || (context2 == null ? void 0 : context2.event) || (context2 && !isNode(context2) ? context2 : null); const res = node === "$event" ? event : resolvePath(path, event); return unwrapSignal(res); } if (node === "_" || node.startsWith("_/") || node.startsWith("_.")) { const path = node.startsWith("_.") || node.startsWith("_/") ? node.slice(2) : node.slice(2); const res = node === "_" ? context2 : resolvePath(path, context2); return unwrapSignal(res); } if (node.startsWith("../")) return unwrapSignal(resolvePath(node, context2)); } if (Array.isArray(node)) return node.map((n) => resolveTemplate(n, context2)); if (node && typeof node === "object") { const res = {}; for (const k in node) res[k] = resolveTemplate(node[k], context2); return res; } return node; }; const hasReactive = (obj) => { if (typeof obj === "string") { return obj.startsWith("=") || obj.startsWith("_") || obj.startsWith("../"); } if (Array.isArray(obj)) return obj.some(hasReactive); if (obj && typeof obj === "object") return Object.values(obj).some(hasReactive); return false; }; if (hasReactive(data)) { return { value: new LazyValue((context2) => resolveTemplate(data, context2)), isLazy: true }; } return { value: data, isLiteral: true }; } catch (e) { } } if (arg.includes("(")) { let nestedExpr = arg; if (arg.startsWith("/")) { nestedExpr = "=" + arg; } else if (globalMode && !arg.startsWith("=") && !arg.startsWith("./")) { nestedExpr = `=/${arg}`; } const val = resolveExpression$1(nestedExpr, context); if (val instanceof LazyValue) { return { value: val, isLazy: true }; } return { value: val, isSignal: false }; } let normalizedPath; if (arg.startsWith("/")) { normalizedPath = "=" + arg; } else if (arg.startsWith("=") || arg.startsWith("./") || arg.startsWith("../")) { normalizedPath = arg; } else if (globalMode) { normalizedPath = `=/${arg}`; } else { normalizedPath = `./${arg}`; } const explosionIdx = arg.indexOf("..."); if (explosionIdx !== -1) { const normExplosionIdx = normalizedPath.indexOf("..."); const pathPart = normalizedPath.slice(0, normExplosionIdx); const propName = arg.slice(explosionIdx + 3); const parent = resolvePath(pathPart, context); const unwrappedParent = unwrapSignal(parent); if (Array.isArray(unwrappedParent)) { const values = unwrappedParent.map((item) => { const unwrappedItem = unwrapSignal(item); if (!propName) return unwrappedItem; return unwrappedItem && typeof unwrappedItem === "object" ? unwrapSignal(unwrappedItem[propName]) : void 0; }); return { value: values, isExplosion: true }; } else if (unwrappedParent && typeof unwrappedParent === "object") { if (!propName) return { value: unwrappedParent, isExplosion: true }; const val = unwrappedParent[propName]; return { value: unwrapSignal(val), isExplosion: true }; } return { value: void 0, isExplosion: true }; } const value = resolvePathAsContext(normalizedPath, context); return { value, isExplosion: false }; }; const TokenType = { PATH: "PATH", // $/user/age, ./name, ../parent LITERAL: "LITERAL", // 123, "hello", true, false, null OPERATOR: "OPERATOR", // +, -, *, /, ++, --, etc. LPAREN: "LPAREN", // ( RPAREN: "RPAREN", // ) COMMA: "COMMA", // , EXPLOSION: "EXPLOSION", // ... suffix PLACEHOLDER: "PLACEHOLDER", // _, _/path THIS: "THIS", // $this EVENT: "EVENT", // $event, $event.target LBRACE: "LBRACE", // { RBRACE: "RBRACE", // } LBRACKET: "LBRACKET", // [ RBRACKET: "RBRACKET", // ] COLON: "COLON", // : EOF: "EOF" }; const getOperatorSymbols = () => { const allOps = /* @__PURE__ */ new Set([ ...operators.prefix.keys(), ...operators.postfix.keys(), ...operators.infix.keys() ]); return [...allOps].sort((a, b) => b.length - a.length); }; const tokenize = (expr) => { var _a, _b; const tokens = []; let i = 0; const len2 = expr.length; const opSymbols = getOperatorSymbols(); while (i < len2) { if (/\s/.test(expr[i])) { i++; continue; } if (expr[i] === "=" && i === 0 && i + 1 < len2) { const prefixOps = [...operators.prefix.keys()].sort((a, b) => b.length - a.length); let matchedPrefix = null; for (const op of prefixOps) { if (expr.slice(i + 1, i + 1 + op.length) === op) { matchedPrefix = op; break; } } if (matchedPrefix) { i++; continue; } const next = expr[i + 1]; if (next === "/" || next === "." || /[a-zA-Z_$]/.test(next)) { i++; continue; } } if (expr[i] === "(") { tokens.push({ type: TokenType.LPAREN, value: "(" }); i++; continue; } if (expr[i] === ")") { tokens.push({ type: TokenType.RPAREN, value: ")" }); i++; continue; } if (expr[i] === ",") { tokens.push({ type: TokenType.COMMA, value: "," }); i++; continue; } if (expr[i] === "{") { tokens.push({ type: TokenType.LBRACE, value: "{" }); i++; continue; } if (expr[i] === "}") { tokens.push({ type: TokenType.RBRACE, value: "}" }); i++; continue; } if (expr[i] === "[") { tokens.push({ type: TokenType.LBRACKET, value: "[" }); i++; continue; } if (expr[i] === "]") { tokens.push({ type: TokenType.RBRACKET, value: "]" }); i++; continue; } if (expr[i] === ":") { tokens.push({ type: TokenType.COLON, value: ":" }); i++; continue; } let matchedOp = null; for (const op of opSymbols) { if (expr.slice(i, i + op.length) === op) { const before = i > 0 ? expr[i - 1] : " "; const after = i + op.length < len2 ? expr[i + op.length] : " "; const infixConf = operators.infix.get(op); const prefixConf = operators.prefix.get(op); const postfixConf = operators.postfix.get(op); if ((_a = infixConf == null ? void 0 : infixConf.options) == null ? void 0 : _a.requiresWhitespace) { if (!prefixConf && !postfixConf) { const isWhitespaceMatch = /\s/.test(before) && /\s/.test(after); if (!isWhitespaceMatch) continue; } } if (infixConf) { const lastTok = tokens[tokens.length - 1]; const isValueContext = lastTok && (lastTok.type === TokenType.PATH || lastTok.type === TokenType.LITERAL || lastTok.type === TokenType.RPAREN || lastTok.type === TokenType.PLACEHOLDER || lastTok.type === TokenType.THIS || lastTok.type === TokenType.EVENT); if (isValueContext) { matchedOp = op; break; } } const validBefore = /[\s)]/.test(before) || i === 0 || tokens.length === 0 || tokens[tokens.length - 1].type === TokenType.LPAREN || tokens[tokens.length - 1].type === TokenType.COMMA || tokens[tokens.length - 1].type === TokenType.OPERATOR; const validAfter = /[\s(=./'"0-9_]/.test(after) || i + op.length >= len2 || opSymbols.some((o) => expr.slice(i + op.length).startsWith(o)); if (validBefore || validAfter) { matchedOp = op; break; } } } if (matchedOp) { tokens.push({ type: TokenType.OPERATOR, value: matchedOp }); i += matchedOp.length; continue; } if (expr[i] === '"' || expr[i] === "'") { const quote = expr[i]; let str = ""; i++; while (i < len2 && expr[i] !== quote) { if (expr[i] === "\\" && i + 1 < len2) { i++; if (expr[i] === "n") str += "\n"; else if (expr[i] === "t") str += " "; else str += expr[i]; } else { str += expr[i]; } i++; } i++; tokens.push({ type: TokenType.LITERAL, value: str }); continue; } if (/\d/.test(expr[i]) || expr[i] === "-" && /\d/.test(expr[i + 1]) && (tokens.length === 0 || tokens[tokens.length - 1].type === TokenType.OPERATOR || tokens[tokens.length - 1].type === TokenType.LPAREN || tokens[tokens.length - 1].type === TokenType.COMMA)) { let num = ""; if (expr[i] === "-") { num = "-"; i++; } while (i < len2 && /[\d.]/.test(expr[i])) { num += expr[i]; i++; } tokens.push({ type: TokenType.LITERAL, value: parseFloat(num) }); continue; } if (expr[i] === "_" && (i + 1 >= len2 || !/[a-zA-Z0-9]/.test(expr[i + 1]) || expr[i + 1] === "/" || expr[i + 1] === ".")) { let placeholder = "_"; i++; if (i < len2 && (expr[i] === "/" || expr[i] === ".")) { while (i < len2 && !/[\s,)(]/.test(expr[i])) { placeholder += expr[i]; i++; } } tokens.push({ type: TokenType.PLACEHOLDER, value: placeholder }); continue; } if (expr.slice(i, i + 5) === "$this") { let thisPath = "$this"; i += 5; while (i < len2 && /[a-zA-Z0-9_./]/.test(expr[i])) { thisPath += expr[i]; i++; } tokens.push({ type: TokenType.THIS, value: thisPath }); continue; } if (expr.slice(i, i + 6) === "$event") { let eventPath = "$event"; i += 6; while (i < len2 && /[a-zA-Z0-9_./]/.test(expr[i])) { eventPath += expr[i]; i++; } tokens.push({ type: TokenType.EVENT, value: eventPath }); continue; } if (expr[i] === "=" || expr[i] === "." || expr[i] === "/") { let path = ""; while (i < len2) { let isOp = false; for (const op of opSymbols) { if (expr.slice(i, i + op.length) === op) { const infixConf = operators.infix.get(op); const prefixConf = operators.prefix.get(op); const postfixConf = operators.postfix.get(op); if ((_b = infixConf == null ? void 0 : infixConf.options) == null ? void 0 : _b.requiresWhitespace) { if (!prefixConf && !postfixConf) { const after = i + op.length < len2 ? expr[i + op.length] : " "; if (/\s/.test(expr[i - 1]) && /\s/.test(after)) { isOp = true; break; } continue; } } if (path.length > 0 && path[path.length - 1] !== "/") { isOp = true; break; } } } if (isOp) break; if (/[\s,()]/.test(expr[i])) break; if (expr.slice(i, i + 3) === "...") { break; } path += expr[i]; i++; } if (expr.slice(i, i + 3) === "...") { tokens.push({ type: TokenType.PATH, value: path }); tokens.push({ type: TokenType.EXPLOSION, value: "..." }); i += 3; } else { tokens.push({ type: TokenType.PATH, value: path }); } continue; } if (/[a-zA-Z]/.test(expr[i])) { let ident = ""; while (i < len2 && /[a-zA-Z0-9_]/.test(expr[i])) { ident += expr[i]; i++; } if (ident === "true") tokens.push({ type: TokenType.LITERAL, value: true }); else if (ident === "false") tokens.push({ type: TokenType.LITERAL, value: false }); else if (ident === "null") tokens.push({ type: TokenType.LITERAL, value: null }); else tokens.push({ type: TokenType.PATH, value: ident }); continue; } i++; } tokens.push({ type: TokenType.EOF, value: null }); return tokens; }; const hasOperatorSyntax = (expr) => { if (!expr || typeof expr !== "string") return false; if (/^=?(\+\+|--|!!)\/?/.test(expr)) { return true; } if (/(\+\+|--)$/.test(expr)) { return true; } if (/\s+([+\-*/%]|>|<|>=|<=|!=|===|==|=)\s+/.test(expr)) { return true; } if (/[^=\s]([+%=]|==|===|!=|!==|<=|>=|<|>)[^=\s]/.test(expr)) { return true; } return false; }; class PrattParser { constructor(tokens, context, isGlobalMode = false) { this.tokens = tokens; this.pos = 0; this.context = context; this.isGlobalMode = isGlobalMode; } peek() { return this.tokens[this.pos] || { type: TokenType.EOF, value: null }; } consume() { return this.tokens[this.pos++]; } expect(type) { const tok = this.consume(); if (tok.type !== type) { throw new Error(`JPRX: Expected ${type} but got ${tok.type}`); } return tok; } /** * Get binding power (precedence) for an infix or postfix operator. */ getInfixPrecedence(op) { const infixInfo = operators.infix.get(op); if (infixInfo) return infixInfo.precedence; const postfixInfo = operators.postfix.get(op); if (postfixInfo) return postfixInfo.precedence; return 0; } /** * Parse an expression with given minimum precedence. */ parseExpression(minPrecedence = 0) { let left = this.parsePrefix(); let tok = this.peek(); while (tok.type === TokenType.OPERATOR) { const prec = this.getInfixPrecedence(tok.value); if (prec < minPrecedence) break; if (operators.postfix.has(tok.value) && !operators.infix.has(tok.value)) { this.consume(); left = { type: "Postfix", operator: tok.value, operand: left }; tok = this.peek(); continue; } if (operators.infix.has(tok.value)) { this.consume(); const right = this.parseExpression(prec + 1); left = { type: "Infix", operator: tok.value, left, right }; tok = this.peek(); continue; } if (!operators.postfix.has(tok.value) && !operators.infix.has(tok.value)) { break; } this.consume(); const nextTok = this.peek(); if (nextTok.type === TokenType.PATH || nextTok.type === TokenType.LITERAL || nextTok.type === TokenType.LPAREN || nextTok.type === TokenType.PLACEHOLDER || nextTok.type === TokenType.EVENT || nextTok.type === TokenType.OPERATOR && operators.prefix.has(nextTok.value)) { const right = this.parseExpression(prec + 1); left = { type: "Infix", operator: tok.value, left, right }; } else { left = { type: "Postfix", operator: tok.value, operand: left }; } tok = this.peek(); } return left; } /** * Parse a prefix expression (literals, paths, prefix operators, groups). */ parsePrefix() { const tok = this.peek(); if (tok.type === TokenType.OPERATOR && operators.prefix.has(tok.value)) { this.consume(); const prefixInfo = operators.prefix.get(tok.value); const operand = this.parseExpression(prefixInfo.precedence); return { type: "Prefix", operator: tok.value, operand }; } if (tok.type === TokenType.LPAREN) { this.consume(); const inner = this.parseExpression(0); this.expect(TokenType.RPAREN); return inner; } if (tok.type === TokenType.LITERAL) { this.consume(); return { type: "Literal", value: tok.value }; } if (tok.type === TokenType.PLACEHOLDER) { this.consume(); return { type: "Placeholder", value: tok.value }; } if (tok.type === TokenType.THIS) { this.consume(); return { type: "This", value: tok.value }; } if (tok.type === TokenType.EVENT) { this.consume(); return { type: "Event", value: tok.value }; } if (tok.type === TokenType.PATH) { this.consume(); const nextTok = this.peek(); if (nextTok.type === TokenType.EXPLOSION) { this.consume(); return { type: "Explosion", path: tok.value }; } if (nextTok.type === TokenType.LPAREN) { this.consume(); const args = []; while (this.peek().type !== TokenType.RPAREN && this.peek().type !== TokenType.EOF) { args.push(this.parseExpression(0)); if (this.peek().type === TokenType.COMMA) { this.consume(); } } this.expect(TokenType.RPAREN); return { type: "Call", helper: tok.value, args }; } return { type: "Path", value: tok.value }; } if (tok.type === TokenType.LBRACE) { return this.parseObjectLiteral(); } if (tok.type === TokenType.LBRACKET) { return this.parseArrayLiteral(); } if (tok.type === TokenType.EOF) { return { type: "Literal", value: void 0 }; } throw new Error(`JPRX: Unexpected token ${tok.type}: ${tok.value}`); } parseObjectLiteral() { this.consume(); const properties = {}; while (this.peek().type !== TokenType.RBRACE && this.peek().type !== TokenType.EOF) { const keyTok = this.consume(); let key; if (keyTok.type === TokenType.LITERAL) key = String(keyTok.value); else if (keyTok.type === TokenType.PATH) key = keyTok.value; else if (keyTok.type === TokenType.PATH) key = keyTok.value; else throw new Error(`JPRX: Expected property name but got ${keyTok.type}`); this.expect(TokenType.COLON); const value = this.parseExpression(0); properties[key] = value; if (this.peek().type === TokenType.COMMA) { this.consume(); } else if (this.peek().type !== TokenType.RBRACE) { break; } } this.expect(TokenType.RBRACE); return { type: "ObjectLiteral", properties }; } parseArrayLiteral() { this.consume(); const elements = []; while (this.peek().type !== TokenType.RBRACKET && this.peek().type !== TokenType.EOF) { const value = this.parseExpression(0); elements.push(value); if (this.peek().type === TokenType.COMMA) { this.consume(); } else if (this.peek().type !== TokenType.RBRACKET) { break; } } this.expect(TokenType.RBRACKET); return { type: "ArrayLiteral", elements }; } } const evaluateAST = (ast, context, forMutation = false) => { var _a; if (!ast) return void 0; switch (ast.type) { case "Literal": return ast.value; case "Path": { const resolved = forMutation ? resolvePathAsContext(ast.value, context) : resolvePath(ast.value, context); return forMutation ? resolved : unwrapSignal(resolved); } case "Placeholder": { return new LazyValue((item) => { if (ast.value === "_") return item; const path = ast.value.startsWith("_.") ? ast.value.slice(2) : ast.value.slice(2); return resolvePath(path, item); }); } case "This": { return new LazyValue((context2) => { const node = (context2 == null ? void 0 : context2.__node__) || context2; if (ast.value === "$this") return node; const path = ast.value.startsWith("$this.") ? ast.value.slice(6) : ast.value.slice(6); return resolvePath(path, node); }); } case "Event": { return new LazyValue((context2) => { const event = (context2 == null ? void 0 : context2.$event) || (context2 == null ? void 0 : context2.event) || context2; if (ast.value === "$event") return event; const path = ast.value.startsWith("$event.") ? ast.value.slice(7) : ast.value.slice(7); return resolvePath(path, event); }); } case "ObjectLiteral": { const res = {}; let hasLazy = false; for (const key in ast.properties) { const val = evaluateAST(ast.properties[key], context, forMutation); if (val && val.isLazy) hasLazy = true; res[key] = val; } if (hasLazy) { return new LazyValue((ctx) => { const resolved = {}; for (const key in res) { resolved[key] = res[key] && res[key].isLazy ? res[key].resolve(ctx) : unwrapSignal(res[key]); } return resolved; }); } return res; } case "ArrayLiteral": { const elements = ast.elements.map((el) => evaluateAST(el, context, forMutation)); const hasLazy = elements.some((el) => el && el.isLazy); if (hasLazy) { return new LazyValue((ctx) => { return elements.map((el) => el && el.isLazy ? el.resolve(ctx) : unwrapSignal(el)); }); } return elements.map((el) => unwrapSignal(el)); } case "Prefix": { const opInfo = operators.prefix.get(ast.operator); if (!opInfo) throw new Error(`JPRX: Unknown prefix operator: ${ast.operator}`); const helper = helpers.get(opInfo.helper); if (!helper) throw new Error(`JPRX: Helper "${opInfo.helper}" for operator "${ast.operator}" not found.`); const opts = helperOptions.get(opInfo.helper) || {}; const operand = evaluateAST(ast.operand, context, opts.pathAware); if (operand && operand.isLazy && !opts.lazyAware) { return new LazyValue((ctx) => { const resolved = operand.resolve(ctx); return helper(opts.pathAware ? resolved : unwrapSignal(resolved)); }); } return helper(opts.pathAware ? operand : unwrapSignal(operand)); } case "Postfix": { const opInfo = operators.postfix.get(ast.operator); if (!opInfo) throw new Error(`JPRX: Unknown postfix operator: ${ast.operator}`); const helper = helpers.get(opInfo.helper); if (!helper) throw new Error(`JPRX: Helper "${opInfo.helper}" for operator "${ast.operator}" not found.`); const opts = helperOptions.get(opInfo.helper) || {}; const operand = evaluateAST(ast.operand, context, opts.pathAware); if (operand && operand.isLazy && !opts.lazyAware) { return new LazyValue((ctx) => { const resolved = operand.resolve(ctx); return helper(opts.pathAware ? resolved : unwrapSignal(resolved)); }); } return helper(opts.pathAware ? operand : unwrapSignal(operand)); } case "Infix": { const opInfo = operators.infix.get(ast.operator); if (!opInfo) throw new Error(`JPRX: Unknown infix operator: ${ast.operator}`); const helper = helpers.get(opInfo.helper); if (!helper) throw new Error(`JPRX: Helper "${opInfo.helper}" for operator "${ast.operator}" not found.`); const opts = helperOptions.get(opInfo.helper) || {}; const left = evaluateAST(ast.left, context, opts.pathAware); const right = evaluateAST(ast.right, context, false); if ((left && left.isLazy || right && right.isLazy) && !opts.lazyAware) { return new LazyValue((ctx) => { const l = left && left.isLazy ? left.resolve(ctx) : left; const r = right && right.isLazy ? right.resolve(ctx) : right; return helper(opts.pathAware ? l : unwrapSignal(l), unwrapSignal(r)); }); } return helper(opts.pathAware ? left : unwrapSignal(left), unwrapSignal(right)); } case "Call": { const helperName = ast.helper.replace(/^=/, ""); const helper = helpers.get(helperName); if (!helper) { (_a = globalThis.console) == null ? void 0 : _a.warn(`JPRX: Helper "${helperName}" not found.`); return void 0; } const opts = helperOptions.get(helperName) || {}; const args = ast.args.map((arg, i) => evaluateAST(arg, context, opts.pathAware && i === 0)); const hasLazy = args.some((arg) => arg && arg.isLazy); if (hasLazy && !opts.lazyAware) { return new LazyValue((ctx) => { const finalArgs2 = args.map((arg, i) => { const val = arg && arg.isLazy ? arg.resolve(ctx) : arg; if (ast.args[i].type === "Explosion" && Array.isArray(val)) { return val.map((v) => unwrapSignal(v)); } return opts.pathAware && i === 0 ? val : unwrapSignal(val); }); const flatArgs = []; for (let i = 0; i < finalArgs2.length; i++) { if (ast.args[i].type === "Explosion" && Array.isArray(finalArgs2[i])) { flatArgs.push(...finalArgs2[i]); } else { flatArgs.push(finalArgs2[i]); } } return helper.apply((context == null ? void 0 : context.__node__) || null, flatArgs); }); } const finalArgs = []; for (let i = 0; i < args.length; i++) { const arg = args[i]; if (ast.args[i].type === "Explosion" && Array.isArray(arg)) { finalArgs.push(...arg.map((v) => unwrapSignal(v))); } else { finalArgs.push(opts.pathAware && i === 0 ? arg : unwrapSignal(arg)); } } return helper.apply((context == null ? void 0 : context.__node__) || null, finalArgs); } case "Explosion": { const result = resolveArgument(ast.path + "...", context, false); return result.value; } default: throw new Error(`JPRX: Unknown AST node type: ${ast.type}`); } }; const parseWithPratt = (expr, context) => { const tokens = tokenize(expr); const parser = new PrattParser(tokens, context); const ast = parser.parseExpression(0); return evaluateAST(ast, context); }; const resolveExpression$1 = (expr, context) => { var _a, _b; if (typeof expr !== "string") return expr; if (hasOperatorSyntax(expr)) { try { return parseWithPratt(expr, context); } catch (e) { (_a = globalThis.console) == null ? void 0 : _a.warn("JPRX: Pratt parser failed, falling back to legacy:", e.message); } } const funcStart = expr.indexOf("("); if (funcStart !== -1 && expr.endsWith(")")) { const fullPath = expr.slice(0, funcStart).trim(); const argsStr = expr.slice(funcStart + 1, -1); const segments = fullPath.split("/"); let funcName = segments.pop().replace(/^=/, ""); if (funcName === "" && (segments.length > 0 || fullPath === "/")) { funcName = "/"; } const navPath = segments.join("/"); const isGlobalExpr = expr.startsWith("=/") || expr.startsWith("="); let baseContext = context; if (navPath && navPath !== "=") { baseContext = resolvePathAsContext(navPath, context); } const helper = helpers.get(funcName); if (!helper) { (_b = globalThis.console) == null ? void 0 : _b.warn(`LightviewCDOM: Helper "${funcName}" not found.`); return expr; } const options = helperOptions.get(funcName) || {}; const argsList = []; let current = "", parenDepth = 0, braceDepth = 0, bracketDepth = 0, quote = null; for (let i = 0; i < argsStr.length; i++) { const char = argsStr[i]; if (char === quote) quote = null; else if (!quote && (char === "'" || char === '"')) quote = char; else if (!quote && char === "(") parenDepth++; else if (!quote && char === ")") parenDepth--; else if (!quote && char === "{") braceDepth++; else if (!quote && char === "}") braceDepth--; else if (!quote && char === "[") bracketDepth++; else if (!quote && char === "]") bracketDepth--; else if (!quote && char === "," && parenDepth === 0 && braceDepth === 0 && bracketDepth === 0) { argsList.push(current.trim()); current = ""; continue; } current += char; } if (current) argsList.push(current.trim()); const resolvedArgs = []; let hasLazy = false; for (let i = 0; i < argsList.length; i++) { const arg = argsList[i]; const useGlobalMode = isGlobalExpr && (navPath === "=" || !navPath); const res = resolveArgument(arg, baseContext, useGlobalMode); if (res.isLazy) hasLazy = true; const shouldUnwrap = !(options.pathAware && i === 0); let val = res.value; if (shouldUnwrap && !(val && val.isLazy)) { val = unwrapSignal(val); } if (res.isExplosion && Array.isArray(val)) { resolvedArgs.push(...val.map((v) => shouldUnwrap && !(v && v.isLazy) ? unwrapSignal(v) : v)); } else { resolvedArgs.push(val); } } if (hasLazy && !options.lazyAware) { return new LazyValue((contextOverride) => { const finalArgs = resolvedArgs.map((arg, i) => { const shouldUnwrap = !(options.pathAware && i === 0); const resolved = arg instanceof LazyValue ? arg.resolve(contextOverride) : arg; return shouldUnwrap ? unwrapSignal(resolved) : resolved; }); return helper(...finalArgs); }); } const result = helper.apply((context == null ? void 0 : context.__node__) || null, resolvedArgs); return unwrapSignal(result); } return unwrapSignal(resolvePath(expr, context)); }; const parseExpression = (expr, context) => { const LV = getLV(); if (!LV || typeof expr !== "string") return expr; return LV.computed(() => resolveExpression$1(expr, context)); }; const parseCDOMC = (input) => { if (typeof input !== "string") return input; let i = 0; const len2 = input.length; const skipWhitespace = () => { while (i < len2) { const char = input[i]; if (/\s/.test(char)) { i++; continue; } if (char === "/") { const next = input[i + 1]; if (next === "/") { i += 2; while (i < len2 && input[i] !== "\n" && input[i] !== "\r") i++; continue; } else if (next === "*") { i += 2; while (i < len2) { if (input[i] === "*" && input[i + 1] === "/") { i += 2; break; } i++; } continue; } } break; } }; const parseString = () => { const quote = input[i++]; let res2 = ""; while (i < len2) { const char = input[i++]; if (char === quote) return res2; if (char === "\\") { const next = input[i++]; if (next === "n") res2 += "\n"; else if (next === "t") res2 += " "; else if (next === '"') res2 += '"'; else if (next === "'") res2 += "'"; else if (next === "\\") res2 += "\\"; else res2 += next; } else { res2 += char; } } throw new Error("Unterminated string"); }; const parseWord = () => { const start = i; let pDepth = 0; let bDepth = 0; let brDepth = 0; let quote = null; const startChar = input[start]; const isExpression = startChar === "=" || startChar === "#"; while (i < len2) { const char = input[i]; if (quote) { if (char === quote && input[i - 1] !== "\\") quote = null; i++; continue; } else if (char === '"' || char === "'" || char === "`") { quote = char; i++; continue; } if (char === "(") { pDepth++; i++; continue; } if (char === "{") { bDepth++; i++; continue; } if (char === "[") { brDepth++; i++; continue; } if (char === ")") { if (pDepth > 0) { pDepth--; i++; continue; } } if (char === "}") { if (bDepth > 0) { bDepth--; i++; continue; } } if (char === "]") { if (brDepth > 0) { brDepth--; i++; continue; } } if (pDepth === 0 && bDepth === 0 && brDepth === 0) { if (isExpression) { if (/[{}[\]"'`()]/.test(char)) { break; } if (char === ",") { break; } if (/[\s:]/.test(char)) { let j = i + 1; while (j < len2 && /\s/.test(input[j])) j++; if (j < len2) { const nextChar = input[j]; if (nextChar === "}" || nextChar === ",") { break; } let wordStart = j; while (j < len2 && /[a-zA-Z0-9_$-]/.test(input[j])) j++; if (j > wordStart) { while (j < len2 && /\s/.test(input[j])) j++; if (j < len2 && input[j] === ":") { break; } } } } } else { if (/[:,{}[\]"'`()\s]/.test(char)) { break; } } } i++; } const word = input.slice(start, i); if (word.startsWith("=") || word.startsWith("#")) { return word; } if (word === "true") return true; if (word === "false") return false; if (word === "null") return null; if (word.trim() !== "" && !isNaN(Number(word))) return Number(word); return word; }; const parseValue = () => { skipWhitespace(); if (i >= len2) return void 0; const char = input[i]; if (char === "{") return parseObject(); if (char === "[") return parseArray(); if (char === '"' || char === "'") return parseString(); return parseWord(); }; const parseObject = () => { i++; const obj = {}; skipWhitespace(); if (i < len2 && input[i] === "}") { i++; return obj; } while (i < len2) { skipWhitespace(); let key; if (input[i] === '"' || input[i] === "'") key = parseString(); else key = parseWord(); skipWhitespace(); if (input[i] !== ":") throw new Error(`Expected ':' at position ${i}, found '${input[i]}'`); i++; const value = parseValue(); obj[String(key)] = value; skipWhitespace(); if (input[i] === "}") { i++; return obj; } if (input[i] === ",") { i++; skipWhitespace(); if (input[i] === "}") { i++; return obj; } continue; } throw new Error(`Expected '}' or ',' at position ${i}, found '${input[i]}'`); } }; const parseArray = () => { i++; const arr = []; skipWhitespace(); if (i < len2 && input[i] === "]") { i++; return arr; } while (i < len2) { const val = parseValue(); arr.push(val); skipWhitespace(); if (input[i] === "]") { i++; return arr; } if (input[i] === ",") { i++; skipWhitespace(); if (input[i] === "]") { i++; return arr; } continue; } throw new Error(`Expected ']' or ',' at position ${i}, found '${input[i]}'`); } }; skipWhitespace(); const res = parseValue(); return res; }; const parseJPRX = (input) => { var _a, _b; if (typeof input !== "string") return input; let result = ""; let i = 0; const len2 = input.length; while (i < len2) { const char = input[i]; if (char === "/" && input[i + 1] === "/") { while (i < len2 && input[i] !== "\n") i++; continue; } if (char === "/" && input[i + 1] === "*") { i += 2; while (i < len2 && !(input[i] === "*" && input[i + 1] === "/")) i++; i += 2; continue; } if (char === '"' || char === "'") { const quote = char; result += '"'; i++; while (i < len2 && input[i] !== quote) { const c = input[i]; if (c === "\\") { result += "\\"; i++; if (i < len2) { const next = input[i]; if (next === '"') result += '\\"'; else result += next; i++; } } else if (c === '"') { result += '\\"'; i++; } else if (c === "\n") { result += "\\n"; i++; } else if (c === "\r") { result += "\\r"; i++; } else if (c === " ") { result += "\\t"; i++; } else { result += c; i++; } } result += '"'; i++; continue; } if ((char === "=" || char === "#") && input[i + 1] === "(") { const prefix = char; let expr = prefix; i++; let parenDepth = 0; let inExprQuote = null; while (i < len2) { const c = input[i]; if (inExprQuote) { if (c === inExprQuote && input[i - 1] !== "\\") inExprQuote = null; } else if (c === '"' || c === "'") { inExprQuote = c; } else { if (c === "(") parenDepth++; else if (c === ")") { parenDepth--; if (parenDepth === 0) { expr += c; i++; break; } } } expr += c; i++; } result += JSON.stringify(expr); continue; } if (char === "=") { let expr = ""; let parenDepth = 0; let braceDepth = 0; let bracketDepth = 0; let inExprQuote = null; while (i < len2) { const c = input[i]; if (inExprQuote) { if (c === inExprQuote && input[i - 1] !== "\\") inExprQuote = null; } else if (c === '"' || c === "'") { inExprQuote = c; } else { if (parenDepth === 0 && braceDepth === 0 && bracketDepth === 0) { if (/[}[\]:]/.test(c) && expr.length > 1) break; if (c === ",") break; if (/\s/.test(c)) { let j = i + 1; while (j < len2 && /\s/.test(input[j])) j++; if (j < len2) { const nextChar = input[j]; if (nextChar === "}" || nextChar === "," || nextChar === "]") { break; } let wordStart = j; while (j < len2 && /[a-zA-Z0-9_$-]/.test(input[j])) j++; if (j > wordStart) { while (j < len2 && /\s/.test(input[j])) j++; if (j < len2 && input[j] === ":") { break; } } } } } if (c === "(") parenDepth++; else if (c === ")") parenDepth--; else if (c === "{") braceDepth++; else if (c === "}") braceDepth--; else if (c === "[") bracketDepth++; else if (c === "]") bracketDepth--; } expr += c; i++; } result += JSON.stringify(expr); continue; } if (char === "#") { let expr = ""; let parenDepth = 0; let bracketDepth = 0; let inExprQuote = null; while (i < len2) { const c = input[i]; if (inExprQuote) { if (c === in