lightview 2.3.5 → 2.3.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lightview-cdom.js CHANGED
@@ -22,7 +22,7 @@ var LightviewCDOM = function(exports) {
22
22
  }
23
23
  if (options) helperOptions.set(name, options);
24
24
  };
25
- const registerOperator = (helperName, symbol, position, precedence) => {
25
+ const registerOperator = (helperName, symbol, position, precedence, options = {}) => {
26
26
  var _a;
27
27
  if (!["prefix", "postfix", "infix"].includes(position)) {
28
28
  throw new Error(`Invalid operator position: ${position}. Must be 'prefix', 'postfix', or 'infix'.`);
@@ -31,7 +31,7 @@ var LightviewCDOM = function(exports) {
31
31
  (_a = globalThis.console) == null ? void 0 : _a.warn(`LightviewCDOM: Operator "${symbol}" registered for helper "${helperName}" which is not yet registered.`);
32
32
  }
33
33
  const prec = precedence ?? DEFAULT_PRECEDENCE[position];
34
- operators[position].set(symbol, { helper: helperName, precedence: prec });
34
+ operators[position].set(symbol, { helper: helperName, precedence: prec, options });
35
35
  };
36
36
  const getLV = () => globalThis.Lightview || null;
37
37
  const getRegistry = () => {
@@ -93,12 +93,13 @@ var LightviewCDOM = function(exports) {
93
93
  if (typeof path !== "string") return path;
94
94
  const registry = getRegistry();
95
95
  if (path === ".") return unwrapSignal(context);
96
- if (path.startsWith("=/")) {
97
- const [rootName, ...rest] = path.slice(2).split("/");
96
+ if (path.startsWith("=/") || path.startsWith("/")) {
97
+ const segments = path.startsWith("=/") ? path.slice(2).split("/") : path.slice(1).split("/");
98
+ const rootName = segments.shift();
98
99
  const LV = getLV();
99
100
  const root = LV ? LV.get(rootName, { scope: (context == null ? void 0 : context.__node__) || context }) : registry == null ? void 0 : registry.get(rootName);
100
101
  if (!root) return void 0;
101
- return traverse(root, rest);
102
+ return traverse(root, segments);
102
103
  }
103
104
  if (path.startsWith("./")) {
104
105
  return traverse(context, path.slice(2).split("/"));
@@ -107,6 +108,10 @@ var LightviewCDOM = function(exports) {
107
108
  return traverse(context == null ? void 0 : context.__parent__, path.slice(3).split("/"));
108
109
  }
109
110
  if (path.includes("/") || path.includes(".")) {
111
+ const unwrapped = unwrapSignal(context);
112
+ if (unwrapped && typeof unwrapped === "object" && path in unwrapped) {
113
+ return unwrapSignal(unwrapped[path]);
114
+ }
110
115
  return traverse(context, path.split(/[\/.]/));
111
116
  }
112
117
  const unwrappedContext = unwrapSignal(context);
@@ -121,8 +126,8 @@ var LightviewCDOM = function(exports) {
121
126
  if (typeof path !== "string") return path;
122
127
  const registry = getRegistry();
123
128
  if (path === ".") return context;
124
- if (path.startsWith("=/")) {
125
- const segments = path.slice(2).split(/[/.]/);
129
+ if (path.startsWith("=/") || path.startsWith("/")) {
130
+ const segments = path.startsWith("=/") ? path.slice(2).split(/[/.]/) : path.slice(1).split(/[/.]/);
126
131
  const rootName = segments.shift();
127
132
  const LV = getLV();
128
133
  const root = LV ? LV.get(rootName, { scope: (context == null ? void 0 : context.__node__) || context }) : registry == null ? void 0 : registry.get(rootName);
@@ -136,6 +141,10 @@ var LightviewCDOM = function(exports) {
136
141
  return traverseAsContext(context == null ? void 0 : context.__parent__, path.slice(3).split(/[\/.]/));
137
142
  }
138
143
  if (path.includes("/") || path.includes(".")) {
144
+ const unwrapped = unwrapSignal(context);
145
+ if (unwrapped && typeof unwrapped === "object" && path in unwrapped) {
146
+ return new BindingTarget(unwrapped, path);
147
+ }
139
148
  return traverseAsContext(context, path.split(/[\/.]/));
140
149
  }
141
150
  const unwrappedContext = unwrapSignal(context);
@@ -321,6 +330,16 @@ var LightviewCDOM = function(exports) {
321
330
  // $this
322
331
  EVENT: "EVENT",
323
332
  // $event, $event.target
333
+ LBRACE: "LBRACE",
334
+ // {
335
+ RBRACE: "RBRACE",
336
+ // }
337
+ LBRACKET: "LBRACKET",
338
+ // [
339
+ RBRACKET: "RBRACKET",
340
+ // ]
341
+ COLON: "COLON",
342
+ // :
324
343
  EOF: "EOF"
325
344
  };
326
345
  const getOperatorSymbols = () => {
@@ -332,6 +351,7 @@ var LightviewCDOM = function(exports) {
332
351
  return [...allOps].sort((a, b) => b.length - a.length);
333
352
  };
334
353
  const tokenize = (expr) => {
354
+ var _a, _b;
335
355
  const tokens = [];
336
356
  let i = 0;
337
357
  const len2 = expr.length;
@@ -341,16 +361,21 @@ var LightviewCDOM = function(exports) {
341
361
  i++;
342
362
  continue;
343
363
  }
344
- if (expr[i] === "=" && i + 1 < len2) {
364
+ if (expr[i] === "=" && i === 0 && i + 1 < len2) {
345
365
  const prefixOps = [...operators.prefix.keys()].sort((a, b) => b.length - a.length);
346
- let isPrefixOp = false;
366
+ let matchedPrefix = null;
347
367
  for (const op of prefixOps) {
348
368
  if (expr.slice(i + 1, i + 1 + op.length) === op) {
349
- isPrefixOp = true;
369
+ matchedPrefix = op;
350
370
  break;
351
371
  }
352
372
  }
353
- if (isPrefixOp) {
373
+ if (matchedPrefix) {
374
+ i++;
375
+ continue;
376
+ }
377
+ const next = expr[i + 1];
378
+ if (next === "/" || next === "." || /[a-zA-Z_$]/.test(next)) {
354
379
  i++;
355
380
  continue;
356
381
  }
@@ -370,20 +395,52 @@ var LightviewCDOM = function(exports) {
370
395
  i++;
371
396
  continue;
372
397
  }
398
+ if (expr[i] === "{") {
399
+ tokens.push({ type: TokenType.LBRACE, value: "{" });
400
+ i++;
401
+ continue;
402
+ }
403
+ if (expr[i] === "}") {
404
+ tokens.push({ type: TokenType.RBRACE, value: "}" });
405
+ i++;
406
+ continue;
407
+ }
408
+ if (expr[i] === "[") {
409
+ tokens.push({ type: TokenType.LBRACKET, value: "[" });
410
+ i++;
411
+ continue;
412
+ }
413
+ if (expr[i] === "]") {
414
+ tokens.push({ type: TokenType.RBRACKET, value: "]" });
415
+ i++;
416
+ continue;
417
+ }
418
+ if (expr[i] === ":") {
419
+ tokens.push({ type: TokenType.COLON, value: ":" });
420
+ i++;
421
+ continue;
422
+ }
373
423
  let matchedOp = null;
374
424
  for (const op of opSymbols) {
375
425
  if (expr.slice(i, i + op.length) === op) {
376
426
  const before = i > 0 ? expr[i - 1] : " ";
377
427
  const after = i + op.length < len2 ? expr[i + op.length] : " ";
378
- const isInfix = operators.infix.has(op);
379
- const isPrefix = operators.prefix.has(op);
380
- const isPostfix = operators.postfix.has(op);
381
- if (isInfix && !isPrefix && !isPostfix) {
382
- if (/\s/.test(before) && /\s/.test(after)) {
428
+ const infixConf = operators.infix.get(op);
429
+ const prefixConf = operators.prefix.get(op);
430
+ const postfixConf = operators.postfix.get(op);
431
+ if ((_a = infixConf == null ? void 0 : infixConf.options) == null ? void 0 : _a.requiresWhitespace) {
432
+ if (!prefixConf && !postfixConf) {
433
+ const isWhitespaceMatch = /\s/.test(before) && /\s/.test(after);
434
+ if (!isWhitespaceMatch) continue;
435
+ }
436
+ }
437
+ if (infixConf) {
438
+ const lastTok = tokens[tokens.length - 1];
439
+ 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);
440
+ if (isValueContext) {
383
441
  matchedOp = op;
384
442
  break;
385
443
  }
386
- continue;
387
444
  }
388
445
  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;
389
446
  const validAfter = /[\s(=./'"0-9_]/.test(after) || i + op.length >= len2 || opSymbols.some((o) => expr.slice(i + op.length).startsWith(o));
@@ -468,16 +525,18 @@ var LightviewCDOM = function(exports) {
468
525
  let isOp = false;
469
526
  for (const op of opSymbols) {
470
527
  if (expr.slice(i, i + op.length) === op) {
471
- const isInfix = operators.infix.has(op);
472
- const isPrefix = operators.prefix.has(op);
473
- const isPostfix = operators.postfix.has(op);
474
- if (isInfix && !isPrefix && !isPostfix) {
475
- const after = i + op.length < len2 ? expr[i + op.length] : " ";
476
- if (/\s/.test(expr[i - 1]) && /\s/.test(after)) {
477
- isOp = true;
478
- break;
528
+ const infixConf = operators.infix.get(op);
529
+ const prefixConf = operators.prefix.get(op);
530
+ const postfixConf = operators.postfix.get(op);
531
+ if ((_b = infixConf == null ? void 0 : infixConf.options) == null ? void 0 : _b.requiresWhitespace) {
532
+ if (!prefixConf && !postfixConf) {
533
+ const after = i + op.length < len2 ? expr[i + op.length] : " ";
534
+ if (/\s/.test(expr[i - 1]) && /\s/.test(after)) {
535
+ isOp = true;
536
+ break;
537
+ }
538
+ continue;
479
539
  }
480
- continue;
481
540
  }
482
541
  if (path.length > 0 && path[path.length - 1] !== "/") {
483
542
  isOp = true;
@@ -521,14 +580,16 @@ var LightviewCDOM = function(exports) {
521
580
  };
522
581
  const hasOperatorSyntax = (expr) => {
523
582
  if (!expr || typeof expr !== "string") return false;
524
- if (expr.includes("(")) return false;
525
- if (/^=(\+\+|--|!!)\/?/.test(expr)) {
583
+ if (/^=?(\+\+|--|!!)\/?/.test(expr)) {
526
584
  return true;
527
585
  }
528
586
  if (/(\+\+|--)$/.test(expr)) {
529
587
  return true;
530
588
  }
531
- if (/\s+([+\-*/]|>|<|>=|<=|!=)\s+/.test(expr)) {
589
+ if (/\s+([+\-*/%]|>|<|>=|<=|!=|===|==|=)\s+/.test(expr)) {
590
+ return true;
591
+ }
592
+ if (/[^=\s]([+%=]|==|===|!=|!==|<=|>=|<|>)[^=\s]/.test(expr)) {
532
593
  return true;
533
594
  }
534
595
  return false;
@@ -640,15 +701,71 @@ var LightviewCDOM = function(exports) {
640
701
  this.consume();
641
702
  return { type: "Explosion", path: tok.value };
642
703
  }
704
+ if (nextTok.type === TokenType.LPAREN) {
705
+ this.consume();
706
+ const args = [];
707
+ while (this.peek().type !== TokenType.RPAREN && this.peek().type !== TokenType.EOF) {
708
+ args.push(this.parseExpression(0));
709
+ if (this.peek().type === TokenType.COMMA) {
710
+ this.consume();
711
+ }
712
+ }
713
+ this.expect(TokenType.RPAREN);
714
+ return { type: "Call", helper: tok.value, args };
715
+ }
643
716
  return { type: "Path", value: tok.value };
644
717
  }
718
+ if (tok.type === TokenType.LBRACE) {
719
+ return this.parseObjectLiteral();
720
+ }
721
+ if (tok.type === TokenType.LBRACKET) {
722
+ return this.parseArrayLiteral();
723
+ }
645
724
  if (tok.type === TokenType.EOF) {
646
725
  return { type: "Literal", value: void 0 };
647
726
  }
648
727
  throw new Error(`JPRX: Unexpected token ${tok.type}: ${tok.value}`);
649
728
  }
729
+ parseObjectLiteral() {
730
+ this.consume();
731
+ const properties = {};
732
+ while (this.peek().type !== TokenType.RBRACE && this.peek().type !== TokenType.EOF) {
733
+ const keyTok = this.consume();
734
+ let key;
735
+ if (keyTok.type === TokenType.LITERAL) key = String(keyTok.value);
736
+ else if (keyTok.type === TokenType.PATH) key = keyTok.value;
737
+ else if (keyTok.type === TokenType.PATH) key = keyTok.value;
738
+ else throw new Error(`JPRX: Expected property name but got ${keyTok.type}`);
739
+ this.expect(TokenType.COLON);
740
+ const value = this.parseExpression(0);
741
+ properties[key] = value;
742
+ if (this.peek().type === TokenType.COMMA) {
743
+ this.consume();
744
+ } else if (this.peek().type !== TokenType.RBRACE) {
745
+ break;
746
+ }
747
+ }
748
+ this.expect(TokenType.RBRACE);
749
+ return { type: "ObjectLiteral", properties };
750
+ }
751
+ parseArrayLiteral() {
752
+ this.consume();
753
+ const elements = [];
754
+ while (this.peek().type !== TokenType.RBRACKET && this.peek().type !== TokenType.EOF) {
755
+ const value = this.parseExpression(0);
756
+ elements.push(value);
757
+ if (this.peek().type === TokenType.COMMA) {
758
+ this.consume();
759
+ } else if (this.peek().type !== TokenType.RBRACKET) {
760
+ break;
761
+ }
762
+ }
763
+ this.expect(TokenType.RBRACKET);
764
+ return { type: "ArrayLiteral", elements };
765
+ }
650
766
  }
651
767
  const evaluateAST = (ast, context, forMutation = false) => {
768
+ var _a;
652
769
  if (!ast) return void 0;
653
770
  switch (ast.type) {
654
771
  case "Literal":
@@ -680,54 +797,126 @@ var LightviewCDOM = function(exports) {
680
797
  return resolvePath(path, event);
681
798
  });
682
799
  }
683
- case "Explosion": {
684
- const result = resolveArgument(ast.path + "...", context, false);
685
- return result.value;
800
+ case "ObjectLiteral": {
801
+ const res = {};
802
+ let hasLazy = false;
803
+ for (const key in ast.properties) {
804
+ const val = evaluateAST(ast.properties[key], context, forMutation);
805
+ if (val && val.isLazy) hasLazy = true;
806
+ res[key] = val;
807
+ }
808
+ if (hasLazy) {
809
+ return new LazyValue((ctx) => {
810
+ const resolved = {};
811
+ for (const key in res) {
812
+ resolved[key] = res[key] && res[key].isLazy ? res[key].resolve(ctx) : unwrapSignal(res[key]);
813
+ }
814
+ return resolved;
815
+ });
816
+ }
817
+ return res;
818
+ }
819
+ case "ArrayLiteral": {
820
+ const elements = ast.elements.map((el) => evaluateAST(el, context, forMutation));
821
+ const hasLazy = elements.some((el) => el && el.isLazy);
822
+ if (hasLazy) {
823
+ return new LazyValue((ctx) => {
824
+ return elements.map((el) => el && el.isLazy ? el.resolve(ctx) : unwrapSignal(el));
825
+ });
826
+ }
827
+ return elements.map((el) => unwrapSignal(el));
686
828
  }
687
829
  case "Prefix": {
688
830
  const opInfo = operators.prefix.get(ast.operator);
689
- if (!opInfo) {
690
- throw new Error(`JPRX: Unknown prefix operator: ${ast.operator}`);
691
- }
831
+ if (!opInfo) throw new Error(`JPRX: Unknown prefix operator: ${ast.operator}`);
692
832
  const helper = helpers.get(opInfo.helper);
693
- if (!helper) {
694
- throw new Error(`JPRX: Helper "${opInfo.helper}" for operator "${ast.operator}" not found.`);
695
- }
833
+ if (!helper) throw new Error(`JPRX: Helper "${opInfo.helper}" for operator "${ast.operator}" not found.`);
696
834
  const opts = helperOptions.get(opInfo.helper) || {};
697
835
  const operand = evaluateAST(ast.operand, context, opts.pathAware);
698
- return helper(operand);
836
+ if (operand && operand.isLazy && !opts.lazyAware) {
837
+ return new LazyValue((ctx) => {
838
+ const resolved = operand.resolve(ctx);
839
+ return helper(opts.pathAware ? resolved : unwrapSignal(resolved));
840
+ });
841
+ }
842
+ return helper(opts.pathAware ? operand : unwrapSignal(operand));
699
843
  }
700
844
  case "Postfix": {
701
845
  const opInfo = operators.postfix.get(ast.operator);
702
- if (!opInfo) {
703
- throw new Error(`JPRX: Unknown postfix operator: ${ast.operator}`);
704
- }
846
+ if (!opInfo) throw new Error(`JPRX: Unknown postfix operator: ${ast.operator}`);
705
847
  const helper = helpers.get(opInfo.helper);
706
- if (!helper) {
707
- throw new Error(`JPRX: Helper "${opInfo.helper}" for operator "${ast.operator}" not found.`);
708
- }
848
+ if (!helper) throw new Error(`JPRX: Helper "${opInfo.helper}" for operator "${ast.operator}" not found.`);
709
849
  const opts = helperOptions.get(opInfo.helper) || {};
710
850
  const operand = evaluateAST(ast.operand, context, opts.pathAware);
711
- return helper(operand);
851
+ if (operand && operand.isLazy && !opts.lazyAware) {
852
+ return new LazyValue((ctx) => {
853
+ const resolved = operand.resolve(ctx);
854
+ return helper(opts.pathAware ? resolved : unwrapSignal(resolved));
855
+ });
856
+ }
857
+ return helper(opts.pathAware ? operand : unwrapSignal(operand));
712
858
  }
713
859
  case "Infix": {
714
860
  const opInfo = operators.infix.get(ast.operator);
715
- if (!opInfo) {
716
- throw new Error(`JPRX: Unknown infix operator: ${ast.operator}`);
717
- }
861
+ if (!opInfo) throw new Error(`JPRX: Unknown infix operator: ${ast.operator}`);
718
862
  const helper = helpers.get(opInfo.helper);
719
- if (!helper) {
720
- throw new Error(`JPRX: Helper "${opInfo.helper}" for operator "${ast.operator}" not found.`);
721
- }
863
+ if (!helper) throw new Error(`JPRX: Helper "${opInfo.helper}" for operator "${ast.operator}" not found.`);
722
864
  const opts = helperOptions.get(opInfo.helper) || {};
723
865
  const left = evaluateAST(ast.left, context, opts.pathAware);
724
866
  const right = evaluateAST(ast.right, context, false);
867
+ if ((left && left.isLazy || right && right.isLazy) && !opts.lazyAware) {
868
+ return new LazyValue((ctx) => {
869
+ const l = left && left.isLazy ? left.resolve(ctx) : left;
870
+ const r = right && right.isLazy ? right.resolve(ctx) : right;
871
+ return helper(opts.pathAware ? l : unwrapSignal(l), unwrapSignal(r));
872
+ });
873
+ }
874
+ return helper(opts.pathAware ? left : unwrapSignal(left), unwrapSignal(right));
875
+ }
876
+ case "Call": {
877
+ const helperName = ast.helper.replace(/^=/, "");
878
+ const helper = helpers.get(helperName);
879
+ if (!helper) {
880
+ (_a = globalThis.console) == null ? void 0 : _a.warn(`JPRX: Helper "${helperName}" not found.`);
881
+ return void 0;
882
+ }
883
+ const opts = helperOptions.get(helperName) || {};
884
+ const args = ast.args.map((arg, i) => evaluateAST(arg, context, opts.pathAware && i === 0));
885
+ const hasLazy = args.some((arg) => arg && arg.isLazy);
886
+ if (hasLazy && !opts.lazyAware) {
887
+ return new LazyValue((ctx) => {
888
+ const finalArgs2 = args.map((arg, i) => {
889
+ const val = arg && arg.isLazy ? arg.resolve(ctx) : arg;
890
+ if (ast.args[i].type === "Explosion" && Array.isArray(val)) {
891
+ return val.map((v) => unwrapSignal(v));
892
+ }
893
+ return opts.pathAware && i === 0 ? val : unwrapSignal(val);
894
+ });
895
+ const flatArgs = [];
896
+ for (let i = 0; i < finalArgs2.length; i++) {
897
+ if (ast.args[i].type === "Explosion" && Array.isArray(finalArgs2[i])) {
898
+ flatArgs.push(...finalArgs2[i]);
899
+ } else {
900
+ flatArgs.push(finalArgs2[i]);
901
+ }
902
+ }
903
+ return helper.apply((context == null ? void 0 : context.__node__) || null, flatArgs);
904
+ });
905
+ }
725
906
  const finalArgs = [];
726
- if (Array.isArray(left) && ast.left.type === "Explosion") finalArgs.push(...left);
727
- else finalArgs.push(unwrapSignal(left));
728
- if (Array.isArray(right) && ast.right.type === "Explosion") finalArgs.push(...right);
729
- else finalArgs.push(unwrapSignal(right));
730
- return helper(...finalArgs);
907
+ for (let i = 0; i < args.length; i++) {
908
+ const arg = args[i];
909
+ if (ast.args[i].type === "Explosion" && Array.isArray(arg)) {
910
+ finalArgs.push(...arg.map((v) => unwrapSignal(v)));
911
+ } else {
912
+ finalArgs.push(opts.pathAware && i === 0 ? arg : unwrapSignal(arg));
913
+ }
914
+ }
915
+ return helper.apply((context == null ? void 0 : context.__node__) || null, finalArgs);
916
+ }
917
+ case "Explosion": {
918
+ const result = resolveArgument(ast.path + "...", context, false);
919
+ return result.value;
731
920
  }
732
921
  default:
733
922
  throw new Error(`JPRX: Unknown AST node type: ${ast.type}`);
@@ -885,10 +1074,12 @@ var LightviewCDOM = function(exports) {
885
1074
  let bDepth = 0;
886
1075
  let brDepth = 0;
887
1076
  let quote = null;
1077
+ const startChar = input[start];
1078
+ const isExpression = startChar === "=" || startChar === "#";
888
1079
  while (i < len2) {
889
1080
  const char = input[i];
890
1081
  if (quote) {
891
- if (char === quote) quote = null;
1082
+ if (char === quote && input[i - 1] !== "\\") quote = null;
892
1083
  i++;
893
1084
  continue;
894
1085
  } else if (char === '"' || char === "'" || char === "`") {
@@ -933,14 +1124,41 @@ var LightviewCDOM = function(exports) {
933
1124
  }
934
1125
  }
935
1126
  if (pDepth === 0 && bDepth === 0 && brDepth === 0) {
936
- if (/[\s:,{}\[\]"'`()]/.test(char)) {
937
- break;
1127
+ if (isExpression) {
1128
+ if (/[{}[\]"'`()]/.test(char)) {
1129
+ break;
1130
+ }
1131
+ if (char === ",") {
1132
+ break;
1133
+ }
1134
+ if (/[\s:]/.test(char)) {
1135
+ let j = i + 1;
1136
+ while (j < len2 && /\s/.test(input[j])) j++;
1137
+ if (j < len2) {
1138
+ const nextChar = input[j];
1139
+ if (nextChar === "}" || nextChar === ",") {
1140
+ break;
1141
+ }
1142
+ let wordStart = j;
1143
+ while (j < len2 && /[a-zA-Z0-9_$-]/.test(input[j])) j++;
1144
+ if (j > wordStart) {
1145
+ while (j < len2 && /\s/.test(input[j])) j++;
1146
+ if (j < len2 && input[j] === ":") {
1147
+ break;
1148
+ }
1149
+ }
1150
+ }
1151
+ }
1152
+ } else {
1153
+ if (/[:,{}[\]"'`()\s]/.test(char)) {
1154
+ break;
1155
+ }
938
1156
  }
939
1157
  }
940
1158
  i++;
941
1159
  }
942
1160
  const word = input.slice(start, i);
943
- if (word.startsWith("=")) {
1161
+ if (word.startsWith("=") || word.startsWith("#")) {
944
1162
  return word;
945
1163
  }
946
1164
  if (word === "true") return true;
@@ -1092,7 +1310,26 @@ var LightviewCDOM = function(exports) {
1092
1310
  inExprQuote = c;
1093
1311
  } else {
1094
1312
  if (parenDepth === 0 && braceDepth === 0 && bracketDepth === 0) {
1095
- if (/[\s,}\]:]/.test(c) && expr.length > 1) break;
1313
+ if (/[}[\]:]/.test(c) && expr.length > 1) break;
1314
+ if (c === ",") break;
1315
+ if (/\s/.test(c)) {
1316
+ let j = i + 1;
1317
+ while (j < len2 && /\s/.test(input[j])) j++;
1318
+ if (j < len2) {
1319
+ const nextChar = input[j];
1320
+ if (nextChar === "}" || nextChar === "," || nextChar === "]") {
1321
+ break;
1322
+ }
1323
+ let wordStart = j;
1324
+ while (j < len2 && /[a-zA-Z0-9_$-]/.test(input[j])) j++;
1325
+ if (j > wordStart) {
1326
+ while (j < len2 && /\s/.test(input[j])) j++;
1327
+ if (j < len2 && input[j] === ":") {
1328
+ break;
1329
+ }
1330
+ }
1331
+ }
1332
+ }
1096
1333
  }
1097
1334
  if (c === "(") parenDepth++;
1098
1335
  else if (c === ")") parenDepth--;
@@ -1210,8 +1447,10 @@ var LightviewCDOM = function(exports) {
1210
1447
  const andHelper = (...args) => args.every(Boolean);
1211
1448
  const orHelper = (...args) => args.some(Boolean);
1212
1449
  const notHelper = (val) => !val;
1213
- const eqHelper = (a, b) => a === b;
1214
- const neqHelper = (a, b) => a !== b;
1450
+ const eqHelper = (a, b) => a == b;
1451
+ const strictEqHelper = (a, b) => a === b;
1452
+ const neqHelper = (a, b) => a != b;
1453
+ const strictNeqHelper = (a, b) => a !== b;
1215
1454
  const registerLogicHelpers = (register) => {
1216
1455
  register("if", ifHelper);
1217
1456
  register("and", andHelper);
@@ -1221,9 +1460,13 @@ var LightviewCDOM = function(exports) {
1221
1460
  register("not", notHelper);
1222
1461
  register("!", notHelper);
1223
1462
  register("eq", eqHelper);
1463
+ register("strictEq", strictEqHelper);
1224
1464
  register("==", eqHelper);
1225
- register("===", eqHelper);
1465
+ register("===", strictEqHelper);
1226
1466
  register("neq", neqHelper);
1467
+ register("strictNeq", strictNeqHelper);
1468
+ register("!=", neqHelper);
1469
+ register("!==", strictNeqHelper);
1227
1470
  };
1228
1471
  const join$1 = (...args) => {
1229
1472
  const separator = args[args.length - 1];
@@ -3253,6 +3496,45 @@ var LightviewCDOM = function(exports) {
3253
3496
  const registerCalcHelpers = (register) => {
3254
3497
  register("calc", calc, { pathAware: true });
3255
3498
  };
3499
+ const registerDOMHelpers = (registerHelper2) => {
3500
+ registerHelper2("xpath", function(expression) {
3501
+ const domNode = this;
3502
+ if (!domNode || !(domNode instanceof Element)) {
3503
+ console.warn("[Lightview-CDOM] xpath() called without valid DOM context");
3504
+ return "";
3505
+ }
3506
+ const forbiddenAxes = /\b(child|descendant|following|following-sibling)::/;
3507
+ if (forbiddenAxes.test(expression)) {
3508
+ console.error(`[Lightview-CDOM] xpath(): Forward-looking axes not allowed: ${expression}`);
3509
+ return "";
3510
+ }
3511
+ const hasShorthandChild = /\/[a-zA-Z]/.test(expression) && !expression.startsWith("/html");
3512
+ if (hasShorthandChild) {
3513
+ console.error(`[Lightview-CDOM] xpath(): Shorthand child axis (/) not allowed: ${expression}`);
3514
+ return "";
3515
+ }
3516
+ const LV = globalThis.Lightview;
3517
+ if (!LV || !LV.computed) {
3518
+ console.warn("[Lightview-CDOM] xpath(): Lightview not available");
3519
+ return "";
3520
+ }
3521
+ return LV.computed(() => {
3522
+ try {
3523
+ const result = document.evaluate(
3524
+ expression,
3525
+ domNode,
3526
+ null,
3527
+ XPathResult.STRING_TYPE,
3528
+ null
3529
+ );
3530
+ return result.stringValue;
3531
+ } catch (e) {
3532
+ console.error(`[Lightview-CDOM] xpath() evaluation failed:`, e.message);
3533
+ return "";
3534
+ }
3535
+ });
3536
+ }, { pathAware: false });
3537
+ };
3256
3538
  const _LV = globalThis.__LIGHTVIEW_INTERNALS__ || (globalThis.__LIGHTVIEW_INTERNALS__ = {
3257
3539
  currentEffect: null,
3258
3540
  registry: /* @__PURE__ */ new Map(),
@@ -3290,6 +3572,7 @@ var LightviewCDOM = function(exports) {
3290
3572
  registerStateHelpers((name, fn) => registerHelper(name, fn, { pathAware: true }));
3291
3573
  registerNetworkHelpers(registerHelper);
3292
3574
  registerCalcHelpers(registerHelper);
3575
+ registerDOMHelpers(registerHelper);
3293
3576
  registerHelper("move", (selector, location = "beforeend") => {
3294
3577
  return {
3295
3578
  isLazy: true,
@@ -3363,15 +3646,19 @@ var LightviewCDOM = function(exports) {
3363
3646
  registerOperator("decrement", "--", "prefix", 80);
3364
3647
  registerOperator("decrement", "--", "postfix", 80);
3365
3648
  registerOperator("toggle", "!!", "prefix", 80);
3649
+ registerOperator("set", "=", "infix", 20);
3366
3650
  registerOperator("+", "+", "infix", 50);
3367
- registerOperator("-", "-", "infix", 50);
3368
- registerOperator("*", "*", "infix", 60);
3369
- registerOperator("/", "/", "infix", 60);
3651
+ registerOperator("-", "-", "infix", 50, { requiresWhitespace: true });
3652
+ registerOperator("*", "*", "infix", 60, { requiresWhitespace: true });
3653
+ registerOperator("/", "/", "infix", 60, { requiresWhitespace: true });
3370
3654
  registerOperator("gt", ">", "infix", 40);
3371
3655
  registerOperator("lt", "<", "infix", 40);
3372
3656
  registerOperator("gte", ">=", "infix", 40);
3373
3657
  registerOperator("lte", "<=", "infix", 40);
3374
3658
  registerOperator("neq", "!=", "infix", 40);
3659
+ registerOperator("strictNeq", "!==", "infix", 40);
3660
+ registerOperator("eq", "==", "infix", 40);
3661
+ registerOperator("strictEq", "===", "infix", 40);
3375
3662
  const getContext = (node, event = null) => {
3376
3663
  return new Proxy({}, {
3377
3664
  get(_, prop) {
@@ -3433,6 +3720,12 @@ var LightviewCDOM = function(exports) {
3433
3720
  if (typeof node === "string" && node.startsWith("'=")) {
3434
3721
  return node.slice(1);
3435
3722
  }
3723
+ if (typeof node === "string" && node.startsWith("'#")) {
3724
+ return node.slice(1);
3725
+ }
3726
+ if (typeof node === "string" && node.startsWith("#")) {
3727
+ return { __xpath__: node.slice(1), __static__: true };
3728
+ }
3436
3729
  if (typeof node === "string" && node.startsWith("=")) {
3437
3730
  return parseExpression(node, parent);
3438
3731
  }
@@ -3476,6 +3769,10 @@ var LightviewCDOM = function(exports) {
3476
3769
  const attrVal = value[attrKey];
3477
3770
  if (typeof attrVal === "string" && attrVal.startsWith("'=")) {
3478
3771
  value[attrKey] = attrVal.slice(1);
3772
+ } else if (typeof attrVal === "string" && attrVal.startsWith("'#")) {
3773
+ value[attrKey] = attrVal.slice(1);
3774
+ } else if (typeof attrVal === "string" && attrVal.startsWith("#")) {
3775
+ value[attrKey] = { __xpath__: attrVal.slice(1), __static__: true };
3479
3776
  } else if (typeof attrVal === "string" && attrVal.startsWith("=")) {
3480
3777
  if (attrKey.startsWith("on")) {
3481
3778
  value[attrKey] = makeEventHandler(attrVal);
@@ -3490,6 +3787,10 @@ var LightviewCDOM = function(exports) {
3490
3787
  }
3491
3788
  if (typeof value === "string" && value.startsWith("'=")) {
3492
3789
  node[key] = value.slice(1);
3790
+ } else if (typeof value === "string" && value.startsWith("'#")) {
3791
+ node[key] = value.slice(1);
3792
+ } else if (typeof value === "string" && value.startsWith("#")) {
3793
+ node[key] = { __xpath__: value.slice(1), __static__: true };
3493
3794
  } else if (typeof value === "string" && value.startsWith("=")) {
3494
3795
  if (key === "onmount" || key === "onunmount" || key.startsWith("on")) {
3495
3796
  node[key] = makeEventHandler(value);
@@ -3504,6 +3805,75 @@ var LightviewCDOM = function(exports) {
3504
3805
  }
3505
3806
  return node;
3506
3807
  };
3808
+ const validateXPath = (xpath) => {
3809
+ const forbiddenAxes = /\b(child|descendant|following|following-sibling)::/;
3810
+ if (forbiddenAxes.test(xpath)) {
3811
+ throw new Error(`XPath: Forward-looking axes not allowed during DOM construction: ${xpath}`);
3812
+ }
3813
+ const hasShorthandChild = /\/[a-zA-Z]/.test(xpath) && !xpath.startsWith("/html");
3814
+ if (hasShorthandChild) {
3815
+ throw new Error(`XPath: Shorthand child axis (/) not allowed during DOM construction: ${xpath}`);
3816
+ }
3817
+ };
3818
+ const resolveStaticXPath = (rootNode) => {
3819
+ var _a, _b;
3820
+ if (!rootNode || !rootNode.nodeType) return;
3821
+ const walker = document.createTreeWalker(
3822
+ rootNode,
3823
+ NodeFilter.SHOW_ALL
3824
+ );
3825
+ const nodesToProcess = [];
3826
+ let node = walker.nextNode();
3827
+ while (node) {
3828
+ nodesToProcess.push(node);
3829
+ node = walker.nextNode();
3830
+ }
3831
+ for (const node2 of nodesToProcess) {
3832
+ if (node2.nodeType === Node.ELEMENT_NODE) {
3833
+ const attributes = [...node2.attributes];
3834
+ for (const attr of attributes) {
3835
+ if (attr.name.startsWith("data-xpath-")) {
3836
+ const realAttr = attr.name.replace("data-xpath-", "");
3837
+ const xpath = attr.value;
3838
+ try {
3839
+ validateXPath(xpath);
3840
+ const result = document.evaluate(
3841
+ xpath,
3842
+ node2,
3843
+ null,
3844
+ XPathResult.STRING_TYPE,
3845
+ null
3846
+ );
3847
+ node2.setAttribute(realAttr, result.stringValue);
3848
+ node2.removeAttribute(attr.name);
3849
+ } catch (e) {
3850
+ (_a = globalThis.console) == null ? void 0 : _a.error(`[Lightview-CDOM] XPath resolution failed for attribute "${realAttr}":`, e.message);
3851
+ }
3852
+ }
3853
+ }
3854
+ }
3855
+ if (node2.__xpathExpr) {
3856
+ const xpath = node2.__xpathExpr;
3857
+ try {
3858
+ validateXPath(xpath);
3859
+ const result = document.evaluate(
3860
+ xpath,
3861
+ node2,
3862
+ // Use text node as context, not its parent!
3863
+ null,
3864
+ XPathResult.STRING_TYPE,
3865
+ null
3866
+ );
3867
+ node2.textContent = result.stringValue;
3868
+ delete node2.__xpathExpr;
3869
+ } catch (e) {
3870
+ (_b = globalThis.console) == null ? void 0 : _b.error(`[Lightview-CDOM] XPath resolution failed for text node:`, e.message);
3871
+ }
3872
+ }
3873
+ }
3874
+ };
3875
+ if (typeof parseCDOMC !== "function") throw new Error("parseCDOMC not found");
3876
+ if (typeof parseJPRX !== "function") throw new Error("parseJPRX not found");
3507
3877
  const LightviewCDOM2 = {
3508
3878
  registerHelper,
3509
3879
  registerOperator,
@@ -3521,15 +3891,28 @@ var LightviewCDOM = function(exports) {
3521
3891
  },
3522
3892
  activate,
3523
3893
  hydrate,
3894
+ resolveStaticXPath,
3524
3895
  version: "1.0.0"
3525
3896
  };
3526
3897
  if (typeof window !== "undefined") {
3527
- globalThis.LightviewCDOM = LightviewCDOM2;
3898
+ globalThis.LightviewCDOM = {};
3899
+ Object.assign(globalThis.LightviewCDOM, LightviewCDOM2);
3528
3900
  }
3901
+ exports.BindingTarget = BindingTarget;
3529
3902
  exports.activate = activate;
3530
3903
  exports.default = LightviewCDOM2;
3531
3904
  exports.getContext = getContext;
3532
3905
  exports.hydrate = hydrate;
3906
+ exports.parseCDOMC = parseCDOMC;
3907
+ exports.parseExpression = parseExpression;
3908
+ exports.parseJPRX = parseJPRX;
3909
+ exports.registerHelper = registerHelper;
3910
+ exports.registerOperator = registerOperator;
3911
+ exports.resolveExpression = resolveExpression$1;
3912
+ exports.resolvePath = resolvePath;
3913
+ exports.resolvePathAsContext = resolvePathAsContext;
3914
+ exports.resolveStaticXPath = resolveStaticXPath;
3915
+ exports.unwrapSignal = unwrapSignal;
3533
3916
  Object.defineProperties(exports, { __esModule: { value: true }, [Symbol.toStringTag]: { value: "Module" } });
3534
3917
  return exports;
3535
3918
  }({});