lightview 2.3.6 → 2.3.7

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
@@ -1018,6 +1018,7 @@ var LightviewCDOM = function(exports) {
1018
1018
  return LV.computed(() => resolveExpression$1(expr, context));
1019
1019
  };
1020
1020
  const parseCDOMC = (input) => {
1021
+ if (typeof input !== "string") return input;
1021
1022
  let i = 0;
1022
1023
  const len2 = input.length;
1023
1024
  const skipWhitespace = () => {
@@ -1245,6 +1246,7 @@ var LightviewCDOM = function(exports) {
1245
1246
  };
1246
1247
  const parseJPRX = (input) => {
1247
1248
  var _a, _b;
1249
+ if (typeof input !== "string") return input;
1248
1250
  let result = "";
1249
1251
  let i = 0;
1250
1252
  const len2 = input.length;
@@ -1344,9 +1346,9 @@ var LightviewCDOM = function(exports) {
1344
1346
  result += JSON.stringify(expr);
1345
1347
  continue;
1346
1348
  }
1347
- if (/[a-zA-Z_$/./]/.test(char)) {
1349
+ if (/[a-zA-Z_$\/.\/]/.test(char)) {
1348
1350
  let word = "";
1349
- while (i < len2 && /[a-zA-Z0-9_$/.-]/.test(input[i])) {
1351
+ while (i < len2 && /[a-zA-Z0-9_$\/.-]/.test(input[i])) {
1350
1352
  word += input[i];
1351
1353
  i++;
1352
1354
  }
@@ -3627,7 +3629,7 @@ var LightviewCDOM = function(exports) {
3627
3629
  const isCDOM = contentType.includes("application/cdom") || contentType.includes("application/jprx") || contentType.includes("application/vdom") || contentType.includes("application/odom") || url.endsWith(".cdom") || url.endsWith(".jprx") || url.endsWith(".vdom") || url.endsWith(".odom");
3628
3630
  if (isCDOM || contentType.includes("application/json") && text.trim().startsWith("{")) {
3629
3631
  try {
3630
- content = hydrate(parseJPRX(text));
3632
+ content = hydrate(parseCDOMC(text));
3631
3633
  } catch (e) {
3632
3634
  }
3633
3635
  }
@@ -3803,77 +3805,89 @@ var LightviewCDOM = function(exports) {
3803
3805
  node[key] = hydrate(value, node);
3804
3806
  }
3805
3807
  }
3808
+ if (!parent && node.tag) {
3809
+ node.attributes = node.attributes || {};
3810
+ const originalOnMount = node.attributes.onmount;
3811
+ node.attributes.onmount = (el) => {
3812
+ if (typeof originalOnMount === "function") originalOnMount(el);
3813
+ resolveStaticXPath(el);
3814
+ };
3815
+ }
3806
3816
  return node;
3807
3817
  };
3808
3818
  const validateXPath = (xpath) => {
3819
+ if (!xpath) return;
3809
3820
  const forbiddenAxes = /\b(child|descendant|following|following-sibling)::/;
3810
3821
  if (forbiddenAxes.test(xpath)) {
3811
3822
  throw new Error(`XPath: Forward-looking axes not allowed during DOM construction: ${xpath}`);
3812
3823
  }
3813
- const hasShorthandChild = /\/[a-zA-Z]/.test(xpath) && !xpath.startsWith("/html");
3824
+ const hasShorthandChild = /\/(?![@.])(?![a-zA-Z0-9_-]+::)[a-zA-Z]/.test(xpath) && !xpath.startsWith("/html");
3814
3825
  if (hasShorthandChild) {
3815
3826
  throw new Error(`XPath: Shorthand child axis (/) not allowed during DOM construction: ${xpath}`);
3816
3827
  }
3817
3828
  };
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;
3829
+ const resolveAttributeXPaths = (el) => {
3830
+ var _a;
3831
+ const attributes = [...el.attributes];
3832
+ for (const attr of attributes) {
3833
+ if (attr.name.startsWith("data-xpath-")) {
3834
+ const realAttr = attr.name.replace("data-xpath-", "");
3857
3835
  try {
3858
- validateXPath(xpath);
3859
- const result = document.evaluate(
3860
- xpath,
3861
- node2,
3862
- // Use text node as context, not its parent!
3836
+ validateXPath(attr.value);
3837
+ const doc = globalThis.document || el.ownerDocument;
3838
+ const result = doc.evaluate(
3839
+ attr.value,
3840
+ el,
3863
3841
  null,
3864
3842
  XPathResult.STRING_TYPE,
3865
3843
  null
3866
3844
  );
3867
- node2.textContent = result.stringValue;
3868
- delete node2.__xpathExpr;
3845
+ el.setAttribute(realAttr, result.stringValue);
3846
+ el.removeAttribute(attr.name);
3869
3847
  } catch (e) {
3870
- (_b = globalThis.console) == null ? void 0 : _b.error(`[Lightview-CDOM] XPath resolution failed for text node:`, e.message);
3848
+ (_a = globalThis.console) == null ? void 0 : _a.error(`[Lightview-CDOM] XPath attribute error ("${realAttr}") at <${el.tagName.toLowerCase()} id="${el.id}">:`, e.message);
3871
3849
  }
3872
3850
  }
3873
3851
  }
3874
3852
  };
3853
+ const resolveTextNodeXPath = (node) => {
3854
+ var _a, _b, _c;
3855
+ if (!node.__xpathExpr) return;
3856
+ const xpath = node.__xpathExpr;
3857
+ try {
3858
+ validateXPath(xpath);
3859
+ const doc = globalThis.document || node.ownerDocument;
3860
+ const contextNode = node.parentNode || node;
3861
+ const result = doc.evaluate(
3862
+ xpath,
3863
+ contextNode,
3864
+ null,
3865
+ XPathResult.STRING_TYPE,
3866
+ null
3867
+ );
3868
+ node.textContent = result.stringValue;
3869
+ } catch (e) {
3870
+ (_c = globalThis.console) == null ? void 0 : _c.error(`[Lightview-CDOM] XPath text node error on <${(_a = node.parentNode) == null ? void 0 : _a.tagName.toLowerCase()} id="${(_b = node.parentNode) == null ? void 0 : _b.id}">:`, e.message);
3871
+ } finally {
3872
+ delete node.__xpathExpr;
3873
+ }
3874
+ };
3875
+ const resolveStaticXPath = (rootNode) => {
3876
+ const node = rootNode instanceof Node ? rootNode : (rootNode == null ? void 0 : rootNode.domEl) || rootNode;
3877
+ if (!node || !node.nodeType) return;
3878
+ if (node.nodeType === Node.ELEMENT_NODE) resolveAttributeXPaths(node);
3879
+ resolveTextNodeXPath(node);
3880
+ const doc = globalThis.document || node.ownerDocument;
3881
+ const walker = doc.createTreeWalker(node, NodeFilter.SHOW_ALL);
3882
+ let current = walker.nextNode();
3883
+ while (current) {
3884
+ if (current.nodeType === Node.ELEMENT_NODE) resolveAttributeXPaths(current);
3885
+ resolveTextNodeXPath(current);
3886
+ current = walker.nextNode();
3887
+ }
3888
+ };
3875
3889
  if (typeof parseCDOMC !== "function") throw new Error("parseCDOMC not found");
3876
- if (typeof parseJPRX !== "function") throw new Error("parseJPRX not found");
3890
+ if (typeof parseJPRX !== "function") throw new Error("oldParseJPRX not found");
3877
3891
  const LightviewCDOM2 = {
3878
3892
  registerHelper,
3879
3893
  registerOperator,
@@ -3882,7 +3896,9 @@ var LightviewCDOM = function(exports) {
3882
3896
  resolvePathAsContext,
3883
3897
  resolveExpression: resolveExpression$1,
3884
3898
  parseCDOMC,
3885
- parseJPRX,
3899
+ parseJPRX: parseCDOMC,
3900
+ // Alias parseJPRX to the more robust parseCDOMC
3901
+ oldParseJPRX: parseJPRX,
3886
3902
  unwrapSignal,
3887
3903
  getContext,
3888
3904
  handleCDOMState: () => {
@@ -3892,7 +3908,7 @@ var LightviewCDOM = function(exports) {
3892
3908
  activate,
3893
3909
  hydrate,
3894
3910
  resolveStaticXPath,
3895
- version: "1.0.0"
3911
+ version: "1.1.0"
3896
3912
  };
3897
3913
  if (typeof window !== "undefined") {
3898
3914
  globalThis.LightviewCDOM = {};
@@ -3903,9 +3919,10 @@ var LightviewCDOM = function(exports) {
3903
3919
  exports.default = LightviewCDOM2;
3904
3920
  exports.getContext = getContext;
3905
3921
  exports.hydrate = hydrate;
3922
+ exports.oldParseJPRX = parseJPRX;
3906
3923
  exports.parseCDOMC = parseCDOMC;
3907
3924
  exports.parseExpression = parseExpression;
3908
- exports.parseJPRX = parseJPRX;
3925
+ exports.parseJPRX = parseCDOMC;
3909
3926
  exports.registerHelper = registerHelper;
3910
3927
  exports.registerOperator = registerOperator;
3911
3928
  exports.resolveExpression = resolveExpression$1;
package/lightview.js CHANGED
@@ -644,7 +644,6 @@
644
644
  return reactiveAttrs;
645
645
  };
646
646
  const processChildren = (children, targetNode, clearExisting = true) => {
647
- var _a;
648
647
  if (clearExisting && targetNode.innerHTML !== void 0) {
649
648
  targetNode.innerHTML = "";
650
649
  }
@@ -717,9 +716,6 @@
717
716
  childElements.push(childEl);
718
717
  }
719
718
  }
720
- if (typeof ((_a = globalThis.LightviewCDOM) == null ? void 0 : _a.resolveStaticXPath) === "function") {
721
- globalThis.LightviewCDOM.resolveStaticXPath(targetNode);
722
- }
723
719
  return childElements;
724
720
  };
725
721
  const setupChildrenInTarget = (children, targetNode) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lightview",
3
- "version": "2.3.6",
3
+ "version": "2.3.7",
4
4
  "description": "A lightweight reactive UI library with features of Bau, Juris, and HTMX",
5
5
  "main": "lightview.js",
6
6
  "workspaces": [
@@ -36,7 +36,7 @@
36
36
  },
37
37
  "dependencies": {
38
38
  "expr-eval": "^2.0.2",
39
- "jprx": "^1.3.0",
39
+ "jprx": "^1.3.1",
40
40
  "linkedom": "^0.18.12",
41
41
  "marked": "^17.0.1"
42
42
  }
@@ -3,7 +3,7 @@
3
3
  * The Reactive Path and Expression Engine for Lightview.
4
4
  */
5
5
 
6
- import { registerHelper, registerOperator, parseExpression, resolvePath, resolvePathAsContext, resolveExpression, parseCDOMC, parseJPRX, unwrapSignal, BindingTarget } from '../jprx/parser.js';
6
+ import { registerHelper, registerOperator, parseExpression, resolvePath, resolvePathAsContext, resolveExpression, parseCDOMC, parseJPRX as oldParseJPRX, unwrapSignal, BindingTarget } from '../jprx/parser.js';
7
7
  import { registerMathHelpers } from '../jprx/helpers/math.js';
8
8
  import { registerLogicHelpers } from '../jprx/helpers/logic.js';
9
9
  import { registerStringHelpers } from '../jprx/helpers/string.js';
@@ -111,7 +111,7 @@ registerHelper('mount', async (url, options = {}) => {
111
111
 
112
112
  if (isCDOM || (contentType.includes('application/json') && text.trim().startsWith('{'))) {
113
113
  try {
114
- content = hydrate(parseJPRX(text));
114
+ content = hydrate(parseCDOMC(text));
115
115
  } catch (e) {
116
116
  // Fail gracefully to text
117
117
  }
@@ -352,6 +352,18 @@ const hydrate = (node, parent = null) => {
352
352
  }
353
353
  }
354
354
 
355
+ // 4. Automatic XPath Resolution
356
+ // If this is a top-level hydrated element, ensure it resolves its static XPaths on mount.
357
+ // We add it to node.attributes so Lightview's element() factory picks it up.
358
+ if (!parent && node.tag) {
359
+ node.attributes = node.attributes || {};
360
+ const originalOnMount = node.attributes.onmount;
361
+ node.attributes.onmount = (el) => {
362
+ if (typeof originalOnMount === 'function') originalOnMount(el);
363
+ resolveStaticXPath(el);
364
+ };
365
+ }
366
+
355
367
  return node;
356
368
  };
357
369
 
@@ -361,15 +373,18 @@ const hydrate = (node, parent = null) => {
361
373
  * @param {string} xpath - The XPath expression to validate
362
374
  */
363
375
  const validateXPath = (xpath) => {
376
+ if (!xpath) return;
377
+
364
378
  // Check for forbidden forward-looking axes
365
379
  const forbiddenAxes = /\b(child|descendant|following|following-sibling)::/;
366
380
  if (forbiddenAxes.test(xpath)) {
367
381
  throw new Error(`XPath: Forward-looking axes not allowed during DOM construction: ${xpath}`);
368
382
  }
369
383
 
370
- // Also check for shorthand forward references like /div (implies child axis)
371
- // But allow / for document root like /html
372
- const hasShorthandChild = /\/[a-zA-Z]/.test(xpath) && !xpath.startsWith('/html');
384
+ // Check for shorthand forward references like /div (implies child axis)
385
+ // We allow '/' if it's followed by '@' (attribute), '.' (parent/self shorthand),
386
+ // or is the start of the document root '/html'.
387
+ const hasShorthandChild = /\/(?![@.])(?![a-zA-Z0-9_-]+::)[a-zA-Z]/.test(xpath) && !xpath.startsWith('/html');
373
388
  if (hasShorthandChild) {
374
389
  throw new Error(`XPath: Shorthand child axis (/) not allowed during DOM construction: ${xpath}`);
375
390
  }
@@ -381,76 +396,90 @@ const validateXPath = (xpath) => {
381
396
  * Walks the tree and resolves all __xpath__ markers.
382
397
  * @param {Node} rootNode - The root DOM node to start walking from
383
398
  */
384
- const resolveStaticXPath = (rootNode) => {
385
- if (!rootNode || !rootNode.nodeType) return;
386
-
387
- const walker = document.createTreeWalker(
388
- rootNode,
389
- NodeFilter.SHOW_ALL
390
- );
391
-
392
- const nodesToProcess = [];
393
- let node = walker.nextNode();
394
- while (node) {
395
- nodesToProcess.push(node);
396
- node = walker.nextNode();
397
- }
398
-
399
- // Process all nodes
400
- for (const node of nodesToProcess) {
401
- // Check for XPath markers in attributes
402
- if (node.nodeType === Node.ELEMENT_NODE) {
403
- const attributes = [...node.attributes];
404
- for (const attr of attributes) {
405
- if (attr.name.startsWith('data-xpath-')) {
406
- const realAttr = attr.name.replace('data-xpath-', '');
407
- const xpath = attr.value;
408
-
409
- try {
410
- validateXPath(xpath);
411
- const result = document.evaluate(
412
- xpath,
413
- node,
414
- null,
415
- XPathResult.STRING_TYPE,
416
- null
417
- );
418
- node.setAttribute(realAttr, result.stringValue);
419
- node.removeAttribute(attr.name);
420
- } catch (e) {
421
- globalThis.console?.error(`[Lightview-CDOM] XPath resolution failed for attribute "${realAttr}":`, e.message);
422
- }
423
- }
424
- }
425
- }
426
-
427
- // Check for XPath markers in text nodes
428
- if (node.__xpathExpr) {
429
- const xpath = node.__xpathExpr;
399
+ /**
400
+ * Resolves static XPath markers for attributes on a specific element.
401
+ */
402
+ const resolveAttributeXPaths = (el) => {
403
+ const attributes = [...el.attributes];
404
+ for (const attr of attributes) {
405
+ if (attr.name.startsWith('data-xpath-')) {
406
+ const realAttr = attr.name.replace('data-xpath-', '');
430
407
  try {
431
- validateXPath(xpath);
432
- const result = document.evaluate(
433
- xpath,
434
- node, // Use text node as context, not its parent!
408
+ validateXPath(attr.value);
409
+ const doc = globalThis.document || el.ownerDocument;
410
+ const result = doc.evaluate(
411
+ attr.value,
412
+ el,
435
413
  null,
436
414
  XPathResult.STRING_TYPE,
437
415
  null
438
416
  );
439
- node.textContent = result.stringValue;
440
- delete node.__xpathExpr;
417
+ el.setAttribute(realAttr, result.stringValue);
418
+ el.removeAttribute(attr.name);
441
419
  } catch (e) {
442
- globalThis.console?.error(`[Lightview-CDOM] XPath resolution failed for text node:`, e.message);
420
+ globalThis.console?.error(`[Lightview-CDOM] XPath attribute error ("${realAttr}") at <${el.tagName.toLowerCase()} id="${el.id}">:`, e.message);
443
421
  }
444
422
  }
445
423
  }
446
424
  };
447
425
 
426
+ /**
427
+ * Resolves static XPath markers for a text node.
428
+ */
429
+ const resolveTextNodeXPath = (node) => {
430
+ if (!node.__xpathExpr) return;
431
+ const xpath = node.__xpathExpr;
432
+ try {
433
+ validateXPath(xpath);
434
+ const doc = globalThis.document || node.ownerDocument;
435
+ // Use the parent node (the element) as the context for evaluation
436
+ // This avoids errors in browsers that don't support Text nodes as context nodes
437
+ // and keeps evaluation consistent with attributes.
438
+ const contextNode = node.parentNode || node;
439
+ const result = doc.evaluate(
440
+ xpath,
441
+ contextNode,
442
+ null,
443
+ XPathResult.STRING_TYPE,
444
+ null
445
+ );
446
+ node.textContent = result.stringValue;
447
+ } catch (e) {
448
+ globalThis.console?.error(`[Lightview-CDOM] XPath text node error on <${node.parentNode?.tagName.toLowerCase()} id="${node.parentNode?.id}">:`, e.message);
449
+ } finally {
450
+ delete node.__xpathExpr;
451
+ }
452
+ };
453
+
454
+ /**
455
+ * Walks the tree and resolves all __xpath__ markers.
456
+ * @param {Node} rootNode - The root DOM node to start walking from
457
+ */
458
+ const resolveStaticXPath = (rootNode) => {
459
+ const node = rootNode instanceof Node ? rootNode : (rootNode?.domEl || rootNode);
460
+ if (!node || !node.nodeType) return;
461
+
462
+ // Process the root node itself
463
+ if (node.nodeType === Node.ELEMENT_NODE) resolveAttributeXPaths(node);
464
+ resolveTextNodeXPath(node);
465
+
466
+ // Process all descendants
467
+ const doc = globalThis.document || node.ownerDocument;
468
+ const walker = doc.createTreeWalker(node, NodeFilter.SHOW_ALL);
469
+ let current = walker.nextNode();
470
+ while (current) {
471
+ if (current.nodeType === Node.ELEMENT_NODE) resolveAttributeXPaths(current);
472
+ resolveTextNodeXPath(current);
473
+ current = walker.nextNode();
474
+ }
475
+ };
476
+
448
477
 
449
478
  // Prevent tree-shaking of parser functions by creating a side-effect
450
479
  // These are used externally by lightview-x.js for .cdomc file loading
451
480
  // The typeof check creates a runtime branch the bundler can't eliminate
452
481
  if (typeof parseCDOMC !== 'function') throw new Error('parseCDOMC not found');
453
- if (typeof parseJPRX !== 'function') throw new Error('parseJPRX not found');
482
+ if (typeof oldParseJPRX !== 'function') throw new Error('oldParseJPRX not found');
454
483
 
455
484
  const LightviewCDOM = {
456
485
  registerHelper,
@@ -460,7 +489,8 @@ const LightviewCDOM = {
460
489
  resolvePathAsContext,
461
490
  resolveExpression,
462
491
  parseCDOMC,
463
- parseJPRX,
492
+ parseJPRX: parseCDOMC, // Alias parseJPRX to the more robust parseCDOMC
493
+ oldParseJPRX,
464
494
  unwrapSignal,
465
495
  getContext,
466
496
  handleCDOMState: () => { },
@@ -468,7 +498,7 @@ const LightviewCDOM = {
468
498
  activate,
469
499
  hydrate,
470
500
  resolveStaticXPath,
471
- version: '1.0.0'
501
+ version: '1.1.0'
472
502
  };
473
503
 
474
504
  // Global export for non-module usage
@@ -486,7 +516,8 @@ export {
486
516
  resolvePathAsContext,
487
517
  resolveExpression,
488
518
  parseCDOMC,
489
- parseJPRX,
519
+ parseCDOMC as parseJPRX,
520
+ oldParseJPRX,
490
521
  unwrapSignal,
491
522
  BindingTarget,
492
523
  getContext,
package/src/lightview.js CHANGED
@@ -520,10 +520,7 @@ const processChildren = (children, targetNode, clearExisting = true) => {
520
520
  }
521
521
  }
522
522
 
523
- // Resolve static XPath expressions after DOM tree is constructed
524
- if (typeof globalThis.LightviewCDOM?.resolveStaticXPath === 'function') {
525
- globalThis.LightviewCDOM.resolveStaticXPath(targetNode);
526
- }
523
+
527
524
 
528
525
  return childElements;
529
526
  };
@@ -1,18 +0,0 @@
1
- // Test preprocessXPath function
2
- import { parseCDOMC } from './jprx/parser.js';
3
-
4
- const testInput = `{
5
- button: {
6
- id: "7",
7
- children: [#../@id]
8
- }
9
- }`;
10
-
11
- console.log('Test input:', testInput);
12
-
13
- try {
14
- const result = parseCDOMC(testInput);
15
- console.log('Parsed successfully:', result);
16
- } catch (e) {
17
- console.error('Parse failed:', e.message);
18
- }