lightview 2.4.4 → 2.5.0
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/AI-GUIDANCE.md +28 -11
- package/README.md +4 -4
- package/docs/assets/styles/site.css +16 -7
- package/docs/benchmarks/bau-tagged-fragment.js +41 -0
- package/docs/cdom-xpath.html +27 -9
- package/docs/cdom.html +186 -125
- package/docs/dom-benchmark.html +103 -4
- package/docs/getting-started/index.html +40 -0
- package/docs/index.html +40 -31
- package/jprx/README.md +15 -13
- package/jprx/package.json +1 -1
- package/jprx/parser.js +85 -1
- package/lightview-all.js +131 -34
- package/lightview-cdom.js +131 -34
- package/package.json +3 -3
- package/scratch.html +83 -0
- package/src/lightview-cdom.js +86 -44
package/AI-GUIDANCE.md
CHANGED
|
@@ -151,8 +151,25 @@ Lightview X allows embedded `${}` expressions in attributes for simple reactivit
|
|
|
151
151
|
|
|
152
152
|
**cDOM (Computed DOM)** and **JPRX (JSON Reactive Expressions)** allow you to build fully reactive UIs using **only JSON**.
|
|
153
153
|
|
|
154
|
-
###
|
|
155
|
-
JPRX
|
|
154
|
+
### Expression Syntax
|
|
155
|
+
* **`=(expr)` (JPRX)**: Wraps reactive expressions that navigate **reactive state** (Signals/States). Use `=(/path)` for paths, `=(function(...))` for helpers.
|
|
156
|
+
* **`#(xpath)` (cDOM XPath)**: Wraps XPath expressions that navigate the **DOM tree** during construction. Use `#(../../@id)` to navigate ancestors and access attributes.
|
|
157
|
+
|
|
158
|
+
**Note**: For initialization functions like `state()` and `signal()`, use `=function(...)` without the outer wrapper, as they execute once on mount rather than being reactive expressions.
|
|
159
|
+
|
|
160
|
+
### Name Resolution & Scoping
|
|
161
|
+
JPRX uses an **up-tree search** to find signals or states:
|
|
162
|
+
1. **Search starts** at the element where the expression is defined.
|
|
163
|
+
2. **Bubbles up** through parent elements looking for a registered name.
|
|
164
|
+
3. **Falls back** to the global registry.
|
|
165
|
+
|
|
166
|
+
**The `$this` Placeholder**:
|
|
167
|
+
Use `{ scope: $this }` in `=state` or `=signal` to register data specifically at the current element's level. This is essential for creating multiple independent instances of a component.
|
|
168
|
+
|
|
169
|
+
### The `...` (Explosion) Operator
|
|
170
|
+
* **Infix Mapping (`/path...property`)**: Extracts `property` from every object in the `/path` array. **Inside a function call, it automatically explodes** the results into separate arguments.
|
|
171
|
+
* **Trailing Spread (`/path...`)**: Spreads a direct array reference into individual arguments.
|
|
172
|
+
* **Warning**: Never write `/path...property...`. The trailing dots will be incorrectly included in the property name lookup.
|
|
156
173
|
|
|
157
174
|
| Category | Helpers |
|
|
158
175
|
| :--- | :--- |
|
|
@@ -163,7 +180,7 @@ JPRX supports a wide range of helpers. AI agents should use these instead of try
|
|
|
163
180
|
| **Stats** | `sum`, `avg`, `min`, `max`, `median`, `stdev`, `variance` |
|
|
164
181
|
| **Data** | `lookup(val, searchArr, resultArr)` (VLOOKUP-style) |
|
|
165
182
|
| **State** | `state(val, opts)`, `set(path, val)`, `bind(path)`, `increment`, `decrement`, `toggle` |
|
|
166
|
-
| **DOM** |
|
|
183
|
+
| **DOM** | `#(path)` (XPath Navigation), `move(target, loc)` |
|
|
167
184
|
| **Net** | `fetchHelper(url, opts)`, `mount(url, opts)` |
|
|
168
185
|
|
|
169
186
|
### The "Decentralized Layout" Pattern (AI Strategy)
|
|
@@ -177,7 +194,7 @@ JPRX supports a wide range of helpers. AI agents should use these instead of try
|
|
|
177
194
|
div: {
|
|
178
195
|
id: "widget-1",
|
|
179
196
|
onmount: ["=state({val:0}, {name:'w1', scope:$this})", "=move('#sidebar')"],
|
|
180
|
-
children: [ { p: "Val:
|
|
197
|
+
children: [ { p: ["Val: ", =(/w1/val)] } ]
|
|
181
198
|
}
|
|
182
199
|
}
|
|
183
200
|
```
|
|
@@ -247,13 +264,13 @@ router.use(
|
|
|
247
264
|
<a name="deep-links"></a>
|
|
248
265
|
## 8. Deep Links & Resources
|
|
249
266
|
|
|
250
|
-
* **Documentation Home**: [
|
|
251
|
-
* **Core Logic**: [
|
|
252
|
-
* **Extension (Hypermedia)**: [
|
|
253
|
-
* **Router**: [
|
|
254
|
-
* **CDOM/JPRX Parser**: [
|
|
255
|
-
* **Component Index**: [
|
|
256
|
-
* **JPRX Helpers**: [
|
|
267
|
+
* **Documentation Home**: [docs/index.html](docs/index.html)
|
|
268
|
+
* **Core Logic**: [lightview.js](lightview.js) and [docs/api/index.html](docs/api/index.html)
|
|
269
|
+
* **Extension (Hypermedia)**: [lightview-x.js](lightview-x.js) and [docs/api/hypermedia.html](docs/api/hypermedia.html)
|
|
270
|
+
* **Router**: [lightview-router.js](lightview-router.js) and [docs/router.html](docs/router.html)
|
|
271
|
+
* **CDOM/JPRX Parser**: [lightview-cdom.js](lightview-cdom.js) and [docs/cdom.html](docs/cdom.html)
|
|
272
|
+
* **Component Index**: [components/index.js](components/index.js) and [docs/components/index.html](docs/components/index.html)
|
|
273
|
+
* **JPRX Helpers**: [jprx/helpers/](jprx/helpers/) and [docs/cdom#helpers](docs/cdom.html#helpers)
|
|
257
274
|
|
|
258
275
|
---
|
|
259
276
|
© 2026 AnyWhichWay LLC.
|
package/README.md
CHANGED
|
@@ -43,17 +43,17 @@ Traditional UI development requires AI agents to generate imperative JavaScript
|
|
|
43
43
|
|
|
44
44
|
```json
|
|
45
45
|
{
|
|
46
|
-
"state": { "count": 0 },
|
|
47
46
|
"div": {
|
|
47
|
+
"onmount": "=state({ count: 0 }, 'counter')",
|
|
48
48
|
"children": [
|
|
49
|
-
{ "p": "
|
|
50
|
-
{ "button": { "onclick": "
|
|
49
|
+
{ "p": ["Count: ", "=(/counter/count)"] },
|
|
50
|
+
{ "button": { "onclick": "=(++/counter/count)", "children": ["Increment"] } }
|
|
51
51
|
]
|
|
52
52
|
}
|
|
53
53
|
}
|
|
54
54
|
```
|
|
55
55
|
|
|
56
|
-
This JSON is **the entire application**. The `
|
|
56
|
+
This JSON is **the entire application**. The `=()` expressions are JPRX—a sandboxed, spreadsheet-like formula language (inspired by **XPath** and **JSON Pointers**) that resolves paths, calls registered helpers, and triggers reactivity automatically.
|
|
57
57
|
|
|
58
58
|
### Learn More
|
|
59
59
|
|
|
@@ -498,7 +498,11 @@ img {
|
|
|
498
498
|
|
|
499
499
|
/* ============= Hero Section ============= */
|
|
500
500
|
.hero {
|
|
501
|
-
|
|
501
|
+
max-height: calc(75vh - var(--site-nav-height));
|
|
502
|
+
display: flex;
|
|
503
|
+
align-items: center;
|
|
504
|
+
justify-content: center;
|
|
505
|
+
padding: 1rem 1.5rem;
|
|
502
506
|
text-align: center;
|
|
503
507
|
background: linear-gradient(135deg, var(--site-bg) 0%, var(--site-bg-alt) 100%);
|
|
504
508
|
position: relative;
|
|
@@ -526,7 +530,7 @@ img {
|
|
|
526
530
|
.hero-logo {
|
|
527
531
|
width: 100px;
|
|
528
532
|
height: 100px;
|
|
529
|
-
margin-bottom:
|
|
533
|
+
margin-bottom: 0.25rem;
|
|
530
534
|
}
|
|
531
535
|
|
|
532
536
|
|
|
@@ -534,7 +538,7 @@ img {
|
|
|
534
538
|
.hero h1 {
|
|
535
539
|
font-size: 3.5rem;
|
|
536
540
|
font-weight: 800;
|
|
537
|
-
margin: 0 0 0.
|
|
541
|
+
margin: 0 0 0.25rem;
|
|
538
542
|
background: linear-gradient(135deg, var(--site-primary) 0%, var(--site-accent) 100%);
|
|
539
543
|
-webkit-background-clip: text;
|
|
540
544
|
-webkit-text-fill-color: transparent;
|
|
@@ -544,14 +548,14 @@ img {
|
|
|
544
548
|
.hero-tagline {
|
|
545
549
|
font-size: 1.5rem;
|
|
546
550
|
color: var(--site-text-secondary);
|
|
547
|
-
margin: 0 0
|
|
551
|
+
margin: 0 0 0.5rem;
|
|
548
552
|
font-weight: 500;
|
|
549
553
|
}
|
|
550
554
|
|
|
551
555
|
.hero-description {
|
|
552
556
|
font-size: 1.125rem;
|
|
553
557
|
color: var(--site-text-secondary);
|
|
554
|
-
margin: 0 0
|
|
558
|
+
margin: 0 0 1.25rem;
|
|
555
559
|
max-width: 600px;
|
|
556
560
|
margin-left: auto;
|
|
557
561
|
margin-right: auto;
|
|
@@ -568,8 +572,8 @@ img {
|
|
|
568
572
|
display: flex;
|
|
569
573
|
gap: 3rem;
|
|
570
574
|
justify-content: center;
|
|
571
|
-
margin-top:
|
|
572
|
-
padding-top:
|
|
575
|
+
margin-top: 1.5rem;
|
|
576
|
+
padding-top: 1rem;
|
|
573
577
|
border-top: 1px solid var(--site-border);
|
|
574
578
|
}
|
|
575
579
|
|
|
@@ -644,6 +648,9 @@ img {
|
|
|
644
648
|
}
|
|
645
649
|
|
|
646
650
|
.feature-card {
|
|
651
|
+
display: block;
|
|
652
|
+
color: inherit;
|
|
653
|
+
text-decoration: none;
|
|
647
654
|
background: var(--site-surface);
|
|
648
655
|
border: 1px solid var(--site-border);
|
|
649
656
|
border-radius: var(--site-radius-lg);
|
|
@@ -673,6 +680,7 @@ img {
|
|
|
673
680
|
font-size: 1.125rem;
|
|
674
681
|
font-weight: 600;
|
|
675
682
|
margin: 0 0 0.5rem;
|
|
683
|
+
color: var(--site-primary);
|
|
676
684
|
}
|
|
677
685
|
|
|
678
686
|
.feature-description {
|
|
@@ -681,6 +689,7 @@ img {
|
|
|
681
689
|
font-size: 0.9375rem;
|
|
682
690
|
}
|
|
683
691
|
|
|
692
|
+
|
|
684
693
|
/* ============= Code Blocks ============= */
|
|
685
694
|
pre,
|
|
686
695
|
code {
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
(async function () {
|
|
2
|
+
// In ES modules, document.currentScript is null, so we use import.meta.url
|
|
3
|
+
const urlParams = new URLSearchParams(import.meta.url.split('?')[1]);
|
|
4
|
+
const count = parseInt(urlParams.get('count')) || 1000;
|
|
5
|
+
|
|
6
|
+
// Settle inside the script context
|
|
7
|
+
if (window.gc) window.gc();
|
|
8
|
+
await new Promise(r => setTimeout(r, 100));
|
|
9
|
+
|
|
10
|
+
const start = performance.now();
|
|
11
|
+
|
|
12
|
+
// Import Bau from CDN
|
|
13
|
+
const { default: Bau } = await import('https://cdn.jsdelivr.net/npm/@grucloud/bau@0.106.0/+esm');
|
|
14
|
+
const bau = Bau();
|
|
15
|
+
const { section, header, h2, h3, div, article, p, span, footer } = bau.tags;
|
|
16
|
+
|
|
17
|
+
const items = [];
|
|
18
|
+
for (let i = 0; i < count; i++) {
|
|
19
|
+
items.push(article({ class: 'item-card' },
|
|
20
|
+
header(h3({ class: 'item-title' }, `Item ${i}`)),
|
|
21
|
+
div({ class: 'item-content' }, p({ class: 'item-description' }, `Detailed description for item ${i} in the benchmark list.`)),
|
|
22
|
+
footer({ class: 'item-footer' }, span(`Metadata for item ${i}`))
|
|
23
|
+
));
|
|
24
|
+
}
|
|
25
|
+
const fragment = section({ class: 'benchmark-container' },
|
|
26
|
+
header(h2('Benchmark Results')),
|
|
27
|
+
div({ class: 'items-grid' }, ...items)
|
|
28
|
+
);
|
|
29
|
+
const target = document.getElementById('benchmark-target');
|
|
30
|
+
target.replaceChildren(fragment);
|
|
31
|
+
|
|
32
|
+
const end = performance.now();
|
|
33
|
+
|
|
34
|
+
// Store timing in a global for the driver to pick up
|
|
35
|
+
window.__bauTaggedBenchmarkTime = end - start;
|
|
36
|
+
|
|
37
|
+
// Signal completion
|
|
38
|
+
if (window.__resolveBauTaggedBenchmark) {
|
|
39
|
+
window.__resolveBauTaggedBenchmark();
|
|
40
|
+
}
|
|
41
|
+
})();
|
package/docs/cdom-xpath.html
CHANGED
|
@@ -69,9 +69,9 @@ const cdom = `{
|
|
|
69
69
|
{ h3: "User Profile" },
|
|
70
70
|
{ button: {
|
|
71
71
|
id: "7",
|
|
72
|
-
// XPath
|
|
73
|
-
// XPath
|
|
74
|
-
children: ["Button ",
|
|
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
75
|
}}
|
|
76
76
|
]
|
|
77
77
|
}
|
|
@@ -116,17 +116,35 @@ $('#example').content(hydrate(parseJPRX(cdom)));</code></pre>
|
|
|
116
116
|
<strong>Forbidden:</strong> <code>child::</code>, <code>descendant::</code>, and <code>following::</code>
|
|
117
117
|
axes are disabled as they could create infinite loops or reference nodes that do not exist yet.
|
|
118
118
|
</p>
|
|
119
|
+
<div
|
|
120
|
+
style="margin-top: 1rem; padding: 1rem; background: var(--site-bg-secondary); border-left: 4px solid var(--site-primary); border-radius: var(--site-radius);">
|
|
121
|
+
<p style="margin: 0; font-size: 0.95rem;">
|
|
122
|
+
<strong>💡 Need Forward-Looking XPath?</strong> For advanced use cases requiring <code>child::</code>,
|
|
123
|
+
<code>descendant::</code>, or <code>following::</code> axes, use the reactive <code>=xpath()</code>
|
|
124
|
+
helper
|
|
125
|
+
in JPRX instead of static <code>#()</code> in cDOM. The reactive helper evaluates XPath expressions
|
|
126
|
+
after the DOM is fully constructed and can access any part of the tree. See the
|
|
127
|
+
<a href="#reactive-xpath">Reactive XPath section</a> below for details.
|
|
128
|
+
</p>
|
|
129
|
+
</div>
|
|
119
130
|
|
|
120
131
|
<h2 id="reactive-xpath">Reactive XPath (<code>=xpath()</code>)</h2>
|
|
121
132
|
<p>
|
|
122
|
-
If you need an XPath expression to update reactively
|
|
123
|
-
use
|
|
133
|
+
If you need an XPath expression to update reactively when attributes elsewhere in the DOM change,
|
|
134
|
+
or if you need to use <strong>forward-looking axes</strong> (like <code>child::</code>,
|
|
135
|
+
<code>descendant::</code>,
|
|
136
|
+
or <code>following::</code>), use the <code>=xpath()</code> helper within a JPRX expression.
|
|
137
|
+
</p>
|
|
138
|
+
<p>
|
|
139
|
+
Unlike static <code>#()</code> expressions which are evaluated once during construction,
|
|
140
|
+
<code>=xpath()</code> creates a reactive computed signal that re-evaluates whenever its dependencies change,
|
|
141
|
+
and it has <strong>no axis restrictions</strong> since the DOM is already fully constructed.
|
|
124
142
|
</p>
|
|
125
143
|
<div class="code-block">
|
|
126
144
|
<pre><code>{
|
|
127
145
|
div: {
|
|
128
|
-
title: "=xpath('../@data-section')",
|
|
129
|
-
class: "=concat('item ', xpath('../@theme'))"
|
|
146
|
+
title: "=(xpath('../@data-section'))",
|
|
147
|
+
class: "=(concat('item ', xpath('../@theme')))"
|
|
130
148
|
}
|
|
131
149
|
}</code></pre>
|
|
132
150
|
</div>
|
|
@@ -142,12 +160,12 @@ $('#example').content(hydrate(parseJPRX(cdom)));</code></pre>
|
|
|
142
160
|
{
|
|
143
161
|
button: {
|
|
144
162
|
id: "7",
|
|
145
|
-
children: [
|
|
163
|
+
children: [#(../@id)]
|
|
146
164
|
}
|
|
147
165
|
}
|
|
148
166
|
|
|
149
167
|
// Support for complex paths with predicates
|
|
150
|
-
{ span: [#ancestor::div[@data-role='container']/@title] }</code></pre>
|
|
168
|
+
{ span: [#(ancestor::div[@data-role='container']/@title)] }</code></pre>
|
|
151
169
|
</div>
|
|
152
170
|
|
|
153
171
|
<h2 id="safety">Security & Safety</h2>
|