lightview 2.3.5 → 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.
@@ -0,0 +1,160 @@
1
+ <script src="/lightview-router.js?base=/index.html"></script>
2
+
3
+ <div class="docs-layout">
4
+ <aside class="docs-sidebar" src="/docs/cdom-nav.html"></aside>
5
+
6
+ <main class="docs-content">
7
+ <h1>XPath Navigation in cDOM</h1>
8
+ <p class="text-secondary" style="font-size: 1.125rem;">
9
+ Navigate and derive element properties from the existing DOM structure.
10
+ </p>
11
+
12
+ <div class="experimental-notice"
13
+ style="margin: 1.5rem 0; padding: 1.25rem; border-radius: var(--site-radius); background: var(--site-accent-light); border: 1px solid var(--site-warning); color: var(--site-text);">
14
+ <div style="display: flex; gap: 0.75rem; align-items: flex-start;">
15
+ <span style="font-size: 1.5rem;">🚨</span>
16
+ <div>
17
+ <strong style="display: block; margin-bottom: 0.25rem; font-size: 1.1rem;">cDOM vs JPRX</strong>
18
+ <p style="margin: 0; font-size: 0.95rem; opacity: 0.9;">
19
+ Static XPaths are availbale to <strong>cDOM</strong> during the construction phase,
20
+ whereas <strong>JPRX</strong> has an <code>xpath</code> helper that returns a computed signal.
21
+ See <a href="/docs/cdom.html#helpers-dom">JPRX documentation</a> on the helper for more info
22
+ about xpath and reactivity.
23
+ </p>
24
+ </div>
25
+ </div>
26
+ </div>
27
+
28
+ <h2 id="overview">Overview</h2>
29
+ <p>
30
+ cDOM allows elements to derive their properties from the existing DOM tree. This can be done statically
31
+ for one-time setup or reactively for values that change over time.
32
+ </p>
33
+ <ol>
34
+ <li><strong>Static XPath (<code>#</code> prefix)</strong>: Available to cDOM. Evaluated once during DOM
35
+ construction.</li>
36
+ <li><strong>Reactive XPath (<code>=xpath()</code> helper)</strong>: Available to JPRX. Evaluates as an
37
+ expression and returns a computed signal.</li>
38
+ </ol>
39
+
40
+ <h2 id="static-xpath">Static XPath (<code>#</code> prefix)</h2>
41
+ <p>
42
+ Static XPath expressions are perfect for keeping your definitions <strong>DRY (Don't Repeat
43
+ Yourself)</strong>.
44
+ By referencing values like <code>id</code> or <code>class</code> from parent elements, you avoid duplicating
45
+ data in your cDOM structure.
46
+ </p>
47
+
48
+
49
+ <div id="xpath-live-demo" style="margin: 2rem 0;">
50
+ <p class="text-sm font-bold opacity-60 uppercase mb-3">Live Interactive Example</p>
51
+ <pre><script>
52
+ examplify(document.currentScript.nextElementSibling, {
53
+ at: document.currentScript.parentElement,
54
+ scripts: ['/lightview.js', '/lightview-x.js', '/lightview-cdom.js'],
55
+ type: 'module',
56
+ height: '180px',
57
+ autoRun: true
58
+ });
59
+ </script><code>await import('/lightview-cdom.js');
60
+ const { parseJPRX, hydrate } = globalThis.LightviewCDOM;
61
+ const { $ } = Lightview;
62
+
63
+ const cdom = `{
64
+ div: {
65
+ id: "profile-container",
66
+ class: "card",
67
+ "data-theme": "dark",
68
+ children: [
69
+ { h3: "User Profile" },
70
+ { button: {
71
+ id: "7",
72
+ // XPath #@id gets "7" from this button's id
73
+ // XPath #../@id gets "profile-container" from the parent div
74
+ children: ["Button ", #@id, " in section ", #../@id]
75
+ }}
76
+ ]
77
+ }
78
+ }`;
79
+
80
+ $('#example').content(hydrate(parseJPRX(cdom)));</code></pre>
81
+ </div>
82
+
83
+ <h2 id="allowed-axes">Allowed Axes (Backward-Looking Only)</h2>
84
+ <p>
85
+ To maintain high performance and safety during construction, cDOM only allows
86
+ <strong>backward-looking</strong>
87
+ XPath axes. You can reference nodes that are already constructed (parents, ancestors, earlier siblings).
88
+ </p>
89
+ <table class="api-table">
90
+ <thead>
91
+ <tr>
92
+ <th>Axis</th>
93
+ <th>Result</th>
94
+ </tr>
95
+ </thead>
96
+ <tbody>
97
+ <tr>
98
+ <td><code>self::</code> / <code>.</code></td>
99
+ <td>The current node.</td>
100
+ </tr>
101
+ <tr>
102
+ <td><code>parent::</code> / <code>..</code></td>
103
+ <td>The parent element.</td>
104
+ </tr>
105
+ <tr>
106
+ <td><code>ancestor::</code></td>
107
+ <td>Higher-level ancestors.</td>
108
+ </tr>
109
+ <tr>
110
+ <td><code>preceding-sibling::</code></td>
111
+ <td>Elements that appear before the current one in the same parent.</td>
112
+ </tr>
113
+ </tbody>
114
+ </table>
115
+ <p class="text-warning">
116
+ <strong>Forbidden:</strong> <code>child::</code>, <code>descendant::</code>, and <code>following::</code>
117
+ axes are disabled as they could create infinite loops or reference nodes that do not exist yet.
118
+ </p>
119
+
120
+ <h2 id="reactive-xpath">Reactive XPath (<code>=xpath()</code>)</h2>
121
+ <p>
122
+ If you need an XPath expression to update reactively if attributes elsewhere in the DOM change,
123
+ use the <code>=xpath()</code> helper within a JPRX expression.
124
+ </p>
125
+ <div class="code-block">
126
+ <pre><code>{
127
+ div: {
128
+ title: "=xpath('../@data-section')",
129
+ class: "=concat('item ', xpath('../@theme'))"
130
+ }
131
+ }</code></pre>
132
+ </div>
133
+
134
+ <h2 id="cdomc">Concise cDOM (cDOMC) Support</h2>
135
+ <p>
136
+ In cDOMC (the shorthand format used in <code>.cdomc</code> files), the parser is <strong>structurally
137
+ aware</strong>
138
+ of XPath expressions. You do not need to quote them even if they contain square brackets or spaces.
139
+ </p>
140
+ <div class="code-block">
141
+ <pre><code>// Clean unquoted syntax in .cdomc files
142
+ {
143
+ button: {
144
+ id: "7",
145
+ children: [#../@id]
146
+ }
147
+ }
148
+
149
+ // Support for complex paths with predicates
150
+ { span: [#ancestor::div[@data-role='container']/@title] }</code></pre>
151
+ </div>
152
+
153
+ <h2 id="safety">Security & Safety</h2>
154
+ <p>
155
+ XPath navigation in cDOM is inherently safer than using arbitrary JavaScript for DOM traversal.
156
+ It provides a restricted, read-only view of the existing structural tree without the risks
157
+ of code injection or side effects.
158
+ </p>
159
+ </main>
160
+ </div>
package/docs/cdom.html CHANGED
@@ -108,8 +108,9 @@
108
108
  <p>
109
109
  cDOM uses <strong>JPRX (JSON Pointer Reactive eXpressions)</strong> as its expression language. JPRX
110
110
  extends <a href="https://www.rfc-editor.org/rfc/rfc6901" target="_blank">JSON Pointer (RFC 6901)</a>
111
- with reactivity, relative paths, and helper functions. It also provides deep integration with
112
- <a href="https://json-schema.org/" target="_blank">JSON Schema</a> (Standard Draft 7+) for
111
+ with reactivity, relative paths, and helper functions. cDOM also supports <strong>standard XPath</strong>
112
+ for powerful DOM navigation during element construction. Together with deep integration for
113
+ <a href="https://json-schema.org/" target="_blank">JSON Schema</a> (Standard Draft 7+), cDOM provides
113
114
  industrial-strength data validation and automatic type coercion.
114
115
  </p>
115
116
 
@@ -156,8 +157,57 @@ $('#example').content(hydrate(parseJPRX(cdom)));</code></pre>
156
157
  <p>
157
158
  The UI automatically updates whenever <code>count</code> changes — no manual DOM manipulation required.
158
159
  </p>
160
+
161
+ <!-- ===== XPATH NAVIGATION ===== -->
162
+ <h2 id="xpath-navigation">Using XPath</h2>
159
163
  <p>
160
- The UI automatically updates whenever <code>count</code> changes no manual DOM manipulation required.
164
+ cDOM allows elements to navigate and reference the DOM structure during construction using standard
165
+ <strong>XPath</strong>. This is strictly a <strong>cDOM feature</strong> (not JPRX) used for structural
166
+ navigation.
167
+ </p>
168
+ <p>
169
+ XPath is incredibly useful for keeping your definitions <strong>DRY (Don't Repeat Yourself)</strong> by
170
+ referencing existing attributes instead of repeating values.
171
+ </p>
172
+ <div id="xpath-demo">
173
+ <pre><script>
174
+ examplify(document.currentScript.nextElementSibling, {
175
+ at: document.currentScript.parentElement,
176
+ scripts: ['/lightview.js', '/lightview-x.js', '/lightview-cdom.js'],
177
+ type: 'module',
178
+ height: '180px',
179
+ autoRun: true,
180
+ controls: false
181
+ });
182
+ </script><code>await import('/lightview-cdom.js');
183
+ const { parseJPRX, hydrate } = globalThis.LightviewCDOM;
184
+ const { $ } = Lightview;
185
+
186
+ const cdom = `{
187
+ div: {
188
+ id: "profile-container",
189
+ class: "card",
190
+ "data-theme": "dark",
191
+ children: [
192
+ { h3: "User Profile" },
193
+ { button: {
194
+ id: "7",
195
+ // XPath #../@id gets the "7" from this button's id
196
+ // XPath #../../@id gets "profile-container" from the g-parent div
197
+ children: ["Button ", #../@id, " in section ", #../../@id]
198
+ }}
199
+ ]
200
+ }
201
+ }`;
202
+
203
+ $('#example').content(hydrate(parseJPRX(cdom)));</code></pre>
204
+ </div>
205
+ <p>
206
+ In the example above, the button's text is derived entirely from its own <code>id</code> and its
207
+ parent's <code>id</code> using <code>#../@id</code> and <code>#../../@id</code>.
208
+ </p>
209
+ <p>
210
+ For more details, see the <a href="/docs/cdom-xpath.html">Full XPath Documentation</a>.
161
211
  </p>
162
212
 
163
213
  <!-- ===== ADVANTAGES ===== -->
@@ -596,7 +646,7 @@ const cdomString = `{
596
646
  children: [
597
647
  { h3: "Shopping Cart" },
598
648
  { ul: {
599
- children: =map(/store/cart/items, { li: { children: [_/name, " - ", =currency(_/price)] } })
649
+ children: =map(/store/cart/items, { li: { children: [_/name, " - ", currency(_/price)] } })
600
650
  }},
601
651
  { p: {
602
652
  style: "font-weight: bold; margin-top: 1rem;",
@@ -1007,10 +1057,22 @@ const liveConfig = LightviewCDOM.hydrate(config);</code></pre>
1007
1057
 
1008
1058
  <h3 id="helpers-network">Network</h3>
1009
1059
  <p>HTTP requests.</p>
1010
- <div class="code-block">
1060
+ <div class="code-block" style="margin-bottom: 2rem;">
1011
1061
  <pre><code>fetch(url, options?)
1012
1062
  <span id="helpers-mount"></span>mount(url, options?)</span></code></pre>
1013
1063
  </div>
1064
+
1065
+ <h3 id="helpers-dom">DOM & XPath</h3>
1066
+ <p>Structural navigation and manipulation.</p>
1067
+ <div class="code-block">
1068
+ <pre><code>move(selector, location?), xpath(expression)</code></pre>
1069
+ </div>
1070
+ <p style="margin-top: 1rem;">
1071
+ The <code>src</code> attribute handler is the primary mechanism for Hypermedia updates. It fetches
1072
+ content
1073
+ (cDOM, vDOM, or oDOM) and injects it into the DOM. If the content contains a <code>=move</code>
1074
+ helper, it will automatically relocate itself upon mounting.
1075
+ </p>
1014
1076
  <table class="api-table">
1015
1077
  <thead>
1016
1078
  <tr>
@@ -1049,13 +1111,10 @@ const liveConfig = LightviewCDOM.hydrate(config);</code></pre>
1049
1111
  </tr>
1050
1112
  </tbody>
1051
1113
  </table>
1052
- <p style="margin-top: 1rem;">
1053
- The <code>mount</code> helper is the primary mechanism for **Hypermedia updates**. It fetches content
1054
- (cDOM, vDOM, or oDOM) and injects it into the DOM. If the content contains a <code>=move</code>
1055
- helper, it will automatically relocate itself upon mounting.
1114
+ <p class="text-xs italic opacity-70" style="margin-top: 0.5rem;">
1115
+ <strong>xpath(expression):</strong> Returns a reactive computed signal based on the DOM structure
1116
+ at evaluation time. Note that full MutationObserver reactivity is currently a TODO.
1117
+ For static DOM navigation, use the <code>#</code> prefix in cDOM.
1056
1118
  </p>
1057
-
1058
-
1059
-
1060
1119
  </main>
1061
1120
  </div>
@@ -1,13 +1,30 @@
1
1
  import { marked } from 'marked';
2
- import { processServerScripts } from './processServerScripts.js';
2
+ //import { processServerScripts } from './processServerScripts.js';
3
3
 
4
4
  export const onRequest = async (context) => {
5
5
  const url = new URL(context.request.url);
6
6
  const isMd = url.pathname.endsWith('.md');
7
7
  const isHtml = url.pathname.endsWith('.html') || (url.pathname.endsWith('/') && !url.pathname.includes('.'));
8
+ const isCdom = url.pathname.endsWith('.cdom');
9
+ console.log(`[Middleware] Processing: ${url.pathname}`);
10
+ // Intercept requests for .cdom files to set correct Content-Type
11
+ if (isCdom) {
12
+ console.log(`[Middleware] Processing: ${url.pathname}`);
13
+ const response = await context.next();
14
+ if (response.ok) {
15
+ const text = await response.text();
16
+ return new Response(text, {
17
+ status: 200,
18
+ headers: {
19
+ 'content-type': 'text/plain; charset=utf-8',
20
+ }
21
+ });
22
+ }
23
+ return response;
24
+ }
8
25
 
9
26
  // Intercept requests for .md and .html files
10
- if (isMd || isHtml) {
27
+ if (isMd) { // || isHtml
11
28
  console.log(`[Middleware] Processing: ${url.pathname}`);
12
29
 
13
30
  // Fetch the asset (the actual file)
@@ -23,7 +40,7 @@ export const onRequest = async (context) => {
23
40
  }
24
41
 
25
42
  // 2. Process Server-Side Scripts (runat="server")
26
- processedHtml = await processServerScripts(processedHtml, context.request);
43
+ //processedHtml = await processServerScripts(processedHtml, context.request);
27
44
 
28
45
  return new Response(processedHtml, {
29
46
  status: 200,
@@ -0,0 +1,69 @@
1
+ /**
2
+ * DOM-related JPRX helpers for navigating and querying the DOM structure.
3
+ */
4
+
5
+ /**
6
+ * Registers DOM-related helpers including the xpath() helper.
7
+ * @param {Function} registerHelper - The helper registration function
8
+ */
9
+ export const registerDOMHelpers = (registerHelper) => {
10
+ /**
11
+ * Evaluates an XPath expression against the current DOM element.
12
+ * Returns a computed signal that re-evaluates when observed nodes change.
13
+ * Only supports backward-looking axes (parent, ancestor, preceding-sibling, etc.)
14
+ *
15
+ * @param {string} expression - The XPath expression to evaluate
16
+ * @param {object} context - The evaluation context (contains __node__)
17
+ * @returns {any} The result of the XPath evaluation
18
+ */
19
+ registerHelper('xpath', function (expression) {
20
+ const domNode = this; // 'this' is bound to the DOM element
21
+
22
+ if (!domNode || !(domNode instanceof Element)) {
23
+ console.warn('[Lightview-CDOM] xpath() called without valid DOM context');
24
+ return '';
25
+ }
26
+
27
+ // Validate the expression (no forward-looking axes)
28
+ const forbiddenAxes = /\b(child|descendant|following|following-sibling)::/;
29
+ if (forbiddenAxes.test(expression)) {
30
+ console.error(`[Lightview-CDOM] xpath(): Forward-looking axes not allowed: ${expression}`);
31
+ return '';
32
+ }
33
+
34
+ const hasShorthandChild = /\/[a-zA-Z]/.test(expression) && !expression.startsWith('/html');
35
+ if (hasShorthandChild) {
36
+ console.error(`[Lightview-CDOM] xpath(): Shorthand child axis (/) not allowed: ${expression}`);
37
+ return '';
38
+ }
39
+
40
+ // Get Lightview's computed function
41
+ const LV = globalThis.Lightview;
42
+ if (!LV || !LV.computed) {
43
+ console.warn('[Lightview-CDOM] xpath(): Lightview not available');
44
+ return '';
45
+ }
46
+
47
+ // Return a computed signal that evaluates the XPath
48
+ return LV.computed(() => {
49
+ try {
50
+ const result = document.evaluate(
51
+ expression,
52
+ domNode,
53
+ null,
54
+ XPathResult.STRING_TYPE,
55
+ null
56
+ );
57
+
58
+ // TODO: Set up MutationObserver for reactivity
59
+ // For now, this just evaluates once
60
+ // Future: Observe parent/ancestor/sibling changes
61
+
62
+ return result.stringValue;
63
+ } catch (e) {
64
+ console.error(`[Lightview-CDOM] xpath() evaluation failed:`, e.message);
65
+ return '';
66
+ }
67
+ });
68
+ }, { pathAware: false });
69
+ };
@@ -6,8 +6,10 @@ export const ifHelper = (condition, thenVal, elseVal) => condition ? thenVal : e
6
6
  export const andHelper = (...args) => args.every(Boolean);
7
7
  export const orHelper = (...args) => args.some(Boolean);
8
8
  export const notHelper = (val) => !val;
9
- export const eqHelper = (a, b) => a === b;
10
- export const neqHelper = (a, b) => a !== b;
9
+ export const eqHelper = (a, b) => a == b;
10
+ export const strictEqHelper = (a, b) => a === b;
11
+ export const neqHelper = (a, b) => a != b;
12
+ export const strictNeqHelper = (a, b) => a !== b;
11
13
 
12
14
  export const registerLogicHelpers = (register) => {
13
15
  register('if', ifHelper);
@@ -18,7 +20,11 @@ export const registerLogicHelpers = (register) => {
18
20
  register('not', notHelper);
19
21
  register('!', notHelper);
20
22
  register('eq', eqHelper);
23
+ register('strictEq', strictEqHelper);
21
24
  register('==', eqHelper);
22
- register('===', eqHelper);
25
+ register('===', strictEqHelper);
23
26
  register('neq', neqHelper);
27
+ register('strictNeq', strictNeqHelper);
28
+ register('!=', neqHelper);
29
+ register('!==', strictNeqHelper);
24
30
  };
package/jprx/index.js CHANGED
@@ -19,7 +19,8 @@ export {
19
19
  resolvePathAsContext,
20
20
  resolveExpression,
21
21
  parseCDOMC,
22
- parseJPRX,
22
+ parseCDOMC as parseJPRX,
23
+ parseJPRX as oldParseJPRX,
23
24
  unwrapSignal,
24
25
  getRegistry,
25
26
  BindingTarget
@@ -39,6 +40,7 @@ export { registerStatsHelpers } from './helpers/stats.js';
39
40
  export { registerStateHelpers, set } from './helpers/state.js';
40
41
  export { registerNetworkHelpers } from './helpers/network.js';
41
42
  export { registerCalcHelpers, calc } from './helpers/calc.js';
43
+ export { registerDOMHelpers } from './helpers/dom.js';
42
44
 
43
45
  // Convenience function to register all standard helpers
44
46
  export const registerAllHelpers = (registerFn) => {
package/jprx/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jprx",
3
- "version": "1.2.1",
3
+ "version": "1.3.1",
4
4
  "description": "JSON Path Reactive eXpressions - A reactive expression language for JSON data",
5
5
  "main": "index.js",
6
6
  "type": "module",