lightview 2.4.7 → 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 CHANGED
@@ -151,9 +151,11 @@ 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
- ### Syntax Prefixes
155
- * **`=` (JPRX)**: Navigates **reactive state** (Signals/States). Starts with `=/` for absolute paths.
156
- * **`#` (cDOM XPath)**: Navigates the **DOM tree** during construction. Use `#../../@id` to skip the text node parent and reach an ancestor's attribute.
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.
157
159
 
158
160
  ### Name Resolution & Scoping
159
161
  JPRX uses an **up-tree search** to find signals or states:
@@ -178,7 +180,7 @@ Use `{ scope: $this }` in `=state` or `=signal` to register data specifically at
178
180
  | **Stats** | `sum`, `avg`, `min`, `max`, `median`, `stdev`, `variance` |
179
181
  | **Data** | `lookup(val, searchArr, resultArr)` (VLOOKUP-style) |
180
182
  | **State** | `state(val, opts)`, `set(path, val)`, `bind(path)`, `increment`, `decrement`, `toggle` |
181
- | **DOM** | `#path` (XPath Navigation), `move(target, loc)` |
183
+ | **DOM** | `#(path)` (XPath Navigation), `move(target, loc)` |
182
184
  | **Net** | `fetchHelper(url, opts)`, `mount(url, opts)` |
183
185
 
184
186
  ### The "Decentralized Layout" Pattern (AI Strategy)
@@ -192,7 +194,7 @@ Use `{ scope: $this }` in `=state` or `=signal` to register data specifically at
192
194
  div: {
193
195
  id: "widget-1",
194
196
  onmount: ["=state({val:0}, {name:'w1', scope:$this})", "=move('#sidebar')"],
195
- children: [ { p: "Val: =/w1/val" } ]
197
+ children: [ { p: ["Val: ", =(/w1/val)] } ]
196
198
  }
197
199
  }
198
200
  ```
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": "{$count}" },
50
- { "button": { "onclick": "{set('count', add(count, 1))}", "children": ["Increment"] } }
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 `{...}` 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.
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
 
@@ -69,9 +69,9 @@ const cdom = `{
69
69
  { h3: "User Profile" },
70
70
  { button: {
71
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]
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 if attributes elsewhere in the DOM change,
123
- use the <code>=xpath()</code> helper within a JPRX expression.
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: [#../@id]
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>
package/docs/cdom.html CHANGED
@@ -89,6 +89,20 @@
89
89
  helper functions, and integration patterns are subject to change as we continue to evolve the
90
90
  library. Use with caution in production environments.
91
91
  </p>
92
+ <p
93
+ style="margin: 0.75rem 0 0 0; padding-top: 0.75rem; border-top: 1px solid var(--site-border); font-size: 0.95rem; opacity: 0.9;">
94
+ <strong>v2.5.0 Syntax Update:</strong> Starting with Lightview v2.5.0 and JPRX v1.4.0,
95
+ expressions use a new wrapper syntax:
96
+ <code
97
+ style="background: var(--site-bg-secondary); padding: 0.125rem 0.375rem; border-radius: 3px;">=(expr)</code>
98
+ for JPRX and
99
+ <code
100
+ style="background: var(--site-bg-secondary); padding: 0.125rem 0.375rem; border-radius: 3px;">#(xpath)</code>
101
+ for XPath.
102
+ The legacy prefix-only syntax (e.g., <code
103
+ style="background: var(--site-bg-secondary); padding: 0.125rem 0.375rem; border-radius: 3px;">=path</code>)
104
+ will be <strong>fully deprecated after March 31, 2026</strong>.
105
+ </p>
92
106
  </div>
93
107
  </div>
94
108
  </div>
@@ -136,10 +150,10 @@ const cdom = `{
136
150
  "id": "Counter",
137
151
  "onmount": "=state({ count: 0 }, { name: 'local', schema: 'auto', scope: $this })",
138
152
  "children": [
139
- { "h2": "#../../@id" }, // XPath shorthand: text node -> h2 -> div
140
- { "p": ["Count: ", "=/local/count"] },
141
- { "button": { "onclick": "=++/local/count", "children": ["+"] } },
142
- { "button": { "onclick": "=--/local/count", "children": ["-"] } }
153
+ { "h2": "#(../../@id)" }, // XPath: text node -> h2 -> div
154
+ { "p": ["Count: ", "=(/local/count)"] },
155
+ { "button": { "onclick": "=(++/local/count)", "children": ["+"] } },
156
+ { "button": { "onclick": "=(--/local/count)", "children": ["-"] } }
143
157
  ]
144
158
  }
145
159
  }`;
@@ -158,10 +172,11 @@ $('#example').content(hydrate(parseJPRX(cdom)));</code></pre>
158
172
  <li><code>=/local/count</code> — A path that reactively displays the count value.</li>
159
173
  </ul>
160
174
  <p style="font-size: 0.95rem; font-style: italic; color: var(--site-text-secondary);">
161
- <strong>Note:</strong> In cDOM, <code>#</code> is the prefix for <strong>XPath</strong> expressions that
162
- navigate and extract data from the DOM, while <code>=</code> is the prefix for <strong>JPRX</strong>
163
- expressions that navigate or apply functions to
164
- reactive state.
175
+ <strong>Note:</strong> In cDOM, expressions use a wrapper syntax for clarity: <code>#(xpath)</code> for
176
+ <strong>XPath</strong> expressions that
177
+ navigate and extract data from the DOM, and <code>=(expr)</code> for <strong>JPRX</strong>
178
+ expressions that navigate or apply functions to reactive state. Legacy syntax without parentheses is still
179
+ supported.
165
180
  </p>
166
181
  <p>
167
182
  The UI automatically updates whenever <code>count</code> changes — no manual DOM manipulation required. It
@@ -226,9 +241,9 @@ const cdom = `{
226
241
  { h3: "User Profile" },
227
242
  { button: {
228
243
  id: "7",
229
- // XPath #../@id gets the "7" from this button's id
230
- // XPath #../../@id gets "profile-container" from the g-parent div
231
- children: ["Button ", #../@id, " in section ", #../../@id]
244
+ // XPath #(../@id) gets the "7" from this button's id
245
+ // XPath #(../../@id) gets "profile-container" from the g-parent div
246
+ children: ["Button ", #(../@id), " in section ", #(../../@id)]
232
247
  }}
233
248
  ]
234
249
  }
@@ -238,7 +253,7 @@ $('#example').content(hydrate(parseJPRX(cdom)));</code></pre>
238
253
  </div>
239
254
  <p>
240
255
  In the example above, the button's text is derived entirely from its own <code>id</code> and its
241
- parent's <code>id</code> using <code>#../@id</code> and <code>#../../@id</code>.
256
+ parent's <code>id</code> using <code>#(../@id)</code> and <code>#(../../@id)</code>.
242
257
  </p>
243
258
  <p>
244
259
  For more details, see the <a href="/docs/cdom-xpath.html">Full XPath Documentation</a>.
@@ -252,54 +267,61 @@ $('#example').content(hydrate(parseJPRX(cdom)));</code></pre>
252
267
  with reactivity, relative paths, and helper functions.
253
268
  </p>
254
269
 
255
- <h3 id="JPRX-delimiters">Delimiters</h3>
270
+ <h3 id="JPRX-delimiters">Expression Syntax</h3>
256
271
  <p>
257
- JPRX expressions begin with a <code>=</code> delimiter:
272
+ JPRX expressions use a <strong>wrapper syntax</strong> for unambiguous parsing. The recommended syntax is
273
+ <code>=(expression)</code>:
258
274
  </p>
259
275
  <table class="api-table">
260
276
  <thead>
261
277
  <tr>
262
- <th>Delimiter</th>
278
+ <th>Syntax</th>
263
279
  <th>Purpose</th>
264
280
  <th>Example</th>
265
281
  </tr>
266
282
  </thead>
267
283
  <tbody>
268
284
  <tr>
269
- <td><code>=/</code></td>
285
+ <td><code>=(/path)</code></td>
270
286
  <td>Access a path in the global registry</td>
271
- <td><code>=/users/0/name</code></td>
287
+ <td><code>=(/users/0/name)</code></td>
272
288
  </tr>
273
289
  <tr>
274
- <td><code>=function(</code></td>
290
+ <td><code>=(function(...))</code></td>
275
291
  <td>Call a helper function</td>
276
- <td><code>=sum(/items...price)</code></td>
292
+ <td><code>=(sum(/items...price))</code></td>
293
+ </tr>
294
+ <tr>
295
+ <td><code>#(xpath)</code></td>
296
+ <td>XPath expression for DOM navigation</td>
297
+ <td><code>#(../../@id)</code></td>
277
298
  </tr>
278
299
  </tbody>
279
300
  </table>
280
301
  <p>
281
- Once inside a JPRX expression, paths follow <strong>JSON Pointer</strong> syntax. The <code>=</code>
282
- is only needed at the start of the expression for paths or function names.
302
+ The wrapper syntax <code>=()</code> and <code>#()</code> makes expressions unambiguous and eliminates
303
+ conflicts with literal strings.
304
+ Legacy syntax without parentheses (e.g., <code>=/path</code>) is still supported for backward compatibility.
283
305
  </p>
284
306
 
285
- <h3 id="JPRX-escaping">Escaping the <code>=</code> Delimiter</h3>
307
+ <h3 id="JPRX-literal-strings">Literal Strings</h3>
286
308
  <p>
287
- When using <strong>oDOM</strong> or <strong>vDOM</strong> (Object DOM), any string value starting with
288
- <code>=</code> is interpreted as a JPRX expression. If you need a literal string that begins with
289
- an equals sign (e.g., a mathematical equation or status message), you can escape it by prefixing
290
- with a single quote:
309
+ With the new wrapper syntax, you no longer need escape sequences. Any string that doesn't match the wrapper
310
+ pattern
311
+ is treated as a literal:
291
312
  </p>
292
313
  <div class="code-block" style="margin-bottom: 1rem;">
293
- <pre><code>// Interpreted as JPRX expression:
294
- { "p": "=/user/name" } // Resolves the path /user/name
314
+ <pre><code>// JPRX expression (wrapped):
315
+ { "p": "=(/user/name)" } // Resolves the path /user/name
295
316
 
296
- // Escaped to produce a literal string:
297
- { "p": "'=E=mc²" } // Renders as "=E=mc²"
298
- { "p": "'=42" } // Renders as "=42"</code></pre>
317
+ // Literal strings (no wrapper):
318
+ { "p": "=E=mc²" } // Renders as "=E=mc²"
319
+ { "p": "=42" } // Renders as "=42"
320
+ { "p": "#ff0000" } // Renders as "#ff0000" (hex color)</code></pre>
299
321
  </div>
300
322
  <p>
301
- The single-quote escape (<code>'=</code>) at the start of a string tells the parser to treat
302
- the rest of the string (including the <code>=</code>) as literal content.
323
+ The wrapper syntax eliminates ambiguity: <code>=(expr)</code> is always an expression, while strings without
324
+ the wrapper pattern are always literals.
303
325
  </p>
304
326
 
305
327
  <h3 id="JPRX-anatomy">Anatomy of a Path</h3>
@@ -723,11 +745,11 @@ const cdomString = `{
723
745
  children: [
724
746
  { h3: "Shopping Cart" },
725
747
  { ul: {
726
- children: =map(/store/cart/items, { li: { children: [_/name, " - ", currency(_/price)] } })
748
+ children: =(map(/store/cart/items, { li: { children: [_/name, " - ", currency(_/price)] } }))
727
749
  }},
728
750
  { p: {
729
751
  style: "font-weight: bold; margin-top: 1rem;",
730
- children: ["Total: ", =currency(sum(/store/cart/items...price))]
752
+ children: ["Total: ", =(currency(sum(/store/cart/items...price)))]
731
753
  }}
732
754
  ]
733
755
  }
@@ -772,10 +794,10 @@ const cdomString = `{
772
794
  onmount: "=signal(0, 'count')",
773
795
  "children": [
774
796
  { "h3": ["Standard JPRX Counter"] },
775
- { "p": { "children": ["Count: ", "=/count"] }},
797
+ { "p": { "children": ["Count: ", "=(/count)"] }},
776
798
  { "div": { "children": [
777
- { "button": { "onclick": "=decrement(/count)", "children": ["-"] } },
778
- { "button": { "onclick": "=increment(/count)", "children": ["+"] } }
799
+ { "button": { "onclick": "=(decrement(/count))", "children": ["-"] } },
800
+ { "button": { "onclick": "=(increment(/count))", "children": ["+"] } }
779
801
  ]}}
780
802
  ]
781
803
  }
@@ -806,10 +828,10 @@ const cdomString = `{
806
828
  onmount: =signal(0, 'count'),
807
829
  children: [
808
830
  { h3: ["Compressed Counter"] },
809
- { p: { children: ["Count: ", =/count] }},
831
+ { p: { children: ["Count: ", =(/count)] }},
810
832
  { div: { children: [
811
- { button: { onclick: =decrement(/count), children: ["-"] } },
812
- { button: { onclick: =increment(/count), children: ["+"] } }
833
+ { button: { onclick: =(decrement(/count)), children: ["-"] } },
834
+ { button: { onclick: =(increment(/count)), children: ["+"] } }
813
835
  ]}}
814
836
  ]
815
837
  }
@@ -840,11 +862,11 @@ const cdomString = `{
840
862
  onmount: =signal(0, 'count'),
841
863
  children: [
842
864
  { h3: ["Operator Counter"] },
843
- { p: { children: ["Count: ", =/count] }},
865
+ { p: { children: ["Count: ", =(/count)] }},
844
866
  { div: { children: [
845
- // Prefix operators: =-- and =++
846
- { button: { onclick: =--/count, children: ["-"] } },
847
- { button: { onclick: =++/count, children: ["+"] } }
867
+ // Prefix operators: =(--/count) and =(++/count)
868
+ { button: { onclick: =(--/count), children: ["-"] } },
869
+ { button: { onclick: =(++/count), children: ["+"] } }
848
870
  ]}}
849
871
  ]
850
872
  }
@@ -533,42 +533,14 @@
533
533
  // Create a temporary container for Juris to render into
534
534
  const tempContainer = document.createElement('div');
535
535
 
536
- // Temporarily disable all console methods to prevent Juris logging from affecting benchmark
537
- const originalConsole = {
538
- log: console.log,
539
- dir: console.dir,
540
- warn: console.warn,
541
- error: console.error,
542
- info: console.info,
543
- debug: console.debug
544
- };
545
- const noop = () => { };
546
- console.log = noop;
547
- console.dir = noop;
548
- console.warn = noop;
549
- console.error = noop;
550
- console.info = noop;
551
- console.debug = noop;
552
-
553
- try {
554
- // Create a Juris instance with the oDOM structure as the layout
555
- const jurisInstance = new Juris({
556
- debug: false, // Disable logging for performance
557
- layout: onode
558
- });
559
-
560
- // Render to the temporary container
561
- jurisInstance.render(tempContainer);
562
- } finally {
563
- // Restore original console methods
564
- console.log = originalConsole.log;
565
- console.dir = originalConsole.dir;
566
- console.warn = originalConsole.warn;
567
- console.error = originalConsole.error;
568
- console.info = originalConsole.info;
569
- console.debug = originalConsole.debug;
570
- }
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
+ });
571
541
 
542
+ // Render to the temporary container
543
+ jurisInstance.render(tempContainer);
572
544
  // Return the first child (the actual rendered content)
573
545
  return tempContainer.firstChild || tempContainer;
574
546
  }
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 = '';