lightview 2.0.9 → 2.2.1

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.
Files changed (115) hide show
  1. package/build-bundles.mjs +105 -0
  2. package/build.js +236 -46
  3. package/components/actions/button.js +16 -3
  4. package/components/actions/swap.js +26 -3
  5. package/components/daisyui.js +1 -1
  6. package/components/data-display/alert.js +13 -3
  7. package/components/data-display/avatar.js +25 -1
  8. package/components/data-display/badge.js +11 -3
  9. package/components/data-display/chart.js +22 -5
  10. package/components/data-display/countdown.js +3 -2
  11. package/components/data-display/kbd.js +9 -3
  12. package/components/data-display/loading.js +11 -3
  13. package/components/data-display/progress.js +11 -3
  14. package/components/data-display/radial-progress.js +12 -3
  15. package/components/data-display/tooltip.js +17 -0
  16. package/components/data-input/checkbox.js +23 -1
  17. package/components/data-input/input.js +24 -1
  18. package/components/data-input/radio.js +37 -2
  19. package/components/data-input/select.js +24 -1
  20. package/components/data-input/toggle.js +21 -1
  21. package/components/layout/divider.js +21 -1
  22. package/components/layout/indicator.js +14 -0
  23. package/components/navigation/breadcrumbs.js +42 -2
  24. package/components/navigation/tabs.js +291 -16
  25. package/docs/api/elements.html +125 -49
  26. package/docs/api/hypermedia.html +29 -2
  27. package/docs/api/index.html +6 -2
  28. package/docs/api/nav.html +18 -4
  29. package/docs/assets/js/examplify.js +1 -1
  30. package/docs/cdom-nav.html +55 -0
  31. package/docs/cdom.html +792 -0
  32. package/docs/components/alert.html +8 -8
  33. package/docs/components/avatar.html +24 -54
  34. package/docs/components/badge.html +69 -14
  35. package/docs/components/breadcrumbs.html +95 -29
  36. package/docs/components/button.html +78 -92
  37. package/docs/components/chart-area.html +3 -3
  38. package/docs/components/chart-bar.html +4 -181
  39. package/docs/components/chart-column.html +4 -189
  40. package/docs/components/chart-line.html +3 -3
  41. package/docs/components/chart-pie.html +112 -166
  42. package/docs/components/chart.html +11 -13
  43. package/docs/components/checkbox.html +48 -28
  44. package/docs/components/collapse.html +6 -6
  45. package/docs/components/component-nav.html +1 -1
  46. package/docs/components/countdown.html +12 -12
  47. package/docs/components/divider.html +65 -21
  48. package/docs/components/dropdown.html +1 -1
  49. package/docs/components/file-input.html +4 -4
  50. package/docs/components/footer.html +11 -11
  51. package/docs/components/indicator.html +85 -31
  52. package/docs/components/input.html +45 -29
  53. package/docs/components/join.html +4 -4
  54. package/docs/components/kbd.html +67 -28
  55. package/docs/components/loading.html +96 -92
  56. package/docs/components/pagination.html +4 -4
  57. package/docs/components/progress.html +50 -7
  58. package/docs/components/radial-progress.html +32 -12
  59. package/docs/components/radio.html +42 -31
  60. package/docs/components/select.html +48 -59
  61. package/docs/components/swap.html +183 -100
  62. package/docs/components/tabs.html +146 -278
  63. package/docs/components/toggle.html +44 -25
  64. package/docs/components/tooltip.html +71 -31
  65. package/docs/getting-started/index.html +8 -6
  66. package/docs/index.html +1 -1
  67. package/docs/syntax-nav.html +10 -0
  68. package/docs/syntax.html +8 -6
  69. package/index.html +2 -2
  70. package/jprx/LICENSE +21 -0
  71. package/jprx/README.md +130 -0
  72. package/jprx/helpers/array.js +75 -0
  73. package/jprx/helpers/compare.js +26 -0
  74. package/jprx/helpers/conditional.js +34 -0
  75. package/jprx/helpers/datetime.js +54 -0
  76. package/jprx/helpers/format.js +20 -0
  77. package/jprx/helpers/logic.js +24 -0
  78. package/jprx/helpers/lookup.js +25 -0
  79. package/jprx/helpers/math.js +34 -0
  80. package/jprx/helpers/network.js +41 -0
  81. package/jprx/helpers/state.js +80 -0
  82. package/jprx/helpers/stats.js +39 -0
  83. package/jprx/helpers/string.js +49 -0
  84. package/jprx/index.js +69 -0
  85. package/jprx/package.json +24 -0
  86. package/jprx/parser.js +1517 -0
  87. package/lightview-all.js +3785 -0
  88. package/lightview-cdom.js +2128 -0
  89. package/lightview-router.js +179 -208
  90. package/lightview-x.js +1435 -1608
  91. package/lightview.js +613 -766
  92. package/lightview.js.bak +1 -0
  93. package/package.json +10 -3
  94. package/src/lightview-all.js +10 -0
  95. package/src/lightview-cdom.js +457 -0
  96. package/src/lightview-router.js +210 -0
  97. package/src/lightview-x.js +1630 -0
  98. package/src/lightview.js +705 -0
  99. package/src/reactivity/signal.js +133 -0
  100. package/src/reactivity/state.js +217 -0
  101. package/{watch.js → start-dev.js} +2 -1
  102. package/tests/cdom/fixtures/helpers.cdomc +62 -0
  103. package/tests/cdom/fixtures/user.cdom +14 -0
  104. package/tests/cdom/fixtures/user.cdomc +12 -0
  105. package/tests/cdom/fixtures/user.odom +18 -0
  106. package/tests/cdom/fixtures/user.vdom +11 -0
  107. package/tests/cdom/helpers.test.js +121 -0
  108. package/tests/cdom/loader.test.js +125 -0
  109. package/tests/cdom/parser.test.js +179 -0
  110. package/tests/cdom/reactivity.test.js +186 -0
  111. package/tests/text-tag.test.js +77 -0
  112. package/vite.config.mjs +52 -0
  113. package/wrangler.toml +0 -3
  114. package/components/data-display/skeleton.js +0 -66
  115. package/docs/components/skeleton.html +0 -447
@@ -71,7 +71,7 @@
71
71
  <!-- Tabs -->
72
72
  <script>
73
73
  globalThis.switchSyntaxTab = (tabId) => {
74
- const tabs = ['tagged', 'vdom', 'object'];
74
+ const tabs = ['tagged', 'vdom', 'object', 'html'];
75
75
  tabs.forEach(t => {
76
76
  const tabEl = document.getElementById(`tab-btn-${t}`);
77
77
  const contentEl = document.getElementById(`syntax-${t}`);
@@ -93,6 +93,8 @@
93
93
  <button id="tab-btn-object" role="tab" class="syntax-tab"
94
94
  onclick="switchSyntaxTab('object')">Object
95
95
  DOM</button>
96
+ <button id="tab-btn-html" role="tab" class="syntax-tab"
97
+ onclick="switchSyntaxTab('html')">HTML</button>
96
98
  </div>
97
99
 
98
100
  <!-- Tagged Syntax -->
@@ -196,16 +198,47 @@ const tooltips = {
196
198
  div: {
197
199
  style: 'display: flex; flex-wrap: wrap; gap: 1rem',
198
200
  children: [
199
- { Tooltip: { tip: 'Tooltip on top', position: 'top', children: [{ Button: { children: ['Top'] } }] } },
200
- { Tooltip: { tip: 'Tooltip on bottom', position: 'bottom', children: [{ Button: { children: ['Bottom'] } }] } },
201
- { Tooltip: { tip: 'Tooltip on left', position: 'left', children: [{ Button: { children: ['Left'] } }] } },
202
- { Tooltip: { tip: 'Tooltip on right', position: 'right', children: [{ Button: { children: ['Right'] } }] } }
201
+ { Tooltip: { tip: 'Tooltip on top', position: 'top', children: [ { Button: { children: ['Top'] } } ] } },
202
+ { Tooltip: { tip: 'Tooltip on bottom', position: 'bottom', children: [ { Button: { children: ['Bottom'] } } ] } },
203
+ { Tooltip: { tip: 'Tooltip on left', position: 'left', children: [ { Button: { children: ['Left'] } } ] } },
204
+ { Tooltip: { tip: 'Tooltip on right', position: 'right', children: [ { Button: { children: ['Right'] } } ] } }
203
205
  ]
204
206
  }
205
207
  };
206
208
 
207
209
  $('#demo').content(tooltips);</code></pre>
208
210
  </div>
211
+
212
+ <!-- HTML Syntax -->
213
+ <div id="syntax-html" style="display: none;">
214
+ <pre><script>
215
+ examplify(document.currentScript.nextElementSibling, {
216
+ at: document.currentScript.parentElement,
217
+ scripts: ['/lightview.js', '/lightview-x.js'],
218
+ styles: ['https://cdn.jsdelivr.net/npm/daisyui@3.9.4/dist/full.min.css'],
219
+ type: 'module',
220
+ language: 'html',
221
+ minHeight: 100,
222
+ html: '<div id="demo" class="p-8"></div>'
223
+ });
224
+ </script><code contenteditable="true" class="language-html">&lt;script type="module" src="/components/data-display/tooltip.js"&gt;&lt;/script&gt;
225
+ &lt;script type="module" src="/components/actions/button.js"&gt;&lt;/script&gt;
226
+
227
+ &lt;div style="display: flex; flex-wrap: wrap; gap: 1rem"&gt;
228
+ &lt;lv-tooltip tip="Tooltip on top" position="top"&gt;
229
+ &lt;lv-button&gt;Top&lt;/lv-button&gt;
230
+ &lt;/lv-tooltip&gt;
231
+ &lt;lv-tooltip tip="Tooltip on bottom" position="bottom"&gt;
232
+ &lt;lv-button&gt;Bottom&lt;/lv-button&gt;
233
+ &lt;/lv-tooltip&gt;
234
+ &lt;lv-tooltip tip="Tooltip on left" position="left"&gt;
235
+ &lt;lv-button&gt;Left&lt;/lv-button&gt;
236
+ &lt;/lv-tooltip&gt;
237
+ &lt;lv-tooltip tip="Tooltip on right" position="right"&gt;
238
+ &lt;lv-button&gt;Right&lt;/lv-button&gt;
239
+ &lt;/lv-tooltip&gt;
240
+ &lt;/div&gt;</code></pre>
241
+ </div>
209
242
  </div>
210
243
  </div>
211
244
 
@@ -382,37 +415,44 @@ $('#demo').content(tooltips);</code></pre>
382
415
  </table>
383
416
  </div>
384
417
 
385
- <!-- Positions -->
386
- <h2 class="text-xl font-bold" style="margin-bottom: 1rem;">Positions</h2>
418
+ <!-- Tooltip Gallery -->
419
+ <h2 class="text-xl font-bold" style="margin-bottom: 1rem;">Tooltip Gallery</h2>
420
+ <p class="text-sm" style="opacity: 0.7; margin-bottom: 1.5rem;">
421
+ Live examples using <code>&lt;lv-tooltip&gt;</code> custom elements.
422
+ </p>
423
+
424
+ <script type="module" src="/components/data-display/tooltip.js"></script>
425
+ <script type="module" src="/components/actions/button.js"></script>
426
+
427
+ <h3 class="text-lg font-semibold" style="margin-bottom: 0.75rem;">Positions</h3>
387
428
  <div style="display: flex; flex-wrap: wrap; gap: 1rem; padding: 2rem; margin-bottom: 2rem;">
388
- <div class="tooltip tooltip-top" data-tip="Top"><button class="btn">Top</button></div>
389
- <div class="tooltip tooltip-bottom" data-tip="Bottom"><button class="btn">Bottom</button></div>
390
- <div class="tooltip tooltip-left" data-tip="Left"><button class="btn">Left</button></div>
391
- <div class="tooltip tooltip-right" data-tip="Right"><button class="btn">Right</button></div>
429
+ <lv-tooltip tip="Top" position="top"><lv-button>Top</lv-button></lv-tooltip>
430
+ <lv-tooltip tip="Bottom" position="bottom"><lv-button>Bottom</lv-button></lv-tooltip>
431
+ <lv-tooltip tip="Left" position="left"><lv-button>Left</lv-button></lv-tooltip>
432
+ <lv-tooltip tip="Right" position="right"><lv-button>Right</lv-button></lv-tooltip>
392
433
  </div>
393
434
 
394
- <!-- Colors -->
395
- <h2 class="text-xl font-bold" style="margin-bottom: 1rem;">Colors</h2>
396
- <div class="example-flex" style="margin-bottom: 2rem;">
397
- <div class="tooltip tooltip-primary" data-tip="Primary"><button class="btn">Primary</button>
398
- </div>
399
- <div class="tooltip tooltip-secondary" data-tip="Secondary"><button
400
- class="btn">Secondary</button></div>
401
- <div class="tooltip tooltip-accent" data-tip="Accent"><button class="btn">Accent</button></div>
402
- <div class="tooltip tooltip-info" data-tip="Info"><button class="btn">Info</button></div>
403
- <div class="tooltip tooltip-success" data-tip="Success"><button class="btn">Success</button>
404
- </div>
405
- <div class="tooltip tooltip-warning" data-tip="Warning"><button class="btn">Warning</button>
406
- </div>
407
- <div class="tooltip tooltip-error" data-tip="Error"><button class="btn">Error</button></div>
435
+ <h3 class="text-lg font-semibold" style="margin-bottom: 0.75rem;">Colors</h3>
436
+ <div style="display: flex; flex-wrap: wrap; gap: 1rem; margin-bottom: 2rem;">
437
+ <lv-tooltip tip="Primary" color="primary"><lv-button
438
+ color="primary">Primary</lv-button></lv-tooltip>
439
+ <lv-tooltip tip="Secondary" color="secondary"><lv-button
440
+ color="secondary">Secondary</lv-button></lv-tooltip>
441
+ <lv-tooltip tip="Accent" color="accent"><lv-button
442
+ color="accent">Accent</lv-button></lv-tooltip>
443
+ <lv-tooltip tip="Info" color="info"><lv-button color="info">Info</lv-button></lv-tooltip>
444
+ <lv-tooltip tip="Success" color="success"><lv-button
445
+ color="success">Success</lv-button></lv-tooltip>
446
+ <lv-tooltip tip="Warning" color="warning"><lv-button
447
+ color="warning">Warning</lv-button></lv-tooltip>
448
+ <lv-tooltip tip="Error" color="error"><lv-button color="error">Error</lv-button></lv-tooltip>
408
449
  </div>
409
450
 
410
- <!-- Force Open -->
411
- <h2 class="text-xl font-bold" style="margin-bottom: 1rem;">Force Open</h2>
412
- <div style="margin-bottom: 2rem;">
413
- <div class="tooltip tooltip-open tooltip-primary" data-tip="Always visible">
414
- <button class="btn btn-primary">Always Open</button>
415
- </div>
451
+ <h3 class="text-lg font-semibold" style="margin-bottom: 0.75rem;">Force Open</h3>
452
+ <div style="margin-bottom: 2rem; padding: 1rem;">
453
+ <lv-tooltip tip="Always visible" open color="primary">
454
+ <lv-button color="primary">Always Open</lv-button>
455
+ </lv-tooltip>
416
456
  </div>
417
457
  </div>
418
458
  </div>
@@ -352,7 +352,7 @@ $('#app').content(App);`,
352
352
  // Fetch source code for display
353
353
  try {
354
354
  const text = await fetch(file).then(r => r.text());
355
- sourceCode.value = text;
355
+ sourceCode.value = new String(text);
356
356
  } catch (e) {
357
357
  sourceCode.value = 'Error loading source';
358
358
  }
@@ -444,7 +444,7 @@ $('#app').content(App);`,
444
444
  class: 'source-code', children: [
445
445
  () => sourceCode.value
446
446
  ]
447
- }
447
+ }
448
448
  }
449
449
  ]
450
450
  }
@@ -453,9 +453,11 @@ $('#app').content(App);`,
453
453
  }
454
454
  },
455
455
 
456
- // Styles
457
- { style: { children: [
458
- \`.reviews-container { width: 100%; box-sizing: border-box; font-family: system-ui, sans-serif; }
456
+ // Styles
457
+ {
458
+ style: {
459
+ children: [
460
+ \`.reviews-container { width: 100%; box-sizing: border-box; font-family: system-ui, sans-serif; }
459
461
  .reviews-header { margin-bottom: 1rem; }
460
462
  .reviews-title { margin: 0 0 0.5rem; }
461
463
  .reviews-subtitle { color: #6b7280; margin: 0 0 0.75rem; font-size: 0.9em; }
@@ -676,7 +678,7 @@ $('#app').content(App);`,
676
678
  <li style="margin-bottom: 0.75rem;"><strong>Context</strong> — Gates are standard functions. They have access to <code>this</code> (the element) and can accept arguments like <code>event</code>.</li>
677
679
  <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>
678
680
  </ul>
679
- <p><strong>Try it:</strong> Click the blue button rapidly, or type quickly into the search box to see the limiters in action!</p>`
681
+ <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>`
680
682
  }
681
683
  };
682
684
 
package/docs/index.html CHANGED
@@ -53,7 +53,7 @@
53
53
  DOM diffing needed. As a bonus, session and local storage are also supported.
54
54
  </p>
55
55
  </a>
56
- <a href="./syntax.html" class="feature-card" style="text-decoration: none; color: inherit;">
56
+ <a href="./api/elements.html" class="feature-card" style="text-decoration: none; color: inherit;">
57
57
  <div class="feature-icon">
58
58
  <svg viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="2">
59
59
  <rect x="3" y="3" width="18" height="18" rx="2" />
@@ -0,0 +1,10 @@
1
+ <nav class="docs-nav">
2
+ <div class="docs-nav-section">
3
+ <div class="docs-nav-title">Syntaxes</div>
4
+ <a href="#overview" class="docs-nav-link">Overview</a>
5
+ <a href="#tagged-api" class="docs-nav-link">Tagged API</a>
6
+ <a href="#vdom" class="docs-nav-link">vDOM Syntax</a>
7
+ <a href="#object-dom" class="docs-nav-link">Object DOM</a>
8
+ <a href="#custom-elements" class="docs-nav-link">Custom Elements</a>
9
+ </div>
10
+ </nav>
package/docs/syntax.html CHANGED
@@ -6,8 +6,10 @@
6
6
  }
7
7
  </script>
8
8
 
9
- <div class="section">
10
- <div class="section-content" style="max-width: 900px;">
9
+ <div class="docs-layout">
10
+ <aside class="docs-sidebar" src="/docs/syntax-nav.html"></aside>
11
+
12
+ <main class="docs-content">
11
13
  <h1>Syntaxes</h1>
12
14
  <p class="text-secondary" style="font-size: 1.125rem;">
13
15
  One reactive engine, four ways to build your UI.
@@ -19,7 +21,7 @@
19
21
  powers it all.
20
22
  </p>
21
23
 
22
- <h2>Comparison at a Glance</h2>
24
+ <h2 id="overview">Comparison at a Glance</h2>
23
25
  <div class="overflow-x-auto">
24
26
  <table class="table w-full">
25
27
  <thead>
@@ -44,7 +46,7 @@
44
46
  <td>Core</td>
45
47
  </tr>
46
48
  <tr>
47
- <td><strong>Object DOM</strong></td>
49
+ <td><strong>oDOM (Object DOM)</strong></td>
48
50
  <td><code>{ div: { ... } }</code></td>
49
51
  <td>Concise templates, config files</td>
50
52
  <td>Lightview X</td>
@@ -102,7 +104,7 @@ $('body').content(app);</code></pre>
102
104
  <p><strong>Pros:</strong> Unambiguous, easy to serialize/deserialize as JSON, perfect for programmatic
103
105
  generation.</p>
104
106
 
105
- <h2 id="object-dom">3. Object DOM</h2>
107
+ <h2 id="object-dom">3. oDOM</h2>
106
108
  <p>
107
109
  A more compact JSON representation provided by <strong>Lightview X</strong>. It uses the tag name as a key
108
110
  to reduce verbosity.
@@ -140,5 +142,5 @@ $('body').content(app);</code></pre>
140
142
  <p><strong>Pros:</strong> Familiar HTML syntax, framework-agnostic, excellent for progressive enhancement of
141
143
  server-rendered pages.</p>
142
144
 
143
- </div>
145
+ </main>
144
146
  </div>
package/index.html CHANGED
@@ -32,11 +32,11 @@
32
32
  <div class="nav-links">
33
33
  <a href="/docs/" class="nav-link">Home</a>
34
34
  <a href="/docs/getting-started/" class="nav-link">Get Started</a>
35
- <a href="/docs/syntax.html" class="nav-link">Syntax</a>
36
35
  <a href="/docs/api/" class="nav-link">API</a>
37
36
  <a href="/docs/styles/" class="nav-link">Styles</a>
38
37
  <a href="/docs/components/" class="nav-link">Components</a>
39
38
  <a href="/docs/router/" class="nav-link">Router</a>
39
+ <a href="/docs/cdom.html" class="nav-link">cDOM</a>
40
40
  <!--a href="/docs/playground/" class="nav-link">Playground</a>-->
41
41
 
42
42
  <a href="/docs/about/" class="nav-link">About</a>
@@ -278,13 +278,13 @@
278
278
  appRouter.use('/docs', '/docs/index.html');
279
279
  appRouter.use('/docs/index.html');
280
280
  appRouter.use('/docs/getting-started', '/docs/getting-started/index.html');
281
- appRouter.use('/docs/syntax.html');
282
281
  appRouter.use('/docs/about/', '/docs/about.html');
283
282
  appRouter.use('/docs/styles/', '/docs/styles/index.html');
284
283
  appRouter.use('/docs/api/', '/docs/api/index.html');
285
284
  appRouter.use('/docs/components/', '/docs/components/index.html');
286
285
  appRouter.use('/docs/examples/', '/docs/examples/index.html');
287
286
  appRouter.use('/docs/router/', '/docs/router.html');
287
+ appRouter.use('/docs/cdom.html');
288
288
 
289
289
  // Wildcard Routes - path replacement with automatic fetch
290
290
  appRouter.use('/docs/api/*');
package/jprx/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 - 2026 Simon Y. Blackwell, AnyWhichWay LLC
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/jprx/README.md ADDED
@@ -0,0 +1,130 @@
1
+ # JPRX (JSON Reactive Path eXpressions)
2
+
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
+
5
+ ## Overview
6
+
7
+ 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.
8
+
9
+ ### Why JPRX?
10
+
11
+ - **Declarative Power**: Define relationships between data points as easily as writing an Excel formula.
12
+ - **Security**: JPRX strictly avoids `eval()`. Expressions are handled by a custom high-performance Pratt parser and a registry of pre-defined helpers, making it safe for dynamic content.
13
+ - **Portability**: Because JPRX expressions are strings within JSON structures, they are easily serialized, stored, and sent over the wire.
14
+ - **Platform Agnostic**: While Lightview is the first implementation, JPRX can be used in any environment that manages reactive state.
15
+
16
+ ## Syntax & Features
17
+
18
+ JPRX extends the base JSON Pointer syntax with:
19
+
20
+ | Feature | Syntax | Description |
21
+ |---------|--------|-------------|
22
+ | **Global Path** | `$/user/name` | Access global state via an absolute path. |
23
+ | **Relative Path** | `./count` | Access properties relative to the current context. |
24
+ | **Parent Path** | `../id` | Traverse up the state hierarchy. |
25
+ | **Functions** | `$sum(/items...price)` | Call registered core helpers. |
26
+ | **Explosion** | `/items...name` | Extract a property from every object in an array (spread). |
27
+ | **Operators** | `$++/count`, `$/a + $/b` | Familiar JS-style prefix, postfix, and infix operators. |
28
+ | **Placeholders** | `_` (item), `$event` | Context-aware placeholders for iteration and interaction. |
29
+
30
+ ## Human & AI Utility
31
+
32
+ JPRX is uniquely positioned to bridge the gap between human developers and AI coding assistants:
33
+
34
+ ### For Humans: "The Excel Paradigm"
35
+ Humans are often familiar with the "recalculation" model of spreadsheets. JPRX brings this to UI development. Instead of writing complex "glue code" (event listeners, state updates, DOM manipulation), developers specify the *formula* for a UI element once, and it stays updated forever.
36
+
37
+ ### For AI: Structured & Concise
38
+ Large Language Models (LLMs) are exceptionally good at generating structured data (JSON) and formulaic expressions. They are often prone to errors when generating large blocks of imperative JavaScript logic. JPRX provides a high-level, declarative "target" for AI to aim at, resulting in:
39
+ - **Higher Accuracy**: Less boilerplate means fewer places for the AI to hallucinate.
40
+ - **Safety**: AI can generate UI logic that remains sandboxed and secure.
41
+ - **Compactness**: Entire interactive components can be described in a few lines of JSON.
42
+
43
+ ## Operators
44
+
45
+ JPRX supports a wide range of operators that provide a more concise and familiar syntax than function calls.
46
+
47
+ ### Arithmetic & Logic (Infix)
48
+ Infix operators require surrounding whitespace in JPRX to avoid ambiguity with path separators.
49
+
50
+ - **Arithmetic**: `+`, `-`, `*`, `/`, `mod`, `pow`
51
+ - **Comparison**: `>`, `<`, `>=`, `<=`, `==`, `!=`
52
+ - **Logic**: `&&`, `||`
53
+
54
+ *Example:* `$/a + $/b * 10 > $/threshold`
55
+
56
+ ### Mutation & Unary (Prefix/Postfix)
57
+ These operators are typically used in event handlers or for immediate state transformation.
58
+
59
+ - **Increment**: `$++/count` (prefix) or `$/count++` (postfix)
60
+ - **Decrement**: `$--/count` (prefix) or `$/count--` (postfix)
61
+ - **Toggle**: `$!!/enabled` (logical NOT/toggle)
62
+
63
+ ## Helper Functions
64
+
65
+ JPRX includes a comprehensive library of built-in helpers. For security, only registered helpers are available—there is no access to the global JavaScript environment.
66
+
67
+ ### Math
68
+ `add`, `sub`, `mul`, `div`, `mod`, `pow`, `sqrt`, `abs`, `round`, `ceil`, `floor`, `min`, `max`
69
+
70
+ ### Stats
71
+ `sum`, `avg`, `min`, `max`, `median`, `stdev`, `var`
72
+
73
+ ### String
74
+ `upper`, `lower`, `trim`, `capitalize`, `titleCase`, `contains`, `startsWith`, `endsWith`, `replace`, `split`, `join`, `concat`, `len`, `default`
75
+
76
+ ### Array
77
+ `count`, `map`, `filter`, `find`, `unique`, `sort`, `reverse`, `first`, `last`, `slice`, `flatten`, `join`, `len`
78
+
79
+ ### Logic & Comparison
80
+ `if`, `and`, `or`, `not`, `eq`, `neq`, `gt`, `lt`, `gte`, `lte`, `between`, `in`
81
+
82
+ ### Formatting
83
+ `number`, `currency`, `percent`, `thousands`
84
+
85
+ ### DateTime
86
+ `now`, `today`, `date`, `formatDate`, `year`, `month`, `day`, `weekday`, `addDays`, `dateDiff`
87
+
88
+ ### Lookup
89
+ `lookup`, `vlookup`, `index`, `match`
90
+
91
+ ### State Mutation
92
+ `set`, `increment`, `decrement`, `toggle`, `push`, `pop`, `assign`, `clear`
93
+
94
+ ### Network
95
+ `fetch(url, options?)` - *Auto-serializes JSON bodies and handles content-types.*
96
+
97
+ ## Example
98
+
99
+ A simple reactive counter described in JPRX syntax:
100
+
101
+ ```json
102
+ {
103
+ "div": {
104
+ "cdom-state": { "count": 0 },
105
+ "children": [
106
+ { "h2": "Counter" },
107
+ { "p": ["Current Count: ", "$/count"] },
108
+ { "button": { "onclick": "$increment(/count)", "children": ["+"] } },
109
+ { "button": { "onclick": "$decrement(/count)", "children": ["-"] } }
110
+ ]
111
+ }
112
+ }
113
+ ```
114
+
115
+ ## Reference Implementation: Lightview
116
+
117
+ JPRX was originally developed for [Lightview](https://github.com/anywhichway/lightview) to power its **Computational DOM (cDOM)**. Lightview serves as the primary example of how a UI library can hydrate JPRX expressions into a live, reactive interface.
118
+
119
+ If you are building a UI library and want to support reactive JSON structures, this parser provides the foundation.
120
+
121
+ ## Getting Started
122
+
123
+ The JPRX package contains:
124
+ 1. `parser.js`: The core Pratt parser and path resolution logic.
125
+ 2. `helpers/`: A comprehensive library of math, logic, string, array, formatting, and state helpers.
126
+
127
+ To use JPRX, you typically register your state-management primitives (like Signals or Proxies) with the parser's registry, and then call `hydrate()` or `resolveExpression()` to activate the logic.
128
+
129
+ ---
130
+ © 2026 Simon Y. Blackwell, AnyWhichWay LLC. Licensed under MIT.
@@ -0,0 +1,75 @@
1
+ /**
2
+ * cdom ARRAY HELPERS
3
+ */
4
+
5
+ export const count = (...args) => args.length;
6
+
7
+ export const filter = (arr, predicate) => {
8
+ if (!Array.isArray(arr)) return [];
9
+ if (typeof predicate === 'function' && predicate.isLazy) {
10
+ return arr.filter(item => predicate.resolve(item));
11
+ }
12
+ return arr.filter(item => !!item);
13
+ };
14
+
15
+ export const map = (arr, transform) => {
16
+ if (!Array.isArray(arr)) return [];
17
+ if (typeof transform === 'string') {
18
+ return arr.map(item => (item && typeof item === 'object') ? item[transform] : item);
19
+ }
20
+ // Check for LazyValue (has isLazy property and resolve method)
21
+ if (transform && transform.isLazy && typeof transform.resolve === 'function') {
22
+ return arr.map(item => transform.resolve(item));
23
+ }
24
+ // If it's a plain function
25
+ if (typeof transform === 'function') {
26
+ return arr.map(transform);
27
+ }
28
+ return arr;
29
+ };
30
+
31
+ export const find = (arr, predicate) => {
32
+ if (!Array.isArray(arr)) return undefined;
33
+ if (predicate && predicate.isLazy) {
34
+ return arr.find(item => predicate.resolve(item));
35
+ }
36
+ return arr.find(item => !!item);
37
+ };
38
+
39
+ export const unique = (arr) => Array.isArray(arr) ? [...new Set(arr)] : [];
40
+
41
+ export const sort = (arr, order = 'asc') => {
42
+ if (!Array.isArray(arr)) return [];
43
+ const sorted = [...arr];
44
+ sorted.sort((a, b) => {
45
+ if (a < b) return order === 'asc' ? -1 : 1;
46
+ if (a > b) return order === 'asc' ? 1 : -1;
47
+ return 0;
48
+ });
49
+ return sorted;
50
+ };
51
+
52
+ export const reverse = (arr) => Array.isArray(arr) ? [...arr].reverse() : [];
53
+ export const first = (arr) => Array.isArray(arr) ? arr[0] : undefined;
54
+ export const last = (arr) => Array.isArray(arr) ? arr[arr.length - 1] : undefined;
55
+ export const slice = (arr, start, end) => Array.isArray(arr) ? arr.slice(start, end) : [];
56
+ export const flatten = (arr) => Array.isArray(arr) ? arr.flat(Infinity) : [];
57
+ export const join = (arr, sep = ',') => Array.isArray(arr) ? arr.join(String(sep)) : '';
58
+ export const length = (arg) => Array.isArray(arg) ? arg.length : (arg ? String(arg).length : 0);
59
+
60
+ export const registerArrayHelpers = (register) => {
61
+ register('count', count);
62
+ register('filter', filter, { lazyAware: true });
63
+ register('map', map, { lazyAware: true });
64
+ register('find', find, { lazyAware: true });
65
+ register('unique', unique);
66
+ register('sort', sort);
67
+ register('reverse', reverse);
68
+ register('first', first);
69
+ register('last', last);
70
+ register('slice', slice);
71
+ register('flatten', flatten);
72
+ register('join', join);
73
+ register('len', length);
74
+ register('length', length);
75
+ };
@@ -0,0 +1,26 @@
1
+ /**
2
+ * cdom COMPARISON HELPERS
3
+ */
4
+
5
+ export const gt = (a, b) => a > b;
6
+ export const lt = (a, b) => a < b;
7
+ export const gte = (a, b) => a >= b;
8
+ export const lte = (a, b) => a <= b;
9
+ export const neq = (a, b) => a !== b;
10
+ export const between = (val, min, max) => val >= min && val <= max;
11
+ export const contains = (arr, val) => Array.isArray(arr) && arr.includes(val);
12
+
13
+ export const registerCompareHelpers = (register) => {
14
+ register('gt', gt);
15
+ register('>', gt);
16
+ register('lt', lt);
17
+ register('<', lt);
18
+ register('gte', gte);
19
+ register('>=', gte);
20
+ register('lte', lte);
21
+ register('<=', lte);
22
+ register('neq', neq);
23
+ register('!=', neq);
24
+ register('between', between);
25
+ register('in', contains);
26
+ };
@@ -0,0 +1,34 @@
1
+ /**
2
+ * cdom CONDITIONAL AGGREGATE HELPERS
3
+ */
4
+
5
+ export const sumIf = (arr, predicate) => {
6
+ if (!Array.isArray(arr)) return 0;
7
+ const filtered = (predicate && predicate.isLazy)
8
+ ? arr.filter(item => predicate.resolve(item))
9
+ : arr;
10
+ return filtered.reduce((a, b) => a + (Number(b) || 0), 0);
11
+ };
12
+
13
+ export const countIf = (arr, predicate) => {
14
+ if (!Array.isArray(arr)) return 0;
15
+ if (predicate && predicate.isLazy) {
16
+ return arr.filter(item => predicate.resolve(item)).length;
17
+ }
18
+ return arr.filter(item => !!item).length;
19
+ };
20
+
21
+ export const avgIf = (arr, predicate) => {
22
+ if (!Array.isArray(arr)) return 0;
23
+ const filtered = (predicate && predicate.isLazy)
24
+ ? arr.filter(item => predicate.resolve(item))
25
+ : arr;
26
+ if (filtered.length === 0) return 0;
27
+ return filtered.reduce((a, b) => a + (Number(b) || 0), 0) / filtered.length;
28
+ };
29
+
30
+ export const registerConditionalHelpers = (register) => {
31
+ register('sumIf', sumIf);
32
+ register('countIf', countIf);
33
+ register('avgIf', avgIf);
34
+ };
@@ -0,0 +1,54 @@
1
+ /**
2
+ * cdom DATE/TIME HELPERS
3
+ */
4
+
5
+ export const now = () => new Date().getTime();
6
+ export const today = () => {
7
+ const d = new Date();
8
+ d.setHours(0, 0, 0, 0);
9
+ return d.getTime();
10
+ };
11
+
12
+ export const date = (val) => new Date(val).getTime();
13
+
14
+ export const formatDate = (val, format) => {
15
+ const d = new Date(val);
16
+ if (isNaN(d.getTime())) return '';
17
+
18
+ // Minimal formatter, can be expanded
19
+ const options = { year: 'numeric', month: '2-digit', day: '2-digit' };
20
+ if (format === 'long') options.month = 'long';
21
+ return d.toLocaleDateString(undefined, options);
22
+ };
23
+
24
+ export const year = (val) => new Date(val).getFullYear();
25
+ export const month = (val) => new Date(val).getMonth() + 1;
26
+ export const day = (val) => new Date(val).getDate();
27
+ export const weekday = (val) => new Date(val).getDay();
28
+
29
+ export const addDays = (val, days) => {
30
+ const d = new Date(val);
31
+ d.setDate(d.getDate() + Number(days));
32
+ return d.getTime();
33
+ };
34
+
35
+ export const dateDiff = (d1, d2, unit = 'days') => {
36
+ const diff = Math.abs(new Date(d1) - new Date(d2));
37
+ if (unit === 'seconds') return diff / 1000;
38
+ if (unit === 'minutes') return diff / (1000 * 60);
39
+ if (unit === 'hours') return diff / (1000 * 60 * 60);
40
+ return diff / (1000 * 60 * 60 * 24);
41
+ };
42
+
43
+ export const registerDateTimeHelpers = (register) => {
44
+ register('now', now);
45
+ register('today', today);
46
+ register('date', date);
47
+ register('formatDate', formatDate);
48
+ register('year', year);
49
+ register('month', month);
50
+ register('day', day);
51
+ register('weekday', weekday);
52
+ register('addDays', addDays);
53
+ register('dateDiff', dateDiff);
54
+ };