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.
@@ -161,9 +161,14 @@
161
161
  class="btn btn-outline btn-benchmark border-slate-300">Run Raw oDOM</button>
162
162
  <button onclick="runBenchmark('vDOM')"
163
163
  class="btn btn-outline btn-benchmark border-slate-300">Run Lightview vDOM</button>
164
+ <button onclick="runBenchmark('JurisODOM')"
165
+ class="btn btn-outline btn-benchmark border-slate-300">Run JurisJS oDOM</button>
164
166
  <button onclick="runBenchmark('TaggedScript')"
165
167
  class="btn btn-outline btn-benchmark border-slate-300">Run Lightview Tagged
166
168
  Script</button>
169
+ <button onclick="runBenchmark('BauTaggedScript')"
170
+ class="btn btn-outline btn-benchmark border-slate-300">Run Bau Tagged
171
+ Script</button>
167
172
  <div class="divider"></div>
168
173
  <button onclick="runAll()" class="btn btn-primary text-white">Run All Benchmarks</button>
169
174
  <button onclick="clearTarget()" class="btn btn-ghost">Clear Target</button>
@@ -303,6 +308,31 @@
303
308
  </div>
304
309
  </div>
305
310
 
311
+ <div class="result-card" id="res-JurisODOM">
312
+ <div class="flex justify-between items-start mb-2">
313
+ <h3 class="font-bold text-slate-500 uppercase text-xs tracking-widest">JurisJS oDOM</h3>
314
+ <div class="flex flex-col items-end gap-1">
315
+ <div class="badge badge-outline badge-xs opacity-50"><span class="size">--</span> KB
316
+ </div>
317
+ <div class="badge badge-accent badge-xs text-white opacity-70"><span
318
+ class="gzip-size">--</span> KB gzip</div>
319
+ <div class="badge badge-ghost badge-sm opacity-50">ms</div>
320
+ </div>
321
+ </div>
322
+ <div class="text-3xl font-black text-slate-800 mb-4"><span class="time">--</span></div>
323
+
324
+ <div class="grid grid-cols-4 gap-2 text-[10px] uppercase font-bold text-slate-400 mb-3">
325
+ <div>Min <span class="min block text-slate-600">--</span></div>
326
+ <div>Avg <span class="avg block text-slate-600">--</span></div>
327
+ <div>Max <span class="max block text-slate-600">--</span></div>
328
+ <div class="text-right">±SD <span class="std block text-slate-600">--</span></div>
329
+ </div>
330
+
331
+ <div class="progress-bar-custom">
332
+ <div class="progress-fill" id="fill-JurisODOM"></div>
333
+ </div>
334
+ </div>
335
+
306
336
  <div class="result-card" id="res-TaggedScript">
307
337
  <div class="flex justify-between items-start mb-2">
308
338
  <h3 class="font-bold text-slate-500 uppercase text-xs tracking-widest">Lightview Tagged
@@ -328,6 +358,32 @@
328
358
  <div class="progress-fill" id="fill-TaggedScript"></div>
329
359
  </div>
330
360
  </div>
361
+
362
+ <div class="result-card" id="res-BauTaggedScript">
363
+ <div class="flex justify-between items-start mb-2">
364
+ <h3 class="font-bold text-slate-500 uppercase text-xs tracking-widest">Bau Tagged
365
+ Script</h3>
366
+ <div class="flex flex-col items-end gap-1">
367
+ <div class="badge badge-outline badge-xs opacity-50"><span class="size">--</span> KB
368
+ </div>
369
+ <div class="badge badge-accent badge-xs text-white opacity-70"><span
370
+ class="gzip-size">--</span> KB gzip</div>
371
+ <div class="badge badge-ghost badge-sm opacity-50">ms</div>
372
+ </div>
373
+ </div>
374
+ <div class="text-3xl font-black text-slate-800 mb-4"><span class="time">--</span></div>
375
+
376
+ <div class="grid grid-cols-4 gap-2 text-[10px] uppercase font-bold text-slate-400 mb-3">
377
+ <div>Min <span class="min block text-slate-600">--</span></div>
378
+ <div>Avg <span class="avg block text-slate-600">--</span></div>
379
+ <div>Max <span class="max block text-slate-600">--</span></div>
380
+ <div class="text-right">±SD <span class="std block text-slate-600">--</span></div>
381
+ </div>
382
+
383
+ <div class="progress-bar-custom">
384
+ <div class="progress-fill" id="fill-BauTaggedScript"></div>
385
+ </div>
386
+ </div>
331
387
  </div>
332
388
 
333
389
  <div class="card bg-white shadow-sm border border-slate-200">
@@ -347,6 +403,7 @@
347
403
  <!-- Core Libraries -->
348
404
  <script src="/lightview.js"></script>
349
405
  <script src="/lightview-x.js"></script>
406
+ <script src="https://cdn.jsdelivr.net/npm/juris@0.9.0/juris.min.js"></script>
350
407
 
351
408
  <script>
352
409
  function generateHTML(count) {
@@ -468,6 +525,27 @@
468
525
  return el;
469
526
  }
470
527
 
528
+ function odomToDOM_Juris(onode) {
529
+ if (typeof onode !== 'object' || onode === null) {
530
+ return document.createTextNode(onode);
531
+ }
532
+
533
+ // Create a temporary container for Juris to render into
534
+ const tempContainer = document.createElement('div');
535
+
536
+ // Create a Juris instance with the oDOM structure as the layout
537
+ const jurisInstance = new Juris({
538
+ debug: false, // Disable logging for performance
539
+ layout: onode
540
+ });
541
+
542
+ // Render to the temporary container
543
+ jurisInstance.render(tempContainer);
544
+ // Return the first child (the actual rendered content)
545
+ return tempContainer.firstChild || tempContainer;
546
+ }
547
+
548
+
471
549
  async function getGzipSize(str) {
472
550
  try {
473
551
  const stream = new Blob([str]).stream();
@@ -518,7 +596,7 @@
518
596
  function clearTarget() {
519
597
  target.replaceChildren();
520
598
  target.innerHTML = '<div class="p-8 text-center text-slate-400 italic">Target is empty.</div>';
521
- ['innerHTML', 'DOMParser', 'vDOM', 'RawVDOM', 'RawoDOM', 'TaggedScript'].forEach(id => {
599
+ ['innerHTML', 'DOMParser', 'vDOM', 'RawVDOM', 'RawoDOM', 'JurisODOM', 'TaggedScript', 'BauTaggedScript'].forEach(id => {
522
600
  const card = document.getElementById(`res-${id}`);
523
601
  card.classList.remove('active');
524
602
  card.querySelector('.time').innerText = '--';
@@ -568,6 +646,12 @@
568
646
  const domEl = odomToDOM(json);
569
647
  target.replaceChildren(domEl);
570
648
  time = performance.now() - start;
649
+ } else if (type === 'JurisODOM') {
650
+ const start = performance.now();
651
+ const json = JSON.parse(data);
652
+ const domEl = odomToDOM_Juris(json);
653
+ target.replaceChildren(domEl);
654
+ time = performance.now() - start;
571
655
  } else if (type === 'TaggedScript') {
572
656
  const existing = document.getElementById('tagged-script-tag');
573
657
  if (existing) existing.remove();
@@ -580,6 +664,19 @@
580
664
  document.body.appendChild(script);
581
665
  });
582
666
  time = window.__taggedBenchmarkTime;
667
+ } else if (type === 'BauTaggedScript') {
668
+ const existing = document.getElementById('bau-tagged-script-tag');
669
+ if (existing) existing.remove();
670
+
671
+ await new Promise(resolve => {
672
+ window.__resolveBauTaggedBenchmark = resolve;
673
+ const script = document.createElement('script');
674
+ script.type = 'module';
675
+ script.id = 'bau-tagged-script-tag';
676
+ script.src = `./benchmarks/bau-tagged-fragment.js?count=${count}&t=${Date.now()}`;
677
+ document.body.appendChild(script);
678
+ });
679
+ time = window.__bauTaggedBenchmarkTime;
583
680
  }
584
681
  return time;
585
682
  }
@@ -592,10 +689,10 @@
592
689
  let data = preGeneratedData;
593
690
 
594
691
  // 1. Preparation phase (outside of timing)
595
- if (!data && type !== 'TaggedScript') {
692
+ if (!data && type !== 'TaggedScript' && type !== 'BauTaggedScript') {
596
693
  if (type === 'innerHTML' || type === 'DOMParser') data = generateHTML(count);
597
694
  if (type === 'vDOM' || type === 'RawVDOM') data = generateVDOM(count);
598
- if (type === 'RawoDOM') data = generateODOM(count);
695
+ if (type === 'RawoDOM' || type === 'JurisODOM') data = generateODOM(count);
599
696
  }
600
697
 
601
698
  console.log(`Starting ${cycles} cycles for ${type}...`);
@@ -628,7 +725,9 @@
628
725
  { id: 'RawVDOM', data: vdomData },
629
726
  { id: 'RawoDOM', data: odomData },
630
727
  { id: 'vDOM', data: vdomData },
631
- { id: 'TaggedScript', data: null }
728
+ { id: 'JurisODOM', data: odomData },
729
+ { id: 'TaggedScript', data: null },
730
+ { id: 'BauTaggedScript', data: null }
632
731
  ];
633
732
 
634
733
  for (const config of configs) {
@@ -15,6 +15,7 @@
15
15
  <button class="btn btn-secondary step-btn" onclick="switchStep(4)">4. Hypermedia</button>
16
16
  <button class="btn btn-secondary step-btn" onclick="switchStep(5)">5. Components</button>
17
17
  <button class="btn btn-secondary step-btn" onclick="switchStep(6)">6. Gating</button>
18
+ <button class="btn btn-secondary step-btn" onclick="switchStep(7)">7. cDOM</button>
18
19
  </div>
19
20
 
20
21
  <!-- Tutorial Layout: Preview on top, Code & Concepts below -->
@@ -679,6 +680,45 @@ $('#app').content(App);`,
679
680
  <li style="margin-bottom: 0.75rem;"><strong>Write your own</strong> — Write your own functions and add the to <code>globalThis</code> to make them available to all templates. They have access to <code>this</code> (the element) and can accept arguments like <code>event</code>.</li>
680
681
  </ul>
681
682
  <p><strong>Try it:</strong> Click the Spam Me button rapidly, or type quickly into the search box to see the limiters in action!</p>`
683
+ },
684
+ 7: {
685
+ options: {
686
+ autoRun: true
687
+ },
688
+ code: `// STEP 7: cDOM - Compressed DOM & JPRX
689
+ await import('/lightview-cdom.js');
690
+ const { parseJPRX, hydrate } = globalThis.LightviewCDOM;
691
+ const { $ } = Lightview;
692
+
693
+ const cdomString = \`{
694
+ div: {
695
+ onmount: =signal(0,"count"),
696
+ children: [
697
+ { h3: ["Standard JPRX Counter"] },
698
+ { p: { children: ["Count: ", =/count] }},
699
+ { div: { children: [
700
+ { button: { onclick: =decrement(/count), children: ["-"] } },
701
+ { button: { onclick: =/count++, children: ["+"] } }
702
+ ]}}
703
+ ]
704
+ }
705
+ }\`;
706
+
707
+ const hydrated = hydrate(parseJPRX(cdomString));
708
+ $('#app').content(hydrated);`,
709
+ concepts: `
710
+ <h3 style="margin-top: 0; color: var(--site-primary);">Step 7: cDOM & JPRX</h3>
711
+ <p><strong><a href="/docs/cdom.html">cDOM</a></strong> (compressed DOM) combined with <strong><a href="/docs/cdom.html#JPRX">JPRX</a></strong> (JSON Path Reactive eXpressions) allows you to define entire reactive UIs as pure JSON-compatible strings.</p>
712
+
713
+ <h4>Key Concepts:</h4>
714
+ <ul style="padding-left: 1.25rem; color: var(--site-text-secondary);">
715
+ <li style="margin-bottom: 0.75rem;"><strong>Portability</strong> — Since the UI is a string, it can be easily stored in databases, sent over the wire, or generated by an LLM without security risks of <code>eval()</code>.</li>
716
+ <li style="margin-bottom: 0.75rem;"><strong>JPRX Expressions</strong> — Use <code>=</code> to denote reactive expressions within the string. For example, <code>=/count</code> binds to a signal.</li>
717
+ <li style="margin-bottom: 0.75rem;"><strong>Flexible Syntax</strong> — Operators and helpers can be called as functions (<code>=decrement(/count)</code>) or using prefix (<code>=--/count</code>), infix (<code>=/count - 1</code>), or postfix (<code>=/count--</code>) notation.</li>
718
+ <li style="margin-bottom: 0.75rem;"><strong>Hydration</strong> — <code>hydrate()</code> turns the parsed JPRX into a live, reactive DOM tree.</li>
719
+ </ul>
720
+ <p><strong>Try it:</strong> Edit the <code>cdomString</code> to change the labels or add new elements!</p>
721
+ `
682
722
  }
683
723
  };
684
724
 
package/docs/index.html CHANGED
@@ -11,7 +11,7 @@
11
11
  More power. Less energy.
12
12
  <br>
13
13
  <a id="link-hero-ai-safe" href="#ai-safe"
14
- style="color: var(--site-primary); font-size: 1.25rem; font-weight: 700; text-decoration: none; display: inline-block; margin-top: 0.5rem; border-bottom: 2px solid var(--site-primary); padding-bottom: 2px;">AI
14
+ style="color: var(--site-primary); font-size: 1.25rem; font-weight: 700; text-decoration: none; display: inline-block; margin-top: 0.25rem; border-bottom: 2px solid var(--site-primary); padding-bottom: 2px;">AI
15
15
  Safe</a>
16
16
  </p>
17
17
  <div class="hero-actions">
@@ -23,12 +23,15 @@
23
23
  <div class="hero-stat-label">Build Steps</div>
24
24
  </div>
25
25
  <div class="hero-stat">
26
- <div class="hero-stat-value">4</div>
26
+ <div class="hero-stat-value"><a id="link-hero-ways-to-build"
27
+ href="http://localhost:3000/docs/api/elements"
28
+ style="color: inherit; text-decoration: none;">5</a></div>
27
29
  <div class="hero-stat-label">Ways to Build</div>
28
30
  </div>
29
31
  <div class="hero-stat">
30
32
  <div class="hero-stat-value">40+</div>
31
- <div class="hero-stat-label">Components</div>
33
+ <div class="hero-stat-label"><a id="link-hero-components" href="http://localhost:3000/docs/components/"
34
+ style="color: inherit; text-decoration: none;">Components</a></div>
32
35
  </div>
33
36
  </div>
34
37
  </div>
@@ -38,79 +41,84 @@
38
41
  <section class="section">
39
42
  <div class="section-content">
40
43
  <div class="section-header">
41
- <h2 class="section-title">Heavy? Not Our Thing.</h2>
42
- <p class="section-subtitle">
43
- Have fun, stay light.
44
- </p>
44
+ <h2 class="section-title">Why Lightview</h2>
45
45
  </div>
46
46
  <div class="feature-grid">
47
- <div class="feature-card">
47
+ <a id="link-feature-signals" href="./api/signals.html" class="feature-card">
48
48
  <div class="feature-icon">
49
49
  <svg viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="2">
50
50
  <circle cx="12" cy="12" r="10" />
51
51
  <path d="M12 6v6l4 2" />
52
52
  </svg>
53
53
  </div>
54
- <h3><a id="link-feature-signals" href="./api/signals.html" class="feature-title">Signals and Object
55
- State Reactivity</a></h3>
54
+ <h3 class="feature-title">Signals and Object State Reactivity</h3>
56
55
  <p class="feature-description">
57
- Fine-grained reactivity with signals and obejct state. Updates propagate automatically—no virtual
58
- DOM diffing needed. As a bonus, session and local storage are also supported.
56
+ Fine-grained reactivity with signals and object state. Updates propagate automatically—no virtual
57
+ DOM diffing needed. Arrays and Dates are directly reactive without destructing or copying. As a
58
+ bonus, session and local storage are also supported.
59
59
  </p>
60
- </div>
61
- <div class="feature-card">
60
+ </a>
61
+ <a id="link-feature-elements" href="./api/elements.html" class="feature-card">
62
62
  <div class="feature-icon">
63
63
  <svg viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="2">
64
64
  <rect x="3" y="3" width="18" height="18" rx="2" />
65
65
  <path d="M3 9h18M9 21V9" />
66
66
  </svg>
67
67
  </div>
68
- <h3><a id="link-feature-elements" href="./api/elements.html" class="feature-title">Multiple Syntaxes</a>
69
- </h3>
68
+ <h3 class="feature-title">Multiple Syntaxes</h3>
70
69
  <p class="feature-description">
71
70
  Tagged API, vDOM, Object DOM, HTML. Pick your style—they all work together.
72
71
  </p>
73
- </div>
74
- <div class="feature-card">
72
+ </a>
73
+ <a id="link-feature-hypermedia" href="/docs/hypermedia/" class="feature-card">
75
74
  <div class="feature-icon">
76
75
  <svg viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="2">
77
76
  <path
78
77
  d="M21 12a9 9 0 01-9 9m9-9a9 9 0 00-9-9m9 9H3m9 9a9 9 0 01-9-9m9 9c1.66 0 3-4 3-9s-1.34-9-3-9m0 18c-1.66 0-3-4-3-9s1.34-9 3-9" />
79
78
  </svg>
80
79
  </div>
81
- <h3><a id="link-feature-hypermedia" href="/docs/hypermedia/" class="feature-title">Hypermedia
82
- Ready</a></h3>
80
+ <h3 class="feature-title">Hypermedia Ready</h3>
83
81
  <p class="feature-description">
84
82
  Fetch HTML, vDOM, or Object DOM with the <code>src</code> attribute. HTMX-like patterns for
85
83
  <code>src</code> and <code>href</code> built right
86
84
  in.
87
85
  </p>
88
- </div>
89
- <div class="feature-card">
86
+ </a>
87
+ <a id="link-feature-components" href="./components/index.html" class="feature-card">
90
88
  <div class="feature-icon">
91
89
  <svg viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="2">
92
90
  <path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5" />
93
91
  </svg>
94
92
  </div>
95
- <h3><a id="link-feature-components" href="./components/index.html" class="feature-title">Component
96
- Library</a></h3>
93
+ <h3 class="feature-title">Component Library</h3>
97
94
  <p class="feature-description">
98
95
  Beautiful, accessible components ready to use. Buttons, modals, forms, charts, and more.
99
96
  </p>
100
- </div>
101
- <div class="feature-card">
97
+ </a>
98
+ <a id="link-feature-zerobuild" href="./getting-started/index.html" class="feature-card">
102
99
  <div class="feature-icon">
103
100
  <svg viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="2">
104
101
  <path
105
102
  d="M14.7 6.3a1 1 0 000 1.4l1.6 1.6a1 1 0 001.4 0l3.77-3.77a6 6 0 01-7.94 7.94l-6.91 6.91a2.12 2.12 0 01-3-3l6.91-6.91a6 6 0 017.94-7.94l-3.76 3.76z" />
106
103
  </svg>
107
104
  </div>
108
- <h3><a id="link-feature-zerobuild" href="./getting-started/index.html" class="feature-title">Zero Build
109
- Step</a></h3>
105
+ <h3 class="feature-title">Zero Build Step</h3>
110
106
  <p class="feature-description">
111
107
  No bundler required. Drop in a script tag and start building. Works everywhere.
112
108
  </p>
113
- </div>
109
+ </a>
110
+ <a id="link-feature-aisafe" href="#ai-safe" class="feature-card">
111
+ <div class="feature-icon">
112
+ <svg viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="2">
113
+ <path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z" />
114
+ </svg>
115
+ </div>
116
+ <h3 class="feature-title">AI Safe</h3>
117
+ <p class="feature-description">
118
+ Build reactive UIs with pure JSON or JSON like data. No eval(), no risky code generation. Safe for
119
+ AI agents.
120
+ </p>
121
+ </a>
114
122
 
115
123
  </div>
116
124
  </div>
@@ -150,7 +158,8 @@
150
158
  <h3 style="color: white; font-size: 1.5rem; margin-bottom: 1rem;">Execution Safety</h3>
151
159
  <p style="color: rgba(255, 255, 255, 0.75); line-height: 1.6;">AI-generated UIs usually require
152
160
  risky JavaScript strings. cDOM creates reactive interfaces using <strong>pure JSON
153
- data</strong> and <a id="link-ai-safe-cdom-helpers" href="/docs/cdom.html#helpers"
161
+ or JSON like data</strong> and <a id="link-ai-safe-cdom-helpers"
162
+ href="/docs/cdom.html#helpers"
154
163
  style="color: #a78bfa; text-decoration: underline; font-weight: 600;">over 50 built-in
155
164
  functions</a> that bring spreadsheet-like
156
165
  functionality to the DOM. No <code>eval()</code>, no <code>new Function()</code>, and <strong>no
@@ -223,7 +232,7 @@
223
232
  <section class="section">
224
233
  <div class="section-content">
225
234
  <div class="section-header">
226
- <h2 class="section-title">Heavy, Complex Frameworks? Lighten Up.</h2>
235
+ <h2 class="section-title">Lighten Up.</h2>
227
236
  <p class="section-subtitle">
228
237
  Get started in minutes. No npm install. No webpack or vite build process. Just code.
229
238
  </p>
package/jprx/README.md CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  **JPRX** is a declarative, reactive expression syntax designed for JSON-based data structures. It extends [JSON Pointer (RFC 6901)](https://www.rfc-editor.org/rfc/rfc6901) with reactivity, relative paths, operator syntax, and a rich library of helper functions.
4
4
 
5
+ > **v1.4.0 Syntax Update**: JPRX now uses a wrapper syntax `=(expr)` for unambiguous expression parsing. Legacy prefix-only syntax (e.g., `=/path`) is still supported but will be deprecated after March 31, 2026.
6
+
5
7
  ## Overview
6
8
 
7
9
  JPRX is a **syntax** and an **expression engine**. While this repository provides the parser and core helper functions, JPRX is intended to be integrated into UI libraries or state management systems that can "hydrate" these expressions into active reactive bindings.
@@ -26,17 +28,17 @@ JPRX extends the base JSON Pointer syntax with:
26
28
 
27
29
  | Feature | Syntax | Description |
28
30
  |---------|--------|-------------|
29
- | **Global Path** | `=/user/name` | Access global state via an absolute path. |
30
- | **Relative Path** | `./count` | Access properties relative to the current context. |
31
- | **Parent Path** | `../id` | Traverse up the state hierarchy (UP-tree search). |
32
- | **Functions** | `=sum(/items...price)` | Call registered core helpers. |
33
- | **Explosion** | `/items...name` | Extract a property from every object in an array (spread). |
34
- | **Operators** | `=++/count`, `=/a + =/b` | Familiar JS-style prefix, postfix, and infix operators. |
31
+ | **Global Path** | `=(/user/name)` | Access global state via an absolute path. |
32
+ | **Relative Path** | `=(./count)` | Access properties relative to the current context. |
33
+ | **Parent Path** | `=(../id)` | Traverse up the state hierarchy (UP-tree search). |
34
+ | **Functions** | `=(sum(/items...price))` | Call registered core helpers. |
35
+ | **Explosion** | `=(/items...name)` | Extract a property from every object in an array (spread). |
36
+ | **Operators** | `=(++/count)`, `=(/a + /b)` | Familiar JS-style prefix, postfix, and infix operators. |
35
37
  | **Placeholders** | `_` (item), `$this`, `$event` | Context-aware placeholders for iteration and interaction. |
36
- | **Two-Way Binding**| `=bind(/user/name)`| Create a managed, two-way reactive link for inputs. |
37
- | **DOM Patches** | `=move(target, loc)`| Decentralized layout: Move/replace host element into a target. |
38
+ | **Two-Way Binding**| `=(bind(/user/name))`| Create a managed, two-way reactive link for inputs. |
39
+ | **DOM Patches** | `=(move(target, loc))`| Decentralized layout: Move/replace host element into a target. |
38
40
 
39
- Once inside a JPRX expression, the `=` prefix is only needed at the start of the expression for paths or function names.
41
+ **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.
40
42
 
41
43
 
42
44
  ## State Management
@@ -104,7 +106,7 @@ To ensure unambiguous data flow, `=bind` only accepts direct paths. It cannot be
104
106
 
105
107
  ### Handling Transformations
106
108
  If you need to transform data during a two-way binding, there are two primary approaches:
107
- 1. **Event-Based**: Use a manual `oninput` handler to apply the transformation, e.g., `=set(/name, upper($event/target/value))`.
109
+ 1. **Event-Based**: Use a manual `oninput` handler to apply the transformation, e.g., `=(set(/name, upper($event/target/value)))`.
108
110
  2. **Schema-Based**: Define a `transform` or `pattern` in the schema for the path. The `=bind` helper will respect the schema rules during the write-back phase.
109
111
 
110
112
  ---
@@ -159,9 +161,9 @@ A modern, lifecycle-based reactive counter:
159
161
  "onmount": "=state({ count: 0 }, { name: 'counter', schema: 'auto', scope: $this })",
160
162
  "children": [
161
163
  { "h2": "Modern JPRX Counter" },
162
- { "p": ["Current Count: ", "=/counter/count"] },
163
- { "button": { "onclick": "=++/counter/count", "children": ["+"] } },
164
- { "button": { "onclick": "=--/counter/count", "children": ["-"] } }
164
+ { "p": ["Current Count: ", "=(/counter/count)"] },
165
+ { "button": { "onclick": "=(++/counter/count)", "children": ["+"] } },
166
+ { "button": { "onclick": "=(--/counter/count)", "children": ["-"] } }
165
167
  ]
166
168
  }
167
169
  }
package/jprx/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jprx",
3
- "version": "1.3.1",
3
+ "version": "1.4.0",
4
4
  "description": "JSON Path Reactive eXpressions - A reactive expression language for JSON data",
5
5
  "main": "index.js",
6
6
  "type": "module",
package/jprx/parser.js CHANGED
@@ -1730,7 +1730,44 @@ export const parseJPRX = (input) => {
1730
1730
  continue;
1731
1731
  }
1732
1732
 
1733
- // Handle JPRX expressions starting with = (MUST come before word handler!)
1733
+ // Handle JPRX expressions starting with = or #
1734
+ // New wrapper syntax: =(expr) or #(xpath)
1735
+ if ((char === '=' || char === '#') && input[i + 1] === '(') {
1736
+ const prefix = char;
1737
+ let expr = prefix;
1738
+ i++; // skip = or #
1739
+ let parenDepth = 0;
1740
+ let inExprQuote = null;
1741
+
1742
+ while (i < len) {
1743
+ const c = input[i];
1744
+
1745
+ if (inExprQuote) {
1746
+ if (c === inExprQuote && input[i - 1] !== '\\') inExprQuote = null;
1747
+ } else if (c === '"' || c === "'") {
1748
+ inExprQuote = c;
1749
+ } else {
1750
+ if (c === '(') parenDepth++;
1751
+ else if (c === ')') {
1752
+ parenDepth--;
1753
+ if (parenDepth === 0) {
1754
+ expr += c;
1755
+ i++;
1756
+ break;
1757
+ }
1758
+ }
1759
+ }
1760
+
1761
+ expr += c;
1762
+ i++;
1763
+ }
1764
+
1765
+ // Use JSON.stringify to safely quote and escape the expression
1766
+ result += JSON.stringify(expr);
1767
+ continue;
1768
+ }
1769
+
1770
+ // Handle legacy JPRX expressions starting with = (without parentheses)
1734
1771
  if (char === '=') {
1735
1772
  let expr = '';
1736
1773
  let parenDepth = 0;
@@ -1792,6 +1829,53 @@ export const parseJPRX = (input) => {
1792
1829
  continue;
1793
1830
  }
1794
1831
 
1832
+ // Handle XPath expressions starting with # (legacy syntax)
1833
+ if (char === '#') {
1834
+ let expr = '';
1835
+ let parenDepth = 0;
1836
+ let bracketDepth = 0;
1837
+ let inExprQuote = null;
1838
+
1839
+ while (i < len) {
1840
+ const c = input[i];
1841
+
1842
+ if (inExprQuote) {
1843
+ if (c === inExprQuote && input[i - 1] !== '\\') inExprQuote = null;
1844
+ } else if (c === '"' || c === "'") {
1845
+ inExprQuote = c;
1846
+ } else {
1847
+ // Check for break BEFORE updating depth
1848
+ if (parenDepth === 0 && bracketDepth === 0) {
1849
+ // Break on structural characters at depth 0
1850
+ if (/[}[\],:]/.test(c) && expr.length > 1) break;
1851
+ // For whitespace, peek ahead
1852
+ if (/\s/.test(c)) {
1853
+ let j = i + 1;
1854
+ while (j < len && /\s/.test(input[j])) j++;
1855
+ if (j < len) {
1856
+ const nextChar = input[j];
1857
+ if (nextChar === '}' || nextChar === ',' || nextChar === ']') {
1858
+ break;
1859
+ }
1860
+ }
1861
+ }
1862
+ }
1863
+
1864
+ if (c === '(') parenDepth++;
1865
+ else if (c === ')') parenDepth--;
1866
+ else if (c === '[') bracketDepth++;
1867
+ else if (c === ']') bracketDepth--;
1868
+ }
1869
+
1870
+ expr += c;
1871
+ i++;
1872
+ }
1873
+
1874
+ // Use JSON.stringify to safely quote and escape the expression
1875
+ result += JSON.stringify(expr);
1876
+ continue;
1877
+ }
1878
+
1795
1879
  // Handle unquoted property names, identifiers, paths, and FUNCTION CALLS
1796
1880
  if (/[a-zA-Z_$\/.\/]/.test(char)) {
1797
1881
  let word = '';