lightview
Version:
A reactive UI library with features of Bau, Juris, and HTMX plus safe LLM UI generation
1,386 lines • 139 kB
JavaScript
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